@askexenow/exe-os 0.9.37 → 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 (69) hide show
  1. package/deploy/stack-manifests/v0.9.json +55 -0
  2. package/dist/bin/backfill-conversations.js +2 -2
  3. package/dist/bin/backfill-responses.js +2 -2
  4. package/dist/bin/backfill-vectors.js +2 -2
  5. package/dist/bin/cleanup-stale-review-tasks.js +2 -2
  6. package/dist/bin/cli.js +554 -164
  7. package/dist/bin/exe-agent.js +2 -2
  8. package/dist/bin/exe-assign.js +2 -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 +2 -2
  12. package/dist/bin/exe-doctor.js +2 -2
  13. package/dist/bin/exe-export-behaviors.js +2 -2
  14. package/dist/bin/exe-forget.js +2 -2
  15. package/dist/bin/exe-gateway.js +24 -3
  16. package/dist/bin/exe-heartbeat.js +2 -2
  17. package/dist/bin/exe-kill.js +2 -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 +2 -2
  21. package/dist/bin/exe-pending-notifications.js +2 -2
  22. package/dist/bin/exe-pending-reviews.js +2 -2
  23. package/dist/bin/exe-rename.js +1747 -199
  24. package/dist/bin/exe-review.js +2 -2
  25. package/dist/bin/exe-search.js +2 -2
  26. package/dist/bin/exe-session-cleanup.js +2 -2
  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 +2 -2
  30. package/dist/bin/exe-team.js +2 -2
  31. package/dist/bin/git-sweep.js +2 -2
  32. package/dist/bin/graph-backfill.js +2 -2
  33. package/dist/bin/graph-export.js +2 -2
  34. package/dist/bin/install.js +68 -2
  35. package/dist/bin/intercom-check.js +2 -2
  36. package/dist/bin/scan-tasks.js +2 -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 +24 -3
  41. package/dist/hooks/bug-report-worker.js +2 -2
  42. package/dist/hooks/codex-stop-task-finalizer.js +2 -2
  43. package/dist/hooks/commit-complete.js +2 -2
  44. package/dist/hooks/error-recall.js +2 -2
  45. package/dist/hooks/ingest.js +2 -2
  46. package/dist/hooks/instructions-loaded.js +2 -2
  47. package/dist/hooks/notification.js +2 -2
  48. package/dist/hooks/post-compact.js +2 -2
  49. package/dist/hooks/post-tool-combined.js +2 -2
  50. package/dist/hooks/pre-compact.js +2 -2
  51. package/dist/hooks/pre-tool-use.js +2 -2
  52. package/dist/hooks/prompt-submit.js +2 -2
  53. package/dist/hooks/session-end.js +2 -2
  54. package/dist/hooks/session-start.js +2 -2
  55. package/dist/hooks/stop.js +2 -2
  56. package/dist/hooks/subagent-stop.js +2 -2
  57. package/dist/hooks/summary-worker.js +2 -2
  58. package/dist/index.js +24 -3
  59. package/dist/lib/employee-templates.js +2 -2
  60. package/dist/lib/exe-daemon.js +11005 -10413
  61. package/dist/lib/hybrid-search.js +2 -2
  62. package/dist/lib/schedules.js +2 -2
  63. package/dist/lib/store.js +2 -2
  64. package/dist/mcp/server.js +6865 -6341
  65. package/dist/runtime/index.js +2 -2
  66. package/dist/tui/App.js +2 -2
  67. package/package.json +4 -1
  68. package/stack.release.json +31 -0
  69. 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
  }
