@locusai/cli 0.16.1 → 0.16.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 (3) hide show
  1. package/bin/agent/worker.js +134 -260
  2. package/bin/locus.js +796 -859
  3. package/package.json +4 -3
package/bin/locus.js CHANGED
@@ -7065,6 +7065,112 @@ var init_resolve_bin = __esm(() => {
7065
7065
  ENV_VARS_TO_STRIP = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
7066
7066
  });
7067
7067
 
7068
+ // ../sdk/src/ai/claude-stream-parser.ts
7069
+ class ClaudeStreamParser {
7070
+ activeTools = new Map;
7071
+ parseLineToChunk(line) {
7072
+ if (!line.trim())
7073
+ return null;
7074
+ try {
7075
+ const item = JSON.parse(line);
7076
+ return this.processItemToChunk(item);
7077
+ } catch {
7078
+ return null;
7079
+ }
7080
+ }
7081
+ parseLine(line, log) {
7082
+ if (!line.trim())
7083
+ return null;
7084
+ try {
7085
+ const item = JSON.parse(line);
7086
+ return this.processItem(item, log);
7087
+ } catch {
7088
+ return null;
7089
+ }
7090
+ }
7091
+ processItemToChunk(item) {
7092
+ if (item.type === "result") {
7093
+ return { type: "result", content: item.result || "" };
7094
+ }
7095
+ if (item.type === "stream_event" && item.event) {
7096
+ return this.handleEventToChunk(item.event);
7097
+ }
7098
+ return null;
7099
+ }
7100
+ handleEventToChunk(event) {
7101
+ const { type, delta, content_block, index } = event;
7102
+ if (type === "content_block_delta" && delta?.type === "text_delta") {
7103
+ return { type: "text_delta", content: delta.text || "" };
7104
+ }
7105
+ if (type === "content_block_delta" && delta?.type === "input_json_delta" && delta.partial_json !== undefined && index !== undefined) {
7106
+ const activeTool = this.activeTools.get(index);
7107
+ if (activeTool) {
7108
+ activeTool.parameterJson += delta.partial_json;
7109
+ }
7110
+ return null;
7111
+ }
7112
+ if (type === "content_block_start" && content_block) {
7113
+ if (content_block.type === "tool_use" && content_block.name) {
7114
+ if (index !== undefined) {
7115
+ this.activeTools.set(index, {
7116
+ name: content_block.name,
7117
+ id: content_block.id,
7118
+ index,
7119
+ parameterJson: "",
7120
+ startTime: Date.now()
7121
+ });
7122
+ }
7123
+ return {
7124
+ type: "tool_use",
7125
+ tool: content_block.name,
7126
+ id: content_block.id
7127
+ };
7128
+ }
7129
+ if (content_block.type === "thinking") {
7130
+ return { type: "thinking" };
7131
+ }
7132
+ }
7133
+ if (type === "content_block_stop" && index !== undefined) {
7134
+ const activeTool = this.activeTools.get(index);
7135
+ if (activeTool?.parameterJson) {
7136
+ try {
7137
+ const parameters = JSON.parse(activeTool.parameterJson);
7138
+ return {
7139
+ type: "tool_parameters",
7140
+ tool: activeTool.name,
7141
+ id: activeTool.id,
7142
+ parameters
7143
+ };
7144
+ } catch {}
7145
+ }
7146
+ return null;
7147
+ }
7148
+ return null;
7149
+ }
7150
+ processItem(item, log) {
7151
+ if (item.type === "result") {
7152
+ return item.result || "";
7153
+ }
7154
+ if (item.type === "stream_event" && item.event) {
7155
+ this.handleEvent(item.event, log);
7156
+ }
7157
+ return null;
7158
+ }
7159
+ handleEvent(event, log) {
7160
+ const { type, content_block } = event;
7161
+ if (type === "content_block_start" && content_block) {
7162
+ if (content_block.type === "tool_use" && content_block.name) {
7163
+ log?.(`
7164
+ ${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
7165
+ `, "info");
7166
+ }
7167
+ }
7168
+ }
7169
+ }
7170
+ var init_claude_stream_parser = __esm(() => {
7171
+ init_colors();
7172
+ });
7173
+
7068
7174
  // ../sdk/src/ai/claude-runner.ts
7069
7175
  import { spawn } from "node:child_process";
7070
7176
  import { resolve } from "node:path";
@@ -7075,7 +7181,6 @@ class ClaudeRunner {
7075
7181
  projectPath;
7076
7182
  eventEmitter;
7077
7183
  currentToolName;
7078
- activeTools = new Map;
7079
7184
  activeProcess = null;
7080
7185
  aborted = false;
7081
7186
  timeoutMs;
@@ -7110,7 +7215,7 @@ class ClaudeRunner {
7110
7215
  }
7111
7216
  if (!isLastAttempt) {
7112
7217
  const delay = Math.pow(2, attempt) * 1000;
7113
- console.warn(`Claude CLI attempt ${attempt} failed: ${err.message}. Retrying in ${delay}ms...`);
7218
+ this.log?.(`Claude CLI attempt ${attempt} failed: ${err.message}. Retrying in ${delay}ms...`, "warn");
7114
7219
  await new Promise((resolve2) => setTimeout(resolve2, delay));
7115
7220
  }
7116
7221
  }
@@ -7150,6 +7255,7 @@ class ClaudeRunner {
7150
7255
  }
7151
7256
  async* runStream(prompt) {
7152
7257
  this.aborted = false;
7258
+ const parser = new ClaudeStreamParser;
7153
7259
  const args = this.buildCliArgs();
7154
7260
  const env = getAugmentedEnv({
7155
7261
  FORCE_COLOR: "1",
@@ -7210,7 +7316,7 @@ class ClaudeRunner {
7210
7316
  `);
7211
7317
  buffer = lines.pop() || "";
7212
7318
  for (const line of lines) {
7213
- const chunk = this.parseStreamLineToChunk(line);
7319
+ const chunk = parser.parseLineToChunk(line);
7214
7320
  if (chunk) {
7215
7321
  if (chunk.type === "result") {
7216
7322
  lastResultContent = chunk.content;
@@ -7319,77 +7425,9 @@ class ClaudeRunner {
7319
7425
  break;
7320
7426
  }
7321
7427
  }
7322
- parseStreamLineToChunk(line) {
7323
- if (!line.trim())
7324
- return null;
7325
- try {
7326
- const item = JSON.parse(line);
7327
- return this.processStreamItemToChunk(item);
7328
- } catch {
7329
- return null;
7330
- }
7331
- }
7332
- processStreamItemToChunk(item) {
7333
- if (item.type === "result") {
7334
- return { type: "result", content: item.result || "" };
7335
- }
7336
- if (item.type === "stream_event" && item.event) {
7337
- return this.handleEventToChunk(item.event);
7338
- }
7339
- return null;
7340
- }
7341
- handleEventToChunk(event) {
7342
- const { type, delta, content_block, index } = event;
7343
- if (type === "content_block_delta" && delta?.type === "text_delta") {
7344
- return { type: "text_delta", content: delta.text || "" };
7345
- }
7346
- if (type === "content_block_delta" && delta?.type === "input_json_delta" && delta.partial_json !== undefined && index !== undefined) {
7347
- const activeTool = this.activeTools.get(index);
7348
- if (activeTool) {
7349
- activeTool.parameterJson += delta.partial_json;
7350
- }
7351
- return null;
7352
- }
7353
- if (type === "content_block_start" && content_block) {
7354
- if (content_block.type === "tool_use" && content_block.name) {
7355
- if (index !== undefined) {
7356
- this.activeTools.set(index, {
7357
- name: content_block.name,
7358
- id: content_block.id,
7359
- index,
7360
- parameterJson: "",
7361
- startTime: Date.now()
7362
- });
7363
- }
7364
- return {
7365
- type: "tool_use",
7366
- tool: content_block.name,
7367
- id: content_block.id
7368
- };
7369
- }
7370
- if (content_block.type === "thinking") {
7371
- return { type: "thinking" };
7372
- }
7373
- }
7374
- if (type === "content_block_stop" && index !== undefined) {
7375
- const activeTool = this.activeTools.get(index);
7376
- if (activeTool?.parameterJson) {
7377
- try {
7378
- const parameters = JSON.parse(activeTool.parameterJson);
7379
- return {
7380
- type: "tool_parameters",
7381
- tool: activeTool.name,
7382
- id: activeTool.id,
7383
- parameters
7384
- };
7385
- } catch {}
7386
- }
7387
- return null;
7388
- }
7389
- return null;
7390
- }
7391
7428
  executeRun(prompt) {
7392
7429
  this.aborted = false;
7430
+ const parser = new ClaudeStreamParser;
7393
7431
  return new Promise((resolve2, reject) => {
7394
7432
  const args = this.buildCliArgs();
7395
7433
  const env = getAugmentedEnv({
@@ -7412,7 +7450,7 @@ class ClaudeRunner {
7412
7450
  `);
7413
7451
  buffer = lines.pop() || "";
7414
7452
  for (const line of lines) {
7415
- const result = this.handleStreamLine(line);
7453
+ const result = parser.parseLine(line, this.log);
7416
7454
  if (result)
7417
7455
  finalResult = result;
7418
7456
  }
@@ -7453,35 +7491,6 @@ class ClaudeRunner {
7453
7491
  claude.stdin.end();
7454
7492
  });
7455
7493
  }
7456
- handleStreamLine(line) {
7457
- if (!line.trim())
7458
- return null;
7459
- try {
7460
- const item = JSON.parse(line);
7461
- return this.processStreamItem(item);
7462
- } catch {
7463
- return null;
7464
- }
7465
- }
7466
- processStreamItem(item) {
7467
- if (item.type === "result") {
7468
- return item.result || "";
7469
- }
7470
- if (item.type === "stream_event" && item.event) {
7471
- this.handleEvent(item.event);
7472
- }
7473
- return null;
7474
- }
7475
- handleEvent(event) {
7476
- const { type, content_block } = event;
7477
- if (type === "content_block_start" && content_block) {
7478
- if (content_block.type === "tool_use" && content_block.name) {
7479
- this.log?.(`
7480
- ${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
7481
- `, "info");
7482
- }
7483
- }
7484
- }
7485
7494
  shouldSuppressLine(line) {
7486
7495
  const infoLogRegex = /^\[\d{2}:\d{2}:\d{2}\]\s\[.*?\]\sℹ\s*$/;
7487
7496
  return infoLogRegex.test(line.trim());
@@ -7495,8 +7504,8 @@ ${c.primary("[Claude]")} ${c.bold(`Running ${content_block.name}...`)}
7495
7504
  var DEFAULT_TIMEOUT_MS;
7496
7505
  var init_claude_runner = __esm(() => {
7497
7506
  init_config();
7498
- init_colors();
7499
7507
  init_resolve_bin();
7508
+ init_claude_stream_parser();
7500
7509
  DEFAULT_TIMEOUT_MS = 60 * 60 * 1000;
7501
7510
  });
7502
7511
 
@@ -7548,7 +7557,7 @@ class CodexRunner {
7548
7557
  }
7549
7558
  if (attempt < maxRetries) {
7550
7559
  const delay = Math.pow(2, attempt) * 1000;
7551
- console.warn(`Codex CLI attempt ${attempt} failed: ${lastError.message}. Retrying in ${delay}ms...`);
7560
+ this.log?.(`Codex CLI attempt ${attempt} failed: ${lastError.message}. Retrying in ${delay}ms...`, "warn");
7552
7561
  await this.sleep(delay);
7553
7562
  }
7554
7563
  }
@@ -7840,6 +7849,20 @@ var init_codex_runner = __esm(() => {
7840
7849
  });
7841
7850
 
7842
7851
  // ../sdk/src/ai/factory.ts
7852
+ function createWorkerLogger(agentId, prefix) {
7853
+ const tag = prefix ? `${prefix}:${agentId.slice(-8)}` : agentId.slice(-8);
7854
+ return (message, level = "info") => {
7855
+ const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
7856
+ const colorFn = {
7857
+ info: c.cyan,
7858
+ success: c.green,
7859
+ warn: c.yellow,
7860
+ error: c.red
7861
+ }[level];
7862
+ const icon = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
7863
+ console.log(`${c.dim(`[${timestamp}]`)} ${c.bold(`[${tag}]`)} ${colorFn(`${icon} ${message}`)}`);
7864
+ };
7865
+ }
7843
7866
  function createAiRunner(provider, config) {
7844
7867
  const resolvedProvider = provider ?? PROVIDER.CLAUDE;
7845
7868
  const model = config.model ?? DEFAULT_MODEL[resolvedProvider];
@@ -7854,8 +7877,10 @@ function createAiRunner(provider, config) {
7854
7877
  return new ClaudeRunner(config.projectPath, model, config.log, config.timeoutMs);
7855
7878
  }
7856
7879
  }
7880
+ var noopLogger = () => {};
7857
7881
  var init_factory = __esm(() => {
7858
7882
  init_config();
7883
+ init_colors();
7859
7884
  init_claude_runner();
7860
7885
  init_codex_runner();
7861
7886
  });
@@ -23201,49 +23226,6 @@ var init_docs = __esm(() => {
23201
23226
  };
23202
23227
  });
23203
23228
 
