@askexenow/exe-os 0.8.61 → 0.8.63

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 (62) hide show
  1. package/dist/bin/backfill-conversations.js +2 -3
  2. package/dist/bin/backfill-responses.js +3 -4
  3. package/dist/bin/backfill-vectors.js +0 -1
  4. package/dist/bin/cleanup-stale-review-tasks.js +0 -1
  5. package/dist/bin/cli.js +1654 -397
  6. package/dist/bin/exe-assign.js +0 -1
  7. package/dist/bin/exe-boot.js +61 -84
  8. package/dist/bin/exe-cloud.js +28 -60
  9. package/dist/bin/exe-dispatch.js +0 -1
  10. package/dist/bin/exe-doctor.js +0 -1
  11. package/dist/bin/exe-export-behaviors.js +1 -2
  12. package/dist/bin/exe-forget.js +0 -1
  13. package/dist/bin/exe-gateway.js +16 -17
  14. package/dist/bin/exe-heartbeat.js +1 -2
  15. package/dist/bin/exe-kill.js +2 -3
  16. package/dist/bin/exe-launch-agent.js +1 -2
  17. package/dist/bin/exe-link.js +49 -78
  18. package/dist/bin/exe-pending-messages.js +1 -2
  19. package/dist/bin/exe-pending-notifications.js +0 -1
  20. package/dist/bin/exe-pending-reviews.js +1 -2
  21. package/dist/bin/exe-review.js +0 -1
  22. package/dist/bin/exe-search.js +2 -3
  23. package/dist/bin/exe-session-cleanup.js +4 -5
  24. package/dist/bin/exe-status.js +0 -1
  25. package/dist/bin/exe-team.js +0 -1
  26. package/dist/bin/git-sweep.js +0 -1
  27. package/dist/bin/graph-backfill.js +5 -6
  28. package/dist/bin/graph-export.js +0 -1
  29. package/dist/bin/scan-tasks.js +0 -1
  30. package/dist/bin/setup.js +1460 -115
  31. package/dist/bin/shard-migrate.js +0 -1
  32. package/dist/bin/wiki-sync.js +0 -1
  33. package/dist/gateway/index.js +16 -17
  34. package/dist/hooks/bug-report-worker.js +11 -12
  35. package/dist/hooks/commit-complete.js +0 -1
  36. package/dist/hooks/error-recall.js +2 -3
  37. package/dist/hooks/ingest-worker.js +14 -15
  38. package/dist/hooks/instructions-loaded.js +0 -1
  39. package/dist/hooks/notification.js +0 -1
  40. package/dist/hooks/post-compact.js +0 -1
  41. package/dist/hooks/pre-compact.js +2 -3
  42. package/dist/hooks/pre-tool-use.js +0 -1
  43. package/dist/hooks/prompt-ingest-worker.js +2 -3
  44. package/dist/hooks/prompt-submit.js +6 -7
  45. package/dist/hooks/response-ingest-worker.js +2 -3
  46. package/dist/hooks/session-end.js +3 -4
  47. package/dist/hooks/session-start.js +2 -3
  48. package/dist/hooks/stop.js +2 -3
  49. package/dist/hooks/subagent-stop.js +0 -1
  50. package/dist/hooks/summary-worker.js +51 -74
  51. package/dist/index.js +7 -8
  52. package/dist/lib/cloud-sync.js +21 -12
  53. package/dist/lib/consolidation.js +0 -1
  54. package/dist/lib/exe-daemon.js +33 -65
  55. package/dist/lib/hybrid-search.js +2 -3
  56. package/dist/lib/keychain.js +19 -58
  57. package/dist/lib/schedules.js +2 -3
  58. package/dist/lib/store.js +0 -1
  59. package/dist/mcp/server.js +29 -30
  60. package/dist/runtime/index.js +2 -3
  61. package/dist/tui/App.js +24 -56
  62. package/package.json +1 -1
package/dist/bin/setup.js CHANGED
@@ -263,6 +263,121 @@ var init_config = __esm({
263
263
  }
264
264
  });
265
265
 
266
+ // src/lib/keychain.ts
267
+ var keychain_exports = {};
268
+ __export(keychain_exports, {
269
+ deleteMasterKey: () => deleteMasterKey,
270
+ exportMnemonic: () => exportMnemonic,
271
+ getMasterKey: () => getMasterKey,
272
+ importMnemonic: () => importMnemonic,
273
+ setMasterKey: () => setMasterKey
274
+ });
275
+ import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
276
+ import { existsSync as existsSync2 } from "fs";
277
+ import path2 from "path";
278
+ import os2 from "os";
279
+ function getKeyDir() {
280
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(os2.homedir(), ".exe-os");
281
+ }
282
+ function getKeyPath() {
283
+ return path2.join(getKeyDir(), "master.key");
284
+ }
285
+ async function tryKeytar() {
286
+ try {
287
+ return await import("keytar");
288
+ } catch {
289
+ return null;
290
+ }
291
+ }
292
+ async function getMasterKey() {
293
+ const keytar = await tryKeytar();
294
+ if (keytar) {
295
+ try {
296
+ const stored = await keytar.getPassword(SERVICE, ACCOUNT);
297
+ if (stored) {
298
+ return Buffer.from(stored, "base64");
299
+ }
300
+ } catch {
301
+ }
302
+ }
303
+ const keyPath = getKeyPath();
304
+ if (!existsSync2(keyPath)) {
305
+ return null;
306
+ }
307
+ try {
308
+ const content = await readFile2(keyPath, "utf-8");
309
+ return Buffer.from(content.trim(), "base64");
310
+ } catch {
311
+ return null;
312
+ }
313
+ }
314
+ async function setMasterKey(key) {
315
+ const b64 = key.toString("base64");
316
+ const keytar = await tryKeytar();
317
+ if (keytar) {
318
+ try {
319
+ await keytar.setPassword(SERVICE, ACCOUNT, b64);
320
+ return;
321
+ } catch {
322
+ }
323
+ }
324
+ const dir = getKeyDir();
325
+ await mkdir2(dir, { recursive: true });
326
+ const keyPath = getKeyPath();
327
+ await writeFile2(keyPath, b64 + "\n", "utf-8");
328
+ await chmod2(keyPath, 384);
329
+ }
330
+ async function deleteMasterKey() {
331
+ const keytar = await tryKeytar();
332
+ if (keytar) {
333
+ try {
334
+ await keytar.deletePassword(SERVICE, ACCOUNT);
335
+ } catch {
336
+ }
337
+ }
338
+ const keyPath = getKeyPath();
339
+ if (existsSync2(keyPath)) {
340
+ await unlink(keyPath);
341
+ }
342
+ }
343
+ async function loadBip39() {
344
+ try {
345
+ return await import("bip39");
346
+ } catch {
347
+ throw new Error(
348
+ "bip39 package not found. Run: npm install -g bip39\nOr reinstall exe-os: npm install -g @askexenow/exe-os"
349
+ );
350
+ }
351
+ }
352
+ async function exportMnemonic(key) {
353
+ if (key.length !== 32) {
354
+ throw new Error(`Key must be 32 bytes, got ${key.length}`);
355
+ }
356
+ const { entropyToMnemonic } = await loadBip39();
357
+ return entropyToMnemonic(key.toString("hex"));
358
+ }
359
+ async function importMnemonic(mnemonic) {
360
+ const trimmed = mnemonic.trim();
361
+ const words = trimmed.split(/\s+/);
362
+ if (words.length !== 24) {
363
+ throw new Error(`Expected 24 words, got ${words.length}`);
364
+ }
365
+ const { validateMnemonic, mnemonicToEntropy } = await loadBip39();
366
+ if (!validateMnemonic(trimmed)) {
367
+ throw new Error("Invalid mnemonic \u2014 check for typos or missing words");
368
+ }
369
+ const entropy = mnemonicToEntropy(trimmed);
370
+ return Buffer.from(entropy, "hex");
371
+ }
372
+ var SERVICE, ACCOUNT;
373
+ var init_keychain = __esm({
374
+ "src/lib/keychain.ts"() {
375
+ "use strict";
376
+ SERVICE = "exe-mem";
377
+ ACCOUNT = "master-key";
378
+ }
379
+ });
380
+
266
381
  // src/types/memory.ts
267
382
  var EMBEDDING_DIM;
