Skill: bao2200220/claude-code-allinone (version 1.0.1)

--- name: claude-code-allinone description: ArkClaw / OpenClaw 沙箱里一键安装并配置 Anthropic Claude Code CLI 的 all-in-one skill。当用户提到 claude code、claude-code、想在 ArkClaw 里用 Anthropic 官方 Coding CLI、提到 ark-code-latest / 火山方舟 AgentPlan 接 claude code、想切换 claude code 的模型供应商、需要做代码 review / 代码改写、或者说"我没有 Anthropic 官方 API Key 但想用 claude code"时,使用本 skill。 license: MIT --- # Claude Code All-in-One for ArkClaw 让 ArkClaw / OpenClaw 用户在**对话框里**完成 Claude Code CLI 的全部配置 — 不用打开终端、不用手写 `~/.claude/settings.json`、不用记 base_url。本 skill 还内置 **review / build 双模式智能路由**: - **review 模式**(`--permission-mode plan`):只读分析,不改文件,适合代码审查 / 评估改造方案 - **build 模式**(`--allowedTools` 白名单):允许 Read / Edit / Write / Bash 等动手工具,适合实现代码 --- ## 整体流程(决策树) ``` 用户触发本 skill(说"用 claude code 帮我..." / "/claude-code-allinone ..." / 报错 等) │ ▼ ┌───────────────────────────┐ │ 运行 scripts/doctor.sh │ ← 每次触发都会运行,检测当前环境状态 └─────────────┬─────────────┘ │ 当前配置状态 = ? │ ┌─────────────┼──────────────────────────────────┐ │ │ │ "全新环境" "本 skill 配过" "用户手动配过" (从未配置) (之前用本 skill 配置的) (有 settings.json/config 但不是本 skill 写的) │ │ │ ▼ ▼ ▼ 路径 ① 路径 ② 路径 ③ 首次配置 正常使用(绝大多数情况) 兼容性检查 │ │ │ │ │ ├─ 检查通过 → 保留不动,转到路径 ② │ │ └─ 检查不通过 → 告知用户具体问题, │ │ 询问是否用本 skill 重新配置 │ │ 问用户选择 智能判定 review/build 模式 A/B 菜单 然后执行: claude -p "用户的需求" --permission-mode plan │ 或: claude -p "用户的需求" --allowedTools <白名单> ▼ 完成配置后 转到路径 ② ``` ### 三条路径说明 | 路径 | 什么时候走 | 用户需要做什么 | |---|---|---| | ① 首次配置 | 环境里从未配过 claude code,或用户主动要求重新配置 | **默认直接走 AgentPlan**,优先尝试自动从 ArkClaw 沙箱环境变量(`ARK_AGENT_PLAN` / `ARK_CODING_PLAN` / `ARK_API_KEY`)零感知配好;失败再让用户粘贴一次 Key。**用户主动说"我有自己的 Anthropic 兼容网关 / 别的 base_url / 自定义模型"时才走 custom 分支** | | ② 正常使用 | 已经配好了(本 skill 配的,或用户自配且体检通过) | **什么都不用做**,直接说编程需求即可 | | ③ 兼容性检查 | 用户手动写过 settings.json,本 skill 首次遇到 | 如果检查通过:无需操作;如果不通过:选择重配或自己修 | --- ## 重要:正常使用时不要打扰用户 当配置已经就绪(路径 ②)时,**不允许**: - 复述配置流程 - 解释 skill 在做什么 - 询问"是否需要切换 / 是否需要重新配置" - 把 skill 的存在感暴露给用户 **只做**: 1. 运行 `bash scripts/ensure-onboarding.sh`(确保 `~/.claude.json` 标记 onboarding 完成) 2. 调用 `bash scripts/run.sh "用户的完整需求原文"`(自动判定 review / build 模式) 3. 把 claude code 的输出原样返回给用户 **用户体验**应该等同于"直接跟 claude code 对话",完全感觉不到 skill 的存在。 只有这三种情况允许打扰用户: 1. 首次配置(环境全新) 2. 兼容性检查不通过,且无法自动修复 3. 用户主动说"切换 / 换一家 / 切到 X" --- ## 触发方式 - **自然语言**:"用 claude code 帮我重构 utils.py" / "claude code review 一下这个 PR" - **显式命令**:`/claude-code-allinone <你的编程需求>`(强制走本 skill) - **报错触发**:用户贴出 `command not found: claude` / `Onboarding has not been completed` / `ANTHROPIC_AUTH_TOKEN is not set` 等 --- ## 详细流程 ### Step 0 — 运行 doctor.sh 检测环境 每次触发都先运行 `bash scripts/doctor.sh`,它会输出一个 JSON,包含: ```json { "claude_installed": true, "claude_version": "2.1.143", "claude_outdated": false, "config_state": "clean | managed-by-skill | user-managed", "active_profile": "agentplan | custom | null", "user_settings_summary": {"base_url": "...", "model": "..."} | null, "user_health": {"is_healthy": true/false, "checks": [...], "issues": [...]} | null, "onboarding_complete": true, "ark_api_key_set": true, "custom_api_key_set": false, "ark_managed_env": true, "ark_agent_plan": true, "ark_coding_plan": false, "ark_api_key_managed": true, "ark_model_id": "ark-code-latest", "issues": [] } ``` 根据 `config_state` 决定走哪条路径。 ### Step 1 — 安装 / 升级 Claude Code CLI(仅首次需要) `scripts/install.sh` 的行为: | 当前状态 | 行为 | |---|---| | 未安装 claude | 通过 npm 安装 `@anthropic-ai/claude-code@latest` | | 版本低于 2.1.0 | 升级到最新 | | 已是最新版 | 跳过,不做任何事 | ### Step 2 — 配置模型服务(仅路径 ① 触发) **默认行为:不问选项,直接走 AgentPlan** 减少用户感知是核心原则。**不要主动给用户列 A/B 菜单**。除非用户主动说"我有自己的 Anthropic 兼容网关 / 自定义 base_url / 别的模型 Key",否则一律默认进入 AgentPlan 配置流程。 #### 零感知首选:扫环境变量自动配好 ArkClaw / OpenClaw 沙箱会**自动注入**当前账号的套餐和 Key 信息,本 skill 应**优先利用这些变量做零感知配置**,避免让用户再粘贴一次 Key。 doctor.sh 输出会包含: ```json { "ark_managed_env": true, "ark_agent_plan": true, "ark_coding_plan": false, "ark_api_key_set": true, "ark_api_key_managed": true, "ark_model_id": "ark-code-latest" } ``` | `ark_managed_env` | 套餐标识 | `ark_api_key_set` | 应执行的动作 | |---|---|---|---| | true | `ark_agent_plan=true` 且 `ark_coding_plan!=true` | true | **零感知配置 (AgentPlan)**:直接 `bash scripts/setup-agentplan.sh --auto`,**完全不要询问用户** | | true | `ark_coding_plan=true` 且 `ark_agent_plan!=true` | true | **零感知配置 (Coding Plan)**:直接 `bash scripts/setup-codingplan.sh --auto`。⚠️ 注意 **Coding Plan 与 AgentPlan 是两套不同套餐**,Key 互不通用,base_url 也不一样(coding vs plan),**绝不能套用 AgentPlan profile** | | true | **两个都为 true(双开)** | true | **优先 AgentPlan**:直接 `bash scripts/setup-agentplan.sh --auto`。脚本内置预校验,若 AgentPlan Key 401 会**自动 fallback** 到 `setup-codingplan.sh --auto --from-fallback`,**全程零感知,不需要询问用户** | | true | 两个都没标 | true | 默认 `bash scripts/setup-agentplan.sh --auto`(并打印一条 warning,401 时再切 codingplan) | | false 或 `ark_api_key_set=false` | — | — | 走"传统首次配置":告知用户使用 AgentPlan,引导粘贴一次 Key | > 实现细节:`setup-agentplan.sh --auto` 内部带**三层防护**:(1) 本地格式自检挡掉粘错的输入;(2) 调用 `/models` endpoint 做活性 ping(不消耗推理 token,3 次重试,仅 401/403 视为失败);(3) AgentPlan 401 且 `ARK_CODING_PLAN=true` 时自动 `exec` 转发到 `setup-codingplan.sh --auto --from-fallback`。所以即使 skill 选错或双开账号 AgentPlan Key 失效,都不会真的把错 Key 写进 profile。 #### 传统首次配置(仅在零感知不可用时触发) 向用户输出: ``` 准备给你配置 Claude Code,使用火山方舟 AgentPlan(套餐内额度,ark-code-latest 智能路由)。 请粘贴 AgentPlan 控制台分配的专属 Key: ``` 拿到 Key 后立即跑: ```bash bash scripts/setup-agentplan.sh "$KEY" ``` #### 何时走自定义网关分支? 只有以下情况才询问/触发 custom 流程,**不要主动给用户列菜单**: - 用户**主动**说:"我有自己的 Anthropic 网关" / "用我们公司的 base_url" / "我有 claude-code-router" / "用 Bedrock / Vertex 转出来的端点" - 用户**主动**给出 base_url + Key + model 三件套 - 用户**主动**说"我没有 AgentPlan Key,但我有别的 Anthropic 兼容端点" 触发后引导用户依次给出 3 个信息,然后跑: ```bash bash scripts/setup-custom.sh "$BASE_URL" "$KEY" "$MODEL" ``` > ⛔ **不允许**在用户没提"自定义/别的网关/我的 Key"等关键词时,主动展示 A/B 菜单或让用户做选择。这会增加感知,违背"默认 AgentPlan + 零感知优先"原则。 配置脚本会自动完成:写 Key → 写 settings.json → 写 `~/.claude.json` 的 `hasCompletedOnboarding: true` → `.token` 文件落盘。如果用户已有 `settings.json`,会**先备份**再覆盖。 ### Step 3 — 兼容性检查(仅路径 ③ 触发) 当 doctor.sh 返回 `config_state == "user-managed"` 时,说明用户之前手动写过 settings.json。此时读取 `user_health` 字段: **检查通过**(`is_healthy == true`): - 不打扰用户,直接当作"已配好",进入正常使用 **检查不通过**(`is_healthy == false`): 向用户展示具体问题,并询问: ``` 检测到你已有的 claude code 配置存在以下问题: 1. <具体问题描述> 2. <具体问题描述> 你希望: A. 让本 skill 帮你重新配置(推荐,会先备份你现有的文件) B. 我自己去修改 请回复 A 或 B。 ``` ### Step 4 — 正常使用(智能路由 review / build 模式) ```bash bash scripts/run.sh "用户的完整需求原文" ``` `run.sh` 内部会: 1. 关键词扫描决定 mode: - 命中 "review / 评审 / 审查 / 看一下 / 分析 / 评估 / 检查 / lint / 评 / read-only" → **review 模式**(`--permission-mode plan`) - 否则默认 **build 模式**(`--allowedTools "Read,Glob,Grep,LS,Bash,Edit,Write"`) 2. 用 `nohup setsid + </dev/null + log file + 110s PID polling` 跑 claude(避免 PTY 挂起) 3. 把日志末尾原样返回 也可以由 Agent 显式指定: ```bash bash scripts/run.sh --mode review "用户需求" bash scripts/run.sh --mode build "用户需求" ``` ### Step 5 — 切换模型服务(用户主动要求时) **仅当用户明确说**"切换 / 换一家 / 切到 X / 换成自定义 / 用回 AgentPlan"等关键词时触发,**不要主动建议切换**。 ```bash bash scripts/switch-profile.sh # 列出已配置的服务 bash scripts/switch-profile.sh agentplan # 切换到 AgentPlan bash scripts/switch-profile.sh custom # 切换到自定义网关 ``` 切换实际上是把 `~/.claude/settings.json` 替换为目标 profile 的内容(同时同步 `.token` 文件),并把 `~/.claude/active-profile` 写成目标名。 --- ## 关键配置点(踩坑后的硬约束) > 这是 v3.1 现网走通后总结出的**必须遵守**的规则,违反就报错。 | 规则 | 说明 | |---|---| | 用 `ANTHROPIC_AUTH_TOKEN`,**不要** `ANTHROPIC_API_KEY` | 后者会被 CLI 强制走 anthropic.com 鉴权链路,在境内 IDC 必然失败 | | `~/.claude.json` 必须有 `hasCompletedOnboarding: true` | 否则 CLI 拒绝在非交互模式启动,报 `Onboarding has not been completed` | | `~/.claude/settings.json` 的 `env` 块 + `~/.claude/.token` 文件 **二者并存** | env 块负责 base_url / model / 各类开关,.token 文件负责 Key(更安全) | | build 模式必须显式 `--allowedTools` 白名单 | 否则默认 ask 模式会等用户交互,在沙箱里直接挂死 | | review 模式必须用 `--permission-mode plan` | 这样不会出现写文件的副作用,review 才是真正只读 | | ⛔ Skill 调用方**禁止**叠加 `pty: true` | run.sh 的 `</dev/null` 会被 PTY 重新打开 stdin,导致 claude 等待输入 | | 110s 超时是硬约束 | 走 `nohup setsid + log file + while-poll PID` 路径;一旦 PID 还活着且超过 110s,主动 `kill -9`,从日志里取已生成的内容 | --- ## 健康检查都检查什么? | 检查项 | 不通过时的具体表现 | |---|---| | `~/.claude/settings.json` 是否存在且 JSON 合法 | claude 启动会直接 abort | | `env.ANTHROPIC_BASE_URL` 是否设置 | claude 不知道请求发往哪里 | | 用了 `ANTHROPIC_AUTH_TOKEN` 而非 `ANTHROPIC_API_KEY` | 后者必然走 anthropic.com,境内 IDC 连不上 | | `~/.claude.json` 是否存在且 `hasCompletedOnboarding == true` | 否则 CLI 在非交互模式下 abort | | `~/.claude/.token` 是否存在且非空 | 否则连接时被拒 401 | | `env.ANTHROPIC_MODEL` 是否设置 | 否则 CLI 会回退默认 sonnet,在自定义网关上找不到模型 | --- ## Profile 设计 每个模型服务在 `~/.claude/profiles/` 下有一个 settings.json 模板: ``` ~/.claude/profiles/ ├── agentplan.json — 火山方舟 AgentPlan(ark-code-latest,base_url /api/plan) ├── codingplan.json — 火山方舟 Coding Plan(ark-code-latest,base_url /api/coding) └── custom.json — 自定义 Anthropic 兼容网关(占位符 __BASE_URL__ / __MODEL__) ``` `~/.claude/settings.json` 是当前激活配置的实际内容(由 setup / switch 脚本写入),`~/.claude/active-profile` 文件记录当前是哪一个。 --- ## 必读参考文档 | 场景 | 需要读的文件 | |---|---| | 引导用户选择模型供应商 | `references/providers.md` | | 用户报错 / 检查不通过 | `references/troubleshooting.md` | | 不确定整体流程 / review vs build | `references/routing-flow.md` | --- ## 安全规则 - 用户的 Key 只保存在 `~/.bashrc`、`~/.claude/.token` 和 `~/.claude/profiles/*.json` 中(用户家目录内) - 覆盖配置前**自动备份**到 `~/.claude/settings.json.bak.<时间戳>` - 不上传任何外部服务,不在日志中记录 Key 明文 - ⛔ 不会出现在日志或 stdout 的任何地方打印完整 Key ## 不做什么 - ❌ **不**尝试连接 Anthropic 官方服务(沙箱网络在境内 IDC,连不上 anthropic.com) - ❌ **不**自动猜测 Key,所有 Key 必须由用户主动粘贴 - ❌ **不**做 Codex 的配置(那是另一个 skill,叫 codex-allinone) - ❌ **不**在用户没确认前覆盖已有的 settings.json - ❌ **不**接管 review/build 之外的复杂权限模式(如 `bypassPermissions`、`dontAsk`),那是用户自己的事 ## 常用对话示例 | 用户说 | Skill 做什么 | |---|---| | "帮我装 claude code" / "在 ArkClaw 里用 claude code" | 安装 + 引导配置 | | "/claude-code-allinone 帮我写个 Python 脚本" | 直接执行(已配好则走 build 模式) | | "claude code review 一下 utils.py" | 直接执行(走 review 模式,只读) | | "切换到自定义网关" / "换一家" | 运行 switch-profile.sh | | "claude 报 Onboarding has not been completed" | 跑 ensure-onboarding.sh | | "claude 报 401" | 用「人话提示」告知用户:Key 失效或贴错套餐,引导去 ArkClaw 控制台刷新 / 切套餐 / 换 Kimi 等 | | "我现在用的哪家?" | 读取 ~/.claude/active-profile | --- ## 给用户的"人话提示"模板(必须遵守) skill 与用户沟通时,**禁止出现** profile / token / settings.json / apiKeyHelper / onboarding 这类内部术语。下表是对应的人话翻译,凡需告知用户错误时一律按此输出: | 内部状态 | 给用户的人话提示 | |---|---| | 401 / 403 鉴权失败 | "连接火山方舟时被拒绝(可能是 Key 过期或贴错套餐)。可以这样修:\n 1. 去火山方舟控制台 → AgentPlan 页面,复制最新的专属 Key 发给我,我帮你重新写入(原配置会自动备份)\n 2. 如果你有别的可用 Key(Coding Plan 的、Kimi 的、DeepSeek 的、自建网关的),告诉我用哪个,我切过去\n请直接回复 1 或 2,或者直接把新 Key / "用 Kimi" 这类话发给我。" | | Onboarding 未完成 | "Claude Code 还没完成首次启动检查,我帮你跳过(2 秒)" | | relay 端口 connection refused | "本地的协议中转服务没起来,我重新启动一下(2 秒)" | | settings.json 字段缺失 | "配置文件里少了一项必要信息,我帮你重新配一下" | | apiKeyHelper 路径不存在 | "Key 文件丢失了(可能清理过家目录),我重写一下" | | Key 格式自检失败 | "你给的这串看起来不像一把有效的 Key — 应该是一串 20 位以上的字母+数字。请去控制台完整复制后再发我" | | 网络抖动跳过预校验 | "网络有点抖,在线校验跳过,先把配置写下来。如果运行时报错我再告诉你怎么修" |

