@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.
- package/bin/locus.js +301 -5
- 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
|
|
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.
|
|
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
|
|
14
|
-
"test:integration": "bun test
|
|
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",
|