@askexenow/exe-os 0.8.37 → 0.8.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/bin/backfill-conversations.js +66 -60
  2. package/dist/bin/backfill-responses.js +7 -8
  3. package/dist/bin/backfill-vectors.js +1 -8
  4. package/dist/bin/cleanup-stale-review-tasks.js +1 -8
  5. package/dist/bin/cli.js +520 -325
  6. package/dist/bin/exe-assign.js +7 -8
  7. package/dist/bin/exe-boot.js +54 -21
  8. package/dist/bin/exe-call.js +9 -4
  9. package/dist/bin/exe-cloud.js +37 -3
  10. package/dist/bin/exe-doctor.js +1 -8
  11. package/dist/bin/exe-export-behaviors.js +4 -11
  12. package/dist/bin/exe-forget.js +1 -8
  13. package/dist/bin/exe-gateway.js +72 -30
  14. package/dist/bin/exe-heartbeat.js +4 -11
  15. package/dist/bin/exe-kill.js +1 -8
  16. package/dist/bin/exe-launch-agent.js +51 -14
  17. package/dist/bin/exe-link.js +13 -3
  18. package/dist/bin/exe-new-employee.js +35 -10
  19. package/dist/bin/exe-pending-messages.js +1 -8
  20. package/dist/bin/exe-pending-notifications.js +1 -8
  21. package/dist/bin/exe-pending-reviews.js +4 -11
  22. package/dist/bin/exe-review.js +7 -8
  23. package/dist/bin/exe-search.js +10 -11
  24. package/dist/bin/exe-session-cleanup.js +11 -12
  25. package/dist/bin/exe-status.js +1 -8
  26. package/dist/bin/exe-team.js +1 -8
  27. package/dist/bin/git-sweep.js +7 -8
  28. package/dist/bin/graph-backfill.js +1 -8
  29. package/dist/bin/graph-export.js +1 -8
  30. package/dist/bin/install.js +9 -0
  31. package/dist/bin/scan-tasks.js +7 -8
  32. package/dist/bin/setup.js +396 -245
  33. package/dist/bin/shard-migrate.js +1 -8
  34. package/dist/bin/wiki-sync.js +1 -8
  35. package/dist/gateway/index.js +30 -30
  36. package/dist/hooks/bug-report-worker.js +4 -11
  37. package/dist/hooks/commit-complete.js +7 -8
  38. package/dist/hooks/error-recall.js +11 -12
  39. package/dist/hooks/ingest-worker.js +24 -9
  40. package/dist/hooks/instructions-loaded.js +7 -8
  41. package/dist/hooks/notification.js +7 -8
  42. package/dist/hooks/post-compact.js +7 -8
  43. package/dist/hooks/pre-compact.js +7 -8
  44. package/dist/hooks/pre-tool-use.js +7 -8
  45. package/dist/hooks/prompt-ingest-worker.js +19 -4
  46. package/dist/hooks/prompt-submit.js +14 -9
  47. package/dist/hooks/response-ingest-worker.js +20 -5
  48. package/dist/hooks/session-end.js +11 -12
  49. package/dist/hooks/session-start.js +11 -12
  50. package/dist/hooks/stop.js +7 -8
  51. package/dist/hooks/subagent-stop.js +7 -8
  52. package/dist/hooks/summary-worker.js +24 -9
  53. package/dist/index.js +11 -5
  54. package/dist/lib/cloud-sync.js +19 -2
  55. package/dist/lib/employee-templates.js +5 -0
  56. package/dist/lib/exe-daemon.js +24 -8
  57. package/dist/lib/hybrid-search.js +10 -11
  58. package/dist/lib/identity-templates.js +16 -7
  59. package/dist/lib/license.js +43 -2
  60. package/dist/lib/schedules.js +1 -8
  61. package/dist/lib/store.js +7 -8
  62. package/dist/mcp/server.js +184 -113
  63. package/dist/mcp/tools/list-tasks.js +35 -27
  64. package/dist/runtime/index.js +7 -2
  65. package/dist/tui/App.js +44 -5
  66. package/package.json +4 -2
