@skrupellose/code-helper 0.1.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/dist/cli.js ADDED
@@ -0,0 +1,651 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { FEATURE_KEYS, FEATURE_LABELS } from "./constants.js";
4
+ import { archiveFeature, listTasks } from "./archive.js";
5
+ import { loadConfig, setFeatureEnabled } from "./config.js";
6
+ import { runChecks } from "./checks.js";
7
+ import { initializeProject } from "./init.js";
8
+ import { normalizeDroppedPath } from "./input-utils.js";
9
+ import { listProjectSkillRegistrations, parseSkillRegistrationTargets, registerProjectSkills, resolveSkillRegistrationTargets, unregisterProjectSkills } from "./skills.js";
10
+ import { canUseInteractiveKeys, promptContinue, promptMultiSelect, promptSelect } from "./terminal-ui.js";
11
+ import { createManualTestDocument, createPlanWorkbench } from "./workflows.js";
12
+ /**
13
+ * CLI 主入口。
14
+ * 支持子命令,也支持无参数时进入交互菜单。
15
+ */
16
+ export async function runCli(argv, projectRoot = process.cwd()) {
17
+ const [command, ...args] = argv;
18
+ try {
19
+ switch (command) {
20
+ case undefined:
21
+ case "menu":
22
+ return runInteractiveMenu(projectRoot);
23
+ case "init":
24
+ return runInit(projectRoot);
25
+ case "check":
26
+ return runCheck(projectRoot);
27
+ case "features":
28
+ return runFeatures(projectRoot, args);
29
+ case "plan":
30
+ return runPlan(projectRoot, args);
31
+ case "manual-test":
32
+ return runManualTest(projectRoot, args);
33
+ case "archive":
34
+ return runArchive(projectRoot, args);
35
+ case "tasks":
36
+ return runTasks(projectRoot, args);
37
+ case "skills":
38
+ return runSkills(projectRoot, args);
39
+ case "help":
40
+ case "--help":
41
+ case "-h":
42
+ printHelp();
43
+ return 0;
44
+ default:
45
+ console.error(`未知命令:${command}`);
46
+ printHelp();
47
+ return 1;
48
+ }
49
+ }
50
+ catch (error) {
51
+ console.error(error instanceof Error ? error.message : String(error));
52
+ return 1;
53
+ }
54
+ }
55
+ /**
56
+ * 无参数时展示交互菜单。
57
+ * 使用 Node 内置 readline,减少首版运行依赖和安装体积。
58
+ */
59
+ async function runInteractiveMenu(projectRoot) {
60
+ const rl = createInterface({ input, output });
61
+ const menuOptions = [
62
+ { value: "1", label: "初始化项目" },
63
+ { value: "2", label: "项目记忆规则优化" },
64
+ { value: "3", label: "项目计划优化" },
65
+ { value: "4", label: "生成人工页面测试文档" },
66
+ { value: "5", label: "功能开关管理" },
67
+ { value: "6", label: "项目规则检查" },
68
+ { value: "7", label: "文档归档" },
69
+ { value: "8", label: "查看任务状态" },
70
+ { value: "9", label: "Skills 管理" },
71
+ { value: "0", label: "退出" }
72
+ ];
73
+ try {
74
+ let shouldExit = false;
75
+ while (!shouldExit) {
76
+ const useKeyMenu = canUseInteractiveKeys(input, output);
77
+ const answer = useKeyMenu
78
+ ? await promptSelect(input, output, "code-helper 操作菜单", menuOptions)
79
+ : await askTextMenu(rl);
80
+ switch (answer.trim()) {
81
+ case "1":
82
+ await runMenuAction("初始化项目", () => runInit(projectRoot));
83
+ await pauseAfterMenuAction(useKeyMenu);
84
+ break;
85
+ case "2":
86
+ await runMenuAction("项目记忆规则优化", async () => {
87
+ await runInit(projectRoot);
88
+ console.log("已刷新项目记忆规则模板。请根据当前变更定向修改 code-helper-docs/user-rules/ 中的专题规则。");
89
+ return 0;
90
+ });
91
+ await pauseAfterMenuAction(useKeyMenu);
92
+ break;
93
+ case "3": {
94
+ printInputHint("项目计划优化需要需求文档路径,支持直接把文件拖到终端。输入 0 或直接回车返回。");
95
+ const requirementPath = await askRequiredMenuInput(rl, "请输入或拖拽需求文档路径:");
96
+ if (requirementPath === undefined) {
97
+ console.log("已取消项目计划优化,返回主菜单。");
98
+ break;
99
+ }
100
+ const featureName = await askOptionalMenuInput(rl, "请输入中文功能名称(可留空,默认取需求标题或中文文件名;输入 0 返回):");
101
+ if (featureName === undefined) {
102
+ console.log("已取消项目计划优化,返回主菜单。");
103
+ break;
104
+ }
105
+ await runMenuAction("项目计划优化", () => runPlan(projectRoot, [normalizeDroppedPath(requirementPath, projectRoot), featureName].filter(Boolean)));
106
+ await pauseAfterMenuAction(useKeyMenu);
107
+ break;
108
+ }
109
+ case "4": {
110
+ printInputHint("生成人工页面测试文档需要功能名称。输入 0 或直接回车返回。");
111
+ const featureName = await askRequiredMenuInput(rl, "请输入功能名称:");
112
+ if (featureName === undefined) {
113
+ console.log("已取消生成人工页面测试文档,返回主菜单。");
114
+ break;
115
+ }
116
+ const title = await askOptionalMenuInput(rl, "请输入测试文档标题(可留空;输入 0 返回):");
117
+ if (title === undefined) {
118
+ console.log("已取消生成人工页面测试文档,返回主菜单。");
119
+ break;
120
+ }
121
+ await runMenuAction("生成人工页面测试文档", () => runManualTest(projectRoot, [featureName, title].filter(Boolean)));
122
+ await pauseAfterMenuAction(useKeyMenu);
123
+ break;
124
+ }
125
+ case "5":
126
+ if (await runFeatureMenu(projectRoot, rl)) {
127
+ await pauseAfterMenuAction(useKeyMenu);
128
+ }
129
+ break;
130
+ case "6":
131
+ await runMenuAction("项目规则检查", () => runCheck(projectRoot));
132
+ await pauseAfterMenuAction(useKeyMenu);
133
+ break;
134
+ case "7": {
135
+ printInputHint("文档归档需要中文功能名称,例如 订单管理升级。输入 0 或直接回车返回。");
136
+ const featureName = await askRequiredMenuInput(rl, "请输入要归档的功能名称:");
137
+ if (featureName === undefined) {
138
+ console.log("已取消文档归档,返回主菜单。");
139
+ break;
140
+ }
141
+ await runMenuAction("文档归档", () => runArchive(projectRoot, [featureName]));
142
+ await pauseAfterMenuAction(useKeyMenu);
143
+ break;
144
+ }
145
+ case "8":
146
+ await runMenuAction("查看任务状态", () => runTasks(projectRoot, []));
147
+ await pauseAfterMenuAction(useKeyMenu);
148
+ break;
149
+ case "9":
150
+ if (await runSkillMenu(projectRoot, rl)) {
151
+ await pauseAfterMenuAction(useKeyMenu);
152
+ }
153
+ break;
154
+ case "0":
155
+ console.log("已退出 code-helper。");
156
+ shouldExit = true;
157
+ break;
158
+ default:
159
+ console.log("无效选择,请重新输入。");
160
+ }
161
+ }
162
+ return 0;
163
+ }
164
+ finally {
165
+ rl.close();
166
+ }
167
+ }
168
+ /**
169
+ * 交互式 Skills 管理菜单。
170
+ * 这里只管理 code-helper 自己的项目级 skill,不触碰用户自定义 skills。
171
+ */
172
+ async function runSkillMenu(projectRoot, rl) {
173
+ const useKeyMenu = canUseInteractiveKeys(input, output);
174
+ const options = [
175
+ { value: "1", label: "查看注册状态" },
176
+ { value: "2", label: "按当前项目注册 Skills" },
177
+ { value: "3", label: "按当前项目取消注册 Skills" },
178
+ { value: "4", label: "仅注册 Codex" },
179
+ { value: "5", label: "仅注册 Claude Code" },
180
+ { value: "6", label: "注册全部" },
181
+ { value: "7", label: "取消注册全部" },
182
+ { value: "0", label: "返回" }
183
+ ];
184
+ const answer = useKeyMenu
185
+ ? await promptSelect(input, output, "Skills 管理", options)
186
+ : await askTextSkillMenu(rl);
187
+ switch (answer.trim()) {
188
+ case "1":
189
+ await runMenuAction("查看 Skills 注册状态", () => runSkills(projectRoot, ["list"]));
190
+ return true;
191
+ case "2":
192
+ await runMenuAction("按当前项目注册 Skills", () => runSkills(projectRoot, ["register"]));
193
+ return true;
194
+ case "3":
195
+ await runMenuAction("按当前项目取消注册 Skills", () => runSkills(projectRoot, ["unregister"]));
196
+ return true;
197
+ case "4":
198
+ await runMenuAction("注册 Codex 项目级 skills", () => runSkills(projectRoot, ["register", "codex"]));
199
+ return true;
200
+ case "5":
201
+ await runMenuAction("注册 Claude Code 项目级 skills", () => runSkills(projectRoot, ["register", "claudecode"]));
202
+ return true;
203
+ case "6":
204
+ await runMenuAction("注册全部项目级 skills", () => runSkills(projectRoot, ["register", "all"]));
205
+ return true;
206
+ case "7":
207
+ await runMenuAction("取消注册全部项目级 skills", () => runSkills(projectRoot, ["unregister", "all"]));
208
+ return true;
209
+ case "0":
210
+ console.log("已返回主菜单。");
211
+ return false;
212
+ default:
213
+ console.log("无效选择,返回主菜单。");
214
+ return false;
215
+ }
216
+ }
217
+ /**
218
+ * TTY 菜单动作结束后暂停,避免下一轮菜单清屏导致结果一闪而过。
219
+ * 非 TTY 兜底模式不暂停,保证管道和脚本执行不会被阻塞。
220
+ */
221
+ async function pauseAfterMenuAction(enabled) {
222
+ if (enabled && canUseInteractiveKeys(input, output)) {
223
+ await promptContinue(input, output);
224
+ }
225
+ }
226
+ /**
227
+ * 包装菜单动作的执行回显。
228
+ * 用户按回车确认后,会立即看到动作开始和完成状态,避免误以为没有响应。
229
+ */
230
+ async function runMenuAction(label, action) {
231
+ console.log(`\n▶ 开始:${label}`);
232
+ try {
233
+ const exitCode = await action();
234
+ if (exitCode === 0) {
235
+ console.log(`✓ 完成:${label}`);
236
+ }
237
+ else {
238
+ console.log(`✗ 失败:${label}(退出码 ${exitCode})`);
239
+ }
240
+ }
241
+ catch (error) {
242
+ console.log(`✗ 失败:${label}`);
243
+ throw error;
244
+ }
245
+ }
246
+ /**
247
+ * 打印需要用户输入的动作提示。
248
+ * 这让用户能区分“正在等待输入”和“程序没有响应”。
249
+ */
250
+ function printInputHint(message) {
251
+ console.log(`\n请输入信息:${message}`);
252
+ }
253
+ /**
254
+ * 初始化命令实现。
255
+ * 输出所有操作结果,便于用户看清哪些文件被创建、更新或跳过。
256
+ */
257
+ async function runInit(projectRoot) {
258
+ const result = await initializeProject({ projectRoot });
259
+ printOperations(result.operations);
260
+ return 0;
261
+ }
262
+ /**
263
+ * 检查命令实现。
264
+ * 存在 error 时返回 1,方便 CI 或 hook 使用。
265
+ */
266
+ async function runCheck(projectRoot) {
267
+ const issues = await runChecks(projectRoot);
268
+ if (issues.length === 0) {
269
+ console.log("code-helper check 通过:未发现协作文档结构问题。");
270
+ return 0;
271
+ }
272
+ for (const issue of issues) {
273
+ console.log(`[${issue.level}] ${issue.code}: ${issue.message}`);
274
+ if (issue.path) {
275
+ console.log(` 路径:${issue.path}`);
276
+ }
277
+ console.log(` 建议:${issue.suggestion}`);
278
+ }
279
+ return issues.some((issue) => issue.level === "error") ? 1 : 0;
280
+ }
281
+ /**
282
+ * 非交互功能开关命令。
283
+ * 支持:features list、features enable <key>、features disable <key>。
284
+ */
285
+ async function runFeatures(projectRoot, args) {
286
+ const [action, feature] = args;
287
+ if (action === undefined || action === "list") {
288
+ printFeatureList(await loadConfig(projectRoot));
289
+ return 0;
290
+ }
291
+ if (!isFeatureKey(feature)) {
292
+ console.error(`无效功能 key:${feature ?? ""}`);
293
+ printFeatureHelp();
294
+ return 1;
295
+ }
296
+ if (action === "enable" || action === "disable") {
297
+ const config = await setFeatureEnabled(projectRoot, feature, action === "enable");
298
+ printFeatureList(config);
299
+ return 0;
300
+ }
301
+ printFeatureHelp();
302
+ return 1;
303
+ }
304
+ /**
305
+ * 交互式功能开关菜单。
306
+ * 修改后只保存配置,不自动重写模板;用户可再执行初始化刷新模板。
307
+ */
308
+ async function runFeatureMenu(projectRoot, rl) {
309
+ const config = await loadConfig(projectRoot);
310
+ if (canUseInteractiveKeys(input, output)) {
311
+ const selectedFeatures = await promptMultiSelect(input, output, "功能开关管理", FEATURE_KEYS.map((feature) => ({
312
+ value: feature,
313
+ label: `${feature} - ${FEATURE_LABELS[feature]}`,
314
+ checked: config.features[feature].enabled
315
+ })));
316
+ if (selectedFeatures.cancelled) {
317
+ console.log("已取消功能开关修改,返回主菜单。");
318
+ return false;
319
+ }
320
+ console.log(`\n▶ 开始:功能开关管理`);
321
+ let changedCount = 0;
322
+ for (const feature of selectedFeatures.options) {
323
+ if (config.features[feature.value].enabled !== feature.checked) {
324
+ await setFeatureEnabled(projectRoot, feature.value, feature.checked);
325
+ changedCount += 1;
326
+ }
327
+ }
328
+ console.log(`已保存功能开关,变更 ${changedCount} 项。`);
329
+ printFeatureList(await loadConfig(projectRoot));
330
+ console.log(`✓ 完成:功能开关管理`);
331
+ return true;
332
+ }
333
+ await runTextFeatureMenu(projectRoot, rl);
334
+ return false;
335
+ }
336
+ /**
337
+ * 读取必填菜单输入。
338
+ * 空回车或输入 0 都表示返回上一级,避免用户误入流程后无法退出。
339
+ */
340
+ async function askRequiredMenuInput(rl, question) {
341
+ const answer = (await askQuestionOrDefault(rl, question, "0")).trim();
342
+ if (answer === "" || answer === "0") {
343
+ return undefined;
344
+ }
345
+ return answer;
346
+ }
347
+ /**
348
+ * 读取可选菜单输入。
349
+ * 空回车表示接受默认值,输入 0 表示返回上一级。
350
+ */
351
+ async function askOptionalMenuInput(rl, question) {
352
+ const answer = (await askQuestionOrDefault(rl, question, "")).trim();
353
+ if (answer === "0") {
354
+ return undefined;
355
+ }
356
+ return answer;
357
+ }
358
+ /**
359
+ * 非 TTY 环境下的数字功能开关菜单。
360
+ * 输入 1..N 切换对应功能,输入 0 返回上一级。
361
+ */
362
+ async function runTextFeatureMenu(projectRoot, rl) {
363
+ let shouldReturn = false;
364
+ while (!shouldReturn) {
365
+ const config = await loadConfig(projectRoot);
366
+ console.log("\n功能开关管理");
367
+ FEATURE_KEYS.forEach((feature, index) => {
368
+ const status = config.features[feature].enabled ? "启用" : "关闭";
369
+ console.log(`${index + 1}. ${FEATURE_LABELS[feature]}(${feature}):${status}`);
370
+ });
371
+ console.log("0. 返回");
372
+ const answer = await askQuestionOrDefault(rl, "请输入数字切换功能,或输入 0 返回:", "0");
373
+ const selectedIndex = Number.parseInt(answer.trim(), 10);
374
+ if (selectedIndex === 0) {
375
+ shouldReturn = true;
376
+ continue;
377
+ }
378
+ if (!Number.isInteger(selectedIndex) || selectedIndex < 1 || selectedIndex > FEATURE_KEYS.length) {
379
+ console.log("无效选择,请输入列表中的数字。");
380
+ continue;
381
+ }
382
+ const selectedFeature = FEATURE_KEYS[selectedIndex - 1];
383
+ const current = config.features[selectedFeature].enabled;
384
+ await setFeatureEnabled(projectRoot, selectedFeature, !current);
385
+ console.log(`已${current ? "关闭" : "启用"}:${FEATURE_LABELS[selectedFeature]}`);
386
+ }
387
+ }
388
+ /**
389
+ * 非 TTY 环境下的文本菜单兜底。
390
+ * 当终端不支持 raw mode 时,仍允许用户输入数字选择。
391
+ */
392
+ async function askTextMenu(rl) {
393
+ console.log("\ncode-helper 操作菜单");
394
+ console.log("1. 初始化项目");
395
+ console.log("2. 项目记忆规则优化");
396
+ console.log("3. 项目计划优化");
397
+ console.log("4. 生成人工页面测试文档");
398
+ console.log("5. 功能开关管理");
399
+ console.log("6. 项目规则检查");
400
+ console.log("7. 文档归档");
401
+ console.log("8. 查看任务状态");
402
+ console.log("9. Skills 管理");
403
+ console.log("0. 退出");
404
+ return askQuestionOrDefault(rl, "请选择操作:", "0");
405
+ }
406
+ /**
407
+ * 非 TTY 环境下的 Skills 管理菜单。
408
+ * 输入 0 立即返回,避免用户误入子菜单后无法退出。
409
+ */
410
+ async function askTextSkillMenu(rl) {
411
+ console.log("\nSkills 管理");
412
+ console.log("1. 查看注册状态");
413
+ console.log("2. 按当前项目注册 Skills");
414
+ console.log("3. 按当前项目取消注册 Skills");
415
+ console.log("4. 仅注册 Codex");
416
+ console.log("5. 仅注册 Claude Code");
417
+ console.log("6. 注册全部");
418
+ console.log("7. 取消注册全部");
419
+ console.log("0. 返回");
420
+ return askQuestionOrDefault(rl, "请选择操作:", "0");
421
+ }
422
+ /**
423
+ * 安全读取用户输入。
424
+ * 当 stdin 已关闭或管道输入提前结束时,返回默认值,避免兜底交互崩溃。
425
+ */
426
+ async function askQuestionOrDefault(rl, question, defaultAnswer) {
427
+ return new Promise((resolve, reject) => {
428
+ let settled = false;
429
+ /**
430
+ * stdin 提前结束时,readline 在部分 Node 版本不会让 question promise settle。
431
+ * 这里主动监听 close,并用默认值返回,避免交互流程悬挂。
432
+ */
433
+ const onClose = () => {
434
+ if (!settled) {
435
+ settled = true;
436
+ resolve(defaultAnswer);
437
+ }
438
+ };
439
+ rl.once("close", onClose);
440
+ rl.question(question)
441
+ .then((answer) => {
442
+ if (!settled) {
443
+ settled = true;
444
+ rl.off("close", onClose);
445
+ resolve(answer);
446
+ }
447
+ })
448
+ .catch((error) => {
449
+ if (settled) {
450
+ return;
451
+ }
452
+ settled = true;
453
+ rl.off("close", onClose);
454
+ if (error instanceof Error && "code" in error && error.code === "ERR_USE_AFTER_CLOSE") {
455
+ resolve(defaultAnswer);
456
+ return;
457
+ }
458
+ reject(error);
459
+ });
460
+ });
461
+ }
462
+ /**
463
+ * 计划文档命令。
464
+ * 参数:plan <需求文档相对路径> [功能名称]。
465
+ */
466
+ async function runPlan(projectRoot, args) {
467
+ const [requirementPath, featureName] = args;
468
+ if (!requirementPath) {
469
+ console.error("缺少需求文档路径。用法:code-helper plan <需求文档相对路径> [功能名称]");
470
+ return 1;
471
+ }
472
+ const normalizedRequirementPath = normalizeDroppedPath(requirementPath, projectRoot);
473
+ const operations = await createPlanWorkbench({ projectRoot, requirementPath: normalizedRequirementPath, featureName });
474
+ printOperations(operations);
475
+ return 0;
476
+ }
477
+ /**
478
+ * 手工测试文档命令。
479
+ * 参数:manual-test <功能名称> [标题]。
480
+ */
481
+ async function runManualTest(projectRoot, args) {
482
+ const [featureName, title] = args;
483
+ if (!featureName) {
484
+ console.error("缺少功能名称。用法:code-helper manual-test <中文功能名> [标题]");
485
+ return 1;
486
+ }
487
+ printOperations([await createManualTestDocument({ projectRoot, featureName, title })]);
488
+ return 0;
489
+ }
490
+ /**
491
+ * 文档归档命令。
492
+ * 参数:archive <功能名称>。
493
+ */
494
+ async function runArchive(projectRoot, args) {
495
+ const [featureName] = args;
496
+ if (!featureName) {
497
+ console.error("缺少功能名称。用法:code-helper archive <中文功能名>");
498
+ return 1;
499
+ }
500
+ printOperations(await archiveFeature(projectRoot, featureName));
501
+ await runTasks(projectRoot, []);
502
+ return 0;
503
+ }
504
+ /**
505
+ * 任务状态列表命令。
506
+ * 参数:tasks [--json]。
507
+ */
508
+ async function runTasks(projectRoot, args) {
509
+ const tasks = await listTasks(projectRoot);
510
+ if (args.includes("--json")) {
511
+ console.log(JSON.stringify(tasks, null, 2));
512
+ return 0;
513
+ }
514
+ if (tasks.length === 0) {
515
+ console.log("当前没有发现 plan/result/status 任务文档。");
516
+ return 0;
517
+ }
518
+ for (const task of tasks) {
519
+ console.log(`${task.featureName}: ${task.status}`);
520
+ if (task.activeArtifacts.length > 0) {
521
+ console.log(` active: ${task.activeArtifacts.join(", ")}`);
522
+ }
523
+ if (task.archivedArtifacts.length > 0) {
524
+ console.log(` archived: ${task.archivedArtifacts.join(", ")}`);
525
+ }
526
+ }
527
+ return 0;
528
+ }
529
+ /**
530
+ * 项目级 skills 注册命令。
531
+ * 支持:skills list、skills register [all|codex|claudecode]、skills unregister [all|codex|claudecode]。
532
+ * register/unregister 不带 target 时按当前项目入口文件推断目标,只有显式 all 才处理两套 agent。
533
+ */
534
+ async function runSkills(projectRoot, args) {
535
+ const [action = "list", rawTarget] = args;
536
+ const targets = await resolveTargetsForSkillAction(projectRoot, action, rawTarget);
537
+ if (action === "list") {
538
+ const statuses = (await Promise.all(targets.map((target) => listProjectSkillRegistrations(projectRoot, target)))).flat();
539
+ printSkillRegistrationStatus(statuses);
540
+ return 0;
541
+ }
542
+ if (action === "register") {
543
+ const operations = (await Promise.all(targets.map((target) => registerProjectSkills(projectRoot, target)))).flat();
544
+ const statuses = (await Promise.all(targets.map((target) => listProjectSkillRegistrations(projectRoot, target)))).flat();
545
+ printOperations(operations);
546
+ printSkillRegistrationStatus(statuses);
547
+ return 0;
548
+ }
549
+ if (action === "unregister") {
550
+ const operations = (await Promise.all(targets.map((target) => unregisterProjectSkills(projectRoot, target)))).flat();
551
+ const statuses = (await Promise.all(targets.map((target) => listProjectSkillRegistrations(projectRoot, target)))).flat();
552
+ printOperations(operations);
553
+ printSkillRegistrationStatus(statuses);
554
+ return 0;
555
+ }
556
+ printSkillsHelp();
557
+ return 1;
558
+ }
559
+ /**
560
+ * 打印操作结果。
561
+ * 路径可能是绝对路径,保留原样方便用户定位。
562
+ */
563
+ function printOperations(operations) {
564
+ for (const operation of operations) {
565
+ console.log(`[${operation.action}] ${operation.path} - ${operation.message}`);
566
+ }
567
+ }
568
+ /**
569
+ * 打印功能开关列表。
570
+ * key 直接展示给用户,便于配合非交互命令使用。
571
+ */
572
+ function printFeatureList(config) {
573
+ for (const feature of FEATURE_KEYS) {
574
+ const status = config.features[feature].enabled ? "启用" : "关闭";
575
+ console.log(`${feature}: ${status} - ${FEATURE_LABELS[feature]}`);
576
+ }
577
+ }
578
+ /**
579
+ * 打印项目级 skills 注册状态。
580
+ * 路径使用绝对路径,方便用户排查对应 agent 是否能扫描到文件。
581
+ */
582
+ function printSkillRegistrationStatus(statuses) {
583
+ for (const status of statuses) {
584
+ console.log(`${status.target}/${status.name}: ${status.registered ? "已注册" : "未注册"}`);
585
+ console.log(` path: ${status.path}`);
586
+ }
587
+ }
588
+ /**
589
+ * 判断字符串是否是合法 FeatureKey。
590
+ * 运行时 CLI 参数需要显式校验,不能只依赖 TypeScript 类型。
591
+ */
592
+ function isFeatureKey(value) {
593
+ return FEATURE_KEYS.includes(value);
594
+ }
595
+ /**
596
+ * 打印功能开关帮助。
597
+ * 保持简短,避免交互和自动化场景输出过重。
598
+ */
599
+ function printFeatureHelp() {
600
+ console.log("用法:");
601
+ console.log(" code-helper features list");
602
+ console.log(" code-helper features enable <featureKey>");
603
+ console.log(" code-helper features disable <featureKey>");
604
+ }
605
+ /**
606
+ * 打印项目级 skills 命令帮助。
607
+ */
608
+ function printSkillsHelp() {
609
+ console.log("用法:");
610
+ console.log(" code-helper skills list");
611
+ console.log(" code-helper skills register [all|codex|claudecode]");
612
+ console.log(" code-helper skills unregister [all|codex|claudecode]");
613
+ console.log("说明:register/unregister 不带 target 时按当前项目已有 AGENTS.md / CLAUDE.md 自动选择目标。");
614
+ }
615
+ /**
616
+ * 打印 CLI 帮助。
617
+ * 所有子命令都提供非交互入口,便于测试和集成到脚本。
618
+ */
619
+ function printHelp() {
620
+ console.log(`code-helper
621
+
622
+ 用法:
623
+ code-helper 打开交互菜单
624
+ code-helper init 初始化项目规则和工作区
625
+ code-helper check 检查协作文档结构
626
+ code-helper features list 查看功能开关
627
+ code-helper features enable <key> 启用功能
628
+ code-helper features disable <key> 关闭功能
629
+ code-helper plan <需求文档> [中文功能名] 生成项目计划文档
630
+ code-helper manual-test <中文功能名> [标题] 生成页面手工测试文档
631
+ code-helper archive <中文功能名> 将功能文档移动到 archive 并识别为已结束
632
+ code-helper tasks [--json] 查看 active / archived / mixed 任务
633
+ code-helper skills list 查看项目级 skills 注册状态
634
+ code-helper skills register [target] 按项目入口或指定 target 注册项目级 skills
635
+ code-helper skills unregister [target] 按项目入口或指定 target 取消注册项目级 skills
636
+ `);
637
+ }
638
+ /**
639
+ * 解析 skills 子命令的目标范围。
640
+ * list 默认展示全部状态;register 和 unregister 默认按当前项目实际入口文件处理。
641
+ */
642
+ async function resolveTargetsForSkillAction(projectRoot, action, rawTarget) {
643
+ if (rawTarget !== undefined) {
644
+ return parseSkillRegistrationTargets(rawTarget);
645
+ }
646
+ if (action === "register" || action === "unregister") {
647
+ return resolveSkillRegistrationTargets(projectRoot);
648
+ }
649
+ return parseSkillRegistrationTargets("all");
650
+ }
651
+ //# sourceMappingURL=cli.js.map