@nvae/llmswitch 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,49 +1,16 @@
1
1
  <p align="center">
2
- <img src="./assets/readme/hero.svg" width="100%" alt="llmswitch:为 Claude Code、Codex 和 OpenCode 切换供应商与模型,并按需转换 API 协议">
2
+ <img src="./assets/readme/hero.svg" width="100%" alt="llmswitch:为 Claude Code、Codex 和 OpenCode 切换供应商与模型工具">
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <strong>用一套 CLI 管理 Claude Code、Codex 与 OpenCode 的供应商、模型、代理和启动流程。</strong>
6
+ <strong>一个 CLI 工具管理 Claude Code、Codex 与 OpenCode 的模型切换。</strong>
7
7
  </p>
8
8
 
9
9
  <p align="center">
10
10
  <strong>简体中文</strong> · <a href="./README_EN.md">English</a>
11
11
  </p>
12
12
 
13
- <p align="center">
14
- <a href="#快速开始">快速开始</a> ·
15
- <a href="#兼容性">兼容性</a> ·
16
- <a href="#命令参考">命令参考</a> ·
17
- <a href="#配置与安全">配置与安全</a> ·
18
- <a href="./LICENSE">MIT License</a>
19
- </p>
20
-
21
- ## llmswitch 是什么?
22
-
23
- `llmswitch` 是一个面向 **Claude Code、Codex 和 OpenCode** 的本地命令行工具。它使用彼此独立的 profile 管理每种工具的 API 地址、密钥、模型和代理,并将选中的 profile 合并写入目标工具的原生配置。
24
-
25
- 原生协议可以直接连接;当 Claude Code 或 Codex 需要访问仅兼容 OpenAI Chat Completions 的上游时,`llmswitch` 会自动启动一个仅监听本机的 bridge,完成常用请求、响应和 SSE 事件的协议转换。
26
-
27
- ```text
28
- ┌─ Claude Code ── Anthropic Messages
29
- provider profiles ─ llms ──┼─ Codex ──────── OpenAI Responses
30
- └─ OpenCode ────── 原生 provider
31
-
32
- 协议不兼容时启用 local bridge
33
-
34
- OpenAI Chat API
35
- ```
36
-
37
- ## 核心能力
38
-
39
- - **统一操作三种工具**:使用一致的命令管理 Claude Code、Codex 和 OpenCode。
40
- - **配置相互隔离**:每种工具独立保存 profile、默认项和当前启用项,不会混用供应商配置。
41
- - **一条命令切换并启动**:选择 profile 和模型、更新配置,然后启动目标 CLI。
42
- - **按需转换协议**:在 Messages / Responses 与 Chat Completions 之间转换常用文本、工具调用、工具结果及流式事件。
43
- - **保留现有配置**:只合并受管理字段,并在修改已有目标文件前创建时间戳备份。
44
- - **支持模型发现**:尝试从上游 `/models` 获取模型;失败时可手动输入模型 ID。
45
- - **支持上游代理**:每个 profile 可独立配置 HTTP、HTTPS 或 ALL proxy。
46
- - **便于脚本集成**:查询、切换、启动计划和 bridge 状态等命令支持 JSON 输出。
13
+ ---
47
14
 
48
15
  ## 快速开始
49
16
 
@@ -57,247 +24,194 @@ npm install -g @nvae/llmswitch
57
24
  bun add -g @nvae/llmswitch
58
25
  ```
59
26
 
60
- 安装后可直接使用 `llms`(或 `llm-switch`)命令。本地开发可用:
27
+ 安装后使用 `llms`(或 `llm-switch`、`llmswitch`)命令。直接输入 `llms` 会自动选择工具并启动;`llms <tool>` 同理。
28
+
29
+ ### 0. 一键启动(连贯流程)
30
+
31
+ ```bash
32
+ # 选择工具并启动;未配置供应商时会自动引导:添加供应商 → 选模型 → 启用 → 启动
33
+ llms
34
+
35
+ # 直接启动 Codex(等价于 llms launch codex)
36
+ llms codex
37
+ ```
38
+
39
+ `llms <tool>` 会根据状态自动衔接:
40
+
41
+ - 已配置供应商 → 直接启动(使用当前启用 / 默认供应商及其默认模型)
42
+ - 未配置 → 自动引导添加供应商、选择模型,静默启用后自动启动
43
+
44
+ ### 1. 显式引导配置
61
45
 
62
46
  ```bash
