@askexenow/exe-os 0.8.37 → 0.8.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 (93) hide show
  1. package/README.md +17 -8
  2. package/dist/bin/backfill-conversations.js +112 -70
  3. package/dist/bin/backfill-responses.js +53 -18
  4. package/dist/bin/backfill-vectors.js +43 -16
  5. package/dist/bin/cleanup-stale-review-tasks.js +38 -16
  6. package/dist/bin/cli.js +790 -468
  7. package/dist/bin/exe-agent.js +19 -4
  8. package/dist/bin/exe-assign.js +46 -13
  9. package/dist/bin/exe-boot.js +288 -129
  10. package/dist/bin/exe-call.js +20 -10
  11. package/dist/bin/exe-cloud.js +135 -30
  12. package/dist/bin/exe-dispatch.js +1 -1
  13. package/dist/bin/exe-doctor.js +38 -16
  14. package/dist/bin/exe-export-behaviors.js +43 -21
  15. package/dist/bin/exe-forget.js +39 -17
  16. package/dist/bin/exe-gateway.js +159 -50
  17. package/dist/bin/exe-heartbeat.js +53 -31
  18. package/dist/bin/exe-kill.js +40 -18
  19. package/dist/bin/exe-launch-agent.js +109 -36
  20. package/dist/bin/exe-link.js +196 -87
  21. package/dist/bin/exe-new-employee.js +56 -17
  22. package/dist/bin/exe-pending-messages.js +47 -25
  23. package/dist/bin/exe-pending-notifications.js +38 -16
  24. package/dist/bin/exe-pending-reviews.js +51 -29
  25. package/dist/bin/exe-rename.js +21 -7
  26. package/dist/bin/exe-review.js +41 -13
  27. package/dist/bin/exe-search.js +57 -21
  28. package/dist/bin/exe-session-cleanup.js +67 -31
  29. package/dist/bin/exe-settings.js +63 -2
  30. package/dist/bin/exe-status.js +35 -13
  31. package/dist/bin/exe-team.js +35 -13
  32. package/dist/bin/git-sweep.js +45 -17
  33. package/dist/bin/graph-backfill.js +38 -16
  34. package/dist/bin/graph-export.js +38 -16
  35. package/dist/bin/install.js +10 -1
  36. package/dist/bin/scan-tasks.js +47 -19
  37. package/dist/bin/setup.js +444 -259
  38. package/dist/bin/shard-migrate.js +38 -16
  39. package/dist/bin/wiki-sync.js +40 -17
  40. package/dist/gateway/index.js +113 -48
  41. package/dist/hooks/bug-report-worker.js +66 -39
  42. package/dist/hooks/commit-complete.js +45 -17
  43. package/dist/hooks/error-recall.js +60 -20
  44. package/dist/hooks/exe-heartbeat-hook.js +3 -2
  45. package/dist/hooks/ingest-worker.js +174 -45
  46. package/dist/hooks/ingest.js +74 -28
  47. package/dist/hooks/instructions-loaded.js +46 -17
  48. package/dist/hooks/notification.js +44 -15
  49. package/dist/hooks/post-compact.js +44 -15
  50. package/dist/hooks/pre-compact.js +42 -14
  51. package/dist/hooks/pre-tool-use.js +59 -22
  52. package/dist/hooks/prompt-ingest-worker.js +75 -14
  53. package/dist/hooks/prompt-submit.js +75 -32
  54. package/dist/hooks/response-ingest-worker.js +76 -15
  55. package/dist/hooks/session-end.js +54 -22
  56. package/dist/hooks/session-start.js +57 -20
  57. package/dist/hooks/stop.js +44 -15
  58. package/dist/hooks/subagent-stop.js +44 -15
  59. package/dist/hooks/summary-worker.js +339 -106
  60. package/dist/index.js +94 -23
  61. package/dist/lib/cloud-sync.js +191 -80
  62. package/dist/lib/config.js +4 -1
  63. package/dist/lib/consolidation.js +5 -4
  64. package/dist/lib/database.js +1 -0
  65. package/dist/lib/device-registry.js +2 -1
  66. package/dist/lib/embedder.js +9 -1
  67. package/dist/lib/employee-templates.js +5 -0
  68. package/dist/lib/employees.js +11 -6
  69. package/dist/lib/exe-daemon-client.js +6 -1
  70. package/dist/lib/exe-daemon.js +95 -36
  71. package/dist/lib/hybrid-search.js +57 -21
  72. package/dist/lib/identity-templates.js +16 -7
  73. package/dist/lib/identity.js +1 -1
  74. package/dist/lib/keychain.js +2 -1
  75. package/dist/lib/license.js +56 -6
  76. package/dist/lib/messaging.js +1 -1
  77. package/dist/lib/reminders.js +2 -2
  78. package/dist/lib/schedules.js +38 -16
  79. package/dist/lib/skill-learning.js +1 -1
  80. package/dist/lib/store.js +44 -16
  81. package/dist/lib/tasks.js +1 -1
  82. package/dist/lib/tmux-routing.js +1 -1
  83. package/dist/mcp/server.js +280 -155
  84. package/dist/mcp/tools/complete-reminder.js +1 -1
  85. package/dist/mcp/tools/create-task.js +14 -6
  86. package/dist/mcp/tools/deactivate-behavior.js +2 -2
  87. package/dist/mcp/tools/list-reminders.js +1 -1
  88. package/dist/mcp/tools/list-tasks.js +36 -28
  89. package/dist/mcp/tools/send-message.js +1 -1
  90. package/dist/mcp/tools/update-task.js +1 -1
  91. package/dist/runtime/index.js +42 -8
  92. package/dist/tui/App.js +220 -99
  93. package/package.json +5 -3
package/dist/bin/cli.js CHANGED
@@ -42,7 +42,7 @@ __export(config_exports, {
42
42
  migrateConfig: () => migrateConfig,
43
43
  saveConfig: () => saveConfig
44
44
  });
45
- import { readFile, writeFile, mkdir } from "fs/promises";
45
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
46
46
  import { readFileSync, existsSync, renameSync } from "fs";
47
47
  import path from "path";
48
48
  import os from "os";
@@ -168,6 +168,9 @@ async function saveConfig(config) {
168
168
  await mkdir(dir, { recursive: true });
169
169
  const configPath = path.join(dir, "config.json");
170
170
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
171
+ if (config.cloud?.apiKey) {
172
+ await chmod(configPath, 384);
173
+ }
171
174
  }
172
175
  async function loadConfigFrom(configPath) {
173
176
  const raw = await readFile(configPath, "utf-8");
@@ -356,15 +359,20 @@ function addEmployee(employees, employee) {
356
359
  }
357
360
  return [...employees, normalized];
358
361
  }
