@askexenow/exe-os 0.9.69 → 0.9.70

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 (75) hide show
  1. package/deploy/stack-manifests/v0.9.json +96 -16
  2. package/dist/bin/agentic-ontology-backfill.js +6 -0
  3. package/dist/bin/agentic-reflection-backfill.js +6 -0
  4. package/dist/bin/agentic-semantic-label.js +6 -0
  5. package/dist/bin/backfill-conversations.js +6 -0
  6. package/dist/bin/backfill-responses.js +6 -0
  7. package/dist/bin/backfill-vectors.js +6 -0
  8. package/dist/bin/bulk-sync-postgres.js +6 -0
  9. package/dist/bin/cleanup-stale-review-tasks.js +6 -0
  10. package/dist/bin/cli.js +1257 -178
  11. package/dist/bin/exe-agent.js +6 -0
  12. package/dist/bin/exe-assign.js +6 -0
  13. package/dist/bin/exe-boot.js +6 -0
  14. package/dist/bin/exe-call.js +6 -0
  15. package/dist/bin/exe-cloud.js +6 -0
  16. package/dist/bin/exe-dispatch.js +6 -0
  17. package/dist/bin/exe-doctor.js +6 -0
  18. package/dist/bin/exe-export-behaviors.js +6 -0
  19. package/dist/bin/exe-forget.js +6 -0
  20. package/dist/bin/exe-gateway.js +151 -110
  21. package/dist/bin/exe-heartbeat.js +6 -0
  22. package/dist/bin/exe-kill.js +6 -0
  23. package/dist/bin/exe-launch-agent.js +6 -0
  24. package/dist/bin/exe-new-employee.js +6 -0
  25. package/dist/bin/exe-pending-messages.js +6 -0
  26. package/dist/bin/exe-pending-notifications.js +6 -0
  27. package/dist/bin/exe-pending-reviews.js +6 -0
  28. package/dist/bin/exe-rename.js +13 -4
  29. package/dist/bin/exe-review.js +6 -0
  30. package/dist/bin/exe-search.js +6 -0
  31. package/dist/bin/exe-session-cleanup.js +6 -0
  32. package/dist/bin/exe-start-codex.js +6 -0
  33. package/dist/bin/exe-start-opencode.js +6 -0
  34. package/dist/bin/exe-status.js +6 -0
  35. package/dist/bin/exe-team.js +6 -0
  36. package/dist/bin/git-sweep.js +6 -0
  37. package/dist/bin/graph-backfill.js +150 -110
  38. package/dist/bin/graph-export.js +6 -0
  39. package/dist/bin/intercom-check.js +6 -0
  40. package/dist/bin/registry-proxy.js +207 -0
  41. package/dist/bin/scan-tasks.js +6 -0
  42. package/dist/bin/setup.js +6 -0
  43. package/dist/bin/shard-migrate.js +6 -0
  44. package/dist/bin/stack-update.js +128 -0
  45. package/dist/gateway/index.js +151 -110
  46. package/dist/hooks/bug-report-worker.js +6 -0
  47. package/dist/hooks/codex-stop-task-finalizer.js +6 -0
  48. package/dist/hooks/commit-complete.js +6 -0
  49. package/dist/hooks/error-recall.js +6 -0
  50. package/dist/hooks/ingest.js +6 -0
  51. package/dist/hooks/instructions-loaded.js +6 -0
  52. package/dist/hooks/notification.js +6 -0
  53. package/dist/hooks/post-compact.js +6 -0
  54. package/dist/hooks/post-tool-combined.js +6 -0
  55. package/dist/hooks/pre-compact.js +6 -0
  56. package/dist/hooks/pre-tool-use.js +6 -0
  57. package/dist/hooks/prompt-submit.js +6 -0
  58. package/dist/hooks/session-end.js +6 -0
  59. package/dist/hooks/session-start.js +6 -0
  60. package/dist/hooks/stop.js +6 -0
  61. package/dist/hooks/subagent-stop.js +6 -0
  62. package/dist/hooks/summary-worker.js +6 -0
  63. package/dist/index.js +151 -110
  64. package/dist/lib/employee-templates.js +6 -0
  65. package/dist/lib/exe-daemon.js +382 -234
  66. package/dist/lib/hybrid-search.js +6 -0
  67. package/dist/lib/registry-proxy.js +162 -0
  68. package/dist/lib/schedules.js +6 -0
  69. package/dist/lib/store.js +6 -0
  70. package/dist/mcp/server.js +318 -222
  71. package/dist/runtime/index.js +6 -0
  72. package/dist/tui/App.js +6 -0
  73. package/package.json +3 -2
  74. package/stack.release.json +6 -4
  75. package/stack.release.schema.json +89 -18