63
- bun run src/index.ts --help
47
+ llms setup
64
48
  ```
65
49
 
66
- ### 1. 添加供应商
50
+ 向导会依次引导:选择工具 添加供应商(API 地址、Key)→ 选择模型 → 启用 → 启动工具。已安装的工具会自动识别,只装一个时无需手动选择。
51
+
52
+ ```bash
53
+ # 跳过工具选择,直接为 Codex 引导
54
+ llms setup --tool codex
55
+ ```
67
56
 
68
- 分别为需要使用的工具创建 profile:
57
+ ### 2. 添加供应商配置
69
58
 
70
59
  ```bash
60
+ # 为 Claude Code 添加配置
71
61
  llms claude provider
62
+
63
+ # 为 Codex 添加配置
72
64
  llms codex provider
65
+
66
+ # 为 OpenCode 添加配置
73
67
  llms opencode provider
74
68
  ```
75
69
 
76
- 交互界面支持添加、查看、编辑、删除、设为默认以及启用/禁用 profile。可从以下模板开始:
70
+ 按提示输入:API 地址、API Key、显示名称。
71
+
72
+ 自定义上游时,工具会自动探测接口类型(Anthropic / OpenAI Chat / OpenAI Responses),无需手动选择;仅当自动识别失败时才需要手动指定。支持本地 Ollama(`/v1` 兼容或原生 `/api/tags`,无需 API Key),预设中也有「Ollama(本地)」可选。
77
73
 
78
- | 模板 | 默认 API 格式 | 默认 Base URL |
79
- | --- | --- | --- |
80
- | 自定义(OpenAI 兼容) | Chat Completions | 手动填写 |
81
- | OpenAI | Responses | `https://api.openai.com/v1` |
82
- | Anthropic | Messages | `https://api.anthropic.com` |
74
+ Profile 名称会自动生成(5 位随机小写字母数字,如 `69pjb`),所有命令都可通过名称或显示名称引用供应商:
83
75
 
84
- 其他供应商可通过兼容的 OpenAI 或 Anthropic API 地址接入;这不代表所有厂商、字段或扩展协议都已完整适配。
76
+ ```bash
77
+ # 名称与显示名称均可引用
78
+ llms codex use 69pjb
79
+ llms codex use DeepSeek
80
+ llms launch codex --profile deepseek # 归一化模糊匹配(忽略大小写/分隔符)
81
+ ```
85
82
 
86
- ### 2. 选择模型
83
+ ### 3. 选择模型
87
84
 
88
85
  ```bash
86
+ # 为当前工具选择模型
89
87
  llms codex model
90
- # 为指定 profile 选择模型
88
+
89
+ # 为指定配置选择模型
91
90
  llms codex model --profile my-provider
92
91
  ```
93
92
 
94
- 有 API Key 时,`llmswitch` 会尝试获取上游模型列表。使用空格多选、回车确认,然后指定默认模型;请求失败或上游没有兼容的模型端点时,可手动输入模型 ID。
93
+ 有 API Key 时会自动获取上游模型列表,也可手动输入模型 ID。
95
94
 
96
- ### 3. 启用 profile
95
+ ### 4. 启用配置
97
96
 
98
97
  ```bash
99
98
  llms codex use my-provider
99
+
100
+ # 查看当前配置状态
100
101
  llms codex current
101
102
  ```
102
103
 
103
- `use` 会备份现有目标配置、合并新的供应商设置,并将该 profile 记录为当前启用项。
104
-
105
- ### 4. 切换模型并启动
104
+ ### 5. 启动工具
106
105
 