@@ -7494,7 +7561,7 @@ var init_platform_procedures = __esm({
7494
7561
  title: "MCP tools \u2014 wiki, documents, and content",
7495
7562
  domain: "tool-use",
7496
7563
  priority: "p1",
7497
- 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."
7498
7565
  },
7499
7566
  {
7500
7567
  title: "MCP tools \u2014 system, operations, and admin",
@@ -7512,7 +7579,7 @@ var init_platform_procedures = __esm({
7512
7579
  title: "MCP tools \u2014 advanced (triggers, skills, orchestration)",
7513
7580
  domain: "tool-use",
7514
7581
  priority: "p1",
7515
- 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."
7516
7583
  }
7517
7584
  ];
7518
7585
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -8994,9 +9061,9 @@ Unclassified: ${unclassified}
8994
9061
  }
8995
9062
  async function exportBatches(options) {
8996
9063
  const fs8 = await import("fs");
8997
- const path48 = await import("path");
9064
+ const path49 = await import("path");
8998
9065
  const client = getClient();
8999
- const outDir = path48.join(process.cwd(), "exe/output/classifications/input");
9066
+ const outDir = path49.join(process.cwd(), "exe/output/classifications/input");
9000
9067
  fs8.mkdirSync(outDir, { recursive: true });
9001
9068
  const countResult = await client.execute({
9002
9069
  sql: "SELECT COUNT(*) as cnt FROM memories WHERE intent IS NULL AND outcome IS NULL AND domain IS NULL",
@@ -9020,7 +9087,7 @@ async function exportBatches(options) {
9020
9087
  const text = String(row.text || "").replace(/\n/g, " ");
9021
9088
  return JSON.stringify({ id: row.id, text });
9022
9089
  });
9023
- 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`);
9024
9091
  fs8.writeFileSync(batchFile, lines.join("\n") + "\n");
9025
9092
  exported += batch.rows.length;
9026
9093
  offset += options.batchSize;
@@ -9036,7 +9103,7 @@ async function exportBatches(options) {
9036
9103
  }
9037
9104
  async function importClassifications(importDir) {
9038
9105
  const fs8 = await import("fs");
9039
- const path48 = await import("path");
9106
+ const path49 = await import("path");
9040
9107
  const client = getClient();
9041
9108
  const files = fs8.readdirSync(importDir).filter((f) => f.endsWith(".jsonl")).sort();
9042
9109
  process.stderr.write(`[backfill-metadata] Found ${files.length} JSONL files to import from ${importDir}
@@ -9044,7 +9111,7 @@ async function importClassifications(importDir) {
9044
9111
  let imported = 0;
9045
9112
  let invalid = 0;
9046
9113
  for (const file of files) {
9047
- 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);
9048
9115
  for (const line of lines) {
9049
9116
  try {
9050
9117
  const rec = JSON.parse(line);
@@ -11677,10 +11744,10 @@ async function disposeEmbedder() {
11677
11744
  async function embedDirect(text) {
11678
11745
  const llamaCpp = await import("node-llama-cpp");
11679
11746
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
11680
- const { existsSync: existsSync34 } = await import("fs");
11681
- const path48 = await import("path");
11682
- const modelPath = path48.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
11683
- 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)) {
11684
11751
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
11685
11752
  }
11686
11753
  const llama = await llamaCpp.getLlama();
@@ -14583,6 +14650,8 @@ async function main2() {
14583
14650
  process.exit(1);
14584
14651
  }
14585
14652
  const [oldName, newName] = args2;
14653
+ const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
14654
+ await initStore2({ lightweight: true });
14586
14655
  const result = await renameEmployee(oldName, newName);
14587
14656
  if (!result.success) {
14588
14657
  console.error(`Error: ${result.error}`);
@@ -16465,6 +16534,309 @@ var init_update = __esm({
16465
16534
  }
16466
16535
  });
16467
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
+
16468
16840
  // node_modules/es-toolkit/dist/function/debounce.mjs
16469
16841
  function debounce(func, debounceMs, { signal, edges } = {}) {
16470
16842
  let pendingThis = void 0;
@@ -18266,7 +18638,7 @@ var init_src = __esm({
18266
18638
 
18267
18639
  // node_modules/terminal-size/index.js
18268
18640
  import process2 from "process";
18269
- import { execFileSync as execFileSync3 } from "child_process";
18641
+ import { execFileSync as execFileSync4 } from "child_process";
18270
18642
  import fs from "fs";
18271
18643
  import tty from "tty";
18272
18644
  function terminalSize() {
@@ -18298,7 +18670,7 @@ var init_terminal_size = __esm({
18298
18670
  "use strict";
18299
18671
  defaultColumns = 80;
18300
18672
  defaultRows = 24;
18301
- exec = (command, arguments_, { shell, env } = {}) => execFileSync3(command, arguments_, {
18673
+ exec = (command, arguments_, { shell, env } = {}) => execFileSync4(command, arguments_, {
18302
18674
  encoding: "utf8",
18303
18675
  stdio: ["ignore", "pipe", "ignore"],
18304
18676
  timeout: 500,
@@ -20823,8 +21195,8 @@ var init_ErrorOverview = __esm({
20823
21195
  "use strict";
20824
21196
  init_Box();
20825
21197
  init_Text();
20826
- cleanupPath = (path48) => {
20827
- return path48?.replace(`file://${cwd()}/`, "");
21198
+ cleanupPath = (path49) => {
21199
+ return path49?.replace(`file://${cwd()}/`, "");
20828
21200
  };
20829
21201
  stackUtils = new StackUtils({
20830
21202
  cwd: cwd(),
@@ -23232,11 +23604,11 @@ function Footer() {
23232
23604
  } catch {
23233
23605
  }
23234
23606
  try {
23235
- const { existsSync: existsSync34 } = await import("fs");
23607
+ const { existsSync: existsSync35 } = await import("fs");
23236
23608
  const { join } = await import("path");
23237
23609
  const home = process.env.HOME ?? "";
23238
23610
  const pidPath = join(home, ".exe-os", "exed.pid");
23239
- setDaemon(existsSync34(pidPath) ? "running" : "stopped");
23611
+ setDaemon(existsSync35(pidPath) ? "running" : "stopped");
23240
23612
  } catch {
23241
23613
  setDaemon("unknown");
23242
23614
  }
@@ -25287,10 +25659,10 @@ var init_hooks = __esm({
25287
25659
  });
25288
25660
 
25289
25661
  // src/runtime/safety-checks.ts
25290
- import path37 from "path";
25662
+ import path38 from "path";
25291
25663
  import os18 from "os";
25292
25664
  function checkPathSafety(filePath) {
25293
- const resolved = path37.resolve(filePath);
25665
+ const resolved = path38.resolve(filePath);
25294
25666
  for (const { pattern, reason } of BYPASS_IMMUNE_PATTERNS) {
25295
25667
  const matches = typeof pattern === "function" ? pattern(resolved) : pattern.test(resolved);
25296
25668
  if (matches) {
@@ -25300,7 +25672,7 @@ function checkPathSafety(filePath) {
25300
25672
  return { safe: true, bypassImmune: true };
25301
25673
  }
25302
25674
  function checkReadPathSafety(filePath) {
25303
- const resolved = path37.resolve(filePath);
25675
+ const resolved = path38.resolve(filePath);
25304
25676
  const credPatterns = BYPASS_IMMUNE_PATTERNS.filter(
25305
25677
  (p) => typeof p.pattern !== "function" && (p.reason.includes("secrets") || p.reason.includes("Private key") || p.reason.includes("Credential"))
25306
25678
  );
@@ -25326,11 +25698,11 @@ var init_safety_checks = __esm({
25326
25698
  reason: "Git config can set hooks and command execution"
25327
25699
  },
25328
25700
  {
25329
- pattern: (p) => p.startsWith(path37.join(HOME, ".claude")),
25701
+ pattern: (p) => p.startsWith(path38.join(HOME, ".claude")),
25330
25702
  reason: "Claude configuration files are protected"
25331
25703
  },
25332
25704
  {
25333
- pattern: (p) => p.startsWith(path37.join(HOME, ".exe-os")),
25705
+ pattern: (p) => p.startsWith(path38.join(HOME, ".exe-os")),
25334
25706
  reason: "exe-os configuration files are protected"
25335
25707
  },
25336
25708
  {
@@ -25347,7 +25719,7 @@ var init_safety_checks = __esm({
25347
25719
  },
25348
25720
  {
25349
25721
  pattern: (p) => {
25350
- const name = path37.basename(p);
25722
+ const name = path38.basename(p);
25351
25723
  return [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile", ".zshenv"].includes(name);
25352
25724
  },
25353
25725
  reason: "Shell configuration files can execute arbitrary code on login"
@@ -25374,7 +25746,7 @@ __export(file_read_exports, {
25374
25746
  FileReadTool: () => FileReadTool
25375
25747
  });
25376
25748
  import fs3 from "fs/promises";
25377
- import path38 from "path";
25749
+ import path39 from "path";
25378
25750
  import { z } from "zod";
25379
25751
  function isBinary(buf) {
25380
25752
  for (let i = 0; i < buf.length; i++) {
@@ -25410,7 +25782,7 @@ var init_file_read = __esm({
25410
25782
  return { behavior: "allow" };
25411
25783
  },
25412
25784
  async call(input, context) {
25413
- 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);
25414
25786
  let stat2;
25415
25787
  try {
25416
25788
  stat2 = await fs3.stat(filePath);
@@ -25450,7 +25822,7 @@ __export(glob_exports, {
25450
25822
  GlobTool: () => GlobTool
25451
25823
  });
25452
25824
  import fs4 from "fs/promises";
25453
- import path39 from "path";
25825
+ import path40 from "path";
25454
25826
  import { z as z2 } from "zod";
25455
25827
  async function walkDir(dir, maxDepth = 10) {
25456
25828
  const results = [];
@@ -25466,7 +25838,7 @@ async function walkDir(dir, maxDepth = 10) {
25466
25838
  if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
25467
25839
  continue;
25468
25840
  }
25469
- const fullPath = path39.join(current, entry.name);
25841
+ const fullPath = path40.join(current, entry.name);
25470
25842
  if (entry.isDirectory()) {
25471
25843
  await walk(fullPath, depth + 1);
25472
25844
  } else {
@@ -25500,11 +25872,11 @@ var init_glob = __esm({
25500
25872
  inputSchema: inputSchema2,
25501
25873
  isReadOnly: true,
25502
25874
  async call(input, context) {
25503
- 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;
25504
25876
  try {
25505
25877
  const entries = await walkDir(baseDir);
25506
25878
  const matched = entries.filter(
25507
- (e) => simpleGlobMatch(path39.relative(baseDir, e.path), input.pattern)
25879
+ (e) => simpleGlobMatch(path40.relative(baseDir, e.path), input.pattern)
25508
25880
  );
25509
25881
  matched.sort((a, b) => b.mtime - a.mtime);
25510
25882
  if (matched.length === 0) {
@@ -25530,7 +25902,7 @@ __export(grep_exports, {
25530
25902
  });
25531
25903
  import { spawn as spawn2 } from "child_process";
25532
25904
  import fs5 from "fs/promises";
25533
- import path40 from "path";
25905
+ import path41 from "path";
25534
25906
  import { z as z3 } from "zod";
25535
25907
  function runRipgrep(input, searchPath, context) {
25536
25908
  return new Promise((resolve, reject) => {
@@ -25584,7 +25956,7 @@ async function nodeGrep(input, searchPath) {
25584
25956
  }
25585
25957
  for (const entry of entries) {
25586
25958
  if (entry.name === "node_modules" || entry.name === ".git") continue;
25587
- const fullPath = path40.join(dir, entry.name);
25959
+ const fullPath = path41.join(dir, entry.name);
25588
25960
  if (entry.isDirectory()) {
25589
25961
  await walk(fullPath);
25590
25962
  } else {
@@ -25630,7 +26002,7 @@ var init_grep = __esm({
25630
26002
  inputSchema: inputSchema3,
25631
26003
  isReadOnly: true,
25632
26004
  async call(input, context) {
25633
- 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;
25634
26006
  try {
25635
26007
  const result = await runRipgrep(input, searchPath, context);
25636
26008
  return result;
@@ -25655,7 +26027,7 @@ __export(file_write_exports, {
25655
26027
  FileWriteTool: () => FileWriteTool
25656
26028
  });
25657
26029
  import fs6 from "fs/promises";
25658
- import path41 from "path";
26030
+ import path42 from "path";
25659
26031
  import { z as z4 } from "zod";
25660
26032
  var inputSchema4, FileWriteTool;
25661
26033
  var init_file_write = __esm({
@@ -25683,8 +26055,8 @@ var init_file_write = __esm({
25683
26055
  return { behavior: "allow" };
25684
26056
  },
25685
26057
  async call(input, context) {
25686
- const filePath = path41.isAbsolute(input.file_path) ? input.file_path : path41.resolve(context.cwd, input.file_path);
25687
- 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);
25688
26060
  await fs6.mkdir(dir, { recursive: true });
25689
26061
  await fs6.writeFile(filePath, input.content, "utf-8");
25690
26062
  return {
@@ -25702,7 +26074,7 @@ __export(file_edit_exports, {
25702
26074
  FileEditTool: () => FileEditTool
25703
26075
  });
25704
26076
  import fs7 from "fs/promises";
25705
- import path42 from "path";
26077
+ import path43 from "path";
25706
26078
  import { z as z5 } from "zod";
25707
26079
  function countOccurrences(haystack, needle) {
25708
26080
  let count = 0;
@@ -25743,7 +26115,7 @@ var init_file_edit = __esm({
25743
26115
  return { behavior: "allow" };
25744
26116
  },
25745
26117
  async call(input, context) {
25746
- 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);
25747
26119
  let content;
25748
26120
  try {
25749
26121
  content = await fs7.readFile(filePath, "utf-8");
@@ -25985,7 +26357,7 @@ var init_bash = __esm({
25985
26357
  // src/tui/views/CommandCenter.tsx
25986
26358
  import { useState as useState6, useEffect as useEffect8, useMemo as useMemo4, useCallback as useCallback4, useRef as useRef4 } from "react";
25987
26359
  import TextInput from "ink-text-input";
25988
- import path43 from "path";
26360
+ import path44 from "path";
25989
26361
  import { homedir as homedir6 } from "os";
25990
26362
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
25991
26363
  function CommandCenterView({
@@ -26020,15 +26392,15 @@ function CommandCenterView({
26020
26392
  const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
26021
26393
  const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
26022
26394
  const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
26023
- const { readFileSync: readFileSync28, existsSync: existsSync34 } = await import("fs");
26395
+ const { readFileSync: readFileSync30, existsSync: existsSync35 } = await import("fs");
26024
26396
  const { join } = await import("path");
26025
26397
  const { homedir: homedir8 } = await import("os");
26026
26398
  const configPath = join(homedir8(), ".exe-os", "config.json");
26027
26399
  let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
26028
26400
  let providerConfigs = {};
26029
- if (existsSync34(configPath)) {
26401
+ if (existsSync35(configPath)) {
26030
26402
  try {
26031
- const raw = JSON.parse(readFileSync28(configPath, "utf8"));
26403
+ const raw = JSON.parse(readFileSync30(configPath, "utf8"));
26032
26404
  if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
26033
26405
  if (raw.providers && typeof raw.providers === "object") {
26034
26406
  providerConfigs = raw.providers;
@@ -26089,7 +26461,7 @@ function CommandCenterView({
26089
26461
  const markerDir = join(homedir8(), ".exe-os", "session-cache");
26090
26462
  const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
26091
26463
  for (const f of agentFiles) {
26092
- const data = JSON.parse(readFileSync28(join(markerDir, f), "utf8"));
26464
+ const data = JSON.parse(readFileSync30(join(markerDir, f), "utf8"));
26093
26465
  if (data.agentRole) {
26094
26466
  agentRole = data.agentRole;
26095
26467
  break;
@@ -26234,7 +26606,7 @@ function CommandCenterView({
26234
26606
  const demoEntries = DEMO_PROJECTS.map((p) => ({
26235
26607
  projectName: p.projectName,
26236
26608
  exeSession: p.exeSession,
26237
- projectDir: path43.join(homedir6(), p.projectName),
26609
+ projectDir: path44.join(homedir6(), p.projectName),
26238
26610
  employeeCount: p.employees.length,
26239
26611
  activeCount: p.employees.filter((e) => e.status === "active").length,
26240
26612
  memoryCount: p.employees.length * 4e3,
@@ -26272,7 +26644,7 @@ function CommandCenterView({
26272
26644
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
26273
26645
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
26274
26646
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
26275
- const { existsSync: existsSync34 } = await import("fs");
26647
+ const { existsSync: existsSync35 } = await import("fs");
26276
26648
  const { join } = await import("path");
26277
26649
  const client = getClient2();
26278
26650
  if (!client) {
@@ -26343,7 +26715,7 @@ function CommandCenterView({
26343
26715
  }
26344
26716
  const memoryCount = memoryCounts.get(name) ?? 0;
26345
26717
  const openTaskCount = openTaskCounts.get(name) ?? 0;
26346
- const hasGit = projectDir ? existsSync34(join(projectDir, ".git")) : false;
26718
+ const hasGit = projectDir ? existsSync35(join(projectDir, ".git")) : false;
26347
26719
  const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
26348
26720
  projectList.push({
26349
26721
  projectName: name,
@@ -26368,7 +26740,7 @@ function CommandCenterView({
26368
26740
  setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
26369
26741
  try {
26370
26742
  const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
26371
- setHealth((h) => ({ ...h, daemon: existsSync34(pidPath) ? "running" : "stopped" }));
26743
+ setHealth((h) => ({ ...h, daemon: existsSync35(pidPath) ? "running" : "stopped" }));
26372
26744
  } catch {
26373
26745
  }
26374
26746
  const activityResult = await client.execute(
@@ -27238,7 +27610,7 @@ var init_useOrchestrator = __esm({
27238
27610
 
27239
27611
  // src/tui/views/Sessions.tsx
27240
27612
  import React19, { useState as useState9, useEffect as useEffect11, useCallback as useCallback6 } from "react";
27241
- import path44 from "path";
27613
+ import path45 from "path";
27242
27614
  import { homedir as homedir7 } from "os";
27243
27615
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
27244
27616
  function isCoordinatorEntry(entry) {
@@ -27276,7 +27648,7 @@ function SessionsView({
27276
27648
  if (demo) {
27277
27649
  setProjects(DEMO_PROJECTS.map((p) => ({
27278
27650
  ...p,
27279
- projectDir: path44.join(homedir7(), p.projectName),
27651
+ projectDir: path45.join(homedir7(), p.projectName),
27280
27652
  employees: p.employees.map((e) => ({ ...e, attached: e.status === "active" }))
27281
27653
  })));
27282
27654
  return;
@@ -28491,12 +28863,12 @@ async function loadGatewayConfig() {
28491
28863
  state.running = false;
28492
28864
  }
28493
28865
  try {
28494
- const { existsSync: existsSync34, readFileSync: readFileSync28 } = await import("fs");
28866
+ const { existsSync: existsSync35, readFileSync: readFileSync30 } = await import("fs");
28495
28867
  const { join } = await import("path");
28496
28868
  const home = process.env.HOME ?? "";
28497
28869
  const configPath = join(home, ".exe-os", "gateway.json");
28498
- if (existsSync34(configPath)) {
28499
- const raw = JSON.parse(readFileSync28(configPath, "utf8"));
28870
+ if (existsSync35(configPath)) {
28871
+ const raw = JSON.parse(readFileSync30(configPath, "utf8"));
28500
28872
  state.port = raw.port ?? 3100;
28501
28873
  state.gatewayUrl = raw.gatewayUrl ?? "";
28502
28874
  if (raw.adapters) {
@@ -29094,12 +29466,12 @@ function TeamView({ onBack, onViewSessions }) {
29094
29466
  setMembers(teamData);
29095
29467
  setDbError(null);
29096
29468
  try {
29097
- const { existsSync: existsSync34, readFileSync: readFileSync28 } = await import("fs");
29469
+ const { existsSync: existsSync35, readFileSync: readFileSync30 } = await import("fs");
29098
29470
  const { join } = await import("path");
29099
29471
  const home = process.env.HOME ?? "";
29100
29472
  const gatewayConfig = join(home, ".exe-os", "gateway.json");
29101
- if (existsSync34(gatewayConfig)) {
29102
- const raw = JSON.parse(readFileSync28(gatewayConfig, "utf8"));
29473
+ if (existsSync35(gatewayConfig)) {
29474
+ const raw = JSON.parse(readFileSync30(gatewayConfig, "utf8"));
29103
29475
  if (raw.agents && raw.agents.length > 0) {
29104
29476
  setExternals(raw.agents.map((a) => ({
29105
29477
  name: a.name,
@@ -29279,8 +29651,8 @@ __export(wiki_client_exports, {
29279
29651
  listDocuments: () => listDocuments,
29280
29652
  listWorkspaces: () => listWorkspaces
29281
29653
  });
29282
- async function wikiFetch(config, path48, method = "GET", body) {
29283
- const url = `${config.baseUrl}/api/v1${path48}`;
29654
+ async function wikiFetch(config, path49, method = "GET", body) {
29655
+ const url = `${config.baseUrl}/api/v1${path49}`;
29284
29656
  const headers = {
29285
29657
  Authorization: `Bearer ${config.apiKey}`,
29286
29658
  "Content-Type": "application/json"
@@ -29313,7 +29685,7 @@ async function wikiFetch(config, path48, method = "GET", body) {
29313
29685
  }
29314
29686
  }
29315
29687
  if (!response.ok) {
29316
- throw new Error(`Wiki API ${method} ${path48}: ${response.status} ${response.statusText}`);
29688
+ throw new Error(`Wiki API ${method} ${path49}: ${response.status} ${response.statusText}`);
29317
29689
  }
29318
29690
  return response.json();
29319
29691
  } finally {
@@ -29907,12 +30279,12 @@ function SettingsView({ onBack }) {
29907
30279
  }
29908
30280
  setProviders(providerList);
29909
30281
  try {
29910
- const { existsSync: existsSync34 } = await import("fs");
30282
+ const { existsSync: existsSync35 } = await import("fs");
29911
30283
  const { join } = await import("path");
29912
30284
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
29913
30285
  const cfg = await loadConfig2();
29914
30286
  const home = process.env.HOME ?? "";
29915
- const hasKey = existsSync34(join(home, ".exe-os", "master.key"));
30287
+ const hasKey = existsSync35(join(home, ".exe-os", "master.key"));
29916
30288
  if (cfg.cloud) {
29917
30289
  setCloud({
29918
30290
  configured: true,
@@ -29925,22 +30297,22 @@ function SettingsView({ onBack }) {
29925
30297
  const pidPath = join(home, ".exe-os", "exed.pid");
29926
30298
  let daemon = "unknown";
29927
30299
  try {
29928
- daemon = existsSync34(pidPath) ? "running" : "stopped";
30300
+ daemon = existsSync35(pidPath) ? "running" : "stopped";
29929
30301
  } catch {
29930
30302
  }
29931
30303
  let version = "unknown";
29932
30304
  try {
29933
- const { readFileSync: readFileSync28 } = await import("fs");
30305
+ const { readFileSync: readFileSync30 } = await import("fs");
29934
30306
  const { createRequire: createRequire3 } = await import("module");
29935
30307
  const require2 = createRequire3(import.meta.url);
29936
30308
  const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
29937
- const pkg = JSON.parse(readFileSync28(pkgPath, "utf8"));
30309
+ const pkg = JSON.parse(readFileSync30(pkgPath, "utf8"));
29938
30310
  version = pkg.version;
29939
30311
  } catch {
29940
30312
  try {
29941
- const { readFileSync: readFileSync28 } = await import("fs");
30313
+ const { readFileSync: readFileSync30 } = await import("fs");
29942
30314
  const { join: joinPath } = await import("path");
29943
- const pkg = JSON.parse(readFileSync28(joinPath(process.cwd(), "package.json"), "utf8"));
30315
+ const pkg = JSON.parse(readFileSync30(joinPath(process.cwd(), "package.json"), "utf8"));
29944
30316
  version = pkg.version;
29945
30317
  } catch {
29946
30318
  }
@@ -30741,15 +31113,15 @@ __export(installer_exports2, {
30741
31113
  verifyOpenCodeHooks: () => verifyOpenCodeHooks
30742
31114
  });
30743
31115
  import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
30744
- import { existsSync as existsSync31, readFileSync as readFileSync26 } from "fs";
30745
- import path45 from "path";
31116
+ import { existsSync as existsSync32, readFileSync as readFileSync28 } from "fs";
31117
+ import path46 from "path";
30746
31118
  import os19 from "os";
30747
31119
  async function registerOpenCodeMcp(packageRoot, homeDir = os19.homedir()) {
30748
- const configDir = path45.join(homeDir, ".config", "opencode");
30749
- const configPath = path45.join(configDir, "opencode.json");
31120
+ const configDir = path46.join(homeDir, ".config", "opencode");
31121
+ const configPath = path46.join(configDir, "opencode.json");
30750
31122
  await mkdir8(configDir, { recursive: true });
30751
31123
  let config = {};
30752
- if (existsSync31(configPath)) {
31124
+ if (existsSync32(configPath)) {
30753
31125
  try {
30754
31126
  config = JSON.parse(await readFile7(configPath, "utf-8"));
30755
31127
  } catch {
@@ -30761,7 +31133,7 @@ async function registerOpenCodeMcp(packageRoot, homeDir = os19.homedir()) {
30761
31133
  }
30762
31134
  const newEntry = {
30763
31135
  type: "local",
30764
- command: ["node", path45.join(packageRoot, "dist", "mcp", "server.js")],
31136
+ command: ["node", path46.join(packageRoot, "dist", "mcp", "server.js")],
30765
31137
  enabled: true
30766
31138
  };
30767
31139
  const current = config.mcp["exe-os"];
@@ -30776,14 +31148,14 @@ async function registerOpenCodeMcp(packageRoot, homeDir = os19.homedir()) {
30776
31148
  return true;
30777
31149
  }
30778
31150
  async function installOpenCodePlugin(packageRoot, homeDir = os19.homedir()) {
30779
- const pluginDir = path45.join(homeDir, ".config", "opencode", "plugins");
30780
- 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");
30781
31153
  await mkdir8(pluginDir, { recursive: true });
30782
31154
  const pluginContent = PLUGIN_TEMPLATE.replace(
30783
31155
  /__PACKAGE_ROOT__/g,
30784
31156
  packageRoot.replace(/\\/g, "\\\\")
30785
31157
  );
30786
- if (existsSync31(pluginPath)) {
31158
+ if (existsSync32(pluginPath)) {
30787
31159
  const existing = await readFile7(pluginPath, "utf-8");
30788
31160
  if (existing === pluginContent) {
30789
31161
  return false;
@@ -30793,16 +31165,16 @@ async function installOpenCodePlugin(packageRoot, homeDir = os19.homedir()) {
30793
31165
  return true;
30794
31166
  }
30795
31167
  function verifyOpenCodeHooks(homeDir = os19.homedir()) {
30796
- const configPath = path45.join(homeDir, ".config", "opencode", "opencode.json");
30797
- const pluginPath = path45.join(homeDir, ".config", "opencode", "plugins", "exe-os.mjs");
30798
- 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;
30799
31171
  try {
30800
- const config = JSON.parse(readFileSync26(configPath, "utf-8"));
31172
+ const config = JSON.parse(readFileSync28(configPath, "utf-8"));
30801
31173
  if (!config.mcp?.["exe-os"]?.enabled) return false;
30802
31174
  } catch {
30803
31175
  return false;
30804
31176
  }
30805
- if (!existsSync31(pluginPath)) return false;
31177
+ if (!existsSync32(pluginPath)) return false;
30806
31178
  return true;
30807
31179
  }
30808
31180
  async function runOpenCodeInstaller(homeDir) {
@@ -30837,19 +31209,19 @@ __export(installer_exports3, {
30837
31209
  verifyCodexHooks: () => verifyCodexHooks
30838
31210
  });
30839
31211
  import { readFile as readFile8, writeFile as writeFile9, mkdir as mkdir9 } from "fs/promises";
30840
- import { existsSync as existsSync32 } from "fs";
30841
- import path46 from "path";
31212
+ import { existsSync as existsSync33 } from "fs";
31213
+ import path47 from "path";
30842
31214
  import os20 from "os";
30843
31215
  async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30844
- const codexDir = path46.join(homeDir, ".codex");
30845
- const hooksPath = path46.join(codexDir, "hooks.json");
30846
- const logsDir = path46.join(homeDir, ".exe-os", "logs");
30847
- 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");
30848
31220
  const logSuffix = ` 2>> "${hookLogPath}"`;
30849
31221
  await mkdir9(codexDir, { recursive: true });
30850
31222
  await mkdir9(logsDir, { recursive: true });
30851
31223
  let hooksJson = {};
30852
- if (existsSync32(hooksPath)) {
31224
+ if (existsSync33(hooksPath)) {
30853
31225
  try {
30854
31226
  hooksJson = JSON.parse(await readFile8(hooksPath, "utf-8"));
30855
31227
  } catch {
@@ -30866,7 +31238,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30866
31238
  hooks: [
30867
31239
  {
30868
31240
  type: "command",
30869
- command: `node "${path46.join(packageRoot, "dist", "hooks", "session-start.js")}"${logSuffix}`,
31241
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "session-start.js")}"${logSuffix}`,
30870
31242
  timeout: 30
30871
31243
  }
30872
31244
  ]
@@ -30882,7 +31254,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30882
31254
  // Combined hook: runs ingest + error-recall in one Node process.
30883
31255
  // Eliminates a cold-start cycle per tool call (~3-6s savings on Codex).
30884
31256
  type: "command",
30885
- 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}`
30886
31258
  }
30887
31259
  ]
30888
31260
  },
@@ -30896,7 +31268,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30896
31268
  // Single hook: prompt-submit handles memory retrieval + entity boost.
30897
31269
  // exe-heartbeat-hook is CC-specific (intercom) — omitted on Codex.
30898
31270
  type: "command",
30899
- command: `node "${path46.join(packageRoot, "dist", "hooks", "prompt-submit.js")}"${logSuffix}`
31271
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "prompt-submit.js")}"${logSuffix}`
30900
31272
  }
30901
31273
  ]
30902
31274
  },
@@ -30908,7 +31280,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30908
31280
  hooks: [
30909
31281
  {
30910
31282
  type: "command",
30911
- command: `node "${path46.join(packageRoot, "dist", "hooks", "stop.js")}"${logSuffix}`
31283
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "stop.js")}"${logSuffix}`
30912
31284
  }
30913
31285
  ]
30914
31286
  },
@@ -30921,7 +31293,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30921
31293
  hooks: [
30922
31294
  {
30923
31295
  type: "command",
30924
- 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}`
30925
31297
  }
30926
31298
  ]
30927
31299
  },
@@ -30953,8 +31325,8 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30953
31325
  return { added, skipped };
30954
31326
  }
30955
31327
  function verifyCodexHooks(homeDir = os20.homedir()) {
30956
- const hooksPath = path46.join(homeDir, ".codex", "hooks.json");
30957
- if (!existsSync32(hooksPath)) return false;
31328
+ const hooksPath = path47.join(homeDir, ".codex", "hooks.json");
31329
+ if (!existsSync33(hooksPath)) return false;
30958
31330
  try {
30959
31331
  const hooksJson = JSON.parse(
30960
31332
  __require("fs").readFileSync(hooksPath, "utf-8")
@@ -30977,11 +31349,11 @@ function verifyCodexHooks(homeDir = os20.homedir()) {
30977
31349
  async function installCodexStatusLine(homeDir = os20.homedir()) {
30978
31350
  const prefs = loadPreferences(homeDir);
30979
31351
  if (prefs.codexStatusLine === false) return "opted-out";
30980
- const codexDir = path46.join(homeDir, ".codex");
30981
- const configPath = path46.join(codexDir, "config.toml");
31352
+ const codexDir = path47.join(homeDir, ".codex");
31353
+ const configPath = path47.join(codexDir, "config.toml");
30982
31354
  await mkdir9(codexDir, { recursive: true });
30983
31355
  let content = "";
30984
- if (existsSync32(configPath)) {
31356
+ if (existsSync33(configPath)) {
30985
31357
  content = await readFile8(configPath, "utf-8");
30986
31358
  if (/\[tui\][\s\S]*?status_line\s*=/.test(content)) {
30987
31359
  return "already-configured";
@@ -31039,12 +31411,12 @@ ${desired}
31039
31411
  return { content: next, changed };
31040
31412
  }
31041
31413
  async function registerCodexMcpServer(packageRoot, homeDir = os20.homedir()) {
31042
- const codexDir = path46.join(homeDir, ".codex");
31043
- const configPath = path46.join(codexDir, "config.toml");
31044
- 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");
31045
31417
  await mkdir9(codexDir, { recursive: true });
31046
31418
  let content = "";
31047
- if (existsSync32(configPath)) {
31419
+ if (existsSync33(configPath)) {
31048
31420
  content = await readFile8(configPath, "utf-8");
31049
31421
  }
31050
31422
  const sectionHeader = "[mcp_servers.exe-os]";
@@ -31069,10 +31441,10 @@ async function registerCodexMcpServer(packageRoot, homeDir = os20.homedir()) {
31069
31441
  return "registered";
31070
31442
  }
31071
31443
  async function ensureCodexHooksFeature(homeDir = os20.homedir()) {
31072
- const configPath = path46.join(homeDir, ".codex", "config.toml");
31073
- 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 });
31074
31446
  let content = "";
31075
- if (existsSync32(configPath)) {
31447
+ if (existsSync33(configPath)) {
31076
31448
  content = await readFile8(configPath, "utf-8");
31077
31449
  }
31078
31450
  if (/\[features\][\s\S]*?codex_hooks\s*=\s*true/.test(content)) {
@@ -31140,14 +31512,14 @@ var init_installer3 = __esm({
31140
31512
  });
31141
31513
 
31142
31514
  // src/bin/cli.ts
31143
- import { existsSync as existsSync33, readFileSync as readFileSync27, writeFileSync as writeFileSync20, readdirSync as readdirSync10, rmSync } from "fs";
31144
- 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";
31145
31517
  import os21 from "os";
31146
31518
  var args = process.argv.slice(2);
31147
31519
  if (args.includes("--version") || args.includes("-v")) {
31148
31520
  try {
31149
- const pkgPath = path47.join(path47.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
31150
- 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"));
31151
31523
  console.log(pkg.version);
31152
31524
  } catch {
31153
31525
  console.log("unknown");
@@ -31306,16 +31678,19 @@ ID: ${result.id}`);
31306
31678
  } else if (args[0] === "update") {
31307
31679
  const { runUpdate: runUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
31308
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();
31309
31684
  } else if (args.includes("--tui") || args.includes("--demo") || args[0] === "tui") {
31310
31685
  checkForUpdateOnBoot().catch(() => {
31311
31686
  });
31312
31687
  await init_App2().then(() => App_exports);
31313
31688
  } else {
31314
- const claudeDir = path47.join(os21.homedir(), ".claude");
31315
- const settingsPath = path47.join(claudeDir, "settings.json");
31316
- 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) && (() => {
31317
31692
  try {
31318
- const raw = readFileSync27(settingsPath, "utf8");
31693
+ const raw = readFileSync29(settingsPath, "utf8");
31319
31694
  return raw.includes("exe-os") || raw.includes("exe-mem");
31320
31695
  } catch {
31321
31696
  return false;
@@ -31325,9 +31700,9 @@ ID: ${result.id}`);
31325
31700
  const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
31326
31701
  let cooName = DEFAULT_COORDINATOR_TEMPLATE_NAME2;
31327
31702
  try {
31328
- const rosterPath = path47.join(os21.homedir(), ".exe-os", "exe-employees.json");
31329
- if (existsSync33(rosterPath)) {
31330
- 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"));
31331
31706
  const coo = roster.find((e) => e.role === "COO");
31332
31707
  if (coo) cooName = coo.name;
31333
31708
  }
@@ -31391,14 +31766,15 @@ async function runCodexInstall() {
31391
31766
  }
31392
31767
  }
31393
31768
  async function runClaudeCheck() {
31394
- const claudeDir = path47.join(os21.homedir(), ".claude");
31395
- const settingsPath = path47.join(claudeDir, "settings.json");
31396
- 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");
31397
31773
  let ok = true;
31398
- if (existsSync33(settingsPath)) {
31774
+ if (existsSync34(settingsPath)) {
31399
31775
  let settings;
31400
31776
  try {
31401
- settings = JSON.parse(readFileSync27(settingsPath, "utf8"));
31777
+ settings = JSON.parse(readFileSync29(settingsPath, "utf8"));
31402
31778
  } catch {
31403
31779
  console.log("\x1B[31m\u2717\x1B[0m settings.json is malformed (invalid JSON)");
31404
31780
  ok = false;
@@ -31424,10 +31800,10 @@ async function runClaudeCheck() {
31424
31800
  console.log("\x1B[31m\u2717\x1B[0m settings.json not found");
31425
31801
  ok = false;
31426
31802
  }
31427
- if (existsSync33(claudeJsonPath)) {
31803
+ if (existsSync34(claudeJsonPath)) {
31428
31804
  let claudeJson;
31429
31805
  try {
31430
- claudeJson = JSON.parse(readFileSync27(claudeJsonPath, "utf8"));
31806
+ claudeJson = JSON.parse(readFileSync29(claudeJsonPath, "utf8"));
31431
31807
  } catch {
31432
31808
  console.log("\x1B[31m\u2717\x1B[0m claude.json is malformed (invalid JSON)");
31433
31809
  ok = false;
@@ -31446,8 +31822,22 @@ async function runClaudeCheck() {
31446
31822
  console.log("\x1B[31m\u2717\x1B[0m claude.json not found");
31447
31823
  ok = false;
31448
31824
  }
31449
- const skillsDir = path47.join(claudeDir, "skills");
31450
- 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)) {
31451
31841
  console.log("\x1B[32m\u2713\x1B[0m Slash skills directory exists");
31452
31842
  } else {
31453
31843
  console.log("\x1B[31m\u2717\x1B[0m Slash skills directory missing");
@@ -31464,16 +31854,16 @@ async function runClaudeUninstall(flags = []) {
31464
31854
  const dryRun = flags.includes("--dry-run");
31465
31855
  const purge = flags.includes("--purge");
31466
31856
  const homeDir = os21.homedir();
31467
- const claudeDir = path47.join(homeDir, ".claude");
31468
- const settingsPath = path47.join(claudeDir, "settings.json");
31469
- const claudeJsonPath = path47.join(homeDir, ".claude.json");
31470
- 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");
31471
31861
  let removed = 0;
31472
31862
  const log = (msg) => console.log(dryRun ? `[dry-run] ${msg}` : msg);
31473
31863
  let settings = {};
31474
- if (existsSync33(settingsPath)) {
31864
+ if (existsSync34(settingsPath)) {
31475
31865
  try {
31476
- settings = JSON.parse(readFileSync27(settingsPath, "utf8"));
31866
+ settings = JSON.parse(readFileSync29(settingsPath, "utf8"));
31477
31867
  } catch {
31478
31868
  console.error("Your ~/.claude/settings.json appears malformed.");
31479
31869
  if (purge) {
@@ -31511,15 +31901,15 @@ async function runClaudeUninstall(flags = []) {
31511
31901
  permCount = before - settings.permissions.allow.length;
31512
31902
  }
31513
31903
  if (!dryRun) {
31514
- writeFileSync20(settingsPath, JSON.stringify(settings, null, 2) + "\n");
31904
+ writeFileSync21(settingsPath, JSON.stringify(settings, null, 2) + "\n");
31515
31905
  }
31516
31906
  log("\u2713 Removed exe-os hooks from settings.json");
31517
31907
  if (permCount > 0) log(`\u2713 Removed ${permCount} MCP permission entries`);
31518
31908
  removed++;
31519
31909
  }
31520
31910
  }
31521
- if (existsSync33(claudeJsonPath)) {
31522
- const raw = readFileSync27(claudeJsonPath, "utf8");
31911
+ if (existsSync34(claudeJsonPath)) {
31912
+ const raw = readFileSync29(claudeJsonPath, "utf8");
31523
31913
  if (raw.length > 1e6) {
31524
31914
  console.error("claude.json exceeds 1 MB \u2014 skipping parse.");
31525
31915
  } else {
@@ -31540,7 +31930,7 @@ async function runClaudeUninstall(flags = []) {
31540
31930
  }
31541
31931
  if (removedMcp) {
31542
31932
  if (!dryRun) {
31543
- writeFileSync20(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
31933
+ writeFileSync21(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
31544
31934
  }
31545
31935
  log("\u2713 Removed exe-os MCP server from claude.json");
31546
31936
  removed++;
@@ -31548,14 +31938,14 @@ async function runClaudeUninstall(flags = []) {
31548
31938
  }
31549
31939
  }
31550
31940
  }
31551
- const skillsDir = path47.join(claudeDir, "skills");
31552
- if (existsSync33(skillsDir)) {
31941
+ const skillsDir = path48.join(claudeDir, "skills");
31942
+ if (existsSync34(skillsDir)) {
31553
31943
  let skillCount = 0;
31554
31944
  try {
31555
31945
  const entries = readdirSync10(skillsDir);
31556
31946
  for (const entry of entries) {
31557
31947
  if (entry.startsWith("exe")) {
31558
- const fullPath = path47.join(skillsDir, entry);
31948
+ const fullPath = path48.join(skillsDir, entry);
31559
31949
  if (!dryRun) rmSync(fullPath, { recursive: true, force: true });
31560
31950
  skillCount++;
31561
31951
  }
@@ -31567,30 +31957,30 @@ async function runClaudeUninstall(flags = []) {
31567
31957
  removed++;
31568
31958
  }
31569
31959
  }
31570
- const claudeMdPath = path47.join(claudeDir, "CLAUDE.md");
31571
- if (existsSync33(claudeMdPath)) {
31572
- const content = readFileSync27(claudeMdPath, "utf8");
31960
+ const claudeMdPath = path48.join(claudeDir, "CLAUDE.md");
31961
+ if (existsSync34(claudeMdPath)) {
31962
+ const content = readFileSync29(claudeMdPath, "utf8");
31573
31963
  const startMarker = "<!-- exe-os:orchestration-start -->";
31574
31964
  const endMarker = "<!-- exe-os:orchestration-end -->";
31575
31965
  const startIdx = content.indexOf(startMarker);
31576
31966
  const endIdx = content.indexOf(endMarker);
31577
31967
  if (startIdx !== -1 && endIdx !== -1) {
31578
31968
  const cleaned = (content.slice(0, startIdx) + content.slice(endIdx + endMarker.length)).replace(/\n{3,}/g, "\n\n").trim() + "\n";
31579
- if (!dryRun) writeFileSync20(claudeMdPath, cleaned);
31969
+ if (!dryRun) writeFileSync21(claudeMdPath, cleaned);
31580
31970
  log("\u2713 Removed orchestration block from CLAUDE.md");
31581
31971
  removed++;
31582
31972
  }
31583
31973
  }
31584
- const agentsDir = path47.join(claudeDir, "agents");
31585
- if (existsSync33(agentsDir)) {
31974
+ const agentsDir = path48.join(claudeDir, "agents");
31975
+ if (existsSync34(agentsDir)) {
31586
31976
  let agentCount = 0;
31587
31977
  try {
31588
31978
  const entries = readdirSync10(agentsDir).filter((f) => f.endsWith(".md"));
31589
31979
  let knownNames = /* @__PURE__ */ new Set();
31590
- const rosterPath = path47.join(exeOsDir, "exe-employees.json");
31591
- if (existsSync33(rosterPath)) {
31980
+ const rosterPath = path48.join(exeOsDir, "exe-employees.json");
31981
+ if (existsSync34(rosterPath)) {
31592
31982
  try {
31593
- const roster = JSON.parse(readFileSync27(rosterPath, "utf8"));
31983
+ const roster = JSON.parse(readFileSync29(rosterPath, "utf8"));
31594
31984
  knownNames = new Set(roster.map((e) => e.name));
31595
31985
  } catch {
31596
31986
  }
@@ -31598,7 +31988,7 @@ async function runClaudeUninstall(flags = []) {
31598
31988
  for (const entry of entries) {
31599
31989
  const name = entry.replace(/\.md$/, "");
31600
31990
  if (knownNames.has(name)) {
31601
- if (!dryRun) rmSync(path47.join(agentsDir, entry), { force: true });
31991
+ if (!dryRun) rmSync(path48.join(agentsDir, entry), { force: true });
31602
31992
  agentCount++;
31603
31993
  }
31604
31994
  }
@@ -31609,16 +31999,16 @@ async function runClaudeUninstall(flags = []) {
31609
31999
  removed++;
31610
32000
  }
31611
32001
  }
31612
- const projectsDir = path47.join(claudeDir, "projects");
31613
- if (existsSync33(projectsDir)) {
32002
+ const projectsDir = path48.join(claudeDir, "projects");
32003
+ if (existsSync34(projectsDir)) {
31614
32004
  let projectCount = 0;
31615
32005
  try {
31616
32006
  const projects = readdirSync10(projectsDir);
31617
32007
  for (const proj of projects) {
31618
- const projSettings = path47.join(projectsDir, proj, "settings.json");
31619
- if (!existsSync33(projSettings)) continue;
32008
+ const projSettings = path48.join(projectsDir, proj, "settings.json");
32009
+ if (!existsSync34(projSettings)) continue;
31620
32010
  try {
31621
- const pSettings = JSON.parse(readFileSync27(projSettings, "utf8"));
32011
+ const pSettings = JSON.parse(readFileSync29(projSettings, "utf8"));
31622
32012
  let changed = false;
31623
32013
  if (Array.isArray(pSettings.permissions?.allow)) {
31624
32014
  const before = pSettings.permissions.allow.length;
@@ -31628,7 +32018,7 @@ async function runClaudeUninstall(flags = []) {
31628
32018
  if (pSettings.permissions.allow.length < before) changed = true;
31629
32019
  }
31630
32020
  if (changed && !dryRun) {
31631
- writeFileSync20(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
32021
+ writeFileSync21(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
31632
32022
  }
31633
32023
  if (changed) projectCount++;
31634
32024
  } catch {
@@ -31652,18 +32042,18 @@ async function runClaudeUninstall(flags = []) {
31652
32042
  };
31653
32043
  const exeBinPath = findExeBin3();
31654
32044
  if (!exeBinPath) throw new Error("exe-os not found in PATH");
31655
- const binDir = path47.dirname(exeBinPath);
32045
+ const binDir = path48.dirname(exeBinPath);
31656
32046
  let symlinkCount = 0;
31657
- const rosterPath = path47.join(exeOsDir, "exe-employees.json");
31658
- if (existsSync33(rosterPath)) {
31659
- 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"));
31660
32050
  const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
31661
32051
  const coordinatorName = roster.find((e) => e.role?.toLowerCase() === "coo")?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME2;
31662
32052
  for (const emp of roster) {
31663
32053
  if (emp.name === coordinatorName) continue;
31664
32054
  for (const suffix of ["", "-opencode"]) {
31665
- const linkPath = path47.join(binDir, `${emp.name}${suffix}`);
31666
- if (existsSync33(linkPath)) {
32055
+ const linkPath = path48.join(binDir, `${emp.name}${suffix}`);
32056
+ if (existsSync34(linkPath)) {
31667
32057
  if (!dryRun) rmSync(linkPath, { force: true });
31668
32058
  symlinkCount++;
31669
32059
  }
@@ -31676,7 +32066,7 @@ async function runClaudeUninstall(flags = []) {
31676
32066
  }
31677
32067
  } catch {
31678
32068
  }
31679
- if (purge && existsSync33(exeOsDir)) {
32069
+ if (purge && existsSync34(exeOsDir)) {
31680
32070
  if (!dryRun) {
31681
32071
  process.stdout.write("\x1B[33m\u26A0 This will delete all memories, identities, and agent data.\x1B[0m\n");
31682
32072
  process.stdout.write(" Removing ~/.exe-os...\n");
@@ -31701,7 +32091,7 @@ async function checkForUpdateOnBoot() {
31701
32091
  const config = await loadConfig2();
31702
32092
  if (!config.autoUpdate.checkOnBoot) return;
31703
32093
  const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
31704
- const packageRoot = path47.resolve(
32094
+ const packageRoot = path48.resolve(
31705
32095
  new URL("../..", import.meta.url).pathname
31706
32096
  );
31707
32097
  const result = checkForUpdate2(packageRoot);
@@ -31761,7 +32151,7 @@ async function runActivate(key) {
31761
32151
  const idTemplate = getIdentityTemplate(identityKey);
31762
32152
  if (idTemplate) {
31763
32153
  const idPath = identityPath2(name);
31764
- const dir = path47.dirname(idPath);
32154
+ const dir = path48.dirname(idPath);
31765
32155
  if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
31766
32156
  fs8.writeFileSync(idPath, idTemplate.replace(/^agent_id: \w+/m, `agent_id: ${name}`), "utf-8");
31767
32157
  }