@locusai/cli 0.22.8 → 0.22.10

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.
Files changed (2) hide show
  1. package/bin/locus.js +265 -32
  2. package/package.json +2 -2
package/bin/locus.js CHANGED
@@ -1047,7 +1047,7 @@ function detectContainerWorkdir(sandboxName, hostProjectRoot) {
1047
1047
  try {
1048
1048
  const raw = execSync2(`docker sandbox exec ${sandboxName} cat /proc/mounts`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
1049
1049
  if (raw) {
1050
- const systemPrefixes = ["/proc", "/sys", "/dev"];
1050
+ const systemPrefixes = ["/proc", "/sys", "/dev", "/run"];
1051
1051
  const candidates = raw.split(`
1052
1052
  `).map((line) => {
1053
1053
  const fields = line.split(" ");
@@ -1055,7 +1055,7 @@ function detectContainerWorkdir(sandboxName, hostProjectRoot) {
1055
1055
  }).filter((mp) => !!mp && mp !== "/" && !systemPrefixes.some((p) => mp === p || mp.startsWith(`${p}/`)));
1056
1056
  for (const candidate of candidates) {
1057
1057
  try {
1058
- execSync2(`docker sandbox exec ${sandboxName} sh -c "test -d ${JSON.stringify(`${candidate}/`)} || test -f ${JSON.stringify(`${candidate}/package.json`)}"`, { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
1058
+ execSync2(`docker sandbox exec ${sandboxName} sh -c "test -d ${JSON.stringify(`${candidate}/.git`)} || test -f ${JSON.stringify(`${candidate}/package.json`)}"`, { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
1059
1059
  log.debug("Detected container workdir from mount table", {
1060
1060
  hostProjectRoot,
1061
1061
  containerWorkdir: candidate
@@ -1952,7 +1952,8 @@ ${bold2("Initializing Locus...")}
1952
1952
  join6(locusDir, "discussions"),
1953
1953
  join6(locusDir, "artifacts"),
1954
1954
  join6(locusDir, "plans"),
1955
- join6(locusDir, "logs")
1955
+ join6(locusDir, "logs"),
1956
+ join6(locusDir, "run-state")
1956
1957
  ];
1957
1958
  for (const dir of dirs) {
1958
1959
  if (!existsSync6(dir)) {
@@ -1987,6 +1988,8 @@ ${bold2("Initializing Locus...")}
1987
1988
  config.logging = { ...config.logging, ...existing.logging };
1988
1989
  if (existing.sandbox)
1989
1990
  config.sandbox = { ...config.sandbox, ...existing.sandbox };
1991
+ if (existing.packages)
1992
+ config.packages = existing.packages;
1990
1993
  } catch {}
1991
1994
  process.stderr.write(`${green("✓")} Updated config.json (preserved existing settings)
1992
1995
  `);
@@ -10177,22 +10180,32 @@ import {
10177
10180
  writeFileSync as writeFileSync9
10178
10181
  } from "node:fs";
10179
10182
  import { dirname as dirname6, join as join20 } from "node:path";
10180
- function getRunStatePath(projectRoot) {
10181
- return join20(projectRoot, ".locus", "run-state.json");
10183
+ function getRunStateDir(projectRoot) {
10184
+ return join20(projectRoot, ".locus", "run-state");
10182
10185
  }
10183
- function loadRunState(projectRoot) {
10184
- const path = getRunStatePath(projectRoot);
10186
+ function sprintSlug(name) {
10187
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
10188
+ }
10189
+ function getRunStatePath(projectRoot, sprintName) {
10190
+ const dir = getRunStateDir(projectRoot);
10191
+ if (sprintName) {
10192
+ return join20(dir, `${sprintSlug(sprintName)}.json`);
10193
+ }
10194
+ return join20(dir, "_parallel.json");
10195
+ }
10196
+ function loadRunState(projectRoot, sprintName) {
10197
+ const path = getRunStatePath(projectRoot, sprintName);
10185
10198
  if (!existsSync20(path))
10186
10199
  return null;
10187
10200
  try {
10188
10201
  return JSON.parse(readFileSync12(path, "utf-8"));
10189
10202
  } catch {
10190
- getLogger().warn("Corrupted run-state.json, ignoring");
10203
+ getLogger().warn("Corrupted run state file, ignoring");
10191
10204
  return null;
10192
10205
  }
10193
10206
  }
10194
10207
  function saveRunState(projectRoot, state) {
10195
- const path = getRunStatePath(projectRoot);
10208
+ const path = getRunStatePath(projectRoot, state.sprint);
10196
10209
  const dir = dirname6(path);
10197
10210
  if (!existsSync20(dir)) {
10198
10211
  mkdirSync14(dir, { recursive: true });
@@ -10200,8 +10213,8 @@ function saveRunState(projectRoot, state) {
10200
10213
  writeFileSync9(path, `${JSON.stringify(state, null, 2)}
10201
10214
  `, "utf-8");
10202
10215
  }
10203
- function clearRunState(projectRoot) {
10204
- const path = getRunStatePath(projectRoot);
10216
+ function clearRunState(projectRoot, sprintName) {
10217
+ const path = getRunStatePath(projectRoot, sprintName);
10205
10218
  if (existsSync20(path)) {
10206
10219
  unlinkSync5(path);
10207
10220
  }
@@ -10544,9 +10557,10 @@ async function runCommand(projectRoot, args, flags = {}) {
10544
10557
  }
10545
10558
  const config = loadConfig(projectRoot);
10546
10559
  const _log = getLogger();
10560
+ const runRef = { sprintName: undefined };
10547
10561
  const cleanupShutdown = registerShutdownHandlers({
10548
10562
  projectRoot,
10549
- getRunState: () => loadRunState(projectRoot)
10563
+ getRunState: () => loadRunState(projectRoot, runRef.sprintName)
10550
10564
  });
10551
10565
  try {
10552
10566
  const sandboxMode = resolveSandboxMode(config.sandbox, flags);
@@ -10580,11 +10594,11 @@ async function runCommand(projectRoot, args, flags = {}) {
10580
10594
  }
10581
10595
  }
10582
10596
  if (flags.resume) {
10583
- return handleResume(projectRoot, config, sandboxed);
10597
+ return handleResume(projectRoot, config, sandboxed, runRef);
10584
10598
  }
10585
10599
  const issueNumbers = args.filter((a) => /^\d+$/.test(a)).map(Number);
10586
10600
  if (issueNumbers.length === 0) {
10587
- return handleSprintRun(projectRoot, config, flags, sandboxed);
10601
+ return handleSprintRun(projectRoot, config, flags, sandboxed, runRef);
10588
10602
  }
10589
10603
  if (issueNumbers.length === 1) {
10590
10604
  return handleSingleIssue(projectRoot, config, issueNumbers[0], flags, sandboxed);
@@ -10594,7 +10608,7 @@ async function runCommand(projectRoot, args, flags = {}) {
10594
10608
  cleanupShutdown();
10595
10609
  }
10596
10610
  }
10597
- async function handleSprintRun(projectRoot, config, flags, sandboxed) {
10611
+ async function handleSprintRun(projectRoot, config, flags, sandboxed, runRef) {
10598
10612
  const log = getLogger();
10599
10613
  const execution = resolveExecutionContext(config, flags.model);
10600
10614
  if (!config.sprint.active) {
@@ -10605,15 +10619,16 @@ async function handleSprintRun(projectRoot, config, flags, sandboxed) {
10605
10619
  return;
10606
10620
  }
10607
10621
  const sprintName = config.sprint.active;
10622
+ runRef.sprintName = sprintName;
10608
10623
  process.stderr.write(`
10609
10624
  ${bold2("Sprint:")} ${cyan2(sprintName)}
10610
10625
  `);
10611
- const existingState = loadRunState(projectRoot);
10612
- if (existingState && existingState.type === "sprint") {
10626
+ const existingState = loadRunState(projectRoot, sprintName);
10627
+ if (existingState) {
10613
10628
  const stats2 = getRunStats(existingState);
10614
10629
  if (stats2.inProgress > 0 || stats2.pending > 0) {
10615
10630
  process.stderr.write(`
10616
- ${yellow2("⚠")} A sprint run is already in progress.
10631
+ ${yellow2("⚠")} A run for this sprint is already in progress.
10617
10632
  `);
10618
10633
  process.stderr.write(` Use ${bold2("locus run --resume")} to continue.
10619
10634
  `);
@@ -10785,7 +10800,7 @@ ${bold2("Summary:")}
10785
10800
  }
10786
10801
  }
10787
10802
  if (stats.failed === 0) {
10788
- clearRunState(projectRoot);
10803
+ clearRunState(projectRoot, sprintName);
10789
10804
  }
10790
10805
  }
10791
10806
  async function handleSingleIssue(projectRoot, config, issueNumber, flags, sandboxed) {
@@ -10966,14 +10981,19 @@ ${yellow2("⚠")} Failed worktrees preserved for debugging:
10966
10981
  clearRunState(projectRoot);
10967
10982
  }
10968
10983
  }
10969
- async function handleResume(projectRoot, config, sandboxed) {
10984
+ async function handleResume(projectRoot, config, sandboxed, runRef) {
10970
10985
  const execution = resolveExecutionContext(config);
10971
- const state = loadRunState(projectRoot);
10986
+ const sprintName = config.sprint.active ?? undefined;
10987
+ let state = sprintName ? loadRunState(projectRoot, sprintName) : null;
10988
+ if (!state) {
10989
+ state = loadRunState(projectRoot);
10990
+ }
10972
10991
  if (!state) {
10973
10992
  process.stderr.write(`${red2("✗")} No run state found. Nothing to resume.
10974
10993
  `);
10975
10994
  return;
10976
10995
  }
10996
+ runRef.sprintName = state.sprint;
10977
10997
  const stats = getRunStats(state);
10978
10998
  process.stderr.write(`
10979
10999
  ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
@@ -11068,7 +11088,7 @@ ${bold2("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fai
11068
11088
  }
11069
11089
  }
11070
11090
  if (finalStats.failed === 0) {
11071
- clearRunState(projectRoot);
11091
+ clearRunState(projectRoot, state.sprint);
11072
11092
  }
11073
11093
  }
11074
11094
  function sortByOrder2(issues) {
@@ -13018,6 +13038,196 @@ var init_artifacts = __esm(() => {
13018
13038
  init_terminal();
13019
13039
  });
13020
13040
 
13041
+ // src/commands/commit.ts
13042
+ var exports_commit = {};
13043
+ __export(exports_commit, {
13044
+ commitCommand: () => commitCommand
13045
+ });
13046
+ import { execSync as execSync20 } from "node:child_process";
13047
+ function printCommitHelp() {
13048
+ process.stderr.write(`
13049
+ ${bold2("locus commit")} — AI-powered commit message generation
13050
+
13051
+ ${bold2("Usage:")}
13052
+ locus commit ${dim2("# Analyze staged changes and commit")}
13053
+ locus commit --dry-run ${dim2("# Preview message without committing")}
13054
+ locus commit --model <name> ${dim2("# Override AI model")}
13055
+
13056
+ ${bold2("Options:")}
13057
+ --dry-run Show the generated message without committing
13058
+ --model <name> Override the AI model for message generation
13059
+
13060
+ ${bold2("How it works:")}
13061
+ 1. Reads your staged changes (git diff --cached)
13062
+ 2. Reads recent commit history for style matching
13063
+ 3. Uses AI to generate a conventional commit message
13064
+ 4. Appends Co-Authored-By trailer and commits
13065
+
13066
+ ${bold2("Examples:")}
13067
+ locus commit ${dim2("# Stage changes, then run")}
13068
+ locus commit --dry-run ${dim2("# Preview before committing")}
13069
+
13070
+ `);
13071
+ }
13072
+ async function commitCommand(projectRoot, args, flags = {}) {
13073
+ if (args[0] === "help") {
13074
+ printCommitHelp();
13075
+ return;
13076
+ }
13077
+ const config = loadConfig(projectRoot);
13078
+ let stagedDiff;
13079
+ try {
13080
+ stagedDiff = execSync20("git diff --cached", {
13081
+ cwd: projectRoot,
13082
+ encoding: "utf-8",
13083
+ stdio: ["pipe", "pipe", "pipe"]
13084
+ }).trim();
13085
+ } catch {
13086
+ process.stderr.write(`${red2("✗")} Failed to read staged changes.
13087
+ `);
13088
+ return;
13089
+ }
13090
+ if (!stagedDiff) {
13091
+ process.stderr.write(`${red2("✗")} No staged changes. Stage files first with ${bold2("git add")}.
13092
+ `);
13093
+ return;
13094
+ }
13095
+ let stagedStat;
13096
+ try {
13097
+ stagedStat = execSync20("git diff --cached --stat", {
13098
+ cwd: projectRoot,
13099
+ encoding: "utf-8",
13100
+ stdio: ["pipe", "pipe", "pipe"]
13101
+ }).trim();
13102
+ } catch {
13103
+ stagedStat = "";
13104
+ }
13105
+ let recentCommits;
13106
+ try {
13107
+ recentCommits = execSync20("git log --oneline -10", {
13108
+ cwd: projectRoot,
13109
+ encoding: "utf-8",
13110
+ stdio: ["pipe", "pipe", "pipe"]
13111
+ }).trim();
13112
+ } catch {
13113
+ recentCommits = "";
13114
+ }
13115
+ process.stderr.write(`
13116
+ ${bold2("Analyzing staged changes...")}
13117
+ `);
13118
+ process.stderr.write(`${dim2(stagedStat)}
13119
+
13120
+ `);
13121
+ const prompt = buildCommitPrompt(stagedDiff, stagedStat, recentCommits);
13122
+ const model = flags.model ?? config.ai.model;
13123
+ const provider = inferProviderFromModel(model) ?? config.ai.provider;
13124
+ const sandboxName = getModelSandboxName(config.sandbox, model, provider);
13125
+ const result = await runAI({
13126
+ prompt,
13127
+ provider,
13128
+ model,
13129
+ cwd: projectRoot,
13130
+ activity: "Generating commit message",
13131
+ silent: true,
13132
+ noInterrupt: true,
13133
+ sandboxed: !!sandboxName,
13134
+ sandboxName,
13135
+ containerWorkdir: config.sandbox.containerWorkdir
13136
+ });
13137
+ if (!result.success) {
13138
+ process.stderr.write(`${red2("✗")} Failed to generate commit message: ${result.error}
13139
+ `);
13140
+ return;
13141
+ }
13142
+ const commitMessage = extractCommitMessage(result.output);
13143
+ if (!commitMessage) {
13144
+ process.stderr.write(`${red2("✗")} Could not extract a commit message from AI response.
13145
+ `);
13146
+ return;
13147
+ }
13148
+ const fullMessage = `${commitMessage}
13149
+
13150
+ Co-Authored-By: LocusAgent <agent@locusai.team>`;
13151
+ process.stderr.write(`${bold2("Generated commit message:")}
13152
+ `);
13153
+ process.stderr.write(`${cyan2("─".repeat(50))}
13154
+ `);
13155
+ process.stderr.write(`${fullMessage}
13156
+ `);
13157
+ process.stderr.write(`${cyan2("─".repeat(50))}
13158
+
13159
+ `);
13160
+ if (flags.dryRun) {
13161
+ process.stderr.write(`${yellow2("⚠")} ${bold2("Dry run")} — no commit created.
13162
+
13163
+ `);
13164
+ return;
13165
+ }
13166
+ try {
13167
+ execSync20("git commit -F -", {
13168
+ input: fullMessage,
13169
+ cwd: projectRoot,
13170
+ encoding: "utf-8",
13171
+ stdio: ["pipe", "pipe", "pipe"]
13172
+ });
13173
+ process.stderr.write(`${green("✓")} Committed successfully.
13174
+
13175
+ `);
13176
+ } catch (e) {
13177
+ process.stderr.write(`${red2("✗")} Commit failed: ${e.message}
13178
+ `);
13179
+ }
13180
+ }
13181
+ function buildCommitPrompt(diff, stat, recentCommits) {
13182
+ const maxDiffLength = 15000;
13183
+ const truncatedDiff = diff.length > maxDiffLength ? `${diff.slice(0, maxDiffLength)}
13184
+
13185
+ ... [diff truncated — ${diff.length - maxDiffLength} chars omitted]` : diff;
13186
+ let prompt = `You are a commit message generator. Analyze the following staged git changes and generate a single, concise conventional commit message.
13187
+
13188
+ Rules:
13189
+ - Use conventional commit format: type(scope): description
13190
+ - Types: feat, fix, chore, refactor, docs, test, style, perf, ci, build
13191
+ - Keep the first line under 72 characters
13192
+ - Add a body paragraph (separated by blank line) ONLY if the changes are complex enough to warrant explanation
13193
+ - Do NOT include any markdown formatting, code blocks, or extra commentary
13194
+ - Output ONLY the commit message text — nothing else
13195
+
13196
+ `;
13197
+ if (recentCommits) {
13198
+ prompt += `Recent commits (for style reference):
13199
+ ${recentCommits}
13200
+
13201
+ `;
13202
+ }
13203
+ prompt += `File summary:
13204
+ ${stat}
13205
+
13206
+ Diff:
13207
+ ${truncatedDiff}`;
13208
+ return prompt;
13209
+ }
13210
+ function extractCommitMessage(output) {
13211
+ const trimmed = output.trim();
13212
+ if (!trimmed)
13213
+ return null;
13214
+ let cleaned = trimmed;
13215
+ if (cleaned.startsWith("```")) {
13216
+ cleaned = cleaned.replace(/^```[^\n]*\n?/, "").replace(/\n?```$/, "");
13217
+ }
13218
+ if (cleaned.startsWith('"') && cleaned.endsWith('"') || cleaned.startsWith("'") && cleaned.endsWith("'")) {
13219
+ cleaned = cleaned.slice(1, -1);
13220
+ }
13221
+ return cleaned.trim() || null;
13222
+ }
13223
+ var init_commit = __esm(() => {
13224
+ init_run_ai();
13225
+ init_ai_models();
13226
+ init_config();
13227
+ init_sandbox();
13228
+ init_terminal();
13229
+ });
13230
+
13021
13231
  // src/commands/sandbox.ts
13022
13232
  var exports_sandbox2 = {};
13023
13233
  __export(exports_sandbox2, {
@@ -13025,7 +13235,7 @@ __export(exports_sandbox2, {
13025
13235
  parseSandboxLogsArgs: () => parseSandboxLogsArgs,
13026
13236
  parseSandboxInstallArgs: () => parseSandboxInstallArgs
13027
13237
  });
13028
- import { execSync as execSync20, spawn as spawn7 } from "node:child_process";
13238
+ import { execSync as execSync21, spawn as spawn7 } from "node:child_process";
13029
13239
  import { createHash } from "node:crypto";
13030
13240
  import { existsSync as existsSync26, readFileSync as readFileSync17 } from "node:fs";
13031
13241
  import { basename as basename4, join as join26 } from "node:path";
@@ -13261,7 +13471,7 @@ function handleRemove(projectRoot) {
13261
13471
  process.stderr.write(`Removing sandbox ${bold2(sandboxName)}...
13262
13472
  `);
13263
13473
  try {
13264
- execSync20(`docker sandbox rm ${sandboxName}`, {
13474
+ execSync21(`docker sandbox rm ${sandboxName}`, {
13265
13475
  encoding: "utf-8",
13266
13476
  stdio: ["pipe", "pipe", "pipe"],
13267
13477
  timeout: 15000
@@ -13553,7 +13763,7 @@ function getInstallCommand(pm) {
13553
13763
  function verifyBinEntries(sandboxName, workdir) {
13554
13764
  try {
13555
13765
  const binDir = `${workdir}/node_modules/.bin/`;
13556
- const result = execSync20(`docker sandbox exec --privileged ${sandboxName} sh -c ${JSON.stringify(`ls ${JSON.stringify(binDir)} 2>/dev/null | head -20`)}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
13766
+ const result = execSync21(`docker sandbox exec --privileged ${sandboxName} sh -c ${JSON.stringify(`ls ${JSON.stringify(binDir)} 2>/dev/null | head -20`)}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
13557
13767
  if (!result) {
13558
13768
  process.stderr.write(`${yellow2("⚠")} node_modules/.bin is empty — binaries like biome may not be available.
13559
13769
  `);
@@ -13766,7 +13976,7 @@ function runInteractiveCommand(command, args) {
13766
13976
  }
13767
13977
  async function createProviderSandbox(provider, sandboxName, projectRoot) {
13768
13978
  try {
13769
- execSync20(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
13979
+ execSync21(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
13770
13980
  stdio: ["pipe", "pipe", "pipe"],
13771
13981
  timeout: 120000
13772
13982
  });
@@ -13781,7 +13991,7 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
13781
13991
  }
13782
13992
  async function ensurePackageManagerInSandbox(sandboxName, pm) {
13783
13993
  try {
13784
- execSync20(`docker sandbox exec --privileged ${sandboxName} which ${pm}`, {
13994
+ execSync21(`docker sandbox exec --privileged ${sandboxName} which ${pm}`, {
13785
13995
  stdio: ["pipe", "pipe", "pipe"],
13786
13996
  timeout: 5000
13787
13997
  });
@@ -13790,7 +14000,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
13790
14000
  process.stderr.write(`Installing ${bold2(pm)} in sandbox...
13791
14001
  `);
13792
14002
  try {
13793
- execSync20(`docker sandbox exec --privileged ${sandboxName} npm install -g ${npmPkg}`, {
14003
+ execSync21(`docker sandbox exec --privileged ${sandboxName} npm install -g ${npmPkg}`, {
13794
14004
  stdio: "inherit",
13795
14005
  timeout: 120000
13796
14006
  });
@@ -13802,7 +14012,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
13802
14012
  }
13803
14013
  async function ensureCodexInSandbox(sandboxName) {
13804
14014
  try {
13805
- execSync20(`docker sandbox exec --privileged ${sandboxName} which codex`, {
14015
+ execSync21(`docker sandbox exec --privileged ${sandboxName} which codex`, {
13806
14016
  stdio: ["pipe", "pipe", "pipe"],
13807
14017
  timeout: 5000
13808
14018
  });
@@ -13810,7 +14020,7 @@ async function ensureCodexInSandbox(sandboxName) {
13810
14020
  process.stderr.write(`Installing codex in sandbox...
13811
14021
  `);
13812
14022
  try {
13813
- execSync20(`docker sandbox exec --privileged ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
14023
+ execSync21(`docker sandbox exec --privileged ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
13814
14024
  } catch {
13815
14025
  process.stderr.write(`${red2("✗")} Failed to install codex in sandbox.
13816
14026
  `);
@@ -13819,7 +14029,7 @@ async function ensureCodexInSandbox(sandboxName) {
13819
14029
  }
13820
14030
  function isSandboxAlive(name) {
13821
14031
  try {
13822
- const output = execSync20("docker sandbox ls", {
14032
+ const output = execSync21("docker sandbox ls", {
13823
14033
  encoding: "utf-8",
13824
14034
  stdio: ["pipe", "pipe", "pipe"],
13825
14035
  timeout: 5000
@@ -13862,8 +14072,17 @@ function getCliVersion() {
13862
14072
  }
13863
14073
  }
13864
14074
  var VERSION = getCliVersion();
14075
+ function normalizeArgs(args) {
14076
+ return args.map((arg) => {
14077
+ if (arg.startsWith("--") || !arg.startsWith("-"))
14078
+ return arg;
14079
+ if (arg.length <= 2)
14080
+ return arg;
14081
+ return `-${arg}`;
14082
+ });
14083
+ }
13865
14084
  function parseArgs(argv) {
13866
- const rawArgs = argv.slice(2);
14085
+ const rawArgs = normalizeArgs(argv.slice(2));
13867
14086
  const flags = {
13868
14087
  debug: false,
13869
14088
  help: false,
@@ -14020,6 +14239,7 @@ ${bold2("Commands:")}
14020
14239
  ${cyan2("discuss")} AI-powered architectural discussions
14021
14240
  ${cyan2("artifacts")} View and manage AI-generated artifacts
14022
14241
  ${cyan2("status")} Dashboard view of current state
14242
+ ${cyan2("commit")} AI-powered commit message generation
14023
14243
  ${cyan2("config")} View and manage settings
14024
14244
  ${cyan2("logs")} View, tail, and manage execution logs
14025
14245
  ${cyan2("create")} ${dim2("<name>")} Scaffold a new community package
@@ -14067,6 +14287,7 @@ function requiresSandboxSync(command, args, flags) {
14067
14287
  case "run":
14068
14288
  case "review":
14069
14289
  case "iterate":
14290
+ case "commit":
14070
14291
  return true;
14071
14292
  case "exec":
14072
14293
  return args[0] !== "sessions" && args[0] !== "help";
@@ -14307,6 +14528,15 @@ async function main() {
14307
14528
  await artifactsCommand2(projectRoot, artifactsArgs);
14308
14529
  break;
14309
14530
  }
14531
+ case "commit": {
14532
+ const { commitCommand: commitCommand2 } = await Promise.resolve().then(() => (init_commit(), exports_commit));
14533
+ const commitArgs = parsed.flags.help ? ["help"] : parsed.args;
14534
+ await commitCommand2(projectRoot, commitArgs, {
14535
+ dryRun: parsed.flags.dryRun,
14536
+ model: parsed.flags.model
14537
+ });
14538
+ break;
14539
+ }
14310
14540
  case "sandbox": {
14311
14541
  const { sandboxCommand: sandboxCommand2 } = await Promise.resolve().then(() => (init_sandbox2(), exports_sandbox2));
14312
14542
  const sandboxArgs = parsed.flags.help ? ["help"] : parsed.args;
@@ -14343,3 +14573,6 @@ ${dim2(error.stack)}
14343
14573
  }
14344
14574
  process.exit(1);
14345
14575
  });
14576
+ export {
14577
+ normalizeArgs
14578
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/cli",
3
- "version": "0.22.8",
3
+ "version": "0.22.10",
4
4
  "description": "GitHub-native AI engineering assistant",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,7 +36,7 @@
36
36
  "license": "MIT",
37
37
  "dependencies": {},
38
38
  "devDependencies": {
39
- "@locusai/sdk": "^0.22.8",
39
+ "@locusai/sdk": "^0.22.10",
40
40
  "@types/bun": "latest",
41
41
  "typescript": "^5.8.3"
42
42
  },