107
106
  ```bash
107
+ # 启动 Codex 并使用指定模型
108
108
  llms launch codex gpt-4.1
109
109
 
110
- # 等价写法
111
- llms launch codex --model gpt-4.1
112
-
113
- # 显式指定 profile
114
- llms launch claude --profile my-provider --model claude-sonnet-4
110
+ # 启动 Claude Code
111
+ llms launch claude --model claude-sonnet-4-20250514
115
112
 
116
- # run 是 launch 的别名
113
+ # 启动 OpenCode
117
114
  llms run opencode my-model
118
- ```
119
-
120
- 未指定 `--profile` 时,profile 的选择顺序为:
121
115
 
122
- 1. 模型列表中包含目标模型的 profile;
123
- 2. 当前启用的 profile
124
- 3. 默认 profile。
116
+ # 指定配置启动
117
+ llms launch claude --profile my-provider --model claude-sonnet-4
118
+ ```
125
119
 
126
- 模型匹配会忽略大小写和常见分隔符。例如,`gpt4.1` 可匹配 `gpt-4.1`。如果输入的模型不在 profile 中,它会被加入模型列表并设为该 profile 的默认模型。
120
+ 未指定 `--profile` 时,会自动选择包含目标模型的配置。
127
121
 
128
- ### 5. 预览或只写配置
122
+ ### 6. 预览执行计划
129
123
 
130
124
  ```bash
131
- # 只查看执行计划,不修改配置、不启动 CLI
125
+ # 只查看计划,不实际启动
132
126
  llms launch codex gpt-4.1 --dry-run
133
127
 
134
- # 输出 JSON 计划
128
+ # 输出 JSON 格式
135
129
  llms launch codex gpt-4.1 --dry-run --json
136
-
137
- # 写入配置,但不启动目标 CLI
138
- llms launch opencode my-model --print-only
139
-
140
- # 将额外参数透传给目标 CLI
141
- llms launch opencode my-model -- --resume
142
130
  ```
143
131
 
144
- 如目标 CLI 不在 `PATH` 中,可指定可执行文件:
132
+ ### 7. 管理本地 Bridge
145
133
 
146
- ```bash
147
- export CLAUDE_BIN=/path/to/claude
148
- export CODEX_BIN=/path/to/codex
149
- export OPENCODE_BIN=/path/to/opencode
150
- ```
151
-
152
- ## 兼容性
153
-
154
- | 目标工具 | Anthropic Messages | OpenAI Chat Completions | OpenAI Responses |
155
- | --- | :---: | :---: | :---: |
156
- | Claude Code | 直连 | 通过 bridge | 不支持 |
157
- | Codex | 不支持 | 通过 bridge | 直连 |
158
- | OpenCode | 直连 | 直连 | 直连 |
159
-
160
- OpenCode 会根据 API 格式写入对应的 AI SDK provider:
161
-
162
- - Anthropic:`@ai-sdk/anthropic`
163
- - OpenAI Responses:`@ai-sdk/openai`
164
- - OpenAI Chat:`@ai-sdk/openai-compatible`
165
-
166
- OpenAI 格式的 Base URL 会规范为末尾恰好一个 `/v1`;Anthropic 格式只移除末尾多余的 `/`。如果某个特殊网关不使用 `/v1` 路径,它可能无法直接使用当前的 OpenAI 格式 profile。
167
-
168
- ## 本地 bridge
169
-
170
- Claude Code 和 Codex 共用一个 bridge 进程,但两侧上游配置相互隔离,可以同时连接不同供应商。默认监听 `127.0.0.1:17890`:
171
-
172
- ```text
173
- Claude Code POST /v1/messages ─┐
174
- ├─ local bridge ─→ POST /v1/chat/completions
175
- Codex POST /v1/responses ─┘
176
- ```
177
-
178
- 当启用需要 bridge 的 profile 时,进程会自动启动;切回原生格式后会清理对应侧上游,两侧均不再使用 bridge 时会停止进程。
179
-
180
- 常用管理命令:
134
+ Claude Code 或 Codex 使用非原生协议时,会自动启动本地 Bridge。
181
135
 
182
136
  ```bash
