@locusai/sdk 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.
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/agent/worker.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,qBAAa,WAAW;IAmBV,OAAO,CAAC,MAAM;IAlB1B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,eAAe,CAAyB;IAGhD,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,YAAY,CAAe;IAGnC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,UAAU,CAAuB;gBAErB,MAAM,EAAE,YAAY;IA+DxC,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAgB;YAQ5D,eAAe;YAcf,WAAW;YAiBX,WAAW;IAqBnB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA+F3B"}
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/agent/worker.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,qBAAa,WAAW;IAmBV,OAAO,CAAC,MAAM;IAlB1B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,eAAe,CAAyB;IAGhD,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,YAAY,CAAe;IAGnC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,UAAU,CAAuB;gBAErB,MAAM,EAAE,YAAY;IAqExC,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAgB;YAe5D,eAAe;YAcf,WAAW;YAiBX,WAAW;IAqBnB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA+F3B"}
@@ -1,6 +1,7 @@
1
1
  import { AnthropicClient } from "../ai/anthropic-client";
2
2
  import { ClaudeRunner } from "../ai/claude-runner";
3
3
  import { LocusClient } from "../index";
4
+ import { c } from "../utils/colors";
4
5
  import { ArtifactSyncer } from "./artifact-syncer";
5
6
  import { CodebaseIndexerService } from "./codebase-indexer-service";
6
7
  import { SprintPlanner } from "./sprint-planner";