package/dist/bin/cli.js CHANGED
@@ -2391,12 +2391,12 @@ function enforceBackgroundJobGuardrails(options = {}) {
2391
2391
  const jobs = readJobsRaw();
2392
2392
  const actions = [];
2393
2393
  let changed = false;
2394
- const ts = now();
2394
+ const ts2 = now();
2395
2395
  for (const job of jobs) {
2396
2396
  if (job.status !== "running") continue;
2397
2397
  if (!isAlive(job.pid)) {
2398
2398
  job.status = "stale";
2399
- job.updatedAt = ts;
2399
+ job.updatedAt = ts2;
2400
2400
  actions.push({ jobId: job.id, name: job.name, pid: job.pid, action: "marked-stale" });
2401
2401
  changed = true;
2402
2402
  continue;
@@ -2404,7 +2404,7 @@ function enforceBackgroundJobGuardrails(options = {}) {
2404
2404
  const heartbeatAge = Date.now() - Date.parse(job.lastHeartbeatAt || job.updatedAt || job.startedAt);
2405
2405
  if (Number.isFinite(heartbeatAge) && heartbeatAge > staleAfter) {
2406
2406
  job.status = "stale";
2407
- job.updatedAt = ts;
2407
+ job.updatedAt = ts2;
2408
2408
  actions.push({ jobId: job.id, name: job.name, pid: job.pid, action: "marked-stale" });
2409
2409
  changed = true;
2410
2410
  continue;
@@ -2420,7 +2420,7 @@ function enforceBackgroundJobGuardrails(options = {}) {
2420
2420
  } catch {
2421
2421
  }
2422
2422
  job.status = "cancelled";
2423
- job.updatedAt = ts;
2423
+ job.updatedAt = ts2;
2424
2424
  job.progressLabel = `Cancelled by heat guardrail after sustained high CPU (${cpu.toFixed(1)}%).`;
2425
2425
  releaseJobLock(job.type);
2426
2426
  actions.push({ jobId: job.id, name: job.name, pid: job.pid, action: "cancelled-hot", cpuPercent: cpu, heatStrikes: job.heatStrikes });
@@ -2432,7 +2432,7 @@ function enforceBackgroundJobGuardrails(options = {}) {
2432
2432
  }
2433
2433
  if (cpu >= reniceAt) {
2434
2434
  renicePid(job.pid, 15);
2435
- job.updatedAt = ts;
2435
+ job.updatedAt = ts2;
2436
2436
  job.progressLabel = `Throttled by heat guardrail (${cpu.toFixed(1)}% CPU).`;
2437
2437
  actions.push({ jobId: job.id, name: job.name, pid: job.pid, action: "reniced", cpuPercent: cpu, heatStrikes: job.heatStrikes });
2438
2438
  changed = true;
@@ -2600,7 +2600,7 @@ async function tryKeytar() {
2600
2600
  }
2601
2601
  function deriveMachineKey() {
2602
2602
  try {
2603
- const crypto14 = __require("crypto");
2603
+ const crypto15 = __require("crypto");
2604
2604
  const material = [
2605
2605
  os8.hostname(),
2606
2606
  os8.userInfo().username,
@@ -2609,23 +2609,23 @@ function deriveMachineKey() {
2609
2609
  // Machine ID on Linux (stable across reboots)
2610
2610
  process.platform === "linux" ? readMachineId() : ""
2611
2611
  ].join("|");
2612
- return crypto14.createHash("sha256").update(material).digest();
2612
+ return crypto15.createHash("sha256").update(material).digest();
2613
2613
  } catch {
2614
2614
  return null;
2615
2615
  }
2616
2616
  }
2617
2617
  function readMachineId() {
2618
2618
  try {
2619
- const { readFileSync: readFileSync35 } = __require("fs");
2620
- return readFileSync35("/etc/machine-id", "utf-8").trim();
2619
+ const { readFileSync: readFileSync36 } = __require("fs");
2620
+ return readFileSync36("/etc/machine-id", "utf-8").trim();
2621
2621
  } catch {
2622
2622
  return "";
2623
2623
  }
2624
2624
  }
2625
2625
  function encryptWithMachineKey(plaintext, machineKey) {
2626
- const crypto14 = __require("crypto");
2627
- const iv = crypto14.randomBytes(12);
2628
- const cipher = crypto14.createCipheriv("aes-256-gcm", machineKey, iv);
2626
+ const crypto15 = __require("crypto");
2627
+ const iv = crypto15.randomBytes(12);
2628
+ const cipher = crypto15.createCipheriv("aes-256-gcm", machineKey, iv);
2629
2629
  let encrypted = cipher.update(plaintext, "utf-8", "base64");
2630
2630
  encrypted += cipher.final("base64");
2631
2631
  const authTag = cipher.getAuthTag().toString("base64");
@@ -2634,13 +2634,13 @@ function encryptWithMachineKey(plaintext, machineKey) {
2634
2634
  function decryptWithMachineKey(encrypted, machineKey) {
2635
2635
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2636
2636
  try {
2637
- const crypto14 = __require("crypto");
2637
+ const crypto15 = __require("crypto");
2638
2638
  const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
2639
2639
  if (parts.length !== 3) return null;
2640
2640
  const [ivB64, tagB64, cipherB64] = parts;
2641
2641
  const iv = Buffer.from(ivB64, "base64");
2642
2642
  const authTag = Buffer.from(tagB64, "base64");
2643
- const decipher = crypto14.createDecipheriv("aes-256-gcm", machineKey, iv);
2643
+ const decipher = crypto15.createDecipheriv("aes-256-gcm", machineKey, iv);
2644
2644
  decipher.setAuthTag(authTag);
2645
2645
  let decrypted = decipher.update(cipherB64, "base64", "utf-8");
2646
2646
  decrypted += decipher.final("utf-8");
@@ -6044,8 +6044,8 @@ async function validateLicense(apiKey, deviceId) {
6044
6044
  }
6045
6045
  function getCacheAgeMs() {
6046
6046
  try {
6047
- const { statSync: statSync7 } = __require("fs");
6048
- const s = statSync7(CACHE_PATH);
6047
+ const { statSync: statSync8 } = __require("fs");
6048
+ const s = statSync8(CACHE_PATH);
6049
6049
  return Date.now() - s.mtimeMs;
6050
6050
  } catch {
6051
6051
  return Infinity;
@@ -6732,8 +6732,8 @@ async function withRosterLock(fn) {
6732
6732
  } catch (err) {
6733
6733
  if (err.code === "EEXIST") {
6734
6734
  try {
6735
- const ts = parseInt(readFileSync12(ROSTER_LOCK_PATH, "utf-8"), 10);
6736
- if (Date.now() - ts < LOCK_STALE_MS) {
6735
+ const ts2 = parseInt(readFileSync12(ROSTER_LOCK_PATH, "utf-8"), 10);
6736
+ if (Date.now() - ts2 < LOCK_STALE_MS) {
6737
6737
  throw new Error("Roster merge already in progress \u2014 another sync is running");
6738
6738
  }
6739
6739
  unlinkSync6(ROSTER_LOCK_PATH);
@@ -8657,6 +8657,12 @@ var init_platform_procedures = __esm({
8657
8657
  priority: "p0",
8658
8658
  content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
8659
8659
  },
8660
+ {
8661
+ title: "Code context first for repository orientation",
8662
+ domain: "workflow",
8663
+ priority: "p1",
8664
+ content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
8665
+ },
8660
8666
  {
8661
8667
  title: "Commit discipline \u2014 never leave verified work floating",
8662
8668
  domain: "workflow",
@@ -11085,10 +11091,10 @@ async function parseConversation(filePath) {
11085
11091
  if (entry.cwd && typeof entry.cwd === "string") {
11086
11092
  conv.cwd = entry.cwd;
11087
11093
  }
11088
- const ts = entry.timestamp;
11089
- if (ts) {
11090
- if (!conv.startTime || ts < conv.startTime) conv.startTime = ts;
11091
- if (!conv.endTime || ts > conv.endTime) conv.endTime = ts;
11094
+ const ts2 = entry.timestamp;
11095
+ if (ts2) {
11096
+ if (!conv.startTime || ts2 < conv.startTime) conv.startTime = ts2;
11097
+ if (!conv.endTime || ts2 > conv.endTime) conv.endTime = ts2;
11092
11098
  }
11093
11099
  const entryType = entry.type;
11094
11100
  if (entryType === "user") {
@@ -11774,9 +11780,9 @@ Unclassified: ${unclassified}
11774
11780
  }
11775
11781
  async function exportBatches(options) {
11776
11782
  const fs8 = await import("fs");
11777
- const path54 = await import("path");
11783
+ const path55 = await import("path");
11778
11784
  const client = getClient();
11779
- const outDir = path54.join(process.cwd(), "exe/output/classifications/input");
11785
+ const outDir = path55.join(process.cwd(), "exe/output/classifications/input");
11780
11786
  fs8.mkdirSync(outDir, { recursive: true });
11781
11787
  const countResult = await client.execute({
11782
11788
  sql: "SELECT COUNT(*) as cnt FROM memories WHERE intent IS NULL AND outcome IS NULL AND domain IS NULL",
@@ -11800,7 +11806,7 @@ async function exportBatches(options) {
11800
11806
  const text = String(row.text || "").replace(/\n/g, " ");
11801
11807
  return JSON.stringify({ id: row.id, text });
11802
11808
  });
11803
- const batchFile = path54.join(outDir, `batch-${String(batchNum).padStart(4, "0")}.jsonl`);
11809
+ const batchFile = path55.join(outDir, `batch-${String(batchNum).padStart(4, "0")}.jsonl`);
11804
11810
  fs8.writeFileSync(batchFile, lines.join("\n") + "\n");
11805
11811
  exported += batch.rows.length;
11806
11812
  offset += options.batchSize;
@@ -11816,7 +11822,7 @@ async function exportBatches(options) {
11816
11822
  }
11817
11823
  async function importClassifications(importDir) {
11818
11824
  const fs8 = await import("fs");
11819
- const path54 = await import("path");
11825
+ const path55 = await import("path");
11820
11826
  const client = getClient();
11821
11827
  const files = fs8.readdirSync(importDir).filter((f) => f.endsWith(".jsonl")).sort();
11822
11828
  process.stderr.write(`[backfill-metadata] Found ${files.length} JSONL files to import from ${importDir}
@@ -11824,7 +11830,7 @@ async function importClassifications(importDir) {
11824
11830
  let imported = 0;
11825
11831
  let invalid = 0;
11826
11832
  for (const file of files) {
11827
- const lines = fs8.readFileSync(path54.join(importDir, file), "utf-8").split("\n").filter(Boolean);
11833
+ const lines = fs8.readFileSync(path55.join(importDir, file), "utf-8").split("\n").filter(Boolean);
11828
11834
  for (const line of lines) {
11829
11835
  try {
11830
11836
  const rec = JSON.parse(line);
@@ -14462,10 +14468,10 @@ async function disposeEmbedder() {
14462
14468
  async function embedDirect(text) {
14463
14469
  const llamaCpp = await import("node-llama-cpp");
14464
14470
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
14465
- const { existsSync: existsSync40 } = await import("fs");
14466
- const path54 = await import("path");
14467
- const modelPath = path54.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
14468
- if (!existsSync40(modelPath)) {
14471
+ const { existsSync: existsSync41 } = await import("fs");
14472
+ const path55 = await import("path");
14473
+ const modelPath = path55.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
14474
+ if (!existsSync41(modelPath)) {
14469
14475
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
14470
14476
  }
14471
14477
  const llama = await llamaCpp.getLlama();
@@ -17279,10 +17285,7 @@ async function renameEmployee(oldName, newName, opts = {}) {
17279
17285
  const newIdentityPath = path35.join(identityDir, `${newName}.md`);
17280
17286
  if (existsSync29(oldIdentityPath)) {
17281
17287
  const content = readFileSync24(oldIdentityPath, "utf-8");
17282
- const updatedContent = content.replace(
17283
- /^(agent_id:\s*)\S+/m,
17284
- `$1${newName}`
17285
- );
17288
+ const updatedContent = rewriteRenamedEmployeeContent(content, rosterOldName, newName);
17286
17289
  renameSync6(oldIdentityPath, newIdentityPath);
17287
17290
  writeFileSync21(newIdentityPath, updatedContent, "utf-8");
17288
17291
  rollbackStack.push({
@@ -17299,7 +17302,9 @@ async function renameEmployee(oldName, newName, opts = {}) {
17299
17302
  const newAgentPath = path35.join(agentsDir, `${newName}.md`);
17300
17303
  if (existsSync29(oldAgentPath)) {
17301
17304
  const agentContent = readFileSync24(oldAgentPath, "utf-8");
17305
+ const updatedAgentContent = rewriteRenamedEmployeeContent(agentContent, rosterOldName, newName);
17302
17306
  renameSync6(oldAgentPath, newAgentPath);
17307
+ writeFileSync21(newAgentPath, updatedAgentContent, "utf-8");
17303
17308
  rollbackStack.push({
17304
17309
  description: "restore agent file",
17305
17310
  undo: () => {
@@ -17372,6 +17377,10 @@ async function renameEmployee(oldName, newName, opts = {}) {
17372
17377
  return { success: false, error: err instanceof Error ? err.message : String(err) };
17373
17378
  }
17374
17379
  }
17380
+ function rewriteRenamedEmployeeContent(content, oldName, newName) {
17381
+ const withAgentId = content.replace(/^(agent_id:\s*)\S+/m, `$1${newName}`);
17382
+ return personalizePrompt(withAgentId, oldName, newName);
17383
+ }
17375
17384
  function findExeBin2() {
17376
17385
  try {
17377
17386
  return execSync12(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
@@ -18295,8 +18304,8 @@ async function validateModel(log) {
18295
18304
  log("Skipping in-memory model validation (low memory \u2014 will validate on first use).");
18296
18305
  const modelPath = path38.join(MODELS_DIR, LOCAL_FILENAME);
18297
18306
  if (existsSync32(modelPath)) {
18298
- const { statSync: statSync7 } = await import("fs");
18299
- const size = statSync7(modelPath).size;
18307
+ const { statSync: statSync8 } = await import("fs");
18308
+ const size = statSync8(modelPath).size;
18300
18309
  if (size > 300 * 1e6) {
18301
18310
  log(`Model file verified (${(size / 1e6).toFixed(0)} MB).`);
18302
18311
  return;
@@ -19500,6 +19509,101 @@ function createStackUpdatePlan(manifest, envRaw, targetVersion) {
19500
19509
  breakingChanges: release.breakingChanges ?? []
19501
19510
  };
19502
19511
  }
19512
+ function validatePinnedGhcrImage(image, label) {
19513
+ const trimmed = image.trim().replace(/^['"]|['"]$/g, "");
19514
+ if (!trimmed) return `${label} is empty`;
19515
+ if (trimmed.includes("${")) return null;
19516
+ if (!trimmed.startsWith("ghcr.io/askexe/") && !trimmed.startsWith("registry.askexe.com/askexe/")) return `${label} must use ghcr.io/askexe/* or registry.askexe.com/askexe/*, got ${trimmed}`;
19517
+ if (/:latest(?:$|[\s#])/.test(trimmed)) return `${label} must not use :latest (${trimmed})`;
19518
+ if (!ASKEXE_GHCR_IMAGE.test(trimmed)) return `${label} must be pinned with an explicit tag or sha256 digest from ghcr.io/askexe or registry.askexe.com/askexe, got ${trimmed}`;
19519
+ return null;
19520
+ }
19521
+ function validateComposeImageLiteral(image, label) {
19522
+ const trimmed = image.trim().replace(/^['"]|['"]$/g, "");
19523
+ if (!trimmed) return `${label} is empty`;
19524
+ if (trimmed.startsWith("ghcr.io/askexe/") || trimmed.startsWith("registry.askexe.com/askexe/")) return validatePinnedGhcrImage(trimmed, label);
19525
+ if (/^(postgres|pgvector\/pgvector|clickhouse\/clickhouse-server|redis|nginx|postgrest\/postgrest|supabase\/gotrue):[^:]+$/i.test(trimmed)) return null;
19526
+ return `${label} uses unsupported non-AskExe image ${trimmed}; customer app images must come from pinned ghcr.io/askexe images`;
19527
+ }
19528
+ function collectProductionDeployGateIssues(plan, envRaw, composeRaw) {
19529
+ const issues = [];
19530
+ const env = parseEnv(envRaw);
19531
+ for (const [serviceName, service] of Object.entries(plan.release.services)) {
19532
+ const manifestIssue = validatePinnedGhcrImage(service.image, `manifest ${plan.targetVersion}.${serviceName}.image`);
19533
+ if (manifestIssue) issues.push({ kind: "manifest-image", message: manifestIssue });
19534
+ const envImage = env.get(service.env);
19535
+ if (envImage) {
19536
+ const envIssue = validatePinnedGhcrImage(envImage, `env ${service.env}`);
19537
+ if (envIssue) issues.push({ kind: "env-image", message: envIssue });
19538
+ }
19539
+ }
19540
+ const lines = composeRaw.split(/\r?\n/);
19541
+ lines.forEach((line, index) => {
19542
+ if (/^\s*build\s*:/.test(line)) {
19543
+ issues.push({ kind: "compose-build", message: `compose line ${index + 1} contains build:, production deploys must pull images` });
19544
+ }
19545
+ const imageMatch = line.match(/^\s*image\s*:\s*(.+?)\s*(?:#.*)?$/);
19546
+ if (imageMatch) {
19547
+ const image = imageMatch[1].trim();
19548
+ if (image.includes("${")) {
19549
+ const fallback = image.match(/:-([^}]+)}/)?.[1];
19550
+ if (fallback) {
19551
+ const composeIssue = validateComposeImageLiteral(fallback, `compose image fallback on line ${index + 1}`);
19552
+ if (composeIssue) issues.push({ kind: "compose-image", message: composeIssue });
19553
+ }
19554
+ } else {
19555
+ const composeIssue = validateComposeImageLiteral(image, `compose image on line ${index + 1}`);
19556
+ if (composeIssue) issues.push({ kind: "compose-image", message: composeIssue });
19557
+ }
19558
+ }
19559
+ });
19560
+ return issues;
19561
+ }
19562
+ function assertDeploymentScopeAllowed(plan, persona = "customer") {
19563
+ if (persona === "askexe-control-plane") return;
19564
+ const blocked = Object.entries(plan.release.services).filter(([, service]) => service.deploymentScope === "askexe-control-plane").map(([name]) => name);
19565
+ if (blocked.length > 0) {
19566
+ throw new Error(
19567
+ `Customer deployment manifest includes AskExe control-plane service(s): ${blocked.join(", ")}. Customer VPSs may deploy customer services and optional agents only.`
19568
+ );
19569
+ }
19570
+ }
19571
+ function assertProductionDeployGate(plan, envRaw, composeRaw, options = {}) {
19572
+ const issues = collectProductionDeployGateIssues(plan, envRaw, composeRaw);
19573
+ if (issues.length === 0) return;
19574
+ if (options.breakGlassReason?.trim()) {
19575
+ writeBreakGlassAudit(plan, issues, options);
19576
+ return;
19577
+ }
19578
+ const details = issues.map((issue) => `- [${issue.kind}] ${issue.message}`).join("\n");
19579
+ throw new Error(
19580
+ `Production deploy gate failed. Exe OS deploys must use pinned ghcr.io/askexe or registry.askexe.com/askexe images and must not build from source on the VPS.
19581
+ ${details}
19582
+ Emergency override requires --break-glass <reason> and writes an audit file.`
19583
+ );
19584
+ }
19585
+ function writeBreakGlassAudit(plan, issues, options) {
19586
+ const now2 = options.now ?? (() => /* @__PURE__ */ new Date());
19587
+ const stamp = now2().toISOString().replace(/[:.]/g, "-");
19588
+ const defaultDir = existsSync34("exe/output") ? "exe/output" : path41.dirname(options.envFile ?? ".");
19589
+ const auditFile = options.breakGlassAuditFile ?? path41.join(defaultDir, `stack-update-break-glass-${stamp}.md`);
19590
+ mkdirSync23(path41.dirname(auditFile), { recursive: true });
19591
+ const body = [
19592
+ `# Stack Update Break-Glass Audit \u2014 ${now2().toISOString()}`,
19593
+ "",
19594
+ `Target version: ${plan.targetVersion}`,
19595
+ `Reason: ${options.breakGlassReason?.trim()}`,
19596
+ "",
19597
+ "## Gate failures overridden",
19598
+ ...issues.map((issue) => `- [${issue.kind}] ${issue.message}`),
19599
+ "",
19600
+ "## Required follow-up",
19601
+ "Return this deployment to the standard pinned GHCR image path immediately after the emergency is resolved.",
19602
+ ""
19603
+ ].join("\n");
19604
+ writeFileSync24(auditFile, body, { mode: 384 });
19605
+ console.warn(`[stack-update] BREAK-GLASS deploy override recorded: ${auditFile}`);
19606
+ }
19503
19607
  function assertBreakingChangesAllowed(plan, allowedIds) {
19504
19608
  const required = plan.breakingChanges.filter((c) => c.requiresConfirmation !== false);
19505
19609
  const missing = required.filter((c) => !allowedIds.includes(c.id));
@@ -19522,6 +19626,15 @@ async function runStackUpdate(options) {
19522
19626
  const envRaw = readFileSync28(options.envFile, "utf8");
19523
19627
  const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
19524
19628
  assertBreakingChangesAllowed(plan, options.allowedBreakingChangeIds ?? []);
19629
+ assertDeploymentScopeAllowed(plan, options.deploymentPersona ?? "customer");
19630
+ const plannedEnvRaw = patchEnv(envRaw, Object.fromEntries(plan.changes.map((c) => [c.key, c.after])));
19631
+ const composeRaw = readFileSync28(options.composeFile, "utf8");
19632
+ assertProductionDeployGate(plan, plannedEnvRaw, composeRaw, {
19633
+ breakGlassReason: options.breakGlassReason,
19634
+ breakGlassAuditFile: options.breakGlassAuditFile,
19635
+ now: now2,
19636
+ envFile: options.envFile
19637
+ });
19525
19638
  const lockFile = options.lockFile ?? path41.join(path41.dirname(options.envFile), ".exe-stack-lock.json");
19526
19639
  const previousVersion = readCurrentStackVersion(lockFile);
19527
19640
  if (options.dryRun || plan.changes.length === 0) {
@@ -19693,9 +19806,11 @@ function loadDefaultPublicKey() {
19693
19806
  }
19694
19807
  return void 0;
19695
19808
  }
19809
+ var ASKEXE_GHCR_IMAGE;
19696
19810
  var init_stack_update = __esm({
19697
19811
  "src/lib/stack-update.ts"() {
19698
19812
  "use strict";
19813
+ ASKEXE_GHCR_IMAGE = /^(?:ghcr\.io\/askexe|registry\.askexe\.com\/askexe)\/[a-z0-9._/-]+(?::[^:@$/{]+|@sha256:[a-f0-9]{64})$/i;
19699
19814
  }
19700
19815
  });
19701
19816
 
@@ -19720,6 +19835,7 @@ function parseArgs4(args2) {
19720
19835
  dryRun: false,
19721
19836
  check: false,
19722
19837
  rollback: false,
19838
+ deploymentPersona: process.env.EXE_STACK_DEPLOYMENT_PERSONA === "askexe-control-plane" ? "askexe-control-plane" : "customer",
19723
19839
  yes: false,
19724
19840
  allowedBreakingChangeIds: []
19725
19841
  };
@@ -19750,6 +19866,18 @@ function parseArgs4(args2) {
19750
19866
  else if (arg === "--license-key") opts.licenseKey = next();
19751
19867
  else if (arg.startsWith("--license-key=")) opts.licenseKey = arg.split("=").slice(1).join("=");
19752
19868
  else if (arg === "--rollback") opts.rollback = true;
19869
+ else if (arg === "--deployment-persona") {
19870
+ const value = next();
19871
+ if (value !== "customer" && value !== "askexe-control-plane") throw new Error(`Invalid --deployment-persona: ${value}`);
19872
+ opts.deploymentPersona = value;
19873
+ } else if (arg.startsWith("--deployment-persona=")) {
19874
+ const value = arg.split("=").slice(1).join("=");
19875
+ if (value !== "customer" && value !== "askexe-control-plane") throw new Error(`Invalid --deployment-persona: ${value}`);
19876
+ opts.deploymentPersona = value;
19877
+ } else if (arg === "--break-glass") opts.breakGlassReason = next();
19878
+ else if (arg.startsWith("--break-glass=")) opts.breakGlassReason = arg.split("=").slice(1).join("=");
19879
+ else if (arg === "--break-glass-audit-file") opts.breakGlassAuditFile = next();
19880
+ else if (arg.startsWith("--break-glass-audit-file=")) opts.breakGlassAuditFile = arg.split("=").slice(1).join("=");
19753
19881
  else if (arg === "--dry-run") opts.dryRun = true;
19754
19882
  else if (arg === "--check") opts.check = true;
19755
19883
  else if (arg === "--yes" || arg === "-y") opts.yes = true;
@@ -19787,6 +19915,9 @@ Options:
19787
19915
  --device-id <id> Device ID to include in deploy audit
19788
19916
  --license-key <key> License key to include in deploy audit
19789
19917
  --rollback Restore latest backed-up .env and restart stack
19918
+ --deployment-persona <customer|askexe-control-plane> Scope gate (default: customer)
19919
+ --break-glass <reason> Emergency override for production deploy gate; writes audit file
19920
+ --break-glass-audit-file <path> Audit file path for --break-glass
19790
19921
  --allow-breaking <ids> Confirm breaking changes, comma-separated
19791
19922
  -y, --yes Non-interactive confirmation
19792
19923
  `);
@@ -19826,6 +19957,13 @@ async function main6() {
19826
19957
  const manifest = await loadStackManifest(opts.manifestRef, void 0, opts.manifestPublicKey, opts.manifestAuthToken);
19827
19958
  const envRaw = readFileSync29(opts.envFile, "utf8");
19828
19959
  const plan = createStackUpdatePlan(manifest, envRaw, opts.targetVersion);
19960
+ assertDeploymentScopeAllowed(plan, opts.deploymentPersona);
19961
+ const plannedEnvRaw = patchEnv(envRaw, Object.fromEntries(plan.changes.map((c) => [c.key, c.after])));
19962
+ assertProductionDeployGate(plan, plannedEnvRaw, readFileSync29(opts.composeFile, "utf8"), {
19963
+ breakGlassReason: opts.breakGlassReason,
19964
+ breakGlassAuditFile: opts.breakGlassAuditFile,
19965
+ envFile: opts.envFile
19966
+ });
19829
19967
  console.log(`Exe OS stack target: ${plan.targetVersion}`);
19830
19968
  console.log(`Manifest: ${opts.manifestRef}`);
19831
19969
  console.log(`Compose: ${opts.composeFile}`);
@@ -19858,6 +19996,210 @@ var init_stack_update2 = __esm({
19858
19996
  }
19859
19997
  });
19860
19998
 
19999
+ // src/lib/registry-proxy.ts
20000
+ import { createServer } from "http";
20001
+ import { Readable } from "stream";
20002
+ function parsePullTokens(raw) {
20003
+ return (raw ?? "").split(/[\n,]/).map((s) => s.trim()).filter(Boolean);
20004
+ }
20005
+ function registryProxyOptionsFromEnv(env = process.env) {
20006
+ const upstreamToken = env.EXE_REGISTRY_PROXY_UPSTREAM_TOKEN || env.GHCR_TOKEN || "";
20007
+ const pullTokens = parsePullTokens(env.EXE_REGISTRY_PROXY_PULL_TOKENS);
20008
+ return {
20009
+ port: Number(env.EXE_REGISTRY_PROXY_PORT || 3201),
20010
+ host: env.EXE_REGISTRY_PROXY_HOST || "0.0.0.0",
20011
+ upstream: env.EXE_REGISTRY_PROXY_UPSTREAM || "https://ghcr.io",
20012
+ upstreamUsername: env.EXE_REGISTRY_PROXY_UPSTREAM_USERNAME || env.GHCR_USERNAME || "askexe",
20013
+ upstreamToken,
20014
+ pullTokens,
20015
+ allowedNamespace: env.EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE || "askexe"
20016
+ };
20017
+ }
20018
+ function assertRegistryProxyConfig(options) {
20019
+ if (!options.upstreamToken) throw new Error("EXE_REGISTRY_PROXY_UPSTREAM_TOKEN or GHCR_TOKEN is required");
20020
+ if (options.pullTokens.length === 0) throw new Error("EXE_REGISTRY_PROXY_PULL_TOKENS is required");
20021
+ if (!options.allowedNamespace || !/^[a-z0-9._-]+$/i.test(options.allowedNamespace)) {
20022
+ throw new Error("EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE must be a registry-safe namespace");
20023
+ }
20024
+ }
20025
+ function timingSafeIncludes(values, candidate) {
20026
+ return values.some((value) => value === candidate);
20027
+ }
20028
+ function parseBasicAuth(header) {
20029
+ if (!header?.startsWith("Basic ")) return null;
20030
+ try {
20031
+ const decoded = Buffer.from(header.slice("Basic ".length), "base64").toString("utf8");
20032
+ const idx = decoded.indexOf(":");
20033
+ if (idx < 0) return null;
20034
+ return { username: decoded.slice(0, idx), password: decoded.slice(idx + 1) };
20035
+ } catch {
20036
+ return null;
20037
+ }
20038
+ }
20039
+ function unauthorized(res) {
20040
+ res.writeHead(401, {
20041
+ "www-authenticate": 'Basic realm="AskExe Registry Proxy"',
20042
+ "content-type": "application/json"
20043
+ });
20044
+ res.end(JSON.stringify({ error: "registry proxy authentication required" }));
20045
+ }
20046
+ function isAllowedPath(pathname, namespace) {
20047
+ if (pathname === "/health") return true;
20048
+ if (pathname === "/v2/" || pathname === "/v2") return true;
20049
+ const prefix = `/v2/${namespace}/`;
20050
+ if (!pathname.startsWith(prefix)) return false;
20051
+ if (pathname.includes("..") || /%2f/i.test(pathname)) return false;
20052
+ return true;
20053
+ }
20054
+ function parseBearerChallenge(header) {
20055
+ if (!header?.toLowerCase().startsWith("bearer ")) return null;
20056
+ const raw = header.slice("Bearer ".length);
20057
+ const params = new URLSearchParams();
20058
+ for (const part of raw.match(/(?:[^,"]+|"[^"]*")+/g) ?? []) {
20059
+ const idx = part.indexOf("=");
20060
+ if (idx < 0) continue;
20061
+ const key = part.slice(0, idx).trim();
20062
+ const value = part.slice(idx + 1).trim().replace(/^"|"$/g, "");
20063
+ params.set(key, value);
20064
+ }
20065
+ const realm = params.get("realm");
20066
+ if (!realm) return null;
20067
+ params.delete("realm");
20068
+ return { realm, params };
20069
+ }
20070
+ async function fetchUpstreamWithRegistryAuth(url, init, upstreamBasicAuth) {
20071
+ const firstHeaders = new Headers(init.headers);
20072
+ firstHeaders.set("authorization", `Basic ${upstreamBasicAuth}`);
20073
+ const first = await fetch(url, { ...init, headers: firstHeaders });
20074
+ if (first.status !== 401) return first;
20075
+ const challenge = parseBearerChallenge(first.headers.get("www-authenticate"));
20076
+ if (!challenge) return first;
20077
+ const tokenUrl = new URL(challenge.realm);
20078
+ for (const [key, value] of challenge.params.entries()) tokenUrl.searchParams.set(key, value);
20079
+ const tokenRes = await fetch(tokenUrl, { headers: { authorization: `Basic ${upstreamBasicAuth}` } });
20080
+ if (!tokenRes.ok) return first;
20081
+ const tokenJson = await tokenRes.json();
20082
+ const token = tokenJson.token ?? tokenJson.access_token;
20083
+ if (!token) return first;
20084
+ const retryHeaders = new Headers(init.headers);
20085
+ retryHeaders.set("authorization", `Bearer ${token}`);
20086
+ return fetch(url, { ...init, headers: retryHeaders });
20087
+ }
20088
+ function copyResponseHeaders(from, to) {
20089
+ for (const [key, value] of from.entries()) {
20090
+ const lower = key.toLowerCase();
20091
+ if (["connection", "keep-alive", "transfer-encoding", "content-encoding"].includes(lower)) continue;
20092
+ to.setHeader(key, value);
20093
+ }
20094
+ }
20095
+ function createRegistryProxyServer(options) {
20096
+ assertRegistryProxyConfig(options);
20097
+ const upstream = new URL(options.upstream ?? "https://ghcr.io");
20098
+ const namespace = options.allowedNamespace ?? "askexe";
20099
+ const upstreamAuth = Buffer.from(`${options.upstreamUsername ?? "askexe"}:${options.upstreamToken}`).toString("base64");
20100
+ return createServer(async (req, res) => {
20101
+ const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
20102
+ if (requestUrl.pathname === "/health") {
20103
+ res.writeHead(200, { "content-type": "application/json" });
20104
+ res.end(JSON.stringify({ ok: true, service: "exe-registry-proxy", upstream: upstream.origin, namespace }));
20105
+ return;
20106
+ }
20107
+ if (!isAllowedPath(requestUrl.pathname, namespace)) {
20108
+ res.writeHead(404, { "content-type": "application/json" });
20109
+ res.end(JSON.stringify({ error: "registry path not allowed" }));
20110
+ return;
20111
+ }
20112
+ const auth = parseBasicAuth(req.headers.authorization);
20113
+ if (!auth || !timingSafeIncludes(options.pullTokens, auth.password)) {
20114
+ unauthorized(res);
20115
+ return;
20116
+ }
20117
+ const upstreamUrl = new URL(requestUrl.pathname + requestUrl.search, upstream.origin);
20118
+ const headers = new Headers();
20119
+ for (const [key, value] of Object.entries(req.headers)) {
20120
+ if (!value) continue;
20121
+ const lower = key.toLowerCase();
20122
+ if (["host", "connection", "authorization", "content-length"].includes(lower)) continue;
20123
+ headers.set(key, Array.isArray(value) ? value.join(",") : value);
20124
+ }
20125
+ headers.set("authorization", `Basic ${upstreamAuth}`);
20126
+ try {
20127
+ const upstreamRes = await fetchUpstreamWithRegistryAuth(upstreamUrl, {
20128
+ method: req.method,
20129
+ headers,
20130
+ body: req.method === "GET" || req.method === "HEAD" ? void 0 : req,
20131
+ // Required by undici when streaming request bodies.
20132
+ duplex: "half"
20133
+ }, upstreamAuth);
20134
+ res.statusCode = upstreamRes.status;
20135
+ res.statusMessage = upstreamRes.statusText;
20136
+ copyResponseHeaders(upstreamRes.headers, res);
20137
+ if (!upstreamRes.body || req.method === "HEAD") {
20138
+ res.end();
20139
+ return;
20140
+ }
20141
+ Readable.fromWeb(upstreamRes.body).pipe(res);
20142
+ } catch (err) {
20143
+ res.writeHead(502, { "content-type": "application/json" });
20144
+ res.end(JSON.stringify({ error: "upstream registry proxy failed", detail: err instanceof Error ? err.message : String(err) }));
20145
+ }
20146
+ });
20147
+ }
20148
+ async function runRegistryProxy(options = registryProxyOptionsFromEnv()) {
20149
+ const server = createRegistryProxyServer(options);
20150
+ await new Promise((resolve) => server.listen(options.port, options.host, resolve));
20151
+ console.log(`exe-registry-proxy listening on ${options.host ?? "0.0.0.0"}:${options.port}`);
20152
+ console.log(`proxying /v2/${options.allowedNamespace ?? "askexe"}/* -> ${options.upstream ?? "https://ghcr.io"}`);
20153
+ }
20154
+ var init_registry_proxy = __esm({
20155
+ "src/lib/registry-proxy.ts"() {
20156
+ "use strict";
20157
+ }
20158
+ });
20159
+
20160
+ // src/bin/registry-proxy.ts
20161
+ var registry_proxy_exports = {};
20162
+ __export(registry_proxy_exports, {
20163
+ main: () => main7
20164
+ });
20165
+ function printHelp2() {
20166
+ console.log(`exe-os registry-proxy \u2014 authenticated pull-through proxy for AskExe customer images
20167
+
20168
+ Environment:
20169
+ EXE_REGISTRY_PROXY_PORT=3201
20170
+ EXE_REGISTRY_PROXY_HOST=0.0.0.0
20171
+ EXE_REGISTRY_PROXY_UPSTREAM=https://ghcr.io
20172
+ EXE_REGISTRY_PROXY_UPSTREAM_USERNAME=<github-user>
20173
+ EXE_REGISTRY_PROXY_UPSTREAM_TOKEN=<askexe-ghcr-read-token>
20174
+ EXE_REGISTRY_PROXY_PULL_TOKENS=<customer-token-1,customer-token-2>
20175
+ EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE=askexe
20176
+
20177
+ Docker image shape for clients:
20178
+ registry.askexe.com/askexe/exed:v0.9.3
20179
+ registry.askexe.com/askexe/exe-crm:v0.9.3
20180
+ `);
20181
+ }
20182
+ async function main7(args2 = process.argv.slice(2)) {
20183
+ if (args2.includes("--help") || args2.includes("-h")) {
20184
+ printHelp2();
20185
+ return;
20186
+ }
20187
+ await runRegistryProxy(registryProxyOptionsFromEnv());
20188
+ }
20189
+ var init_registry_proxy2 = __esm({
20190
+ "src/bin/registry-proxy.ts"() {
20191
+ "use strict";
20192
+ init_is_main();
20193
+ init_registry_proxy();
20194
+ if (isMainModule(import.meta.url)) {
20195
+ main7().catch((err) => {
20196
+ console.error(err instanceof Error ? err.message : String(err));
20197
+ process.exit(1);
20198
+ });
20199
+ }
20200
+ }
20201
+ });
20202
+
19861
20203
  // node_modules/es-toolkit/dist/function/debounce.mjs
19862
20204
  function debounce(func, debounceMs, { signal, edges } = {}) {
19863
20205
  let pendingThis = void 0;
@@ -24216,8 +24558,8 @@ var init_ErrorOverview = __esm({
24216
24558
  "use strict";
24217
24559
  init_Box();
24218
24560
  init_Text();
24219
- cleanupPath = (path54) => {
24220
- return path54?.replace(`file://${cwd()}/`, "");
24561
+ cleanupPath = (path55) => {
24562
+ return path55?.replace(`file://${cwd()}/`, "");
24221
24563
  };
24222
24564
  stackUtils = new StackUtils({
24223
24565
  cwd: cwd(),
@@ -26625,11 +26967,11 @@ function Footer() {
26625
26967
  } catch {
26626
26968
  }
26627
26969
  try {
26628
- const { existsSync: existsSync40 } = await import("fs");
26970
+ const { existsSync: existsSync41 } = await import("fs");
26629
26971
  const { join } = await import("path");
26630
26972
  const home = process.env.HOME ?? "";
26631
26973
  const pidPath = join(home, ".exe-os", "exed.pid");
26632
- setDaemon(existsSync40(pidPath) ? "running" : "stopped");
26974
+ setDaemon(existsSync41(pidPath) ? "running" : "stopped");
26633
26975
  } catch {
26634
26976
  setDaemon("unknown");
26635
26977
  }
@@ -29413,15 +29755,15 @@ function CommandCenterView({
29413
29755
  const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
29414
29756
  const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
29415
29757
  const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
29416
- const { readFileSync: readFileSync35, existsSync: existsSync40 } = await import("fs");
29758
+ const { readFileSync: readFileSync36, existsSync: existsSync41 } = await import("fs");
29417
29759
  const { join } = await import("path");
29418
29760
  const { homedir: homedir8 } = await import("os");
29419
29761
  const configPath = join(homedir8(), ".exe-os", "config.json");
29420
29762
  let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
29421
29763
  let providerConfigs = {};
29422
- if (existsSync40(configPath)) {
29764
+ if (existsSync41(configPath)) {
29423
29765
  try {
29424
- const raw = JSON.parse(readFileSync35(configPath, "utf8"));
29766
+ const raw = JSON.parse(readFileSync36(configPath, "utf8"));
29425
29767
  if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
29426
29768
  if (raw.providers && typeof raw.providers === "object") {
29427
29769
  providerConfigs = raw.providers;
@@ -29482,7 +29824,7 @@ function CommandCenterView({
29482
29824
  const markerDir = join(homedir8(), ".exe-os", "session-cache");
29483
29825
  const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
29484
29826
  for (const f of agentFiles) {
29485
- const data = JSON.parse(readFileSync35(join(markerDir, f), "utf8"));
29827
+ const data = JSON.parse(readFileSync36(join(markerDir, f), "utf8"));
29486
29828
  if (data.agentRole) {
29487
29829
  agentRole = data.agentRole;
29488
29830
  break;
@@ -29665,7 +30007,7 @@ function CommandCenterView({
29665
30007
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
29666
30008
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
29667
30009
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
29668
- const { existsSync: existsSync40 } = await import("fs");
30010
+ const { existsSync: existsSync41 } = await import("fs");
29669
30011
  const { join } = await import("path");
29670
30012
  const client = getClient2();
29671
30013
  if (!client) {
@@ -29736,7 +30078,7 @@ function CommandCenterView({
29736
30078
  }
29737
30079
  const memoryCount = memoryCounts.get(name) ?? 0;
29738
30080
  const openTaskCount = openTaskCounts.get(name) ?? 0;
29739
- const hasGit = projectDir ? existsSync40(join(projectDir, ".git")) : false;
30081
+ const hasGit = projectDir ? existsSync41(join(projectDir, ".git")) : false;
29740
30082
  const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
29741
30083
  projectList.push({
29742
30084
  projectName: name,
@@ -29761,7 +30103,7 @@ function CommandCenterView({
29761
30103
  setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
29762
30104
  try {
29763
30105
  const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
29764
- setHealth((h) => ({ ...h, daemon: existsSync40(pidPath) ? "running" : "stopped" }));
30106
+ setHealth((h) => ({ ...h, daemon: existsSync41(pidPath) ? "running" : "stopped" }));
29765
30107
  } catch {
29766
30108
  }
29767
30109
  const activityResult = await client.execute(
@@ -31884,12 +32226,12 @@ async function loadGatewayConfig() {
31884
32226
  state.running = false;
31885
32227
  }
31886
32228
  try {
31887
- const { existsSync: existsSync40, readFileSync: readFileSync35 } = await import("fs");
32229
+ const { existsSync: existsSync41, readFileSync: readFileSync36 } = await import("fs");
31888
32230
  const { join } = await import("path");
31889
32231
  const home = process.env.HOME ?? "";
31890
32232
  const configPath = join(home, ".exe-os", "gateway.json");
31891
- if (existsSync40(configPath)) {
31892
- const raw = JSON.parse(readFileSync35(configPath, "utf8"));
32233
+ if (existsSync41(configPath)) {
32234
+ const raw = JSON.parse(readFileSync36(configPath, "utf8"));
31893
32235
  state.port = raw.port ?? 3100;
31894
32236
  state.gatewayUrl = raw.gatewayUrl ?? "";
31895
32237
  if (raw.adapters) {
@@ -32487,12 +32829,12 @@ function TeamView({ onBack, onViewSessions }) {
32487
32829
  setMembers(teamData);
32488
32830
  setDbError(null);
32489
32831
  try {
32490
- const { existsSync: existsSync40, readFileSync: readFileSync35 } = await import("fs");
32832
+ const { existsSync: existsSync41, readFileSync: readFileSync36 } = await import("fs");
32491
32833
  const { join } = await import("path");
32492
32834
  const home = process.env.HOME ?? "";
32493
32835
  const gatewayConfig = join(home, ".exe-os", "gateway.json");
32494
- if (existsSync40(gatewayConfig)) {
32495
- const raw = JSON.parse(readFileSync35(gatewayConfig, "utf8"));
32836
+ if (existsSync41(gatewayConfig)) {
32837
+ const raw = JSON.parse(readFileSync36(gatewayConfig, "utf8"));
32496
32838
  if (raw.agents && raw.agents.length > 0) {
32497
32839
  setExternals(raw.agents.map((a) => ({
32498
32840
  name: a.name,
@@ -32672,8 +33014,8 @@ __export(wiki_client_exports, {
32672
33014
  listDocuments: () => listDocuments,
32673
33015
  listWorkspaces: () => listWorkspaces
32674
33016
  });
32675
- async function wikiFetch(config, path54, method = "GET", body) {
32676
- const url = `${config.baseUrl}/api/v1${path54}`;
33017
+ async function wikiFetch(config, path55, method = "GET", body) {
33018
+ const url = `${config.baseUrl}/api/v1${path55}`;
32677
33019
  const headers = {
32678
33020
  Authorization: `Bearer ${config.apiKey}`,
32679
33021
  "Content-Type": "application/json"
@@ -32706,7 +33048,7 @@ async function wikiFetch(config, path54, method = "GET", body) {
32706
33048
  }
32707
33049
  }
32708
33050
  if (!response.ok) {
32709
- throw new Error(`Wiki API ${method} ${path54}: ${response.status} ${response.statusText}`);
33051
+ throw new Error(`Wiki API ${method} ${path55}: ${response.status} ${response.statusText}`);
32710
33052
  }
32711
33053
  return response.json();
32712
33054
  } finally {
@@ -33300,12 +33642,12 @@ function SettingsView({ onBack }) {
33300
33642
  }
33301
33643
  setProviders(providerList);
33302
33644
  try {
33303
- const { existsSync: existsSync40 } = await import("fs");
33645
+ const { existsSync: existsSync41 } = await import("fs");
33304
33646
  const { join } = await import("path");
33305
33647
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
33306
33648
  const cfg = await loadConfig2();
33307
33649
  const home = process.env.HOME ?? "";
33308
- const hasKey = existsSync40(join(home, ".exe-os", "master.key"));
33650
+ const hasKey = existsSync41(join(home, ".exe-os", "master.key"));
33309
33651
  if (cfg.cloud) {
33310
33652
  setCloud({
33311
33653
  configured: true,
@@ -33318,22 +33660,22 @@ function SettingsView({ onBack }) {
33318
33660
  const pidPath = join(home, ".exe-os", "exed.pid");
33319
33661
  let daemon = "unknown";
33320
33662
  try {
33321
- daemon = existsSync40(pidPath) ? "running" : "stopped";
33663
+ daemon = existsSync41(pidPath) ? "running" : "stopped";
33322
33664
  } catch {
33323
33665
  }
33324
33666
  let version = "unknown";
33325
33667
  try {
33326
- const { readFileSync: readFileSync35 } = await import("fs");
33668
+ const { readFileSync: readFileSync36 } = await import("fs");
33327
33669
  const { createRequire: createRequire3 } = await import("module");
33328
33670
  const require2 = createRequire3(import.meta.url);
33329
33671
  const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
33330
- const pkg = JSON.parse(readFileSync35(pkgPath, "utf8"));
33672
+ const pkg = JSON.parse(readFileSync36(pkgPath, "utf8"));
33331
33673
  version = pkg.version;
33332
33674
  } catch {
33333
33675
  try {
33334
- const { readFileSync: readFileSync35 } = await import("fs");
33676
+ const { readFileSync: readFileSync36 } = await import("fs");
33335
33677
  const { join: joinPath } = await import("path");
33336
- const pkg = JSON.parse(readFileSync35(joinPath(process.cwd(), "package.json"), "utf8"));
33678
+ const pkg = JSON.parse(readFileSync36(joinPath(process.cwd(), "package.json"), "utf8"));
33337
33679
  version = pkg.version;
33338
33680
  } catch {
33339
33681
  }
@@ -33941,6 +34283,662 @@ Unhandled rejection: ${reason}
33941
34283
  }
33942
34284
  });
33943
34285
 
34286
+ // src/lib/code-chunker.ts
34287
+ import ts from "typescript";
34288
+ function languageForFile(filePath) {
34289
+ const base = filePath.split(/[\\/]/).pop()?.toLowerCase() ?? "";
34290
+ if (["dockerfile", "makefile", "rakefile", "gemfile"].includes(base)) return base;
34291
+ const ext = base.includes(".") ? base.split(".").pop() : void 0;
34292
+ return ext ? LANGUAGE_BY_EXTENSION[ext] : void 0;
34293
+ }
34294
+ function chunkSourceFile(source, fileName = "file.ts") {
34295
+ const language = languageForFile(fileName);
34296
+ if (language === "typescript" || language === "javascript") {
34297
+ return chunkTypeScriptLike(source, fileName);
34298
+ }
34299
+ return chunkGenericSource(source, fileName, language);
34300
+ }
34301
+ function chunkTypeScriptLike(source, fileName) {
34302
+ const sourceFile = ts.createSourceFile(
34303
+ fileName,
34304
+ source,
34305
+ ts.ScriptTarget.Latest,
34306
+ true,
34307
+ fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
34308
+ );
34309
+ const chunks = [];
34310
+ const lines = source.split("\n");
34311
+ const importLines = [];
34312
+ function getLineNumber(pos) {
34313
+ return sourceFile.getLineAndCharacterOfPosition(pos).line + 1;
34314
+ }
34315
+ function getLeadingComment(node) {
34316
+ const fullText = sourceFile.getFullText();
34317
+ const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
34318
+ if (!ranges || ranges.length === 0) return void 0;
34319
+ return ranges.map((r) => fullText.slice(r.pos, r.end)).join("\n");
34320
+ }
34321
+ function getNodeText(node) {
34322
+ return node.getText(sourceFile);
34323
+ }
34324
+ function getName(node) {
34325
+ if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
34326
+ return node.name?.getText(sourceFile) ?? "(anonymous)";
34327
+ }
34328
+ if (ts.isClassDeclaration(node)) return node.name?.getText(sourceFile) ?? "(anonymous class)";
34329
+ if (ts.isInterfaceDeclaration(node)) return node.name.getText(sourceFile);
34330
+ if (ts.isTypeAliasDeclaration(node)) return node.name.getText(sourceFile);
34331
+ if (ts.isEnumDeclaration(node)) return node.name.getText(sourceFile);
34332
+ if (ts.isVariableStatement(node)) return node.declarationList.declarations.map((d) => d.name.getText(sourceFile)).join(", ");
34333
+ if (ts.isExportAssignment(node)) return "default export";
34334
+ return "(unknown)";
34335
+ }
34336
+ function visitTopLevel(node) {
34337
+ if (ts.isImportDeclaration(node)) {
34338
+ importLines.push({ start: getLineNumber(node.getStart(sourceFile)), end: getLineNumber(node.getEnd()) });
34339
+ return;
34340
+ }
34341
+ if (ts.isFunctionDeclaration(node)) {
34342
+ chunks.push({ kind: "function", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
34343
+ return;
34344
+ }
34345
+ if (ts.isClassDeclaration(node)) {
34346
+ chunks.push({ kind: "class", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
34347
+ return;
34348
+ }
34349
+ if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node)) {
34350
+ chunks.push({ kind: "type", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
34351
+ return;
34352
+ }
34353
+ if (ts.isVariableStatement(node)) {
34354
+ const isFnLike = node.declarationList.declarations.some((d) => d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer)));
34355
+ chunks.push({ kind: isFnLike ? "function" : "variable", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
34356
+ return;
34357
+ }
34358
+ if (ts.isExportAssignment(node)) {
34359
+ chunks.push({ kind: "export", name: "default export", text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
34360
+ return;
34361
+ }
34362
+ if (ts.isExpressionStatement(node)) {
34363
+ const text = getNodeText(node);
34364
+ if (text.length > 10) chunks.push({ kind: "other", name: text.slice(0, 40).replace(/\n/g, " "), text, startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()) });
34365
+ }
34366
+ }
34367
+ sourceFile.statements.forEach(visitTopLevel);
34368
+ if (importLines.length > 0) {
34369
+ const startLine = importLines[0].start;
34370
+ const endLine = importLines[importLines.length - 1].end;
34371
+ chunks.unshift({ kind: "import", name: `${importLines.length} imports`, text: lines.slice(startLine - 1, endLine).join("\n"), startLine, endLine });
34372
+ }
34373
+ chunks.sort((a, b) => a.startLine - b.startLine);
34374
+ return chunks.length > 0 ? chunks : chunkByWindows(source, 80);
34375
+ }
34376
+ function chunkGenericSource(source, _fileName, language) {
34377
+ const lines = source.split("\n");
34378
+ if (source.trim().length === 0) return [];
34379
+ if (language && TEXT_LIKE_LANGUAGES.has(language)) return chunkTextLike(lines, language);
34380
+ const chunks = [];
34381
+ const patterns = [
34382
+ { kind: "function", regex: /^\s*(?:async\s+)?def\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
34383
+ { kind: "class", regex: /^\s*class\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
34384
+ { kind: "function", regex: /^\s*(?:pub\s+)?fn\s+([A-Za-z_][\w]*)\s*[<(]/, name: (m) => m[1] },
34385
+ { kind: "class", regex: /^\s*(?:pub\s+)?(?:struct|enum|trait)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
34386
+ { kind: "function", regex: /^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
34387
+ { kind: "function", regex: /^\s*(?:public|private|protected|static|final|suspend|fun|override|open|internal|export|async|func|function|subroutine)\s+.*?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
34388
+ { kind: "function", regex: /^\s*function\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
34389
+ { kind: "type", regex: /^\s*(?:interface|type|enum|record|data\s+class|case\s+class|contract|library)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
34390
+ { kind: "section", regex: /^\s{0,3}#{1,6}\s+(.+)$/, name: (m) => m[1].trim() }
34391
+ ];
34392
+ const starts = [];
34393
+ for (let i = 0; i < lines.length; i++) {
34394
+ const line = lines[i];
34395
+ for (const pattern of patterns) {
34396
+ const match = line.match(pattern.regex);
34397
+ if (match) {
34398
+ starts.push({ line: i + 1, kind: pattern.kind, name: pattern.name(match) });
34399
+ break;
34400
+ }
34401
+ }
34402
+ }
34403
+ if (starts.length === 0) return chunkByWindows(source, 80);
34404
+ for (let i = 0; i < starts.length; i++) {
34405
+ const start = starts[i];
34406
+ const next = starts[i + 1]?.line ?? lines.length + 1;
34407
+ const endLine = Math.max(start.line, next - 1);
34408
+ chunks.push({
34409
+ kind: start.kind,
34410
+ name: start.name,
34411
+ text: lines.slice(start.line - 1, endLine).join("\n"),
34412
+ startLine: start.line,
34413
+ endLine
34414
+ });
34415
+ }
34416
+ return chunks;
34417
+ }
34418
+ function chunkTextLike(lines, language) {
34419
+ if (language === "markdown" || language === "mdx") {
34420
+ const starts = lines.map((line, i) => ({ line, i })).filter(({ line }) => /^\s{0,3}#{1,6}\s+/.test(line));
34421
+ if (starts.length > 0) {
34422
+ return starts.map((start, idx) => {
34423
+ const end = starts[idx + 1]?.i ?? lines.length;
34424
+ const title = start.line.replace(/^\s{0,3}#{1,6}\s+/, "").trim();
34425
+ return { kind: "section", name: title, text: lines.slice(start.i, end).join("\n"), startLine: start.i + 1, endLine: end };
34426
+ });
34427
+ }
34428
+ }
34429
+ return chunkByWindows(lines.join("\n"), 80);
34430
+ }
34431
+ function chunkByWindows(source, windowLines) {
34432
+ const lines = source.split("\n");
34433
+ const chunks = [];
34434
+ for (let i = 0; i < lines.length; i += windowLines) {
34435
+ const end = Math.min(lines.length, i + windowLines);
34436
+ const text = lines.slice(i, end).join("\n");
34437
+ if (text.trim()) chunks.push({ kind: "other", name: `lines ${i + 1}-${end}`, text, startLine: i + 1, endLine: end });
34438
+ }
34439
+ return chunks;
34440
+ }
34441
+ function summarizeChunk(chunk, filePath) {
34442
+ const location = `${filePath}:${chunk.startLine}-${chunk.endLine}`;
34443
+ const comment = chunk.comment ? chunk.comment.replace(/\/\*\*|\*\/|\*\s?/g, "").trim().split("\n")[0] : "";
34444
+ switch (chunk.kind) {
34445
+ case "function":
34446
+ return `Function ${chunk.name} in ${location}${comment ? ` \u2014 ${comment}` : ""}`;
34447
+ case "class":
34448
+ return `Class ${chunk.name} in ${location}${comment ? ` \u2014 ${comment}` : ""}`;
34449
+ case "type":
34450
+ return `Type ${chunk.name} in ${location}`;
34451
+ case "import":
34452
+ return `Imports (${chunk.name}) in ${filePath}`;
34453
+ case "variable":
34454
+ return `Variable ${chunk.name} in ${location}`;
34455
+ case "export":
34456
+ return `Default export in ${location}`;
34457
+ case "section":
34458
+ return `Section ${chunk.name} in ${location}`;
34459
+ default:
34460
+ return `${chunk.kind} in ${location}`;
34461
+ }
34462
+ }
34463
+ function isChunkable(filePath) {
34464
+ return Boolean(languageForFile(filePath));
34465
+ }
34466
+ var LANGUAGE_BY_EXTENSION, TEXT_LIKE_LANGUAGES;
34467
+ var init_code_chunker = __esm({
34468
+ "src/lib/code-chunker.ts"() {
34469
+ "use strict";
34470
+ LANGUAGE_BY_EXTENSION = {
34471
+ c: "c",
34472
+ cc: "cpp",
34473
+ cpp: "cpp",
34474
+ cxx: "cpp",
34475
+ h: "c",
34476
+ hh: "cpp",
34477
+ hpp: "cpp",
34478
+ cs: "csharp",
34479
+ css: "css",
34480
+ scss: "scss",
34481
+ f: "fortran",
34482
+ f90: "fortran",
34483
+ f95: "fortran",
34484
+ go: "go",
34485
+ html: "html",
34486
+ htm: "html",
34487
+ java: "java",
34488
+ js: "javascript",
34489
+ jsx: "javascript",
34490
+ json: "json",
34491
+ kt: "kotlin",
34492
+ kts: "kotlin",
34493
+ lua: "lua",
34494
+ md: "markdown",
34495
+ mdx: "mdx",
34496
+ pas: "pascal",
34497
+ php: "php",
34498
+ py: "python",
34499
+ r: "r",
34500
+ rb: "ruby",
34501
+ rs: "rust",
34502
+ scala: "scala",
34503
+ sc: "scala",
34504
+ sol: "solidity",
34505
+ sql: "sql",
34506
+ svelte: "svelte",
34507
+ swift: "swift",
34508
+ toml: "toml",
34509
+ ts: "typescript",
34510
+ tsx: "typescript",
34511
+ vue: "vue",
34512
+ xml: "xml",
34513
+ yaml: "yaml",
34514
+ yml: "yaml",
34515
+ sh: "shell",
34516
+ bash: "shell",
34517
+ zsh: "shell"
34518
+ };
34519
+ TEXT_LIKE_LANGUAGES = /* @__PURE__ */ new Set(["json", "markdown", "mdx", "toml", "yaml", "xml", "html", "css", "scss", "sql"]);
34520
+ }
34521
+ });
34522
+
34523
+ // src/lib/code-context-index.ts
34524
+ var code_context_index_exports = {};
34525
+ __export(code_context_index_exports, {
34526
+ analyzeBlastRadius: () => analyzeBlastRadius,
34527
+ buildCodeContextIndex: () => buildCodeContextIndex,
34528
+ getCodeContextIndexPath: () => getCodeContextIndexPath,
34529
+ getCodeContextStats: () => getCodeContextStats,
34530
+ loadOrBuildCodeContextIndex: () => loadOrBuildCodeContextIndex,
34531
+ searchCodeContext: () => searchCodeContext,
34532
+ traceCodeSymbol: () => traceCodeSymbol
34533
+ });
34534
+ import crypto14 from "crypto";
34535
+ import path50 from "path";
34536
+ import { existsSync as existsSync36, mkdirSync as mkdirSync24, readFileSync as readFileSync31, readdirSync as readdirSync11, statSync as statSync7, writeFileSync as writeFileSync25 } from "fs";
34537
+ import { spawnSync } from "child_process";
34538
+ function normalizeProjectRoot(projectRoot) {
34539
+ return path50.resolve(projectRoot || process.cwd());
34540
+ }
34541
+ function hashText(text) {
34542
+ return crypto14.createHash("sha256").update(text).digest("hex");
34543
+ }
34544
+ function indexDir() {
34545
+ const dir = path50.join(EXE_AI_DIR, "code-context");
34546
+ mkdirSync24(dir, { recursive: true });
34547
+ return dir;
34548
+ }
34549
+ function getCodeContextIndexPath(projectRoot) {
34550
+ const root = normalizeProjectRoot(projectRoot);
34551
+ const rootHash = hashText(root).slice(0, 16);
34552
+ return path50.join(indexDir(), `${rootHash}.json`);
34553
+ }
34554
+ function currentBranch(projectRoot) {
34555
+ const result = spawnSync("git", ["branch", "--show-current"], { cwd: projectRoot, encoding: "utf8", timeout: 2e3 });
34556
+ const branch = result.status === 0 ? result.stdout.trim() : "";
34557
+ return branch || "detached-or-unknown";
34558
+ }
34559
+ function shouldIgnore(relPath) {
34560
+ const parts = relPath.split(/[\\/]/);
34561
+ return parts.some((part) => IGNORE_SEGMENTS.has(part));
34562
+ }
34563
+ function listRecursive(projectRoot, dir = projectRoot, out = []) {
34564
+ for (const entry of readdirSync11(dir, { withFileTypes: true })) {
34565
+ const abs = path50.join(dir, entry.name);
34566
+ const rel = path50.relative(projectRoot, abs).replaceAll(path50.sep, "/");
34567
+ if (shouldIgnore(rel)) continue;
34568
+ if (entry.isDirectory()) listRecursive(projectRoot, abs, out);
34569
+ else if (entry.isFile()) out.push(rel);
34570
+ }
34571
+ return out;
34572
+ }
34573
+ function listCodeFiles(projectRoot, maxFiles) {
34574
+ const git = spawnSync("git", ["ls-files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
34575
+ let files = [];
34576
+ if (git.status === 0 && git.stdout.trim()) {
34577
+ files = git.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
34578
+ } else {
34579
+ const rg = spawnSync("rg", ["--files"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3, maxBuffer: 1024 * 1024 * 16 });
34580
+ files = rg.status === 0 && rg.stdout.trim() ? rg.stdout.split("\n").map((s) => s.trim()).filter(Boolean) : listRecursive(projectRoot);
34581
+ }
34582
+ return files.map((file) => file.replaceAll(path50.sep, "/")).filter((file) => isChunkable(file) && !shouldIgnore(file)).slice(0, maxFiles).sort();
34583
+ }
34584
+ function parseImportPaths(importText) {
34585
+ const paths = [];
34586
+ const patterns = [/from\s+["']([^"']+)["']/g, /^import\s+["']([^"']+)["']/gm, /require\(["']([^"']+)["']\)/g, /use\s+([A-Za-z0-9_:]+)::/g, /#include\s+[<"]([^>"]+)[>"]/g];
34587
+ for (const regex of patterns) {
34588
+ let match;
34589
+ while ((match = regex.exec(importText)) !== null) if (!paths.includes(match[1])) paths.push(match[1]);
34590
+ }
34591
+ return paths;
34592
+ }
34593
+ function resolveImport(fromFile, importPath, allFiles) {
34594
+ if (!importPath.startsWith(".")) return null;
34595
+ const base = path50.posix.normalize(path50.posix.join(path50.posix.dirname(fromFile.replaceAll(path50.sep, "/")), importPath));
34596
+ const withoutKnownExt = base.replace(/\.(?:[a-z0-9]+)$/i, "");
34597
+ const candidates = [base];
34598
+ for (const ext of ["ts", "tsx", "js", "jsx", "py", "rs", "go", "java", "cs", "cpp", "c", "rb", "php", "swift", "kt", "scala", "sql", "md", "json", "yaml", "yml"]) {
34599
+ candidates.push(`${withoutKnownExt}.${ext}`, `${base}.${ext}`);
34600
+ }
34601
+ for (const indexName of ["index.ts", "index.tsx", "index.js", "mod.rs", "__init__.py"]) candidates.push(path50.posix.join(base, indexName));
34602
+ return candidates.find((candidate) => allFiles.has(candidate)) ?? null;
34603
+ }
34604
+ function symbolId(filePath, chunk) {
34605
+ return hashText(`${filePath}:${chunk.kind}:${chunk.name}:${chunk.startLine}:${chunk.endLine}`).slice(0, 24);
34606
+ }
34607
+ function loadIndex(projectRoot) {
34608
+ const file = getCodeContextIndexPath(projectRoot);
34609
+ if (!existsSync36(file)) return null;
34610
+ try {
34611
+ const parsed = JSON.parse(readFileSync31(file, "utf8"));
34612
+ if (parsed.version !== INDEX_VERSION || parsed.projectRoot !== projectRoot) return null;
34613
+ return parsed;
34614
+ } catch {
34615
+ return null;
34616
+ }
34617
+ }
34618
+ function saveIndex(index) {
34619
+ writeFileSync25(getCodeContextIndexPath(index.projectRoot), JSON.stringify(index, null, 2));
34620
+ }
34621
+ function buildFileRecord(projectRoot, relPath, allFiles, previous) {
34622
+ const absPath = path50.join(projectRoot, relPath);
34623
+ let stat2;
34624
+ try {
34625
+ stat2 = statSync7(absPath);
34626
+ } catch {
34627
+ return { record: null, reused: false };
34628
+ }
34629
+ if (!stat2.isFile()) return { record: null, reused: false };
34630
+ const language = languageForFile(relPath);
34631
+ if (!language || !isChunkable(relPath)) return { record: null, reused: false };
34632
+ const source = readFileSync31(absPath, "utf8");
34633
+ const hash = hashText(source);
34634
+ if (previous && previous.hash === hash && previous.mtimeMs === stat2.mtimeMs && previous.size === stat2.size && previous.language === language) {
34635
+ return { record: previous, reused: true };
34636
+ }
34637
+ const chunks = chunkSourceFile(source, relPath);
34638
+ const imports = chunks.filter((chunk) => chunk.kind === "import").flatMap((chunk) => parseImportPaths(chunk.text));
34639
+ const resolvedImports = imports.map((importPath) => resolveImport(relPath, importPath, allFiles)).filter((file) => Boolean(file));
34640
+ const symbols = chunks.filter((chunk) => chunk.name !== "(unknown)").map((chunk) => ({
34641
+ id: symbolId(relPath, chunk),
34642
+ name: chunk.name,
34643
+ kind: chunk.kind,
34644
+ filePath: relPath,
34645
+ language,
34646
+ startLine: chunk.startLine,
34647
+ endLine: chunk.endLine,
34648
+ summary: summarizeChunk(chunk, relPath),
34649
+ text: chunk.text.slice(0, 8e3),
34650
+ comment: chunk.comment
34651
+ }));
34652
+ return { record: { path: relPath, absPath, language, hash, mtimeMs: stat2.mtimeMs, size: stat2.size, imports, resolvedImports, symbols }, reused: false };
34653
+ }
34654
+ function buildCodeContextIndex(options = {}) {
34655
+ const projectRoot = normalizeProjectRoot(options.projectRoot);
34656
+ const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
34657
+ const branch = currentBranch(projectRoot);
34658
+ const previous = options.force ? null : loadIndex(projectRoot);
34659
+ const files = listCodeFiles(projectRoot, maxFiles);
34660
+ const allFiles = new Set(files.map((file) => file.replaceAll(path50.sep, "/")));
34661
+ const fileRecords = {};
34662
+ let rebuiltFiles = 0;
34663
+ let reusedFiles = 0;
34664
+ let skippedFiles = 0;
34665
+ for (const rel of files) {
34666
+ const normalized = rel.replaceAll(path50.sep, "/");
34667
+ const { record, reused } = buildFileRecord(projectRoot, normalized, allFiles, previous?.files[normalized]);
34668
+ if (record) {
34669
+ fileRecords[normalized] = record;
34670
+ if (reused) reusedFiles++;
34671
+ else rebuiltFiles++;
34672
+ } else skippedFiles++;
34673
+ }
34674
+ const languageBreakdown = {};
34675
+ for (const file of Object.values(fileRecords)) languageBreakdown[file.language] = (languageBreakdown[file.language] ?? 0) + 1;
34676
+ const index = {
34677
+ version: INDEX_VERSION,
34678
+ projectRoot,
34679
+ rootHash: hashText(projectRoot).slice(0, 16),
34680
+ branch,
34681
+ indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
34682
+ stats: { filesSeen: files.length, filesIndexed: Object.keys(fileRecords).length, rebuiltFiles, reusedFiles, skippedFiles, languageBreakdown },
34683
+ files: fileRecords
34684
+ };
34685
+ saveIndex(index);
34686
+ return index;
34687
+ }
34688
+ function loadOrBuildCodeContextIndex(options = {}) {
34689
+ const projectRoot = normalizeProjectRoot(options.projectRoot);
34690
+ if (!options.force) {
34691
+ const loaded = loadIndex(projectRoot);
34692
+ if (loaded) {
34693
+ const currentFiles = listCodeFiles(projectRoot, options.maxFiles ?? DEFAULT_MAX_FILES);
34694
+ const unchanged = currentFiles.every((rel) => {
34695
+ const normalized = rel.replaceAll(path50.sep, "/");
34696
+ const existing = loaded.files[normalized];
34697
+ if (!existing) return false;
34698
+ try {
34699
+ const stat2 = statSync7(path50.join(projectRoot, normalized));
34700
+ return stat2.mtimeMs === existing.mtimeMs && stat2.size === existing.size;
34701
+ } catch {
34702
+ return false;
34703
+ }
34704
+ });
34705
+ if (unchanged && Object.keys(loaded.files).length === currentFiles.length) return loaded;
34706
+ }
34707
+ }
34708
+ return buildCodeContextIndex(options);
34709
+ }
34710
+ function normalizeLanguage(language) {
34711
+ return language.toLowerCase().replace(/^c\+\+$/, "cpp").replace(/^c#$/, "csharp").replace(/^js$/, "javascript").replace(/^ts$/, "typescript");
34712
+ }
34713
+ function tokenize2(query) {
34714
+ const raw = query.toLowerCase().replace(/([a-z])([A-Z])/g, "$1 $2").split(/[^a-z0-9_.$/-]+/).map((s) => s.trim()).filter((s) => s.length >= 2);
34715
+ const expanded = /* @__PURE__ */ new Set();
34716
+ for (const term of raw) {
34717
+ expanded.add(term);
34718
+ for (const part of term.split(/[_.$/-]+/)) if (part.length >= 2) expanded.add(part);
34719
+ const dashed = term.replace(/[_.$/]+/g, "-");
34720
+ if (dashed.length >= 2) expanded.add(dashed);
34721
+ if (!term.includes("-")) expanded.add(`${term}s`);
34722
+ if (term.endsWith("s") && term.length > 3) expanded.add(term.slice(0, -1));
34723
+ }
34724
+ return [...expanded];
34725
+ }
34726
+ function ngrams(terms) {
34727
+ const grams = [...terms];
34728
+ for (let i = 0; i < terms.length - 1; i++) grams.push(`${terms[i]} ${terms[i + 1]}`);
34729
+ return grams;
34730
+ }
34731
+ function globToRegex(pattern) {
34732
+ let out = "";
34733
+ for (let i = 0; i < pattern.length; i++) {
34734
+ const ch = pattern[i];
34735
+ const next = pattern[i + 1];
34736
+ if (ch === "*" && next === "*") {
34737
+ out += ".*";
34738
+ i++;
34739
+ continue;
34740
+ }
34741
+ if (ch === "*") {
34742
+ out += "[^/]*";
34743
+ continue;
34744
+ }
34745
+ if (".+^${}()|[]\\".includes(ch)) out += `\\${ch}`;
34746
+ else out += ch;
34747
+ }
34748
+ return new RegExp(`^${out}$`);
34749
+ }
34750
+ function matchesPath(filePath, patterns) {
34751
+ if (!patterns || patterns.length === 0) return true;
34752
+ const normalized = filePath.replaceAll(path50.sep, "/");
34753
+ return patterns.some((pattern) => {
34754
+ const p = pattern.replaceAll(path50.sep, "/").replace(/^\.\//, "");
34755
+ return normalized === p || normalized.startsWith(`${p}/`) || normalized.endsWith(p) || globToRegex(p).test(normalized);
34756
+ });
34757
+ }
34758
+ function scoreSymbol(symbol, terms) {
34759
+ const grams = ngrams(terms);
34760
+ const haystacks = {
34761
+ name: symbol.name.toLowerCase(),
34762
+ path: symbol.filePath.toLowerCase(),
34763
+ language: symbol.language.toLowerCase(),
34764
+ summary: symbol.summary.toLowerCase(),
34765
+ text: symbol.text.toLowerCase()
34766
+ };
34767
+ let score = 0;
34768
+ const matches = [];
34769
+ for (const term of terms) {
34770
+ if (haystacks.name === term) {
34771
+ score += 100;
34772
+ matches.push(`name=${term}`);
34773
+ continue;
34774
+ }
34775
+ if (haystacks.name.includes(term)) {
34776
+ score += 45;
34777
+ matches.push(`name~${term}`);
34778
+ }
34779
+ if (haystacks.path.includes(term)) {
34780
+ score += 18;
34781
+ matches.push(`path~${term}`);
34782
+ }
34783
+ if (haystacks.language === term) {
34784
+ score += 18;
34785
+ matches.push(`language=${term}`);
34786
+ }
34787
+ if (haystacks.summary.includes(term)) {
34788
+ score += 16;
34789
+ matches.push(`summary~${term}`);
34790
+ }
34791
+ if (haystacks.text.includes(term)) {
34792
+ score += 5;
34793
+ matches.push(`text~${term}`);
34794
+ }
34795
+ }
34796
+ for (const gram of grams.filter((g) => g.includes(" "))) {
34797
+ if (haystacks.text.includes(gram) || haystacks.summary.includes(gram)) {
34798
+ score += 20;
34799
+ matches.push(`phrase~${gram}`);
34800
+ }
34801
+ }
34802
+ const uniqueMatches = new Set(matches.map((m) => m.replace(/^[^=~]+[=~]/, ""))).size;
34803
+ score += uniqueMatches * 3;
34804
+ return { score, matches };
34805
+ }
34806
+ function filteredFiles(index, options = {}) {
34807
+ const languages = options.languages?.map(normalizeLanguage).filter(Boolean);
34808
+ return Object.values(index.files).filter((file) => {
34809
+ if (languages && languages.length > 0 && !languages.includes(normalizeLanguage(file.language))) return false;
34810
+ return matchesPath(file.path, options.paths);
34811
+ });
34812
+ }
34813
+ function searchCodeContext(query, options = {}) {
34814
+ const terms = tokenize2(query);
34815
+ if (terms.length === 0) return [];
34816
+ const index = loadOrBuildCodeContextIndex({ projectRoot: options.projectRoot, force: options.force || options.refreshIndex, maxFiles: options.maxFiles });
34817
+ const results = [];
34818
+ for (const file of filteredFiles(index, options)) {
34819
+ for (const symbol of file.symbols) {
34820
+ const scored = scoreSymbol(symbol, terms);
34821
+ if (scored.score > 0) {
34822
+ results.push({
34823
+ symbol,
34824
+ score: scored.score,
34825
+ matches: scored.matches,
34826
+ filePath: symbol.filePath,
34827
+ language: symbol.language,
34828
+ content: symbol.text,
34829
+ startLine: symbol.startLine,
34830
+ endLine: symbol.endLine
34831
+ });
34832
+ }
34833
+ }
34834
+ }
34835
+ const offset = Math.max(0, options.offset ?? 0);
34836
+ const limit = options.limit ?? 20;
34837
+ return results.sort((a, b) => b.score - a.score || a.filePath.localeCompare(b.filePath)).slice(offset, offset + limit);
34838
+ }
34839
+ function dependentsMap(index) {
34840
+ const map = /* @__PURE__ */ new Map();
34841
+ for (const file of Object.values(index.files)) {
34842
+ for (const dep of file.resolvedImports) {
34843
+ if (!map.has(dep)) map.set(dep, /* @__PURE__ */ new Set());
34844
+ map.get(dep).add(file.path);
34845
+ }
34846
+ }
34847
+ return map;
34848
+ }
34849
+ function findSymbols(index, symbolName, limit = 20) {
34850
+ const q = symbolName.toLowerCase();
34851
+ const exact = [];
34852
+ const fuzzy = [];
34853
+ for (const file of Object.values(index.files)) {
34854
+ for (const symbol of file.symbols) {
34855
+ if (symbol.name.toLowerCase() === q) exact.push(symbol);
34856
+ else if (symbol.name.toLowerCase().includes(q) || symbol.summary.toLowerCase().includes(q)) fuzzy.push(symbol);
34857
+ }
34858
+ }
34859
+ return [...exact, ...fuzzy].slice(0, limit);
34860
+ }
34861
+ function traceCodeSymbol(symbolName, options = {}) {
34862
+ const index = loadOrBuildCodeContextIndex(options);
34863
+ const matches = findSymbols(index, symbolName, options.limit ?? 20);
34864
+ const dependents = dependentsMap(index);
34865
+ return { query: symbolName, matches, definitions: matches.map((symbol) => {
34866
+ const file = index.files[symbol.filePath];
34867
+ return { symbol, imports: file.resolvedImports, dependents: [...dependents.get(symbol.filePath) ?? /* @__PURE__ */ new Set()].sort(), relatedSymbols: file.symbols.filter((s) => s.id !== symbol.id).slice(0, 20) };
34868
+ }) };
34869
+ }
34870
+ function resolveTargetFile(index, input) {
34871
+ if (input.filePath) {
34872
+ const normalized = input.filePath.replaceAll(path50.sep, "/").replace(/^\.\//, "");
34873
+ if (index.files[normalized]) return { filePath: normalized, target: normalized };
34874
+ const suffix = Object.keys(index.files).find((file) => file.endsWith(normalized));
34875
+ if (suffix) return { filePath: suffix, target: input.filePath };
34876
+ }
34877
+ if (input.symbol) {
34878
+ const match = findSymbols(index, input.symbol, 1)[0];
34879
+ if (match) return { filePath: match.filePath, target: input.symbol };
34880
+ }
34881
+ return null;
34882
+ }
34883
+ function analyzeBlastRadius(input) {
34884
+ const index = loadOrBuildCodeContextIndex({ projectRoot: input.projectRoot, force: input.force });
34885
+ const target = resolveTargetFile(index, { filePath: input.filePath, symbol: input.symbol });
34886
+ if (!target) return null;
34887
+ const dependents = dependentsMap(index);
34888
+ const maxDepth = input.depth ?? 2;
34889
+ const impacted = /* @__PURE__ */ new Map();
34890
+ const queue = [{ filePath: target.filePath, distance: 0 }];
34891
+ impacted.set(target.filePath, { distance: 0, reason: "target" });
34892
+ while (queue.length > 0) {
34893
+ const item = queue.shift();
34894
+ if (item.distance >= maxDepth) continue;
34895
+ for (const dep of dependents.get(item.filePath) ?? []) {
34896
+ if (!impacted.has(dep)) {
34897
+ impacted.set(dep, { distance: item.distance + 1, reason: `imports ${item.filePath}` });
34898
+ queue.push({ filePath: dep, distance: item.distance + 1 });
34899
+ }
34900
+ }
34901
+ }
34902
+ const targetBase = path50.basename(target.filePath).replace(/\.[^.]+$/, "").toLowerCase();
34903
+ const symbolLower = input.symbol?.toLowerCase();
34904
+ const tests = Object.keys(index.files).filter((file) => {
34905
+ const lower = file.toLowerCase();
34906
+ return (lower.includes("test") || lower.includes("spec")) && (lower.includes(targetBase) || (symbolLower ? index.files[file].symbols.some((s) => s.text.toLowerCase().includes(symbolLower)) : false));
34907
+ });
34908
+ for (const test of tests) if (!impacted.has(test)) impacted.set(test, { distance: 1, reason: "related test/spec" });
34909
+ const impactedFiles = [...impacted.entries()].map(([filePath, value]) => ({ filePath, distance: value.distance, reason: value.reason })).sort((a, b) => a.distance - b.distance || a.filePath.localeCompare(b.filePath));
34910
+ const nonTestImpacted = impactedFiles.filter((f) => !f.filePath.match(/(test|spec)\./i)).length;
34911
+ return { target: target.target, targetFile: target.filePath, impactedFiles, tests, symbolsInTarget: index.files[target.filePath]?.symbols ?? [], riskLevel: nonTestImpacted >= 8 ? "high" : nonTestImpacted >= 4 ? "medium" : "low" };
34912
+ }
34913
+ function getCodeContextStats(options = {}) {
34914
+ const index = loadOrBuildCodeContextIndex(options);
34915
+ return {
34916
+ projectRoot: index.projectRoot,
34917
+ branch: index.branch,
34918
+ indexedAt: index.indexedAt,
34919
+ indexAgeMs: Math.max(0, Date.now() - Date.parse(index.indexedAt)),
34920
+ files: Object.keys(index.files).length,
34921
+ symbols: Object.values(index.files).reduce((sum, file) => sum + file.symbols.length, 0),
34922
+ imports: Object.values(index.files).reduce((sum, file) => sum + file.resolvedImports.length, 0),
34923
+ languageBreakdown: index.stats.languageBreakdown,
34924
+ rebuiltFiles: index.stats.rebuiltFiles,
34925
+ reusedFiles: index.stats.reusedFiles,
34926
+ skippedFiles: index.stats.skippedFiles,
34927
+ indexPath: getCodeContextIndexPath(index.projectRoot)
34928
+ };
34929
+ }
34930
+ var INDEX_VERSION, DEFAULT_MAX_FILES, IGNORE_SEGMENTS;
34931
+ var init_code_context_index = __esm({
34932
+ "src/lib/code-context-index.ts"() {
34933
+ "use strict";
34934
+ init_config();
34935
+ init_code_chunker();
34936
+ INDEX_VERSION = 2;
34937
+ DEFAULT_MAX_FILES = 5e3;
34938
+ IGNORE_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", "coverage", ".worktrees", ".next", "build", "target", "vendor"]);
34939
+ }
34940
+ });
34941
+
33944
34942
  // src/adapters/opencode/plugin-template.ts
33945
34943
  var PLUGIN_TEMPLATE;
33946
34944
  var init_plugin_template = __esm({
@@ -34134,16 +35132,16 @@ __export(installer_exports2, {
34134
35132
  verifyOpenCodeHooks: () => verifyOpenCodeHooks
34135
35133
  });
34136
35134
  import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
34137
- import { existsSync as existsSync36, readFileSync as readFileSync31 } from "fs";
34138
- import path50 from "path";
35135
+ import { existsSync as existsSync37, readFileSync as readFileSync32 } from "fs";
35136
+ import path51 from "path";
34139
35137
  import os22 from "os";
34140
35138
  async function registerOpenCodeMcp(packageRoot, homeDir = os22.homedir()) {
34141
35139
  void packageRoot;
34142
- const configDir = path50.join(homeDir, ".config", "opencode");
34143
- const configPath = path50.join(configDir, "opencode.json");
35140
+ const configDir = path51.join(homeDir, ".config", "opencode");
35141
+ const configPath = path51.join(configDir, "opencode.json");
34144
35142
  await mkdir8(configDir, { recursive: true });
34145
35143
  let config = {};
34146
- if (existsSync36(configPath)) {
35144
+ if (existsSync37(configPath)) {
34147
35145
  try {
34148
35146
  config = JSON.parse(await readFile7(configPath, "utf-8"));
34149
35147
  } catch {
@@ -34171,14 +35169,14 @@ async function registerOpenCodeMcp(packageRoot, homeDir = os22.homedir()) {
34171
35169
  return true;
34172
35170
  }
34173
35171
  async function installOpenCodePlugin(packageRoot, homeDir = os22.homedir()) {
34174
- const pluginDir = path50.join(homeDir, ".config", "opencode", "plugins");
34175
- const pluginPath = path50.join(pluginDir, "exe-os.mjs");
35172
+ const pluginDir = path51.join(homeDir, ".config", "opencode", "plugins");
35173
+ const pluginPath = path51.join(pluginDir, "exe-os.mjs");
34176
35174
  await mkdir8(pluginDir, { recursive: true });
34177
35175
  const pluginContent = PLUGIN_TEMPLATE.replace(
34178
35176
  /__PACKAGE_ROOT__/g,
34179
35177
  packageRoot.replace(/\\/g, "\\\\")
34180
35178
  );
34181
- if (existsSync36(pluginPath)) {
35179
+ if (existsSync37(pluginPath)) {
34182
35180
  const existing = await readFile7(pluginPath, "utf-8");
34183
35181
  if (existing === pluginContent) {
34184
35182
  return false;
@@ -34188,18 +35186,18 @@ async function installOpenCodePlugin(packageRoot, homeDir = os22.homedir()) {
34188
35186
  return true;
34189
35187
  }
34190
35188
  function verifyOpenCodeHooks(homeDir = os22.homedir()) {
34191
- const configPath = path50.join(homeDir, ".config", "opencode", "opencode.json");
34192
- const pluginPath = path50.join(homeDir, ".config", "opencode", "plugins", "exe-os.mjs");
34193
- if (!existsSync36(configPath)) return false;
35189
+ const configPath = path51.join(homeDir, ".config", "opencode", "opencode.json");
35190
+ const pluginPath = path51.join(homeDir, ".config", "opencode", "plugins", "exe-os.mjs");
35191
+ if (!existsSync37(configPath)) return false;
34194
35192
  try {
34195
- const config = JSON.parse(readFileSync31(configPath, "utf-8"));
35193
+ const config = JSON.parse(readFileSync32(configPath, "utf-8"));
34196
35194
  if (!config.mcp?.["exe-os"]?.enabled) return false;
34197
35195
  } catch {
34198
35196
  return false;
34199
35197
  }
34200
- if (!existsSync36(pluginPath)) return false;
35198
+ if (!existsSync37(pluginPath)) return false;
34201
35199
  try {
34202
- const plugin = readFileSync31(pluginPath, "utf-8");
35200
+ const plugin = readFileSync32(pluginPath, "utf-8");
34203
35201
  if (!plugin.includes(EXE_HOOK_FILES.postToolCombined)) return false;
34204
35202
  if (textHasLegacySplitPostToolHook(plugin)) return false;
34205
35203
  } catch {
@@ -34241,19 +35239,19 @@ __export(installer_exports3, {
34241
35239
  verifyCodexHooks: () => verifyCodexHooks
34242
35240
  });
34243
35241
  import { readFile as readFile8, writeFile as writeFile9, mkdir as mkdir9 } from "fs/promises";
34244
- import { existsSync as existsSync37, readFileSync as readFileSync32 } from "fs";
34245
- import path51 from "path";
35242
+ import { existsSync as existsSync38, readFileSync as readFileSync33 } from "fs";
35243
+ import path52 from "path";
34246
35244
  import os23 from "os";
34247
35245
  async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
34248
- const codexDir = path51.join(homeDir, ".codex");
34249
- const hooksPath = path51.join(codexDir, "hooks.json");
34250
- const logsDir = path51.join(homeDir, ".exe-os", "logs");
34251
- const hookLogPath = path51.join(logsDir, "hooks.log");
35246
+ const codexDir = path52.join(homeDir, ".codex");
35247
+ const hooksPath = path52.join(codexDir, "hooks.json");
35248
+ const logsDir = path52.join(homeDir, ".exe-os", "logs");
35249
+ const hookLogPath = path52.join(logsDir, "hooks.log");
34252
35250
  const logSuffix = ` 2>> "${hookLogPath}"`;
34253
35251
  await mkdir9(codexDir, { recursive: true });
34254
35252
  await mkdir9(logsDir, { recursive: true });
34255
35253
  let hooksJson = {};
34256
- if (existsSync37(hooksPath)) {
35254
+ if (existsSync38(hooksPath)) {
34257
35255
  try {
34258
35256
  hooksJson = JSON.parse(await readFile8(hooksPath, "utf-8"));
34259
35257
  } catch {
@@ -34273,7 +35271,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
34273
35271
  // Combined hook: runs ingest + error-recall in one Node process.
34274
35272
  // Eliminates a cold-start cycle per tool call (~3-6s savings on Codex).
34275
35273
  type: "command",
34276
- command: `node "${path51.join(packageRoot, "dist", "hooks", "post-tool-combined.js")}"${logSuffix}`
35274
+ command: `node "${path52.join(packageRoot, "dist", "hooks", "post-tool-combined.js")}"${logSuffix}`
34277
35275
  }
34278
35276
  ]
34279
35277
  },
@@ -34287,7 +35285,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
34287
35285
  // Single hook: prompt-submit handles memory retrieval + entity boost.
34288
35286
  // exe-heartbeat-hook is CC-specific (intercom) — omitted on Codex.
34289
35287
  type: "command",
34290
- command: `node "${path51.join(packageRoot, "dist", "hooks", "prompt-submit.js")}"${logSuffix}`
35288
+ command: `node "${path52.join(packageRoot, "dist", "hooks", "prompt-submit.js")}"${logSuffix}`
34291
35289
  }
34292
35290
  ]
34293
35291
  },
@@ -34299,7 +35297,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
34299
35297
  hooks: [
34300
35298
  {
34301
35299
  type: "command",
34302
- command: `node "${path51.join(packageRoot, "dist", "hooks", "stop.js")}"${logSuffix}`
35300
+ command: `node "${path52.join(packageRoot, "dist", "hooks", "stop.js")}"${logSuffix}`
34303
35301
  }
34304
35302
  ]
34305
35303
  },
@@ -34312,7 +35310,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
34312
35310
  hooks: [
34313
35311
  {
34314
35312
  type: "command",
34315
- command: `node "${path51.join(packageRoot, "dist", "hooks", "pre-tool-use.js")}"${logSuffix}`
35313
+ command: `node "${path52.join(packageRoot, "dist", "hooks", "pre-tool-use.js")}"${logSuffix}`
34316
35314
  }
34317
35315
  ]
34318
35316
  },
@@ -34361,10 +35359,10 @@ async function mergeCodexHooks(packageRoot, homeDir = os23.homedir()) {
34361
35359
  return { added, skipped };
34362
35360
  }
34363
35361
  function verifyCodexHooks(homeDir = os23.homedir()) {
34364
- const hooksPath = path51.join(homeDir, ".codex", "hooks.json");
34365
- if (!existsSync37(hooksPath)) return false;
35362
+ const hooksPath = path52.join(homeDir, ".codex", "hooks.json");
35363
+ if (!existsSync38(hooksPath)) return false;
34366
35364
  try {
34367
- const hooksJson = JSON.parse(readFileSync32(hooksPath, "utf-8"));
35365
+ const hooksJson = JSON.parse(readFileSync33(hooksPath, "utf-8"));
34368
35366
  if (!hooksJson.hooks) return false;
34369
35367
  const required = ["PostToolUse", "UserPromptSubmit", "Stop", "PreToolUse"];
34370
35368
  for (const event of required) {
@@ -34396,11 +35394,11 @@ function verifyCodexHooks(homeDir = os23.homedir()) {
34396
35394
  async function installCodexStatusLine(homeDir = os23.homedir()) {
34397
35395
  const prefs = loadPreferences(homeDir);
34398
35396
  if (prefs.codexStatusLine === false) return "opted-out";
34399
- const codexDir = path51.join(homeDir, ".codex");
34400
- const configPath = path51.join(codexDir, "config.toml");
35397
+ const codexDir = path52.join(homeDir, ".codex");
35398
+ const configPath = path52.join(codexDir, "config.toml");
34401
35399
  await mkdir9(codexDir, { recursive: true });
34402
35400
  let content = "";
34403
- if (existsSync37(configPath)) {
35401
+ if (existsSync38(configPath)) {
34404
35402
  content = await readFile8(configPath, "utf-8");
34405
35403
  if (/\[tui\][\s\S]*?status_line\s*=/.test(content)) {
34406
35404
  return "already-configured";
@@ -34452,12 +35450,12 @@ ${line}
34452
35450
  return { content: next, changed: next !== sectionContent };
34453
35451
  }
34454
35452
  async function registerCodexMcpServer(packageRoot, homeDir = os23.homedir()) {
34455
- const codexDir = path51.join(homeDir, ".codex");
34456
- const configPath = path51.join(codexDir, "config.toml");
35453
+ const codexDir = path52.join(homeDir, ".codex");
35454
+ const configPath = path52.join(codexDir, "config.toml");
34457
35455
  void packageRoot;
34458
35456
  await mkdir9(codexDir, { recursive: true });
34459
35457
  let content = "";
34460
- if (existsSync37(configPath)) {
35458
+ if (existsSync38(configPath)) {
34461
35459
  content = await readFile8(configPath, "utf-8");
34462
35460
  }
34463
35461
  const sectionHeader = "[mcp_servers.exe-os]";
@@ -34482,10 +35480,10 @@ async function registerCodexMcpServer(packageRoot, homeDir = os23.homedir()) {
34482
35480
  return "registered";
34483
35481
  }
34484
35482
  async function ensureCodexHooksFeature(homeDir = os23.homedir()) {
34485
- const configPath = path51.join(homeDir, ".codex", "config.toml");
34486
- await mkdir9(path51.join(homeDir, ".codex"), { recursive: true });
35483
+ const configPath = path52.join(homeDir, ".codex", "config.toml");
35484
+ await mkdir9(path52.join(homeDir, ".codex"), { recursive: true });
34487
35485
  let content = "";
34488
- if (existsSync37(configPath)) {
35486
+ if (existsSync38(configPath)) {
34489
35487
  content = await readFile8(configPath, "utf-8");
34490
35488
  }
34491
35489
  if (/\[features\][\s\S]*?codex_hooks\s*=\s*true/.test(content)) {
@@ -34560,32 +35558,32 @@ __export(mcp_diagnostics_exports, {
34560
35558
  diagnoseClaudeMcpConfig: () => diagnoseClaudeMcpConfig,
34561
35559
  formatMcpDiagnosticReport: () => formatMcpDiagnosticReport
34562
35560
  });
34563
- import { existsSync as existsSync38, readFileSync as readFileSync33 } from "fs";
34564
- import path52 from "path";
35561
+ import { existsSync as existsSync39, readFileSync as readFileSync34 } from "fs";
35562
+ import path53 from "path";
34565
35563
  import os24 from "os";
34566
35564
  function readJson(filePath) {
34567
- if (!existsSync38(filePath)) return null;
35565
+ if (!existsSync39(filePath)) return null;
34568
35566
  try {
34569
- return JSON.parse(readFileSync33(filePath, "utf8"));
35567
+ return JSON.parse(readFileSync34(filePath, "utf8"));
34570
35568
  } catch {
34571
35569
  return null;
34572
35570
  }
34573
35571
  }
34574
35572
  function pathApplies2(projectPath, cwd2) {
34575
- const project = path52.resolve(projectPath);
34576
- const current = path52.resolve(cwd2);
34577
- return current === project || current.startsWith(project + path52.sep);
35573
+ const project = path53.resolve(projectPath);
35574
+ const current = path53.resolve(cwd2);
35575
+ return current === project || current.startsWith(project + path53.sep);
34578
35576
  }
34579
35577
  function findAncestorMcpJsons2(cwd2, homeDir) {
34580
35578
  const files = [];
34581
- let dir = path52.resolve(cwd2);
34582
- const root = path52.parse(dir).root;
34583
- const stop = path52.resolve(homeDir);
35579
+ let dir = path53.resolve(cwd2);
35580
+ const root = path53.parse(dir).root;
35581
+ const stop = path53.resolve(homeDir);
34584
35582
  while (dir !== root) {
34585
- const candidate = path52.join(dir, ".mcp.json");
34586
- if (existsSync38(candidate)) files.push(candidate);
35583
+ const candidate = path53.join(dir, ".mcp.json");
35584
+ if (existsSync39(candidate)) files.push(candidate);
34587
35585
  if (dir === stop) break;
34588
- dir = path52.dirname(dir);
35586
+ dir = path53.dirname(dir);
34589
35587
  }
34590
35588
  return files;
34591
35589
  }
@@ -34602,8 +35600,8 @@ function serverAllowedByPermissions(serverName, prefixes) {
34602
35600
  return prefixes.has(serverName) || prefixes.has(serverName.replace(/-/g, "_"));
34603
35601
  }
34604
35602
  function diagnoseClaudeMcpConfig(homeDir = os24.homedir(), cwd2 = process.cwd(), env = process.env) {
34605
- const claudeJsonPath = path52.join(homeDir, ".claude.json");
34606
- const settingsPath = path52.join(homeDir, ".claude", "settings.json");
35603
+ const claudeJsonPath = path53.join(homeDir, ".claude.json");
35604
+ const settingsPath = path53.join(homeDir, ".claude", "settings.json");
34607
35605
  const claudeJson = readJson(claudeJsonPath);
34608
35606
  const settings = readJson(settingsPath);
34609
35607
  const issues = [];
@@ -34723,7 +35721,7 @@ function diagnoseClaudeMcpConfig(homeDir = os24.homedir(), cwd2 = process.cwd(),
34723
35721
  });
34724
35722
  }
34725
35723
  return {
34726
- cwd: path52.resolve(cwd2),
35724
+ cwd: path53.resolve(cwd2),
34727
35725
  globalServers,
34728
35726
  projectServers: projectServers.sort((a, b) => a.name.localeCompare(b.name)),
34729
35727
  mcpJsonServers: mcpJsonServers.sort((a, b) => a.name.localeCompare(b.name)),
@@ -34760,14 +35758,14 @@ var init_mcp_diagnostics = __esm({
34760
35758
  });
34761
35759
 
34762
35760
  // src/bin/cli.ts
34763
- import { existsSync as existsSync39, readFileSync as readFileSync34, writeFileSync as writeFileSync25, readdirSync as readdirSync11, rmSync as rmSync2 } from "fs";
34764
- import path53 from "path";
35761
+ import { existsSync as existsSync40, readFileSync as readFileSync35, writeFileSync as writeFileSync26, readdirSync as readdirSync12, rmSync as rmSync2 } from "fs";
35762
+ import path54 from "path";
34765
35763
  import os25 from "os";
34766
35764
  var args = process.argv.slice(2);
34767
35765
  if (args.includes("--version") || args.includes("-v")) {
34768
35766
  try {
34769
- const pkgPath = path53.join(path53.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
34770
- const pkg = JSON.parse(readFileSync34(pkgPath, "utf8"));
35767
+ const pkgPath = path54.join(path54.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
35768
+ const pkg = JSON.parse(readFileSync35(pkgPath, "utf8"));
34771
35769
  console.log(pkg.version);
34772
35770
  } catch {
34773
35771
  console.log("unknown");
@@ -34842,6 +35840,8 @@ if (args.includes("--global")) {
34842
35840
  } else if (args[0] === "org") {
34843
35841
  const { main: runOrg } = await Promise.resolve().then(() => (init_exe_org(), exe_org_exports));
34844
35842
  await runOrg(args.slice(1));
35843
+ } else if (args[0] === "code-context") {
35844
+ await runCodeContext(args.slice(1));
34845
35845
  } else if (args[0] === "jobs") {
34846
35846
  const { cancelBackgroundJob: cancelBackgroundJob2, formatJobs: formatJobs2, listBackgroundJobs: listBackgroundJobs2 } = await Promise.resolve().then(() => (init_background_jobs(), background_jobs_exports));
34847
35847
  const sub = args[1] ?? "status";
@@ -34982,16 +35982,19 @@ ID: ${result.id}`);
34982
35982
  } else if (args[0] === "stack-update") {
34983
35983
  const { runStackUpdateCli } = await Promise.resolve().then(() => (init_stack_update2(), stack_update_exports));
34984
35984
  await runStackUpdateCli();
35985
+ } else if (args[0] === "registry-proxy") {
35986
+ const { main: runRegistryProxy2 } = await Promise.resolve().then(() => (init_registry_proxy2(), registry_proxy_exports));
35987
+ await runRegistryProxy2(args.slice(1));
34985
35988
  } else if (args.includes("--tui") || args.includes("--demo") || args[0] === "tui") {
34986
35989
  checkForUpdateOnBoot().catch(() => {
34987
35990
  });
34988
35991
  await init_App2().then(() => App_exports);
34989
35992
  } else {
34990
- const claudeDir = path53.join(os25.homedir(), ".claude");
34991
- const settingsPath = path53.join(claudeDir, "settings.json");
34992
- const hasClaudeCode = existsSync39(settingsPath) && (() => {
35993
+ const claudeDir = path54.join(os25.homedir(), ".claude");
35994
+ const settingsPath = path54.join(claudeDir, "settings.json");
35995
+ const hasClaudeCode = existsSync40(settingsPath) && (() => {
34993
35996
  try {
34994
- const raw = readFileSync34(settingsPath, "utf8");
35997
+ const raw = readFileSync35(settingsPath, "utf8");
34995
35998
  return raw.includes("exe-os") || raw.includes("exe-mem");
34996
35999
  } catch {
34997
36000
  return false;
@@ -35001,9 +36004,9 @@ ID: ${result.id}`);
35001
36004
  const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
35002
36005
  let cooName = DEFAULT_COORDINATOR_TEMPLATE_NAME2;
35003
36006
  try {
35004
- const rosterPath = path53.join(os25.homedir(), ".exe-os", "exe-employees.json");
35005
- if (existsSync39(rosterPath)) {
35006
- const roster = JSON.parse(readFileSync34(rosterPath, "utf8"));
36007
+ const rosterPath = path54.join(os25.homedir(), ".exe-os", "exe-employees.json");
36008
+ if (existsSync40(rosterPath)) {
36009
+ const roster = JSON.parse(readFileSync35(rosterPath, "utf8"));
35007
36010
  const coo = roster.find((e) => e.role === "COO");
35008
36011
  if (coo) cooName = coo.name;
35009
36012
  }
@@ -35042,6 +36045,82 @@ cd into a project folder and run \x1B[1m${cooName}1\x1B[0m to boot your COO.
35042
36045
  await init_App2().then(() => App_exports);
35043
36046
  }
35044
36047
  }
36048
+ async function runCodeContext(ccArgs) {
36049
+ const { buildCodeContextIndex: buildCodeContextIndex2, getCodeContextStats: getCodeContextStats2, searchCodeContext: searchCodeContext2 } = await Promise.resolve().then(() => (init_code_context_index(), code_context_index_exports));
36050
+ const sub = ccArgs[0] ?? "status";
36051
+ const projectRoot = valueAfter(ccArgs, "--project-root") ?? process.cwd();
36052
+ if (sub === "init" || sub === "index") {
36053
+ const force = ccArgs.includes("--force") || ccArgs.includes("--refresh");
36054
+ const index = buildCodeContextIndex2({ projectRoot, force });
36055
+ console.log(JSON.stringify({ projectRoot: index.projectRoot, branch: index.branch, indexedAt: index.indexedAt, files: Object.keys(index.files).length, symbols: Object.values(index.files).reduce((sum, f) => sum + f.symbols.length, 0), languageBreakdown: index.stats.languageBreakdown, rebuiltFiles: index.stats.rebuiltFiles, reusedFiles: index.stats.reusedFiles }, null, 2));
36056
+ return;
36057
+ }
36058
+ if (sub === "status" || sub === "stats") {
36059
+ console.log(JSON.stringify(getCodeContextStats2({ projectRoot }), null, 2));
36060
+ return;
36061
+ }
36062
+ if (sub === "search") {
36063
+ const query = valueAfter(ccArgs, "--query") ?? positionalSearchQuery(ccArgs.slice(1));
36064
+ if (!query) {
36065
+ console.error("Usage: exe-os code-context search <query> [--limit N] [--offset N] [--language python] [--path 'src/**'] [--refresh]");
36066
+ process.exit(1);
36067
+ }
36068
+ const languageFilters = [...valuesAfter(ccArgs, "--language") ?? [], ...valuesAfter(ccArgs, "--languages") ?? []];
36069
+ const pathFilters = [...valuesAfter(ccArgs, "--path") ?? [], ...valuesAfter(ccArgs, "--paths") ?? []];
36070
+ const results = searchCodeContext2(query, {
36071
+ projectRoot,
36072
+ limit: numberAfter(ccArgs, "--limit") ?? 20,
36073
+ offset: numberAfter(ccArgs, "--offset") ?? 0,
36074
+ refreshIndex: ccArgs.includes("--refresh") || ccArgs.includes("--refresh-index"),
36075
+ languages: languageFilters.length > 0 ? languageFilters : void 0,
36076
+ paths: pathFilters.length > 0 ? pathFilters : void 0
36077
+ });
36078
+ console.log(JSON.stringify({ query, results }, null, 2));
36079
+ return;
36080
+ }
36081
+ if (sub === "doctor") {
36082
+ const stats = getCodeContextStats2({ projectRoot });
36083
+ console.log(JSON.stringify({ ok: stats.files > 0, ...stats }, null, 2));
36084
+ process.exit(stats.files > 0 ? 0 : 1);
36085
+ }
36086
+ console.error("Usage: exe-os code-context init|index|status|stats|search|doctor");
36087
+ process.exit(1);
36088
+ }
36089
+ function positionalSearchQuery(args2) {
36090
+ const valueFlags = /* @__PURE__ */ new Set(["--project-root", "--limit", "--offset", "--language", "--languages", "--path", "--paths", "--query"]);
36091
+ const parts = [];
36092
+ for (let i = 0; i < args2.length; i++) {
36093
+ const arg = args2[i];
36094
+ if (valueFlags.has(arg)) {
36095
+ i++;
36096
+ continue;
36097
+ }
36098
+ if (arg.startsWith("--")) continue;
36099
+ parts.push(arg);
36100
+ }
36101
+ return parts.join(" ").trim();
36102
+ }
36103
+ function valueAfter(args2, flag) {
36104
+ const i = args2.indexOf(flag);
36105
+ if (i >= 0) return args2[i + 1];
36106
+ const prefixed = args2.find((arg) => arg.startsWith(`${flag}=`));
36107
+ return prefixed?.slice(flag.length + 1);
36108
+ }
36109
+ function valuesAfter(args2, flag) {
36110
+ const values = [];
36111
+ for (let i = 0; i < args2.length; i++) {
36112
+ const arg = args2[i];
36113
+ if (arg === flag && args2[i + 1]) values.push(args2[++i]);
36114
+ else if (arg.startsWith(`${flag}=`)) values.push(arg.slice(flag.length + 1));
36115
+ }
36116
+ return values.length > 0 ? values.flatMap((v) => v.split(",").map((s) => s.trim()).filter(Boolean)) : void 0;
36117
+ }
36118
+ function numberAfter(args2, flag) {
36119
+ const value = valueAfter(args2, flag);
36120
+ if (!value) return void 0;
36121
+ const parsed = Number(value);
36122
+ return Number.isFinite(parsed) ? parsed : void 0;
36123
+ }
35045
36124
  async function runClaudeInstall() {
35046
36125
  const { runInstaller: runInstaller2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
35047
36126
  try {
@@ -35080,14 +36159,14 @@ async function runCodexInstall() {
35080
36159
  }
35081
36160
  async function runClaudeCheck() {
35082
36161
  const { detectMcpNameCollisions: detectMcpNameCollisions2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
35083
- const claudeDir = path53.join(os25.homedir(), ".claude");
35084
- const settingsPath = path53.join(claudeDir, "settings.json");
35085
- const claudeJsonPath = path53.join(os25.homedir(), ".claude.json");
36162
+ const claudeDir = path54.join(os25.homedir(), ".claude");
36163
+ const settingsPath = path54.join(claudeDir, "settings.json");
36164
+ const claudeJsonPath = path54.join(os25.homedir(), ".claude.json");
35086
36165
  let ok = true;
35087
- if (existsSync39(settingsPath)) {
36166
+ if (existsSync40(settingsPath)) {
35088
36167
  let settings;
35089
36168
  try {
35090
- settings = JSON.parse(readFileSync34(settingsPath, "utf8"));
36169
+ settings = JSON.parse(readFileSync35(settingsPath, "utf8"));
35091
36170
  } catch {
35092
36171
  console.log("\x1B[31m\u2717\x1B[0m settings.json is malformed (invalid JSON)");
35093
36172
  ok = false;
@@ -35113,10 +36192,10 @@ async function runClaudeCheck() {
35113
36192
  console.log("\x1B[31m\u2717\x1B[0m settings.json not found");
35114
36193
  ok = false;
35115
36194
  }
35116
- if (existsSync39(claudeJsonPath)) {
36195
+ if (existsSync40(claudeJsonPath)) {
35117
36196
  let claudeJson;
35118
36197
  try {
35119
- claudeJson = JSON.parse(readFileSync34(claudeJsonPath, "utf8"));
36198
+ claudeJson = JSON.parse(readFileSync35(claudeJsonPath, "utf8"));
35120
36199
  } catch {
35121
36200
  console.log("\x1B[31m\u2717\x1B[0m claude.json is malformed (invalid JSON)");
35122
36201
  ok = false;
@@ -35149,8 +36228,8 @@ async function runClaudeCheck() {
35149
36228
  } else {
35150
36229
  console.log("\x1B[32m\u2713\x1B[0m No .mcp.json/project MCP name collisions detected");
35151
36230
  }
35152
- const skillsDir = path53.join(claudeDir, "skills");
35153
- if (existsSync39(skillsDir)) {
36231
+ const skillsDir = path54.join(claudeDir, "skills");
36232
+ if (existsSync40(skillsDir)) {
35154
36233
  console.log("\x1B[32m\u2713\x1B[0m Slash skills directory exists");
35155
36234
  } else {
35156
36235
  console.log("\x1B[31m\u2717\x1B[0m Slash skills directory missing");
@@ -35174,16 +36253,16 @@ async function runClaudeUninstall(flags = []) {
35174
36253
  const dryRun = flags.includes("--dry-run");
35175
36254
  const purge = flags.includes("--purge");
35176
36255
  const homeDir = os25.homedir();
35177
- const claudeDir = path53.join(homeDir, ".claude");
35178
- const settingsPath = path53.join(claudeDir, "settings.json");
35179
- const claudeJsonPath = path53.join(homeDir, ".claude.json");
35180
- const exeOsDir = path53.join(homeDir, ".exe-os");
36256
+ const claudeDir = path54.join(homeDir, ".claude");
36257
+ const settingsPath = path54.join(claudeDir, "settings.json");
36258
+ const claudeJsonPath = path54.join(homeDir, ".claude.json");
36259
+ const exeOsDir = path54.join(homeDir, ".exe-os");
35181
36260
  let removed = 0;
35182
36261
  const log = (msg) => console.log(dryRun ? `[dry-run] ${msg}` : msg);
35183
36262
  let settings = {};
35184
- if (existsSync39(settingsPath)) {
36263
+ if (existsSync40(settingsPath)) {
35185
36264
  try {
35186
- settings = JSON.parse(readFileSync34(settingsPath, "utf8"));
36265
+ settings = JSON.parse(readFileSync35(settingsPath, "utf8"));
35187
36266
  } catch {
35188
36267
  console.error("Your ~/.claude/settings.json appears malformed.");
35189
36268
  if (purge) {
@@ -35221,15 +36300,15 @@ async function runClaudeUninstall(flags = []) {
35221
36300
  permCount = before - settings.permissions.allow.length;
35222
36301
  }
35223
36302
  if (!dryRun) {
35224
- writeFileSync25(settingsPath, JSON.stringify(settings, null, 2) + "\n");
36303
+ writeFileSync26(settingsPath, JSON.stringify(settings, null, 2) + "\n");
35225
36304
  }
35226
36305
  log("\u2713 Removed exe-os hooks from settings.json");
35227
36306
  if (permCount > 0) log(`\u2713 Removed ${permCount} MCP permission entries`);
35228
36307
  removed++;
35229
36308
  }
35230
36309
  }
35231
- if (existsSync39(claudeJsonPath)) {
35232
- const raw = readFileSync34(claudeJsonPath, "utf8");
36310
+ if (existsSync40(claudeJsonPath)) {
36311
+ const raw = readFileSync35(claudeJsonPath, "utf8");
35233
36312
  if (raw.length > 1e6) {
35234
36313
  console.error("claude.json exceeds 1 MB \u2014 skipping parse.");
35235
36314
  } else {
@@ -35250,7 +36329,7 @@ async function runClaudeUninstall(flags = []) {
35250
36329
  }
35251
36330
  if (removedMcp) {
35252
36331
  if (!dryRun) {
35253
- writeFileSync25(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
36332
+ writeFileSync26(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
35254
36333
  }
35255
36334
  log("\u2713 Removed exe-os MCP server from claude.json");
35256
36335
  removed++;
@@ -35258,14 +36337,14 @@ async function runClaudeUninstall(flags = []) {
35258
36337
  }
35259
36338
  }
35260
36339
  }
35261
- const skillsDir = path53.join(claudeDir, "skills");
35262
- if (existsSync39(skillsDir)) {
36340
+ const skillsDir = path54.join(claudeDir, "skills");
36341
+ if (existsSync40(skillsDir)) {
35263
36342
  let skillCount = 0;
35264
36343
  try {
35265
- const entries = readdirSync11(skillsDir);
36344
+ const entries = readdirSync12(skillsDir);
35266
36345
  for (const entry of entries) {
35267
36346
  if (entry.startsWith("exe")) {
35268
- const fullPath = path53.join(skillsDir, entry);
36347
+ const fullPath = path54.join(skillsDir, entry);
35269
36348
  if (!dryRun) rmSync2(fullPath, { recursive: true, force: true });
35270
36349
  skillCount++;
35271
36350
  }
@@ -35277,30 +36356,30 @@ async function runClaudeUninstall(flags = []) {
35277
36356
  removed++;
35278
36357
  }
35279
36358
  }
35280
- const claudeMdPath = path53.join(claudeDir, "CLAUDE.md");
35281
- if (existsSync39(claudeMdPath)) {
35282
- const content = readFileSync34(claudeMdPath, "utf8");
36359
+ const claudeMdPath = path54.join(claudeDir, "CLAUDE.md");
36360
+ if (existsSync40(claudeMdPath)) {
36361
+ const content = readFileSync35(claudeMdPath, "utf8");
35283
36362
  const startMarker = "<!-- exe-os:orchestration-start -->";
35284
36363
  const endMarker = "<!-- exe-os:orchestration-end -->";
35285
36364
  const startIdx = content.indexOf(startMarker);
35286
36365
  const endIdx = content.indexOf(endMarker);
35287
36366
  if (startIdx !== -1 && endIdx !== -1) {
35288
36367
  const cleaned = (content.slice(0, startIdx) + content.slice(endIdx + endMarker.length)).replace(/\n{3,}/g, "\n\n").trim() + "\n";
35289
- if (!dryRun) writeFileSync25(claudeMdPath, cleaned);
36368
+ if (!dryRun) writeFileSync26(claudeMdPath, cleaned);
35290
36369
  log("\u2713 Removed orchestration block from CLAUDE.md");
35291
36370
  removed++;
35292
36371
  }
35293
36372
  }
35294
- const agentsDir = path53.join(claudeDir, "agents");
35295
- if (existsSync39(agentsDir)) {
36373
+ const agentsDir = path54.join(claudeDir, "agents");
36374
+ if (existsSync40(agentsDir)) {
35296
36375
  let agentCount = 0;
35297
36376
  try {
35298
- const entries = readdirSync11(agentsDir).filter((f) => f.endsWith(".md"));
36377
+ const entries = readdirSync12(agentsDir).filter((f) => f.endsWith(".md"));
35299
36378
  let knownNames = /* @__PURE__ */ new Set();
35300
- const rosterPath = path53.join(exeOsDir, "exe-employees.json");
35301
- if (existsSync39(rosterPath)) {
36379
+ const rosterPath = path54.join(exeOsDir, "exe-employees.json");
36380
+ if (existsSync40(rosterPath)) {
35302
36381
  try {
35303
- const roster = JSON.parse(readFileSync34(rosterPath, "utf8"));
36382
+ const roster = JSON.parse(readFileSync35(rosterPath, "utf8"));
35304
36383
  knownNames = new Set(roster.map((e) => e.name));
35305
36384
  } catch {
35306
36385
  }
@@ -35308,7 +36387,7 @@ async function runClaudeUninstall(flags = []) {
35308
36387
  for (const entry of entries) {
35309
36388
  const name = entry.replace(/\.md$/, "");
35310
36389
  if (knownNames.has(name)) {
35311
- if (!dryRun) rmSync2(path53.join(agentsDir, entry), { force: true });
36390
+ if (!dryRun) rmSync2(path54.join(agentsDir, entry), { force: true });
35312
36391
  agentCount++;
35313
36392
  }
35314
36393
  }
@@ -35319,16 +36398,16 @@ async function runClaudeUninstall(flags = []) {
35319
36398
  removed++;
35320
36399
  }
35321
36400
  }
35322
- const projectsDir = path53.join(claudeDir, "projects");
35323
- if (existsSync39(projectsDir)) {
36401
+ const projectsDir = path54.join(claudeDir, "projects");
36402
+ if (existsSync40(projectsDir)) {
35324
36403
  let projectCount = 0;
35325
36404
  try {
35326
- const projects = readdirSync11(projectsDir);
36405
+ const projects = readdirSync12(projectsDir);
35327
36406
  for (const proj of projects) {
35328
- const projSettings = path53.join(projectsDir, proj, "settings.json");
35329
- if (!existsSync39(projSettings)) continue;
36407
+ const projSettings = path54.join(projectsDir, proj, "settings.json");
36408
+ if (!existsSync40(projSettings)) continue;
35330
36409
  try {
35331
- const pSettings = JSON.parse(readFileSync34(projSettings, "utf8"));
36410
+ const pSettings = JSON.parse(readFileSync35(projSettings, "utf8"));
35332
36411
  let changed = false;
35333
36412
  if (Array.isArray(pSettings.permissions?.allow)) {
35334
36413
  const before = pSettings.permissions.allow.length;
@@ -35338,7 +36417,7 @@ async function runClaudeUninstall(flags = []) {
35338
36417
  if (pSettings.permissions.allow.length < before) changed = true;
35339
36418
  }
35340
36419
  if (changed && !dryRun) {
35341
- writeFileSync25(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
36420
+ writeFileSync26(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
35342
36421
  }
35343
36422
  if (changed) projectCount++;
35344
36423
  } catch {
@@ -35362,18 +36441,18 @@ async function runClaudeUninstall(flags = []) {
35362
36441
  };
35363
36442
  const exeBinPath = findExeBin3();
35364
36443
  if (!exeBinPath) throw new Error("exe-os not found in PATH");
35365
- const binDir = path53.dirname(exeBinPath);
36444
+ const binDir = path54.dirname(exeBinPath);
35366
36445
  let symlinkCount = 0;
35367
- const rosterPath = path53.join(exeOsDir, "exe-employees.json");
35368
- if (existsSync39(rosterPath)) {
35369
- const roster = JSON.parse(readFileSync34(rosterPath, "utf8"));
36446
+ const rosterPath = path54.join(exeOsDir, "exe-employees.json");
36447
+ if (existsSync40(rosterPath)) {
36448
+ const roster = JSON.parse(readFileSync35(rosterPath, "utf8"));
35370
36449
  const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
35371
36450
  const coordinatorName = roster.find((e) => e.role?.toLowerCase() === "coo")?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME2;
35372
36451
  for (const emp of roster) {
35373
36452
  if (emp.name === coordinatorName) continue;
35374
36453
  for (const suffix of ["", "-opencode"]) {
35375
- const linkPath = path53.join(binDir, `${emp.name}${suffix}`);
35376
- if (existsSync39(linkPath)) {
36454
+ const linkPath = path54.join(binDir, `${emp.name}${suffix}`);
36455
+ if (existsSync40(linkPath)) {
35377
36456
  if (!dryRun) rmSync2(linkPath, { force: true });
35378
36457
  symlinkCount++;
35379
36458
  }
@@ -35386,7 +36465,7 @@ async function runClaudeUninstall(flags = []) {
35386
36465
  }
35387
36466
  } catch {
35388
36467
  }
35389
- if (purge && existsSync39(exeOsDir)) {
36468
+ if (purge && existsSync40(exeOsDir)) {
35390
36469
  if (!dryRun) {
35391
36470
  process.stdout.write("\x1B[33m\u26A0 This will delete all memories, identities, and agent data.\x1B[0m\n");
35392
36471
  process.stdout.write(" Removing ~/.exe-os...\n");
@@ -35411,7 +36490,7 @@ async function checkForUpdateOnBoot() {
35411
36490
  const config = await loadConfig2();
35412
36491
  if (!config.autoUpdate.checkOnBoot) return;
35413
36492
  const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
35414
- const packageRoot = path53.resolve(
36493
+ const packageRoot = path54.resolve(
35415
36494
  new URL("../..", import.meta.url).pathname
35416
36495
  );
35417
36496
  const result = checkForUpdate2(packageRoot);
@@ -35472,7 +36551,7 @@ async function runActivate(key) {
35472
36551
  const idTemplate = getIdentityTemplate(identityKey);
35473
36552
  if (idTemplate) {
35474
36553
  const idPath = identityPath2(name);
35475
- const dir = path53.dirname(idPath);
36554
+ const dir = path54.dirname(idPath);
35476
36555
  if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
35477
36556
  fs8.writeFileSync(idPath, idTemplate.replace(/^agent_id: \w+/m, `agent_id: ${name}`), "utf-8");
35478
36557
  }