137
+ # 查看 Bridge 状态
183
138
  llms bridge status
184
- llms bridge status --json
139
+
140
+ # 手动启动/停止
185
141
  llms bridge start
186
142
  llms bridge stop
143
+
144
+ # 重载配置
187
145
  llms bridge reload claude
188
146
  llms bridge reload codex --profile my-provider
189
-
190
- # 前台运行,便于排查问题
191
- llms bridge serve
192
- ```
193
-
194
- Bridge 提供以下本地端点:
195
-
196
- | 方法 | 路径 | 用途 |
197
- | --- | --- | --- |
198
- | `GET` | `/health`、`/v1/health` | 查看服务及两侧上游状态 |
199
- | `GET` | `/models`、`/v1/models` | 合并已配置上游的模型列表 |
200
- | `POST` | `/messages`、`/v1/messages` | Anthropic Messages → Chat |
201
- | `POST` | `/responses`、`/v1/responses` | OpenAI Responses → Chat/Completions |
202
-
203
- ```bash
204
- curl http://127.0.0.1:17890/health
205
147
  ```
206
148
 
207
- > [!WARNING]
208
- > Bridge 本身没有身份认证、限流或请求体大小限制。请保持默认回环地址,不要将其作为公网或多租户生产网关使用。
149
+ ---
209
150
 
210
- ## 上游代理
211
-
212
- 添加或编辑 profile 时可以配置:
213
-
214
- - `HTTP_PROXY`
215
- - `HTTPS_PROXY`
216
- - `ALL_PROXY`,例如 `socks5h://127.0.0.1:1080`
217
-
218
- ```bash
219
- llms claude provider
220
- # 选择“添加”或“编辑”,然后填写代理字段
221
- ```
222
-
223
- 原生直连 profile 会将适用的代理变量合并到目标工具配置;bridge profile 则由 bridge 使用对应上游的代理设置。实际代理行为仍取决于目标 CLI、Node.js 网络环境和代理协议,建议在真实环境中验证。
224
-
225
- ## 命令参考
226
-
227
- 统一形式为 `llms <tool> <action>`,其中 `<tool>` 为 `claude`、`codex` 或 `opencode`。
151
+ ## 常用命令
228
152
 
229
153
  | 命令 | 说明 |
230
154
  | --- | --- |
231
- | `llms <tool> provider` | 交互式管理 profile;`--json` 可查看列表 |
232
- | `llms <tool> use [name]` | 启用已有 profile;支持 `--json` |
233
- | `llms <tool> current` | 查看默认项和当前启用项;API Key 会脱敏 |
234
- | `llms <tool> model [--profile name]` | 获取、选择并保存模型;支持 `--json` |
235
- | `llms launch\|run <tool> [model]` | 切换 profile/模型并启动目标 CLI |
236
- | `llms bridge status` | 查看 bridge 进程和两侧上游状态 |
237
- | `llms bridge start\|stop` | 手动启动或停止 bridge |
238
- | `llms bridge reload [tool]` | 从当前或指定 profile 刷新 bridge 上游 |
239
- | `llms bridge serve` | 在前台运行 bridge |
240
- | `llms path` | 显示 llmswitch 本地数据目录 |
241
-
242
- 查看完整参数:
155
+ | `llms` | 选择工具并启动(未配置时自动引导) |
156
+ | `llms <tool>` | 直接启动工具(未配置时自动引导完整链路) |
157
+ | `llms setup [--tool <tool>]` | 显式引导配置(添加供应商 模型 → 启用 → 启动) |
158
+ | `llms <tool> provider` | 管理供应商配置(添加、查看、编辑、删除) |
159
+ | `llms <tool> use [name]` | 启用指定配置 |
160
+ | `llms <tool> current` | 查看当前配置 |
161
+ | `llms <tool> model` | 选择模型 |
162
+ | `llms launch/run <tool> [model]` | 启动工具 |
163
+ | `llms bridge status` | 查看 Bridge 状态 |
164
+ | `llms path` | 查看数据目录 |
165
+
166
+ `<tool>` 可选 `claude`、`codex`、`opencode`。
167
+
168
+ 查看完整帮助:
243
169
 
