@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.
Files changed (2) hide show
  1. package/bin/locus.js +108 -94
  2. 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
- const learnings = readFileSafe(join14(projectRoot, ".locus", "LEARNINGS.md"));
7588
- if (learnings) {
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
- const learnings = readFileSafe(join14(projectRoot, ".locus", "LEARNINGS.md"));
7621
- if (learnings) {
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", ["sandbox", "exec", "-it", "-w", workdir, sandboxName, agent], {
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 buildNoSymlinksInstallScript(workdir, pm) {
13864
+ function buildSandboxDepsScript(workdir, pm) {
13812
13865
  const installCmd = getInstallCommand(pm).join(" ");
13813
13866
  return [
13814
13867
  "set -e",
13815
- 'TMPDIR="/tmp/locus-deps-install"',
13868
+ `DEPS_DIR="${SANDBOX_DEPS_DIR}"`,
13816
13869
  `WORKDIR="${workdir}"`,
13817
13870
  "",
13818
- "# Clean previous attempt",
13819
- 'rm -rf "$TMPDIR"',
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 lockfiles and config files that affect installation",
13830
- "for lf in bun.lock bun.lockb yarn.lock pnpm-lock.yaml package-lock.json; do",
13831
- ' [ -f "$WORKDIR/$lf" ] && cp "$WORKDIR/$lf" "$TMPDIR/$lf" || true',
13832
- "done",
13833
- "for cf in bunfig.toml .npmrc .yarnrc .yarnrc.yml .pnpmfile.cjs pnpm-workspace.yaml; do",
13834
- ' [ -f "$WORKDIR/$cf" ] && cp "$WORKDIR/$cf" "$TMPDIR/$cf" || true',
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 node_modules back to bind mount, dereferencing all symlinks.",
13842
- "# -L follows symlinks so they become regular files/dirs on the bind mount.",
13843
- "# maxdepth 5 covers root + nested workspace packages",
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
- "# Ensure .bin entries are executable (some cp implementations drop +x)",
13853
- 'if [ -d "$WORKDIR/node_modules/.bin" ]; then',
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, noSymlinks) {
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
- let installOk;
13872
- if (noSymlinks) {
13873
- const installCmd = getInstallCommand(pm);
13874
- process.stderr.write(`
13875
- Installing dependencies (${bold2(installCmd.join(" "))}, symlink-free) in sandbox ${dim2(sandboxName)}...
13876
- `);
13877
- const script = buildNoSymlinksInstallScript(workdir, pm);
13878
- installOk = await runInteractiveCommand("docker", [
13879
- "sandbox",
13880
- "exec",
13881
- "--privileged",
13882
- sandboxName,
13883
- "sh",
13884
- "-c",
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(`${red2("")} Dependency install failed in sandbox ${dim2(sandboxName)}.
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("✓")} Dependencies installed in sandbox ${dim2(sandboxName)}.
13921
+ process.stderr.write(`${green("✓")} Sandbox dependencies installed.
13908
13922
  `);
13909
- verifyBinEntries(sandboxName, workdir);
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.12",
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.12",
39
+ "@locusai/sdk": "^0.22.13",
40
40
  "@types/bun": "latest",
41
41
  "typescript": "^5.8.3"
42
42
  },