@askexenow/exe-os 0.9.36 → 0.9.38

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 (71) hide show
  1. package/deploy/stack-manifests/v0.9.json +55 -0
  2. package/dist/bin/backfill-conversations.js +5 -2
  3. package/dist/bin/backfill-responses.js +5 -2
  4. package/dist/bin/backfill-vectors.js +2 -2
  5. package/dist/bin/cleanup-stale-review-tasks.js +5 -2
  6. package/dist/bin/cli.js +557 -164
  7. package/dist/bin/exe-agent.js +2 -2
  8. package/dist/bin/exe-assign.js +5 -2
  9. package/dist/bin/exe-boot.js +2 -2
  10. package/dist/bin/exe-call.js +2 -2
  11. package/dist/bin/exe-dispatch.js +5 -2
  12. package/dist/bin/exe-doctor.js +2 -2
  13. package/dist/bin/exe-export-behaviors.js +5 -2
  14. package/dist/bin/exe-forget.js +5 -2
  15. package/dist/bin/exe-gateway.js +27 -3
  16. package/dist/bin/exe-heartbeat.js +5 -2
  17. package/dist/bin/exe-kill.js +5 -2
  18. package/dist/bin/exe-launch-agent.js +2 -2
  19. package/dist/bin/exe-new-employee.js +71 -4
  20. package/dist/bin/exe-pending-messages.js +5 -2
  21. package/dist/bin/exe-pending-notifications.js +5 -2
  22. package/dist/bin/exe-pending-reviews.js +5 -2
  23. package/dist/bin/exe-rename.js +1747 -199
  24. package/dist/bin/exe-review.js +5 -2
  25. package/dist/bin/exe-search.js +11 -10
  26. package/dist/bin/exe-session-cleanup.js +9 -3
  27. package/dist/bin/exe-start-codex.js +2 -2
  28. package/dist/bin/exe-start-opencode.js +2 -2
  29. package/dist/bin/exe-status.js +5 -2
  30. package/dist/bin/exe-team.js +5 -2
  31. package/dist/bin/git-sweep.js +5 -2
  32. package/dist/bin/graph-backfill.js +2 -2
  33. package/dist/bin/graph-export.js +5 -2
  34. package/dist/bin/install.js +68 -2
  35. package/dist/bin/intercom-check.js +5 -2
  36. package/dist/bin/scan-tasks.js +5 -2
  37. package/dist/bin/setup.js +2 -2
  38. package/dist/bin/shard-migrate.js +2 -2
  39. package/dist/bin/stack-update.js +308 -0
  40. package/dist/gateway/index.js +27 -3
  41. package/dist/hooks/bug-report-worker.js +5 -2
  42. package/dist/hooks/codex-stop-task-finalizer.js +5 -2
  43. package/dist/hooks/commit-complete.js +5 -2
  44. package/dist/hooks/error-recall.js +11 -10
  45. package/dist/hooks/ingest-worker.js +9 -2
  46. package/dist/hooks/ingest.js +5 -2
  47. package/dist/hooks/instructions-loaded.js +5 -2
  48. package/dist/hooks/notification.js +5 -2
  49. package/dist/hooks/post-compact.js +5 -2
  50. package/dist/hooks/post-tool-combined.js +20 -12
  51. package/dist/hooks/pre-compact.js +7 -3
  52. package/dist/hooks/pre-tool-use.js +5 -2
  53. package/dist/hooks/prompt-submit.js +11 -10
  54. package/dist/hooks/session-end.js +133 -5
  55. package/dist/hooks/session-start.js +11 -10
  56. package/dist/hooks/stop.js +9 -4
  57. package/dist/hooks/subagent-stop.js +5 -2
  58. package/dist/hooks/summary-worker.js +122 -20
  59. package/dist/index.js +49 -17
  60. package/dist/lib/employee-templates.js +2 -2
  61. package/dist/lib/exe-daemon.js +11011 -10418
  62. package/dist/lib/hybrid-search.js +11 -10
  63. package/dist/lib/schedules.js +2 -2
  64. package/dist/lib/store.js +5 -2
  65. package/dist/mcp/server.js +6881 -6356
  66. package/dist/runtime/index.js +27 -16
  67. package/dist/tui/App.js +5 -2
  68. package/package.json +4 -1
  69. package/src/commands/exe/save.md +9 -5
  70. package/stack.release.json +31 -0
  71. package/stack.release.schema.json +31 -0
