Skill: bao2200220/codex-allinone (version 1.0.2)

--- name: codex-allinone description: ArkClaw / OpenClaw 沙箱里一键安装并配置 Codex CLI 的 all-in-one skill。当用户提到 codex、想在 ArkClaw 里用 OpenAI 官方 Coding CLI、提到 ark-code-latest / 火山方舟 AgentPlan / Kimi / DeepSeek / 自定义 OpenAI 兼容端点 接 codex、想切换 codex 的模型供应商、或者说"我没有 OpenAI 官方账号但想用 codex / claude code 这类工具"时,使用本 skill。 license: MIT --- # Codex All-in-One for ArkClaw 让 ArkClaw / OpenClaw 用户在**对话框里**完成 Codex CLI 的全部配置 — 不用打开终端、不用写 config.toml、不用记 base_url。 --- ## 整体流程(决策树) ``` 用户触发本 skill(说"用 codex 帮我..." / "/codex-allinone ..." / 报错 等) │ ▼ ┌───────────────────────────┐ │ 运行 scripts/doctor.sh │ ← 每次触发都会运行,检测当前环境状态 └─────────────┬─────────────┘ │ 当前配置状态 = ? │ ┌─────────────┼──────────────────────────────────┐ │ │ │ "全新环境" "本 skill 配过" "用户手动配过" (从未配置) (之前用本 skill 配置的) (有 config.toml 但不是本 skill 写的) │ │ │ ▼ ▼ ▼ 路径 ① 路径 ② 路径 ③ 首次配置 正常使用(绝大多数情况) 兼容性检查 │ │ │ │ │ ├─ 检查通过 → 保留不动,转到路径 ② │ │ └─ 检查不通过 → 告知用户具体问题, │ │ 询问是否用本 skill 重新配置 │ │ 问用户选择 自动确保服务就绪 A/B 菜单 然后执行: codex exec "用户的需求" │ ▼ 完成配置后 转到路径 ② ``` ### 三条路径说明 | 路径 | 什么时候走 | 用户需要做什么 | |---|---|---| | ① 首次配置 | 环境里从未配过 codex,或用户主动要求重新配置 | **默认直接走 AgentPlan**,优先尝试自动从 ArkClaw 沙箱环境变量(`ARK_AGENT_PLAN` / `ARK_CODING_PLAN` / `ARK_API_KEY`)零感知配好;失败再让用户粘贴一次 Key。**用户主动说"切第三方/换 Kimi/换 DeepSeek/自定义"时才走第三方分支** | | ② 正常使用 | 已经配好了(本 skill 配的,或用户自配且体检通过) | **什么都不用做**,直接说编程需求即可 | | ③ 兼容性检查 | 用户手动写过 config.toml,本 skill 首次遇到 | 如果检查通过:无需操作;如果不通过:选择重配或自己修 | --- ## 重要:正常使用时不要打扰用户 当配置已经就绪(路径 ②)时,**不允许**: - 复述配置流程 - 解释 skill 在做什么 - 询问"是否需要切换 / 是否需要重新配置" - 把 skill 的存在感暴露给用户 **只做**: 1. 运行 `bash scripts/ensure-relay.sh`(自动按当前 profile 启对应端口的 relay,不需要时立即退出) 2. 执行 `cd ~ && codex exec --skip-git-repo-check "用户的完整需求原文"` 3. 把 codex 的输出原样返回给用户 **用户体验**应该等同于"直接跟 codex 对话",完全感觉不到 skill 的存在。 只有这三种情况允许打扰用户: 1. 首次配置(环境全新) 2. 兼容性检查不通过,且无法自动修复 3. 用户主动说"切换 / 换一家 / 切到 X" --- ## 触发方式 - **自然语言**:"用 codex 帮我重构 utils.py" / "codex 跑一下这个测试" - **显式命令**:`/codex-allinone <你的编程需求>`(强制走本 skill) - **报错触发**:用户贴出 `command not found: codex` / `connection refused 444X` 等 --- ## 详细流程 ### Step 0 — 运行 doctor.sh 检测环境 每次触发都先运行 `bash scripts/doctor.sh`,它会输出一个 JSON,包含: ```json { "codex_installed": true, "codex_version": "0.130.x", "codex_outdated": false, "config_state": "clean | managed-by-skill | user-managed", "active_profile": "agentplan | kimi | deepseek | arkv3 | custom | null", "user_config_summary": {"model_provider": "...", "model": "..."} | null, "user_health": {"is_healthy": true/false, "checks": [...], "issues": [...]} | null, "expected_relay_port": 4446 | 4447 | 4448 | 4449 | null, "relay_running": true, "ark_api_key_set": true, "moonshot_api_key_set": false, "deepseek_api_key_set": false, "ark_v3_api_key_set": false, "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 — 安装 / 升级 Codex CLI(仅首次需要) `scripts/install.sh` 的行为: | 当前状态 | 行为 | |---|---| | 未安装 codex | 通过 npm 安装最新版 | | 版本低于 0.130 | 升级到最新 | | 已是最新版 | 跳过,不做任何事 | ### Step 2 — 配置模型服务(仅路径 ① 触发) **默认行为:不问选项,直接走 AgentPlan** 减少用户感知是核心原则。**不要主动给用户列 A/B 菜单**。除非用户主动说"我要用 Kimi / 切第三方 / 用自定义网关 / 用我自购的方舟 v3 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 也不一样:AgentPlan → `/api/plan/v3` (relay 4446);Coding Plan → `/api/coding/v3` (relay 4450)。**绝不能套用 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 时再切第三方分支) | | 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。 #### 传统首次配置(仅在零感知不可用时触发) 向用户输出: ``` 准备给你配置 Codex,使用火山方舟 AgentPlan(套餐内额度,ark-code-latest 智能路由)。 请粘贴 AgentPlan 控制台分配的专属 Key: ``` 拿到 Key 后立即跑: ```bash bash scripts/setup-agentplan.sh "$KEY" ``` #### 何时走第三方分支? 只有以下情况才询问/触发第三方流程,**不要主动给用户列菜单**: - 用户**主动**说:"我要用 Kimi / Moonshot" / "切到 DeepSeek" / "用自定义网关" / "用我的方舟 v3 Key" - 用户**主动**给出 base_url + Key + model 三件套 - 用户**主动**说"我没有 AgentPlan Key,但我有 Kimi / DeepSeek / 自建网关 Key" 触发后引导用户给出对应 Key,然后跑: ```bash bash scripts/setup-thirdparty.sh <provider> "$KEY" # 或 custom: bash scripts/setup-thirdparty.sh custom "$KEY" "<model>" "<base_url>" ``` > ⛔ **不允许**在用户没主动提"切/换/第三方/Kimi/DeepSeek/自定义"等关键词时,主动展示 A/B 菜单或让用户做选择。这会增加感知,违背"默认 AgentPlan + 零感知优先"原则。 配置脚本会自动完成:写 Key → 写 profile → 按需启动 codex-relay → 写自动恢复命令到 .bashrc。如果用户已有 config.toml,会**先备份**再覆盖。 ### Step 3 — 兼容性检查(仅路径 ③ 触发) 当 doctor.sh 返回 `config_state == "user-managed"` 时,说明用户之前手动写过 config.toml。此时读取 `user_health` 字段: **检查通过**(`is_healthy == true`): - 不打扰用户,直接当作"已配好",进入正常使用 **检查不通过**(`is_healthy == false`): 向用户展示具体问题,并询问: ``` 检测到你已有的 codex 配置存在以下问题: 1. <具体问题描述> 2. <具体问题描述> 你希望: A. 让本 skill 帮你重新配置(推荐,会先备份你现有的文件) B. 我自己去修改 请回复 A 或 B。 ``` ### Step 4 — 正常使用 ```bash bash scripts/ensure-relay.sh cd ~ && codex exec --skip-git-repo-check "用户的完整需求原文" ``` > `ensure-relay.sh` 做什么? > - 读取 `~/.codex/active-profile`,根据当前 profile 自动启对应端口的 codex-relay > - 端口分配:agentplan=4446 / kimi=4447 / deepseek=4448 / custom=4449 > - arkv3(直连)和 custom 直连模式立即退出,不耗时 ### Step 5 — 切换模型服务(用户主动要求时) ```bash bash scripts/switch-profile.sh # 列出已配置的服务 bash scripts/switch-profile.sh kimi # 切换到 Kimi ``` --- ## codex-relay 是什么? Codex CLI 0.130+ 默认发 OpenAI **Responses 协议**,而多数模型服务只支持 **Chat 协议**。codex-relay 是一个轻量的本地服务,负责做协议翻译: ``` Codex CLI ──(Responses)──→ codex-relay (本机端口) ──(Chat)──→ 上游模型服务 ``` 每个 profile 占一个独立端口: | Profile | 端口 | 是否需要 relay | |---------|------|---------------| | agentplan | 4446 | ✅ | | codingplan | 4450 | ✅ | | kimi | 4447 | ✅ | | deepseek | 4448 | ✅ | | arkv3 | — | ❌ 直连(原生支持 Responses) | | custom | 4449 / — | 🔍 自动探测(setup 时 curl `/responses` 决定) | custom 路径的探测:`POST {base_url}/responses` 返回 404 → 不支持 → 走 relay;返回 200/400/401/403/422 → 支持 → 直连。 安装方式: ```bash pip install --user --break-system-packages codex-relay ``` 安装后可通过 `codex-relay --help` 验证。 --- ## 健康检查都检查什么? | 检查项 | 不通过时的具体表现 | |---|---| | model_provider 字段是否设置 | Codex 不知道连接哪个服务 | | base_url 地址是否填写 | Codex 不知道请求发往哪里 | | wire_api 协议是否有效 | 老版本的 `chat_completions` 在 0.130 以后已废弃;`chat` 也已 deprecated,推荐 `responses` | | API Key 环境变量是否存在 | 连接时会被拒绝(401 错误) | | 当前 profile 对应端口的 relay 是否在运行 | 会报 connection refused | | 是否硬编码了 temperature/top_p | 部分模型不支持,会返回 400 错误 | --- ## Profile 设计 每个模型服务在 `~/.codex/profiles/` 下有一个 .toml 配置文件: ``` ~/.codex/profiles/ ├── agentplan.toml — 火山方舟 AgentPlan(经 relay 端口 4446 → /api/plan/v3) ├── codingplan.toml — 火山方舟 Coding Plan(经 relay 端口 4450 → /api/coding/v3) ├── kimi.toml — Kimi (月之暗面 Moonshot,经 relay 端口 4447) ├── deepseek.toml — DeepSeek(经 relay 端口 4448) ├── arkv3.toml — 火山方舟普通 v3 Key(直连) └── custom.toml — 自定义 OpenAI 兼容端点(自动探测,直连或经 relay 端口 4449) ``` `~/.codex/config.toml` 是一个快捷方式(软链接),指向当前使用的配置文件。切换模型服务 = 把快捷方式指向另一个文件。 --- ## 必读参考文档 | 场景 | 需要读的文件 | |---|---| | 引导用户选择第三方 Key | `references/thirdparty-providers.md` | | 用户报错 / 检查不通过 | `references/troubleshooting.md` | | 不确定整体流程 | `references/routing-flow.md` | --- ## 安全规则 - 用户的 Key 只保存在 `~/.bashrc` 和 `~/.codex/profiles/*.toml` 中(用户家目录内) - 覆盖配置前**自动备份**到 `~/.codex/config.toml.bak.<时间戳>` - 不上传任何外部服务,不在日志中记录 Key 明文 ## 不做什么 - ❌ **不**尝试连接 OpenAI 官方服务(沙箱网络在境内 IDC,连不上 openai.com) - ❌ **不**自动猜测 Key,所有 Key 必须由用户主动粘贴 - ❌ **不**做 Claude Code 的配置(那是另一个 skill) - ❌ **不**在用户没确认前覆盖已有的 config.toml ## 常用对话示例 | 用户说 | Skill 做什么 | |---|---| | "帮我装 codex" / "在 ArkClaw 里用 codex" | 安装 + 引导配置 | | "/codex-allinone 帮我写个 Python 脚本" | 直接执行(如果已配好) | | "切换到 Kimi" / "换成 DeepSeek" | 运行 switch-profile.sh | | "切换模型服务" / "换一家" | 列出已配置的选项 | | "codex 报 connection refused 444X" | 用人话告知:"本地协议中转服务没起来,我重启一下",然后跑 ensure-relay.sh | | "codex 报 401" | 用人话告知:Key 失效或贴错套餐,引导去控制台刷新 / 切套餐 / 换 Kimi 等 | | "我现在用的哪家?" | 读取 ~/.codex/active-profile | --- ## 给用户的"人话提示"模板(必须遵守) skill 与用户沟通时,**禁止出现** profile / config.toml / relay / Responses API / wire_api 等内部术语。下表是对应的人话翻译,凡需告知用户错误时一律按此输出: | 内部状态 | 给用户的人话提示 | |---|---| | 401 / 403 鉴权失败 | "连接火山方舟时被拒绝(可能是 Key 过期或贴错套餐)。可以这样修:\n 1. 去火山方舟控制台 → AgentPlan 页面,复制最新的专属 Key 发给我,我帮你重新写入(原配置会自动备份)\n 2. 如果你有别的可用 Key(Coding Plan 的、Kimi 的、DeepSeek 的、自建网关的),告诉我用哪个,我切过去\n请直接回复 1 或 2,或者直接把新 Key / "用 Kimi" 这类话发给我。" | | connection refused 444X | "本地的协议中转服务没起来,我重启一下(2 秒)" | | model_provider / base_url 缺失 | "配置文件里少了一项必要信息,我帮你重新配一下" | | ARK_API_KEY 环境变量缺失 | "Key 还没注入到当前 shell,我重新加载一下" | | Key 格式自检失败 | "你给的这串看起来不像一把有效的 Key — 应该是一串 20 位以上的字母+数字。请去控制台完整复制后再发我" | | 网络抖动跳过预校验 | "网络有点抖,在线校验跳过,先把配置写下来。如果运行时报错我再告诉你怎么修" | | codex-relay 安装失败 | "本地中转服务装不上,可能是 pip 网络问题。我重试一次,如果还不行你可以手动执行: `pip install --user --break-system-packages codex-relay`" | | relay 端口活着但调用 401 | "本地中转服务在运行,但没拿到 Key(可能是早期版本残留进程),我重启一下让它重新读取(3 秒)" |

