#!/usr/bin/env bash
# jps-cli 远程安装（macOS）——Go 单二进制版：
#   1. 探测平台（mac 防 Rosetta 误判）
#   2. 从发布源拉 latest.json → 下对应平台二进制 → sha256 + 版本预检；marker 缺失的
#      ~/JpsCli 整体备份，有 marker 的旧安装整目录换新 → 放 ~/JpsCli/jps-cli
#   3. ~/.local/bin 建软链 + 收敛 shell rc 的 PATH
#   4. 拉 skill 文档包 + 注册 Claude / Codex / Cursor 三家 AI（best-effort）；Codex 侧先装
#      native skill，只有 Codex 自己没发现它时才补一段 AGENTS.md 路由垫片
#   5. 落 .jps_managed_install 标记 + 安装 Claude/Codex task/bug 会话 Hook
#
# 无 git clone、无 venv、无 pip。日常升级由 jps-cli 自身 self-update 完成（见 internal/selfupdate）。
# 典型入口（放公开 bootstrap 处）：curl -fsSL <BASE>/install.sh | bash
set -euo pipefail

# 发布源基址：**须与 go/internal/selfupdate/selfupdate.go 的 defaultBaseURL 同源**。
# 可用 JPS_CLI_DIST_BASE 覆盖（联调指向测试目录）。
BASE="${JPS_CLI_DIST_BASE:-https://jps-cli.devtestapp.com}"
BASE="${BASE%/}"
ROOT="$HOME/JpsCli"           # 安装目录（二进制 + skill 文档）

log() { printf "[install] %s\n" "$*" >&2; }
die() { log "✗ $*"; exit 1; }

# 1. 平台探测：GOOS_GOARCH，对齐 latest.json assets 的 key。
detect_platform() {
  local os arch
  case "$(uname -s)" in
    Darwin) os=darwin ;;
    Linux)  die "当前发布不支持 Linux" ;;
    *) die "不支持的系统 $(uname -s)" ;;
  esac
  if [ "$os" = darwin ]; then
    # Rosetta 2 下 uname -m 会假报 x86_64；用 sysctl 探物理芯片才准。
    if [ "$(sysctl -n hw.optional.arm64 2>/dev/null)" = "1" ]; then arch=arm64; else arch=amd64; fi
  fi
  printf '%s_%s' "$os" "$arch"
}

sha256_of() {  # 优先 sha256sum；macOS 默认回退 shasum
  if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}'
  else shasum -a 256 "$1" | awk '{print $1}'; fi
}

# 从 latest.json 抠某平台的 url / sha256（无 jq，按已知格式 grep）。
json_field() {  # $1=manifest $2=platform $3=url|sha256
  printf '%s' "$1" | tr -d '\n' \
    | grep -o "\"$2\"[^}]*}" \
    | grep -o "\"$3\": *\"[^\"]*\"" \
    | sed -E "s/.*\"$3\": *\"([^\"]*)\".*/\1/"
}

PLATFORM=$(detect_platform)
log "平台：$PLATFORM"

# no-cache：latest.json 是版本指针，防途中透明代理 / CDN 给旧版
MANIFEST=$(curl -fsSL -H "Cache-Control: no-cache" "$BASE/latest.json") || die "拉取 $BASE/latest.json 失败（确认发布源可达）"
VERSION=$(printf '%s' "$MANIFEST" | tr -d '\n' | grep -o '"version": *"[^"]*"' | sed -E 's/.*"([^"]*)".*/\1/')
URL=$(json_field "$MANIFEST" "$PLATFORM" url)
SHA=$(json_field "$MANIFEST" "$PLATFORM" sha256)
[ -n "$URL" ] && [ -n "$SHA" ] || die "latest.json 无 $PLATFORM 的资源（该平台可能未发布）"
log "版本：v$VERSION"

