#!/usr/bin/env bash
# TRW Framework installer bootstrap
# Usage: curl -fsSL https://trwframework.com/install.sh | bash
#
# Job: the served FIRST-RUN installer. It installs one stable release pair: the one bundled for this build, or the
# release TRW_VERSION names (it fetches that release's installer). The local channel (a wheelhouse cut) is the job
# of scripts/install.sh, the channel-and-pin installer in the repository. tests/test_install_sh_flow.py keeps the two copies' flag surfaces and failure messages in step.
#
# Authenticates first, then downloads and runs the full installer.
# Authentication is required for full framework access — create a free
# account at https://trwframework.com/signup to get started.
set -euo pipefail

# ── Argument parsing ────────────────────────────────────────────────
API_KEY="${TRW_API_KEY:-}"
API_URL="https://api.trwframework.com"
# PRD-SEC-006-FR02: pin a specific release (else "latest").
TRW_VERSION="${TRW_VERSION:-}"
# PRD-SEC-006-FR08: install the BSL package without account creation.
UNAUTHENTICATED=false
# installer-client-4: gate the destructive pip --break-system-packages fallback
# behind an explicit opt-in (flag or env). The flag is ALSO forwarded to
# install-trw.py via EXTRA_ARGS (its own gate); here it additionally controls the
# bootstrap's own Step-2 trw-mcp install.
ALLOW_SYSTEM_PYTHON="${TRW_ALLOW_SYSTEM_PYTHON:-false}"
# sweep-4 supply-chain honesty: enterprise fail-closed lever. When set (env or
# --require-checksum), an ABSENT or unfetchable installer_checksum is a hard
# ABORT instead of the default warn+proceed-on-TLS. Default unset preserves the
# backward-compatible fail-open behavior. TRW_INSTALLER_SHA256 (out-of-band pin)
# continues to work and takes precedence over the API-served checksum.
REQUIRE_CHECKSUM="${TRW_REQUIRE_INSTALLER_CHECKSUM:-}"
# installer-bundle hardening (scripts/publish-release.sh immutability guard):
# a read-only mode that resolves the version, fetches its published record,
# downloads the installer, verifies its checksum, prints OK, and exits —
# never installs trw-mcp (Step 2) or runs the full installer (Step 6). Backs
# `make verify-published VERSION=<v>`, the last step of a hardened publish.
VERIFY_ONLY=false
# PRD-INFRA-209: run a local install-trw.py (env, or --installer-file): Step 5 makes no network call and a missing key
# never starts sign-in (the installer runs with --skip-auth).
LOCAL_INSTALLER="${TRW_INSTALLER_FILE:-}"
# PRD-SEC-004-FR03: collect unrecognized flags and forward them verbatim to
# install-trw.py (notably --no-telemetry / --telemetry / --offline
# / --allow-system-python). They are NOT dropped.
EXTRA_ARGS=()
# E2E-INC-146: the cross-host sync opt-ins; consumed here (written after the installer), never forwarded.
SHARE_LEARNINGS=""
TEAM_SYNC=""

# ── ANSI colors ──────────────────────────────────────────────────────
# Defined before arg parsing so the fail() helper below is usable inside the
# parsing loop (flag-value validation).
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[0;33m'
    CYAN='\033[0;36m'
    BOLD='\033[1m'
    DIM='\033[2m'
    NC='\033[0m'
else
    RED='' GREEN='' YELLOW='' CYAN='' BOLD='' DIM='' NC=''
fi

# >>> trw-headless: --headless / --json / the run log; kept byte-identical in scripts/install.sh and the served bootstrap (a test pins it)
# TRW_HEADLESS=1 / --headless: no prompt ever (every choice from a flag, an env var or its documented default).
# TRW_JSON=1 / --json: ONE JSON document on stdout, everything human on stderr. Both modes write a 0600 run log.
HEADLESS=false
JSON_OUT=false
case "$(printf '%s' "${TRW_HEADLESS:-}" | tr 'A-Z' 'a-z')" in 1|true|yes) HEADLESS=true ;; esac
case "$(printf '%s' "${TRW_JSON:-}" | tr 'A-Z' 'a-z')" in 1|true|yes) JSON_OUT=true ;; esac
TRW_LOG=""
TRW_WARN_FILE=""
TRW_FAIL_MSG=""
TRW_AUTH_SKIPPED=false
TRW_START=$(date +%s)

_redact_stream() {  # nothing secret reaches the log: the key itself, trw_ keys, bearer tokens, KEY=value
    local k="${API_KEY:-}" lit='s/^$//'
    case "$k" in ''|*[!A-Za-z0-9_-]*) ;; *) if [ "${#k}" -ge 8 ]; then lit="s/$k/<REDACTED>/g"; fi ;; esac
    sed -E -e 's/trw_(dk_)?[A-Za-z0-9_-]{8,}/<REDACTED>/g' \
        -e 's#(Bearer|Token) +[A-Za-z0-9._~+/=-]{8,}#\1 <REDACTED>#g' \
        -e 's/((TRW_[A-Z_]*KEY|api[_-]?key)[=:] *)[^ ]+/\1<REDACTED>/g' -e "$lit"
}
_log() {
    [ -n "$TRW_LOG" ] || return 0
    printf '%s\n' "$*" | _redact_stream >>"$TRW_LOG" 2>/dev/null || true
}
_note_warn() {
    _log "WARN: $*"
    [ -z "$TRW_WARN_FILE" ] || printf '%s\n' "$(printf '%s' "$*" | tr '\n' ' ')" >>"$TRW_WARN_FILE" 2>/dev/null || true
}
_run_logged() {  # $1 = the child's stdin, then the command: its output reaches the user AND the run log
    # On a terminal the child runs under script(1), so it still sees a tty (prompts, colour, spinners unchanged);
    # otherwise its output is teed. The transcript (a 0600 temp file) is cleaned, redacted into the log, then removed.
    local input=$1 out rc=0
    shift
    if [ -z "$TRW_LOG" ] || ! out=$(umask 077; mktemp "$TRW_LOG.out.XXXXXX" 2>/dev/null); then
        "$@" <"$input"
        return
    fi
    local pty=""
    if [ "$input" = /dev/tty ] && [ -t 1 ] && command -v script >/dev/null 2>&1; then
        case "$(uname -s)" in
            Darwin | *BSD | DragonFly) pty=bsd ;;  # returns the child's status
            *) case "$(script --version 2>/dev/null)" in *util-linux*) pty=util-linux ;; esac ;;  # -e: same
        esac
    fi
    if [ "$pty" = bsd ]; then
        script -q "$out" "$@" <"$input" || rc=$?
    elif [ "$pty" = util-linux ]; then
        script -q -e -c "$(printf '%q ' "$@")" "$out" <"$input" || rc=$?
    elif [ "$input" = /dev/tty ] && [ -t 1 ]; then  # no usable script(1): the terminal hand-off wins, the log gets a note
        "$@" <"$input" || rc=$?
        printf '%s\n' "(the installer's own output is not in this log: no script(1) to record a terminal session)" >"$out"
    else
        "$@" <"$input" 2>&1 | tee "$out" || rc=$?
    fi
    # A menu that redraws in place writes ESC[<n>F (up n lines) and its lines again, so the transcript holds every
    # frame. Each such sequence drops the n lines before it and the last frame is what the log keeps. A transcript
    # without one passes through unchanged, and if awk fails the transcript is used as it is.
    {
        LC_ALL=C awk 'BEGIN { up = sprintf("%c", 27) "\\[[0-9]+F"; kept = 0 }
            { while (match($0, up)) {
                  n = substr($0, RSTART + 2, RLENGTH - 3) + 0
                  if (n <= kept) kept -= n
                  $0 = substr($0, 1, RSTART - 1) substr($0, RSTART + RLENGTH)
              }
              line[++kept] = $0 }
            END { for (i = 1; i <= kept; i++) print line[i] }' "$out" 2>/dev/null || cat "$out"
    } | sed -E $'s/\r$//; s/.*\r//; s/\x1b\\[[0-9;?]*[A-Za-z]//g' | tr -d '\000-\010\013\014\016-\037\177' \
        | _redact_stream >>"$TRW_LOG" 2>/dev/null || true
    rm -f "$out"
    return "$rc"
}
_trw_setup() {  # once, after the flags are parsed
    local dir="${TMPDIR:-/tmp}" stamp
    dir="${dir%/}"
    stamp=$(date +%Y%m%dT%H%M%S)
    if TRW_LOG=$(umask 077; mktemp "$dir/trw-install-$stamp-$$-XXXXXX" 2>/dev/null); then  # exclusive, 0600, never a planted link
        TRW_WARN_FILE="$TRW_LOG.warn"
        (umask 077; set -C; : >"$TRW_WARN_FILE") 2>/dev/null || TRW_WARN_FILE=""  # noclobber: refuses an existing name
    else
        TRW_LOG=""
    fi
    if [ "$HEADLESS" = true ]; then
        export TRW_HEADLESS=1
        RED='' GREEN='' YELLOW='' CYAN='' BOLD='' DIM='' NC=''
    fi
    if [ "$JSON_OUT" = true ]; then exec 3>&1 1>&2; fi
    trap '_trw_on_exit' EXIT
}
_trw_shebang_python() {  # the interpreter a console-script trw-mcp runs on (pipx, venv, uv tool)
    local p
    p=$(command -v trw-mcp 2>/dev/null) || return 0
    head -n 1 "$p" 2>/dev/null | sed -n 's/^#! *//p' | awk '{print $1}'
}
_report_python() {  # first interpreter that has the report module (the installed trw-mcp ships it)
    local c
    for c in "${PYTHON:-}" "$(_trw_shebang_python)"; do
        if [ -n "$c" ] && "$c" -c 'import trw_mcp.bootstrap._headless_report' >/dev/null 2>&1 </dev/null; then
            printf '%s' "$c"
            return 0
        fi
    done
    return 0
}
_json_str() { printf '%s' "$1" | tr '\n\t' '  ' | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'; }
_fallback_json() {  # the result when no interpreter can build the full document; $1 = exit code
    local ok=false note="the detailed report is unavailable (no installed trw-mcp has the report module); see the run log"
    [ "$1" -eq 0 ] && ok=true
    [ -z "$TRW_FAIL_MSG" ] || note=$(printf '%s' "$TRW_FAIL_MSG" | head -n 1)
    printf '{"schema_version":1,"ok":%s,"version":null,"interpreter":"%s","install_path":"%s","clients_configured":[],"doctor":{"status":"unknown","summary":"not run"},"warnings":[{"message":"%s","remedy":"read the run log"}],"files_changed":[],"next_steps":["read the run log"],"log_path":"%s"}\n' \
        "$ok" "$(_json_str "${PYTHON:-}")" "$(_json_str "$PWD")" "$(_json_str "$(printf '%s' "$note" | _redact_stream)")" "$(_json_str "$TRW_LOG")"
}
_trw_on_exit() {
    local rc=$? py="" doc="" args
    set +e
    py=$(_report_python)
    if [ -n "$TRW_LOG" ] && [ -f "$TRW_LOG" ]; then
        [ -n "$TRW_FAIL_MSG" ] || TRW_FAIL_MSG=$(grep '^FAIL: ' "$TRW_LOG" | tail -n 1 | sed 's/^FAIL: //')
        [ -z "$py" ] || TRW_API_KEY="${API_KEY:-}" "$py" -m trw_mcp.bootstrap._headless_report redact-file "$TRW_LOG" 2>/dev/null
        chmod 600 "$TRW_LOG" 2>/dev/null
        echo "  [TRW] Run log: $TRW_LOG" >&2
    fi
    if [ "$JSON_OUT" = true ]; then
        if [ -n "$py" ]; then
            args=(report --dir "$PWD" --exit-code "$rc" --log "$TRW_LOG" --since "$TRW_START" --error "$TRW_FAIL_MSG")
            [ -z "$TRW_WARN_FILE" ] || args+=(--warnings "$TRW_WARN_FILE")
            [ "$TRW_AUTH_SKIPPED" != true ] || args+=(--auth-skipped)
            doc=$(TRW_API_KEY="${API_KEY:-}" "$py" -m trw_mcp.bootstrap._headless_report "${args[@]}" 2>/dev/null </dev/null)
        fi
        [ -n "$doc" ] || doc=$(_fallback_json "$rc")
        printf '%s\n' "$doc" >&3
    fi
    [ -z "$TRW_WARN_FILE" ] || rm -f "$TRW_WARN_FILE"
    return 0
}
# <<< trw-headless
# The key reaches curl as a header FILE (a /dev/fd pipe), never as an argv word: argv is readable by every local
# user through ps. Same idiom as scripts/smoke-test.sh (printf is a builtin, so the key is in no process's argv).
auth_header() { printf 'Authorization: Bearer %s\n' "$API_KEY"; }

# ── Helper functions ─────────────────────────────────────────────────
info()  { echo -e "  ${GREEN}[TRW]${NC} $*"; _log "$*"; }
warn()  { echo -e "  ${YELLOW}[TRW]${NC} $*"; _note_warn "$*"; }
# warn_raw: the text is printed verbatim (warn is echo -e, which eats the backslashes of a path or a %q-quoted command).
warn_raw() { printf '  %b[TRW]%b %s\n' "$YELLOW" "$NC" "$*"; _note_warn "$*"; }
fail()  { TRW_FAIL_MSG=$(printf '%b' "$*"); _log "FAIL: $TRW_FAIL_MSG"; echo -e "  ${RED}[TRW]${NC} $*" >&2; exit 1; }

# Native Windows (Git Bash, MSYS2, Cygwin) is not supported in this release: the memory
# store's file-safety checks need POSIX. WSL2 is Linux and works.
case "$(uname -s 2>/dev/null)" in
    MINGW*|MSYS*|CYGWIN*|Windows_NT*) fail "Native Windows is not supported in this release. Install and run TRW inside WSL2 (https://learn.microsoft.com/windows/wsl/install), which it treats as Linux." ;;