268
383
  var init_memory = __esm({
@@ -650,10 +765,10 @@ async function disposeEmbedder() {
650
765
  async function embedDirect(text) {
651
766
  const llamaCpp = await import("node-llama-cpp");
652
767
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
653
- const { existsSync: existsSync9 } = await import("fs");
654
- const path9 = await import("path");
655
- const modelPath = path9.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
656
- if (!existsSync9(modelPath)) {
768
+ const { existsSync: existsSync11 } = await import("fs");
769
+ const path11 = await import("path");
770
+ const modelPath = path11.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
771
+ if (!existsSync11(modelPath)) {
657
772
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
658
773
  }
659
774
  const llama = await llamaCpp.getLlama();
@@ -1068,6 +1183,109 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
1068
1183
  }
1069
1184
  });
1070
1185
 
1186
+ // src/lib/crypto.ts
1187
+ var crypto_exports = {};
1188
+ __export(crypto_exports, {
1189
+ decryptSyncBlob: () => decryptSyncBlob,
1190
+ encryptSyncBlob: () => encryptSyncBlob,
1191
+ initSyncCrypto: () => initSyncCrypto,
1192
+ isSyncCryptoInitialized: () => isSyncCryptoInitialized
1193
+ });
1194
+ import crypto from "crypto";
1195
+ function initSyncCrypto(masterKey) {
1196
+ if (masterKey.length !== 32) {
1197
+ throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
1198
+ }
1199
+ _syncKey = Buffer.from(
1200
+ crypto.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
1201
+ );
1202
+ }
1203
+ function isSyncCryptoInitialized() {
1204
+ return _syncKey !== null;
1205
+ }
1206
+ function requireSyncKey() {
1207
+ if (!_syncKey) {
1208
+ throw new Error("Sync crypto not initialized. Call initSyncCrypto(masterKey) first.");
1209
+ }
1210
+ return _syncKey;
1211
+ }
1212
+ function encryptSyncBlob(data) {
1213
+ const key = requireSyncKey();
1214
+ const iv = crypto.randomBytes(IV_LENGTH);
1215
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
1216
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
1217
+ const tag = cipher.getAuthTag();
1218
+ return Buffer.concat([iv, encrypted, tag]).toString("base64");
1219
+ }
1220
+ function decryptSyncBlob(ciphertext) {
1221
+ const key = requireSyncKey();
1222
+ const combined = Buffer.from(ciphertext, "base64");
1223
+ if (combined.length < IV_LENGTH + TAG_LENGTH) {
1224
+ throw new Error("Sync blob too short to contain IV + tag");
1225
+ }
1226
+ const iv = combined.subarray(0, IV_LENGTH);
1227
+ const tag = combined.subarray(combined.length - TAG_LENGTH);
1228
+ const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
1229
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
1230
+ decipher.setAuthTag(tag);
1231
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]);
1232
+ }
1233
+ var ALGORITHM, IV_LENGTH, TAG_LENGTH, SYNC_HKDF_INFO, _syncKey;
1234
+ var init_crypto = __esm({
1235
+ "src/lib/crypto.ts"() {
1236
+ "use strict";
1237
+ ALGORITHM = "aes-256-gcm";
1238
+ IV_LENGTH = 12;
1239
+ TAG_LENGTH = 16;
1240
+ SYNC_HKDF_INFO = "exe-mem-sync-v2";
1241
+ _syncKey = null;
1242
+ }
1243
+ });
1244
+
1245
+ // src/lib/db-retry.ts
1246
+ var init_db_retry = __esm({
1247
+ "src/lib/db-retry.ts"() {
1248
+ "use strict";
1249
+ }
1250
+ });
1251
+
1252
+ // src/lib/database.ts
1253
+ import { createClient } from "@libsql/client";
1254
+ function getClient() {
1255
+ if (!_resilientClient) {
1256
+ throw new Error("Database client not initialized. Call initDatabase() first.");
1257
+ }
1258
+ return _resilientClient;
1259
+ }
1260
+ var _resilientClient;
1261
+ var init_database = __esm({
1262
+ "src/lib/database.ts"() {
1263
+ "use strict";
1264
+ init_db_retry();
1265
+ _resilientClient = null;
1266
+ }
1267
+ });
1268
+
1269
+ // src/lib/compress.ts
1270
+ import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
1271
+ function compress(input) {
1272
+ if (input.length === 0) return Buffer.alloc(0);
1273
+ return brotliCompressSync(input, {
1274
+ params: {
1275
+ [constants.BROTLI_PARAM_QUALITY]: 4
1276
+ }
1277
+ });
1278
+ }
1279
+ function decompress(input) {
1280
+ if (input.length === 0) return Buffer.alloc(0);
1281
+ return brotliDecompressSync(input);
1282
+ }
1283
+ var init_compress = __esm({
1284
+ "src/lib/compress.ts"() {
1285
+ "use strict";
1286
+ }
1287
+ });
1288
+
1071
1289
  // src/lib/employees.ts
1072
1290
  var employees_exports = {};
