@locusai/cli 0.19.0 → 0.19.1

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 +301 -5
  2. package/package.json +3 -3
package/bin/locus.js CHANGED
@@ -7926,7 +7926,6 @@ __export(exports_exec, {
7926
7926
  });
7927
7927
  async function execCommand(projectRoot, args, flags = {}) {
7928
7928
  const config = loadConfig(projectRoot);
7929
- const _log = getLogger();
7930
7929
  if (args[0] === "sessions") {
7931
7930
  return handleSessionSubcommand(projectRoot, args.slice(1));
7932
7931
  }
@@ -8094,7 +8093,6 @@ function formatAge(isoDate) {
8094
8093
  var init_exec = __esm(() => {
8095
8094
  init_runner();
8096
8095
  init_config();
8097
- init_logger();
8098
8096
  init_prompt_builder();
8099
8097
  init_sandbox();
8100
8098
  init_terminal();
@@ -11263,7 +11261,10 @@ var init_artifacts = __esm(() => {
11263
11261
  // src/commands/sandbox.ts
11264
11262
  var exports_sandbox2 = {};
11265
11263
  __export(exports_sandbox2, {
11266
- sandboxCommand: () => sandboxCommand
11264
+ sandboxCommand: () => sandboxCommand,
11265
+ parseSandboxLogsArgs: () => parseSandboxLogsArgs,
11266
+ parseSandboxInstallArgs: () => parseSandboxInstallArgs,
11267
+ parseSandboxExecArgs: () => parseSandboxExecArgs
11267
11268
  });
11268
11269
  import { execSync as execSync17, spawn as spawn6 } from "node:child_process";
11269
11270
  import { createHash } from "node:crypto";
@@ -11276,6 +11277,10 @@ ${bold("Usage:")}
11276
11277
  locus sandbox ${dim("# Create claude/codex sandboxes and enable sandbox mode")}
11277
11278
  locus sandbox claude ${dim("# Run claude interactively (for login)")}
11278
11279
  locus sandbox codex ${dim("# Run codex interactively (for login)")}
11280
+ locus sandbox install <pkg> ${dim("# npm install -g package(s) in sandbox(es)")}
11281
+ locus sandbox exec <provider> -- <cmd...> ${dim("# Run one command inside provider sandbox")}
11282
+ locus sandbox shell <provider> ${dim("# Open interactive shell in provider sandbox")}
11283
+ locus sandbox logs <provider> ${dim("# Show provider sandbox logs")}
11279
11284
  locus sandbox rm ${dim("# Destroy all provider sandboxes and disable sandbox mode")}
11280
11285
  locus sandbox status ${dim("# Show current sandbox state")}
11281
11286
 
@@ -11283,7 +11288,7 @@ ${bold("Flow:")}
11283
11288
  1. ${cyan("locus sandbox")} Create provider sandboxes
11284
11289
  2. ${cyan("locus sandbox claude")} Login Claude inside its sandbox
11285
11290
  3. ${cyan("locus sandbox codex")} Login Codex inside its sandbox
11286
- 4. ${cyan("locus exec")}/${cyan("locus run")} Commands resync + execute in provider sandbox
11291
+ 4. ${cyan("locus sandbox install bun")} Install extra tools (optional)
11287
11292
 
11288
11293
  `);
11289
11294
  }
@@ -11296,6 +11301,14 @@ async function sandboxCommand(projectRoot, args) {
11296
11301
  case "claude":
11297
11302
  case "codex":
11298
11303
  return handleAgentLogin(projectRoot, subcommand);
11304
+ case "install":
11305
+ return handleInstall(projectRoot, args.slice(1));
11306
+ case "exec":
11307
+ return handleExec(projectRoot, args.slice(1));
11308
+ case "shell":
11309
+ return handleShell(projectRoot, args.slice(1));
11310
+ case "logs":
11311
+ return handleLogs(projectRoot, args.slice(1));
11299
11312
  case "rm":
11300
11313
  return handleRemove(projectRoot);
11301
11314
  case "status":
@@ -11305,7 +11318,7 @@ async function sandboxCommand(projectRoot, args) {
11305
11318
  default:
11306
11319
  process.stderr.write(`${red("✗")} Unknown sandbox subcommand: ${bold(subcommand)}
11307
11320
  `);
11308
- process.stderr.write(` Available: ${cyan("claude")}, ${cyan("codex")}, ${cyan("rm")}, ${cyan("status")}
11321
+ process.stderr.write(` Available: ${cyan("claude")}, ${cyan("codex")}, ${cyan("install")}, ${cyan("exec")}, ${cyan("shell")}, ${cyan("logs")}, ${cyan("rm")}, ${cyan("status")}
11309
11322
  `);
11310
11323
  }
11311
11324
  }
@@ -11458,6 +11471,259 @@ ${bold("Sandbox Status")}
11458
11471
  process.stderr.write(`
11459
11472
  `);
11460
11473
  }
11474
+ function parseSandboxInstallArgs(args) {
11475
+ const packages = [];
11476
+ let provider = "all";
11477
+ for (let i = 0;i < args.length; i++) {
11478
+ const token = args[i];
11479
+ if (token === "--provider") {
11480
+ const value = args[i + 1];
11481
+ if (!value) {
11482
+ return {
11483
+ provider,
11484
+ packages,
11485
+ error: "Missing value for --provider (expected claude, codex, or all)."
11486
+ };
11487
+ }
11488
+ if (value !== "claude" && value !== "codex" && value !== "all") {
11489
+ return {
11490
+ provider,
11491
+ packages,
11492
+ error: `Invalid provider "${value}". Expected claude, codex, or all.`
11493
+ };
11494
+ }
11495
+ provider = value;
11496
+ i++;
11497
+ continue;
11498
+ }
11499
+ if (token.startsWith("--provider=")) {
11500
+ const value = token.slice("--provider=".length);
11501
+ if (value !== "claude" && value !== "codex" && value !== "all") {
11502
+ return {
11503
+ provider,
11504
+ packages,
11505
+ error: `Invalid provider "${value}". Expected claude, codex, or all.`
11506
+ };
11507
+ }
11508
+ provider = value;
11509
+ continue;
11510
+ }
11511
+ if (token.startsWith("-")) {
11512
+ return {
11513
+ provider,
11514
+ packages,
11515
+ error: `Unknown option "${token}".`
11516
+ };
11517
+ }
11518
+ packages.push(token);
11519
+ }
11520
+ if (packages.length === 0) {
11521
+ return {
11522
+ provider,
11523
+ packages,
11524
+ error: "Usage: locus sandbox install <package...> [--provider claude|codex|all]"
11525
+ };
11526
+ }
11527
+ return { provider, packages };
11528
+ }
11529
+ async function handleInstall(projectRoot, args) {
11530
+ const parsed = parseSandboxInstallArgs(args);
11531
+ if (parsed.error) {
11532
+ process.stderr.write(`${red("✗")} ${parsed.error}
11533
+ `);
11534
+ return;
11535
+ }
11536
+ const config = loadConfig(projectRoot);
11537
+ const targets = getTargetProviders(config.sandbox.providers, parsed.provider);
11538
+ if (targets.length === 0) {
11539
+ process.stderr.write(`${red("✗")} No provider sandboxes are configured. Run ${cyan("locus sandbox")} first.
11540
+ `);
11541
+ return;
11542
+ }
11543
+ let anySucceeded = false;
11544
+ let anyFailed = false;
11545
+ for (const provider of targets) {
11546
+ const sandboxName = config.sandbox.providers[provider];
11547
+ if (!sandboxName) {
11548
+ process.stderr.write(`${yellow("⚠")} ${provider} sandbox is not configured. Run ${cyan("locus sandbox")} first.
11549
+ `);
11550
+ anyFailed = true;
11551
+ continue;
11552
+ }
11553
+ if (!isSandboxAlive(sandboxName)) {
11554
+ process.stderr.write(`${yellow("⚠")} ${provider} sandbox is not running: ${dim(sandboxName)}
11555
+ `);
11556
+ anyFailed = true;
11557
+ continue;
11558
+ }
11559
+ process.stderr.write(`Installing ${bold(parsed.packages.join(", "))} in ${provider} sandbox ${dim(sandboxName)}...
11560
+ `);
11561
+ const ok = await runInteractiveCommand("docker", [
11562
+ "sandbox",
11563
+ "exec",
11564
+ sandboxName,
11565
+ "npm",
11566
+ "install",
11567
+ "-g",
11568
+ ...parsed.packages
11569
+ ]);
11570
+ if (ok) {
11571
+ anySucceeded = true;
11572
+ process.stderr.write(`${green("✓")} Installed package(s) in ${provider} sandbox.
11573
+ `);
11574
+ } else {
11575
+ anyFailed = true;
11576
+ process.stderr.write(`${red("✗")} Failed to install package(s) in ${provider} sandbox.
11577
+ `);
11578
+ }
11579
+ }
11580
+ if (!anySucceeded && anyFailed) {
11581
+ process.stderr.write(`${yellow("⚠")} No package installs completed successfully.
11582
+ `);
11583
+ }
11584
+ }
11585
+ function parseSandboxExecArgs(args) {
11586
+ if (args.length === 0) {
11587
+ return {
11588
+ command: [],
11589
+ error: "Usage: locus sandbox exec <provider> -- <command...>"
11590
+ };
11591
+ }
11592
+ const provider = args[0];
11593
+ if (provider !== "claude" && provider !== "codex") {
11594
+ return {
11595
+ command: [],
11596
+ error: `Invalid provider "${provider}". Expected claude or codex.`
11597
+ };
11598
+ }
11599
+ const separatorIndex = args.indexOf("--");
11600
+ const command = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : args.slice(1);
11601
+ if (command.length === 0) {
11602
+ return {
11603
+ provider,
11604
+ command: [],
11605
+ error: "Missing command. Example: locus sandbox exec codex -- bun --version"
11606
+ };
11607
+ }
11608
+ return { provider, command };
11609
+ }
11610
+ async function handleExec(projectRoot, args) {
11611
+ const parsed = parseSandboxExecArgs(args);
11612
+ if (parsed.error || !parsed.provider) {
11613
+ process.stderr.write(`${red("✗")} ${parsed.error}
11614
+ `);
11615
+ return;
11616
+ }
11617
+ const sandboxName = getActiveProviderSandbox(projectRoot, parsed.provider);
11618
+ if (!sandboxName) {
11619
+ return;
11620
+ }
11621
+ await runInteractiveCommand("docker", [
11622
+ "sandbox",
11623
+ "exec",
11624
+ "-w",
11625
+ projectRoot,
11626
+ sandboxName,
11627
+ ...parsed.command
11628
+ ]);
11629
+ }
11630
+ async function handleShell(projectRoot, args) {
11631
+ const provider = args[0];
11632
+ if (provider !== "claude" && provider !== "codex") {
11633
+ process.stderr.write(`${red("✗")} Usage: locus sandbox shell <provider> (provider: claude|codex)
11634
+ `);
11635
+ return;
11636
+ }
11637
+ const sandboxName = getActiveProviderSandbox(projectRoot, provider);
11638
+ if (!sandboxName) {
11639
+ return;
11640
+ }
11641
+ process.stderr.write(`Opening shell in ${provider} sandbox ${dim(sandboxName)}...
11642
+ `);
11643
+ await runInteractiveCommand("docker", [
11644
+ "sandbox",
11645
+ "exec",
11646
+ "-it",
11647
+ "-w",
11648
+ projectRoot,
11649
+ sandboxName,
11650
+ "sh"
11651
+ ]);
11652
+ }
11653
+ function parseSandboxLogsArgs(args) {
11654
+ if (args.length === 0) {
11655
+ return {
11656
+ follow: false,
11657
+ error: "Usage: locus sandbox logs <provider> [--follow] [--tail <lines>]"
11658
+ };
11659
+ }
11660
+ const provider = args[0];
11661
+ if (provider !== "claude" && provider !== "codex") {
11662
+ return {
11663
+ follow: false,
11664
+ error: `Invalid provider "${provider}". Expected claude or codex.`
11665
+ };
11666
+ }
11667
+ let follow = false;
11668
+ let tail;
11669
+ for (let i = 1;i < args.length; i++) {
11670
+ const token = args[i];
11671
+ if (token === "--follow" || token === "-f") {
11672
+ follow = true;
11673
+ continue;
11674
+ }
11675
+ if (token === "--tail") {
11676
+ const value = args[i + 1];
11677
+ if (!value) {
11678
+ return { provider, follow, tail, error: "Missing value for --tail." };
11679
+ }
11680
+ const parsedTail = Number.parseInt(value, 10);
11681
+ if (!Number.isFinite(parsedTail) || parsedTail < 0) {
11682
+ return { provider, follow, tail, error: "Invalid --tail value." };
11683
+ }
11684
+ tail = parsedTail;
11685
+ i++;
11686
+ continue;
11687
+ }
11688
+ if (token.startsWith("--tail=")) {
11689
+ const value = token.slice("--tail=".length);
11690
+ const parsedTail = Number.parseInt(value, 10);
11691
+ if (!Number.isFinite(parsedTail) || parsedTail < 0) {
11692
+ return { provider, follow, tail, error: "Invalid --tail value." };
11693
+ }
11694
+ tail = parsedTail;
11695
+ continue;
11696
+ }
11697
+ return {
11698
+ provider,
11699
+ follow,
11700
+ tail,
11701
+ error: `Unknown option "${token}".`
11702
+ };
11703
+ }
11704
+ return { provider, follow, tail };
11705
+ }
11706
+ async function handleLogs(projectRoot, args) {
11707
+ const parsed = parseSandboxLogsArgs(args);
11708
+ if (parsed.error || !parsed.provider) {
11709
+ process.stderr.write(`${red("✗")} ${parsed.error}
11710
+ `);
11711
+ return;
11712
+ }
11713
+ const sandboxName = getActiveProviderSandbox(projectRoot, parsed.provider);
11714
+ if (!sandboxName) {
11715
+ return;
11716
+ }
11717
+ const dockerArgs = ["sandbox", "logs"];
11718
+ if (parsed.follow) {
11719
+ dockerArgs.push("--follow");
11720
+ }
11721
+ if (parsed.tail !== undefined) {
11722
+ dockerArgs.push("--tail", String(parsed.tail));
11723
+ }
11724
+ dockerArgs.push(sandboxName);
11725
+ await runInteractiveCommand("docker", dockerArgs);
11726
+ }
11461
11727
  function buildProviderSandboxNames(projectRoot) {
11462
11728
  const segment = sanitizeSegment(basename4(projectRoot));
11463
11729
  const hash = createHash("sha1").update(projectRoot).digest("hex").slice(0, 8);
@@ -11470,6 +11736,36 @@ function sanitizeSegment(input) {
11470
11736
  const cleaned = input.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
11471
11737
  return cleaned || "workspace";
11472
11738
  }
11739
+ function getTargetProviders(sandboxes, provider) {
11740
+ if (provider === "claude" || provider === "codex") {
11741
+ return [provider];
11742
+ }
11743
+ return PROVIDERS.filter((name) => Boolean(sandboxes[name]));
11744
+ }
11745
+ function getActiveProviderSandbox(projectRoot, provider) {
11746
+ const config = loadConfig(projectRoot);
11747
+ const sandboxName = config.sandbox.providers[provider];
11748
+ if (!sandboxName) {
11749
+ process.stderr.write(`${red("✗")} No ${provider} sandbox configured. Run ${cyan("locus sandbox")} first.
11750
+ `);
11751
+ return null;
11752
+ }
11753
+ if (!isSandboxAlive(sandboxName)) {
11754
+ process.stderr.write(`${red("✗")} ${provider} sandbox is not running: ${dim(sandboxName)}
11755
+ `);
11756
+ process.stderr.write(` Recreate it with ${cyan("locus sandbox")}.
11757
+ `);
11758
+ return null;
11759
+ }
11760
+ return sandboxName;
11761
+ }
11762
+ function runInteractiveCommand(command, args) {
11763
+ return new Promise((resolve2) => {
11764
+ const child = spawn6(command, args, { stdio: "inherit" });
11765
+ child.on("close", (code) => resolve2(code === 0));
11766
+ child.on("error", () => resolve2(false));
11767
+ });
11768
+ }
11473
11769
  async function createProviderSandbox(provider, sandboxName, projectRoot) {
11474
11770
  try {
11475
11771
  execSync17(`docker sandbox run --name ${sandboxName} claude ${projectRoot} -- --version`, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/cli",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "description": "GitHub-native AI engineering assistant",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,8 +10,8 @@
10
10
  "build": "bun build src/cli.ts --outfile bin/locus.js --target node",
11
11
  "dev": "bun run src/cli.ts",
12
12
  "test": "bun test",
13
- "test:unit": "bun test src/core/__tests__ src/repl/__tests__ src/display/__tests__ src/ai/__tests__",
14
- "test:integration": "bun test src/commands/__tests__",
13
+ "test:unit": "bun test __tests__",
14
+ "test:integration": "bun test __tests__",
15
15
  "test:watch": "bun test --watch",
16
16
  "test:coverage": "bun test --coverage",
17
17
  "typecheck": "tsc --noEmit",