esac

# ── Package-index reachability preflight ─────────────────────────────
# Regional install gate (added 2026-09-20). Operator decision 2026-10-01: keep the gate.
# Owner: operator (Tyler Wall) via swarm-lead; review by 2026-10-16.
# Disclosure: to check your egress location this preflight may send your public IP to
# Cloudflare and ipinfo.io. Revert: set INDEX_PREFLIGHT_ENABLED=false, or delete this block
# and its one `index_preflight` call below. Mirrors the same preflight in install-trw.py.
# Bypass: TRW_SKIP_INDEX_PREFLIGHT=1.
INDEX_PREFLIGHT_ENABLED=true
INDEX_PREFLIGHT_REGIONS="US"
# Local UTC offsets consistent with INDEX_PREFLIGHT_REGIONS. +00 passes too:
# containers, CI and servers keep a UTC clock (INC-144).
INDEX_PREFLIGHT_OFFSETS="+00 -04 -05 -06 -07 -08 -09 -10 -11"
# Networks that terminate consumer tunnels. Hyperscalers deliberately absent:
# CI runners egress from them.
INDEX_PREFLIGHT_NETWORKS='m247|datacamp|packethub|leaseweb|tzulo|quadranet|psychz|gthost|zenlayer|hostroyale|31173|cdn77|mullvad|nordvpn|tefincom|expressvpn|surfshark|privateinternetaccess|london trust|ipvanish|cyberghost|proton ag|protonvpn|windscribe|vpn'

index_unavailable() {
    fail "The installer stopped before contacting the package index: the regional check (${1:-region, clock offset, VPN or proxy}) did not pass. The package index itself refused nothing.\n  To check your egress location, this preflight may send your public IP to Cloudflare and ipinfo.io.\n  If you are in a supported region, check for a VPN or proxy and try again, or set TRW_SKIP_INDEX_PREFLIGHT=1 to skip this check."
}

index_preflight() {
    [ "$INDEX_PREFLIGHT_ENABLED" = "true" ] || return 0
    case "${TRW_SKIP_INDEX_PREFLIGHT:-}" in
        1|true|yes) return 0 ;;
    esac

    local trace="" loc="" warp="" org="" offset=""
    trace="$(curl -fsSL --max-time 4 https://www.cloudflare.com/cdn-cgi/trace 2>/dev/null)" || true
    loc="$(printf '%s\n' "$trace" | sed -n 's/^loc=//p' | tr -d '[:space:]' | tr '[:lower:]' '[:upper:]')"
    warp="$(printf '%s\n' "$trace" | sed -n 's/^warp=//p' | tr -d '[:space:]')"

    # No signal (offline, proxy, endpoint down) -> proceed.
    [ -n "$loc" ] || return 0

    case " $INDEX_PREFLIGHT_REGIONS " in
        *" $loc "*) ;;
        *) index_unavailable ;;
    esac

    [ "$warp" != "on" ] || index_unavailable

    offset="$(date +%z 2>/dev/null | cut -c1-3)"
    if [ -n "$offset" ]; then
        case " $INDEX_PREFLIGHT_OFFSETS " in
            *" $offset "*) ;;
            *) index_unavailable "clock offset UTC$offset" ;;
        esac
    fi

    org="$(curl -fsSL --max-time 4 https://ipinfo.io/json 2>/dev/null \
        | sed -n 's/.*"org"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
        | tr '[:upper:]' '[:lower:]')" || true
    if [ -n "$org" ] && printf '%s' "$org" | grep -Eq "$INDEX_PREFLIGHT_NETWORKS"; then
        index_unavailable
    fi
}

index_preflight
step()  { echo -e "  ${GREEN}✓${NC} $*"; _log "$*"; }

while [ $# -gt 0 ]; do
    case "$1" in
        --api-key)
            # Explicit non-empty check — the old `shift; API_KEY="${1:-}"` form
            # silently accepted a missing value (empty key → later auth failure).
            shift
            [ -n "${1:-}" ] || fail "--api-key requires a value"
            API_KEY="$1"
            ;;
        --api-key=*)  API_KEY="${1#*=}" ;;
        --version)
            shift
            [ -n "${1:-}" ] || fail "--version requires a value"
            TRW_VERSION="$1"
            ;;
        --version=*)  TRW_VERSION="${1#*=}" ;;
        --headless)             HEADLESS=true ;;
        --share-learnings)      SHARE_LEARNINGS=true ;;
        --no-share-learnings)   SHARE_LEARNINGS=false ;;
        --team-sync)            TEAM_SYNC=true ;;
        --no-team-sync)         TEAM_SYNC=false ;;
        --json)                 JSON_OUT=true ;;
        --allow-unauthenticated) UNAUTHENTICATED=true ;;
        --require-checksum)      REQUIRE_CHECKSUM=1 ;;
        --verify-only)            VERIFY_ONLY=true ;;
        --installer-file)
            shift
            [ -n "${1:-}" ] || fail "--installer-file requires a value"
            LOCAL_INSTALLER="$1"
            ;;
        --installer-file=*)       LOCAL_INSTALLER="${1#*=}" ;;
        --allow-system-python)
            # Set the bootstrap's own gate AND forward to install-trw.py (its gate).
            ALLOW_SYSTEM_PYTHON=true
            EXTRA_ARGS+=("$1")
            ;;
        --help|-h)
            echo "Usage: curl -fsSL https://trwframework.com/install.sh | bash"
            echo ""
            echo "Options:"
            echo "  --api-key KEY              Use this API key (CI/CD, skips device auth)"
            echo "  --version VER              Pin a release version (verifies its checksum)"
            echo "  --allow-unauthenticated    Install the trw-mcp package without an account"
            echo "  --require-checksum         Fail-closed: ABORT if no installer checksum is published (enterprise)"
            echo "  --verify-only              Resolve + download + verify the installer checksum, then exit (no install)"
            echo "  --allow-system-python      Permit pip --break-system-packages (destructive; prefer a venv/pipx)"
            echo "  --no-telemetry             Disable usage telemetry (forwarded to installer)"
            echo "  --telemetry                Enable usage telemetry (forwarded to installer)"
            echo "  --share-learnings          Let this host push its learnings to the platform (sets learning_sharing_enabled)"
            echo "  --team-sync                Let this host pull the team's learnings (sets team_sync_enabled)"
            echo "                             (--no-share-learnings / --no-team-sync turn either off)"
            echo "  --offline                  Install from embedded wheels only (forwarded)"
            echo "  --headless                 Never prompt: every choice comes from a flag, an env var or its default (same as TRW_HEADLESS=1)"
            echo "  --json                     Print ONE JSON result document on stdout, all human output on stderr (same as TRW_JSON=1)"
            echo "  --help                     Show this help"
            echo ""
            echo "Any other flags are forwarded verbatim to install-trw.py."
            echo ""
            echo "Environment:"
            echo "  TRW_HEADLESS / TRW_JSON    Same as --headless / --json. Every run writes a 0600 log to \$TMPDIR/trw-install-<stamp>.log (secrets redacted)"
            echo "  TRW_API_KEY                API key (passed to the installer via env, not argv)"
            echo "  TRW_VERSION                Pin a release version (same as --version)"
            echo "  TRW_INSTALLER_SHA256       Expected SHA-256 of the installer (overrides API checksum; hard abort on mismatch)"
            echo "  TRW_INSTALLER_FILE         Path to a local install-trw.py (same as --installer-file): no download, no sign-in without a key"
            echo "  TRW_REQUIRE_INSTALLER_CHECKSUM  Set to '1' to fail-closed when no checksum is published (same as --require-checksum)"
            echo "  TRW_ALLOW_SYSTEM_PYTHON    Set to 'true' to permit the --break-system-packages fallback"
            echo "  TRW_PYTHON                 Interpreter to install into or upgrade; wins over auto-detection."
            echo "                             Default: the Python your existing TRW runs on (project .mcp.json, trw-mcp on"
            echo "                             PATH, memory launcher record), upgraded in place; else the newest 3.11+ on PATH"
            echo "  HTTPS_PROXY/HTTP_PROXY/NO_PROXY   Proxy support for air-gapped/proxied hosts"
            echo ""
            echo "License: trw-mcp + trw-memory are source-available under BSL-1.1"
            echo "  https://trwframework.com/license"
            exit 0
            ;;
        *)
            # PRD-SEC-004-FR03: forward unknown flags to install-trw.py.
            EXTRA_ARGS+=("$1")
            ;;
    esac
    shift
