claude360 0.2.8 → 0.3.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 +107 -126
- package/bin/claude360.js +7 -3
- package/package.json +1 -1
- package/src/account-status.js +8 -5
- package/src/auth.js +9 -2
- package/src/banner.js +47 -6
- package/src/cc-switch.js +34 -6
- package/src/colors.js +31 -0
- package/src/diagnostics.js +68 -25
- package/src/glyphs.js +33 -0
- package/src/index.js +248 -105
- package/src/init-config.js +61 -27
- package/src/init-flow.js +25 -6
- package/src/mcp-skill.js +48 -21
- package/src/menu.js +20 -13
- package/src/messages.js +78 -0
- package/src/notices.js +33 -0
- package/src/onboarding.js +12 -5
- package/src/prompts.js +7 -6
- package/src/token-manager.js +90 -20
- package/src/tool-installer.js +25 -9
- package/src/tool-launcher.js +150 -46
- package/src/topup.js +55 -5
- package/src/ui.js +264 -8
- package/src/workflows.js +10 -7
- package/src/zcf-notice.js +15 -1
package/src/index.js
CHANGED
|
@@ -10,7 +10,9 @@ import { colorLevel, formatCheckLine, playBanner, renderBanner } from "./banner.
|
|
|
10
10
|
import { runCcSwitchGenerator } from "./cc-switch.js";
|
|
11
11
|
import { createConfigStore } from "./config-store.js";
|
|
12
12
|
import { createPrompts, isInteractive } from "./prompts.js";
|
|
13
|
-
import {
|
|
13
|
+
import { createMessenger, formatMessage } from "./messages.js";
|
|
14
|
+
import { renderDivider, renderHeader, renderKeyValueTable, renderStructuredError, renderTaskEnd, renderTaskStart, renderTaskStep, selectedSummary, warningBlock } from "./ui.js";
|
|
15
|
+
import { CHANGELOG_ENTRIES, LICENSE_TEXT } from "./notices.js";
|
|
14
16
|
import {
|
|
15
17
|
commandVersion,
|
|
16
18
|
defaultExecCommand,
|
|
@@ -49,9 +51,10 @@ import {
|
|
|
49
51
|
launchClaudeCode as startClaudeCode,
|
|
50
52
|
launchCodex as startCodex,
|
|
51
53
|
resolveCodexConfigPath,
|
|
54
|
+
resolveCodexProfileConfigPath,
|
|
52
55
|
} from "./tool-launcher.js";
|
|
53
56
|
import { installClaudeWorkflows, installCodexWorkflows } from "./workflows.js";
|
|
54
|
-
import { showOpenSourceNotice } from "./zcf-notice.js";
|
|
57
|
+
import { formatOpenSourceNoticeDetail, showOpenSourceNotice } from "./zcf-notice.js";
|
|
55
58
|
import { runWechatTopUp } from "./topup.js";
|
|
56
59
|
import { chooseOrCreateToken, createNewToken } from "./token-manager.js";
|
|
57
60
|
|
|
@@ -105,6 +108,7 @@ export async function runCli({
|
|
|
105
108
|
writeLine = console.log,
|
|
106
109
|
forceSetup = false,
|
|
107
110
|
command = "",
|
|
111
|
+
verbose = false,
|
|
108
112
|
checkEnvironment = ensureEnvironment,
|
|
109
113
|
showBanner = true,
|
|
110
114
|
version = readCliVersion(),
|
|
@@ -114,6 +118,10 @@ export async function runCli({
|
|
|
114
118
|
let api = createApiClient({ baseUrl, cliToken: config.cliToken || "" });
|
|
115
119
|
// 仅在真实终端(writeLine 未被测试替换)启用彩色与动效
|
|
116
120
|
const fancyOutput = writeLine === console.log ? colorLevel() : 0;
|
|
121
|
+
// 语义消息器:把行内交互文字按角色着色(成功/失败/警告/步骤/键值回显/路径/
|
|
122
|
+
// 次要说明等),统一层级,见 messages.js。color 跟随 fancyOutput,
|
|
123
|
+
// 测试/管道(writeLine 被替换)下为 0,输出退化为纯文本前缀。
|
|
124
|
+
const msg = createMessenger({ writeLine, color: fancyOutput });
|
|
117
125
|
// 方向键交互:真实 TTY 且交互未被测试替换时启用(优化需求第五节),
|
|
118
126
|
// 非交互环境降级为编号输入,由 prompts.js 统一处理
|
|
119
127
|
const interactiveUi = writeLine === console.log && isInteractive();
|
|
@@ -192,8 +200,8 @@ export async function runCli({
|
|
|
192
200
|
}
|
|
193
201
|
break;
|
|
194
202
|
}
|
|
195
|
-
|
|
196
|
-
|
|
203
|
+
msg.info("请在浏览器中完成 Claude360 授权。");
|
|
204
|
+
msg.info("如果浏览器没有自动打开,请根据终端提示访问授权页面并输入授权码。");
|
|
197
205
|
let cliToken;
|
|
198
206
|
try {
|
|
199
207
|
cliToken = await authWithBrowser({ api, openBrowser, sleep, writeLine });
|
|
@@ -206,7 +214,7 @@ export async function runCli({
|
|
|
206
214
|
}
|
|
207
215
|
await saveConfig({ cliToken });
|
|
208
216
|
api = createApiClient({ baseUrl, cliToken });
|
|
209
|
-
|
|
217
|
+
msg.success("授权成功,登录状态已保存。");
|
|
210
218
|
return true;
|
|
211
219
|
}
|
|
212
220
|
|
|
@@ -241,11 +249,14 @@ export async function runCli({
|
|
|
241
249
|
async function showBalanceAndUsage() {
|
|
242
250
|
const me = await fetchMe();
|
|
243
251
|
if (!me) {
|
|
244
|
-
|
|
252
|
+
msg.error("暂时无法获取余额与今日用量。");
|
|
245
253
|
return null;
|
|
246
254
|
}
|
|
247
|
-
|
|
248
|
-
|
|
255
|
+
msg.result("余额", me.balance_display ?? me.quota);
|
|
256
|
+
if (me.low_balance) {
|
|
257
|
+
msg.warn("余额较低");
|
|
258
|
+
}
|
|
259
|
+
msg.result("今日用量", me.today_usage_display || "-");
|
|
249
260
|
return me;
|
|
250
261
|
}
|
|
251
262
|
|
|
@@ -266,40 +277,40 @@ export async function runCli({
|
|
|
266
277
|
|
|
267
278
|
// 检查 / 安装 / 更新工具(PRD 6.1 / 8.1):展示当前与最新版本,更新需确认
|
|
268
279
|
async function checkInstallOrUpdateTool(target, toolName, command) {
|
|
269
|
-
|
|
280
|
+
msg.step(`正在检查 ${toolName}...`);
|
|
270
281
|
const check = await commandVersion(execCommand, command, ["--version"]);
|
|
271
282
|
if (!check.ok) {
|
|
272
283
|
return ensureToolInstalled(target, toolName, command);
|
|
273
284
|
}
|
|
274
|
-
|
|
285
|
+
msg.result("当前版本", check.version);
|
|
275
286
|
const packageName = buildInstallCommand(target).packageName;
|
|
276
287
|
const latest = await execCommand("npm", ["view", packageName, "version"]);
|
|
277
288
|
if (latest.ok) {
|
|
278
|
-
|
|
289
|
+
msg.result("最新版本", `v${latest.stdout.trim()}`);
|
|
279
290
|
} else {
|
|
280
|
-
|
|
291
|
+
msg.warn("最新版本获取失败(npm 网络异常时可使用国内镜像)");
|
|
281
292
|
}
|
|
282
|
-
|
|
293
|
+
msg.info(`检测到 ${toolName} 将通过 npm 更新。`);
|
|
283
294
|
const action = await promptSelect(`是否更新 ${toolName}?`, [
|
|
284
295
|
{ label: "更新到最新版", value: "update" },
|
|
285
296
|
{ label: "跳过", value: "skip" },
|
|
286
297
|
]);
|
|
287
298
|
if (action !== "update") {
|
|
288
|
-
|
|
299
|
+
msg.info(`已跳过 ${toolName} 更新。`);
|
|
289
300
|
return true;
|
|
290
301
|
}
|
|
291
302
|
const results = await installOrUpdateTools({ targets: [target], confirm });
|
|
292
303
|
const result = (results || []).find((item) => item.target === target);
|
|
293
304
|
if (result?.skipped) {
|
|
294
|
-
|
|
305
|
+
msg.info(`已跳过 ${toolName} 更新。`);
|
|
295
306
|
return true;
|
|
296
307
|
}
|
|
297
308
|
if (result?.ok === false) {
|
|
298
|
-
|
|
299
|
-
|
|
309
|
+
msg.error(`更新 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
|
|
310
|
+
msg.hint("国内网络失败时可尝试:npm install -g --registry=https://registry.npmmirror.com");
|
|
300
311
|
return false;
|
|
301
312
|
}
|
|
302
|
-
|
|
313
|
+
msg.success(`${toolName} 已更新到最新版。`);
|
|
303
314
|
return true;
|
|
304
315
|
}
|
|
305
316
|
|
|
@@ -315,24 +326,64 @@ export async function runCli({
|
|
|
315
326
|
if (action === "back") {
|
|
316
327
|
return false;
|
|
317
328
|
}
|
|
329
|
+
writeLine(renderTaskStart("配置 Claude360 API", {
|
|
330
|
+
intro: ["检查授权状态", "选择 API Key", "写入本地配置", "完成验证"],
|
|
331
|
+
}));
|
|
332
|
+
writeLine("");
|
|
333
|
+
writeLine(renderTaskStep(1, 4, "检查授权状态"));
|
|
334
|
+
if (!(await ensureLogin())) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
msg.success("Claude360 授权状态有效。");
|
|
338
|
+
writeLine("");
|
|
339
|
+
writeLine(renderDivider("section"));
|
|
340
|
+
writeLine("");
|
|
341
|
+
writeLine(renderTaskStep(2, 4, "选择 API Key"));
|
|
342
|
+
let token;
|
|
318
343
|
if (action === "create") {
|
|
319
|
-
await
|
|
344
|
+
token = await createToken({ api, promptSelect, promptInput, writeLine });
|
|
345
|
+
writeLine("");
|
|
346
|
+
writeLine(selectedSummary("已创建 API Key", [
|
|
347
|
+
["当前 Key", token.tokenName || `Token #${token.tokenId}`],
|
|
348
|
+
["当前分组", token.group || "-"],
|
|
349
|
+
]));
|
|
320
350
|
} else if (action === "reselect") {
|
|
321
|
-
|
|
322
|
-
await saveConfig({
|
|
323
|
-
apiKey: token.apiKey,
|
|
324
|
-
selectedTokenId: token.tokenId,
|
|
325
|
-
tokenName: token.tokenName,
|
|
326
|
-
group: token.group,
|
|
327
|
-
});
|
|
328
|
-
writeLine(`✓ 当前 Key 已切换为 ${token.tokenName}`);
|
|
351
|
+
token = await chooseToken({ api, promptSelect, promptInput, writeLine });
|
|
329
352
|
} else {
|
|
330
|
-
await ensureApiKey();
|
|
353
|
+
token = await ensureApiKey();
|
|
354
|
+
writeLine("");
|
|
355
|
+
writeLine(selectedSummary("已选择 API Key", [
|
|
356
|
+
["当前 Key", token.tokenName || config.tokenName || "-"],
|
|
357
|
+
["当前分组", token.group || config.group || "-"],
|
|
358
|
+
]));
|
|
331
359
|
}
|
|
360
|
+
writeLine("");
|
|
361
|
+
writeLine(renderDivider("section"));
|
|
362
|
+
writeLine("");
|
|
363
|
+
writeLine(renderTaskStep(3, 4, "写入本地配置"));
|
|
364
|
+
await saveConfig({
|
|
365
|
+
apiKey: token.apiKey || config.apiKey,
|
|
366
|
+
selectedTokenId: token.tokenId ?? config.selectedTokenId,
|
|
367
|
+
tokenName: token.tokenName || config.tokenName,
|
|
368
|
+
group: token.group || config.group,
|
|
369
|
+
});
|
|
332
370
|
await markConfigured("claudeCode");
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
371
|
+
msg.success("本地 Claude360 API 配置已更新。");
|
|
372
|
+
msg.path(`ANTHROPIC_BASE_URL=${baseUrl}`);
|
|
373
|
+
msg.path("ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
|
|
374
|
+
writeLine("");
|
|
375
|
+
writeLine(renderDivider("section"));
|
|
376
|
+
writeLine("");
|
|
377
|
+
writeLine(renderTaskStep(4, 4, "完成验证"));
|
|
378
|
+
msg.success("Claude Code 启动时将注入 Claude360 配置(不修改 shell profile)。");
|
|
379
|
+
writeLine("");
|
|
380
|
+
writeLine(renderTaskEnd("Claude360 API 配置完成", {
|
|
381
|
+
summary: [
|
|
382
|
+
["当前 Key", config.tokenName || "-"],
|
|
383
|
+
["当前分组", config.group || "-"],
|
|
384
|
+
["下一步", "启动 Claude Code 或返回主菜单"],
|
|
385
|
+
],
|
|
386
|
+
}));
|
|
336
387
|
return true;
|
|
337
388
|
}
|
|
338
389
|
}
|
|
@@ -393,8 +444,9 @@ export async function runCli({
|
|
|
393
444
|
async function runCodexProviderMenu() {
|
|
394
445
|
while (true) {
|
|
395
446
|
writeLine(renderHeader("Codex Provider 配置", { color: fancyOutput }));
|
|
396
|
-
|
|
397
|
-
|
|
447
|
+
msg.result("当前 Provider", "claude360");
|
|
448
|
+
msg.result("当前 Profile", "claude360");
|
|
449
|
+
writeLine("");
|
|
398
450
|
const action = await promptSelect("请选择:", [
|
|
399
451
|
{ label: "写入 / 修复 Claude360 Provider", value: "write" },
|
|
400
452
|
{ label: "查看当前 Codex 配置", value: "view" },
|
|
@@ -407,30 +459,51 @@ export async function runCli({
|
|
|
407
459
|
await doConfigureCodex();
|
|
408
460
|
continue;
|
|
409
461
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
462
|
+
await showCodexConfigSummary();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function showCodexConfigSummary() {
|
|
467
|
+
const configPath = resolveCodexConfigPath();
|
|
468
|
+
const profilePath = resolveCodexProfileConfigPath();
|
|
469
|
+
const configInfo = await readOptionalText(configPath);
|
|
470
|
+
const profileInfo = await readOptionalText(profilePath);
|
|
471
|
+
msg.heading("当前 Codex 配置摘要");
|
|
472
|
+
writeLine(renderKeyValueTable([
|
|
473
|
+
["config.toml", configInfo.exists ? "已找到" : "未找到"],
|
|
474
|
+
["claude360.config.toml", profileInfo.exists ? "已找到" : "未找到"],
|
|
475
|
+
["model_providers.claude360", configInfo.exists && /\[model_providers\.claude360\]/.test(configInfo.content) ? "已配置" : "未配置"],
|
|
476
|
+
["profile 覆盖层", profileInfo.exists && /model_provider\s*=\s*"claude360"/.test(profileInfo.content) ? "已配置" : "未配置"],
|
|
477
|
+
], { width: outputWidth() }));
|
|
478
|
+
msg.hint("查看完整配置:使用系统编辑器打开上述文件,或运行 Codex 自带配置查看命令。");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function readOptionalText(filePath) {
|
|
482
|
+
try {
|
|
483
|
+
return { exists: true, content: await readFileImpl(filePath, "utf8") };
|
|
484
|
+
} catch (error) {
|
|
485
|
+
if (error?.code === "ENOENT") {
|
|
486
|
+
return { exists: false, content: "" };
|
|
420
487
|
}
|
|
488
|
+
throw error;
|
|
421
489
|
}
|
|
422
490
|
}
|
|
423
491
|
|
|
492
|
+
function showFullLocalConfig() {
|
|
493
|
+
writeLine("完整配置视图");
|
|
494
|
+
writeLine(JSON.stringify(config, null, 2));
|
|
495
|
+
}
|
|
496
|
+
|
|
424
497
|
// Codex 协议兼容性检测(PRD 8.6)
|
|
425
498
|
async function checkCodexCompatStep() {
|
|
426
|
-
|
|
499
|
+
msg.step("正在检测 Claude360 是否支持 Codex 所需协议...");
|
|
427
500
|
const compat = await codexCompat(api);
|
|
428
501
|
if (compat.supported) {
|
|
429
|
-
|
|
502
|
+
msg.success("Claude360 已支持 Codex 所需协议");
|
|
430
503
|
return true;
|
|
431
504
|
}
|
|
432
|
-
|
|
433
|
-
|
|
505
|
+
msg.warn("当前 Claude360 网关暂不支持 Codex 所需协议。");
|
|
506
|
+
msg.hint("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
|
|
434
507
|
const next = await promptSelect("请选择:", [
|
|
435
508
|
{ label: "返回", value: "back" },
|
|
436
509
|
{ label: "打开 Claude360 控制台", value: "console" },
|
|
@@ -443,7 +516,7 @@ export async function runCli({
|
|
|
443
516
|
|
|
444
517
|
async function testConnectionStep() {
|
|
445
518
|
const me = await fetchMe();
|
|
446
|
-
writeLine(me ? "
|
|
519
|
+
writeLine(me ? formatMessage("success", "Claude360 连接测试通过", { color: fancyOutput }) : formatMessage("warn", "Claude360 连接测试失败,可稍后在诊断菜单排查", { color: fancyOutput }));
|
|
447
520
|
}
|
|
448
521
|
|
|
449
522
|
// ──────────────────────────────────────────────
|
|
@@ -458,15 +531,15 @@ export async function runCli({
|
|
|
458
531
|
showBalance: showBalanceAndUsage,
|
|
459
532
|
ensureApiKey: async () => {
|
|
460
533
|
await ensureApiKey();
|
|
461
|
-
|
|
534
|
+
msg.result("当前 Key", config.tokenName || "-");
|
|
462
535
|
},
|
|
463
536
|
installTool: () => checkInstallOrUpdateTool("claude", "Claude Code", "claude"),
|
|
464
537
|
configureApi: async () => {
|
|
465
538
|
await ensureApiKey();
|
|
466
539
|
await markConfigured("claudeCode");
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
540
|
+
msg.success("Claude Code 将在启动时注入 Claude360 配置(不修改 shell profile):");
|
|
541
|
+
msg.path(` ANTHROPIC_BASE_URL=${baseUrl}`);
|
|
542
|
+
msg.path(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
|
|
470
543
|
},
|
|
471
544
|
configureLanguage: () => configureLanguage({ baseDir: claudeDir, filePath: claudeMemoryPath, promptSelect, writeLine }),
|
|
472
545
|
configureStyle: () => configureStyle({ baseDir: claudeDir, filePath: claudeMemoryPath, promptSelect, writeLine }),
|
|
@@ -491,7 +564,7 @@ export async function runCli({
|
|
|
491
564
|
showBalance: showBalanceAndUsage,
|
|
492
565
|
ensureApiKey: async () => {
|
|
493
566
|
await ensureApiKey();
|
|
494
|
-
|
|
567
|
+
msg.result("当前 Key", config.tokenName || "-");
|
|
495
568
|
},
|
|
496
569
|
installTool: () => checkInstallOrUpdateTool("codex", "Codex", "codex"),
|
|
497
570
|
configureLanguage: () => configureLanguage({ baseDir: codexDir, filePath: agentsPath, promptSelect, writeLine }),
|
|
@@ -502,7 +575,7 @@ export async function runCli({
|
|
|
502
575
|
await ensureApiKey();
|
|
503
576
|
await configureCodex({ config, confirmConflict: confirmDanger, writeLine });
|
|
504
577
|
await markConfigured("codex");
|
|
505
|
-
|
|
578
|
+
msg.success("已写入 Codex claude360 provider(~/.codex/config.toml)与 profile 覆盖层(~/.codex/claude360.config.toml)");
|
|
506
579
|
},
|
|
507
580
|
checkCompat: checkCodexCompatStep,
|
|
508
581
|
installMcps: () => installCodexMcpServers({ api, codexDir, multiSelect, confirm, writeLine }),
|
|
@@ -568,19 +641,19 @@ export async function runCli({
|
|
|
568
641
|
async function ensureToolInstalled(target, toolName, command) {
|
|
569
642
|
const check = await commandVersion(execCommand, command, ["--version"]);
|
|
570
643
|
if (check.ok) {
|
|
571
|
-
|
|
644
|
+
msg.success(`${toolName} 已安装(${check.version})`);
|
|
572
645
|
return true;
|
|
573
646
|
}
|
|
574
|
-
|
|
647
|
+
msg.info(`未检测到 ${toolName}。`);
|
|
575
648
|
const results = await installOrUpdateTools({ targets: [target], confirm });
|
|
576
649
|
const result = (results || []).find((item) => item.target === target);
|
|
577
650
|
if (result?.skipped) {
|
|
578
|
-
|
|
651
|
+
msg.info(`已跳过 ${toolName} 安装。`);
|
|
579
652
|
return false;
|
|
580
653
|
}
|
|
581
654
|
if (result?.ok === false) {
|
|
582
|
-
|
|
583
|
-
|
|
655
|
+
msg.error(`安装 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
|
|
656
|
+
msg.hint("国内网络失败时可尝试:npx --registry=https://registry.npmmirror.com claude360");
|
|
584
657
|
return false;
|
|
585
658
|
}
|
|
586
659
|
return true;
|
|
@@ -590,17 +663,17 @@ export async function runCli({
|
|
|
590
663
|
async function preflightLaunch(toolName) {
|
|
591
664
|
const me = await fetchMe();
|
|
592
665
|
if (!config.apiKey) {
|
|
593
|
-
|
|
666
|
+
msg.warn("当前没有可用 API Key,先完成 Key 配置。");
|
|
594
667
|
await ensureApiKey();
|
|
595
668
|
}
|
|
596
|
-
|
|
597
|
-
|
|
669
|
+
msg.heading(`${toolName} 启动前检查`);
|
|
670
|
+
msg.result("当前 Key", config.tokenName || "-");
|
|
598
671
|
if (me) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
672
|
+
msg.success("Claude360 服务可访问");
|
|
673
|
+
msg.result("余额", me.balance_display ?? me.quota);
|
|
674
|
+
msg.result("今日用量", `${me.today_usage_display || "-"}(已刷新)`);
|
|
602
675
|
} else {
|
|
603
|
-
|
|
676
|
+
msg.warn("Claude360 服务状态获取失败,仍可尝试启动");
|
|
604
677
|
}
|
|
605
678
|
if (me?.low_balance) {
|
|
606
679
|
const action = await promptSelect(
|
|
@@ -625,7 +698,7 @@ export async function runCli({
|
|
|
625
698
|
return false;
|
|
626
699
|
}
|
|
627
700
|
await markConfigured("claudeCode");
|
|
628
|
-
|
|
701
|
+
msg.step("正在启动 Claude Code...");
|
|
629
702
|
await launchClaudeCode({ config });
|
|
630
703
|
return true;
|
|
631
704
|
}
|
|
@@ -633,16 +706,16 @@ export async function runCli({
|
|
|
633
706
|
async function doLaunchCodex() {
|
|
634
707
|
const compat = await codexCompat(api);
|
|
635
708
|
if (!compat.supported) {
|
|
636
|
-
|
|
637
|
-
|
|
709
|
+
msg.warn("当前 Claude360 网关暂不支持 Codex 所需协议。");
|
|
710
|
+
msg.hint("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
|
|
638
711
|
return false;
|
|
639
712
|
}
|
|
640
|
-
|
|
713
|
+
msg.success("Claude360 已支持 Codex 所需协议");
|
|
641
714
|
if (!(await preflightLaunch("Codex"))) {
|
|
642
715
|
return false;
|
|
643
716
|
}
|
|
644
717
|
await markConfigured("codex");
|
|
645
|
-
|
|
718
|
+
msg.step("正在启动 Codex...");
|
|
646
719
|
await launchCodex({ config, confirmConflict: confirmDanger, writeLine });
|
|
647
720
|
return true;
|
|
648
721
|
}
|
|
@@ -650,27 +723,27 @@ export async function runCli({
|
|
|
650
723
|
async function doConfigureCodex() {
|
|
651
724
|
const compat = await codexCompat(api);
|
|
652
725
|
if (!compat.supported) {
|
|
653
|
-
|
|
726
|
+
msg.warn("当前 Claude360 网关暂不支持 Codex 所需协议,已停止写入配置。");
|
|
654
727
|
return false;
|
|
655
728
|
}
|
|
656
729
|
await ensureApiKey();
|
|
657
730
|
await configureCodex({ config, confirmConflict: confirmDanger, writeLine });
|
|
658
731
|
await markConfigured("codex");
|
|
659
|
-
|
|
732
|
+
msg.success("已写入 Codex claude360 provider/profile(~/.codex/config.toml)");
|
|
660
733
|
return true;
|
|
661
734
|
}
|
|
662
735
|
|
|
663
736
|
async function doWechatTopUp() {
|
|
664
737
|
try {
|
|
665
738
|
const result = await runWechatTopUp({ api, promptSelect, promptInput, writeLine, sleep });
|
|
666
|
-
|
|
739
|
+
msg.success("支付成功");
|
|
667
740
|
if (result?.balance) {
|
|
668
|
-
|
|
669
|
-
|
|
741
|
+
msg.result("当前余额", result.balance.balance_display ?? result.balance.quota);
|
|
742
|
+
msg.result("今日用量", result.balance.today_usage_display || "-");
|
|
670
743
|
}
|
|
671
744
|
return result;
|
|
672
745
|
} catch (error) {
|
|
673
|
-
|
|
746
|
+
msg.error(`充值未完成:${safeErrorMessage(error)}`);
|
|
674
747
|
return null;
|
|
675
748
|
}
|
|
676
749
|
}
|
|
@@ -688,47 +761,96 @@ export async function runCli({
|
|
|
688
761
|
return;
|
|
689
762
|
}
|
|
690
763
|
const targets = selected === "all" ? ["claude", "codex"] : [selected];
|
|
691
|
-
|
|
764
|
+
writeLine(renderTaskStart("安装或更新工具", {
|
|
765
|
+
intro: ["检查版本", "执行安装或更新", "验证安装结果"],
|
|
766
|
+
}));
|
|
767
|
+
writeLine("");
|
|
768
|
+
writeLine(renderTaskStep(1, 3, "检查版本"));
|
|
769
|
+
for (const target of targets) {
|
|
770
|
+
const commandName = installTargetCommand(target);
|
|
771
|
+
const check = await commandVersion(execCommand, commandName, ["--version"]);
|
|
772
|
+
if (check.ok) {
|
|
773
|
+
msg.result(`${target} 当前版本`, check.version);
|
|
774
|
+
} else {
|
|
775
|
+
msg.info(`${target} 当前未安装或不可用。`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
writeLine("");
|
|
779
|
+
writeLine(renderDivider("section"));
|
|
780
|
+
writeLine("");
|
|
781
|
+
writeLine(renderTaskStep(2, 3, "执行安装或更新"));
|
|
782
|
+
if (!verbose) {
|
|
783
|
+
// 非 verbose 下 npm 输出被捕获不直显,给一个进行中提示,避免显得卡死
|
|
784
|
+
msg.step("正在执行 npm 安装/更新(耗时较长,请稍候)...");
|
|
785
|
+
}
|
|
786
|
+
const results = await installOrUpdateTools({ targets, confirm, verbose });
|
|
787
|
+
writeLine("");
|
|
788
|
+
writeLine(renderDivider("section"));
|
|
789
|
+
writeLine("");
|
|
790
|
+
writeLine(renderTaskStep(3, 3, "验证安装结果"));
|
|
692
791
|
for (const result of results || []) {
|
|
693
792
|
if (result?.skipped) {
|
|
694
|
-
|
|
793
|
+
msg.info(`已跳过 ${result.target}。`);
|
|
695
794
|
continue;
|
|
696
795
|
}
|
|
697
796
|
if (result?.ok === false) {
|
|
698
|
-
writeLine(`安装或更新 ${result.target}
|
|
797
|
+
writeLine(renderStructuredError(`安装或更新 ${result.target} 失败`, {
|
|
798
|
+
reason: result.error || "安装命令执行失败。",
|
|
799
|
+
suggestions: [
|
|
800
|
+
result.remediation || "检查 npm registry 是否可访问。",
|
|
801
|
+
"确认当前用户有 npm 全局安装权限。",
|
|
802
|
+
],
|
|
803
|
+
detailCommand: "claude360 doctor --verbose",
|
|
804
|
+
}));
|
|
699
805
|
continue;
|
|
700
806
|
}
|
|
701
|
-
|
|
807
|
+
const commandName = installTargetCommand(result.target);
|
|
808
|
+
const check = await commandVersion(execCommand, commandName, ["--version"]);
|
|
809
|
+
if (check.ok) {
|
|
810
|
+
msg.result("当前版本", check.version);
|
|
811
|
+
} else if (result.target !== "claude360") {
|
|
812
|
+
msg.warn(`${result.target} 版本验证失败:${check.detail || "未检测到命令"}`);
|
|
813
|
+
}
|
|
814
|
+
msg.success(`${result.target} 安装或更新完成${result.usedMirror ? "(使用国内镜像)" : ""}`);
|
|
702
815
|
if (result.target === "claude360") {
|
|
703
|
-
|
|
816
|
+
msg.info("claude360 CLI 已更新,重新运行 npx claude360 后生效。");
|
|
704
817
|
}
|
|
705
818
|
}
|
|
819
|
+
writeLine("");
|
|
820
|
+
writeLine(renderTaskEnd("安装或更新工具完成"));
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function installTargetCommand(target) {
|
|
824
|
+
return target === "claude" ? "claude" : target === "codex" ? "codex" : "claude360";
|
|
706
825
|
}
|
|
707
826
|
|
|
708
827
|
async function openPage(path, label) {
|
|
709
828
|
const url = `${baseUrl}${path}`;
|
|
710
|
-
|
|
829
|
+
msg.step(`正在打开${label}:${url}`);
|
|
711
830
|
await openBrowser(url);
|
|
712
831
|
}
|
|
713
832
|
|
|
714
833
|
// 退出登录:仅清除本地凭证,不触碰网站侧账号与 Key(PRD 25.2 logout)
|
|
715
834
|
async function doLogout() {
|
|
716
835
|
if (!config.cliToken && !config.apiKey) {
|
|
717
|
-
|
|
836
|
+
msg.info("当前未登录。");
|
|
718
837
|
return true;
|
|
719
838
|
}
|
|
720
839
|
const approved = await confirm(
|
|
721
|
-
"
|
|
840
|
+
warningBlock("即将退出登录", {
|
|
841
|
+
action: "清除本地保存的 CLI Token 与 API Key",
|
|
842
|
+
impact: "仅影响本机 Claude360 CLI 配置;不会删除 Claude360 网站上的账号或 API Key。",
|
|
843
|
+
}),
|
|
722
844
|
{ danger: true },
|
|
723
845
|
);
|
|
724
846
|
if (!approved) {
|
|
725
|
-
|
|
847
|
+
msg.info("已取消。");
|
|
726
848
|
return false;
|
|
727
849
|
}
|
|
728
850
|
config = { baseUrl };
|
|
729
851
|
await configStore.save(config);
|
|
730
852
|
api = createApiClient({ baseUrl, cliToken: "" });
|
|
731
|
-
|
|
853
|
+
msg.info("已退出登录。");
|
|
732
854
|
return true;
|
|
733
855
|
}
|
|
734
856
|
|
|
@@ -744,17 +866,20 @@ export async function runCli({
|
|
|
744
866
|
|
|
745
867
|
async function doClearConfig() {
|
|
746
868
|
const approved = await confirm(
|
|
747
|
-
"
|
|
869
|
+
warningBlock("即将清除本地配置", {
|
|
870
|
+
action: "清除本地 Claude360 CLI 登录状态和已保存 Key",
|
|
871
|
+
impact: "仅影响本机 Claude360 CLI 配置;不会删除 Claude360 网站上的账号或 API Key。",
|
|
872
|
+
}),
|
|
748
873
|
{ danger: true },
|
|
749
874
|
);
|
|
750
875
|
if (!approved) {
|
|
751
|
-
|
|
876
|
+
msg.info("已取消。");
|
|
752
877
|
return false;
|
|
753
878
|
}
|
|
754
879
|
config = { baseUrl };
|
|
755
880
|
await configStore.save(config);
|
|
756
881
|
api = createApiClient({ baseUrl, cliToken: "" });
|
|
757
|
-
|
|
882
|
+
msg.info("本地配置已清除。");
|
|
758
883
|
return ensureLogin();
|
|
759
884
|
}
|
|
760
885
|
|
|
@@ -767,7 +892,7 @@ export async function runCli({
|
|
|
767
892
|
writeLine(renderHeader("余额与充值", { color: fancyOutput }));
|
|
768
893
|
const me = await showBalanceAndUsage();
|
|
769
894
|
if (me?.low_balance) {
|
|
770
|
-
|
|
895
|
+
msg.warn("余额偏低,建议充值");
|
|
771
896
|
}
|
|
772
897
|
const action = await promptSelect("请选择:", [
|
|
773
898
|
{ label: "微信扫码充值", value: "wechat" },
|
|
@@ -787,7 +912,7 @@ export async function runCli({
|
|
|
787
912
|
}
|
|
788
913
|
|
|
789
914
|
async function doCreateKey() {
|
|
790
|
-
const token = await createToken({ api, promptSelect, promptInput });
|
|
915
|
+
const token = await createToken({ api, promptSelect, promptInput, writeLine });
|
|
791
916
|
const useNow = await confirm(`已创建 API Key:${token.tokenName}。是否将其设为当前使用的 Key?`, { defaultYes: true });
|
|
792
917
|
if (useNow) {
|
|
793
918
|
await saveConfig({
|
|
@@ -796,7 +921,7 @@ export async function runCli({
|
|
|
796
921
|
tokenName: token.tokenName,
|
|
797
922
|
group: token.group,
|
|
798
923
|
});
|
|
799
|
-
|
|
924
|
+
msg.success(`当前 Key 已切换为 ${token.tokenName}`);
|
|
800
925
|
}
|
|
801
926
|
return token;
|
|
802
927
|
}
|
|
@@ -967,14 +1092,14 @@ export async function runCli({
|
|
|
967
1092
|
switch (action) {
|
|
968
1093
|
case "diagnose": {
|
|
969
1094
|
const report = await runDiagnostics({ config, api, execCommand });
|
|
970
|
-
writeLine(formatDiagnosticsSummary(report, { width: outputWidth() }));
|
|
1095
|
+
writeLine(formatDiagnosticsSummary(report, { width: outputWidth(), color: fancyOutput, verbose }));
|
|
971
1096
|
break;
|
|
972
1097
|
}
|
|
973
1098
|
case "fix_claude":
|
|
974
1099
|
await ensureApiKey();
|
|
975
1100
|
await markConfigured("claudeCode");
|
|
976
1101
|
await showBalanceAndUsage();
|
|
977
|
-
|
|
1102
|
+
msg.success("Claude Code 配置修复完成(启动时注入环境变量)。");
|
|
978
1103
|
break;
|
|
979
1104
|
case "fix_codex":
|
|
980
1105
|
await doConfigureCodex();
|
|
@@ -987,7 +1112,7 @@ export async function runCli({
|
|
|
987
1112
|
tokenName: token.tokenName,
|
|
988
1113
|
group: token.group,
|
|
989
1114
|
});
|
|
990
|
-
|
|
1115
|
+
msg.success(`当前 Key 已切换为 ${token.tokenName}`);
|
|
991
1116
|
break;
|
|
992
1117
|
}
|
|
993
1118
|
case "reset":
|
|
@@ -1021,7 +1146,7 @@ export async function runCli({
|
|
|
1021
1146
|
return false;
|
|
1022
1147
|
}
|
|
1023
1148
|
await markConfigured("claudeCode");
|
|
1024
|
-
|
|
1149
|
+
msg.success("Claude Code 将在启动时注入 Claude360 配置。");
|
|
1025
1150
|
}
|
|
1026
1151
|
if (targets.includes("codex")) {
|
|
1027
1152
|
if (!(await ensureToolInstalled("codex", "Codex", "codex"))) {
|
|
@@ -1057,7 +1182,7 @@ export async function runCli({
|
|
|
1057
1182
|
|
|
1058
1183
|
// 测试连接
|
|
1059
1184
|
const me = await fetchMe();
|
|
1060
|
-
writeLine(me ? "
|
|
1185
|
+
writeLine(me ? formatMessage("success", "Claude360 连接测试通过", { color: fancyOutput }) : formatMessage("warn", "Claude360 连接测试失败,可稍后在诊断菜单排查", { color: fancyOutput }));
|
|
1061
1186
|
|
|
1062
1187
|
// 启动
|
|
1063
1188
|
if (targets.length === 1) {
|
|
@@ -1087,7 +1212,7 @@ export async function runCli({
|
|
|
1087
1212
|
try {
|
|
1088
1213
|
await checkEnvironment({ writeLine, execCommand });
|
|
1089
1214
|
} catch (error) {
|
|
1090
|
-
|
|
1215
|
+
msg.error(safeErrorMessage(error));
|
|
1091
1216
|
return "environment_failed";
|
|
1092
1217
|
}
|
|
1093
1218
|
}
|
|
@@ -1165,7 +1290,7 @@ export async function runCli({
|
|
|
1165
1290
|
tokenName: token.tokenName,
|
|
1166
1291
|
group: token.group,
|
|
1167
1292
|
});
|
|
1168
|
-
|
|
1293
|
+
msg.success(`当前 Key 已切换为 ${token.tokenName}`);
|
|
1169
1294
|
break;
|
|
1170
1295
|
}
|
|
1171
1296
|
case "claude_model":
|
|
@@ -1182,11 +1307,8 @@ export async function runCli({
|
|
|
1182
1307
|
|
|
1183
1308
|
async function runDailyFlow() {
|
|
1184
1309
|
while (true) {
|
|
1185
|
-
if (interactiveUi) {
|
|
1186
|
-
writeLine(renderHeader("主菜单", { color: fancyOutput }));
|
|
1187
|
-
}
|
|
1188
1310
|
const status = await loadAccountStatus({ api, config });
|
|
1189
|
-
writeLine(formatAccountStatus({ ...status, width: outputWidth() }));
|
|
1311
|
+
writeLine(formatAccountStatus({ ...status, width: outputWidth(), color: fancyOutput }));
|
|
1190
1312
|
writeLine("");
|
|
1191
1313
|
const action = await promptMenu({
|
|
1192
1314
|
menu: buildDailyMenu(),
|
|
@@ -1262,6 +1384,23 @@ export async function runCli({
|
|
|
1262
1384
|
|
|
1263
1385
|
async function runShortcut(name) {
|
|
1264
1386
|
switch (name) {
|
|
1387
|
+
case "about":
|
|
1388
|
+
writeLine(formatOpenSourceNoticeDetail());
|
|
1389
|
+
return "about";
|
|
1390
|
+
case "config view":
|
|
1391
|
+
case "config:view":
|
|
1392
|
+
showFullLocalConfig();
|
|
1393
|
+
return "config_view";
|
|
1394
|
+
case "license":
|
|
1395
|
+
writeLine(LICENSE_TEXT);
|
|
1396
|
+
return "license";
|
|
1397
|
+
case "changelog":
|
|
1398
|
+
writeLine([
|
|
1399
|
+
"更新日志(最近 3 条):",
|
|
1400
|
+
"",
|
|
1401
|
+
...CHANGELOG_ENTRIES.slice(0, 3).map((entry, index) => `${index + 1}. ${entry}`),
|
|
1402
|
+
].join("\n"));
|
|
1403
|
+
return "changelog";
|
|
1265
1404
|
case "claude":
|
|
1266
1405
|
if (!(await ensureLogin())) {
|
|
1267
1406
|
return "login_required";
|
|
@@ -1280,7 +1419,7 @@ export async function runCli({
|
|
|
1280
1419
|
return "cc_switch";
|
|
1281
1420
|
case "doctor": {
|
|
1282
1421
|
const report = await runDiagnostics({ config, api, execCommand });
|
|
1283
|
-
writeLine(formatDiagnosticsSummary(report, { width: outputWidth() }));
|
|
1422
|
+
writeLine(formatDiagnosticsSummary(report, { width: outputWidth(), color: fancyOutput }));
|
|
1284
1423
|
return "doctor";
|
|
1285
1424
|
}
|
|
1286
1425
|
case "login":
|
|
@@ -1318,6 +1457,10 @@ function formatCliUsage() {
|
|
|
1318
1457
|
"doctor 一键诊断",
|
|
1319
1458
|
"login 浏览器授权登录",
|
|
1320
1459
|
"logout 退出登录并清除本地凭证",
|
|
1460
|
+
"about 查看开源参考声明全文",
|
|
1461
|
+
"config view 显式查看本地完整配置",
|
|
1462
|
+
"license 查看许可证说明",
|
|
1463
|
+
"changelog 查看更新日志说明",
|
|
1321
1464
|
].join("\n");
|
|
1322
1465
|
}
|
|
1323
1466
|
|