#!/bin/sh
# Excellent CLI installer — the one-line install behind
#   curl -fsSL https://excellent.so/install.sh | sh
#
# Installs the `excellent` command: the verification system that runs beside your
# coding agent and checks its work. It records each attempt, gathers independent
# evidence, and returns a checked result instead of taking the agent's word for
# "done". Safe to re-run — idempotent.
#
# Sources, in priority order:
#   1. EXCELLENT_CLI_URL=<tarball>   download + `npm i -g` a prebuilt bundle
#   2. a monorepo checkout           build (if needed) + `install-skills` (works today)
#   3. npm                           `npm install -g @excellent-so/cli` (public release)
#   4. the DigitalOcean CDN tarball  download + `npm i -g` (the "download like the
#                                    app" channel — always available, npm-org-free)
#
# Downloaded tarballs are checksum-verified against the SHA256SUMS published
# beside them. Verification FAILS CLOSED: a mismatch, a missing SHA256SUMS, a
# missing entry for the artifact, or no sha256 tool all abort the install.
#
# Env / flags:
#   --dry-run | EXCELLENT_DRY_RUN=1   print what would happen; change nothing
#   --source DIR | EXCELLENT_SOURCE   path to an Excellent monorepo checkout
#   EXCELLENT_ALLOW_UNVERIFIED=1      install without checksum verification.
#                                     Unsafe. Only for a private mirror that
#                                     publishes no SHA256SUMS.
#   --help
set -eu

PKG_NAME="@excellent-so/cli" # public npm package (release target)
# The prebuilt bundle mirrored to DO Spaces on every release (see scripts/release-cli.mjs).
# `latest` always points at the newest published build; a versioned copy sits beside it.
CDN_URL="${EXCELLENT_CDN_URL:-https://excellent-downloads.nyc3.cdn.digitaloceanspaces.com/cli/excellent-cli-latest.tgz}"
MIN_NODE_MAJOR=22
MIN_NODE_MINOR=3
MIN_NODE_VERSION="22.3.0"

# ── pretty output ──────────────────────────────────────────────────────────────
if [ -t 1 ]; then B="$(printf '\033[1m')"; DIM="$(printf '\033[2m')"; GRN="$(printf '\033[32m')"; YLW="$(printf '\033[33m')"; RED="$(printf '\033[31m')"; RST="$(printf '\033[0m')"; else B=""; DIM=""; GRN=""; YLW=""; RED=""; RST=""; fi
say() { printf '%s\n' "$*"; }
step() { printf '%s→%s %s\n' "$DIM" "$RST" "$*"; }
ok() { printf '%s✓%s %s\n' "$GRN" "$RST" "$*"; }
warn() { printf '%s!%s %s\n' "$YLW" "$RST" "$*" >&2; }
die() { printf '%s✗%s %s\n' "$RED" "$RST" "$*" >&2; exit 1; }

DRY="${EXCELLENT_DRY_RUN:-}"
SOURCE_DIR="${EXCELLENT_SOURCE:-}"
while [ $# -gt 0 ]; do
  case "$1" in
    --dry-run) DRY=1 ;;
    --source) SOURCE_DIR="${2:-}"; shift ;;
    --help|-h)
      sed -n '2,27p' "$0" | sed 's/^# \{0,1\}//'
      exit 0 ;;
    *) warn "ignoring unknown arg: $1" ;;
  esac
  shift
done
run() { if [ -n "$DRY" ]; then printf '%s[dry-run]%s %s\n' "$DIM" "$RST" "$*"; else eval "$*"; fi; }

say ""
say "${B}Excellent${RST} — installing the ${B}excellent${RST} CLI"
say "${DIM}verification for coding agents — it runs beside your agent and checks its work${RST}"
say ""

# ── prerequisites ──────────────────────────────────────────────────────────────
command -v node >/dev/null 2>&1 || die "node not found. Install Node.js >= ${MIN_NODE_VERSION} (https://nodejs.org) and re-run."
NODE_VERSION="$(node -p 'process.versions.node' 2>/dev/null || echo 0.0.0)"
NODE_MAJOR="${NODE_VERSION%%.*}"
NODE_REST="${NODE_VERSION#*.}"
NODE_MINOR="${NODE_REST%%.*}"
if ! [ "$NODE_MAJOR" -gt "$MIN_NODE_MAJOR" ] 2>/dev/null && {
  ! [ "$NODE_MAJOR" -eq "$MIN_NODE_MAJOR" ] 2>/dev/null ||
  ! [ "$NODE_MINOR" -ge "$MIN_NODE_MINOR" ] 2>/dev/null;
}; then
  die "node >= ${MIN_NODE_VERSION} required (found $(node -v 2>/dev/null))."
