@askexenow/exe-os 0.9.37 → 0.9.39

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 (73) hide show
  1. package/deploy/stack-manifests/v0.9.json +55 -0
  2. package/dist/bin/backfill-conversations.js +36 -9
  3. package/dist/bin/backfill-responses.js +36 -9
  4. package/dist/bin/backfill-vectors.js +36 -9
  5. package/dist/bin/cleanup-stale-review-tasks.js +37 -10
  6. package/dist/bin/cli.js +624 -204
  7. package/dist/bin/exe-agent.js +13 -5
  8. package/dist/bin/exe-assign.js +36 -9
  9. package/dist/bin/exe-boot.js +50 -20
  10. package/dist/bin/exe-call.js +134 -342
  11. package/dist/bin/exe-dispatch.js +36 -9
  12. package/dist/bin/exe-doctor.js +39 -12
  13. package/dist/bin/exe-export-behaviors.js +38 -11
  14. package/dist/bin/exe-forget.js +36 -9
  15. package/dist/bin/exe-gateway.js +64 -15
  16. package/dist/bin/exe-heartbeat.js +37 -10
  17. package/dist/bin/exe-kill.js +36 -9
  18. package/dist/bin/exe-launch-agent.js +287 -1081
  19. package/dist/bin/exe-new-employee.js +100 -14
  20. package/dist/bin/exe-pending-messages.js +36 -9
  21. package/dist/bin/exe-pending-notifications.js +36 -9
  22. package/dist/bin/exe-pending-reviews.js +36 -9
  23. package/dist/bin/exe-rename.js +1780 -204
  24. package/dist/bin/exe-review.js +36 -9
  25. package/dist/bin/exe-search.js +38 -11
  26. package/dist/bin/exe-session-cleanup.js +38 -11
  27. package/dist/bin/exe-start-codex.js +38 -11
  28. package/dist/bin/exe-start-opencode.js +38 -11
  29. package/dist/bin/exe-status.js +37 -10
  30. package/dist/bin/exe-team.js +36 -9
  31. package/dist/bin/git-sweep.js +36 -9
  32. package/dist/bin/graph-backfill.js +36 -9
  33. package/dist/bin/graph-export.js +36 -9
  34. package/dist/bin/install.js +70 -3
  35. package/dist/bin/intercom-check.js +38 -11
  36. package/dist/bin/scan-tasks.js +36 -9
  37. package/dist/bin/setup.js +20 -19
  38. package/dist/bin/shard-migrate.js +36 -9
  39. package/dist/bin/stack-update.js +308 -0
  40. package/dist/gateway/index.js +62 -13
  41. package/dist/hooks/bug-report-worker.js +40 -12
  42. package/dist/hooks/codex-stop-task-finalizer.js +38 -11
  43. package/dist/hooks/commit-complete.js +36 -9
  44. package/dist/hooks/error-recall.js +38 -11
  45. package/dist/hooks/ingest.js +38 -10
  46. package/dist/hooks/instructions-loaded.js +44 -12
  47. package/dist/hooks/notification.js +36 -9
  48. package/dist/hooks/post-compact.js +36 -9
  49. package/dist/hooks/post-tool-combined.js +39 -12
  50. package/dist/hooks/pre-compact.js +37 -10
  51. package/dist/hooks/pre-tool-use.js +38 -10
  52. package/dist/hooks/prompt-submit.js +43 -15
  53. package/dist/hooks/session-end.js +37 -10
  54. package/dist/hooks/session-start.js +49 -16
  55. package/dist/hooks/stop.js +37 -10
  56. package/dist/hooks/subagent-stop.js +36 -9
  57. package/dist/hooks/summary-worker.js +45 -18
  58. package/dist/index.js +60 -11
  59. package/dist/lib/consolidation.js +2 -1
  60. package/dist/lib/employee-templates.js +4 -3
  61. package/dist/lib/employees.js +2 -1
  62. package/dist/lib/exe-daemon.js +11229 -10537
  63. package/dist/lib/hybrid-search.js +38 -11
  64. package/dist/lib/identity.js +8 -3
  65. package/dist/lib/schedules.js +36 -9
  66. package/dist/lib/store.js +36 -9
  67. package/dist/mcp/server.js +6873 -6249
  68. package/dist/mcp/tools/create-task.js +10 -4
  69. package/dist/runtime/index.js +36 -9
  70. package/dist/tui/App.js +42 -13
  71. package/package.json +4 -1
  72. package/stack.release.json +31 -0
  73. package/stack.release.schema.json +31 -0
package/dist/bin/cli.js CHANGED
@@ -540,7 +540,8 @@ function isMultiInstance(agentName, employees) {
540
540
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
541
541
  }
542
542
  function addEmployee(employees, employee) {
543
- const normalized = { ...employee, name: employee.name.toLowerCase() };
543
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
544
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
544
545
  if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
545
546
  throw new Error(`Employee '${normalized.name}' already exists`);
546
547
  }