1073
1291
  __export(employees_exports, {
@@ -1205,27 +1423,989 @@ var init_employees = __esm({
1205
1423
  }
1206
1424
  });
1207
1425
 
1208
- // src/lib/db-retry.ts
1209
- var init_db_retry = __esm({
1210
- "src/lib/db-retry.ts"() {
1211
- "use strict";
1212
- }
1426
+ // src/lib/cloud-sync.ts
1427
+ var cloud_sync_exports = {};
1428
+ __export(cloud_sync_exports, {
1429
+ assertSecureEndpoint: () => assertSecureEndpoint,
1430
+ buildRosterBlob: () => buildRosterBlob,
1431
+ cloudPull: () => cloudPull,
1432
+ cloudPullBehaviors: () => cloudPullBehaviors,
1433
+ cloudPullBlob: () => cloudPullBlob,
1434
+ cloudPullConversations: () => cloudPullConversations,
1435
+ cloudPullDocuments: () => cloudPullDocuments,
1436
+ cloudPullGlobalProcedures: () => cloudPullGlobalProcedures,
1437
+ cloudPullGraphRAG: () => cloudPullGraphRAG,
1438
+ cloudPullRoster: () => cloudPullRoster,
1439
+ cloudPullTasks: () => cloudPullTasks,
1440
+ cloudPush: () => cloudPush,
1441
+ cloudPushBehaviors: () => cloudPushBehaviors,
1442
+ cloudPushBlob: () => cloudPushBlob,
1443
+ cloudPushConversations: () => cloudPushConversations,
1444
+ cloudPushDocuments: () => cloudPushDocuments,
1445
+ cloudPushGlobalProcedures: () => cloudPushGlobalProcedures,
1446
+ cloudPushGraphRAG: () => cloudPushGraphRAG,
1447
+ cloudPushRoster: () => cloudPushRoster,
1448
+ cloudPushTasks: () => cloudPushTasks,
1449
+ cloudSync: () => cloudSync,
1450
+ mergeConfig: () => mergeConfig,
1451
+ mergeRosterFromRemote: () => mergeRosterFromRemote,
1452
+ recordRosterDeletion: () => recordRosterDeletion
1213
1453
  });
1214
-
1215
- // src/lib/database.ts
1216
- import { createClient } from "@libsql/client";
1217
- function getClient() {
1218
- if (!_resilientClient) {
1219
- throw new Error("Database client not initialized. Call initDatabase() first.");
1454
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync7, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2 } from "fs";
1455
+ import crypto2 from "crypto";
1456
+ import path7 from "path";
1457
+ import { homedir } from "os";
1458
+ function sqlSafe(v) {
1459
+ return v === void 0 ? null : v;
1460
+ }
1461
+ function logError(msg) {
1462
+ try {
1463
+ const logPath = path7.join(homedir(), ".exe-os", "workers.log");
1464
+ appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
1465
+ `);
1466
+ } catch {
1220
1467
  }
1221
- return _resilientClient;
1222
1468
  }
1223
- var _resilientClient;
1224
- var init_database = __esm({
1225
- "src/lib/database.ts"() {
1469
+ async function withRosterLock(fn) {
1470
+ try {
1471
+ const fd = openSync2(ROSTER_LOCK_PATH, "wx");
1472
+ closeSync2(fd);
1473
+ writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
1474
+ } catch (err) {
1475
+ if (err.code === "EEXIST") {
1476
+ try {
1477
+ const ts = parseInt(readFileSync5(ROSTER_LOCK_PATH, "utf-8"), 10);
1478
+ if (Date.now() - ts < LOCK_STALE_MS) {
1479
+ throw new Error("Roster merge already in progress \u2014 another sync is running");
1480
+ }
1481
+ unlinkSync3(ROSTER_LOCK_PATH);
1482
+ const fd = openSync2(ROSTER_LOCK_PATH, "wx");
1483
+ closeSync2(fd);
1484
+ writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
1485
+ } catch (retryErr) {
1486
+ if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
1487
+ throw new Error("Roster merge already in progress \u2014 another sync is running");
1488
+ }
1489
+ } else {
1490
+ throw err;
1491
+ }
1492
+ }
1493
+ try {
1494
+ return await fn();
1495
+ } finally {
1496
+ try {
1497
+ unlinkSync3(ROSTER_LOCK_PATH);
1498
+ } catch {
1499
+ }
1500
+ }
1501
+ }
1502
+ async function fetchWithRetry(url, init) {
1503
+ const MAX_RETRIES = 3;
1504
+ const BASE_DELAY_MS = 200;
1505
+ let lastError;
1506
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
1507
+ try {
1508
+ const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
1509
+ const resp = await fetch(url, { ...init, signal });
1510
+ if (resp && resp.status >= 500 && attempt < MAX_RETRIES) {
1511
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS * Math.pow(2, attempt)));
1512
+ continue;
1513
+ }
1514
+ return resp;
1515
+ } catch (err) {
1516
+ lastError = err;
1517
+ if (attempt === MAX_RETRIES) throw err;
1518
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS * Math.pow(2, attempt)));
1519
+ }
1520
+ }
1521
+ throw lastError;
1522
+ }
1523
+ function assertSecureEndpoint(endpoint) {
1524
+ if (endpoint.startsWith("https://")) return;
1525
+ if (endpoint.startsWith("http://")) {
1526
+ try {
1527
+ const parsed = new URL(endpoint);
1528
+ if (LOCALHOST_PATTERNS.test(parsed.hostname)) return;
1529
+ } catch {
1530
+ return;
1531
+ }
1532
+ throw new Error(
1533
+ `Insecure cloud endpoint rejected: "${endpoint}". Use https:// for remote hosts. Plain http:// is only allowed for localhost.`
1534
+ );
1535
+ }
1536
+ }
1537
+ async function cloudPush(records, maxVersion, config) {
1538
+ if (records.length === 0) return true;
1539
+ assertSecureEndpoint(config.endpoint);
1540
+ try {
1541
+ const json = JSON.stringify(records);
1542
+ const compressed = compress(Buffer.from(json, "utf8"));
1543
+ const blob = encryptSyncBlob(compressed);
1544
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
1545
+ method: "POST",
1546
+ headers: {
1547
+ Authorization: `Bearer ${config.apiKey}`,
1548
+ "Content-Type": "application/json",
1549
+ "X-Device-Id": loadDeviceId(),
1550
+ "X-Expected-Version": String(maxVersion)
1551
+ },
1552
+ body: JSON.stringify({ version: maxVersion, blob })
1553
+ });
1554
+ if (resp == null) {
1555
+ logError("[cloud-sync] PUSH FAILED: no response from server");
1556
+ return false;
1557
+ }
1558
+ if (resp.status === 409) {
1559
+ logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
1560
+ return false;
1561
+ }
1562
+ return resp.ok;
1563
+ } catch (err) {
1564
+ logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
1565
+ return false;
1566
+ }
1567
+ }
1568
+ async function cloudPull(sinceVersion, config) {
1569
+ assertSecureEndpoint(config.endpoint);
1570
+ try {
1571
+ const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
1572
+ method: "POST",
1573
+ headers: {
1574
+ Authorization: `Bearer ${config.apiKey}`,
1575
+ "Content-Type": "application/json",
1576
+ "X-Device-Id": loadDeviceId()
1577
+ },
1578
+ body: JSON.stringify({ since_version: sinceVersion })
1579
+ });
1580
+ if (response == null) {
1581
+ logError("[cloud-sync] PULL FAILED: no response from server");
1582
+ return { records: [], maxVersion: sinceVersion };
1583
+ }
1584
+ if (!response.ok) return { records: [], maxVersion: sinceVersion };
1585
+ const data = await response.json();
1586
+ const allRecords = [];
1587
+ for (const { blob } of data.blobs ?? []) {
1588
+ try {
1589
+ const compressed = decryptSyncBlob(blob);
1590
+ const json = decompress(compressed).toString("utf8");
1591
+ const records = JSON.parse(json);
1592
+ allRecords.push(...records);
1593
+ } catch {
1594
+ continue;
1595
+ }
1596
+ }
1597
+ return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
1598
+ } catch (err) {
1599
+ logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
1600
+ return { records: [], maxVersion: sinceVersion };
1601
+ }
1602
+ }
1603
+ async function cloudSync(config) {
1604
+ let client;
1605
+ try {
1606
+ client = getClient();
1607
+ } catch {
1608
+ throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
1609
+ }
1610
+ try {
1611
+ await client.execute(
1612
+ "CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
1613
+ );
1614
+ } catch (e) {
1615
+ logError(`[cloud-sync] sync_meta CREATE failed: ${e instanceof Error ? e.message : String(e)}`);
1616
+ }
1617
+ const pullMeta = await client.execute(
1618
+ "SELECT value FROM sync_meta WHERE key = 'last_cloud_pull_version'"
1619
+ );
1620
+ const lastPullVersion = pullMeta.rows.length > 0 ? Number(pullMeta.rows[0].value) : 0;
1621
+ const pullResult = await cloudPull(lastPullVersion, config);
1622
+ let pulled = 0;
1623
+ if (pullResult.records.length > 0) {
1624
+ const stmts = pullResult.records.map((rec) => ({
1625
+ sql: `INSERT OR REPLACE INTO memories
1626
+ (id, agent_id, agent_role, session_id, timestamp,
1627
+ tool_name, project_name, has_error, raw_text, version,
1628
+ author_device_id, scope)
1629
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1630
+ args: [
1631
+ sqlSafe(rec.id),
1632
+ sqlSafe(rec.agent_id),
1633
+ sqlSafe(rec.agent_role),
1634
+ sqlSafe(rec.session_id),
1635
+ sqlSafe(rec.timestamp),
1636
+ sqlSafe(rec.tool_name),
1637
+ sqlSafe(rec.project_name),
1638
+ sqlSafe(rec.has_error ?? 0),
1639
+ sqlSafe(rec.raw_text ?? ""),
1640
+ sqlSafe(rec.version ?? 0),
1641
+ sqlSafe(rec.author_device_id),
1642
+ sqlSafe(rec.scope ?? "business")
1643
+ ]
1644
+ }));
1645
+ await client.batch(stmts, "write");
1646
+ pulled = pullResult.records.length;
1647
+ }
1648
+ if (pullResult.maxVersion > lastPullVersion) {
1649
+ await client.execute({
1650
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_pull_version', ?)",
1651
+ args: [String(pullResult.maxVersion)]
1652
+ });
1653
+ }
1654
+ const pushMeta = await client.execute(
1655
+ "SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
1656
+ );
1657
+ const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
1658
+ let pushed = 0;
1659
+ let batchCursor = lastPushVersion;
1660
+ while (true) {
1661
+ const recordsResult = await client.execute({
1662
+ sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
1663
+ tool_name, project_name, has_error, raw_text, version,
1664
+ author_device_id, scope
1665
+ FROM memories
1666
+ WHERE version > ?
1667
+ AND (scope IS NULL OR scope != 'personal')
1668
+ ORDER BY version ASC
1669
+ LIMIT ?`,
1670
+ args: [batchCursor, PUSH_BATCH_SIZE]
1671
+ });
1672
+ if (recordsResult.rows.length === 0) break;
1673
+ const records = recordsResult.rows.map((row) => ({
1674
+ id: row.id,
1675
+ agent_id: row.agent_id,
1676
+ agent_role: row.agent_role,
1677
+ session_id: row.session_id,
1678
+ timestamp: row.timestamp,
1679
+ tool_name: row.tool_name,
1680
+ project_name: row.project_name,
1681
+ has_error: row.has_error,
1682
+ raw_text: row.raw_text,
1683
+ version: row.version,
1684
+ author_device_id: row.author_device_id,
1685
+ scope: row.scope
1686
+ }));
1687
+ const maxVersion = Number(records[records.length - 1].version);
1688
+ const pushOk = await cloudPush(records, maxVersion, config);
1689
+ if (!pushOk) break;
1690
+ await client.execute({
1691
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
1692
+ args: [String(maxVersion)]
1693
+ });
1694
+ pushed += records.length;
1695
+ batchCursor = maxVersion;
1696
+ if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
1697
+ }
1698
+ try {
1699
+ await cloudPushRoster(config);
1700
+ } catch (err) {
1701
+ logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
1702
+ }
1703
+ try {
1704
+ await cloudPullRoster(config);
1705
+ } catch (err) {
1706
+ logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
1707
+ }
1708
+ try {
1709
+ await cloudPushGlobalProcedures(config);
1710
+ } catch (err) {
1711
+ logError(`[cloud-sync] Global procedures push: ${err instanceof Error ? err.message : String(err)}`);
1712
+ }
1713
+ try {
1714
+ await cloudPullGlobalProcedures(config);
1715
+ } catch (err) {
1716
+ logError(`[cloud-sync] Global procedures pull: ${err instanceof Error ? err.message : String(err)}`);
1717
+ }
1718
+ let behaviorsResult = { pushed: false, pulled: 0 };
1719
+ try {
1720
+ behaviorsResult.pushed = await cloudPushBehaviors(config);
1721
+ } catch (err) {
1722
+ logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
1723
+ }
1724
+ try {
1725
+ const pullResult2 = await cloudPullBehaviors(config);
1726
+ behaviorsResult.pulled = pullResult2.pulled;
1727
+ } catch (err) {
1728
+ logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
1729
+ }
1730
+ let graphragResult = { pushed: false, pulled: 0 };
1731
+ try {
1732
+ graphragResult.pushed = await cloudPushGraphRAG(config);
1733
+ } catch (err) {
1734
+ logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
1735
+ }
1736
+ try {
1737
+ const pullResult2 = await cloudPullGraphRAG(config);
1738
+ graphragResult.pulled = pullResult2.pulled;
1739
+ } catch (err) {
1740
+ logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
1741
+ }
1742
+ let tasksResult = { pushed: false, pulled: 0 };
1743
+ try {
1744
+ tasksResult.pushed = await cloudPushTasks(config);
1745
+ } catch (err) {
1746
+ logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
1747
+ }
1748
+ try {
1749
+ const pullResult2 = await cloudPullTasks(config);
1750
+ tasksResult.pulled = pullResult2.pulled;
1751
+ } catch (err) {
1752
+ logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
1753
+ }
1754
+ let conversationsResult = { pushed: false, pulled: 0 };
1755
+ try {
1756
+ conversationsResult.pushed = await cloudPushConversations(config);
1757
+ } catch (err) {
1758
+ logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
1759
+ }
1760
+ try {
1761
+ const pullResult2 = await cloudPullConversations(config);
1762
+ conversationsResult.pulled = pullResult2.pulled;
1763
+ } catch (err) {
1764
+ logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
1765
+ }
1766
+ let documentsResult = { pushed: false, pulled: 0 };
1767
+ try {
1768
+ documentsResult.pushed = await cloudPushDocuments(config);
1769
+ } catch (err) {
1770
+ logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
1771
+ }
1772
+ try {
1773
+ const pullResult2 = await cloudPullDocuments(config);
1774
+ documentsResult.pulled = pullResult2.pulled;
1775
+ } catch (err) {
1776
+ logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
1777
+ }
1778
+ return {
1779
+ pushed,
1780
+ pulled,
1781
+ behaviors: behaviorsResult,
1782
+ graphrag: graphragResult,
1783
+ tasks: tasksResult,
1784
+ conversations: conversationsResult,
1785
+ documents: documentsResult
1786
+ };
1787
+ }
1788
+ function recordRosterDeletion(name) {
1789
+ let deletions = [];
1790
+ try {
1791
+ if (existsSync7(ROSTER_DELETIONS_PATH)) {
1792
+ deletions = JSON.parse(readFileSync5(ROSTER_DELETIONS_PATH, "utf-8"));
1793
+ }
1794
+ } catch {
1795
+ }
1796
+ if (!deletions.includes(name)) deletions.push(name);
1797
+ writeFileSync2(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
1798
+ }
1799
+ function consumeRosterDeletions() {
1800
+ try {
1801
+ if (!existsSync7(ROSTER_DELETIONS_PATH)) return [];
1802
+ const deletions = JSON.parse(readFileSync5(ROSTER_DELETIONS_PATH, "utf-8"));
1803
+ writeFileSync2(ROSTER_DELETIONS_PATH, "[]");
1804
+ return deletions;
1805
+ } catch {
1806
+ return [];
1807
+ }
1808
+ }
1809
+ function buildRosterBlob(paths) {
1810
+ const rosterPath = paths?.rosterPath ?? path7.join(EXE_AI_DIR, "exe-employees.json");
1811
+ const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
1812
+ const configPath = paths?.configPath ?? path7.join(EXE_AI_DIR, "config.json");
1813
+ let roster = [];
1814
+ if (existsSync7(rosterPath)) {
1815
+ try {
1816
+ roster = JSON.parse(readFileSync5(rosterPath, "utf-8"));
1817
+ } catch {
1818
+ }
1819
+ }
1820
+ const identities = {};
1821
+ if (existsSync7(identityDir)) {
1822
+ for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
1823
+ try {
1824
+ identities[file] = readFileSync5(path7.join(identityDir, file), "utf-8");
1825
+ } catch {
1826
+ }
1827
+ }
1828
+ }
1829
+ let config;
1830
+ if (existsSync7(configPath)) {
1831
+ try {
1832
+ config = JSON.parse(readFileSync5(configPath, "utf-8"));
1833
+ } catch {
1834
+ }
1835
+ }
1836
+ const deletedNames = consumeRosterDeletions();
1837
+ const content = JSON.stringify({ roster, identities, config, deletedNames });
1838
+ const hash = crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
1839
+ return { roster, identities, config, deletedNames, version: hash };
1840
+ }
1841
+ async function cloudPushRoster(config) {
1842
+ assertSecureEndpoint(config.endpoint);
1843
+ const blob = buildRosterBlob();
1844
+ if (blob.roster.length === 0) return true;
1845
+ try {
1846
+ const client = getClient();
1847
+ const meta = await client.execute(
1848
+ "SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
1849
+ );
1850
+ const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
1851
+ if (blob.version === lastVersion) return true;
1852
+ } catch {
1853
+ }
1854
+ try {
1855
+ const json = JSON.stringify(blob);
1856
+ const compressed = compress(Buffer.from(json, "utf8"));
1857
+ const encrypted = encryptSyncBlob(compressed);
1858
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
1859
+ method: "POST",
1860
+ headers: {
1861
+ Authorization: `Bearer ${config.apiKey}`,
1862
+ "Content-Type": "application/json",
1863
+ "X-Device-Id": loadDeviceId()
1864
+ },
1865
+ body: JSON.stringify({ blob: encrypted })
1866
+ });
1867
+ if (resp.ok) {
1868
+ try {
1869
+ const client = getClient();
1870
+ await client.execute({
1871
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
1872
+ args: [String(blob.version)]
1873
+ });
1874
+ } catch {
1875
+ }
1876
+ }
1877
+ return resp.ok;
1878
+ } catch (err) {
1879
+ process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
1880
+ `);
1881
+ return false;
1882
+ }
1883
+ }
1884
+ async function cloudPullRoster(config) {
1885
+ assertSecureEndpoint(config.endpoint);
1886
+ try {
1887
+ const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
1888
+ method: "GET",
1889
+ headers: {
1890
+ Authorization: `Bearer ${config.apiKey}`,
1891
+ "X-Device-Id": loadDeviceId()
1892
+ }
1893
+ });
1894
+ if (!resp.ok) return { added: 0 };
1895
+ const data = await resp.json();
1896
+ if (!data.blob) return { added: 0 };
1897
+ const compressed = decryptSyncBlob(data.blob);
1898
+ const json = decompress(compressed).toString("utf8");
1899
+ const remote = JSON.parse(json);
1900
+ return mergeRosterFromRemote(remote);
1901
+ } catch (err) {
1902
+ process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
1903
+ `);
1904
+ return { added: 0 };
1905
+ }
1906
+ }
1907
+ function mergeConfig(remoteConfig, configPath) {
1908
+ const cfgPath = configPath ?? path7.join(EXE_AI_DIR, "config.json");
1909
+ let local = {};
1910
+ if (existsSync7(cfgPath)) {
1911
+ try {
1912
+ local = JSON.parse(readFileSync5(cfgPath, "utf-8"));
1913
+ } catch {
1914
+ }
1915
+ }
1916
+ const merged = { ...remoteConfig, ...local };
1917
+ const dir = path7.dirname(cfgPath);
1918
+ if (!existsSync7(dir)) mkdirSync2(dir, { recursive: true });
1919
+ writeFileSync2(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
1920
+ }
1921
+ async function mergeRosterFromRemote(remote, paths) {
1922
+ return withRosterLock(async () => {
1923
+ const rosterPath = paths?.rosterPath ?? void 0;
1924
+ const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
1925
+ const localEmployees = await loadEmployees(rosterPath);
1926
+ const localNames = new Set(localEmployees.map((e) => e.name));
1927
+ let added = 0;
1928
+ let identitiesUpdated = 0;
1929
+ for (const remoteEmp of remote.roster) {
1930
+ if (!localNames.has(remoteEmp.name)) {
1931
+ localEmployees.push(remoteEmp);
1932
+ localNames.add(remoteEmp.name);
1933
+ added++;
1934
+ try {
1935
+ registerBinSymlinks(remoteEmp.name);
1936
+ } catch {
1937
+ }
1938
+ }
1939
+ const remoteIdentity = remote.identities[`${remoteEmp.name}.md`];
1940
+ if (remoteIdentity) {
1941
+ if (!existsSync7(identityDir)) mkdirSync2(identityDir, { recursive: true });
1942
+ const idPath = path7.join(identityDir, `${remoteEmp.name}.md`);
1943
+ let localIdentity = null;
1944
+ try {
1945
+ localIdentity = existsSync7(idPath) ? readFileSync5(idPath, "utf-8") : null;
1946
+ } catch {
1947
+ }
1948
+ if (localIdentity !== remoteIdentity) {
1949
+ writeFileSync2(idPath, remoteIdentity, "utf-8");
1950
+ identitiesUpdated++;
1951
+ }
1952
+ }
1953
+ }
1954
+ let removed = 0;
1955
+ if (remote.deletedNames && remote.deletedNames.length > 0) {
1956
+ const toRemove = new Set(remote.deletedNames);
1957
+ const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
1958
+ removed = localEmployees.length - filtered.length;
1959
+ if (removed > 0) {
1960
+ localEmployees.length = 0;
1961
+ localEmployees.push(...filtered);
1962
+ }
1963
+ }
1964
+ if (added > 0 || removed > 0) {
1965
+ await saveEmployees(localEmployees, rosterPath);
1966
+ }
1967
+ if (remote.config && Object.keys(remote.config).length > 0) {
1968
+ try {
1969
+ mergeConfig(remote.config, paths?.configPath);
1970
+ } catch {
1971
+ }
1972
+ }
1973
+ return { added, identitiesUpdated };
1974
+ });
1975
+ }
1976
+ async function cloudPushBlob(route, data, metaKey, config) {
1977
+ if (data.length === 0) return { ok: true };
1978
+ assertSecureEndpoint(config.endpoint);
1979
+ const json = JSON.stringify(data);
1980
+ const version = Buffer.from(json).length;
1981
+ try {
1982
+ const client = getClient();
1983
+ const meta = await client.execute({
1984
+ sql: "SELECT value FROM sync_meta WHERE key = ?",
1985
+ args: [metaKey]
1986
+ });
1987
+ const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
1988
+ if (version === lastVersion) return { ok: true };
1989
+ } catch {
1990
+ }
1991
+ try {
1992
+ const compressed = compress(Buffer.from(json, "utf8"));
1993
+ const encrypted = encryptSyncBlob(compressed);
1994
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
1995
+ method: "POST",
1996
+ headers: {
1997
+ Authorization: `Bearer ${config.apiKey}`,
1998
+ "Content-Type": "application/json",
1999
+ "X-Device-Id": loadDeviceId()
2000
+ },
2001
+ body: JSON.stringify({ blob: encrypted })
2002
+ });
2003
+ if (resp.ok) {
2004
+ try {
2005
+ const client = getClient();
2006
+ await client.execute({
2007
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
2008
+ args: [metaKey, String(version)]
2009
+ });
2010
+ } catch {
2011
+ }
2012
+ }
2013
+ return { ok: resp.ok };
2014
+ } catch (err) {
2015
+ logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
2016
+ return { ok: false };
2017
+ }
2018
+ }
2019
+ async function cloudPullBlob(route, config) {
2020
+ assertSecureEndpoint(config.endpoint);
2021
+ try {
2022
+ const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
2023
+ method: "GET",
2024
+ headers: {
2025
+ Authorization: `Bearer ${config.apiKey}`,
2026
+ "X-Device-Id": loadDeviceId()
2027
+ }
2028
+ });
2029
+ if (!resp.ok) return null;
2030
+ const data = await resp.json();
2031
+ if (!data.blob) return null;
2032
+ const compressed = decryptSyncBlob(data.blob);
2033
+ const json = decompress(compressed).toString("utf8");
2034
+ return JSON.parse(json);
2035
+ } catch (err) {
2036
+ logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
2037
+ return null;
2038
+ }
2039
+ }
2040
+ async function cloudPushGlobalProcedures(config) {
2041
+ const client = getClient();
2042
+ const result = await client.execute("SELECT * FROM global_procedures LIMIT 1000");
2043
+ const rows = result.rows;
2044
+ const { ok } = await cloudPushBlob(
2045
+ "/sync/push-global-procedures",
2046
+ rows,
2047
+ "last_global_procedures_push_version",
2048
+ config
2049
+ );
2050
+ return ok;
2051
+ }
2052
+ async function cloudPullGlobalProcedures(config) {
2053
+ const remoteProcs = await cloudPullBlob(
2054
+ "/sync/pull-global-procedures",
2055
+ config
2056
+ );
2057
+ if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
2058
+ const client = getClient();
2059
+ const stmts = remoteProcs.map((p) => ({
2060
+ sql: `INSERT INTO global_procedures
2061
+ (id, title, content, priority, domain, active, created_at, updated_at)
2062
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2063
+ ON CONFLICT(id) DO UPDATE SET
2064
+ title = excluded.title,
2065
+ content = excluded.content,
2066
+ priority = excluded.priority,
2067
+ domain = excluded.domain,
2068
+ active = excluded.active,
2069
+ updated_at = excluded.updated_at
2070
+ WHERE excluded.updated_at > global_procedures.updated_at`,
2071
+ args: [
2072
+ sqlSafe(p.id),
2073
+ sqlSafe(p.title),
2074
+ sqlSafe(p.content),
2075
+ sqlSafe(p.priority ?? "p0"),
2076
+ sqlSafe(p.domain),
2077
+ sqlSafe(p.active ?? 1),
2078
+ sqlSafe(p.created_at),
2079
+ sqlSafe(p.updated_at)
2080
+ ]
2081
+ }));
2082
+ await client.batch(stmts, "write");
2083
+ return { pulled: remoteProcs.length };
2084
+ }
2085
+ async function cloudPushBehaviors(config) {
2086
+ const client = getClient();
2087
+ const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
2088
+ const rows = result.rows;
2089
+ const { ok } = await cloudPushBlob(
2090
+ "/sync/push-behaviors",
2091
+ rows,
2092
+ "last_behaviors_push_version",
2093
+ config
2094
+ );
2095
+ return ok;
2096
+ }
2097
+ async function cloudPullBehaviors(config) {
2098
+ const remoteBehaviors = await cloudPullBlob(
2099
+ "/sync/pull-behaviors",
2100
+ config
2101
+ );
2102
+ if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
2103
+ const client = getClient();
2104
+ let pulled = 0;
2105
+ for (const behavior of remoteBehaviors) {
2106
+ const existing = await client.execute({
2107
+ sql: `SELECT COUNT(*) as cnt FROM behaviors
2108
+ WHERE agent_id = ? AND content = ?`,
2109
+ args: [sqlSafe(behavior.agent_id), sqlSafe(behavior.content)]
2110
+ });
2111
+ if (Number(existing.rows[0]?.cnt) > 0) continue;
2112
+ await client.execute({
2113
+ sql: `INSERT OR IGNORE INTO behaviors
2114
+ (id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
2115
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2116
+ args: [
2117
+ sqlSafe(behavior.id),
2118
+ sqlSafe(behavior.agent_id),
2119
+ sqlSafe(behavior.project_name),
2120
+ sqlSafe(behavior.domain),
2121
+ sqlSafe(behavior.content),
2122
+ sqlSafe(behavior.active ?? 1),
2123
+ sqlSafe(behavior.priority ?? "p1"),
2124
+ sqlSafe(behavior.created_at),
2125
+ sqlSafe(behavior.updated_at)
2126
+ ]
2127
+ });
2128
+ pulled++;
2129
+ }
2130
+ return { pulled };
2131
+ }
2132
+ async function cloudPushGraphRAG(config) {
2133
+ const client = getClient();
2134
+ const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
2135
+ client.execute("SELECT * FROM entities LIMIT 50000"),
2136
+ client.execute("SELECT * FROM relationships LIMIT 50000"),
2137
+ client.execute("SELECT * FROM entity_aliases LIMIT 50000"),
2138
+ client.execute("SELECT * FROM entity_memories LIMIT 50000"),
2139
+ client.execute("SELECT * FROM relationship_memories LIMIT 50000"),
2140
+ client.execute("SELECT * FROM hyperedges LIMIT 50000"),
2141
+ client.execute("SELECT * FROM hyperedge_nodes LIMIT 50000")
2142
+ ]);
2143
+ const blob = {
2144
+ entities: entities.rows,
2145
+ relationships: relationships.rows,
2146
+ entity_aliases: aliases.rows,
2147
+ entity_memories: entityMems.rows,
2148
+ relationship_memories: relMems.rows,
2149
+ hyperedges: hyperedges.rows,
2150
+ hyperedge_nodes: hyperedgeNodes.rows
2151
+ };
2152
+ const { ok } = await cloudPushBlob(
2153
+ "/sync/push-graphrag",
2154
+ [blob],
2155
+ "last_graphrag_push_version",
2156
+ config
2157
+ );
2158
+ return ok;
2159
+ }
2160
+ async function cloudPullGraphRAG(config) {
2161
+ const data = await cloudPullBlob(
2162
+ "/sync/pull-graphrag",
2163
+ config
2164
+ );
2165
+ if (!data || data.length === 0) return { pulled: 0 };
2166
+ const blob = data[0];
2167
+ const client = getClient();
2168
+ let pulled = 0;
2169
+ if (blob.entities.length > 0) {
2170
+ const stmts = blob.entities.map((e) => ({
2171
+ sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
2172
+ VALUES (?, ?, ?, ?, ?, ?)`,
2173
+ args: [sqlSafe(e.id), sqlSafe(e.name), sqlSafe(e.type), sqlSafe(e.first_seen), sqlSafe(e.last_seen), sqlSafe(e.properties ?? "{}")]
2174
+ }));
2175
+ await client.batch(stmts, "write");
2176
+ pulled += stmts.length;
2177
+ }
2178
+ if (blob.relationships.length > 0) {
2179
+ const stmts = blob.relationships.map((r) => ({
2180
+ sql: `INSERT OR IGNORE INTO relationships
2181
+ (id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
2182
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2183
+ args: [
2184
+ sqlSafe(r.id),
2185
+ sqlSafe(r.source_entity_id),
2186
+ sqlSafe(r.target_entity_id),
2187
+ sqlSafe(r.type),
2188
+ sqlSafe(r.weight ?? 1),
2189
+ sqlSafe(r.timestamp),
2190
+ sqlSafe(r.properties ?? "{}"),
2191
+ sqlSafe(r.confidence ?? 1),
2192
+ sqlSafe(r.confidence_label ?? "extracted")
2193
+ ]
2194
+ }));
2195
+ await client.batch(stmts, "write");
2196
+ pulled += stmts.length;
2197
+ }
2198
+ if (blob.entity_aliases.length > 0) {
2199
+ const stmts = blob.entity_aliases.map((a) => ({
2200
+ sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
2201
+ args: [sqlSafe(a.alias), sqlSafe(a.canonical_entity_id)]
2202
+ }));
2203
+ await client.batch(stmts, "write");
2204
+ pulled += stmts.length;
2205
+ }
2206
+ if (blob.entity_memories.length > 0) {
2207
+ const stmts = blob.entity_memories.map((em) => ({
2208
+ sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
2209
+ args: [sqlSafe(em.entity_id), sqlSafe(em.memory_id)]
2210
+ }));
2211
+ await client.batch(stmts, "write");
2212
+ pulled += stmts.length;
2213
+ }
2214
+ if (blob.relationship_memories.length > 0) {
2215
+ const stmts = blob.relationship_memories.map((rm) => ({
2216
+ sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
2217
+ args: [sqlSafe(rm.relationship_id), sqlSafe(rm.memory_id)]
2218
+ }));
2219
+ await client.batch(stmts, "write");
2220
+ pulled += stmts.length;
2221
+ }
2222
+ if (blob.hyperedges.length > 0) {
2223
+ const stmts = blob.hyperedges.map((h) => ({
2224
+ sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
2225
+ VALUES (?, ?, ?, ?, ?)`,
2226
+ args: [sqlSafe(h.id), sqlSafe(h.label), sqlSafe(h.relation), sqlSafe(h.confidence ?? 1), sqlSafe(h.timestamp)]
2227
+ }));
2228
+ await client.batch(stmts, "write");
2229
+ pulled += stmts.length;
2230
+ }
2231
+ if (blob.hyperedge_nodes.length > 0) {
2232
+ const stmts = blob.hyperedge_nodes.map((hn) => ({
2233
+ sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
2234
+ args: [sqlSafe(hn.hyperedge_id), sqlSafe(hn.entity_id)]
2235
+ }));
2236
+ await client.batch(stmts, "write");
2237
+ pulled += stmts.length;
2238
+ }
2239
+ return { pulled };
2240
+ }
2241
+ async function cloudPushTasks(config) {
2242
+ const client = getClient();
2243
+ const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
2244
+ const rows = result.rows;
2245
+ const { ok } = await cloudPushBlob(
2246
+ "/sync/push-tasks",
2247
+ rows,
2248
+ "last_tasks_push_version",
2249
+ config
2250
+ );
2251
+ return ok;
2252
+ }
2253
+ async function cloudPullTasks(config) {
2254
+ const remoteTasks = await cloudPullBlob(
2255
+ "/sync/pull-tasks",
2256
+ config
2257
+ );
2258
+ if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
2259
+ const client = getClient();
2260
+ const stmts = remoteTasks.map((t) => ({
2261
+ sql: `INSERT OR IGNORE INTO tasks
2262
+ (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
2263
+ blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
2264
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2265
+ args: [
2266
+ sqlSafe(t.id),
2267
+ sqlSafe(t.title),
2268
+ sqlSafe(t.assigned_to),
2269
+ sqlSafe(t.assigned_by),
2270
+ sqlSafe(t.project_name),
2271
+ sqlSafe(t.priority ?? "p1"),
2272
+ sqlSafe(t.status ?? "open"),
2273
+ sqlSafe(t.task_file),
2274
+ sqlSafe(t.created_at),
2275
+ sqlSafe(t.updated_at),
2276
+ sqlSafe(t.blocked_by),
2277
+ sqlSafe(t.parent_task_id),
2278
+ sqlSafe(t.budget_tokens),
2279
+ sqlSafe(t.budget_fallback_model),
2280
+ sqlSafe(t.tokens_used ?? 0),
2281
+ sqlSafe(t.tokens_warned_at)
2282
+ ]
2283
+ }));
2284
+ await client.batch(stmts, "write");
2285
+ return { pulled: remoteTasks.length };
2286
+ }
2287
+ async function cloudPushConversations(config) {
2288
+ const client = getClient();
2289
+ const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
2290
+ const rows = result.rows;
2291
+ const { ok } = await cloudPushBlob(
2292
+ "/sync/push-conversations",
2293
+ rows,
2294
+ "last_conversations_push_version",
2295
+ config
2296
+ );
2297
+ return ok;
2298
+ }
2299
+ async function cloudPullConversations(config) {
2300
+ const remoteConvos = await cloudPullBlob(
2301
+ "/sync/pull-conversations",
2302
+ config
2303
+ );
2304
+ if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
2305
+ const client = getClient();
2306
+ const stmts = remoteConvos.map((c) => ({
2307
+ sql: `INSERT OR IGNORE INTO conversations
2308
+ (id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
2309
+ recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
2310
+ content_metadata, agent_response, agent_name, timestamp, ingested_at)
2311
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2312
+ args: [
2313
+ sqlSafe(c.id),
2314
+ sqlSafe(c.platform),
2315
+ sqlSafe(c.external_id),
2316
+ sqlSafe(c.sender_id),
2317
+ sqlSafe(c.sender_name),
2318
+ sqlSafe(c.sender_phone),
2319
+ sqlSafe(c.sender_email),
2320
+ sqlSafe(c.recipient_id),
2321
+ sqlSafe(c.channel_id),
2322
+ sqlSafe(c.thread_id),
2323
+ sqlSafe(c.reply_to_id),
2324
+ sqlSafe(c.content_text),
2325
+ sqlSafe(c.content_media),
2326
+ sqlSafe(c.content_metadata),
2327
+ sqlSafe(c.agent_response),
2328
+ sqlSafe(c.agent_name),
2329
+ sqlSafe(c.timestamp),
2330
+ sqlSafe(c.ingested_at)
2331
+ ]
2332
+ }));
2333
+ await client.batch(stmts, "write");
2334
+ return { pulled: remoteConvos.length };
2335
+ }
2336
+ async function cloudPushDocuments(config) {
2337
+ const client = getClient();
2338
+ const [workspaces, documents] = await Promise.all([
2339
+ client.execute("SELECT * FROM workspaces LIMIT 1000"),
2340
+ client.execute("SELECT * FROM documents LIMIT 10000")
2341
+ ]);
2342
+ const blob = {
2343
+ workspaces: workspaces.rows,
2344
+ documents: documents.rows
2345
+ };
2346
+ const { ok } = await cloudPushBlob(
2347
+ "/sync/push-documents",
2348
+ [blob],
2349
+ "last_documents_push_version",
2350
+ config
2351
+ );
2352
+ return ok;
2353
+ }
2354
+ async function cloudPullDocuments(config) {
2355
+ const data = await cloudPullBlob(
2356
+ "/sync/pull-documents",
2357
+ config
2358
+ );
2359
+ if (!data || data.length === 0) return { pulled: 0 };
2360
+ const blob = data[0];
2361
+ const client = getClient();
2362
+ let pulled = 0;
2363
+ if (blob.workspaces.length > 0) {
2364
+ const stmts = blob.workspaces.map((w) => ({
2365
+ sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
2366
+ VALUES (?, ?, ?, ?, ?, ?)`,
2367
+ args: [sqlSafe(w.id), sqlSafe(w.slug), sqlSafe(w.name), sqlSafe(w.owner_agent_id), sqlSafe(w.created_at), sqlSafe(w.metadata)]
2368
+ }));
2369
+ await client.batch(stmts, "write");
2370
+ pulled += stmts.length;
2371
+ }
2372
+ if (blob.documents.length > 0) {
2373
+ const stmts = blob.documents.map((d) => ({
2374
+ sql: `INSERT OR IGNORE INTO documents
2375
+ (id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
2376
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
2377
+ args: [
2378
+ sqlSafe(d.id),
2379
+ sqlSafe(d.workspace_id),
2380
+ sqlSafe(d.filename),
2381
+ sqlSafe(d.mime),
2382
+ sqlSafe(d.source_type),
2383
+ sqlSafe(d.user_id),
2384
+ sqlSafe(d.uploaded_at),
2385
+ sqlSafe(d.metadata)
2386
+ ]
2387
+ }));
2388
+ await client.batch(stmts, "write");
2389
+ pulled += stmts.length;
2390
+ }
2391
+ return { pulled };
2392
+ }
2393
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
2394
+ var init_cloud_sync = __esm({
2395
+ "src/lib/cloud-sync.ts"() {
1226
2396
  "use strict";
1227
- init_db_retry();
1228
- _resilientClient = null;
2397
+ init_database();
2398
+ init_crypto();
2399
+ init_compress();
2400
+ init_license();
2401
+ init_config();
2402
+ init_employees();
2403
+ LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
2404
+ FETCH_TIMEOUT_MS = 3e4;
2405
+ PUSH_BATCH_SIZE = 5e3;
2406
+ ROSTER_LOCK_PATH = path7.join(EXE_AI_DIR, "roster-merge.lock");
2407
+ LOCK_STALE_MS = 3e4;
2408
+ ROSTER_DELETIONS_PATH = path7.join(EXE_AI_DIR, "roster-deletions.json");
1229
2409
  }
1230
2410
  });
1231
2411
 
@@ -1990,17 +3170,17 @@ __export(identity_exports, {
1990
3170
  listIdentities: () => listIdentities,
1991
3171
  updateIdentity: () => updateIdentity
1992
3172
  });
1993
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1994
- import { readdirSync } from "fs";
1995
- import path7 from "path";
3173
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
3174
+ import { readdirSync as readdirSync2 } from "fs";
3175
+ import path8 from "path";
1996
3176
  import { createHash as createHash2 } from "crypto";
1997
3177
  function ensureDir() {
1998
- if (!existsSync7(IDENTITY_DIR)) {
1999
- mkdirSync2(IDENTITY_DIR, { recursive: true });
3178
+ if (!existsSync8(IDENTITY_DIR)) {
3179
+ mkdirSync3(IDENTITY_DIR, { recursive: true });
2000
3180
  }
2001
3181
  }
2002
3182
  function identityPath(agentId) {
2003
- return path7.join(IDENTITY_DIR, `${agentId}.md`);
3183
+ return path8.join(IDENTITY_DIR, `${agentId}.md`);
2004
3184
  }
2005
3185
  function parseFrontmatter(raw) {
2006
3186
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
@@ -2041,8 +3221,8 @@ function contentHash(content) {
2041
3221
  }
2042
3222
  function getIdentity(agentId) {
2043
3223
  const filePath = identityPath(agentId);
2044
- if (!existsSync7(filePath)) return null;
2045
- const raw = readFileSync5(filePath, "utf-8");
3224
+ if (!existsSync8(filePath)) return null;
3225
+ const raw = readFileSync6(filePath, "utf-8");
2046
3226
  const { frontmatter, body } = parseFrontmatter(raw);
2047
3227
  return {
2048
3228
  agentId,
@@ -2056,7 +3236,7 @@ async function updateIdentity(agentId, content, updatedBy) {
2056
3236
  ensureDir();
2057
3237
  const filePath = identityPath(agentId);
2058
3238
  const hash = contentHash(content);
2059
- writeFileSync2(filePath, content, "utf-8");
3239
+ writeFileSync3(filePath, content, "utf-8");
2060
3240
  try {
2061
3241
  const client = getClient();
2062
3242
  await client.execute({
@@ -2073,7 +3253,7 @@ async function updateIdentity(agentId, content, updatedBy) {
2073
3253
  }
2074
3254
  function listIdentities() {
2075
3255
  ensureDir();
2076
- const files = readdirSync(IDENTITY_DIR).filter((f) => f.endsWith(".md"));
3256
+ const files = readdirSync2(IDENTITY_DIR).filter((f) => f.endsWith(".md"));
2077
3257
  const results = [];
2078
3258
  for (const file of files) {
2079
3259
  const agentId = file.replace(".md", "");
@@ -2112,7 +3292,7 @@ var init_identity = __esm({
2112
3292
  "use strict";
2113
3293
  init_config();
2114
3294
  init_database();
2115
- IDENTITY_DIR = path7.join(EXE_AI_DIR, "identity");
3295
+ IDENTITY_DIR = path8.join(EXE_AI_DIR, "identity");
2116
3296
  }
2117
3297
  });
2118
3298
 
@@ -2646,73 +3826,129 @@ ${PLAN_MODE_COMPAT}
2646
3826
  }
2647
3827
  });
2648
3828
 
2649
- // src/lib/setup-wizard.ts
2650
- init_config();
2651
- import crypto2 from "crypto";
2652
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3 } from "fs";
2653
- import os3 from "os";
2654
- import path8 from "path";
2655
- import { createInterface } from "readline";
2656
-
2657
- // src/lib/keychain.ts
2658
- import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
2659
- import { existsSync as existsSync2 } from "fs";
2660
- import path2 from "path";
2661
- import os2 from "os";
2662
- import crypto from "crypto";
2663
- var SERVICE = "exe-mem";
2664
- var ACCOUNT = "master-key";
2665
- function getKeyDir() {
2666
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(os2.homedir(), ".exe-os");
2667
- }
2668
- function getKeyPath() {
2669
- return path2.join(getKeyDir(), "master.key");
2670
- }
2671
- async function tryKeytar() {
3829
+ // src/lib/session-wrappers.ts
3830
+ var session_wrappers_exports = {};
3831
+ __export(session_wrappers_exports, {
3832
+ generateSessionWrappers: () => generateSessionWrappers
3833
+ });
3834
+ import {
3835
+ existsSync as existsSync9,
3836
+ readFileSync as readFileSync7,
3837
+ writeFileSync as writeFileSync4,
3838
+ mkdirSync as mkdirSync4,
3839
+ chmodSync,
3840
+ readdirSync as readdirSync3,
3841
+ unlinkSync as unlinkSync4
3842
+ } from "fs";
3843
+ import path9 from "path";
3844
+ import { homedir as homedir2 } from "os";
3845
+ function generateSessionWrappers(packageRoot, homeDir) {
3846
+ const home = homeDir ?? homedir2();
3847
+ const binDir = path9.join(home, ".exe-os", "bin");
3848
+ const rosterPath = path9.join(home, ".exe-os", "exe-employees.json");
3849
+ mkdirSync4(binDir, { recursive: true });
3850
+ const exeStartDst = path9.join(binDir, "exe-start");
3851
+ const candidates = [
3852
+ path9.join(packageRoot, "dist", "bin", "exe-start.sh"),
3853
+ path9.join(packageRoot, "src", "bin", "exe-start.sh")
3854
+ ];
3855
+ for (const src of candidates) {
3856
+ if (existsSync9(src)) {
3857
+ writeFileSync4(exeStartDst, readFileSync7(src));
3858
+ chmodSync(exeStartDst, 493);
3859
+ break;
3860
+ }
3861
+ }
3862
+ let employees = [];
2672
3863
  try {
2673
- return await import("keytar");
3864
+ employees = JSON.parse(readFileSync7(rosterPath, "utf8"));
2674
3865
  } catch {
2675
- return null;
3866
+ return { created: 0, pathConfigured: false };
2676
3867
  }
2677
- }
2678
- async function getMasterKey() {
2679
- const keytar = await tryKeytar();
2680
- if (keytar) {
2681
- try {
2682
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
2683
- if (stored) {
2684
- return Buffer.from(stored, "base64");
2685
- }
2686
- } catch {
2687
- }
2688
- }
2689
- const keyPath = getKeyPath();
2690
- if (!existsSync2(keyPath)) {
2691
- return null;
3868
+ if (employees.length === 0) {
3869
+ return { created: 0, pathConfigured: false };
2692
3870
  }
2693
3871
  try {
2694
- const content = await readFile2(keyPath, "utf-8");
2695
- return Buffer.from(content.trim(), "base64");
3872
+ for (const f of readdirSync3(binDir)) {
3873
+ if (f === "exe-start") continue;
3874
+ const fPath = path9.join(binDir, f);
3875
+ try {
3876
+ const content = readFileSync7(fPath, "utf8");
3877
+ if (content.includes("exe-start")) {
3878
+ unlinkSync4(fPath);
3879
+ }
3880
+ } catch {
3881
+ }
3882
+ }
2696
3883
  } catch {
2697
- return null;
2698
3884
  }
3885
+ let created = 0;
3886
+ const wrapperContent = `#!/bin/bash
3887
+ exec "${exeStartDst}" "$0" "$@"
3888
+ `;
3889
+ for (const emp of employees) {
3890
+ for (let n = 1; n <= MAX_N; n++) {
3891
+ const wrapperPath = path9.join(binDir, `${emp.name}${n}`);
3892
+ writeFileSync4(wrapperPath, wrapperContent);
3893
+ chmodSync(wrapperPath, 493);
3894
+ created++;
3895
+ }
3896
+ }
3897
+ const pathConfigured = ensurePath(home, binDir);
3898
+ return { created, pathConfigured };
2699
3899
  }
2700
- async function setMasterKey(key) {
2701
- const b64 = key.toString("base64");
2702
- const keytar = await tryKeytar();
2703
- if (keytar) {
3900
+ function ensurePath(home, binDir) {
3901
+ if (process.env.PATH?.split(":").includes(binDir)) {
3902
+ return false;
3903
+ }
3904
+ const exportLine = `
3905
+ # exe-os session commands
3906
+ export PATH="${binDir}:$PATH"
3907
+ `;
3908
+ const shell = process.env.SHELL ?? "/bin/bash";
3909
+ const profilePaths = [];
3910
+ if (shell.includes("zsh")) {
3911
+ profilePaths.push(path9.join(home, ".zshrc"));
3912
+ } else if (shell.includes("bash")) {
3913
+ profilePaths.push(path9.join(home, ".bashrc"));
3914
+ profilePaths.push(path9.join(home, ".bash_profile"));
3915
+ } else {
3916
+ profilePaths.push(path9.join(home, ".profile"));
3917
+ }
3918
+ for (const profilePath of profilePaths) {
2704
3919
  try {
2705
- await keytar.setPassword(SERVICE, ACCOUNT, b64);
2706
- return;
3920
+ let content = "";
3921
+ try {
3922
+ content = readFileSync7(profilePath, "utf8");
3923
+ } catch {
3924
+ }
3925
+ if (content.includes(".exe-os/bin")) {
3926
+ return false;
3927
+ }
3928
+ writeFileSync4(profilePath, content + exportLine);
3929
+ return true;
2707
3930
  } catch {
3931
+ continue;
2708
3932
  }
2709
3933
  }
2710
- const dir = getKeyDir();
2711
- await mkdir2(dir, { recursive: true });
2712
- const keyPath = getKeyPath();
2713
- await writeFile2(keyPath, b64 + "\n", "utf-8");
2714
- await chmod2(keyPath, 384);
3934
+ return false;
2715
3935
  }
3936
+ var MAX_N;
3937
+ var init_session_wrappers = __esm({
3938
+ "src/lib/session-wrappers.ts"() {
3939
+ "use strict";
3940
+ MAX_N = 9;
3941
+ }
3942
+ });
3943
+
3944
+ // src/lib/setup-wizard.ts
3945
+ init_config();
3946
+ init_keychain();
3947
+ import crypto3 from "crypto";
3948
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5, unlinkSync as unlinkSync5 } from "fs";
3949
+ import os3 from "os";
3950
+ import path10 from "path";
3951
+ import { createInterface } from "readline";
2716
3952
 
2717
3953
  // src/lib/model-downloader.ts
2718
3954
  import { createWriteStream, createReadStream, existsSync as existsSync3, unlinkSync, renameSync as renameSync2 } from "fs";
@@ -2802,21 +4038,37 @@ async function fileHash(filePath) {
2802
4038
  }
2803
4039
 
2804
4040
  // src/lib/setup-wizard.ts
2805
- var SETUP_STATE_PATH = path8.join(os3.homedir(), ".exe-os", "setup-state.json");
4041
+ function findPackageRoot2() {
4042
+ let dir = path10.dirname(new URL(import.meta.url).pathname);
4043
+ const root = path10.parse(dir).root;
4044
+ while (dir !== root) {
4045
+ const pkgPath = path10.join(dir, "package.json");
4046
+ if (existsSync10(pkgPath)) {
4047
+ try {
4048
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
4049
+ if (pkg.name === "@askexenow/exe-os" || pkg.name === "exe-os") return dir;
4050
+ } catch {
4051
+ }
4052
+ }
4053
+ dir = path10.dirname(dir);
4054
+ }
4055
+ return null;
4056
+ }
4057
+ var SETUP_STATE_PATH = path10.join(os3.homedir(), ".exe-os", "setup-state.json");
2806
4058
  function loadSetupState() {
2807
4059
  try {
2808
- return JSON.parse(readFileSync6(SETUP_STATE_PATH, "utf8"));
4060
+ return JSON.parse(readFileSync8(SETUP_STATE_PATH, "utf8"));
2809
4061
  } catch {
2810
4062
  return { completedSteps: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
2811
4063
  }
2812
4064
  }
2813
4065
  function saveSetupState(state) {
2814
- mkdirSync3(path8.dirname(SETUP_STATE_PATH), { recursive: true });
2815
- writeFileSync3(SETUP_STATE_PATH, JSON.stringify(state, null, 2));
4066
+ mkdirSync5(path10.dirname(SETUP_STATE_PATH), { recursive: true });
4067
+ writeFileSync5(SETUP_STATE_PATH, JSON.stringify(state, null, 2));
2816
4068
  }
2817
4069
  function clearSetupState() {
2818
4070
  try {
2819
- unlinkSync3(SETUP_STATE_PATH);
4071
+ unlinkSync5(SETUP_STATE_PATH);
2820
4072
  } catch {
2821
4073
  }
2822
4074
  }
@@ -2850,20 +4102,67 @@ async function runSetupWizard(opts = {}) {
2850
4102
  log("");
2851
4103
  log("=== exe-os Setup ===");
2852
4104
  log("");
2853
- log("Already set up on another device?");
2854
- log(" Run `exe-link` to sync your encryption key and skip setup.");
4105
+ log("Is this a new installation or pairing with an existing device?");
2855
4106
  log("");
2856
- const skipSetup = await ask(rl, "Fresh install? (Y/n): ");
2857
- if (skipSetup.toLowerCase() === "n") {
2858
- log("Run `exe-link` to import your encryption key from another device.");
2859
- rl.close();
2860
- return;
4107
+ log(" [1] New installation (first device)");
4108
+ log(" [2] Pair with existing device (I have a 24-word phrase)");
4109
+ log("");
4110
+ const installType = await ask(rl, "Choice (1/2): ");
4111
+ let isPairing = false;
4112
+ let pairingRosterPulled = false;
4113
+ if (installType === "2") {
4114
+ isPairing = true;
4115
+ log("");
4116
+ const { importMnemonic: importMnemonic2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
4117
+ const mnemonic = await ask(rl, "Paste your 24-word recovery phrase: ");
4118
+ try {
4119
+ const key = await importMnemonic2(mnemonic);
4120
+ await setMasterKey(key);
4121
+ log("Master key imported and stored securely.");
4122
+ log("");
4123
+ log("Enter the API key from your existing device.");
4124
+ log("(Find it in ~/.exe-os/config.json under cloud.apiKey, or ask your team lead.)");
4125
+ log("");
4126
+ const apiKey = await ask(rl, "API key (exe_sk_...): ");
4127
+ if (apiKey && apiKey.startsWith("exe_sk_")) {
4128
+ const cloudEndpoint = "https://askexe.com/cloud";
4129
+ const cloudCfg = { apiKey, endpoint: cloudEndpoint };
4130
+ const earlyConfig = await loadConfig();
4131
+ earlyConfig.cloud = cloudCfg;
4132
+ await saveConfig(earlyConfig);
4133
+ const { saveLicense: saveLic, mirrorLicenseKey: mirrorLic } = await Promise.resolve().then(() => (init_license(), license_exports));
4134
+ saveLic(apiKey);
4135
+ mirrorLic(apiKey);
4136
+ log("Cloud sync configured.");
4137
+ try {
4138
+ const { initSyncCrypto: initSyncCrypto2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
4139
+ const { cloudPullRoster: cloudPullRoster2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
4140
+ initSyncCrypto2(key);
4141
+ const result = await cloudPullRoster2({ apiKey, endpoint: cloudEndpoint });
4142
+ if (result.added > 0) {
4143
+ log(`Pulled ${result.added} employee(s) from Exe Cloud.`);
4144
+ pairingRosterPulled = true;
4145
+ }
4146
+ } catch {
4147
+ log("Could not pull roster from cloud \u2014 you can set up employees manually.");
4148
+ }
4149
+ } else {
4150
+ log("No API key provided \u2014 cloud sync will need to be configured later.");
4151
+ log("Run /exe-cloud after setup to connect.");
4152
+ }
4153
+ log("");
4154
+ } catch (err) {
4155
+ log(`Key import failed: ${err instanceof Error ? err.message : String(err)}`);
4156
+ log("Check your phrase and try again, or choose option 1 for a fresh install.");
4157
+ rl.close();
4158
+ return;
4159
+ }
2861
4160
  }
2862
4161
  const state = loadSetupState();
2863
4162
  if (state.completedSteps.length > 0) {
2864
4163
  log(`Resuming setup from step ${Math.max(...state.completedSteps) + 1}...`);
2865
4164
  }
2866
- if (existsSync8(LEGACY_LANCE_PATH)) {
4165
+ if (existsSync10(LEGACY_LANCE_PATH)) {
2867
4166
  log("\u26A0 Found v1.0 LanceDB at ~/.exe-os/local.lance");
2868
4167
  log(" v1.1 uses libSQL (SQLite). Your existing memories are not automatically migrated.");
2869
4168
  log(" The old directory will not be modified or deleted.");
@@ -2875,7 +4174,7 @@ async function runSetupWizard(opts = {}) {
2875
4174
  log("Encryption key already exists \u2014 skipping generation.");
2876
4175
  } else {
2877
4176
  log("Generating 256-bit encryption key...");
2878
- const key = crypto2.randomBytes(32);
4177
+ const key = crypto3.randomBytes(32);
2879
4178
  await setMasterKey(key);
2880
4179
  log("Encryption key generated and stored securely.");
2881
4180
  }
@@ -2886,7 +4185,15 @@ async function runSetupWizard(opts = {}) {
2886
4185
  }
2887
4186
  log("");
2888
4187
  let cloudConfig;
2889
- if (!state.completedSteps.includes(2)) {
4188
+ if (isPairing) {
4189
+ const pairingConfig = await loadConfig();
4190
+ if (pairingConfig.cloud?.apiKey) {
4191
+ cloudConfig = pairingConfig.cloud;
4192
+ log("Cloud sync: using shared API key from pairing.");
4193
+ }
4194
+ state.completedSteps.push(2);
4195
+ saveSetupState(state);
4196
+ } else if (!state.completedSteps.includes(2)) {
2890
4197
  log("Exe Cloud: your memories are end-to-end encrypted, compressed, and");
2891
4198
  log("backed up on Exe Cloud. Free for all plans. We can't read your data \u2014");
2892
4199
  log("only your encryption key can decrypt it.");
@@ -2967,10 +4274,10 @@ async function runSetupWizard(opts = {}) {
2967
4274
  await saveConfig(config);
2968
4275
  log("");
2969
4276
  try {
2970
- const claudeJsonPath = path8.join(os3.homedir(), ".claude.json");
4277
+ const claudeJsonPath = path10.join(os3.homedir(), ".claude.json");
2971
4278
  let claudeJson = {};
2972
4279
  try {
2973
- claudeJson = JSON.parse(readFileSync6(claudeJsonPath, "utf8"));
4280
+ claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
2974
4281
  } catch {
2975
4282
  }
2976
4283
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -2979,7 +4286,7 @@ async function runSetupWizard(opts = {}) {
2979
4286
  if (!projects[dir]) projects[dir] = {};
2980
4287
  projects[dir].hasTrustDialogAccepted = true;
2981
4288
  }
2982
- writeFileSync3(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4289
+ writeFileSync5(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
2983
4290
  } catch {
2984
4291
  }
2985
4292
  state.completedSteps.push(5);
@@ -3005,7 +4312,34 @@ async function runSetupWizard(opts = {}) {
3005
4312
  } = await Promise.resolve().then(() => (init_license(), license_exports));
3006
4313
  const createdEmployees = [];
3007
4314
  let cooName = "exe";
3008
- if (!state.completedSteps.includes(6)) {
4315
+ if (pairingRosterPulled) {
4316
+ const roster = await loadEmployees2(EMPLOYEES_PATH2).catch(() => []);
4317
+ const existingCoo = roster.find((e) => e.role === "COO");
4318
+ if (existingCoo) {
4319
+ cooName = existingCoo.name;
4320
+ log(`Team synced from cloud. COO: ${cooName}`);
4321
+ const teamList = roster.map((e) => `${e.name} (${e.role})`).join(", ");
4322
+ log(`Team: ${teamList}`);
4323
+ createdEmployees.push(...roster.map((e) => ({ name: e.name, role: e.role })));
4324
+ }
4325
+ for (const emp of roster) {
4326
+ registerBinSymlinks2(emp.name);
4327
+ }
4328
+ try {
4329
+ const { generateSessionWrappers: generateSessionWrappers2 } = await Promise.resolve().then(() => (init_session_wrappers(), session_wrappers_exports));
4330
+ const pkgRoot = findPackageRoot2();
4331
+ if (pkgRoot) {
4332
+ const wrapResult = generateSessionWrappers2(pkgRoot);
4333
+ if (wrapResult.created > 0) {
4334
+ log(`Session shortcuts generated: ${roster.map((e) => `${e.name}1`).join(", ")}, ...`);
4335
+ }
4336
+ }
4337
+ } catch {
4338
+ }
4339
+ state.completedSteps.push(6, 7, 8);
4340
+ saveSetupState(state);
4341
+ log("");
4342
+ } else if (!state.completedSteps.includes(6)) {
3009
4343
  log("=== Your Team ===");
3010
4344
  log("");
3011
4345
  log("Every install starts with a COO \u2014 your right-hand operator.");
@@ -3031,9 +4365,9 @@ async function runSetupWizard(opts = {}) {
3031
4365
  const cooIdentityContent = getIdentityTemplate("coo");
3032
4366
  if (cooIdentityContent) {
3033
4367
  const cooIdPath = identityPath2(cooName);
3034
- mkdirSync3(path8.dirname(cooIdPath), { recursive: true });
4368
+ mkdirSync5(path10.dirname(cooIdPath), { recursive: true });
3035
4369
  const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
3036
- writeFileSync3(cooIdPath, replaced, "utf-8");
4370
+ writeFileSync5(cooIdPath, replaced, "utf-8");
3037
4371
  }
3038
4372
  registerBinSymlinks2(cooName);
3039
4373
  createdEmployees.push({ name: cooName, role: "COO" });
@@ -3132,9 +4466,9 @@ async function runSetupWizard(opts = {}) {
3132
4466
  const ctoIdentityContent = getIdentityTemplate("cto");
3133
4467
  if (ctoIdentityContent) {
3134
4468
  const ctoIdPath = identityPath2(ctoName);
3135
- mkdirSync3(path8.dirname(ctoIdPath), { recursive: true });
4469
+ mkdirSync5(path10.dirname(ctoIdPath), { recursive: true });
3136
4470
  const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
3137
- writeFileSync3(ctoIdPath, replaced, "utf-8");
4471
+ writeFileSync5(ctoIdPath, replaced, "utf-8");
3138
4472
  }
3139
4473
  registerBinSymlinks2(ctoName);
3140
4474
  createdEmployees.push({ name: ctoName, role: "CTO" });
@@ -3160,9 +4494,9 @@ async function runSetupWizard(opts = {}) {
3160
4494
  const cmoIdentityContent = getIdentityTemplate("cmo");
3161
4495
  if (cmoIdentityContent) {
3162
4496
  const cmoIdPath = identityPath2(cmoName);
3163
- mkdirSync3(path8.dirname(cmoIdPath), { recursive: true });
4497
+ mkdirSync5(path10.dirname(cmoIdPath), { recursive: true });
3164
4498
  const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
3165
- writeFileSync3(cmoIdPath, replaced, "utf-8");
4499
+ writeFileSync5(cmoIdPath, replaced, "utf-8");
3166
4500
  }
3167
4501
  registerBinSymlinks2(cmoName);
3168
4502
  createdEmployees.push({ name: cmoName, role: "CMO" });
@@ -3175,11 +4509,24 @@ async function runSetupWizard(opts = {}) {
3175
4509
  } else {
3176
4510
  log("Step 8 already complete \u2014 skipping.");
3177
4511
  }
4512
+ if (!pairingRosterPulled) {
4513
+ try {
4514
+ const { generateSessionWrappers: generateSessionWrappers2 } = await Promise.resolve().then(() => (init_session_wrappers(), session_wrappers_exports));
4515
+ const pkgRoot = findPackageRoot2();
4516
+ if (pkgRoot) {
4517
+ const wrapResult = generateSessionWrappers2(pkgRoot);
4518
+ if (wrapResult.created > 0) {
4519
+ log(`Session shortcuts generated (${cooName}1, ${cooName}2, ...)`);
4520
+ }
4521
+ }
4522
+ } catch {
4523
+ }
4524
+ }
3178
4525
  clearSetupState();
3179
4526
  log("=== Two Ways to Work ===");
3180
4527
  log("");
3181
4528
  log(" 1. Claude Code mode");
3182
- log(` Type \`${cooName}\` in your terminal. Works with your Claude Code subscription.`);
4529
+ log(` Type \`${cooName}1\` in your project folder. Works with your Claude Code subscription.`);
3183
4530
  log(" Best for developers who live in the terminal.");
3184
4531
  log("");
3185
4532
  log(" 2. Dashboard mode");
@@ -3189,8 +4536,6 @@ async function runSetupWizard(opts = {}) {
3189
4536
  log(" Both modes share the same memory, employees, and data.");
3190
4537
  log(" You can switch anytime.");
3191
4538
  log("");
3192
- log(" For Claude Code mode, run: exe-os claude");
3193
- log("");
3194
4539
  log("=== Setup Complete ===");
3195
4540
  log("Database: " + config.dbPath);
3196
4541
  if (cloudConfig) {
@@ -3206,7 +4551,7 @@ async function runSetupWizard(opts = {}) {
3206
4551
  log("Team: " + createdEmployees.map((e) => `${e.name} (${e.role})`).join(", "));
3207
4552
  }
3208
4553
  log("");
3209
- log(`Type \`${cooName}\` to start (Claude Code) or \`exe-os\` for dashboard.`);
4554
+ log(`Type \`${cooName}1\` to start (Claude Code) or \`exe-os\` for dashboard.`);
3210
4555
  log("");
3211
4556
  } finally {
3212
4557
  rl.close();