@locusai/cli 0.22.0 → 0.22.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 +656 -299
- package/package.json +2 -2
package/bin/locus.js
CHANGED
|
@@ -29,7 +29,7 @@ var init_dist = __esm(() => {
|
|
|
29
29
|
},
|
|
30
30
|
ai: {
|
|
31
31
|
provider: "claude",
|
|
32
|
-
model: "
|
|
32
|
+
model: "opus"
|
|
33
33
|
},
|
|
34
34
|
agent: {
|
|
35
35
|
maxParallel: 3,
|
|
@@ -2226,35 +2226,384 @@ var init_init = __esm(() => {
|
|
|
2226
2226
|
GITIGNORE_ENTRIES = ["", "# Locus", ".locus/", "!.locus/LEARNINGS.md"];
|
|
2227
2227
|
});
|
|
2228
2228
|
|
|
2229
|
+
// src/commands/create.ts
|
|
2230
|
+
var exports_create = {};
|
|
2231
|
+
__export(exports_create, {
|
|
2232
|
+
createCommand: () => createCommand
|
|
2233
|
+
});
|
|
2234
|
+
import { execSync as execSync4 } from "node:child_process";
|
|
2235
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2236
|
+
import { join as join7 } from "node:path";
|
|
2237
|
+
function validateName(name) {
|
|
2238
|
+
if (!name)
|
|
2239
|
+
return "Package name is required.";
|
|
2240
|
+
if (!NAME_PATTERN.test(name))
|
|
2241
|
+
return "Name must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.";
|
|
2242
|
+
if (name.startsWith("locus-"))
|
|
2243
|
+
return `Don't include the "locus-" prefix — it's added automatically.`;
|
|
2244
|
+
if (name.length > 50)
|
|
2245
|
+
return "Name must be 50 characters or fewer.";
|
|
2246
|
+
return null;
|
|
2247
|
+
}
|
|
2248
|
+
function capitalize(str) {
|
|
2249
|
+
return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2250
|
+
}
|
|
2251
|
+
function checkNpmExists(fullName) {
|
|
2252
|
+
try {
|
|
2253
|
+
execSync4(`npm view ${fullName} version 2>/dev/null`, {
|
|
2254
|
+
stdio: "pipe",
|
|
2255
|
+
timeout: 1e4
|
|
2256
|
+
});
|
|
2257
|
+
return true;
|
|
2258
|
+
} catch {
|
|
2259
|
+
return false;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
function parseCreateArgs(args) {
|
|
2263
|
+
let name = "";
|
|
2264
|
+
let description = "";
|
|
2265
|
+
let i = 0;
|
|
2266
|
+
while (i < args.length) {
|
|
2267
|
+
const arg = args[i];
|
|
2268
|
+
if (arg === "--description" || arg === "-D") {
|
|
2269
|
+
description = args[++i] ?? "";
|
|
2270
|
+
} else if (arg === "help" || arg === "--help" || arg === "-h") {
|
|
2271
|
+
return null;
|
|
2272
|
+
} else if (!arg.startsWith("-")) {
|
|
2273
|
+
name = arg;
|
|
2274
|
+
}
|
|
2275
|
+
i++;
|
|
2276
|
+
}
|
|
2277
|
+
return { name, description };
|
|
2278
|
+
}
|
|
2279
|
+
function generatePackageJson(name, displayName, description, sdkVersion) {
|
|
2280
|
+
const pkg = {
|
|
2281
|
+
name: `@locusai/locus-${name}`,
|
|
2282
|
+
version: "0.1.0",
|
|
2283
|
+
description,
|
|
2284
|
+
type: "module",
|
|
2285
|
+
bin: {
|
|
2286
|
+
[`locus-${name}`]: `./bin/locus-${name}.js`
|
|
2287
|
+
},
|
|
2288
|
+
files: ["bin", "package.json", "README.md"],
|
|
2289
|
+
locus: {
|
|
2290
|
+
displayName,
|
|
2291
|
+
description,
|
|
2292
|
+
commands: [name],
|
|
2293
|
+
version: "0.1.0"
|
|
2294
|
+
},
|
|
2295
|
+
scripts: {
|
|
2296
|
+
build: `bun build src/cli.ts --outfile bin/locus-${name}.js --target node`,
|
|
2297
|
+
typecheck: "tsc --noEmit",
|
|
2298
|
+
lint: "biome lint .",
|
|
2299
|
+
format: "biome format --write ."
|
|
2300
|
+
},
|
|
2301
|
+
dependencies: {
|
|
2302
|
+
"@locusai/sdk": `^${sdkVersion}`
|
|
2303
|
+
},
|
|
2304
|
+
devDependencies: {
|
|
2305
|
+
typescript: "^5.8.3"
|
|
2306
|
+
},
|
|
2307
|
+
keywords: ["locusai-package", "locus", name],
|
|
2308
|
+
engines: {
|
|
2309
|
+
node: ">=18"
|
|
2310
|
+
},
|
|
2311
|
+
license: "MIT"
|
|
2312
|
+
};
|
|
2313
|
+
return `${JSON.stringify(pkg, null, 2)}
|
|
2314
|
+
`;
|
|
2315
|
+
}
|
|
2316
|
+
function generateTsconfig() {
|
|
2317
|
+
const config = {
|
|
2318
|
+
compilerOptions: {
|
|
2319
|
+
target: "ES2022",
|
|
2320
|
+
module: "ESNext",
|
|
2321
|
+
moduleResolution: "bundler",
|
|
2322
|
+
strict: true,
|
|
2323
|
+
skipLibCheck: true,
|
|
2324
|
+
esModuleInterop: true,
|
|
2325
|
+
isolatedModules: true,
|
|
2326
|
+
resolveJsonModule: true,
|
|
2327
|
+
noEmit: true,
|
|
2328
|
+
rootDir: "./src"
|
|
2329
|
+
},
|
|
2330
|
+
include: ["src/**/*"],
|
|
2331
|
+
exclude: ["node_modules", "dist", "bin"]
|
|
2332
|
+
};
|
|
2333
|
+
return `${JSON.stringify(config, null, 2)}
|
|
2334
|
+
`;
|
|
2335
|
+
}
|
|
2336
|
+
function generateCliTs() {
|
|
2337
|
+
return `#!/usr/bin/env node
|
|
2338
|
+
|
|
2339
|
+
import { main } from "./index.js";
|
|
2340
|
+
|
|
2341
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
2342
|
+
console.error(\`Fatal error: \${error.message}\`);
|
|
2343
|
+
process.exit(1);
|
|
2344
|
+
});
|
|
2345
|
+
`;
|
|
2346
|
+
}
|
|
2347
|
+
function generateIndexTs(name) {
|
|
2348
|
+
return `import { createLogger, readLocusConfig } from "@locusai/sdk";
|
|
2349
|
+
|
|
2350
|
+
const logger = createLogger("${name}");
|
|
2351
|
+
|
|
2352
|
+
export async function main(args: string[]): Promise<void> {
|
|
2353
|
+
const command = args[0] ?? "help";
|
|
2354
|
+
|
|
2355
|
+
switch (command) {
|
|
2356
|
+
case "start":
|
|
2357
|
+
return handleStart();
|
|
2358
|
+
case "help":
|
|
2359
|
+
case "--help":
|
|
2360
|
+
case "-h":
|
|
2361
|
+
return printHelp();
|
|
2362
|
+
default:
|
|
2363
|
+
console.error(\`Unknown command: \${command}\`);
|
|
2364
|
+
printHelp();
|
|
2365
|
+
process.exit(1);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// ─── Commands ────────────────────────────────────────────────────────────────
|
|
2370
|
+
|
|
2371
|
+
function handleStart(): void {
|
|
2372
|
+
const config = readLocusConfig();
|
|
2373
|
+
logger.info(\`Hello from locus-${name}! Repo: \${config.github.owner}/\${config.github.repo}\`);
|
|
2374
|
+
// TODO: Implement your package logic here
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
// ─── Help ────────────────────────────────────────────────────────────────────
|
|
2378
|
+
|
|
2379
|
+
function printHelp(): void {
|
|
2380
|
+
console.log(\`
|
|
2381
|
+
locus-${name}
|
|
2382
|
+
|
|
2383
|
+
Usage:
|
|
2384
|
+
locus pkg ${name} <command>
|
|
2385
|
+
|
|
2386
|
+
Commands:
|
|
2387
|
+
start Start the ${name} integration
|
|
2388
|
+
help Show this help message
|
|
2389
|
+
\`);
|
|
2390
|
+
}
|
|
2391
|
+
`;
|
|
2392
|
+
}
|
|
2393
|
+
function generateReadme(name, displayName, description) {
|
|
2394
|
+
return `# @locusai/locus-${name}
|
|
2395
|
+
|
|
2396
|
+
${description}
|
|
2397
|
+
|
|
2398
|
+
## Installation
|
|
2399
|
+
|
|
2400
|
+
\`\`\`bash
|
|
2401
|
+
locus install ${name}
|
|
2402
|
+
\`\`\`
|
|
2403
|
+
|
|
2404
|
+
## Usage
|
|
2405
|
+
|
|
2406
|
+
\`\`\`bash
|
|
2407
|
+
locus pkg ${name} start # Start the integration
|
|
2408
|
+
locus pkg ${name} help # Show help
|
|
2409
|
+
\`\`\`
|
|
2410
|
+
|
|
2411
|
+
## Configuration
|
|
2412
|
+
|
|
2413
|
+
Configure via \`locus config\`:
|
|
2414
|
+
|
|
2415
|
+
\`\`\`bash
|
|
2416
|
+
locus config set packages.${name}.apiKey "your-api-key"
|
|
2417
|
+
\`\`\`
|
|
2418
|
+
|
|
2419
|
+
## Development
|
|
2420
|
+
|
|
2421
|
+
\`\`\`bash
|
|
2422
|
+
# Build
|
|
2423
|
+
bun run build
|
|
2424
|
+
|
|
2425
|
+
# Type check
|
|
2426
|
+
bun run typecheck
|
|
2427
|
+
|
|
2428
|
+
# Lint
|
|
2429
|
+
bun run lint
|
|
2430
|
+
|
|
2431
|
+
# Test locally
|
|
2432
|
+
locus pkg ${name}
|
|
2433
|
+
\`\`\`
|
|
2434
|
+
|
|
2435
|
+
## License
|
|
2436
|
+
|
|
2437
|
+
MIT
|
|
2438
|
+
`;
|
|
2439
|
+
}
|
|
2440
|
+
function printHelp() {
|
|
2441
|
+
process.stderr.write(`
|
|
2442
|
+
${bold2("locus create")} — Scaffold a new Locus community package
|
|
2443
|
+
|
|
2444
|
+
${bold2("Usage:")}
|
|
2445
|
+
locus create <name> [options]
|
|
2446
|
+
|
|
2447
|
+
${bold2("Arguments:")}
|
|
2448
|
+
${cyan2("<name>")} Package short name (e.g. slack, discord, jira)
|
|
2449
|
+
|
|
2450
|
+
${bold2("Options:")}
|
|
2451
|
+
${dim2("--description, -D")} Package description (default: auto-generated)
|
|
2452
|
+
${dim2("--help, -h")} Show this help
|
|
2453
|
+
|
|
2454
|
+
${bold2("Examples:")}
|
|
2455
|
+
locus create slack ${dim2("# Create packages/slack/")}
|
|
2456
|
+
locus create discord -D "Control Locus via Discord" ${dim2("# With custom description")}
|
|
2457
|
+
|
|
2458
|
+
${bold2("What gets created:")}
|
|
2459
|
+
packages/<name>/
|
|
2460
|
+
├── src/
|
|
2461
|
+
│ ├── cli.ts ${dim2("# Entry point")}
|
|
2462
|
+
│ └── index.ts ${dim2("# Main logic with command dispatch")}
|
|
2463
|
+
├── package.json ${dim2("# Full config with locus manifest")}
|
|
2464
|
+
├── tsconfig.json ${dim2("# TypeScript configuration")}
|
|
2465
|
+
└── README.md ${dim2("# Package documentation")}
|
|
2466
|
+
|
|
2467
|
+
${bold2("Next steps after creation:")}
|
|
2468
|
+
${gray2("1.")} cd packages/<name>
|
|
2469
|
+
${gray2("2.")} Implement your logic in src/index.ts
|
|
2470
|
+
${gray2("3.")} bun install && bun run build
|
|
2471
|
+
${gray2("4.")} Test locally with: locus pkg <name>
|
|
2472
|
+
${gray2("5.")} Submit a pull request
|
|
2473
|
+
|
|
2474
|
+
`);
|
|
2475
|
+
}
|
|
2476
|
+
async function createCommand(args) {
|
|
2477
|
+
const parsed = parseCreateArgs(args);
|
|
2478
|
+
if (!parsed || !parsed.name) {
|
|
2479
|
+
printHelp();
|
|
2480
|
+
if (parsed && !parsed.name) {
|
|
2481
|
+
process.stderr.write(`${red2("✗")} Package name is required.
|
|
2482
|
+
|
|
2483
|
+
`);
|
|
2484
|
+
process.stderr.write(` Usage: ${bold2("locus create <name>")}
|
|
2485
|
+
|
|
2486
|
+
`);
|
|
2487
|
+
process.exit(1);
|
|
2488
|
+
}
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
const { name } = parsed;
|
|
2492
|
+
const displayName = capitalize(name);
|
|
2493
|
+
const description = parsed.description || `${displayName} integration for Locus`;
|
|
2494
|
+
const fullNpmName = `@locusai/locus-${name}`;
|
|
2495
|
+
process.stderr.write(`
|
|
2496
|
+
${bold2("Creating package:")} ${cyan2(fullNpmName)}
|
|
2497
|
+
|
|
2498
|
+
`);
|
|
2499
|
+
const nameError = validateName(name);
|
|
2500
|
+
if (nameError) {
|
|
2501
|
+
process.stderr.write(`${red2("✗")} ${nameError}
|
|
2502
|
+
`);
|
|
2503
|
+
process.exit(1);
|
|
2504
|
+
}
|
|
2505
|
+
process.stderr.write(`${green("✓")} Name is valid: ${bold2(name)}
|
|
2506
|
+
`);
|
|
2507
|
+
const packagesDir = join7(process.cwd(), "packages", name);
|
|
2508
|
+
if (existsSync7(packagesDir)) {
|
|
2509
|
+
process.stderr.write(`${red2("✗")} Directory already exists: ${bold2(`packages/${name}/`)}
|
|
2510
|
+
`);
|
|
2511
|
+
process.exit(1);
|
|
2512
|
+
}
|
|
2513
|
+
process.stderr.write(`${cyan2("●")} Checking npm registry...`);
|
|
2514
|
+
const existsOnNpm = checkNpmExists(fullNpmName);
|
|
2515
|
+
if (existsOnNpm) {
|
|
2516
|
+
process.stderr.write(`\r${yellow2("⚠")} Package ${bold2(fullNpmName)} already exists on npm. You may want to choose a different name.
|
|
2517
|
+
`);
|
|
2518
|
+
} else {
|
|
2519
|
+
process.stderr.write(`\r${green("✓")} Name is available on npm
|
|
2520
|
+
`);
|
|
2521
|
+
}
|
|
2522
|
+
let sdkVersion = "0.22.0";
|
|
2523
|
+
try {
|
|
2524
|
+
const sdkPkgPath = join7(process.cwd(), "packages", "sdk", "package.json");
|
|
2525
|
+
if (existsSync7(sdkPkgPath)) {
|
|
2526
|
+
const { readFileSync: readFileSync5 } = await import("node:fs");
|
|
2527
|
+
const sdkPkg = JSON.parse(readFileSync5(sdkPkgPath, "utf-8"));
|
|
2528
|
+
if (sdkPkg.version)
|
|
2529
|
+
sdkVersion = sdkPkg.version;
|
|
2530
|
+
}
|
|
2531
|
+
} catch {}
|
|
2532
|
+
mkdirSync6(join7(packagesDir, "src"), { recursive: true });
|
|
2533
|
+
mkdirSync6(join7(packagesDir, "bin"), { recursive: true });
|
|
2534
|
+
process.stderr.write(`${green("✓")} Created directory structure
|
|
2535
|
+
`);
|
|
2536
|
+
writeFileSync5(join7(packagesDir, "package.json"), generatePackageJson(name, displayName, description, sdkVersion), "utf-8");
|
|
2537
|
+
process.stderr.write(`${green("✓")} Generated package.json
|
|
2538
|
+
`);
|
|
2539
|
+
writeFileSync5(join7(packagesDir, "tsconfig.json"), generateTsconfig(), "utf-8");
|
|
2540
|
+
process.stderr.write(`${green("✓")} Generated tsconfig.json
|
|
2541
|
+
`);
|
|
2542
|
+
writeFileSync5(join7(packagesDir, "src", "cli.ts"), generateCliTs(), "utf-8");
|
|
2543
|
+
process.stderr.write(`${green("✓")} Generated src/cli.ts
|
|
2544
|
+
`);
|
|
2545
|
+
writeFileSync5(join7(packagesDir, "src", "index.ts"), generateIndexTs(name), "utf-8");
|
|
2546
|
+
process.stderr.write(`${green("✓")} Generated src/index.ts
|
|
2547
|
+
`);
|
|
2548
|
+
writeFileSync5(join7(packagesDir, "README.md"), generateReadme(name, displayName, description), "utf-8");
|
|
2549
|
+
process.stderr.write(`${green("✓")} Generated README.md
|
|
2550
|
+
`);
|
|
2551
|
+
process.stderr.write(`
|
|
2552
|
+
${bold2(green("Package created!"))}
|
|
2553
|
+
|
|
2554
|
+
`);
|
|
2555
|
+
process.stderr.write(`${bold2("Next steps:")}
|
|
2556
|
+
`);
|
|
2557
|
+
process.stderr.write(` ${gray2("1.")} Implement your logic in ${bold2(`packages/${name}/src/index.ts`)}
|
|
2558
|
+
`);
|
|
2559
|
+
process.stderr.write(` ${gray2("2.")} Install dependencies: ${bold2("bun install")}
|
|
2560
|
+
`);
|
|
2561
|
+
process.stderr.write(` ${gray2("3.")} Build your package: ${bold2(`cd packages/${name} && bun run build`)}
|
|
2562
|
+
`);
|
|
2563
|
+
process.stderr.write(` ${gray2("4.")} Test locally: ${bold2(`locus pkg ${name}`)}
|
|
2564
|
+
`);
|
|
2565
|
+
process.stderr.write(` ${gray2("5.")} Submit a pull request
|
|
2566
|
+
`);
|
|
2567
|
+
process.stderr.write(`
|
|
2568
|
+
${dim2("See the full guide:")} ${cyan2("packages/sdk/PACKAGE_GUIDE.md")}
|
|
2569
|
+
|
|
2570
|
+
`);
|
|
2571
|
+
}
|
|
2572
|
+
var NAME_PATTERN;
|
|
2573
|
+
var init_create = __esm(() => {
|
|
2574
|
+
init_terminal();
|
|
2575
|
+
NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
2576
|
+
});
|
|
2577
|
+
|
|
2229
2578
|
// src/packages/registry.ts
|
|
2230
2579
|
import {
|
|
2231
|
-
existsSync as
|
|
2232
|
-
mkdirSync as
|
|
2580
|
+
existsSync as existsSync8,
|
|
2581
|
+
mkdirSync as mkdirSync7,
|
|
2233
2582
|
readFileSync as readFileSync5,
|
|
2234
2583
|
renameSync,
|
|
2235
|
-
writeFileSync as
|
|
2584
|
+
writeFileSync as writeFileSync6
|
|
2236
2585
|
} from "node:fs";
|
|
2237
2586
|
import { homedir as homedir2 } from "node:os";
|
|
2238
|
-
import { join as
|
|
2587
|
+
import { join as join8 } from "node:path";
|
|
2239
2588
|
function getPackagesDir() {
|
|
2240
2589
|
const home = process.env.HOME || homedir2();
|
|
2241
|
-
const dir =
|
|
2242
|
-
if (!
|
|
2243
|
-
|
|
2590
|
+
const dir = join8(home, ".locus", "packages");
|
|
2591
|
+
if (!existsSync8(dir)) {
|
|
2592
|
+
mkdirSync7(dir, { recursive: true });
|
|
2244
2593
|
}
|
|
2245
|
-
const pkgJson =
|
|
2246
|
-
if (!
|
|
2247
|
-
|
|
2594
|
+
const pkgJson = join8(dir, "package.json");
|
|
2595
|
+
if (!existsSync8(pkgJson)) {
|
|
2596
|
+
writeFileSync6(pkgJson, `${JSON.stringify({ private: true }, null, 2)}
|
|
2248
2597
|
`, "utf-8");
|
|
2249
2598
|
}
|
|
2250
2599
|
return dir;
|
|
2251
2600
|
}
|
|
2252
2601
|
function getRegistryPath() {
|
|
2253
|
-
return
|
|
2602
|
+
return join8(getPackagesDir(), "registry.json");
|
|
2254
2603
|
}
|
|
2255
2604
|
function loadRegistry() {
|
|
2256
2605
|
const registryPath = getRegistryPath();
|
|
2257
|
-
if (!
|
|
2606
|
+
if (!existsSync8(registryPath)) {
|
|
2258
2607
|
return { packages: {} };
|
|
2259
2608
|
}
|
|
2260
2609
|
try {
|
|
@@ -2271,7 +2620,7 @@ function loadRegistry() {
|
|
|
2271
2620
|
}
|
|
2272
2621
|
if (pruned) {
|
|
2273
2622
|
const tmp = `${registryPath}.tmp`;
|
|
2274
|
-
|
|
2623
|
+
writeFileSync6(tmp, `${JSON.stringify(registry, null, 2)}
|
|
2275
2624
|
`, "utf-8");
|
|
2276
2625
|
renameSync(tmp, registryPath);
|
|
2277
2626
|
}
|
|
@@ -2285,15 +2634,15 @@ function loadRegistry() {
|
|
|
2285
2634
|
function saveRegistry(registry) {
|
|
2286
2635
|
const registryPath = getRegistryPath();
|
|
2287
2636
|
const tmp = `${registryPath}.tmp`;
|
|
2288
|
-
|
|
2637
|
+
writeFileSync6(tmp, `${JSON.stringify(registry, null, 2)}
|
|
2289
2638
|
`, "utf-8");
|
|
2290
2639
|
renameSync(tmp, registryPath);
|
|
2291
2640
|
}
|
|
2292
2641
|
function resolvePackageBinary(packageName) {
|
|
2293
2642
|
const fullName = normalizePackageName(packageName);
|
|
2294
2643
|
const binName = fullName.includes("/") ? fullName.split("/").pop() : fullName;
|
|
2295
|
-
const binPath =
|
|
2296
|
-
return
|
|
2644
|
+
const binPath = join8(getPackagesDir(), "node_modules", ".bin", binName);
|
|
2645
|
+
return existsSync8(binPath) ? binPath : null;
|
|
2297
2646
|
}
|
|
2298
2647
|
function normalizePackageName(input) {
|
|
2299
2648
|
if (input.startsWith(SCOPED_PREFIX)) {
|
|
@@ -2319,7 +2668,7 @@ __export(exports_pkg, {
|
|
|
2319
2668
|
listInstalledPackages: () => listInstalledPackages
|
|
2320
2669
|
});
|
|
2321
2670
|
import { spawn } from "node:child_process";
|
|
2322
|
-
import { existsSync as
|
|
2671
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
2323
2672
|
function listInstalledPackages() {
|
|
2324
2673
|
const registry = loadRegistry();
|
|
2325
2674
|
const entries = Object.values(registry.packages);
|
|
@@ -2398,7 +2747,7 @@ async function pkgCommand(args, _flags) {
|
|
|
2398
2747
|
return;
|
|
2399
2748
|
}
|
|
2400
2749
|
let binaryPath = entry.binaryPath;
|
|
2401
|
-
if (!binaryPath || !
|
|
2750
|
+
if (!binaryPath || !existsSync9(binaryPath)) {
|
|
2402
2751
|
const resolved = resolvePackageBinary(packageName);
|
|
2403
2752
|
if (resolved) {
|
|
2404
2753
|
binaryPath = resolved;
|
|
@@ -2531,8 +2880,8 @@ __export(exports_install, {
|
|
|
2531
2880
|
installCommand: () => installCommand
|
|
2532
2881
|
});
|
|
2533
2882
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2534
|
-
import { existsSync as
|
|
2535
|
-
import { join as
|
|
2883
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
|
|
2884
|
+
import { join as join9 } from "node:path";
|
|
2536
2885
|
function parsePackageArg(raw) {
|
|
2537
2886
|
if (raw.startsWith("@")) {
|
|
2538
2887
|
const slashIdx = raw.indexOf("/");
|
|
@@ -2609,8 +2958,8 @@ ${red2("✗")} Failed to install ${bold2(packageSpec)}.
|
|
|
2609
2958
|
process.exit(1);
|
|
2610
2959
|
return;
|
|
2611
2960
|
}
|
|
2612
|
-
const installedPkgJsonPath =
|
|
2613
|
-
if (!
|
|
2961
|
+
const installedPkgJsonPath = join9(packagesDir, "node_modules", packageName, "package.json");
|
|
2962
|
+
if (!existsSync10(installedPkgJsonPath)) {
|
|
2614
2963
|
process.stderr.write(`
|
|
2615
2964
|
${red2("✗")} Package installed but package.json not found at:
|
|
2616
2965
|
`);
|
|
@@ -2888,16 +3237,16 @@ __export(exports_logs, {
|
|
|
2888
3237
|
logsCommand: () => logsCommand
|
|
2889
3238
|
});
|
|
2890
3239
|
import {
|
|
2891
|
-
existsSync as
|
|
3240
|
+
existsSync as existsSync11,
|
|
2892
3241
|
readdirSync as readdirSync2,
|
|
2893
3242
|
readFileSync as readFileSync7,
|
|
2894
3243
|
statSync as statSync2,
|
|
2895
3244
|
unlinkSync as unlinkSync2
|
|
2896
3245
|
} from "node:fs";
|
|
2897
|
-
import { join as
|
|
3246
|
+
import { join as join10 } from "node:path";
|
|
2898
3247
|
async function logsCommand(cwd, options) {
|
|
2899
|
-
const logsDir =
|
|
2900
|
-
if (!
|
|
3248
|
+
const logsDir = join10(cwd, ".locus", "logs");
|
|
3249
|
+
if (!existsSync11(logsDir)) {
|
|
2901
3250
|
process.stderr.write(`${dim2("No logs found.")}
|
|
2902
3251
|
`);
|
|
2903
3252
|
return;
|
|
@@ -2952,8 +3301,8 @@ async function tailLog(logFile, levelFilter) {
|
|
|
2952
3301
|
process.stderr.write(`${bold2("Tailing:")} ${dim2(logFile)} ${dim2("(Ctrl+C to stop)")}
|
|
2953
3302
|
|
|
2954
3303
|
`);
|
|
2955
|
-
let lastSize =
|
|
2956
|
-
if (
|
|
3304
|
+
let lastSize = existsSync11(logFile) ? statSync2(logFile).size : 0;
|
|
3305
|
+
if (existsSync11(logFile)) {
|
|
2957
3306
|
const content = readFileSync7(logFile, "utf-8");
|
|
2958
3307
|
const lines = content.trim().split(`
|
|
2959
3308
|
`).filter(Boolean);
|
|
@@ -2972,7 +3321,7 @@ async function tailLog(logFile, levelFilter) {
|
|
|
2972
3321
|
}
|
|
2973
3322
|
return new Promise((resolve) => {
|
|
2974
3323
|
const interval = setInterval(() => {
|
|
2975
|
-
if (!
|
|
3324
|
+
if (!existsSync11(logFile))
|
|
2976
3325
|
return;
|
|
2977
3326
|
const currentSize = statSync2(logFile).size;
|
|
2978
3327
|
if (currentSize <= lastSize)
|
|
@@ -3032,7 +3381,7 @@ function cleanLogs(logsDir) {
|
|
|
3032
3381
|
`);
|
|
3033
3382
|
}
|
|
3034
3383
|
function getLogFiles(logsDir) {
|
|
3035
|
-
return readdirSync2(logsDir).filter((f) => f.startsWith("locus-") && f.endsWith(".log")).map((f) =>
|
|
3384
|
+
return readdirSync2(logsDir).filter((f) => f.startsWith("locus-") && f.endsWith(".log")).map((f) => join10(logsDir, f)).sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs);
|
|
3036
3385
|
}
|
|
3037
3386
|
function formatEntry(entry) {
|
|
3038
3387
|
const time = dim2(new Date(entry.ts).toLocaleTimeString());
|
|
@@ -3341,10 +3690,10 @@ var init_stream_renderer = __esm(() => {
|
|
|
3341
3690
|
});
|
|
3342
3691
|
|
|
3343
3692
|
// src/repl/clipboard.ts
|
|
3344
|
-
import { execSync as
|
|
3345
|
-
import { existsSync as
|
|
3693
|
+
import { execSync as execSync5 } from "node:child_process";
|
|
3694
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8 } from "node:fs";
|
|
3346
3695
|
import { tmpdir } from "node:os";
|
|
3347
|
-
import { join as
|
|
3696
|
+
import { join as join11 } from "node:path";
|
|
3348
3697
|
function readClipboardImage() {
|
|
3349
3698
|
if (process.platform === "darwin") {
|
|
3350
3699
|
return readMacOSClipboardImage();
|
|
@@ -3355,14 +3704,14 @@ function readClipboardImage() {
|
|
|
3355
3704
|
return null;
|
|
3356
3705
|
}
|
|
3357
3706
|
function ensureStableDir() {
|
|
3358
|
-
if (!
|
|
3359
|
-
|
|
3707
|
+
if (!existsSync12(STABLE_DIR)) {
|
|
3708
|
+
mkdirSync8(STABLE_DIR, { recursive: true });
|
|
3360
3709
|
}
|
|
3361
3710
|
}
|
|
3362
3711
|
function readMacOSClipboardImage() {
|
|
3363
3712
|
try {
|
|
3364
3713
|
ensureStableDir();
|
|
3365
|
-
const destPath =
|
|
3714
|
+
const destPath = join11(STABLE_DIR, `clipboard-${Date.now()}.png`);
|
|
3366
3715
|
const script = [
|
|
3367
3716
|
`set destPath to POSIX file "${destPath}"`,
|
|
3368
3717
|
"try",
|
|
@@ -3380,13 +3729,13 @@ function readMacOSClipboardImage() {
|
|
|
3380
3729
|
`return "ok"`
|
|
3381
3730
|
].join(`
|
|
3382
3731
|
`);
|
|
3383
|
-
const result =
|
|
3732
|
+
const result = execSync5("osascript", {
|
|
3384
3733
|
input: script,
|
|
3385
3734
|
encoding: "utf-8",
|
|
3386
3735
|
timeout: 5000,
|
|
3387
3736
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3388
3737
|
}).trim();
|
|
3389
|
-
if (result === "ok" &&
|
|
3738
|
+
if (result === "ok" && existsSync12(destPath)) {
|
|
3390
3739
|
return destPath;
|
|
3391
3740
|
}
|
|
3392
3741
|
} catch {}
|
|
@@ -3394,14 +3743,14 @@ function readMacOSClipboardImage() {
|
|
|
3394
3743
|
}
|
|
3395
3744
|
function readLinuxClipboardImage() {
|
|
3396
3745
|
try {
|
|
3397
|
-
const targets =
|
|
3746
|
+
const targets = execSync5("xclip -selection clipboard -t TARGETS -o 2>/dev/null", { encoding: "utf-8", timeout: 3000 });
|
|
3398
3747
|
if (!targets.includes("image/png")) {
|
|
3399
3748
|
return null;
|
|
3400
3749
|
}
|
|
3401
3750
|
ensureStableDir();
|
|
3402
|
-
const destPath =
|
|
3403
|
-
|
|
3404
|
-
if (
|
|
3751
|
+
const destPath = join11(STABLE_DIR, `clipboard-${Date.now()}.png`);
|
|
3752
|
+
execSync5(`xclip -selection clipboard -t image/png -o > "${destPath}" 2>/dev/null`, { timeout: 5000 });
|
|
3753
|
+
if (existsSync12(destPath)) {
|
|
3405
3754
|
return destPath;
|
|
3406
3755
|
}
|
|
3407
3756
|
} catch {}
|
|
@@ -3409,13 +3758,13 @@ function readLinuxClipboardImage() {
|
|
|
3409
3758
|
}
|
|
3410
3759
|
var STABLE_DIR;
|
|
3411
3760
|
var init_clipboard = __esm(() => {
|
|
3412
|
-
STABLE_DIR =
|
|
3761
|
+
STABLE_DIR = join11(tmpdir(), "locus-images");
|
|
3413
3762
|
});
|
|
3414
3763
|
|
|
3415
3764
|
// src/repl/image-detect.ts
|
|
3416
|
-
import { copyFileSync, existsSync as
|
|
3765
|
+
import { copyFileSync, existsSync as existsSync13, mkdirSync as mkdirSync9 } from "node:fs";
|
|
3417
3766
|
import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
|
|
3418
|
-
import { basename, extname, join as
|
|
3767
|
+
import { basename, extname, join as join12, resolve } from "node:path";
|
|
3419
3768
|
function detectImages(input) {
|
|
3420
3769
|
const detected = [];
|
|
3421
3770
|
const byResolved = new Map;
|
|
@@ -3509,15 +3858,15 @@ function collectReferencedAttachments(input, attachments) {
|
|
|
3509
3858
|
return dedupeByResolvedPath(selected);
|
|
3510
3859
|
}
|
|
3511
3860
|
function relocateImages(images, projectRoot) {
|
|
3512
|
-
const targetDir =
|
|
3861
|
+
const targetDir = join12(projectRoot, ".locus", "tmp", "images");
|
|
3513
3862
|
for (const img of images) {
|
|
3514
3863
|
if (!img.exists)
|
|
3515
3864
|
continue;
|
|
3516
3865
|
try {
|
|
3517
|
-
if (!
|
|
3518
|
-
|
|
3866
|
+
if (!existsSync13(targetDir)) {
|
|
3867
|
+
mkdirSync9(targetDir, { recursive: true });
|
|
3519
3868
|
}
|
|
3520
|
-
const dest =
|
|
3869
|
+
const dest = join12(targetDir, basename(img.stablePath));
|
|
3521
3870
|
copyFileSync(img.stablePath, dest);
|
|
3522
3871
|
img.stablePath = dest;
|
|
3523
3872
|
} catch {}
|
|
@@ -3529,7 +3878,7 @@ function addIfImage(rawPath, rawMatch, detected, byResolved) {
|
|
|
3529
3878
|
return;
|
|
3530
3879
|
let resolved = stripQuotes(rawPath).replace(/\\ /g, " ");
|
|
3531
3880
|
if (resolved.startsWith("~/")) {
|
|
3532
|
-
resolved =
|
|
3881
|
+
resolved = join12(homedir3(), resolved.slice(2));
|
|
3533
3882
|
}
|
|
3534
3883
|
resolved = resolve(resolved);
|
|
3535
3884
|
const existing = byResolved.get(resolved);
|
|
@@ -3542,7 +3891,7 @@ function addIfImage(rawPath, rawMatch, detected, byResolved) {
|
|
|
3542
3891
|
]);
|
|
3543
3892
|
return;
|
|
3544
3893
|
}
|
|
3545
|
-
const exists =
|
|
3894
|
+
const exists = existsSync13(resolved);
|
|
3546
3895
|
let stablePath = resolved;
|
|
3547
3896
|
if (exists) {
|
|
3548
3897
|
stablePath = copyToStable(resolved);
|
|
@@ -3596,10 +3945,10 @@ function dedupeByResolvedPath(images) {
|
|
|
3596
3945
|
}
|
|
3597
3946
|
function copyToStable(sourcePath) {
|
|
3598
3947
|
try {
|
|
3599
|
-
if (!
|
|
3600
|
-
|
|
3948
|
+
if (!existsSync13(STABLE_DIR2)) {
|
|
3949
|
+
mkdirSync9(STABLE_DIR2, { recursive: true });
|
|
3601
3950
|
}
|
|
3602
|
-
const dest =
|
|
3951
|
+
const dest = join12(STABLE_DIR2, `${Date.now()}-${basename(sourcePath)}`);
|
|
3603
3952
|
copyFileSync(sourcePath, dest);
|
|
3604
3953
|
return dest;
|
|
3605
3954
|
} catch {
|
|
@@ -3619,7 +3968,7 @@ var init_image_detect = __esm(() => {
|
|
|
3619
3968
|
".tif",
|
|
3620
3969
|
".tiff"
|
|
3621
3970
|
]);
|
|
3622
|
-
STABLE_DIR2 =
|
|
3971
|
+
STABLE_DIR2 = join12(tmpdir2(), "locus-images");
|
|
3623
3972
|
PLACEHOLDER_ID_PATTERN = /\(locus:\/\/screenshot-(\d+)\)/g;
|
|
3624
3973
|
});
|
|
3625
3974
|
|
|
@@ -4421,7 +4770,7 @@ __export(exports_claude, {
|
|
|
4421
4770
|
buildClaudeArgs: () => buildClaudeArgs,
|
|
4422
4771
|
ClaudeRunner: () => ClaudeRunner
|
|
4423
4772
|
});
|
|
4424
|
-
import { execSync as
|
|
4773
|
+
import { execSync as execSync6, spawn as spawn2 } from "node:child_process";
|
|
4425
4774
|
function buildClaudeArgs(options) {
|
|
4426
4775
|
const args = ["--dangerously-skip-permissions", "--no-session-persistence"];
|
|
4427
4776
|
if (options.model) {
|
|
@@ -4439,7 +4788,7 @@ class ClaudeRunner {
|
|
|
4439
4788
|
aborted = false;
|
|
4440
4789
|
async isAvailable() {
|
|
4441
4790
|
try {
|
|
4442
|
-
|
|
4791
|
+
execSync6("claude --version", {
|
|
4443
4792
|
encoding: "utf-8",
|
|
4444
4793
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4445
4794
|
});
|
|
@@ -4450,7 +4799,7 @@ class ClaudeRunner {
|
|
|
4450
4799
|
}
|
|
4451
4800
|
async getVersion() {
|
|
4452
4801
|
try {
|
|
4453
|
-
const output =
|
|
4802
|
+
const output = execSync6("claude --version", {
|
|
4454
4803
|
encoding: "utf-8",
|
|
4455
4804
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4456
4805
|
}).trim();
|
|
@@ -4617,8 +4966,8 @@ var init_claude = __esm(() => {
|
|
|
4617
4966
|
import { exec } from "node:child_process";
|
|
4618
4967
|
import {
|
|
4619
4968
|
cpSync,
|
|
4620
|
-
existsSync as
|
|
4621
|
-
mkdirSync as
|
|
4969
|
+
existsSync as existsSync14,
|
|
4970
|
+
mkdirSync as mkdirSync10,
|
|
4622
4971
|
mkdtempSync,
|
|
4623
4972
|
readdirSync as readdirSync3,
|
|
4624
4973
|
readFileSync as readFileSync8,
|
|
@@ -4626,10 +4975,10 @@ import {
|
|
|
4626
4975
|
statSync as statSync3
|
|
4627
4976
|
} from "node:fs";
|
|
4628
4977
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
4629
|
-
import { dirname as dirname3, join as
|
|
4978
|
+
import { dirname as dirname3, join as join13, relative } from "node:path";
|
|
4630
4979
|
import { promisify } from "node:util";
|
|
4631
4980
|
function parseIgnoreFile(filePath) {
|
|
4632
|
-
if (!
|
|
4981
|
+
if (!existsSync14(filePath))
|
|
4633
4982
|
return [];
|
|
4634
4983
|
const content = readFileSync8(filePath, "utf-8");
|
|
4635
4984
|
const rules = [];
|
|
@@ -4699,7 +5048,7 @@ function findIgnoredPaths(projectRoot, rules) {
|
|
|
4699
5048
|
for (const name of entries) {
|
|
4700
5049
|
if (SKIP_DIRS.has(name))
|
|
4701
5050
|
continue;
|
|
4702
|
-
const fullPath =
|
|
5051
|
+
const fullPath = join13(dir, name);
|
|
4703
5052
|
let stat = null;
|
|
4704
5053
|
try {
|
|
4705
5054
|
stat = statSync3(fullPath);
|
|
@@ -4733,7 +5082,7 @@ function findIgnoredPaths(projectRoot, rules) {
|
|
|
4733
5082
|
}
|
|
4734
5083
|
function backupIgnoredFiles(projectRoot) {
|
|
4735
5084
|
const log = getLogger();
|
|
4736
|
-
const ignorePath =
|
|
5085
|
+
const ignorePath = join13(projectRoot, ".sandboxignore");
|
|
4737
5086
|
const rules = parseIgnoreFile(ignorePath);
|
|
4738
5087
|
if (rules.length === 0)
|
|
4739
5088
|
return NOOP_BACKUP;
|
|
@@ -4742,7 +5091,7 @@ function backupIgnoredFiles(projectRoot) {
|
|
|
4742
5091
|
return NOOP_BACKUP;
|
|
4743
5092
|
let backupDir;
|
|
4744
5093
|
try {
|
|
4745
|
-
backupDir = mkdtempSync(
|
|
5094
|
+
backupDir = mkdtempSync(join13(tmpdir3(), "locus-sandbox-backup-"));
|
|
4746
5095
|
} catch (err) {
|
|
4747
5096
|
log.debug("Failed to create sandbox backup dir", {
|
|
4748
5097
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -4752,9 +5101,9 @@ function backupIgnoredFiles(projectRoot) {
|
|
|
4752
5101
|
const backed = [];
|
|
4753
5102
|
for (const src of paths) {
|
|
4754
5103
|
const rel = relative(projectRoot, src);
|
|
4755
|
-
const dest =
|
|
5104
|
+
const dest = join13(backupDir, rel);
|
|
4756
5105
|
try {
|
|
4757
|
-
|
|
5106
|
+
mkdirSync10(dirname3(dest), { recursive: true });
|
|
4758
5107
|
cpSync(src, dest, { recursive: true, preserveTimestamps: true });
|
|
4759
5108
|
backed.push({ src, dest });
|
|
4760
5109
|
} catch (err) {
|
|
@@ -4776,7 +5125,7 @@ function backupIgnoredFiles(projectRoot) {
|
|
|
4776
5125
|
restore() {
|
|
4777
5126
|
for (const { src, dest } of backed) {
|
|
4778
5127
|
try {
|
|
4779
|
-
|
|
5128
|
+
mkdirSync10(dirname3(src), { recursive: true });
|
|
4780
5129
|
cpSync(dest, src, { recursive: true, preserveTimestamps: true });
|
|
4781
5130
|
} catch (err) {
|
|
4782
5131
|
log.debug("Failed to restore ignored file (potential data loss)", {
|
|
@@ -4794,7 +5143,7 @@ function backupIgnoredFiles(projectRoot) {
|
|
|
4794
5143
|
}
|
|
4795
5144
|
async function enforceSandboxIgnore(sandboxName, projectRoot) {
|
|
4796
5145
|
const log = getLogger();
|
|
4797
|
-
const ignorePath =
|
|
5146
|
+
const ignorePath = join13(projectRoot, ".sandboxignore");
|
|
4798
5147
|
const rules = parseIgnoreFile(ignorePath);
|
|
4799
5148
|
if (rules.length === 0)
|
|
4800
5149
|
return;
|
|
@@ -5039,7 +5388,7 @@ var init_claude_sandbox = __esm(() => {
|
|
|
5039
5388
|
});
|
|
5040
5389
|
|
|
5041
5390
|
// src/ai/codex.ts
|
|
5042
|
-
import { execSync as
|
|
5391
|
+
import { execSync as execSync7, spawn as spawn4 } from "node:child_process";
|
|
5043
5392
|
function buildCodexArgs(model) {
|
|
5044
5393
|
const args = ["exec", "--full-auto", "--skip-git-repo-check", "--json"];
|
|
5045
5394
|
if (model) {
|
|
@@ -5055,7 +5404,7 @@ class CodexRunner {
|
|
|
5055
5404
|
aborted = false;
|
|
5056
5405
|
async isAvailable() {
|
|
5057
5406
|
try {
|
|
5058
|
-
|
|
5407
|
+
execSync7("codex --version", {
|
|
5059
5408
|
encoding: "utf-8",
|
|
5060
5409
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5061
5410
|
});
|
|
@@ -5066,7 +5415,7 @@ class CodexRunner {
|
|
|
5066
5415
|
}
|
|
5067
5416
|
async getVersion() {
|
|
5068
5417
|
try {
|
|
5069
|
-
const output =
|
|
5418
|
+
const output = execSync7("codex --version", {
|
|
5070
5419
|
encoding: "utf-8",
|
|
5071
5420
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5072
5421
|
}).trim();
|
|
@@ -7094,9 +7443,9 @@ var init_sprint = __esm(() => {
|
|
|
7094
7443
|
});
|
|
7095
7444
|
|
|
7096
7445
|
// src/core/prompt-builder.ts
|
|
7097
|
-
import { execSync as
|
|
7098
|
-
import { existsSync as
|
|
7099
|
-
import { join as
|
|
7446
|
+
import { execSync as execSync8 } from "node:child_process";
|
|
7447
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
7448
|
+
import { join as join14 } from "node:path";
|
|
7100
7449
|
function buildExecutionPrompt(ctx) {
|
|
7101
7450
|
const sections = [];
|
|
7102
7451
|
sections.push(buildSystemContext(ctx.projectRoot));
|
|
@@ -7126,13 +7475,13 @@ function buildFeedbackPrompt(ctx) {
|
|
|
7126
7475
|
}
|
|
7127
7476
|
function buildReplPrompt(userMessage, projectRoot, _config, previousMessages) {
|
|
7128
7477
|
const sections = [];
|
|
7129
|
-
const locusmd = readFileSafe(
|
|
7478
|
+
const locusmd = readFileSafe(join14(projectRoot, ".locus", "LOCUS.md"));
|
|
7130
7479
|
if (locusmd) {
|
|
7131
7480
|
sections.push(`<project-instructions>
|
|
7132
7481
|
${locusmd}
|
|
7133
7482
|
</project-instructions>`);
|
|
7134
7483
|
}
|
|
7135
|
-
const learnings = readFileSafe(
|
|
7484
|
+
const learnings = readFileSafe(join14(projectRoot, ".locus", "LEARNINGS.md"));
|
|
7136
7485
|
if (learnings) {
|
|
7137
7486
|
sections.push(`<past-learnings>
|
|
7138
7487
|
${learnings}
|
|
@@ -7159,24 +7508,24 @@ ${userMessage}
|
|
|
7159
7508
|
}
|
|
7160
7509
|
function buildSystemContext(projectRoot) {
|
|
7161
7510
|
const parts = [];
|
|
7162
|
-
const locusmd = readFileSafe(
|
|
7511
|
+
const locusmd = readFileSafe(join14(projectRoot, ".locus", "LOCUS.md"));
|
|
7163
7512
|
if (locusmd) {
|
|
7164
7513
|
parts.push(`<project-instructions>
|
|
7165
7514
|
${locusmd}
|
|
7166
7515
|
</project-instructions>`);
|
|
7167
7516
|
}
|
|
7168
|
-
const learnings = readFileSafe(
|
|
7517
|
+
const learnings = readFileSafe(join14(projectRoot, ".locus", "LEARNINGS.md"));
|
|
7169
7518
|
if (learnings) {
|
|
7170
7519
|
parts.push(`<past-learnings>
|
|
7171
7520
|
${learnings}
|
|
7172
7521
|
</past-learnings>`);
|
|
7173
7522
|
}
|
|
7174
|
-
const discussionsDir =
|
|
7175
|
-
if (
|
|
7523
|
+
const discussionsDir = join14(projectRoot, ".locus", "discussions");
|
|
7524
|
+
if (existsSync15(discussionsDir)) {
|
|
7176
7525
|
try {
|
|
7177
7526
|
const files = readdirSync4(discussionsDir).filter((f) => f.endsWith(".md")).slice(0, 3);
|
|
7178
7527
|
for (const file of files) {
|
|
7179
|
-
const content = readFileSafe(
|
|
7528
|
+
const content = readFileSafe(join14(discussionsDir, file));
|
|
7180
7529
|
if (content) {
|
|
7181
7530
|
const name = file.replace(".md", "");
|
|
7182
7531
|
parts.push(`<discussion name="${name}">
|
|
@@ -7243,7 +7592,7 @@ ${parts.join(`
|
|
|
7243
7592
|
function buildRepoContext(projectRoot) {
|
|
7244
7593
|
const parts = [];
|
|
7245
7594
|
try {
|
|
7246
|
-
const tree =
|
|
7595
|
+
const tree = execSync8("find . -maxdepth 2 -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/.locus/*' -not -path '*/dist/*' -not -path '*/build/*' | head -80", { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
7247
7596
|
if (tree) {
|
|
7248
7597
|
parts.push(`<file-tree>
|
|
7249
7598
|
\`\`\`
|
|
@@ -7253,7 +7602,7 @@ ${tree}
|
|
|
7253
7602
|
}
|
|
7254
7603
|
} catch {}
|
|
7255
7604
|
try {
|
|
7256
|
-
const gitLog =
|
|
7605
|
+
const gitLog = execSync8("git log --oneline -10", {
|
|
7257
7606
|
cwd: projectRoot,
|
|
7258
7607
|
encoding: "utf-8",
|
|
7259
7608
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7267,7 +7616,7 @@ ${gitLog}
|
|
|
7267
7616
|
}
|
|
7268
7617
|
} catch {}
|
|
7269
7618
|
try {
|
|
7270
|
-
const branch =
|
|
7619
|
+
const branch = execSync8("git rev-parse --abbrev-ref HEAD", {
|
|
7271
7620
|
cwd: projectRoot,
|
|
7272
7621
|
encoding: "utf-8",
|
|
7273
7622
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7329,7 +7678,7 @@ function buildFeedbackInstructions() {
|
|
|
7329
7678
|
}
|
|
7330
7679
|
function readFileSafe(path) {
|
|
7331
7680
|
try {
|
|
7332
|
-
if (!
|
|
7681
|
+
if (!existsSync15(path))
|
|
7333
7682
|
return null;
|
|
7334
7683
|
return readFileSync9(path, "utf-8");
|
|
7335
7684
|
} catch {
|
|
@@ -7523,7 +7872,7 @@ var init_diff_renderer = __esm(() => {
|
|
|
7523
7872
|
});
|
|
7524
7873
|
|
|
7525
7874
|
// src/repl/commands.ts
|
|
7526
|
-
import { execSync as
|
|
7875
|
+
import { execSync as execSync9 } from "node:child_process";
|
|
7527
7876
|
function getSlashCommands() {
|
|
7528
7877
|
return [
|
|
7529
7878
|
{
|
|
@@ -7721,7 +8070,7 @@ function cmdModel(args, ctx) {
|
|
|
7721
8070
|
}
|
|
7722
8071
|
function cmdDiff(_args, ctx) {
|
|
7723
8072
|
try {
|
|
7724
|
-
const diff =
|
|
8073
|
+
const diff = execSync9("git diff", {
|
|
7725
8074
|
cwd: ctx.projectRoot,
|
|
7726
8075
|
encoding: "utf-8",
|
|
7727
8076
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7757,7 +8106,7 @@ function cmdDiff(_args, ctx) {
|
|
|
7757
8106
|
}
|
|
7758
8107
|
function cmdUndo(_args, ctx) {
|
|
7759
8108
|
try {
|
|
7760
|
-
const status =
|
|
8109
|
+
const status = execSync9("git status --porcelain", {
|
|
7761
8110
|
cwd: ctx.projectRoot,
|
|
7762
8111
|
encoding: "utf-8",
|
|
7763
8112
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7767,7 +8116,7 @@ function cmdUndo(_args, ctx) {
|
|
|
7767
8116
|
`);
|
|
7768
8117
|
return;
|
|
7769
8118
|
}
|
|
7770
|
-
|
|
8119
|
+
execSync9("git checkout .", {
|
|
7771
8120
|
cwd: ctx.projectRoot,
|
|
7772
8121
|
encoding: "utf-8",
|
|
7773
8122
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7809,7 +8158,7 @@ var init_commands = __esm(() => {
|
|
|
7809
8158
|
|
|
7810
8159
|
// src/repl/completions.ts
|
|
7811
8160
|
import { readdirSync as readdirSync5 } from "node:fs";
|
|
7812
|
-
import { basename as basename2, dirname as dirname4, join as
|
|
8161
|
+
import { basename as basename2, dirname as dirname4, join as join15 } from "node:path";
|
|
7813
8162
|
|
|
7814
8163
|
class SlashCommandCompletion {
|
|
7815
8164
|
commands;
|
|
@@ -7864,7 +8213,7 @@ class FilePathCompletion {
|
|
|
7864
8213
|
}
|
|
7865
8214
|
findMatches(partial) {
|
|
7866
8215
|
try {
|
|
7867
|
-
const dir = partial.includes("/") ?
|
|
8216
|
+
const dir = partial.includes("/") ? join15(this.projectRoot, dirname4(partial)) : this.projectRoot;
|
|
7868
8217
|
const prefix = basename2(partial);
|
|
7869
8218
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
7870
8219
|
return entries.filter((e) => {
|
|
@@ -7900,14 +8249,14 @@ class CombinedCompletion {
|
|
|
7900
8249
|
var init_completions = () => {};
|
|
7901
8250
|
|
|
7902
8251
|
// src/repl/input-history.ts
|
|
7903
|
-
import { existsSync as
|
|
7904
|
-
import { dirname as dirname5, join as
|
|
8252
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
|
|
8253
|
+
import { dirname as dirname5, join as join16 } from "node:path";
|
|
7905
8254
|
|
|
7906
8255
|
class InputHistory {
|
|
7907
8256
|
entries = [];
|
|
7908
8257
|
filePath;
|
|
7909
8258
|
constructor(projectRoot) {
|
|
7910
|
-
this.filePath =
|
|
8259
|
+
this.filePath = join16(projectRoot, ".locus", "sessions", ".input-history");
|
|
7911
8260
|
this.load();
|
|
7912
8261
|
}
|
|
7913
8262
|
add(text) {
|
|
@@ -7946,7 +8295,7 @@ class InputHistory {
|
|
|
7946
8295
|
}
|
|
7947
8296
|
load() {
|
|
7948
8297
|
try {
|
|
7949
|
-
if (!
|
|
8298
|
+
if (!existsSync16(this.filePath))
|
|
7950
8299
|
return;
|
|
7951
8300
|
const content = readFileSync10(this.filePath, "utf-8");
|
|
7952
8301
|
this.entries = content.split(`
|
|
@@ -7956,12 +8305,12 @@ class InputHistory {
|
|
|
7956
8305
|
save() {
|
|
7957
8306
|
try {
|
|
7958
8307
|
const dir = dirname5(this.filePath);
|
|
7959
|
-
if (!
|
|
7960
|
-
|
|
8308
|
+
if (!existsSync16(dir)) {
|
|
8309
|
+
mkdirSync11(dir, { recursive: true });
|
|
7961
8310
|
}
|
|
7962
8311
|
const content = this.entries.map((e) => this.escape(e)).join(`
|
|
7963
8312
|
`);
|
|
7964
|
-
|
|
8313
|
+
writeFileSync7(this.filePath, content, "utf-8");
|
|
7965
8314
|
} catch {}
|
|
7966
8315
|
}
|
|
7967
8316
|
escape(text) {
|
|
@@ -7987,21 +8336,21 @@ var init_model_config = __esm(() => {
|
|
|
7987
8336
|
|
|
7988
8337
|
// src/repl/session-manager.ts
|
|
7989
8338
|
import {
|
|
7990
|
-
existsSync as
|
|
7991
|
-
mkdirSync as
|
|
8339
|
+
existsSync as existsSync17,
|
|
8340
|
+
mkdirSync as mkdirSync12,
|
|
7992
8341
|
readdirSync as readdirSync6,
|
|
7993
8342
|
readFileSync as readFileSync11,
|
|
7994
8343
|
unlinkSync as unlinkSync3,
|
|
7995
|
-
writeFileSync as
|
|
8344
|
+
writeFileSync as writeFileSync8
|
|
7996
8345
|
} from "node:fs";
|
|
7997
|
-
import { basename as basename3, join as
|
|
8346
|
+
import { basename as basename3, join as join17 } from "node:path";
|
|
7998
8347
|
|
|
7999
8348
|
class SessionManager {
|
|
8000
8349
|
sessionsDir;
|
|
8001
8350
|
constructor(projectRoot) {
|
|
8002
|
-
this.sessionsDir =
|
|
8003
|
-
if (!
|
|
8004
|
-
|
|
8351
|
+
this.sessionsDir = join17(projectRoot, ".locus", "sessions");
|
|
8352
|
+
if (!existsSync17(this.sessionsDir)) {
|
|
8353
|
+
mkdirSync12(this.sessionsDir, { recursive: true });
|
|
8005
8354
|
}
|
|
8006
8355
|
}
|
|
8007
8356
|
create(options) {
|
|
@@ -8026,12 +8375,12 @@ class SessionManager {
|
|
|
8026
8375
|
}
|
|
8027
8376
|
isPersisted(sessionOrId) {
|
|
8028
8377
|
const sessionId = typeof sessionOrId === "string" ? sessionOrId : sessionOrId.id;
|
|
8029
|
-
return
|
|
8378
|
+
return existsSync17(this.getSessionPath(sessionId));
|
|
8030
8379
|
}
|
|
8031
8380
|
load(idOrPrefix) {
|
|
8032
8381
|
const files = this.listSessionFiles();
|
|
8033
8382
|
const exactPath = this.getSessionPath(idOrPrefix);
|
|
8034
|
-
if (
|
|
8383
|
+
if (existsSync17(exactPath)) {
|
|
8035
8384
|
try {
|
|
8036
8385
|
return JSON.parse(readFileSync11(exactPath, "utf-8"));
|
|
8037
8386
|
} catch {
|
|
@@ -8054,7 +8403,7 @@ class SessionManager {
|
|
|
8054
8403
|
save(session) {
|
|
8055
8404
|
session.updated = new Date().toISOString();
|
|
8056
8405
|
const path = this.getSessionPath(session.id);
|
|
8057
|
-
|
|
8406
|
+
writeFileSync8(path, `${JSON.stringify(session, null, 2)}
|
|
8058
8407
|
`, "utf-8");
|
|
8059
8408
|
}
|
|
8060
8409
|
addMessage(session, message) {
|
|
@@ -8081,7 +8430,7 @@ class SessionManager {
|
|
|
8081
8430
|
}
|
|
8082
8431
|
delete(sessionId) {
|
|
8083
8432
|
const path = this.getSessionPath(sessionId);
|
|
8084
|
-
if (
|
|
8433
|
+
if (existsSync17(path)) {
|
|
8085
8434
|
unlinkSync3(path);
|
|
8086
8435
|
return true;
|
|
8087
8436
|
}
|
|
@@ -8111,7 +8460,7 @@ class SessionManager {
|
|
|
8111
8460
|
const remaining = withStats.length - pruned;
|
|
8112
8461
|
if (remaining > MAX_SESSIONS) {
|
|
8113
8462
|
const toRemove = remaining - MAX_SESSIONS;
|
|
8114
|
-
const alive = withStats.filter((e) =>
|
|
8463
|
+
const alive = withStats.filter((e) => existsSync17(e.path));
|
|
8115
8464
|
for (let i = 0;i < toRemove && i < alive.length; i++) {
|
|
8116
8465
|
try {
|
|
8117
8466
|
unlinkSync3(alive[i].path);
|
|
@@ -8126,7 +8475,7 @@ class SessionManager {
|
|
|
8126
8475
|
}
|
|
8127
8476
|
listSessionFiles() {
|
|
8128
8477
|
try {
|
|
8129
|
-
return readdirSync6(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) =>
|
|
8478
|
+
return readdirSync6(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join17(this.sessionsDir, f));
|
|
8130
8479
|
} catch {
|
|
8131
8480
|
return [];
|
|
8132
8481
|
}
|
|
@@ -8135,7 +8484,7 @@ class SessionManager {
|
|
|
8135
8484
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
8136
8485
|
}
|
|
8137
8486
|
getSessionPath(sessionId) {
|
|
8138
|
-
return
|
|
8487
|
+
return join17(this.sessionsDir, `${sessionId}.json`);
|
|
8139
8488
|
}
|
|
8140
8489
|
}
|
|
8141
8490
|
var MAX_SESSIONS = 50, SESSION_MAX_AGE_MS;
|
|
@@ -8145,17 +8494,17 @@ var init_session_manager = __esm(() => {
|
|
|
8145
8494
|
});
|
|
8146
8495
|
|
|
8147
8496
|
// src/repl/voice.ts
|
|
8148
|
-
import { execSync as
|
|
8149
|
-
import { existsSync as
|
|
8497
|
+
import { execSync as execSync10, spawn as spawn6 } from "node:child_process";
|
|
8498
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync13, unlinkSync as unlinkSync4 } from "node:fs";
|
|
8150
8499
|
import { cpus, homedir as homedir4, platform, tmpdir as tmpdir4 } from "node:os";
|
|
8151
|
-
import { join as
|
|
8500
|
+
import { join as join18 } from "node:path";
|
|
8152
8501
|
function getWhisperModelPath() {
|
|
8153
|
-
return
|
|
8502
|
+
return join18(WHISPER_MODELS_DIR, `ggml-${WHISPER_MODEL}.bin`);
|
|
8154
8503
|
}
|
|
8155
8504
|
function commandExists(cmd) {
|
|
8156
8505
|
try {
|
|
8157
8506
|
const which = platform() === "win32" ? "where" : "which";
|
|
8158
|
-
|
|
8507
|
+
execSync10(`${which} ${cmd}`, { stdio: "pipe" });
|
|
8159
8508
|
return true;
|
|
8160
8509
|
} catch {
|
|
8161
8510
|
return false;
|
|
@@ -8168,16 +8517,16 @@ function findWhisperBinary() {
|
|
|
8168
8517
|
return name;
|
|
8169
8518
|
}
|
|
8170
8519
|
for (const name of candidates) {
|
|
8171
|
-
const fullPath =
|
|
8172
|
-
if (
|
|
8520
|
+
const fullPath = join18(LOCUS_BIN_DIR, name);
|
|
8521
|
+
if (existsSync18(fullPath))
|
|
8173
8522
|
return fullPath;
|
|
8174
8523
|
}
|
|
8175
8524
|
if (platform() === "darwin") {
|
|
8176
8525
|
const brewDirs = ["/opt/homebrew/bin", "/usr/local/bin"];
|
|
8177
8526
|
for (const dir of brewDirs) {
|
|
8178
8527
|
for (const name of candidates) {
|
|
8179
|
-
const fullPath =
|
|
8180
|
-
if (
|
|
8528
|
+
const fullPath = join18(dir, name);
|
|
8529
|
+
if (existsSync18(fullPath))
|
|
8181
8530
|
return fullPath;
|
|
8182
8531
|
}
|
|
8183
8532
|
}
|
|
@@ -8192,11 +8541,11 @@ function findSoxRecBinary() {
|
|
|
8192
8541
|
if (platform() === "darwin") {
|
|
8193
8542
|
const brewDirs = ["/opt/homebrew/bin", "/usr/local/bin"];
|
|
8194
8543
|
for (const dir of brewDirs) {
|
|
8195
|
-
const recPath =
|
|
8196
|
-
if (
|
|
8544
|
+
const recPath = join18(dir, "rec");
|
|
8545
|
+
if (existsSync18(recPath))
|
|
8197
8546
|
return recPath;
|
|
8198
|
-
const soxPath =
|
|
8199
|
-
if (
|
|
8547
|
+
const soxPath = join18(dir, "sox");
|
|
8548
|
+
if (existsSync18(soxPath))
|
|
8200
8549
|
return soxPath;
|
|
8201
8550
|
}
|
|
8202
8551
|
}
|
|
@@ -8205,7 +8554,7 @@ function findSoxRecBinary() {
|
|
|
8205
8554
|
function checkDependencies() {
|
|
8206
8555
|
const soxBinary = findSoxRecBinary();
|
|
8207
8556
|
const whisperBinary = findWhisperBinary();
|
|
8208
|
-
const modelDownloaded =
|
|
8557
|
+
const modelDownloaded = existsSync18(getWhisperModelPath());
|
|
8209
8558
|
return {
|
|
8210
8559
|
sox: soxBinary !== null,
|
|
8211
8560
|
whisper: whisperBinary !== null,
|
|
@@ -8281,22 +8630,22 @@ function installSox(pm) {
|
|
|
8281
8630
|
try {
|
|
8282
8631
|
switch (pm) {
|
|
8283
8632
|
case "brew":
|
|
8284
|
-
|
|
8633
|
+
execSync10("brew install sox", { stdio: "inherit", timeout: 300000 });
|
|
8285
8634
|
break;
|
|
8286
8635
|
case "apt":
|
|
8287
|
-
|
|
8636
|
+
execSync10("sudo apt-get install -y sox", {
|
|
8288
8637
|
stdio: "inherit",
|
|
8289
8638
|
timeout: 300000
|
|
8290
8639
|
});
|
|
8291
8640
|
break;
|
|
8292
8641
|
case "dnf":
|
|
8293
|
-
|
|
8642
|
+
execSync10("sudo dnf install -y sox", {
|
|
8294
8643
|
stdio: "inherit",
|
|
8295
8644
|
timeout: 300000
|
|
8296
8645
|
});
|
|
8297
8646
|
break;
|
|
8298
8647
|
case "pacman":
|
|
8299
|
-
|
|
8648
|
+
execSync10("sudo pacman -S --noconfirm sox", {
|
|
8300
8649
|
stdio: "inherit",
|
|
8301
8650
|
timeout: 300000
|
|
8302
8651
|
});
|
|
@@ -8310,7 +8659,7 @@ function installSox(pm) {
|
|
|
8310
8659
|
function installWhisperCpp(pm) {
|
|
8311
8660
|
if (pm === "brew") {
|
|
8312
8661
|
try {
|
|
8313
|
-
|
|
8662
|
+
execSync10("brew install whisper-cpp", {
|
|
8314
8663
|
stdio: "inherit",
|
|
8315
8664
|
timeout: 300000
|
|
8316
8665
|
});
|
|
@@ -8332,19 +8681,19 @@ function ensureBuildDeps(pm) {
|
|
|
8332
8681
|
try {
|
|
8333
8682
|
switch (pm) {
|
|
8334
8683
|
case "apt":
|
|
8335
|
-
|
|
8684
|
+
execSync10("sudo apt-get install -y cmake g++ make git", {
|
|
8336
8685
|
stdio: "inherit",
|
|
8337
8686
|
timeout: 300000
|
|
8338
8687
|
});
|
|
8339
8688
|
break;
|
|
8340
8689
|
case "dnf":
|
|
8341
|
-
|
|
8690
|
+
execSync10("sudo dnf install -y cmake gcc-c++ make git", {
|
|
8342
8691
|
stdio: "inherit",
|
|
8343
8692
|
timeout: 300000
|
|
8344
8693
|
});
|
|
8345
8694
|
break;
|
|
8346
8695
|
case "pacman":
|
|
8347
|
-
|
|
8696
|
+
execSync10("sudo pacman -S --noconfirm cmake gcc make git", {
|
|
8348
8697
|
stdio: "inherit",
|
|
8349
8698
|
timeout: 300000
|
|
8350
8699
|
});
|
|
@@ -8359,40 +8708,40 @@ function ensureBuildDeps(pm) {
|
|
|
8359
8708
|
}
|
|
8360
8709
|
function buildWhisperFromSource(pm) {
|
|
8361
8710
|
const out = process.stderr;
|
|
8362
|
-
const buildDir =
|
|
8711
|
+
const buildDir = join18(tmpdir4(), `locus-whisper-build-${process.pid}`);
|
|
8363
8712
|
if (!ensureBuildDeps(pm)) {
|
|
8364
8713
|
out.write(` ${red2("✗")} Could not install build tools (cmake, g++, git).
|
|
8365
8714
|
`);
|
|
8366
8715
|
return false;
|
|
8367
8716
|
}
|
|
8368
8717
|
try {
|
|
8369
|
-
|
|
8370
|
-
|
|
8718
|
+
mkdirSync13(buildDir, { recursive: true });
|
|
8719
|
+
mkdirSync13(LOCUS_BIN_DIR, { recursive: true });
|
|
8371
8720
|
out.write(` ${dim2("Cloning whisper.cpp...")}
|
|
8372
8721
|
`);
|
|
8373
|
-
|
|
8374
|
-
const srcDir =
|
|
8722
|
+
execSync10(`git clone --depth 1 https://github.com/ggerganov/whisper.cpp.git "${join18(buildDir, "whisper.cpp")}"`, { stdio: ["pipe", "pipe", "pipe"], timeout: 120000 });
|
|
8723
|
+
const srcDir = join18(buildDir, "whisper.cpp");
|
|
8375
8724
|
const numCpus = cpus().length || 2;
|
|
8376
8725
|
out.write(` ${dim2("Building whisper.cpp (this may take a few minutes)...")}
|
|
8377
8726
|
`);
|
|
8378
|
-
|
|
8727
|
+
execSync10("cmake -B build -DCMAKE_BUILD_TYPE=Release", {
|
|
8379
8728
|
cwd: srcDir,
|
|
8380
8729
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8381
8730
|
timeout: 120000
|
|
8382
8731
|
});
|
|
8383
|
-
|
|
8732
|
+
execSync10(`cmake --build build --config Release -j${numCpus}`, {
|
|
8384
8733
|
cwd: srcDir,
|
|
8385
8734
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8386
8735
|
timeout: 600000
|
|
8387
8736
|
});
|
|
8388
|
-
const destPath =
|
|
8737
|
+
const destPath = join18(LOCUS_BIN_DIR, "whisper-cli");
|
|
8389
8738
|
const binaryCandidates = [
|
|
8390
|
-
|
|
8391
|
-
|
|
8739
|
+
join18(srcDir, "build", "bin", "whisper-cli"),
|
|
8740
|
+
join18(srcDir, "build", "bin", "main")
|
|
8392
8741
|
];
|
|
8393
8742
|
for (const candidate of binaryCandidates) {
|
|
8394
|
-
if (
|
|
8395
|
-
|
|
8743
|
+
if (existsSync18(candidate)) {
|
|
8744
|
+
execSync10(`cp "${candidate}" "${destPath}" && chmod +x "${destPath}"`, {
|
|
8396
8745
|
stdio: "pipe"
|
|
8397
8746
|
});
|
|
8398
8747
|
return true;
|
|
@@ -8407,7 +8756,7 @@ function buildWhisperFromSource(pm) {
|
|
|
8407
8756
|
return false;
|
|
8408
8757
|
} finally {
|
|
8409
8758
|
try {
|
|
8410
|
-
|
|
8759
|
+
execSync10(`rm -rf "${buildDir}"`, { stdio: "pipe" });
|
|
8411
8760
|
} catch {}
|
|
8412
8761
|
}
|
|
8413
8762
|
}
|
|
@@ -8470,20 +8819,20 @@ ${bold2("Installing voice dependencies...")}
|
|
|
8470
8819
|
}
|
|
8471
8820
|
function downloadModel() {
|
|
8472
8821
|
const modelPath = getWhisperModelPath();
|
|
8473
|
-
if (
|
|
8822
|
+
if (existsSync18(modelPath))
|
|
8474
8823
|
return true;
|
|
8475
|
-
|
|
8824
|
+
mkdirSync13(WHISPER_MODELS_DIR, { recursive: true });
|
|
8476
8825
|
const url = `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/${WHISPER_MODEL === "base.en" ? "ggml-base.en.bin" : `ggml-${WHISPER_MODEL}.bin`}`;
|
|
8477
8826
|
process.stderr.write(`${dim2("Downloading whisper model")} ${bold2(WHISPER_MODEL)} ${dim2("(~150MB)...")}
|
|
8478
8827
|
`);
|
|
8479
8828
|
try {
|
|
8480
8829
|
if (commandExists("curl")) {
|
|
8481
|
-
|
|
8830
|
+
execSync10(`curl -L -o "${modelPath}" "${url}"`, {
|
|
8482
8831
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8483
8832
|
timeout: 300000
|
|
8484
8833
|
});
|
|
8485
8834
|
} else if (commandExists("wget")) {
|
|
8486
|
-
|
|
8835
|
+
execSync10(`wget -O "${modelPath}" "${url}"`, {
|
|
8487
8836
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8488
8837
|
timeout: 300000
|
|
8489
8838
|
});
|
|
@@ -8517,7 +8866,7 @@ class VoiceController {
|
|
|
8517
8866
|
onStateChange;
|
|
8518
8867
|
constructor(options) {
|
|
8519
8868
|
this.onStateChange = options.onStateChange;
|
|
8520
|
-
this.tempFile =
|
|
8869
|
+
this.tempFile = join18(tmpdir4(), `locus-voice-${process.pid}.wav`);
|
|
8521
8870
|
this.deps = checkDependencies();
|
|
8522
8871
|
}
|
|
8523
8872
|
getState() {
|
|
@@ -8585,7 +8934,7 @@ ${red2("✗")} Recording failed: ${err.message}\r
|
|
|
8585
8934
|
this.recordProcess = null;
|
|
8586
8935
|
this.setState("idle");
|
|
8587
8936
|
await sleep2(200);
|
|
8588
|
-
if (!
|
|
8937
|
+
if (!existsSync18(this.tempFile)) {
|
|
8589
8938
|
return null;
|
|
8590
8939
|
}
|
|
8591
8940
|
try {
|
|
@@ -8676,12 +9025,12 @@ function sleep2(ms) {
|
|
|
8676
9025
|
var WHISPER_MODEL = "base.en", WHISPER_MODELS_DIR, LOCUS_BIN_DIR;
|
|
8677
9026
|
var init_voice = __esm(() => {
|
|
8678
9027
|
init_terminal();
|
|
8679
|
-
WHISPER_MODELS_DIR =
|
|
8680
|
-
LOCUS_BIN_DIR =
|
|
9028
|
+
WHISPER_MODELS_DIR = join18(homedir4(), ".locus", "whisper-models");
|
|
9029
|
+
LOCUS_BIN_DIR = join18(homedir4(), ".locus", "bin");
|
|
8681
9030
|
});
|
|
8682
9031
|
|
|
8683
9032
|
// src/repl/repl.ts
|
|
8684
|
-
import { execSync as
|
|
9033
|
+
import { execSync as execSync11 } from "node:child_process";
|
|
8685
9034
|
async function startRepl(options) {
|
|
8686
9035
|
const { projectRoot, config } = options;
|
|
8687
9036
|
const sessionManager = new SessionManager(projectRoot);
|
|
@@ -8699,7 +9048,7 @@ async function startRepl(options) {
|
|
|
8699
9048
|
} else {
|
|
8700
9049
|
let branch = "main";
|
|
8701
9050
|
try {
|
|
8702
|
-
branch =
|
|
9051
|
+
branch = execSync11("git rev-parse --abbrev-ref HEAD", {
|
|
8703
9052
|
cwd: projectRoot,
|
|
8704
9053
|
encoding: "utf-8",
|
|
8705
9054
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9187,11 +9536,11 @@ var init_exec = __esm(() => {
|
|
|
9187
9536
|
});
|
|
9188
9537
|
|
|
9189
9538
|
// src/core/submodule.ts
|
|
9190
|
-
import { execSync as
|
|
9191
|
-
import { existsSync as
|
|
9192
|
-
import { join as
|
|
9539
|
+
import { execSync as execSync12 } from "node:child_process";
|
|
9540
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
9541
|
+
import { join as join19 } from "node:path";
|
|
9193
9542
|
function git2(args, cwd) {
|
|
9194
|
-
return
|
|
9543
|
+
return execSync12(`git ${args}`, {
|
|
9195
9544
|
cwd,
|
|
9196
9545
|
encoding: "utf-8",
|
|
9197
9546
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9205,7 +9554,7 @@ function gitSafe(args, cwd) {
|
|
|
9205
9554
|
}
|
|
9206
9555
|
}
|
|
9207
9556
|
function hasSubmodules(cwd) {
|
|
9208
|
-
return
|
|
9557
|
+
return existsSync19(join19(cwd, ".gitmodules"));
|
|
9209
9558
|
}
|
|
9210
9559
|
function listSubmodules(cwd) {
|
|
9211
9560
|
if (!hasSubmodules(cwd))
|
|
@@ -9225,7 +9574,7 @@ function listSubmodules(cwd) {
|
|
|
9225
9574
|
continue;
|
|
9226
9575
|
submodules.push({
|
|
9227
9576
|
path,
|
|
9228
|
-
absolutePath:
|
|
9577
|
+
absolutePath: join19(cwd, path),
|
|
9229
9578
|
dirty
|
|
9230
9579
|
});
|
|
9231
9580
|
}
|
|
@@ -9238,7 +9587,7 @@ function getDirtySubmodules(cwd) {
|
|
|
9238
9587
|
const submodules = listSubmodules(cwd);
|
|
9239
9588
|
const dirty = [];
|
|
9240
9589
|
for (const sub of submodules) {
|
|
9241
|
-
if (!
|
|
9590
|
+
if (!existsSync19(sub.absolutePath))
|
|
9242
9591
|
continue;
|
|
9243
9592
|
const status = gitSafe("status --porcelain", sub.absolutePath);
|
|
9244
9593
|
if (status && status.trim().length > 0) {
|
|
@@ -9259,7 +9608,7 @@ function commitDirtySubmodules(cwd, issueNumber, issueTitle) {
|
|
|
9259
9608
|
const message = `chore: complete #${issueNumber} - ${issueTitle}
|
|
9260
9609
|
|
|
9261
9610
|
Co-Authored-By: LocusAgent <agent@locusai.team>`;
|
|
9262
|
-
|
|
9611
|
+
execSync12("git commit -F -", {
|
|
9263
9612
|
input: message,
|
|
9264
9613
|
cwd: sub.absolutePath,
|
|
9265
9614
|
encoding: "utf-8",
|
|
@@ -9325,7 +9674,7 @@ function pushSubmoduleBranches(cwd) {
|
|
|
9325
9674
|
const log = getLogger();
|
|
9326
9675
|
const submodules = listSubmodules(cwd);
|
|
9327
9676
|
for (const sub of submodules) {
|
|
9328
|
-
if (!
|
|
9677
|
+
if (!existsSync19(sub.absolutePath))
|
|
9329
9678
|
continue;
|
|
9330
9679
|
const branch = gitSafe("rev-parse --abbrev-ref HEAD", sub.absolutePath)?.trim();
|
|
9331
9680
|
if (!branch || branch === "HEAD")
|
|
@@ -9346,7 +9695,7 @@ var init_submodule = __esm(() => {
|
|
|
9346
9695
|
});
|
|
9347
9696
|
|
|
9348
9697
|
// src/core/agent.ts
|
|
9349
|
-
import { execSync as
|
|
9698
|
+
import { execSync as execSync13 } from "node:child_process";
|
|
9350
9699
|
async function executeIssue(projectRoot, options) {
|
|
9351
9700
|
const log = getLogger();
|
|
9352
9701
|
const timer = createTimer();
|
|
@@ -9375,7 +9724,7 @@ ${cyan2("●")} ${bold2(`#${issueNumber}`)} ${issue.title}
|
|
|
9375
9724
|
}
|
|
9376
9725
|
let issueComments = [];
|
|
9377
9726
|
try {
|
|
9378
|
-
const commentsRaw =
|
|
9727
|
+
const commentsRaw = execSync13(`gh issue view ${issueNumber} --json comments --jq '.comments[].body'`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
9379
9728
|
if (commentsRaw) {
|
|
9380
9729
|
issueComments = commentsRaw.split(`
|
|
9381
9730
|
`).filter(Boolean);
|
|
@@ -9539,12 +9888,12 @@ ${aiResult.success ? green("✓") : red2("✗")} Iteration ${aiResult.success ?
|
|
|
9539
9888
|
}
|
|
9540
9889
|
async function createIssuePR(projectRoot, config, issue) {
|
|
9541
9890
|
try {
|
|
9542
|
-
const currentBranch =
|
|
9891
|
+
const currentBranch = execSync13("git rev-parse --abbrev-ref HEAD", {
|
|
9543
9892
|
cwd: projectRoot,
|
|
9544
9893
|
encoding: "utf-8",
|
|
9545
9894
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9546
9895
|
}).trim();
|
|
9547
|
-
const diff =
|
|
9896
|
+
const diff = execSync13(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
|
|
9548
9897
|
cwd: projectRoot,
|
|
9549
9898
|
encoding: "utf-8",
|
|
9550
9899
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9554,7 +9903,7 @@ async function createIssuePR(projectRoot, config, issue) {
|
|
|
9554
9903
|
return;
|
|
9555
9904
|
}
|
|
9556
9905
|
pushSubmoduleBranches(projectRoot);
|
|
9557
|
-
|
|
9906
|
+
execSync13(`git push -u origin ${currentBranch}`, {
|
|
9558
9907
|
cwd: projectRoot,
|
|
9559
9908
|
encoding: "utf-8",
|
|
9560
9909
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9609,9 +9958,9 @@ var init_agent = __esm(() => {
|
|
|
9609
9958
|
});
|
|
9610
9959
|
|
|
9611
9960
|
// src/core/conflict.ts
|
|
9612
|
-
import { execSync as
|
|
9961
|
+
import { execSync as execSync14 } from "node:child_process";
|
|
9613
9962
|
function git3(args, cwd) {
|
|
9614
|
-
return
|
|
9963
|
+
return execSync14(`git ${args}`, {
|
|
9615
9964
|
cwd,
|
|
9616
9965
|
encoding: "utf-8",
|
|
9617
9966
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9740,19 +10089,19 @@ var init_conflict = __esm(() => {
|
|
|
9740
10089
|
|
|
9741
10090
|
// src/core/run-state.ts
|
|
9742
10091
|
import {
|
|
9743
|
-
existsSync as
|
|
9744
|
-
mkdirSync as
|
|
10092
|
+
existsSync as existsSync20,
|
|
10093
|
+
mkdirSync as mkdirSync14,
|
|
9745
10094
|
readFileSync as readFileSync12,
|
|
9746
10095
|
unlinkSync as unlinkSync5,
|
|
9747
|
-
writeFileSync as
|
|
10096
|
+
writeFileSync as writeFileSync9
|
|
9748
10097
|
} from "node:fs";
|
|
9749
|
-
import { dirname as dirname6, join as
|
|
10098
|
+
import { dirname as dirname6, join as join20 } from "node:path";
|
|
9750
10099
|
function getRunStatePath(projectRoot) {
|
|
9751
|
-
return
|
|
10100
|
+
return join20(projectRoot, ".locus", "run-state.json");
|
|
9752
10101
|
}
|
|
9753
10102
|
function loadRunState(projectRoot) {
|
|
9754
10103
|
const path = getRunStatePath(projectRoot);
|
|
9755
|
-
if (!
|
|
10104
|
+
if (!existsSync20(path))
|
|
9756
10105
|
return null;
|
|
9757
10106
|
try {
|
|
9758
10107
|
return JSON.parse(readFileSync12(path, "utf-8"));
|
|
@@ -9764,15 +10113,15 @@ function loadRunState(projectRoot) {
|
|
|
9764
10113
|
function saveRunState(projectRoot, state) {
|
|
9765
10114
|
const path = getRunStatePath(projectRoot);
|
|
9766
10115
|
const dir = dirname6(path);
|
|
9767
|
-
if (!
|
|
9768
|
-
|
|
10116
|
+
if (!existsSync20(dir)) {
|
|
10117
|
+
mkdirSync14(dir, { recursive: true });
|
|
9769
10118
|
}
|
|
9770
|
-
|
|
10119
|
+
writeFileSync9(path, `${JSON.stringify(state, null, 2)}
|
|
9771
10120
|
`, "utf-8");
|
|
9772
10121
|
}
|
|
9773
10122
|
function clearRunState(projectRoot) {
|
|
9774
10123
|
const path = getRunStatePath(projectRoot);
|
|
9775
|
-
if (
|
|
10124
|
+
if (existsSync20(path)) {
|
|
9776
10125
|
unlinkSync5(path);
|
|
9777
10126
|
}
|
|
9778
10127
|
}
|
|
@@ -9912,11 +10261,11 @@ var init_shutdown = __esm(() => {
|
|
|
9912
10261
|
});
|
|
9913
10262
|
|
|
9914
10263
|
// src/core/worktree.ts
|
|
9915
|
-
import { execSync as
|
|
9916
|
-
import { existsSync as
|
|
9917
|
-
import { join as
|
|
10264
|
+
import { execSync as execSync15 } from "node:child_process";
|
|
10265
|
+
import { existsSync as existsSync21, readdirSync as readdirSync7, realpathSync, statSync as statSync4 } from "node:fs";
|
|
10266
|
+
import { join as join21 } from "node:path";
|
|
9918
10267
|
function git4(args, cwd) {
|
|
9919
|
-
return
|
|
10268
|
+
return execSync15(`git ${args}`, {
|
|
9920
10269
|
cwd,
|
|
9921
10270
|
encoding: "utf-8",
|
|
9922
10271
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9930,10 +10279,10 @@ function gitSafe3(args, cwd) {
|
|
|
9930
10279
|
}
|
|
9931
10280
|
}
|
|
9932
10281
|
function getWorktreeDir(projectRoot) {
|
|
9933
|
-
return
|
|
10282
|
+
return join21(projectRoot, ".locus", "worktrees");
|
|
9934
10283
|
}
|
|
9935
10284
|
function getWorktreePath(projectRoot, issueNumber) {
|
|
9936
|
-
return
|
|
10285
|
+
return join21(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
|
|
9937
10286
|
}
|
|
9938
10287
|
function generateBranchName(issueNumber) {
|
|
9939
10288
|
const randomSuffix = Math.random().toString(36).slice(2, 8);
|
|
@@ -9941,7 +10290,7 @@ function generateBranchName(issueNumber) {
|
|
|
9941
10290
|
}
|
|
9942
10291
|
function getWorktreeBranch(worktreePath) {
|
|
9943
10292
|
try {
|
|
9944
|
-
return
|
|
10293
|
+
return execSync15("git branch --show-current", {
|
|
9945
10294
|
cwd: worktreePath,
|
|
9946
10295
|
encoding: "utf-8",
|
|
9947
10296
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9953,7 +10302,7 @@ function getWorktreeBranch(worktreePath) {
|
|
|
9953
10302
|
function createWorktree(projectRoot, issueNumber, baseBranch) {
|
|
9954
10303
|
const log = getLogger();
|
|
9955
10304
|
const worktreePath = getWorktreePath(projectRoot, issueNumber);
|
|
9956
|
-
if (
|
|
10305
|
+
if (existsSync21(worktreePath)) {
|
|
9957
10306
|
log.verbose(`Worktree already exists for issue #${issueNumber}`);
|
|
9958
10307
|
const existingBranch = getWorktreeBranch(worktreePath) ?? `locus/issue-${issueNumber}`;
|
|
9959
10308
|
return {
|
|
@@ -9981,7 +10330,7 @@ function createWorktree(projectRoot, issueNumber, baseBranch) {
|
|
|
9981
10330
|
function removeWorktree(projectRoot, issueNumber) {
|
|
9982
10331
|
const log = getLogger();
|
|
9983
10332
|
const worktreePath = getWorktreePath(projectRoot, issueNumber);
|
|
9984
|
-
if (!
|
|
10333
|
+
if (!existsSync21(worktreePath)) {
|
|
9985
10334
|
log.verbose(`Worktree for issue #${issueNumber} does not exist`);
|
|
9986
10335
|
return;
|
|
9987
10336
|
}
|
|
@@ -10000,7 +10349,7 @@ function removeWorktree(projectRoot, issueNumber) {
|
|
|
10000
10349
|
function listWorktrees(projectRoot) {
|
|
10001
10350
|
const log = getLogger();
|
|
10002
10351
|
const worktreeDir = getWorktreeDir(projectRoot);
|
|
10003
|
-
if (!
|
|
10352
|
+
if (!existsSync21(worktreeDir)) {
|
|
10004
10353
|
return [];
|
|
10005
10354
|
}
|
|
10006
10355
|
const entries = readdirSync7(worktreeDir).filter((entry) => entry.startsWith("issue-"));
|
|
@@ -10020,7 +10369,7 @@ function listWorktrees(projectRoot) {
|
|
|
10020
10369
|
if (!match)
|
|
10021
10370
|
continue;
|
|
10022
10371
|
const issueNumber = Number.parseInt(match[1], 10);
|
|
10023
|
-
const path =
|
|
10372
|
+
const path = join21(worktreeDir, entry);
|
|
10024
10373
|
const branch = getWorktreeBranch(path) ?? `locus/issue-${issueNumber}`;
|
|
10025
10374
|
let resolvedPath;
|
|
10026
10375
|
try {
|
|
@@ -10068,7 +10417,7 @@ var exports_run = {};
|
|
|
10068
10417
|
__export(exports_run, {
|
|
10069
10418
|
runCommand: () => runCommand
|
|
10070
10419
|
});
|
|
10071
|
-
import { execSync as
|
|
10420
|
+
import { execSync as execSync16 } from "node:child_process";
|
|
10072
10421
|
function resolveExecutionContext(config, modelOverride) {
|
|
10073
10422
|
const model = modelOverride ?? config.ai.model;
|
|
10074
10423
|
const provider = inferProviderFromModel(model) ?? config.ai.provider;
|
|
@@ -10228,7 +10577,7 @@ ${yellow2("⚠")} A sprint run is already in progress.
|
|
|
10228
10577
|
}
|
|
10229
10578
|
if (!flags.dryRun) {
|
|
10230
10579
|
try {
|
|
10231
|
-
|
|
10580
|
+
execSync16(`git checkout -B ${branchName}`, {
|
|
10232
10581
|
cwd: projectRoot,
|
|
10233
10582
|
encoding: "utf-8",
|
|
10234
10583
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10278,7 +10627,7 @@ ${red2("✗")} Auto-rebase failed. Resolve manually.
|
|
|
10278
10627
|
let sprintContext;
|
|
10279
10628
|
if (i > 0 && !flags.dryRun) {
|
|
10280
10629
|
try {
|
|
10281
|
-
sprintContext =
|
|
10630
|
+
sprintContext = execSync16(`git diff origin/${config.agent.baseBranch}..HEAD`, {
|
|
10282
10631
|
cwd: projectRoot,
|
|
10283
10632
|
encoding: "utf-8",
|
|
10284
10633
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10343,7 +10692,7 @@ ${bold2("Summary:")}
|
|
|
10343
10692
|
const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
|
|
10344
10693
|
if (prNumber !== undefined) {
|
|
10345
10694
|
try {
|
|
10346
|
-
|
|
10695
|
+
execSync16(`git checkout ${config.agent.baseBranch}`, {
|
|
10347
10696
|
cwd: projectRoot,
|
|
10348
10697
|
encoding: "utf-8",
|
|
10349
10698
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10388,7 +10737,7 @@ ${bold2("Running issue")} ${cyan2(`#${issueNumber}`)} ${dim2(`(branch: ${branchN
|
|
|
10388
10737
|
`);
|
|
10389
10738
|
if (!flags.dryRun) {
|
|
10390
10739
|
try {
|
|
10391
|
-
|
|
10740
|
+
execSync16(`git checkout -B ${branchName} ${config.agent.baseBranch}`, {
|
|
10392
10741
|
cwd: projectRoot,
|
|
10393
10742
|
encoding: "utf-8",
|
|
10394
10743
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10413,7 +10762,7 @@ ${bold2("Running issue")} ${cyan2(`#${issueNumber}`)} ${dim2(`(branch: ${branchN
|
|
|
10413
10762
|
if (!flags.dryRun) {
|
|
10414
10763
|
if (result.success) {
|
|
10415
10764
|
try {
|
|
10416
|
-
|
|
10765
|
+
execSync16(`git checkout ${config.agent.baseBranch}`, {
|
|
10417
10766
|
cwd: projectRoot,
|
|
10418
10767
|
encoding: "utf-8",
|
|
10419
10768
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10550,13 +10899,13 @@ ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
|
|
|
10550
10899
|
`);
|
|
10551
10900
|
if (state.type === "sprint" && state.branch) {
|
|
10552
10901
|
try {
|
|
10553
|
-
const currentBranch =
|
|
10902
|
+
const currentBranch = execSync16("git rev-parse --abbrev-ref HEAD", {
|
|
10554
10903
|
cwd: projectRoot,
|
|
10555
10904
|
encoding: "utf-8",
|
|
10556
10905
|
stdio: ["pipe", "pipe", "pipe"]
|
|
10557
10906
|
}).trim();
|
|
10558
10907
|
if (currentBranch !== state.branch) {
|
|
10559
|
-
|
|
10908
|
+
execSync16(`git checkout ${state.branch}`, {
|
|
10560
10909
|
cwd: projectRoot,
|
|
10561
10910
|
encoding: "utf-8",
|
|
10562
10911
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10623,7 +10972,7 @@ ${bold2("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fai
|
|
|
10623
10972
|
const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
|
|
10624
10973
|
if (prNumber !== undefined) {
|
|
10625
10974
|
try {
|
|
10626
|
-
|
|
10975
|
+
execSync16(`git checkout ${config.agent.baseBranch}`, {
|
|
10627
10976
|
cwd: projectRoot,
|
|
10628
10977
|
encoding: "utf-8",
|
|
10629
10978
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10659,14 +11008,14 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
|
10659
11008
|
process.stderr.write(` ${dim2(`Committed submodule changes: ${committedSubmodules.join(", ")}`)}
|
|
10660
11009
|
`);
|
|
10661
11010
|
}
|
|
10662
|
-
const status =
|
|
11011
|
+
const status = execSync16("git status --porcelain", {
|
|
10663
11012
|
cwd: projectRoot,
|
|
10664
11013
|
encoding: "utf-8",
|
|
10665
11014
|
stdio: ["pipe", "pipe", "pipe"]
|
|
10666
11015
|
}).trim();
|
|
10667
11016
|
if (!status)
|
|
10668
11017
|
return;
|
|
10669
|
-
|
|
11018
|
+
execSync16("git add -A", {
|
|
10670
11019
|
cwd: projectRoot,
|
|
10671
11020
|
encoding: "utf-8",
|
|
10672
11021
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10674,7 +11023,7 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
|
10674
11023
|
const message = `chore: complete #${issueNumber} - ${issueTitle}
|
|
10675
11024
|
|
|
10676
11025
|
Co-Authored-By: LocusAgent <agent@locusai.team>`;
|
|
10677
|
-
|
|
11026
|
+
execSync16(`git commit -F -`, {
|
|
10678
11027
|
input: message,
|
|
10679
11028
|
cwd: projectRoot,
|
|
10680
11029
|
encoding: "utf-8",
|
|
@@ -10688,7 +11037,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
10688
11037
|
if (!config.agent.autoPR)
|
|
10689
11038
|
return;
|
|
10690
11039
|
try {
|
|
10691
|
-
const diff =
|
|
11040
|
+
const diff = execSync16(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
|
|
10692
11041
|
cwd: projectRoot,
|
|
10693
11042
|
encoding: "utf-8",
|
|
10694
11043
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10699,7 +11048,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
10699
11048
|
return;
|
|
10700
11049
|
}
|
|
10701
11050
|
pushSubmoduleBranches(projectRoot);
|
|
10702
|
-
|
|
11051
|
+
execSync16(`git push -u origin ${branchName}`, {
|
|
10703
11052
|
cwd: projectRoot,
|
|
10704
11053
|
encoding: "utf-8",
|
|
10705
11054
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10857,14 +11206,14 @@ __export(exports_plan, {
|
|
|
10857
11206
|
parsePlanArgs: () => parsePlanArgs
|
|
10858
11207
|
});
|
|
10859
11208
|
import {
|
|
10860
|
-
existsSync as
|
|
10861
|
-
mkdirSync as
|
|
11209
|
+
existsSync as existsSync22,
|
|
11210
|
+
mkdirSync as mkdirSync15,
|
|
10862
11211
|
readdirSync as readdirSync8,
|
|
10863
11212
|
readFileSync as readFileSync13,
|
|
10864
|
-
writeFileSync as
|
|
11213
|
+
writeFileSync as writeFileSync10
|
|
10865
11214
|
} from "node:fs";
|
|
10866
|
-
import { join as
|
|
10867
|
-
function
|
|
11215
|
+
import { join as join22 } from "node:path";
|
|
11216
|
+
function printHelp2() {
|
|
10868
11217
|
process.stderr.write(`
|
|
10869
11218
|
${bold2("locus plan")} — AI-powered sprint planning
|
|
10870
11219
|
|
|
@@ -10894,12 +11243,12 @@ function normalizeSprintName(name) {
|
|
|
10894
11243
|
return name.trim().toLowerCase();
|
|
10895
11244
|
}
|
|
10896
11245
|
function getPlansDir(projectRoot) {
|
|
10897
|
-
return
|
|
11246
|
+
return join22(projectRoot, ".locus", "plans");
|
|
10898
11247
|
}
|
|
10899
11248
|
function ensurePlansDir(projectRoot) {
|
|
10900
11249
|
const dir = getPlansDir(projectRoot);
|
|
10901
|
-
if (!
|
|
10902
|
-
|
|
11250
|
+
if (!existsSync22(dir)) {
|
|
11251
|
+
mkdirSync15(dir, { recursive: true });
|
|
10903
11252
|
}
|
|
10904
11253
|
return dir;
|
|
10905
11254
|
}
|
|
@@ -10908,14 +11257,14 @@ function generateId() {
|
|
|
10908
11257
|
}
|
|
10909
11258
|
function loadPlanFile(projectRoot, id) {
|
|
10910
11259
|
const dir = getPlansDir(projectRoot);
|
|
10911
|
-
if (!
|
|
11260
|
+
if (!existsSync22(dir))
|
|
10912
11261
|
return null;
|
|
10913
11262
|
const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
|
|
10914
11263
|
const match = files.find((f) => f.startsWith(id));
|
|
10915
11264
|
if (!match)
|
|
10916
11265
|
return null;
|
|
10917
11266
|
try {
|
|
10918
|
-
const content = readFileSync13(
|
|
11267
|
+
const content = readFileSync13(join22(dir, match), "utf-8");
|
|
10919
11268
|
return JSON.parse(content);
|
|
10920
11269
|
} catch {
|
|
10921
11270
|
return null;
|
|
@@ -10923,7 +11272,7 @@ function loadPlanFile(projectRoot, id) {
|
|
|
10923
11272
|
}
|
|
10924
11273
|
async function planCommand(projectRoot, args, flags = {}) {
|
|
10925
11274
|
if (args[0] === "help" || args.length === 0) {
|
|
10926
|
-
|
|
11275
|
+
printHelp2();
|
|
10927
11276
|
return;
|
|
10928
11277
|
}
|
|
10929
11278
|
if (args[0] === "list") {
|
|
@@ -10961,7 +11310,7 @@ async function planCommand(projectRoot, args, flags = {}) {
|
|
|
10961
11310
|
}
|
|
10962
11311
|
function handleListPlans(projectRoot) {
|
|
10963
11312
|
const dir = getPlansDir(projectRoot);
|
|
10964
|
-
if (!
|
|
11313
|
+
if (!existsSync22(dir)) {
|
|
10965
11314
|
process.stderr.write(`${dim2("No saved plans yet.")}
|
|
10966
11315
|
`);
|
|
10967
11316
|
return;
|
|
@@ -10979,7 +11328,7 @@ ${bold2("Saved Plans:")}
|
|
|
10979
11328
|
for (const file of files) {
|
|
10980
11329
|
const id = file.replace(".json", "");
|
|
10981
11330
|
try {
|
|
10982
|
-
const content = readFileSync13(
|
|
11331
|
+
const content = readFileSync13(join22(dir, file), "utf-8");
|
|
10983
11332
|
const plan = JSON.parse(content);
|
|
10984
11333
|
const date = plan.createdAt ? plan.createdAt.slice(0, 10) : "";
|
|
10985
11334
|
const issueCount = Array.isArray(plan.issues) ? plan.issues.length : 0;
|
|
@@ -11090,7 +11439,7 @@ ${bold2("Approving plan:")}
|
|
|
11090
11439
|
async function handleAIPlan(projectRoot, config, directive, sprintName, flags) {
|
|
11091
11440
|
const id = generateId();
|
|
11092
11441
|
const plansDir = ensurePlansDir(projectRoot);
|
|
11093
|
-
const planPath =
|
|
11442
|
+
const planPath = join22(plansDir, `${id}.json`);
|
|
11094
11443
|
const planPathRelative = `.locus/plans/${id}.json`;
|
|
11095
11444
|
const displayDirective = directive;
|
|
11096
11445
|
process.stderr.write(`
|
|
@@ -11132,7 +11481,7 @@ ${red2("✗")} Planning failed: ${aiResult.error}
|
|
|
11132
11481
|
`);
|
|
11133
11482
|
return;
|
|
11134
11483
|
}
|
|
11135
|
-
if (!
|
|
11484
|
+
if (!existsSync22(planPath)) {
|
|
11136
11485
|
process.stderr.write(`
|
|
11137
11486
|
${yellow2("⚠")} Plan file was not created at ${bold2(planPathRelative)}.
|
|
11138
11487
|
`);
|
|
@@ -11164,7 +11513,7 @@ ${yellow2("⚠")} Plan file has no issues.
|
|
|
11164
11513
|
plan.sprint = sprintName;
|
|
11165
11514
|
if (!plan.createdAt)
|
|
11166
11515
|
plan.createdAt = new Date().toISOString();
|
|
11167
|
-
|
|
11516
|
+
writeFileSync10(planPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
11168
11517
|
process.stderr.write(`
|
|
11169
11518
|
${bold2("Plan saved:")} ${cyan2(id)}
|
|
11170
11519
|
|
|
@@ -11313,15 +11662,15 @@ ${directive}${sprintName ? `
|
|
|
11313
11662
|
|
|
11314
11663
|
**Sprint:** ${sprintName}` : ""}
|
|
11315
11664
|
</directive>`);
|
|
11316
|
-
const locusPath =
|
|
11317
|
-
if (
|
|
11665
|
+
const locusPath = join22(projectRoot, ".locus", "LOCUS.md");
|
|
11666
|
+
if (existsSync22(locusPath)) {
|
|
11318
11667
|
const content = readFileSync13(locusPath, "utf-8");
|
|
11319
11668
|
parts.push(`<project-context>
|
|
11320
11669
|
${content.slice(0, 3000)}
|
|
11321
11670
|
</project-context>`);
|
|
11322
11671
|
}
|
|
11323
|
-
const learningsPath =
|
|
11324
|
-
if (
|
|
11672
|
+
const learningsPath = join22(projectRoot, ".locus", "LEARNINGS.md");
|
|
11673
|
+
if (existsSync22(learningsPath)) {
|
|
11325
11674
|
const content = readFileSync13(learningsPath, "utf-8");
|
|
11326
11675
|
parts.push(`<past-learnings>
|
|
11327
11676
|
${content.slice(0, 2000)}
|
|
@@ -11501,10 +11850,10 @@ var exports_review = {};
|
|
|
11501
11850
|
__export(exports_review, {
|
|
11502
11851
|
reviewCommand: () => reviewCommand
|
|
11503
11852
|
});
|
|
11504
|
-
import { execFileSync as execFileSync2, execSync as
|
|
11505
|
-
import { existsSync as
|
|
11506
|
-
import { join as
|
|
11507
|
-
function
|
|
11853
|
+
import { execFileSync as execFileSync2, execSync as execSync17 } from "node:child_process";
|
|
11854
|
+
import { existsSync as existsSync23, readFileSync as readFileSync14 } from "node:fs";
|
|
11855
|
+
import { join as join23 } from "node:path";
|
|
11856
|
+
function printHelp3() {
|
|
11508
11857
|
process.stderr.write(`
|
|
11509
11858
|
${bold2("locus review")} — AI-powered code review
|
|
11510
11859
|
|
|
@@ -11527,7 +11876,7 @@ ${bold2("Examples:")}
|
|
|
11527
11876
|
}
|
|
11528
11877
|
async function reviewCommand(projectRoot, args, flags = {}) {
|
|
11529
11878
|
if (args[0] === "help") {
|
|
11530
|
-
|
|
11879
|
+
printHelp3();
|
|
11531
11880
|
return;
|
|
11532
11881
|
}
|
|
11533
11882
|
const config = loadConfig(projectRoot);
|
|
@@ -11587,7 +11936,7 @@ ${bold2("Review complete:")} ${green(`✓ ${reviewed}`)}${failed > 0 ? ` ${red2(
|
|
|
11587
11936
|
async function reviewSinglePR(projectRoot, config, prNumber, focus, flags) {
|
|
11588
11937
|
let prInfo;
|
|
11589
11938
|
try {
|
|
11590
|
-
const result =
|
|
11939
|
+
const result = execSync17(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
11591
11940
|
const raw = JSON.parse(result);
|
|
11592
11941
|
prInfo = {
|
|
11593
11942
|
number: raw.number,
|
|
@@ -11671,8 +12020,8 @@ function buildReviewPrompt(projectRoot, config, pr, diff, focus) {
|
|
|
11671
12020
|
parts.push(`<role>
|
|
11672
12021
|
You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.
|
|
11673
12022
|
</role>`);
|
|
11674
|
-
const locusPath =
|
|
11675
|
-
if (
|
|
12023
|
+
const locusPath = join23(projectRoot, ".locus", "LOCUS.md");
|
|
12024
|
+
if (existsSync23(locusPath)) {
|
|
11676
12025
|
const content = readFileSync14(locusPath, "utf-8");
|
|
11677
12026
|
parts.push(`<project-context>
|
|
11678
12027
|
${content.slice(0, 2000)}
|
|
@@ -11734,8 +12083,8 @@ var exports_iterate = {};
|
|
|
11734
12083
|
__export(exports_iterate, {
|
|
11735
12084
|
iterateCommand: () => iterateCommand
|
|
11736
12085
|
});
|
|
11737
|
-
import { execSync as
|
|
11738
|
-
function
|
|
12086
|
+
import { execSync as execSync18 } from "node:child_process";
|
|
12087
|
+
function printHelp4() {
|
|
11739
12088
|
process.stderr.write(`
|
|
11740
12089
|
${bold2("locus iterate")} — Re-execute tasks with PR feedback
|
|
11741
12090
|
|
|
@@ -11760,7 +12109,7 @@ ${bold2("Examples:")}
|
|
|
11760
12109
|
}
|
|
11761
12110
|
async function iterateCommand(projectRoot, args, flags = {}) {
|
|
11762
12111
|
if (args[0] === "help") {
|
|
11763
|
-
|
|
12112
|
+
printHelp4();
|
|
11764
12113
|
return;
|
|
11765
12114
|
}
|
|
11766
12115
|
const config = loadConfig(projectRoot);
|
|
@@ -11952,12 +12301,12 @@ ${bold2("Summary:")} ${green(`✓ ${succeeded}`)}${failed > 0 ? ` ${red2(`✗ ${
|
|
|
11952
12301
|
}
|
|
11953
12302
|
function findPRForIssue(projectRoot, issueNumber) {
|
|
11954
12303
|
try {
|
|
11955
|
-
const result =
|
|
12304
|
+
const result = execSync18(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
11956
12305
|
const parsed = JSON.parse(result);
|
|
11957
12306
|
if (parsed.length > 0) {
|
|
11958
12307
|
return parsed[0].number;
|
|
11959
12308
|
}
|
|
11960
|
-
const branchResult =
|
|
12309
|
+
const branchResult = execSync18(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
11961
12310
|
const branchParsed = JSON.parse(branchResult);
|
|
11962
12311
|
if (branchParsed.length > 0) {
|
|
11963
12312
|
return branchParsed[0].number;
|
|
@@ -11993,15 +12342,15 @@ __export(exports_discuss, {
|
|
|
11993
12342
|
discussCommand: () => discussCommand
|
|
11994
12343
|
});
|
|
11995
12344
|
import {
|
|
11996
|
-
existsSync as
|
|
11997
|
-
mkdirSync as
|
|
12345
|
+
existsSync as existsSync24,
|
|
12346
|
+
mkdirSync as mkdirSync16,
|
|
11998
12347
|
readdirSync as readdirSync9,
|
|
11999
12348
|
readFileSync as readFileSync15,
|
|
12000
12349
|
unlinkSync as unlinkSync6,
|
|
12001
|
-
writeFileSync as
|
|
12350
|
+
writeFileSync as writeFileSync11
|
|
12002
12351
|
} from "node:fs";
|
|
12003
|
-
import { join as
|
|
12004
|
-
function
|
|
12352
|
+
import { join as join24 } from "node:path";
|
|
12353
|
+
function printHelp5() {
|
|
12005
12354
|
process.stderr.write(`
|
|
12006
12355
|
${bold2("locus discuss")} — AI-powered architectural discussions
|
|
12007
12356
|
|
|
@@ -12022,12 +12371,12 @@ ${bold2("Examples:")}
|
|
|
12022
12371
|
`);
|
|
12023
12372
|
}
|
|
12024
12373
|
function getDiscussionsDir(projectRoot) {
|
|
12025
|
-
return
|
|
12374
|
+
return join24(projectRoot, ".locus", "discussions");
|
|
12026
12375
|
}
|
|
12027
12376
|
function ensureDiscussionsDir(projectRoot) {
|
|
12028
12377
|
const dir = getDiscussionsDir(projectRoot);
|
|
12029
|
-
if (!
|
|
12030
|
-
|
|
12378
|
+
if (!existsSync24(dir)) {
|
|
12379
|
+
mkdirSync16(dir, { recursive: true });
|
|
12031
12380
|
}
|
|
12032
12381
|
return dir;
|
|
12033
12382
|
}
|
|
@@ -12036,7 +12385,7 @@ function generateId2() {
|
|
|
12036
12385
|
}
|
|
12037
12386
|
async function discussCommand(projectRoot, args, flags = {}) {
|
|
12038
12387
|
if (args[0] === "help") {
|
|
12039
|
-
|
|
12388
|
+
printHelp5();
|
|
12040
12389
|
return;
|
|
12041
12390
|
}
|
|
12042
12391
|
const subcommand = args[0];
|
|
@@ -12053,7 +12402,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
|
|
|
12053
12402
|
return deleteDiscussion(projectRoot, args[1]);
|
|
12054
12403
|
}
|
|
12055
12404
|
if (args.length === 0) {
|
|
12056
|
-
|
|
12405
|
+
printHelp5();
|
|
12057
12406
|
return;
|
|
12058
12407
|
}
|
|
12059
12408
|
const topic = args.join(" ").trim();
|
|
@@ -12061,7 +12410,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
|
|
|
12061
12410
|
}
|
|
12062
12411
|
function listDiscussions(projectRoot) {
|
|
12063
12412
|
const dir = getDiscussionsDir(projectRoot);
|
|
12064
|
-
if (!
|
|
12413
|
+
if (!existsSync24(dir)) {
|
|
12065
12414
|
process.stderr.write(`${dim2("No discussions yet.")}
|
|
12066
12415
|
`);
|
|
12067
12416
|
return;
|
|
@@ -12078,7 +12427,7 @@ ${bold2("Discussions:")}
|
|
|
12078
12427
|
`);
|
|
12079
12428
|
for (const file of files) {
|
|
12080
12429
|
const id = file.replace(".md", "");
|
|
12081
|
-
const content = readFileSync15(
|
|
12430
|
+
const content = readFileSync15(join24(dir, file), "utf-8");
|
|
12082
12431
|
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
12083
12432
|
const title = titleMatch ? titleMatch[1] : id;
|
|
12084
12433
|
const dateMatch = content.match(/\*\*Date:\*\*\s*(.+)/);
|
|
@@ -12096,7 +12445,7 @@ function showDiscussion(projectRoot, id) {
|
|
|
12096
12445
|
return;
|
|
12097
12446
|
}
|
|
12098
12447
|
const dir = getDiscussionsDir(projectRoot);
|
|
12099
|
-
if (!
|
|
12448
|
+
if (!existsSync24(dir)) {
|
|
12100
12449
|
process.stderr.write(`${red2("✗")} No discussions found.
|
|
12101
12450
|
`);
|
|
12102
12451
|
return;
|
|
@@ -12108,7 +12457,7 @@ function showDiscussion(projectRoot, id) {
|
|
|
12108
12457
|
`);
|
|
12109
12458
|
return;
|
|
12110
12459
|
}
|
|
12111
|
-
const content = readFileSync15(
|
|
12460
|
+
const content = readFileSync15(join24(dir, match), "utf-8");
|
|
12112
12461
|
process.stdout.write(`${content}
|
|
12113
12462
|
`);
|
|
12114
12463
|
}
|
|
@@ -12119,7 +12468,7 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
12119
12468
|
return;
|
|
12120
12469
|
}
|
|
12121
12470
|
const dir = getDiscussionsDir(projectRoot);
|
|
12122
|
-
if (!
|
|
12471
|
+
if (!existsSync24(dir)) {
|
|
12123
12472
|
process.stderr.write(`${red2("✗")} No discussions found.
|
|
12124
12473
|
`);
|
|
12125
12474
|
return;
|
|
@@ -12131,7 +12480,7 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
12131
12480
|
`);
|
|
12132
12481
|
return;
|
|
12133
12482
|
}
|
|
12134
|
-
unlinkSync6(
|
|
12483
|
+
unlinkSync6(join24(dir, match));
|
|
12135
12484
|
process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
|
|
12136
12485
|
`);
|
|
12137
12486
|
}
|
|
@@ -12144,7 +12493,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
|
|
|
12144
12493
|
return;
|
|
12145
12494
|
}
|
|
12146
12495
|
const dir = getDiscussionsDir(projectRoot);
|
|
12147
|
-
if (!
|
|
12496
|
+
if (!existsSync24(dir)) {
|
|
12148
12497
|
process.stderr.write(`${red2("✗")} No discussions found.
|
|
12149
12498
|
`);
|
|
12150
12499
|
return;
|
|
@@ -12156,7 +12505,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
|
|
|
12156
12505
|
`);
|
|
12157
12506
|
return;
|
|
12158
12507
|
}
|
|
12159
|
-
const content = readFileSync15(
|
|
12508
|
+
const content = readFileSync15(join24(dir, match), "utf-8");
|
|
12160
12509
|
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
12161
12510
|
const discussionTitle = titleMatch ? titleMatch[1].trim() : id;
|
|
12162
12511
|
await planCommand(projectRoot, [
|
|
@@ -12281,7 +12630,7 @@ ${turn.content}`;
|
|
|
12281
12630
|
...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
|
|
12282
12631
|
].join(`
|
|
12283
12632
|
`);
|
|
12284
|
-
|
|
12633
|
+
writeFileSync11(join24(dir, `${id}.md`), markdown, "utf-8");
|
|
12285
12634
|
process.stderr.write(`
|
|
12286
12635
|
${green("✓")} Discussion saved: ${cyan2(id)} ${dim2(`(${timer.formatted()})`)}
|
|
12287
12636
|
`);
|
|
@@ -12296,15 +12645,15 @@ function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFi
|
|
|
12296
12645
|
parts.push(`<role>
|
|
12297
12646
|
You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.
|
|
12298
12647
|
</role>`);
|
|
12299
|
-
const locusPath =
|
|
12300
|
-
if (
|
|
12648
|
+
const locusPath = join24(projectRoot, ".locus", "LOCUS.md");
|
|
12649
|
+
if (existsSync24(locusPath)) {
|
|
12301
12650
|
const content = readFileSync15(locusPath, "utf-8");
|
|
12302
12651
|
parts.push(`<project-context>
|
|
12303
12652
|
${content.slice(0, 3000)}
|
|
12304
12653
|
</project-context>`);
|
|
12305
12654
|
}
|
|
12306
|
-
const learningsPath =
|
|
12307
|
-
if (
|
|
12655
|
+
const learningsPath = join24(projectRoot, ".locus", "LEARNINGS.md");
|
|
12656
|
+
if (existsSync24(learningsPath)) {
|
|
12308
12657
|
const content = readFileSync15(learningsPath, "utf-8");
|
|
12309
12658
|
parts.push(`<past-learnings>
|
|
12310
12659
|
${content.slice(0, 2000)}
|
|
@@ -12376,9 +12725,9 @@ __export(exports_artifacts, {
|
|
|
12376
12725
|
formatDate: () => formatDate2,
|
|
12377
12726
|
artifactsCommand: () => artifactsCommand
|
|
12378
12727
|
});
|
|
12379
|
-
import { existsSync as
|
|
12380
|
-
import { join as
|
|
12381
|
-
function
|
|
12728
|
+
import { existsSync as existsSync25, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
|
|
12729
|
+
import { join as join25 } from "node:path";
|
|
12730
|
+
function printHelp6() {
|
|
12382
12731
|
process.stderr.write(`
|
|
12383
12732
|
${bold2("locus artifacts")} — View and manage AI-generated artifacts
|
|
12384
12733
|
|
|
@@ -12397,14 +12746,14 @@ ${dim2("Artifact names support partial matching.")}
|
|
|
12397
12746
|
`);
|
|
12398
12747
|
}
|
|
12399
12748
|
function getArtifactsDir(projectRoot) {
|
|
12400
|
-
return
|
|
12749
|
+
return join25(projectRoot, ".locus", "artifacts");
|
|
12401
12750
|
}
|
|
12402
12751
|
function listArtifacts(projectRoot) {
|
|
12403
12752
|
const dir = getArtifactsDir(projectRoot);
|
|
12404
|
-
if (!
|
|
12753
|
+
if (!existsSync25(dir))
|
|
12405
12754
|
return [];
|
|
12406
12755
|
return readdirSync10(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
|
|
12407
|
-
const filePath =
|
|
12756
|
+
const filePath = join25(dir, fileName);
|
|
12408
12757
|
const stat = statSync5(filePath);
|
|
12409
12758
|
return {
|
|
12410
12759
|
name: fileName.replace(/\.md$/, ""),
|
|
@@ -12417,8 +12766,8 @@ function listArtifacts(projectRoot) {
|
|
|
12417
12766
|
function readArtifact(projectRoot, name) {
|
|
12418
12767
|
const dir = getArtifactsDir(projectRoot);
|
|
12419
12768
|
const fileName = name.endsWith(".md") ? name : `${name}.md`;
|
|
12420
|
-
const filePath =
|
|
12421
|
-
if (!
|
|
12769
|
+
const filePath = join25(dir, fileName);
|
|
12770
|
+
if (!existsSync25(filePath))
|
|
12422
12771
|
return null;
|
|
12423
12772
|
const stat = statSync5(filePath);
|
|
12424
12773
|
return {
|
|
@@ -12450,7 +12799,7 @@ function formatDate2(date) {
|
|
|
12450
12799
|
}
|
|
12451
12800
|
async function artifactsCommand(projectRoot, args) {
|
|
12452
12801
|
if (args[0] === "help") {
|
|
12453
|
-
|
|
12802
|
+
printHelp6();
|
|
12454
12803
|
return;
|
|
12455
12804
|
}
|
|
12456
12805
|
const subcommand = args[0];
|
|
@@ -12587,10 +12936,10 @@ __export(exports_sandbox2, {
|
|
|
12587
12936
|
parseSandboxLogsArgs: () => parseSandboxLogsArgs,
|
|
12588
12937
|
parseSandboxInstallArgs: () => parseSandboxInstallArgs
|
|
12589
12938
|
});
|
|
12590
|
-
import { execSync as
|
|
12939
|
+
import { execSync as execSync19, spawn as spawn7 } from "node:child_process";
|
|
12591
12940
|
import { createHash } from "node:crypto";
|
|
12592
|
-
import { existsSync as
|
|
12593
|
-
import { basename as basename4, join as
|
|
12941
|
+
import { existsSync as existsSync26, readFileSync as readFileSync17 } from "node:fs";
|
|
12942
|
+
import { basename as basename4, join as join26 } from "node:path";
|
|
12594
12943
|
import { createInterface as createInterface3 } from "node:readline";
|
|
12595
12944
|
function printSandboxHelp() {
|
|
12596
12945
|
process.stderr.write(`
|
|
@@ -12799,7 +13148,7 @@ function handleRemove(projectRoot) {
|
|
|
12799
13148
|
process.stderr.write(`Removing sandbox ${bold2(sandboxName)}...
|
|
12800
13149
|
`);
|
|
12801
13150
|
try {
|
|
12802
|
-
|
|
13151
|
+
execSync19(`docker sandbox rm ${sandboxName}`, {
|
|
12803
13152
|
encoding: "utf-8",
|
|
12804
13153
|
stdio: ["pipe", "pipe", "pipe"],
|
|
12805
13154
|
timeout: 15000
|
|
@@ -13048,7 +13397,7 @@ async function handleLogs(projectRoot, args) {
|
|
|
13048
13397
|
}
|
|
13049
13398
|
function detectPackageManager2(projectRoot) {
|
|
13050
13399
|
try {
|
|
13051
|
-
const raw = readFileSync17(
|
|
13400
|
+
const raw = readFileSync17(join26(projectRoot, "package.json"), "utf-8");
|
|
13052
13401
|
const pkgJson = JSON.parse(raw);
|
|
13053
13402
|
if (typeof pkgJson.packageManager === "string") {
|
|
13054
13403
|
const name = pkgJson.packageManager.split("@")[0];
|
|
@@ -13057,13 +13406,13 @@ function detectPackageManager2(projectRoot) {
|
|
|
13057
13406
|
}
|
|
13058
13407
|
}
|
|
13059
13408
|
} catch {}
|
|
13060
|
-
if (
|
|
13409
|
+
if (existsSync26(join26(projectRoot, "bun.lock")) || existsSync26(join26(projectRoot, "bun.lockb"))) {
|
|
13061
13410
|
return "bun";
|
|
13062
13411
|
}
|
|
13063
|
-
if (
|
|
13412
|
+
if (existsSync26(join26(projectRoot, "yarn.lock"))) {
|
|
13064
13413
|
return "yarn";
|
|
13065
13414
|
}
|
|
13066
|
-
if (
|
|
13415
|
+
if (existsSync26(join26(projectRoot, "pnpm-lock.yaml"))) {
|
|
13067
13416
|
return "pnpm";
|
|
13068
13417
|
}
|
|
13069
13418
|
return "npm";
|
|
@@ -13112,8 +13461,8 @@ Installing dependencies (${bold2(installCmd.join(" "))}) in sandbox ${dim2(sandb
|
|
|
13112
13461
|
${dim2(`Detected ${ecosystem} project — skipping JS package install.`)}
|
|
13113
13462
|
`);
|
|
13114
13463
|
}
|
|
13115
|
-
const setupScript =
|
|
13116
|
-
if (
|
|
13464
|
+
const setupScript = join26(projectRoot, ".locus", "sandbox-setup.sh");
|
|
13465
|
+
if (existsSync26(setupScript)) {
|
|
13117
13466
|
process.stderr.write(`Running ${bold2(".locus/sandbox-setup.sh")} in sandbox ${dim2(sandboxName)}...
|
|
13118
13467
|
`);
|
|
13119
13468
|
const hookOk = await runInteractiveCommand("docker", [
|
|
@@ -13201,7 +13550,7 @@ function runInteractiveCommand(command, args) {
|
|
|
13201
13550
|
}
|
|
13202
13551
|
async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
13203
13552
|
try {
|
|
13204
|
-
|
|
13553
|
+
execSync19(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
|
|
13205
13554
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13206
13555
|
timeout: 120000
|
|
13207
13556
|
});
|
|
@@ -13222,7 +13571,7 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
|
13222
13571
|
}
|
|
13223
13572
|
async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
13224
13573
|
try {
|
|
13225
|
-
|
|
13574
|
+
execSync19(`docker sandbox exec ${sandboxName} which ${pm}`, {
|
|
13226
13575
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13227
13576
|
timeout: 5000
|
|
13228
13577
|
});
|
|
@@ -13231,7 +13580,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
|
13231
13580
|
process.stderr.write(`Installing ${bold2(pm)} in sandbox...
|
|
13232
13581
|
`);
|
|
13233
13582
|
try {
|
|
13234
|
-
|
|
13583
|
+
execSync19(`docker sandbox exec ${sandboxName} npm install -g ${npmPkg}`, {
|
|
13235
13584
|
stdio: "inherit",
|
|
13236
13585
|
timeout: 120000
|
|
13237
13586
|
});
|
|
@@ -13243,7 +13592,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
|
13243
13592
|
}
|
|
13244
13593
|
async function ensureCodexInSandbox(sandboxName) {
|
|
13245
13594
|
try {
|
|
13246
|
-
|
|
13595
|
+
execSync19(`docker sandbox exec ${sandboxName} which codex`, {
|
|
13247
13596
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13248
13597
|
timeout: 5000
|
|
13249
13598
|
});
|
|
@@ -13251,7 +13600,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
13251
13600
|
process.stderr.write(`Installing codex in sandbox...
|
|
13252
13601
|
`);
|
|
13253
13602
|
try {
|
|
13254
|
-
|
|
13603
|
+
execSync19(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
|
|
13255
13604
|
} catch {
|
|
13256
13605
|
process.stderr.write(`${red2("✗")} Failed to install codex in sandbox.
|
|
13257
13606
|
`);
|
|
@@ -13260,7 +13609,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
13260
13609
|
}
|
|
13261
13610
|
function isSandboxAlive(name) {
|
|
13262
13611
|
try {
|
|
13263
|
-
const output =
|
|
13612
|
+
const output = execSync19("docker sandbox ls", {
|
|
13264
13613
|
encoding: "utf-8",
|
|
13265
13614
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13266
13615
|
timeout: 5000
|
|
@@ -13286,13 +13635,13 @@ init_context();
|
|
|
13286
13635
|
init_logger();
|
|
13287
13636
|
init_rate_limiter();
|
|
13288
13637
|
init_terminal();
|
|
13289
|
-
import { existsSync as
|
|
13290
|
-
import { join as
|
|
13638
|
+
import { existsSync as existsSync27, readFileSync as readFileSync18 } from "node:fs";
|
|
13639
|
+
import { join as join27 } from "node:path";
|
|
13291
13640
|
import { fileURLToPath } from "node:url";
|
|
13292
13641
|
function getCliVersion() {
|
|
13293
13642
|
const fallbackVersion = "0.0.0";
|
|
13294
|
-
const packageJsonPath =
|
|
13295
|
-
if (!
|
|
13643
|
+
const packageJsonPath = join27(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
|
|
13644
|
+
if (!existsSync27(packageJsonPath)) {
|
|
13296
13645
|
return fallbackVersion;
|
|
13297
13646
|
}
|
|
13298
13647
|
try {
|
|
@@ -13442,7 +13791,7 @@ function printLogo() {
|
|
|
13442
13791
|
`);
|
|
13443
13792
|
}
|
|
13444
13793
|
}
|
|
13445
|
-
function
|
|
13794
|
+
function printHelp7() {
|
|
13446
13795
|
printLogo();
|
|
13447
13796
|
process.stderr.write(`
|
|
13448
13797
|
|
|
@@ -13463,6 +13812,7 @@ ${bold2("Commands:")}
|
|
|
13463
13812
|
${cyan2("status")} Dashboard view of current state
|
|
13464
13813
|
${cyan2("config")} View and manage settings
|
|
13465
13814
|
${cyan2("logs")} View, tail, and manage execution logs
|
|
13815
|
+
${cyan2("create")} ${dim2("<name>")} Scaffold a new community package
|
|
13466
13816
|
${cyan2("install")} Install a community package
|
|
13467
13817
|
${cyan2("uninstall")} Remove an installed package
|
|
13468
13818
|
${cyan2("packages")} Manage installed packages (list, outdated)
|
|
@@ -13546,7 +13896,7 @@ async function main() {
|
|
|
13546
13896
|
process.exit(0);
|
|
13547
13897
|
}
|
|
13548
13898
|
if (parsed.flags.help && !parsed.command) {
|
|
13549
|
-
|
|
13899
|
+
printHelp7();
|
|
13550
13900
|
process.exit(0);
|
|
13551
13901
|
}
|
|
13552
13902
|
const command = resolveAlias(parsed.command);
|
|
@@ -13557,7 +13907,7 @@ async function main() {
|
|
|
13557
13907
|
try {
|
|
13558
13908
|
const root = getGitRoot(cwd);
|
|
13559
13909
|
if (isInitialized(root)) {
|
|
13560
|
-
logDir =
|
|
13910
|
+
logDir = join27(root, ".locus", "logs");
|
|
13561
13911
|
getRateLimiter(root);
|
|
13562
13912
|
}
|
|
13563
13913
|
} catch {}
|
|
@@ -13578,7 +13928,7 @@ async function main() {
|
|
|
13578
13928
|
printVersionNotice = startVersionCheck2(VERSION);
|
|
13579
13929
|
}
|
|
13580
13930
|
if (!command) {
|
|
13581
|
-
|
|
13931
|
+
printHelp7();
|
|
13582
13932
|
process.exit(0);
|
|
13583
13933
|
}
|
|
13584
13934
|
if (command === "init") {
|
|
@@ -13587,6 +13937,13 @@ async function main() {
|
|
|
13587
13937
|
logger.destroy();
|
|
13588
13938
|
return;
|
|
13589
13939
|
}
|
|
13940
|
+
if (command === "create") {
|
|
13941
|
+
const { createCommand: createCommand2 } = await Promise.resolve().then(() => (init_create(), exports_create));
|
|
13942
|
+
const createArgs = parsed.flags.help ? ["help"] : parsed.args;
|
|
13943
|
+
await createCommand2(createArgs);
|
|
13944
|
+
logger.destroy();
|
|
13945
|
+
return;
|
|
13946
|
+
}
|
|
13590
13947
|
if (command === "install") {
|
|
13591
13948
|
if (parsed.flags.list) {
|
|
13592
13949
|
const { packagesCommand: packagesCommand2 } = await Promise.resolve().then(() => (init_packages(), exports_packages));
|