path: profiles/arkv3.toml
content: # profile: arkv3
# 火山方舟普通 v3 端点(自购 Key,非 AgentPlan 套餐)
#
# 为什么不需要 relay?
#   方舟普通 v3 端点本身就原生支持 OpenAI Responses API(/api/v3/responses),
#   Codex CLI 可以直连,无需任何本地中转服务。
#
# 注意:这是 /api/v3,跟 AgentPlan 的 /api/plan/v3 是不同路径,Key 也不同。

model = "doubao-seed-1-6-250615"
model_provider = "arkv3"

[model_providers.arkv3]
name = "Volcengine Ark v3"
base_url = "https://ark.cn-beijing.volces.com/api/v3"
wire_api = "responses"
env_key = "ARK_V3_API_KEY"

request_max_retries = 4
stream_max_retries = 5
responses_websockets_v2 = true


path: profiles/deepseek.toml
content: # profile: deepseek
# DeepSeek + codex-relay 协议中转
#
# 为什么需要 relay?
#   DeepSeek 服务端只支持 OpenAI Chat Completions API(没有 Responses API)。
#   而 Codex CLI 0.130+ 已废弃 chat wire_api,默认发 Responses 协议。
#   codex-relay 在本机 4448 端口接 Responses 请求,翻译成 Chat 后转发给 DeepSeek。

model = "deepseek-chat"
model_provider = "deepseek"

[model_providers.deepseek]
name = "DeepSeek via relay"
base_url = "http://127.0.0.1:4448/v1"
wire_api = "responses"
env_key = "DEEPSEEK_API_KEY"

request_max_retries = 4
stream_max_retries = 5
responses_websockets_v2 = true


path: profiles/custom.toml
content: # profile: custom
# 用户自填:任何 OpenAI 兼容端点(Chat Completions 或 Responses API)
# 占位符由 setup-thirdparty.sh 在写入时替换:
#   __MODEL__     - 用户提供的模型名
#   __BASE_URL__  - 实际指向的端点(经过 relay 自动探测后,可能是上游直连或本地 relay 端口)
#   __WIRE_API__  - 永远是 "responses"(因为 Codex 0.130+ 已弃用 chat;
#                   若上游不支持 Responses,会通过 relay 翻译)

model = "__MODEL__"
model_provider = "custom"

[model_providers.custom]
name = "Custom Provider"
base_url = "__BASE_URL__"
wire_api = "responses"
env_key = "CUSTOM_API_KEY"

request_max_retries = 4
stream_max_retries = 5
responses_websockets_v2 = true


path: profiles/kimi.toml
content: # profile: kimi
# Kimi (月之暗面 Moonshot) + codex-relay 协议中转
#
# 为什么需要 relay?
#   Kimi 服务端只支持 OpenAI Chat Completions API(没有 Responses API)。
#   而 Codex CLI 0.130+ 已废弃 chat wire_api,默认发 Responses 协议。
#   codex-relay 在本机 4447 端口接 Responses 请求,翻译成 Chat 后转发给 Kimi。

model = "kimi-k2-turbo-preview"
model_provider = "kimi"

[model_providers.kimi]
name = "Kimi via relay"
base_url = "http://127.0.0.1:4447/v1"
wire_api = "responses"
env_key = "MOONSHOT_API_KEY"

request_max_retries = 4
stream_max_retries = 5
responses_websockets_v2 = true


path: profiles/agentplan.toml
content: # profile: agentplan
# 火山方舟 AgentPlan + codex-relay 协议中转
# 上游模型走 ark-code-latest 智能路由(Kimi K2 / Doubao / DeepSeek / GLM 一键切换)

model = "ark-code-latest"
model_provider = "agentplan"
model_reasoning_effort = "medium"

# Codex 0.130 已废弃 chat,统一用 responses;由 codex-relay 翻译成 chat 打到上游
[model_providers.agentplan]
name = "AgentPlan via relay"
base_url = "http://127.0.0.1:4446/v1"
wire_api = "responses"
env_key = "ARK_API_KEY"

# --- 重试与稳定性开关(必须三个都设,否则模型会重复回复 N 次)---
request_max_retries = 4
stream_max_retries = 5
responses_websockets_v2 = true

# 不要写 temperature / top_p,后端 ark-code-latest 写死了采样参数,加了会 400


path: profiles/codingplan.toml
content: # profile: codingplan
# 火山方舟 Coding Plan + codex-relay 协议中转
# 上游 base_url: https://ark.cn-beijing.volces.com/api/coding/v3 (OpenAI 协议)
# 模型 ark-code-latest 支持控制台切换 (Doubao-Seed-Code / GLM-4.7 / Kimi-K2.5 等)

model = "ark-code-latest"
model_provider = "codingplan"
model_reasoning_effort = "medium"

# Coding Plan 上游目前只支持 Chat,不支持 Responses;由 codex-relay 翻译
[model_providers.codingplan]
name = "Coding Plan via relay"
base_url = "http://127.0.0.1:4450/v1"
wire_api = "responses"
env_key = "ARK_API_KEY"

# --- 重试与稳定性开关 ---
request_max_retries = 4
stream_max_retries = 5
responses_websockets_v2 = true

# 不要写 temperature / top_p,后端 ark-code-latest 写死了采样参数,加了会 400


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. 写入 agentplan.toml 配置文件
#   3. 将 codex 的配置指向该文件(如果已有配置会先备份)
#   4. 安装并启动 codex-relay 协议转换服务(端口 4446)
#   5. 写入开机自动恢复命令
#
# --auto 模式(零感知配置):
#   ArkClaw / OpenClaw 沙箱会通过 OPENCLAW_SERVICE_MANAGED_ENV_KEYS 托管 ARK_API_KEY,
#   套餐归属由 ARK_AGENT_PLAN / ARK_CODING_PLAN 标识。当这些变量都已注入时,本脚本会
#   直接复用 $ARK_API_KEY,无需用户再粘贴。
#
# 为什么需要 codex-relay?
#   AgentPlan 服务端只支持 OpenAI Chat Completions API,而 Codex CLI 0.130+ 默认发
#   Responses 协议。codex-relay 在本机 4446 端口接 Responses 请求,翻译成 Chat
#   后转发给火山方舟。

set -e

ARG1="${1:-}"
AUTO_MODE="no"
KEY=""

if [ "$ARG1" = "--auto" ]; then
  AUTO_MODE="yes"
  # 从环境变量取 Key(沙箱托管的 ARK_API_KEY)
  KEY="${ARK_API_KEY:-}"
  if [ -z "$KEY" ]; then
    echo "❌ --auto 模式需要环境变量 ARK_API_KEY,但未注入。" >&2
    echo "   请改用: bash setup-agentplan.sh \"<你的 AgentPlan 专属 Key>\"" >&2
    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/v3   (relay 4446)
  #   CodingPlan -> https://ark.cn-beijing.volces.com/api/coding/v3 (relay 4450)
  # 所以 --auto 检测到 ARK_CODING_PLAN=true 时要转发到 setup-codingplan.sh,不能套用 AgentPlan profile
  if [ "$is_coding_plan" = "true" ] || [ "$is_coding_plan" = "1" ]; then
    if [ "$is_agent_plan" != "true" ] && [ "$is_agent_plan" != "1" ]; then
      echo "[AgentPlan] 检测到 ARK_CODING_PLAN=true,转发到 setup-codingplan.sh" >&2
      SCRIPT_DIR_FWD="$(cd "$(dirname "$0")" && pwd)"
      exec bash "$SCRIPT_DIR_FWD/setup-codingplan.sh" --auto
    fi
  fi

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

SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT_DIR="$SKILL_DIR/scripts"
CODEX_DIR="$HOME/.codex"
PROFILES_DIR="$CODEX_DIR/profiles"

# 加载共享工具函数
. "$SCRIPT_DIR/relay-helper.sh"

log() { printf '\033[1;36m[AgentPlan]\033[0m %s\n' "$*"; }

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

# 直接 ping 上游 /models endpoint,不消耗推理 token
codex_ping_key "$KEY" "https://ark.cn-beijing.volces.com/api/plan/v3"
ping_ret=$?
case "$ping_ret" in
  0)
    log "步骤 0/5: Key 在线校验通过 ✅"
    ;;
  2)
    log "步骤 0/5: 网络抖动,跳过在线校验,直接写入配置(若运行时报 401 我会再处理)"
    ;;
  1)
    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
        echo "⚠️  AgentPlan 鉴权失败(HTTP 401),但当前账号也开通了 Coding Plan,自动切换中..." >&2
        SCRIPT_DIR_FWD="$(cd "$(dirname "$0")" && pwd)"
        exec bash "$SCRIPT_DIR_FWD/setup-codingplan.sh" --auto --from-fallback
      fi
    fi
    echo "❌ 用这把 Key 连火山方舟 AgentPlan 时被拒绝(HTTP 401/403)。" >&2
    echo "   通常的原因是:" >&2
    echo "   • Key 已过期或被回收 — 请去 ArkClaw 控制台刷新一下" >&2
    echo "   • 你贴的是 Coding Plan 的 Key,不是 AgentPlan 的" >&2
    echo "   • 你贴的是火山方舟普通 v3 Key,不是套餐专属 Key" >&2
    echo "   请确认后重新提供 Key,或告诉我用别的方式接入(Kimi/DeepSeek/自定义网关)" >&2
    exit 1
    ;;
esac

mkdir -p "$PROFILES_DIR"

# ============================================================
# 步骤 1: 保存 ARK_API_KEY 到 ~/.bashrc
# ============================================================
sed -i '/^export ARK_API_KEY=/d' "$HOME/.bashrc" 2>/dev/null || true
echo "export ARK_API_KEY=\"$KEY\"" >> "$HOME/.bashrc"
export ARK_API_KEY="$KEY"
log "步骤 1/5: ARK_API_KEY 已保存到 ~/.bashrc"

