@locusai/cli 0.22.9 → 0.22.11

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 +335 -75
  2. package/package.json +2 -2
package/bin/locus.js CHANGED
@@ -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
  `);
@@ -4893,39 +4896,49 @@ class ClaudeRunner {
4893
4896
  stdio: ["pipe", "pipe", "pipe"],
4894
4897
  env
4895
4898
  });
4899
+ let flushLineBuffer = null;
4896
4900
  if (options.verbose) {
4897
4901
  let lineBuffer = "";
4898
4902
  const seenToolIds = new Set;
4903
+ const processLine = (line) => {
4904
+ if (!line.trim())
4905
+ return;
4906
+ try {
4907
+ const event = JSON.parse(line);
4908
+ if (event.type === "assistant" && event.message?.content) {
4909
+ for (const item of event.message.content) {
4910
+ if (item.type === "tool_use" && item.id && !seenToolIds.has(item.id)) {
4911
+ seenToolIds.add(item.id);
4912
+ options.onToolActivity?.(formatToolCall(item.name ?? "", item.input ?? {}));
4913
+ }
4914
+ }
4915
+ } else if (event.type === "result") {
4916
+ const text = event.result ?? "";
4917
+ output = text;
4918
+ options.onOutput?.(text);
4919
+ }
4920
+ } catch {
4921
+ const newLine = `${line}
4922
+ `;
4923
+ output += newLine;
4924
+ options.onOutput?.(newLine);
4925
+ }
4926
+ };
4899
4927
  this.process.stdout?.on("data", (chunk) => {
4900
4928
  lineBuffer += chunk.toString();
4901
4929
  const lines = lineBuffer.split(`
4902
4930
  `);
4903
4931
  lineBuffer = lines.pop() ?? "";
4904
4932
  for (const line of lines) {
4905
- if (!line.trim())
4906
- continue;
4907
- try {
4908
- const event = JSON.parse(line);
4909
- if (event.type === "assistant" && event.message?.content) {
4910
- for (const item of event.message.content) {
4911
- if (item.type === "tool_use" && item.id && !seenToolIds.has(item.id)) {
4912
- seenToolIds.add(item.id);
4913
- options.onToolActivity?.(formatToolCall(item.name ?? "", item.input ?? {}));
4914
- }
4915
- }
4916
- } else if (event.type === "result") {
4917
- const text = event.result ?? "";
4918
- output = text;
4919
- options.onOutput?.(text);
4920
- }
4921
- } catch {
4922
- const newLine = `${line}
4923
- `;
4924
- output += newLine;
4925
- options.onOutput?.(newLine);
4926
- }
4933
+ processLine(line);
4927
4934
  }
4928
4935
  });
4936
+ flushLineBuffer = () => {
4937
+ if (lineBuffer.trim()) {
4938
+ processLine(lineBuffer);
4939
+ lineBuffer = "";
4940
+ }
4941
+ };
4929
4942
  } else {
4930
4943
  this.process.stdout?.on("data", (chunk) => {
4931
4944
  const text = chunk.toString();
@@ -4940,6 +4953,7 @@ class ClaudeRunner {
4940
4953
  });
4941
4954
  this.process.on("close", (code) => {
4942
4955
  this.process = null;
4956
+ flushLineBuffer?.();
4943
4957
  if (this.aborted) {
4944
4958
  resolve2({
4945
4959
  success: false,
@@ -5314,39 +5328,49 @@ class SandboxedClaudeRunner {
5314
5328
  stdio: ["ignore", "pipe", "pipe"],
5315
5329
  env: process.env
5316
5330
  });
5331
+ let flushLineBuffer = null;
5317
5332
  if (options.verbose) {
5318
5333
  let lineBuffer = "";
5319
5334
  const seenToolIds = new Set;
5335
+ const processLine = (line) => {
5336
+ if (!line.trim())
5337
+ return;
5338
+ try {
5339
+ const event = JSON.parse(line);
5340
+ if (event.type === "assistant" && event.message?.content) {
5341
+ for (const item of event.message.content) {
5342
+ if (item.type === "tool_use" && item.id && !seenToolIds.has(item.id)) {
5343
+ seenToolIds.add(item.id);
5344
+ options.onToolActivity?.(formatToolCall2(item.name ?? "", item.input ?? {}));
5345
+ }
5346
+ }
5347
+ } else if (event.type === "result") {
5348
+ const text = event.result ?? "";
5349
+ output = text;
5350
+ options.onOutput?.(text);
5351
+ }
5352
+ } catch {
5353
+ const newLine = `${line}
5354
+ `;
5355
+ output += newLine;
5356
+ options.onOutput?.(newLine);
5357
+ }
5358
+ };
5320
5359
  this.process.stdout?.on("data", (chunk) => {
5321
5360
  lineBuffer += chunk.toString();
5322
5361
  const lines = lineBuffer.split(`