package/dist/bin/cli.js CHANGED
@@ -814,6 +814,7 @@ var installer_exports = {};
814
814
  __export(installer_exports, {
815
815
  cleanOldShellFunctions: () => cleanOldShellFunctions,
816
816
  copySlashCommands: () => copySlashCommands,
817
+ detectMcpNameCollisions: () => detectMcpNameCollisions,
817
818
  installStatusLine: () => installStatusLine,
818
819
  mergeHooks: () => mergeHooks,
819
820
  registerMcpServer: () => registerMcpServer,
@@ -899,6 +900,60 @@ name: ${skillName}
899
900
  await writeFile3(destPath, content);
900
901
  return true;
901
902
  }
903
+ function readJsonFile(filePath) {
904
+ try {
905
+ return JSON.parse(readFileSync5(filePath, "utf-8"));
906
+ } catch {
907
+ return null;
908
+ }
909
+ }
910
+ function findAncestorMcpJsons(startDir, homeDir) {
911
+ const files = [];
912
+ let dir = path6.resolve(startDir);
913
+ const root = path6.parse(dir).root;
914
+ const stop = path6.resolve(homeDir);
915
+ while (dir !== root) {
916
+ const candidate = path6.join(dir, ".mcp.json");
917
+ if (existsSync7(candidate)) files.push(candidate);
918
+ if (dir === stop) break;
919
+ dir = path6.dirname(dir);
920
+ }
921
+ return files;
922
+ }
923
+ function pathApplies(projectPath, cwd2) {
924
+ const project = path6.resolve(projectPath);
925
+ const current = path6.resolve(cwd2);
926
+ return current === project || current.startsWith(project + path6.sep);
927
+ }
928
+ function detectMcpNameCollisions(homeDir = os5.homedir(), cwd2 = process.cwd()) {
929
+ const claudeJsonPath = path6.join(homeDir, ".claude.json");
930
+ if (!existsSync7(claudeJsonPath)) return [];
931
+ const claudeJson = readJsonFile(claudeJsonPath);
932
+ if (!claudeJson?.projects) return [];
933
+ const collisions = [];
934
+ const mcpJsons = findAncestorMcpJsons(cwd2, homeDir);
935
+ if (mcpJsons.length === 0) return [];
936
+ for (const [projectPath, projectConfig] of Object.entries(claudeJson.projects)) {
937
+ if (!pathApplies(projectPath, cwd2)) continue;
938
+ const projectServerNames = new Set(Object.keys(projectConfig.mcpServers ?? {}));
939
+ if (projectServerNames.size === 0) continue;
940
+ const enabled = new Set(projectConfig.enabledMcpjsonServers ?? []);
941
+ for (const mcpJsonPath of mcpJsons) {
942
+ const mcpJson = readJsonFile(mcpJsonPath);
943
+ const mcpServerNames = Object.keys(mcpJson?.mcpServers ?? {});
944
+ for (const serverName of mcpServerNames) {
945
+ if (!projectServerNames.has(serverName)) continue;
946
+ collisions.push({
947
+ mcpJsonPath,
948
+ projectPath,
949
+ serverName,
950
+ disabledInMcpJson: !enabled.has(serverName)
951
+ });
952
+ }
953
+ }
954
+ }
955
+ return collisions;
956
+ }
902
957
  async function registerMcpServer(packageRoot, homeDir = os5.homedir()) {
903
958
  const claudeJsonPath = path6.join(homeDir, ".claude.json");
904
959
  let claudeJson = {};
@@ -927,12 +982,26 @@ async function registerMcpServer(packageRoot, homeDir = os5.homedir()) {
927
982
  if (osMatches) {
928
983
  await cleanSettingsJsonMcp(path6.join(homeDir, ".claude", "settings.json"));
929
984
  await migratePermissionsToExeOs(path6.join(homeDir, ".claude", "settings.json"));
985
+ const collisions2 = detectMcpNameCollisions(homeDir, packageRoot).filter((c) => c.serverName === MCP_PRIMARY_KEY || c.serverName === MCP_LEGACY_KEY);
986
+ for (const c of collisions2) {
987
+ process.stderr.write(
988
+ `exe-os: WARNING Claude Code MCP name collision: ${c.serverName} exists in ${c.mcpJsonPath} and ~/.claude.json project ${c.projectPath}. Remove or rename the .mcp.json entry if tools do not surface.
989
+ `
990
+ );
991
+ }
930
992
  return false;
931
993
  }
932
994
  claudeJson.mcpServers[MCP_PRIMARY_KEY] = newEntry;
933
995
  await writeFile3(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
934
996
  await cleanSettingsJsonMcp(path6.join(homeDir, ".claude", "settings.json"));
935
997
  await migratePermissionsToExeOs(path6.join(homeDir, ".claude", "settings.json"));
998
+ const collisions = detectMcpNameCollisions(homeDir, packageRoot).filter((c) => c.serverName === MCP_PRIMARY_KEY || c.serverName === MCP_LEGACY_KEY);
999
+ for (const c of collisions) {
1000
+ process.stderr.write(
1001
+ `exe-os: WARNING Claude Code MCP name collision: ${c.serverName} exists in ${c.mcpJsonPath} and ~/.claude.json project ${c.projectPath}. Remove or rename the .mcp.json entry if tools do not surface.
1002
+ `
1003
+ );
1004
+ }
936
1005
  return true;
937
1006
  }
938
1007
  async function cleanSettingsJsonMcp(settingsPath) {
@@ -1234,8 +1303,6 @@ async function mergeHooks(packageRoot, homeDir = os5.homedir()) {
1234
1303
  "purge_document",
1235
1304
  "rerank_documents",
1236
1305
  "set_document_importance",
1237
- "create_wiki_page",
1238
- "update_wiki_page",
1239
1306
  "get_wiki_page",
1240
1307
  "list_wiki_pages",
1241
1308
  // System
@@ -1773,8 +1840,8 @@ function deriveMachineKey() {
1773
1840
  }
1774
1841
  function readMachineId() {
1775
1842
  try {
1776
- const { readFileSync: readFileSync28 } = __require("fs");
1777
- return readFileSync28("/etc/machine-id", "utf-8").trim();
1843
+ const { readFileSync: readFileSync30 } = __require("fs");
1844
+ return readFileSync30("/etc/machine-id", "utf-8").trim();
1778
1845
  } catch {
1779
1846
  return "";
1780
1847
  }
@@ -6839,6 +6906,9 @@ function classifyMemoryType(input) {
6839
6906
  if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
6840
6907
  if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
6841
6908
  if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
6909
+ if (tool.includes("checkpoint") || text.startsWith("context checkpoint")) return "checkpoint";
6910
+ if (tool.includes("sessionsummary") || tool.includes("session-summary")) return "summary";
6911
+ if (tool.includes("sessionend") || text.startsWith("session ended")) return "summary";
6842
6912
  if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
6843
6913
  if (tool === "store_memory" || tool === "manual") return "observation";
6844
6914
  return "raw";
@@ -7491,7 +7561,7 @@ var init_platform_procedures = __esm({
7491
7561
  title: "MCP tools \u2014 wiki, documents, and content",
7492
7562
  domain: "tool-use",
7493
7563
  priority: "p1",
7494
- content: "create_wiki_page: create a wiki page in exe-wiki. list_wiki_pages: browse wiki pages. get_wiki_page: read a wiki page. update_wiki_page: edit a wiki page. ingest_document: import a file (PDF, MD, etc.) into memory as chunks. list_documents: browse ingested documents by workspace. purge_document: remove a document and its memory chunks. set_document_importance: adjust chunk importance scores. rerank_documents: re-score document relevance for a query."
7564
+ content: "wiki: read/list wiki pages only. Direct wiki write tools are removed; wiki updates flow through raw-data ingestion/projection into the curated wiki store. Legacy aliases: list_wiki_pages/get_wiki_page. crm: read/list/get CRM records from exe-db. raw_data: read capped raw landing-pad events from exe-db with payload opt-in. ingest_document: import a file (PDF, MD, etc.) into memory as chunks. list_documents: browse ingested documents by workspace. purge_document: remove a document and its memory chunks. set_document_importance: adjust chunk importance scores. rerank_documents: re-score document relevance for a query."
7495
7565
  },
7496
7566
  {
7497
7567
  title: "MCP tools \u2014 system, operations, and admin",
@@ -7509,7 +7579,7 @@ var init_platform_procedures = __esm({
7509
7579
  title: "MCP tools \u2014 advanced (triggers, skills, orchestration)",
7510
7580
  domain: "tool-use",
7511
7581
  priority: "p1",
7512
- content: "create_trigger: set up a scheduled recurring agent job (cron). list_triggers: view active triggers. load_skill: load a slash-command skill dynamically. apply_starter_pack: import a pre-built behavior + identity pack for a role. export_orchestration: export full org state (tasks, behaviors, identities) as portable JSON. import_orchestration: import org state into a new instance. deploy_client: deploy a customer client instance. query_company_brain: unified RAG query across all company knowledge. create_reminder: set a text reminder (shown in boot brief). list_reminders: view pending reminders. complete_reminder: mark a reminder done. global_procedure: manage Layer 0 procedures (actions: store, list, deactivate). Legacy aliases: store_global_procedure, list_global_procedures, deactivate_global_procedure."
7582
+ content: "create_trigger: set up a scheduled recurring agent job (cron). list_triggers: view active triggers. load_skill: load a slash-command skill dynamically. apply_starter_pack: import a pre-built behavior + identity pack for a role. export_orchestration: export full org state (tasks, behaviors, identities) as portable JSON. import_orchestration: import org state into a new instance. deploy_client: deploy a customer client instance. query_company_brain: unified RAG query across all company knowledge. create_reminder: set a text reminder (shown in boot brief). list_reminders: view pending reminders. complete_reminder: mark a reminder done. global_procedure: manage customer-owned company procedures (Layer 0; actions: store, list, deactivate). Legacy aliases: store_global_procedure, list_global_procedures, deactivate_global_procedure."
7513
7583
  }
7514
7584
  ];
7515
7585
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -8991,9 +9061,9 @@ Unclassified: ${unclassified}
8991
9061
  }
8992
9062
  async function exportBatches(options) {
8993
9063
  const fs8 = await import("fs");
8994
- const path48 = await import("path");
9064
+ const path49 = await import("path");
8995
9065
  const client = getClient();
8996
- const outDir = path48.join(process.cwd(), "exe/output/classifications/input");
9066
+ const outDir = path49.join(process.cwd(), "exe/output/classifications/input");
8997
9067
  fs8.mkdirSync(outDir, { recursive: true });
8998
9068
  const countResult = await client.execute({
8999
9069
  sql: "SELECT COUNT(*) as cnt FROM memories WHERE intent IS NULL AND outcome IS NULL AND domain IS NULL",
@@ -9017,7 +9087,7 @@ async function exportBatches(options) {
9017
9087
  const text = String(row.text || "").replace(/\n/g, " ");
9018
9088
  return JSON.stringify({ id: row.id, text });
9019
9089
  });
9020
- const batchFile = path48.join(outDir, `batch-${String(batchNum).padStart(4, "0")}.jsonl`);
9090
+ const batchFile = path49.join(outDir, `batch-${String(batchNum).padStart(4, "0")}.jsonl`);
9021
9091
  fs8.writeFileSync(batchFile, lines.join("\n") + "\n");
9022
9092
  exported += batch.rows.length;
9023
9093
  offset += options.batchSize;
@@ -9033,7 +9103,7 @@ async function exportBatches(options) {
9033
9103
  }
9034
9104
  async function importClassifications(importDir) {
9035
9105
  const fs8 = await import("fs");
9036
- const path48 = await import("path");
9106
+ const path49 = await import("path");
9037
9107
  const client = getClient();
9038
9108
  const files = fs8.readdirSync(importDir).filter((f) => f.endsWith(".jsonl")).sort();
9039
9109
  process.stderr.write(`[backfill-metadata] Found ${files.length} JSONL files to import from ${importDir}
@@ -9041,7 +9111,7 @@ async function importClassifications(importDir) {
9041
9111
  let imported = 0;
9042
9112
  let invalid = 0;
9043
9113
  for (const file of files) {
9044
- const lines = fs8.readFileSync(path48.join(importDir, file), "utf-8").split("\n").filter(Boolean);
9114
+ const lines = fs8.readFileSync(path49.join(importDir, file), "utf-8").split("\n").filter(Boolean);
9045
9115
  for (const line of lines) {
9046
9116
  try {
9047
9117
  const rec = JSON.parse(line);
@@ -11674,10 +11744,10 @@ async function disposeEmbedder() {
11674
11744
  async function embedDirect(text) {
11675
11745
  const llamaCpp = await import("node-llama-cpp");
11676
11746
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
11677
- const { existsSync: existsSync34 } = await import("fs");
11678
- const path48 = await import("path");
11679
- const modelPath = path48.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
11680
- if (!existsSync34(modelPath)) {
11747
+ const { existsSync: existsSync35 } = await import("fs");
11748
+ const path49 = await import("path");
11749
+ const modelPath = path49.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
11750
+ if (!existsSync35(modelPath)) {
11681
11751
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
11682
11752
  }
11683
11753
  const llama = await llamaCpp.getLlama();
@@ -14580,6 +14650,8 @@ async function main2() {
14580
14650
  process.exit(1);
14581
14651
  }
14582
14652
  const [oldName, newName] = args2;
14653
+ const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
14654
+ await initStore2({ lightweight: true });
14583
14655
  const result = await renameEmployee(oldName, newName);
14584
14656
  if (!result.success) {
14585
14657
  console.error(`Error: ${result.error}`);
@@ -16462,6 +16534,309 @@ var init_update = __esm({
16462
16534
  }
16463
16535
  });
16464
16536
 
16537
+ // src/lib/stack-update.ts
16538
+ import { execFileSync as execFileSync3 } from "child_process";
16539
+ import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync25, renameSync as renameSync6, writeFileSync as writeFileSync20 } from "fs";
16540
+ import http from "http";
16541
+ import https from "https";
16542
+ import path37 from "path";
16543
+ function parseStackManifest(raw) {
16544
+ const parsed = JSON.parse(raw);
16545
+ if (parsed.schemaVersion !== 1) throw new Error("Unsupported stack manifest schemaVersion");
16546
+ if (!parsed.latest || !parsed.stacks || typeof parsed.stacks !== "object") {
16547
+ throw new Error("Invalid stack manifest: latest and stacks are required");
16548
+ }
16549
+ for (const [version, release] of Object.entries(parsed.stacks)) {
16550
+ if (!release.version) release.version = version;
16551
+ if (!release.services || typeof release.services !== "object") {
16552
+ throw new Error(`Invalid stack manifest: release ${version} has no services`);
16553
+ }
16554
+ for (const [serviceName, service] of Object.entries(release.services)) {
16555
+ if (!service.image || !service.env) {
16556
+ throw new Error(`Invalid stack manifest: ${version}.${serviceName} requires image and env`);
16557
+ }
16558
+ }
16559
+ }
16560
+ return parsed;
16561
+ }
16562
+ async function loadStackManifest(ref, fetchText = defaultFetchText) {
16563
+ if (/^https?:\/\//.test(ref)) return parseStackManifest(await fetchText(ref));
16564
+ return parseStackManifest(readFileSync25(ref, "utf8"));
16565
+ }
16566
+ function parseEnv(raw) {
16567
+ const env = /* @__PURE__ */ new Map();
16568
+ for (const line of raw.split(/\r?\n/)) {
16569
+ const trimmed = line.trim();
16570
+ if (!trimmed || trimmed.startsWith("#")) continue;
16571
+ const idx = line.indexOf("=");
16572
+ if (idx <= 0) continue;
16573
+ env.set(line.slice(0, idx).trim(), line.slice(idx + 1));
16574
+ }
16575
+ return env;
16576
+ }
16577
+ function patchEnv(raw, updates) {
16578
+ const seen = /* @__PURE__ */ new Set();
16579
+ const lines = raw.replace(/\n$/, "").split(/\r?\n/);
16580
+ const patched = lines.map((line) => {
16581
+ const idx = line.indexOf("=");
16582
+ if (idx <= 0 || line.trim().startsWith("#")) return line;
16583
+ const key = line.slice(0, idx).trim();
16584
+ if (!(key in updates)) return line;
16585
+ seen.add(key);
16586
+ return `${key}=${updates[key]}`;
16587
+ });
16588
+ for (const [key, value] of Object.entries(updates)) {
16589
+ if (!seen.has(key)) patched.push(`${key}=${value}`);
16590
+ }
16591
+ return patched.join("\n").replace(/\n*$/, "\n");
16592
+ }
16593
+ function createStackUpdatePlan(manifest, envRaw, targetVersion) {
16594
+ const version = targetVersion ?? manifest.latest;
16595
+ const release = manifest.stacks[version];
16596
+ if (!release) throw new Error(`Stack version ${version} not found in manifest`);
16597
+ const env = parseEnv(envRaw);
16598
+ const changes = [];
16599
+ for (const [serviceName, service] of Object.entries(release.services)) {
16600
+ const before = env.get(service.env);
16601
+ if (before !== service.image) {
16602
+ changes.push({ key: service.env, before, after: service.image, service: serviceName });
16603
+ }
16604
+ }
16605
+ return {
16606
+ manifest,
16607
+ release,
16608
+ targetVersion: version,
16609
+ changes,
16610
+ breakingChanges: release.breakingChanges ?? []
16611
+ };
16612
+ }
16613
+ function assertBreakingChangesAllowed(plan, allowedIds) {
16614
+ const required = plan.breakingChanges.filter((c) => c.requiresConfirmation !== false);
16615
+ const missing = required.filter((c) => !allowedIds.includes(c.id));
16616
+ if (missing.length > 0) {
16617
+ const details = missing.map((c) => `- ${c.id}: ${c.title}
16618
+ ${c.description}
16619
+ Action: ${c.requiredAction ?? "Review release notes."}`).join("\n");
16620
+ throw new Error(
16621
+ `Stack ${plan.targetVersion} has breaking changes that require confirmation:
16622
+ ${details}
16623
+ Re-run with --allow-breaking ${missing.map((c) => c.id).join(",")}`
16624
+ );
16625
+ }
16626
+ }
16627
+ async function runStackUpdate(options) {
16628
+ const exec2 = options.exec ?? defaultExec;
16629
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
16630
+ const manifest = await loadStackManifest(options.manifestRef, options.fetchText);
16631
+ const envRaw = readFileSync25(options.envFile, "utf8");
16632
+ const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
16633
+ assertBreakingChangesAllowed(plan, options.allowedBreakingChangeIds ?? []);
16634
+ const lockFile = options.lockFile ?? path37.join(path37.dirname(options.envFile), ".exe-stack-lock.json");
16635
+ if (options.dryRun || plan.changes.length === 0) {
16636
+ return { status: "planned", targetVersion: plan.targetVersion, changes: plan.changes, lockFile };
16637
+ }
16638
+ const backupDir = path37.join(path37.dirname(options.envFile), ".exe-stack-backups");
16639
+ mkdirSync19(backupDir, { recursive: true });
16640
+ const stamp = now().toISOString().replace(/[:.]/g, "-");
16641
+ const backupEnvFile = path37.join(backupDir, `env-${stamp}.bak`);
16642
+ writeFileSync20(backupEnvFile, envRaw, { mode: 384 });
16643
+ const updates = Object.fromEntries(plan.changes.map((c) => [c.key, c.after]));
16644
+ const patched = patchEnv(envRaw, updates);
16645
+ const tmp = `${options.envFile}.tmp-${process.pid}`;
16646
+ writeFileSync20(tmp, patched, { mode: 384 });
16647
+ renameSync6(tmp, options.envFile);
16648
+ const composeArgs = ["compose", "--file", options.composeFile, "--env-file", options.envFile];
16649
+ try {
16650
+ exec2("docker", [...composeArgs, "pull"]);
16651
+ exec2("docker", [...composeArgs, "up", "-d"]);
16652
+ await verifyReleaseHealth(plan.release, options.healthRetries ?? 12, options.healthDelayMs ?? 5e3);
16653
+ writeFileSync20(lockFile, JSON.stringify({ stackVersion: plan.targetVersion, updatedAt: now().toISOString(), services: plan.release.services }, null, 2) + "\n");
16654
+ return { status: "updated", targetVersion: plan.targetVersion, changes: plan.changes, backupEnvFile, lockFile };
16655
+ } catch (err) {
16656
+ writeFileSync20(options.envFile, envRaw, { mode: 384 });
16657
+ try {
16658
+ exec2("docker", [...composeArgs, "up", "-d"]);
16659
+ } catch {
16660
+ }
16661
+ const reason = err instanceof Error ? err.message : String(err);
16662
+ throw new Error(`Stack update failed and rollback was attempted: ${reason}`);
16663
+ }
16664
+ }
16665
+ async function verifyReleaseHealth(release, retries, delayMs) {
16666
+ for (const [serviceName, service] of Object.entries(release.services)) {
16667
+ if (!service.healthUrl) continue;
16668
+ await waitForHttpOk(service.healthUrl, retries, delayMs, serviceName);
16669
+ }
16670
+ }
16671
+ async function waitForHttpOk(url, retries, delayMs, label) {
16672
+ let last = "";
16673
+ for (let i = 0; i < retries; i++) {
16674
+ try {
16675
+ const status = await httpStatus(url);
16676
+ if (status >= 200 && status < 300) return;
16677
+ last = `HTTP ${status}`;
16678
+ } catch (err) {
16679
+ last = err instanceof Error ? err.message : String(err);
16680
+ }
16681
+ if (i < retries - 1) await new Promise((resolve) => setTimeout(resolve, delayMs));
16682
+ }
16683
+ throw new Error(`Health check failed for ${label} (${url}): ${last}`);
16684
+ }
16685
+ function httpStatus(urlString) {
16686
+ return new Promise((resolve, reject) => {
16687
+ const url = new URL(urlString);
16688
+ const mod = url.protocol === "https:" ? https : http;
16689
+ const req = mod.request(url, { method: "GET", timeout: 5e3 }, (res) => {
16690
+ res.resume();
16691
+ resolve(res.statusCode ?? 0);
16692
+ });
16693
+ req.on("timeout", () => req.destroy(new Error("timeout")));
16694
+ req.on("error", reject);
16695
+ req.end();
16696
+ });
16697
+ }
16698
+ function defaultExec(cmd, args2, opts) {
16699
+ execFileSync3(cmd, args2, { stdio: "inherit", cwd: opts?.cwd });
16700
+ }
16701
+ async function defaultFetchText(ref) {
16702
+ const res = await fetch(ref);
16703
+ if (!res.ok) throw new Error(`Failed to fetch ${ref}: HTTP ${res.status}`);
16704
+ return res.text();
16705
+ }
16706
+ function defaultStackPaths() {
16707
+ const cwdCompose = path37.resolve("docker-compose.yml");
16708
+ const cwdEnv = path37.resolve(".env");
16709
+ return {
16710
+ composeFile: process.env.EXE_STACK_COMPOSE_FILE || (existsSync30(cwdCompose) ? cwdCompose : "/opt/exe-stack/docker-compose.yml"),
16711
+ envFile: process.env.EXE_STACK_ENV_FILE || (existsSync30(cwdEnv) ? cwdEnv : "/opt/exe-stack/.env"),
16712
+ manifestRef: process.env.EXE_STACK_MANIFEST || "https://updates.askexe.com/stack-manifest.json"
16713
+ };
16714
+ }
16715
+ var init_stack_update = __esm({
16716
+ "src/lib/stack-update.ts"() {
16717
+ "use strict";
16718
+ }
16719
+ });
16720
+
16721
+ // src/bin/stack-update.ts
16722
+ var stack_update_exports = {};
16723
+ __export(stack_update_exports, {
16724
+ runStackUpdateCli: () => main3
16725
+ });
16726
+ import { readFileSync as readFileSync26 } from "fs";
16727
+ function parseArgs4(args2) {
16728
+ const defaults = defaultStackPaths();
16729
+ const opts = {
16730
+ manifestRef: defaults.manifestRef,
16731
+ composeFile: defaults.composeFile,
16732
+ envFile: defaults.envFile,
16733
+ dryRun: false,
16734
+ check: false,
16735
+ yes: false,
16736
+ allowedBreakingChangeIds: []
16737
+ };
16738
+ for (let i = 0; i < args2.length; i++) {
16739
+ const arg = args2[i];
16740
+ const next = () => args2[++i] ?? "";
16741
+ if (arg === "--manifest") opts.manifestRef = next();
16742
+ else if (arg.startsWith("--manifest=")) opts.manifestRef = arg.split("=").slice(1).join("=");
16743
+ else if (arg === "--target") opts.targetVersion = next();
16744
+ else if (arg.startsWith("--target=")) opts.targetVersion = arg.split("=")[1];
16745
+ else if (arg === "--compose-file") opts.composeFile = next();
16746
+ else if (arg.startsWith("--compose-file=")) opts.composeFile = arg.split("=").slice(1).join("=");
16747
+ else if (arg === "--env-file") opts.envFile = next();
16748
+ else if (arg.startsWith("--env-file=")) opts.envFile = arg.split("=").slice(1).join("=");
16749
+ else if (arg === "--lock-file") opts.lockFile = next();
16750
+ else if (arg === "--dry-run") opts.dryRun = true;
16751
+ else if (arg === "--check") opts.check = true;
16752
+ else if (arg === "--yes" || arg === "-y") opts.yes = true;
16753
+ else if (arg === "--allow-breaking") opts.allowedBreakingChangeIds.push(...next().split(",").map((s) => s.trim()).filter(Boolean));
16754
+ else if (arg.startsWith("--allow-breaking=")) opts.allowedBreakingChangeIds.push(...arg.split("=")[1].split(",").map((s) => s.trim()).filter(Boolean));
16755
+ else if (arg === "--help" || arg === "-h") {
16756
+ printHelp();
16757
+ process.exit(0);
16758
+ } else {
16759
+ throw new Error(`Unknown option: ${arg}`);
16760
+ }
16761
+ }
16762
+ return opts;
16763
+ }
16764
+ function printHelp() {
16765
+ console.log(`exe-os stack-update \u2014 update a self-hosted Exe OS stack from a pinned manifest
16766
+
16767
+ Usage:
16768
+ exe-os stack-update [--manifest <path-or-url>] [--target <version>] [--yes]
16769
+
16770
+ Options:
16771
+ --manifest <ref> Stack manifest JSON path or URL (default: updates.askexe.com)
16772
+ --target <version> Stack version to install (default: manifest.latest)
16773
+ --compose-file <path> docker-compose.yml path (default: ./docker-compose.yml or /opt/exe-stack/docker-compose.yml)
16774
+ --env-file <path> .env path (default: ./.env or /opt/exe-stack/.env)
16775
+ --lock-file <path> Lock file path (default: beside .env)
16776
+ --check Print available changes only
16777
+ --dry-run Plan only; do not run Docker
16778
+ --allow-breaking <ids> Confirm breaking changes, comma-separated
16779
+ -y, --yes Non-interactive confirmation
16780
+ `);
16781
+ }
16782
+ function printChanges(changes) {
16783
+ if (changes.length === 0) {
16784
+ console.log("\u2705 Stack already matches target manifest.");
16785
+ return;
16786
+ }
16787
+ console.log("Planned image tag changes:");
16788
+ for (const c of changes) {
16789
+ console.log(` - ${c.service}: ${c.key}`);
16790
+ console.log(` ${c.before ?? "<unset>"} \u2192 ${c.after}`);
16791
+ }
16792
+ }
16793
+ function printBreaking(changes) {
16794
+ if (changes.length === 0) return;
16795
+ console.log("\nBreaking-change notices:");
16796
+ for (const c of changes) {
16797
+ console.log(` - ${c.id}: ${c.title}`);
16798
+ console.log(` ${c.description}`);
16799
+ if (c.requiredAction) console.log(` Action: ${c.requiredAction}`);
16800
+ if (c.expectedDowntimeMinutes) console.log(` Expected downtime: ${c.expectedDowntimeMinutes} minutes`);
16801
+ }
16802
+ }
16803
+ async function main3() {
16804
+ const opts = parseArgs4(process.argv.slice(2));
16805
+ const manifest = await loadStackManifest(opts.manifestRef);
16806
+ const envRaw = readFileSync26(opts.envFile, "utf8");
16807
+ const plan = createStackUpdatePlan(manifest, envRaw, opts.targetVersion);
16808
+ console.log(`Exe OS stack target: ${plan.targetVersion}`);
16809
+ console.log(`Manifest: ${opts.manifestRef}`);
16810
+ console.log(`Compose: ${opts.composeFile}`);
16811
+ console.log(`Env: ${opts.envFile}
16812
+ `);
16813
+ printChanges(plan.changes);
16814
+ printBreaking(plan.breakingChanges);
16815
+ if (opts.check || opts.dryRun) return;
16816
+ if (!opts.yes) {
16817
+ console.error("\nRefusing to update without --yes. Re-run with --yes after reviewing the plan.");
16818
+ process.exit(2);
16819
+ }
16820
+ const result = await runStackUpdate(opts);
16821
+ console.log(`
16822
+ \u2705 Stack ${result.status}: ${result.targetVersion}`);
16823
+ if (result.backupEnvFile) console.log(`Backup env: ${result.backupEnvFile}`);
16824
+ console.log(`Lock file: ${result.lockFile}`);
16825
+ }
16826
+ var init_stack_update2 = __esm({
16827
+ "src/bin/stack-update.ts"() {
16828
+ "use strict";
16829
+ init_is_main();
16830
+ init_stack_update();
16831
+ if (isMainModule(import.meta.url)) {
16832
+ main3().catch((err) => {
16833
+ console.error(err instanceof Error ? err.message : String(err));
16834
+ process.exit(1);
16835
+ });
16836
+ }
16837
+ }
16838
+ });
16839
+
16465
16840
  // node_modules/es-toolkit/dist/function/debounce.mjs
16466
16841
  function debounce(func, debounceMs, { signal, edges } = {}) {
16467
16842
  let pendingThis = void 0;
@@ -18263,7 +18638,7 @@ var init_src = __esm({
18263
18638
 
18264
18639
  // node_modules/terminal-size/index.js
18265
18640
  import process2 from "process";
18266
- import { execFileSync as execFileSync3 } from "child_process";
18641
+ import { execFileSync as execFileSync4 } from "child_process";
18267
18642
  import fs from "fs";
18268
18643
  import tty from "tty";
18269
18644
  function terminalSize() {
@@ -18295,7 +18670,7 @@ var init_terminal_size = __esm({
18295
18670
  "use strict";
18296
18671
  defaultColumns = 80;
18297
18672
  defaultRows = 24;
18298
- exec = (command, arguments_, { shell, env } = {}) => execFileSync3(command, arguments_, {
18673
+ exec = (command, arguments_, { shell, env } = {}) => execFileSync4(command, arguments_, {
18299
18674
  encoding: "utf8",
18300
18675
  stdio: ["ignore", "pipe", "ignore"],
18301
18676
  timeout: 500,
@@ -20820,8 +21195,8 @@ var init_ErrorOverview = __esm({
20820
21195
  "use strict";
20821
21196
  init_Box();
20822
21197
  init_Text();
20823
- cleanupPath = (path48) => {
20824
- return path48?.replace(`file://${cwd()}/`, "");
21198
+ cleanupPath = (path49) => {
21199
+ return path49?.replace(`file://${cwd()}/`, "");
20825
21200
  };
20826
21201
  stackUtils = new StackUtils({
20827
21202
  cwd: cwd(),
@@ -23229,11 +23604,11 @@ function Footer() {
23229
23604
  } catch {
23230
23605
  }
23231
23606
  try {
23232
- const { existsSync: existsSync34 } = await import("fs");
23607
+ const { existsSync: existsSync35 } = await import("fs");
23233
23608
  const { join } = await import("path");
23234
23609
  const home = process.env.HOME ?? "";
23235
23610
  const pidPath = join(home, ".exe-os", "exed.pid");
23236
- setDaemon(existsSync34(pidPath) ? "running" : "stopped");
23611
+ setDaemon(existsSync35(pidPath) ? "running" : "stopped");
23237
23612
  } catch {
23238
23613
  setDaemon("unknown");
23239
23614
  }
@@ -25284,10 +25659,10 @@ var init_hooks = __esm({
25284
25659
  });
25285
25660
 
25286
25661
  // src/runtime/safety-checks.ts
25287
- import path37 from "path";
25662
+ import path38 from "path";
25288
25663
  import os18 from "os";
25289
25664
  function checkPathSafety(filePath) {
25290
- const resolved = path37.resolve(filePath);
25665
+ const resolved = path38.resolve(filePath);
25291
25666
  for (const { pattern, reason } of BYPASS_IMMUNE_PATTERNS) {
25292
25667
  const matches = typeof pattern === "function" ? pattern(resolved) : pattern.test(resolved);
25293
25668
  if (matches) {
@@ -25297,7 +25672,7 @@ function checkPathSafety(filePath) {
25297
25672
  return { safe: true, bypassImmune: true };
25298
25673
  }
25299
25674
  function checkReadPathSafety(filePath) {
25300
- const resolved = path37.resolve(filePath);
25675
+ const resolved = path38.resolve(filePath);
25301
25676
  const credPatterns = BYPASS_IMMUNE_PATTERNS.filter(
25302
25677
  (p) => typeof p.pattern !== "function" && (p.reason.includes("secrets") || p.reason.includes("Private key") || p.reason.includes("Credential"))
25303
25678
  );
@@ -25323,11 +25698,11 @@ var init_safety_checks = __esm({
25323
25698
  reason: "Git config can set hooks and command execution"
25324
25699
  },
25325
25700
  {
25326
- pattern: (p) => p.startsWith(path37.join(HOME, ".claude")),
25701
+ pattern: (p) => p.startsWith(path38.join(HOME, ".claude")),
25327
25702
  reason: "Claude configuration files are protected"
25328
25703
  },
25329
25704
  {
25330
- pattern: (p) => p.startsWith(path37.join(HOME, ".exe-os")),
25705
+ pattern: (p) => p.startsWith(path38.join(HOME, ".exe-os")),
25331
25706
  reason: "exe-os configuration files are protected"
25332
25707
  },
25333
25708
  {
@@ -25344,7 +25719,7 @@ var init_safety_checks = __esm({
25344
25719
  },
25345
25720
  {
25346
25721
  pattern: (p) => {
25347
- const name = path37.basename(p);
25722
+ const name = path38.basename(p);
25348
25723
  return [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile", ".zshenv"].includes(name);
25349
25724
  },
25350
25725
  reason: "Shell configuration files can execute arbitrary code on login"
@@ -25371,7 +25746,7 @@ __export(file_read_exports, {
25371
25746
  FileReadTool: () => FileReadTool
25372
25747
  });
25373
25748
  import fs3 from "fs/promises";
25374
- import path38 from "path";
25749
+ import path39 from "path";
25375
25750
  import { z } from "zod";
25376
25751
  function isBinary(buf) {
25377
25752
  for (let i = 0; i < buf.length; i++) {
@@ -25407,7 +25782,7 @@ var init_file_read = __esm({
25407
25782
  return { behavior: "allow" };
25408
25783
  },
25409
25784
  async call(input, context) {
25410
- const filePath = path38.isAbsolute(input.file_path) ? input.file_path : path38.resolve(context.cwd, input.file_path);
25785
+ const filePath = path39.isAbsolute(input.file_path) ? input.file_path : path39.resolve(context.cwd, input.file_path);
25411
25786
  let stat2;
25412
25787
  try {
25413
25788
  stat2 = await fs3.stat(filePath);
@@ -25447,7 +25822,7 @@ __export(glob_exports, {
25447
25822
  GlobTool: () => GlobTool
25448
25823
  });
25449
25824
  import fs4 from "fs/promises";
25450
- import path39 from "path";
25825
+ import path40 from "path";
25451
25826
  import { z as z2 } from "zod";
25452
25827
  async function walkDir(dir, maxDepth = 10) {
25453
25828
  const results = [];
@@ -25463,7 +25838,7 @@ async function walkDir(dir, maxDepth = 10) {
25463
25838
  if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
25464
25839
  continue;
25465
25840
  }
25466
- const fullPath = path39.join(current, entry.name);
25841
+ const fullPath = path40.join(current, entry.name);
25467
25842
  if (entry.isDirectory()) {
25468
25843
  await walk(fullPath, depth + 1);
25469
25844
  } else {
@@ -25497,11 +25872,11 @@ var init_glob = __esm({
25497
25872
  inputSchema: inputSchema2,
25498
25873
  isReadOnly: true,
25499
25874
  async call(input, context) {
25500
- const baseDir = input.path ? path39.isAbsolute(input.path) ? input.path : path39.resolve(context.cwd, input.path) : context.cwd;
25875
+ const baseDir = input.path ? path40.isAbsolute(input.path) ? input.path : path40.resolve(context.cwd, input.path) : context.cwd;
25501
25876
  try {
25502
25877
  const entries = await walkDir(baseDir);
25503
25878
  const matched = entries.filter(
25504
- (e) => simpleGlobMatch(path39.relative(baseDir, e.path), input.pattern)
25879
+ (e) => simpleGlobMatch(path40.relative(baseDir, e.path), input.pattern)
25505
25880
  );
25506
25881
  matched.sort((a, b) => b.mtime - a.mtime);
25507
25882
  if (matched.length === 0) {
@@ -25527,7 +25902,7 @@ __export(grep_exports, {
25527
25902
  });
25528
25903
  import { spawn as spawn2 } from "child_process";
25529
25904
  import fs5 from "fs/promises";
25530
- import path40 from "path";
25905
+ import path41 from "path";
25531
25906
  import { z as z3 } from "zod";
25532
25907
  function runRipgrep(input, searchPath, context) {
25533
25908
  return new Promise((resolve, reject) => {
@@ -25581,7 +25956,7 @@ async function nodeGrep(input, searchPath) {
25581
25956
  }
25582
25957
  for (const entry of entries) {
25583
25958
  if (entry.name === "node_modules" || entry.name === ".git") continue;
25584
- const fullPath = path40.join(dir, entry.name);
25959
+ const fullPath = path41.join(dir, entry.name);
25585
25960
  if (entry.isDirectory()) {
25586
25961
  await walk(fullPath);
25587
25962
  } else {
@@ -25627,7 +26002,7 @@ var init_grep = __esm({
25627
26002
  inputSchema: inputSchema3,
25628
26003
  isReadOnly: true,
25629
26004
  async call(input, context) {
25630
- const searchPath = input.path ? path40.isAbsolute(input.path) ? input.path : path40.resolve(context.cwd, input.path) : context.cwd;
26005
+ const searchPath = input.path ? path41.isAbsolute(input.path) ? input.path : path41.resolve(context.cwd, input.path) : context.cwd;
25631
26006
  try {
25632
26007
  const result = await runRipgrep(input, searchPath, context);
25633
26008
  return result;
@@ -25652,7 +26027,7 @@ __export(file_write_exports, {
25652
26027
  FileWriteTool: () => FileWriteTool
25653
26028
  });
25654
26029
  import fs6 from "fs/promises";
25655
- import path41 from "path";
26030
+ import path42 from "path";
25656
26031
  import { z as z4 } from "zod";
25657
26032
  var inputSchema4, FileWriteTool;
25658
26033
  var init_file_write = __esm({
@@ -25680,8 +26055,8 @@ var init_file_write = __esm({
25680
26055
  return { behavior: "allow" };
25681
26056
  },
25682
26057
  async call(input, context) {
25683
- const filePath = path41.isAbsolute(input.file_path) ? input.file_path : path41.resolve(context.cwd, input.file_path);
25684
- const dir = path41.dirname(filePath);
26058
+ const filePath = path42.isAbsolute(input.file_path) ? input.file_path : path42.resolve(context.cwd, input.file_path);
26059
+ const dir = path42.dirname(filePath);
25685
26060
  await fs6.mkdir(dir, { recursive: true });
25686
26061
  await fs6.writeFile(filePath, input.content, "utf-8");
25687
26062
  return {
@@ -25699,7 +26074,7 @@ __export(file_edit_exports, {
25699
26074
  FileEditTool: () => FileEditTool
25700
26075
  });
25701
26076
  import fs7 from "fs/promises";
25702
- import path42 from "path";
26077
+ import path43 from "path";
25703
26078
  import { z as z5 } from "zod";
25704
26079
  function countOccurrences(haystack, needle) {
25705
26080
  let count = 0;
@@ -25740,7 +26115,7 @@ var init_file_edit = __esm({
25740
26115
  return { behavior: "allow" };
25741
26116
  },
25742
26117
  async call(input, context) {
25743
- const filePath = path42.isAbsolute(input.file_path) ? input.file_path : path42.resolve(context.cwd, input.file_path);
26118
+ const filePath = path43.isAbsolute(input.file_path) ? input.file_path : path43.resolve(context.cwd, input.file_path);
25744
26119
  let content;
25745
26120
  try {
25746
26121
  content = await fs7.readFile(filePath, "utf-8");
@@ -25982,7 +26357,7 @@ var init_bash = __esm({
25982
26357
  // src/tui/views/CommandCenter.tsx
25983
26358
  import { useState as useState6, useEffect as useEffect8, useMemo as useMemo4, useCallback as useCallback4, useRef as useRef4 } from "react";
25984
26359
  import TextInput from "ink-text-input";
25985
- import path43 from "path";
26360
+ import path44 from "path";
25986
26361
  import { homedir as homedir6 } from "os";
25987
26362
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
25988
26363
  function CommandCenterView({
@@ -26017,15 +26392,15 @@ function CommandCenterView({
26017
26392
  const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
26018
26393
  const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
26019
26394
  const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
26020
- const { readFileSync: readFileSync28, existsSync: existsSync34 } = await import("fs");
26395
+ const { readFileSync: readFileSync30, existsSync: existsSync35 } = await import("fs");
26021
26396
  const { join } = await import("path");
26022
26397
  const { homedir: homedir8 } = await import("os");
26023
26398
  const configPath = join(homedir8(), ".exe-os", "config.json");
26024
26399
  let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
26025
26400
  let providerConfigs = {};
26026
- if (existsSync34(configPath)) {
26401
+ if (existsSync35(configPath)) {
26027
26402
  try {
26028
- const raw = JSON.parse(readFileSync28(configPath, "utf8"));
26403
+ const raw = JSON.parse(readFileSync30(configPath, "utf8"));
26029
26404
  if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
26030
26405
  if (raw.providers && typeof raw.providers === "object") {
26031
26406
  providerConfigs = raw.providers;
@@ -26086,7 +26461,7 @@ function CommandCenterView({
26086
26461
  const markerDir = join(homedir8(), ".exe-os", "session-cache");
26087
26462
  const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
26088
26463
  for (const f of agentFiles) {
26089
- const data = JSON.parse(readFileSync28(join(markerDir, f), "utf8"));
26464
+ const data = JSON.parse(readFileSync30(join(markerDir, f), "utf8"));
26090
26465
  if (data.agentRole) {
26091
26466
  agentRole = data.agentRole;
26092
26467
  break;
@@ -26231,7 +26606,7 @@ function CommandCenterView({
26231
26606
  const demoEntries = DEMO_PROJECTS.map((p) => ({
26232
26607
  projectName: p.projectName,
26233
26608
  exeSession: p.exeSession,
26234
- projectDir: path43.join(homedir6(), p.projectName),
26609
+ projectDir: path44.join(homedir6(), p.projectName),
26235
26610
  employeeCount: p.employees.length,
26236
26611
  activeCount: p.employees.filter((e) => e.status === "active").length,
26237
26612
  memoryCount: p.employees.length * 4e3,
@@ -26269,7 +26644,7 @@ function CommandCenterView({
26269
26644
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
26270
26645
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
26271
26646
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
26272
- const { existsSync: existsSync34 } = await import("fs");
26647
+ const { existsSync: existsSync35 } = await import("fs");
26273
26648
  const { join } = await import("path");
26274
26649
  const client = getClient2();
26275
26650
  if (!client) {
@@ -26340,7 +26715,7 @@ function CommandCenterView({
26340
26715
  }
26341
26716
  const memoryCount = memoryCounts.get(name) ?? 0;
26342
26717
  const openTaskCount = openTaskCounts.get(name) ?? 0;
26343
- const hasGit = projectDir ? existsSync34(join(projectDir, ".git")) : false;
26718
+ const hasGit = projectDir ? existsSync35(join(projectDir, ".git")) : false;
26344
26719
  const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
26345
26720
  projectList.push({
26346
26721
  projectName: name,
@@ -26365,7 +26740,7 @@ function CommandCenterView({
26365
26740
  setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
26366
26741
  try {
26367
26742
  const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
26368
- setHealth((h) => ({ ...h, daemon: existsSync34(pidPath) ? "running" : "stopped" }));
26743
+ setHealth((h) => ({ ...h, daemon: existsSync35(pidPath) ? "running" : "stopped" }));
26369
26744
  } catch {
26370
26745
  }
26371
26746
  const activityResult = await client.execute(
@@ -27235,7 +27610,7 @@ var init_useOrchestrator = __esm({
27235
27610
 
27236
27611
  // src/tui/views/Sessions.tsx
27237
27612
  import React19, { useState as useState9, useEffect as useEffect11, useCallback as useCallback6 } from "react";
27238
- import path44 from "path";
27613
+ import path45 from "path";
27239
27614
  import { homedir as homedir7 } from "os";
27240
27615
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
27241
27616
  function isCoordinatorEntry(entry) {
@@ -27273,7 +27648,7 @@ function SessionsView({
27273
27648
  if (demo) {
27274
27649
  setProjects(DEMO_PROJECTS.map((p) => ({
27275
27650
  ...p,
27276
- projectDir: path44.join(homedir7(), p.projectName),
27651
+ projectDir: path45.join(homedir7(), p.projectName),
27277
27652
  employees: p.employees.map((e) => ({ ...e, attached: e.status === "active" }))
27278
27653
  })));
27279
27654
  return;
@@ -28488,12 +28863,12 @@ async function loadGatewayConfig() {
28488
28863
  state.running = false;
28489
28864
  }
28490
28865
  try {
28491
- const { existsSync: existsSync34, readFileSync: readFileSync28 } = await import("fs");
28866
+ const { existsSync: existsSync35, readFileSync: readFileSync30 } = await import("fs");
28492
28867
  const { join } = await import("path");
28493
28868
  const home = process.env.HOME ?? "";
28494
28869
  const configPath = join(home, ".exe-os", "gateway.json");
28495
- if (existsSync34(configPath)) {
28496
- const raw = JSON.parse(readFileSync28(configPath, "utf8"));
28870
+ if (existsSync35(configPath)) {
28871
+ const raw = JSON.parse(readFileSync30(configPath, "utf8"));
28497
28872
  state.port = raw.port ?? 3100;
28498
28873
  state.gatewayUrl = raw.gatewayUrl ?? "";
28499
28874
  if (raw.adapters) {
@@ -29091,12 +29466,12 @@ function TeamView({ onBack, onViewSessions }) {
29091
29466
  setMembers(teamData);
29092
29467
  setDbError(null);
29093
29468
  try {
29094
- const { existsSync: existsSync34, readFileSync: readFileSync28 } = await import("fs");
29469
+ const { existsSync: existsSync35, readFileSync: readFileSync30 } = await import("fs");
29095
29470
  const { join } = await import("path");
29096
29471
  const home = process.env.HOME ?? "";
29097
29472
  const gatewayConfig = join(home, ".exe-os", "gateway.json");
29098
- if (existsSync34(gatewayConfig)) {
29099
- const raw = JSON.parse(readFileSync28(gatewayConfig, "utf8"));
29473
+ if (existsSync35(gatewayConfig)) {
29474
+ const raw = JSON.parse(readFileSync30(gatewayConfig, "utf8"));
29100
29475
  if (raw.agents && raw.agents.length > 0) {
29101
29476
  setExternals(raw.agents.map((a) => ({
29102
29477
  name: a.name,
@@ -29276,8 +29651,8 @@ __export(wiki_client_exports, {
29276
29651
  listDocuments: () => listDocuments,
29277
29652
  listWorkspaces: () => listWorkspaces
29278
29653
  });
29279
- async function wikiFetch(config, path48, method = "GET", body) {
29280
- const url = `${config.baseUrl}/api/v1${path48}`;
29654
+ async function wikiFetch(config, path49, method = "GET", body) {
29655
+ const url = `${config.baseUrl}/api/v1${path49}`;
29281
29656
  const headers = {
29282
29657
  Authorization: `Bearer ${config.apiKey}`,
29283
29658
  "Content-Type": "application/json"
@@ -29310,7 +29685,7 @@ async function wikiFetch(config, path48, method = "GET", body) {
29310
29685
  }
29311
29686
  }
29312
29687
  if (!response.ok) {
29313
- throw new Error(`Wiki API ${method} ${path48}: ${response.status} ${response.statusText}`);
29688
+ throw new Error(`Wiki API ${method} ${path49}: ${response.status} ${response.statusText}`);
29314
29689
  }
29315
29690
  return response.json();
29316
29691
  } finally {
@@ -29904,12 +30279,12 @@ function SettingsView({ onBack }) {
29904
30279
  }
29905
30280
  setProviders(providerList);
29906
30281
  try {
29907
- const { existsSync: existsSync34 } = await import("fs");
30282
+ const { existsSync: existsSync35 } = await import("fs");
29908
30283
  const { join } = await import("path");
29909
30284
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
29910
30285
  const cfg = await loadConfig2();
29911
30286
  const home = process.env.HOME ?? "";
29912
- const hasKey = existsSync34(join(home, ".exe-os", "master.key"));
30287
+ const hasKey = existsSync35(join(home, ".exe-os", "master.key"));
29913
30288
  if (cfg.cloud) {
29914
30289
  setCloud({
29915
30290
  configured: true,
@@ -29922,22 +30297,22 @@ function SettingsView({ onBack }) {
29922
30297
  const pidPath = join(home, ".exe-os", "exed.pid");
29923
30298
  let daemon = "unknown";
29924
30299
  try {
29925
- daemon = existsSync34(pidPath) ? "running" : "stopped";
30300
+ daemon = existsSync35(pidPath) ? "running" : "stopped";
29926
30301
  } catch {
29927
30302
  }
29928
30303
  let version = "unknown";
29929
30304
  try {
29930
- const { readFileSync: readFileSync28 } = await import("fs");
30305
+ const { readFileSync: readFileSync30 } = await import("fs");
29931
30306
  const { createRequire: createRequire3 } = await import("module");
29932
30307
  const require2 = createRequire3(import.meta.url);
29933
30308
  const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
29934
- const pkg = JSON.parse(readFileSync28(pkgPath, "utf8"));
30309
+ const pkg = JSON.parse(readFileSync30(pkgPath, "utf8"));
29935
30310
  version = pkg.version;
29936
30311
  } catch {
29937
30312
  try {
29938
- const { readFileSync: readFileSync28 } = await import("fs");
30313
+ const { readFileSync: readFileSync30 } = await import("fs");
29939
30314
  const { join: joinPath } = await import("path");
29940
- const pkg = JSON.parse(readFileSync28(joinPath(process.cwd(), "package.json"), "utf8"));
30315
+ const pkg = JSON.parse(readFileSync30(joinPath(process.cwd(), "package.json"), "utf8"));
29941
30316
  version = pkg.version;
29942
30317
  } catch {
29943
30318
  }
@@ -30738,15 +31113,15 @@ __export(installer_exports2, {
30738
31113
  verifyOpenCodeHooks: () => verifyOpenCodeHooks
30739
31114
  });
30740
31115
  import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
30741
- import { existsSync as existsSync31, readFileSync as readFileSync26 } from "fs";
30742
- import path45 from "path";
31116
+ import { existsSync as existsSync32, readFileSync as readFileSync28 } from "fs";
31117
+ import path46 from "path";
30743
31118
  import os19 from "os";
30744
31119
  async function registerOpenCodeMcp(packageRoot, homeDir = os19.homedir()) {
30745
- const configDir = path45.join(homeDir, ".config", "opencode");
30746
- const configPath = path45.join(configDir, "opencode.json");
31120
+ const configDir = path46.join(homeDir, ".config", "opencode");
31121
+ const configPath = path46.join(configDir, "opencode.json");
30747
31122
  await mkdir8(configDir, { recursive: true });
30748
31123
  let config = {};
30749
- if (existsSync31(configPath)) {
31124
+ if (existsSync32(configPath)) {
30750
31125
  try {
30751
31126
  config = JSON.parse(await readFile7(configPath, "utf-8"));
30752
31127
  } catch {
@@ -30758,7 +31133,7 @@ async function registerOpenCodeMcp(packageRoot, homeDir = os19.homedir()) {
30758
31133
  }
30759
31134
  const newEntry = {
30760
31135
  type: "local",
30761
- command: ["node", path45.join(packageRoot, "dist", "mcp", "server.js")],
31136
+ command: ["node", path46.join(packageRoot, "dist", "mcp", "server.js")],
30762
31137
  enabled: true
30763
31138
  };
30764
31139
  const current = config.mcp["exe-os"];
@@ -30773,14 +31148,14 @@ async function registerOpenCodeMcp(packageRoot, homeDir = os19.homedir()) {
30773
31148
  return true;
30774
31149
  }
30775
31150
  async function installOpenCodePlugin(packageRoot, homeDir = os19.homedir()) {
30776
- const pluginDir = path45.join(homeDir, ".config", "opencode", "plugins");
30777
- const pluginPath = path45.join(pluginDir, "exe-os.mjs");
31151
+ const pluginDir = path46.join(homeDir, ".config", "opencode", "plugins");
31152
+ const pluginPath = path46.join(pluginDir, "exe-os.mjs");
30778
31153
  await mkdir8(pluginDir, { recursive: true });
30779
31154
  const pluginContent = PLUGIN_TEMPLATE.replace(
30780
31155
  /__PACKAGE_ROOT__/g,
30781
31156
  packageRoot.replace(/\\/g, "\\\\")
30782
31157
  );
30783
- if (existsSync31(pluginPath)) {
31158
+ if (existsSync32(pluginPath)) {
30784
31159
  const existing = await readFile7(pluginPath, "utf-8");
30785
31160
  if (existing === pluginContent) {
30786
31161
  return false;
@@ -30790,16 +31165,16 @@ async function installOpenCodePlugin(packageRoot, homeDir = os19.homedir()) {
30790
31165
  return true;
30791
31166
  }
30792
31167
  function verifyOpenCodeHooks(homeDir = os19.homedir()) {
30793
- const configPath = path45.join(homeDir, ".config", "opencode", "opencode.json");
30794
- const pluginPath = path45.join(homeDir, ".config", "opencode", "plugins", "exe-os.mjs");
30795
- if (!existsSync31(configPath)) return false;
31168
+ const configPath = path46.join(homeDir, ".config", "opencode", "opencode.json");
31169
+ const pluginPath = path46.join(homeDir, ".config", "opencode", "plugins", "exe-os.mjs");
31170
+ if (!existsSync32(configPath)) return false;
30796
31171
  try {
30797
- const config = JSON.parse(readFileSync26(configPath, "utf-8"));
31172
+ const config = JSON.parse(readFileSync28(configPath, "utf-8"));
30798
31173
  if (!config.mcp?.["exe-os"]?.enabled) return false;
30799
31174
  } catch {
30800
31175
  return false;
30801
31176
  }
30802
- if (!existsSync31(pluginPath)) return false;
31177
+ if (!existsSync32(pluginPath)) return false;
30803
31178
  return true;
30804
31179
  }
30805
31180
  async function runOpenCodeInstaller(homeDir) {
@@ -30834,19 +31209,19 @@ __export(installer_exports3, {
30834
31209
  verifyCodexHooks: () => verifyCodexHooks
30835
31210
  });
30836
31211
  import { readFile as readFile8, writeFile as writeFile9, mkdir as mkdir9 } from "fs/promises";
30837
- import { existsSync as existsSync32 } from "fs";
30838
- import path46 from "path";
31212
+ import { existsSync as existsSync33 } from "fs";
31213
+ import path47 from "path";
30839
31214
  import os20 from "os";
30840
31215
  async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30841
- const codexDir = path46.join(homeDir, ".codex");
30842
- const hooksPath = path46.join(codexDir, "hooks.json");
30843
- const logsDir = path46.join(homeDir, ".exe-os", "logs");
30844
- const hookLogPath = path46.join(logsDir, "hooks.log");
31216
+ const codexDir = path47.join(homeDir, ".codex");
31217
+ const hooksPath = path47.join(codexDir, "hooks.json");
31218
+ const logsDir = path47.join(homeDir, ".exe-os", "logs");
31219
+ const hookLogPath = path47.join(logsDir, "hooks.log");
30845
31220
  const logSuffix = ` 2>> "${hookLogPath}"`;
30846
31221
  await mkdir9(codexDir, { recursive: true });
30847
31222
  await mkdir9(logsDir, { recursive: true });
30848
31223
  let hooksJson = {};
30849
- if (existsSync32(hooksPath)) {
31224
+ if (existsSync33(hooksPath)) {
30850
31225
  try {
30851
31226
  hooksJson = JSON.parse(await readFile8(hooksPath, "utf-8"));
30852
31227
  } catch {
@@ -30863,7 +31238,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30863
31238
  hooks: [
30864
31239
  {
30865
31240
  type: "command",
30866
- command: `node "${path46.join(packageRoot, "dist", "hooks", "session-start.js")}"${logSuffix}`,
31241
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "session-start.js")}"${logSuffix}`,
30867
31242
  timeout: 30
30868
31243
  }
30869
31244
  ]
@@ -30879,7 +31254,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30879
31254
  // Combined hook: runs ingest + error-recall in one Node process.
30880
31255
  // Eliminates a cold-start cycle per tool call (~3-6s savings on Codex).
30881
31256
  type: "command",
30882
- command: `node "${path46.join(packageRoot, "dist", "hooks", "post-tool-combined.js")}"${logSuffix}`
31257
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "post-tool-combined.js")}"${logSuffix}`
30883
31258
  }
30884
31259
  ]
30885
31260
  },
@@ -30893,7 +31268,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30893
31268
  // Single hook: prompt-submit handles memory retrieval + entity boost.
30894
31269
  // exe-heartbeat-hook is CC-specific (intercom) — omitted on Codex.
30895
31270
  type: "command",
30896
- command: `node "${path46.join(packageRoot, "dist", "hooks", "prompt-submit.js")}"${logSuffix}`
31271
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "prompt-submit.js")}"${logSuffix}`
30897
31272
  }
30898
31273
  ]
30899
31274
  },
@@ -30905,7 +31280,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30905
31280
  hooks: [
30906
31281
  {
30907
31282
  type: "command",
30908
- command: `node "${path46.join(packageRoot, "dist", "hooks", "stop.js")}"${logSuffix}`
31283
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "stop.js")}"${logSuffix}`
30909
31284
  }
30910
31285
  ]
30911
31286
  },
@@ -30918,7 +31293,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30918
31293
  hooks: [
30919
31294
  {
30920
31295
  type: "command",
30921
- command: `node "${path46.join(packageRoot, "dist", "hooks", "pre-tool-use.js")}"${logSuffix}`
31296
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "pre-tool-use.js")}"${logSuffix}`
30922
31297
  }
30923
31298
  ]
30924
31299
  },
@@ -30950,8 +31325,8 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30950
31325
  return { added, skipped };
30951
31326
  }
30952
31327
  function verifyCodexHooks(homeDir = os20.homedir()) {
30953
- const hooksPath = path46.join(homeDir, ".codex", "hooks.json");
30954
- if (!existsSync32(hooksPath)) return false;
31328
+ const hooksPath = path47.join(homeDir, ".codex", "hooks.json");
31329
+ if (!existsSync33(hooksPath)) return false;
30955
31330
  try {
30956
31331
  const hooksJson = JSON.parse(
30957
31332
  __require("fs").readFileSync(hooksPath, "utf-8")
@@ -30974,11 +31349,11 @@ function verifyCodexHooks(homeDir = os20.homedir()) {
30974
31349
  async function installCodexStatusLine(homeDir = os20.homedir()) {
30975
31350
  const prefs = loadPreferences(homeDir);
30976
31351
  if (prefs.codexStatusLine === false) return "opted-out";
30977
- const codexDir = path46.join(homeDir, ".codex");
30978
- const configPath = path46.join(codexDir, "config.toml");
31352
+ const codexDir = path47.join(homeDir, ".codex");
31353
+ const configPath = path47.join(codexDir, "config.toml");
30979
31354
  await mkdir9(codexDir, { recursive: true });
30980
31355
  let content = "";
30981
- if (existsSync32(configPath)) {
31356
+ if (existsSync33(configPath)) {
30982
31357
  content = await readFile8(configPath, "utf-8");
30983
31358
  if (/\[tui\][\s\S]*?status_line\s*=/.test(content)) {
30984
31359
  return "already-configured";
@@ -31036,12 +31411,12 @@ ${desired}
31036
31411
  return { content: next, changed };
31037
31412
  }
31038
31413
  async function registerCodexMcpServer(packageRoot, homeDir = os20.homedir()) {
31039
- const codexDir = path46.join(homeDir, ".codex");
31040
- const configPath = path46.join(codexDir, "config.toml");
31041
- const serverJsPath = path46.join(packageRoot, "dist", "mcp", "server.js");
31414
+ const codexDir = path47.join(homeDir, ".codex");
31415
+ const configPath = path47.join(codexDir, "config.toml");
31416
+ const serverJsPath = path47.join(packageRoot, "dist", "mcp", "server.js");
31042
31417
  await mkdir9(codexDir, { recursive: true });
31043
31418
  let content = "";
31044
- if (existsSync32(configPath)) {
31419
+ if (existsSync33(configPath)) {
31045
31420
  content = await readFile8(configPath, "utf-8");
31046
31421
  }
31047
31422
  const sectionHeader = "[mcp_servers.exe-os]";
@@ -31066,10 +31441,10 @@ async function registerCodexMcpServer(packageRoot, homeDir = os20.homedir()) {
31066
31441
  return "registered";
31067
31442
  }
31068
31443
  async function ensureCodexHooksFeature(homeDir = os20.homedir()) {
31069
- const configPath = path46.join(homeDir, ".codex", "config.toml");
31070
- await mkdir9(path46.join(homeDir, ".codex"), { recursive: true });
31444
+ const configPath = path47.join(homeDir, ".codex", "config.toml");
31445
+ await mkdir9(path47.join(homeDir, ".codex"), { recursive: true });
31071
31446
  let content = "";
31072
- if (existsSync32(configPath)) {
31447
+ if (existsSync33(configPath)) {
31073
31448
  content = await readFile8(configPath, "utf-8");
31074
31449
  }
31075
31450
  if (/\[features\][\s\S]*?codex_hooks\s*=\s*true/.test(content)) {
@@ -31137,14 +31512,14 @@ var init_installer3 = __esm({
31137
31512
  });
31138
31513
 
31139
31514
  // src/bin/cli.ts
31140
- import { existsSync as existsSync33, readFileSync as readFileSync27, writeFileSync as writeFileSync20, readdirSync as readdirSync10, rmSync } from "fs";
31141
- import path47 from "path";
31515
+ import { existsSync as existsSync34, readFileSync as readFileSync29, writeFileSync as writeFileSync21, readdirSync as readdirSync10, rmSync } from "fs";
31516
+ import path48 from "path";
31142
31517
  import os21 from "os";
31143
31518
  var args = process.argv.slice(2);
31144
31519
  if (args.includes("--version") || args.includes("-v")) {
31145
31520
  try {
31146
- const pkgPath = path47.join(path47.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
31147
- const pkg = JSON.parse(readFileSync27(pkgPath, "utf8"));
31521
+ const pkgPath = path48.join(path48.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
31522
+ const pkg = JSON.parse(readFileSync29(pkgPath, "utf8"));
31148
31523
  console.log(pkg.version);
31149
31524
  } catch {
31150
31525
  console.log("unknown");
@@ -31303,16 +31678,19 @@ ID: ${result.id}`);
31303
31678
  } else if (args[0] === "update") {
31304
31679
  const { runUpdate: runUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
31305
31680
  await runUpdate2(args.slice(1));
31681
+ } else if (args[0] === "stack-update") {
31682
+ const { runStackUpdateCli } = await Promise.resolve().then(() => (init_stack_update2(), stack_update_exports));
31683
+ await runStackUpdateCli();
31306
31684
  } else if (args.includes("--tui") || args.includes("--demo") || args[0] === "tui") {
31307
31685
  checkForUpdateOnBoot().catch(() => {
31308
31686
  });
31309
31687
  await init_App2().then(() => App_exports);
31310
31688
  } else {
31311
- const claudeDir = path47.join(os21.homedir(), ".claude");
31312
- const settingsPath = path47.join(claudeDir, "settings.json");
31313
- const hasClaudeCode = existsSync33(settingsPath) && (() => {
31689
+ const claudeDir = path48.join(os21.homedir(), ".claude");
31690
+ const settingsPath = path48.join(claudeDir, "settings.json");
31691
+ const hasClaudeCode = existsSync34(settingsPath) && (() => {
31314
31692
  try {
31315
- const raw = readFileSync27(settingsPath, "utf8");
31693
+ const raw = readFileSync29(settingsPath, "utf8");
31316
31694
  return raw.includes("exe-os") || raw.includes("exe-mem");
31317
31695
  } catch {
31318
31696
  return false;
@@ -31322,9 +31700,9 @@ ID: ${result.id}`);
31322
31700
  const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
31323
31701
  let cooName = DEFAULT_COORDINATOR_TEMPLATE_NAME2;
31324
31702
  try {
31325
- const rosterPath = path47.join(os21.homedir(), ".exe-os", "exe-employees.json");
31326
- if (existsSync33(rosterPath)) {
31327
- const roster = JSON.parse(readFileSync27(rosterPath, "utf8"));
31703
+ const rosterPath = path48.join(os21.homedir(), ".exe-os", "exe-employees.json");
31704
+ if (existsSync34(rosterPath)) {
31705
+ const roster = JSON.parse(readFileSync29(rosterPath, "utf8"));
31328
31706
  const coo = roster.find((e) => e.role === "COO");
31329
31707
  if (coo) cooName = coo.name;
31330
31708
  }
@@ -31388,14 +31766,15 @@ async function runCodexInstall() {
31388
31766
  }
31389
31767
  }
31390
31768
  async function runClaudeCheck() {
31391
- const claudeDir = path47.join(os21.homedir(), ".claude");
31392
- const settingsPath = path47.join(claudeDir, "settings.json");
31393
- const claudeJsonPath = path47.join(os21.homedir(), ".claude.json");
31769
+ const { detectMcpNameCollisions: detectMcpNameCollisions2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
31770
+ const claudeDir = path48.join(os21.homedir(), ".claude");
31771
+ const settingsPath = path48.join(claudeDir, "settings.json");
31772
+ const claudeJsonPath = path48.join(os21.homedir(), ".claude.json");
31394
31773
  let ok = true;
31395
- if (existsSync33(settingsPath)) {
31774
+ if (existsSync34(settingsPath)) {
31396
31775
  let settings;
31397
31776
  try {
31398
- settings = JSON.parse(readFileSync27(settingsPath, "utf8"));
31777
+ settings = JSON.parse(readFileSync29(settingsPath, "utf8"));
31399
31778
  } catch {
31400
31779
  console.log("\x1B[31m\u2717\x1B[0m settings.json is malformed (invalid JSON)");
31401
31780
  ok = false;
@@ -31421,10 +31800,10 @@ async function runClaudeCheck() {
31421
31800
  console.log("\x1B[31m\u2717\x1B[0m settings.json not found");
31422
31801
  ok = false;
31423
31802
  }
31424
- if (existsSync33(claudeJsonPath)) {
31803
+ if (existsSync34(claudeJsonPath)) {
31425
31804
  let claudeJson;
31426
31805
  try {
31427
- claudeJson = JSON.parse(readFileSync27(claudeJsonPath, "utf8"));
31806
+ claudeJson = JSON.parse(readFileSync29(claudeJsonPath, "utf8"));
31428
31807
  } catch {
31429
31808
  console.log("\x1B[31m\u2717\x1B[0m claude.json is malformed (invalid JSON)");
31430
31809
  ok = false;
@@ -31443,8 +31822,22 @@ async function runClaudeCheck() {
31443
31822
  console.log("\x1B[31m\u2717\x1B[0m claude.json not found");
31444
31823
  ok = false;
31445
31824
  }
31446
- const skillsDir = path47.join(claudeDir, "skills");
31447
- if (existsSync33(skillsDir)) {
31825
+ const collisions = detectMcpNameCollisions2(os21.homedir(), process.cwd());
31826
+ const disabledCollisions = collisions.filter((c) => c.disabledInMcpJson);
31827
+ if (disabledCollisions.length > 0) {
31828
+ console.log("\x1B[31m\u2717\x1B[0m MCP name collision: disabled .mcp.json entries shadow project MCP servers");
31829
+ for (const c of disabledCollisions) {
31830
+ console.log(` - ${c.serverName}: ${c.mcpJsonPath} shadows ~/.claude.json project ${c.projectPath}`);
31831
+ }
31832
+ console.log(" Fix: remove/rename the duplicate .mcp.json server entry, or use one MCP config source of truth.");
31833
+ ok = false;
31834
+ } else if (collisions.length > 0) {
31835
+ console.log("\x1B[33m!\x1B[0m MCP server names duplicated between .mcp.json and project config");
31836
+ } else {
31837
+ console.log("\x1B[32m\u2713\x1B[0m No .mcp.json/project MCP name collisions detected");
31838
+ }
31839
+ const skillsDir = path48.join(claudeDir, "skills");
31840
+ if (existsSync34(skillsDir)) {
31448
31841
  console.log("\x1B[32m\u2713\x1B[0m Slash skills directory exists");
31449
31842
  } else {
31450
31843
  console.log("\x1B[31m\u2717\x1B[0m Slash skills directory missing");
@@ -31461,16 +31854,16 @@ async function runClaudeUninstall(flags = []) {
31461
31854
  const dryRun = flags.includes("--dry-run");
31462
31855
  const purge = flags.includes("--purge");
31463
31856
  const homeDir = os21.homedir();
31464
- const claudeDir = path47.join(homeDir, ".claude");
31465
- const settingsPath = path47.join(claudeDir, "settings.json");
31466
- const claudeJsonPath = path47.join(homeDir, ".claude.json");
31467
- const exeOsDir = path47.join(homeDir, ".exe-os");
31857
+ const claudeDir = path48.join(homeDir, ".claude");
31858
+ const settingsPath = path48.join(claudeDir, "settings.json");
31859
+ const claudeJsonPath = path48.join(homeDir, ".claude.json");
31860
+ const exeOsDir = path48.join(homeDir, ".exe-os");
31468
31861
  let removed = 0;
31469
31862
  const log = (msg) => console.log(dryRun ? `[dry-run] ${msg}` : msg);
31470
31863
  let settings = {};
31471
- if (existsSync33(settingsPath)) {
31864
+ if (existsSync34(settingsPath)) {
31472
31865
  try {
31473
- settings = JSON.parse(readFileSync27(settingsPath, "utf8"));
31866
+ settings = JSON.parse(readFileSync29(settingsPath, "utf8"));
31474
31867
  } catch {
31475
31868
  console.error("Your ~/.claude/settings.json appears malformed.");
31476
31869
  if (purge) {
@@ -31508,15 +31901,15 @@ async function runClaudeUninstall(flags = []) {
31508
31901
  permCount = before - settings.permissions.allow.length;
31509
31902
  }
31510
31903
  if (!dryRun) {
31511
- writeFileSync20(settingsPath, JSON.stringify(settings, null, 2) + "\n");
31904
+ writeFileSync21(settingsPath, JSON.stringify(settings, null, 2) + "\n");
31512
31905
  }
31513
31906
  log("\u2713 Removed exe-os hooks from settings.json");
31514
31907
  if (permCount > 0) log(`\u2713 Removed ${permCount} MCP permission entries`);
31515
31908
  removed++;
31516
31909
  }
31517
31910
  }
31518
- if (existsSync33(claudeJsonPath)) {
31519
- const raw = readFileSync27(claudeJsonPath, "utf8");
31911
+ if (existsSync34(claudeJsonPath)) {
31912
+ const raw = readFileSync29(claudeJsonPath, "utf8");
31520
31913
  if (raw.length > 1e6) {
31521
31914
  console.error("claude.json exceeds 1 MB \u2014 skipping parse.");
31522
31915
  } else {
@@ -31537,7 +31930,7 @@ async function runClaudeUninstall(flags = []) {
31537
31930
  }
31538
31931
  if (removedMcp) {
31539
31932
  if (!dryRun) {
31540
- writeFileSync20(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
31933
+ writeFileSync21(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
31541
31934
  }
31542
31935
  log("\u2713 Removed exe-os MCP server from claude.json");
31543
31936
  removed++;
@@ -31545,14 +31938,14 @@ async function runClaudeUninstall(flags = []) {
31545
31938
  }
31546
31939
  }
31547
31940
  }
31548
- const skillsDir = path47.join(claudeDir, "skills");
31549
- if (existsSync33(skillsDir)) {
31941
+ const skillsDir = path48.join(claudeDir, "skills");
31942
+ if (existsSync34(skillsDir)) {
31550
31943
  let skillCount = 0;
31551
31944
  try {
31552
31945
  const entries = readdirSync10(skillsDir);
31553
31946
  for (const entry of entries) {
31554
31947
  if (entry.startsWith("exe")) {
31555
- const fullPath = path47.join(skillsDir, entry);
31948
+ const fullPath = path48.join(skillsDir, entry);
31556
31949
  if (!dryRun) rmSync(fullPath, { recursive: true, force: true });
31557
31950
  skillCount++;
31558
31951
  }
@@ -31564,30 +31957,30 @@ async function runClaudeUninstall(flags = []) {
31564
31957
  removed++;
31565
31958
  }
31566
31959
  }
31567
- const claudeMdPath = path47.join(claudeDir, "CLAUDE.md");
31568
- if (existsSync33(claudeMdPath)) {
31569
- const content = readFileSync27(claudeMdPath, "utf8");
31960
+ const claudeMdPath = path48.join(claudeDir, "CLAUDE.md");
31961
+ if (existsSync34(claudeMdPath)) {
31962
+ const content = readFileSync29(claudeMdPath, "utf8");
31570
31963
  const startMarker = "<!-- exe-os:orchestration-start -->";
31571
31964
  const endMarker = "<!-- exe-os:orchestration-end -->";
31572
31965
  const startIdx = content.indexOf(startMarker);
31573
31966
  const endIdx = content.indexOf(endMarker);
31574
31967
  if (startIdx !== -1 && endIdx !== -1) {
31575
31968
  const cleaned = (content.slice(0, startIdx) + content.slice(endIdx + endMarker.length)).replace(/\n{3,}/g, "\n\n").trim() + "\n";
31576
- if (!dryRun) writeFileSync20(claudeMdPath, cleaned);
31969
+ if (!dryRun) writeFileSync21(claudeMdPath, cleaned);
31577
31970
  log("\u2713 Removed orchestration block from CLAUDE.md");
31578
31971
  removed++;
31579
31972
  }
31580
31973
  }
31581
- const agentsDir = path47.join(claudeDir, "agents");
31582
- if (existsSync33(agentsDir)) {
31974
+ const agentsDir = path48.join(claudeDir, "agents");
31975
+ if (existsSync34(agentsDir)) {
31583
31976
  let agentCount = 0;
31584
31977
  try {
31585
31978
  const entries = readdirSync10(agentsDir).filter((f) => f.endsWith(".md"));
31586
31979
  let knownNames = /* @__PURE__ */ new Set();
31587
- const rosterPath = path47.join(exeOsDir, "exe-employees.json");
31588
- if (existsSync33(rosterPath)) {
31980
+ const rosterPath = path48.join(exeOsDir, "exe-employees.json");
31981
+ if (existsSync34(rosterPath)) {
31589
31982
  try {
31590
- const roster = JSON.parse(readFileSync27(rosterPath, "utf8"));
31983
+ const roster = JSON.parse(readFileSync29(rosterPath, "utf8"));
31591
31984
  knownNames = new Set(roster.map((e) => e.name));
31592
31985
  } catch {
31593
31986
  }
@@ -31595,7 +31988,7 @@ async function runClaudeUninstall(flags = []) {
31595
31988
  for (const entry of entries) {
31596
31989
  const name = entry.replace(/\.md$/, "");
31597
31990
  if (knownNames.has(name)) {
31598
- if (!dryRun) rmSync(path47.join(agentsDir, entry), { force: true });
31991
+ if (!dryRun) rmSync(path48.join(agentsDir, entry), { force: true });
31599
31992
  agentCount++;
31600
31993
  }
31601
31994
  }
@@ -31606,16 +31999,16 @@ async function runClaudeUninstall(flags = []) {
31606
31999
  removed++;
31607
32000
  }
31608
32001
  }
31609
- const projectsDir = path47.join(claudeDir, "projects");
31610
- if (existsSync33(projectsDir)) {
32002
+ const projectsDir = path48.join(claudeDir, "projects");
32003
+ if (existsSync34(projectsDir)) {
31611
32004
  let projectCount = 0;
31612
32005
  try {
31613
32006
  const projects = readdirSync10(projectsDir);
31614
32007
  for (const proj of projects) {
31615
- const projSettings = path47.join(projectsDir, proj, "settings.json");
31616
- if (!existsSync33(projSettings)) continue;
32008
+ const projSettings = path48.join(projectsDir, proj, "settings.json");
32009
+ if (!existsSync34(projSettings)) continue;
31617
32010
  try {
31618
- const pSettings = JSON.parse(readFileSync27(projSettings, "utf8"));
32011
+ const pSettings = JSON.parse(readFileSync29(projSettings, "utf8"));
31619
32012
  let changed = false;
31620
32013
  if (Array.isArray(pSettings.permissions?.allow)) {
31621
32014
  const before = pSettings.permissions.allow.length;
@@ -31625,7 +32018,7 @@ async function runClaudeUninstall(flags = []) {
31625
32018
  if (pSettings.permissions.allow.length < before) changed = true;
31626
32019
  }
31627
32020
  if (changed && !dryRun) {
31628
- writeFileSync20(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
32021
+ writeFileSync21(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
31629
32022
  }
31630
32023
  if (changed) projectCount++;
31631
32024
  } catch {
@@ -31649,18 +32042,18 @@ async function runClaudeUninstall(flags = []) {
31649
32042
  };
31650
32043
  const exeBinPath = findExeBin3();
31651
32044
  if (!exeBinPath) throw new Error("exe-os not found in PATH");
31652
- const binDir = path47.dirname(exeBinPath);
32045
+ const binDir = path48.dirname(exeBinPath);
31653
32046
  let symlinkCount = 0;
31654
- const rosterPath = path47.join(exeOsDir, "exe-employees.json");
31655
- if (existsSync33(rosterPath)) {
31656
- const roster = JSON.parse(readFileSync27(rosterPath, "utf8"));
32047
+ const rosterPath = path48.join(exeOsDir, "exe-employees.json");
32048
+ if (existsSync34(rosterPath)) {
32049
+ const roster = JSON.parse(readFileSync29(rosterPath, "utf8"));
31657
32050
  const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
31658
32051
  const coordinatorName = roster.find((e) => e.role?.toLowerCase() === "coo")?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME2;
31659
32052
  for (const emp of roster) {
31660
32053
  if (emp.name === coordinatorName) continue;
31661
32054
  for (const suffix of ["", "-opencode"]) {
31662
- const linkPath = path47.join(binDir, `${emp.name}${suffix}`);
31663
- if (existsSync33(linkPath)) {
32055
+ const linkPath = path48.join(binDir, `${emp.name}${suffix}`);
32056
+ if (existsSync34(linkPath)) {
31664
32057
  if (!dryRun) rmSync(linkPath, { force: true });
31665
32058
  symlinkCount++;
31666
32059
  }
@@ -31673,7 +32066,7 @@ async function runClaudeUninstall(flags = []) {
31673
32066
  }
31674
32067
  } catch {
31675
32068
  }
31676
- if (purge && existsSync33(exeOsDir)) {
32069
+ if (purge && existsSync34(exeOsDir)) {
31677
32070
  if (!dryRun) {
31678
32071
  process.stdout.write("\x1B[33m\u26A0 This will delete all memories, identities, and agent data.\x1B[0m\n");
31679
32072
  process.stdout.write(" Removing ~/.exe-os...\n");
@@ -31698,7 +32091,7 @@ async function checkForUpdateOnBoot() {
31698
32091
  const config = await loadConfig2();
31699
32092
  if (!config.autoUpdate.checkOnBoot) return;
31700
32093
  const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
31701
- const packageRoot = path47.resolve(
32094
+ const packageRoot = path48.resolve(
31702
32095
  new URL("../..", import.meta.url).pathname
31703
32096
  );
31704
32097
  const result = checkForUpdate2(packageRoot);
@@ -31758,7 +32151,7 @@ async function runActivate(key) {
31758
32151
  const idTemplate = getIdentityTemplate(identityKey);
31759
32152
  if (idTemplate) {
31760
32153
  const idPath = identityPath2(name);
31761
- const dir = path47.dirname(idPath);
32154
+ const dir = path48.dirname(idPath);
31762
32155
  if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
31763
32156
  fs8.writeFileSync(idPath, idTemplate.replace(/^agent_id: \w+/m, `agent_id: ${name}`), "utf-8");
31764
32157
  }