@locusai/cli 0.23.4 → 0.23.5

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 +232 -19
  2. package/package.json +2 -2
package/bin/locus.js CHANGED
@@ -11282,6 +11282,7 @@ var exports_status = {};
11282
11282
  __export(exports_status, {
11283
11283
  statusCommand: () => statusCommand
11284
11284
  });
11285
+ import { execSync as execSync18 } from "node:child_process";
11285
11286
  async function statusCommand(projectRoot) {
11286
11287
  const config = loadConfig(projectRoot);
11287
11288
  const spinner = new Spinner;
@@ -11362,12 +11363,223 @@ async function statusCommand(projectRoot) {
11362
11363
  }
11363
11364
  }
11364
11365
  } catch {}
11366
+ const agents = getActiveAgents(config.sandbox);
11367
+ if (agents.length > 0) {
11368
+ lines.push("");
11369
+ lines.push(` ${bold2("Agents:")}`);
11370
+ for (const agent of agents) {
11371
+ const icon = agent.status === "running" ? green("●") : dim2("○");
11372
+ const details = [agent.provider];
11373
+ if (agent.sandbox)
11374
+ details.push(dim2(`sandbox:${agent.sandbox}`));
11375
+ if (agent.pid)
11376
+ details.push(dim2(`pid:${agent.pid}`));
11377
+ if (agent.uptime)
11378
+ details.push(dim2(agent.uptime));
11379
+ if (agent.memory)
11380
+ details.push(dim2(agent.memory));
11381
+ lines.push(` ${icon} ${cyan2(agent.provider)} ${details.slice(1).join(" ")}`);
11382
+ if (agent.sandboxProcesses && agent.sandboxProcesses.length > 0) {
11383
+ for (const proc of agent.sandboxProcesses) {
11384
+ lines.push(` ${dim2("└")} ${yellow2(proc.name)} ${dim2(`pid:${proc.pid}`)}`);
11385
+ }
11386
+ } else if (agent.sandbox && agent.status === "running") {
11387
+ lines.push(` ${dim2("└ no agent processes detected")}`);
11388
+ }
11389
+ }
11390
+ }
11391
+ const registry = loadRegistry();
11392
+ const entries = Object.values(registry.packages);
11393
+ if (entries.length > 0) {
11394
+ const pm2Processes = getPm2Processes();
11395
+ lines.push("");
11396
+ lines.push(` ${bold2("Packages:")}`);
11397
+ for (const entry of entries) {
11398
+ const shortName = extractShortName(entry.name);
11399
+ const processName = `locus-${shortName}`;
11400
+ const proc = pm2Processes.find((p) => p.name === processName);
11401
+ const statusStr = proc ? proc.status === "online" ? green("online") : proc.status === "stopped" ? dim2("stopped") : yellow2(proc.status) : dim2("not running");
11402
+ const details = [`v${entry.version}`, statusStr];
11403
+ if (proc?.pid) {
11404
+ details.push(dim2(`pid:${proc.pid}`));
11405
+ }
11406
+ if (proc?.uptime) {
11407
+ details.push(dim2(formatUptime(proc.uptime)));
11408
+ }
11409
+ if (proc?.memory) {
11410
+ details.push(dim2(`${(proc.memory / (1024 * 1024)).toFixed(0)}MB`));
11411
+ }
11412
+ lines.push(` ${proc?.status === "online" ? green("●") : dim2("○")} ${cyan2(shortName)} ${details.join(" ")}`);
11413
+ }
11414
+ }
11365
11415
  spinner.stop();
11366
11416
  lines.push("");
11367
11417
  process.stderr.write(`
11368
11418
  ${drawBox(lines, { title: "Locus Status" })}
11369
11419
  `);
11370
11420
  }
