@askexenow/exe-os 0.8.37 → 0.8.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +17 -8
  2. package/dist/bin/backfill-conversations.js +112 -70
  3. package/dist/bin/backfill-responses.js +53 -18
  4. package/dist/bin/backfill-vectors.js +43 -16
  5. package/dist/bin/cleanup-stale-review-tasks.js +38 -16
  6. package/dist/bin/cli.js +790 -468
  7. package/dist/bin/exe-agent.js +19 -4
  8. package/dist/bin/exe-assign.js +46 -13
  9. package/dist/bin/exe-boot.js +288 -129
  10. package/dist/bin/exe-call.js +20 -10
  11. package/dist/bin/exe-cloud.js +135 -30
  12. package/dist/bin/exe-dispatch.js +1 -1
  13. package/dist/bin/exe-doctor.js +38 -16
  14. package/dist/bin/exe-export-behaviors.js +43 -21
  15. package/dist/bin/exe-forget.js +39 -17
  16. package/dist/bin/exe-gateway.js +159 -50
  17. package/dist/bin/exe-heartbeat.js +53 -31
  18. package/dist/bin/exe-kill.js +40 -18
  19. package/dist/bin/exe-launch-agent.js +109 -36
  20. package/dist/bin/exe-link.js +196 -87
  21. package/dist/bin/exe-new-employee.js +56 -17
  22. package/dist/bin/exe-pending-messages.js +47 -25
  23. package/dist/bin/exe-pending-notifications.js +38 -16
  24. package/dist/bin/exe-pending-reviews.js +51 -29
  25. package/dist/bin/exe-rename.js +21 -7
  26. package/dist/bin/exe-review.js +41 -13
  27. package/dist/bin/exe-search.js +57 -21
  28. package/dist/bin/exe-session-cleanup.js +67 -31
  29. package/dist/bin/exe-settings.js +63 -2
  30. package/dist/bin/exe-status.js +35 -13
  31. package/dist/bin/exe-team.js +35 -13
  32. package/dist/bin/git-sweep.js +45 -17
  33. package/dist/bin/graph-backfill.js +38 -16
  34. package/dist/bin/graph-export.js +38 -16
  35. package/dist/bin/install.js +10 -1
  36. package/dist/bin/scan-tasks.js +47 -19
  37. package/dist/bin/setup.js +444 -259
  38. package/dist/bin/shard-migrate.js +38 -16
  39. package/dist/bin/wiki-sync.js +40 -17
  40. package/dist/gateway/index.js +113 -48
  41. package/dist/hooks/bug-report-worker.js +66 -39
  42. package/dist/hooks/commit-complete.js +45 -17
  43. package/dist/hooks/error-recall.js +60 -20
  44. package/dist/hooks/exe-heartbeat-hook.js +3 -2
  45. package/dist/hooks/ingest-worker.js +174 -45
  46. package/dist/hooks/ingest.js +74 -28
  47. package/dist/hooks/instructions-loaded.js +46 -17
  48. package/dist/hooks/notification.js +44 -15
  49. package/dist/hooks/post-compact.js +44 -15
  50. package/dist/hooks/pre-compact.js +42 -14
  51. package/dist/hooks/pre-tool-use.js +59 -22
  52. package/dist/hooks/prompt-ingest-worker.js +75 -14
  53. package/dist/hooks/prompt-submit.js +75 -32
  54. package/dist/hooks/response-ingest-worker.js +76 -15
  55. package/dist/hooks/session-end.js +54 -22
  56. package/dist/hooks/session-start.js +57 -20
  57. package/dist/hooks/stop.js +44 -15
  58. package/dist/hooks/subagent-stop.js +44 -15
  59. package/dist/hooks/summary-worker.js +339 -106
  60. package/dist/index.js +94 -23
  61. package/dist/lib/cloud-sync.js +191 -80
  62. package/dist/lib/config.js +4 -1
  63. package/dist/lib/consolidation.js +5 -4
  64. package/dist/lib/database.js +1 -0
  65. package/dist/lib/device-registry.js +2 -1
  66. package/dist/lib/embedder.js +9 -1
  67. package/dist/lib/employee-templates.js +5 -0
  68. package/dist/lib/employees.js +11 -6
  69. package/dist/lib/exe-daemon-client.js +6 -1
  70. package/dist/lib/exe-daemon.js +95 -36
  71. package/dist/lib/hybrid-search.js +57 -21
  72. package/dist/lib/identity-templates.js +16 -7
  73. package/dist/lib/identity.js +1 -1
  74. package/dist/lib/keychain.js +2 -1
  75. package/dist/lib/license.js +56 -6
  76. package/dist/lib/messaging.js +1 -1
  77. package/dist/lib/reminders.js +2 -2
  78. package/dist/lib/schedules.js +38 -16
  79. package/dist/lib/skill-learning.js +1 -1
  80. package/dist/lib/store.js +44 -16
  81. package/dist/lib/tasks.js +1 -1
  82. package/dist/lib/tmux-routing.js +1 -1
  83. package/dist/mcp/server.js +280 -155
  84. package/dist/mcp/tools/complete-reminder.js +1 -1
  85. package/dist/mcp/tools/create-task.js +14 -6
  86. package/dist/mcp/tools/deactivate-behavior.js +2 -2
  87. package/dist/mcp/tools/list-reminders.js +1 -1
  88. package/dist/mcp/tools/list-tasks.js +36 -28
  89. package/dist/mcp/tools/send-message.js +1 -1
  90. package/dist/mcp/tools/update-task.js +1 -1
  91. package/dist/runtime/index.js +42 -8
  92. package/dist/tui/App.js +220 -99
  93. package/package.json +5 -3