244
170
  ```bash
245
171
  llms --help
246
172
  llms launch --help
247
- llms bridge --help
248
- llms codex --help
249
173
  ```
250
174
 
251
- ## 配置与安全
175
+ ---
252
176
 
253
- ### 配置位置
177
+ ## 配置位置
254
178
 
255
- | 数据 | 默认位置 | 覆盖方式 |
256
- | --- | --- | --- |
257
- | llmswitch(macOS/Linux) | `~/.config/llm-switch/` | `LLM_SWITCH_HOME` 或 `XDG_CONFIG_HOME` |
258
- | llmswitch(Windows) | `%APPDATA%\llm-switch\` | `LLM_SWITCH_HOME` |
259
- | Claude Code | `~/.claude/settings.json` | `CLAUDE_CONFIG_DIR` |
260
- | Codex | `~/.codex/config.toml`、`~/.codex/.env` | `CODEX_HOME` |
261
- | OpenCode 配置 | `~/.config/opencode/opencode.json` | `OPENCODE_CONFIG_DIR` 或 `XDG_CONFIG_HOME` |
262
- | OpenCode认证 | `~/.local/share/opencode/auth.json` | `OPENCODE_DATA_DIR` 或 `XDG_DATA_HOME` |
179
+ | 数据 | 位置 |
180
+ | --- | --- |
181
+ | llmswitch 配置 | `~/.config/llm-switch/` |
182
+ | Claude Code | `~/.claude/settings.json` |
183
+ | Codex | `~/.codex/config.toml` |
184
+ | OpenCode | `~/.config/opencode/opencode.json` |
263
185
 
264
- 为兼容当前程序和已有配置,llmswitch 的默认数据目录仍使用 `llm-switch` 目录名。Windows 下,OpenCode 认证默认位于 `%LOCALAPPDATA%\opencode\auth.json`。运行以下命令可查看当前环境中的实际数据目录:
186
+ 查看实际路径:
265
187
 
266
188
  ```bash
267
189
  llms path