package/dist/bin/cli.js CHANGED
@@ -621,6 +621,10 @@ async function mergeHooks(packageRoot, homeDir = os3.homedir()) {
621
621
  if (!settings.hooks) {
622
622
  settings.hooks = {};
623
623
  }
624
+ if (settings.hooks && typeof settings.hooks !== "object") {
625
+ console.warn("[exe-os] Unexpected hooks schema in settings.json \u2014 skipping hook installation");
626
+ return { added: 0, skipped: 0 };
627
+ }
624
628
  const hooksToRegister = [
625
629
  {
626
630
  event: "PostToolUse",
@@ -783,6 +787,11 @@ async function mergeHooks(packageRoot, homeDir = os3.homedir()) {
783
787
  if (!settings.hooks[event]) {
784
788
  settings.hooks[event] = [];
785
789
  }
790
+ if (!Array.isArray(settings.hooks[event])) {
791
+ console.warn(`[exe-os] Hook event "${event}" has unexpected structure \u2014 skipping`);
792
+ skipped++;
793
+ continue;
794
+ }
786
795
  const existing = settings.hooks[event];
787
796
  const correctCommand = group.hooks[0]?.command ?? "";
788
797
  const alreadyCorrect = existing.some(
@@ -2054,7 +2063,7 @@ __export(shard_manager_exports, {
2054
2063
  shardExists: () => shardExists
2055
2064
  });
2056
2065
  import path6 from "path";
2057
- import { existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
2066
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync } from "fs";
2058
2067
  import { createClient as createClient2 } from "@libsql/client";
2059
2068
  function initShardManager(encryptionKey) {
2060
2069
  _encryptionKey = encryptionKey;
@@ -2093,8 +2102,7 @@ function shardExists(projectName) {
2093
2102
  }
2094
2103
  function listShards() {
2095
2104
  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", ""));
2105
+ return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2098
2106
  }
2099
2107
  async function ensureShardSchema(client) {
2100
2108
  await client.execute("PRAGMA journal_mode = WAL");
@@ -2377,6 +2385,12 @@ async function writeMemory(record) {
2377
2385
  supersedes_id: record.supersedes_id ?? null
2378
2386
  };
2379
2387
  _pendingRecords.push(dbRow);
2388
+ const MAX_PENDING = 1e3;
2389
+ if (_pendingRecords.length > MAX_PENDING) {
2390
+ const dropped = _pendingRecords.length - MAX_PENDING;
2391
+ _pendingRecords = _pendingRecords.slice(-MAX_PENDING);
2392
+ console.warn(`[store] Dropped ${dropped} oldest pending records (overflow)`);
2393
+ }
2380
2394
  if (_flushTimer === null) {
2381
2395
  _flushTimer = setInterval(() => {
2382
2396
  void flushBatch();
@@ -3347,65 +3361,72 @@ async function backfillConversations(options) {
3347
3361
  process.stderr.write(`[backfill-conversations] Found ${files.length} JSONL files to process
3348
3362
  `);
3349
3363
  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(`
3364
+ const BATCH_SIZE = 50;
3365
+ const MAX_DEDUP_SIZE = 5e4;
3366
+ for (let batchStart = 0; batchStart < files.length; batchStart += BATCH_SIZE) {
3367
+ const batch = files.slice(batchStart, batchStart + BATCH_SIZE);
3368
+ for (const file of batch) {
3369
+ stats.filesScanned++;
3370
+ if (existingPaths.size < MAX_DEDUP_SIZE && existingPaths.has(file)) {
3371
+ stats.skippedDedup++;
3372
+ continue;
3373
+ }
3374
+ const conv = await parseConversation(file);
3375
+ if (conv.totalMessages < MIN_MESSAGES) {
3376
+ stats.skippedTooShort++;
3377
+ continue;
3378
+ }
3379
+ const summary = buildSummary(conv);
3380
+ if (options.dryRun) {
3381
+ process.stdout.write(`
3364
3382
  \u2500\u2500\u2500 ${file} \u2500\u2500\u2500
3365
3383
  `);
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}
3384
+ process.stdout.write(`Project: ${conv.projectName} | Messages: ${conv.totalMessages}`);
3385
+ process.stdout.write(` | Tools: ${Object.keys(conv.toolCounts).length}`);
3386
+ process.stdout.write(` | Files: ${conv.filesTouched.size}
3369
3387
  `);
3370
- const firstPrompt = conv.userMessages[0];
3371
- if (firstPrompt) {
3372
- process.stdout.write(`First prompt: ${firstPrompt.slice(0, 120)}
3388
+ const firstPrompt = conv.userMessages[0];
3389
+ if (firstPrompt) {
3390
+ process.stdout.write(`First prompt: ${firstPrompt.slice(0, 120)}
3373
3391
  `);
3392
+ }
3393
+ stats.conversationsStored++;
3394
+ continue;
3395
+ }
3396
+ let vector = null;
3397
+ if (daemonConnected) {
3398
+ try {
3399
+ vector = await embedViaClient(summary, "low");
3400
+ if (!vector) stats.embedFailed++;
3401
+ } catch {
3402
+ stats.embedFailed++;
3403
+ }
3404
+ }
3405
+ await writeMemory({
3406
+ id: crypto2.randomUUID(),
3407
+ agent_id: conv.agentId,
3408
+ agent_role: conv.agentId === "exe" ? "COO" : "specialist",
3409
+ session_id: conv.sessionId,
3410
+ timestamp: conv.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
3411
+ tool_name: TOOL_NAME,
3412
+ project_name: conv.projectName,
3413
+ has_error: conv.errorCount > 0,
3414
+ raw_text: summary,
3415
+ vector,
3416
+ source_path: file,
3417
+ source_type: "conversation"
3418
+ });
3419
+ if (existingPaths.size < MAX_DEDUP_SIZE) {
3420
+ existingPaths.add(file);
3374
3421
  }
3375
3422
  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
3423
+ if (stats.filesScanned % 50 === 0) {
3424
+ process.stderr.write(
3425
+ `[backfill-conversations] Progress: ${stats.filesScanned}/${files.length} files, ${stats.conversationsStored} stored
3406
3426
  `
3407
- );
3408
- await flushBatch();
3427
+ );
3428
+ await flushBatch();
3429
+ }
3409
3430
  }
3410
3431
  }
3411
3432
  if (!options.dryRun) {
@@ -3463,6 +3484,7 @@ __export(employee_templates_exports, {
3463
3484
  buildCustomEmployeePrompt: () => buildCustomEmployeePrompt,
3464
3485
  getSessionPrompt: () => getSessionPrompt,
3465
3486
  getTemplate: () => getTemplate,
3487
+ getTemplateByRole: () => getTemplateByRole,
3466
3488
  personalizePrompt: () => personalizePrompt,
3467
3489
  renderClientCOOTemplate: () => renderClientCOOTemplate
3468
3490
  });
@@ -3478,6 +3500,10 @@ function buildCustomEmployeePrompt(name, role) {
3478
3500
  function getTemplate(name) {
3479
3501
  return TEMPLATES[name];
3480
3502
  }
3503
+ function getTemplateByRole(role) {
3504
+ const lower = role.toLowerCase();
3505
+ return Object.values(TEMPLATES).find((t) => t.role.toLowerCase() === lower);
3506
+ }
3481
3507
  function personalizePrompt(prompt, templateName, actualName) {
3482
3508
  if (templateName === actualName) return prompt;
3483
3509
  const escaped = templateName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -4262,48 +4288,67 @@ async function downloadModel(opts) {
4262
4288
  const tmpPath = destPath + ".tmp";
4263
4289
  await mkdir5(destDir, { recursive: true });
4264
4290
  if (existsSync9(destPath)) {
4265
- const hash2 = await fileHash(destPath);
4266
- if (hash2 === EXPECTED_SHA256) {
4291
+ const hash = await fileHash(destPath);
4292
+ if (hash === EXPECTED_SHA256) {
4267
4293
  return destPath;
4268
4294
  }
4269
4295
  }
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);
4296
+ const MAX_RETRIES2 = 3;
4297
+ const DOWNLOAD_TIMEOUT_MS = 3e5;
4298
+ let lastErr;
4276
4299
  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));
4300
+ for (let attempt = 1; attempt <= MAX_RETRIES2; attempt++) {
4301
+ try {
4302
+ if (existsSync9(tmpPath)) unlinkSync3(tmpPath);
4303
+ const response = await fetchFn(GGUF_URL, {
4304
+ redirect: "follow",
4305
+ signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
4306
+ });
4307
+ if (!response.ok || !response.body) {
4308
+ throw new Error(`Download failed: HTTP ${response.status}`);
4309
+ }
4310
+ const contentLength = Number(response.headers.get("content-length") ?? EXPECTED_SIZE);
4311
+ const hash = createHash("sha256");
4312
+ const fileStream = createWriteStream(tmpPath);
4313
+ const reader = response.body.getReader();
4314
+ try {
4315
+ while (true) {
4316
+ const { done, value } = await reader.read();
4317
+ if (done) break;
4318
+ if (!fileStream.write(value)) {
4319
+ await new Promise((resolve) => fileStream.once("drain", resolve));
4320
+ }
4321
+ hash.update(value);
4322
+ downloaded += value.byteLength;
4323
+ onProgress?.(downloaded, contentLength);
4324
+ }
4325
+ } finally {
4326
+ fileStream.end();
4327
+ await new Promise((resolve, reject) => {
4328
+ fileStream.on("finish", resolve);
4329
+ fileStream.on("error", reject);
4330
+ });
4331
+ }
4332
+ const actualHash = hash.digest("hex");
4333
+ if (actualHash !== EXPECTED_SHA256) {
4334
+ unlinkSync3(tmpPath);
4335
+ throw new Error(
4336
+ `SHA256 mismatch: expected ${EXPECTED_SHA256}, got ${actualHash}`
4337
+ );
4338
+ }
4339
+ renameSync3(tmpPath, destPath);
4340
+ return destPath;
4341
+ } catch (err) {
4342
+ lastErr = err instanceof Error ? err : new Error(String(err));
4343
+ if (attempt < MAX_RETRIES2) {
4344
+ process.stderr.write(`
4345
+ Download attempt ${attempt} failed, retrying...
4346
+ `);
4347
+ if (existsSync9(tmpPath)) unlinkSync3(tmpPath);
4286
4348
  }
4287
- hash.update(value);
4288
- downloaded += value.byteLength;
4289
- onProgress?.(downloaded, contentLength);
4290
4349
  }
4291
- } finally {
4292
- fileStream.end();
4293
- await new Promise((resolve, reject) => {
4294
- fileStream.on("finish", resolve);
4295
- fileStream.on("error", reject);
4296
- });
4297
- }
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
4350
  }
4305
- renameSync3(tmpPath, destPath);
4306
- return destPath;
4351
+ throw lastErr;
4307
4352
  }
4308
4353
  async function fileHash(filePath) {
4309
4354
  return new Promise((resolve, reject) => {
@@ -4406,6 +4451,8 @@ __export(license_exports, {
4406
4451
  loadLicense: () => loadLicense,
4407
4452
  mirrorLicenseKey: () => mirrorLicenseKey,
4408
4453
  saveLicense: () => saveLicense,
4454
+ startLicenseRevalidation: () => startLicenseRevalidation,
4455
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
4409
4456
  validateLicense: () => validateLicense
4410
4457
  });
4411
4458
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync3 } from "fs";
@@ -4530,14 +4577,23 @@ async function validateLicense(apiKey, deviceId) {
4530
4577
  } catch {
4531
4578
  const cached = await getCachedLicense();
4532
4579
  if (cached) return cached;
4533
- return FREE_LICENSE;
4580
+ return { ...FREE_LICENSE, valid: false, error: "offline" };
4581
+ }
4582
+ }
4583
+ function getCacheAgeMs() {
4584
+ try {
4585
+ const { statSync: statSync2 } = __require("fs");
4586
+ const s = statSync2(CACHE_PATH);
4587
+ return Date.now() - s.mtimeMs;
4588
+ } catch {
4589
+ return Infinity;
4534
4590
  }
4535
4591
  }
4536
4592
  async function checkLicense() {
4537
4593
  const key = loadLicense();
4538
4594
  if (!key) return FREE_LICENSE;
4539
4595
  const cached = await getCachedLicense();
4540
- if (cached) return cached;
4596
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
4541
4597
  const deviceId = loadDeviceId();
4542
4598
  return validateLicense(key, deviceId);
4543
4599
  }
@@ -4658,7 +4714,28 @@ async function assertVpsLicense(opts) {
4658
4714
  `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
4715
  );
4660
4716
  }
4661
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
4717
+ function startLicenseRevalidation(intervalMs = 36e5) {
4718
+ if (_revalTimer) return;
4719
+ _revalTimer = setInterval(async () => {
4720
+ try {
4721
+ const license = await checkLicense();
4722
+ if (!license.valid) {
4723
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
4724
+ }
4725
+ } catch {
4726
+ }
4727
+ }, intervalMs);
4728
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
4729
+ _revalTimer.unref();
4730
+ }
4731
+ }
4732
+ function stopLicenseRevalidation() {
4733
+ if (_revalTimer) {
4734
+ clearInterval(_revalTimer);
4735
+ _revalTimer = null;
4736
+ }
4737
+ }
4738
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
4662
4739
  var init_license = __esm({
4663
4740
  "src/lib/license.ts"() {
4664
4741
  "use strict";
@@ -4688,6 +4765,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
4688
4765
  employeeLimit: 1,
4689
4766
  memoryLimit: 5e3
4690
4767
  };
4768
+ CACHE_MAX_AGE_MS = 36e5;
4769
+ _revalTimer = null;
4691
4770
  }
4692
4771
  });
4693
4772
 
@@ -4701,7 +4780,7 @@ __export(identity_exports, {
4701
4780
  updateIdentity: () => updateIdentity
4702
4781
  });
4703
4782
  import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
4704
- import { readdirSync } from "fs";
4783
+ import { readdirSync as readdirSync2 } from "fs";
4705
4784
  import path12 from "path";
4706
4785
  import { createHash as createHash2 } from "crypto";
4707
4786
  function ensureDir() {
@@ -4783,7 +4862,7 @@ async function updateIdentity(agentId, content, updatedBy) {
4783
4862
  }
4784
4863
  function listIdentities() {
4785
4864
  ensureDir();
4786
- const files = readdirSync(IDENTITY_DIR).filter((f) => f.endsWith(".md"));
4865
+ const files = readdirSync2(IDENTITY_DIR).filter((f) => f.endsWith(".md"));
4787
4866
  const results = [];
4788
4867
  for (const file of files) {
4789
4868
  const agentId = file.replace(".md", "");
@@ -4830,6 +4909,7 @@ var init_identity = __esm({
4830
4909
  var identity_templates_exports = {};
4831
4910
  __export(identity_templates_exports, {
4832
4911
  IDENTITY_TEMPLATES: () => IDENTITY_TEMPLATES,
4912
+ PLAN_MODE_COMPAT: () => PLAN_MODE_COMPAT,
4833
4913
  POST_WORK_CHECKLIST: () => POST_WORK_CHECKLIST,
4834
4914
  getTemplate: () => getTemplate2,
4835
4915
  getTemplateForTitle: () => getTemplateForTitle
@@ -4849,10 +4929,18 @@ function getTemplateForTitle(title) {
4849
4929
  if (t.includes("review") || t.includes("audit") || t.includes("qa")) return IDENTITY_TEMPLATES["staff-code-reviewer"];
4850
4930
  return null;
4851
4931
  }
4852
- var POST_WORK_CHECKLIST, IDENTITY_TEMPLATES;
4932
+ var PLAN_MODE_COMPAT, POST_WORK_CHECKLIST, IDENTITY_TEMPLATES;
4853
4933
  var init_identity_templates = __esm({
4854
4934
  "src/lib/identity-templates.ts"() {
4855
4935
  "use strict";
4936
+ PLAN_MODE_COMPAT = `
4937
+ ## Plan Mode Compatibility
4938
+ If tool execution is unavailable (e.g., CC plan mode), switch to planning:
4939
+ - Reason about the task and create a written plan
4940
+ - Document what tools you would call and with what parameters
4941
+ - Output structured text that can be acted on when tools become available
4942
+ Do not repeatedly attempt tool calls that fail \u2014 switch to planning mode.
4943
+ `;
4856
4944
  POST_WORK_CHECKLIST = `
4857
4945
  5. Check for pending reviews (list_tasks status='needs_review' where you are reviewer) \u2014 reviews are work, process before new tasks
4858
4946
  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 +5020,7 @@ Never say "I have no memories" without first searching broadly. Your memory may
4932
5020
  - **update_identity** \u2014 rewrite any agent's identity when role/responsibilities change (exe/founder only)
4933
5021
  - **get_identity** \u2014 read any agent's identity for coordination
4934
5022
  - **send_message** \u2014 direct intercom to employees
4935
-
5023
+ ${PLAN_MODE_COMPAT}
4936
5024
  ## Completion Workflow
4937
5025
 
4938
5026
  1. Read the task file and verify the deliverable matches the brief
@@ -5003,7 +5091,7 @@ You are \${agent_id}. CTO. You hold deep context on the entire codebase, archite
5003
5091
  - **store_behavior** \u2014 record corrections for engineers (p0 = always injected)
5004
5092
  - **get_identity** \u2014 read any agent's identity for review context
5005
5093
  - **query_relationships** \u2014 GraphRAG entity connections for architecture analysis
5006
-
5094
+ ${PLAN_MODE_COMPAT}
5007
5095
  ## Completion Workflow
5008
5096
 
5009
5097
  1. Read ARCHITECTURE.md before starting work on any repo
@@ -5070,7 +5158,7 @@ You are \${agent_id}. CMO. You hold deep context on design, branding, storytelli
5070
5158
  - **update_task** \u2014 mark tasks done with result summary
5071
5159
  - **store_memory** \u2014 report completions with brand alignment notes, SEO considerations
5072
5160
  - **get_identity** \u2014 read team identities for brand-consistent communication
5073
-
5161
+ ${PLAN_MODE_COMPAT}
5074
5162
  ## Completion Workflow
5075
5163
 
5076
5164
  1. Read the task file and understand the brief \u2014 tone, format, channel requirements
@@ -5137,7 +5225,7 @@ You are a principal engineer. You write production-grade code with zero shortcut
5137
5225
  - **recall_my_memory** \u2014 check past work, patterns, gotchas in this project
5138
5226
  - **store_memory** \u2014 report completions for org visibility
5139
5227
  - **ask_team_memory** \u2014 pull context from colleagues when specs reference their work
5140
-
5228
+ ${PLAN_MODE_COMPAT}
5141
5229
  ## Completion Workflow
5142
5230
 
5143
5231
  1. Read ARCHITECTURE.md if it exists \u2014 understand architecture before changing anything
@@ -5197,7 +5285,7 @@ You are the content production specialist. You turn scripts and creative briefs
5197
5285
  - **update_task** \u2014 mark tasks done with result summary
5198
5286
  - **recall_my_memory** \u2014 check past work: which models worked, which prompts produced good results
5199
5287
  - **store_memory** \u2014 report completions with production decisions for future reference
5200
-
5288
+ ${PLAN_MODE_COMPAT}
5201
5289
  ## Completion Workflow
5202
5290
 
5203
5291
  1. Read the task file \u2014 understand the brief, check budget constraints
@@ -5269,7 +5357,7 @@ You are the AI Product Lead \u2014 the competitive intelligence engine. You stud
5269
5357
  - **update_task** \u2014 mark tasks done with analysis results
5270
5358
  - **store_memory** \u2014 persist competitive analyses, evaluations, recommendations
5271
5359
  - **create_task** \u2014 when a feature is worth building, spec it for the CTO
5272
-
5360
+ ${PLAN_MODE_COMPAT}
5273
5361
  ## Completion Workflow
5274
5362
 
5275
5363
  1. Read the task \u2014 understand what capability is needed
@@ -5332,7 +5420,7 @@ You are \${agent_id}. Staff Code Reviewer and System Auditor. Last line of defen
5332
5420
  - **store_behavior** \u2014 record new patterns
5333
5421
  - **update_task** \u2014 mark reviews done with structured findings
5334
5422
  - **create_task** \u2014 assign fixes to the CTO
5335
-
5423
+ ${PLAN_MODE_COMPAT}
5336
5424
  ## Completion Workflow
5337
5425
 
5338
5426
  1. Read the task brief and understand the audit scope
@@ -5354,10 +5442,27 @@ __export(setup_wizard_exports, {
5354
5442
  validateModel: () => validateModel
5355
5443
  });
5356
5444
  import crypto3 from "crypto";
5357
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
5445
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4, unlinkSync as unlinkSync4 } from "fs";
5358
5446
  import os4 from "os";
5359
5447
  import path13 from "path";
5360
5448
  import { createInterface as createInterface2 } from "readline";
5449
+ function loadSetupState() {
5450
+ try {
5451
+ return JSON.parse(readFileSync8(SETUP_STATE_PATH, "utf8"));
5452
+ } catch {
5453
+ return { completedSteps: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
5454
+ }
5455
+ }
5456
+ function saveSetupState(state) {
5457
+ mkdirSync5(path13.dirname(SETUP_STATE_PATH), { recursive: true });
5458
+ writeFileSync4(SETUP_STATE_PATH, JSON.stringify(state, null, 2));
5459
+ }
5460
+ function clearSetupState() {
5461
+ try {
5462
+ unlinkSync4(SETUP_STATE_PATH);
5463
+ } catch {
5464
+ }
5465
+ }
5361
5466
  function ask(rl, prompt) {
5362
5467
  return new Promise((resolve) => {
5363
5468
  const doAsk = () => {
@@ -5397,88 +5502,122 @@ async function runSetupWizard(opts = {}) {
5397
5502
  rl.close();
5398
5503
  return;
5399
5504
  }
5505
+ const state = loadSetupState();
5506
+ if (state.completedSteps.length > 0) {
5507
+ log(`Resuming setup from step ${Math.max(...state.completedSteps) + 1}...`);
5508
+ }
5400
5509
  if (existsSync12(LEGACY_LANCE_PATH)) {
5401
5510
  log("\u26A0 Found v1.0 LanceDB at ~/.exe-os/local.lance");
5402
5511
  log(" v1.1 uses libSQL (SQLite). Your existing memories are not automatically migrated.");
5403
5512
  log(" The old directory will not be modified or deleted.");
5404
5513
  log("");
5405
5514
  }
5406
- const existingKey = await getMasterKey();
5407
- if (existingKey) {
5408
- log("Encryption key already exists \u2014 skipping generation.");
5515
+ if (!state.completedSteps.includes(1)) {
5516
+ const existingKey = await getMasterKey();
5517
+ if (existingKey) {
5518
+ log("Encryption key already exists \u2014 skipping generation.");
5519
+ } else {
5520
+ log("Generating 256-bit encryption key...");
5521
+ const key = crypto3.randomBytes(32);
5522
+ await setMasterKey(key);
5523
+ log("Encryption key generated and stored securely.");
5524
+ }
5525
+ state.completedSteps.push(1);
5526
+ saveSetupState(state);
5409
5527
  } 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.");
5528
+ log("Step 1 already complete \u2014 skipping.");
5414
5529
  }
5415
5530
  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
5531
  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.");
5532
+ if (!state.completedSteps.includes(2)) {
5533
+ log("Exe Cloud: your memories are end-to-end encrypted, compressed, and");
5534
+ log("backed up on Exe Cloud. Free for all plans. We can't read your data \u2014");
5535
+ log("only your encryption key can decrypt it.");
5536
+ try {
5537
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
5538
+ const deviceId = loadDeviceId2();
5539
+ const res = await fetch("https://askexe.com/cloud/auth/auto-provision", {
5540
+ method: "POST",
5541
+ headers: { "Content-Type": "application/json" },
5542
+ body: JSON.stringify({ deviceId }),
5543
+ signal: AbortSignal.timeout(1e4)
5544
+ });
5545
+ if (res.ok) {
5546
+ const data = await res.json();
5547
+ if (data.apiKey) {
5548
+ cloudConfig = { apiKey: data.apiKey, endpoint: "https://askexe.com/cloud" };
5549
+ const { saveLicense: saveLicense3, mirrorLicenseKey: mirrorLicenseKey3 } = await Promise.resolve().then(() => (init_license(), license_exports));
5550
+ saveLicense3(data.apiKey);
5551
+ mirrorLicenseKey3(data.apiKey);
5552
+ log("Cloud sync activated automatically.");
5553
+ }
5437
5554
  }
5555
+ } catch {
5556
+ log("Cloud sync will activate when online.");
5438
5557
  }
5439
- } catch {
5440
- log("Cloud sync will activate when online.");
5558
+ state.completedSteps.push(2);
5559
+ saveSetupState(state);
5560
+ } else {
5561
+ log("Step 2 already complete \u2014 skipping.");
5441
5562
  }
5442
5563
  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.");
5564
+ if (!state.completedSteps.includes(3)) {
5565
+ if (!skipModel) {
5566
+ log("Note: jina-embeddings-v5-text-small is licensed CC-BY-NC-4.0 (non-commercial)");
5567
+ log("");
5568
+ await downloadModel({
5569
+ destDir: MODELS_DIR,
5570
+ onProgress: (downloaded, total) => {
5571
+ const pct = (downloaded / total * 100).toFixed(1);
5572
+ const dlMB = (downloaded / 1e6).toFixed(0);
5573
+ const totalMB = (total / 1e6).toFixed(0);
5574
+ process.stderr.write(`\rDownloading model: ${pct}% (${dlMB}/${totalMB} MB)`);
5575
+ }
5576
+ });
5577
+ process.stderr.write("\n");
5578
+ log("Model downloaded and verified.");
5579
+ }
5580
+ state.completedSteps.push(3);
5581
+ saveSetupState(state);
5582
+ } else {
5583
+ log("Step 3 already complete \u2014 skipping.");
5457
5584
  }
5458
- if (!skipModel && !skipModelValidation) {
5459
- await validateModel(log);
5585
+ if (!state.completedSteps.includes(4)) {
5586
+ if (!skipModel && !skipModelValidation) {
5587
+ await validateModel(log);
5588
+ }
5589
+ state.completedSteps.push(4);
5590
+ saveSetupState(state);
5591
+ } else {
5592
+ log("Step 4 already complete \u2014 skipping.");
5460
5593
  }
5461
5594
  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 = {};
5595
+ if (!state.completedSteps.includes(5)) {
5596
+ if (cloudConfig) {
5597
+ config.cloud = cloudConfig;
5598
+ }
5599
+ await saveConfig(config);
5600
+ log("");
5470
5601
  try {
5471
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
5602
+ const claudeJsonPath = path13.join(os4.homedir(), ".claude.json");
5603
+ let claudeJson = {};
5604
+ try {
5605
+ claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
5606
+ } catch {
5607
+ }
5608
+ if (!claudeJson.projects) claudeJson.projects = {};
5609
+ const projects = claudeJson.projects;
5610
+ for (const dir of [process.cwd(), os4.homedir()]) {
5611
+ if (!projects[dir]) projects[dir] = {};
5612
+ projects[dir].hasTrustDialogAccepted = true;
5613
+ }
5614
+ writeFileSync4(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5472
5615
  } catch {
5473
5616
  }
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 {
5617
+ state.completedSteps.push(5);
5618
+ saveSetupState(state);
5619
+ } else {
5620
+ log("Step 5 already complete \u2014 skipping.");
5482
5621
  }
5483
5622
  const {
5484
5623
  loadEmployees: loadEmployees2,
@@ -5487,7 +5626,7 @@ async function runSetupWizard(opts = {}) {
5487
5626
  registerBinSymlinks: registerBinSymlinks2,
5488
5627
  EMPLOYEES_PATH: EMPLOYEES_PATH2
5489
5628
  } = await Promise.resolve().then(() => (init_employees(), employees_exports));
5490
- const { getTemplate: getEmployeeTemplate } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5629
+ const { getTemplateByRole: getTemplateByRole2 } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5491
5630
  const { identityPath: identityPath2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
5492
5631
  const { getTemplate: getIdentityTemplate } = await Promise.resolve().then(() => (init_identity_templates(), identity_templates_exports));
5493
5632
  const {
@@ -5497,152 +5636,178 @@ async function runSetupWizard(opts = {}) {
5497
5636
  validateLicense: validateLicense2
5498
5637
  } = await Promise.resolve().then(() => (init_license(), license_exports));
5499
5638
  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)");
5639
+ let cooName = "exe";
5640
+ if (!state.completedSteps.includes(6)) {
5641
+ log("=== Your Team ===");
5564
5642
  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.");
5643
+ log("Every install starts with a COO \u2014 your right-hand operator.");
5644
+ log("They hold the big picture: priorities, progress, and blockers.");
5645
+ log("You talk to them. They coordinate everyone else.");
5568
5646
  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
- }
5647
+ const cooNameInput = await ask(rl, "Name your COO (default: exe): ");
5648
+ cooName = (cooNameInput || "exe").toLowerCase();
5649
+ let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
5650
+ if (!employees.some((e) => e.name === cooName)) {
5651
+ const { DEFAULT_EXE: DEFAULT_EXE2, personalizePrompt: personalizePrompt2 } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5652
+ const cooEmployee = {
5653
+ name: cooName,
5654
+ role: "COO",
5655
+ systemPrompt: personalizePrompt2(DEFAULT_EXE2.systemPrompt, "exe", cooName),
5656
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5657
+ templateName: "exe",
5658
+ templateVersion: 1
5659
+ };
5660
+ employees = addEmployee2(employees, cooEmployee);
5661
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
5662
+ }
5663
+ const cooIdentityContent = getIdentityTemplate("coo");
5664
+ if (cooIdentityContent) {
5665
+ const cooIdPath = identityPath2(cooName);
5666
+ mkdirSync5(path13.dirname(cooIdPath), { recursive: true });
5667
+ const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
5668
+ writeFileSync4(cooIdPath, replaced, "utf-8");
5669
+ }
5670
+ registerBinSymlinks2(cooName);
5671
+ createdEmployees.push({ name: cooName, role: "COO" });
5672
+ state.completedSteps.push(6);
5673
+ saveSetupState(state);
5674
+ } else {
5675
+ log("Step 6 already complete \u2014 skipping.");
5676
+ const roster = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
5677
+ const existingCoo = roster.find((e) => e.role === "COO");
5678
+ if (existingCoo) cooName = existingCoo.name;
5585
5679
  }
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)`);
5680
+ log("");
5681
+ if (!state.completedSteps.includes(7)) {
5682
+ log("=== Meet Your Specialists ===");
5589
5683
  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");
5684
+ log("Your COO coordinates specialists. Here's who you can hire:");
5685
+ log("");
5686
+ log(" CTO (default: yoshi)");
5687
+ log(" Your head of engineering. Architecture, code reviews, tech decisions.");
5688
+ log(" Manages your projects and delegates to engineers when there's parallel work.");
5689
+ log("");
5690
+ log(" CMO (default: mari)");
5691
+ log(" Design, brand, content, SEO. Builds your visual identity, writes");
5692
+ log(" your copy, and gets you found online. Delegates to content specialists.");
5693
+ log("");
5694
+ log("Why separate roles instead of one AI that does everything?");
5695
+ log("");
5696
+ log("Memory saturates. One agent juggling architecture decisions AND landing page");
5697
+ log("copy AND CI/CD AND social media loses context on all of them. Competing");
5698
+ log("priorities \u2014 should I fix the auth bug or write the blog post? When you split");
5699
+ log("responsibilities, each specialist stays sharp because they stay focused.");
5700
+ log("Your COO connects them so nothing falls through the cracks.");
5701
+ log("");
5702
+ log("This is how real companies scale \u2014 you're just doing it with AI");
5703
+ log("instead of headcount.");
5704
+ log("");
5705
+ await ask(rl, "Press Enter to continue. ");
5706
+ state.completedSteps.push(7);
5707
+ saveSetupState(state);
5708
+ } else {
5709
+ log("Step 7 already complete \u2014 skipping.");
5710
+ }
5711
+ if (!state.completedSteps.includes(8)) {
5712
+ let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
5713
+ let license;
5714
+ try {
5715
+ license = await checkLicense2();
5716
+ } catch {
5717
+ license = { valid: true, plan: "free", email: "", expiresAt: null, deviceLimit: 1, employeeLimit: 2, memoryLimit: 1e3 };
5718
+ }
5719
+ if (license.plan === "free") {
5720
+ log("Your plan: Free \u2014 1 employee (your COO)");
5721
+ log("");
5722
+ log("The CTO and CMO are available on Solopreneur ($97/mo) \u2014 full engineering");
5723
+ log("and marketing team, unlimited tasks, priority support.");
5724
+ log("Get your key at https://askexe.com, then paste it here.");
5725
+ log("");
5726
+ const licenseInput = await ask(rl, "License key (or press Enter to skip): ");
5727
+ if (licenseInput.startsWith("exe_sk_")) {
5728
+ saveLicense2(licenseInput);
5729
+ mirrorLicenseKey2(licenseInput);
5730
+ try {
5731
+ license = await validateLicense2(licenseInput);
5732
+ } catch {
5733
+ log("Couldn't reach the license server \u2014 your key has been saved.");
5734
+ log("Run exe-os --activate <key> anytime to finish activation.");
5735
+ }
5736
+ } else if (!licenseInput) {
5737
+ log("You can activate anytime with: exe-os --activate <key>");
5738
+ } else {
5739
+ log("That doesn't look like a license key (should start with exe_sk_).");
5740
+ log("You can activate anytime with: exe-os --activate <key>");
5741
+ }
5742
+ }
5743
+ if (license.plan !== "free") {
5744
+ const planName = license.plan === "pro" ? "Solopreneur" : license.plan.charAt(0).toUpperCase() + license.plan.slice(1);
5745
+ log(`Your plan: ${planName} (up to ${license.employeeLimit} employees)`);
5746
+ log("");
5747
+ const createCto = await ask(rl, "Create your CTO? (Y/n): ");
5748
+ if (createCto.toLowerCase() !== "n") {
5749
+ const ctoTemplate = getTemplateByRole2("CTO");
5750
+ const ctoDefault = ctoTemplate?.name ?? "cto";
5751
+ const ctoNameInput = await ask(rl, `Name your CTO (default: ${ctoDefault}): `);
5752
+ const ctoName = (ctoNameInput || ctoDefault).toLowerCase();
5753
+ if (!employees.some((e) => e.name === ctoName)) {
5754
+ const { personalizePrompt: personalizeCto } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5755
+ const ctoEmployee = {
5756
+ name: ctoName,
5757
+ role: "CTO",
5758
+ systemPrompt: personalizeCto(ctoTemplate?.systemPrompt ?? "", ctoDefault, ctoName),
5759
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
5760
+ };
5761
+ employees = addEmployee2(employees, ctoEmployee);
5762
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
5763
+ }
5764
+ const ctoIdentityContent = getIdentityTemplate("cto");
5765
+ if (ctoIdentityContent) {
5766
+ const ctoIdPath = identityPath2(ctoName);
5767
+ mkdirSync5(path13.dirname(ctoIdPath), { recursive: true });
5768
+ const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
5769
+ writeFileSync4(ctoIdPath, replaced, "utf-8");
5770
+ }
5771
+ registerBinSymlinks2(ctoName);
5772
+ createdEmployees.push({ name: ctoName, role: "CTO" });
5773
+ log(`Created ${ctoName} (CTO)`);
5774
+ }
5775
+ const createCmo = await ask(rl, "Create your CMO? (Y/n): ");
5776
+ if (createCmo.toLowerCase() !== "n") {
5777
+ const cmoTemplate = getTemplateByRole2("CMO");
5778
+ const cmoDefault = cmoTemplate?.name ?? "cmo";
5779
+ const cmoNameInput = await ask(rl, `Name your CMO (default: ${cmoDefault}): `);
5780
+ const cmoName = (cmoNameInput || cmoDefault).toLowerCase();
5781
+ if (!employees.some((e) => e.name === cmoName)) {
5782
+ const { personalizePrompt: personalizeCmo } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
5783
+ const cmoEmployee = {
5784
+ name: cmoName,
5785
+ role: "CMO",
5786
+ systemPrompt: personalizeCmo(cmoTemplate?.systemPrompt ?? "", cmoDefault, cmoName),
5787
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
5788
+ };
5789
+ employees = addEmployee2(employees, cmoEmployee);
5790
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
5791
+ }
5792
+ const cmoIdentityContent = getIdentityTemplate("cmo");
5793
+ if (cmoIdentityContent) {
5794
+ const cmoIdPath = identityPath2(cmoName);
5795
+ mkdirSync5(path13.dirname(cmoIdPath), { recursive: true });
5796
+ const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
5797
+ writeFileSync4(cmoIdPath, replaced, "utf-8");
5798
+ }
5799
+ registerBinSymlinks2(cmoName);
5800
+ createdEmployees.push({ name: cmoName, role: "CMO" });
5801
+ log(`Created ${cmoName} (CMO)`);
5639
5802
  }
5640
- registerBinSymlinks2(cmoName);
5641
- createdEmployees.push({ name: cmoName, role: "CMO" });
5642
- log(`Created ${cmoName} (CMO)`);
5803
+ log("");
5643
5804
  }
5644
- log("");
5805
+ state.completedSteps.push(8);
5806
+ saveSetupState(state);
5807
+ } else {
5808
+ log("Step 8 already complete \u2014 skipping.");
5645
5809
  }
5810
+ clearSetupState();
5646
5811
  log("=== Two Ways to Work ===");
5647
5812
  log("");
5648
5813
  log(" 1. Claude Code mode");
@@ -5679,12 +5844,14 @@ async function runSetupWizard(opts = {}) {
5679
5844
  rl.close();
5680
5845
  }
5681
5846
  }
5847
+ var SETUP_STATE_PATH;
5682
5848
  var init_setup_wizard = __esm({
5683
5849
  "src/lib/setup-wizard.ts"() {
5684
5850
  "use strict";
5685
5851
  init_config();
5686
5852
  init_keychain();
5687
5853
  init_model_downloader();
5854
+ SETUP_STATE_PATH = path13.join(os4.homedir(), ".exe-os", "setup-state.json");
5688
5855
  }
5689
5856
  });
5690
5857
 
@@ -16354,8 +16521,8 @@ import path23 from "path";
16354
16521
  import os8 from "os";
16355
16522
  import {
16356
16523
  readFileSync as readFileSync13,
16357
- readdirSync as readdirSync2,
16358
- unlinkSync as unlinkSync4,
16524
+ readdirSync as readdirSync3,
16525
+ unlinkSync as unlinkSync5,
16359
16526
  existsSync as existsSync17,
16360
16527
  rmdirSync
16361
16528
  } from "fs";
@@ -16837,7 +17004,7 @@ var init_tasks_crud = __esm({
16837
17004
 
16838
17005
  // src/lib/tasks-review.ts
16839
17006
  import path25 from "path";
16840
- import { existsSync as existsSync19, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
17007
+ import { existsSync as existsSync19, readdirSync as readdirSync4, unlinkSync as unlinkSync6 } from "fs";
16841
17008
  async function countPendingReviews() {
16842
17009
  const client = getClient();
16843
17010
  const result = await client.execute({
@@ -16959,9 +17126,9 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
16959
17126
  try {
16960
17127
  const cacheDir = path25.join(EXE_AI_DIR, "session-cache");
16961
17128
  if (existsSync19(cacheDir)) {
16962
- for (const f of readdirSync3(cacheDir)) {
17129
+ for (const f of readdirSync4(cacheDir)) {
16963
17130
  if (f.startsWith("review-notified-")) {
16964
- unlinkSync5(path25.join(cacheDir, f));
17131
+ unlinkSync6(path25.join(cacheDir, f));
16965
17132
  }
16966
17133
  }
16967
17134
  }
@@ -17562,7 +17729,7 @@ __export(tasks_exports, {
17562
17729
  writeCheckpoint: () => writeCheckpoint
17563
17730
  });
17564
17731
  import path28 from "path";
17565
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, unlinkSync as unlinkSync6 } from "fs";
17732
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7 } from "fs";
17566
17733
  async function createTask(input) {
17567
17734
  const result = await createTaskCore(input);
17568
17735
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -17588,7 +17755,7 @@ async function updateTask(input) {
17588
17755
  writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
17589
17756
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
17590
17757
  try {
17591
- unlinkSync6(cachePath);
17758
+ unlinkSync7(cachePath);
17592
17759
  } catch {
17593
17760
  }
17594
17761
  }
@@ -18007,7 +18174,7 @@ import { readFileSync as readFileSync15, writeFileSync as writeFileSync8, mkdirS
18007
18174
  import path29 from "path";
18008
18175
  import os9 from "os";
18009
18176
  import { fileURLToPath as fileURLToPath4 } from "url";
18010
- import { unlinkSync as unlinkSync7 } from "fs";
18177
+ import { unlinkSync as unlinkSync8 } from "fs";
18011
18178
  function spawnLockPath(sessionName) {
18012
18179
  return path29.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
18013
18180
  }
@@ -18039,7 +18206,7 @@ function acquireSpawnLock2(sessionName) {
18039
18206
  }
18040
18207
  function releaseSpawnLock2(sessionName) {
18041
18208
  try {
18042
- unlinkSync7(spawnLockPath(sessionName));
18209
+ unlinkSync8(spawnLockPath(sessionName));
18043
18210
  } catch {
18044
18211
  }
18045
18212
  }
@@ -21827,7 +21994,7 @@ var init_update = __esm({
21827
21994
  });
21828
21995
 
21829
21996
  // src/bin/cli.ts
21830
- import { existsSync as existsSync21, readFileSync as readFileSync17, writeFileSync as writeFileSync9, readdirSync as readdirSync4, rmSync } from "fs";
21997
+ import { existsSync as existsSync21, readFileSync as readFileSync17, writeFileSync as writeFileSync9, readdirSync as readdirSync5, rmSync } from "fs";
21831
21998
  import path31 from "path";
21832
21999
  import os10 from "os";
21833
22000
  var args = process.argv.slice(2);
@@ -21907,7 +22074,14 @@ async function runClaudeCheck() {
21907
22074
  const claudeJsonPath = path31.join(os10.homedir(), ".claude.json");
21908
22075
  let ok = true;
21909
22076
  if (existsSync21(settingsPath)) {
21910
- const settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
22077
+ let settings;
22078
+ try {
22079
+ settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
22080
+ } catch {
22081
+ console.log("\x1B[31m\u2717\x1B[0m settings.json is malformed (invalid JSON)");
22082
+ ok = false;
22083
+ settings = {};
22084
+ }
21911
22085
  const hasHooks = settings.hooks && Object.keys(settings.hooks).some((k) => {
21912
22086
  const groups = settings.hooks[k];
21913
22087
  return Array.isArray(groups) && groups.some((g) => {
@@ -21929,7 +22103,14 @@ async function runClaudeCheck() {
21929
22103
  ok = false;
21930
22104
  }
21931
22105
  if (existsSync21(claudeJsonPath)) {
21932
- const claudeJson = JSON.parse(readFileSync17(claudeJsonPath, "utf8"));
22106
+ let claudeJson;
22107
+ try {
22108
+ claudeJson = JSON.parse(readFileSync17(claudeJsonPath, "utf8"));
22109
+ } catch {
22110
+ console.log("\x1B[31m\u2717\x1B[0m claude.json is malformed (invalid JSON)");
22111
+ ok = false;
22112
+ claudeJson = {};
22113
+ }
21933
22114
  const hasMcp = claudeJson.mcpServers && (claudeJson.mcpServers["exe-mem"] || claudeJson.mcpServers["exe-os"]);
21934
22115
  if (hasMcp) {
21935
22116
  console.log("\x1B[32m\u2713\x1B[0m MCP server configured in claude.json");
@@ -21967,8 +22148,19 @@ async function runClaudeUninstall(flags = []) {
21967
22148
  const exeOsDir = path31.join(homeDir, ".exe-os");
21968
22149
  let removed = 0;
21969
22150
  const log = (msg) => console.log(dryRun ? `[dry-run] ${msg}` : msg);
22151
+ let settings = {};
21970
22152
  if (existsSync21(settingsPath)) {
21971
- const settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
22153
+ try {
22154
+ settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
22155
+ } catch {
22156
+ console.error("Your ~/.claude/settings.json appears malformed.");
22157
+ if (purge) {
22158
+ console.error("Skipping settings cleanup (--purge).");
22159
+ } else {
22160
+ console.error("Try running: exe-os claude install");
22161
+ process.exit(1);
22162
+ }
22163
+ }
21972
22164
  if (settings.hooks) {
21973
22165
  for (const key of Object.keys(settings.hooks)) {
21974
22166
  const groups = settings.hooks[key];
@@ -22027,7 +22219,7 @@ async function runClaudeUninstall(flags = []) {
22027
22219
  if (existsSync21(skillsDir)) {
22028
22220
  let skillCount = 0;
22029
22221
  try {
22030
- const entries = readdirSync4(skillsDir);
22222
+ const entries = readdirSync5(skillsDir);
22031
22223
  for (const entry of entries) {
22032
22224
  if (entry.startsWith("exe")) {
22033
22225
  const fullPath = path31.join(skillsDir, entry);
@@ -22060,7 +22252,7 @@ async function runClaudeUninstall(flags = []) {
22060
22252
  if (existsSync21(agentsDir)) {
22061
22253
  let agentCount = 0;
22062
22254
  try {
22063
- const entries = readdirSync4(agentsDir).filter((f) => f.endsWith(".md"));
22255
+ const entries = readdirSync5(agentsDir).filter((f) => f.endsWith(".md"));
22064
22256
  let knownNames = /* @__PURE__ */ new Set();
22065
22257
  const rosterPath = path31.join(exeOsDir, "exe-employees.json");
22066
22258
  if (existsSync21(rosterPath)) {
@@ -22088,7 +22280,7 @@ async function runClaudeUninstall(flags = []) {
22088
22280
  if (existsSync21(projectsDir)) {
22089
22281
  let projectCount = 0;
22090
22282
  try {
22091
- const projects = readdirSync4(projectsDir);
22283
+ const projects = readdirSync5(projectsDir);
22092
22284
  for (const proj of projects) {
22093
22285
  const projSettings = path31.join(projectsDir, proj, "settings.json");
22094
22286
  if (!existsSync21(projSettings)) continue;
@@ -22195,13 +22387,16 @@ async function runActivate(key) {
22195
22387
  mirrorLicenseKey2(key);
22196
22388
  try {
22197
22389
  const license = await validateLicense2(key);
22198
- if (!license.valid) {
22390
+ if (!license.valid && license.error === "offline") {
22391
+ console.log("\u26A0 Activated in offline mode \u2014 will verify when connected");
22392
+ } else if (!license.valid) {
22199
22393
  process.stderr.write(`License invalid: ${license.plan || "unknown"}
22200
22394
  `);
22201
22395
  process.exit(1);
22396
+ } else {
22397
+ const planLabel = license.plan === "pro" ? "Solopreneur" : license.plan.charAt(0).toUpperCase() + license.plan.slice(1);
22398
+ console.log(`Plan activated: ${planLabel}`);
22202
22399
  }
22203
- const planLabel = license.plan === "pro" ? "Solopreneur" : license.plan.charAt(0).toUpperCase() + license.plan.slice(1);
22204
- console.log(`Plan activated: ${planLabel}`);
22205
22400
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
22206
22401
  const ask2 = (prompt) => new Promise((resolve) => rl.question(prompt, (a) => resolve(a.trim())));
22207
22402
  let employees = await loadEmployees2();