# 2. 下载 + 校验 + 落盘。临时文件放在 HOME 下的独立同盘目录：
# marker 缺失的历史 checkout 可能需整体移为备份，临时文件不能跟着 ROOT 一起被移走。
STAGE_DIR=$(mktemp -d "$HOME/.jps-cli-install.XXXXXX") || die "创建临时目录失败"
TMP="$STAGE_DIR/jps-cli"
ROOT_BACKUP=""
MANAGED_BACKUP=""
INSTALL_COMMITTED=0
cleanup_stage() {
  rm -f "$TMP" 2>/dev/null || true
  rmdir "$STAGE_DIR" 2>/dev/null || true
}
cleanup_install() {
  cleanup_stage
  if [ "$INSTALL_COMMITTED" != 1 ]; then
    if [ -n "$MANAGED_BACKUP" ] && [ -d "$MANAGED_BACKUP" ]; then
      rm -rf "$ROOT" 2>/dev/null || true
      mv "$MANAGED_BACKUP" "$ROOT" 2>/dev/null \
        || log "⚠ 安装失败且旧托管目录自动恢复失败：$MANAGED_BACKUP"
    elif [ -n "$ROOT_BACKUP" ] && [ -d "$ROOT_BACKUP" ]; then
      rm -rf "$ROOT" 2>/dev/null || true
      mv "$ROOT_BACKUP" "$ROOT" 2>/dev/null \
        || log "⚠ 安装失败且旧源码目录自动恢复失败：$ROOT_BACKUP"
    fi
  fi
}
trap cleanup_install EXIT
log "下载 $URL"
curl -fSL "$URL" -o "$TMP" || die "下载二进制失败"
GOT=$(sha256_of "$TMP")
[ "$GOT" = "$SHA" ] || die "sha256 校验失败（期望 ${SHA} 实得 ${GOT}）"
chmod +x "$TMP"

# 在触碰旧安装前先执行下载件并核对版本。平台错误、损坏或清单错配时，原目录保持不动。
PREFLIGHT_JSON=$(JPS_CLI_NO_AUTOUPDATE=1 "$TMP" version --format json 2>/dev/null) \
  || die "下载件无法运行（平台不匹配或文件损坏），旧安装未改动"
printf '%s' "$PREFLIGHT_JSON" | tr -d '[:space:]' | grep -Fq "\"version\":\"$VERSION\"" \
  || die "下载件版本读回不一致（期望 ${VERSION}），旧安装未改动"

