#!/bin/sh # spec: one-line installer. # # Hosted at: https://spec.lightreach.io/install.sh # Source: spec/frontend/public/install.sh in the lightreach.io repo. # Docs: https://spec.lightreach.io/install # # Usage: # curl -LsSf https://spec.lightreach.io/install.sh | sh # # What it does: # 1. Ensures `uv` (https://docs.astral.sh/uv) is installed. # 2. Installs the `spec` CLI as an isolated tool via `uv tool install`, # so it never collides with the system Python and you don't have # to think about pyenv / pipx / virtualenv plumbing. # # Knobs (env vars): # SPEC_REF git ref to install (default: main) # SPEC_REPO git URL of the CLI source (default: official mirror) # SPEC_NO_PATH if "1", skip the post-install PATH suggestion # SPEC_NO_SHELL_INTEGRATION if "1", skip wiring `git init` → `spec init` # into the user's shell rc file set -eu if [ -t 1 ]; then GREEN="$(printf '\033[32m')" AMBER="$(printf '\033[33m')" DIM="$(printf '\033[2m')" BOLD="$(printf '\033[1m')" RESET="$(printf '\033[0m')" else GREEN=""; AMBER=""; DIM=""; BOLD=""; RESET="" fi step() { printf '%s▸%s %s\n' "$BOLD" "$RESET" "$*"; } ok() { printf '%s✓%s %s\n' "$GREEN" "$RESET" "$*"; } warn() { printf '%s!%s %s\n' "$AMBER" "$RESET" "$*" >&2; } note() { printf '%s%s%s\n' "$DIM" "$*" "$RESET"; } die() { printf 'error: %s\n' "$*" >&2; exit 1; } SPEC_REPO="${SPEC_REPO:-https://github.com/Unit237/specforge-cli.git}" SPEC_REF="${SPEC_REF:-main}" command -v curl >/dev/null 2>&1 || die "curl is required." step "installing spec" # 1. Ensure uv is available: it manages an isolated Python for spec # without touching whatever the system already has. if ! command -v uv >/dev/null 2>&1; then step "uv not found; installing from astral.sh/uv" curl -LsSf https://astral.sh/uv/install.sh | sh # uv's installer drops the binary at $HOME/.local/bin/uv. It edits the # user's shell rc to add that to PATH on next sessions, but for the # rest of *this* script we have to add it ourselves. export PATH="$HOME/.local/bin:$PATH" command -v uv >/dev/null 2>&1 \ || die "uv installed but not on PATH; cannot continue." fi step "uv tool install spec ($SPEC_REPO @ $SPEC_REF)" # --force makes re-running this script a clean upgrade, not a no-op. uv tool install --force "git+${SPEC_REPO}@${SPEC_REF}" # 2. Find the just-installed `spec` binary even if uv hasn't put # $HOME/.local/bin on PATH yet, needed for the shell-integration # step below and for the success message. SPEC_BIN="" if command -v spec >/dev/null 2>&1; then SPEC_BIN="$(command -v spec)" elif [ -x "$HOME/.local/bin/spec" ]; then SPEC_BIN="$HOME/.local/bin/spec" fi # 3. Wire `git init` → `spec init` into the user's interactive shell # so the moment they install spec, every future `git init` also # scaffolds a Spec bundle. Idempotent: re-running the installer # just refreshes the block in place. Skipped under SPEC_NO_SHELL_INTEGRATION=1. if [ -n "$SPEC_BIN" ] && [ "${SPEC_NO_SHELL_INTEGRATION:-0}" != "1" ]; then step "wiring spec into your shell (git init → spec init)" # `spec shell install` auto-detects $SHELL and writes the wrapper to the # right rc file. Failures are non-fatal: the install itself succeeded, # users can re-run `spec shell install` later if they hit a permission # or rc-file edge case. if "$SPEC_BIN" shell install; then : else warn "couldn't install shell integration automatically." note "re-run later with: spec shell install" fi fi # 4. Confirm the binary is reachable in this shell. uv normally drops # binaries at $HOME/.local/bin, which may or may not be on PATH yet. if [ -n "$SPEC_BIN" ] && command -v spec >/dev/null 2>&1; then printf '\n' ok "installed. try: spec --help" exit 0 fi if [ "${SPEC_NO_PATH:-0}" = "1" ]; then exit 0 fi printf '\n' warn "spec was installed but is not yet on your PATH." note "fix it with:" printf '\n uv tool update-shell\n\n' note "then open a new terminal."