hamster-wheel-cli 0.2.0 → 0.3.1

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 CHANGED
@@ -146,6 +146,15 @@ async function runCommand(command, args, options = {}) {
146
146
  function isoNow() {
147
147
  return (/* @__PURE__ */ new Date()).toISOString();
148
148
  }
149
+ function localTimestamp(date = /* @__PURE__ */ new Date()) {
150
+ const year = date.getFullYear();
151
+ const month = pad2(date.getMonth() + 1);
152
+ const day = pad2(date.getDate());
153
+ const hours = pad2(date.getHours());
154
+ const minutes = pad2(date.getMinutes());
155
+ const seconds = pad2(date.getSeconds());
156
+ return `${year}${month}${day}-${hours}${minutes}${seconds}`;
157
+ }
149
158
  function resolvePath(cwd, target) {
150
159
  return import_node_path.default.isAbsolute(target) ? target : import_node_path.default.join(cwd, target);
151
160
  }
@@ -389,10 +398,37 @@ function normalizeShortcutName(name) {
389
398
  function normalizeAliasName(name) {
390
399
  return normalizeShortcutName(name);
391
400
  }
401
+ function normalizeAgentName(name) {
402
+ return normalizeShortcutName(name);
403
+ }
392
404
  function formatTomlString(value) {
393
405
  const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
394
406
  return `"${escaped}"`;
395
407
  }
408
+ function collectSectionRanges(lines) {
409
+ const ranges = [];
410
+ let current = null;
411
+ for (let i = 0; i < lines.length; i += 1) {
412
+ const match = /^\s*\[(.+?)\]\s*$/.exec(lines[i]);
413
+ if (!match) continue;
414
+ if (current) {
415
+ current.end = i;
416
+ ranges.push(current);
417
+ }
418
+ current = {
419
+ name: match[1].trim(),
420
+ start: i,
421
+ end: lines.length
422
+ };
423
+ }
424
+ if (current) {
425
+ ranges.push(current);
426
+ }
427
+ return ranges;
428
+ }
429
+ function isAgentSection(name) {
430
+ return name === "agent" || name === "agents";
431
+ }
396
432
  function updateAliasContent(content, name, command) {
397
433
  const lines = content.split(/\r?\n/);
398
434
  const entryLine = `${name} = ${formatTomlString(command)}`;
@@ -436,6 +472,33 @@ ${entryLine}
436
472
  return output.endsWith("\n") ? output : `${output}
437
473
  `;
438
474
  }
475
+ function removeAliasContent(content, name) {
476
+ const lines = content.split(/\r?\n/);
477
+ let currentSection = null;
478
+ const removeIndices = [];
479
+ for (let i = 0; i < lines.length; i += 1) {
480
+ const sectionMatch = /^\s*\[(.+?)\]\s*$/.exec(lines[i]);
481
+ if (sectionMatch) {
482
+ currentSection = sectionMatch[1].trim();
483
+ continue;
484
+ }
485
+ if (currentSection !== "alias") continue;
486
+ const parsed = parseTomlKeyValue(stripTomlComment(lines[i]).trim());
487
+ if (!parsed) continue;
488
+ if (parsed.key === name) {
489
+ removeIndices.push(i);
490
+ }
491
+ }
492
+ if (removeIndices.length === 0) {
493
+ return { removed: false, nextContent: ensureTrailingNewline(content) };
494
+ }
495
+ removeIndices.sort((a, b) => b - a).forEach((index) => {
496
+ lines.splice(index, 1);
497
+ });
498
+ const output = lines.join("\n");
499
+ return { removed: true, nextContent: output.endsWith("\n") ? output : `${output}
500
+ ` };
501
+ }
439
502
  async function upsertAliasEntry(name, command, filePath = getGlobalConfigPath()) {
440
503
  const exists = await import_fs_extra2.default.pathExists(filePath);
441
504
  const content = exists ? await import_fs_extra2.default.readFile(filePath, "utf8") : "";
@@ -443,6 +506,104 @@ async function upsertAliasEntry(name, command, filePath = getGlobalConfigPath())
443
506
  await import_fs_extra2.default.mkdirp(import_node_path3.default.dirname(filePath));
444
507
  await import_fs_extra2.default.writeFile(filePath, nextContent, "utf8");
445
508
  }
509
+ async function removeAliasEntry(name, filePath = getGlobalConfigPath()) {
510
+ const exists = await import_fs_extra2.default.pathExists(filePath);
511
+ if (!exists) return false;
512
+ const content = await import_fs_extra2.default.readFile(filePath, "utf8");
513
+ const { removed, nextContent } = removeAliasContent(content, name);
514
+ if (!removed) return false;
515
+ await import_fs_extra2.default.writeFile(filePath, nextContent, "utf8");
516
+ return true;
517
+ }
518
+ function ensureTrailingNewline(content) {
519
+ if (!content) return "";
520
+ return content.endsWith("\n") ? content : `${content}
521
+ `;
522
+ }
523
+ function findAgentRangeWithEntry(lines, ranges, name) {
524
+ for (const range of ranges) {
525
+ for (let i = range.start + 1; i < range.end; i += 1) {
526
+ const parsed = parseTomlKeyValue(stripTomlComment(lines[i]).trim());
527
+ if (!parsed) continue;
528
+ if (parsed.key === name) return range;
529
+ }
530
+ }
531
+ return null;
532
+ }
533
+ function updateAgentContent(content, name, command) {
534
+ const lines = content.split(/\r?\n/);
535
+ const entryLine = `${name} = ${formatTomlString(command)}`;
536
+ const ranges = collectSectionRanges(lines);
537
+ const agentRanges = ranges.filter((range) => isAgentSection(range.name));
538
+ let targetRange = findAgentRangeWithEntry(lines, agentRanges, name);
539
+ if (!targetRange) {
540
+ targetRange = agentRanges.find((range) => range.name === "agent") ?? agentRanges.find((range) => range.name === "agents") ?? null;
541
+ }
542
+ if (!targetRange) {
543
+ const trimmed = content.trimEnd();
544
+ const prefix = trimmed.length > 0 ? `${trimmed}
545
+
546
+ ` : "";
547
+ return `${prefix}[agent]
548
+ ${entryLine}
549
+ `;
550
+ }
551
+ let replaced = false;
552
+ for (let i = targetRange.start + 1; i < targetRange.end; i += 1) {
553
+ const parsed = parseTomlKeyValue(stripTomlComment(lines[i]).trim());
554
+ if (!parsed) continue;
555
+ if (parsed.key === name) {
556
+ lines[i] = entryLine;
557
+ replaced = true;
558
+ break;
559
+ }
560
+ }
561
+ if (!replaced) {
562
+ lines.splice(targetRange.end, 0, entryLine);
563
+ }
564
+ const output = lines.join("\n");
565
+ return output.endsWith("\n") ? output : `${output}
566
+ `;
567
+ }
568
+ function removeAgentContent(content, name) {
569
+ const lines = content.split(/\r?\n/);
570
+ const ranges = collectSectionRanges(lines).filter((range) => isAgentSection(range.name));
571
+ const removeIndices = [];
572
+ for (const range of ranges) {
573
+ for (let i = range.start + 1; i < range.end; i += 1) {
574
+ const parsed = parseTomlKeyValue(stripTomlComment(lines[i]).trim());
575
+ if (!parsed) continue;
576
+ if (parsed.key === name) {
577
+ removeIndices.push(i);
578
+ }
579
+ }
580
+ }
581
+ if (removeIndices.length === 0) {
582
+ return { removed: false, nextContent: ensureTrailingNewline(content) };
583
+ }
584
+ removeIndices.sort((a, b) => b - a).forEach((index) => {
585
+ lines.splice(index, 1);
586
+ });
587
+ const output = lines.join("\n");
588
+ return { removed: true, nextContent: output.endsWith("\n") ? output : `${output}
589
+ ` };
590
+ }
591
+ async function upsertAgentEntry(name, command, filePath = getGlobalConfigPath()) {
592
+ const exists = await import_fs_extra2.default.pathExists(filePath);
593
+ const content = exists ? await import_fs_extra2.default.readFile(filePath, "utf8") : "";
594
+ const nextContent = updateAgentContent(content, name, command);
595
+ await import_fs_extra2.default.mkdirp(import_node_path3.default.dirname(filePath));
596
+ await import_fs_extra2.default.writeFile(filePath, nextContent, "utf8");
597
+ }
598
+ async function removeAgentEntry(name, filePath = getGlobalConfigPath()) {
599
+ const exists = await import_fs_extra2.default.pathExists(filePath);
600
+ if (!exists) return false;
601
+ const content = await import_fs_extra2.default.readFile(filePath, "utf8");
602
+ const { removed, nextContent } = removeAgentContent(content, name);
603
+ if (!removed) return false;
604
+ await import_fs_extra2.default.writeFile(filePath, nextContent, "utf8");
605
+ return true;
606
+ }
446
607
  function parseGlobalConfig(content) {
447
608
  const lines = content.split(/\r?\n/);
448
609
  let currentSection = null;
@@ -509,6 +670,34 @@ function parseAliasEntries(content) {
509
670
  }
510
671
  return entries;
511
672
  }
673
+ function parseAgentEntries(content) {
674
+ const lines = content.split(/\r?\n/);
675
+ let currentSection = null;
676
+ const entries = [];
677
+ const names = /* @__PURE__ */ new Set();
678
+ for (const rawLine of lines) {
679
+ const line = stripTomlComment(rawLine).trim();
680
+ if (!line) continue;
681
+ const sectionMatch = /^\[(.+)\]$/.exec(line);
682
+ if (sectionMatch) {
683
+ currentSection = sectionMatch[1].trim();
684
+ continue;
685
+ }
686
+ if (!isAgentSection(currentSection)) continue;
687
+ const parsed = parseTomlKeyValue(line);
688
+ if (!parsed) continue;
689
+ const name = normalizeShortcutName(parsed.key);
690
+ const command = parsed.value.trim();
691
+ if (!name || !command) continue;
692
+ if (names.has(name)) continue;
693
+ names.add(name);
694
+ entries.push({
695
+ name,
696
+ command
697
+ });
698
+ }
699
+ return entries;
700
+ }
512
701
  async function loadGlobalConfig(logger) {
513
702
  const filePath = getGlobalConfigPath();
514
703
  const exists = await import_fs_extra2.default.pathExists(filePath);
@@ -1212,7 +1401,7 @@ async function readLogLines(logFile) {
1212
1401
  }
1213
1402
  function buildListHeader(state, columns) {
1214
1403
  const total = state.logs.length;
1215
- const title = `\u65E5\u5FD7\u5217\u8868\uFF08${total} \u6761\uFF09\uFF5C\u2191/\u2193 \u9009\u62E9 Enter \u67E5\u770B q \u9000\u51FA`;
1404
+ const title = `\u65E5\u5FD7\u5217\u8868\uFF08${total} \u6761\uFF09\uFF5C\u2191/\u2193 \u9009\u62E9 PageUp/PageDown \u7FFB\u9875 Enter \u67E5\u770B q \u9000\u51FA`;
1216
1405
  return truncateLine2(title, columns);
1217
1406
  }
1218
1407
  function buildListStatus(state, columns) {
@@ -1427,6 +1616,18 @@ async function runLogsViewer() {
1427
1616
  render2(state);
1428
1617
  return;
1429
1618
  }
1619
+ if (isPageUp(input)) {
1620
+ const pageSize = getPageSize2(getTerminalSize2().rows);
1621
+ state.selectedIndex = clampIndex2(state.selectedIndex - pageSize, state.logs.length);
1622
+ render2(state);
1623
+ return;
1624
+ }
1625
+ if (isPageDown(input)) {
1626
+ const pageSize = getPageSize2(getTerminalSize2().rows);
1627
+ state.selectedIndex = clampIndex2(state.selectedIndex + pageSize, state.logs.length);
1628
+ render2(state);
1629
+ return;
1630
+ }
1430
1631
  if (isEnter(input)) {
1431
1632
  void openView();
1432
1633
  return;
@@ -2532,13 +2733,26 @@ function normalizeWebhookUrls(urls) {
2532
2733
  return urls.map((url) => url.trim()).filter((url) => url.length > 0);
2533
2734
  }
2534
2735
  function buildWebhookPayload(input) {
2535
- return {
2536
- event: input.event,
2537
- task: input.task,
2736
+ const base = {
2538
2737
  branch: input.branch ?? "",
2539
2738
  iteration: input.iteration,
2540
2739
  stage: input.stage,
2541
- timestamp: input.timestamp ?? isoNow()
2740
+ timestamp: input.timestamp ?? localTimestamp(),
2741
+ project: input.project,
2742
+ commit: input.commit ?? "",
2743
+ pr: input.pr ?? "",
2744
+ ...input.plan !== void 0 ? { plan: input.plan } : {}
2745
+ };
2746
+ if (input.event === "task_start") {
2747
+ return {
2748
+ ...base,
2749
+ event: "task_start",
2750
+ task: input.task
2751
+ };
2752
+ }
2753
+ return {
2754
+ ...base,
2755
+ event: input.event
2542
2756
  };
2543
2757
  }
2544
2758
  function resolveFetcher(fetcher) {
@@ -2596,11 +2810,14 @@ function trimOutput(output, limit = MAX_LOG_LENGTH) {
2596
2810
  return `${output.slice(0, limit)}
2597
2811
  \u2026\u2026\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF0C\u539F\u59CB\u957F\u5EA6 ${output.length} \u5B57\u7B26\uFF09`;
2598
2812
  }
2599
- function truncateText(text, limit = 24) {
2813
+ function truncateText(text, limit = 100) {
2600
2814
  const trimmed = text.trim();
2601
2815
  if (trimmed.length <= limit) return trimmed;
2602
2816
  return `${trimmed.slice(0, limit)}...`;
2603
2817
  }
2818
+ function normalizePlanForWebhook(plan) {
2819
+ return plan.replace(/\r\n?/g, "\n");
2820
+ }
2604
2821
  async function safeCommandOutput(command, args, cwd, logger, label, verboseCommand) {
2605
2822
  const result = await runCommand(command, args, {
2606
2823
  cwd,
@@ -2614,6 +2831,41 @@ async function safeCommandOutput(command, args, cwd, logger, label, verboseComma
2614
2831
  }
2615
2832
  return result.stdout.trim();
2616
2833
  }
2834
+ function normalizeWebhookUrl(value) {
2835
+ if (!value) return "";
2836
+ const trimmed = value.trim();
2837
+ if (!/^https?:\/\//i.test(trimmed)) return "";
2838
+ return trimmed;
2839
+ }
2840
+ function normalizeRepoUrl(remoteUrl) {
2841
+ const trimmed = remoteUrl.trim();
2842
+ if (!trimmed) return null;
2843
+ const withoutGit = trimmed.replace(/\.git$/i, "");
2844
+ if (withoutGit.includes("://")) {
2845
+ try {
2846
+ const parsed = new URL(withoutGit);
2847
+ const protocol = parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.protocol : "https:";
2848
+ const pathname = parsed.pathname.replace(/\.git$/i, "");
2849
+ return `${protocol}//${parsed.host}${pathname}`.replace(/\/+$/, "");
2850
+ } catch {
2851
+ return null;
2852
+ }
2853
+ }
2854
+ const scpMatch = withoutGit.match(/^(?:[^@]+@)?([^:]+):(.+)$/);
2855
+ if (!scpMatch) return null;
2856
+ const host = scpMatch[1];
2857
+ const repoPath = scpMatch[2];
2858
+ return `https://${host}/${repoPath}`.replace(/\/+$/, "");
2859
+ }
2860
+ async function resolveCommitLink(cwd, logger) {
2861
+ const sha = await safeCommandOutput("git", ["rev-parse", "HEAD"], cwd, logger, "git", "git rev-parse HEAD");
2862
+ if (!sha) return "";
2863
+ const remote = await safeCommandOutput("git", ["remote", "get-url", "origin"], cwd, logger, "git", "git remote get-url origin");
2864
+ if (!remote) return "";
2865
+ const repoUrl = normalizeRepoUrl(remote);
2866
+ if (!repoUrl) return "";
2867
+ return `${repoUrl}/commit/${sha}`;
2868
+ }
2617
2869
  async function runSingleTest(kind, command, cwd, logger) {
2618
2870
  const label = kind === "unit" ? "\u5355\u5143\u6D4B\u8BD5" : "e2e \u6D4B\u8BD5";
2619
2871
  logger.info(`\u6267\u884C${label}: ${command}`);
@@ -2806,14 +3058,33 @@ async function runLoop(config) {
2806
3058
  let prInfo = null;
2807
3059
  let prFailed = false;
2808
3060
  let sessionIndex = 0;
3061
+ let commitLink = "";
3062
+ let prLink = "";
3063
+ let commitCreated = false;
3064
+ let pushSucceeded = false;
2809
3065
  const preWorktreeRecords = [];
2810
- const notifyWebhook = async (event, iteration, stage) => {
2811
- const payload = buildWebhookPayload({
3066
+ const resolveProjectName = () => import_node_path9.default.basename(workDir);
3067
+ const notifyWebhook = async (event, iteration, stage, plan) => {
3068
+ const project = resolveProjectName();
3069
+ const payload = event === "task_start" ? buildWebhookPayload({
2812
3070
  event,
2813
3071
  task: config.task,
2814
3072
  branch: branchName,
2815
3073
  iteration,
2816
- stage
3074
+ stage,
3075
+ project,
3076
+ commit: commitLink,
3077
+ pr: prLink,
3078
+ plan
3079
+ }) : buildWebhookPayload({
3080
+ event,
3081
+ branch: branchName,
3082
+ iteration,
3083
+ stage,
3084
+ project,
3085
+ commit: commitLink,
3086
+ pr: prLink,
3087
+ plan
2817
3088
  });
2818
3089
  await sendWebhookNotifications(config.webhooks, payload, logger);
2819
3090
  };
@@ -2891,7 +3162,8 @@ async function runLoop(config) {
2891
3162
  });
2892
3163
  const runAiSession = async (stage, prompt, extras) => {
2893
3164
  sessionIndex += 1;
2894
- await notifyWebhook("iteration_start", sessionIndex, stage);
3165
+ const webhookPlan = stage === "\u8BA1\u5212\u751F\u6210" ? normalizePlanForWebhook(await readFileSafe(workflowFiles.planFile)) : void 0;
3166
+ await notifyWebhook("iteration_start", sessionIndex, stage, webhookPlan);
2895
3167
  logger.info(`${stage} \u63D0\u793A\u6784\u5EFA\u5B8C\u6210\uFF0C\u8C03\u7528 AI CLI...`);
2896
3168
  const aiResult = await runAi(prompt, aiConfig, logger, extras?.cwd ?? workDir);
2897
3169
  accumulatedUsage = mergeTokenUsage(accumulatedUsage, aiResult.usage);
@@ -3107,6 +3379,7 @@ async function runLoop(config) {
3107
3379
  };
3108
3380
  try {
3109
3381
  const committed = await commitAll(commitMessage, workDir, logger);
3382
+ commitCreated = committed;
3110
3383
  deliveryNotes.push(committed ? `\u81EA\u52A8\u63D0\u4EA4\uFF1A\u5DF2\u63D0\u4EA4\uFF08${commitMessage.title}\uFF09` : "\u81EA\u52A8\u63D0\u4EA4\uFF1A\u672A\u751F\u6210\u63D0\u4EA4\uFF08\u53EF\u80FD\u65E0\u53D8\u66F4\u6216\u63D0\u4EA4\u5931\u8D25\uFF09");
3111
3384
  } catch (error) {
3112
3385
  deliveryNotes.push(`\u81EA\u52A8\u63D0\u4EA4\uFF1A\u5931\u8D25\uFF08${String(error)}\uFF09`);
@@ -3123,6 +3396,7 @@ async function runLoop(config) {
3123
3396
  } else {
3124
3397
  try {
3125
3398
  await pushBranch(branchName, workDir, logger);
3399
+ pushSucceeded = true;
3126
3400
  deliveryNotes.push(`\u81EA\u52A8\u63A8\u9001\uFF1A\u5DF2\u63A8\u9001\uFF08${branchName}\uFF09`);
3127
3401
  } catch (error) {
3128
3402
  deliveryNotes.push(`\u81EA\u52A8\u63A8\u9001\uFF1A\u5931\u8D25\uFF08${String(error)}\uFF09`);
@@ -3131,6 +3405,9 @@ async function runLoop(config) {
3131
3405
  } else {
3132
3406
  deliveryNotes.push("\u81EA\u52A8\u63A8\u9001\uFF1A\u672A\u5F00\u542F");
3133
3407
  }
3408
+ if (commitCreated && pushSucceeded) {
3409
+ commitLink = await resolveCommitLink(workDir, logger);
3410
+ }
3134
3411
  if (config.pr.enable) {
3135
3412
  if (lastTestFailed) {
3136
3413
  deliveryNotes.push("PR \u521B\u5EFA\uFF1A\u5DF2\u8DF3\u8FC7\uFF08\u6D4B\u8BD5\u672A\u901A\u8FC7\uFF09");
@@ -3189,6 +3466,7 @@ async function runLoop(config) {
3189
3466
  } else {
3190
3467
  deliveryNotes.push("PR \u521B\u5EFA\uFF1A\u672A\u5F00\u542F\uFF08\u7F3A\u5C11\u5206\u652F\u540D\uFF09");
3191
3468
  }
3469
+ prLink = normalizeWebhookUrl(prInfo?.url);
3192
3470
  if (deliveryNotes.length > 0) {
3193
3471
  const record = formatSystemRecord("\u63D0\u4EA4\u4E0EPR", deliveryNotes.join("\n"), isoNow());
3194
3472
  await appendSection(workflowFiles.notesFile, record);
@@ -3840,6 +4118,8 @@ var RUN_OPTION_SPECS = [
3840
4118
  var RUN_OPTION_FLAG_MAP = new Map(
3841
4119
  RUN_OPTION_SPECS.flatMap((spec) => spec.flags.map((flag) => [flag, spec]))
3842
4120
  );
4121
+ var USE_ALIAS_FLAG = "--use-alias";
4122
+ var USE_AGENT_FLAG = "--use-agent";
3843
4123
  function parseInteger(value, defaultValue) {
3844
4124
  const parsed = Number.parseInt(value, 10);
3845
4125
  if (Number.isNaN(parsed)) return defaultValue;
@@ -3869,7 +4149,21 @@ function buildBackgroundArgs(argv, logFile, branchName, injectBranch = false) {
3869
4149
  }
3870
4150
  function extractAliasCommandArgs(argv, name) {
3871
4151
  const args = argv.slice(2);
3872
- const start = args.findIndex((arg, index) => arg === "set" && args[index + 1] === "alias" && args[index + 2] === name);
4152
+ const start = args.findIndex((arg, index) => {
4153
+ const legacyMatch = arg === "set" && args[index + 1] === "alias" && args[index + 2] === name;
4154
+ const aliasMatch = isAliasCommandToken(arg) && args[index + 1] === "set" && args[index + 2] === name;
4155
+ return legacyMatch || aliasMatch;
4156
+ });
4157
+ if (start < 0) return [];
4158
+ const rest = args.slice(start + 3);
4159
+ if (rest[0] === "--") return rest.slice(1);
4160
+ return rest;
4161
+ }
4162
+ function extractAgentCommandArgs(argv, action, name) {
4163
+ const args = argv.slice(2);
4164
+ const start = args.findIndex(
4165
+ (arg, index) => arg === "agent" && args[index + 1] === action && args[index + 2] === name
4166
+ );
3873
4167
  if (start < 0) return [];
3874
4168
  const rest = args.slice(start + 3);
3875
4169
  if (rest[0] === "--") return rest.slice(1);
@@ -3888,7 +4182,7 @@ function extractAliasRunArgs(argv, name) {
3888
4182
  if (rest[0] === "--") return rest.slice(1);
3889
4183
  return rest;
3890
4184
  }
3891
- function normalizeAliasCommandArgs(args) {
4185
+ function normalizeRunCommandArgs(args) {
3892
4186
  let start = 0;
3893
4187
  if (args[start] === "wheel-ai") {
3894
4188
  start += 1;
@@ -3959,18 +4253,133 @@ function parseArgSegments(tokens) {
3959
4253
  }
3960
4254
  return segments;
3961
4255
  }
3962
- function mergeAliasCommandArgs(aliasTokens, additionTokens) {
3963
- const aliasSegments = parseArgSegments(aliasTokens);
4256
+ function mergeRunCommandArgs(baseTokens, additionTokens) {
4257
+ const baseSegments = parseArgSegments(baseTokens);
3964
4258
  const additionSegments = parseArgSegments(additionTokens);
3965
4259
  const overrideNames = new Set(
3966
4260
  additionSegments.flatMap((segment) => segment.name ? [segment.name] : [])
3967
4261
  );
3968
4262
  const merged = [
3969
- ...aliasSegments.filter((segment) => !segment.name || !overrideNames.has(segment.name)),
4263
+ ...baseSegments.filter((segment) => !segment.name || !overrideNames.has(segment.name)),
3970
4264
  ...additionSegments
3971
4265
  ];
3972
4266
  return merged.flatMap((segment) => segment.tokens);
3973
4267
  }
4268
+ function extractRunCommandArgs(argv) {
4269
+ const args = argv.slice(2);
4270
+ const start = args.findIndex((arg) => arg === "run");
4271
+ if (start < 0) return [];
4272
+ return args.slice(start + 1);
4273
+ }
4274
+ function parseUseOptionToken(token, flag) {
4275
+ if (token === flag) {
4276
+ return { matched: true };
4277
+ }
4278
+ if (token.startsWith(`${flag}=`)) {
4279
+ return { matched: true, value: token.slice(flag.length + 1) };
4280
+ }
4281
+ return { matched: false };
4282
+ }
4283
+ function resolveUseOption(tokens, index) {
4284
+ const token = tokens[index];
4285
+ const aliasMatch = parseUseOptionToken(token, USE_ALIAS_FLAG);
4286
+ if (aliasMatch.matched) {
4287
+ const value = aliasMatch.value ?? tokens[index + 1];
4288
+ if (!value) {
4289
+ throw new Error(`${USE_ALIAS_FLAG} \u9700\u8981\u63D0\u4F9B\u540D\u79F0`);
4290
+ }
4291
+ const nextIndex = aliasMatch.value ? index + 1 : index + 2;
4292
+ return { type: "alias", name: value, nextIndex };
4293
+ }
4294
+ const agentMatch = parseUseOptionToken(token, USE_AGENT_FLAG);
4295
+ if (agentMatch.matched) {
4296
+ const value = agentMatch.value ?? tokens[index + 1];
4297
+ if (!value) {
4298
+ throw new Error(`${USE_AGENT_FLAG} \u9700\u8981\u63D0\u4F9B\u540D\u79F0`);
4299
+ }
4300
+ const nextIndex = agentMatch.value ? index + 1 : index + 2;
4301
+ return { type: "agent", name: value, nextIndex };
4302
+ }
4303
+ return null;
4304
+ }
4305
+ function buildAliasRunArgs(entry) {
4306
+ return normalizeRunCommandArgs(splitCommandArgs(entry.command));
4307
+ }
4308
+ function buildAgentRunArgs(entry) {
4309
+ const tokens = normalizeRunCommandArgs(splitCommandArgs(entry.command));
4310
+ if (tokens.length === 0) return [];
4311
+ if (tokens[0].startsWith("-")) {
4312
+ return tokens;
4313
+ }
4314
+ const [command, ...args] = tokens;
4315
+ if (args.length === 0) {
4316
+ return ["--ai-cli", command];
4317
+ }
4318
+ return ["--ai-cli", command, "--ai-args", ...args];
4319
+ }
4320
+ function expandRunTokens(tokens, lookup, stack) {
4321
+ let mergedTokens = [];
4322
+ let buffer = [];
4323
+ let expanded = false;
4324
+ let index = 0;
4325
+ while (index < tokens.length) {
4326
+ const token = tokens[index];
4327
+ if (token === "--") {
4328
+ buffer.push(...tokens.slice(index));
4329
+ break;
4330
+ }
4331
+ const match = resolveUseOption(tokens, index);
4332
+ if (!match) {
4333
+ buffer.push(token);
4334
+ index += 1;
4335
+ continue;
4336
+ }
4337
+ if (buffer.length > 0) {
4338
+ mergedTokens = mergeRunCommandArgs(mergedTokens, buffer);
4339
+ buffer = [];
4340
+ }
4341
+ expanded = true;
4342
+ if (match.type === "alias") {
4343
+ const normalized2 = normalizeAliasName(match.name);
4344
+ if (!normalized2) {
4345
+ throw new Error("alias \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u5B57\u7B26");
4346
+ }
4347
+ if (stack.alias.has(normalized2)) {
4348
+ throw new Error(`alias \u5FAA\u73AF\u5F15\u7528\uFF1A${normalized2}`);
4349
+ }
4350
+ const entry2 = lookup.aliasEntries.get(normalized2);
4351
+ if (!entry2) {
4352
+ throw new Error(`\u672A\u627E\u5230 alias\uFF1A${normalized2}`);
4353
+ }
4354
+ stack.alias.add(normalized2);
4355
+ const resolved2 = expandRunTokens(buildAliasRunArgs(entry2), lookup, stack);
4356
+ stack.alias.delete(normalized2);
4357
+ mergedTokens = mergeRunCommandArgs(mergedTokens, resolved2.tokens);
4358
+ index = match.nextIndex;
4359
+ continue;
4360
+ }
4361
+ const normalized = normalizeAgentName(match.name);
4362
+ if (!normalized) {
4363
+ throw new Error("agent \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u5B57\u7B26");
4364
+ }
4365
+ if (stack.agent.has(normalized)) {
4366
+ throw new Error(`agent \u5FAA\u73AF\u5F15\u7528\uFF1A${normalized}`);
4367
+ }
4368
+ const entry = lookup.agentEntries.get(normalized);
4369
+ if (!entry) {
4370
+ throw new Error(`\u672A\u627E\u5230 agent\uFF1A${normalized}`);
4371
+ }
4372
+ stack.agent.add(normalized);
4373
+ const resolved = expandRunTokens(buildAgentRunArgs(entry), lookup, stack);
4374
+ stack.agent.delete(normalized);
4375
+ mergedTokens = mergeRunCommandArgs(mergedTokens, resolved.tokens);
4376
+ index = match.nextIndex;
4377
+ }
4378
+ if (buffer.length > 0) {
4379
+ mergedTokens = mergeRunCommandArgs(mergedTokens, buffer);
4380
+ }
4381
+ return { tokens: mergedTokens, expanded };
4382
+ }
3974
4383
  async function runForegroundWithDetach(options) {
3975
4384
  const args = buildBackgroundArgs(options.argv, options.logFile, options.branchName, options.injectBranch);
3976
4385
  const child = (0, import_node_child_process.spawn)(process.execPath, [...process.execArgv, ...args], {
@@ -4054,12 +4463,45 @@ async function runCli(argv) {
4054
4463
  const globalConfig = await loadGlobalConfig(defaultLogger);
4055
4464
  const effectiveArgv = applyShortcutArgv(argv, globalConfig);
4056
4465
  const program = new import_commander.Command();
4057
- program.name("wheel-ai").description("\u57FA\u4E8E AI CLI \u7684\u6301\u7EED\u8FED\u4EE3\u5F00\u53D1\u5DE5\u5177").version("0.2.0");
4466
+ program.name("wheel-ai").description("\u57FA\u4E8E AI CLI \u7684\u6301\u7EED\u8FED\u4EE3\u5F00\u53D1\u5DE5\u5177").version("0.2.1");
4058
4467
  program.addHelpText(
4059
4468
  "after",
4060
- "\n\u522B\u540D\u6267\u884C\uFF1A\n wheel-ai alias run <alias> <addition...>\n \u8FFD\u52A0\u547D\u4EE4\u4E0E alias \u91CD\u53E0\u65F6\uFF0C\u4EE5\u8FFD\u52A0\u4E3A\u51C6\u3002\n"
4469
+ "\nalias \u7BA1\u7406\uFF1A\n wheel-ai alias set <name> <options...>\n wheel-ai alias list\n wheel-ai alias delete <name>\n\nalias/agent \u53E0\u52A0\uFF1A\n wheel-ai run --use-alias <name> [--use-alias <name>...]\n wheel-ai run --use-agent <name> [--use-agent <name>...]\n \u540C\u540D\u9009\u9879\u6309\u51FA\u73B0\u987A\u5E8F\u8986\u76D6\u3002\n"
4061
4470
  );
4062
- program.command("run").option("-t, --task <task>", "\u9700\u8981\u5B8C\u6210\u7684\u4EFB\u52A1\u63CF\u8FF0\uFF08\u53EF\u91CD\u590D\u4F20\u5165\uFF0C\u72EC\u7ACB\u5904\u7406\uFF09", collect, []).option("-i, --iterations <number>", "\u6700\u5927\u8FED\u4EE3\u6B21\u6570", (value) => parseInteger(value, 5), 5).option("--ai-cli <command>", "AI CLI \u547D\u4EE4", "claude").option("--ai-args <args...>", "AI CLI \u53C2\u6570", []).option("--ai-prompt-arg <flag>", "\u7528\u4E8E\u4F20\u5165 prompt \u7684\u53C2\u6570\uFF08\u4E3A\u7A7A\u5219\u4F7F\u7528 stdin\uFF09").option("--notes-file <path>", "\u6301\u4E45\u5316\u8BB0\u5FC6\u6587\u4EF6", defaultNotesPath()).option("--plan-file <path>", "\u8BA1\u5212\u6587\u4EF6", defaultPlanPath()).option("--workflow-doc <path>", "AI \u5DE5\u4F5C\u6D41\u7A0B\u8BF4\u660E\u6587\u4EF6", defaultWorkflowDoc()).option("--worktree", "\u5728\u72EC\u7ACB worktree \u4E0A\u6267\u884C", false).option("--branch <name>", "worktree \u5206\u652F\u540D\uFF08\u9ED8\u8BA4\u81EA\u52A8\u751F\u6210\u6216\u5F53\u524D\u5206\u652F\uFF09").option("--worktree-path <path>", "worktree \u8DEF\u5F84\uFF0C\u9ED8\u8BA4 ../worktrees/<branch>").option("--base-branch <name>", "\u521B\u5EFA\u5206\u652F\u7684\u57FA\u7EBF\u5206\u652F", "main").option("--skip-install", "\u8DF3\u8FC7\u5F00\u59CB\u4EFB\u52A1\u524D\u7684\u4F9D\u8D56\u68C0\u67E5", false).option("--run-tests", "\u8FD0\u884C\u5355\u5143\u6D4B\u8BD5\u547D\u4EE4", false).option("--run-e2e", "\u8FD0\u884C e2e \u6D4B\u8BD5\u547D\u4EE4", false).option("--unit-command <cmd>", "\u5355\u5143\u6D4B\u8BD5\u547D\u4EE4", "yarn test").option("--e2e-command <cmd>", "e2e \u6D4B\u8BD5\u547D\u4EE4", "yarn e2e").option("--auto-commit", "\u81EA\u52A8 git commit", false).option("--auto-push", "\u81EA\u52A8 git push", false).option("--pr", "\u4F7F\u7528 gh \u521B\u5EFA PR", false).option("--pr-title <title>", "PR \u6807\u9898").option("--pr-body <path>", "PR \u63CF\u8FF0\u6587\u4EF6\u8DEF\u5F84\uFF08\u53EF\u7559\u7A7A\u81EA\u52A8\u751F\u6210\uFF09").option("--draft", "\u4EE5\u8349\u7A3F\u5F62\u5F0F\u521B\u5EFA PR", false).option("--reviewer <user...>", "PR reviewers", collect, []).option("--auto-merge", "PR \u68C0\u67E5\u901A\u8FC7\u540E\u81EA\u52A8\u5408\u5E76", false).option("--webhook <url>", "webhook \u901A\u77E5 URL\uFF08\u53EF\u91CD\u590D\uFF09", collect, []).option("--webhook-timeout <ms>", "webhook \u8BF7\u6C42\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09", (value) => parseInteger(value, 8e3)).option("--multi-task-mode <mode>", "\u591A\u4EFB\u52A1\u6267\u884C\u6A21\u5F0F\uFF08relay/serial/serial-continue/parallel\uFF0C\u6216\u4E2D\u6587\u63CF\u8FF0\uFF09", "relay").option("--stop-signal <token>", "AI \u8F93\u51FA\u4E2D\u7684\u505C\u6B62\u6807\u8BB0", "<<DONE>>").option("--log-file <path>", "\u65E5\u5FD7\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84").option("--background", "\u5207\u5165\u540E\u53F0\u8FD0\u884C", false).option("-v, --verbose", "\u8F93\u51FA\u8C03\u8BD5\u65E5\u5FD7", false).option("--skip-quality", "\u8DF3\u8FC7\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5", false).action(async (options) => {
4471
+ program.command("run").option("-t, --task <task>", "\u9700\u8981\u5B8C\u6210\u7684\u4EFB\u52A1\u63CF\u8FF0\uFF08\u53EF\u91CD\u590D\u4F20\u5165\uFF0C\u72EC\u7ACB\u5904\u7406\uFF09", collect, []).option("--use-alias <name>", "\u53E0\u52A0 alias \u914D\u7F6E\uFF08\u53EF\u91CD\u590D\uFF09", collect, []).option("--use-agent <name>", "\u53E0\u52A0 agent \u914D\u7F6E\uFF08\u53EF\u91CD\u590D\uFF09", collect, []).option("-i, --iterations <number>", "\u6700\u5927\u8FED\u4EE3\u6B21\u6570", (value) => parseInteger(value, 5), 5).option("--ai-cli <command>", "AI CLI \u547D\u4EE4", "claude").option("--ai-args <args...>", "AI CLI \u53C2\u6570", []).option("--ai-prompt-arg <flag>", "\u7528\u4E8E\u4F20\u5165 prompt \u7684\u53C2\u6570\uFF08\u4E3A\u7A7A\u5219\u4F7F\u7528 stdin\uFF09").option("--notes-file <path>", "\u6301\u4E45\u5316\u8BB0\u5FC6\u6587\u4EF6", defaultNotesPath()).option("--plan-file <path>", "\u8BA1\u5212\u6587\u4EF6", defaultPlanPath()).option("--workflow-doc <path>", "AI \u5DE5\u4F5C\u6D41\u7A0B\u8BF4\u660E\u6587\u4EF6", defaultWorkflowDoc()).option("--worktree", "\u5728\u72EC\u7ACB worktree \u4E0A\u6267\u884C", false).option("--branch <name>", "worktree \u5206\u652F\u540D\uFF08\u9ED8\u8BA4\u81EA\u52A8\u751F\u6210\u6216\u5F53\u524D\u5206\u652F\uFF09").option("--worktree-path <path>", "worktree \u8DEF\u5F84\uFF0C\u9ED8\u8BA4 ../worktrees/<branch>").option("--base-branch <name>", "\u521B\u5EFA\u5206\u652F\u7684\u57FA\u7EBF\u5206\u652F", "main").option("--skip-install", "\u8DF3\u8FC7\u5F00\u59CB\u4EFB\u52A1\u524D\u7684\u4F9D\u8D56\u68C0\u67E5", false).option("--run-tests", "\u8FD0\u884C\u5355\u5143\u6D4B\u8BD5\u547D\u4EE4", false).option("--run-e2e", "\u8FD0\u884C e2e \u6D4B\u8BD5\u547D\u4EE4", false).option("--unit-command <cmd>", "\u5355\u5143\u6D4B\u8BD5\u547D\u4EE4", "yarn test").option("--e2e-command <cmd>", "e2e \u6D4B\u8BD5\u547D\u4EE4", "yarn e2e").option("--auto-commit", "\u81EA\u52A8 git commit", false).option("--auto-push", "\u81EA\u52A8 git push", false).option("--pr", "\u4F7F\u7528 gh \u521B\u5EFA PR", false).option("--pr-title <title>", "PR \u6807\u9898").option("--pr-body <path>", "PR \u63CF\u8FF0\u6587\u4EF6\u8DEF\u5F84\uFF08\u53EF\u7559\u7A7A\u81EA\u52A8\u751F\u6210\uFF09").option("--draft", "\u4EE5\u8349\u7A3F\u5F62\u5F0F\u521B\u5EFA PR", false).option("--reviewer <user...>", "PR reviewers", collect, []).option("--auto-merge", "PR \u68C0\u67E5\u901A\u8FC7\u540E\u81EA\u52A8\u5408\u5E76", false).option("--webhook <url>", "webhook \u901A\u77E5 URL\uFF08\u53EF\u91CD\u590D\uFF09", collect, []).option("--webhook-timeout <ms>", "webhook \u8BF7\u6C42\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09", (value) => parseInteger(value, 8e3)).option("--multi-task-mode <mode>", "\u591A\u4EFB\u52A1\u6267\u884C\u6A21\u5F0F\uFF08relay/serial/serial-continue/parallel\uFF0C\u6216\u4E2D\u6587\u63CF\u8FF0\uFF09", "relay").option("--stop-signal <token>", "AI \u8F93\u51FA\u4E2D\u7684\u505C\u6B62\u6807\u8BB0", "<<DONE>>").option("--log-file <path>", "\u65E5\u5FD7\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84").option("--background", "\u5207\u5165\u540E\u53F0\u8FD0\u884C", false).option("-v, --verbose", "\u8F93\u51FA\u8C03\u8BD5\u65E5\u5FD7", false).option("--skip-quality", "\u8DF3\u8FC7\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5", false).action(async (options) => {
4472
+ const rawRunArgs = extractRunCommandArgs(effectiveArgv);
4473
+ const hasUseOptions = rawRunArgs.some(
4474
+ (token) => token === USE_ALIAS_FLAG || token.startsWith(`${USE_ALIAS_FLAG}=`) || token === USE_AGENT_FLAG || token.startsWith(`${USE_AGENT_FLAG}=`)
4475
+ );
4476
+ if (hasUseOptions) {
4477
+ const filePath = getGlobalConfigPath();
4478
+ const exists = await import_fs_extra12.default.pathExists(filePath);
4479
+ const content = exists ? await import_fs_extra12.default.readFile(filePath, "utf8") : "";
4480
+ const aliasEntries = parseAliasEntries(content);
4481
+ const agentEntries = parseAgentEntries(content);
4482
+ const resolved = expandRunTokens(
4483
+ rawRunArgs,
4484
+ {
4485
+ aliasEntries: new Map(aliasEntries.map((entry) => [entry.name, entry])),
4486
+ agentEntries: new Map(agentEntries.map((entry) => [entry.name, entry]))
4487
+ },
4488
+ {
4489
+ alias: /* @__PURE__ */ new Set(),
4490
+ agent: /* @__PURE__ */ new Set()
4491
+ }
4492
+ );
4493
+ if (resolved.expanded) {
4494
+ const nextArgv = [process.argv[0], process.argv[1], "run", ...resolved.tokens];
4495
+ const originalArgv = process.argv;
4496
+ process.argv = nextArgv;
4497
+ try {
4498
+ await runCli(nextArgv);
4499
+ } finally {
4500
+ process.argv = originalArgv;
4501
+ }
4502
+ return;
4503
+ }
4504
+ }
4063
4505
  const tasks = normalizeTaskList(options.task);
4064
4506
  if (tasks.length === 0) {
4065
4507
  throw new Error("\u9700\u8981\u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A\u4EFB\u52A1\u63CF\u8FF0");
@@ -4222,7 +4664,78 @@ async function runCli(argv) {
4222
4664
  program.command("logs").description("\u67E5\u770B\u5386\u53F2\u65E5\u5FD7").action(async () => {
4223
4665
  await runLogsViewer();
4224
4666
  });
4225
- program.command("set").description("\u5199\u5165\u5168\u5C40\u914D\u7F6E").command("alias <name> [options...]").description("\u8BBE\u7F6E alias").allowUnknownOption(true).action(async (name) => {
4667
+ const agentCommand = program.command("agent").description("\u7BA1\u7406 AI CLI agent \u914D\u7F6E");
4668
+ agentCommand.command("add <name> [command...]").description("\u65B0\u589E agent").allowUnknownOption(true).allowExcessArguments(true).action(async (name) => {
4669
+ const normalized = normalizeAgentName(name);
4670
+ if (!normalized) {
4671
+ throw new Error("agent \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u5B57\u7B26");
4672
+ }
4673
+ const commandArgs = extractAgentCommandArgs(effectiveArgv, "add", name);
4674
+ const commandLine = formatCommandLine(commandArgs);
4675
+ if (!commandLine) {
4676
+ throw new Error("agent \u547D\u4EE4\u4E0D\u80FD\u4E3A\u7A7A");
4677
+ }
4678
+ const filePath = getGlobalConfigPath();
4679
+ const exists = await import_fs_extra12.default.pathExists(filePath);
4680
+ const content = exists ? await import_fs_extra12.default.readFile(filePath, "utf8") : "";
4681
+ const entries = parseAgentEntries(content);
4682
+ if (entries.some((entry) => entry.name === normalized)) {
4683
+ throw new Error(`agent \u5DF2\u5B58\u5728\uFF1A${normalized}`);
4684
+ }
4685
+ await upsertAgentEntry(normalized, commandLine);
4686
+ console.log(`\u5DF2\u65B0\u589E agent\uFF1A${normalized}`);
4687
+ });
4688
+ agentCommand.command("set <name> [command...]").description("\u5199\u5165 agent").allowUnknownOption(true).allowExcessArguments(true).action(async (name) => {
4689
+ const normalized = normalizeAgentName(name);
4690
+ if (!normalized) {
4691
+ throw new Error("agent \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u5B57\u7B26");
4692
+ }
4693
+ const commandArgs = extractAgentCommandArgs(effectiveArgv, "set", name);
4694
+ const commandLine = formatCommandLine(commandArgs);
4695
+ if (!commandLine) {
4696
+ throw new Error("agent \u547D\u4EE4\u4E0D\u80FD\u4E3A\u7A7A");
4697
+ }
4698
+ await upsertAgentEntry(normalized, commandLine);
4699
+ console.log(`\u5DF2\u5199\u5165 agent\uFF1A${normalized}`);
4700
+ });
4701
+ agentCommand.command("delete <name>").description("\u5220\u9664 agent").action(async (name) => {
4702
+ const normalized = normalizeAgentName(name);
4703
+ if (!normalized) {
4704
+ throw new Error("agent \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u5B57\u7B26");
4705
+ }
4706
+ const filePath = getGlobalConfigPath();
4707
+ const exists = await import_fs_extra12.default.pathExists(filePath);
4708
+ if (!exists) {
4709
+ throw new Error(`\u672A\u627E\u5230 agent \u914D\u7F6E\u6587\u4EF6\uFF1A${filePath}`);
4710
+ }
4711
+ const removed = await removeAgentEntry(normalized);
4712
+ if (!removed) {
4713
+ throw new Error(`\u672A\u627E\u5230 agent\uFF1A${normalized}`);
4714
+ }
4715
+ console.log(`\u5DF2\u5220\u9664 agent\uFF1A${normalized}`);
4716
+ });
4717
+ agentCommand.command("list").description("\u5217\u51FA agent \u914D\u7F6E").action(async () => {
4718
+ const filePath = getGlobalConfigPath();
4719
+ const exists = await import_fs_extra12.default.pathExists(filePath);
4720
+ if (!exists) {
4721
+ console.log("\u672A\u53D1\u73B0 agent \u914D\u7F6E");
4722
+ return;
4723
+ }
4724
+ const content = await import_fs_extra12.default.readFile(filePath, "utf8");
4725
+ const entries = parseAgentEntries(content);
4726
+ if (entries.length === 0) {
4727
+ console.log("\u672A\u53D1\u73B0 agent \u914D\u7F6E");
4728
+ return;
4729
+ }
4730
+ entries.forEach((entry) => {
4731
+ console.log(`${entry.name}: ${entry.command}`);
4732
+ });
4733
+ });
4734
+ agentCommand.action(() => {
4735
+ agentCommand.help();
4736
+ });
4737
+ const aliasCommand = program.command("alias").alias("aliases").description("\u7BA1\u7406\u5168\u5C40 alias \u914D\u7F6E");
4738
+ aliasCommand.command("set <name> [options...]").description("\u5199\u5165 alias").allowUnknownOption(true).allowExcessArguments(true).action(async (name) => {
4226
4739
  const normalized = normalizeAliasName(name);
4227
4740
  if (!normalized) {
4228
4741
  throw new Error("alias \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u5B57\u7B26");
@@ -4235,7 +4748,39 @@ async function runCli(argv) {
4235
4748
  await upsertAliasEntry(normalized, commandLine);
4236
4749
  console.log(`\u5DF2\u5199\u5165 alias\uFF1A${normalized}`);
4237
4750
  });
4238
- const aliasCommand = program.command("alias").alias("aliases").description("\u6D4F\u89C8\u5168\u5C40 alias \u914D\u7F6E\uFF08alias run \u53EF\u6267\u884C\u5E76\u8FFD\u52A0\u547D\u4EE4\uFF09");
4751
+ aliasCommand.command("list").description("\u5217\u51FA alias \u914D\u7F6E").action(async () => {
4752
+ const filePath = getGlobalConfigPath();
4753
+ const exists = await import_fs_extra12.default.pathExists(filePath);
4754
+ if (!exists) {
4755
+ console.log("\u672A\u53D1\u73B0 alias \u914D\u7F6E");
4756
+ return;
4757
+ }
4758
+ const content = await import_fs_extra12.default.readFile(filePath, "utf8");
4759
+ const entries = parseAliasEntries(content).filter((entry) => entry.source === "alias");
4760
+ if (entries.length === 0) {
4761
+ console.log("\u672A\u53D1\u73B0 alias \u914D\u7F6E");
4762
+ return;
4763
+ }
4764
+ entries.forEach((entry) => {
4765
+ console.log(`${entry.name}: ${entry.command}`);
4766
+ });
4767
+ });
4768
+ aliasCommand.command("delete <name>").description("\u5220\u9664 alias").action(async (name) => {
4769
+ const normalized = normalizeAliasName(name);
4770
+ if (!normalized) {
4771
+ throw new Error("alias \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u5B57\u7B26");
4772
+ }
4773
+ const filePath = getGlobalConfigPath();
4774
+ const exists = await import_fs_extra12.default.pathExists(filePath);
4775
+ if (!exists) {
4776
+ throw new Error(`\u672A\u627E\u5230 alias \u914D\u7F6E\u6587\u4EF6\uFF1A${filePath}`);
4777
+ }
4778
+ const removed = await removeAliasEntry(normalized);
4779
+ if (!removed) {
4780
+ throw new Error(`\u672A\u627E\u5230 alias\uFF1A${normalized}`);
4781
+ }
4782
+ console.log(`\u5DF2\u5220\u9664 alias\uFF1A${normalized}`);
4783
+ });
4239
4784
  aliasCommand.command("run <name> [addition...]").description("\u6267\u884C alias \u5E76\u8FFD\u52A0\u547D\u4EE4").allowUnknownOption(true).allowExcessArguments(true).action(async (name) => {
4240
4785
  const normalized = normalizeAliasName(name);
4241
4786
  if (!normalized) {
@@ -4252,9 +4797,9 @@ async function runCli(argv) {
4252
4797
  if (!entry) {
4253
4798
  throw new Error(`\u672A\u627E\u5230 alias\uFF1A${normalized}`);
4254
4799
  }
4255
- const aliasTokens = normalizeAliasCommandArgs(splitCommandArgs(entry.command));
4800
+ const aliasTokens = normalizeRunCommandArgs(splitCommandArgs(entry.command));
4256
4801
  const additionTokens = extractAliasRunArgs(effectiveArgv, normalized);
4257
- const mergedTokens = mergeAliasCommandArgs(aliasTokens, additionTokens);
4802
+ const mergedTokens = mergeRunCommandArgs(aliasTokens, additionTokens);
4258
4803
  if (mergedTokens.length === 0) {
4259
4804
  throw new Error("alias \u547D\u4EE4\u4E0D\u80FD\u4E3A\u7A7A");
4260
4805
  }