hamster-wheel-cli 0.2.0-beta.2 → 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/CHANGELOG.md +29 -2
- package/README.md +56 -19
- package/dist/cli.js +567 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.js +567 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +376 -18
- package/src/global-config.ts +251 -0
- package/src/logs-viewer.ts +13 -1
- package/src/loop.ts +92 -12
- package/src/utils.ts +13 -0
- package/src/webhook.ts +54 -9
- package/tests/e2e/cli.e2e.test.ts +41 -3
- package/tests/global-config.test.ts +89 -1
- package/tests/webhook.test.ts +10 -6
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
|
-
|
|
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 ??
|
|
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 =
|
|
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
|
|
2811
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
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
|
|
3963
|
-
const
|
|
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
|
-
...
|
|
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("
|
|
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
|
-
"\
|
|
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("
|
|
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
|
-
|
|
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 =
|
|
4800
|
+
const aliasTokens = normalizeRunCommandArgs(splitCommandArgs(entry.command));
|
|
4256
4801
|
const additionTokens = extractAliasRunArgs(effectiveArgv, normalized);
|
|
4257
|
-
const mergedTokens =
|
|
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
|
}
|