@@ -10,7 +10,7 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/lib/config.ts
13
- import { readFile, writeFile, mkdir } from "fs/promises";
13
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
14
14
  import { readFileSync, existsSync, renameSync } from "fs";
15
15
  import path from "path";
16
16
  import os from "os";
@@ -193,15 +193,20 @@ function addEmployee(employees, employee) {
193
193
  }
194
194
  return [...employees, normalized];
195
195
  }
196
+ function findExeBin() {
197
+ try {
198
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
196
203
  function registerBinSymlinks(name) {
197
204
  const created = [];
198
205
  const skipped = [];
199
206
  const errors = [];
200
- let exeBinPath;
201
- try {
202
- exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
203
- } catch {
204
- errors.push("Could not find 'exe' in PATH");
207
+ const exeBinPath = findExeBin();
208
+ if (!exeBinPath) {
209
+ errors.push("Could not find 'exe-os' in PATH");
205
210
  return { created, skipped, errors };
206
211
  }
207
212
  const binDir = path2.dirname(exeBinPath);
@@ -249,6 +254,7 @@ __export(employee_templates_exports, {
249
254
  buildCustomEmployeePrompt: () => buildCustomEmployeePrompt,
250
255
  getSessionPrompt: () => getSessionPrompt,
251
256
  getTemplate: () => getTemplate,
257
+ getTemplateByRole: () => getTemplateByRole,
252
258
  personalizePrompt: () => personalizePrompt,
253
259
  renderClientCOOTemplate: () => renderClientCOOTemplate
254
260
  });
@@ -264,6 +270,10 @@ function buildCustomEmployeePrompt(name, role) {
264
270
  function getTemplate(name) {
265
271
  return TEMPLATES[name];
266
272
  }
273
+ function getTemplateByRole(role) {
274
+ const lower = role.toLowerCase();
275
+ return Object.values(TEMPLATES).find((t) => t.role.toLowerCase() === lower);
276
+ }
267
277
  function personalizePrompt(prompt, templateName, actualName) {
268
278
  if (templateName === actualName) return prompt;
269
279
  const escaped = templateName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -907,7 +917,8 @@ if (isMainModule(import.meta.url)) {
907
917
  const bootPath = path3.join(__dirname, "exe-boot.js");
908
918
  try {
909
919
  execSync2(`node "${bootPath}"`, { stdio: "inherit" });
910
- } catch {
920
+ } catch (err) {
921
+ console.error(`Failed to boot exe: ${err instanceof Error ? err.message : String(err)}`);
911
922
  process.exit(1);
912
923
  }
913
924
  process.exit(0);
@@ -944,9 +955,8 @@ if (isMainModule(import.meta.url)) {
944
955
  );
945
956
  execSync2("claude --dangerously-skip-permissions", { stdio: "inherit", env });
946
957
  } catch (err) {
947
- console.error(
948
- err instanceof Error ? err.message : String(err)
949
- );
958
+ console.error(`Failed to launch employee session: ${err instanceof Error ? err.message : String(err)}`);
959
+ console.error("Try running: exe-os team");
950
960
  process.exit(1);
951
961
  }
952
962
  }
@@ -16,15 +16,15 @@ var __export = (target, all) => {
16
16
  };
17
17
 
18
18
  // src/lib/config.ts
19
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
19
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
20
20
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
21
21
  import path2 from "path";
22
- import os from "os";
22
+ import os2 from "os";
23
23
  function resolveDataDir() {
24
24
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
25
25
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
26
- const newDir = path2.join(os.homedir(), ".exe-os");
27
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
26
+ const newDir = path2.join(os2.homedir(), ".exe-os");
27
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
28
28
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
29
29
  try {
30
30
  renameSync(legacyDir, newDir);
@@ -111,7 +111,7 @@ async function loadConfig() {
111
111
  normalizeAutoUpdate(migratedCfg);
112
112
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
113
113
  if (config.dbPath.startsWith("~")) {
114
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
114
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
115
115
  }
116
116
  return config;
117
117
  } catch {
@@ -123,6 +123,9 @@ async function saveConfig(config) {
123
123
  await mkdir2(dir, { recursive: true });
124
124
  const configPath = path2.join(dir, "config.json");
125
125
  await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
126
+ if (config.cloud?.apiKey) {
127
+ await chmod2(configPath, 384);
128
+ }
126
129
  }
127
130
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
128
131
  var init_config = __esm({
@@ -223,24 +226,34 @@ __export(license_exports, {
223
226
  loadLicense: () => loadLicense,
224
227
  mirrorLicenseKey: () => mirrorLicenseKey,
225
228
  saveLicense: () => saveLicense,
229
+ startLicenseRevalidation: () => startLicenseRevalidation,
230
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
226
231
  validateLicense: () => validateLicense
227
232
  });
228
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3, mkdirSync } from "fs";
233
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync } from "fs";
229
234
  import { randomUUID } from "crypto";
230
- import path3 from "path";
235
+ import path4 from "path";
231
236
  import { jwtVerify, importSPKI } from "jose";
237
+ async function fetchRetry(url, init) {
238
+ try {
239
+ return await fetch(url, init);
240
+ } catch {
241
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
242
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
243
+ }
244
+ }
232
245
  function loadDeviceId() {
233
- const deviceJsonPath = path3.join(EXE_AI_DIR, "device.json");
246
+ const deviceJsonPath = path4.join(EXE_AI_DIR, "device.json");
234
247
  try {
235
- if (existsSync3(deviceJsonPath)) {
236
- const data = JSON.parse(readFileSync2(deviceJsonPath, "utf8"));
248
+ if (existsSync4(deviceJsonPath)) {
249
+ const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
237
250
  if (data.deviceId) return data.deviceId;
238
251
  }
239
252
  } catch {
240
253
  }
241
254
  try {
242
- if (existsSync3(DEVICE_ID_PATH)) {
243
- const id2 = readFileSync2(DEVICE_ID_PATH, "utf8").trim();
255
+ if (existsSync4(DEVICE_ID_PATH)) {
256
+ const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
244
257
  if (id2) return id2;
245
258
  }
246
259
  } catch {
@@ -252,15 +265,15 @@ function loadDeviceId() {
252
265
  }
253
266
  function loadLicense() {
254
267
  try {
255
- if (!existsSync3(LICENSE_PATH)) return null;
256
- return readFileSync2(LICENSE_PATH, "utf8").trim();
268
+ if (!existsSync4(LICENSE_PATH)) return null;
269
+ return readFileSync3(LICENSE_PATH, "utf8").trim();
257
270
  } catch {
258
271
  return null;
259
272
  }
260
273
  }
261
274
  function saveLicense(apiKey) {
262
275
  mkdirSync(EXE_AI_DIR, { recursive: true });
263
- writeFileSync(LICENSE_PATH, apiKey.trim(), "utf8");
276
+ writeFileSync(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
264
277
  }
265
278
  async function verifyLicenseJwt(token) {
266
279
  try {
@@ -286,8 +299,8 @@ async function verifyLicenseJwt(token) {
286
299
  }
287
300
  async function getCachedLicense() {
288
301
  try {
289
- if (!existsSync3(CACHE_PATH)) return null;
290
- const raw = JSON.parse(readFileSync2(CACHE_PATH, "utf8"));
302
+ if (!existsSync4(CACHE_PATH)) return null;
303
+ const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
291
304
  if (!raw.token || typeof raw.token !== "string") return null;
292
305
  return await verifyLicenseJwt(raw.token);
293
306
  } catch {
@@ -296,8 +309,8 @@ async function getCachedLicense() {
296
309
  }
297
310
  function readCachedToken() {
298
311
  try {
299
- if (!existsSync3(CACHE_PATH)) return null;
300
- const raw = JSON.parse(readFileSync2(CACHE_PATH, "utf8"));
312
+ if (!existsSync4(CACHE_PATH)) return null;
313
+ const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
301
314
  return typeof raw.token === "string" ? raw.token : null;
302
315
  } catch {
303
316
  return null;
@@ -312,7 +325,7 @@ function cacheResponse(token) {
312
325
  async function validateLicense(apiKey, deviceId) {
313
326
  const did = deviceId ?? loadDeviceId();
314
327
  try {
315
- const res = await fetch(`${API_BASE}/auth/activate`, {
328
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
316
329
  method: "POST",
317
330
  headers: { "Content-Type": "application/json" },
318
331
  body: JSON.stringify({ apiKey, deviceId: did }),
@@ -347,14 +360,23 @@ async function validateLicense(apiKey, deviceId) {
347
360
  } catch {
348
361
  const cached = await getCachedLicense();
349
362
  if (cached) return cached;
350
- return FREE_LICENSE;
363
+ return { ...FREE_LICENSE, valid: false, error: "offline" };
364
+ }
365
+ }
366
+ function getCacheAgeMs() {
367
+ try {
368
+ const { statSync } = __require("fs");
369
+ const s = statSync(CACHE_PATH);
370
+ return Date.now() - s.mtimeMs;
371
+ } catch {
372
+ return Infinity;
351
373
  }
352
374
  }
353
375
  async function checkLicense() {
354
376
  const key = loadLicense();
355
377
  if (!key) return FREE_LICENSE;
356
378
  const cached = await getCachedLicense();
357
- if (cached) return cached;
379
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
358
380
  const deviceId = loadDeviceId();
359
381
  return validateLicense(key, deviceId);
360
382
  }
@@ -394,7 +416,7 @@ async function assertVpsLicense(opts) {
394
416
  let explicitRejection = false;
395
417
  let transientFailure = false;
396
418
  try {
397
- const res = await fetch(`${API_BASE}/auth/activate`, {
419
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
398
420
  method: "POST",
399
421
  headers: { "Content-Type": "application/json" },
400
422
  body: JSON.stringify({ apiKey, deviceId }),
@@ -475,15 +497,37 @@ async function assertVpsLicense(opts) {
475
497
  `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.`
476
498
  );
477
499
  }
478
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
500
+ function startLicenseRevalidation(intervalMs = 36e5) {
501
+ if (_revalTimer) return;
502
+ _revalTimer = setInterval(async () => {
503
+ try {
504
+ const license = await checkLicense();
505
+ if (!license.valid) {
506
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
507
+ }
508
+ } catch {
509
+ }
510
+ }, intervalMs);
511
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
512
+ _revalTimer.unref();
513
+ }
514
+ }
515
+ function stopLicenseRevalidation() {
516
+ if (_revalTimer) {
517
+ clearInterval(_revalTimer);
518
+ _revalTimer = null;
519
+ }
520
+ }
521
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
479
522
  var init_license = __esm({
480
523
  "src/lib/license.ts"() {
481
524
  "use strict";
482
525
  init_config();
483
- LICENSE_PATH = path3.join(EXE_AI_DIR, "license.key");
484
- CACHE_PATH = path3.join(EXE_AI_DIR, "license-cache.json");
485
- DEVICE_ID_PATH = path3.join(EXE_AI_DIR, "device-id");
526
+ LICENSE_PATH = path4.join(EXE_AI_DIR, "license.key");
527
+ CACHE_PATH = path4.join(EXE_AI_DIR, "license-cache.json");
528
+ DEVICE_ID_PATH = path4.join(EXE_AI_DIR, "device-id");
486
529
  API_BASE = "https://askexe.com/cloud";
530
+ RETRY_DELAY_MS = 500;
487
531
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
488
532
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
489
533
  4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
@@ -505,6 +549,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
505
549
  employeeLimit: 1,
506
550
  memoryLimit: 5e3
507
551
  };
552
+ CACHE_MAX_AGE_MS = 36e5;
553
+ _revalTimer = null;
508
554
  }
509
555
  });
510
556
 
@@ -515,11 +561,12 @@ import { createInterface } from "readline";
515
561
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
516
562
  import { existsSync } from "fs";
517
563
  import path from "path";
564
+ import os from "os";
518
565
  import crypto from "crypto";
519
566
  var SERVICE = "exe-mem";
520
567
  var ACCOUNT = "master-key";
521
568
  function getKeyDir() {
522
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
569
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
523
570
  }
524
571
  function getKeyPath() {
525
572
  return path.join(getKeyDir(), "master.key");
@@ -662,6 +709,58 @@ function isMainModule(importMetaUrl) {
662
709
  }
663
710
  }
664
711
 
712
+ // src/lib/cloud-sync.ts
713
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync } from "fs";
714
+ import path6 from "path";
715
+ import { homedir } from "os";
716
+
717
+ // src/lib/database.ts
718
+ import { createClient } from "@libsql/client";
719
+
720
+ // src/lib/crypto.ts
721
+ import crypto3 from "crypto";
722
+
723
+ // src/lib/compress.ts
724
+ import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
725
+
726
+ // src/lib/plan-limits.ts
727
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
728
+ import path5 from "path";
729
+
730
+ // src/lib/employees.ts
731
+ init_config();
732
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
733
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
734
+ import { execSync } from "child_process";
735
+ import path3 from "path";
736
+ var EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
737
+
738
+ // src/lib/plan-limits.ts
739
+ init_license();
740
+ init_config();
741
+ var CACHE_PATH2 = path5.join(EXE_AI_DIR, "license-cache.json");
742
+
743
+ // src/lib/cloud-sync.ts
744
+ init_license();
745
+ init_config();
746
+ var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
747
+ var ROSTER_LOCK_PATH = path6.join(EXE_AI_DIR, "roster-merge.lock");
748
+ function assertSecureEndpoint(endpoint) {
749
+ if (endpoint.startsWith("https://")) return;
750
+ if (endpoint.startsWith("http://")) {
751
+ try {
752
+ const parsed = new URL(endpoint);
753
+ if (LOCALHOST_PATTERNS.test(parsed.hostname)) return;
754
+ } catch {
755
+ return;
756
+ }
757
+ throw new Error(
758
+ `Insecure cloud endpoint rejected: "${endpoint}". Use https:// for remote hosts. Plain http:// is only allowed for localhost.`
759
+ );
760
+ }
761
+ }
762
+ var ROSTER_DELETIONS_PATH = path6.join(EXE_AI_DIR, "roster-deletions.json");
763
+
665
764
  // src/bin/exe-cloud.ts
666
765
  var BAR = "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550";
667
766
  function ask(rl, question) {
@@ -709,6 +808,7 @@ async function setupMode() {
709
808
  const orgId = deriveOrgId(masterKey);
710
809
  const authTokenHash = hashAuthToken(deriveWsAuthToken(masterKey));
711
810
  try {
811
+ assertSecureEndpoint(config.cloud.endpoint);
712
812
  const regResp = await fetch(`${config.cloud.endpoint}/api/register-org`, {
713
813
  method: "POST",
714
814
  headers: { Authorization: `Bearer ${config.cloud.apiKey}`, "Content-Type": "application/json" },
@@ -730,6 +830,7 @@ async function setupMode() {
730
830
  const endpoint = await ask(rl, " Endpoint [https://askexe.com/cloud]: ") || "https://askexe.com/cloud";
731
831
  console.log(" Validating...");
732
832
  try {
833
+ assertSecureEndpoint(endpoint);
733
834
  const resp = await fetch(`${endpoint}/auth/verify`, {
734
835
  method: "POST",
735
836
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" }
@@ -740,7 +841,8 @@ async function setupMode() {
740
841
  try {
741
842
  const { mirrorLicenseKey: mirrorLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
742
843
  mirrorLicenseKey2(apiKey);
743
- } catch {
844
+ } catch (err) {
845
+ console.log(` \u26A0 License mirror failed: ${err instanceof Error ? err.message : String(err)}`);
744
846
  }
745
847
  console.log(" \u2713 Cloud sync configured.\n");
746
848
  const masterKey = await getMasterKey();
@@ -749,6 +851,7 @@ async function setupMode() {
749
851
  const orgId = deriveOrgId(masterKey);
750
852
  const authTokenHash = hashAuthToken(deriveWsAuthToken(masterKey));
751
853
  try {
854
+ assertSecureEndpoint(endpoint);
752
855
  const regResp = await fetch(`${endpoint}/api/register-org`, {
753
856
  method: "POST",
754
857
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
@@ -796,9 +899,11 @@ async function syncMode() {
796
899
  console.log(" 24-word recovery phrase:");
797
900
  console.log(` ${mnemonic}
798
901
  `);
902
+ console.log(" \u26A0 Clear your terminal history after copying.\n");
799
903
  if (config.cloud?.apiKey) {
904
+ const maskedKey = config.cloud.apiKey.slice(0, 10) + "..." + config.cloud.apiKey.slice(-4);
800
905
  console.log(" Cloud API key:");
801
- console.log(` ${config.cloud.apiKey}
906
+ console.log(` ${maskedKey}
802
907
  `);
803
908
  console.log(" Endpoint:");
804
909
  console.log(` ${config.cloud.endpoint}
@@ -335,7 +335,7 @@ var init_database = __esm({
335
335
  });
336
336
 
337
337
  // src/lib/config.ts
338
- import { readFile, writeFile, mkdir } from "fs/promises";
338
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
339
339
  import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
340
340
  import path3 from "path";
341
341
  import os3 from "os";
@@ -1,12 +1,6 @@
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
- });
10
4
  var __esm = (fn, res) => function __init() {
11
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
6
  };
@@ -16,15 +10,15 @@ var __export = (target, all) => {
16
10
  };
17
11
 
18
12
  // src/lib/config.ts
19
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
13
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
20
14
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
21
15
  import path2 from "path";
22
- import os from "os";
16
+ import os2 from "os";
23
17
  function resolveDataDir() {
24
18
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
25
19
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
26
- const newDir = path2.join(os.homedir(), ".exe-os");
27
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
20
+ const newDir = path2.join(os2.homedir(), ".exe-os");
21
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
28
22
  if (!existsSync2(newDir) && existsSync2(legacyDir)) {
29
23
  try {
30
24
  renameSync(legacyDir, newDir);
@@ -111,7 +105,7 @@ async function loadConfig() {
111
105
  normalizeAutoUpdate(migratedCfg);
112
106
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
113
107
  if (config.dbPath.startsWith("~")) {
114
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
108
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
115
109
  }
116
110
  return config;
117
111
  } catch {
@@ -218,7 +212,7 @@ __export(shard_manager_exports, {
218
212
  shardExists: () => shardExists
219
213
  });
220
214
  import path3 from "path";
221
- import { existsSync as existsSync3, mkdirSync } from "fs";
215
+ import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
222
216
  import { createClient as createClient2 } from "@libsql/client";
223
217
  function initShardManager(encryptionKey) {
224
218
  _encryptionKey = encryptionKey;
@@ -257,7 +251,6 @@ function shardExists(projectName) {
257
251
  }
258
252
  function listShards() {
259
253
  if (!existsSync3(SHARDS_DIR)) return [];
260
- const { readdirSync } = __require("fs");
261
254
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
262
255
  }
263
256
  async function ensureShardSchema(client) {
@@ -533,6 +526,7 @@ async function ensureSchema() {
533
526
  const client = getRawClient();
534
527
  await client.execute("PRAGMA journal_mode = WAL");
535
528
  await client.execute("PRAGMA busy_timeout = 30000");
529
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
536
530
  try {
537
531
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
538
532
  } catch {
@@ -1326,11 +1320,12 @@ async function ensureSchema() {
1326
1320
  import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
1327
1321
  import { existsSync } from "fs";
1328
1322
  import path from "path";
1323
+ import os from "os";
1329
1324
  import crypto from "crypto";
1330
1325
  var SERVICE = "exe-mem";
1331
1326
  var ACCOUNT = "master-key";
1332
1327
  function getKeyDir() {
1333
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
1328
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
1334
1329
  }
1335
1330
  function getKeyPath() {
1336
1331
  return path.join(getKeyDir(), "master.key");
@@ -1367,6 +1362,30 @@ async function getMasterKey() {
1367
1362
 
1368
1363
  // src/lib/store.ts
1369
1364
  init_config();
1365
+ var INIT_MAX_RETRIES = 3;
1366
+ var INIT_RETRY_DELAY_MS = 1e3;
1367
+ function isBusyError2(err) {
1368
+ if (err instanceof Error) {
1369
+ const msg = err.message.toLowerCase();
1370
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
1371
+ }
1372
+ return false;
1373
+ }
1374
+ async function retryOnBusy2(fn, label) {
1375
+ for (let attempt = 0; attempt <= INIT_MAX_RETRIES; attempt++) {
1376
+ try {
1377
+ return await fn();
1378
+ } catch (err) {
1379
+ if (!isBusyError2(err) || attempt === INIT_MAX_RETRIES) throw err;
1380
+ process.stderr.write(
1381
+ `[store] SQLITE_BUSY during ${label}, retry ${attempt + 1}/${INIT_MAX_RETRIES}
1382
+ `
1383
+ );
1384
+ await new Promise((r) => setTimeout(r, INIT_RETRY_DELAY_MS * (attempt + 1)));
1385
+ }
1386
+ }
1387
+ throw new Error("unreachable");
1388
+ }
1370
1389
  var _pendingRecords = [];
1371
1390
  var _batchSize = 20;
1372
1391
  var _flushIntervalMs = 1e4;
@@ -1401,14 +1420,17 @@ async function initStore(options) {
1401
1420
  dbPath,
1402
1421
  encryptionKey: hexKey
1403
1422
  });
1404
- await ensureSchema();
1423
+ await retryOnBusy2(() => ensureSchema(), "ensureSchema");
1405
1424
  try {
1406
1425
  const { initShardManager: initShardManager2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
1407
1426
  initShardManager2(hexKey);
1408
1427
  } catch {
1409
1428
  }
1410
1429
  const client = getClient();
1411
- const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1430
+ const vResult = await retryOnBusy2(
1431
+ () => client.execute("SELECT MAX(version) as max_v FROM memories"),
1432
+ "version-query"
1433
+ );
1412
1434
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1413
1435
  }
1414
1436