fi
ok "node $(node -v)"

OS="$(uname -s 2>/dev/null || echo unknown)"
ARCH="$(uname -m 2>/dev/null || echo unknown)"
step "platform: ${OS}/${ARCH}"

# ── locate a monorepo checkout (the source-install path that works today) ──────
find_checkout() {
  # explicit --source / EXCELLENT_SOURCE / EXCELLENT_HOME, else walk up from PWD.
  for cand in "$SOURCE_DIR" "${EXCELLENT_HOME:-}" "$PWD"; do
    [ -n "$cand" ] || continue
    d="$cand"
    i=0
    while [ "$i" -lt 8 ] && [ -n "$d" ] && [ "$d" != "/" ]; do
      if [ -f "$d/pnpm-workspace.yaml" ] && [ -d "$d/packages/excellent-mcp" ]; then
        printf '%s' "$d"; return 0
      fi
      d="$(dirname "$d")"; i=$((i + 1))
    done
  done
  return 1
}

install_from_source() {
  root="$1"
  cli="$root/packages/excellent-mcp/dist/cli.js"
  step "source install from ${root}"
  if [ ! -f "$cli" ]; then
    warn "CLI not built yet — building (@excellent/mcp)…"
    run "cd '$root' && (pnpm --filter @excellent/mcp build || npm --prefix '$root/packages/excellent-mcp' run build)"
  fi
  # install-skills writes the excellent + excellent-mcp PATH shims (+ ensures PATH).
  run "EXCELLENT_HOME='$root' node '$cli' install-skills"
}

verify_checksum() {
  # $1 = tarball URL, $2 = downloaded local path.
  #
  # FAIL CLOSED. Whatever these bytes contain is about to be installed globally,
  # so we refuse to install bytes we could not verify. Every "cannot verify"
  # path — an unreachable or absent SHA256SUMS, no entry for this artifact, no
  # sha256 tool, a mismatch — calls die() and exits non-zero.
  # EXCELLENT_ALLOW_UNVERIFIED=1 is the deliberate, documented opt-out.
  url="$1"; file="$2"
  base="$(basename "$url")"
  sums_url="${url%/*}/SHA256SUMS"

  if [ -n "${EXCELLENT_ALLOW_UNVERIFIED:-}" ]; then
    warn "EXCELLENT_ALLOW_UNVERIFIED=1 — installing ${base} WITHOUT checksum verification."
    return 0
  fi

  if command -v shasum >/dev/null 2>&1; then HASH_CMD="shasum -a 256"
  elif command -v sha256sum >/dev/null 2>&1; then HASH_CMD="sha256sum"
  else
    die "cannot verify ${base}: no 'shasum' or 'sha256sum' on this machine. Install one and re-run, or set EXCELLENT_ALLOW_UNVERIFIED=1 to install unverified."
  fi

  sums="$(curl -fsSL "$sums_url" 2>/dev/null)" ||
    die "cannot verify ${base}: no checksum file at ${sums_url}. Refusing to install unverified code. Set EXCELLENT_ALLOW_UNVERIFIED=1 to override."

  # SHA256SUMS lines are "<sha256>  <name>" (text) or "<sha256> *<name>" (binary).
  expected="$(printf '%s\n' "$sums" | awk -v f="$base" '{ n = $2; sub(/^\*/, "", n); if (n == f) { print $1; exit } }')"
  if [ -z "$expected" ]; then
    die "cannot verify ${base}: ${sums_url} has no entry for it. Refusing to install unverified code. Set EXCELLENT_ALLOW_UNVERIFIED=1 to override."
  fi

  actual="$($HASH_CMD "$file" | awk '{print $1}')"
  if [ "$expected" != "$actual" ]; then
    die "checksum mismatch for ${base}: expected ${expected}, got ${actual}. Refusing to install."
  fi
  ok "checksum verified (sha256 ${actual})"
}