5323
5362
  `);
5324
5363
  lineBuffer = lines.pop() ?? "";
5325
5364
  for (const line of lines) {
5326
- if (!line.trim())
5327
- continue;
5328
- try {
5329
- const event = JSON.parse(line);
5330
- if (event.type === "assistant" && event.message?.content) {
5331
- for (const item of event.message.content) {
5332
- if (item.type === "tool_use" && item.id && !seenToolIds.has(item.id)) {
5333
- seenToolIds.add(item.id);
5334
- options.onToolActivity?.(formatToolCall2(item.name ?? "", item.input ?? {}));
5335
- }
5336
- }
5337
- } else if (event.type === "result") {
5338
- const text = event.result ?? "";
5339
- output = text;
5340
- options.onOutput?.(text);
5341
- }
5342
- } catch {
5343
- const newLine = `${line}
5344
- `;
5345
- output += newLine;
5346
- options.onOutput?.(newLine);
5347
- }
5365
+ processLine(line);
5348
5366
  }
5349
5367
  });
5368
+ flushLineBuffer = () => {
5369
+ if (lineBuffer.trim()) {
5370
+ processLine(lineBuffer);
5371
+ lineBuffer = "";
5372
+ }
5373
+ };
5350
5374
  } else {
5351
5375
  this.process.stdout?.on("data", (chunk) => {
5352
5376
  const text = chunk.toString();
@@ -5361,6 +5385,7 @@ class SandboxedClaudeRunner {
5361
5385
  });
5362
5386
  this.process.on("close", (code) => {
5363
5387
  this.process = null;
5388
+ flushLineBuffer?.();
5364
5389
  if (this.aborted) {
5365
5390
  resolve2({
5366
5391
  success: false,
@@ -10177,22 +10202,32 @@ import {
10177
10202
  writeFileSync as writeFileSync9
10178
10203
  } from "node:fs";
10179
10204
  import { dirname as dirname6, join as join20 } from "node:path";
10180
- function getRunStatePath(projectRoot) {
10181
- return join20(projectRoot, ".locus", "run-state.json");
10205
+ function getRunStateDir(projectRoot) {
10206
+ return join20(projectRoot, ".locus", "run-state");
10207
+ }
10208
+ function sprintSlug(name) {
10209
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
10210
+ }
10211
+ function getRunStatePath(projectRoot, sprintName) {
10212
+ const dir = getRunStateDir(projectRoot);
10213
+ if (sprintName) {
10214
+ return join20(dir, `${sprintSlug(sprintName)}.json`);
10215
+ }
10216
+ return join20(dir, "_parallel.json");
10182
10217
  }
10183
- function loadRunState(projectRoot) {
10184
- const path = getRunStatePath(projectRoot);
10218
+ function loadRunState(projectRoot, sprintName) {
10219
+ const path = getRunStatePath(projectRoot, sprintName);
10185
10220
  if (!existsSync20(path))
10186
10221
  return null;
10187
10222
  try {
10188
10223
  return JSON.parse(readFileSync12(path, "utf-8"));
10189
10224
  } catch {
10190
- getLogger().warn("Corrupted run-state.json, ignoring");
10225
+ getLogger().warn("Corrupted run state file, ignoring");
10191
10226
  return null;
10192
10227
  }
10193
10228
  }
10194
10229
  function saveRunState(projectRoot, state) {
10195
- const path = getRunStatePath(projectRoot);
10230
+ const path = getRunStatePath(projectRoot, state.sprint);
10196
10231
  const dir = dirname6(path);
10197
10232
  if (!existsSync20(dir)) {
10198
10233
  mkdirSync14(dir, { recursive: true });
@@ -10200,8 +10235,8 @@ function saveRunState(projectRoot, state) {
10200
10235
  writeFileSync9(path, `${JSON.stringify(state, null, 2)}