# ============================================================
# 步骤 2: 写入 profile 配置文件
# ============================================================
cp "$SKILL_DIR/profiles/agentplan.toml" "$PROFILES_DIR/agentplan.toml"
log "步骤 2/5: 配置文件已写入 $PROFILES_DIR/agentplan.toml"

# ============================================================
# 步骤 3: 激活该 profile(如果有旧的自定义配置会先备份)
# ============================================================
if [ -f "$CODEX_DIR/config.toml" ] && [ ! -L "$CODEX_DIR/config.toml" ]; then
  BACKUP="$CODEX_DIR/config.toml.bak.$(date +%Y%m%d_%H%M%S)"
  cp "$CODEX_DIR/config.toml" "$BACKUP"
  log "  → 已备份你原有的 config.toml 到 $BACKUP"
fi
ln -sf "$PROFILES_DIR/agentplan.toml" "$CODEX_DIR/config.toml"
echo "agentplan" > "$CODEX_DIR/active-profile"
log "步骤 3/5: 已激活 AgentPlan 配置"

# ============================================================
# 步骤 4: 安装并启动 codex-relay(端口 4446)
# ============================================================
log "步骤 4/5: 准备 codex-relay 协议转换服务..."
ensure_codex_relay_installed || exit 3

# 端口已被占用就先停掉旧进程
if relay_running 4446; then
  stop_relay_on_port 4446
fi

start_relay "agentplan" 4446 "https://ark.cn-beijing.volces.com/api/plan/v3" || exit 3
log "  → codex-relay 启动成功 (端口 4446)"

# ============================================================
# 步骤 5: 写入自动恢复命令(沙箱重启后自动生效)
# ============================================================
# 通用恢复脚本:根据 active-profile 启动对应端口的 relay,适用于所有需要 relay 的 profile
RELAY_AUTOSTART_MARKER='# [codex-allinone] auto-recover relay'
if ! grep -qF "$RELAY_AUTOSTART_MARKER" "$HOME/.bashrc" 2>/dev/null; then
  cat >> "$HOME/.bashrc" <<'EOF'

# [codex-allinone] auto-recover relay
if [ -f "$HOME/.codex/active-profile" ] && command -v codex-relay >/dev/null 2>&1; then
  __codex_profile="$(cat "$HOME/.codex/active-profile" 2>/dev/null | tr -d '[:space:]')"
  case "$__codex_profile" in
    agentplan) __codex_port=4446; __codex_upstream="https://ark.cn-beijing.volces.com/api/plan/v3"; __codex_env_key="ARK_API_KEY" ;;
    codingplan) __codex_port=4450; __codex_upstream="https://ark.cn-beijing.volces.com/api/coding/v3"; __codex_env_key="ARK_API_KEY" ;;
    kimi)      __codex_port=4447; __codex_upstream="https://api.moonshot.cn/v1"; __codex_env_key="MOONSHOT_API_KEY" ;;
    deepseek)  __codex_port=4448; __codex_upstream="https://api.deepseek.com/v1"; __codex_env_key="DEEPSEEK_API_KEY" ;;
    custom)    __codex_port=4449; __codex_upstream="$(cat "$HOME/.codex/custom-upstream" 2>/dev/null || echo)"; __codex_env_key="CUSTOM_API_KEY" ;;
    *)         __codex_port="" ;;
  esac
  if [ -n "$__codex_port" ] && [ -n "$__codex_upstream" ] && [ -n "$__codex_env_key" ]; then
    if ! (echo > /dev/tcp/127.0.0.1/$__codex_port) >/dev/null 2>&1; then
      __codex_key_value="$(eval echo \"\$$__codex_env_key\")"
      if [ -n "$__codex_key_value" ]; then
        CODEX_RELAY_API_KEY="$__codex_key_value" nohup codex-relay --port $__codex_port --upstream "$__codex_upstream" \
          > "$HOME/.codex-relay-${__codex_profile}.log" 2>&1 &
      fi
      unset __codex_key_value
    fi
  fi
  unset __codex_profile __codex_port __codex_upstream __codex_env_key
fi
EOF
  log "步骤 5/5: 已写入自动恢复命令到 ~/.bashrc"
fi

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


path: scripts/relay-helper.sh
content: #!/usr/bin/env bash
# relay-helper.sh — codex-relay 共享工具函数
#
# 使用方式: source 这个文件,然后调用其中的函数。
#
# 提供的函数:
#   profile_relay_port <profile>     — 返回该 profile 用的 relay 端口(arkv3 返回空)
#   profile_relay_upstream <profile> — 返回该 profile 的上游 URL
#   profile_env_key_name <profile>   — 返回该 profile 用的环境变量名
#   probe_responses_support <url> <key> — 探测上游是否原生支持 Responses API(0=支持, 1=不支持)
#   ensure_codex_relay_installed     — 确保 codex-relay 已安装(自动 pip install)
#   start_relay <profile> <port> <upstream> — 后台启动 relay,等待端口就绪
#   stop_relay_on_port <port>        — 停止指定端口的 relay
#   relay_running <port>             — 端口上是否有服务在跑

# ============================================================
# Profile → 端口/上游 映射
# ============================================================
profile_relay_port() {
  case "$1" in
    agentplan)  echo "4446" ;;
    codingplan) echo "4450" ;;
    kimi)       echo "4447" ;;
    deepseek)   echo "4448" ;;
    custom)     echo "4449" ;;
    arkv3)      echo "" ;;       # arkv3 直连,不用 relay
    *)          echo "" ;;
  esac
}

profile_relay_upstream() {
  case "$1" in
    agentplan)  echo "https://ark.cn-beijing.volces.com/api/plan/v3" ;;
    codingplan) echo "https://ark.cn-beijing.volces.com/api/coding/v3" ;;
    kimi)       echo "https://api.moonshot.cn/v1" ;;
    deepseek)   echo "https://api.deepseek.com/v1" ;;
    custom)     echo "${CUSTOM_UPSTREAM:-}" ;;   # custom 由调用方通过环境变量传入
    *)          echo "" ;;
  esac
}

profile_env_key_name() {
  case "$1" in
    agentplan)  echo "ARK_API_KEY" ;;
    codingplan) echo "ARK_API_KEY" ;;
    kimi)       echo "MOONSHOT_API_KEY" ;;
    deepseek)   echo "DEEPSEEK_API_KEY" ;;
    arkv3)      echo "ARK_V3_API_KEY" ;;
    custom)     echo "CUSTOM_API_KEY" ;;
    *)          echo "" ;;
  esac
}

# ============================================================
# Key 格式自检 — 不联网,本地正则
# 仅用于挡掉"明显贴错"的输入(空格、换行、含中文等)
# 返回 0 = 看起来像个 Key;1 = 明显不像
# ============================================================
codex_format_check_key() {
  local key="$1"
  [ -n "$key" ] || return 1
  [ "${#key}" -ge 20 ] || return 1
  echo "$key" | grep -qE '^[A-Za-z0-9._-]+$' || return 1
  return 0
}

# ============================================================
# Key 活性预校验 — 抗网络抖动
# 用法: codex_ping_key <key> <base_url>
# 返回:
#   0 = 联通且鉴权 OK (拿到 2xx/4xx 中的非鉴权码)
#   1 = 明确鉴权失败 (HTTP 401 / 403)
#   2 = 网络抖动,无法判断 (3 次重试都没拿到明确答案,放行)
# ============================================================
codex_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
}

# ============================================================
# 探测上游是否支持 OpenAI Responses API
# ============================================================
# 用法: probe_responses_support <base_url> <api_key>
# 返回: 0 = 支持 Responses(可直连)
#       1 = 不支持(应过 relay)
probe_responses_support() {
  local base_url="$1"
  local api_key="$2"
  local code

  # 去掉末尾斜杠
  base_url="${base_url%/}"

  code="$(curl -s -o /dev/null -w '%{http_code}' \
    --max-time 6 \
    -X POST "${base_url}/responses" \
    -H "Authorization: Bearer ${api_key}" \
    -H "Content-Type: application/json" \
    -d '{"model":"probe","input":"ping"}' 2>/dev/null || echo "000")"

  # 404 = 该端点不存在 → 不支持 Responses
  # 401/403 = 鉴权问题但端点存在 → 支持(只是 Key 不一定对)
  # 400/422 = 参数问题但端点存在 → 支持
  # 200 = 正常返回(model 名不对也能通过 schema 校验) → 支持
  # 000/其它 = 网络不通,无法判断,保守按"不支持"处理(走 relay 更安全)
  case "$code" in
    404)             return 1 ;;
    200|400|401|403|422) return 0 ;;
    *)               return 1 ;;
  esac
}

# ============================================================
# 确保 codex-relay 已安装
# ============================================================
ensure_codex_relay_installed() {
  if command -v codex-relay >/dev/null 2>&1; then
    return 0
  fi

  # PATH 可能没加载,先手动加
  export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
  if command -v codex-relay >/dev/null 2>&1; then
    return 0
  fi

  echo "正在安装 codex-relay..."
  pip install --user --break-system-packages codex-relay >/dev/null 2>&1 || {
    echo "❌ codex-relay 安装失败。请手动执行: pip install --user --break-system-packages codex-relay" >&2
    return 1
  }

  # 确保 ~/.local/bin 在 PATH 中
  if ! grep -q '.local/bin' "$HOME/.bashrc" 2>/dev/null; then
    echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc"
  fi

  command -v codex-relay >/dev/null 2>&1 || {
    echo "❌ codex-relay 安装完但找不到命令。请检查 PATH。" >&2
    return 1
  }
}

# ============================================================
# 端口检查
# ============================================================
relay_running() {
  local port="$1"
  (echo > /dev/tcp/127.0.0.1/"$port") >/dev/null 2>&1
}

# ============================================================
# 启动 relay
# ============================================================
# 用法: start_relay <profile> <port> <upstream_url>
start_relay() {
  local profile="$1"
  local port="$2"
  local upstream="$3"
  local key_name
  key_name="$(profile_env_key_name "$profile")"

  if [ -z "$port" ] || [ -z "$upstream" ]; then
    echo "❌ start_relay: 缺少 port 或 upstream 参数" >&2
    return 1
  fi

  # 端口已经有服务,直接复用
  if relay_running "$port"; then
    return 0
  fi

  ensure_codex_relay_installed || return 1

  # 加载 Key 到环境(当前 shell 可能还没 source .bashrc)
  if [ -n "$key_name" ] && [ -z "$(eval echo \$$key_name)" ]; then
    local key_line
    key_line="$(grep "^export ${key_name}=" "$HOME/.bashrc" 2>/dev/null | tail -1 || true)"
    [ -n "$key_line" ] && eval "$key_line"
  fi

  # ⚠️ 关键修复(v3.2):codex-relay 不会从客户端请求里提取 Authorization,
  # 必须通过 CODEX_RELAY_API_KEY 环境变量把 Key 注入给 relay 进程,否则上游会 401。
  local key_value=""
  if [ -n "$key_name" ]; then
    key_value="$(eval echo \"\$$key_name\")"
  fi
  if [ -z "$key_value" ]; then
    echo "❌ start_relay: 找不到环境变量 $key_name 的值。请先确保 Key 已写入 ~/.bashrc 并 export 到当前 shell。" >&2
    return 1
  fi

  # 后台启动
  local log_file="$HOME/.codex-relay-${profile}.log"
  CODEX_RELAY_API_KEY="$key_value" nohup codex-relay \
    --port "$port" \
    --upstream "$upstream" \
    > "$log_file" 2>&1 &

  # 最多等 5 秒
  for i in 1 2 3 4 5; do
    sleep 1
    if relay_running "$port"; then
      return 0
    fi
  done

  echo "❌ codex-relay (profile=$profile, port=$port) 启动失败。日志: $log_file" >&2
  return 1
}

# ============================================================
# 停止 relay
# ============================================================
stop_relay_on_port() {
  local port="$1"
  pkill -f "codex-relay --port $port" 2>/dev/null || true
  sleep 1
}


