#!/bin/sh
# Hermes Engineer installer — https://get.abliter8.ai/hermes-engineer.sh
#
# Read this before running it. That is not a formality: you were probably told to pipe it to a
# shell, and the only reason that is reasonable is that you can read it first and it is short.
#
#   curl -fsSL https://get.abliter8.ai/hermes-engineer.sh -o install.sh
#   less install.sh && sh install.sh
#
# What it does:
#   * asks which harness (Claude Code, Codex, native Hermes, or a generic harness) and where
#   * shows a plan first, and writes nothing until you confirm
#   * renders each harness's instruction file so its skill paths resolve at the chosen root
#   * backs up anything it would overwrite as <file>.hermes-bak.<timestamp>
#
# What it will never do: use sudo, install a toolchain, edit your shell profile, contact a model
# or provider, or touch .env, auth.json, memories/ or sessions/.
#
# Re-running is safe. Nothing is deleted.
set -eu

REPO_URL="${A8_HE_REPO_URL:-https://github.com/abliter8-ai/hermes-engineer.git}"
REF="${A8_HE_REF:-main}"
MB='<!-- >>> hermes-engineer (managed) >>> -->'
ME='<!-- <<< hermes-engineer (managed) <<< -->'
STAMP=$(date +%Y%m%d-%H%M%S 2>/dev/null || echo backup)

TARGET=""
SCOPE=""
TARGET_DIR=""
ASSUME_YES=0
DRY_ONLY=0
DRY=1

# ---------------------------------------------------------------- ui helpers
say()  { printf '  %s\n' "$*"; }
step() { printf '\n▸ %s\n' "$*"; }
warn() { printf 'WARN: %s\n' "$*" >&2; }
die()  { printf '\nstopped: %s\n' "$*" >&2; exit 1; }
plan() { printf '  - %s\n' "$*"; }

usage() {
  cat <<'EOF'
Hermes Engineer installer

USAGE
  install.sh                         Interactive wizard
  install.sh --target T [options]    Non-interactive

OPTIONS
  --target  claude | codex | hermes | generic
  --scope   project | user           (claude/codex; default: project)
  --dir     PATH                     Install root for project scope (default: .)
  --ref     TAG|BRANCH               Ref to fetch when bootstrapping (default: main)
  --yes, -y                          Skip the confirmation prompt
  --dry-run                          Print the plan and exit, write nothing
  --help, -h                         Show this help

EXAMPLES
  install.sh --target claude --scope project --dir .
  install.sh --target codex  --scope user --yes
  install.sh --target hermes
EOF
}

# ------------------------------------------------------------- arg parsing
# Accepts both "--flag value" and "--flag=value".
while [ $# -gt 0 ]; do
  case "$1" in
    --target=*) TARGET=${1#*=}; shift ;;
    --target)   TARGET=${2:-}; shift 2 ;;
    --scope=*)  SCOPE=${1#*=}; shift ;;
    --scope)    SCOPE=${2:-}; shift 2 ;;
    --dir=*)    TARGET_DIR=${1#*=}; shift ;;
    --dir)      TARGET_DIR=${2:-}; shift 2 ;;
    --ref=*)    REF=${1#*=}; shift ;;
    --ref)      REF=${2:-}; shift 2 ;;
    --yes|-y)   ASSUME_YES=1; shift ;;
    --dry-run)  DRY_ONLY=1; shift ;;
    --help|-h)  usage; exit 0 ;;
    *) die "unknown argument: $1 (try --help)" ;;
  esac
done

# ------------------------------------------------- locate repo assets (ROOT)
is_repo_root() {
  [ -f "$1/templates/core-instructions.md" ] &&
  [ -d "$1/skills" ] &&
  [ -d "$1/claude-hermes-engineer" ]
}

