@askexenow/exe-os 0.9.47 → 0.9.49

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.
package/dist/bin/cli.js CHANGED
@@ -825,8 +825,8 @@ __export(installer_exports, {
825
825
  setupTmux: () => setupTmux
826
826
  });
827
827
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir } from "fs/promises";
828
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync4, copyFileSync, mkdirSync as mkdirSync3 } from "fs";
829
- import { createHash } from "crypto";
828
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync4, copyFileSync, mkdirSync as mkdirSync3, chmodSync as chmodSync2 } from "fs";
829
+ import { createHash, randomBytes } from "crypto";
830
830
  import path6 from "path";
831
831
  import os5 from "os";
832
832
  import { execSync as execSync2 } from "child_process";
@@ -955,6 +955,55 @@ function detectMcpNameCollisions(homeDir = os5.homedir(), cwd2 = process.cwd())
955
955
  }
956
956
  return collisions;
957
957
  }
958
+ function readOrCreateDaemonToken(homeDir) {
959
+ const exeDir = path6.join(homeDir, ".exe-os");
960
+ const tokenPath = path6.join(exeDir, "exed.token");
961
+ try {
962
+ if (existsSync7(tokenPath)) {
963
+ const token2 = readFileSync5(tokenPath, "utf-8").trim();
964
+ if (token2) return token2;
965
+ }
966
+ } catch {
967
+ }
968
+ const token = randomBytes(32).toString("hex");
969
+ mkdirSync3(exeDir, { recursive: true });
970
+ writeFileSync4(tokenPath, `${token}
971
+ `, "utf-8");
972
+ try {
973
+ chmodSync2(tokenPath, 384);
974
+ } catch {
975
+ }
976
+ return token;
977
+ }
978
+ function buildHttpMcpEntry(homeDir) {
979
+ const port = process.env.EXE_MCP_PORT || "48739";
980
+ const token = readOrCreateDaemonToken(homeDir);
981
+ return {
982
+ type: "http",
983
+ url: `http://127.0.0.1:${port}/mcp`,
984
+ headers: {
985
+ Authorization: `Bearer ${token}`,
986
+ "X-Agent-Id": "${AGENT_ID:-exe}",
987
+ "X-Agent-Role": "${AGENT_ROLE:-COO}"
988
+ }
989
+ };
990
+ }
991
+ function buildStdioMcpEntry(packageRoot) {
992
+ return {
993
+ type: "stdio",
994
+ command: "node",
995
+ args: [path6.join(packageRoot, "dist", "mcp", "server.js")],
996
+ env: {}
997
+ };
998
+ }
999
+ function mcpTransportMode() {
1000
+ const value = process.env.EXE_OS_MCP_TRANSPORT?.trim().toLowerCase();
1001
+ if (value === "stdio") return "stdio";
1002
+ if (value === "http") return "http";
1003
+ const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1004
+ if (totalGB <= 8) return "stdio";
1005
+ return "http";
1006
+ }
958
1007
  async function registerMcpServer(packageRoot, homeDir = os5.homedir()) {
959
1008
  const claudeJsonPath = path6.join(homeDir, ".claude.json");
960
1009
  let claudeJson = {};
@@ -968,12 +1017,7 @@ async function registerMcpServer(packageRoot, homeDir = os5.homedir()) {
968
1017
  if (!claudeJson.mcpServers) {
969
1018
  claudeJson.mcpServers = {};
970
1019
  }
971
- const newEntry = {
972
- type: "stdio",
973
- command: "node",
974
- args: [path6.join(packageRoot, "dist", "mcp", "server.js")],
975
- env: {}
976
- };
1020
+ const newEntry = mcpTransportMode() === "stdio" ? buildStdioMcpEntry(packageRoot) : buildHttpMcpEntry(homeDir);
977
1021
  if (claudeJson.mcpServers[MCP_LEGACY_KEY]) {
978
1022
  delete claudeJson.mcpServers[MCP_LEGACY_KEY];
979
1023
  process.stderr.write("exe-os: migrated MCP server key exe-mem \u2192 exe-os\n");
@@ -1842,8 +1886,8 @@ function deriveMachineKey() {
1842
1886
  }
1843
1887
  function readMachineId() {
1844
1888
  try {
1845
- const { readFileSync: readFileSync30 } = __require("fs");
1846
- return readFileSync30("/etc/machine-id", "utf-8").trim();
1889
+ const { readFileSync: readFileSync31 } = __require("fs");
1890
+ return readFileSync31("/etc/machine-id", "utf-8").trim();
1847
1891
  } catch {
1848
1892
  return "";
1849
1893
  }
@@ -9096,9 +9140,9 @@ Unclassified: ${unclassified}
9096
9140
  }
9097
9141
  async function exportBatches(options) {
9098
9142
  const fs8 = await import("fs");
9099
- const path49 = await import("path");
9143
+ const path50 = await import("path");
9100
9144
  const client = getClient();
9101
- const outDir = path49.join(process.cwd(), "exe/output/classifications/input");
9145
+ const outDir = path50.join(process.cwd(), "exe/output/classifications/input");
9102
9146
  fs8.mkdirSync(outDir, { recursive: true });
9103
9147
  const countResult = await client.execute({
9104
9148
  sql: "SELECT COUNT(*) as cnt FROM memories WHERE intent IS NULL AND outcome IS NULL AND domain IS NULL",
@@ -9122,7 +9166,7 @@ async function exportBatches(options) {
9122
9166
  const text = String(row.text || "").replace(/\n/g, " ");
9123
9167
  return JSON.stringify({ id: row.id, text });
9124
9168
  });
9125
- const batchFile = path49.join(outDir, `batch-${String(batchNum).padStart(4, "0")}.jsonl`);
9169
+ const batchFile = path50.join(outDir, `batch-${String(batchNum).padStart(4, "0")}.jsonl`);
9126
9170
  fs8.writeFileSync(batchFile, lines.join("\n") + "\n");
9127
9171
  exported += batch.rows.length;
9128
9172
  offset += options.batchSize;
@@ -9138,7 +9182,7 @@ async function exportBatches(options) {
9138
9182
  }
9139
9183
  async function importClassifications(importDir) {
9140
9184
  const fs8 = await import("fs");
9141
- const path49 = await import("path");
9185
+ const path50 = await import("path");
9142
9186
  const client = getClient();
9143
9187
  const files = fs8.readdirSync(importDir).filter((f) => f.endsWith(".jsonl")).sort();
9144
9188
  process.stderr.write(`[backfill-metadata] Found ${files.length} JSONL files to import from ${importDir}
@@ -9146,7 +9190,7 @@ async function importClassifications(importDir) {
9146
9190
  let imported = 0;
9147
9191
  let invalid = 0;
9148
9192
  for (const file of files) {
9149
- const lines = fs8.readFileSync(path49.join(importDir, file), "utf-8").split("\n").filter(Boolean);
9193
+ const lines = fs8.readFileSync(path50.join(importDir, file), "utf-8").split("\n").filter(Boolean);
9150
9194
  for (const line of lines) {
9151
9195
  try {
9152
9196
  const rec = JSON.parse(line);
@@ -11784,10 +11828,10 @@ async function disposeEmbedder() {
11784
11828
  async function embedDirect(text) {
11785
11829
  const llamaCpp = await import("node-llama-cpp");
11786
11830
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
11787
- const { existsSync: existsSync35 } = await import("fs");
11788
- const path49 = await import("path");
11789
- const modelPath = path49.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
11790
- if (!existsSync35(modelPath)) {
11831
+ const { existsSync: existsSync36 } = await import("fs");
11832
+ const path50 = await import("path");
11833
+ const modelPath = path50.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
11834
+ if (!existsSync36(modelPath)) {
11791
11835
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
11792
11836
  }
11793
11837
  const llama = await llamaCpp.getLlama();
@@ -15365,7 +15409,7 @@ import {
15365
15409
  readFileSync as readFileSync22,
15366
15410
  writeFileSync as writeFileSync18,
15367
15411
  mkdirSync as mkdirSync17,
15368
- chmodSync as chmodSync2,
15412
+ chmodSync as chmodSync3,
15369
15413
  readdirSync as readdirSync9,
15370
15414
  unlinkSync as unlinkSync13
15371
15415
  } from "fs";
@@ -15384,7 +15428,7 @@ function generateSessionWrappers(packageRoot, homeDir) {
15384
15428
  for (const src of candidates) {
15385
15429
  if (existsSync27(src)) {
15386
15430
  writeFileSync18(exeStartDst, readFileSync22(src));
15387
- chmodSync2(exeStartDst, 493);
15431
+ chmodSync3(exeStartDst, 493);
15388
15432
  break;
15389
15433
  }
15390
15434
  }
@@ -15419,11 +15463,11 @@ exec "${exeStartDst}" "$0" "$@"
15419
15463
  for (let n = 1; n <= MAX_N; n++) {
15420
15464
  const wrapperPath = path33.join(binDir, `${emp.name}${n}`);
15421
15465
  writeFileSync18(wrapperPath, wrapperContent);
15422
- chmodSync2(wrapperPath, 493);
15466
+ chmodSync3(wrapperPath, 493);
15423
15467
  created++;
15424
15468
  const codexPath = path33.join(binDir, `${emp.name}${n}-codex`);
15425
15469
  writeFileSync18(codexPath, wrapperContent);
15426
- chmodSync2(codexPath, 493);
15470
+ chmodSync3(codexPath, 493);
15427
15471
  created++;
15428
15472
  }
15429
15473
  }
@@ -15445,7 +15489,7 @@ exec "${exeStartDst}" "$0" "$@"
15445
15489
  exec node "${codexLauncher}" --agent ${emp.name} "$@"
15446
15490
  `;
15447
15491
  writeFileSync18(wrapperPath, content);
15448
- chmodSync2(wrapperPath, 493);
15492
+ chmodSync3(wrapperPath, 493);
15449
15493
  created++;
15450
15494
  }
15451
15495
  }
@@ -21264,8 +21308,8 @@ var init_ErrorOverview = __esm({
21264
21308
  "use strict";
21265
21309
  init_Box();
21266
21310
  init_Text();
21267
- cleanupPath = (path49) => {
21268
- return path49?.replace(`file://${cwd()}/`, "");
21311
+ cleanupPath = (path50) => {
21312
+ return path50?.replace(`file://${cwd()}/`, "");
21269
21313
  };
21270
21314
  stackUtils = new StackUtils({
21271
21315
  cwd: cwd(),
@@ -23673,11 +23717,11 @@ function Footer() {
23673
23717
  } catch {
23674
23718
  }
23675
23719
  try {
23676
- const { existsSync: existsSync35 } = await import("fs");
23720
+ const { existsSync: existsSync36 } = await import("fs");
23677
23721
  const { join } = await import("path");
23678
23722
  const home = process.env.HOME ?? "";
23679
23723
  const pidPath = join(home, ".exe-os", "exed.pid");
23680
- setDaemon(existsSync35(pidPath) ? "running" : "stopped");
23724
+ setDaemon(existsSync36(pidPath) ? "running" : "stopped");
23681
23725
  } catch {
23682
23726
  setDaemon("unknown");
23683
23727
  }
@@ -26461,15 +26505,15 @@ function CommandCenterView({
26461
26505
  const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
26462
26506
  const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
26463
26507
  const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
26464
- const { readFileSync: readFileSync30, existsSync: existsSync35 } = await import("fs");
26508
+ const { readFileSync: readFileSync31, existsSync: existsSync36 } = await import("fs");
26465
26509
  const { join } = await import("path");
26466
26510
  const { homedir: homedir8 } = await import("os");
26467
26511
  const configPath = join(homedir8(), ".exe-os", "config.json");
26468
26512
  let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
26469
26513
  let providerConfigs = {};
26470
- if (existsSync35(configPath)) {
26514
+ if (existsSync36(configPath)) {
26471
26515
  try {
26472
- const raw = JSON.parse(readFileSync30(configPath, "utf8"));
26516
+ const raw = JSON.parse(readFileSync31(configPath, "utf8"));
26473
26517
  if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
26474
26518
  if (raw.providers && typeof raw.providers === "object") {
26475
26519
  providerConfigs = raw.providers;
@@ -26530,7 +26574,7 @@ function CommandCenterView({
26530
26574
  const markerDir = join(homedir8(), ".exe-os", "session-cache");
26531
26575
  const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
26532
26576
  for (const f of agentFiles) {
26533
- const data = JSON.parse(readFileSync30(join(markerDir, f), "utf8"));
26577
+ const data = JSON.parse(readFileSync31(join(markerDir, f), "utf8"));
26534
26578
  if (data.agentRole) {
26535
26579
  agentRole = data.agentRole;
26536
26580
  break;
@@ -26713,7 +26757,7 @@ function CommandCenterView({
26713
26757
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
26714
26758
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
26715
26759
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
26716
- const { existsSync: existsSync35 } = await import("fs");
26760
+ const { existsSync: existsSync36 } = await import("fs");
26717
26761
  const { join } = await import("path");
26718
26762
  const client = getClient2();
26719
26763
  if (!client) {
@@ -26784,7 +26828,7 @@ function CommandCenterView({
26784
26828
  }
26785
26829
  const memoryCount = memoryCounts.get(name) ?? 0;
26786
26830
  const openTaskCount = openTaskCounts.get(name) ?? 0;
26787
- const hasGit = projectDir ? existsSync35(join(projectDir, ".git")) : false;
26831
+ const hasGit = projectDir ? existsSync36(join(projectDir, ".git")) : false;
26788
26832
  const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
26789
26833
  projectList.push({
26790
26834
  projectName: name,
@@ -26809,7 +26853,7 @@ function CommandCenterView({
26809
26853
  setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
26810
26854
  try {
26811
26855
  const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
26812
- setHealth((h) => ({ ...h, daemon: existsSync35(pidPath) ? "running" : "stopped" }));
26856
+ setHealth((h) => ({ ...h, daemon: existsSync36(pidPath) ? "running" : "stopped" }));
26813
26857
  } catch {
26814
26858
  }
26815
26859
  const activityResult = await client.execute(
@@ -28932,12 +28976,12 @@ async function loadGatewayConfig() {
28932
28976
  state.running = false;
28933
28977
  }
28934
28978
  try {
28935
- const { existsSync: existsSync35, readFileSync: readFileSync30 } = await import("fs");
28979
+ const { existsSync: existsSync36, readFileSync: readFileSync31 } = await import("fs");
28936
28980
  const { join } = await import("path");
28937
28981
  const home = process.env.HOME ?? "";
28938
28982
  const configPath = join(home, ".exe-os", "gateway.json");
28939
- if (existsSync35(configPath)) {
28940
- const raw = JSON.parse(readFileSync30(configPath, "utf8"));
28983
+ if (existsSync36(configPath)) {
28984
+ const raw = JSON.parse(readFileSync31(configPath, "utf8"));
28941
28985
  state.port = raw.port ?? 3100;
28942
28986
  state.gatewayUrl = raw.gatewayUrl ?? "";
28943
28987
  if (raw.adapters) {
@@ -29535,12 +29579,12 @@ function TeamView({ onBack, onViewSessions }) {
29535
29579
  setMembers(teamData);
29536
29580
  setDbError(null);
29537
29581
  try {
29538
- const { existsSync: existsSync35, readFileSync: readFileSync30 } = await import("fs");
29582
+ const { existsSync: existsSync36, readFileSync: readFileSync31 } = await import("fs");
29539
29583
  const { join } = await import("path");
29540
29584
  const home = process.env.HOME ?? "";
29541
29585
  const gatewayConfig = join(home, ".exe-os", "gateway.json");
29542
- if (existsSync35(gatewayConfig)) {
29543
- const raw = JSON.parse(readFileSync30(gatewayConfig, "utf8"));
29586
+ if (existsSync36(gatewayConfig)) {
29587
+ const raw = JSON.parse(readFileSync31(gatewayConfig, "utf8"));
29544
29588
  if (raw.agents && raw.agents.length > 0) {
29545
29589
  setExternals(raw.agents.map((a) => ({
29546
29590
  name: a.name,
@@ -29720,8 +29764,8 @@ __export(wiki_client_exports, {
29720
29764
  listDocuments: () => listDocuments,
29721
29765
  listWorkspaces: () => listWorkspaces
29722
29766
  });
29723
- async function wikiFetch(config, path49, method = "GET", body) {
29724
- const url = `${config.baseUrl}/api/v1${path49}`;
29767
+ async function wikiFetch(config, path50, method = "GET", body) {
29768
+ const url = `${config.baseUrl}/api/v1${path50}`;
29725
29769
  const headers = {
29726
29770
  Authorization: `Bearer ${config.apiKey}`,
29727
29771
  "Content-Type": "application/json"
@@ -29754,7 +29798,7 @@ async function wikiFetch(config, path49, method = "GET", body) {
29754
29798
  }
29755
29799
  }
29756
29800
  if (!response.ok) {
29757
- throw new Error(`Wiki API ${method} ${path49}: ${response.status} ${response.statusText}`);
29801
+ throw new Error(`Wiki API ${method} ${path50}: ${response.status} ${response.statusText}`);
29758
29802
  }
29759
29803
  return response.json();
29760
29804
  } finally {
@@ -30348,12 +30392,12 @@ function SettingsView({ onBack }) {
30348
30392
  }
30349
30393
  setProviders(providerList);
30350
30394
  try {
30351
- const { existsSync: existsSync35 } = await import("fs");
30395
+ const { existsSync: existsSync36 } = await import("fs");
30352
30396
  const { join } = await import("path");
30353
30397
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
30354
30398
  const cfg = await loadConfig2();
30355
30399
  const home = process.env.HOME ?? "";
30356
- const hasKey = existsSync35(join(home, ".exe-os", "master.key"));
30400
+ const hasKey = existsSync36(join(home, ".exe-os", "master.key"));
30357
30401
  if (cfg.cloud) {
30358
30402
  setCloud({
30359
30403
  configured: true,
@@ -30366,22 +30410,22 @@ function SettingsView({ onBack }) {
30366
30410
  const pidPath = join(home, ".exe-os", "exed.pid");
30367
30411
  let daemon = "unknown";
30368
30412
  try {
30369
- daemon = existsSync35(pidPath) ? "running" : "stopped";
30413
+ daemon = existsSync36(pidPath) ? "running" : "stopped";
30370
30414
  } catch {
30371
30415
  }
30372
30416
  let version = "unknown";
30373
30417
  try {
30374
- const { readFileSync: readFileSync30 } = await import("fs");
30418
+ const { readFileSync: readFileSync31 } = await import("fs");
30375
30419
  const { createRequire: createRequire3 } = await import("module");
30376
30420
  const require2 = createRequire3(import.meta.url);
30377
30421
  const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
30378
- const pkg = JSON.parse(readFileSync30(pkgPath, "utf8"));
30422
+ const pkg = JSON.parse(readFileSync31(pkgPath, "utf8"));
30379
30423
  version = pkg.version;
30380
30424
  } catch {
30381
30425
  try {
30382
- const { readFileSync: readFileSync30 } = await import("fs");
30426
+ const { readFileSync: readFileSync31 } = await import("fs");
30383
30427
  const { join: joinPath } = await import("path");
30384
- const pkg = JSON.parse(readFileSync30(joinPath(process.cwd(), "package.json"), "utf8"));
30428
+ const pkg = JSON.parse(readFileSync31(joinPath(process.cwd(), "package.json"), "utf8"));
30385
30429
  version = pkg.version;
30386
30430
  } catch {
30387
30431
  }
@@ -31580,15 +31624,199 @@ var init_installer3 = __esm({
31580
31624
  }
31581
31625
  });
31582
31626
 
31583
- // src/bin/cli.ts
31584
- import { existsSync as existsSync34, readFileSync as readFileSync29, writeFileSync as writeFileSync21, readdirSync as readdirSync10, rmSync } from "fs";
31627
+ // src/adapters/claude/mcp-diagnostics.ts
31628
+ var mcp_diagnostics_exports = {};
31629
+ __export(mcp_diagnostics_exports, {
31630
+ diagnoseClaudeMcpConfig: () => diagnoseClaudeMcpConfig,
31631
+ formatMcpDiagnosticReport: () => formatMcpDiagnosticReport
31632
+ });
31633
+ import { existsSync as existsSync34, readFileSync as readFileSync29 } from "fs";
31585
31634
  import path48 from "path";
31586
31635
  import os21 from "os";
31636
+ function readJson(filePath) {
31637
+ if (!existsSync34(filePath)) return null;
31638
+ try {
31639
+ return JSON.parse(readFileSync29(filePath, "utf8"));
31640
+ } catch {
31641
+ return null;
31642
+ }
31643
+ }
31644
+ function pathApplies2(projectPath, cwd2) {
31645
+ const project = path48.resolve(projectPath);
31646
+ const current = path48.resolve(cwd2);
31647
+ return current === project || current.startsWith(project + path48.sep);
31648
+ }
31649
+ function findAncestorMcpJsons2(cwd2, homeDir) {
31650
+ const files = [];
31651
+ let dir = path48.resolve(cwd2);
31652
+ const root = path48.parse(dir).root;
31653
+ const stop = path48.resolve(homeDir);
31654
+ while (dir !== root) {
31655
+ const candidate = path48.join(dir, ".mcp.json");
31656
+ if (existsSync34(candidate)) files.push(candidate);
31657
+ if (dir === stop) break;
31658
+ dir = path48.dirname(dir);
31659
+ }
31660
+ return files;
31661
+ }
31662
+ function extractPermissionPrefixes(settings) {
31663
+ const allow = settings?.permissions?.allow ?? [];
31664
+ const prefixes = /* @__PURE__ */ new Set();
31665
+ for (const entry of allow) {
31666
+ const match = entry.match(/^mcp__(.+?)__/);
31667
+ if (match?.[1]) prefixes.add(match[1]);
31668
+ }
31669
+ return [...prefixes].sort();
31670
+ }
31671
+ function serverAllowedByPermissions(serverName, prefixes) {
31672
+ return prefixes.has(serverName) || prefixes.has(serverName.replace(/-/g, "_"));
31673
+ }
31674
+ function diagnoseClaudeMcpConfig(homeDir = os21.homedir(), cwd2 = process.cwd(), env = process.env) {
31675
+ const claudeJsonPath = path48.join(homeDir, ".claude.json");
31676
+ const settingsPath = path48.join(homeDir, ".claude", "settings.json");
31677
+ const claudeJson = readJson(claudeJsonPath);
31678
+ const settings = readJson(settingsPath);
31679
+ const issues = [];
31680
+ if (!claudeJson) {
31681
+ issues.push({
31682
+ severity: "error",
31683
+ code: "claude-json-missing-or-invalid",
31684
+ message: "~/.claude.json is missing or invalid, so Claude MCP server config cannot be audited.",
31685
+ fix: "Run exe-os claude install, then re-open Claude Code."
31686
+ });
31687
+ }
31688
+ if (!settings) {
31689
+ issues.push({
31690
+ severity: "warn",
31691
+ code: "settings-json-missing-or-invalid",
31692
+ message: "~/.claude/settings.json is missing or invalid; MCP permissions cannot be audited.",
31693
+ fix: "Run exe-os claude install, then re-open Claude Code."
31694
+ });
31695
+ }
31696
+ const globalServers = Object.keys(claudeJson?.mcpServers ?? {}).sort();
31697
+ const projectServers = [];
31698
+ const applicableProjects = [];
31699
+ for (const [projectPath, projectConfig] of Object.entries(claudeJson?.projects ?? {})) {
31700
+ if (!pathApplies2(projectPath, cwd2)) continue;
31701
+ applicableProjects.push([projectPath, projectConfig]);
31702
+ for (const name of Object.keys(projectConfig.mcpServers ?? {})) {
31703
+ projectServers.push({ name, source: "project", path: projectPath });
31704
+ }
31705
+ if (Array.isArray(projectConfig.enabledMcpServers)) {
31706
+ issues.push({
31707
+ severity: projectConfig.enabledMcpServers.length === 0 ? "warn" : "info",
31708
+ code: "enabled-mcp-servers-present",
31709
+ message: `Project ${projectPath} contains enabledMcpServers=${JSON.stringify(projectConfig.enabledMcpServers)}. This key is not the same as enabledMcpjsonServers and has caused confusing /mcp debugging reports.`,
31710
+ fix: "If MCP tools are hidden, back up ~/.claude.json, remove only this key, restart Claude Code, and compare /mcp + ToolSearch."
31711
+ });
31712
+ }
31713
+ }
31714
+ const mcpJsonServers = [];
31715
+ for (const mcpJsonPath of findAncestorMcpJsons2(cwd2, homeDir)) {
31716
+ const mcpJson = readJson(mcpJsonPath);
31717
+ for (const name of Object.keys(mcpJson?.mcpServers ?? {})) {
31718
+ mcpJsonServers.push({ name, source: "mcp.json", path: mcpJsonPath });
31719
+ }
31720
+ }
31721
+ const permissionPrefixes = extractPermissionPrefixes(settings);
31722
+ const permissionPrefixSet = new Set(permissionPrefixes);
31723
+ const allConfiguredServerNames = [...globalServers, ...projectServers.map((s) => s.name), ...mcpJsonServers.map((s) => s.name)];
31724
+ const configuredServers = [...new Set(allConfiguredServerNames)].sort();
31725
+ const nonExeServers = configuredServers.filter((s) => s !== "exe-os" && s !== "exe-mem");
31726
+ const nonExeWithoutPermissions = nonExeServers.filter((s) => !serverAllowedByPermissions(s, permissionPrefixSet));
31727
+ if (nonExeWithoutPermissions.length > 0 && permissionPrefixes.some((p) => p === "exe-os" || p === "exe-mem")) {
31728
+ issues.push({
31729
+ severity: "warn",
31730
+ code: "non-exe-mcp-not-permission-allowlisted",
31731
+ message: `Interactive Claude Code may only surface/auto-run permission-allowed MCP tools. exe-os is allowlisted, but these configured servers are not: ${nonExeWithoutPermissions.join(", ")}.`,
31732
+ fix: "In Claude Code, run /permissions and allow the needed MCP server tools (for example mcp__figma__*), then restart. As a diagnostic only, test ENABLE_TOOL_SEARCH=false claude to compare tool injection."
31733
+ });
31734
+ }
31735
+ const duplicateNames = allConfiguredServerNames.filter((name, index, arr) => arr.indexOf(name) !== index);
31736
+ if (duplicateNames.length > 0) {
31737
+ issues.push({
31738
+ severity: "warn",
31739
+ code: "duplicate-mcp-server-names",
31740
+ message: `Duplicate MCP server names exist across global/project/.mcp.json scopes: ${[...new Set(duplicateNames)].join(", ")}. Claude Code can show Connected while dropping tools when scopes collide.`,
31741
+ fix: "Use one source of truth per MCP server name; remove or rename duplicates."
31742
+ });
31743
+ }
31744
+ for (const server of mcpJsonServers) {
31745
+ for (const [projectPath, projectConfig] of applicableProjects) {
31746
+ if (!projectConfig.mcpServers?.[server.name]) continue;
31747
+ const enabled = new Set(projectConfig.enabledMcpjsonServers ?? []);
31748
+ if (!enabled.has(server.name)) {
31749
+ issues.push({
31750
+ severity: "error",
31751
+ code: "disabled-mcp-json-shadows-project-server",
31752
+ message: `${server.name} appears in ${server.path} and project ${projectPath}, but is not enabled in enabledMcpjsonServers. This can shadow the project server and yield Connected/0 tools.`,
31753
+ fix: "Remove/rename the duplicate .mcp.json entry or enable that .mcp.json server explicitly."
31754
+ });
31755
+ }
31756
+ }
31757
+ }
31758
+ if (env.ENABLE_TOOL_SEARCH && env.ENABLE_TOOL_SEARCH !== "auto") {
31759
+ issues.push({
31760
+ severity: "info",
31761
+ code: "enable-tool-search-env-set",
31762
+ message: `ENABLE_TOOL_SEARCH is set to ${env.ENABLE_TOOL_SEARCH}. This changes Claude Code's deferred tool injection path.`,
31763
+ fix: "For debugging, compare interactive launches with ENABLE_TOOL_SEARCH=false, ENABLE_TOOL_SEARCH=auto, and unset."
31764
+ });
31765
+ }
31766
+ if (configuredServers.length > 8) {
31767
+ issues.push({
31768
+ severity: "info",
31769
+ code: "large-mcp-surface",
31770
+ message: `${configuredServers.length} MCP servers are configured for this project. If pipe mode sees tools but interactive mode does not, reduce scope temporarily or enable only needed servers while debugging Claude Code tool injection.`,
31771
+ fix: "Keep exe-os global, move project/design tools into the active project only, and use /permissions for the servers needed in that session."
31772
+ });
31773
+ }
31774
+ return {
31775
+ cwd: path48.resolve(cwd2),
31776
+ globalServers,
31777
+ projectServers: projectServers.sort((a, b) => a.name.localeCompare(b.name)),
31778
+ mcpJsonServers: mcpJsonServers.sort((a, b) => a.name.localeCompare(b.name)),
31779
+ permissionPrefixes,
31780
+ issues
31781
+ };
31782
+ }
31783
+ function formatMcpDiagnosticReport(report) {
31784
+ const lines = [];
31785
+ lines.push("Claude MCP Diagnostic");
31786
+ lines.push(`cwd: ${report.cwd}`);
31787
+ lines.push("");
31788
+ lines.push(`global servers: ${report.globalServers.length ? report.globalServers.join(", ") : "none"}`);
31789
+ lines.push(`project servers: ${report.projectServers.length ? report.projectServers.map((s) => `${s.name} (${s.path})`).join(", ") : "none"}`);
31790
+ lines.push(`.mcp.json servers: ${report.mcpJsonServers.length ? report.mcpJsonServers.map((s) => `${s.name} (${s.path})`).join(", ") : "none"}`);
31791
+ lines.push(`permission MCP prefixes: ${report.permissionPrefixes.length ? report.permissionPrefixes.join(", ") : "none"}`);
31792
+ lines.push("");
31793
+ if (report.issues.length === 0) {
31794
+ lines.push("\u2713 No obvious MCP config hazards detected.");
31795
+ return lines.join("\n");
31796
+ }
31797
+ lines.push("Issues / clues:");
31798
+ for (const issue of report.issues) {
31799
+ const icon = issue.severity === "error" ? "\u2717" : issue.severity === "warn" ? "!" : "i";
31800
+ lines.push(`${icon} [${issue.code}] ${issue.message}`);
31801
+ if (issue.fix) lines.push(` Fix/test: ${issue.fix}`);
31802
+ }
31803
+ return lines.join("\n");
31804
+ }
31805
+ var init_mcp_diagnostics = __esm({
31806
+ "src/adapters/claude/mcp-diagnostics.ts"() {
31807
+ "use strict";
31808
+ }
31809
+ });
31810
+
31811
+ // src/bin/cli.ts
31812
+ import { existsSync as existsSync35, readFileSync as readFileSync30, writeFileSync as writeFileSync21, readdirSync as readdirSync10, rmSync } from "fs";
31813
+ import path49 from "path";
31814
+ import os22 from "os";
31587
31815
  var args = process.argv.slice(2);
31588
31816
  if (args.includes("--version") || args.includes("-v")) {
31589
31817
  try {
31590
- const pkgPath = path48.join(path48.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
31591
- const pkg = JSON.parse(readFileSync29(pkgPath, "utf8"));
31818
+ const pkgPath = path49.join(path49.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
31819
+ const pkg = JSON.parse(readFileSync30(pkgPath, "utf8"));
31592
31820
  console.log(pkg.version);
31593
31821
  } catch {
31594
31822
  console.log("unknown");
@@ -31622,6 +31850,17 @@ if (args.includes("--global")) {
31622
31850
  case "check":
31623
31851
  await runClaudeCheck();
31624
31852
  break;
31853
+ case "mcp-doctor":
31854
+ await runClaudeMcpDoctor();
31855
+ break;
31856
+ case "mcp":
31857
+ if (args[2] === "doctor") {
31858
+ await runClaudeMcpDoctor();
31859
+ } else {
31860
+ console.error("Usage: exe-os claude mcp-doctor");
31861
+ process.exit(1);
31862
+ }
31863
+ break;
31625
31864
  case "uninstall":
31626
31865
  await runClaudeUninstall(args.slice(2));
31627
31866
  break;
@@ -31755,11 +31994,11 @@ ID: ${result.id}`);
31755
31994
  });
31756
31995
  await init_App2().then(() => App_exports);
31757
31996
  } else {
31758
- const claudeDir = path48.join(os21.homedir(), ".claude");
31759
- const settingsPath = path48.join(claudeDir, "settings.json");
31760
- const hasClaudeCode = existsSync34(settingsPath) && (() => {
31997
+ const claudeDir = path49.join(os22.homedir(), ".claude");
31998
+ const settingsPath = path49.join(claudeDir, "settings.json");
31999
+ const hasClaudeCode = existsSync35(settingsPath) && (() => {
31761
32000
  try {
31762
- const raw = readFileSync29(settingsPath, "utf8");
32001
+ const raw = readFileSync30(settingsPath, "utf8");
31763
32002
  return raw.includes("exe-os") || raw.includes("exe-mem");
31764
32003
  } catch {
31765
32004
  return false;
@@ -31769,9 +32008,9 @@ ID: ${result.id}`);
31769
32008
  const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
31770
32009
  let cooName = DEFAULT_COORDINATOR_TEMPLATE_NAME2;
31771
32010
  try {
31772
- const rosterPath = path48.join(os21.homedir(), ".exe-os", "exe-employees.json");
31773
- if (existsSync34(rosterPath)) {
31774
- const roster = JSON.parse(readFileSync29(rosterPath, "utf8"));
32011
+ const rosterPath = path49.join(os22.homedir(), ".exe-os", "exe-employees.json");
32012
+ if (existsSync35(rosterPath)) {
32013
+ const roster = JSON.parse(readFileSync30(rosterPath, "utf8"));
31775
32014
  const coo = roster.find((e) => e.role === "COO");
31776
32015
  if (coo) cooName = coo.name;
31777
32016
  }
@@ -31789,6 +32028,7 @@ cd into a project folder and run \x1B[1m${cooName}1\x1B[0m to boot your COO.
31789
32028
  exe-os setup Re-run setup wizard
31790
32029
  exe-os cloud sync Sync all data with Exe Cloud
31791
32030
  exe-os claude check Verify Claude Code integration
32031
+ exe-os claude mcp-doctor Diagnose Claude MCP tool visibility
31792
32032
  exe-os tui Launch TUI dashboard (Mode 2)
31793
32033
  exe-os --help Show all commands
31794
32034
  `);
@@ -31836,14 +32076,14 @@ async function runCodexInstall() {
31836
32076
  }
31837
32077
  async function runClaudeCheck() {
31838
32078
  const { detectMcpNameCollisions: detectMcpNameCollisions2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
31839
- const claudeDir = path48.join(os21.homedir(), ".claude");
31840
- const settingsPath = path48.join(claudeDir, "settings.json");
31841
- const claudeJsonPath = path48.join(os21.homedir(), ".claude.json");
32079
+ const claudeDir = path49.join(os22.homedir(), ".claude");
32080
+ const settingsPath = path49.join(claudeDir, "settings.json");
32081
+ const claudeJsonPath = path49.join(os22.homedir(), ".claude.json");
31842
32082
  let ok = true;
31843
- if (existsSync34(settingsPath)) {
32083
+ if (existsSync35(settingsPath)) {
31844
32084
  let settings;
31845
32085
  try {
31846
- settings = JSON.parse(readFileSync29(settingsPath, "utf8"));
32086
+ settings = JSON.parse(readFileSync30(settingsPath, "utf8"));
31847
32087
  } catch {
31848
32088
  console.log("\x1B[31m\u2717\x1B[0m settings.json is malformed (invalid JSON)");
31849
32089
  ok = false;
@@ -31869,10 +32109,10 @@ async function runClaudeCheck() {
31869
32109
  console.log("\x1B[31m\u2717\x1B[0m settings.json not found");
31870
32110
  ok = false;
31871
32111
  }
31872
- if (existsSync34(claudeJsonPath)) {
32112
+ if (existsSync35(claudeJsonPath)) {
31873
32113
  let claudeJson;
31874
32114
  try {
31875
- claudeJson = JSON.parse(readFileSync29(claudeJsonPath, "utf8"));
32115
+ claudeJson = JSON.parse(readFileSync30(claudeJsonPath, "utf8"));
31876
32116
  } catch {
31877
32117
  console.log("\x1B[31m\u2717\x1B[0m claude.json is malformed (invalid JSON)");
31878
32118
  ok = false;
@@ -31891,7 +32131,7 @@ async function runClaudeCheck() {
31891
32131
  console.log("\x1B[31m\u2717\x1B[0m claude.json not found");
31892
32132
  ok = false;
31893
32133
  }
31894
- const collisions = detectMcpNameCollisions2(os21.homedir(), process.cwd());
32134
+ const collisions = detectMcpNameCollisions2(os22.homedir(), process.cwd());
31895
32135
  const disabledCollisions = collisions.filter((c) => c.disabledInMcpJson);
31896
32136
  if (disabledCollisions.length > 0) {
31897
32137
  console.log("\x1B[31m\u2717\x1B[0m MCP name collision: disabled .mcp.json entries shadow project MCP servers");
@@ -31905,8 +32145,8 @@ async function runClaudeCheck() {
31905
32145
  } else {
31906
32146
  console.log("\x1B[32m\u2713\x1B[0m No .mcp.json/project MCP name collisions detected");
31907
32147
  }
31908
- const skillsDir = path48.join(claudeDir, "skills");
31909
- if (existsSync34(skillsDir)) {
32148
+ const skillsDir = path49.join(claudeDir, "skills");
32149
+ if (existsSync35(skillsDir)) {
31910
32150
  console.log("\x1B[32m\u2713\x1B[0m Slash skills directory exists");
31911
32151
  } else {
31912
32152
  console.log("\x1B[31m\u2717\x1B[0m Slash skills directory missing");
@@ -31919,20 +32159,27 @@ async function runClaudeCheck() {
31919
32159
  console.log("\nAll checks passed.");
31920
32160
  }
31921
32161
  }
32162
+ async function runClaudeMcpDoctor() {
32163
+ const { diagnoseClaudeMcpConfig: diagnoseClaudeMcpConfig2, formatMcpDiagnosticReport: formatMcpDiagnosticReport2 } = await Promise.resolve().then(() => (init_mcp_diagnostics(), mcp_diagnostics_exports));
32164
+ const report = diagnoseClaudeMcpConfig2(os22.homedir(), process.cwd(), process.env);
32165
+ console.log(formatMcpDiagnosticReport2(report));
32166
+ const hasError = report.issues.some((issue) => issue.severity === "error");
32167
+ process.exit(hasError ? 1 : 0);
32168
+ }
31922
32169
  async function runClaudeUninstall(flags = []) {
31923
32170
  const dryRun = flags.includes("--dry-run");
31924
32171
  const purge = flags.includes("--purge");
31925
- const homeDir = os21.homedir();
31926
- const claudeDir = path48.join(homeDir, ".claude");
31927
- const settingsPath = path48.join(claudeDir, "settings.json");
31928
- const claudeJsonPath = path48.join(homeDir, ".claude.json");
31929
- const exeOsDir = path48.join(homeDir, ".exe-os");
32172
+ const homeDir = os22.homedir();
32173
+ const claudeDir = path49.join(homeDir, ".claude");
32174
+ const settingsPath = path49.join(claudeDir, "settings.json");
32175
+ const claudeJsonPath = path49.join(homeDir, ".claude.json");
32176
+ const exeOsDir = path49.join(homeDir, ".exe-os");
31930
32177
  let removed = 0;
31931
32178
  const log = (msg) => console.log(dryRun ? `[dry-run] ${msg}` : msg);
31932
32179
  let settings = {};
31933
- if (existsSync34(settingsPath)) {
32180
+ if (existsSync35(settingsPath)) {
31934
32181
  try {
31935
- settings = JSON.parse(readFileSync29(settingsPath, "utf8"));
32182
+ settings = JSON.parse(readFileSync30(settingsPath, "utf8"));
31936
32183
  } catch {
31937
32184
  console.error("Your ~/.claude/settings.json appears malformed.");
31938
32185
  if (purge) {
@@ -31977,8 +32224,8 @@ async function runClaudeUninstall(flags = []) {
31977
32224
  removed++;
31978
32225
  }
31979
32226
  }
31980
- if (existsSync34(claudeJsonPath)) {
31981
- const raw = readFileSync29(claudeJsonPath, "utf8");
32227
+ if (existsSync35(claudeJsonPath)) {
32228
+ const raw = readFileSync30(claudeJsonPath, "utf8");
31982
32229
  if (raw.length > 1e6) {
31983
32230
  console.error("claude.json exceeds 1 MB \u2014 skipping parse.");
31984
32231
  } else {
@@ -32007,14 +32254,14 @@ async function runClaudeUninstall(flags = []) {
32007
32254
  }
32008
32255
  }
32009
32256
  }
32010
- const skillsDir = path48.join(claudeDir, "skills");
32011
- if (existsSync34(skillsDir)) {
32257
+ const skillsDir = path49.join(claudeDir, "skills");
32258
+ if (existsSync35(skillsDir)) {
32012
32259
  let skillCount = 0;
32013
32260
  try {
32014
32261
  const entries = readdirSync10(skillsDir);
32015
32262
  for (const entry of entries) {
32016
32263
  if (entry.startsWith("exe")) {
32017
- const fullPath = path48.join(skillsDir, entry);
32264
+ const fullPath = path49.join(skillsDir, entry);
32018
32265
  if (!dryRun) rmSync(fullPath, { recursive: true, force: true });
32019
32266
  skillCount++;
32020
32267
  }
@@ -32026,9 +32273,9 @@ async function runClaudeUninstall(flags = []) {
32026
32273
  removed++;
32027
32274
  }
32028
32275
  }
32029
- const claudeMdPath = path48.join(claudeDir, "CLAUDE.md");
32030
- if (existsSync34(claudeMdPath)) {
32031
- const content = readFileSync29(claudeMdPath, "utf8");
32276
+ const claudeMdPath = path49.join(claudeDir, "CLAUDE.md");
32277
+ if (existsSync35(claudeMdPath)) {
32278
+ const content = readFileSync30(claudeMdPath, "utf8");
32032
32279
  const startMarker = "<!-- exe-os:orchestration-start -->";
32033
32280
  const endMarker = "<!-- exe-os:orchestration-end -->";
32034
32281
  const startIdx = content.indexOf(startMarker);
@@ -32040,16 +32287,16 @@ async function runClaudeUninstall(flags = []) {
32040
32287
  removed++;
32041
32288
  }
32042
32289
  }
32043
- const agentsDir = path48.join(claudeDir, "agents");
32044
- if (existsSync34(agentsDir)) {
32290
+ const agentsDir = path49.join(claudeDir, "agents");
32291
+ if (existsSync35(agentsDir)) {
32045
32292
  let agentCount = 0;
32046
32293
  try {
32047
32294
  const entries = readdirSync10(agentsDir).filter((f) => f.endsWith(".md"));
32048
32295
  let knownNames = /* @__PURE__ */ new Set();
32049
- const rosterPath = path48.join(exeOsDir, "exe-employees.json");
32050
- if (existsSync34(rosterPath)) {
32296
+ const rosterPath = path49.join(exeOsDir, "exe-employees.json");
32297
+ if (existsSync35(rosterPath)) {
32051
32298
  try {
32052
- const roster = JSON.parse(readFileSync29(rosterPath, "utf8"));
32299
+ const roster = JSON.parse(readFileSync30(rosterPath, "utf8"));
32053
32300
  knownNames = new Set(roster.map((e) => e.name));
32054
32301
  } catch {
32055
32302
  }
@@ -32057,7 +32304,7 @@ async function runClaudeUninstall(flags = []) {
32057
32304
  for (const entry of entries) {
32058
32305
  const name = entry.replace(/\.md$/, "");
32059
32306
  if (knownNames.has(name)) {
32060
- if (!dryRun) rmSync(path48.join(agentsDir, entry), { force: true });
32307
+ if (!dryRun) rmSync(path49.join(agentsDir, entry), { force: true });
32061
32308
  agentCount++;
32062
32309
  }
32063
32310
  }
@@ -32068,16 +32315,16 @@ async function runClaudeUninstall(flags = []) {
32068
32315
  removed++;
32069
32316
  }
32070
32317
  }
32071
- const projectsDir = path48.join(claudeDir, "projects");
32072
- if (existsSync34(projectsDir)) {
32318
+ const projectsDir = path49.join(claudeDir, "projects");
32319
+ if (existsSync35(projectsDir)) {
32073
32320
  let projectCount = 0;
32074
32321
  try {
32075
32322
  const projects = readdirSync10(projectsDir);
32076
32323
  for (const proj of projects) {
32077
- const projSettings = path48.join(projectsDir, proj, "settings.json");
32078
- if (!existsSync34(projSettings)) continue;
32324
+ const projSettings = path49.join(projectsDir, proj, "settings.json");
32325
+ if (!existsSync35(projSettings)) continue;
32079
32326
  try {
32080
- const pSettings = JSON.parse(readFileSync29(projSettings, "utf8"));
32327
+ const pSettings = JSON.parse(readFileSync30(projSettings, "utf8"));
32081
32328
  let changed = false;
32082
32329
  if (Array.isArray(pSettings.permissions?.allow)) {
32083
32330
  const before = pSettings.permissions.allow.length;
@@ -32111,18 +32358,18 @@ async function runClaudeUninstall(flags = []) {
32111
32358
  };
32112
32359
  const exeBinPath = findExeBin3();
32113
32360
  if (!exeBinPath) throw new Error("exe-os not found in PATH");
32114
- const binDir = path48.dirname(exeBinPath);
32361
+ const binDir = path49.dirname(exeBinPath);
32115
32362
  let symlinkCount = 0;
32116
- const rosterPath = path48.join(exeOsDir, "exe-employees.json");
32117
- if (existsSync34(rosterPath)) {
32118
- const roster = JSON.parse(readFileSync29(rosterPath, "utf8"));
32363
+ const rosterPath = path49.join(exeOsDir, "exe-employees.json");
32364
+ if (existsSync35(rosterPath)) {
32365
+ const roster = JSON.parse(readFileSync30(rosterPath, "utf8"));
32119
32366
  const { DEFAULT_COORDINATOR_TEMPLATE_NAME: DEFAULT_COORDINATOR_TEMPLATE_NAME2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
32120
32367
  const coordinatorName = roster.find((e) => e.role?.toLowerCase() === "coo")?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME2;
32121
32368
  for (const emp of roster) {
32122
32369
  if (emp.name === coordinatorName) continue;
32123
32370
  for (const suffix of ["", "-opencode"]) {
32124
- const linkPath = path48.join(binDir, `${emp.name}${suffix}`);
32125
- if (existsSync34(linkPath)) {
32371
+ const linkPath = path49.join(binDir, `${emp.name}${suffix}`);
32372
+ if (existsSync35(linkPath)) {
32126
32373
  if (!dryRun) rmSync(linkPath, { force: true });
32127
32374
  symlinkCount++;
32128
32375
  }
@@ -32135,7 +32382,7 @@ async function runClaudeUninstall(flags = []) {
32135
32382
  }
32136
32383
  } catch {
32137
32384
  }
32138
- if (purge && existsSync34(exeOsDir)) {
32385
+ if (purge && existsSync35(exeOsDir)) {
32139
32386
  if (!dryRun) {
32140
32387
  process.stdout.write("\x1B[33m\u26A0 This will delete all memories, identities, and agent data.\x1B[0m\n");
32141
32388
  process.stdout.write(" Removing ~/.exe-os...\n");
@@ -32160,7 +32407,7 @@ async function checkForUpdateOnBoot() {
32160
32407
  const config = await loadConfig2();
32161
32408
  if (!config.autoUpdate.checkOnBoot) return;
32162
32409
  const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
32163
- const packageRoot = path48.resolve(
32410
+ const packageRoot = path49.resolve(
32164
32411
  new URL("../..", import.meta.url).pathname
32165
32412
  );
32166
32413
  const result = checkForUpdate2(packageRoot);
@@ -32221,7 +32468,7 @@ async function runActivate(key) {
32221
32468
  const idTemplate = getIdentityTemplate(identityKey);
32222
32469
  if (idTemplate) {
32223
32470
  const idPath = identityPath2(name);
32224
- const dir = path48.dirname(idPath);
32471
+ const dir = path49.dirname(idPath);
32225
32472
  if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
32226
32473
  fs8.writeFileSync(idPath, idTemplate.replace(/^agent_id: \w+/m, `agent_id: ${name}`), "utf-8");
32227
32474
  }
@@ -1272,8 +1272,8 @@ __export(installer_exports, {
1272
1272
  setupTmux: () => setupTmux
1273
1273
  });
1274
1274
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir } from "fs/promises";
1275
- import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync7, copyFileSync, mkdirSync as mkdirSync6 } from "fs";
1276
- import { createHash as createHash2 } from "crypto";
1275
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync7, copyFileSync, mkdirSync as mkdirSync6, chmodSync as chmodSync3 } from "fs";
1276
+ import { createHash as createHash2, randomBytes } from "crypto";
1277
1277
  import path11 from "path";
1278
1278
  import os7 from "os";
1279
1279
  import { execSync as execSync2 } from "child_process";
@@ -1402,6 +1402,55 @@ function detectMcpNameCollisions(homeDir = os7.homedir(), cwd = process.cwd()) {
1402
1402
  }
1403
1403
  return collisions;
1404
1404
  }
1405
+ function readOrCreateDaemonToken(homeDir) {
1406
+ const exeDir = path11.join(homeDir, ".exe-os");
1407
+ const tokenPath = path11.join(exeDir, "exed.token");
1408
+ try {
1409
+ if (existsSync11(tokenPath)) {
1410
+ const token2 = readFileSync9(tokenPath, "utf-8").trim();
1411
+ if (token2) return token2;
1412
+ }
1413
+ } catch {
1414
+ }
1415
+ const token = randomBytes(32).toString("hex");
1416
+ mkdirSync6(exeDir, { recursive: true });
1417
+ writeFileSync7(tokenPath, `${token}
1418
+ `, "utf-8");
1419
+ try {
1420
+ chmodSync3(tokenPath, 384);
1421
+ } catch {
1422
+ }
1423
+ return token;
1424
+ }
1425
+ function buildHttpMcpEntry(homeDir) {
1426
+ const port = process.env.EXE_MCP_PORT || "48739";
1427
+ const token = readOrCreateDaemonToken(homeDir);
1428
+ return {
1429
+ type: "http",
1430
+ url: `http://127.0.0.1:${port}/mcp`,
1431
+ headers: {
1432
+ Authorization: `Bearer ${token}`,
1433
+ "X-Agent-Id": "${AGENT_ID:-exe}",
1434
+ "X-Agent-Role": "${AGENT_ROLE:-COO}"
1435
+ }
1436
+ };
1437
+ }
1438
+ function buildStdioMcpEntry(packageRoot) {
1439
+ return {
1440
+ type: "stdio",
1441
+ command: "node",
1442
+ args: [path11.join(packageRoot, "dist", "mcp", "server.js")],
1443
+ env: {}
1444
+ };
1445
+ }
1446
+ function mcpTransportMode() {
1447
+ const value = process.env.EXE_OS_MCP_TRANSPORT?.trim().toLowerCase();
1448
+ if (value === "stdio") return "stdio";
1449
+ if (value === "http") return "http";
1450
+ const totalGB = os7.totalmem() / (1024 * 1024 * 1024);
1451
+ if (totalGB <= 8) return "stdio";
1452
+ return "http";
1453
+ }
1405
1454
  async function registerMcpServer(packageRoot, homeDir = os7.homedir()) {
1406
1455
  const claudeJsonPath = path11.join(homeDir, ".claude.json");
1407
1456
  let claudeJson = {};
@@ -1415,12 +1464,7 @@ async function registerMcpServer(packageRoot, homeDir = os7.homedir()) {
1415
1464
  if (!claudeJson.mcpServers) {
1416
1465
  claudeJson.mcpServers = {};
1417
1466
  }
1418
- const newEntry = {
1419
- type: "stdio",
1420
- command: "node",
1421
- args: [path11.join(packageRoot, "dist", "mcp", "server.js")],
1422
- env: {}
1423
- };
1467
+ const newEntry = mcpTransportMode() === "stdio" ? buildStdioMcpEntry(packageRoot) : buildHttpMcpEntry(homeDir);
1424
1468
  if (claudeJson.mcpServers[MCP_LEGACY_KEY]) {
1425
1469
  delete claudeJson.mcpServers[MCP_LEGACY_KEY];
1426
1470
  process.stderr.write("exe-os: migrated MCP server key exe-mem \u2192 exe-os\n");
@@ -3608,8 +3608,8 @@ var init_preferences = __esm({
3608
3608
 
3609
3609
  // src/adapters/claude/installer.ts
3610
3610
  import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4, readdir } from "fs/promises";
3611
- import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync6, copyFileSync, mkdirSync as mkdirSync6 } from "fs";
3612
- import { createHash as createHash2 } from "crypto";
3611
+ import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync6, copyFileSync, mkdirSync as mkdirSync6, chmodSync as chmodSync2 } from "fs";
3612
+ import { createHash as createHash2, randomBytes } from "crypto";
3613
3613
  import path12 from "path";
3614
3614
  import os9 from "os";
3615
3615
  import { execSync as execSync5 } from "child_process";
@@ -3597,8 +3597,8 @@ var init_preferences = __esm({
3597
3597
 
3598
3598
  // src/adapters/claude/installer.ts
3599
3599
  import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4, readdir } from "fs/promises";
3600
- import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync6, copyFileSync, mkdirSync as mkdirSync6 } from "fs";
3601
- import { createHash as createHash2 } from "crypto";
3600
+ import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync6, copyFileSync, mkdirSync as mkdirSync6, chmodSync as chmodSync2 } from "fs";
3601
+ import { createHash as createHash2, randomBytes } from "crypto";
3602
3602
  import path12 from "path";
3603
3603
  import os9 from "os";
3604
3604
  import { execSync as execSync5 } from "child_process";
@@ -610,8 +610,8 @@ var init_preferences = __esm({
610
610
 
611
611
  // src/adapters/claude/installer.ts
612
612
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir } from "fs/promises";
613
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync4, copyFileSync, mkdirSync as mkdirSync3 } from "fs";
614
- import { createHash } from "crypto";
613
+ import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync4, copyFileSync, mkdirSync as mkdirSync3, chmodSync as chmodSync2 } from "fs";
614
+ import { createHash, randomBytes } from "crypto";
615
615
  import path6 from "path";
616
616
  import os5 from "os";
617
617
  import { execSync as execSync2 } from "child_process";
@@ -740,6 +740,55 @@ function detectMcpNameCollisions(homeDir = os5.homedir(), cwd = process.cwd()) {
740
740
  }
741
741
  return collisions;
742
742
  }
743
+ function readOrCreateDaemonToken(homeDir) {
744
+ const exeDir = path6.join(homeDir, ".exe-os");
745
+ const tokenPath = path6.join(exeDir, "exed.token");
746
+ try {
747
+ if (existsSync7(tokenPath)) {
748
+ const token2 = readFileSync5(tokenPath, "utf-8").trim();
749
+ if (token2) return token2;
750
+ }
751
+ } catch {
752
+ }
753
+ const token = randomBytes(32).toString("hex");
754
+ mkdirSync3(exeDir, { recursive: true });
755
+ writeFileSync4(tokenPath, `${token}
756
+ `, "utf-8");
757
+ try {
758
+ chmodSync2(tokenPath, 384);
759
+ } catch {
760
+ }
761
+ return token;
762
+ }
763
+ function buildHttpMcpEntry(homeDir) {
764
+ const port = process.env.EXE_MCP_PORT || "48739";
765
+ const token = readOrCreateDaemonToken(homeDir);
766
+ return {
767
+ type: "http",
768
+ url: `http://127.0.0.1:${port}/mcp`,
769
+ headers: {
770
+ Authorization: `Bearer ${token}`,
771
+ "X-Agent-Id": "${AGENT_ID:-exe}",
772
+ "X-Agent-Role": "${AGENT_ROLE:-COO}"
773
+ }
774
+ };
775
+ }
776
+ function buildStdioMcpEntry(packageRoot) {
777
+ return {
778
+ type: "stdio",
779
+ command: "node",
780
+ args: [path6.join(packageRoot, "dist", "mcp", "server.js")],
781
+ env: {}
782
+ };
783
+ }
784
+ function mcpTransportMode() {
785
+ const value = process.env.EXE_OS_MCP_TRANSPORT?.trim().toLowerCase();
786
+ if (value === "stdio") return "stdio";
787
+ if (value === "http") return "http";
788
+ const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
789
+ if (totalGB <= 8) return "stdio";
790
+ return "http";
791
+ }
743
792
  async function registerMcpServer(packageRoot, homeDir = os5.homedir()) {
744
793
  const claudeJsonPath = path6.join(homeDir, ".claude.json");
745
794
  let claudeJson = {};
@@ -753,12 +802,7 @@ async function registerMcpServer(packageRoot, homeDir = os5.homedir()) {
753
802
  if (!claudeJson.mcpServers) {
754
803
  claudeJson.mcpServers = {};
755
804
  }
756
- const newEntry = {
757
- type: "stdio",
758
- command: "node",
759
- args: [path6.join(packageRoot, "dist", "mcp", "server.js")],
760
- env: {}
761
- };
805
+ const newEntry = mcpTransportMode() === "stdio" ? buildStdioMcpEntry(packageRoot) : buildHttpMcpEntry(homeDir);
762
806
  if (claudeJson.mcpServers[MCP_LEGACY_KEY]) {
763
807
  delete claudeJson.mcpServers[MCP_LEGACY_KEY];
764
808
  process.stderr.write("exe-os: migrated MCP server key exe-mem \u2192 exe-os\n");
@@ -1831,7 +1875,7 @@ import {
1831
1875
  readFileSync as readFileSync6,
1832
1876
  writeFileSync as writeFileSync5,
1833
1877
  mkdirSync as mkdirSync4,
1834
- chmodSync as chmodSync2,
1878
+ chmodSync as chmodSync3,
1835
1879
  readdirSync,
1836
1880
  unlinkSync as unlinkSync2
1837
1881
  } from "fs";
@@ -1851,7 +1895,7 @@ function generateSessionWrappers(packageRoot, homeDir) {
1851
1895
  for (const src of candidates) {
1852
1896
  if (existsSync8(src)) {
1853
1897
  writeFileSync5(exeStartDst, readFileSync6(src));
1854
- chmodSync2(exeStartDst, 493);
1898
+ chmodSync3(exeStartDst, 493);
1855
1899
  break;
1856
1900
  }
1857
1901
  }
@@ -1886,11 +1930,11 @@ exec "${exeStartDst}" "$0" "$@"
1886
1930
  for (let n = 1; n <= MAX_N; n++) {
1887
1931
  const wrapperPath = path7.join(binDir, `${emp.name}${n}`);
1888
1932
  writeFileSync5(wrapperPath, wrapperContent);
1889
- chmodSync2(wrapperPath, 493);
1933
+ chmodSync3(wrapperPath, 493);
1890
1934
  created++;
1891
1935
  const codexPath = path7.join(binDir, `${emp.name}${n}-codex`);
1892
1936
  writeFileSync5(codexPath, wrapperContent);
1893
- chmodSync2(codexPath, 493);
1937
+ chmodSync3(codexPath, 493);
1894
1938
  created++;
1895
1939
  }
1896
1940
  }
@@ -1912,7 +1956,7 @@ exec "${exeStartDst}" "$0" "$@"
1912
1956
  exec node "${codexLauncher}" --agent ${emp.name} "$@"
1913
1957
  `;
1914
1958
  writeFileSync5(wrapperPath, content);
1915
- chmodSync2(wrapperPath, 493);
1959
+ chmodSync3(wrapperPath, 493);
1916
1960
  created++;
1917
1961
  }
1918
1962
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askexenow/exe-os",
3
- "version": "0.9.47",
3
+ "version": "0.9.49",
4
4
  "description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",