path: scripts/switch-profile.sh
content: #!/usr/bin/env bash
# switch-profile.sh — 在不同模型服务之间快速切换 (v7)
#
# 用法:
#   bash switch-profile.sh           # 列出已配置的模型,供用户选择
#   bash switch-profile.sh <名称>    # 直接切换到指定模型配置
#
# 原理:
#   ~/.codex/config.toml 是一个快捷方式(软链接),指向 ~/.codex/profiles/ 中的某个配置文件。
#   "切换"就是把这个快捷方式重新指向另一个配置文件,并按需启动对应端口的 relay。

set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/relay-helper.sh"

CODEX_DIR="$HOME/.codex"
PROFILES_DIR="$CODEX_DIR/profiles"
ACTIVE_FILE="$CODEX_DIR/active-profile"

TARGET="${1:-}"

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

  echo "已配置的模型服务:"
  for f in "$PROFILES_DIR"/*.toml; do
    [ -f "$f" ] || continue
    name="$(basename "$f" .toml)"
    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 kimi"
  exit 0
fi

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

ln -sf "$PROFILE_FILE" "$CODEX_DIR/config.toml"
echo "$TARGET" > "$ACTIVE_FILE"
echo "✅ 已切换到: $TARGET"

# 按需启动对应端口的 relay (arkv3 直连不需要;custom 直连模式也不需要)
RELAY_PORT=""
RELAY_UPSTREAM=""
case "$TARGET" in
  agentplan)
    RELAY_PORT=4446
    RELAY_UPSTREAM="https://ark.cn-beijing.volces.com/api/plan/v3"
    ;;
  codingplan)
    RELAY_PORT=4450
    RELAY_UPSTREAM="https://ark.cn-beijing.volces.com/api/coding/v3"
    ;;
  kimi)
    RELAY_PORT=4447
    RELAY_UPSTREAM="https://api.moonshot.cn/v1"
    ;;
  deepseek)
    RELAY_PORT=4448
    RELAY_UPSTREAM="https://api.deepseek.com/v1"
    ;;
  custom)
    if [ -f "$HOME/.codex/custom-upstream" ]; then
      RELAY_PORT=4449
      RELAY_UPSTREAM="$(cat "$HOME/.codex/custom-upstream" | tr -d '[:space:]')"
    fi
    ;;
  arkv3)
    : # 直连
    ;;
esac

if [ -n "$RELAY_PORT" ] && [ -n "$RELAY_UPSTREAM" ]; then
  if ! relay_running "$RELAY_PORT"; then
    echo ""
    echo "⚠️  $TARGET 需要 codex-relay 服务(端口 $RELAY_PORT),正在启动..."
    if ensure_codex_relay_installed && start_relay "$TARGET" "$RELAY_PORT" "$RELAY_UPSTREAM"; then
      echo "✅ codex-relay 已就绪"
    else
      echo "❌ codex-relay 启动失败,请查看 ~/.codex-relay-${TARGET}.log"
      exit 2
    fi
  fi
fi


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

set -e

ARG1="${1:-}"
ARG2="${2:-}"
AUTO_MODE="no"
FROM_FALLBACK="no"
KEY=""

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

SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT_DIR="$SKILL_DIR/scripts"
CODEX_DIR="$HOME/.codex"
PROFILES_DIR="$CODEX_DIR/profiles"

# 加载共享工具函数
. "$SCRIPT_DIR/relay-helper.sh"

log() { printf '\033[1;36m[CodingPlan]\033[0m %s\n' "$*"; }

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

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

mkdir -p "$PROFILES_DIR"

# ============================================================
# 步骤 1: 保存 ARK_API_KEY 到 ~/.bashrc
# ============================================================
sed -i '/^export ARK_API_KEY=/d' "$HOME/.bashrc" 2>/dev/null || true
echo "export ARK_API_KEY=\"$KEY\"" >> "$HOME/.bashrc"
export ARK_API_KEY="$KEY"
log "步骤 1/5: ARK_API_KEY 已保存到 ~/.bashrc"

# ============================================================
# 步骤 2: 写入 profile 配置文件
# ============================================================
cp "$SKILL_DIR/profiles/codingplan.toml" "$PROFILES_DIR/codingplan.toml"
log "步骤 2/5: 配置文件已写入 $PROFILES_DIR/codingplan.toml"

# ============================================================
# 步骤 3: 激活该 profile(如果有旧的自定义配置会先备份)
# ============================================================
if [ -f "$CODEX_DIR/config.toml" ] && [ ! -L "$CODEX_DIR/config.toml" ]; then
  BACKUP="$CODEX_DIR/config.toml.bak.$(date +%Y%m%d_%H%M%S)"
  cp "$CODEX_DIR/config.toml" "$BACKUP"
  log "  → 已备份你原有的 config.toml 到 $BACKUP"
fi
ln -sf "$PROFILES_DIR/codingplan.toml" "$CODEX_DIR/config.toml"
echo "codingplan" > "$CODEX_DIR/active-profile"
log "步骤 3/5: 已激活 Coding Plan 配置"

# ============================================================
# 步骤 4: 安装并启动 codex-relay(端口 4450)
# ============================================================
log "步骤 4/5: 准备 codex-relay 协议转换服务..."
ensure_codex_relay_installed || exit 3

# 端口已被占用就先停掉旧进程
if relay_running 4450; then
  stop_relay_on_port 4450
fi

start_relay "codingplan" 4450 "https://ark.cn-beijing.volces.com/api/coding/v3" || exit 3
log "  → codex-relay 启动成功 (端口 4450)"

# ============================================================
# 步骤 5: 写入自动恢复命令(沙箱重启后自动生效)
# ============================================================
# 通用恢复脚本:根据 active-profile 启动对应端口的 relay,适用于所有需要 relay 的 profile
RELAY_AUTOSTART_MARKER='# [codex-allinone] auto-recover relay'
if ! grep -qF "$RELAY_AUTOSTART_MARKER" "$HOME/.bashrc" 2>/dev/null; then
  cat >> "$HOME/.bashrc" <<'EOF'

# [codex-allinone] auto-recover relay
if [ -f "$HOME/.codex/active-profile" ] && command -v codex-relay >/dev/null 2>&1; then
  __codex_profile="$(cat "$HOME/.codex/active-profile" 2>/dev/null | tr -d '[:space:]')"
  case "$__codex_profile" in
    agentplan) __codex_port=4446; __codex_upstream="https://ark.cn-beijing.volces.com/api/plan/v3"; __codex_env_key="ARK_API_KEY" ;;
    codingplan) __codex_port=4450; __codex_upstream="https://ark.cn-beijing.volces.com/api/coding/v3"; __codex_env_key="ARK_API_KEY" ;;
    kimi)      __codex_port=4447; __codex_upstream="https://api.moonshot.cn/v1"; __codex_env_key="MOONSHOT_API_KEY" ;;
    deepseek)  __codex_port=4448; __codex_upstream="https://api.deepseek.com/v1"; __codex_env_key="DEEPSEEK_API_KEY" ;;
    custom)    __codex_port=4449; __codex_upstream="$(cat "$HOME/.codex/custom-upstream" 2>/dev/null || echo)"; __codex_env_key="CUSTOM_API_KEY" ;;
    *)         __codex_port="" ;;
  esac
  if [ -n "$__codex_port" ] && [ -n "$__codex_upstream" ] && [ -n "$__codex_env_key" ]; then
    if ! (echo > /dev/tcp/127.0.0.1/$__codex_port) >/dev/null 2>&1; then
      __codex_key_value="$(eval echo \"\$$__codex_env_key\")"
      if [ -n "$__codex_key_value" ]; then
        CODEX_RELAY_API_KEY="$__codex_key_value" nohup codex-relay --port $__codex_port --upstream "$__codex_upstream" \
          > "$HOME/.codex-relay-${__codex_profile}.log" 2>&1 &
      fi
      unset __codex_key_value
    fi
  fi
  unset __codex_profile __codex_port __codex_upstream __codex_env_key
fi
EOF
  log "步骤 5/5: 已写入自动恢复命令到 ~/.bashrc"
fi

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


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

set -e

NPM_GLOBAL_BIN="$HOME/.npm-global/bin"
MIN_VERSION="0.130.0"

log()  { printf '\033[1;36m[install]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[install]\033[0m %s\n' "$*"; }
err()  { printf '\033[1;31m[install]\033[0m %s\n' "$*" >&2; }

# --- 1. 检查 npm ---
if ! command -v npm >/dev/null 2>&1; then
  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"
  log "已将 npm 全局 bin 目录添加到 ~/.bashrc 的 PATH 中"
fi
export PATH="$NPM_GLOBAL_BIN:$PATH"

# --- 3. 检查当前版本 ---
version_ge() {
  # 判断 $1 >= $2
  [ "$(printf '%s\n%s\n' "$2" "$1" | sort -V | head -n1)" = "$2" ]
}

CURRENT_VERSION=""
if command -v codex >/dev/null 2>&1; then
  CURRENT_VERSION="$(codex --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")"
fi

if [ -n "$CURRENT_VERSION" ] && version_ge "$CURRENT_VERSION" "$MIN_VERSION"; then
  log "Codex CLI 已安装,版本 $CURRENT_VERSION(满足最低要求 $MIN_VERSION),无需操作。"
  exit 0
fi

if [ -n "$CURRENT_VERSION" ]; then
  warn "当前 Codex CLI 版本 $CURRENT_VERSION 低于最低要求 $MIN_VERSION,准备升级..."
else
  log "未检测到 Codex CLI,准备安装..."
fi

# --- 4. 安装 / 升级 ---
npm install -g @openai/codex@latest

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


path: scripts/setup-thirdparty.sh
content: #!/usr/bin/env bash
# setup-thirdparty.sh — 配置第三方 OpenAI 兼容模型服务（v7: all-via-relay 架构）
#
# 用法:
#   bash setup-thirdparty.sh kimi     "<Key>" [model]
#   bash setup-thirdparty.sh deepseek "<Key>" [model]
#   bash setup-thirdparty.sh arkv3    "<Key>" [model]
#   bash setup-thirdparty.sh custom   "<Key>" "<model>" "<base_url>"
#
# v7 架构说明:
#   Codex CLI 0.130+ 默认发 OpenAI Responses 协议,而多数第三方服务只支持 Chat。
#   因此除 arkv3(原生 Responses)外,其他 provider 都通过 codex-relay 协议中转。
#
#   provider  端口   说明
#   --------  -----  ----------------------------------------------------
#   kimi      4447   Moonshot 仅支持 Chat,必须 relay
#   deepseek  4448   DeepSeek 仅支持 Chat,必须 relay
#   arkv3     —      火山方舟 v3 原生支持 Responses,直连
#   custom    4449   自动探测:支持 Responses 直连;否则 relay
#
# 功能:
#   1. 将对应的 API Key 保存到 ~/.bashrc
#   2. 写入对应的 profile 配置文件
#   3. 将 codex 的配置指向该文件(如有旧配置会先备份)
#   4. 按需启动 codex-relay
#   5. 自动恢复命令已由 setup-agentplan / 通用 .bashrc 块负责

set -e

PROVIDER="${1:-}"
KEY="${2:-}"
CUSTOM_MODEL="${3:-}"
CUSTOM_BASE_URL="${4:-}"

SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT_DIR="$SKILL_DIR/scripts"
CODEX_DIR="$HOME/.codex"
PROFILES_DIR="$CODEX_DIR/profiles"

# 加载共享工具函数
. "$SCRIPT_DIR/relay-helper.sh"

log() { printf '\033[1;36m[%s]\033[0m %s\n' "$PROVIDER" "$*"; }
err() { printf '\033[1;31m[%s]\033[0m %s\n' "${PROVIDER:-setup}" "$*" >&2; }

if [ -z "$PROVIDER" ] || [ -z "$KEY" ]; then
  err "用法: bash setup-thirdparty.sh <provider> \"<Key>\" [model] [base_url]"
  err "可选 provider: kimi | deepseek | arkv3 | custom"
  exit 1
fi

case "$PROVIDER" in
  kimi|deepseek|arkv3|custom) ;;
  *)
    err "不支持的 provider: $PROVIDER"
    err "支持的选项: kimi | deepseek | arkv3 | custom"
    exit 1
    ;;
esac

# custom 必须额外提供 model 和 base_url
if [ "$PROVIDER" = "custom" ]; then
  if [ -z "$CUSTOM_MODEL" ] || [ -z "$CUSTOM_BASE_URL" ]; then
    err "自定义模式必须提供 3 个参数: <Key> <model> <base_url>"
    err "用法: bash setup-thirdparty.sh custom \"<Key>\" \"<model>\" \"<base_url>\""
    err "示例: bash setup-thirdparty.sh custom \"sk-xxx\" \"qwen-max\" \"https://dashscope.aliyuncs.com/compatible-mode/v1\""
    exit 1
  fi
fi

mkdir -p "$PROFILES_DIR"

ENV_KEY_NAME="$(profile_env_key_name "$PROVIDER")"
TMPL="$SKILL_DIR/profiles/${PROVIDER}.toml"
PROFILE_FILE="$PROFILES_DIR/${PROVIDER}.toml"

if [ ! -f "$TMPL" ]; then
  err "找不到配置模板: $TMPL"
  exit 1
fi

# ============================================================
# 步骤 1: 保存 API Key 到 ~/.bashrc
# ============================================================
sed -i "/^export $ENV_KEY_NAME=/d" "$HOME/.bashrc" 2>/dev/null || true
echo "export $ENV_KEY_NAME=\"$KEY\"" >> "$HOME/.bashrc"
export "$ENV_KEY_NAME=$KEY"
log "步骤 1/4: $ENV_KEY_NAME 已保存到 ~/.bashrc"

# ============================================================
# 步骤 2: 决定走 relay 还是直连,然后写 profile
# ============================================================
USE_RELAY="no"
RELAY_PORT=""
RELAY_UPSTREAM=""
EFFECTIVE_BASE_URL=""

case "$PROVIDER" in
  kimi)
    USE_RELAY="yes"
    RELAY_PORT="4447"
    RELAY_UPSTREAM="https://api.moonshot.cn/v1"
    EFFECTIVE_BASE_URL="http://127.0.0.1:${RELAY_PORT}/v1"
    ;;
  deepseek)
    USE_RELAY="yes"
    RELAY_PORT="4448"
    RELAY_UPSTREAM="https://api.deepseek.com/v1"
    EFFECTIVE_BASE_URL="http://127.0.0.1:${RELAY_PORT}/v1"
    ;;
  arkv3)
    USE_RELAY="no"
    EFFECTIVE_BASE_URL="https://ark.cn-beijing.volces.com/api/v3"
    ;;
  custom)
    # 自动探测上游是否原生支持 Responses API
    log "正在探测 $CUSTOM_BASE_URL 是否原生支持 Responses API..."
    if probe_responses_support "$CUSTOM_BASE_URL" "$KEY"; then
      log "  → 上游原生支持 Responses,直连无需 relay"
      USE_RELAY="no"
      EFFECTIVE_BASE_URL="$CUSTOM_BASE_URL"
    else
      log "  → 上游仅支持 Chat,将通过本地 relay (端口 4449) 翻译"
      USE_RELAY="yes"
      RELAY_PORT="4449"
      RELAY_UPSTREAM="$CUSTOM_BASE_URL"
      EFFECTIVE_BASE_URL="http://127.0.0.1:${RELAY_PORT}/v1"
      # 持久化上游 URL,供 .bashrc 自动恢复使用
      echo "$CUSTOM_BASE_URL" > "$CODEX_DIR/custom-upstream"
    fi
    ;;
esac

# 渲染 profile
if [ "$PROVIDER" = "custom" ]; then
  sed -e "s|__MODEL__|$CUSTOM_MODEL|g" \
      -e "s|__BASE_URL__|$EFFECTIVE_BASE_URL|g" \
      "$TMPL" > "$PROFILE_FILE"
else
  cp "$TMPL" "$PROFILE_FILE"
  if [ -n "$CUSTOM_MODEL" ]; then
    sed -i "s|^model = \".*\"|model = \"$CUSTOM_MODEL\"|" "$PROFILE_FILE"
  fi
fi
log "步骤 2/4: 配置文件已写入 $PROFILE_FILE"

# ============================================================
# 步骤 3: 激活该 profile (如有旧配置会先备份)
# ============================================================
if [ -f "$CODEX_DIR/config.toml" ] && [ ! -L "$CODEX_DIR/config.toml" ]; then
  BACKUP="$CODEX_DIR/config.toml.bak.$(date +%Y%m%d_%H%M%S)"
  cp "$CODEX_DIR/config.toml" "$BACKUP"
  log "  → 已备份你原有的 config.toml 到 $BACKUP"
fi
ln -sf "$PROFILE_FILE" "$CODEX_DIR/config.toml"
echo "$PROVIDER" > "$CODEX_DIR/active-profile"
log "步骤 3/4: 已激活 $PROVIDER 配置"

# ============================================================
# 步骤 4: 按需启动 codex-relay
# ============================================================
if [ "$USE_RELAY" = "yes" ]; then
  log "步骤 4/4: 启动 codex-relay (端口 $RELAY_PORT → $RELAY_UPSTREAM)..."
  ensure_codex_relay_installed || exit 3

  if relay_running "$RELAY_PORT"; then
    stop_relay_on_port "$RELAY_PORT"
  fi

  start_relay "$PROVIDER" "$RELAY_PORT" "$RELAY_UPSTREAM" || exit 3
  log "  → codex-relay 启动成功 (端口 $RELAY_PORT)"
else
  log "步骤 4/4: $PROVIDER 直连模式,无需 relay"
fi

# ============================================================
# 写入通用自动恢复命令(沙箱重启后自动重启对应端口的 relay)
# ============================================================
RELAY_AUTOSTART_MARKER='# [codex-allinone] auto-recover relay'
if ! grep -qF "$RELAY_AUTOSTART_MARKER" "$HOME/.bashrc" 2>/dev/null; then
  cat >> "$HOME/.bashrc" <<'EOF'

# [codex-allinone] auto-recover relay
if [ -f "$HOME/.codex/active-profile" ] && command -v codex-relay >/dev/null 2>&1; then
  __codex_profile="$(cat "$HOME/.codex/active-profile" 2>/dev/null | tr -d '[:space:]')"
  case "$__codex_profile" in
    agentplan) __codex_port=4446; __codex_upstream="https://ark.cn-beijing.volces.com/api/plan/v3"; __codex_env_key="ARK_API_KEY" ;;
    codingplan) __codex_port=4450; __codex_upstream="https://ark.cn-beijing.volces.com/api/coding/v3"; __codex_env_key="ARK_API_KEY" ;;
    kimi)      __codex_port=4447; __codex_upstream="https://api.moonshot.cn/v1"; __codex_env_key="MOONSHOT_API_KEY" ;;
    deepseek)  __codex_port=4448; __codex_upstream="https://api.deepseek.com/v1"; __codex_env_key="DEEPSEEK_API_KEY" ;;
    custom)    __codex_port=4449; __codex_upstream="$(cat "$HOME/.codex/custom-upstream" 2>/dev/null || echo)"; __codex_env_key="CUSTOM_API_KEY" ;;
    *)         __codex_port="" ;;
  esac
  if [ -n "$__codex_port" ] && [ -n "$__codex_upstream" ] && [ -n "$__codex_env_key" ]; then
    if ! (echo > /dev/tcp/127.0.0.1/$__codex_port) >/dev/null 2>&1; then
      __codex_key_value="$(eval echo \"\$$__codex_env_key\")"
      if [ -n "$__codex_key_value" ]; then
        CODEX_RELAY_API_KEY="$__codex_key_value" nohup codex-relay --port $__codex_port --upstream "$__codex_upstream" \
          > "$HOME/.codex-relay-${__codex_profile}.log" 2>&1 &
      fi
      unset __codex_key_value
    fi
  fi
  unset __codex_profile __codex_port __codex_upstream __codex_env_key
fi
EOF
  log "  → 已写入自动恢复命令到 ~/.bashrc"
fi

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


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

set -e

CODEX_DIR="$HOME/.codex"
PROFILES_DIR="$CODEX_DIR/profiles"
ACTIVE_FILE="$CODEX_DIR/active-profile"
CONFIG_TOML="$CODEX_DIR/config.toml"
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# --- 检测 Codex CLI 是否安装 ---
codex_installed=false
codex_version=""
codex_outdated=false
MIN_VERSION="0.130.0"

if command -v codex >/dev/null 2>&1; then
  codex_installed=true
  codex_version="$(codex --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")"
  if [ -n "$codex_version" ]; then
    if [ "$(printf '%s\n%s\n' "$MIN_VERSION" "$codex_version" | sort -V | head -n1)" != "$MIN_VERSION" ]; then
      codex_outdated=true
    fi
  fi
fi

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

if [ -f "$ACTIVE_FILE" ]; then
  config_state="managed-by-skill"
  active_profile="\"$(cat "$ACTIVE_FILE" | tr -d '[:space:]')\""
elif [ -f "$CONFIG_TOML" ]; then
  USER_PROVIDER="$(grep -E '^model_provider' "$CONFIG_TOML" 2>/dev/null | head -1 | sed -E 's/.*=\s*"([^"]*)".*/\1/')"
  USER_MODEL="$(grep -E '^model\s*=' "$CONFIG_TOML" 2>/dev/null | head -1 | sed -E 's/.*=\s*"([^"]*)".*/\1/')"
  if [ -n "$USER_PROVIDER" ] || [ -n "$USER_MODEL" ]; then
    config_state="user-managed"
    user_config_summary="{\"model_provider\":\"$USER_PROVIDER\",\"model\":\"$USER_MODEL\"}"
  fi
fi

config_toml_exists=false
[ -f "$CONFIG_TOML" ] && config_toml_exists=true

# --- 决定当前 profile 应该用的 relay 端口 ---
# v7: 端口随 profile 变化
ACTIVE="$(echo "$active_profile" | tr -d '"')"
expected_relay_port=""
expected_relay_upstream=""
case "$ACTIVE" in
  agentplan) expected_relay_port=4446 ;;
  codingplan) expected_relay_port=4450 ;;
  kimi)      expected_relay_port=4447 ;;
  deepseek)  expected_relay_port=4448 ;;
  custom)
    # custom 看是否存了上游 URL,存了就要 relay
    if [ -f "$CODEX_DIR/custom-upstream" ]; then
      expected_relay_port=4449
    fi
    ;;
  arkv3|null|"") : ;;   # 直连,不需要 relay
