claude360 0.2.2 → 0.2.3

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
@@ -1,7 +1,9 @@
1
1
  import readline from "node:readline/promises";
2
2
  import { stdin as input, stdout as output } from "node:process";
3
3
  import { execFile } from "node:child_process";
4
+ import { readFile as fsReadFile } from "node:fs/promises";
4
5
  import { createRequire } from "node:module";
6
+ import path from "node:path";
5
7
 
6
8
  import { ApiClient } from "./api-client.js";
7
9
  import { authenticateWithBrowser } from "./auth.js";
@@ -15,26 +17,41 @@ import {
15
17
  formatDiagnosticsSummary,
16
18
  runDiagnostics as collectDiagnostics,
17
19
  } from "./diagnostics.js";
20
+ import {
21
+ configureClaudeDefaultModel,
22
+ configureCodexDefaultModel,
23
+ configureOutputLanguage,
24
+ configurePromptStyle,
25
+ importClaudeEnvPermissions,
26
+ resolveClaudeDir,
27
+ resolveCodexDir,
28
+ } from "./init-config.js";
29
+ import { buildClaudeInitSteps, buildCodexInitSteps, runFullInit } from "./init-flow.js";
18
30
  import {
19
31
  buildDailyMenu,
20
32
  buildFirstRunMenu,
21
33
  promptMenu,
34
+ promptMultiSelect,
22
35
  } from "./menu.js";
23
36
  import {
24
37
  installCodexAgents,
38
+ installCodexMcps,
25
39
  installRecommendedMcps,
26
40
  installRecommendedSkills,
27
41
  listInstalledEnhancements,
28
42
  } from "./mcp-skill.js";
29
43
  import { ensureEnvironment } from "./onboarding.js";
30
44
  import { safeErrorMessage } from "./sanitize.js";
31
- import { installOrUpdateTools as installTools } from "./tool-installer.js";
45
+ import { buildInstallCommand, installOrUpdateTools as installTools } from "./tool-installer.js";
32
46
  import {
33
47
  checkCodexCompat,
34
48
  configureCodexProvider,
35
49
  launchClaudeCode as startClaudeCode,
36
50
  launchCodex as startCodex,
51
+ resolveCodexConfigPath,
37
52
  } from "./tool-launcher.js";
53
+ import { installClaudeWorkflows, installCodexWorkflows } from "./workflows.js";
54
+ import { showOpenSourceNotice } from "./zcf-notice.js";
38
55
  import { runWechatTopUp } from "./topup.js";
39
56
  import { chooseOrCreateToken, createNewToken } from "./token-manager.js";
40
57
 
