@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/agent/worker.js +109 -4
- package/bin/locus.js +1160 -143
- package/package.json +4 -3
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
|
-
|
|
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.
|
|
43542
|
-
|
|
43543
|
-
|
|
43544
|
-
|
|
43545
|
-
|
|
43546
|
-
|
|
43547
|
-
|
|
43548
|
-
|
|
43549
|
-
|
|
43550
|
-
|
|
43551
|
-
|
|
43552
|
-
|
|
43553
|
-
|
|
43554
|
-
|
|
43555
|
-
|
|
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
|
-
|
|
43561
|
-
|
|
43562
|
-
|
|
43563
|
-
|
|
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
|
-
|
|
43568
|
-
this.
|
|
43569
|
-
|
|
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.
|
|
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
|
-
|
|
43592
|
-
this.readline.prompt();
|
|
44188
|
+
this.inputHandler?.showPrompt();
|
|
43593
44189
|
return;
|
|
43594
44190
|
}
|
|
43595
44191
|
}
|
|
43596
44192
|
await this.executePrompt(trimmed);
|
|
43597
|
-
this.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
44903
|
+
inputHandler.stop();
|
|
44301
44904
|
process.exit(0);
|
|
44302
44905
|
};
|
|
44303
|
-
|
|
44304
|
-
|
|
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 === ""
|
|
44320
|
-
|
|
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 (
|
|
44339
|
-
|
|
44340
|
-
const
|
|
44341
|
-
|
|
44342
|
-
|
|
44343
|
-
|
|
44344
|
-
|
|
44345
|
-
|
|
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
|
-
|
|
44348
|
-
|
|
44939
|
+
process.stdout.write(summary);
|
|
44940
|
+
process.stdout.write(`
|
|
44349
44941
|
`);
|
|
44350
|
-
|
|
44351
|
-
|
|
44352
|
-
|
|
44353
|
-
|
|
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
|
-
|
|
44948
|
+
console.log(` ${c.dim("Messages:")} ${discussion2.messages.length} ${c.dim("Insights:")} ${discussion2.insights.length}
|
|
44357
44949
|
`);
|
|
44358
|
-
|
|
44359
|
-
|
|
44360
|
-
|
|
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
|
-
|
|
44363
|
-
|
|
44364
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
45390
|
-
import { join as
|
|
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 =
|
|
45530
|
-
if (!
|
|
45531
|
-
|
|
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 =
|
|
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/
|
|
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
|
|
45633
|
-
import {
|
|
45634
|
-
import {
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
45868
|
-
const isMonorepo =
|
|
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 =
|
|
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
|
+
};
|