11421
+ function getPm2Processes() {
11422
+ try {
11423
+ const output = execSync18("npx pm2 jlist", {
11424
+ encoding: "utf-8",
11425
+ stdio: ["pipe", "pipe", "pipe"],
11426
+ timeout: 5000
11427
+ });
11428
+ const processes = JSON.parse(output);
11429
+ return processes.map((p) => ({
11430
+ name: p.name,
11431
+ status: p.pm2_env?.status ?? "unknown",
11432
+ pid: p.pid ?? null,
11433
+ uptime: p.pm2_env?.pm_uptime ?? null,
11434
+ memory: p.monit?.memory ?? null
11435
+ }));
11436
+ } catch {
11437
+ return [];
11438
+ }
11439
+ }
11440
+ function getActiveAgents(sandboxConfig) {
11441
+ const agents = [];
11442
+ if (sandboxConfig.enabled) {
11443
+ const sandboxes = getSandboxProcesses();
11444
+ for (const [provider, name] of Object.entries(sandboxConfig.providers)) {
11445
+ if (!name)
11446
+ continue;
11447
+ const sb = sandboxes.find((s) => s.name === name);
11448
+ const isRunning = !!sb && sb.status === "running";
11449
+ let sandboxProcesses;
11450
+ if (isRunning) {
11451
+ sandboxProcesses = getProcessesInsideSandbox(name);
11452
+ }
11453
+ agents.push({
11454
+ provider,
11455
+ status: isRunning ? "running" : "stopped",
11456
+ sandbox: name,
11457
+ sandboxProcesses
11458
+ });
11459
+ }
11460
+ } else {
11461
+ const procs = getAgentProcesses();
11462
+ agents.push(...procs);
11463
+ }
11464
+ return agents;
11465
+ }
11466
+ function getSandboxProcesses() {
11467
+ try {
11468
+ const output = execSync18("docker sandbox ls", {
11469
+ encoding: "utf-8",
11470
+ stdio: ["pipe", "pipe", "pipe"],
11471
+ timeout: 5000
11472
+ });
11473
+ const lines = output.trim().split(`
11474
+ `);
11475
+ if (lines.length < 2)
11476
+ return [];
11477
+ const header = lines[0];
11478
+ const statusIdx = header.search(/\bSTATUS\b/i);
11479
+ return lines.slice(1).map((line) => {
11480
+ const parts = line.trim().split(/\s+/);
11481
+ const name = parts[0] ?? "";
11482
+ let status = "";
11483
+ if (statusIdx >= 0 && line.length > statusIdx) {
11484
+ const rest = line.substring(statusIdx).trim();
11485
+ status = (rest.split(/\s+/)[0] ?? "").toLowerCase();
11486
+ } else {
11487
+ status = (parts[parts.length - 2] ?? parts[1] ?? "").toLowerCase();
11488
+ }
11489
+ return { name, status };
11490
+ }).filter((s) => s.name.length > 0);
11491
+ } catch {
11492
+ return [];
11493
+ }
11494
+ }
11495
+ function getProcessesInsideSandbox(sandboxName) {
11496
+ try {
11497
+ const output = execSync18(`docker sandbox exec ${sandboxName} sh -c "ps -eo pid,pcpu,pmem,args"`, {
11498
+ encoding: "utf-8",
11499
+ stdio: ["pipe", "pipe", "pipe"],
11500
+ timeout: 1e4
11501
+ });
11502
+ const lines = output.trim().split(`
11503
+ `).slice(1);
11504
+ const processes = [];
11505
+ for (const line of lines) {
11506
+ const lower = line.toLowerCase();
11507
+ let agentName;
11508
+ if (lower.includes("claude"))
11509
+ agentName = "claude";
11510
+ else if (lower.includes("codex"))
11511
+ agentName = "codex";
11512
+ if (!agentName)
11513
+ continue;
11514
+ const pidMatch = line.trim().match(/^(\d+)/);
11515
+ if (!pidMatch)
11516
+ continue;
11517
+ processes.push({
11518
+ name: agentName,
11519
+ pid: pidMatch[1],
11520
+ command: ""
11521
+ });
11522
+ }
11523
+ return processes;
11524
+ } catch {
11525
+ return [];
11526
+ }
11527
+ }
11528
+ function getAgentProcesses() {
11529
+ const agents = [];
11530
+ try {
11531
+ const output = execSync18("ps -eo pid,pcpu,pmem,args", {
11532
+ encoding: "utf-8",
11533
+ stdio: ["pipe", "pipe", "pipe"],
11534
+ timeout: 5000
11535
+ });
11536
+ const lines = output.trim().split(`
11537
+ `).slice(1);
11538
+ const providerMatches = {};
11539
+ for (const line of lines) {
11540
+ const trimmed = line.trim();
11541
+ if (!trimmed)
11542
+ continue;
11543
+ const match = trimmed.match(/^(\d+)\s+([\d.]+)\s+([\d.]+)\s+(.+)$/);
11544
+ if (!match)
11545
+ continue;
11546
+ const [, pid, cpu, mem, command] = match;
11547
+ const cmdLower = command.toLowerCase();
11548
+ if (cmdLower.includes("ps -eo") || cmdLower.includes("grep") || cmdLower.includes("locus status") || cmdLower.includes("locus-cli"))
11549
+ continue;
11550
+ for (const provider of ["claude", "codex"]) {
11551
+ if (providerMatches[provider])
11552
+ continue;
11553
+ const binPattern = new RegExp(`(^|/)${provider}(\\s|$|-)`);
11554
+ if (binPattern.test(command)) {
11555
+ providerMatches[provider] = { pid, cpu, mem, command };
11556
+ }
11557
+ }
11558
+ }
11559
+ for (const [provider, info] of Object.entries(providerMatches)) {
11560
+ agents.push({
11561
+ provider,
11562
+ status: "running",
11563
+ pid: info.pid,
11564
+ memory: info.mem !== "0.0" ? `${info.mem}%` : undefined
11565
+ });
11566
+ }
11567
+ } catch {}
11568
+ return agents;
11569
+ }
11570
+ function formatUptime(pmUptime) {
11571
+ const seconds = Math.floor((Date.now() - pmUptime) / 1000);
11572
+ if (seconds < 60)
11573
+ return `${seconds}s`;
11574
+ const minutes = Math.floor(seconds / 60);
11575
+ if (minutes < 60)
11576
+ return `${minutes}m`;
11577
+ const hours = Math.floor(minutes / 60);
11578
+ if (hours < 24)
11579
+ return `${hours}h ${minutes % 60}m`;
11580
+ const days = Math.floor(hours / 24);
11581
+ return `${days}d ${hours % 24}h`;
11582
+ }
11371
11583
  var init_status = __esm(() => {
11372
11584
  init_config();
11373
11585
  init_github();
@@ -11375,6 +11587,7 @@ var init_status = __esm(() => {
11375
11587
  init_worktree();
11376
11588
  init_progress();
11377
11589
  init_terminal();
11590
+ init_registry();
11378
11591
  });
11379
11592
 
11380
11593
  // src/commands/plan.ts
@@ -12039,7 +12252,7 @@ var exports_review = {};
12039
12252
  __export(exports_review, {
12040
12253
  reviewCommand: () => reviewCommand
12041
12254
  });
12042
- import { execFileSync as execFileSync2, execSync as execSync18 } from "node:child_process";
12255
+ import { execFileSync as execFileSync2, execSync as execSync19 } from "node:child_process";
12043
12256
  import { existsSync as existsSync23, readFileSync as readFileSync14 } from "node:fs";
12044
12257
  import { join as join23 } from "node:path";
12045
12258
  function printHelp3() {
@@ -12125,7 +12338,7 @@ ${bold2("Review complete:")} ${green(`✓ ${reviewed}`)}${failed > 0 ? ` ${red2(
12125
12338
  async function reviewSinglePR(projectRoot, config, prNumber, focus, flags) {
12126
12339
  let prInfo;
12127
12340
  try {
12128
- const result = execSync18(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
12341
+ const result = execSync19(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
12129
12342
  const raw = JSON.parse(result);
12130
12343
  prInfo = {
12131
12344
  number: raw.number,
@@ -12273,7 +12486,7 @@ var exports_iterate = {};
12273
12486
  __export(exports_iterate, {
12274
12487
  iterateCommand: () => iterateCommand
12275
12488
  });
12276
- import { execSync as execSync19 } from "node:child_process";
12489
+ import { execSync as execSync20 } from "node:child_process";
12277
12490
  function printHelp4() {
12278
12491
  process.stderr.write(`
12279
12492
  ${bold2("locus iterate")} — Re-execute tasks with PR feedback
@@ -12491,12 +12704,12 @@ ${bold2("Summary:")} ${green(`✓ ${succeeded}`)}${failed > 0 ? ` ${red2(`✗ ${
12491
12704
  }
12492
12705
  function findPRForIssue(projectRoot, issueNumber) {
12493
12706
  try {
12494
- const result = execSync19(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
12707
+ const result = execSync20(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
12495
12708
  const parsed = JSON.parse(result);
12496
12709
  if (parsed.length > 0) {
12497
12710
  return parsed[0].number;
12498
12711
  }
12499
- const branchResult = execSync19(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
12712
+ const branchResult = execSync20(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
12500
12713
  const branchParsed = JSON.parse(branchResult);
12501
12714
  if (branchParsed.length > 0) {
12502
12715
  return branchParsed[0].number;
@@ -13125,7 +13338,7 @@ var exports_commit = {};
13125
13338
  __export(exports_commit, {
13126
13339
  commitCommand: () => commitCommand
13127
13340
  });
13128
- import { execSync as execSync20 } from "node:child_process";
13341
+ import { execSync as execSync21 } from "node:child_process";
13129
13342
  function printCommitHelp() {
13130
13343
  process.stderr.write(`
13131
13344
  ${bold2("locus commit")} — AI-powered commit message generation
@@ -13159,7 +13372,7 @@ async function commitCommand(projectRoot, args, flags = {}) {
13159
13372
  const config = loadConfig(projectRoot);
13160
13373
  let stagedDiff;
13161
13374
  try {
13162
- stagedDiff = execSync20("git diff --cached", {
13375
+ stagedDiff = execSync21("git diff --cached", {
13163
13376
  cwd: projectRoot,
13164
13377
  encoding: "utf-8",
13165
13378
  stdio: ["pipe", "pipe", "pipe"]
@@ -13176,7 +13389,7 @@ async function commitCommand(projectRoot, args, flags = {}) {
13176
13389
  }
13177
13390
  let stagedStat;
13178
13391
  try {
13179
- stagedStat = execSync20("git diff --cached --stat", {
13392
+ stagedStat = execSync21("git diff --cached --stat", {
13180
13393
  cwd: projectRoot,
13181
13394
  encoding: "utf-8",
13182
13395
  stdio: ["pipe", "pipe", "pipe"]
@@ -13186,7 +13399,7 @@ async function commitCommand(projectRoot, args, flags = {}) {
13186
13399
  }
13187
13400
  let recentCommits;
13188
13401
  try {
13189
- recentCommits = execSync20("git log --oneline -10", {
13402
+ recentCommits = execSync21("git log --oneline -10", {
13190
13403
  cwd: projectRoot,
13191
13404
  encoding: "utf-8",
13192
13405
  stdio: ["pipe", "pipe", "pipe"]
@@ -13246,7 +13459,7 @@ Co-Authored-By: LocusAgent <agent@locusai.team>`;
13246
13459
  return;
13247
13460
  }
13248
13461
  try {
13249
- execSync20("git commit -F -", {
13462
+ execSync21("git commit -F -", {
13250
13463
  input: fullMessage,
13251
13464
  cwd: projectRoot,
13252
13465
  encoding: "utf-8",
@@ -13317,7 +13530,7 @@ __export(exports_sandbox2, {
13317
13530
  parseSandboxLogsArgs: () => parseSandboxLogsArgs,
13318
13531
  parseSandboxInstallArgs: () => parseSandboxInstallArgs
13319
13532
  });
13320
- import { execSync as execSync21, spawn as spawn7 } from "node:child_process";
13533
+ import { execSync as execSync22, spawn as spawn7 } from "node:child_process";
13321
13534
  import { createHash } from "node:crypto";
13322
13535
  import { existsSync as existsSync26, readFileSync as readFileSync17 } from "node:fs";
13323
13536
  import { basename as basename4, join as join26 } from "node:path";
@@ -13562,7 +13775,7 @@ function handleRemove(projectRoot) {
13562
13775
  process.stderr.write(`Removing sandbox ${bold2(sandboxName)}...
13563
13776
  `);
13564
13777
  try {
13565
- execSync21(`docker sandbox rm ${sandboxName}`, {
13778
+ execSync22(`docker sandbox rm ${sandboxName}`, {
13566
13779
  encoding: "utf-8",
13567
13780
  stdio: ["pipe", "pipe", "pipe"],
13568
13781
  timeout: 15000
@@ -13885,7 +14098,7 @@ function getInstallCommand(pm) {
13885
14098
  function verifyBinEntries(sandboxName, workdir) {
13886
14099
  try {
13887
14100
  const binDir = `${workdir}/node_modules/.bin/`;
13888
- const result = execSync21(`docker sandbox exec --privileged ${sandboxName} sh -c ${JSON.stringify(`ls ${JSON.stringify(binDir)} 2>/dev/null | head -20`)}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
14101
+ const result = execSync22(`docker sandbox exec --privileged ${sandboxName} sh -c ${JSON.stringify(`ls ${JSON.stringify(binDir)} 2>/dev/null | head -20`)}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
13889
14102
  if (!result) {
13890
14103
  process.stderr.write(`${yellow2("⚠")} node_modules/.bin is empty — binaries like biome may not be available.
13891
14104
  `);
@@ -14059,7 +14272,7 @@ function runInteractiveCommand(command, args) {
14059
14272
  }
14060
14273
  async function createProviderSandbox(provider, sandboxName, projectRoot) {
14061
14274
  try {
14062
- execSync21(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
14275
+ execSync22(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
14063
14276
  stdio: ["pipe", "pipe", "pipe"],
14064
14277
  timeout: 120000
14065
14278
  });
@@ -14074,7 +14287,7 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
14074
14287
  }
14075
14288
  async function ensurePackageManagerInSandbox(sandboxName, pm) {
14076
14289
  try {
14077
- execSync21(`docker sandbox exec --privileged ${sandboxName} which ${pm}`, {
14290
+ execSync22(`docker sandbox exec --privileged ${sandboxName} which ${pm}`, {
14078
14291
  stdio: ["pipe", "pipe", "pipe"],
14079
14292
  timeout: 5000
14080
14293
  });
@@ -14083,7 +14296,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
14083
14296
  process.stderr.write(`Installing ${bold2(pm)} in sandbox...
14084
14297
  `);
14085
14298
  try {
14086
- execSync21(`docker sandbox exec --privileged ${sandboxName} npm install -g ${npmPkg}`, {
14299
+ execSync22(`docker sandbox exec --privileged ${sandboxName} npm install -g ${npmPkg}`, {
14087
14300
  stdio: "inherit",
14088
14301
  timeout: 120000
14089
14302
  });
@@ -14095,7 +14308,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
14095
14308
  }
14096
14309
  async function ensureCodexInSandbox(sandboxName) {
14097
14310
  try {
14098
- execSync21(`docker sandbox exec --privileged ${sandboxName} which codex`, {
14311
+ execSync22(`docker sandbox exec --privileged ${sandboxName} which codex`, {
14099
14312
  stdio: ["pipe", "pipe", "pipe"],
14100
14313
  timeout: 5000
14101
14314
  });
@@ -14103,7 +14316,7 @@ async function ensureCodexInSandbox(sandboxName) {
14103
14316
  process.stderr.write(`Installing codex in sandbox...
14104
14317
  `);
14105
14318
  try {
14106
- execSync21(`docker sandbox exec --privileged ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
14319
+ execSync22(`docker sandbox exec --privileged ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
14107
14320
  } catch {
14108
14321
  process.stderr.write(`${red2("✗")} Failed to install codex in sandbox.
14109
14322
  `);
@@ -14112,7 +14325,7 @@ async function ensureCodexInSandbox(sandboxName) {
14112
14325
  }
14113
14326
  function isSandboxAlive(name) {
14114
14327
  try {
14115
- const output = execSync21("docker sandbox ls", {
14328
+ const output = execSync22("docker sandbox ls", {
14116
14329
  encoding: "utf-8",
14117
14330
  stdio: ["pipe", "pipe", "pipe"],
14118
14331
  timeout: 5000
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/cli",
3
- "version": "0.23.4",
3
+ "version": "0.23.5",
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.23.4",
39
+ "@locusai/sdk": "^0.23.5",
40
40
  "@types/bun": "latest",
41
41
  "typescript": "^5.8.3"
42
42
  },