spec.md 7.8 KB

provider-launch Specification

Purpose

选中 provider 后启动 claude 子进程的完整语义:env 引用解析、并集清理、注入规则、可执行文件查找、stdio 透传、信号转发、退出码传播与退出清理保证。

Requirements

Requirement: 解析 env 引用

启动流程在"并集清理"之前 SHALL 先对父进程环境取一次快照(os.Environ())。对于选中 provider 的每一项 env value,若匹配 ^env:[A-Za-z_][A-Za-z0-9_]*$,SHALL 从该快照中查找同名变量:

  • 若存在,该 key 的实际 value 替换为快照中的值;
  • 若不存在,工具 MUST 以非零退出码退出,stderr 指出哪个 key 引用的哪个 VAR 缺失,且此时 MUST NOT 启动 claude、MUST NOT 对 env 做任何 mutation。

解析完成后得到的"物化"env map 作为后续"并集清理 + 注入"的输入。解析到的值 MUST 不做任何 shell 扩展或二次引用解析(不支持链式 env:Aenv:B)。

Scenario: 引用成功

  • WHEN 父 shell 已 export MY_KEY=sk-abc,provider foo.env.ANTHROPIC_API_KEY=env:MY_KEY,用户执行 cc-switch use foo
  • THEN 子进程 claude 收到 ANTHROPIC_API_KEY=sk-abc

Scenario: 引用变量缺失

  • WHEN 父 shell 未 export MY_KEY,provider foo.env.ANTHROPIC_API_KEY=env:MY_KEY
  • THEN 工具非零退出;stderr 形如 ANTHROPIC_API_KEY references env var MY_KEY which is not setclaude 未被启动

Scenario: 引用的 VAR 恰好在并集中

  • WHEN provider A 的 env key 含 ANTHROPIC_API_KEY,provider B 的 env.ANTHROPIC_API_KEY=env:ANTHROPIC_API_KEY,父 shell 已 export ANTHROPIC_API_KEY=sk-real,用户执行 cc-switch use B
  • THEN 由于解析发生在清理之前,子进程 claude 正确收到 ANTHROPIC_API_KEY=sk-real(来自快照)

Scenario: 不做链式解析

  • WHEN 父 shell 有 A=env:BB=plain,provider 的 value 为 env:A
  • THEN 工具解析一次,子进程收到字面 env:B(不继续解析 B)

Requirement: 环境变量并集清理

启动 claude 前,工具 SHALL 计算所有已配置 provider 的 env key 的并集(记为 K),并从即将传给子进程的环境中移除 K 中每一个 key。该清理 MUST 在注入选中 provider 的 env 之前完成。工具 MUST NOT 修改 cc-switch 自身进程的 os.Environ,也 MUST NOT 尝试修改父 shell 的环境。

Scenario: 父 shell 存在旧变量

  • WHEN 父 shell 已 export ANTHROPIC_API_KEY=old 且该 key 出现在某个 provider 的 env 中,用户执行 cc-switch use foofoo.env 不含 ANTHROPIC_API_KEY
  • THEN 子进程 claude 的环境中 ANTHROPIC_API_KEY 不存在(被清理、且 foo 未重新设置)

Scenario: 与 provider 无关的变量原样继承

  • WHEN 父 shell 有 HOMEPATHLANG 等变量,它们不在任何 provider 的 env key 集合中
  • THEN 子进程 claude 继承这些变量、值与父进程一致

Scenario: 并集跨 provider 生效

  • WHEN provider A 的 env 含 ANTHROPIC_API_KEY、provider B 的 env 含 ANTHROPIC_AUTH_TOKEN,用户 cc-switch use A
  • THEN 子进程环境中 ANTHROPIC_AUTH_TOKEN 被清理(因其属于并集),同时 provider A 的 ANTHROPIC_API_KEY 被写入

Requirement: 选中 provider 的 env 注入

清理完成后,工具 SHALL 将选中 provider 的 env 中每一个 key=value 写入子进程环境。若 key 与清理后的继承环境中某个 key 同名,provider 的 value SHALL 胜出(注入覆盖)。Value MUST 原样传递,不做任何 shell 扩展或变量替换。

Scenario: 注入覆盖继承值

  • WHEN 父 shell 有 ANTHROPIC_MODEL=x,该 key 不在并集中但选中 provider 设置了 ANTHROPIC_MODEL=y
  • THEN 子进程环境中 ANTHROPIC_MODEL=y