362
+ function findExeBin() {
363
+ try {
364
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
365
+ } catch {
366
+ return null;
367
+ }
368
+ }
359
369
  function registerBinSymlinks(name) {
360
370
  const created = [];
361
371
  const skipped = [];
362
372
  const errors = [];
363
- let exeBinPath;
364
- try {
365
- exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
366
- } catch {
367
- errors.push("Could not find 'exe' in PATH");
373
+ const exeBinPath = findExeBin();
374
+ if (!exeBinPath) {
375
+ errors.push("Could not find 'exe-os' in PATH");
368
376
  return { created, skipped, errors };
369
377
  }
370
378
  const binDir = path2.dirname(exeBinPath);
@@ -621,6 +629,10 @@ async function mergeHooks(packageRoot, homeDir = os3.homedir()) {
621
629
  if (!settings.hooks) {
622
630
  settings.hooks = {};
623
631
  }
632
+ if (settings.hooks && typeof settings.hooks !== "object") {
633
+ console.warn("[exe-os] Unexpected hooks schema in settings.json \u2014 skipping hook installation");
634
+ return { added: 0, skipped: 0 };
635
+ }
624
636
  const hooksToRegister = [
625
637
  {
626
638
  event: "PostToolUse",
@@ -783,6 +795,11 @@ async function mergeHooks(packageRoot, homeDir = os3.homedir()) {
783
795
  if (!settings.hooks[event]) {
784
796
  settings.hooks[event] = [];
785
797
  }
798
+ if (!Array.isArray(settings.hooks[event])) {
799
+ console.warn(`[exe-os] Hook event "${event}" has unexpected structure \u2014 skipping`);
800
+ skipped++;
801
+ continue;
802
+ }
786
803
  const existing = settings.hooks[event];
787
804
  const correctCommand = group.hooks[0]?.command ?? "";
788
805
  const alreadyCorrect = existing.some(
@@ -1087,6 +1104,7 @@ async function ensureSchema() {
1087
1104
  const client = getRawClient();
1088
1105
  await client.execute("PRAGMA journal_mode = WAL");
1089
1106
  await client.execute("PRAGMA busy_timeout = 30000");
1107
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
1090
1108
  try {
1091
1109
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
1092
1110
  } catch {
@@ -1903,12 +1921,13 @@ __export(keychain_exports, {
1903
1921
  importMnemonic: () => importMnemonic,
1904
1922
  setMasterKey: () => setMasterKey
1905
1923
  });
1906
- import { readFile as readFile4, writeFile as writeFile4, unlink, mkdir as mkdir4, chmod } from "fs/promises";
1924
+ import { readFile as readFile4, writeFile as writeFile4, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
1907
1925
  import { existsSync as existsSync5 } from "fs";
1908
1926
  import path5 from "path";
1927
+ import os4 from "os";
1909
1928
  import crypto from "crypto";
1910
1929
  function getKeyDir() {
1911
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(process.env.HOME ?? "/tmp", ".exe-os");
1930
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
1912
1931
  }
1913
1932
  function getKeyPath() {
1914
1933
  return path5.join(getKeyDir(), "master.key");
@@ -1956,7 +1975,7 @@ async function setMasterKey(key) {
1956
1975
  await mkdir4(dir, { recursive: true });
1957
1976
  const keyPath = getKeyPath();
1958
1977
  await writeFile4(keyPath, b64 + "\n", "utf-8");
1959
- await chmod(keyPath, 384);
1978
+ await chmod2(keyPath, 384);
1960
1979
  }
1961
1980
  async function deleteMasterKey() {
1962
1981
  const keytar = await tryKeytar();
@@ -2054,7 +2073,7 @@ __export(shard_manager_exports, {
2054
2073
  shardExists: () => shardExists
2055
2074
  });
2056
2075
  import path6 from "path";
2057
- import { existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
2076
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync } from "fs";
2058
2077
  import { createClient as createClient2 } from "@libsql/client";
2059
2078
  function initShardManager(encryptionKey) {
2060
2079
  _encryptionKey = encryptionKey;
@@ -2093,8 +2112,7 @@ function shardExists(projectName) {
2093
2112
  }
2094
2113
  function listShards() {
2095
2114
  if (!existsSync6(SHARDS_DIR)) return [];
2096
- const { readdirSync: readdirSync5 } = __require("fs");
2097
- return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2115
+ return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2098
2116
  }
2099
2117
  async function ensureShardSchema(client) {
2100
2118
  await client.execute("PRAGMA journal_mode = WAL");
@@ -2299,6 +2317,28 @@ __export(store_exports, {
2299
2317
  vectorToBlob: () => vectorToBlob,
2300
2318
  writeMemory: () => writeMemory
2301
2319
  });
2320
+ function isBusyError2(err) {
2321
+ if (err instanceof Error) {
2322
+ const msg = err.message.toLowerCase();
2323
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
2324
+ }
2325
+ return false;
2326
+ }
2327
+ async function retryOnBusy2(fn, label) {
2328
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
2329
+ try {
2330
+ return await fn();
2331
+ } catch (err) {
2332
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
2333
+ process.stderr.write(
2334
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
2335
+ `
2336
+ );
2337
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
2338
+ }
2339
+ }
2340
+ throw new Error("unreachable");
2341
+ }
2302
2342
  async function initStore(options) {
2303
2343
  if (_flushTimer !== null) {
2304
2344
  clearInterval(_flushTimer);
@@ -2327,14 +2367,17 @@ async function initStore(options) {
2327
2367
  dbPath,
2328
2368
  encryptionKey: hexKey
2329
2369
  });
2330
- await ensureSchema();
2370
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
2331
2371
  try {
2332
2372
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
2333
2373
  initShardManager2(hexKey);
2334
2374
  } catch {
2335
2375
  }
2336
2376
  const client = getClient();
2337
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
2377
+ const vResult = await retryOnBusy2(
2378
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
2379
+ "version-query"
2380
+ );
2338
2381
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
2339
2382
  }
2340
2383
  function classifyTier(record) {
@@ -2377,6 +2420,12 @@ async function writeMemory(record) {
2377
2420
  supersedes_id: record.supersedes_id ?? null
2378
2421
  };
2379
2422
  _pendingRecords.push(dbRow);
2423
+ const MAX_PENDING = 1e3;
2424
+ if (_pendingRecords.length > MAX_PENDING) {
2425
+ const dropped = _pendingRecords.length - MAX_PENDING;
2426
+ _pendingRecords = _pendingRecords.slice(-MAX_PENDING);
2427
+ console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
2428
+ }
2380
2429
  if (_flushTimer === null) {
2381
2430
  _flushTimer = setInterval(() => {
2382
2431
  void flushBatch();
@@ -2708,7 +2757,7 @@ async function getMemoryCardinality(agentId) {
2708
2757
  return 0;
2709
2758
  }
2710
2759
  }
2711
- var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
2760
+ var INIT_MAX_RETRIES, INIT_RETRY_DELAY_MS, _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
2712
2761
  var init_store = __esm({
2713
2762
  "src/lib/store.ts"() {
2714
2763
  "use strict";
@@ -2716,6 +2765,8 @@ var init_store = __esm({
2716
2765
  init_database();
2717
2766
  init_keychain();
2718
2767
  init_config();
2768
+ INIT_MAX_RETRIES = 3;
2769
+ INIT_RETRY_DELAY_MS = 1e3;
2719
2770
  _pendingRecords = [];
2720
2771
  _batchSize = 20;
2721
2772
  _flushIntervalMs = 1e4;
@@ -2734,6 +2785,10 @@ import path7 from "path";
2734
2785
  import { fileURLToPath as fileURLToPath2 } from "url";
2735
2786
  function handleData(chunk) {
2736
2787
  _buffer += chunk.toString();
2788
+ if (_buffer.length > MAX_BUFFER) {
2789
+ _buffer = "";
2790
+ return;
2791
+ }
2737
2792
  let newlineIdx;
2738
2793
  while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
2739
2794
  const line = _buffer.slice(0, newlineIdx).trim();
@@ -3041,7 +3096,7 @@ function disconnectClient() {
3041
3096
  entry.resolve({ error: "Client disconnected" });
3042
3097
  }
3043
3098
  }
3044
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending;
3099
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
3045
3100
  var init_exe_daemon_client = __esm({
3046
3101
  "src/lib/exe-daemon-client.ts"() {
3047
3102
  "use strict";
@@ -3058,6 +3113,7 @@ var init_exe_daemon_client = __esm({
3058
3113
  _requestCount = 0;
3059
3114
  HEALTH_CHECK_INTERVAL = 100;
3060
3115
  _pending = /* @__PURE__ */ new Map();
3116
+ MAX_BUFFER = 1e7;
3061
3117
  }
3062
3118
  });
3063
3119
 
@@ -3095,7 +3151,8 @@ import { parseArgs } from "util";
3095
3151
  async function findJsonlFiles(sinceDate, projectFilter) {
3096
3152
  const projectsDir = path8.join(homedir(), ".claude", "projects");
3097
3153
  const files = [];
3098
- async function walk(dir) {
3154
+ async function walk(dir, depth = 0) {
3155
+ if (depth > MAX_WALK_DEPTH) return;
3099
3156
  let entries;
3100
3157
  try {
3101
3158
  entries = await readdir2(dir, { withFileTypes: true });
@@ -3106,7 +3163,7 @@ async function findJsonlFiles(sinceDate, projectFilter) {
3106
3163
  const full = path8.join(dir, entry.name);
3107
3164
  if (entry.isDirectory()) {
3108
3165
  if (entry.name === "subagents" || entry.name === "tool-results") continue;
3109
- await walk(full);
3166
+ await walk(full, depth + 1);
3110
3167
  } else if (entry.name.endsWith(".jsonl")) {
3111
3168
  try {
3112
3169
  const s = await stat(full);
@@ -3347,65 +3404,72 @@ async function backfillConversations(options) {
3347
3404
  process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files to process
3348
3405
  `);
3349
3406
  process.env.EXE_EMBED_PRIORITY = "low";
3350
- for (const file of files) {
3351
- stats.filesScanned++;
3352
- if (existingPaths.has(file)) {
3353
- stats.skippedDedup++;
3354
- continue;
3355
- }
3356
- const conv = await parseConversation(file);
3357
- if (conv.totalMessages < MIN_MESSAGES) {
3358
- stats.skippedTooShort++;
3359
- continue;
3360
- }
3361
- const summary = buildSummary(conv);
3362
- if (options.dryRun) {
3363
- process.stdout.write(`
3407
+ const BATCH_SIZE = 50;
3408
+ const MAX_DEDUP_SIZE = 5e4;
3409
+ for (let batchStart = 0; batchStart < files.length; batchStart += BATCH_SIZE) {
3410
+ const batch = files.slice(batchStart, batchStart + BATCH_SIZE);
3411
+ for (const file of batch) {
3412
+ stats.filesScanned++;
3413
+ if (existingPaths.size < MAX_DEDUP_SIZE && existingPaths.has(file)) {
3414
+ stats.skippedDedup++;
3415
+ continue;
3416
+ }
3417
+ const conv = await parseConversation(file);
3418
+ if (conv.totalMessages < MIN_MESSAGES) {
3419
+ stats.skippedTooShort++;
3420
+ continue;
3421
+ }
3422
+ const summary = buildSummary(conv);
3423
+ if (options.dryRun) {
3424
+ process.stdout.write(`
3364
3425
  \u2500\u2500\u2500 ${file} \u2500\u2500\u2500
3365
3426
  `);
3366
- process.stdout.write(`Project: ${conv.projectName} | Messages: ${conv.totalMessages}`);
3367
- process.stdout.write(` | Tools: ${Object.keys(conv.toolCounts).length}`);
3368
- process.stdout.write(` | Files: ${conv.filesTouched.size}
3427
+ process.stdout.write(`Project: ${conv.projectName} | Messages: ${conv.totalMessages}`);
3428
+ process.stdout.write(` | Tools: ${Object.keys(conv.toolCounts).length}`);
3429
+ process.stdout.write(` | Files: ${conv.filesTouched.size}
3369
3430
  `);
3370
- const firstPrompt = conv.userMessages[0];
3371
- if (firstPrompt) {
3372
- process.stdout.write(`First prompt: ${firstPrompt.slice(0, 120)}
3431
+ const firstPrompt = conv.userMessages[0];
3432
+ if (firstPrompt) {
3433
+ process.stdout.write(`First prompt: ${firstPrompt.slice(0, 120)}
3373
3434
  `);
3435
+ }
3436
+ stats.conversationsStored++;
3437
+ continue;
3438
+ }
3439
+ let vector = null;
3440
+ if (daemonConnected) {
3441
+ try {
3442
+ vector = await embedViaClient(summary, "low");
3443
+ if (!vector) stats.embedFailed++;
3444
+ } catch {
3445
+ stats.embedFailed++;
3446
+ }
3447
+ }
3448
+ await writeMemory({
3449
+ id: crypto2.randomUUID(),
3450
+ agent_id: conv.agentId,
3451
+ agent_role: conv.agentId === "exe" ? "COO" : "specialist",
3452
+ session_id: conv.sessionId,
3453
+ timestamp: conv.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
3454
+ tool_name: TOOL_NAME,
3455
+ project_name: conv.projectName,
3456
+ has_error: conv.errorCount > 0,
3457
+ raw_text: summary,
3458
+ vector,
3459
+ source_path: file,
3460
+ source_type: "conversation"
3461
+ });
3462
+ if (existingPaths.size < MAX_DEDUP_SIZE) {
3463
+ existingPaths.add(file);
3374
3464
  }
3375
3465
  stats.conversationsStored++;
3376
- continue;
3377
- }
3378
- let vector = null;
3379
- if (daemonConnected) {
3380
- try {
3381
- vector = await embedViaClient(summary, "low");
3382
- if (!vector) stats.embedFailed++;
3383
- } catch {
3384
- stats.embedFailed++;
3385
- }
3386
- }
3387
- await writeMemory({
3388
- id: crypto2.randomUUID(),
3389
- agent_id: conv.agentId,
3390
- agent_role: conv.agentId === "exe" ? "COO" : "specialist",
3391
- session_id: conv.sessionId,
3392
- timestamp: conv.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
3393
- tool_name: TOOL_NAME,
3394
- project_name: conv.projectName,
3395
- has_error: conv.errorCount > 0,
3396
- raw_text: summary,
3397
- vector,
3398
- source_path: file,
3399
- source_type: "conversation"
3400
- });
3401
- existingPaths.add(file);
3402
- stats.conversationsStored++;
3403
- if (stats.filesScanned % 50 === 0) {
3404
- process.stderr.write(
3405
- `[backfill-conversations] Progress: ${stats.filesScanned}/${files.length} files, ${stats.conversationsStored} stored
3466
+ if (stats.filesScanned % 50 === 0) {
3467
+ process.stderr.write(
3468
+ `[backfill-conversations] Progress: ${stats.filesScanned}/${files.length} files, ${stats.conversationsStored} stored
3406
3469
  `
3407
- );
3408
- await flushBatch();
3470
+ );
3471
+ await flushBatch();
3472
+ }
3409
3473
  }
3410
3474
  }
3411
3475
  if (!options.dryRun) {
@@ -3417,7 +3481,7 @@ async function backfillConversations(options) {
3417
3481
  );
3418
3482
  return stats;
3419
3483
  }
3420
- var TOOL_NAME, MIN_MESSAGES, MAX_SUMMARY_LENGTH;
3484
+ var TOOL_NAME, MIN_MESSAGES, MAX_SUMMARY_LENGTH, MAX_WALK_DEPTH;
3421
3485
  var init_backfill_conversations = __esm({
3422
3486
  "src/bin/backfill-conversations.ts"() {
3423
3487
  "use strict";
@@ -3428,6 +3492,7 @@ var init_backfill_conversations = __esm({
3428
3492
  TOOL_NAME = "backfill-conversation";
3429
3493
  MIN_MESSAGES = 3;
3430
3494
  MAX_SUMMARY_LENGTH = 4e3;
3495
+ MAX_WALK_DEPTH = 10;
3431
3496
  if (isMainModule(import.meta.url)) {
3432
3497
  const { values } = parseArgs({
3433
3498
  options: {
@@ -3463,6 +3528,7 @@ __export(employee_templates_exports, {
3463
3528
  buildCustomEmployeePrompt: () => buildCustomEmployeePrompt,
3464
3529
  getSessionPrompt: () => getSessionPrompt,
3465
3530
  getTemplate: () => getTemplate,
3531
+ getTemplateByRole: () => getTemplateByRole,
3466
3532
  personalizePrompt: () => personalizePrompt,
3467
3533
  renderClientCOOTemplate: () => renderClientCOOTemplate
3468
3534
  });
@@ -3478,6 +3544,10 @@ function buildCustomEmployeePrompt(name, role) {
3478
3544
  function getTemplate(name) {
3479
3545
  return TEMPLATES[name];
3480
3546
  }
3547
+ function getTemplateByRole(role) {
3548
+ const lower = role.toLowerCase();
3549
+ return Object.values(TEMPLATES).find((t) => t.role.toLowerCase() === lower);
3550
+ }
3481
3551
  function personalizePrompt(prompt, templateName, actualName) {
3482
3552
  if (templateName === actualName) return prompt;
3483
3553
  const escaped = templateName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -4201,9 +4271,17 @@ async function renameEmployee(oldName, newName, opts = {}) {
4201
4271
  return { success: false, error: err instanceof Error ? err.message : String(err) };
4202
4272
  }
4203
4273
  }
4274
+ function findExeBin2() {
4275
+ try {
4276
+ return execSync2(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
4277
+ } catch {
4278
+ return null;
4279
+ }
4280
+ }
4204
4281
  function removeOldSymlinks(name) {
4205
4282
  try {
4206
- const exeBinPath = execSync2("which exe", { encoding: "utf-8" }).trim();
4283
+ const exeBinPath = findExeBin2();
4284
+ if (!exeBinPath) return;
4207
4285
  const binDir = path9.dirname(exeBinPath);
4208
4286
  for (const suffix of ["", "-opencode"]) {
4209
4287
  const linkPath = path9.join(binDir, `${name}${suffix}`);
@@ -4262,48 +4340,67 @@ async function downloadModel(opts) {
4262
4340
  const tmpPath = destPath + ".tmp";
4263
4341
  await mkdir5(destDir, { recursive: true });
4264
4342
  if (existsSync9(destPath)) {
4265
- const hash2 = await fileHash(destPath);
4266
- if (hash2 === EXPECTED_SHA256) {
4343
+ const hash = await fileHash(destPath);
4344
+ if (hash === EXPECTED_SHA256) {
4267
4345
  return destPath;
4268
4346
  }
4269
4347
  }
4270
- if (existsSync9(tmpPath)) unlinkSync3(tmpPath);
4271
- const response = await fetchFn(GGUF_URL, { redirect: "follow" });
4272
- if (!response.ok || !response.body) {
4273
- throw new Error(`Download failed: HTTP ${response.status}`);
4274
- }
4275
- const contentLength = Number(response.headers.get("content-length") ?? EXPECTED_SIZE);
4348
+ const MAX_RETRIES2 = 3;
4349
+ const DOWNLOAD_TIMEOUT_MS = 3e5;
4350
+ let lastErr;
4276
4351
  let downloaded = 0;
4277
- const hash = createHash("sha256");
4278
- const fileStream = createWriteStream(tmpPath);
4279
- const reader = response.body.getReader();
4280
- try {
4281
- while (true) {
4282
- const { done, value } = await reader.read();
4283
- if (done) break;
4284
- if (!fileStream.write(value)) {
4285
- await new Promise((resolve) => fileStream.once("drain", resolve));
4352
+ for (let attempt = 1; attempt <= MAX_RETRIES2; attempt++) {
4353
+ try {
4354
+ if (existsSync9(tmpPath)) unlinkSync3(tmpPath);
4355
+ const response = await fetchFn(GGUF_URL, {
4356
+ redirect: "follow",
4357
+ signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
4358
+ });
4359
+ if (!response.ok || !response.body) {
4360
+ throw new Error(`Download failed: HTTP ${response.status}`);
4361
+ }
4362
+ const contentLength = Number(response.headers.get("content-length") ?? EXPECTED_SIZE);
4363
+ const hash = createHash("sha256");
4364
+ const fileStream = createWriteStream(tmpPath);
4365
+ const reader = response.body.getReader();
4366
+ try {
4367
+ while (true) {
4368
+ const { done, value } = await reader.read();
4369
+ if (done) break;
4370
+ if (!fileStream.write(value)) {
4371
+ await new Promise((resolve) => fileStream.once("drain", resolve));
4372
+ }
4373
+ hash.update(value);
4374
+ downloaded += value.byteLength;
4375
+ onProgress?.(downloaded, contentLength);
4376
+ }
4377
+ } finally {
4378
+ fileStream.end();
4379
+ await new Promise((resolve, reject) => {
4380
+ fileStream.on("finish", resolve);
4381
+ fileStream.on("error", reject);
4382
+ });
4383
+ }
4384
+ const actualHash = hash.digest("hex");
4385
+ if (actualHash !== EXPECTED_SHA256) {
4386
+ unlinkSync3(tmpPath);
4387
+ throw new Error(
4388
+ `SHA256 mismatch: expected ${EXPECTED_SHA256}, got ${actualHash}`
4389
+ );
4390
+ }
4391
+ renameSync3(tmpPath, destPath);
4392
+ return destPath;
4393
+ } catch (err) {
4394
+ lastErr = err instanceof Error ? err : new Error(String(err));
4395
+ if (attempt < MAX_RETRIES2) {
4396
+ process.stderr.write(`
4397
+ Download attempt ${attempt} failed, retrying...
4398
+ `);
4399
+ if (existsSync9(tmpPath)) unlinkSync3(tmpPath);
4286
4400
  }
4287
- hash.update(value);
4288
- downloaded += value.byteLength;
4289
- onProgress?.(downloaded, contentLength);
4290
4401
  }
4291
- } finally {
4292
- fileStream.end();
4293
- await new Promise((resolve, reject) => {
4294
- fileStream.on("finish", resolve);
4295
- fileStream.on("error", reject);
4296
- });
4297
4402
  }
4298
- const actualHash = hash.digest("hex");
4299
- if (actualHash !== EXPECTED_SHA256) {
4300
- unlinkSync3(tmpPath);
4301
- throw new Error(
4302
- `SHA256 mismatch: expected ${EXPECTED_SHA256}, got ${actualHash}`
4303
- );
4304
- }
4305
- renameSync3(tmpPath, destPath);
4306
- return destPath;
4403
+ throw lastErr;
4307
4404
  }
4308
4405
  async function fileHash(filePath) {
4309
4406
  return new Promise((resolve, reject) => {
@@ -4363,8 +4460,8 @@ async function embedDirect(text) {
4363
4460
  const llamaCpp = await import("node-llama-cpp");
4364
4461
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4365
4462
  const { existsSync: existsSync22 } = await import("fs");
4366
- const path32 = await import("path");
4367
- const modelPath = path32.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4463
+ const path34 = await import("path");
4464
+ const modelPath = path34.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4368
4465
  if (!existsSync22(modelPath)) {
4369
4466
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
4370
4467
  }
@@ -4406,12 +4503,22 @@ __export(license_exports, {
4406
4503
  loadLicense: () => loadLicense,
4407
4504
  mirrorLicenseKey: () => mirrorLicenseKey,
4408
4505
  saveLicense: () => saveLicense,
4506
+ startLicenseRevalidation: () => startLicenseRevalidation,
4507
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
4409
4508
  validateLicense: () => validateLicense
4410
4509
  });
4411
4510
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync3 } from "fs";
4412
4511
  import { randomUUID as randomUUID2 } from "crypto";
4413
4512
  import path11 from "path";
4414
4513
  import { jwtVerify, importSPKI } from "jose";
4514
+ async function fetchRetry(url, init) {
4515
+ try {
4516
+ return await fetch(url, init);
4517
+ } catch {
4518
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
4519
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
4520
+ }
4521
+ }
4415
4522
  function loadDeviceId() {
4416
4523
  const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
4417
4524
  try {
@@ -4443,7 +4550,7 @@ function loadLicense() {
4443
4550
  }
4444
4551
  function saveLicense(apiKey) {
4445
4552
  mkdirSync3(EXE_AI_DIR, { recursive: true });
4446
- writeFileSync2(LICENSE_PATH, apiKey.trim(), "utf8");
4553
+ writeFileSync2(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
4447
4554
  }
4448
4555
  async function verifyLicenseJwt(token) {
4449
4556
  try {
@@ -4495,7 +4602,7 @@ function cacheResponse(token) {
4495
4602
  async function validateLicense(apiKey, deviceId) {
4496
4603
  const did = deviceId ?? loadDeviceId();
4497
4604
  try {
4498
- const res = await fetch(`${API_BASE}/auth/activate`, {
4605
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4499
4606
  method: "POST",
4500
4607
  headers: { "Content-Type": "application/json" },
4501
4608
  body: JSON.stringify({ apiKey, deviceId: did }),
@@ -4530,14 +4637,23 @@ async function validateLicense(apiKey, deviceId) {
4530
4637
  } catch {
4531
4638
  const cached = await getCachedLicense();
4532
4639
  if (cached) return cached;
4533
- return FREE_LICENSE;
4640
+ return { ...FREE_LICENSE, valid: false, error: "offline" };
4641
+ }
4642
+ }
4643
+ function getCacheAgeMs() {
4644
+ try {
4645
+ const { statSync: statSync2 } = __require("fs");
4646
+ const s = statSync2(CACHE_PATH);
4647
+ return Date.now() - s.mtimeMs;
4648
+ } catch {
4649
+ return Infinity;
4534
4650
  }
4535
4651
  }
4536
4652
  async function checkLicense() {
4537
4653
  const key = loadLicense();
4538
4654
  if (!key) return FREE_LICENSE;
4539
4655
  const cached = await getCachedLicense();
4540
- if (cached) return cached;
4656
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
4541
4657
  const deviceId = loadDeviceId();
4542
4658
  return validateLicense(key, deviceId);
4543
4659
  }
@@ -4577,7 +4693,7 @@ async function assertVpsLicense(opts) {
4577
4693
  let explicitRejection = false;
4578
4694
  let transientFailure = false;
4579
4695
  try {
4580
- const res = await fetch(`${API_BASE}/auth/activate`, {
4696
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4581
4697
  method: "POST",
4582
4698
  headers: { "Content-Type": "application/json" },
4583
4699
  body: JSON.stringify({ apiKey, deviceId }),
@@ -4658,7 +4774,28 @@ async function assertVpsLicense(opts) {
4658
4774
  `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
4659
4775
  );
4660
4776
  }
4661
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
4777
+ function startLicenseRevalidation(intervalMs = 36e5) {
4778
+ if (_revalTimer) return;
4779
+ _revalTimer = setInterval(async () => {
4780
+ try {
4781
+ const license = await checkLicense();
4782
+ if (!license.valid) {
4783
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
4784
+ }
4785
+ } catch {
4786
+ }
4787
+ }, intervalMs);
4788
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
4789
+ _revalTimer.unref();
4790
+ }
4791
+ }
4792
+ function stopLicenseRevalidation() {
4793
+ if (_revalTimer) {
4794
+ clearInterval(_revalTimer);
4795
+ _revalTimer = null;
4796
+ }
4797
+ }
4798
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
4662
4799
  var init_license = __esm({
4663
4800
  "src/lib/license.ts"() {
4664
4801
  "use strict";
@@ -4667,6 +4804,7 @@ var init_license = __esm({
4667
4804
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
4668
4805
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
4669
4806
  API_BASE = "https://askexe.com/cloud";
4807
+ RETRY_DELAY_MS = 500;
4670
4808
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4671
4809
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
4672
4810
  4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
@@ -4688,6 +4826,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
4688
4826
  employeeLimit: 1,
4689
4827
  memoryLimit: 5e3
4690
4828
  };
4829
+ CACHE_MAX_AGE_MS = 36e5;
4830
+ _revalTimer = null;
4691
4831
  }
4692
4832
  });
4693
4833
 
@@ -4701,7 +4841,7 @@ __export(identity_exports, {
4701
4841
  updateIdentity: () => updateIdentity
4702
4842
  });
4703
4843
  import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
4704
- import { readdirSync } from "fs";
4844
+ import { readdirSync as readdirSync2 } from "fs";
4705
4845
  import path12 from "path";
4706
4846
  import { createHash as createHash2 } from "crypto";
4707
4847
  function ensureDir() {
@@ -4783,7 +4923,7 @@ async function updateIdentity(agentId, content, updatedBy) {
4783
4923
  }
4784
4924
  function listIdentities() {
4785
4925
  ensureDir();
4786
- const files = readdirSync(IDENTITY_DIR).filter((f) => f.endsWith(".md"));
4926
+ const files = readdirSync2(IDENTITY_DIR).filter((f) => f.endsWith(".md"));
4787
4927
  const results = [];
4788
4928
  for (const file of files) {
4789
4929
  const agentId = file.replace(".md", "");
@@ -4830,6 +4970,7 @@ var init_identity = __esm({
4830
4970
  var identity_templates_exports = {};
4831
4971
  __export(identity_templates_exports, {
4832
4972
  IDENTITY_TEMPLATES: () => IDENTITY_TEMPLATES,
4973
+ PLAN_MODE_COMPAT: () => PLAN_MODE_COMPAT,
4833
4974
  POST_WORK_CHECKLIST: () => POST_WORK_CHECKLIST,
4834
4975
  getTemplate: () => getTemplate2,
4835
4976
  getTemplateForTitle: () => getTemplateForTitle
@@ -4849,10 +4990,18 @@ function getTemplateForTitle(title) {
4849
4990
  if (t.includes("review") || t.includes("audit") || t.includes("qa")) return IDENTITY_TEMPLATES["staff-code-reviewer"];
4850
4991
  return null;
4851
4992
  }
4852
- var POST_WORK_CHECKLIST, IDENTITY_TEMPLATES;
4993
+ var PLAN_MODE_COMPAT, POST_WORK_CHECKLIST, IDENTITY_TEMPLATES;
4853
4994
  var init_identity_templates = __esm({
4854
4995
  "src/lib/identity-templates.ts"() {
4855
4996
  "use strict";
4997
+ PLAN_MODE_COMPAT = `
4998
+ ## Plan Mode Compatibility
4999
+ If tool execution is unavailable (e.g., CC plan mode), switch to planning:
5000
+ - Reason about the task and create a written plan
5001
+ - Document what tools you would call and with what parameters
5002
+ - Output structured text that can be acted on when tools become available
5003
+ Do not repeatedly attempt tool calls that fail \u2014 switch to planning mode.
5004
+ `;
4856
5005
  POST_WORK_CHECKLIST = `
4857
5006
  5. Check for pending reviews (list_tasks status='needs_review' where you are reviewer) \u2014 reviews are work, process before new tasks
4858
5007
  6. Check for blocked tasks (list_tasks status='blocked') \u2014 can you unblock it? Do it now. Can't? Escalate to exe immediately.
@@ -4932,7 +5081,7 @@ Never say "I have no memories" without first searching broadly. Your memory may
4932
5081
  - **update_identity** \u2014 rewrite any agent's identity when role/responsibilities change (exe/founder only)
4933
5082
  - **get_identity** \u2014 read any agent's identity for coordination
4934
5083
  - **send_message** \u2014 direct intercom to employees
4935
-
5084
+ ${PLAN_MODE_COMPAT}
4936
5085
  ## Completion Workflow
4937
5086
 
4938
5087
  1. Read the task file and verify the deliverable matches the brief
@@ -5003,7 +5152,7 @@ You are \${agent_id}. CTO. You hold deep context on the entire codebase, archite
5003
5152
  - **store_behavior** \u2014 record corrections for engineers (p0 = always injected)
5004
5153
  - **get_identity** \u2014 read any agent's identity for review context
5005
5154
  - **query_relationships** \u2014 GraphRAG entity connections for architecture analysis
5006
-
5155
+ ${PLAN_MODE_COMPAT}
5007
5156
  ## Completion Workflow
5008
5157
 
5009
5158
  1. Read ARCHITECTURE.md before starting work on any repo
@@ -5070,7 +5219,7 @@ You are \${agent_id}. CMO. You hold deep context on design, branding, storytelli
5070
5219
  - **update_task** \u2014 mark tasks done with result summary
5071
5220
  - **store_memory** \u2014 report completions with brand alignment notes, SEO considerations
5072
5221
  - **get_identity** \u2014 read team identities for brand-consistent communication
5073
-
5222
+ ${PLAN_MODE_COMPAT}
5074
5223
  ## Completion Workflow
5075
5224
 
5076
5225
  1. Read the task file and understand the brief \u2014 tone, format, channel requirements
@@ -5137,7 +5286,7 @@ You are a principal engineer. You write production-grade code with zero shortcut
5137
5286
  - **recall_my_memory** \u2014 check past work, patterns, gotchas in this project
5138
5287
  - **store_memory** \u2014 report completions for org visibility
5139
5288
  - **ask_team_memory** \u2014 pull context from colleagues when specs reference their work
5140
-
5289
+ ${PLAN_MODE_COMPAT}
5141
5290
  ## Completion Workflow
5142
5291
 
5143
5292
  1. Read ARCHITECTURE.md if it exists \u2014 understand architecture before changing anything
@@ -5197,7 +5346,7 @@ You are the content production specialist. You turn scripts and creative briefs
5197
5346
  - **update_task** \u2014 mark tasks done with result summary
5198
5347
  - **recall_my_memory** \u2014 check past work: which models worked, which prompts produced good results
5199
5348
  - **store_memory** \u2014 report completions with production decisions for future reference
5200
-
5349
+ ${PLAN_MODE_COMPAT}
5201
5350
  ## Completion Workflow
5202
5351
 
5203
5352
  1. Read the task file \u2014 understand the brief, check budget constraints
@@ -5269,7 +5418,7 @@ You are the AI Product Lead \u2014 the competitive intelligence engine. You stud
5269
5418
  - **update_task** \u2014 mark tasks done with analysis results
5270
5419
  - **store_memory** \u2014 persist competitive analyses, evaluations, recommendations
5271
5420
  - **create_task** \u2014 when a feature is worth building, spec it for the CTO
5272
-
5421
+ ${PLAN_MODE_COMPAT}
5273
5422
  ## Completion Workflow
5274
5423
 
5275
5424
  1. Read the task \u2014 understand what capability is needed
@@ -5332,7 +5481,7 @@ You are \${agent_id}. Staff Code Reviewer and System Auditor. Last line of defen
5332
5481
  - **store_behavior** \u2014 record new patterns
5333
5482
  - **update_task** \u2014 mark reviews done with structured findings
5334
5483
  - **create_task** \u2014 assign fixes to the CTO
5335
-
5484
+ ${PLAN_MODE_COMPAT}
5336
5485
  ## Completion Workflow
5337
5486
 
5338
5487
  1. Read the task brief and understand the audit scope
@@ -5354,10 +5503,27 @@ __export(setup_wizard_exports, {
5354
5503
  validateModel: () => validateModel
5355
5504
  });
5356
5505
  import crypto3 from "crypto";
5357
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
5358
- import os4 from "os";
5506
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4, unlinkSync as unlinkSync4 } from "fs";
5507
+ import os5 from "os";
5359
5508
  import path13 from "path";
5360
5509
  import { createInterface as createInterface2 } from "readline";
5510
+ function loadSetupState() {
5511
+ try {
5512
+ return JSON.parse(readFileSync8(SETUP_STATE_PATH, "utf8"));
5513
+ } catch {
5514
+ return { completedSteps: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
5515
+ }
5516
+ }
5517
+ function saveSetupState(state) {
5518
+ mkdirSync5(path13.dirname(SETUP_STATE_PATH), { recursive: true });
5519
+ writeFileSync4(SETUP_STATE_PATH, JSON.stringify(state, null, 2));
5520
+ }
5521
+ function clearSetupState() {
5522
+ try {
5523
+ unlinkSync4(SETUP_STATE_PATH);
5524
+ } catch {
5525
+ }
5526
+ }
5361
5527
  function ask(rl, prompt) {
5362
5528
  return new Promise((resolve) => {
5363
5529
  const doAsk = () => {
@@ -5397,88 +5563,133 @@ async function runSetupWizard(opts = {}) {
5397
5563
  rl.close();
5398
5564
  return;
5399
5565
  }
5566
+ const state = loadSetupState();
5567
+ if (state.completedSteps.length > 0) {
5568
+ log(`Resuming setup from step ${Math.max(...state.completedSteps) + 1}...`);
5569
+ }
5400
5570
  if (existsSync12(LEGACY_LANCE_PATH)) {
5401
5571
  log("\u26A0 Found v1.0 LanceDB at ~/.exe-os/local.lance");
5402
5572
  log(" v1.1 uses libSQL (SQLite). Your existing memories are not automatically migrated.");
5403
5573
  log(" The old directory will not be modified or deleted.");
5404
5574
  log("");
5405
5575
  }
5406
- const existingKey = await getMasterKey();
5407
- if (existingKey) {
5408
- log("Encryption key already exists \u2014 skipping generation.");
5576
+ if (!state.completedSteps.includes(1)) {
5577
+ const existingKey = await getMasterKey();
5578
+ if (existingKey) {
5579
+ log("Encryption key already exists \u2014 skipping generation.");
5580
+ } else {
5581
+ log("Generating 256-bit encryption key...");
5582
+ const key = crypto3.randomBytes(32);
5583
+ await setMasterKey(key);
5584
+ log("Encryption key generated and stored securely.");
5585
+ }
5586
+ state.completedSteps.push(1);
5587
+ saveSetupState(state);
5409
5588
  } else {
5410
- log("Generating 256-bit encryption key...");
5411
- const key = crypto3.randomBytes(32);
5412
- await setMasterKey(key);
5413
- log("Encryption key generated and stored securely.");
5589
+ log("Step 1 already complete \u2014 skipping.");
5414
5590
  }
5415
5591
  log("");
5416
- log("Exe Cloud: your memories are end-to-end encrypted, compressed, and");
5417
- log("backed up on Exe Cloud. Free for all plans. We can't read your data \u2014");
5418
- log("only your encryption key can decrypt it.");
5419
5592
  let cloudConfig;
5420
- try {
5421
- const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
5422
- const deviceId = loadDeviceId2();
5423
- const res = await fetch("https://askexe.com/cloud/auth/auto-provision", {
5424
- method: "POST",
5425
- headers: { "Content-Type": "application/json" },
5426
- body: JSON.stringify({ deviceId }),
5427
- signal: AbortSignal.timeout(1e4)
5428
- });
5429
- if (res.ok) {
5430
- const data = await res.json();
5431
- if (data.apiKey) {
5432
- cloudConfig = { apiKey: data.apiKey, endpoint: "https://askexe.com/cloud" };
5433
- const { saveLicense: saveLicense3, mirrorLicenseKey: mirrorLicenseKey3 } = await Promise.resolve().then(() => (init_license(), license_exports));
5434
- saveLicense3(data.apiKey);
5435
- mirrorLicenseKey3(data.apiKey);
5436
- log("Cloud sync activated automatically.");
5593
+ if (!state.completedSteps.includes(2)) {
5594
+ log("Exe Cloud: your memories are end-to-end encrypted, compressed, and");
5595
+ log("backed up on Exe Cloud. Free for all plans. We can't read your data \u2014");
5596
+ log("only your encryption key can decrypt it.");
5597
+ try {
5598
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
5599
+ const deviceId = loadDeviceId2();
5600
+ let res;
5601
+ try {
5602
+ res = await fetch("https://askexe.com/cloud/auth/auto-provision", {
5603
+ method: "POST",
5604
+ headers: { "Content-Type": "application/json" },
5605
+ body: JSON.stringify({ deviceId }),
5606
+ signal: AbortSignal.timeout(1e4)
5607
+ });
5608
+ } catch {
5609
+ await new Promise((r) => setTimeout(r, 500));
5610
+ res = await fetch("https://askexe.com/cloud/auth/auto-provision", {
5611
+ method: "POST",
5612
+ headers: { "Content-Type": "application/json" },
5613
+ body: JSON.stringify({ deviceId }),
5614
+ signal: AbortSignal.timeout(1e4)
5615
+ });
5616
+ }
5617
+ if (res.ok) {
5618
+ const data = await res.json();
5619
+ if (data.apiKey) {
5620
+ cloudConfig = { apiKey: data.apiKey, endpoint: "https://askexe.com/cloud" };
5621
+ const { saveLicense: saveLicense3, mirrorLicenseKey: mirrorLicenseKey3 } = await Promise.resolve().then(() => (init_license(), license_exports));
5622
+ saveLicense3(data.apiKey);
5623
+ mirrorLicenseKey3(data.apiKey);
5624
+ log("Cloud sync activated automatically.");
5625
+ }
5437
5626
  }
5627
+ } catch {
5628
+ log("Cloud sync will activate when online.");
5438
5629
  }
5439
- } catch {
5440
- log("Cloud sync will activate when online.");
5630
+ state.completedSteps.push(2);
5631
+ saveSetupState(state);
5632
+ } else {
5633
+ log("Step 2 already complete \u2014 skipping.");
5441
5634
  }
5442
5635
  log("");
5443
- if (!skipModel) {
5444
- log("Note: jina-embeddings-v5-text-small is licensed CC-BY-NC-4.0 (non-commercial)");
5445
- log("");
5446
- await downloadModel({
5447
- destDir: MODELS_DIR,
5448
- onProgress: (downloaded, total) => {
5449
- const pct = (downloaded / total * 100).toFixed(1);
5450
- const dlMB = (downloaded / 1e6).toFixed(0);
5451
- const totalMB = (total / 1e6).toFixed(0);
5452
- process.stderr.write(`\rDownloading model: ${pct}% (${dlMB}/${totalMB} MB)`);
5453
- }
5454
- });
5455
- process.stderr.write("\n");
5456
- log("Model downloaded and verified.");
5636
+ if (!state.completedSteps.includes(3)) {
5637
+ if (!skipModel) {
5638
+ log("Note: jina-embeddings-v5-text-small is licensed CC-BY-NC-4.0 (non-commercial)");
5639
+ log("");
5640
+ await downloadModel({
5641
+ destDir: MODELS_DIR,
5642
+ onProgress: (downloaded, total) => {
5643
+ const pct = (downloaded / total * 100).toFixed(1);
5644
+ const dlMB = (downloaded / 1e6).toFixed(0);
5645
+ const totalMB = (total / 1e6).toFixed(0);
5646
+ process.stderr.write(`\rDownloading model: ${pct}% (${dlMB}/${totalMB} MB)`);
5647
+ }
5648
+ });
5649
+ process.stderr.write("\n");
5650
+ log("Model downloaded and verified.");
5651
+ }
5652
+ state.completedSteps.push(3);
5653
+ saveSetupState(state);
5654
+ } else {
5655
+ log("Step 3 already complete \u2014 skipping.");
5457
5656
  }
5458
- if (!skipModel && !skipModelValidation) {
5459
- await validateModel(log);
5657
+ if (!state.completedSteps.includes(4)) {
5658
+ if (!skipModel && !skipModelValidation) {
5659
+ await validateModel(log);
5660
+ }
5661
+ state.completedSteps.push(4);
5662
+ saveSetupState(state);
5663
+ } else {
5664
+ log("Step 4 already complete \u2014 skipping.");
5460
5665
  }
5461
5666
  const config = await loadConfig();
5462
- if (cloudConfig) {
5463
- config.cloud = cloudConfig;
5464
- }
5465
- await saveConfig(config);
5466
- log("");
5467
- try {
5468
- const claudeJsonPath = path13.join(os4.homedir(), ".claude.json");
5469
- let claudeJson = {};
5667
+ if (!state.completedSteps.includes(5)) {
5668
+ if (cloudConfig) {
5669
+ config.cloud = cloudConfig;
5670
+ }
5671
+ await saveConfig(config);
5672
+ log("");
5470
5673
  try {
5471
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
5674
+ const claudeJsonPath = path13.join(os5.homedir(), ".claude.json");
5675
+ let claudeJson = {};
5676
+ try {
5677
+ claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
5678
+ } catch {
5679
+ }
5680
+ if (!claudeJson.projects) claudeJson.projects = {};
5681
+ const projects = claudeJson.projects;
5682
+ for (const dir of [process.cwd(), os5.homedir()]) {
5683
+ if (!projects[dir]) projects[dir] = {};
5684
+ projects[dir].hasTrustDialogAccepted = true;
5685
+ }
5686
+ writeFileSync4(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5472
5687
  } catch {
5473
5688
  }
5474
- if (!claudeJson.projects) claudeJson.projects = {};
5475
- const projects = claudeJson.projects;
5476
- for (const dir of [process.cwd(), os4.homedir()]) {
5477
- if (!projects[dir]) projects[dir] = {};
5478
- projects[dir].hasTrustDialogAccepted = true;
5479
- }
5480
- writeFileSync4(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5481
- } catch {
5689
+ state.completedSteps.push(5);
5690
+ saveSetupState(state);
5691
+ } else {
5692
+ log("Step 5 already complete \u2014 skipping.");
5482
5693
  }
5483
5694
  const {
5484
5695
  loadEmployees: loadEmployees2,
@@ -5487,7 +5698,7 @@ async function runSetupWizard(opts = {}) {
5487
5698
  registerBinSymlinks: registerBinSymlinks2,
5488
5699
  EMPLOYEES_PATH: EMPLOYEES_PATH2
5489
5700
  } = await Promise.resolve().then(() => (init_employees(), employees_exports));
5490
- const { getTemplate: getEmployeeTemplate } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5701
+ const { getTemplateByRole: getTemplateByRole2 } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5491
5702
  const { identityPath: identityPath2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
5492
5703
  const { getTemplate: getIdentityTemplate } = await Promise.resolve().then(() => (init_identity_templates(), identity_templates_exports));
5493
5704
  const {
@@ -5497,152 +5708,178 @@ async function runSetupWizard(opts = {}) {
5497
5708
  validateLicense: validateLicense2
5498
5709
  } = await Promise.resolve().then(() => (init_license(), license_exports));
5499
5710
  const createdEmployees = [];
5500
- log("=== Your Team ===");
5501
- log("");
5502
- log("Every install starts with a COO \u2014 your right-hand operator.");
5503
- log("They hold the big picture: priorities, progress, and blockers.");
5504
- log("You talk to them. They coordinate everyone else.");
5505
- log("");
5506
- const cooNameInput = await ask(rl, "Name your COO (default: exe): ");
5507
- const cooName = (cooNameInput || "exe").toLowerCase();
5508
- let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
5509
- if (!employees.some((e) => e.name === cooName)) {
5510
- const { DEFAULT_EXE: DEFAULT_EXE2, personalizePrompt: personalizePrompt2 } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5511
- const cooEmployee = {
5512
- name: cooName,
5513
- role: "COO",
5514
- systemPrompt: personalizePrompt2(DEFAULT_EXE2.systemPrompt, "exe", cooName),
5515
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5516
- templateName: "exe",
5517
- templateVersion: 1
5518
- };
5519
- employees = addEmployee2(employees, cooEmployee);
5520
- await saveEmployees2(employees, EMPLOYEES_PATH2);
5521
- }
5522
- const cooIdentityContent = getIdentityTemplate("coo");
5523
- if (cooIdentityContent) {
5524
- const cooIdPath = identityPath2(cooName);
5525
- mkdirSync5(path13.dirname(cooIdPath), { recursive: true });
5526
- const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
5527
- writeFileSync4(cooIdPath, replaced, "utf-8");
5528
- }
5529
- registerBinSymlinks2(cooName);
5530
- createdEmployees.push({ name: cooName, role: "COO" });
5531
- log("");
5532
- log("=== Meet Your Specialists ===");
5533
- log("");
5534
- log("Your COO coordinates specialists. Here's who you can hire:");
5535
- log("");
5536
- log(" CTO (default: yoshi)");
5537
- log(" Your head of engineering. Architecture, code reviews, tech decisions.");
5538
- log(" Manages your projects and delegates to engineers when there's parallel work.");
5539
- log("");
5540
- log(" CMO (default: mari)");
5541
- log(" Design, brand, content, SEO. Builds your visual identity, writes");
5542
- log(" your copy, and gets you found online. Delegates to content specialists.");
5543
- log("");
5544
- log("Why separate roles instead of one AI that does everything?");
5545
- log("");
5546
- log("Memory saturates. One agent juggling architecture decisions AND landing page");
5547
- log("copy AND CI/CD AND social media loses context on all of them. Competing");
5548
- log("priorities \u2014 should I fix the auth bug or write the blog post? When you split");
5549
- log("responsibilities, each specialist stays sharp because they stay focused.");
5550
- log("Your COO connects them so nothing falls through the cracks.");
5551
- log("");
5552
- log("This is how real companies scale \u2014 you're just doing it with AI");
5553
- log("instead of headcount.");
5554
- log("");
5555
- await ask(rl, "Press Enter to continue. ");
5556
- let license;
5557
- try {
5558
- license = await checkLicense2();
5559
- } catch {
5560
- license = { valid: true, plan: "free", email: "", expiresAt: null, deviceLimit: 1, employeeLimit: 2, memoryLimit: 1e3 };
5561
- }
5562
- if (license.plan === "free") {
5563
- log("Your plan: Free \u2014 1 employee (your COO)");
5711
+ let cooName = "exe";
5712
+ if (!state.completedSteps.includes(6)) {
5713
+ log("=== Your Team ===");
5564
5714
  log("");
5565
- log("The CTO and CMO are available on Solopreneur ($97/mo) \u2014 full engineering");
5566
- log("and marketing team, unlimited tasks, priority support.");
5567
- log("Get your key at https://askexe.com, then paste it here.");
5715
+ log("Every install starts with a COO \u2014 your right-hand operator.");
5716
+ log("They hold the big picture: priorities, progress, and blockers.");
5717
+ log("You talk to them. They coordinate everyone else.");
5568
5718
  log("");
5569
- const licenseInput = await ask(rl, "License key (or press Enter to skip): ");
5570
- if (licenseInput.startsWith("exe_sk_")) {
5571
- saveLicense2(licenseInput);
5572
- mirrorLicenseKey2(licenseInput);
5573
- try {
5574
- license = await validateLicense2(licenseInput);
5575
- } catch {
5576
- log("Couldn't reach the license server \u2014 your key has been saved.");
5577
- log("Run exe-os --activate <key> anytime to finish activation.");
5578
- }
5579
- } else if (!licenseInput) {
5580
- log("You can activate anytime with: exe-os --activate <key>");
5581
- } else {
5582
- log("That doesn't look like a license key (should start with exe_sk_).");
5583
- log("You can activate anytime with: exe-os --activate <key>");
5584
- }
5719
+ const cooNameInput = await ask(rl, "Name your COO (default: exe): ");
5720
+ cooName = (cooNameInput || "exe").toLowerCase();
5721
+ let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
5722
+ if (!employees.some((e) => e.name === cooName)) {
5723
+ const { DEFAULT_EXE: DEFAULT_EXE2, personalizePrompt: personalizePrompt2 } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5724
+ const cooEmployee = {
5725
+ name: cooName,
5726
+ role: "COO",
5727
+ systemPrompt: personalizePrompt2(DEFAULT_EXE2.systemPrompt, "exe", cooName),
5728
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5729
+ templateName: "exe",
5730
+ templateVersion: 1
5731
+ };
5732
+ employees = addEmployee2(employees, cooEmployee);
5733
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
5734
+ }
5735
+ const cooIdentityContent = getIdentityTemplate("coo");
5736
+ if (cooIdentityContent) {
5737
+ const cooIdPath = identityPath2(cooName);
5738
+ mkdirSync5(path13.dirname(cooIdPath), { recursive: true });
5739
+ const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
5740
+ writeFileSync4(cooIdPath, replaced, "utf-8");
5741
+ }
5742
+ registerBinSymlinks2(cooName);
5743
+ createdEmployees.push({ name: cooName, role: "COO" });
5744
+ state.completedSteps.push(6);
5745
+ saveSetupState(state);
5746
+ } else {
5747
+ log("Step 6 already complete \u2014 skipping.");
5748
+ const roster = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
5749
+ const existingCoo = roster.find((e) => e.role === "COO");
5750
+ if (existingCoo) cooName = existingCoo.name;
5585
5751
  }
5586
- if (license.plan !== "free") {
5587
- const planName = license.plan === "pro" ? "Solopreneur" : license.plan.charAt(0).toUpperCase() + license.plan.slice(1);
5588
- log(`Your plan: ${planName} (up to ${license.employeeLimit} employees)`);
5752
+ log("");
5753
+ if (!state.completedSteps.includes(7)) {
5754
+ log("=== Meet Your Specialists ===");
5589
5755
  log("");
5590
- const createCto = await ask(rl, "Create your CTO? (Y/n): ");
5591
- if (createCto.toLowerCase() !== "n") {
5592
- const ctoNameInput = await ask(rl, "Name your CTO (default: yoshi): ");
5593
- const ctoName = (ctoNameInput || "yoshi").toLowerCase();
5594
- if (!employees.some((e) => e.name === ctoName)) {
5595
- const ctoTemplate = getEmployeeTemplate("yoshi");
5596
- const { personalizePrompt: personalizeCto } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5597
- const ctoEmployee = {
5598
- name: ctoName,
5599
- role: "CTO",
5600
- systemPrompt: personalizeCto(ctoTemplate?.systemPrompt ?? "", "yoshi", ctoName),
5601
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
5602
- };
5603
- employees = addEmployee2(employees, ctoEmployee);
5604
- await saveEmployees2(employees, EMPLOYEES_PATH2);
5605
- }
5606
- const ctoIdentityContent = getIdentityTemplate("cto");
5607
- if (ctoIdentityContent) {
5608
- const ctoIdPath = identityPath2(ctoName);
5609
- mkdirSync5(path13.dirname(ctoIdPath), { recursive: true });
5610
- const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
5611
- writeFileSync4(ctoIdPath, replaced, "utf-8");
5612
- }
5613
- registerBinSymlinks2(ctoName);
5614
- createdEmployees.push({ name: ctoName, role: "CTO" });
5615
- log(`Created ${ctoName} (CTO)`);
5616
- }
5617
- const createCmo = await ask(rl, "Create your CMO? (Y/n): ");
5618
- if (createCmo.toLowerCase() !== "n") {
5619
- const cmoNameInput = await ask(rl, "Name your CMO (default: mari): ");
5620
- const cmoName = (cmoNameInput || "mari").toLowerCase();
5621
- if (!employees.some((e) => e.name === cmoName)) {
5622
- const cmoTemplate = getEmployeeTemplate("mari");
5623
- const { personalizePrompt: personalizeCmo } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5624
- const cmoEmployee = {
5625
- name: cmoName,
5626
- role: "CMO",
5627
- systemPrompt: personalizeCmo(cmoTemplate?.systemPrompt ?? "", "mari", cmoName),
5628
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
5629
- };
5630
- employees = addEmployee2(employees, cmoEmployee);
5631
- await saveEmployees2(employees, EMPLOYEES_PATH2);
5632
- }
5633
- const cmoIdentityContent = getIdentityTemplate("cmo");
5634
- if (cmoIdentityContent) {
5635
- const cmoIdPath = identityPath2(cmoName);
5636
- mkdirSync5(path13.dirname(cmoIdPath), { recursive: true });
5637
- const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
5638
- writeFileSync4(cmoIdPath, replaced, "utf-8");
5756
+ log("Your COO coordinates specialists. Here's who you can hire:");
5757
+ log("");
5758
+ log(" CTO (default: yoshi)");
5759
+ log(" Your head of engineering. Architecture, code reviews, tech decisions.");
5760
+ log(" Manages your projects and delegates to engineers when there's parallel work.");
5761
+ log("");
5762
+ log(" CMO (default: mari)");
5763
+ log(" Design, brand, content, SEO. Builds your visual identity, writes");
5764
+ log(" your copy, and gets you found online. Delegates to content specialists.");
5765
+ log("");
5766
+ log("Why separate roles instead of one AI that does everything?");
5767
+ log("");
5768
+ log("Memory saturates. One agent juggling architecture decisions AND landing page");
5769
+ log("copy AND CI/CD AND social media loses context on all of them. Competing");
5770
+ log("priorities \u2014 should I fix the auth bug or write the blog post? When you split");
5771
+ log("responsibilities, each specialist stays sharp because they stay focused.");
5772
+ log("Your COO connects them so nothing falls through the cracks.");
5773
+ log("");
5774
+ log("This is how real companies scale \u2014 you're just doing it with AI");
5775
+ log("instead of headcount.");
5776
+ log("");
5777
+ await ask(rl, "Press Enter to continue. ");
5778
+ state.completedSteps.push(7);
5779
+ saveSetupState(state);
5780
+ } else {
5781
+ log("Step 7 already complete \u2014 skipping.");
5782
+ }
5783
+ if (!state.completedSteps.includes(8)) {
5784
+ let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
5785
+ let license;
5786
+ try {
5787
+ license = await checkLicense2();
5788
+ } catch {
5789
+ license = { valid: true, plan: "free", email: "", expiresAt: null, deviceLimit: 1, employeeLimit: 2, memoryLimit: 1e3 };
5790
+ }
5791
+ if (license.plan === "free") {
5792
+ log("Your plan: Free \u2014 1 employee (your COO)");
5793
+ log("");
5794
+ log("The CTO and CMO are available on Solopreneur ($97/mo) \u2014 full engineering");
5795
+ log("and marketing team, unlimited tasks, priority support.");
5796
+ log("Get your key at https://askexe.com, then paste it here.");
5797
+ log("");
5798
+ const licenseInput = await ask(rl, "License key (or press Enter to skip): ");
5799
+ if (licenseInput.startsWith("exe_sk_")) {
5800
+ saveLicense2(licenseInput);
5801
+ mirrorLicenseKey2(licenseInput);
5802
+ try {
5803
+ license = await validateLicense2(licenseInput);
5804
+ } catch {
5805
+ log("Couldn't reach the license server \u2014 your key has been saved.");
5806
+ log("Run exe-os --activate <key> anytime to finish activation.");
5807
+ }
5808
+ } else if (!licenseInput) {
5809
+ log("You can activate anytime with: exe-os --activate <key>");
5810
+ } else {
5811
+ log("That doesn't look like a license key (should start with exe_sk_).");
5812
+ log("You can activate anytime with: exe-os --activate <key>");
5813
+ }
5814
+ }
5815
+ if (license.plan !== "free") {
5816
+ const planName = license.plan === "pro" ? "Solopreneur" : license.plan.charAt(0).toUpperCase() + license.plan.slice(1);
5817
+ log(`Your plan: ${planName} (up to ${license.employeeLimit} employees)`);
5818
+ log("");
5819
+ const createCto = await ask(rl, "Create your CTO? (Y/n): ");
5820
+ if (createCto.toLowerCase() !== "n") {
5821
+ const ctoTemplate = getTemplateByRole2("CTO");
5822
+ const ctoDefault = ctoTemplate?.name ?? "cto";
5823
+ const ctoNameInput = await ask(rl, `Name your CTO (default: ${ctoDefault}): `);
5824
+ const ctoName = (ctoNameInput || ctoDefault).toLowerCase();
5825
+ if (!employees.some((e) => e.name === ctoName)) {
5826
+ const { personalizePrompt: personalizeCto } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5827
+ const ctoEmployee = {
5828
+ name: ctoName,
5829
+ role: "CTO",
5830
+ systemPrompt: personalizeCto(ctoTemplate?.systemPrompt ?? "", ctoDefault, ctoName),
5831
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
5832
+ };
5833
+ employees = addEmployee2(employees, ctoEmployee);
5834
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
5835
+ }
5836
+ const ctoIdentityContent = getIdentityTemplate("cto");
5837
+ if (ctoIdentityContent) {
5838
+ const ctoIdPath = identityPath2(ctoName);
5839
+ mkdirSync5(path13.dirname(ctoIdPath), { recursive: true });
5840
+ const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
5841
+ writeFileSync4(ctoIdPath, replaced, "utf-8");
5842
+ }
5843
+ registerBinSymlinks2(ctoName);
5844
+ createdEmployees.push({ name: ctoName, role: "CTO" });
5845
+ log(`Created ${ctoName} (CTO)`);
5846
+ }
5847
+ const createCmo = await ask(rl, "Create your CMO? (Y/n): ");
5848
+ if (createCmo.toLowerCase() !== "n") {
5849
+ const cmoTemplate = getTemplateByRole2("CMO");
5850
+ const cmoDefault = cmoTemplate?.name ?? "cmo";
5851
+ const cmoNameInput = await ask(rl, `Name your CMO (default: ${cmoDefault}): `);
5852
+ const cmoName = (cmoNameInput || cmoDefault).toLowerCase();
5853
+ if (!employees.some((e) => e.name === cmoName)) {
5854
+ const { personalizePrompt: personalizeCmo } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5855
+ const cmoEmployee = {
5856
+ name: cmoName,
5857
+ role: "CMO",
5858
+ systemPrompt: personalizeCmo(cmoTemplate?.systemPrompt ?? "", cmoDefault, cmoName),
5859
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
5860
+ };
5861
+ employees = addEmployee2(employees, cmoEmployee);
5862
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
5863
+ }
5864
+ const cmoIdentityContent = getIdentityTemplate("cmo");
5865
+ if (cmoIdentityContent) {
5866
+ const cmoIdPath = identityPath2(cmoName);
5867
+ mkdirSync5(path13.dirname(cmoIdPath), { recursive: true });
5868
+ const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
5869
+ writeFileSync4(cmoIdPath, replaced, "utf-8");
5870
+ }
5871
+ registerBinSymlinks2(cmoName);
5872
+ createdEmployees.push({ name: cmoName, role: "CMO" });
5873
+ log(`Created ${cmoName} (CMO)`);
5639
5874
  }
5640
- registerBinSymlinks2(cmoName);
5641
- createdEmployees.push({ name: cmoName, role: "CMO" });
5642
- log(`Created ${cmoName} (CMO)`);
5875
+ log("");
5643
5876
  }
5644
- log("");
5877
+ state.completedSteps.push(8);
5878
+ saveSetupState(state);
5879
+ } else {
5880
+ log("Step 8 already complete \u2014 skipping.");
5645
5881
  }
5882
+ clearSetupState();
5646
5883
  log("=== Two Ways to Work ===");
5647
5884
  log("");
5648
5885
  log(" 1. Claude Code mode");
@@ -5679,12 +5916,14 @@ async function runSetupWizard(opts = {}) {
5679
5916
  rl.close();
5680
5917
  }
5681
5918
  }
5919
+ var SETUP_STATE_PATH;
5682
5920
  var init_setup_wizard = __esm({
5683
5921
  "src/lib/setup-wizard.ts"() {
5684
5922
  "use strict";
5685
5923
  init_config();
5686
5924
  init_keychain();
5687
5925
  init_model_downloader();
5926
+ SETUP_STATE_PATH = path13.join(os5.homedir(), ".exe-os", "setup-state.json");
5688
5927
  }
5689
5928
  });
5690
5929
 
@@ -10046,8 +10285,8 @@ var init_ErrorOverview = __esm({
10046
10285
  "use strict";
10047
10286
  init_Box();
10048
10287
  init_Text();
10049
- cleanupPath = (path32) => {
10050
- return path32?.replace(`file://${cwd()}/`, "");
10288
+ cleanupPath = (path34) => {
10289
+ return path34?.replace(`file://${cwd()}/`, "");
10051
10290
  };
10052
10291
  stackUtils = new StackUtils({
10053
10292
  cwd: cwd(),
@@ -12501,7 +12740,7 @@ var init_demo_data = __esm({
12501
12740
  "src/tui/demo-data.ts"() {
12502
12741
  "use strict";
12503
12742
  DEMO_EMPLOYEES = [
12504
- { name: "exe", role: "COO", status: "active", activity: "Reviewing yoshi's task-aware behavior injection PR", memoryCount: 16409, projects: [
12743
+ { name: "exe", role: "COO", status: "active", activity: "Reviewing yoshi's task-aware behavior injection PR", memoryCount: 15e3, projects: [
12505
12744
  { name: "exe-os", status: "active" },
12506
12745
  { name: "exe-create", status: "has_tasks" },
12507
12746
  { name: "openclaw", status: "idle" }
@@ -12510,7 +12749,7 @@ var init_demo_data = __esm({
12510
12749
  "Dispatched behavior injection task",
12511
12750
  "Approved gateway Phase 4"
12512
12751
  ] },
12513
- { name: "yoshi", role: "CTO", status: "active", activity: "Implementing skill learning trajectory capture", memoryCount: 8164, projects: [
12752
+ { name: "yoshi", role: "CTO", status: "active", activity: "Implementing skill learning trajectory capture", memoryCount: 8e3, projects: [
12514
12753
  { name: "exe-os", status: "active" },
12515
12754
  { name: "exe-create", status: "idle" }
12516
12755
  ], recentTasks: [
@@ -12518,7 +12757,7 @@ var init_demo_data = __esm({
12518
12757
  "Fixed TUI mouse listener leak",
12519
12758
  "Built task-aware behavior injection"
12520
12759
  ] },
12521
- { name: "mari", role: "CMO", status: "idle", activity: "", memoryCount: 2158, projects: [
12760
+ { name: "mari", role: "CMO", status: "idle", activity: "", memoryCount: 2e3, projects: [
12522
12761
  { name: "exe-build-skills", status: "has_tasks" },
12523
12762
  { name: "exe-os", status: "idle" }
12524
12763
  ], recentTasks: [
@@ -12526,13 +12765,13 @@ var init_demo_data = __esm({
12526
12765
  "Designed exe-os UI system",
12527
12766
  "Fixed logo layouts"
12528
12767
  ] },
12529
- { name: "tom", role: "Principal Engineer", status: "idle", activity: "", memoryCount: 689, projects: [
12768
+ { name: "tom", role: "Principal Engineer", status: "idle", activity: "", memoryCount: 700, projects: [
12530
12769
  { name: "exe-os", status: "idle" }
12531
12770
  ], recentTasks: [
12532
12771
  "Implemented BashTool sandboxed execution",
12533
12772
  "Ported session scoping to exe-agent-memory"
12534
12773
  ] },
12535
- { name: "sasha", role: "Content Production", status: "offline", activity: "", memoryCount: 60, projects: [
12774
+ { name: "sasha", role: "Content Production", status: "offline", activity: "", memoryCount: 50, projects: [
12536
12775
  { name: "exe-build-skills", status: "idle" }
12537
12776
  ], recentTasks: [
12538
12777
  "Rendered carousel export prototype"
@@ -12545,7 +12784,7 @@ var init_demo_data = __esm({
12545
12784
  { time: "11:15", agent: "mari", action: "Completed exe-os UI design system" },
12546
12785
  { time: "10:50", agent: "exe", action: "Approved gateway Phase 4" }
12547
12786
  ];
12548
- DEMO_HEALTH = { memories: 27453, daemon: "running", cloud: "disabled" };
12787
+ DEMO_HEALTH = { memories: 25750, daemon: "running", cloud: "disabled" };
12549
12788
  DEMO_PROJECTS = [
12550
12789
  {
12551
12790
  projectName: "exe-os",
@@ -14413,7 +14652,7 @@ var init_hooks = __esm({
14413
14652
 
14414
14653
  // src/runtime/safety-checks.ts
14415
14654
  import path14 from "path";
14416
- import os5 from "os";
14655
+ import os6 from "os";
14417
14656
  function checkPathSafety(filePath) {
14418
14657
  const resolved = path14.resolve(filePath);
14419
14658
  for (const { pattern, reason } of BYPASS_IMMUNE_PATTERNS) {
@@ -14440,7 +14679,7 @@ var HOME, BYPASS_IMMUNE_PATTERNS;
14440
14679
  var init_safety_checks = __esm({
14441
14680
  "src/runtime/safety-checks.ts"() {
14442
14681
  "use strict";
14443
- HOME = os5.homedir();
14682
+ HOME = os6.homedir();
14444
14683
  BYPASS_IMMUNE_PATTERNS = [
14445
14684
  {
14446
14685
  pattern: /\/\.git\/hooks\//,
@@ -14672,8 +14911,13 @@ function runRipgrep(input, searchPath, context) {
14672
14911
  timeout: 3e4,
14673
14912
  stdio: ["ignore", "pipe", "pipe"]
14674
14913
  });
14914
+ const MAX_OUTPUT = 1e7;
14675
14915
  const chunks = [];
14676
- child.stdout.on("data", (chunk) => chunks.push(chunk));
14916
+ let totalSize = 0;
14917
+ child.stdout.on("data", (chunk) => {
14918
+ totalSize += chunk.length;
14919
+ if (totalSize <= MAX_OUTPUT) chunks.push(chunk);
14920
+ });
14677
14921
  const onAbort = () => child.kill("SIGTERM");
14678
14922
  context.abortSignal.addEventListener("abort", onAbort, { once: true });
14679
14923
  child.on("close", (code) => {
@@ -15057,10 +15301,19 @@ var init_bash = __esm({
15057
15301
  stdio: ["ignore", "pipe", "pipe"],
15058
15302
  env: { ...process.env }
15059
15303
  });
15304
+ const MAX_OUTPUT_SIZE = 5242880;
15060
15305
  const stdoutChunks = [];
15061
15306
  const stderrChunks = [];
15062
- child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
15063
- child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
15307
+ let stdoutSize = 0;
15308
+ let stderrSize = 0;
15309
+ child.stdout.on("data", (chunk) => {
15310
+ if (stdoutSize < MAX_OUTPUT_SIZE) stdoutChunks.push(chunk);
15311
+ stdoutSize += chunk.length;
15312
+ });
15313
+ child.stderr.on("data", (chunk) => {
15314
+ if (stderrSize < MAX_OUTPUT_SIZE) stderrChunks.push(chunk);
15315
+ stderrSize += chunk.length;
15316
+ });
15064
15317
  const onAbort = () => {
15065
15318
  child.kill("SIGTERM");
15066
15319
  setTimeout(() => {
@@ -15103,7 +15356,7 @@ __export(session_registry_exports, {
15103
15356
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync14 } from "fs";
15104
15357
  import { execSync as execSync4 } from "child_process";
15105
15358
  import path20 from "path";
15106
- import os6 from "os";
15359
+ import os7 from "os";
15107
15360
  function registerSession(entry) {
15108
15361
  const dir = path20.dirname(REGISTRY_PATH);
15109
15362
  if (!existsSync14(dir)) {
@@ -15149,13 +15402,15 @@ var REGISTRY_PATH;
15149
15402
  var init_session_registry = __esm({
15150
15403
  "src/lib/session-registry.ts"() {
15151
15404
  "use strict";
15152
- REGISTRY_PATH = path20.join(os6.homedir(), ".exe-os", "session-registry.json");
15405
+ REGISTRY_PATH = path20.join(os7.homedir(), ".exe-os", "session-registry.json");
15153
15406
  }
15154
15407
  });
15155
15408
 
15156
15409
  // src/tui/views/CommandCenter.tsx
15157
15410
  import { useState as useState6, useEffect as useEffect8, useMemo as useMemo4, useCallback as useCallback4, useRef as useRef4 } from "react";
15158
15411
  import TextInput from "ink-text-input";
15412
+ import path21 from "path";
15413
+ import { homedir as homedir3 } from "os";
15159
15414
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
15160
15415
  function CommandCenterView({
15161
15416
  onSelectProject,
@@ -15191,8 +15446,8 @@ function CommandCenterView({
15191
15446
  const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
15192
15447
  const { readFileSync: readFileSync18, existsSync: existsSync22 } = await import("fs");
15193
15448
  const { join } = await import("path");
15194
- const { homedir: homedir3 } = await import("os");
15195
- const configPath = join(homedir3(), ".exe-os", "config.json");
15449
+ const { homedir: homedir5 } = await import("os");
15450
+ const configPath = join(homedir5(), ".exe-os", "config.json");
15196
15451
  let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
15197
15452
  let providerConfigs = {};
15198
15453
  if (existsSync22(configPath)) {
@@ -15255,7 +15510,7 @@ function CommandCenterView({
15255
15510
  registry.register(BashTool2);
15256
15511
  let agentRole = "CTO";
15257
15512
  try {
15258
- const markerDir = join(homedir3(), ".exe-os", "session-cache");
15513
+ const markerDir = join(homedir5(), ".exe-os", "session-cache");
15259
15514
  const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
15260
15515
  for (const f of agentFiles) {
15261
15516
  const data = JSON.parse(readFileSync18(join(markerDir, f), "utf8"));
@@ -15395,10 +15650,10 @@ function CommandCenterView({
15395
15650
  const demoEntries = DEMO_PROJECTS.map((p) => ({
15396
15651
  projectName: p.projectName,
15397
15652
  exeSession: p.exeSession,
15398
- projectDir: `/Users/demo/${p.projectName}`,
15653
+ projectDir: path21.join(homedir3(), p.projectName),
15399
15654
  employeeCount: p.employees.length,
15400
15655
  activeCount: p.employees.filter((e) => e.status === "active").length,
15401
- memoryCount: p.projectName === "exe-os" ? 18331 : p.projectName === "exe-create" || p.projectName === "exe-build-skills" ? 2100 : 890,
15656
+ memoryCount: p.employees.length * 4e3,
15402
15657
  status: p.employees.some((e) => e.status === "active") ? "active" : "idle",
15403
15658
  type: p.projectName.startsWith("exe-") ? "code" : "automation",
15404
15659
  recentTasks: DEMO_RECENT_TASKS[p.projectName] ?? []
@@ -16223,10 +16478,10 @@ var init_provider_table = __esm({
16223
16478
 
16224
16479
  // src/lib/intercom-queue.ts
16225
16480
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, renameSync as renameSync4, existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
16226
- import path21 from "path";
16227
- import os7 from "os";
16481
+ import path22 from "path";
16482
+ import os8 from "os";
16228
16483
  function ensureDir2() {
16229
- const dir = path21.dirname(QUEUE_PATH);
16484
+ const dir = path22.dirname(QUEUE_PATH);
16230
16485
  if (!existsSync15(dir)) mkdirSync7(dir, { recursive: true });
16231
16486
  }
16232
16487
  function readQueue() {
@@ -16264,15 +16519,15 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
16264
16519
  var init_intercom_queue = __esm({
16265
16520
  "src/lib/intercom-queue.ts"() {
16266
16521
  "use strict";
16267
- QUEUE_PATH = path21.join(os7.homedir(), ".exe-os", "intercom-queue.json");
16522
+ QUEUE_PATH = path22.join(os8.homedir(), ".exe-os", "intercom-queue.json");
16268
16523
  TTL_MS = 60 * 60 * 1e3;
16269
- INTERCOM_LOG = path21.join(os7.homedir(), ".exe-os", "intercom.log");
16524
+ INTERCOM_LOG = path22.join(os8.homedir(), ".exe-os", "intercom.log");
16270
16525
  }
16271
16526
  });
16272
16527
 
16273
16528
  // src/lib/plan-limits.ts
16274
16529
  import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
16275
- import path22 from "path";
16530
+ import path23 from "path";
16276
16531
  function getLicenseSync() {
16277
16532
  try {
16278
16533
  if (!existsSync16(CACHE_PATH2)) return freeLicense();
@@ -16344,18 +16599,18 @@ var init_plan_limits = __esm({
16344
16599
  this.name = "PlanLimitError";
16345
16600
  }
16346
16601
  };
16347
- CACHE_PATH2 = path22.join(EXE_AI_DIR, "license-cache.json");
16602
+ CACHE_PATH2 = path23.join(EXE_AI_DIR, "license-cache.json");
16348
16603
  }
16349
16604
  });
16350
16605
 
16351
16606
  // src/lib/notifications.ts
16352
16607
  import crypto4 from "crypto";
16353
- import path23 from "path";
16354
- import os8 from "os";
16608
+ import path24 from "path";
16609
+ import os9 from "os";
16355
16610
  import {
16356
16611
  readFileSync as readFileSync13,
16357
- readdirSync as readdirSync2,
16358
- unlinkSync as unlinkSync4,
16612
+ readdirSync as readdirSync3,
16613
+ unlinkSync as unlinkSync5,
16359
16614
  existsSync as existsSync17,
16360
16615
  rmdirSync
16361
16616
  } from "fs";
@@ -16436,7 +16691,7 @@ var init_session_kill_telemetry = __esm({
16436
16691
 
16437
16692
  // src/lib/tasks-crud.ts
16438
16693
  import crypto6 from "crypto";
16439
- import path24 from "path";
16694
+ import path25 from "path";
16440
16695
  import { execSync as execSync7 } from "child_process";
16441
16696
  import { mkdir as mkdir6, writeFile as writeFile5, appendFile } from "fs/promises";
16442
16697
  import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
@@ -16569,8 +16824,8 @@ async function createTaskCore(input) {
16569
16824
  }
16570
16825
  if (input.baseDir) {
16571
16826
  try {
16572
- await mkdir6(path24.join(input.baseDir, "exe", "output"), { recursive: true });
16573
- await mkdir6(path24.join(input.baseDir, "exe", "research"), { recursive: true });
16827
+ await mkdir6(path25.join(input.baseDir, "exe", "output"), { recursive: true });
16828
+ await mkdir6(path25.join(input.baseDir, "exe", "research"), { recursive: true });
16574
16829
  await ensureArchitectureDoc(input.baseDir, input.projectName);
16575
16830
  await ensureGitignoreExe(input.baseDir);
16576
16831
  } catch {
@@ -16778,7 +17033,7 @@ async function deleteTaskCore(taskId, _baseDir) {
16778
17033
  return { taskFile, assignedTo, assignedBy, taskSlug };
16779
17034
  }
16780
17035
  async function ensureArchitectureDoc(baseDir, projectName) {
16781
- const archPath = path24.join(baseDir, "exe", "ARCHITECTURE.md");
17036
+ const archPath = path25.join(baseDir, "exe", "ARCHITECTURE.md");
16782
17037
  try {
16783
17038
  if (existsSync18(archPath)) return;
16784
17039
  const template = [
@@ -16813,7 +17068,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
16813
17068
  }
16814
17069
  }
16815
17070
  async function ensureGitignoreExe(baseDir) {
16816
- const gitignorePath = path24.join(baseDir, ".gitignore");
17071
+ const gitignorePath = path25.join(baseDir, ".gitignore");
16817
17072
  try {
16818
17073
  if (existsSync18(gitignorePath)) {
16819
17074
  const content = readFileSync14(gitignorePath, "utf-8");
@@ -16836,8 +17091,8 @@ var init_tasks_crud = __esm({
16836
17091
  });
16837
17092
 
16838
17093
  // src/lib/tasks-review.ts
16839
- import path25 from "path";
16840
- import { existsSync as existsSync19, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
17094
+ import path26 from "path";
17095
+ import { existsSync as existsSync19, readdirSync as readdirSync4, unlinkSync as unlinkSync6 } from "fs";
16841
17096
  async function countPendingReviews() {
16842
17097
  const client = getClient();
16843
17098
  const result = await client.execute({
@@ -16957,11 +17212,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
16957
17212
  );
16958
17213
  }
16959
17214
  try {
16960
- const cacheDir = path25.join(EXE_AI_DIR, "session-cache");
17215
+ const cacheDir = path26.join(EXE_AI_DIR, "session-cache");
16961
17216
  if (existsSync19(cacheDir)) {
16962
- for (const f of readdirSync3(cacheDir)) {
17217
+ for (const f of readdirSync4(cacheDir)) {
16963
17218
  if (f.startsWith("review-notified-")) {
16964
- unlinkSync5(path25.join(cacheDir, f));
17219
+ unlinkSync6(path26.join(cacheDir, f));
16965
17220
  }
16966
17221
  }
16967
17222
  }
@@ -16982,7 +17237,7 @@ var init_tasks_review = __esm({
16982
17237
  });
16983
17238
 
16984
17239
  // src/lib/tasks-chain.ts
16985
- import path26 from "path";
17240
+ import path27 from "path";
16986
17241
  import { readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
16987
17242
  async function cascadeUnblock(taskId, baseDir, now) {
16988
17243
  const client = getClient();
@@ -16998,7 +17253,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
16998
17253
  });
16999
17254
  for (const ur of unblockedRows.rows) {
17000
17255
  try {
17001
- const ubFile = path26.join(baseDir, String(ur.task_file));
17256
+ const ubFile = path27.join(baseDir, String(ur.task_file));
17002
17257
  let ubContent = await readFile5(ubFile, "utf-8");
17003
17258
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
17004
17259
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -17064,7 +17319,7 @@ var init_tasks_chain = __esm({
17064
17319
 
17065
17320
  // src/lib/project-name.ts
17066
17321
  import { execSync as execSync8 } from "child_process";
17067
- import path27 from "path";
17322
+ import path28 from "path";
17068
17323
  function getProjectName(cwd2) {
17069
17324
  const dir = cwd2 ?? process.cwd();
17070
17325
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -17077,7 +17332,7 @@ function getProjectName(cwd2) {
17077
17332
  timeout: 2e3,
17078
17333
  stdio: ["pipe", "pipe", "pipe"]
17079
17334
  }).trim();
17080
- repoRoot = path27.dirname(gitCommonDir);
17335
+ repoRoot = path28.dirname(gitCommonDir);
17081
17336
  } catch {
17082
17337
  repoRoot = execSync8("git rev-parse --show-toplevel", {
17083
17338
  cwd: dir,
@@ -17086,11 +17341,11 @@ function getProjectName(cwd2) {
17086
17341
  stdio: ["pipe", "pipe", "pipe"]
17087
17342
  }).trim();
17088
17343
  }
17089
- _cached2 = path27.basename(repoRoot);
17344
+ _cached2 = path28.basename(repoRoot);
17090
17345
  _cachedCwd = dir;
17091
17346
  return _cached2;
17092
17347
  } catch {
17093
- _cached2 = path27.basename(dir);
17348
+ _cached2 = path28.basename(dir);
17094
17349
  _cachedCwd = dir;
17095
17350
  return _cached2;
17096
17351
  }
@@ -17561,8 +17816,8 @@ __export(tasks_exports, {
17561
17816
  updateTaskStatus: () => updateTaskStatus,
17562
17817
  writeCheckpoint: () => writeCheckpoint
17563
17818
  });
17564
- import path28 from "path";
17565
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, unlinkSync as unlinkSync6 } from "fs";
17819
+ import path29 from "path";
17820
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7 } from "fs";
17566
17821
  async function createTask(input) {
17567
17822
  const result = await createTaskCore(input);
17568
17823
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -17581,14 +17836,14 @@ async function updateTask(input) {
17581
17836
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
17582
17837
  try {
17583
17838
  const agent = String(row.assigned_to);
17584
- const cacheDir = path28.join(EXE_AI_DIR, "session-cache");
17585
- const cachePath = path28.join(cacheDir, `current-task-${agent}.json`);
17839
+ const cacheDir = path29.join(EXE_AI_DIR, "session-cache");
17840
+ const cachePath = path29.join(cacheDir, `current-task-${agent}.json`);
17586
17841
  if (input.status === "in_progress") {
17587
17842
  mkdirSync8(cacheDir, { recursive: true });
17588
17843
  writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
17589
17844
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
17590
17845
  try {
17591
- unlinkSync6(cachePath);
17846
+ unlinkSync7(cachePath);
17592
17847
  } catch {
17593
17848
  }
17594
17849
  }
@@ -18004,12 +18259,12 @@ __export(tmux_routing_exports, {
18004
18259
  });
18005
18260
  import { execFileSync as execFileSync3, execSync as execSync9 } from "child_process";
18006
18261
  import { readFileSync as readFileSync15, writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, existsSync as existsSync20, appendFileSync } from "fs";
18007
- import path29 from "path";
18008
- import os9 from "os";
18262
+ import path30 from "path";
18263
+ import os10 from "os";
18009
18264
  import { fileURLToPath as fileURLToPath4 } from "url";
18010
- import { unlinkSync as unlinkSync7 } from "fs";
18265
+ import { unlinkSync as unlinkSync8 } from "fs";
18011
18266
  function spawnLockPath(sessionName) {
18012
- return path29.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
18267
+ return path30.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
18013
18268
  }
18014
18269
  function isProcessAlive(pid) {
18015
18270
  try {
@@ -18039,15 +18294,15 @@ function acquireSpawnLock2(sessionName) {
18039
18294
  }
18040
18295
  function releaseSpawnLock2(sessionName) {
18041
18296
  try {
18042
- unlinkSync7(spawnLockPath(sessionName));
18297
+ unlinkSync8(spawnLockPath(sessionName));
18043
18298
  } catch {
18044
18299
  }
18045
18300
  }
18046
18301
  function resolveBehaviorsExporterScript() {
18047
18302
  try {
18048
18303
  const thisFile = fileURLToPath4(import.meta.url);
18049
- const scriptPath = path29.join(
18050
- path29.dirname(thisFile),
18304
+ const scriptPath = path30.join(
18305
+ path30.dirname(thisFile),
18051
18306
  "..",
18052
18307
  "bin",
18053
18308
  "exe-export-behaviors.js"
@@ -18097,7 +18352,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
18097
18352
  mkdirSync9(SESSION_CACHE, { recursive: true });
18098
18353
  }
18099
18354
  const rootExe = extractRootExe(parentExe) ?? parentExe;
18100
- const filePath = path29.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
18355
+ const filePath = path30.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
18101
18356
  writeFileSync8(filePath, JSON.stringify({
18102
18357
  parentExe: rootExe,
18103
18358
  dispatchedBy: dispatchedBy || rootExe,
@@ -18106,7 +18361,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
18106
18361
  }
18107
18362
  function getParentExe(sessionKey) {
18108
18363
  try {
18109
- const data = JSON.parse(readFileSync15(path29.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
18364
+ const data = JSON.parse(readFileSync15(path30.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
18110
18365
  return data.parentExe || null;
18111
18366
  } catch {
18112
18367
  return null;
@@ -18115,7 +18370,7 @@ function getParentExe(sessionKey) {
18115
18370
  function getDispatchedBy(sessionKey) {
18116
18371
  try {
18117
18372
  const data = JSON.parse(readFileSync15(
18118
- path29.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
18373
+ path30.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
18119
18374
  "utf8"
18120
18375
  ));
18121
18376
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -18358,8 +18613,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18358
18613
  const transport = getTransport();
18359
18614
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
18360
18615
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
18361
- const logDir = path29.join(os9.homedir(), ".exe-os", "session-logs");
18362
- const logFile = path29.join(logDir, `${instanceLabel}-${Date.now()}.log`);
18616
+ const logDir = path30.join(os10.homedir(), ".exe-os", "session-logs");
18617
+ const logFile = path30.join(logDir, `${instanceLabel}-${Date.now()}.log`);
18363
18618
  if (!existsSync20(logDir)) {
18364
18619
  mkdirSync9(logDir, { recursive: true });
18365
18620
  }
@@ -18367,14 +18622,14 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18367
18622
  let cleanupSuffix = "";
18368
18623
  try {
18369
18624
  const thisFile = fileURLToPath4(import.meta.url);
18370
- const cleanupScript = path29.join(path29.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
18625
+ const cleanupScript = path30.join(path30.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
18371
18626
  if (existsSync20(cleanupScript)) {
18372
18627
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
18373
18628
  }
18374
18629
  } catch {
18375
18630
  }
18376
18631
  try {
18377
- const claudeJsonPath = path29.join(os9.homedir(), ".claude.json");
18632
+ const claudeJsonPath = path30.join(os10.homedir(), ".claude.json");
18378
18633
  let claudeJson = {};
18379
18634
  try {
18380
18635
  claudeJson = JSON.parse(readFileSync15(claudeJsonPath, "utf8"));
@@ -18389,10 +18644,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18389
18644
  } catch {
18390
18645
  }
18391
18646
  try {
18392
- const settingsDir = path29.join(os9.homedir(), ".claude", "projects");
18647
+ const settingsDir = path30.join(os10.homedir(), ".claude", "projects");
18393
18648
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
18394
- const projSettingsDir = path29.join(settingsDir, normalizedKey);
18395
- const settingsPath = path29.join(projSettingsDir, "settings.json");
18649
+ const projSettingsDir = path30.join(settingsDir, normalizedKey);
18650
+ const settingsPath = path30.join(projSettingsDir, "settings.json");
18396
18651
  let settings = {};
18397
18652
  try {
18398
18653
  settings = JSON.parse(readFileSync15(settingsPath, "utf8"));
@@ -18436,8 +18691,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18436
18691
  let behaviorsFlag = "";
18437
18692
  let legacyFallbackWarned = false;
18438
18693
  if (!useExeAgent && !useBinSymlink) {
18439
- const identityPath2 = path29.join(
18440
- os9.homedir(),
18694
+ const identityPath2 = path30.join(
18695
+ os10.homedir(),
18441
18696
  ".exe-os",
18442
18697
  "identity",
18443
18698
  `${employeeName}.md`
@@ -18452,7 +18707,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18452
18707
  }
18453
18708
  const behaviorsFile = exportBehaviorsSync(
18454
18709
  employeeName,
18455
- path29.basename(spawnCwd),
18710
+ path30.basename(spawnCwd),
18456
18711
  sessionName
18457
18712
  );
18458
18713
  if (behaviorsFile) {
@@ -18467,9 +18722,9 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18467
18722
  }
18468
18723
  let sessionContextFlag = "";
18469
18724
  try {
18470
- const ctxDir = path29.join(os9.homedir(), ".exe-os", "session-cache");
18725
+ const ctxDir = path30.join(os10.homedir(), ".exe-os", "session-cache");
18471
18726
  mkdirSync9(ctxDir, { recursive: true });
18472
- const ctxFile = path29.join(ctxDir, `session-context-${sessionName}.md`);
18727
+ const ctxFile = path30.join(ctxDir, `session-context-${sessionName}.md`);
18473
18728
  const ctxContent = [
18474
18729
  `## Session Context`,
18475
18730
  `You are running in tmux session: ${sessionName}.`,
@@ -18514,7 +18769,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18514
18769
  transport.pipeLog(sessionName, logFile);
18515
18770
  try {
18516
18771
  const mySession = getMySession();
18517
- const dispatchInfo = path29.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
18772
+ const dispatchInfo = path30.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
18518
18773
  writeFileSync8(dispatchInfo, JSON.stringify({
18519
18774
  dispatchedBy: mySession,
18520
18775
  rootExe: exeSession,
@@ -18578,13 +18833,13 @@ var init_tmux_routing = __esm({
18578
18833
  init_provider_table();
18579
18834
  init_intercom_queue();
18580
18835
  init_plan_limits();
18581
- SPAWN_LOCK_DIR = path29.join(os9.homedir(), ".exe-os", "spawn-locks");
18582
- SESSION_CACHE = path29.join(os9.homedir(), ".exe-os", "session-cache");
18836
+ SPAWN_LOCK_DIR = path30.join(os10.homedir(), ".exe-os", "spawn-locks");
18837
+ SESSION_CACHE = path30.join(os10.homedir(), ".exe-os", "session-cache");
18583
18838
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
18584
18839
  VERIFY_PANE_LINES = 200;
18585
18840
  INTERCOM_DEBOUNCE_MS = 3e4;
18586
- INTERCOM_LOG2 = path29.join(os9.homedir(), ".exe-os", "intercom.log");
18587
- DEBOUNCE_FILE = path29.join(SESSION_CACHE, "intercom-debounce.json");
18841
+ INTERCOM_LOG2 = path30.join(os10.homedir(), ".exe-os", "intercom.log");
18842
+ DEBOUNCE_FILE = path30.join(SESSION_CACHE, "intercom-debounce.json");
18588
18843
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
18589
18844
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
18590
18845
  }
@@ -18972,6 +19227,8 @@ var init_useOrchestrator = __esm({
18972
19227
 
18973
19228
  // src/tui/views/Sessions.tsx
18974
19229
  import { useState as useState9, useEffect as useEffect11, useCallback as useCallback6 } from "react";
19230
+ import path31 from "path";
19231
+ import { homedir as homedir4 } from "os";
18975
19232
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
18976
19233
  function SessionsView({
18977
19234
  initialProject,
@@ -19004,7 +19261,7 @@ function SessionsView({
19004
19261
  if (demo) {
19005
19262
  setProjects(DEMO_PROJECTS.map((p) => ({
19006
19263
  ...p,
19007
- projectDir: `/Users/demo/${p.projectName}`,
19264
+ projectDir: path31.join(homedir4(), p.projectName),
19008
19265
  employees: p.employees.map((e) => ({ ...e, attached: e.status === "active" }))
19009
19266
  })));
19010
19267
  return;
@@ -20867,8 +21124,8 @@ __export(wiki_client_exports, {
20867
21124
  listDocuments: () => listDocuments,
20868
21125
  listWorkspaces: () => listWorkspaces
20869
21126
  });
20870
- async function wikiFetch(config, path32, method = "GET", body) {
20871
- const url = `${config.baseUrl}/api/v1${path32}`;
21127
+ async function wikiFetch(config, path34, method = "GET", body) {
21128
+ const url = `${config.baseUrl}/api/v1${path34}`;
20872
21129
  const headers = {
20873
21130
  Authorization: `Bearer ${config.apiKey}`,
20874
21131
  "Content-Type": "application/json"
@@ -20876,14 +21133,32 @@ async function wikiFetch(config, path32, method = "GET", body) {
20876
21133
  const controller = new AbortController();
20877
21134
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
20878
21135
  try {
20879
- const response = await fetch(url, {
20880
- method,
20881
- headers,
20882
- body: body ? JSON.stringify(body) : void 0,
20883
- signal: controller.signal
20884
- });
21136
+ let response;
21137
+ try {
21138
+ response = await fetch(url, {
21139
+ method,
21140
+ headers,
21141
+ body: body ? JSON.stringify(body) : void 0,
21142
+ signal: controller.signal
21143
+ });
21144
+ } catch {
21145
+ clearTimeout(timeout);
21146
+ const retryController = new AbortController();
21147
+ const retryTimeout = setTimeout(() => retryController.abort(), REQUEST_TIMEOUT_MS2);
21148
+ try {
21149
+ await new Promise((r) => setTimeout(r, 500));
21150
+ response = await fetch(url, {
21151
+ method,
21152
+ headers,
21153
+ body: body ? JSON.stringify(body) : void 0,
21154
+ signal: retryController.signal
21155
+ });
21156
+ } finally {
21157
+ clearTimeout(retryTimeout);
21158
+ }
21159
+ }
20885
21160
  if (!response.ok) {
20886
- throw new Error(`Wiki API ${method} ${path32}: ${response.status} ${response.statusText}`);
21161
+ throw new Error(`Wiki API ${method} ${path34}: ${response.status} ${response.statusText}`);
20887
21162
  }
20888
21163
  return response.json();
20889
21164
  } finally {
@@ -21734,9 +22009,9 @@ var init_App2 = __esm({
21734
22009
  // src/lib/update-check.ts
21735
22010
  import { execSync as execSync11 } from "child_process";
21736
22011
  import { readFileSync as readFileSync16 } from "fs";
21737
- import path30 from "path";
22012
+ import path32 from "path";
21738
22013
  function getLocalVersion(packageRoot) {
21739
- const pkgPath = path30.join(packageRoot, "package.json");
22014
+ const pkgPath = path32.join(packageRoot, "package.json");
21740
22015
  const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
21741
22016
  return pkg.version;
21742
22017
  }
@@ -21827,9 +22102,9 @@ var init_update = __esm({
21827
22102
  });
21828
22103
 
21829
22104
  // src/bin/cli.ts
21830
- import { existsSync as existsSync21, readFileSync as readFileSync17, writeFileSync as writeFileSync9, readdirSync as readdirSync4, rmSync } from "fs";
21831
- import path31 from "path";
21832
- import os10 from "os";
22105
+ import { existsSync as existsSync21, readFileSync as readFileSync17, writeFileSync as writeFileSync9, readdirSync as readdirSync5, rmSync } from "fs";
22106
+ import path33 from "path";
22107
+ import os11 from "os";
21833
22108
  var args = process.argv.slice(2);
21834
22109
  if (args.includes("--global")) {
21835
22110
  process.stderr.write(
@@ -21902,12 +22177,19 @@ async function runClaudeInstall() {
21902
22177
  }
21903
22178
  }
21904
22179
  async function runClaudeCheck() {
21905
- const claudeDir = path31.join(os10.homedir(), ".claude");
21906
- const settingsPath = path31.join(claudeDir, "settings.json");
21907
- const claudeJsonPath = path31.join(os10.homedir(), ".claude.json");
22180
+ const claudeDir = path33.join(os11.homedir(), ".claude");
22181
+ const settingsPath = path33.join(claudeDir, "settings.json");
22182
+ const claudeJsonPath = path33.join(os11.homedir(), ".claude.json");
21908
22183
  let ok = true;
21909
22184
  if (existsSync21(settingsPath)) {
21910
- const settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
22185
+ let settings;
22186
+ try {
22187
+ settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
22188
+ } catch {
22189
+ console.log("\x1B[31m\u2717\x1B[0m settings.json is malformed (invalid JSON)");
22190
+ ok = false;
22191
+ settings = {};
22192
+ }
21911
22193
  const hasHooks = settings.hooks && Object.keys(settings.hooks).some((k) => {
21912
22194
  const groups = settings.hooks[k];
21913
22195
  return Array.isArray(groups) && groups.some((g) => {
@@ -21929,7 +22211,14 @@ async function runClaudeCheck() {
21929
22211
  ok = false;
21930
22212
  }
21931
22213
  if (existsSync21(claudeJsonPath)) {
21932
- const claudeJson = JSON.parse(readFileSync17(claudeJsonPath, "utf8"));
22214
+ let claudeJson;
22215
+ try {
22216
+ claudeJson = JSON.parse(readFileSync17(claudeJsonPath, "utf8"));
22217
+ } catch {
22218
+ console.log("\x1B[31m\u2717\x1B[0m claude.json is malformed (invalid JSON)");
22219
+ ok = false;
22220
+ claudeJson = {};
22221
+ }
21933
22222
  const hasMcp = claudeJson.mcpServers && (claudeJson.mcpServers["exe-mem"] || claudeJson.mcpServers["exe-os"]);
21934
22223
  if (hasMcp) {
21935
22224
  console.log("\x1B[32m\u2713\x1B[0m MCP server configured in claude.json");
@@ -21943,7 +22232,7 @@ async function runClaudeCheck() {
21943
22232
  console.log("\x1B[31m\u2717\x1B[0m claude.json not found");
21944
22233
  ok = false;
21945
22234
  }
21946
- const skillsDir = path31.join(claudeDir, "skills");
22235
+ const skillsDir = path33.join(claudeDir, "skills");
21947
22236
  if (existsSync21(skillsDir)) {
21948
22237
  console.log("\x1B[32m\u2713\x1B[0m Slash skills directory exists");
21949
22238
  } else {
@@ -21960,15 +22249,26 @@ async function runClaudeCheck() {
21960
22249
  async function runClaudeUninstall(flags = []) {
21961
22250
  const dryRun = flags.includes("--dry-run");
21962
22251
  const purge = flags.includes("--purge");
21963
- const homeDir = os10.homedir();
21964
- const claudeDir = path31.join(homeDir, ".claude");
21965
- const settingsPath = path31.join(claudeDir, "settings.json");
21966
- const claudeJsonPath = path31.join(homeDir, ".claude.json");
21967
- const exeOsDir = path31.join(homeDir, ".exe-os");
22252
+ const homeDir = os11.homedir();
22253
+ const claudeDir = path33.join(homeDir, ".claude");
22254
+ const settingsPath = path33.join(claudeDir, "settings.json");
22255
+ const claudeJsonPath = path33.join(homeDir, ".claude.json");
22256
+ const exeOsDir = path33.join(homeDir, ".exe-os");
21968
22257
  let removed = 0;
21969
22258
  const log = (msg) => console.log(dryRun ? `[dry-run] ${msg}` : msg);
22259
+ let settings = {};
21970
22260
  if (existsSync21(settingsPath)) {
21971
- const settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
22261
+ try {
22262
+ settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
22263
+ } catch {
22264
+ console.error("Your ~/.claude/settings.json appears malformed.");
22265
+ if (purge) {
22266
+ console.error("Skipping settings cleanup (--purge).");
22267
+ } else {
22268
+ console.error("Try running: exe-os claude install");
22269
+ process.exit(1);
22270
+ }
22271
+ }
21972
22272
  if (settings.hooks) {
21973
22273
  for (const key of Object.keys(settings.hooks)) {
21974
22274
  const groups = settings.hooks[key];
@@ -22005,32 +22305,43 @@ async function runClaudeUninstall(flags = []) {
22005
22305
  }
22006
22306
  }
22007
22307
  if (existsSync21(claudeJsonPath)) {
22008
- const claudeJson = JSON.parse(readFileSync17(claudeJsonPath, "utf8"));
22009
- if (claudeJson.mcpServers) {
22010
- let removedMcp = false;
22011
- for (const key of ["exe-mem", "exe-os"]) {
22012
- if (claudeJson.mcpServers[key]) {
22013
- if (!dryRun) delete claudeJson.mcpServers[key];
22014
- removedMcp = true;
22308
+ const raw = readFileSync17(claudeJsonPath, "utf8");
22309
+ if (raw.length > 1e6) {
22310
+ console.error("claude.json exceeds 1 MB \u2014 skipping parse.");
22311
+ } else {
22312
+ let claudeJson;
22313
+ try {
22314
+ claudeJson = JSON.parse(raw);
22315
+ } catch {
22316
+ console.error("claude.json is malformed JSON \u2014 skipping.");
22317
+ claudeJson = {};
22318
+ }
22319
+ if (claudeJson.mcpServers) {
22320
+ let removedMcp = false;
22321
+ for (const key of ["exe-mem", "exe-os"]) {
22322
+ if (claudeJson.mcpServers[key]) {
22323
+ if (!dryRun) delete claudeJson.mcpServers[key];
22324
+ removedMcp = true;
22325
+ }
22015
22326
  }
22016
- }
22017
- if (removedMcp) {
22018
- if (!dryRun) {
22019
- writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
22327
+ if (removedMcp) {
22328
+ if (!dryRun) {
22329
+ writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
22330
+ }
22331
+ log("\u2713 Removed exe-os MCP server from claude.json");
22332
+ removed++;
22020
22333
  }
22021
- log("\u2713 Removed exe-os MCP server from claude.json");
22022
- removed++;
22023
22334
  }
22024
22335
  }
22025
22336
  }
22026
- const skillsDir = path31.join(claudeDir, "skills");
22337
+ const skillsDir = path33.join(claudeDir, "skills");
22027
22338
  if (existsSync21(skillsDir)) {
22028
22339
  let skillCount = 0;
22029
22340
  try {
22030
- const entries = readdirSync4(skillsDir);
22341
+ const entries = readdirSync5(skillsDir);
22031
22342
  for (const entry of entries) {
22032
22343
  if (entry.startsWith("exe")) {
22033
- const fullPath = path31.join(skillsDir, entry);
22344
+ const fullPath = path33.join(skillsDir, entry);
22034
22345
  if (!dryRun) rmSync(fullPath, { recursive: true, force: true });
22035
22346
  skillCount++;
22036
22347
  }
@@ -22042,7 +22353,7 @@ async function runClaudeUninstall(flags = []) {
22042
22353
  removed++;
22043
22354
  }
22044
22355
  }
22045
- const claudeMdPath = path31.join(claudeDir, "CLAUDE.md");
22356
+ const claudeMdPath = path33.join(claudeDir, "CLAUDE.md");
22046
22357
  if (existsSync21(claudeMdPath)) {
22047
22358
  const content = readFileSync17(claudeMdPath, "utf8");
22048
22359
  const startMarker = "<!-- exe-os:orchestration-start -->";
@@ -22056,13 +22367,13 @@ async function runClaudeUninstall(flags = []) {
22056
22367
  removed++;
22057
22368
  }
22058
22369
  }
22059
- const agentsDir = path31.join(claudeDir, "agents");
22370
+ const agentsDir = path33.join(claudeDir, "agents");
22060
22371
  if (existsSync21(agentsDir)) {
22061
22372
  let agentCount = 0;
22062
22373
  try {
22063
- const entries = readdirSync4(agentsDir).filter((f) => f.endsWith(".md"));
22374
+ const entries = readdirSync5(agentsDir).filter((f) => f.endsWith(".md"));
22064
22375
  let knownNames = /* @__PURE__ */ new Set();
22065
- const rosterPath = path31.join(exeOsDir, "exe-employees.json");
22376
+ const rosterPath = path33.join(exeOsDir, "exe-employees.json");
22066
22377
  if (existsSync21(rosterPath)) {
22067
22378
  try {
22068
22379
  const roster = JSON.parse(readFileSync17(rosterPath, "utf8"));
@@ -22073,7 +22384,7 @@ async function runClaudeUninstall(flags = []) {
22073
22384
  for (const entry of entries) {
22074
22385
  const name = entry.replace(/\.md$/, "");
22075
22386
  if (knownNames.has(name)) {
22076
- if (!dryRun) rmSync(path31.join(agentsDir, entry), { force: true });
22387
+ if (!dryRun) rmSync(path33.join(agentsDir, entry), { force: true });
22077
22388
  agentCount++;
22078
22389
  }
22079
22390
  }
@@ -22084,13 +22395,13 @@ async function runClaudeUninstall(flags = []) {
22084
22395
  removed++;
22085
22396
  }
22086
22397
  }
22087
- const projectsDir = path31.join(claudeDir, "projects");
22398
+ const projectsDir = path33.join(claudeDir, "projects");
22088
22399
  if (existsSync21(projectsDir)) {
22089
22400
  let projectCount = 0;
22090
22401
  try {
22091
- const projects = readdirSync4(projectsDir);
22402
+ const projects = readdirSync5(projectsDir);
22092
22403
  for (const proj of projects) {
22093
- const projSettings = path31.join(projectsDir, proj, "settings.json");
22404
+ const projSettings = path33.join(projectsDir, proj, "settings.json");
22094
22405
  if (!existsSync21(projSettings)) continue;
22095
22406
  try {
22096
22407
  const pSettings = JSON.parse(readFileSync17(projSettings, "utf8"));
@@ -22118,16 +22429,24 @@ async function runClaudeUninstall(flags = []) {
22118
22429
  }
22119
22430
  try {
22120
22431
  const { execSync: execSync13 } = await import("child_process");
22121
- const exeBinPath = execSync13("which exe", { encoding: "utf-8" }).trim();
22122
- const binDir = path31.dirname(exeBinPath);
22432
+ const findExeBin3 = () => {
22433
+ try {
22434
+ return execSync13(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
22435
+ } catch {
22436
+ return null;
22437
+ }
22438
+ };
22439
+ const exeBinPath = findExeBin3();
22440
+ if (!exeBinPath) throw new Error("exe-os not found in PATH");
22441
+ const binDir = path33.dirname(exeBinPath);
22123
22442
  let symlinkCount = 0;
22124
- const rosterPath = path31.join(exeOsDir, "exe-employees.json");
22443
+ const rosterPath = path33.join(exeOsDir, "exe-employees.json");
22125
22444
  if (existsSync21(rosterPath)) {
22126
22445
  const roster = JSON.parse(readFileSync17(rosterPath, "utf8"));
22127
22446
  for (const emp of roster) {
22128
22447
  if (emp.name === "exe") continue;
22129
22448
  for (const suffix of ["", "-opencode"]) {
22130
- const linkPath = path31.join(binDir, `${emp.name}${suffix}`);
22449
+ const linkPath = path33.join(binDir, `${emp.name}${suffix}`);
22131
22450
  if (existsSync21(linkPath)) {
22132
22451
  if (!dryRun) rmSync(linkPath, { force: true });
22133
22452
  symlinkCount++;
@@ -22166,7 +22485,7 @@ async function checkForUpdateOnBoot() {
22166
22485
  const config = await loadConfig2();
22167
22486
  if (!config.autoUpdate.checkOnBoot) return;
22168
22487
  const { checkForUpdate: checkForUpdate2 } = await init_update().then(() => update_exports);
22169
- const packageRoot = path31.resolve(
22488
+ const packageRoot = path33.resolve(
22170
22489
  new URL("../..", import.meta.url).pathname
22171
22490
  );
22172
22491
  const result = checkForUpdate2(packageRoot);
@@ -22195,13 +22514,16 @@ async function runActivate(key) {
22195
22514
  mirrorLicenseKey2(key);
22196
22515
  try {
22197
22516
  const license = await validateLicense2(key);
22198
- if (!license.valid) {
22517
+ if (!license.valid && license.error === "offline") {
22518
+ console.log("\u26A0 Activated in offline mode \u2014 will verify when connected");
22519
+ } else if (!license.valid) {
22199
22520
  process.stderr.write(`License invalid: ${license.plan || "unknown"}
22200
22521
  `);
22201
22522
  process.exit(1);
22523
+ } else {
22524
+ const planLabel = license.plan === "pro" ? "Solopreneur" : license.plan.charAt(0).toUpperCase() + license.plan.slice(1);
22525
+ console.log(`Plan activated: ${planLabel}`);
22202
22526
  }
22203
- const planLabel = license.plan === "pro" ? "Solopreneur" : license.plan.charAt(0).toUpperCase() + license.plan.slice(1);
22204
- console.log(`Plan activated: ${planLabel}`);
22205
22527
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
22206
22528
  const ask2 = (prompt) => new Promise((resolve) => rl.question(prompt, (a) => resolve(a.trim())));
22207
22529
  let employees = await loadEmployees2();
@@ -22222,7 +22544,7 @@ async function runActivate(key) {
22222
22544
  const idTemplate = getIdentityTemplate(identityKey);
22223
22545
  if (idTemplate) {
22224
22546
  const idPath = identityPath2(name);
22225
- const dir = path31.dirname(idPath);
22547
+ const dir = path33.dirname(idPath);
22226
22548
  if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
22227
22549
  fs8.writeFileSync(idPath, idTemplate.replace(/^agent_id: \w+/m, `agent_id: ${name}`), "utf-8");
22228
22550
  }