#!/usr/bin/env bash
# claude-multiacc installer — macOS + Linux, idempotent, fully reversible.
# Installs an addon that PATH-shadows `claude`; NEVER touches the Claude Code app,
# its install dir, or its update machinery.
#
#   ./install.sh [--server user@host]     install or update (data untouched)
#   ./install.sh --no-server              install with NO sync target (local-only pool:
#                                         a panel/runner daemon distributes accounts)
#   ./install.sh --instance NAME          label this install's agents (default: derived
#                                         from the pool root when it is not the default)
#   ./install.sh --uninstall              remove addon (keeps ~/.claude-accounts)
#   ./install.sh --uninstall --purge-data remove addon + all account data
#
# INSTANCE-SCOPED POOLS: export CLAUDE_ACCOUNTS_ROOT / CODEX_ACCOUNTS_ROOT before
# running this and the whole install serves that pool — its LaunchAgents/cron entries
# get their own labels and carry the roots in their environment, so several app-robot
# instances on one machine never touch each other's accounts or agents.
set -u

REPO_DIR="$(cd "$(dirname "$0")" && pwd -P)"
# shellcheck source=lib/common.sh
. "$REPO_DIR/lib/common.sh"
# common.sh is sourced in claude mode (ACC_ROOT = the claude pool); the codex pool
# root is needed here too for the codex agents and uninstall/purge messaging.
CODEX_ACC_ROOT="${CODEX_ACCOUNTS_ROOT:-${CODEX_ACCOUNTS_DIR:-$HOME/.codex-accounts}}"

UNINSTALL=0
PURGE=0
SERVER_OVERRIDE=""
NO_SCHEDULE=0
INSTANCE_OVERRIDE=""
while [ $# -gt 0 ]; do
  case "$1" in
    --uninstall) UNINSTALL=1; shift ;;
    --purge-data) PURGE=1; shift ;;
    --server) SERVER_OVERRIDE="${2:?--server requires a value}"; shift 2 ;;
    --no-server) SERVER_OVERRIDE="none"; shift ;;
    --instance) INSTANCE_OVERRIDE="${2:?--instance requires a name}"; shift 2 ;;
    --no-schedule) NO_SCHEDULE=1; shift ;;
    *) echo "unknown flag: $1" >&2; exit 1 ;;
  esac
done

# ---- instance scoping -----------------------------------------------------------
# An install whose pool roots are the defaults keeps the historical labels and rc
# blocks byte-for-byte. Anything else is an INSTANCE: its agents get a suffix (two
# instances would otherwise overwrite each other's plists) and the shell rc block is
# left alone, because one interactive PATH cannot point at two pools at once.
default_pool_roots() {
  [ "$ACC_ROOT" = "$HOME/.claude-accounts" ] && [ "$CODEX_ACC_ROOT" = "$HOME/.codex-accounts" ]
}
short_hash() { # stable 8-char digest of $1, on macOS and Linux alike
  if command -v shasum >/dev/null 2>&1; then printf '%s' "$1" | shasum | cut -c1-8
  elif command -v sha1sum >/dev/null 2>&1; then printf '%s' "$1" | sha1sum | cut -c1-8
  else printf '%s' "$1" | cksum | tr -d ' ' | cut -c1-8; fi
}
if [ -n "$INSTANCE_OVERRIDE" ]; then
  case "$INSTANCE_OVERRIDE" in
    *[!A-Za-z0-9-]*) echo "--instance must be letters, digits and dashes: $INSTANCE_OVERRIDE" >&2; exit 1 ;;
  esac
  INSTANCE="$INSTANCE_OVERRIDE"
elif default_pool_roots; then
  INSTANCE=""
else
  INSTANCE="i$(short_hash "$ACC_ROOT")"
fi
# The roots are interpolated into plists and crontab lines; a quote or newline in one
# would break out of that context, so such a root is refused up front.
case "$ACC_ROOT$CODEX_ACC_ROOT" in
  *["'\"<>&"]*|*"
"*) echo "pool root contains a character that cannot be scheduled safely: $ACC_ROOT / $CODEX_ACC_ROOT" >&2; exit 1 ;;
esac