Scenario: Value 不做 shell 扩展

  • WHEN 选中 provider 的 ANTHROPIC_BASE_URL=$HOME/api
  • THEN 子进程收到的值字面量就是 $HOME/api,不会被展开为用户 home 目录

Requirement: 解析 claude 可执行文件路径

工具 SHALL 按以下顺序解析要执行的路径:

  1. claude_path 已配置,使用之(支持 ~ 展开),并校验可执行位;
  2. 否则使用 exec.LookPath("claude")。 任一步找到且可执行即使用该路径;均失败时 SHALL 以非零退出码终止,并输出清晰错误(指出用户可执行 cc-switch config set claude-path <path> 来解决)。

Scenario: 使用配置路径

  • WHEN claude_path=~/.claude/local/claude 且该文件存在且有执行位
  • THEN 工具以该路径启动子进程

Scenario: 回退到 PATH 查找

  • WHEN claude_path 未配置且 PATH 中存在 claude
  • THEN 工具使用 exec.LookPath 结果启动子进程

Scenario: 无处可寻

  • WHEN claude_path 未配置且 PATH 中无 claude
  • THEN 工具非零退出并打印引导用户配置 claude-path 的提示

Requirement: 子进程透传 stdio

启动 claude 时,工具 SHALL 将 cc-switch 自身的 stdin / stdout / stderr 直接继承给子进程(cmd.Stdin/Stdout/Stderr = os.Stdin/Stdout/Stderr),不做任何缓冲或改写。

Scenario: 交互输入透传

  • WHEN 用户在 claude 运行期间敲入字符
  • THEN 字符被 claude 即时读取,与直接运行 claude 行为一致

Scenario: 输出无额外前缀

  • WHEN claude 写入 stdout
  • THEN 终端看到的字节与直接运行 claude 完全一致,cc-switch 不添加前缀、颜色或时间戳

Requirement: 信号转发

cc-switch SHALL 捕获 SIGINT、SIGTERM、SIGHUP 并转发给子进程 claude;收到信号后 MUST NOT 立即退出,而是继续等待子进程自行收尾后读取其退出码。

Scenario: Ctrl+C 传递

  • WHEN 用户在 claude 运行时按下 Ctrl+C(SIGINT)
  • THEN 子进程 claude 收到 SIGINT,进入自己的取消流程;cc-switch 不立即退出

Scenario: 终端关闭

  • WHEN 用户关闭终端窗口(SIGHUP 送达 cc-switch
  • THEN cc-switch 向子进程转发 SIGHUP,等待其退出后再退出

Requirement: 退出码透传

cc-switch SHALL 使用子进程 claude 的退出码作为自身退出码。若子进程因信号终止,cc-switch SHALL 退出码 128 + signum(符合 POSIX 约定)。若 cc-switch 自身在调用 claude 前失败(如配置错误、路径解析失败),退出码 SHALL 为非零且不是 0–125 范围内容易与 claude 混淆的值(使用 64、70 之类的约定值或 >= 1,在实现中固定)。

Scenario: 正常退出码

  • WHEN claude 以退出码 0 退出
  • THEN cc-switch 以退出码 0 退出

Scenario: 非零退出码

  • WHEN claude 以退出码 42 退出
  • THEN cc-switch 以退出码 42 退出

Scenario: 信号终止

  • WHEN claude 被 SIGKILL(9)杀死
  • THEN cc-switch 以退出码 137(128+9)退出

Requirement: 退出后清理语义

claude 子进程退出后,并集中的 env key MUST 不残留在任何与 cc-switch 运行相关的 shell 状态中。由于工具采用"构造子进程 Env 切片"的实现,子进程退出即意味着其私有环境被销毁,cc-switch 本身未修改过 os.Environ,父 shell 未受影响,该要求天然满足。工具 SHALL 在 --verbose 模式下打印一条 trace 日志确认"cleanup complete"。

Scenario: 父 shell 不受影响

  • WHEN 用户 cc-switch use foo 运行并退出后,回到 shell 执行 env | grep ANTHROPIC_
  • THEN 父 shell 中没有任何由 foo 新引入的 env 变量(只保留用户自己 export 的那些)

Scenario: verbose trace

  • WHEN 用户执行 cc-switch -v use fooclaude 正常退出
  • THEN stderr 中可见一条 cleanup complete 或等价语义的 trace 行