path: profiles/codingplan.json
content: {
  "env": {
    "ANTHROPIC_BASE_URL": "https://ark.cn-beijing.volces.com/api/coding",
    "ANTHROPIC_MODEL": "ark-code-latest",
    "ANTHROPIC_SMALL_FAST_MODEL": "ark-code-latest",
    "DISABLE_TELEMETRY": "1",
    "DISABLE_BUG_COMMAND": "1",
    "DISABLE_AUTOUPDATER": "1",
    "DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1",
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
  },
  "apiKeyHelper": "cat ~/.claude/.token"
}


path: profiles/custom.json
content: {
  "env": {
    "ANTHROPIC_BASE_URL": "__BASE_URL__",
    "ANTHROPIC_MODEL": "__MODEL__",
    "ANTHROPIC_SMALL_FAST_MODEL": "__MODEL__",
    "DISABLE_TELEMETRY": "1",
    "DISABLE_BUG_COMMAND": "1",
    "DISABLE_AUTOUPDATER": "1",
    "DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1",
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
  },
  "apiKeyHelper": "cat ~/.claude/.token"
}


path: profiles/agentplan.json
content: {
  "env": {
    "ANTHROPIC_BASE_URL": "https://ark.cn-beijing.volces.com/api/plan",
    "ANTHROPIC_MODEL": "ark-code-latest",
    "ANTHROPIC_SMALL_FAST_MODEL": "ark-code-latest",
    "DISABLE_TELEMETRY": "1",
    "DISABLE_BUG_COMMAND": "1",
    "DISABLE_AUTOUPDATER": "1",
    "DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1",
    "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
  },
  "apiKeyHelper": "cat ~/.claude/.token"
}


path: scripts/setup-agentplan.sh
content: #!/usr/bin/env bash
# setup-agentplan.sh — 一键配置 AgentPlan 模式
#
# 用法:
#   bash setup-agentplan.sh "<AgentPlan 专属 Key>"
#   bash setup-agentplan.sh --auto         # 零感知:自动从 ArkClaw 沙箱注入的 ARK_API_KEY 取 Key
#
# 这个脚本会依次完成:
#   1. 将 ARK_API_KEY 保存到 ~/.bashrc(沙箱重启后仍然有效)
#   2. 把 Key 写入 ~/.claude/.token 文件(settings.json 的 apiKeyHelper 读它)
#   3. 写入 ~/.claude/profiles/agentplan.json (settings.json 模板)
#   4. 把 ~/.claude/settings.json 替换为该 profile 内容(已有则先备份)
#   5. 标记 ~/.claude.json hasCompletedOnboarding=true
#   6. 写 active-profile marker
#
# --auto 模式(零感知配置):
#   ArkClaw / OpenClaw 沙箱会通过 OPENCLAW_SERVICE_MANAGED_ENV_KEYS 托管 ARK_API_KEY,
#   套餐归属由 ARK_AGENT_PLAN / ARK_CODING_PLAN 标识。当这些变量都已注入时,本脚本会
#   直接复用 $ARK_API_KEY,无需用户再粘贴。

set -e

ARG1="${1:-}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/helpers.sh"

AUTO_MODE="no"
KEY=""

if [ "$ARG1" = "--auto" ]; then
  AUTO_MODE="yes"
  KEY="${ARK_API_KEY:-}"
  if [ -z "$KEY" ]; then
    cc_err "--auto 模式需要环境变量 ARK_API_KEY,但未注入。"
    cc_err "请改用: bash setup-agentplan.sh \"<你的 AgentPlan 专属 Key>\""
    exit 1
  fi
  is_agent_plan="$(printf '%s' "${ARK_AGENT_PLAN:-}" | tr '[:upper:]' '[:lower:]')"
  is_coding_plan="$(printf '%s' "${ARK_CODING_PLAN:-}" | tr '[:upper:]' '[:lower:]')"

  # 套餐分流: AgentPlan 和 Coding Plan 是两套不同的上游 + 两套不同的 Key,
  # 沙箱根据用户当前套餐只会注入其中一把,base_url 也不一样:
  #   AgentPlan  -> https://ark.cn-beijing.volces.com/api/plan
  #   CodingPlan -> https://ark.cn-beijing.volces.com/api/coding
  # 所以 --auto 检测到 ARK_CODING_PLAN=true 时,要转发去 setup-codingplan.sh,
  # 不能套用 AgentPlan profile,否则 401 / 路由错误必现。
  if [ "$is_coding_plan" = "true" ] || [ "$is_coding_plan" = "1" ]; then
    if [ "$is_agent_plan" != "true" ] && [ "$is_agent_plan" != "1" ]; then
      cc_log "检测到 ARK_CODING_PLAN=true,转发到 setup-codingplan.sh"
      exec bash "$SCRIPT_DIR/setup-codingplan.sh" --auto
    fi
  fi

  if [ "$is_agent_plan" != "true" ] && [ "$is_agent_plan" != "1" ] \
     && [ "$is_coding_plan" != "true" ] && [ "$is_coding_plan" != "1" ]; then
    cc_warn "环境中未检测到 ARK_AGENT_PLAN=true 或 ARK_CODING_PLAN=true,但 ARK_API_KEY 已注入。"
    cc_warn "仍按 AgentPlan 配置继续(若 Key 不匹配,后续 401 时请改用 custom 网关)。"
  fi
else
  KEY="$ARG1"
  if [ -z "$KEY" ]; then
    cc_err "缺少 AgentPlan Key。"
    cc_err "用法 1: bash setup-agentplan.sh \"<你的 AgentPlan 专属 Key>\""
    cc_err "用法 2: bash setup-agentplan.sh --auto   # 零感知,从沙箱注入的 ARK_API_KEY 取"
    cc_err "Key 获取方式: 登录 AgentPlan 控制台 → 复制专属 Key"
    exit 1
  fi
