@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.
- package/bin/locus.js +265 -32
- 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}
|
|
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
|
|
10181
|
-
return join20(projectRoot, ".locus", "run-state
|
|
10183
|
+
function getRunStateDir(projectRoot) {
|
|
10184
|
+
return join20(projectRoot, ".locus", "run-state");
|
|
10182
10185
|
}
|
|
10183
|
-
function
|
|
10184
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
39
|
+
"@locusai/sdk": "^0.22.10",
|
|
40
40
|
"@types/bun": "latest",
|
|
41
41
|
"typescript": "^5.8.3"
|
|
42
42
|
},
|