done

_trw_setup

# PLAN N4 (2026-10-11): an inherited TRW_CHANNEL reached the bundled installer, which refuses anything but stable,
# only after this script had installed trw-mcp. Refuse here, before anything is fetched or installed.
case "${TRW_CHANNEL:-stable}" in
    stable) ;;
    *) fail "TRW_CHANNEL=${TRW_CHANNEL} is set, and this installer installs only the stable release (it installed nothing). From a checkout, run \`TRW_CHANNEL=${TRW_CHANNEL} bash scripts/install.sh\` for a local cut, or unset TRW_CHANNEL (TRW_VERSION=<x.y.z> pins a release)." ;;
esac

# sweep-4: normalize the fail-closed lever. It is later tested with [ -n ], so a
# literal TRW_REQUIRE_INSTALLER_CHECKSUM=false / =0 would WRONGLY enable it. Only
# 1/true/yes (case-insensitive) count as set; empty/0/false/no reset to unset.
case "$(printf '%s' "$REQUIRE_CHECKSUM" | tr 'A-Z' 'a-z')" in
    1|true|yes) REQUIRE_CHECKSUM=1 ;;
    *)          REQUIRE_CHECKSUM="" ;;
esac

# ── Banner ───────────────────────────────────────────────────────────
# >>> trw-banners: every box row is padded from its VISIBLE width (ANSI stripped, a UTF-8 character counts once);
# scripts/tests/test_install_sh_banner_alignment.py renders this block, so keep it free of side effects.
W=50
_visible_width() {
    local esc; esc=$(printf '\033')
    printf '%s' "$(printf '%b' "$1")" | sed "s/${esc}\[[0-9;]*m//g" | LC_ALL=C tr -d '\200-\277' | wc -c | tr -d ' '
}
box_edge() {  # box_edge COLOR LEFT_CORNER RIGHT_CORNER
    local bar; bar=$(printf '%*s' "$W" '' | sed 's/ /─/g')
    printf '%b%s%s%s%b\n' "$1" "$2" "$bar" "$3" "$NC"
}
box_line() {  # box_line COLOR [TEXT]
    local text pad
    text=$(printf '%b' "${2:-}")
    pad=$((W - 2 - $(_visible_width "$text"))); [ "$pad" -ge 0 ] || pad=0
    printf '%b│%b  %s%*s%b│%b\n' "$1" "$NC" "$text" "$pad" '' "$1" "$NC"
}
show_banner() {
    echo ""
    box_edge "$CYAN" ╭ ╮
    box_line "$CYAN" "${BOLD}TRW Framework${NC}"
    box_line "$CYAN" "${DIM}The engineering operating layer for AI agents${NC}"
    box_line "$CYAN" "${DIM}https://trwframework.com${NC}"
    box_edge "$CYAN" ╰ ╯
    # PRD-SEC-006-FR05: BSL-1.1 source-available license notice in the install flow.
    echo -e "  ${DIM}trw-mcp + trw-memory are source-available under BSL-1.1. License: https://trwframework.com/license${NC}"
    echo ""
}
show_package_only_box() {  # arg "init": the project is not initialised yet
    box_edge "$YELLOW" ╭ ╮
    box_line "$YELLOW" "${YELLOW}${BOLD}trw-mcp installed (package only)${NC}"
    box_line "$YELLOW"
    box_line "$YELLOW" "${DIM}Add the full framework with a free account:${NC}"
    box_line "$YELLOW" "  https://trwframework.com/signup"
    box_line "$YELLOW" "${DIM}Then run:${NC}"
    box_line "$YELLOW" "  trw-mcp auth login"
    [ -z "${1:-}" ] || box_line "$YELLOW" "  trw-mcp init-project ."
    box_edge "$YELLOW" ╰ ╯
}
# <<< trw-banners

show_banner

# ── Step 1: Check Python 3.11+ ──────────────────────────────────────
info "Checking Python..."
# >>> trw-interpreter-selection: kept byte-identical in scripts/install.sh and the served bootstrap (a test pins it)
# Which Python does this machine's TRW already run on?
#
# A second interpreter must never receive a second install. Clients keep launching the first one, so the project's
# hooks and skills would be re-rendered from the new version on top of a server that never changes (2026-10-01:
# trw-mcp 8.1.5 went into python3.14 while every client launched 8.1.2 from python3.13). An existing install is
# upgraded IN PLACE, by the interpreter it runs on. Evidence of what the clients launch, strongest first:
#   TRW_PYTHON       the operator's own word; it wins over everything and is refused, never ignored, when unusable.
#   .mcp.json        the command this project's clients literally spawn (its "trw" server).
#   trw-mcp on PATH  what a client configured with the bare command spawns (generated configs are bare).
#   launcher record  the interpreter the shared store's memory daemon starts from (trw-memory's launcher.json). It is
#                    machine-wide, but written by a server that booted, so it can lag a reinstall: it ranks last.
# An interpreter on PATH that carries trw-mcp but that none of those names only shows that an install EXISTS: one such
# install is used (nothing else says where TRW lives), several are refused, never guessed between. Only a machine
# with no install keeps the newest-first probe (FB-INSTALL-PYDETECT).
TRW_PY_MODE="probe"      # override | pinned | found | ambiguous | probe: how PYTHON was chosen
TRW_IN_PLACE=false       # true: the install lives in $PYTHON and may only be upgraded THERE, never beside it
TRW_PY_AMBIGUOUS=false   # true: several installs exist and nothing says which one the clients launch

_pkg_ver() {
    # Print the installed trw-mcp version as seen by interpreter $1 (empty on any failure).
    "$1" -c 'import importlib.metadata as m; print(m.version("trw-mcp"))' 2>/dev/null </dev/null || true
}

_py_usable() {
    # 0 when $1 resolves to a Python 3.11+ (the floor).
    local major minor
    command -v "$1" >/dev/null 2>&1 || return 1
    major=$("$1" -c 'import sys; print(sys.version_info.major)' 2>/dev/null </dev/null || echo "0")
    minor=$("$1" -c 'import sys; print(sys.version_info.minor)' 2>/dev/null </dev/null || echo "0")
    [ "$major" -ge 3 ] 2>/dev/null && [ "$minor" -ge 11 ] 2>/dev/null
}

_dev_install() {
    # 0 when $1's trw-mcp is an editable install (a development checkout): a release installer must not replace it.
    # Exit 3 is the only "yes", so a probe that fails or says nothing reads as "not editable".
    local rc=0
    "$1" -c 'import importlib.metadata as m, json; d = json.loads(m.distribution("trw-mcp").read_text("direct_url.json") or "{}"); raise SystemExit(3 if d.get("dir_info", {}).get("editable") else 0)' >/dev/null 2>&1 </dev/null || rc=$?
    [ "$rc" -eq 3 ]
}

_py_key() {
    # The identity of $1's environment: its prefix, so `python3.13`, `/opt/homebrew/bin/python3.13` and a launcher's
    # shebang path are one install, and two venvs built from one base are two.
    local key
    key=$("$1" -c 'import sys; print(sys.prefix)' 2>/dev/null </dev/null || true)
    printf '%s\n' "${key:-$1}"
}

_launcher_python() {
    # Print the interpreter launcher command $1 runs on (nothing when that cannot be told). Follows a shebang
    # (`#!/path/python`, `#!/usr/bin/env python3`), pip's long-path header (`'''exec' "/path/python"`) and the shim
    # this installer writes for a managed venv (`exec "<venv>/bin/trw-mcp"`), then the python beside the script.
    local cmd="$1" depth="${2:-0}" file top first target
    [ -n "$cmd" ] && [ "$depth" -lt 4 ] || return 0
    case "${cmd##*/}" in python|python[0-9]*) printf '%s\n' "$cmd"; return 0 ;; esac
    file=$(command -v "$cmd" 2>/dev/null || true)
    [ -n "$file" ] && [ -f "$file" ] || return 0
    top=$(head -n 3 "$file" 2>/dev/null | LC_ALL=C tr -d '\000' || true)
    first=$(printf '%s\n' "$top" | head -n 1)
    target=$(printf '%s\n' "$top" | sed -n "s/^.*exec'\{0,1\} \"\([^\"]*\)\".*\$/\1/p" | head -n 1)
    if [ -n "$target" ]; then
        _launcher_python "$target" $((depth + 1))
        return 0
    fi
    case "$first" in
        '#!'*)
            set -f
            # shellcheck disable=SC2086  # word splitting IS the parse of the shebang line
            set -- ${first#\#!}
            set +f
            case "${1##*/}" in
                python|python[0-9]*) printf '%s\n' "$1"; return 0 ;;
                env)
                    shift
                    while [ $# -gt 0 ]; do
                        case "$1" in -*|*=*) shift ;; *) break ;; esac
                    done
                    case "${1##*/}" in python|python[0-9]*) printf '%s\n' "$1"; return 0 ;; esac
                    ;;
            esac
            ;;
    esac
    if [ -x "$(dirname "$file")/python" ]; then printf '%s\n' "$(dirname "$file")/python"; fi
    return 0
}