fi

# ============================================================
# 步骤 0: 预校验 (本地格式 + 抗抖动 ping)
# ============================================================
# 0.1 本地格式自检 - 不联网,挡掉粘错的输入(空格/换行/中文等)
if ! cc_format_check_key "$KEY"; then
  cc_err "你给的这串看起来不像一把有效的 Key。"
  cc_err "AgentPlan 的 Key 应该是一串 20 位以上的字母+数字(可能含 - 或 _)。"
  cc_err "请去火山方舟控制台 → AgentPlan 页面,完整复制专属 Key 后再发给我。"
  exit 1
fi

# 0.2 活性 ping(可选,失败有 fallback)
AGENTPLAN_BASE="https://ark.cn-beijing.volces.com/api/plan"
cc_ping_key "$KEY" "$AGENTPLAN_BASE"
ping_ret=$?
case "$ping_ret" in
  0)
    cc_log "步骤 0/6: Key 在线校验通过 ✅"
    ;;
  2)
    cc_warn "步骤 0/6: 网络抖动,跳过在线校验,直接写入配置(若运行时报 401 我会再处理)"
    ;;
  1)
    # AgentPlan Key 鉴权失败
    if [ "$AUTO_MODE" = "yes" ]; then
      is_coding_plan="$(printf '%s' "${ARK_CODING_PLAN:-}" | tr '[:upper:]' '[:lower:]')"
      if [ "$is_coding_plan" = "true" ] || [ "$is_coding_plan" = "1" ]; then
        cc_warn "AgentPlan 鉴权失败(HTTP 401),但检测到当前账号也开通了 Coding Plan。"
        cc_warn "自动切换到 Coding Plan 试试..."
        exec bash "$SCRIPT_DIR/setup-codingplan.sh" --auto --from-fallback
      fi
    fi
    cc_err "用这把 Key 连火山方舟 AgentPlan 时被拒绝(HTTP 401/403)。"
    cc_err "通常的原因是:"
    cc_err "  • Key 已经过期或被回收 — 请去 ArkClaw 控制台刷新一下"
    cc_err "  • 你贴的是 Coding Plan 的 Key,不是 AgentPlan 的"
    cc_err "  • 你贴的是火山方舟普通 v3 Key,不是套餐专属 Key"
    cc_err "请确认后重新提供 Key,或告诉我用别的方式接入(比如 Kimi / DeepSeek / 你自己的网关)"
    exit 1
    ;;
esac

SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
TMPL="$SKILL_DIR/profiles/agentplan.json"
TARGET_PROFILE="$CC_PROFILES_DIR/agentplan.json"

mkdir -p "$CC_PROFILES_DIR"

# ============================================================
# 步骤 1: 保存 ARK_API_KEY 到 ~/.bashrc
# ============================================================
cc_persist_env_to_bashrc "ARK_API_KEY" "$KEY"
cc_log "步骤 1/6: ARK_API_KEY 已保存到 ~/.bashrc"

# ============================================================
# 步骤 2: 写 Key 到 ~/.claude/.token
# ============================================================
cc_write_token "$KEY"
cc_log "步骤 2/6: Key 已写入 ~/.claude/.token (仅 600 权限)"

# ============================================================
# 步骤 3: 渲染 profile 文件 (agentplan 模板无占位符,直接拷贝)
# ============================================================
cp "$TMPL" "$TARGET_PROFILE"
chmod 600 "$TARGET_PROFILE" 2>/dev/null || true
cc_log "步骤 3/6: 配置文件已写入 $TARGET_PROFILE"

# ============================================================
# 步骤 4: 激活该 profile (备份旧 settings.json 后覆盖)
# ============================================================
cc_backup_if_needed "$CC_SETTINGS"
cp "$TARGET_PROFILE" "$CC_SETTINGS"
chmod 600 "$CC_SETTINGS" 2>/dev/null || true
cc_log "步骤 4/6: 已激活 AgentPlan 配置 → $CC_SETTINGS"

# ============================================================
# 步骤 5: 标记 onboarding 完成
# ============================================================
cc_ensure_onboarding_completed
cc_log "步骤 5/6: ~/.claude.json hasCompletedOnboarding=true"

# ============================================================
# 步骤 6: 写 active-profile marker
# ============================================================
cc_set_active_profile "agentplan"
cc_log "步骤 6/6: ~/.claude/active-profile = agentplan"

echo ""
if [ "$AUTO_MODE" = "yes" ]; then
  cc_log "✅ AgentPlan 配置已自动完成 (使用沙箱注入的 ARK_API_KEY,无需粘贴 Key)"
else
  cc_log "✅ AgentPlan 配置全部完成!"
fi
cc_log "   现在可以直接说编程需求,例如:"
cc_log "   「用 claude code 帮我重构 utils.py」"
cc_log "   「claude code review 一下 main.py」"


path: scripts/switch-profile.sh
content: #!/usr/bin/env bash
# switch-profile.sh — 在不同模型服务之间快速切换
#
# 用法:
#   bash switch-profile.sh           # 列出已配置的模型,供用户选择
#   bash switch-profile.sh <名称>    # 直接切换到指定模型配置
#
# 原理:
#   ~/.claude/settings.json 是当前激活配置的实际内容(由 setup / switch 脚本写入),
#   切换 = 用对应 profile 文件的内容覆盖 settings.json,并同步 .token 文件 + active-profile marker。
#
# 注意:
#   .token 文件保存的是当前 profile 的 Key。切换 profile 时需要同步切换 Key。
#   - agentplan → 取 ARK_API_KEY (从 .bashrc 或当前环境)
#   - custom    → 取 CUSTOM_ANTHROPIC_KEY

set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/helpers.sh"

TARGET="${1:-}"

list_profiles() {
  if [ ! -d "$CC_PROFILES_DIR" ]; then
    echo "❌ 还没有配置任何模型服务。请先完成初始配置。"
    exit 1
  fi
  CURRENT=""
  [ -f "$CC_ACTIVE_FILE" ] && CURRENT="$(cat "$CC_ACTIVE_FILE" | tr -d '[:space:]')"

  echo "已配置的模型服务:"
  for f in "$CC_PROFILES_DIR"/*.json; do
    [ -f "$f" ] || continue
    name="$(basename "$f" .json)"
    if [ "$name" = "$CURRENT" ]; then
      echo "  ★ $name (当前使用)"
    else
      echo "    $name"
    fi
  done
}

if [ -z "$TARGET" ]; then
  list_profiles
  echo ""
  echo "切换方式: bash switch-profile.sh <名称>"
  echo "例如:     bash switch-profile.sh agentplan"
  exit 0
fi

PROFILE_FILE="$CC_PROFILES_DIR/${TARGET}.json"
if [ ! -f "$PROFILE_FILE" ]; then
  echo "❌ 配置 '$TARGET' 不存在或还没设置。" >&2
  echo "" >&2
  list_profiles
  exit 1
fi

# 备份当前 settings.json (如果不是软链)
cc_backup_if_needed "$CC_SETTINGS"

# 用 profile 文件覆盖 settings.json
cp "$PROFILE_FILE" "$CC_SETTINGS"
chmod 600 "$CC_SETTINGS" 2>/dev/null || true

# 同步 .token 文件: 取目标 profile 对应的 Key
SOURCE_KEY=""
case "$TARGET" in
  agentplan)
    SOURCE_KEY="${ARK_API_KEY:-}"
    if [ -z "$SOURCE_KEY" ]; then
      SOURCE_KEY="$(grep -E '^export ARK_API_KEY=' "$HOME/.bashrc" 2>/dev/null | tail -1 | sed -E 's/^export ARK_API_KEY=\"?([^\"]*)\"?.*/\1/')"
    fi
    ;;
  codingplan)
    SOURCE_KEY="${ARK_API_KEY:-}"
    if [ -z "$SOURCE_KEY" ]; then
      SOURCE_KEY="$(grep -E '^export ARK_API_KEY=' "$HOME/.bashrc" 2>/dev/null | tail -1 | sed -E 's/^export ARK_API_KEY=\"?([^\"]*)\"?.*/\1/')"
    fi
    ;;
  custom)
    SOURCE_KEY="${CUSTOM_ANTHROPIC_KEY:-}"
    if [ -z "$SOURCE_KEY" ]; then
      SOURCE_KEY="$(grep -E '^export CUSTOM_ANTHROPIC_KEY=' "$HOME/.bashrc" 2>/dev/null | tail -1 | sed -E 's/^export CUSTOM_ANTHROPIC_KEY=\"?([^\"]*)\"?.*/\1/')"
    fi
    ;;
esac

if [ -n "$SOURCE_KEY" ]; then
  cc_write_token "$SOURCE_KEY"
  echo "✅ 已同步 ~/.claude/.token (来源: $TARGET 对应的环境变量)"
else
  echo "⚠️  警告: 没找到 $TARGET 对应的 Key,~/.claude/.token 未更新"
  echo "    请重跑对应的 setup 脚本传入正确的 Key"
fi

# 写 active-profile marker
cc_set_active_profile "$TARGET"

# 确保 onboarding 标记
cc_ensure_onboarding_completed

echo "✅ 已切换到: $TARGET"
echo "   settings.json: $CC_SETTINGS"
echo "   active-profile: $CC_ACTIVE_FILE"


path: scripts/setup-codingplan.sh
content: #!/usr/bin/env bash
# setup-codingplan.sh — 一键配置 火山方舟 Coding Plan 模式
#
# Coding Plan 与 AgentPlan 是两套独立的套餐,Key 互不通用,base_url 也不一样:
#   AgentPlan  -> https://ark.cn-beijing.volces.com/api/plan
#   CodingPlan -> https://ark.cn-beijing.volces.com/api/coding   (Anthropic 协议)
#
# 用法:
#   bash setup-codingplan.sh "<Coding Plan 专属 Key>"
#   bash setup-codingplan.sh --auto         # 零感知:从沙箱注入的 ARK_API_KEY 取
#
# 步骤等同 setup-agentplan.sh,只是写入的是 codingplan profile。

set -e

ARG1="${1:-}"
ARG2="${2:-}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/helpers.sh"

AUTO_MODE="no"
FROM_FALLBACK="no"
KEY=""

# 支持 --auto 和 --auto --from-fallback
if [ "$ARG1" = "--auto" ]; then
  AUTO_MODE="yes"
  if [ "$ARG2" = "--from-fallback" ]; then
    FROM_FALLBACK="yes"
  fi
  KEY="${ARK_API_KEY:-}"
  if [ -z "$KEY" ]; then
    cc_err "--auto 模式需要环境变量 ARK_API_KEY,但未注入。"
    cc_err "请改用: bash setup-codingplan.sh \"<你的 Coding Plan 专属 Key>\""
    exit 1
  fi
  is_coding_plan="$(printf '%s' "${ARK_CODING_PLAN:-}" | tr '[:upper:]' '[:lower:]')"
  if [ "$is_coding_plan" != "true" ] && [ "$is_coding_plan" != "1" ] && [ "$FROM_FALLBACK" != "yes" ]; then
    cc_warn "环境中未检测到 ARK_CODING_PLAN=true,但 ARK_API_KEY 已注入。"
    cc_warn "仍按 Coding Plan 配置继续(若 Key 不匹配,后续 401 时请改用 setup-agentplan)。"
  fi
else
  KEY="$ARG1"
  if [ -z "$KEY" ]; then
    cc_err "缺少 Coding Plan Key。"
    cc_err "用法 1: bash setup-codingplan.sh \"<你的 Coding Plan 专属 Key>\""
    cc_err "用法 2: bash setup-codingplan.sh --auto   # 零感知,从沙箱注入的 ARK_API_KEY 取"
    cc_err "Key 获取方式: 火山引擎方舟控制台 → API Key 管理 → 复制专属 Key"
    exit 1
  fi
