用户在多家 Claude Code 兼容的 coding plan 间切换,本质是"换一组环境变量再启动 claude"。当前痛点:
ANTHROPIC_AUTH_TOKEN,有的用 ANTHROPIC_API_KEY + ANTHROPIC_BASE_URL,有的还设 ANTHROPIC_MODEL / ANTHROPIC_SMALL_FAST_MODEL),漏清理会造成"以为切了但没切"的串扰;该工具目标是做成一个薄封装:配置集中、切换确定性强、启动 claude 后行为与直接在 shell 里运行一致(stdio、信号、退出码都透传)。
Goals:
claude 等价。cc-switch use <name>) 与交互 (cc-switch 裸跑或 cc-switch use 无参) 两种入口。Non-Goals:
claude 行为,只负责"换环境再启动"。采用 YAML,路径遵循 XDG:默认 $XDG_CONFIG_HOME/cc-switch/config.yaml(未设置时回落到 ~/.config/cc-switch/config.yaml);CC_SWITCH_CONFIG 环境变量可覆盖。
key: value 展示。用户可能同时维护多个 provider,手改体验优于 TOML/JSON。构造子进程的 Env 切片,而非修改 cc-switch 自身的 os.Environ:
os.Environ() 作为基础;union(all providers' env keys);exec.Cmd.Env,启动 claude。
这样"清理 → 注入"只作用于子进程环境,cc-switch 自身进程和父 shell 完全不受影响;"退出后清理"由进程隔离天然保证(子进程环境随其生命周期结束)。对外我们保留"退出后再清理一次"的语义承诺,在 cc-switch 退出前打印一条 trace(可选 --verbose)确认,但不做实际 mutation。os.Environ 做有状态 mutation 带来的测试复杂度和泄漏风险;读写集中在一处易于单测。os.Setenv / os.Unsetenv 再 fork 子进程——需要在 defer 中回滚,panic 路径容易漏;对多 goroutine 场景不友好。claude 可执行文件解析优先使用配置里的 claude_path(绝对或相对路径,支持 ~);未配置时 exec.LookPath("claude");都找不到则以明确错误退出(退出码保留给用户,不伪装成 claude 自身错误)。
claude 装在 ~/.claude/local/claude 这类非 PATH 路径,需要显式覆盖;同时多数用户 PATH 里就有,不应强制配置。使用 os/exec 启动 claude,cc-switch 作为父进程守护:
cmd.Wait() 完成后提取 ExitCode() 作为 cc-switch 自身退出码。
不使用 syscall.Exec(替换当前进程映像)。exec 语义);便于未来加 --dry-run、--print-env 等 hook。claude 本身就是长交互进程,进程链深一层对用户无感。采用 github.com/spf13/cobra 做子命令装配,gopkg.in/yaml.v3 做 YAML 序列化。
flag(子命令层级要自己搭)、urfave/cli(也可行但生态小一号)。优先使用轻量方案:在 tty 下用简单数字菜单(stdin 读一行),不引入完整 TUI 库。若未来体验不足再评估 survey/bubbletea。
<name>)。创建配置文件与其父目录时使用 0700(目录)、0600(文件)。读写配置前若发现权限过宽(group/other 可读),打印一条 warning 但不中止——降低误操作阻塞成本。
保存配置使用 write-to-temp-then-rename:先写 config.yaml.tmp,fsync,再 os.Rename 为目标文件。
支持值形如 env:VAR_NAME 从启动时的父进程环境中读取实际值(正则约束 ^env:[A-Za-z_][A-Za-z0-9_]*$)。解析时机:启动阶段,在"并集清理"之前先对 os.Environ() 取快照,针对选中 provider 的 env 值逐一解析引用;引用的 VAR_NAME 未在快照中出现则非零退出并打印哪一个 key 引用的哪个 VAR 缺失。引用的变量值本身仍被视为最终 env value,写入子进程环境前不会再做任何 shell 扩展。
ANTHROPIC_API_KEY: env:ANTHROPIC_API_KEY),如果清理后再解析就读不到了。先快照绕过这层次序耦合。${VAR} 风格(与"value 字面传递"的承诺冲突、视觉上像 shell 扩展易误解);YAML 显式结构 {from_env: VAR}(schema 复杂、增删改 UX 差);!env YAML 标签(不易被通用 YAML 工具处理)。env: 开头且并非引用的情况极少,v1 不支持转义;文档明示"若确实需要字面 env:xxx,请改用不同写法或等 v2 转义方案"。在二进制中内嵌(go:embed 一份 templates.yaml)一组常见 provider 的 env key 骨架,作为 cc-switch add --from-template <tpl> 的起点。v1 的种子模板:anthropic-official、openrouter、deepseek、moonshot(Kimi)、zhipu(GLM)、custom-base(仅 BASE_URL + AUTH_TOKEN,给未列出的 OpenAI-兼容服务用)。每个模板包含:description、env key 列表(仅键名 + 每项可选的 hint 提示文案和可选的 default 示例 value,例如 ANTHROPIC_BASE_URL 默认填官方 URL)。
add <name> --from-template openrouter 进入向导,逐个 key 提示输入(显示 hint;允许直接输入 value 或 env:VAR 引用;有 default 的回车接受)。提供 --non-interactive 时只带出 key 骨架写入(value 为空占位),要求用户后续 edit 补齐。$XDG_CONFIG_HOME/cc-switch/templates/。README 会附贡献新模板到 upstream 的 PR 指引。env:VAR_NAME 语法支持从外部 env 引用(v1 可先设计字段,实现留到后续)——v1 不实现该语法,但在 schema 中预留扩展位。union(all providers' keys) 做清理,即便这些变量来自父 shell 也会被覆盖;但如果用户 export 的某个 key 未出现在任何 provider 配置里,则会被原样继承(这是预期行为)。文档中明确说明"union"范围,并建议用户把所有相关 key 都登记到至少一个 provider 里以便统一清理。stdin.IsTerminal(),非 tty 时要求必须显式给 <name>,否则非零退出并打印提示。cc-switch add 提供交互式向导,把常见 key 列出来让用户勾选填值。这是全新仓库、无历史用户:
add/list/use);edit/remove/config;go install 双通道)。
无回滚策略需求(新项目)。add 时提供常见 provider 的 env key preset(Anthropic 官方、DeepSeek、Kimi、Moonshot、OpenRouter 等兼容层)作为可选引导?—— 倾向 v1 不做,由 README 给出 snippet;v2 观察用户反馈再加。cc-switch use foo --set ANTHROPIC_MODEL=claude-opus-4-7?—— v1 不做,保持配置单一事实来源;v2 可加 --set/--unset 作为临时 override。cc-switch doctor 做健康检查(claude 路径存在、配置权限正确、tty 检测)?—— v1 暂不做,但在 list -v 中附带展示 claude 路径解析结果。