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/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 { renderHeader } from "./ui.js";
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
- writeLine("请在浏览器中完成 Claude360 授权。");
196
- writeLine("如果浏览器没有自动打开,请根据终端提示访问授权页面并输入授权码。");
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
- writeLine("授权成功,登录状态已保存。");
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
- writeLine("暂时无法获取余额与今日用量。");
252
+ msg.error("暂时无法获取余额与今日用量。");
245
253
  return null;
246
254
  }
247
- writeLine(`余额:${me.balance_display ?? me.quota}${me.low_balance ? " ! 余额较低" : ""}`);
248
- writeLine(`今日用量:${me.today_usage_display || "-"}`);
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
- writeLine(`正在检查 ${toolName}...`);
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
- writeLine(`当前版本:${check.version}`);
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
- writeLine(`最新版本:v${latest.stdout.trim()}`);
289
+ msg.result("最新版本", `v${latest.stdout.trim()}`);
279
290
  } else {
280
- writeLine("最新版本:获取失败(npm 网络异常时可使用国内镜像)");
291
+ msg.warn("最新版本获取失败(npm 网络异常时可使用国内镜像)");
281
292
  }
282
- writeLine(`检测到 ${toolName} 将通过 npm 更新。`);
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
- writeLine(`已跳过 ${toolName} 更新。`);
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
- writeLine(`已跳过 ${toolName} 更新。`);
305
+ msg.info(`已跳过 ${toolName} 更新。`);
295
306
  return true;
296
307
  }