fi

# ============================================================
# 步骤 0: 预校验
# ============================================================
if ! cc_format_check_key "$KEY"; then
  cc_err "你给的这串看起来不像一把有效的 Key。"
  cc_err "Coding Plan 的 Key 应该是一串 20 位以上的字母+数字(可能含 - 或 _)。"
  cc_err "请去火山方舟控制台 → Coding Plan 页面,完整复制专属 Key 后再发给我。"
  exit 1
fi

CODINGPLAN_BASE="https://ark.cn-beijing.volces.com/api/coding"
cc_ping_key "$KEY" "$CODINGPLAN_BASE"
ping_ret=$?
case "$ping_ret" in
  0)
    cc_log "步骤 0/6: Key 在线校验通过 ✅"
    ;;
  2)
    cc_warn "步骤 0/6: 网络抖动,跳过在线校验,直接写入配置(若运行时报 401 我会再处理)"
    ;;
  1)
    cc_err "用这把 Key 连火山方舟 Coding Plan 时被拒绝(HTTP 401/403)。"
    cc_err "通常的原因是:"
    cc_err "  • Key 已经过期或被回收 — 请去 ArkClaw 控制台刷新"
    cc_err "  • 你贴的是 AgentPlan 的 Key,不是 Coding Plan 的"
    cc_err "请确认后重新提供 Key,或告诉我用别的方式接入"
    exit 1
    ;;
esac

SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
TMPL="$SKILL_DIR/profiles/codingplan.json"
TARGET_PROFILE="$CC_PROFILES_DIR/codingplan.json"

mkdir -p "$CC_PROFILES_DIR"

# ============================================================
# 步骤 1: 保存 ARK_API_KEY 到 ~/.bashrc
# ============================================================
cc_persist_env_to_bashrc "ARK_API_KEY" "$KEY"
cc_log "步骤 1/6: ARK_API_KEY 已保存到 ~/.bashrc"

# ============================================================
# 步骤 2: 写 Key 到 ~/.claude/.token
# ============================================================
cc_write_token "$KEY"
cc_log "步骤 2/6: Key 已写入 ~/.claude/.token (仅 600 权限)"

# ============================================================
# 步骤 3: 渲染 profile 文件 (codingplan 模板无占位符,直接拷贝)
# ============================================================
cp "$TMPL" "$TARGET_PROFILE"
chmod 600 "$TARGET_PROFILE" 2>/dev/null || true
cc_log "步骤 3/6: 配置文件已写入 $TARGET_PROFILE"

# ============================================================
# 步骤 4: 激活该 profile (备份旧 settings.json 后覆盖)
# ============================================================
cc_backup_if_needed "$CC_SETTINGS"
cp "$TARGET_PROFILE" "$CC_SETTINGS"
chmod 600 "$CC_SETTINGS" 2>/dev/null || true
cc_log "步骤 4/6: 已激活 Coding Plan 配置 → $CC_SETTINGS"

# ============================================================
# 步骤 5: 标记 onboarding 完成
# ============================================================
cc_ensure_onboarding_completed
cc_log "步骤 5/6: ~/.claude.json hasCompletedOnboarding=true"

# ============================================================
# 步骤 6: 写 active-profile marker
# ============================================================
cc_set_active_profile "codingplan"
cc_log "步骤 6/6: ~/.claude/active-profile = codingplan"

echo ""
if [ "$AUTO_MODE" = "yes" ]; then
  if [ "$FROM_FALLBACK" = "yes" ]; then
    cc_log "✅ 已配置 Coding Plan(AgentPlan Key 不可用,自动切到了 Coding Plan)"
  else
    cc_log "✅ Coding Plan 配置已自动完成 (使用沙箱注入的 ARK_API_KEY,无需粘贴 Key)"
  fi
else
  cc_log "✅ Coding Plan 配置全部完成!"
fi
cc_log "   现在可以直接说编程需求,例如:"
cc_log "   「用 claude code 帮我重构 utils.py」"
cc_log "   「claude code review 一下 main.py」"


path: scripts/install.sh
content: #!/usr/bin/env bash
# install.sh — 安装或升级 Claude Code CLI
#
# 功能:
#   - 检测当前是否已安装 claude 及其版本
#   - 版本低于 2.1.0 则升级,未安装则安装
#   - 已是最新版直接跳过,不浪费时间
#   - 确保 npm 全局路径在 PATH 中(沙箱重启不丢)
#
# 不涉及任何 Key 或 provider 的配置。

set -e

NPM_GLOBAL_BIN="$HOME/.npm-global/bin"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/helpers.sh"

# --- 1. 检查 npm ---
if ! command -v npm >/dev/null 2>&1; then
  cc_err "未检测到 npm。ArkClaw 沙箱默认自带 Node.js,若确实没有请先在沙箱中安装 Node.js。"
  exit 1
fi

# --- 2. 设置 npm 全局目录到用户家目录(避免权限问题,沙箱重启后依然存在)---
mkdir -p "$NPM_GLOBAL_BIN"
npm config set prefix "$HOME/.npm-global" >/dev/null 2>&1 || true

# 把全局 bin 目录加入 PATH(避免重复添加)
if ! grep -q '.npm-global/bin' "$HOME/.bashrc" 2>/dev/null; then
  echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> "$HOME/.bashrc"
  cc_log "已将 npm 全局 bin 目录添加到 ~/.bashrc 的 PATH 中"
fi
export PATH="$NPM_GLOBAL_BIN:$PATH"

# --- 3. 检查当前版本 ---
CURRENT_VERSION=""
if command -v claude >/dev/null 2>&1; then
  CURRENT_VERSION="$(claude --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")"
fi

if [ -n "$CURRENT_VERSION" ] && cc_version_ge "$CURRENT_VERSION" "$CC_MIN_VERSION"; then
  cc_log "Claude Code CLI 已安装,版本 $CURRENT_VERSION(满足最低要求 $CC_MIN_VERSION),无需操作。"
  exit 0
fi

if [ -n "$CURRENT_VERSION" ]; then
  cc_warn "当前 Claude Code CLI 版本 $CURRENT_VERSION 低于最低要求 $CC_MIN_VERSION,准备升级..."
else
  cc_log "未检测到 Claude Code CLI,准备安装 $CC_NPM_PACKAGE..."
fi

# --- 4. 安装 / 升级 ---
npm install -g "${CC_NPM_PACKAGE}@latest"

NEW_VERSION="$(claude --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "?")"
cc_log "✅ Claude Code CLI 安装完成,版本 $NEW_VERSION"
cc_log "提示:新开终端或执行 source ~/.bashrc 即可生效。"


path: scripts/doctor.sh
content: #!/usr/bin/env bash
# doctor.sh — 输出当前环境状态(JSON 格式),供 skill 决策使用
#
# 每次 skill 被触发时都会先运行本脚本,根据输出的 JSON 判断:
#   - 是否需要安装 Claude Code CLI
#   - 当前配置属于谁(本 skill / 用户手动 / 无配置)
#   - onboarding 是否标记完成
#   - 关键 Key 是否已设置
#
# 本脚本不修改任何文件,只读检测。

set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/helpers.sh"
SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"

# --- 检测 Claude Code CLI 是否安装 ---
claude_installed=false
claude_version=""
claude_outdated=false

if command -v claude >/dev/null 2>&1; then
  claude_installed=true
  claude_version="$(claude --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")"
  if [ -n "$claude_version" ]; then
    if ! cc_version_ge "$claude_version" "$CC_MIN_VERSION"; then
      claude_outdated=true
    fi
  fi
fi

# --- 判断配置归属 ---
config_state="clean"
active_profile="null"
user_settings_summary="null"

if [ -f "$CC_ACTIVE_FILE" ]; then
  config_state="managed-by-skill"
  ap="$(tr -d '[:space:]' < "$CC_ACTIVE_FILE")"
  active_profile="\"$ap\""
elif [ -f "$CC_SETTINGS" ]; then
  USER_BASE_URL="$(cc_read_env_value ANTHROPIC_BASE_URL)"
  USER_MODEL="$(cc_read_env_value ANTHROPIC_MODEL)"
  if [ -n "$USER_BASE_URL" ] || [ -n "$USER_MODEL" ]; then
    config_state="user-managed"
    user_settings_summary="{\"base_url\":\"$USER_BASE_URL\",\"model\":\"$USER_MODEL\"}"
  fi
fi

settings_exists=false
[ -f "$CC_SETTINGS" ] && settings_exists=true

# --- onboarding 状态 ---
onboarding_complete=false
if [ -f "$CC_USER_JSON" ]; then
  if grep -q '"hasCompletedOnboarding"\s*:\s*true' "$CC_USER_JSON"; then
    onboarding_complete=true
  fi
fi

# --- token 文件 ---
token_set=false
[ -s "$CC_TOKEN_FILE" ] && token_set=true

# --- 各 provider 的 Key 是否设置 ---
ark_api_key_set=false
custom_api_key_set=false

[ -n "${ARK_API_KEY:-}" ] && ark_api_key_set=true
[ -n "${CUSTOM_ANTHROPIC_KEY:-}" ] && custom_api_key_set=true

# --- ArkClaw / OpenClaw 沙箱托管的环境变量探测 ---
# 沙箱通常会注入: ARK_API_KEY / ARK_MODEL_ID / ARK_AGENT_PLAN / ARK_CODING_PLAN
# 列在 OPENCLAW_SERVICE_MANAGED_ENV_KEYS 中的变量是被托管的
ark_managed_env=false
ark_agent_plan=false
ark_coding_plan=false
ark_api_key_managed=false
ark_model_id="${ARK_MODEL_ID:-}"

if [ -n "${OPENCLAW_SERVICE_MANAGED_ENV_KEYS:-}" ]; then
  ark_managed_env=true
  case ",${OPENCLAW_SERVICE_MANAGED_ENV_KEYS}," in
    *,ARK_API_KEY,*) ark_api_key_managed=true ;;
  esac
fi
case "$(printf '%s' "${ARK_AGENT_PLAN:-}" | tr '[:upper:]' '[:lower:]')" in
  true|1|yes) ark_agent_plan=true ;;
esac
case "$(printf '%s' "${ARK_CODING_PLAN:-}" | tr '[:upper:]' '[:lower:]')" in
  true|1|yes) ark_coding_plan=true ;;
esac

# 兼容: Key 写在 .bashrc 里但当前 shell 没 source
if [ "$config_state" = "managed-by-skill" ]; then
  ACTIVE="$(echo "$active_profile" | tr -d '"')"
  case "$ACTIVE" in
    agentplan) [ "$ark_api_key_set" = false ] && grep -q '^export ARK_API_KEY=' "$HOME/.bashrc" 2>/dev/null && ark_api_key_set=true ;;
    codingplan) [ "$ark_api_key_set" = false ] && grep -q '^export ARK_API_KEY=' "$HOME/.bashrc" 2>/dev/null && ark_api_key_set=true ;;
    custom)    [ "$custom_api_key_set" = false ] && grep -q '^export CUSTOM_ANTHROPIC_KEY=' "$HOME/.bashrc" 2>/dev/null && custom_api_key_set=true ;;
  esac
fi

# --- 用户手动配置时,运行兼容性检查 ---
user_health="null"
if [ "$config_state" = "user-managed" ] && [ -x "$SKILL_DIR/scripts/health-check.sh" ]; then
  user_health="$(bash "$SKILL_DIR/scripts/health-check.sh" 2>/dev/null || echo 'null')"
fi

# --- 生成问题列表 ---
issues=()
if [ "$claude_installed" = false ]; then
  issues+=("\"Claude Code CLI 未安装\"")
elif [ "$claude_outdated" = true ]; then
  issues+=("\"Claude Code CLI 版本过旧($claude_version),需要 ≥ $CC_MIN_VERSION\"")
fi