esac

relay_running=false
if [ -n "$expected_relay_port" ]; then
  if (echo > /dev/tcp/127.0.0.1/$expected_relay_port) >/dev/null 2>&1; then
    relay_running=true
  fi
fi

# --- relay 鉴权闭环检查 (v3.2) ---
# 端口在 ≠ 上游真的能通,常见 bug:relay 启动时没拿到 Key,导致所有请求 401。
# 用一个轻量 probe 调用 relay 的 /v1/chat/completions:
#   200/400/422 = 关键字段问题或正常 → relay 自身有 Key,健康
#   401/403     = relay 缺 Key 或 Key 失效 → 不健康
#   其它       = 无法判断,保守视为健康
relay_auth_healthy="null"
if [ "$relay_running" = true ] && [ -n "$expected_relay_port" ]; then
  __probe_code=$(curl -sS -o /dev/null -w "%{http_code}" \
    --max-time 4 \
    -X POST "http://127.0.0.1:${expected_relay_port}/v1/chat/completions" \
    -H "Content-Type: application/json" \
    -d '{"model":"probe","messages":[{"role":"user","content":"ping"}],"max_tokens":1}' 2>/dev/null || echo "000")
  case "$__probe_code" in
    401|403) relay_auth_healthy=false ;;
    2*|400|404|405|422) relay_auth_healthy=true ;;
    *) relay_auth_healthy="null" ;;
  esac
fi

# --- 检测各 provider 的 Key 是否设置 ---
ark_api_key_set=false
moonshot_api_key_set=false
deepseek_api_key_set=false
ark_v3_api_key_set=false
custom_api_key_set=false