resolve_entry() {
  local source="$1" link_dir
  while [ -L "$source" ]; do
    link_dir="$(cd -P "$(dirname "$source")" >/dev/null 2>&1 && pwd)" || return 1
    source="$(readlink "$source")" || return 1
    case "$source" in /*) ;; *) source="$link_dir/$source" ;; esac
  done
  printf '%s' "$source"
}

is_jps_checkout_entry() {
  local target="$1" checkout_root module_line
  [ "$(basename "$target")" = "jps-cli" ] || return 1
  for checkout_root in "$(dirname "$target")" "$(dirname "$(dirname "$target")")"; do
    [ -f "$checkout_root/go/go.mod" ] || continue
    module_line="$(sed -n '1p' "$checkout_root/go/go.mod" 2>/dev/null | tr -d '\r')"
    [ "$module_line" = "module gitee.com/goodskill/jpscli" ] || continue
    if [ "$target" = "$checkout_root/jps-cli" ] || [ "$target" = "$checkout_root/go/jps-cli" ]; then
      return 0
    fi
  done
  return 1
}

# 未知的 canonical 命令入口不接管，而且必须在移动旧 ROOT 前失败关闭。
CANONICAL_ENTRY="$HOME/.local/bin/jps-cli"
if [ -e "$CANONICAL_ENTRY" ] || [ -L "$CANONICAL_ENTRY" ]; then
  [ -L "$CANONICAL_ENTRY" ] \
    || die "$CANONICAL_ENTRY 已存在且不是软链，未覆盖；请确认并移走后重试"
  CANONICAL_TARGET="$(resolve_entry "$CANONICAL_ENTRY" 2>/dev/null || true)"
  CANONICAL_KNOWN=0
  [ "$CANONICAL_ENTRY" -ef "$ROOT/jps-cli" ] && CANONICAL_KNOWN=1
  case "$CANONICAL_TARGET" in
    "$ROOT/jps-cli"|"$ROOT/go/jps-cli") CANONICAL_KNOWN=1 ;;
  esac
  [ -n "$CANONICAL_TARGET" ] && is_jps_checkout_entry "$CANONICAL_TARGET" && CANONICAL_KNOWN=1
  if [ "$CANONICAL_KNOWN" != 1 ]; then
    die "$CANONICAL_ENTRY 指向未知目标 ${CANONICAL_TARGET:-<无法解析>}，未覆盖；请确认并移走后重试"
  fi
fi

# 2.5 安装目录身份与旧版清理。放在新二进制已验证之后：
# - 有 marker：整个旧托管目录临时移走，安装成功后删除，失败时恢复；
# - 无 marker 且目录非空：无法无损区分历史托管 checkout 和真实开发 clone，
#   整体移到时间戳备份后再新装，不再因工作树干净就删掉源码。
if [ -L "$ROOT" ] || { [ -e "$ROOT" ] && [ ! -d "$ROOT" ]; }; then
  die "$ROOT 已存在且不是普通目录，未覆盖"
fi
if [ -d "$ROOT" ] && [ -n "$(find "$ROOT" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null)" ]; then
  if [ -f "$ROOT/.jps_managed_install" ]; then
    MANAGED_BACKUP="$HOME/.JpsCli-managed-reinstall-$(date +%Y%m%d-%H%M%S)-$$"
    mv "$ROOT" "$MANAGED_BACKUP" || die "无法临时移走旧托管安装目录 $ROOT"
    log "检测到托管重装，旧目录已临时隔离：$MANAGED_BACKUP"
  else
    ROOT_BACKUP="$HOME/JpsCli-source-backup-$(date +%Y%m%d-%H%M%S)-$$"
    mv "$ROOT" "$ROOT_BACKUP" || die "无法将 marker 缺失的旧目录移到备份 $ROOT_BACKUP"
    log "⚠ 检测到 marker 缺失的旧安装/源码目录，已整体备份到：$ROOT_BACKUP"
  fi
fi
mkdir -p "$ROOT" || die "创建安装目录 $ROOT 失败"

mv -f "$TMP" "$ROOT/jps-cli" || die "落盘新二进制失败"
cleanup_stage
# macOS：去掉 quarantine 标记（curl 下载本不打标，但二进制若曾被浏览器下载/AirDrop 过会带上
# → Gatekeeper 拦执行「已损坏/无法验证开发者」。放探针之前做；best-effort，无标记时报错吞掉）。
if [ "$(uname -s)" = "Darwin" ] && command -v xattr >/dev/null 2>&1; then
  xattr -d com.apple.quarantine "$ROOT/jps-cli" 2>/dev/null || true
fi
log "✓ 二进制：$ROOT/jps-cli"

# 新二进制落盘后立即建立托管身份，后续任何探针都不再把安装目录误判为开发 checkout。
printf 'JpsCli managed install (go binary) — 自更新自愈。勿手改；升级走 jps-cli 自身 self-update。\n' \
  > "$ROOT/.jps_managed_install" || die "写入托管安装标记失败"

# 从官方安装器重装时回到官方发布源；只有本次显式传入灰度源才继续持久化。
# 直接重置 state 还会让安装后首次命令立即建立新的 lastOk 基准。
STATE_DIR="$HOME/.jps_cli"
STATE_FILE="$STATE_DIR/update-state.json"
if [ -n "${JPS_CLI_DIST_BASE:-}" ]; then
  mkdir -p "$STATE_DIR"
  printf '{"distBase": "%s"}\n' "$BASE" > "$STATE_FILE" || die "写入发布源状态失败：$STATE_FILE"
  log "✓ 发布源已持久化（后续自更新沿用）：$BASE"
elif [ -f "$STATE_FILE" ]; then
  rm -f "$STATE_FILE" || die "清除历史发布源状态失败：$STATE_FILE"
  log "✓ 已清除历史自定义发布源，回到官方源"
fi

# 运行探针（抓平台不匹配 / 损坏 / 版本不一致）。安装器内探针禁止另起后台更新，
# 避免与本次落盘、入口修复竞争；托管入口修复在 disabled 门禁之前执行，仍然生效。
PROBE_JSON=$(JPS_CLI_NO_AUTOUPDATE=1 "$ROOT/jps-cli" version --format json 2>/dev/null) \
  || die "新二进制无法运行（平台不匹配或文件损坏）"
printf '%s' "$PROBE_JSON" | tr -d '[:space:]' | grep -Fq "\"version\":\"$VERSION\"" \
  || die "新二进制版本读回不一致（期望 ${VERSION}）"
log "✓ 二进制版本读回：v$VERSION"

# 3. skill 文档包（best-effort；缺失只是不注册 AI skill，不影响命令）
if curl -fsSL "$BASE/jpscli-docs.tar.gz" -o "$ROOT/.docs.tar.gz" 2>/dev/null; then
  tar -xzf "$ROOT/.docs.tar.gz" -C "$ROOT" 2>/dev/null && log "✓ skill 文档已更新" || true
  rm -f "$ROOT/.docs.tar.gz"
fi

# 4. 全局入口 + PATH。新安装统一收敛到 ~/.local/bin，不再根据“第一个可写目录”
# 随机落在 ~/bin 或 /usr/local/bin。历史入口仅在确认指向 JpsCli 官方 checkout 时修复，
# 普通文件和未知软链不覆盖。
BIN="$HOME/.local/bin"
mkdir -p "$BIN" || die "无法创建全局命令目录 $BIN"
ln -sfn "$ROOT/jps-cli" "$BIN/jps-cli"
log "✓ 软链：$BIN/jps-cli -> $ROOT/jps-cli"

repair_historical_entry() {
  local entry="$1" resolved
  [ -L "$entry" ] || return 0
  [ "$entry" -ef "$ROOT/jps-cli" ] && return 0
  resolved="$(resolve_entry "$entry" 2>/dev/null || true)"
  [ -n "$resolved" ] && is_jps_checkout_entry "$resolved" || return 0
  if [ ! -w "$(dirname "$entry")" ]; then
    log "⚠ 历史入口指向开发 checkout 但目录不可写：$entry -> $resolved"
    return 0
  fi
  ln -sfn "$ROOT/jps-cli" "$entry"
  log "✓ 已修复历史全局入口：$entry -> $ROOT/jps-cli"
}

for entry in "$HOME/bin/jps-cli" /usr/local/bin/jps-cli; do
  repair_historical_entry "$entry"
done

inject_path() {
  local rc="$1" line="$2" create="$3" tmp found=0 replace_next=0 current
  if [ "$create" != "1" ] && [ ! -f "$rc" ]; then return; fi
  mkdir -p "$(dirname "$rc")" 2>/dev/null || true
  touch "$rc" 2>/dev/null || { log "⚠ 无法写 $rc ，跳过"; return; }
  tmp="$rc.jps-cli.$$"
  : > "$tmp" || { log "⚠ 无法创建 $rc 的临时文件，跳过"; return; }
  while IFS= read -r current || [ -n "$current" ]; do
    if [ "$replace_next" = 1 ]; then
      printf '%s\n' "$line" >> "$tmp"
      replace_next=0
      continue
    fi
    printf '%s\n' "$current" >> "$tmp"
    if [ "$current" = "# jps-cli" ]; then
      found=1
      replace_next=1
    fi
  done < "$rc"
  if [ "$found" = 0 ]; then
    { printf '\n# jps-cli\n'; printf '%s\n' "$line"; } >> "$tmp"
  elif [ "$replace_next" = 1 ]; then
    printf '%s\n' "$line" >> "$tmp"
  fi
  mv -f "$tmp" "$rc" || { rm -f "$tmp"; log "⚠ 无法更新 ${rc}，跳过"; return; }
  log "✓ 已收敛 $rc 的 jps-cli PATH 段"
}

if [[ "${SHELL:-}" == */fish ]]; then
  inject_path "$HOME/.config/fish/config.fish" "set -gx PATH $BIN \$PATH" 1
  log "→ 新开终端后生效"
