@locusai/cli 0.4.0 → 0.4.2

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 +164 -50
  2. package/package.json +2 -2
package/bin/locus.js CHANGED
@@ -1,5 +1,4 @@
1
- #!/usr/bin/env bun
2
- // @bun
1
+ #!/usr/bin/env node
3
2
  import { createRequire } from "node:module";
4
3
  var __create = Object.create;
5
4
  var __getProtoOf = Object.getPrototypeOf;
@@ -17646,13 +17645,21 @@ class CodebaseIndexer {
17646
17645
  onProgress("Generating file tree...");
17647
17646
  const files = await globby(["**/*"], {
17648
17647
  cwd: this.projectPath,
17648
+ gitignore: true,
17649
17649
  ignore: [
17650
17650
  "**/node_modules/**",
17651
17651
  "**/dist/**",
17652
17652
  "**/build/**",
17653
+ "**/target/**",
17654
+ "**/bin/**",
17655
+ "**/obj/**",
17653
17656
  "**/.next/**",
17657
+ "**/.svelte-kit/**",
17658
+ "**/.nuxt/**",
17659
+ "**/.cache/**",
17654
17660
  "**/out/**",
17655
17661
  "**/__tests__/**",
17662
+ "**/coverage/**",
17656
17663
  "**/*.test.*",
17657
17664
  "**/*.spec.*",
17658
17665
  "**/*.d.ts",
@@ -17660,9 +17667,20 @@ class CodebaseIndexer {
17660
17667
  "**/.locus/*.json",
17661
17668
  "**/.locus/*.md",
17662
17669
  "**/.locus/!(artifacts)/**",
17663
- "bun.lock",
17664
- "package-lock.json",
17665
- "yarn.lock"
17670
+ "**/.git/**",
17671
+ "**/.svn/**",
17672
+ "**/.hg/**",
17673
+ "**/.vscode/**",
17674
+ "**/.idea/**",
17675
+ "**/.DS_Store",
17676
+ "**/bun.lock",
17677
+ "**/package-lock.json",
17678
+ "**/yarn.lock",
17679
+ "**/pnpm-lock.yaml",
17680
+ "**/Cargo.lock",
17681
+ "**/go.sum",
17682
+ "**/poetry.lock",
17683
+ "**/*.{png,jpg,jpeg,gif,svg,ico,mp4,webm,wav,mp3,woff,woff2,eot,ttf,otf,pdf,zip,tar.gz,rar}"
17666
17684
  ]
17667
17685
  });
17668
17686
  const treeString = files.join(`
@@ -21929,8 +21947,11 @@ class ClaudeRunner {
21929
21947
  claude.on("close", (code) => {
21930
21948
  if (code === 0)
21931
21949
  resolve(output);
21932
- else
21933
- reject(new Error(`Claude exited with code ${code}: ${errorOutput}`));
21950
+ else {
21951
+ const detail = errorOutput.trim();
21952
+ const message = detail ? `Claude CLI error: ${detail}` : `Claude CLI exited with code ${code}. Please ensure the Claude CLI is installed and you are logged in (run 'claude' manually to check).`;
21953
+ reject(new Error(message));
21954
+ }
21934
21955
  });
21935
21956
  claude.stdin.write(prompt);
21936
21957
  claude.stdin.end();
@@ -30149,10 +30170,16 @@ var TasksResponseSchema = exports_external.object({
30149
30170
  tasks: exports_external.array(TaskSchema)
30150
30171
  });
30151
30172
  // ../shared/src/models/workspace.ts
30173
+ var ChecklistItemSchema = exports_external.object({
30174
+ id: exports_external.string(),
30175
+ text: exports_external.string(),
30176
+ done: exports_external.boolean()
30177
+ });
30152
30178
  var WorkspaceSchema = BaseEntitySchema.extend({
30153
30179
  orgId: exports_external.string().uuid(),
30154
30180
  name: exports_external.string().min(1, "Name is required").max(100),
30155
- slug: exports_external.string().min(1, "Slug is required").regex(/^[a-z0-9-]+$/, "Slug must be lowercase alphanumeric with hyphens")
30181
+ slug: exports_external.string().min(1, "Slug is required").regex(/^[a-z0-9-]+$/, "Slug must be lowercase alphanumeric with hyphens"),
30182
+ defaultChecklist: exports_external.array(ChecklistItemSchema).nullable().optional()
30156
30183
  });
30157
30184
  var CreateWorkspaceSchema = exports_external.object({
30158
30185
  name: exports_external.string().min(1, "Name is required").max(100),
@@ -30312,6 +30339,27 @@ class LocusClient {
30312
30339
  this.invitations = new InvitationsModule(this.api, this.emitter);
30313
30340
  this.docs = new DocsModule(this.api, this.emitter);
30314
30341
  this.ci = new CiModule(this.api, this.emitter);
30342
+ if (config.retryOptions) {
30343
+ this.setupRetryInterceptor(config.retryOptions);
30344
+ }
30345
+ }
30346
+ setupRetryInterceptor(retryOptions) {
30347
+ this.api.interceptors.response.use(undefined, async (error2) => {
30348
+ const config = error2.config;
30349
+ if (!config || !retryOptions) {
30350
+ return Promise.reject(error2);
30351
+ }
30352
+ config._retryCount = config._retryCount || 0;
30353
+ const maxRetries = retryOptions.maxRetries ?? 3;
30354
+ const shouldRetry = config._retryCount < maxRetries && (retryOptions.retryCondition ? retryOptions.retryCondition(error2) : !error2.response || error2.response.status >= 500);
30355
+ if (shouldRetry) {
30356
+ config._retryCount++;
30357
+ const delay = Math.min((retryOptions.initialDelay ?? 1000) * Math.pow(retryOptions.factor ?? 2, config._retryCount - 1), retryOptions.maxDelay ?? 5000);
30358
+ await new Promise((resolve) => setTimeout(resolve, delay));
30359
+ return this.api(config);
30360
+ }
30361
+ return Promise.reject(error2);
30362
+ });
30315
30363
  }
30316
30364
  setupInterceptors() {
30317
30365
  this.api.interceptors.response.use((response) => {
@@ -30351,6 +30399,54 @@ class LocusClient {
30351
30399
  }
30352
30400
  }
30353
30401
 
30402
+ // ../sdk/src/utils/colors.ts
30403
+ var ESC = "\x1B[";
30404
+ var RESET = `${ESC}0m`;
30405
+ var colors = {
30406
+ reset: RESET,
30407
+ bold: `${ESC}1m`,
30408
+ dim: `${ESC}2m`,
30409
+ italic: `${ESC}3m`,
30410
+ underline: `${ESC}4m`,
30411
+ black: `${ESC}30m`,
30412
+ red: `${ESC}31m`,
30413
+ green: `${ESC}32m`,
30414
+ yellow: `${ESC}33m`,
30415
+ blue: `${ESC}34m`,
30416
+ magenta: `${ESC}35m`,
30417
+ cyan: `${ESC}36m`,
30418
+ white: `${ESC}37m`,
30419
+ gray: `${ESC}90m`,
30420
+ brightRed: `${ESC}91m`,
30421
+ brightGreen: `${ESC}92m`,
30422
+ brightYellow: `${ESC}93m`,
30423
+ brightBlue: `${ESC}94m`,
30424
+ brightMagenta: `${ESC}95m`,
30425
+ brightCyan: `${ESC}96m`,
30426
+ brightWhite: `${ESC}97m`
30427
+ };
30428
+ var c = {
30429
+ text: (text, ...colorNames) => {
30430
+ const codes = colorNames.map((name) => colors[name]).join("");
30431
+ return `${codes}${text}${RESET}`;
30432
+ },
30433
+ bold: (t) => c.text(t, "bold"),
30434
+ dim: (t) => c.text(t, "dim"),
30435
+ red: (t) => c.text(t, "red"),
30436
+ green: (t) => c.text(t, "green"),
30437
+ yellow: (t) => c.text(t, "yellow"),
30438
+ blue: (t) => c.text(t, "blue"),
30439
+ magenta: (t) => c.text(t, "magenta"),
30440
+ cyan: (t) => c.text(t, "cyan"),
30441
+ gray: (t) => c.text(t, "gray"),
30442
+ success: (t) => c.text(t, "green", "bold"),
30443
+ error: (t) => c.text(t, "red", "bold"),
30444
+ warning: (t) => c.text(t, "yellow", "bold"),
30445
+ info: (t) => c.text(t, "cyan", "bold"),
30446
+ primary: (t) => c.text(t, "blue", "bold"),
30447
+ underline: (t) => c.text(t, "underline")
30448
+ };
30449
+
30354
30450
  // ../sdk/src/agent/worker.ts
30355
30451
  class AgentWorker {
30356
30452
  config;
@@ -30372,7 +30468,13 @@ class AgentWorker {
30372
30468
  const projectPath = config.projectPath || process.cwd();
30373
30469
  this.client = new LocusClient({
30374
30470
  baseUrl: config.apiBase,
30375
- token: config.apiKey
30471
+ token: config.apiKey,
30472
+ retryOptions: {
30473
+ maxRetries: 3,
30474
+ initialDelay: 1000,
30475
+ maxDelay: 5000,
30476
+ factor: 2
30477
+ }
30376
30478
  });
30377
30479
  this.claudeRunner = new ClaudeRunner(projectPath, config.model);
30378
30480
  this.anthropicClient = config.anthropicApiKey ? new AnthropicClient({
@@ -30412,8 +30514,14 @@ class AgentWorker {
30412
30514
  }
30413
30515
  log(message, level = "info") {
30414
30516
  const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
30517
+ const colorFn = {
30518
+ info: c.cyan,
30519
+ success: c.green,
30520
+ warn: c.yellow,
30521
+ error: c.red
30522
+ }[level];
30415
30523
  const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
30416
- console.log(`[${timestamp}] [${this.config.agentId.slice(-8)}] ${prefix} ${message}`);
30524
+ console.log(`${c.dim(`[${timestamp}]`)} ${c.bold(`[${this.config.agentId.slice(-8)}]`)} ${colorFn(`${prefix} ${message}`)}`);
30417
30525
  }
30418
30526
  async getActiveSprint() {
30419
30527
  try {
@@ -30569,11 +30677,11 @@ class AgentOrchestrator extends EventEmitter3 {
30569
30677
  try {
30570
30678
  const sprint2 = await this.client.sprints.getActive(this.config.workspaceId);
30571
30679
  if (sprint2?.id) {
30572
- console.log(`\uD83D\uDCCB Using active sprint: ${sprint2.name}`);
30680
+ console.log(c.info(`\uD83D\uDCCB Using active sprint: ${sprint2.name}`));
30573
30681
  return sprint2.id;
30574
30682
  }
30575
30683
  } catch {}
30576
- console.log("ℹ No sprint specified, working with all workspace tasks");
30684
+ console.log(c.dim("ℹ No sprint specified, working with all workspace tasks"));
30577
30685
  return "";
30578
30686
  }
30579
30687
  async start() {
@@ -30599,18 +30707,18 @@ class AgentOrchestrator extends EventEmitter3 {
30599
30707
  sprintId: this.resolvedSprintId
30600
30708
  });
30601
30709
  console.log(`
30602
- \uD83E\uDD16 Locus Agent Orchestrator`);
30603
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
30604
- console.log(`Workspace: ${this.config.workspaceId}`);
30710
+ ${c.primary("\uD83E\uDD16 Locus Agent Orchestrator")}`);
30711
+ console.log(c.dim("----------------------------------------------"));
30712
+ console.log(`${c.bold("Workspace:")} ${this.config.workspaceId}`);
30605
30713
  if (this.resolvedSprintId) {
30606
- console.log(`Sprint: ${this.resolvedSprintId}`);
30714
+ console.log(`${c.bold("Sprint:")} ${this.resolvedSprintId}`);
30607
30715
  }
30608
- console.log(`API Base: ${this.config.apiBase}`);
30609
- console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
30610
- `);
30716
+ console.log(`${c.bold("API Base:")} ${this.config.apiBase}`);
30717
+ console.log(c.dim(`----------------------------------------------
30718
+ `));
30611
30719
  const tasks2 = await this.getAvailableTasks();
30612
30720
  if (tasks2.length === 0) {
30613
- console.log("ℹ No available tasks found in the backlog.");
30721
+ console.log(c.dim("ℹ No available tasks found in the backlog."));
30614
30722
  return;
30615
30723
  }
30616
30724
  await this.spawnAgent();
@@ -30622,7 +30730,7 @@ class AgentOrchestrator extends EventEmitter3 {
30622
30730
  await this.sleep(2000);
30623
30731
  }
30624
30732
  console.log(`
30625
- ✅ Orchestrator finished`);
30733
+ ${c.success("✅ Orchestrator finished")}`);
30626
30734
  }