[ -n "${ARK_API_KEY:-}" ] && ark_api_key_set=true
[ -n "${MOONSHOT_API_KEY:-}" ] && moonshot_api_key_set=true
[ -n "${DEEPSEEK_API_KEY:-}" ] && deepseek_api_key_set=true
[ -n "${ARK_V3_API_KEY:-}" ] && ark_v3_api_key_set=true
[ -n "${CUSTOM_API_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
  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 ;;
    kimi)      [ "$moonshot_api_key_set" = false ] && grep -q '^export MOONSHOT_API_KEY=' "$HOME/.bashrc" 2>/dev/null && moonshot_api_key_set=true ;;
    deepseek)  [ "$deepseek_api_key_set" = false ] && grep -q '^export DEEPSEEK_API_KEY=' "$HOME/.bashrc" 2>/dev/null && deepseek_api_key_set=true ;;
    arkv3)     [ "$ark_v3_api_key_set" = false ] && grep -q '^export ARK_V3_API_KEY=' "$HOME/.bashrc" 2>/dev/null && ark_v3_api_key_set=true ;;
    custom)    [ "$custom_api_key_set" = false ] && grep -q '^export CUSTOM_API_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 [ "$codex_installed" = false ]; then
  issues+=("\"Codex CLI 未安装\"")
elif [ "$codex_outdated" = true ]; then
  issues+=("\"Codex CLI 版本过旧($codex_version),需要 ≥ $MIN_VERSION\"")
fi

case "$ACTIVE" in
  agentplan)
    [ "$relay_running" = false ] && issues+=("\"AgentPlan 模式已激活,但 codex-relay 未运行(端口 4446)\"")
    [ "$ark_api_key_set" = false ] && issues+=("\"AgentPlan 模式已激活,但 ARK_API_KEY 未设置\"")
    ;;
  codingplan)
    [ "$relay_running" = false ] && issues+=("\"Coding Plan 模式已激活,但 codex-relay 未运行(端口 4450)\"")
    [ "$ark_api_key_set" = false ] && issues+=("\"Coding Plan 模式已激活,但 ARK_API_KEY 未设置\"")
    ;;
  kimi)
    [ "$relay_running" = false ] && issues+=("\"Kimi 模式已激活,但 codex-relay 未运行(端口 4447)\"")
    [ "$moonshot_api_key_set" = false ] && issues+=("\"Kimi 模式已激活,但 MOONSHOT_API_KEY 未设置\"")
    ;;
  deepseek)
    [ "$relay_running" = false ] && issues+=("\"DeepSeek 模式已激活,但 codex-relay 未运行(端口 4448)\"")
    [ "$deepseek_api_key_set" = false ] && issues+=("\"DeepSeek 模式已激活,但 DEEPSEEK_API_KEY 未设置\"")
    ;;
  arkv3)
    [ "$ark_v3_api_key_set" = false ] && issues+=("\"火山方舟 v3 模式已激活,但 ARK_V3_API_KEY 未设置\"")
    ;;
  custom)
    [ "$custom_api_key_set" = false ] && issues+=("\"自定义模式已激活,但 CUSTOM_API_KEY 未设置\"")
    if [ -n "$expected_relay_port" ] && [ "$relay_running" = false ]; then
      issues+=("\"自定义模式需要 codex-relay(端口 $expected_relay_port),但当前未运行\"")
    fi
    ;;
esac

if [ "$config_state" = "clean" ] && [ "$codex_installed" = true ]; then
  issues+=("\"Codex CLI 已安装但尚未配置模型服务,请选择 AgentPlan 或第三方 Key\"")
fi

# relay 鉴权闭环异常 — 端口活着但调用 401 = relay 启动时没拿到 Key
if [ "$relay_auth_healthy" = false ]; then
  issues+=("\"本地协议中转服务已启动但鉴权失败(可能启动时未注入 Key),需要重启 relay\"")
fi

# --- 输出 JSON ---
expected_relay_port_json="null"
[ -n "$expected_relay_port" ] && expected_relay_port_json="$expected_relay_port"

issues_json="$(IFS=,; echo "${issues[*]}")"
cat <<EOF
{
  "codex_installed": $codex_installed,
  "codex_version": "$codex_version",
  "codex_outdated": $codex_outdated,
  "config_state": "$config_state",
  "active_profile": $active_profile,
  "user_config_summary": $user_config_summary,
  "user_health": $user_health,
  "config_toml_exists": $config_toml_exists,
  "expected_relay_port": $expected_relay_port_json,
  "relay_running": $relay_running,
  "relay_auth_healthy": $relay_auth_healthy,
  "ark_api_key_set": $ark_api_key_set,
  "moonshot_api_key_set": $moonshot_api_key_set,
  "deepseek_api_key_set": $deepseek_api_key_set,
  "ark_v3_api_key_set": $ark_v3_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/ensure-relay.sh
content: #!/usr/bin/env bash
# ensure-relay.sh — 根据当前 active-profile 启动对应端口的 codex-relay (v7)
#
# 设计目的:
#   沙箱重启后所有进程会被清理,本脚本在每次使用 codex 前自动恢复对应的 relay。
#   通过读取 ~/.codex/active-profile 决定该启哪个端口,对用户完全透明。
#
# 行为:
#   - active-profile = arkv3 或不存在 → 直连模式,什么都不做
#   - active-profile = agentplan/kimi/deepseek → 启对应固定端口
#   - active-profile = custom → 读 ~/.codex/custom-upstream 决定端口和上游
#                                (如果该文件不存在,说明 custom 是直连模式,无需 relay)
#   - relay 已在运行 → 立即退出
#   - 否则后台启动并最多等 5 秒确认就绪

set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/relay-helper.sh"

ACTIVE_FILE="$HOME/.codex/active-profile"
if [ ! -f "$ACTIVE_FILE" ]; then
  exit 0   # 还没配置,无事可做
fi

ACTIVE="$(cat "$ACTIVE_FILE" | tr -d '[:space:]')"
if [ -z "$ACTIVE" ]; then
  exit 0
fi

# 确定端口和上游
RELAY_PORT=""
RELAY_UPSTREAM=""
case "$ACTIVE" in
  agentplan)
    RELAY_PORT=4446
    RELAY_UPSTREAM="https://ark.cn-beijing.volces.com/api/plan/v3"
    ;;
  codingplan)
    RELAY_PORT=4450
    RELAY_UPSTREAM="https://ark.cn-beijing.volces.com/api/coding/v3"
    ;;
  kimi)
    RELAY_PORT=4447
    RELAY_UPSTREAM="https://api.moonshot.cn/v1"
    ;;
  deepseek)
    RELAY_PORT=4448
    RELAY_UPSTREAM="https://api.deepseek.com/v1"
    ;;
  custom)
    # custom 上游存盘了才说明是 relay 模式;否则是原生 Responses 直连
    if [ -f "$HOME/.codex/custom-upstream" ]; then
      RELAY_PORT=4449
      RELAY_UPSTREAM="$(cat "$HOME/.codex/custom-upstream" | tr -d '[:space:]')"
    else
      exit 0   # custom 直连模式
    fi
    ;;
  arkv3)
    exit 0   # 火山方舟 v3 原生支持 Responses,直连
    ;;
  *)
    echo "⚠️  未识别的 profile: $ACTIVE,跳过 relay 启动" >&2
    exit 0
    ;;
esac

if [ -z "$RELAY_PORT" ] || [ -z "$RELAY_UPSTREAM" ]; then
  exit 0
fi

# 已在跑就退出 — 但要做一次"鉴权闭环"探测,
# 防止 v3.1 之前残留的 relay 进程没拿到 Key 导致 401。
if relay_running "$RELAY_PORT"; then
  __probe_code=$(curl -sS -o /dev/null -w "%{http_code}" \
    --max-time 4 \
    -X POST "http://127.0.0.1:${RELAY_PORT}/v1/chat/completions" \
    -H "Content-Type: application/json" \
    -d '{"model":"probe","messages":[{"role":"user","content":"ping"}],"max_tokens":1}' 2>/dev/null || echo "000")
  case "$__probe_code" in
    401|403)
      # relay 启动时没拿到 Key,杀掉旧进程重启
      echo "⚠️  检测到 relay 鉴权失败(可能 Key 未注入),正在重启..." >&2
      stop_relay_on_port "$RELAY_PORT"
      ;;
    *)
      exit 0
      ;;
  esac
fi

# 没装就装
ensure_codex_relay_installed || exit 2

# 启动并等待就绪
if start_relay "$ACTIVE" "$RELAY_PORT" "$RELAY_UPSTREAM"; then
  echo "✅ codex-relay 已自动恢复 (profile=$ACTIVE, 端口=$RELAY_PORT)"
  exit 0
fi

echo "❌ codex-relay 启动失败。请查看日志: cat ~/.codex-relay-${ACTIVE}.log" >&2
exit 4


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

set -e

CONFIG_TOML="$HOME/.codex/config.toml"

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 "$CONFIG_TOML" ]; then
  echo "{\"is_healthy\":false,\"checks\":[],\"issues\":[\"~/.codex/config.toml 文件不存在\"]}"
  exit 0
fi