ACTIVE="$(echo "$active_profile" | tr -d '"')"
case "$ACTIVE" in
  agentplan)
    [ "$ark_api_key_set" = false ] && issues+=("\"AgentPlan 模式已激活,但 ARK_API_KEY 未设置\"")
    [ "$token_set" = false ] && issues+=("\"AgentPlan 模式已激活,但 ~/.claude/.token 未生成\"")
    ;;
  codingplan)
    [ "$ark_api_key_set" = false ] && issues+=("\"Coding Plan 模式已激活,但 ARK_API_KEY 未设置\"")
    [ "$token_set" = false ] && issues+=("\"Coding Plan 模式已激活,但 ~/.claude/.token 未生成\"")
    ;;
  custom)
    [ "$custom_api_key_set" = false ] && issues+=("\"自定义模式已激活,但 CUSTOM_ANTHROPIC_KEY 未设置\"")
    [ "$token_set" = false ] && issues+=("\"自定义模式已激活,但 ~/.claude/.token 未生成\"")
    ;;
esac

if [ "$config_state" = "managed-by-skill" ] && [ "$onboarding_complete" = false ]; then
  issues+=("\"~/.claude.json 缺少 hasCompletedOnboarding=true,运行 ensure-onboarding.sh 可修复\"")
fi

if [ "$config_state" = "clean" ] && [ "$claude_installed" = true ]; then
  issues+=("\"Claude Code CLI 已安装但尚未配置模型服务,请选择 AgentPlan 或自定义网关\"")
fi

# --- 输出 JSON ---
issues_json="$(IFS=,; echo "${issues[*]}")"
cat <<EOF
{
  "claude_installed": $claude_installed,
  "claude_version": "$claude_version",
  "claude_outdated": $claude_outdated,
  "config_state": "$config_state",
  "active_profile": $active_profile,
  "user_settings_summary": $user_settings_summary,
  "user_health": $user_health,
  "settings_exists": $settings_exists,
  "onboarding_complete": $onboarding_complete,
  "token_set": $token_set,
  "ark_api_key_set": $ark_api_key_set,
  "custom_api_key_set": $custom_api_key_set,
  "ark_managed_env": $ark_managed_env,
  "ark_agent_plan": $ark_agent_plan,
  "ark_coding_plan": $ark_coding_plan,
  "ark_api_key_managed": $ark_api_key_managed,
  "ark_model_id": "$ark_model_id",
  "issues": [$issues_json]
}
EOF


path: scripts/helpers.sh
content: #!/usr/bin/env bash
# helpers.sh — claude-code-allinone 共享工具函数
#
# 使用方式: . "$SCRIPT_DIR/helpers.sh"
#
# 不直接执行,只提供给其他脚本 source。

# ============================================================
# 常量
# ============================================================
CC_MIN_VERSION="2.1.0"
CC_NPM_PACKAGE="@anthropic-ai/claude-code"

CC_HOME="$HOME/.claude"
CC_PROFILES_DIR="$CC_HOME/profiles"
CC_SETTINGS="$CC_HOME/settings.json"
CC_TOKEN_FILE="$CC_HOME/.token"
CC_ACTIVE_FILE="$CC_HOME/active-profile"
CC_USER_JSON="$HOME/.claude.json"

# ============================================================
# 日志辅助
# ============================================================
cc_log()  { printf '\033[1;36m[claude-code]\033[0m %s\n' "$*"; }
cc_warn() { printf '\033[1;33m[claude-code]\033[0m %s\n' "$*"; }
cc_err()  { printf '\033[1;31m[claude-code]\033[0m %s\n' "$*" >&2; }

# ============================================================
# 版本比较: $1 >= $2 ?
# ============================================================
cc_version_ge() {
  [ "$(printf '%s\n%s\n' "$2" "$1" | sort -V | head -n1)" = "$2" ]
}

# ============================================================
# 安全 JSON 写文件 (没装 jq 也能用)
# ============================================================
cc_write_json_atomic() {
  local target="$1"
  local content="$2"
  local tmp
  tmp="$(mktemp "${target}.XXXXXX")"
  printf '%s' "$content" > "$tmp"
  mv -f "$tmp" "$target"
  chmod 600 "$target" 2>/dev/null || true
}

# ============================================================
# 备份现有文件 (不是软链才备份)
# ============================================================
cc_backup_if_needed() {
  local f="$1"
  if [ -f "$f" ] && [ ! -L "$f" ]; then
    local bak="${f}.bak.$(date +%Y%m%d_%H%M%S)"
    cp "$f" "$bak"
    cc_log "  → 已备份 $f → $bak"
  fi
}

# ============================================================
# 把 ~/.claude.json 标记 hasCompletedOnboarding=true
# 没装 jq 也得能干活: 退化为 sed 改写或最小化新建
# ============================================================
cc_ensure_onboarding_completed() {
  if [ ! -f "$CC_USER_JSON" ]; then
    cc_write_json_atomic "$CC_USER_JSON" '{"hasCompletedOnboarding":true}'
    return 0
  fi

  if command -v jq >/dev/null 2>&1; then
    local tmp
    tmp="$(mktemp "${CC_USER_JSON}.XXXXXX")"
    jq '. + {"hasCompletedOnboarding": true}' "$CC_USER_JSON" > "$tmp" 2>/dev/null \
      && mv -f "$tmp" "$CC_USER_JSON" \
      || { rm -f "$tmp"; return 1; }
    chmod 600 "$CC_USER_JSON" 2>/dev/null || true
    return 0
  fi

  # 无 jq 兜底: 简单字符串替换
  if grep -q '"hasCompletedOnboarding"' "$CC_USER_JSON"; then
    sed -i 's/"hasCompletedOnboarding"\s*:\s*false/"hasCompletedOnboarding": true/' "$CC_USER_JSON"
  else
    # 在最后一个 } 之前插入字段
    sed -i '$ s/}$/,"hasCompletedOnboarding":true}/' "$CC_USER_JSON"
  fi
}

# ============================================================
# 写 Key 到 .token 文件 (settings.json 的 apiKeyHelper 会读它)
# ============================================================
cc_write_token() {
  local key="$1"
  mkdir -p "$CC_HOME"
  printf '%s' "$key" > "$CC_TOKEN_FILE"
  chmod 600 "$CC_TOKEN_FILE" 2>/dev/null || true
}

# ============================================================
# 把模板 profile 渲染到 ~/.claude/profiles/<name>.json
# 第三个起为 sed 替换对 (key=value)
# ============================================================
cc_render_profile() {
  local src="$1" dst="$2"
  shift 2
  cp "$src" "$dst"
  while [ $# -gt 0 ]; do
    local pair="$1"; shift
    local k="${pair%%=*}"
    local v="${pair#*=}"
    # 用 | 作分隔符避免 URL 里的 / 把 sed 搞坏
    local v_esc
    v_esc="$(printf '%s' "$v" | sed -e 's/[\&|]/\\&/g')"
    sed -i "s|$k|$v_esc|g" "$dst"
  done
  chmod 600 "$dst" 2>/dev/null || true
}

# ============================================================
# 写 Key 到 ~/.bashrc (供调试和 fallback 使用)
# 主链路读 .token 文件,不依赖此变量
# ============================================================
cc_persist_env_to_bashrc() {
  local var_name="$1" var_val="$2"
  sed -i "/^export $var_name=/d" "$HOME/.bashrc" 2>/dev/null || true
  echo "export $var_name=\"$var_val\"" >> "$HOME/.bashrc"
  export "$var_name=$var_val"
}

# ============================================================
# 把当前 active profile 名写入 marker
# ============================================================
cc_set_active_profile() {
  local name="$1"
  mkdir -p "$CC_HOME"
  echo "$name" > "$CC_ACTIVE_FILE"
}

# ============================================================
# Key 格式自检 — 不联网,本地正则
# 火山方舟 Key 通常是字母数字和连字符,长度 20+
# 仅用于挡掉"明显贴错"的输入(空格、换行、含中文等)
# 返回 0 = 看起来像个 Key;1 = 明显不像
# ============================================================
cc_format_check_key() {
  local key="$1"
  [ -n "$key" ] || return 1
  # 长度至少 20
  [ "${#key}" -ge 20 ] || return 1
  # 仅允许字母数字 + 连字符 + 下划线 + 点
  echo "$key" | grep -qE '^[A-Za-z0-9._-]+$' || return 1
  return 0
}

# ============================================================
# Key 活性预校验 — 抗网络抖动
# 用法: cc_ping_key <key> <base_url>
# 返回:
#   0 = 联通且鉴权 OK (拿到 2xx/4xx 中的非鉴权码)
#   1 = 明确鉴权失败 (HTTP 401 / 403)
#   2 = 网络抖动,无法判断 (3 次重试都没拿到明确答案,放行写盘)
#
# 设计原则:
#   - 用 /models endpoint,不消耗推理 token,不计费
#   - 单次超时 3 秒,最多 3 次重试,最坏 9 秒
#   - 只看到 401/403 才报错,其它一律放行
# ============================================================
cc_ping_key() {
  local key="$1" base_url="$2"
  local code
  local i
  for i in 1 2 3; do
    code=$(curl -sS -o /dev/null -w "%{http_code}" \
                --max-time 3 \
                -H "Authorization: Bearer $key" \
                "${base_url%/}/models" 2>/dev/null || echo "000")
    case "$code" in
      401|403)
        return 1
        ;;
      2*|400|404|405|422)
        return 0
        ;;
      000|408|429|5*)
        sleep 1
        continue
        ;;
      *)
        # 未知状态码,保守视为联通
        return 0
        ;;
    esac
  done
  # 三次都没拿到明确答案,视为网络问题,放行
  return 2
}

# ============================================================
# 读取 settings.json 中某个 env key 的值 (无 jq 兜底)
# ============================================================
cc_read_env_value() {
  local key="$1"
  [ -f "$CC_SETTINGS" ] || { echo ""; return; }
  if command -v jq >/dev/null 2>&1; then
    jq -r ".env.\"$key\" // empty" "$CC_SETTINGS" 2>/dev/null
  else
    grep -E "\"$key\"\s*:" "$CC_SETTINGS" \
      | head -1 \
      | sed -E 's/.*"'"$key"'"\s*:\s*"([^"]*)".*/\1/'
  fi
}


path: scripts/setup-custom.sh
content: #!/usr/bin/env bash
# setup-custom.sh — 一键配置自定义 Anthropic 兼容网关
#
# 用法: bash setup-custom.sh "<base_url>" "<Key>" "<model>"
#
# 适用场景:
#   - 公司自建的 Anthropic Messages 协议网关
#   - 第三方厂商提供的 Anthropic 兼容端点
#   - 火山方舟之外的其他 Anthropic 协议服务
#
# 这个脚本会依次完成:
#   1. 探测 base_url + Key 是否能跑通(可选,失败仅警告不退出)
#   2. 将 CUSTOM_ANTHROPIC_KEY 保存到 ~/.bashrc
#   3. 把 Key 写入 ~/.claude/.token
#   4. 渲染 ~/.claude/profiles/custom.json (替换 __BASE_URL__ / __MODEL__)
#   5. 把 ~/.claude/settings.json 替换为该 profile (已有则先备份)
#   6. 标记 ~/.claude.json hasCompletedOnboarding=true
#   7. 写 active-profile marker

set -e

BASE_URL="${1:-}"
KEY="${2:-}"
MODEL="${3:-}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/helpers.sh"

if [ -z "$BASE_URL" ] || [ -z "$KEY" ] || [ -z "$MODEL" ]; then
  cc_err "用法: bash setup-custom.sh \"<base_url>\" \"<Key>\" \"<model>\""
  cc_err "示例: bash setup-custom.sh \"https://internal-gw.example.com\" \"sk-xxx\" \"claude-3-5-sonnet\""
  exit 1
fi

# 去掉 base_url 末尾的斜杠 (claude code 会自动拼)
BASE_URL="${BASE_URL%/}"

SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
TMPL="$SKILL_DIR/profiles/custom.json"
TARGET_PROFILE="$CC_PROFILES_DIR/custom.json"

