@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/setup.js CHANGED
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
4
10
  var __esm = (fn, res) => function __init() {
5
11
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
12
  };
@@ -678,6 +684,8 @@ __export(license_exports, {
678
684
  loadLicense: () => loadLicense,
679
685
  mirrorLicenseKey: () => mirrorLicenseKey,
680
686
  saveLicense: () => saveLicense,
687
+ startLicenseRevalidation: () => startLicenseRevalidation,
688
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
681
689
  validateLicense: () => validateLicense
682
690
  });
683
691
  import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync5, mkdirSync } from "fs";
@@ -802,14 +810,23 @@ async function validateLicense(apiKey, deviceId) {
802
810
  } catch {
803
811
  const cached = await getCachedLicense();
804
812
  if (cached) return cached;
805
- return FREE_LICENSE;
813
+ return { ...FREE_LICENSE, valid: false, error: "offline" };
814
+ }
815
+ }
816
+ function getCacheAgeMs() {
817
+ try {
818
+ const { statSync: statSync2 } = __require("fs");
819
+ const s = statSync2(CACHE_PATH);
820
+ return Date.now() - s.mtimeMs;
821
+ } catch {
822
+ return Infinity;
806
823
  }
807
824
  }
808
825
  async function checkLicense() {
809
826
  const key = loadLicense();
810
827
  if (!key) return FREE_LICENSE;
811
828
  const cached = await getCachedLicense();
812
- if (cached) return cached;
829
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
813
830
  const deviceId = loadDeviceId();
814
831
  return validateLicense(key, deviceId);
815
832
  }
@@ -930,7 +947,28 @@ async function assertVpsLicense(opts) {
930
947
  `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.`
931
948
  );
932
949
  }
933
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
950
+ function startLicenseRevalidation(intervalMs = 36e5) {
951
+ if (_revalTimer) return;
952
+ _revalTimer = setInterval(async () => {
953
+ try {
954
+ const license = await checkLicense();
955
+ if (!license.valid) {
956
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
957
+ }
958
+ } catch {
959
+ }
960
+ }, intervalMs);
961
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
962
+ _revalTimer.unref();
963
+ }
964
+ }
965
+ function stopLicenseRevalidation() {
966
+ if (_revalTimer) {
967
+ clearInterval(_revalTimer);
968
+ _revalTimer = null;
969
+ }
970
+ }
971
+ 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;
934
972
  var init_license = __esm({
935
973
  "src/lib/license.ts"() {
936
974
  "use strict";
@@ -960,6 +998,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
960
998
  employeeLimit: 1,
961
999
  memoryLimit: 5e3
962
1000
  };
1001
+ CACHE_MAX_AGE_MS = 36e5;
1002
+ _revalTimer = null;
963
1003
  }
964
1004
  });
965
1005
 
@@ -1106,6 +1146,7 @@ __export(employee_templates_exports, {
1106
1146
  buildCustomEmployeePrompt: () => buildCustomEmployeePrompt,
1107
1147
  getSessionPrompt: () => getSessionPrompt,
1108
1148
  getTemplate: () => getTemplate,
1149
+ getTemplateByRole: () => getTemplateByRole,
1109
1150
  personalizePrompt: () => personalizePrompt,
1110
1151
  renderClientCOOTemplate: () => renderClientCOOTemplate
1111
1152
  });