# A pool root under a temp directory is by definition gone tomorrow, but the launchd
# agent or crontab line naming it is not: it keeps firing forever against a path that
# no longer exists, one leaked set per run. That is how a Mac collected 57 orphaned
# agent sets and a server 4 stray cron blocks. EITHER root disqualifies the whole set,
# because one install writes agents for both providers: the leaked sets above named a
# temp CLAUDE root and the REAL codex pool, so they went right on polling a live usage
# endpoint every five minutes. Install the binaries for such a root; never schedule it.
ephemeral_root() { # $1 = a pool root
  case "$1" in
    /tmp/*|/private/tmp/*|/var/tmp/*|/private/var/tmp/*|/var/folders/*|/private/var/folders/*)
      return 0 ;;
    *) return 1 ;;
  esac
}
if [ "$NO_SCHEDULE" != "1" ] && { ephemeral_root "$ACC_ROOT" || ephemeral_root "$CODEX_ACC_ROOT"; }; then
  NO_SCHEDULE=1
  echo "  note: a pool root is under a temp directory (claude: $ACC_ROOT, codex: $CODEX_ACC_ROOT)" >&2
  echo "        — installing WITHOUT schedulers, which would outlive it." >&2
  echo "        Pass --no-schedule to silence this." >&2
fi

LABEL="com.claude-multiacc${INSTANCE:+.$INSTANCE}"
MARK_BEGIN="# >>> claude-multiacc >>>"
MARK_END="# <<< claude-multiacc <<<"
CRON_TAG="# claude-multiacc${INSTANCE:+ $INSTANCE}"
PLIST_LIMITS="$HOME/Library/LaunchAgents/$LABEL.limits.plist"
PLIST_HEALTH="$HOME/Library/LaunchAgents/$LABEL.health.plist"
PLIST_UPDATE="$HOME/Library/LaunchAgents/$LABEL.update.plist"
PLIST_CODEX_LIMITS="$HOME/Library/LaunchAgents/$LABEL.codex-limits.plist"
PLIST_CODEX_HEALTH="$HOME/Library/LaunchAgents/$LABEL.codex-health.plist"
PROFILED="/etc/profile.d/claude-multiacc.sh"

# Pool roots (and any sync override) for a scheduled agent, so it refreshes THIS
# instance's pool. Empty for a default install — nothing changes there.
plist_env_block() {
  [ -n "$INSTANCE" ] || return 0
  printf '  <key>EnvironmentVariables</key><dict>\n'
  printf '    <key>CLAUDE_ACCOUNTS_ROOT</key><string>%s</string>\n' "$ACC_ROOT"
  printf '    <key>CODEX_ACCOUNTS_ROOT</key><string>%s</string>\n' "$CODEX_ACC_ROOT"
  printf '  </dict>\n'
}
cron_env_prefix() {
  [ -n "$INSTANCE" ] || return 0
  printf "CLAUDE_ACCOUNTS_ROOT='%s' CODEX_ACCOUNTS_ROOT='%s' " "$ACC_ROOT" "$CODEX_ACC_ROOT"
}

is_pool_root() { # true when $1 is safe to delete as an account pool
  case "$1" in
    ''|/|"$HOME") return 1 ;;
    */.claude-accounts|*/.codex-accounts) return 0 ;;
    /*) [ -f "$1/accounts.json" ] ;;
    *) return 1 ;;
  esac
}

strip_block() { # remove our marked block from a file (portable, no sed -i)
  local f="$1"
  [ -f "$f" ] || return 0
  # Unmatched begin marker (end line hand-deleted): stripping would eat the rest
  # of the rc file. Leave it alone and say so.
  if grep -qF "$MARK_BEGIN" "$f" && ! grep -qF "$MARK_END" "$f"; then
    echo "  WARNING: $f has an unterminated claude-multiacc block — fix it by hand; not touching this file" >&2
    return 1
  fi
  awk -v b="$MARK_BEGIN" -v e="$MARK_END" '
    $0 == b { skip = 1; next }
    $0 == e { skip = 0; next }
    !skip { print }
  ' "$f" > "$f.claude-multiacc.tmp" && mv "$f.claude-multiacc.tmp" "$f"
}

path_block_body() {
  # Move (not just add) the shim dir to the front: later rc lines prepend
  # ~/.local/bin, so a plain add-once guard would leave the real binary first.
  printf 'PATH="$(printf %%s ":$PATH:" | sed '\''s|:%s/bin:|:|g; s|^:||; s|:$||'\'')"\n' "$REPO_DIR"
  printf 'export PATH="%s/bin:$PATH"\n' "$REPO_DIR"
}

append_block() { # strip then append our PATH block to a file
  local f="$1"
  strip_block "$f" || return 1   # never create a second block next to a broken one
  {
    printf '%s\n' "$MARK_BEGIN"
    path_block_body
    printf '%s\n' "$MARK_END"
  } >> "$f"
}

mac_schedule_install() {
  mkdir -p "$HOME/Library/LaunchAgents"
  cat > "$PLIST_LIMITS" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>$LABEL.limits</string>
  <key>ProgramArguments</key><array>
    <string>$REPO_DIR/bin/claude-accounts</string>
    <string>limits</string>
    <string>--quiet</string>
  </array>
$(plist_env_block)  <key>StartInterval</key><integer>900</integer>
  <key>RunAtLoad</key><true/>
  <key>StandardOutPath</key><string>/dev/null</string>
  <key>StandardErrorPath</key><string>/dev/null</string>
</dict></plist>
EOF
  cat > "$PLIST_HEALTH" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>$LABEL.health</string>
  <key>ProgramArguments</key><array>
    <string>$REPO_DIR/bin/claude-accounts</string>
    <string>health</string>
  </array>
$(plist_env_block)  <key>StartCalendarInterval</key><dict>
    <key>Weekday</key><integer>1</integer>
    <key>Hour</key><integer>9</integer>
    <key>Minute</key><integer>17</integer>
  </dict>
  <key>StandardOutPath</key><string>/dev/null</string>
  <key>StandardErrorPath</key><string>/dev/null</string>
</dict></plist>
EOF
  # Daily auto-update (04:07). No-op unless this is an npm or git install.
  cat > "$PLIST_UPDATE" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>$LABEL.update</string>
  <key>ProgramArguments</key><array>
    <string>$REPO_DIR/bin/claude-accounts</string>
    <string>self-update</string>
    <string>--quiet</string>
  </array>
$(plist_env_block)  <key>StartCalendarInterval</key><dict>
    <key>Hour</key><integer>4</integer>
    <key>Minute</key><integer>7</integer>
  </dict>
  <key>StandardOutPath</key><string>/dev/null</string>
  <key>StandardErrorPath</key><string>/dev/null</string>
</dict></plist>
EOF
  # Codex pool: its own limits/health agents, so either provider's telemetry can
  # fail or be removed without touching the other's.
  cat > "$PLIST_CODEX_LIMITS" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>$LABEL.codex-limits</string>
  <key>ProgramArguments</key><array>
    <string>$REPO_DIR/bin/codex-accounts</string>
    <string>limits</string>
    <string>--quiet</string>
  </array>
$(plist_env_block)  <key>StartInterval</key><integer>300</integer>
  <key>RunAtLoad</key><true/>
  <key>StandardOutPath</key><string>/dev/null</string>
  <key>StandardErrorPath</key><string>/dev/null</string>
</dict></plist>
EOF
  cat > "$PLIST_CODEX_HEALTH" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>$LABEL.codex-health</string>
  <key>ProgramArguments</key><array>
    <string>$REPO_DIR/bin/codex-accounts</string>
    <string>health</string>
  </array>
$(plist_env_block)  <key>StartCalendarInterval</key><dict>
    <key>Weekday</key><integer>1</integer>
    <key>Hour</key><integer>9</integer>
    <key>Minute</key><integer>37</integer>
  </dict>
  <key>StandardOutPath</key><string>/dev/null</string>
  <key>StandardErrorPath</key><string>/dev/null</string>
</dict></plist>
EOF
  launchctl unload "$PLIST_LIMITS" 2>/dev/null || true
  launchctl unload "$PLIST_HEALTH" 2>/dev/null || true
  launchctl unload "$PLIST_UPDATE" 2>/dev/null || true
  launchctl unload "$PLIST_CODEX_LIMITS" 2>/dev/null || true
  launchctl unload "$PLIST_CODEX_HEALTH" 2>/dev/null || true
  launchctl load -w "$PLIST_LIMITS" 2>/dev/null || echo "  (launchctl load limits agent failed — limits refresh will rely on the shim's opportunistic kick)"
  launchctl load -w "$PLIST_HEALTH" 2>/dev/null || true
  launchctl load -w "$PLIST_CODEX_LIMITS" 2>/dev/null || true
  launchctl load -w "$PLIST_CODEX_HEALTH" 2>/dev/null || true
  [ "${CLAUDE_MULTIACC_AUTOUPDATE:-1}" = "1" ] && launchctl load -w "$PLIST_UPDATE" 2>/dev/null || true
  echo "  launchd: limits refresh (claude 15m / codex 5m) + weekly health checks + daily auto-update"
}

mac_schedule_remove() {
  launchctl unload "$PLIST_LIMITS" 2>/dev/null || true
  launchctl unload "$PLIST_HEALTH" 2>/dev/null || true
  launchctl unload "$PLIST_UPDATE" 2>/dev/null || true
  launchctl unload "$PLIST_CODEX_LIMITS" 2>/dev/null || true
  launchctl unload "$PLIST_CODEX_HEALTH" 2>/dev/null || true
  rm -f "$PLIST_LIMITS" "$PLIST_HEALTH" "$PLIST_UPDATE" "$PLIST_CODEX_LIMITS" "$PLIST_CODEX_HEALTH"
}

# Only THIS instance's lines are dropped: the tag is matched anchored to end of line,
# so a default install ("# claude-multiacc") never removes an instance's lines
# ("# claude-multiacc iXXXXXXXX") or the other way round.
cron_strip_ours() { grep -v -E "$CRON_TAG\$" || true; }

linux_schedule_install() {
  local tmp env
  tmp="$(mktemp)"
  env="$(cron_env_prefix)"
  { crontab -l 2>/dev/null | cron_strip_ours; } > "$tmp"
  {
    echo "*/15 * * * * $env$REPO_DIR/bin/claude-accounts limits --quiet >/dev/null 2>&1 $CRON_TAG"
    echo "*/5 * * * * $env$REPO_DIR/bin/codex-accounts limits --quiet >/dev/null 2>&1 $CRON_TAG"
    echo "17 9 * * 1 $env$REPO_DIR/bin/claude-accounts health >/dev/null 2>&1 $CRON_TAG"
    echo "37 9 * * 1 $env$REPO_DIR/bin/codex-accounts health >/dev/null 2>&1 $CRON_TAG"
    [ "${CLAUDE_MULTIACC_AUTOUPDATE:-1}" = "1" ] && \
      echo "7 4 * * * $env$REPO_DIR/bin/claude-accounts self-update --quiet >/dev/null 2>&1 $CRON_TAG"
  } >> "$tmp"
  crontab "$tmp"
  rm -f "$tmp"
  echo "  cron: limits refresh (claude 15m / codex 5m) + weekly health checks + daily auto-update"
}

