hamster-wheel-cli 0.2.0-beta.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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], {
@@ -3923,7 +4057,11 @@ async function runCli(argv) {
3923
4057
  const globalConfig = await loadGlobalConfig(defaultLogger);
3924
4058
  const effectiveArgv = applyShortcutArgv(argv, globalConfig);
3925
4059
  const program = new import_commander.Command();
3926
- program.name("wheel-ai").description("\u57FA\u4E8E AI CLI \u7684\u6301\u7EED\u8FED\u4EE3\u5F00\u53D1\u5DE5\u5177").version("1.0.0");
4060
+ program.name("wheel-ai").description("\u57FA\u4E8E AI CLI \u7684\u6301\u7EED\u8FED\u4EE3\u5F00\u53D1\u5DE5\u5177").version("0.2.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").action(async () => {
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);