@ccsuite/cli-router 0.0.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 +11 -0
- package/es/cli.mjs +76 -0
- package/es/env-guard-FutNkHyi.js +175 -0
- package/es/index.mjs +13 -0
- package/package.json +62 -0
- package/types/cli.d.ts +1 -0
- package/types/index.d.ts +76 -0
package/README.md
ADDED
package/es/cli.mjs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn as I } from "node:child_process";
|
|
3
|
+
import { p as m, l as E, s as S, f as h, a as u, c as v, w as g, r as G, d as w, h as y, S as T, b as P } from "./env-guard-FutNkHyi.js";
|
|
4
|
+
const x = "claude", N = async () => {
|
|
5
|
+
const { profileName: e, passthrough: s } = m(process.argv.slice(2)), c = E(), { profile: n } = S(c, e);
|
|
6
|
+
if (h(n.env).length > 0) {
|
|
7
|
+
try {
|
|
8
|
+
await u(n);
|
|
9
|
+
} catch (o) {
|
|
10
|
+
if (o instanceof v)
|
|
11
|
+
return process.stderr.write(`已取消,未写入配置。
|
|
12
|
+
`), process.exit(1);
|
|
13
|
+
throw o;
|
|
14
|
+
}
|
|
15
|
+
g(c), G();
|
|
16
|
+
}
|
|
17
|
+
const p = w(), i = y(p);
|
|
18
|
+
if (i.length > 0)
|
|
19
|
+
return process.stderr.write(
|
|
20
|
+
`检测到 ${T} 的 env 块含模型路由类 key:${i.join(", ")}。请手动清理这些 key 后重试。
|
|
21
|
+
`
|
|
22
|
+
), process.exit(1);
|
|
23
|
+
const d = P(process.env, n.env), a = I(x, s, {
|
|
24
|
+
stdio: "inherit",
|
|
25
|
+
shell: !1,
|
|
26
|
+
env: d
|
|
27
|
+
});
|
|
28
|
+
return new Promise((o, l) => {
|
|
29
|
+
a.on("error", (r) => {
|
|
30
|
+
try {
|
|
31
|
+
if (r.code === "ENOENT") {
|
|
32
|
+
process.stderr.write(
|
|
33
|
+
`未找到 claude 可执行文件,请确认 claude-code 已安装并在 PATH 中。
|
|
34
|
+
`
|
|
35
|
+
), process.exit(127);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
process.stderr.write(`启动 claude 失败:${r.message}
|
|
39
|
+
`), process.exit(1);
|
|
40
|
+
} catch (t) {
|
|
41
|
+
l(t);
|
|
42
|
+
}
|
|
43
|
+
}), a.on("exit", (r, t) => {
|
|
44
|
+
try {
|
|
45
|
+
if (t) {
|
|
46
|
+
process.exit(128 + A(t));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
process.exit(r ?? 1);
|
|
50
|
+
} catch (f) {
|
|
51
|
+
l(f);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}, A = (e) => ({
|
|
56
|
+
SIGHUP: 1,
|
|
57
|
+
SIGINT: 2,
|
|
58
|
+
SIGQUIT: 3,
|
|
59
|
+
SIGILL: 4,
|
|
60
|
+
SIGABRT: 6,
|
|
61
|
+
SIGFPE: 8,
|
|
62
|
+
SIGKILL: 9,
|
|
63
|
+
SIGSEGV: 11,
|
|
64
|
+
SIGPIPE: 13,
|
|
65
|
+
SIGALRM: 14,
|
|
66
|
+
SIGTERM: 15
|
|
67
|
+
})[e] ?? 1, C = async () => {
|
|
68
|
+
try {
|
|
69
|
+
await N();
|
|
70
|
+
} catch (e) {
|
|
71
|
+
const s = e instanceof Error ? e.message : String(e);
|
|
72
|
+
process.stderr.write(`${s}
|
|
73
|
+
`), process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
C();
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import T from "node:readline";
|
|
3
|
+
import i from "node:fs";
|
|
4
|
+
import p from "node:path";
|
|
5
|
+
import m from "node:os";
|
|
6
|
+
const n = p.join(
|
|
7
|
+
m.homedir(),
|
|
8
|
+
".ccsuite",
|
|
9
|
+
"router",
|
|
10
|
+
"profile.json"
|
|
11
|
+
), d = "--meta-profile=", U = (e) => {
|
|
12
|
+
let r;
|
|
13
|
+
const s = [];
|
|
14
|
+
for (const t of e) {
|
|
15
|
+
if (t.startsWith(d)) {
|
|
16
|
+
r = t.slice(d.length);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
s.push(t);
|
|
20
|
+
}
|
|
21
|
+
return { profileName: r, passthrough: s };
|
|
22
|
+
}, g = (e, r) => {
|
|
23
|
+
const s = r ?? e.defaultProfile, t = e.profiles[s];
|
|
24
|
+
if (!t) {
|
|
25
|
+
const o = Object.keys(e.profiles).join(", ") || "(无)";
|
|
26
|
+
throw new Error(
|
|
27
|
+
`profile "${s}" 不存在。可用 profile:${o}。配置文件:${n}`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return { name: s, profile: t };
|
|
31
|
+
}, L = /TOKEN|KEY|SECRET/i;
|
|
32
|
+
class A extends Error {
|
|
33
|
+
constructor() {
|
|
34
|
+
super("用户中断输入"), this.name = "PromptAbortError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const S = (e) => L.test(e), H = (e) => e === "" ? "" : "******", w = (e) => Object.keys(e).filter((r) => e[r] === "");
|
|
38
|
+
let E = !1, c = null;
|
|
39
|
+
const C = (e) => new Promise((r, s) => {
|
|
40
|
+
const t = T.createInterface({
|
|
41
|
+
input: process.stdin,
|
|
42
|
+
output: process.stderr
|
|
43
|
+
});
|
|
44
|
+
c = t, t.on("close", () => {
|
|
45
|
+
c === t && (c = null);
|
|
46
|
+
}), t.question(e, (o) => {
|
|
47
|
+
t.close(), r(o);
|
|
48
|
+
}), t.on("SIGINT", () => {
|
|
49
|
+
t.close(), s(new A());
|
|
50
|
+
});
|
|
51
|
+
}), P = 3, h = 4, v = 127, R = (e) => new Promise((r, s) => {
|
|
52
|
+
process.stderr.write(e);
|
|
53
|
+
const t = process.stdin;
|
|
54
|
+
typeof t.setRawMode == "function" && (t.setRawMode(!0), E = !0), t.resume(), t.setEncoding("utf8");
|
|
55
|
+
let o = "";
|
|
56
|
+
const l = () => {
|
|
57
|
+
t.removeListener("data", u);
|
|
58
|
+
}, u = (y) => {
|
|
59
|
+
for (const a of y) {
|
|
60
|
+
const f = a.charCodeAt(0);
|
|
61
|
+
if (a === "\r" || a === `
|
|
62
|
+
`) {
|
|
63
|
+
l(), process.stderr.write(`
|
|
64
|
+
`), r(o);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (f === P || f === h) {
|
|
68
|
+
l(), process.stderr.write(`
|
|
69
|
+
`), s(new A());
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (f === v || a === "\b") {
|
|
73
|
+
o = o.slice(0, -1);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
o += a;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
t.on("data", u);
|
|
80
|
+
}), K = async (e) => {
|
|
81
|
+
const r = w(e.env);
|
|
82
|
+
for (const s of r) {
|
|
83
|
+
const t = S(s);
|
|
84
|
+
let o = "";
|
|
85
|
+
for (; o === ""; ) {
|
|
86
|
+
const l = `请输入 ${s}: `;
|
|
87
|
+
o = t ? await R(l) : await C(l);
|
|
88
|
+
}
|
|
89
|
+
e.env[s] = o;
|
|
90
|
+
}
|
|
91
|
+
}, $ = () => {
|
|
92
|
+
c && (c.close(), c = null);
|
|
93
|
+
const e = process.stdin;
|
|
94
|
+
E && typeof e.setRawMode == "function" && (e.setRawMode(!1), E = !1), e.removeAllListeners("data"), e.removeAllListeners("keypress"), e.removeAllListeners("readable"), e.pause();
|
|
95
|
+
}, _ = {
|
|
96
|
+
defaultProfile: "deepseek",
|
|
97
|
+
profiles: {
|
|
98
|
+
deepseek: {
|
|
99
|
+
env: {
|
|
100
|
+
ANTHROPIC_BASE_URL: "https://api.deepseek.com/anthropic",
|
|
101
|
+
ANTHROPIC_AUTH_TOKEN: "",
|
|
102
|
+
ANTHROPIC_MODEL: "deepseek-v4-pro[1m]",
|
|
103
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "deepseek-v4-pro[1m]",
|
|
104
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "deepseek-v4-pro[1m]",
|
|
105
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "deepseek-v4-flash[1m]",
|
|
106
|
+
CLAUDE_CODE_SUBAGENT_MODEL: "deepseek-v4-pro[1m]",
|
|
107
|
+
CLAUDE_CODE_EFFORT_LEVEL: "max"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}, D = (e) => {
|
|
112
|
+
i.mkdirSync(p.dirname(n), { recursive: !0 }), i.writeFileSync(n, JSON.stringify(e, null, 2)), i.chmodSync(n, 384);
|
|
113
|
+
}, V = () => {
|
|
114
|
+
if (!i.existsSync(n))
|
|
115
|
+
return D(_), _;
|
|
116
|
+
const e = i.readFileSync(n, "utf8");
|
|
117
|
+
let r;
|
|
118
|
+
try {
|
|
119
|
+
r = JSON.parse(e);
|
|
120
|
+
} catch (t) {
|
|
121
|
+
const o = t instanceof Error ? t.message : String(t);
|
|
122
|
+
throw new Error(`配置文件非法 JSON:${n}(${o})`);
|
|
123
|
+
}
|
|
124
|
+
if (typeof r != "object" || r === null)
|
|
125
|
+
throw new Error(`配置文件结构非法:${n}(应为 JSON 对象)`);
|
|
126
|
+
const s = r;
|
|
127
|
+
if (typeof s.defaultProfile != "string")
|
|
128
|
+
throw new Error(`配置文件缺少字符串字段 defaultProfile:${n}`);
|
|
129
|
+
if (typeof s.profiles != "object" || s.profiles === null || Array.isArray(s.profiles))
|
|
130
|
+
throw new Error(`配置文件缺少对象字段 profiles:${n}`);
|
|
131
|
+
return s;
|
|
132
|
+
}, N = /^ANTHROPIC_/, b = [
|
|
133
|
+
"CLAUDE_CODE_SUBAGENT_MODEL",
|
|
134
|
+
"CLAUDE_CODE_EFFORT_LEVEL"
|
|
135
|
+
], I = p.join(
|
|
136
|
+
m.homedir(),
|
|
137
|
+
".claude",
|
|
138
|
+
"settings.json"
|
|
139
|
+
), O = (e) => N.test(e) || b.includes(e), J = (e, r) => {
|
|
140
|
+
const s = {};
|
|
141
|
+
for (const [t, o] of Object.entries(e))
|
|
142
|
+
o !== void 0 && (O(t) || (s[t] = o));
|
|
143
|
+
for (const [t, o] of Object.entries(r))
|
|
144
|
+
s[t] = o;
|
|
145
|
+
return s;
|
|
146
|
+
}, x = (e) => !e || typeof e != "object" ? [] : Object.keys(e).filter((r) => O(r)), B = () => {
|
|
147
|
+
let e;
|
|
148
|
+
try {
|
|
149
|
+
e = JSON.parse(i.readFileSync(I, "utf8"));
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
if (typeof e != "object" || e === null)
|
|
154
|
+
return null;
|
|
155
|
+
const r = e.env;
|
|
156
|
+
return typeof r != "object" || r === null || Array.isArray(r) ? null : r;
|
|
157
|
+
};
|
|
158
|
+
export {
|
|
159
|
+
_ as D,
|
|
160
|
+
n as P,
|
|
161
|
+
I as S,
|
|
162
|
+
K as a,
|
|
163
|
+
J as b,
|
|
164
|
+
A as c,
|
|
165
|
+
B as d,
|
|
166
|
+
w as f,
|
|
167
|
+
x as h,
|
|
168
|
+
S as i,
|
|
169
|
+
V as l,
|
|
170
|
+
H as m,
|
|
171
|
+
U as p,
|
|
172
|
+
$ as r,
|
|
173
|
+
g as s,
|
|
174
|
+
D as w
|
|
175
|
+
};
|
package/es/index.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { D as e, P as E, b as i, f as l, h as n, i as r, m as o, p as P, s as d } from "./env-guard-FutNkHyi.js";
|
|
3
|
+
export {
|
|
4
|
+
e as DEEPSEEK_TEMPLATE,
|
|
5
|
+
E as PROFILE_PATH,
|
|
6
|
+
i as buildChildEnv,
|
|
7
|
+
l as findEmptyKeys,
|
|
8
|
+
n as hasModelEnvConflict,
|
|
9
|
+
r as isSecretKey,
|
|
10
|
+
o as maskValue,
|
|
11
|
+
P as parseArgv,
|
|
12
|
+
d as selectProfile
|
|
13
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ccsuite/cli-router",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "claude-code路由",
|
|
5
|
+
"private": false,
|
|
6
|
+
"module": "es/index.mjs",
|
|
7
|
+
"types": "types/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"cc-router": "es/cli.mjs"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./es/index.mjs",
|
|
15
|
+
"types": "./types/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"es",
|
|
20
|
+
"lib",
|
|
21
|
+
"types"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"predev": "dc-inject",
|
|
25
|
+
"dev": "vite build -w -m hotBuild",
|
|
26
|
+
"prebuild": "dc-inject",
|
|
27
|
+
"build": "vite build",
|
|
28
|
+
"prepack": "pnpm build",
|
|
29
|
+
"test": "vitest",
|
|
30
|
+
"coverage": "vitest run --coverage"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/done-coding/ccsuite.git",
|
|
35
|
+
"directory": "packages/router"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public",
|
|
39
|
+
"registry": "https://registry.npmjs.org/"
|
|
40
|
+
},
|
|
41
|
+
"author": "JustSoSu",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@done-coding/cli-inject": "^0.5.17",
|
|
46
|
+
"@types/node": "^18.0.0",
|
|
47
|
+
"@types/prompts": "^2.4.6",
|
|
48
|
+
"@types/yargs": "^17.0.28",
|
|
49
|
+
"@vitest/coverage-v8": "^1.6.1",
|
|
50
|
+
"typescript": "^5.8.3",
|
|
51
|
+
"vite": "^5.0.10",
|
|
52
|
+
"vite-plugin-dts": "^3.7.0",
|
|
53
|
+
"vitest": "^1.6.1"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@done-coding/cli-utils": "^0.7.4"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18.0.0"
|
|
60
|
+
},
|
|
61
|
+
"gitHead": "e594cd05886a94ddfc97055239a7794e500e03bd"
|
|
62
|
+
}
|
package/types/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { }
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REQ-7 层 1:strip-then-inject(纯函数,可单测)。
|
|
3
|
+
* 复制 processEnv → 删除所有模型路由类 key → 注入 profileEnv(顺序不可反)。
|
|
4
|
+
* 仅作用于返回的子进程 env 对象,[MUST NOT] 修改 cc-router 自身 process.env,
|
|
5
|
+
* [MUST NOT] 读/写任何文件。空 profileEnv `{}` 仍 strip(不回退继承值)。
|
|
6
|
+
* 非模型 CLAUDE_CODE_* / PATH / HOME 等一律不动。
|
|
7
|
+
*/
|
|
8
|
+
export declare const buildChildEnv: (processEnv: NodeJS.ProcessEnv, profileEnv: Record<string, string>) => Record<string, string>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* REQ-3 内置 deepseek 模板。键集合与 requirements REQ-3 表逐键一致;
|
|
12
|
+
* ANTHROPIC_AUTH_TOKEN 恒为空字符串(绝不含真实 token)。
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEEPSEEK_TEMPLATE: ProfileConfig;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 找出值严格等于 `""` 的键(纯函数,可单测)。
|
|
18
|
+
* 仅严格空字符串命中("REPLACE_ME" 等非空占位不算);按 Object.keys 插入顺序返回。
|
|
19
|
+
*/
|
|
20
|
+
export declare const findEmptyKeys: (env: Record<string, string>) => string[];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* REQ-7 层 2 纯判定(纯函数,可单测):返回 settings.json env 块中
|
|
24
|
+
* 命中模型路由类判定的 key 列表(判定规则与层 1 完全一致)。
|
|
25
|
+
*/
|
|
26
|
+
export declare const hasModelEnvConflict: (settingsEnvObj: Record<string, unknown> | null | undefined) => string[];
|
|
27
|
+
|
|
28
|
+
/** 键名是否为敏感(token 类)键(纯函数,可单测)。 */
|
|
29
|
+
export declare const isSecretKey: (key: string) => boolean;
|
|
30
|
+
|
|
31
|
+
/** 固定遮蔽:不含任何明文片段(纯函数,可单测,REQ-6 报错脱敏用)。 */
|
|
32
|
+
export declare const maskValue: (v: string) => string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 解析 argv(纯函数,可单测)。
|
|
36
|
+
* 仅消费 `--meta-profile=<name>`(多个取最后一个生效,被消费项不入 passthrough);
|
|
37
|
+
* 其余所有参数按原始顺序原样收集,[MUST NOT] trim/去引号/合并/重排/校验。
|
|
38
|
+
*/
|
|
39
|
+
export declare const parseArgv: (argv: string[]) => ParsedArgv;
|
|
40
|
+
|
|
41
|
+
/** argv 解析结果:消费的 --meta-profile + 其余原样透传 */
|
|
42
|
+
export declare interface ParsedArgv {
|
|
43
|
+
profileName?: string;
|
|
44
|
+
passthrough: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 单个 profile:可选描述 + env 注入键值 */
|
|
48
|
+
export declare interface Profile {
|
|
49
|
+
describe?: string;
|
|
50
|
+
env: Record<string, string>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* profile 配置文件唯一路径:~/.ccsuite/router/profile.json
|
|
55
|
+
* `~` 不写死具体用户,由 os.homedir() 解析当前用户 home。
|
|
56
|
+
*/
|
|
57
|
+
export declare const PROFILE_PATH: string;
|
|
58
|
+
|
|
59
|
+
/** profile 配置文件结构(~/.ccsuite/router/profile.json) */
|
|
60
|
+
export declare interface ProfileConfig {
|
|
61
|
+
defaultProfile: string;
|
|
62
|
+
profiles: Record<string, Profile>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 选择 profile(纯函数,可单测)。
|
|
67
|
+
* 有 profileName 用之,否则用 cfg.defaultProfile。
|
|
68
|
+
* 目标不存在 / defaultProfile 悬空 → throw(列可用名 + 配置路径),
|
|
69
|
+
* [MUST NOT] 猜最近名 / 回退 default。profile.env 为空 `{}` 合法。
|
|
70
|
+
*/
|
|
71
|
+
export declare const selectProfile: (cfg: ProfileConfig, profileName?: string) => {
|
|
72
|
+
name: string;
|
|
73
|
+
profile: Profile;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export { }
|