@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
@@ -15,9 +15,34 @@ var __export = (target, all) => {
15
15
  __defProp(target, name, { get: all[name], enumerable: true });
16
16
  };
17
17
 
18
+ // src/lib/secure-files.ts
19
+ import { chmodSync as chmodSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
20
+ import { chmod, mkdir } from "fs/promises";
21
+ function ensurePrivateDirSync(dirPath) {
22
+ mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
23
+ try {
24
+ chmodSync2(dirPath, PRIVATE_DIR_MODE);
25
+ } catch {
26
+ }
27
+ }
28
+ function enforcePrivateFileSync(filePath) {
29
+ try {
30
+ if (existsSync2(filePath)) chmodSync2(filePath, PRIVATE_FILE_MODE);
31
+ } catch {
32
+ }
33
+ }
34
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
35
+ var init_secure_files = __esm({
36
+ "src/lib/secure-files.ts"() {
37
+ "use strict";
38
+ PRIVATE_DIR_MODE = 448;
39
+ PRIVATE_FILE_MODE = 384;
40
+ }
41
+ });
42
+
18
43
  // src/lib/config.ts
19
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
20
- import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
44
+ import { readFile, writeFile } from "fs/promises";
45
+ import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
21
46
  import path2 from "path";
22
47
  import os from "os";
23
48
  function resolveDataDir() {
@@ -25,7 +50,7 @@ function resolveDataDir() {
25
50
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
26
51
  const newDir = path2.join(os.homedir(), ".exe-os");
27
52
  const legacyDir = path2.join(os.homedir(), ".exe-mem");
28
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
53
+ if (!existsSync3(newDir) && existsSync3(legacyDir)) {
29
54
  try {
30
55
  renameSync(legacyDir, newDir);
31
56
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -40,6 +65,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
40
65
  var init_config = __esm({
41
66
  "src/lib/config.ts"() {
42
67
  "use strict";
68
+ init_secure_files();
43
69
  EXE_AI_DIR = resolveDataDir();
44
70
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
45
71
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -146,10 +172,10 @@ __export(agent_config_exports, {
146
172
  saveAgentConfig: () => saveAgentConfig,
147
173
  setAgentRuntime: () => setAgentRuntime
148
174
  });
149
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
175
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
150
176
  import path3 from "path";
151
177
  function loadAgentConfig() {
152
- if (!existsSync3(AGENT_CONFIG_PATH)) return {};
178
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
153
179
  try {
154
180
  return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
155
181
  } catch {
@@ -158,8 +184,9 @@ function loadAgentConfig() {
158
184
  }
159
185
  function saveAgentConfig(config) {
160
186
  const dir = path3.dirname(AGENT_CONFIG_PATH);
161
- if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
187
+ ensurePrivateDirSync(dir);
162
188
  writeFileSync2(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
189
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
163
190
  }
164
191
  function getAgentRuntime(agentId) {
165
192
  const config = loadAgentConfig();
@@ -199,6 +226,7 @@ var init_agent_config = __esm({
199
226
  "use strict";
200
227
  init_config();
201
228
  init_runtime_table();
229
+ init_secure_files();
202
230
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
203
231
  KNOWN_RUNTIMES = {
204
232
  claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
@@ -220,7 +248,7 @@ var init_agent_config = __esm({
220
248
 
221
249
  // src/lib/employees.ts
222
250
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
223
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
251
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
224
252
  import { execSync } from "child_process";
225
253
  import path4 from "path";
226
254
  import os2 from "os";
@@ -249,7 +277,7 @@ function validateEmployeeName(name) {
249
277
  return { valid: true };
250
278
  }
251
279
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
252
- if (!existsSync4(employeesPath)) {
280
+ if (!existsSync5(employeesPath)) {
253
281
  return [];
254
282
  }
255
283
  const raw = await readFile2(employeesPath, "utf-8");
@@ -264,7 +292,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
264
292
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
265
293
  }
266
294
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
267
- if (!existsSync4(employeesPath)) return [];
295
+ if (!existsSync5(employeesPath)) return [];
268
296
  try {
269
297
  return JSON.parse(readFileSync4(employeesPath, "utf-8"));
270
298
  } catch {
@@ -282,7 +310,7 @@ function appendToCoordinatorTeam(employee) {
282
310
  const coordinator = getCoordinatorEmployee(loadEmployeesSync());
283
311
  if (!coordinator) return;
284
312
  const idPath = path4.join(IDENTITY_DIR, `${coordinator.name}.md`);
285
- if (!existsSync4(idPath)) return;
313
+ if (!existsSync5(idPath)) return;
286
314
  const content = readFileSync4(idPath, "utf-8");
287
315
  if (content.includes(`**${capitalize(employee.name)}`)) return;
288
316
  const teamMatch = content.match(TEAM_SECTION_RE);
@@ -351,7 +379,7 @@ function registerBinSymlinks(name) {
351
379
  for (const suffix of ["", "-opencode"]) {
352
380
  const linkName = `${name}${suffix}`;
353
381
  const linkPath = path4.join(binDir, linkName);
354
- if (existsSync4(linkPath)) {
382
+ if (existsSync5(linkPath)) {
355
383
  skipped.push(linkName);
356
384
  continue;
357
385
  }
@@ -984,12 +1012,12 @@ __export(identity_exports, {
984
1012
  listIdentities: () => listIdentities,
985
1013
  updateIdentity: () => updateIdentity
986
1014
  });
987
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
1015
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
988
1016
  import { readdirSync as readdirSync2 } from "fs";
989
1017
  import path8 from "path";
990
1018
  import { createHash } from "crypto";
991
1019
  function ensureDir() {
992
- if (!existsSync7(IDENTITY_DIR2)) {
1020
+ if (!existsSync8(IDENTITY_DIR2)) {
993
1021
  mkdirSync4(IDENTITY_DIR2, { recursive: true });
994
1022
  }
995
1023
  }
@@ -1035,7 +1063,7 @@ function contentHash(content) {
1035
1063
  }
1036
1064
  function getIdentity(agentId) {
1037
1065
  const filePath = identityPath(agentId);
1038
- if (!existsSync7(filePath)) return null;
1066
+ if (!existsSync8(filePath)) return null;
1039
1067
  const raw = readFileSync7(filePath, "utf-8");
1040
1068
  const { frontmatter, body } = parseFrontmatter(raw);
1041
1069
  return {
@@ -1111,10 +1139,10 @@ var init_identity = __esm({
1111
1139
  });
1112
1140
 
1113
1141
  // src/lib/agent-symlinks.ts
1114
- import os4 from "os";
1142
+ import os5 from "os";
1115
1143
  import path9 from "path";
1116
1144
  import {
1117
- existsSync as existsSync8,
1145
+ existsSync as existsSync9,
1118
1146
  lstatSync,
1119
1147
  mkdirSync as mkdirSync5,
1120
1148
  readlinkSync as readlinkSync2,
@@ -1129,11 +1157,11 @@ function identitySourcePath(homeDir, agentId) {
1129
1157
  function claudeAgentLinkPath(homeDir, agentId) {
1130
1158
  return path9.join(claudeAgentsDir(homeDir), `${agentId}.md`);
1131
1159
  }
1132
- function ensureAgentSymlink(agentId, homeDir = os4.homedir()) {
1160
+ function ensureAgentSymlink(agentId, homeDir = os5.homedir()) {
1133
1161
  const target = identitySourcePath(homeDir, agentId);
1134
1162
  const link = claudeAgentLinkPath(homeDir, agentId);
1135
1163
  mkdirSync5(claudeAgentsDir(homeDir), { recursive: true });
1136
- if (existsSync8(link)) {
1164
+ if (existsSync9(link)) {
1137
1165
  let stat;
1138
1166
  try {
1139
1167
  stat = lstatSync(link);
@@ -1167,7 +1195,7 @@ function ensureAgentSymlink(agentId, homeDir = os4.homedir()) {
1167
1195
  }
1168
1196
  return { agentId, action: "created", target, link };
1169
1197
  }
1170
- async function ensureAllAgentSymlinks(homeDir = os4.homedir()) {
1198
+ async function ensureAllAgentSymlinks(homeDir = os5.homedir()) {
1171
1199
  const employees = await loadEmployees();
1172
1200
  return employees.map((emp) => ensureAgentSymlink(emp.name, homeDir));
1173
1201
  }
@@ -1202,12 +1230,12 @@ var init_mcp_prefix = __esm({
1202
1230
  });
1203
1231
 
1204
1232
  // src/lib/preferences.ts
1205
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
1233
+ import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
1206
1234
  import path10 from "path";
1207
- import os5 from "os";
1208
- function loadPreferences(homeDir = os5.homedir()) {
1235
+ import os6 from "os";
1236
+ function loadPreferences(homeDir = os6.homedir()) {
1209
1237
  const configPath = path10.join(homeDir, ".exe-os", "config.json");
1210
- if (!existsSync9(configPath)) return {};
1238
+ if (!existsSync10(configPath)) return {};
1211
1239
  try {
1212
1240
  const config = JSON.parse(readFileSync8(configPath, "utf-8"));
1213
1241
  return config.preferences ?? {};
@@ -1218,6 +1246,7 @@ function loadPreferences(homeDir = os5.homedir()) {
1218
1246
  var init_preferences = __esm({
1219
1247
  "src/lib/preferences.ts"() {
1220
1248
  "use strict";
1249
+ init_secure_files();
1221
1250
  }
1222
1251
  });
1223
1252
 
@@ -1235,9 +1264,9 @@ __export(installer_exports, {
1235
1264
  setupTmux: () => setupTmux
1236
1265
  });
1237
1266
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir } from "fs/promises";
1238
- import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync7, copyFileSync, mkdirSync as mkdirSync7 } from "fs";
1267
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync7, copyFileSync, mkdirSync as mkdirSync6 } from "fs";
1239
1268
  import path11 from "path";
1240
- import os6 from "os";
1269
+ import os7 from "os";
1241
1270
  import { execSync as execSync2 } from "child_process";
1242
1271
  import { fileURLToPath as fileURLToPath2 } from "url";
1243
1272
  function resolvePackageRoot() {
@@ -1246,7 +1275,7 @@ function resolvePackageRoot() {
1246
1275
  const root = path11.parse(dir).root;
1247
1276
  while (dir !== root) {
1248
1277
  const pkgPath = path11.join(dir, "package.json");
1249
- if (existsSync10(pkgPath)) {
1278
+ if (existsSync11(pkgPath)) {
1250
1279
  try {
1251
1280
  const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
1252
1281
  if (pkg.name === "@askexenow/exe-os" || pkg.name === "exe-os") return dir;
@@ -1257,12 +1286,12 @@ function resolvePackageRoot() {
1257
1286
  }
1258
1287
  return path11.resolve(path11.dirname(thisFile), "..", "..", "..");
1259
1288
  }
1260
- async function copySlashCommands(packageRoot, homeDir = os6.homedir()) {
1289
+ async function copySlashCommands(packageRoot, homeDir = os7.homedir()) {
1261
1290
  let copied = 0;
1262
1291
  let skipped = 0;
1263
1292
  const skillsBase = path11.join(homeDir, ".claude", "skills");
1264
1293
  const exeDir = path11.join(packageRoot, "src", "commands", "exe");
1265
- if (existsSync10(exeDir)) {
1294
+ if (existsSync11(exeDir)) {
1266
1295
  const entries = await readdir(exeDir);
1267
1296
  const mdFiles = entries.filter((f) => f.endsWith(".md"));
1268
1297
  for (const file of mdFiles) {
@@ -1277,7 +1306,7 @@ async function copySlashCommands(packageRoot, homeDir = os6.homedir()) {
1277
1306
  }
1278
1307
  }
1279
1308
  const topLevelSrc = path11.join(packageRoot, "src", "commands", "exe.md");
1280
- if (existsSync10(topLevelSrc)) {
1309
+ if (existsSync11(topLevelSrc)) {
1281
1310
  const destDir = path11.join(skillsBase, "exe");
1282
1311
  await mkdir3(destDir, { recursive: true });
1283
1312
  const destPath = path11.join(destDir, "SKILL.md");
@@ -1303,17 +1332,17 @@ name: ${skillName}
1303
1332
  `);
1304
1333
  }
1305
1334
  }
1306
- if (existsSync10(destPath)) {
1335
+ if (existsSync11(destPath)) {
1307
1336
  const existing = await readFile3(destPath, "utf-8");
1308
1337
  if (existing === content) return false;
1309
1338
  }
1310
1339
  await writeFile3(destPath, content);
1311
1340
  return true;
1312
1341
  }
1313
- async function registerMcpServer(packageRoot, homeDir = os6.homedir()) {
1342
+ async function registerMcpServer(packageRoot, homeDir = os7.homedir()) {
1314
1343
  const claudeJsonPath = path11.join(homeDir, ".claude.json");
1315
1344
  let claudeJson = {};
1316
- if (existsSync10(claudeJsonPath)) {
1345
+ if (existsSync11(claudeJsonPath)) {
1317
1346
  try {
1318
1347
  claudeJson = JSON.parse(await readFile3(claudeJsonPath, "utf-8"));
1319
1348
  } catch {
@@ -1330,21 +1359,21 @@ async function registerMcpServer(packageRoot, homeDir = os6.homedir()) {
1330
1359
  env: {}
1331
1360
  };
1332
1361
  const currentMem = claudeJson.mcpServers[MCP_LEGACY_KEY];
1333
- const currentOs = claudeJson.mcpServers[MCP_PRIMARY_KEY];
1334
1362
  const memMatches = currentMem && JSON.stringify(currentMem) === JSON.stringify(newEntry);
1335
- const osMatches = currentOs && JSON.stringify(currentOs) === JSON.stringify(newEntry);
1336
- if (memMatches && osMatches) {
1363
+ if (claudeJson.mcpServers[MCP_PRIMARY_KEY]) {
1364
+ delete claudeJson.mcpServers[MCP_PRIMARY_KEY];
1365
+ }
1366
+ if (memMatches && !claudeJson.mcpServers[MCP_PRIMARY_KEY]) {
1337
1367
  await cleanSettingsJsonMcp(path11.join(homeDir, ".claude", "settings.json"));
1338
1368
  return false;
1339
1369
  }
1340
1370
  claudeJson.mcpServers[MCP_LEGACY_KEY] = newEntry;
1341
- claudeJson.mcpServers[MCP_PRIMARY_KEY] = newEntry;
1342
1371
  await writeFile3(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
1343
1372
  await cleanSettingsJsonMcp(path11.join(homeDir, ".claude", "settings.json"));
1344
1373
  return true;
1345
1374
  }
1346
1375
  async function cleanSettingsJsonMcp(settingsPath) {
1347
- if (!existsSync10(settingsPath)) return;
1376
+ if (!existsSync11(settingsPath)) return;
1348
1377
  try {
1349
1378
  const settings = JSON.parse(await readFile3(settingsPath, "utf-8"));
1350
1379
  const servers = settings.mcpServers;
@@ -1364,14 +1393,14 @@ async function cleanSettingsJsonMcp(settingsPath) {
1364
1393
  } catch {
1365
1394
  }
1366
1395
  }
1367
- async function mergeHooks(packageRoot, homeDir = os6.homedir()) {
1396
+ async function mergeHooks(packageRoot, homeDir = os7.homedir()) {
1368
1397
  const settingsPath = path11.join(homeDir, ".claude", "settings.json");
1369
1398
  const logsDir = path11.join(homeDir, ".exe-os", "logs");
1370
1399
  const hookLogPath = path11.join(logsDir, "hooks.log");
1371
1400
  const logSuffix = ` 2>> "${hookLogPath}"`;
1372
1401
  await mkdir3(logsDir, { recursive: true });
1373
1402
  let settings = {};
1374
- if (existsSync10(settingsPath)) {
1403
+ if (existsSync11(settingsPath)) {
1375
1404
  try {
1376
1405
  settings = JSON.parse(await readFile3(settingsPath, "utf-8"));
1377
1406
  } catch {
@@ -1637,9 +1666,9 @@ async function mergeHooks(packageRoot, homeDir = os6.homedir()) {
1637
1666
  await writeFile3(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1638
1667
  return { added, skipped };
1639
1668
  }
1640
- async function cleanOldShellFunctions(homeDir = os6.homedir()) {
1669
+ async function cleanOldShellFunctions(homeDir = os7.homedir()) {
1641
1670
  const rosterPath = path11.join(homeDir, ".exe-os", "exe-employees.json");
1642
- if (!existsSync10(rosterPath)) return 0;
1671
+ if (!existsSync11(rosterPath)) return 0;
1643
1672
  let employees;
1644
1673
  try {
1645
1674
  employees = JSON.parse(await readFile3(rosterPath, "utf-8"));
@@ -1660,7 +1689,7 @@ async function cleanOldShellFunctions(homeDir = os6.homedir()) {
1660
1689
  const REMOVED_MARKER = "# Removed by exe-os \u2014 wrappers now at ~/.exe-os/bin/";
1661
1690
  let totalRemoved = 0;
1662
1691
  for (const rcPath of rcFiles) {
1663
- if (!existsSync10(rcPath)) continue;
1692
+ if (!existsSync11(rcPath)) continue;
1664
1693
  let content;
1665
1694
  try {
1666
1695
  content = await readFile3(rcPath, "utf-8");
@@ -1787,19 +1816,19 @@ async function injectOrchestrationRules(homeDir) {
1787
1816
  await writeFile3(claudeMdPath, existing + separator + ORCHESTRATION_RULES + "\n", "utf-8");
1788
1817
  return "injected";
1789
1818
  }
1790
- async function installStatusLine(packageRoot, homeDir = os6.homedir()) {
1819
+ async function installStatusLine(packageRoot, homeDir = os7.homedir()) {
1791
1820
  const prefs = loadPreferences(homeDir);
1792
1821
  if (prefs.ccStatusLine === false) return "opted-out";
1793
1822
  const claudeDir = path11.join(homeDir, ".claude");
1794
1823
  await mkdir3(claudeDir, { recursive: true });
1795
1824
  const assetPath = path11.join(packageRoot, "dist", "assets", "statusline-command.sh");
1796
- if (!existsSync10(assetPath)) return "asset-missing";
1825
+ if (!existsSync11(assetPath)) return "asset-missing";
1797
1826
  const destScript = path11.join(claudeDir, "statusline-command.sh");
1798
1827
  const assetContent = await readFile3(assetPath, "utf-8");
1799
1828
  await writeFile3(destScript, assetContent, { mode: 493 });
1800
1829
  const settingsPath = path11.join(claudeDir, "settings.json");
1801
1830
  let settings = {};
1802
- if (existsSync10(settingsPath)) {
1831
+ if (existsSync11(settingsPath)) {
1803
1832
  try {
1804
1833
  settings = JSON.parse(await readFile3(settingsPath, "utf-8"));
1805
1834
  } catch {
@@ -1836,9 +1865,9 @@ async function runInstaller(homeDir) {
1836
1865
  `Hooks: ${hookResult.added} added, ${hookResult.skipped} unchanged
1837
1866
  `
1838
1867
  );
1839
- const resolvedHome = homeDir ?? os6.homedir();
1868
+ const resolvedHome = homeDir ?? os7.homedir();
1840
1869
  const exeWorkspace = path11.join(resolvedHome, "exe");
1841
- if (!existsSync10(exeWorkspace)) {
1870
+ if (!existsSync11(exeWorkspace)) {
1842
1871
  try {
1843
1872
  await mkdir3(path11.join(exeWorkspace, "content"), { recursive: true });
1844
1873
  await mkdir3(path11.join(exeWorkspace, "operations"), { recursive: true });
@@ -1877,7 +1906,7 @@ exe-os installed successfully.
1877
1906
  `);
1878
1907
  }
1879
1908
  function setupTmux(home) {
1880
- const homeDir = home ?? os6.homedir();
1909
+ const homeDir = home ?? os7.homedir();
1881
1910
  const exeDir = path11.join(homeDir, ".exe-os");
1882
1911
  const exeTmuxConf = path11.join(exeDir, "tmux.conf");
1883
1912
  const userTmuxConf = path11.join(homeDir, ".tmux.conf");
@@ -1885,17 +1914,17 @@ function setupTmux(home) {
1885
1914
  const sourceLine = "source-file ~/.exe-os/tmux.conf";
1886
1915
  const pkgRoot = resolvePackageRoot();
1887
1916
  const assetPath = path11.join(pkgRoot, "dist", "assets", "tmux.conf");
1888
- if (!existsSync10(assetPath)) {
1917
+ if (!existsSync11(assetPath)) {
1889
1918
  process.stderr.write(`exe-os: tmux.conf asset not found at ${assetPath} \u2014 skipping tmux setup
1890
1919
  `);
1891
1920
  return;
1892
1921
  }
1893
- mkdirSync7(exeDir, { recursive: true });
1922
+ mkdirSync6(exeDir, { recursive: true });
1894
1923
  copyFileSync(assetPath, exeTmuxConf);
1895
- if (existsSync10(userTmuxConf)) {
1924
+ if (existsSync11(userTmuxConf)) {
1896
1925
  const existing = readFileSync9(userTmuxConf, "utf8");
1897
1926
  if (!existing.includes(sourceLine)) {
1898
- if (!existsSync10(backupPath)) {
1927
+ if (!existsSync11(backupPath)) {
1899
1928
  copyFileSync(userTmuxConf, backupPath);
1900
1929
  process.stderr.write(`exe-os: backed up existing tmux config to ${backupPath}
1901
1930
  `);
@@ -1915,10 +1944,10 @@ ${sourceLine}
1915
1944
  process.stderr.write("exe-os: tmux config installed\n");
1916
1945
  }
1917
1946
  function setupGhostty(home) {
1918
- const homeDir = home ?? os6.homedir();
1947
+ const homeDir = home ?? os7.homedir();
1919
1948
  const xdgConfig = path11.join(homeDir, ".config", "ghostty");
1920
1949
  const macConfig = path11.join(homeDir, "Library", "Application Support", "com.mitchellh.ghostty");
1921
- const ghosttyInstalled = existsSync10(xdgConfig) || existsSync10(macConfig) || (() => {
1950
+ const ghosttyInstalled = existsSync11(xdgConfig) || existsSync11(macConfig) || (() => {
1922
1951
  try {
1923
1952
  execSync2("which ghostty 2>/dev/null");
1924
1953
  return true;
@@ -1931,28 +1960,28 @@ function setupGhostty(home) {
1931
1960
  }
1932
1961
  const pkgRoot = resolvePackageRoot();
1933
1962
  const assetPath = path11.join(pkgRoot, "dist", "assets", "ghostty.conf");
1934
- if (!existsSync10(assetPath)) {
1963
+ if (!existsSync11(assetPath)) {
1935
1964
  process.stderr.write("exe-os: ghostty.conf asset not found \u2014 skipping Ghostty setup\n");
1936
1965
  return;
1937
1966
  }
1938
1967
  const configDir = xdgConfig;
1939
1968
  const configPath = path11.join(configDir, "config");
1940
1969
  const backupPath = path11.join(configDir, "config.backup");
1941
- mkdirSync7(configDir, { recursive: true });
1970
+ mkdirSync6(configDir, { recursive: true });
1942
1971
  const START_MARKER = "# \u2500\u2500 exe-os:ghostty-start \u2500\u2500";
1943
1972
  const END_MARKER = "# \u2500\u2500 exe-os:ghostty-end \u2500\u2500";
1944
1973
  const assetContent = readFileSync9(assetPath, "utf8").trim();
1945
1974
  const markedSection = `${START_MARKER}
1946
1975
  ${assetContent}
1947
1976
  ${END_MARKER}`;
1948
- if (existsSync10(configPath)) {
1977
+ if (existsSync11(configPath)) {
1949
1978
  const existing = readFileSync9(configPath, "utf8");
1950
1979
  if (existing.includes(START_MARKER) && existing.includes(END_MARKER)) {
1951
1980
  const before = existing.slice(0, existing.indexOf(START_MARKER));
1952
1981
  const after = existing.slice(existing.indexOf(END_MARKER) + END_MARKER.length);
1953
1982
  writeFileSync7(configPath, `${before}${markedSection}${after}`);
1954
1983
  } else if (existing.includes("Exe OS")) {
1955
- if (!existsSync10(backupPath)) {
1984
+ if (!existsSync11(backupPath)) {
1956
1985
  copyFileSync(configPath, backupPath);
1957
1986
  process.stderr.write(`exe-os: backed up existing Ghostty config to ${backupPath}
1958
1987
  `);
@@ -1960,7 +1989,7 @@ ${END_MARKER}`;
1960
1989
  writeFileSync7(configPath, `${markedSection}
1961
1990
  `);
1962
1991
  } else {
1963
- if (!existsSync10(backupPath)) {
1992
+ if (!existsSync11(backupPath)) {
1964
1993
  copyFileSync(configPath, backupPath);
1965
1994
  process.stderr.write(`exe-os: backed up existing Ghostty config to ${backupPath}
1966
1995
  `);
@@ -2011,7 +2040,7 @@ ${EXE_SECTION_END}`;
2011
2040
  });
2012
2041
 
2013
2042
  // src/bin/exe-new-employee.ts
2014
- import { existsSync as existsSync11, mkdirSync as mkdirSync8 } from "fs";
2043
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7 } from "fs";
2015
2044
  import path12 from "path";
2016
2045
 
2017
2046
  // src/lib/session-wrappers.ts
@@ -2604,13 +2633,16 @@ function isMainModule(importMetaUrl) {
2604
2633
  // src/lib/plan-limits.ts
2605
2634
  init_database();
2606
2635
  init_employees();
2607
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
2636
+ import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
2608
2637
  import path7 from "path";
2609
2638
 
2610
2639
  // src/lib/license.ts
2611
2640
  init_config();
2612
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
2641
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
2613
2642
  import { randomUUID as randomUUID2 } from "crypto";
2643
+ import { createRequire as createRequire2 } from "module";
2644
+ import { pathToFileURL as pathToFileURL2 } from "url";
2645
+ import os4 from "os";
2614
2646
  import path6 from "path";
2615
2647
  import { jwtVerify, importSPKI } from "jose";
2616
2648
  var LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
@@ -2650,14 +2682,14 @@ var FREE_LICENSE = {
2650
2682
  function loadDeviceId() {
2651
2683
  const deviceJsonPath = path6.join(EXE_AI_DIR, "device.json");
2652
2684
  try {
2653
- if (existsSync5(deviceJsonPath)) {
2685
+ if (existsSync6(deviceJsonPath)) {
2654
2686
  const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
2655
2687
  if (data.deviceId) return data.deviceId;
2656
2688
  }
2657
2689
  } catch {
2658
2690
  }
2659
2691
  try {
2660
- if (existsSync5(DEVICE_ID_PATH)) {
2692
+ if (existsSync6(DEVICE_ID_PATH)) {
2661
2693
  const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
2662
2694
  if (id2) return id2;
2663
2695
  }
@@ -2670,7 +2702,7 @@ function loadDeviceId() {
2670
2702
  }
2671
2703
  function loadLicense() {
2672
2704
  try {
2673
- if (!existsSync5(LICENSE_PATH)) return null;
2705
+ if (!existsSync6(LICENSE_PATH)) return null;
2674
2706
  return readFileSync5(LICENSE_PATH, "utf8").trim();
2675
2707
  } catch {
2676
2708
  return null;
@@ -2704,7 +2736,7 @@ async function verifyLicenseJwt(token) {
2704
2736
  }
2705
2737
  async function getCachedLicense() {
2706
2738
  try {
2707
- if (!existsSync5(CACHE_PATH)) return null;
2739
+ if (!existsSync6(CACHE_PATH)) return null;
2708
2740
  const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
2709
2741
  if (!raw.token || typeof raw.token !== "string") return null;
2710
2742
  return await verifyLicenseJwt(raw.token);
@@ -2714,7 +2746,7 @@ async function getCachedLicense() {
2714
2746
  }
2715
2747
  function readCachedToken() {
2716
2748
  try {
2717
- if (!existsSync5(CACHE_PATH)) return null;
2749
+ if (!existsSync6(CACHE_PATH)) return null;
2718
2750
  const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
2719
2751
  return typeof raw.token === "string" ? raw.token : null;
2720
2752
  } catch {
@@ -2753,52 +2785,128 @@ function cacheResponse(token) {
2753
2785
  } catch {
2754
2786
  }
2755
2787
  }
2756
- async function validateLicense(apiKey, deviceId) {
2757
- const did = deviceId ?? loadDeviceId();
2788
+ var _prismaPromise = null;
2789
+ var _prismaFailed = false;
2790
+ function loadPrismaForLicense() {
2791
+ if (_prismaFailed) return null;
2792
+ const dbUrl = process.env.DATABASE_URL;
2793
+ if (!dbUrl) {
2794
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os4.homedir(), "exe-db");
2795
+ if (!existsSync6(path6.join(exeDbRoot, "package.json"))) {
2796
+ _prismaFailed = true;
2797
+ return null;
2798
+ }
2799
+ }
2800
+ if (!_prismaPromise) {
2801
+ _prismaPromise = (async () => {
2802
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
2803
+ if (explicitPath) {
2804
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
2805
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
2806
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
2807
+ return new Ctor2();
2808
+ }
2809
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os4.homedir(), "exe-db");
2810
+ const req = createRequire2(path6.join(exeDbRoot, "package.json"));
2811
+ const entry = req.resolve("@prisma/client");
2812
+ const mod = await import(pathToFileURL2(entry).href);
2813
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
2814
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
2815
+ return new Ctor();
2816
+ })().catch((err) => {
2817
+ _prismaFailed = true;
2818
+ _prismaPromise = null;
2819
+ throw err;
2820
+ });
2821
+ }
2822
+ return _prismaPromise;
2823
+ }
2824
+ async function validateViaPostgres(apiKey) {
2825
+ const loader = loadPrismaForLicense();
2826
+ if (!loader) return null;
2827
+ try {
2828
+ const prisma = await loader;
2829
+ const rows = await prisma.$queryRawUnsafe(
2830
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
2831
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
2832
+ apiKey
2833
+ );
2834
+ if (!rows || rows.length === 0) return null;
2835
+ const row = rows[0];
2836
+ if (row.status !== "active") return null;
2837
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
2838
+ const plan = row.plan;
2839
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
2840
+ return {
2841
+ valid: true,
2842
+ plan,
2843
+ email: row.email,
2844
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
2845
+ deviceLimit: row.device_limit ?? limits.devices,
2846
+ employeeLimit: row.employee_limit ?? limits.employees,
2847
+ memoryLimit: row.memory_limit ?? limits.memories
2848
+ };
2849
+ } catch {
2850
+ return null;
2851
+ }
2852
+ }
2853
+ async function validateViaCFWorker(apiKey, deviceId) {
2758
2854
  try {
2759
2855
  const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2760
2856
  method: "POST",
2761
2857
  headers: { "Content-Type": "application/json" },
2762
- body: JSON.stringify({ apiKey, deviceId: did }),
2858
+ body: JSON.stringify({ apiKey, deviceId }),
2763
2859
  signal: AbortSignal.timeout(1e4)
2764
2860
  });
2765
- if (res.ok) {
2766
- const data = await res.json();
2767
- if (data.error === "device_limit_exceeded") {
2768
- const cached2 = await getCachedLicense();
2769
- if (cached2) return cached2;
2770
- const raw2 = getRawCachedPlan();
2771
- if (raw2) return { ...raw2, valid: false };
2772
- return { ...FREE_LICENSE, valid: false, plan: "free" };
2773
- }
2774
- if (data.token) {
2775
- cacheResponse(data.token);
2776
- const verified = await verifyLicenseJwt(data.token);
2777
- if (verified) return verified;
2861
+ if (!res.ok) return null;
2862
+ const data = await res.json();
2863
+ if (data.error === "device_limit_exceeded") return null;
2864
+ if (!data.valid) return null;
2865
+ if (data.token) {
2866
+ cacheResponse(data.token);
2867
+ const verified = await verifyLicenseJwt(data.token);
2868
+ if (verified) return verified;
2869
+ }
2870
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
2871
+ return {
2872
+ valid: data.valid,
2873
+ plan: data.plan,
2874
+ email: data.email,
2875
+ expiresAt: data.expiresAt,
2876
+ deviceLimit: limits.devices,
2877
+ employeeLimit: limits.employees,
2878
+ memoryLimit: limits.memories
2879
+ };
2880
+ } catch {
2881
+ return null;
2882
+ }
2883
+ }
2884
+ async function validateLicense(apiKey, deviceId) {
2885
+ const did = deviceId ?? loadDeviceId();
2886
+ const pgResult = await validateViaPostgres(apiKey);
2887
+ if (pgResult) {
2888
+ try {
2889
+ writeFileSync4(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
2890
+ } catch {
2891
+ }
2892
+ return pgResult;
2893
+ }
2894
+ const cfResult = await validateViaCFWorker(apiKey, did);
2895
+ if (cfResult) return cfResult;
2896
+ const cached = await getCachedLicense();
2897
+ if (cached) return cached;
2898
+ try {
2899
+ if (existsSync6(CACHE_PATH)) {
2900
+ const raw = JSON.parse(readFileSync5(CACHE_PATH, "utf8"));
2901
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
2902
+ return raw.pgLicense;
2778
2903
  }
2779
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
2780
- return {
2781
- valid: data.valid,
2782
- plan: data.plan,
2783
- email: data.email,
2784
- expiresAt: data.expiresAt,
2785
- deviceLimit: limits.devices,
2786
- employeeLimit: limits.employees,
2787
- memoryLimit: limits.memories
2788
- };
2789
2904
  }
2790
- const cached = await getCachedLicense();
2791
- if (cached) return cached;
2792
- const raw = getRawCachedPlan();
2793
- if (raw) return raw;
2794
- return { ...FREE_LICENSE, valid: false, plan: "free" };
2795
2905
  } catch {
2796
- const cached = await getCachedLicense();
2797
- if (cached) return cached;
2798
- const rawFallback = getRawCachedPlan();
2799
- if (rawFallback) return rawFallback;
2800
- return { ...FREE_LICENSE, valid: false, error: "offline" };
2801
2906
  }
2907
+ const rawFallback = getRawCachedPlan();
2908
+ if (rawFallback) return rawFallback;
2909
+ return { ...FREE_LICENSE, valid: false };
2802
2910
  }
2803
2911
  var CACHE_MAX_AGE_MS = 36e5;
2804
2912
  function getCacheAgeMs() {
@@ -2815,7 +2923,7 @@ async function checkLicense() {
2815
2923
  if (!key) {
2816
2924
  try {
2817
2925
  const configPath = path6.join(EXE_AI_DIR, "config.json");
2818
- if (existsSync5(configPath)) {
2926
+ if (existsSync6(configPath)) {
2819
2927
  const raw = JSON.parse(readFileSync5(configPath, "utf8"));
2820
2928
  const cloud = raw.cloud;
2821
2929
  if (cloud?.apiKey) {
@@ -2943,8 +3051,8 @@ async function main() {
2943
3051
  } catch {
2944
3052
  }
2945
3053
  const taskDir = path12.join(process.cwd(), "exe", name);
2946
- if (!existsSync11(taskDir)) {
2947
- mkdirSync8(taskDir, { recursive: true });
3054
+ if (!existsSync12(taskDir)) {
3055
+ mkdirSync7(taskDir, { recursive: true });
2948
3056
  }
2949
3057
  const bins = registerBinSymlinks(newEmployee.name);
2950
3058
  if (bins.created.length > 0) {