@locusai/cli 0.14.5 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/locus.js CHANGED
@@ -7077,6 +7077,7 @@ class ClaudeRunner {
7077
7077
  currentToolName;
7078
7078
  activeTools = new Map;
7079
7079
  activeProcess = null;
7080
+ aborted = false;
7080
7081
  timeoutMs;
7081
7082
  constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CLAUDE], log, timeoutMs) {
7082
7083
  this.model = model;
@@ -7089,6 +7090,7 @@ class ClaudeRunner {
7089
7090
  }
7090
7091
  abort() {
7091
7092
  if (this.activeProcess && !this.activeProcess.killed) {
7093
+ this.aborted = true;
7092
7094
  this.activeProcess.kill("SIGTERM");
7093
7095
  this.activeProcess = null;
7094
7096
  }
@@ -7147,6 +7149,7 @@ class ClaudeRunner {
7147
7149
  return args;
7148
7150
  }
7149
7151
  async* runStream(prompt) {
7152
+ this.aborted = false;
7150
7153
  const args = this.buildCliArgs();
7151
7154
  const env = getAugmentedEnv({
7152
7155
  FORCE_COLOR: "1",
@@ -7241,7 +7244,7 @@ class ClaudeRunner {
7241
7244
  process.stderr.write(`${stderrBuffer}
7242
7245
  `);
7243
7246
  }
7244
- if (code !== 0 && !errorMessage) {
7247
+ if (code !== 0 && !errorMessage && !this.aborted) {
7245
7248
  const detail = stderrFull.trim() || lastResultContent.trim();
7246
7249
  errorMessage = this.createExecutionError(code, detail).message;
7247
7250
  this.eventEmitter?.emitErrorOccurred(errorMessage, `EXIT_${code}`);
@@ -7386,6 +7389,7 @@ class ClaudeRunner {
7386
7389
  return null;
7387
7390
  }
7388
7391
  executeRun(prompt) {
7392
+ this.aborted = false;
7389
7393
  return new Promise((resolve2, reject) => {
7390
7394
  const args = this.buildCliArgs();
7391
7395
  const env = getAugmentedEnv({
@@ -7438,7 +7442,7 @@ class ClaudeRunner {
7438
7442
  }
7439
7443
  process.stdout.write(`
7440
7444
  `);
7441
- if (code === 0) {
7445
+ if (code === 0 || this.aborted) {
7442
7446
  resolve2(finalResult);
7443
7447
  } else {
7444
7448
  const detail = errorOutput.trim() || finalResult.trim();
@@ -7509,6 +7513,7 @@ class CodexRunner {
7509
7513
  log;
7510
7514
  reasoningEffort;
7511
7515
  activeProcess = null;
7516
+ aborted = false;
7512
7517
  eventEmitter;
7513
7518
  currentToolName;
7514
7519
  timeoutMs;
@@ -7524,11 +7529,13 @@ class CodexRunner {
7524
7529
  }
7525
7530
  abort() {
7526
7531
  if (this.activeProcess && !this.activeProcess.killed) {
7532
+ this.aborted = true;
7527
7533
  this.activeProcess.kill("SIGTERM");
7528
7534
  this.activeProcess = null;
7529
7535
  }
7530
7536
  }
7531
7537
  async run(prompt) {
7538
+ this.aborted = false;
7532
7539
  const maxRetries = 3;
7533
7540
  let lastError = null;
7534
7541
  for (let attempt = 1;attempt <= maxRetries; attempt++) {
@@ -7644,7 +7651,7 @@ class CodexRunner {
7644
7651
  });
7645
7652
  codex.on("close", (code) => {
7646
7653
  this.activeProcess = null;
7647
- if (code === 0) {
7654
+ if (code === 0 || this.aborted) {
7648
7655
  const result = this.readOutput(outputPath, finalOutput);
7649
7656
  this.cleanupTempFile(outputPath);
7650
7657
  if (result && finalContent.trim().length === 0) {
@@ -7757,7 +7764,7 @@ class CodexRunner {
7757
7764
  });
7758
7765
  codex.on("close", (code) => {
7759
7766
  this.activeProcess = null;
7760
- if (code === 0) {
7767
+ if (code === 0 || this.aborted) {
7761
7768
  const result = this.readOutput(outputPath, output);
7762
7769
  this.cleanupTempFile(outputPath);
7763
7770
  resolve2(result);
@@ -23345,6 +23352,29 @@ var init_sprints = __esm(() => {
23345
23352
  };
23346
23353
  });
23347
23354
 
23355
+ // ../sdk/src/modules/suggestions.ts
23356
+ var SuggestionsModule;
23357
+ var init_suggestions = __esm(() => {
23358
+ SuggestionsModule = class SuggestionsModule extends BaseModule {
23359
+ async create(workspaceId, data) {
23360
+ const { data: res } = await this.api.post(`/workspaces/${workspaceId}/suggestions`, data);
23361
+ return res.suggestion;
23362
+ }
23363
+ async list(workspaceId, params) {
23364
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/suggestions`, { params });
23365
+ return data.suggestions;
23366
+ }
23367
+ async get(workspaceId, id) {
23368
+ const { data } = await this.api.get(`/workspaces/${workspaceId}/suggestions/${id}`);
23369
+ return data.suggestion;
23370
+ }
23371
+ async updateStatus(workspaceId, id, status) {
23372
+ const { data } = await this.api.patch(`/workspaces/${workspaceId}/suggestions/${id}/status`, status);
23373
+ return data.suggestion;
23374
+ }
23375
+ };
23376
+ });
23377
+
23348
23378
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/core.js
23349
23379
  function $constructor(name, initializer, params) {
23350
23380
  function init(inst, def) {
@@ -37660,6 +37690,75 @@ var init_auth2 = __esm(() => {
37660
37690
  });
37661
37691
  });
37662
37692
 
37693
+ // ../shared/src/models/autonomy.ts
37694
+ var RiskLevel, ChangeCategory, AutonomyRuleSchema, DEFAULT_AUTONOMY_RULES;
37695
+ var init_autonomy = __esm(() => {
37696
+ init_zod();
37697
+ ((RiskLevel2) => {
37698
+ RiskLevel2["LOW"] = "LOW";
37699
+ RiskLevel2["HIGH"] = "HIGH";
37700
+ })(RiskLevel ||= {});
37701
+ ((ChangeCategory2) => {
37702
+ ChangeCategory2["FIX"] = "FIX";
37703
+ ChangeCategory2["REFACTOR"] = "REFACTOR";
37704
+ ChangeCategory2["STYLE"] = "STYLE";
37705
+ ChangeCategory2["DEPENDENCY"] = "DEPENDENCY";
37706
+ ChangeCategory2["FEATURE"] = "FEATURE";
37707
+ ChangeCategory2["ARCHITECTURE"] = "ARCHITECTURE";
37708
+ ChangeCategory2["DATABASE"] = "DATABASE";
37709
+ ChangeCategory2["AUTH"] = "AUTH";
37710
+ ChangeCategory2["API"] = "API";
37711
+ })(ChangeCategory ||= {});
37712
+ AutonomyRuleSchema = exports_external.object({
37713
+ category: exports_external.enum(ChangeCategory),
37714
+ riskLevel: exports_external.enum(RiskLevel),
37715
+ autoExecute: exports_external.boolean()
37716
+ });
37717
+ DEFAULT_AUTONOMY_RULES = [
37718
+ { category: "FIX" /* FIX */, riskLevel: "LOW" /* LOW */, autoExecute: true },
37719
+ {
37720
+ category: "REFACTOR" /* REFACTOR */,
37721
+ riskLevel: "LOW" /* LOW */,
37722
+ autoExecute: true
37723
+ },
37724
+ {
37725
+ category: "STYLE" /* STYLE */,
37726
+ riskLevel: "LOW" /* LOW */,
37727
+ autoExecute: true
37728
+ },
37729
+ {
37730
+ category: "DEPENDENCY" /* DEPENDENCY */,
37731
+ riskLevel: "LOW" /* LOW */,
37732
+ autoExecute: true
37733
+ },
37734
+ {
37735
+ category: "FEATURE" /* FEATURE */,
37736
+ riskLevel: "HIGH" /* HIGH */,
37737
+ autoExecute: false
37738
+ },
37739
+ {
37740
+ category: "ARCHITECTURE" /* ARCHITECTURE */,
37741
+ riskLevel: "HIGH" /* HIGH */,
37742
+ autoExecute: false
37743
+ },
37744
+ {
37745
+ category: "DATABASE" /* DATABASE */,
37746
+ riskLevel: "HIGH" /* HIGH */,
37747
+ autoExecute: false
37748
+ },
37749
+ {
37750
+ category: "AUTH" /* AUTH */,
37751
+ riskLevel: "HIGH" /* HIGH */,
37752
+ autoExecute: false
37753
+ },
37754
+ {
37755
+ category: "API" /* API */,
37756
+ riskLevel: "HIGH" /* HIGH */,
37757
+ autoExecute: false
37758
+ }
37759
+ ];
37760
+ });
37761
+
37663
37762
  // ../shared/src/models/aws-instance.ts
37664
37763
  var InstanceAction, AwsCredentialsSchema, IntegrationSchema, AwsInstanceSchema, CreateAwsInstanceSchema, UpdateAwsInstanceSchema, SaveAwsCredentialsSchema, ProvisionAwsInstanceSchema, InstanceActionBodySchema, InstanceIdParamSchema, CIDR_REGEX, UpdateSecurityRulesSchema;
37665
37764
  var init_aws_instance = __esm(() => {
@@ -37978,6 +38077,49 @@ var init_sprint = __esm(() => {
37978
38077
  });
37979
38078
  });
37980
38079
 
38080
+ // ../shared/src/models/suggestion.ts
38081
+ var SuggestionStatus, SuggestionType, SuggestionSchema, CreateSuggestionSchema, UpdateSuggestionStatusSchema;
38082
+ var init_suggestion = __esm(() => {
38083
+ init_zod();
38084
+ ((SuggestionStatus2) => {
38085
+ SuggestionStatus2["NEW"] = "NEW";
38086
+ SuggestionStatus2["NOTIFIED"] = "NOTIFIED";
38087
+ SuggestionStatus2["ACTED_ON"] = "ACTED_ON";
38088
+ SuggestionStatus2["SKIPPED"] = "SKIPPED";
38089
+ SuggestionStatus2["EXPIRED"] = "EXPIRED";
38090
+ })(SuggestionStatus ||= {});
38091
+ ((SuggestionType2) => {
38092
+ SuggestionType2["CODE_FIX"] = "CODE_FIX";
38093
+ SuggestionType2["DEPENDENCY_UPDATE"] = "DEPENDENCY_UPDATE";
38094
+ SuggestionType2["NEXT_STEP"] = "NEXT_STEP";
38095
+ SuggestionType2["REFACTOR"] = "REFACTOR";
38096
+ SuggestionType2["TEST_FIX"] = "TEST_FIX";
38097
+ })(SuggestionType ||= {});
38098
+ SuggestionSchema = exports_external.object({
38099
+ id: exports_external.string(),
38100
+ type: exports_external.enum(SuggestionType),
38101
+ status: exports_external.enum(SuggestionStatus),
38102
+ title: exports_external.string(),
38103
+ description: exports_external.string(),
38104
+ jobRunId: exports_external.string().optional(),
38105
+ workspaceId: exports_external.string(),
38106
+ createdAt: exports_external.string(),
38107
+ expiresAt: exports_external.string(),
38108
+ metadata: exports_external.record(exports_external.string(), exports_external.any()).optional()
38109
+ });
38110
+ CreateSuggestionSchema = exports_external.object({
38111
+ type: exports_external.enum(SuggestionType),
38112
+ title: exports_external.string().min(1, "Title is required"),
38113
+ description: exports_external.string().min(1, "Description is required"),
38114
+ jobRunId: exports_external.string().uuid().optional(),
38115
+ metadata: exports_external.record(exports_external.string(), exports_external.any()).optional(),
38116
+ expiresAt: exports_external.string().optional()
38117
+ });
38118
+ UpdateSuggestionStatusSchema = exports_external.object({
38119
+ status: exports_external.enum(SuggestionStatus)
38120
+ });
38121
+ });
38122
+
37981
38123
  // ../shared/src/models/task.ts
37982
38124
  var AcceptanceItemSchema, TaskSchema, CreateTaskSchema, UpdateTaskSchema, AddCommentSchema, DispatchTaskSchema, TaskIdParamSchema, TaskQuerySchema, TaskResponseSchema, TasksResponseSchema;
37983
38125
  var init_task = __esm(() => {
@@ -38117,6 +38259,7 @@ var init_models = __esm(() => {
38117
38259
  init_activity();
38118
38260
  init_agent();
38119
38261
  init_auth2();
38262
+ init_autonomy();
38120
38263
  init_aws_instance();
38121
38264
  init_ci2();
38122
38265
  init_doc();
@@ -38124,6 +38267,7 @@ var init_models = __esm(() => {
38124
38267
  init_invitation();
38125
38268
  init_organization();
38126
38269
  init_sprint();
38270
+ init_suggestion();
38127
38271
  init_task();
38128
38272
  init_user();
38129
38273
  init_workspace();
@@ -38846,6 +38990,7 @@ class LocusClient {
38846
38990
  docs;
38847
38991
  ci;
38848
38992
  instances;
38993
+ suggestions;
38849
38994
  constructor(config2) {
38850
38995
  this.emitter = new LocusEmitter;
38851
38996
  this.api = axios_default.create({
@@ -38866,6 +39011,7 @@ class LocusClient {
38866
39011
  this.docs = new DocsModule(this.api, this.emitter);
38867
39012
  this.ci = new CiModule(this.api, this.emitter);
38868
39013
  this.instances = new InstancesModule(this.api, this.emitter);
39014
+ this.suggestions = new SuggestionsModule(this.api, this.emitter);
38869
39015
  if (config2.retryOptions) {
38870
39016
  this.setupRetryInterceptor(config2.retryOptions);
38871
39017
  }
@@ -38935,6 +39081,7 @@ var init_src2 = __esm(() => {
38935
39081
  init_invitations();
38936
39082
  init_organizations();
38937
39083
  init_sprints();
39084
+ init_suggestions();
38938
39085
  init_tasks();
38939
39086
  init_workspaces();
38940
39087
  init_discussion_types();
@@ -38946,6 +39093,7 @@ var init_src2 = __esm(() => {
38946
39093
  init_invitations();
38947
39094
  init_organizations();
38948
39095
  init_sprints();
39096
+ init_suggestions();
38949
39097
  init_tasks();
38950
39098
  init_workspaces();
38951
39099
  });
@@ -41888,6 +42036,22 @@ var init_planning = __esm(() => {
41888
42036
  init_sprint_plan();
41889
42037
  });
41890
42038
 
42039
+ // ../sdk/src/proposals/context-gatherer.ts
42040
+ var init_context_gatherer = () => {};
42041
+
42042
+ // ../sdk/src/proposals/proposal-engine.ts
42043
+ var init_proposal_engine = __esm(() => {
42044
+ init_src();
42045
+ init_factory();
42046
+ init_context_gatherer();
42047
+ });
42048
+
42049
+ // ../sdk/src/proposals/index.ts
42050
+ var init_proposals = __esm(() => {
42051
+ init_context_gatherer();
42052
+ init_proposal_engine();
42053
+ });
42054
+
41891
42055
  // ../sdk/src/index-node.ts
41892
42056
  var init_index_node = __esm(() => {
41893
42057
  init_prompt_builder();
@@ -41902,6 +42066,7 @@ var init_index_node = __esm(() => {
41902
42066
  init_git();
41903
42067
  init_src2();
41904
42068
  init_planning();
42069
+ init_proposals();
41905
42070
  });
41906
42071
 
41907
42072
  // src/utils/version.ts
@@ -42340,9 +42505,22 @@ class SettingsManager {
42340
42505
  exists() {
42341
42506
  return existsSync15(getSettingsPath(this.projectPath));
42342
42507
  }
42508
+ getAutonomyRules() {
42509
+ const settings = this.load();
42510
+ const userRules = settings.autonomy?.rules ?? [];
42511
+ if (userRules.length === 0) {
42512
+ return DEFAULT_AUTONOMY_RULES;
42513
+ }
42514
+ const ruleMap = new Map(DEFAULT_AUTONOMY_RULES.map((r) => [r.category, r]));
42515
+ for (const rule of userRules) {
42516
+ ruleMap.set(rule.category, rule);
42517
+ }
42518
+ return Array.from(ruleMap.values());
42519
+ }
42343
42520
  }
42344
42521
  var init_settings_manager = __esm(() => {
42345
42522
  init_index_node();
42523
+ init_src();
42346
42524
  });
42347
42525
 
42348
42526
  // src/workspace-resolver.ts
@@ -43283,6 +43461,430 @@ var init_progress_renderer = __esm(() => {
43283
43461
  SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
43284
43462
  });
43285
43463
 
43464
+ // src/repl/image-detect.ts
43465
+ import { copyFileSync, existsSync as existsSync18, mkdirSync as mkdirSync8 } from "node:fs";
43466
+ import { homedir as homedir2, tmpdir as tmpdir2 } from "node:os";
43467
+ import { basename as basename2, join as join18 } from "node:path";
43468
+ function hasImageExtension(p) {
43469
+ const dot = p.lastIndexOf(".");
43470
+ if (dot === -1)
43471
+ return false;
43472
+ return IMAGE_EXTENSIONS.has(p.slice(dot).toLowerCase());
43473
+ }
43474
+ function resolvePath(raw) {
43475
+ let p = raw.replace(/\\ /g, " ").trim();
43476
+ if (p.startsWith("'") && p.endsWith("'") || p.startsWith('"') && p.endsWith('"')) {
43477
+ p = p.slice(1, -1);
43478
+ }
43479
+ if (p.startsWith("~/")) {
43480
+ p = homedir2() + p.slice(1);
43481
+ }
43482
+ return p;
43483
+ }
43484
+ function copyToStable(srcPath) {
43485
+ try {
43486
+ mkdirSync8(STABLE_IMAGE_DIR, { recursive: true });
43487
+ const ts = Date.now();
43488
+ const name = `${ts}-${basename2(srcPath)}`;
43489
+ const destPath = join18(STABLE_IMAGE_DIR, name);
43490
+ copyFileSync(srcPath, destPath);
43491
+ return destPath;
43492
+ } catch {
43493
+ return null;
43494
+ }
43495
+ }
43496
+ function detectImages(input) {
43497
+ const seen = new Set;
43498
+ const images = [];
43499
+ const tryAdd = (raw) => {
43500
+ const normalized = resolvePath(raw);
43501
+ if (!normalized || seen.has(normalized))
43502
+ return;
43503
+ if (!hasImageExtension(normalized))
43504
+ return;
43505
+ seen.add(normalized);
43506
+ let exists = false;
43507
+ let stablePath = normalized;
43508
+ try {
43509
+ exists = existsSync18(normalized);
43510
+ } catch {}
43511
+ if (exists) {
43512
+ const copied = copyToStable(normalized);
43513
+ if (copied) {
43514
+ stablePath = copied;
43515
+ }
43516
+ }
43517
+ images.push({ path: normalized, stablePath, raw: raw.trim(), exists });
43518
+ };
43519
+ for (const line of input.split(`
43520
+ `)) {
43521
+ const trimmed = line.trim();
43522
+ const unquoted = trimmed.replace(/^['"]|['"]$/g, "");
43523
+ if (unquoted.startsWith("/") || unquoted.startsWith("~/")) {
43524
+ tryAdd(trimmed);
43525
+ }
43526
+ }
43527
+ const escapedRe = /(?:~\/|\/)[^\s]*(?:\\ [^\s]*)*\.(png|jpe?g|gif|webp|bmp|svg|tiff?)\b/gi;
43528
+ for (const match of input.matchAll(escapedRe)) {
43529
+ tryAdd(match[0]);
43530
+ }
43531
+ const simpleRe = /(?:~\/|\/)\S+\.(png|jpe?g|gif|webp|bmp|svg|tiff?)\b/gi;
43532
+ for (const match of input.matchAll(simpleRe)) {
43533
+ tryAdd(match[0]);
43534
+ }
43535
+ return images;
43536
+ }
43537
+ function imageDisplayName(imagePath) {
43538
+ return basename2(imagePath);
43539
+ }
43540
+ function stripImagePaths(input, images) {
43541
+ if (images.length === 0)
43542
+ return input;
43543
+ let result = input;
43544
+ for (const img of images) {
43545
+ result = result.replace(img.raw, "");
43546
+ if (img.raw !== img.path) {
43547
+ result = result.replace(img.path, "");
43548
+ }
43549
+ }
43550
+ return result.split(`
43551
+ `).map((line) => line.trim()).filter((line) => line.length > 0).join(`
43552
+ `).trim();
43553
+ }
43554
+ function buildImageContext(images) {
43555
+ if (images.length === 0)
43556
+ return "";
43557
+ const existingImages = images.filter((img) => img.exists);
43558
+ if (existingImages.length === 0)
43559
+ return "";
43560
+ const pathList = existingImages.map((img) => `- ${img.stablePath}`).join(`
43561
+ `);
43562
+ const noun = existingImages.length === 1 ? "an image" : "images";
43563
+ const pronoun = existingImages.length === 1 ? "it" : "them";
43564
+ return `
43565
+
43566
+ [The user has attached ${noun}. Use the Read tool to view ${pronoun}:
43567
+ ${pathList}
43568
+ ]`;
43569
+ }
43570
+ var IMAGE_EXTENSIONS, STABLE_IMAGE_DIR;
43571
+ var init_image_detect = __esm(() => {
43572
+ IMAGE_EXTENSIONS = new Set([
43573
+ ".png",
43574
+ ".jpg",
43575
+ ".jpeg",
43576
+ ".gif",
43577
+ ".webp",
43578
+ ".bmp",
43579
+ ".svg",
43580
+ ".tif",
43581
+ ".tiff"
43582
+ ]);
43583
+ STABLE_IMAGE_DIR = join18(tmpdir2(), "locus-images");
43584
+ });
43585
+
43586
+ // src/repl/input-handler.ts
43587
+ class InputHandler {
43588
+ buffer = "";
43589
+ isPasting = false;
43590
+ locked = false;
43591
+ active = false;
43592
+ displayedLines = 0;
43593
+ prompt;
43594
+ continuationPrompt;
43595
+ onSubmit;
43596
+ onInterrupt;
43597
+ onClose;
43598
+ constructor(options) {
43599
+ this.prompt = options.prompt ?? "> ";
43600
+ this.continuationPrompt = options.continuationPrompt ?? "… ";
43601
+ this.onSubmit = options.onSubmit;
43602
+ this.onInterrupt = options.onInterrupt;
43603
+ this.onClose = options.onClose;
43604
+ }
43605
+ start() {
43606
+ if (this.active)
43607
+ return;
43608
+ if (!process.stdin.isTTY)
43609
+ return;
43610
+ process.stdin.setRawMode(true);
43611
+ process.stdin.resume();
43612
+ process.stdin.setEncoding("utf-8");
43613
+ process.stdout.write(ENABLE_BRACKETED_PASTE);
43614
+ this.active = true;
43615
+ process.stdin.on("data", this.handleData);
43616
+ }
43617
+ stop() {
43618
+ if (!this.active)
43619
+ return;
43620
+ process.stdout.write(DISABLE_BRACKETED_PASTE);
43621
+ if (process.stdin.isTTY) {
43622
+ process.stdin.setRawMode(false);
43623
+ }
43624
+ process.stdin.removeListener("data", this.handleData);
43625
+ process.stdin.pause();
43626
+ this.active = false;
43627
+ }
43628
+ lock() {
43629
+ this.locked = true;
43630
+ }
43631
+ showPrompt() {
43632
+ this.locked = false;
43633
+ this.buffer = "";
43634
+ this.displayedLines = 0;
43635
+ process.stdout.write(this.prompt);
43636
+ this.displayedLines = 1;
43637
+ }
43638
+ handleData = (data) => {
43639
+ if (this.locked) {
43640
+ if (data.includes("\x03")) {
43641
+ this.onInterrupt();
43642
+ }
43643
+ return;
43644
+ }
43645
+ if (data.includes(PASTE_START) || this.isPasting) {
43646
+ this.handleBracketedPaste(data);
43647
+ return;
43648
+ }
43649
+ if (this.looksLikePaste(data)) {
43650
+ this.handleHeuristicPaste(data);
43651
+ return;
43652
+ }
43653
+ this.processKeystrokes(data);
43654
+ };
43655
+ handleBracketedPaste(data) {
43656
+ let remaining = data;
43657
+ const startIdx = remaining.indexOf(PASTE_START);
43658
+ if (startIdx !== -1) {
43659
+ if (startIdx > 0) {
43660
+ this.processKeystrokes(remaining.slice(0, startIdx));
43661
+ }
43662
+ this.isPasting = true;
43663
+ remaining = remaining.slice(startIdx + PASTE_START.length);
43664
+ }
43665
+ const endIdx = remaining.indexOf(PASTE_END);
43666
+ if (endIdx !== -1) {
43667
+ this.appendPasteContent(remaining.slice(0, endIdx));
43668
+ this.isPasting = false;
43669
+ const afterPaste = remaining.slice(endIdx + PASTE_END.length);
43670
+ if (afterPaste.length > 0) {
43671
+ this.processKeystrokes(afterPaste);
43672
+ }
43673
+ return;
43674
+ }
43675
+ if (this.isPasting) {
43676
+ this.appendPasteContent(remaining);
43677
+ }
43678
+ }
43679
+ appendPasteContent(content) {
43680
+ const normalized = content.replace(/\r\n/g, `
43681
+ `).replace(/\r/g, `
43682
+ `);
43683
+ this.buffer += normalized;
43684
+ this.fullRender();
43685
+ }
43686
+ looksLikePaste(data) {
43687
+ const crIdx = data.indexOf("\r");
43688
+ if (crIdx === -1)
43689
+ return false;
43690
+ let afterIdx = crIdx + 1;
43691
+ if (afterIdx < data.length && data[afterIdx] === `
43692
+ `)
43693
+ afterIdx++;
43694
+ return afterIdx < data.length && data.charCodeAt(afterIdx) >= 32;
43695
+ }
43696
+ handleHeuristicPaste(data) {
43697
+ const normalized = data.replace(/\r\n/g, `
43698
+ `).replace(/\r/g, `
43699
+ `);
43700
+ this.buffer += normalized;
43701
+ this.fullRender();
43702
+ }
43703
+ processKeystrokes(data) {
43704
+ let i = 0;
43705
+ while (i < data.length) {
43706
+ if (this.locked)
43707
+ break;
43708
+ const ch = data[i];
43709
+ if (ch === "\x03") {
43710
+ if (this.buffer.length > 0) {
43711
+ this.buffer = "";
43712
+ process.stdout.write(`\r
43713
+ ${CLEAR_SCREEN_DOWN}${this.prompt}`);
43714
+ this.displayedLines = 1;
43715
+ } else {
43716
+ this.onInterrupt();
43717
+ }
43718
+ i++;
43719
+ continue;
43720
+ }
43721
+ if (ch === "\x04") {
43722
+ if (this.buffer.length === 0) {
43723
+ process.stdout.write(`
43724
+ `);
43725
+ this.onClose();
43726
+ }
43727
+ i++;
43728
+ continue;
43729
+ }
43730
+ if (ch === "\r") {
43731
+ i++;
43732
+ if (i < data.length && data[i] === `
43733
+ `)
43734
+ i++;
43735
+ this.submit();
43736
+ continue;
43737
+ }
43738
+ if (ch === `
43739
+ `) {
43740
+ this.insertNewline();
43741
+ i++;
43742
+ continue;
43743
+ }
43744
+ if (ch === "\x1B") {
43745
+ const consumed = this.processEscapeSequence(data, i);
43746
+ i += consumed;
43747
+ continue;
43748
+ }
43749
+ if (ch === "" || ch === "\b") {
43750
+ this.deleteLastChar();
43751
+ i++;
43752
+ continue;
43753
+ }
43754
+ if (ch === "\x15") {
43755
+ this.buffer = "";
43756
+ this.fullRender();
43757
+ i++;
43758
+ continue;
43759
+ }
43760
+ if (ch === "\x17") {
43761
+ this.deleteLastWord();
43762
+ i++;
43763
+ continue;
43764
+ }
43765
+ if (ch === "\t") {
43766
+ i++;
43767
+ continue;
43768
+ }
43769
+ if (ch.charCodeAt(0) < 32) {
43770
+ i++;
43771
+ continue;
43772
+ }
43773
+ this.buffer += ch;
43774
+ this.fullRender();
43775
+ i++;
43776
+ }
43777
+ }
43778
+ processEscapeSequence(data, pos) {
43779
+ if (pos + 1 >= data.length)
43780
+ return 1;
43781
+ const next = data[pos + 1];
43782
+ if (next === "\r") {
43783
+ this.insertNewline();
43784
+ if (pos + 2 < data.length && data[pos + 2] === `
43785
+ `)
43786
+ return 3;
43787
+ return 2;
43788
+ }
43789
+ if (next === "[") {
43790
+ return this.processCSI(data, pos);
43791
+ }
43792
+ if (next === "O") {
43793
+ return pos + 2 < data.length ? 3 : 2;
43794
+ }
43795
+ return 2;
43796
+ }
43797
+ processCSI(data, pos) {
43798
+ let j = pos + 2;
43799
+ while (j < data.length && data.charCodeAt(j) >= 48 && data.charCodeAt(j) <= 63)
43800
+ j++;
43801
+ while (j < data.length && data.charCodeAt(j) >= 32 && data.charCodeAt(j) <= 47)
43802
+ j++;
43803
+ if (j < data.length)
43804
+ j++;
43805
+ const seq = data.slice(pos, j);
43806
+ if (seq === "\x1B[13;2u") {
43807
+ this.insertNewline();
43808
+ }
43809
+ return j - pos;
43810
+ }
43811
+ insertNewline() {
43812
+ this.buffer += `
43813
+ `;
43814
+ this.fullRender();
43815
+ }
43816
+ deleteLastChar() {
43817
+ if (this.buffer.length === 0)
43818
+ return;
43819
+ this.buffer = this.buffer.slice(0, -1);
43820
+ this.fullRender();
43821
+ }
43822
+ deleteLastWord() {
43823
+ if (this.buffer.length === 0)
43824
+ return;
43825
+ const prev = this.buffer;
43826
+ this.buffer = this.buffer.replace(/\S*\s*$/, "");
43827
+ if (this.buffer !== prev) {
43828
+ this.fullRender();
43829
+ }
43830
+ }
43831
+ submit() {
43832
+ const input = this.buffer;
43833
+ this.buffer = "";
43834
+ this.displayedLines = 0;
43835
+ process.stdout.write(`
43836
+ `);
43837
+ this.locked = true;
43838
+ this.onSubmit(input);
43839
+ }
43840
+ visualLength(str) {
43841
+ const ESC2 = String.fromCharCode(27);
43842
+ return str.replace(new RegExp(`${ESC2}\\[[0-9;]*m`, "g"), "").length;
43843
+ }
43844
+ calculatePhysicalLines() {
43845
+ const cols = process.stdout.columns || 80;
43846
+ const lines = this.buffer.split(`
43847
+ `);
43848
+ let total = 0;
43849
+ for (let i = 0;i < lines.length; i++) {
43850
+ const prefix = i === 0 ? this.prompt : this.continuationPrompt;
43851
+ const len = this.visualLength(prefix) + lines[i].length;
43852
+ total += len === 0 ? 1 : Math.ceil(len / cols) || 1;
43853
+ }
43854
+ return total;
43855
+ }
43856
+ fullRender() {
43857
+ let output = "";
43858
+ if (this.displayedLines > 0) {
43859
+ if (this.displayedLines > 1) {
43860
+ output += `\r${CSI}${this.displayedLines - 1}A`;
43861
+ } else {
43862
+ output += "\r";
43863
+ }
43864
+ output += CLEAR_SCREEN_DOWN;
43865
+ }
43866
+ const lines = this.buffer.split(`
43867
+ `);
43868
+ for (let idx = 0;idx < lines.length; idx++) {
43869
+ const p = idx === 0 ? this.prompt : this.continuationPrompt;
43870
+ if (idx > 0)
43871
+ output += `\r
43872
+ `;
43873
+ output += p + lines[idx];
43874
+ }
43875
+ process.stdout.write(output);
43876
+ this.displayedLines = this.calculatePhysicalLines();
43877
+ }
43878
+ }
43879
+ var CSI = "\x1B[", PASTE_START, PASTE_END, ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, CLEAR_SCREEN_DOWN;
43880
+ var init_input_handler = __esm(() => {
43881
+ PASTE_START = `${CSI}200~`;
43882
+ PASTE_END = `${CSI}201~`;
43883
+ ENABLE_BRACKETED_PASTE = `${CSI}?2004h`;
43884
+ DISABLE_BRACKETED_PASTE = `${CSI}?2004l`;
43885
+ CLEAR_SCREEN_DOWN = `${CSI}J`;
43886
+ });
43887
+
43286
43888
  // src/display/execution-stats.ts
43287
43889
  class ExecutionStatsTracker {
43288
43890
  startTime;
@@ -43404,6 +44006,15 @@ function showHelp() {
43404
44006
  ${c.success("history")} / ${c.dim("hist")} List recent sessions
43405
44007
  ${c.success("session")} / ${c.dim("sid")} Show current session ID
43406
44008
 
44009
+ ${c.primary("Key Bindings")}
44010
+
44011
+ ${c.success("Enter")} Send message
44012
+ ${c.success("Shift+Enter")} Insert newline (also: Alt+Enter, Ctrl+J)
44013
+ ${c.success("Ctrl+C")} Interrupt running agent / clear input / exit
44014
+ ${c.success("Ctrl+D")} Exit (on empty input)
44015
+ ${c.success("Ctrl+U")} Clear current input
44016
+ ${c.success("Ctrl+W")} Delete last word
44017
+
43407
44018
  ${c.dim("Any other input will be sent as a prompt to the AI.")}
43408
44019
  `);
43409
44020
  }
@@ -43492,23 +44103,20 @@ var exports_interactive_session = {};
43492
44103
  __export(exports_interactive_session, {
43493
44104
  InteractiveSession: () => InteractiveSession
43494
44105
  });
43495
- import * as readline2 from "node:readline";
43496
44106
 
43497
44107
  class InteractiveSession {
43498
- readline = null;
44108
+ inputHandler = null;
43499
44109
  aiRunner;
43500
44110
  promptBuilder;
43501
44111
  renderer;
43502
44112
  isProcessing = false;
44113
+ interrupted = false;
43503
44114
  conversationHistory = [];
43504
44115
  historyManager;
43505
44116
  currentSession;
43506
44117
  projectPath;
43507
44118
  model;
43508
44119
  provider;
43509
- inputBuffer = [];
43510
- inputDebounceTimer = null;
43511
- static PASTE_DEBOUNCE_MS = 50;
43512
44120
  constructor(options) {
43513
44121
  this.aiRunner = createAiRunner(options.provider, {
43514
44122
  projectPath: options.projectPath,
@@ -43538,49 +44146,38 @@ class InteractiveSession {
43538
44146
  }
43539
44147
  async start() {
43540
44148
  this.printWelcome();
43541
- this.readline = readline2.createInterface({
43542
- input: process.stdin,
43543
- output: process.stdout,
43544
- terminal: true
43545
- });
43546
- this.readline.setPrompt(c.cyan("> "));
43547
- this.readline.prompt();
43548
- this.readline.on("line", (input) => this.handleLine(input));
43549
- this.readline.on("close", () => this.shutdown());
43550
- process.on("SIGINT", () => {
43551
- if (this.inputDebounceTimer) {
43552
- clearTimeout(this.inputDebounceTimer);
43553
- this.inputDebounceTimer = null;
43554
- }
43555
- this.inputBuffer = [];
43556
- if (this.isProcessing) {
43557
- this.renderer.stopThinkingAnimation();
43558
- console.log(c.dim(`
44149
+ this.inputHandler = new InputHandler({
44150
+ prompt: c.cyan("> "),
44151
+ continuationPrompt: c.dim("… "),
44152
+ onSubmit: (input) => {
44153
+ this.handleSubmit(input).catch((err) => {
44154
+ console.error(c.error(`Error: ${err instanceof Error ? err.message : String(err)}`));
44155
+ this.inputHandler?.showPrompt();
44156
+ });
44157
+ },
44158
+ onInterrupt: () => {
44159
+ if (this.isProcessing) {
44160
+ this.interrupted = true;
44161
+ this.renderer.stopThinkingAnimation();
44162
+ this.aiRunner.abort();
44163
+ console.log(c.dim(`
43559
44164
  [Interrupted]`));
43560
- this.isProcessing = false;
43561
- this.readline?.prompt();
43562
- } else {
43563
- this.shutdown();
43564
- }
44165
+ this.isProcessing = false;
44166
+ this.inputHandler?.showPrompt();
44167
+ } else {
44168
+ this.shutdown();
44169
+ }
44170
+ },
44171
+ onClose: () => this.shutdown()
43565
44172
  });
44173
+ this.inputHandler.start();
44174
+ this.inputHandler.showPrompt();
43566
44175
  }
43567
- handleLine(input) {
43568
- this.inputBuffer.push(input);
43569
- if (this.inputDebounceTimer) {
43570
- clearTimeout(this.inputDebounceTimer);
43571
- }
43572
- this.inputDebounceTimer = setTimeout(() => {
43573
- this.processBufferedInput();
43574
- }, InteractiveSession.PASTE_DEBOUNCE_MS);
43575
- }
43576
- async processBufferedInput() {
43577
- const fullInput = this.inputBuffer.join(`
43578
- `);
43579
- this.inputBuffer = [];
43580
- this.inputDebounceTimer = null;
43581
- const trimmed = fullInput.trim();
44176
+ async handleSubmit(input) {
44177
+ this.interrupted = false;
44178
+ const trimmed = input.trim();
43582
44179
  if (trimmed === "") {
43583
- this.readline?.prompt();
44180
+ this.inputHandler?.showPrompt();
43584
44181
  return;
43585
44182
  }
43586
44183
  if (!trimmed.includes(`
@@ -43588,19 +44185,30 @@ class InteractiveSession {
43588
44185
  const command = parseCommand(trimmed);
43589
44186
  if (command) {
43590
44187
  await command.execute(this, trimmed.slice(command.name.length).trim());
43591
- if (this.readline)
43592
- this.readline.prompt();
44188
+ this.inputHandler?.showPrompt();
43593
44189
  return;
43594
44190
  }
43595
44191
  }
43596
44192
  await this.executePrompt(trimmed);
43597
- this.readline?.prompt();
44193
+ if (!this.interrupted) {
44194
+ this.inputHandler?.showPrompt();
44195
+ }
43598
44196
  }
43599
44197
  async executePrompt(prompt) {
43600
44198
  this.isProcessing = true;
43601
44199
  const statsTracker = new ExecutionStatsTracker;
43602
44200
  try {
43603
- const fullPrompt = await this.buildPromptWithHistory(prompt);
44201
+ const images = detectImages(prompt);
44202
+ if (images.length > 0) {
44203
+ for (const img of images) {
44204
+ const status = img.exists ? c.success("attached") : c.warning("not found");
44205
+ process.stdout.write(` ${c.cyan(`[Image: ${imageDisplayName(img.path)}]`)} ${status}\r
44206
+ `);
44207
+ }
44208
+ }
44209
+ const cleanedPrompt = stripImagePaths(prompt, images);
44210
+ const effectivePrompt = cleanedPrompt + buildImageContext(images);
44211
+ const fullPrompt = await this.buildPromptWithHistory(effectivePrompt);
43604
44212
  const stream4 = this.aiRunner.runStream(fullPrompt);
43605
44213
  let responseContent = "";
43606
44214
  this.renderer.showThinkingStarted();
@@ -43698,15 +44306,11 @@ ${userInput}`;
43698
44306
  this.historyManager.pruneSessions();
43699
44307
  }
43700
44308
  shutdown() {
43701
- if (this.inputDebounceTimer) {
43702
- clearTimeout(this.inputDebounceTimer);
43703
- this.inputDebounceTimer = null;
43704
- }
43705
44309
  this.renderer.stopThinkingAnimation();
43706
44310
  this.aiRunner.abort();
43707
44311
  console.log(c.dim(`
43708
44312
  Goodbye!`));
43709
- this.readline?.close();
44313
+ this.inputHandler?.stop();
43710
44314
  process.exit(0);
43711
44315
  }
43712
44316
  printWelcome() {
@@ -43716,6 +44320,7 @@ Goodbye!`));
43716
44320
  ${c.primary("Locus Interactive Mode")}
43717
44321
  ${sessionInfo}
43718
44322
  ${c.dim("Type your prompt, or 'help' for commands")}
44323
+ ${c.dim("Enter to send, Shift+Enter for newline")}
43719
44324
  ${c.dim("Use 'exit' or Ctrl+D to quit")}
43720
44325
  `);
43721
44326
  }
@@ -43724,6 +44329,8 @@ var init_interactive_session = __esm(() => {
43724
44329
  init_index_node();
43725
44330
  init_progress_renderer();
43726
44331
  init_commands();
44332
+ init_image_detect();
44333
+ init_input_handler();
43727
44334
  });
43728
44335
 
43729
44336
  // src/cli.ts
@@ -44190,9 +44797,10 @@ async function configCommand(args) {
44190
44797
  // src/commands/discuss.ts
44191
44798
  init_index_node();
44192
44799
  init_progress_renderer();
44800
+ init_image_detect();
44801
+ init_input_handler();
44193
44802
  init_settings_manager();
44194
44803
  init_utils3();
44195
- import * as readline from "node:readline";
44196
44804
  import { parseArgs as parseArgs3 } from "node:util";
44197
44805
  async function discussCommand(args) {
44198
44806
  const { values, positionals } = parseArgs3({
@@ -44278,16 +44886,11 @@ async function discussCommand(args) {
44278
44886
  `);
44279
44887
  process.exit(1);
44280
44888
  }
44281
- console.log(` ${c.dim("Type your response, or 'help' for commands. Use 'exit' or Ctrl+C to quit.")}
44889
+ console.log(` ${c.dim("Type your response, or 'help' for commands.")}`);
44890
+ console.log(` ${c.dim("Enter to send, Shift+Enter for newline. Use 'exit' or Ctrl+D to quit.")}
44282
44891
  `);
44283
- const rl = readline.createInterface({
44284
- input: process.stdin,
44285
- output: process.stdout,
44286
- terminal: true
44287
- });
44288
- rl.setPrompt(c.cyan("> "));
44289
- rl.prompt();
44290
44892
  let isProcessing = false;
44893
+ let interrupted = false;
44291
44894
  const shutdown = () => {
44292
44895
  if (isProcessing) {
44293
44896
  aiRunner.abort();
@@ -44297,83 +44900,83 @@ async function discussCommand(args) {
44297
44900
  console.log(c.dim(`
44298
44901
  Goodbye!
44299
44902
  `));
44300
- rl.close();
44903
+ inputHandler.stop();
44301
44904
  process.exit(0);
44302
44905
  };
44303
- process.on("SIGINT", () => {
44304
- if (isProcessing) {
44305
- aiRunner.abort();
44306
- isProcessing = false;
44307
- console.log(c.dim(`
44308
- [Interrupted]`));
44309
- rl.prompt();
44310
- } else {
44311
- shutdown();
44312
- }
44313
- });
44314
- rl.on("close", () => {
44315
- shutdown();
44316
- });
44317
- rl.on("line", async (input) => {
44906
+ const handleSubmit = async (input) => {
44907
+ interrupted = false;
44318
44908
  const trimmed = input.trim();
44319
- if (trimmed === "" || isProcessing) {
44320
- rl.prompt();
44321
- return;
44322
- }
44323
- const lowerInput = trimmed.toLowerCase();
44324
- if (lowerInput === "help") {
44325
- showReplHelp();
44326
- rl.prompt();
44327
- return;
44328
- }
44329
- if (lowerInput === "exit" || lowerInput === "quit") {
44330
- shutdown();
44331
- return;
44332
- }
44333
- if (lowerInput === "insights") {
44334
- showCurrentInsights(discussionManager, discussionId);
44335
- rl.prompt();
44909
+ if (trimmed === "") {
44910
+ inputHandler.showPrompt();
44336
44911
  return;
44337
44912
  }
44338
- if (lowerInput === "summary") {
44339
- isProcessing = true;
44340
- const summaryRenderer = new ProgressRenderer({ animated: true });
44341
- try {
44342
- summaryRenderer.showThinkingStarted();
44343
- const summary = await facilitator.summarizeDiscussion(discussionId);
44344
- summaryRenderer.showThinkingStopped();
44345
- process.stdout.write(`
44913
+ if (!trimmed.includes(`
44914
+ `)) {
44915
+ const lowerInput = trimmed.toLowerCase();
44916
+ if (lowerInput === "help") {
44917
+ showReplHelp();
44918
+ inputHandler.showPrompt();
44919
+ return;
44920
+ }
44921
+ if (lowerInput === "exit" || lowerInput === "quit") {
44922
+ shutdown();
44923
+ return;
44924
+ }
44925
+ if (lowerInput === "insights") {
44926
+ showCurrentInsights(discussionManager, discussionId);
44927
+ inputHandler.showPrompt();
44928
+ return;
44929
+ }
44930
+ if (lowerInput === "summary") {
44931
+ isProcessing = true;
44932
+ const summaryRenderer = new ProgressRenderer({ animated: true });
44933
+ try {
44934
+ summaryRenderer.showThinkingStarted();
44935
+ const summary = await facilitator.summarizeDiscussion(discussionId);
44936
+ summaryRenderer.showThinkingStopped();
44937
+ process.stdout.write(`
44346
44938
  `);
44347
- process.stdout.write(summary);
44348
- process.stdout.write(`
44939
+ process.stdout.write(summary);
44940
+ process.stdout.write(`
44349
44941
  `);
44350
- summaryRenderer.finalize();
44351
- const discussion2 = discussionManager.load(discussionId);
44352
- if (discussion2) {
44353
- console.log(`
44942
+ summaryRenderer.finalize();
44943
+ const discussion2 = discussionManager.load(discussionId);
44944
+ if (discussion2) {
44945
+ console.log(`
44354
44946
  ${c.success("✔")} ${c.success("Discussion completed!")}
44355
44947
  `);
44356
- console.log(` ${c.dim("Messages:")} ${discussion2.messages.length} ${c.dim("Insights:")} ${discussion2.insights.length}
44948
+ console.log(` ${c.dim("Messages:")} ${discussion2.messages.length} ${c.dim("Insights:")} ${discussion2.insights.length}
44357
44949
  `);
44358
- }
44359
- console.log(` ${c.dim("To review:")} ${c.cyan(`locus discuss --show ${discussionId}`)}`);
44360
- console.log(` ${c.dim("To list all:")} ${c.cyan("locus discuss --list")}
44950
+ }
44951
+ console.log(` ${c.dim("To review:")} ${c.cyan(`locus discuss --show ${discussionId}`)}`);
44952
+ console.log(` ${c.dim("To list all:")} ${c.cyan("locus discuss --list")}
44361
44953
  `);
44362
- } catch (error48) {
44363
- summaryRenderer.finalize();
44364
- console.error(`
44954
+ } catch (error48) {
44955
+ summaryRenderer.finalize();
44956
+ console.error(`
44365
44957
  ${c.error("✖")} ${c.red("Failed to summarize:")} ${error48 instanceof Error ? error48.message : String(error48)}
44958
+ `);
44959
+ }
44960
+ inputHandler.stop();
44961
+ process.exit(0);
44962
+ return;
44963
+ }
44964
+ }
44965
+ const images = detectImages(trimmed);
44966
+ if (images.length > 0) {
44967
+ for (const img of images) {
44968
+ const status = img.exists ? c.success("attached") : c.warning("not found");
44969
+ process.stdout.write(` ${c.cyan(`[Image: ${imageDisplayName(img.path)}]`)} ${status}\r
44366
44970
  `);
44367
44971
  }
44368
- rl.close();
44369
- process.exit(0);
44370
- return;
44371
44972
  }
44973
+ const cleanedInput = stripImagePaths(trimmed, images);
44974
+ const effectiveInput = cleanedInput + buildImageContext(images);
44372
44975
  isProcessing = true;
44373
44976
  const chunkRenderer = new ProgressRenderer({ animated: true });
44374
44977
  try {
44375
44978
  chunkRenderer.showThinkingStarted();
44376
- const stream4 = facilitator.continueDiscussionStream(discussionId, trimmed);
44979
+ const stream4 = facilitator.continueDiscussionStream(discussionId, effectiveInput);
44377
44980
  let result = {
44378
44981
  response: "",
44379
44982
  insights: []
@@ -44401,8 +45004,37 @@ async function discussCommand(args) {
44401
45004
  `);
44402
45005
  }
44403
45006
  isProcessing = false;
44404
- rl.prompt();
45007
+ if (!interrupted) {
45008
+ inputHandler.showPrompt();
45009
+ }
45010
+ };
45011
+ const inputHandler = new InputHandler({
45012
+ prompt: c.cyan("> "),
45013
+ continuationPrompt: c.dim("… "),
45014
+ onSubmit: (input) => {
45015
+ handleSubmit(input).catch((err) => {
45016
+ console.error(`
45017
+ ${c.error("✖")} ${c.red(err instanceof Error ? err.message : String(err))}
45018
+ `);
45019
+ inputHandler.showPrompt();
45020
+ });
45021
+ },
45022
+ onInterrupt: () => {
45023
+ if (isProcessing) {
45024
+ interrupted = true;
45025
+ aiRunner.abort();
45026
+ isProcessing = false;
45027
+ console.log(c.dim(`
45028
+ [Interrupted]`));
45029
+ inputHandler.showPrompt();
45030
+ } else {
45031
+ shutdown();
45032
+ }
45033
+ },
45034
+ onClose: () => shutdown()
44405
45035
  });
45036
+ inputHandler.start();
45037
+ inputHandler.showPrompt();
44406
45038
  }
44407
45039
  function listDiscussions(discussionManager) {
44408
45040
  const discussions = discussionManager.list();
@@ -44507,6 +45139,14 @@ function showReplHelp() {
44507
45139
  ${c.cyan("exit")} Save and exit without generating a summary
44508
45140
  ${c.cyan("help")} Show this help message
44509
45141
 
45142
+ ${c.header(" KEY BINDINGS ")}
45143
+
45144
+ ${c.cyan("Enter")} Send message
45145
+ ${c.cyan("Shift+Enter")} Insert newline (also: Alt+Enter, Ctrl+J)
45146
+ ${c.cyan("Ctrl+C")} Interrupt / clear input / exit
45147
+ ${c.cyan("Ctrl+U")} Clear current input
45148
+ ${c.cyan("Ctrl+W")} Delete last word
45149
+
44510
45150
  ${c.dim("Type anything else to continue the discussion.")}
44511
45151
  `);
44512
45152
  }
@@ -45214,6 +45854,10 @@ function showHelp2() {
45214
45854
  ${c.dim("sessions show <id> Show session messages")}
45215
45855
  ${c.dim("sessions delete <id> Delete a session")}
45216
45856
  ${c.dim("sessions clear Clear all sessions")}
45857
+ ${c.success("service")} Manage the Locus system service
45858
+ ${c.dim("install Install as systemd/launchd service")}
45859
+ ${c.dim("uninstall Remove the system service")}
45860
+ ${c.dim("status Check if service is running")}
45217
45861
  ${c.success("artifacts")} List and manage knowledge artifacts
45218
45862
  ${c.dim("show <name> Show artifact content")}
45219
45863
  ${c.dim("plan <name> Convert artifact to a plan")}
@@ -45229,10 +45873,10 @@ function showHelp2() {
45229
45873
  ${c.header(" GETTING STARTED ")}
45230
45874
  ${c.dim("$")} ${c.primary("locus init")}
45231
45875
  ${c.dim("$")} ${c.primary("locus config setup")}
45232
- ${c.dim("$")} ${c.primary("locus run")}
45876
+ ${c.dim("$")} ${c.primary("locus telegram setup")}
45877
+ ${c.dim("$")} ${c.primary("locus service install")}
45233
45878
 
45234
45879
  ${c.header(" EXAMPLES ")}
45235
- ${c.dim("$")} ${c.primary("locus config show")}
45236
45880
  ${c.dim("$")} ${c.primary("locus run")}
45237
45881
  ${c.dim("$")} ${c.primary("locus docs sync")}
45238
45882
  ${c.dim("$")} ${c.primary("locus review")}
@@ -45241,7 +45885,7 @@ function showHelp2() {
45241
45885
  ${c.dim("$")} ${c.primary('locus discuss "how should we design the auth system?"')}
45242
45886
  ${c.dim("$")} ${c.primary("locus exec sessions list")}
45243
45887
  ${c.dim("$")} ${c.primary("locus artifacts")}
45244
- ${c.dim("$")} ${c.primary("locus artifacts show reduce-cli-terminal-output")}
45888
+ ${c.dim("$")} ${c.primary("locus service install")}
45245
45889
 
45246
45890
  For more information, visit: ${c.underline("https://docs.locusai.dev")}
45247
45891
  `);
@@ -45386,8 +46030,8 @@ init_config_manager();
45386
46030
  init_settings_manager();
45387
46031
  init_utils3();
45388
46032
  init_workspace_resolver();
45389
- import { existsSync as existsSync18, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
45390
- import { join as join18 } from "node:path";
46033
+ import { existsSync as existsSync19, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "node:fs";
46034
+ import { join as join19 } from "node:path";
45391
46035
  import { parseArgs as parseArgs7 } from "node:util";
45392
46036
  async function reviewCommand(args) {
45393
46037
  const subcommand = args[0];
@@ -45526,12 +46170,12 @@ async function reviewLocalCommand(args) {
45526
46170
  `);
45527
46171
  return;
45528
46172
  }
45529
- const reviewsDir = join18(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.reviewsDir);
45530
- if (!existsSync18(reviewsDir)) {
45531
- mkdirSync8(reviewsDir, { recursive: true });
46173
+ const reviewsDir = join19(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.reviewsDir);
46174
+ if (!existsSync19(reviewsDir)) {
46175
+ mkdirSync9(reviewsDir, { recursive: true });
45532
46176
  }
45533
46177
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
45534
- const reportPath = join18(reviewsDir, `review-${timestamp}.md`);
46178
+ const reportPath = join19(reviewsDir, `review-${timestamp}.md`);
45535
46179
  writeFileSync8(reportPath, report, "utf-8");
45536
46180
  console.log(`
45537
46181
  ${c.success("✔")} ${c.success("Review complete!")}`);
@@ -45625,15 +46269,376 @@ ${c.info(`Received ${signal}. Stopping agent and cleaning up...`)}`);
45625
46269
  console.log(` ${c.dim("A PR will be opened when all tasks are done")}`);
45626
46270
  await orchestrator.start();
45627
46271
  }
45628
- // src/commands/telegram.ts
46272
+ // src/commands/service.ts
45629
46273
  init_index_node();
45630
46274
  init_settings_manager();
46275
+ init_utils3();
45631
46276
  import { spawn as spawn4 } from "node:child_process";
45632
- import { existsSync as existsSync19 } from "node:fs";
45633
- import { join as join19 } from "node:path";
45634
- import { createInterface as createInterface4 } from "node:readline";
46277
+ import { existsSync as existsSync20, writeFileSync as writeFileSync9 } from "node:fs";
46278
+ import { homedir as homedir3 } from "node:os";
46279
+ import { join as join20 } from "node:path";
46280
+ var SERVICE_NAME = "locus";
46281
+ var SYSTEMD_UNIT_PATH = `/etc/systemd/system/${SERVICE_NAME}.service`;
46282
+ var PLIST_LABEL = "com.locus.agent";
46283
+ function getPlistPath() {
46284
+ return join20(homedir3(), "Library/LaunchAgents", `${PLIST_LABEL}.plist`);
46285
+ }
46286
+ function showServiceHelp() {
46287
+ console.log(`
46288
+ ${c.header(" SERVICE ")}
46289
+ ${c.primary("locus service")} ${c.dim("<subcommand>")}
46290
+
46291
+ ${c.header(" SUBCOMMANDS ")}
46292
+ ${c.success("install")} Install Locus as a system service
46293
+ ${c.dim("Sets up systemd (Linux) or launchd (macOS)")}
46294
+ ${c.dim("to run the Telegram bot + proposal scheduler")}
46295
+ ${c.success("uninstall")} Remove the system service
46296
+ ${c.success("status")} Check if the service is running
46297
+
46298
+ ${c.header(" EXAMPLES ")}
46299
+ ${c.dim("$")} ${c.primary("locus service install")}
46300
+ ${c.dim("$")} ${c.primary("locus service status")}
46301
+ ${c.dim("$")} ${c.primary("locus service uninstall")}
46302
+ `);
46303
+ }
46304
+ function runShell(cmd, args) {
46305
+ return new Promise((resolve2) => {
46306
+ const proc = spawn4(cmd, args, { stdio: ["pipe", "pipe", "pipe"] });
46307
+ let stdout = "";
46308
+ let stderr = "";
46309
+ proc.stdout?.on("data", (d) => {
46310
+ stdout += d.toString();
46311
+ });
46312
+ proc.stderr?.on("data", (d) => {
46313
+ stderr += d.toString();
46314
+ });
46315
+ proc.on("close", (exitCode) => resolve2({ exitCode, stdout, stderr }));
46316
+ proc.on("error", (err) => resolve2({ exitCode: 1, stdout, stderr: err.message }));
46317
+ });
46318
+ }
46319
+ function generateSystemdUnit(projectPath, user2, binaryPath) {
46320
+ return `[Unit]
46321
+ Description=Locus AI Agent (Telegram bot + proposal scheduler)
46322
+ After=network-online.target
46323
+ Wants=network-online.target
46324
+
46325
+ [Service]
46326
+ Type=simple
46327
+ User=${user2}
46328
+ WorkingDirectory=${projectPath}
46329
+ ExecStart=${binaryPath}
46330
+ Restart=on-failure
46331
+ RestartSec=10
46332
+ Environment=PATH=/usr/local/bin:/usr/bin:/bin:${homedir3()}/.bun/bin:${homedir3()}/.nvm/current/bin:${homedir3()}/.local/bin
46333
+ Environment=HOME=${homedir3()}
46334
+
46335
+ [Install]
46336
+ WantedBy=multi-user.target
46337
+ `;
46338
+ }
46339
+ async function installSystemd(projectPath) {
46340
+ const user2 = process.env.USER || "root";
46341
+ const monorepoEntry = join20(projectPath, "packages/telegram/src/index.ts");
46342
+ const isMonorepo = existsSync20(monorepoEntry);
46343
+ let binaryPath;
46344
+ if (isMonorepo) {
46345
+ const bunPath = (await runShell("which", ["bun"])).stdout.trim() || join20(homedir3(), ".bun/bin/bun");
46346
+ binaryPath = `${bunPath} run ${monorepoEntry}`;
46347
+ } else {
46348
+ const npmGlobalBin = (await runShell("npm", ["bin", "-g"])).stdout.trim();
46349
+ const telegramBin = join20(npmGlobalBin, "locus-telegram");
46350
+ binaryPath = existsSync20(telegramBin) ? telegramBin : "locus-telegram";
46351
+ }
46352
+ const unit = generateSystemdUnit(projectPath, user2, binaryPath);
46353
+ console.log(`
46354
+ ${c.info("▶")} Writing systemd unit to ${c.dim(SYSTEMD_UNIT_PATH)}`);
46355
+ writeFileSync9(SYSTEMD_UNIT_PATH, unit, "utf-8");
46356
+ console.log(` ${c.info("▶")} Reloading systemd daemon...`);
46357
+ await runShell("systemctl", ["daemon-reload"]);
46358
+ console.log(` ${c.info("▶")} Enabling and starting ${SERVICE_NAME}...`);
46359
+ await runShell("systemctl", ["enable", SERVICE_NAME]);
46360
+ const startResult = await runShell("systemctl", ["start", SERVICE_NAME]);
46361
+ if (startResult.exitCode !== 0) {
46362
+ console.error(`
46363
+ ${c.error("✖")} Failed to start service: ${startResult.stderr.trim()}`);
46364
+ console.error(` ${c.dim("Check logs with:")} ${c.primary(`journalctl -u ${SERVICE_NAME} -f`)}`);
46365
+ return;
46366
+ }
46367
+ console.log(`
46368
+ ${c.success("✔")} ${c.bold("Locus service installed and running!")}
46369
+
46370
+ ${c.bold("Service:")} ${SERVICE_NAME}
46371
+ ${c.bold("Unit file:")} ${SYSTEMD_UNIT_PATH}
46372
+
46373
+ ${c.bold("Useful commands:")}
46374
+ ${c.dim("$")} ${c.primary(`sudo systemctl status ${SERVICE_NAME}`)}
46375
+ ${c.dim("$")} ${c.primary(`sudo systemctl restart ${SERVICE_NAME}`)}
46376
+ ${c.dim("$")} ${c.primary(`journalctl -u ${SERVICE_NAME} -f`)}
46377
+ `);
46378
+ }
46379
+ async function uninstallSystemd() {
46380
+ if (!existsSync20(SYSTEMD_UNIT_PATH)) {
46381
+ console.log(`
46382
+ ${c.dim("No systemd service found. Nothing to remove.")}
46383
+ `);
46384
+ return;
46385
+ }
46386
+ console.log(` ${c.info("▶")} Stopping and disabling ${SERVICE_NAME}...`);
46387
+ await runShell("systemctl", ["stop", SERVICE_NAME]);
46388
+ await runShell("systemctl", ["disable", SERVICE_NAME]);
46389
+ const { unlinkSync: unlinkSync6 } = await import("node:fs");
46390
+ unlinkSync6(SYSTEMD_UNIT_PATH);
46391
+ await runShell("systemctl", ["daemon-reload"]);
46392
+ console.log(`
46393
+ ${c.success("✔")} ${c.bold("Locus service removed.")}
46394
+ `);
46395
+ }
46396
+ async function statusSystemd() {
46397
+ const result = await runShell("systemctl", ["is-active", SERVICE_NAME]);
46398
+ const state = result.stdout.trim();
46399
+ if (state === "active") {
46400
+ console.log(`
46401
+ ${c.success("●")} ${c.bold("Locus service is running")} ${c.dim("(systemd)")}
46402
+ `);
46403
+ } else if (existsSync20(SYSTEMD_UNIT_PATH)) {
46404
+ console.log(`
46405
+ ${c.secondary("●")} ${c.bold(`Locus service is ${state}`)} ${c.dim("(systemd)")}
46406
+ `);
46407
+ console.log(` ${c.dim("Start with:")} ${c.primary(`sudo systemctl start ${SERVICE_NAME}`)}
46408
+ `);
46409
+ } else {
46410
+ console.log(`
46411
+ ${c.secondary("●")} ${c.bold("Locus service is not installed")}
46412
+ `);
46413
+ console.log(` ${c.dim("Install with:")} ${c.primary("locus service install")}
46414
+ `);
46415
+ }
46416
+ }
46417
+ function generatePlist(projectPath, binaryPath, binaryArgs) {
46418
+ const argsXml = [binaryPath, ...binaryArgs].map((a) => ` <string>${a}</string>`).join(`
46419
+ `);
46420
+ const logDir = join20(homedir3(), "Library/Logs/Locus");
46421
+ return `<?xml version="1.0" encoding="UTF-8"?>
46422
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
46423
+ <plist version="1.0">
46424
+ <dict>
46425
+ <key>Label</key>
46426
+ <string>${PLIST_LABEL}</string>
46427
+ <key>ProgramArguments</key>
46428
+ <array>
46429
+ ${argsXml}
46430
+ </array>
46431
+ <key>WorkingDirectory</key>
46432
+ <string>${projectPath}</string>
46433
+ <key>RunAtLoad</key>
46434
+ <true/>
46435
+ <key>KeepAlive</key>
46436
+ <true/>
46437
+ <key>StandardOutPath</key>
46438
+ <string>${join20(logDir, "locus.log")}</string>
46439
+ <key>StandardErrorPath</key>
46440
+ <string>${join20(logDir, "locus-error.log")}</string>
46441
+ <key>EnvironmentVariables</key>
46442
+ <dict>
46443
+ <key>PATH</key>
46444
+ <string>/usr/local/bin:/usr/bin:/bin:${homedir3()}/.bun/bin:${homedir3()}/.nvm/current/bin:${homedir3()}/.local/bin</string>
46445
+ </dict>
46446
+ </dict>
46447
+ </plist>
46448
+ `;
46449
+ }
46450
+ async function installLaunchd(projectPath) {
46451
+ const plistPath = getPlistPath();
46452
+ if (existsSync20(plistPath)) {
46453
+ await runShell("launchctl", ["unload", plistPath]);
46454
+ }
46455
+ const monorepoEntry = join20(projectPath, "packages/telegram/src/index.ts");
46456
+ const isMonorepo = existsSync20(monorepoEntry);
46457
+ let binaryPath;
46458
+ let binaryArgs;
46459
+ if (isMonorepo) {
46460
+ const bunPath = (await runShell("which", ["bun"])).stdout.trim() || join20(homedir3(), ".bun/bin/bun");
46461
+ binaryPath = bunPath;
46462
+ binaryArgs = ["run", monorepoEntry];
46463
+ } else {
46464
+ const npmGlobalBin = (await runShell("npm", ["bin", "-g"])).stdout.trim();
46465
+ const telegramBin = join20(npmGlobalBin, "locus-telegram");
46466
+ binaryPath = existsSync20(telegramBin) ? telegramBin : "locus-telegram";
46467
+ binaryArgs = [];
46468
+ }
46469
+ const logDir = join20(homedir3(), "Library/Logs/Locus");
46470
+ const { mkdirSync: mkdirSync10 } = await import("node:fs");
46471
+ mkdirSync10(logDir, { recursive: true });
46472
+ const launchAgentsDir = join20(homedir3(), "Library/LaunchAgents");
46473
+ mkdirSync10(launchAgentsDir, { recursive: true });
46474
+ const plist = generatePlist(projectPath, binaryPath, binaryArgs);
46475
+ console.log(`
46476
+ ${c.info("▶")} Writing plist to ${c.dim(plistPath)}`);
46477
+ writeFileSync9(plistPath, plist, "utf-8");
46478
+ console.log(` ${c.info("▶")} Loading service...`);
46479
+ const loadResult = await runShell("launchctl", ["load", plistPath]);
46480
+ if (loadResult.exitCode !== 0) {
46481
+ console.error(`
46482
+ ${c.error("✖")} Failed to load service: ${loadResult.stderr.trim()}`);
46483
+ return;
46484
+ }
46485
+ const logPath = join20(logDir, "locus.log");
46486
+ console.log(`
46487
+ ${c.success("✔")} ${c.bold("Locus service installed and running!")}
46488
+
46489
+ ${c.bold("Plist:")} ${plistPath}
46490
+ ${c.bold("Logs:")} ${logPath}
46491
+
46492
+ ${c.bold("Useful commands:")}
46493
+ ${c.dim("$")} ${c.primary(`launchctl list | grep ${PLIST_LABEL}`)}
46494
+ ${c.dim("$")} ${c.primary(`tail -f ${logPath}`)}
46495
+ `);
46496
+ }
46497
+ async function uninstallLaunchd() {
46498
+ const plistPath = getPlistPath();
46499
+ if (!existsSync20(plistPath)) {
46500
+ console.log(`
46501
+ ${c.dim("No launchd service found. Nothing to remove.")}
46502
+ `);
46503
+ return;
46504
+ }
46505
+ console.log(` ${c.info("▶")} Unloading service...`);
46506
+ await runShell("launchctl", ["unload", plistPath]);
46507
+ const { unlinkSync: unlinkSync6 } = await import("node:fs");
46508
+ unlinkSync6(plistPath);
46509
+ console.log(`
46510
+ ${c.success("✔")} ${c.bold("Locus service removed.")}
46511
+ `);
46512
+ }
46513
+ async function statusLaunchd() {
46514
+ const plistPath = getPlistPath();
46515
+ if (!existsSync20(plistPath)) {
46516
+ console.log(`
46517
+ ${c.secondary("●")} ${c.bold("Locus service is not installed")}
46518
+ `);
46519
+ console.log(` ${c.dim("Install with:")} ${c.primary("locus service install")}
46520
+ `);
46521
+ return;
46522
+ }
46523
+ const result = await runShell("launchctl", ["list"]);
46524
+ const lines = result.stdout.split(`
46525
+ `);
46526
+ const match = lines.find((l) => l.includes(PLIST_LABEL));
46527
+ if (match) {
46528
+ const parts = match.trim().split(/\s+/);
46529
+ const pid = parts[0] === "-" ? null : parts[0];
46530
+ if (pid) {
46531
+ console.log(`
46532
+ ${c.success("●")} ${c.bold("Locus service is running")} ${c.dim(`(PID ${pid}, launchd)`)}
46533
+ `);
46534
+ } else {
46535
+ console.log(`
46536
+ ${c.secondary("●")} ${c.bold("Locus service is stopped")} ${c.dim("(launchd)")}
46537
+ `);
46538
+ console.log(` ${c.dim("Start with:")} ${c.primary(`launchctl load ${plistPath}`)}
46539
+ `);
46540
+ }
46541
+ } else {
46542
+ console.log(`
46543
+ ${c.secondary("●")} ${c.bold("Locus service is not loaded")} ${c.dim("(plist exists but not loaded)")}
46544
+ `);
46545
+ console.log(` ${c.dim("Load with:")} ${c.primary(`launchctl load ${plistPath}`)}
46546
+ `);
46547
+ }
46548
+ }
46549
+ function getPlatform() {
46550
+ if (process.platform === "linux")
46551
+ return "linux";
46552
+ if (process.platform === "darwin")
46553
+ return "darwin";
46554
+ return null;
46555
+ }
46556
+ async function installCommand(projectPath) {
46557
+ const platform = getPlatform();
46558
+ if (!platform) {
46559
+ console.error(`
46560
+ ${c.error("✖")} ${c.bold(`Unsupported platform: ${process.platform}`)}
46561
+ Service management is supported on Linux (systemd) and macOS (launchd).
46562
+ `);
46563
+ process.exit(1);
46564
+ }
46565
+ const manager = new SettingsManager(projectPath);
46566
+ const settings = manager.load();
46567
+ if (!settings.telegram?.botToken || !settings.telegram?.chatId) {
46568
+ console.error(`
46569
+ ${c.error("✖")} ${c.bold("Telegram is not configured.")}
46570
+ Run ${c.primary("locus telegram setup")} first.
46571
+ `);
46572
+ process.exit(1);
46573
+ }
46574
+ if (!settings.apiKey) {
46575
+ console.error(`
46576
+ ${c.error("✖")} ${c.bold("API key is not configured.")}
46577
+ Run ${c.primary("locus config setup --api-key <key>")} first.
46578
+ `);
46579
+ process.exit(1);
46580
+ }
46581
+ if (platform === "linux") {
46582
+ await installSystemd(projectPath);
46583
+ } else {
46584
+ await installLaunchd(projectPath);
46585
+ }
46586
+ }
46587
+ async function uninstallCommand() {
46588
+ const platform = getPlatform();
46589
+ if (!platform) {
46590
+ console.error(`
46591
+ ${c.error("✖")} Unsupported platform: ${process.platform}
46592
+ `);
46593
+ process.exit(1);
46594
+ }
46595
+ if (platform === "linux") {
46596
+ await uninstallSystemd();
46597
+ } else {
46598
+ await uninstallLaunchd();
46599
+ }
46600
+ }
46601
+ async function statusCommandHandler() {
46602
+ const platform = getPlatform();
46603
+ if (!platform) {
46604
+ console.error(`
46605
+ ${c.error("✖")} Unsupported platform: ${process.platform}
46606
+ `);
46607
+ process.exit(1);
46608
+ }
46609
+ if (platform === "linux") {
46610
+ await statusSystemd();
46611
+ } else {
46612
+ await statusLaunchd();
46613
+ }
46614
+ }
46615
+ async function serviceCommand(args) {
46616
+ const projectPath = process.cwd();
46617
+ requireInitialization(projectPath, "service");
46618
+ const subcommand = args[0];
46619
+ switch (subcommand) {
46620
+ case "install":
46621
+ await installCommand(projectPath);
46622
+ break;
46623
+ case "uninstall":
46624
+ await uninstallCommand();
46625
+ break;
46626
+ case "status":
46627
+ await statusCommandHandler();
46628
+ break;
46629
+ default:
46630
+ showServiceHelp();
46631
+ }
46632
+ }
46633
+ // src/commands/telegram.ts
46634
+ init_index_node();
46635
+ init_settings_manager();
46636
+ import { spawn as spawn5 } from "node:child_process";
46637
+ import { existsSync as existsSync21 } from "node:fs";
46638
+ import { join as join21 } from "node:path";
46639
+ import { createInterface as createInterface2 } from "node:readline";
45635
46640
  function ask2(question) {
45636
- const rl = createInterface4({
46641
+ const rl = createInterface2({
45637
46642
  input: process.stdin,
45638
46643
  output: process.stdout
45639
46644
  });
@@ -45748,7 +46753,8 @@ async function setupCommand2(args, projectPath) {
45748
46753
  ${c.primary("Chat ID:")} ${parsedChatId}
45749
46754
 
45750
46755
  ${c.bold("Next steps:")}
45751
- Start the bot with: ${c.primary("locus telegram run")}
46756
+ Install as service: ${c.primary("locus service install")}
46757
+ Or run manually: ${c.primary("locus telegram run")}
45752
46758
  `);
45753
46759
  }
45754
46760
  function configCommand2(projectPath) {
@@ -45864,8 +46870,8 @@ function runBotCommand(projectPath) {
45864
46870
  `);
45865
46871
  process.exit(1);
45866
46872
  }
45867
- const monorepoTelegramEntry = join19(projectPath, "packages/telegram/src/index.ts");
45868
- const isMonorepo = existsSync19(monorepoTelegramEntry);
46873
+ const monorepoTelegramEntry = join21(projectPath, "packages/telegram/src/index.ts");
46874
+ const isMonorepo = existsSync21(monorepoTelegramEntry);
45869
46875
  let cmd;
45870
46876
  let args;
45871
46877
  if (isMonorepo) {
@@ -45876,7 +46882,7 @@ function runBotCommand(projectPath) {
45876
46882
  args = [];
45877
46883
  }
45878
46884
  const env = { ...process.env };
45879
- const child = spawn4(cmd, args, {
46885
+ const child = spawn5(cmd, args, {
45880
46886
  cwd: projectPath,
45881
46887
  stdio: "inherit",
45882
46888
  env
@@ -46061,6 +47067,9 @@ async function main() {
46061
47067
  case "config":
46062
47068
  await configCommand(args);
46063
47069
  break;
47070
+ case "service":
47071
+ await serviceCommand(args);
47072
+ break;
46064
47073
  case "docs":
46065
47074
  await docsCommand(args);
46066
47075
  break;
@@ -46089,3 +47098,11 @@ main().catch((err) => {
46089
47098
  ${c.error("✖ Fatal Error")} ${c.red(err.message)}`);
46090
47099
  process.exit(1);
46091
47100
  });
47101
+
47102
+ // index.ts
47103
+ var _emit = process.emit;
47104
+ process.emit = function(name, data, ...args) {
47105
+ if (name === "warning" && data?.code === "DEP0040")
47106
+ return false;
47107
+ return _emit.apply(process, [name, data, ...args]);
47108
+ };