10201
10236
  `, "utf-8");
10202
10237
  }
10203
- function clearRunState(projectRoot) {
10204
- const path = getRunStatePath(projectRoot);
10238
+ function clearRunState(projectRoot, sprintName) {
10239
+ const path = getRunStatePath(projectRoot, sprintName);
10205
10240
  if (existsSync20(path)) {
10206
10241
  unlinkSync5(path);
10207
10242
  }
@@ -10503,7 +10538,12 @@ function resolveExecutionContext(config, modelOverride) {
10503
10538
  const model = modelOverride ?? config.ai.model;
10504
10539
  const provider = inferProviderFromModel(model) ?? config.ai.provider;
10505
10540
  const sandboxName = getModelSandboxName(config.sandbox, model, provider);
10506
- return { provider, model, sandboxName, containerWorkdir: config.sandbox.containerWorkdir };
10541
+ return {
10542
+ provider,
10543
+ model,
10544
+ sandboxName,
10545
+ containerWorkdir: config.sandbox.containerWorkdir
10546
+ };
10507
10547
  }
10508
10548
  function printRunHelp() {
10509
10549
  process.stderr.write(`
@@ -10544,9 +10584,10 @@ async function runCommand(projectRoot, args, flags = {}) {
10544
10584
  }
10545
10585
  const config = loadConfig(projectRoot);
10546
10586
  const _log = getLogger();
10587
+ const runRef = { sprintName: undefined };
10547
10588
  const cleanupShutdown = registerShutdownHandlers({
10548
10589
  projectRoot,
10549
- getRunState: () => loadRunState(projectRoot)
10590
+ getRunState: () => loadRunState(projectRoot, runRef.sprintName)
10550
10591
  });
10551
10592
  try {
10552
10593
  const sandboxMode = resolveSandboxMode(config.sandbox, flags);
@@ -10580,11 +10621,11 @@ async function runCommand(projectRoot, args, flags = {}) {
10580
10621
  }
10581
10622
  }
10582
10623
  if (flags.resume) {
10583
- return handleResume(projectRoot, config, sandboxed);
10624
+ return handleResume(projectRoot, config, sandboxed, runRef);
10584
10625
  }
10585
10626
  const issueNumbers = args.filter((a) => /^\d+$/.test(a)).map(Number);
10586
10627
  if (issueNumbers.length === 0) {
10587
- return handleSprintRun(projectRoot, config, flags, sandboxed);
10628
+ return handleSprintRun(projectRoot, config, flags, sandboxed, runRef);
10588
10629
  }
10589
10630
  if (issueNumbers.length === 1) {
10590
10631
  return handleSingleIssue(projectRoot, config, issueNumbers[0], flags, sandboxed);
@@ -10594,7 +10635,7 @@ async function runCommand(projectRoot, args, flags = {}) {
10594
10635
  cleanupShutdown();
10595
10636
  }
10596
10637
  }
