@locusai/cli 0.15.3 → 0.15.5
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 +1824 -1764
- package/package.json +4 -4
package/bin/locus.js
CHANGED
|
@@ -43451,9 +43451,9 @@ var init_progress_renderer = __esm(() => {
|
|
|
43451
43451
|
});
|
|
43452
43452
|
|
|
43453
43453
|
// src/repl/image-detect.ts
|
|
43454
|
-
import { copyFileSync, existsSync as
|
|
43455
|
-
import { homedir as
|
|
43456
|
-
import { basename as basename2, join as
|
|
43454
|
+
import { copyFileSync, existsSync as existsSync20, mkdirSync as mkdirSync9 } from "node:fs";
|
|
43455
|
+
import { homedir as homedir4, tmpdir as tmpdir2 } from "node:os";
|
|
43456
|
+
import { basename as basename2, join as join20 } from "node:path";
|
|
43457
43457
|
function hasImageExtension(p) {
|
|
43458
43458
|
const dot = p.lastIndexOf(".");
|
|
43459
43459
|
if (dot === -1)
|
|
@@ -43466,16 +43466,16 @@ function resolvePath(raw) {
|
|
|
43466
43466
|
p = p.slice(1, -1);
|
|
43467
43467
|
}
|
|
43468
43468
|
if (p.startsWith("~/")) {
|
|
43469
|
-
p =
|
|
43469
|
+
p = homedir4() + p.slice(1);
|
|
43470
43470
|
}
|
|
43471
43471
|
return p;
|
|
43472
43472
|
}
|
|
43473
43473
|
function copyToStable(srcPath) {
|
|
43474
43474
|
try {
|
|
43475
|
-
|
|
43475
|
+
mkdirSync9(STABLE_IMAGE_DIR, { recursive: true });
|
|
43476
43476
|
const ts = Date.now();
|
|
43477
43477
|
const name = `${ts}-${basename2(srcPath)}`;
|
|
43478
|
-
const destPath =
|
|
43478
|
+
const destPath = join20(STABLE_IMAGE_DIR, name);
|
|
43479
43479
|
copyFileSync(srcPath, destPath);
|
|
43480
43480
|
return destPath;
|
|
43481
43481
|
} catch {
|
|
@@ -43495,7 +43495,7 @@ function detectImages(input) {
|
|
|
43495
43495
|
let exists = false;
|
|
43496
43496
|
let stablePath = normalized;
|
|
43497
43497
|
try {
|
|
43498
|
-
exists =
|
|
43498
|
+
exists = existsSync20(normalized);
|
|
43499
43499
|
} catch {}
|
|
43500
43500
|
if (exists) {
|
|
43501
43501
|
const copied = copyToStable(normalized);
|
|
@@ -43569,7 +43569,7 @@ var init_image_detect = __esm(() => {
|
|
|
43569
43569
|
".tif",
|
|
43570
43570
|
".tiff"
|
|
43571
43571
|
]);
|
|
43572
|
-
STABLE_IMAGE_DIR =
|
|
43572
|
+
STABLE_IMAGE_DIR = join20(tmpdir2(), "locus-images");
|
|
43573
43573
|
});
|
|
43574
43574
|
|
|
43575
43575
|
// src/repl/input-handler.ts
|
|
@@ -44783,1954 +44783,2034 @@ async function configCommand(args) {
|
|
|
44783
44783
|
showConfigHelp();
|
|
44784
44784
|
}
|
|
44785
44785
|
}
|
|
44786
|
-
// src/commands/
|
|
44786
|
+
// src/commands/daemon.ts
|
|
44787
44787
|
init_index_node();
|
|
44788
|
-
init_progress_renderer();
|
|
44789
|
-
init_image_detect();
|
|
44790
|
-
init_input_handler();
|
|
44791
44788
|
init_settings_manager();
|
|
44792
44789
|
init_utils3();
|
|
44793
|
-
import {
|
|
44794
|
-
|
|
44795
|
-
|
|
44796
|
-
|
|
44797
|
-
|
|
44798
|
-
|
|
44799
|
-
|
|
44800
|
-
|
|
44801
|
-
|
|
44802
|
-
|
|
44803
|
-
|
|
44804
|
-
|
|
44805
|
-
|
|
44806
|
-
|
|
44807
|
-
|
|
44808
|
-
|
|
44790
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, unlinkSync as unlinkSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
44791
|
+
import { homedir as homedir3 } from "node:os";
|
|
44792
|
+
import { join as join19 } from "node:path";
|
|
44793
|
+
|
|
44794
|
+
// src/utils/process.ts
|
|
44795
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
44796
|
+
import { existsSync as existsSync18, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "node:fs";
|
|
44797
|
+
import { homedir as homedir2 } from "node:os";
|
|
44798
|
+
import { dirname as dirname4, join as join18 } from "node:path";
|
|
44799
|
+
function runShell(cmd, args) {
|
|
44800
|
+
return new Promise((resolve2) => {
|
|
44801
|
+
const proc = spawn4(cmd, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
44802
|
+
let stdout = "";
|
|
44803
|
+
let stderr = "";
|
|
44804
|
+
proc.stdout?.on("data", (d) => {
|
|
44805
|
+
stdout += d.toString();
|
|
44806
|
+
});
|
|
44807
|
+
proc.stderr?.on("data", (d) => {
|
|
44808
|
+
stderr += d.toString();
|
|
44809
|
+
});
|
|
44810
|
+
proc.on("close", (exitCode) => resolve2({ exitCode, stdout, stderr }));
|
|
44811
|
+
proc.on("error", (err) => resolve2({ exitCode: 1, stdout, stderr: err.message }));
|
|
44809
44812
|
});
|
|
44810
|
-
|
|
44811
|
-
|
|
44812
|
-
const
|
|
44813
|
-
|
|
44814
|
-
|
|
44813
|
+
}
|
|
44814
|
+
async function findTelegramBinary() {
|
|
44815
|
+
const result = await runShell("which", ["locus-telegram"]);
|
|
44816
|
+
const p = result.stdout.trim();
|
|
44817
|
+
return p?.startsWith?.("/") ? p : null;
|
|
44818
|
+
}
|
|
44819
|
+
async function findBinDir(binary) {
|
|
44820
|
+
const result = await runShell("which", [binary]);
|
|
44821
|
+
const p = result.stdout.trim();
|
|
44822
|
+
if (p?.startsWith?.("/"))
|
|
44823
|
+
return dirname4(p);
|
|
44824
|
+
return null;
|
|
44825
|
+
}
|
|
44826
|
+
function resolveNvmBinDir() {
|
|
44827
|
+
const nvmDir = process.env.NVM_DIR || join18(homedir2(), ".nvm");
|
|
44828
|
+
const versionsDir = join18(nvmDir, "versions", "node");
|
|
44829
|
+
if (!existsSync18(versionsDir))
|
|
44830
|
+
return null;
|
|
44831
|
+
let versions2;
|
|
44832
|
+
try {
|
|
44833
|
+
versions2 = readdirSync6(versionsDir).filter((d) => d.startsWith("v"));
|
|
44834
|
+
} catch {
|
|
44835
|
+
return null;
|
|
44815
44836
|
}
|
|
44816
|
-
if (
|
|
44817
|
-
return
|
|
44837
|
+
if (versions2.length === 0)
|
|
44838
|
+
return null;
|
|
44839
|
+
const currentNodeVersion = `v${process.versions.node}`;
|
|
44840
|
+
const currentBin = join18(versionsDir, currentNodeVersion, "bin");
|
|
44841
|
+
if (versions2.includes(currentNodeVersion) && existsSync18(currentBin)) {
|
|
44842
|
+
return currentBin;
|
|
44818
44843
|
}
|
|
44819
|
-
|
|
44820
|
-
|
|
44844
|
+
const aliasPath = join18(nvmDir, "alias", "default");
|
|
44845
|
+
if (existsSync18(aliasPath)) {
|
|
44846
|
+
try {
|
|
44847
|
+
const alias = readFileSync15(aliasPath, "utf-8").trim();
|
|
44848
|
+
const match = versions2.find((v) => v === `v${alias}` || v.startsWith(`v${alias}.`));
|
|
44849
|
+
if (match) {
|
|
44850
|
+
const bin2 = join18(versionsDir, match, "bin");
|
|
44851
|
+
if (existsSync18(bin2))
|
|
44852
|
+
return bin2;
|
|
44853
|
+
}
|
|
44854
|
+
} catch {}
|
|
44821
44855
|
}
|
|
44822
|
-
|
|
44823
|
-
|
|
44856
|
+
const sorted = versions2.sort((a, b) => {
|
|
44857
|
+
const pa = a.slice(1).split(".").map(Number);
|
|
44858
|
+
const pb = b.slice(1).split(".").map(Number);
|
|
44859
|
+
for (let i = 0;i < 3; i++) {
|
|
44860
|
+
if ((pa[i] || 0) !== (pb[i] || 0))
|
|
44861
|
+
return (pb[i] || 0) - (pa[i] || 0);
|
|
44862
|
+
}
|
|
44863
|
+
return 0;
|
|
44864
|
+
});
|
|
44865
|
+
const bin = join18(versionsDir, sorted[0], "bin");
|
|
44866
|
+
return existsSync18(bin) ? bin : null;
|
|
44867
|
+
}
|
|
44868
|
+
async function buildServicePath() {
|
|
44869
|
+
const home = homedir2();
|
|
44870
|
+
const dirs = new Set;
|
|
44871
|
+
dirs.add("/usr/local/bin");
|
|
44872
|
+
dirs.add("/usr/bin");
|
|
44873
|
+
dirs.add("/bin");
|
|
44874
|
+
const candidates = [
|
|
44875
|
+
join18(home, ".bun", "bin"),
|
|
44876
|
+
join18(home, ".local", "bin"),
|
|
44877
|
+
join18(home, ".npm", "bin"),
|
|
44878
|
+
join18(home, ".npm-global", "bin"),
|
|
44879
|
+
join18(home, ".yarn", "bin")
|
|
44880
|
+
];
|
|
44881
|
+
for (const d of candidates) {
|
|
44882
|
+
if (existsSync18(d))
|
|
44883
|
+
dirs.add(d);
|
|
44824
44884
|
}
|
|
44825
|
-
const
|
|
44826
|
-
if (
|
|
44827
|
-
|
|
44885
|
+
const nvmBin = resolveNvmBinDir();
|
|
44886
|
+
if (nvmBin)
|
|
44887
|
+
dirs.add(nvmBin);
|
|
44888
|
+
const fnmCurrent = join18(home, ".fnm", "current", "bin");
|
|
44889
|
+
if (existsSync18(fnmCurrent))
|
|
44890
|
+
dirs.add(fnmCurrent);
|
|
44891
|
+
for (const bin of ["claude", "codex"]) {
|
|
44892
|
+
const dir = await findBinDir(bin);
|
|
44893
|
+
if (dir)
|
|
44894
|
+
dirs.add(dir);
|
|
44895
|
+
}
|
|
44896
|
+
return Array.from(dirs).join(":");
|
|
44897
|
+
}
|
|
44898
|
+
var SERVICE_NAME = "locus";
|
|
44899
|
+
var SYSTEMD_UNIT_PATH = `/etc/systemd/system/${SERVICE_NAME}.service`;
|
|
44900
|
+
var PLIST_LABEL = "com.locus.agent";
|
|
44901
|
+
function getPlistPath() {
|
|
44902
|
+
return join18(homedir2(), "Library/LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
44903
|
+
}
|
|
44904
|
+
function getPlatform() {
|
|
44905
|
+
if (process.platform === "linux")
|
|
44906
|
+
return "linux";
|
|
44907
|
+
if (process.platform === "darwin")
|
|
44908
|
+
return "darwin";
|
|
44909
|
+
return null;
|
|
44910
|
+
}
|
|
44911
|
+
async function killOrphanedProcesses() {
|
|
44912
|
+
const result = await runShell("pgrep", ["-f", "locus-telegram"]);
|
|
44913
|
+
const pids = result.stdout.trim().split(`
|
|
44914
|
+
`).filter((p) => p.length > 0);
|
|
44915
|
+
if (pids.length === 0)
|
|
44828
44916
|
return;
|
|
44917
|
+
console.log(` Killing ${pids.length} orphaned locus-telegram process${pids.length > 1 ? "es" : ""}...`);
|
|
44918
|
+
await runShell("pkill", ["-f", "locus-telegram"]);
|
|
44919
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2000));
|
|
44920
|
+
const check2 = await runShell("pgrep", ["-f", "locus-telegram"]);
|
|
44921
|
+
if (check2.stdout.trim().length > 0) {
|
|
44922
|
+
await runShell("pkill", ["-9", "-f", "locus-telegram"]);
|
|
44829
44923
|
}
|
|
44830
|
-
|
|
44831
|
-
|
|
44832
|
-
const
|
|
44833
|
-
|
|
44834
|
-
|
|
44835
|
-
|
|
44836
|
-
|
|
44837
|
-
|
|
44838
|
-
|
|
44839
|
-
|
|
44840
|
-
|
|
44841
|
-
|
|
44842
|
-
|
|
44843
|
-
|
|
44844
|
-
|
|
44845
|
-
|
|
44846
|
-
|
|
44847
|
-
|
|
44848
|
-
|
|
44849
|
-
|
|
44850
|
-
|
|
44924
|
+
}
|
|
44925
|
+
async function isDaemonRunning() {
|
|
44926
|
+
const platform = getPlatform();
|
|
44927
|
+
if (platform === "linux") {
|
|
44928
|
+
const result = await runShell("systemctl", ["is-active", SERVICE_NAME]);
|
|
44929
|
+
return result.stdout.trim() === "active";
|
|
44930
|
+
}
|
|
44931
|
+
if (platform === "darwin") {
|
|
44932
|
+
const plistPath = getPlistPath();
|
|
44933
|
+
if (!existsSync18(plistPath))
|
|
44934
|
+
return false;
|
|
44935
|
+
const result = await runShell("launchctl", ["list"]);
|
|
44936
|
+
const match = result.stdout.split(`
|
|
44937
|
+
`).find((l) => l.includes(PLIST_LABEL));
|
|
44938
|
+
if (!match)
|
|
44939
|
+
return false;
|
|
44940
|
+
const pid = match.trim().split(/\s+/)[0];
|
|
44941
|
+
return pid !== "-";
|
|
44942
|
+
}
|
|
44943
|
+
return false;
|
|
44944
|
+
}
|
|
44945
|
+
async function restartDaemonIfRunning() {
|
|
44946
|
+
const platform = getPlatform();
|
|
44947
|
+
if (!platform)
|
|
44948
|
+
return false;
|
|
44949
|
+
const running = await isDaemonRunning();
|
|
44950
|
+
if (!running)
|
|
44951
|
+
return false;
|
|
44952
|
+
if (platform === "linux") {
|
|
44953
|
+
const result = await runShell("systemctl", ["restart", SERVICE_NAME]);
|
|
44954
|
+
return result.exitCode === 0;
|
|
44955
|
+
}
|
|
44956
|
+
if (platform === "darwin") {
|
|
44957
|
+
const plistPath = getPlistPath();
|
|
44958
|
+
await runShell("launchctl", ["unload", plistPath]);
|
|
44959
|
+
const result = await runShell("launchctl", ["load", plistPath]);
|
|
44960
|
+
return result.exitCode === 0;
|
|
44961
|
+
}
|
|
44962
|
+
return false;
|
|
44963
|
+
}
|
|
44964
|
+
|
|
44965
|
+
// src/commands/daemon.ts
|
|
44966
|
+
function showDaemonHelp() {
|
|
44851
44967
|
console.log(`
|
|
44852
|
-
${c.header("
|
|
44853
|
-
|
|
44854
|
-
|
|
44855
|
-
|
|
44856
|
-
|
|
44857
|
-
|
|
44858
|
-
|
|
44859
|
-
|
|
44860
|
-
|
|
44861
|
-
const result = await facilitator.startDiscussion(topic);
|
|
44862
|
-
renderer.showThinkingStopped();
|
|
44863
|
-
discussionId = result.discussion.id;
|
|
44864
|
-
process.stdout.write(`
|
|
44865
|
-
`);
|
|
44866
|
-
process.stdout.write(result.message);
|
|
44867
|
-
process.stdout.write(`
|
|
44968
|
+
${c.header(" DAEMON ")}
|
|
44969
|
+
${c.primary("locus daemon")} ${c.dim("<subcommand>")}
|
|
44970
|
+
|
|
44971
|
+
${c.header(" SUBCOMMANDS ")}
|
|
44972
|
+
${c.success("start")} Install and start Locus as a background service
|
|
44973
|
+
${c.dim("Sets up systemd (Linux) or launchd (macOS)")}
|
|
44974
|
+
${c.success("stop")} Stop and remove the background service
|
|
44975
|
+
${c.success("restart")} Restart the background service
|
|
44976
|
+
${c.success("status")} Check if the service is running
|
|
44868
44977
|
|
|
44978
|
+
${c.header(" EXAMPLES ")}
|
|
44979
|
+
${c.dim("$")} ${c.primary("locus daemon start")}
|
|
44980
|
+
${c.dim("$")} ${c.primary("locus daemon status")}
|
|
44981
|
+
${c.dim("$")} ${c.primary("locus daemon restart")}
|
|
44982
|
+
${c.dim("$")} ${c.primary("locus daemon stop")}
|
|
44869
44983
|
`);
|
|
44870
|
-
|
|
44871
|
-
|
|
44872
|
-
|
|
44984
|
+
}
|
|
44985
|
+
async function resolveBinaries() {
|
|
44986
|
+
const binaryPath = await findTelegramBinary();
|
|
44987
|
+
if (!binaryPath) {
|
|
44873
44988
|
console.error(`
|
|
44874
|
-
${c.error("✖")} ${c.
|
|
44989
|
+
${c.error("✖")} ${c.bold("Could not find locus-telegram binary.")}
|
|
44990
|
+
` + ` Install with: ${c.primary("npm install -g @locusai/telegram")}
|
|
44875
44991
|
`);
|
|
44876
44992
|
process.exit(1);
|
|
44877
44993
|
}
|
|
44878
|
-
|
|
44879
|
-
|
|
44994
|
+
for (const bin of ["claude", "codex"]) {
|
|
44995
|
+
if (!await findBinDir(bin)) {
|
|
44996
|
+
console.warn(`
|
|
44997
|
+
${c.secondary("⚠")} ${c.bold(`Could not find '${bin}' CLI in PATH.`)}
|
|
44998
|
+
` + ` The service may need it to execute tasks.
|
|
44880
44999
|
`);
|
|
44881
|
-
let isProcessing = false;
|
|
44882
|
-
let interrupted = false;
|
|
44883
|
-
const shutdown = () => {
|
|
44884
|
-
if (isProcessing) {
|
|
44885
|
-
aiRunner.abort();
|
|
44886
|
-
}
|
|
44887
|
-
console.log(`
|
|
44888
|
-
${c.dim("Discussion saved.")} ${c.dim("ID:")} ${c.cyan(discussionId)}`);
|
|
44889
|
-
console.log(c.dim(`
|
|
44890
|
-
Goodbye!
|
|
44891
|
-
`));
|
|
44892
|
-
inputHandler.stop();
|
|
44893
|
-
process.exit(0);
|
|
44894
|
-
};
|
|
44895
|
-
const handleSubmit = async (input) => {
|
|
44896
|
-
interrupted = false;
|
|
44897
|
-
const trimmed = input.trim();
|
|
44898
|
-
if (trimmed === "") {
|
|
44899
|
-
inputHandler.showPrompt();
|
|
44900
|
-
return;
|
|
44901
45000
|
}
|
|
44902
|
-
|
|
44903
|
-
|
|
44904
|
-
|
|
44905
|
-
|
|
44906
|
-
|
|
44907
|
-
|
|
44908
|
-
|
|
44909
|
-
|
|
44910
|
-
|
|
44911
|
-
|
|
44912
|
-
return;
|
|
44913
|
-
}
|
|
44914
|
-
if (lowerInput === "insights") {
|
|
44915
|
-
showCurrentInsights(discussionManager, discussionId);
|
|
44916
|
-
inputHandler.showPrompt();
|
|
44917
|
-
return;
|
|
44918
|
-
}
|
|
44919
|
-
if (lowerInput === "summary") {
|
|
44920
|
-
isProcessing = true;
|
|
44921
|
-
const summaryRenderer = new ProgressRenderer;
|
|
44922
|
-
try {
|
|
44923
|
-
summaryRenderer.showThinkingStarted();
|
|
44924
|
-
const summary = await facilitator.summarizeDiscussion(discussionId);
|
|
44925
|
-
summaryRenderer.showThinkingStopped();
|
|
44926
|
-
process.stdout.write(`
|
|
45001
|
+
}
|
|
45002
|
+
const servicePath = await buildServicePath();
|
|
45003
|
+
return { binaryPath, servicePath };
|
|
45004
|
+
}
|
|
45005
|
+
function requirePlatform() {
|
|
45006
|
+
const platform = getPlatform();
|
|
45007
|
+
if (!platform) {
|
|
45008
|
+
console.error(`
|
|
45009
|
+
${c.error("✖")} ${c.bold(`Unsupported platform: ${process.platform}`)}
|
|
45010
|
+
` + ` Daemon management is supported on Linux (systemd) and macOS (launchd).
|
|
44927
45011
|
`);
|
|
44928
|
-
|
|
44929
|
-
|
|
45012
|
+
process.exit(1);
|
|
45013
|
+
}
|
|
45014
|
+
return platform;
|
|
45015
|
+
}
|
|
45016
|
+
function validateConfig(projectPath) {
|
|
45017
|
+
const manager = new SettingsManager(projectPath);
|
|
45018
|
+
const settings = manager.load();
|
|
45019
|
+
if (!settings.telegram?.botToken || !settings.telegram?.chatId) {
|
|
45020
|
+
console.error(`
|
|
45021
|
+
${c.error("✖")} ${c.bold("Telegram is not configured.")}
|
|
45022
|
+
` + ` Run ${c.primary("locus telegram setup")} first.
|
|
44930
45023
|
`);
|
|
44931
|
-
|
|
44932
|
-
|
|
44933
|
-
|
|
44934
|
-
|
|
44935
|
-
${c.
|
|
44936
|
-
`)
|
|
44937
|
-
console.log(` ${c.dim("Messages:")} ${discussion2.messages.length} ${c.dim("Insights:")} ${discussion2.insights.length}
|
|
44938
|
-
`);
|
|
44939
|
-
}
|
|
44940
|
-
console.log(` ${c.dim("To review:")} ${c.cyan(`locus discuss --show ${discussionId}`)}`);
|
|
44941
|
-
console.log(` ${c.dim("To list all:")} ${c.cyan("locus discuss --list")}
|
|
44942
|
-
`);
|
|
44943
|
-
} catch (error48) {
|
|
44944
|
-
summaryRenderer.finalize();
|
|
44945
|
-
console.error(`
|
|
44946
|
-
${c.error("✖")} ${c.red("Failed to summarize:")} ${error48 instanceof Error ? error48.message : String(error48)}
|
|
44947
|
-
`);
|
|
44948
|
-
}
|
|
44949
|
-
inputHandler.stop();
|
|
44950
|
-
process.exit(0);
|
|
44951
|
-
return;
|
|
44952
|
-
}
|
|
44953
|
-
}
|
|
44954
|
-
const images = detectImages(trimmed);
|
|
44955
|
-
if (images.length > 0) {
|
|
44956
|
-
for (const img of images) {
|
|
44957
|
-
const status = img.exists ? c.success("attached") : c.warning("not found");
|
|
44958
|
-
process.stdout.write(` ${c.cyan(`[Image: ${imageDisplayName(img.path)}]`)} ${status}\r
|
|
44959
|
-
`);
|
|
44960
|
-
}
|
|
44961
|
-
}
|
|
44962
|
-
const cleanedInput = stripImagePaths(trimmed, images);
|
|
44963
|
-
const effectiveInput = cleanedInput + buildImageContext(images);
|
|
44964
|
-
isProcessing = true;
|
|
44965
|
-
const chunkRenderer = new ProgressRenderer;
|
|
44966
|
-
try {
|
|
44967
|
-
chunkRenderer.showThinkingStarted();
|
|
44968
|
-
const stream4 = facilitator.continueDiscussionStream(discussionId, effectiveInput);
|
|
44969
|
-
let result = {
|
|
44970
|
-
response: "",
|
|
44971
|
-
insights: []
|
|
44972
|
-
};
|
|
44973
|
-
let iterResult = await stream4.next();
|
|
44974
|
-
while (!iterResult.done) {
|
|
44975
|
-
chunkRenderer.renderChunk(iterResult.value);
|
|
44976
|
-
iterResult = await stream4.next();
|
|
44977
|
-
}
|
|
44978
|
-
result = iterResult.value;
|
|
44979
|
-
chunkRenderer.finalize();
|
|
44980
|
-
if (result.insights.length > 0) {
|
|
44981
|
-
console.log("");
|
|
44982
|
-
for (const insight of result.insights) {
|
|
44983
|
-
const tag = formatInsightTag(insight.type);
|
|
44984
|
-
console.log(` ${tag} ${c.bold(insight.title)}`);
|
|
44985
|
-
console.log(` ${c.dim(insight.content)}
|
|
44986
|
-
`);
|
|
44987
|
-
}
|
|
44988
|
-
}
|
|
44989
|
-
} catch (error48) {
|
|
44990
|
-
chunkRenderer.finalize();
|
|
44991
|
-
console.error(`
|
|
44992
|
-
${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
|
|
45024
|
+
process.exit(1);
|
|
45025
|
+
}
|
|
45026
|
+
if (!settings.apiKey) {
|
|
45027
|
+
console.error(`
|
|
45028
|
+
${c.error("✖")} ${c.bold("API key is not configured.")}
|
|
45029
|
+
` + ` Run ${c.primary("locus config setup --api-key <key>")} first.
|
|
44993
45030
|
`);
|
|
44994
|
-
|
|
44995
|
-
|
|
44996
|
-
|
|
44997
|
-
|
|
44998
|
-
|
|
44999
|
-
|
|
45000
|
-
|
|
45001
|
-
|
|
45002
|
-
|
|
45003
|
-
|
|
45004
|
-
|
|
45005
|
-
|
|
45006
|
-
|
|
45031
|
+
process.exit(1);
|
|
45032
|
+
}
|
|
45033
|
+
}
|
|
45034
|
+
function generateSystemdUnit(projectPath, user2, bins) {
|
|
45035
|
+
return `[Unit]
|
|
45036
|
+
Description=Locus AI Agent (Telegram bot + proposal scheduler)
|
|
45037
|
+
After=network-online.target
|
|
45038
|
+
Wants=network-online.target
|
|
45039
|
+
|
|
45040
|
+
[Service]
|
|
45041
|
+
Type=simple
|
|
45042
|
+
User=${user2}
|
|
45043
|
+
WorkingDirectory=${projectPath}
|
|
45044
|
+
ExecStart=${bins.binaryPath}
|
|
45045
|
+
Restart=on-failure
|
|
45046
|
+
RestartSec=10
|
|
45047
|
+
Environment=PATH=${bins.servicePath}
|
|
45048
|
+
Environment=HOME=${homedir3()}
|
|
45049
|
+
|
|
45050
|
+
[Install]
|
|
45051
|
+
WantedBy=multi-user.target
|
|
45052
|
+
`;
|
|
45053
|
+
}
|
|
45054
|
+
async function startSystemd(projectPath, bins) {
|
|
45055
|
+
const user2 = process.env.USER || "root";
|
|
45056
|
+
const unit = generateSystemdUnit(projectPath, user2, bins);
|
|
45057
|
+
console.log(`
|
|
45058
|
+
${c.info("▶")} Writing systemd unit to ${c.dim(SYSTEMD_UNIT_PATH)}`);
|
|
45059
|
+
writeFileSync8(SYSTEMD_UNIT_PATH, unit, "utf-8");
|
|
45060
|
+
console.log(` ${c.info("▶")} Reloading systemd daemon...`);
|
|
45061
|
+
await runShell("systemctl", ["daemon-reload"]);
|
|
45062
|
+
console.log(` ${c.info("▶")} Enabling and starting ${SERVICE_NAME}...`);
|
|
45063
|
+
await runShell("systemctl", ["enable", SERVICE_NAME]);
|
|
45064
|
+
const result = await runShell("systemctl", ["start", SERVICE_NAME]);
|
|
45065
|
+
if (result.exitCode !== 0) {
|
|
45066
|
+
console.error(`
|
|
45067
|
+
${c.error("✖")} Failed to start service: ${result.stderr.trim()}`);
|
|
45068
|
+
console.error(` ${c.dim("Check logs with:")} ${c.primary(`journalctl -u ${SERVICE_NAME} -f`)}`);
|
|
45069
|
+
return;
|
|
45070
|
+
}
|
|
45071
|
+
console.log(`
|
|
45072
|
+
${c.success("✔")} ${c.bold("Locus daemon started!")}
|
|
45073
|
+
|
|
45074
|
+
${c.bold("Service:")} ${SERVICE_NAME}
|
|
45075
|
+
${c.bold("Unit file:")} ${SYSTEMD_UNIT_PATH}
|
|
45076
|
+
|
|
45077
|
+
${c.bold("Useful commands:")}
|
|
45078
|
+
${c.dim("$")} ${c.primary(`sudo systemctl status ${SERVICE_NAME}`)}
|
|
45079
|
+
${c.dim("$")} ${c.primary(`journalctl -u ${SERVICE_NAME} -f`)}
|
|
45007
45080
|
`);
|
|
45008
|
-
inputHandler.showPrompt();
|
|
45009
|
-
});
|
|
45010
|
-
},
|
|
45011
|
-
onInterrupt: () => {
|
|
45012
|
-
if (isProcessing) {
|
|
45013
|
-
interrupted = true;
|
|
45014
|
-
aiRunner.abort();
|
|
45015
|
-
isProcessing = false;
|
|
45016
|
-
console.log(c.dim(`
|
|
45017
|
-
[Interrupted]`));
|
|
45018
|
-
inputHandler.showPrompt();
|
|
45019
|
-
} else {
|
|
45020
|
-
shutdown();
|
|
45021
|
-
}
|
|
45022
|
-
},
|
|
45023
|
-
onClose: () => shutdown()
|
|
45024
|
-
});
|
|
45025
|
-
inputHandler.start();
|
|
45026
|
-
inputHandler.showPrompt();
|
|
45027
45081
|
}
|
|
45028
|
-
function
|
|
45029
|
-
|
|
45030
|
-
if (discussions.length === 0) {
|
|
45082
|
+
async function stopSystemd() {
|
|
45083
|
+
if (!existsSync19(SYSTEMD_UNIT_PATH)) {
|
|
45031
45084
|
console.log(`
|
|
45032
|
-
${c.dim("No
|
|
45033
|
-
`);
|
|
45034
|
-
console.log(` ${c.dim("Start one with:")} ${c.cyan('locus discuss "your topic"')}
|
|
45085
|
+
${c.dim("No systemd service found. Nothing to stop.")}
|
|
45035
45086
|
`);
|
|
45087
|
+
await killOrphanedProcesses();
|
|
45036
45088
|
return;
|
|
45037
45089
|
}
|
|
45090
|
+
console.log(` ${c.info("▶")} Stopping and disabling ${SERVICE_NAME}...`);
|
|
45091
|
+
await runShell("systemctl", ["stop", SERVICE_NAME]);
|
|
45092
|
+
await runShell("systemctl", ["disable", SERVICE_NAME]);
|
|
45093
|
+
unlinkSync6(SYSTEMD_UNIT_PATH);
|
|
45094
|
+
await runShell("systemctl", ["daemon-reload"]);
|
|
45095
|
+
await killOrphanedProcesses();
|
|
45038
45096
|
console.log(`
|
|
45039
|
-
${c.
|
|
45097
|
+
${c.success("✔")} ${c.bold("Locus daemon stopped.")}
|
|
45040
45098
|
`);
|
|
45041
|
-
for (const disc of discussions) {
|
|
45042
|
-
const statusIcon = disc.status === "active" ? c.warning("◯") : disc.status === "completed" ? c.success("✔") : c.dim("⊘");
|
|
45043
|
-
console.log(` ${statusIcon} ${c.bold(disc.title)} ${c.dim(`[${disc.status}]`)} ${c.dim(`— ${disc.messages.length} messages, ${disc.insights.length} insights`)}`);
|
|
45044
|
-
console.log(` ${c.dim("ID:")} ${disc.id}`);
|
|
45045
|
-
console.log(` ${c.dim("Created:")} ${disc.createdAt}`);
|
|
45046
|
-
console.log("");
|
|
45047
|
-
}
|
|
45048
45099
|
}
|
|
45049
|
-
function
|
|
45050
|
-
|
|
45051
|
-
|
|
45100
|
+
async function restartSystemd() {
|
|
45101
|
+
if (!existsSync19(SYSTEMD_UNIT_PATH)) {
|
|
45102
|
+
console.log(`
|
|
45103
|
+
${c.dim("No systemd service found. Use")} ${c.primary("locus daemon start")} ${c.dim("first.")}
|
|
45104
|
+
`);
|
|
45105
|
+
return;
|
|
45106
|
+
}
|
|
45107
|
+
console.log(` ${c.info("▶")} Restarting ${SERVICE_NAME}...`);
|
|
45108
|
+
const result = await runShell("systemctl", ["restart", SERVICE_NAME]);
|
|
45109
|
+
if (result.exitCode !== 0) {
|
|
45052
45110
|
console.error(`
|
|
45053
|
-
${c.error("✖")}
|
|
45111
|
+
${c.error("✖")} Failed to restart: ${result.stderr.trim()}
|
|
45054
45112
|
`);
|
|
45055
|
-
|
|
45113
|
+
return;
|
|
45056
45114
|
}
|
|
45057
45115
|
console.log(`
|
|
45058
|
-
${
|
|
45116
|
+
${c.success("✔")} ${c.bold("Locus daemon restarted.")}
|
|
45059
45117
|
`);
|
|
45060
45118
|
}
|
|
45061
|
-
function
|
|
45062
|
-
|
|
45063
|
-
|
|
45119
|
+
async function statusSystemd() {
|
|
45120
|
+
const result = await runShell("systemctl", ["is-active", SERVICE_NAME]);
|
|
45121
|
+
const state = result.stdout.trim();
|
|
45122
|
+
if (state === "active") {
|
|
45064
45123
|
console.log(`
|
|
45065
|
-
${c.success("
|
|
45124
|
+
${c.success("●")} ${c.bold("Locus daemon is running")} ${c.dim("(systemd)")}
|
|
45066
45125
|
`);
|
|
45067
|
-
}
|
|
45068
|
-
console.
|
|
45069
|
-
${c.
|
|
45126
|
+
} else if (existsSync19(SYSTEMD_UNIT_PATH)) {
|
|
45127
|
+
console.log(`
|
|
45128
|
+
${c.secondary("●")} ${c.bold(`Locus daemon is ${state}`)} ${c.dim("(systemd)")}
|
|
45070
45129
|
`);
|
|
45071
|
-
|
|
45072
|
-
|
|
45073
|
-
}
|
|
45074
|
-
function deleteDiscussion(discussionManager, id) {
|
|
45075
|
-
try {
|
|
45076
|
-
discussionManager.delete(id);
|
|
45130
|
+
console.log(` ${c.dim("Start with:")} ${c.primary("locus daemon start")}
|
|
45131
|
+
`);
|
|
45132
|
+
} else {
|
|
45077
45133
|
console.log(`
|
|
45078
|
-
${c.
|
|
45134
|
+
${c.secondary("●")} ${c.bold("Locus daemon is not installed")}
|
|
45079
45135
|
`);
|
|
45080
|
-
}
|
|
45081
|
-
console.error(`
|
|
45082
|
-
${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
|
|
45136
|
+
console.log(` ${c.dim("Start with:")} ${c.primary("locus daemon start")}
|
|
45083
45137
|
`);
|
|
45084
|
-
process.exit(1);
|
|
45085
45138
|
}
|
|
45086
45139
|
}
|
|
45087
|
-
function
|
|
45088
|
-
const
|
|
45089
|
-
|
|
45140
|
+
function generatePlist(projectPath, bins) {
|
|
45141
|
+
const logDir = join19(homedir3(), "Library/Logs/Locus");
|
|
45142
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
45143
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
45144
|
+
<plist version="1.0">
|
|
45145
|
+
<dict>
|
|
45146
|
+
<key>Label</key>
|
|
45147
|
+
<string>${PLIST_LABEL}</string>
|
|
45148
|
+
<key>ProgramArguments</key>
|
|
45149
|
+
<array>
|
|
45150
|
+
<string>${bins.binaryPath}</string>
|
|
45151
|
+
</array>
|
|
45152
|
+
<key>WorkingDirectory</key>
|
|
45153
|
+
<string>${projectPath}</string>
|
|
45154
|
+
<key>RunAtLoad</key>
|
|
45155
|
+
<true/>
|
|
45156
|
+
<key>KeepAlive</key>
|
|
45157
|
+
<true/>
|
|
45158
|
+
<key>StandardOutPath</key>
|
|
45159
|
+
<string>${join19(logDir, "locus.log")}</string>
|
|
45160
|
+
<key>StandardErrorPath</key>
|
|
45161
|
+
<string>${join19(logDir, "locus-error.log")}</string>
|
|
45162
|
+
<key>EnvironmentVariables</key>
|
|
45163
|
+
<dict>
|
|
45164
|
+
<key>PATH</key>
|
|
45165
|
+
<string>${bins.servicePath}</string>
|
|
45166
|
+
</dict>
|
|
45167
|
+
</dict>
|
|
45168
|
+
</plist>
|
|
45169
|
+
`;
|
|
45170
|
+
}
|
|
45171
|
+
async function startLaunchd(projectPath, bins) {
|
|
45172
|
+
const plistPath = getPlistPath();
|
|
45173
|
+
if (existsSync19(plistPath)) {
|
|
45174
|
+
await runShell("launchctl", ["unload", plistPath]);
|
|
45175
|
+
}
|
|
45176
|
+
const logDir = join19(homedir3(), "Library/Logs/Locus");
|
|
45177
|
+
mkdirSync8(logDir, { recursive: true });
|
|
45178
|
+
mkdirSync8(join19(homedir3(), "Library/LaunchAgents"), { recursive: true });
|
|
45179
|
+
const plist = generatePlist(projectPath, bins);
|
|
45180
|
+
console.log(`
|
|
45181
|
+
${c.info("▶")} Writing plist to ${c.dim(plistPath)}`);
|
|
45182
|
+
writeFileSync8(plistPath, plist, "utf-8");
|
|
45183
|
+
console.log(` ${c.info("▶")} Loading service...`);
|
|
45184
|
+
const result = await runShell("launchctl", ["load", plistPath]);
|
|
45185
|
+
if (result.exitCode !== 0) {
|
|
45186
|
+
console.error(`
|
|
45187
|
+
${c.error("✖")} Failed to load service: ${result.stderr.trim()}`);
|
|
45188
|
+
return;
|
|
45189
|
+
}
|
|
45190
|
+
const logPath = join19(logDir, "locus.log");
|
|
45191
|
+
console.log(`
|
|
45192
|
+
${c.success("✔")} ${c.bold("Locus daemon started!")}
|
|
45193
|
+
|
|
45194
|
+
${c.bold("Plist:")} ${plistPath}
|
|
45195
|
+
${c.bold("Logs:")} ${logPath}
|
|
45196
|
+
|
|
45197
|
+
${c.bold("Useful commands:")}
|
|
45198
|
+
${c.dim("$")} ${c.primary(`launchctl list | grep ${PLIST_LABEL}`)}
|
|
45199
|
+
${c.dim("$")} ${c.primary(`tail -f ${logPath}`)}
|
|
45200
|
+
`);
|
|
45201
|
+
}
|
|
45202
|
+
async function stopLaunchd() {
|
|
45203
|
+
const plistPath = getPlistPath();
|
|
45204
|
+
if (!existsSync19(plistPath)) {
|
|
45090
45205
|
console.log(`
|
|
45091
|
-
${c.dim("No
|
|
45206
|
+
${c.dim("No launchd service found. Nothing to stop.")}
|
|
45092
45207
|
`);
|
|
45208
|
+
await killOrphanedProcesses();
|
|
45093
45209
|
return;
|
|
45094
45210
|
}
|
|
45211
|
+
console.log(` ${c.info("▶")} Unloading service...`);
|
|
45212
|
+
await runShell("launchctl", ["unload", plistPath]);
|
|
45213
|
+
unlinkSync6(plistPath);
|
|
45214
|
+
await killOrphanedProcesses();
|
|
45095
45215
|
console.log(`
|
|
45096
|
-
${c.
|
|
45216
|
+
${c.success("✔")} ${c.bold("Locus daemon stopped.")}
|
|
45097
45217
|
`);
|
|
45098
|
-
for (const insight of discussion2.insights) {
|
|
45099
|
-
const tag = formatInsightTag(insight.type);
|
|
45100
|
-
console.log(` ${tag} ${c.bold(insight.title)}`);
|
|
45101
|
-
console.log(` ${c.dim(insight.content)}`);
|
|
45102
|
-
if (insight.tags.length > 0) {
|
|
45103
|
-
console.log(` ${c.dim(`Tags: ${insight.tags.join(", ")}`)}`);
|
|
45104
|
-
}
|
|
45105
|
-
console.log("");
|
|
45106
|
-
}
|
|
45107
45218
|
}
|
|
45108
|
-
function
|
|
45109
|
-
|
|
45110
|
-
|
|
45111
|
-
|
|
45112
|
-
|
|
45113
|
-
|
|
45114
|
-
|
|
45115
|
-
|
|
45116
|
-
|
|
45117
|
-
|
|
45118
|
-
|
|
45119
|
-
|
|
45219
|
+
async function restartLaunchd() {
|
|
45220
|
+
const plistPath = getPlistPath();
|
|
45221
|
+
if (!existsSync19(plistPath)) {
|
|
45222
|
+
console.log(`
|
|
45223
|
+
${c.dim("No launchd service found. Use")} ${c.primary("locus daemon start")} ${c.dim("first.")}
|
|
45224
|
+
`);
|
|
45225
|
+
return;
|
|
45226
|
+
}
|
|
45227
|
+
console.log(` ${c.info("▶")} Restarting service...`);
|
|
45228
|
+
await runShell("launchctl", ["unload", plistPath]);
|
|
45229
|
+
const result = await runShell("launchctl", ["load", plistPath]);
|
|
45230
|
+
if (result.exitCode !== 0) {
|
|
45231
|
+
console.error(`
|
|
45232
|
+
${c.error("✖")} Failed to restart: ${result.stderr.trim()}
|
|
45233
|
+
`);
|
|
45234
|
+
return;
|
|
45120
45235
|
}
|
|
45121
|
-
}
|
|
45122
|
-
function showReplHelp() {
|
|
45123
45236
|
console.log(`
|
|
45124
|
-
${c.
|
|
45125
|
-
|
|
45126
|
-
${c.cyan("summary")} Generate a final summary and end the discussion
|
|
45127
|
-
${c.cyan("insights")} Show all insights extracted so far
|
|
45128
|
-
${c.cyan("exit")} Save and exit without generating a summary
|
|
45129
|
-
${c.cyan("help")} Show this help message
|
|
45130
|
-
|
|
45131
|
-
${c.header(" KEY BINDINGS ")}
|
|
45132
|
-
|
|
45133
|
-
${c.cyan("Enter")} Send message
|
|
45134
|
-
${c.cyan("Shift+Enter")} Insert newline (also: Alt+Enter, Ctrl+J)
|
|
45135
|
-
${c.cyan("Ctrl+C")} Interrupt / clear input / exit
|
|
45136
|
-
${c.cyan("Ctrl+U")} Clear current input
|
|
45137
|
-
${c.cyan("Ctrl+W")} Delete last word
|
|
45138
|
-
|
|
45139
|
-
${c.dim("Type anything else to continue the discussion.")}
|
|
45237
|
+
${c.success("✔")} ${c.bold("Locus daemon restarted.")}
|
|
45140
45238
|
`);
|
|
45141
45239
|
}
|
|
45142
|
-
function
|
|
45143
|
-
|
|
45144
|
-
|
|
45145
|
-
|
|
45146
|
-
${c.bold("
|
|
45147
|
-
|
|
45148
|
-
${c.
|
|
45149
|
-
|
|
45150
|
-
|
|
45151
|
-
|
|
45152
|
-
|
|
45153
|
-
|
|
45154
|
-
|
|
45155
|
-
|
|
45156
|
-
|
|
45157
|
-
|
|
45158
|
-
|
|
45159
|
-
|
|
45160
|
-
|
|
45161
|
-
|
|
45162
|
-
|
|
45163
|
-
|
|
45164
|
-
|
|
45165
|
-
|
|
45166
|
-
|
|
45167
|
-
|
|
45168
|
-
|
|
45169
|
-
|
|
45170
|
-
|
|
45171
|
-
|
|
45172
|
-
|
|
45173
|
-
${c.
|
|
45240
|
+
async function statusLaunchd() {
|
|
45241
|
+
const plistPath = getPlistPath();
|
|
45242
|
+
if (!existsSync19(plistPath)) {
|
|
45243
|
+
console.log(`
|
|
45244
|
+
${c.secondary("●")} ${c.bold("Locus daemon is not installed")}
|
|
45245
|
+
`);
|
|
45246
|
+
console.log(` ${c.dim("Start with:")} ${c.primary("locus daemon start")}
|
|
45247
|
+
`);
|
|
45248
|
+
return;
|
|
45249
|
+
}
|
|
45250
|
+
const result = await runShell("launchctl", ["list"]);
|
|
45251
|
+
const match = result.stdout.split(`
|
|
45252
|
+
`).find((l) => l.includes(PLIST_LABEL));
|
|
45253
|
+
if (match) {
|
|
45254
|
+
const parts = match.trim().split(/\s+/);
|
|
45255
|
+
const pid = parts[0] === "-" ? null : parts[0];
|
|
45256
|
+
if (pid) {
|
|
45257
|
+
console.log(`
|
|
45258
|
+
${c.success("●")} ${c.bold("Locus daemon is running")} ${c.dim(`(PID ${pid}, launchd)`)}
|
|
45259
|
+
`);
|
|
45260
|
+
} else {
|
|
45261
|
+
console.log(`
|
|
45262
|
+
${c.secondary("●")} ${c.bold("Locus daemon is stopped")} ${c.dim("(launchd)")}
|
|
45263
|
+
`);
|
|
45264
|
+
console.log(` ${c.dim("Start with:")} ${c.primary("locus daemon start")}
|
|
45265
|
+
`);
|
|
45266
|
+
}
|
|
45267
|
+
} else {
|
|
45268
|
+
console.log(`
|
|
45269
|
+
${c.secondary("●")} ${c.bold("Locus daemon is not loaded")} ${c.dim("(plist exists but not loaded)")}
|
|
45270
|
+
`);
|
|
45271
|
+
console.log(` ${c.dim("Start with:")} ${c.primary("locus daemon start")}
|
|
45174
45272
|
`);
|
|
45273
|
+
}
|
|
45175
45274
|
}
|
|
45176
|
-
|
|
45177
|
-
|
|
45178
|
-
|
|
45179
|
-
init_settings_manager();
|
|
45180
|
-
init_utils3();
|
|
45181
|
-
init_workspace_resolver();
|
|
45182
|
-
import { parseArgs as parseArgs4 } from "node:util";
|
|
45183
|
-
async function docsCommand(args) {
|
|
45275
|
+
async function daemonCommand(args) {
|
|
45276
|
+
const projectPath = process.cwd();
|
|
45277
|
+
requireInitialization(projectPath, "daemon");
|
|
45184
45278
|
const subcommand = args[0];
|
|
45185
|
-
const
|
|
45279
|
+
const platform = subcommand ? requirePlatform() : null;
|
|
45280
|
+
const isLinux = platform === "linux";
|
|
45186
45281
|
switch (subcommand) {
|
|
45187
|
-
case "
|
|
45188
|
-
|
|
45282
|
+
case "start": {
|
|
45283
|
+
validateConfig(projectPath);
|
|
45284
|
+
const bins = await resolveBinaries();
|
|
45285
|
+
if (isLinux)
|
|
45286
|
+
await startSystemd(projectPath, bins);
|
|
45287
|
+
else
|
|
45288
|
+
await startLaunchd(projectPath, bins);
|
|
45189
45289
|
break;
|
|
45190
|
-
|
|
45191
|
-
|
|
45290
|
+
}
|
|
45291
|
+
case "stop":
|
|
45292
|
+
if (isLinux)
|
|
45293
|
+
await stopSystemd();
|
|
45294
|
+
else
|
|
45295
|
+
await stopLaunchd();
|
|
45296
|
+
break;
|
|
45297
|
+
case "restart":
|
|
45298
|
+
if (isLinux)
|
|
45299
|
+
await restartSystemd();
|
|
45300
|
+
else
|
|
45301
|
+
await restartLaunchd();
|
|
45192
45302
|
break;
|
|
45303
|
+
case "status":
|
|
45304
|
+
if (isLinux)
|
|
45305
|
+
await statusSystemd();
|
|
45306
|
+
else
|
|
45307
|
+
await statusLaunchd();
|
|
45308
|
+
break;
|
|
45309
|
+
default:
|
|
45310
|
+
showDaemonHelp();
|
|
45193
45311
|
}
|
|
45194
45312
|
}
|
|
45195
|
-
|
|
45196
|
-
|
|
45313
|
+
// src/commands/discuss.ts
|
|
45314
|
+
init_index_node();
|
|
45315
|
+
init_progress_renderer();
|
|
45316
|
+
init_image_detect();
|
|
45317
|
+
init_input_handler();
|
|
45318
|
+
init_settings_manager();
|
|
45319
|
+
init_utils3();
|
|
45320
|
+
import { parseArgs as parseArgs3 } from "node:util";
|
|
45321
|
+
async function discussCommand(args) {
|
|
45322
|
+
const { values, positionals } = parseArgs3({
|
|
45197
45323
|
args,
|
|
45198
45324
|
options: {
|
|
45199
|
-
|
|
45200
|
-
|
|
45201
|
-
|
|
45202
|
-
|
|
45203
|
-
|
|
45325
|
+
list: { type: "boolean" },
|
|
45326
|
+
show: { type: "string" },
|
|
45327
|
+
archive: { type: "string" },
|
|
45328
|
+
delete: { type: "string" },
|
|
45329
|
+
model: { type: "string" },
|
|
45330
|
+
provider: { type: "string" },
|
|
45331
|
+
"reasoning-effort": { type: "string" },
|
|
45332
|
+
dir: { type: "string" }
|
|
45204
45333
|
},
|
|
45205
|
-
strict: false
|
|
45334
|
+
strict: false,
|
|
45335
|
+
allowPositionals: true
|
|
45206
45336
|
});
|
|
45207
|
-
if (values.help) {
|
|
45208
|
-
showDocsSyncHelp();
|
|
45209
|
-
return;
|
|
45210
|
-
}
|
|
45211
45337
|
const projectPath = values.dir || process.cwd();
|
|
45212
|
-
requireInitialization(projectPath, "
|
|
45213
|
-
const
|
|
45214
|
-
|
|
45215
|
-
|
|
45216
|
-
const settings = settingsManager.load();
|
|
45217
|
-
const apiKey = values["api-key"] || settings.apiKey;
|
|
45218
|
-
if (!apiKey) {
|
|
45219
|
-
console.error(`
|
|
45220
|
-
${c.error("✖")} ${c.red("API key is required")}
|
|
45221
|
-
` + ` ${c.dim(`Configure with: locus config setup --api-key <key>
|
|
45222
|
-
Or pass --api-key flag`)}
|
|
45223
|
-
`);
|
|
45224
|
-
process.exit(1);
|
|
45338
|
+
requireInitialization(projectPath, "discuss");
|
|
45339
|
+
const discussionManager = new DiscussionManager(projectPath);
|
|
45340
|
+
if (values.list) {
|
|
45341
|
+
return listDiscussions(discussionManager);
|
|
45225
45342
|
}
|
|
45226
|
-
|
|
45227
|
-
|
|
45228
|
-
apiKey,
|
|
45229
|
-
apiBase,
|
|
45230
|
-
workspaceId: values.workspace
|
|
45231
|
-
});
|
|
45232
|
-
let workspaceId;
|
|
45233
|
-
try {
|
|
45234
|
-
workspaceId = await resolver.resolve();
|
|
45235
|
-
} catch (error48) {
|
|
45236
|
-
console.error(`
|
|
45237
|
-
${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
|
|
45238
|
-
`);
|
|
45239
|
-
process.exit(1);
|
|
45343
|
+
if (values.show) {
|
|
45344
|
+
return showDiscussion(discussionManager, values.show);
|
|
45240
45345
|
}
|
|
45241
|
-
|
|
45242
|
-
|
|
45243
|
-
|
|
45346
|
+
if (values.archive) {
|
|
45347
|
+
return archiveDiscussion(discussionManager, values.archive);
|
|
45348
|
+
}
|
|
45349
|
+
if (values.delete) {
|
|
45350
|
+
return deleteDiscussion(discussionManager, values.delete);
|
|
45351
|
+
}
|
|
45352
|
+
const topic = positionals.join(" ").trim();
|
|
45353
|
+
if (!topic) {
|
|
45354
|
+
showDiscussHelp();
|
|
45355
|
+
return;
|
|
45356
|
+
}
|
|
45357
|
+
const settings = new SettingsManager(projectPath).load();
|
|
45358
|
+
const provider = resolveProvider3(values.provider || settings.provider);
|
|
45359
|
+
const model = values.model || settings.model || DEFAULT_MODEL[provider];
|
|
45360
|
+
const reasoningEffort = values["reasoning-effort"];
|
|
45361
|
+
const aiRunner = createAiRunner(provider, {
|
|
45362
|
+
projectPath,
|
|
45363
|
+
model,
|
|
45364
|
+
reasoningEffort
|
|
45244
45365
|
});
|
|
45245
|
-
const
|
|
45246
|
-
|
|
45247
|
-
|
|
45366
|
+
const log = (message, level) => {
|
|
45367
|
+
const icon = level === "success" ? c.success("✔") : level === "error" ? c.error("✖") : level === "warn" ? c.warning("!") : c.info("●");
|
|
45368
|
+
console.log(` ${icon} ${message}`);
|
|
45369
|
+
};
|
|
45370
|
+
const facilitator = new DiscussionFacilitator({
|
|
45248
45371
|
projectPath,
|
|
45249
|
-
|
|
45250
|
-
|
|
45251
|
-
|
|
45252
|
-
|
|
45253
|
-
|
|
45254
|
-
if (level === "warn") {
|
|
45255
|
-
console.log(` ${c.warning("!")} ${message}`);
|
|
45256
|
-
return;
|
|
45257
|
-
}
|
|
45258
|
-
if (level === "success") {
|
|
45259
|
-
console.log(` ${c.success("✔")} ${message}`);
|
|
45260
|
-
return;
|
|
45261
|
-
}
|
|
45262
|
-
console.log(` ${c.info("●")} ${message}`);
|
|
45263
|
-
}
|
|
45372
|
+
aiRunner,
|
|
45373
|
+
discussionManager,
|
|
45374
|
+
log,
|
|
45375
|
+
provider,
|
|
45376
|
+
model
|
|
45264
45377
|
});
|
|
45265
45378
|
console.log(`
|
|
45266
|
-
${c.
|
|
45379
|
+
${c.header(" DISCUSSION ")} ${c.bold("Starting interactive discussion...")}
|
|
45380
|
+
`);
|
|
45381
|
+
console.log(` ${c.dim("Topic:")} ${c.bold(topic)}`);
|
|
45382
|
+
console.log(` ${c.dim("Model:")} ${c.dim(`${model} (${provider})`)}
|
|
45267
45383
|
`);
|
|
45384
|
+
const renderer = new ProgressRenderer;
|
|
45385
|
+
let discussionId;
|
|
45268
45386
|
try {
|
|
45269
|
-
|
|
45270
|
-
|
|
45271
|
-
|
|
45387
|
+
renderer.showThinkingStarted();
|
|
45388
|
+
const result = await facilitator.startDiscussion(topic);
|
|
45389
|
+
renderer.showThinkingStopped();
|
|
45390
|
+
discussionId = result.discussion.id;
|
|
45391
|
+
process.stdout.write(`
|
|
45392
|
+
`);
|
|
45393
|
+
process.stdout.write(result.message);
|
|
45394
|
+
process.stdout.write(`
|
|
45395
|
+
|
|
45272
45396
|
`);
|
|
45397
|
+
renderer.finalize();
|
|
45273
45398
|
} catch (error48) {
|
|
45399
|
+
renderer.finalize();
|
|
45274
45400
|
console.error(`
|
|
45275
|
-
${c.error("✖")} ${c.red(
|
|
45401
|
+
${c.error("✖")} ${c.red("Failed to start discussion:")} ${error48 instanceof Error ? error48.message : String(error48)}
|
|
45276
45402
|
`);
|
|
45277
45403
|
process.exit(1);
|
|
45278
45404
|
}
|
|
45279
|
-
}
|
|
45280
|
-
|
|
45281
|
-
console.log(`
|
|
45282
|
-
${c.header(" DOCS ")}
|
|
45283
|
-
${c.primary("locus docs")} ${c.dim("<command> [options]")}
|
|
45284
|
-
|
|
45285
|
-
${c.header(" COMMANDS ")}
|
|
45286
|
-
${c.success("sync")} Sync workspace docs from API to .locus/documents
|
|
45287
|
-
|
|
45288
|
-
${c.header(" EXAMPLES ")}
|
|
45289
|
-
${c.dim("$")} ${c.primary("locus docs sync")}
|
|
45290
|
-
${c.dim("$")} ${c.primary("locus docs sync --workspace ws_123")}
|
|
45291
|
-
`);
|
|
45292
|
-
}
|
|
45293
|
-
function showDocsSyncHelp() {
|
|
45294
|
-
console.log(`
|
|
45295
|
-
${c.header(" DOCS SYNC ")}
|
|
45296
|
-
${c.primary("locus docs sync")} ${c.dim("[options]")}
|
|
45297
|
-
|
|
45298
|
-
${c.header(" OPTIONS ")}
|
|
45299
|
-
${c.secondary("--api-key")} <key> API key override (reads from settings.json)
|
|
45300
|
-
${c.secondary("--api-url")} <url> API base URL (default: https://api.locusai.dev/api)
|
|
45301
|
-
${c.secondary("--workspace")} <id> Workspace ID (optional if persisted or resolvable)
|
|
45302
|
-
${c.secondary("--dir")} <path> Project directory (default: current)
|
|
45303
|
-
${c.secondary("--help")} Show docs sync help
|
|
45304
|
-
|
|
45305
|
-
${c.header(" EXAMPLES ")}
|
|
45306
|
-
${c.dim("$")} ${c.primary("locus docs sync")}
|
|
45307
|
-
${c.dim("$")} ${c.primary("locus docs sync --workspace ws_123")}
|
|
45308
|
-
`);
|
|
45309
|
-
}
|
|
45310
|
-
// src/commands/exec.ts
|
|
45311
|
-
init_index_node();
|
|
45312
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
45313
|
-
import { parseArgs as parseArgs5 } from "node:util";
|
|
45314
|
-
|
|
45315
|
-
// src/display/json-stream-renderer.ts
|
|
45316
|
-
init_src();
|
|
45317
|
-
|
|
45318
|
-
class JsonStreamRenderer {
|
|
45319
|
-
sessionId;
|
|
45320
|
-
command;
|
|
45321
|
-
model;
|
|
45322
|
-
provider;
|
|
45323
|
-
cwd;
|
|
45324
|
-
statsTracker;
|
|
45325
|
-
started = false;
|
|
45326
|
-
done = false;
|
|
45327
|
-
constructor(options) {
|
|
45328
|
-
this.sessionId = options.sessionId;
|
|
45329
|
-
this.command = options.command;
|
|
45330
|
-
this.model = options.model;
|
|
45331
|
-
this.provider = options.provider;
|
|
45332
|
-
this.cwd = options.cwd;
|
|
45333
|
-
this.statsTracker = new ExecutionStatsTracker;
|
|
45334
|
-
}
|
|
45335
|
-
emitStart() {
|
|
45336
|
-
if (this.started)
|
|
45337
|
-
return;
|
|
45338
|
-
this.started = true;
|
|
45339
|
-
this.emit(createCliStreamEvent(CliStreamEventType.START, this.sessionId, {
|
|
45340
|
-
command: this.command,
|
|
45341
|
-
model: this.model,
|
|
45342
|
-
provider: this.provider,
|
|
45343
|
-
cwd: this.cwd
|
|
45344
|
-
}));
|
|
45345
|
-
}
|
|
45346
|
-
handleChunk(chunk) {
|
|
45347
|
-
this.ensureStarted();
|
|
45348
|
-
switch (chunk.type) {
|
|
45349
|
-
case "text_delta":
|
|
45350
|
-
this.emit(createCliStreamEvent(CliStreamEventType.TEXT_DELTA, this.sessionId, {
|
|
45351
|
-
content: chunk.content
|
|
45352
|
-
}));
|
|
45353
|
-
break;
|
|
45354
|
-
case "thinking":
|
|
45355
|
-
this.emit(createCliStreamEvent(CliStreamEventType.THINKING, this.sessionId, {
|
|
45356
|
-
content: chunk.content
|
|
45357
|
-
}));
|
|
45358
|
-
break;
|
|
45359
|
-
case "tool_use":
|
|
45360
|
-
this.statsTracker.toolStarted(chunk.tool, chunk.id);
|
|
45361
|
-
this.emit(createCliStreamEvent(CliStreamEventType.TOOL_STARTED, this.sessionId, {
|
|
45362
|
-
tool: chunk.tool,
|
|
45363
|
-
toolId: chunk.id,
|
|
45364
|
-
parameters: chunk.parameters
|
|
45365
|
-
}));
|
|
45366
|
-
break;
|
|
45367
|
-
case "tool_result":
|
|
45368
|
-
if (chunk.success) {
|
|
45369
|
-
this.statsTracker.toolCompleted(chunk.tool, chunk.id);
|
|
45370
|
-
} else {
|
|
45371
|
-
this.statsTracker.toolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
|
|
45372
|
-
}
|
|
45373
|
-
this.emit(createCliStreamEvent(CliStreamEventType.TOOL_COMPLETED, this.sessionId, {
|
|
45374
|
-
tool: chunk.tool,
|
|
45375
|
-
toolId: chunk.id,
|
|
45376
|
-
success: chunk.success,
|
|
45377
|
-
duration: chunk.duration,
|
|
45378
|
-
error: chunk.error
|
|
45379
|
-
}));
|
|
45380
|
-
break;
|
|
45381
|
-
case "tool_parameters":
|
|
45382
|
-
break;
|
|
45383
|
-
case "result":
|
|
45384
|
-
break;
|
|
45385
|
-
case "error":
|
|
45386
|
-
this.statsTracker.setError(chunk.error);
|
|
45387
|
-
this.emitError("UNKNOWN", chunk.error);
|
|
45388
|
-
break;
|
|
45389
|
-
}
|
|
45390
|
-
}
|
|
45391
|
-
emitStatus(status, message) {
|
|
45392
|
-
this.ensureStarted();
|
|
45393
|
-
this.emit(createCliStreamEvent(CliStreamEventType.STATUS, this.sessionId, {
|
|
45394
|
-
status,
|
|
45395
|
-
message
|
|
45396
|
-
}));
|
|
45397
|
-
}
|
|
45398
|
-
emitError(code, message, options) {
|
|
45399
|
-
this.ensureStarted();
|
|
45400
|
-
this.emit(createCliStreamEvent(CliStreamEventType.ERROR, this.sessionId, {
|
|
45401
|
-
error: createProtocolError(code, message, options)
|
|
45402
|
-
}));
|
|
45403
|
-
}
|
|
45404
|
-
emitDone(exitCode) {
|
|
45405
|
-
if (this.done)
|
|
45406
|
-
return;
|
|
45407
|
-
this.done = true;
|
|
45408
|
-
this.ensureStarted();
|
|
45409
|
-
const stats = this.statsTracker.finalize();
|
|
45410
|
-
this.emit(createCliStreamEvent(CliStreamEventType.DONE, this.sessionId, {
|
|
45411
|
-
exitCode,
|
|
45412
|
-
duration: stats.duration,
|
|
45413
|
-
toolsUsed: stats.toolsUsed.length > 0 ? stats.toolsUsed : undefined,
|
|
45414
|
-
tokensUsed: stats.tokensUsed,
|
|
45415
|
-
success: exitCode === 0
|
|
45416
|
-
}));
|
|
45417
|
-
}
|
|
45418
|
-
emitFatalError(code, message, options) {
|
|
45419
|
-
this.statsTracker.setError(message);
|
|
45420
|
-
this.emitError(code, message, {
|
|
45421
|
-
...options,
|
|
45422
|
-
recoverable: false
|
|
45423
|
-
});
|
|
45424
|
-
this.emitDone(1);
|
|
45425
|
-
}
|
|
45426
|
-
isDone() {
|
|
45427
|
-
return this.done;
|
|
45428
|
-
}
|
|
45429
|
-
ensureStarted() {
|
|
45430
|
-
if (!this.started) {
|
|
45431
|
-
this.emitStart();
|
|
45432
|
-
}
|
|
45433
|
-
}
|
|
45434
|
-
emit(event) {
|
|
45435
|
-
process.stdout.write(`${JSON.stringify(event)}
|
|
45436
|
-
`);
|
|
45437
|
-
}
|
|
45438
|
-
}
|
|
45439
|
-
|
|
45440
|
-
// src/commands/exec.ts
|
|
45441
|
-
init_progress_renderer();
|
|
45442
|
-
init_settings_manager();
|
|
45443
|
-
init_utils3();
|
|
45444
|
-
|
|
45445
|
-
// src/commands/exec-sessions.ts
|
|
45446
|
-
init_index_node();
|
|
45447
|
-
function formatRelativeTime(timestamp) {
|
|
45448
|
-
const now = Date.now();
|
|
45449
|
-
const diff = now - timestamp;
|
|
45450
|
-
const seconds = Math.floor(diff / 1000);
|
|
45451
|
-
const minutes = Math.floor(seconds / 60);
|
|
45452
|
-
const hours = Math.floor(minutes / 60);
|
|
45453
|
-
const days = Math.floor(hours / 24);
|
|
45454
|
-
if (days > 0) {
|
|
45455
|
-
return days === 1 ? "yesterday" : `${days} days ago`;
|
|
45456
|
-
}
|
|
45457
|
-
if (hours > 0) {
|
|
45458
|
-
return hours === 1 ? "1 hour ago" : `${hours} hours ago`;
|
|
45459
|
-
}
|
|
45460
|
-
if (minutes > 0) {
|
|
45461
|
-
return minutes === 1 ? "1 minute ago" : `${minutes} minutes ago`;
|
|
45462
|
-
}
|
|
45463
|
-
return "just now";
|
|
45464
|
-
}
|
|
45465
|
-
|
|
45466
|
-
class SessionCommands {
|
|
45467
|
-
historyManager;
|
|
45468
|
-
constructor(projectPath) {
|
|
45469
|
-
this.historyManager = new HistoryManager(projectPath);
|
|
45470
|
-
}
|
|
45471
|
-
async list() {
|
|
45472
|
-
const sessions = this.historyManager.listSessions();
|
|
45473
|
-
if (sessions.length === 0) {
|
|
45474
|
-
console.log(`
|
|
45475
|
-
${c.dim("No exec sessions found.")}
|
|
45405
|
+
console.log(` ${c.dim("Type your response, or 'help' for commands.")}`);
|
|
45406
|
+
console.log(` ${c.dim("Enter to send, Shift+Enter for newline. Use 'exit' or Ctrl+D to quit.")}
|
|
45476
45407
|
`);
|
|
45477
|
-
|
|
45408
|
+
let isProcessing = false;
|
|
45409
|
+
let interrupted = false;
|
|
45410
|
+
const shutdown = () => {
|
|
45411
|
+
if (isProcessing) {
|
|
45412
|
+
aiRunner.abort();
|
|
45478
45413
|
}
|
|
45479
45414
|
console.log(`
|
|
45480
|
-
${c.
|
|
45481
|
-
`
|
|
45482
|
-
|
|
45483
|
-
const shortId = this.getShortId(session2.id);
|
|
45484
|
-
const age = formatRelativeTime(session2.updatedAt);
|
|
45485
|
-
const msgCount = session2.messages.length;
|
|
45486
|
-
const firstUserMsg = session2.messages.find((m) => m.role === "user");
|
|
45487
|
-
const preview = firstUserMsg ? firstUserMsg.content.slice(0, 50).replace(/\n/g, " ") : "(empty session)";
|
|
45488
|
-
console.log(` ${c.cyan(shortId)} ${c.gray("-")} ${preview}${firstUserMsg && firstUserMsg.content.length > 50 ? "..." : ""}`);
|
|
45489
|
-
console.log(` ${c.dim(`${msgCount} messages • ${age}`)}`);
|
|
45490
|
-
console.log();
|
|
45491
|
-
}
|
|
45492
|
-
if (sessions.length > 10) {
|
|
45493
|
-
console.log(c.dim(` ... and ${sessions.length - 10} more sessions
|
|
45415
|
+
${c.dim("Discussion saved.")} ${c.dim("ID:")} ${c.cyan(discussionId)}`);
|
|
45416
|
+
console.log(c.dim(`
|
|
45417
|
+
Goodbye!
|
|
45494
45418
|
`));
|
|
45495
|
-
|
|
45496
|
-
|
|
45497
|
-
|
|
45498
|
-
|
|
45499
|
-
|
|
45500
|
-
|
|
45501
|
-
|
|
45502
|
-
|
|
45503
|
-
`);
|
|
45419
|
+
inputHandler.stop();
|
|
45420
|
+
process.exit(0);
|
|
45421
|
+
};
|
|
45422
|
+
const handleSubmit = async (input) => {
|
|
45423
|
+
interrupted = false;
|
|
45424
|
+
const trimmed = input.trim();
|
|
45425
|
+
if (trimmed === "") {
|
|
45426
|
+
inputHandler.showPrompt();
|
|
45504
45427
|
return;
|
|
45505
45428
|
}
|
|
45506
|
-
|
|
45507
|
-
|
|
45508
|
-
|
|
45509
|
-
|
|
45510
|
-
|
|
45511
|
-
|
|
45429
|
+
if (!trimmed.includes(`
|
|
45430
|
+
`)) {
|
|
45431
|
+
const lowerInput = trimmed.toLowerCase();
|
|
45432
|
+
if (lowerInput === "help") {
|
|
45433
|
+
showReplHelp();
|
|
45434
|
+
inputHandler.showPrompt();
|
|
45435
|
+
return;
|
|
45436
|
+
}
|
|
45437
|
+
if (lowerInput === "exit" || lowerInput === "quit") {
|
|
45438
|
+
shutdown();
|
|
45439
|
+
return;
|
|
45440
|
+
}
|
|
45441
|
+
if (lowerInput === "insights") {
|
|
45442
|
+
showCurrentInsights(discussionManager, discussionId);
|
|
45443
|
+
inputHandler.showPrompt();
|
|
45444
|
+
return;
|
|
45445
|
+
}
|
|
45446
|
+
if (lowerInput === "summary") {
|
|
45447
|
+
isProcessing = true;
|
|
45448
|
+
const summaryRenderer = new ProgressRenderer;
|
|
45449
|
+
try {
|
|
45450
|
+
summaryRenderer.showThinkingStarted();
|
|
45451
|
+
const summary = await facilitator.summarizeDiscussion(discussionId);
|
|
45452
|
+
summaryRenderer.showThinkingStopped();
|
|
45453
|
+
process.stdout.write(`
|
|
45512
45454
|
`);
|
|
45513
|
-
|
|
45514
|
-
|
|
45515
|
-
console.log(`
|
|
45516
|
-
${c.primary("Session:")} ${c.cyan(session2.id)}`);
|
|
45517
|
-
console.log(` ${c.dim(`Created: ${new Date(session2.createdAt).toLocaleString()}`)}`);
|
|
45518
|
-
console.log(` ${c.dim(`Model: ${session2.metadata.model} (${session2.metadata.provider})`)}
|
|
45455
|
+
process.stdout.write(summary);
|
|
45456
|
+
process.stdout.write(`
|
|
45519
45457
|
`);
|
|
45520
|
-
|
|
45521
|
-
|
|
45458
|
+
summaryRenderer.finalize();
|
|
45459
|
+
const discussion2 = discussionManager.load(discussionId);
|
|
45460
|
+
if (discussion2) {
|
|
45461
|
+
console.log(`
|
|
45462
|
+
${c.success("✔")} ${c.success("Discussion completed!")}
|
|
45522
45463
|
`);
|
|
45523
|
-
|
|
45524
|
-
}
|
|
45525
|
-
console.log(c.dim(" ─".repeat(30)));
|
|
45526
|
-
console.log();
|
|
45527
|
-
for (const message of session2.messages) {
|
|
45528
|
-
const role = message.role === "user" ? c.cyan("You") : c.green("AI");
|
|
45529
|
-
const content = message.content;
|
|
45530
|
-
console.log(` ${role}:`);
|
|
45531
|
-
const lines = content.split(`
|
|
45464
|
+
console.log(` ${c.dim("Messages:")} ${discussion2.messages.length} ${c.dim("Insights:")} ${discussion2.insights.length}
|
|
45532
45465
|
`);
|
|
45533
|
-
|
|
45534
|
-
|
|
45535
|
-
|
|
45536
|
-
console.log();
|
|
45537
|
-
}
|
|
45538
|
-
}
|
|
45539
|
-
async delete(sessionId) {
|
|
45540
|
-
if (!sessionId) {
|
|
45541
|
-
console.error(`
|
|
45542
|
-
${c.error("Error:")} Session ID is required
|
|
45466
|
+
}
|
|
45467
|
+
console.log(` ${c.dim("To review:")} ${c.cyan(`locus discuss --show ${discussionId}`)}`);
|
|
45468
|
+
console.log(` ${c.dim("To list all:")} ${c.cyan("locus discuss --list")}
|
|
45543
45469
|
`);
|
|
45544
|
-
|
|
45470
|
+
} catch (error48) {
|
|
45471
|
+
summaryRenderer.finalize();
|
|
45472
|
+
console.error(`
|
|
45473
|
+
${c.error("✖")} ${c.red("Failed to summarize:")} ${error48 instanceof Error ? error48.message : String(error48)}
|
|
45545
45474
|
`);
|
|
45546
|
-
|
|
45475
|
+
}
|
|
45476
|
+
inputHandler.stop();
|
|
45477
|
+
process.exit(0);
|
|
45478
|
+
return;
|
|
45479
|
+
}
|
|
45547
45480
|
}
|
|
45548
|
-
const
|
|
45549
|
-
if (
|
|
45550
|
-
|
|
45551
|
-
|
|
45481
|
+
const images = detectImages(trimmed);
|
|
45482
|
+
if (images.length > 0) {
|
|
45483
|
+
for (const img of images) {
|
|
45484
|
+
const status = img.exists ? c.success("attached") : c.warning("not found");
|
|
45485
|
+
process.stdout.write(` ${c.cyan(`[Image: ${imageDisplayName(img.path)}]`)} ${status}\r
|
|
45552
45486
|
`);
|
|
45553
|
-
|
|
45487
|
+
}
|
|
45554
45488
|
}
|
|
45555
|
-
const
|
|
45556
|
-
|
|
45557
|
-
|
|
45558
|
-
|
|
45489
|
+
const cleanedInput = stripImagePaths(trimmed, images);
|
|
45490
|
+
const effectiveInput = cleanedInput + buildImageContext(images);
|
|
45491
|
+
isProcessing = true;
|
|
45492
|
+
const chunkRenderer = new ProgressRenderer;
|
|
45493
|
+
try {
|
|
45494
|
+
chunkRenderer.showThinkingStarted();
|
|
45495
|
+
const stream4 = facilitator.continueDiscussionStream(discussionId, effectiveInput);
|
|
45496
|
+
let result = {
|
|
45497
|
+
response: "",
|
|
45498
|
+
insights: []
|
|
45499
|
+
};
|
|
45500
|
+
let iterResult = await stream4.next();
|
|
45501
|
+
while (!iterResult.done) {
|
|
45502
|
+
chunkRenderer.renderChunk(iterResult.value);
|
|
45503
|
+
iterResult = await stream4.next();
|
|
45504
|
+
}
|
|
45505
|
+
result = iterResult.value;
|
|
45506
|
+
chunkRenderer.finalize();
|
|
45507
|
+
if (result.insights.length > 0) {
|
|
45508
|
+
console.log("");
|
|
45509
|
+
for (const insight of result.insights) {
|
|
45510
|
+
const tag = formatInsightTag(insight.type);
|
|
45511
|
+
console.log(` ${tag} ${c.bold(insight.title)}`);
|
|
45512
|
+
console.log(` ${c.dim(insight.content)}
|
|
45559
45513
|
`);
|
|
45560
|
-
|
|
45514
|
+
}
|
|
45515
|
+
}
|
|
45516
|
+
} catch (error48) {
|
|
45517
|
+
chunkRenderer.finalize();
|
|
45561
45518
|
console.error(`
|
|
45562
|
-
${c.error("
|
|
45563
|
-
`);
|
|
45564
|
-
}
|
|
45565
|
-
}
|
|
45566
|
-
async clear() {
|
|
45567
|
-
const count = this.historyManager.getSessionCount();
|
|
45568
|
-
if (count === 0) {
|
|
45569
|
-
console.log(`
|
|
45570
|
-
${c.dim("No sessions to clear.")}
|
|
45519
|
+
${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
|
|
45571
45520
|
`);
|
|
45572
|
-
return;
|
|
45573
45521
|
}
|
|
45574
|
-
|
|
45575
|
-
|
|
45576
|
-
|
|
45577
|
-
`);
|
|
45578
|
-
}
|
|
45579
|
-
getShortId(sessionId) {
|
|
45580
|
-
const parts = sessionId.split("-");
|
|
45581
|
-
if (parts.length >= 3) {
|
|
45582
|
-
return parts.slice(-1)[0].slice(0, 8);
|
|
45522
|
+
isProcessing = false;
|
|
45523
|
+
if (!interrupted) {
|
|
45524
|
+
inputHandler.showPrompt();
|
|
45583
45525
|
}
|
|
45584
|
-
|
|
45585
|
-
|
|
45586
|
-
|
|
45587
|
-
|
|
45588
|
-
|
|
45589
|
-
|
|
45590
|
-
|
|
45591
|
-
${c.
|
|
45592
|
-
${c.success("show")} ${c.dim("<id>")} Show all messages in a session
|
|
45593
|
-
${c.success("delete")} ${c.dim("<id>")} Delete a specific session
|
|
45594
|
-
${c.success("clear")} Clear all exec sessions
|
|
45595
|
-
|
|
45596
|
-
${c.header(" EXAMPLES ")}
|
|
45597
|
-
${c.dim("$")} locus exec sessions list
|
|
45598
|
-
${c.dim("$")} locus exec sessions show e7f3a2b1
|
|
45599
|
-
${c.dim("$")} locus exec sessions delete e7f3a2b1
|
|
45600
|
-
${c.dim("$")} locus exec sessions clear
|
|
45601
|
-
|
|
45602
|
-
${c.dim("Session IDs can be partial (first 8 characters).")}
|
|
45526
|
+
};
|
|
45527
|
+
const inputHandler = new InputHandler({
|
|
45528
|
+
prompt: c.cyan("> "),
|
|
45529
|
+
continuationPrompt: c.dim("… "),
|
|
45530
|
+
onSubmit: (input) => {
|
|
45531
|
+
handleSubmit(input).catch((err) => {
|
|
45532
|
+
console.error(`
|
|
45533
|
+
${c.error("✖")} ${c.red(err instanceof Error ? err.message : String(err))}
|
|
45603
45534
|
`);
|
|
45604
|
-
|
|
45605
|
-
|
|
45606
|
-
// src/commands/exec.ts
|
|
45607
|
-
async function execCommand(args) {
|
|
45608
|
-
const { values, positionals } = parseArgs5({
|
|
45609
|
-
args,
|
|
45610
|
-
options: {
|
|
45611
|
-
model: { type: "string" },
|
|
45612
|
-
provider: { type: "string" },
|
|
45613
|
-
"reasoning-effort": { type: "string" },
|
|
45614
|
-
dir: { type: "string" },
|
|
45615
|
-
"no-stream": { type: "boolean" },
|
|
45616
|
-
"no-status": { type: "boolean" },
|
|
45617
|
-
interactive: { type: "boolean", short: "i" },
|
|
45618
|
-
session: { type: "string", short: "s" },
|
|
45619
|
-
"session-id": { type: "string" },
|
|
45620
|
-
"json-stream": { type: "boolean" }
|
|
45535
|
+
inputHandler.showPrompt();
|
|
45536
|
+
});
|
|
45621
45537
|
},
|
|
45622
|
-
|
|
45538
|
+
onInterrupt: () => {
|
|
45539
|
+
if (isProcessing) {
|
|
45540
|
+
interrupted = true;
|
|
45541
|
+
aiRunner.abort();
|
|
45542
|
+
isProcessing = false;
|
|
45543
|
+
console.log(c.dim(`
|
|
45544
|
+
[Interrupted]`));
|
|
45545
|
+
inputHandler.showPrompt();
|
|
45546
|
+
} else {
|
|
45547
|
+
shutdown();
|
|
45548
|
+
}
|
|
45549
|
+
},
|
|
45550
|
+
onClose: () => shutdown()
|
|
45623
45551
|
});
|
|
45624
|
-
|
|
45625
|
-
|
|
45626
|
-
|
|
45627
|
-
|
|
45552
|
+
inputHandler.start();
|
|
45553
|
+
inputHandler.showPrompt();
|
|
45554
|
+
}
|
|
45555
|
+
function listDiscussions(discussionManager) {
|
|
45556
|
+
const discussions = discussionManager.list();
|
|
45557
|
+
if (discussions.length === 0) {
|
|
45558
|
+
console.log(`
|
|
45559
|
+
${c.dim("No discussions found.")}
|
|
45560
|
+
`);
|
|
45561
|
+
console.log(` ${c.dim("Start one with:")} ${c.cyan('locus discuss "your topic"')}
|
|
45562
|
+
`);
|
|
45628
45563
|
return;
|
|
45629
45564
|
}
|
|
45630
|
-
|
|
45631
|
-
|
|
45632
|
-
|
|
45633
|
-
|
|
45634
|
-
const
|
|
45635
|
-
|
|
45636
|
-
|
|
45637
|
-
|
|
45638
|
-
|
|
45639
|
-
case "show":
|
|
45640
|
-
await cmds.show(sessionArg);
|
|
45641
|
-
return;
|
|
45642
|
-
case "delete":
|
|
45643
|
-
await cmds.delete(sessionArg);
|
|
45644
|
-
return;
|
|
45645
|
-
case "clear":
|
|
45646
|
-
await cmds.clear();
|
|
45647
|
-
return;
|
|
45648
|
-
default:
|
|
45649
|
-
showSessionsHelp();
|
|
45650
|
-
return;
|
|
45651
|
-
}
|
|
45652
|
-
}
|
|
45653
|
-
const execSettings = new SettingsManager(projectPath).load();
|
|
45654
|
-
const provider = resolveProvider3(values.provider || execSettings.provider);
|
|
45655
|
-
const model = values.model || execSettings.model || DEFAULT_MODEL[provider];
|
|
45656
|
-
const isInteractive = values.interactive;
|
|
45657
|
-
const sessionId = values.session;
|
|
45658
|
-
if (isInteractive) {
|
|
45659
|
-
const { InteractiveSession: InteractiveSession2 } = await Promise.resolve().then(() => (init_interactive_session(), exports_interactive_session));
|
|
45660
|
-
const session2 = new InteractiveSession2({
|
|
45661
|
-
projectPath,
|
|
45662
|
-
provider,
|
|
45663
|
-
model,
|
|
45664
|
-
sessionId
|
|
45665
|
-
});
|
|
45666
|
-
await session2.start();
|
|
45667
|
-
return;
|
|
45565
|
+
console.log(`
|
|
45566
|
+
${c.header(" DISCUSSIONS ")} ${c.dim(`(${discussions.length})`)}
|
|
45567
|
+
`);
|
|
45568
|
+
for (const disc of discussions) {
|
|
45569
|
+
const statusIcon = disc.status === "active" ? c.warning("◯") : disc.status === "completed" ? c.success("✔") : c.dim("⊘");
|
|
45570
|
+
console.log(` ${statusIcon} ${c.bold(disc.title)} ${c.dim(`[${disc.status}]`)} ${c.dim(`— ${disc.messages.length} messages, ${disc.insights.length} insights`)}`);
|
|
45571
|
+
console.log(` ${c.dim("ID:")} ${disc.id}`);
|
|
45572
|
+
console.log(` ${c.dim("Created:")} ${disc.createdAt}`);
|
|
45573
|
+
console.log("");
|
|
45668
45574
|
}
|
|
45669
|
-
|
|
45670
|
-
|
|
45671
|
-
|
|
45575
|
+
}
|
|
45576
|
+
function showDiscussion(discussionManager, id) {
|
|
45577
|
+
const md = discussionManager.getMarkdown(id);
|
|
45578
|
+
if (!md) {
|
|
45579
|
+
console.error(`
|
|
45580
|
+
${c.error("✖")} ${c.red(`Discussion not found: ${id}`)}
|
|
45581
|
+
`);
|
|
45672
45582
|
process.exit(1);
|
|
45673
|
-
}
|
|
45674
|
-
|
|
45675
|
-
|
|
45676
|
-
const aiRunner = createAiRunner(provider, {
|
|
45677
|
-
projectPath,
|
|
45678
|
-
model,
|
|
45679
|
-
reasoningEffort
|
|
45680
|
-
});
|
|
45681
|
-
const builder = new PromptBuilder(projectPath);
|
|
45682
|
-
const fullPrompt = await builder.buildGenericPrompt(promptInput);
|
|
45683
|
-
console.log("");
|
|
45684
|
-
console.log(`${c.primary("\uD83D\uDE80")} ${c.bold("Executing prompt with repository context...")}`);
|
|
45685
|
-
console.log("");
|
|
45686
|
-
let responseContent = "";
|
|
45687
|
-
try {
|
|
45688
|
-
if (useStreaming) {
|
|
45689
|
-
const renderer = new ProgressRenderer;
|
|
45690
|
-
const statsTracker = new ExecutionStatsTracker;
|
|
45691
|
-
const stream4 = aiRunner.runStream(fullPrompt);
|
|
45692
|
-
renderer.showThinkingStarted();
|
|
45693
|
-
for await (const chunk of stream4) {
|
|
45694
|
-
switch (chunk.type) {
|
|
45695
|
-
case "text_delta":
|
|
45696
|
-
renderer.renderTextDelta(chunk.content);
|
|
45697
|
-
responseContent += chunk.content;
|
|
45698
|
-
break;
|
|
45699
|
-
case "tool_use":
|
|
45700
|
-
statsTracker.toolStarted(chunk.tool, chunk.id);
|
|
45701
|
-
renderer.showToolStarted(chunk.tool, chunk.id);
|
|
45702
|
-
break;
|
|
45703
|
-
case "thinking":
|
|
45704
|
-
renderer.showThinkingStarted();
|
|
45705
|
-
break;
|
|
45706
|
-
case "tool_result":
|
|
45707
|
-
if (chunk.success) {
|
|
45708
|
-
statsTracker.toolCompleted(chunk.tool, chunk.id);
|
|
45709
|
-
renderer.showToolCompleted(chunk.tool, undefined, chunk.id);
|
|
45710
|
-
} else {
|
|
45711
|
-
statsTracker.toolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
|
|
45712
|
-
renderer.showToolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
|
|
45713
|
-
}
|
|
45714
|
-
break;
|
|
45715
|
-
case "result":
|
|
45716
|
-
break;
|
|
45717
|
-
case "error": {
|
|
45718
|
-
statsTracker.setError(chunk.error);
|
|
45719
|
-
renderer.renderError(chunk.error);
|
|
45720
|
-
renderer.finalize();
|
|
45721
|
-
const errorStats = statsTracker.finalize();
|
|
45722
|
-
renderer.showSummary(errorStats);
|
|
45723
|
-
console.error(`
|
|
45724
|
-
${c.error("✖")} ${c.error("Execution failed!")}
|
|
45583
|
+
}
|
|
45584
|
+
console.log(`
|
|
45585
|
+
${md}
|
|
45725
45586
|
`);
|
|
45726
|
-
|
|
45727
|
-
|
|
45728
|
-
|
|
45729
|
-
|
|
45730
|
-
renderer.finalize();
|
|
45731
|
-
const stats = statsTracker.finalize();
|
|
45732
|
-
renderer.showSummary(stats);
|
|
45733
|
-
} else {
|
|
45734
|
-
responseContent = await aiRunner.run(fullPrompt);
|
|
45735
|
-
console.log(responseContent);
|
|
45736
|
-
}
|
|
45587
|
+
}
|
|
45588
|
+
function archiveDiscussion(discussionManager, id) {
|
|
45589
|
+
try {
|
|
45590
|
+
discussionManager.archive(id);
|
|
45737
45591
|
console.log(`
|
|
45738
|
-
${c.success("✔")} ${c.
|
|
45592
|
+
${c.success("✔")} ${c.dim("Discussion archived.")}
|
|
45739
45593
|
`);
|
|
45740
45594
|
} catch (error48) {
|
|
45741
45595
|
console.error(`
|
|
45742
|
-
${c.error("✖")} ${c.
|
|
45596
|
+
${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
|
|
45743
45597
|
`);
|
|
45744
45598
|
process.exit(1);
|
|
45745
45599
|
}
|
|
45746
45600
|
}
|
|
45747
|
-
|
|
45748
|
-
const sessionId = values["session-id"] ?? values.session ?? randomUUID2();
|
|
45749
|
-
const execSettings = new SettingsManager(projectPath).load();
|
|
45750
|
-
const provider = resolveProvider3(values.provider || execSettings.provider);
|
|
45751
|
-
const model = values.model || execSettings.model || DEFAULT_MODEL[provider];
|
|
45752
|
-
const renderer = new JsonStreamRenderer({
|
|
45753
|
-
sessionId,
|
|
45754
|
-
command: "exec",
|
|
45755
|
-
model,
|
|
45756
|
-
provider,
|
|
45757
|
-
cwd: projectPath
|
|
45758
|
-
});
|
|
45759
|
-
const handleSignal = () => {
|
|
45760
|
-
if (renderer.isDone()) {
|
|
45761
|
-
return;
|
|
45762
|
-
}
|
|
45763
|
-
renderer.emitFatalError("PROCESS_CRASHED", "Process terminated by signal");
|
|
45764
|
-
if (process.stdout.writableNeedDrain) {
|
|
45765
|
-
process.stdout.once("drain", () => process.exit(1));
|
|
45766
|
-
} else {
|
|
45767
|
-
process.exit(1);
|
|
45768
|
-
}
|
|
45769
|
-
};
|
|
45770
|
-
process.on("SIGINT", handleSignal);
|
|
45771
|
-
process.on("SIGTERM", handleSignal);
|
|
45601
|
+
function deleteDiscussion(discussionManager, id) {
|
|
45772
45602
|
try {
|
|
45773
|
-
|
|
45774
|
-
|
|
45775
|
-
|
|
45776
|
-
|
|
45777
|
-
process.exit(1);
|
|
45778
|
-
}
|
|
45779
|
-
const promptInput = positionals.join(" ");
|
|
45780
|
-
if (!promptInput) {
|
|
45781
|
-
renderer.emitFatalError("UNKNOWN", 'Prompt is required. Usage: locus exec --json-stream "your prompt"');
|
|
45782
|
-
process.exit(1);
|
|
45783
|
-
}
|
|
45784
|
-
renderer.emitStart();
|
|
45785
|
-
renderer.emitStatus("running", "Building prompt context");
|
|
45786
|
-
const aiRunner = createAiRunner(provider, {
|
|
45787
|
-
projectPath,
|
|
45788
|
-
model
|
|
45789
|
-
});
|
|
45790
|
-
const builder = new PromptBuilder(projectPath);
|
|
45791
|
-
const fullPrompt = await builder.buildGenericPrompt(promptInput);
|
|
45792
|
-
renderer.emitStatus("streaming", "Streaming AI response");
|
|
45793
|
-
const stream4 = aiRunner.runStream(fullPrompt);
|
|
45794
|
-
for await (const chunk of stream4) {
|
|
45795
|
-
renderer.handleChunk(chunk);
|
|
45796
|
-
}
|
|
45797
|
-
renderer.emitDone(0);
|
|
45798
|
-
process.removeListener("SIGINT", handleSignal);
|
|
45799
|
-
process.removeListener("SIGTERM", handleSignal);
|
|
45603
|
+
discussionManager.delete(id);
|
|
45604
|
+
console.log(`
|
|
45605
|
+
${c.success("✔")} ${c.dim("Discussion deleted.")}
|
|
45606
|
+
`);
|
|
45800
45607
|
} catch (error48) {
|
|
45801
|
-
|
|
45802
|
-
|
|
45803
|
-
|
|
45804
|
-
}
|
|
45608
|
+
console.error(`
|
|
45609
|
+
${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
|
|
45610
|
+
`);
|
|
45805
45611
|
process.exit(1);
|
|
45806
45612
|
}
|
|
45807
45613
|
}
|
|
45808
|
-
|
|
45809
|
-
|
|
45810
|
-
|
|
45614
|
+
function showCurrentInsights(discussionManager, discussionId) {
|
|
45615
|
+
const discussion2 = discussionManager.load(discussionId);
|
|
45616
|
+
if (!discussion2 || discussion2.insights.length === 0) {
|
|
45617
|
+
console.log(`
|
|
45618
|
+
${c.dim("No insights extracted yet.")}
|
|
45619
|
+
`);
|
|
45620
|
+
return;
|
|
45621
|
+
}
|
|
45811
45622
|
console.log(`
|
|
45812
|
-
${c.header("
|
|
45813
|
-
|
|
45814
|
-
|
|
45815
|
-
|
|
45816
|
-
${c.
|
|
45817
|
-
${c.
|
|
45818
|
-
|
|
45819
|
-
|
|
45820
|
-
|
|
45821
|
-
|
|
45822
|
-
|
|
45823
|
-
|
|
45824
|
-
|
|
45825
|
-
|
|
45826
|
-
|
|
45827
|
-
|
|
45828
|
-
|
|
45829
|
-
|
|
45830
|
-
|
|
45831
|
-
|
|
45832
|
-
|
|
45833
|
-
|
|
45834
|
-
|
|
45835
|
-
|
|
45836
|
-
|
|
45837
|
-
|
|
45838
|
-
|
|
45839
|
-
|
|
45840
|
-
|
|
45841
|
-
${c.dim("--session, -s <id> Resume a previous session")}
|
|
45842
|
-
${c.dim("sessions list List recent sessions")}
|
|
45843
|
-
${c.dim("sessions show <id> Show session messages")}
|
|
45844
|
-
${c.dim("sessions delete <id> Delete a session")}
|
|
45845
|
-
${c.dim("sessions clear Clear all sessions")}
|
|
45846
|
-
${c.success("service")} Manage the Locus system service
|
|
45847
|
-
${c.dim("install Install as systemd/launchd service")}
|
|
45848
|
-
${c.dim("uninstall Remove the system service")}
|
|
45849
|
-
${c.dim("status Check if service is running")}
|
|
45850
|
-
${c.success("artifacts")} List and manage knowledge artifacts
|
|
45851
|
-
${c.dim("show <name> Show artifact content")}
|
|
45852
|
-
${c.dim("plan <name> Convert artifact to a plan")}
|
|
45853
|
-
${c.success("version")} Show installed package versions
|
|
45854
|
-
${c.success("upgrade")} Update CLI and Telegram to the latest version
|
|
45623
|
+
${c.header(" INSIGHTS ")} ${c.dim(`(${discussion2.insights.length})`)}
|
|
45624
|
+
`);
|
|
45625
|
+
for (const insight of discussion2.insights) {
|
|
45626
|
+
const tag = formatInsightTag(insight.type);
|
|
45627
|
+
console.log(` ${tag} ${c.bold(insight.title)}`);
|
|
45628
|
+
console.log(` ${c.dim(insight.content)}`);
|
|
45629
|
+
if (insight.tags.length > 0) {
|
|
45630
|
+
console.log(` ${c.dim(`Tags: ${insight.tags.join(", ")}`)}`);
|
|
45631
|
+
}
|
|
45632
|
+
console.log("");
|
|
45633
|
+
}
|
|
45634
|
+
}
|
|
45635
|
+
function formatInsightTag(type) {
|
|
45636
|
+
switch (type) {
|
|
45637
|
+
case "decision":
|
|
45638
|
+
return c.green("[DECISION]");
|
|
45639
|
+
case "requirement":
|
|
45640
|
+
return c.blue("[REQUIREMENT]");
|
|
45641
|
+
case "idea":
|
|
45642
|
+
return c.yellow("[IDEA]");
|
|
45643
|
+
case "concern":
|
|
45644
|
+
return c.red("[CONCERN]");
|
|
45645
|
+
case "learning":
|
|
45646
|
+
return c.cyan("[LEARNING]");
|
|
45647
|
+
}
|
|
45648
|
+
}
|
|
45649
|
+
function showReplHelp() {
|
|
45650
|
+
console.log(`
|
|
45651
|
+
${c.header(" DISCUSSION COMMANDS ")}
|
|
45855
45652
|
|
|
45856
|
-
|
|
45857
|
-
${c.
|
|
45858
|
-
${c.
|
|
45859
|
-
${c.
|
|
45860
|
-
${c.secondary("--reasoning-effort")} <level> Codex reasoning effort: ${c.dim("low, medium, high")} (default: model default)
|
|
45653
|
+
${c.cyan("summary")} Generate a final summary and end the discussion
|
|
45654
|
+
${c.cyan("insights")} Show all insights extracted so far
|
|
45655
|
+
${c.cyan("exit")} Save and exit without generating a summary
|
|
45656
|
+
${c.cyan("help")} Show this help message
|
|
45861
45657
|
|
|
45862
|
-
${c.header("
|
|
45863
|
-
${c.dim("$")} ${c.primary("locus init")}
|
|
45864
|
-
${c.dim("$")} ${c.primary("locus config setup")}
|
|
45865
|
-
${c.dim("$")} ${c.primary("locus telegram setup")}
|
|
45866
|
-
${c.dim("$")} ${c.primary("locus service install")}
|
|
45658
|
+
${c.header(" KEY BINDINGS ")}
|
|
45867
45659
|
|
|
45868
|
-
|
|
45869
|
-
${c.
|
|
45870
|
-
${c.
|
|
45871
|
-
${c.
|
|
45872
|
-
${c.
|
|
45873
|
-
${c.dim("$")} ${c.primary("locus telegram setup")}
|
|
45874
|
-
${c.dim("$")} ${c.primary('locus discuss "how should we design the auth system?"')}
|
|
45875
|
-
${c.dim("$")} ${c.primary("locus exec sessions list")}
|
|
45876
|
-
${c.dim("$")} ${c.primary("locus artifacts")}
|
|
45877
|
-
${c.dim("$")} ${c.primary("locus service install")}
|
|
45660
|
+
${c.cyan("Enter")} Send message
|
|
45661
|
+
${c.cyan("Shift+Enter")} Insert newline (also: Alt+Enter, Ctrl+J)
|
|
45662
|
+
${c.cyan("Ctrl+C")} Interrupt / clear input / exit
|
|
45663
|
+
${c.cyan("Ctrl+U")} Clear current input
|
|
45664
|
+
${c.cyan("Ctrl+W")} Delete last word
|
|
45878
45665
|
|
|
45879
|
-
|
|
45666
|
+
${c.dim("Type anything else to continue the discussion.")}
|
|
45880
45667
|
`);
|
|
45881
45668
|
}
|
|
45882
|
-
|
|
45883
|
-
|
|
45884
|
-
|
|
45885
|
-
import { parseArgs as parseArgs6 } from "node:util";
|
|
45669
|
+
function showDiscussHelp() {
|
|
45670
|
+
console.log(`
|
|
45671
|
+
${c.header(" LOCUS DISCUSS ")} ${c.dim("— Interactive AI Discussion")}
|
|
45886
45672
|
|
|
45887
|
-
|
|
45888
|
-
|
|
45673
|
+
${c.bold("Usage:")}
|
|
45674
|
+
${c.cyan('locus discuss "topic"')} Start a discussion on a topic
|
|
45675
|
+
${c.cyan("locus discuss --list")} List all discussions
|
|
45676
|
+
${c.cyan("locus discuss --show <id>")} Show discussion details
|
|
45677
|
+
${c.cyan("locus discuss --archive <id>")} Archive a discussion
|
|
45678
|
+
${c.cyan("locus discuss --delete <id>")} Delete a discussion
|
|
45889
45679
|
|
|
45890
|
-
|
|
45891
|
-
|
|
45892
|
-
|
|
45893
|
-
|
|
45894
|
-
|
|
45895
|
-
async summarize(tree) {
|
|
45896
|
-
const prompt = `Analyze this file tree and generate a JSON index.
|
|
45897
|
-
Return ONLY a JSON object with this structure:
|
|
45898
|
-
{
|
|
45899
|
-
"symbols": {},
|
|
45900
|
-
"responsibilities": { "path": "Description" }
|
|
45901
|
-
}
|
|
45680
|
+
${c.bold("Options:")}
|
|
45681
|
+
${c.dim("--model <model>")} AI model (claude: opus, sonnet, haiku | codex: gpt-5.3-codex, gpt-5-codex-mini)
|
|
45682
|
+
${c.dim("--provider <p>")} AI provider (claude, codex)
|
|
45683
|
+
${c.dim("--reasoning-effort <level>")} Reasoning effort (low, medium, high)
|
|
45684
|
+
${c.dim("--dir <path>")} Project directory
|
|
45902
45685
|
|
|
45903
|
-
|
|
45904
|
-
${
|
|
45905
|
-
|
|
45906
|
-
|
|
45907
|
-
|
|
45686
|
+
${c.bold("REPL Commands:")}
|
|
45687
|
+
${c.dim("summary")} Generate final summary and end the discussion
|
|
45688
|
+
${c.dim("insights")} Show all insights extracted so far
|
|
45689
|
+
${c.dim("exit")} Save and exit without generating a summary
|
|
45690
|
+
${c.dim("help")} Show available commands
|
|
45691
|
+
|
|
45692
|
+
${c.bold("Examples:")}
|
|
45693
|
+
${c.dim("# Start a discussion about architecture")}
|
|
45694
|
+
${c.cyan('locus discuss "how should we structure the auth system?"')}
|
|
45695
|
+
|
|
45696
|
+
${c.dim("# Review a past discussion")}
|
|
45697
|
+
${c.cyan("locus discuss --show disc-1234567890")}
|
|
45698
|
+
|
|
45699
|
+
${c.dim("# List all discussions")}
|
|
45700
|
+
${c.cyan("locus discuss --list")}
|
|
45701
|
+
`);
|
|
45702
|
+
}
|
|
45703
|
+
// src/commands/docs.ts
|
|
45704
|
+
init_index_node();
|
|
45705
|
+
init_config_manager();
|
|
45706
|
+
init_settings_manager();
|
|
45707
|
+
init_utils3();
|
|
45708
|
+
init_workspace_resolver();
|
|
45709
|
+
import { parseArgs as parseArgs4 } from "node:util";
|
|
45710
|
+
async function docsCommand(args) {
|
|
45711
|
+
const subcommand = args[0];
|
|
45712
|
+
const subArgs = args.slice(1);
|
|
45713
|
+
switch (subcommand) {
|
|
45714
|
+
case "sync":
|
|
45715
|
+
await docsSyncCommand(subArgs);
|
|
45716
|
+
break;
|
|
45717
|
+
default:
|
|
45718
|
+
showDocsHelp();
|
|
45719
|
+
break;
|
|
45908
45720
|
}
|
|
45909
45721
|
}
|
|
45910
|
-
|
|
45911
|
-
|
|
45912
|
-
init_utils3();
|
|
45913
|
-
async function indexCommand(args) {
|
|
45914
|
-
const { values } = parseArgs6({
|
|
45722
|
+
async function docsSyncCommand(args) {
|
|
45723
|
+
const { values } = parseArgs4({
|
|
45915
45724
|
args,
|
|
45916
45725
|
options: {
|
|
45726
|
+
"api-key": { type: "string" },
|
|
45727
|
+
"api-url": { type: "string" },
|
|
45728
|
+
workspace: { type: "string" },
|
|
45917
45729
|
dir: { type: "string" },
|
|
45918
|
-
|
|
45919
|
-
provider: { type: "string" }
|
|
45730
|
+
help: { type: "boolean" }
|
|
45920
45731
|
},
|
|
45921
45732
|
strict: false
|
|
45922
45733
|
});
|
|
45734
|
+
if (values.help) {
|
|
45735
|
+
showDocsSyncHelp();
|
|
45736
|
+
return;
|
|
45737
|
+
}
|
|
45923
45738
|
const projectPath = values.dir || process.cwd();
|
|
45924
|
-
requireInitialization(projectPath, "
|
|
45925
|
-
new ConfigManager(projectPath)
|
|
45926
|
-
|
|
45927
|
-
const
|
|
45928
|
-
const
|
|
45739
|
+
requireInitialization(projectPath, "docs sync");
|
|
45740
|
+
const configManager = new ConfigManager(projectPath);
|
|
45741
|
+
configManager.updateVersion(VERSION2);
|
|
45742
|
+
const settingsManager = new SettingsManager(projectPath);
|
|
45743
|
+
const settings = settingsManager.load();
|
|
45744
|
+
const apiKey = values["api-key"] || settings.apiKey;
|
|
45745
|
+
if (!apiKey) {
|
|
45746
|
+
console.error(`
|
|
45747
|
+
${c.error("✖")} ${c.red("API key is required")}
|
|
45748
|
+
` + ` ${c.dim(`Configure with: locus config setup --api-key <key>
|
|
45749
|
+
Or pass --api-key flag`)}
|
|
45750
|
+
`);
|
|
45751
|
+
process.exit(1);
|
|
45752
|
+
}
|
|
45753
|
+
const apiBase = values["api-url"] || settings.apiUrl || "https://api.locusai.dev/api";
|
|
45754
|
+
const resolver = new WorkspaceResolver({
|
|
45755
|
+
apiKey,
|
|
45756
|
+
apiBase,
|
|
45757
|
+
workspaceId: values.workspace
|
|
45758
|
+
});
|
|
45759
|
+
let workspaceId;
|
|
45760
|
+
try {
|
|
45761
|
+
workspaceId = await resolver.resolve();
|
|
45762
|
+
} catch (error48) {
|
|
45763
|
+
console.error(`
|
|
45764
|
+
${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
|
|
45765
|
+
`);
|
|
45766
|
+
process.exit(1);
|
|
45767
|
+
}
|
|
45768
|
+
const client = new LocusClient({
|
|
45769
|
+
baseUrl: apiBase,
|
|
45770
|
+
token: apiKey
|
|
45771
|
+
});
|
|
45772
|
+
const fetcher = new DocumentFetcher({
|
|
45773
|
+
client,
|
|
45774
|
+
workspaceId,
|
|
45929
45775
|
projectPath,
|
|
45930
|
-
|
|
45776
|
+
log: (message, level) => {
|
|
45777
|
+
if (level === "error") {
|
|
45778
|
+
console.log(` ${c.error("✖")} ${message}`);
|
|
45779
|
+
return;
|
|
45780
|
+
}
|
|
45781
|
+
if (level === "warn") {
|
|
45782
|
+
console.log(` ${c.warning("!")} ${message}`);
|
|
45783
|
+
return;
|
|
45784
|
+
}
|
|
45785
|
+
if (level === "success") {
|
|
45786
|
+
console.log(` ${c.success("✔")} ${message}`);
|
|
45787
|
+
return;
|
|
45788
|
+
}
|
|
45789
|
+
console.log(` ${c.info("●")} ${message}`);
|
|
45790
|
+
}
|
|
45931
45791
|
});
|
|
45932
|
-
const summarizer = new TreeSummarizer(aiRunner);
|
|
45933
|
-
const indexer = new CodebaseIndexer(projectPath);
|
|
45934
45792
|
console.log(`
|
|
45935
|
-
${c.
|
|
45793
|
+
${c.info("●")} ${c.bold("Syncing docs from API...")}
|
|
45794
|
+
`);
|
|
45936
45795
|
try {
|
|
45937
|
-
|
|
45938
|
-
|
|
45939
|
-
|
|
45940
|
-
|
|
45796
|
+
await fetcher.fetch();
|
|
45797
|
+
console.log(`
|
|
45798
|
+
${c.success("✔")} ${c.success("Docs sync complete.")} ${c.dim("Local docs: .locus/documents")}
|
|
45799
|
+
`);
|
|
45941
45800
|
} catch (error48) {
|
|
45942
45801
|
console.error(`
|
|
45943
|
-
${c.error("✖")} ${c.
|
|
45944
|
-
|
|
45945
|
-
|
|
45802
|
+
${c.error("✖")} ${c.red(`Docs sync failed: ${error48 instanceof Error ? error48.message : String(error48)}`)}
|
|
45803
|
+
`);
|
|
45804
|
+
process.exit(1);
|
|
45946
45805
|
}
|
|
45806
|
+
}
|
|
45807
|
+
function showDocsHelp() {
|
|
45947
45808
|
console.log(`
|
|
45948
|
-
${c.
|
|
45809
|
+
${c.header(" DOCS ")}
|
|
45810
|
+
${c.primary("locus docs")} ${c.dim("<command> [options]")}
|
|
45811
|
+
|
|
45812
|
+
${c.header(" COMMANDS ")}
|
|
45813
|
+
${c.success("sync")} Sync workspace docs from API to .locus/documents
|
|
45814
|
+
|
|
45815
|
+
${c.header(" EXAMPLES ")}
|
|
45816
|
+
${c.dim("$")} ${c.primary("locus docs sync")}
|
|
45817
|
+
${c.dim("$")} ${c.primary("locus docs sync --workspace ws_123")}
|
|
45949
45818
|
`);
|
|
45950
45819
|
}
|
|
45951
|
-
|
|
45952
|
-
|
|
45953
|
-
|
|
45954
|
-
|
|
45955
|
-
async function initCommand() {
|
|
45956
|
-
const projectPath = process.cwd();
|
|
45957
|
-
const configManager = new ConfigManager(projectPath);
|
|
45958
|
-
if (isProjectInitialized(projectPath)) {
|
|
45959
|
-
console.log(`
|
|
45960
|
-
${c.info("ℹ️")} ${c.bold("Locus is already initialized. Updating configuration...")}
|
|
45961
|
-
`);
|
|
45962
|
-
const result = await configManager.reinit(VERSION2);
|
|
45963
|
-
const updates = [];
|
|
45964
|
-
if (result.versionUpdated) {
|
|
45965
|
-
updates.push(`Version updated: ${c.dim(result.previousVersion || "unknown")} → ${c.primary(VERSION2)}`);
|
|
45966
|
-
}
|
|
45967
|
-
if (result.directoriesCreated.length > 0) {
|
|
45968
|
-
updates.push(`Directories created: ${result.directoriesCreated.map((d) => c.dim(d)).join(", ")}`);
|
|
45969
|
-
}
|
|
45970
|
-
if (result.gitignoreUpdated) {
|
|
45971
|
-
updates.push(`Gitignore updated with Locus patterns`);
|
|
45972
|
-
}
|
|
45973
|
-
if (updates.length === 0) {
|
|
45974
|
-
console.log(` ${c.success("✔")} ${c.success("Configuration is already up to date!")}
|
|
45820
|
+
function showDocsSyncHelp() {
|
|
45821
|
+
console.log(`
|
|
45822
|
+
${c.header(" DOCS SYNC ")}
|
|
45823
|
+
${c.primary("locus docs sync")} ${c.dim("[options]")}
|
|
45975
45824
|
|
|
45976
|
-
${c.
|
|
45977
|
-
}
|
|
45978
|
-
|
|
45825
|
+
${c.header(" OPTIONS ")}
|
|
45826
|
+
${c.secondary("--api-key")} <key> API key override (reads from settings.json)
|
|
45827
|
+
${c.secondary("--api-url")} <url> API base URL (default: https://api.locusai.dev/api)
|
|
45828
|
+
${c.secondary("--workspace")} <id> Workspace ID (optional if persisted or resolvable)
|
|
45829
|
+
${c.secondary("--dir")} <path> Project directory (default: current)
|
|
45830
|
+
${c.secondary("--help")} Show docs sync help
|
|
45979
45831
|
|
|
45980
|
-
${c.
|
|
45981
|
-
${
|
|
45982
|
-
|
|
45832
|
+
${c.header(" EXAMPLES ")}
|
|
45833
|
+
${c.dim("$")} ${c.primary("locus docs sync")}
|
|
45834
|
+
${c.dim("$")} ${c.primary("locus docs sync --workspace ws_123")}
|
|
45983
45835
|
`);
|
|
45836
|
+
}
|
|
45837
|
+
// src/commands/exec.ts
|
|
45838
|
+
init_index_node();
|
|
45839
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
45840
|
+
import { parseArgs as parseArgs5 } from "node:util";
|
|
45841
|
+
|
|
45842
|
+
// src/display/json-stream-renderer.ts
|
|
45843
|
+
init_src();
|
|
45844
|
+
|
|
45845
|
+
class JsonStreamRenderer {
|
|
45846
|
+
sessionId;
|
|
45847
|
+
command;
|
|
45848
|
+
model;
|
|
45849
|
+
provider;
|
|
45850
|
+
cwd;
|
|
45851
|
+
statsTracker;
|
|
45852
|
+
started = false;
|
|
45853
|
+
done = false;
|
|
45854
|
+
constructor(options) {
|
|
45855
|
+
this.sessionId = options.sessionId;
|
|
45856
|
+
this.command = options.command;
|
|
45857
|
+
this.model = options.model;
|
|
45858
|
+
this.provider = options.provider;
|
|
45859
|
+
this.cwd = options.cwd;
|
|
45860
|
+
this.statsTracker = new ExecutionStatsTracker;
|
|
45861
|
+
}
|
|
45862
|
+
emitStart() {
|
|
45863
|
+
if (this.started)
|
|
45864
|
+
return;
|
|
45865
|
+
this.started = true;
|
|
45866
|
+
this.emit(createCliStreamEvent(CliStreamEventType.START, this.sessionId, {
|
|
45867
|
+
command: this.command,
|
|
45868
|
+
model: this.model,
|
|
45869
|
+
provider: this.provider,
|
|
45870
|
+
cwd: this.cwd
|
|
45871
|
+
}));
|
|
45872
|
+
}
|
|
45873
|
+
handleChunk(chunk) {
|
|
45874
|
+
this.ensureStarted();
|
|
45875
|
+
switch (chunk.type) {
|
|
45876
|
+
case "text_delta":
|
|
45877
|
+
this.emit(createCliStreamEvent(CliStreamEventType.TEXT_DELTA, this.sessionId, {
|
|
45878
|
+
content: chunk.content
|
|
45879
|
+
}));
|
|
45880
|
+
break;
|
|
45881
|
+
case "thinking":
|
|
45882
|
+
this.emit(createCliStreamEvent(CliStreamEventType.THINKING, this.sessionId, {
|
|
45883
|
+
content: chunk.content
|
|
45884
|
+
}));
|
|
45885
|
+
break;
|
|
45886
|
+
case "tool_use":
|
|
45887
|
+
this.statsTracker.toolStarted(chunk.tool, chunk.id);
|
|
45888
|
+
this.emit(createCliStreamEvent(CliStreamEventType.TOOL_STARTED, this.sessionId, {
|
|
45889
|
+
tool: chunk.tool,
|
|
45890
|
+
toolId: chunk.id,
|
|
45891
|
+
parameters: chunk.parameters
|
|
45892
|
+
}));
|
|
45893
|
+
break;
|
|
45894
|
+
case "tool_result":
|
|
45895
|
+
if (chunk.success) {
|
|
45896
|
+
this.statsTracker.toolCompleted(chunk.tool, chunk.id);
|
|
45897
|
+
} else {
|
|
45898
|
+
this.statsTracker.toolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
|
|
45899
|
+
}
|
|
45900
|
+
this.emit(createCliStreamEvent(CliStreamEventType.TOOL_COMPLETED, this.sessionId, {
|
|
45901
|
+
tool: chunk.tool,
|
|
45902
|
+
toolId: chunk.id,
|
|
45903
|
+
success: chunk.success,
|
|
45904
|
+
duration: chunk.duration,
|
|
45905
|
+
error: chunk.error
|
|
45906
|
+
}));
|
|
45907
|
+
break;
|
|
45908
|
+
case "tool_parameters":
|
|
45909
|
+
break;
|
|
45910
|
+
case "result":
|
|
45911
|
+
break;
|
|
45912
|
+
case "error":
|
|
45913
|
+
this.statsTracker.setError(chunk.error);
|
|
45914
|
+
this.emitError("UNKNOWN", chunk.error);
|
|
45915
|
+
break;
|
|
45916
|
+
}
|
|
45917
|
+
}
|
|
45918
|
+
emitStatus(status, message) {
|
|
45919
|
+
this.ensureStarted();
|
|
45920
|
+
this.emit(createCliStreamEvent(CliStreamEventType.STATUS, this.sessionId, {
|
|
45921
|
+
status,
|
|
45922
|
+
message
|
|
45923
|
+
}));
|
|
45924
|
+
}
|
|
45925
|
+
emitError(code, message, options) {
|
|
45926
|
+
this.ensureStarted();
|
|
45927
|
+
this.emit(createCliStreamEvent(CliStreamEventType.ERROR, this.sessionId, {
|
|
45928
|
+
error: createProtocolError(code, message, options)
|
|
45929
|
+
}));
|
|
45930
|
+
}
|
|
45931
|
+
emitDone(exitCode) {
|
|
45932
|
+
if (this.done)
|
|
45933
|
+
return;
|
|
45934
|
+
this.done = true;
|
|
45935
|
+
this.ensureStarted();
|
|
45936
|
+
const stats = this.statsTracker.finalize();
|
|
45937
|
+
this.emit(createCliStreamEvent(CliStreamEventType.DONE, this.sessionId, {
|
|
45938
|
+
exitCode,
|
|
45939
|
+
duration: stats.duration,
|
|
45940
|
+
toolsUsed: stats.toolsUsed.length > 0 ? stats.toolsUsed : undefined,
|
|
45941
|
+
tokensUsed: stats.tokensUsed,
|
|
45942
|
+
success: exitCode === 0
|
|
45943
|
+
}));
|
|
45944
|
+
}
|
|
45945
|
+
emitFatalError(code, message, options) {
|
|
45946
|
+
this.statsTracker.setError(message);
|
|
45947
|
+
this.emitError(code, message, {
|
|
45948
|
+
...options,
|
|
45949
|
+
recoverable: false
|
|
45950
|
+
});
|
|
45951
|
+
this.emitDone(1);
|
|
45952
|
+
}
|
|
45953
|
+
isDone() {
|
|
45954
|
+
return this.done;
|
|
45955
|
+
}
|
|
45956
|
+
ensureStarted() {
|
|
45957
|
+
if (!this.started) {
|
|
45958
|
+
this.emitStart();
|
|
45984
45959
|
}
|
|
45985
|
-
console.log(` ${c.bold("Next steps:")}
|
|
45986
|
-
1. Run '${c.primary("locus config setup")}' to configure your API key
|
|
45987
|
-
2. Run '${c.primary("locus index")}' to index your codebase
|
|
45988
|
-
3. Run '${c.primary("locus run")}' to start an agent
|
|
45989
|
-
|
|
45990
|
-
For more information, visit: ${c.underline("https://docs.locusai.dev")}
|
|
45991
|
-
`);
|
|
45992
|
-
return;
|
|
45993
45960
|
}
|
|
45994
|
-
|
|
45995
|
-
|
|
45996
|
-
${c.success("✨ Locus initialized successfully!")}
|
|
45997
|
-
|
|
45998
|
-
${c.bold("Created:")}
|
|
45999
|
-
${c.primary("\uD83D\uDCC1")} ${c.bold(".locus/")} ${c.dim("Configuration directory")}
|
|
46000
|
-
${c.primary("\uD83D\uDCC4")} ${c.bold(".locus/config.json")} ${c.dim("Project settings")}
|
|
46001
|
-
${c.primary("\uD83D\uDCDD")} ${c.bold(".locus/LOCUS.md")} ${c.dim("AI agent instructions")}
|
|
46002
|
-
${c.primary("\uD83D\uDCDD")} ${c.bold(".locus/LEARNINGS.md")} ${c.dim("Continuous learning log")}
|
|
46003
|
-
|
|
46004
|
-
${c.bold("Next steps:")}
|
|
46005
|
-
1. Run '${c.primary("locus config setup")}' to configure your API key
|
|
46006
|
-
2. Run '${c.primary("locus index")}' to index your codebase
|
|
46007
|
-
3. Run '${c.primary("locus run")}' to start an agent
|
|
46008
|
-
|
|
46009
|
-
For more information, visit: ${c.underline("https://docs.locusai.dev")}
|
|
45961
|
+
emit(event) {
|
|
45962
|
+
process.stdout.write(`${JSON.stringify(event)}
|
|
46010
45963
|
`);
|
|
45964
|
+
}
|
|
46011
45965
|
}
|
|
46012
45966
|
|
|
46013
|
-
// src/commands/
|
|
46014
|
-
|
|
46015
|
-
|
|
46016
|
-
// src/commands/review.ts
|
|
46017
|
-
init_index_node();
|
|
46018
|
-
init_config_manager();
|
|
45967
|
+
// src/commands/exec.ts
|
|
45968
|
+
init_progress_renderer();
|
|
46019
45969
|
init_settings_manager();
|
|
46020
45970
|
init_utils3();
|
|
46021
|
-
|
|
46022
|
-
|
|
46023
|
-
|
|
46024
|
-
|
|
46025
|
-
|
|
46026
|
-
const
|
|
46027
|
-
|
|
46028
|
-
|
|
45971
|
+
|
|
45972
|
+
// src/commands/exec-sessions.ts
|
|
45973
|
+
init_index_node();
|
|
45974
|
+
function formatRelativeTime(timestamp) {
|
|
45975
|
+
const now = Date.now();
|
|
45976
|
+
const diff = now - timestamp;
|
|
45977
|
+
const seconds = Math.floor(diff / 1000);
|
|
45978
|
+
const minutes = Math.floor(seconds / 60);
|
|
45979
|
+
const hours = Math.floor(minutes / 60);
|
|
45980
|
+
const days = Math.floor(hours / 24);
|
|
45981
|
+
if (days > 0) {
|
|
45982
|
+
return days === 1 ? "yesterday" : `${days} days ago`;
|
|
46029
45983
|
}
|
|
46030
|
-
|
|
46031
|
-
}
|
|
46032
|
-
async function reviewPrsCommand(args) {
|
|
46033
|
-
const { values } = parseArgs7({
|
|
46034
|
-
args,
|
|
46035
|
-
options: {
|
|
46036
|
-
"api-key": { type: "string" },
|
|
46037
|
-
workspace: { type: "string" },
|
|
46038
|
-
model: { type: "string" },
|
|
46039
|
-
provider: { type: "string" },
|
|
46040
|
-
"api-url": { type: "string" },
|
|
46041
|
-
dir: { type: "string" }
|
|
46042
|
-
},
|
|
46043
|
-
strict: false
|
|
46044
|
-
});
|
|
46045
|
-
const projectPath = values.dir || process.cwd();
|
|
46046
|
-
requireInitialization(projectPath, "review");
|
|
46047
|
-
const configManager = new ConfigManager(projectPath);
|
|
46048
|
-
configManager.updateVersion(VERSION2);
|
|
46049
|
-
const settingsManager = new SettingsManager(projectPath);
|
|
46050
|
-
const settings = settingsManager.load();
|
|
46051
|
-
const apiKey = values["api-key"] || settings.apiKey;
|
|
46052
|
-
if (!apiKey) {
|
|
46053
|
-
console.error(c.error("Error: API key is required for PR review"));
|
|
46054
|
-
console.error(c.dim(`Configure with: locus config setup --api-key <key>
|
|
46055
|
-
Or pass --api-key flag`));
|
|
46056
|
-
console.error(c.dim("For local staged-changes review, use: locus review local"));
|
|
46057
|
-
process.exit(1);
|
|
45984
|
+
if (hours > 0) {
|
|
45985
|
+
return hours === 1 ? "1 hour ago" : `${hours} hours ago`;
|
|
46058
45986
|
}
|
|
46059
|
-
|
|
46060
|
-
|
|
46061
|
-
const apiBase = values["api-url"] || settings.apiUrl || "https://api.locusai.dev/api";
|
|
46062
|
-
let workspaceId;
|
|
46063
|
-
try {
|
|
46064
|
-
const resolver = new WorkspaceResolver({
|
|
46065
|
-
apiKey,
|
|
46066
|
-
apiBase,
|
|
46067
|
-
workspaceId: values.workspace
|
|
46068
|
-
});
|
|
46069
|
-
workspaceId = await resolver.resolve();
|
|
46070
|
-
} catch (error48) {
|
|
46071
|
-
console.error(c.error(error48 instanceof Error ? error48.message : String(error48)));
|
|
46072
|
-
process.exit(1);
|
|
45987
|
+
if (minutes > 0) {
|
|
45988
|
+
return minutes === 1 ? "1 minute ago" : `${minutes} minutes ago`;
|
|
46073
45989
|
}
|
|
46074
|
-
|
|
46075
|
-
|
|
46076
|
-
|
|
46077
|
-
|
|
46078
|
-
|
|
46079
|
-
|
|
46080
|
-
|
|
46081
|
-
|
|
46082
|
-
|
|
46083
|
-
|
|
46084
|
-
|
|
46085
|
-
|
|
46086
|
-
|
|
45990
|
+
return "just now";
|
|
45991
|
+
}
|
|
45992
|
+
|
|
45993
|
+
class SessionCommands {
|
|
45994
|
+
historyManager;
|
|
45995
|
+
constructor(projectPath) {
|
|
45996
|
+
this.historyManager = new HistoryManager(projectPath);
|
|
45997
|
+
}
|
|
45998
|
+
async list() {
|
|
45999
|
+
const sessions = this.historyManager.listSessions();
|
|
46000
|
+
if (sessions.length === 0) {
|
|
46001
|
+
console.log(`
|
|
46002
|
+
${c.dim("No exec sessions found.")}
|
|
46003
|
+
`);
|
|
46004
|
+
return;
|
|
46005
|
+
}
|
|
46087
46006
|
console.log(`
|
|
46088
|
-
${c.
|
|
46007
|
+
${c.primary("Recent Exec Sessions:")}
|
|
46089
46008
|
`);
|
|
46090
|
-
|
|
46009
|
+
for (const session2 of sessions.slice(0, 10)) {
|
|
46010
|
+
const shortId = this.getShortId(session2.id);
|
|
46011
|
+
const age = formatRelativeTime(session2.updatedAt);
|
|
46012
|
+
const msgCount = session2.messages.length;
|
|
46013
|
+
const firstUserMsg = session2.messages.find((m) => m.role === "user");
|
|
46014
|
+
const preview = firstUserMsg ? firstUserMsg.content.slice(0, 50).replace(/\n/g, " ") : "(empty session)";
|
|
46015
|
+
console.log(` ${c.cyan(shortId)} ${c.gray("-")} ${preview}${firstUserMsg && firstUserMsg.content.length > 50 ? "..." : ""}`);
|
|
46016
|
+
console.log(` ${c.dim(`${msgCount} messages • ${age}`)}`);
|
|
46017
|
+
console.log();
|
|
46018
|
+
}
|
|
46019
|
+
if (sessions.length > 10) {
|
|
46020
|
+
console.log(c.dim(` ... and ${sessions.length - 10} more sessions
|
|
46021
|
+
`));
|
|
46022
|
+
}
|
|
46091
46023
|
}
|
|
46092
|
-
|
|
46093
|
-
|
|
46024
|
+
async show(sessionId) {
|
|
46025
|
+
if (!sessionId) {
|
|
46026
|
+
console.error(`
|
|
46027
|
+
${c.error("Error:")} Session ID is required
|
|
46028
|
+
`);
|
|
46029
|
+
console.log(` ${c.dim("Usage: locus exec sessions show <session-id>")}
|
|
46094
46030
|
`);
|
|
46095
|
-
const agentId = `reviewer-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
46096
|
-
const reviewer = new ReviewerWorker({
|
|
46097
|
-
agentId,
|
|
46098
|
-
workspaceId,
|
|
46099
|
-
apiBase,
|
|
46100
|
-
projectPath,
|
|
46101
|
-
apiKey,
|
|
46102
|
-
model,
|
|
46103
|
-
provider
|
|
46104
|
-
});
|
|
46105
|
-
let isShuttingDown = false;
|
|
46106
|
-
const handleSignal = () => {
|
|
46107
|
-
if (isShuttingDown)
|
|
46108
46031
|
return;
|
|
46109
|
-
|
|
46032
|
+
}
|
|
46033
|
+
const session2 = this.historyManager.findSessionByPartialId(sessionId);
|
|
46034
|
+
if (!session2) {
|
|
46035
|
+
console.error(`
|
|
46036
|
+
${c.error("Error:")} Session ${c.cyan(sessionId)} not found
|
|
46037
|
+
`);
|
|
46038
|
+
console.log(` ${c.dim("Use 'locus exec sessions list' to see available sessions")}
|
|
46039
|
+
`);
|
|
46040
|
+
return;
|
|
46041
|
+
}
|
|
46110
46042
|
console.log(`
|
|
46111
|
-
${c.
|
|
46112
|
-
|
|
46113
|
-
}
|
|
46114
|
-
|
|
46115
|
-
|
|
46116
|
-
|
|
46117
|
-
|
|
46118
|
-
|
|
46119
|
-
|
|
46120
|
-
|
|
46121
|
-
|
|
46122
|
-
|
|
46123
|
-
|
|
46124
|
-
|
|
46125
|
-
|
|
46126
|
-
|
|
46127
|
-
|
|
46128
|
-
|
|
46129
|
-
|
|
46130
|
-
const localSettings = new SettingsManager(projectPath).load();
|
|
46131
|
-
const provider = resolveProvider3(values.provider || localSettings.provider);
|
|
46132
|
-
const model = values.model || localSettings.model || DEFAULT_MODEL[provider];
|
|
46133
|
-
const aiRunner = createAiRunner(provider, {
|
|
46134
|
-
projectPath,
|
|
46135
|
-
model
|
|
46136
|
-
});
|
|
46137
|
-
const reviewService = new ReviewService({
|
|
46138
|
-
aiRunner,
|
|
46139
|
-
projectPath,
|
|
46140
|
-
log: (msg, level) => {
|
|
46141
|
-
switch (level) {
|
|
46142
|
-
case "error":
|
|
46143
|
-
console.log(` ${c.error("✖")} ${msg}`);
|
|
46144
|
-
break;
|
|
46145
|
-
case "success":
|
|
46146
|
-
console.log(` ${c.success("✔")} ${msg}`);
|
|
46147
|
-
break;
|
|
46148
|
-
default:
|
|
46149
|
-
console.log(` ${c.dim(msg)}`);
|
|
46043
|
+
${c.primary("Session:")} ${c.cyan(session2.id)}`);
|
|
46044
|
+
console.log(` ${c.dim(`Created: ${new Date(session2.createdAt).toLocaleString()}`)}`);
|
|
46045
|
+
console.log(` ${c.dim(`Model: ${session2.metadata.model} (${session2.metadata.provider})`)}
|
|
46046
|
+
`);
|
|
46047
|
+
if (session2.messages.length === 0) {
|
|
46048
|
+
console.log(` ${c.dim("(No messages in this session)")}
|
|
46049
|
+
`);
|
|
46050
|
+
return;
|
|
46051
|
+
}
|
|
46052
|
+
console.log(c.dim(" ─".repeat(30)));
|
|
46053
|
+
console.log();
|
|
46054
|
+
for (const message of session2.messages) {
|
|
46055
|
+
const role = message.role === "user" ? c.cyan("You") : c.green("AI");
|
|
46056
|
+
const content = message.content;
|
|
46057
|
+
console.log(` ${role}:`);
|
|
46058
|
+
const lines = content.split(`
|
|
46059
|
+
`);
|
|
46060
|
+
for (const line of lines) {
|
|
46061
|
+
console.log(` ${line}`);
|
|
46150
46062
|
}
|
|
46063
|
+
console.log();
|
|
46151
46064
|
}
|
|
46152
|
-
}
|
|
46153
|
-
|
|
46154
|
-
|
|
46065
|
+
}
|
|
46066
|
+
async delete(sessionId) {
|
|
46067
|
+
if (!sessionId) {
|
|
46068
|
+
console.error(`
|
|
46069
|
+
${c.error("Error:")} Session ID is required
|
|
46155
46070
|
`);
|
|
46156
|
-
|
|
46157
|
-
|
|
46158
|
-
|
|
46071
|
+
console.log(` ${c.dim("Usage: locus exec sessions delete <session-id>")}
|
|
46072
|
+
`);
|
|
46073
|
+
return;
|
|
46074
|
+
}
|
|
46075
|
+
const session2 = this.historyManager.findSessionByPartialId(sessionId);
|
|
46076
|
+
if (!session2) {
|
|
46077
|
+
console.error(`
|
|
46078
|
+
${c.error("Error:")} Session ${c.cyan(sessionId)} not found
|
|
46079
|
+
`);
|
|
46080
|
+
return;
|
|
46081
|
+
}
|
|
46082
|
+
const deleted = this.historyManager.deleteSession(session2.id);
|
|
46083
|
+
if (deleted) {
|
|
46084
|
+
console.log(`
|
|
46085
|
+
${c.success("✔")} Deleted session ${c.cyan(this.getShortId(session2.id))}
|
|
46086
|
+
`);
|
|
46087
|
+
} else {
|
|
46088
|
+
console.error(`
|
|
46089
|
+
${c.error("Error:")} Failed to delete session
|
|
46090
|
+
`);
|
|
46091
|
+
}
|
|
46092
|
+
}
|
|
46093
|
+
async clear() {
|
|
46094
|
+
const count = this.historyManager.getSessionCount();
|
|
46095
|
+
if (count === 0) {
|
|
46096
|
+
console.log(`
|
|
46097
|
+
${c.dim("No sessions to clear.")}
|
|
46098
|
+
`);
|
|
46099
|
+
return;
|
|
46100
|
+
}
|
|
46101
|
+
const deleted = this.historyManager.clearAllSessions();
|
|
46102
|
+
console.log(`
|
|
46103
|
+
${c.success("✔")} Cleared ${deleted} exec session${deleted === 1 ? "" : "s"}
|
|
46159
46104
|
`);
|
|
46160
|
-
return;
|
|
46161
46105
|
}
|
|
46162
|
-
|
|
46163
|
-
|
|
46164
|
-
|
|
46106
|
+
getShortId(sessionId) {
|
|
46107
|
+
const parts = sessionId.split("-");
|
|
46108
|
+
if (parts.length >= 3) {
|
|
46109
|
+
return parts.slice(-1)[0].slice(0, 8);
|
|
46110
|
+
}
|
|
46111
|
+
return sessionId.slice(0, 8);
|
|
46165
46112
|
}
|
|
46166
|
-
|
|
46167
|
-
|
|
46168
|
-
writeFileSync8(reportPath, report, "utf-8");
|
|
46113
|
+
}
|
|
46114
|
+
function showSessionsHelp() {
|
|
46169
46115
|
console.log(`
|
|
46170
|
-
${c.
|
|
46171
|
-
|
|
46116
|
+
${c.primary("Session Commands")}
|
|
46117
|
+
|
|
46118
|
+
${c.success("list")} List recent exec sessions
|
|
46119
|
+
${c.success("show")} ${c.dim("<id>")} Show all messages in a session
|
|
46120
|
+
${c.success("delete")} ${c.dim("<id>")} Delete a specific session
|
|
46121
|
+
${c.success("clear")} Clear all exec sessions
|
|
46122
|
+
|
|
46123
|
+
${c.header(" EXAMPLES ")}
|
|
46124
|
+
${c.dim("$")} locus exec sessions list
|
|
46125
|
+
${c.dim("$")} locus exec sessions show e7f3a2b1
|
|
46126
|
+
${c.dim("$")} locus exec sessions delete e7f3a2b1
|
|
46127
|
+
${c.dim("$")} locus exec sessions clear
|
|
46128
|
+
|
|
46129
|
+
${c.dim("Session IDs can be partial (first 8 characters).")}
|
|
46172
46130
|
`);
|
|
46173
46131
|
}
|
|
46174
|
-
|
|
46175
|
-
|
|
46176
|
-
|
|
46177
|
-
|
|
46178
|
-
init_utils3();
|
|
46179
|
-
init_workspace_resolver();
|
|
46180
|
-
import { parseArgs as parseArgs8 } from "node:util";
|
|
46181
|
-
async function runCommand(args) {
|
|
46182
|
-
const { values } = parseArgs8({
|
|
46132
|
+
|
|
46133
|
+
// src/commands/exec.ts
|
|
46134
|
+
async function execCommand(args) {
|
|
46135
|
+
const { values, positionals } = parseArgs5({
|
|
46183
46136
|
args,
|
|
46184
46137
|
options: {
|
|
46185
|
-
"api-key": { type: "string" },
|
|
46186
|
-
workspace: { type: "string" },
|
|
46187
|
-
sprint: { type: "string" },
|
|
46188
46138
|
model: { type: "string" },
|
|
46189
46139
|
provider: { type: "string" },
|
|
46190
46140
|
"reasoning-effort": { type: "string" },
|
|
46191
|
-
|
|
46192
|
-
"
|
|
46193
|
-
|
|
46141
|
+
dir: { type: "string" },
|
|
46142
|
+
"no-stream": { type: "boolean" },
|
|
46143
|
+
"no-status": { type: "boolean" },
|
|
46144
|
+
interactive: { type: "boolean", short: "i" },
|
|
46145
|
+
session: { type: "string", short: "s" },
|
|
46146
|
+
"session-id": { type: "string" },
|
|
46147
|
+
"json-stream": { type: "boolean" }
|
|
46194
46148
|
},
|
|
46195
46149
|
strict: false
|
|
46196
46150
|
});
|
|
46151
|
+
const jsonStream = values["json-stream"];
|
|
46197
46152
|
const projectPath = values.dir || process.cwd();
|
|
46198
|
-
|
|
46199
|
-
|
|
46200
|
-
|
|
46201
|
-
|
|
46202
|
-
|
|
46203
|
-
|
|
46204
|
-
|
|
46205
|
-
|
|
46206
|
-
|
|
46207
|
-
|
|
46153
|
+
if (jsonStream) {
|
|
46154
|
+
await execJsonStream(values, positionals, projectPath);
|
|
46155
|
+
return;
|
|
46156
|
+
}
|
|
46157
|
+
requireInitialization(projectPath, "exec");
|
|
46158
|
+
if (positionals[0] === "sessions") {
|
|
46159
|
+
const sessionAction = positionals[1];
|
|
46160
|
+
const sessionArg = positionals[2];
|
|
46161
|
+
const cmds = new SessionCommands(projectPath);
|
|
46162
|
+
switch (sessionAction) {
|
|
46163
|
+
case "list":
|
|
46164
|
+
await cmds.list();
|
|
46165
|
+
return;
|
|
46166
|
+
case "show":
|
|
46167
|
+
await cmds.show(sessionArg);
|
|
46168
|
+
return;
|
|
46169
|
+
case "delete":
|
|
46170
|
+
await cmds.delete(sessionArg);
|
|
46171
|
+
return;
|
|
46172
|
+
case "clear":
|
|
46173
|
+
await cmds.clear();
|
|
46174
|
+
return;
|
|
46175
|
+
default:
|
|
46176
|
+
showSessionsHelp();
|
|
46177
|
+
return;
|
|
46178
|
+
}
|
|
46179
|
+
}
|
|
46180
|
+
const execSettings = new SettingsManager(projectPath).load();
|
|
46181
|
+
const provider = resolveProvider3(values.provider || execSettings.provider);
|
|
46182
|
+
const model = values.model || execSettings.model || DEFAULT_MODEL[provider];
|
|
46183
|
+
const isInteractive = values.interactive;
|
|
46184
|
+
const sessionId = values.session;
|
|
46185
|
+
if (isInteractive) {
|
|
46186
|
+
const { InteractiveSession: InteractiveSession2 } = await Promise.resolve().then(() => (init_interactive_session(), exports_interactive_session));
|
|
46187
|
+
const session2 = new InteractiveSession2({
|
|
46188
|
+
projectPath,
|
|
46189
|
+
provider,
|
|
46190
|
+
model,
|
|
46191
|
+
sessionId
|
|
46192
|
+
});
|
|
46193
|
+
await session2.start();
|
|
46194
|
+
return;
|
|
46195
|
+
}
|
|
46196
|
+
const promptInput = positionals.join(" ");
|
|
46197
|
+
if (!promptInput) {
|
|
46198
|
+
console.error(c.error('Error: Prompt is required. Usage: locus exec "your prompt" or locus exec --interactive'));
|
|
46208
46199
|
process.exit(1);
|
|
46209
46200
|
}
|
|
46210
|
-
|
|
46211
|
-
const
|
|
46212
|
-
const
|
|
46213
|
-
|
|
46201
|
+
const useStreaming = !values["no-stream"];
|
|
46202
|
+
const reasoningEffort = values["reasoning-effort"];
|
|
46203
|
+
const aiRunner = createAiRunner(provider, {
|
|
46204
|
+
projectPath,
|
|
46205
|
+
model,
|
|
46206
|
+
reasoningEffort
|
|
46207
|
+
});
|
|
46208
|
+
const builder = new PromptBuilder(projectPath);
|
|
46209
|
+
const fullPrompt = await builder.buildGenericPrompt(promptInput);
|
|
46210
|
+
console.log("");
|
|
46211
|
+
console.log(`${c.primary("\uD83D\uDE80")} ${c.bold("Executing prompt with repository context...")}`);
|
|
46212
|
+
console.log("");
|
|
46213
|
+
let responseContent = "";
|
|
46214
46214
|
try {
|
|
46215
|
-
|
|
46216
|
-
|
|
46217
|
-
|
|
46218
|
-
|
|
46219
|
-
|
|
46220
|
-
|
|
46215
|
+
if (useStreaming) {
|
|
46216
|
+
const renderer = new ProgressRenderer;
|
|
46217
|
+
const statsTracker = new ExecutionStatsTracker;
|
|
46218
|
+
const stream4 = aiRunner.runStream(fullPrompt);
|
|
46219
|
+
renderer.showThinkingStarted();
|
|
46220
|
+
for await (const chunk of stream4) {
|
|
46221
|
+
switch (chunk.type) {
|
|
46222
|
+
case "text_delta":
|
|
46223
|
+
renderer.renderTextDelta(chunk.content);
|
|
46224
|
+
responseContent += chunk.content;
|
|
46225
|
+
break;
|
|
46226
|
+
case "tool_use":
|
|
46227
|
+
statsTracker.toolStarted(chunk.tool, chunk.id);
|
|
46228
|
+
renderer.showToolStarted(chunk.tool, chunk.id);
|
|
46229
|
+
break;
|
|
46230
|
+
case "thinking":
|
|
46231
|
+
renderer.showThinkingStarted();
|
|
46232
|
+
break;
|
|
46233
|
+
case "tool_result":
|
|
46234
|
+
if (chunk.success) {
|
|
46235
|
+
statsTracker.toolCompleted(chunk.tool, chunk.id);
|
|
46236
|
+
renderer.showToolCompleted(chunk.tool, undefined, chunk.id);
|
|
46237
|
+
} else {
|
|
46238
|
+
statsTracker.toolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
|
|
46239
|
+
renderer.showToolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
|
|
46240
|
+
}
|
|
46241
|
+
break;
|
|
46242
|
+
case "result":
|
|
46243
|
+
break;
|
|
46244
|
+
case "error": {
|
|
46245
|
+
statsTracker.setError(chunk.error);
|
|
46246
|
+
renderer.renderError(chunk.error);
|
|
46247
|
+
renderer.finalize();
|
|
46248
|
+
const errorStats = statsTracker.finalize();
|
|
46249
|
+
renderer.showSummary(errorStats);
|
|
46250
|
+
console.error(`
|
|
46251
|
+
${c.error("✖")} ${c.error("Execution failed!")}
|
|
46252
|
+
`);
|
|
46253
|
+
process.exit(1);
|
|
46254
|
+
}
|
|
46255
|
+
}
|
|
46256
|
+
}
|
|
46257
|
+
renderer.finalize();
|
|
46258
|
+
const stats = statsTracker.finalize();
|
|
46259
|
+
renderer.showSummary(stats);
|
|
46260
|
+
} else {
|
|
46261
|
+
responseContent = await aiRunner.run(fullPrompt);
|
|
46262
|
+
console.log(responseContent);
|
|
46263
|
+
}
|
|
46264
|
+
console.log(`
|
|
46265
|
+
${c.success("✔")} ${c.success("Execution finished!")}
|
|
46266
|
+
`);
|
|
46221
46267
|
} catch (error48) {
|
|
46222
|
-
console.error(
|
|
46268
|
+
console.error(`
|
|
46269
|
+
${c.error("✖")} ${c.error("Execution failed:")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
|
|
46270
|
+
`);
|
|
46223
46271
|
process.exit(1);
|
|
46224
46272
|
}
|
|
46225
|
-
|
|
46226
|
-
|
|
46227
|
-
|
|
46228
|
-
|
|
46273
|
+
}
|
|
46274
|
+
async function execJsonStream(values, positionals, projectPath) {
|
|
46275
|
+
const sessionId = values["session-id"] ?? values.session ?? randomUUID2();
|
|
46276
|
+
const execSettings = new SettingsManager(projectPath).load();
|
|
46277
|
+
const provider = resolveProvider3(values.provider || execSettings.provider);
|
|
46278
|
+
const model = values.model || execSettings.model || DEFAULT_MODEL[provider];
|
|
46279
|
+
const renderer = new JsonStreamRenderer({
|
|
46280
|
+
sessionId,
|
|
46281
|
+
command: "exec",
|
|
46229
46282
|
model,
|
|
46230
|
-
provider,
|
|
46231
|
-
|
|
46232
|
-
apiBase,
|
|
46233
|
-
maxIterations: 100,
|
|
46234
|
-
projectPath,
|
|
46235
|
-
apiKey
|
|
46283
|
+
provider,
|
|
46284
|
+
cwd: projectPath
|
|
46236
46285
|
});
|
|
46237
|
-
|
|
46238
|
-
|
|
46239
|
-
orchestrator.on("task:completed", (data) => console.log(` ${c.success("✔")} ${c.success("Completed:")} ${c.dim(data.taskId)}`));
|
|
46240
|
-
orchestrator.on("task:failed", (data) => console.log(` ${c.error("✖")} ${c.error("Failed:")} ${c.bold(data.taskId)}: ${data.error}`));
|
|
46241
|
-
orchestrator.on("agent:stale", (data) => console.log(` ${c.error("⚠")} ${c.error("Stale agent killed:")} ${data.agentId}`));
|
|
46242
|
-
let isShuttingDown = false;
|
|
46243
|
-
const handleSignal = async (signal) => {
|
|
46244
|
-
if (isShuttingDown)
|
|
46286
|
+
const handleSignal = () => {
|
|
46287
|
+
if (renderer.isDone()) {
|
|
46245
46288
|
return;
|
|
46246
|
-
|
|
46247
|
-
|
|
46248
|
-
|
|
46249
|
-
|
|
46250
|
-
|
|
46289
|
+
}
|
|
46290
|
+
renderer.emitFatalError("PROCESS_CRASHED", "Process terminated by signal");
|
|
46291
|
+
if (process.stdout.writableNeedDrain) {
|
|
46292
|
+
process.stdout.once("drain", () => process.exit(1));
|
|
46293
|
+
} else {
|
|
46294
|
+
process.exit(1);
|
|
46295
|
+
}
|
|
46251
46296
|
};
|
|
46252
|
-
process.on("SIGINT",
|
|
46253
|
-
process.on("SIGTERM",
|
|
46254
|
-
console.log(`
|
|
46255
|
-
${c.primary("\uD83D\uDE80")} ${c.bold("Starting agent in")} ${c.primary(projectPath)}...`);
|
|
46256
|
-
console.log(` ${c.dim("Tasks will be executed sequentially on a single branch")}`);
|
|
46257
|
-
console.log(` ${c.dim("Changes will be committed and pushed after each task")}`);
|
|
46258
|
-
console.log(` ${c.dim("A PR will be opened when all tasks are done")}`);
|
|
46259
|
-
await orchestrator.start();
|
|
46260
|
-
}
|
|
46261
|
-
// src/commands/service.ts
|
|
46262
|
-
init_index_node();
|
|
46263
|
-
init_settings_manager();
|
|
46264
|
-
init_utils3();
|
|
46265
|
-
import { spawn as spawn4 } from "node:child_process";
|
|
46266
|
-
import { existsSync as existsSync20, readdirSync as readdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "node:fs";
|
|
46267
|
-
import { homedir as homedir3 } from "node:os";
|
|
46268
|
-
import { dirname as dirname4, join as join20 } from "node:path";
|
|
46269
|
-
async function findBinary() {
|
|
46270
|
-
const result = await runShell("which", ["locus-telegram"]);
|
|
46271
|
-
const p = result.stdout.trim();
|
|
46272
|
-
return p?.startsWith?.("/") ? p : null;
|
|
46273
|
-
}
|
|
46274
|
-
async function findBinDir(binary) {
|
|
46275
|
-
const result = await runShell("which", [binary]);
|
|
46276
|
-
const p = result.stdout.trim();
|
|
46277
|
-
if (p?.startsWith?.("/"))
|
|
46278
|
-
return dirname4(p);
|
|
46279
|
-
return null;
|
|
46280
|
-
}
|
|
46281
|
-
function resolveNvmBinDir() {
|
|
46282
|
-
const nvmDir = process.env.NVM_DIR || join20(homedir3(), ".nvm");
|
|
46283
|
-
const versionsDir = join20(nvmDir, "versions", "node");
|
|
46284
|
-
if (!existsSync20(versionsDir))
|
|
46285
|
-
return null;
|
|
46286
|
-
let versions2;
|
|
46297
|
+
process.on("SIGINT", handleSignal);
|
|
46298
|
+
process.on("SIGTERM", handleSignal);
|
|
46287
46299
|
try {
|
|
46288
|
-
versions2 = readdirSync6(versionsDir).filter((d) => d.startsWith("v"));
|
|
46289
|
-
} catch {
|
|
46290
|
-
return null;
|
|
46291
|
-
}
|
|
46292
|
-
if (versions2.length === 0)
|
|
46293
|
-
return null;
|
|
46294
|
-
const currentNodeVersion = `v${process.versions.node}`;
|
|
46295
|
-
const currentBin = join20(versionsDir, currentNodeVersion, "bin");
|
|
46296
|
-
if (versions2.includes(currentNodeVersion) && existsSync20(currentBin)) {
|
|
46297
|
-
return currentBin;
|
|
46298
|
-
}
|
|
46299
|
-
const aliasPath = join20(nvmDir, "alias", "default");
|
|
46300
|
-
if (existsSync20(aliasPath)) {
|
|
46301
46300
|
try {
|
|
46302
|
-
|
|
46303
|
-
|
|
46304
|
-
|
|
46305
|
-
|
|
46306
|
-
if (existsSync20(bin2))
|
|
46307
|
-
return bin2;
|
|
46308
|
-
}
|
|
46309
|
-
} catch {}
|
|
46310
|
-
}
|
|
46311
|
-
const sorted = versions2.sort((a, b) => {
|
|
46312
|
-
const pa = a.slice(1).split(".").map(Number);
|
|
46313
|
-
const pb = b.slice(1).split(".").map(Number);
|
|
46314
|
-
for (let i = 0;i < 3; i++) {
|
|
46315
|
-
if ((pa[i] || 0) !== (pb[i] || 0))
|
|
46316
|
-
return (pb[i] || 0) - (pa[i] || 0);
|
|
46301
|
+
requireInitialization(projectPath, "exec");
|
|
46302
|
+
} catch (initError) {
|
|
46303
|
+
renderer.emitFatalError("CLI_NOT_FOUND", initError instanceof Error ? initError.message : String(initError));
|
|
46304
|
+
process.exit(1);
|
|
46317
46305
|
}
|
|
46318
|
-
|
|
46319
|
-
|
|
46320
|
-
|
|
46321
|
-
|
|
46322
|
-
}
|
|
46323
|
-
|
|
46324
|
-
|
|
46325
|
-
|
|
46326
|
-
|
|
46327
|
-
|
|
46328
|
-
|
|
46329
|
-
|
|
46330
|
-
|
|
46331
|
-
|
|
46332
|
-
|
|
46333
|
-
|
|
46334
|
-
|
|
46335
|
-
|
|
46336
|
-
|
|
46337
|
-
|
|
46338
|
-
|
|
46339
|
-
}
|
|
46340
|
-
|
|
46341
|
-
|
|
46342
|
-
|
|
46343
|
-
|
|
46344
|
-
|
|
46345
|
-
dirs.add(fnmCurrent);
|
|
46346
|
-
for (const bin of ["claude", "codex"]) {
|
|
46347
|
-
const dir = await findBinDir(bin);
|
|
46348
|
-
if (dir)
|
|
46349
|
-
dirs.add(dir);
|
|
46306
|
+
const promptInput = positionals.join(" ");
|
|
46307
|
+
if (!promptInput) {
|
|
46308
|
+
renderer.emitFatalError("UNKNOWN", 'Prompt is required. Usage: locus exec --json-stream "your prompt"');
|
|
46309
|
+
process.exit(1);
|
|
46310
|
+
}
|
|
46311
|
+
renderer.emitStart();
|
|
46312
|
+
renderer.emitStatus("running", "Building prompt context");
|
|
46313
|
+
const aiRunner = createAiRunner(provider, {
|
|
46314
|
+
projectPath,
|
|
46315
|
+
model
|
|
46316
|
+
});
|
|
46317
|
+
const builder = new PromptBuilder(projectPath);
|
|
46318
|
+
const fullPrompt = await builder.buildGenericPrompt(promptInput);
|
|
46319
|
+
renderer.emitStatus("streaming", "Streaming AI response");
|
|
46320
|
+
const stream4 = aiRunner.runStream(fullPrompt);
|
|
46321
|
+
for await (const chunk of stream4) {
|
|
46322
|
+
renderer.handleChunk(chunk);
|
|
46323
|
+
}
|
|
46324
|
+
renderer.emitDone(0);
|
|
46325
|
+
process.removeListener("SIGINT", handleSignal);
|
|
46326
|
+
process.removeListener("SIGTERM", handleSignal);
|
|
46327
|
+
} catch (error48) {
|
|
46328
|
+
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
46329
|
+
if (!renderer.isDone()) {
|
|
46330
|
+
renderer.emitFatalError("UNKNOWN", message);
|
|
46331
|
+
}
|
|
46332
|
+
process.exit(1);
|
|
46350
46333
|
}
|
|
46351
|
-
return Array.from(dirs).join(":");
|
|
46352
|
-
}
|
|
46353
|
-
var SERVICE_NAME = "locus";
|
|
46354
|
-
var SYSTEMD_UNIT_PATH = `/etc/systemd/system/${SERVICE_NAME}.service`;
|
|
46355
|
-
var PLIST_LABEL = "com.locus.agent";
|
|
46356
|
-
function getPlistPath() {
|
|
46357
|
-
return join20(homedir3(), "Library/LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
46358
46334
|
}
|
|
46359
|
-
|
|
46335
|
+
// src/commands/help.ts
|
|
46336
|
+
init_index_node();
|
|
46337
|
+
function showHelp2() {
|
|
46360
46338
|
console.log(`
|
|
46361
|
-
${c.header("
|
|
46362
|
-
${c.primary("locus
|
|
46339
|
+
${c.header(" USAGE ")}
|
|
46340
|
+
${c.primary("locus")} ${c.dim("<command> [options]")}
|
|
46363
46341
|
|
|
46364
|
-
${c.header("
|
|
46365
|
-
${c.success("
|
|
46366
|
-
|
|
46367
|
-
|
|
46368
|
-
|
|
46369
|
-
|
|
46342
|
+
${c.header(" COMMANDS ")}
|
|
46343
|
+
${c.success("init")} Initialize Locus in the current directory
|
|
46344
|
+
${c.success("config")} Manage settings (API key, provider, model)
|
|
46345
|
+
${c.dim("setup Interactive one-time setup")}
|
|
46346
|
+
${c.dim("show Show current settings")}
|
|
46347
|
+
${c.dim("set <k> <v> Update a setting")}
|
|
46348
|
+
${c.dim("remove Remove all settings")}
|
|
46349
|
+
${c.success("index")} Index the codebase for AI context
|
|
46350
|
+
${c.success("run")} Start agent to work on tasks sequentially
|
|
46351
|
+
${c.success("discuss")} Start an interactive AI discussion on a topic
|
|
46352
|
+
${c.dim("--list List all discussions")}
|
|
46353
|
+
${c.dim("--show <id> Show discussion details")}
|
|
46354
|
+
${c.dim("--archive <id> Archive a discussion")}
|
|
46355
|
+
${c.dim("--delete <id> Delete a discussion")}
|
|
46356
|
+
${c.success("plan")} Run async planning meeting to create sprint plans
|
|
46357
|
+
${c.success("docs")} Manage workspace docs
|
|
46358
|
+
${c.dim("sync Sync docs from API to .locus/documents")}
|
|
46359
|
+
${c.success("review")} Review open Locus PRs on GitHub with AI
|
|
46360
|
+
${c.dim("local Review staged changes locally (no GitHub)")}
|
|
46361
|
+
${c.success("telegram")} Manage the Telegram bot
|
|
46362
|
+
${c.dim("start Start the Telegram bot")}
|
|
46363
|
+
${c.dim("setup Interactive bot token and chat ID setup")}
|
|
46364
|
+
${c.dim("config Show current configuration")}
|
|
46365
|
+
${c.dim("set <k> <v> Update a config value")}
|
|
46366
|
+
${c.dim("remove Remove Telegram configuration")}
|
|
46367
|
+
${c.success("daemon")} Manage the Locus background service
|
|
46368
|
+
${c.dim("start Install and start the daemon")}
|
|
46369
|
+
${c.dim("stop Stop and remove the daemon")}
|
|
46370
|
+
${c.dim("restart Restart the daemon")}
|
|
46371
|
+
${c.dim("status Check if the daemon is running")}
|
|
46372
|
+
${c.success("exec")} Run a prompt with repository context
|
|
46373
|
+
${c.dim("--interactive, -i Start interactive REPL mode")}
|
|
46374
|
+
${c.dim("--session, -s <id> Resume a previous session")}
|
|
46375
|
+
${c.dim("sessions list List recent sessions")}
|
|
46376
|
+
${c.dim("sessions show <id> Show session messages")}
|
|
46377
|
+
${c.dim("sessions delete <id> Delete a session")}
|
|
46378
|
+
${c.dim("sessions clear Clear all sessions")}
|
|
46379
|
+
${c.success("artifacts")} List and manage knowledge artifacts
|
|
46380
|
+
${c.dim("show <name> Show artifact content")}
|
|
46381
|
+
${c.dim("plan <name> Convert artifact to a plan")}
|
|
46382
|
+
${c.success("version")} Show installed package versions
|
|
46383
|
+
${c.success("upgrade")} Update CLI and Telegram to the latest version
|
|
46384
|
+
|
|
46385
|
+
${c.header(" OPTIONS ")}
|
|
46386
|
+
${c.secondary("--help")} Show this help message
|
|
46387
|
+
${c.secondary("--provider")} <name> AI provider: ${c.dim("claude")} or ${c.dim("codex")} (default: ${c.dim("claude")})
|
|
46388
|
+
${c.secondary("--model")} <name> AI model (claude: ${c.dim("opus, sonnet, haiku")} | codex: ${c.dim("gpt-5.3-codex, gpt-5-codex-mini")})
|
|
46389
|
+
${c.secondary("--reasoning-effort")} <level> Codex reasoning effort: ${c.dim("low, medium, high")} (default: model default)
|
|
46390
|
+
|
|
46391
|
+
${c.header(" GETTING STARTED ")}
|
|
46392
|
+
${c.dim("$")} ${c.primary("locus init")}
|
|
46393
|
+
${c.dim("$")} ${c.primary("locus config setup")}
|
|
46394
|
+
${c.dim("$")} ${c.primary("locus telegram setup")}
|
|
46395
|
+
${c.dim("$")} ${c.primary("locus daemon start")}
|
|
46370
46396
|
|
|
46371
46397
|
${c.header(" EXAMPLES ")}
|
|
46372
|
-
${c.dim("$")} ${c.primary("locus
|
|
46373
|
-
${c.dim("$")} ${c.primary("locus
|
|
46374
|
-
${c.dim("$")} ${c.primary("locus
|
|
46398
|
+
${c.dim("$")} ${c.primary("locus run")}
|
|
46399
|
+
${c.dim("$")} ${c.primary("locus docs sync")}
|
|
46400
|
+
${c.dim("$")} ${c.primary("locus review")}
|
|
46401
|
+
${c.dim("$")} ${c.primary("locus review local")}
|
|
46402
|
+
${c.dim("$")} ${c.primary("locus telegram start")}
|
|
46403
|
+
${c.dim("$")} ${c.primary('locus discuss "how should we design the auth system?"')}
|
|
46404
|
+
${c.dim("$")} ${c.primary("locus exec sessions list")}
|
|
46405
|
+
${c.dim("$")} ${c.primary("locus artifacts")}
|
|
46406
|
+
${c.dim("$")} ${c.primary("locus daemon start")}
|
|
46407
|
+
|
|
46408
|
+
For more information, visit: ${c.underline("https://docs.locusai.dev")}
|
|
46375
46409
|
`);
|
|
46376
46410
|
}
|
|
46377
|
-
|
|
46378
|
-
|
|
46379
|
-
|
|
46380
|
-
|
|
46381
|
-
let stderr = "";
|
|
46382
|
-
proc.stdout?.on("data", (d) => {
|
|
46383
|
-
stdout += d.toString();
|
|
46384
|
-
});
|
|
46385
|
-
proc.stderr?.on("data", (d) => {
|
|
46386
|
-
stderr += d.toString();
|
|
46387
|
-
});
|
|
46388
|
-
proc.on("close", (exitCode) => resolve2({ exitCode, stdout, stderr }));
|
|
46389
|
-
proc.on("error", (err) => resolve2({ exitCode: 1, stdout, stderr: err.message }));
|
|
46390
|
-
});
|
|
46391
|
-
}
|
|
46392
|
-
function generateSystemdUnit(projectPath, user2, binaryPath, servicePath) {
|
|
46393
|
-
return `[Unit]
|
|
46394
|
-
Description=Locus AI Agent (Telegram bot + proposal scheduler)
|
|
46395
|
-
After=network-online.target
|
|
46396
|
-
Wants=network-online.target
|
|
46411
|
+
// src/commands/index-codebase.ts
|
|
46412
|
+
init_index_node();
|
|
46413
|
+
init_config_manager();
|
|
46414
|
+
import { parseArgs as parseArgs6 } from "node:util";
|
|
46397
46415
|
|
|
46398
|
-
|
|
46399
|
-
|
|
46400
|
-
User=${user2}
|
|
46401
|
-
WorkingDirectory=${projectPath}
|
|
46402
|
-
ExecStart=${binaryPath}
|
|
46403
|
-
Restart=on-failure
|
|
46404
|
-
RestartSec=10
|
|
46405
|
-
Environment=PATH=${servicePath}
|
|
46406
|
-
Environment=HOME=${homedir3()}
|
|
46416
|
+
// src/tree-summarizer.ts
|
|
46417
|
+
init_index_node();
|
|
46407
46418
|
|
|
46408
|
-
|
|
46409
|
-
|
|
46410
|
-
|
|
46411
|
-
|
|
46412
|
-
async function installSystemd(projectPath) {
|
|
46413
|
-
const user2 = process.env.USER || "root";
|
|
46414
|
-
const binaryPath = await findBinary();
|
|
46415
|
-
if (!binaryPath) {
|
|
46416
|
-
console.error(`
|
|
46417
|
-
${c.error("✖")} ${c.bold("Could not find locus-telegram binary.")}
|
|
46418
|
-
` + ` Install with: ${c.primary("npm install -g @locusai/telegram")}
|
|
46419
|
-
`);
|
|
46420
|
-
process.exit(1);
|
|
46421
|
-
}
|
|
46422
|
-
if (!await findBinDir("claude")) {
|
|
46423
|
-
console.warn(`
|
|
46424
|
-
${c.secondary("⚠")} ${c.bold("Could not find 'claude' CLI in PATH.")}
|
|
46425
|
-
` + ` The service needs the Claude Code CLI to execute tasks.
|
|
46426
|
-
` + ` Install with: ${c.primary("npm install -g @anthropic-ai/claude-code")}
|
|
46427
|
-
`);
|
|
46428
|
-
}
|
|
46429
|
-
if (!await findBinDir("codex")) {
|
|
46430
|
-
console.warn(`
|
|
46431
|
-
${c.secondary("⚠")} ${c.bold("Could not find 'codex' CLI in PATH.")}
|
|
46432
|
-
` + ` The service needs the Codex CLI if using the Codex provider.
|
|
46433
|
-
` + ` Install with: ${c.primary("npm install -g @openai/codex")}
|
|
46434
|
-
`);
|
|
46435
|
-
}
|
|
46436
|
-
const servicePath = await buildServicePath();
|
|
46437
|
-
const unit = generateSystemdUnit(projectPath, user2, binaryPath, servicePath);
|
|
46438
|
-
console.log(`
|
|
46439
|
-
${c.info("▶")} Writing systemd unit to ${c.dim(SYSTEMD_UNIT_PATH)}`);
|
|
46440
|
-
writeFileSync9(SYSTEMD_UNIT_PATH, unit, "utf-8");
|
|
46441
|
-
console.log(` ${c.info("▶")} Reloading systemd daemon...`);
|
|
46442
|
-
await runShell("systemctl", ["daemon-reload"]);
|
|
46443
|
-
console.log(` ${c.info("▶")} Enabling and starting ${SERVICE_NAME}...`);
|
|
46444
|
-
await runShell("systemctl", ["enable", SERVICE_NAME]);
|
|
46445
|
-
const startResult = await runShell("systemctl", ["start", SERVICE_NAME]);
|
|
46446
|
-
if (startResult.exitCode !== 0) {
|
|
46447
|
-
console.error(`
|
|
46448
|
-
${c.error("✖")} Failed to start service: ${startResult.stderr.trim()}`);
|
|
46449
|
-
console.error(` ${c.dim("Check logs with:")} ${c.primary(`journalctl -u ${SERVICE_NAME} -f`)}`);
|
|
46450
|
-
return;
|
|
46419
|
+
class TreeSummarizer {
|
|
46420
|
+
aiRunner;
|
|
46421
|
+
constructor(aiRunner) {
|
|
46422
|
+
this.aiRunner = aiRunner;
|
|
46451
46423
|
}
|
|
46452
|
-
|
|
46453
|
-
|
|
46454
|
-
|
|
46455
|
-
|
|
46456
|
-
|
|
46424
|
+
async summarize(tree) {
|
|
46425
|
+
const prompt = `Analyze this file tree and generate a JSON index.
|
|
46426
|
+
Return ONLY a JSON object with this structure:
|
|
46427
|
+
{
|
|
46428
|
+
"symbols": {},
|
|
46429
|
+
"responsibilities": { "path": "Description" }
|
|
46430
|
+
}
|
|
46457
46431
|
|
|
46458
|
-
|
|
46459
|
-
|
|
46460
|
-
|
|
46461
|
-
|
|
46462
|
-
|
|
46432
|
+
File Tree:
|
|
46433
|
+
${tree}`;
|
|
46434
|
+
const output = await this.aiRunner.run(prompt);
|
|
46435
|
+
const jsonStr = extractJsonFromLLMOutput(output);
|
|
46436
|
+
return JSON.parse(jsonStr);
|
|
46437
|
+
}
|
|
46463
46438
|
}
|
|
46464
|
-
|
|
46465
|
-
|
|
46466
|
-
|
|
46467
|
-
|
|
46468
|
-
|
|
46469
|
-
|
|
46439
|
+
|
|
46440
|
+
// src/commands/index-codebase.ts
|
|
46441
|
+
init_utils3();
|
|
46442
|
+
async function indexCommand(args) {
|
|
46443
|
+
const { values } = parseArgs6({
|
|
46444
|
+
args,
|
|
46445
|
+
options: {
|
|
46446
|
+
dir: { type: "string" },
|
|
46447
|
+
model: { type: "string" },
|
|
46448
|
+
provider: { type: "string" }
|
|
46449
|
+
},
|
|
46450
|
+
strict: false
|
|
46451
|
+
});
|
|
46452
|
+
const projectPath = values.dir || process.cwd();
|
|
46453
|
+
requireInitialization(projectPath, "index");
|
|
46454
|
+
new ConfigManager(projectPath).updateVersion(VERSION2);
|
|
46455
|
+
const provider = resolveProvider3(values.provider);
|
|
46456
|
+
const model = values.model || DEFAULT_MODEL[provider];
|
|
46457
|
+
const aiRunner = createAiRunner(provider, {
|
|
46458
|
+
projectPath,
|
|
46459
|
+
model
|
|
46460
|
+
});
|
|
46461
|
+
const summarizer = new TreeSummarizer(aiRunner);
|
|
46462
|
+
const indexer = new CodebaseIndexer(projectPath);
|
|
46463
|
+
console.log(`
|
|
46464
|
+
${c.step(" INDEX ")} ${c.primary("Analyzing codebase in")} ${c.bold(projectPath)}...`);
|
|
46465
|
+
try {
|
|
46466
|
+
const index = await indexer.index((msg) => console.log(` ${c.dim(msg)}`), (tree) => summarizer.summarize(tree));
|
|
46467
|
+
if (index) {
|
|
46468
|
+
indexer.saveIndex(index);
|
|
46469
|
+
}
|
|
46470
|
+
} catch (error48) {
|
|
46471
|
+
console.error(`
|
|
46472
|
+
${c.error("✖")} ${c.error("Indexing failed:")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}`);
|
|
46473
|
+
console.error(c.dim(` The agent might have limited context until indexing succeeds.
|
|
46474
|
+
`));
|
|
46470
46475
|
}
|
|
46471
|
-
console.log(` ${c.info("▶")} Stopping and disabling ${SERVICE_NAME}...`);
|
|
46472
|
-
await runShell("systemctl", ["stop", SERVICE_NAME]);
|
|
46473
|
-
await runShell("systemctl", ["disable", SERVICE_NAME]);
|
|
46474
|
-
const { unlinkSync: unlinkSync6 } = await import("node:fs");
|
|
46475
|
-
unlinkSync6(SYSTEMD_UNIT_PATH);
|
|
46476
|
-
await runShell("systemctl", ["daemon-reload"]);
|
|
46477
46476
|
console.log(`
|
|
46478
|
-
${c.success("✔")} ${c.
|
|
46477
|
+
${c.success("✔")} ${c.success("Indexing complete!")}
|
|
46479
46478
|
`);
|
|
46480
46479
|
}
|
|
46481
|
-
|
|
46482
|
-
|
|
46483
|
-
|
|
46484
|
-
|
|
46485
|
-
|
|
46486
|
-
|
|
46487
|
-
|
|
46488
|
-
|
|
46489
|
-
console.log(`
|
|
46490
|
-
${c.secondary("●")} ${c.bold(`Locus service is ${state}`)} ${c.dim("(systemd)")}
|
|
46491
|
-
`);
|
|
46492
|
-
console.log(` ${c.dim("Start with:")} ${c.primary(`sudo systemctl start ${SERVICE_NAME}`)}
|
|
46493
|
-
`);
|
|
46494
|
-
} else {
|
|
46480
|
+
// src/commands/init.ts
|
|
46481
|
+
init_index_node();
|
|
46482
|
+
init_config_manager();
|
|
46483
|
+
init_utils3();
|
|
46484
|
+
async function initCommand() {
|
|
46485
|
+
const projectPath = process.cwd();
|
|
46486
|
+
const configManager = new ConfigManager(projectPath);
|
|
46487
|
+
if (isProjectInitialized(projectPath)) {
|
|
46495
46488
|
console.log(`
|
|
46496
|
-
${c.
|
|
46497
|
-
`);
|
|
46498
|
-
console.log(` ${c.dim("Install with:")} ${c.primary("locus service install")}
|
|
46499
|
-
`);
|
|
46500
|
-
}
|
|
46501
|
-
}
|
|
46502
|
-
function generatePlist(projectPath, binaryPath, binaryArgs, servicePath) {
|
|
46503
|
-
const argsXml = [binaryPath, ...binaryArgs].map((a) => ` <string>${a}</string>`).join(`
|
|
46504
|
-
`);
|
|
46505
|
-
const logDir = join20(homedir3(), "Library/Logs/Locus");
|
|
46506
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
46507
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
46508
|
-
<plist version="1.0">
|
|
46509
|
-
<dict>
|
|
46510
|
-
<key>Label</key>
|
|
46511
|
-
<string>${PLIST_LABEL}</string>
|
|
46512
|
-
<key>ProgramArguments</key>
|
|
46513
|
-
<array>
|
|
46514
|
-
${argsXml}
|
|
46515
|
-
</array>
|
|
46516
|
-
<key>WorkingDirectory</key>
|
|
46517
|
-
<string>${projectPath}</string>
|
|
46518
|
-
<key>RunAtLoad</key>
|
|
46519
|
-
<true/>
|
|
46520
|
-
<key>KeepAlive</key>
|
|
46521
|
-
<true/>
|
|
46522
|
-
<key>StandardOutPath</key>
|
|
46523
|
-
<string>${join20(logDir, "locus.log")}</string>
|
|
46524
|
-
<key>StandardErrorPath</key>
|
|
46525
|
-
<string>${join20(logDir, "locus-error.log")}</string>
|
|
46526
|
-
<key>EnvironmentVariables</key>
|
|
46527
|
-
<dict>
|
|
46528
|
-
<key>PATH</key>
|
|
46529
|
-
<string>${servicePath}</string>
|
|
46530
|
-
</dict>
|
|
46531
|
-
</dict>
|
|
46532
|
-
</plist>
|
|
46533
|
-
`;
|
|
46534
|
-
}
|
|
46535
|
-
async function installLaunchd(projectPath) {
|
|
46536
|
-
const plistPath = getPlistPath();
|
|
46537
|
-
if (existsSync20(plistPath)) {
|
|
46538
|
-
await runShell("launchctl", ["unload", plistPath]);
|
|
46539
|
-
}
|
|
46540
|
-
const binaryPath = await findBinary();
|
|
46541
|
-
if (!binaryPath) {
|
|
46542
|
-
console.error(`
|
|
46543
|
-
${c.error("✖")} ${c.bold("Could not find locus-telegram binary.")}
|
|
46544
|
-
Install with: ${c.primary("npm install -g @locusai/telegram")}
|
|
46489
|
+
${c.info("ℹ️")} ${c.bold("Locus is already initialized. Updating configuration...")}
|
|
46545
46490
|
`);
|
|
46546
|
-
|
|
46547
|
-
|
|
46548
|
-
|
|
46549
|
-
|
|
46550
|
-
|
|
46551
|
-
|
|
46552
|
-
|
|
46553
|
-
|
|
46491
|
+
const result = await configManager.reinit(VERSION2);
|
|
46492
|
+
const updates = [];
|
|
46493
|
+
if (result.versionUpdated) {
|
|
46494
|
+
updates.push(`Version updated: ${c.dim(result.previousVersion || "unknown")} → ${c.primary(VERSION2)}`);
|
|
46495
|
+
}
|
|
46496
|
+
if (result.directoriesCreated.length > 0) {
|
|
46497
|
+
updates.push(`Directories created: ${result.directoriesCreated.map((d) => c.dim(d)).join(", ")}`);
|
|
46498
|
+
}
|
|
46499
|
+
if (result.gitignoreUpdated) {
|
|
46500
|
+
updates.push(`Gitignore updated with Locus patterns`);
|
|
46501
|
+
}
|
|
46502
|
+
if (updates.length === 0) {
|
|
46503
|
+
console.log(` ${c.success("✔")} ${c.success("Configuration is already up to date!")}
|
|
46504
|
+
|
|
46505
|
+
${c.dim(`Version: ${VERSION2}`)}`);
|
|
46506
|
+
} else {
|
|
46507
|
+
console.log(` ${c.success("✔")} ${c.success("Configuration updated successfully!")}
|
|
46508
|
+
|
|
46509
|
+
${c.bold("Changes:")}
|
|
46510
|
+
${updates.map((u) => `${c.primary("•")} ${u}`).join(`
|
|
46511
|
+
`)}
|
|
46554
46512
|
`);
|
|
46555
|
-
|
|
46556
|
-
|
|
46557
|
-
|
|
46558
|
-
|
|
46559
|
-
|
|
46560
|
-
|
|
46513
|
+
}
|
|
46514
|
+
console.log(` ${c.bold("Next steps:")}
|
|
46515
|
+
1. Run '${c.primary("locus config setup")}' to configure your API key
|
|
46516
|
+
2. Run '${c.primary("locus index")}' to index your codebase
|
|
46517
|
+
3. Run '${c.primary("locus run")}' to start an agent
|
|
46518
|
+
|
|
46519
|
+
For more information, visit: ${c.underline("https://docs.locusai.dev")}
|
|
46561
46520
|
`);
|
|
46562
|
-
}
|
|
46563
|
-
const logDir = join20(homedir3(), "Library/Logs/Locus");
|
|
46564
|
-
const { mkdirSync: mkdirSync10 } = await import("node:fs");
|
|
46565
|
-
mkdirSync10(logDir, { recursive: true });
|
|
46566
|
-
const launchAgentsDir = join20(homedir3(), "Library/LaunchAgents");
|
|
46567
|
-
mkdirSync10(launchAgentsDir, { recursive: true });
|
|
46568
|
-
const servicePath = await buildServicePath();
|
|
46569
|
-
const plist = generatePlist(projectPath, binaryPath, binaryArgs, servicePath);
|
|
46570
|
-
console.log(`
|
|
46571
|
-
${c.info("▶")} Writing plist to ${c.dim(plistPath)}`);
|
|
46572
|
-
writeFileSync9(plistPath, plist, "utf-8");
|
|
46573
|
-
console.log(` ${c.info("▶")} Loading service...`);
|
|
46574
|
-
const loadResult = await runShell("launchctl", ["load", plistPath]);
|
|
46575
|
-
if (loadResult.exitCode !== 0) {
|
|
46576
|
-
console.error(`
|
|
46577
|
-
${c.error("✖")} Failed to load service: ${loadResult.stderr.trim()}`);
|
|
46578
46521
|
return;
|
|
46579
46522
|
}
|
|
46580
|
-
|
|
46523
|
+
await configManager.init(VERSION2);
|
|
46581
46524
|
console.log(`
|
|
46582
|
-
${c.success("
|
|
46525
|
+
${c.success("✨ Locus initialized successfully!")}
|
|
46583
46526
|
|
|
46584
|
-
${c.bold("
|
|
46585
|
-
|
|
46527
|
+
${c.bold("Created:")}
|
|
46528
|
+
${c.primary("\uD83D\uDCC1")} ${c.bold(".locus/")} ${c.dim("Configuration directory")}
|
|
46529
|
+
${c.primary("\uD83D\uDCC4")} ${c.bold(".locus/config.json")} ${c.dim("Project settings")}
|
|
46530
|
+
${c.primary("\uD83D\uDCDD")} ${c.bold(".locus/LOCUS.md")} ${c.dim("AI agent instructions")}
|
|
46531
|
+
${c.primary("\uD83D\uDCDD")} ${c.bold(".locus/LEARNINGS.md")} ${c.dim("Continuous learning log")}
|
|
46586
46532
|
|
|
46587
|
-
${c.bold("
|
|
46588
|
-
${c.
|
|
46589
|
-
${c.
|
|
46533
|
+
${c.bold("Next steps:")}
|
|
46534
|
+
1. Run '${c.primary("locus config setup")}' to configure your API key
|
|
46535
|
+
2. Run '${c.primary("locus index")}' to index your codebase
|
|
46536
|
+
3. Run '${c.primary("locus run")}' to start an agent
|
|
46537
|
+
|
|
46538
|
+
For more information, visit: ${c.underline("https://docs.locusai.dev")}
|
|
46590
46539
|
`);
|
|
46591
46540
|
}
|
|
46592
|
-
|
|
46593
|
-
|
|
46594
|
-
|
|
46595
|
-
|
|
46596
|
-
|
|
46597
|
-
|
|
46598
|
-
|
|
46541
|
+
|
|
46542
|
+
// src/commands/index.ts
|
|
46543
|
+
init_plan();
|
|
46544
|
+
|
|
46545
|
+
// src/commands/review.ts
|
|
46546
|
+
init_index_node();
|
|
46547
|
+
init_config_manager();
|
|
46548
|
+
init_settings_manager();
|
|
46549
|
+
init_utils3();
|
|
46550
|
+
init_workspace_resolver();
|
|
46551
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
|
|
46552
|
+
import { join as join21 } from "node:path";
|
|
46553
|
+
import { parseArgs as parseArgs7 } from "node:util";
|
|
46554
|
+
async function reviewCommand(args) {
|
|
46555
|
+
const subcommand = args[0];
|
|
46556
|
+
if (subcommand === "local") {
|
|
46557
|
+
return reviewLocalCommand(args.slice(1));
|
|
46599
46558
|
}
|
|
46600
|
-
|
|
46601
|
-
await runShell("launchctl", ["unload", plistPath]);
|
|
46602
|
-
const { unlinkSync: unlinkSync6 } = await import("node:fs");
|
|
46603
|
-
unlinkSync6(plistPath);
|
|
46604
|
-
console.log(`
|
|
46605
|
-
${c.success("✔")} ${c.bold("Locus service removed.")}
|
|
46606
|
-
`);
|
|
46559
|
+
return reviewPrsCommand(args);
|
|
46607
46560
|
}
|
|
46608
|
-
async function
|
|
46609
|
-
const
|
|
46610
|
-
|
|
46561
|
+
async function reviewPrsCommand(args) {
|
|
46562
|
+
const { values } = parseArgs7({
|
|
46563
|
+
args,
|
|
46564
|
+
options: {
|
|
46565
|
+
"api-key": { type: "string" },
|
|
46566
|
+
workspace: { type: "string" },
|
|
46567
|
+
model: { type: "string" },
|
|
46568
|
+
provider: { type: "string" },
|
|
46569
|
+
"api-url": { type: "string" },
|
|
46570
|
+
dir: { type: "string" }
|
|
46571
|
+
},
|
|
46572
|
+
strict: false
|
|
46573
|
+
});
|
|
46574
|
+
const projectPath = values.dir || process.cwd();
|
|
46575
|
+
requireInitialization(projectPath, "review");
|
|
46576
|
+
const configManager = new ConfigManager(projectPath);
|
|
46577
|
+
configManager.updateVersion(VERSION2);
|
|
46578
|
+
const settingsManager = new SettingsManager(projectPath);
|
|
46579
|
+
const settings = settingsManager.load();
|
|
46580
|
+
const apiKey = values["api-key"] || settings.apiKey;
|
|
46581
|
+
if (!apiKey) {
|
|
46582
|
+
console.error(c.error("Error: API key is required for PR review"));
|
|
46583
|
+
console.error(c.dim(`Configure with: locus config setup --api-key <key>
|
|
46584
|
+
Or pass --api-key flag`));
|
|
46585
|
+
console.error(c.dim("For local staged-changes review, use: locus review local"));
|
|
46586
|
+
process.exit(1);
|
|
46587
|
+
}
|
|
46588
|
+
const provider = resolveProvider3(values.provider || settings.provider);
|
|
46589
|
+
const model = values.model || settings.model || DEFAULT_MODEL[provider];
|
|
46590
|
+
const apiBase = values["api-url"] || settings.apiUrl || "https://api.locusai.dev/api";
|
|
46591
|
+
let workspaceId;
|
|
46592
|
+
try {
|
|
46593
|
+
const resolver = new WorkspaceResolver({
|
|
46594
|
+
apiKey,
|
|
46595
|
+
apiBase,
|
|
46596
|
+
workspaceId: values.workspace
|
|
46597
|
+
});
|
|
46598
|
+
workspaceId = await resolver.resolve();
|
|
46599
|
+
} catch (error48) {
|
|
46600
|
+
console.error(c.error(error48 instanceof Error ? error48.message : String(error48)));
|
|
46601
|
+
process.exit(1);
|
|
46602
|
+
}
|
|
46603
|
+
const log = (msg, level = "info") => {
|
|
46604
|
+
const colorFn = {
|
|
46605
|
+
info: c.cyan,
|
|
46606
|
+
success: c.green,
|
|
46607
|
+
warn: c.yellow,
|
|
46608
|
+
error: c.red
|
|
46609
|
+
}[level];
|
|
46610
|
+
const prefix = { info: "ℹ", success: "✓", warn: "⚠", error: "✗" }[level];
|
|
46611
|
+
console.log(` ${colorFn(`${prefix} ${msg}`)}`);
|
|
46612
|
+
};
|
|
46613
|
+
const prService = new PrService(projectPath, log);
|
|
46614
|
+
const unreviewedPrs = prService.listUnreviewedLocusPrs();
|
|
46615
|
+
if (unreviewedPrs.length === 0) {
|
|
46611
46616
|
console.log(`
|
|
46612
|
-
${c.
|
|
46613
|
-
`);
|
|
46614
|
-
console.log(` ${c.dim("Install with:")} ${c.primary("locus service install")}
|
|
46617
|
+
${c.dim("No unreviewed Locus PRs found.")}
|
|
46615
46618
|
`);
|
|
46616
46619
|
return;
|
|
46617
46620
|
}
|
|
46618
|
-
|
|
46619
|
-
|
|
46620
|
-
`);
|
|
46621
|
-
const match = lines.find((l) => l.includes(PLIST_LABEL));
|
|
46622
|
-
if (match) {
|
|
46623
|
-
const parts = match.trim().split(/\s+/);
|
|
46624
|
-
const pid = parts[0] === "-" ? null : parts[0];
|
|
46625
|
-
if (pid) {
|
|
46626
|
-
console.log(`
|
|
46627
|
-
${c.success("●")} ${c.bold("Locus service is running")} ${c.dim(`(PID ${pid}, launchd)`)}
|
|
46628
|
-
`);
|
|
46629
|
-
} else {
|
|
46630
|
-
console.log(`
|
|
46631
|
-
${c.secondary("●")} ${c.bold("Locus service is stopped")} ${c.dim("(launchd)")}
|
|
46632
|
-
`);
|
|
46633
|
-
console.log(` ${c.dim("Start with:")} ${c.primary(`launchctl load ${plistPath}`)}
|
|
46621
|
+
console.log(`
|
|
46622
|
+
${c.primary("\uD83D\uDD0D")} ${c.bold(`Found ${unreviewedPrs.length} unreviewed PR(s). Starting reviewer...`)}
|
|
46634
46623
|
`);
|
|
46635
|
-
|
|
46636
|
-
|
|
46624
|
+
const agentId = `reviewer-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
46625
|
+
const reviewer = new ReviewerWorker({
|
|
46626
|
+
agentId,
|
|
46627
|
+
workspaceId,
|
|
46628
|
+
apiBase,
|
|
46629
|
+
projectPath,
|
|
46630
|
+
apiKey,
|
|
46631
|
+
model,
|
|
46632
|
+
provider
|
|
46633
|
+
});
|
|
46634
|
+
let isShuttingDown = false;
|
|
46635
|
+
const handleSignal = () => {
|
|
46636
|
+
if (isShuttingDown)
|
|
46637
|
+
return;
|
|
46638
|
+
isShuttingDown = true;
|
|
46637
46639
|
console.log(`
|
|
46638
|
-
|
|
46639
|
-
|
|
46640
|
-
|
|
46641
|
-
|
|
46642
|
-
|
|
46643
|
-
|
|
46644
|
-
function getPlatform() {
|
|
46645
|
-
if (process.platform === "linux")
|
|
46646
|
-
return "linux";
|
|
46647
|
-
if (process.platform === "darwin")
|
|
46648
|
-
return "darwin";
|
|
46649
|
-
return null;
|
|
46640
|
+
${c.info("Received shutdown signal. Stopping reviewer...")}`);
|
|
46641
|
+
process.exit(0);
|
|
46642
|
+
};
|
|
46643
|
+
process.on("SIGINT", handleSignal);
|
|
46644
|
+
process.on("SIGTERM", handleSignal);
|
|
46645
|
+
await reviewer.run();
|
|
46650
46646
|
}
|
|
46651
|
-
async function
|
|
46652
|
-
const
|
|
46653
|
-
|
|
46654
|
-
|
|
46655
|
-
|
|
46656
|
-
|
|
46657
|
-
|
|
46658
|
-
|
|
46659
|
-
|
|
46660
|
-
|
|
46661
|
-
const
|
|
46662
|
-
|
|
46663
|
-
|
|
46664
|
-
|
|
46665
|
-
|
|
46647
|
+
async function reviewLocalCommand(args) {
|
|
46648
|
+
const { values } = parseArgs7({
|
|
46649
|
+
args,
|
|
46650
|
+
options: {
|
|
46651
|
+
model: { type: "string" },
|
|
46652
|
+
provider: { type: "string" },
|
|
46653
|
+
dir: { type: "string" }
|
|
46654
|
+
},
|
|
46655
|
+
strict: false
|
|
46656
|
+
});
|
|
46657
|
+
const projectPath = values.dir || process.cwd();
|
|
46658
|
+
requireInitialization(projectPath, "review local");
|
|
46659
|
+
const localSettings = new SettingsManager(projectPath).load();
|
|
46660
|
+
const provider = resolveProvider3(values.provider || localSettings.provider);
|
|
46661
|
+
const model = values.model || localSettings.model || DEFAULT_MODEL[provider];
|
|
46662
|
+
const aiRunner = createAiRunner(provider, {
|
|
46663
|
+
projectPath,
|
|
46664
|
+
model
|
|
46665
|
+
});
|
|
46666
|
+
const reviewService = new ReviewService({
|
|
46667
|
+
aiRunner,
|
|
46668
|
+
projectPath,
|
|
46669
|
+
log: (msg, level) => {
|
|
46670
|
+
switch (level) {
|
|
46671
|
+
case "error":
|
|
46672
|
+
console.log(` ${c.error("✖")} ${msg}`);
|
|
46673
|
+
break;
|
|
46674
|
+
case "success":
|
|
46675
|
+
console.log(` ${c.success("✔")} ${msg}`);
|
|
46676
|
+
break;
|
|
46677
|
+
default:
|
|
46678
|
+
console.log(` ${c.dim(msg)}`);
|
|
46679
|
+
}
|
|
46680
|
+
}
|
|
46681
|
+
});
|
|
46682
|
+
console.log(`
|
|
46683
|
+
${c.primary("\uD83D\uDD0D")} ${c.bold("Reviewing staged changes...")}
|
|
46666
46684
|
`);
|
|
46667
|
-
|
|
46668
|
-
|
|
46669
|
-
|
|
46670
|
-
console.error(`
|
|
46671
|
-
${c.error("✖")} ${c.bold("API key is not configured.")}
|
|
46672
|
-
Run ${c.primary("locus config setup --api-key <key>")} first.
|
|
46685
|
+
const report = await reviewService.reviewStagedChanges(null);
|
|
46686
|
+
if (!report) {
|
|
46687
|
+
console.log(` ${c.dim("No changes to review.")}
|
|
46673
46688
|
`);
|
|
46674
|
-
|
|
46689
|
+
return;
|
|
46675
46690
|
}
|
|
46676
|
-
|
|
46677
|
-
|
|
46678
|
-
|
|
46679
|
-
await installLaunchd(projectPath);
|
|
46691
|
+
const reviewsDir = join21(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.reviewsDir);
|
|
46692
|
+
if (!existsSync21(reviewsDir)) {
|
|
46693
|
+
mkdirSync10(reviewsDir, { recursive: true });
|
|
46680
46694
|
}
|
|
46681
|
-
|
|
46682
|
-
|
|
46683
|
-
|
|
46684
|
-
|
|
46685
|
-
|
|
46686
|
-
${c.
|
|
46695
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
46696
|
+
const reportPath = join21(reviewsDir, `review-${timestamp}.md`);
|
|
46697
|
+
writeFileSync9(reportPath, report, "utf-8");
|
|
46698
|
+
console.log(`
|
|
46699
|
+
${c.success("✔")} ${c.success("Review complete!")}`);
|
|
46700
|
+
console.log(` ${c.dim("Report saved to:")} ${c.primary(reportPath)}
|
|
46687
46701
|
`);
|
|
46688
|
-
process.exit(1);
|
|
46689
|
-
}
|
|
46690
|
-
if (platform === "linux") {
|
|
46691
|
-
await uninstallSystemd();
|
|
46692
|
-
} else {
|
|
46693
|
-
await uninstallLaunchd();
|
|
46694
|
-
}
|
|
46695
46702
|
}
|
|
46696
|
-
|
|
46697
|
-
|
|
46698
|
-
|
|
46699
|
-
|
|
46700
|
-
|
|
46701
|
-
|
|
46703
|
+
// src/commands/run.ts
|
|
46704
|
+
init_index_node();
|
|
46705
|
+
init_config_manager();
|
|
46706
|
+
init_settings_manager();
|
|
46707
|
+
init_utils3();
|
|
46708
|
+
init_workspace_resolver();
|
|
46709
|
+
import { parseArgs as parseArgs8 } from "node:util";
|
|
46710
|
+
async function runCommand(args) {
|
|
46711
|
+
const { values } = parseArgs8({
|
|
46712
|
+
args,
|
|
46713
|
+
options: {
|
|
46714
|
+
"api-key": { type: "string" },
|
|
46715
|
+
workspace: { type: "string" },
|
|
46716
|
+
sprint: { type: "string" },
|
|
46717
|
+
model: { type: "string" },
|
|
46718
|
+
provider: { type: "string" },
|
|
46719
|
+
"reasoning-effort": { type: "string" },
|
|
46720
|
+
"skip-planning": { type: "boolean" },
|
|
46721
|
+
"api-url": { type: "string" },
|
|
46722
|
+
dir: { type: "string" }
|
|
46723
|
+
},
|
|
46724
|
+
strict: false
|
|
46725
|
+
});
|
|
46726
|
+
const projectPath = values.dir || process.cwd();
|
|
46727
|
+
requireInitialization(projectPath, "run");
|
|
46728
|
+
const configManager = new ConfigManager(projectPath);
|
|
46729
|
+
configManager.updateVersion(VERSION2);
|
|
46730
|
+
const settingsManager = new SettingsManager(projectPath);
|
|
46731
|
+
const settings = settingsManager.load();
|
|
46732
|
+
const apiKey = values["api-key"] || settings.apiKey;
|
|
46733
|
+
if (!apiKey) {
|
|
46734
|
+
console.error(c.error("Error: API key is required"));
|
|
46735
|
+
console.error(c.dim(`Configure with: locus config setup --api-key <key>
|
|
46736
|
+
Or pass --api-key flag`));
|
|
46702
46737
|
process.exit(1);
|
|
46703
46738
|
}
|
|
46704
|
-
|
|
46705
|
-
|
|
46706
|
-
|
|
46707
|
-
|
|
46739
|
+
let workspaceId = values.workspace;
|
|
46740
|
+
const provider = resolveProvider3(values.provider || settings.provider);
|
|
46741
|
+
const model = values.model || settings.model || DEFAULT_MODEL[provider];
|
|
46742
|
+
const apiBase = values["api-url"] || settings.apiUrl || "https://api.locusai.dev/api";
|
|
46743
|
+
try {
|
|
46744
|
+
const resolver = new WorkspaceResolver({
|
|
46745
|
+
apiKey,
|
|
46746
|
+
apiBase,
|
|
46747
|
+
workspaceId: values.workspace
|
|
46748
|
+
});
|
|
46749
|
+
workspaceId = await resolver.resolve();
|
|
46750
|
+
} catch (error48) {
|
|
46751
|
+
console.error(c.error(error48 instanceof Error ? error48.message : String(error48)));
|
|
46752
|
+
process.exit(1);
|
|
46708
46753
|
}
|
|
46754
|
+
const reasoningEffort = values["reasoning-effort"];
|
|
46755
|
+
const orchestrator = new AgentOrchestrator({
|
|
46756
|
+
workspaceId,
|
|
46757
|
+
sprintId: values.sprint || "",
|
|
46758
|
+
model,
|
|
46759
|
+
provider,
|
|
46760
|
+
reasoningEffort,
|
|
46761
|
+
apiBase,
|
|
46762
|
+
maxIterations: 100,
|
|
46763
|
+
projectPath,
|
|
46764
|
+
apiKey
|
|
46765
|
+
});
|
|
46766
|
+
orchestrator.on("agent:spawned", (data) => console.log(` ${c.info("●")} ${c.bold("Agent spawned:")} ${data.agentId}`));
|
|
46767
|
+
orchestrator.on("task:assigned", (data) => console.log(` ${c.info("●")} ${c.bold("Claimed:")} ${data.title}`));
|
|
46768
|
+
orchestrator.on("task:completed", (data) => console.log(` ${c.success("✔")} ${c.success("Completed:")} ${c.dim(data.taskId)}`));
|
|
46769
|
+
orchestrator.on("task:failed", (data) => console.log(` ${c.error("✖")} ${c.error("Failed:")} ${c.bold(data.taskId)}: ${data.error}`));
|
|
46770
|
+
orchestrator.on("agent:stale", (data) => console.log(` ${c.error("⚠")} ${c.error("Stale agent killed:")} ${data.agentId}`));
|
|
46771
|
+
let isShuttingDown = false;
|
|
46772
|
+
const handleSignal = async (signal) => {
|
|
46773
|
+
if (isShuttingDown)
|
|
46774
|
+
return;
|
|
46775
|
+
isShuttingDown = true;
|
|
46776
|
+
console.log(`
|
|
46777
|
+
${c.info(`Received ${signal}. Stopping agent and cleaning up...`)}`);
|
|
46778
|
+
await orchestrator.stop();
|
|
46779
|
+
process.exit(0);
|
|
46780
|
+
};
|
|
46781
|
+
process.on("SIGINT", () => handleSignal("SIGINT"));
|
|
46782
|
+
process.on("SIGTERM", () => handleSignal("SIGTERM"));
|
|
46783
|
+
console.log(`
|
|
46784
|
+
${c.primary("\uD83D\uDE80")} ${c.bold("Starting agent in")} ${c.primary(projectPath)}...`);
|
|
46785
|
+
console.log(` ${c.dim("Tasks will be executed sequentially on a single branch")}`);
|
|
46786
|
+
console.log(` ${c.dim("Changes will be committed and pushed after each task")}`);
|
|
46787
|
+
console.log(` ${c.dim("A PR will be opened when all tasks are done")}`);
|
|
46788
|
+
await orchestrator.start();
|
|
46709
46789
|
}
|
|
46790
|
+
// src/commands/service.ts
|
|
46791
|
+
init_index_node();
|
|
46710
46792
|
async function serviceCommand(args) {
|
|
46711
|
-
const projectPath = process.cwd();
|
|
46712
|
-
requireInitialization(projectPath, "service");
|
|
46713
46793
|
const subcommand = args[0];
|
|
46714
|
-
|
|
46715
|
-
|
|
46716
|
-
|
|
46717
|
-
|
|
46718
|
-
|
|
46719
|
-
|
|
46720
|
-
|
|
46721
|
-
|
|
46722
|
-
|
|
46723
|
-
|
|
46724
|
-
|
|
46725
|
-
|
|
46794
|
+
const mapping = {
|
|
46795
|
+
install: "start",
|
|
46796
|
+
uninstall: "stop",
|
|
46797
|
+
status: "status"
|
|
46798
|
+
};
|
|
46799
|
+
const mapped = subcommand ? mapping[subcommand] : undefined;
|
|
46800
|
+
if (mapped) {
|
|
46801
|
+
console.log(` ${c.dim(`Hint: 'locus service ${subcommand}' is now 'locus daemon ${mapped}'`)}
|
|
46802
|
+
`);
|
|
46803
|
+
await daemonCommand([mapped, ...args.slice(1)]);
|
|
46804
|
+
} else {
|
|
46805
|
+
await daemonCommand(args);
|
|
46726
46806
|
}
|
|
46727
46807
|
}
|
|
46728
46808
|
// src/commands/telegram.ts
|
|
46729
46809
|
init_index_node();
|
|
46730
46810
|
init_settings_manager();
|
|
46731
46811
|
import { spawn as spawn5 } from "node:child_process";
|
|
46732
|
-
import { existsSync as
|
|
46733
|
-
import { join as
|
|
46812
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
46813
|
+
import { join as join22 } from "node:path";
|
|
46734
46814
|
import { createInterface as createInterface2 } from "node:readline";
|
|
46735
46815
|
function ask2(question) {
|
|
46736
46816
|
const rl = createInterface2({
|
|
@@ -46756,7 +46836,7 @@ function showTelegramHelp() {
|
|
|
46756
46836
|
${c.primary("locus telegram")} ${c.dim("<subcommand> [options]")}
|
|
46757
46837
|
|
|
46758
46838
|
${c.header(" SUBCOMMANDS ")}
|
|
46759
|
-
${c.success("
|
|
46839
|
+
${c.success("start")} Start the Telegram bot
|
|
46760
46840
|
${c.success("setup")} Interactive Telegram bot setup (or pass flags below)
|
|
46761
46841
|
${c.success("config")} Show current Telegram configuration
|
|
46762
46842
|
${c.success("set")} Set a config value
|
|
@@ -46765,7 +46845,7 @@ function showTelegramHelp() {
|
|
|
46765
46845
|
${c.success("remove")} Remove Telegram configuration
|
|
46766
46846
|
|
|
46767
46847
|
${c.header(" EXAMPLES ")}
|
|
46768
|
-
${c.dim("$")} ${c.primary("locus telegram
|
|
46848
|
+
${c.dim("$")} ${c.primary("locus telegram start")}
|
|
46769
46849
|
${c.dim("$")} ${c.primary('locus telegram setup --token "123:ABC" --chat-id 987654')}
|
|
46770
46850
|
${c.dim("$")} ${c.primary("locus telegram config")}
|
|
46771
46851
|
${c.dim("$")} ${c.primary("locus telegram remove")}
|
|
@@ -46776,7 +46856,7 @@ function showTelegramHelp() {
|
|
|
46776
46856
|
${c.primary("locus config set <key> <value>")}
|
|
46777
46857
|
`);
|
|
46778
46858
|
}
|
|
46779
|
-
async function
|
|
46859
|
+
async function setup(args, projectPath) {
|
|
46780
46860
|
let token;
|
|
46781
46861
|
let chatId;
|
|
46782
46862
|
for (let i = 0;i < args.length; i++) {
|
|
@@ -46848,11 +46928,11 @@ async function setupCommand2(args, projectPath) {
|
|
|
46848
46928
|
${c.primary("Chat ID:")} ${parsedChatId}
|
|
46849
46929
|
|
|
46850
46930
|
${c.bold("Next steps:")}
|
|
46851
|
-
|
|
46852
|
-
Or run manually:
|
|
46931
|
+
Start as daemon: ${c.primary("locus daemon start")}
|
|
46932
|
+
Or run manually: ${c.primary("locus telegram start")}
|
|
46853
46933
|
`);
|
|
46854
46934
|
}
|
|
46855
|
-
function
|
|
46935
|
+
function showConfig(projectPath) {
|
|
46856
46936
|
const manager = new SettingsManager(projectPath);
|
|
46857
46937
|
const settings = manager.load();
|
|
46858
46938
|
const tg = settings.telegram;
|
|
@@ -46868,36 +46948,28 @@ function configCommand2(projectPath) {
|
|
|
46868
46948
|
console.log(` ${c.dim("File: .locus/settings.json (telegram section)")}
|
|
46869
46949
|
`);
|
|
46870
46950
|
const entries = [];
|
|
46871
|
-
if (tg.botToken)
|
|
46951
|
+
if (tg.botToken)
|
|
46872
46952
|
entries.push(["botToken", maskToken(tg.botToken)]);
|
|
46873
|
-
|
|
46874
|
-
if (tg.chatId) {
|
|
46953
|
+
if (tg.chatId)
|
|
46875
46954
|
entries.push(["chatId", String(tg.chatId)]);
|
|
46876
|
-
|
|
46877
|
-
if (tg.testMode !== undefined) {
|
|
46955
|
+
if (tg.testMode !== undefined)
|
|
46878
46956
|
entries.push(["testMode", String(tg.testMode)]);
|
|
46879
|
-
|
|
46880
|
-
if (settings.apiKey) {
|
|
46957
|
+
if (settings.apiKey)
|
|
46881
46958
|
entries.push(["apiKey (shared)", maskToken(settings.apiKey)]);
|
|
46882
|
-
|
|
46883
|
-
if (settings.apiUrl) {
|
|
46959
|
+
if (settings.apiUrl)
|
|
46884
46960
|
entries.push(["apiUrl (shared)", settings.apiUrl]);
|
|
46885
|
-
|
|
46886
|
-
if (settings.provider) {
|
|
46961
|
+
if (settings.provider)
|
|
46887
46962
|
entries.push(["provider (shared)", settings.provider]);
|
|
46888
|
-
|
|
46889
|
-
if (settings.model) {
|
|
46963
|
+
if (settings.model)
|
|
46890
46964
|
entries.push(["model (shared)", settings.model]);
|
|
46891
|
-
|
|
46892
|
-
if (settings.workspaceId) {
|
|
46965
|
+
if (settings.workspaceId)
|
|
46893
46966
|
entries.push(["workspaceId (shared)", settings.workspaceId]);
|
|
46894
|
-
}
|
|
46895
46967
|
for (const [key, value] of entries) {
|
|
46896
46968
|
console.log(` ${c.primary(`${key}:`)} ${value}`);
|
|
46897
46969
|
}
|
|
46898
46970
|
console.log("");
|
|
46899
46971
|
}
|
|
46900
|
-
function
|
|
46972
|
+
function setValue(args, projectPath) {
|
|
46901
46973
|
const key = args[0]?.trim();
|
|
46902
46974
|
const value = args.slice(1).join(" ").trim();
|
|
46903
46975
|
if (!key || !value) {
|
|
@@ -46940,7 +47012,7 @@ function setCommand2(args, projectPath) {
|
|
|
46940
47012
|
${c.success("✔")} Set ${c.primary(key)} = ${displayValue}
|
|
46941
47013
|
`);
|
|
46942
47014
|
}
|
|
46943
|
-
function
|
|
47015
|
+
function removeConfig(projectPath) {
|
|
46944
47016
|
const manager = new SettingsManager(projectPath);
|
|
46945
47017
|
const settings = manager.load();
|
|
46946
47018
|
if (!settings.telegram) {
|
|
@@ -46955,7 +47027,7 @@ function removeCommand2(projectPath) {
|
|
|
46955
47027
|
${c.success("✔")} ${c.bold("Telegram configuration removed.")}
|
|
46956
47028
|
`);
|
|
46957
47029
|
}
|
|
46958
|
-
function
|
|
47030
|
+
function startBot(projectPath) {
|
|
46959
47031
|
const manager = new SettingsManager(projectPath);
|
|
46960
47032
|
const settings = manager.load();
|
|
46961
47033
|
if (!settings.telegram?.botToken || !settings.telegram?.chatId) {
|
|
@@ -46965,22 +47037,14 @@ function runBotCommand(projectPath) {
|
|
|
46965
47037
|
`);
|
|
46966
47038
|
process.exit(1);
|
|
46967
47039
|
}
|
|
46968
|
-
const
|
|
46969
|
-
const isMonorepo =
|
|
46970
|
-
|
|
46971
|
-
|
|
46972
|
-
|
|
46973
|
-
cmd = "bun";
|
|
46974
|
-
args = ["run", monorepoTelegramEntry];
|
|
46975
|
-
} else {
|
|
46976
|
-
cmd = "locus-telegram";
|
|
46977
|
-
args = [];
|
|
46978
|
-
}
|
|
46979
|
-
const env = { ...process.env };
|
|
46980
|
-
const child = spawn5(cmd, args, {
|
|
47040
|
+
const monorepoEntry = join22(projectPath, "packages/telegram/src/index.ts");
|
|
47041
|
+
const isMonorepo = existsSync22(monorepoEntry);
|
|
47042
|
+
const cmd = isMonorepo ? "bun" : "locus-telegram";
|
|
47043
|
+
const cmdArgs = isMonorepo ? ["run", monorepoEntry] : [];
|
|
47044
|
+
const child = spawn5(cmd, cmdArgs, {
|
|
46981
47045
|
cwd: projectPath,
|
|
46982
47046
|
stdio: "inherit",
|
|
46983
|
-
env
|
|
47047
|
+
env: { ...process.env }
|
|
46984
47048
|
});
|
|
46985
47049
|
child.on("error", (err) => {
|
|
46986
47050
|
if (err.code === "ENOENT" && !isMonorepo) {
|
|
@@ -47001,22 +47065,23 @@ function runBotCommand(projectPath) {
|
|
|
47001
47065
|
}
|
|
47002
47066
|
async function telegramCommand(args) {
|
|
47003
47067
|
const projectPath = process.cwd();
|
|
47004
|
-
const subcommand = args
|
|
47068
|
+
const [subcommand, ...subArgs] = args;
|
|
47005
47069
|
switch (subcommand) {
|
|
47070
|
+
case "start":
|
|
47006
47071
|
case "run":
|
|
47007
|
-
|
|
47072
|
+
startBot(projectPath);
|
|
47008
47073
|
break;
|
|
47009
47074
|
case "setup":
|
|
47010
|
-
await
|
|
47075
|
+
await setup(subArgs, projectPath);
|
|
47011
47076
|
break;
|
|
47012
47077
|
case "config":
|
|
47013
|
-
|
|
47078
|
+
showConfig(projectPath);
|
|
47014
47079
|
break;
|
|
47015
47080
|
case "set":
|
|
47016
|
-
|
|
47081
|
+
setValue(subArgs, projectPath);
|
|
47017
47082
|
break;
|
|
47018
47083
|
case "remove":
|
|
47019
|
-
|
|
47084
|
+
removeConfig(projectPath);
|
|
47020
47085
|
break;
|
|
47021
47086
|
default:
|
|
47022
47087
|
showTelegramHelp();
|
|
@@ -47025,10 +47090,7 @@ async function telegramCommand(args) {
|
|
|
47025
47090
|
// src/commands/upgrade.ts
|
|
47026
47091
|
init_index_node();
|
|
47027
47092
|
import { execSync as execSync3 } from "node:child_process";
|
|
47028
|
-
import { existsSync as existsSync22 } from "node:fs";
|
|
47029
47093
|
var PACKAGES = ["@locusai/cli", "@locusai/telegram"];
|
|
47030
|
-
var SYSTEMD_UNIT_PATH2 = "/etc/systemd/system/locus.service";
|
|
47031
|
-
var SYSTEMD_TELEGRAM_UNIT_PATH = "/etc/systemd/system/locus-telegram.service";
|
|
47032
47094
|
function getInstalledVersion(pkg) {
|
|
47033
47095
|
try {
|
|
47034
47096
|
const output = execSync3(`npm list -g ${pkg} --depth=0 --json`, {
|
|
@@ -47055,6 +47117,7 @@ async function upgradeCommand() {
|
|
|
47055
47117
|
console.log(`
|
|
47056
47118
|
${c.header(" UPGRADE ")}
|
|
47057
47119
|
`);
|
|
47120
|
+
const daemonWasRunning = await isDaemonRunning();
|
|
47058
47121
|
try {
|
|
47059
47122
|
console.log(` ${c.dim("◌")} Cleaning npm cache...`);
|
|
47060
47123
|
execSync3("npm cache clean --force", {
|
|
@@ -47066,6 +47129,7 @@ async function upgradeCommand() {
|
|
|
47066
47129
|
console.log(` ${c.dim("⚠")} Could not clean npm cache, continuing...
|
|
47067
47130
|
`);
|
|
47068
47131
|
}
|
|
47132
|
+
let anyUpdated = false;
|
|
47069
47133
|
for (const pkg of PACKAGES) {
|
|
47070
47134
|
const current = getInstalledVersion(pkg);
|
|
47071
47135
|
const latest = getLatestVersion(pkg);
|
|
@@ -47088,33 +47152,26 @@ async function upgradeCommand() {
|
|
|
47088
47152
|
});
|
|
47089
47153
|
console.log(` ${c.success("✔")} ${c.bold(pkg)} updated to ${c.primary(`v${latest}`)}
|
|
47090
47154
|
`);
|
|
47155
|
+
anyUpdated = true;
|
|
47091
47156
|
} catch {
|
|
47092
47157
|
console.error(` ${c.error("✖")} Failed to update ${c.bold(pkg)}. Try manually:
|
|
47093
47158
|
` + ` ${c.primary(`npm install -g ${pkg}@latest`)}
|
|
47094
47159
|
`);
|
|
47095
47160
|
}
|
|
47096
47161
|
}
|
|
47097
|
-
if (
|
|
47098
|
-
|
|
47099
|
-
|
|
47100
|
-
|
|
47101
|
-
|
|
47102
|
-
if (!split) {
|
|
47103
|
-
throw "PATH NOTH FOUND";
|
|
47104
|
-
}
|
|
47105
|
-
const name = split.replace(".service", "");
|
|
47106
|
-
try {
|
|
47107
|
-
console.log(` ${c.info("▶")} Restarting ${name} service...`);
|
|
47108
|
-
execSync3(`systemctl restart ${name}`, {
|
|
47109
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
47110
|
-
});
|
|
47111
|
-
console.log(` ${c.success("✔")} ${name} service restarted
|
|
47162
|
+
if (daemonWasRunning && anyUpdated) {
|
|
47163
|
+
console.log(` ${c.info("▶")} Restarting locus daemon...`);
|
|
47164
|
+
const restarted = await restartDaemonIfRunning();
|
|
47165
|
+
if (restarted) {
|
|
47166
|
+
console.log(` ${c.success("✔")} Locus daemon restarted
|
|
47112
47167
|
`);
|
|
47113
|
-
|
|
47114
|
-
|
|
47168
|
+
} else {
|
|
47169
|
+
console.log(` ${c.dim("⚠")} Could not restart daemon (may need sudo)
|
|
47115
47170
|
`);
|
|
47116
|
-
}
|
|
47117
47171
|
}
|
|
47172
|
+
} else if (daemonWasRunning && !anyUpdated) {
|
|
47173
|
+
console.log(` ${c.dim("No updates — daemon left running")}
|
|
47174
|
+
`);
|
|
47118
47175
|
}
|
|
47119
47176
|
console.log("");
|
|
47120
47177
|
}
|
|
@@ -47187,6 +47244,9 @@ async function main() {
|
|
|
47187
47244
|
case "config":
|
|
47188
47245
|
await configCommand(args);
|
|
47189
47246
|
break;
|
|
47247
|
+
case "daemon":
|
|
47248
|
+
await daemonCommand(args);
|
|
47249
|
+
break;
|
|
47190
47250
|
case "service":
|
|
47191
47251
|
await serviceCommand(args);
|
|
47192
47252
|
break;
|