297
308
  if (result?.ok === false) {
298
- writeLine(`更新 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
299
- writeLine("国内网络失败时可尝试:npm install -g --registry=https://registry.npmmirror.com");
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
- writeLine(`✓ ${toolName} 已更新到最新版。`);
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 doCreateKey();
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
- const token = await chooseToken({ api, promptSelect, promptInput, writeLine });
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
- writeLine(" Claude Code 将在启动时注入 Claude360 配置(不修改 shell profile):");
334
- writeLine(` ANTHROPIC_BASE_URL=${baseUrl}`);
335
- writeLine(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
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
- writeLine("当前 Providerclaude360");
397
- writeLine("当前 Profileclaude360\n");
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
- try {
411
- const content = await readFileImpl(resolveCodexConfigPath(), "utf8");
412
- writeLine("当前 ~/.codex/config.toml 内容:\n");
413
- writeLine(content);
414
- } catch (error) {
415
- if (error?.code === "ENOENT") {
416
- writeLine("未找到 ~/.codex/config.toml,可先执行「写入 / 修复 Claude360 Provider」。");
417
- } else {
418
- writeLine(`读取 Codex 配置失败:${safeErrorMessage(error)}`);
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
- writeLine("正在检测 Claude360 是否支持 Codex 所需协议...");
499
+ msg.step("正在检测 Claude360 是否支持 Codex 所需协议...");
427
500
  const compat = await codexCompat(api);
428
501
  if (compat.supported) {
429
- writeLine("Claude360 已支持 Codex 所需协议");
502
+ msg.success("Claude360 已支持 Codex 所需协议");
430
503
  return true;
431
504
  }
432
- writeLine("当前 Claude360 网关暂不支持 Codex 所需协议。");
433
- writeLine("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
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 ? " Claude360 连接测试通过" : "! Claude360 连接测试失败,可稍后在诊断菜单排查");
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
- writeLine(`✓ 当前 Key:${config.tokenName || "-"}`);
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
- writeLine("Claude Code 将在启动时注入 Claude360 配置(不修改 shell profile):");
468
- writeLine(` ANTHROPIC_BASE_URL=${baseUrl}`);
469
- writeLine(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
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
- writeLine(`✓ 当前 Key:${config.tokenName || "-"}`);
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
- writeLine("已写入 Codex claude360 provider/profile(~/.codex/config.toml)");
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
- writeLine(`✓ ${toolName} 已安装(${check.version})`);
644
+ msg.success(`${toolName} 已安装(${check.version})`);
572
645
  return true;
573
646
  }
574
- writeLine(`未检测到 ${toolName}。`);
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
- writeLine(`已跳过 ${toolName} 安装。`);
651
+ msg.info(`已跳过 ${toolName} 安装。`);
579
652
  return false;
580
653
  }
581
654
  if (result?.ok === false) {
582
- writeLine(`安装 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
583
- writeLine("国内网络失败时可尝试:npx --registry=https://registry.npmmirror.com claude360");
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
- writeLine("当前没有可用 API Key,先完成 Key 配置。");
666
+ msg.warn("当前没有可用 API Key,先完成 Key 配置。");
594
667
  await ensureApiKey();
595
668
  }
596
- writeLine(`✓ ${toolName} 启动前检查`);
597
- writeLine(`✓ 当前 Key:${config.tokenName || "-"}`);
669
+ msg.heading(`${toolName} 启动前检查`);
670
+ msg.result("当前 Key", config.tokenName || "-");
598
671
  if (me) {
599
- writeLine(`✓ Claude360 服务可访问`);
600
- writeLine(`✓ 余额:${me.balance_display ?? me.quota}`);
601
- writeLine(`✓ 今日用量:${me.today_usage_display || "-"}(已刷新)`);
672
+ msg.success("Claude360 服务可访问");
673
+ msg.result("余额", me.balance_display ?? me.quota);
674
+ msg.result("今日用量", `${me.today_usage_display || "-"}(已刷新)`);
602
675
  } else {
603
- writeLine("! Claude360 服务状态获取失败,仍可尝试启动");
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
- writeLine("正在启动 Claude Code...");
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
- writeLine("当前 Claude360 网关暂不支持 Codex 所需协议。");
637
- writeLine("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
709
+ msg.warn("当前 Claude360 网关暂不支持 Codex 所需协议。");
710
+ msg.hint("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
638
711
  return false;
639
712
  }
640
- writeLine("Claude360 已支持 Codex 所需协议");
713
+ msg.success("Claude360 已支持 Codex 所需协议");
641
714
  if (!(await preflightLaunch("Codex"))) {
642
715
  return false;
643
716
  }
644
717
  await markConfigured("codex");
645
- writeLine("正在启动 Codex...");
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
- writeLine("当前 Claude360 网关暂不支持 Codex 所需协议,已停止写入配置。");
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
- writeLine("已写入 Codex claude360 provider/profile(~/.codex/config.toml)");
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
- writeLine("支付成功");
739
+ msg.success("支付成功");
667
740
  if (result?.balance) {
668
- writeLine(`当前余额:${result.balance.balance_display ?? result.balance.quota}`);
669
- writeLine(`今日用量:${result.balance.today_usage_display || "-"}`);
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
- writeLine(`充值未完成:${safeErrorMessage(error)}`);
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
- const results = await installOrUpdateTools({ targets, confirm });
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
- writeLine(`已跳过 ${result.target}。`);
793
+ msg.info(`已跳过 ${result.target}。`);
695
794
  continue;
696
795
  }
697
796
  if (result?.ok === false) {
698
- writeLine(`安装或更新 ${result.target} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
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
- writeLine(`✓ ${result.target} 安装或更新完成${result.usedMirror ? "(使用国内镜像)" : ""}`);
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
- writeLine("claude360 CLI 已更新,重新运行 npx claude360 后生效。");
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
- writeLine(`正在打开${label}:${url}`);
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
- writeLine("当前未登录。");
836
+ msg.info("当前未登录。");
718
837
  return true;
719
838
  }
720
839
  const approved = await confirm(
721
- "将退出登录并清除本地保存的 CLI Token 与 API Key。\n不会删除 Claude360 网站上的账号或 API Key。\n是否继续?",
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
- writeLine("已取消。");
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
- writeLine("已退出登录。");
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
- "该操作会清除本地 Claude360 CLI 登录状态和已保存 Key。\n不会删除 Claude360 网站上的账号或 API Key。\n是否继续?",
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
- writeLine("已取消。");
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
- writeLine("本地配置已清除。");
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
- writeLine("状态:余额偏低,建议充值");
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
- writeLine(`✓ 当前 Key 已切换为 ${token.tokenName}`);
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
- writeLine("Claude Code 配置修复完成(启动时注入环境变量)。");
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
- writeLine(`✓ 当前 Key 已切换为 ${token.tokenName}`);
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
- writeLine("Claude Code 将在启动时注入 Claude360 配置。");
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 ? " Claude360 连接测试通过" : "! Claude360 连接测试失败,可稍后在诊断菜单排查");
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
- writeLine(safeErrorMessage(error));
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
- writeLine(`✓ 当前 Key 已切换为 ${token.tokenName}`);
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