_mcp_json_command() {
    # The command .mcp.json $1 launches for its "trw" server (else any command naming trw-mcp); nothing if none.
    local flat found
    # A flat "env" object ahead of "command" would end the [^}]* match early, so drop those objects first.
    flat=$(tr '\n' ' ' <"$1" 2>/dev/null | sed 's/"env"[[:space:]]*:[[:space:]]*{[^}]*}//g' || true)
    found=$(printf '%s' "$flat" | grep -o '"trw"[[:space:]]*:[[:space:]]*{[^}]*"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -n 1 || true)
    [ -n "$found" ] || found=$(printf '%s' "$flat" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*trw[^"]*"' | head -n 1 || true)
    printf '%s' "$found" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]*\)"$/\1/p'
    return 0
}

_launcher_record_path() {
    # Where trw-memory keeps the launcher record: TRW_USER_DIR, else XDG_DATA_HOME, else ~/.trw.
    if [ -n "${TRW_USER_DIR:-}" ]; then
        printf '%s\n' "$TRW_USER_DIR/memory/launcher.json"
    elif [ -n "${XDG_DATA_HOME:-}" ]; then
        printf '%s\n' "$XDG_DATA_HOME/trw/memory/launcher.json"
    else
        printf '%s\n' "$HOME/.trw/memory/launcher.json"
    fi
}

_trw_candidates() {
    # "<source>|<python>|<how it was found>" lines: the evidence of what the clients launch, then PATH and the
    # isolated environments this installer, pipx and uv create (which only show that an install exists).
    local cmd py rec name
    if [ -f .mcp.json ]; then
        cmd=$(_mcp_json_command .mcp.json)
        py=$(_launcher_python "$cmd")
        if [ -n "$py" ]; then printf 'mcp|%s|.mcp.json launches %s\n' "$py" "$cmd"; fi
    fi
    cmd=$(command -v trw-mcp 2>/dev/null || true)
    py=$(_launcher_python "$cmd")
    if [ -n "$py" ]; then printf 'path|%s|trw-mcp on PATH\n' "$py"; fi
    rec=$(_launcher_record_path)
    if [ -f "$rec" ]; then
        py=$(sed -n 's/.*"python"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$rec" | head -n 1)
        if [ -n "$py" ]; then printf 'launcher|%s|memory launcher record\n' "$py"; fi
    fi
    for name in python3.14 python3.13 python3.12 python3.11 python3 python; do
        py=$(command -v "$name" 2>/dev/null || true)
        [ -n "$py" ] || continue
        # Apple's /usr/bin python3 is a 3.9 stub that can raise an installer dialog when run: never a candidate.
        case "$(uname -s 2>/dev/null) $py" in "Darwin /usr/bin/"*) continue ;; esac
        printf 'scan|%s|%s on PATH\n' "$py" "$name"
    done
    for py in "${TRW_VENV_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/trw/venv}/bin/python" \
              "${PIPX_LOCAL_VENVS:-${PIPX_HOME:-$HOME/.local/pipx}/venvs}/trw-mcp/bin/python" \
              "${XDG_DATA_HOME:-$HOME/.local/share}/uv/tools/trw-mcp/bin/python"; do
        if [ -x "$py" ]; then printf 'scan|%s|an isolated trw-mcp environment\n' "$py"; fi
    done
    return 0
}

_trw_installs() {
    # "<python>|<trw-mcp version>|<source>|<how found>" for each distinct environment that holds trw-mcp, in
    # evidence order (an interpreter named twice is one install, whichever way it is named).
    local seen="" src py how ver key
    while IFS='|' read -r src py how; do
        [ -n "$py" ] || continue
        _py_usable "$py" || continue
        ver=$(_pkg_ver "$py")
        [ -n "$ver" ] || continue
        if _dev_install "$py"; then continue; fi
        key=$(_py_key "$py")
        case "|$seen|" in *"|$key|"*) continue ;; esac
        seen="$seen|$key"
        printf '%s|%s|%s|%s\n' "$py" "$ver" "$src" "$how"
    done <<EOT
$(_trw_candidates)
EOT
    return 0
}

_probe_newest_python() {
    # No install anywhere: newest first (FB-INSTALL-PYDETECT). A bare `python3` is often an old system Python that
    # sits ahead of a newer one. "quiet" (the machine is ambiguous and nothing will be installed) sets PYTHON alone.
    local cmd version
    for cmd in python3.14 python3.13 python3.12 python3.11 python3 python; do
        if _py_usable "$cmd"; then
            PYTHON="$cmd"
            version=$("$cmd" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null </dev/null || true)
            [ "${1:-}" = quiet ] || step "Python $version ($cmd)"
            return 0
        fi
    done
    return 0
}

_report_installs() {
    # $1 = the install lines, $2 = a headline, $3 = the python this run installs into (empty: none chosen).
    local ipy iver isrc ihow own mark
    warn "$2"
    own=""
    [ -z "$3" ] || own=$(_py_key "$3")
    while IFS='|' read -r ipy iver isrc ihow; do
        mark=""
        if [ -n "$own" ] && [ "$(_py_key "$ipy")" = "$own" ]; then
            case "$isrc" in
                mcp|path) mark="   <- your clients launch this one ($ihow); upgraded in place" ;;
                launcher) mark="   <- the launcher record names this one; upgraded in place" ;;
                *)        mark="   <- upgraded in place" ;;
            esac
        elif [ -n "$own" ]; then
            mark="   <- left untouched; remove it with: $(printf '%q' "$ipy") -m pip uninstall trw-mcp"
        fi
        warn_raw "    trw-mcp $iver  $ipy$mark"
    done <<EOT
$1
EOT
    return 0
}

select_trw_python() {
    # Sets PYTHON (and TRW_PY_MODE, TRW_IN_PLACE, TRW_PY_AMBIGUOUS). Prints what it chose and why.
    local installs first count src ver how own_ver
    PYTHON=""
    installs=""
    if [ -n "${TRW_PYTHON:-}" ]; then
        _py_usable "$TRW_PYTHON" || fail "TRW_PYTHON=$TRW_PYTHON is not a usable Python 3.11+. Point it at an interpreter that runs (python3.13, /path/to/python) or unset it."
        PYTHON="$TRW_PYTHON"
        own_ver=$(_pkg_ver "$PYTHON")
        if [ -n "$own_ver" ]; then
            TRW_PY_MODE=pinned
            TRW_IN_PLACE=true
            step "Existing install: trw-mcp $own_ver on $PYTHON (TRW_PYTHON) — upgrading it in place"
        else
            TRW_PY_MODE=override
            step "Python $("$PYTHON" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null </dev/null || true) ($PYTHON, from TRW_PYTHON)"
        fi
        installs=$(_trw_installs)
        count=0
        [ -z "$installs" ] || count=$(printf '%s\n' "$installs" | wc -l | tr -d ' ')
        if [ "$count" -gt 1 ] || { [ "$count" -eq 1 ] && [ -z "$own_ver" ]; }; then
            _report_installs "$installs" "TRW is installed more than once on this machine:" "$PYTHON"
            if [ -z "$own_ver" ]; then
                warn "    Installing into $PYTHON because TRW_PYTHON says so. The other copy is left untouched, and any client that launches it keeps running it."
            fi
        fi
        return 0
    fi
    if [ "${VERIFY_ONLY:-false}" = true ]; then
        _probe_newest_python
        return 0
    fi
    installs=$(_trw_installs)
    if [ -z "$installs" ]; then
        _probe_newest_python
        return 0
    fi
    first=$(printf '%s\n' "$installs" | head -n 1)
    count=$(printf '%s\n' "$installs" | wc -l | tr -d ' ')
    src=$(printf '%s' "$first" | cut -d'|' -f3)
    PYTHON=$(printf '%s' "$first" | cut -d'|' -f1)
    ver=$(printf '%s' "$first" | cut -d'|' -f2)
    how=$(printf '%s' "$first" | cut -d'|' -f4)
    if [ "$src" != scan ]; then
        TRW_PY_MODE=pinned
        TRW_IN_PLACE=true
        step "Existing install: trw-mcp $ver on $PYTHON ($how) — upgrading it in place"
        [ "$count" -lt 2 ] || _report_installs "$installs" "TRW is installed more than once on this machine:" "$PYTHON"
    elif [ "$count" -eq 1 ]; then
        TRW_PY_MODE=found
        step "Found an existing trw-mcp install: $PYTHON ($how)"
    else
        TRW_PY_MODE=ambiguous
        TRW_PY_AMBIGUOUS=true
        _report_installs "$installs" "TRW is installed more than once and nothing says which one your clients launch:" ""
        _probe_newest_python quiet  # only so later steps have an interpreter to read with; nothing is installed
    fi
    return 0
}

refuse_ambiguous_python() {
    # Called right before any package would be touched.
    [ "$TRW_PY_AMBIGUOUS" = true ] || return 0
    [ "${HEADLESS:-false}" != true ] || fail "Headless: set TRW_PYTHON=<the interpreter your clients launch> (TRW is installed more than once and nothing says which copy to upgrade)."
    fail "Not installing: nothing says which copy of TRW your clients launch, and installing beside the wrong one is how a machine ends up on two versions.
    Re-run with TRW_PYTHON=<the interpreter your clients launch> to upgrade that copy in place (TRW_PYTHON=<another python> installs there on purpose)."
}
# <<< trw-interpreter-selection
select_trw_python

[ -z "$PYTHON" ] && fail "Python 3.11+ required. Install from https://python.org"

# ── Step 2: Install trw-mcp (needed for auth CLI) ────────────────────
# Capture each pip attempt's output so the final failure branch can show real
# diagnostics (mirrors scripts/install.sh; previously all output went to
# /dev/null, leaving a bare "externally managed" guess). Bash has exactly ONE
# EXIT trap per process, so register a PIP_LOG-only cleanup now and REPLACE it
# with a both-files cleanup once INSTALLER_TMP is created (Step 5). Registered
# unconditionally (even under --verify-only, which skips the install attempts
# below) so the later Step 5 trap always finds a bound $PIP_LOG.
PIP_LOG=$(mktemp)
# Output of the isolated-install rungs (pipx, managed venv, uv): the failure message shows the last one's real error
# instead of blaming PEP 668 whatever happened (FB-INSTALL-PEP668-SERVED).
RUNG_LOG=$(mktemp)
trap '_trw_on_exit; rm -f "$PIP_LOG" "$RUNG_LOG"' EXIT
_rung_log() { printf '%s\n' "--- $1 ---" >>"$RUNG_LOG"; }

if [ "$VERIFY_ONLY" = true ]; then
    step "Skipping trw-mcp install (--verify-only — read-only checksum check)"
else
refuse_ambiguous_python
info "Installing trw-mcp..."

# The bootstrap only needs the trw-mcp CONSOLE SCRIPT here (Step 3 runs
# `trw-mcp auth login`, binary-first with a `$PYTHON -m trw_mcp.server`
# fallback); the full framework is installed later by install-trw.py. So on an
# externally-managed Python (PEP 668) where both system and `--user` pip are
# blocked, pipx is the correct fallback: it installs trw-mcp into its own
# isolated venv and exposes the `trw-mcp` binary on PATH — exactly what the
# later steps invoke — without mutating the OS Python. We prepend the pipx bin
# dir to PATH so the freshly-installed binary resolves in THIS shell.
_add_pipx_bindir_to_path() {
    local bindir
    bindir=$(pipx environment --value PIPX_BIN_DIR 2>/dev/null || true)
    [ -z "$bindir" ] && bindir="$HOME/.local/bin"
    # Persist the bin dir into the user's shell profile so FUTURE shells — and
    # the non-interactive environments MCP clients spawn `trw-mcp` in — inherit
    # it. Prepending to THIS shell's PATH (below) only helps the current process.
    # Best-effort: never fail the install if ensurepath is unavailable.
    pipx ensurepath >/dev/null 2>&1 || true
    case ":$PATH:" in
        *":$bindir:"*)
            # Already on the inherited PATH — non-interactive clients inherit it too.
            : ;;
        *)
            # Not on the PATH we inherited. Make `trw-mcp` resolve in THIS shell,
            # but be honest: a client that spawns trw-mcp in a fresh
            # non-interactive shell may not see it until a re-login.
            export PATH="$bindir:$PATH"
            warn "pipx installed trw-mcp into $bindir, which was not on your PATH."
            warn "Ran 'pipx ensurepath' to persist it — open a new shell or re-login to pick it up."
            warn "Non-interactive MCP clients may need an absolute path: $bindir/trw-mcp"
            ;;
    esac
}

