hamster-wheel-cli 0.2.0-beta.1 → 0.2.0-beta.2
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 +4 -0
- package/README.md +7 -0
- package/dist/cli.js +171 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.js +171 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +222 -5
package/dist/index.js
CHANGED
|
@@ -3805,6 +3805,44 @@ async function tailLogFile(options) {
|
|
|
3805
3805
|
|
|
3806
3806
|
// src/cli.ts
|
|
3807
3807
|
var FOREGROUND_CHILD_ENV = "WHEEL_AI_FOREGROUND_CHILD";
|
|
3808
|
+
var RUN_OPTION_SPECS = [
|
|
3809
|
+
{ name: "task", flags: ["-t", "--task"], valueMode: "single" },
|
|
3810
|
+
{ name: "iterations", flags: ["-i", "--iterations"], valueMode: "single" },
|
|
3811
|
+
{ name: "ai-cli", flags: ["--ai-cli"], valueMode: "single" },
|
|
3812
|
+
{ name: "ai-args", flags: ["--ai-args"], valueMode: "variadic" },
|
|
3813
|
+
{ name: "ai-prompt-arg", flags: ["--ai-prompt-arg"], valueMode: "single" },
|
|
3814
|
+
{ name: "notes-file", flags: ["--notes-file"], valueMode: "single" },
|
|
3815
|
+
{ name: "plan-file", flags: ["--plan-file"], valueMode: "single" },
|
|
3816
|
+
{ name: "workflow-doc", flags: ["--workflow-doc"], valueMode: "single" },
|
|
3817
|
+
{ name: "worktree", flags: ["--worktree"], valueMode: "none" },
|
|
3818
|
+
{ name: "branch", flags: ["--branch"], valueMode: "single" },
|
|
3819
|
+
{ name: "worktree-path", flags: ["--worktree-path"], valueMode: "single" },
|
|
3820
|
+
{ name: "base-branch", flags: ["--base-branch"], valueMode: "single" },
|
|
3821
|
+
{ name: "skip-install", flags: ["--skip-install"], valueMode: "none" },
|
|
3822
|
+
{ name: "run-tests", flags: ["--run-tests"], valueMode: "none" },
|
|
3823
|
+
{ name: "run-e2e", flags: ["--run-e2e"], valueMode: "none" },
|
|
3824
|
+
{ name: "unit-command", flags: ["--unit-command"], valueMode: "single" },
|
|
3825
|
+
{ name: "e2e-command", flags: ["--e2e-command"], valueMode: "single" },
|
|
3826
|
+
{ name: "auto-commit", flags: ["--auto-commit"], valueMode: "none" },
|
|
3827
|
+
{ name: "auto-push", flags: ["--auto-push"], valueMode: "none" },
|
|
3828
|
+
{ name: "pr", flags: ["--pr"], valueMode: "none" },
|
|
3829
|
+
{ name: "pr-title", flags: ["--pr-title"], valueMode: "single" },
|
|
3830
|
+
{ name: "pr-body", flags: ["--pr-body"], valueMode: "single" },
|
|
3831
|
+
{ name: "draft", flags: ["--draft"], valueMode: "none" },
|
|
3832
|
+
{ name: "reviewer", flags: ["--reviewer"], valueMode: "variadic" },
|
|
3833
|
+
{ name: "auto-merge", flags: ["--auto-merge"], valueMode: "none" },
|
|
3834
|
+
{ name: "webhook", flags: ["--webhook"], valueMode: "single" },
|
|
3835
|
+
{ name: "webhook-timeout", flags: ["--webhook-timeout"], valueMode: "single" },
|
|
3836
|
+
{ name: "multi-task-mode", flags: ["--multi-task-mode"], valueMode: "single" },
|
|
3837
|
+
{ name: "stop-signal", flags: ["--stop-signal"], valueMode: "single" },
|
|
3838
|
+
{ name: "log-file", flags: ["--log-file"], valueMode: "single" },
|
|
3839
|
+
{ name: "background", flags: ["--background"], valueMode: "none" },
|
|
3840
|
+
{ name: "verbose", flags: ["-v", "--verbose"], valueMode: "none" },
|
|
3841
|
+
{ name: "skip-quality", flags: ["--skip-quality"], valueMode: "none" }
|
|
3842
|
+
];
|
|
3843
|
+
var RUN_OPTION_FLAG_MAP = new Map(
|
|
3844
|
+
RUN_OPTION_SPECS.flatMap((spec) => spec.flags.map((flag) => [flag, spec]))
|
|
3845
|
+
);
|
|
3808
3846
|
function parseInteger(value, defaultValue) {
|
|
3809
3847
|
const parsed = Number.parseInt(value, 10);
|
|
3810
3848
|
if (Number.isNaN(parsed)) return defaultValue;
|
|
@@ -3840,6 +3878,102 @@ function extractAliasCommandArgs(argv, name) {
|
|
|
3840
3878
|
if (rest[0] === "--") return rest.slice(1);
|
|
3841
3879
|
return rest;
|
|
3842
3880
|
}
|
|
3881
|
+
function isAliasCommandToken(token) {
|
|
3882
|
+
return token === "alias" || token === "aliases";
|
|
3883
|
+
}
|
|
3884
|
+
function extractAliasRunArgs(argv, name) {
|
|
3885
|
+
const args = argv.slice(2);
|
|
3886
|
+
const start = args.findIndex(
|
|
3887
|
+
(arg, index) => isAliasCommandToken(arg) && args[index + 1] === "run" && args[index + 2] === name
|
|
3888
|
+
);
|
|
3889
|
+
if (start < 0) return [];
|
|
3890
|
+
const rest = args.slice(start + 3);
|
|
3891
|
+
if (rest[0] === "--") return rest.slice(1);
|
|
3892
|
+
return rest;
|
|
3893
|
+
}
|
|
3894
|
+
function normalizeAliasCommandArgs(args) {
|
|
3895
|
+
let start = 0;
|
|
3896
|
+
if (args[start] === "wheel-ai") {
|
|
3897
|
+
start += 1;
|
|
3898
|
+
}
|
|
3899
|
+
if (args[start] === "run") {
|
|
3900
|
+
start += 1;
|
|
3901
|
+
}
|
|
3902
|
+
return args.slice(start);
|
|
3903
|
+
}
|
|
3904
|
+
function resolveRunOptionSpec(token) {
|
|
3905
|
+
const equalIndex = token.indexOf("=");
|
|
3906
|
+
const flag = equalIndex > 0 ? token.slice(0, equalIndex) : token;
|
|
3907
|
+
const spec = RUN_OPTION_FLAG_MAP.get(flag);
|
|
3908
|
+
if (!spec) return null;
|
|
3909
|
+
if (equalIndex > 0) {
|
|
3910
|
+
return { spec, inlineValue: token.slice(equalIndex + 1) };
|
|
3911
|
+
}
|
|
3912
|
+
return { spec };
|
|
3913
|
+
}
|
|
3914
|
+
function parseArgSegments(tokens) {
|
|
3915
|
+
const segments = [];
|
|
3916
|
+
let index = 0;
|
|
3917
|
+
while (index < tokens.length) {
|
|
3918
|
+
const token = tokens[index];
|
|
3919
|
+
if (token === "--") {
|
|
3920
|
+
segments.push({ tokens: tokens.slice(index) });
|
|
3921
|
+
break;
|
|
3922
|
+
}
|
|
3923
|
+
const match = resolveRunOptionSpec(token);
|
|
3924
|
+
if (!match) {
|
|
3925
|
+
segments.push({ tokens: [token] });
|
|
3926
|
+
index += 1;
|
|
3927
|
+
continue;
|
|
3928
|
+
}
|
|
3929
|
+
if (match.inlineValue !== void 0) {
|
|
3930
|
+
segments.push({ name: match.spec.name, tokens: [token] });
|
|
3931
|
+
index += 1;
|
|
3932
|
+
continue;
|
|
3933
|
+
}
|
|
3934
|
+
if (match.spec.valueMode === "none") {
|
|
3935
|
+
segments.push({ name: match.spec.name, tokens: [token] });
|
|
3936
|
+
index += 1;
|
|
3937
|
+
continue;
|
|
3938
|
+
}
|
|
3939
|
+
if (match.spec.valueMode === "single") {
|
|
3940
|
+
const next = tokens[index + 1];
|
|
3941
|
+
if (next !== void 0) {
|
|
3942
|
+
segments.push({ name: match.spec.name, tokens: [token, next] });
|
|
3943
|
+
index += 2;
|
|
3944
|
+
} else {
|
|
3945
|
+
segments.push({ name: match.spec.name, tokens: [token] });
|
|
3946
|
+
index += 1;
|
|
3947
|
+
}
|
|
3948
|
+
continue;
|
|
3949
|
+
}
|
|
3950
|
+
const values = [];
|
|
3951
|
+
let cursor = index + 1;
|
|
3952
|
+
while (cursor < tokens.length) {
|
|
3953
|
+
const next = tokens[cursor];
|
|
3954
|
+
if (next === "--") break;
|
|
3955
|
+
const nextMatch = resolveRunOptionSpec(next);
|
|
3956
|
+
if (nextMatch) break;
|
|
3957
|
+
values.push(next);
|
|
3958
|
+
cursor += 1;
|
|
3959
|
+
}
|
|
3960
|
+
segments.push({ name: match.spec.name, tokens: [token, ...values] });
|
|
3961
|
+
index = cursor;
|
|
3962
|
+
}
|
|
3963
|
+
return segments;
|
|
3964
|
+
}
|
|
3965
|
+
function mergeAliasCommandArgs(aliasTokens, additionTokens) {
|
|
3966
|
+
const aliasSegments = parseArgSegments(aliasTokens);
|
|
3967
|
+
const additionSegments = parseArgSegments(additionTokens);
|
|
3968
|
+
const overrideNames = new Set(
|
|
3969
|
+
additionSegments.flatMap((segment) => segment.name ? [segment.name] : [])
|
|
3970
|
+
);
|
|
3971
|
+
const merged = [
|
|
3972
|
+
...aliasSegments.filter((segment) => !segment.name || !overrideNames.has(segment.name)),
|
|
3973
|
+
...additionSegments
|
|
3974
|
+
];
|
|
3975
|
+
return merged.flatMap((segment) => segment.tokens);
|
|
3976
|
+
}
|
|
3843
3977
|
async function runForegroundWithDetach(options) {
|
|
3844
3978
|
const args = buildBackgroundArgs(options.argv, options.logFile, options.branchName, options.injectBranch);
|
|
3845
3979
|
const child = (0, import_node_child_process.spawn)(process.execPath, [...process.execArgv, ...args], {
|
|
@@ -3924,6 +4058,10 @@ async function runCli(argv) {
|
|
|
3924
4058
|
const effectiveArgv = applyShortcutArgv(argv, globalConfig);
|
|
3925
4059
|
const program = new import_commander.Command();
|
|
3926
4060
|
program.name("wheel-ai").description("\u57FA\u4E8E AI CLI \u7684\u6301\u7EED\u8FED\u4EE3\u5F00\u53D1\u5DE5\u5177").version("1.0.0");
|
|
4061
|
+
program.addHelpText(
|
|
4062
|
+
"after",
|
|
4063
|
+
"\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"
|
|
4064
|
+
);
|
|
3927
4065
|
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) => {
|
|
3928
4066
|
const tasks = normalizeTaskList(options.task);
|
|
3929
4067
|
if (tasks.length === 0) {
|
|
@@ -4100,7 +4238,39 @@ async function runCli(argv) {
|
|
|
4100
4238
|
await upsertAliasEntry(normalized, commandLine);
|
|
4101
4239
|
console.log(`\u5DF2\u5199\u5165 alias\uFF1A${normalized}`);
|
|
4102
4240
|
});
|
|
4103
|
-
program.command("alias").alias("aliases").description("\u6D4F\u89C8\u5168\u5C40 alias \u914D\u7F6E")
|
|
4241
|
+
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");
|
|
4242
|
+
aliasCommand.command("run <name> [addition...]").description("\u6267\u884C alias \u5E76\u8FFD\u52A0\u547D\u4EE4").allowUnknownOption(true).allowExcessArguments(true).action(async (name) => {
|
|
4243
|
+
const normalized = normalizeAliasName(name);
|
|
4244
|
+
if (!normalized) {
|
|
4245
|
+
throw new Error("alias \u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u5B57\u7B26");
|
|
4246
|
+
}
|
|
4247
|
+
const filePath = getGlobalConfigPath();
|
|
4248
|
+
const exists = await import_fs_extra12.default.pathExists(filePath);
|
|
4249
|
+
if (!exists) {
|
|
4250
|
+
throw new Error(`\u672A\u627E\u5230 alias \u914D\u7F6E\u6587\u4EF6\uFF1A${filePath}`);
|
|
4251
|
+
}
|
|
4252
|
+
const content = await import_fs_extra12.default.readFile(filePath, "utf8");
|
|
4253
|
+
const entries = parseAliasEntries(content);
|
|
4254
|
+
const entry = entries.find((item) => item.name === normalized);
|
|
4255
|
+
if (!entry) {
|
|
4256
|
+
throw new Error(`\u672A\u627E\u5230 alias\uFF1A${normalized}`);
|
|
4257
|
+
}
|
|
4258
|
+
const aliasTokens = normalizeAliasCommandArgs(splitCommandArgs(entry.command));
|
|
4259
|
+
const additionTokens = extractAliasRunArgs(effectiveArgv, normalized);
|
|
4260
|
+
const mergedTokens = mergeAliasCommandArgs(aliasTokens, additionTokens);
|
|
4261
|
+
if (mergedTokens.length === 0) {
|
|
4262
|
+
throw new Error("alias \u547D\u4EE4\u4E0D\u80FD\u4E3A\u7A7A");
|
|
4263
|
+
}
|
|
4264
|
+
const nextArgv = [process.argv[0], process.argv[1], "run", ...mergedTokens];
|
|
4265
|
+
const originalArgv = process.argv;
|
|
4266
|
+
process.argv = nextArgv;
|
|
4267
|
+
try {
|
|
4268
|
+
await runCli(nextArgv);
|
|
4269
|
+
} finally {
|
|
4270
|
+
process.argv = originalArgv;
|
|
4271
|
+
}
|
|
4272
|
+
});
|
|
4273
|
+
aliasCommand.action(async () => {
|
|
4104
4274
|
await runAliasViewer();
|
|
4105
4275
|
});
|
|
4106
4276
|
await program.parseAsync(effectiveArgv);
|