easyrouter-config 1.0.3 → 1.0.4
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/dist/index.js +88 -57
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,9 +16,11 @@ import {
|
|
|
16
16
|
import { execa } from "execa";
|
|
17
17
|
import pc from "picocolors";
|
|
18
18
|
import { parseArgs } from "util";
|
|
19
|
-
var VERSION = "0.
|
|
20
|
-
var
|
|
21
|
-
var
|
|
19
|
+
var VERSION = true ? "1.0.4" : "0.0.0-dev";
|
|
20
|
+
var PRIMARY_HOST = "https://easyrouter.io";
|
|
21
|
+
var FALLBACK_HOSTS = ["https://ezr.sh"];
|
|
22
|
+
var PROBE_TIMEOUT_MS = 5e3;
|
|
23
|
+
var CODEX_DEFAULT_MODEL = "gpt-5-codex";
|
|
22
24
|
function parseCliArgs() {
|
|
23
25
|
try {
|
|
24
26
|
const { values } = parseArgs({
|
|
@@ -60,76 +62,100 @@ ${pc.bold("\u53C2\u6570:")}
|
|
|
60
62
|
-k, --api-key <key> EasyRouter API Key\uFF08\u53EF\u7701\u7565 sk- \u524D\u7F00\uFF09
|
|
61
63
|
--only <tool> claude | codex \uFF08\u53EA\u914D\u5176\u4E00\uFF09
|
|
62
64
|
--skip-verify \u8DF3\u8FC7\u8FDE\u901A\u6027\u9A8C\u8BC1
|
|
63
|
-
-v, --verbose \u663E\u793A
|
|
65
|
+
-v, --verbose \u663E\u793A\u5E95\u5C42\u7EC6\u8282\uFF08\u5305\u62EC BaseURL \u63A2\u6D4B\u8FC7\u7A0B\uFF09
|
|
64
66
|
-h, --help \u663E\u793A\u5E2E\u52A9
|
|
65
67
|
-V, --version \u663E\u793A\u7248\u672C
|
|
66
68
|
|
|
67
69
|
${pc.bold("\u5185\u7F6E BaseURL:")}
|
|
68
|
-
Claude Code \u2192 ${
|
|
69
|
-
Codex \u2192 ${
|
|
70
|
+
Claude Code \u2192 ${PRIMARY_HOST}
|
|
71
|
+
Codex \u2192 ${PRIMARY_HOST}/v1
|
|
70
72
|
|
|
71
73
|
${pc.bold("\u539F\u7406:")}
|
|
72
|
-
\
|
|
73
|
-
\
|
|
74
|
+
Claude Code \u914D\u7F6E\u7531 ${pc.underline("zcf")} (https://github.com/UfoMiao/zcf) \u81EA\u52A8\u5199\u5165
|
|
75
|
+
Codex \u914D\u7F6E\u7531\u672C\u5DE5\u5177\u76F4\u63A5\u5199\u5165\uFF08\u66F4\u53EF\u63A7\u3001\u66F4\u7A33\u5B9A\uFF09
|
|
76
|
+
|
|
77
|
+
\u5B9E\u9645\u5199\u5165\u7684\u6587\u4EF6\uFF1A
|
|
74
78
|
\u2022 Claude Code \u2192 ~/.claude/settings.json
|
|
75
79
|
\u2022 Codex \u2192 ~/.codex/config.toml + ~/.codex/auth.json
|
|
76
80
|
`);
|
|
77
81
|
}
|
|
82
|
+
async function pickAvailableHost(verbose) {
|
|
83
|
+
const candidates = [PRIMARY_HOST, ...FALLBACK_HOSTS];
|
|
84
|
+
for (const host of candidates) {
|
|
85
|
+
const ok = await probeHost(host);
|
|
86
|
+
if (verbose) {
|
|
87
|
+
const tag = ok ? pc.green("\u2713") : pc.red("\u2717");
|
|
88
|
+
log.info(`${tag} \u63A2\u6D4B ${host} \u2192 ${ok ? "\u53EF\u8FBE" : "\u4E0D\u53EF\u8FBE"}`);
|
|
89
|
+
}
|
|
90
|
+
if (ok) return host;
|
|
91
|
+
}
|
|
92
|
+
if (verbose) log.warn(`\u6240\u6709\u5019\u9009\u57DF\u540D\u5747\u4E0D\u53EF\u8FBE\uFF0C\u4F7F\u7528\u4E3B\u57DF\u540D ${PRIMARY_HOST}`);
|
|
93
|
+
return PRIMARY_HOST;
|
|
94
|
+
}
|
|
95
|
+
async function probeHost(host) {
|
|
96
|
+
const url = host.replace(/\/+$/, "") + "/v1/models";
|
|
97
|
+
try {
|
|
98
|
+
const ctrl = new AbortController();
|
|
99
|
+
const timer = setTimeout(() => ctrl.abort(), PROBE_TIMEOUT_MS);
|
|
100
|
+
const res = await fetch(url, { method: "HEAD", signal: ctrl.signal });
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
return res.status > 0;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
78
107
|
var IDLE_TIMEOUT_MS = 6e4;
|
|
79
108
|
var HARD_TIMEOUT_MS = 3e5;
|
|
80
|
-
async function
|
|
109
|
+
async function configureViaZcf(opts) {
|
|
81
110
|
const args = [
|
|
82
111
|
"-y",
|
|
83
112
|
"zcf",
|
|
84
113
|
"init",
|
|
85
114
|
"-s",
|
|
86
|
-
// --skip-prompt
|
|
87
|
-
"
|
|
88
|
-
opts.
|
|
89
|
-
|
|
90
|
-
|
|
115
|
+
// --skip-prompt 非交互模式
|
|
116
|
+
"-T",
|
|
117
|
+
opts.target,
|
|
118
|
+
// 'cc' | 'codex'(用文档推荐的简写 -T)
|
|
119
|
+
// EasyRouter 是 OpenAI 兼容网关,统一用 api_key(Bearer Authorization)
|
|
91
120
|
"--api-type",
|
|
92
|
-
"
|
|
93
|
-
|
|
94
|
-
"-u",
|
|
95
|
-
opts.baseUrl,
|
|
96
|
-
"-k",
|
|
121
|
+
"api_key",
|
|
122
|
+
"--api-key",
|
|
97
123
|
opts.apiKey,
|
|
124
|
+
"--api-url",
|
|
125
|
+
opts.baseUrl,
|
|
98
126
|
"-r",
|
|
99
127
|
"backup",
|
|
100
128
|
// 已有配置自动备份
|
|
101
129
|
"--mcp-services",
|
|
102
130
|
"skip",
|
|
103
|
-
//
|
|
131
|
+
// 不装 MCP(即便 zcf 偶尔忽略也无伤)
|
|
104
132
|
"--workflows",
|
|
105
133
|
"skip",
|
|
106
|
-
// 不装工作流模板
|
|
107
134
|
"--output-styles",
|
|
108
135
|
"skip",
|
|
109
|
-
// 不装输出样式
|
|
110
136
|
"--install-cometix-line",
|
|
111
137
|
"false"
|
|
112
|
-
// 不装状态栏
|
|
113
138
|
];
|
|
139
|
+
if (opts.target === "codex") {
|
|
140
|
+
args.push("--api-model", CODEX_DEFAULT_MODEL);
|
|
141
|
+
}
|
|
114
142
|
const isWin = process.platform === "win32";
|
|
115
143
|
const cmd = isWin ? "npx.cmd" : "npx";
|
|
116
144
|
let capturedOut = "";
|
|
117
145
|
let capturedErr = "";
|
|
118
146
|
let lastDataAt = Date.now();
|
|
119
147
|
let killedReason = null;
|
|
148
|
+
let lastProgressAt = 0;
|
|
149
|
+
const PROGRESS_THROTTLE_MS = 200;
|
|
120
150
|
const child = execa(cmd, args, {
|
|
121
151
|
stdio: ["ignore", "pipe", "pipe"],
|
|
122
152
|
env: {
|
|
123
153
|
...process.env,
|
|
124
154
|
FORCE_COLOR: "0",
|
|
125
|
-
// 强制无颜色,免得 ANSI 干扰我们的检测
|
|
126
155
|
CI: "1",
|
|
127
|
-
// 让某些库自动进入"非交互模式"
|
|
128
156
|
npm_config_yes: "true"
|
|
129
|
-
// npx 自动 yes
|
|
130
157
|
},
|
|
131
158
|
reject: false,
|
|
132
|
-
// 我们自己处理退出码,避免 throw 时丢失输出
|
|
133
159
|
windowsHide: true
|
|
134
160
|
});
|
|
135
161
|
const handleChunk = (chunk, target) => {
|
|
@@ -138,9 +164,13 @@ async function runZcf(opts) {
|
|
|
138
164
|
else capturedErr += text2;
|
|
139
165
|
lastDataAt = Date.now();
|
|
140
166
|
if (opts.onProgress) {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
if (now - lastProgressAt >= PROGRESS_THROTTLE_MS) {
|
|
169
|
+
lastProgressAt = now;
|
|
170
|
+
const lines = text2.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
171
|
+
const last = lines[lines.length - 1];
|
|
172
|
+
if (last) opts.onProgress(last.slice(0, 60));
|
|
173
|
+
}
|
|
144
174
|
}
|
|
145
175
|
if (opts.verbose) {
|
|
146
176
|
process.stderr.write(text2);
|
|
@@ -180,7 +210,7 @@ async function runZcf(opts) {
|
|
|
180
210
|
`zcf \u8C03\u7528\u5931\u8D25\uFF1A${killedReason}
|
|
181
211
|
|
|
182
212
|
\u5E38\u89C1\u539F\u56E0\uFF1A
|
|
183
|
-
\u2022 \
|
|
213
|
+
\u2022 \u76EE\u6807\u5BA2\u6237\u7AEF\u672A\u5B89\u88C5\uFF08zcf \u8BD5\u56FE\u8BE2\u95EE\u662F\u5426\u5B89\u88C5\uFF09
|
|
184
214
|
\u2022 npm \u955C\u50CF/\u7F51\u7EDC\u6162\uFF0Cnpx \u4E0B\u8F7D zcf \u5305\u5361\u4F4F
|
|
185
215
|
\u2022 \u9632\u706B\u5899\u62E6\u622A\u4E86 registry.npmjs.org
|
|
186
216
|
|
|
@@ -190,7 +220,7 @@ async function runZcf(opts) {
|
|
|
190
220
|
if (exitCode !== 0 || signal) {
|
|
191
221
|
const detail = (capturedErr || capturedOut || "").slice(-2e3) || `exit ${exitCode}, signal ${signal}`;
|
|
192
222
|
throw new Error(
|
|
193
|
-
`zcf \u5F02\u5E38\u9000\u51FA\
|
|
223
|
+
`zcf \u5F02\u5E38\u9000\u51FA\uFF08exit=${exitCode}\uFF09:
|
|
194
224
|
${detail}
|
|
195
225
|
|
|
196
226
|
\u{1F4A1} \u7528 ${pc.green("--verbose")} \u91CD\u65B0\u8FD0\u884C\u53EF\u770B\u5230\u5B8C\u6574\u65E5\u5FD7`
|
|
@@ -200,7 +230,7 @@ ${detail}
|
|
|
200
230
|
if (/CACError|Unknown option/i.test(combined)) {
|
|
201
231
|
const match = combined.match(/(CACError:.*?)(?:\n|$)/);
|
|
202
232
|
throw new Error(
|
|
203
|
-
`zcf \u62D2\u7EDD\u4E86\u6211\u4EEC\u4F20\u7684\u53C2\u6570\
|
|
233
|
+
`zcf \u62D2\u7EDD\u4E86\u6211\u4EEC\u4F20\u7684\u53C2\u6570\uFF1A
|
|
204
234
|
${match?.[1] || "Unknown option"}
|
|
205
235
|
|
|
206
236
|
\u8FD9\u901A\u5E38\u610F\u5473\u7740 zcf \u5347\u7EA7\u540E flag \u547D\u540D\u53D8\u4E86\u3002\u8BF7\u8054\u7CFB service@easyrouter.io\u3002`
|
|
@@ -248,6 +278,10 @@ async function main() {
|
|
|
248
278
|
}
|
|
249
279
|
console.log();
|
|
250
280
|
intro(pc.bgCyan(pc.black(" EasyRouter Config ")) + pc.dim(" v" + VERSION));
|
|
281
|
+
const verbose = !!args.verbose;
|
|
282
|
+
const selectedHost = await pickAvailableHost(verbose);
|
|
283
|
+
const claudeBaseUrl = selectedHost;
|
|
284
|
+
const codexBaseUrl = selectedHost.replace(/\/+$/, "") + "/v1";
|
|
251
285
|
let apiKey;
|
|
252
286
|
if (args.apiKey) {
|
|
253
287
|
apiKey = normalizeApiKey(args.apiKey);
|
|
@@ -294,8 +328,8 @@ async function main() {
|
|
|
294
328
|
note(
|
|
295
329
|
[
|
|
296
330
|
`${pc.bold("\u8BA1\u8D39\u6A21\u5F0F")} \u6309\u91CF\u4ED8\u8D39`,
|
|
297
|
-
configClaude ? `${pc.bold("Claude URL")} ${
|
|
298
|
-
configCodex ? `${pc.bold("Codex URL")} ${
|
|
331
|
+
configClaude ? `${pc.bold("Claude URL")} ${PRIMARY_HOST}` : null,
|
|
332
|
+
configCodex ? `${pc.bold("Codex URL")} ${PRIMARY_HOST}/v1` : null,
|
|
299
333
|
`${pc.bold("API Key")} ${maskKey(apiKey)}`,
|
|
300
334
|
`${pc.bold("\u5C06\u914D\u7F6E")} ${[
|
|
301
335
|
configClaude && "Claude Code",
|
|
@@ -311,20 +345,18 @@ async function main() {
|
|
|
311
345
|
process.exit(0);
|
|
312
346
|
}
|
|
313
347
|
}
|
|
314
|
-
const verbose = !!args.verbose;
|
|
315
348
|
if (configClaude) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
const s = verbose || !process.stdout.isTTY ? null : spinner();
|
|
349
|
+
const useSpinner = !verbose && process.stdout.isTTY;
|
|
350
|
+
const s = useSpinner ? spinner() : null;
|
|
320
351
|
s?.start("\u914D\u7F6E Claude Code");
|
|
321
|
-
if (!s
|
|
352
|
+
if (!s) log.info("\u6B63\u5728\u914D\u7F6E Claude Code\uFF08\u9996\u6B21\u8FD0\u884C\u9700\u4E0B\u8F7D zcf\uFF0C\u7EA6 30~60 \u79D2\uFF09...");
|
|
322
353
|
try {
|
|
323
|
-
await
|
|
324
|
-
|
|
325
|
-
baseUrl:
|
|
354
|
+
await configureViaZcf({
|
|
355
|
+
target: "cc",
|
|
356
|
+
baseUrl: claudeBaseUrl,
|
|
326
357
|
apiKey,
|
|
327
358
|
verbose,
|
|
359
|
+
// 节流的进度回调:spinner 不会高频闪烁
|
|
328
360
|
onProgress: (line) => s?.message(`\u914D\u7F6E Claude Code \xB7 ${pc.dim(line)}`)
|
|
329
361
|
});
|
|
330
362
|
s?.stop(pc.green("\u2713 Claude Code \u914D\u7F6E\u5B8C\u6210 ") + pc.dim("\u2192 ~/.claude/settings.json"));
|
|
@@ -336,22 +368,20 @@ async function main() {
|
|
|
336
368
|
}
|
|
337
369
|
}
|
|
338
370
|
if (configCodex) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
const s = verbose || !process.stdout.isTTY ? null : spinner();
|
|
371
|
+
const useSpinner = !verbose && process.stdout.isTTY;
|
|
372
|
+
const s = useSpinner ? spinner() : null;
|
|
343
373
|
s?.start("\u914D\u7F6E Codex");
|
|
344
|
-
if (!s
|
|
374
|
+
if (!s) log.info("\u6B63\u5728\u914D\u7F6E Codex...");
|
|
345
375
|
try {
|
|
346
|
-
await
|
|
347
|
-
|
|
348
|
-
baseUrl:
|
|
376
|
+
await configureViaZcf({
|
|
377
|
+
target: "codex",
|
|
378
|
+
baseUrl: codexBaseUrl,
|
|
349
379
|
apiKey,
|
|
350
380
|
verbose,
|
|
351
381
|
onProgress: (line) => s?.message(`\u914D\u7F6E Codex \xB7 ${pc.dim(line)}`)
|
|
352
382
|
});
|
|
353
|
-
s?.stop(pc.green("\u2713 Codex \u914D\u7F6E\u5B8C\u6210 ") + pc.dim("\u2192 ~/.codex/config.toml"));
|
|
354
|
-
if (!s) log.success(pc.green("\u2713 Codex \u914D\u7F6E\u5B8C\u6210 \u2192 ~/.codex/config.toml"));
|
|
383
|
+
s?.stop(pc.green("\u2713 Codex \u914D\u7F6E\u5B8C\u6210 ") + pc.dim("\u2192 ~/.codex/config.toml + auth.json"));
|
|
384
|
+
if (!s) log.success(pc.green("\u2713 Codex \u914D\u7F6E\u5B8C\u6210 \u2192 ~/.codex/config.toml + auth.json"));
|
|
355
385
|
} catch (err) {
|
|
356
386
|
s?.stop(pc.red("\u2717 Codex \u914D\u7F6E\u5931\u8D25"));
|
|
357
387
|
console.error(err.message);
|
|
@@ -359,10 +389,11 @@ async function main() {
|
|
|
359
389
|
}
|
|
360
390
|
}
|
|
361
391
|
if (!args.skipVerify) {
|
|
362
|
-
const
|
|
392
|
+
const useSpinner = process.stdout.isTTY;
|
|
393
|
+
const s = useSpinner ? spinner() : null;
|
|
363
394
|
s?.start("\u9A8C\u8BC1\u8FDE\u901A\u6027");
|
|
364
395
|
if (!s) log.info("\u6B63\u5728\u9A8C\u8BC1\u8FDE\u901A\u6027...");
|
|
365
|
-
const result = await verifyConnection(
|
|
396
|
+
const result = await verifyConnection(claudeBaseUrl, apiKey);
|
|
366
397
|
if (result.ok) {
|
|
367
398
|
const msg = pc.green("\u2713 \u94FE\u8DEF\u9A8C\u8BC1\u901A\u8FC7");
|
|
368
399
|
s?.stop(msg) ?? log.success(msg);
|
|
@@ -378,7 +409,7 @@ async function main() {
|
|
|
378
409
|
if (configCodex) tips.push(pc.cyan("codex") + pc.dim(" # \u542F\u52A8 Codex"));
|
|
379
410
|
note(tips.join("\n"), "\u{1F389} \u5168\u90E8\u5B8C\u6210\uFF01\u73B0\u5728\u53EF\u4EE5\u8FD0\u884C\uFF1A");
|
|
380
411
|
outro(
|
|
381
|
-
pc.dim("\u611F\u8C22\u4F7F\u7528 EasyRouter ") + pc.underline(pc.cyan(
|
|
412
|
+
pc.dim("\u611F\u8C22\u4F7F\u7528 EasyRouter ") + pc.underline(pc.cyan(PRIMARY_HOST)) + pc.dim(" \xB7 Claude Code auto-configuration is powered by ") + pc.underline("UfoMiao/zcf")
|
|
382
413
|
);
|
|
383
414
|
}
|
|
384
415
|
main().catch((err) => {
|