mkdir -p "$CC_PROFILES_DIR"

# ============================================================
# 步骤 1: 轻量探测 base_url(失败仅警告,不退出 — 用户网络可能临时抖动)
# ============================================================
cc_log "步骤 1/7: 探测 $BASE_URL ..."
if command -v curl >/dev/null 2>&1; then
  PROBE_CODE="$(curl -sS -m 8 -o /dev/null -w '%{http_code}' \
                  -H "x-api-key: $KEY" \
                  -H "anthropic-version: 2023-06-01" \
                  -X POST "$BASE_URL/v1/messages" \
                  -H "Content-Type: application/json" \
                  -d '{"model":"'"$MODEL"'","max_tokens":1,"messages":[{"role":"user","content":"ping"}]}' 2>/dev/null || echo "000")"
  case "$PROBE_CODE" in
    2*|400|422)
      cc_log "  → 端点可达 (HTTP $PROBE_CODE)"
      ;;
    401|403)
      cc_warn "  → 端点可达但鉴权失败 (HTTP $PROBE_CODE),请确认 Key 正确"
      ;;
    404)
      cc_warn "  → /v1/messages 返回 404,base_url 可能不正确;继续配置但实际调用会失败"
      ;;
    000)
      cc_warn "  → 网络不可达或超时,继续配置(用户可能后续会修)"
      ;;
    *)
      cc_warn "  → 探测返回 HTTP $PROBE_CODE,继续配置"
      ;;
  esac
else
  cc_warn "  → 没装 curl,跳过探测"
fi

# ============================================================
# 步骤 2: 保存 CUSTOM_ANTHROPIC_KEY 到 ~/.bashrc
# ============================================================
cc_persist_env_to_bashrc "CUSTOM_ANTHROPIC_KEY" "$KEY"
cc_log "步骤 2/7: CUSTOM_ANTHROPIC_KEY 已保存到 ~/.bashrc"

# ============================================================
# 步骤 3: 写 Key 到 ~/.claude/.token
# ============================================================
cc_write_token "$KEY"
cc_log "步骤 3/7: Key 已写入 ~/.claude/.token (仅 600 权限)"

# ============================================================
# 步骤 4: 渲染 profile 文件
# ============================================================
cc_render_profile "$TMPL" "$TARGET_PROFILE" \
  "__BASE_URL__=$BASE_URL" \
  "__MODEL__=$MODEL"
cc_log "步骤 4/7: 配置文件已写入 $TARGET_PROFILE"
cc_log "  → base_url: $BASE_URL"
cc_log "  → model:    $MODEL"

# ============================================================
# 步骤 5: 激活该 profile (备份旧 settings.json 后覆盖)
# ============================================================
cc_backup_if_needed "$CC_SETTINGS"
cp "$TARGET_PROFILE" "$CC_SETTINGS"
chmod 600 "$CC_SETTINGS" 2>/dev/null || true
cc_log "步骤 5/7: 已激活 custom 配置 → $CC_SETTINGS"

# ============================================================
# 步骤 6: 标记 onboarding 完成
# ============================================================
cc_ensure_onboarding_completed
cc_log "步骤 6/7: ~/.claude.json hasCompletedOnboarding=true"

# ============================================================
# 步骤 7: 写 active-profile marker
# ============================================================
cc_set_active_profile "custom"
cc_log "步骤 7/7: ~/.claude/active-profile = custom"

echo ""
cc_log "✅ 自定义网关配置完成!"
cc_log "   现在可以直接说编程需求,例如:"
cc_log "   「用 claude code 帮我写一个 Python 脚本」"
cc_log ""
cc_log "💡 需要切换到其他模型服务? 执行: bash switch-profile.sh"


path: scripts/run.sh
content: #!/usr/bin/env bash
# run.sh — 统一入口,执行用户的编程需求 (review / build 双模式智能路由)
#
# 用法:
#   bash run.sh "<用户需求原文>"
#   bash run.sh --mode review "<用户需求>"
#   bash run.sh --mode build  "<用户需求>"
#   bash run.sh --cwd /path/to/workspace "<用户需求>"
#   bash run.sh --timeout 110 "<用户需求>"
#
# 模式判定:
#   - 显式 --mode 优先
#   - 否则按用户需求关键词扫描:
#       review/评审/审查/分析/评估/检查/lint/code review → review 模式
#       否则                                            → build 模式
#
# 防挂死核心(v3.1 现网验证):
#   nohup setsid claude ... </dev/null > log 2>&1 &
#   while sleep 1; check 进程; check 日志末尾;
#   超过 timeout 主动 kill -9, 输出已生成日志
#
# ⛔ 主对话 Agent 调用本脚本时,禁止叠加 pty: true,会破坏 </dev/null 隔离

set -u

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/helpers.sh"

MODE=""
CWD=""
TIMEOUT_SEC=110
PROMPT=""

# --- 解析参数 ---
while [ $# -gt 0 ]; do
  case "$1" in
    --mode)
      MODE="$2"; shift 2 ;;
    --cwd)
      CWD="$2"; shift 2 ;;
    --timeout)
      TIMEOUT_SEC="$2"; shift 2 ;;
    --help|-h)
      grep '^#' "$0" | sed 's/^#//'
      exit 0
      ;;
    --*)
      cc_err "未知参数: $1"
      exit 2
      ;;
    *)
      if [ -z "$PROMPT" ]; then
        PROMPT="$1"
      else
        PROMPT="$PROMPT $1"
      fi
      shift
      ;;
  esac
done

if [ -z "$PROMPT" ]; then
  cc_err "缺少用户需求 prompt"
  cc_err "用法: bash run.sh \"<用户需求>\""
  exit 2
fi

# --- 自动判定模式 ---
if [ -z "$MODE" ]; then
  # 关键词扫描 (中英文都查)
  case "$PROMPT" in
    *review*|*Review*|*REVIEW*|*评审*|*审查*|*分析*|*评估*|*检查*|*lint*|*Lint*|*只读*|*read-only*)
      MODE="review"
      ;;
    *)
      MODE="build"
      ;;
  esac
fi

case "$MODE" in
  review|build) ;;
  *)
    cc_err "无效的 mode: $MODE (只支持 review / build)"
    exit 2
    ;;
esac

# --- 切到工作目录 ---
if [ -n "$CWD" ]; then
  if [ ! -d "$CWD" ]; then
    cc_err "工作目录不存在: $CWD"
    exit 2
  fi
  cd "$CWD"
fi

# --- 准备日志路径 ---
RUN_TS="$(date +%Y%m%d_%H%M%S)"
LOG_DIR="$HOME/.claude-runs"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/run-${MODE}-${RUN_TS}.log"

# --- 找到 claude 可执行文件 ---
if ! command -v claude >/dev/null 2>&1; then
  if [ -x "$HOME/.npm-global/bin/claude" ]; then
    export PATH="$HOME/.npm-global/bin:$PATH"
  fi
fi
if ! command -v claude >/dev/null 2>&1; then
  cc_err "claude 命令不可用,请先运行 bash scripts/install.sh"
  exit 3
fi

# --- 环境兜底:确保 onboarding 完成 ---
cc_ensure_onboarding_completed >/dev/null 2>&1 || true

# --- 拼装 claude 命令参数 ---
CLAUDE_ARGS=( -p "$PROMPT" )

case "$MODE" in
  review)
    CLAUDE_ARGS+=( --permission-mode plan )
    cc_log "模式: review (只读分析,--permission-mode plan)"
    ;;
  build)
    CLAUDE_ARGS+=( --allowedTools "Read,Glob,Grep,LS,Bash,Edit,Write" )
    cc_log "模式: build (允许的工具: Read,Glob,Grep,LS,Bash,Edit,Write)"
    ;;
esac

cc_log "工作目录: $(pwd)"
cc_log "日志文件: $LOG_FILE"
cc_log "超时: ${TIMEOUT_SEC}s"

# --- 启动: nohup setsid + </dev/null + log file ---
# setsid 把 claude 放进新会话,完全脱离任何可能的 controlling tty
# </dev/null 把 stdin 接到 /dev/null,claude 不会读到任何东西
# >$LOG_FILE 2>&1 把所有输出落盘
nohup setsid claude "${CLAUDE_ARGS[@]}" </dev/null >"$LOG_FILE" 2>&1 &
CLAUDE_PID=$!

cc_log "Claude PID: $CLAUDE_PID"

# --- 轮询: 进程存活就继续等,到点就 kill ---
elapsed=0
while kill -0 "$CLAUDE_PID" 2>/dev/null; do
  if [ "$elapsed" -ge "$TIMEOUT_SEC" ]; then
    cc_warn "超时 ${TIMEOUT_SEC}s,主动结束 PID $CLAUDE_PID"
    kill -9 "$CLAUDE_PID" 2>/dev/null || true
    sleep 1
    break
  fi
  sleep 1
  elapsed=$((elapsed + 1))
done

# 等 1 秒让最后的 buffer flush 到日志
sleep 1

# --- 输出: 取日志末尾若干行 ---
cc_log "================ Claude Code 输出 (来自 $LOG_FILE) ================"
if [ -s "$LOG_FILE" ]; then
  # 取最后 400 行避免太多;如果整体不到 400 行就全打
  TOTAL_LINES="$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0)"
  if [ "$TOTAL_LINES" -le 400 ]; then
    cat "$LOG_FILE"
  else
    echo "...(前 $((TOTAL_LINES - 400)) 行已省略,完整日志见 $LOG_FILE)..."
    tail -n 400 "$LOG_FILE"
  fi
else
  cc_warn "日志为空,可能 claude 启动失败或网络中断"
fi

cc_log "================ 输出结束 ================"
cc_log "✅ run 结束,完整日志保留在: $LOG_FILE"


path: scripts/ensure-onboarding.sh
content: #!/usr/bin/env bash
# ensure-onboarding.sh — 确保 ~/.claude.json 标记 onboarding 已完成
#
# Claude Code CLI 在非交互模式 (-p) 下,如果 ~/.claude.json 中
# hasCompletedOnboarding 不为 true,会直接报错退出:
#   "Onboarding has not been completed"
#
# 本脚本幂等地把这个字段设成 true,不动任何其他字段。

set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/helpers.sh"

cc_ensure_onboarding_completed
cc_log "✅ ~/.claude.json: hasCompletedOnboarding=true"


path: scripts/health-check.sh
content: #!/usr/bin/env bash
# health-check.sh — 对用户已有的 claude code 配置做兼容性检查
#
# 什么时候会调用?
#   当检测到用户手动写过 ~/.claude/settings.json (不是本 skill 生成的) 时,
#   doctor.sh 会自动调用本脚本,检查现有配置能否正常运行 Claude Code CLI。
#
# 输出: JSON 格式
#   {
#     "is_healthy": true/false,
#     "checks": [...],     // 每项检查的详细结果
#     "issues": [...]      // 不通过的项目(人话描述,可直接展示给用户)
#   }
#
# 本脚本不修改任何文件,只读检测。

set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/helpers.sh"

issues=()
checks=()

add_check() {
  local name="$1" ok="$2" detail="$3"
  checks+=("{\"name\":\"$name\",\"ok\":$ok,\"detail\":\"$detail\"}")
  if [ "$ok" = "false" ]; then
    issues+=("\"$detail\"")
  fi
}

# 文件不存在就直接返回
if [ ! -f "$CC_SETTINGS" ]; then
  echo "{\"is_healthy\":false,\"checks\":[],\"issues\":[\"~/.claude/settings.json 文件不存在\"]}"
  exit 0
fi

# --- 提取关键字段 (env 块下的几个常用变量) ---
BASE_URL="$(cc_read_env_value ANTHROPIC_BASE_URL)"
MODEL="$(cc_read_env_value ANTHROPIC_MODEL)"
USED_API_KEY_VAR=""
if grep -q 'ANTHROPIC_API_KEY' "$CC_SETTINGS"; then
  USED_API_KEY_VAR="ANTHROPIC_API_KEY"
