@locusai/cli 0.22.0 → 0.22.2

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.
Files changed (2) hide show
  1. package/bin/locus.js +768 -345
  2. 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: "claude-sonnet-4-6"
32
+ model: "opus"
33
33
  },
34
34
  agent: {
35
35
  maxParallel: 3,
@@ -896,9 +896,10 @@ __export(exports_sandbox, {
896
896
  getModelSandboxName: () => getModelSandboxName,
897
897
  displaySandboxWarning: () => displaySandboxWarning,
898
898
  detectSandboxSupport: () => detectSandboxSupport,
899
+ detectContainerWorkdir: () => detectContainerWorkdir,
899
900
  checkProviderSandboxMismatch: () => checkProviderSandboxMismatch
900
901
  });
901
- import { execFile } from "node:child_process";
902
+ import { execFile, execSync as execSync2 } from "node:child_process";
902
903
  import { createInterface } from "node:readline";
903
904
  function getProviderSandboxName(config, provider) {
904
905
  return config.providers[provider];
@@ -1033,6 +1034,34 @@ ${yellow2("⚠")} Docker sandbox not available. Install Docker Desktop 4.58+ fo
1033
1034
  }
1034
1035
  return true;
1035
1036
  }
1037
+ function detectContainerWorkdir(sandboxName, hostProjectRoot) {
1038
+ const log = getLogger();
1039
+ try {
1040
+ execSync2(`docker sandbox exec ${sandboxName} test -d ${JSON.stringify(hostProjectRoot)}`, { stdio: ["pipe", "pipe", "pipe"], timeout: 5000 });
1041
+ log.debug("Container workdir matches host path", { hostProjectRoot });
1042
+ return null;
1043
+ } catch {}
1044
+ try {
1045
+ const result = execSync2(`docker sandbox exec ${sandboxName} find / -maxdepth 5 -path '*/.locus/config.json' -type f 2>/dev/null`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 }).trim();
1046
+ if (result) {
1047
+ const configPath = result.split(`
1048
+ `)[0].trim();
1049
+ const workdir = configPath.replace(/\/.locus\/config\.json$/, "");
1050
+ if (workdir) {
1051
+ log.debug("Detected container workdir", {
1052
+ hostProjectRoot,
1053
+ containerWorkdir: workdir
1054
+ });
1055
+ return workdir;
1056
+ }
1057
+ }
1058
+ } catch (err) {
1059
+ log.debug("Container workdir probe failed", {
1060
+ error: err instanceof Error ? err.message : String(err)
1061
+ });
1062
+ }
1063
+ return null;
1064
+ }
1036
1065
  function waitForEnter() {
1037
1066
  return new Promise((resolve) => {
1038
1067
  const rl = createInterface({
@@ -1062,7 +1091,7 @@ __export(exports_upgrade, {
1062
1091
  fetchLatestVersion: () => fetchLatestVersion,
1063
1092
  compareSemver: () => compareSemver
1064
1093
  });
1065
- import { execSync as execSync2 } from "node:child_process";
1094
+ import { execSync as execSync3 } from "node:child_process";
1066
1095
  function compareSemver(a, b) {
1067
1096
  const partsA = a.replace(/^v/, "").split(".").map(Number);
1068
1097
  const partsB = b.replace(/^v/, "").split(".").map(Number);
@@ -1078,7 +1107,7 @@ function compareSemver(a, b) {
1078
1107
  }
1079
1108
  function fetchLatestVersion() {
1080
1109
  try {
1081
- const result = execSync2(`npm view ${PACKAGE_NAME} version`, {
1110
+ const result = execSync3(`npm view ${PACKAGE_NAME} version`, {
1082
1111
  encoding: "utf-8",
1083
1112
  stdio: ["pipe", "pipe", "pipe"],
1084
1113
  timeout: 15000
@@ -1090,14 +1119,14 @@ function fetchLatestVersion() {
1090
1119
  }
1091
1120
  function installVersion(version) {
1092
1121
  try {
1093
- execSync2("npm cache clean --force", {
1122
+ execSync3("npm cache clean --force", {
1094
1123
  encoding: "utf-8",
1095
1124
  stdio: ["pipe", "pipe", "pipe"],
1096
1125
  timeout: 30000
1097
1126
  });
1098
1127
  } catch {}
1099
1128
  try {
1100
- execSync2(`npm install -g ${PACKAGE_NAME}@${version}`, {
1129
+ execSync3(`npm install -g ${PACKAGE_NAME}@${version}`, {
1101
1130
  encoding: "utf-8",
1102
1131
  stdio: ["pipe", "pipe", "pipe"],
1103
1132
  timeout: 120000
@@ -1109,7 +1138,7 @@ function installVersion(version) {
1109
1138
  }
1110
1139
  function verifyInstalled(expectedVersion) {
1111
1140
  try {
1112
- const installed = execSync2(`npm list -g ${PACKAGE_NAME} --depth=0 --json`, {
1141
+ const installed = execSync3(`npm list -g ${PACKAGE_NAME} --depth=0 --json`, {
1113
1142
  encoding: "utf-8",
1114
1143
  stdio: ["pipe", "pipe", "pipe"],
1115
1144
  timeout: 1e4
@@ -1473,7 +1502,7 @@ var init_ecosystem = __esm(() => {
1473
1502
  // src/core/github.ts
1474
1503
  import {
1475
1504
  execFileSync,
1476
- execSync as execSync3
1505
+ execSync as execSync4
1477
1506
  } from "node:child_process";
1478
1507
  function gh(args, options = {}) {
1479
1508
  const log = getLogger();
@@ -1482,7 +1511,7 @@ function gh(args, options = {}) {
1482
1511
  log.debug(`gh ${args}`, { cwd });
1483
1512
  const startTime = Date.now();
1484
1513
  try {
1485
- const result = execSync3(`gh ${args}`, {
1514
+ const result = execSync4(`gh ${args}`, {
1486
1515
  cwd,
1487
1516
  encoding: "utf-8",
1488
1517
  stdio: ["pipe", "pipe", "pipe"],
@@ -2226,35 +2255,384 @@ var init_init = __esm(() => {
2226
2255
  GITIGNORE_ENTRIES = ["", "# Locus", ".locus/", "!.locus/LEARNINGS.md"];
2227
2256
  });
2228
2257
 
2258
+ // src/commands/create.ts
2259
+ var exports_create = {};
2260
+ __export(exports_create, {
2261
+ createCommand: () => createCommand
2262
+ });
2263
+ import { execSync as execSync5 } from "node:child_process";
2264
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
2265
+ import { join as join7 } from "node:path";
2266
+ function validateName(name) {
2267
+ if (!name)
2268
+ return "Package name is required.";
2269
+ if (!NAME_PATTERN.test(name))
2270
+ return "Name must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.";
2271
+ if (name.startsWith("locus-"))
2272
+ return `Don't include the "locus-" prefix — it's added automatically.`;
2273
+ if (name.length > 50)
2274
+ return "Name must be 50 characters or fewer.";
2275
+ return null;
2276
+ }
2277
+ function capitalize(str) {
2278
+ return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
2279
+ }
2280
+ function checkNpmExists(fullName) {
2281
+ try {
2282
+ execSync5(`npm view ${fullName} version 2>/dev/null`, {
2283
+ stdio: "pipe",
2284
+ timeout: 1e4
2285
+ });
2286
+ return true;
2287
+ } catch {
2288
+ return false;
2289
+ }
2290
+ }
2291
+ function parseCreateArgs(args) {
2292
+ let name = "";
2293
+ let description = "";
2294
+ let i = 0;
2295
+ while (i < args.length) {
2296
+ const arg = args[i];
2297
+ if (arg === "--description" || arg === "-D") {
2298
+ description = args[++i] ?? "";
2299
+ } else if (arg === "help" || arg === "--help" || arg === "-h") {
2300
+ return null;
2301
+ } else if (!arg.startsWith("-")) {
2302
+ name = arg;
2303
+ }
2304
+ i++;
2305
+ }
2306
+ return { name, description };
2307
+ }
2308
+ function generatePackageJson(name, displayName, description, sdkVersion) {
2309
+ const pkg = {
2310
+ name: `@locusai/locus-${name}`,
2311
+ version: "0.1.0",
2312
+ description,
2313
+ type: "module",
2314
+ bin: {
2315
+ [`locus-${name}`]: `./bin/locus-${name}.js`
2316
+ },
2317
+ files: ["bin", "package.json", "README.md"],
2318
+ locus: {
2319
+ displayName,
2320
+ description,
2321
+ commands: [name],
2322
+ version: "0.1.0"
2323
+ },
2324
+ scripts: {
2325
+ build: `bun build src/cli.ts --outfile bin/locus-${name}.js --target node`,
2326
+ typecheck: "tsc --noEmit",
2327
+ lint: "biome lint .",
2328
+ format: "biome format --write ."
2329
+ },
2330
+ dependencies: {
2331
+ "@locusai/sdk": `^${sdkVersion}`
2332
+ },
2333
+ devDependencies: {
2334
+ typescript: "^5.8.3"
2335
+ },
2336
+ keywords: ["locusai-package", "locus", name],
2337
+ engines: {
2338
+ node: ">=18"
2339
+ },
2340
+ license: "MIT"
2341
+ };
2342
+ return `${JSON.stringify(pkg, null, 2)}
2343
+ `;
2344
+ }
2345
+ function generateTsconfig() {
2346
+ const config = {
2347
+ compilerOptions: {
2348
+ target: "ES2022",
2349
+ module: "ESNext",
2350
+ moduleResolution: "bundler",
2351
+ strict: true,
2352
+ skipLibCheck: true,
2353
+ esModuleInterop: true,
2354
+ isolatedModules: true,
2355
+ resolveJsonModule: true,
2356
+ noEmit: true,
2357
+ rootDir: "./src"
2358
+ },
2359
+ include: ["src/**/*"],
2360
+ exclude: ["node_modules", "dist", "bin"]
2361
+ };
2362
+ return `${JSON.stringify(config, null, 2)}
2363
+ `;
2364
+ }
2365
+ function generateCliTs() {
2366
+ return `#!/usr/bin/env node
2367
+
2368
+ import { main } from "./index.js";
2369
+
2370
+ main(process.argv.slice(2)).catch((error) => {
2371
+ console.error(\`Fatal error: \${error.message}\`);
2372
+ process.exit(1);
2373
+ });
2374
+ `;
2375
+ }
2376
+ function generateIndexTs(name) {
2377
+ return `import { createLogger, readLocusConfig } from "@locusai/sdk";
2378
+
2379
+ const logger = createLogger("${name}");
2380
+
2381
+ export async function main(args: string[]): Promise<void> {
2382
+ const command = args[0] ?? "help";
2383
+
2384
+ switch (command) {
2385
+ case "start":
2386
+ return handleStart();
2387
+ case "help":
2388
+ case "--help":
2389
+ case "-h":
2390
+ return printHelp();
2391
+ default:
2392
+ console.error(\`Unknown command: \${command}\`);
2393
+ printHelp();
2394
+ process.exit(1);
2395
+ }
2396
+ }
2397
+
2398
+ // ─── Commands ────────────────────────────────────────────────────────────────
2399
+
2400
+ function handleStart(): void {
2401
+ const config = readLocusConfig();
2402
+ logger.info(\`Hello from locus-${name}! Repo: \${config.github.owner}/\${config.github.repo}\`);
2403
+ // TODO: Implement your package logic here
2404
+ }
2405
+
2406
+ // ─── Help ────────────────────────────────────────────────────────────────────
2407
+
2408
+ function printHelp(): void {
2409
+ console.log(\`
2410
+ locus-${name}
2411
+
2412
+ Usage:
2413
+ locus pkg ${name} <command>
2414
+
2415
+ Commands:
2416
+ start Start the ${name} integration
2417
+ help Show this help message
2418
+ \`);
2419
+ }
2420
+ `;
2421
+ }
2422
+ function generateReadme(name, description) {
2423
+ return `# @locusai/locus-${name}
2424
+
2425
+ ${description}
2426
+
2427
+ ## Installation
2428
+
2429
+ \`\`\`bash
2430
+ locus install ${name}
2431
+ \`\`\`
2432
+
2433
+ ## Usage
2434
+
2435
+ \`\`\`bash
2436
+ locus pkg ${name} start # Start the integration
2437
+ locus pkg ${name} help # Show help
2438
+ \`\`\`
2439
+
2440
+ ## Configuration
2441
+
2442
+ Configure via \`locus config\`:
2443
+
2444
+ \`\`\`bash
2445
+ locus config set packages.${name}.apiKey "your-api-key"
2446
+ \`\`\`
2447
+
2448
+ ## Development
2449
+
2450
+ \`\`\`bash
2451
+ # Build
2452
+ bun run build
2453
+
2454
+ # Type check
2455
+ bun run typecheck
2456
+
2457
+ # Lint
2458
+ bun run lint
2459
+
2460
+ # Test locally
2461
+ locus pkg ${name}
2462
+ \`\`\`
2463
+
2464
+ ## License
2465
+
2466
+ MIT
2467
+ `;
2468
+ }
2469
+ function printHelp() {
2470
+ process.stderr.write(`
2471
+ ${bold2("locus create")} — Scaffold a new Locus community package
2472
+
2473
+ ${bold2("Usage:")}
2474
+ locus create <name> [options]
2475
+
2476
+ ${bold2("Arguments:")}
2477
+ ${cyan2("<name>")} Package short name (e.g. slack, discord, jira)
2478
+
2479
+ ${bold2("Options:")}
2480
+ ${dim2("--description, -D")} Package description (default: auto-generated)
2481
+ ${dim2("--help, -h")} Show this help
2482
+
2483
+ ${bold2("Examples:")}
2484
+ locus create slack ${dim2("# Create packages/slack/")}
2485
+ locus create discord -D "Control Locus via Discord" ${dim2("# With custom description")}
2486
+
2487
+ ${bold2("What gets created:")}
2488
+ packages/<name>/
2489
+ ├── src/
2490
+ │ ├── cli.ts ${dim2("# Entry point")}
2491
+ │ └── index.ts ${dim2("# Main logic with command dispatch")}
2492
+ ├── package.json ${dim2("# Full config with locus manifest")}
2493
+ ├── tsconfig.json ${dim2("# TypeScript configuration")}
2494
+ └── README.md ${dim2("# Package documentation")}
2495
+
2496
+ ${bold2("Next steps after creation:")}
2497
+ ${gray2("1.")} cd packages/<name>
2498
+ ${gray2("2.")} Implement your logic in src/index.ts
2499
+ ${gray2("3.")} bun install && bun run build
2500
+ ${gray2("4.")} Test locally with: locus pkg <name>
2501
+ ${gray2("5.")} Submit a pull request
2502
+
2503
+ `);
2504
+ }
2505
+ async function createCommand(args) {
2506
+ const parsed = parseCreateArgs(args);
2507
+ if (!parsed || !parsed.name) {
2508
+ printHelp();
2509
+ if (parsed && !parsed.name) {
2510
+ process.stderr.write(`${red2("✗")} Package name is required.
2511
+
2512
+ `);
2513
+ process.stderr.write(` Usage: ${bold2("locus create <name>")}
2514
+
2515
+ `);
2516
+ process.exit(1);
2517
+ }
2518
+ return;
2519
+ }
2520
+ const { name } = parsed;
2521
+ const displayName = capitalize(name);
2522
+ const description = parsed.description || `${displayName} integration for Locus`;
2523
+ const fullNpmName = `@locusai/locus-${name}`;
2524
+ process.stderr.write(`
2525
+ ${bold2("Creating package:")} ${cyan2(fullNpmName)}
2526
+
2527
+ `);
2528
+ const nameError = validateName(name);
2529
+ if (nameError) {
2530
+ process.stderr.write(`${red2("✗")} ${nameError}
2531
+ `);
2532
+ process.exit(1);
2533
+ }
2534
+ process.stderr.write(`${green("✓")} Name is valid: ${bold2(name)}
2535
+ `);
2536
+ const packagesDir = join7(process.cwd(), "packages", name);
2537
+ if (existsSync7(packagesDir)) {
2538
+ process.stderr.write(`${red2("✗")} Directory already exists: ${bold2(`packages/${name}/`)}
2539
+ `);
2540
+ process.exit(1);
2541
+ }
2542
+ process.stderr.write(`${cyan2("●")} Checking npm registry...`);
2543
+ const existsOnNpm = checkNpmExists(fullNpmName);
2544
+ if (existsOnNpm) {
2545
+ process.stderr.write(`\r${yellow2("⚠")} Package ${bold2(fullNpmName)} already exists on npm. You may want to choose a different name.
2546
+ `);
2547
+ } else {
2548
+ process.stderr.write(`\r${green("✓")} Name is available on npm
2549
+ `);
2550
+ }
2551
+ let sdkVersion = "0.22.0";
2552
+ try {
2553
+ const sdkPkgPath = join7(process.cwd(), "packages", "sdk", "package.json");
2554
+ if (existsSync7(sdkPkgPath)) {
2555
+ const { readFileSync: readFileSync5 } = await import("node:fs");
2556
+ const sdkPkg = JSON.parse(readFileSync5(sdkPkgPath, "utf-8"));
2557
+ if (sdkPkg.version)
2558
+ sdkVersion = sdkPkg.version;
2559
+ }
2560
+ } catch {}
2561
+ mkdirSync6(join7(packagesDir, "src"), { recursive: true });
2562
+ mkdirSync6(join7(packagesDir, "bin"), { recursive: true });
2563
+ process.stderr.write(`${green("✓")} Created directory structure
2564
+ `);
2565
+ writeFileSync5(join7(packagesDir, "package.json"), generatePackageJson(name, displayName, description, sdkVersion), "utf-8");
2566
+ process.stderr.write(`${green("✓")} Generated package.json
2567
+ `);
2568
+ writeFileSync5(join7(packagesDir, "tsconfig.json"), generateTsconfig(), "utf-8");
2569
+ process.stderr.write(`${green("✓")} Generated tsconfig.json
2570
+ `);
2571
+ writeFileSync5(join7(packagesDir, "src", "cli.ts"), generateCliTs(), "utf-8");
2572
+ process.stderr.write(`${green("✓")} Generated src/cli.ts
2573
+ `);
2574
+ writeFileSync5(join7(packagesDir, "src", "index.ts"), generateIndexTs(name), "utf-8");
2575
+ process.stderr.write(`${green("✓")} Generated src/index.ts
2576
+ `);
2577
+ writeFileSync5(join7(packagesDir, "README.md"), generateReadme(name, description), "utf-8");
2578
+ process.stderr.write(`${green("✓")} Generated README.md
2579
+ `);
2580
+ process.stderr.write(`
2581
+ ${bold2(green("Package created!"))}
2582
+
2583
+ `);
2584
+ process.stderr.write(`${bold2("Next steps:")}
2585
+ `);
2586
+ process.stderr.write(` ${gray2("1.")} Implement your logic in ${bold2(`packages/${name}/src/index.ts`)}
2587
+ `);
2588
+ process.stderr.write(` ${gray2("2.")} Install dependencies: ${bold2("bun install")}
2589
+ `);
2590
+ process.stderr.write(` ${gray2("3.")} Build your package: ${bold2(`cd packages/${name} && bun run build`)}
2591
+ `);
2592
+ process.stderr.write(` ${gray2("4.")} Test locally: ${bold2(`locus pkg ${name}`)}
2593
+ `);
2594
+ process.stderr.write(` ${gray2("5.")} Submit a pull request
2595
+ `);
2596
+ process.stderr.write(`
2597
+ ${dim2("See the full guide:")} ${cyan2("packages/sdk/PACKAGE_GUIDE.md")}
2598
+
2599
+ `);
2600
+ }
2601
+ var NAME_PATTERN;
2602
+ var init_create = __esm(() => {
2603
+ init_terminal();
2604
+ NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
2605
+ });
2606
+
2229
2607
  // src/packages/registry.ts
2230
2608
  import {
2231
- existsSync as existsSync7,
2232
- mkdirSync as mkdirSync6,
2609
+ existsSync as existsSync8,
2610
+ mkdirSync as mkdirSync7,
2233
2611
  readFileSync as readFileSync5,
2234
2612
  renameSync,
2235
- writeFileSync as writeFileSync5
2613
+ writeFileSync as writeFileSync6
2236
2614
  } from "node:fs";
2237
2615
  import { homedir as homedir2 } from "node:os";
2238
- import { join as join7 } from "node:path";
2616
+ import { join as join8 } from "node:path";
2239
2617
  function getPackagesDir() {
2240
2618
  const home = process.env.HOME || homedir2();
2241
- const dir = join7(home, ".locus", "packages");
2242
- if (!existsSync7(dir)) {
2243
- mkdirSync6(dir, { recursive: true });
2619
+ const dir = join8(home, ".locus", "packages");
2620
+ if (!existsSync8(dir)) {
2621
+ mkdirSync7(dir, { recursive: true });
2244
2622
  }
2245
- const pkgJson = join7(dir, "package.json");
2246
- if (!existsSync7(pkgJson)) {
2247
- writeFileSync5(pkgJson, `${JSON.stringify({ private: true }, null, 2)}
2623
+ const pkgJson = join8(dir, "package.json");
2624
+ if (!existsSync8(pkgJson)) {
2625
+ writeFileSync6(pkgJson, `${JSON.stringify({ private: true }, null, 2)}
2248
2626
  `, "utf-8");
2249
2627
  }
2250
2628
  return dir;
2251
2629
  }
2252
2630
  function getRegistryPath() {
2253
- return join7(getPackagesDir(), "registry.json");
2631
+ return join8(getPackagesDir(), "registry.json");
2254
2632
  }
2255
2633
  function loadRegistry() {
2256
2634
  const registryPath = getRegistryPath();
2257
- if (!existsSync7(registryPath)) {
2635
+ if (!existsSync8(registryPath)) {
2258
2636
  return { packages: {} };
2259
2637
  }
2260
2638
  try {
@@ -2271,7 +2649,7 @@ function loadRegistry() {
2271
2649
  }
2272
2650
  if (pruned) {
2273
2651
  const tmp = `${registryPath}.tmp`;
2274
- writeFileSync5(tmp, `${JSON.stringify(registry, null, 2)}
2652
+ writeFileSync6(tmp, `${JSON.stringify(registry, null, 2)}
2275
2653
  `, "utf-8");
2276
2654
  renameSync(tmp, registryPath);
2277
2655
  }
@@ -2285,15 +2663,15 @@ function loadRegistry() {
2285
2663
  function saveRegistry(registry) {
2286
2664
  const registryPath = getRegistryPath();
2287
2665
  const tmp = `${registryPath}.tmp`;
2288
- writeFileSync5(tmp, `${JSON.stringify(registry, null, 2)}
2666
+ writeFileSync6(tmp, `${JSON.stringify(registry, null, 2)}
2289
2667
  `, "utf-8");
2290
2668
  renameSync(tmp, registryPath);
2291
2669
  }
2292
2670
  function resolvePackageBinary(packageName) {
2293
2671
  const fullName = normalizePackageName(packageName);
2294
2672
  const binName = fullName.includes("/") ? fullName.split("/").pop() : fullName;
2295
- const binPath = join7(getPackagesDir(), "node_modules", ".bin", binName);
2296
- return existsSync7(binPath) ? binPath : null;
2673
+ const binPath = join8(getPackagesDir(), "node_modules", ".bin", binName);
2674
+ return existsSync8(binPath) ? binPath : null;
2297
2675
  }
2298
2676
  function normalizePackageName(input) {
2299
2677
  if (input.startsWith(SCOPED_PREFIX)) {
@@ -2319,7 +2697,7 @@ __export(exports_pkg, {
2319
2697
  listInstalledPackages: () => listInstalledPackages
2320
2698
  });
2321
2699
  import { spawn } from "node:child_process";
2322
- import { existsSync as existsSync8 } from "node:fs";
2700
+ import { existsSync as existsSync9 } from "node:fs";
2323
2701
  function listInstalledPackages() {
2324
2702
  const registry = loadRegistry();
2325
2703
  const entries = Object.values(registry.packages);
@@ -2398,7 +2776,7 @@ async function pkgCommand(args, _flags) {
2398
2776
  return;
2399
2777
  }
2400
2778
  let binaryPath = entry.binaryPath;
2401
- if (!binaryPath || !existsSync8(binaryPath)) {
2779
+ if (!binaryPath || !existsSync9(binaryPath)) {
2402
2780
  const resolved = resolvePackageBinary(packageName);
2403
2781
  if (resolved) {
2404
2782
  binaryPath = resolved;
@@ -2531,8 +2909,8 @@ __export(exports_install, {
2531
2909
  installCommand: () => installCommand
2532
2910
  });
2533
2911
  import { spawnSync as spawnSync2 } from "node:child_process";
2534
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
2535
- import { join as join8 } from "node:path";
2912
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
2913
+ import { join as join9 } from "node:path";
2536
2914
  function parsePackageArg(raw) {
2537
2915
  if (raw.startsWith("@")) {
2538
2916
  const slashIdx = raw.indexOf("/");
@@ -2609,8 +2987,8 @@ ${red2("✗")} Failed to install ${bold2(packageSpec)}.
2609
2987
  process.exit(1);
2610
2988
  return;
2611
2989
  }
2612
- const installedPkgJsonPath = join8(packagesDir, "node_modules", packageName, "package.json");
2613
- if (!existsSync9(installedPkgJsonPath)) {
2990
+ const installedPkgJsonPath = join9(packagesDir, "node_modules", packageName, "package.json");
2991
+ if (!existsSync10(installedPkgJsonPath)) {
2614
2992
  process.stderr.write(`
2615
2993
  ${red2("✗")} Package installed but package.json not found at:
2616
2994
  `);
@@ -2888,16 +3266,16 @@ __export(exports_logs, {
2888
3266
  logsCommand: () => logsCommand
2889
3267
  });
2890
3268
  import {
2891
- existsSync as existsSync10,
3269
+ existsSync as existsSync11,
2892
3270
  readdirSync as readdirSync2,
2893
3271
  readFileSync as readFileSync7,
2894
3272
  statSync as statSync2,
2895
3273
  unlinkSync as unlinkSync2
2896
3274
  } from "node:fs";
2897
- import { join as join9 } from "node:path";
3275
+ import { join as join10 } from "node:path";
2898
3276
  async function logsCommand(cwd, options) {
2899
- const logsDir = join9(cwd, ".locus", "logs");
2900
- if (!existsSync10(logsDir)) {
3277
+ const logsDir = join10(cwd, ".locus", "logs");
3278
+ if (!existsSync11(logsDir)) {
2901
3279
  process.stderr.write(`${dim2("No logs found.")}
2902
3280
  `);
2903
3281
  return;
@@ -2952,8 +3330,8 @@ async function tailLog(logFile, levelFilter) {
2952
3330
  process.stderr.write(`${bold2("Tailing:")} ${dim2(logFile)} ${dim2("(Ctrl+C to stop)")}
2953
3331
 
2954
3332
  `);
2955
- let lastSize = existsSync10(logFile) ? statSync2(logFile).size : 0;
2956
- if (existsSync10(logFile)) {
3333
+ let lastSize = existsSync11(logFile) ? statSync2(logFile).size : 0;
3334
+ if (existsSync11(logFile)) {
2957
3335
  const content = readFileSync7(logFile, "utf-8");
2958
3336
  const lines = content.trim().split(`
2959
3337
  `).filter(Boolean);
@@ -2972,7 +3350,7 @@ async function tailLog(logFile, levelFilter) {
2972
3350
  }
2973
3351
  return new Promise((resolve) => {
2974
3352
  const interval = setInterval(() => {
2975
- if (!existsSync10(logFile))
3353
+ if (!existsSync11(logFile))
2976
3354
  return;
2977
3355
  const currentSize = statSync2(logFile).size;
2978
3356
  if (currentSize <= lastSize)
@@ -3032,7 +3410,7 @@ function cleanLogs(logsDir) {
3032
3410
  `);
3033
3411
  }
3034
3412
  function getLogFiles(logsDir) {
3035
- return readdirSync2(logsDir).filter((f) => f.startsWith("locus-") && f.endsWith(".log")).map((f) => join9(logsDir, f)).sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs);
3413
+ 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
3414
  }
3037
3415
  function formatEntry(entry) {
3038
3416
  const time = dim2(new Date(entry.ts).toLocaleTimeString());
@@ -3341,10 +3719,10 @@ var init_stream_renderer = __esm(() => {
3341
3719
  });
3342
3720
 
3343
3721
  // src/repl/clipboard.ts
3344
- import { execSync as execSync4 } from "node:child_process";
3345
- import { existsSync as existsSync11, mkdirSync as mkdirSync7 } from "node:fs";
3722
+ import { execSync as execSync6 } from "node:child_process";
3723
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8 } from "node:fs";
3346
3724
  import { tmpdir } from "node:os";
3347
- import { join as join10 } from "node:path";
3725
+ import { join as join11 } from "node:path";
3348
3726
  function readClipboardImage() {
3349
3727
  if (process.platform === "darwin") {
3350
3728
  return readMacOSClipboardImage();
@@ -3355,14 +3733,14 @@ function readClipboardImage() {
3355
3733
  return null;
3356
3734
  }
3357
3735
  function ensureStableDir() {
3358
- if (!existsSync11(STABLE_DIR)) {
3359
- mkdirSync7(STABLE_DIR, { recursive: true });
3736
+ if (!existsSync12(STABLE_DIR)) {
3737
+ mkdirSync8(STABLE_DIR, { recursive: true });
3360
3738
  }
3361
3739
  }
3362
3740
  function readMacOSClipboardImage() {
3363
3741
  try {
3364
3742
  ensureStableDir();
3365
- const destPath = join10(STABLE_DIR, `clipboard-${Date.now()}.png`);
3743
+ const destPath = join11(STABLE_DIR, `clipboard-${Date.now()}.png`);
3366
3744
  const script = [
3367
3745
  `set destPath to POSIX file "${destPath}"`,
3368
3746
  "try",
@@ -3380,13 +3758,13 @@ function readMacOSClipboardImage() {
3380
3758
  `return "ok"`
3381
3759
  ].join(`
3382
3760
  `);
3383
- const result = execSync4("osascript", {
3761
+ const result = execSync6("osascript", {
3384
3762
  input: script,
3385
3763
  encoding: "utf-8",
3386
3764
  timeout: 5000,
3387
3765
  stdio: ["pipe", "pipe", "pipe"]
3388
3766
  }).trim();
3389
- if (result === "ok" && existsSync11(destPath)) {
3767
+ if (result === "ok" && existsSync12(destPath)) {
3390
3768
  return destPath;
3391
3769
  }
3392
3770
  } catch {}
@@ -3394,14 +3772,14 @@ function readMacOSClipboardImage() {
3394
3772
  }
3395
3773
  function readLinuxClipboardImage() {
3396
3774
  try {
3397
- const targets = execSync4("xclip -selection clipboard -t TARGETS -o 2>/dev/null", { encoding: "utf-8", timeout: 3000 });
3775
+ const targets = execSync6("xclip -selection clipboard -t TARGETS -o 2>/dev/null", { encoding: "utf-8", timeout: 3000 });
3398
3776
  if (!targets.includes("image/png")) {
3399
3777
  return null;
3400
3778
  }
3401
3779
  ensureStableDir();
3402
- const destPath = join10(STABLE_DIR, `clipboard-${Date.now()}.png`);
3403
- execSync4(`xclip -selection clipboard -t image/png -o > "${destPath}" 2>/dev/null`, { timeout: 5000 });
3404
- if (existsSync11(destPath)) {
3780
+ const destPath = join11(STABLE_DIR, `clipboard-${Date.now()}.png`);
3781
+ execSync6(`xclip -selection clipboard -t image/png -o > "${destPath}" 2>/dev/null`, { timeout: 5000 });
3782
+ if (existsSync12(destPath)) {
3405
3783
  return destPath;
3406
3784
  }
3407
3785
  } catch {}
@@ -3409,13 +3787,13 @@ function readLinuxClipboardImage() {
3409
3787
  }
3410
3788
  var STABLE_DIR;
3411
3789
  var init_clipboard = __esm(() => {
3412
- STABLE_DIR = join10(tmpdir(), "locus-images");
3790
+ STABLE_DIR = join11(tmpdir(), "locus-images");
3413
3791
  });
3414
3792
 
3415
3793
  // src/repl/image-detect.ts
3416
- import { copyFileSync, existsSync as existsSync12, mkdirSync as mkdirSync8 } from "node:fs";
3794
+ import { copyFileSync, existsSync as existsSync13, mkdirSync as mkdirSync9 } from "node:fs";
3417
3795
  import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
3418
- import { basename, extname, join as join11, resolve } from "node:path";
3796
+ import { basename, extname, join as join12, resolve } from "node:path";
3419
3797
  function detectImages(input) {
3420
3798
  const detected = [];
3421
3799
  const byResolved = new Map;
@@ -3509,15 +3887,15 @@ function collectReferencedAttachments(input, attachments) {
3509
3887
  return dedupeByResolvedPath(selected);
3510
3888
  }
3511
3889
  function relocateImages(images, projectRoot) {
3512
- const targetDir = join11(projectRoot, ".locus", "tmp", "images");
3890
+ const targetDir = join12(projectRoot, ".locus", "tmp", "images");
3513
3891
  for (const img of images) {
3514
3892
  if (!img.exists)
3515
3893
  continue;
3516
3894
  try {
3517
- if (!existsSync12(targetDir)) {
3518
- mkdirSync8(targetDir, { recursive: true });
3895
+ if (!existsSync13(targetDir)) {
3896
+ mkdirSync9(targetDir, { recursive: true });
3519
3897
  }
3520
- const dest = join11(targetDir, basename(img.stablePath));
3898
+ const dest = join12(targetDir, basename(img.stablePath));
3521
3899
  copyFileSync(img.stablePath, dest);
3522
3900
  img.stablePath = dest;
3523
3901
  } catch {}
@@ -3529,7 +3907,7 @@ function addIfImage(rawPath, rawMatch, detected, byResolved) {
3529
3907
  return;
3530
3908
  let resolved = stripQuotes(rawPath).replace(/\\ /g, " ");
3531
3909
  if (resolved.startsWith("~/")) {
3532
- resolved = join11(homedir3(), resolved.slice(2));
3910
+ resolved = join12(homedir3(), resolved.slice(2));
3533
3911
  }
3534
3912
  resolved = resolve(resolved);
3535
3913
  const existing = byResolved.get(resolved);
@@ -3542,7 +3920,7 @@ function addIfImage(rawPath, rawMatch, detected, byResolved) {
3542
3920
  ]);
3543
3921
  return;
3544
3922
  }
3545
- const exists = existsSync12(resolved);
3923
+ const exists = existsSync13(resolved);
3546
3924
  let stablePath = resolved;
3547
3925
  if (exists) {
3548
3926
  stablePath = copyToStable(resolved);
@@ -3596,10 +3974,10 @@ function dedupeByResolvedPath(images) {
3596
3974
  }
3597
3975
  function copyToStable(sourcePath) {
3598
3976
  try {
3599
- if (!existsSync12(STABLE_DIR2)) {
3600
- mkdirSync8(STABLE_DIR2, { recursive: true });
3977
+ if (!existsSync13(STABLE_DIR2)) {
3978
+ mkdirSync9(STABLE_DIR2, { recursive: true });
3601
3979
  }
3602
- const dest = join11(STABLE_DIR2, `${Date.now()}-${basename(sourcePath)}`);
3980
+ const dest = join12(STABLE_DIR2, `${Date.now()}-${basename(sourcePath)}`);
3603
3981
  copyFileSync(sourcePath, dest);
3604
3982
  return dest;
3605
3983
  } catch {
@@ -3619,7 +3997,7 @@ var init_image_detect = __esm(() => {
3619
3997
  ".tif",
3620
3998
  ".tiff"
3621
3999
  ]);
3622
- STABLE_DIR2 = join11(tmpdir2(), "locus-images");
4000
+ STABLE_DIR2 = join12(tmpdir2(), "locus-images");
3623
4001
  PLACEHOLDER_ID_PATTERN = /\(locus:\/\/screenshot-(\d+)\)/g;
3624
4002
  });
3625
4003
 
@@ -4421,7 +4799,7 @@ __export(exports_claude, {
4421
4799
  buildClaudeArgs: () => buildClaudeArgs,
4422
4800
  ClaudeRunner: () => ClaudeRunner
4423
4801
  });
4424
- import { execSync as execSync5, spawn as spawn2 } from "node:child_process";
4802
+ import { execSync as execSync7, spawn as spawn2 } from "node:child_process";
4425
4803
  function buildClaudeArgs(options) {
4426
4804
  const args = ["--dangerously-skip-permissions", "--no-session-persistence"];
4427
4805
  if (options.model) {
@@ -4439,7 +4817,7 @@ class ClaudeRunner {
4439
4817
  aborted = false;
4440
4818
  async isAvailable() {
4441
4819
  try {
4442
- execSync5("claude --version", {
4820
+ execSync7("claude --version", {
4443
4821
  encoding: "utf-8",
4444
4822
  stdio: ["pipe", "pipe", "pipe"]
4445
4823
  });
@@ -4450,7 +4828,7 @@ class ClaudeRunner {
4450
4828
  }
4451
4829
  async getVersion() {
4452
4830
  try {
4453
- const output = execSync5("claude --version", {
4831
+ const output = execSync7("claude --version", {
4454
4832
  encoding: "utf-8",
4455
4833
  stdio: ["pipe", "pipe", "pipe"]
4456
4834
  }).trim();
@@ -4617,8 +4995,8 @@ var init_claude = __esm(() => {
4617
4995
  import { exec } from "node:child_process";
4618
4996
  import {
4619
4997
  cpSync,
4620
- existsSync as existsSync13,
4621
- mkdirSync as mkdirSync9,
4998
+ existsSync as existsSync14,
4999
+ mkdirSync as mkdirSync10,
4622
5000
  mkdtempSync,
4623
5001
  readdirSync as readdirSync3,
4624
5002
  readFileSync as readFileSync8,
@@ -4626,10 +5004,10 @@ import {
4626
5004
  statSync as statSync3
4627
5005
  } from "node:fs";
4628
5006
  import { tmpdir as tmpdir3 } from "node:os";
4629
- import { dirname as dirname3, join as join12, relative } from "node:path";
5007
+ import { dirname as dirname3, join as join13, relative } from "node:path";
4630
5008
  import { promisify } from "node:util";
4631
5009
  function parseIgnoreFile(filePath) {
4632
- if (!existsSync13(filePath))
5010
+ if (!existsSync14(filePath))
4633
5011
  return [];
4634
5012
  const content = readFileSync8(filePath, "utf-8");
4635
5013
  const rules = [];
@@ -4699,7 +5077,7 @@ function findIgnoredPaths(projectRoot, rules) {
4699
5077
  for (const name of entries) {
4700
5078
  if (SKIP_DIRS.has(name))
4701
5079
  continue;
4702
- const fullPath = join12(dir, name);
5080
+ const fullPath = join13(dir, name);
4703
5081
  let stat = null;
4704
5082
  try {
4705
5083
  stat = statSync3(fullPath);
@@ -4733,7 +5111,7 @@ function findIgnoredPaths(projectRoot, rules) {
4733
5111
  }
4734
5112
  function backupIgnoredFiles(projectRoot) {
4735
5113
  const log = getLogger();
4736
- const ignorePath = join12(projectRoot, ".sandboxignore");
5114
+ const ignorePath = join13(projectRoot, ".sandboxignore");
4737
5115
  const rules = parseIgnoreFile(ignorePath);
4738
5116
  if (rules.length === 0)
4739
5117
  return NOOP_BACKUP;
@@ -4742,7 +5120,7 @@ function backupIgnoredFiles(projectRoot) {
4742
5120
  return NOOP_BACKUP;
4743
5121
  let backupDir;
4744
5122
  try {
4745
- backupDir = mkdtempSync(join12(tmpdir3(), "locus-sandbox-backup-"));
5123
+ backupDir = mkdtempSync(join13(tmpdir3(), "locus-sandbox-backup-"));
4746
5124
  } catch (err) {
4747
5125
  log.debug("Failed to create sandbox backup dir", {
4748
5126
  error: err instanceof Error ? err.message : String(err)
@@ -4752,9 +5130,9 @@ function backupIgnoredFiles(projectRoot) {
4752
5130
  const backed = [];
4753
5131
  for (const src of paths) {
4754
5132
  const rel = relative(projectRoot, src);
4755
- const dest = join12(backupDir, rel);
5133
+ const dest = join13(backupDir, rel);
4756
5134
  try {
4757
- mkdirSync9(dirname3(dest), { recursive: true });
5135
+ mkdirSync10(dirname3(dest), { recursive: true });
4758
5136
  cpSync(src, dest, { recursive: true, preserveTimestamps: true });
4759
5137
  backed.push({ src, dest });
4760
5138
  } catch (err) {
@@ -4776,7 +5154,7 @@ function backupIgnoredFiles(projectRoot) {
4776
5154
  restore() {
4777
5155
  for (const { src, dest } of backed) {
4778
5156
  try {
4779
- mkdirSync9(dirname3(src), { recursive: true });
5157
+ mkdirSync10(dirname3(src), { recursive: true });
4780
5158
  cpSync(dest, src, { recursive: true, preserveTimestamps: true });
4781
5159
  } catch (err) {
4782
5160
  log.debug("Failed to restore ignored file (potential data loss)", {
@@ -4792,13 +5170,13 @@ function backupIgnoredFiles(projectRoot) {
4792
5170
  }
4793
5171
  };
4794
5172
  }
4795
- async function enforceSandboxIgnore(sandboxName, projectRoot) {
5173
+ async function enforceSandboxIgnore(sandboxName, projectRoot, containerWorkdir) {
4796
5174
  const log = getLogger();
4797
- const ignorePath = join12(projectRoot, ".sandboxignore");
5175
+ const ignorePath = join13(projectRoot, ".sandboxignore");
4798
5176
  const rules = parseIgnoreFile(ignorePath);
4799
5177
  if (rules.length === 0)
4800
5178
  return;
4801
- const script = buildCleanupScript(rules, projectRoot);
5179
+ const script = buildCleanupScript(rules, containerWorkdir ?? projectRoot);
4802
5180
  if (!script)
4803
5181
  return;
4804
5182
  log.debug("Enforcing .sandboxignore", {
@@ -4838,11 +5216,13 @@ import { spawn as spawn3 } from "node:child_process";
4838
5216
 
4839
5217
  class SandboxedClaudeRunner {
4840
5218
  sandboxName;
5219
+ containerWorkdir;
4841
5220
  name = "claude-sandboxed";
4842
5221
  process = null;
4843
5222
  aborted = false;
4844
- constructor(sandboxName) {
5223
+ constructor(sandboxName, containerWorkdir) {
4845
5224
  this.sandboxName = sandboxName;
5225
+ this.containerWorkdir = containerWorkdir;
4846
5226
  }
4847
5227
  async isAvailable() {
4848
5228
  const { ClaudeRunner: ClaudeRunner2 } = await Promise.resolve().then(() => (init_claude(), exports_claude));
@@ -4868,13 +5248,14 @@ class SandboxedClaudeRunner {
4868
5248
  const claudeArgs = ["-p", options.prompt, ...buildClaudeArgs(options)];
4869
5249
  options.onStatusChange?.("Syncing sandbox...");
4870
5250
  const backup = backupIgnoredFiles(options.cwd);
4871
- await enforceSandboxIgnore(this.sandboxName, options.cwd);
5251
+ await enforceSandboxIgnore(this.sandboxName, options.cwd, this.containerWorkdir);
4872
5252
  options.onStatusChange?.("Thinking...");
5253
+ const workdir = this.containerWorkdir ?? options.cwd;
4873
5254
  const dockerArgs = [
4874
5255
  "sandbox",
4875
5256
  "exec",
4876
5257
  "-w",
4877
- options.cwd,
5258
+ workdir,
4878
5259
  this.sandboxName,
4879
5260
  "claude",
4880
5261
  ...claudeArgs
@@ -5039,7 +5420,7 @@ var init_claude_sandbox = __esm(() => {
5039
5420
  });
5040
5421
 
5041
5422
  // src/ai/codex.ts
5042
- import { execSync as execSync6, spawn as spawn4 } from "node:child_process";
5423
+ import { execSync as execSync8, spawn as spawn4 } from "node:child_process";
5043
5424
  function buildCodexArgs(model) {
5044
5425
  const args = ["exec", "--full-auto", "--skip-git-repo-check", "--json"];
5045
5426
  if (model) {
@@ -5055,7 +5436,7 @@ class CodexRunner {
5055
5436
  aborted = false;
5056
5437
  async isAvailable() {
5057
5438
  try {
5058
- execSync6("codex --version", {
5439
+ execSync8("codex --version", {
5059
5440
  encoding: "utf-8",
5060
5441
  stdio: ["pipe", "pipe", "pipe"]
5061
5442
  });
@@ -5066,7 +5447,7 @@ class CodexRunner {
5066
5447
  }
5067
5448
  async getVersion() {
5068
5449
  try {
5069
- const output = execSync6("codex --version", {
5450
+ const output = execSync8("codex --version", {
5070
5451
  encoding: "utf-8",
5071
5452
  stdio: ["pipe", "pipe", "pipe"]
5072
5453
  }).trim();
@@ -5218,12 +5599,14 @@ import { spawn as spawn5 } from "node:child_process";
5218
5599
 
5219
5600
  class SandboxedCodexRunner {
5220
5601
  sandboxName;
5602
+ containerWorkdir;
5221
5603
  name = "codex-sandboxed";
5222
5604
  process = null;
5223
5605
  aborted = false;
5224
5606
  codexInstalled = false;
5225
- constructor(sandboxName) {
5607
+ constructor(sandboxName, containerWorkdir) {
5226
5608
  this.sandboxName = sandboxName;
5609
+ this.containerWorkdir = containerWorkdir;
5227
5610
  }
5228
5611
  async isAvailable() {
5229
5612
  const delegate = new CodexRunner;
@@ -5247,19 +5630,20 @@ class SandboxedCodexRunner {
5247
5630
  const codexArgs = buildCodexArgs(options.model);
5248
5631
  options.onStatusChange?.("Syncing sandbox...");
5249
5632
  const backup = backupIgnoredFiles(options.cwd);
5250
- await enforceSandboxIgnore(this.sandboxName, options.cwd);
5633
+ await enforceSandboxIgnore(this.sandboxName, options.cwd, this.containerWorkdir);
5251
5634
  if (!this.codexInstalled) {
5252
5635
  options.onStatusChange?.("Checking codex...");
5253
5636
  await this.ensureCodexInstalled(this.sandboxName);
5254
5637
  this.codexInstalled = true;
5255
5638
  }
5256
5639
  options.onStatusChange?.("Thinking...");
5640
+ const workdir = this.containerWorkdir ?? options.cwd;
5257
5641
  const dockerArgs = [
5258
5642
  "sandbox",
5259
5643
  "exec",
5260
5644
  "-i",
5261
5645
  "-w",
5262
- options.cwd,
5646
+ workdir,
5263
5647
  this.sandboxName,
5264
5648
  "codex",
5265
5649
  ...codexArgs
@@ -5440,12 +5824,12 @@ async function createRunnerAsync(provider, sandboxed) {
5440
5824
  throw new Error(`Unknown AI provider: ${provider}`);
5441
5825
  }
5442
5826
  }
5443
- function createUserManagedSandboxRunner(provider, sandboxName) {
5827
+ function createUserManagedSandboxRunner(provider, sandboxName, containerWorkdir) {
5444
5828
  switch (provider) {
5445
5829
  case "claude":
5446
- return new SandboxedClaudeRunner(sandboxName);
5830
+ return new SandboxedClaudeRunner(sandboxName, containerWorkdir);
5447
5831
  case "codex":
5448
- return new SandboxedCodexRunner(sandboxName);
5832
+ return new SandboxedCodexRunner(sandboxName, containerWorkdir);
5449
5833
  default:
5450
5834
  throw new Error(`Unknown AI provider: ${provider}`);
5451
5835
  }
@@ -5556,7 +5940,7 @@ ${red2("✗")} ${dim2("Force exit.")}\r
5556
5940
  exitCode: 1
5557
5941
  };
5558
5942
  }
5559
- runner = createUserManagedSandboxRunner(resolvedProvider, options.sandboxName);
5943
+ runner = createUserManagedSandboxRunner(resolvedProvider, options.sandboxName, options.containerWorkdir);
5560
5944
  } else {
5561
5945
  runner = await createRunnerAsync(resolvedProvider, false);
5562
5946
  }
@@ -5891,7 +6275,8 @@ async function issueCreate(projectRoot, parsed) {
5891
6275
  silent: true,
5892
6276
  activity: "generating issue",
5893
6277
  sandboxed: config.sandbox.enabled,
5894
- sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider)
6278
+ sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider),
6279
+ containerWorkdir: config.sandbox.containerWorkdir
5895
6280
  });
5896
6281
  if (!aiResult.success && !aiResult.interrupted) {
5897
6282
  process.stderr.write(`${red2("✗")} Failed to generate issue: ${aiResult.error}
@@ -7094,9 +7479,9 @@ var init_sprint = __esm(() => {
7094
7479
  });
7095
7480
 
7096
7481
  // src/core/prompt-builder.ts
7097
- import { execSync as execSync7 } from "node:child_process";
7098
- import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
7099
- import { join as join13 } from "node:path";
7482
+ import { execSync as execSync9 } from "node:child_process";
7483
+ import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
7484
+ import { join as join14 } from "node:path";
7100
7485
  function buildExecutionPrompt(ctx) {
7101
7486
  const sections = [];
7102
7487
  sections.push(buildSystemContext(ctx.projectRoot));
@@ -7126,13 +7511,13 @@ function buildFeedbackPrompt(ctx) {
7126
7511
  }
7127
7512
  function buildReplPrompt(userMessage, projectRoot, _config, previousMessages) {
7128
7513
  const sections = [];
7129
- const locusmd = readFileSafe(join13(projectRoot, ".locus", "LOCUS.md"));
7514
+ const locusmd = readFileSafe(join14(projectRoot, ".locus", "LOCUS.md"));
7130
7515
  if (locusmd) {
7131
7516
  sections.push(`<project-instructions>
7132
7517
  ${locusmd}
7133
7518
  </project-instructions>`);
7134
7519
  }
7135
- const learnings = readFileSafe(join13(projectRoot, ".locus", "LEARNINGS.md"));
7520
+ const learnings = readFileSafe(join14(projectRoot, ".locus", "LEARNINGS.md"));
7136
7521
  if (learnings) {
7137
7522
  sections.push(`<past-learnings>
7138
7523
  ${learnings}
@@ -7159,24 +7544,24 @@ ${userMessage}
7159
7544
  }
7160
7545
  function buildSystemContext(projectRoot) {
7161
7546
  const parts = [];
7162
- const locusmd = readFileSafe(join13(projectRoot, ".locus", "LOCUS.md"));
7547
+ const locusmd = readFileSafe(join14(projectRoot, ".locus", "LOCUS.md"));
7163
7548
  if (locusmd) {
7164
7549
  parts.push(`<project-instructions>
7165
7550
  ${locusmd}
7166
7551
  </project-instructions>`);
7167
7552
  }
7168
- const learnings = readFileSafe(join13(projectRoot, ".locus", "LEARNINGS.md"));
7553
+ const learnings = readFileSafe(join14(projectRoot, ".locus", "LEARNINGS.md"));
7169
7554
  if (learnings) {
7170
7555
  parts.push(`<past-learnings>
7171
7556
  ${learnings}
7172
7557
  </past-learnings>`);
7173
7558
  }
7174
- const discussionsDir = join13(projectRoot, ".locus", "discussions");
7175
- if (existsSync14(discussionsDir)) {
7559
+ const discussionsDir = join14(projectRoot, ".locus", "discussions");
7560
+ if (existsSync15(discussionsDir)) {
7176
7561
  try {
7177
7562
  const files = readdirSync4(discussionsDir).filter((f) => f.endsWith(".md")).slice(0, 3);
7178
7563
  for (const file of files) {
7179
- const content = readFileSafe(join13(discussionsDir, file));
7564
+ const content = readFileSafe(join14(discussionsDir, file));
7180
7565
  if (content) {
7181
7566
  const name = file.replace(".md", "");
7182
7567
  parts.push(`<discussion name="${name}">
@@ -7243,7 +7628,7 @@ ${parts.join(`
7243
7628
  function buildRepoContext(projectRoot) {
7244
7629
  const parts = [];
7245
7630
  try {
7246
- const tree = execSync7("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();
7631
+ const tree = execSync9("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
7632
  if (tree) {
7248
7633
  parts.push(`<file-tree>
7249
7634
  \`\`\`
@@ -7253,7 +7638,7 @@ ${tree}
7253
7638
  }
7254
7639
  } catch {}
7255
7640
  try {
7256
- const gitLog = execSync7("git log --oneline -10", {
7641
+ const gitLog = execSync9("git log --oneline -10", {
7257
7642
  cwd: projectRoot,
7258
7643
  encoding: "utf-8",
7259
7644
  stdio: ["pipe", "pipe", "pipe"]
@@ -7267,7 +7652,7 @@ ${gitLog}
7267
7652
  }
7268
7653
  } catch {}
7269
7654
  try {
7270
- const branch = execSync7("git rev-parse --abbrev-ref HEAD", {
7655
+ const branch = execSync9("git rev-parse --abbrev-ref HEAD", {
7271
7656
  cwd: projectRoot,
7272
7657
  encoding: "utf-8",
7273
7658
  stdio: ["pipe", "pipe", "pipe"]
@@ -7329,7 +7714,7 @@ function buildFeedbackInstructions() {
7329
7714
  }
7330
7715
  function readFileSafe(path) {
7331
7716
  try {
7332
- if (!existsSync14(path))
7717
+ if (!existsSync15(path))
7333
7718
  return null;
7334
7719
  return readFileSync9(path, "utf-8");
7335
7720
  } catch {
@@ -7523,7 +7908,7 @@ var init_diff_renderer = __esm(() => {
7523
7908
  });
7524
7909
 
7525
7910
  // src/repl/commands.ts
7526
- import { execSync as execSync8 } from "node:child_process";
7911
+ import { execSync as execSync10 } from "node:child_process";
7527
7912
  function getSlashCommands() {
7528
7913
  return [
7529
7914
  {
@@ -7721,7 +8106,7 @@ function cmdModel(args, ctx) {
7721
8106
  }
7722
8107
  function cmdDiff(_args, ctx) {
7723
8108
  try {
7724
- const diff = execSync8("git diff", {
8109
+ const diff = execSync10("git diff", {
7725
8110
  cwd: ctx.projectRoot,
7726
8111
  encoding: "utf-8",
7727
8112
  stdio: ["pipe", "pipe", "pipe"]
@@ -7757,7 +8142,7 @@ function cmdDiff(_args, ctx) {
7757
8142
  }
7758
8143
  function cmdUndo(_args, ctx) {
7759
8144
  try {
7760
- const status = execSync8("git status --porcelain", {
8145
+ const status = execSync10("git status --porcelain", {
7761
8146
  cwd: ctx.projectRoot,
7762
8147
  encoding: "utf-8",
7763
8148
  stdio: ["pipe", "pipe", "pipe"]
@@ -7767,7 +8152,7 @@ function cmdUndo(_args, ctx) {
7767
8152
  `);
7768
8153
  return;
7769
8154
  }
7770
- execSync8("git checkout .", {
8155
+ execSync10("git checkout .", {
7771
8156
  cwd: ctx.projectRoot,
7772
8157
  encoding: "utf-8",
7773
8158
  stdio: ["pipe", "pipe", "pipe"]
@@ -7809,7 +8194,7 @@ var init_commands = __esm(() => {
7809
8194
 
7810
8195
  // src/repl/completions.ts
7811
8196
  import { readdirSync as readdirSync5 } from "node:fs";
7812
- import { basename as basename2, dirname as dirname4, join as join14 } from "node:path";
8197
+ import { basename as basename2, dirname as dirname4, join as join15 } from "node:path";
7813
8198
 
7814
8199
  class SlashCommandCompletion {
7815
8200
  commands;
@@ -7864,7 +8249,7 @@ class FilePathCompletion {
7864
8249
  }
7865
8250
  findMatches(partial) {
7866
8251
  try {
7867
- const dir = partial.includes("/") ? join14(this.projectRoot, dirname4(partial)) : this.projectRoot;
8252
+ const dir = partial.includes("/") ? join15(this.projectRoot, dirname4(partial)) : this.projectRoot;
7868
8253
  const prefix = basename2(partial);
7869
8254
  const entries = readdirSync5(dir, { withFileTypes: true });
7870
8255
  return entries.filter((e) => {
@@ -7900,14 +8285,14 @@ class CombinedCompletion {
7900
8285
  var init_completions = () => {};
7901
8286
 
7902
8287
  // src/repl/input-history.ts
7903
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
7904
- import { dirname as dirname5, join as join15 } from "node:path";
8288
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
8289
+ import { dirname as dirname5, join as join16 } from "node:path";
7905
8290
 
7906
8291
  class InputHistory {
7907
8292
  entries = [];
7908
8293
  filePath;
7909
8294
  constructor(projectRoot) {
7910
- this.filePath = join15(projectRoot, ".locus", "sessions", ".input-history");
8295
+ this.filePath = join16(projectRoot, ".locus", "sessions", ".input-history");
7911
8296
  this.load();
7912
8297
  }
7913
8298
  add(text) {
@@ -7946,7 +8331,7 @@ class InputHistory {
7946
8331
  }
7947
8332
  load() {
7948
8333
  try {
7949
- if (!existsSync15(this.filePath))
8334
+ if (!existsSync16(this.filePath))
7950
8335
  return;
7951
8336
  const content = readFileSync10(this.filePath, "utf-8");
7952
8337
  this.entries = content.split(`
@@ -7956,12 +8341,12 @@ class InputHistory {
7956
8341
  save() {
7957
8342
  try {
7958
8343
  const dir = dirname5(this.filePath);
7959
- if (!existsSync15(dir)) {
7960
- mkdirSync10(dir, { recursive: true });
8344
+ if (!existsSync16(dir)) {
8345
+ mkdirSync11(dir, { recursive: true });
7961
8346
  }
7962
8347
  const content = this.entries.map((e) => this.escape(e)).join(`
7963
8348
  `);
7964
- writeFileSync6(this.filePath, content, "utf-8");
8349
+ writeFileSync7(this.filePath, content, "utf-8");
7965
8350
  } catch {}
7966
8351
  }
7967
8352
  escape(text) {
@@ -7987,21 +8372,21 @@ var init_model_config = __esm(() => {
7987
8372
 
7988
8373
  // src/repl/session-manager.ts
7989
8374
  import {
7990
- existsSync as existsSync16,
7991
- mkdirSync as mkdirSync11,
8375
+ existsSync as existsSync17,
8376
+ mkdirSync as mkdirSync12,
7992
8377
  readdirSync as readdirSync6,
7993
8378
  readFileSync as readFileSync11,
7994
8379
  unlinkSync as unlinkSync3,
7995
- writeFileSync as writeFileSync7
8380
+ writeFileSync as writeFileSync8
7996
8381
  } from "node:fs";
7997
- import { basename as basename3, join as join16 } from "node:path";
8382
+ import { basename as basename3, join as join17 } from "node:path";
7998
8383
 
7999
8384
  class SessionManager {
8000
8385
  sessionsDir;
8001
8386
  constructor(projectRoot) {
8002
- this.sessionsDir = join16(projectRoot, ".locus", "sessions");
8003
- if (!existsSync16(this.sessionsDir)) {
8004
- mkdirSync11(this.sessionsDir, { recursive: true });
8387
+ this.sessionsDir = join17(projectRoot, ".locus", "sessions");
8388
+ if (!existsSync17(this.sessionsDir)) {
8389
+ mkdirSync12(this.sessionsDir, { recursive: true });
8005
8390
  }
8006
8391
  }
8007
8392
  create(options) {
@@ -8026,12 +8411,12 @@ class SessionManager {
8026
8411
  }
8027
8412
  isPersisted(sessionOrId) {
8028
8413
  const sessionId = typeof sessionOrId === "string" ? sessionOrId : sessionOrId.id;
8029
- return existsSync16(this.getSessionPath(sessionId));
8414
+ return existsSync17(this.getSessionPath(sessionId));
8030
8415
  }
8031
8416
  load(idOrPrefix) {
8032
8417
  const files = this.listSessionFiles();
8033
8418
  const exactPath = this.getSessionPath(idOrPrefix);
8034
- if (existsSync16(exactPath)) {
8419
+ if (existsSync17(exactPath)) {
8035
8420
  try {
8036
8421
  return JSON.parse(readFileSync11(exactPath, "utf-8"));
8037
8422
  } catch {
@@ -8054,7 +8439,7 @@ class SessionManager {
8054
8439
  save(session) {
8055
8440
  session.updated = new Date().toISOString();
8056
8441
  const path = this.getSessionPath(session.id);
8057
- writeFileSync7(path, `${JSON.stringify(session, null, 2)}
8442
+ writeFileSync8(path, `${JSON.stringify(session, null, 2)}
8058
8443
  `, "utf-8");
8059
8444
  }
8060
8445
  addMessage(session, message) {
@@ -8081,7 +8466,7 @@ class SessionManager {
8081
8466
  }
8082
8467
  delete(sessionId) {
8083
8468
  const path = this.getSessionPath(sessionId);
8084
- if (existsSync16(path)) {
8469
+ if (existsSync17(path)) {
8085
8470
  unlinkSync3(path);
8086
8471
  return true;
8087
8472
  }
@@ -8111,7 +8496,7 @@ class SessionManager {
8111
8496
  const remaining = withStats.length - pruned;
8112
8497
  if (remaining > MAX_SESSIONS) {
8113
8498
  const toRemove = remaining - MAX_SESSIONS;
8114
- const alive = withStats.filter((e) => existsSync16(e.path));
8499
+ const alive = withStats.filter((e) => existsSync17(e.path));
8115
8500
  for (let i = 0;i < toRemove && i < alive.length; i++) {
8116
8501
  try {
8117
8502
  unlinkSync3(alive[i].path);
@@ -8126,7 +8511,7 @@ class SessionManager {
8126
8511
  }
8127
8512
  listSessionFiles() {
8128
8513
  try {
8129
- return readdirSync6(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join16(this.sessionsDir, f));
8514
+ return readdirSync6(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join17(this.sessionsDir, f));
8130
8515
  } catch {
8131
8516
  return [];
8132
8517
  }
@@ -8135,7 +8520,7 @@ class SessionManager {
8135
8520
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
8136
8521
  }
8137
8522
  getSessionPath(sessionId) {
8138
- return join16(this.sessionsDir, `${sessionId}.json`);
8523
+ return join17(this.sessionsDir, `${sessionId}.json`);
8139
8524
  }
8140
8525
  }
8141
8526
  var MAX_SESSIONS = 50, SESSION_MAX_AGE_MS;
@@ -8145,17 +8530,17 @@ var init_session_manager = __esm(() => {
8145
8530
  });
8146
8531
 
8147
8532
  // src/repl/voice.ts
8148
- import { execSync as execSync9, spawn as spawn6 } from "node:child_process";
8149
- import { existsSync as existsSync17, mkdirSync as mkdirSync12, unlinkSync as unlinkSync4 } from "node:fs";
8533
+ import { execSync as execSync11, spawn as spawn6 } from "node:child_process";
8534
+ import { existsSync as existsSync18, mkdirSync as mkdirSync13, unlinkSync as unlinkSync4 } from "node:fs";
8150
8535
  import { cpus, homedir as homedir4, platform, tmpdir as tmpdir4 } from "node:os";
8151
- import { join as join17 } from "node:path";
8536
+ import { join as join18 } from "node:path";
8152
8537
  function getWhisperModelPath() {
8153
- return join17(WHISPER_MODELS_DIR, `ggml-${WHISPER_MODEL}.bin`);
8538
+ return join18(WHISPER_MODELS_DIR, `ggml-${WHISPER_MODEL}.bin`);
8154
8539
  }
8155
8540
  function commandExists(cmd) {
8156
8541
  try {
8157
8542
  const which = platform() === "win32" ? "where" : "which";
8158
- execSync9(`${which} ${cmd}`, { stdio: "pipe" });
8543
+ execSync11(`${which} ${cmd}`, { stdio: "pipe" });
8159
8544
  return true;
8160
8545
  } catch {
8161
8546
  return false;
@@ -8168,16 +8553,16 @@ function findWhisperBinary() {
8168
8553
  return name;
8169
8554
  }
8170
8555
  for (const name of candidates) {
8171
- const fullPath = join17(LOCUS_BIN_DIR, name);
8172
- if (existsSync17(fullPath))
8556
+ const fullPath = join18(LOCUS_BIN_DIR, name);
8557
+ if (existsSync18(fullPath))
8173
8558
  return fullPath;
8174
8559
  }
8175
8560
  if (platform() === "darwin") {
8176
8561
  const brewDirs = ["/opt/homebrew/bin", "/usr/local/bin"];
8177
8562
  for (const dir of brewDirs) {
8178
8563
  for (const name of candidates) {
8179
- const fullPath = join17(dir, name);
8180
- if (existsSync17(fullPath))
8564
+ const fullPath = join18(dir, name);
8565
+ if (existsSync18(fullPath))
8181
8566
  return fullPath;
8182
8567
  }
8183
8568
  }
@@ -8192,11 +8577,11 @@ function findSoxRecBinary() {
8192
8577
  if (platform() === "darwin") {
8193
8578
  const brewDirs = ["/opt/homebrew/bin", "/usr/local/bin"];
8194
8579
  for (const dir of brewDirs) {
8195
- const recPath = join17(dir, "rec");
8196
- if (existsSync17(recPath))
8580
+ const recPath = join18(dir, "rec");
8581
+ if (existsSync18(recPath))
8197
8582
  return recPath;
8198
- const soxPath = join17(dir, "sox");
8199
- if (existsSync17(soxPath))
8583
+ const soxPath = join18(dir, "sox");
8584
+ if (existsSync18(soxPath))
8200
8585
  return soxPath;
8201
8586
  }
8202
8587
  }
@@ -8205,7 +8590,7 @@ function findSoxRecBinary() {
8205
8590
  function checkDependencies() {
8206
8591
  const soxBinary = findSoxRecBinary();
8207
8592
  const whisperBinary = findWhisperBinary();
8208
- const modelDownloaded = existsSync17(getWhisperModelPath());
8593
+ const modelDownloaded = existsSync18(getWhisperModelPath());
8209
8594
  return {
8210
8595
  sox: soxBinary !== null,
8211
8596
  whisper: whisperBinary !== null,
@@ -8281,22 +8666,22 @@ function installSox(pm) {
8281
8666
  try {
8282
8667
  switch (pm) {
8283
8668
  case "brew":
8284
- execSync9("brew install sox", { stdio: "inherit", timeout: 300000 });
8669
+ execSync11("brew install sox", { stdio: "inherit", timeout: 300000 });
8285
8670
  break;
8286
8671
  case "apt":
8287
- execSync9("sudo apt-get install -y sox", {
8672
+ execSync11("sudo apt-get install -y sox", {
8288
8673
  stdio: "inherit",
8289
8674
  timeout: 300000
8290
8675
  });
8291
8676
  break;
8292
8677
  case "dnf":
8293
- execSync9("sudo dnf install -y sox", {
8678
+ execSync11("sudo dnf install -y sox", {
8294
8679
  stdio: "inherit",
8295
8680
  timeout: 300000
8296
8681
  });
8297
8682
  break;
8298
8683
  case "pacman":
8299
- execSync9("sudo pacman -S --noconfirm sox", {
8684
+ execSync11("sudo pacman -S --noconfirm sox", {
8300
8685
  stdio: "inherit",
8301
8686
  timeout: 300000
8302
8687
  });
@@ -8310,7 +8695,7 @@ function installSox(pm) {
8310
8695
  function installWhisperCpp(pm) {
8311
8696
  if (pm === "brew") {
8312
8697
  try {
8313
- execSync9("brew install whisper-cpp", {
8698
+ execSync11("brew install whisper-cpp", {
8314
8699
  stdio: "inherit",
8315
8700
  timeout: 300000
8316
8701
  });
@@ -8332,19 +8717,19 @@ function ensureBuildDeps(pm) {
8332
8717
  try {
8333
8718
  switch (pm) {
8334
8719
  case "apt":
8335
- execSync9("sudo apt-get install -y cmake g++ make git", {
8720
+ execSync11("sudo apt-get install -y cmake g++ make git", {
8336
8721
  stdio: "inherit",
8337
8722
  timeout: 300000
8338
8723
  });
8339
8724
  break;
8340
8725
  case "dnf":
8341
- execSync9("sudo dnf install -y cmake gcc-c++ make git", {
8726
+ execSync11("sudo dnf install -y cmake gcc-c++ make git", {
8342
8727
  stdio: "inherit",
8343
8728
  timeout: 300000
8344
8729
  });
8345
8730
  break;
8346
8731
  case "pacman":
8347
- execSync9("sudo pacman -S --noconfirm cmake gcc make git", {
8732
+ execSync11("sudo pacman -S --noconfirm cmake gcc make git", {
8348
8733
  stdio: "inherit",
8349
8734
  timeout: 300000
8350
8735
  });
@@ -8359,40 +8744,40 @@ function ensureBuildDeps(pm) {
8359
8744
  }
8360
8745
  function buildWhisperFromSource(pm) {
8361
8746
  const out = process.stderr;
8362
- const buildDir = join17(tmpdir4(), `locus-whisper-build-${process.pid}`);
8747
+ const buildDir = join18(tmpdir4(), `locus-whisper-build-${process.pid}`);
8363
8748
  if (!ensureBuildDeps(pm)) {
8364
8749
  out.write(` ${red2("✗")} Could not install build tools (cmake, g++, git).
8365
8750
  `);
8366
8751
  return false;
8367
8752
  }
8368
8753
  try {
8369
- mkdirSync12(buildDir, { recursive: true });
8370
- mkdirSync12(LOCUS_BIN_DIR, { recursive: true });
8754
+ mkdirSync13(buildDir, { recursive: true });
8755
+ mkdirSync13(LOCUS_BIN_DIR, { recursive: true });
8371
8756
  out.write(` ${dim2("Cloning whisper.cpp...")}
8372
8757
  `);
8373
- execSync9(`git clone --depth 1 https://github.com/ggerganov/whisper.cpp.git "${join17(buildDir, "whisper.cpp")}"`, { stdio: ["pipe", "pipe", "pipe"], timeout: 120000 });
8374
- const srcDir = join17(buildDir, "whisper.cpp");
8758
+ execSync11(`git clone --depth 1 https://github.com/ggerganov/whisper.cpp.git "${join18(buildDir, "whisper.cpp")}"`, { stdio: ["pipe", "pipe", "pipe"], timeout: 120000 });
8759
+ const srcDir = join18(buildDir, "whisper.cpp");
8375
8760
  const numCpus = cpus().length || 2;
8376
8761
  out.write(` ${dim2("Building whisper.cpp (this may take a few minutes)...")}
8377
8762
  `);
8378
- execSync9("cmake -B build -DCMAKE_BUILD_TYPE=Release", {
8763
+ execSync11("cmake -B build -DCMAKE_BUILD_TYPE=Release", {
8379
8764
  cwd: srcDir,
8380
8765
  stdio: ["pipe", "pipe", "pipe"],
8381
8766
  timeout: 120000
8382
8767
  });
8383
- execSync9(`cmake --build build --config Release -j${numCpus}`, {
8768
+ execSync11(`cmake --build build --config Release -j${numCpus}`, {
8384
8769
  cwd: srcDir,
8385
8770
  stdio: ["pipe", "pipe", "pipe"],
8386
8771
  timeout: 600000
8387
8772
  });
8388
- const destPath = join17(LOCUS_BIN_DIR, "whisper-cli");
8773
+ const destPath = join18(LOCUS_BIN_DIR, "whisper-cli");
8389
8774
  const binaryCandidates = [
8390
- join17(srcDir, "build", "bin", "whisper-cli"),
8391
- join17(srcDir, "build", "bin", "main")
8775
+ join18(srcDir, "build", "bin", "whisper-cli"),
8776
+ join18(srcDir, "build", "bin", "main")
8392
8777
  ];
8393
8778
  for (const candidate of binaryCandidates) {
8394
- if (existsSync17(candidate)) {
8395
- execSync9(`cp "${candidate}" "${destPath}" && chmod +x "${destPath}"`, {
8779
+ if (existsSync18(candidate)) {
8780
+ execSync11(`cp "${candidate}" "${destPath}" && chmod +x "${destPath}"`, {
8396
8781
  stdio: "pipe"
8397
8782
  });
8398
8783
  return true;
@@ -8407,7 +8792,7 @@ function buildWhisperFromSource(pm) {
8407
8792
  return false;
8408
8793
  } finally {
8409
8794
  try {
8410
- execSync9(`rm -rf "${buildDir}"`, { stdio: "pipe" });
8795
+ execSync11(`rm -rf "${buildDir}"`, { stdio: "pipe" });
8411
8796
  } catch {}
8412
8797
  }
8413
8798
  }
@@ -8470,20 +8855,20 @@ ${bold2("Installing voice dependencies...")}
8470
8855
  }
8471
8856
  function downloadModel() {
8472
8857
  const modelPath = getWhisperModelPath();
8473
- if (existsSync17(modelPath))
8858
+ if (existsSync18(modelPath))
8474
8859
  return true;
8475
- mkdirSync12(WHISPER_MODELS_DIR, { recursive: true });
8860
+ mkdirSync13(WHISPER_MODELS_DIR, { recursive: true });
8476
8861
  const url = `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/${WHISPER_MODEL === "base.en" ? "ggml-base.en.bin" : `ggml-${WHISPER_MODEL}.bin`}`;
8477
8862
  process.stderr.write(`${dim2("Downloading whisper model")} ${bold2(WHISPER_MODEL)} ${dim2("(~150MB)...")}
8478
8863
  `);
8479
8864
  try {
8480
8865
  if (commandExists("curl")) {
8481
- execSync9(`curl -L -o "${modelPath}" "${url}"`, {
8866
+ execSync11(`curl -L -o "${modelPath}" "${url}"`, {
8482
8867
  stdio: ["pipe", "pipe", "pipe"],
8483
8868
  timeout: 300000
8484
8869
  });
8485
8870
  } else if (commandExists("wget")) {
8486
- execSync9(`wget -O "${modelPath}" "${url}"`, {
8871
+ execSync11(`wget -O "${modelPath}" "${url}"`, {
8487
8872
  stdio: ["pipe", "pipe", "pipe"],
8488
8873
  timeout: 300000
8489
8874
  });
@@ -8517,7 +8902,7 @@ class VoiceController {
8517
8902
  onStateChange;
8518
8903
  constructor(options) {
8519
8904
  this.onStateChange = options.onStateChange;
8520
- this.tempFile = join17(tmpdir4(), `locus-voice-${process.pid}.wav`);
8905
+ this.tempFile = join18(tmpdir4(), `locus-voice-${process.pid}.wav`);
8521
8906
  this.deps = checkDependencies();
8522
8907
  }
8523
8908
  getState() {
@@ -8585,7 +8970,7 @@ ${red2("✗")} Recording failed: ${err.message}\r
8585
8970
  this.recordProcess = null;
8586
8971
  this.setState("idle");
8587
8972
  await sleep2(200);
8588
- if (!existsSync17(this.tempFile)) {
8973
+ if (!existsSync18(this.tempFile)) {
8589
8974
  return null;
8590
8975
  }
8591
8976
  try {
@@ -8676,12 +9061,12 @@ function sleep2(ms) {
8676
9061
  var WHISPER_MODEL = "base.en", WHISPER_MODELS_DIR, LOCUS_BIN_DIR;
8677
9062
  var init_voice = __esm(() => {
8678
9063
  init_terminal();
8679
- WHISPER_MODELS_DIR = join17(homedir4(), ".locus", "whisper-models");
8680
- LOCUS_BIN_DIR = join17(homedir4(), ".locus", "bin");
9064
+ WHISPER_MODELS_DIR = join18(homedir4(), ".locus", "whisper-models");
9065
+ LOCUS_BIN_DIR = join18(homedir4(), ".locus", "bin");
8681
9066
  });
8682
9067
 
8683
9068
  // src/repl/repl.ts
8684
- import { execSync as execSync10 } from "node:child_process";
9069
+ import { execSync as execSync12 } from "node:child_process";
8685
9070
  async function startRepl(options) {
8686
9071
  const { projectRoot, config } = options;
8687
9072
  const sessionManager = new SessionManager(projectRoot);
@@ -8699,7 +9084,7 @@ async function startRepl(options) {
8699
9084
  } else {
8700
9085
  let branch = "main";
8701
9086
  try {
8702
- branch = execSync10("git rev-parse --abbrev-ref HEAD", {
9087
+ branch = execSync12("git rev-parse --abbrev-ref HEAD", {
8703
9088
  cwd: projectRoot,
8704
9089
  encoding: "utf-8",
8705
9090
  stdio: ["pipe", "pipe", "pipe"]
@@ -8751,7 +9136,7 @@ async function runInteractiveRepl(session, sessionManager, options) {
8751
9136
  const provider = inferProviderFromModel(config.ai.model) || config.ai.provider;
8752
9137
  const sandboxName = getProviderSandboxName(config.sandbox, provider);
8753
9138
  if (sandboxName) {
8754
- sandboxRunner = createUserManagedSandboxRunner(provider, sandboxName);
9139
+ sandboxRunner = createUserManagedSandboxRunner(provider, sandboxName, config.sandbox.containerWorkdir);
8755
9140
  process.stderr.write(`${dim2("Using")} ${dim2(provider)} ${dim2("sandbox")} ${dim2(sandboxName)}
8756
9141
  `);
8757
9142
  } else {
@@ -8802,7 +9187,7 @@ async function runInteractiveRepl(session, sessionManager, options) {
8802
9187
  if (providerChanged && config.sandbox.enabled) {
8803
9188
  const sandboxName = getProviderSandboxName(config.sandbox, inferredProvider);
8804
9189
  if (sandboxName) {
8805
- sandboxRunner = createUserManagedSandboxRunner(inferredProvider, sandboxName);
9190
+ sandboxRunner = createUserManagedSandboxRunner(inferredProvider, sandboxName, config.sandbox.containerWorkdir);
8806
9191
  process.stderr.write(`${dim2("Switched sandbox agent to")} ${dim2(inferredProvider)} ${dim2(`(${sandboxName})`)}
8807
9192
  `);
8808
9193
  } else {
@@ -8930,6 +9315,7 @@ async function executeAITurn(prompt, session, options, verbose = false, runner)
8930
9315
  verbose,
8931
9316
  sandboxed: config.sandbox.enabled,
8932
9317
  sandboxName,
9318
+ containerWorkdir: config.sandbox.containerWorkdir,
8933
9319
  runner
8934
9320
  });
8935
9321
  if (aiResult.interrupted) {
@@ -9134,7 +9520,7 @@ async function handleJsonStream(projectRoot, config, args, sessionId) {
9134
9520
  try {
9135
9521
  const fullPrompt = buildReplPrompt(prompt, projectRoot, config);
9136
9522
  const sandboxName = getProviderSandboxName(config.sandbox, config.ai.provider);
9137
- const runner = config.sandbox.enabled ? sandboxName ? createUserManagedSandboxRunner(config.ai.provider, sandboxName) : null : await createRunnerAsync(config.ai.provider, false);
9523
+ const runner = config.sandbox.enabled ? sandboxName ? createUserManagedSandboxRunner(config.ai.provider, sandboxName, config.sandbox.containerWorkdir) : null : await createRunnerAsync(config.ai.provider, false);
9138
9524
  if (!runner) {
9139
9525
  const mismatch = checkProviderSandboxMismatch(config.sandbox, config.ai.model, config.ai.provider);
9140
9526
  stream.emitError(mismatch ?? `No sandbox configured for "${config.ai.provider}". Run "locus sandbox" to create one.`, false);
@@ -9187,11 +9573,11 @@ var init_exec = __esm(() => {
9187
9573
  });
9188
9574
 
9189
9575
  // src/core/submodule.ts
9190
- import { execSync as execSync11 } from "node:child_process";
9191
- import { existsSync as existsSync18 } from "node:fs";
9192
- import { join as join18 } from "node:path";
9576
+ import { execSync as execSync13 } from "node:child_process";
9577
+ import { existsSync as existsSync19 } from "node:fs";
9578
+ import { join as join19 } from "node:path";
9193
9579
  function git2(args, cwd) {
9194
- return execSync11(`git ${args}`, {
9580
+ return execSync13(`git ${args}`, {
9195
9581
  cwd,
9196
9582
  encoding: "utf-8",
9197
9583
  stdio: ["pipe", "pipe", "pipe"]
@@ -9205,7 +9591,7 @@ function gitSafe(args, cwd) {
9205
9591
  }
9206
9592
  }
9207
9593
  function hasSubmodules(cwd) {
9208
- return existsSync18(join18(cwd, ".gitmodules"));
9594
+ return existsSync19(join19(cwd, ".gitmodules"));
9209
9595
  }
9210
9596
  function listSubmodules(cwd) {
9211
9597
  if (!hasSubmodules(cwd))
@@ -9225,7 +9611,7 @@ function listSubmodules(cwd) {
9225
9611
  continue;
9226
9612
  submodules.push({
9227
9613
  path,
9228
- absolutePath: join18(cwd, path),
9614
+ absolutePath: join19(cwd, path),
9229
9615
  dirty
9230
9616
  });
9231
9617
  }
@@ -9238,7 +9624,7 @@ function getDirtySubmodules(cwd) {
9238
9624
  const submodules = listSubmodules(cwd);
9239
9625
  const dirty = [];
9240
9626
  for (const sub of submodules) {
9241
- if (!existsSync18(sub.absolutePath))
9627
+ if (!existsSync19(sub.absolutePath))
9242
9628
  continue;
9243
9629
  const status = gitSafe("status --porcelain", sub.absolutePath);
9244
9630
  if (status && status.trim().length > 0) {
@@ -9259,7 +9645,7 @@ function commitDirtySubmodules(cwd, issueNumber, issueTitle) {
9259
9645
  const message = `chore: complete #${issueNumber} - ${issueTitle}
9260
9646
 
9261
9647
  Co-Authored-By: LocusAgent <agent@locusai.team>`;
9262
- execSync11("git commit -F -", {
9648
+ execSync13("git commit -F -", {
9263
9649
  input: message,
9264
9650
  cwd: sub.absolutePath,
9265
9651
  encoding: "utf-8",
@@ -9325,7 +9711,7 @@ function pushSubmoduleBranches(cwd) {
9325
9711
  const log = getLogger();
9326
9712
  const submodules = listSubmodules(cwd);
9327
9713
  for (const sub of submodules) {
9328
- if (!existsSync18(sub.absolutePath))
9714
+ if (!existsSync19(sub.absolutePath))
9329
9715
  continue;
9330
9716
  const branch = gitSafe("rev-parse --abbrev-ref HEAD", sub.absolutePath)?.trim();
9331
9717
  if (!branch || branch === "HEAD")
@@ -9346,7 +9732,7 @@ var init_submodule = __esm(() => {
9346
9732
  });
9347
9733
 
9348
9734
  // src/core/agent.ts
9349
- import { execSync as execSync12 } from "node:child_process";
9735
+ import { execSync as execSync14 } from "node:child_process";
9350
9736
  async function executeIssue(projectRoot, options) {
9351
9737
  const log = getLogger();
9352
9738
  const timer = createTimer();
@@ -9375,7 +9761,7 @@ ${cyan2("●")} ${bold2(`#${issueNumber}`)} ${issue.title}
9375
9761
  }
9376
9762
  let issueComments = [];
9377
9763
  try {
9378
- const commentsRaw = execSync12(`gh issue view ${issueNumber} --json comments --jq '.comments[].body'`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
9764
+ const commentsRaw = execSync14(`gh issue view ${issueNumber} --json comments --jq '.comments[].body'`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
9379
9765
  if (commentsRaw) {
9380
9766
  issueComments = commentsRaw.split(`
9381
9767
  `).filter(Boolean);
@@ -9412,7 +9798,8 @@ ${yellow2("⚠")} ${bold2("Dry run")} — would execute with:
9412
9798
  cwd: options.worktreePath ?? projectRoot,
9413
9799
  activity: `issue #${issueNumber}`,
9414
9800
  sandboxed: options.sandboxed,
9415
- sandboxName: options.sandboxName
9801
+ sandboxName: options.sandboxName,
9802
+ containerWorkdir: options.containerWorkdir
9416
9803
  });
9417
9804
  const output = aiResult.output;
9418
9805
  if (aiResult.interrupted) {
@@ -9518,7 +9905,8 @@ ${c.body}`),
9518
9905
  cwd: projectRoot,
9519
9906
  activity: `iterating on PR #${prNumber}`,
9520
9907
  sandboxed: config.sandbox.enabled,
9521
- sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider)
9908
+ sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider),
9909
+ containerWorkdir: config.sandbox.containerWorkdir
9522
9910
  });
9523
9911
  if (aiResult.interrupted) {
9524
9912
  process.stderr.write(`
@@ -9539,12 +9927,12 @@ ${aiResult.success ? green("✓") : red2("✗")} Iteration ${aiResult.success ?
9539
9927
  }
9540
9928
  async function createIssuePR(projectRoot, config, issue) {
9541
9929
  try {
9542
- const currentBranch = execSync12("git rev-parse --abbrev-ref HEAD", {
9930
+ const currentBranch = execSync14("git rev-parse --abbrev-ref HEAD", {
9543
9931
  cwd: projectRoot,
9544
9932
  encoding: "utf-8",
9545
9933
  stdio: ["pipe", "pipe", "pipe"]
9546
9934
  }).trim();
9547
- const diff = execSync12(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
9935
+ const diff = execSync14(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
9548
9936
  cwd: projectRoot,
9549
9937
  encoding: "utf-8",
9550
9938
  stdio: ["pipe", "pipe", "pipe"]
@@ -9554,7 +9942,7 @@ async function createIssuePR(projectRoot, config, issue) {
9554
9942
  return;
9555
9943
  }
9556
9944
  pushSubmoduleBranches(projectRoot);
9557
- execSync12(`git push -u origin ${currentBranch}`, {
9945
+ execSync14(`git push -u origin ${currentBranch}`, {
9558
9946
  cwd: projectRoot,
9559
9947
  encoding: "utf-8",
9560
9948
  stdio: ["pipe", "pipe", "pipe"]
@@ -9609,9 +9997,9 @@ var init_agent = __esm(() => {
9609
9997
  });
9610
9998
 
9611
9999
  // src/core/conflict.ts
9612
- import { execSync as execSync13 } from "node:child_process";
10000
+ import { execSync as execSync15 } from "node:child_process";
9613
10001
  function git3(args, cwd) {
9614
- return execSync13(`git ${args}`, {
10002
+ return execSync15(`git ${args}`, {
9615
10003
  cwd,
9616
10004
  encoding: "utf-8",
9617
10005
  stdio: ["pipe", "pipe", "pipe"]
@@ -9740,19 +10128,19 @@ var init_conflict = __esm(() => {
9740
10128
 
9741
10129
  // src/core/run-state.ts
9742
10130
  import {
9743
- existsSync as existsSync19,
9744
- mkdirSync as mkdirSync13,
10131
+ existsSync as existsSync20,
10132
+ mkdirSync as mkdirSync14,
9745
10133
  readFileSync as readFileSync12,
9746
10134
  unlinkSync as unlinkSync5,
9747
- writeFileSync as writeFileSync8
10135
+ writeFileSync as writeFileSync9
9748
10136
  } from "node:fs";
9749
- import { dirname as dirname6, join as join19 } from "node:path";
10137
+ import { dirname as dirname6, join as join20 } from "node:path";
9750
10138
  function getRunStatePath(projectRoot) {
9751
- return join19(projectRoot, ".locus", "run-state.json");
10139
+ return join20(projectRoot, ".locus", "run-state.json");
9752
10140
  }
9753
10141
  function loadRunState(projectRoot) {
9754
10142
  const path = getRunStatePath(projectRoot);
9755
- if (!existsSync19(path))
10143
+ if (!existsSync20(path))
9756
10144
  return null;
9757
10145
  try {
9758
10146
  return JSON.parse(readFileSync12(path, "utf-8"));
@@ -9764,15 +10152,15 @@ function loadRunState(projectRoot) {
9764
10152
  function saveRunState(projectRoot, state) {
9765
10153
  const path = getRunStatePath(projectRoot);
9766
10154
  const dir = dirname6(path);
9767
- if (!existsSync19(dir)) {
9768
- mkdirSync13(dir, { recursive: true });
10155
+ if (!existsSync20(dir)) {
10156
+ mkdirSync14(dir, { recursive: true });
9769
10157
  }
9770
- writeFileSync8(path, `${JSON.stringify(state, null, 2)}
10158
+ writeFileSync9(path, `${JSON.stringify(state, null, 2)}
9771
10159
  `, "utf-8");
9772
10160
  }
9773
10161
  function clearRunState(projectRoot) {
9774
10162
  const path = getRunStatePath(projectRoot);
9775
- if (existsSync19(path)) {
10163
+ if (existsSync20(path)) {
9776
10164
  unlinkSync5(path);
9777
10165
  }
9778
10166
  }
@@ -9912,11 +10300,11 @@ var init_shutdown = __esm(() => {
9912
10300
  });
9913
10301
 
9914
10302
  // src/core/worktree.ts
9915
- import { execSync as execSync14 } from "node:child_process";
9916
- import { existsSync as existsSync20, readdirSync as readdirSync7, realpathSync, statSync as statSync4 } from "node:fs";
9917
- import { join as join20 } from "node:path";
10303
+ import { execSync as execSync16 } from "node:child_process";
10304
+ import { existsSync as existsSync21, readdirSync as readdirSync7, realpathSync, statSync as statSync4 } from "node:fs";
10305
+ import { join as join21 } from "node:path";
9918
10306
  function git4(args, cwd) {
9919
- return execSync14(`git ${args}`, {
10307
+ return execSync16(`git ${args}`, {
9920
10308
  cwd,
9921
10309
  encoding: "utf-8",
9922
10310
  stdio: ["pipe", "pipe", "pipe"]
@@ -9930,10 +10318,10 @@ function gitSafe3(args, cwd) {
9930
10318
  }
9931
10319
  }
9932
10320
  function getWorktreeDir(projectRoot) {
9933
- return join20(projectRoot, ".locus", "worktrees");
10321
+ return join21(projectRoot, ".locus", "worktrees");
9934
10322
  }
9935
10323
  function getWorktreePath(projectRoot, issueNumber) {
9936
- return join20(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
10324
+ return join21(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
9937
10325
  }
9938
10326
  function generateBranchName(issueNumber) {
9939
10327
  const randomSuffix = Math.random().toString(36).slice(2, 8);
@@ -9941,7 +10329,7 @@ function generateBranchName(issueNumber) {
9941
10329
  }
9942
10330
  function getWorktreeBranch(worktreePath) {
9943
10331
  try {
9944
- return execSync14("git branch --show-current", {
10332
+ return execSync16("git branch --show-current", {
9945
10333
  cwd: worktreePath,
9946
10334
  encoding: "utf-8",
9947
10335
  stdio: ["pipe", "pipe", "pipe"]
@@ -9953,7 +10341,7 @@ function getWorktreeBranch(worktreePath) {
9953
10341
  function createWorktree(projectRoot, issueNumber, baseBranch) {
9954
10342
  const log = getLogger();
9955
10343
  const worktreePath = getWorktreePath(projectRoot, issueNumber);
9956
- if (existsSync20(worktreePath)) {
10344
+ if (existsSync21(worktreePath)) {
9957
10345
  log.verbose(`Worktree already exists for issue #${issueNumber}`);
9958
10346
  const existingBranch = getWorktreeBranch(worktreePath) ?? `locus/issue-${issueNumber}`;
9959
10347
  return {
@@ -9981,7 +10369,7 @@ function createWorktree(projectRoot, issueNumber, baseBranch) {
9981
10369
  function removeWorktree(projectRoot, issueNumber) {
9982
10370
  const log = getLogger();
9983
10371
  const worktreePath = getWorktreePath(projectRoot, issueNumber);
9984
- if (!existsSync20(worktreePath)) {
10372
+ if (!existsSync21(worktreePath)) {
9985
10373
  log.verbose(`Worktree for issue #${issueNumber} does not exist`);
9986
10374
  return;
9987
10375
  }
@@ -10000,7 +10388,7 @@ function removeWorktree(projectRoot, issueNumber) {
10000
10388
  function listWorktrees(projectRoot) {
10001
10389
  const log = getLogger();
10002
10390
  const worktreeDir = getWorktreeDir(projectRoot);
10003
- if (!existsSync20(worktreeDir)) {
10391
+ if (!existsSync21(worktreeDir)) {
10004
10392
  return [];
10005
10393
  }
10006
10394
  const entries = readdirSync7(worktreeDir).filter((entry) => entry.startsWith("issue-"));
@@ -10020,7 +10408,7 @@ function listWorktrees(projectRoot) {
10020
10408
  if (!match)
10021
10409
  continue;
10022
10410
  const issueNumber = Number.parseInt(match[1], 10);
10023
- const path = join20(worktreeDir, entry);
10411
+ const path = join21(worktreeDir, entry);
10024
10412
  const branch = getWorktreeBranch(path) ?? `locus/issue-${issueNumber}`;
10025
10413
  let resolvedPath;
10026
10414
  try {
@@ -10068,12 +10456,12 @@ var exports_run = {};
10068
10456
  __export(exports_run, {
10069
10457
  runCommand: () => runCommand
10070
10458
  });
10071
- import { execSync as execSync15 } from "node:child_process";
10459
+ import { execSync as execSync17 } from "node:child_process";
10072
10460
  function resolveExecutionContext(config, modelOverride) {
10073
10461
  const model = modelOverride ?? config.ai.model;
10074
10462
  const provider = inferProviderFromModel(model) ?? config.ai.provider;
10075
10463
  const sandboxName = getModelSandboxName(config.sandbox, model, provider);
10076
- return { provider, model, sandboxName };
10464
+ return { provider, model, sandboxName, containerWorkdir: config.sandbox.containerWorkdir };
10077
10465
  }
10078
10466
  function printRunHelp() {
10079
10467
  process.stderr.write(`
@@ -10228,7 +10616,7 @@ ${yellow2("⚠")} A sprint run is already in progress.
10228
10616
  }
10229
10617
  if (!flags.dryRun) {
10230
10618
  try {
10231
- execSync15(`git checkout -B ${branchName}`, {
10619
+ execSync17(`git checkout -B ${branchName}`, {
10232
10620
  cwd: projectRoot,
10233
10621
  encoding: "utf-8",
10234
10622
  stdio: ["pipe", "pipe", "pipe"]
@@ -10278,7 +10666,7 @@ ${red2("✗")} Auto-rebase failed. Resolve manually.
10278
10666
  let sprintContext;
10279
10667
  if (i > 0 && !flags.dryRun) {
10280
10668
  try {
10281
- sprintContext = execSync15(`git diff origin/${config.agent.baseBranch}..HEAD`, {
10669
+ sprintContext = execSync17(`git diff origin/${config.agent.baseBranch}..HEAD`, {
10282
10670
  cwd: projectRoot,
10283
10671
  encoding: "utf-8",
10284
10672
  stdio: ["pipe", "pipe", "pipe"]
@@ -10299,7 +10687,8 @@ ${progressBar(i, state.tasks.length, { label: "Sprint Progress" })}
10299
10687
  sprintContext,
10300
10688
  skipPR: true,
10301
10689
  sandboxed,
10302
- sandboxName: execution.sandboxName
10690
+ sandboxName: execution.sandboxName,
10691
+ containerWorkdir: execution.containerWorkdir
10303
10692
  });
10304
10693
  if (result.success) {
10305
10694
  if (!flags.dryRun) {
@@ -10343,7 +10732,7 @@ ${bold2("Summary:")}
10343
10732
  const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
10344
10733
  if (prNumber !== undefined) {
10345
10734
  try {
10346
- execSync15(`git checkout ${config.agent.baseBranch}`, {
10735
+ execSync17(`git checkout ${config.agent.baseBranch}`, {
10347
10736
  cwd: projectRoot,
10348
10737
  encoding: "utf-8",
10349
10738
  stdio: ["pipe", "pipe", "pipe"]
@@ -10376,7 +10765,8 @@ ${bold2("Running sprint issue")} ${cyan2(`#${issueNumber}`)} ${dim2("(sequential
10376
10765
  model: execution.model,
10377
10766
  dryRun: flags.dryRun,
10378
10767
  sandboxed,
10379
- sandboxName: execution.sandboxName
10768
+ sandboxName: execution.sandboxName,
10769
+ containerWorkdir: execution.containerWorkdir
10380
10770
  });
10381
10771
  return;
10382
10772
  }
@@ -10388,7 +10778,7 @@ ${bold2("Running issue")} ${cyan2(`#${issueNumber}`)} ${dim2(`(branch: ${branchN
10388
10778
  `);
10389
10779
  if (!flags.dryRun) {
10390
10780
  try {
10391
- execSync15(`git checkout -B ${branchName} ${config.agent.baseBranch}`, {
10781
+ execSync17(`git checkout -B ${branchName} ${config.agent.baseBranch}`, {
10392
10782
  cwd: projectRoot,
10393
10783
  encoding: "utf-8",
10394
10784
  stdio: ["pipe", "pipe", "pipe"]
@@ -10413,7 +10803,7 @@ ${bold2("Running issue")} ${cyan2(`#${issueNumber}`)} ${dim2(`(branch: ${branchN
10413
10803
  if (!flags.dryRun) {
10414
10804
  if (result.success) {
10415
10805
  try {
10416
- execSync15(`git checkout ${config.agent.baseBranch}`, {
10806
+ execSync17(`git checkout ${config.agent.baseBranch}`, {
10417
10807
  cwd: projectRoot,
10418
10808
  encoding: "utf-8",
10419
10809
  stdio: ["pipe", "pipe", "pipe"]
@@ -10484,7 +10874,8 @@ ${bold2("Running")} ${cyan2(`${issueNumbers.length} issues`)} ${dim2(`(max ${max
10484
10874
  model: execution.model,
10485
10875
  dryRun: flags.dryRun,
10486
10876
  sandboxed,
10487
- sandboxName: execution.sandboxName
10877
+ sandboxName: execution.sandboxName,
10878
+ containerWorkdir: execution.containerWorkdir
10488
10879
  });
10489
10880
  if (result.success) {
10490
10881
  markTaskDone(state, issueNumber, result.prNumber);
@@ -10550,13 +10941,13 @@ ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
10550
10941
  `);
10551
10942
  if (state.type === "sprint" && state.branch) {
10552
10943
  try {
10553
- const currentBranch = execSync15("git rev-parse --abbrev-ref HEAD", {
10944
+ const currentBranch = execSync17("git rev-parse --abbrev-ref HEAD", {
10554
10945
  cwd: projectRoot,
10555
10946
  encoding: "utf-8",
10556
10947
  stdio: ["pipe", "pipe", "pipe"]
10557
10948
  }).trim();
10558
10949
  if (currentBranch !== state.branch) {
10559
- execSync15(`git checkout ${state.branch}`, {
10950
+ execSync17(`git checkout ${state.branch}`, {
10560
10951
  cwd: projectRoot,
10561
10952
  encoding: "utf-8",
10562
10953
  stdio: ["pipe", "pipe", "pipe"]
@@ -10584,7 +10975,8 @@ ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
10584
10975
  model: execution.model,
10585
10976
  skipPR: isSprintRun,
10586
10977
  sandboxed,
10587
- sandboxName: execution.sandboxName
10978
+ sandboxName: execution.sandboxName,
10979
+ containerWorkdir: execution.containerWorkdir
10588
10980
  });
10589
10981
  if (result.success) {
10590
10982
  if (isSprintRun) {
@@ -10623,7 +11015,7 @@ ${bold2("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fai
10623
11015
  const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
10624
11016
  if (prNumber !== undefined) {
10625
11017
  try {
10626
- execSync15(`git checkout ${config.agent.baseBranch}`, {
11018
+ execSync17(`git checkout ${config.agent.baseBranch}`, {
10627
11019
  cwd: projectRoot,
10628
11020
  encoding: "utf-8",
10629
11021
  stdio: ["pipe", "pipe", "pipe"]
@@ -10659,14 +11051,14 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
10659
11051
  process.stderr.write(` ${dim2(`Committed submodule changes: ${committedSubmodules.join(", ")}`)}
10660
11052
  `);
10661
11053
  }
10662
- const status = execSync15("git status --porcelain", {
11054
+ const status = execSync17("git status --porcelain", {
10663
11055
  cwd: projectRoot,
10664
11056
  encoding: "utf-8",
10665
11057
  stdio: ["pipe", "pipe", "pipe"]
10666
11058
  }).trim();
10667
11059
  if (!status)
10668
11060
  return;
10669
- execSync15("git add -A", {
11061
+ execSync17("git add -A", {
10670
11062
  cwd: projectRoot,
10671
11063
  encoding: "utf-8",
10672
11064
  stdio: ["pipe", "pipe", "pipe"]
@@ -10674,7 +11066,7 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
10674
11066
  const message = `chore: complete #${issueNumber} - ${issueTitle}
10675
11067
 
10676
11068
  Co-Authored-By: LocusAgent <agent@locusai.team>`;
10677
- execSync15(`git commit -F -`, {
11069
+ execSync17(`git commit -F -`, {
10678
11070
  input: message,
10679
11071
  cwd: projectRoot,
10680
11072
  encoding: "utf-8",
@@ -10688,7 +11080,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
10688
11080
  if (!config.agent.autoPR)
10689
11081
  return;
10690
11082
  try {
10691
- const diff = execSync15(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
11083
+ const diff = execSync17(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
10692
11084
  cwd: projectRoot,
10693
11085
  encoding: "utf-8",
10694
11086
  stdio: ["pipe", "pipe", "pipe"]
@@ -10699,7 +11091,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
10699
11091
  return;
10700
11092
  }
10701
11093
  pushSubmoduleBranches(projectRoot);
10702
- execSync15(`git push -u origin ${branchName}`, {
11094
+ execSync17(`git push -u origin ${branchName}`, {
10703
11095
  cwd: projectRoot,
10704
11096
  encoding: "utf-8",
10705
11097
  stdio: ["pipe", "pipe", "pipe"]
@@ -10857,14 +11249,14 @@ __export(exports_plan, {
10857
11249
  parsePlanArgs: () => parsePlanArgs
10858
11250
  });
10859
11251
  import {
10860
- existsSync as existsSync21,
10861
- mkdirSync as mkdirSync14,
11252
+ existsSync as existsSync22,
11253
+ mkdirSync as mkdirSync15,
10862
11254
  readdirSync as readdirSync8,
10863
11255
  readFileSync as readFileSync13,
10864
- writeFileSync as writeFileSync9
11256
+ writeFileSync as writeFileSync10
10865
11257
  } from "node:fs";
10866
- import { join as join21 } from "node:path";
10867
- function printHelp() {
11258
+ import { join as join22 } from "node:path";
11259
+ function printHelp2() {
10868
11260
  process.stderr.write(`
10869
11261
  ${bold2("locus plan")} — AI-powered sprint planning
10870
11262
 
@@ -10894,12 +11286,12 @@ function normalizeSprintName(name) {
10894
11286
  return name.trim().toLowerCase();
10895
11287
  }
10896
11288
  function getPlansDir(projectRoot) {
10897
- return join21(projectRoot, ".locus", "plans");
11289
+ return join22(projectRoot, ".locus", "plans");
10898
11290
  }
10899
11291
  function ensurePlansDir(projectRoot) {
10900
11292
  const dir = getPlansDir(projectRoot);
10901
- if (!existsSync21(dir)) {
10902
- mkdirSync14(dir, { recursive: true });
11293
+ if (!existsSync22(dir)) {
11294
+ mkdirSync15(dir, { recursive: true });
10903
11295
  }
10904
11296
  return dir;
10905
11297
  }
@@ -10908,14 +11300,14 @@ function generateId() {
10908
11300
  }
10909
11301
  function loadPlanFile(projectRoot, id) {
10910
11302
  const dir = getPlansDir(projectRoot);
10911
- if (!existsSync21(dir))
11303
+ if (!existsSync22(dir))
10912
11304
  return null;
10913
11305
  const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
10914
11306
  const match = files.find((f) => f.startsWith(id));
10915
11307
  if (!match)
10916
11308
  return null;
10917
11309
  try {
10918
- const content = readFileSync13(join21(dir, match), "utf-8");
11310
+ const content = readFileSync13(join22(dir, match), "utf-8");
10919
11311
  return JSON.parse(content);
10920
11312
  } catch {
10921
11313
  return null;
@@ -10923,7 +11315,7 @@ function loadPlanFile(projectRoot, id) {
10923
11315
  }
10924
11316
  async function planCommand(projectRoot, args, flags = {}) {
10925
11317
  if (args[0] === "help" || args.length === 0) {
10926
- printHelp();
11318
+ printHelp2();
10927
11319
  return;
10928
11320
  }
10929
11321
  if (args[0] === "list") {
@@ -10961,7 +11353,7 @@ async function planCommand(projectRoot, args, flags = {}) {
10961
11353
  }
10962
11354
  function handleListPlans(projectRoot) {
10963
11355
  const dir = getPlansDir(projectRoot);
10964
- if (!existsSync21(dir)) {
11356
+ if (!existsSync22(dir)) {
10965
11357
  process.stderr.write(`${dim2("No saved plans yet.")}
10966
11358
  `);
10967
11359
  return;
@@ -10979,7 +11371,7 @@ ${bold2("Saved Plans:")}
10979
11371
  for (const file of files) {
10980
11372
  const id = file.replace(".json", "");
10981
11373
  try {
10982
- const content = readFileSync13(join21(dir, file), "utf-8");
11374
+ const content = readFileSync13(join22(dir, file), "utf-8");
10983
11375
  const plan = JSON.parse(content);
10984
11376
  const date = plan.createdAt ? plan.createdAt.slice(0, 10) : "";
10985
11377
  const issueCount = Array.isArray(plan.issues) ? plan.issues.length : 0;
@@ -11090,7 +11482,7 @@ ${bold2("Approving plan:")}
11090
11482
  async function handleAIPlan(projectRoot, config, directive, sprintName, flags) {
11091
11483
  const id = generateId();
11092
11484
  const plansDir = ensurePlansDir(projectRoot);
11093
- const planPath = join21(plansDir, `${id}.json`);
11485
+ const planPath = join22(plansDir, `${id}.json`);
11094
11486
  const planPathRelative = `.locus/plans/${id}.json`;
11095
11487
  const displayDirective = directive;
11096
11488
  process.stderr.write(`
@@ -11118,7 +11510,8 @@ ${bold2("Planning:")} ${cyan2(displayDirective)}
11118
11510
  cwd: projectRoot,
11119
11511
  activity: "planning",
11120
11512
  sandboxed: config.sandbox.enabled,
11121
- sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
11513
+ sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
11514
+ containerWorkdir: config.sandbox.containerWorkdir
11122
11515
  });
11123
11516
  if (aiResult.interrupted) {
11124
11517
  process.stderr.write(`
@@ -11132,7 +11525,7 @@ ${red2("✗")} Planning failed: ${aiResult.error}
11132
11525
  `);
11133
11526
  return;
11134
11527
  }
11135
- if (!existsSync21(planPath)) {
11528
+ if (!existsSync22(planPath)) {
11136
11529
  process.stderr.write(`
11137
11530
  ${yellow2("⚠")} Plan file was not created at ${bold2(planPathRelative)}.
11138
11531
  `);
@@ -11164,7 +11557,7 @@ ${yellow2("⚠")} Plan file has no issues.
11164
11557
  plan.sprint = sprintName;
11165
11558
  if (!plan.createdAt)
11166
11559
  plan.createdAt = new Date().toISOString();
11167
- writeFileSync9(planPath, JSON.stringify(plan, null, 2), "utf-8");
11560
+ writeFileSync10(planPath, JSON.stringify(plan, null, 2), "utf-8");
11168
11561
  process.stderr.write(`
11169
11562
  ${bold2("Plan saved:")} ${cyan2(id)}
11170
11563
 
@@ -11241,7 +11634,8 @@ Start with foundational/setup tasks, then core features, then integration/testin
11241
11634
  activity: "issue ordering",
11242
11635
  silent: true,
11243
11636
  sandboxed: config.sandbox.enabled,
11244
- sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
11637
+ sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
11638
+ containerWorkdir: config.sandbox.containerWorkdir
11245
11639
  });
11246
11640
  if (aiResult.interrupted) {
11247
11641
  process.stderr.write(`
@@ -11313,15 +11707,15 @@ ${directive}${sprintName ? `
11313
11707
 
11314
11708
  **Sprint:** ${sprintName}` : ""}
11315
11709
  </directive>`);
11316
- const locusPath = join21(projectRoot, ".locus", "LOCUS.md");
11317
- if (existsSync21(locusPath)) {
11710
+ const locusPath = join22(projectRoot, ".locus", "LOCUS.md");
11711
+ if (existsSync22(locusPath)) {
11318
11712
  const content = readFileSync13(locusPath, "utf-8");
11319
11713
  parts.push(`<project-context>
11320
11714
  ${content.slice(0, 3000)}
11321
11715
  </project-context>`);
11322
11716
  }
11323
- const learningsPath = join21(projectRoot, ".locus", "LEARNINGS.md");
11324
- if (existsSync21(learningsPath)) {
11717
+ const learningsPath = join22(projectRoot, ".locus", "LEARNINGS.md");
11718
+ if (existsSync22(learningsPath)) {
11325
11719
  const content = readFileSync13(learningsPath, "utf-8");
11326
11720
  parts.push(`<past-learnings>
11327
11721
  ${content.slice(0, 2000)}
@@ -11501,10 +11895,10 @@ var exports_review = {};
11501
11895
  __export(exports_review, {
11502
11896
  reviewCommand: () => reviewCommand
11503
11897
  });
11504
- import { execFileSync as execFileSync2, execSync as execSync16 } from "node:child_process";
11505
- import { existsSync as existsSync22, readFileSync as readFileSync14 } from "node:fs";
11506
- import { join as join22 } from "node:path";
11507
- function printHelp2() {
11898
+ import { execFileSync as execFileSync2, execSync as execSync18 } from "node:child_process";
11899
+ import { existsSync as existsSync23, readFileSync as readFileSync14 } from "node:fs";
11900
+ import { join as join23 } from "node:path";
11901
+ function printHelp3() {
11508
11902
  process.stderr.write(`
11509
11903
  ${bold2("locus review")} — AI-powered code review
11510
11904
 
@@ -11527,7 +11921,7 @@ ${bold2("Examples:")}
11527
11921
  }
11528
11922
  async function reviewCommand(projectRoot, args, flags = {}) {
11529
11923
  if (args[0] === "help") {
11530
- printHelp2();
11924
+ printHelp3();
11531
11925
  return;
11532
11926
  }
11533
11927
  const config = loadConfig(projectRoot);
@@ -11587,7 +11981,7 @@ ${bold2("Review complete:")} ${green(`✓ ${reviewed}`)}${failed > 0 ? ` ${red2(
11587
11981
  async function reviewSinglePR(projectRoot, config, prNumber, focus, flags) {
11588
11982
  let prInfo;
11589
11983
  try {
11590
- const result = execSync16(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
11984
+ const result = execSync18(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
11591
11985
  const raw = JSON.parse(result);
11592
11986
  prInfo = {
11593
11987
  number: raw.number,
@@ -11632,7 +12026,8 @@ async function reviewPR(projectRoot, config, pr, focus, flags) {
11632
12026
  cwd: projectRoot,
11633
12027
  activity: `PR #${pr.number}`,
11634
12028
  sandboxed: config.sandbox.enabled,
11635
- sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
12029
+ sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
12030
+ containerWorkdir: config.sandbox.containerWorkdir
11636
12031
  });
11637
12032
  if (aiResult.interrupted) {
11638
12033
  process.stderr.write(` ${yellow2("⚡")} Review interrupted.
@@ -11671,8 +12066,8 @@ function buildReviewPrompt(projectRoot, config, pr, diff, focus) {
11671
12066
  parts.push(`<role>
11672
12067
  You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.
11673
12068
  </role>`);
11674
- const locusPath = join22(projectRoot, ".locus", "LOCUS.md");
11675
- if (existsSync22(locusPath)) {
12069
+ const locusPath = join23(projectRoot, ".locus", "LOCUS.md");
12070
+ if (existsSync23(locusPath)) {
11676
12071
  const content = readFileSync14(locusPath, "utf-8");
11677
12072
  parts.push(`<project-context>
11678
12073
  ${content.slice(0, 2000)}
@@ -11734,8 +12129,8 @@ var exports_iterate = {};
11734
12129
  __export(exports_iterate, {
11735
12130
  iterateCommand: () => iterateCommand
11736
12131
  });
11737
- import { execSync as execSync17 } from "node:child_process";
11738
- function printHelp3() {
12132
+ import { execSync as execSync19 } from "node:child_process";
12133
+ function printHelp4() {
11739
12134
  process.stderr.write(`
11740
12135
  ${bold2("locus iterate")} — Re-execute tasks with PR feedback
11741
12136
 
@@ -11760,7 +12155,7 @@ ${bold2("Examples:")}
11760
12155
  }
11761
12156
  async function iterateCommand(projectRoot, args, flags = {}) {
11762
12157
  if (args[0] === "help") {
11763
- printHelp3();
12158
+ printHelp4();
11764
12159
  return;
11765
12160
  }
11766
12161
  const config = loadConfig(projectRoot);
@@ -11952,12 +12347,12 @@ ${bold2("Summary:")} ${green(`✓ ${succeeded}`)}${failed > 0 ? ` ${red2(`✗ ${
11952
12347
  }
11953
12348
  function findPRForIssue(projectRoot, issueNumber) {
11954
12349
  try {
11955
- const result = execSync17(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
12350
+ const result = execSync19(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
11956
12351
  const parsed = JSON.parse(result);
11957
12352
  if (parsed.length > 0) {
11958
12353
  return parsed[0].number;
11959
12354
  }
11960
- const branchResult = execSync17(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
12355
+ const branchResult = execSync19(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
11961
12356
  const branchParsed = JSON.parse(branchResult);
11962
12357
  if (branchParsed.length > 0) {
11963
12358
  return branchParsed[0].number;
@@ -11993,15 +12388,15 @@ __export(exports_discuss, {
11993
12388
  discussCommand: () => discussCommand
11994
12389
  });
11995
12390
  import {
11996
- existsSync as existsSync23,
11997
- mkdirSync as mkdirSync15,
12391
+ existsSync as existsSync24,
12392
+ mkdirSync as mkdirSync16,
11998
12393
  readdirSync as readdirSync9,
11999
12394
  readFileSync as readFileSync15,
12000
12395
  unlinkSync as unlinkSync6,
12001
- writeFileSync as writeFileSync10
12396
+ writeFileSync as writeFileSync11
12002
12397
  } from "node:fs";
12003
- import { join as join23 } from "node:path";
12004
- function printHelp4() {
12398
+ import { join as join24 } from "node:path";
12399
+ function printHelp5() {
12005
12400
  process.stderr.write(`
12006
12401
  ${bold2("locus discuss")} — AI-powered architectural discussions
12007
12402
 
@@ -12022,12 +12417,12 @@ ${bold2("Examples:")}
12022
12417
  `);
12023
12418
  }
12024
12419
  function getDiscussionsDir(projectRoot) {
12025
- return join23(projectRoot, ".locus", "discussions");
12420
+ return join24(projectRoot, ".locus", "discussions");
12026
12421
  }
12027
12422
  function ensureDiscussionsDir(projectRoot) {
12028
12423
  const dir = getDiscussionsDir(projectRoot);
12029
- if (!existsSync23(dir)) {
12030
- mkdirSync15(dir, { recursive: true });
12424
+ if (!existsSync24(dir)) {
12425
+ mkdirSync16(dir, { recursive: true });
12031
12426
  }
12032
12427
  return dir;
12033
12428
  }
@@ -12036,7 +12431,7 @@ function generateId2() {
12036
12431
  }
12037
12432
  async function discussCommand(projectRoot, args, flags = {}) {
12038
12433
  if (args[0] === "help") {
12039
- printHelp4();
12434
+ printHelp5();
12040
12435
  return;
12041
12436
  }
12042
12437
  const subcommand = args[0];
@@ -12053,7 +12448,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
12053
12448
  return deleteDiscussion(projectRoot, args[1]);
12054
12449
  }
12055
12450
  if (args.length === 0) {
12056
- printHelp4();
12451
+ printHelp5();
12057
12452
  return;
12058
12453
  }
12059
12454
  const topic = args.join(" ").trim();
@@ -12061,7 +12456,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
12061
12456
  }
12062
12457
  function listDiscussions(projectRoot) {
12063
12458
  const dir = getDiscussionsDir(projectRoot);
12064
- if (!existsSync23(dir)) {
12459
+ if (!existsSync24(dir)) {
12065
12460
  process.stderr.write(`${dim2("No discussions yet.")}
12066
12461
  `);
12067
12462
  return;
@@ -12078,7 +12473,7 @@ ${bold2("Discussions:")}
12078
12473
  `);
12079
12474
  for (const file of files) {
12080
12475
  const id = file.replace(".md", "");
12081
- const content = readFileSync15(join23(dir, file), "utf-8");
12476
+ const content = readFileSync15(join24(dir, file), "utf-8");
12082
12477
  const titleMatch = content.match(/^#\s+(.+)/m);
12083
12478
  const title = titleMatch ? titleMatch[1] : id;
12084
12479
  const dateMatch = content.match(/\*\*Date:\*\*\s*(.+)/);
@@ -12096,7 +12491,7 @@ function showDiscussion(projectRoot, id) {
12096
12491
  return;
12097
12492
  }
12098
12493
  const dir = getDiscussionsDir(projectRoot);
12099
- if (!existsSync23(dir)) {
12494
+ if (!existsSync24(dir)) {
12100
12495
  process.stderr.write(`${red2("✗")} No discussions found.
12101
12496
  `);
12102
12497
  return;
@@ -12108,7 +12503,7 @@ function showDiscussion(projectRoot, id) {
12108
12503
  `);
12109
12504
  return;
12110
12505
  }
12111
- const content = readFileSync15(join23(dir, match), "utf-8");
12506
+ const content = readFileSync15(join24(dir, match), "utf-8");
12112
12507
  process.stdout.write(`${content}
12113
12508
  `);
12114
12509
  }
@@ -12119,7 +12514,7 @@ function deleteDiscussion(projectRoot, id) {
12119
12514
  return;
12120
12515
  }
12121
12516
  const dir = getDiscussionsDir(projectRoot);
12122
- if (!existsSync23(dir)) {
12517
+ if (!existsSync24(dir)) {
12123
12518
  process.stderr.write(`${red2("✗")} No discussions found.
12124
12519
  `);
12125
12520
  return;
@@ -12131,7 +12526,7 @@ function deleteDiscussion(projectRoot, id) {
12131
12526
  `);
12132
12527
  return;
12133
12528
  }
12134
- unlinkSync6(join23(dir, match));
12529
+ unlinkSync6(join24(dir, match));
12135
12530
  process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
12136
12531
  `);
12137
12532
  }
@@ -12144,7 +12539,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
12144
12539
  return;
12145
12540
  }
12146
12541
  const dir = getDiscussionsDir(projectRoot);
12147
- if (!existsSync23(dir)) {
12542
+ if (!existsSync24(dir)) {
12148
12543
  process.stderr.write(`${red2("✗")} No discussions found.
12149
12544
  `);
12150
12545
  return;
@@ -12156,7 +12551,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
12156
12551
  `);
12157
12552
  return;
12158
12553
  }
12159
- const content = readFileSync15(join23(dir, match), "utf-8");
12554
+ const content = readFileSync15(join24(dir, match), "utf-8");
12160
12555
  const titleMatch = content.match(/^#\s+(.+)/m);
12161
12556
  const discussionTitle = titleMatch ? titleMatch[1].trim() : id;
12162
12557
  await planCommand(projectRoot, [
@@ -12214,7 +12609,8 @@ ${bold2("Discussion:")} ${cyan2(topic)}
12214
12609
  cwd: projectRoot,
12215
12610
  activity: "discussion",
12216
12611
  sandboxed: config.sandbox.enabled,
12217
- sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
12612
+ sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
12613
+ containerWorkdir: config.sandbox.containerWorkdir
12218
12614
  });
12219
12615
  if (aiResult.interrupted) {
12220
12616
  process.stderr.write(`
@@ -12281,7 +12677,7 @@ ${turn.content}`;
12281
12677
  ...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
12282
12678
  ].join(`
12283
12679
  `);
12284
- writeFileSync10(join23(dir, `${id}.md`), markdown, "utf-8");
12680
+ writeFileSync11(join24(dir, `${id}.md`), markdown, "utf-8");
12285
12681
  process.stderr.write(`
12286
12682
  ${green("✓")} Discussion saved: ${cyan2(id)} ${dim2(`(${timer.formatted()})`)}
12287
12683
  `);
@@ -12296,15 +12692,15 @@ function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFi
12296
12692
  parts.push(`<role>
12297
12693
  You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.
12298
12694
  </role>`);
12299
- const locusPath = join23(projectRoot, ".locus", "LOCUS.md");
12300
- if (existsSync23(locusPath)) {
12695
+ const locusPath = join24(projectRoot, ".locus", "LOCUS.md");
12696
+ if (existsSync24(locusPath)) {
12301
12697
  const content = readFileSync15(locusPath, "utf-8");
12302
12698
  parts.push(`<project-context>
12303
12699
  ${content.slice(0, 3000)}
12304
12700
  </project-context>`);
12305
12701
  }
12306
- const learningsPath = join23(projectRoot, ".locus", "LEARNINGS.md");
12307
- if (existsSync23(learningsPath)) {
12702
+ const learningsPath = join24(projectRoot, ".locus", "LEARNINGS.md");
12703
+ if (existsSync24(learningsPath)) {
12308
12704
  const content = readFileSync15(learningsPath, "utf-8");
12309
12705
  parts.push(`<past-learnings>
12310
12706
  ${content.slice(0, 2000)}
@@ -12376,9 +12772,9 @@ __export(exports_artifacts, {
12376
12772
  formatDate: () => formatDate2,
12377
12773
  artifactsCommand: () => artifactsCommand
12378
12774
  });
12379
- import { existsSync as existsSync24, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
12380
- import { join as join24 } from "node:path";
12381
- function printHelp5() {
12775
+ import { existsSync as existsSync25, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
12776
+ import { join as join25 } from "node:path";
12777
+ function printHelp6() {
12382
12778
  process.stderr.write(`
12383
12779
  ${bold2("locus artifacts")} — View and manage AI-generated artifacts
12384
12780
 
@@ -12397,14 +12793,14 @@ ${dim2("Artifact names support partial matching.")}
12397
12793
  `);
12398
12794
  }
12399
12795
  function getArtifactsDir(projectRoot) {
12400
- return join24(projectRoot, ".locus", "artifacts");
12796
+ return join25(projectRoot, ".locus", "artifacts");
12401
12797
  }
12402
12798
  function listArtifacts(projectRoot) {
12403
12799
  const dir = getArtifactsDir(projectRoot);
12404
- if (!existsSync24(dir))
12800
+ if (!existsSync25(dir))
12405
12801
  return [];
12406
12802
  return readdirSync10(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
12407
- const filePath = join24(dir, fileName);
12803
+ const filePath = join25(dir, fileName);
12408
12804
  const stat = statSync5(filePath);
12409
12805
  return {
12410
12806
  name: fileName.replace(/\.md$/, ""),
@@ -12417,8 +12813,8 @@ function listArtifacts(projectRoot) {
12417
12813
  function readArtifact(projectRoot, name) {
12418
12814
  const dir = getArtifactsDir(projectRoot);
12419
12815
  const fileName = name.endsWith(".md") ? name : `${name}.md`;
12420
- const filePath = join24(dir, fileName);
12421
- if (!existsSync24(filePath))
12816
+ const filePath = join25(dir, fileName);
12817
+ if (!existsSync25(filePath))
12422
12818
  return null;
12423
12819
  const stat = statSync5(filePath);
12424
12820
  return {
@@ -12450,7 +12846,7 @@ function formatDate2(date) {
12450
12846
  }
12451
12847
  async function artifactsCommand(projectRoot, args) {
12452
12848
  if (args[0] === "help") {
12453
- printHelp5();
12849
+ printHelp6();
12454
12850
  return;
12455
12851
  }
12456
12852
  const subcommand = args[0];
@@ -12587,10 +12983,10 @@ __export(exports_sandbox2, {
12587
12983
  parseSandboxLogsArgs: () => parseSandboxLogsArgs,
12588
12984
  parseSandboxInstallArgs: () => parseSandboxInstallArgs
12589
12985
  });
12590
- import { execSync as execSync18, spawn as spawn7 } from "node:child_process";
12986
+ import { execSync as execSync20, spawn as spawn7 } from "node:child_process";
12591
12987
  import { createHash } from "node:crypto";
12592
- import { existsSync as existsSync25, readFileSync as readFileSync17 } from "node:fs";
12593
- import { basename as basename4, join as join25 } from "node:path";
12988
+ import { existsSync as existsSync26, readFileSync as readFileSync17 } from "node:fs";
12989
+ import { basename as basename4, join as join26 } from "node:path";
12594
12990
  import { createInterface as createInterface3 } from "node:readline";
12595
12991
  function printSandboxHelp() {
12596
12992
  process.stderr.write(`
@@ -12721,11 +13117,20 @@ async function handleCreate(projectRoot) {
12721
13117
  }
12722
13118
  process.stderr.write(`${green("✓")} ${provider} sandbox created: ${bold2(name)}
12723
13119
  `);
13120
+ if (!config.sandbox.containerWorkdir) {
13121
+ const containerWorkdir = detectContainerWorkdir(name, projectRoot);
13122
+ if (containerWorkdir) {
13123
+ config.sandbox.containerWorkdir = containerWorkdir;
13124
+ process.stderr.write(` ${dim2(`Container workdir: ${containerWorkdir}`)}
13125
+ `);
13126
+ }
13127
+ }
12724
13128
  readySandboxes[provider] = name;
12725
13129
  config.sandbox.enabled = true;
12726
13130
  config.sandbox.providers = readySandboxes;
12727
13131
  saveConfig(projectRoot, config);
12728
- await runSandboxSetup(name, projectRoot);
13132
+ const workdir = config.sandbox.containerWorkdir ?? projectRoot;
13133
+ await runSandboxSetup(name, projectRoot, workdir);
12729
13134
  process.stderr.write(`
12730
13135
  ${green("✓")} Sandbox mode enabled for ${bold2(provider)}.
12731
13136
  `);
@@ -12750,19 +13155,20 @@ async function handleAgentLogin(projectRoot, agent) {
12750
13155
  if (agent === "codex") {
12751
13156
  await ensureCodexInSandbox(sandboxName);
12752
13157
  }
13158
+ const workdir = config.sandbox.containerWorkdir ?? projectRoot;
12753
13159
  process.stderr.write(`Connecting to ${agent} sandbox ${dim2(sandboxName)}...
12754
13160
  `);
12755
13161
  process.stderr.write(`${dim2("Login and then exit when ready.")}
12756
13162
 
12757
13163
  `);
12758
- const child = spawn7("docker", ["sandbox", "exec", "-it", "-w", projectRoot, sandboxName, agent], {
13164
+ const child = spawn7("docker", ["sandbox", "exec", "-it", "-w", workdir, sandboxName, agent], {
12759
13165
  stdio: "inherit"
12760
13166
  });
12761
13167
  await new Promise((resolve2) => {
12762
13168
  child.on("close", async (code) => {
12763
13169
  const backup = backupIgnoredFiles(projectRoot);
12764
13170
  try {
12765
- await enforceSandboxIgnore(sandboxName, projectRoot);
13171
+ await enforceSandboxIgnore(sandboxName, projectRoot, config.sandbox.containerWorkdir);
12766
13172
  } finally {
12767
13173
  backup.restore();
12768
13174
  }
@@ -12799,7 +13205,7 @@ function handleRemove(projectRoot) {
12799
13205
  process.stderr.write(`Removing sandbox ${bold2(sandboxName)}...
12800
13206
  `);
12801
13207
  try {
12802
- execSync18(`docker sandbox rm ${sandboxName}`, {
13208
+ execSync20(`docker sandbox rm ${sandboxName}`, {
12803
13209
  encoding: "utf-8",
12804
13210
  stdio: ["pipe", "pipe", "pipe"],
12805
13211
  timeout: 15000
@@ -12808,6 +13214,7 @@ function handleRemove(projectRoot) {
12808
13214
  }
12809
13215
  config.sandbox.providers = {};
12810
13216
  config.sandbox.enabled = false;
13217
+ delete config.sandbox.containerWorkdir;
12811
13218
  saveConfig(projectRoot, config);
12812
13219
  process.stderr.write(`${green("✓")} Provider sandboxes removed. Sandbox mode disabled.
12813
13220
  `);
@@ -12820,6 +13227,10 @@ ${bold2("Sandbox Status")}
12820
13227
  `);
12821
13228
  process.stderr.write(` ${dim2("Enabled:")} ${config.sandbox.enabled ? green("yes") : red2("no")}
12822
13229
  `);
13230
+ if (config.sandbox.containerWorkdir) {
13231
+ process.stderr.write(` ${dim2("Container workdir:")} ${config.sandbox.containerWorkdir}
13232
+ `);
13233
+ }
12823
13234
  for (const provider of PROVIDERS) {
12824
13235
  const name = config.sandbox.providers[provider];
12825
13236
  process.stderr.write(` ${dim2(`${provider}:`).padEnd(15)}${name ? bold2(name) : dim2("(not configured)")}
@@ -12956,10 +13367,12 @@ async function handleShell(projectRoot, args) {
12956
13367
  `);
12957
13368
  return;
12958
13369
  }
13370
+ const config = loadConfig(projectRoot);
12959
13371
  const sandboxName = getActiveProviderSandbox(projectRoot, provider);
12960
13372
  if (!sandboxName) {
12961
13373
  return;
12962
13374
  }
13375
+ const workdir = config.sandbox.containerWorkdir ?? projectRoot;
12963
13376
  process.stderr.write(`Opening shell in ${provider} sandbox ${dim2(sandboxName)}...
12964
13377
  `);
12965
13378
  await runInteractiveCommand("docker", [
@@ -12967,7 +13380,7 @@ async function handleShell(projectRoot, args) {
12967
13380
  "exec",
12968
13381
  "-it",
12969
13382
  "-w",
12970
- projectRoot,
13383
+ workdir,
12971
13384
  sandboxName,
12972
13385
  "sh"
12973
13386
  ]);
@@ -13048,7 +13461,7 @@ async function handleLogs(projectRoot, args) {
13048
13461
  }
13049
13462
  function detectPackageManager2(projectRoot) {
13050
13463
  try {
13051
- const raw = readFileSync17(join25(projectRoot, "package.json"), "utf-8");
13464
+ const raw = readFileSync17(join26(projectRoot, "package.json"), "utf-8");
13052
13465
  const pkgJson = JSON.parse(raw);
13053
13466
  if (typeof pkgJson.packageManager === "string") {
13054
13467
  const name = pkgJson.packageManager.split("@")[0];
@@ -13057,13 +13470,13 @@ function detectPackageManager2(projectRoot) {
13057
13470
  }
13058
13471
  }
13059
13472
  } catch {}
13060
- if (existsSync25(join25(projectRoot, "bun.lock")) || existsSync25(join25(projectRoot, "bun.lockb"))) {
13473
+ if (existsSync26(join26(projectRoot, "bun.lock")) || existsSync26(join26(projectRoot, "bun.lockb"))) {
13061
13474
  return "bun";
13062
13475
  }
13063
- if (existsSync25(join25(projectRoot, "yarn.lock"))) {
13476
+ if (existsSync26(join26(projectRoot, "yarn.lock"))) {
13064
13477
  return "yarn";
13065
13478
  }
13066
- if (existsSync25(join25(projectRoot, "pnpm-lock.yaml"))) {
13479
+ if (existsSync26(join26(projectRoot, "pnpm-lock.yaml"))) {
13067
13480
  return "pnpm";
13068
13481
  }
13069
13482
  return "npm";
@@ -13080,7 +13493,8 @@ function getInstallCommand(pm) {
13080
13493
  return ["npm", "install"];
13081
13494
  }
13082
13495
  }
13083
- async function runSandboxSetup(sandboxName, projectRoot) {
13496
+ async function runSandboxSetup(sandboxName, projectRoot, containerWorkdir) {
13497
+ const workdir = containerWorkdir ?? projectRoot;
13084
13498
  const ecosystem = detectProjectEcosystem(projectRoot);
13085
13499
  const isJS = isJavaScriptEcosystem(ecosystem);
13086
13500
  if (isJS) {
@@ -13096,7 +13510,7 @@ Installing dependencies (${bold2(installCmd.join(" "))}) in sandbox ${dim2(sandb
13096
13510
  "sandbox",
13097
13511
  "exec",
13098
13512
  "-w",
13099
- projectRoot,
13513
+ workdir,
13100
13514
  sandboxName,
13101
13515
  ...installCmd
13102
13516
  ]);
@@ -13112,18 +13526,19 @@ Installing dependencies (${bold2(installCmd.join(" "))}) in sandbox ${dim2(sandb
13112
13526
  ${dim2(`Detected ${ecosystem} project — skipping JS package install.`)}
13113
13527
  `);
13114
13528
  }
13115
- const setupScript = join25(projectRoot, ".locus", "sandbox-setup.sh");
13116
- if (existsSync25(setupScript)) {
13529
+ const setupScript = join26(projectRoot, ".locus", "sandbox-setup.sh");
13530
+ const containerSetupScript = containerWorkdir ? join26(containerWorkdir, ".locus", "sandbox-setup.sh") : setupScript;
13531
+ if (existsSync26(setupScript)) {
13117
13532
  process.stderr.write(`Running ${bold2(".locus/sandbox-setup.sh")} in sandbox ${dim2(sandboxName)}...
13118
13533
  `);
13119
13534
  const hookOk = await runInteractiveCommand("docker", [
13120
13535
  "sandbox",
13121
13536
  "exec",
13122
13537
  "-w",
13123
- projectRoot,
13538
+ workdir,
13124
13539
  sandboxName,
13125
13540
  "sh",
13126
- setupScript
13541
+ containerSetupScript
13127
13542
  ]);
13128
13543
  if (!hookOk) {
13129
13544
  process.stderr.write(`${yellow2("⚠")} Setup hook failed in sandbox ${dim2(sandboxName)}.
@@ -13154,7 +13569,7 @@ async function handleSetup(projectRoot) {
13154
13569
  `);
13155
13570
  continue;
13156
13571
  }
13157
- await runSandboxSetup(sandboxName, projectRoot);
13572
+ await runSandboxSetup(sandboxName, projectRoot, config.sandbox.containerWorkdir);
13158
13573
  }
13159
13574
  }
13160
13575
  function buildProviderSandboxNames(projectRoot) {
@@ -13199,9 +13614,9 @@ function runInteractiveCommand(command, args) {
13199
13614
  child.on("error", () => resolve2(false));
13200
13615
  });
13201
13616
  }
13202
- async function createProviderSandbox(provider, sandboxName, projectRoot) {
13617
+ async function createProviderSandbox(provider, sandboxName, projectRoot, containerWorkdir) {
13203
13618
  try {
13204
- execSync18(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
13619
+ execSync20(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
13205
13620
  stdio: ["pipe", "pipe", "pipe"],
13206
13621
  timeout: 120000
13207
13622
  });
@@ -13214,7 +13629,7 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
13214
13629
  }
13215
13630
  const backup = backupIgnoredFiles(projectRoot);
13216
13631
  try {
13217
- await enforceSandboxIgnore(sandboxName, projectRoot);
13632
+ await enforceSandboxIgnore(sandboxName, projectRoot, containerWorkdir);
13218
13633
  } finally {
13219
13634
  backup.restore();
13220
13635
  }
@@ -13222,7 +13637,7 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
13222
13637
  }
13223
13638
  async function ensurePackageManagerInSandbox(sandboxName, pm) {
13224
13639
  try {
13225
- execSync18(`docker sandbox exec ${sandboxName} which ${pm}`, {
13640
+ execSync20(`docker sandbox exec ${sandboxName} which ${pm}`, {
13226
13641
  stdio: ["pipe", "pipe", "pipe"],
13227
13642
  timeout: 5000
13228
13643
  });
@@ -13231,7 +13646,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
13231
13646
  process.stderr.write(`Installing ${bold2(pm)} in sandbox...
13232
13647
  `);
13233
13648
  try {
13234
- execSync18(`docker sandbox exec ${sandboxName} npm install -g ${npmPkg}`, {
13649
+ execSync20(`docker sandbox exec ${sandboxName} npm install -g ${npmPkg}`, {
13235
13650
  stdio: "inherit",
13236
13651
  timeout: 120000
13237
13652
  });
@@ -13243,7 +13658,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
13243
13658
  }
13244
13659
  async function ensureCodexInSandbox(sandboxName) {
13245
13660
  try {
13246
- execSync18(`docker sandbox exec ${sandboxName} which codex`, {
13661
+ execSync20(`docker sandbox exec ${sandboxName} which codex`, {
13247
13662
  stdio: ["pipe", "pipe", "pipe"],
13248
13663
  timeout: 5000
13249
13664
  });
@@ -13251,7 +13666,7 @@ async function ensureCodexInSandbox(sandboxName) {
13251
13666
  process.stderr.write(`Installing codex in sandbox...
13252
13667
  `);
13253
13668
  try {
13254
- execSync18(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
13669
+ execSync20(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
13255
13670
  } catch {
13256
13671
  process.stderr.write(`${red2("✗")} Failed to install codex in sandbox.
13257
13672
  `);
@@ -13260,7 +13675,7 @@ async function ensureCodexInSandbox(sandboxName) {
13260
13675
  }
13261
13676
  function isSandboxAlive(name) {
13262
13677
  try {
13263
- const output = execSync18("docker sandbox ls", {
13678
+ const output = execSync20("docker sandbox ls", {
13264
13679
  encoding: "utf-8",
13265
13680
  stdio: ["pipe", "pipe", "pipe"],
13266
13681
  timeout: 5000
@@ -13286,13 +13701,13 @@ init_context();
13286
13701
  init_logger();
13287
13702
  init_rate_limiter();
13288
13703
  init_terminal();
13289
- import { existsSync as existsSync26, readFileSync as readFileSync18 } from "node:fs";
13290
- import { join as join26 } from "node:path";
13704
+ import { existsSync as existsSync27, readFileSync as readFileSync18 } from "node:fs";
13705
+ import { join as join27 } from "node:path";
13291
13706
  import { fileURLToPath } from "node:url";
13292
13707
  function getCliVersion() {
13293
13708
  const fallbackVersion = "0.0.0";
13294
- const packageJsonPath = join26(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
13295
- if (!existsSync26(packageJsonPath)) {
13709
+ const packageJsonPath = join27(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
13710
+ if (!existsSync27(packageJsonPath)) {
13296
13711
  return fallbackVersion;
13297
13712
  }
13298
13713
  try {
@@ -13442,7 +13857,7 @@ function printLogo() {
13442
13857
  `);
13443
13858
  }
13444
13859
  }
13445
- function printHelp6() {
13860
+ function printHelp7() {
13446
13861
  printLogo();
13447
13862
  process.stderr.write(`
13448
13863
 
@@ -13463,6 +13878,7 @@ ${bold2("Commands:")}
13463
13878
  ${cyan2("status")} Dashboard view of current state
13464
13879
  ${cyan2("config")} View and manage settings
13465
13880
  ${cyan2("logs")} View, tail, and manage execution logs
13881
+ ${cyan2("create")} ${dim2("<name>")} Scaffold a new community package
13466
13882
  ${cyan2("install")} Install a community package
13467
13883
  ${cyan2("uninstall")} Remove an installed package
13468
13884
  ${cyan2("packages")} Manage installed packages (list, outdated)
@@ -13546,7 +13962,7 @@ async function main() {
13546
13962
  process.exit(0);
13547
13963
  }
13548
13964
  if (parsed.flags.help && !parsed.command) {
13549
- printHelp6();
13965
+ printHelp7();
13550
13966
  process.exit(0);
13551
13967
  }
13552
13968
  const command = resolveAlias(parsed.command);
@@ -13557,7 +13973,7 @@ async function main() {
13557
13973
  try {
13558
13974
  const root = getGitRoot(cwd);
13559
13975
  if (isInitialized(root)) {
13560
- logDir = join26(root, ".locus", "logs");
13976
+ logDir = join27(root, ".locus", "logs");
13561
13977
  getRateLimiter(root);
13562
13978
  }
13563
13979
  } catch {}
@@ -13578,7 +13994,7 @@ async function main() {
13578
13994
  printVersionNotice = startVersionCheck2(VERSION);
13579
13995
  }
13580
13996
  if (!command) {
13581
- printHelp6();
13997
+ printHelp7();
13582
13998
  process.exit(0);
13583
13999
  }
13584
14000
  if (command === "init") {
@@ -13587,6 +14003,13 @@ async function main() {
13587
14003
  logger.destroy();
13588
14004
  return;
13589
14005
  }
14006
+ if (command === "create") {
14007
+ const { createCommand: createCommand2 } = await Promise.resolve().then(() => (init_create(), exports_create));
14008
+ const createArgs = parsed.flags.help ? ["help"] : parsed.args;
14009
+ await createCommand2(createArgs);
14010
+ logger.destroy();
14011
+ return;
14012
+ }
13590
14013
  if (command === "install") {
13591
14014
  if (parsed.flags.list) {
13592
14015
  const { packagesCommand: packagesCommand2 } = await Promise.resolve().then(() => (init_packages(), exports_packages));