@locusai/cli 0.20.2 → 0.20.4
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 +144 -49
- package/package.json +1 -1
package/bin/locus.js
CHANGED
|
@@ -890,7 +890,8 @@ __export(exports_sandbox, {
|
|
|
890
890
|
getProviderSandboxName: () => getProviderSandboxName,
|
|
891
891
|
getModelSandboxName: () => getModelSandboxName,
|
|
892
892
|
displaySandboxWarning: () => displaySandboxWarning,
|
|
893
|
-
detectSandboxSupport: () => detectSandboxSupport
|
|
893
|
+
detectSandboxSupport: () => detectSandboxSupport,
|
|
894
|
+
checkProviderSandboxMismatch: () => checkProviderSandboxMismatch
|
|
894
895
|
});
|
|
895
896
|
import { execFile } from "node:child_process";
|
|
896
897
|
import { createInterface } from "node:readline";
|
|
@@ -901,6 +902,19 @@ function getModelSandboxName(config, model, fallbackProvider) {
|
|
|
901
902
|
const provider = inferProviderFromModel(model) ?? fallbackProvider;
|
|
902
903
|
return getProviderSandboxName(config, provider);
|
|
903
904
|
}
|
|
905
|
+
function checkProviderSandboxMismatch(config, model, fallbackProvider) {
|
|
906
|
+
if (!config.enabled)
|
|
907
|
+
return null;
|
|
908
|
+
const targetProvider = inferProviderFromModel(model) ?? fallbackProvider;
|
|
909
|
+
const sandboxName = getProviderSandboxName(config, targetProvider);
|
|
910
|
+
if (sandboxName)
|
|
911
|
+
return null;
|
|
912
|
+
const configured = ["claude", "codex"].filter((p) => config.providers[p]);
|
|
913
|
+
if (configured.length > 0) {
|
|
914
|
+
return `Sandbox is configured for ${configured.join(", ")} but not for ${targetProvider}. ` + `Run "locus sandbox" and select ${targetProvider} to create its sandbox.`;
|
|
915
|
+
}
|
|
916
|
+
return `No sandbox is configured for ${targetProvider}. ` + `Run "locus sandbox" to create one.`;
|
|
917
|
+
}
|
|
904
918
|
async function detectSandboxSupport() {
|
|
905
919
|
if (cachedStatus)
|
|
906
920
|
return cachedStatus;
|
|
@@ -5337,7 +5351,7 @@ ${red("✗")} ${dim("Force exit.")}\r
|
|
|
5337
5351
|
return {
|
|
5338
5352
|
success: false,
|
|
5339
5353
|
output: "",
|
|
5340
|
-
error: `
|
|
5354
|
+
error: `No sandbox configured for "${resolvedProvider}". ` + `Run "locus sandbox" and select "${resolvedProvider}" to create its sandbox, ` + `then authenticate via "locus sandbox ${resolvedProvider}".`,
|
|
5341
5355
|
interrupted: false,
|
|
5342
5356
|
exitCode: 1
|
|
5343
5357
|
};
|
|
@@ -7983,7 +7997,8 @@ async function runInteractiveRepl(session, sessionManager, options) {
|
|
|
7983
7997
|
process.stderr.write(`${dim("Using")} ${dim(provider)} ${dim("sandbox")} ${dim(sandboxName)}
|
|
7984
7998
|
`);
|
|
7985
7999
|
} else {
|
|
7986
|
-
|
|
8000
|
+
const mismatch = checkProviderSandboxMismatch(config.sandbox, config.ai.model, config.ai.provider);
|
|
8001
|
+
process.stderr.write(`${yellow("⚠")} ${dim(mismatch ?? `No sandbox configured for ${provider}. Run locus sandbox.`)}
|
|
7987
8002
|
`);
|
|
7988
8003
|
}
|
|
7989
8004
|
}
|
|
@@ -8027,7 +8042,8 @@ async function runInteractiveRepl(session, sessionManager, options) {
|
|
|
8027
8042
|
`);
|
|
8028
8043
|
} else {
|
|
8029
8044
|
sandboxRunner = null;
|
|
8030
|
-
|
|
8045
|
+
const mismatch = checkProviderSandboxMismatch(config.sandbox, model, config.ai.provider);
|
|
8046
|
+
process.stderr.write(`${yellow("⚠")} ${dim(mismatch ?? `No sandbox configured for ${inferredProvider}. Run locus sandbox.`)}
|
|
8031
8047
|
`);
|
|
8032
8048
|
}
|
|
8033
8049
|
}
|
|
@@ -8332,7 +8348,8 @@ async function handleJsonStream(projectRoot, config, args, sessionId) {
|
|
|
8332
8348
|
const sandboxName = getProviderSandboxName(config.sandbox, config.ai.provider);
|
|
8333
8349
|
const runner = config.sandbox.enabled ? sandboxName ? createUserManagedSandboxRunner(config.ai.provider, sandboxName) : null : await createRunnerAsync(config.ai.provider, false);
|
|
8334
8350
|
if (!runner) {
|
|
8335
|
-
|
|
8351
|
+
const mismatch = checkProviderSandboxMismatch(config.sandbox, config.ai.model, config.ai.provider);
|
|
8352
|
+
stream.emitError(mismatch ?? `No sandbox configured for "${config.ai.provider}". Run "locus sandbox" to create one.`, false);
|
|
8336
8353
|
return;
|
|
8337
8354
|
}
|
|
8338
8355
|
const available = await runner.isAvailable();
|
|
@@ -9335,6 +9352,15 @@ async function runCommand(projectRoot, args, flags = {}) {
|
|
|
9335
9352
|
process.stderr.write(`${yellow("⚠")} Running without sandbox. The AI agent will have unrestricted access to your filesystem, network, and environment variables.
|
|
9336
9353
|
`);
|
|
9337
9354
|
}
|
|
9355
|
+
if (sandboxed) {
|
|
9356
|
+
const model = flags.model ?? config.ai.model;
|
|
9357
|
+
const mismatch = checkProviderSandboxMismatch(config.sandbox, model, config.ai.provider);
|
|
9358
|
+
if (mismatch) {
|
|
9359
|
+
process.stderr.write(`${red("✗")} ${mismatch}
|
|
9360
|
+
`);
|
|
9361
|
+
return;
|
|
9362
|
+
}
|
|
9363
|
+
}
|
|
9338
9364
|
if (flags.resume) {
|
|
9339
9365
|
return handleResume(projectRoot, config, sandboxed);
|
|
9340
9366
|
}
|
|
@@ -10288,6 +10314,14 @@ ${bold("Planning:")} ${cyan(displayDirective)}
|
|
|
10288
10314
|
}
|
|
10289
10315
|
process.stderr.write(`
|
|
10290
10316
|
`);
|
|
10317
|
+
if (config.sandbox.enabled) {
|
|
10318
|
+
const mismatch = checkProviderSandboxMismatch(config.sandbox, flags.model ?? config.ai.model, config.ai.provider);
|
|
10319
|
+
if (mismatch) {
|
|
10320
|
+
process.stderr.write(`${red("✗")} ${mismatch}
|
|
10321
|
+
`);
|
|
10322
|
+
return;
|
|
10323
|
+
}
|
|
10324
|
+
}
|
|
10291
10325
|
const prompt = buildPlanningPrompt(projectRoot, config, directive, sprintName, id, planPathRelative);
|
|
10292
10326
|
const aiResult = await runAI({
|
|
10293
10327
|
prompt,
|
|
@@ -10383,6 +10417,14 @@ ${bold("Organizing issues for:")} ${cyan(sprintName)}
|
|
|
10383
10417
|
`);
|
|
10384
10418
|
return;
|
|
10385
10419
|
}
|
|
10420
|
+
if (config.sandbox.enabled) {
|
|
10421
|
+
const mismatch = checkProviderSandboxMismatch(config.sandbox, flags.model ?? config.ai.model, config.ai.provider);
|
|
10422
|
+
if (mismatch) {
|
|
10423
|
+
process.stderr.write(`${red("✗")} ${mismatch}
|
|
10424
|
+
`);
|
|
10425
|
+
return;
|
|
10426
|
+
}
|
|
10427
|
+
}
|
|
10386
10428
|
const issueDescriptions = issues.map((i) => `#${i.number}: ${i.title}
|
|
10387
10429
|
${i.body?.slice(0, 300) ?? ""}`).join(`
|
|
10388
10430
|
|
|
@@ -10712,6 +10754,14 @@ async function reviewCommand(projectRoot, args, flags = {}) {
|
|
|
10712
10754
|
prNumber = Number.parseInt(args[i], 10);
|
|
10713
10755
|
}
|
|
10714
10756
|
}
|
|
10757
|
+
if (config.sandbox.enabled) {
|
|
10758
|
+
const mismatch = checkProviderSandboxMismatch(config.sandbox, flags.model ?? config.ai.model, config.ai.provider);
|
|
10759
|
+
if (mismatch) {
|
|
10760
|
+
process.stderr.write(`${red("✗")} ${mismatch}
|
|
10761
|
+
`);
|
|
10762
|
+
return;
|
|
10763
|
+
}
|
|
10764
|
+
}
|
|
10715
10765
|
if (prNumber) {
|
|
10716
10766
|
return reviewSinglePR(projectRoot, config, prNumber, focus, flags);
|
|
10717
10767
|
}
|
|
@@ -10939,6 +10989,14 @@ async function iterateCommand(projectRoot, args, flags = {}) {
|
|
|
10939
10989
|
issueNumber = Number.parseInt(args[i], 10);
|
|
10940
10990
|
}
|
|
10941
10991
|
}
|
|
10992
|
+
if (config.sandbox.enabled) {
|
|
10993
|
+
const mismatch = checkProviderSandboxMismatch(config.sandbox, config.ai.model, config.ai.provider);
|
|
10994
|
+
if (mismatch) {
|
|
10995
|
+
process.stderr.write(`${red("✗")} ${mismatch}
|
|
10996
|
+
`);
|
|
10997
|
+
return;
|
|
10998
|
+
}
|
|
10999
|
+
}
|
|
10942
11000
|
if (prNumber) {
|
|
10943
11001
|
return handleSinglePR(projectRoot, config, prNumber, flags);
|
|
10944
11002
|
}
|
|
@@ -11135,6 +11193,7 @@ var init_iterate = __esm(() => {
|
|
|
11135
11193
|
init_agent();
|
|
11136
11194
|
init_config();
|
|
11137
11195
|
init_github();
|
|
11196
|
+
init_sandbox();
|
|
11138
11197
|
init_progress();
|
|
11139
11198
|
init_terminal();
|
|
11140
11199
|
});
|
|
@@ -11339,6 +11398,14 @@ async function startDiscussion(projectRoot, topic, flags) {
|
|
|
11339
11398
|
const config = loadConfig(projectRoot);
|
|
11340
11399
|
const timer = createTimer();
|
|
11341
11400
|
const id = generateId2();
|
|
11401
|
+
if (config.sandbox.enabled) {
|
|
11402
|
+
const mismatch = checkProviderSandboxMismatch(config.sandbox, flags.model ?? config.ai.model, config.ai.provider);
|
|
11403
|
+
if (mismatch) {
|
|
11404
|
+
process.stderr.write(`${red("✗")} ${mismatch}
|
|
11405
|
+
`);
|
|
11406
|
+
return;
|
|
11407
|
+
}
|
|
11408
|
+
}
|
|
11342
11409
|
process.stderr.write(`
|
|
11343
11410
|
${bold("Discussion:")} ${cyan(topic)}
|
|
11344
11411
|
|
|
@@ -11733,12 +11800,13 @@ import { execSync as execSync17, spawn as spawn6 } from "node:child_process";
|
|
|
11733
11800
|
import { createHash } from "node:crypto";
|
|
11734
11801
|
import { existsSync as existsSync24, readFileSync as readFileSync17 } from "node:fs";
|
|
11735
11802
|
import { basename as basename4, join as join24 } from "node:path";
|
|
11803
|
+
import { createInterface as createInterface3 } from "node:readline";
|
|
11736
11804
|
function printSandboxHelp() {
|
|
11737
11805
|
process.stderr.write(`
|
|
11738
11806
|
${bold("locus sandbox")} — Manage Docker sandbox lifecycle
|
|
11739
11807
|
|
|
11740
11808
|
${bold("Usage:")}
|
|
11741
|
-
locus sandbox ${dim("#
|
|
11809
|
+
locus sandbox ${dim("# Select a provider and create its sandbox")}
|
|
11742
11810
|
locus sandbox claude ${dim("# Run claude interactively (for login)")}
|
|
11743
11811
|
locus sandbox codex ${dim("# Run codex interactively (for login)")}
|
|
11744
11812
|
locus sandbox setup ${dim("# Re-run dependency install in sandbox(es)")}
|
|
@@ -11749,10 +11817,10 @@ ${bold("Usage:")}
|
|
|
11749
11817
|
locus sandbox status ${dim("# Show current sandbox state")}
|
|
11750
11818
|
|
|
11751
11819
|
${bold("Flow:")}
|
|
11752
|
-
1. ${cyan("locus sandbox")}
|
|
11753
|
-
2. ${cyan("locus sandbox
|
|
11754
|
-
3. ${cyan("locus sandbox
|
|
11755
|
-
4. ${cyan("locus sandbox
|
|
11820
|
+
1. ${cyan("locus sandbox")} Select a provider and create its sandbox
|
|
11821
|
+
2. ${cyan("locus sandbox <provider>")} Login to the provider inside its sandbox
|
|
11822
|
+
3. ${cyan("locus sandbox install bun")} Install extra tools (optional)
|
|
11823
|
+
4. Run ${cyan("locus sandbox")} again to add another provider (optional)
|
|
11756
11824
|
|
|
11757
11825
|
`);
|
|
11758
11826
|
}
|
|
@@ -11786,6 +11854,43 @@ async function sandboxCommand(projectRoot, args) {
|
|
|
11786
11854
|
`);
|
|
11787
11855
|
}
|
|
11788
11856
|
}
|
|
11857
|
+
async function promptProviderSelection() {
|
|
11858
|
+
process.stderr.write(`
|
|
11859
|
+
${bold("Select a provider to create a sandbox for:")}
|
|
11860
|
+
|
|
11861
|
+
`);
|
|
11862
|
+
for (let i = 0;i < PROVIDERS.length; i++) {
|
|
11863
|
+
process.stderr.write(` ${bold(String(i + 1))}. ${PROVIDERS[i]}
|
|
11864
|
+
`);
|
|
11865
|
+
}
|
|
11866
|
+
process.stderr.write(`
|
|
11867
|
+
`);
|
|
11868
|
+
const rl = createInterface3({
|
|
11869
|
+
input: process.stdin,
|
|
11870
|
+
output: process.stderr
|
|
11871
|
+
});
|
|
11872
|
+
return new Promise((resolve2) => {
|
|
11873
|
+
rl.question("Enter choice (1-2): ", (answer) => {
|
|
11874
|
+
const trimmed = answer.trim().toLowerCase();
|
|
11875
|
+
if (trimmed === "claude" || trimmed === "codex") {
|
|
11876
|
+
resolve2(trimmed);
|
|
11877
|
+
rl.close();
|
|
11878
|
+
return;
|
|
11879
|
+
}
|
|
11880
|
+
const num = Number.parseInt(trimmed, 10);
|
|
11881
|
+
if (num >= 1 && num <= PROVIDERS.length) {
|
|
11882
|
+
resolve2(PROVIDERS[num - 1]);
|
|
11883
|
+
rl.close();
|
|
11884
|
+
return;
|
|
11885
|
+
}
|
|
11886
|
+
process.stderr.write(`${red("✗")} Invalid selection.
|
|
11887
|
+
`);
|
|
11888
|
+
resolve2(null);
|
|
11889
|
+
rl.close();
|
|
11890
|
+
});
|
|
11891
|
+
rl.on("close", () => resolve2(null));
|
|
11892
|
+
});
|
|
11893
|
+
}
|
|
11789
11894
|
async function handleCreate(projectRoot) {
|
|
11790
11895
|
const config = loadConfig(projectRoot);
|
|
11791
11896
|
const status = await detectSandboxSupport();
|
|
@@ -11796,54 +11901,44 @@ async function handleCreate(projectRoot) {
|
|
|
11796
11901
|
`);
|
|
11797
11902
|
return;
|
|
11798
11903
|
}
|
|
11904
|
+
const provider = await promptProviderSelection();
|
|
11905
|
+
if (!provider)
|
|
11906
|
+
return;
|
|
11799
11907
|
const sandboxNames = buildProviderSandboxNames(projectRoot);
|
|
11800
|
-
const
|
|
11801
|
-
const
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
|
|
11908
|
+
const name = sandboxNames[provider];
|
|
11909
|
+
const readySandboxes = { ...config.sandbox.providers };
|
|
11910
|
+
if (isSandboxAlive(name)) {
|
|
11911
|
+
process.stderr.write(`${green("✓")} ${provider} sandbox already exists: ${bold(name)}
|
|
11912
|
+
`);
|
|
11913
|
+
readySandboxes[provider] = name;
|
|
11914
|
+
config.sandbox.enabled = true;
|
|
11915
|
+
config.sandbox.providers = readySandboxes;
|
|
11916
|
+
saveConfig(projectRoot, config);
|
|
11917
|
+
process.stderr.write(` Next: run ${cyan(`locus sandbox ${provider}`)} to authenticate.
|
|
11807
11918
|
`);
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11919
|
+
return;
|
|
11920
|
+
}
|
|
11921
|
+
process.stderr.write(`Creating ${bold(provider)} sandbox ${dim(name)} with workspace ${dim(projectRoot)}...
|
|
11811
11922
|
`);
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
|
|
11923
|
+
const created = await createProviderSandbox(provider, name, projectRoot);
|
|
11924
|
+
if (!created) {
|
|
11925
|
+
process.stderr.write(`${red("✗")} Failed to create ${provider} sandbox (${name}).
|
|
11815
11926
|
`);
|
|
11816
|
-
|
|
11817
|
-
}
|
|
11818
|
-
process.stderr.write(`${green("✓")} ${provider} sandbox created: ${bold(name)}
|
|
11927
|
+
process.stderr.write(` Re-run ${cyan("locus sandbox")} after resolving Docker issues.
|
|
11819
11928
|
`);
|
|
11820
|
-
return
|
|
11821
|
-
}));
|
|
11822
|
-
for (const result of createResults) {
|
|
11823
|
-
if (result.created || result.existed) {
|
|
11824
|
-
readySandboxes[result.provider] = result.name;
|
|
11825
|
-
if (result.created)
|
|
11826
|
-
newlyCreated.add(result.name);
|
|
11827
|
-
} else {
|
|
11828
|
-
failed = true;
|
|
11829
|
-
}
|
|
11929
|
+
return;
|
|
11830
11930
|
}
|
|
11931
|
+
process.stderr.write(`${green("✓")} ${provider} sandbox created: ${bold(name)}
|
|
11932
|
+
`);
|
|
11933
|
+
readySandboxes[provider] = name;
|
|
11831
11934
|
config.sandbox.enabled = true;
|
|
11832
11935
|
config.sandbox.providers = readySandboxes;
|
|
11833
11936
|
saveConfig(projectRoot, config);
|
|
11834
|
-
await
|
|
11835
|
-
const sandboxName = readySandboxes[provider];
|
|
11836
|
-
return sandboxName && newlyCreated.has(sandboxName);
|
|
11837
|
-
}).map((provider) => runSandboxSetup(readySandboxes[provider], projectRoot)));
|
|
11838
|
-
if (failed) {
|
|
11839
|
-
process.stderr.write(`
|
|
11840
|
-
${yellow("⚠")} Some sandboxes failed to create. Re-run ${cyan("locus sandbox")} after resolving Docker issues.
|
|
11841
|
-
`);
|
|
11842
|
-
}
|
|
11937
|
+
await runSandboxSetup(name, projectRoot);
|
|
11843
11938
|
process.stderr.write(`
|
|
11844
|
-
${green("✓")} Sandbox mode enabled
|
|
11939
|
+
${green("✓")} Sandbox mode enabled for ${bold(provider)}.
|
|
11845
11940
|
`);
|
|
11846
|
-
process.stderr.write(` Next: run ${cyan(
|
|
11941
|
+
process.stderr.write(` Next: run ${cyan(`locus sandbox ${provider}`)} to authenticate.
|
|
11847
11942
|
`);
|
|
11848
11943
|
}
|
|
11849
11944
|
async function handleAgentLogin(projectRoot, agent) {
|
|
@@ -11939,9 +12034,9 @@ ${bold("Sandbox Status")}
|
|
|
11939
12034
|
`);
|
|
11940
12035
|
}
|
|
11941
12036
|
}
|
|
11942
|
-
if (!config.sandbox.providers.claude
|
|
12037
|
+
if (!config.sandbox.providers.claude && !config.sandbox.providers.codex) {
|
|
11943
12038
|
process.stderr.write(`
|
|
11944
|
-
${yellow("⚠")}
|
|
12039
|
+
${yellow("⚠")} No provider sandboxes configured. Run ${bold("locus sandbox")} to create one.
|
|
11945
12040
|
`);
|
|
11946
12041
|
}
|
|
11947
12042
|
process.stderr.write(`
|