30627
30735
  findPackageRoot(startPath) {
30628
30736
  let currentDir = startPath;
@@ -30645,7 +30753,7 @@ class AgentOrchestrator extends EventEmitter3 {
30645
30753
  lastHeartbeat: new Date
30646
30754
  };
30647
30755
  this.agents.set(agentId, agentState);
30648
- console.log(`\uD83D\uDE80 Agent started: ${agentId}
30756
+ console.log(`${c.primary("\uD83D\uDE80 Agent started:")} ${c.bold(agentId)}
30649
30757
  `);
30650
30758
  const potentialPaths = [];
30651
30759
  try {
@@ -30681,7 +30789,7 @@ class AgentOrchestrator extends EventEmitter3 {
30681
30789
  if (this.resolvedSprintId) {
30682
30790
  workerArgs.push("--sprint-id", this.resolvedSprintId);
30683
30791
  }
30684
- const agentProcess = spawn2("bun", ["run", workerPath, ...workerArgs]);
30792
+ const agentProcess = spawn2(process.execPath, [workerPath, ...workerArgs]);
30685
30793
  agentState.process = agentProcess;
30686
30794
  agentProcess.on("message", (msg) => {
30687
30795
  if (msg.type === "stats") {
@@ -30886,16 +30994,22 @@ Return ONLY a JSON object with this structure:
30886
30994
  File Tree:
30887
30995
  ${tree}`;
30888
30996
  const claude = spawn3("claude", ["--print"], {
30889
- stdio: ["pipe", "pipe", "ignore"],
30997
+ stdio: ["pipe", "pipe", "pipe"],
30890
30998
  cwd: this.projectPath
30891
30999
  });
30892
31000
  let output = "";
31001
+ let errorOutput = "";
30893
31002
  claude.stdout.on("data", (data) => {
30894
31003
  output += data.toString();
30895
31004
  });
31005
+ claude.stderr.on("data", (data) => {
31006
+ errorOutput += data.toString();
31007
+ });
30896
31008
  claude.on("close", (code) => {
30897
- if (code !== 0)
30898
- return reject(new Error(`Claude exited with code ${code}`));
31009
+ if (code !== 0) {
31010
+ const errMsg = errorOutput.trim() || `Claude exited with code ${code}`;
31011
+ return reject(new Error(errMsg));
31012
+ }
30899
31013
  try {
30900
31014
  const jsonMatch = output.match(/\{[\s\S]*\}/);
30901
31015
  if (jsonMatch)
@@ -30927,13 +31041,13 @@ function getVersion() {
30927
31041
  }
30928
31042
  var VERSION3 = getVersion();
30929
31043
  function printBanner() {
30930
- console.log(`
30931
- ██ ██████ ██████ ██ ██ ██████
30932
- ██ ██ ██ ██ ██ ██ ██
30933
- ██ ██ ██ ██ ██ ██ ███████
30934
- ██ ██ ██ ██ ██ ██ ██
30935
- ███████ ██████ ██████ ██████ ███████ v${VERSION3}
30936
- `);
31044
+ console.log(c.primary(`
31045
+ ## ###### ###### ## ## ######
31046
+ ## ## ## ## ## ## ##
31047
+ ## ## ## ## ## ## ######
31048
+ ## ## ## ## ## ## ##
31049
+ ####### ###### ###### ###### ###### v${VERSION3}
31050
+ `));
30937
31051
  }
30938
31052
  function isProjectInitialized(projectPath) {
30939
31053
  const locusDir = join6(projectPath, LOCUS_CONFIG.dir);
@@ -30943,12 +31057,12 @@ function isProjectInitialized(projectPath) {
30943
31057
  function requireInitialization(projectPath, command) {
30944
31058
  if (!isProjectInitialized(projectPath)) {
30945
31059
  console.error(`
30946
- ❌ Error: Locus is not initialized in this directory.
31060
+ ${c.error("❌ Error: Locus is not initialized in this directory.")}
30947
31061
 
30948
- The '${command}' command requires a Locus project to be initialized.
31062
+ The '${c.bold(command)}' command requires a Locus project to be initialized.
30949
31063
 
30950
31064
  To initialize Locus in this directory, run:
30951
- locus init
31065
+ ${c.primary("locus init")}
30952
31066
 
30953
31067
  This will create a .locus directory with the necessary configuration.
30954
31068
  `);
@@ -30975,7 +31089,7 @@ async function runCommand(args) {
30975
31089
  const anthropicApiKey = values["anthropic-api-key"] || process.env.ANTHROPIC_API_KEY;
30976
31090
  const workspaceId = values.workspace || process.env.LOCUS_WORKSPACE_ID;
30977
31091
  if (!apiKey || !workspaceId) {
30978
- console.error("Error: --api-key and --workspace are required");
31092
+ console.error(c.error("Error: --api-key and --workspace are required"));
30979
31093
  process.exit(1);
30980
31094
  }
30981
31095
  const orchestrator = new AgentOrchestrator({
@@ -30991,7 +31105,7 @@ async function runCommand(args) {
30991
31105
  orchestrator.on("task:assigned", (data) => console.log(`ℹ [CLAIMED] ${data.title}`));
30992
31106
  orchestrator.on("task:completed", (data) => console.log(`✓ [COMPLETED] ${data.taskId}`));
30993
31107
  orchestrator.on("task:failed", (data) => console.log(`✗ [FAILED] ${data.taskId}: ${data.error}`));
30994
- console.log(`\uD83D\uDE80 Starting agent in ${projectPath}...`);
31108
+ console.log(`${c.primary("\uD83D\uDE80 Starting agent in")} ${c.bold(projectPath)}...`);
30995
31109
  await orchestrator.start();
30996
31110
  }
30997
31111
  async function indexCommand(args) {
@@ -31004,18 +31118,18 @@ async function indexCommand(args) {
31004
31118
  requireInitialization(projectPath, "index");
31005
31119
  const summarizer = new TreeSummarizer(projectPath);
31006
31120
  const indexer = new CodebaseIndexer(projectPath);
31007
- console.log(`\uD83D\uDD0D Indexing codebase in ${projectPath}...`);
31008
- const index = await indexer.index((msg) => console.log(` ${msg}`), (tree) => summarizer.summarize(tree));
31121
+ console.log(`${c.primary("\uD83D\uDD0D Indexing codebase in")} ${c.bold(projectPath)}...`);
31122
+ const index = await indexer.index((msg) => console.log(` ${c.dim(msg)}`), (tree) => summarizer.summarize(tree));
31009
31123
  indexer.saveIndex(index);
31010
- console.log("✅ Indexing complete!");
31124
+ console.log(c.success("✅ Indexing complete!"));
31011
31125
  }
31012
31126
  async function initCommand() {
31013
31127
  const projectPath = process.cwd();
31014
31128
  if (isProjectInitialized(projectPath)) {
31015
31129
  console.log(`
31016
- ℹ️ Locus is already initialized in this directory.
31130
+ ${c.info("ℹ️ Locus is already initialized in this directory.")}
31017
31131
 
31018
- Configuration found at: ${join6(projectPath, LOCUS_CONFIG.dir)}
31132
+ Configuration found at: ${c.bold(join6(projectPath, LOCUS_CONFIG.dir))}
31019
31133
 
31020
31134
  If you want to reinitialize, please remove the .locus directory first.
31021
31135
  `);
@@ -31023,18 +31137,18 @@ If you want to reinitialize, please remove the .locus directory first.
31023
31137
  }
31024
31138
  await new ConfigManager(projectPath).init(VERSION3);
31025
31139
  console.log(`
31026
- ✨ Locus initialized successfully!
31140
+ ${c.success("✨ Locus initialized successfully!")}
31027
31141
 
31028
31142
  Created:
31029
- \uD83D\uDCC1 .locus/ - Locus configuration directory
31030
- \uD83D\uDCC4 .locus/config.json - Project configuration
31031
- \uD83D\uDCDD CLAUDE.md - AI context file
31143
+ \uD83D\uDCC1 ${c.dim(".locus/")} - Locus configuration directory
31144
+ \uD83D\uDCC4 ${c.dim(".locus/config.json")} - Project configuration
31145
+ \uD83D\uDCDD ${c.dim("CLAUDE.md")} - AI context file
31032
31146
 
31033
31147
  Next steps:
31034
- 1. Run 'locus index' to index your codebase
31035
- 2. Run 'locus run' to start an agent (requires --api-key and --workspace)
31148
+ 1. Run '${c.primary("locus index")}' to index your codebase
31149
+ 2. Run '${c.primary("locus run")}' to start an agent (requires --api-key and --workspace)
31036
31150
 
31037
- For more information, visit: https://locus.dev/docs
31151
+ For more information, visit: ${c.underline("https://locus.dev/docs")}
31038
31152
  `);
31039
31153
  }
31040
31154
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Local-first AI development platform & engineering workspace",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,7 @@
31
31
  "author": "",
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
- "@locusai/sdk": "^0.4.0"
34
+ "@locusai/sdk": "^0.4.2"
35
35
  },
36
36
  "devDependencies": {}
37
37
  }