10597
- async function handleSprintRun(projectRoot, config, flags, sandboxed) {
10638
+ async function handleSprintRun(projectRoot, config, flags, sandboxed, runRef) {
10598
10639
  const log = getLogger();
10599
10640
  const execution = resolveExecutionContext(config, flags.model);
10600
10641
  if (!config.sprint.active) {
@@ -10605,15 +10646,16 @@ async function handleSprintRun(projectRoot, config, flags, sandboxed) {
10605
10646
  return;
10606
10647
  }
10607
10648
  const sprintName = config.sprint.active;
10649
+ runRef.sprintName = sprintName;
10608
10650
  process.stderr.write(`
10609
10651
  ${bold2("Sprint:")} ${cyan2(sprintName)}
10610
10652
  `);
10611
- const existingState = loadRunState(projectRoot);
10612
- if (existingState && existingState.type === "sprint") {
10653
+ const existingState = loadRunState(projectRoot, sprintName);
10654
+ if (existingState) {
10613
10655
  const stats2 = getRunStats(existingState);
10614
10656
  if (stats2.inProgress > 0 || stats2.pending > 0) {
10615
10657
  process.stderr.write(`
10616
- ${yellow2("⚠")} A sprint run is already in progress.
10658
+ ${yellow2("⚠")} A run for this sprint is already in progress.
10617
10659
  `);
10618
10660
  process.stderr.write(` Use ${bold2("locus run --resume")} to continue.
10619
10661
  `);
@@ -10785,7 +10827,7 @@ ${bold2("Summary:")}
10785
10827
  }
10786
10828
  }
10787
10829
  if (stats.failed === 0) {
10788
- clearRunState(projectRoot);
10830
+ clearRunState(projectRoot, sprintName);
10789
10831
  }
10790
10832
  }
10791
10833
  async function handleSingleIssue(projectRoot, config, issueNumber, flags, sandboxed) {
@@ -10966,14 +11008,19 @@ ${yellow2("⚠")} Failed worktrees preserved for debugging:
10966
11008
  clearRunState(projectRoot);
10967
11009
  }
10968
11010
  }
10969
- async function handleResume(projectRoot, config, sandboxed) {
11011
+ async function handleResume(projectRoot, config, sandboxed, runRef) {
10970
11012
  const execution = resolveExecutionContext(config);
10971
- const state = loadRunState(projectRoot);
11013
+ const sprintName = config.sprint.active ?? undefined;
11014
+ let state = sprintName ? loadRunState(projectRoot, sprintName) : null;
11015
+ if (!state) {
11016
+ state = loadRunState(projectRoot);
11017
+ }
10972
11018
  if (!state) {
10973
11019
  process.stderr.write(`${red2("✗")} No run state found. Nothing to resume.
10974
11020
  `);
10975
11021
  return;
10976
11022
  }
11023
+ runRef.sprintName = state.sprint;
10977
11024
  const stats = getRunStats(state);
10978
11025
  process.stderr.write(`
10979
11026
  ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
@@ -11068,7 +11115,7 @@ ${bold2("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fai
11068
11115
  }
11069
11116
  }
11070
11117
  if (finalStats.failed === 0) {
11071
- clearRunState(projectRoot);
11118
+ clearRunState(projectRoot, state.sprint);
11072
11119
  }
11073
11120
  }
11074
11121
  function sortByOrder2(issues) {
@@ -13018,6 +13065,196 @@ var init_artifacts = __esm(() => {
13018
13065
  init_terminal();
13019
13066
  });
13020
13067
 
13068
+ // src/commands/commit.ts
13069
+ var exports_commit = {};
13070
+ __export(exports_commit, {
13071
+ commitCommand: () => commitCommand
13072
+ });
13073
+ import { execSync as execSync20 } from "node:child_process";
13074
+ function printCommitHelp() {
13075
+ process.stderr.write(`
13076
+ ${bold2("locus commit")} — AI-powered commit message generation
13077
+
13078
+ ${bold2("Usage:")}
13079
+ locus commit ${dim2("# Analyze staged changes and commit")}
13080
+ locus commit --dry-run ${dim2("# Preview message without committing")}
13081
+ locus commit --model <name> ${dim2("# Override AI model")}
13082
+
13083
+ ${bold2("Options:")}
13084
+ --dry-run Show the generated message without committing
13085
+ --model <name> Override the AI model for message generation
13086
+
13087
+ ${bold2("How it works:")}
13088
+ 1. Reads your staged changes (git diff --cached)
13089
+ 2. Reads recent commit history for style matching
13090
+ 3. Uses AI to generate a conventional commit message
13091
+ 4. Appends Co-Authored-By trailer and commits
13092
+
13093
+ ${bold2("Examples:")}
13094
+ locus commit ${dim2("# Stage changes, then run")}
13095
+ locus commit --dry-run ${dim2("# Preview before committing")}
13096
+
13097
+ `);
13098
+ }
13099
+ async function commitCommand(projectRoot, args, flags = {}) {
13100
+ if (args[0] === "help") {
13101
+ printCommitHelp();
13102
+ return;
13103
+ }
13104
+ const config = loadConfig(projectRoot);
13105
+ let stagedDiff;
13106
+ try {
13107
+ stagedDiff = execSync20("git diff --cached", {
13108
+ cwd: projectRoot,
13109
+ encoding: "utf-8",
13110
+ stdio: ["pipe", "pipe", "pipe"]
13111
+ }).trim();
13112
+ } catch {
13113
+ process.stderr.write(`${red2("✗")} Failed to read staged changes.
13114
+ `);
13115
+ return;
13116
+ }
13117
+ if (!stagedDiff) {
13118
+ process.stderr.write(`${red2("✗")} No staged changes. Stage files first with ${bold2("git add")}.
13119
+ `);
13120
+ return;
13121
+ }
13122
+ let stagedStat;
13123
+ try {
13124
+ stagedStat = execSync20("git diff --cached --stat", {
13125
+ cwd: projectRoot,
13126
+ encoding: "utf-8",
13127
+ stdio: ["pipe", "pipe", "pipe"]
13128
+ }).trim();
13129
+ } catch {
13130
+ stagedStat = "";
13131
+ }
13132
+ let recentCommits;
13133
+ try {
13134
+ recentCommits = execSync20("git log --oneline -10", {
13135
+ cwd: projectRoot,
13136
+ encoding: "utf-8",
13137
+ stdio: ["pipe", "pipe", "pipe"]
13138
+ }).trim();
13139
+ } catch {
13140
+ recentCommits = "";
13141
+ }
13142
+ process.stderr.write(`
13143
+ ${bold2("Analyzing staged changes...")}
13144
+ `);
13145
+ process.stderr.write(`${dim2(stagedStat)}
13146
+
13147
+ `);
13148
+ const prompt = buildCommitPrompt(stagedDiff, stagedStat, recentCommits);
13149
+ const model = flags.model ?? config.ai.model;
13150
+ const provider = inferProviderFromModel(model) ?? config.ai.provider;
13151
+ const sandboxName = getModelSandboxName(config.sandbox, model, provider);
13152
+ const result = await runAI({
13153
+ prompt,
13154
+ provider,
13155
+ model,
13156
+ cwd: projectRoot,
13157
+ activity: "Generating commit message",
13158
+ silent: true,
13159
+ noInterrupt: true,
13160
+ sandboxed: !!sandboxName,
13161
+ sandboxName,
13162
+ containerWorkdir: config.sandbox.containerWorkdir
13163
+ });
13164
+ if (!result.success) {
13165
+ process.stderr.write(`${red2("✗")} Failed to generate commit message: ${result.error}
13166
+ `);
13167
+ return;
13168
+ }
13169
+ const commitMessage = extractCommitMessage(result.output);
13170
+ if (!commitMessage) {
13171
+ process.stderr.write(`${red2("✗")} Could not extract a commit message from AI response.
13172
+ `);
13173
+ return;
13174
+ }
13175
+ const fullMessage = `${commitMessage}
13176
+
13177
+ Co-Authored-By: LocusAgent <agent@locusai.team>`;
13178
+ process.stderr.write(`${bold2("Generated commit message:")}
13179
+ `);
13180
+ process.stderr.write(`${cyan2("─".repeat(50))}
13181
+ `);
13182
+ process.stderr.write(`${fullMessage}
13183
+ `);
13184
+ process.stderr.write(`${cyan2("─".repeat(50))}
13185
+
13186
+ `);
13187
+ if (flags.dryRun) {
13188
+ process.stderr.write(`${yellow2("⚠")} ${bold2("Dry run")} — no commit created.
13189
+
13190
+ `);
13191
+ return;
13192
+ }
13193
+ try {
13194
+ execSync20("git commit -F -", {
13195
+ input: fullMessage,
13196
+ cwd: projectRoot,
13197
+ encoding: "utf-8",
13198
+ stdio: ["pipe", "pipe", "pipe"]
13199
+ });
13200
+ process.stderr.write(`${green("✓")} Committed successfully.
13201
+
13202
+ `);
13203
+ } catch (e) {
13204
+ process.stderr.write(`${red2("✗")} Commit failed: ${e.message}
13205
+ `);
13206
+ }
13207
+ }
13208
+ function buildCommitPrompt(diff, stat, recentCommits) {
13209
+ const maxDiffLength = 15000;
13210
+ const truncatedDiff = diff.length > maxDiffLength ? `${diff.slice(0, maxDiffLength)}
13211
+
13212
+ ... [diff truncated — ${diff.length - maxDiffLength} chars omitted]` : diff;
13213
+ let prompt = `You are a commit message generator. Analyze the following staged git changes and generate a single, concise conventional commit message.
13214
+
13215
+ Rules:
13216
+ - Use conventional commit format: type(scope): description
13217
+ - Types: feat, fix, chore, refactor, docs, test, style, perf, ci, build
13218
+ - Keep the first line under 72 characters
13219
+ - Add a body paragraph (separated by blank line) ONLY if the changes are complex enough to warrant explanation
13220
+ - Do NOT include any markdown formatting, code blocks, or extra commentary
13221
+ - Output ONLY the commit message text — nothing else
13222
+
13223
+ `;
13224
+ if (recentCommits) {
13225
+ prompt += `Recent commits (for style reference):
13226
+ ${recentCommits}
13227
+
13228
+ `;
13229
+ }
13230
+ prompt += `File summary:
13231
+ ${stat}
13232
+
13233
+ Diff:
13234
+ ${truncatedDiff}`;
13235
+ return prompt;
13236
+ }
13237
+ function extractCommitMessage(output) {
13238
+ const trimmed = output.trim();
13239
+ if (!trimmed)
13240
+ return null;
13241
+ let cleaned = trimmed;
13242
+ if (cleaned.startsWith("```")) {
13243
+ cleaned = cleaned.replace(/^```[^\n]*\n?/, "").replace(/\n?```$/, "");
13244
+ }
13245
+ if (cleaned.startsWith('"') && cleaned.endsWith('"') || cleaned.startsWith("'") && cleaned.endsWith("'")) {
13246
+ cleaned = cleaned.slice(1, -1);
13247
+ }
13248
+ return cleaned.trim() || null;
13249
+ }
13250
+ var init_commit = __esm(() => {
13251
+ init_run_ai();
13252
+ init_ai_models();
13253
+ init_config();
13254
+ init_sandbox();
13255
+ init_terminal();
13256
+ });
13257
+
13021
13258
  // src/commands/sandbox.ts
13022
13259
  var exports_sandbox2 = {};
13023
13260
  __export(exports_sandbox2, {
@@ -13025,7 +13262,7 @@ __export(exports_sandbox2, {
13025
13262
  parseSandboxLogsArgs: () => parseSandboxLogsArgs,
13026
13263
  parseSandboxInstallArgs: () => parseSandboxInstallArgs
13027
13264
  });
13028
- import { execSync as execSync20, spawn as spawn7 } from "node:child_process";
13265
+ import { execSync as execSync21, spawn as spawn7 } from "node:child_process";
13029
13266
  import { createHash } from "node:crypto";
13030
13267
  import { existsSync as existsSync26, readFileSync as readFileSync17 } from "node:fs";
13031
13268
  import { basename as basename4, join as join26 } from "node:path";
@@ -13261,7 +13498,7 @@ function handleRemove(projectRoot) {
13261
13498
  process.stderr.write(`Removing sandbox ${bold2(sandboxName)}...
13262
13499
  `);
13263
13500
  try {
13264
- execSync20(`docker sandbox rm ${sandboxName}`, {
13501
+ execSync21(`docker sandbox rm ${sandboxName}`, {
13265
13502
  encoding: "utf-8",
13266
13503
  stdio: ["pipe", "pipe", "pipe"],
13267
13504
  timeout: 15000
@@ -13553,7 +13790,7 @@ function getInstallCommand(pm) {
13553
13790
  function verifyBinEntries(sandboxName, workdir) {
13554
13791
  try {
13555
13792
  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();
13793
+ 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
13794
  if (!result) {
13558
13795
  process.stderr.write(`${yellow2("⚠")} node_modules/.bin is empty — binaries like biome may not be available.
13559
13796
  `);
@@ -13766,7 +14003,7 @@ function runInteractiveCommand(command, args) {
13766
14003
  }
13767
14004
  async function createProviderSandbox(provider, sandboxName, projectRoot) {
13768
14005
  try {
13769
- execSync20(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
14006
+ execSync21(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
13770
14007
  stdio: ["pipe", "pipe", "pipe"],
13771
14008
  timeout: 120000
13772
14009
  });
@@ -13781,7 +14018,7 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
13781
14018
  }
13782
14019
  async function ensurePackageManagerInSandbox(sandboxName, pm) {
13783
14020
  try {
13784
- execSync20(`docker sandbox exec --privileged ${sandboxName} which ${pm}`, {
14021
+ execSync21(`docker sandbox exec --privileged ${sandboxName} which ${pm}`, {
13785
14022
  stdio: ["pipe", "pipe", "pipe"],
13786
14023
  timeout: 5000
13787
14024
  });
@@ -13790,7 +14027,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
13790
14027
  process.stderr.write(`Installing ${bold2(pm)} in sandbox...
13791
14028
  `);
13792
14029
  try {
13793
- execSync20(`docker sandbox exec --privileged ${sandboxName} npm install -g ${npmPkg}`, {
14030
+ execSync21(`docker sandbox exec --privileged ${sandboxName} npm install -g ${npmPkg}`, {
13794
14031
  stdio: "inherit",
13795
14032
  timeout: 120000
13796
14033
  });
@@ -13802,7 +14039,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
13802
14039
  }
13803
14040
  async function ensureCodexInSandbox(sandboxName) {
13804
14041
  try {
13805
- execSync20(`docker sandbox exec --privileged ${sandboxName} which codex`, {
14042
+ execSync21(`docker sandbox exec --privileged ${sandboxName} which codex`, {
13806
14043
  stdio: ["pipe", "pipe", "pipe"],
13807
14044
  timeout: 5000
13808
14045
  });
@@ -13810,7 +14047,7 @@ async function ensureCodexInSandbox(sandboxName) {
13810
14047
  process.stderr.write(`Installing codex in sandbox...
13811
14048
  `);
13812
14049
  try {
13813
- execSync20(`docker sandbox exec --privileged ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
14050
+ execSync21(`docker sandbox exec --privileged ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
13814
14051
  } catch {
13815
14052
  process.stderr.write(`${red2("✗")} Failed to install codex in sandbox.
13816
14053
  `);
@@ -13819,7 +14056,7 @@ async function ensureCodexInSandbox(sandboxName) {
13819
14056
  }
13820
14057
  function isSandboxAlive(name) {
13821
14058
  try {
13822
- const output = execSync20("docker sandbox ls", {
14059
+ const output = execSync21("docker sandbox ls", {
13823
14060
  encoding: "utf-8",
13824
14061
  stdio: ["pipe", "pipe", "pipe"],
13825
14062
  timeout: 5000
@@ -13862,8 +14099,17 @@ function getCliVersion() {
13862
14099
  }
13863
14100
  }
13864
14101
  var VERSION = getCliVersion();
14102
+ function normalizeArgs(args) {
14103
+ return args.map((arg) => {
14104
+ if (arg.startsWith("--") || !arg.startsWith("-"))
14105
+ return arg;
14106
+ if (arg.length <= 2)
14107
+ return arg;
14108
+ return `-${arg}`;
14109
+ });
14110
+ }
13865
14111
  function parseArgs(argv) {
13866
- const rawArgs = argv.slice(2);
14112
+ const rawArgs = normalizeArgs(argv.slice(2));
13867
14113
  const flags = {
13868
14114
  debug: false,
13869
14115
  help: false,
@@ -14020,6 +14266,7 @@ ${bold2("Commands:")}
14020
14266
  ${cyan2("discuss")} AI-powered architectural discussions
14021
14267
  ${cyan2("artifacts")} View and manage AI-generated artifacts
14022
14268
  ${cyan2("status")} Dashboard view of current state
14269
+ ${cyan2("commit")} AI-powered commit message generation
14023
14270
  ${cyan2("config")} View and manage settings
14024
14271
  ${cyan2("logs")} View, tail, and manage execution logs
14025
14272
  ${cyan2("create")} ${dim2("<name>")} Scaffold a new community package
@@ -14067,6 +14314,7 @@ function requiresSandboxSync(command, args, flags) {
14067
14314
  case "run":
14068
14315
  case "review":
14069
14316
  case "iterate":
14317
+ case "commit":
14070
14318
  return true;
14071
14319
  case "exec":
14072
14320
  return args[0] !== "sessions" && args[0] !== "help";
@@ -14307,6 +14555,15 @@ async function main() {
14307
14555
  await artifactsCommand2(projectRoot, artifactsArgs);
14308
14556
  break;
14309
14557
  }
14558
+ case "commit": {
14559
+ const { commitCommand: commitCommand2 } = await Promise.resolve().then(() => (init_commit(), exports_commit));
14560
+ const commitArgs = parsed.flags.help ? ["help"] : parsed.args;
14561
+ await commitCommand2(projectRoot, commitArgs, {
14562
+ dryRun: parsed.flags.dryRun,
14563
+ model: parsed.flags.model
14564
+ });
14565
+ break;
14566
+ }
14310
14567
  case "sandbox": {
14311
14568
  const { sandboxCommand: sandboxCommand2 } = await Promise.resolve().then(() => (init_sandbox2(), exports_sandbox2));
14312
14569
  const sandboxArgs = parsed.flags.help ? ["help"] : parsed.args;
@@ -14343,3 +14600,6 @@ ${dim2(error.stack)}
14343
14600
  }
14344
14601
  process.exit(1);
14345
14602
  });
14603
+ export {
14604
+ normalizeArgs
14605
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/cli",
3
- "version": "0.22.9",
3
+ "version": "0.22.11",
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.9",
39
+ "@locusai/sdk": "^0.22.11",
40
40
  "@types/bun": "latest",
41
41
  "typescript": "^5.8.3"
42
42
  },