# On an externally-managed Python with neither pipx nor a writable pip — the
# DEFAULT macOS + Homebrew Python state (PEP 668) — a dedicated venv is the most
# portable PEP 668-safe target: `python3 -m venv` ships with CPython/Homebrew,
# and packages installed INSIDE a venv are never blocked by PEP 668 (a venv is
# not an "externally managed" environment). We install trw-mcp there and expose
# its console scripts on ~/.local/bin so the later `trw-mcp` calls resolve.
# sqlite-vec is a base dependency of trw-mcp since 6.1.0 (the [vectors] extra
# is gone). Semantic embeddings are install-trw.py's job (Step 6): it installs
# trw-memory[embeddings] plus the configured model by default and exits non-zero
# when it cannot; --no-embeddings is forwarded verbatim via EXTRA_ARGS. The
# --allow-unauthenticated branch below never reaches Step 6, so it says so.
TRW_MCP_SPEC="trw-mcp"
# A pinned release (TRW_VERSION / --version) pins the bootstrap's own trw-mcp too, not just the installer download.
[ -z "$TRW_VERSION" ] || TRW_MCP_SPEC="trw-mcp==$TRW_VERSION"
TRW_TOOL_VENV="${TRW_VENV_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/trw/venv}"
# The trw-mcp version this run actually installed (set by whichever rung wins);
# used by the post-install shadow check to warn if a stale copy owns PATH.
TRW_FRESH_VERSION=""
# C06: the interpreter that owns the trw-mcp binary this run just installed —
# NOT necessarily $PYTHON. A pipx/uv/managed-venv rung installs trw-mcp into an
# isolated venv; install-trw.py (Step 6) must install the bundled wheels into
# THAT interpreter, not the bootstrap's $PYTHON, or the bundled wheels land
# somewhere trw-mcp never imports them from. Set by whichever rung wins below;
# exported (with a $PYTHON fallback) right before Step 6 runs.
TRW_TARGET_PYTHON=""

_persist_bindir() {
    # Best-effort, idempotent: append a PATH export to the user's shell profiles
    # so FUTURE (and non-interactive) shells inherit the bin dir. Mirrors what
    # `pipx ensurepath` does for the pipx path.
    local dir="$1" profile
    for profile in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile"; do
        [ -f "$profile" ] || continue
        grep -qsF "$dir" "$profile" 2>/dev/null && continue
        printf '\n# Added by TRW installer\nexport PATH="%s:$PATH"\n' "$dir" >> "$profile" 2>/dev/null || true
    done
}

_expose_trw_bin() {
    # Expose the trw console scripts from an isolated bin dir ($1) on ~/.local/bin
    # via a LAUNCHER SHIM — a real file that execs the managed binary by absolute
    # path — rather than a bare symlink (agy 2nd-opinion 2026-07-21). A shim is
    # more robust for tools that stat/reject symlinks, and the absolute exec means
    # only ~/.local/bin's PATH position matters. Overwriting an existing
    # ~/.local/bin/trw-mcp is exactly how a fresh install supersedes a stale
    # shadow THERE (Option A: PATH-winner). Shadows higher in PATH are handled by
    # _verify_no_stale_shadow (we never touch system dirs).
    # $2 (optional) names the scripts to expose; default trw-mcp and trw-memory.
    local src_bindir="$1" exes="${2:-trw-mcp trw-memory}" dest="$HOME/.local/bin" exe target
    mkdir -p "$dest"
    for exe in $exes; do
        target="$src_bindir/$exe"
        [ -x "$target" ] || continue
        cat > "$dest/$exe" <<SHIM
#!/usr/bin/env bash
# TRW launcher shim → managed install. Regenerated by the installer; do not edit.
exec "$target" "\$@"
SHIM
        chmod +x "$dest/$exe"
    done
    case ":$PATH:" in
        *":$dest:"*) : ;;
        *)
            export PATH="$dest:$PATH"
            _persist_bindir "$dest"
            warn "Installed trw-mcp shim into $dest (added to PATH; open a new shell to persist)."
            warn "Non-interactive MCP clients may need the absolute path: $dest/trw-mcp"
            ;;
    esac
}

_verify_no_stale_shadow() {
    # Option A post-install guard: confirm the trw-mcp that PATH resolves is the
    # version we just installed. A stale copy HIGHER in PATH than ~/.local/bin can
    # still win; we must not touch system dirs, so warn with the exact fix.
    [ -n "${TRW_FRESH_VERSION:-}" ] || return 0
    local resolved resolved_ver
    resolved=$(command -v trw-mcp 2>/dev/null || true)
    [ -n "$resolved" ] || return 0
    # `|| true`: under `set -euo pipefail` a shadow binary whose --version exits
    # non-zero (broken deps — the archetypal shadow) made this assignment fail
    # and killed the whole installer silently, mid-run. An unreadable version is
    # "cannot determine", not "no shadow": warn and continue.
    resolved_ver=$("$resolved" --version 2>/dev/null | awk '{print $NF}' || true)
    if [ -z "$resolved_ver" ]; then
        warn "A trw-mcp is first on your PATH ($resolved) but does not report a version"
        warn "(a broken install shadows the freshly-installed $TRW_FRESH_VERSION). If trw-mcp"
        warn "misbehaves, remove that copy or put ~/.local/bin ahead of it in PATH."
        return 0
    fi
    if [ "$resolved_ver" != "$TRW_FRESH_VERSION" ]; then
        warn "A stale trw-mcp is first on your PATH: $resolved_ver ($resolved),"
        warn "shadowing the freshly-installed $TRW_FRESH_VERSION — the new version will NOT run until fixed."
        warn "  Fix: remove the stale one ('pip uninstall trw-mcp' in its environment), or run:"
        warn_raw "      rm $(printf '%q' "$resolved")"
        warn "       or put ~/.local/bin ahead of it in PATH, then reconnect the MCP client."
    fi
}

_pipx_venv_python() {
    # Best-effort resolution of the interpreter INSIDE the pipx venv pipx just
    # created/reused for trw-mcp (C06) — this, not $PYTHON (the bootstrap
    # interpreter), is what install-trw.py must install the bundled wheels
    # into. Prints nothing (caller falls back to $PYTHON) if it cannot be
    # found. Always returns 0: this is called as `VAR=$(_pipx_venv_python)`
    # under `set -e`, where a nonzero exit would abort the whole script.
    local venvs_dir cand
    venvs_dir=$(pipx environment --value PIPX_LOCAL_VENVS 2>/dev/null || true)
    [ -n "$venvs_dir" ] || venvs_dir="$HOME/.local/pipx/venvs"
    cand="$venvs_dir/trw-mcp/bin/python"
    [ -x "$cand" ] && echo "$cand"
    return 0
}

_uv_tool_venv_python() {
    # Same idea as _pipx_venv_python but for `uv tool install`'s per-tool venv
    # (distinct from `uv tool dir --bin`, which is only the launcher-symlink
    # dir). Always returns 0 — see _pipx_venv_python for why.
    local tools_dir cand
    tools_dir=$(uv tool dir 2>/dev/null || true)
    [ -n "$tools_dir" ] || tools_dir="$HOME/.local/share/uv/tools"
    cand="$tools_dir/trw-mcp/bin/python"
    [ -x "$cand" ] && echo "$cand"
    return 0
}

_install_via_managed_venv() {
    # Isolated, PEP 668-immune, no third-party tool. Returns 0 on success.
    _rung_log "managed venv ($TRW_TOOL_VENV)"
    "$PYTHON" -m venv "$TRW_TOOL_VENV" >>"$RUNG_LOG" 2>&1 || return 1
    "$TRW_TOOL_VENV/bin/python" -m pip install --upgrade --quiet pip >>"$RUNG_LOG" 2>&1 || true
    "$TRW_TOOL_VENV/bin/python" -m pip install --upgrade --quiet "$TRW_MCP_SPEC" >>"$RUNG_LOG" 2>&1 || return 1
    TRW_FRESH_VERSION=$(_pkg_ver "$TRW_TOOL_VENV/bin/python")
    _expose_trw_bin "$TRW_TOOL_VENV/bin"
}

_install_via_uv() {
    # Deepest fallback for when even `python3 -m venv` is unavailable (e.g. a
    # Debian/Ubuntu base without the python3-venv package / ensurepip). uv brings
    # its own isolation (and a Python if needed), sidestepping PEP 668.
    _rung_log "uv"
    if ! command -v uv >/dev/null 2>&1; then
        curl -LsSf https://astral.sh/uv/install.sh | env UV_NO_MODIFY_PATH=1 sh >>"$RUNG_LOG" 2>&1 || return 1
        local d
        for d in "$HOME/.local/bin" "$HOME/.cargo/bin"; do
            [ -x "$d/uv" ] && export PATH="$d:$PATH"
        done
    fi
    command -v uv >/dev/null 2>&1 || { echo "uv is not installed and could not be fetched" >>"$RUNG_LOG"; return 1; }
    # C05: --force reinstalls the tool venv from $TRW_MCP_SPEC whether trw-mcp
    # is absent or already present, on ONE code path. The prior `install ||
    # upgrade` fallback re-ran `uv tool upgrade trw-mcp` (the bare name, no
    # extras) on an existing install, so an in-place upgrade never gained the
    # extras the fresh-install rung got.
    uv tool install --force "$TRW_MCP_SPEC" >>"$RUNG_LOG" 2>&1 || return 1
    # Resolve the launcher dir: an explicit UV_TOOL_BIN_DIR wins, then `uv tool
    # dir --bin` (absent on older uv), then uv's default. Version discovery is
    # BEST EFFORT and must never decide the rung's outcome — the install above
    # already succeeded. The previous form ended the function on
    # `[ -x "$uvbin/trw-mcp" ] && TRW_FRESH_VERSION=...`, so whenever the
    # launcher sat somewhere else (older uv, custom UV_TOOL_BIN_DIR) the
    # function returned 1 after a successful install and the bootstrap reported
    # "Could not install trw-mcp".
    local uvbin resolved
    uvbin="${UV_TOOL_BIN_DIR:-}"
    [ -n "$uvbin" ] || uvbin=$(uv tool dir --bin 2>/dev/null || true)
    [ -n "$uvbin" ] || uvbin="$HOME/.local/bin"
    case ":$PATH:" in *":$uvbin:"*) : ;; *) export PATH="$uvbin:$PATH"; _persist_bindir "$uvbin" ;; esac
    # F9 (2026-09-07 review): only a launcher in a directory THIS run controls
    # may supply the fresh version. A bare `command -v trw-mcp` can resolve a
    # stale shadow earlier on PATH, and the shadow guard would then compare the
    # shadow against itself and stay silent. Unknown location => empty
    # TRW_FRESH_VERSION => the guard no-ops instead of self-confirming.
    local resolved="" d
    for d in "$uvbin" "$HOME/.local/bin" "$HOME/.cargo/bin"; do
        if [ -x "$d/trw-mcp" ]; then resolved="$d/trw-mcp"; break; fi
    done
    if [ -n "$resolved" ]; then
        TRW_FRESH_VERSION=$("$resolved" --version 2>/dev/null | awk '{print $NF}' || true)
    else
        warn "uv installed trw-mcp but its launcher is not under $uvbin — check 'uv tool dir --bin' is on your PATH"
    fi
    return 0
}