@@ -65,9 +82,22 @@ export async function runCli({
65
82
  codexCompat = checkCodexCompat,
66
83
  configureCodex = configureCodexProvider,
67
84
  installMcps = installRecommendedMcps,
85
+ installCodexMcpServers = installCodexMcps,
68
86
  installSkills = installRecommendedSkills,
69
87
  installAgents = installCodexAgents,
88
+ installClaudeWfs = installClaudeWorkflows,
89
+ installCodexWfs = installCodexWorkflows,
70
90
  listEnhancements = listInstalledEnhancements,
91
+ showNotice = showOpenSourceNotice,
92
+ configureLanguage = configureOutputLanguage,
93
+ configureStyle = configurePromptStyle,
94
+ configureClaudeModel = configureClaudeDefaultModel,
95
+ configureCodexModel = configureCodexDefaultModel,
96
+ importEnvPermissions = importClaudeEnvPermissions,
97
+ multiSelectInput = promptMultiSelect,
98
+ readFileImpl = fsReadFile,
99
+ claudeDir = resolveClaudeDir(),
100
+ codexDir = resolveCodexDir(),
71
101
  ccSwitchGenerator = runCcSwitchGenerator,
72
102
  runDiagnostics = collectDiagnostics,
73
103
  execCommand = defaultExecCommand,
@@ -165,7 +195,7 @@ export async function runCli({
165
195
  if (config.apiKey) {
166
196
  return { apiKey: config.apiKey, tokenName: config.tokenName };
167
197
  }
168
- const token = await chooseToken({ api, promptSelect, promptInput });
198
+ const token = await chooseToken({ api, promptSelect, promptInput, writeLine });
169
199
  await saveConfig({
170
200
  apiKey: token.apiKey,
171
201
  selectedTokenId: token.tokenId,
@@ -206,6 +236,316 @@ export async function runCli({
206
236
  });
207
237
  }
208
238
 
239
+ // 多选交互:基于 promptInput 的复选列表(A 全选 / I 反选 / Enter 确认)
240
+ const multiSelect = (options) => multiSelectInput({ ...options, promptInput, writeLine });
241
+
242
+ // 终端宽度:仅真实终端下用于表格降级,测试/管道输出不限宽保证稳定
243
+ const outputWidth = () => (writeLine === console.log ? (process.stdout.columns || 0) : 0);
244
+
245
+ // 开源参考声明(PRD 第 2 章):进入借鉴自 NPX ZCF 的功能前展示
246
+ const confirmNotice = () => showNotice({ promptSelect, writeLine });
247
+
248
+ // 检查 / 安装 / 更新工具(PRD 6.1 / 8.1):展示当前与最新版本,更新需确认
249
+ async function checkInstallOrUpdateTool(target, toolName, command) {
250
+ writeLine(`正在检查 ${toolName}...`);
251
+ const check = await commandVersion(execCommand, command, ["--version"]);
252
+ if (!check.ok) {
253
+ return ensureToolInstalled(target, toolName, command);
254
+ }
255
+ writeLine(`当前版本:${check.version}`);
256
+ const packageName = buildInstallCommand(target).packageName;
257
+ const latest = await execCommand("npm", ["view", packageName, "version"]);
258
+ if (latest.ok) {
259
+ writeLine(`最新版本:v${latest.stdout.trim()}`);
260
+ } else {
261
+ writeLine("最新版本:获取失败(npm 网络异常时可使用国内镜像)");
262
+ }
263
+ writeLine(`检测到 ${toolName} 将通过 npm 更新。`);
264
+ const action = await promptSelect(`是否更新 ${toolName}?`, [
265
+ { label: "更新到最新版", value: "update" },
266
+ { label: "跳过", value: "skip" },
267
+ ]);
268
+ if (action !== "update") {
269
+ writeLine(`已跳过 ${toolName} 更新。`);
270
+ return true;
271
+ }
272
+ const results = await installOrUpdateTools({ targets: [target], confirm });
273
+ const result = (results || []).find((item) => item.target === target);
274
+ if (result?.skipped) {
275
+ writeLine(`已跳过 ${toolName} 更新。`);
276
+ return true;
277
+ }
278
+ if (result?.ok === false) {
279
+ writeLine(`更新 ${toolName} 失败:${result.error || ""}${result.remediation ? `\n建议:${result.remediation}` : ""}`);
280
+ writeLine("国内网络失败时可尝试:npm install -g --registry=https://registry.npmmirror.com");
281
+ return false;
282
+ }
283
+ writeLine(`✓ ${toolName} 已更新到最新版。`);
284
+ return true;
285
+ }
286
+
287
+ // 配置 Claude360 API(PRD 6.2):Key 选择 / 创建 / 重选,Base URL 不可手填
288
+ async function runClaudeApiConfigMenu() {
289
+ while (true) {
290
+ const action = await promptSelect("配置 Claude360 API", [
291
+ { label: `使用当前 API Key:${config.tokenName || "(未选择)"}`, value: "current" },
292
+ { label: "创建新的 API Key", value: "create" },
293
+ { label: "重新选择 API Key", value: "reselect" },
294
+ { label: "返回", value: "back" },
295
+ ]);
296
+ if (action === "back") {
297
+ return false;
298
+ }
299
+ if (action === "create") {
300
+ await doCreateKey();
301
+ } else if (action === "reselect") {
302
+ const token = await chooseToken({ api, promptSelect, promptInput, writeLine });
303
+ await saveConfig({
304
+ apiKey: token.apiKey,
305
+ selectedTokenId: token.tokenId,
306
+ tokenName: token.tokenName,
307
+ group: token.group,
308
+ });
309
+ writeLine(`✓ 当前 Key 已切换为 ${token.tokenName}`);
310
+ } else {
311
+ await ensureApiKey();
312
+ }
313
+ await markConfigured("claudeCode");
314
+ writeLine("✓ Claude Code 将在启动时注入 Claude360 配置(不修改 shell profile):");
315
+ writeLine(` ANTHROPIC_BASE_URL=${baseUrl}`);
316
+ writeLine(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
317
+ return true;
318
+ }
319
+ }
320
+
321
+ // Claude 全局记忆(PRD 5.1 第 6 项):语言 / 风格 / 助手规则,全部写标记区块
322
+ async function runClaudeMemoryMenu() {
323
+ const claudeMemoryPath = path.join(claudeDir, "CLAUDE.md");
324
+ while (true) {
325
+ const action = await promptSelect("配置 Claude 全局记忆(~/.claude/CLAUDE.md)", [
326
+ { label: "AI 输出语言", value: "language" },
327
+ { label: "系统提示词风格", value: "style" },
328
+ { label: "编程助手规则 / 工作流 Skill", value: "skills" },
329
+ { label: "返回", value: "back" },
330
+ ]);
331
+ switch (action) {
332
+ case "language":
333
+ await configureLanguage({ baseDir: claudeDir, filePath: claudeMemoryPath, promptSelect, writeLine });
334
+ break;
335
+ case "style":
336
+ await configureStyle({ baseDir: claudeDir, filePath: claudeMemoryPath, promptSelect, writeLine });
337
+ break;
338
+ case "skills":
339
+ await installSkills({ promptSelect, confirm, writeLine });
340
+ break;
341
+ default:
342
+ return;
343
+ }
344
+ }
345
+ }
346
+
347
+ // Codex 全局记忆(PRD 7.1 第 6 项):写 ~/.codex/AGENTS.md 标记区块
348
+ async function runCodexMemoryMenu() {
349
+ const agentsPath = path.join(codexDir, "AGENTS.md");
350
+ while (true) {
351
+ const action = await promptSelect("配置 Codex 全局记忆(~/.codex/AGENTS.md)", [
352
+ { label: "AI 输出语言", value: "language" },
353
+ { label: "系统提示词风格", value: "style" },
354
+ { label: "Claude360 协作规范(AGENTS)", value: "agents" },
355
+ { label: "返回", value: "back" },
356
+ ]);
357
+ switch (action) {
358
+ case "language":
359
+ await configureLanguage({ baseDir: codexDir, filePath: agentsPath, promptSelect, writeLine });
360
+ break;
361
+ case "style":
362
+ await configureStyle({ baseDir: codexDir, filePath: agentsPath, promptSelect, writeLine });
363
+ break;
364
+ case "agents":
365
+ await installAgents({ confirm, writeLine });
366
+ break;
367
+ default:
368
+ return;
369
+ }
370
+ }
371
+ }
372
+
373
+ // Claude360 Provider 菜单(PRD 8.5)
374
+ async function runCodexProviderMenu() {
375
+ while (true) {
376
+ writeLine("配置 Claude360 Provider\n");
377
+ writeLine("当前 Provider:claude360");
378
+ writeLine("当前 Profile:claude360\n");
379
+ const action = await promptSelect("请选择:", [
380
+ { label: "写入 / 修复 Claude360 Provider", value: "write" },
381
+ { label: "查看当前 Codex 配置", value: "view" },
382
+ { label: "返回", value: "back" },
383
+ ]);
384
+ if (action === "back") {
385
+ return;
386
+ }
387
+ if (action === "write") {
388
+ await doConfigureCodex();
389
+ continue;
390
+ }
391
+ try {
392
+ const content = await readFileImpl(resolveCodexConfigPath(), "utf8");
393
+ writeLine("当前 ~/.codex/config.toml 内容:\n");
394
+ writeLine(content);
395
+ } catch (error) {
396
+ if (error?.code === "ENOENT") {
397
+ writeLine("未找到 ~/.codex/config.toml,可先执行「写入 / 修复 Claude360 Provider」。");
398
+ } else {
399
+ writeLine(`读取 Codex 配置失败:${safeErrorMessage(error)}`);
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ // Codex 协议兼容性检测(PRD 8.6)
406
+ async function checkCodexCompatStep() {
407
+ writeLine("正在检测 Claude360 是否支持 Codex 所需协议...");
408
+ const compat = await codexCompat(api);
409
+ if (compat.supported) {
410
+ writeLine("✓ Claude360 已支持 Codex 所需协议");
411
+ return true;
412
+ }
413
+ writeLine("当前 Claude360 网关暂不支持 Codex 所需协议。");
414
+ writeLine("你仍可使用 Claude Code,或稍后更新 Claude360 CLI。");
415
+ const next = await promptSelect("请选择:", [
416
+ { label: "返回", value: "back" },
417
+ { label: "打开 Claude360 控制台", value: "console" },
418
+ ]);
419
+ if (next === "console") {
420
+ await openPage("/console", "Claude360 控制台");
421
+ }
422
+ return false;
423
+ }
424
+
425
+ async function testConnectionStep() {
426
+ const me = await fetchMe();
427
+ writeLine(me ? "✓ Claude360 连接测试通过" : "! Claude360 连接测试失败,可稍后在诊断菜单排查");
428
+ }
429
+
430
+ // ──────────────────────────────────────────────
431
+ // 完整初始化(PRD 4 / 5 / 7 章)
432
+ // ──────────────────────────────────────────────
433
+
434
+ function buildClaudeFullInitDeps({ noticeApproved = false } = {}) {
435
+ const claudeMemoryPath = path.join(claudeDir, "CLAUDE.md");
436
+ return {
437
+ showNotice: () => (noticeApproved ? true : confirmNotice()),
438
+ ensureLogin,
439
+ showBalance: showBalanceAndUsage,
440
+ ensureApiKey: async () => {
441
+ await ensureApiKey();
442
+ writeLine(`✓ 当前 Key:${config.tokenName || "-"}`);
443
+ },
444
+ installTool: () => checkInstallOrUpdateTool("claude", "Claude Code", "claude"),
445
+ configureApi: async () => {
446
+ await ensureApiKey();
447
+ await markConfigured("claudeCode");
448
+ writeLine("✓ Claude Code 将在启动时注入 Claude360 配置(不修改 shell profile):");
449
+ writeLine(` ANTHROPIC_BASE_URL=${baseUrl}`);
450
+ writeLine(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
451
+ },
452
+ configureLanguage: () => configureLanguage({ baseDir: claudeDir, filePath: claudeMemoryPath, promptSelect, writeLine }),
453
+ configureStyle: () => configureStyle({ baseDir: claudeDir, filePath: claudeMemoryPath, promptSelect, writeLine }),
454
+ installWorkflows: () => installClaudeWfs({ claudeDir, multiSelect, confirm, writeLine }),
455
+ installMcps: () => installMcps({ multiSelect, confirm, writeLine }),
456
+ configureModel: () => configureClaudeModel({ api, claudeDir, promptSelect, writeLine }),
457
+ importEnvPermissions: () => importEnvPermissions({ claudeDir, confirm, writeLine }),
458
+ testConnection: testConnectionStep,
459
+ askLaunch: async () => {
460
+ if (await confirm("初始化完成。是否立即启动 Claude Code?")) {
461
+ await doLaunchClaude();
462
+ }
463
+ },
464
+ };
465
+ }
466
+
467
+ function buildCodexFullInitDeps({ noticeApproved = false } = {}) {
468
+ const agentsPath = path.join(codexDir, "AGENTS.md");
469
+ return {
470
+ showNotice: () => (noticeApproved ? true : confirmNotice()),
471
+ ensureLogin,
472
+ showBalance: showBalanceAndUsage,
473
+ ensureApiKey: async () => {
474
+ await ensureApiKey();
475
+ writeLine(`✓ 当前 Key:${config.tokenName || "-"}`);
476
+ },
477
+ installTool: () => checkInstallOrUpdateTool("codex", "Codex", "codex"),
478
+ configureLanguage: () => configureLanguage({ baseDir: codexDir, filePath: agentsPath, promptSelect, writeLine }),
479
+ configureStyle: () => configureStyle({ baseDir: codexDir, filePath: agentsPath, promptSelect, writeLine }),
480
+ installAgents: () => installAgents({ confirm, writeLine }),
481
+ installWorkflows: () => installCodexWfs({ codexDir, multiSelect, confirm, writeLine }),
482
+ configureProvider: async () => {
483
+ await ensureApiKey();
484
+ await configureCodex({ config, confirmConflict: confirm, writeLine });
485
+ await markConfigured("codex");
486
+ writeLine("✓ 已写入 Codex claude360 provider/profile(~/.codex/config.toml)");
487
+ },
488
+ checkCompat: checkCodexCompatStep,
489
+ installMcps: () => installCodexMcpServers({ codexDir, multiSelect, confirm, writeLine }),
490
+ testConnection: testConnectionStep,
491
+ askLaunch: async () => {
492
+ if (await confirm("初始化完成。是否立即启动 Codex?")) {
493
+ await doLaunchCodex();
494
+ }
495
+ },
496
+ };
497
+ }
498
+
499
+ async function runClaudeFullInitFlow({ noticeApproved = false } = {}) {
500
+ const result = await runFullInit({
501
+ title: "Claude Code 完整初始化",
502
+ steps: buildClaudeInitSteps(buildClaudeFullInitDeps({ noticeApproved })),
503
+ writeLine,
504
+ });
505
+ return result.completed;
506
+ }
507
+
508
+ async function runCodexFullInitFlow({ noticeApproved = false } = {}) {
509
+ const result = await runFullInit({
510
+ title: "Codex 完整初始化",
511
+ steps: buildCodexInitSteps(buildCodexFullInitDeps({ noticeApproved })),
512
+ writeLine,
513
+ });
514
+ return result.completed;
515
+ }
516
+
517
+ // 一键完整初始化菜单(PRD 第 4 章)
518
+ async function runFullInitMenu() {
519
+ while (true) {
520
+ const action = await promptSelect("一键完整初始化\n\n请选择要配置的工具:", [
521
+ { label: "完整初始化 Claude Code", value: "claude" },
522
+ { label: "完整初始化 Codex", value: "codex" },
523
+ { label: "同时初始化 Claude Code + Codex", value: "both" },
524
+ { label: "返回", value: "back" },
525
+ ]);
526
+ switch (action) {
527
+ case "claude":
528
+ await runClaudeFullInitFlow();
529
+ break;
530
+ case "codex":
531
+ await runCodexFullInitFlow();
532
+ break;
533
+ case "both": {
534
+ // 声明只展示一次,两个流程共用
535
+ if (!(await confirmNotice())) {
536
+ break;
537
+ }
538
+ if (await runClaudeFullInitFlow({ noticeApproved: true })) {
539
+ await runCodexFullInitFlow({ noticeApproved: true });
540
+ }
541
+ break;
542
+ }
543
+ default:
544
+ return;
545
+ }
546
+ }
547
+ }
548
+
209
549
  async function ensureToolInstalled(target, toolName, command) {
210
550
  const check = await commandVersion(execCommand, command, ["--version"]);
211
551
  if (check.ok) {
@@ -440,35 +780,49 @@ export async function runCli({
440
780
  return token;
441
781
  }
442
782
 
783
+ // Claude Code 配置子菜单(PRD 5.1)
443
784
  async function runClaudeCodeMenu() {
444
785
  while (true) {
445
- const action = await promptSelect("Claude Code", [
446
- { label: "安装 Claude Code", value: "install" },
447
- { label: "更新 Claude Code", value: "update" },
448
- { label: "写入 / 修复 Claude360 配置", value: "repair" },
449
- { label: "安装推荐 MCP", value: "mcp" },
450
- { label: "安装推荐工作流 / Skill", value: "skill" },
786
+ const action = await promptSelect("Claude Code 配置", [
787
+ { label: "完整初始化", value: "full" },
788
+ { label: "导入推荐工作流", value: "workflows" },
789
+ { label: "配置 Claude360 API", value: "api" },
790
+ { label: "配置推荐 MCP 服务", value: "mcp" },
791
+ { label: "配置默认模型", value: "model" },
792
+ { label: "配置 Claude 全局记忆", value: "memory" },
793
+ { label: "导入推荐环境变量和权限配置", value: "env" },
794
+ { label: "安装 / 更新 Claude Code", value: "install" },
451
795
  { label: "启动 Claude Code", value: "launch" },
452
796
  { label: "返回", value: "back" },
453
797
  ]);
454
798
  switch (action) {
455
- case "install":
456
- case "update":
457
- await installOrUpdateTools({ targets: ["claude"], confirm });
799
+ case "full":
800
+ await runClaudeFullInitFlow();
458
801
  break;
459
- case "repair":
460
- await ensureApiKey();
461
- await markConfigured("claudeCode");
462
- writeLine("✓ Claude Code 通过启动时环境变量注入 Claude360 配置(不修改 shell profile)。");
463
- writeLine(` ANTHROPIC_BASE_URL=${baseUrl}`);
464
- writeLine(" ANTHROPIC_AUTH_TOKEN=<当前 API Key>");
465
- await showBalanceAndUsage();
802
+ case "workflows":
803
+ if (await confirmNotice()) {
804
+ await installClaudeWfs({ claudeDir, multiSelect, confirm, writeLine });
805
+ }
806
+ break;
807
+ case "api":
808
+ await runClaudeApiConfigMenu();
466
809
  break;
467
810
  case "mcp":
468
- await installMcps({ promptSelect, confirm, writeLine });
811
+ if (await confirmNotice()) {
812
+ await installMcps({ multiSelect, confirm, writeLine });
813
+ }
469
814
  break;
470
- case "skill":
471
- await installSkills({ promptSelect, confirm, writeLine });
815
+ case "model":
816
+ await configureClaudeModel({ api, claudeDir, promptSelect, writeLine });
817
+ break;
818
+ case "memory":
819
+ await runClaudeMemoryMenu();
820
+ break;
821
+ case "env":
822
+ await importEnvPermissions({ claudeDir, confirm, writeLine });
823
+ break;
824
+ case "install":
825
+ await checkInstallOrUpdateTool("claude", "Claude Code", "claude");
472
826
  break;
473
827
  case "launch":
474
828
  if (await doLaunchClaude()) {
@@ -481,26 +835,45 @@ export async function runCli({
481
835
  }
482
836
  }
483
837
 
838
+ // Codex 配置子菜单(PRD 7.1)
484
839
  async function runCodexMenu() {
485
840
  while (true) {
486
- const action = await promptSelect("Codex", [
487
- { label: "安装 Codex", value: "install" },
488
- { label: "更新 Codex", value: "update" },
489
- { label: "写入 / 修复 Claude360 Provider", value: "repair" },
490
- { label: "安装推荐 AGENTS / Skill", value: "agents" },
841
+ const action = await promptSelect("Codex 配置", [
842
+ { label: "完整初始化", value: "full" },
843
+ { label: "导入推荐工作流", value: "workflows" },
844
+ { label: "配置 Claude360 API Provider", value: "provider" },
845
+ { label: "配置推荐 MCP 服务", value: "mcp" },
846
+ { label: "配置默认模型", value: "model" },
847
+ { label: "配置 Codex 全局记忆", value: "memory" },
848
+ { label: "安装 / 更新 Codex", value: "install" },
491
849
  { label: "启动 Codex", value: "launch" },
492
850
  { label: "返回", value: "back" },
493
851
  ]);
494
852
  switch (action) {
495
- case "install":
496
- case "update":
497
- await installOrUpdateTools({ targets: ["codex"], confirm });
853
+ case "full":
854
+ await runCodexFullInitFlow();
498
855
  break;
499
- case "repair":
500
- await doConfigureCodex();
856
+ case "workflows":
857
+ if (await confirmNotice()) {
858
+ await installCodexWfs({ codexDir, multiSelect, confirm, writeLine });
859
+ }
501
860
  break;
502
- case "agents":
503
- await installAgents({ confirm, writeLine });
861
+ case "provider":
862
+ await runCodexProviderMenu();
863
+ break;
864
+ case "mcp":
865
+ if (await confirmNotice()) {
866
+ await installCodexMcpServers({ codexDir, multiSelect, confirm, writeLine });
867
+ }
868
+ break;
869
+ case "model":
870
+ await configureCodexModel({ api, codexDir, promptSelect, writeLine });
871
+ break;
872
+ case "memory":
873
+ await runCodexMemoryMenu();
874
+ break;
875
+ case "install":
876
+ await checkInstallOrUpdateTool("codex", "Codex", "codex");
504
877
  break;
505
878
  case "launch":
506
879
  if (await doLaunchCodex()) {
@@ -513,24 +886,43 @@ export async function runCli({
513
886
  }
514
887
  }
515
888
 
889
+ // 推荐 MCP / Skill 独立菜单(PRD 第 9 章):进入时展示开源参考声明
516
890
  async function runMcpSkillMenu() {
891
+ if (!(await confirmNotice())) {
892
+ return;
893
+ }
517
894
  while (true) {
518
895
  const action = await promptSelect("推荐 MCP / Skill", [
519
- { label: "为 Claude Code 安装推荐 MCP", value: "mcp" },
520
- { label: "为 Claude Code 安装推荐工作流 / Skill", value: "skill" },
521
- { label: "为 Codex 安装推荐 AGENTS / Skill", value: "agents" },
896
+ { label: "为 Claude Code 安装推荐 MCP", value: "claude_mcp" },
897
+ { label: "为 Claude Code 安装推荐工作流 / Skill", value: "claude_skill" },
898
+ { label: "为 Codex 安装推荐 MCP", value: "codex_mcp" },
899
+ { label: "为 Codex 安装推荐 AGENTS / prompts", value: "codex_agents" },
522
900
  { label: "查看已安装增强配置", value: "list" },
523
901
  { label: "返回", value: "back" },
524
902
  ]);
525
903
  switch (action) {
526
- case "mcp":
527
- await installMcps({ promptSelect, confirm, writeLine });
904
+ case "claude_mcp":
905
+ await installMcps({ multiSelect, confirm, writeLine });
528
906
  break;
529
- case "skill":
530
- await installSkills({ promptSelect, confirm, writeLine });
907
+ case "claude_skill": {
908
+ const kind = await promptSelect("请选择要安装的内容:", [
909
+ { label: "推荐工作流(commands)", value: "workflows" },
910
+ { label: "推荐 Skill / 全局记忆规则", value: "skills" },
911
+ { label: "返回", value: "back" },
912
+ ]);
913
+ if (kind === "workflows") {
914
+ await installClaudeWfs({ claudeDir, multiSelect, confirm, writeLine });
915
+ } else if (kind === "skills") {
916
+ await installSkills({ promptSelect, confirm, writeLine });
917
+ }
531
918
  break;
532
- case "agents":
919
+ }
920
+ case "codex_mcp":
921
+ await installCodexMcpServers({ codexDir, multiSelect, confirm, writeLine });
922
+ break;
923
+ case "codex_agents":
533
924
  await installAgents({ confirm, writeLine });
925
+ await installCodexWfs({ codexDir, multiSelect, confirm, writeLine });
534
926
  break;
535
927
  case "list":
536
928
  writeLine(await listEnhancements());
@@ -554,7 +946,7 @@ export async function runCli({
554
946
  switch (action) {
555
947
  case "diagnose": {
556
948
  const report = await runDiagnostics({ config, api, execCommand });
557
- writeLine(formatDiagnosticsSummary(report));
949
+ writeLine(formatDiagnosticsSummary(report, { width: outputWidth() }));
558
950
  break;
559
951
  }
560
952
  case "fix_claude":
@@ -567,7 +959,7 @@ export async function runCli({
567
959
  await doConfigureCodex();
568
960
  break;
569
961
  case "rekey": {
570
- const token = await chooseToken({ api, promptSelect, promptInput });
962
+ const token = await chooseToken({ api, promptSelect, promptInput, writeLine });
571
963
  await saveConfig({
572
964
  apiKey: token.apiKey,
573
965
  selectedTokenId: token.tokenId,
@@ -593,6 +985,10 @@ export async function runCli({
593
985
  // ──────────────────────────────────────────────
594
986
 
595
987
  async function runFirstSetup(targets) {
988
+ // PRD 2.1:一键配置 Claude Code / Codex 属于借鉴 ZCF 的初始化流程,先展示声明
989
+ if (!(await confirmNotice())) {
990
+ return false;
991
+ }
596
992
  if (!(await ensureLogin())) {
597
993
  return false;
598
994
  }
@@ -728,7 +1124,7 @@ export async function runCli({
728
1124
  async function runDailyFlow() {
729
1125
  while (true) {
730
1126
  const status = await loadAccountStatus({ api, config });
731
- writeLine(formatAccountStatus(status));
1127
+ writeLine(formatAccountStatus({ ...status, width: outputWidth() }));
732
1128
  writeLine("");
733
1129
  const action = await promptMenu({ menu: buildDailyMenu(), promptInput, writeLine });
734
1130
  switch (action) {
@@ -761,6 +1157,9 @@ export async function runCli({
761
1157
  return action;
762
1158
  }
763
1159
  break;
1160
+ case "full_init":
1161
+ await runFullInitMenu();
1162
+ break;
764
1163
  case "mcp_skill":
765
1164
  await runMcpSkillMenu();
766
1165
  break;
@@ -811,7 +1210,7 @@ export async function runCli({
811
1210
  return "cc_switch";
812
1211
  case "doctor": {
813
1212
  const report = await runDiagnostics({ config, api, execCommand });
814
- writeLine(formatDiagnosticsSummary(report));
1213
+ writeLine(formatDiagnosticsSummary(report, { width: outputWidth() }));
815
1214
  return "doctor";
816
1215
  }
817
1216
  case "login":