else
  EXPORT_LINE="export PATH=\"$BIN:\$PATH\""
  case "${SHELL:-}" in
    */zsh)  PRIMARY="$HOME/.zshrc" ;;
    */bash)
      if [ -f "$HOME/.bash_profile" ]; then PRIMARY="$HOME/.bash_profile"
      elif [ -f "$HOME/.profile" ]; then PRIMARY="$HOME/.profile"
      else PRIMARY="$HOME/.bash_profile"; fi
      ;;
    *)      PRIMARY="$HOME/.profile" ;;
  esac
  inject_path "$PRIMARY" "$EXPORT_LINE" 1
  for rc in "$HOME/.zshrc" "$HOME/.bash_profile" "$HOME/.bashrc"; do
    [ "$rc" != "$PRIMARY" ] && inject_path "$rc" "$EXPORT_LINE" 0
  done
  log "→ 新开终端，或 source 上面的 rc 后生效"
fi

GLOBAL_ENTRY_WARNING=""
CONFIGURED_ENTRY="$(PATH="$BIN:$PATH" command -v jps-cli 2>/dev/null || true)"
if [ -z "$CONFIGURED_ENTRY" ] || [ ! "$CONFIGURED_ENTRY" -ef "$ROOT/jps-cli" ]; then
  die "配置后的全局入口读回失败（期望 $BIN/jps-cli，实得 ${CONFIGURED_ENTRY:-<未找到>}）"
