@done-coding/cli-cc-switch 0.1.1 → 0.2.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 +20 -1
- package/es/cli.mjs +1 -1
- package/es/index.mjs +1 -1
- package/es/main-BC_Ug-V2.js +615 -0
- package/package.json +2 -2
- package/types/index.d.ts +41 -3
- package/es/main-B-J--Kd8.js +0 -337
package/README.md
CHANGED
|
@@ -28,10 +28,29 @@ cc-router [command] [options] # 兼容
|
|
|
28
28
|
| ---- | ---- |
|
|
29
29
|
| `--meta-profile=<name>` | 显式指定 profile 启动 |
|
|
30
30
|
| `--meta-pick` | 终端交互选择 profile 启动(需 TTY) |
|
|
31
|
+
| `--meta-generate` | 从 provider.json + model.json 重建 profile.json |
|
|
32
|
+
| `--meta-apiKey=<key>` | 更新指定提供商 apiKey(交互选提供商或 `--meta-provider`,自动重建) |
|
|
33
|
+
| `--meta-model-name=<name>` | 添加模型(交互选提供商或 `--meta-provider`,自动重建;id=去 `[1m]`、name+`[1m]`) |
|
|
34
|
+
| `--meta-provider=<id>` | 显式指定提供商(供 apiKey / model-name 跳过交互选择,非独立动作) |
|
|
35
|
+
| `--meta-provider-list` | 输出提供商列表(`id(name)`,不含 apiKey) |
|
|
36
|
+
| `--meta-model-list` | 输出模型列表(`name(provider)`,同模型多 provider 各一行) |
|
|
31
37
|
| `--meta-help` | 显示自身帮助 |
|
|
32
38
|
| `--meta-version` | 显示自身版本 |
|
|
33
39
|
|
|
34
|
-
优先级:`--meta-help` > `--meta-version` > `--meta-pick` > `--meta-profile=`。
|
|
40
|
+
优先级:`--meta-help` > `--meta-version` > `--meta-generate` > `--meta-model-list` > `--meta-provider-list` > `--meta-model-name` > `--meta-apiKey` > `--meta-pick` > `--meta-profile=`。
|
|
41
|
+
|
|
42
|
+
`--meta-apiKey` 与 `--meta-model-name` 互斥、均不得与 `--meta-generate` 同用;`--meta-provider` 仅随二者使用。
|
|
43
|
+
|
|
44
|
+
## 配置源(provider / model 分层)
|
|
45
|
+
|
|
46
|
+
profile.json 由两个 DRY 源经 `--meta-generate` 生成(启动不自动生成,保速度;profile 缺失或默认 profile 悬空时提示手动运行):
|
|
47
|
+
|
|
48
|
+
- `~/.done-coding/cc-switch/provider.json` — 服务商层:`{ providers: { id: { name, url, apiKey, envExtraParams? } } }`
|
|
49
|
+
- `~/.done-coding/cc-switch/model.json` — 模型层:`{ defaultProfile, models: [{ provider, id, name, envExtraParams? }] }`
|
|
50
|
+
|
|
51
|
+
profile 名 = `${provider}-${id}`;每个 profile 的 env 按 `{...通用, ...providerEnvExtraParams, ...modelEnvExtraParams}` 合并(通用 = provider.url/apiKey + model.name 推导的 BASE_URL/AUTH_TOKEN/MODEL/四档/SUBAGENT)。例:pro 档让 haiku 用 flash,在该 model 的 `envExtraParams` 设 `ANTHROPIC_DEFAULT_HAIKU_MODEL`。
|
|
52
|
+
|
|
53
|
+
运行 `dc-cc-switch --meta-generate` 后生成 profile.json(保持原格式、chmod 600)。
|
|
35
54
|
|
|
36
55
|
<!-- repo-map:start -->
|
|
37
56
|
|
package/es/cli.mjs
CHANGED
package/es/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { D as e, P as E, b as i, c as n, f as r, h as l, i as o, m as c, p as d, s as m } from "./main-
|
|
2
|
+
import { D as e, P as E, b as i, c as n, f as r, h as l, i as o, m as c, p as d, s as m } from "./main-BC_Ug-V2.js";
|
|
3
3
|
export {
|
|
4
4
|
e as DEEPSEEK_TEMPLATE,
|
|
5
5
|
E as PROFILE_PATH,
|
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn as se } from "node:child_process";
|
|
3
|
+
import b from "node:os";
|
|
4
|
+
import P from "node:path";
|
|
5
|
+
import { resolveHandlerContext as J, xPrompts as W } from "@done-coding/cli-utils";
|
|
6
|
+
import m from "node:fs";
|
|
7
|
+
import ne from "node:readline";
|
|
8
|
+
const l = P.join(
|
|
9
|
+
b.homedir(),
|
|
10
|
+
".done-coding",
|
|
11
|
+
"cc-switch",
|
|
12
|
+
"profile.json"
|
|
13
|
+
), p = P.join(
|
|
14
|
+
b.homedir(),
|
|
15
|
+
".done-coding",
|
|
16
|
+
"cc-switch",
|
|
17
|
+
"provider.json"
|
|
18
|
+
), d = P.join(
|
|
19
|
+
b.homedir(),
|
|
20
|
+
".done-coding",
|
|
21
|
+
"cc-switch",
|
|
22
|
+
"model.json"
|
|
23
|
+
), g = "--meta-profile=", j = "--meta-pick", A = "--meta-generate", v = "--meta-apiKey=", $ = "--meta-model-name=", h = "--meta-provider=", I = "--meta-provider-list", L = "--meta-model-list", N = "--meta-help", R = "--meta-version", F = {
|
|
24
|
+
run: 0,
|
|
25
|
+
profile: 1,
|
|
26
|
+
pick: 2,
|
|
27
|
+
setkey: 3,
|
|
28
|
+
addmodel: 4,
|
|
29
|
+
providerlist: 5,
|
|
30
|
+
modellist: 6,
|
|
31
|
+
generate: 7,
|
|
32
|
+
version: 8,
|
|
33
|
+
help: 9
|
|
34
|
+
}, w = (e, r) => F[r] > F[e] ? r : e, ie = (e) => e.startsWith("--meta-") && e !== j && e !== A && e !== N && e !== R && e !== I && e !== L && !e.startsWith(g) && !e.startsWith(v) && !e.startsWith($) && !e.startsWith(h), ce = () => {
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
[
|
|
37
|
+
"dc-cc-switch(cc-router)— claude-code 模型路由透传",
|
|
38
|
+
"",
|
|
39
|
+
"用法:",
|
|
40
|
+
" dc-cc-switch [meta 选项] <claude 参数...>",
|
|
41
|
+
"",
|
|
42
|
+
"meta 选项(cc-switch 自身命令面,不透传给 claude):",
|
|
43
|
+
` ${g}<name> 显式指定 profile 启动`,
|
|
44
|
+
` ${j} 终端交互选择 profile 启动`,
|
|
45
|
+
` ${A} 从 provider.json + model.json 重新生成 profile.json`,
|
|
46
|
+
` ${v}<key> 更新指定提供商 apiKey(自动重建)`,
|
|
47
|
+
` ${$}<name> 添加模型(自动重建)`,
|
|
48
|
+
` ${h}<id> 显式指定 provider(供 apiKey/model-name 跳过选择)`,
|
|
49
|
+
` ${I} 输出提供商列表(id + name)`,
|
|
50
|
+
` ${L} 输出模型列表(name + 所属 provider)`,
|
|
51
|
+
` ${N} 显示本帮助`,
|
|
52
|
+
` ${R} 显示版本`,
|
|
53
|
+
"",
|
|
54
|
+
"其余所有参数原样透传给 claude。",
|
|
55
|
+
`配置: ${l}`,
|
|
56
|
+
"源: provider.json / model.json(--meta-generate 消费)",
|
|
57
|
+
""
|
|
58
|
+
].join(`
|
|
59
|
+
`)
|
|
60
|
+
);
|
|
61
|
+
}, ae = (e) => {
|
|
62
|
+
process.stdout.write(`${e}
|
|
63
|
+
`);
|
|
64
|
+
}, le = async (e) => {
|
|
65
|
+
const r = Object.keys(e.profiles);
|
|
66
|
+
r.length === 0 && (process.stderr.write(
|
|
67
|
+
`配置中没有可用 profile,请先编辑 ${l}。
|
|
68
|
+
`
|
|
69
|
+
), process.exit(1)), J().interactive || (process.stderr.write(
|
|
70
|
+
`--meta-pick 需要交互式终端,请改用 --meta-profile=<name> 显式指定。
|
|
71
|
+
`
|
|
72
|
+
), process.exit(1));
|
|
73
|
+
const { profile: t } = await W({
|
|
74
|
+
type: "select",
|
|
75
|
+
name: "profile",
|
|
76
|
+
message: "选择以哪个配置启动 claude",
|
|
77
|
+
choices: r.map((o) => ({ title: o, value: o }))
|
|
78
|
+
});
|
|
79
|
+
return t;
|
|
80
|
+
}, de = async (e) => {
|
|
81
|
+
const r = Object.keys(e.providers);
|
|
82
|
+
r.length === 0 && (process.stderr.write(
|
|
83
|
+
`provider.json 没有可用提供商,请先编辑 ${p}。
|
|
84
|
+
`
|
|
85
|
+
), process.exit(1)), J().interactive || (process.stderr.write(
|
|
86
|
+
`需要交互式终端选择提供商,请改用 ${h}<id> 显式指定。
|
|
87
|
+
`
|
|
88
|
+
), process.exit(1));
|
|
89
|
+
const { provider: t } = await W({
|
|
90
|
+
type: "select",
|
|
91
|
+
name: "provider",
|
|
92
|
+
message: "选择提供商",
|
|
93
|
+
choices: r.map((o) => ({
|
|
94
|
+
title: `${o}(${e.providers[o].name})`,
|
|
95
|
+
value: o
|
|
96
|
+
}))
|
|
97
|
+
});
|
|
98
|
+
return t;
|
|
99
|
+
}, pe = (e) => {
|
|
100
|
+
let r, t, o, s, n = "run";
|
|
101
|
+
const c = [];
|
|
102
|
+
for (const i of e) {
|
|
103
|
+
if (i.startsWith(g)) {
|
|
104
|
+
r = i.slice(g.length), n = w(n, "profile");
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (i.startsWith(v)) {
|
|
108
|
+
if (t = i.slice(v.length), !t)
|
|
109
|
+
throw new Error(`${v} 需提供 apiKey 值`);
|
|
110
|
+
n = w(n, "setkey");
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (i.startsWith($)) {
|
|
114
|
+
if (o = i.slice($.length), !o)
|
|
115
|
+
throw new Error(`${$} 需提供模型名`);
|
|
116
|
+
n = w(n, "addmodel");
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (i.startsWith(h)) {
|
|
120
|
+
if (s = i.slice(h.length), !s)
|
|
121
|
+
throw new Error(`${h} 需提供 provider id`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (i === j) {
|
|
125
|
+
n = w(n, "pick");
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (i === A) {
|
|
129
|
+
n = w(n, "generate");
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (i === I) {
|
|
133
|
+
n = w(n, "providerlist");
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (i === L) {
|
|
137
|
+
n = w(n, "modellist");
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (i === N) {
|
|
141
|
+
n = w(n, "help");
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (i === R) {
|
|
145
|
+
n = w(n, "version");
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (ie(i))
|
|
149
|
+
throw new Error(
|
|
150
|
+
`未知 meta 选项:${i}。可用选项:${g}<name>、${j}、${A}、${v}<key>、${$}<name>、${h}<id>、${I}、${L}、${N}、${R}`
|
|
151
|
+
);
|
|
152
|
+
c.push(i);
|
|
153
|
+
}
|
|
154
|
+
return fe({ action: n, apiKey: t, modelName: o, providerId: s }), { action: n, profileName: r, apiKey: t, modelName: o, providerId: s, passthrough: c };
|
|
155
|
+
}, fe = (e) => {
|
|
156
|
+
const { action: r, apiKey: t, modelName: o, providerId: s } = e, n = [t, o].filter((c) => c !== void 0).length;
|
|
157
|
+
if (n > 1)
|
|
158
|
+
throw new Error(
|
|
159
|
+
`不能同时指定 ${v} 与 ${$}`
|
|
160
|
+
);
|
|
161
|
+
if (r === "generate" && n > 0)
|
|
162
|
+
throw new Error(
|
|
163
|
+
`${A} 不能与 ${v} / ${$} 同时使用`
|
|
164
|
+
);
|
|
165
|
+
if (s !== void 0 && r !== "setkey" && r !== "addmodel" && r !== "help" && r !== "version")
|
|
166
|
+
throw new Error(
|
|
167
|
+
`${h} 仅用于 ${v} / ${$}`
|
|
168
|
+
);
|
|
169
|
+
}, me = (e, r) => {
|
|
170
|
+
const t = r ?? e.defaultProfile, o = e.profiles[t];
|
|
171
|
+
if (!o) {
|
|
172
|
+
const s = Object.keys(e.profiles).join(", ") || "(无)";
|
|
173
|
+
throw new Error(
|
|
174
|
+
`profile "${t}" 不存在。可用 profile:${s}。配置文件:${l}。若需从 provider.json + model.json 重建,请运行 --meta-generate`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return { name: t, profile: o };
|
|
178
|
+
}, Ee = /^ANTHROPIC_/, ue = [
|
|
179
|
+
"CLAUDE_CODE_SUBAGENT_MODEL",
|
|
180
|
+
"CLAUDE_CODE_EFFORT_LEVEL"
|
|
181
|
+
], B = P.join(
|
|
182
|
+
b.homedir(),
|
|
183
|
+
".claude",
|
|
184
|
+
"settings.json"
|
|
185
|
+
), X = (e) => Ee.test(e) || ue.includes(e), we = (e, r) => {
|
|
186
|
+
const t = {};
|
|
187
|
+
for (const [o, s] of Object.entries(e))
|
|
188
|
+
s !== void 0 && (X(o) || (t[o] = s));
|
|
189
|
+
for (const [o, s] of Object.entries(r))
|
|
190
|
+
t[o] = s;
|
|
191
|
+
return t;
|
|
192
|
+
}, ve = (e) => !e || typeof e != "object" ? [] : Object.keys(e).filter((r) => X(r)), $e = () => {
|
|
193
|
+
let e;
|
|
194
|
+
try {
|
|
195
|
+
e = JSON.parse(m.readFileSync(B, "utf8"));
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
if (typeof e != "object" || e === null)
|
|
200
|
+
return null;
|
|
201
|
+
const r = e.env;
|
|
202
|
+
return typeof r != "object" || r === null || Array.isArray(r) ? null : r;
|
|
203
|
+
}, G = {
|
|
204
|
+
defaultProfile: "deepseek",
|
|
205
|
+
profiles: {
|
|
206
|
+
deepseek: {
|
|
207
|
+
env: {
|
|
208
|
+
ANTHROPIC_BASE_URL: "https://api.deepseek.com/anthropic",
|
|
209
|
+
ANTHROPIC_AUTH_TOKEN: "",
|
|
210
|
+
ANTHROPIC_MODEL: "deepseek-v4-pro[1m]",
|
|
211
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "deepseek-v4-pro[1m]",
|
|
212
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "deepseek-v4-pro[1m]",
|
|
213
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "deepseek-v4-flash[1m]",
|
|
214
|
+
CLAUDE_CODE_SUBAGENT_MODEL: "deepseek-v4-pro[1m]",
|
|
215
|
+
CLAUDE_CODE_EFFORT_LEVEL: "max"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}, C = (e, r) => {
|
|
220
|
+
m.mkdirSync(P.dirname(e), { recursive: !0 }), m.writeFileSync(e, JSON.stringify(r, null, 2)), m.chmodSync(e, 384);
|
|
221
|
+
}, k = (e) => C(l, e), he = (e) => C(p, e), ye = (e) => C(d, e), Oe = () => {
|
|
222
|
+
if (!m.existsSync(l)) {
|
|
223
|
+
if (m.existsSync(p) || m.existsSync(d))
|
|
224
|
+
throw new Error(
|
|
225
|
+
`${l} 不存在,但已检测到 provider/model 源。请运行 --meta-generate 生成配置。`
|
|
226
|
+
);
|
|
227
|
+
return k(G), G;
|
|
228
|
+
}
|
|
229
|
+
const e = m.readFileSync(l, "utf8");
|
|
230
|
+
let r;
|
|
231
|
+
try {
|
|
232
|
+
r = JSON.parse(e);
|
|
233
|
+
} catch (o) {
|
|
234
|
+
const s = o instanceof Error ? o.message : String(o);
|
|
235
|
+
throw new Error(`配置文件非法 JSON:${l}(${s})`);
|
|
236
|
+
}
|
|
237
|
+
if (typeof r != "object" || r === null)
|
|
238
|
+
throw new Error(`配置文件结构非法:${l}(应为 JSON 对象)`);
|
|
239
|
+
const t = r;
|
|
240
|
+
if (typeof t.defaultProfile != "string")
|
|
241
|
+
throw new Error(`配置文件缺少字符串字段 defaultProfile:${l}`);
|
|
242
|
+
if (typeof t.profiles != "object" || t.profiles === null || Array.isArray(t.profiles))
|
|
243
|
+
throw new Error(`配置文件缺少对象字段 profiles:${l}`);
|
|
244
|
+
return t;
|
|
245
|
+
}, _ = (e) => typeof e == "object" && e !== null && !Array.isArray(e), Y = (e, r) => {
|
|
246
|
+
if (e === void 0) return {};
|
|
247
|
+
if (!_(e))
|
|
248
|
+
throw new Error(`${r} [MUST] 为字符串键值对象`);
|
|
249
|
+
const t = {};
|
|
250
|
+
for (const [o, s] of Object.entries(e)) {
|
|
251
|
+
if (typeof s != "string")
|
|
252
|
+
throw new Error(`${r}.${o} [MUST] 为字符串`);
|
|
253
|
+
t[o] = s;
|
|
254
|
+
}
|
|
255
|
+
return t;
|
|
256
|
+
}, q = (e, r) => {
|
|
257
|
+
if (!m.existsSync(e))
|
|
258
|
+
throw new Error(`源配置文件不存在:${e}(请先创建 ${r})`);
|
|
259
|
+
const t = m.readFileSync(e, "utf8");
|
|
260
|
+
let o;
|
|
261
|
+
try {
|
|
262
|
+
o = JSON.parse(t);
|
|
263
|
+
} catch (s) {
|
|
264
|
+
const n = s instanceof Error ? s.message : String(s);
|
|
265
|
+
throw new Error(`${r} 非法 JSON:${e}(${n})`);
|
|
266
|
+
}
|
|
267
|
+
if (!_(o))
|
|
268
|
+
throw new Error(`${r} 结构非法:${e}(应为 JSON 对象)`);
|
|
269
|
+
return o;
|
|
270
|
+
}, T = () => {
|
|
271
|
+
const r = q(p, "provider.json").providers;
|
|
272
|
+
if (!_(r))
|
|
273
|
+
throw new Error(`provider.json 缺少对象字段 providers:${p}`);
|
|
274
|
+
const t = {};
|
|
275
|
+
for (const [o, s] of Object.entries(r)) {
|
|
276
|
+
if (!_(s))
|
|
277
|
+
throw new Error(`provider「${o}」[MUST] 为对象:${p}`);
|
|
278
|
+
const { name: n, url: c, apiKey: i } = s;
|
|
279
|
+
if (typeof n != "string" || n.length === 0)
|
|
280
|
+
throw new Error(`provider「${o}」缺字符串字段 name:${p}`);
|
|
281
|
+
if (typeof c != "string" || c.length === 0)
|
|
282
|
+
throw new Error(`provider「${o}」缺字符串字段 url:${p}`);
|
|
283
|
+
if (typeof i != "string" || i.length === 0)
|
|
284
|
+
throw new Error(`provider「${o}」缺字符串字段 apiKey:${p}`);
|
|
285
|
+
const a = Y(
|
|
286
|
+
s.envExtraParams,
|
|
287
|
+
`provider「${o}」envExtraParams`
|
|
288
|
+
);
|
|
289
|
+
t[o] = {
|
|
290
|
+
name: n,
|
|
291
|
+
url: c,
|
|
292
|
+
apiKey: i,
|
|
293
|
+
...Object.keys(a).length > 0 ? { envExtraParams: a } : {}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return { providers: t };
|
|
297
|
+
}, D = () => {
|
|
298
|
+
const e = q(d, "model.json"), r = e.defaultProfile;
|
|
299
|
+
if (typeof r != "string" || r.length === 0)
|
|
300
|
+
throw new Error(`model.json 缺少字符串字段 defaultProfile:${d}`);
|
|
301
|
+
const t = e.models;
|
|
302
|
+
if (!Array.isArray(t))
|
|
303
|
+
throw new Error(`model.json 缺少数组字段 models:${d}`);
|
|
304
|
+
const o = [];
|
|
305
|
+
for (const [s, n] of t.entries()) {
|
|
306
|
+
if (!_(n))
|
|
307
|
+
throw new Error(`model.json models[${s}] [MUST] 为对象:${d}`);
|
|
308
|
+
const { provider: c, id: i, name: a } = n;
|
|
309
|
+
if (typeof c != "string" || c.length === 0)
|
|
310
|
+
throw new Error(
|
|
311
|
+
`model.json models[${s}] 缺字符串字段 provider:${d}`
|
|
312
|
+
);
|
|
313
|
+
if (typeof i != "string" || i.length === 0)
|
|
314
|
+
throw new Error(
|
|
315
|
+
`model.json models[${s}] 缺字符串字段 id:${d}`
|
|
316
|
+
);
|
|
317
|
+
if (typeof a != "string" || a.length === 0)
|
|
318
|
+
throw new Error(
|
|
319
|
+
`model.json models[${s}] 缺字符串字段 name:${d}`
|
|
320
|
+
);
|
|
321
|
+
const f = Y(
|
|
322
|
+
n.envExtraParams,
|
|
323
|
+
`model.json models[${s}] envExtraParams`
|
|
324
|
+
);
|
|
325
|
+
o.push({
|
|
326
|
+
provider: c,
|
|
327
|
+
id: i,
|
|
328
|
+
name: a,
|
|
329
|
+
...Object.keys(f).length > 0 ? { envExtraParams: f } : {}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return { defaultProfile: r, models: o };
|
|
333
|
+
}, ge = (e, r) => ({
|
|
334
|
+
ANTHROPIC_BASE_URL: e.url,
|
|
335
|
+
ANTHROPIC_AUTH_TOKEN: e.apiKey,
|
|
336
|
+
ANTHROPIC_MODEL: r.name,
|
|
337
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: r.name,
|
|
338
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: r.name,
|
|
339
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: r.name,
|
|
340
|
+
CLAUDE_CODE_SUBAGENT_MODEL: r.name,
|
|
341
|
+
...e.envExtraParams ?? {},
|
|
342
|
+
...r.envExtraParams ?? {}
|
|
343
|
+
}), Ae = (e, r) => {
|
|
344
|
+
const t = {}, o = /* @__PURE__ */ new Set();
|
|
345
|
+
for (const s of r.models) {
|
|
346
|
+
const n = e.providers[s.provider];
|
|
347
|
+
if (!n)
|
|
348
|
+
throw new Error(
|
|
349
|
+
`model「${s.id}」引用了不存在的 provider「${s.provider}」:${d}`
|
|
350
|
+
);
|
|
351
|
+
const c = `${s.provider}-${s.id}`;
|
|
352
|
+
if (o.has(c))
|
|
353
|
+
throw new Error(
|
|
354
|
+
`profile 名重复:${c}(provider+id 组合重复):${d}`
|
|
355
|
+
);
|
|
356
|
+
o.add(c), t[c] = { env: ge(n, s) };
|
|
357
|
+
}
|
|
358
|
+
if (Object.keys(t).length === 0)
|
|
359
|
+
throw new Error(
|
|
360
|
+
`model.json 的 models 为空,无可生成 profile:${d}`
|
|
361
|
+
);
|
|
362
|
+
if (!t[r.defaultProfile]) {
|
|
363
|
+
const s = Object.keys(t).join(", ");
|
|
364
|
+
throw new Error(
|
|
365
|
+
`defaultProfile「${r.defaultProfile}」不在生成的 profile 中。可用:${s}。配置文件:${d}`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
return { defaultProfile: r.defaultProfile, profiles: t };
|
|
369
|
+
}, x = () => {
|
|
370
|
+
const e = Ae(T(), D());
|
|
371
|
+
return k(e), e;
|
|
372
|
+
}, z = (e) => {
|
|
373
|
+
const r = e.trim();
|
|
374
|
+
if (r.length === 0)
|
|
375
|
+
throw new Error("模型名为空(--meta-model-name 值不可为空)");
|
|
376
|
+
const t = r.replace(/\[1m\]$/, "");
|
|
377
|
+
return { id: t, name: `${t}[1m]` };
|
|
378
|
+
}, _e = (e, r) => {
|
|
379
|
+
const t = T(), o = t.providers[e];
|
|
380
|
+
if (!o)
|
|
381
|
+
throw new Error(
|
|
382
|
+
`provider「${e}」不存在。可用:${Object.keys(t.providers).join(", ")}。配置文件:${p}`
|
|
383
|
+
);
|
|
384
|
+
return o.apiKey = r, he(t), x();
|
|
385
|
+
}, Te = (e, r) => {
|
|
386
|
+
const { id: t, name: o } = z(r), s = T();
|
|
387
|
+
if (!s.providers[e])
|
|
388
|
+
throw new Error(
|
|
389
|
+
`provider「${e}」不存在。可用:${Object.keys(s.providers).join(", ")}。配置文件:${p}`
|
|
390
|
+
);
|
|
391
|
+
const n = D(), c = n.models.find((i) => i.provider === e && i.id === t);
|
|
392
|
+
if (c)
|
|
393
|
+
throw new Error(
|
|
394
|
+
`model「${e}/${t}」已存在(name=${c.name}):${d}`
|
|
395
|
+
);
|
|
396
|
+
return n.models.push({ provider: e, id: t, name: o }), ye(n), x();
|
|
397
|
+
}, Pe = (e) => Object.entries(e.providers).map(([r, t]) => `${r}(${t.name})`), Se = (e) => e.models.map((r) => `${r.name}(${r.provider})`), je = /TOKEN|KEY|SECRET/i;
|
|
398
|
+
class K extends Error {
|
|
399
|
+
constructor() {
|
|
400
|
+
super("用户中断输入"), this.name = "PromptAbortError";
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const Ie = (e) => je.test(e), We = (e) => e === "" ? "" : "******", Q = (e) => Object.keys(e).filter((r) => e[r] === "");
|
|
404
|
+
let M = !1, O = null;
|
|
405
|
+
const Le = (e) => new Promise((r, t) => {
|
|
406
|
+
const o = ne.createInterface({
|
|
407
|
+
input: process.stdin,
|
|
408
|
+
output: process.stderr
|
|
409
|
+
});
|
|
410
|
+
O = o, o.on("close", () => {
|
|
411
|
+
O === o && (O = null);
|
|
412
|
+
}), o.question(e, (s) => {
|
|
413
|
+
o.close(), r(s);
|
|
414
|
+
}), o.on("SIGINT", () => {
|
|
415
|
+
o.close(), t(new K());
|
|
416
|
+
});
|
|
417
|
+
}), Ne = 3, Re = 4, be = 127, Me = (e) => new Promise((r, t) => {
|
|
418
|
+
process.stderr.write(e);
|
|
419
|
+
const o = process.stdin;
|
|
420
|
+
typeof o.setRawMode == "function" && (o.setRawMode(!0), M = !0), o.resume(), o.setEncoding("utf8");
|
|
421
|
+
let s = "";
|
|
422
|
+
const n = () => {
|
|
423
|
+
o.removeListener("data", c);
|
|
424
|
+
}, c = (i) => {
|
|
425
|
+
for (const a of i) {
|
|
426
|
+
const f = a.charCodeAt(0);
|
|
427
|
+
if (a === "\r" || a === `
|
|
428
|
+
`) {
|
|
429
|
+
n(), process.stderr.write(`
|
|
430
|
+
`), r(s);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (f === Ne || f === Re) {
|
|
434
|
+
n(), process.stderr.write(`
|
|
435
|
+
`), t(new K());
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (f === be || a === "\b") {
|
|
439
|
+
s = s.slice(0, -1);
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
s += a;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
o.on("data", c);
|
|
446
|
+
}), Ce = async (e) => {
|
|
447
|
+
const r = Q(e.env);
|
|
448
|
+
for (const t of r) {
|
|
449
|
+
const o = Ie(t);
|
|
450
|
+
let s = "";
|
|
451
|
+
for (; s === ""; ) {
|
|
452
|
+
const n = `请输入 ${t}: `;
|
|
453
|
+
s = o ? await Me(n) : await Le(n);
|
|
454
|
+
}
|
|
455
|
+
e.env[t] = s;
|
|
456
|
+
}
|
|
457
|
+
}, ke = () => {
|
|
458
|
+
O && (O.close(), O = null);
|
|
459
|
+
const e = process.stdin;
|
|
460
|
+
M && typeof e.setRawMode == "function" && (e.setRawMode(!1), M = !1), e.removeAllListeners("data"), e.removeAllListeners("keypress"), e.removeAllListeners("readable"), e.pause();
|
|
461
|
+
}, Z = {
|
|
462
|
+
version: "0.2.0",
|
|
463
|
+
cliConfig: {
|
|
464
|
+
moduleName: "cc-switch"
|
|
465
|
+
}
|
|
466
|
+
}, De = "claude", ee = async (e) => {
|
|
467
|
+
const { action: r, profileName: t, apiKey: o, modelName: s, providerId: n, passthrough: c } = pe(e ?? process.argv.slice(2));
|
|
468
|
+
if (r === "help" && (ce(), process.exit(0)), r === "version" && (ae(Z.version), process.exit(0)), r === "generate") {
|
|
469
|
+
const E = x();
|
|
470
|
+
process.stdout.write(
|
|
471
|
+
`已生成 ${Object.keys(E.profiles).length} 个 profile → ${l}
|
|
472
|
+
`
|
|
473
|
+
), process.exit(0);
|
|
474
|
+
}
|
|
475
|
+
if (r === "providerlist" || r === "modellist") {
|
|
476
|
+
const E = r === "providerlist" ? Pe(T()) : Se(D());
|
|
477
|
+
for (const u of E)
|
|
478
|
+
process.stdout.write(`${u}
|
|
479
|
+
`);
|
|
480
|
+
process.exit(0);
|
|
481
|
+
}
|
|
482
|
+
if (r === "setkey" || r === "addmodel") {
|
|
483
|
+
const E = T(), u = n ?? await de(E), y = r === "setkey" ? _e(u, o) : Te(u, s);
|
|
484
|
+
process.stdout.write(
|
|
485
|
+
r === "setkey" ? `已更新 provider「${u}」apiKey,重建 ${Object.keys(y.profiles).length} 个 profile → ${l}
|
|
486
|
+
` : `已添加 model「${u}/${z(s).id}」,重建 ${Object.keys(y.profiles).length} 个 profile → ${l}
|
|
487
|
+
`
|
|
488
|
+
), process.exit(0);
|
|
489
|
+
}
|
|
490
|
+
const i = Oe(), a = r === "pick" ? await le(i) : t, { profile: f } = me(i, a);
|
|
491
|
+
if (Q(f.env).length > 0) {
|
|
492
|
+
try {
|
|
493
|
+
await Ce(f);
|
|
494
|
+
} catch (E) {
|
|
495
|
+
if (E instanceof K)
|
|
496
|
+
return process.stderr.write(`已取消,未写入配置。
|
|
497
|
+
`), process.exit(1);
|
|
498
|
+
throw E;
|
|
499
|
+
}
|
|
500
|
+
k(i), ke();
|
|
501
|
+
}
|
|
502
|
+
const re = $e(), U = ve(re);
|
|
503
|
+
if (U.length > 0)
|
|
504
|
+
return process.stderr.write(
|
|
505
|
+
`检测到 ${B} 的 env 块含模型路由类 key:${U.join(", ")}。请手动清理这些 key 后重试。
|
|
506
|
+
`
|
|
507
|
+
), process.exit(1);
|
|
508
|
+
const oe = we(process.env, f.env), H = se(De, c, {
|
|
509
|
+
stdio: "inherit",
|
|
510
|
+
shell: !1,
|
|
511
|
+
env: oe
|
|
512
|
+
});
|
|
513
|
+
return new Promise((E, u) => {
|
|
514
|
+
H.on("error", (y) => {
|
|
515
|
+
try {
|
|
516
|
+
if (y.code === "ENOENT") {
|
|
517
|
+
process.stderr.write(
|
|
518
|
+
`未找到 claude 可执行文件,请确认 claude-code 已安装并在 PATH 中。
|
|
519
|
+
`
|
|
520
|
+
), process.exit(127);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
process.stderr.write(`启动 claude 失败:${y.message}
|
|
524
|
+
`), process.exit(1);
|
|
525
|
+
} catch (S) {
|
|
526
|
+
u(S);
|
|
527
|
+
}
|
|
528
|
+
}), H.on("exit", (y, S) => {
|
|
529
|
+
try {
|
|
530
|
+
if (S) {
|
|
531
|
+
process.exit(128 + xe(S));
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
process.exit(y ?? 1);
|
|
535
|
+
} catch (te) {
|
|
536
|
+
u(te);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
}, xe = (e) => ({
|
|
541
|
+
SIGHUP: 1,
|
|
542
|
+
SIGINT: 2,
|
|
543
|
+
SIGQUIT: 3,
|
|
544
|
+
SIGILL: 4,
|
|
545
|
+
SIGABRT: 6,
|
|
546
|
+
SIGFPE: 8,
|
|
547
|
+
SIGKILL: 9,
|
|
548
|
+
SIGSEGV: 11,
|
|
549
|
+
SIGPIPE: 13,
|
|
550
|
+
SIGALRM: 14,
|
|
551
|
+
SIGTERM: 15
|
|
552
|
+
})[e] ?? 1, {
|
|
553
|
+
cliConfig: { moduleName: V }
|
|
554
|
+
} = Z, Be = async () => {
|
|
555
|
+
try {
|
|
556
|
+
await ee();
|
|
557
|
+
} catch (e) {
|
|
558
|
+
const r = e instanceof Error ? e.message : String(e);
|
|
559
|
+
process.stderr.write(`${r}
|
|
560
|
+
`), process.exit(1);
|
|
561
|
+
}
|
|
562
|
+
}, Xe = () => ({
|
|
563
|
+
command: V,
|
|
564
|
+
describe: "claude-code 模型路由透传(dc-cc-switch / cc-router)",
|
|
565
|
+
builder(e) {
|
|
566
|
+
return e.strict(!1).option("meta-profile", {
|
|
567
|
+
type: "string",
|
|
568
|
+
describe: "选择 profile(~/.done-coding/cc-switch/profile.json 中的 profile 名)"
|
|
569
|
+
}).option("meta-pick", {
|
|
570
|
+
type: "boolean",
|
|
571
|
+
describe: "终端交互选择 profile 启动"
|
|
572
|
+
}).option("meta-generate", {
|
|
573
|
+
type: "boolean",
|
|
574
|
+
describe: "从 provider.json + model.json 重建 profile.json"
|
|
575
|
+
}).option("meta-apiKey", {
|
|
576
|
+
type: "string",
|
|
577
|
+
describe: "更新指定提供商 apiKey(自动重建)"
|
|
578
|
+
}).option("meta-model-name", {
|
|
579
|
+
type: "string",
|
|
580
|
+
describe: "添加模型到指定提供商(自动重建)"
|
|
581
|
+
}).option("meta-provider", {
|
|
582
|
+
type: "string",
|
|
583
|
+
describe: "显式指定 provider id(供 apiKey/model-name 跳过选择)"
|
|
584
|
+
}).option("meta-provider-list", {
|
|
585
|
+
type: "boolean",
|
|
586
|
+
describe: "输出提供商列表(id + name)"
|
|
587
|
+
}).option("meta-model-list", {
|
|
588
|
+
type: "boolean",
|
|
589
|
+
describe: "输出模型列表(name + 所属 provider)"
|
|
590
|
+
}).option("meta-help", {
|
|
591
|
+
type: "boolean",
|
|
592
|
+
describe: "显示 cc-switch 自身帮助"
|
|
593
|
+
}).option("meta-version", {
|
|
594
|
+
type: "boolean",
|
|
595
|
+
describe: "显示 cc-switch 自身版本"
|
|
596
|
+
});
|
|
597
|
+
},
|
|
598
|
+
handler() {
|
|
599
|
+
const e = process.argv.findIndex((t) => t === V), r = e >= 0 ? process.argv.slice(e + 1) : process.argv.slice(2);
|
|
600
|
+
return ee(r);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
export {
|
|
604
|
+
G as D,
|
|
605
|
+
l as P,
|
|
606
|
+
Be as a,
|
|
607
|
+
we as b,
|
|
608
|
+
Xe as c,
|
|
609
|
+
Q as f,
|
|
610
|
+
ve as h,
|
|
611
|
+
Ie as i,
|
|
612
|
+
We as m,
|
|
613
|
+
pe as p,
|
|
614
|
+
me as s
|
|
615
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@done-coding/cli-cc-switch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "claude-code路由",
|
|
5
5
|
"private": false,
|
|
6
6
|
"module": "es/index.mjs",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=18.0.0"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "f56359da7c5f246511bedf8f814369c702f5456c"
|
|
62
62
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -44,8 +44,25 @@ export declare const isSecretKey: (key: string) => boolean;
|
|
|
44
44
|
/** 固定遮蔽:不含任何明文片段(纯函数,可单测,REQ-6 报错脱敏用)。 */
|
|
45
45
|
export declare const maskValue: (v: string) => string;
|
|
46
46
|
|
|
47
|
-
/** meta 动作:run=透传启动 / profile=显式指定 / pick=交互选择 /
|
|
48
|
-
|
|
47
|
+
/** meta 动作:run=透传启动 / profile=显式指定 / pick=交互选择 / setkey=改 provider key /
|
|
48
|
+
* addmodel=添加模型 / providerlist=列提供商 / modellist=列模型 / generate=重建配置 / help / version */
|
|
49
|
+
export declare type MetaAction = "run" | "profile" | "pick" | "setkey" | "addmodel" | "providerlist" | "modellist" | "generate" | "help" | "version";
|
|
50
|
+
|
|
51
|
+
/** model:模型层(绑 provider,id 为该 provider 下局部标识 → profile 名 = `${provider}-${id}`) */
|
|
52
|
+
export declare interface Model {
|
|
53
|
+
provider: string;
|
|
54
|
+
id: string;
|
|
55
|
+
/** 实际模型串(如 deepseek-v4-flash[1m]) */
|
|
56
|
+
name: string;
|
|
57
|
+
/** 附加 env 覆盖(如 pro 档 ANTHROPIC_DEFAULT_HAIKU_MODEL 用 flash) */
|
|
58
|
+
envExtraParams?: Record<string, string>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** model 配置文件结构(~/.done-coding/cc-switch/model.json,defaultProfile 为生成后 profile 名) */
|
|
62
|
+
export declare interface ModelConfig {
|
|
63
|
+
defaultProfile: string;
|
|
64
|
+
models: Model[];
|
|
65
|
+
}
|
|
49
66
|
|
|
50
67
|
/**
|
|
51
68
|
* 解析 argv(纯函数,可单测)。
|
|
@@ -56,10 +73,16 @@ export declare type MetaAction = "run" | "profile" | "pick" | "help" | "version"
|
|
|
56
73
|
*/
|
|
57
74
|
export declare const parseArgv: (argv: string[]) => ParsedArgv;
|
|
58
75
|
|
|
59
|
-
/** argv 解析结果:meta 动作归并 +
|
|
76
|
+
/** argv 解析结果:meta 动作归并 + 消费的各 meta 值 + 其余原样透传 */
|
|
60
77
|
export declare interface ParsedArgv {
|
|
61
78
|
action: MetaAction;
|
|
62
79
|
profileName?: string;
|
|
80
|
+
/** --meta-apiKey= 值(setkey 消费) */
|
|
81
|
+
apiKey?: string;
|
|
82
|
+
/** --meta-model-name= 值(addmodel 消费) */
|
|
83
|
+
modelName?: string;
|
|
84
|
+
/** --meta-provider= 值(setkey/addmodel 的 provider 选择旁路,非独立动作) */
|
|
85
|
+
providerId?: string;
|
|
63
86
|
passthrough: string[];
|
|
64
87
|
}
|
|
65
88
|
|
|
@@ -81,6 +104,21 @@ export declare interface ProfileConfig {
|
|
|
81
104
|
profiles: Record<string, Profile>;
|
|
82
105
|
}
|
|
83
106
|
|
|
107
|
+
/** provider:服务商层(url/apiKey 单点 + 可选附加 env,供 --meta-generate 消费) */
|
|
108
|
+
export declare interface Provider {
|
|
109
|
+
/** 展示名(服务商名) */
|
|
110
|
+
name: string;
|
|
111
|
+
url: string;
|
|
112
|
+
apiKey: string;
|
|
113
|
+
/** 附加 env(如 CLAUDE_CODE_EFFORT_LEVEL=max),合并序 model > provider > 通用 */
|
|
114
|
+
envExtraParams?: Record<string, string>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** provider 配置文件结构(~/.done-coding/cc-switch/provider.json) */
|
|
118
|
+
export declare interface ProviderConfig {
|
|
119
|
+
providers: Record<string, Provider>;
|
|
120
|
+
}
|
|
121
|
+
|
|
84
122
|
/**
|
|
85
123
|
* 选择 profile(纯函数,可单测)。
|
|
86
124
|
* 有 profileName 用之,否则用 cfg.defaultProfile。
|
package/es/main-B-J--Kd8.js
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawn as j } from "node:child_process";
|
|
3
|
-
import C from "node:os";
|
|
4
|
-
import _ from "node:path";
|
|
5
|
-
import { resolveHandlerContext as F, xPrompts as H } from "@done-coding/cli-utils";
|
|
6
|
-
import p from "node:fs";
|
|
7
|
-
import U from "node:readline";
|
|
8
|
-
const n = _.join(
|
|
9
|
-
C.homedir(),
|
|
10
|
-
".done-coding",
|
|
11
|
-
"cc-switch",
|
|
12
|
-
"profile.json"
|
|
13
|
-
), d = "--meta-profile=", h = "--meta-pick", w = "--meta-help", y = "--meta-version", P = {
|
|
14
|
-
run: 0,
|
|
15
|
-
profile: 1,
|
|
16
|
-
pick: 2,
|
|
17
|
-
version: 3,
|
|
18
|
-
help: 4
|
|
19
|
-
}, m = (e, t) => P[t] > P[e] ? t : e, G = (e) => e.startsWith("--meta-") && e !== h && e !== w && e !== y && !e.startsWith(d), K = () => {
|
|
20
|
-
process.stdout.write(
|
|
21
|
-
[
|
|
22
|
-
"dc-cc-switch(cc-router)— claude-code 模型路由透传",
|
|
23
|
-
"",
|
|
24
|
-
"用法:",
|
|
25
|
-
" dc-cc-switch [meta 选项] <claude 参数...>",
|
|
26
|
-
"",
|
|
27
|
-
"meta 选项(cc-switch 自身命令面,不透传给 claude):",
|
|
28
|
-
` ${d}<name> 显式指定 profile 启动`,
|
|
29
|
-
` ${h} 终端交互选择 profile 启动`,
|
|
30
|
-
` ${w} 显示本帮助`,
|
|
31
|
-
` ${y} 显示版本`,
|
|
32
|
-
"",
|
|
33
|
-
"其余所有参数原样透传给 claude。",
|
|
34
|
-
`配置: ${n}`,
|
|
35
|
-
""
|
|
36
|
-
].join(`
|
|
37
|
-
`)
|
|
38
|
-
);
|
|
39
|
-
}, V = (e) => {
|
|
40
|
-
process.stdout.write(`${e}
|
|
41
|
-
`);
|
|
42
|
-
}, B = async (e) => {
|
|
43
|
-
const t = Object.keys(e.profiles);
|
|
44
|
-
t.length === 0 && (process.stderr.write(
|
|
45
|
-
`配置中没有可用 profile,请先编辑 ${n}。
|
|
46
|
-
`
|
|
47
|
-
), process.exit(1)), F().interactive || (process.stderr.write(
|
|
48
|
-
`--meta-pick 需要交互式终端,请改用 --meta-profile=<name> 显式指定。
|
|
49
|
-
`
|
|
50
|
-
), process.exit(1));
|
|
51
|
-
const { profile: r } = await H({
|
|
52
|
-
type: "select",
|
|
53
|
-
name: "profile",
|
|
54
|
-
message: "选择以哪个配置启动 claude",
|
|
55
|
-
choices: t.map((s) => ({ title: s, value: s }))
|
|
56
|
-
});
|
|
57
|
-
return r;
|
|
58
|
-
}, J = (e) => {
|
|
59
|
-
let t, r = "run";
|
|
60
|
-
const s = [];
|
|
61
|
-
for (const o of e) {
|
|
62
|
-
if (o.startsWith(d)) {
|
|
63
|
-
t = o.slice(d.length), r = m(r, "profile");
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (o === h) {
|
|
67
|
-
r = m(r, "pick");
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (o === w) {
|
|
71
|
-
r = m(r, "help");
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (o === y) {
|
|
75
|
-
r = m(r, "version");
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (G(o))
|
|
79
|
-
throw new Error(
|
|
80
|
-
`未知 meta 选项:${o}。可用选项:${d}<name>、${h}、${w}、${y}`
|
|
81
|
-
);
|
|
82
|
-
s.push(o);
|
|
83
|
-
}
|
|
84
|
-
return { action: r, profileName: t, passthrough: s };
|
|
85
|
-
}, W = (e, t) => {
|
|
86
|
-
const r = t ?? e.defaultProfile, s = e.profiles[r];
|
|
87
|
-
if (!s) {
|
|
88
|
-
const o = Object.keys(e.profiles).join(", ") || "(无)";
|
|
89
|
-
throw new Error(
|
|
90
|
-
`profile "${r}" 不存在。可用 profile:${o}。配置文件:${n}`
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
return { name: r, profile: s };
|
|
94
|
-
}, Y = /^ANTHROPIC_/, X = [
|
|
95
|
-
"CLAUDE_CODE_SUBAGENT_MODEL",
|
|
96
|
-
"CLAUDE_CODE_EFFORT_LEVEL"
|
|
97
|
-
], b = _.join(
|
|
98
|
-
C.homedir(),
|
|
99
|
-
".claude",
|
|
100
|
-
"settings.json"
|
|
101
|
-
), R = (e) => Y.test(e) || X.includes(e), q = (e, t) => {
|
|
102
|
-
const r = {};
|
|
103
|
-
for (const [s, o] of Object.entries(e))
|
|
104
|
-
o !== void 0 && (R(s) || (r[s] = o));
|
|
105
|
-
for (const [s, o] of Object.entries(t))
|
|
106
|
-
r[s] = o;
|
|
107
|
-
return r;
|
|
108
|
-
}, Q = (e) => !e || typeof e != "object" ? [] : Object.keys(e).filter((t) => R(t)), z = () => {
|
|
109
|
-
let e;
|
|
110
|
-
try {
|
|
111
|
-
e = JSON.parse(p.readFileSync(b, "utf8"));
|
|
112
|
-
} catch {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
if (typeof e != "object" || e === null)
|
|
116
|
-
return null;
|
|
117
|
-
const t = e.env;
|
|
118
|
-
return typeof t != "object" || t === null || Array.isArray(t) ? null : t;
|
|
119
|
-
}, L = {
|
|
120
|
-
defaultProfile: "deepseek",
|
|
121
|
-
profiles: {
|
|
122
|
-
deepseek: {
|
|
123
|
-
env: {
|
|
124
|
-
ANTHROPIC_BASE_URL: "https://api.deepseek.com/anthropic",
|
|
125
|
-
ANTHROPIC_AUTH_TOKEN: "",
|
|
126
|
-
ANTHROPIC_MODEL: "deepseek-v4-pro[1m]",
|
|
127
|
-
ANTHROPIC_DEFAULT_OPUS_MODEL: "deepseek-v4-pro[1m]",
|
|
128
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL: "deepseek-v4-pro[1m]",
|
|
129
|
-
ANTHROPIC_DEFAULT_HAIKU_MODEL: "deepseek-v4-flash[1m]",
|
|
130
|
-
CLAUDE_CODE_SUBAGENT_MODEL: "deepseek-v4-pro[1m]",
|
|
131
|
-
CLAUDE_CODE_EFFORT_LEVEL: "max"
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}, g = (e) => {
|
|
136
|
-
p.mkdirSync(_.dirname(n), { recursive: !0 }), p.writeFileSync(n, JSON.stringify(e, null, 2)), p.chmodSync(n, 384);
|
|
137
|
-
}, Z = () => {
|
|
138
|
-
if (!p.existsSync(n))
|
|
139
|
-
return g(L), L;
|
|
140
|
-
const e = p.readFileSync(n, "utf8");
|
|
141
|
-
let t;
|
|
142
|
-
try {
|
|
143
|
-
t = JSON.parse(e);
|
|
144
|
-
} catch (s) {
|
|
145
|
-
const o = s instanceof Error ? s.message : String(s);
|
|
146
|
-
throw new Error(`配置文件非法 JSON:${n}(${o})`);
|
|
147
|
-
}
|
|
148
|
-
if (typeof t != "object" || t === null)
|
|
149
|
-
throw new Error(`配置文件结构非法:${n}(应为 JSON 对象)`);
|
|
150
|
-
const r = t;
|
|
151
|
-
if (typeof r.defaultProfile != "string")
|
|
152
|
-
throw new Error(`配置文件缺少字符串字段 defaultProfile:${n}`);
|
|
153
|
-
if (typeof r.profiles != "object" || r.profiles === null || Array.isArray(r.profiles))
|
|
154
|
-
throw new Error(`配置文件缺少对象字段 profiles:${n}`);
|
|
155
|
-
return r;
|
|
156
|
-
}, ee = /TOKEN|KEY|SECRET/i;
|
|
157
|
-
class T extends Error {
|
|
158
|
-
constructor() {
|
|
159
|
-
super("用户中断输入"), this.name = "PromptAbortError";
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const te = (e) => ee.test(e), we = (e) => e === "" ? "" : "******", k = (e) => Object.keys(e).filter((t) => e[t] === "");
|
|
163
|
-
let A = !1, f = null;
|
|
164
|
-
const re = (e) => new Promise((t, r) => {
|
|
165
|
-
const s = U.createInterface({
|
|
166
|
-
input: process.stdin,
|
|
167
|
-
output: process.stderr
|
|
168
|
-
});
|
|
169
|
-
f = s, s.on("close", () => {
|
|
170
|
-
f === s && (f = null);
|
|
171
|
-
}), s.question(e, (o) => {
|
|
172
|
-
s.close(), t(o);
|
|
173
|
-
}), s.on("SIGINT", () => {
|
|
174
|
-
s.close(), r(new T());
|
|
175
|
-
});
|
|
176
|
-
}), se = 3, oe = 4, ne = 127, ce = (e) => new Promise((t, r) => {
|
|
177
|
-
process.stderr.write(e);
|
|
178
|
-
const s = process.stdin;
|
|
179
|
-
typeof s.setRawMode == "function" && (s.setRawMode(!0), A = !0), s.resume(), s.setEncoding("utf8");
|
|
180
|
-
let o = "";
|
|
181
|
-
const c = () => {
|
|
182
|
-
s.removeListener("data", a);
|
|
183
|
-
}, a = (S) => {
|
|
184
|
-
for (const i of S) {
|
|
185
|
-
const l = i.charCodeAt(0);
|
|
186
|
-
if (i === "\r" || i === `
|
|
187
|
-
`) {
|
|
188
|
-
c(), process.stderr.write(`
|
|
189
|
-
`), t(o);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
if (l === se || l === oe) {
|
|
193
|
-
c(), process.stderr.write(`
|
|
194
|
-
`), r(new T());
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (l === ne || i === "\b") {
|
|
198
|
-
o = o.slice(0, -1);
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
o += i;
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
s.on("data", a);
|
|
205
|
-
}), ie = async (e) => {
|
|
206
|
-
const t = k(e.env);
|
|
207
|
-
for (const r of t) {
|
|
208
|
-
const s = te(r);
|
|
209
|
-
let o = "";
|
|
210
|
-
for (; o === ""; ) {
|
|
211
|
-
const c = `请输入 ${r}: `;
|
|
212
|
-
o = s ? await ce(c) : await re(c);
|
|
213
|
-
}
|
|
214
|
-
e.env[r] = o;
|
|
215
|
-
}
|
|
216
|
-
}, ae = () => {
|
|
217
|
-
f && (f.close(), f = null);
|
|
218
|
-
const e = process.stdin;
|
|
219
|
-
A && typeof e.setRawMode == "function" && (e.setRawMode(!1), A = !1), e.removeAllListeners("data"), e.removeAllListeners("keypress"), e.removeAllListeners("readable"), e.pause();
|
|
220
|
-
}, M = {
|
|
221
|
-
version: "0.1.1",
|
|
222
|
-
cliConfig: {
|
|
223
|
-
moduleName: "cc-switch"
|
|
224
|
-
}
|
|
225
|
-
}, le = "claude", $ = async (e) => {
|
|
226
|
-
const { action: t, profileName: r, passthrough: s } = J(
|
|
227
|
-
e ?? process.argv.slice(2)
|
|
228
|
-
);
|
|
229
|
-
t === "help" && (K(), process.exit(0)), t === "version" && (V(M.version), process.exit(0));
|
|
230
|
-
const o = Z(), c = t === "pick" ? await B(o) : r, { profile: a } = W(o, c);
|
|
231
|
-
if (k(a.env).length > 0) {
|
|
232
|
-
try {
|
|
233
|
-
await ie(a);
|
|
234
|
-
} catch (v) {
|
|
235
|
-
if (v instanceof T)
|
|
236
|
-
return process.stderr.write(`已取消,未写入配置。
|
|
237
|
-
`), process.exit(1);
|
|
238
|
-
throw v;
|
|
239
|
-
}
|
|
240
|
-
g(o), ae();
|
|
241
|
-
}
|
|
242
|
-
const i = z(), l = Q(i);
|
|
243
|
-
if (l.length > 0)
|
|
244
|
-
return process.stderr.write(
|
|
245
|
-
`检测到 ${b} 的 env 块含模型路由类 key:${l.join(", ")}。请手动清理这些 key 后重试。
|
|
246
|
-
`
|
|
247
|
-
), process.exit(1);
|
|
248
|
-
const D = q(process.env, a.env), I = j(le, s, {
|
|
249
|
-
stdio: "inherit",
|
|
250
|
-
shell: !1,
|
|
251
|
-
env: D
|
|
252
|
-
});
|
|
253
|
-
return new Promise((v, O) => {
|
|
254
|
-
I.on("error", (u) => {
|
|
255
|
-
try {
|
|
256
|
-
if (u.code === "ENOENT") {
|
|
257
|
-
process.stderr.write(
|
|
258
|
-
`未找到 claude 可执行文件,请确认 claude-code 已安装并在 PATH 中。
|
|
259
|
-
`
|
|
260
|
-
), process.exit(127);
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
process.stderr.write(`启动 claude 失败:${u.message}
|
|
264
|
-
`), process.exit(1);
|
|
265
|
-
} catch (E) {
|
|
266
|
-
O(E);
|
|
267
|
-
}
|
|
268
|
-
}), I.on("exit", (u, E) => {
|
|
269
|
-
try {
|
|
270
|
-
if (E) {
|
|
271
|
-
process.exit(128 + pe(E));
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
process.exit(u ?? 1);
|
|
275
|
-
} catch (x) {
|
|
276
|
-
O(x);
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
}, pe = (e) => ({
|
|
281
|
-
SIGHUP: 1,
|
|
282
|
-
SIGINT: 2,
|
|
283
|
-
SIGQUIT: 3,
|
|
284
|
-
SIGILL: 4,
|
|
285
|
-
SIGABRT: 6,
|
|
286
|
-
SIGFPE: 8,
|
|
287
|
-
SIGKILL: 9,
|
|
288
|
-
SIGSEGV: 11,
|
|
289
|
-
SIGPIPE: 13,
|
|
290
|
-
SIGALRM: 14,
|
|
291
|
-
SIGTERM: 15
|
|
292
|
-
})[e] ?? 1, {
|
|
293
|
-
cliConfig: { moduleName: N }
|
|
294
|
-
} = M, ye = async () => {
|
|
295
|
-
try {
|
|
296
|
-
await $();
|
|
297
|
-
} catch (e) {
|
|
298
|
-
const t = e instanceof Error ? e.message : String(e);
|
|
299
|
-
process.stderr.write(`${t}
|
|
300
|
-
`), process.exit(1);
|
|
301
|
-
}
|
|
302
|
-
}, ve = () => ({
|
|
303
|
-
command: N,
|
|
304
|
-
describe: "claude-code 模型路由透传(dc-cc-switch / cc-router)",
|
|
305
|
-
builder(e) {
|
|
306
|
-
return e.strict(!1).option("meta-profile", {
|
|
307
|
-
type: "string",
|
|
308
|
-
describe: "选择 profile(~/.done-coding/cc-switch/profile.json 中的 profile 名)"
|
|
309
|
-
}).option("meta-pick", {
|
|
310
|
-
type: "boolean",
|
|
311
|
-
describe: "终端交互选择 profile 启动"
|
|
312
|
-
}).option("meta-help", {
|
|
313
|
-
type: "boolean",
|
|
314
|
-
describe: "显示 cc-switch 自身帮助"
|
|
315
|
-
}).option("meta-version", {
|
|
316
|
-
type: "boolean",
|
|
317
|
-
describe: "显示 cc-switch 自身版本"
|
|
318
|
-
});
|
|
319
|
-
},
|
|
320
|
-
handler() {
|
|
321
|
-
const e = process.argv.findIndex((r) => r === N), t = e >= 0 ? process.argv.slice(e + 1) : process.argv.slice(2);
|
|
322
|
-
return $(t);
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
export {
|
|
326
|
-
L as D,
|
|
327
|
-
n as P,
|
|
328
|
-
ye as a,
|
|
329
|
-
q as b,
|
|
330
|
-
ve as c,
|
|
331
|
-
k as f,
|
|
332
|
-
Q as h,
|
|
333
|
-
te as i,
|
|
334
|
-
we as m,
|
|
335
|
-
J as p,
|
|
336
|
-
W as s
|
|
337
|
-
};
|