@askexenow/exe-os 0.8.32 → 0.8.36

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 (87) hide show
  1. package/dist/bin/backfill-conversations.js +332 -348
  2. package/dist/bin/backfill-responses.js +72 -12
  3. package/dist/bin/backfill-vectors.js +72 -12
  4. package/dist/bin/cleanup-stale-review-tasks.js +63 -3
  5. package/dist/bin/cli.js +1518 -1122
  6. package/dist/bin/exe-agent.js +4 -4
  7. package/dist/bin/exe-assign.js +80 -18
  8. package/dist/bin/exe-boot.js +408 -89
  9. package/dist/bin/exe-call.js +83 -24
  10. package/dist/bin/exe-dispatch.js +18 -10
  11. package/dist/bin/exe-doctor.js +63 -3
  12. package/dist/bin/exe-export-behaviors.js +64 -3
  13. package/dist/bin/exe-forget.js +69 -4
  14. package/dist/bin/exe-gateway.js +121 -36
  15. package/dist/bin/exe-heartbeat.js +77 -13
  16. package/dist/bin/exe-kill.js +64 -3
  17. package/dist/bin/exe-launch-agent.js +162 -35
  18. package/dist/bin/exe-link.js +946 -0
  19. package/dist/bin/exe-new-employee.js +121 -36
  20. package/dist/bin/exe-pending-messages.js +72 -7
  21. package/dist/bin/exe-pending-notifications.js +63 -3
  22. package/dist/bin/exe-pending-reviews.js +75 -10
  23. package/dist/bin/exe-rename.js +1287 -0
  24. package/dist/bin/exe-review.js +64 -4
  25. package/dist/bin/exe-search.js +79 -13
  26. package/dist/bin/exe-session-cleanup.js +91 -26
  27. package/dist/bin/exe-status.js +64 -4
  28. package/dist/bin/exe-team.js +64 -4
  29. package/dist/bin/git-sweep.js +71 -4
  30. package/dist/bin/graph-backfill.js +64 -3
  31. package/dist/bin/graph-export.js +64 -3
  32. package/dist/bin/install.js +3 -3
  33. package/dist/bin/scan-tasks.js +71 -4
  34. package/dist/bin/setup.js +156 -38
  35. package/dist/bin/shard-migrate.js +64 -3
  36. package/dist/bin/wiki-sync.js +64 -3
  37. package/dist/gateway/index.js +122 -37
  38. package/dist/hooks/bug-report-worker.js +209 -23
  39. package/dist/hooks/commit-complete.js +71 -4
  40. package/dist/hooks/error-recall.js +79 -13
  41. package/dist/hooks/ingest-worker.js +129 -43
  42. package/dist/hooks/instructions-loaded.js +71 -4
  43. package/dist/hooks/notification.js +71 -4
  44. package/dist/hooks/post-compact.js +71 -4
  45. package/dist/hooks/pre-compact.js +71 -4
  46. package/dist/hooks/pre-tool-use.js +413 -194
  47. package/dist/hooks/prompt-ingest-worker.js +82 -22
  48. package/dist/hooks/prompt-submit.js +103 -37
  49. package/dist/hooks/response-ingest-worker.js +87 -22
  50. package/dist/hooks/session-end.js +71 -4
  51. package/dist/hooks/session-start.js +79 -13
  52. package/dist/hooks/stop.js +71 -4
  53. package/dist/hooks/subagent-stop.js +71 -4
  54. package/dist/hooks/summary-worker.js +303 -50
  55. package/dist/index.js +134 -46
  56. package/dist/lib/cloud-sync.js +209 -15
  57. package/dist/lib/consolidation.js +4 -4
  58. package/dist/lib/database.js +64 -2
  59. package/dist/lib/device-registry.js +70 -3
  60. package/dist/lib/employee-templates.js +48 -22
  61. package/dist/lib/employees.js +34 -1
  62. package/dist/lib/exe-daemon.js +136 -53
  63. package/dist/lib/hybrid-search.js +79 -13
  64. package/dist/lib/identity-templates.js +57 -6
  65. package/dist/lib/identity.js +3 -3
  66. package/dist/lib/messaging.js +22 -14
  67. package/dist/lib/reminders.js +3 -3
  68. package/dist/lib/schedules.js +63 -3
  69. package/dist/lib/skill-learning.js +3 -3
  70. package/dist/lib/status-brief.js +63 -5
  71. package/dist/lib/store.js +64 -3
  72. package/dist/lib/task-router.js +4 -2
  73. package/dist/lib/tasks.js +48 -21
  74. package/dist/lib/tmux-routing.js +47 -20
  75. package/dist/mcp/server.js +727 -58
  76. package/dist/mcp/tools/complete-reminder.js +3 -3
  77. package/dist/mcp/tools/create-reminder.js +3 -3
  78. package/dist/mcp/tools/create-task.js +151 -24
  79. package/dist/mcp/tools/deactivate-behavior.js +3 -3
  80. package/dist/mcp/tools/list-reminders.js +3 -3
  81. package/dist/mcp/tools/list-tasks.js +17 -8
  82. package/dist/mcp/tools/send-message.js +24 -16
  83. package/dist/mcp/tools/update-task.js +25 -16
  84. package/dist/runtime/index.js +112 -24
  85. package/dist/tui/App.js +139 -36
  86. package/package.json +6 -2
  87. package/src/commands/exe/rename.md +12 -0