@@ -814,6 +815,7 @@ var installer_exports = {};
814
815
  __export(installer_exports, {
815
816
  cleanOldShellFunctions: () => cleanOldShellFunctions,
816
817
  copySlashCommands: () => copySlashCommands,
818
+ detectMcpNameCollisions: () => detectMcpNameCollisions,
817
819
  installStatusLine: () => installStatusLine,
818
820
  mergeHooks: () => mergeHooks,
819
821
  registerMcpServer: () => registerMcpServer,
@@ -899,6 +901,60 @@ name: ${skillName}
899
901
  await writeFile3(destPath, content);
900
902
  return true;
901
903
  }
904
+ function readJsonFile(filePath) {
905
+ try {
906
+ return JSON.parse(readFileSync5(filePath, "utf-8"));
907
+ } catch {
908
+ return null;
909
+ }
910
+ }
911
+ function findAncestorMcpJsons(startDir, homeDir) {
912
+ const files = [];
913
+ let dir = path6.resolve(startDir);
914
+ const root = path6.parse(dir).root;
915
+ const stop = path6.resolve(homeDir);
916
+ while (dir !== root) {
917
+ const candidate = path6.join(dir, ".mcp.json");
918
+ if (existsSync7(candidate)) files.push(candidate);
919
+ if (dir === stop) break;
920
+ dir = path6.dirname(dir);
921
+ }
922
+ return files;
923
+ }
924
+ function pathApplies(projectPath, cwd2) {
925
+ const project = path6.resolve(projectPath);
926
+ const current = path6.resolve(cwd2);
927
+ return current === project || current.startsWith(project + path6.sep);
928
+ }
929
+ function detectMcpNameCollisions(homeDir = os5.homedir(), cwd2 = process.cwd()) {
930
+ const claudeJsonPath = path6.join(homeDir, ".claude.json");
931
+ if (!existsSync7(claudeJsonPath)) return [];
932
+ const claudeJson = readJsonFile(claudeJsonPath);
933
+ if (!claudeJson?.projects) return [];
934
+ const collisions = [];
935
+ const mcpJsons = findAncestorMcpJsons(cwd2, homeDir);
936
+ if (mcpJsons.length === 0) return [];
937
+ for (const [projectPath, projectConfig] of Object.entries(claudeJson.projects)) {
938
+ if (!pathApplies(projectPath, cwd2)) continue;
939
+ const projectServerNames = new Set(Object.keys(projectConfig.mcpServers ?? {}));
940
+ if (projectServerNames.size === 0) continue;
941
+ const enabled = new Set(projectConfig.enabledMcpjsonServers ?? []);
942
+ for (const mcpJsonPath of mcpJsons) {
943
+ const mcpJson = readJsonFile(mcpJsonPath);
944
+ const mcpServerNames = Object.keys(mcpJson?.mcpServers ?? {});
945
+ for (const serverName of mcpServerNames) {
946
+ if (!projectServerNames.has(serverName)) continue;
947
+ collisions.push({
948
+ mcpJsonPath,
949
+ projectPath,
950
+ serverName,
951
+ disabledInMcpJson: !enabled.has(serverName)
952
+ });
953
+ }
954
+ }
955
+ }
956
+ return collisions;
957
+ }
902
958
  async function registerMcpServer(packageRoot, homeDir = os5.homedir()) {
903
959
  const claudeJsonPath = path6.join(homeDir, ".claude.json");
904
960
  let claudeJson = {};
@@ -927,12 +983,26 @@ async function registerMcpServer(packageRoot, homeDir = os5.homedir()) {
927
983
  if (osMatches) {
928
984
  await cleanSettingsJsonMcp(path6.join(homeDir, ".claude", "settings.json"));
929
985
  await migratePermissionsToExeOs(path6.join(homeDir, ".claude", "settings.json"));
986
+ const collisions2 = detectMcpNameCollisions(homeDir, packageRoot).filter((c) => c.serverName === MCP_PRIMARY_KEY || c.serverName === MCP_LEGACY_KEY);
987
+ for (const c of collisions2) {
988
+ process.stderr.write(
989
+ `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.
990
+ `
991
+ );
992
+ }
930
993
  return false;
931
994
  }
932
995
  claudeJson.mcpServers[MCP_PRIMARY_KEY] = newEntry;
933
996
  await writeFile3(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
934
997
  await cleanSettingsJsonMcp(path6.join(homeDir, ".claude", "settings.json"));
935
998
  await migratePermissionsToExeOs(path6.join(homeDir, ".claude", "settings.json"));
999
+ const collisions = detectMcpNameCollisions(homeDir, packageRoot).filter((c) => c.serverName === MCP_PRIMARY_KEY || c.serverName === MCP_LEGACY_KEY);
1000
+ for (const c of collisions) {
1001
+ process.stderr.write(
1002
+ `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.
1003
+ `
1004
+ );
1005
+ }
936
1006
  return true;
937
1007
  }
938
1008
  async function cleanSettingsJsonMcp(settingsPath) {
@@ -1234,8 +1304,6 @@ async function mergeHooks(packageRoot, homeDir = os5.homedir()) {
1234
1304
  "purge_document",
1235
1305
  "rerank_documents",
1236
1306
  "set_document_importance",
1237
- "create_wiki_page",
1238
- "update_wiki_page",
1239
1307
  "get_wiki_page",
1240
1308
  "list_wiki_pages",
1241
1309
  // System
@@ -1773,8 +1841,8 @@ function deriveMachineKey() {
1773
1841
  }
1774
1842
  function readMachineId() {
1775
1843
  try {
1776
- const { readFileSync: readFileSync28 } = __require("fs");
1777
- return readFileSync28("/etc/machine-id", "utf-8").trim();
1844
+ const { readFileSync: readFileSync30 } = __require("fs");
1845
+ return readFileSync30("/etc/machine-id", "utf-8").trim();
1778
1846
  } catch {
1779
1847
  return "";
1780
1848
  }
@@ -4797,8 +4865,8 @@ async function validateLicense(apiKey, deviceId) {
4797
4865
  }
4798
4866
  function getCacheAgeMs() {
4799
4867
  try {
4800
- const { statSync: statSync4 } = __require("fs");
4801
- const s = statSync4(CACHE_PATH);
4868
+ const { statSync: statSync5 } = __require("fs");
4869
+ const s = statSync5(CACHE_PATH);
4802
4870
  return Date.now() - s.mtimeMs;
4803
4871
  } catch {
4804
4872
  return Infinity;
@@ -7035,7 +7103,7 @@ __export(shard_manager_exports, {
7035
7103
  shardExists: () => shardExists
7036
7104
  });
7037
7105
  import path15 from "path";
7038
- import { existsSync as existsSync15, mkdirSync as mkdirSync8, readdirSync as readdirSync3 } from "fs";
7106
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, readdirSync as readdirSync3, renameSync as renameSync3, statSync as statSync4 } from "fs";
7039
7107
  import { createClient as createClient2 } from "@libsql/client";
7040
7108
  function initShardManager(encryptionKey) {
7041
7109
  _encryptionKey = encryptionKey;
@@ -7057,7 +7125,7 @@ function getShardClient(projectName) {
7057
7125
  if (!_encryptionKey) {
7058
7126
  throw new Error("Shard manager not initialized. Call initShardManager() first.");
7059
7127
  }
7060
- const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
7128
+ const safeName = safeShardName(projectName);
7061
7129
  if (!safeName || safeName === "unknown") {
7062
7130
  throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
7063
7131
  }
@@ -7079,9 +7147,12 @@ function getShardClient(projectName) {
7079
7147
  return client;
7080
7148
  }
7081
7149
  function shardExists(projectName) {
7082
- const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
7150
+ const safeName = safeShardName(projectName);
7083
7151
  return existsSync15(path15.join(SHARDS_DIR, `${safeName}.db`));
7084
7152
  }
7153
+ function safeShardName(projectName) {
7154
+ return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
7155
+ }
7085
7156
  function listShards() {
7086
7157
  if (!existsSync15(SHARDS_DIR)) return [];
7087
7158
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
@@ -7175,7 +7246,8 @@ async function ensureShardSchema(client) {
7175
7246
  "ALTER TABLE memories ADD COLUMN token_cost REAL",
7176
7247
  "ALTER TABLE memories ADD COLUMN audience TEXT",
7177
7248
  "ALTER TABLE memories ADD COLUMN language_type TEXT",
7178
- "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
7249
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT",
7250
+ "ALTER TABLE memories ADD COLUMN deleted_at TEXT"
7179
7251
  ]) {
7180
7252
  try {
7181
7253
  await client.execute(col);
@@ -7271,9 +7343,32 @@ async function ensureShardSchema(client) {
7271
7343
  }
7272
7344
  }
7273
7345
  async function getReadyShardClient(projectName) {
7274
- const client = getShardClient(projectName);
7275
- await ensureShardSchema(client);
7276
- return client;
7346
+ const safeName = safeShardName(projectName);
7347
+ let client = getShardClient(projectName);
7348
+ try {
7349
+ await ensureShardSchema(client);
7350
+ return client;
7351
+ } catch (err) {
7352
+ const message = err instanceof Error ? err.message : String(err);
7353
+ if (!/SQLITE_NOTADB|file is not a database/i.test(message)) throw err;
7354
+ client.close();
7355
+ _shards.delete(safeName);
7356
+ _shardLastAccess.delete(safeName);
7357
+ const dbPath = path15.join(SHARDS_DIR, `${safeName}.db`);
7358
+ if (existsSync15(dbPath)) {
7359
+ const stat2 = statSync4(dbPath);
7360
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7361
+ const archivedPath = path15.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
7362
+ renameSync3(dbPath, archivedPath);
7363
+ process.stderr.write(
7364
+ `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat2.size} bytes, mtime ${stat2.mtime.toISOString()})
7365
+ `
7366
+ );
7367
+ }
7368
+ client = getShardClient(projectName);
7369
+ await ensureShardSchema(client);
7370
+ return client;
7371
+ }
7277
7372
  }
7278
7373
  function evictLRU() {
7279
7374
  let oldest = null;
@@ -7494,7 +7589,7 @@ var init_platform_procedures = __esm({
7494
7589
  title: "MCP tools \u2014 wiki, documents, and content",
7495
7590
  domain: "tool-use",
7496
7591
  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."
7592
+ 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
7593
  },
7499
7594
  {
7500
7595
  title: "MCP tools \u2014 system, operations, and admin",
@@ -7512,7 +7607,7 @@ var init_platform_procedures = __esm({
7512
7607
  title: "MCP tools \u2014 advanced (triggers, skills, orchestration)",
7513
7608
  domain: "tool-use",
7514
7609
  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."
7610
+ 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
7611
  }
7517
7612
  ];
7518
7613
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -8994,9 +9089,9 @@ Unclassified: ${unclassified}
8994
9089
  }
8995
9090
  async function exportBatches(options) {
8996
9091
  const fs8 = await import("fs");
8997
- const path48 = await import("path");
9092
+ const path49 = await import("path");
8998
9093
  const client = getClient();
8999
- const outDir = path48.join(process.cwd(), "exe/output/classifications/input");
9094
+ const outDir = path49.join(process.cwd(), "exe/output/classifications/input");
9000
9095
  fs8.mkdirSync(outDir, { recursive: true });
9001
9096
  const countResult = await client.execute({
9002
9097
  sql: "SELECT COUNT(*) as cnt FROM memories WHERE intent IS NULL AND outcome IS NULL AND domain IS NULL",
@@ -9020,7 +9115,7 @@ async function exportBatches(options) {
9020
9115
  const text = String(row.text || "").replace(/\n/g, " ");
9021
9116
  return JSON.stringify({ id: row.id, text });
9022
9117
  });
9023
- const batchFile = path48.join(outDir, `batch-${String(batchNum).padStart(4, "0")}.jsonl`);
9118
+ const batchFile = path49.join(outDir, `batch-${String(batchNum).padStart(4, "0")}.jsonl`);
9024
9119
  fs8.writeFileSync(batchFile, lines.join("\n") + "\n");
9025
9120
  exported += batch.rows.length;
9026
9121
  offset += options.batchSize;
@@ -9036,7 +9131,7 @@ async function exportBatches(options) {
9036
9131
  }
9037
9132
  async function importClassifications(importDir) {
9038
9133
  const fs8 = await import("fs");
9039
- const path48 = await import("path");
9134
+ const path49 = await import("path");
9040
9135
  const client = getClient();
9041
9136
  const files = fs8.readdirSync(importDir).filter((f) => f.endsWith(".jsonl")).sort();
9042
9137
  process.stderr.write(`[backfill-metadata] Found ${files.length} JSONL files to import from ${importDir}
@@ -9044,7 +9139,7 @@ async function importClassifications(importDir) {
9044
9139
  let imported = 0;
9045
9140
  let invalid = 0;
9046
9141
  for (const file of files) {
9047
- const lines = fs8.readFileSync(path48.join(importDir, file), "utf-8").split("\n").filter(Boolean);
9142
+ const lines = fs8.readFileSync(path49.join(importDir, file), "utf-8").split("\n").filter(Boolean);
9048
9143
  for (const line of lines) {
9049
9144
  try {
9050
9145
  const rec = JSON.parse(line);
@@ -9197,6 +9292,9 @@ function ensureDir() {
9197
9292
  function identityPath(agentId) {
9198
9293
  return path17.join(IDENTITY_DIR2, `${agentId}.md`);
9199
9294
  }
9295
+ function sanitizeIdentityBody(body) {
9296
+ return body.replace(/<!--[\s\S]*?-->/g, "").trim();
9297
+ }
9200
9298
  function parseFrontmatter(raw) {
9201
9299
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
9202
9300
  if (!match) {
@@ -9209,11 +9307,11 @@ function parseFrontmatter(raw) {
9209
9307
  created_by: "system",
9210
9308
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
9211
9309
  },
9212
- body: raw
9310
+ body: sanitizeIdentityBody(raw)
9213
9311
  };
9214
9312
  }
9215
9313
  const yamlStr = match[1];
9216
- const body = match[2].trim();
9314
+ const body = sanitizeIdentityBody(match[2]);
9217
9315
  const fm = {};
9218
9316
  for (const line of yamlStr.split("\n")) {
9219
9317
  const kv = line.match(/^(\w+):\s*(.+)$/);
@@ -9278,7 +9376,9 @@ function listIdentities() {
9278
9376
  const summary = lines[0]?.trim().slice(0, 120) ?? identity.frontmatter.title;
9279
9377
  results.push({
9280
9378
  agentId,
9281
- title: `${identity.frontmatter.title} (${identity.frontmatter.role.toUpperCase()})`,
9379
+ // User-facing/team-facing title only. `frontmatter.role` is internal
9380
+ // routing metadata and must not leak as an external title.
9381
+ title: identity.frontmatter.title,
9282
9382
  summary
9283
9383
  });
9284
9384
  }
@@ -10109,7 +10209,7 @@ __export(intercom_queue_exports, {
10109
10209
  queueIntercom: () => queueIntercom,
10110
10210
  readQueue: () => readQueue
10111
10211
  });
10112
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync13, renameSync as renameSync3, existsSync as existsSync19, mkdirSync as mkdirSync13 } from "fs";
10212
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync13, renameSync as renameSync4, existsSync as existsSync19, mkdirSync as mkdirSync13 } from "fs";
10113
10213
  import path21 from "path";
10114
10214
  import os12 from "os";
10115
10215
  function ensureDir2() {
@@ -10128,7 +10228,7 @@ function writeQueue(queue) {
10128
10228
  ensureDir2();
10129
10229
  const tmp = `${QUEUE_PATH}.tmp`;
10130
10230
  writeFileSync13(tmp, JSON.stringify(queue, null, 2));
10131
- renameSync3(tmp, QUEUE_PATH);
10231
+ renameSync4(tmp, QUEUE_PATH);
10132
10232
  }
10133
10233
  function queueIntercom(targetSession, reason) {
10134
10234
  const queue = readQueue();
@@ -11677,10 +11777,10 @@ async function disposeEmbedder() {
11677
11777
  async function embedDirect(text) {
11678
11778
  const llamaCpp = await import("node-llama-cpp");
11679
11779
  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)) {
11780
+ const { existsSync: existsSync35 } = await import("fs");
11781
+ const path49 = await import("path");
11782
+ const modelPath = path49.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
11783
+ if (!existsSync35(modelPath)) {
11684
11784
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
11685
11785
  }
11686
11786
  const llama = await llamaCpp.getLlama();
@@ -13808,7 +13908,8 @@ __export(employee_templates_exports, {
13808
13908
  });
13809
13909
  function getSessionPrompt(storedPrompt) {
13810
13910
  const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
13811
- const rolePrompt = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
13911
+ const withoutProcedures = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
13912
+ const rolePrompt = withoutProcedures.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").replace(/<!--[\s\S]*?-->/g, "").trimStart();
13812
13913
  const globalBlock = getGlobalProceduresBlock();
13813
13914
  return `${globalBlock}${rolePrompt}
13814
13915
  ${BASE_OPERATING_PROCEDURES}`;
@@ -14416,7 +14517,7 @@ __export(exe_rename_exports, {
14416
14517
  main: () => main2,
14417
14518
  renameEmployee: () => renameEmployee
14418
14519
  });
14419
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync17, renameSync as renameSync4, unlinkSync as unlinkSync11, existsSync as existsSync25 } from "fs";
14520
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync17, renameSync as renameSync5, unlinkSync as unlinkSync11, existsSync as existsSync25 } from "fs";
14420
14521
  import { execSync as execSync11 } from "child_process";
14421
14522
  import path31 from "path";
14422
14523
  import { homedir as homedir4 } from "os";
@@ -14445,7 +14546,8 @@ async function renameEmployee(oldName, newName, opts = {}) {
14445
14546
  const originalName = employee.name;
14446
14547
  const originalPrompt = employee.systemPrompt;
14447
14548
  employee.name = newName;
14448
- employee.systemPrompt = personalizePrompt(originalPrompt, rosterOldName, newName);
14549
+ if (originalPrompt) employee.systemPrompt = personalizePrompt(originalPrompt, rosterOldName, newName);
14550
+ else delete employee.systemPrompt;
14449
14551
  await saveEmployees(employees, rosterPath);
14450
14552
  rollbackStack.push({
14451
14553
  description: "restore roster",
@@ -14463,14 +14565,14 @@ async function renameEmployee(oldName, newName, opts = {}) {
14463
14565
  /^(agent_id:\s*)\S+/m,
14464
14566
  `$1${newName}`
14465
14567
  );
14466
- renameSync4(oldIdentityPath, newIdentityPath);
14568
+ renameSync5(oldIdentityPath, newIdentityPath);
14467
14569
  writeFileSync17(newIdentityPath, updatedContent, "utf-8");
14468
14570
  rollbackStack.push({
14469
14571
  description: "restore identity file",
14470
14572
  undo: () => {
14471
14573
  if (existsSync25(newIdentityPath)) {
14472
14574
  writeFileSync17(newIdentityPath, content, "utf-8");
14473
- renameSync4(newIdentityPath, oldIdentityPath);
14575
+ renameSync5(newIdentityPath, oldIdentityPath);
14474
14576
  }
14475
14577
  }
14476
14578
  });
@@ -14479,12 +14581,12 @@ async function renameEmployee(oldName, newName, opts = {}) {
14479
14581
  const newAgentPath = path31.join(agentsDir, `${newName}.md`);
14480
14582
  if (existsSync25(oldAgentPath)) {
14481
14583
  const agentContent = readFileSync21(oldAgentPath, "utf-8");
14482
- renameSync4(oldAgentPath, newAgentPath);
14584
+ renameSync5(oldAgentPath, newAgentPath);
14483
14585
  rollbackStack.push({
14484
14586
  description: "restore agent file",
14485
14587
  undo: () => {
14486
14588
  if (existsSync25(newAgentPath)) {
14487
- renameSync4(newAgentPath, oldAgentPath);
14589
+ renameSync5(newAgentPath, oldAgentPath);
14488
14590
  writeFileSync17(oldAgentPath, agentContent, "utf-8");
14489
14591
  }
14490
14592
  }
@@ -14583,6 +14685,8 @@ async function main2() {
14583
14685
  process.exit(1);
14584
14686
  }
14585
14687
  const [oldName, newName] = args2;
14688
+ const { initStore: initStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
14689
+ await initStore2({ lightweight: true });
14586
14690
  const result = await renameEmployee(oldName, newName);
14587
14691
  if (!result.success) {
14588
14692
  console.error(`Error: ${result.error}`);
@@ -14611,7 +14715,7 @@ var init_exe_rename = __esm({
14611
14715
  });
14612
14716
 
14613
14717
  // src/lib/model-downloader.ts
14614
- import { createWriteStream, createReadStream as createReadStream2, existsSync as existsSync26, unlinkSync as unlinkSync12, renameSync as renameSync5 } from "fs";
14718
+ import { createWriteStream, createReadStream as createReadStream2, existsSync as existsSync26, unlinkSync as unlinkSync12, renameSync as renameSync6 } from "fs";
14615
14719
  import { mkdir as mkdir6 } from "fs/promises";
14616
14720
  import { createHash as createHash4 } from "crypto";
14617
14721
  import path32 from "path";
@@ -14669,7 +14773,7 @@ async function downloadModel(opts) {
14669
14773
  `SHA256 mismatch: expected ${EXPECTED_SHA256}, got ${actualHash}`
14670
14774
  );
14671
14775
  }
14672
- renameSync5(tmpPath, destPath);
14776
+ renameSync6(tmpPath, destPath);
14673
14777
  return destPath;
14674
14778
  } catch (err) {
14675
14779
  lastErr = err instanceof Error ? err : new Error(String(err));
@@ -15473,8 +15577,8 @@ async function validateModel(log) {
15473
15577
  log("Skipping in-memory model validation (low memory \u2014 will validate on first use).");
15474
15578
  const modelPath = path34.join(MODELS_DIR, LOCAL_FILENAME);
15475
15579
  if (existsSync28(modelPath)) {
15476
- const { statSync: statSync4 } = await import("fs");
15477
- const size = statSync4(modelPath).size;
15580
+ const { statSync: statSync5 } = await import("fs");
15581
+ const size = statSync5(modelPath).size;
15478
15582
  if (size > 300 * 1e6) {
15479
15583
  log(`Model file verified (${(size / 1e6).toFixed(0)} MB).`);
15480
15584
  return;
@@ -15895,15 +15999,9 @@ async function runSetupWizard(opts = {}) {
15895
15999
  cooName = (cooNameInput || DEFAULT_COORDINATOR_TEMPLATE_NAME2).toLowerCase();
15896
16000
  let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
15897
16001
  if (!employees.some((e) => e.name === cooName)) {
15898
- const { DEFAULT_EXE: DEFAULT_EXE2, personalizePrompt: personalizePrompt2 } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
15899
16002
  const cooEmployee = {
15900
16003
  name: cooName,
15901
16004
  role: "COO",
15902
- systemPrompt: personalizePrompt2(
15903
- DEFAULT_EXE2.systemPrompt,
15904
- DEFAULT_COORDINATOR_TEMPLATE_NAME2,
15905
- cooName
15906
- ),
15907
16005
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
15908
16006
  templateName: DEFAULT_COORDINATOR_TEMPLATE_NAME2,
15909
16007
  templateVersion: 1
@@ -15997,12 +16095,12 @@ async function runSetupWizard(opts = {}) {
15997
16095
  const ctoNameInput = await ask2(rl, `Name your CTO (default: ${ctoDefault}): `);
15998
16096
  const ctoName = (ctoNameInput || ctoDefault).toLowerCase();
15999
16097
  if (!employees.some((e) => e.name === ctoName)) {
16000
- const { personalizePrompt: personalizeCto } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
16001
16098
  const ctoEmployee = {
16002
16099
  name: ctoName,
16003
16100
  role: "CTO",
16004
- systemPrompt: personalizeCto(ctoTemplate?.systemPrompt ?? "", ctoDefault, ctoName),
16005
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
16101
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
16102
+ templateName: ctoDefault,
16103
+ templateVersion: 1
16006
16104
  };
16007
16105
  employees = addEmployee2(employees, ctoEmployee);
16008
16106
  await saveEmployees2(employees, EMPLOYEES_PATH2);
@@ -16020,12 +16118,12 @@ async function runSetupWizard(opts = {}) {
16020
16118
  const cmoNameInput = await ask2(rl, `Name your CMO (default: ${cmoDefault}): `);
16021
16119
  const cmoName = (cmoNameInput || cmoDefault).toLowerCase();
16022
16120
  if (!employees.some((e) => e.name === cmoName)) {
16023
- const { personalizePrompt: personalizeCmo } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
16024
16121
  const cmoEmployee = {
16025
16122
  name: cmoName,
16026
16123
  role: "CMO",
16027
- systemPrompt: personalizeCmo(cmoTemplate?.systemPrompt ?? "", cmoDefault, cmoName),
16028
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
16124
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
16125
+ templateName: cmoDefault,
16126
+ templateVersion: 1
16029
16127
  };
16030
16128
  employees = addEmployee2(employees, cmoEmployee);
16031
16129
  await saveEmployees2(employees, EMPLOYEES_PATH2);
@@ -16465,6 +16563,309 @@ var init_update = __esm({
16465
16563
  }
16466
16564
  });
16467
16565
 
16566
+ // src/lib/stack-update.ts
16567
+ import { execFileSync as execFileSync3 } from "child_process";
16568
+ import { existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync25, renameSync as renameSync7, writeFileSync as writeFileSync20 } from "fs";
16569
+ import http from "http";
16570
+ import https from "https";
16571
+ import path37 from "path";
16572
+ function parseStackManifest(raw) {
16573
+ const parsed = JSON.parse(raw);
16574
+ if (parsed.schemaVersion !== 1) throw new Error("Unsupported stack manifest schemaVersion");
16575
+ if (!parsed.latest || !parsed.stacks || typeof parsed.stacks !== "object") {
16576
+ throw new Error("Invalid stack manifest: latest and stacks are required");
16577
+ }
16578
+ for (const [version, release] of Object.entries(parsed.stacks)) {
16579
+ if (!release.version) release.version = version;
16580
+ if (!release.services || typeof release.services !== "object") {
16581
+ throw new Error(`Invalid stack manifest: release ${version} has no services`);
16582
+ }
16583
+ for (const [serviceName, service] of Object.entries(release.services)) {
16584
+ if (!service.image || !service.env) {
16585
+ throw new Error(`Invalid stack manifest: ${version}.${serviceName} requires image and env`);
16586
+ }
16587
+ }
16588
+ }
16589
+ return parsed;
16590
+ }
16591
+ async function loadStackManifest(ref, fetchText = defaultFetchText) {
16592
+ if (/^https?:\/\//.test(ref)) return parseStackManifest(await fetchText(ref));
16593
+ return parseStackManifest(readFileSync25(ref, "utf8"));
16594
+ }
16595
+ function parseEnv(raw) {
16596
+ const env = /* @__PURE__ */ new Map();
16597
+ for (const line of raw.split(/\r?\n/)) {
16598
+ const trimmed = line.trim();
16599
+ if (!trimmed || trimmed.startsWith("#")) continue;
16600
+ const idx = line.indexOf("=");
16601
+ if (idx <= 0) continue;
16602
+ env.set(line.slice(0, idx).trim(), line.slice(idx + 1));
16603
+ }
16604
+ return env;
16605
+ }
16606
+ function patchEnv(raw, updates) {
16607
+ const seen = /* @__PURE__ */ new Set();
16608
+ const lines = raw.replace(/\n$/, "").split(/\r?\n/);
16609
+ const patched = lines.map((line) => {
16610
+ const idx = line.indexOf("=");
16611
+ if (idx <= 0 || line.trim().startsWith("#")) return line;
16612
+ const key = line.slice(0, idx).trim();
16613
+ if (!(key in updates)) return line;
16614
+ seen.add(key);
16615
+ return `${key}=${updates[key]}`;
16616
+ });
16617
+ for (const [key, value] of Object.entries(updates)) {
16618
+ if (!seen.has(key)) patched.push(`${key}=${value}`);
16619
+ }
16620
+ return patched.join("\n").replace(/\n*$/, "\n");
16621
+ }
16622
+ function createStackUpdatePlan(manifest, envRaw, targetVersion) {
16623
+ const version = targetVersion ?? manifest.latest;
16624
+ const release = manifest.stacks[version];
16625
+ if (!release) throw new Error(`Stack version ${version} not found in manifest`);
16626
+ const env = parseEnv(envRaw);
16627
+ const changes = [];
16628
+ for (const [serviceName, service] of Object.entries(release.services)) {
16629
+ const before = env.get(service.env);
16630
+ if (before !== service.image) {
16631
+ changes.push({ key: service.env, before, after: service.image, service: serviceName });
16632
+ }
16633
+ }
16634
+ return {
16635
+ manifest,
16636
+ release,
16637
+ targetVersion: version,
16638
+ changes,
16639
+ breakingChanges: release.breakingChanges ?? []
16640
+ };
16641
+ }
16642
+ function assertBreakingChangesAllowed(plan, allowedIds) {
16643
+ const required = plan.breakingChanges.filter((c) => c.requiresConfirmation !== false);
16644
+ const missing = required.filter((c) => !allowedIds.includes(c.id));
16645
+ if (missing.length > 0) {
16646
+ const details = missing.map((c) => `- ${c.id}: ${c.title}
16647
+ ${c.description}
16648
+ Action: ${c.requiredAction ?? "Review release notes."}`).join("\n");
16649
+ throw new Error(
16650
+ `Stack ${plan.targetVersion} has breaking changes that require confirmation:
16651
+ ${details}
16652
+ Re-run with --allow-breaking ${missing.map((c) => c.id).join(",")}`
16653
+ );
16654
+ }
16655
+ }
16656
+ async function runStackUpdate(options) {
16657
+ const exec2 = options.exec ?? defaultExec;
16658
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
16659
+ const manifest = await loadStackManifest(options.manifestRef, options.fetchText);
16660
+ const envRaw = readFileSync25(options.envFile, "utf8");
16661
+ const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
16662
+ assertBreakingChangesAllowed(plan, options.allowedBreakingChangeIds ?? []);
16663
+ const lockFile = options.lockFile ?? path37.join(path37.dirname(options.envFile), ".exe-stack-lock.json");
16664
+ if (options.dryRun || plan.changes.length === 0) {
16665
+ return { status: "planned", targetVersion: plan.targetVersion, changes: plan.changes, lockFile };
16666
+ }
16667
+ const backupDir = path37.join(path37.dirname(options.envFile), ".exe-stack-backups");
16668
+ mkdirSync19(backupDir, { recursive: true });
16669
+ const stamp = now().toISOString().replace(/[:.]/g, "-");
16670
+ const backupEnvFile = path37.join(backupDir, `env-${stamp}.bak`);
16671
+ writeFileSync20(backupEnvFile, envRaw, { mode: 384 });
16672
+ const updates = Object.fromEntries(plan.changes.map((c) => [c.key, c.after]));
16673
+ const patched = patchEnv(envRaw, updates);
16674
+ const tmp = `${options.envFile}.tmp-${process.pid}`;
16675
+ writeFileSync20(tmp, patched, { mode: 384 });
16676
+ renameSync7(tmp, options.envFile);
16677
+ const composeArgs = ["compose", "--file", options.composeFile, "--env-file", options.envFile];
16678
+ try {
16679
+ exec2("docker", [...composeArgs, "pull"]);
16680
+ exec2("docker", [...composeArgs, "up", "-d"]);
16681
+ await verifyReleaseHealth(plan.release, options.healthRetries ?? 12, options.healthDelayMs ?? 5e3);
16682
+ writeFileSync20(lockFile, JSON.stringify({ stackVersion: plan.targetVersion, updatedAt: now().toISOString(), services: plan.release.services }, null, 2) + "\n");
16683
+ return { status: "updated", targetVersion: plan.targetVersion, changes: plan.changes, backupEnvFile, lockFile };
16684
+ } catch (err) {
16685
+ writeFileSync20(options.envFile, envRaw, { mode: 384 });
16686
+ try {
16687
+ exec2("docker", [...composeArgs, "up", "-d"]);
16688
+ } catch {
16689
+ }
16690
+ const reason = err instanceof Error ? err.message : String(err);
16691
+ throw new Error(`Stack update failed and rollback was attempted: ${reason}`);
16692
+ }
16693
+ }
16694
+ async function verifyReleaseHealth(release, retries, delayMs) {
16695
+ for (const [serviceName, service] of Object.entries(release.services)) {
16696
+ if (!service.healthUrl) continue;
16697
+ await waitForHttpOk(service.healthUrl, retries, delayMs, serviceName);
16698
+ }
16699
+ }
16700
+ async function waitForHttpOk(url, retries, delayMs, label) {
16701
+ let last = "";
16702
+ for (let i = 0; i < retries; i++) {
16703
+ try {
16704
+ const status = await httpStatus(url);
16705
+ if (status >= 200 && status < 300) return;
16706
+ last = `HTTP ${status}`;
16707
+ } catch (err) {
16708
+ last = err instanceof Error ? err.message : String(err);
16709
+ }
16710
+ if (i < retries - 1) await new Promise((resolve) => setTimeout(resolve, delayMs));
16711
+ }
16712
+ throw new Error(`Health check failed for ${label} (${url}): ${last}`);
16713
+ }
16714
+ function httpStatus(urlString) {
16715
+ return new Promise((resolve, reject) => {
16716
+ const url = new URL(urlString);
16717
+ const mod = url.protocol === "https:" ? https : http;
16718
+ const req = mod.request(url, { method: "GET", timeout: 5e3 }, (res) => {
16719
+ res.resume();
16720
+ resolve(res.statusCode ?? 0);
16721
+ });
16722
+ req.on("timeout", () => req.destroy(new Error("timeout")));
16723
+ req.on("error", reject);
16724
+ req.end();
16725
+ });
16726
+ }
16727
+ function defaultExec(cmd, args2, opts) {
16728
+ execFileSync3(cmd, args2, { stdio: "inherit", cwd: opts?.cwd });
16729
+ }
16730
+ async function defaultFetchText(ref) {
16731
+ const res = await fetch(ref);
16732
+ if (!res.ok) throw new Error(`Failed to fetch ${ref}: HTTP ${res.status}`);
16733
+ return res.text();
16734
+ }
16735
+ function defaultStackPaths() {
16736
+ const cwdCompose = path37.resolve("docker-compose.yml");
16737
+ const cwdEnv = path37.resolve(".env");
16738
+ return {
16739
+ composeFile: process.env.EXE_STACK_COMPOSE_FILE || (existsSync30(cwdCompose) ? cwdCompose : "/opt/exe-stack/docker-compose.yml"),
16740
+ envFile: process.env.EXE_STACK_ENV_FILE || (existsSync30(cwdEnv) ? cwdEnv : "/opt/exe-stack/.env"),
16741
+ manifestRef: process.env.EXE_STACK_MANIFEST || "https://updates.askexe.com/stack-manifest.json"
16742
+ };
16743
+ }
16744
+ var init_stack_update = __esm({
16745
+ "src/lib/stack-update.ts"() {
16746
+ "use strict";
16747
+ }
16748
+ });
16749
+
16750
+ // src/bin/stack-update.ts
16751
+ var stack_update_exports = {};
16752
+ __export(stack_update_exports, {
16753
+ runStackUpdateCli: () => main3
16754
+ });
16755
+ import { readFileSync as readFileSync26 } from "fs";
16756
+ function parseArgs4(args2) {
16757
+ const defaults = defaultStackPaths();
16758
+ const opts = {
16759
+ manifestRef: defaults.manifestRef,
16760
+ composeFile: defaults.composeFile,
16761
+ envFile: defaults.envFile,
16762
+ dryRun: false,
16763
+ check: false,
16764
+ yes: false,
16765
+ allowedBreakingChangeIds: []
16766
+ };
16767
+ for (let i = 0; i < args2.length; i++) {
16768
+ const arg = args2[i];
16769
+ const next = () => args2[++i] ?? "";
16770
+ if (arg === "--manifest") opts.manifestRef = next();
16771
+ else if (arg.startsWith("--manifest=")) opts.manifestRef = arg.split("=").slice(1).join("=");
16772
+ else if (arg === "--target") opts.targetVersion = next();
16773
+ else if (arg.startsWith("--target=")) opts.targetVersion = arg.split("=")[1];
16774
+ else if (arg === "--compose-file") opts.composeFile = next();
16775
+ else if (arg.startsWith("--compose-file=")) opts.composeFile = arg.split("=").slice(1).join("=");
16776
+ else if (arg === "--env-file") opts.envFile = next();
16777
+ else if (arg.startsWith("--env-file=")) opts.envFile = arg.split("=").slice(1).join("=");
16778
+ else if (arg === "--lock-file") opts.lockFile = next();
16779
+ else if (arg === "--dry-run") opts.dryRun = true;
16780
+ else if (arg === "--check") opts.check = true;
16781
+ else if (arg === "--yes" || arg === "-y") opts.yes = true;
16782
+ else if (arg === "--allow-breaking") opts.allowedBreakingChangeIds.push(...next().split(",").map((s) => s.trim()).filter(Boolean));
16783
+ else if (arg.startsWith("--allow-breaking=")) opts.allowedBreakingChangeIds.push(...arg.split("=")[1].split(",").map((s) => s.trim()).filter(Boolean));
16784
+ else if (arg === "--help" || arg === "-h") {
16785
+ printHelp();
16786
+ process.exit(0);
16787
+ } else {
16788
+ throw new Error(`Unknown option: ${arg}`);
16789
+ }
16790
+ }
16791
+ return opts;
16792
+ }
16793
+ function printHelp() {
16794
+ console.log(`exe-os stack-update \u2014 update a self-hosted Exe OS stack from a pinned manifest
16795
+
16796
+ Usage:
16797
+ exe-os stack-update [--manifest <path-or-url>] [--target <version>] [--yes]
16798
+
16799
+ Options:
16800
+ --manifest <ref> Stack manifest JSON path or URL (default: updates.askexe.com)
16801
+ --target <version> Stack version to install (default: manifest.latest)
16802
+ --compose-file <path> docker-compose.yml path (default: ./docker-compose.yml or /opt/exe-stack/docker-compose.yml)
16803
+ --env-file <path> .env path (default: ./.env or /opt/exe-stack/.env)
16804
+ --lock-file <path> Lock file path (default: beside .env)
16805
+ --check Print available changes only
16806
+ --dry-run Plan only; do not run Docker
16807
+ --allow-breaking <ids> Confirm breaking changes, comma-separated
16808
+ -y, --yes Non-interactive confirmation
16809
+ `);
16810
+ }
16811
+ function printChanges(changes) {
16812
+ if (changes.length === 0) {
16813
+ console.log("\u2705 Stack already matches target manifest.");
16814
+ return;
16815
+ }
16816
+ console.log("Planned image tag changes:");
16817
+ for (const c of changes) {
16818
+ console.log(` - ${c.service}: ${c.key}`);
16819
+ console.log(` ${c.before ?? "<unset>"} \u2192 ${c.after}`);
16820
+ }
16821
+ }
16822
+ function printBreaking(changes) {
16823
+ if (changes.length === 0) return;
16824
+ console.log("\nBreaking-change notices:");
16825
+ for (const c of changes) {
16826
+ console.log(` - ${c.id}: ${c.title}`);
16827
+ console.log(` ${c.description}`);
16828
+ if (c.requiredAction) console.log(` Action: ${c.requiredAction}`);
16829
+ if (c.expectedDowntimeMinutes) console.log(` Expected downtime: ${c.expectedDowntimeMinutes} minutes`);
16830
+ }
16831
+ }
16832
+ async function main3() {
16833
+ const opts = parseArgs4(process.argv.slice(2));
16834
+ const manifest = await loadStackManifest(opts.manifestRef);
16835
+ const envRaw = readFileSync26(opts.envFile, "utf8");
16836
+ const plan = createStackUpdatePlan(manifest, envRaw, opts.targetVersion);
16837
+ console.log(`Exe OS stack target: ${plan.targetVersion}`);
16838
+ console.log(`Manifest: ${opts.manifestRef}`);
16839
+ console.log(`Compose: ${opts.composeFile}`);
16840
+ console.log(`Env: ${opts.envFile}
16841
+ `);
16842
+ printChanges(plan.changes);
16843
+ printBreaking(plan.breakingChanges);
16844
+ if (opts.check || opts.dryRun) return;
16845
+ if (!opts.yes) {
16846
+ console.error("\nRefusing to update without --yes. Re-run with --yes after reviewing the plan.");
16847
+ process.exit(2);
16848
+ }
16849
+ const result = await runStackUpdate(opts);
16850
+ console.log(`
16851
+ \u2705 Stack ${result.status}: ${result.targetVersion}`);
16852
+ if (result.backupEnvFile) console.log(`Backup env: ${result.backupEnvFile}`);
16853
+ console.log(`Lock file: ${result.lockFile}`);
16854
+ }
16855
+ var init_stack_update2 = __esm({
16856
+ "src/bin/stack-update.ts"() {
16857
+ "use strict";
16858
+ init_is_main();
16859
+ init_stack_update();
16860
+ if (isMainModule(import.meta.url)) {
16861
+ main3().catch((err) => {
16862
+ console.error(err instanceof Error ? err.message : String(err));
16863
+ process.exit(1);
16864
+ });
16865
+ }
16866
+ }
16867
+ });
16868
+
16468
16869
  // node_modules/es-toolkit/dist/function/debounce.mjs
16469
16870
  function debounce(func, debounceMs, { signal, edges } = {}) {
16470
16871
  let pendingThis = void 0;
@@ -18266,7 +18667,7 @@ var init_src = __esm({
18266
18667
 
18267
18668
  // node_modules/terminal-size/index.js
18268
18669
  import process2 from "process";
18269
- import { execFileSync as execFileSync3 } from "child_process";
18670
+ import { execFileSync as execFileSync4 } from "child_process";
18270
18671
  import fs from "fs";
18271
18672
  import tty from "tty";
18272
18673
  function terminalSize() {
@@ -18298,7 +18699,7 @@ var init_terminal_size = __esm({
18298
18699
  "use strict";
18299
18700
  defaultColumns = 80;
18300
18701
  defaultRows = 24;
18301
- exec = (command, arguments_, { shell, env } = {}) => execFileSync3(command, arguments_, {
18702
+ exec = (command, arguments_, { shell, env } = {}) => execFileSync4(command, arguments_, {
18302
18703
  encoding: "utf8",
18303
18704
  stdio: ["ignore", "pipe", "ignore"],
18304
18705
  timeout: 500,
@@ -20823,8 +21224,8 @@ var init_ErrorOverview = __esm({
20823
21224
  "use strict";
20824
21225
  init_Box();
20825
21226
  init_Text();
20826
- cleanupPath = (path48) => {
20827
- return path48?.replace(`file://${cwd()}/`, "");
21227
+ cleanupPath = (path49) => {
21228
+ return path49?.replace(`file://${cwd()}/`, "");
20828
21229
  };
20829
21230
  stackUtils = new StackUtils({
20830
21231
  cwd: cwd(),
@@ -23232,11 +23633,11 @@ function Footer() {
23232
23633
  } catch {
23233
23634
  }
23234
23635
  try {
23235
- const { existsSync: existsSync34 } = await import("fs");
23636
+ const { existsSync: existsSync35 } = await import("fs");
23236
23637
  const { join } = await import("path");
23237
23638
  const home = process.env.HOME ?? "";
23238
23639
  const pidPath = join(home, ".exe-os", "exed.pid");
23239
- setDaemon(existsSync34(pidPath) ? "running" : "stopped");
23640
+ setDaemon(existsSync35(pidPath) ? "running" : "stopped");
23240
23641
  } catch {
23241
23642
  setDaemon("unknown");
23242
23643
  }
@@ -25287,10 +25688,10 @@ var init_hooks = __esm({
25287
25688
  });
25288
25689
 
25289
25690
  // src/runtime/safety-checks.ts
25290
- import path37 from "path";
25691
+ import path38 from "path";
25291
25692
  import os18 from "os";
25292
25693
  function checkPathSafety(filePath) {
25293
- const resolved = path37.resolve(filePath);
25694
+ const resolved = path38.resolve(filePath);
25294
25695
  for (const { pattern, reason } of BYPASS_IMMUNE_PATTERNS) {
25295
25696
  const matches = typeof pattern === "function" ? pattern(resolved) : pattern.test(resolved);
25296
25697
  if (matches) {
@@ -25300,7 +25701,7 @@ function checkPathSafety(filePath) {
25300
25701
  return { safe: true, bypassImmune: true };
25301
25702
  }
25302
25703
  function checkReadPathSafety(filePath) {
25303
- const resolved = path37.resolve(filePath);
25704
+ const resolved = path38.resolve(filePath);
25304
25705
  const credPatterns = BYPASS_IMMUNE_PATTERNS.filter(
25305
25706
  (p) => typeof p.pattern !== "function" && (p.reason.includes("secrets") || p.reason.includes("Private key") || p.reason.includes("Credential"))
25306
25707
  );
@@ -25326,11 +25727,11 @@ var init_safety_checks = __esm({
25326
25727
  reason: "Git config can set hooks and command execution"
25327
25728
  },
25328
25729
  {
25329
- pattern: (p) => p.startsWith(path37.join(HOME, ".claude")),
25730
+ pattern: (p) => p.startsWith(path38.join(HOME, ".claude")),
25330
25731
  reason: "Claude configuration files are protected"
25331
25732
  },
25332
25733
  {
25333
- pattern: (p) => p.startsWith(path37.join(HOME, ".exe-os")),
25734
+ pattern: (p) => p.startsWith(path38.join(HOME, ".exe-os")),
25334
25735
  reason: "exe-os configuration files are protected"
25335
25736
  },
25336
25737
  {
@@ -25347,7 +25748,7 @@ var init_safety_checks = __esm({
25347
25748
  },
25348
25749
  {
25349
25750
  pattern: (p) => {
25350
- const name = path37.basename(p);
25751
+ const name = path38.basename(p);
25351
25752
  return [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile", ".zshenv"].includes(name);
25352
25753
  },
25353
25754
  reason: "Shell configuration files can execute arbitrary code on login"
@@ -25374,7 +25775,7 @@ __export(file_read_exports, {
25374
25775
  FileReadTool: () => FileReadTool
25375
25776
  });
25376
25777
  import fs3 from "fs/promises";
25377
- import path38 from "path";
25778
+ import path39 from "path";
25378
25779
  import { z } from "zod";
25379
25780
  function isBinary(buf) {
25380
25781
  for (let i = 0; i < buf.length; i++) {
@@ -25410,7 +25811,7 @@ var init_file_read = __esm({
25410
25811
  return { behavior: "allow" };
25411
25812
  },
25412
25813
  async call(input, context) {
25413
- const filePath = path38.isAbsolute(input.file_path) ? input.file_path : path38.resolve(context.cwd, input.file_path);
25814
+ const filePath = path39.isAbsolute(input.file_path) ? input.file_path : path39.resolve(context.cwd, input.file_path);
25414
25815
  let stat2;
25415
25816
  try {
25416
25817
  stat2 = await fs3.stat(filePath);
@@ -25450,7 +25851,7 @@ __export(glob_exports, {
25450
25851
  GlobTool: () => GlobTool
25451
25852
  });
25452
25853
  import fs4 from "fs/promises";
25453
- import path39 from "path";
25854
+ import path40 from "path";
25454
25855
  import { z as z2 } from "zod";
25455
25856
  async function walkDir(dir, maxDepth = 10) {
25456
25857
  const results = [];
@@ -25466,7 +25867,7 @@ async function walkDir(dir, maxDepth = 10) {
25466
25867
  if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
25467
25868
  continue;
25468
25869
  }
25469
- const fullPath = path39.join(current, entry.name);
25870
+ const fullPath = path40.join(current, entry.name);
25470
25871
  if (entry.isDirectory()) {
25471
25872
  await walk(fullPath, depth + 1);
25472
25873
  } else {
@@ -25500,11 +25901,11 @@ var init_glob = __esm({
25500
25901
  inputSchema: inputSchema2,
25501
25902
  isReadOnly: true,
25502
25903
  async call(input, context) {
25503
- const baseDir = input.path ? path39.isAbsolute(input.path) ? input.path : path39.resolve(context.cwd, input.path) : context.cwd;
25904
+ const baseDir = input.path ? path40.isAbsolute(input.path) ? input.path : path40.resolve(context.cwd, input.path) : context.cwd;
25504
25905
  try {
25505
25906
  const entries = await walkDir(baseDir);
25506
25907
  const matched = entries.filter(
25507
- (e) => simpleGlobMatch(path39.relative(baseDir, e.path), input.pattern)
25908
+ (e) => simpleGlobMatch(path40.relative(baseDir, e.path), input.pattern)
25508
25909
  );
25509
25910
  matched.sort((a, b) => b.mtime - a.mtime);
25510
25911
  if (matched.length === 0) {
@@ -25530,7 +25931,7 @@ __export(grep_exports, {
25530
25931
  });
25531
25932
  import { spawn as spawn2 } from "child_process";
25532
25933
  import fs5 from "fs/promises";
25533
- import path40 from "path";
25934
+ import path41 from "path";
25534
25935
  import { z as z3 } from "zod";
25535
25936
  function runRipgrep(input, searchPath, context) {
25536
25937
  return new Promise((resolve, reject) => {
@@ -25584,7 +25985,7 @@ async function nodeGrep(input, searchPath) {
25584
25985
  }
25585
25986
  for (const entry of entries) {
25586
25987
  if (entry.name === "node_modules" || entry.name === ".git") continue;
25587
- const fullPath = path40.join(dir, entry.name);
25988
+ const fullPath = path41.join(dir, entry.name);
25588
25989
  if (entry.isDirectory()) {
25589
25990
  await walk(fullPath);
25590
25991
  } else {
@@ -25630,7 +26031,7 @@ var init_grep = __esm({
25630
26031
  inputSchema: inputSchema3,
25631
26032
  isReadOnly: true,
25632
26033
  async call(input, context) {
25633
- const searchPath = input.path ? path40.isAbsolute(input.path) ? input.path : path40.resolve(context.cwd, input.path) : context.cwd;
26034
+ const searchPath = input.path ? path41.isAbsolute(input.path) ? input.path : path41.resolve(context.cwd, input.path) : context.cwd;
25634
26035
  try {
25635
26036
  const result = await runRipgrep(input, searchPath, context);
25636
26037
  return result;
@@ -25655,7 +26056,7 @@ __export(file_write_exports, {
25655
26056
  FileWriteTool: () => FileWriteTool
25656
26057
  });
25657
26058
  import fs6 from "fs/promises";
25658
- import path41 from "path";
26059
+ import path42 from "path";
25659
26060
  import { z as z4 } from "zod";
25660
26061
  var inputSchema4, FileWriteTool;
25661
26062
  var init_file_write = __esm({
@@ -25683,8 +26084,8 @@ var init_file_write = __esm({
25683
26084
  return { behavior: "allow" };
25684
26085
  },
25685
26086
  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);
26087
+ const filePath = path42.isAbsolute(input.file_path) ? input.file_path : path42.resolve(context.cwd, input.file_path);
26088
+ const dir = path42.dirname(filePath);
25688
26089
  await fs6.mkdir(dir, { recursive: true });
25689
26090
  await fs6.writeFile(filePath, input.content, "utf-8");
25690
26091
  return {
@@ -25702,7 +26103,7 @@ __export(file_edit_exports, {
25702
26103
  FileEditTool: () => FileEditTool
25703
26104
  });
25704
26105
  import fs7 from "fs/promises";
25705
- import path42 from "path";
26106
+ import path43 from "path";
25706
26107
  import { z as z5 } from "zod";
25707
26108
  function countOccurrences(haystack, needle) {
25708
26109
  let count = 0;
@@ -25743,7 +26144,7 @@ var init_file_edit = __esm({
25743
26144
  return { behavior: "allow" };
25744
26145
  },
25745
26146
  async call(input, context) {
25746
- const filePath = path42.isAbsolute(input.file_path) ? input.file_path : path42.resolve(context.cwd, input.file_path);
26147
+ const filePath = path43.isAbsolute(input.file_path) ? input.file_path : path43.resolve(context.cwd, input.file_path);
25747
26148
  let content;
25748
26149
  try {
25749
26150
  content = await fs7.readFile(filePath, "utf-8");
@@ -25985,7 +26386,7 @@ var init_bash = __esm({
25985
26386
  // src/tui/views/CommandCenter.tsx
25986
26387
  import { useState as useState6, useEffect as useEffect8, useMemo as useMemo4, useCallback as useCallback4, useRef as useRef4 } from "react";
25987
26388
  import TextInput from "ink-text-input";
25988
- import path43 from "path";
26389
+ import path44 from "path";
25989
26390
  import { homedir as homedir6 } from "os";
25990
26391
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
25991
26392
  function CommandCenterView({
@@ -26020,15 +26421,15 @@ function CommandCenterView({
26020
26421
  const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
26021
26422
  const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
26022
26423
  const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
26023
- const { readFileSync: readFileSync28, existsSync: existsSync34 } = await import("fs");
26424
+ const { readFileSync: readFileSync30, existsSync: existsSync35 } = await import("fs");
26024
26425
  const { join } = await import("path");
26025
26426
  const { homedir: homedir8 } = await import("os");
26026
26427
  const configPath = join(homedir8(), ".exe-os", "config.json");
26027
26428
  let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
26028
26429
  let providerConfigs = {};
26029
- if (existsSync34(configPath)) {
26430
+ if (existsSync35(configPath)) {
26030
26431
  try {
26031
- const raw = JSON.parse(readFileSync28(configPath, "utf8"));
26432
+ const raw = JSON.parse(readFileSync30(configPath, "utf8"));
26032
26433
  if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
26033
26434
  if (raw.providers && typeof raw.providers === "object") {
26034
26435
  providerConfigs = raw.providers;
@@ -26089,7 +26490,7 @@ function CommandCenterView({
26089
26490
  const markerDir = join(homedir8(), ".exe-os", "session-cache");
26090
26491
  const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
26091
26492
  for (const f of agentFiles) {
26092
- const data = JSON.parse(readFileSync28(join(markerDir, f), "utf8"));
26493
+ const data = JSON.parse(readFileSync30(join(markerDir, f), "utf8"));
26093
26494
  if (data.agentRole) {
26094
26495
  agentRole = data.agentRole;
26095
26496
  break;
@@ -26234,7 +26635,7 @@ function CommandCenterView({
26234
26635
  const demoEntries = DEMO_PROJECTS.map((p) => ({
26235
26636
  projectName: p.projectName,
26236
26637
  exeSession: p.exeSession,
26237
- projectDir: path43.join(homedir6(), p.projectName),
26638
+ projectDir: path44.join(homedir6(), p.projectName),
26238
26639
  employeeCount: p.employees.length,
26239
26640
  activeCount: p.employees.filter((e) => e.status === "active").length,
26240
26641
  memoryCount: p.employees.length * 4e3,
@@ -26272,7 +26673,7 @@ function CommandCenterView({
26272
26673
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
26273
26674
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
26274
26675
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
26275
- const { existsSync: existsSync34 } = await import("fs");
26676
+ const { existsSync: existsSync35 } = await import("fs");
26276
26677
  const { join } = await import("path");
26277
26678
  const client = getClient2();
26278
26679
  if (!client) {
@@ -26343,7 +26744,7 @@ function CommandCenterView({
26343
26744
  }
26344
26745
  const memoryCount = memoryCounts.get(name) ?? 0;
26345
26746
  const openTaskCount = openTaskCounts.get(name) ?? 0;
26346
- const hasGit = projectDir ? existsSync34(join(projectDir, ".git")) : false;
26747
+ const hasGit = projectDir ? existsSync35(join(projectDir, ".git")) : false;
26347
26748
  const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
26348
26749
  projectList.push({
26349
26750
  projectName: name,
@@ -26368,7 +26769,7 @@ function CommandCenterView({
26368
26769
  setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
26369
26770
  try {
26370
26771
  const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
26371
- setHealth((h) => ({ ...h, daemon: existsSync34(pidPath) ? "running" : "stopped" }));
26772
+ setHealth((h) => ({ ...h, daemon: existsSync35(pidPath) ? "running" : "stopped" }));
26372
26773
  } catch {
26373
26774
  }
26374
26775
  const activityResult = await client.execute(
@@ -27238,7 +27639,7 @@ var init_useOrchestrator = __esm({
27238
27639
 
27239
27640
  // src/tui/views/Sessions.tsx
27240
27641
  import React19, { useState as useState9, useEffect as useEffect11, useCallback as useCallback6 } from "react";
27241
- import path44 from "path";
27642
+ import path45 from "path";
27242
27643
  import { homedir as homedir7 } from "os";
27243
27644
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
27244
27645
  function isCoordinatorEntry(entry) {
@@ -27276,7 +27677,7 @@ function SessionsView({
27276
27677
  if (demo) {
27277
27678
  setProjects(DEMO_PROJECTS.map((p) => ({
27278
27679
  ...p,
27279
- projectDir: path44.join(homedir7(), p.projectName),
27680
+ projectDir: path45.join(homedir7(), p.projectName),
27280
27681
  employees: p.employees.map((e) => ({ ...e, attached: e.status === "active" }))
27281
27682
  })));
27282
27683
  return;
@@ -28491,12 +28892,12 @@ async function loadGatewayConfig() {
28491
28892
  state.running = false;
28492
28893
  }
28493
28894
  try {
28494
- const { existsSync: existsSync34, readFileSync: readFileSync28 } = await import("fs");
28895
+ const { existsSync: existsSync35, readFileSync: readFileSync30 } = await import("fs");
28495
28896
  const { join } = await import("path");
28496
28897
  const home = process.env.HOME ?? "";
28497
28898
  const configPath = join(home, ".exe-os", "gateway.json");
28498
- if (existsSync34(configPath)) {
28499
- const raw = JSON.parse(readFileSync28(configPath, "utf8"));
28899
+ if (existsSync35(configPath)) {
28900
+ const raw = JSON.parse(readFileSync30(configPath, "utf8"));
28500
28901
  state.port = raw.port ?? 3100;
28501
28902
  state.gatewayUrl = raw.gatewayUrl ?? "";
28502
28903
  if (raw.adapters) {
@@ -29094,12 +29495,12 @@ function TeamView({ onBack, onViewSessions }) {
29094
29495
  setMembers(teamData);
29095
29496
  setDbError(null);
29096
29497
  try {
29097
- const { existsSync: existsSync34, readFileSync: readFileSync28 } = await import("fs");
29498
+ const { existsSync: existsSync35, readFileSync: readFileSync30 } = await import("fs");
29098
29499
  const { join } = await import("path");
29099
29500
  const home = process.env.HOME ?? "";
29100
29501
  const gatewayConfig = join(home, ".exe-os", "gateway.json");
29101
- if (existsSync34(gatewayConfig)) {
29102
- const raw = JSON.parse(readFileSync28(gatewayConfig, "utf8"));
29502
+ if (existsSync35(gatewayConfig)) {
29503
+ const raw = JSON.parse(readFileSync30(gatewayConfig, "utf8"));
29103
29504
  if (raw.agents && raw.agents.length > 0) {
29104
29505
  setExternals(raw.agents.map((a) => ({
29105
29506
  name: a.name,
@@ -29279,8 +29680,8 @@ __export(wiki_client_exports, {
29279
29680
  listDocuments: () => listDocuments,
29280
29681
  listWorkspaces: () => listWorkspaces
29281
29682
  });
29282
- async function wikiFetch(config, path48, method = "GET", body) {
29283
- const url = `${config.baseUrl}/api/v1${path48}`;
29683
+ async function wikiFetch(config, path49, method = "GET", body) {
29684
+ const url = `${config.baseUrl}/api/v1${path49}`;
29284
29685
  const headers = {
29285
29686
  Authorization: `Bearer ${config.apiKey}`,
29286
29687
  "Content-Type": "application/json"
@@ -29313,7 +29714,7 @@ async function wikiFetch(config, path48, method = "GET", body) {
29313
29714
  }
29314
29715
  }
29315
29716
  if (!response.ok) {
29316
- throw new Error(`Wiki API ${method} ${path48}: ${response.status} ${response.statusText}`);
29717
+ throw new Error(`Wiki API ${method} ${path49}: ${response.status} ${response.statusText}`);
29317
29718
  }
29318
29719
  return response.json();
29319
29720
  } finally {
@@ -29907,12 +30308,12 @@ function SettingsView({ onBack }) {
29907
30308
  }
29908
30309
  setProviders(providerList);
29909
30310
  try {
29910
- const { existsSync: existsSync34 } = await import("fs");
30311
+ const { existsSync: existsSync35 } = await import("fs");
29911
30312
  const { join } = await import("path");
29912
30313
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
29913
30314
  const cfg = await loadConfig2();
29914
30315
  const home = process.env.HOME ?? "";
29915
- const hasKey = existsSync34(join(home, ".exe-os", "master.key"));
30316
+ const hasKey = existsSync35(join(home, ".exe-os", "master.key"));
29916
30317
  if (cfg.cloud) {
29917
30318
  setCloud({
29918
30319
  configured: true,
@@ -29925,22 +30326,22 @@ function SettingsView({ onBack }) {
29925
30326
  const pidPath = join(home, ".exe-os", "exed.pid");
29926
30327
  let daemon = "unknown";
29927
30328
  try {
29928
- daemon = existsSync34(pidPath) ? "running" : "stopped";
30329
+ daemon = existsSync35(pidPath) ? "running" : "stopped";
29929
30330
  } catch {
29930
30331
  }
29931
30332
  let version = "unknown";
29932
30333
  try {
29933
- const { readFileSync: readFileSync28 } = await import("fs");
30334
+ const { readFileSync: readFileSync30 } = await import("fs");
29934
30335
  const { createRequire: createRequire3 } = await import("module");
29935
30336
  const require2 = createRequire3(import.meta.url);
29936
30337
  const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
29937
- const pkg = JSON.parse(readFileSync28(pkgPath, "utf8"));
30338
+ const pkg = JSON.parse(readFileSync30(pkgPath, "utf8"));
29938
30339
  version = pkg.version;
29939
30340
  } catch {
29940
30341
  try {
29941
- const { readFileSync: readFileSync28 } = await import("fs");
30342
+ const { readFileSync: readFileSync30 } = await import("fs");
29942
30343
  const { join: joinPath } = await import("path");
29943
- const pkg = JSON.parse(readFileSync28(joinPath(process.cwd(), "package.json"), "utf8"));
30344
+ const pkg = JSON.parse(readFileSync30(joinPath(process.cwd(), "package.json"), "utf8"));
29944
30345
  version = pkg.version;
29945
30346
  } catch {
29946
30347
  }
@@ -30741,15 +31142,15 @@ __export(installer_exports2, {
30741
31142
  verifyOpenCodeHooks: () => verifyOpenCodeHooks
30742
31143
  });
30743
31144
  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";
31145
+ import { existsSync as existsSync32, readFileSync as readFileSync28 } from "fs";
31146
+ import path46 from "path";
30746
31147
  import os19 from "os";
30747
31148
  async function registerOpenCodeMcp(packageRoot, homeDir = os19.homedir()) {
30748
- const configDir = path45.join(homeDir, ".config", "opencode");
30749
- const configPath = path45.join(configDir, "opencode.json");
31149
+ const configDir = path46.join(homeDir, ".config", "opencode");
31150
+ const configPath = path46.join(configDir, "opencode.json");
30750
31151
  await mkdir8(configDir, { recursive: true });
30751
31152
  let config = {};
30752
- if (existsSync31(configPath)) {
31153
+ if (existsSync32(configPath)) {
30753
31154
  try {
30754
31155
  config = JSON.parse(await readFile7(configPath, "utf-8"));
30755
31156
  } catch {
@@ -30761,7 +31162,7 @@ async function registerOpenCodeMcp(packageRoot, homeDir = os19.homedir()) {
30761
31162
  }
30762
31163
  const newEntry = {
30763
31164
  type: "local",
30764
- command: ["node", path45.join(packageRoot, "dist", "mcp", "server.js")],
31165
+ command: ["node", path46.join(packageRoot, "dist", "mcp", "server.js")],
30765
31166
  enabled: true
30766
31167
  };
30767
31168
  const current = config.mcp["exe-os"];
@@ -30776,14 +31177,14 @@ async function registerOpenCodeMcp(packageRoot, homeDir = os19.homedir()) {
30776
31177
  return true;
30777
31178
  }
30778
31179
  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");
31180
+ const pluginDir = path46.join(homeDir, ".config", "opencode", "plugins");
31181
+ const pluginPath = path46.join(pluginDir, "exe-os.mjs");
30781
31182
  await mkdir8(pluginDir, { recursive: true });
30782
31183
  const pluginContent = PLUGIN_TEMPLATE.replace(
30783
31184
  /__PACKAGE_ROOT__/g,
30784
31185
  packageRoot.replace(/\\/g, "\\\\")
30785
31186
  );
30786
- if (existsSync31(pluginPath)) {
31187
+ if (existsSync32(pluginPath)) {
30787
31188
  const existing = await readFile7(pluginPath, "utf-8");
30788
31189
  if (existing === pluginContent) {
30789
31190
  return false;
@@ -30793,16 +31194,16 @@ async function installOpenCodePlugin(packageRoot, homeDir = os19.homedir()) {
30793
31194
  return true;
30794
31195
  }
30795
31196
  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;
31197
+ const configPath = path46.join(homeDir, ".config", "opencode", "opencode.json");
31198
+ const pluginPath = path46.join(homeDir, ".config", "opencode", "plugins", "exe-os.mjs");
31199
+ if (!existsSync32(configPath)) return false;
30799
31200
  try {
30800
- const config = JSON.parse(readFileSync26(configPath, "utf-8"));
31201
+ const config = JSON.parse(readFileSync28(configPath, "utf-8"));
30801
31202
  if (!config.mcp?.["exe-os"]?.enabled) return false;
30802
31203
  } catch {
30803
31204
  return false;
30804
31205
  }
30805
- if (!existsSync31(pluginPath)) return false;
31206
+ if (!existsSync32(pluginPath)) return false;
30806
31207
  return true;
30807
31208
  }
30808
31209
  async function runOpenCodeInstaller(homeDir) {
@@ -30837,19 +31238,19 @@ __export(installer_exports3, {
30837
31238
  verifyCodexHooks: () => verifyCodexHooks
30838
31239
  });
30839
31240
  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";
31241
+ import { existsSync as existsSync33 } from "fs";
31242
+ import path47 from "path";
30842
31243
  import os20 from "os";
30843
31244
  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");
31245
+ const codexDir = path47.join(homeDir, ".codex");
31246
+ const hooksPath = path47.join(codexDir, "hooks.json");
31247
+ const logsDir = path47.join(homeDir, ".exe-os", "logs");
31248
+ const hookLogPath = path47.join(logsDir, "hooks.log");
30848
31249
  const logSuffix = ` 2>> "${hookLogPath}"`;
30849
31250
  await mkdir9(codexDir, { recursive: true });
30850
31251
  await mkdir9(logsDir, { recursive: true });
30851
31252
  let hooksJson = {};
30852
- if (existsSync32(hooksPath)) {
31253
+ if (existsSync33(hooksPath)) {
30853
31254
  try {
30854
31255
  hooksJson = JSON.parse(await readFile8(hooksPath, "utf-8"));
30855
31256
  } catch {
@@ -30866,7 +31267,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30866
31267
  hooks: [
30867
31268
  {
30868
31269
  type: "command",
30869
- command: `node "${path46.join(packageRoot, "dist", "hooks", "session-start.js")}"${logSuffix}`,
31270
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "session-start.js")}"${logSuffix}`,
30870
31271
  timeout: 30
30871
31272
  }
30872
31273
  ]
@@ -30882,7 +31283,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30882
31283
  // Combined hook: runs ingest + error-recall in one Node process.
30883
31284
  // Eliminates a cold-start cycle per tool call (~3-6s savings on Codex).
30884
31285
  type: "command",
30885
- command: `node "${path46.join(packageRoot, "dist", "hooks", "post-tool-combined.js")}"${logSuffix}`
31286
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "post-tool-combined.js")}"${logSuffix}`
30886
31287
  }
30887
31288
  ]
30888
31289
  },
@@ -30896,7 +31297,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30896
31297
  // Single hook: prompt-submit handles memory retrieval + entity boost.
30897
31298
  // exe-heartbeat-hook is CC-specific (intercom) — omitted on Codex.
30898
31299
  type: "command",
30899
- command: `node "${path46.join(packageRoot, "dist", "hooks", "prompt-submit.js")}"${logSuffix}`
31300
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "prompt-submit.js")}"${logSuffix}`
30900
31301
  }
30901
31302
  ]
30902
31303
  },
@@ -30908,7 +31309,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30908
31309
  hooks: [
30909
31310
  {
30910
31311
  type: "command",
30911
- command: `node "${path46.join(packageRoot, "dist", "hooks", "stop.js")}"${logSuffix}`
31312
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "stop.js")}"${logSuffix}`
30912
31313
  }
30913
31314
  ]
30914
31315
  },
@@ -30921,7 +31322,7 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30921
31322
  hooks: [
30922
31323
  {
30923
31324
  type: "command",
30924
- command: `node "${path46.join(packageRoot, "dist", "hooks", "pre-tool-use.js")}"${logSuffix}`
31325
+ command: `node "${path47.join(packageRoot, "dist", "hooks", "pre-tool-use.js")}"${logSuffix}`
30925
31326
  }
30926
31327
  ]
30927
31328
  },
@@ -30953,8 +31354,8 @@ async function mergeCodexHooks(packageRoot, homeDir = os20.homedir()) {
30953
31354
  return { added, skipped };
30954
31355
  }
30955
31356
  function verifyCodexHooks(homeDir = os20.homedir()) {
30956
- const hooksPath = path46.join(homeDir, ".codex", "hooks.json");
30957
- if (!existsSync32(hooksPath)) return false;
31357
+ const hooksPath = path47.join(homeDir, ".codex", "hooks.json");
31358
+ if (!existsSync33(hooksPath)) return false;
30958
31359
  try {
30959
31360
  const hooksJson = JSON.parse(
30960
31361
  __require("fs").readFileSync(hooksPath, "utf-8")
@@ -30977,11 +31378,11 @@ function verifyCodexHooks(homeDir = os20.homedir()) {
30977
31378
  async function installCodexStatusLine(homeDir = os20.homedir()) {
30978
31379
  const prefs = loadPreferences(homeDir);
30979
31380
  if (prefs.codexStatusLine === false) return "opted-out";
30980
- const codexDir = path46.join(homeDir, ".codex");
30981
- const configPath = path46.join(codexDir, "config.toml");
31381
+ const codexDir = path47.join(homeDir, ".codex");
31382
+ const configPath = path47.join(codexDir, "config.toml");
30982
31383
  await mkdir9(codexDir, { recursive: true });
30983
31384
  let content = "";
30984
- if (existsSync32(configPath)) {
31385
+ if (existsSync33(configPath)) {
30985
31386
  content = await readFile8(configPath, "utf-8");
30986
31387
  if (/\[tui\][\s\S]*?status_line\s*=/.test(content)) {
30987
31388
  return "already-configured";
@@ -31039,12 +31440,12 @@ ${desired}
31039
31440
  return { content: next, changed };
31040
31441
  }
31041
31442
  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");
31443
+ const codexDir = path47.join(homeDir, ".codex");
31444
+ const configPath = path47.join(codexDir, "config.toml");
31445
+ const serverJsPath = path47.join(packageRoot, "dist", "mcp", "server.js");
31045
31446
  await mkdir9(codexDir, { recursive: true });
31046
31447
  let content = "";
31047
- if (existsSync32(configPath)) {
31448
+ if (existsSync33(configPath)) {
31048
31449
  content = await readFile8(configPath, "utf-8");
31049
31450
  }
31050
31451
  const sectionHeader = "[mcp_servers.exe-os]";
@@ -31069,10 +31470,10 @@ async function registerCodexMcpServer(packageRoot, homeDir = os20.homedir()) {
31069
31470
  return "registered";
31070
31471
  }
31071
31472
  async function ensureCodexHooksFeature(homeDir = os20.homedir()) {
31072
- const configPath = path46.join(homeDir, ".codex", "config.toml");
31073
- await mkdir9(path46.join(homeDir, ".codex"), { recursive: true });
31473
+ const configPath = path47.join(homeDir, ".codex", "config.toml");
31474
+ await mkdir9(path47.join(homeDir, ".codex"), { recursive: true });
31074
31475
  let content = "";
31075
- if (existsSync32(configPath)) {
31476
+ if (existsSync33(configPath)) {
31076
31477
  content = await readFile8(configPath, "utf-8");
31077
31478
  }
31078
31479
  if (/\[features\][\s\S]*?codex_hooks\s*=\s*true/.test(content)) {
@@ -31140,14 +31541,14 @@ var init_installer3 = __esm({
31140
31541
  });
31141
31542
 
31142
31543
  // 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";
31544
+ import { existsSync as existsSync34, readFileSync as readFileSync29, writeFileSync as writeFileSync21, readdirSync as readdirSync10, rmSync } from "fs";
31545
+ import path48 from "path";
31145
31546
  import os21 from "os";
31146
31547
  var args = process.argv.slice(2);
31147
31548
  if (args.includes("--version") || args.includes("-v")) {
31148
31549
  try {
31149
- const pkgPath = path47.join(path47.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
31150
- const pkg = JSON.parse(readFileSync27(pkgPath, "utf8"));
31550
+ const pkgPath = path48.join(path48.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
31551
+ const pkg = JSON.parse(readFileSync29(pkgPath, "utf8"));
31151
31552
  console.log(pkg.version);
31152
31553
  } catch {
31153
31554
  console.log("unknown");
@@ -31306,16 +31707,19 @@ ID: ${result.id}`);
31306
31707
  } else if (args[0] === "update") {
31307
31708
  const { runUpdate: runUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
31308
31709
  await runUpdate2(args.slice(1));
31710
+ } else if (args[0] === "stack-update") {
31711
+ const { runStackUpdateCli } = await Promise.resolve().then(() => (init_stack_update2(), stack_update_exports));
31712
+ await runStackUpdateCli();
31309
31713
  } else if (args.includes("--tui") || args.includes("--demo") || args[0] === "tui") {
31310
31714
  checkForUpdateOnBoot().catch(() => {
31311
31715
  });
31312
31716
  await init_App2().then(() => App_exports);
31313
31717
  } else {
31314
- const claudeDir = path47.join(os21.homedir(), ".claude");
31315
- const settingsPath = path47.join(claudeDir, "settings.json");
31316
- const hasClaudeCode = existsSync33(settingsPath) && (() => {
31718
+ const claudeDir = path48.join(os21.homedir(), ".claude");
31719
+ const settingsPath = path48.join(claudeDir, "settings.json");
31720
+ const hasClaudeCode = existsSync34(settingsPath) && (() => {
31317
31721
  try {
31318
- const raw = readFileSync27(settingsPath, "utf8");
31722
+ const raw = readFileSync29(settingsPath, "utf8");
31319
31723
  return raw.includes("exe-os") || raw.includes("exe-mem");
31320
31724
  } catch {
31321
31725
  return false;
@@ -31325,9 +31729,9 @@ ID: ${result.id}`);
31325
31729
  const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
31326
31730
  let cooName = DEFAULT_COORDINATOR_TEMPLATE_NAME2;
31327
31731
  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"));
31732
+ const rosterPath = path48.join(os21.homedir(), ".exe-os", "exe-employees.json");
31733
+ if (existsSync34(rosterPath)) {
31734
+ const roster = JSON.parse(readFileSync29(rosterPath, "utf8"));
31331
31735
  const coo = roster.find((e) => e.role === "COO");
31332
31736
  if (coo) cooName = coo.name;
31333
31737
  }
@@ -31391,14 +31795,15 @@ async function runCodexInstall() {
31391
31795
  }
31392
31796
  }
31393
31797
  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");
31798
+ const { detectMcpNameCollisions: detectMcpNameCollisions2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
31799
+ const claudeDir = path48.join(os21.homedir(), ".claude");
31800
+ const settingsPath = path48.join(claudeDir, "settings.json");
31801
+ const claudeJsonPath = path48.join(os21.homedir(), ".claude.json");
31397
31802
  let ok = true;
31398
- if (existsSync33(settingsPath)) {
31803
+ if (existsSync34(settingsPath)) {
31399
31804
  let settings;
31400
31805
  try {
31401
- settings = JSON.parse(readFileSync27(settingsPath, "utf8"));
31806
+ settings = JSON.parse(readFileSync29(settingsPath, "utf8"));
31402
31807
  } catch {
31403
31808
  console.log("\x1B[31m\u2717\x1B[0m settings.json is malformed (invalid JSON)");
31404
31809
  ok = false;
@@ -31424,10 +31829,10 @@ async function runClaudeCheck() {
31424
31829
  console.log("\x1B[31m\u2717\x1B[0m settings.json not found");
31425
31830
  ok = false;
31426
31831
  }
31427
- if (existsSync33(claudeJsonPath)) {
31832
+ if (existsSync34(claudeJsonPath)) {
31428
31833
  let claudeJson;
31429
31834
  try {
31430
- claudeJson = JSON.parse(readFileSync27(claudeJsonPath, "utf8"));
31835
+ claudeJson = JSON.parse(readFileSync29(claudeJsonPath, "utf8"));
31431
31836
  } catch {
31432
31837
  console.log("\x1B[31m\u2717\x1B[0m claude.json is malformed (invalid JSON)");
31433
31838
  ok = false;
@@ -31446,8 +31851,22 @@ async function runClaudeCheck() {
31446
31851
  console.log("\x1B[31m\u2717\x1B[0m claude.json not found");
31447
31852
  ok = false;
31448
31853
  }
31449
- const skillsDir = path47.join(claudeDir, "skills");
31450
- if (existsSync33(skillsDir)) {
31854
+ const collisions = detectMcpNameCollisions2(os21.homedir(), process.cwd());
31855
+ const disabledCollisions = collisions.filter((c) => c.disabledInMcpJson);
31856
+ if (disabledCollisions.length > 0) {
31857
+ console.log("\x1B[31m\u2717\x1B[0m MCP name collision: disabled .mcp.json entries shadow project MCP servers");
31858
+ for (const c of disabledCollisions) {
31859
+ console.log(` - ${c.serverName}: ${c.mcpJsonPath} shadows ~/.claude.json project ${c.projectPath}`);
31860
+ }
31861
+ console.log(" Fix: remove/rename the duplicate .mcp.json server entry, or use one MCP config source of truth.");
31862
+ ok = false;
31863
+ } else if (collisions.length > 0) {
31864
+ console.log("\x1B[33m!\x1B[0m MCP server names duplicated between .mcp.json and project config");
31865
+ } else {
31866
+ console.log("\x1B[32m\u2713\x1B[0m No .mcp.json/project MCP name collisions detected");
31867
+ }
31868
+ const skillsDir = path48.join(claudeDir, "skills");
31869
+ if (existsSync34(skillsDir)) {
31451
31870
  console.log("\x1B[32m\u2713\x1B[0m Slash skills directory exists");
31452
31871
  } else {
31453
31872
  console.log("\x1B[31m\u2717\x1B[0m Slash skills directory missing");
@@ -31464,16 +31883,16 @@ async function runClaudeUninstall(flags = []) {
31464
31883
  const dryRun = flags.includes("--dry-run");
31465
31884
  const purge = flags.includes("--purge");
31466
31885
  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");
31886
+ const claudeDir = path48.join(homeDir, ".claude");
31887
+ const settingsPath = path48.join(claudeDir, "settings.json");
31888
+ const claudeJsonPath = path48.join(homeDir, ".claude.json");
31889
+ const exeOsDir = path48.join(homeDir, ".exe-os");
31471
31890
  let removed = 0;
31472
31891
  const log = (msg) => console.log(dryRun ? `[dry-run] ${msg}` : msg);
31473
31892
  let settings = {};
31474
- if (existsSync33(settingsPath)) {
31893
+ if (existsSync34(settingsPath)) {
31475
31894
  try {
31476
- settings = JSON.parse(readFileSync27(settingsPath, "utf8"));
31895
+ settings = JSON.parse(readFileSync29(settingsPath, "utf8"));
31477
31896
  } catch {
31478
31897
  console.error("Your ~/.claude/settings.json appears malformed.");
31479
31898
  if (purge) {
@@ -31511,15 +31930,15 @@ async function runClaudeUninstall(flags = []) {
31511
31930
  permCount = before - settings.permissions.allow.length;
31512
31931
  }
31513
31932
  if (!dryRun) {
31514
- writeFileSync20(settingsPath, JSON.stringify(settings, null, 2) + "\n");
31933
+ writeFileSync21(settingsPath, JSON.stringify(settings, null, 2) + "\n");
31515
31934
  }
31516
31935
  log("\u2713 Removed exe-os hooks from settings.json");
31517
31936
  if (permCount > 0) log(`\u2713 Removed ${permCount} MCP permission entries`);
31518
31937
  removed++;
31519
31938
  }
31520
31939
  }
31521
- if (existsSync33(claudeJsonPath)) {
31522
- const raw = readFileSync27(claudeJsonPath, "utf8");
31940
+ if (existsSync34(claudeJsonPath)) {
31941
+ const raw = readFileSync29(claudeJsonPath, "utf8");
31523
31942
  if (raw.length > 1e6) {
31524
31943
  console.error("claude.json exceeds 1 MB \u2014 skipping parse.");
31525
31944
  } else {
@@ -31540,7 +31959,7 @@ async function runClaudeUninstall(flags = []) {
31540
31959
  }
31541
31960
  if (removedMcp) {
31542
31961
  if (!dryRun) {
31543
- writeFileSync20(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
31962
+ writeFileSync21(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
31544
31963
  }
31545
31964
  log("\u2713 Removed exe-os MCP server from claude.json");
31546
31965
  removed++;
@@ -31548,14 +31967,14 @@ async function runClaudeUninstall(flags = []) {
31548
31967
  }
31549
31968
  }
31550
31969
  }
31551
- const skillsDir = path47.join(claudeDir, "skills");
31552
- if (existsSync33(skillsDir)) {
31970
+ const skillsDir = path48.join(claudeDir, "skills");
31971
+ if (existsSync34(skillsDir)) {
31553
31972
  let skillCount = 0;
31554
31973
  try {
31555
31974
  const entries = readdirSync10(skillsDir);
31556
31975
  for (const entry of entries) {
31557
31976
  if (entry.startsWith("exe")) {
31558
- const fullPath = path47.join(skillsDir, entry);
31977
+ const fullPath = path48.join(skillsDir, entry);
31559
31978
  if (!dryRun) rmSync(fullPath, { recursive: true, force: true });
31560
31979
  skillCount++;
31561
31980
  }
@@ -31567,30 +31986,30 @@ async function runClaudeUninstall(flags = []) {
31567
31986
  removed++;
31568
31987
  }
31569
31988
  }
31570
- const claudeMdPath = path47.join(claudeDir, "CLAUDE.md");
31571
- if (existsSync33(claudeMdPath)) {
31572
- const content = readFileSync27(claudeMdPath, "utf8");
31989
+ const claudeMdPath = path48.join(claudeDir, "CLAUDE.md");
31990
+ if (existsSync34(claudeMdPath)) {
31991
+ const content = readFileSync29(claudeMdPath, "utf8");
31573
31992
  const startMarker = "<!-- exe-os:orchestration-start -->";
31574
31993
  const endMarker = "<!-- exe-os:orchestration-end -->";
31575
31994
  const startIdx = content.indexOf(startMarker);
31576
31995
  const endIdx = content.indexOf(endMarker);
31577
31996
  if (startIdx !== -1 && endIdx !== -1) {
31578
31997
  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);
31998
+ if (!dryRun) writeFileSync21(claudeMdPath, cleaned);
31580
31999
  log("\u2713 Removed orchestration block from CLAUDE.md");
31581
32000
  removed++;
31582
32001
  }
31583
32002
  }
31584
- const agentsDir = path47.join(claudeDir, "agents");
31585
- if (existsSync33(agentsDir)) {
32003
+ const agentsDir = path48.join(claudeDir, "agents");
32004
+ if (existsSync34(agentsDir)) {
31586
32005
  let agentCount = 0;
31587
32006
  try {
31588
32007
  const entries = readdirSync10(agentsDir).filter((f) => f.endsWith(".md"));
31589
32008
  let knownNames = /* @__PURE__ */ new Set();
31590
- const rosterPath = path47.join(exeOsDir, "exe-employees.json");
31591
- if (existsSync33(rosterPath)) {
32009
+ const rosterPath = path48.join(exeOsDir, "exe-employees.json");
32010
+ if (existsSync34(rosterPath)) {
31592
32011
  try {
31593
- const roster = JSON.parse(readFileSync27(rosterPath, "utf8"));
32012
+ const roster = JSON.parse(readFileSync29(rosterPath, "utf8"));
31594
32013
  knownNames = new Set(roster.map((e) => e.name));
31595
32014
  } catch {
31596
32015
  }
@@ -31598,7 +32017,7 @@ async function runClaudeUninstall(flags = []) {
31598
32017
  for (const entry of entries) {
31599
32018
  const name = entry.replace(/\.md$/, "");
31600
32019
  if (knownNames.has(name)) {
31601
- if (!dryRun) rmSync(path47.join(agentsDir, entry), { force: true });
32020
+ if (!dryRun) rmSync(path48.join(agentsDir, entry), { force: true });
31602
32021
  agentCount++;
31603
32022
  }
31604
32023
  }
@@ -31609,16 +32028,16 @@ async function runClaudeUninstall(flags = []) {
31609
32028
  removed++;
31610
32029
  }
31611
32030
  }
31612
- const projectsDir = path47.join(claudeDir, "projects");
31613
- if (existsSync33(projectsDir)) {
32031
+ const projectsDir = path48.join(claudeDir, "projects");
32032
+ if (existsSync34(projectsDir)) {
31614
32033
  let projectCount = 0;
31615
32034
  try {
31616
32035
  const projects = readdirSync10(projectsDir);
31617
32036
  for (const proj of projects) {
31618
- const projSettings = path47.join(projectsDir, proj, "settings.json");
31619
- if (!existsSync33(projSettings)) continue;
32037
+ const projSettings = path48.join(projectsDir, proj, "settings.json");
32038
+ if (!existsSync34(projSettings)) continue;
31620
32039
  try {
31621
- const pSettings = JSON.parse(readFileSync27(projSettings, "utf8"));
32040
+ const pSettings = JSON.parse(readFileSync29(projSettings, "utf8"));
31622
32041
  let changed = false;
31623
32042
  if (Array.isArray(pSettings.permissions?.allow)) {
31624
32043
  const before = pSettings.permissions.allow.length;
@@ -31628,7 +32047,7 @@ async function runClaudeUninstall(flags = []) {
31628
32047
  if (pSettings.permissions.allow.length < before) changed = true;
31629
32048
  }
31630
32049
  if (changed && !dryRun) {
31631
- writeFileSync20(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
32050
+ writeFileSync21(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
31632
32051
  }
31633
32052
  if (changed) projectCount++;
31634
32053
  } catch {
@@ -31652,18 +32071,18 @@ async function runClaudeUninstall(flags = []) {
31652
32071
  };
31653
32072
  const exeBinPath = findExeBin3();
31654
32073
  if (!exeBinPath) throw new Error("exe-os not found in PATH");
31655
- const binDir = path47.dirname(exeBinPath);
32074
+ const binDir = path48.dirname(exeBinPath);
31656
32075
  let symlinkCount = 0;
31657
- const rosterPath = path47.join(exeOsDir, "exe-employees.json");
31658
- if (existsSync33(rosterPath)) {
31659
- const roster = JSON.parse(readFileSync27(rosterPath, "utf8"));
32076
+ const rosterPath = path48.join(exeOsDir, "exe-employees.json");
32077
+ if (existsSync34(rosterPath)) {
32078
+ const roster = JSON.parse(readFileSync29(rosterPath, "utf8"));
31660
32079
  const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
31661
32080
  const coordinatorName = roster.find((e) => e.role?.toLowerCase() === "coo")?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME2;
31662
32081
  for (const emp of roster) {
31663
32082
  if (emp.name === coordinatorName) continue;
31664
32083
  for (const suffix of ["", "-opencode"]) {
31665
- const linkPath = path47.join(binDir, `${emp.name}${suffix}`);
31666
- if (existsSync33(linkPath)) {
32084
+ const linkPath = path48.join(binDir, `${emp.name}${suffix}`);
32085
+ if (existsSync34(linkPath)) {
31667
32086
  if (!dryRun) rmSync(linkPath, { force: true });
31668
32087
  symlinkCount++;
31669
32088
  }
@@ -31676,7 +32095,7 @@ async function runClaudeUninstall(flags = []) {
31676
32095
  }
31677
32096
  } catch {
31678
32097
  }
31679
- if (purge && existsSync33(exeOsDir)) {
32098
+ if (purge && existsSync34(exeOsDir)) {
31680
32099
  if (!dryRun) {
31681
32100
  process.stdout.write("\x1B[33m\u26A0 This will delete all memories, identities, and agent data.\x1B[0m\n");
31682
32101
  process.stdout.write(" Removing ~/.exe-os...\n");
@@ -31701,7 +32120,7 @@ async function checkForUpdateOnBoot() {
31701
32120
  const config = await loadConfig2();
31702
32121
  if (!config.autoUpdate.checkOnBoot) return;
31703
32122
  const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
31704
- const packageRoot = path47.resolve(
32123
+ const packageRoot = path48.resolve(
31705
32124
  new URL("../..", import.meta.url).pathname
31706
32125
  );
31707
32126
  const result = checkForUpdate2(packageRoot);
@@ -31721,7 +32140,7 @@ async function runActivate(key) {
31721
32140
  }
31722
32141
  const { saveLicense: saveLicense2, mirrorLicenseKey: mirrorLicenseKey2, validateLicense: validateLicense2 } = await Promise.resolve().then(() => (init_license(), license_exports));
31723
32142
  const { loadEmployees: loadEmployees2, saveEmployees: saveEmployees2, addEmployee: addEmployee2, registerBinSymlinks: registerBinSymlinks2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
31724
- const { getTemplateByRole: getTemplateByRole2, personalizePrompt: personalizePrompt2 } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
32143
+ const { getTemplateByRole: getTemplateByRole2 } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
31725
32144
  const { getTemplate: getIdentityTemplate } = await Promise.resolve().then(() => (init_identity_templates(), identity_templates_exports));
31726
32145
  const { identityPath: identityPath2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
31727
32146
  const { createInterface: createInterface5 } = await import("readline");
@@ -31753,7 +32172,8 @@ async function runActivate(key) {
31753
32172
  if (create3.toLowerCase() === "n") return;
31754
32173
  const nameInput = await ask3(`Name your ${role} [${defaultName}]: `);
31755
32174
  const name = nameInput || defaultName;
31756
- const emp = { ...template, name, systemPrompt: personalizePrompt2(template.systemPrompt, template.name, name), createdAt: (/* @__PURE__ */ new Date()).toISOString() };
32175
+ const { systemPrompt: _templatePrompt, ...templateMeta } = template;
32176
+ const emp = { ...templateMeta, name, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
31757
32177
  employees = addEmployee2(employees, emp);
31758
32178
  await saveEmployees2(employees);
31759
32179
  registerBinSymlinks2(name);
@@ -31761,7 +32181,7 @@ async function runActivate(key) {
31761
32181
  const idTemplate = getIdentityTemplate(identityKey);
31762
32182
  if (idTemplate) {
31763
32183
  const idPath = identityPath2(name);
31764
- const dir = path47.dirname(idPath);
32184
+ const dir = path48.dirname(idPath);
31765
32185
  if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
31766
32186
  fs8.writeFileSync(idPath, idTemplate.replace(/^agent_id: \w+/m, `agent_id: ${name}`), "utf-8");
31767
32187
  }