@locusai/cli 0.15.2 → 0.15.4

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.
@@ -15621,6 +15621,7 @@ var init_resolve_bin = __esm(() => {
15621
15621
  join2(homedir(), ".local", "bin"),
15622
15622
  join2(homedir(), ".npm", "bin"),
15623
15623
  join2(homedir(), ".npm-global", "bin"),
15624
+ join2(homedir(), ".npm-packages", "bin"),
15624
15625
  join2(homedir(), ".yarn", "bin"),
15625
15626
  join2(homedir(), ".bun", "bin"),
15626
15627
  join2(homedir(), "Library", "pnpm"),
package/bin/locus.js CHANGED
@@ -7055,6 +7055,7 @@ var init_resolve_bin = __esm(() => {
7055
7055
  join4(homedir(), ".local", "bin"),
7056
7056
  join4(homedir(), ".npm", "bin"),
7057
7057
  join4(homedir(), ".npm-global", "bin"),
7058
+ join4(homedir(), ".npm-packages", "bin"),
7058
7059
  join4(homedir(), ".yarn", "bin"),
7059
7060
  join4(homedir(), ".bun", "bin"),
7060
7061
  join4(homedir(), "Library", "pnpm"),
@@ -43125,31 +43126,19 @@ class ProgressRenderer {
43125
43126
  toolDisplay = new ToolDisplay;
43126
43127
  toolDisplayShown = false;
43127
43128
  thinkingShown = false;
43128
- animated;
43129
43129
  spinnerInterval = null;
43130
43130
  spinnerFrameIndex = 0;
43131
43131
  thinkingStartTime = null;
43132
43132
  isInTextBlock = false;
43133
43133
  textBuffer = "";
43134
- constructor(options = {}) {
43135
- this.animated = options.animated ?? false;
43136
- }
43137
43134
  showThinkingStarted() {
43138
43135
  if (this.isThinking)
43139
43136
  return;
43140
43137
  this.isThinking = true;
43141
43138
  this.thinkingStartTime = Date.now();
43142
- if (this.animated) {
43143
- if (!this.thinkingShown) {
43144
- this.thinkingShown = true;
43145
- this.startThinkingAnimation();
43146
- }
43147
- } else {
43148
- if (!this.thinkingShown) {
43149
- console.log(c.dim(`\uD83E\uDD14 Thinking...
43150
- `));
43151
- this.thinkingShown = true;
43152
- }
43139
+ if (!this.thinkingShown) {
43140
+ this.thinkingShown = true;
43141
+ this.startThinkingAnimation();
43153
43142
  }
43154
43143
  }
43155
43144
  showThinkingStopped() {
@@ -43180,7 +43169,7 @@ class ProgressRenderer {
43180
43169
  clearInterval(this.spinnerInterval);
43181
43170
  this.spinnerInterval = null;
43182
43171
  }
43183
- if (this.animated && this.thinkingShown && this.isThinking) {
43172
+ if (this.thinkingShown && this.isThinking) {
43184
43173
  process.stdout.write(`${ANSI.MOVE_TO_START}${ANSI.CLEAR_LINE}
43185
43174
  `);
43186
43175
  }
@@ -44123,7 +44112,7 @@ class InteractiveSession {
44123
44112
  model: options.model
44124
44113
  });
44125
44114
  this.promptBuilder = new PromptBuilder(options.projectPath);
44126
- this.renderer = new ProgressRenderer({ animated: true });
44115
+ this.renderer = new ProgressRenderer;
44127
44116
  this.historyManager = new HistoryManager(options.projectPath);
44128
44117
  this.projectPath = options.projectPath;
44129
44118
  this.model = options.model;
@@ -44865,7 +44854,7 @@ async function discussCommand(args) {
44865
44854
  console.log(` ${c.dim("Topic:")} ${c.bold(topic)}`);
44866
44855
  console.log(` ${c.dim("Model:")} ${c.dim(`${model} (${provider})`)}
44867
44856
  `);
44868
- const renderer = new ProgressRenderer({ animated: true });
44857
+ const renderer = new ProgressRenderer;
44869
44858
  let discussionId;
44870
44859
  try {
44871
44860
  renderer.showThinkingStarted();
@@ -44929,7 +44918,7 @@ async function discussCommand(args) {
44929
44918
  }
44930
44919
  if (lowerInput === "summary") {
44931
44920
  isProcessing = true;
44932
- const summaryRenderer = new ProgressRenderer({ animated: true });
44921
+ const summaryRenderer = new ProgressRenderer;
44933
44922
  try {
44934
44923
  summaryRenderer.showThinkingStarted();
44935
44924
  const summary = await facilitator.summarizeDiscussion(discussionId);
@@ -44973,7 +44962,7 @@ async function discussCommand(args) {
44973
44962
  const cleanedInput = stripImagePaths(trimmed, images);
44974
44963
  const effectiveInput = cleanedInput + buildImageContext(images);
44975
44964
  isProcessing = true;
44976
- const chunkRenderer = new ProgressRenderer({ animated: true });
44965
+ const chunkRenderer = new ProgressRenderer;
44977
44966
  try {
44978
44967
  chunkRenderer.showThinkingStarted();
44979
44968
  const stream4 = facilitator.continueDiscussionStream(discussionId, effectiveInput);
@@ -46274,14 +46263,93 @@ init_index_node();
46274
46263
  init_settings_manager();
46275
46264
  init_utils3();
46276
46265
  import { spawn as spawn4 } from "node:child_process";
46277
- import { existsSync as existsSync20, writeFileSync as writeFileSync9 } from "node:fs";
46266
+ import { existsSync as existsSync20, readdirSync as readdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "node:fs";
46278
46267
  import { homedir as homedir3 } from "node:os";
46279
- import { join as join20 } from "node:path";
46268
+ import { dirname as dirname4, join as join20 } from "node:path";
46280
46269
  async function findBinary() {
46281
46270
  const result = await runShell("which", ["locus-telegram"]);
46282
46271
  const p = result.stdout.trim();
46283
46272
  return p?.startsWith?.("/") ? p : null;
46284
46273
  }
46274
+ async function findBinDir(binary) {
46275
+ const result = await runShell("which", [binary]);
46276
+ const p = result.stdout.trim();
46277
+ if (p?.startsWith?.("/"))
46278
+ return dirname4(p);
46279
+ return null;
46280
+ }
46281
+ function resolveNvmBinDir() {
46282
+ const nvmDir = process.env.NVM_DIR || join20(homedir3(), ".nvm");
46283
+ const versionsDir = join20(nvmDir, "versions", "node");
46284
+ if (!existsSync20(versionsDir))
46285
+ return null;
46286
+ let versions2;
46287
+ try {
46288
+ versions2 = readdirSync6(versionsDir).filter((d) => d.startsWith("v"));
46289
+ } catch {
46290
+ return null;
46291
+ }
46292
+ if (versions2.length === 0)
46293
+ return null;
46294
+ const currentNodeVersion = `v${process.versions.node}`;
46295
+ const currentBin = join20(versionsDir, currentNodeVersion, "bin");
46296
+ if (versions2.includes(currentNodeVersion) && existsSync20(currentBin)) {
46297
+ return currentBin;
46298
+ }
46299
+ const aliasPath = join20(nvmDir, "alias", "default");
46300
+ if (existsSync20(aliasPath)) {
46301
+ try {
46302
+ const alias = readFileSync15(aliasPath, "utf-8").trim();
46303
+ const match = versions2.find((v) => v === `v${alias}` || v.startsWith(`v${alias}.`));
46304
+ if (match) {
46305
+ const bin2 = join20(versionsDir, match, "bin");
46306
+ if (existsSync20(bin2))
46307
+ return bin2;
46308
+ }
46309
+ } catch {}
46310
+ }
46311
+ const sorted = versions2.sort((a, b) => {
46312
+ const pa = a.slice(1).split(".").map(Number);
46313
+ const pb = b.slice(1).split(".").map(Number);
46314
+ for (let i = 0;i < 3; i++) {
46315
+ if ((pa[i] || 0) !== (pb[i] || 0))
46316
+ return (pb[i] || 0) - (pa[i] || 0);
46317
+ }
46318
+ return 0;
46319
+ });
46320
+ const bin = join20(versionsDir, sorted[0], "bin");
46321
+ return existsSync20(bin) ? bin : null;
46322
+ }
46323
+ async function buildServicePath() {
46324
+ const home = homedir3();
46325
+ const dirs = new Set;
46326
+ dirs.add("/usr/local/bin");
46327
+ dirs.add("/usr/bin");
46328
+ dirs.add("/bin");
46329
+ const candidates = [
46330
+ join20(home, ".bun", "bin"),
46331
+ join20(home, ".local", "bin"),
46332
+ join20(home, ".npm", "bin"),
46333
+ join20(home, ".npm-global", "bin"),
46334
+ join20(home, ".yarn", "bin")
46335
+ ];
46336
+ for (const d of candidates) {
46337
+ if (existsSync20(d))
46338
+ dirs.add(d);
46339
+ }
46340
+ const nvmBin = resolveNvmBinDir();
46341
+ if (nvmBin)
46342
+ dirs.add(nvmBin);
46343
+ const fnmCurrent = join20(home, ".fnm", "current", "bin");
46344
+ if (existsSync20(fnmCurrent))
46345
+ dirs.add(fnmCurrent);
46346
+ for (const bin of ["claude", "codex"]) {
46347
+ const dir = await findBinDir(bin);
46348
+ if (dir)
46349
+ dirs.add(dir);
46350
+ }
46351
+ return Array.from(dirs).join(":");
46352
+ }
46285
46353
  var SERVICE_NAME = "locus";
46286
46354
  var SYSTEMD_UNIT_PATH = `/etc/systemd/system/${SERVICE_NAME}.service`;
46287
46355
  var PLIST_LABEL = "com.locus.agent";
@@ -46321,7 +46389,21 @@ function runShell(cmd, args) {
46321
46389
  proc.on("error", (err) => resolve2({ exitCode: 1, stdout, stderr: err.message }));
46322
46390
  });
46323
46391
  }
46324
- function generateSystemdUnit(projectPath, user2, binaryPath) {
46392
+ async function killOrphanedProcesses() {
46393
+ const result = await runShell("pgrep", ["-f", "locus-telegram"]);
46394
+ const pids = result.stdout.trim().split(`
46395
+ `).filter((p) => p.length > 0);
46396
+ if (pids.length === 0)
46397
+ return;
46398
+ console.log(` ${c.info("▶")} Killing ${pids.length} orphaned locus-telegram process${pids.length > 1 ? "es" : ""}...`);
46399
+ await runShell("pkill", ["-f", "locus-telegram"]);
46400
+ await new Promise((resolve2) => setTimeout(resolve2, 2000));
46401
+ const check2 = await runShell("pgrep", ["-f", "locus-telegram"]);
46402
+ if (check2.stdout.trim().length > 0) {
46403
+ await runShell("pkill", ["-9", "-f", "locus-telegram"]);
46404
+ }
46405
+ }
46406
+ function generateSystemdUnit(projectPath, user2, binaryPath, servicePath) {
46325
46407
  return `[Unit]
46326
46408
  Description=Locus AI Agent (Telegram bot + proposal scheduler)
46327
46409
  After=network-online.target
@@ -46334,7 +46416,7 @@ WorkingDirectory=${projectPath}
46334
46416
  ExecStart=${binaryPath}
46335
46417
  Restart=on-failure
46336
46418
  RestartSec=10
46337
- Environment=PATH=/usr/local/bin:/usr/bin:/bin:${homedir3()}/.bun/bin:${homedir3()}/.nvm/current/bin:${homedir3()}/.local/bin
46419
+ Environment=PATH=${servicePath}
46338
46420
  Environment=HOME=${homedir3()}
46339
46421
 
46340
46422
  [Install]
@@ -46351,7 +46433,22 @@ async function installSystemd(projectPath) {
46351
46433
  `);
46352
46434
  process.exit(1);
46353
46435
  }
46354
- const unit = generateSystemdUnit(projectPath, user2, binaryPath);
46436
+ if (!await findBinDir("claude")) {
46437
+ console.warn(`
46438
+ ${c.secondary("⚠")} ${c.bold("Could not find 'claude' CLI in PATH.")}
46439
+ ` + ` The service needs the Claude Code CLI to execute tasks.
46440
+ ` + ` Install with: ${c.primary("npm install -g @anthropic-ai/claude-code")}
46441
+ `);
46442
+ }
46443
+ if (!await findBinDir("codex")) {
46444
+ console.warn(`
46445
+ ${c.secondary("⚠")} ${c.bold("Could not find 'codex' CLI in PATH.")}
46446
+ ` + ` The service needs the Codex CLI if using the Codex provider.
46447
+ ` + ` Install with: ${c.primary("npm install -g @openai/codex")}
46448
+ `);
46449
+ }
46450
+ const servicePath = await buildServicePath();
46451
+ const unit = generateSystemdUnit(projectPath, user2, binaryPath, servicePath);
46355
46452
  console.log(`
46356
46453
  ${c.info("▶")} Writing systemd unit to ${c.dim(SYSTEMD_UNIT_PATH)}`);
46357
46454
  writeFileSync9(SYSTEMD_UNIT_PATH, unit, "utf-8");
@@ -46383,6 +46480,7 @@ async function uninstallSystemd() {
46383
46480
  console.log(`
46384
46481
  ${c.dim("No systemd service found. Nothing to remove.")}
46385
46482
  `);
46483
+ await killOrphanedProcesses();
46386
46484
  return;
46387
46485
  }
46388
46486
  console.log(` ${c.info("▶")} Stopping and disabling ${SERVICE_NAME}...`);
@@ -46391,6 +46489,7 @@ async function uninstallSystemd() {
46391
46489
  const { unlinkSync: unlinkSync6 } = await import("node:fs");
46392
46490
  unlinkSync6(SYSTEMD_UNIT_PATH);
46393
46491
  await runShell("systemctl", ["daemon-reload"]);
46492
+ await killOrphanedProcesses();
46394
46493
  console.log(`
46395
46494
  ${c.success("✔")} ${c.bold("Locus service removed.")}
46396
46495
  `);
@@ -46416,7 +46515,7 @@ async function statusSystemd() {
46416
46515
  `);
46417
46516
  }
46418
46517
  }
46419
- function generatePlist(projectPath, binaryPath, binaryArgs) {
46518
+ function generatePlist(projectPath, binaryPath, binaryArgs, servicePath) {
46420
46519
  const argsXml = [binaryPath, ...binaryArgs].map((a) => ` <string>${a}</string>`).join(`
46421
46520
  `);
46422
46521
  const logDir = join20(homedir3(), "Library/Logs/Locus");
@@ -46443,7 +46542,7 @@ ${argsXml}
46443
46542
  <key>EnvironmentVariables</key>
46444
46543
  <dict>
46445
46544
  <key>PATH</key>
46446
- <string>/usr/local/bin:/usr/bin:/bin:${homedir3()}/.bun/bin:${homedir3()}/.nvm/current/bin:${homedir3()}/.local/bin</string>
46545
+ <string>${servicePath}</string>
46447
46546
  </dict>
46448
46547
  </dict>
46449
46548
  </plist>
@@ -46463,12 +46562,27 @@ async function installLaunchd(projectPath) {
46463
46562
  process.exit(1);
46464
46563
  }
46465
46564
  const binaryArgs = [];
46565
+ if (!await findBinDir("claude")) {
46566
+ console.warn(`
46567
+ ${c.secondary("⚠")} ${c.bold("Could not find 'claude' CLI in PATH.")}
46568
+ The service needs the Claude Code CLI to execute tasks.
46569
+ Install with: ${c.primary("npm install -g @anthropic-ai/claude-code")}
46570
+ `);
46571
+ }
46572
+ if (!await findBinDir("codex")) {
46573
+ console.warn(`
46574
+ ${c.secondary("⚠")} ${c.bold("Could not find 'codex' CLI in PATH.")}
46575
+ The service needs the Codex CLI if using the Codex provider.
46576
+ Install with: ${c.primary("npm install -g @openai/codex")}
46577
+ `);
46578
+ }
46466
46579
  const logDir = join20(homedir3(), "Library/Logs/Locus");
46467
46580
  const { mkdirSync: mkdirSync10 } = await import("node:fs");
46468
46581
  mkdirSync10(logDir, { recursive: true });
46469
46582
  const launchAgentsDir = join20(homedir3(), "Library/LaunchAgents");
46470
46583
  mkdirSync10(launchAgentsDir, { recursive: true });
46471
- const plist = generatePlist(projectPath, binaryPath, binaryArgs);
46584
+ const servicePath = await buildServicePath();
46585
+ const plist = generatePlist(projectPath, binaryPath, binaryArgs, servicePath);
46472
46586
  console.log(`
46473
46587
  ${c.info("▶")} Writing plist to ${c.dim(plistPath)}`);
46474
46588
  writeFileSync9(plistPath, plist, "utf-8");
@@ -46497,12 +46611,14 @@ async function uninstallLaunchd() {
46497
46611
  console.log(`
46498
46612
  ${c.dim("No launchd service found. Nothing to remove.")}
46499
46613
  `);
46614
+ await killOrphanedProcesses();
46500
46615
  return;
46501
46616
  }
46502
46617
  console.log(` ${c.info("▶")} Unloading service...`);
46503
46618
  await runShell("launchctl", ["unload", plistPath]);
46504
46619
  const { unlinkSync: unlinkSync6 } = await import("node:fs");
46505
46620
  unlinkSync6(plistPath);
46621
+ await killOrphanedProcesses();
46506
46622
  console.log(`
46507
46623
  ${c.success("✔")} ${c.bold("Locus service removed.")}
46508
46624
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/cli",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
4
4
  "description": "CLI for Locus - AI-native project management platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,9 +32,9 @@
32
32
  "author": "",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "@locusai/sdk": "^0.15.2",
36
- "@locusai/shared": "^0.15.2",
37
- "@locusai/telegram": "^0.15.2"
35
+ "@locusai/sdk": "^0.15.4",
36
+ "@locusai/shared": "^0.15.4",
37
+ "@locusai/telegram": "^0.15.4"
38
38
  },
39
39
  "devDependencies": {}
40
40
  }