# cli Specification ## Purpose `cc-switch` 的命令行入口与交互形态规范:二进制布局、子命令分发、裸跑与交互模式、全局 flag,以及 `config` / `add` / `templates` 等子命令的用户可见行为。 ## Requirements ### Requirement: 二进制与子命令入口 工具 SHALL 以单个二进制 `cc-switch` 分发,采用子命令架构。顶层 SHALL 至少暴露以下子命令:`add`、`list`(别名 `ls`)、`edit`、`remove`(别名 `rm`)、`use`、`config`、`templates`、`help`、`version`。未识别的子命令 SHALL 以非零退出码退出并建议 `cc-switch --help`。 #### Scenario: 列出帮助 - **WHEN** 用户执行 `cc-switch --help` - **THEN** 输出包含上述所有子命令的一行摘要,且以 0 退出 #### Scenario: 未知子命令 - **WHEN** 用户执行 `cc-switch foobar` - **THEN** 工具以非零退出码退出,并在 stderr 提示 `foobar` 未知、可查看 `--help` #### Scenario: 版本信息 - **WHEN** 用户执行 `cc-switch version` - **THEN** 输出至少包含语义化版本号;构建时可注入 commit 与日期信息 ### Requirement: 裸跑入口 = 交互式 use 执行 `cc-switch`(无参数)SHALL 等价于 `cc-switch use`(无参数),即进入交互式 provider 选择流程。 #### Scenario: tty 裸跑 - **WHEN** 用户在终端裸跑 `cc-switch` - **THEN** 屏幕出现带编号的 provider 列表,等待用户输入编号或名称 ### Requirement: 交互式 provider 选择 `cc-switch use`(无参)在 stdin 为 tty 时 SHALL 展示编号列表让用户选择;若存在 `default_provider`,在列表中 SHALL 标注为默认,空输入(直接回车)SHALL 选中默认 provider;用户可输入编号或 provider 名。输入不匹配时 SHALL 重新提示(最多 3 次),全部失败后以非零退出码退出。 #### Scenario: 回车选默认 - **WHEN** 存在 `default_provider=foo`,用户执行 `cc-switch use` 并直接回车 - **THEN** `foo` 被选中、进入启动流程 #### Scenario: 输入编号 - **WHEN** 列表中编号 2 对应 `bar`,用户输入 `2` 回车 - **THEN** `bar` 被选中、进入启动流程 #### Scenario: 输入名称 - **WHEN** 用户输入 `bar` 回车 - **THEN** `bar` 被选中、进入启动流程 #### Scenario: 连续非法输入 - **WHEN** 用户连续 3 次输入不存在的编号或名称 - **THEN** 工具非零退出、不启动 `claude` ### Requirement: 非交互式 provider 选择 `cc-switch use ` SHALL 直接选中指定 provider 并进入启动流程,无需任何 tty 交互。未指定 `` 且 stdin 非 tty 时 SHALL 尝试使用 `default_provider`;若也未设置默认,SHALL 以非零退出码退出并提示需要显式 ``。 #### Scenario: 直接指定 - **WHEN** 用户执行 `cc-switch use foo` 且 `foo` 已配置 - **THEN** 工具立即进入清理/注入/启动流程,不做任何交互 #### Scenario: 指定不存在 provider - **WHEN** 用户执行 `cc-switch use nonexistent` - **THEN** 工具非零退出、stderr 提示 `nonexistent` 不存在、建议 `cc-switch list` #### Scenario: 管道模式使用默认 - **WHEN** stdin 非 tty(例如被 `:` 或先设置默认 provider ### Requirement: 全局 flag 工具 SHALL 支持以下全局 flag:`-v / --verbose`(输出 trace 日志到 stderr)、`--config `(等价于 `CC_SWITCH_CONFIG` 但优先级更高)、`-h / --help`。这些 flag SHALL 在任意子命令中可用。 #### Scenario: --config 覆盖 env - **WHEN** 已设置 `CC_SWITCH_CONFIG=/a.yaml`,用户执行 `cc-switch --config /b.yaml list` - **THEN** 工具读写 `/b.yaml`,忽略 env 中的路径 #### Scenario: verbose 生效 - **WHEN** 用户执行 `cc-switch -v use foo` - **THEN** stderr 中可见解析到的配置路径、最终 claude 路径、最终 env key 列表(value 不打印)等 trace 信息 ### Requirement: `config` 子命令 `cc-switch config` SHALL 提供 `get` / `set` / `show` 子命令: - `config show`:打印当前配置文件路径与内容(env value 脱敏为 `***`) - `config set claude-path `:设置 `claude_path` - `config set default `:设置 `default_provider` - `config get `:打印单个配置项值(同样脱敏 env value) #### Scenario: show - **WHEN** 用户执行 `cc-switch config show` - **THEN** 第一行打印配置文件绝对路径,之后打印脱敏后的 YAML #### Scenario: set default - **WHEN** 用户执行 `cc-switch config set default foo` - **THEN** 若 `foo` 存在,写入配置并打印确认;若不存在,非零退出 ### Requirement: Add 向导(可选交互) `cc-switch add ` SHALL 支持三种用法: 1. 通过 `--env KEY=VALUE`(可重复)与 `--description "..."` 一次性填入; 2. 通过 `--from-template ` 以内置模板为起点,tty 下逐 key 提示输入 value(显示 hint、回车接受 default);可与 `--description` 共存; 3. 不带任何 `--env` / `--from-template` 时进入交互式自由向导,逐个提示输入 key/value(输入空 key 结束),tty 下可用。 所有方式 MUST 最终落入同一份校验逻辑(至少一个 env key、provider 名合法)。`--from-template` 与 `--env` 可以组合使用——先以模板为骨架,`--env` 再覆盖或补充同名/新 key。value 输入阶段 MUST 允许用户输入 `env:VAR_NAME` 形式的引用(见 provider-config spec 对应 Requirement),工具 MUST NOT 在 `add` 阶段尝试解析该引用、MUST NOT 校验该 VAR 是否存在。 #### Scenario: 一次性命令行 - **WHEN** 用户执行 `cc-switch add foo --env K1=v1 --env K2=v2 --description "desc"` - **THEN** `foo` 被写入,`env` 含两个键,`description=desc` #### Scenario: 交互式向导 - **WHEN** 用户在 tty 下执行 `cc-switch add foo`,依次输入 `K1` / `v1` / `K2` / `v2` / 空键 - **THEN** `foo` 被写入与一次性命令行等价的结构 #### Scenario: 非 tty 且无 --env 无模板 - **WHEN** stdin 非 tty 用户执行 `cc-switch add foo` 且未提供任何 `--env` 或 `--from-template` - **THEN** 工具非零退出,提示在非交互模式下需给出 `--env KEY=VALUE` 或 `--from-template` #### Scenario: 使用模板(tty) - **WHEN** 用户在 tty 下执行 `cc-switch add official --from-template anthropic-official`,依次对每个模板 key 按回车接受 default / 输入新 value - **THEN** `official` 被写入,`env` 包含模板定义的所有 key,value 为用户确认后的值 #### Scenario: 模板 + --env 覆盖 - **WHEN** 用户执行 `cc-switch add x --from-template openrouter --env ANTHROPIC_MODEL=anthropic/claude-opus-4 --non-interactive` - **THEN** `x` 被写入,env 结构为"模板骨架 ∪ --env 覆盖",`ANTHROPIC_MODEL` 取自 `--env`;模板中未被 `--env` 提供的 key 保留模板 default 或占位空字符串(value 为空的 key 在落盘前 MUST 报错要求补齐) #### Scenario: 引用语法直接写入 - **WHEN** 用户执行 `cc-switch add foo --env ANTHROPIC_API_KEY=env:MY_KEY` - **THEN** 配置写入的字面 value 即为 `env:MY_KEY`,不在 add 阶段校验 `MY_KEY` 是否存在 #### Scenario: 未知模板名 - **WHEN** 用户执行 `cc-switch add foo --from-template nonexistent` - **THEN** 工具非零退出,stderr 列出可用模板名并建议 `cc-switch templates list` ### Requirement: `templates` 子命令 `cc-switch templates` SHALL 暴露两个子命令:`list` 与 `show `。模板为内嵌资源,工具 MUST 不访问网络、MUST 不读取用户目录之外的文件。 #### Scenario: 列出模板 - **WHEN** 用户执行 `cc-switch templates list` - **THEN** 输出每个内置模板的名称与一行 description #### Scenario: 查看模板细节 - **WHEN** 用户执行 `cc-switch templates show openrouter` - **THEN** 输出该模板的 description、env key 列表、每个 key 的 hint 与 default(若有) #### Scenario: 查看不存在的模板 - **WHEN** 用户执行 `cc-switch templates show nonexistent` - **THEN** 工具非零退出,提示不存在并建议 `cc-switch templates list` ### Requirement: v1 内嵌模板集 二进制 SHALL 内嵌至少以下模板:`anthropic-official`、`openrouter`、`deepseek`、`moonshot`、`zhipu`、`custom-base`。每个模板 MUST 至少包含一条 env key;模板 key 列表 MUST 只描述键名与 hint/default,MUST NOT 预置任何真实 token 或 API key。 #### Scenario: 种子模板齐全 - **WHEN** 用户执行 `cc-switch templates list` - **THEN** 输出至少包含上述 6 个模板名