install_from_tarball() {
  url="$1"
  step "installing bundle from ${url}"
  if [ -n "$DRY" ]; then
    printf '%s[dry-run]%s npm install -g %s (after downloading + verifying checksum)\n' "$DIM" "$RST" "$url"
    return 0
  fi
  # npm only treats a local path as a tarball when it ends in .tgz/.tar.gz, but
  # BSD/macOS mktemp only randomizes trailing X's (a template with a literal
  # suffix after them, e.g. "XXXXXX.tgz", comes back unrandomized) — so make the
  # random name first, then append the extension.
  tmpbase="$(mktemp "${TMPDIR:-/tmp}/excellent-cli-XXXXXX" 2>/dev/null)" || tmpbase="${TMPDIR:-/tmp}/excellent-cli-$$"
  tmpfile="${tmpbase}.tgz"
  mv "$tmpbase" "$tmpfile"
  trap 'rm -f "$tmpfile"' EXIT
  step "downloading tarball"
  curl -fsSL "$url" -o "$tmpfile" || die "failed to download ${url}"
  verify_checksum "$url" "$tmpfile"
  npm install -g "$tmpfile"
  rm -f "$tmpfile"
  trap - EXIT
}

install_from_npm() {
  step "installing ${PKG_NAME} from npm"
  if [ -n "$DRY" ]; then printf '%s[dry-run]%s npm install -g %s\n' "$DIM" "$RST" "$PKG_NAME"; return 0; fi
  if npm install -g "$PKG_NAME" >/dev/null 2>&1; then return 0; fi
  return 1
}

integrate_detected_agents() {
  step "configuring detected coding agents"
  if [ -n "$DRY" ]; then
    printf '%s[dry-run]%s node "$(npm root -g)/%s/dist/cli.js" install-agent-mcps\n' "$DIM" "$RST" "$PKG_NAME"
    return 0
  fi
  global_root="$(npm root -g 2>/dev/null || true)"
  packaged_cli="${global_root}/${PKG_NAME}/dist/cli.js"
  if [ -z "$global_root" ] || [ ! -f "$packaged_cli" ]; then
    warn "Excellent installed, but its package path could not be resolved; run 'excellent-mcp install-agent-mcps' after PATH refreshes."
    return 0
  fi
  if node "$packaged_cli" install-agent-mcps; then
    ok "detected coding agents configured"
  else
    warn "Excellent installed, but one or more agent configs could not be updated. Run 'excellent-mcp install-agent-mcps' for details."
  fi
}

INSTALLED=""
if [ -n "${EXCELLENT_CLI_URL:-}" ]; then
  install_from_tarball "$EXCELLENT_CLI_URL" && INSTALLED="tarball"
elif root="$(find_checkout)"; then
  install_from_source "$root" && INSTALLED="source"
elif install_from_npm; then
  INSTALLED="npm"
elif install_from_tarball "$CDN_URL"; then
  INSTALLED="cdn"
else
  die "no install source. Either run inside an Excellent checkout, set EXCELLENT_CLI_URL=<bundle>, or check your network."
fi
[ -n "$DRY" ] && INSTALLED="${INSTALLED:-dry-run}"
ok "installed (${INSTALLED})"

# Source installs already perform this phase inside install-skills. Public npm,
# custom-tarball, and CDN installs use the bundled server path instead.
if [ "$INSTALLED" != "source" ]; then
  integrate_detected_agents
fi

# ── PATH check ─────────────────────────────────────────────────────────────────
LOCAL_BIN="$HOME/.local/bin"
case ":${PATH}:" in
  *":${LOCAL_BIN}:"*) : ;;
  *) warn "${LOCAL_BIN} is not on your PATH. Add it:"; say "    export PATH=\"\$HOME/.local/bin:\$PATH\"" ;;
esac

# ── verify + next steps ────────────────────────────────────────────────────────
if [ -z "$DRY" ] && command -v excellent >/dev/null 2>&1; then
  ok "excellent → $(command -v excellent)"
elif [ -z "$DRY" ]; then
  warn "the 'excellent' command isn't on PATH yet — open a new terminal (or run 'hash -r')."
fi

say ""
say "${B}Done.${RST} Next:"
say "  ${B}cd your-repo${RST}        ${DIM}a repository you already use with your coding agent${RST}"
say "  ${B}excellent init${RST}      ${DIM}scaffold AGENTS.md + .excellent/ in this repo${RST}"
say "  ${B}excellent doctor${RST}    ${DIM}check runtime, database, providers, CLIs, PATH${RST}"
say "  ${B}excellent run --agent <role> --task <task-id>${RST}"
say "                          ${DIM}run a task and check the result${RST}"
say "  ${B}excellent --help${RST}    ${DIM}every command${RST}"
say ""
say "${DIM}Quickstart: https://excellent.so/docs/quickstart${RST}"
say ""
