@askexenow/exe-os 0.9.8 → 0.9.9

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 (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1295 -856
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +778 -427
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +276 -139
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +677 -388
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +440 -250
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +404 -212
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +412 -220
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +533 -320
  44. package/dist/hooks/bug-report-worker.js +344 -193
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +402 -210
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3423 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +408 -216
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +541 -328
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +443 -240
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +538 -324
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +935 -587
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +280 -234
  88. package/dist/lib/tmux-routing.js +172 -125
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1326 -609
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +306 -248
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1848 -199
  99. package/dist/runtime/index.js +441 -248
  100. package/dist/tui/App.js +761 -424
  101. package/package.json +1 -1
@@ -9,9 +9,34 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/lib/secure-files.ts
13
+ import { chmodSync, existsSync, mkdirSync } from "fs";
14
+ import { chmod, mkdir } from "fs/promises";
15
+ function ensurePrivateDirSync(dirPath) {
16
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
17
+ try {
18
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
19
+ } catch {
20
+ }
21
+ }
22
+ function enforcePrivateFileSync(filePath) {
23
+ try {
24
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
25
+ } catch {
26
+ }
27
+ }
28
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
29
+ var init_secure_files = __esm({
30
+ "src/lib/secure-files.ts"() {
31
+ "use strict";
32
+ PRIVATE_DIR_MODE = 448;
33
+ PRIVATE_FILE_MODE = 384;
34
+ }
35
+ });
36
+
12
37
  // src/lib/config.ts
13
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
14
- import { readFileSync, existsSync, renameSync } from "fs";
38
+ import { readFile, writeFile } from "fs/promises";
39
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
15
40
  import path from "path";
16
41
  import os from "os";
17
42
  function resolveDataDir() {
@@ -19,7 +44,7 @@ function resolveDataDir() {
19
44
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
20
45
  const newDir = path.join(os.homedir(), ".exe-os");
21
46
  const legacyDir = path.join(os.homedir(), ".exe-mem");
22
- if (!existsSync(newDir) && existsSync(legacyDir)) {
47
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
23
48
  try {
24
49
  renameSync(legacyDir, newDir);
25
50
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -34,6 +59,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
34
59
  var init_config = __esm({
35
60
  "src/lib/config.ts"() {
36
61
  "use strict";
62
+ init_secure_files();
37
63
  EXE_AI_DIR = resolveDataDir();
38
64
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
39
65
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -140,10 +166,10 @@ __export(agent_config_exports, {
140
166
  saveAgentConfig: () => saveAgentConfig,
141
167
  setAgentRuntime: () => setAgentRuntime
142
168
  });
143
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
169
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
144
170
  import path2 from "path";
145
171
  function loadAgentConfig() {
146
- if (!existsSync2(AGENT_CONFIG_PATH)) return {};
172
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
147
173
  try {
148
174
  return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
149
175
  } catch {
@@ -152,8 +178,9 @@ function loadAgentConfig() {
152
178
  }
153
179
  function saveAgentConfig(config) {
154
180
  const dir = path2.dirname(AGENT_CONFIG_PATH);
155
- if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
181
+ ensurePrivateDirSync(dir);
156
182
  writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
183
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
157
184
  }
158
185
  function getAgentRuntime(agentId) {
159
186
  const config = loadAgentConfig();
@@ -193,6 +220,7 @@ var init_agent_config = __esm({
193
220
  "use strict";
194
221
  init_config();
195
222
  init_runtime_table();
223
+ init_secure_files();
196
224
  AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
197
225
  KNOWN_RUNTIMES = {
198
226
  claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
@@ -240,7 +268,7 @@ __export(employees_exports, {
240
268
  validateEmployeeName: () => validateEmployeeName
241
269
  });
242
270
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
243
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
271
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
244
272
  import { execSync } from "child_process";
245
273
  import path3 from "path";
246
274
  import os2 from "os";
@@ -279,7 +307,7 @@ function validateEmployeeName(name) {
279
307
  return { valid: true };
280
308
  }
281
309
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
282
- if (!existsSync3(employeesPath)) {
310
+ if (!existsSync4(employeesPath)) {
283
311
  return [];
284
312
  }
285
313
  const raw = await readFile2(employeesPath, "utf-8");
@@ -294,7 +322,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
294
322
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
295
323
  }
296
324
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
297
- if (!existsSync3(employeesPath)) return [];
325
+ if (!existsSync4(employeesPath)) return [];
298
326
  try {
299
327
  return JSON.parse(readFileSync3(employeesPath, "utf-8"));
300
328
  } catch {
@@ -342,7 +370,7 @@ function appendToCoordinatorTeam(employee) {
342
370
  const coordinator = getCoordinatorEmployee(loadEmployeesSync());
343
371
  if (!coordinator) return;
344
372
  const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
345
- if (!existsSync3(idPath)) return;
373
+ if (!existsSync4(idPath)) return;
346
374
  const content = readFileSync3(idPath, "utf-8");
347
375
  if (content.includes(`**${capitalize(employee.name)}`)) return;
348
376
  const teamMatch = content.match(TEAM_SECTION_RE);
@@ -396,9 +424,9 @@ async function normalizeRosterCase(rosterPath) {
396
424
  const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
397
425
  const oldPath = path3.join(identityDir, `${oldName}.md`);
398
426
  const newPath = path3.join(identityDir, `${emp.name}.md`);
399
- if (existsSync3(oldPath) && !existsSync3(newPath)) {
427
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
400
428
  renameSync2(oldPath, newPath);
401
- } else if (existsSync3(oldPath) && oldPath !== newPath) {
429
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
402
430
  const content = readFileSync3(oldPath, "utf-8");
403
431
  writeFileSync2(newPath, content, "utf-8");
404
432
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
@@ -441,7 +469,7 @@ function registerBinSymlinks(name) {
441
469
  for (const suffix of ["", "-opencode"]) {
442
470
  const linkName = `${name}${suffix}`;
443
471
  const linkPath = path3.join(binDir, linkName);
444
- if (existsSync3(linkPath)) {
472
+ if (existsSync4(linkPath)) {
445
473
  skipped.push(linkName);
446
474
  continue;
447
475
  }
@@ -117,9 +117,34 @@ var init_keychain = __esm({
117
117
  }
118
118
  });
119
119
 
120
+ // src/lib/secure-files.ts
121
+ import { chmodSync, existsSync as existsSync2, mkdirSync } from "fs";
122
+ import { chmod as chmod2, mkdir as mkdir2 } from "fs/promises";
123
+ async function ensurePrivateDir(dirPath) {
124
+ await mkdir2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
125
+ try {
126
+ await chmod2(dirPath, PRIVATE_DIR_MODE);
127
+ } catch {
128
+ }
129
+ }
130
+ async function enforcePrivateFile(filePath) {
131
+ try {
132
+ await chmod2(filePath, PRIVATE_FILE_MODE);
133
+ } catch {
134
+ }
135
+ }
136
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
137
+ var init_secure_files = __esm({
138
+ "src/lib/secure-files.ts"() {
139
+ "use strict";
140
+ PRIVATE_DIR_MODE = 448;
141
+ PRIVATE_FILE_MODE = 384;
142
+ }
143
+ });
144
+
120
145
  // src/lib/config.ts
121
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
122
- import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
146
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
147
+ import { readFileSync, existsSync as existsSync3, renameSync } from "fs";
123
148
  import path2 from "path";
124
149
  import os2 from "os";
125
150
  function resolveDataDir() {
@@ -127,7 +152,7 @@ function resolveDataDir() {
127
152
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
128
153
  const newDir = path2.join(os2.homedir(), ".exe-os");
129
154
  const legacyDir = path2.join(os2.homedir(), ".exe-mem");
130
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
155
+ if (!existsSync3(newDir) && existsSync3(legacyDir)) {
131
156
  try {
132
157
  renameSync(legacyDir, newDir);
133
158
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -190,9 +215,9 @@ function normalizeAutoUpdate(raw) {
190
215
  }
191
216
  async function loadConfig() {
192
217
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
193
- await mkdir2(dir, { recursive: true });
218
+ await ensurePrivateDir(dir);
194
219
  const configPath = path2.join(dir, "config.json");
195
- if (!existsSync2(configPath)) {
220
+ if (!existsSync3(configPath)) {
196
221
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
197
222
  }
198
223
  const raw = await readFile2(configPath, "utf-8");
@@ -205,6 +230,7 @@ async function loadConfig() {
205
230
  `);
206
231
  try {
207
232
  await writeFile2(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
233
+ await enforcePrivateFile(configPath);
208
234
  } catch {
209
235
  }
210
236
  }
@@ -222,17 +248,16 @@ async function loadConfig() {
222
248
  }
223
249
  async function saveConfig(config) {
224
250
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
225
- await mkdir2(dir, { recursive: true });
251
+ await ensurePrivateDir(dir);
226
252
  const configPath = path2.join(dir, "config.json");
227
253
  await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
228
- if (config.cloud?.apiKey) {
229
- await chmod2(configPath, 384);
230
- }
254
+ await enforcePrivateFile(configPath);
231
255
  }
232
256
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
233
257
  var init_config = __esm({
234
258
  "src/lib/config.ts"() {
235
259
  "use strict";
260
+ init_secure_files();
236
261
  EXE_AI_DIR = resolveDataDir();
237
262
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
238
263
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -318,7 +343,7 @@ var init_db_retry = __esm({
318
343
 
319
344
  // src/lib/employees.ts
320
345
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
321
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
346
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
322
347
  import { execSync } from "child_process";
323
348
  import path3 from "path";
324
349
  import os3 from "os";
@@ -381,8 +406,11 @@ __export(license_exports, {
381
406
  stopLicenseRevalidation: () => stopLicenseRevalidation,
382
407
  validateLicense: () => validateLicense
383
408
  });
384
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync } from "fs";
409
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
385
410
  import { randomUUID } from "crypto";
411
+ import { createRequire as createRequire2 } from "module";
412
+ import { pathToFileURL as pathToFileURL2 } from "url";
413
+ import os5 from "os";
386
414
  import path5 from "path";
387
415
  import { jwtVerify, importSPKI } from "jose";
388
416
  async function fetchRetry(url, init) {
@@ -396,34 +424,34 @@ async function fetchRetry(url, init) {
396
424
  function loadDeviceId() {
397
425
  const deviceJsonPath = path5.join(EXE_AI_DIR, "device.json");
398
426
  try {
399
- if (existsSync4(deviceJsonPath)) {
427
+ if (existsSync5(deviceJsonPath)) {
400
428
  const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
401
429
  if (data.deviceId) return data.deviceId;
402
430
  }
403
431
  } catch {
404
432
  }
405
433
  try {
406
- if (existsSync4(DEVICE_ID_PATH)) {
434
+ if (existsSync5(DEVICE_ID_PATH)) {
407
435
  const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
408
436
  if (id2) return id2;
409
437
  }
410
438
  } catch {
411
439
  }
412
440
  const id = randomUUID();
413
- mkdirSync(EXE_AI_DIR, { recursive: true });
441
+ mkdirSync2(EXE_AI_DIR, { recursive: true });
414
442
  writeFileSync2(DEVICE_ID_PATH, id, "utf8");
415
443
  return id;
416
444
  }
417
445
  function loadLicense() {
418
446
  try {
419
- if (!existsSync4(LICENSE_PATH)) return null;
447
+ if (!existsSync5(LICENSE_PATH)) return null;
420
448
  return readFileSync3(LICENSE_PATH, "utf8").trim();
421
449
  } catch {
422
450
  return null;
423
451
  }
424
452
  }
425
453
  function saveLicense(apiKey) {
426
- mkdirSync(EXE_AI_DIR, { recursive: true });
454
+ mkdirSync2(EXE_AI_DIR, { recursive: true });
427
455
  writeFileSync2(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
428
456
  }
429
457
  async function verifyLicenseJwt(token) {
@@ -450,7 +478,7 @@ async function verifyLicenseJwt(token) {
450
478
  }
451
479
  async function getCachedLicense() {
452
480
  try {
453
- if (!existsSync4(CACHE_PATH)) return null;
481
+ if (!existsSync5(CACHE_PATH)) return null;
454
482
  const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
455
483
  if (!raw.token || typeof raw.token !== "string") return null;
456
484
  return await verifyLicenseJwt(raw.token);
@@ -460,7 +488,7 @@ async function getCachedLicense() {
460
488
  }
461
489
  function readCachedToken() {
462
490
  try {
463
- if (!existsSync4(CACHE_PATH)) return null;
491
+ if (!existsSync5(CACHE_PATH)) return null;
464
492
  const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
465
493
  return typeof raw.token === "string" ? raw.token : null;
466
494
  } catch {
@@ -499,52 +527,126 @@ function cacheResponse(token) {
499
527
  } catch {
500
528
  }
501
529
  }
502
- async function validateLicense(apiKey, deviceId) {
503
- const did = deviceId ?? loadDeviceId();
530
+ function loadPrismaForLicense() {
531
+ if (_prismaFailed) return null;
532
+ const dbUrl = process.env.DATABASE_URL;
533
+ if (!dbUrl) {
534
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path5.join(os5.homedir(), "exe-db");
535
+ if (!existsSync5(path5.join(exeDbRoot, "package.json"))) {
536
+ _prismaFailed = true;
537
+ return null;
538
+ }
539
+ }
540
+ if (!_prismaPromise) {
541
+ _prismaPromise = (async () => {
542
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
543
+ if (explicitPath) {
544
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
545
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
546
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
547
+ return new Ctor2();
548
+ }
549
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path5.join(os5.homedir(), "exe-db");
550
+ const req = createRequire2(path5.join(exeDbRoot, "package.json"));
551
+ const entry = req.resolve("@prisma/client");
552
+ const mod = await import(pathToFileURL2(entry).href);
553
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
554
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
555
+ return new Ctor();
556
+ })().catch((err) => {
557
+ _prismaFailed = true;
558
+ _prismaPromise = null;
559
+ throw err;
560
+ });
561
+ }
562
+ return _prismaPromise;
563
+ }
564
+ async function validateViaPostgres(apiKey) {
565
+ const loader = loadPrismaForLicense();
566
+ if (!loader) return null;
567
+ try {
568
+ const prisma = await loader;
569
+ const rows = await prisma.$queryRawUnsafe(
570
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
571
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
572
+ apiKey
573
+ );
574
+ if (!rows || rows.length === 0) return null;
575
+ const row = rows[0];
576
+ if (row.status !== "active") return null;
577
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
578
+ const plan = row.plan;
579
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
580
+ return {
581
+ valid: true,
582
+ plan,
583
+ email: row.email,
584
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
585
+ deviceLimit: row.device_limit ?? limits.devices,
586
+ employeeLimit: row.employee_limit ?? limits.employees,
587
+ memoryLimit: row.memory_limit ?? limits.memories
588
+ };
589
+ } catch {
590
+ return null;
591
+ }
592
+ }
593
+ async function validateViaCFWorker(apiKey, deviceId) {
504
594
  try {
505
595
  const res = await fetchRetry(`${API_BASE}/auth/activate`, {
506
596
  method: "POST",
507
597
  headers: { "Content-Type": "application/json" },
508
- body: JSON.stringify({ apiKey, deviceId: did }),
598
+ body: JSON.stringify({ apiKey, deviceId }),
509
599
  signal: AbortSignal.timeout(1e4)
510
600
  });
511
- if (res.ok) {
512
- const data = await res.json();
513
- if (data.error === "device_limit_exceeded") {
514
- const cached2 = await getCachedLicense();
515
- if (cached2) return cached2;
516
- const raw2 = getRawCachedPlan();
517
- if (raw2) return { ...raw2, valid: false };
518
- return { ...FREE_LICENSE, valid: false, plan: "free" };
519
- }
520
- if (data.token) {
521
- cacheResponse(data.token);
522
- const verified = await verifyLicenseJwt(data.token);
523
- if (verified) return verified;
601
+ if (!res.ok) return null;
602
+ const data = await res.json();
603
+ if (data.error === "device_limit_exceeded") return null;
604
+ if (!data.valid) return null;
605
+ if (data.token) {
606
+ cacheResponse(data.token);
607
+ const verified = await verifyLicenseJwt(data.token);
608
+ if (verified) return verified;
609
+ }
610
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
611
+ return {
612
+ valid: data.valid,
613
+ plan: data.plan,
614
+ email: data.email,
615
+ expiresAt: data.expiresAt,
616
+ deviceLimit: limits.devices,
617
+ employeeLimit: limits.employees,
618
+ memoryLimit: limits.memories
619
+ };
620
+ } catch {
621
+ return null;
622
+ }
623
+ }
624
+ async function validateLicense(apiKey, deviceId) {
625
+ const did = deviceId ?? loadDeviceId();
626
+ const pgResult = await validateViaPostgres(apiKey);
627
+ if (pgResult) {
628
+ try {
629
+ writeFileSync2(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
630
+ } catch {
631
+ }
632
+ return pgResult;
633
+ }
634
+ const cfResult = await validateViaCFWorker(apiKey, did);
635
+ if (cfResult) return cfResult;
636
+ const cached = await getCachedLicense();
637
+ if (cached) return cached;
638
+ try {
639
+ if (existsSync5(CACHE_PATH)) {
640
+ const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
641
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
642
+ return raw.pgLicense;
524
643
  }
525
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
526
- return {
527
- valid: data.valid,
528
- plan: data.plan,
529
- email: data.email,
530
- expiresAt: data.expiresAt,
531
- deviceLimit: limits.devices,
532
- employeeLimit: limits.employees,
533
- memoryLimit: limits.memories
534
- };
535
644
  }
536
- const cached = await getCachedLicense();
537
- if (cached) return cached;
538
- const raw = getRawCachedPlan();
539
- if (raw) return raw;
540
- return { ...FREE_LICENSE, valid: false, plan: "free" };
541
645
  } catch {
542
- const cached = await getCachedLicense();
543
- if (cached) return cached;
544
- const rawFallback = getRawCachedPlan();
545
- if (rawFallback) return rawFallback;
546
- return { ...FREE_LICENSE, valid: false, error: "offline" };
547
646
  }
647
+ const rawFallback = getRawCachedPlan();
648
+ if (rawFallback) return rawFallback;
649
+ return { ...FREE_LICENSE, valid: false };
548
650
  }
549
651
  function getCacheAgeMs() {
550
652
  try {
@@ -560,7 +662,7 @@ async function checkLicense() {
560
662
  if (!key) {
561
663
  try {
562
664
  const configPath = path5.join(EXE_AI_DIR, "config.json");
563
- if (existsSync4(configPath)) {
665
+ if (existsSync5(configPath)) {
564
666
  const raw = JSON.parse(readFileSync3(configPath, "utf8"));
565
667
  const cloud = raw.cloud;
566
668
  if (cloud?.apiKey) {
@@ -715,7 +817,7 @@ function stopLicenseRevalidation() {
715
817
  _revalTimer = null;
716
818
  }
717
819
  }
718
- 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;
820
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
719
821
  var init_license = __esm({
720
822
  "src/lib/license.ts"() {
721
823
  "use strict";
@@ -746,6 +848,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
746
848
  employeeLimit: 1,
747
849
  memoryLimit: 5e3
748
850
  };
851
+ _prismaPromise = null;
852
+ _prismaFailed = false;
749
853
  CACHE_MAX_AGE_MS = 36e5;
750
854
  _revalTimer = null;
751
855
  }
@@ -753,7 +857,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
753
857
 
754
858
  // src/lib/crdt-sync.ts
755
859
  import * as Y from "yjs";
756
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
860
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "fs";
757
861
  import path6 from "path";
758
862
  import { homedir } from "os";
759
863
  var DEFAULT_STATE_PATH;
@@ -801,7 +905,7 @@ function isMainModule(importMetaUrl) {
801
905
 
802
906
  // src/lib/cloud-sync.ts
803
907
  init_database();
804
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync3, appendFileSync, unlinkSync as unlinkSync3, openSync, closeSync } from "fs";
908
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7, readdirSync, mkdirSync as mkdirSync4, appendFileSync, unlinkSync as unlinkSync3, openSync, closeSync } from "fs";
805
909
  import crypto3 from "crypto";
806
910
  import path7 from "path";
807
911
  import { homedir as homedir2 } from "os";
@@ -817,6 +921,7 @@ init_license();
817
921
  init_config();
818
922
  init_crdt_sync();
819
923
  init_employees();
924
+ init_secure_files();
820
925
  var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
821
926
  var ROSTER_LOCK_PATH = path7.join(EXE_AI_DIR, "roster-merge.lock");
822
927
  function assertSecureEndpoint(endpoint) {