fi
CONFIGURED_JSON=$(JPS_CLI_NO_AUTOUPDATE=1 "$CONFIGURED_ENTRY" version --format json 2>/dev/null) \
  || die "配置后的全局入口无法运行：$CONFIGURED_ENTRY"
printf '%s' "$CONFIGURED_JSON" | tr -d '[:space:]' | grep -Fq "\"version\":\"$VERSION\"" \
  || die "配置后的全局入口版本不一致（期望 ${VERSION}）"
log "✓ 配置后的全局入口读回：$CONFIGURED_ENTRY → v$VERSION"

ACTIVE_ENTRY="$(command -v jps-cli 2>/dev/null || true)"
if [ -n "$ACTIVE_ENTRY" ] && { [ ! -e "$ACTIVE_ENTRY" ] || [ ! "$ACTIVE_ENTRY" -ef "$ROOT/jps-cli" ]; }; then
  GLOBAL_ENTRY_WARNING="启动安装器的旧 shell 仍命中 $ACTIVE_ENTRY；新开终端后将命中 $BIN/jps-cli"
  log "⚠ $GLOBAL_ENTRY_WARNING"
fi

# 5. AI skill 注册（仅当文档包已就位；沿用旧 install.sh 的三家逻辑）
# Cursor 没有 skill 发现机制，只能落一份常驻 rule。写的是几行 stub 而不是 SKILL.md 全文：
# rule 的 frontmatter schema（description/globs/alwaysApply）与 SKILL.md 的（name/description）
# 是两套，共用一个文件会让任一侧加字段时污染另一侧。正文按需读 $root/SKILL.md。
write_cursor_rule() {
  local root="$1" link="$2" parent
  parent="$(dirname "$link")"
  mkdir -p "$parent" 2>/dev/null || { log "⚠ Cursor 跳过：无法创建 $parent"; return; }
  # 接管范围严格限定在「本工具装出来的东西」：新版 stub（带 marker）、旧版指向安装目录的软链、
  # 旧版复制过去的 SKILL.md（认 frontmatter 的 name）。其余一律不动——用户自建的同名软链或文件
  # 被安装器悄悄覆盖，比少装一条 rule 严重得多。
  if [ -L "$link" ]; then
    local target
    target="$(readlink "$link" 2>/dev/null || true)"
    case "$target" in
      "$root"/*) : ;;
      *) log "⚠ Cursor 跳过：$link 是指向 $target 的软链，非本工具管理"; return ;;
    esac
  elif [ -e "$link" ]; then
    if ! grep -Fq "jps-cli-managed-rule" "$link" 2>/dev/null \
       && ! grep -Eq '^name:[[:space:]]*jps-cli[[:space:]]*$' "$link" 2>/dev/null; then
      log "⚠ Cursor 跳过：$link 已存在且非本工具管理"; return
    fi
  fi
  rm -f "$link" 2>/dev/null || true
  cat > "$link" <<EOF || { log "⚠ Cursor 写入失败：$link"; return; }
---
description: 公司内部 JPS 工作流的命令行入口 jps-cli，覆盖账号、项目、任务、Bug、应用、包、工作流、调研、素材、模版、需求、发版、AI 任务与后端接口查询。用户提到 JPS 工单、项目数据或上述场景时读取本规则。
---

<!-- jps-cli-managed-rule：JpsCli 安装器生成，删本文件即可移除 -->

# jps-cli

公司内部 JPS 工作流统一走全局命令 \`jps-cli\`，在用户当前目录原地调用，不直接请求 JPS 后端。

- 列全部命令：\`jps-cli commands --format json\`
- 机器读取统一加 \`--format json\`
- 完整触发边界、字段语义与领域流程见 $root/SKILL.md（按需读）
EOF
  log "✓ Cursor rule → $link"
}
link_claude_skill() {
  local root="$1" dir="$HOME/.claude/skills/jpscli"
  mkdir -p "$(dirname "$dir")" 2>/dev/null || { log "⚠ Claude skill 跳过：无法创建目录"; return; }
  if [ -L "$dir" ]; then rm -f "$dir"
  elif [ -e "$dir" ] && [ ! -d "$dir" ]; then log "⚠ Claude skill 跳过：$dir 非目录"; return
  elif [ -d "$dir" ] && [ ! -f "$dir/.jpscli-managed" ]; then log "⚠ Claude skill 跳过：$dir 非本工具管理"; return; fi
  if [ -d "$dir" ]; then rm -rf "$dir" || { log "⚠ Claude skill 跳过：无法清理旧受管目录 $dir"; return; }; fi
  mkdir -p "$dir" || { log "⚠ Claude skill 跳过：无法创建 $dir"; return; }
  : > "$dir/.jpscli-managed"
  for name in reference docs; do
    [ -d "$dir/$name" ] && [ ! -L "$dir/$name" ] && rm -rf "$dir/$name"
  done
  ln -sfn "$root/reference" "$dir/reference"
  ln -sfn "$root/docs"      "$dir/docs"
  ln -sf  "$root/SKILL.md"  "$dir/SKILL.md"
  log "✓ Claude Code skill → $dir"
}
link_codex_native_skill() {
  local root="$1" dir="$HOME/.agents/skills/jps-cli"
  mkdir -p "$(dirname "$dir")" 2>/dev/null || { log "⚠ Codex native skill 跳过：无法创建目录"; return; }
  if [ -L "$dir" ]; then rm -f "$dir"
  elif [ -e "$dir" ] && [ ! -d "$dir" ]; then log "⚠ Codex native skill 跳过：$dir 非目录"; return
  elif [ -d "$dir" ] && [ ! -f "$dir/.jpscli-managed" ]; then log "⚠ Codex native skill 跳过：$dir 非本工具管理"; return; fi
  if [ -d "$dir" ]; then rm -rf "$dir" || { log "⚠ Codex native skill 跳过：无法清理旧受管目录 $dir"; return; }; fi
  mkdir -p "$dir" || { log "⚠ Codex native skill 跳过：无法创建 $dir"; return; }
  : > "$dir/.jpscli-managed"
  for name in reference docs; do
    [ -d "$dir/$name" ] && [ ! -L "$dir/$name" ] && rm -rf "$dir/$name"
  done
  ln -sfn "$root/reference" "$dir/reference"
  ln -sfn "$root/docs"      "$dir/docs"
  # SKILL.md 必须是真文件：Codex 的 skill 扫描器不跟随软链的入口文件，软链会让整个 skill
  # 不被发现（reference/ docs/ 由 agent 用普通文件读取，软链无妨）。副本随文档包刷新。
  rm -f "$dir/SKILL.md" 2>/dev/null || true
  cp -f "$root/SKILL.md" "$dir/SKILL.md"
  log "✓ Codex native skill → $dir"
}
# Codex 是否真的发现了刚装的 native skill。答案来自 Codex 自己（app-server skills/list），
# 不猜版本号、不看我们往磁盘写了什么。发现了就不必再往全局 AGENTS.md 常驻一段路由文字；
# 探测失败（Codex 太老、不在 PATH、超时）一律当作「没发现」，行为与加探测前一致。
# 探测到 native skill 生效后，把旧安装器追加过的常驻段删掉——它是本工具自己的产物，
# 段首就写着「删本段即可移除」。不删的话既有用户同时加载 skill 和常驻路由，等于没减重。
remove_codex_shim() {
  local link="$1" tmp
  [ -f "$link" ] || return 0
  grep -Fq "# >>> jps-cli >>>" "$link" 2>/dev/null || return 0
  tmp="$link.jps-cli.$$"
  if sed '/# >>> jps-cli >>>/,/# <<< jps-cli <<</d' "$link" > "$tmp" 2>/dev/null; then
    mv -f "$tmp" "$link" && log "✓ 已移除 $link 中冗余的 jps-cli 常驻段（native skill 已生效）"
  else
    rm -f "$tmp" 2>/dev/null || true
    log "⚠ 未能移除 $link 的 jps-cli 段（不阻断安装）"
  fi
}
codex_discovers_skill() {
  JPS_CLI_NO_AUTOUPDATE=1 "$ROOT/jps-cli" observe status --format json 2>/dev/null \
    | tr -d ' \n' | grep -q '"codexNativeSkill":true'
}
append_codex_skill() {
  local guide="$1" link="$HOME/.codex/AGENTS.md"
  mkdir -p "$HOME/.codex" 2>/dev/null || { log "⚠ Codex 跳过：无法创建 ~/.codex"; return; }
  if [ -L "$link" ]; then log "✓ Codex 已软链，跳过"; return; fi
  if grep -Fq "# >>> jps-cli >>>" "$link" 2>/dev/null; then log "✓ Codex 已含 jps-cli 段，跳过"; return; fi
  {
    printf '\n# >>> jps-cli >>>  (JpsCli 安装器自动添加；删本段即可移除)\n'
    printf '公司内部「JPS 工作流」命令行工具已装为 `jps-cli`。涉及账号/项目/任务/Bug/应用/工作流/\n'
    printf '调研/关卡/素材/模版/需求/发版/AI 任务/查后端接口 等场景运行 `jps-cli <子命令>`；\n'
    printf '`jps-cli commands --format json` 列全部命令，触发指南见 %s（按需读）。\n' "$guide"
    printf '目标命令成功即 CLI 健康；不得凭 symlink/shell wrapper/file 结果判断损坏或擅自安装、自修复。\n'
    printf '版本只认 `jps-cli version`（运行时）/ version.go（源码）；CHANGELOG 顶部与旧 cli_version 不是当前版本。\n'
    printf '# <<< jps-cli <<<\n'
  } >> "$link"
  log "✓ Codex CLI → 已追加 jps-cli 段"
}
if [ -f "$ROOT/SKILL.md" ]; then
  [ -d "$HOME/.claude" ] && link_claude_skill "$ROOT"
  if [ -d "$HOME/.codex" ]; then
    link_codex_native_skill "$ROOT"
    if codex_discovers_skill; then
      log "✓ Codex 已发现 native skill，跳过 AGENTS.md 路由垫片"
      remove_codex_shim "$HOME/.codex/AGENTS.md"
    else
      append_codex_skill "$ROOT/SKILL.md"
    fi
  fi
  [ -d "$HOME/.cursor" ] && write_cursor_rule "$ROOT" "$HOME/.cursor/rules/jpscli.md"
fi

# 6. Claude/Codex 全局 Hook（best-effort）：需 Git 仓库，且仅在会话明确关联
# task/bug 后上传 S3 并挂附件；配置异常只告警，不影响安装与 AI 正常使用。
if JPS_CLI_NO_AUTOUPDATE=1 "$ROOT/jps-cli" observe install --format off >/dev/null 2>&1; then
  log "✓ Claude/Codex task/bug 会话上报配置已安装"
else
  log "⚠ AI 观测 Hook 暂未安装（不影响 jps-cli；后续运行 jps-cli 会自动修复）"
fi

# 到这里新托管安装已经验活、入口已收敛；提交事务并删除旧托管目录的全部历史产物。
INSTALL_COMMITTED=1
if [ -n "$MANAGED_BACKUP" ]; then
  rm -rf "$MANAGED_BACKUP" 2>/dev/null || true
  if [ -e "$MANAGED_BACKUP" ]; then
    log "⚠ 旧托管安装目录未能删净，请手动检查：$MANAGED_BACKUP"
  else
    log "✓ 已删除旧托管安装目录及全部历史产物"
  fi
fi

# 7. 探测登录态（本地读 ~/.jps_auth_info，无网络）——旧安装的登录态清理时已保留，
#    据此分态给提示：已登录直接开跑、未登录才引导 login。user-info 未登录返回非 0，
#    set -e 下必须 || true 兜住；用 $ROOT/jps-cli 直调，绕开尚未生效的当前 shell PATH。
JPS_USER="$(JPS_CLI_NO_AUTOUPDATE=1 "$ROOT/jps-cli" user-info --field username 2>/dev/null || true)"

cat >&2 <<EOF

✓ 安装完成 jps-cli v${VERSION}（${PLATFORM}）。
✓ 托管二进制读回：$ROOT/jps-cli → v${VERSION}
EOF

if [ -n "$ROOT_BACKUP" ]; then
  log "· 旧安装/源码备份已保留：$ROOT_BACKUP"
fi
if [ -n "$GLOBAL_ENTRY_WARNING" ]; then
  log "· 全局入口待新 shell 读回：$GLOBAL_ENTRY_WARNING"
fi

if [ -n "$JPS_USER" ]; then
  cat >&2 <<EOF

✓ 已检测到登录态（${JPS_USER}），可直接开跑：
  jps-cli --help       # 看完整命令
EOF
else
  cat >&2 <<EOF

下一步（首次使用请先登录）：
  jps-cli login        # 弹浏览器走飞书 SSO
  jps-cli user-info    # 验证登录态
  jps-cli --help       # 看完整命令
EOF
fi

cat >&2 <<EOF

若仍提示 'command not found'，请重开终端，或：exec \$SHELL
EOF