23204
- // ../sdk/src/modules/instances.ts
23205
- var InstancesModule;
23206
- var init_instances = __esm(() => {
23207
- InstancesModule = class InstancesModule extends BaseModule {
23208
- async list(workspaceId) {
23209
- const { data } = await this.api.get(`/workspaces/${workspaceId}/aws-instances`);
23210
- return data.instances;
23211
- }
23212
- async get(workspaceId, instanceId) {
23213
- const { data } = await this.api.get(`/workspaces/${workspaceId}/aws-instances/${instanceId}`);
23214
- return data.instance;
23215
- }
23216
- async provision(workspaceId, body) {
23217
- const { data } = await this.api.post(`/workspaces/${workspaceId}/aws-instances`, body);
23218
- return data.instance;
23219
- }
23220
- async performAction(workspaceId, instanceId, action) {
23221
- const { data } = await this.api.post(`/workspaces/${workspaceId}/aws-instances/${instanceId}/actions`, { action });
23222
- return data.instance;
23223
- }
23224
- async sync(workspaceId, instanceId) {
23225
- const { data } = await this.api.post(`/workspaces/${workspaceId}/aws-instances/${instanceId}/sync`);
23226
- return data.instance;
23227
- }
23228
- async checkUpdates(workspaceId, instanceId) {
23229
- const { data } = await this.api.get(`/workspaces/${workspaceId}/aws-instances/${instanceId}/updates`);
23230
- return data.update;
23231
- }
23232
- async applyUpdate(workspaceId, instanceId) {
23233
- const { data } = await this.api.post(`/workspaces/${workspaceId}/aws-instances/${instanceId}/updates`);
23234
- return data.update;
23235
- }
23236
- async getSecurity(workspaceId, instanceId) {
23237
- const { data } = await this.api.get(`/workspaces/${workspaceId}/aws-instances/${instanceId}/security`);
23238
- return data.rules;
23239
- }
23240
- async updateSecurity(workspaceId, instanceId, body) {
23241
- const { data } = await this.api.put(`/workspaces/${workspaceId}/aws-instances/${instanceId}/security`, body);
23242
- return data.rules;
23243
- }
23244
- };
23245
- });
23246
-
23247
23229
  // ../sdk/src/modules/invitations.ts
23248
23230
  var InvitationsModule;