# --- 从配置文件中提取关键字段 ---
MODEL_PROVIDER="$(grep -E '^model_provider' "$CONFIG_TOML" 2>/dev/null | head -1 | sed -E 's/.*=\s*"([^"]*)".*/\1/')"
MODEL="$(grep -E '^model\s*=' "$CONFIG_TOML" 2>/dev/null | head -1 | sed -E 's/.*=\s*"([^"]*)".*/\1/')"

# 提取对应 provider 配置段中的 base_url / wire_api / env_key
BASE_URL=""
WIRE_API=""
ENV_KEY=""
if [ -n "$MODEL_PROVIDER" ]; then
  PROVIDER_BLOCK="$(awk -v p="[model_providers.$MODEL_PROVIDER]" '
    $0 == p { in_block = 1; next }
    /^\[/ && in_block { exit }
    in_block { print }
  ' "$CONFIG_TOML")"
  BASE_URL="$(echo "$PROVIDER_BLOCK" | grep -E '^base_url' | head -1 | sed -E 's/.*=\s*"([^"]*)".*/\1/')"
  WIRE_API="$(echo "$PROVIDER_BLOCK" | grep -E '^wire_api' | head -1 | sed -E 's/.*=\s*"([^"]*)".*/\1/')"
  ENV_KEY="$(echo "$PROVIDER_BLOCK" | grep -E '^env_key' | head -1 | sed -E 's/.*=\s*"([^"]*)".*/\1/')"
fi

# ============================================================
# 检查 1: model_provider 字段是否已设置
# ============================================================
if [ -n "$MODEL_PROVIDER" ]; then
  add_check "model_provider_set" true "model_provider=$MODEL_PROVIDER"
else
  add_check "model_provider_set" false "配置文件中缺少 model_provider 字段,Codex 不知道该连接哪个服务"
fi

# ============================================================
# 检查 2: 对应的 provider 配置段中是否有 base_url
# ============================================================
if [ -n "$MODEL_PROVIDER" ]; then
  if [ -n "$BASE_URL" ]; then
    add_check "base_url_defined" true "base_url=$BASE_URL"
  else
    add_check "base_url_defined" false "[model_providers.$MODEL_PROVIDER] 段中缺少 base_url,Codex 不知道请求发往哪里"
  fi
fi

# ============================================================
# 检查 3: wire_api 协议是否有效
# ============================================================
# Codex 0.130+ 默认发 "responses";"chat" 仍可解析但已被官方标记 deprecated
# 老版本的 "chat_completions" 已彻底废弃
if [ -n "$WIRE_API" ]; then
  case "$WIRE_API" in
    responses)
      add_check "wire_api_valid" true "wire_api=$WIRE_API"
      ;;
    chat)
      add_check "wire_api_valid" true "wire_api=chat (已 deprecated,建议改为 responses + relay)"
      ;;
    *)
      add_check "wire_api_valid" false "wire_api=\"$WIRE_API\" 已不被支持,Codex 0.130+ 只接受 \"responses\" 或 \"chat\""
      ;;
  esac
fi

# ============================================================
# 检查 4: API Key 环境变量是否已设置
# ============================================================
if [ -n "$ENV_KEY" ]; then
  KEY_LINE="$(grep "^export $ENV_KEY=" "$HOME/.bashrc" 2>/dev/null | tail -1 || true)"
  if [ -n "$KEY_LINE" ]; then
    KEY_VAL="$(echo "$KEY_LINE" | sed -E "s/^export $ENV_KEY=\"?([^\"]*)\"?.*/\1/")"
    if [ -n "$KEY_VAL" ] && [ "$KEY_VAL" != "" ]; then
      add_check "env_key_set" true "$ENV_KEY 已设置(长度 ${#KEY_VAL} 字符)"
    else
      add_check "env_key_set" false "$ENV_KEY 存在但值为空,Codex 连接时会被拒绝(401)"
    fi
  else
    # 也检查当前 shell 环境
    CUR_VAL="$(eval "echo \${$ENV_KEY:-}")"
    if [ -n "$CUR_VAL" ]; then
      add_check "env_key_set" true "$ENV_KEY 在当前环境中已设置"
    else
      add_check "env_key_set" false "$ENV_KEY 未设置(~/.bashrc 和当前环境中都没找到),Codex 连接时会被拒绝(401)"
    fi
  fi
fi

# ============================================================
# 检查 5: 如果配置指向本地 relay (任意端口 4446-4449),检查 relay 是否在运行
# ============================================================
RELAY_PORT_DETECTED="$(echo "$BASE_URL" | grep -oE '127\.0\.0\.1:[0-9]+' | head -1 | cut -d: -f2)"
if [ -n "$RELAY_PORT_DETECTED" ]; then
  if (echo > /dev/tcp/127.0.0.1/$RELAY_PORT_DETECTED) >/dev/null 2>&1; then
    add_check "relay_running" true "codex-relay 服务正常运行(端口 $RELAY_PORT_DETECTED)"
  else
    add_check "relay_running" false "配置指向本地 127.0.0.1:$RELAY_PORT_DETECTED 但该服务未运行,Codex 会报 connection refused"
  fi
fi

# ============================================================
# 检查 6: 是否硬编码了采样参数(会导致火山方舟部分模型返回 400 错误)
# ============================================================
if grep -qE '^(temperature|top_p)\s*=' "$CONFIG_TOML"; then
  add_check "no_sampling_override" false "配置中硬编码了 temperature 或 top_p,部分模型不支持自定义采样参数,会返回 400 错误"
else
  add_check "no_sampling_override" true "未硬编码采样参数"
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/troubleshooting.md
content: # 故障排查 — 看 doctor.sh 的 issues / 看 codex 报错信息,对照下表给用户处方

> **v7 架构提醒**:每个 profile 占独立的 relay 端口(agentplan=4446 / kimi=4447 / deepseek=4448 / custom=4449),arkv3 是直连模式。报"connection refused 127.0.0.1:444X"时,先看当前 active-profile 是哪个,再排查对应端口。

## 按现象索引

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

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

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

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

**原因**:codex 版本 < 0.130,新版有 breaking change(`--print` 废弃、强制 `responses` 协议)。

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

### 3. `Not inside a trusted directory and --skip-git-repo-check was not specified`

**原因**:codex 默认要求在 git 目录跑,沙箱家目录通常不是。

**处方**:每次调用 codex 都加 `--skip-git-repo-check`:
```bash
codex exec --skip-git-repo-check "你的需求"
```
本 skill 在使用态调用模板里已经默认带这个参数,不会触发。

### 4. `error: unexpected argument '--print' found`

**原因**:用了老脚本里的 `codex --print`,Codex 0.130 已废弃。

**处方**:改成 `codex exec`。

### 5. `wire_api = "chat" is no longer supported` 或 codex 卡死无响应

**原因**:profile 里写了 `wire_api = "chat"`,但 codex 0.130+ 强制 `responses`。

**处方**:v7 之后所有 profile 的 `wire_api` 都是 `responses`,Chat-only 的上游通过本机 codex-relay 翻译。如果用户 profile 还是老版的 `chat`:
```bash
# 重跑 setup 即可生成新 profile
bash scripts/setup-thirdparty.sh <provider> "<key>" [model]
```

如果 setup 脚本写出来的 profile 有问题:
```bash
cat ~/.codex/profiles/<name>.toml
# 应该看到 wire_api = "responses",base_url 视 provider 不同
#   agentplan → http://127.0.0.1:4446/v1
#   kimi      → http://127.0.0.1:4447/v1
#   deepseek  → http://127.0.0.1:4448/v1
#   arkv3     → https://ark.cn-beijing.volces.com/api/v3   (直连)
#   custom    → 上游 URL 或 http://127.0.0.1:4449/v1       (探测决定)
```

### 6. `connection refused 127.0.0.1:444X`(任意 profile)

**原因**:对应端口的 codex-relay 没启动。每个 profile 占一个端口:agentplan=4446 / kimi=4447 / deepseek=4448 / custom=4449。

**处方**:
```bash
# 1. 看当前是哪个 profile
cat ~/.codex/active-profile

# 2. 一键自动恢复
bash scripts/ensure-relay.sh

# 3. 看日志(替换 <profile> 为实际名字)
tail -50 ~/.codex-relay-<profile>.log

# 4. 如果还不行,手动重启
codex-relay --port <端口> --upstream <对应上游URL>
```

### 7. `401 unauthorized`

**原因**:Key 错或不在对应路径有权限。

**处方**:
- AgentPlan 路径:必须用 **AgentPlan 控制台分配的"专属 Key"**,不能用普通 API Key 管理里的 Key
- 普通 v3 路径:必须用普通 API Key 管理里的 Key,不能用 AgentPlan 的
- 重新跑对应 setup 脚本传入正确的 Key:
  ```bash
  bash scripts/setup-agentplan.sh "<正确的 AgentPlan Key>"
  bash scripts/setup-thirdparty.sh kimi "<正确的 Moonshot Key>"
  ```

### 8. `404 Not Found`

**原因**:base_url 写错了。

**处方**:
- 看 `~/.codex/config.toml`(其实是当前 active profile 的软链)的 `base_url`
- 各 profile 期望值:
  - agentplan → `http://127.0.0.1:4446/v1`
  - kimi → `http://127.0.0.1:4447/v1`
  - deepseek → `http://127.0.0.1:4448/v1`
  - arkv3 → `https://ark.cn-beijing.volces.com/api/v3`(直连)
  - custom → 自动探测,要么是上游 URL,要么是 `http://127.0.0.1:4449/v1`

### 9. `400 invalid temperature/top_p`

**原因**:profile 里写了被后端模型写死的采样参数。

**处方**:删掉 profile 里所有 `temperature` / `top_p` 字段。本 skill 出厂的 profile 已经不带这些,如果用户手动加了 → 删掉。

### 10. 模型重复回复 N 次

**原因**:`request_max_retries / stream_max_retries / responses_websockets_v2` 三个开关没设全。

**处方**:本 skill profile 模板已经预设了。如果用户手动改过 profile 删了这几行 → 重跑 setup 脚本恢复。

### 11. doctor.sh 显示对应 Key 是空的

**原因**:`source ~/.bashrc` 还没生效,或者 setup 没成功。

**处方**:
```bash
# 看哪个 Key 没设
case "$(cat ~/.codex/active-profile)" in
  agentplan) echo "${#ARK_API_KEY}" ;;
  kimi)      echo "${#MOONSHOT_API_KEY}" ;;
  deepseek)  echo "${#DEEPSEEK_API_KEY}" ;;
  arkv3)     echo "${#ARK_V3_API_KEY}" ;;
  custom)    echo "${#CUSTOM_API_KEY}" ;;
esac
# 应该不为 0;为 0 → 重跑对应 setup 脚本
source ~/.bashrc
```

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

**原因**:Key 没持久化或 codex-relay 没自启。

**处方**:本 skill 的 setup 脚本会写一个**通用** auto-recover 块到 `~/.bashrc`,根据 active-profile 自动启对应端口的 relay。正常 `source ~/.bashrc` 即恢复。如果还是不行:
```bash
grep -E '(API_KEY|codex-allinone)' ~/.bashrc   # 应能看到 Key + auto-recover marker
bash scripts/ensure-relay.sh                    # 手动触发恢复
```

### 13. 切换 profile 后 codex 还是用旧的

**原因**:codex 是按 `~/.codex/config.toml` 读的,本 skill 用软链切换。检查软链:
```bash
ls -la ~/.codex/config.toml
# 应该指向 ~/.codex/profiles/<active>.toml
cat ~/.codex/active-profile
```

如果软链坏了:
```bash
bash scripts/switch-profile.sh <name>   # 重跑切换
```

### 14. `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 自己看,告诉它在什么状态下做什么。

## v7 架构核心(必须先理解)

Codex CLI 0.130+ 默认发 OpenAI **Responses 协议**,而多数第三方服务只懂 **Chat 协议**。
本 skill 的解决方案:用本机 `codex-relay` 进程做协议翻译,每个 profile 独占一个端口。

| Profile | 端口 | 上游 | 协议适配 |
|---------|------|------|---------|
| agentplan | 4446 | https://ark.cn-beijing.volces.com/api/plan/v3 | relay 翻译 (Responses → Chat) |
| kimi | 4447 | https://api.moonshot.cn/v1 | relay 翻译 |
| deepseek | 4448 | https://api.deepseek.com/v1 | relay 翻译 |
| arkv3 | — | https://ark.cn-beijing.volces.com/api/v3 | **直连**(原生支持 Responses) |
| custom | 4449 或直连 | 用户提供 | **自动探测**(404 → relay;否则直连) |

custom 路径的探测逻辑见 `scripts/relay-helper.sh#probe_responses_support`,
对用户给的 base_url 发一个 `POST /responses`,根据 HTTP code 决定是否需要 relay。

## 状态机

```
                    ┌─────────────────────────┐
                    │ 用户触发 codex-allinone  │
                    └─────────────┬───────────┘
                                  ▼
                       bash scripts/doctor.sh
                                  │
                 ┌────────────────┼────────────────────┐
                 ▼                ▼                    ▼
         Codex 未安装       版本过旧              已安装且满足要求
                 │                │                    │
                 └────────┬───────┘                    │
                          ▼                            │
                 scripts/install.sh                    │
                          │                            │
                          └──────────┬─────────────────┘
                                     ▼
                            config_state == ?
                                     │
        ┌────────────────────────────┼────────────────────────────┐
        ▼                            ▼                            ▼
    "clean"                  "managed-by-skill"             "user-managed"
   全新环境                  本 skill 之前配过               用户手动配过
        │                            │                            │
        ▼                            ▼                            ▼
  【路径 ①】                   【路径 ②】                    【路径 ③】
  首次配置                    正常使用                      兼容性检查
        │                            │                            │
        │                            │                     is_healthy ?
        │                            │                     ├─ 通过 → 转路径 ②
        │                            │                     └─ 不通 → 告知用户问题
        │                            │                              ├─ 选 A 重配 → 路径 ①
        │                            │                              └─ 选 B 自修 → 退出
        │                            │
        ├─ 选 A AgentPlan             ├─ ensure-relay.sh(确保服务就绪)
        │  → setup-agentplan.sh      │
        │                            ├─ codex exec --skip-git-repo-check "用户需求"
        └─ 选 B 第三方                │
           → 二级菜单                 ├─ 用户说"切换"
           → setup-thirdparty.sh     │  → switch-profile.sh
                                     │
                                     └─ 用户报错
                                        → 参考 troubleshooting.md
```

## 与用户对话的措辞模板

### 主菜单(仅全新环境触发)

> 你想用哪种模型来驱动 Codex?
>
> **A. 火山方舟 AgentPlan**(推荐)
> - 套餐内额度,无需额外付费
> - `ark-code-latest` 智能路由,可在 AgentPlan 控制台切换 Kimi K2 / Doubao / DeepSeek / GLM
> - 你需要:**AgentPlan 控制台**分配的"专属 Key"(注意不是普通 API Key 管理里的)
>
> **B. 第三方 Key**(已有 Kimi / DeepSeek / 自购方舟 Key,或公司自建网关等)
> - 自由选模型
> - 你需要:对应服务的 API Key
>
> 请回复 **A** 或 **B**。

### 二级菜单(选了 B 之后)

> 你的 Key 是哪家的?
>
> 1. **Kimi (Moonshot)** — 默认模型 `kimi-k2-turbo-preview`
>    需要:Kimi 平台的 API Key
>
> 2. **DeepSeek** — 默认模型 `deepseek-chat`
>    需要:DeepSeek 平台的 API Key
>
> 3. **火山方舟普通 v3 Key** — 默认 `doubao-seed-1-6-250615`
>    需要:火山方舟控制台 → API Key 管理里的普通 Key(注意:这跟 AgentPlan 的"专属 Key"不一样)
>
> 4. **自定义 OpenAI 兼容端点** — 任何符合 OpenAI Chat Completions 协议的服务
>    需要你**同时提供三样东西**:
>    - **API Key**(如 `sk-xxx`)
>    - **模型名**(如 `qwen-max` / `glm-4.6` / `yi-large` / 公司网关里的 model id)
>    - **Base URL**(如 `https://dashscope.aliyuncs.com/compatible-mode/v1`,通常以 `/v1` 结尾)
>
>    适用场景:通义千问、智谱 GLM、Yi、百川、LiteLLM、vLLM、Ollama、公司自建网关等。
>    ⚠️ 沙箱在境内 IDC,境外服务(OpenAI / Anthropic 官方等)通常连不上。
>
> 请回复数字。

### 兼容性检查不通过时

> 检测到你已有的 codex 配置存在以下问题:
>
> 1. {issues[0]}
> 2. {issues[1]}
>    ...
>
> 你希望:
>
> **A. 让本 skill 帮你重新配置**(推荐)
>    - 会先把你现有的 config.toml 备份,你随时可以恢复
>    - 然后重新选择 AgentPlan / 第三方 Key
>
> **B. 我自己去修改**
>    - 退出本 skill,你手动修改 config.toml
>    - 改完后再次触发 skill 会自动重新检查
>
> 请回复 **A** 或 **B**。

### 正常使用时的调用方式

用户说编程需求时,先确保服务就绪,再执行:

```bash
bash scripts/ensure-relay.sh
cd ~ && codex exec --skip-git-repo-check "用户的完整需求原文"
```

> 说明:
> - `ensure-relay.sh` 会读取当前 active-profile,自动启动对应端口的 relay。arkv3 直连模式以及 custom 直连模式会立即退出,不耗时
> - 端口分配:agentplan=4446 / kimi=4447 / deepseek=4448 / custom=4449 / arkv3=直连
> - `cd ~` 是因为 codex 默认要求在 git 目录中运行,沙箱家目录通常不是 git 仓库
> - `--skip-git-repo-check` 跳过这个限制

### 切换模型服务

用户说"切换"/"换一家"/"切到 Kimi" 时:
- 没指定具体哪家 → `bash scripts/switch-profile.sh`(列出已配置的选项)
- 指定了 → `bash scripts/switch-profile.sh <名称>`

如果用户之前是手动配置的(`config_state == "user-managed"`),切换前提醒:
"切换会覆盖你现有的 config.toml(会先备份),确认吗?"

## 禁止事项

- ❌ 用户没有选择之前不能自动运行配置脚本
- ❌ 不能自动猜测 Key,必须让用户主动粘贴
- ❌ 不能连接 OpenAI 官方服务(沙箱在境内 IDC,网络不通)
- ❌ 没有备份的情况下不能覆盖用户的配置文件
- ❌ 不能在日志或屏幕上打印完整的 Key
- ❌ 配置已就绪时不能反复询问用户"是否要切换"— 静默执行即可
- ❌ 兼容性检查通过时不能打扰用户 — 直接进入正常使用


path: references/thirdparty-providers.md
content: # 第三方 Provider 配置详情 (v7)

每家 provider 的 base_url、推荐模型、注意事项。Skill 在引导用户选 B 路径时读这份。

## 协议适配总览

Codex CLI 0.130+ 默认发 OpenAI **Responses 协议**,而多数第三方服务只支持 **Chat 协议**。本 skill 通过 `codex-relay` 协议中转层抹平这个差异。

| Profile | 端口 | 上游 | 是否走 relay |
|---------|------|------|-------------|
| agentplan | 4446 | https://ark.cn-beijing.volces.com/api/plan/v3 | ✅ relay |
| kimi | 4447 | https://api.moonshot.cn/v1 | ✅ relay |
| deepseek | 4448 | https://api.deepseek.com/v1 | ✅ relay |
| arkv3 | — | https://ark.cn-beijing.volces.com/api/v3 | ❌ 直连(原生 Responses) |
| custom | 4449 或直连 | 用户提供 | 🔍 自动探测 |

## 1. Kimi (Moonshot)

| 字段 | 值 |
|---|---|
| base_url(Codex 看到的) | `http://127.0.0.1:4447/v1` |
| 真实上游 | `https://api.moonshot.cn/v1` |
| 默认 model | `kimi-k2-turbo-preview` |
| wire_api | `responses`(经 relay 翻译为 Chat) |
| env_key | `MOONSHOT_API_KEY` |
| Key 获取 | <https://platform.moonshot.cn/console/api-keys> |
| 计费 | 按 token,Kimi 平台账户余额 |

**可选模型**(用户可在 setup 时通过第三参数覆盖):
- `kimi-k2-turbo-preview` — 默认,综合能力最强,长上下文
- `kimi-k2-thinking` — 思考模式,带 reasoning
- `moonshot-v1-128k` — 老一代 128K 模型

**注意**:Moonshot 不支持 OpenAI Responses API,只支持 Chat Completions,所以必须经 codex-relay 翻译。

## 2. DeepSeek

| 字段 | 值 |
|---|---|
| base_url(Codex 看到的) | `http://127.0.0.1:4448/v1` |
| 真实上游 | `https://api.deepseek.com/v1` |
| 默认 model | `deepseek-chat` |
| wire_api | `responses`(经 relay 翻译为 Chat) |
| env_key | `DEEPSEEK_API_KEY` |
| Key 获取 | <https://platform.deepseek.com/api_keys> |
| 计费 | 按 token,DeepSeek 平台账户余额(便宜) |

**可选模型**:
- `deepseek-chat` — V3 通用对话,默认
- `deepseek-reasoner` — R1 推理模型,带思考链

**注意**:DeepSeek 偶尔会有限流,profile 里已经设了 `request_max_retries = 4`。

## 3. 火山方舟普通 v3 (arkv3)

| 字段 | 值 |
|---|---|
| base_url | `https://ark.cn-beijing.volces.com/api/v3` |
| 默认 model | `doubao-seed-1-6-250615` |
| wire_api | `responses`(原生支持) |
| env_key | `ARK_V3_API_KEY` |
| Key 获取 | 火山方舟控制台 → API Key 管理 |
| 计费 | 自购 Key,按 token 计费 |

**与 AgentPlan 的区别**:

| 维度 | AgentPlan(profile=agentplan) | 普通 v3(profile=arkv3) |
|---|---|---|
| 路径 | `/api/plan/v3` | `/api/v3` |
| Key 类型 | AgentPlan 控制台分配的"专属 Key" | API Key 管理的普通 Key |
| 计费 | 套餐内额度 | 自购,按 token |
| Responses 支持 | 仅 Chat,需 codex-relay 翻译 | 原生支持,直连 |
| 模型字段 | `ark-code-latest`(智能路由) | 具体模型名(如 `doubao-seed-1-6-250615`) |

**注意**:很多用户会把这两个搞混。如果用户有"AgentPlan 控制台的 Key" → 选 A 路径;如果用户有"普通方舟控制台 API Key 管理里的 Key" → 选 B 路径的 arkv3。

## 4. Custom(自定义 OpenAI 兼容端点)

适用于:公司自建 LLM 网关、自建反代、其他未在上述列表中的 OpenAI 兼容服务(如 GLM / 通义千问 / Yi / 百川 的官方 OpenAI 兼容接口、LiteLLM、vLLM、Ollama 等)。

**用户必须提供三样东西**:

| 参数 | 必填 | 示例 | 说明 |
|---|---|---|---|
| API Key | ✅ | `sk-xxxx` 或自建网关的 token | 写入环境变量 `CUSTOM_API_KEY` |
| 模型名 (model) | ✅ | `qwen-max` / `glm-4.6` / `yi-large` | 服务端能识别的 model id |
| Base URL | ✅ | `https://dashscope.aliyuncs.com/compatible-mode/v1` | OpenAI 兼容端点的根地址,**通常以 `/v1` 结尾** |

### 自动探测逻辑

setup-thirdparty.sh 会对用户给的 `base_url` 发一次 `POST {base_url}/responses`(`relay-helper.sh#probe_responses_support`):

| HTTP code | 判定 | 路由方式 |
|---|---|---|
| 200 / 400 / 401 / 403 / 422 | 端点存在,**支持** Responses | 直连,profile 里 base_url 直接写上游 |
| 404 | 端点不存在,**不支持** Responses | 走 relay(端口 4449),profile 里 base_url 写 `http://127.0.0.1:4449/v1` |
| 网络不通 / 其他 | 保守按"不支持"处理 | 走 relay |

走 relay 模式时,上游 URL 会写到 `~/.codex/custom-upstream`,沙箱重启时由 `.bashrc` 的 auto-recover 块自动恢复。

### 调用方式

```bash
bash setup-thirdparty.sh custom "$KEY" "<model>" "<base_url>"
```

### 示例

**通义千问(阿里云 DashScope)** — Chat-only,会自动启 relay:
```bash
bash setup-thirdparty.sh custom "sk-xxx" "qwen-max" "https://dashscope.aliyuncs.com/compatible-mode/v1"
```

**智谱 GLM** — Chat-only,会自动启 relay:
```bash
bash setup-thirdparty.sh custom "xxx.xxx" "glm-4.6" "https://open.bigmodel.cn/api/paas/v4"
```

**公司内部网关(若已原生支持 Responses)** — 自动直连:
```bash
bash setup-thirdparty.sh custom "internal-token" "gpt-4o" "https://llm-gateway.your-company.com/v1"
```

### 注意

- base_url 必须是 OpenAI 兼容(Chat Completions 至少要通)
- 沙箱环境(ArkClaw)在境内 IDC,**境外服务**(如 OpenAI 官方、OpenRouter、Anthropic 官方)通常连不上,custom 路径只建议接境内可达的端点
- 如果接通后报 4xx/5xx 错误,先用 `curl` 直接测一下端点是否能正常返回,排除是网关本身的问题
- 如果探测误判(罕见),用户可以手动编辑 `~/.codex/profiles/custom.toml` 把 `base_url` 改回直连或 relay 模式,然后:
  - 切到直连模式 → 删除 `~/.codex/custom-upstream`
  - 切到 relay 模式 → 把上游 URL 写入 `~/.codex/custom-upstream`,然后跑 `bash scripts/ensure-relay.sh`

## 选哪个?

| 用户场景 | 推荐 profile |
|---|---|
| 字节内部用户,有 AgentPlan 配额 | **agentplan**(套餐内,免费) |
| 个人有 Kimi 账号(月之暗面送的免费额度) | kimi |
| 想要最便宜的 token 价格 | deepseek |
| 自购了方舟 Key,但不在 AgentPlan 套餐 | arkv3 |
| 公司内有自建 OpenAI 兼容网关 / 想接通义/GLM/Yi 等境内服务 | custom |

> 如果用户不确定,默认推荐 **agentplan** — 沙箱内可用、免费、模型层面已经覆盖 Kimi/Doubao/DeepSeek/GLM。

> ⚠️ **关于境外服务(OpenAI / OpenRouter / Anthropic 官方等)**:ArkClaw 沙箱在境内 IDC,网络出口受限,这类服务**通常无法直连**,因此本 skill 不在二级菜单里提供。如用户坚持要接,可走 custom 路径自行尝试,但失败概率较高。


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