@@ -1121,6 +1162,10 @@ function buildCustomEmployeePrompt(name, role) {
1121
1162
  function getTemplate(name) {
1122
1163
  return TEMPLATES[name];
1123
1164
  }
1165
+ function getTemplateByRole(role) {
1166
+ const lower = role.toLowerCase();
1167
+ return Object.values(TEMPLATES).find((t) => t.role.toLowerCase() === lower);
1168
+ }
1124
1169
  function personalizePrompt(prompt, templateName, actualName) {
1125
1170
  if (templateName === actualName) return prompt;
1126
1171
  const escaped = templateName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -1868,6 +1913,7 @@ var init_identity = __esm({
1868
1913
  var identity_templates_exports = {};
1869
1914
  __export(identity_templates_exports, {
1870
1915
  IDENTITY_TEMPLATES: () => IDENTITY_TEMPLATES,
1916
+ PLAN_MODE_COMPAT: () => PLAN_MODE_COMPAT,
1871
1917
  POST_WORK_CHECKLIST: () => POST_WORK_CHECKLIST,
1872
1918
  getTemplate: () => getTemplate2,
1873
1919
  getTemplateForTitle: () => getTemplateForTitle
@@ -1887,10 +1933,18 @@ function getTemplateForTitle(title) {
1887
1933
  if (t.includes("review") || t.includes("audit") || t.includes("qa")) return IDENTITY_TEMPLATES["staff-code-reviewer"];
1888
1934
  return null;
1889
1935
  }
1890
- var POST_WORK_CHECKLIST, IDENTITY_TEMPLATES;
1936
+ var PLAN_MODE_COMPAT, POST_WORK_CHECKLIST, IDENTITY_TEMPLATES;
1891
1937
  var init_identity_templates = __esm({
1892
1938
  "src/lib/identity-templates.ts"() {
1893
1939
  "use strict";
1940
+ PLAN_MODE_COMPAT = `
1941
+ ## Plan Mode Compatibility
1942
+ If tool execution is unavailable (e.g., CC plan mode), switch to planning:
1943
+ - Reason about the task and create a written plan
1944
+ - Document what tools you would call and with what parameters
1945
+ - Output structured text that can be acted on when tools become available
1946
+ Do not repeatedly attempt tool calls that fail \u2014 switch to planning mode.
1947
+ `;
1894
1948
  POST_WORK_CHECKLIST = `
1895
1949
  5. Check for pending reviews (list_tasks status='needs_review' where you are reviewer) \u2014 reviews are work, process before new tasks
1896
1950
  6. Check for blocked tasks (list_tasks status='blocked') \u2014 can you unblock it? Do it now. Can't? Escalate to exe immediately.
@@ -1970,7 +2024,7 @@ Never say "I have no memories" without first searching broadly. Your memory may
1970
2024
  - **update_identity** \u2014 rewrite any agent's identity when role/responsibilities change (exe/founder only)
1971
2025
  - **get_identity** \u2014 read any agent's identity for coordination
1972
2026
  - **send_message** \u2014 direct intercom to employees
1973
-
2027
+ ${PLAN_MODE_COMPAT}
1974
2028
  ## Completion Workflow
1975
2029
 
1976
2030
  1. Read the task file and verify the deliverable matches the brief
@@ -2041,7 +2095,7 @@ You are \${agent_id}. CTO. You hold deep context on the entire codebase, archite
2041
2095
  - **store_behavior** \u2014 record corrections for engineers (p0 = always injected)
2042
2096
  - **get_identity** \u2014 read any agent's identity for review context
2043
2097
  - **query_relationships** \u2014 GraphRAG entity connections for architecture analysis
2044
-
2098
+ ${PLAN_MODE_COMPAT}
2045
2099
  ## Completion Workflow
2046
2100
 
2047
2101
  1. Read ARCHITECTURE.md before starting work on any repo
@@ -2108,7 +2162,7 @@ You are \${agent_id}. CMO. You hold deep context on design, branding, storytelli
2108
2162
  - **update_task** \u2014 mark tasks done with result summary
2109
2163
  - **store_memory** \u2014 report completions with brand alignment notes, SEO considerations
2110
2164
  - **get_identity** \u2014 read team identities for brand-consistent communication
2111
-
2165
+ ${PLAN_MODE_COMPAT}
2112
2166
  ## Completion Workflow
2113
2167
 
2114
2168
  1. Read the task file and understand the brief \u2014 tone, format, channel requirements
@@ -2175,7 +2229,7 @@ You are a principal engineer. You write production-grade code with zero shortcut
2175
2229
  - **recall_my_memory** \u2014 check past work, patterns, gotchas in this project
2176
2230
  - **store_memory** \u2014 report completions for org visibility
2177
2231
  - **ask_team_memory** \u2014 pull context from colleagues when specs reference their work
2178
-
2232
+ ${PLAN_MODE_COMPAT}
2179
2233
  ## Completion Workflow
2180
2234
 
2181
2235
  1. Read ARCHITECTURE.md if it exists \u2014 understand architecture before changing anything
@@ -2235,7 +2289,7 @@ You are the content production specialist. You turn scripts and creative briefs
2235
2289
  - **update_task** \u2014 mark tasks done with result summary
2236
2290
  - **recall_my_memory** \u2014 check past work: which models worked, which prompts produced good results
2237
2291
  - **store_memory** \u2014 report completions with production decisions for future reference
2238
-
2292
+ ${PLAN_MODE_COMPAT}
2239
2293
  ## Completion Workflow
2240
2294
 
2241
2295
  1. Read the task file \u2014 understand the brief, check budget constraints
@@ -2307,7 +2361,7 @@ You are the AI Product Lead \u2014 the competitive intelligence engine. You stud
2307
2361
  - **update_task** \u2014 mark tasks done with analysis results
2308
2362
  - **store_memory** \u2014 persist competitive analyses, evaluations, recommendations
2309
2363
  - **create_task** \u2014 when a feature is worth building, spec it for the CTO
2310
-
2364
+ ${PLAN_MODE_COMPAT}
2311
2365
  ## Completion Workflow
2312
2366
 
2313
2367
  1. Read the task \u2014 understand what capability is needed
@@ -2370,7 +2424,7 @@ You are \${agent_id}. Staff Code Reviewer and System Auditor. Last line of defen
2370
2424
  - **store_behavior** \u2014 record new patterns
2371
2425
  - **update_task** \u2014 mark reviews done with structured findings
2372
2426
  - **create_task** \u2014 assign fixes to the CTO
2373
-
2427
+ ${PLAN_MODE_COMPAT}
2374
2428
  ## Completion Workflow
2375
2429
 
2376
2430
  1. Read the task brief and understand the audit scope
@@ -2388,7 +2442,7 @@ You are \${agent_id}. Staff Code Reviewer and System Auditor. Last line of defen
2388
2442
  // src/lib/setup-wizard.ts
2389
2443
  init_config();
2390
2444
  import crypto2 from "crypto";
2391
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
2445
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3 } from "fs";
2392
2446
  import os2 from "os";
2393
2447
  import path8 from "path";
2394
2448
  import { createInterface } from "readline";
@@ -2467,48 +2521,67 @@ async function downloadModel(opts) {
2467
2521
  const tmpPath = destPath + ".tmp";
2468
2522
  await mkdir3(destDir, { recursive: true });
2469
2523
  if (existsSync3(destPath)) {
2470
- const hash2 = await fileHash(destPath);
2471
- if (hash2 === EXPECTED_SHA256) {
2524
+ const hash = await fileHash(destPath);
2525
+ if (hash === EXPECTED_SHA256) {
2472
2526
  return destPath;
2473
2527
  }
2474
2528
  }
2475
- if (existsSync3(tmpPath)) unlinkSync(tmpPath);
2476
- const response = await fetchFn(GGUF_URL, { redirect: "follow" });
2477
- if (!response.ok || !response.body) {
2478
- throw new Error(`Download failed: HTTP ${response.status}`);
2479
- }
2480
- const contentLength = Number(response.headers.get("content-length") ?? EXPECTED_SIZE);
2529
+ const MAX_RETRIES = 3;
2530
+ const DOWNLOAD_TIMEOUT_MS = 3e5;
2531
+ let lastErr;
2481
2532
  let downloaded = 0;
2482
- const hash = createHash("sha256");
2483
- const fileStream = createWriteStream(tmpPath);
2484
- const reader = response.body.getReader();
2485
- try {
2486
- while (true) {
2487
- const { done, value } = await reader.read();
2488
- if (done) break;
2489
- if (!fileStream.write(value)) {
2490
- await new Promise((resolve) => fileStream.once("drain", resolve));
2533
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
2534
+ try {
2535
+ if (existsSync3(tmpPath)) unlinkSync(tmpPath);
2536
+ const response = await fetchFn(GGUF_URL, {
2537
+ redirect: "follow",
2538
+ signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
2539
+ });
2540
+ if (!response.ok || !response.body) {
2541
+ throw new Error(`Download failed: HTTP ${response.status}`);
2542
+ }
2543
+ const contentLength = Number(response.headers.get("content-length") ?? EXPECTED_SIZE);
2544
+ const hash = createHash("sha256");
2545
+ const fileStream = createWriteStream(tmpPath);
2546
+ const reader = response.body.getReader();
2547
+ try {
2548
+ while (true) {
2549
+ const { done, value } = await reader.read();
2550
+ if (done) break;
2551
+ if (!fileStream.write(value)) {
2552
+ await new Promise((resolve) => fileStream.once("drain", resolve));
2553
+ }
2554
+ hash.update(value);
2555
+ downloaded += value.byteLength;
2556
+ onProgress?.(downloaded, contentLength);
2557
+ }
2558
+ } finally {
2559
+ fileStream.end();
2560
+ await new Promise((resolve, reject) => {
2561
+ fileStream.on("finish", resolve);
2562
+ fileStream.on("error", reject);
2563
+ });
2564
+ }
2565
+ const actualHash = hash.digest("hex");
2566
+ if (actualHash !== EXPECTED_SHA256) {
2567
+ unlinkSync(tmpPath);
2568
+ throw new Error(
2569
+ `SHA256 mismatch: expected ${EXPECTED_SHA256}, got ${actualHash}`
2570
+ );
2571
+ }
2572
+ renameSync2(tmpPath, destPath);
2573
+ return destPath;
2574
+ } catch (err) {
2575
+ lastErr = err instanceof Error ? err : new Error(String(err));
2576
+ if (attempt < MAX_RETRIES) {
2577
+ process.stderr.write(`
2578
+ Download attempt ${attempt} failed, retrying...
2579
+ `);
2580
+ if (existsSync3(tmpPath)) unlinkSync(tmpPath);
2491
2581
  }
2492
- hash.update(value);
2493
- downloaded += value.byteLength;
2494
- onProgress?.(downloaded, contentLength);
2495
2582
  }
2496
- } finally {
2497
- fileStream.end();
2498
- await new Promise((resolve, reject) => {
2499
- fileStream.on("finish", resolve);
2500
- fileStream.on("error", reject);
2501
- });
2502
2583
  }
2503
- const actualHash = hash.digest("hex");
2504
- if (actualHash !== EXPECTED_SHA256) {
2505
- unlinkSync(tmpPath);
2506
- throw new Error(
2507
- `SHA256 mismatch: expected ${EXPECTED_SHA256}, got ${actualHash}`
2508
- );
2509
- }
2510
- renameSync2(tmpPath, destPath);
2511
- return destPath;
2584
+ throw lastErr;
2512
2585
  }
2513
2586
  async function fileHash(filePath) {
2514
2587
  return new Promise((resolve, reject) => {
@@ -2521,6 +2594,24 @@ async function fileHash(filePath) {
2521
2594
  }
2522
2595
 
2523
2596
  // src/lib/setup-wizard.ts
2597
+ var SETUP_STATE_PATH = path8.join(os2.homedir(), ".exe-os", "setup-state.json");
2598
+ function loadSetupState() {
2599
+ try {
2600
+ return JSON.parse(readFileSync6(SETUP_STATE_PATH, "utf8"));
2601
+ } catch {
2602
+ return { completedSteps: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
2603
+ }
2604
+ }
2605
+ function saveSetupState(state) {
2606
+ mkdirSync3(path8.dirname(SETUP_STATE_PATH), { recursive: true });
2607
+ writeFileSync3(SETUP_STATE_PATH, JSON.stringify(state, null, 2));
2608
+ }
2609
+ function clearSetupState() {
2610
+ try {
2611
+ unlinkSync3(SETUP_STATE_PATH);
2612
+ } catch {
2613
+ }
2614
+ }
2524
2615
  function ask(rl, prompt) {
2525
2616
  return new Promise((resolve) => {
2526
2617
  const doAsk = () => {
@@ -2560,88 +2651,122 @@ async function runSetupWizard(opts = {}) {
2560
2651
  rl.close();
2561
2652
  return;
2562
2653
  }
2654
+ const state = loadSetupState();
2655
+ if (state.completedSteps.length > 0) {
2656
+ log(`Resuming setup from step ${Math.max(...state.completedSteps) + 1}...`);
2657
+ }
2563
2658
  if (existsSync8(LEGACY_LANCE_PATH)) {
2564
2659
  log("\u26A0 Found v1.0 LanceDB at ~/.exe-os/local.lance");
2565
2660
  log(" v1.1 uses libSQL (SQLite). Your existing memories are not automatically migrated.");
2566
2661
  log(" The old directory will not be modified or deleted.");
2567
2662
  log("");
2568
2663
  }
2569
- const existingKey = await getMasterKey();
2570
- if (existingKey) {
2571
- log("Encryption key already exists \u2014 skipping generation.");
2664
+ if (!state.completedSteps.includes(1)) {
2665
+ const existingKey = await getMasterKey();
2666
+ if (existingKey) {
2667
+ log("Encryption key already exists \u2014 skipping generation.");
2668
+ } else {
2669
+ log("Generating 256-bit encryption key...");
2670
+ const key = crypto2.randomBytes(32);
2671
+ await setMasterKey(key);
2672
+ log("Encryption key generated and stored securely.");
2673
+ }
2674
+ state.completedSteps.push(1);
2675
+ saveSetupState(state);
2572
2676
  } else {
2573
- log("Generating 256-bit encryption key...");
2574
- const key = crypto2.randomBytes(32);
2575
- await setMasterKey(key);
2576
- log("Encryption key generated and stored securely.");
2677
+ log("Step 1 already complete \u2014 skipping.");
2577
2678
  }
2578
2679
  log("");
2579
- log("Exe Cloud: your memories are end-to-end encrypted, compressed, and");
2580
- log("backed up on Exe Cloud. Free for all plans. We can't read your data \u2014");
2581
- log("only your encryption key can decrypt it.");
2582
2680
  let cloudConfig;
2583
- try {
2584
- const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
2585
- const deviceId = loadDeviceId2();
2586
- const res = await fetch("https://askexe.com/cloud/auth/auto-provision", {
2587
- method: "POST",
2588
- headers: { "Content-Type": "application/json" },
2589
- body: JSON.stringify({ deviceId }),
2590
- signal: AbortSignal.timeout(1e4)
2591
- });
2592
- if (res.ok) {
2593
- const data = await res.json();
2594
- if (data.apiKey) {
2595
- cloudConfig = { apiKey: data.apiKey, endpoint: "https://askexe.com/cloud" };
2596
- const { saveLicense: saveLicense3, mirrorLicenseKey: mirrorLicenseKey3 } = await Promise.resolve().then(() => (init_license(), license_exports));
2597
- saveLicense3(data.apiKey);
2598
- mirrorLicenseKey3(data.apiKey);
2599
- log("Cloud sync activated automatically.");
2681
+ if (!state.completedSteps.includes(2)) {
2682
+ log("Exe Cloud: your memories are end-to-end encrypted, compressed, and");
2683
+ log("backed up on Exe Cloud. Free for all plans. We can't read your data \u2014");
2684
+ log("only your encryption key can decrypt it.");
2685
+ try {
2686
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
2687
+ const deviceId = loadDeviceId2();
2688
+ const res = await fetch("https://askexe.com/cloud/auth/auto-provision", {
2689
+ method: "POST",
2690
+ headers: { "Content-Type": "application/json" },
2691
+ body: JSON.stringify({ deviceId }),
2692
+ signal: AbortSignal.timeout(1e4)
2693
+ });
2694
+ if (res.ok) {
2695
+ const data = await res.json();
2696
+ if (data.apiKey) {
2697
+ cloudConfig = { apiKey: data.apiKey, endpoint: "https://askexe.com/cloud" };
2698
+ const { saveLicense: saveLicense3, mirrorLicenseKey: mirrorLicenseKey3 } = await Promise.resolve().then(() => (init_license(), license_exports));
2699
+ saveLicense3(data.apiKey);
2700
+ mirrorLicenseKey3(data.apiKey);
2701
+ log("Cloud sync activated automatically.");
2702
+ }
2600
2703
  }
2704
+ } catch {
2705
+ log("Cloud sync will activate when online.");
2601
2706
  }
2602
- } catch {
2603
- log("Cloud sync will activate when online.");
2707
+ state.completedSteps.push(2);
2708
+ saveSetupState(state);
2709
+ } else {
2710
+ log("Step 2 already complete \u2014 skipping.");
2604
2711
  }
2605
2712
  log("");
2606
- if (!skipModel2) {
2607
- log("Note: jina-embeddings-v5-text-small is licensed CC-BY-NC-4.0 (non-commercial)");
2608
- log("");
2609
- await downloadModel({
2610
- destDir: MODELS_DIR,
2611
- onProgress: (downloaded, total) => {
2612
- const pct = (downloaded / total * 100).toFixed(1);
2613
- const dlMB = (downloaded / 1e6).toFixed(0);
2614
- const totalMB = (total / 1e6).toFixed(0);
2615
- process.stderr.write(`\rDownloading model: ${pct}% (${dlMB}/${totalMB} MB)`);
2616
- }
2617
- });
2618
- process.stderr.write("\n");
2619
- log("Model downloaded and verified.");
2713
+ if (!state.completedSteps.includes(3)) {
2714
+ if (!skipModel2) {
2715
+ log("Note: jina-embeddings-v5-text-small is licensed CC-BY-NC-4.0 (non-commercial)");
2716
+ log("");
2717
+ await downloadModel({
2718
+ destDir: MODELS_DIR,
2719
+ onProgress: (downloaded, total) => {
2720
+ const pct = (downloaded / total * 100).toFixed(1);
2721
+ const dlMB = (downloaded / 1e6).toFixed(0);
2722
+ const totalMB = (total / 1e6).toFixed(0);
2723
+ process.stderr.write(`\rDownloading model: ${pct}% (${dlMB}/${totalMB} MB)`);
2724
+ }
2725
+ });
2726
+ process.stderr.write("\n");
2727
+ log("Model downloaded and verified.");
2728
+ }
2729
+ state.completedSteps.push(3);
2730
+ saveSetupState(state);
2731
+ } else {
2732
+ log("Step 3 already complete \u2014 skipping.");
2620
2733
  }
2621
- if (!skipModel2 && !skipModelValidation) {
2622
- await validateModel(log);
2734
+ if (!state.completedSteps.includes(4)) {
2735
+ if (!skipModel2 && !skipModelValidation) {
2736
+ await validateModel(log);
2737
+ }
2738
+ state.completedSteps.push(4);
2739
+ saveSetupState(state);
2740
+ } else {
2741
+ log("Step 4 already complete \u2014 skipping.");
2623
2742
  }
2624
2743
  const config = await loadConfig();
2625
- if (cloudConfig) {
2626
- config.cloud = cloudConfig;
2627
- }
2628
- await saveConfig(config);
2629
- log("");
2630
- try {
2631
- const claudeJsonPath = path8.join(os2.homedir(), ".claude.json");
2632
- let claudeJson = {};
2744
+ if (!state.completedSteps.includes(5)) {
2745
+ if (cloudConfig) {
2746
+ config.cloud = cloudConfig;
2747
+ }
2748
+ await saveConfig(config);
2749
+ log("");
2633
2750
  try {
2634
- claudeJson = JSON.parse(readFileSync6(claudeJsonPath, "utf8"));
2751
+ const claudeJsonPath = path8.join(os2.homedir(), ".claude.json");
2752
+ let claudeJson = {};
2753
+ try {
2754
+ claudeJson = JSON.parse(readFileSync6(claudeJsonPath, "utf8"));
2755
+ } catch {
2756
+ }
2757
+ if (!claudeJson.projects) claudeJson.projects = {};
2758
+ const projects = claudeJson.projects;
2759
+ for (const dir of [process.cwd(), os2.homedir()]) {
2760
+ if (!projects[dir]) projects[dir] = {};
2761
+ projects[dir].hasTrustDialogAccepted = true;
2762
+ }
2763
+ writeFileSync3(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
2635
2764
  } catch {
2636
2765
  }
2637
- if (!claudeJson.projects) claudeJson.projects = {};
2638
- const projects = claudeJson.projects;
2639
- for (const dir of [process.cwd(), os2.homedir()]) {
2640
- if (!projects[dir]) projects[dir] = {};
2641
- projects[dir].hasTrustDialogAccepted = true;
2642
- }
2643
- writeFileSync3(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
2644
- } catch {
2766
+ state.completedSteps.push(5);
2767
+ saveSetupState(state);
2768
+ } else {
2769
+ log("Step 5 already complete \u2014 skipping.");
2645
2770
  }
2646
2771
  const {
2647
2772
  loadEmployees: loadEmployees2,
@@ -2650,7 +2775,7 @@ async function runSetupWizard(opts = {}) {
2650
2775
  registerBinSymlinks: registerBinSymlinks2,
2651
2776
  EMPLOYEES_PATH: EMPLOYEES_PATH2
2652
2777
  } = await Promise.resolve().then(() => (init_employees(), employees_exports));
2653
- const { getTemplate: getEmployeeTemplate } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
2778
+ const { getTemplateByRole: getTemplateByRole2 } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
2654
2779
  const { identityPath: identityPath2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
2655
2780
  const { getTemplate: getIdentityTemplate } = await Promise.resolve().then(() => (init_identity_templates(), identity_templates_exports));
2656
2781
  const {
@@ -2660,152 +2785,178 @@ async function runSetupWizard(opts = {}) {
2660
2785
  validateLicense: validateLicense2
2661
2786
  } = await Promise.resolve().then(() => (init_license(), license_exports));
2662
2787
  const createdEmployees = [];
2663
- log("=== Your Team ===");
2664
- log("");
2665
- log("Every install starts with a COO \u2014 your right-hand operator.");
2666
- log("They hold the big picture: priorities, progress, and blockers.");
2667
- log("You talk to them. They coordinate everyone else.");
2668
- log("");
2669
- const cooNameInput = await ask(rl, "Name your COO (default: exe): ");
2670
- const cooName = (cooNameInput || "exe").toLowerCase();
2671
- let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
2672
- if (!employees.some((e) => e.name === cooName)) {
2673
- const { DEFAULT_EXE: DEFAULT_EXE2, personalizePrompt: personalizePrompt2 } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
2674
- const cooEmployee = {
2675
- name: cooName,
2676
- role: "COO",
2677
- systemPrompt: personalizePrompt2(DEFAULT_EXE2.systemPrompt, "exe", cooName),
2678
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2679
- templateName: "exe",
2680
- templateVersion: 1
2681
- };
2682
- employees = addEmployee2(employees, cooEmployee);
2683
- await saveEmployees2(employees, EMPLOYEES_PATH2);
2684
- }
2685
- const cooIdentityContent = getIdentityTemplate("coo");
2686
- if (cooIdentityContent) {
2687
- const cooIdPath = identityPath2(cooName);
2688
- mkdirSync3(path8.dirname(cooIdPath), { recursive: true });
2689
- const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
2690
- writeFileSync3(cooIdPath, replaced, "utf-8");
2691
- }
2692
- registerBinSymlinks2(cooName);
2693
- createdEmployees.push({ name: cooName, role: "COO" });
2694
- log("");
2695
- log("=== Meet Your Specialists ===");
2696
- log("");
2697
- log("Your COO coordinates specialists. Here's who you can hire:");
2698
- log("");
2699
- log(" CTO (default: yoshi)");
2700
- log(" Your head of engineering. Architecture, code reviews, tech decisions.");
2701
- log(" Manages your projects and delegates to engineers when there's parallel work.");
2702
- log("");
2703
- log(" CMO (default: mari)");
2704
- log(" Design, brand, content, SEO. Builds your visual identity, writes");
2705
- log(" your copy, and gets you found online. Delegates to content specialists.");
2706
- log("");
2707
- log("Why separate roles instead of one AI that does everything?");
2708
- log("");
2709
- log("Memory saturates. One agent juggling architecture decisions AND landing page");
2710
- log("copy AND CI/CD AND social media loses context on all of them. Competing");
2711
- log("priorities \u2014 should I fix the auth bug or write the blog post? When you split");
2712
- log("responsibilities, each specialist stays sharp because they stay focused.");
2713
- log("Your COO connects them so nothing falls through the cracks.");
2714
- log("");
2715
- log("This is how real companies scale \u2014 you're just doing it with AI");
2716
- log("instead of headcount.");
2717
- log("");
2718
- await ask(rl, "Press Enter to continue. ");
2719
- let license;
2720
- try {
2721
- license = await checkLicense2();
2722
- } catch {
2723
- license = { valid: true, plan: "free", email: "", expiresAt: null, deviceLimit: 1, employeeLimit: 2, memoryLimit: 1e3 };
2724
- }
2725
- if (license.plan === "free") {
2726
- log("Your plan: Free \u2014 1 employee (your COO)");
2788
+ let cooName = "exe";
2789
+ if (!state.completedSteps.includes(6)) {
2790
+ log("=== Your Team ===");
2727
2791
  log("");
2728
- log("The CTO and CMO are available on Solopreneur ($97/mo) \u2014 full engineering");
2729
- log("and marketing team, unlimited tasks, priority support.");
2730
- log("Get your key at https://askexe.com, then paste it here.");
2792
+ log("Every install starts with a COO \u2014 your right-hand operator.");
2793
+ log("They hold the big picture: priorities, progress, and blockers.");
2794
+ log("You talk to them. They coordinate everyone else.");
2731
2795
  log("");
2732
- const licenseInput = await ask(rl, "License key (or press Enter to skip): ");
2733
- if (licenseInput.startsWith("exe_sk_")) {
2734
- saveLicense2(licenseInput);
2735
- mirrorLicenseKey2(licenseInput);
2736
- try {
2737
- license = await validateLicense2(licenseInput);
2738
- } catch {
2739
- log("Couldn't reach the license server \u2014 your key has been saved.");
2740
- log("Run exe-os --activate <key> anytime to finish activation.");
2741
- }
2742
- } else if (!licenseInput) {
2743
- log("You can activate anytime with: exe-os --activate <key>");
2744
- } else {
2745
- log("That doesn't look like a license key (should start with exe_sk_).");
2746
- log("You can activate anytime with: exe-os --activate <key>");
2796
+ const cooNameInput = await ask(rl, "Name your COO (default: exe): ");
2797
+ cooName = (cooNameInput || "exe").toLowerCase();
2798
+ let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
2799
+ if (!employees.some((e) => e.name === cooName)) {
2800
+ const { DEFAULT_EXE: DEFAULT_EXE2, personalizePrompt: personalizePrompt2 } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
2801
+ const cooEmployee = {
2802
+ name: cooName,
2803
+ role: "COO",
2804
+ systemPrompt: personalizePrompt2(DEFAULT_EXE2.systemPrompt, "exe", cooName),
2805
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2806
+ templateName: "exe",
2807
+ templateVersion: 1
2808
+ };
2809
+ employees = addEmployee2(employees, cooEmployee);
2810
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
2811
+ }
2812
+ const cooIdentityContent = getIdentityTemplate("coo");
2813
+ if (cooIdentityContent) {
2814
+ const cooIdPath = identityPath2(cooName);
2815
+ mkdirSync3(path8.dirname(cooIdPath), { recursive: true });
2816
+ const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
2817
+ writeFileSync3(cooIdPath, replaced, "utf-8");
2747
2818
  }
2819
+ registerBinSymlinks2(cooName);
2820
+ createdEmployees.push({ name: cooName, role: "COO" });
2821
+ state.completedSteps.push(6);
2822
+ saveSetupState(state);
2823
+ } else {
2824
+ log("Step 6 already complete \u2014 skipping.");
2825
+ const roster = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
2826
+ const existingCoo = roster.find((e) => e.role === "COO");
2827
+ if (existingCoo) cooName = existingCoo.name;
2748
2828
  }
2749
- if (license.plan !== "free") {
2750
- const planName = license.plan === "pro" ? "Solopreneur" : license.plan.charAt(0).toUpperCase() + license.plan.slice(1);
2751
- log(`Your plan: ${planName} (up to ${license.employeeLimit} employees)`);
2829
+ log("");
2830
+ if (!state.completedSteps.includes(7)) {
2831
+ log("=== Meet Your Specialists ===");
2752
2832
  log("");
2753
- const createCto = await ask(rl, "Create your CTO? (Y/n): ");
2754
- if (createCto.toLowerCase() !== "n") {
2755
- const ctoNameInput = await ask(rl, "Name your CTO (default: yoshi): ");
2756
- const ctoName = (ctoNameInput || "yoshi").toLowerCase();
2757
- if (!employees.some((e) => e.name === ctoName)) {
2758
- const ctoTemplate = getEmployeeTemplate("yoshi");
2759
- const { personalizePrompt: personalizeCto } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
2760
- const ctoEmployee = {
2761
- name: ctoName,
2762
- role: "CTO",
2763
- systemPrompt: personalizeCto(ctoTemplate?.systemPrompt ?? "", "yoshi", ctoName),
2764
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2765
- };
2766
- employees = addEmployee2(employees, ctoEmployee);
2767
- await saveEmployees2(employees, EMPLOYEES_PATH2);
2768
- }
2769
- const ctoIdentityContent = getIdentityTemplate("cto");
2770
- if (ctoIdentityContent) {
2771
- const ctoIdPath = identityPath2(ctoName);
2772
- mkdirSync3(path8.dirname(ctoIdPath), { recursive: true });
2773
- const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
2774
- writeFileSync3(ctoIdPath, replaced, "utf-8");
2833
+ log("Your COO coordinates specialists. Here's who you can hire:");
2834
+ log("");
2835
+ log(" CTO (default: yoshi)");
2836
+ log(" Your head of engineering. Architecture, code reviews, tech decisions.");
2837
+ log(" Manages your projects and delegates to engineers when there's parallel work.");
2838
+ log("");
2839
+ log(" CMO (default: mari)");
2840
+ log(" Design, brand, content, SEO. Builds your visual identity, writes");
2841
+ log(" your copy, and gets you found online. Delegates to content specialists.");
2842
+ log("");
2843
+ log("Why separate roles instead of one AI that does everything?");
2844
+ log("");
2845
+ log("Memory saturates. One agent juggling architecture decisions AND landing page");
2846
+ log("copy AND CI/CD AND social media loses context on all of them. Competing");
2847
+ log("priorities \u2014 should I fix the auth bug or write the blog post? When you split");
2848
+ log("responsibilities, each specialist stays sharp because they stay focused.");
2849
+ log("Your COO connects them so nothing falls through the cracks.");
2850
+ log("");
2851
+ log("This is how real companies scale \u2014 you're just doing it with AI");
2852
+ log("instead of headcount.");
2853
+ log("");
2854
+ await ask(rl, "Press Enter to continue. ");
2855
+ state.completedSteps.push(7);
2856
+ saveSetupState(state);
2857
+ } else {
2858
+ log("Step 7 already complete \u2014 skipping.");
2859
+ }
2860
+ if (!state.completedSteps.includes(8)) {
2861
+ let employees = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
2862
+ let license;
2863
+ try {
2864
+ license = await checkLicense2();
2865
+ } catch {
2866
+ license = { valid: true, plan: "free", email: "", expiresAt: null, deviceLimit: 1, employeeLimit: 2, memoryLimit: 1e3 };
2867
+ }
2868
+ if (license.plan === "free") {
2869
+ log("Your plan: Free \u2014 1 employee (your COO)");
2870
+ log("");
2871
+ log("The CTO and CMO are available on Solopreneur ($97/mo) \u2014 full engineering");
2872
+ log("and marketing team, unlimited tasks, priority support.");
2873
+ log("Get your key at https://askexe.com, then paste it here.");
2874
+ log("");
2875
+ const licenseInput = await ask(rl, "License key (or press Enter to skip): ");
2876
+ if (licenseInput.startsWith("exe_sk_")) {
2877
+ saveLicense2(licenseInput);
2878
+ mirrorLicenseKey2(licenseInput);
2879
+ try {
2880
+ license = await validateLicense2(licenseInput);
2881
+ } catch {
2882
+ log("Couldn't reach the license server \u2014 your key has been saved.");
2883
+ log("Run exe-os --activate <key> anytime to finish activation.");
2884
+ }
2885
+ } else if (!licenseInput) {
2886
+ log("You can activate anytime with: exe-os --activate <key>");
2887
+ } else {
2888
+ log("That doesn't look like a license key (should start with exe_sk_).");
2889
+ log("You can activate anytime with: exe-os --activate <key>");
2775
2890
  }
2776
- registerBinSymlinks2(ctoName);
2777
- createdEmployees.push({ name: ctoName, role: "CTO" });
2778
- log(`Created ${ctoName} (CTO)`);
2779
2891
  }
2780
- const createCmo = await ask(rl, "Create your CMO? (Y/n): ");
2781
- if (createCmo.toLowerCase() !== "n") {
2782
- const cmoNameInput = await ask(rl, "Name your CMO (default: mari): ");
2783
- const cmoName = (cmoNameInput || "mari").toLowerCase();
2784
- if (!employees.some((e) => e.name === cmoName)) {
2785
- const cmoTemplate = getEmployeeTemplate("mari");
2786
- const { personalizePrompt: personalizeCmo } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
2787
- const cmoEmployee = {
2788
- name: cmoName,
2789
- role: "CMO",
2790
- systemPrompt: personalizeCmo(cmoTemplate?.systemPrompt ?? "", "mari", cmoName),
2791
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2792
- };
2793
- employees = addEmployee2(employees, cmoEmployee);
2794
- await saveEmployees2(employees, EMPLOYEES_PATH2);
2892
+ if (license.plan !== "free") {
2893
+ const planName = license.plan === "pro" ? "Solopreneur" : license.plan.charAt(0).toUpperCase() + license.plan.slice(1);
2894
+ log(`Your plan: ${planName} (up to ${license.employeeLimit} employees)`);
2895
+ log("");
2896
+ const createCto = await ask(rl, "Create your CTO? (Y/n): ");
2897
+ if (createCto.toLowerCase() !== "n") {
2898
+ const ctoTemplate = getTemplateByRole2("CTO");
2899
+ const ctoDefault = ctoTemplate?.name ?? "cto";
2900
+ const ctoNameInput = await ask(rl, `Name your CTO (default: ${ctoDefault}): `);
2901
+ const ctoName = (ctoNameInput || ctoDefault).toLowerCase();
2902
+ if (!employees.some((e) => e.name === ctoName)) {
2903
+ const { personalizePrompt: personalizeCto } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
2904
+ const ctoEmployee = {
2905
+ name: ctoName,
2906
+ role: "CTO",
2907
+ systemPrompt: personalizeCto(ctoTemplate?.systemPrompt ?? "", ctoDefault, ctoName),
2908
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2909
+ };
2910
+ employees = addEmployee2(employees, ctoEmployee);
2911
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
2912
+ }
2913
+ const ctoIdentityContent = getIdentityTemplate("cto");
2914
+ if (ctoIdentityContent) {
2915
+ const ctoIdPath = identityPath2(ctoName);
2916
+ mkdirSync3(path8.dirname(ctoIdPath), { recursive: true });
2917
+ const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
2918
+ writeFileSync3(ctoIdPath, replaced, "utf-8");
2919
+ }
2920
+ registerBinSymlinks2(ctoName);
2921
+ createdEmployees.push({ name: ctoName, role: "CTO" });
2922
+ log(`Created ${ctoName} (CTO)`);
2795
2923
  }
2796
- const cmoIdentityContent = getIdentityTemplate("cmo");
2797
- if (cmoIdentityContent) {
2798
- const cmoIdPath = identityPath2(cmoName);
2799
- mkdirSync3(path8.dirname(cmoIdPath), { recursive: true });
2800
- const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
2801
- writeFileSync3(cmoIdPath, replaced, "utf-8");
2924
+ const createCmo = await ask(rl, "Create your CMO? (Y/n): ");
2925
+ if (createCmo.toLowerCase() !== "n") {
2926
+ const cmoTemplate = getTemplateByRole2("CMO");
2927
+ const cmoDefault = cmoTemplate?.name ?? "cmo";
2928
+ const cmoNameInput = await ask(rl, `Name your CMO (default: ${cmoDefault}): `);
2929
+ const cmoName = (cmoNameInput || cmoDefault).toLowerCase();
2930
+ if (!employees.some((e) => e.name === cmoName)) {
2931
+ const { personalizePrompt: personalizeCmo } = await Promise.resolve().then(() => (init_employee_templates(), employee_templates_exports));
2932
+ const cmoEmployee = {
2933
+ name: cmoName,
2934
+ role: "CMO",
2935
+ systemPrompt: personalizeCmo(cmoTemplate?.systemPrompt ?? "", cmoDefault, cmoName),
2936
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2937
+ };
2938
+ employees = addEmployee2(employees, cmoEmployee);
2939
+ await saveEmployees2(employees, EMPLOYEES_PATH2);
2940
+ }
2941
+ const cmoIdentityContent = getIdentityTemplate("cmo");
2942
+ if (cmoIdentityContent) {
2943
+ const cmoIdPath = identityPath2(cmoName);
2944
+ mkdirSync3(path8.dirname(cmoIdPath), { recursive: true });
2945
+ const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
2946
+ writeFileSync3(cmoIdPath, replaced, "utf-8");
2947
+ }
2948
+ registerBinSymlinks2(cmoName);
2949
+ createdEmployees.push({ name: cmoName, role: "CMO" });
2950
+ log(`Created ${cmoName} (CMO)`);
2802
2951
  }
2803
- registerBinSymlinks2(cmoName);
2804
- createdEmployees.push({ name: cmoName, role: "CMO" });
2805
- log(`Created ${cmoName} (CMO)`);
2952
+ log("");
2806
2953
  }
2807
- log("");
2954
+ state.completedSteps.push(8);
2955
+ saveSetupState(state);
2956
+ } else {
2957
+ log("Step 8 already complete \u2014 skipping.");
2808
2958
  }
2959
+ clearSetupState();
2809
2960
  log("=== Two Ways to Work ===");
2810
2961
  log("");
2811
2962
  log(" 1. Claude Code mode");