_upgrade_in_place_with_uv() {
    # An install in an environment with no pip of its own (uv's tool venvs): upgrade THAT environment with uv. Where
    # pip exists and refused, its refusal (PEP 668, a dependency, no network) stands: routing around it is exactly
    # how a second install appears.
    "$PYTHON" -m pip --version >/dev/null 2>&1 </dev/null && return 1
    command -v uv >/dev/null 2>&1 || return 1
    _rung_log "uv pip install into $PYTHON"
    uv pip install --python "$PYTHON" --upgrade "$TRW_MCP_SPEC" >>"$RUNG_LOG" 2>&1
}

# An existing install (TRW_IN_PLACE) is upgraded where it lives or not at all: the isolated rungs below build a NEW
# environment, which clients launching the old one would never run.
if "$PYTHON" -m pip install --upgrade --quiet "$TRW_MCP_SPEC" >"$PIP_LOG" 2>&1; then
    step "trw-mcp installed"
    TRW_TARGET_PYTHON="$PYTHON"
elif "$PYTHON" -m pip install --upgrade --quiet --user "$TRW_MCP_SPEC" >>"$PIP_LOG" 2>&1; then
    step "trw-mcp installed (--user)"
    TRW_TARGET_PYTHON="$PYTHON"
elif [ "$TRW_IN_PLACE" = true ] && _upgrade_in_place_with_uv; then
    step "trw-mcp upgraded in place (uv pip, $PYTHON)"
    TRW_TARGET_PYTHON="$PYTHON"
elif [ "$TRW_IN_PLACE" != true ] && command -v pipx >/dev/null 2>&1 && { _rung_log "pipx"; pipx install "$TRW_MCP_SPEC" --force >>"$RUNG_LOG" 2>&1; }; then
    # PEP 668 resilience: pipx installs into an isolated venv (no system
    # mutation) and puts the `trw-mcp` console script on PATH. `--force`
    # reinstalls from $TRW_MCP_SPEC whether trw-mcp is absent or already
    # present, on ONE code path (C05: the prior `install || upgrade` fallback
    # re-ran a bare `pipx upgrade trw-mcp` on an existing install, so an
    # in-place upgrade never gained the extras a fresh install got).
    _add_pipx_bindir_to_path
    step "trw-mcp installed (pipx)"
    TRW_TARGET_PYTHON=$(_pipx_venv_python)
elif [ "$TRW_IN_PLACE" != true ] && _install_via_managed_venv; then
    # PEP 668-safe primary fallback — a dedicated venv, no third-party tool
    # required (`python3 -m venv` ships with CPython/Homebrew Python). This is
    # the rung that catches the common macOS + Homebrew case (externally
    # managed, no pipx) that previously dead-ended at the fail branch below.
    step "trw-mcp installed (isolated venv: $TRW_TOOL_VENV)"
    TRW_TARGET_PYTHON="$TRW_TOOL_VENV/bin/python"
elif [ "$TRW_IN_PLACE" != true ] && _install_via_uv; then
    # Deepest fallback — even `python3 -m venv` unavailable (e.g. Debian/Ubuntu
    # without the python3-venv package). uv brings its own isolation + Python.
    step "trw-mcp installed (uv tool)"
    TRW_TARGET_PYTHON=$(_uv_tool_venv_python)
elif [ "$ALLOW_SYSTEM_PYTHON" = true ] && "$PYTHON" -m pip install --upgrade --quiet --user --break-system-packages "$TRW_MCP_SPEC" >>"$PIP_LOG" 2>&1; then
    # installer-client-4: only with an explicit --allow-system-python opt-in —
    # never silently mutate the OS-managed Python.
    warn "Installed with --break-system-packages (system Python modified at your request)"
    step "trw-mcp installed (--break-system-packages)"
    TRW_TARGET_PYTHON="$PYTHON"
else
    echo ""
    # Show what the last isolated rung that ran actually said, then name PEP 668 only when pip itself reported it:
    # the real cause (an unresolvable dependency, no network, a missing python3-venv) is what the user can act on.
    if [ -s "$RUNG_LOG" ]; then
        warn "Last isolated-install attempt output:"
        tail -20 "$RUNG_LOG" | sed 's/^/    /'
        echo ""
    fi
    warn "pip install output:"
    tail -20 "$PIP_LOG" | sed 's/^/    /'
    echo ""
    if [ "$TRW_IN_PLACE" = true ]; then
        # No isolated rung ran, by design: say so, and give the ways forward (never re-suggest the command that failed).
        warn "Could not upgrade trw-mcp in place: it is installed in $PYTHON, and pip there failed (output above)."
        warn "No second copy was installed: a copy beside the existing one would not replace the one already in use."
        if grep -qs 'externally-managed-environment' "$PIP_LOG" && [ "$ALLOW_SYSTEM_PYTHON" != true ]; then
            # E2E-INC-139 a: this pip command is the one that just failed; do not hand it back as the retry.
            warn "    • that Python is externally managed (PEP 668): re-run with --allow-system-python to let pip modify it,"
            warn "      or with TRW_PYTHON=<a venv python> to upgrade a copy that pip may modify"
        else
            warn_raw "    • retry it yourself:  $(printf '%q' "$PYTHON") -m pip install --upgrade $(printf '%q' "$TRW_MCP_SPEC")"
        fi
        warn "    • or install a separate copy on purpose:  TRW_PYTHON=<another python>  (clients keep launching the current one until you repoint them)"
        fail "Could not upgrade trw-mcp in place (see the options above)."
    fi
    # Reached only when pip, --user, pipx, the managed venv, AND uv all failed —
    # a genuinely locked-down environment. Be honest that the automatic isolated
    # fallbacks already ran, and give manual options (never re-suggest the exact
    # command that just failed).
    if grep -qs 'externally-managed-environment' "$PIP_LOG"; then
        warn "Could not install trw-mcp — your Python is externally managed (PEP 668), so system and --user pip"
        warn "were refused, and the automatic isolated-install fallbacks (venv, uv) did not succeed (output above)."
    else
        warn "Could not install trw-mcp — pip, --user, pipx, the isolated venv and uv all failed."
        warn "The errors above are the cause; this is not a PEP 668 refusal."
    fi
    warn "Manual options:"
    warn "    • virtualenv:   python3 -m venv .venv && . .venv/bin/activate && pip install '$TRW_MCP_SPEC'"
    warn "    • uv:           curl -LsSf https://astral.sh/uv/install.sh | sh  &&  uv tool install '$TRW_MCP_SPEC'"
    if command -v pipx >/dev/null 2>&1; then
        warn "    • pipx (present): pipx install '$TRW_MCP_SPEC'"
    fi
    if [ "$ALLOW_SYSTEM_PYTHON" != true ]; then
        warn "    • modify system Python at your own risk: re-run with --allow-system-python"
    fi
    fail "Could not install trw-mcp (see the manual options above)."
fi
fi

# Option A shadow guard: verify the PATH-resolved trw-mcp is the one we just
# installed. When the winning rung did not already record a version, derive it
# from $TRW_TARGET_PYTHON — the interpreter that OWNS this run's install — and
# never from $PYTHON. The pipx/uv rungs install into an isolated venv the
# bootstrap $PYTHON never touches, so reading $PYTHON there reported whatever
# unrelated trw-mcp it happened to hold and INVERTED the warning ("stale 2.0.1
# shadowing freshly-installed 1.0.5"). If the owning interpreter cannot be
# resolved the version stays empty and the guard safely no-ops — an unknown
# fresh version must not produce a confident, wrong claim. The guard helper is
# defined inside the skipped Step 2 block under --verify-only (no install
# happened, so there is nothing to shadow-check).
if [ "$VERIFY_ONLY" != true ]; then
    if [ -z "${TRW_FRESH_VERSION:-}" ] && [ -n "${TRW_TARGET_PYTHON:-}" ]; then
        TRW_FRESH_VERSION=$(_pkg_ver "$TRW_TARGET_PYTHON")
    fi
    _verify_no_stale_shadow
fi
# C06: export the interpreter that owns the trw-mcp binary this run installed
# so install-trw.py (Step 6) targets it instead of unconditionally assuming
# $PYTHON (the bootstrap interpreter, which a pipx/uv/managed-venv rung never
# touches). Empty when no rung resolved one (e.g. --verify-only, or a resolver
# helper came up empty) — install-trw.py's own check_python_version() falls
# back to sys.executable in that case.
[ -n "${TRW_TARGET_PYTHON:-}" ] || TRW_TARGET_PYTHON="$PYTHON"
export TRW_TARGET_PYTHON

# ── Step 3: Authenticate ─────────────────────────────────────────────
# Auth is required before the installer can be downloaded.
# PRD-FIX-067: Reuse existing API key from prior installation.

# Extract the platform_api_key value from a YAML credential file. Matches the
# exact quoting trw-mcp writes (platform_api_key: "<key>"); tolerates unquoted.
read_stored_key() {
    [ -f "$1" ] || return 0
    sed -n 's/^platform_api_key: *"\{0,1\}\([^"]*\)"\{0,1\}/\1/p' "$1" | head -1
}

