@locusai/cli 0.23.5 → 0.24.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 +81 -57
  2. package/package.json +2 -2
package/bin/locus.js CHANGED
@@ -11283,6 +11283,8 @@ __export(exports_status, {
11283
11283
  statusCommand: () => statusCommand
11284
11284
  });
11285
11285
  import { execSync as execSync18 } from "node:child_process";
11286
+ import { existsSync as existsSync22 } from "node:fs";
11287
+ import { dirname as dirname7, join as join22 } from "node:path";
11286
11288
  async function statusCommand(projectRoot) {
11287
11289
  const config = loadConfig(projectRoot);
11288
11290
  const spinner = new Spinner;
@@ -11418,9 +11420,31 @@ async function statusCommand(projectRoot) {
11418
11420
  ${drawBox(lines, { title: "Locus Status" })}
11419
11421
  `);
11420
11422
  }
11423
+ function getPm2Bin() {
11424
+ const pkgsBin = join22(getPackagesDir(), "node_modules", ".bin", "pm2");
11425
+ if (existsSync22(pkgsBin))
11426
+ return pkgsBin;
11427
+ let dir = process.cwd();
11428
+ while (dir !== dirname7(dir)) {
11429
+ const candidate = join22(dir, "node_modules", ".bin", "pm2");
11430
+ if (existsSync22(candidate))
11431
+ return candidate;
11432
+ dir = dirname7(dir);
11433
+ }
11434
+ try {
11435
+ const result = execSync18("which pm2", {
11436
+ encoding: "utf-8",
11437
+ stdio: ["pipe", "pipe", "pipe"]
11438
+ }).trim();
11439
+ if (result)
11440
+ return result;
11441
+ } catch {}
11442
+ return "npx pm2";
11443
+ }
11421
11444
  function getPm2Processes() {
11422
11445
  try {
11423
- const output = execSync18("npx pm2 jlist", {
11446
+ const pm2 = getPm2Bin();
11447
+ const output = execSync18(`${pm2} jlist`, {
11424
11448
  encoding: "utf-8",
11425
11449
  stdio: ["pipe", "pipe", "pipe"],
11426
11450
  timeout: 5000
@@ -11599,13 +11623,13 @@ __export(exports_plan, {
11599
11623
  parsePlanArgs: () => parsePlanArgs
11600
11624
  });
11601
11625
  import {
11602
- existsSync as existsSync22,
11626
+ existsSync as existsSync23,
11603
11627
  mkdirSync as mkdirSync15,
11604
11628
  readdirSync as readdirSync8,
11605
11629
  readFileSync as readFileSync13,
11606
11630
  writeFileSync as writeFileSync10
11607
11631
  } from "node:fs";
11608
- import { join as join22 } from "node:path";
11632
+ import { join as join23 } from "node:path";
11609
11633
  function printHelp2() {
11610
11634
  process.stderr.write(`
11611
11635
  ${bold2("locus plan")} — AI-powered sprint planning
@@ -11635,11 +11659,11 @@ function normalizeSprintName(name) {
11635
11659
  return name.trim().toLowerCase();
11636
11660
  }
11637
11661
  function getPlansDir(projectRoot) {
11638
- return join22(projectRoot, ".locus", "plans");
11662
+ return join23(projectRoot, ".locus", "plans");
11639
11663
  }
11640
11664
  function ensurePlansDir(projectRoot) {
11641
11665
  const dir = getPlansDir(projectRoot);
11642
- if (!existsSync22(dir)) {
11666
+ if (!existsSync23(dir)) {
11643
11667
  mkdirSync15(dir, { recursive: true });
11644
11668
  }
11645
11669
  return dir;
@@ -11649,14 +11673,14 @@ function generateId() {
11649
11673
  }
11650
11674
  function loadPlanFile(projectRoot, id) {
11651
11675
  const dir = getPlansDir(projectRoot);
11652
- if (!existsSync22(dir))
11676
+ if (!existsSync23(dir))
11653
11677
  return null;
11654
11678
  const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
11655
11679
  const match = files.find((f) => f.startsWith(id));
11656
11680
  if (!match)
11657
11681
  return null;
11658
11682
  try {
11659
- const content = readFileSync13(join22(dir, match), "utf-8");
11683
+ const content = readFileSync13(join23(dir, match), "utf-8");
11660
11684
  return JSON.parse(content);
11661
11685
  } catch {
11662
11686
  return null;
@@ -11710,7 +11734,7 @@ async function planCommand(projectRoot, args, flags = {}) {
11710
11734
  }
11711
11735
  function handleListPlans(projectRoot) {
11712
11736
  const dir = getPlansDir(projectRoot);
11713
- if (!existsSync22(dir)) {
11737
+ if (!existsSync23(dir)) {
11714
11738
  process.stderr.write(`${dim2("No saved plans yet.")}
11715
11739
  `);
11716
11740
  return;
@@ -11728,7 +11752,7 @@ ${bold2("Saved Plans:")}
11728
11752
  for (const file of files) {
11729
11753
  const id = file.replace(".json", "");
11730
11754
  try {
11731
- const content = readFileSync13(join22(dir, file), "utf-8");
11755
+ const content = readFileSync13(join23(dir, file), "utf-8");
11732
11756
  const plan = JSON.parse(content);
11733
11757
  const date = plan.createdAt ? plan.createdAt.slice(0, 10) : "";
11734
11758
  const issueCount = Array.isArray(plan.issues) ? plan.issues.length : 0;
@@ -11839,7 +11863,7 @@ ${bold2("Approving plan:")}
11839
11863
  async function handleAIPlan(projectRoot, config, directive, sprintName, flags) {
11840
11864
  const id = generateId();
11841
11865
  const plansDir = ensurePlansDir(projectRoot);
11842
- const planPath = join22(plansDir, `${id}.json`);
11866
+ const planPath = join23(plansDir, `${id}.json`);
11843
11867
  const planPathRelative = `.locus/plans/${id}.json`;
11844
11868
  const displayDirective = directive;
11845
11869
  process.stderr.write(`
@@ -11882,7 +11906,7 @@ ${red2("✗")} Planning failed: ${aiResult.error}
11882
11906
  `);
11883
11907
  return;
11884
11908
  }
11885
- if (!existsSync22(planPath)) {
11909
+ if (!existsSync23(planPath)) {
11886
11910
  process.stderr.write(`
11887
11911
  ${yellow2("⚠")} Plan file was not created at ${bold2(planPathRelative)}.
11888
11912
  `);
@@ -12064,15 +12088,15 @@ ${directive}${sprintName ? `
12064
12088
 
12065
12089
  **Sprint:** ${sprintName}` : ""}
12066
12090
  </directive>`);
12067
- const locusPath = join22(projectRoot, ".locus", "LOCUS.md");
12068
- if (existsSync22(locusPath)) {
12091
+ const locusPath = join23(projectRoot, ".locus", "LOCUS.md");
12092
+ if (existsSync23(locusPath)) {
12069
12093
  const content = readFileSync13(locusPath, "utf-8");
12070
12094
  parts.push(`<project-context>
12071
12095
  ${content.slice(0, 3000)}
12072
12096
  </project-context>`);
12073
12097
  }
12074
- const learningsPath = join22(projectRoot, ".locus", "LEARNINGS.md");
12075
- if (existsSync22(learningsPath)) {
12098
+ const learningsPath = join23(projectRoot, ".locus", "LEARNINGS.md");
12099
+ if (existsSync23(learningsPath)) {
12076
12100
  const content = readFileSync13(learningsPath, "utf-8");
12077
12101
  parts.push(`<past-learnings>
12078
12102
  ${content.slice(0, 2000)}
@@ -12253,8 +12277,8 @@ __export(exports_review, {
12253
12277
  reviewCommand: () => reviewCommand
12254
12278
  });
12255
12279
  import { execFileSync as execFileSync2, execSync as execSync19 } from "node:child_process";
12256
- import { existsSync as existsSync23, readFileSync as readFileSync14 } from "node:fs";
12257
- import { join as join23 } from "node:path";
12280
+ import { existsSync as existsSync24, readFileSync as readFileSync14 } from "node:fs";
12281
+ import { join as join24 } from "node:path";
12258
12282
  function printHelp3() {
12259
12283
  process.stderr.write(`
12260
12284
  ${bold2("locus review")} — AI-powered code review
@@ -12423,8 +12447,8 @@ function buildReviewPrompt(projectRoot, config, pr, diff, focus) {
12423
12447
  parts.push(`<role>
12424
12448
  You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.
12425
12449
  </role>`);
12426
- const locusPath = join23(projectRoot, ".locus", "LOCUS.md");
12427
- if (existsSync23(locusPath)) {
12450
+ const locusPath = join24(projectRoot, ".locus", "LOCUS.md");
12451
+ if (existsSync24(locusPath)) {
12428
12452
  const content = readFileSync14(locusPath, "utf-8");
12429
12453
  parts.push(`<project-context>
12430
12454
  ${content.slice(0, 2000)}
@@ -12745,14 +12769,14 @@ __export(exports_discuss, {
12745
12769
  discussCommand: () => discussCommand
12746
12770
  });
12747
12771
  import {
12748
- existsSync as existsSync24,
12772
+ existsSync as existsSync25,
12749
12773
  mkdirSync as mkdirSync16,
12750
12774
  readdirSync as readdirSync9,
12751
12775
  readFileSync as readFileSync15,
12752
12776
  unlinkSync as unlinkSync6,
12753
12777
  writeFileSync as writeFileSync11
12754
12778
  } from "node:fs";
12755
- import { join as join24 } from "node:path";
12779
+ import { join as join25 } from "node:path";
12756
12780
  function printHelp5() {
12757
12781
  process.stderr.write(`
12758
12782
  ${bold2("locus discuss")} — AI-powered architectural discussions
@@ -12774,11 +12798,11 @@ ${bold2("Examples:")}
12774
12798
  `);
12775
12799
  }
12776
12800
  function getDiscussionsDir(projectRoot) {
12777
- return join24(projectRoot, ".locus", "discussions");
12801
+ return join25(projectRoot, ".locus", "discussions");
12778
12802
  }
12779
12803
  function ensureDiscussionsDir(projectRoot) {
12780
12804
  const dir = getDiscussionsDir(projectRoot);
12781
- if (!existsSync24(dir)) {
12805
+ if (!existsSync25(dir)) {
12782
12806
  mkdirSync16(dir, { recursive: true });
12783
12807
  }
12784
12808
  return dir;
@@ -12813,7 +12837,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
12813
12837
  }
12814
12838
  function listDiscussions(projectRoot) {
12815
12839
  const dir = getDiscussionsDir(projectRoot);
12816
- if (!existsSync24(dir)) {
12840
+ if (!existsSync25(dir)) {
12817
12841
  process.stderr.write(`${dim2("No discussions yet.")}
12818
12842
  `);
12819
12843
  return;
@@ -12830,7 +12854,7 @@ ${bold2("Discussions:")}
12830
12854
  `);
12831
12855
  for (const file of files) {
12832
12856
  const id = file.replace(".md", "");
12833
- const content = readFileSync15(join24(dir, file), "utf-8");
12857
+ const content = readFileSync15(join25(dir, file), "utf-8");
12834
12858
  const titleMatch = content.match(/^#\s+(.+)/m);
12835
12859
  const title = titleMatch ? titleMatch[1] : id;
12836
12860
  const dateMatch = content.match(/\*\*Date:\*\*\s*(.+)/);
@@ -12848,7 +12872,7 @@ function showDiscussion(projectRoot, id) {
12848
12872
  return;
12849
12873
  }
12850
12874
  const dir = getDiscussionsDir(projectRoot);
12851
- if (!existsSync24(dir)) {
12875
+ if (!existsSync25(dir)) {
12852
12876
  process.stderr.write(`${red2("✗")} No discussions found.
12853
12877
  `);
12854
12878
  return;
@@ -12860,7 +12884,7 @@ function showDiscussion(projectRoot, id) {
12860
12884
  `);
12861
12885
  return;
12862
12886
  }
12863
- const content = readFileSync15(join24(dir, match), "utf-8");
12887
+ const content = readFileSync15(join25(dir, match), "utf-8");
12864
12888
  process.stdout.write(`${content}
12865
12889
  `);
12866
12890
  }
@@ -12871,7 +12895,7 @@ function deleteDiscussion(projectRoot, id) {
12871
12895
  return;
12872
12896
  }
12873
12897
  const dir = getDiscussionsDir(projectRoot);
12874
- if (!existsSync24(dir)) {
12898
+ if (!existsSync25(dir)) {
12875
12899
  process.stderr.write(`${red2("✗")} No discussions found.
12876
12900
  `);
12877
12901
  return;
@@ -12883,7 +12907,7 @@ function deleteDiscussion(projectRoot, id) {
12883
12907
  `);
12884
12908
  return;
12885
12909
  }
12886
- unlinkSync6(join24(dir, match));
12910
+ unlinkSync6(join25(dir, match));
12887
12911
  process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
12888
12912
  `);
12889
12913
  }
@@ -12896,7 +12920,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
12896
12920
  return;
12897
12921
  }
12898
12922
  const dir = getDiscussionsDir(projectRoot);
12899
- if (!existsSync24(dir)) {
12923
+ if (!existsSync25(dir)) {
12900
12924
  process.stderr.write(`${red2("✗")} No discussions found.
12901
12925
  `);
12902
12926
  return;
@@ -12908,7 +12932,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
12908
12932
  `);
12909
12933
  return;
12910
12934
  }
12911
- const content = readFileSync15(join24(dir, match), "utf-8");
12935
+ const content = readFileSync15(join25(dir, match), "utf-8");
12912
12936
  const titleMatch = content.match(/^#\s+(.+)/m);
12913
12937
  const discussionTitle = titleMatch ? titleMatch[1].trim() : id;
12914
12938
  await planCommand(projectRoot, [
@@ -13034,7 +13058,7 @@ ${turn.content}`;
13034
13058
  ...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
13035
13059
  ].join(`
13036
13060
  `);
13037
- writeFileSync11(join24(dir, `${id}.md`), markdown, "utf-8");
13061
+ writeFileSync11(join25(dir, `${id}.md`), markdown, "utf-8");
13038
13062
  process.stderr.write(`
13039
13063
  ${green("✓")} Discussion saved: ${cyan2(id)} ${dim2(`(${timer.formatted()})`)}
13040
13064
  `);
@@ -13049,15 +13073,15 @@ function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFi
13049
13073
  parts.push(`<role>
13050
13074
  You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.
13051
13075
  </role>`);
13052
- const locusPath = join24(projectRoot, ".locus", "LOCUS.md");
13053
- if (existsSync24(locusPath)) {
13076
+ const locusPath = join25(projectRoot, ".locus", "LOCUS.md");
13077
+ if (existsSync25(locusPath)) {
13054
13078
  const content = readFileSync15(locusPath, "utf-8");
13055
13079
  parts.push(`<project-context>
13056
13080
  ${content.slice(0, 3000)}
13057
13081
  </project-context>`);
13058
13082
  }
13059
- const learningsPath = join24(projectRoot, ".locus", "LEARNINGS.md");
13060
- if (existsSync24(learningsPath)) {
13083
+ const learningsPath = join25(projectRoot, ".locus", "LEARNINGS.md");
13084
+ if (existsSync25(learningsPath)) {
13061
13085
  const content = readFileSync15(learningsPath, "utf-8");
13062
13086
  parts.push(`<past-learnings>
13063
13087
  ${content.slice(0, 2000)}
@@ -13129,8 +13153,8 @@ __export(exports_artifacts, {
13129
13153
  formatDate: () => formatDate2,
13130
13154
  artifactsCommand: () => artifactsCommand
13131
13155
  });
13132
- import { existsSync as existsSync25, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
13133
- import { join as join25 } from "node:path";
13156
+ import { existsSync as existsSync26, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
13157
+ import { join as join26 } from "node:path";
13134
13158
  function printHelp6() {
13135
13159
  process.stderr.write(`
13136
13160
  ${bold2("locus artifacts")} — View and manage AI-generated artifacts
@@ -13150,14 +13174,14 @@ ${dim2("Artifact names support partial matching.")}
13150
13174
  `);
13151
13175
  }
13152
13176
  function getArtifactsDir(projectRoot) {
13153
- return join25(projectRoot, ".locus", "artifacts");
13177
+ return join26(projectRoot, ".locus", "artifacts");
13154
13178
  }
13155
13179
  function listArtifacts(projectRoot) {
13156
13180
  const dir = getArtifactsDir(projectRoot);
13157
- if (!existsSync25(dir))
13181
+ if (!existsSync26(dir))
13158
13182
  return [];
13159
13183
  return readdirSync10(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
13160
- const filePath = join25(dir, fileName);
13184
+ const filePath = join26(dir, fileName);
13161
13185
  const stat = statSync5(filePath);
13162
13186
  return {
13163
13187
  name: fileName.replace(/\.md$/, ""),
@@ -13170,8 +13194,8 @@ function listArtifacts(projectRoot) {
13170
13194
  function readArtifact(projectRoot, name) {
13171
13195
  const dir = getArtifactsDir(projectRoot);
13172
13196
  const fileName = name.endsWith(".md") ? name : `${name}.md`;
13173
- const filePath = join25(dir, fileName);
13174
- if (!existsSync25(filePath))
13197
+ const filePath = join26(dir, fileName);
13198
+ if (!existsSync26(filePath))
13175
13199
  return null;
13176
13200
  const stat = statSync5(filePath);
13177
13201
  return {
@@ -13532,8 +13556,8 @@ __export(exports_sandbox2, {
13532
13556
  });
13533
13557
  import { execSync as execSync22, spawn as spawn7 } from "node:child_process";
13534
13558
  import { createHash } from "node:crypto";
13535
- import { existsSync as existsSync26, readFileSync as readFileSync17 } from "node:fs";
13536
- import { basename as basename4, join as join26 } from "node:path";
13559
+ import { existsSync as existsSync27, readFileSync as readFileSync17 } from "node:fs";
13560
+ import { basename as basename4, join as join27 } from "node:path";
13537
13561
  import { createInterface as createInterface3 } from "node:readline";
13538
13562
  function printSandboxHelp() {
13539
13563
  process.stderr.write(`
@@ -14063,7 +14087,7 @@ async function handleLogs(projectRoot, args) {
14063
14087
  }
14064
14088
  function detectPackageManager2(projectRoot) {
14065
14089
  try {
14066
- const raw = readFileSync17(join26(projectRoot, "package.json"), "utf-8");
14090
+ const raw = readFileSync17(join27(projectRoot, "package.json"), "utf-8");
14067
14091
  const pkgJson = JSON.parse(raw);
14068
14092
  if (typeof pkgJson.packageManager === "string") {
14069
14093
  const name = pkgJson.packageManager.split("@")[0];
@@ -14072,13 +14096,13 @@ function detectPackageManager2(projectRoot) {
14072
14096
  }
14073
14097
  }
14074
14098
  } catch {}
14075
- if (existsSync26(join26(projectRoot, "bun.lock")) || existsSync26(join26(projectRoot, "bun.lockb"))) {
14099
+ if (existsSync27(join27(projectRoot, "bun.lock")) || existsSync27(join27(projectRoot, "bun.lockb"))) {
14076
14100
  return "bun";
14077
14101
  }
14078
- if (existsSync26(join26(projectRoot, "yarn.lock"))) {
14102
+ if (existsSync27(join27(projectRoot, "yarn.lock"))) {
14079
14103
  return "yarn";
14080
14104
  }
14081
- if (existsSync26(join26(projectRoot, "pnpm-lock.yaml"))) {
14105
+ if (existsSync27(join27(projectRoot, "pnpm-lock.yaml"))) {
14082
14106
  return "pnpm";
14083
14107
  }
14084
14108
  return "npm";
@@ -14181,9 +14205,9 @@ Installing sandbox dependencies (${bold2(installCmd.join(" "))}) to container fi
14181
14205
  ${dim2(`Detected ${ecosystem} project — skipping JS package install.`)}
14182
14206
  `);
14183
14207
  }
14184
- const setupScript = join26(projectRoot, ".locus", "sandbox-setup.sh");
14185
- const containerSetupScript = containerWorkdir ? join26(containerWorkdir, ".locus", "sandbox-setup.sh") : setupScript;
14186
- if (existsSync26(setupScript)) {
14208
+ const setupScript = join27(projectRoot, ".locus", "sandbox-setup.sh");
14209
+ const containerSetupScript = containerWorkdir ? join27(containerWorkdir, ".locus", "sandbox-setup.sh") : setupScript;
14210
+ if (existsSync27(setupScript)) {
14187
14211
  process.stderr.write(`Running ${bold2(".locus/sandbox-setup.sh")} in sandbox ${dim2(sandboxName)}...
14188
14212
  `);
14189
14213
  const hookOk = await runInteractiveCommand("docker", [
@@ -14351,13 +14375,13 @@ init_context();
14351
14375
  init_logger();
14352
14376
  init_rate_limiter();
14353
14377
  init_terminal();
14354
- import { existsSync as existsSync27, readFileSync as readFileSync18 } from "node:fs";
14355
- import { join as join27 } from "node:path";
14378
+ import { existsSync as existsSync28, readFileSync as readFileSync18 } from "node:fs";
14379
+ import { join as join28 } from "node:path";
14356
14380
  import { fileURLToPath } from "node:url";
14357
14381
  function getCliVersion() {
14358
14382
  const fallbackVersion = "0.0.0";
14359
- const packageJsonPath = join27(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
14360
- if (!existsSync27(packageJsonPath)) {
14383
+ const packageJsonPath = join28(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
14384
+ if (!existsSync28(packageJsonPath)) {
14361
14385
  return fallbackVersion;
14362
14386
  }
14363
14387
  try {
@@ -14634,7 +14658,7 @@ async function main() {
14634
14658
  try {
14635
14659
  const root = getGitRoot(cwd);
14636
14660
  if (isInitialized(root)) {
14637
- logDir = join27(root, ".locus", "logs");
14661
+ logDir = join28(root, ".locus", "logs");
14638
14662
  getRateLimiter(root);
14639
14663
  }
14640
14664
  } catch {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/cli",
3
- "version": "0.23.5",
3
+ "version": "0.24.1",
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.5",
39
+ "@locusai/sdk": "^0.24.1",
40
40
  "@types/bun": "latest",
41
41
  "typescript": "^5.8.3"
42
42
  },