linux_schedule_remove() {
  local tmp
  tmp="$(mktemp)"
  { crontab -l 2>/dev/null | cron_strip_ours; } > "$tmp"
  crontab "$tmp"
  rm -f "$tmp"
}

do_uninstall() {
  echo "claude-multiacc: uninstalling (restoring stock behavior)"
  # An instance install never wrote a shell rc block, a /etc/profile.d file or the
  # /usr/local/bin shims — those belong to the DEFAULT install and are shared with it.
  # Removing them here would disable the operator's shim while only one instance was
  # being uninstalled, so an instance removes exactly its own agents.
  if [ -n "$INSTANCE" ]; then
    echo "  instance $INSTANCE: removing its agents only (PATH block and shims belong to the default install)"
  else
    strip_block "$HOME/.zshenv"
    strip_block "$HOME/.zprofile"
    strip_block "$HOME/.zshrc"
    strip_block "$HOME/.bashrc"
    strip_block "$HOME/.bash_profile"
    strip_block "$HOME/.profile"
  fi
  if [ "$(machine_kind)" = "mac" ]; then
    mac_schedule_remove
  else
    linux_schedule_remove
    if [ -z "$INSTANCE" ]; then
      [ -f "$PROFILED" ] && rm -f "$PROFILED"
      if [ -L /usr/local/bin/claude ] && [ "$(canon_path /usr/local/bin/claude)" = "$(canon_path "$REPO_DIR/bin/claude")" ]; then
        rm -f /usr/local/bin/claude
        echo "  removed /usr/local/bin/claude shim symlink"
      fi
      if [ -L /usr/local/bin/codex ] && [ "$(canon_path /usr/local/bin/codex)" = "$(canon_path "$REPO_DIR/bin/codex")" ]; then
        rm -f /usr/local/bin/codex
        echo "  removed /usr/local/bin/codex shim symlink"
      fi
    fi
  fi
  local root
  for root in "$ACC_ROOT" "$CODEX_ACC_ROOT"; do
    if [ "$PURGE" = "1" ]; then
      # Either the conventional location, or a directory that is provably a pool (it
      # holds a manifest) — an instance root lives anywhere, so the manifest is what
      # makes `rm -rf` safe. Anything else is left alone.
      if is_pool_root "$root"; then
        rm -rf "$root"; echo "  purged $root"
      else
        echo "  refusing to purge unusual accounts root: $root" >&2
      fi
    fi
  done
  if [ "$PURGE" != "1" ]; then
    echo "  account data kept at $ACC_ROOT and $CODEX_ACC_ROOT (use --purge-data to remove)"
  fi
  echo "uninstall complete — stock claude behavior restored"
}