@@ -1,10 +1,942 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
4
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
5
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
6
  }) : x)(function(x) {
5
7
  if (typeof require !== "undefined") return require.apply(this, arguments);
6
8
  throw Error('Dynamic require of "' + x + '" is not supported');
7
9
  });
10
+ var __esm = (fn, res) => function __init() {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ };
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+
18
+ // src/lib/config.ts
19
+ var config_exports = {};
20
+ __export(config_exports, {
21
+ CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
22
+ CONFIG_PATH: () => CONFIG_PATH,
23
+ CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
24
+ DB_PATH: () => DB_PATH,
25
+ EXE_AI_DIR: () => EXE_AI_DIR,
26
+ LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
27
+ MODELS_DIR: () => MODELS_DIR,
28
+ loadConfig: () => loadConfig,
29
+ loadConfigFrom: () => loadConfigFrom,
30
+ loadConfigSync: () => loadConfigSync,
31
+ migrateConfig: () => migrateConfig,
32
+ saveConfig: () => saveConfig
33
+ });
34
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
35
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
36
+ import path2 from "path";
37
+ import os from "os";
38
+ function resolveDataDir() {
39
+ if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
40
+ if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
41
+ const newDir = path2.join(os.homedir(), ".exe-os");
42
+ const legacyDir = path2.join(os.homedir(), ".exe-mem");
43
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
44
+ try {
45
+ renameSync(legacyDir, newDir);
46
+ process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
47
+ `);
48
+ } catch {
49
+ return legacyDir;
50
+ }
51
+ }
52
+ return newDir;
53
+ }
54
+ function migrateLegacyConfig(raw) {
55
+ if ("r2" in raw) {
56
+ process.stderr.write(
57
+ "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
58
+ );
59
+ delete raw.r2;
60
+ }
61
+ if ("syncIntervalMs" in raw) {
62
+ delete raw.syncIntervalMs;
63
+ }
64
+ return raw;
65
+ }
66
+ function migrateConfig(raw) {
67
+ const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
68
+ let currentVersion = fromVersion;
69
+ let migrated = false;
70
+ if (currentVersion > CURRENT_CONFIG_VERSION) {
71
+ return { config: raw, migrated: false, fromVersion };
72
+ }
73
+ for (const migration of CONFIG_MIGRATIONS) {
74
+ if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
75
+ raw = migration.migrate(raw);
76
+ currentVersion = migration.to;
77
+ migrated = true;
78
+ }
79
+ }
80
+ return { config: raw, migrated, fromVersion };
81
+ }
82
+ function normalizeScalingRoadmap(raw) {
83
+ const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
84
+ const userRoadmap = raw.scalingRoadmap ?? {};
85
+ const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
86
+ if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
87
+ userAuto.enabled = raw.rerankerEnabled;
88
+ }
89
+ raw.scalingRoadmap = {
90
+ ...userRoadmap,
91
+ rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
92
+ };
93
+ }
94
+ function normalizeSessionLifecycle(raw) {
95
+ const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
96
+ const userSL = raw.sessionLifecycle ?? {};
97
+ raw.sessionLifecycle = { ...defaultSL, ...userSL };
98
+ }
99
+ function normalizeAutoUpdate(raw) {
100
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
101
+ const userAU = raw.autoUpdate ?? {};
102
+ raw.autoUpdate = { ...defaultAU, ...userAU };
103
+ }
104
+ async function loadConfig() {
105
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
106
+ await mkdir2(dir, { recursive: true });
107
+ const configPath = path2.join(dir, "config.json");
108
+ if (!existsSync2(configPath)) {
109
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
110
+ }
111
+ const raw = await readFile2(configPath, "utf-8");
112
+ try {
113
+ let parsed = JSON.parse(raw);
114
+ parsed = migrateLegacyConfig(parsed);
115
+ const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
116
+ if (migrated) {
117
+ process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
118
+ `);
119
+ try {
120
+ await writeFile2(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
121
+ } catch {
122
+ }
123
+ }
124
+ normalizeScalingRoadmap(migratedCfg);
125
+ normalizeSessionLifecycle(migratedCfg);
126
+ normalizeAutoUpdate(migratedCfg);
127
+ const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
128
+ if (config.dbPath.startsWith("~")) {
129
+ config.dbPath = config.dbPath.replace(/^~/, os.homedir());
130
+ }
131
+ return config;
132
+ } catch {
133
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
134
+ }
135
+ }
136
+ function loadConfigSync() {
137
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
138
+ const configPath = path2.join(dir, "config.json");
139
+ if (!existsSync2(configPath)) {
140
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
141
+ }
142
+ try {
143
+ const raw = readFileSync(configPath, "utf-8");
144
+ let parsed = JSON.parse(raw);
145
+ parsed = migrateLegacyConfig(parsed);
146
+ const { config: migratedCfg } = migrateConfig(parsed);
147
+ normalizeScalingRoadmap(migratedCfg);
148
+ normalizeSessionLifecycle(migratedCfg);
149
+ normalizeAutoUpdate(migratedCfg);
150
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
151
+ } catch {
152
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
153
+ }
154
+ }
155
+ async function saveConfig(config) {
156
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
157
+ await mkdir2(dir, { recursive: true });
158
+ const configPath = path2.join(dir, "config.json");
159
+ await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
160
+ }
161
+ async function loadConfigFrom(configPath) {
162
+ const raw = await readFile2(configPath, "utf-8");
163
+ try {
164
+ let parsed = JSON.parse(raw);
165
+ parsed = migrateLegacyConfig(parsed);
166
+ const { config: migratedCfg } = migrateConfig(parsed);
167
+ normalizeScalingRoadmap(migratedCfg);
168
+ normalizeSessionLifecycle(migratedCfg);
169
+ normalizeAutoUpdate(migratedCfg);
170
+ return { ...DEFAULT_CONFIG, ...migratedCfg };
171
+ } catch {
172
+ return { ...DEFAULT_CONFIG };
173
+ }
174
+ }
175
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
176
+ var init_config = __esm({
177
+ "src/lib/config.ts"() {
178
+ "use strict";
179
+ EXE_AI_DIR = resolveDataDir();
180
+ DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
181
+ MODELS_DIR = path2.join(EXE_AI_DIR, "models");
182
+ CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
183
+ LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
184
+ CURRENT_CONFIG_VERSION = 1;
185
+ DEFAULT_CONFIG = {
186
+ config_version: CURRENT_CONFIG_VERSION,
187
+ dbPath: DB_PATH,
188
+ modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
189
+ embeddingDim: 1024,
190
+ batchSize: 20,
191
+ flushIntervalMs: 1e4,
192
+ autoIngestion: true,
193
+ autoRetrieval: true,
194
+ searchMode: "hybrid",
195
+ hookSearchMode: "hybrid",
196
+ fileGrepEnabled: true,
197
+ splashEffect: true,
198
+ consolidationEnabled: true,
199
+ consolidationIntervalMs: 6 * 60 * 60 * 1e3,
200
+ consolidationModel: "claude-haiku-4-5-20251001",
201
+ consolidationMaxCallsPerRun: 20,
202
+ selfQueryRouter: true,
203
+ selfQueryModel: "claude-haiku-4-5-20251001",
204
+ rerankerEnabled: true,
205
+ scalingRoadmap: {
206
+ rerankerAutoTrigger: {
207
+ enabled: true,
208
+ broadQueryMinCardinality: 5e4,
209
+ fetchTopK: 150,
210
+ returnTopK: 5
211
+ }
212
+ },
213
+ graphRagEnabled: true,
214
+ wikiEnabled: false,
215
+ wikiUrl: "",
216
+ wikiApiKey: "",
217
+ wikiSyncIntervalMs: 30 * 60 * 1e3,
218
+ wikiWorkspaceMapping: {
219
+ exe: "Executive",
220
+ yoshi: "Engineering",
221
+ mari: "Marketing",
222
+ tom: "Engineering",
223
+ sasha: "Production"
224
+ },
225
+ wikiAutoUpdate: true,
226
+ wikiAutoUpdateThreshold: 0.5,
227
+ wikiAutoUpdateCreateNew: true,
228
+ skillLearning: true,
229
+ skillThreshold: 3,
230
+ skillModel: "claude-haiku-4-5-20251001",
231
+ exeHeartbeat: {
232
+ enabled: true,
233
+ intervalSeconds: 60,
234
+ staleInProgressThresholdHours: 2
235
+ },
236
+ sessionLifecycle: {
237
+ idleKillEnabled: true,
238
+ idleKillTicksRequired: 3,
239
+ idleKillIntercomAckWindowMs: 1e4,
240
+ maxAutoInstances: 10
241
+ },
242
+ autoUpdate: {
243
+ checkOnBoot: true,
244
+ autoInstall: false,
245
+ checkIntervalMs: 24 * 60 * 60 * 1e3
246
+ }
247
+ };
248
+ CONFIG_MIGRATIONS = [
249
+ {
250
+ from: 0,
251
+ to: 1,
252
+ migrate: (cfg) => {
253
+ cfg.config_version = 1;
254
+ return cfg;
255
+ }
256
+ }
257
+ ];
258
+ }
259
+ });
260
+
261
+ // src/lib/crypto.ts
262
+ var crypto_exports = {};
263
+ __export(crypto_exports, {
264
+ decryptSyncBlob: () => decryptSyncBlob,
265
+ encryptSyncBlob: () => encryptSyncBlob,
266
+ initSyncCrypto: () => initSyncCrypto,
267
+ isSyncCryptoInitialized: () => isSyncCryptoInitialized
268
+ });
269
+ import crypto2 from "crypto";
270
+ function initSyncCrypto(masterKey) {
271
+ if (masterKey.length !== 32) {
272
+ throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
273
+ }
274
+ _syncKey = Buffer.from(
275
+ crypto2.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
276
+ );
277
+ }
278
+ function isSyncCryptoInitialized() {
279
+ return _syncKey !== null;
280
+ }
281
+ function requireSyncKey() {
282
+ if (!_syncKey) {
283
+ throw new Error("Sync crypto not initialized. Call initSyncCrypto(masterKey) first.");
284
+ }
285
+ return _syncKey;
286
+ }
287
+ function encryptSyncBlob(data) {
288
+ const key = requireSyncKey();
289
+ const iv = crypto2.randomBytes(IV_LENGTH);
290
+ const cipher = crypto2.createCipheriv(ALGORITHM, key, iv);
291
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
292
+ const tag = cipher.getAuthTag();
293
+ return Buffer.concat([iv, encrypted, tag]).toString("base64");
294
+ }
295
+ function decryptSyncBlob(ciphertext) {
296
+ const key = requireSyncKey();
297
+ const combined = Buffer.from(ciphertext, "base64");
298
+ if (combined.length < IV_LENGTH + TAG_LENGTH) {
299
+ throw new Error("Sync blob too short to contain IV + tag");
300
+ }
301
+ const iv = combined.subarray(0, IV_LENGTH);
302
+ const tag = combined.subarray(combined.length - TAG_LENGTH);
303
+ const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
304
+ const decipher = crypto2.createDecipheriv(ALGORITHM, key, iv);
305
+ decipher.setAuthTag(tag);
306
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]);
307
+ }
308
+ var ALGORITHM, IV_LENGTH, TAG_LENGTH, SYNC_HKDF_INFO, _syncKey;
309
+ var init_crypto = __esm({
310
+ "src/lib/crypto.ts"() {
311
+ "use strict";
312
+ ALGORITHM = "aes-256-gcm";
313
+ IV_LENGTH = 12;
314
+ TAG_LENGTH = 16;
315
+ SYNC_HKDF_INFO = "exe-mem-sync-v2";
316
+ _syncKey = null;
317
+ }
318
+ });
319
+
320
+ // src/lib/db-retry.ts
321
+ var init_db_retry = __esm({
322
+ "src/lib/db-retry.ts"() {
323
+ "use strict";
324
+ }
325
+ });
326
+
327
+ // src/lib/database.ts
328
+ import { createClient } from "@libsql/client";
329
+ function getClient() {
330
+ if (!_resilientClient) {
331
+ throw new Error("Database client not initialized. Call initDatabase() first.");
332
+ }
333
+ return _resilientClient;
334
+ }
335
+ var _resilientClient;
336
+ var init_database = __esm({
337
+ "src/lib/database.ts"() {
338
+ "use strict";
339
+ init_db_retry();
340
+ _resilientClient = null;
341
+ }
342
+ });
343
+
344
+ // src/lib/compress.ts
345
+ import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
346
+ function compress(input) {
347
+ if (input.length === 0) return Buffer.alloc(0);
348
+ return brotliCompressSync(input, {
349
+ params: {
350
+ [constants.BROTLI_PARAM_QUALITY]: 4
351
+ }
352
+ });
353
+ }
354
+ function decompress(input) {
355
+ if (input.length === 0) return Buffer.alloc(0);
356
+ return brotliDecompressSync(input);
357
+ }
358
+ var init_compress = __esm({
359
+ "src/lib/compress.ts"() {
360
+ "use strict";
361
+ }
362
+ });
363
+
364
+ // src/lib/employees.ts
365
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
366
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
367
+ import { execSync } from "child_process";
368
+ import path3 from "path";
369
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
370
+ if (!existsSync3(employeesPath)) {
371
+ return [];
372
+ }
373
+ const raw = await readFile3(employeesPath, "utf-8");
374
+ try {
375
+ return JSON.parse(raw);
376
+ } catch {
377
+ return [];
378
+ }
379
+ }
380
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
381
+ await mkdir3(path3.dirname(employeesPath), { recursive: true });
382
+ await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
383
+ }
384
+ function registerBinSymlinks(name) {
385
+ const created = [];
386
+ const skipped = [];
387
+ const errors = [];
388
+ let exeBinPath;
389
+ try {
390
+ exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
391
+ } catch {
392
+ errors.push("Could not find 'exe' in PATH");
393
+ return { created, skipped, errors };
394
+ }
395
+ const binDir = path3.dirname(exeBinPath);
396
+ let target;
397
+ try {
398
+ target = readlinkSync(exeBinPath);
399
+ } catch {
400
+ errors.push("Could not read 'exe' symlink");
401
+ return { created, skipped, errors };
402
+ }
403
+ for (const suffix of ["", "-opencode"]) {
404
+ const linkName = `${name}${suffix}`;
405
+ const linkPath = path3.join(binDir, linkName);
406
+ if (existsSync3(linkPath)) {
407
+ skipped.push(linkName);
408
+ continue;
409
+ }
410
+ try {
411
+ symlinkSync(target, linkPath);
412
+ created.push(linkName);
413
+ } catch (err) {
414
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
415
+ }
416
+ }
417
+ return { created, skipped, errors };
418
+ }
419
+ var EMPLOYEES_PATH;
420
+ var init_employees = __esm({
421
+ "src/lib/employees.ts"() {
422
+ "use strict";
423
+ init_config();
424
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
425
+ }
426
+ });
427
+
428
+ // src/lib/license.ts
429
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync4, mkdirSync } from "fs";
430
+ import { randomUUID } from "crypto";
431
+ import path4 from "path";
432
+ import { jwtVerify, importSPKI } from "jose";
433
+ function loadDeviceId() {
434
+ const deviceJsonPath = path4.join(EXE_AI_DIR, "device.json");
435
+ try {
436
+ if (existsSync4(deviceJsonPath)) {
437
+ const data = JSON.parse(readFileSync3(deviceJsonPath, "utf8"));
438
+ if (data.deviceId) return data.deviceId;
439
+ }
440
+ } catch {
441
+ }
442
+ try {
443
+ if (existsSync4(DEVICE_ID_PATH)) {
444
+ const id2 = readFileSync3(DEVICE_ID_PATH, "utf8").trim();
445
+ if (id2) return id2;
446
+ }
447
+ } catch {
448
+ }
449
+ const id = randomUUID();
450
+ mkdirSync(EXE_AI_DIR, { recursive: true });
451
+ writeFileSync(DEVICE_ID_PATH, id, "utf8");
452
+ return id;
453
+ }
454
+ function loadLicense() {
455
+ try {
456
+ if (!existsSync4(LICENSE_PATH)) return null;
457
+ return readFileSync3(LICENSE_PATH, "utf8").trim();
458
+ } catch {
459
+ return null;
460
+ }
461
+ }
462
+ async function verifyLicenseJwt(token) {
463
+ try {
464
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
465
+ const { payload } = await jwtVerify(token, key, {
466
+ algorithms: [LICENSE_JWT_ALG]
467
+ });
468
+ const plan = payload.plan ?? "free";
469
+ const email = payload.sub ?? "";
470
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
471
+ return {
472
+ valid: true,
473
+ plan,
474
+ email,
475
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
476
+ deviceLimit: limits.devices,
477
+ employeeLimit: limits.employees,
478
+ memoryLimit: limits.memories
479
+ };
480
+ } catch {
481
+ return null;
482
+ }
483
+ }
484
+ async function getCachedLicense() {
485
+ try {
486
+ if (!existsSync4(CACHE_PATH)) return null;
487
+ const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
488
+ if (!raw.token || typeof raw.token !== "string") return null;
489
+ return await verifyLicenseJwt(raw.token);
490
+ } catch {
491
+ return null;
492
+ }
493
+ }
494
+ function cacheResponse(token) {
495
+ try {
496
+ writeFileSync(CACHE_PATH, JSON.stringify({ token }), "utf8");
497
+ } catch {
498
+ }
499
+ }
500
+ async function validateLicense(apiKey, deviceId) {
501
+ const did = deviceId ?? loadDeviceId();
502
+ try {
503
+ const res = await fetch(`${API_BASE}/auth/activate`, {
504
+ method: "POST",
505
+ headers: { "Content-Type": "application/json" },
506
+ body: JSON.stringify({ apiKey, deviceId: did }),
507
+ signal: AbortSignal.timeout(1e4)
508
+ });
509
+ if (res.ok) {
510
+ const data = await res.json();
511
+ if (data.error === "device_limit_exceeded") {
512
+ const cached2 = await getCachedLicense();
513
+ if (cached2) return cached2;
514
+ return { ...FREE_LICENSE, valid: false, plan: "free" };
515
+ }
516
+ if (data.token) {
517
+ cacheResponse(data.token);
518
+ const verified = await verifyLicenseJwt(data.token);
519
+ if (verified) return verified;
520
+ }
521
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
522
+ return {
523
+ valid: data.valid,
524
+ plan: data.plan,
525
+ email: data.email,
526
+ expiresAt: data.expiresAt,
527
+ deviceLimit: limits.devices,
528
+ employeeLimit: limits.employees,
529
+ memoryLimit: limits.memories
530
+ };
531
+ }
532
+ const cached = await getCachedLicense();
533
+ if (cached) return cached;
534
+ return { ...FREE_LICENSE, valid: false, plan: "free" };
535
+ } catch {
536
+ const cached = await getCachedLicense();
537
+ if (cached) return cached;
538
+ return FREE_LICENSE;
539
+ }
540
+ }
541
+ async function checkLicense() {
542
+ const key = loadLicense();
543
+ if (!key) return FREE_LICENSE;
544
+ const cached = await getCachedLicense();
545
+ if (cached) return cached;
546
+ const deviceId = loadDeviceId();
547
+ return validateLicense(key, deviceId);
548
+ }
549
+ function isFeatureAllowed(license, feature) {
550
+ switch (feature) {
551
+ case "cloud_sync":
552
+ case "external_agents":
553
+ case "wiki":
554
+ return license.plan !== "free";
555
+ case "unlimited_employees":
556
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
557
+ }
558
+ }
559
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE;
560
+ var init_license = __esm({
561
+ "src/lib/license.ts"() {
562
+ "use strict";
563
+ init_config();
564
+ LICENSE_PATH = path4.join(EXE_AI_DIR, "license.key");
565
+ CACHE_PATH = path4.join(EXE_AI_DIR, "license-cache.json");
566
+ DEVICE_ID_PATH = path4.join(EXE_AI_DIR, "device-id");
567
+ API_BASE = "https://askexe.com/cloud";
568
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
569
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
570
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
571
+ -----END PUBLIC KEY-----`;
572
+ LICENSE_JWT_ALG = "ES256";
573
+ PLAN_LIMITS = {
574
+ free: { devices: 1, employees: 1, memories: 5e3 },
575
+ pro: { devices: 2, employees: 5, memories: 1e5 },
576
+ team: { devices: 10, employees: 20, memories: 1e6 },
577
+ agency: { devices: 50, employees: 100, memories: 1e7 },
578
+ enterprise: { devices: -1, employees: -1, memories: -1 }
579
+ };
580
+ FREE_LICENSE = {
581
+ valid: true,
582
+ plan: "free",
583
+ email: "",
584
+ expiresAt: null,
585
+ deviceLimit: 1,
586
+ employeeLimit: 1,
587
+ memoryLimit: 5e3
588
+ };
589
+ }
590
+ });
591
+
592
+ // src/lib/plan-limits.ts
593
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
594
+ import path5 from "path";
595
+ async function assertFeature(feature) {
596
+ const license = await checkLicense();
597
+ if (!isFeatureAllowed(license, feature)) {
598
+ throw new PlanLimitError(
599
+ `Feature "${feature}" requires a paid plan. Current plan: ${license.plan}. Upgrade at https://askexe.com.`
600
+ );
601
+ }
602
+ }
603
+ var PlanLimitError, CACHE_PATH2;
604
+ var init_plan_limits = __esm({
605
+ "src/lib/plan-limits.ts"() {
606
+ "use strict";
607
+ init_database();
608
+ init_employees();
609
+ init_license();
610
+ init_config();
611
+ PlanLimitError = class extends Error {
612
+ constructor(message) {
613
+ super(message);
614
+ this.name = "PlanLimitError";
615
+ }
616
+ };
617
+ CACHE_PATH2 = path5.join(EXE_AI_DIR, "license-cache.json");
618
+ }
619
+ });
620
+
621
+ // src/lib/cloud-sync.ts
622
+ var cloud_sync_exports = {};
623
+ __export(cloud_sync_exports, {
624
+ buildRosterBlob: () => buildRosterBlob,
625
+ cloudPull: () => cloudPull,
626
+ cloudPullRoster: () => cloudPullRoster,
627
+ cloudPush: () => cloudPush,
628
+ cloudPushRoster: () => cloudPushRoster,
629
+ cloudSync: () => cloudSync,
630
+ mergeRosterFromRemote: () => mergeRosterFromRemote
631
+ });
632
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2, appendFileSync } from "fs";
633
+ import path6 from "path";
634
+ import { homedir } from "os";
635
+ function logError(msg) {
636
+ try {
637
+ const logPath = path6.join(homedir(), ".exe-os", "workers.log");
638
+ appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
639
+ `);
640
+ } catch {
641
+ }
642
+ }
643
+ function assertSecureEndpoint(endpoint) {
644
+ if (endpoint.startsWith("https://")) return;
645
+ if (endpoint.startsWith("http://")) {
646
+ try {
647
+ const parsed = new URL(endpoint);
648
+ if (LOCALHOST_PATTERNS.test(parsed.hostname)) return;
649
+ } catch {
650
+ return;
651
+ }
652
+ throw new Error(
653
+ `Insecure cloud endpoint rejected: "${endpoint}". Use https:// for remote hosts. Plain http:// is only allowed for localhost.`
654
+ );
655
+ }
656
+ }
657
+ async function cloudPush(records, maxVersion, config) {
658
+ if (records.length === 0) return true;
659
+ assertSecureEndpoint(config.endpoint);
660
+ try {
661
+ const json = JSON.stringify(records);
662
+ const compressed = compress(Buffer.from(json, "utf8"));
663
+ const blob = encryptSyncBlob(compressed);
664
+ const resp = await fetch(`${config.endpoint}/sync/push`, {
665
+ method: "POST",
666
+ headers: {
667
+ Authorization: `Bearer ${config.apiKey}`,
668
+ "Content-Type": "application/json",
669
+ "X-Device-Id": loadDeviceId()
670
+ },
671
+ body: JSON.stringify({ version: maxVersion, blob })
672
+ });
673
+ return resp.ok;
674
+ } catch (err) {
675
+ logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
676
+ return false;
677
+ }
678
+ }
679
+ async function cloudPull(sinceVersion, config) {
680
+ assertSecureEndpoint(config.endpoint);
681
+ try {
682
+ const response = await fetch(`${config.endpoint}/sync/pull`, {
683
+ method: "POST",
684
+ headers: {
685
+ Authorization: `Bearer ${config.apiKey}`,
686
+ "Content-Type": "application/json",
687
+ "X-Device-Id": loadDeviceId()
688
+ },
689
+ body: JSON.stringify({ since_version: sinceVersion })
690
+ });
691
+ if (!response.ok) return { records: [], maxVersion: sinceVersion };
692
+ const data = await response.json();
693
+ const allRecords = [];
694
+ for (const { blob } of data.blobs ?? []) {
695
+ try {
696
+ const compressed = decryptSyncBlob(blob);
697
+ const json = decompress(compressed).toString("utf8");
698
+ const records = JSON.parse(json);
699
+ allRecords.push(...records);
700
+ } catch {
701
+ continue;
702
+ }
703
+ }
704
+ return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
705
+ } catch (err) {
706
+ logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
707
+ return { records: [], maxVersion: sinceVersion };
708
+ }
709
+ }
710
+ async function cloudSync(config) {
711
+ await assertFeature("cloud_sync");
712
+ let client;
713
+ try {
714
+ client = getClient();
715
+ } catch {
716
+ throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
717
+ }
718
+ const pullMeta = await client.execute(
719
+ "SELECT value FROM sync_meta WHERE key = 'last_cloud_pull_version'"
720
+ );
721
+ const lastPullVersion = pullMeta.rows.length > 0 ? Number(pullMeta.rows[0].value) : 0;
722
+ const pullResult = await cloudPull(lastPullVersion, config);
723
+ let pulled = 0;
724
+ if (pullResult.records.length > 0) {
725
+ const stmts = pullResult.records.map((rec) => ({
726
+ sql: `INSERT OR REPLACE INTO memories
727
+ (id, agent_id, agent_role, session_id, timestamp,
728
+ tool_name, project_name, has_error, raw_text, version,
729
+ author_device_id, scope)
730
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
731
+ args: [
732
+ rec.id,
733
+ rec.agent_id,
734
+ rec.agent_role,
735
+ rec.session_id,
736
+ rec.timestamp,
737
+ rec.tool_name,
738
+ rec.project_name,
739
+ rec.has_error,
740
+ rec.raw_text,
741
+ rec.version,
742
+ rec.author_device_id ?? null,
743
+ rec.scope ?? "business"
744
+ ]
745
+ }));
746
+ await client.batch(stmts, "write");
747
+ pulled = pullResult.records.length;
748
+ }
749
+ if (pullResult.maxVersion > lastPullVersion) {
750
+ await client.execute({
751
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_pull_version', ?)",
752
+ args: [String(pullResult.maxVersion)]
753
+ });
754
+ }
755
+ const pushMeta = await client.execute(
756
+ "SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
757
+ );
758
+ const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
759
+ const recordsResult = await client.execute({
760
+ sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
761
+ tool_name, project_name, has_error, raw_text, version,
762
+ author_device_id, scope
763
+ FROM memories
764
+ WHERE version > ?
765
+ AND (scope IS NULL OR scope != 'personal')
766
+ ORDER BY version ASC`,
767
+ args: [lastPushVersion]
768
+ });
769
+ let pushed = 0;
770
+ if (recordsResult.rows.length > 0) {
771
+ const records = recordsResult.rows.map((row) => ({
772
+ id: row.id,
773
+ agent_id: row.agent_id,
774
+ agent_role: row.agent_role,
775
+ session_id: row.session_id,
776
+ timestamp: row.timestamp,
777
+ tool_name: row.tool_name,
778
+ project_name: row.project_name,
779
+ has_error: row.has_error,
780
+ raw_text: row.raw_text,
781
+ version: row.version,
782
+ author_device_id: row.author_device_id,
783
+ scope: row.scope
784
+ }));
785
+ const maxVersion = Number(records[records.length - 1].version);
786
+ const pushOk = await cloudPush(records, maxVersion, config);
787
+ if (pushOk) {
788
+ await client.execute({
789
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
790
+ args: [String(maxVersion)]
791
+ });
792
+ pushed = records.length;
793
+ }
794
+ }
795
+ try {
796
+ await cloudPushRoster(config);
797
+ } catch (err) {
798
+ process.stderr.write(`[cloud-sync] Roster push warning: ${err instanceof Error ? err.message : String(err)}
799
+ `);
800
+ }
801
+ try {
802
+ await cloudPullRoster(config);
803
+ } catch (err) {
804
+ process.stderr.write(`[cloud-sync] Roster pull warning: ${err instanceof Error ? err.message : String(err)}
805
+ `);
806
+ }
807
+ return { pushed, pulled };
808
+ }
809
+ function buildRosterBlob(paths) {
810
+ const rosterPath = paths?.rosterPath ?? path6.join(EXE_AI_DIR, "exe-employees.json");
811
+ const identityDir = paths?.identityDir ?? path6.join(EXE_AI_DIR, "identity");
812
+ let roster = [];
813
+ if (existsSync6(rosterPath)) {
814
+ try {
815
+ roster = JSON.parse(readFileSync5(rosterPath, "utf-8"));
816
+ } catch {
817
+ }
818
+ }
819
+ const identities = {};
820
+ if (existsSync6(identityDir)) {
821
+ for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
822
+ try {
823
+ identities[file] = readFileSync5(path6.join(identityDir, file), "utf-8");
824
+ } catch {
825
+ }
826
+ }
827
+ }
828
+ const content = JSON.stringify({ roster, identities });
829
+ const hash = Buffer.from(content).length;
830
+ return { roster, identities, version: hash };
831
+ }
832
+ async function cloudPushRoster(config) {
833
+ assertSecureEndpoint(config.endpoint);
834
+ const blob = buildRosterBlob();
835
+ if (blob.roster.length === 0) return true;
836
+ try {
837
+ const client = getClient();
838
+ const meta = await client.execute(
839
+ "SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
840
+ );
841
+ const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
842
+ if (blob.version === lastVersion) return true;
843
+ } catch {
844
+ }
845
+ try {
846
+ const json = JSON.stringify(blob);
847
+ const compressed = compress(Buffer.from(json, "utf8"));
848
+ const encrypted = encryptSyncBlob(compressed);
849
+ const resp = await fetch(`${config.endpoint}/sync/push-roster`, {
850
+ method: "POST",
851
+ headers: {
852
+ Authorization: `Bearer ${config.apiKey}`,
853
+ "Content-Type": "application/json",
854
+ "X-Device-Id": loadDeviceId()
855
+ },
856
+ body: JSON.stringify({ blob: encrypted })
857
+ });
858
+ if (resp.ok) {
859
+ try {
860
+ const client = getClient();
861
+ await client.execute({
862
+ sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
863
+ args: [String(blob.version)]
864
+ });
865
+ } catch {
866
+ }
867
+ }
868
+ return resp.ok;
869
+ } catch (err) {
870
+ process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
871
+ `);
872
+ return false;
873
+ }
874
+ }
875
+ async function cloudPullRoster(config) {
876
+ assertSecureEndpoint(config.endpoint);
877
+ try {
878
+ const resp = await fetch(`${config.endpoint}/sync/pull-roster`, {
879
+ method: "GET",
880
+ headers: {
881
+ Authorization: `Bearer ${config.apiKey}`,
882
+ "X-Device-Id": loadDeviceId()
883
+ }
884
+ });
885
+ if (!resp.ok) return { added: 0 };
886
+ const data = await resp.json();
887
+ if (!data.blob) return { added: 0 };
888
+ const compressed = decryptSyncBlob(data.blob);
889
+ const json = decompress(compressed).toString("utf8");
890
+ const remote = JSON.parse(json);
891
+ return mergeRosterFromRemote(remote);
892
+ } catch (err) {
893
+ process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
894
+ `);
895
+ return { added: 0 };
896
+ }
897
+ }
898
+ async function mergeRosterFromRemote(remote, paths) {
899
+ const rosterPath = paths?.rosterPath ?? void 0;
900
+ const identityDir = paths?.identityDir ?? path6.join(EXE_AI_DIR, "identity");
901
+ const localEmployees = await loadEmployees(rosterPath);
902
+ const localNames = new Set(localEmployees.map((e) => e.name));
903
+ let added = 0;
904
+ for (const remoteEmp of remote.roster) {
905
+ if (localNames.has(remoteEmp.name)) continue;
906
+ localEmployees.push(remoteEmp);
907
+ localNames.add(remoteEmp.name);
908
+ added++;
909
+ if (remote.identities[`${remoteEmp.name}.md`]) {
910
+ if (!existsSync6(identityDir)) mkdirSync2(identityDir, { recursive: true });
911
+ const idPath = path6.join(identityDir, `${remoteEmp.name}.md`);
912
+ if (!existsSync6(idPath)) {
913
+ writeFileSync2(idPath, remote.identities[`${remoteEmp.name}.md`], "utf-8");
914
+ }
915
+ }
916
+ try {
917
+ registerBinSymlinks(remoteEmp.name);
918
+ } catch {
919
+ }
920
+ }
921
+ if (added > 0) {
922
+ await saveEmployees(localEmployees, rosterPath);
923
+ }
924
+ return { added };
925
+ }
926
+ var LOCALHOST_PATTERNS;
927
+ var init_cloud_sync = __esm({
928
+ "src/lib/cloud-sync.ts"() {
929
+ "use strict";
930
+ init_database();
931
+ init_crypto();
932
+ init_compress();
933
+ init_plan_limits();
934
+ init_license();
935
+ init_config();
936
+ init_employees();
937
+ LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
938
+ }
939
+ });
8
940
 
9
941
  // src/bin/exe-link.ts
10
942
  import { createInterface } from "readline";
@@ -169,6 +1101,20 @@ async function main() {
169
1101
  const key = importMnemonic(mnemonic);
170
1102
  await setMasterKey(key);
171
1103
  console.log("Master key imported and stored securely.");
1104
+ try {
1105
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
1106
+ const { initSyncCrypto: initSyncCrypto2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
1107
+ const { cloudPullRoster: cloudPullRoster2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
1108
+ const config = await loadConfig2();
1109
+ if (config.cloud?.apiKey && config.cloud?.endpoint) {
1110
+ initSyncCrypto2(key);
1111
+ const result = await cloudPullRoster2({ apiKey: config.cloud.apiKey, endpoint: config.cloud.endpoint });
1112
+ if (result.added > 0) {
1113
+ console.log(`Pulled ${result.added} employee(s) from Exe Cloud.`);
1114
+ }
1115
+ }
1116
+ } catch {
1117
+ }
172
1118
  console.log("Run /exe-setup to configure sync and download the embedding model.");
173
1119
  } catch (err) {
174
1120
  console.error(err instanceof Error ? err.message : String(err));