268
190
  ```
269
191
 
270
- ### 备份策略
271
-
272
- 修改或禁用配置前,已存在的目标文件会备份到对应工具目录的 `backups/` 中,文件名包含时间戳。首次创建的文件没有可备份内容。写入过程使用同目录临时文件再重命名,降低配置只写入一部分的风险。
192
+ ---
273
193
 
274
- ### API Key
194
+ ## 指定可执行文件路径
275
195
 
276
- API Key 会以**明文**保存在本机 profile、目标工具认证文件或 bridge 上游配置中。llmswitch 会尝试将新写文件权限设置为 `0600`,但不会使用系统钥匙串,也不会加密密钥。
196
+ 如果工具不在 `PATH` 中,可通过环境变量指定:
277
197
 
278
- 请保护配置目录,不要将其中内容提交到版本控制,不要在不受信任的设备上保存长期密钥。JSON 查询结果会对 API Key 脱敏,但磁盘文件仍包含真实值。
279
-
280
- ## 已知限制
198
+ ```bash
199
+ export CLAUDE_BIN=/path/to/claude
200
+ export CODEX_BIN=/path/to/codex
201
+ export OPENCODE_BIN=/path/to/opencode
202
+ ```
281
203
 
282
- - Bridge 是面向本地使用的协议适配器,不保证 OpenAI 与 Anthropic 协议 100% 等价。
283
- - Responses 中的加密 reasoning 内容不会转发;未识别的工具类型可能被忽略。
284
- - Anthropic 转换主要覆盖文本、工具调用和工具结果,图片等非文本内容可能无法完整保留。
285
- - Codex 的 Completions 模式是降级路径:对话会被展平为 prompt,复杂工具调用或 agent 流程可能表现不佳。
286
- - Codex Chat 模式仅在上游返回 HTTP `404` 或 `405` 时回退到 Completions;认证、网络及其他错误不会触发回退。
287
- - 请求中的托管 `web_search` 会转换为普通 Chat function;若上游自行搜索并以完整 `<web_search>...</web_search>` 块返回结果,bridge 会将其恢复为 Responses `web_search_call`,并保留去标签后的结果正文。
288
- - 自动发现模型需要 API Key 和兼容的 `/models` 端点;失败时需手动输入模型。
204
+ ---
289
205
 
290
- ## 参与贡献
206
+ ## 反馈与建议
291
207
 
292
- 欢迎通过 Issue 报告兼容性问题,或提交 Pull Request 改进配置适配、协议转换、测试和文档。
208
+ 遇到问题或有改进想法,欢迎反馈:
293
209
 
294
- 提交新供应商或协议适配时,建议同时提供:
210
+ - 邮箱:rchdg50@gmail.com
211
+ - GitHub Issues:https://github.com/rchdg/llmswitch/issues
295
212
 
296
- - 目标工具与 API 格式;
297
- - 可复现的请求和响应行为;
298
- - 普通响应与流式响应的测试;
299
- - 工具调用、工具结果和错误响应等边界场景。
213
+ ---
300
214
 
301
215
  ## License
302
216
 
303
- 本项目基于 [MIT License](./LICENSE) 开源。
217
+ MIT License - 查看 [LICENSE](./LICENSE)
package/dist/cli.js CHANGED
@@ -3,13 +3,16 @@ import { TOOLS } from "./types.js";
3
3
  import { registerToolCommand } from "./commands/tool.js";
4
4
  import { registerLaunchCommand } from "./commands/launch-cmd.js";
5
5
  import { registerBridgeCommand } from "./commands/bridge-cmd.js";
6
+ import { registerSetupCommand } from "./commands/setup-cmd.js";
7
+ import { registerHomeCommand } from "./commands/home-cmd.js";
6
8
  import { getAppConfigRoot } from "./utils/paths.js";
9
+ import { getVersion } from "./utils/version.js";
7
10
  export function createProgram() {
8
11
  const program = new Command();
9
12
  program
10
13
  .name("llms")
11
14
  .description("为 Claude Code / Codex / OpenCode 切换供应商、模型与上游代理")
12
- .version("0.2.0")
15
+ .version(getVersion())
13
16
  .option("--json", "部分命令支持 JSON 输出(见子命令)");
14
17
  program
15
18
  .command("path")
@@ -19,12 +22,15 @@ export function createProgram() {
19
22
  });
20
23
  registerLaunchCommand(program);
21
24
  registerBridgeCommand(program);
25
+ registerSetupCommand(program);
22
26
  for (const tool of TOOLS) {
23
27
  registerToolCommand(program, tool);
24
28
  }
29
+ registerHomeCommand(program);
25
30
  program.configureOutput({
26
31
  writeErr: (str) => process.stderr.write(str),
27
32
  });
33
+ program.addHelpText("afterAll", "\n反馈与建议:rchdg50@gmail.com\nGitHub Issues:https://github.com/rchdg/llmswitch/issues\n");
28
34
  return program;
29
35
  }
30
36
  export async function run(argv = process.argv) {
@@ -2,7 +2,7 @@ import { ensureBridgeForProfile, isBridgeAlive, isPidRunning, readPid, runBridge
2
2
  import { bridgeBaseUrl, bridgeRootUrl, readBridgeState, } from "../bridge/state.js";
3
3
  import { parseBridgePort } from "../bridge/runtime.js";
4
4
  import { DEFAULT_BRIDGE_HOST, DEFAULT_BRIDGE_PORT } from "../bridge/types.js";
5
- import { getActiveProfile, requireProfile } from "../store/profiles.js";
5
+ import { getActiveProfile, resolveProfileOrThrow } from "../store/profiles.js";
6
6
  import { isTool } from "../types.js";
7
7
  export function registerBridgeCommand(program) {
8
8
  const bridge = program
@@ -104,7 +104,7 @@ export function registerBridgeCommand(program) {
104
104
  }
105
105
  const tool = toolName;
106
106
  const profile = opts.profile
107
- ? requireProfile(tool, opts.profile)
107
+ ? resolveProfileOrThrow(tool, opts.profile)
108
108
  : getActiveProfile(tool);
109
109
  if (!profile) {
110
110
  throw new Error(`没有可用的 ${tool} profile`);
@@ -0,0 +1,8 @@
1
+ import { pickSetupTool, runToolFlow } from "./setup-cmd.js";
2
+ /** 无子命令时:选择工具 → 连贯启动(未配置则自动引导)。 */
3
+ export function registerHomeCommand(program) {
4
+ program.action(async () => {
5
+ const tool = await pickSetupTool();
6
+ await runToolFlow(tool);
7
+ });
8
+ }
@@ -1,5 +1,8 @@
1
+ import * as p from "@clack/prompts";
1
2
  import { isTool, TOOLS } from "../types.js";
3
+ import { listProfiles } from "../store/profiles.js";
2
4
  import { launchTool, resolveBinary, resolveLaunchTarget, } from "./launch.js";
5
+ import { runSetupWizard } from "./setup-cmd.js";
3
6
  /**
4
7
  * ollama-like quick start:
5
8
  * llms launch codex --model gpt-4.1
@@ -23,6 +26,21 @@ export function registerLaunchCommand(program) {
23
26
  if (!isTool(toolArg)) {
24
27
  throw new Error(`未知工具「${toolArg}」。可选:${TOOLS.join(", ")}`);
25
28
  }
29
+ if (listProfiles(toolArg).length === 0 &&
30
+ process.stdin.isTTY &&
31
+ !opts.dryRun) {
32
+ const setup = await p.confirm({
33
+ message: `${toolArg} 尚未配置供应商,是否进入引导?`,
34
+ initialValue: true,
35
+ });
36
+ if (p.isCancel(setup)) {
37
+ p.cancel("已取消");
38
+ process.exit(0);
39
+ }
40
+ if (setup) {
41
+ await runSetupWizard(toolArg);
42
+ }
43
+ }
26
44
  let model = opts.model?.trim() || undefined;
27
45
  const queue = [...parts];
28
46
  if (!model && queue[0] && !queue[0].startsWith("-")) {
@@ -3,7 +3,7 @@ import { existsSync } from "node:fs";
3
3
  import { delimiter, join } from "node:path";
4
4
  import { isTool } from "../types.js";
5
5
  import { applyProfile } from "../adapters/index.js";
6
- import { getActiveProfile, getDefaultProfile, listProfiles, requireProfile, saveProfile, ensureDefaultProvider, } from "../store/profiles.js";
6
+ import { getActiveProfile, getDefaultProfile, listProfiles, requireProfile, resolveProfileOrThrow, saveProfile, ensureDefaultProvider, } from "../store/profiles.js";
7
7
  const BINARY = {
8
8
  claude: "claude",
9
9
  codex: "codex",
@@ -75,7 +75,7 @@ export function resolveLaunchTarget(tool, opts) {
75
75
  const modelQuery = opts.model?.trim() || undefined;
76
76
  let profile = null;
77
77
  if (opts.profile) {
78
- profile = requireProfile(tool, opts.profile);
78
+ profile = resolveProfileOrThrow(tool, opts.profile);
79
79
  }
80
80
  if (!profile && modelQuery) {
81
81
  profile = findProfileForModel(profiles, modelQuery);