do_install() {
  local kind
  kind="$(machine_kind)"
  echo "claude-multiacc: installing (mode: $kind, repo: $REPO_DIR)"
  if [ -n "$INSTANCE" ]; then
    echo "  instance: $INSTANCE (agents labelled $LABEL.*, pools $ACC_ROOT + $CODEX_ACC_ROOT)"
  fi

  [ -x "$REPO_DIR/bin/claude" ] || chmod +x "$REPO_DIR/bin/claude" 2>/dev/null || true
  [ -x "$REPO_DIR/bin/claude-accounts" ] || chmod +x "$REPO_DIR/bin/claude-accounts" 2>/dev/null || true
  [ -x "$REPO_DIR/bin/codex" ] || chmod +x "$REPO_DIR/bin/codex" 2>/dev/null || true
  [ -x "$REPO_DIR/bin/codex-accounts" ] || chmod +x "$REPO_DIR/bin/codex-accounts" 2>/dev/null || true

  local real
  if real="$(find_real_claude "$REPO_DIR/bin/claude")"; then
    echo "  real claude binary: $real ($("$real" --version 2>/dev/null | head -1 || echo 'version unknown'))"
  else
    echo "  WARNING: no real claude binary found yet — install Claude Code first (https://claude.com/claude-code)" >&2
  fi
  local real_codex
  if real_codex="$(find_real_codex "$REPO_DIR/bin/codex")"; then
    echo "  real codex binary: $real_codex ($("$real_codex" --version 2>/dev/null | head -1 || echo 'version unknown'))"
  else
    echo "  note: no codex binary found — the codex pool stays idle until Codex CLI is installed (npm i -g @openai/codex)"
  fi

  # Keychain-mode detection: per-dir /login isolation needs file-based credentials.
  if [ "$kind" = "mac" ] && [ ! -f "$HOME/.claude/.credentials.json" ] \
    && security find-generic-password -s "Claude Code-credentials" >/dev/null 2>&1; then
    echo "  WARNING: this Mac stores Claude Code credentials in the Keychain, not files." >&2
    echo "  Per-directory logins may not isolate; use token-based accounts (claude-accounts mint/import --token-file)." >&2
  fi

  manifest_init "${SERVER_OVERRIDE:-$DEFAULT_SERVER}"
  if [ -n "$SERVER_OVERRIDE" ]; then
    "$PYBIN" - "$MANIFEST" "$SERVER_OVERRIDE" <<'PYEOF'
import json, os, sys
doc = json.load(open(sys.argv[1]))
doc['server'] = sys.argv[2]
with open(sys.argv[1] + '.tmp', 'w') as f:
    json.dump(doc, f, indent=2)
    f.write('\n')
os.replace(sys.argv[1] + '.tmp', sys.argv[1])
PYEOF
  fi
  if sync_target_is_local "$(sync_target)"; then
    echo "  account pool: $ACC_ROOT (manifest ready; sync target: none — local-only)"
  else
    echo "  account pool: $ACC_ROOT (manifest ready; sync target: $(sync_target))"
  fi
  # The codex pool gets its own skeleton + manifest (same schema, separate root).
  if ! "$REPO_DIR/bin/codex-accounts" init-pool "${SERVER_OVERRIDE:-}" >/dev/null 2>&1; then
    echo "  WARNING: codex pool init failed (codex-accounts init-pool)" >&2
  else
    echo "  codex account pool: $CODEX_ACC_ROOT (manifest ready)"
  fi

  # An INSTANCE install never rewrites the shell rc blocks: one interactive PATH
  # cannot serve two pools, and clobbering the default install's block would point
  # the operator's shell at an instance pool. Its agents (below) carry the roots
  # instead, and the runner daemon invokes the shims by absolute path.
  if [ -n "$INSTANCE" ]; then
    echo "  PATH block: skipped (instance install) — for a shell against this pool:"
    echo "    export CLAUDE_ACCOUNTS_ROOT='$ACC_ROOT' CODEX_ACCOUNTS_ROOT='$CODEX_ACC_ROOT'"
    echo "    export PATH=\"$REPO_DIR/bin:\$PATH\""
  fi
  if [ "$kind" = "mac" ]; then
    # .zshenv covers non-interactive zsh; the .zshrc block must be LAST so it wins
    # over ~/.local/bin re-prepends done earlier in .zshrc/.zprofile.
    # zsh reads: .zshenv always; .zprofile for login; .zshrc for interactive.
    # The block must end each file that later re-prepends ~/.local/bin.
    if [ -z "$INSTANCE" ]; then
      touch "$HOME/.zshenv"
      append_block "$HOME/.zshenv"
      touch "$HOME/.zprofile"
      append_block "$HOME/.zprofile"
      touch "$HOME/.zshrc"
      append_block "$HOME/.zshrc"
      [ -f "$HOME/.bash_profile" ] && append_block "$HOME/.bash_profile"
      [ -f "$HOME/.bashrc" ] && append_block "$HOME/.bashrc"
      echo "  PATH block: end of ~/.zshenv, ~/.zprofile, ~/.zshrc (+ bash rc files if present)"
    fi
    [ "$NO_SCHEDULE" = "1" ] || mac_schedule_install
  else
    if [ -z "$INSTANCE" ]; then
      touch "$HOME/.bashrc"
      append_block "$HOME/.bashrc"
      if [ -w /etc/profile.d ] 2>/dev/null || [ "$(id -u)" = "0" ]; then
        {
          printf '%s\n' "$MARK_BEGIN"
          path_block_body
          printf '%s\n' "$MARK_END"
        } > "$PROFILED"
        echo "  PATH block: ~/.bashrc + $PROFILED"
      fi
    fi
    if [ "$(id -u)" = "0" ]; then
      if [ -e /usr/local/bin/claude ] && [ ! -L /usr/local/bin/claude ]; then
        echo "  WARNING: /usr/local/bin/claude exists and is a real file — NOT overwriting." >&2
      else
        ln -sfn "$REPO_DIR/bin/claude" /usr/local/bin/claude
        echo "  shim: /usr/local/bin/claude -> $REPO_DIR/bin/claude (systemd-PATH compatible)"
      fi
      if [ -e /usr/local/bin/codex ] && [ ! -L /usr/local/bin/codex ]; then
        echo "  WARNING: /usr/local/bin/codex exists and is a real file — NOT overwriting." >&2
      else
        ln -sfn "$REPO_DIR/bin/codex" /usr/local/bin/codex
        echo "  shim: /usr/local/bin/codex -> $REPO_DIR/bin/codex (systemd-PATH compatible)"
      fi
    fi
    [ "$NO_SCHEDULE" = "1" ] || linux_schedule_install
  fi

  echo
  echo "install complete. Open a new shell (or 'export PATH=\"$REPO_DIR/bin:\$PATH\"'), then:"
  echo "  claude-accounts list      # the Claude Code pool"
  echo "  codex-accounts list       # the Codex pool"
  echo "  claude-accounts status | codex-accounts status    # auth + limits detail"
}

if [ "$UNINSTALL" = "1" ]; then
  do_uninstall
else
  do_install
fi