# The computer-wide sign-in (~/.trw/credentials.yaml, written by 'trw-mcp auth login' or
# 'trw-mcp auth promote'): the last stored-key layer, below this project's own files, so one
# sign-in serves every project on this computer. Read only when it is a regular file (not a
# symlink) the current user owns with no group/other permission bits -- the same rule trw-mcp
# applies at runtime. Sets MACHINE_KEY (no subshell, so a refusal warning is recorded).
MACHINE_CREDENTIALS="$HOME/.trw/credentials.yaml"
MACHINE_KEY=""
load_machine_key() {
    MACHINE_KEY=""
    [ -e "$MACHINE_CREDENTIALS" ] || [ -L "$MACHINE_CREDENTIALS" ] || return 0
    if [ -L "$MACHINE_CREDENTIALS" ] || [ ! -f "$MACHINE_CREDENTIALS" ] || [ ! -O "$MACHINE_CREDENTIALS" ]; then
        warn "Ignoring ~/.trw/credentials.yaml: it is not a regular file you own"
        return 0
    fi
    case "$(ls -ld "$MACHINE_CREDENTIALS" 2>/dev/null)" in
        -rw-------*|-r--------*) ;;
        *) warn "Ignoring ~/.trw/credentials.yaml: permissions looser than 0600 (fix: chmod 600 ~/.trw/credentials.yaml)"
           return 0 ;;
    esac
    MACHINE_KEY=$(sed -n 's/^platform_api_key: *"\{0,1\}\([^"]*\)"\{0,1\}/\1/p' "$MACHINE_CREDENTIALS" | head -1)
}

# PRD-SEC-005-FR06: read the bearer credential from the ignored, 0600
# credentials.yaml first, falling back to the deprecated config.yaml for
# backward compatibility with installs that predate the migration.
KEY_REPORTED=false   # one line says where the key came from (FB-INSTALL-17)
if [ -z "$API_KEY" ]; then
    PRIOR_KEY=$(read_stored_key .trw/credentials.yaml)
    if [ -n "$PRIOR_KEY" ]; then
        API_KEY="$PRIOR_KEY"
        KEY_REPORTED=true
        step "Using existing API key from .trw/credentials.yaml"
    fi
fi

if [ -z "$API_KEY" ]; then
    PRIOR_KEY=$(read_stored_key .trw/config.yaml)
    if [ -n "$PRIOR_KEY" ]; then
        API_KEY="$PRIOR_KEY"
        KEY_REPORTED=true
        step "Using existing API key from .trw/config.yaml (deprecated — run 'trw-mcp update-project .' to migrate)"
    fi
fi

if [ -z "$API_KEY" ]; then
    load_machine_key
    if [ -n "$MACHINE_KEY" ]; then
        API_KEY="$MACHINE_KEY"
        KEY_REPORTED=true
        step "Using your computer-wide sign-in from ~/.trw/credentials.yaml"
    fi
fi

# PRD-SEC-006-FR08: --allow-unauthenticated installs the source-available BSL
# package without account creation. The full bundled installer (skills, agents,
# hooks, CLAUDE.md scaffold) requires auth; the unauthenticated path gets the
# trw-mcp package only.
# --headless never starts a sign-in: with no key from a flag, TRW_API_KEY or a stored credential it takes the
# package-only path and says so (the JSON result carries the remedy).
# A local installer file is the one no-key path that still runs the installer (with --skip-auth), so it takes
# precedence over the package-only fallback and shares the "no key, no sign-in" rule below.
NO_KEY_SKIP_AUTH=false
if [ -z "$API_KEY" ] && [ -n "$LOCAL_INSTALLER" ] && [ "$UNAUTHENTICATED" != true ]; then
    NO_KEY_SKIP_AUTH=true
    warn "Local installer and no API key: skipping sign-in, running the installer with --skip-auth"
elif [ -z "$API_KEY" ] && [ "$HEADLESS" = true ] && [ "$UNAUTHENTICATED" != true ]; then
    UNAUTHENTICATED=true
    TRW_AUTH_SKIPPED=true
    warn "Headless and no API key (set TRW_API_KEY or pass --api-key): skipping sign-in, installing the package only"
fi

if [ -z "$API_KEY" ] && [ "$UNAUTHENTICATED" = true ]; then
    warn "Unauthenticated mode — installing the trw-mcp package only"
    warn "Framework content (skills, agents, hooks) requires an account: https://trwframework.com/signup"
    echo ""
    if [ -d .git ] || git rev-parse --git-dir >/dev/null 2>&1; then
        # Guard the success line behind the actual init result — the ||-chain
        # previously printed "trw-mcp package installed" even when both init
        # attempts failed and only warn() ran.
        if trw-mcp init-project . >/dev/null 2>&1 || \
           "$PYTHON" -m trw_mcp.server init-project . >/dev/null 2>&1; then
            step "trw-mcp package installed"
        else
            warn "Project init failed (run 'trw-mcp init-project .' manually)"
        fi
    else
        warn "Not a git repository — skipping project init"
    fi
    # Never silently keyword-only: this branch skips install-trw.py, which is
    # what installs embeddings by default.
    warn "Semantic embeddings are not installed on this path — recall is keyword-only."
    warn_raw "  Fix:  $(printf '%q' "$PYTHON") -m pip install 'trw-memory[embeddings]'"
    warn "  then check: trw-mcp doctor ."
    echo ""
    show_package_only_box
    echo ""
    exit 0
fi

if [ -z "$API_KEY" ] && [ "$VERIFY_ONLY" = true ] && [ "$NO_KEY_SKIP_AUTH" != true ]; then
    fail "--verify-only requires an API key: pass --api-key KEY, set TRW_API_KEY, or run 'trw-mcp auth login' once to populate .trw/credentials.yaml."
fi

if [ "$NO_KEY_SKIP_AUTH" = true ]; then
    :  # no sign-in: the local installer runs with --skip-auth and no key
elif [ -z "$API_KEY" ]; then
    echo ""
    info "Authentication required"
    echo -e "    ${DIM}Sign in to your TRW account to continue.${NC}"
    echo -e "    ${DIM}No account yet? Create one free at https://trwframework.com/signup${NC}"
    echo ""

    # Ensure .trw/ exists for auth to write config
    mkdir -p .trw

    # Probe for a controlling terminal (same guard as Step 6). Device auth reads
    # from /dev/tty, which does not exist in CI / non-interactive hosts — fail
    # with actionable guidance instead of a confusing device-auth error.
    if ! (exec </dev/tty) 2>/dev/null; then
        fail "No terminal available for interactive sign-in. Pass --api-key KEY or set TRW_API_KEY (CI), or run 'trw-mcp auth login' from a terminal."
    fi

    # Run device auth flow — connect to /dev/tty for interactive input
    # even when piped via curl | bash.
    if trw-mcp auth login </dev/tty 2>&1; then
        step "Authentication complete"
    elif "$PYTHON" -m trw_mcp.server auth login </dev/tty 2>&1; then
        step "Authentication complete"
    else
        echo ""
        fail "Authentication failed. Sign in, or create a free account at https://trwframework.com/signup
    Then run: trw-mcp auth login"
    fi

    # Extract API key from the credential file written by auth login
    # (PRD-SEC-005-FR06: credentials.yaml first, config.yaml fallback).
    API_KEY=$(read_stored_key .trw/credentials.yaml)
    if [ -z "$API_KEY" ]; then
        API_KEY=$(read_stored_key .trw/config.yaml)
    fi
    if [ -z "$API_KEY" ]; then
        load_machine_key   # the sign-in was saved for every project on this computer
        API_KEY="$MACHINE_KEY"
    fi

    if [ -z "$API_KEY" ]; then
        fail "Authentication succeeded but no API key was saved. Run: trw-mcp auth login"
    fi
else
    [ "$KEY_REPORTED" = true ] || step "Using provided API key"
fi

# ── Step 4: Resolve the release + its published checksum ─────────────
# PRD-SEC-006-FR02: pin a version (TRW_VERSION / --version) and fetch its
# published SHA-256 from the releases API so the downloaded installer can be
# verified before execution. An unpinned install resolves "latest".
EXPECTED_SHA256="${TRW_INSTALLER_SHA256:-}"
# Version actually resolved by the metadata call. Step 5 pins its download to
# THIS version so both calls describe the same release — two independent
# "latest" resolutions raced a concurrent publish (metadata=vN, download=vN+1)
# into a false checksum-mismatch hard abort.
RESOLVED_VERSION="$TRW_VERSION"
if [ -z "$EXPECTED_SHA256" ] && [ -z "$LOCAL_INSTALLER" ]; then
    if [ -n "$TRW_VERSION" ]; then
        META_URL="${API_URL}/v1/releases/${TRW_VERSION}/artifact"
    else
        META_URL="${API_URL}/v1/releases/latest"
    fi
    META_JSON=$(curl -sfL -H @<(auth_header) "$META_URL" 2>/dev/null || true)
    if [ -n "$META_JSON" ]; then
        if [ -z "$RESOLVED_VERSION" ]; then
            RESOLVED_VERSION=$(printf '%s' "$META_JSON" | grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' | grep -m1 . | sed 's/.*: *"\([^"]*\)"/\1/' || true)
        fi
        # Verify the installer ONLY against installer_checksum — the SHA-256 of
        # install-trw.py itself. NEVER fall back to artifact_checksum: that is the
        # SHA of the release ZIP, a DIFFERENT file, so comparing installer bytes
        # against it always mismatches → self-DoS. If installer_checksum is absent
        # (older release metadata), EXPECTED_SHA256 stays empty and we proceed on
        # TLS only (fail-open for backward compat — see Step 5). Pure grep — no jq
        # dependency. ``|| true`` keeps a no-match from tripping ``set -e``;
        # ``grep -m1`` avoids the SIGPIPE a trailing ``head`` would raise under
        # ``pipefail``.
        EXPECTED_SHA256=$(printf '%s' "$META_JSON" | grep -oE '"installer_checksum"[[:space:]]*:[[:space:]]*"[0-9a-fA-F]{64}"' | grep -oE '[0-9a-fA-F]{64}' | grep -m1 . || true)
    fi
fi

# ── Step 5: Download the full installer (auth-gated) ─────────────────
[ -n "$LOCAL_INSTALLER" ] || info "Downloading installer..."
INSTALLER_TMP=$(mktemp "${TMPDIR:-/tmp}/install-trw-XXXXXX")
# Bash allows only ONE EXIT trap per process, so this REPLACES the earlier
# PIP_LOG-only trap. Re-register to clean up BOTH temp files.
trap '_trw_on_exit; rm -f "$PIP_LOG" "$INSTALLER_TMP"' EXIT

if [ -n "$LOCAL_INSTALLER" ]; then
    # PRD-INFRA-209: a local install-trw.py replaces the download — no release metadata, no fetch. Only a readable
    # regular file (never a symlink, which could be swapped between this check and the copy) is accepted.
    if [ -L "$LOCAL_INSTALLER" ] || [ ! -f "$LOCAL_INSTALLER" ] || [ ! -r "$LOCAL_INSTALLER" ]; then
        fail "TRW_INSTALLER_FILE=$LOCAL_INSTALLER is not a readable file"
    fi
    cp "$LOCAL_INSTALLER" "$INSTALLER_TMP" || fail "TRW_INSTALLER_FILE=$LOCAL_INSTALLER is not a readable file"
    if command -v sha256sum >/dev/null 2>&1; then
        LOCAL_SHA256=$(sha256sum "$INSTALLER_TMP" | awk '{print $1}')
    elif command -v shasum >/dev/null 2>&1; then
        LOCAL_SHA256=$(shasum -a 256 "$INSTALLER_TMP" | awk '{print $1}')
    else
        LOCAL_SHA256=""
    fi
    step "Using local installer $LOCAL_INSTALLER (sha256 ${LOCAL_SHA256:-unavailable})"
