@nvae/llmswitch 0.4.0 → 0.5.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 +111 -197
- package/dist/cli.js +7 -1
- package/dist/commands/bridge-cmd.js +2 -2
- package/dist/commands/home-cmd.js +8 -0
- package/dist/commands/launch-cmd.js +18 -0
- package/dist/commands/launch.js +2 -2
- package/dist/commands/prompts.js +110 -65
- package/dist/commands/setup-cmd.js +175 -0
- package/dist/commands/tool.js +18 -33
- package/dist/index.js +0 -0
- package/dist/presets/index.js +10 -1
- package/dist/store/profiles.js +38 -0
- package/dist/utils/detect-format.js +178 -0
- package/dist/utils/fetch-models.js +9 -3
- package/dist/utils/version.js +9 -0
- package/package.json +2 -1
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
llms setup
|
|
64
48
|
```
|
|
65
49
|
|
|
66
|
-
|
|
50
|
+
向导会依次引导:选择工具 → 添加供应商(API 地址、Key)→ 选择模型 → 启用 → 启动工具。已安装的工具会自动识别,只装一个时无需手动选择。
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 跳过工具选择,直接为 Codex 引导
|
|
54
|
+
llms setup --tool codex
|
|
55
|
+
```
|
|
67
56
|
|
|
68
|
-
|
|
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
|
-
|
|
70
|
+
按提示输入:API 地址、API Key、显示名称。
|
|
71
|
+
|
|
72
|
+
自定义上游时,工具会自动探测接口类型(Anthropic / OpenAI Chat / OpenAI Responses),无需手动选择;仅当自动识别失败时才需要手动指定。支持本地 Ollama(`/v1` 兼容或原生 `/api/tags`,无需 API Key),预设中也有「Ollama(本地)」可选。
|
|
77
73
|
|
|
78
|
-
|
|
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
|
-
|
|
76
|
+
```bash
|
|
77
|
+
# 名称与显示名称均可引用
|
|
78
|
+
llms codex use 69pjb
|
|
79
|
+
llms codex use DeepSeek
|
|
80
|
+
llms launch codex --profile deepseek # 归一化模糊匹配(忽略大小写/分隔符)
|
|
81
|
+
```
|
|
85
82
|
|
|
86
|
-
###
|
|
83
|
+
### 3. 选择模型
|
|
87
84
|
|
|
88
85
|
```bash
|
|
86
|
+
# 为当前工具选择模型
|
|
89
87
|
llms codex model
|
|
90
|
-
|
|
88
|
+
|
|
89
|
+
# 为指定配置选择模型
|
|
91
90
|
llms codex model --profile my-provider
|
|
92
91
|
```
|
|
93
92
|
|
|
94
|
-
有 API Key
|
|
93
|
+
有 API Key 时会自动获取上游模型列表,也可手动输入模型 ID。
|
|
95
94
|
|
|
96
|
-
###
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
113
|
+
# 启动 OpenCode
|
|
117
114
|
llms run opencode my-model
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
未指定 `--profile` 时,profile 的选择顺序为:
|
|
121
115
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
116
|
+
# 指定配置启动
|
|
117
|
+
llms launch claude --profile my-provider --model claude-sonnet-4
|
|
118
|
+
```
|
|
125
119
|
|
|
126
|
-
|
|
120
|
+
未指定 `--profile` 时,会自动选择包含目标模型的配置。
|
|
127
121
|
|
|
128
|
-
###
|
|
122
|
+
### 6. 预览执行计划
|
|
129
123
|
|
|
130
124
|
```bash
|
|
131
|
-
#
|
|
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
|
-
|
|
132
|
+
### 7. 管理本地 Bridge
|
|
145
133
|
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
232
|
-
| `llms <tool
|
|
233
|
-
| `llms <tool>
|
|
234
|
-
| `llms <tool>
|
|
235
|
-
| `llms
|
|
236
|
-
| `llms
|
|
237
|
-
| `llms
|
|
238
|
-
| `llms
|
|
239
|
-
| `llms bridge
|
|
240
|
-
| `llms path` |
|
|
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
|
|
258
|
-
|
|
|
259
|
-
|
|
|
260
|
-
|
|
|
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
|
-
|
|
186
|
+
查看实际路径:
|
|
265
187
|
|
|
266
188
|
```bash
|
|
267
189
|
llms path
|
|
268
190
|
```
|
|
269
191
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
修改或禁用配置前,已存在的目标文件会备份到对应工具目录的 `backups/` 中,文件名包含时间戳。首次创建的文件没有可备份内容。写入过程使用同目录临时文件再重命名,降低配置只写入一部分的风险。
|
|
192
|
+
---
|
|
273
193
|
|
|
274
|
-
|
|
194
|
+
## 指定可执行文件路径
|
|
275
195
|
|
|
276
|
-
|
|
196
|
+
如果工具不在 `PATH` 中,可通过环境变量指定:
|
|
277
197
|
|
|
278
|
-
|
|
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
|
-
|
|
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
|
-
|
|
208
|
+
遇到问题或有改进想法,欢迎反馈:
|
|
293
209
|
|
|
294
|
-
|
|
210
|
+
- 邮箱:rchdg50@gmail.com
|
|
211
|
+
- GitHub Issues:https://github.com/rchdg/llmswitch/issues
|
|
295
212
|
|
|
296
|
-
|
|
297
|
-
- 可复现的请求和响应行为;
|
|
298
|
-
- 普通响应与流式响应的测试;
|
|
299
|
-
- 工具调用、工具结果和错误响应等边界场景。
|
|
213
|
+
---
|
|
300
214
|
|
|
301
215
|
## License
|
|
302
216
|
|
|
303
|
-
|
|
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(
|
|
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,
|
|
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
|
-
?
|
|
107
|
+
? resolveProfileOrThrow(tool, opts.profile)
|
|
108
108
|
: getActiveProfile(tool);
|
|
109
109
|
if (!profile) {
|
|
110
110
|
throw new Error(`没有可用的 ${tool} profile`);
|
|
@@ -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("-")) {
|
package/dist/commands/launch.js
CHANGED
|
@@ -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 =
|
|
78
|
+
profile = resolveProfileOrThrow(tool, opts.profile);
|
|
79
79
|
}
|
|
80
80
|
if (!profile && modelQuery) {
|
|
81
81
|
profile = findProfileForModel(profiles, modelQuery);
|