easyrouter-config 1.0.3 → 1.0.5
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 +150 -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.5" : "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_FALLBACK_MODEL = "gpt-5.5";
|
|
22
24
|
function parseCliArgs() {
|
|
23
25
|
try {
|
|
24
26
|
const { values } = parseArgs({
|
|
@@ -60,76 +62,155 @@ ${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
|
+
}
|
|
107
|
+
var FETCH_MODELS_TIMEOUT_MS = 8e3;
|
|
108
|
+
async function fetchEasyRouterModels(host, verbose) {
|
|
109
|
+
const url = host.replace(/\/+$/, "") + "/api/pricing";
|
|
110
|
+
try {
|
|
111
|
+
const ctrl = new AbortController();
|
|
112
|
+
const timer = setTimeout(() => ctrl.abort(), FETCH_MODELS_TIMEOUT_MS);
|
|
113
|
+
const res = await fetch(url, { signal: ctrl.signal });
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
if (!res.ok) {
|
|
116
|
+
if (verbose) log.warn(`\u62C9\u53D6\u6A21\u578B\u5217\u8868\u5931\u8D25\uFF1AHTTP ${res.status}\uFF08\u5C06\u4F7F\u7528\u515C\u5E95\u6A21\u578B\uFF09`);
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const json = await res.json();
|
|
120
|
+
const models = Array.isArray(json?.data) ? json.data : [];
|
|
121
|
+
if (verbose) log.info(`\u2713 \u62C9\u53D6\u5230 ${models.length} \u4E2A EasyRouter \u6A21\u578B`);
|
|
122
|
+
return models;
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (verbose) log.warn(`\u62C9\u53D6\u6A21\u578B\u5217\u8868\u5F02\u5E38\uFF1A${err?.message || err}\uFF08\u5C06\u4F7F\u7528\u515C\u5E95\u6A21\u578B\uFF09`);
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function pickBestCodexModel(models) {
|
|
129
|
+
const isLlm = (m) => (m.tags || "").toUpperCase().includes("LLM");
|
|
130
|
+
const supportsOpenAI = (m) => !m.supported_endpoint_types || m.supported_endpoint_types.includes("openai");
|
|
131
|
+
const isGpt5 = (m) => /^gpt-5(\b|[.\-])/i.test(m.model_name);
|
|
132
|
+
const isSmallTier = (m) => /(^|[\-_.])(mini|nano|small|lite|tiny)([\-_.]|$)/i.test(m.model_name);
|
|
133
|
+
const byNameDesc = (a, b) => b.model_name.localeCompare(a.model_name, "en");
|
|
134
|
+
const gpt5Big = models.filter((m) => isGpt5(m) && isLlm(m) && supportsOpenAI(m) && !isSmallTier(m)).sort(byNameDesc);
|
|
135
|
+
if (gpt5Big.length) return gpt5Big[0].model_name;
|
|
136
|
+
const gpt5Any = models.filter((m) => isGpt5(m) && isLlm(m) && supportsOpenAI(m)).sort(byNameDesc);
|
|
137
|
+
if (gpt5Any.length) return gpt5Any[0].model_name;
|
|
138
|
+
const anyBig = models.filter((m) => isLlm(m) && supportsOpenAI(m) && !isSmallTier(m)).sort(byNameDesc);
|
|
139
|
+
if (anyBig.length) return anyBig[0].model_name;
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
async function resolveCodexModel(host, interactive, verbose) {
|
|
143
|
+
const models = await fetchEasyRouterModels(host, verbose);
|
|
144
|
+
const auto = pickBestCodexModel(models);
|
|
145
|
+
if (auto) {
|
|
146
|
+
if (verbose) log.info(`\u81EA\u52A8\u9009\u62E9 Codex \u6A21\u578B\uFF1A${pc.cyan(auto)}`);
|
|
147
|
+
return auto;
|
|
148
|
+
}
|
|
149
|
+
if (interactive && models.length) {
|
|
150
|
+
const candidates = models.filter((m) => /^gpt-5|codex/i.test(m.model_name)).sort((a, b) => b.model_name.localeCompare(a.model_name, "en")).slice(0, 8);
|
|
151
|
+
if (candidates.length) {
|
|
152
|
+
const picked = await select({
|
|
153
|
+
message: "\u672A\u81EA\u52A8\u8BC6\u522B Codex \u63A8\u8350\u6A21\u578B\uFF0C\u8BF7\u624B\u52A8\u9009\u62E9",
|
|
154
|
+
options: candidates.map((m) => ({ value: m.model_name, label: m.model_name }))
|
|
155
|
+
});
|
|
156
|
+
if (!isCancel(picked)) return picked;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (verbose) log.warn(`\u65E0\u6CD5\u81EA\u52A8\u9009\u62E9 Codex \u6A21\u578B\uFF0C\u4F7F\u7528\u515C\u5E95\uFF1A${CODEX_FALLBACK_MODEL}`);
|
|
160
|
+
return CODEX_FALLBACK_MODEL;
|
|
161
|
+
}
|
|
78
162
|
var IDLE_TIMEOUT_MS = 6e4;
|
|
79
163
|
var HARD_TIMEOUT_MS = 3e5;
|
|
80
|
-
async function
|
|
164
|
+
async function configureViaZcf(opts) {
|
|
81
165
|
const args = [
|
|
82
166
|
"-y",
|
|
83
167
|
"zcf",
|
|
84
168
|
"init",
|
|
85
169
|
"-s",
|
|
86
|
-
// --skip-prompt
|
|
87
|
-
"
|
|
88
|
-
opts.
|
|
89
|
-
|
|
90
|
-
|
|
170
|
+
// --skip-prompt 非交互模式
|
|
171
|
+
"-T",
|
|
172
|
+
opts.target,
|
|
173
|
+
// 'cc' | 'codex'(用文档推荐的简写 -T)
|
|
174
|
+
// EasyRouter 是 OpenAI 兼容网关,统一用 api_key(Bearer Authorization)
|
|
91
175
|
"--api-type",
|
|
92
|
-
"
|
|
93
|
-
|
|
94
|
-
"-u",
|
|
95
|
-
opts.baseUrl,
|
|
96
|
-
"-k",
|
|
176
|
+
"api_key",
|
|
177
|
+
"--api-key",
|
|
97
178
|
opts.apiKey,
|
|
179
|
+
"--api-url",
|
|
180
|
+
opts.baseUrl,
|
|
98
181
|
"-r",
|
|
99
182
|
"backup",
|
|
100
183
|
// 已有配置自动备份
|
|
101
184
|
"--mcp-services",
|
|
102
185
|
"skip",
|
|
103
|
-
//
|
|
186
|
+
// 不装 MCP(即便 zcf 偶尔忽略也无伤)
|
|
104
187
|
"--workflows",
|
|
105
188
|
"skip",
|
|
106
|
-
// 不装工作流模板
|
|
107
189
|
"--output-styles",
|
|
108
190
|
"skip",
|
|
109
|
-
// 不装输出样式
|
|
110
191
|
"--install-cometix-line",
|
|
111
192
|
"false"
|
|
112
|
-
// 不装状态栏
|
|
113
193
|
];
|
|
194
|
+
if (opts.target === "codex") {
|
|
195
|
+
args.push("--api-model", opts.apiModel || CODEX_FALLBACK_MODEL);
|
|
196
|
+
}
|
|
114
197
|
const isWin = process.platform === "win32";
|
|
115
198
|
const cmd = isWin ? "npx.cmd" : "npx";
|
|
116
199
|
let capturedOut = "";
|
|
117
200
|
let capturedErr = "";
|
|
118
201
|
let lastDataAt = Date.now();
|
|
119
202
|
let killedReason = null;
|
|
203
|
+
let lastProgressAt = 0;
|
|
204
|
+
const PROGRESS_THROTTLE_MS = 200;
|
|
120
205
|
const child = execa(cmd, args, {
|
|
121
206
|
stdio: ["ignore", "pipe", "pipe"],
|
|
122
207
|
env: {
|
|
123
208
|
...process.env,
|
|
124
209
|
FORCE_COLOR: "0",
|
|
125
|
-
// 强制无颜色,免得 ANSI 干扰我们的检测
|
|
126
210
|
CI: "1",
|
|
127
|
-
// 让某些库自动进入"非交互模式"
|
|
128
211
|
npm_config_yes: "true"
|
|
129
|
-
// npx 自动 yes
|
|
130
212
|
},
|
|
131
213
|
reject: false,
|
|
132
|
-
// 我们自己处理退出码,避免 throw 时丢失输出
|
|
133
214
|
windowsHide: true
|
|
134
215
|
});
|
|
135
216
|
const handleChunk = (chunk, target) => {
|
|
@@ -138,9 +219,13 @@ async function runZcf(opts) {
|
|
|
138
219
|
else capturedErr += text2;
|
|
139
220
|
lastDataAt = Date.now();
|
|
140
221
|
if (opts.onProgress) {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
if (now - lastProgressAt >= PROGRESS_THROTTLE_MS) {
|
|
224
|
+
lastProgressAt = now;
|
|
225
|
+
const lines = text2.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
226
|
+
const last = lines[lines.length - 1];
|
|
227
|
+
if (last) opts.onProgress(last.slice(0, 60));
|
|
228
|
+
}
|
|
144
229
|
}
|
|
145
230
|
if (opts.verbose) {
|
|
146
231
|
process.stderr.write(text2);
|
|
@@ -180,7 +265,7 @@ async function runZcf(opts) {
|
|
|
180
265
|
`zcf \u8C03\u7528\u5931\u8D25\uFF1A${killedReason}
|
|
181
266
|
|
|
182
267
|
\u5E38\u89C1\u539F\u56E0\uFF1A
|
|
183
|
-
\u2022 \
|
|
268
|
+
\u2022 \u76EE\u6807\u5BA2\u6237\u7AEF\u672A\u5B89\u88C5\uFF08zcf \u8BD5\u56FE\u8BE2\u95EE\u662F\u5426\u5B89\u88C5\uFF09
|
|
184
269
|
\u2022 npm \u955C\u50CF/\u7F51\u7EDC\u6162\uFF0Cnpx \u4E0B\u8F7D zcf \u5305\u5361\u4F4F
|
|
185
270
|
\u2022 \u9632\u706B\u5899\u62E6\u622A\u4E86 registry.npmjs.org
|
|
186
271
|
|
|
@@ -190,7 +275,7 @@ async function runZcf(opts) {
|
|
|
190
275
|
if (exitCode !== 0 || signal) {
|
|
191
276
|
const detail = (capturedErr || capturedOut || "").slice(-2e3) || `exit ${exitCode}, signal ${signal}`;
|
|
192
277
|
throw new Error(
|
|
193
|
-
`zcf \u5F02\u5E38\u9000\u51FA\
|
|
278
|
+
`zcf \u5F02\u5E38\u9000\u51FA\uFF08exit=${exitCode}\uFF09:
|
|
194
279
|
${detail}
|
|
195
280
|
|
|
196
281
|
\u{1F4A1} \u7528 ${pc.green("--verbose")} \u91CD\u65B0\u8FD0\u884C\u53EF\u770B\u5230\u5B8C\u6574\u65E5\u5FD7`
|
|
@@ -200,7 +285,7 @@ ${detail}
|
|
|
200
285
|
if (/CACError|Unknown option/i.test(combined)) {
|
|
201
286
|
const match = combined.match(/(CACError:.*?)(?:\n|$)/);
|
|
202
287
|
throw new Error(
|
|
203
|
-
`zcf \u62D2\u7EDD\u4E86\u6211\u4EEC\u4F20\u7684\u53C2\u6570\
|
|
288
|
+
`zcf \u62D2\u7EDD\u4E86\u6211\u4EEC\u4F20\u7684\u53C2\u6570\uFF1A
|
|
204
289
|
${match?.[1] || "Unknown option"}
|
|
205
290
|
|
|
206
291
|
\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 +333,10 @@ async function main() {
|
|
|
248
333
|
}
|
|
249
334
|
console.log();
|
|
250
335
|
intro(pc.bgCyan(pc.black(" EasyRouter Config ")) + pc.dim(" v" + VERSION));
|
|
336
|
+
const verbose = !!args.verbose;
|
|
337
|
+
const selectedHost = await pickAvailableHost(verbose);
|
|
338
|
+
const claudeBaseUrl = selectedHost;
|
|
339
|
+
const codexBaseUrl = selectedHost.replace(/\/+$/, "") + "/v1";
|
|
251
340
|
let apiKey;
|
|
252
341
|
if (args.apiKey) {
|
|
253
342
|
apiKey = normalizeApiKey(args.apiKey);
|
|
@@ -294,8 +383,8 @@ async function main() {
|
|
|
294
383
|
note(
|
|
295
384
|
[
|
|
296
385
|
`${pc.bold("\u8BA1\u8D39\u6A21\u5F0F")} \u6309\u91CF\u4ED8\u8D39`,
|
|
297
|
-
configClaude ? `${pc.bold("Claude URL")} ${
|
|
298
|
-
configCodex ? `${pc.bold("Codex URL")} ${
|
|
386
|
+
configClaude ? `${pc.bold("Claude URL")} ${PRIMARY_HOST}` : null,
|
|
387
|
+
configCodex ? `${pc.bold("Codex URL")} ${PRIMARY_HOST}/v1` : null,
|
|
299
388
|
`${pc.bold("API Key")} ${maskKey(apiKey)}`,
|
|
300
389
|
`${pc.bold("\u5C06\u914D\u7F6E")} ${[
|
|
301
390
|
configClaude && "Claude Code",
|
|
@@ -311,20 +400,18 @@ async function main() {
|
|
|
311
400
|
process.exit(0);
|
|
312
401
|
}
|
|
313
402
|
}
|
|
314
|
-
const verbose = !!args.verbose;
|
|
315
403
|
if (configClaude) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
const s = verbose || !process.stdout.isTTY ? null : spinner();
|
|
404
|
+
const useSpinner = !verbose && process.stdout.isTTY;
|
|
405
|
+
const s = useSpinner ? spinner() : null;
|
|
320
406
|
s?.start("\u914D\u7F6E Claude Code");
|
|
321
|
-
if (!s
|
|
407
|
+
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
408
|
try {
|
|
323
|
-
await
|
|
324
|
-
|
|
325
|
-
baseUrl:
|
|
409
|
+
await configureViaZcf({
|
|
410
|
+
target: "cc",
|
|
411
|
+
baseUrl: claudeBaseUrl,
|
|
326
412
|
apiKey,
|
|
327
413
|
verbose,
|
|
414
|
+
// 节流的进度回调:spinner 不会高频闪烁
|
|
328
415
|
onProgress: (line) => s?.message(`\u914D\u7F6E Claude Code \xB7 ${pc.dim(line)}`)
|
|
329
416
|
});
|
|
330
417
|
s?.stop(pc.green("\u2713 Claude Code \u914D\u7F6E\u5B8C\u6210 ") + pc.dim("\u2192 ~/.claude/settings.json"));
|
|
@@ -336,22 +423,27 @@ async function main() {
|
|
|
336
423
|
}
|
|
337
424
|
}
|
|
338
425
|
if (configCodex) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
426
|
+
const codexModel = await resolveCodexModel(
|
|
427
|
+
selectedHost,
|
|
428
|
+
/* interactive */
|
|
429
|
+
!args.apiKey,
|
|
430
|
+
verbose
|
|
431
|
+
);
|
|
432
|
+
const useSpinner = !verbose && process.stdout.isTTY;
|
|
433
|
+
const s = useSpinner ? spinner() : null;
|
|
343
434
|
s?.start("\u914D\u7F6E Codex");
|
|
344
|
-
if (!s
|
|
435
|
+
if (!s) log.info("\u6B63\u5728\u914D\u7F6E Codex...");
|
|
345
436
|
try {
|
|
346
|
-
await
|
|
347
|
-
|
|
348
|
-
baseUrl:
|
|
437
|
+
await configureViaZcf({
|
|
438
|
+
target: "codex",
|
|
439
|
+
baseUrl: codexBaseUrl,
|
|
349
440
|
apiKey,
|
|
441
|
+
apiModel: codexModel,
|
|
350
442
|
verbose,
|
|
351
443
|
onProgress: (line) => s?.message(`\u914D\u7F6E Codex \xB7 ${pc.dim(line)}`)
|
|
352
444
|
});
|
|
353
|
-
s?.stop(pc.green("\u2713 Codex \u914D\u7F6E\u5B8C\u6210 ") + pc.dim(
|
|
354
|
-
if (!s) log.success(pc.green(
|
|
445
|
+
s?.stop(pc.green("\u2713 Codex \u914D\u7F6E\u5B8C\u6210 ") + pc.dim(`\u2192 model=${codexModel}`));
|
|
446
|
+
if (!s) log.success(pc.green(`\u2713 Codex \u914D\u7F6E\u5B8C\u6210 \u2192 model=${codexModel}`));
|
|
355
447
|
} catch (err) {
|
|
356
448
|
s?.stop(pc.red("\u2717 Codex \u914D\u7F6E\u5931\u8D25"));
|
|
357
449
|
console.error(err.message);
|
|
@@ -359,10 +451,11 @@ async function main() {
|
|
|
359
451
|
}
|
|
360
452
|
}
|
|
361
453
|
if (!args.skipVerify) {
|
|
362
|
-
const
|
|
454
|
+
const useSpinner = process.stdout.isTTY;
|
|
455
|
+
const s = useSpinner ? spinner() : null;
|
|
363
456
|
s?.start("\u9A8C\u8BC1\u8FDE\u901A\u6027");
|
|
364
457
|
if (!s) log.info("\u6B63\u5728\u9A8C\u8BC1\u8FDE\u901A\u6027...");
|
|
365
|
-
const result = await verifyConnection(
|
|
458
|
+
const result = await verifyConnection(claudeBaseUrl, apiKey);
|
|
366
459
|
if (result.ok) {
|
|
367
460
|
const msg = pc.green("\u2713 \u94FE\u8DEF\u9A8C\u8BC1\u901A\u8FC7");
|
|
368
461
|
s?.stop(msg) ?? log.success(msg);
|
|
@@ -378,7 +471,7 @@ async function main() {
|
|
|
378
471
|
if (configCodex) tips.push(pc.cyan("codex") + pc.dim(" # \u542F\u52A8 Codex"));
|
|
379
472
|
note(tips.join("\n"), "\u{1F389} \u5168\u90E8\u5B8C\u6210\uFF01\u73B0\u5728\u53EF\u4EE5\u8FD0\u884C\uFF1A");
|
|
380
473
|
outro(
|
|
381
|
-
pc.dim("\u611F\u8C22\u4F7F\u7528 EasyRouter ") + pc.underline(pc.cyan(
|
|
474
|
+
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
475
|
);
|
|
383
476
|
}
|
|
384
477
|
main().catch((err) => {
|