23249
23231
  var init_invitations = __esm(() => {
@@ -37273,7 +37255,7 @@ var init_constants = __esm(() => {
37273
37255
  });
37274
37256
 
37275
37257
  // ../shared/src/enums.ts
37276
- var UserRole, MembershipRole, TaskStatus, TaskPriority, AssigneeRole, SprintStatus, InstanceStatus, AwsRegion, EventType;
37258
+ var UserRole, MembershipRole, TaskStatus, TaskPriority, AssigneeRole, SprintStatus, EventType;
37277
37259
  var init_enums = __esm(() => {
37278
37260
  ((UserRole2) => {
37279
37261
  UserRole2["USER"] = "USER";
@@ -37310,16 +37292,6 @@ var init_enums = __esm(() => {
37310
37292
  SprintStatus2["ACTIVE"] = "ACTIVE";
37311
37293
  SprintStatus2["COMPLETED"] = "COMPLETED";
37312
37294
  })(SprintStatus ||= {});
37313
- ((InstanceStatus2) => {
37314
- InstanceStatus2["PROVISIONING"] = "PROVISIONING";
37315
- InstanceStatus2["RUNNING"] = "RUNNING";
37316
- InstanceStatus2["STOPPED"] = "STOPPED";
37317
- InstanceStatus2["TERMINATED"] = "TERMINATED";
37318
- InstanceStatus2["ERROR"] = "ERROR";
37319
- })(InstanceStatus ||= {});
37320
- ((AwsRegion2) => {
37321
- AwsRegion2["US_EAST_1"] = "us-east-1";
37322
- })(AwsRegion ||= {});
37323
37295
  ((EventType2) => {
37324
37296
  EventType2["TASK_CREATED"] = "TASK_CREATED";
37325
37297
  EventType2["TASK_UPDATED"] = "TASK_UPDATED";
@@ -37667,76 +37639,6 @@ var init_auth2 = __esm(() => {
37667
37639
  });
37668
37640
  });
37669
37641
 
37670
- // ../shared/src/models/aws-instance.ts
37671
- var InstanceAction, AwsCredentialsSchema, IntegrationSchema, AwsInstanceSchema, CreateAwsInstanceSchema, UpdateAwsInstanceSchema, SaveAwsCredentialsSchema, ProvisionAwsInstanceSchema, InstanceActionBodySchema, InstanceIdParamSchema, CIDR_REGEX, UpdateSecurityRulesSchema;
37672
- var init_aws_instance = __esm(() => {
37673
- init_zod();
37674
- init_common();
37675
- init_enums();
37676
- ((InstanceAction2) => {
37677
- InstanceAction2["START"] = "START";
37678
- InstanceAction2["STOP"] = "STOP";
37679
- InstanceAction2["TERMINATE"] = "TERMINATE";
37680
- })(InstanceAction ||= {});
37681
- AwsCredentialsSchema = exports_external.object({
37682
- accessKeyId: exports_external.string().min(1),
37683
- secretAccessKey: exports_external.string().min(1),
37684
- region: exports_external.enum(AwsRegion).default("us-east-1" /* US_EAST_1 */)
37685
- });
37686
- IntegrationSchema = exports_external.object({
37687
- name: exports_external.string(),
37688
- config: exports_external.record(exports_external.string(), exports_external.string())
37689
- });
37690
- AwsInstanceSchema = BaseEntitySchema.extend({
37691
- workspaceId: exports_external.uuid(),
37692
- instanceId: exports_external.string(),
37693
- status: exports_external.enum(InstanceStatus),
37694
- instanceType: exports_external.enum(["t3.micro", "t3.small", "t3.medium"]),
37695
- region: exports_external.enum(AwsRegion).default("us-east-1" /* US_EAST_1 */),
37696
- publicIp: exports_external.string().nullable().optional(),
37697
- launchTime: exports_external.union([exports_external.date(), exports_external.number()]).nullable().optional(),
37698
- repoUrl: exports_external.string().nullable().optional(),
37699
- integrations: exports_external.array(IntegrationSchema).default([])
37700
- });
37701
- CreateAwsInstanceSchema = exports_external.object({
37702
- workspaceId: exports_external.uuid(),
37703
- instanceType: exports_external.enum(["t3.micro", "t3.small", "t3.medium"]).default("t3.micro"),
37704
- region: exports_external.enum(AwsRegion).default("us-east-1" /* US_EAST_1 */),
37705
- repoUrl: exports_external.string().optional(),
37706
- integrations: exports_external.array(IntegrationSchema).optional().default([])
37707
- });
37708
- UpdateAwsInstanceSchema = exports_external.object({
37709
- status: exports_external.enum(InstanceStatus).optional(),
37710
- instanceType: exports_external.enum(["t3.micro", "t3.small", "t3.medium"]).optional(),
37711
- publicIp: exports_external.string().nullable().optional(),
37712
- launchTime: exports_external.union([exports_external.date(), exports_external.number()]).nullable().optional(),
37713
- repoUrl: exports_external.string().nullable().optional(),
37714
- integrations: exports_external.array(IntegrationSchema).optional()
37715
- });
37716
- SaveAwsCredentialsSchema = exports_external.object({
37717
- accessKeyId: exports_external.string().min(16),
37718
- secretAccessKey: exports_external.string().min(1),
37719
- region: exports_external.string().default("us-east-1")
37720
- });
37721
- ProvisionAwsInstanceSchema = exports_external.object({
37722
- repoUrl: exports_external.string().min(1),
37723
- githubToken: exports_external.string().min(1),
37724
- instanceType: exports_external.enum(["t3.micro", "t3.small", "t3.medium"]).default("t3.small"),
37725
- integrations: exports_external.array(IntegrationSchema).optional().default([])
37726
- });
37727
- InstanceActionBodySchema = exports_external.object({
37728
- action: exports_external.nativeEnum(InstanceAction)
37729
- });
37730
- InstanceIdParamSchema = exports_external.object({
37731
- workspaceId: exports_external.string().uuid("Invalid Workspace ID"),
37732
- instanceId: exports_external.string().uuid("Invalid Instance ID")
37733
- });
37734
- CIDR_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/;
37735
- UpdateSecurityRulesSchema = exports_external.object({
37736
- allowedIps: exports_external.array(exports_external.string().regex(CIDR_REGEX, "Invalid CIDR format (e.g. 1.2.3.4/32)"))
37737
- });
37738
- });
37739
-
37740
37642
  // ../shared/src/models/ci.ts
37741
37643
  var RecordCiSchema;
37742
37644
  var init_ci2 = __esm(() => {
@@ -38124,7 +38026,6 @@ var init_models = __esm(() => {
38124
38026
  init_activity();
38125
38027
  init_agent();
38126
38028
  init_auth2();
38127
- init_aws_instance();
38128
38029
  init_ci2();
38129
38030
  init_doc();
38130
38031
  init_doc_group();
@@ -38793,17 +38694,6 @@ var init_workspaces = __esm(() => {
38793
38694
  async deleteApiKey(workspaceId, keyId) {
38794
38695
  await this.api.delete(`/workspaces/${workspaceId}/api-keys/${keyId}`);
38795
38696
  }
38796
- async getAwsCredentials(workspaceId) {
38797
- const { data } = await this.api.get(`/workspaces/${workspaceId}/aws-credentials`);
38798
- return data.credential;
38799
- }
38800
- async saveAwsCredentials(workspaceId, body) {
38801
- const { data } = await this.api.put(`/workspaces/${workspaceId}/aws-credentials`, body);
38802
- return data.credential;
38803
- }
38804
- async deleteAwsCredentials(workspaceId) {
38805
- await this.api.delete(`/workspaces/${workspaceId}/aws-credentials`);
38806
- }
38807
38697
  };
38808
38698
  });
38809
38699
 
@@ -38852,7 +38742,6 @@ class LocusClient {
38852
38742
  invitations;
38853
38743
  docs;
38854
38744
  ci;
38855
- instances;
38856
38745
  constructor(config2) {
38857
38746
  this.emitter = new LocusEmitter;
38858
38747
  this.api = axios_default.create({
@@ -38872,7 +38761,6 @@ class LocusClient {
38872
38761
  this.invitations = new InvitationsModule(this.api, this.emitter);
38873
38762
  this.docs = new DocsModule(this.api, this.emitter);
38874
38763
  this.ci = new CiModule(this.api, this.emitter);
38875
- this.instances = new InstancesModule(this.api, this.emitter);
38876
38764
  if (config2.retryOptions) {
38877
38765
  this.setupRetryInterceptor(config2.retryOptions);
38878
38766
  }
@@ -38938,7 +38826,6 @@ var init_src2 = __esm(() => {
38938
38826
  init_auth();
38939
38827
  init_ci();
38940
38828
  init_docs();
38941
- init_instances();
38942
38829
  init_invitations();
38943
38830
  init_organizations();
38944
38831
  init_sprints();
@@ -38949,7 +38836,6 @@ var init_src2 = __esm(() => {
38949
38836
  init_auth();
38950
38837
  init_ci();
38951
38838
  init_docs();
38952
- init_instances();
38953
38839
  init_invitations();
38954
38840
  init_organizations();
38955
38841
  init_sprints();
@@ -38975,9 +38861,11 @@ class ReviewerWorker {
38975
38861
  currentTaskId = null;
38976
38862
  maxReviews = 50;
38977
38863
  reviewsCompleted = 0;
38864
+ log;
38978
38865
  constructor(config2) {
38979
38866
  this.config = config2;
38980
38867
  const projectPath = config2.projectPath || process.cwd();
38868
+ this.log = createWorkerLogger(config2.agentId, "R");
38981
38869
  this.client = new LocusClient({
38982
38870
  baseUrl: config2.apiBase,
38983
38871
  token: config2.apiKey,
@@ -38988,28 +38876,16 @@ class ReviewerWorker {
38988
38876
  factor: 2
38989
38877
  }
38990
38878
  });
38991
- const log = this.log.bind(this);
38992
38879
  const provider = config2.provider ?? PROVIDER.CLAUDE;
38993
38880
  this.aiRunner = createAiRunner(provider, {
38994
38881
  projectPath,
38995
38882
  model: config2.model,
38996
- log
38883
+ log: this.log
38997
38884
  });
38998
- this.prService = new PrService(projectPath, log);
38885
+ this.prService = new PrService(projectPath, this.log);
38999
38886
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
39000
38887
  this.log(`Reviewer agent using ${providerLabel} CLI`, "info");
39001
38888
  }
39002
- log(message, level = "info") {
39003
- const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
39004
- const colorFn = {
39005
- info: c.cyan,
39006
- success: c.green,
39007
- warn: c.yellow,
39008
- error: c.red
39009
- }[level];
39010
- const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
39011
- console.log(`${c.dim(`[${timestamp}]`)} ${c.bold(`[R:${this.config.agentId.slice(-8)}]`)} ${colorFn(`${prefix} ${message}`)}`);
39012
- }
39013
38889
  getNextUnreviewedPr() {
39014
38890
  const prs = this.prService.listUnreviewedLocusPrs();
39015
38891
  return prs.length > 0 ? prs[0] : null;
@@ -39129,7 +39005,6 @@ var init_reviewer_worker = __esm(() => {
39129
39005
  init_git_utils();
39130
39006
  init_pr_service();
39131
39007
  init_src2();
39132
- init_colors();
39133
39008
  reviewerEntrypoint = process.argv[1]?.split(/[\\/]/).pop();
39134
39009
  if (reviewerEntrypoint === "reviewer-worker.js" || reviewerEntrypoint === "reviewer-worker.ts") {
39135
39010
  process.title = "locus-reviewer";
@@ -39758,9 +39633,11 @@ class AgentWorker {
39758
39633
  currentTaskId = null;
39759
39634
  completedTaskList = [];
39760
39635
  taskSummaries = [];
39636
+ log;
39761
39637
  constructor(config2) {
39762
39638
  this.config = config2;
39763
39639
  const projectPath = config2.projectPath || process.cwd();
39640
+ this.log = createWorkerLogger(config2.agentId);
39764
39641
  this.client = new LocusClient({
39765
39642
  baseUrl: config2.apiBase,
39766
39643
  token: config2.apiKey,
@@ -39771,7 +39648,6 @@ class AgentWorker {
39771
39648
  factor: 2
39772
39649
  }
39773
39650
  });
39774
- const log = this.log.bind(this);
39775
39651
  if (!isGitAvailable()) {
39776
39652
  this.log("git is not installed — branch management will not work", "error");
39777
39653
  }
@@ -39782,29 +39658,18 @@ class AgentWorker {
39782
39658
  this.aiRunner = createAiRunner(provider, {
39783
39659
  projectPath,
39784
39660
  model: config2.model,
39785
- log,
39661
+ log: this.log,
39786
39662
  reasoningEffort: config2.reasoningEffort
39787
39663
  });
39788
39664
  this.taskExecutor = new TaskExecutor({
39789
39665
  aiRunner: this.aiRunner,
39790
39666
  projectPath,
39791
- log
39667
+ log: this.log
39792
39668
  });
39793
- this.gitWorkflow = new GitWorkflow(config2, log);
39669
+ this.gitWorkflow = new GitWorkflow(config2, this.log);
39794
39670
  const providerLabel = provider === "codex" ? "Codex" : "Claude";
39795
39671
  this.log(`Using ${providerLabel} CLI for all phases`, "info");
39796
39672
  }
39797
- log(message, level = "info") {
39798
- const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
39799
- const colorFn = {
39800
- info: c.cyan,
39801
- success: c.green,
39802
- warn: c.yellow,
39803
- error: c.red
39804
- }[level];
39805
- const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
39806
- console.log(`${c.dim(`[${timestamp}]`)} ${c.bold(`[${this.config.agentId.slice(-8)}]`)} ${colorFn(`${prefix} ${message}`)}`);
39807
- }
39808
39673
  async getActiveSprint() {
39809
39674
  try {
39810
39675
  if (this.config.sprintId) {
@@ -39981,7 +39846,6 @@ var init_worker = __esm(() => {
39981
39846
  init_config();
39982
39847
  init_git_utils();
39983
39848
  init_src2();
39984
- init_colors();
39985
39849
  init_git_workflow();
39986
39850
  init_task_executor();
39987
39851
  workerEntrypoint = process.argv[1]?.split(/[\\/]/).pop();
@@ -40012,6 +39876,7 @@ var init_agent2 = __esm(() => {
40012
39876
  // ../sdk/src/ai/index.ts
40013
39877
  var init_ai = __esm(() => {
40014
39878
  init_claude_runner();
39879
+ init_claude_stream_parser();
40015
39880
  init_codex_runner();
40016
39881
  init_factory();
40017
39882
  });
@@ -40183,9 +40048,7 @@ class DiscussionFacilitator {
40183
40048
  this.projectPath = config2.projectPath;
40184
40049
  this.aiRunner = config2.aiRunner;
40185
40050
  this.discussionManager = config2.discussionManager;
40186
- this.log = config2.log ?? ((_msg) => {
40187
- return;
40188
- });
40051
+ this.log = config2.log ?? noopLogger;
40189
40052
  this.provider = config2.provider;
40190
40053
  this.model = config2.model;
40191
40054
  }
@@ -40356,6 +40219,7 @@ If you need more information about the project strategies, plans, or architectur
40356
40219
  }
40357
40220
  }
40358
40221
  var init_discussion_facilitator = __esm(() => {
40222
+ init_factory();
40359
40223
  init_config();
40360
40224
  });
40361
40225
 
@@ -41843,9 +41707,7 @@ class PlanningMeeting {
41843
41707
  constructor(config2) {
41844
41708
  this.projectPath = config2.projectPath;
41845
41709
  this.aiRunner = config2.aiRunner;
41846
- this.log = config2.log ?? ((_msg) => {
41847
- return;
41848
- });
41710
+ this.log = config2.log ?? noopLogger;
41849
41711
  }
41850
41712
  async run(directive, feedback) {
41851
41713
  this.log("Planning sprint...", "info");
@@ -41885,6 +41747,7 @@ class PlanningMeeting {
41885
41747
  }
41886
41748
  }
41887
41749
  var init_planning_meeting = __esm(() => {
41750
+ init_factory();
41888
41751
  init_config();
41889
41752
  });
41890
41753
 
@@ -41911,24 +41774,313 @@ var init_index_node = __esm(() => {
41911
41774
  init_planning();
41912
41775
  });
41913
41776
 
41777
+ // ../commands/src/config.ts
41778
+ import { existsSync as existsSync12 } from "node:fs";
41779
+ import { join as join12 } from "node:path";
41780
+ function isProjectInitialized(projectPath) {
41781
+ const locusDir = join12(projectPath, LOCUS_CONFIG.dir);
41782
+ const configPath = join12(locusDir, LOCUS_CONFIG.configFile);
41783
+ return existsSync12(locusDir) && existsSync12(configPath);
41784
+ }
41785
+ function resolveProvider3(input) {
41786
+ if (!input)
41787
+ return PROVIDER.CLAUDE;
41788
+ if (input === PROVIDER.CLAUDE || input === PROVIDER.CODEX)
41789
+ return input;
41790
+ throw new Error(`Invalid provider '${input}'. Must be 'claude' or 'codex'.`);
41791
+ }
41792
+ function maskSecret(value) {
41793
+ if (value.length <= 8)
41794
+ return "****";
41795
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
41796
+ }
41797
+ var init_config2 = __esm(() => {
41798
+ init_index_node();
41799
+ });
41800
+
41801
+ // ../commands/src/settings.ts
41802
+ import { existsSync as existsSync13, readFileSync as readFileSync11, unlinkSync as unlinkSync4, writeFileSync as writeFileSync6 } from "node:fs";
41803
+ import { join as join13 } from "node:path";
41804
+ function getSettingsPath(projectPath) {
41805
+ return join13(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
41806
+ }
41807
+
41808
+ class SettingsManager {
41809
+ projectPath;
41810
+ constructor(projectPath) {
41811
+ this.projectPath = projectPath;
41812
+ }
41813
+ load() {
41814
+ const settingsPath = getSettingsPath(this.projectPath);
41815
+ if (!existsSync13(settingsPath)) {
41816
+ return {};
41817
+ }
41818
+ return JSON.parse(readFileSync11(settingsPath, "utf-8"));
41819
+ }
41820
+ save(settings) {
41821
+ const { $schema: _2, ...rest } = settings;
41822
+ const ordered = { $schema: LOCUS_SCHEMAS.settings, ...rest };
41823
+ const settingsPath = getSettingsPath(this.projectPath);
41824
+ writeFileSync6(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
41825
+ }
41826
+ get(key) {
41827
+ return this.load()[key];
41828
+ }
41829
+ set(key, value) {
41830
+ const settings = this.load();
41831
+ settings[key] = value;
41832
+ this.save(settings);
41833
+ }
41834
+ remove() {
41835
+ const settingsPath = getSettingsPath(this.projectPath);
41836
+ if (existsSync13(settingsPath)) {
41837
+ unlinkSync4(settingsPath);
41838
+ }
41839
+ }
41840
+ exists() {
41841
+ return existsSync13(getSettingsPath(this.projectPath));
41842
+ }
41843
+ }
41844
+ var init_settings = __esm(() => {
41845
+ init_index_node();
41846
+ });
41847
+
41848
+ // ../commands/src/logger.ts
41849
+ function createCliLogger() {
41850
+ return (message, level) => {
41851
+ const icon = level === "success" ? c.success("✔") : level === "error" ? c.error("✖") : level === "warn" ? c.warning("!") : c.info("●");
41852
+ console.log(` ${icon} ${message}`);
41853
+ };
41854
+ }
41855
+ var init_logger = __esm(() => {
41856
+ init_index_node();
41857
+ init_index_node();
41858
+ });
41859
+
41860
+ // ../commands/src/workspace.ts
41861
+ class WorkspaceResolver {
41862
+ options;
41863
+ constructor(options) {
41864
+ this.options = options;
41865
+ }
41866
+ async resolve() {
41867
+ if (this.options.workspaceId) {
41868
+ return this.options.workspaceId;
41869
+ }
41870
+ const log = this.options.log ?? noopLogger;
41871
+ try {
41872
+ log("Resolving workspace from API key...");
41873
+ const client = new LocusClient({
41874
+ baseUrl: this.options.apiBase,
41875
+ token: this.options.apiKey
41876
+ });
41877
+ const info = await client.auth.getApiKeyInfo();
41878
+ if (info.workspaceId) {
41879
+ log(`Resolved workspace: ${info.workspaceId}`);
41880
+ return info.workspaceId;
41881
+ }
41882
+ throw new Error("API key is not associated with a workspace. Please specify a workspace ID.");
41883
+ } catch (error48) {
41884
+ if (error48 instanceof Error && error48.message.includes("API key is not")) {
41885
+ throw error48;
41886
+ }
41887
+ throw new Error(`Error resolving workspace: ${error48 instanceof Error ? error48.message : String(error48)}`);
41888
+ }
41889
+ }
41890
+ }
41891
+ var init_workspace2 = __esm(() => {
41892
+ init_src2();
41893
+ init_logger();
41894
+ });
41895
+
41896
+ // ../commands/src/api-context.ts
41897
+ async function resolveApiContext(options) {
41898
+ const settings = new SettingsManager(options.projectPath).load();
41899
+ const apiKey = options.apiKey || settings.apiKey;
41900
+ if (!apiKey) {
41901
+ throw new Error("API key is required. Configure with: locus config setup --api-key <key>");
41902
+ }
41903
+ const apiBase = options.apiUrl || settings.apiUrl || DEFAULT_API_BASE;
41904
+ const resolver = new WorkspaceResolver({
41905
+ apiKey,
41906
+ apiBase,
41907
+ workspaceId: options.workspaceId
41908
+ });
41909
+ const workspaceId = await resolver.resolve();
41910
+ const client = new LocusClient({
41911
+ baseUrl: apiBase,
41912
+ token: apiKey
41913
+ });
41914
+ return { client, workspaceId, apiKey, apiBase };
41915
+ }
41916
+ function resolveAiSettings(options) {
41917
+ const settings = new SettingsManager(options.projectPath).load();
41918
+ const provider = resolveProvider3(options.provider || settings.provider);
41919
+ const model = options.model || settings.model || DEFAULT_MODEL[provider];
41920
+ return { provider, model };
41921
+ }
41922
+ var DEFAULT_API_BASE = "https://api.locusai.dev/api";
41923
+ var init_api_context = __esm(() => {
41924
+ init_src2();
41925
+ init_index_node();
41926
+ init_config2();
41927
+ init_settings();
41928
+ init_workspace2();
41929
+ });
41930
+
41931
+ // ../commands/src/artifacts.ts
41932
+ import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync12, statSync } from "node:fs";
41933
+ import { join as join14 } from "node:path";
41934
+ function listArtifacts(projectPath) {
41935
+ const artifactsDir = getLocusPath(projectPath, "artifactsDir");
41936
+ if (!existsSync14(artifactsDir)) {
41937
+ return [];
41938
+ }
41939
+ const files = readdirSync5(artifactsDir).filter((f) => f.endsWith(".md"));
41940
+ return files.map((fileName) => {
41941
+ const filePath = join14(artifactsDir, fileName);
41942
+ const stat = statSync(filePath);
41943
+ const name = fileName.replace(/\.md$/, "");
41944
+ return {
41945
+ name,
41946
+ fileName,
41947
+ createdAt: stat.birthtime,
41948
+ size: stat.size
41949
+ };
41950
+ }).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
41951
+ }
41952
+ function readArtifact(projectPath, name) {
41953
+ const artifactsDir = getLocusPath(projectPath, "artifactsDir");
41954
+ const fileName = name.endsWith(".md") ? name : `${name}.md`;
41955
+ const filePath = join14(artifactsDir, fileName);
41956
+ if (!existsSync14(filePath)) {
41957
+ return null;
41958
+ }
41959
+ const stat = statSync(filePath);
41960
+ const content = readFileSync12(filePath, "utf-8");
41961
+ return {
41962
+ content,
41963
+ info: {
41964
+ name: fileName.replace(/\.md$/, ""),
41965
+ fileName,
41966
+ createdAt: stat.birthtime,
41967
+ size: stat.size
41968
+ }
41969
+ };
41970
+ }
41971
+ function findArtifact(projectPath, name) {
41972
+ const exact = readArtifact(projectPath, name);
41973
+ if (exact) {
41974
+ return { match: true, content: exact.content, info: exact.info };
41975
+ }
41976
+ const artifacts = listArtifacts(projectPath);
41977
+ const matches = artifacts.filter((a) => a.name.toLowerCase().includes(name.toLowerCase()));
41978
+ if (matches.length === 1) {
41979
+ const result = readArtifact(projectPath, matches[0].name);
41980
+ if (result) {
41981
+ return { match: true, content: result.content, info: result.info };
41982
+ }
41983
+ }
41984
+ if (matches.length > 1) {
41985
+ return { match: false, ambiguous: matches };
41986
+ }
41987
+ return null;
41988
+ }
41989
+ function formatSize(bytes) {
41990
+ if (bytes < 1024)
41991
+ return `${bytes}B`;
41992
+ const kb = bytes / 1024;
41993
+ if (kb < 1024)
41994
+ return `${kb.toFixed(1)}KB`;
41995
+ const mb = kb / 1024;
41996
+ return `${mb.toFixed(1)}MB`;
41997
+ }
41998
+ function formatDate(date5) {
41999
+ return date5.toLocaleDateString("en-US", {
42000
+ year: "numeric",
42001
+ month: "short",
42002
+ day: "numeric",
42003
+ hour: "2-digit",
42004
+ minute: "2-digit"
42005
+ });
42006
+ }
42007
+ var init_artifacts = __esm(() => {
42008
+ init_index_node();
42009
+ });
42010
+
42011
+ // ../commands/src/discussions.ts
42012
+ function listDiscussions(manager) {
42013
+ const discussions = manager.list();
42014
+ return discussions.map((d) => ({
42015
+ id: d.id,
42016
+ title: d.title,
42017
+ status: d.status,
42018
+ messageCount: d.messages.length,
42019
+ insightCount: d.insights.length,
42020
+ createdAt: d.createdAt
42021
+ }));
42022
+ }
42023
+ function showDiscussion(manager, id) {
42024
+ return manager.getMarkdown(id);
42025
+ }
42026
+ function archiveDiscussion(manager, id) {
42027
+ manager.archive(id);
42028
+ }
42029
+ function deleteDiscussion(manager, id) {
42030
+ manager.delete(id);
42031
+ }
42032
+
42033
+ // ../commands/src/plans.ts
42034
+ function listPlans(manager, status) {
42035
+ const plans = manager.list(status);
42036
+ return plans.map((p) => ({
42037
+ id: p.id,
42038
+ name: p.name,
42039
+ status: p.status,
42040
+ taskCount: p.tasks.length,
42041
+ directive: p.directive,
42042
+ createdAt: p.createdAt,
42043
+ feedback: p.feedback
42044
+ }));
42045
+ }
42046
+ function showPlan(manager, idOrSlug) {
42047
+ return manager.getMarkdown(idOrSlug);
42048
+ }
42049
+ function cancelPlan(manager, idOrSlug) {
42050
+ manager.cancel(idOrSlug);
42051
+ }
42052
+ function rejectPlan(manager, idOrSlug, feedback) {
42053
+ return manager.reject(idOrSlug, feedback);
42054
+ }
42055
+
42056
+ // ../commands/src/index.ts
42057
+ var init_src3 = __esm(() => {
42058
+ init_api_context();
42059
+ init_artifacts();
42060
+ init_config2();
42061
+ init_logger();
42062
+ init_settings();
42063
+ init_workspace2();
42064
+ });
42065
+
41914
42066
  // src/utils/version.ts
41915
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
41916
- import { dirname as dirname3, join as join12 } from "node:path";
42067
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
42068
+ import { dirname as dirname3, join as join15 } from "node:path";
41917
42069
  import { fileURLToPath as fileURLToPath3 } from "node:url";
41918
42070
  function getVersion() {
41919
42071
  try {
41920
42072
  const __filename2 = fileURLToPath3(import.meta.url);
41921
42073
  const __dirname2 = dirname3(__filename2);
41922
- const bundledPath = join12(__dirname2, "..", "package.json");
41923
- const sourcePath = join12(__dirname2, "..", "..", "package.json");
41924
- if (existsSync12(bundledPath)) {
41925
- const pkg = JSON.parse(readFileSync11(bundledPath, "utf-8"));
42074
+ const bundledPath = join15(__dirname2, "..", "package.json");
42075
+ const sourcePath = join15(__dirname2, "..", "..", "package.json");
42076
+ if (existsSync15(bundledPath)) {
42077
+ const pkg = JSON.parse(readFileSync13(bundledPath, "utf-8"));
41926
42078
  if (pkg.name === "@locusai/cli") {
41927
42079
  return pkg.version || "0.0.0";
41928
42080
  }
41929
42081
  }
41930
- if (existsSync12(sourcePath)) {
41931
- const pkg = JSON.parse(readFileSync11(sourcePath, "utf-8"));
42082
+ if (existsSync15(sourcePath)) {
42083
+ const pkg = JSON.parse(readFileSync13(sourcePath, "utf-8"));
41932
42084
  if (pkg.name === "@locusai/cli") {
41933
42085
  return pkg.version || "0.0.0";
41934
42086
  }
@@ -41957,13 +42109,6 @@ var init_banner = __esm(() => {
41957
42109
  });
41958
42110
 
41959
42111
  // src/utils/helpers.ts
41960
- import { existsSync as existsSync13 } from "node:fs";
41961
- import { join as join13 } from "node:path";
41962
- function isProjectInitialized(projectPath) {
41963
- const locusDir = join13(projectPath, LOCUS_CONFIG.dir);
41964
- const configPath = join13(locusDir, LOCUS_CONFIG.configFile);
41965
- return existsSync13(locusDir) && existsSync13(configPath);
41966
- }
41967
42112
  function requireInitialization(projectPath, command) {
41968
42113
  if (!isProjectInitialized(projectPath)) {
41969
42114
  console.error(`
@@ -41979,15 +42124,16 @@ function requireInitialization(projectPath, command) {
41979
42124
  process.exit(1);
41980
42125
  }
41981
42126
  }
41982
- function resolveProvider3(input) {
41983
- if (!input)
41984
- return PROVIDER.CLAUDE;
41985
- if (input === PROVIDER.CLAUDE || input === PROVIDER.CODEX)
41986
- return input;
41987
- console.error(c.error(`Error: invalid provider '${input}'. Use 'claude' or 'codex'.`));
41988
- process.exit(1);
42127
+ function resolveProvider4(input) {
42128
+ try {
42129
+ return resolveProvider3(input);
42130
+ } catch {
42131
+ console.error(c.error(`Error: invalid provider '${input}'. Use 'claude' or 'codex'.`));
42132
+ process.exit(1);
42133
+ }
41989
42134
  }
41990
42135
  var init_helpers2 = __esm(() => {
42136
+ init_src3();
41991
42137
  init_index_node();
41992
42138
  });
41993
42139
 
@@ -41998,17 +42144,107 @@ var init_utils3 = __esm(() => {
41998
42144
  init_version();
41999
42145
  });
42000
42146
 
42001
- // src/config-manager.ts
42147
+ // src/templates.ts
42148
+ var LOCUS_GITIGNORE_MARKER = "# Locus AI", LOCUS_MD_TEMPLATE = `## Planning First
42149
+
42150
+ Complex tasks must be planned before writing code. Create ".locus/plans/<task-name>.md" with:
42151
+ - **Goal**: What we're trying to achieve and why
42152
+ - **Approach**: Step-by-step strategy with technical decisions
42153
+ - **Affected files**: List of files to create/modify/delete
42154
+ - **Acceptance criteria**: Specific, testable conditions for completion
42155
+ - **Dependencies**: Required packages, APIs, or external services
42156
+
42157
+ Delete the planning .md files after successful execution.
42158
+
42159
+ ## Code Quality
42160
+
42161
+ - **Follow existing patterns**: Run formatters and linters before finishing (check "package.json" scripts or project config)
42162
+ - **Minimize changes**: Keep modifications atomic. Separate refactors from behavioral changes into different tasks
42163
+ - **Never commit secrets**: No API keys, passwords, or credentials in code. Use environment variables or secret management
42164
+ - **Test as you go**: If tests exist, run relevant ones. If breaking changes occur, update tests accordingly
42165
+ - **Comment complex logic**: Explain *why*, not *what*. Focus on business logic and non-obvious decisions
42166
+
42167
+ ## Artifacts
42168
+
42169
+ When a task produces knowledge, analysis, or research output rather than (or in addition to) code changes, you **must** save results as Markdown in ".locus/artifacts/<descriptive-name>.md":
42170
+
42171
+ **Always create artifacts for:**
42172
+ - Code quality audits, security reviews, vulnerability assessments
42173
+ - Architecture analyses, system design proposals, or recommendations
42174
+ - Dependency reports, performance profiling, benchmarking results
42175
+ - Research summaries, technology comparisons, or feasibility studies
42176
+ - Migration plans, deployment strategies, or rollback procedures
42177
+ - Post-mortems, incident analysis, or debugging investigations
42178
+
42179
+ **Artifact structure:**
42180
+ - Clear title and date
42181
+ - Executive summary (2-3 sentences)
42182
+ - Detailed findings/analysis
42183
+ - Actionable recommendations (if applicable)
42184
+
42185
+ ## Git Operations
42186
+
42187
+ - **Do NOT run**: git add, git commit, git push, git checkout, git branch, or any git commands
42188
+ - **Why**: The Locus orchestrator handles all version control automatically after execution
42189
+ - **Your role**: Focus solely on making file changes. The system commits, pushes, and creates PRs
42190
+
42191
+ ## Continuous Learning
42192
+
42193
+ Read ".locus/LEARNINGS.md" **before starting any task** to avoid repeating mistakes.
42194
+
42195
+ **When to update:**
42196
+ - User corrects your approach or provides guidance
42197
+ - You discover a better pattern while working
42198
+ - A decision prevents future confusion (e.g., "use X not Y because Z")
42199
+ - You encounter and solve a tricky problem
42200
+
42201
+ **What to record:**
42202
+ - Architectural decisions and their rationale
42203
+ - Preferred libraries, tools, or patterns for this project
42204
+ - Common pitfalls and how to avoid them
42205
+ - Project-specific conventions or user preferences
42206
+ - Solutions to non-obvious problems
42207
+
42208
+ **Format (append-only, never delete):**
42209
+
42210
+ """
42211
+ - **[Category]**: Concise description (1-2 lines max). *Context if needed.*
42212
+ """
42213
+
42214
+ **Categories:** Architecture, Dependencies, Patterns, Debugging, Performance, Security, DevOps, User Preferences
42215
+
42216
+ ## Error Handling
42217
+
42218
+ - **Read error messages carefully**: Don't guess. Parse the actual error before proposing fixes
42219
+ - **Check dependencies first**: Missing packages, wrong versions, and environment issues are common
42220
+ - **Verify assumptions**: If something "should work," confirm it actually does in this environment
42221
+ - **Ask for context**: If you need environment details, configuration, or logs, request them explicitly
42222
+
42223
+ ## Communication
42224
+
42225
+ - **Be precise**: When uncertain, state what you know and what you're assuming
42226
+ - **Show your work**: For complex changes, briefly explain the approach before executing
42227
+ - **Highlight trade-offs**: If multiple approaches exist, note why you chose one over others
42228
+ - **Request feedback**: For ambiguous requirements, propose an approach and ask for confirmation
42229
+ `, DEFAULT_LEARNINGS_MD = `# Learnings
42230
+
42231
+ This file captures important lessons, decisions, and corrections made during development.
42232
+ It is read by AI agents before every task to avoid repeating mistakes and to follow established patterns.
42233
+
42234
+ <!-- Add learnings below this line. Format: - **[Category]**: Description -->
42235
+ `;
42236
+
42237
+ // src/git-helpers.ts
42002
42238
  import { execSync as execSync2 } from "node:child_process";
42003
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
42004
- import { join as join14 } from "node:path";
42239
+ import { existsSync as existsSync16, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "node:fs";
42240
+ import { join as join16 } from "node:path";
42005
42241
  function updateGitignore(projectPath) {
42006
- const gitignorePath = join14(projectPath, ".gitignore");
42242
+ const gitignorePath = join16(projectPath, ".gitignore");
42007
42243
  let content = "";
42008
42244
  const locusBlock = LOCUS_GITIGNORE_PATTERNS.join(`
42009
42245
  `);
42010
- if (existsSync14(gitignorePath)) {
42011
- content = readFileSync12(gitignorePath, "utf-8");
42246
+ if (existsSync16(gitignorePath)) {
42247
+ content = readFileSync14(gitignorePath, "utf-8");
42012
42248
  if (content.includes(LOCUS_GITIGNORE_MARKER)) {
42013
42249
  const lines = content.split(`
42014
42250
  `);
@@ -42025,7 +42261,7 @@ function updateGitignore(projectPath) {
42025
42261
  const after = lines.slice(endIdx + 1);
42026
42262
  content = [...before, locusBlock, ...after].join(`
42027
42263
  `);
42028
- writeFileSync6(gitignorePath, content);
42264
+ writeFileSync7(gitignorePath, content);
42029
42265
  return;
42030
42266
  }
42031
42267
  if (content.length > 0 && !content.endsWith(`
@@ -42040,7 +42276,7 @@ function updateGitignore(projectPath) {
42040
42276
  }
42041
42277
  content += `${locusBlock}
42042
42278
  `;
42043
- writeFileSync6(gitignorePath, content);
42279
+ writeFileSync7(gitignorePath, content);
42044
42280
  }
42045
42281
  function ensureGitIdentity(projectPath) {
42046
42282
  const hasName = (() => {
@@ -42080,6 +42316,13 @@ function ensureGitIdentity(projectPath) {
42080
42316
  stdio: "ignore"
42081
42317
  });
42082
42318
  }
42319
+ var init_git_helpers = __esm(() => {
42320
+ init_index_node();
42321
+ });
42322
+
42323
+ // src/config-manager.ts
42324
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
42325
+ import { join as join17 } from "node:path";
42083
42326
 
42084
42327
  class ConfigManager {
42085
42328
  projectPath;
@@ -42087,9 +42330,9 @@ class ConfigManager {
42087
42330
  this.projectPath = projectPath;
42088
42331
  }
42089
42332
  async init(version2) {
42090
- const locusConfigDir = join14(this.projectPath, LOCUS_CONFIG.dir);
42333
+ const locusConfigDir = join17(this.projectPath, LOCUS_CONFIG.dir);
42091
42334
  const locusConfigPath = getLocusPath(this.projectPath, "configFile");
42092
- if (!existsSync14(locusConfigDir)) {
42335
+ if (!existsSync17(locusConfigDir)) {
42093
42336
  mkdirSync7(locusConfigDir, { recursive: true });
42094
42337
  }
42095
42338
  const locusSubdirs = [
@@ -42101,35 +42344,35 @@ class ConfigManager {
42101
42344
  LOCUS_CONFIG.discussionsDir
42102
42345
  ];
42103
42346
  for (const subdir of locusSubdirs) {
42104
- const subdirPath = join14(locusConfigDir, subdir);
42105
- if (!existsSync14(subdirPath)) {
42347
+ const subdirPath = join17(locusConfigDir, subdir);
42348
+ if (!existsSync17(subdirPath)) {
42106
42349
  mkdirSync7(subdirPath, { recursive: true });
42107
42350
  }
42108
42351
  }
42109
42352
  const locusMdPath = getLocusPath(this.projectPath, "contextFile");
42110
- if (!existsSync14(locusMdPath)) {
42111
- writeFileSync6(locusMdPath, LOCUS_MD_TEMPLATE);
42353
+ if (!existsSync17(locusMdPath)) {
42354
+ writeFileSync8(locusMdPath, LOCUS_MD_TEMPLATE);
42112
42355
  }
42113
42356
  const learningsMdPath = getLocusPath(this.projectPath, "learningsFile");
42114
- if (!existsSync14(learningsMdPath)) {
42115
- writeFileSync6(learningsMdPath, DEFAULT_LEARNINGS_MD);
42357
+ if (!existsSync17(learningsMdPath)) {
42358
+ writeFileSync8(learningsMdPath, DEFAULT_LEARNINGS_MD);
42116
42359
  }
42117
- if (!existsSync14(locusConfigPath)) {
42360
+ if (!existsSync17(locusConfigPath)) {
42118
42361
  const config2 = {
42119
42362
  $schema: LOCUS_SCHEMAS.config,
42120
42363
  version: version2,
42121
42364
  createdAt: new Date().toISOString(),
42122
42365
  projectPath: "."
42123
42366
  };
42124
- writeFileSync6(locusConfigPath, JSON.stringify(config2, null, 2));
42367
+ writeFileSync8(locusConfigPath, JSON.stringify(config2, null, 2));
42125
42368
  }
42126
42369
  updateGitignore(this.projectPath);
42127
42370
  ensureGitIdentity(this.projectPath);
42128
42371
  }
42129
42372
  loadConfig() {
42130
42373
  const path2 = getLocusPath(this.projectPath, "configFile");
42131
- if (existsSync14(path2)) {
42132
- return JSON.parse(readFileSync12(path2, "utf-8"));
42374
+ if (existsSync17(path2)) {
42375
+ return JSON.parse(readFileSync15(path2, "utf-8"));
42133
42376
  }
42134
42377
  return null;
42135
42378
  }
@@ -42159,19 +42402,19 @@ class ConfigManager {
42159
42402
  this.saveConfig(config2);
42160
42403
  }
42161
42404
  }
42162
- const settingsPath = join14(this.projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
42163
- if (existsSync14(settingsPath)) {
42164
- const raw = readFileSync12(settingsPath, "utf-8");
42405
+ const settingsPath = join17(this.projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
42406
+ if (existsSync17(settingsPath)) {
42407
+ const raw = readFileSync15(settingsPath, "utf-8");
42165
42408
  const settings = JSON.parse(raw);
42166
42409
  if (settings.$schema !== LOCUS_SCHEMAS.settings) {
42167
42410
  const { $schema: _2, ...rest } = settings;
42168
42411
  const ordered = { $schema: LOCUS_SCHEMAS.settings, ...rest };
42169
- writeFileSync6(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
42412
+ writeFileSync8(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
42170
42413
  }
42171
42414
  }
42172
42415
  const locusMdPath = getLocusPath(this.projectPath, "contextFile");
42173
- const locusMdExisted = existsSync14(locusMdPath);
42174
- writeFileSync6(locusMdPath, LOCUS_MD_TEMPLATE);
42416
+ const locusMdExisted = existsSync17(locusMdPath);
42417
+ writeFileSync8(locusMdPath, LOCUS_MD_TEMPLATE);
42175
42418
  if (!locusMdExisted) {
42176
42419
  result.directoriesCreated.push(".locus/LOCUS.md");
42177
42420
  }
@@ -42183,23 +42426,23 @@ class ConfigManager {
42183
42426
  LOCUS_CONFIG.plansDir,
42184
42427
  LOCUS_CONFIG.discussionsDir
42185
42428
  ];
42186
- const locusConfigDir = join14(this.projectPath, LOCUS_CONFIG.dir);
42429
+ const locusConfigDir = join17(this.projectPath, LOCUS_CONFIG.dir);
42187
42430
  for (const subdir of locusSubdirs) {
42188
- const subdirPath = join14(locusConfigDir, subdir);
42189
- if (!existsSync14(subdirPath)) {
42431
+ const subdirPath = join17(locusConfigDir, subdir);
42432
+ if (!existsSync17(subdirPath)) {
42190
42433
  mkdirSync7(subdirPath, { recursive: true });
42191
42434
  result.directoriesCreated.push(`.locus/${subdir}`);
42192
42435
  }
42193
42436
  }
42194
42437
  const learningsMdPath = getLocusPath(this.projectPath, "learningsFile");
42195
- if (!existsSync14(learningsMdPath)) {
42196
- writeFileSync6(learningsMdPath, DEFAULT_LEARNINGS_MD);
42438
+ if (!existsSync17(learningsMdPath)) {
42439
+ writeFileSync8(learningsMdPath, DEFAULT_LEARNINGS_MD);
42197
42440
  result.directoriesCreated.push(".locus/LEARNINGS.md");
42198
42441
  }
42199
- const gitignorePath = join14(this.projectPath, ".gitignore");
42200
- const gitignoreBefore = existsSync14(gitignorePath) ? readFileSync12(gitignorePath, "utf-8") : "";
42442
+ const gitignorePath = join17(this.projectPath, ".gitignore");
42443
+ const gitignoreBefore = existsSync17(gitignorePath) ? readFileSync15(gitignorePath, "utf-8") : "";
42201
42444
  updateGitignore(this.projectPath);
42202
- const gitignoreAfter = readFileSync12(gitignorePath, "utf-8");
42445
+ const gitignoreAfter = readFileSync15(gitignorePath, "utf-8");
42203
42446
  if (gitignoreBefore !== gitignoreAfter) {
42204
42447
  result.gitignoreUpdated = true;
42205
42448
  }
@@ -42210,177 +42453,12 @@ class ConfigManager {
42210
42453
  const { $schema: _2, ...rest } = config2;
42211
42454
  const ordered = { $schema: LOCUS_SCHEMAS.config, ...rest };
42212
42455
  const path2 = getLocusPath(this.projectPath, "configFile");
42213
- writeFileSync6(path2, JSON.stringify(ordered, null, 2));
42456
+ writeFileSync8(path2, JSON.stringify(ordered, null, 2));
42214
42457
  }
42215
42458
  }
42216
- var LOCUS_GITIGNORE_MARKER = "# Locus AI", LOCUS_MD_TEMPLATE = `## Planning First
42217
-
42218
- Complex tasks must be planned before writing code. Create ".locus/plans/<task-name>.md" with:
42219
- - **Goal**: What we're trying to achieve and why
42220
- - **Approach**: Step-by-step strategy with technical decisions
42221
- - **Affected files**: List of files to create/modify/delete
42222
- - **Acceptance criteria**: Specific, testable conditions for completion
42223
- - **Dependencies**: Required packages, APIs, or external services
42224
-
42225
- Delete the planning .md files after successful execution.
42226
-
42227
- ## Code Quality
42228
-
42229
- - **Follow existing patterns**: Run formatters and linters before finishing (check "package.json" scripts or project config)
42230
- - **Minimize changes**: Keep modifications atomic. Separate refactors from behavioral changes into different tasks
42231
- - **Never commit secrets**: No API keys, passwords, or credentials in code. Use environment variables or secret management
42232
- - **Test as you go**: If tests exist, run relevant ones. If breaking changes occur, update tests accordingly
42233
- - **Comment complex logic**: Explain *why*, not *what*. Focus on business logic and non-obvious decisions
42234
-
42235
- ## Artifacts
42236
-
42237
- When a task produces knowledge, analysis, or research output rather than (or in addition to) code changes, you **must** save results as Markdown in ".locus/artifacts/<descriptive-name>.md":
42238
-
42239
- **Always create artifacts for:**
42240
- - Code quality audits, security reviews, vulnerability assessments
42241
- - Architecture analyses, system design proposals, or recommendations
42242
- - Dependency reports, performance profiling, benchmarking results
42243
- - Research summaries, technology comparisons, or feasibility studies
42244
- - Migration plans, deployment strategies, or rollback procedures
42245
- - Post-mortems, incident analysis, or debugging investigations
42246
-
42247
- **Artifact structure:**
42248
- - Clear title and date
42249
- - Executive summary (2-3 sentences)
42250
- - Detailed findings/analysis
42251
- - Actionable recommendations (if applicable)
42252
-
42253
- ## Git Operations
42254
-
42255
- - **Do NOT run**: git add, git commit, git push, git checkout, git branch, or any git commands
42256
- - **Why**: The Locus orchestrator handles all version control automatically after execution
42257
- - **Your role**: Focus solely on making file changes. The system commits, pushes, and creates PRs
42258
-
42259
- ## Continuous Learning
42260
-
42261
- Read ".locus/LEARNINGS.md" **before starting any task** to avoid repeating mistakes.
42262
-
42263
- **When to update:**
42264
- - User corrects your approach or provides guidance
42265
- - You discover a better pattern while working
42266
- - A decision prevents future confusion (e.g., "use X not Y because Z")
42267
- - You encounter and solve a tricky problem
42268
-
42269
- **What to record:**
42270
- - Architectural decisions and their rationale
42271
- - Preferred libraries, tools, or patterns for this project
42272
- - Common pitfalls and how to avoid them
42273
- - Project-specific conventions or user preferences
42274
- - Solutions to non-obvious problems
42275
-
42276
- **Format (append-only, never delete):**
42277
-
42278
- """
42279
- - **[Category]**: Concise description (1-2 lines max). *Context if needed.*
42280
- """
42281
-
42282
- **Categories:** Architecture, Dependencies, Patterns, Debugging, Performance, Security, DevOps, User Preferences
42283
-
42284
- ## Error Handling
42285
-
42286
- - **Read error messages carefully**: Don't guess. Parse the actual error before proposing fixes
42287
- - **Check dependencies first**: Missing packages, wrong versions, and environment issues are common
42288
- - **Verify assumptions**: If something "should work," confirm it actually does in this environment
42289
- - **Ask for context**: If you need environment details, configuration, or logs, request them explicitly
42290
-
42291
- ## Communication
42292
-
42293
- - **Be precise**: When uncertain, state what you know and what you're assuming
42294
- - **Show your work**: For complex changes, briefly explain the approach before executing
42295
- - **Highlight trade-offs**: If multiple approaches exist, note why you chose one over others
42296
- - **Request feedback**: For ambiguous requirements, propose an approach and ask for confirmation
42297
- `, DEFAULT_LEARNINGS_MD = `# Learnings
42298
-
42299
- This file captures important lessons, decisions, and corrections made during development.
42300
- It is read by AI agents before every task to avoid repeating mistakes and to follow established patterns.
42301
-
42302
- <!-- Add learnings below this line. Format: - **[Category]**: Description -->
42303
- `;
42304
42459
  var init_config_manager = __esm(() => {
42305
42460
  init_index_node();
42306
- });
42307
-
42308
- // src/settings-manager.ts
42309
- import { existsSync as existsSync15, readFileSync as readFileSync13, unlinkSync as unlinkSync4, writeFileSync as writeFileSync7 } from "node:fs";
42310
- import { join as join15 } from "node:path";
42311
- function getSettingsPath(projectPath) {
42312
- return join15(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
42313
- }
42314
-
42315
- class SettingsManager {
42316
- projectPath;
42317
- constructor(projectPath) {
42318
- this.projectPath = projectPath;
42319
- }
42320
- load() {
42321
- const settingsPath = getSettingsPath(this.projectPath);
42322
- if (!existsSync15(settingsPath)) {
42323
- return {};
42324
- }
42325
- return JSON.parse(readFileSync13(settingsPath, "utf-8"));
42326
- }
42327
- save(settings) {
42328
- const { $schema: _2, ...rest } = settings;
42329
- const ordered = { $schema: LOCUS_SCHEMAS.settings, ...rest };
42330
- const settingsPath = getSettingsPath(this.projectPath);
42331
- writeFileSync7(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
42332
- }
42333
- get(key) {
42334
- return this.load()[key];
42335
- }
42336
- set(key, value) {
42337
- const settings = this.load();
42338
- settings[key] = value;
42339
- this.save(settings);
42340
- }
42341
- remove() {
42342
- const settingsPath = getSettingsPath(this.projectPath);
42343
- if (existsSync15(settingsPath)) {
42344
- unlinkSync4(settingsPath);
42345
- }
42346
- }
42347
- exists() {
42348
- return existsSync15(getSettingsPath(this.projectPath));
42349
- }
42350
- }
42351
- var init_settings_manager = __esm(() => {
42352
- init_index_node();
42353
- });
42354
-
42355
- // src/workspace-resolver.ts
42356
- class WorkspaceResolver {
42357
- options;
42358
- constructor(options) {
42359
- this.options = options;
42360
- }
42361
- async resolve() {
42362
- if (this.options.workspaceId) {
42363
- return this.options.workspaceId;
42364
- }
42365
- try {
42366
- console.log(c.dim("ℹ Resolving workspace from API key..."));
42367
- const client = new LocusClient({
42368
- baseUrl: this.options.apiBase,
42369
- token: this.options.apiKey
42370
- });
42371
- const info = await client.auth.getApiKeyInfo();
42372
- if (info.workspaceId) {
42373
- console.log(c.success(`✓ Resolved workspace: ${info.workspaceId}`));
42374
- return info.workspaceId;
42375
- }
42376
- throw new Error("API key is not associated with a workspace. Please specify --workspace.");
42377
- } catch (error48) {
42378
- throw new Error(`Error resolving workspace: ${error48 instanceof Error ? error48.message : String(error48)}`);
42379
- }
42380
- }
42381
- }
42382
- var init_workspace_resolver = __esm(() => {
42383
- init_index_node();
42461
+ init_git_helpers();
42384
42462
  });
42385
42463
 
42386
42464
  // src/commands/plan.ts
@@ -42388,8 +42466,8 @@ var exports_plan = {};
42388
42466
  __export(exports_plan, {
42389
42467
  planCommand: () => planCommand
42390
42468
  });
42391
- import { existsSync as existsSync16, unlinkSync as unlinkSync5 } from "node:fs";
42392
- import { join as join16 } from "node:path";
42469
+ import { existsSync as existsSync18, unlinkSync as unlinkSync5 } from "node:fs";
42470
+ import { join as join18 } from "node:path";
42393
42471
  import { parseArgs } from "node:util";
42394
42472
  function normalizePlanIdArgs(args) {
42395
42473
  const planIdFlags = new Set(["--approve", "--reject", "--cancel", "--show"]);
@@ -42433,13 +42511,34 @@ async function planCommand(args) {
42433
42511
  requireInitialization(projectPath, "plan");
42434
42512
  const planManager = new PlanManager(projectPath);
42435
42513
  if (values.list) {
42436
- return listPlans(planManager);
42514
+ return renderPlanList(planManager);
42437
42515
  }
42438
42516
  if (values.show) {
42439
- return showPlan(planManager, values.show);
42517
+ const md = showPlan(planManager, values.show);
42518
+ if (!md) {
42519
+ console.error(`
42520
+ ${c.error("✖")} ${c.red(`Plan not found: ${values.show}`)}
42521
+ `);
42522
+ process.exit(1);
42523
+ }
42524
+ console.log(`
42525
+ ${md}
42526
+ `);
42527
+ return;
42440
42528
  }
42441
42529
  if (values.cancel) {
42442
- return cancelPlan(planManager, values.cancel);
42530
+ try {
42531
+ cancelPlan(planManager, values.cancel);
42532
+ console.log(`
42533
+ ${c.success("✔")} ${c.dim("Plan cancelled.")}
42534
+ `);
42535
+ } catch (error48) {
42536
+ console.error(`
42537
+ ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
42538
+ `);
42539
+ process.exit(1);
42540
+ }
42541
+ return;
42443
42542
  }
42444
42543
  if (values.reject) {
42445
42544
  const feedback2 = values.feedback;
@@ -42451,30 +42550,59 @@ async function planCommand(args) {
42451
42550
  `);
42452
42551
  process.exit(1);
42453
42552
  }
42454
- return rejectPlan(planManager, values.reject, feedback2);
42553
+ try {
42554
+ const plan = rejectPlan(planManager, values.reject, feedback2);
42555
+ console.log(`
42556
+ ${c.warning("!")} ${c.bold("Plan rejected:")} ${plan.name}
42557
+ `);
42558
+ console.log(` ${c.dim("Feedback saved:")} ${feedback2}
42559
+ `);
42560
+ console.log(` ${c.dim("Re-run the planning meeting with the same directive to incorporate feedback:")}`);
42561
+ console.log(` ${c.cyan(`locus plan "${plan.directive}"`)}
42562
+ `);
42563
+ } catch (error48) {
42564
+ console.error(`
42565
+ ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
42566
+ `);
42567
+ process.exit(1);
42568
+ }
42569
+ return;
42455
42570
  }
42456
42571
  if (values.approve) {
42457
- const { client, workspaceId } = await resolveApiContext(projectPath, values);
42458
- return approvePlan(planManager, values.approve, client, workspaceId);
42572
+ const configManager = new ConfigManager(projectPath);
42573
+ configManager.updateVersion(VERSION2);
42574
+ try {
42575
+ const { client, workspaceId } = await resolveApiContext({
42576
+ projectPath,
42577
+ apiKey: values["api-key"],
42578
+ apiUrl: values["api-url"],
42579
+ workspaceId: values.workspace
42580
+ });
42581
+ return await renderApprovePlan(planManager, values.approve, client, workspaceId);
42582
+ } catch (error48) {
42583
+ console.error(`
42584
+ ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
42585
+ `);
42586
+ process.exit(1);
42587
+ }
42459
42588
  }
42460
42589
  const directive = positionals.join(" ").trim();
42461
42590
  if (!directive) {
42462
42591
  showPlanHelp();
42463
42592
  return;
42464
42593
  }
42465
- const planSettings = new SettingsManager(projectPath).load();
42466
- const provider = resolveProvider3(values.provider || planSettings.provider);
42467
- const model = values.model || planSettings.model || DEFAULT_MODEL[provider];
42594
+ const { provider, model } = resolveAiSettings({
42595
+ projectPath,
42596
+ provider: values.provider,
42597
+ model: values.model
42598
+ });
42468
42599
  const reasoningEffort = values["reasoning-effort"];
42469
42600
  const aiRunner = createAiRunner(provider, {
42470
42601
  projectPath,
42471
42602
  model,
42472
42603
  reasoningEffort
42473
42604
  });
42474
- const log = (message, level) => {
42475
- const icon = level === "success" ? c.success("✔") : level === "error" ? c.error("✖") : level === "warn" ? c.warning("!") : c.info("●");
42476
- console.log(` ${icon} ${message}`);
42477
- };
42605
+ const log = createCliLogger();
42478
42606
  console.log(`
42479
42607
  ${c.header(" PLANNING MEETING ")} ${c.bold("Starting async planning meeting...")}
42480
42608
  `);
@@ -42496,8 +42624,8 @@ async function planCommand(args) {
42496
42624
  try {
42497
42625
  const result = await meeting.run(directive, feedback);
42498
42626
  planManager.save(result.plan);
42499
- const tempFile = join16(getLocusPath(projectPath, "plansDir"), `${result.plan.id}.json`);
42500
- if (existsSync16(tempFile)) {
42627
+ const tempFile = join18(getLocusPath(projectPath, "plansDir"), `${result.plan.id}.json`);
42628
+ if (existsSync18(tempFile)) {
42501
42629
  unlinkSync5(tempFile);
42502
42630
  }
42503
42631
  console.log(`
@@ -42518,8 +42646,8 @@ async function planCommand(args) {
42518
42646
  process.exit(1);
42519
42647
  }
42520
42648
  }
42521
- function listPlans(planManager) {
42522
- const plans = planManager.list();
42649
+ function renderPlanList(planManager) {
42650
+ const plans = listPlans(planManager);
42523
42651
  if (plans.length === 0) {
42524
42652
  console.log(`
42525
42653
  ${c.dim("No plans found.")}
@@ -42533,7 +42661,7 @@ function listPlans(planManager) {
42533
42661
  `);
42534
42662
  for (const plan of plans) {
42535
42663
  const statusIcon = plan.status === "pending" ? c.warning("◯") : plan.status === "approved" ? c.success("✔") : plan.status === "rejected" ? c.error("✖") : c.dim("⊘");
42536
- console.log(` ${statusIcon} ${c.bold(plan.name)} ${c.dim(`[${plan.status}]`)} ${c.dim(`— ${plan.tasks.length} tasks`)}`);
42664
+ console.log(` ${statusIcon} ${c.bold(plan.name)} ${c.dim(`[${plan.status}]`)} ${c.dim(`— ${plan.taskCount} tasks`)}`);
42537
42665
  console.log(` ${c.dim("ID:")} ${plan.id}`);
42538
42666
  console.log(` ${c.dim("Created:")} ${plan.createdAt}`);
42539
42667
  if (plan.feedback) {
@@ -42542,50 +42670,7 @@ function listPlans(planManager) {
42542
42670
  console.log("");
42543
42671
  }
42544
42672
  }
42545
- function showPlan(planManager, idOrSlug) {
42546
- const md = planManager.getMarkdown(idOrSlug);
42547
- if (!md) {
42548
- console.error(`
42549
- ${c.error("✖")} ${c.red(`Plan not found: ${idOrSlug}`)}
42550
- `);
42551
- process.exit(1);
42552
- }
42553
- console.log(`
42554
- ${md}
42555
- `);
42556
- }
42557
- function cancelPlan(planManager, idOrSlug) {
42558
- try {
42559
- planManager.cancel(idOrSlug);
42560
- console.log(`
42561
- ${c.success("✔")} ${c.dim("Plan cancelled.")}
42562
- `);
42563
- } catch (error48) {
42564
- console.error(`
42565
- ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
42566
- `);
42567
- process.exit(1);
42568
- }
42569
- }
42570
- function rejectPlan(planManager, idOrSlug, feedback) {
42571
- try {
42572
- const plan = planManager.reject(idOrSlug, feedback);
42573
- console.log(`
42574
- ${c.warning("!")} ${c.bold("Plan rejected:")} ${plan.name}
42575
- `);
42576
- console.log(` ${c.dim("Feedback saved:")} ${feedback}
42577
- `);
42578
- console.log(` ${c.dim("Re-run the planning meeting with the same directive to incorporate feedback:")}`);
42579
- console.log(` ${c.cyan(`locus plan "${plan.directive}"`)}
42580
- `);
42581
- } catch (error48) {
42582
- console.error(`
42583
- ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
42584
- `);
42585
- process.exit(1);
42586
- }
42587
- }
42588
- async function approvePlan(planManager, idOrSlug, client, workspaceId) {
42673
+ async function renderApprovePlan(planManager, idOrSlug, client, workspaceId) {
42589
42674
  try {
42590
42675
  console.log(`
42591
42676
  ${c.info("●")} ${c.bold("Approving plan and creating sprint...")}
@@ -42608,34 +42693,6 @@ async function approvePlan(planManager, idOrSlug, client, workspaceId) {
42608
42693
  process.exit(1);
42609
42694
  }
42610
42695
  }
42611
- async function resolveApiContext(projectPath, values) {
42612
- const configManager = new ConfigManager(projectPath);
42613
- configManager.updateVersion(VERSION2);
42614
- const settingsManager = new SettingsManager(projectPath);
42615
- const settings = settingsManager.load();
42616
- const apiKey = values["api-key"] || settings.apiKey;
42617
- if (!apiKey) {
42618
- console.error(`
42619
- ${c.error("✖")} ${c.red("API key is required for this operation")}
42620
- `);
42621
- console.error(` ${c.dim(`Configure with: locus config setup --api-key <key>
42622
- Or pass --api-key flag`)}
42623
- `);
42624
- process.exit(1);
42625
- }
42626
- const apiBase = values["api-url"] || settings.apiUrl || "https://api.locusai.dev/api";
42627
- const resolver = new WorkspaceResolver({
42628
- apiKey,
42629
- apiBase,
42630
- workspaceId: values.workspace
42631
- });
42632
- const workspaceId = await resolver.resolve();
42633
- const client = new LocusClient({
42634
- baseUrl: apiBase,
42635
- token: apiKey
42636
- });
42637
- return { client, workspaceId };
42638
- }
42639
42696
  function printPlanSummary(plan) {
42640
42697
  console.log(` ${c.bold("Sprint:")} ${plan.name}`);
42641
42698
  console.log(` ${c.bold("Goal:")} ${plan.goal}`);
@@ -42693,11 +42750,10 @@ function showPlanHelp() {
42693
42750
  `);
42694
42751
  }
42695
42752
  var init_plan = __esm(() => {
42753
+ init_src3();
42696
42754
  init_index_node();
42697
42755
  init_config_manager();
42698
- init_settings_manager();
42699
42756
  init_utils3();
42700
- init_workspace_resolver();
42701
42757
  });
42702
42758
 
42703
42759
  // src/display/tool-display.ts
@@ -43291,9 +43347,9 @@ var init_progress_renderer = __esm(() => {
43291
43347
  });
43292
43348
 
43293
43349
  // src/repl/image-detect.ts
43294
- import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync8 } from "node:fs";
43350
+ import { copyFileSync, existsSync as existsSync19, mkdirSync as mkdirSync8 } from "node:fs";
43295
43351
  import { homedir as homedir2, tmpdir as tmpdir2 } from "node:os";
43296
- import { basename as basename2, join as join18 } from "node:path";
43352
+ import { basename as basename2, join as join19 } from "node:path";
43297
43353
  function hasImageExtension(p) {
43298
43354
  const dot = p.lastIndexOf(".");
43299
43355
  if (dot === -1)
@@ -43315,7 +43371,7 @@ function copyToStable(srcPath) {
43315
43371
  mkdirSync8(STABLE_IMAGE_DIR, { recursive: true });
43316
43372
  const ts = Date.now();
43317
43373
  const name = `${ts}-${basename2(srcPath)}`;
43318
- const destPath = join18(STABLE_IMAGE_DIR, name);
43374
+ const destPath = join19(STABLE_IMAGE_DIR, name);
43319
43375
  copyFileSync(srcPath, destPath);
43320
43376
  return destPath;
43321
43377
  } catch {
@@ -43335,7 +43391,7 @@ function detectImages(input) {
43335
43391
  let exists = false;
43336
43392
  let stablePath = normalized;
43337
43393
  try {
43338
- exists = existsSync18(normalized);
43394
+ exists = existsSync19(normalized);
43339
43395
  } catch {}
43340
43396
  if (exists) {
43341
43397
  const copied = copyToStable(normalized);
@@ -43409,7 +43465,7 @@ var init_image_detect = __esm(() => {
43409
43465
  ".tif",
43410
43466
  ".tiff"
43411
43467
  ]);
43412
- STABLE_IMAGE_DIR = join18(tmpdir2(), "locus-images");
43468
+ STABLE_IMAGE_DIR = join19(tmpdir2(), "locus-images");
43413
43469
  });
43414
43470
 
43415
43471
  // src/repl/input-handler.ts
@@ -44166,66 +44222,10 @@ var init_interactive_session = __esm(() => {
44166
44222
  init_index_node();
44167
44223
 
44168
44224
  // src/commands/artifacts.ts
44225
+ init_src3();
44169
44226
  init_index_node();
44170
44227
  init_utils3();
44171
- import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync14, statSync } from "node:fs";
44172
- import { join as join17 } from "node:path";
44173
44228
  import { parseArgs as parseArgs2 } from "node:util";
44174
- function listArtifacts(projectPath) {
44175
- const artifactsDir = getLocusPath(projectPath, "artifactsDir");
44176
- if (!existsSync17(artifactsDir)) {
44177
- return [];
44178
- }
44179
- const files = readdirSync5(artifactsDir).filter((f) => f.endsWith(".md"));
44180
- return files.map((fileName) => {
44181
- const filePath = join17(artifactsDir, fileName);
44182
- const stat = statSync(filePath);
44183
- const name = fileName.replace(/\.md$/, "");
44184
- return {
44185
- name,
44186
- fileName,
44187
- createdAt: stat.birthtime,
44188
- size: stat.size
44189
- };
44190
- }).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
44191
- }
44192
- function readArtifact(projectPath, name) {
44193
- const artifactsDir = getLocusPath(projectPath, "artifactsDir");
44194
- const fileName = name.endsWith(".md") ? name : `${name}.md`;
44195
- const filePath = join17(artifactsDir, fileName);
44196
- if (!existsSync17(filePath)) {
44197
- return null;
44198
- }
44199
- const stat = statSync(filePath);
44200
- const content = readFileSync14(filePath, "utf-8");
44201
- return {
44202
- content,
44203
- info: {
44204
- name: fileName.replace(/\.md$/, ""),
44205
- fileName,
44206
- createdAt: stat.birthtime,
44207
- size: stat.size
44208
- }
44209
- };
44210
- }
44211
- function formatSize(bytes) {
44212
- if (bytes < 1024)
44213
- return `${bytes}B`;
44214
- const kb = bytes / 1024;
44215
- if (kb < 1024)
44216
- return `${kb.toFixed(1)}KB`;
44217
- const mb = kb / 1024;
44218
- return `${mb.toFixed(1)}MB`;
44219
- }
44220
- function formatDate(date5) {
44221
- return date5.toLocaleDateString("en-US", {
44222
- year: "numeric",
44223
- month: "short",
44224
- day: "numeric",
44225
- hour: "2-digit",
44226
- minute: "2-digit"
44227
- });
44228
- }
44229
44229
  async function artifactsCommand(args) {
44230
44230
  const { positionals } = parseArgs2({
44231
44231
  args,
@@ -44296,27 +44296,8 @@ async function listArtifactsCommand(projectPath) {
44296
44296
  `);
44297
44297
  }
44298
44298
  async function showArtifact(projectPath, name) {
44299
- const result = readArtifact(projectPath, name);
44299
+ const result = findArtifact(projectPath, name);
44300
44300
  if (!result) {
44301
- const artifacts = listArtifacts(projectPath);
44302
- const matches = artifacts.filter((a) => a.name.toLowerCase().includes(name.toLowerCase()));
44303
- if (matches.length === 1) {
44304
- const match = readArtifact(projectPath, matches[0].name);
44305
- if (match) {
44306
- printArtifact(match.info, match.content);
44307
- return;
44308
- }
44309
- }
44310
- if (matches.length > 1) {
44311
- console.error(`
44312
- ${c.error("Error:")} Multiple artifacts match "${name}":
44313
- `);
44314
- for (const m of matches) {
44315
- console.log(` ${c.cyan(m.name)}`);
44316
- }
44317
- console.log();
44318
- return;
44319
- }
44320
44301
  console.error(`
44321
44302
  ${c.error("Error:")} Artifact "${name}" not found
44322
44303
  `);
@@ -44324,6 +44305,16 @@ async function showArtifact(projectPath, name) {
44324
44305
  `);
44325
44306
  return;
44326
44307
  }
44308
+ if (!result.match) {
44309
+ console.error(`
44310
+ ${c.error("Error:")} Multiple artifacts match "${name}":
44311
+ `);
44312
+ for (const m of result.ambiguous) {
44313
+ console.log(` ${c.cyan(m.name)}`);
44314
+ }
44315
+ console.log();
44316
+ return;
44317
+ }
44327
44318
  printArtifact(result.info, result.content);
44328
44319
  }
44329
44320
  function printArtifact(info, content) {
@@ -44337,17 +44328,16 @@ function printArtifact(info, content) {
44337
44328
  console.log(content);
44338
44329
  }
44339
44330
  async function convertToPlan(projectPath, name) {
44340
- const result = readArtifact(projectPath, name);
44331
+ const result = findArtifact(projectPath, name);
44341
44332
  if (!result) {
44342
- const artifacts = listArtifacts(projectPath);
44343
- const matches = artifacts.filter((a) => a.name.toLowerCase().includes(name.toLowerCase()));
44344
- if (matches.length === 1) {
44345
- const match = readArtifact(projectPath, matches[0].name);
44346
- if (match) {
44347
- await runPlanConversion(match.info.name);
44348
- return;
44349
- }
44350
- }
44333
+ console.error(`
44334
+ ${c.error("Error:")} Artifact "${name}" not found
44335
+ `);
44336
+ console.log(` ${c.dim("Use 'locus artifacts' to see available artifacts")}
44337
+ `);
44338
+ return;
44339
+ }
44340
+ if (!result.match) {
44351
44341
  console.error(`
44352
44342
  ${c.error("Error:")} Artifact "${name}" not found
44353
44343
  `);
@@ -44366,8 +44356,8 @@ async function runPlanConversion(artifactName) {
44366
44356
  await planCommand2([directive]);
44367
44357
  }
44368
44358
  // src/commands/config.ts
44359
+ init_src3();
44369
44360
  init_index_node();
44370
- init_settings_manager();
44371
44361
  import { createInterface } from "node:readline";
44372
44362
  function ask(question) {
44373
44363
  const rl = createInterface({
@@ -44394,11 +44384,6 @@ var TELEGRAM_KEYS = [
44394
44384
  "telegram.testMode"
44395
44385
  ];
44396
44386
  var ALL_KEYS = [...TOP_LEVEL_KEYS, ...TELEGRAM_KEYS];
44397
- function maskSecret(value) {
44398
- if (value.length <= 8)
44399
- return "****";
44400
- return `${value.slice(0, 4)}...${value.slice(-4)}`;
44401
- }
44402
44387
  function showConfigHelp() {
44403
44388
  console.log(`
44404
44389
  ${c.header(" CONFIG ")}
@@ -44624,11 +44609,11 @@ async function configCommand(args) {
44624
44609
  }
44625
44610
  }
44626
44611
  // src/commands/discuss.ts
44612
+ init_src3();
44627
44613
  init_index_node();
44628
44614
  init_progress_renderer();
44629
44615
  init_image_detect();
44630
44616
  init_input_handler();
44631
- init_settings_manager();
44632
44617
  init_utils3();
44633
44618
  import { parseArgs as parseArgs3 } from "node:util";
44634
44619
  async function discussCommand(args) {
@@ -44651,35 +44636,66 @@ async function discussCommand(args) {
44651
44636
  requireInitialization(projectPath, "discuss");
44652
44637
  const discussionManager = new DiscussionManager(projectPath);
44653
44638
  if (values.list) {
44654
- return listDiscussions(discussionManager);
44639
+ return renderDiscussionList(discussionManager);
44655
44640
  }
44656
44641
  if (values.show) {
44657
- return showDiscussion(discussionManager, values.show);
44642
+ const md = showDiscussion(discussionManager, values.show);
44643
+ if (!md) {
44644
+ console.error(`
44645
+ ${c.error("✖")} ${c.red(`Discussion not found: ${values.show}`)}
44646
+ `);
44647
+ process.exit(1);
44648
+ }
44649
+ console.log(`
44650
+ ${md}
44651
+ `);
44652
+ return;
44658
44653
  }
44659
44654
  if (values.archive) {
44660
- return archiveDiscussion(discussionManager, values.archive);
44655
+ try {
44656
+ archiveDiscussion(discussionManager, values.archive);
44657
+ console.log(`
44658
+ ${c.success("✔")} ${c.dim("Discussion archived.")}
44659
+ `);
44660
+ } catch (error48) {
44661
+ console.error(`
44662
+ ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
44663
+ `);
44664
+ process.exit(1);
44665
+ }
44666
+ return;
44661
44667
  }
44662
44668
  if (values.delete) {
44663
- return deleteDiscussion(discussionManager, values.delete);
44669
+ try {
44670
+ deleteDiscussion(discussionManager, values.delete);
44671
+ console.log(`
44672
+ ${c.success("✔")} ${c.dim("Discussion deleted.")}
44673
+ `);
44674
+ } catch (error48) {
44675
+ console.error(`
44676
+ ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
44677
+ `);
44678
+ process.exit(1);
44679
+ }
44680
+ return;
44664
44681
  }
44665
44682
  const topic = positionals.join(" ").trim();
44666
44683
  if (!topic) {
44667
44684
  showDiscussHelp();
44668
44685
  return;
44669
44686
  }
44670
- const settings = new SettingsManager(projectPath).load();
44671
- const provider = resolveProvider3(values.provider || settings.provider);
44672
- const model = values.model || settings.model || DEFAULT_MODEL[provider];
44687
+ const { provider, model } = resolveAiSettings({
44688
+ projectPath,
44689
+ provider: values.provider,
44690
+ model: values.model
44691
+ });
44673
44692
  const reasoningEffort = values["reasoning-effort"];
44674
44693
  const aiRunner = createAiRunner(provider, {
44675
44694
  projectPath,
44676
44695
  model,
44677
44696
  reasoningEffort
44678
44697
  });
44679
- const log = (message, level) => {
44680
- const icon = level === "success" ? c.success("✔") : level === "error" ? c.error("✖") : level === "warn" ? c.warning("!") : c.info("●");
44681
- console.log(` ${icon} ${message}`);
44682
- };
44698
+ const log = createCliLogger();
44683
44699
  const facilitator = new DiscussionFacilitator({
44684
44700
  projectPath,
44685
44701
  aiRunner,
@@ -44865,8 +44881,8 @@ async function discussCommand(args) {
44865
44881
  inputHandler.start();
44866
44882
  inputHandler.showPrompt();
44867
44883
  }
44868
- function listDiscussions(discussionManager) {
44869
- const discussions = discussionManager.list();
44884
+ function renderDiscussionList(discussionManager) {
44885
+ const discussions = listDiscussions(discussionManager);
44870
44886
  if (discussions.length === 0) {
44871
44887
  console.log(`
44872
44888
  ${c.dim("No discussions found.")}
@@ -44880,50 +44896,12 @@ function listDiscussions(discussionManager) {
44880
44896
  `);
44881
44897
  for (const disc of discussions) {
44882
44898
  const statusIcon = disc.status === "active" ? c.warning("◯") : disc.status === "completed" ? c.success("✔") : c.dim("⊘");
44883
- console.log(` ${statusIcon} ${c.bold(disc.title)} ${c.dim(`[${disc.status}]`)} ${c.dim(`— ${disc.messages.length} messages, ${disc.insights.length} insights`)}`);
44899
+ console.log(` ${statusIcon} ${c.bold(disc.title)} ${c.dim(`[${disc.status}]`)} ${c.dim(`— ${disc.messageCount} messages, ${disc.insightCount} insights`)}`);
44884
44900
  console.log(` ${c.dim("ID:")} ${disc.id}`);
44885
44901
  console.log(` ${c.dim("Created:")} ${disc.createdAt}`);
44886
44902
  console.log("");
44887
44903
  }
44888
44904
  }
44889
- function showDiscussion(discussionManager, id) {
44890
- const md = discussionManager.getMarkdown(id);
44891
- if (!md) {
44892
- console.error(`
44893
- ${c.error("✖")} ${c.red(`Discussion not found: ${id}`)}
44894
- `);
44895
- process.exit(1);
44896
- }
44897
- console.log(`
44898
- ${md}
44899
- `);
44900
- }
44901
- function archiveDiscussion(discussionManager, id) {
44902
- try {
44903
- discussionManager.archive(id);
44904
- console.log(`
44905
- ${c.success("✔")} ${c.dim("Discussion archived.")}
44906
- `);
44907
- } catch (error48) {
44908
- console.error(`
44909
- ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
44910
- `);
44911
- process.exit(1);
44912
- }
44913
- }
44914
- function deleteDiscussion(discussionManager, id) {
44915
- try {
44916
- discussionManager.delete(id);
44917
- console.log(`
44918
- ${c.success("✔")} ${c.dim("Discussion deleted.")}
44919
- `);
44920
- } catch (error48) {
44921
- console.error(`
44922
- ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
44923
- `);
44924
- process.exit(1);
44925
- }
44926
- }
44927
44905
  function showCurrentInsights(discussionManager, discussionId) {
44928
44906
  const discussion2 = discussionManager.load(discussionId);
44929
44907
  if (!discussion2 || discussion2.insights.length === 0) {
@@ -45014,11 +44992,10 @@ function showDiscussHelp() {
45014
44992
  `);
45015
44993
  }
45016
44994
  // src/commands/docs.ts
44995
+ init_src3();
45017
44996
  init_index_node();
45018
44997
  init_config_manager();
45019
- init_settings_manager();
45020
44998
  init_utils3();
45021
- init_workspace_resolver();
45022
44999
  import { parseArgs as parseArgs4 } from "node:util";
45023
45000
  async function docsCommand(args) {
45024
45001
  const subcommand = args[0];
@@ -45052,55 +45029,25 @@ async function docsSyncCommand(args) {
45052
45029
  requireInitialization(projectPath, "docs sync");
45053
45030
  const configManager = new ConfigManager(projectPath);
45054
45031
  configManager.updateVersion(VERSION2);
45055
- const settingsManager = new SettingsManager(projectPath);
45056
- const settings = settingsManager.load();
45057
- const apiKey = values["api-key"] || settings.apiKey;
45058
- if (!apiKey) {
45059
- console.error(`
45060
- ${c.error("✖")} ${c.red("API key is required")}
45061
- ` + ` ${c.dim(`Configure with: locus config setup --api-key <key>
45062
- Or pass --api-key flag`)}
45063
- `);
45064
- process.exit(1);
45065
- }
45066
- const apiBase = values["api-url"] || settings.apiUrl || "https://api.locusai.dev/api";
45067
- const resolver = new WorkspaceResolver({
45068
- apiKey,
45069
- apiBase,
45070
- workspaceId: values.workspace
45071
- });
45072
- let workspaceId;
45032
+ let apiContext;
45073
45033
  try {
45074
- workspaceId = await resolver.resolve();
45034
+ apiContext = await resolveApiContext({
45035
+ projectPath,
45036
+ apiKey: values["api-key"],
45037
+ apiUrl: values["api-url"],
45038
+ workspaceId: values.workspace
45039
+ });
45075
45040
  } catch (error48) {
45076
45041
  console.error(`
45077
45042
  ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
45078
45043
  `);
45079
45044
  process.exit(1);
45080
45045
  }
45081
- const client = new LocusClient({
45082
- baseUrl: apiBase,
45083
- token: apiKey
45084
- });
45085
45046
  const fetcher = new DocumentFetcher({
45086
- client,
45087
- workspaceId,
45047
+ client: apiContext.client,
45048
+ workspaceId: apiContext.workspaceId,
45088
45049
  projectPath,
45089
- log: (message, level) => {
45090
- if (level === "error") {
45091
- console.log(` ${c.error("✖")} ${message}`);
45092
- return;
45093
- }
45094
- if (level === "warn") {
45095
- console.log(` ${c.warning("!")} ${message}`);
45096
- return;
45097
- }
45098
- if (level === "success") {
45099
- console.log(` ${c.success("✔")} ${message}`);
45100
- return;
45101
- }
45102
- console.log(` ${c.info("●")} ${message}`);
45103
- }
45050
+ log: createCliLogger()
45104
45051
  });
45105
45052
  console.log(`
45106
45053
  ${c.info("●")} ${c.bold("Syncing docs from API...")}
@@ -45279,7 +45226,11 @@ class JsonStreamRenderer {
45279
45226
 
45280
45227
  // src/commands/exec.ts
45281
45228
  init_progress_renderer();
45282
- init_settings_manager();
45229
+
45230
+ // src/settings-manager.ts
45231
+ init_src3();
45232
+
45233
+ // src/commands/exec.ts
45283
45234
  init_utils3();
45284
45235
 
45285
45236
  // src/commands/exec-sessions.ts
@@ -45491,7 +45442,7 @@ async function execCommand(args) {
45491
45442
  }
45492
45443
  }
45493
45444
  const execSettings = new SettingsManager(projectPath).load();
45494
- const provider = resolveProvider3(values.provider || execSettings.provider);
45445
+ const provider = resolveProvider4(values.provider || execSettings.provider);
45495
45446
  const model = values.model || execSettings.model || DEFAULT_MODEL[provider];
45496
45447
  const isInteractive = values.interactive;
45497
45448
  const sessionId = values.session;
@@ -45587,7 +45538,7 @@ async function execCommand(args) {
45587
45538
  async function execJsonStream(values, positionals, projectPath) {
45588
45539
  const sessionId = values["session-id"] ?? values.session ?? randomUUID2();
45589
45540
  const execSettings = new SettingsManager(projectPath).load();
45590
- const provider = resolveProvider3(values.provider || execSettings.provider);
45541
+ const provider = resolveProvider4(values.provider || execSettings.provider);
45591
45542
  const model = values.model || execSettings.model || DEFAULT_MODEL[provider];
45592
45543
  const renderer = new JsonStreamRenderer({
45593
45544
  sessionId,
@@ -45757,7 +45708,7 @@ async function indexCommand(args) {
45757
45708
  const projectPath = values.dir || process.cwd();
45758
45709
  requireInitialization(projectPath, "index");
45759
45710
  new ConfigManager(projectPath).updateVersion(VERSION2);
45760
- const provider = resolveProvider3(values.provider);
45711
+ const provider = resolveProvider4(values.provider);
45761
45712
  const model = values.model || DEFAULT_MODEL[provider];
45762
45713
  const aiRunner = createAiRunner(provider, {
45763
45714
  projectPath,
@@ -45848,13 +45799,12 @@ async function initCommand() {
45848
45799
  init_plan();
45849
45800
 
45850
45801
  // src/commands/review.ts
45802
+ init_src3();
45851
45803
  init_index_node();
45852
45804
  init_config_manager();
45853
- init_settings_manager();
45854
45805
  init_utils3();
45855
- init_workspace_resolver();
45856
- import { existsSync as existsSync19, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "node:fs";
45857
- import { join as join19 } from "node:path";
45806
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "node:fs";
45807
+ import { join as join20 } from "node:path";
45858
45808
  import { parseArgs as parseArgs7 } from "node:util";
45859
45809
  async function reviewCommand(args) {
45860
45810
  const subcommand = args[0];
@@ -45880,41 +45830,25 @@ async function reviewPrsCommand(args) {
45880
45830
  requireInitialization(projectPath, "review");
45881
45831
  const configManager = new ConfigManager(projectPath);
45882
45832
  configManager.updateVersion(VERSION2);
45883
- const settingsManager = new SettingsManager(projectPath);
45884
- const settings = settingsManager.load();
45885
- const apiKey = values["api-key"] || settings.apiKey;
45886
- if (!apiKey) {
45887
- console.error(c.error("Error: API key is required for PR review"));
45888
- console.error(c.dim(`Configure with: locus config setup --api-key <key>
45889
- Or pass --api-key flag`));
45890
- console.error(c.dim("For local staged-changes review, use: locus review local"));
45891
- process.exit(1);
45892
- }
45893
- const provider = resolveProvider3(values.provider || settings.provider);
45894
- const model = values.model || settings.model || DEFAULT_MODEL[provider];
45895
- const apiBase = values["api-url"] || settings.apiUrl || "https://api.locusai.dev/api";
45896
- let workspaceId;
45833
+ let apiContext;
45897
45834
  try {
45898
- const resolver = new WorkspaceResolver({
45899
- apiKey,
45900
- apiBase,
45835
+ apiContext = await resolveApiContext({
45836
+ projectPath,
45837
+ apiKey: values["api-key"],
45838
+ apiUrl: values["api-url"],
45901
45839
  workspaceId: values.workspace
45902
45840
  });
45903
- workspaceId = await resolver.resolve();
45904
45841
  } catch (error48) {
45905
45842
  console.error(c.error(error48 instanceof Error ? error48.message : String(error48)));
45843
+ console.error(c.dim("For local staged-changes review, use: locus review local"));
45906
45844
  process.exit(1);
45907
45845
  }
45908
- const log = (msg, level = "info") => {
45909
- const colorFn = {
45910
- info: c.cyan,
45911
- success: c.green,
45912
- warn: c.yellow,
45913
- error: c.red
45914
- }[level];
45915
- const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
45916
- console.log(` ${colorFn(`${prefix} ${msg}`)}`);
45917
- };
45846
+ const { provider, model } = resolveAiSettings({
45847
+ projectPath,
45848
+ provider: values.provider,
45849
+ model: values.model
45850
+ });
45851
+ const log = createCliLogger();
45918
45852
  const prService = new PrService(projectPath, log);
45919
45853
  const unreviewedPrs = prService.listUnreviewedLocusPrs();
45920
45854
  if (unreviewedPrs.length === 0) {
@@ -45929,10 +45863,10 @@ async function reviewPrsCommand(args) {
45929
45863
  const agentId = `reviewer-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
45930
45864
  const reviewer = new ReviewerWorker({
45931
45865
  agentId,
45932
- workspaceId,
45933
- apiBase,
45866
+ workspaceId: apiContext.workspaceId,
45867
+ apiBase: apiContext.apiBase,
45934
45868
  projectPath,
45935
- apiKey,
45869
+ apiKey: apiContext.apiKey,
45936
45870
  model,
45937
45871
  provider
45938
45872
  });
@@ -45961,9 +45895,11 @@ async function reviewLocalCommand(args) {
45961
45895
  });
45962
45896
  const projectPath = values.dir || process.cwd();
45963
45897
  requireInitialization(projectPath, "review local");
45964
- const localSettings = new SettingsManager(projectPath).load();
45965
- const provider = resolveProvider3(values.provider || localSettings.provider);
45966
- const model = values.model || localSettings.model || DEFAULT_MODEL[provider];
45898
+ const { provider, model } = resolveAiSettings({
45899
+ projectPath,
45900
+ provider: values.provider,
45901
+ model: values.model
45902
+ });
45967
45903
  const aiRunner = createAiRunner(provider, {
45968
45904
  projectPath,
45969
45905
  model
@@ -45971,18 +45907,7 @@ async function reviewLocalCommand(args) {
45971
45907
  const reviewService = new ReviewService({
45972
45908
  aiRunner,
45973
45909
  projectPath,
45974
- log: (msg, level) => {
45975
- switch (level) {
45976
- case "error":
45977
- console.log(` ${c.error("✖")} ${msg}`);
45978
- break;
45979
- case "success":
45980
- console.log(` ${c.success("✔")} ${msg}`);
45981
- break;
45982
- default:
45983
- console.log(` ${c.dim(msg)}`);
45984
- }
45985
- }
45910
+ log: createCliLogger()
45986
45911
  });
45987
45912
  console.log(`
45988
45913
  ${c.primary("\uD83D\uDD0D")} ${c.bold("Reviewing staged changes...")}
@@ -45993,13 +45918,13 @@ async function reviewLocalCommand(args) {
45993
45918
  `);
45994
45919
  return;
45995
45920
  }
45996
- const reviewsDir = join19(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.reviewsDir);
45997
- if (!existsSync19(reviewsDir)) {
45921
+ const reviewsDir = join20(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.reviewsDir);
45922
+ if (!existsSync20(reviewsDir)) {
45998
45923
  mkdirSync9(reviewsDir, { recursive: true });
45999
45924
  }
46000
45925
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
46001
- const reportPath = join19(reviewsDir, `review-${timestamp}.md`);
46002
- writeFileSync8(reportPath, report, "utf-8");
45926
+ const reportPath = join20(reviewsDir, `review-${timestamp}.md`);
45927
+ writeFileSync9(reportPath, report, "utf-8");
46003
45928
  console.log(`
46004
45929
  ${c.success("✔")} ${c.success("Review complete!")}`);
46005
45930
  console.log(` ${c.dim("Report saved to:")} ${c.primary(reportPath)}
@@ -46008,10 +45933,23 @@ async function reviewLocalCommand(args) {
46008
45933
  // src/commands/run.ts
46009
45934
  init_index_node();
46010
45935
  init_config_manager();
46011
- init_settings_manager();
46012
- init_utils3();
46013
- init_workspace_resolver();
46014
45936
  import { parseArgs as parseArgs8 } from "node:util";
45937
+ init_utils3();
45938
+
45939
+ // src/workspace-resolver.ts
45940
+ init_src3();
45941
+ init_index_node();
45942
+
45943
+ class WorkspaceResolver2 extends WorkspaceResolver {
45944
+ constructor(options) {
45945
+ super({
45946
+ ...options,
45947
+ log: (msg) => console.log(c.dim(`ℹ ${msg}`))
45948
+ });
45949
+ }
45950
+ }
45951
+
45952
+ // src/commands/run.ts
46015
45953
  async function runCommand(args) {
46016
45954
  const { values } = parseArgs8({
46017
45955
  args,
@@ -46042,11 +45980,11 @@ async function runCommand(args) {
46042
45980
  process.exit(1);
46043
45981
  }
46044
45982
  let workspaceId = values.workspace;
46045
- const provider = resolveProvider3(values.provider || settings.provider);
45983
+ const provider = resolveProvider4(values.provider || settings.provider);
46046
45984
  const model = values.model || settings.model || DEFAULT_MODEL[provider];
46047
45985
  const apiBase = values["api-url"] || settings.apiUrl || "https://api.locusai.dev/api";
46048
45986
  try {
46049
- const resolver = new WorkspaceResolver({
45987
+ const resolver = new WorkspaceResolver2({
46050
45988
  apiKey,
46051
45989
  apiBase,
46052
45990
  workspaceId: values.workspace
@@ -46094,10 +46032,9 @@ ${c.info(`Received ${signal}. Stopping agent and cleaning up...`)}`);
46094
46032
  }
46095
46033
  // src/commands/telegram.ts
46096
46034
  init_index_node();
46097
- init_settings_manager();
46098
46035
  import { spawn as spawn4 } from "node:child_process";
46099
- import { existsSync as existsSync20 } from "node:fs";
46100
- import { join as join20 } from "node:path";
46036
+ import { existsSync as existsSync21 } from "node:fs";
46037
+ import { join as join21 } from "node:path";
46101
46038
  import { createInterface as createInterface2 } from "node:readline";
46102
46039
  function ask2(question) {
46103
46040
  const rl = createInterface2({
@@ -46331,8 +46268,8 @@ function runBotCommand(projectPath) {
46331
46268
  `);
46332
46269
  process.exit(1);
46333
46270
  }
46334
- const monorepoTelegramEntry = join20(projectPath, "packages/telegram/src/index.ts");
46335
- const isMonorepo = existsSync20(monorepoTelegramEntry);
46271
+ const monorepoTelegramEntry = join21(projectPath, "packages/telegram/src/index.ts");
46272
+ const isMonorepo = existsSync21(monorepoTelegramEntry);
46336
46273
  let cmd;
46337
46274
  let args;
46338
46275
  if (isMonorepo) {