elif grep -q 'ANTHROPIC_AUTH_TOKEN' "$CC_SETTINGS"; then
  USED_API_KEY_VAR="ANTHROPIC_AUTH_TOKEN"
fi

# ============================================================
# 检查 1: settings.json 是否合法 JSON
# ============================================================
if command -v jq >/dev/null 2>&1; then
  if jq -e . "$CC_SETTINGS" >/dev/null 2>&1; then
    add_check "settings_valid_json" true "settings.json 是合法 JSON"
  else
    add_check "settings_valid_json" false "~/.claude/settings.json 不是合法 JSON,Claude Code 启动会直接 abort"
  fi
else
  add_check "settings_valid_json" true "未安装 jq,跳过 JSON 合法性检查"
fi

# ============================================================
# 检查 2: ANTHROPIC_BASE_URL 是否设置
# ============================================================
if [ -n "$BASE_URL" ]; then
  add_check "base_url_set" true "base_url=$BASE_URL"
else
  add_check "base_url_set" false "settings.json 的 env 块缺少 ANTHROPIC_BASE_URL,Claude Code 不知道请求发往哪里"
fi

# ============================================================
# 检查 3: 用了 ANTHROPIC_AUTH_TOKEN 而不是 ANTHROPIC_API_KEY
# ============================================================
# ANTHROPIC_API_KEY 会被 CLI 强制走 anthropic.com 鉴权链路,境内 IDC 必然失败
case "$USED_API_KEY_VAR" in
  ANTHROPIC_AUTH_TOKEN|"")
    # 没在 env 块里写 Key 是 OK 的(可能用 apiKeyHelper)
    add_check "no_anthropic_api_key" true "未使用 ANTHROPIC_API_KEY (推荐)"
    ;;
  ANTHROPIC_API_KEY)
    add_check "no_anthropic_api_key" false "settings.json 使用了 ANTHROPIC_API_KEY,会被 CLI 强制走 anthropic.com 鉴权,境内 IDC 必然失败。请改用 ANTHROPIC_AUTH_TOKEN 或 apiKeyHelper"
    ;;
esac

# ============================================================
# 检查 4: 是否有 Key 来源 (env.ANTHROPIC_AUTH_TOKEN / apiKeyHelper / .token 文件)
# ============================================================
HAS_KEY_SOURCE=false
if grep -q 'ANTHROPIC_AUTH_TOKEN' "$CC_SETTINGS"; then HAS_KEY_SOURCE=true; fi
if grep -q 'apiKeyHelper' "$CC_SETTINGS"; then HAS_KEY_SOURCE=true; fi
if [ -s "$CC_TOKEN_FILE" ]; then HAS_KEY_SOURCE=true; fi

if [ "$HAS_KEY_SOURCE" = true ]; then
  add_check "key_source_available" true "找到至少一种 Key 来源 (env / apiKeyHelper / .token)"
else
  add_check "key_source_available" false "settings.json 没配 ANTHROPIC_AUTH_TOKEN / apiKeyHelper,~/.claude/.token 也不存在,Claude Code 连接时会被拒(401)"
fi

# ============================================================
# 检查 5: ~/.claude.json 标记 onboarding 完成
# ============================================================
if [ -f "$CC_USER_JSON" ] && grep -q '"hasCompletedOnboarding"\s*:\s*true' "$CC_USER_JSON"; then
  add_check "onboarding_complete" true "~/.claude.json hasCompletedOnboarding=true"
else
  add_check "onboarding_complete" false "~/.claude.json 缺少 hasCompletedOnboarding=true,Claude Code 在非交互模式下会直接 abort"
fi

# ============================================================
# 检查 6: ANTHROPIC_MODEL 是否设置
# ============================================================
if [ -n "$MODEL" ]; then
  add_check "model_set" true "model=$MODEL"
else
  add_check "model_set" false "settings.json 的 env 块缺少 ANTHROPIC_MODEL,部分自定义网关会找不到模型"
fi

