claude360 0.2.8 → 0.2.9

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.
@@ -3,8 +3,9 @@ import { access, readFile as fsReadFile } from "node:fs/promises";
3
3
  import { constants } from "node:fs";
4
4
  import os from "node:os";
5
5
 
6
- import { resolveCodexConfigPath } from "./tool-launcher.js";
6
+ import { resolveCodexConfigPath, resolveCodexProfileConfigPath } from "./tool-launcher.js";
7
7
  import { sanitizeError, sanitizeText } from "./sanitize.js";
8
+ import { formatMessage } from "./messages.js";
8
9
  import { renderHeader, renderSectionTitle, renderTable } from "./ui.js";
9
10
 
10
11
  export async function runDiagnostics({
@@ -14,6 +15,7 @@ export async function runDiagnostics({
14
15
  checkPathWritable = defaultCheckPathWritable,
15
16
  readFile = fsReadFile,
16
17
  codexConfigPath = resolveCodexConfigPath(),
18
+ codexProfileConfigPath = resolveCodexProfileConfigPath(),
17
19
  api,
18
20
  } = {}) {
19
21
  const node = await commandVersion(execCommand, "node", ["--version"]);
@@ -65,7 +67,7 @@ export async function runDiagnostics({
65
67
  token: buildTokenStatus({ config, tokens }),
66
68
  topUp: buildTopUpStatus({ topUpOptions }),
67
69
  claudeCodeConfig: buildClaudeCodeConfigStatus({ config, claudeCode }),
68
- codexConfig: await buildCodexConfigStatus({ readFile, codexConfigPath, codex }),
70
+ codexConfig: await buildCodexConfigStatus({ readFile, codexConfigPath, codexProfileConfigPath, codex }),
69
71
  codexCompat: buildCodexCompatStatus({ codexCompat }),
70
72
  };
71
73
  }
@@ -108,22 +110,39 @@ function buildClaudeCodeConfigStatus({ config, claudeCode }) {
108
110
  return { ok: true, detail: "启动时注入 ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN" };
109
111
  }
110
112
 
111
- async function buildCodexConfigStatus({ readFile, codexConfigPath, codex }) {
113
+ async function buildCodexConfigStatus({ readFile, codexConfigPath, codexProfileConfigPath, codex }) {
112
114
  if (!codex.ok) {
113
115
  return { ok: false, detail: "Codex 未安装" };
114
116
  }
117
+ let baseContent;
115
118
  try {
116
- const content = await readFile(codexConfigPath, "utf8");
117
- if (content.includes("[model_providers.claude360]") && content.includes("[profiles.claude360]")) {
118
- return { ok: true, detail: "profile claude360 正常" };
119
- }
120
- return { ok: false, detail: "未写入 claude360 provider/profile" };
119
+ baseContent = await readFile(codexConfigPath, "utf8");
121
120
  } catch (error) {
122
121
  if (error?.code === "ENOENT") {
123
122
  return { ok: false, detail: "未找到 ~/.codex/config.toml" };
124
123
  }
125
124
  return { ok: false, detail: sanitizeError(error) || "读取 config.toml 失败" };
126
125
  }
126
+ if (!baseContent.includes("[model_providers.claude360]")) {
127
+ return { ok: false, detail: "未写入 claude360 provider" };
128
+ }
129
+ // legacy [profiles.claude360] 与 --profile 共存会导致新版 Codex 启动失败,需重新写入迁移
130
+ if (baseContent.includes("[profiles.claude360]")) {
131
+ return { ok: false, detail: "检测到 legacy [profiles.claude360],请在「Codex 配置」中重新写入以迁移" };
132
+ }
133
+ let profileContent;
134
+ try {
135
+ profileContent = await readFile(codexProfileConfigPath, "utf8");
136
+ } catch (error) {
137
+ if (error?.code === "ENOENT") {
138
+ return { ok: false, detail: "缺少 ~/.codex/claude360.config.toml(profile 覆盖层)" };
139
+ }
140
+ return { ok: false, detail: sanitizeError(error) || "读取 claude360.config.toml 失败" };
141
+ }
142
+ if (!/model_provider\s*=\s*"claude360"/.test(profileContent)) {
143
+ return { ok: false, detail: "claude360.config.toml 缺少 model_provider" };
144
+ }
145
+ return { ok: true, detail: "profile claude360 正常" };
127
146
  }
128
147
 
129
148
  function buildCodexCompatStatus({ codexCompat }) {
@@ -139,7 +158,7 @@ function buildCodexCompatStatus({ codexCompat }) {
139
158
  // 诊断报告(补充需求第 5 节):按模块分组的小表格展示,
140
159
  // 状态用文字标签(通过/警告/失败)明确区分,失败项附修复建议,
141
160
  // 输出适合直接复制给客服或开发排查(出口统一脱敏)。
142
- export function formatDiagnosticsSummary(report, { width = 0 } = {}) {
161
+ export function formatDiagnosticsSummary(report, { width = 0, color = false } = {}) {
143
162
  const groups = [
144
163
  {
145
164
  title: "系统环境",
@@ -179,31 +198,31 @@ export function formatDiagnosticsSummary(report, { width = 0 } = {}) {
179
198
  },
180
199
  ];
181
200
 
182
- const lines = [renderHeader("诊断报告")];
201
+ const lines = [renderHeader("诊断报告", { color })];
183
202
  for (const group of groups) {
184
- lines.push("", renderSectionTitle(group.title));
203
+ lines.push("", renderSectionTitle(group.title, { color }));
185
204
  lines.push(renderTable({
186
205
  head: ["检查项", "结果", "状态"],
187
206
  rows: group.items.map(({ label, item, valueKey = "detail" }) => [
188
207
  label,
189
208
  String(item[valueKey] || item.detail || "-"),
190
- statusLabel(item),
209
+ statusLabel(item, color),
191
210
  ]),
192
- }, { width }));
211
+ }, { width, color }));
193
212
  const fixes = group.items.filter(({ item, fix }) => !item.ok && fix);
194
213
  for (const { label, item, fix } of fixes) {
195
- lines.push(`${item.warn ? "!" : "×"} ${label} 建议:${fix}`);
214
+ lines.push(formatMessage(item.warn ? "warn" : "error", `${label} 建议:${fix}`, { color }));
196
215
  }
197
216
  }
198
217
  // 出口兜底:report 由调用方注入,逐项 detail 可能未经脱敏
199
218
  return sanitizeText(lines.join("\n"));
200
219
  }
201
220
 
202
- function statusLabel(item) {
221
+ function statusLabel(item, color = false) {
203
222
  if (item.ok) {
204
- return " 通过";
223
+ return formatMessage("success", "通过", { color });
205
224
  }
206
- return item.warn ? "! 警告" : "× 失败";
225
+ return item.warn ? formatMessage("warn", "警告", { color }) : formatMessage("error", "失败", { color });
207
226
  }
208
227
 
209
228
  function defaultPlatform() {
package/src/index.js CHANGED
@@ -10,6 +10,7 @@ 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 { createMessenger, formatMessage } from "./messages.js";
13
14
  import { renderHeader } from "./ui.js";
14
15
  import {
15
16
  commandVersion,
@@ -49,6 +50,7 @@ import {
49
50
  launchClaudeCode as startClaudeCode,
50
51
  launchCodex as startCodex,
51
52
  resolveCodexConfigPath,
53
+ resolveCodexProfileConfigPath,
52
54
  } from "./tool-launcher.js";
53
55
  import { installClaudeWorkflows, installCodexWorkflows } from "./workflows.js";
54
56
  import { showOpenSourceNotice } from "./zcf-notice.js";
@@ -114,6 +116,10 @@ export async function runCli({
114
116
  let api = createApiClient({ baseUrl, cliToken: config.cliToken || "" });
115
117
  // 仅在真实终端(writeLine 未被测试替换)启用彩色与动效
116
118
  const fancyOutput = writeLine === console.log ? colorLevel() : 0;
119
+ // 语义消息器:把行内交互文字按角色着色(成功/失败/警告/步骤/键值回显/路径/
120
+ // 次要说明等),统一层级,见 messages.js。color 跟随 fancyOutput,
121
+ // 测试/管道(writeLine 被替换)下为 0,输出退化为纯文本前缀。
122
+ const msg = createMessenger({ writeLine, color: fancyOutput });
117
123
  // 方向键交互:真实 TTY 且交互未被测试替换时启用(优化需求第五节),
118
124
  // 非交互环境降级为编号输入,由 prompts.js 统一处理
119
125
  const interactiveUi = writeLine === console.log && isInteractive();
@@ -192,8 +198,8 @@ export async function runCli({
192
198
  }
193
199
  break;
194
200
  }
195
- writeLine("请在浏览器中完成 Claude360 授权。");
196
- writeLine("如果浏览器没有自动打开,请根据终端提示访问授权页面并输入授权码。");
201
+ msg.info("请在浏览器中完成 Claude360 授权。");
202
+ msg.info("如果浏览器没有自动打开,请根据终端提示访问授权页面并输入授权码。");
197
203
  let cliToken;
198
204
  try {
199
205
  cliToken = await authWithBrowser({ api, openBrowser, sleep, writeLine });
@@ -206,7 +212,7 @@ export async function runCli({
206
212
  }
207
213
  await saveConfig({ cliToken });
208
214
  api = createApiClient({ baseUrl, cliToken });
209
- writeLine("授权成功,登录状态已保存。");
215
+ msg.success("授权成功,登录状态已保存。");
210
216
  return true;
211
217
  }
212
218
 
@@ -241,11 +247,14 @@ export async function runCli({
241
247
  async function showBalanceAndUsage() {
242
248
  const me = await fetchMe();
243
249
  if (!me) {
244
- writeLine("暂时无法获取余额与今日用量。");
250
+ msg.error("暂时无法获取余额与今日用量。");
245
251
  return null;
246
252
  }
247
- writeLine(`余额:${me.balance_display ?? me.quota}${me.low_balance ? " ! 余额较低" : ""}`);
248
- writeLine(`今日用量:${me.today_usage_display || "-"}`);
253
+ msg.result("余额", me.balance_display ?? me.quota);
254
+ if (me.low_balance) {
255
+ msg.warn("余额较低");
256
+ }
257
+ msg.result("今日用量", me.today_usage_display || "-");
249
258
  return me;
250
259
  }
251
260
 
@@ -266,40 +275,40 @@ export async function runCli({
266
275
 
267
276
  // 检查 / 安装 / 更新工具(PRD 6.1 / 8.1):展示当前与最新版本,更新需确认
268
277
  async function checkInstallOrUpdateTool(target, toolName, command) {
269
- writeLine(`正在检查 ${toolName}...`);
278
+ msg.step(`正在检查 ${toolName}...`);
270
279
  const check = await commandVersion(execCommand, command, ["--version"]);
271
280
  if (!check.ok) {
272
281
  return ensureToolInstalled(target, toolName, command);
273
282
  }
274
- writeLine(`当前版本:${check.version}`);
283
+ msg.result("当前版本", check.version);
275
284
  const packageName = buildInstallCommand(target).packageName;
276
285
  const latest = await execCommand("npm", ["view", packageName, "version"]);
277
286
  if (latest.ok) {
278
- writeLine(`最新版本:v${latest.stdout.trim()}`);
287
+ msg.result("最新版本", `v${latest.stdout.trim()}`);
279
288
  } else {
280
- writeLine("最新版本:获取失败(npm 网络异常时可使用国内镜像)");
289
+ msg.warn("最新版本获取失败(npm 网络异常时可使用国内镜像)");
281
290
  }
282
- writeLine(`检测到 ${toolName} 将通过 npm 更新。`);
291
+ msg.info(`检测到 ${toolName} 将通过 npm 更新。`);
283
292
  const action = await promptSelect(`是否更新 ${toolName}?`, [
284
293
  { label: "更新到最新版", value: "update" },
285
294
  { label: "跳过", value: "skip" },
286
295
  ]);
287
296
  if (action !== "update") {
288
- writeLine(`已跳过 ${toolName} 更新。`);
297
+ msg.info(`已跳过 ${toolName} 更新。`);
289
298
  return true;
290
299
  }
291
300
  const results = await installOrUpdateTools({ targets: [target], confirm });
292
301
  const result = (results || []).find((item) => item.target === target);
293
302
  if (result?.skipped) {
294
- writeLine(`已跳过 ${toolName} 更新。`);
303
+ msg.info(`已跳过 ${toolName} 更新。`);
295
304
  return true;
296
305
  }
297
306
  if (result?.ok === false) {
298
- writeLine(`更新 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
299
- writeLine("国内网络失败时可尝试:npm install -g --registry=https://registry.npmmirror.com");
307
+ msg.error(`更新 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
308
+ msg.hint("国内网络失败时可尝试:npm install -g --registry=https://registry.npmmirror.com");
300
309
  return false;
301
310
  }
302
- writeLine(`✓ ${toolName} 已更新到最新版。`);
311
+ msg.success(`${toolName} 已更新到最新版。`);
303
312
  return true;
304
313
  }
305
314
 
@@ -325,14 +334,14 @@ export async function runCli({
325
334
  tokenName: token.tokenName,
326
335
  group: token.group,
327
336
  });
328
- writeLine(`✓ 当前 Key 已切换为 ${token.tokenName}`);
337
+ msg.success(`当前 Key 已切换为 ${token.tokenName}`);
329
338
  } else {
330
339
  await ensureApiKey();
331
340
  }
332
341
  await markConfigured("claudeCode");
333
- writeLine("Claude Code 将在启动时注入 Claude360 配置(不修改 shell profile):");
334
- writeLine(` ANTHROPIC_BASE_URL=${baseUrl}`);
335
- writeLine(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
342
+ msg.success("Claude Code 将在启动时注入 Claude360 配置(不修改 shell profile):");
343
+ msg.path(` ANTHROPIC_BASE_URL=${baseUrl}`);
344
+ msg.path(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
336
345
  return true;
337
346
  }
338
347
  }
@@ -393,8 +402,9 @@ export async function runCli({
393
402
  async function runCodexProviderMenu() {
394
403
  while (true) {
395
404
  writeLine(renderHeader("Codex Provider 配置", { color: fancyOutput }));
396
- writeLine("当前 Providerclaude360");
397
- writeLine("当前 Profileclaude360\n");
405
+ msg.result("当前 Provider", "claude360");
406
+ msg.result("当前 Profile", "claude360");
407
+ writeLine("");
398
408
  const action = await promptSelect("请选择:", [
399
409
  { label: "写入 / 修复 Claude360 Provider", value: "write" },
400
410
  { label: "查看当前 Codex 配置", value: "view" },
@@ -409,13 +419,27 @@ export async function runCli({
409
419
  }
410
420
  try {
411
421
  const content = await readFileImpl(resolveCodexConfigPath(), "utf8");
412
- writeLine("当前 ~/.codex/config.toml 内容:\n");
422
+ msg.info("当前 ~/.codex/config.toml 内容:");
423
+ writeLine("");
413
424
  writeLine(content);
414
425
  } catch (error) {
415
426
  if (error?.code === "ENOENT") {
416
- writeLine("未找到 ~/.codex/config.toml,可先执行「写入 / 修复 Claude360 Provider」。");
427
+ msg.info("未找到 ~/.codex/config.toml,可先执行「写入 / 修复 Claude360 Provider」。");
428
+ } else {
429
+ msg.error(`读取 Codex 配置失败:${safeErrorMessage(error)}`);
430
+ }
431
+ }
432
+ try {
433
+ const profileContent = await readFileImpl(resolveCodexProfileConfigPath(), "utf8");
434
+ writeLine("");
435
+ msg.info("当前 ~/.codex/claude360.config.toml(profile 覆盖层)内容:");
436
+ writeLine("");
437
+ writeLine(profileContent);
438
+ } catch (error) {
439
+ if (error?.code === "ENOENT") {
440
+ msg.info("未找到 ~/.codex/claude360.config.toml(profile 覆盖层),可执行「写入 / 修复 Claude360 Provider」生成。");
417
441
  } else {
418
- writeLine(`读取 Codex 配置失败:${safeErrorMessage(error)}`);
442
+ msg.error(`读取 Codex profile 配置失败:${safeErrorMessage(error)}`);
419
443
  }
420
444
  }
421
445
  }
@@ -423,14 +447,14 @@ export async function runCli({
423
447
 
424
448
  // Codex 协议兼容性检测(PRD 8.6)
425
449
  async function checkCodexCompatStep() {
426
- writeLine("正在检测 Claude360 是否支持 Codex 所需协议...");
450
+ msg.step("正在检测 Claude360 是否支持 Codex 所需协议...");
427
451
  const compat = await codexCompat(api);
428
452
  if (compat.supported) {
429
- writeLine("Claude360 已支持 Codex 所需协议");
453
+ msg.success("Claude360 已支持 Codex 所需协议");
430
454
  return true;
431
455
  }
432
- writeLine("当前 Claude360 网关暂不支持 Codex 所需协议。");
433
- writeLine("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
456
+ msg.warn("当前 Claude360 网关暂不支持 Codex 所需协议。");
457
+ msg.hint("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
434
458
  const next = await promptSelect("请选择:", [
435
459
  { label: "返回", value: "back" },
436
460
  { label: "打开 Claude360 控制台", value: "console" },
@@ -443,7 +467,7 @@ export async function runCli({
443
467
 
444
468
  async function testConnectionStep() {
445
469
  const me = await fetchMe();
446
- writeLine(me ? " Claude360 连接测试通过" : "! Claude360 连接测试失败,可稍后在诊断菜单排查");
470
+ writeLine(me ? formatMessage("success", "Claude360 连接测试通过", { color: fancyOutput }) : formatMessage("warn", "Claude360 连接测试失败,可稍后在诊断菜单排查", { color: fancyOutput }));
447
471
  }
448
472
 
449
473
  // ──────────────────────────────────────────────
@@ -458,15 +482,15 @@ export async function runCli({
458
482
  showBalance: showBalanceAndUsage,
459
483
  ensureApiKey: async () => {
460
484
  await ensureApiKey();
461
- writeLine(`✓ 当前 Key:${config.tokenName || "-"}`);
485
+ msg.result("当前 Key", config.tokenName || "-");
462
486
  },
463
487
  installTool: () => checkInstallOrUpdateTool("claude", "Claude Code", "claude"),
464
488
  configureApi: async () => {
465
489
  await ensureApiKey();
466
490
  await markConfigured("claudeCode");
467
- writeLine("Claude Code 将在启动时注入 Claude360 配置(不修改 shell profile):");
468
- writeLine(` ANTHROPIC_BASE_URL=${baseUrl}`);
469
- writeLine(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
491
+ msg.success("Claude Code 将在启动时注入 Claude360 配置(不修改 shell profile):");
492
+ msg.path(` ANTHROPIC_BASE_URL=${baseUrl}`);
493
+ msg.path(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
470
494
  },
471
495
  configureLanguage: () => configureLanguage({ baseDir: claudeDir, filePath: claudeMemoryPath, promptSelect, writeLine }),
472
496
  configureStyle: () => configureStyle({ baseDir: claudeDir, filePath: claudeMemoryPath, promptSelect, writeLine }),
@@ -491,7 +515,7 @@ export async function runCli({
491
515
  showBalance: showBalanceAndUsage,
492
516
  ensureApiKey: async () => {
493
517
  await ensureApiKey();
494
- writeLine(`✓ 当前 Key:${config.tokenName || "-"}`);
518
+ msg.result("当前 Key", config.tokenName || "-");
495
519
  },
496
520
  installTool: () => checkInstallOrUpdateTool("codex", "Codex", "codex"),
497
521
  configureLanguage: () => configureLanguage({ baseDir: codexDir, filePath: agentsPath, promptSelect, writeLine }),
@@ -502,7 +526,7 @@ export async function runCli({
502
526
  await ensureApiKey();
503
527
  await configureCodex({ config, confirmConflict: confirmDanger, writeLine });
504
528
  await markConfigured("codex");
505
- writeLine("已写入 Codex claude360 provider/profile(~/.codex/config.toml)");
529
+ msg.success("已写入 Codex claude360 provider(~/.codex/config.toml)与 profile 覆盖层(~/.codex/claude360.config.toml)");
506
530
  },
507
531
  checkCompat: checkCodexCompatStep,
508
532
  installMcps: () => installCodexMcpServers({ api, codexDir, multiSelect, confirm, writeLine }),
@@ -568,19 +592,19 @@ export async function runCli({
568
592
  async function ensureToolInstalled(target, toolName, command) {
569
593
  const check = await commandVersion(execCommand, command, ["--version"]);
570
594
  if (check.ok) {
571
- writeLine(`✓ ${toolName} 已安装(${check.version})`);
595
+ msg.success(`${toolName} 已安装(${check.version})`);
572
596
  return true;
573
597
  }
574
- writeLine(`未检测到 ${toolName}。`);
598
+ msg.info(`未检测到 ${toolName}。`);
575
599
  const results = await installOrUpdateTools({ targets: [target], confirm });
576
600
  const result = (results || []).find((item) => item.target === target);
577
601
  if (result?.skipped) {
578
- writeLine(`已跳过 ${toolName} 安装。`);
602
+ msg.info(`已跳过 ${toolName} 安装。`);
579
603
  return false;
580
604
  }
581
605
  if (result?.ok === false) {
582
- writeLine(`安装 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
583
- writeLine("国内网络失败时可尝试:npx --registry=https://registry.npmmirror.com claude360");
606
+ msg.error(`安装 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
607
+ msg.hint("国内网络失败时可尝试:npx --registry=https://registry.npmmirror.com claude360");
584
608
  return false;
585
609
  }
586
610
  return true;
@@ -590,17 +614,17 @@ export async function runCli({
590
614
  async function preflightLaunch(toolName) {
591
615
  const me = await fetchMe();
592
616
  if (!config.apiKey) {
593
- writeLine("当前没有可用 API Key,先完成 Key 配置。");
617
+ msg.warn("当前没有可用 API Key,先完成 Key 配置。");
594
618
  await ensureApiKey();
595
619
  }
596
- writeLine(`✓ ${toolName} 启动前检查`);
597
- writeLine(`✓ 当前 Key:${config.tokenName || "-"}`);
620
+ msg.heading(`${toolName} 启动前检查`);
621
+ msg.result("当前 Key", config.tokenName || "-");
598
622
  if (me) {
599
- writeLine(`✓ Claude360 服务可访问`);
600
- writeLine(`✓ 余额:${me.balance_display ?? me.quota}`);
601
- writeLine(`✓ 今日用量:${me.today_usage_display || "-"}(已刷新)`);
623
+ msg.success("Claude360 服务可访问");
624
+ msg.result("余额", me.balance_display ?? me.quota);
625
+ msg.result("今日用量", `${me.today_usage_display || "-"}(已刷新)`);
602
626
  } else {
603
- writeLine("! Claude360 服务状态获取失败,仍可尝试启动");
627
+ msg.warn("Claude360 服务状态获取失败,仍可尝试启动");
604
628
  }
605
629
  if (me?.low_balance) {
606
630
  const action = await promptSelect(
@@ -625,7 +649,7 @@ export async function runCli({
625
649
  return false;
626
650
  }
627
651
  await markConfigured("claudeCode");
628
- writeLine("正在启动 Claude Code...");
652
+ msg.step("正在启动 Claude Code...");
629
653
  await launchClaudeCode({ config });
630
654
  return true;
631
655
  }
@@ -633,16 +657,16 @@ export async function runCli({
633
657
  async function doLaunchCodex() {
634
658
  const compat = await codexCompat(api);
635
659
  if (!compat.supported) {
636
- writeLine("当前 Claude360 网关暂不支持 Codex 所需协议。");
637
- writeLine("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
660
+ msg.warn("当前 Claude360 网关暂不支持 Codex 所需协议。");
661
+ msg.hint("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
638
662
  return false;
639
663
  }
640
- writeLine("Claude360 已支持 Codex 所需协议");
664
+ msg.success("Claude360 已支持 Codex 所需协议");
641
665
  if (!(await preflightLaunch("Codex"))) {
642
666
  return false;
643
667
  }
644
668
  await markConfigured("codex");
645
- writeLine("正在启动 Codex...");
669
+ msg.step("正在启动 Codex...");
646
670
  await launchCodex({ config, confirmConflict: confirmDanger, writeLine });
647
671
  return true;
648
672
  }
@@ -650,27 +674,27 @@ export async function runCli({
650
674
  async function doConfigureCodex() {
651
675
  const compat = await codexCompat(api);
652
676
  if (!compat.supported) {
653
- writeLine("当前 Claude360 网关暂不支持 Codex 所需协议,已停止写入配置。");
677
+ msg.warn("当前 Claude360 网关暂不支持 Codex 所需协议,已停止写入配置。");
654
678
  return false;
655
679
  }
656
680
  await ensureApiKey();
657
681
  await configureCodex({ config, confirmConflict: confirmDanger, writeLine });
658
682
  await markConfigured("codex");
659
- writeLine("已写入 Codex claude360 provider/profile(~/.codex/config.toml)");
683
+ msg.success("已写入 Codex claude360 provider/profile(~/.codex/config.toml)");
660
684
  return true;
661
685
  }
662
686
 
663
687
  async function doWechatTopUp() {
664
688
  try {
665
689
  const result = await runWechatTopUp({ api, promptSelect, promptInput, writeLine, sleep });
666
- writeLine("支付成功");
690
+ msg.success("支付成功");
667
691
  if (result?.balance) {
668
- writeLine(`当前余额:${result.balance.balance_display ?? result.balance.quota}`);
669
- writeLine(`今日用量:${result.balance.today_usage_display || "-"}`);
692
+ msg.result("当前余额", result.balance.balance_display ?? result.balance.quota);
693
+ msg.result("今日用量", result.balance.today_usage_display || "-");
670
694
  }
671
695
  return result;
672
696
  } catch (error) {
673
- writeLine(`充值未完成:${safeErrorMessage(error)}`);
697
+ msg.error(`充值未完成:${safeErrorMessage(error)}`);
674
698
  return null;
675
699
  }
676
700
  }
@@ -691,30 +715,30 @@ export async function runCli({
691
715
  const results = await installOrUpdateTools({ targets, confirm });
692
716
  for (const result of results || []) {
693
717
  if (result?.skipped) {
694
- writeLine(`已跳过 ${result.target}。`);
718
+ msg.info(`已跳过 ${result.target}。`);
695
719
  continue;
696
720
  }
697
721
  if (result?.ok === false) {
698
- writeLine(`安装或更新 ${result.target} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
722
+ msg.error(`安装或更新 ${result.target} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
699
723
  continue;
700
724
  }
701
- writeLine(`✓ ${result.target} 安装或更新完成${result.usedMirror ? "(使用国内镜像)" : ""}`);
725
+ msg.success(`${result.target} 安装或更新完成${result.usedMirror ? "(使用国内镜像)" : ""}`);
702
726
  if (result.target === "claude360") {
703
- writeLine("claude360 CLI 已更新,重新运行 npx claude360 后生效。");
727
+ msg.info("claude360 CLI 已更新,重新运行 npx claude360 后生效。");
704
728
  }
705
729
  }
706
730
  }
707
731
 
708
732
  async function openPage(path, label) {
709
733
  const url = `${baseUrl}${path}`;
710
- writeLine(`正在打开${label}:${url}`);
734
+ msg.step(`正在打开${label}:${url}`);
711
735
  await openBrowser(url);
712
736
  }
713
737
 
714
738
  // 退出登录:仅清除本地凭证,不触碰网站侧账号与 Key(PRD 25.2 logout)
715
739
  async function doLogout() {
716
740
  if (!config.cliToken && !config.apiKey) {
717
- writeLine("当前未登录。");
741
+ msg.info("当前未登录。");
718
742
  return true;
719
743
  }
720
744
  const approved = await confirm(
@@ -722,13 +746,13 @@ export async function runCli({
722
746
  { danger: true },
723
747
  );
724
748
  if (!approved) {
725
- writeLine("已取消。");
749
+ msg.info("已取消。");
726
750
  return false;
727
751
  }
728
752
  config = { baseUrl };
729
753
  await configStore.save(config);
730
754
  api = createApiClient({ baseUrl, cliToken: "" });
731
- writeLine("已退出登录。");
755
+ msg.info("已退出登录。");
732
756
  return true;
733
757
  }
734
758
 
@@ -748,13 +772,13 @@ export async function runCli({
748
772
  { danger: true },
749
773
  );
750
774
  if (!approved) {
751
- writeLine("已取消。");
775
+ msg.info("已取消。");
752
776
  return false;
753
777
  }
754
778
  config = { baseUrl };
755
779
  await configStore.save(config);
756
780
  api = createApiClient({ baseUrl, cliToken: "" });
757
- writeLine("本地配置已清除。");
781
+ msg.info("本地配置已清除。");
758
782
  return ensureLogin();
759
783
  }
760
784
 
@@ -767,7 +791,7 @@ export async function runCli({
767
791
  writeLine(renderHeader("余额与充值", { color: fancyOutput }));
768
792
  const me = await showBalanceAndUsage();
769
793
  if (me?.low_balance) {
770
- writeLine("状态:余额偏低,建议充值");
794
+ msg.warn("余额偏低,建议充值");
771
795
  }
772
796
  const action = await promptSelect("请选择:", [
773
797
  { label: "微信扫码充值", value: "wechat" },
@@ -796,7 +820,7 @@ export async function runCli({
796
820
  tokenName: token.tokenName,
797
821
  group: token.group,
798
822
  });
799
- writeLine(`✓ 当前 Key 已切换为 ${token.tokenName}`);
823
+ msg.success(`当前 Key 已切换为 ${token.tokenName}`);
800
824
  }
801
825
  return token;
802
826
  }
@@ -967,14 +991,14 @@ export async function runCli({
967
991
  switch (action) {
968
992
  case "diagnose": {
969
993
  const report = await runDiagnostics({ config, api, execCommand });
970
- writeLine(formatDiagnosticsSummary(report, { width: outputWidth() }));
994
+ writeLine(formatDiagnosticsSummary(report, { width: outputWidth(), color: fancyOutput }));
971
995
  break;
972
996
  }
973
997
  case "fix_claude":
974
998
  await ensureApiKey();
975
999
  await markConfigured("claudeCode");
976
1000
  await showBalanceAndUsage();
977
- writeLine("Claude Code 配置修复完成(启动时注入环境变量)。");
1001
+ msg.success("Claude Code 配置修复完成(启动时注入环境变量)。");
978
1002
  break;
979
1003
  case "fix_codex":
980
1004
  await doConfigureCodex();
@@ -987,7 +1011,7 @@ export async function runCli({
987
1011
  tokenName: token.tokenName,
988
1012
  group: token.group,
989
1013
  });
990
- writeLine(`✓ 当前 Key 已切换为 ${token.tokenName}`);
1014
+ msg.success(`当前 Key 已切换为 ${token.tokenName}`);
991
1015
  break;
992
1016
  }
993
1017
  case "reset":
@@ -1021,7 +1045,7 @@ export async function runCli({
1021
1045
  return false;
1022
1046
  }
1023
1047
  await markConfigured("claudeCode");
1024
- writeLine("Claude Code 将在启动时注入 Claude360 配置。");
1048
+ msg.success("Claude Code 将在启动时注入 Claude360 配置。");
1025
1049
  }
1026
1050
  if (targets.includes("codex")) {
1027
1051
  if (!(await ensureToolInstalled("codex", "Codex", "codex"))) {
@@ -1057,7 +1081,7 @@ export async function runCli({
1057
1081
 
1058
1082
  // 测试连接
1059
1083
  const me = await fetchMe();
1060
- writeLine(me ? " Claude360 连接测试通过" : "! Claude360 连接测试失败,可稍后在诊断菜单排查");
1084
+ writeLine(me ? formatMessage("success", "Claude360 连接测试通过", { color: fancyOutput }) : formatMessage("warn", "Claude360 连接测试失败,可稍后在诊断菜单排查", { color: fancyOutput }));
1061
1085
 
1062
1086
  // 启动
1063
1087
  if (targets.length === 1) {
@@ -1087,7 +1111,7 @@ export async function runCli({
1087
1111
  try {
1088
1112
  await checkEnvironment({ writeLine, execCommand });
1089
1113
  } catch (error) {
1090
- writeLine(safeErrorMessage(error));
1114
+ msg.error(safeErrorMessage(error));
1091
1115
  return "environment_failed";
1092
1116
  }
1093
1117
  }
@@ -1165,7 +1189,7 @@ export async function runCli({
1165
1189
  tokenName: token.tokenName,
1166
1190
  group: token.group,
1167
1191
  });
1168
- writeLine(`✓ 当前 Key 已切换为 ${token.tokenName}`);
1192
+ msg.success(`当前 Key 已切换为 ${token.tokenName}`);
1169
1193
  break;
1170
1194
  }
1171
1195
  case "claude_model":
@@ -1186,7 +1210,7 @@ export async function runCli({
1186
1210
  writeLine(renderHeader("主菜单", { color: fancyOutput }));
1187
1211
  }
1188
1212
  const status = await loadAccountStatus({ api, config });
1189
- writeLine(formatAccountStatus({ ...status, width: outputWidth() }));
1213
+ writeLine(formatAccountStatus({ ...status, width: outputWidth(), color: fancyOutput }));
1190
1214
  writeLine("");
1191
1215
  const action = await promptMenu({
1192
1216
  menu: buildDailyMenu(),
@@ -1280,7 +1304,7 @@ export async function runCli({
1280
1304
  return "cc_switch";
1281
1305
  case "doctor": {
1282
1306
  const report = await runDiagnostics({ config, api, execCommand });
1283
- writeLine(formatDiagnosticsSummary(report, { width: outputWidth() }));
1307
+ writeLine(formatDiagnosticsSummary(report, { width: outputWidth(), color: fancyOutput }));
1284
1308
  return "doctor";
1285
1309
  }
1286
1310
  case "login":