@locusai/cli 0.22.12 → 0.22.13
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 +108 -94
- package/package.json +2 -2
package/bin/locus.js
CHANGED
|
@@ -898,10 +898,15 @@ __export(exports_sandbox, {
|
|
|
898
898
|
displaySandboxWarning: () => displaySandboxWarning,
|
|
899
899
|
detectSandboxSupport: () => detectSandboxSupport,
|
|
900
900
|
detectContainerWorkdir: () => detectContainerWorkdir,
|
|
901
|
-
checkProviderSandboxMismatch: () => checkProviderSandboxMismatch
|
|
901
|
+
checkProviderSandboxMismatch: () => checkProviderSandboxMismatch,
|
|
902
|
+
buildSandboxEnvWrapper: () => buildSandboxEnvWrapper,
|
|
903
|
+
SANDBOX_DEPS_DIR: () => SANDBOX_DEPS_DIR
|
|
902
904
|
});
|
|
903
905
|
import { execFile, execSync as execSync2 } from "node:child_process";
|
|
904
906
|
import { createInterface } from "node:readline";
|
|
907
|
+
function buildSandboxEnvWrapper(workdir) {
|
|
908
|
+
return 'PATH="' + SANDBOX_DEPS_DIR + "/node_modules/.bin:" + workdir + '/node_modules/.bin:$PATH"; export PATH; ' + 'NODE_PATH="' + SANDBOX_DEPS_DIR + '/node_modules${NODE_PATH:+:$NODE_PATH}"; export NODE_PATH; ' + 'exec "$@"';
|
|
909
|
+
}
|
|
905
910
|
function getProviderSandboxName(config, provider) {
|
|
906
911
|
return config.providers[provider];
|
|
907
912
|
}
|
|
@@ -1096,7 +1101,7 @@ function probeSymlinkSupport(sandboxName, workdir) {
|
|
|
1096
1101
|
return true;
|
|
1097
1102
|
} catch {
|
|
1098
1103
|
try {
|
|
1099
|
-
execSync2(`docker sandbox exec ${sandboxName} rm -f ${JSON.stringify(`${workdir}/${probe}`)}`, { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
1104
|
+
execSync2(`docker sandbox exec --privileged ${sandboxName} rm -f ${JSON.stringify(`${workdir}/${probe}`)}`, { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
1100
1105
|
} catch {}
|
|
1101
1106
|
log.debug("Symlink probe failed — bind mount does not support symlinks (ENOSYS)");
|
|
1102
1107
|
return false;
|
|
@@ -1117,7 +1122,7 @@ function waitForEnter() {
|
|
|
1117
1122
|
});
|
|
1118
1123
|
});
|
|
1119
1124
|
}
|
|
1120
|
-
var TIMEOUT_MS = 5000, cachedStatus = null;
|
|
1125
|
+
var SANDBOX_DEPS_DIR = "/tmp/sandbox-deps", TIMEOUT_MS = 5000, cachedStatus = null;
|
|
1121
1126
|
var init_sandbox = __esm(() => {
|
|
1122
1127
|
init_terminal();
|
|
1123
1128
|
init_ai_models();
|
|
@@ -2348,7 +2353,7 @@ function parseCreateArgs(args) {
|
|
|
2348
2353
|
}
|
|
2349
2354
|
return { name, description };
|
|
2350
2355
|
}
|
|
2351
|
-
function generatePackageJson(name, displayName, description, sdkVersion) {
|
|
2356
|
+
function generatePackageJson(name, displayName, description, sdkVersion, gatewayVersion) {
|
|
2352
2357
|
const pkg = {
|
|
2353
2358
|
name: `@locusai/locus-${name}`,
|
|
2354
2359
|
version: "0.1.0",
|
|
@@ -2371,6 +2376,7 @@ function generatePackageJson(name, displayName, description, sdkVersion) {
|
|
|
2371
2376
|
format: "biome format --write ."
|
|
2372
2377
|
},
|
|
2373
2378
|
dependencies: {
|
|
2379
|
+
"@locusai/locus-gateway": `^${gatewayVersion}`,
|
|
2374
2380
|
"@locusai/sdk": `^${sdkVersion}`
|
|
2375
2381
|
},
|
|
2376
2382
|
devDependencies: {
|
|
@@ -2592,20 +2598,27 @@ ${bold2("Creating package:")} ${cyan2(fullNpmName)}
|
|
|
2592
2598
|
`);
|
|
2593
2599
|
}
|
|
2594
2600
|
let sdkVersion = "0.22.0";
|
|
2601
|
+
let gatewayVersion = "0.22.0";
|
|
2595
2602
|
try {
|
|
2603
|
+
const { readFileSync: readFileSync5 } = await import("node:fs");
|
|
2596
2604
|
const sdkPkgPath = join7(process.cwd(), "packages", "sdk", "package.json");
|
|
2597
2605
|
if (existsSync7(sdkPkgPath)) {
|
|
2598
|
-
const { readFileSync: readFileSync5 } = await import("node:fs");
|
|
2599
2606
|
const sdkPkg = JSON.parse(readFileSync5(sdkPkgPath, "utf-8"));
|
|
2600
2607
|
if (sdkPkg.version)
|
|
2601
2608
|
sdkVersion = sdkPkg.version;
|
|
2602
2609
|
}
|
|
2610
|
+
const gatewayPkgPath = join7(process.cwd(), "packages", "gateway", "package.json");
|
|
2611
|
+
if (existsSync7(gatewayPkgPath)) {
|
|
2612
|
+
const gatewayPkg = JSON.parse(readFileSync5(gatewayPkgPath, "utf-8"));
|
|
2613
|
+
if (gatewayPkg.version)
|
|
2614
|
+
gatewayVersion = gatewayPkg.version;
|
|
2615
|
+
}
|
|
2603
2616
|
} catch {}
|
|
2604
2617
|
mkdirSync6(join7(packagesDir, "src"), { recursive: true });
|
|
2605
2618
|
mkdirSync6(join7(packagesDir, "bin"), { recursive: true });
|
|
2606
2619
|
process.stderr.write(`${green("✓")} Created directory structure
|
|
2607
2620
|
`);
|
|
2608
|
-
writeFileSync5(join7(packagesDir, "package.json"), generatePackageJson(name, displayName, description, sdkVersion), "utf-8");
|
|
2621
|
+
writeFileSync5(join7(packagesDir, "package.json"), generatePackageJson(name, displayName, description, sdkVersion, gatewayVersion), "utf-8");
|
|
2609
2622
|
process.stderr.write(`${green("✓")} Generated package.json
|
|
2610
2623
|
`);
|
|
2611
2624
|
writeFileSync5(join7(packagesDir, "tsconfig.json"), generateTsconfig(), "utf-8");
|
|
@@ -5305,6 +5318,7 @@ class SandboxedClaudeRunner {
|
|
|
5305
5318
|
await enforceSandboxIgnore(this.sandboxName, options.cwd, this.containerWorkdir);
|
|
5306
5319
|
options.onStatusChange?.("Thinking...");
|
|
5307
5320
|
const workdir = this.containerWorkdir ?? options.cwd;
|
|
5321
|
+
const envWrapper = buildSandboxEnvWrapper(workdir);
|
|
5308
5322
|
const dockerArgs = [
|
|
5309
5323
|
"sandbox",
|
|
5310
5324
|
"exec",
|
|
@@ -5312,6 +5326,10 @@ class SandboxedClaudeRunner {
|
|
|
5312
5326
|
"-w",
|
|
5313
5327
|
workdir,
|
|
5314
5328
|
this.sandboxName,
|
|
5329
|
+
"sh",
|
|
5330
|
+
"-c",
|
|
5331
|
+
envWrapper,
|
|
5332
|
+
"_",
|
|
5315
5333
|
"claude",
|
|
5316
5334
|
...claudeArgs
|
|
5317
5335
|
];
|
|
@@ -5481,6 +5499,7 @@ function formatToolCall2(name, input) {
|
|
|
5481
5499
|
}
|
|
5482
5500
|
var init_claude_sandbox = __esm(() => {
|
|
5483
5501
|
init_logger();
|
|
5502
|
+
init_sandbox();
|
|
5484
5503
|
init_sandbox_ignore();
|
|
5485
5504
|
init_claude();
|
|
5486
5505
|
});
|
|
@@ -5704,6 +5723,7 @@ class SandboxedCodexRunner {
|
|
|
5704
5723
|
}
|
|
5705
5724
|
options.onStatusChange?.("Thinking...");
|
|
5706
5725
|
const workdir = this.containerWorkdir ?? options.cwd;
|
|
5726
|
+
const envWrapper = buildSandboxEnvWrapper(workdir);
|
|
5707
5727
|
const dockerArgs = [
|
|
5708
5728
|
"sandbox",
|
|
5709
5729
|
"exec",
|
|
@@ -5712,6 +5732,10 @@ class SandboxedCodexRunner {
|
|
|
5712
5732
|
"-w",
|
|
5713
5733
|
workdir,
|
|
5714
5734
|
this.sandboxName,
|
|
5735
|
+
"sh",
|
|
5736
|
+
"-c",
|
|
5737
|
+
envWrapper,
|
|
5738
|
+
"_",
|
|
5715
5739
|
"codex",
|
|
5716
5740
|
...codexArgs
|
|
5717
5741
|
];
|
|
@@ -5873,6 +5897,7 @@ class SandboxedCodexRunner {
|
|
|
5873
5897
|
}
|
|
5874
5898
|
var init_codex_sandbox = __esm(() => {
|
|
5875
5899
|
init_logger();
|
|
5900
|
+
init_sandbox();
|
|
5876
5901
|
init_sandbox_ignore();
|
|
5877
5902
|
init_codex();
|
|
5878
5903
|
});
|
|
@@ -7584,12 +7609,8 @@ function buildReplPrompt(userMessage, projectRoot, _config, previousMessages) {
|
|
|
7584
7609
|
${locusmd}
|
|
7585
7610
|
</project-instructions>`);
|
|
7586
7611
|
}
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
sections.push(`<past-learnings>
|
|
7590
|
-
${learnings}
|
|
7591
|
-
</past-learnings>`);
|
|
7592
|
-
}
|
|
7612
|
+
sections.push(`<past-learnings>
|
|
7613
|
+
Past learnings are located in \`.locus/LEARNINGS.md\`.</past-learnings>`);
|
|
7593
7614
|
sections.push(`<learnings-reminder>IMPORTANT: If during this interaction you discover reusable lessons (architectural patterns, non-obvious constraints, user corrections), you MUST append them to \`.locus/LEARNINGS.md\` before finishing. This is mandatory — see the "Continuous Learning" section in project instructions.</learnings-reminder>`);
|
|
7594
7615
|
if (previousMessages && previousMessages.length > 0) {
|
|
7595
7616
|
const recent = previousMessages.slice(-10);
|
|
@@ -7617,12 +7638,8 @@ function buildSystemContext(projectRoot) {
|
|
|
7617
7638
|
${locusmd}
|
|
7618
7639
|
</project-instructions>`);
|
|
7619
7640
|
}
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
parts.push(`<past-learnings>
|
|
7623
|
-
${learnings}
|
|
7624
|
-
</past-learnings>`);
|
|
7625
|
-
}
|
|
7641
|
+
parts.push(`<past-learnings>
|
|
7642
|
+
Past learnings are located in \`.locus/LEARNINGS.md\`.</past-learnings>`);
|
|
7626
7643
|
const discussionsDir = join14(projectRoot, ".locus", "discussions");
|
|
7627
7644
|
if (existsSync15(discussionsDir)) {
|
|
7628
7645
|
try {
|
|
@@ -13454,7 +13471,16 @@ async function handleAgentLogin(projectRoot, agent) {
|
|
|
13454
13471
|
process.stderr.write(`${dim2("Login and then exit when ready.")}
|
|
13455
13472
|
|
|
13456
13473
|
`);
|
|
13457
|
-
const child = spawn7("docker", [
|
|
13474
|
+
const child = spawn7("docker", [
|
|
13475
|
+
"sandbox",
|
|
13476
|
+
"exec",
|
|
13477
|
+
"--privileged",
|
|
13478
|
+
"-it",
|
|
13479
|
+
"-w",
|
|
13480
|
+
workdir,
|
|
13481
|
+
sandboxName,
|
|
13482
|
+
agent
|
|
13483
|
+
], {
|
|
13458
13484
|
stdio: "inherit"
|
|
13459
13485
|
});
|
|
13460
13486
|
await new Promise((resolve2) => {
|
|
@@ -13668,15 +13694,42 @@ async function handleShell(projectRoot, args) {
|
|
|
13668
13694
|
}
|
|
13669
13695
|
const workdir = config.sandbox.containerWorkdir ?? projectRoot;
|
|
13670
13696
|
process.stderr.write(`Opening shell in ${provider} sandbox ${dim2(sandboxName)}...
|
|
13697
|
+
`);
|
|
13698
|
+
const binDirs = [
|
|
13699
|
+
`"${SANDBOX_DEPS_DIR}/node_modules/.bin"`,
|
|
13700
|
+
`"${workdir}/node_modules/.bin"`,
|
|
13701
|
+
`"${workdir}/.venv/bin"`,
|
|
13702
|
+
`"${workdir}/venv/bin"`,
|
|
13703
|
+
`"${workdir}/vendor/bundle/ruby/*/bin"`,
|
|
13704
|
+
`"$HOME/.cargo/bin"`,
|
|
13705
|
+
`"$HOME/go/bin"`,
|
|
13706
|
+
`"$HOME/.local/bin"`,
|
|
13707
|
+
`"$HOME/.bun/bin"`,
|
|
13708
|
+
`"$HOME/.deno/bin"`,
|
|
13709
|
+
`"$HOME/.sdkman/candidates/*/current/bin"`
|
|
13710
|
+
].join(" ");
|
|
13711
|
+
const shellInit = [
|
|
13712
|
+
`for d in ${binDirs}; do`,
|
|
13713
|
+
` [ -d "$d" ] && case ":$PATH:" in *":$d:"*) ;; *) PATH="$d:$PATH" ;; esac`,
|
|
13714
|
+
`done`,
|
|
13715
|
+
`export PATH`,
|
|
13716
|
+
'NODE_PATH="' + SANDBOX_DEPS_DIR + '/node_modules${NODE_PATH:+:$NODE_PATH}"',
|
|
13717
|
+
`export NODE_PATH`,
|
|
13718
|
+
`[ -f "${workdir}/.locus/sandbox-profile.sh" ] && . "${workdir}/.locus/sandbox-profile.sh"`,
|
|
13719
|
+
`exec sh`
|
|
13720
|
+
].join(`
|
|
13671
13721
|
`);
|
|
13672
13722
|
await runInteractiveCommand("docker", [
|
|
13673
13723
|
"sandbox",
|
|
13674
13724
|
"exec",
|
|
13725
|
+
"--privileged",
|
|
13675
13726
|
"-it",
|
|
13676
13727
|
"-w",
|
|
13677
13728
|
workdir,
|
|
13678
13729
|
sandboxName,
|
|
13679
|
-
"sh"
|
|
13730
|
+
"sh",
|
|
13731
|
+
"-c",
|
|
13732
|
+
shellInit
|
|
13680
13733
|
]);
|
|
13681
13734
|
}
|
|
13682
13735
|
function parseSandboxLogsArgs(args) {
|
|
@@ -13808,58 +13861,34 @@ function verifyBinEntries(sandboxName, workdir) {
|
|
|
13808
13861
|
}
|
|
13809
13862
|
} catch {}
|
|
13810
13863
|
}
|
|
13811
|
-
function
|
|
13864
|
+
function buildSandboxDepsScript(workdir, pm) {
|
|
13812
13865
|
const installCmd = getInstallCommand(pm).join(" ");
|
|
13813
13866
|
return [
|
|
13814
13867
|
"set -e",
|
|
13815
|
-
|
|
13868
|
+
`DEPS_DIR="${SANDBOX_DEPS_DIR}"`,
|
|
13816
13869
|
`WORKDIR="${workdir}"`,
|
|
13817
13870
|
"",
|
|
13818
|
-
|
|
13819
|
-
'
|
|
13820
|
-
'mkdir -p "$TMPDIR"',
|
|
13821
|
-
'cd "$WORKDIR"',
|
|
13822
|
-
"",
|
|
13823
|
-
"# Copy all package.json files (preserving workspace directory structure)",
|
|
13824
|
-
'find . -name package.json -not -path "*/node_modules/*" -not -path "./.git/*" | while read f; do',
|
|
13825
|
-
' mkdir -p "$TMPDIR/$(dirname "$f")"',
|
|
13826
|
-
' cp "$f" "$TMPDIR/$f"',
|
|
13827
|
-
"done",
|
|
13871
|
+
'rm -rf "$DEPS_DIR"',
|
|
13872
|
+
'mkdir -p "$DEPS_DIR"',
|
|
13828
13873
|
"",
|
|
13829
|
-
"# Copy
|
|
13830
|
-
"
|
|
13831
|
-
|
|
13832
|
-
"
|
|
13833
|
-
"
|
|
13834
|
-
|
|
13835
|
-
"done",
|
|
13836
|
-
"",
|
|
13837
|
-
"# Install on native filesystem (symlinks work here)",
|
|
13838
|
-
'cd "$TMPDIR"',
|
|
13839
|
-
`${installCmd}`,
|
|
13874
|
+
"# Copy package.json, stripping workspaces to avoid resolution errors",
|
|
13875
|
+
"if command -v node >/dev/null 2>&1; then",
|
|
13876
|
+
` node -e "var p=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));delete p.workspaces;require('fs').writeFileSync(process.argv[2],JSON.stringify(p,null,2));" "$WORKDIR/package.json" "$DEPS_DIR/package.json"`,
|
|
13877
|
+
"else",
|
|
13878
|
+
' cp "$WORKDIR/package.json" "$DEPS_DIR/package.json"',
|
|
13879
|
+
"fi",
|
|
13840
13880
|
"",
|
|
13841
|
-
"# Copy
|
|
13842
|
-
"
|
|
13843
|
-
"
|
|
13844
|
-
'find "$TMPDIR" -maxdepth 5 -name node_modules -type d | while read nm; do',
|
|
13845
|
-
' rel="${nm#$TMPDIR/}"',
|
|
13846
|
-
' dest="$WORKDIR/$rel"',
|
|
13847
|
-
' rm -rf "$dest"',
|
|
13848
|
-
" # cp -rL dereferences symlinks; fall back to cp -rH if -L is unsupported",
|
|
13849
|
-
' cp -rL "$nm" "$dest" 2>/dev/null || cp -rH "$nm" "$dest" 2>/dev/null || cp -r "$nm" "$dest"',
|
|
13881
|
+
"# Copy lockfiles and config files",
|
|
13882
|
+
"for f in bun.lock bun.lockb yarn.lock pnpm-lock.yaml package-lock.json bunfig.toml .npmrc .yarnrc .yarnrc.yml; do",
|
|
13883
|
+
' [ -f "$WORKDIR/$f" ] && cp "$WORKDIR/$f" "$DEPS_DIR/$f" || true',
|
|
13850
13884
|
"done",
|
|
13851
13885
|
"",
|
|
13852
|
-
|
|
13853
|
-
|
|
13854
|
-
' chmod +x "$WORKDIR/node_modules/.bin/"* 2>/dev/null || true',
|
|
13855
|
-
"fi",
|
|
13856
|
-
"",
|
|
13857
|
-
"# Cleanup",
|
|
13858
|
-
'rm -rf "$TMPDIR"'
|
|
13886
|
+
'cd "$DEPS_DIR"',
|
|
13887
|
+
installCmd
|
|
13859
13888
|
].join(`
|
|
13860
13889
|
`);
|
|
13861
13890
|
}
|
|
13862
|
-
async function runSandboxSetup(sandboxName, projectRoot, containerWorkdir,
|
|
13891
|
+
async function runSandboxSetup(sandboxName, projectRoot, containerWorkdir, _noSymlinks) {
|
|
13863
13892
|
const workdir = containerWorkdir ?? projectRoot;
|
|
13864
13893
|
const ecosystem = detectProjectEcosystem(projectRoot);
|
|
13865
13894
|
const isJS = isJavaScriptEcosystem(ecosystem);
|
|
@@ -13868,45 +13897,30 @@ async function runSandboxSetup(sandboxName, projectRoot, containerWorkdir, noSym
|
|
|
13868
13897
|
if (pm !== "npm") {
|
|
13869
13898
|
await ensurePackageManagerInSandbox(sandboxName, pm);
|
|
13870
13899
|
}
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
13879
|
-
|
|
13880
|
-
|
|
13881
|
-
|
|
13882
|
-
|
|
13883
|
-
|
|
13884
|
-
|
|
13885
|
-
script
|
|
13886
|
-
]);
|
|
13887
|
-
} else {
|
|
13888
|
-
const installCmd = getInstallCommand(pm);
|
|
13889
|
-
process.stderr.write(`
|
|
13890
|
-
Installing dependencies (${bold2(installCmd.join(" "))}) in sandbox ${dim2(sandboxName)}...
|
|
13891
|
-
`);
|
|
13892
|
-
installOk = await runInteractiveCommand("docker", [
|
|
13893
|
-
"sandbox",
|
|
13894
|
-
"exec",
|
|
13895
|
-
"--privileged",
|
|
13896
|
-
"-w",
|
|
13897
|
-
workdir,
|
|
13898
|
-
sandboxName,
|
|
13899
|
-
...installCmd
|
|
13900
|
-
]);
|
|
13901
|
-
}
|
|
13900
|
+
const installCmd = getInstallCommand(pm);
|
|
13901
|
+
process.stderr.write(`
|
|
13902
|
+
Installing sandbox dependencies (${bold2(installCmd.join(" "))}) to container filesystem...
|
|
13903
|
+
`);
|
|
13904
|
+
const script = buildSandboxDepsScript(workdir, pm);
|
|
13905
|
+
const installOk = await runInteractiveCommand("docker", [
|
|
13906
|
+
"sandbox",
|
|
13907
|
+
"exec",
|
|
13908
|
+
"--privileged",
|
|
13909
|
+
sandboxName,
|
|
13910
|
+
"sh",
|
|
13911
|
+
"-c",
|
|
13912
|
+
script
|
|
13913
|
+
]);
|
|
13902
13914
|
if (!installOk) {
|
|
13903
|
-
process.stderr.write(`${
|
|
13915
|
+
process.stderr.write(`${yellow2("⚠")} Sandbox dependency install failed. Platform-specific tools may not work.
|
|
13916
|
+
`);
|
|
13917
|
+
process.stderr.write(` ${dim2("Host node_modules will be used as fallback.")}
|
|
13904
13918
|
`);
|
|
13905
13919
|
return false;
|
|
13906
13920
|
}
|
|
13907
|
-
process.stderr.write(`${green("✓")}
|
|
13921
|
+
process.stderr.write(`${green("✓")} Sandbox dependencies installed.
|
|
13908
13922
|
`);
|
|
13909
|
-
verifyBinEntries(sandboxName,
|
|
13923
|
+
verifyBinEntries(sandboxName, SANDBOX_DEPS_DIR);
|
|
13910
13924
|
} else {
|
|
13911
13925
|
process.stderr.write(`
|
|
13912
13926
|
${dim2(`Detected ${ecosystem} project — skipping JS package install.`)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@locusai/cli",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.13",
|
|
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.13",
|
|
40
40
|
"@types/bun": "latest",
|
|
41
41
|
"typescript": "^5.8.3"
|
|
42
42
|
},
|