@locusai/cli 0.19.1 → 0.20.0
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 +307 -88
- package/package.json +1 -1
package/bin/locus.js
CHANGED
|
@@ -280,7 +280,7 @@ function drawBox(lines, options) {
|
|
|
280
280
|
return parts.join(`
|
|
281
281
|
`);
|
|
282
282
|
}
|
|
283
|
-
var cachedCapabilities = null, enabled = () => getCapabilities().colorBasic, bold, dim, italic, underline, strikethrough, red, green, yellow, blue, magenta, cyan, white, gray, redBright, greenBright, yellowBright, blueBright, magentaBright, cyanBright, bgRed, bgGreen, bgYellow, bgBlue, box;
|
|
283
|
+
var cachedCapabilities = null, enabled = () => getCapabilities().colorBasic, bold, dim, italic, underline, strikethrough, red, green, yellow, blue, magenta, cyan, white, gray, redBright, greenBright, yellowBright, blueBright, magentaBright, cyanBright, bgRed, bgGreen, bgYellow, bgBlue, bgGray, box;
|
|
284
284
|
var init_terminal = __esm(() => {
|
|
285
285
|
bold = wrap("\x1B[1m", "\x1B[22m");
|
|
286
286
|
dim = wrap("\x1B[2m", "\x1B[22m");
|
|
@@ -305,6 +305,7 @@ var init_terminal = __esm(() => {
|
|
|
305
305
|
bgGreen = wrap("\x1B[42m", "\x1B[49m");
|
|
306
306
|
bgYellow = wrap("\x1B[43m", "\x1B[49m");
|
|
307
307
|
bgBlue = wrap("\x1B[44m", "\x1B[49m");
|
|
308
|
+
bgGray = wrap("\x1B[100m", "\x1B[49m");
|
|
308
309
|
box = {
|
|
309
310
|
topLeft: "┌",
|
|
310
311
|
topRight: "┐",
|
|
@@ -1714,6 +1715,8 @@ ${bold("Initializing Locus...")}
|
|
|
1714
1715
|
config.sprint = { ...config.sprint, ...existing.sprint };
|
|
1715
1716
|
if (existing.logging)
|
|
1716
1717
|
config.logging = { ...config.logging, ...existing.logging };
|
|
1718
|
+
if (existing.sandbox)
|
|
1719
|
+
config.sandbox = { ...config.sandbox, ...existing.sandbox };
|
|
1717
1720
|
} catch {}
|
|
1718
1721
|
process.stderr.write(`${green("✓")} Updated config.json (preserved existing settings)
|
|
1719
1722
|
`);
|
|
@@ -1817,14 +1820,27 @@ ${bold("Sandbox mode")} ${dim("(recommended)")}
|
|
|
1817
1820
|
}
|
|
1818
1821
|
var LOCUS_MD_TEMPLATE = `## Planning First
|
|
1819
1822
|
|
|
1820
|
-
|
|
1823
|
+
**Before writing any code** for complex or multi-step tasks, you **must** create a plan file at \`.locus/plans/<task-name>.md\`. Do NOT skip this step — write the plan file to disk first, then execute.
|
|
1824
|
+
|
|
1825
|
+
**Plan file structure:**
|
|
1821
1826
|
- **Goal**: What we're trying to achieve and why
|
|
1822
1827
|
- **Approach**: Step-by-step strategy with technical decisions
|
|
1823
1828
|
- **Affected files**: List of files to create/modify/delete
|
|
1824
1829
|
- **Acceptance criteria**: Specific, testable conditions for completion
|
|
1825
1830
|
- **Dependencies**: Required packages, APIs, or external services
|
|
1826
1831
|
|
|
1827
|
-
|
|
1832
|
+
**When to plan:**
|
|
1833
|
+
- Tasks that touch 3+ files
|
|
1834
|
+
- New features or architectural changes
|
|
1835
|
+
- Tasks with ambiguous requirements that need decomposition
|
|
1836
|
+
- Any task where multiple approaches exist
|
|
1837
|
+
|
|
1838
|
+
**When you can skip planning:**
|
|
1839
|
+
- Single-file bug fixes with obvious root cause
|
|
1840
|
+
- Typo corrections, comment updates, or trivial changes
|
|
1841
|
+
- Tasks with very specific, step-by-step instructions already provided
|
|
1842
|
+
|
|
1843
|
+
Delete the planning \`.md\` files after successful execution.
|
|
1828
1844
|
|
|
1829
1845
|
## Code Quality
|
|
1830
1846
|
|
|
@@ -1834,6 +1850,26 @@ Delete the planning .md files after successful execution.
|
|
|
1834
1850
|
- **Test as you go**: If tests exist, run relevant ones. If breaking changes occur, update tests accordingly
|
|
1835
1851
|
- **Comment complex logic**: Explain *why*, not *what*. Focus on business logic and non-obvious decisions
|
|
1836
1852
|
|
|
1853
|
+
## Parallel Execution with Subagents
|
|
1854
|
+
|
|
1855
|
+
Use the **Task tool** to launch subagents for parallelizing independent work. Subagents run autonomously and return results when done.
|
|
1856
|
+
|
|
1857
|
+
**When to use subagents:**
|
|
1858
|
+
- **Codebase exploration**: Use \`subagent_type: "Explore"\` to search for files, patterns, or understand architecture across multiple locations simultaneously
|
|
1859
|
+
- **Independent research**: Launch multiple explore agents in parallel when you need to understand different parts of the codebase at once
|
|
1860
|
+
- **Complex multi-area changes**: When a task touches unrelated areas, use explore agents to gather context from each area in parallel before making changes
|
|
1861
|
+
|
|
1862
|
+
**How to use:**
|
|
1863
|
+
- Specify \`subagent_type\` — use \`"Explore"\` for codebase research, \`"general-purpose"\` for multi-step autonomous tasks
|
|
1864
|
+
- Launch multiple agents in a **single message** to run them concurrently
|
|
1865
|
+
- Provide clear, detailed prompts so agents can work autonomously
|
|
1866
|
+
- Do NOT duplicate work — if you delegate research to a subagent, wait for results instead of searching yourself
|
|
1867
|
+
|
|
1868
|
+
**When NOT to use subagents:**
|
|
1869
|
+
- Simple, directed searches (use Glob or Grep directly)
|
|
1870
|
+
- Reading a specific known file (use Read directly)
|
|
1871
|
+
- Tasks that require sequential steps where each depends on the previous
|
|
1872
|
+
|
|
1837
1873
|
## Artifacts
|
|
1838
1874
|
|
|
1839
1875
|
When a task produces knowledge, analysis, or research output rather than (or in addition to) code changes, you **must** save results as Markdown in ".locus/artifacts/<descriptive-name>.md":
|
|
@@ -1990,7 +2026,8 @@ import {
|
|
|
1990
2026
|
import { homedir as homedir2 } from "node:os";
|
|
1991
2027
|
import { join as join6 } from "node:path";
|
|
1992
2028
|
function getPackagesDir() {
|
|
1993
|
-
const
|
|
2029
|
+
const home = process.env.HOME || homedir2();
|
|
2030
|
+
const dir = join6(home, ".locus", "packages");
|
|
1994
2031
|
if (!existsSync6(dir)) {
|
|
1995
2032
|
mkdirSync6(dir, { recursive: true });
|
|
1996
2033
|
}
|
|
@@ -2903,6 +2940,8 @@ class StreamRenderer {
|
|
|
2903
2940
|
renderTimer = null;
|
|
2904
2941
|
catchUpMode = false;
|
|
2905
2942
|
totalLinesRendered = 0;
|
|
2943
|
+
isFirstLine = true;
|
|
2944
|
+
hasContent = false;
|
|
2906
2945
|
onRender;
|
|
2907
2946
|
constructor(onRender) {
|
|
2908
2947
|
this.onRender = onRender ?? ((line) => process.stdout.write(`${line}\r
|
|
@@ -2918,6 +2957,12 @@ class StreamRenderer {
|
|
|
2918
2957
|
}
|
|
2919
2958
|
push(text) {
|
|
2920
2959
|
this.buffer += text;
|
|
2960
|
+
if (!this.hasContent) {
|
|
2961
|
+
this.buffer = this.buffer.replace(/^\n+/, "");
|
|
2962
|
+
if (this.buffer.length === 0)
|
|
2963
|
+
return;
|
|
2964
|
+
this.hasContent = true;
|
|
2965
|
+
}
|
|
2921
2966
|
const lines = this.buffer.split(`
|
|
2922
2967
|
`);
|
|
2923
2968
|
this.buffer = lines.pop() ?? "";
|
|
@@ -2930,6 +2975,9 @@ class StreamRenderer {
|
|
|
2930
2975
|
clearInterval(this.renderTimer);
|
|
2931
2976
|
this.renderTimer = null;
|
|
2932
2977
|
}
|
|
2978
|
+
while (this.lineQueue.length > 0 && this.lineQueue[this.lineQueue.length - 1]?.trim() === "") {
|
|
2979
|
+
this.lineQueue.pop();
|
|
2980
|
+
}
|
|
2933
2981
|
while (this.lineQueue.length > 0) {
|
|
2934
2982
|
const line = this.lineQueue.shift();
|
|
2935
2983
|
if (line !== undefined)
|
|
@@ -2937,8 +2985,8 @@ class StreamRenderer {
|
|
|
2937
2985
|
}
|
|
2938
2986
|
if (this.buffer.trim()) {
|
|
2939
2987
|
this.renderLine(this.buffer);
|
|
2940
|
-
this.buffer = "";
|
|
2941
2988
|
}
|
|
2989
|
+
this.buffer = "";
|
|
2942
2990
|
this.inTable = false;
|
|
2943
2991
|
}
|
|
2944
2992
|
getLinesRendered() {
|
|
@@ -2965,7 +3013,10 @@ class StreamRenderer {
|
|
|
2965
3013
|
}
|
|
2966
3014
|
}
|
|
2967
3015
|
renderLine(raw) {
|
|
2968
|
-
const
|
|
3016
|
+
const prefix = this.isFirstLine ? `${dim("●")} ` : " ";
|
|
3017
|
+
if (this.isFirstLine)
|
|
3018
|
+
this.isFirstLine = false;
|
|
3019
|
+
const formatted = `${prefix}${this.formatMarkdown(raw)}`;
|
|
2969
3020
|
beginSync();
|
|
2970
3021
|
this.onRender(formatted);
|
|
2971
3022
|
endSync();
|
|
@@ -3016,7 +3067,7 @@ class StreamRenderer {
|
|
|
3016
3067
|
result = result.replace(/\*\*([^*]+)\*\*/g, (_, content) => bold(content));
|
|
3017
3068
|
result = result.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, (_, content) => italic(content));
|
|
3018
3069
|
result = result.replace(/~~([^~]+)~~/g, (_, content) => dim(content));
|
|
3019
|
-
result = result.replace(/`([^`]+)`/g, (_, content) => cyan(content));
|
|
3070
|
+
result = result.replace(/`([^`]+)`/g, (_, content) => bold(cyan(content)));
|
|
3020
3071
|
return result;
|
|
3021
3072
|
}
|
|
3022
3073
|
formatTableLine(line) {
|
|
@@ -5126,6 +5177,9 @@ ${red("✗")} ${dim("Force exit.")}\r
|
|
|
5126
5177
|
if (!hasOutput) {
|
|
5127
5178
|
hasOutput = true;
|
|
5128
5179
|
indicator.stop();
|
|
5180
|
+
if (!options.silent)
|
|
5181
|
+
process.stdout.write(`
|
|
5182
|
+
`);
|
|
5129
5183
|
}
|
|
5130
5184
|
renderer?.push(chunk);
|
|
5131
5185
|
output += chunk;
|
|
@@ -5148,6 +5202,10 @@ ${red("✗")} ${dim("Force exit.")}\r
|
|
|
5148
5202
|
});
|
|
5149
5203
|
renderer?.stop();
|
|
5150
5204
|
indicator.stop();
|
|
5205
|
+
if (hasOutput && !wasAborted && renderer) {
|
|
5206
|
+
process.stdout.write(`
|
|
5207
|
+
`);
|
|
5208
|
+
}
|
|
5151
5209
|
if (wasAborted) {
|
|
5152
5210
|
return {
|
|
5153
5211
|
success: false,
|
|
@@ -7894,15 +7952,31 @@ ${red("✗")} ${aiResult.error}
|
|
|
7894
7952
|
function printWelcome(session) {
|
|
7895
7953
|
process.stderr.write(`
|
|
7896
7954
|
`);
|
|
7897
|
-
|
|
7955
|
+
const logoWidth = 10;
|
|
7956
|
+
const gap = " ";
|
|
7957
|
+
const infoLines = [
|
|
7958
|
+
`${bold("Locus")} ${dim("REPL")}`,
|
|
7959
|
+
`${dim("Provider:")} ${session.metadata.provider} ${dim("/")} ${session.metadata.model}`,
|
|
7960
|
+
`${dim("Session:")} ${dim(session.id)}`
|
|
7961
|
+
];
|
|
7962
|
+
const textOffset = 1;
|
|
7963
|
+
const totalLines = Math.max(LOCUS_LOGO.length, infoLines.length + textOffset);
|
|
7964
|
+
for (let i = 0;i < totalLines; i++) {
|
|
7965
|
+
const logoLine = LOCUS_LOGO[i] ?? "";
|
|
7966
|
+
const paddedLogo = logoLine.padEnd(logoWidth);
|
|
7967
|
+
const infoIdx = i - textOffset;
|
|
7968
|
+
const infoLine = infoIdx >= 0 && infoIdx < infoLines.length ? infoLines[infoIdx] : "";
|
|
7969
|
+
process.stderr.write(`${bold(white(paddedLogo))}${gap}${infoLine}
|
|
7898
7970
|
`);
|
|
7899
|
-
|
|
7971
|
+
}
|
|
7972
|
+
process.stderr.write(`
|
|
7900
7973
|
`);
|
|
7901
|
-
process.stderr.write(
|
|
7974
|
+
process.stderr.write(` ${dim("Type /help for commands, Shift+Enter for newline, Ctrl+C twice to exit")}
|
|
7902
7975
|
`);
|
|
7903
7976
|
process.stderr.write(`
|
|
7904
7977
|
`);
|
|
7905
7978
|
}
|
|
7979
|
+
var LOCUS_LOGO;
|
|
7906
7980
|
var init_repl = __esm(() => {
|
|
7907
7981
|
init_run_ai();
|
|
7908
7982
|
init_runner();
|
|
@@ -7917,6 +7991,18 @@ var init_repl = __esm(() => {
|
|
|
7917
7991
|
init_input_history();
|
|
7918
7992
|
init_model_config();
|
|
7919
7993
|
init_session_manager();
|
|
7994
|
+
LOCUS_LOGO = [
|
|
7995
|
+
" ▄█ ",
|
|
7996
|
+
" ▄▄████▄▄▄▄ ",
|
|
7997
|
+
" ████▀ ████ ",
|
|
7998
|
+
" ████▄▄████ ",
|
|
7999
|
+
" ▀█▄█▀███▀▀ ▄▄██▄▄ ",
|
|
8000
|
+
" ████ ▄██████▀███▄ ",
|
|
8001
|
+
" ████▄██▀ ▀▀███ ▄████ ",
|
|
8002
|
+
" ▀▀██████▄ ██████▀ ",
|
|
8003
|
+
" ▀▀█████▄▄██▀▀ ",
|
|
8004
|
+
" ▀▀██▀▀ "
|
|
8005
|
+
];
|
|
7920
8006
|
});
|
|
7921
8007
|
|
|
7922
8008
|
// src/commands/exec.ts
|
|
@@ -8590,15 +8676,6 @@ var init_run_state = __esm(() => {
|
|
|
8590
8676
|
});
|
|
8591
8677
|
|
|
8592
8678
|
// src/core/shutdown.ts
|
|
8593
|
-
import { execSync as execSync12 } from "node:child_process";
|
|
8594
|
-
function cleanupActiveSandboxes() {
|
|
8595
|
-
for (const name of activeSandboxes) {
|
|
8596
|
-
try {
|
|
8597
|
-
execSync12(`docker sandbox rm ${name}`, { timeout: 1e4 });
|
|
8598
|
-
} catch {}
|
|
8599
|
-
}
|
|
8600
|
-
activeSandboxes.clear();
|
|
8601
|
-
}
|
|
8602
8679
|
function registerShutdownHandlers(ctx) {
|
|
8603
8680
|
shutdownContext = ctx;
|
|
8604
8681
|
interruptCount = 0;
|
|
@@ -8632,7 +8709,6 @@ Interrupted. Saving state...
|
|
|
8632
8709
|
`);
|
|
8633
8710
|
}
|
|
8634
8711
|
}
|
|
8635
|
-
cleanupActiveSandboxes();
|
|
8636
8712
|
shutdownContext?.onShutdown?.();
|
|
8637
8713
|
if (interruptTimer)
|
|
8638
8714
|
clearTimeout(interruptTimer);
|
|
@@ -8660,18 +8736,17 @@ Interrupted. Saving state...
|
|
|
8660
8736
|
}
|
|
8661
8737
|
};
|
|
8662
8738
|
}
|
|
8663
|
-
var shutdownRegistered = false, shutdownContext = null, interruptCount = 0, interruptTimer = null
|
|
8739
|
+
var shutdownRegistered = false, shutdownContext = null, interruptCount = 0, interruptTimer = null;
|
|
8664
8740
|
var init_shutdown = __esm(() => {
|
|
8665
8741
|
init_run_state();
|
|
8666
|
-
activeSandboxes = new Set;
|
|
8667
8742
|
});
|
|
8668
8743
|
|
|
8669
8744
|
// src/core/worktree.ts
|
|
8670
|
-
import { execSync as
|
|
8745
|
+
import { execSync as execSync12 } from "node:child_process";
|
|
8671
8746
|
import { existsSync as existsSync17, readdirSync as readdirSync6, realpathSync, statSync as statSync3 } from "node:fs";
|
|
8672
8747
|
import { join as join17 } from "node:path";
|
|
8673
8748
|
function git3(args, cwd) {
|
|
8674
|
-
return
|
|
8749
|
+
return execSync12(`git ${args}`, {
|
|
8675
8750
|
cwd,
|
|
8676
8751
|
encoding: "utf-8",
|
|
8677
8752
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8696,7 +8771,7 @@ function generateBranchName(issueNumber) {
|
|
|
8696
8771
|
}
|
|
8697
8772
|
function getWorktreeBranch(worktreePath) {
|
|
8698
8773
|
try {
|
|
8699
|
-
return
|
|
8774
|
+
return execSync12("git branch --show-current", {
|
|
8700
8775
|
cwd: worktreePath,
|
|
8701
8776
|
encoding: "utf-8",
|
|
8702
8777
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8821,7 +8896,7 @@ var exports_run = {};
|
|
|
8821
8896
|
__export(exports_run, {
|
|
8822
8897
|
runCommand: () => runCommand
|
|
8823
8898
|
});
|
|
8824
|
-
import { execSync as
|
|
8899
|
+
import { execSync as execSync13 } from "node:child_process";
|
|
8825
8900
|
function resolveExecutionContext(config, modelOverride) {
|
|
8826
8901
|
const model = modelOverride ?? config.ai.model;
|
|
8827
8902
|
const provider = inferProviderFromModel(model) ?? config.ai.provider;
|
|
@@ -8972,7 +9047,7 @@ ${yellow("⚠")} A sprint run is already in progress.
|
|
|
8972
9047
|
}
|
|
8973
9048
|
if (!flags.dryRun) {
|
|
8974
9049
|
try {
|
|
8975
|
-
|
|
9050
|
+
execSync13(`git checkout -B ${branchName}`, {
|
|
8976
9051
|
cwd: projectRoot,
|
|
8977
9052
|
encoding: "utf-8",
|
|
8978
9053
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9022,7 +9097,7 @@ ${red("✗")} Auto-rebase failed. Resolve manually.
|
|
|
9022
9097
|
let sprintContext;
|
|
9023
9098
|
if (i > 0 && !flags.dryRun) {
|
|
9024
9099
|
try {
|
|
9025
|
-
sprintContext =
|
|
9100
|
+
sprintContext = execSync13(`git diff origin/${config.agent.baseBranch}..HEAD`, {
|
|
9026
9101
|
cwd: projectRoot,
|
|
9027
9102
|
encoding: "utf-8",
|
|
9028
9103
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9087,7 +9162,7 @@ ${bold("Summary:")}
|
|
|
9087
9162
|
const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
|
|
9088
9163
|
if (prNumber !== undefined) {
|
|
9089
9164
|
try {
|
|
9090
|
-
|
|
9165
|
+
execSync13(`git checkout ${config.agent.baseBranch}`, {
|
|
9091
9166
|
cwd: projectRoot,
|
|
9092
9167
|
encoding: "utf-8",
|
|
9093
9168
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9287,13 +9362,13 @@ ${bold("Resuming")} ${state.type} run ${dim(state.runId)}
|
|
|
9287
9362
|
`);
|
|
9288
9363
|
if (state.type === "sprint" && state.branch) {
|
|
9289
9364
|
try {
|
|
9290
|
-
const currentBranch =
|
|
9365
|
+
const currentBranch = execSync13("git rev-parse --abbrev-ref HEAD", {
|
|
9291
9366
|
cwd: projectRoot,
|
|
9292
9367
|
encoding: "utf-8",
|
|
9293
9368
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9294
9369
|
}).trim();
|
|
9295
9370
|
if (currentBranch !== state.branch) {
|
|
9296
|
-
|
|
9371
|
+
execSync13(`git checkout ${state.branch}`, {
|
|
9297
9372
|
cwd: projectRoot,
|
|
9298
9373
|
encoding: "utf-8",
|
|
9299
9374
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9360,7 +9435,7 @@ ${bold("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fail
|
|
|
9360
9435
|
const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
|
|
9361
9436
|
if (prNumber !== undefined) {
|
|
9362
9437
|
try {
|
|
9363
|
-
|
|
9438
|
+
execSync13(`git checkout ${config.agent.baseBranch}`, {
|
|
9364
9439
|
cwd: projectRoot,
|
|
9365
9440
|
encoding: "utf-8",
|
|
9366
9441
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9391,14 +9466,14 @@ function getOrder2(issue) {
|
|
|
9391
9466
|
}
|
|
9392
9467
|
function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
9393
9468
|
try {
|
|
9394
|
-
const status =
|
|
9469
|
+
const status = execSync13("git status --porcelain", {
|
|
9395
9470
|
cwd: projectRoot,
|
|
9396
9471
|
encoding: "utf-8",
|
|
9397
9472
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9398
9473
|
}).trim();
|
|
9399
9474
|
if (!status)
|
|
9400
9475
|
return;
|
|
9401
|
-
|
|
9476
|
+
execSync13("git add -A", {
|
|
9402
9477
|
cwd: projectRoot,
|
|
9403
9478
|
encoding: "utf-8",
|
|
9404
9479
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9406,7 +9481,7 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
|
9406
9481
|
const message = `chore: complete #${issueNumber} - ${issueTitle}
|
|
9407
9482
|
|
|
9408
9483
|
Co-Authored-By: LocusAgent <agent@locusai.team>`;
|
|
9409
|
-
|
|
9484
|
+
execSync13(`git commit -F -`, {
|
|
9410
9485
|
input: message,
|
|
9411
9486
|
cwd: projectRoot,
|
|
9412
9487
|
encoding: "utf-8",
|
|
@@ -9420,7 +9495,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
9420
9495
|
if (!config.agent.autoPR)
|
|
9421
9496
|
return;
|
|
9422
9497
|
try {
|
|
9423
|
-
const diff =
|
|
9498
|
+
const diff = execSync13(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
|
|
9424
9499
|
cwd: projectRoot,
|
|
9425
9500
|
encoding: "utf-8",
|
|
9426
9501
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9430,7 +9505,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
9430
9505
|
`);
|
|
9431
9506
|
return;
|
|
9432
9507
|
}
|
|
9433
|
-
|
|
9508
|
+
execSync13(`git push -u origin ${branchName}`, {
|
|
9434
9509
|
cwd: projectRoot,
|
|
9435
9510
|
encoding: "utf-8",
|
|
9436
9511
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10208,7 +10283,7 @@ var exports_review = {};
|
|
|
10208
10283
|
__export(exports_review, {
|
|
10209
10284
|
reviewCommand: () => reviewCommand
|
|
10210
10285
|
});
|
|
10211
|
-
import { execSync as
|
|
10286
|
+
import { execSync as execSync14 } from "node:child_process";
|
|
10212
10287
|
import { existsSync as existsSync19, readFileSync as readFileSync14 } from "node:fs";
|
|
10213
10288
|
import { join as join19 } from "node:path";
|
|
10214
10289
|
function printHelp2() {
|
|
@@ -10286,7 +10361,7 @@ ${bold("Review complete:")} ${green(`✓ ${reviewed}`)}${failed > 0 ? ` ${red(`
|
|
|
10286
10361
|
async function reviewSinglePR(projectRoot, config, prNumber, focus, flags) {
|
|
10287
10362
|
let prInfo;
|
|
10288
10363
|
try {
|
|
10289
|
-
const result =
|
|
10364
|
+
const result = execSync14(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
10290
10365
|
const raw = JSON.parse(result);
|
|
10291
10366
|
prInfo = {
|
|
10292
10367
|
number: raw.number,
|
|
@@ -10352,7 +10427,7 @@ ${output.slice(0, 60000)}
|
|
|
10352
10427
|
|
|
10353
10428
|
---
|
|
10354
10429
|
_Reviewed by Locus AI (${config.ai.provider}/${flags.model ?? config.ai.model})_`;
|
|
10355
|
-
|
|
10430
|
+
execSync14(`gh pr comment ${pr.number} --body ${JSON.stringify(reviewBody)}`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
10356
10431
|
process.stderr.write(` ${green("✓")} Review posted ${dim(`(${timer.formatted()})`)}
|
|
10357
10432
|
`);
|
|
10358
10433
|
} catch (e) {
|
|
@@ -10432,7 +10507,7 @@ var exports_iterate = {};
|
|
|
10432
10507
|
__export(exports_iterate, {
|
|
10433
10508
|
iterateCommand: () => iterateCommand
|
|
10434
10509
|
});
|
|
10435
|
-
import { execSync as
|
|
10510
|
+
import { execSync as execSync15 } from "node:child_process";
|
|
10436
10511
|
function printHelp3() {
|
|
10437
10512
|
process.stderr.write(`
|
|
10438
10513
|
${bold("locus iterate")} — Re-execute tasks with PR feedback
|
|
@@ -10642,12 +10717,12 @@ ${bold("Summary:")} ${green(`✓ ${succeeded}`)}${failed > 0 ? ` ${red(`✗ ${fa
|
|
|
10642
10717
|
}
|
|
10643
10718
|
function findPRForIssue(projectRoot, issueNumber) {
|
|
10644
10719
|
try {
|
|
10645
|
-
const result =
|
|
10720
|
+
const result = execSync15(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
10646
10721
|
const parsed = JSON.parse(result);
|
|
10647
10722
|
if (parsed.length > 0) {
|
|
10648
10723
|
return parsed[0].number;
|
|
10649
10724
|
}
|
|
10650
|
-
const branchResult =
|
|
10725
|
+
const branchResult = execSync15(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
10651
10726
|
const branchParsed = JSON.parse(branchResult);
|
|
10652
10727
|
if (branchParsed.length > 0) {
|
|
10653
10728
|
return branchParsed[0].number;
|
|
@@ -11266,9 +11341,10 @@ __export(exports_sandbox2, {
|
|
|
11266
11341
|
parseSandboxInstallArgs: () => parseSandboxInstallArgs,
|
|
11267
11342
|
parseSandboxExecArgs: () => parseSandboxExecArgs
|
|
11268
11343
|
});
|
|
11269
|
-
import { execSync as
|
|
11344
|
+
import { execSync as execSync16, spawn as spawn6 } from "node:child_process";
|
|
11270
11345
|
import { createHash } from "node:crypto";
|
|
11271
|
-
import {
|
|
11346
|
+
import { existsSync as existsSync22, readFileSync as readFileSync17 } from "node:fs";
|
|
11347
|
+
import { basename as basename4, join as join22 } from "node:path";
|
|
11272
11348
|
function printSandboxHelp() {
|
|
11273
11349
|
process.stderr.write(`
|
|
11274
11350
|
${bold("locus sandbox")} — Manage Docker sandbox lifecycle
|
|
@@ -11277,15 +11353,15 @@ ${bold("Usage:")}
|
|
|
11277
11353
|
locus sandbox ${dim("# Create claude/codex sandboxes and enable sandbox mode")}
|
|
11278
11354
|
locus sandbox claude ${dim("# Run claude interactively (for login)")}
|
|
11279
11355
|
locus sandbox codex ${dim("# Run codex interactively (for login)")}
|
|
11356
|
+
locus sandbox setup ${dim("# Re-run dependency install in sandbox(es)")}
|
|
11280
11357
|
locus sandbox install <pkg> ${dim("# npm install -g package(s) in sandbox(es)")}
|
|
11281
|
-
locus sandbox exec <provider> -- <cmd...> ${dim("# Run one command inside provider sandbox")}
|
|
11282
11358
|
locus sandbox shell <provider> ${dim("# Open interactive shell in provider sandbox")}
|
|
11283
11359
|
locus sandbox logs <provider> ${dim("# Show provider sandbox logs")}
|
|
11284
11360
|
locus sandbox rm ${dim("# Destroy all provider sandboxes and disable sandbox mode")}
|
|
11285
11361
|
locus sandbox status ${dim("# Show current sandbox state")}
|
|
11286
11362
|
|
|
11287
11363
|
${bold("Flow:")}
|
|
11288
|
-
1. ${cyan("locus sandbox")} Create
|
|
11364
|
+
1. ${cyan("locus sandbox")} Create sandboxes (auto-installs dependencies)
|
|
11289
11365
|
2. ${cyan("locus sandbox claude")} Login Claude inside its sandbox
|
|
11290
11366
|
3. ${cyan("locus sandbox codex")} Login Codex inside its sandbox
|
|
11291
11367
|
4. ${cyan("locus sandbox install bun")} Install extra tools (optional)
|
|
@@ -11301,10 +11377,10 @@ async function sandboxCommand(projectRoot, args) {
|
|
|
11301
11377
|
case "claude":
|
|
11302
11378
|
case "codex":
|
|
11303
11379
|
return handleAgentLogin(projectRoot, subcommand);
|
|
11380
|
+
case "setup":
|
|
11381
|
+
return handleSetup(projectRoot);
|
|
11304
11382
|
case "install":
|
|
11305
11383
|
return handleInstall(projectRoot, args.slice(1));
|
|
11306
|
-
case "exec":
|
|
11307
|
-
return handleExec(projectRoot, args.slice(1));
|
|
11308
11384
|
case "shell":
|
|
11309
11385
|
return handleShell(projectRoot, args.slice(1));
|
|
11310
11386
|
case "logs":
|
|
@@ -11318,7 +11394,7 @@ async function sandboxCommand(projectRoot, args) {
|
|
|
11318
11394
|
default:
|
|
11319
11395
|
process.stderr.write(`${red("✗")} Unknown sandbox subcommand: ${bold(subcommand)}
|
|
11320
11396
|
`);
|
|
11321
|
-
process.stderr.write(` Available: ${cyan("claude")}, ${cyan("codex")}, ${cyan("install")}, ${cyan("exec")}, ${cyan("shell")}, ${cyan("logs")}, ${cyan("rm")}, ${cyan("status")}
|
|
11397
|
+
process.stderr.write(` Available: ${cyan("claude")}, ${cyan("codex")}, ${cyan("setup")}, ${cyan("install")}, ${cyan("exec")}, ${cyan("shell")}, ${cyan("logs")}, ${cyan("rm")}, ${cyan("status")}
|
|
11322
11398
|
`);
|
|
11323
11399
|
}
|
|
11324
11400
|
}
|
|
@@ -11334,14 +11410,14 @@ async function handleCreate(projectRoot) {
|
|
|
11334
11410
|
}
|
|
11335
11411
|
const sandboxNames = buildProviderSandboxNames(projectRoot);
|
|
11336
11412
|
const readySandboxes = {};
|
|
11413
|
+
const newlyCreated = new Set;
|
|
11337
11414
|
let failed = false;
|
|
11338
|
-
|
|
11415
|
+
const createResults = await Promise.all(PROVIDERS.map(async (provider) => {
|
|
11339
11416
|
const name = sandboxNames[provider];
|
|
11340
11417
|
if (isSandboxAlive(name)) {
|
|
11341
11418
|
process.stderr.write(`${green("✓")} ${provider} sandbox ready: ${bold(name)}
|
|
11342
11419
|
`);
|
|
11343
|
-
|
|
11344
|
-
continue;
|
|
11420
|
+
return { provider, name, created: false, existed: true };
|
|
11345
11421
|
}
|
|
11346
11422
|
process.stderr.write(`Creating ${bold(provider)} sandbox ${dim(name)} with workspace ${dim(projectRoot)}...
|
|
11347
11423
|
`);
|
|
@@ -11349,16 +11425,28 @@ async function handleCreate(projectRoot) {
|
|
|
11349
11425
|
if (!created) {
|
|
11350
11426
|
process.stderr.write(`${red("✗")} Failed to create ${provider} sandbox (${name}).
|
|
11351
11427
|
`);
|
|
11352
|
-
|
|
11353
|
-
continue;
|
|
11428
|
+
return { provider, name, created: false, existed: false };
|
|
11354
11429
|
}
|
|
11355
11430
|
process.stderr.write(`${green("✓")} ${provider} sandbox created: ${bold(name)}
|
|
11356
11431
|
`);
|
|
11357
|
-
|
|
11432
|
+
return { provider, name, created: true, existed: false };
|
|
11433
|
+
}));
|
|
11434
|
+
for (const result of createResults) {
|
|
11435
|
+
if (result.created || result.existed) {
|
|
11436
|
+
readySandboxes[result.provider] = result.name;
|
|
11437
|
+
if (result.created)
|
|
11438
|
+
newlyCreated.add(result.name);
|
|
11439
|
+
} else {
|
|
11440
|
+
failed = true;
|
|
11441
|
+
}
|
|
11358
11442
|
}
|
|
11359
11443
|
config.sandbox.enabled = true;
|
|
11360
11444
|
config.sandbox.providers = readySandboxes;
|
|
11361
11445
|
saveConfig(projectRoot, config);
|
|
11446
|
+
await Promise.all(PROVIDERS.filter((provider) => {
|
|
11447
|
+
const sandboxName = readySandboxes[provider];
|
|
11448
|
+
return sandboxName && newlyCreated.has(sandboxName);
|
|
11449
|
+
}).map((provider) => runSandboxSetup(readySandboxes[provider], projectRoot)));
|
|
11362
11450
|
if (failed) {
|
|
11363
11451
|
process.stderr.write(`
|
|
11364
11452
|
${yellow("⚠")} Some sandboxes failed to create. Re-run ${cyan("locus sandbox")} after resolving Docker issues.
|
|
@@ -11432,7 +11520,7 @@ function handleRemove(projectRoot) {
|
|
|
11432
11520
|
process.stderr.write(`Removing sandbox ${bold(sandboxName)}...
|
|
11433
11521
|
`);
|
|
11434
11522
|
try {
|
|
11435
|
-
|
|
11523
|
+
execSync16(`docker sandbox rm ${sandboxName}`, {
|
|
11436
11524
|
encoding: "utf-8",
|
|
11437
11525
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11438
11526
|
timeout: 15000
|
|
@@ -11607,26 +11695,6 @@ function parseSandboxExecArgs(args) {
|
|
|
11607
11695
|
}
|
|
11608
11696
|
return { provider, command };
|
|
11609
11697
|
}
|
|
11610
|
-
async function handleExec(projectRoot, args) {
|
|
11611
|
-
const parsed = parseSandboxExecArgs(args);
|
|
11612
|
-
if (parsed.error || !parsed.provider) {
|
|
11613
|
-
process.stderr.write(`${red("✗")} ${parsed.error}
|
|
11614
|
-
`);
|
|
11615
|
-
return;
|
|
11616
|
-
}
|
|
11617
|
-
const sandboxName = getActiveProviderSandbox(projectRoot, parsed.provider);
|
|
11618
|
-
if (!sandboxName) {
|
|
11619
|
-
return;
|
|
11620
|
-
}
|
|
11621
|
-
await runInteractiveCommand("docker", [
|
|
11622
|
-
"sandbox",
|
|
11623
|
-
"exec",
|
|
11624
|
-
"-w",
|
|
11625
|
-
projectRoot,
|
|
11626
|
-
sandboxName,
|
|
11627
|
-
...parsed.command
|
|
11628
|
-
]);
|
|
11629
|
-
}
|
|
11630
11698
|
async function handleShell(projectRoot, args) {
|
|
11631
11699
|
const provider = args[0];
|
|
11632
11700
|
if (provider !== "claude" && provider !== "codex") {
|
|
@@ -11724,6 +11792,104 @@ async function handleLogs(projectRoot, args) {
|
|
|
11724
11792
|
dockerArgs.push(sandboxName);
|
|
11725
11793
|
await runInteractiveCommand("docker", dockerArgs);
|
|
11726
11794
|
}
|
|
11795
|
+
function detectPackageManager(projectRoot) {
|
|
11796
|
+
try {
|
|
11797
|
+
const raw = readFileSync17(join22(projectRoot, "package.json"), "utf-8");
|
|
11798
|
+
const pkgJson = JSON.parse(raw);
|
|
11799
|
+
if (typeof pkgJson.packageManager === "string") {
|
|
11800
|
+
const name = pkgJson.packageManager.split("@")[0];
|
|
11801
|
+
if (name === "bun" || name === "npm" || name === "yarn" || name === "pnpm") {
|
|
11802
|
+
return name;
|
|
11803
|
+
}
|
|
11804
|
+
}
|
|
11805
|
+
} catch {}
|
|
11806
|
+
if (existsSync22(join22(projectRoot, "bun.lock")) || existsSync22(join22(projectRoot, "bun.lockb"))) {
|
|
11807
|
+
return "bun";
|
|
11808
|
+
}
|
|
11809
|
+
if (existsSync22(join22(projectRoot, "yarn.lock"))) {
|
|
11810
|
+
return "yarn";
|
|
11811
|
+
}
|
|
11812
|
+
if (existsSync22(join22(projectRoot, "pnpm-lock.yaml"))) {
|
|
11813
|
+
return "pnpm";
|
|
11814
|
+
}
|
|
11815
|
+
return "npm";
|
|
11816
|
+
}
|
|
11817
|
+
function getInstallCommand(pm) {
|
|
11818
|
+
switch (pm) {
|
|
11819
|
+
case "bun":
|
|
11820
|
+
return ["bun", "install"];
|
|
11821
|
+
case "yarn":
|
|
11822
|
+
return ["yarn", "install"];
|
|
11823
|
+
case "pnpm":
|
|
11824
|
+
return ["pnpm", "install"];
|
|
11825
|
+
case "npm":
|
|
11826
|
+
return ["npm", "install"];
|
|
11827
|
+
}
|
|
11828
|
+
}
|
|
11829
|
+
async function runSandboxSetup(sandboxName, projectRoot) {
|
|
11830
|
+
const pm = detectPackageManager(projectRoot);
|
|
11831
|
+
if (pm !== "npm") {
|
|
11832
|
+
await ensurePackageManagerInSandbox(sandboxName, pm);
|
|
11833
|
+
}
|
|
11834
|
+
const installCmd = getInstallCommand(pm);
|
|
11835
|
+
process.stderr.write(`
|
|
11836
|
+
Installing dependencies (${bold(installCmd.join(" "))}) in sandbox ${dim(sandboxName)}...
|
|
11837
|
+
`);
|
|
11838
|
+
const installOk = await runInteractiveCommand("docker", [
|
|
11839
|
+
"sandbox",
|
|
11840
|
+
"exec",
|
|
11841
|
+
"-w",
|
|
11842
|
+
projectRoot,
|
|
11843
|
+
sandboxName,
|
|
11844
|
+
...installCmd
|
|
11845
|
+
]);
|
|
11846
|
+
if (!installOk) {
|
|
11847
|
+
process.stderr.write(`${red("✗")} Dependency install failed in sandbox ${dim(sandboxName)}.
|
|
11848
|
+
`);
|
|
11849
|
+
return false;
|
|
11850
|
+
}
|
|
11851
|
+
process.stderr.write(`${green("✓")} Dependencies installed in sandbox ${dim(sandboxName)}.
|
|
11852
|
+
`);
|
|
11853
|
+
const setupScript = join22(projectRoot, ".locus", "sandbox-setup.sh");
|
|
11854
|
+
if (existsSync22(setupScript)) {
|
|
11855
|
+
process.stderr.write(`Running ${bold(".locus/sandbox-setup.sh")} in sandbox ${dim(sandboxName)}...
|
|
11856
|
+
`);
|
|
11857
|
+
const hookOk = await runInteractiveCommand("docker", [
|
|
11858
|
+
"sandbox",
|
|
11859
|
+
"exec",
|
|
11860
|
+
"-w",
|
|
11861
|
+
projectRoot,
|
|
11862
|
+
sandboxName,
|
|
11863
|
+
"sh",
|
|
11864
|
+
setupScript
|
|
11865
|
+
]);
|
|
11866
|
+
if (!hookOk) {
|
|
11867
|
+
process.stderr.write(`${yellow("⚠")} Setup hook failed in sandbox ${dim(sandboxName)}.
|
|
11868
|
+
`);
|
|
11869
|
+
}
|
|
11870
|
+
}
|
|
11871
|
+
return true;
|
|
11872
|
+
}
|
|
11873
|
+
async function handleSetup(projectRoot) {
|
|
11874
|
+
const config = loadConfig(projectRoot);
|
|
11875
|
+
const providers = config.sandbox.providers;
|
|
11876
|
+
if (!providers.claude && !providers.codex) {
|
|
11877
|
+
process.stderr.write(`${red("✗")} No sandboxes configured. Run ${cyan("locus sandbox")} first.
|
|
11878
|
+
`);
|
|
11879
|
+
return;
|
|
11880
|
+
}
|
|
11881
|
+
for (const provider of PROVIDERS) {
|
|
11882
|
+
const sandboxName = providers[provider];
|
|
11883
|
+
if (!sandboxName)
|
|
11884
|
+
continue;
|
|
11885
|
+
if (!isSandboxAlive(sandboxName)) {
|
|
11886
|
+
process.stderr.write(`${yellow("⚠")} ${provider} sandbox is not running: ${dim(sandboxName)}
|
|
11887
|
+
`);
|
|
11888
|
+
continue;
|
|
11889
|
+
}
|
|
11890
|
+
await runSandboxSetup(sandboxName, projectRoot);
|
|
11891
|
+
}
|
|
11892
|
+
}
|
|
11727
11893
|
function buildProviderSandboxNames(projectRoot) {
|
|
11728
11894
|
const segment = sanitizeSegment(basename4(projectRoot));
|
|
11729
11895
|
const hash = createHash("sha1").update(projectRoot).digest("hex").slice(0, 8);
|
|
@@ -11768,7 +11934,7 @@ function runInteractiveCommand(command, args) {
|
|
|
11768
11934
|
}
|
|
11769
11935
|
async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
11770
11936
|
try {
|
|
11771
|
-
|
|
11937
|
+
execSync16(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
|
|
11772
11938
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11773
11939
|
timeout: 120000
|
|
11774
11940
|
});
|
|
@@ -11782,9 +11948,30 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
|
11782
11948
|
await enforceSandboxIgnore(sandboxName, projectRoot);
|
|
11783
11949
|
return true;
|
|
11784
11950
|
}
|
|
11951
|
+
async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
11952
|
+
try {
|
|
11953
|
+
execSync16(`docker sandbox exec ${sandboxName} which ${pm}`, {
|
|
11954
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
11955
|
+
timeout: 5000
|
|
11956
|
+
});
|
|
11957
|
+
} catch {
|
|
11958
|
+
const npmPkg = pm === "bun" ? "bun" : pm === "yarn" ? "yarn" : "pnpm";
|
|
11959
|
+
process.stderr.write(`Installing ${bold(pm)} in sandbox...
|
|
11960
|
+
`);
|
|
11961
|
+
try {
|
|
11962
|
+
execSync16(`docker sandbox exec ${sandboxName} npm install -g ${npmPkg}`, {
|
|
11963
|
+
stdio: "inherit",
|
|
11964
|
+
timeout: 120000
|
|
11965
|
+
});
|
|
11966
|
+
} catch {
|
|
11967
|
+
process.stderr.write(`${yellow("⚠")} Failed to install ${pm} in sandbox. Dependency install may fail.
|
|
11968
|
+
`);
|
|
11969
|
+
}
|
|
11970
|
+
}
|
|
11971
|
+
}
|
|
11785
11972
|
async function ensureCodexInSandbox(sandboxName) {
|
|
11786
11973
|
try {
|
|
11787
|
-
|
|
11974
|
+
execSync16(`docker sandbox exec ${sandboxName} which codex`, {
|
|
11788
11975
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11789
11976
|
timeout: 5000
|
|
11790
11977
|
});
|
|
@@ -11792,7 +11979,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
11792
11979
|
process.stderr.write(`Installing codex in sandbox...
|
|
11793
11980
|
`);
|
|
11794
11981
|
try {
|
|
11795
|
-
|
|
11982
|
+
execSync16(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
|
|
11796
11983
|
} catch {
|
|
11797
11984
|
process.stderr.write(`${red("✗")} Failed to install codex in sandbox.
|
|
11798
11985
|
`);
|
|
@@ -11801,7 +11988,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
11801
11988
|
}
|
|
11802
11989
|
function isSandboxAlive(name) {
|
|
11803
11990
|
try {
|
|
11804
|
-
const output =
|
|
11991
|
+
const output = execSync16("docker sandbox ls", {
|
|
11805
11992
|
encoding: "utf-8",
|
|
11806
11993
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11807
11994
|
timeout: 5000
|
|
@@ -11826,17 +12013,17 @@ init_context();
|
|
|
11826
12013
|
init_logger();
|
|
11827
12014
|
init_rate_limiter();
|
|
11828
12015
|
init_terminal();
|
|
11829
|
-
import { existsSync as
|
|
11830
|
-
import { join as
|
|
12016
|
+
import { existsSync as existsSync23, readFileSync as readFileSync18 } from "node:fs";
|
|
12017
|
+
import { join as join23 } from "node:path";
|
|
11831
12018
|
import { fileURLToPath } from "node:url";
|
|
11832
12019
|
function getCliVersion() {
|
|
11833
12020
|
const fallbackVersion = "0.0.0";
|
|
11834
|
-
const packageJsonPath =
|
|
11835
|
-
if (!
|
|
12021
|
+
const packageJsonPath = join23(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
|
|
12022
|
+
if (!existsSync23(packageJsonPath)) {
|
|
11836
12023
|
return fallbackVersion;
|
|
11837
12024
|
}
|
|
11838
12025
|
try {
|
|
11839
|
-
const parsed = JSON.parse(
|
|
12026
|
+
const parsed = JSON.parse(readFileSync18(packageJsonPath, "utf-8"));
|
|
11840
12027
|
return parsed.version ?? fallbackVersion;
|
|
11841
12028
|
} catch {
|
|
11842
12029
|
return fallbackVersion;
|
|
@@ -11950,9 +12137,41 @@ function parseArgs(argv) {
|
|
|
11950
12137
|
const args = positional.slice(1);
|
|
11951
12138
|
return { command, args, flags };
|
|
11952
12139
|
}
|
|
12140
|
+
var LOCUS_LOGO2 = [
|
|
12141
|
+
" ▄█ ",
|
|
12142
|
+
"▄▄████▄▄▄▄ ",
|
|
12143
|
+
"████▀ ████ ",
|
|
12144
|
+
"████▄▄████ ",
|
|
12145
|
+
"▀█▄█▀███▀▀ ▄▄██▄▄ ",
|
|
12146
|
+
"████ ▄██████▀███▄ ",
|
|
12147
|
+
"████▄██▀ ▀▀███ ▄████ ",
|
|
12148
|
+
"▀▀██████▄ ██████▀ ",
|
|
12149
|
+
" ▀▀█████▄▄██▀▀ ",
|
|
12150
|
+
" ▀▀██▀▀ "
|
|
12151
|
+
];
|
|
12152
|
+
function printLogo() {
|
|
12153
|
+
process.stderr.write(`
|
|
12154
|
+
`);
|
|
12155
|
+
const logoWidth = 10;
|
|
12156
|
+
const gap = " ";
|
|
12157
|
+
const infoLines = [
|
|
12158
|
+
`${bold("Locus")} ${dim(`v${VERSION}`)}`,
|
|
12159
|
+
`${dim("GitHub-native AI engineering assistant")}`
|
|
12160
|
+
];
|
|
12161
|
+
const textOffset = 1;
|
|
12162
|
+
const totalLines = Math.max(LOCUS_LOGO2.length, infoLines.length + textOffset);
|
|
12163
|
+
for (let i = 0;i < totalLines; i++) {
|
|
12164
|
+
const logoLine = LOCUS_LOGO2[i] ?? "";
|
|
12165
|
+
const paddedLogo = logoLine.padEnd(logoWidth);
|
|
12166
|
+
const infoIdx = i - textOffset;
|
|
12167
|
+
const infoLine = infoIdx >= 0 && infoIdx < infoLines.length ? infoLines[infoIdx] : "";
|
|
12168
|
+
process.stderr.write(`${bold(white(paddedLogo))}${gap}${infoLine}
|
|
12169
|
+
`);
|
|
12170
|
+
}
|
|
12171
|
+
}
|
|
11953
12172
|
function printHelp6() {
|
|
12173
|
+
printLogo();
|
|
11954
12174
|
process.stderr.write(`
|
|
11955
|
-
${bold("Locus")} ${dim(`v${VERSION}`)} — GitHub-native AI engineering assistant
|
|
11956
12175
|
|
|
11957
12176
|
${bold("Usage:")}
|
|
11958
12177
|
locus <command> [options]
|
|
@@ -12065,7 +12284,7 @@ async function main() {
|
|
|
12065
12284
|
try {
|
|
12066
12285
|
const root = getGitRoot(cwd);
|
|
12067
12286
|
if (isInitialized(root)) {
|
|
12068
|
-
logDir =
|
|
12287
|
+
logDir = join23(root, ".locus", "logs");
|
|
12069
12288
|
getRateLimiter(root);
|
|
12070
12289
|
}
|
|
12071
12290
|
} catch {}
|