@@ -33,6 +34,12 @@ export class AgentWorker {
33
34
  this.client = new LocusClient({
34
35
  baseUrl: config.apiBase,
35
36
  token: config.apiKey,
37
+ retryOptions: {
38
+ maxRetries: 3,
39
+ initialDelay: 1000,
40
+ maxDelay: 5000,
41
+ factor: 2,
42
+ },
36
43
  });
37
44
  // Initialize AI clients
38
45
  this.claudeRunner = new ClaudeRunner(projectPath, config.model);
@@ -78,8 +85,14 @@ export class AgentWorker {
78
85
  }
79
86
  log(message, level = "info") {
80
87
  const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
88
+ const colorFn = {
89
+ info: c.cyan,
90
+ success: c.green,
91
+ warn: c.yellow,
92
+ error: c.red,
93
+ }[level];
81
94
  const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
82
- console.log(`[${timestamp}] [${this.config.agentId.slice(-8)}] ${prefix} ${message}`);
95
+ console.log(`${c.dim(`[${timestamp}]`)} ${c.bold(`[${this.config.agentId.slice(-8)}]`)} ${colorFn(`${prefix} ${message}`)}`);
83
96
  }
84
97
  async getActiveSprint() {
85
98
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"claude-runner.d.ts","sourceRoot":"","sources":["../../src/ai/claude-runner.ts"],"names":[],"mappings":"AAGA,qBAAa,YAAY;IAErB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,KAAK;gBADL,WAAW,EAAE,MAAM,EACnB,KAAK,GAAE,MAAsB;IAGvC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;CAuC1D"}
1
+ {"version":3,"file":"claude-runner.d.ts","sourceRoot":"","sources":["../../src/ai/claude-runner.ts"],"names":[],"mappings":"AAGA,qBAAa,YAAY;IAErB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,KAAK;gBADL,WAAW,EAAE,MAAM,EACnB,KAAK,GAAE,MAAsB;IAGvC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;CA4C1D"}
@@ -33,8 +33,13 @@ export class ClaudeRunner {
33
33
  claude.on("close", (code) => {
34
34
  if (code === 0)
35
35
  resolve(output);
36
- else
37
- reject(new Error(`Claude exited with code ${code}: ${errorOutput}`));
36
+ else {
37
+ const detail = errorOutput.trim();
38
+ const message = detail
39
+ ? `Claude CLI error: ${detail}`
40
+ : `Claude CLI exited with code ${code}. Please ensure the Claude CLI is installed and you are logged in (run 'claude' manually to check).`;
41
+ reject(new Error(message));
42
+ }
38
43
  });
39
44
  claude.stdin.write(prompt);
40
45
  claude.stdin.end();
@@ -1 +1 @@
1
- {"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../src/core/indexer.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;gBAEd,WAAW,EAAE,MAAM;IAK/B;;;OAGG;IACG,KAAK,CACT,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACtC,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,GACxD,OAAO,CAAC,aAAa,CAAC;IA8CzB,SAAS,IAAI,aAAa,GAAG,IAAI;IAWjC,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;CAOtC"}
1
+ {"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../src/core/indexer.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;gBAEd,WAAW,EAAE,MAAM;IAK/B;;;OAGG;IACG,KAAK,CACT,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACtC,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,GACxD,OAAO,CAAC,aAAa,CAAC;IAkEzB,SAAS,IAAI,aAAa,GAAG,IAAI;IAWjC,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;CAOtC"}
@@ -21,23 +21,43 @@ export class CodebaseIndexer {
21
21
  // 1. Get a comprehensive but clean file tree
22
22
  const files = await globby(["**/*"], {
23
23
  cwd: this.projectPath,
24
+ gitignore: true,
24
25
  ignore: [
25
26
  "**/node_modules/**",
26
27
  "**/dist/**",
27
28
  "**/build/**",
29
+ "**/target/**", // Rust build artifacts
30
+ "**/bin/**",
31
+ "**/obj/**",
28
32
  "**/.next/**",
33
+ "**/.svelte-kit/**",
34
+ "**/.nuxt/**",
35
+ "**/.cache/**",
29
36
  "**/out/**",
30
37
  "**/__tests__/**",
38
+ "**/coverage/**",
31
39
  "**/*.test.*",
32
40
  "**/*.spec.*",
33
41
  "**/*.d.ts",
34
42
  "**/tsconfig.tsbuildinfo",
35
43
  "**/.locus/*.json", // Ignore index and other system JSONs
36
- "**/.locus/*.md", // Ignore system MDs if any (except artifacts handled below)
44
+ "**/.locus/*.md", // Ignore system MDs
37
45
  "**/.locus/!(artifacts)/**", // Ignore everything in .locus EXCEPT artifacts
38
- "bun.lock",
39
- "package-lock.json",
40
- "yarn.lock",
46
+ "**/.git/**",
47
+ "**/.svn/**",
48
+ "**/.hg/**",
49
+ "**/.vscode/**",
50
+ "**/.idea/**",
51
+ "**/.DS_Store",
52
+ "**/bun.lock",
53
+ "**/package-lock.json",
54
+ "**/yarn.lock",
55
+ "**/pnpm-lock.yaml",
56
+ "**/Cargo.lock",
57
+ "**/go.sum",
58
+ "**/poetry.lock",
59
+ // Binary/Large Assets
60
+ "**/*.{png,jpg,jpeg,gif,svg,ico,mp4,webm,wav,mp3,woff,woff2,eot,ttf,otf,pdf,zip,tar.gz,rar}",
41
61
  ],
42
62
  });
43
63
  // Format the tree for the AI
package/dist/events.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { EventEmitter } from "events";
2
+ import { RetryOptions } from "./utils/retry";
2
3
  export declare enum LocusEvent {
3
4
  TOKEN_EXPIRED = "TOKEN_EXPIRED",
4
5
  AUTH_ERROR = "AUTH_ERROR",
@@ -8,6 +9,7 @@ export interface LocusConfig {
8
9
  baseUrl: string;
9
10
  token?: string | null;
10
11
  timeout?: number;
12
+ retryOptions?: RetryOptions;
11
13
  }
12
14
  export declare class LocusEmitter extends EventEmitter {
13
15
  on(event: LocusEvent.TOKEN_EXPIRED, listener: () => void): this;
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,oBAAY,UAAU;IACpB,aAAa,kBAAkB;IAC/B,UAAU,eAAe;IACzB,aAAa,kBAAkB;CAChC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,YAAa,SAAQ,YAAY;IAC5C,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAC/D,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IACxE,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAQ3E,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,aAAa,GAAG,OAAO;IAC9C,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO;IACzD,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,aAAa,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO;CAI7D"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,oBAAY,UAAU;IACpB,aAAa,kBAAkB;IAC/B,UAAU,eAAe;IACzB,aAAa,kBAAkB;CAChC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,qBAAa,YAAa,SAAQ,YAAY;IAC5C,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,aAAa,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAC/D,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IACxE,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAQ3E,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,aAAa,GAAG,OAAO;IAC9C,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO;IACzD,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,aAAa,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO;CAI7D"}
@@ -11,4 +11,5 @@ export * from "./ai";
11
11
  export * from "./core";
12
12
  export * from "./index";
13
13
  export { AgentOrchestrator, type OrchestratorConfig } from "./orchestrator";
14
+ export { c } from "./utils/colors";
14
15
  //# sourceMappingURL=index-node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-node.d.ts","sourceRoot":"","sources":["../src/index-node.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,cAAc,SAAS,CAAC;AAExB,cAAc,MAAM,CAAC;AAErB,cAAc,QAAQ,CAAC;AAEvB,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index-node.d.ts","sourceRoot":"","sources":["../src/index-node.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,cAAc,SAAS,CAAC;AAExB,cAAc,MAAM,CAAC;AAErB,cAAc,QAAQ,CAAC;AAEvB,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG5E,OAAO,EAAE,CAAC,EAAE,MAAM,gBAAgB,CAAC"}
@@ -16,3 +16,5 @@ export * from "./core";
16
16
  export * from "./index";
17
17
  // Node.js-only: Orchestrator
18
18
  export { AgentOrchestrator } from "./orchestrator";
19
+ // Utilities
20
+ export { c } from "./utils/colors";
package/dist/index.d.ts CHANGED
@@ -28,6 +28,7 @@ export declare class LocusClient {
28
28
  readonly docs: DocsModule;
29
29
  readonly ci: CiModule;
30
30
  constructor(config: LocusConfig);
31
+ private setupRetryInterceptor;
31
32
  private setupInterceptors;
32
33
  setToken(token: string | null): void;
33
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAc,MAAM,UAAU,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAGxD,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AAErC,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,SAAgB,OAAO,EAAE,YAAY,CAAC;IAEtC,SAAgB,IAAI,EAAE,UAAU,CAAC;IACjC,SAAgB,KAAK,EAAE,WAAW,CAAC;IACnC,SAAgB,OAAO,EAAE,aAAa,CAAC;IACvC,SAAgB,UAAU,EAAE,gBAAgB,CAAC;IAC7C,SAAgB,aAAa,EAAE,mBAAmB,CAAC;IACnD,SAAgB,WAAW,EAAE,iBAAiB,CAAC;IAC/C,SAAgB,IAAI,EAAE,UAAU,CAAC;IACjC,SAAgB,EAAE,EAAE,QAAQ,CAAC;gBAEjB,MAAM,EAAE,WAAW;IAyB/B,OAAO,CAAC,iBAAiB;IAmDlB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CAOrC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAc,MAAM,UAAU,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAIxD,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AAErC,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,SAAgB,OAAO,EAAE,YAAY,CAAC;IAEtC,SAAgB,IAAI,EAAE,UAAU,CAAC;IACjC,SAAgB,KAAK,EAAE,WAAW,CAAC;IACnC,SAAgB,OAAO,EAAE,aAAa,CAAC;IACvC,SAAgB,UAAU,EAAE,gBAAgB,CAAC;IAC7C,SAAgB,aAAa,EAAE,mBAAmB,CAAC;IACnD,SAAgB,WAAW,EAAE,iBAAiB,CAAC;IAC/C,SAAgB,IAAI,EAAE,UAAU,CAAC;IACjC,SAAgB,EAAE,EAAE,QAAQ,CAAC;gBAEjB,MAAM,EAAE,WAAW;IA6B/B,OAAO,CAAC,qBAAqB;IAmC7B,OAAO,CAAC,iBAAiB;IAmDlB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CAOrC"}
package/dist/index.js CHANGED
@@ -49,6 +49,31 @@ export class LocusClient {
49
49
  this.invitations = new InvitationsModule(this.api, this.emitter);
50
50
  this.docs = new DocsModule(this.api, this.emitter);
51
51
  this.ci = new CiModule(this.api, this.emitter);
52
+ if (config.retryOptions) {
53
+ this.setupRetryInterceptor(config.retryOptions);
54
+ }
55
+ }
56
+ setupRetryInterceptor(retryOptions) {
57
+ this.api.interceptors.response.use(undefined, async (error) => {
58
+ const config = error.config;
59
+ if (!config || !retryOptions) {
60
+ return Promise.reject(error);
61
+ }
62
+ config._retryCount = config._retryCount || 0;
63
+ const maxRetries = retryOptions.maxRetries ?? 3;
64
+ const shouldRetry = config._retryCount < maxRetries &&
65
+ (retryOptions.retryCondition
66
+ ? retryOptions.retryCondition(error)
67
+ : !error.response || error.response.status >= 500);
68
+ if (shouldRetry) {
69
+ config._retryCount++;
70
+ const delay = Math.min((retryOptions.initialDelay ?? 1000) *
71
+ Math.pow(retryOptions.factor ?? 2, config._retryCount - 1), retryOptions.maxDelay ?? 5000);
72
+ await new Promise((resolve) => setTimeout(resolve, delay));
73
+ return this.api(config);
74
+ }
75
+ return Promise.reject(error);
76
+ });
52
77
  }
53
78
  setupInterceptors() {
54
79
  this.api.interceptors.response.use((response) => {
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAS,MAAM,oBAAoB,CAAC;AAGzD,OAAO,EAAE,IAAI,EAA4B,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACpD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,gBAAgB,CAAuB;gBAEnC,MAAM,EAAE,kBAAkB;IAStC;;OAEG;YACW,eAAe;IAsB7B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B;;OAEG;YACW,iBAAiB;IA4C/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAYvB;;OAEG;YACW,UAAU;IA8HxB;;OAEG;YACW,UAAU;IAKxB;;OAEG;YACW,iBAAiB;IAc/B;;OAEG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IA2C9D;;OAEG;IACG,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IA4BhB;;OAEG;IACG,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC;IAyBhB;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B;;OAEG;YACW,OAAO;IAWrB;;OAEG;IACH,QAAQ;;;;;;IAeR,OAAO,CAAC,KAAK;CAGd"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAS,MAAM,oBAAoB,CAAC;AAGzD,OAAO,EAAE,IAAI,EAA4B,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAItC,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACpD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,gBAAgB,CAAuB;gBAEnC,MAAM,EAAE,kBAAkB;IAStC;;OAEG;YACW,eAAe;IAwB7B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B;;OAEG;YACW,iBAAiB;IA4C/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAYvB;;OAEG;YACW,UAAU;IA8HxB;;OAEG;YACW,UAAU;IAKxB;;OAEG;YACW,iBAAiB;IAc/B;;OAEG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IA2C9D;;OAEG;IACG,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IA4BhB;;OAEG;IACG,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC;IAyBhB;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B;;OAEG;YACW,OAAO;IAWrB;;OAEG;IACH,QAAQ;;;;;;IAeR,OAAO,CAAC,KAAK;CAGd"}
@@ -4,6 +4,7 @@ import { dirname, join } from "node:path";
4
4
  import { TaskPriority, TaskStatus } from "@locusai/shared";
5
5
  import { EventEmitter } from "events";
6
6
  import { LocusClient } from "./index";
7
+ import { c } from "./utils/colors";
7
8
  export class AgentOrchestrator extends EventEmitter {
8
9
  client;
9
10
  config;
@@ -30,14 +31,14 @@ export class AgentOrchestrator extends EventEmitter {
30
31
  try {
31
32
  const sprint = await this.client.sprints.getActive(this.config.workspaceId);
32
33
  if (sprint?.id) {
33
- console.log(`📋 Using active sprint: ${sprint.name}`);
34
+ console.log(c.info(`📋 Using active sprint: ${sprint.name}`));
34
35
  return sprint.id;
35
36
  }
36
37
  }
37
38
  catch {
38
39
  // No active sprint found, will work with all tasks
39
40
  }
40
- console.log("ℹ No sprint specified, working with all workspace tasks");
41
+ console.log(c.dim("ℹ No sprint specified, working with all workspace tasks"));
41
42
  return "";
42
43
  }
43
44
  /**
@@ -71,18 +72,18 @@ export class AgentOrchestrator extends EventEmitter {
71
72
  config: this.config,
72
73
  sprintId: this.resolvedSprintId,
73
74
  });
74
- console.log("\n🤖 Locus Agent Orchestrator");
75
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
76
- console.log(`Workspace: ${this.config.workspaceId}`);
75
+ console.log(`\n${c.primary("🤖 Locus Agent Orchestrator")}`);
76
+ console.log(c.dim("----------------------------------------------"));
77
+ console.log(`${c.bold("Workspace:")} ${this.config.workspaceId}`);
77
78
  if (this.resolvedSprintId) {
78
- console.log(`Sprint: ${this.resolvedSprintId}`);
79
+ console.log(`${c.bold("Sprint:")} ${this.resolvedSprintId}`);
79
80
  }
80
- console.log(`API Base: ${this.config.apiBase}`);
81
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
81
+ console.log(`${c.bold("API Base:")} ${this.config.apiBase}`);
82
+ console.log(c.dim("----------------------------------------------\n"));
82
83
  // Check if there are tasks to work on before spawning
83
84
  const tasks = await this.getAvailableTasks();
84
85
  if (tasks.length === 0) {
85
- console.log("ℹ No available tasks found in the backlog.");
86
+ console.log(c.dim("ℹ No available tasks found in the backlog."));
86
87
  return;
87
88
  }
88
89
  // Spawn single agent
@@ -95,7 +96,7 @@ export class AgentOrchestrator extends EventEmitter {
95
96
  }
96
97
  await this.sleep(2000);
97
98
  }
98
- console.log("\n✅ Orchestrator finished");
99
+ console.log(`\n${c.success("✅ Orchestrator finished")}`);
99
100
  }
100
101
  /**
101
102
  * Find the package root by looking for package.json
@@ -127,7 +128,7 @@ export class AgentOrchestrator extends EventEmitter {
127
128
  lastHeartbeat: new Date(),
128
129
  };
129
130
  this.agents.set(agentId, agentState);
130
- console.log(`🚀 Agent started: ${agentId}\n`);
131
+ console.log(`${c.primary("🚀 Agent started:")} ${c.bold(agentId)}\n`);
131
132
  // Build arguments for agent worker
132
133
  // Try multiple resolution strategies
133
134
  const potentialPaths = [];
@@ -176,8 +177,8 @@ export class AgentOrchestrator extends EventEmitter {
176
177
  if (this.resolvedSprintId) {
177
178
  workerArgs.push("--sprint-id", this.resolvedSprintId);
178
179
  }
179
- // Use bun to run the worker script
180
- const agentProcess = spawn("bun", ["run", workerPath, ...workerArgs]);
180
+ // Use node to run the worker script
181
+ const agentProcess = spawn(process.execPath, [workerPath, ...workerArgs]);
181
182
  agentState.process = agentProcess;
182
183
  agentProcess.on("message", (msg) => {
183
184
  if (msg.type === "stats") {
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Simple ANSI color utility for terminal output
3
+ * Dependency-free and works in Node.js/Bun environments
4
+ */
5
+ declare const colors: {
6
+ reset: string;
7
+ bold: string;
8
+ dim: string;
9
+ italic: string;
10
+ underline: string;
11
+ black: string;
12
+ red: string;
13
+ green: string;
14
+ yellow: string;
15
+ blue: string;
16
+ magenta: string;
17
+ cyan: string;
18
+ white: string;
19
+ gray: string;
20
+ brightRed: string;
21
+ brightGreen: string;
22
+ brightYellow: string;
23
+ brightBlue: string;
24
+ brightMagenta: string;
25
+ brightCyan: string;
26
+ brightWhite: string;
27
+ };
28
+ type ColorName = keyof typeof colors;
29
+ export declare const c: {
30
+ text: (text: string, ...colorNames: ColorName[]) => string;
31
+ bold: (t: string) => string;
32
+ dim: (t: string) => string;
33
+ red: (t: string) => string;
34
+ green: (t: string) => string;
35
+ yellow: (t: string) => string;
36
+ blue: (t: string) => string;
37
+ magenta: (t: string) => string;
38
+ cyan: (t: string) => string;
39
+ gray: (t: string) => string;
40
+ success: (t: string) => string;
41
+ error: (t: string) => string;
42
+ warning: (t: string) => string;
43
+ info: (t: string) => string;
44
+ primary: (t: string) => string;
45
+ underline: (t: string) => string;
46
+ };
47
+ export {};
48
+ //# sourceMappingURL=colors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/utils/colors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;CA0BX,CAAC;AAEF,KAAK,SAAS,GAAG,MAAM,OAAO,MAAM,CAAC;AAErC,eAAO,MAAM,CAAC;iBACC,MAAM,iBAAiB,SAAS,EAAE;cAMrC,MAAM;aACP,MAAM;aACN,MAAM;eACJ,MAAM;gBACL,MAAM;cACR,MAAM;iBACH,MAAM;cACT,MAAM;cACN,MAAM;iBAGH,MAAM;eACR,MAAM;iBACJ,MAAM;cACT,MAAM;iBACH,MAAM;mBACJ,MAAM;CACtB,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Simple ANSI color utility for terminal output
3
+ * Dependency-free and works in Node.js/Bun environments
4
+ */
5
+ const ESC = "\u001b[";
6
+ const RESET = `${ESC}0m`;
7
+ const colors = {
8
+ reset: RESET,
9
+ bold: `${ESC}1m`,
10
+ dim: `${ESC}2m`,
11
+ italic: `${ESC}3m`,
12
+ underline: `${ESC}4m`,
13
+ // Foreground colors
14
+ black: `${ESC}30m`,
15
+ red: `${ESC}31m`,
16
+ green: `${ESC}32m`,
17
+ yellow: `${ESC}33m`,
18
+ blue: `${ESC}34m`,
19
+ magenta: `${ESC}35m`,
20
+ cyan: `${ESC}36m`,
21
+ white: `${ESC}37m`,
22
+ gray: `${ESC}90m`,
23
+ // Foreground bright colors
24
+ brightRed: `${ESC}91m`,
25
+ brightGreen: `${ESC}92m`,
26
+ brightYellow: `${ESC}93m`,
27
+ brightBlue: `${ESC}94m`,
28
+ brightMagenta: `${ESC}95m`,
29
+ brightCyan: `${ESC}96m`,
30
+ brightWhite: `${ESC}97m`,
31
+ };
32
+ export const c = {
33
+ text: (text, ...colorNames) => {
34
+ const codes = colorNames.map((name) => colors[name]).join("");
35
+ return `${codes}${text}${RESET}`;
36
+ },
37
+ // Shortcuts
38
+ bold: (t) => c.text(t, "bold"),
39
+ dim: (t) => c.text(t, "dim"),
40
+ red: (t) => c.text(t, "red"),
41
+ green: (t) => c.text(t, "green"),
42
+ yellow: (t) => c.text(t, "yellow"),
43
+ blue: (t) => c.text(t, "blue"),
44
+ magenta: (t) => c.text(t, "magenta"),
45
+ cyan: (t) => c.text(t, "cyan"),
46
+ gray: (t) => c.text(t, "gray"),
47
+ // Combinations
48
+ success: (t) => c.text(t, "green", "bold"),
49
+ error: (t) => c.text(t, "red", "bold"),
50
+ warning: (t) => c.text(t, "yellow", "bold"),
51
+ info: (t) => c.text(t, "cyan", "bold"),
52
+ primary: (t) => c.text(t, "blue", "bold"),
53
+ underline: (t) => c.text(t, "underline"),
54
+ };
@@ -0,0 +1,13 @@
1
+ export interface RetryOptions {
2
+ maxRetries?: number;
3
+ initialDelay?: number;
4
+ maxDelay?: number;
5
+ factor?: number;
6
+ retryCondition?: (error: unknown) => boolean;
7
+ }
8
+ export declare const DEFAULT_RETRY_OPTIONS: Required<RetryOptions>;
9
+ /**
10
+ * Retries an async function with exponential backoff
11
+ */
12
+ export declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
13
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/utils/retry.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;CAC9C;AAED,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,YAAY,CAaxD,CAAC;AAEF;;GAEG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,CAAC,CAAC,CAwBZ"}
@@ -0,0 +1,37 @@
1
+ import { isAxiosError } from "axios";
2
+ export const DEFAULT_RETRY_OPTIONS = {
3
+ maxRetries: 3,
4
+ initialDelay: 1000,
5
+ maxDelay: 5000,
6
+ factor: 2,
7
+ retryCondition: (error) => {
8
+ // Retry on network errors or 5xx server errors
9
+ if (isAxiosError(error)) {
10
+ if (!error.response)
11
+ return true; // Network error
12
+ return error.response.status >= 500;
13
+ }
14
+ return true; // Retry on other unknown errors
15
+ },
16
+ };
17
+ /**
18
+ * Retries an async function with exponential backoff
19
+ */
20
+ export async function withRetry(fn, options = {}) {
21
+ const config = { ...DEFAULT_RETRY_OPTIONS, ...options };
22
+ let lastError;
23
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
24
+ try {
25
+ return await fn();
26
+ }
27
+ catch (error) {
28
+ lastError = error;
29
+ if (attempt === config.maxRetries || !config.retryCondition(error)) {
30
+ throw error;
31
+ }
32
+ const delay = Math.min(config.initialDelay * Math.pow(config.factor, attempt), config.maxDelay);
33
+ await new Promise((resolve) => setTimeout(resolve, delay));
34
+ }
35
+ }
36
+ throw lastError;
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/sdk",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@anthropic-ai/sdk": "^0.71.2",
44
- "@locusai/shared": "^0.4.0",
44
+ "@locusai/shared": "^0.4.2",
45
45
  "axios": "^1.13.2",
46
46
  "events": "^3.3.0",
47
47
  "globby": "^14.0.2"
@@ -2,6 +2,7 @@ import type { Sprint, Task, TaskStatus } from "@locusai/shared";
2
2
  import { AnthropicClient } from "../ai/anthropic-client";
3
3
  import { ClaudeRunner } from "../ai/claude-runner";
4
4
  import { LocusClient } from "../index";
5
+ import { c } from "../utils/colors";
5
6
  import { ArtifactSyncer } from "./artifact-syncer";
6
7
  import { CodebaseIndexerService } from "./codebase-indexer-service";
7
8
  import { SprintPlanner } from "./sprint-planner";
@@ -48,6 +49,12 @@ export class AgentWorker {
48
49
  this.client = new LocusClient({
49
50
  baseUrl: config.apiBase,
50
51
  token: config.apiKey,
52
+ retryOptions: {
53
+ maxRetries: 3,
54
+ initialDelay: 1000,
55
+ maxDelay: 5000,
56
+ factor: 2,
57
+ },
51
58
  });
52
59
 
53
60
  // Initialize AI clients
@@ -106,9 +113,16 @@ export class AgentWorker {
106
113
 
107
114
  log(message: string, level: "info" | "success" | "warn" | "error" = "info") {
108
115
  const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
116
+ const colorFn = {
117
+ info: c.cyan,
118
+ success: c.green,
119
+ warn: c.yellow,
120
+ error: c.red,
121
+ }[level];
109
122
  const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
123
+
110
124
  console.log(
111
- `[${timestamp}] [${this.config.agentId.slice(-8)}] ${prefix} ${message}`
125
+ `${c.dim(`[${timestamp}]`)} ${c.bold(`[${this.config.agentId.slice(-8)}]`)} ${colorFn(`${prefix} ${message}`)}`
112
126
  );
113
127
  }
114
128
 
@@ -38,8 +38,13 @@ export class ClaudeRunner {
38
38
  );
39
39
  claude.on("close", (code) => {
40
40
  if (code === 0) resolve(output);
41
- else
42
- reject(new Error(`Claude exited with code ${code}: ${errorOutput}`));
41
+ else {
42
+ const detail = errorOutput.trim();
43
+ const message = detail
44
+ ? `Claude CLI error: ${detail}`
45
+ : `Claude CLI exited with code ${code}. Please ensure the Claude CLI is installed and you are logged in (run 'claude' manually to check).`;
46
+ reject(new Error(message));
47
+ }
43
48
  });
44
49
 
45
50
  claude.stdin.write(prompt);
@@ -34,23 +34,43 @@ export class CodebaseIndexer {
34
34
  // 1. Get a comprehensive but clean file tree
35
35
  const files = await globby(["**/*"], {
36
36
  cwd: this.projectPath,
37
+ gitignore: true,
37
38
  ignore: [
38
39
  "**/node_modules/**",
39
40
  "**/dist/**",
40
41
  "**/build/**",
42
+ "**/target/**", // Rust build artifacts
43
+ "**/bin/**",
44
+ "**/obj/**",
41
45
  "**/.next/**",
46
+ "**/.svelte-kit/**",
47
+ "**/.nuxt/**",
48
+ "**/.cache/**",
42
49
  "**/out/**",
43
50
  "**/__tests__/**",
51
+ "**/coverage/**",
44
52
  "**/*.test.*",
45
53
  "**/*.spec.*",
46
54
  "**/*.d.ts",
47
55
  "**/tsconfig.tsbuildinfo",
48
56
  "**/.locus/*.json", // Ignore index and other system JSONs
49
- "**/.locus/*.md", // Ignore system MDs if any (except artifacts handled below)
57
+ "**/.locus/*.md", // Ignore system MDs
50
58
  "**/.locus/!(artifacts)/**", // Ignore everything in .locus EXCEPT artifacts
51
- "bun.lock",
52
- "package-lock.json",
53
- "yarn.lock",
59
+ "**/.git/**",
60
+ "**/.svn/**",
61
+ "**/.hg/**",
62
+ "**/.vscode/**",
63
+ "**/.idea/**",
64
+ "**/.DS_Store",
65
+ "**/bun.lock",
66
+ "**/package-lock.json",
67
+ "**/yarn.lock",
68
+ "**/pnpm-lock.yaml",
69
+ "**/Cargo.lock",
70
+ "**/go.sum",
71
+ "**/poetry.lock",
72
+ // Binary/Large Assets
73
+ "**/*.{png,jpg,jpeg,gif,svg,ico,mp4,webm,wav,mp3,woff,woff2,eot,ttf,otf,pdf,zip,tar.gz,rar}",
54
74
  ],
55
75
  });
56
76
 
package/src/events.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { EventEmitter } from "events";
2
2
 
3
+ import { RetryOptions } from "./utils/retry";
4
+
3
5
  export enum LocusEvent {
4
6
  TOKEN_EXPIRED = "TOKEN_EXPIRED",
5
7
  AUTH_ERROR = "AUTH_ERROR",
@@ -10,6 +12,7 @@ export interface LocusConfig {
10
12
  baseUrl: string;
11
13
  token?: string | null;
12
14
  timeout?: number;
15
+ retryOptions?: RetryOptions;
13
16
  }
14
17
 
15
18
  export class LocusEmitter extends EventEmitter {
package/src/index-node.ts CHANGED
@@ -18,3 +18,6 @@ export * from "./index";
18
18
 
19
19
  // Node.js-only: Orchestrator
20
20
  export { AgentOrchestrator, type OrchestratorConfig } from "./orchestrator";
21
+
22
+ // Utilities
23
+ export { c } from "./utils/colors";
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import axios, { AxiosInstance } from "axios";
1
+ import axios, { AxiosInstance, InternalAxiosRequestConfig } from "axios";
2
2
  import { LocusConfig, LocusEmitter, LocusEvent } from "./events";
3
3
  import { AuthModule } from "./modules/auth";
4
4
  import { CiModule } from "./modules/ci";
@@ -8,6 +8,7 @@ import { OrganizationsModule } from "./modules/organizations";
8
8
  import { SprintsModule } from "./modules/sprints";
9
9
  import { TasksModule } from "./modules/tasks";
10
10
  import { WorkspacesModule } from "./modules/workspaces";
11
+ import { RetryOptions } from "./utils/retry";
11
12
 
12
13
  // Browser-safe exports only
13
14
  export * from "./events";
@@ -56,6 +57,45 @@ export class LocusClient {
56
57
  this.invitations = new InvitationsModule(this.api, this.emitter);
57
58
  this.docs = new DocsModule(this.api, this.emitter);
58
59
  this.ci = new CiModule(this.api, this.emitter);
60
+
61
+ if (config.retryOptions) {
62
+ this.setupRetryInterceptor(config.retryOptions);
63
+ }
64
+ }
65
+
66
+ private setupRetryInterceptor(retryOptions: RetryOptions) {
67
+ this.api.interceptors.response.use(undefined, async (error) => {
68
+ const config = error.config as InternalAxiosRequestConfig & {
69
+ _retryCount?: number;
70
+ };
71
+
72
+ if (!config || !retryOptions) {
73
+ return Promise.reject(error);
74
+ }
75
+
76
+ config._retryCount = config._retryCount || 0;
77
+
78
+ const maxRetries = retryOptions.maxRetries ?? 3;
79
+ const shouldRetry =
80
+ config._retryCount < maxRetries &&
81
+ (retryOptions.retryCondition
82
+ ? retryOptions.retryCondition(error)
83
+ : !error.response || error.response.status >= 500);
84
+
85
+ if (shouldRetry) {
86
+ config._retryCount++;
87
+ const delay = Math.min(
88
+ (retryOptions.initialDelay ?? 1000) *
89
+ Math.pow(retryOptions.factor ?? 2, config._retryCount - 1),
90
+ retryOptions.maxDelay ?? 5000
91
+ );
92
+
93
+ await new Promise((resolve) => setTimeout(resolve, delay));
94
+ return this.api(config);
95
+ }
96
+
97
+ return Promise.reject(error);
98
+ });
59
99
  }
60
100
 
61
101
  private setupInterceptors() {
@@ -4,6 +4,7 @@ import { dirname, join } from "node:path";
4
4
  import { Task, TaskPriority, TaskStatus } from "@locusai/shared";
5
5
  import { EventEmitter } from "events";
6
6
  import { LocusClient } from "./index";
7
+ import { c } from "./utils/colors";
7
8
 
8
9
  export interface AgentConfig {
9
10
  id: string;
@@ -62,14 +63,16 @@ export class AgentOrchestrator extends EventEmitter {
62
63
  this.config.workspaceId
63
64
  );
64
65
  if (sprint?.id) {
65
- console.log(`📋 Using active sprint: ${sprint.name}`);
66
+ console.log(c.info(`📋 Using active sprint: ${sprint.name}`));
66
67
  return sprint.id;
67
68
  }
68
69
  } catch {
69
70
  // No active sprint found, will work with all tasks
70
71
  }
71
72
 
72
- console.log("ℹ No sprint specified, working with all workspace tasks");
73
+ console.log(
74
+ c.dim("ℹ No sprint specified, working with all workspace tasks")
75
+ );
73
76
  return "";
74
77
  }
75
78
 
@@ -107,20 +110,20 @@ export class AgentOrchestrator extends EventEmitter {
107
110
  sprintId: this.resolvedSprintId,
108
111
  });
109
112
 
110
- console.log("\n🤖 Locus Agent Orchestrator");
111
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
112
- console.log(`Workspace: ${this.config.workspaceId}`);
113
+ console.log(`\n${c.primary("🤖 Locus Agent Orchestrator")}`);
114
+ console.log(c.dim("----------------------------------------------"));
115
+ console.log(`${c.bold("Workspace:")} ${this.config.workspaceId}`);
113
116
  if (this.resolvedSprintId) {
114
- console.log(`Sprint: ${this.resolvedSprintId}`);
117
+ console.log(`${c.bold("Sprint:")} ${this.resolvedSprintId}`);
115
118
  }
116
- console.log(`API Base: ${this.config.apiBase}`);
117
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
119
+ console.log(`${c.bold("API Base:")} ${this.config.apiBase}`);
120
+ console.log(c.dim("----------------------------------------------\n"));
118
121
 
119
122
  // Check if there are tasks to work on before spawning
120
123
  const tasks = await this.getAvailableTasks();
121
124
 
122
125
  if (tasks.length === 0) {
123
- console.log("ℹ No available tasks found in the backlog.");
126
+ console.log(c.dim("ℹ No available tasks found in the backlog."));
124
127
  return;
125
128
  }
126
129
 
@@ -138,7 +141,7 @@ export class AgentOrchestrator extends EventEmitter {
138
141
  await this.sleep(2000);
139
142
  }
140
143
 
141
- console.log("\n✅ Orchestrator finished");
144
+ console.log(`\n${c.success("✅ Orchestrator finished")}`);
142
145
  }
143
146
 
144
147
  /**
@@ -175,7 +178,7 @@ export class AgentOrchestrator extends EventEmitter {
175
178
 
176
179
  this.agents.set(agentId, agentState);
177
180
 
178
- console.log(`🚀 Agent started: ${agentId}\n`);
181
+ console.log(`${c.primary("🚀 Agent started:")} ${c.bold(agentId)}\n`);
179
182
 
180
183
  // Build arguments for agent worker
181
184
  // Try multiple resolution strategies
@@ -243,8 +246,8 @@ export class AgentOrchestrator extends EventEmitter {
243
246
  workerArgs.push("--sprint-id", this.resolvedSprintId);
244
247
  }
245
248
 
246
- // Use bun to run the worker script
247
- const agentProcess = spawn("bun", ["run", workerPath, ...workerArgs]);
249
+ // Use node to run the worker script
250
+ const agentProcess = spawn(process.execPath, [workerPath, ...workerArgs]);
248
251
 
249
252
  agentState.process = agentProcess;
250
253
 
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Simple ANSI color utility for terminal output
3
+ * Dependency-free and works in Node.js/Bun environments
4
+ */
5
+
6
+ const ESC = "\u001b[";
7
+ const RESET = `${ESC}0m`;
8
+
9
+ const colors = {
10
+ reset: RESET,
11
+ bold: `${ESC}1m`,
12
+ dim: `${ESC}2m`,
13
+ italic: `${ESC}3m`,
14
+ underline: `${ESC}4m`,
15
+
16
+ // Foreground colors
17
+ black: `${ESC}30m`,
18
+ red: `${ESC}31m`,
19
+ green: `${ESC}32m`,
20
+ yellow: `${ESC}33m`,
21
+ blue: `${ESC}34m`,
22
+ magenta: `${ESC}35m`,
23
+ cyan: `${ESC}36m`,
24
+ white: `${ESC}37m`,
25
+ gray: `${ESC}90m`,
26
+
27
+ // Foreground bright colors
28
+ brightRed: `${ESC}91m`,
29
+ brightGreen: `${ESC}92m`,
30
+ brightYellow: `${ESC}93m`,
31
+ brightBlue: `${ESC}94m`,
32
+ brightMagenta: `${ESC}95m`,
33
+ brightCyan: `${ESC}96m`,
34
+ brightWhite: `${ESC}97m`,
35
+ };
36
+
37
+ type ColorName = keyof typeof colors;
38
+
39
+ export const c = {
40
+ text: (text: string, ...colorNames: ColorName[]) => {
41
+ const codes = colorNames.map((name) => colors[name]).join("");
42
+ return `${codes}${text}${RESET}`;
43
+ },
44
+
45
+ // Shortcuts
46
+ bold: (t: string) => c.text(t, "bold"),
47
+ dim: (t: string) => c.text(t, "dim"),
48
+ red: (t: string) => c.text(t, "red"),
49
+ green: (t: string) => c.text(t, "green"),
50
+ yellow: (t: string) => c.text(t, "yellow"),
51
+ blue: (t: string) => c.text(t, "blue"),
52
+ magenta: (t: string) => c.text(t, "magenta"),
53
+ cyan: (t: string) => c.text(t, "cyan"),
54
+ gray: (t: string) => c.text(t, "gray"),
55
+
56
+ // Combinations
57
+ success: (t: string) => c.text(t, "green", "bold"),
58
+ error: (t: string) => c.text(t, "red", "bold"),
59
+ warning: (t: string) => c.text(t, "yellow", "bold"),
60
+ info: (t: string) => c.text(t, "cyan", "bold"),
61
+ primary: (t: string) => c.text(t, "blue", "bold"),
62
+ underline: (t: string) => c.text(t, "underline"),
63
+ };
@@ -0,0 +1,56 @@
1
+ import { isAxiosError } from "axios";
2
+
3
+ export interface RetryOptions {
4
+ maxRetries?: number;
5
+ initialDelay?: number;
6
+ maxDelay?: number;
7
+ factor?: number;
8
+ retryCondition?: (error: unknown) => boolean;
9
+ }
10
+
11
+ export const DEFAULT_RETRY_OPTIONS: Required<RetryOptions> = {
12
+ maxRetries: 3,
13
+ initialDelay: 1000,
14
+ maxDelay: 5000,
15
+ factor: 2,
16
+ retryCondition: (error: unknown) => {
17
+ // Retry on network errors or 5xx server errors
18
+ if (isAxiosError(error)) {
19
+ if (!error.response) return true; // Network error
20
+ return error.response.status >= 500;
21
+ }
22
+ return true; // Retry on other unknown errors
23
+ },
24
+ };
25
+
26
+ /**
27
+ * Retries an async function with exponential backoff
28
+ */
29
+ export async function withRetry<T>(
30
+ fn: () => Promise<T>,
31
+ options: RetryOptions = {}
32
+ ): Promise<T> {
33
+ const config = { ...DEFAULT_RETRY_OPTIONS, ...options };
34
+ let lastError: unknown;
35
+
36
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
37
+ try {
38
+ return await fn();
39
+ } catch (error: unknown) {
40
+ lastError = error;
41
+
42
+ if (attempt === config.maxRetries || !config.retryCondition(error)) {
43
+ throw error;
44
+ }
45
+
46
+ const delay = Math.min(
47
+ config.initialDelay * Math.pow(config.factor, attempt),
48
+ config.maxDelay
49
+ );
50
+
51
+ await new Promise((resolve) => setTimeout(resolve, delay));
52
+ }
53
+ }
54
+
55
+ throw lastError;
56
+ }