@locusai/cli 0.19.2 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/locus.js +307 -89
- 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) {
|
|
@@ -4483,7 +4534,6 @@ class SandboxedClaudeRunner {
|
|
|
4483
4534
|
const text = chunk.toString();
|
|
4484
4535
|
errorOutput += text;
|
|
4485
4536
|
log.debug("sandboxed claude stderr", { text: text.slice(0, 500) });
|
|
4486
|
-
options.onOutput?.(text);
|
|
4487
4537
|
});
|
|
4488
4538
|
this.process.on("close", (code) => {
|
|
4489
4539
|
this.process = null;
|
|
@@ -5126,6 +5176,9 @@ ${red("✗")} ${dim("Force exit.")}\r
|
|
|
5126
5176
|
if (!hasOutput) {
|
|
5127
5177
|
hasOutput = true;
|
|
5128
5178
|
indicator.stop();
|
|
5179
|
+
if (!options.silent)
|
|
5180
|
+
process.stdout.write(`
|
|
5181
|
+
`);
|
|
5129
5182
|
}
|
|
5130
5183
|
renderer?.push(chunk);
|
|
5131
5184
|
output += chunk;
|
|
@@ -5148,6 +5201,10 @@ ${red("✗")} ${dim("Force exit.")}\r
|
|
|
5148
5201
|
});
|
|
5149
5202
|
renderer?.stop();
|
|
5150
5203
|
indicator.stop();
|
|
5204
|
+
if (hasOutput && !wasAborted && renderer) {
|
|
5205
|
+
process.stdout.write(`
|
|
5206
|
+
`);
|
|
5207
|
+
}
|
|
5151
5208
|
if (wasAborted) {
|
|
5152
5209
|
return {
|
|
5153
5210
|
success: false,
|
|
@@ -7894,15 +7951,31 @@ ${red("✗")} ${aiResult.error}
|
|
|
7894
7951
|
function printWelcome(session) {
|
|
7895
7952
|
process.stderr.write(`
|
|
7896
7953
|
`);
|
|
7897
|
-
|
|
7954
|
+
const logoWidth = 10;
|
|
7955
|
+
const gap = " ";
|
|
7956
|
+
const infoLines = [
|
|
7957
|
+
`${bold("Locus")} ${dim("REPL")}`,
|
|
7958
|
+
`${dim("Provider:")} ${session.metadata.provider} ${dim("/")} ${session.metadata.model}`,
|
|
7959
|
+
`${dim("Session:")} ${dim(session.id)}`
|
|
7960
|
+
];
|
|
7961
|
+
const textOffset = 1;
|
|
7962
|
+
const totalLines = Math.max(LOCUS_LOGO.length, infoLines.length + textOffset);
|
|
7963
|
+
for (let i = 0;i < totalLines; i++) {
|
|
7964
|
+
const logoLine = LOCUS_LOGO[i] ?? "";
|
|
7965
|
+
const paddedLogo = logoLine.padEnd(logoWidth);
|
|
7966
|
+
const infoIdx = i - textOffset;
|
|
7967
|
+
const infoLine = infoIdx >= 0 && infoIdx < infoLines.length ? infoLines[infoIdx] : "";
|
|
7968
|
+
process.stderr.write(`${bold(white(paddedLogo))}${gap}${infoLine}
|
|
7898
7969
|
`);
|
|
7899
|
-
|
|
7970
|
+
}
|
|
7971
|
+
process.stderr.write(`
|
|
7900
7972
|
`);
|
|
7901
|
-
process.stderr.write(
|
|
7973
|
+
process.stderr.write(` ${dim("Type /help for commands, Shift+Enter for newline, Ctrl+C twice to exit")}
|
|
7902
7974
|
`);
|
|
7903
7975
|
process.stderr.write(`
|
|
7904
7976
|
`);
|
|
7905
7977
|
}
|
|
7978
|
+
var LOCUS_LOGO;
|
|
7906
7979
|
var init_repl = __esm(() => {
|
|
7907
7980
|
init_run_ai();
|
|
7908
7981
|
init_runner();
|
|
@@ -7917,6 +7990,18 @@ var init_repl = __esm(() => {
|
|
|
7917
7990
|
init_input_history();
|
|
7918
7991
|
init_model_config();
|
|
7919
7992
|
init_session_manager();
|
|
7993
|
+
LOCUS_LOGO = [
|
|
7994
|
+
" ▄█ ",
|
|
7995
|
+
" ▄▄████▄▄▄▄ ",
|
|
7996
|
+
" ████▀ ████ ",
|
|
7997
|
+
" ████▄▄████ ",
|
|
7998
|
+
" ▀█▄█▀███▀▀ ▄▄██▄▄ ",
|
|
7999
|
+
" ████ ▄██████▀███▄ ",
|
|
8000
|
+
" ████▄██▀ ▀▀███ ▄████ ",
|
|
8001
|
+
" ▀▀██████▄ ██████▀ ",
|
|
8002
|
+
" ▀▀█████▄▄██▀▀ ",
|
|
8003
|
+
" ▀▀██▀▀ "
|
|
8004
|
+
];
|
|
7920
8005
|
});
|
|
7921
8006
|
|
|
7922
8007
|
// src/commands/exec.ts
|
|
@@ -8590,15 +8675,6 @@ var init_run_state = __esm(() => {
|
|
|
8590
8675
|
});
|
|
8591
8676
|
|
|
8592
8677
|
// 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
8678
|
function registerShutdownHandlers(ctx) {
|
|
8603
8679
|
shutdownContext = ctx;
|
|
8604
8680
|
interruptCount = 0;
|
|
@@ -8632,7 +8708,6 @@ Interrupted. Saving state...
|
|
|
8632
8708
|
`);
|
|
8633
8709
|
}
|
|
8634
8710
|
}
|
|
8635
|
-
cleanupActiveSandboxes();
|
|
8636
8711
|
shutdownContext?.onShutdown?.();
|
|
8637
8712
|
if (interruptTimer)
|
|
8638
8713
|
clearTimeout(interruptTimer);
|
|
@@ -8660,18 +8735,17 @@ Interrupted. Saving state...
|
|
|
8660
8735
|
}
|
|
8661
8736
|
};
|
|
8662
8737
|
}
|
|
8663
|
-
var shutdownRegistered = false, shutdownContext = null, interruptCount = 0, interruptTimer = null
|
|
8738
|
+
var shutdownRegistered = false, shutdownContext = null, interruptCount = 0, interruptTimer = null;
|
|
8664
8739
|
var init_shutdown = __esm(() => {
|
|
8665
8740
|
init_run_state();
|
|
8666
|
-
activeSandboxes = new Set;
|
|
8667
8741
|
});
|
|
8668
8742
|
|
|
8669
8743
|
// src/core/worktree.ts
|
|
8670
|
-
import { execSync as
|
|
8744
|
+
import { execSync as execSync12 } from "node:child_process";
|
|
8671
8745
|
import { existsSync as existsSync17, readdirSync as readdirSync6, realpathSync, statSync as statSync3 } from "node:fs";
|
|
8672
8746
|
import { join as join17 } from "node:path";
|
|
8673
8747
|
function git3(args, cwd) {
|
|
8674
|
-
return
|
|
8748
|
+
return execSync12(`git ${args}`, {
|
|
8675
8749
|
cwd,
|
|
8676
8750
|
encoding: "utf-8",
|
|
8677
8751
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8696,7 +8770,7 @@ function generateBranchName(issueNumber) {
|
|
|
8696
8770
|
}
|
|
8697
8771
|
function getWorktreeBranch(worktreePath) {
|
|
8698
8772
|
try {
|
|
8699
|
-
return
|
|
8773
|
+
return execSync12("git branch --show-current", {
|
|
8700
8774
|
cwd: worktreePath,
|
|
8701
8775
|
encoding: "utf-8",
|
|
8702
8776
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8821,7 +8895,7 @@ var exports_run = {};
|
|
|
8821
8895
|
__export(exports_run, {
|
|
8822
8896
|
runCommand: () => runCommand
|
|
8823
8897
|
});
|
|
8824
|
-
import { execSync as
|
|
8898
|
+
import { execSync as execSync13 } from "node:child_process";
|
|
8825
8899
|
function resolveExecutionContext(config, modelOverride) {
|
|
8826
8900
|
const model = modelOverride ?? config.ai.model;
|
|
8827
8901
|
const provider = inferProviderFromModel(model) ?? config.ai.provider;
|
|
@@ -8972,7 +9046,7 @@ ${yellow("⚠")} A sprint run is already in progress.
|
|
|
8972
9046
|
}
|
|
8973
9047
|
if (!flags.dryRun) {
|
|
8974
9048
|
try {
|
|
8975
|
-
|
|
9049
|
+
execSync13(`git checkout -B ${branchName}`, {
|
|
8976
9050
|
cwd: projectRoot,
|
|
8977
9051
|
encoding: "utf-8",
|
|
8978
9052
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9022,7 +9096,7 @@ ${red("✗")} Auto-rebase failed. Resolve manually.
|
|
|
9022
9096
|
let sprintContext;
|
|
9023
9097
|
if (i > 0 && !flags.dryRun) {
|
|
9024
9098
|
try {
|
|
9025
|
-
sprintContext =
|
|
9099
|
+
sprintContext = execSync13(`git diff origin/${config.agent.baseBranch}..HEAD`, {
|
|
9026
9100
|
cwd: projectRoot,
|
|
9027
9101
|
encoding: "utf-8",
|
|
9028
9102
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9087,7 +9161,7 @@ ${bold("Summary:")}
|
|
|
9087
9161
|
const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
|
|
9088
9162
|
if (prNumber !== undefined) {
|
|
9089
9163
|
try {
|
|
9090
|
-
|
|
9164
|
+
execSync13(`git checkout ${config.agent.baseBranch}`, {
|
|
9091
9165
|
cwd: projectRoot,
|
|
9092
9166
|
encoding: "utf-8",
|
|
9093
9167
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9287,13 +9361,13 @@ ${bold("Resuming")} ${state.type} run ${dim(state.runId)}
|
|
|
9287
9361
|
`);
|
|
9288
9362
|
if (state.type === "sprint" && state.branch) {
|
|
9289
9363
|
try {
|
|
9290
|
-
const currentBranch =
|
|
9364
|
+
const currentBranch = execSync13("git rev-parse --abbrev-ref HEAD", {
|
|
9291
9365
|
cwd: projectRoot,
|
|
9292
9366
|
encoding: "utf-8",
|
|
9293
9367
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9294
9368
|
}).trim();
|
|
9295
9369
|
if (currentBranch !== state.branch) {
|
|
9296
|
-
|
|
9370
|
+
execSync13(`git checkout ${state.branch}`, {
|
|
9297
9371
|
cwd: projectRoot,
|
|
9298
9372
|
encoding: "utf-8",
|
|
9299
9373
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9360,7 +9434,7 @@ ${bold("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fail
|
|
|
9360
9434
|
const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
|
|
9361
9435
|
if (prNumber !== undefined) {
|
|
9362
9436
|
try {
|
|
9363
|
-
|
|
9437
|
+
execSync13(`git checkout ${config.agent.baseBranch}`, {
|
|
9364
9438
|
cwd: projectRoot,
|
|
9365
9439
|
encoding: "utf-8",
|
|
9366
9440
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9391,14 +9465,14 @@ function getOrder2(issue) {
|
|
|
9391
9465
|
}
|
|
9392
9466
|
function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
9393
9467
|
try {
|
|
9394
|
-
const status =
|
|
9468
|
+
const status = execSync13("git status --porcelain", {
|
|
9395
9469
|
cwd: projectRoot,
|
|
9396
9470
|
encoding: "utf-8",
|
|
9397
9471
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9398
9472
|
}).trim();
|
|
9399
9473
|
if (!status)
|
|
9400
9474
|
return;
|
|
9401
|
-
|
|
9475
|
+
execSync13("git add -A", {
|
|
9402
9476
|
cwd: projectRoot,
|
|
9403
9477
|
encoding: "utf-8",
|
|
9404
9478
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9406,7 +9480,7 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
|
9406
9480
|
const message = `chore: complete #${issueNumber} - ${issueTitle}
|
|
9407
9481
|
|
|
9408
9482
|
Co-Authored-By: LocusAgent <agent@locusai.team>`;
|
|
9409
|
-
|
|
9483
|
+
execSync13(`git commit -F -`, {
|
|
9410
9484
|
input: message,
|
|
9411
9485
|
cwd: projectRoot,
|
|
9412
9486
|
encoding: "utf-8",
|
|
@@ -9420,7 +9494,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
9420
9494
|
if (!config.agent.autoPR)
|
|
9421
9495
|
return;
|
|
9422
9496
|
try {
|
|
9423
|
-
const diff =
|
|
9497
|
+
const diff = execSync13(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
|
|
9424
9498
|
cwd: projectRoot,
|
|
9425
9499
|
encoding: "utf-8",
|
|
9426
9500
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9430,7 +9504,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
9430
9504
|
`);
|
|
9431
9505
|
return;
|
|
9432
9506
|
}
|
|
9433
|
-
|
|
9507
|
+
execSync13(`git push -u origin ${branchName}`, {
|
|
9434
9508
|
cwd: projectRoot,
|
|
9435
9509
|
encoding: "utf-8",
|
|
9436
9510
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10208,7 +10282,7 @@ var exports_review = {};
|
|
|
10208
10282
|
__export(exports_review, {
|
|
10209
10283
|
reviewCommand: () => reviewCommand
|
|
10210
10284
|
});
|
|
10211
|
-
import { execSync as
|
|
10285
|
+
import { execSync as execSync14 } from "node:child_process";
|
|
10212
10286
|
import { existsSync as existsSync19, readFileSync as readFileSync14 } from "node:fs";
|
|
10213
10287
|
import { join as join19 } from "node:path";
|
|
10214
10288
|
function printHelp2() {
|
|
@@ -10286,7 +10360,7 @@ ${bold("Review complete:")} ${green(`✓ ${reviewed}`)}${failed > 0 ? ` ${red(`
|
|
|
10286
10360
|
async function reviewSinglePR(projectRoot, config, prNumber, focus, flags) {
|
|
10287
10361
|
let prInfo;
|
|
10288
10362
|
try {
|
|
10289
|
-
const result =
|
|
10363
|
+
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
10364
|
const raw = JSON.parse(result);
|
|
10291
10365
|
prInfo = {
|
|
10292
10366
|
number: raw.number,
|
|
@@ -10352,7 +10426,7 @@ ${output.slice(0, 60000)}
|
|
|
10352
10426
|
|
|
10353
10427
|
---
|
|
10354
10428
|
_Reviewed by Locus AI (${config.ai.provider}/${flags.model ?? config.ai.model})_`;
|
|
10355
|
-
|
|
10429
|
+
execSync14(`gh pr comment ${pr.number} --body ${JSON.stringify(reviewBody)}`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
10356
10430
|
process.stderr.write(` ${green("✓")} Review posted ${dim(`(${timer.formatted()})`)}
|
|
10357
10431
|
`);
|
|
10358
10432
|
} catch (e) {
|
|
@@ -10432,7 +10506,7 @@ var exports_iterate = {};
|
|
|
10432
10506
|
__export(exports_iterate, {
|
|
10433
10507
|
iterateCommand: () => iterateCommand
|
|
10434
10508
|
});
|
|
10435
|
-
import { execSync as
|
|
10509
|
+
import { execSync as execSync15 } from "node:child_process";
|
|
10436
10510
|
function printHelp3() {
|
|
10437
10511
|
process.stderr.write(`
|
|
10438
10512
|
${bold("locus iterate")} — Re-execute tasks with PR feedback
|
|
@@ -10642,12 +10716,12 @@ ${bold("Summary:")} ${green(`✓ ${succeeded}`)}${failed > 0 ? ` ${red(`✗ ${fa
|
|
|
10642
10716
|
}
|
|
10643
10717
|
function findPRForIssue(projectRoot, issueNumber) {
|
|
10644
10718
|
try {
|
|
10645
|
-
const result =
|
|
10719
|
+
const result = execSync15(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
10646
10720
|
const parsed = JSON.parse(result);
|
|
10647
10721
|
if (parsed.length > 0) {
|
|
10648
10722
|
return parsed[0].number;
|
|
10649
10723
|
}
|
|
10650
|
-
const branchResult =
|
|
10724
|
+
const branchResult = execSync15(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
10651
10725
|
const branchParsed = JSON.parse(branchResult);
|
|
10652
10726
|
if (branchParsed.length > 0) {
|
|
10653
10727
|
return branchParsed[0].number;
|
|
@@ -11266,9 +11340,10 @@ __export(exports_sandbox2, {
|
|
|
11266
11340
|
parseSandboxInstallArgs: () => parseSandboxInstallArgs,
|
|
11267
11341
|
parseSandboxExecArgs: () => parseSandboxExecArgs
|
|
11268
11342
|
});
|
|
11269
|
-
import { execSync as
|
|
11343
|
+
import { execSync as execSync16, spawn as spawn6 } from "node:child_process";
|
|
11270
11344
|
import { createHash } from "node:crypto";
|
|
11271
|
-
import {
|
|
11345
|
+
import { existsSync as existsSync22, readFileSync as readFileSync17 } from "node:fs";
|
|
11346
|
+
import { basename as basename4, join as join22 } from "node:path";
|
|
11272
11347
|
function printSandboxHelp() {
|
|
11273
11348
|
process.stderr.write(`
|
|
11274
11349
|
${bold("locus sandbox")} — Manage Docker sandbox lifecycle
|
|
@@ -11277,15 +11352,15 @@ ${bold("Usage:")}
|
|
|
11277
11352
|
locus sandbox ${dim("# Create claude/codex sandboxes and enable sandbox mode")}
|
|
11278
11353
|
locus sandbox claude ${dim("# Run claude interactively (for login)")}
|
|
11279
11354
|
locus sandbox codex ${dim("# Run codex interactively (for login)")}
|
|
11355
|
+
locus sandbox setup ${dim("# Re-run dependency install in sandbox(es)")}
|
|
11280
11356
|
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
11357
|
locus sandbox shell <provider> ${dim("# Open interactive shell in provider sandbox")}
|
|
11283
11358
|
locus sandbox logs <provider> ${dim("# Show provider sandbox logs")}
|
|
11284
11359
|
locus sandbox rm ${dim("# Destroy all provider sandboxes and disable sandbox mode")}
|
|
11285
11360
|
locus sandbox status ${dim("# Show current sandbox state")}
|
|
11286
11361
|
|
|
11287
11362
|
${bold("Flow:")}
|
|
11288
|
-
1. ${cyan("locus sandbox")} Create
|
|
11363
|
+
1. ${cyan("locus sandbox")} Create sandboxes (auto-installs dependencies)
|
|
11289
11364
|
2. ${cyan("locus sandbox claude")} Login Claude inside its sandbox
|
|
11290
11365
|
3. ${cyan("locus sandbox codex")} Login Codex inside its sandbox
|
|
11291
11366
|
4. ${cyan("locus sandbox install bun")} Install extra tools (optional)
|
|
@@ -11301,10 +11376,10 @@ async function sandboxCommand(projectRoot, args) {
|
|
|
11301
11376
|
case "claude":
|
|
11302
11377
|
case "codex":
|
|
11303
11378
|
return handleAgentLogin(projectRoot, subcommand);
|
|
11379
|
+
case "setup":
|
|
11380
|
+
return handleSetup(projectRoot);
|
|
11304
11381
|
case "install":
|
|
11305
11382
|
return handleInstall(projectRoot, args.slice(1));
|
|
11306
|
-
case "exec":
|
|
11307
|
-
return handleExec(projectRoot, args.slice(1));
|
|
11308
11383
|
case "shell":
|
|
11309
11384
|
return handleShell(projectRoot, args.slice(1));
|
|
11310
11385
|
case "logs":
|
|
@@ -11318,7 +11393,7 @@ async function sandboxCommand(projectRoot, args) {
|
|
|
11318
11393
|
default:
|
|
11319
11394
|
process.stderr.write(`${red("✗")} Unknown sandbox subcommand: ${bold(subcommand)}
|
|
11320
11395
|
`);
|
|
11321
|
-
process.stderr.write(` Available: ${cyan("claude")}, ${cyan("codex")}, ${cyan("install")}, ${cyan("exec")}, ${cyan("shell")}, ${cyan("logs")}, ${cyan("rm")}, ${cyan("status")}
|
|
11396
|
+
process.stderr.write(` Available: ${cyan("claude")}, ${cyan("codex")}, ${cyan("setup")}, ${cyan("install")}, ${cyan("exec")}, ${cyan("shell")}, ${cyan("logs")}, ${cyan("rm")}, ${cyan("status")}
|
|
11322
11397
|
`);
|
|
11323
11398
|
}
|
|
11324
11399
|
}
|
|
@@ -11334,14 +11409,14 @@ async function handleCreate(projectRoot) {
|
|
|
11334
11409
|
}
|
|
11335
11410
|
const sandboxNames = buildProviderSandboxNames(projectRoot);
|
|
11336
11411
|
const readySandboxes = {};
|
|
11412
|
+
const newlyCreated = new Set;
|
|
11337
11413
|
let failed = false;
|
|
11338
|
-
|
|
11414
|
+
const createResults = await Promise.all(PROVIDERS.map(async (provider) => {
|
|
11339
11415
|
const name = sandboxNames[provider];
|
|
11340
11416
|
if (isSandboxAlive(name)) {
|
|
11341
11417
|
process.stderr.write(`${green("✓")} ${provider} sandbox ready: ${bold(name)}
|
|
11342
11418
|
`);
|
|
11343
|
-
|
|
11344
|
-
continue;
|
|
11419
|
+
return { provider, name, created: false, existed: true };
|
|
11345
11420
|
}
|
|
11346
11421
|
process.stderr.write(`Creating ${bold(provider)} sandbox ${dim(name)} with workspace ${dim(projectRoot)}...
|
|
11347
11422
|
`);
|
|
@@ -11349,16 +11424,28 @@ async function handleCreate(projectRoot) {
|
|
|
11349
11424
|
if (!created) {
|
|
11350
11425
|
process.stderr.write(`${red("✗")} Failed to create ${provider} sandbox (${name}).
|
|
11351
11426
|
`);
|
|
11352
|
-
|
|
11353
|
-
continue;
|
|
11427
|
+
return { provider, name, created: false, existed: false };
|
|
11354
11428
|
}
|
|
11355
11429
|
process.stderr.write(`${green("✓")} ${provider} sandbox created: ${bold(name)}
|
|
11356
11430
|
`);
|
|
11357
|
-
|
|
11431
|
+
return { provider, name, created: true, existed: false };
|
|
11432
|
+
}));
|
|
11433
|
+
for (const result of createResults) {
|
|
11434
|
+
if (result.created || result.existed) {
|
|
11435
|
+
readySandboxes[result.provider] = result.name;
|
|
11436
|
+
if (result.created)
|
|
11437
|
+
newlyCreated.add(result.name);
|
|
11438
|
+
} else {
|
|
11439
|
+
failed = true;
|
|
11440
|
+
}
|
|
11358
11441
|
}
|
|
11359
11442
|
config.sandbox.enabled = true;
|
|
11360
11443
|
config.sandbox.providers = readySandboxes;
|
|
11361
11444
|
saveConfig(projectRoot, config);
|
|
11445
|
+
await Promise.all(PROVIDERS.filter((provider) => {
|
|
11446
|
+
const sandboxName = readySandboxes[provider];
|
|
11447
|
+
return sandboxName && newlyCreated.has(sandboxName);
|
|
11448
|
+
}).map((provider) => runSandboxSetup(readySandboxes[provider], projectRoot)));
|
|
11362
11449
|
if (failed) {
|
|
11363
11450
|
process.stderr.write(`
|
|
11364
11451
|
${yellow("⚠")} Some sandboxes failed to create. Re-run ${cyan("locus sandbox")} after resolving Docker issues.
|
|
@@ -11432,7 +11519,7 @@ function handleRemove(projectRoot) {
|
|
|
11432
11519
|
process.stderr.write(`Removing sandbox ${bold(sandboxName)}...
|
|
11433
11520
|
`);
|
|
11434
11521
|
try {
|
|
11435
|
-
|
|
11522
|
+
execSync16(`docker sandbox rm ${sandboxName}`, {
|
|
11436
11523
|
encoding: "utf-8",
|
|
11437
11524
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11438
11525
|
timeout: 15000
|
|
@@ -11607,26 +11694,6 @@ function parseSandboxExecArgs(args) {
|
|
|
11607
11694
|
}
|
|
11608
11695
|
return { provider, command };
|
|
11609
11696
|
}
|
|
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
11697
|
async function handleShell(projectRoot, args) {
|
|
11631
11698
|
const provider = args[0];
|
|
11632
11699
|
if (provider !== "claude" && provider !== "codex") {
|
|
@@ -11724,6 +11791,104 @@ async function handleLogs(projectRoot, args) {
|
|
|
11724
11791
|
dockerArgs.push(sandboxName);
|
|
11725
11792
|
await runInteractiveCommand("docker", dockerArgs);
|
|
11726
11793
|
}
|
|
11794
|
+
function detectPackageManager(projectRoot) {
|
|
11795
|
+
try {
|
|
11796
|
+
const raw = readFileSync17(join22(projectRoot, "package.json"), "utf-8");
|
|
11797
|
+
const pkgJson = JSON.parse(raw);
|
|
11798
|
+
if (typeof pkgJson.packageManager === "string") {
|
|
11799
|
+
const name = pkgJson.packageManager.split("@")[0];
|
|
11800
|
+
if (name === "bun" || name === "npm" || name === "yarn" || name === "pnpm") {
|
|
11801
|
+
return name;
|
|
11802
|
+
}
|
|
11803
|
+
}
|
|
11804
|
+
} catch {}
|
|
11805
|
+
if (existsSync22(join22(projectRoot, "bun.lock")) || existsSync22(join22(projectRoot, "bun.lockb"))) {
|
|
11806
|
+
return "bun";
|
|
11807
|
+
}
|
|
11808
|
+
if (existsSync22(join22(projectRoot, "yarn.lock"))) {
|
|
11809
|
+
return "yarn";
|
|
11810
|
+
}
|
|
11811
|
+
if (existsSync22(join22(projectRoot, "pnpm-lock.yaml"))) {
|
|
11812
|
+
return "pnpm";
|
|
11813
|
+
}
|
|
11814
|
+
return "npm";
|
|
11815
|
+
}
|
|
11816
|
+
function getInstallCommand(pm) {
|
|
11817
|
+
switch (pm) {
|
|
11818
|
+
case "bun":
|
|
11819
|
+
return ["bun", "install"];
|
|
11820
|
+
case "yarn":
|
|
11821
|
+
return ["yarn", "install"];
|
|
11822
|
+
case "pnpm":
|
|
11823
|
+
return ["pnpm", "install"];
|
|
11824
|
+
case "npm":
|
|
11825
|
+
return ["npm", "install"];
|
|
11826
|
+
}
|
|
11827
|
+
}
|
|
11828
|
+
async function runSandboxSetup(sandboxName, projectRoot) {
|
|
11829
|
+
const pm = detectPackageManager(projectRoot);
|
|
11830
|
+
if (pm !== "npm") {
|
|
11831
|
+
await ensurePackageManagerInSandbox(sandboxName, pm);
|
|
11832
|
+
}
|
|
11833
|
+
const installCmd = getInstallCommand(pm);
|
|
11834
|
+
process.stderr.write(`
|
|
11835
|
+
Installing dependencies (${bold(installCmd.join(" "))}) in sandbox ${dim(sandboxName)}...
|
|
11836
|
+
`);
|
|
11837
|
+
const installOk = await runInteractiveCommand("docker", [
|
|
11838
|
+
"sandbox",
|
|
11839
|
+
"exec",
|
|
11840
|
+
"-w",
|
|
11841
|
+
projectRoot,
|
|
11842
|
+
sandboxName,
|
|
11843
|
+
...installCmd
|
|
11844
|
+
]);
|
|
11845
|
+
if (!installOk) {
|
|
11846
|
+
process.stderr.write(`${red("✗")} Dependency install failed in sandbox ${dim(sandboxName)}.
|
|
11847
|
+
`);
|
|
11848
|
+
return false;
|
|
11849
|
+
}
|
|
11850
|
+
process.stderr.write(`${green("✓")} Dependencies installed in sandbox ${dim(sandboxName)}.
|
|
11851
|
+
`);
|
|
11852
|
+
const setupScript = join22(projectRoot, ".locus", "sandbox-setup.sh");
|
|
11853
|
+
if (existsSync22(setupScript)) {
|
|
11854
|
+
process.stderr.write(`Running ${bold(".locus/sandbox-setup.sh")} in sandbox ${dim(sandboxName)}...
|
|
11855
|
+
`);
|
|
11856
|
+
const hookOk = await runInteractiveCommand("docker", [
|
|
11857
|
+
"sandbox",
|
|
11858
|
+
"exec",
|
|
11859
|
+
"-w",
|
|
11860
|
+
projectRoot,
|
|
11861
|
+
sandboxName,
|
|
11862
|
+
"sh",
|
|
11863
|
+
setupScript
|
|
11864
|
+
]);
|
|
11865
|
+
if (!hookOk) {
|
|
11866
|
+
process.stderr.write(`${yellow("⚠")} Setup hook failed in sandbox ${dim(sandboxName)}.
|
|
11867
|
+
`);
|
|
11868
|
+
}
|
|
11869
|
+
}
|
|
11870
|
+
return true;
|
|
11871
|
+
}
|
|
11872
|
+
async function handleSetup(projectRoot) {
|
|
11873
|
+
const config = loadConfig(projectRoot);
|
|
11874
|
+
const providers = config.sandbox.providers;
|
|
11875
|
+
if (!providers.claude && !providers.codex) {
|
|
11876
|
+
process.stderr.write(`${red("✗")} No sandboxes configured. Run ${cyan("locus sandbox")} first.
|
|
11877
|
+
`);
|
|
11878
|
+
return;
|
|
11879
|
+
}
|
|
11880
|
+
for (const provider of PROVIDERS) {
|
|
11881
|
+
const sandboxName = providers[provider];
|
|
11882
|
+
if (!sandboxName)
|
|
11883
|
+
continue;
|
|
11884
|
+
if (!isSandboxAlive(sandboxName)) {
|
|
11885
|
+
process.stderr.write(`${yellow("⚠")} ${provider} sandbox is not running: ${dim(sandboxName)}
|
|
11886
|
+
`);
|
|
11887
|
+
continue;
|
|
11888
|
+
}
|
|
11889
|
+
await runSandboxSetup(sandboxName, projectRoot);
|
|
11890
|
+
}
|
|
11891
|
+
}
|
|
11727
11892
|
function buildProviderSandboxNames(projectRoot) {
|
|
11728
11893
|
const segment = sanitizeSegment(basename4(projectRoot));
|
|
11729
11894
|
const hash = createHash("sha1").update(projectRoot).digest("hex").slice(0, 8);
|
|
@@ -11768,7 +11933,7 @@ function runInteractiveCommand(command, args) {
|
|
|
11768
11933
|
}
|
|
11769
11934
|
async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
11770
11935
|
try {
|
|
11771
|
-
|
|
11936
|
+
execSync16(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
|
|
11772
11937
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11773
11938
|
timeout: 120000
|
|
11774
11939
|
});
|
|
@@ -11782,9 +11947,30 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
|
11782
11947
|
await enforceSandboxIgnore(sandboxName, projectRoot);
|
|
11783
11948
|
return true;
|
|
11784
11949
|
}
|
|
11950
|
+
async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
11951
|
+
try {
|
|
11952
|
+
execSync16(`docker sandbox exec ${sandboxName} which ${pm}`, {
|
|
11953
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
11954
|
+
timeout: 5000
|
|
11955
|
+
});
|
|
11956
|
+
} catch {
|
|
11957
|
+
const npmPkg = pm === "bun" ? "bun" : pm === "yarn" ? "yarn" : "pnpm";
|
|
11958
|
+
process.stderr.write(`Installing ${bold(pm)} in sandbox...
|
|
11959
|
+
`);
|
|
11960
|
+
try {
|
|
11961
|
+
execSync16(`docker sandbox exec ${sandboxName} npm install -g ${npmPkg}`, {
|
|
11962
|
+
stdio: "inherit",
|
|
11963
|
+
timeout: 120000
|
|
11964
|
+
});
|
|
11965
|
+
} catch {
|
|
11966
|
+
process.stderr.write(`${yellow("⚠")} Failed to install ${pm} in sandbox. Dependency install may fail.
|
|
11967
|
+
`);
|
|
11968
|
+
}
|
|
11969
|
+
}
|
|
11970
|
+
}
|
|
11785
11971
|
async function ensureCodexInSandbox(sandboxName) {
|
|
11786
11972
|
try {
|
|
11787
|
-
|
|
11973
|
+
execSync16(`docker sandbox exec ${sandboxName} which codex`, {
|
|
11788
11974
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11789
11975
|
timeout: 5000
|
|
11790
11976
|
});
|
|
@@ -11792,7 +11978,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
11792
11978
|
process.stderr.write(`Installing codex in sandbox...
|
|
11793
11979
|
`);
|
|
11794
11980
|
try {
|
|
11795
|
-
|
|
11981
|
+
execSync16(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
|
|
11796
11982
|
} catch {
|
|
11797
11983
|
process.stderr.write(`${red("✗")} Failed to install codex in sandbox.
|
|
11798
11984
|
`);
|
|
@@ -11801,7 +11987,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
11801
11987
|
}
|
|
11802
11988
|
function isSandboxAlive(name) {
|
|
11803
11989
|
try {
|
|
11804
|
-
const output =
|
|
11990
|
+
const output = execSync16("docker sandbox ls", {
|
|
11805
11991
|
encoding: "utf-8",
|
|
11806
11992
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11807
11993
|
timeout: 5000
|
|
@@ -11826,17 +12012,17 @@ init_context();
|
|
|
11826
12012
|
init_logger();
|
|
11827
12013
|
init_rate_limiter();
|
|
11828
12014
|
init_terminal();
|
|
11829
|
-
import { existsSync as
|
|
11830
|
-
import { join as
|
|
12015
|
+
import { existsSync as existsSync23, readFileSync as readFileSync18 } from "node:fs";
|
|
12016
|
+
import { join as join23 } from "node:path";
|
|
11831
12017
|
import { fileURLToPath } from "node:url";
|
|
11832
12018
|
function getCliVersion() {
|
|
11833
12019
|
const fallbackVersion = "0.0.0";
|
|
11834
|
-
const packageJsonPath =
|
|
11835
|
-
if (!
|
|
12020
|
+
const packageJsonPath = join23(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
|
|
12021
|
+
if (!existsSync23(packageJsonPath)) {
|
|
11836
12022
|
return fallbackVersion;
|
|
11837
12023
|
}
|
|
11838
12024
|
try {
|
|
11839
|
-
const parsed = JSON.parse(
|
|
12025
|
+
const parsed = JSON.parse(readFileSync18(packageJsonPath, "utf-8"));
|
|
11840
12026
|
return parsed.version ?? fallbackVersion;
|
|
11841
12027
|
} catch {
|
|
11842
12028
|
return fallbackVersion;
|
|
@@ -11950,9 +12136,41 @@ function parseArgs(argv) {
|
|
|
11950
12136
|
const args = positional.slice(1);
|
|
11951
12137
|
return { command, args, flags };
|
|
11952
12138
|
}
|
|
12139
|
+
var LOCUS_LOGO2 = [
|
|
12140
|
+
" ▄█ ",
|
|
12141
|
+
"▄▄████▄▄▄▄ ",
|
|
12142
|
+
"████▀ ████ ",
|
|
12143
|
+
"████▄▄████ ",
|
|
12144
|
+
"▀█▄█▀███▀▀ ▄▄██▄▄ ",
|
|
12145
|
+
"████ ▄██████▀███▄ ",
|
|
12146
|
+
"████▄██▀ ▀▀███ ▄████ ",
|
|
12147
|
+
"▀▀██████▄ ██████▀ ",
|
|
12148
|
+
" ▀▀█████▄▄██▀▀ ",
|
|
12149
|
+
" ▀▀██▀▀ "
|
|
12150
|
+
];
|
|
12151
|
+
function printLogo() {
|
|
12152
|
+
process.stderr.write(`
|
|
12153
|
+
`);
|
|
12154
|
+
const logoWidth = 10;
|
|
12155
|
+
const gap = " ";
|
|
12156
|
+
const infoLines = [
|
|
12157
|
+
`${bold("Locus")} ${dim(`v${VERSION}`)}`,
|
|
12158
|
+
`${dim("GitHub-native AI engineering assistant")}`
|
|
12159
|
+
];
|
|
12160
|
+
const textOffset = 1;
|
|
12161
|
+
const totalLines = Math.max(LOCUS_LOGO2.length, infoLines.length + textOffset);
|
|
12162
|
+
for (let i = 0;i < totalLines; i++) {
|
|
12163
|
+
const logoLine = LOCUS_LOGO2[i] ?? "";
|
|
12164
|
+
const paddedLogo = logoLine.padEnd(logoWidth);
|
|
12165
|
+
const infoIdx = i - textOffset;
|
|
12166
|
+
const infoLine = infoIdx >= 0 && infoIdx < infoLines.length ? infoLines[infoIdx] : "";
|
|
12167
|
+
process.stderr.write(`${bold(white(paddedLogo))}${gap}${infoLine}
|
|
12168
|
+
`);
|
|
12169
|
+
}
|
|
12170
|
+
}
|
|
11953
12171
|
function printHelp6() {
|
|
12172
|
+
printLogo();
|
|
11954
12173
|
process.stderr.write(`
|
|
11955
|
-
${bold("Locus")} ${dim(`v${VERSION}`)} — GitHub-native AI engineering assistant
|
|
11956
12174
|
|
|
11957
12175
|
${bold("Usage:")}
|
|
11958
12176
|
locus <command> [options]
|
|
@@ -12065,7 +12283,7 @@ async function main() {
|
|
|
12065
12283
|
try {
|
|
12066
12284
|
const root = getGitRoot(cwd);
|
|
12067
12285
|
if (isInitialized(root)) {
|
|
12068
|
-
logDir =
|
|
12286
|
+
logDir = join23(root, ".locus", "logs");
|
|
12069
12287
|
getRateLimiter(root);
|
|
12070
12288
|
}
|
|
12071
12289
|
} catch {}
|