# --- 输出 JSON ---
is_healthy=true
[ ${#issues[@]} -gt 0 ] && is_healthy=false

checks_json="$(IFS=,; echo "${checks[*]}")"
issues_json="$(IFS=,; echo "${issues[*]}")"

cat <<EOF
{
  "is_healthy": $is_healthy,
  "checks": [$checks_json],
  "issues": [$issues_json]
}
EOF


path: references/providers.md
content: # 模型供应商参考 — 引导用户选择 / 收集 Key

> 这份文档给主对话 Agent 看:用户问"我应该选哪个"或"这个 Key 怎么搞"时,从这里抄答案。

## 核心原则:默认 AgentPlan,不主动列菜单

减少用户感知是本 skill 的核心设计。**首次配置时一律默认走 AgentPlan**,只让用户粘贴一次 Key。**只有**用户主动表达"我有自己的 Anthropic 兼容网关 / 自定义 base_url / 别的 Key"等语义时,才进入 custom 分支。

⛔ 禁止主动给用户列 A/B 菜单。这会增加感知,违背"无感配置"原则。

---

## A. 火山方舟 AgentPlan(默认 / 无需询问)

**这是首次配置的默认路径。** 不要问用户"要选 A 还是 B",直接说:

> "准备给你配置 Claude Code,使用火山方舟 AgentPlan(套餐内额度,ark-code-latest 智能路由)。请粘贴 AgentPlan 控制台分配的专属 Key:"

- **base_url**: `https://ark.cn-beijing.volces.com/api/plan`
- **协议**: 原生 Anthropic Messages 协议(Claude Code 直连即可)
- **模型名**: `ark-code-latest`(智能路由,后端自动分发到 Kimi K2 / Doubao / DeepSeek / GLM 等)
- **Key 来源**:登录 [AgentPlan 控制台](https://www.volcengine.com/) → 找到"AgentPlan 专属 Key"页面 → 复制
- **优点**:
  - 套餐内额度,不需要单独付费
  - 智能路由,后端帮你选最合适的模型
  - 境内 IDC 直连,延迟低
- **限制**:
  - Key 必须是 AgentPlan 控制台分配的"专属 Key",不能用普通 v3 API Key
  - 不支持自定义 temperature / top_p

### 配置命令
```bash
bash scripts/setup-agentplan.sh "<AgentPlan 专属 Key>"
```

### 引导话术(默认场景,**不**给 A/B 菜单)
```
准备给你配置 Claude Code,使用火山方舟 AgentPlan(套餐内额度,ark-code-latest 智能路由)。

请粘贴你的 AgentPlan 专属 Key:

📋 怎么获取 Key?
   1. 登录火山方舟控制台
   2. 进入 AgentPlan 模块
   3. 找到"专属 Key"或"分配 Key"页面
   4. 复制里面的 Key

⚠️ 注意:这个 Key 跟普通的"API Key 管理"里的 Key 不是一回事,
   AgentPlan 必须用专属 Key,普通 Key 会报 401。
```

---

## B. 自定义 Anthropic 兼容网关(仅用户主动要求时)

**触发条件**:用户语句里出现以下任一(或类似)语义,才进入这条分支。否则一律默认 AgentPlan,不询问。

- "我有自己的 / 我们公司的 / 我们自建的 网关 / base_url / 端点"
- "我有别的 Anthropic 兼容 Key"
- "用 claude-code-router / kimi-router / Bedrock / Vertex 出来的端点"
- "不用 AgentPlan,我有自己的 Key"
- 用户**直接给出**了 base_url + Key + model 三件套

⛔ 用户没说这些,**不要主动**问"要不要用自定义网关"。

- **base_url**: 由用户提供
- **协议**: 必须实现 Anthropic Messages 协议(`POST /v1/messages`)
- **模型名**: 由用户提供
- **典型场景**:
  - 公司自建的 Anthropic 协议网关
  - 第三方厂商提供的 Anthropic 兼容端点
  - 自部署的 Anthropic 协议代理(claude-code-router 等)
- **不适用场景**:
  - OpenAI 协议端点(请去 codex-allinone)
  - 普通 Chat Completions 接口(本 skill 不做协议转换)

### 配置命令
```bash
bash scripts/setup-custom.sh "<base_url>" "<Key>" "<model>"
```

### 引导话术
```
请按顺序提供 3 个信息:

1. base_url(网关地址,不含 /v1 后缀)
   示例: https://my-gateway.example.com

2. API Key
   示例: sk-xxxxxxxxxxxxxxxx

3. 模型名(网关上能识别的)
   示例: claude-3-5-sonnet-20241022 / kimi-k2 / 自定义名

可以一次贴出来,格式:
  base_url=...
  key=...
  model=...
```

setup-custom.sh 会自动:
- 探测 `<base_url>/v1/messages` 是否可达 (curl POST 一次, 返回 200/400/422 视为通)
- 把占位符 `__BASE_URL__` / `__MODEL__` 替换到 profile 文件
- 备份旧 settings.json 后写入新内容

---

## 不支持的供应商(本 skill 不做)

| 供应商 | 协议 | 应该用什么 |
|---|---|---|
| OpenAI 官方 | OpenAI Chat | codex-allinone(OpenAI 协议)|
| 火山方舟 v3 普通 Key | OpenAI Chat | codex-allinone arkv3 模式 |
| Kimi (Moonshot) | OpenAI Chat | codex-allinone kimi 模式 |
| DeepSeek | OpenAI Chat | codex-allinone deepseek 模式 |
| Anthropic 官方 (anthropic.com) | Anthropic | 境内 IDC 网络不通,无法使用 |
| AWS Bedrock | Anthropic via AWS | (后续版本可能支持)|
| Google Vertex | Anthropic via GCP | (后续版本可能支持)|

---

## Key 安全说明

setup 脚本会把 Key 存到 3 个位置(全部在用户家目录,不出沙箱):

1. `~/.bashrc` 的 `export ARK_API_KEY=...` 或 `export CUSTOM_ANTHROPIC_KEY=...`
   - 用途:沙箱重启后自动恢复环境变量
   - 权限:跟 `.bashrc` 一致(通常 644,但内容只在用户家目录)

2. `~/.claude/.token`
   - 用途:settings.json 的 `apiKeyHelper: "cat ~/.claude/.token"` 会读这个
   - 权限:600(仅当前用户可读)

3. `~/.claude/profiles/<name>.json` 不含 Key,只含 base_url / model 等
   - profile 文件不存 Key,切换 profile 时 .token 文件由 switch 脚本同步

⛔ Skill 永远不会把 Key 上传到任何外部服务,也不会在日志里打印完整 Key。


path: references/troubleshooting.md
content: # 故障排查 — 看 doctor.sh 的 issues / 看 claude code 报错信息,对照下表给用户处方

> v3.1 现网验证后总结的踩坑清单。每条都给出"原因 → 处方"。

## 按现象索引

### 1. `command not found: claude`

**原因**:npm 全局 bin 不在 PATH。

**处方**:
```bash
bash scripts/install.sh   # install 会自动写 PATH 到 .bashrc
source ~/.bashrc
```

### 2. doctor.sh 显示 `claude_outdated=true`

**原因**:claude code 版本 < 2.1.0,部分参数(如 `--permission-mode plan`)不存在。

**处方**:
```bash
bash scripts/install.sh   # 会自动升级到最新
```

### 3. `Onboarding has not been completed`

**原因**:`~/.claude.json` 缺少 `hasCompletedOnboarding: true`,Claude Code 在非交互模式 (`-p`) 下会直接 abort。

**处方**:
```bash
bash scripts/ensure-onboarding.sh   # 幂等设置,可反复跑
```

每次 setup-* 脚本都会自动跑这一步,正常情况下用户碰不到。

### 4. `ANTHROPIC_AUTH_TOKEN is not set` / `401 Unauthorized`

**原因**:Key 没生效。可能的具体场景:
- `~/.claude/.token` 文件不存在或为空
- 当前 shell 没 source `~/.bashrc`(环境变量没生效)
- 用户用了错误的 Key (AgentPlan 专属 Key vs 普通 v3 Key 混淆)

**处方**:
```bash
# 看 token 文件
ls -la ~/.claude/.token
wc -c < ~/.claude/.token   # 应该非 0

# 看当前激活 profile 是哪个
cat ~/.claude/active-profile

# 重跑对应 setup
bash scripts/setup-agentplan.sh "<正确的 AgentPlan Key>"
# 或
bash scripts/setup-custom.sh "<base_url>" "<Key>" "<model>"
```

### 5. `403 Forbidden` / 模型在某些 base_url 下不存在

**原因**:base_url 与 Key 不匹配,或者 model 名错误。

**处方**:
- AgentPlan 路径必须 model = `ark-code-latest`
- 自定义网关的 model 名必须是网关那边能识别的(常见误区:把 `claude-3-5-sonnet` 缩写成 `sonnet`)

```bash
# 看当前 settings.json
cat ~/.claude/settings.json
```

### 6. ArkClaw / OpenClaw 沙箱里 claude 一直挂死,无任何输出

**原因**:十有八九是主对话 Agent 调用 Skill 时叠加了 `pty: true`,导致 `</dev/null` 隔离失效,claude 在等 stdin。

**处方**(给主对话 Agent 看):
- 调用 run.sh 时 **不要** 在执行器里启用 PTY
- 直接用 bash 执行 `bash /path/to/skill/scripts/run.sh "<需求>"`,不要走伪终端通道
- run.sh 内部已经做好了 `nohup setsid + </dev/null + log file + 110s 超时` 三件套

如果你不是用 Skill 调用,而是手工跑 claude:
```bash
# 错误示范
claude -p "改个东西"   # 在某些 sandbox 里会等 stdin,挂死

# 正确示范
nohup setsid claude -p "改个东西" --allowedTools "Read,Edit,Write,Bash" </dev/null > /tmp/claude.log 2>&1 &
sleep 60
cat /tmp/claude.log
```

### 7. claude 报 `Input must be provided through stdin or as a prompt argument`

**原因**:你忘了 `-p`,或者 PTY 模式让 claude 以为要从 stdin 读。

**处方**:
- 必须用 `-p "<prompt>"` 传需求(而不是 echo "..." | claude)
- Skill 调用方禁止 PTY

### 8. claude 报 `unknown permission-mode: accept`

**原因**:用了不存在的 mode 值。

**合法值**:`acceptEdits | auto | bypassPermissions | default | dontAsk | plan`

**处方**:本 skill 只用 `plan` (review 模式) 和 `--allowedTools` (build 模式),不要自己改其他 mode。

### 9. build 模式跑完没改文件

**可能原因**:
- 用户的 prompt 里没有"修改"语义,claude 自己也判定不需要改
- `--allowedTools` 白名单不够(比如要装 npm 包但白名单里没 Bash)
- 工作目录不是预期目录

**处方**:
```bash
# 显式指定工作目录
bash scripts/run.sh --cwd /path/to/repo --mode build "明确说要改什么"

# 看完整日志
ls -lt ~/.claude-runs | head -5
cat ~/.claude-runs/run-build-XXX.log
```

### 10. review 模式 claude 输出了改文件的代码块,但没真改

**这是预期行为**:`--permission-mode plan` 就是只读分析,只输出方案/diff,不真去改。
如果用户想动手,改用 build 模式:
```bash
bash scripts/run.sh --mode build "<需求>"
```

### 11. `~/.claude/settings.json` 被覆盖,我之前的自定义配置丢了

**原因**:setup-* / switch-profile.sh 会覆盖 settings.json。但脚本会先备份。

**处方**:
```bash
ls -lt ~/.claude/settings.json.bak.*
# 找到最近的备份,人工合并你需要的字段
```

### 12. 切换 profile 后还是用旧的 base_url

**原因**:settings.json 没真的被替换,或者主进程里有 cached 环境变量。

**处方**:
```bash
# 看当前 settings.json 实际内容
cat ~/.claude/settings.json

# 看 active-profile marker
cat ~/.claude/active-profile

# 重跑 switch
bash scripts/switch-profile.sh agentplan
```

### 13. 沙箱重启后挂了

**原因**:某些 Key 没持久化(只在当前 shell 环境)。

**处方**:本 skill 的 setup-* 脚本会写 `export ARK_API_KEY="..."` 到 ~/.bashrc。`~/.claude/.token` 文件也是磁盘持久的。`source ~/.bashrc` 即恢复。如果还不行:
```bash
grep -E '(ANTHROPIC|ARK_API_KEY|CUSTOM_ANTHROPIC_KEY)' ~/.bashrc
ls -la ~/.claude/{.token,settings.json,active-profile}
```

### 14. doctor.sh 显示 `claude_installed=false` 但你确定装过

**原因**:npm 全局 bin 不在当前 shell 的 PATH。

**处方**:
```bash
ls $HOME/.npm-global/bin/claude   # 看实际位置
export PATH="$HOME/.npm-global/bin:$PATH"
which claude
```

### 15. `failed to load skill ... missing YAML frontmatter`

**原因**:同目录下有其他 Skill 的 SKILL.md 格式错了(不影响本 skill 自身)。

**处方**:可忽略;或者把那个有问题的 skill 目录删了。

## 兜底:把 doctor.sh 完整输出贴给我

如果上面都对不上,让用户:
```bash
bash scripts/doctor.sh
```
把完整 JSON 贴回来,Skill 根据 issues 数组逐项处理。


path: references/routing-flow.md
content: # 路由流程参考 — Skill 内部决策路径详解

> 这份文档是给 Skill 调用方(主对话 Agent)看的:每次触发时该走哪条分支、判断的是什么、命中后跑哪个脚本。

## 顶层路由(三条路径)

每次 skill 被触发,**第一步**必须先跑 doctor.sh,根据它返回的 JSON 决定走哪一条。

```
bash scripts/doctor.sh > /tmp/cc_doctor.json
```

输出 JSON 关键字段:

| 字段 | 含义 |
|---|---|
| `claude_installed` | claude CLI 是否已装 |
| `claude_outdated`  | 装了但版本低于 2.1.0 |
| `config_state`     | `clean` / `managed-by-skill` / `user-managed` |
| `active_profile`   | 当前激活的 profile 名 (managed-by-skill 时) |
| `user_health`      | user-managed 时的健康检查结果 |
| `onboarding_complete` | `~/.claude.json` 是否已标记 onboarding 完成 |
| `token_set`        | `~/.claude/.token` 是否非空 |
| `issues[]`         | 问题列表(人话) |

### 路径 ① 首次配置 (config_state == "clean")

**核心原则:减少用户感知,默认走 AgentPlan,不主动列菜单。**

- 必要时跑 install.sh
- **默认直接走 AgentPlan**:只问用户一句"请粘贴 AgentPlan 专属 Key",拿到 Key 后跑 `setup-agentplan.sh`
- **仅当用户主动表达**"我有自己的 Anthropic 兼容网关 / 别的 base_url / 自定义模型 / 公司自建网关 / claude-code-router"等关键词时,才走 custom 分支(`setup-custom.sh`)
- ⛔ 用户没主动提"自定义"语义时,**禁止**主动给用户列 A/B 菜单 — 这违背"默认 AgentPlan,无感配置"原则
- setup 脚本会自动把 config_state 切到 managed-by-skill,转到路径 ②

### 自定义分支的触发关键词(仅供参考)

只有用户语句里出现以下任一(或类似)语义,才走 custom 分支:

- "我有自己的 / 我们公司的 / 我们自建的 网关 / base_url / 端点"
- "我有别的 Anthropic 兼容 Key"
- "用 claude-code-router / kimi-router / Bedrock / Vertex"
- "不用 AgentPlan,我有自己的 Key"
- 用户直接给出了 base_url + Key + model 三件套

否则一律默认 AgentPlan,不询问。

### 路径 ② 正常使用 (config_state == "managed-by-skill")

- 跑 ensure-onboarding.sh (幂等,几乎无开销)
- 跑 run.sh "<用户需求>"
- 把 run.sh 的输出原样回给用户
- ⛔ 不要解释流程,不要询问"是否切换",体验要等同于直接对话 claude code

### 路径 ③ 兼容性检查 (config_state == "user-managed")

读 `user_health.is_healthy`:

- `true`  → 当作正常使用,转到路径 ②
- `false` → 把 `user_health.issues[]` 展示给用户,询问是 A. 让本 skill 重配 还是 B. 自己修

---

## 二级路由 — review vs build 模式判定

run.sh 内部通过关键词扫描决定走哪个模式。

| 触发关键词 | 模式 | claude 参数 |
|---|---|---|
| review / Review / REVIEW / 评审 / 审查 / 分析 / 评估 / 检查 / lint / 只读 / read-only | review | `--permission-mode plan` |
| (其他所有情况) | build | `--allowedTools "Read,Glob,Grep,LS,Bash,Edit,Write"` |

也可以由 Agent 显式指定:
```bash
bash scripts/run.sh --mode review "审一下 utils.py"
bash scripts/run.sh --mode build  "把这个 bug 修了"
```

### 为什么要分模式?

| 场景 | 不分模式的后果 |
|---|---|
| 用户只想做 PR review | 默认 build 会真去改文件,造成误改 |
| 用户想动手实现 | 默认 plan 模式只输出方案不动手,用户要手动套一遍 |

review 模式的 `--permission-mode plan` 会让 claude 进入 "看,但不改" 状态。
build 模式的 `--allowedTools` 白名单是 v3.1 现网验证过的最小可用集合,既能完成大多数编程任务,也不会误用危险工具。

---

## 防挂死三件套(必须遵守)

run.sh 启动 claude 时严格遵循:

```bash
nohup setsid claude -p "..." </dev/null > "$LOG_FILE" 2>&1 &
CLAUDE_PID=$!

# 轮询 PID,超过 110s 主动 kill -9
elapsed=0
while kill -0 "$CLAUDE_PID" 2>/dev/null; do
  [ $elapsed -ge 110 ] && { kill -9 "$CLAUDE_PID"; break; }
  sleep 1; elapsed=$((elapsed+1))
done

cat "$LOG_FILE"
```

| 元素 | 作用 | 不能省 |
|---|---|---|
| `nohup setsid` | 完全脱离 controlling tty,不受父进程的信号影响 | 否则父 shell 退出会带走 claude |
| `</dev/null`   | claude 的 stdin 接到 /dev/null,绝不等待输入 | 否则 claude 会陷入"等用户回车"挂死 |
| `> log 2>&1`   | 所有输出落盘,后续 cat | 否则 buffer 满会 SIGPIPE 中断 |
| 110s 轮询超时 | 极限值,超过就 kill -9 取已生成内容 | 否则会无限挂起 |

### ⛔ 主对话 Agent 调用本 skill 时的禁忌

- **禁止叠加 `pty: true`**:许多沙箱执行器有"分配伪终端"开关,一旦打开,会把 stdin 重新接回新 PTY,直接破坏 `</dev/null` 隔离,导致 claude 等输入挂死
- **禁止用 `bypassPermissions`**:run.sh 的 `--allowedTools` 已经给出了安全的最小工具集,bypass 会失去白名单保护
- **禁止把 `--permission-mode` 改成 `acceptEdits` / `dontAsk`**:这些模式在沙箱非交互环境会触发未知行为

---

## profile 切换的内部机制

```
~/.claude/profiles/
├── agentplan.json      ← setup-agentplan 写入的模板副本
└── custom.json         ← setup-custom 写入的模板副本(已替换 __BASE_URL__/__MODEL__)

~/.claude/settings.json ← 当前激活 profile 的实际内容(由 setup/switch 复制写入)
~/.claude/.token        ← 当前激活 profile 的 Key (apiKeyHelper 读这个)
~/.claude/active-profile← 文本: 当前激活的 profile 名
```

切换 = 用 `~/.claude/profiles/<name>.json` 覆盖 `settings.json` + 同步 `.token` + 写 `active-profile`。

切换不需要重启任何服务(没有 relay),立刻生效。


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)