else
    # Pin the download to the SAME release the metadata call resolved (explicit
    # --version, or the version reported by /releases/latest). Only when neither
    # is known (metadata fetch failed / out-of-band TRW_INSTALLER_SHA256 pin with
    # no --version) fall back to the floating "latest" URL.
    if [ -n "$RESOLVED_VERSION" ]; then
        INSTALLER_URL="${API_URL}/v1/releases/${RESOLVED_VERSION}/installer"
    else
        INSTALLER_URL="${API_URL}/v1/releases/latest/installer"
    fi

    HTTP_CODE=$(curl -sfL -w '%{http_code}' \
        -H @<(auth_header) \
        "$INSTALLER_URL" \
        -o "$INSTALLER_TMP" 2>/dev/null || true)

    if [ "$HTTP_CODE" = "200" ]; then
        # curl -f -L follows redirects and reports the FINAL status, so a real
        # download always lands on 200 (a dangling 302 cannot occur with -f -L).
        step "Installer downloaded"
    elif [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; then
        fail "API key rejected (HTTP $HTTP_CODE). Run: trw-mcp auth login"
    elif [ "$HTTP_CODE" = "404" ]; then
        fail "No release found. The platform may be updating — try again in a few minutes."
    elif [ "$HTTP_CODE" = "410" ]; then
        fail "This release was withdrawn (security or critical defect). Omit --version to get the latest release, or pick another version."
    elif [ "$HTTP_CODE" = "000" ] || [ -z "$HTTP_CODE" ]; then
        # curl emits 000 for "no HTTP transaction" (DNS/connect/TLS failure) — a
        # network/proxy problem, not an HTTP status worth echoing back.
        fail "Could not reach the release server. Check your network/proxy (HTTPS_PROXY) and try again."
    else
        fail "Failed to download installer (HTTP ${HTTP_CODE:-timeout}). Check your network and try again."
    fi
fi

# Sanity-check: the downloaded file should be a Python script
if ! head -1 "$INSTALLER_TMP" | grep -q "python"; then
    fail "Downloaded file is not a valid installer. Try again or contact support."
fi

# PRD-SEC-006-FR02: verify the installer against the published SHA-256 before
# executing it. A mismatch ABORTS — we never run an unverified installer. When
# no checksum is published (older release metadata), warn and proceed (TLS is
# the floor) so a valid release is never rejected (NFR02).
if [ -n "$EXPECTED_SHA256" ]; then
    if command -v sha256sum >/dev/null 2>&1; then
        ACTUAL_SHA256=$(sha256sum "$INSTALLER_TMP" | awk '{print $1}')
    elif command -v shasum >/dev/null 2>&1; then
        ACTUAL_SHA256=$(shasum -a 256 "$INSTALLER_TMP" | awk '{print $1}')
    else
        ACTUAL_SHA256=""
        # sweep-4: under fail-closed, inability to verify a published checksum is
        # itself a hard abort (no local sha tooling = no integrity proof).
        if [ -n "$REQUIRE_CHECKSUM" ]; then
            fail "TRW_REQUIRE_INSTALLER_CHECKSUM set but no sha256sum/shasum is available to verify — aborting."
        fi
        warn "No sha256sum/shasum available — cannot verify installer checksum"
    fi
    if [ -n "$ACTUAL_SHA256" ]; then
        # Case-insensitive compare.
        if [ "$(printf '%s' "$ACTUAL_SHA256" | tr 'A-Z' 'a-z')" = "$(printf '%s' "$EXPECTED_SHA256" | tr 'A-Z' 'a-z')" ]; then
            step "Installer checksum verified (SHA-256)"
        else
            fail "Installer checksum mismatch — aborting. Expected ${EXPECTED_SHA256}, got ${ACTUAL_SHA256}. Do not run this installer."
        fi
    fi
elif [ -n "$REQUIRE_CHECKSUM" ]; then
    # sweep-4 supply-chain honesty: enterprise fail-closed lever. With no
    # published installer_checksum (and no TRW_INSTALLER_SHA256 pin), there is no
    # integrity proof beyond TLS — abort rather than the default warn+proceed.
    fail "No published installer checksum available and TRW_REQUIRE_INSTALLER_CHECKSUM is set — aborting (fail-closed). Pin TRW_INSTALLER_SHA256=<sha> from a trusted channel, or unset the requirement to proceed on TLS only."
else
    if [ -n "$LOCAL_INSTALLER" ]; then
        warn "unverified local installer — set TRW_INSTALLER_SHA256=<sha> to pin it"
    else
        warn "No published checksum available — proceeding with TLS verification only"
        # With no checksum to verify, add a cheap size floor to catch a truncated
        # download or an error-page body that slipped past the shebang sniff. The
        # real installer is ~4MB; a sub-100KB response is never a valid installer.
        INSTALLER_SIZE=$(wc -c < "$INSTALLER_TMP" | tr -d '[:space:]')
        if [ "${INSTALLER_SIZE:-0}" -lt 100000 ]; then
            fail "Downloaded installer is implausibly small (${INSTALLER_SIZE} bytes) — likely a truncated or error-page response. Aborting."
        fi
    fi
fi

# installer-bundle hardening: a read-only mode stops here, after checksum
# verification, before Step 6 ever runs the installer. This is the real path
# `make verify-published VERSION=<v>` drives post-publish (scripts/publish-release.sh).
if [ "$VERIFY_ONLY" = true ]; then
    if [ -n "$LOCAL_INSTALLER" ] && [ -z "$EXPECTED_SHA256" ]; then
        fail "--verify-only: no TRW_INSTALLER_SHA256 pin to verify $LOCAL_INSTALLER against."
    fi
    if [ -z "${ACTUAL_SHA256:-}" ]; then
        fail "--verify-only: no published installer checksum found for version ${RESOLVED_VERSION:-latest} — cannot verify."
    fi
    step "Verify-only OK — installer checksum matches for version ${RESOLVED_VERSION:-latest} (${ACTUAL_SHA256})"
    exit 0
fi

# ── Step 6: Run the full installer ───────────────────────────────────
info "Running installer..."
echo ""

# PRD-SEC-006-FR04: pass the API key via the TRW_API_KEY env var, never as an
# argv argument (which is visible in `ps`). PRD-SEC-004-FR03: forward any
# unrecognized flags (e.g. --no-telemetry) to the installer.
# ${EXTRA_ARGS[@]+...} guards the empty-array case under `set -u`.
# Redirect stdin from /dev/tty so install-trw.py detects interactive mode under
# curl | bash; fall back to inherited stdin when no controlling terminal exists
# (e.g. CI / non-interactive hosts) instead of aborting.
if [ "$HEADLESS" = true ]; then
    # No tty, no stdin: the installer runs in its script mode and every line it prints is scrubbed into the run log.
    HEADLESS_RC=0
    TRW_API_KEY="$API_KEY" "$PYTHON" "$INSTALLER_TMP" --skip-auth --script ${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"} </dev/null 2>&1 \
        | { _redact_stream | tee -a "${TRW_LOG:-/dev/null}"; } || HEADLESS_RC=$?
    [ "$HEADLESS_RC" -eq 0 ] || fail "The installer exited with status $HEADLESS_RC (see the run log)."
elif (exec </dev/tty) 2>/dev/null; then
    # _run_logged keeps the installer on a terminal and copies everything it prints into the run log.
    TRW_API_KEY="$API_KEY" _run_logged /dev/tty "$PYTHON" "$INSTALLER_TMP" --skip-auth ${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"}
else
    TRW_API_KEY="$API_KEY" _run_logged /dev/stdin "$PYTHON" "$INSTALLER_TMP" --skip-auth ${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"}
fi

# E2E-INC-146: write the cross-host sync opt-ins the flags asked for. The installer has just rewritten config.yaml and keeps
# keys it does not know, so this runs after it. Never through a symlink; the file is rewritten in place (same inode and mode).
_set_config_bool() {
    local cfg=.trw/config.yaml tmp
    if [ ! -f "$cfg" ] || [ -L "$cfg" ] || [ -L .trw ]; then
        warn "$1 not set: there is no regular .trw/config.yaml in this directory"
        return 0
    fi
    tmp=$(mktemp "${TMPDIR:-/tmp}/trw-config-XXXXXX") || return 0
    { grep -v "^$1:" "$cfg" || true; printf '%s: %s\n' "$1" "$2"; } > "$tmp" && cat "$tmp" > "$cfg"
    rm -f "$tmp"
}
[ -z "$SHARE_LEARNINGS" ] || _set_config_bool learning_sharing_enabled "$SHARE_LEARNINGS"
[ -z "$TEAM_SYNC" ] || _set_config_bool team_sync_enabled "$TEAM_SYNC"

# C06: re-run the Option A shadow guard after the FULL installer, not only
# after Step 2's minimal trw-mcp install. Step 6 (install-trw.py) is what
# actually installs the bundled framework wheels — into $TRW_TARGET_PYTHON,
# which can differ from $PYTHON — so a shadow could still exist (or first
# appear, e.g. a shell profile Step 6 touched) even when the Step 2 check
# passed. $TRW_FRESH_VERSION/_verify_no_stale_shadow are no-ops when either is
# unset, so this is a safe no-op under --verify-only (never reaches this line;
# exits earlier) or when Step 2 never resolved a version.
_verify_no_stale_shadow

# Step 6 can add more TRW console scripts to the managed venv (an entitled install's packages) after the shims above
# were written, and then tells the user to run them. Give each the same launcher shim, but only beside a
# ~/.local/bin/trw-mcp that is TRW's shim into that venv, and never over a file there that is not TRW's own shim.
_shim_dir=$(sed -n 's|^exec "\(.*\)/trw-mcp" "\$@"$|\1|p' "$HOME/.local/bin/trw-mcp" 2>/dev/null | head -n 1 || true)
if [ -n "$_shim_dir" ] && grep -qsF "TRW launcher shim" "$HOME/.local/bin/trw-mcp"; then
    for _exe in "$_shim_dir"/trw-*; do
        _exe=${_exe##*/}
        if [ ! -e "$HOME/.local/bin/$_exe" ] || grep -qsF "TRW launcher shim" "$HOME/.local/bin/$_exe"; then
            _expose_trw_bin "$_shim_dir" "$_exe"
        fi
    done
fi