SELF_DIR=""
case "$0" in
  */*) SELF_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" 2>/dev/null && pwd) || SELF_DIR="" ;;
esac

ROOT=""
# When run from a clone the assets are one level up from install/, or in the cwd.
if [ -n "$SELF_DIR" ] && is_repo_root "$SELF_DIR/.."; then
  ROOT=$(CDPATH= cd -- "$SELF_DIR/.." && pwd)
elif [ -n "$SELF_DIR" ] && is_repo_root "$SELF_DIR"; then
  ROOT="$SELF_DIR"
elif is_repo_root "$(pwd)"; then
  ROOT="$(pwd)"
else
  command -v git >/dev/null 2>&1 || die "git is required to bootstrap. Install git, or clone the repo and run install/install.sh."
  BOOT=$(mktemp -d 2>/dev/null || mktemp -d -t hermes-eng) || die "could not create a temp dir"
  step "Fetching Hermes Engineer ($REF)"
  git clone --depth 1 --branch "$REF" --quiet "$REPO_URL" "$BOOT/hermes-engineer" 2>/dev/null \
    || git clone --depth 1 --quiet "$REPO_URL" "$BOOT/hermes-engineer" \
    || die "git clone failed"
  ROOT="$BOOT/hermes-engineer"
fi
is_repo_root "$ROOT" || die "repo assets not found under $ROOT"

# ---------------------------------------------------------- interactive tty
read_tty() { # prompt varname
  if [ -r /dev/tty ]; then
    printf '%s' "$1" > /dev/tty
    IFS= read -r _ans < /dev/tty || _ans=""
  else
    die "no terminal for interactive input; pass --target/--scope/--dir instead."
  fi
  eval "$2=\$_ans"
}

wizard() {
  step "Which harness are you installing into?"
  say "1) Claude Code"
  say "2) Codex"
  say "3) Native Hermes (profile via the hermes CLI)"
  say "4) Generic harness (root instruction files + skills)"
  read_tty "Choose [1-4]: " _c
  case "$_c" in
    1) TARGET=claude ;;
    2) TARGET=codex ;;
    3) TARGET=hermes ;;
    4) TARGET=generic ;;
    *) die "invalid choice" ;;
  esac

  if [ "$TARGET" = claude ] || [ "$TARGET" = codex ]; then
    step "Install scope?"
    say "1) This project / a folder  (relative paths resolve at that root)"
    say "2) User-global  (~/.$TARGET: agents, commands and skills for every project)"
    read_tty "Choose [1-2]: " _s
    case "$_s" in
      1) SCOPE=project ;;
      2) SCOPE=user ;;
      *) die "invalid choice" ;;
    esac
    if [ "$SCOPE" = project ]; then
      read_tty "Install root [.]: " _d
      TARGET_DIR=${_d:-.}
    fi
  fi
}

# -------------------------------------------------------- resolve + validate
[ -z "$TARGET" ] && wizard

case "$TARGET" in
  claude|codex|hermes|generic) ;;
  *) die "invalid --target '$TARGET' (claude|codex|hermes|generic)" ;;
esac

case "$TARGET" in
  hermes)  SCOPE=na ;;
  generic) SCOPE=project; [ -z "$TARGET_DIR" ] && TARGET_DIR="." ;;
  claude|codex)
    [ -z "$SCOPE" ] && SCOPE=project
    case "$SCOPE" in
      project) [ -z "$TARGET_DIR" ] && TARGET_DIR="." ;;
      user) ;;
      *) die "invalid --scope '$SCOPE' (project|user)" ;;
    esac ;;
esac

if [ -n "$TARGET_DIR" ]; then
  mkdir -p "$TARGET_DIR" 2>/dev/null || true
  TARGET_DIR=$(CDPATH= cd -- "$TARGET_DIR" 2>/dev/null && pwd) || die "cannot resolve --dir '$TARGET_DIR'"
fi

# --------------------------------------------------------------- primitives
backup_path() { printf '%s.hermes-bak.%s' "$1" "$STAMP"; }

merge_tree() { # src dest  (merge files, back up conflicts, keep unrelated files)
  _src=$1; _dest=$2
  [ -d "$_src" ] || return 0
  if [ "$DRY" = 1 ]; then
    _n=$(find "$_src" -type f | wc -l | tr -d ' ')
    plan "copy $_n file(s) -> $_dest/   (backs up any conflicting file)"
    return 0
  fi
  find "$_src" -type f | while IFS= read -r _f; do
    _rel=${_f#"$_src"/}
    _d="$_dest/$_rel"
    [ -e "$_d" ] && mv "$_d" "$(backup_path "$_d")"
    mkdir -p "$(dirname "$_d")"
    cp "$_f" "$_d"
  done
}

install_file() { # srcfile dest
  _src=$1; _dest=$2
  if [ "$DRY" = 1 ]; then plan "write $_dest   (backs up existing)"; return 0; fi
  [ -e "$_dest" ] && mv "$_dest" "$(backup_path "$_dest")"
  mkdir -p "$(dirname "$_dest")"
  cp "$_src" "$_dest"
}

render() { # manifest baseline outfile
  _m=$1; _b=$2; _out=$3
  sed -e "s|{{SKILL_MANIFEST_PATH}}|$_m|g" -e "s|{{BASELINE_PATH}}|$_b|g" \
    "$ROOT/templates/core-instructions.md" > "$_out"
}

render_file() { # manifest baseline dest  (whole-file, with backup)
  _m=$1; _b=$2; _dest=$3
  if [ "$DRY" = 1 ]; then plan "render instruction -> $_dest   (backs up existing)"; return 0; fi
  _tmp=$(mktemp); render "$_m" "$_b" "$_tmp"; install_file "$_tmp" "$_dest"; rm -f "$_tmp"
}

render_block() { # manifest baseline targetfile  (idempotent managed block)
  _m=$1; _b=$2; _tf=$3
  if [ "$DRY" = 1 ]; then plan "merge instruction block -> $_tf   (managed markers, idempotent)"; return 0; fi
  mkdir -p "$(dirname "$_tf")"
  _body=$(mktemp); render "$_m" "$_b" "$_body"
  _keep=$(mktemp)
  if [ -f "$_tf" ]; then
    awk -v b="$MB" -v e="$ME" '
      $0==b {skip=1; next}
      $0==e {skip=0; next}
      skip!=1 {print}
    ' "$_tf" > "$_keep"
  fi
  {
    if [ -s "$_keep" ]; then cat "$_keep"; printf '\n'; fi
    printf '%s\n' "$MB"
    cat "$_body"
    printf '%s\n' "$ME"
  } > "$_tf.new"
  mv "$_tf.new" "$_tf"
  rm -f "$_body" "$_keep"
}

# ------------------------------------------------------------ per-target ops
do_claude() {
  if [ "$SCOPE" = project ]; then
    D=$TARGET_DIR
    merge_tree "$ROOT/claude-hermes-engineer/.claude" "$D/.claude"
    render_file "skills/hermes-agent-docs/references/MANIFEST.md" "hermes.json" "$D/CLAUDE.md"
    merge_tree "$ROOT/skills" "$D/skills"
    install_file "$ROOT/hermes.json" "$D/hermes.json"
  else
    C="$HOME/.claude"
    merge_tree "$ROOT/claude-hermes-engineer/.claude/agents"   "$C/agents"
    merge_tree "$ROOT/claude-hermes-engineer/.claude/commands" "$C/commands"
    merge_tree "$ROOT/skills" "$C/skills"
    install_file "$ROOT/hermes.json" "$C/hermes-engineer/hermes.json"
    render_block "$HOME/.claude/skills/hermes-agent-docs/references/MANIFEST.md" \
                 "$HOME/.claude/hermes-engineer/hermes.json" "$C/CLAUDE.md"
  fi
}

do_codex() {
  if [ "$SCOPE" = project ]; then
    D=$TARGET_DIR
    merge_tree "$ROOT/codex-hermes-engineer/.codex" "$D/.codex"
    render_file "skills/hermes-agent-docs/references/MANIFEST.md" "hermes.json" "$D/AGENTS.md"
    merge_tree "$ROOT/skills" "$D/skills"
    install_file "$ROOT/hermes.json" "$D/hermes.json"
  else
    C="$HOME/.codex"
    merge_tree "$ROOT/codex-hermes-engineer/.codex/agents" "$C/agents"
    merge_tree "$ROOT/skills" "$C/skills"
    install_file "$ROOT/hermes.json" "$C/hermes-engineer/hermes.json"
    render_block "$HOME/.codex/skills/hermes-agent-docs/references/MANIFEST.md" \
                 "$HOME/.codex/hermes-engineer/hermes.json" "$C/AGENTS.md"
  fi
}

do_generic() {
  D=$TARGET_DIR
  render_file "skills/hermes-agent-docs/references/MANIFEST.md" "hermes.json" "$D/CLAUDE.md"
  render_file "skills/hermes-agent-docs/references/MANIFEST.md" "hermes.json" "$D/AGENTS.md"
  merge_tree "$ROOT/skills" "$D/skills"
  install_file "$ROOT/hermes.json" "$D/hermes.json"
}

do_hermes() {
  PROF="$ROOT/hermes/profiles/hermes-engineer"
  if command -v hermes >/dev/null 2>&1; then
    if [ "$DRY" = 1 ]; then plan "run: hermes profile install \"$PROF\" --alias"; return 0; fi
    hermes profile install "$PROF" --alias
  else
    if [ "$DRY" = 1 ]; then plan "hermes CLI not found — will print the install command"; return 0; fi
    warn "hermes CLI not found on PATH."
    say  "Install Hermes >= 0.17, then run:"
    say  "hermes profile install \"$PROF\" --alias"
  fi
}

dispatch() {
  case "$TARGET" in
    claude)  do_claude ;;
    codex)   do_codex ;;
    generic) do_generic ;;
    hermes)  do_hermes ;;
  esac
}

# --------------------------------------------------------------- run: plan
printf 'Hermes Engineer installer\n'
[ "$DRY_ONLY" = 1 ] && say "(dry run — nothing will be changed)"
step "Plan"
say "target=$TARGET  scope=$SCOPE${TARGET_DIR:+  dir=$TARGET_DIR}"
printf '\n'
DRY=1
dispatch

if [ "$DRY_ONLY" = 1 ]; then
  printf '\nDry run complete. Nothing was changed.\n'
  exit 0
fi

if [ "$ASSUME_YES" != 1 ]; then
  printf '\n'
  read_tty "Proceed with these changes? [y/N]: " _yn
  case "$_yn" in
    y|Y|yes|YES) ;;
    *) say "Aborted. Nothing was written."; exit 0 ;;
  esac
fi

# --------------------------------------------------------------- run: apply
step "Installing"
DRY=0
dispatch

# --------------------------------------------------------------- next steps
step "Done"
case "$TARGET" in
  claude)
    if [ "$SCOPE" = project ]; then
      say "Launch Claude Code from: $TARGET_DIR"
      say "Sub-agents: config-architect, runtime-diagnostician, security-auditor, documentation-expert."
      say "Commands: /diagnose, /propose-config, /audit-security."
    else
      say "Installed user-global agents, commands and skills under ~/.claude."
      say "They are available in every Claude Code project on this machine."
    fi ;;
  codex)
    if [ "$SCOPE" = project ]; then
      say "Launch Codex from: $TARGET_DIR"
    else
      say "Installed user-global agents and skills under ~/.codex."
    fi ;;
  generic)
    say "Point your harness at: $TARGET_DIR (it loads CLAUDE.md / AGENTS.md + skills/)." ;;
  hermes)
    say "Select the profile and configure a provider:"
    say "hermes profile list"
    say "hermes setup"
    say "hermes config check" ;;
esac
printf '\n'
say "Backups (if any) were saved next to originals as *.hermes-bak.$STAMP"
