@askexenow/exe-os 0.8.0 → 0.8.2

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 (107) hide show
  1. package/README.md +178 -79
  2. package/package.json +1 -1
  3. package/dist/bin/backfill-responses.js +0 -1912
  4. package/dist/bin/backfill-vectors.js +0 -1642
  5. package/dist/bin/cleanup-stale-review-tasks.js +0 -1339
  6. package/dist/bin/cli.js +0 -18800
  7. package/dist/bin/exe-agent.js +0 -1858
  8. package/dist/bin/exe-assign.js +0 -1957
  9. package/dist/bin/exe-boot.js +0 -6460
  10. package/dist/bin/exe-call.js +0 -197
  11. package/dist/bin/exe-cloud.js +0 -850
  12. package/dist/bin/exe-dispatch.js +0 -1146
  13. package/dist/bin/exe-doctor.js +0 -1657
  14. package/dist/bin/exe-export-behaviors.js +0 -1494
  15. package/dist/bin/exe-forget.js +0 -1627
  16. package/dist/bin/exe-gateway.js +0 -7732
  17. package/dist/bin/exe-healthcheck.js +0 -207
  18. package/dist/bin/exe-heartbeat.js +0 -1647
  19. package/dist/bin/exe-kill.js +0 -1479
  20. package/dist/bin/exe-launch-agent.js +0 -1704
  21. package/dist/bin/exe-link.js +0 -192
  22. package/dist/bin/exe-new-employee.js +0 -852
  23. package/dist/bin/exe-pending-messages.js +0 -1446
  24. package/dist/bin/exe-pending-notifications.js +0 -1321
  25. package/dist/bin/exe-pending-reviews.js +0 -1468
  26. package/dist/bin/exe-repo-drift.js +0 -95
  27. package/dist/bin/exe-review.js +0 -1590
  28. package/dist/bin/exe-search.js +0 -2651
  29. package/dist/bin/exe-session-cleanup.js +0 -3173
  30. package/dist/bin/exe-settings.js +0 -354
  31. package/dist/bin/exe-status.js +0 -1532
  32. package/dist/bin/exe-team.js +0 -1324
  33. package/dist/bin/git-sweep.js +0 -2185
  34. package/dist/bin/graph-backfill.js +0 -1968
  35. package/dist/bin/graph-export.js +0 -1604
  36. package/dist/bin/install.js +0 -656
  37. package/dist/bin/list-providers.js +0 -140
  38. package/dist/bin/scan-tasks.js +0 -1820
  39. package/dist/bin/setup.js +0 -951
  40. package/dist/bin/shard-migrate.js +0 -1494
  41. package/dist/bin/update.js +0 -95
  42. package/dist/bin/wiki-sync.js +0 -1514
  43. package/dist/gateway/index.js +0 -8848
  44. package/dist/hooks/bug-report-worker.js +0 -2743
  45. package/dist/hooks/commit-complete.js +0 -2108
  46. package/dist/hooks/error-recall.js +0 -2861
  47. package/dist/hooks/exe-heartbeat-hook.js +0 -232
  48. package/dist/hooks/ingest-worker.js +0 -4793
  49. package/dist/hooks/ingest.js +0 -684
  50. package/dist/hooks/instructions-loaded.js +0 -1880
  51. package/dist/hooks/notification.js +0 -1726
  52. package/dist/hooks/post-compact.js +0 -1751
  53. package/dist/hooks/pre-compact.js +0 -1746
  54. package/dist/hooks/pre-tool-use.js +0 -2191
  55. package/dist/hooks/prompt-ingest-worker.js +0 -2126
  56. package/dist/hooks/prompt-submit.js +0 -4693
  57. package/dist/hooks/response-ingest-worker.js +0 -1936
  58. package/dist/hooks/session-end.js +0 -1752
  59. package/dist/hooks/session-start.js +0 -2795
  60. package/dist/hooks/stop.js +0 -1835
  61. package/dist/hooks/subagent-stop.js +0 -1726
  62. package/dist/hooks/summary-worker.js +0 -2661
  63. package/dist/index.js +0 -11834
  64. package/dist/lib/cloud-sync.js +0 -495
  65. package/dist/lib/config.js +0 -222
  66. package/dist/lib/consolidation.js +0 -476
  67. package/dist/lib/crypto.js +0 -51
  68. package/dist/lib/database.js +0 -730
  69. package/dist/lib/device-registry.js +0 -900
  70. package/dist/lib/embedder.js +0 -632
  71. package/dist/lib/employee-templates.js +0 -543
  72. package/dist/lib/employees.js +0 -177
  73. package/dist/lib/error-detector.js +0 -156
  74. package/dist/lib/exe-daemon-client.js +0 -451
  75. package/dist/lib/exe-daemon.js +0 -8285
  76. package/dist/lib/file-grep.js +0 -199
  77. package/dist/lib/hybrid-search.js +0 -1819
  78. package/dist/lib/identity-templates.js +0 -320
  79. package/dist/lib/identity.js +0 -223
  80. package/dist/lib/keychain.js +0 -145
  81. package/dist/lib/license.js +0 -377
  82. package/dist/lib/messaging.js +0 -1376
  83. package/dist/lib/reminders.js +0 -63
  84. package/dist/lib/schedules.js +0 -1396
  85. package/dist/lib/session-registry.js +0 -52
  86. package/dist/lib/skill-learning.js +0 -477
  87. package/dist/lib/status-brief.js +0 -235
  88. package/dist/lib/store.js +0 -1551
  89. package/dist/lib/task-router.js +0 -62
  90. package/dist/lib/tasks.js +0 -2456
  91. package/dist/lib/tmux-routing.js +0 -2836
  92. package/dist/lib/tmux-status.js +0 -261
  93. package/dist/lib/tmux-transport.js +0 -83
  94. package/dist/lib/transport.js +0 -128
  95. package/dist/lib/ws-auth.js +0 -19
  96. package/dist/lib/ws-client.js +0 -160
  97. package/dist/mcp/server.js +0 -10538
  98. package/dist/mcp/tools/complete-reminder.js +0 -67
  99. package/dist/mcp/tools/create-reminder.js +0 -52
  100. package/dist/mcp/tools/create-task.js +0 -1853
  101. package/dist/mcp/tools/deactivate-behavior.js +0 -263
  102. package/dist/mcp/tools/list-reminders.js +0 -62
  103. package/dist/mcp/tools/list-tasks.js +0 -463
  104. package/dist/mcp/tools/send-message.js +0 -1382
  105. package/dist/mcp/tools/update-task.js +0 -1692
  106. package/dist/runtime/index.js +0 -6809
  107. package/dist/tui/App.js +0 -17479
package/dist/bin/setup.js DELETED
@@ -1,951 +0,0 @@
1
- #!/usr/bin/env node
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __esm = (fn, res) => function __init() {
5
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
- };
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
-
12
- // src/lib/config.ts
13
- var config_exports = {};
14
- __export(config_exports, {
15
- CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
16
- CONFIG_PATH: () => CONFIG_PATH,
17
- CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
18
- DB_PATH: () => DB_PATH,
19
- EXE_AI_DIR: () => EXE_AI_DIR,
20
- LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
21
- MODELS_DIR: () => MODELS_DIR,
22
- loadConfig: () => loadConfig,
23
- loadConfigFrom: () => loadConfigFrom,
24
- loadConfigSync: () => loadConfigSync,
25
- migrateConfig: () => migrateConfig,
26
- saveConfig: () => saveConfig
27
- });
28
- import { readFile, writeFile, mkdir } from "fs/promises";
29
- import { readFileSync, existsSync, renameSync } from "fs";
30
- import path from "path";
31
- import os from "os";
32
- function resolveDataDir() {
33
- if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
34
- if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
35
- const newDir = path.join(os.homedir(), ".exe-os");
36
- const legacyDir = path.join(os.homedir(), ".exe-mem");
37
- if (!existsSync(newDir) && existsSync(legacyDir)) {
38
- try {
39
- renameSync(legacyDir, newDir);
40
- process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
41
- `);
42
- } catch {
43
- return legacyDir;
44
- }
45
- }
46
- return newDir;
47
- }
48
- function migrateLegacyConfig(raw) {
49
- if ("r2" in raw) {
50
- process.stderr.write(
51
- "[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"
52
- );
53
- delete raw.r2;
54
- }
55
- if ("syncIntervalMs" in raw) {
56
- delete raw.syncIntervalMs;
57
- }
58
- return raw;
59
- }
60
- function migrateConfig(raw) {
61
- const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
62
- let currentVersion = fromVersion;
63
- let migrated = false;
64
- if (currentVersion > CURRENT_CONFIG_VERSION) {
65
- return { config: raw, migrated: false, fromVersion };
66
- }
67
- for (const migration of CONFIG_MIGRATIONS) {
68
- if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
69
- raw = migration.migrate(raw);
70
- currentVersion = migration.to;
71
- migrated = true;
72
- }
73
- }
74
- return { config: raw, migrated, fromVersion };
75
- }
76
- function normalizeScalingRoadmap(raw) {
77
- const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
78
- const userRoadmap = raw.scalingRoadmap ?? {};
79
- const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
80
- if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
81
- userAuto.enabled = raw.rerankerEnabled;
82
- }
83
- raw.scalingRoadmap = {
84
- ...userRoadmap,
85
- rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
86
- };
87
- }
88
- function normalizeSessionLifecycle(raw) {
89
- const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
90
- const userSL = raw.sessionLifecycle ?? {};
91
- raw.sessionLifecycle = { ...defaultSL, ...userSL };
92
- }
93
- async function loadConfig() {
94
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
95
- await mkdir(dir, { recursive: true });
96
- const configPath = path.join(dir, "config.json");
97
- if (!existsSync(configPath)) {
98
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
99
- }
100
- const raw = await readFile(configPath, "utf-8");
101
- try {
102
- let parsed = JSON.parse(raw);
103
- parsed = migrateLegacyConfig(parsed);
104
- const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
105
- if (migrated) {
106
- process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
107
- `);
108
- try {
109
- await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
110
- } catch {
111
- }
112
- }
113
- normalizeScalingRoadmap(migratedCfg);
114
- normalizeSessionLifecycle(migratedCfg);
115
- const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
116
- if (config.dbPath.startsWith("~")) {
117
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
118
- }
119
- return config;
120
- } catch {
121
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
122
- }
123
- }
124
- function loadConfigSync() {
125
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
126
- const configPath = path.join(dir, "config.json");
127
- if (!existsSync(configPath)) {
128
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
129
- }
130
- try {
131
- const raw = readFileSync(configPath, "utf-8");
132
- let parsed = JSON.parse(raw);
133
- parsed = migrateLegacyConfig(parsed);
134
- const { config: migratedCfg } = migrateConfig(parsed);
135
- normalizeScalingRoadmap(migratedCfg);
136
- normalizeSessionLifecycle(migratedCfg);
137
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
138
- } catch {
139
- return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
140
- }
141
- }
142
- async function saveConfig(config) {
143
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
144
- await mkdir(dir, { recursive: true });
145
- const configPath = path.join(dir, "config.json");
146
- await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
147
- }
148
- async function loadConfigFrom(configPath) {
149
- const raw = await readFile(configPath, "utf-8");
150
- try {
151
- let parsed = JSON.parse(raw);
152
- parsed = migrateLegacyConfig(parsed);
153
- const { config: migratedCfg } = migrateConfig(parsed);
154
- normalizeScalingRoadmap(migratedCfg);
155
- normalizeSessionLifecycle(migratedCfg);
156
- return { ...DEFAULT_CONFIG, ...migratedCfg };
157
- } catch {
158
- return { ...DEFAULT_CONFIG };
159
- }
160
- }
161
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
162
- var init_config = __esm({
163
- "src/lib/config.ts"() {
164
- "use strict";
165
- EXE_AI_DIR = resolveDataDir();
166
- DB_PATH = path.join(EXE_AI_DIR, "memories.db");
167
- MODELS_DIR = path.join(EXE_AI_DIR, "models");
168
- CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
169
- LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
170
- CURRENT_CONFIG_VERSION = 1;
171
- DEFAULT_CONFIG = {
172
- config_version: CURRENT_CONFIG_VERSION,
173
- dbPath: DB_PATH,
174
- modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
175
- embeddingDim: 1024,
176
- batchSize: 20,
177
- flushIntervalMs: 1e4,
178
- autoIngestion: true,
179
- autoRetrieval: true,
180
- searchMode: "hybrid",
181
- hookSearchMode: "hybrid",
182
- fileGrepEnabled: true,
183
- splashEffect: true,
184
- consolidationEnabled: true,
185
- consolidationIntervalMs: 6 * 60 * 60 * 1e3,
186
- consolidationModel: "claude-haiku-4-5-20251001",
187
- consolidationMaxCallsPerRun: 20,
188
- selfQueryRouter: true,
189
- selfQueryModel: "claude-haiku-4-5-20251001",
190
- rerankerEnabled: true,
191
- scalingRoadmap: {
192
- rerankerAutoTrigger: {
193
- enabled: true,
194
- broadQueryMinCardinality: 5e4,
195
- fetchTopK: 150,
196
- returnTopK: 5
197
- }
198
- },
199
- graphRagEnabled: true,
200
- wikiEnabled: false,
201
- wikiUrl: "",
202
- wikiApiKey: "",
203
- wikiSyncIntervalMs: 30 * 60 * 1e3,
204
- wikiWorkspaceMapping: {
205
- exe: "Executive",
206
- yoshi: "Engineering",
207
- mari: "Marketing",
208
- tom: "Engineering",
209
- sasha: "Production"
210
- },
211
- wikiAutoUpdate: true,
212
- wikiAutoUpdateThreshold: 0.5,
213
- wikiAutoUpdateCreateNew: true,
214
- skillLearning: true,
215
- skillThreshold: 3,
216
- skillModel: "claude-haiku-4-5-20251001",
217
- exeHeartbeat: {
218
- enabled: true,
219
- intervalSeconds: 60,
220
- staleInProgressThresholdHours: 2
221
- },
222
- sessionLifecycle: {
223
- idleKillEnabled: true,
224
- idleKillTicksRequired: 3,
225
- idleKillIntercomAckWindowMs: 1e4,
226
- maxAutoInstances: 10
227
- }
228
- };
229
- CONFIG_MIGRATIONS = [
230
- {
231
- from: 0,
232
- to: 1,
233
- migrate: (cfg) => {
234
- cfg.config_version = 1;
235
- return cfg;
236
- }
237
- }
238
- ];
239
- }
240
- });
241
-
242
- // src/types/memory.ts
243
- var EMBEDDING_DIM;
244
- var init_memory = __esm({
245
- "src/types/memory.ts"() {
246
- "use strict";
247
- EMBEDDING_DIM = 1024;
248
- }
249
- });
250
-
251
- // src/lib/exe-daemon-client.ts
252
- import net from "net";
253
- import { spawn } from "child_process";
254
- import { randomUUID } from "crypto";
255
- import { existsSync as existsSync4, unlinkSync as unlinkSync2, readFileSync as readFileSync2, openSync, closeSync, statSync } from "fs";
256
- import path4 from "path";
257
- import { fileURLToPath } from "url";
258
- function handleData(chunk) {
259
- _buffer += chunk.toString();
260
- let newlineIdx;
261
- while ((newlineIdx = _buffer.indexOf("\n")) !== -1) {
262
- const line = _buffer.slice(0, newlineIdx).trim();
263
- _buffer = _buffer.slice(newlineIdx + 1);
264
- if (!line) continue;
265
- try {
266
- const response = JSON.parse(line);
267
- const entry = _pending.get(response.id);
268
- if (entry) {
269
- clearTimeout(entry.timer);
270
- _pending.delete(response.id);
271
- entry.resolve(response);
272
- }
273
- } catch {
274
- }
275
- }
276
- }
277
- function cleanupStaleFiles() {
278
- if (existsSync4(PID_PATH)) {
279
- try {
280
- const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
281
- if (pid > 0) {
282
- try {
283
- process.kill(pid, 0);
284
- return;
285
- } catch {
286
- }
287
- }
288
- } catch {
289
- }
290
- try {
291
- unlinkSync2(PID_PATH);
292
- } catch {
293
- }
294
- try {
295
- unlinkSync2(SOCKET_PATH);
296
- } catch {
297
- }
298
- }
299
- }
300
- function findPackageRoot() {
301
- let dir = path4.dirname(fileURLToPath(import.meta.url));
302
- const { root } = path4.parse(dir);
303
- while (dir !== root) {
304
- if (existsSync4(path4.join(dir, "package.json"))) return dir;
305
- dir = path4.dirname(dir);
306
- }
307
- return null;
308
- }
309
- function spawnDaemon() {
310
- const pkgRoot = findPackageRoot();
311
- if (!pkgRoot) {
312
- process.stderr.write("[exed-client] WARN: cannot find package root\n");
313
- return;
314
- }
315
- const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
316
- if (!existsSync4(daemonPath)) {
317
- process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
318
- `);
319
- return;
320
- }
321
- const resolvedPath = daemonPath;
322
- process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
323
- `);
324
- const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
325
- let stderrFd = "ignore";
326
- try {
327
- stderrFd = openSync(logPath, "a");
328
- } catch {
329
- }
330
- const child = spawn(process.execPath, [resolvedPath], {
331
- detached: true,
332
- stdio: ["ignore", "ignore", stderrFd],
333
- env: {
334
- ...process.env,
335
- EXE_DAEMON_SOCK: SOCKET_PATH,
336
- EXE_DAEMON_PID: PID_PATH
337
- }
338
- });
339
- child.unref();
340
- if (typeof stderrFd === "number") {
341
- try {
342
- closeSync(stderrFd);
343
- } catch {
344
- }
345
- }
346
- }
347
- function acquireSpawnLock() {
348
- try {
349
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
350
- closeSync(fd);
351
- return true;
352
- } catch {
353
- try {
354
- const stat = statSync(SPAWN_LOCK_PATH);
355
- if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
356
- try {
357
- unlinkSync2(SPAWN_LOCK_PATH);
358
- } catch {
359
- }
360
- try {
361
- const fd = openSync(SPAWN_LOCK_PATH, "wx");
362
- closeSync(fd);
363
- return true;
364
- } catch {
365
- }
366
- }
367
- } catch {
368
- }
369
- return false;
370
- }
371
- }
372
- function releaseSpawnLock() {
373
- try {
374
- unlinkSync2(SPAWN_LOCK_PATH);
375
- } catch {
376
- }
377
- }
378
- function connectToSocket() {
379
- return new Promise((resolve) => {
380
- if (_socket && _connected) {
381
- resolve(true);
382
- return;
383
- }
384
- const socket = net.createConnection({ path: SOCKET_PATH });
385
- const connectTimeout = setTimeout(() => {
386
- socket.destroy();
387
- resolve(false);
388
- }, 2e3);
389
- socket.on("connect", () => {
390
- clearTimeout(connectTimeout);
391
- _socket = socket;
392
- _connected = true;
393
- _buffer = "";
394
- socket.on("data", handleData);
395
- socket.on("close", () => {
396
- _connected = false;
397
- _socket = null;
398
- for (const [id, entry] of _pending) {
399
- clearTimeout(entry.timer);
400
- _pending.delete(id);
401
- entry.resolve({ error: "Connection closed" });
402
- }
403
- });
404
- socket.on("error", () => {
405
- _connected = false;
406
- _socket = null;
407
- });
408
- resolve(true);
409
- });
410
- socket.on("error", () => {
411
- clearTimeout(connectTimeout);
412
- resolve(false);
413
- });
414
- });
415
- }
416
- async function connectEmbedDaemon() {
417
- if (_socket && _connected) return true;
418
- if (await connectToSocket()) return true;
419
- if (acquireSpawnLock()) {
420
- try {
421
- cleanupStaleFiles();
422
- spawnDaemon();
423
- } finally {
424
- releaseSpawnLock();
425
- }
426
- }
427
- const start = Date.now();
428
- let delay = 100;
429
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
430
- await new Promise((r) => setTimeout(r, delay));
431
- if (await connectToSocket()) return true;
432
- delay = Math.min(delay * 2, 3e3);
433
- }
434
- return false;
435
- }
436
- function sendRequest(texts, priority) {
437
- return new Promise((resolve) => {
438
- if (!_socket || !_connected) {
439
- resolve({ error: "Not connected" });
440
- return;
441
- }
442
- const id = randomUUID();
443
- const timer = setTimeout(() => {
444
- _pending.delete(id);
445
- resolve({ error: "Request timeout" });
446
- }, REQUEST_TIMEOUT_MS);
447
- _pending.set(id, { resolve, timer });
448
- try {
449
- _socket.write(JSON.stringify({ id, texts, priority }) + "\n");
450
- } catch {
451
- clearTimeout(timer);
452
- _pending.delete(id);
453
- resolve({ error: "Write failed" });
454
- }
455
- });
456
- }
457
- async function pingDaemon() {
458
- if (!_socket || !_connected) return null;
459
- return new Promise((resolve) => {
460
- const id = randomUUID();
461
- const timer = setTimeout(() => {
462
- _pending.delete(id);
463
- resolve(null);
464
- }, 5e3);
465
- _pending.set(id, {
466
- resolve: (data) => {
467
- if (data.health) {
468
- resolve(data.health);
469
- } else {
470
- resolve(null);
471
- }
472
- },
473
- timer
474
- });
475
- try {
476
- _socket.write(JSON.stringify({ id, type: "health" }) + "\n");
477
- } catch {
478
- clearTimeout(timer);
479
- _pending.delete(id);
480
- resolve(null);
481
- }
482
- });
483
- }
484
- function killAndRespawnDaemon() {
485
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
486
- if (existsSync4(PID_PATH)) {
487
- try {
488
- const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
489
- if (pid > 0) {
490
- try {
491
- process.kill(pid, "SIGKILL");
492
- } catch {
493
- }
494
- }
495
- } catch {
496
- }
497
- }
498
- if (_socket) {
499
- _socket.destroy();
500
- _socket = null;
501
- }
502
- _connected = false;
503
- _buffer = "";
504
- try {
505
- unlinkSync2(PID_PATH);
506
- } catch {
507
- }
508
- try {
509
- unlinkSync2(SOCKET_PATH);
510
- } catch {
511
- }
512
- spawnDaemon();
513
- }
514
- async function embedViaClient(text, priority = "high") {
515
- if (!_connected && !await connectEmbedDaemon()) return null;
516
- _requestCount++;
517
- if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
518
- const health = await pingDaemon();
519
- if (!health) {
520
- process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
521
- `);
522
- killAndRespawnDaemon();
523
- const start = Date.now();
524
- let delay = 200;
525
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
526
- await new Promise((r) => setTimeout(r, delay));
527
- if (await connectToSocket()) break;
528
- delay = Math.min(delay * 2, 3e3);
529
- }
530
- if (!_connected) return null;
531
- }
532
- }
533
- const result = await sendRequest([text], priority);
534
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
535
- if (result.error) {
536
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
537
- `);
538
- killAndRespawnDaemon();
539
- const start = Date.now();
540
- let delay = 200;
541
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
542
- await new Promise((r) => setTimeout(r, delay));
543
- if (await connectToSocket()) break;
544
- delay = Math.min(delay * 2, 3e3);
545
- }
546
- if (!_connected) return null;
547
- const retry = await sendRequest([text], priority);
548
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
549
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
550
- `);
551
- }
552
- return null;
553
- }
554
- function disconnectClient() {
555
- if (_socket) {
556
- _socket.destroy();
557
- _socket = null;
558
- }
559
- _connected = false;
560
- _buffer = "";
561
- for (const [id, entry] of _pending) {
562
- clearTimeout(entry.timer);
563
- _pending.delete(id);
564
- entry.resolve({ error: "Client disconnected" });
565
- }
566
- }
567
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending;
568
- var init_exe_daemon_client = __esm({
569
- "src/lib/exe-daemon-client.ts"() {
570
- "use strict";
571
- init_config();
572
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
573
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
574
- SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
575
- SPAWN_LOCK_STALE_MS = 3e4;
576
- CONNECT_TIMEOUT_MS = 15e3;
577
- REQUEST_TIMEOUT_MS = 3e4;
578
- _socket = null;
579
- _connected = false;
580
- _buffer = "";
581
- _requestCount = 0;
582
- HEALTH_CHECK_INTERVAL = 100;
583
- _pending = /* @__PURE__ */ new Map();
584
- }
585
- });
586
-
587
- // src/lib/embedder.ts
588
- var embedder_exports = {};
589
- __export(embedder_exports, {
590
- disposeEmbedder: () => disposeEmbedder,
591
- embed: () => embed,
592
- embedDirect: () => embedDirect,
593
- getEmbedder: () => getEmbedder
594
- });
595
- async function getEmbedder() {
596
- const ok = await connectEmbedDaemon();
597
- if (!ok) {
598
- throw new Error(
599
- "Could not connect to embedding daemon. Ensure the model is installed (run /exe-setup)."
600
- );
601
- }
602
- }
603
- async function embed(text) {
604
- const priority = process.env.EXE_EMBED_PRIORITY === "low" ? "low" : "high";
605
- const vector = await embedViaClient(text, priority);
606
- if (!vector) {
607
- throw new Error(
608
- "Embedding failed: daemon unavailable. Run /exe-setup to verify model installation."
609
- );
610
- }
611
- if (vector.length !== EMBEDDING_DIM) {
612
- throw new Error(
613
- `Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}. Ensure the correct Jina v5-small Q4_K_M GGUF model is installed.`
614
- );
615
- }
616
- return vector;
617
- }
618
- async function disposeEmbedder() {
619
- disconnectClient();
620
- }
621
- async function embedDirect(text) {
622
- const llamaCpp = await import("node-llama-cpp");
623
- const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
624
- const { existsSync: existsSync6 } = await import("fs");
625
- const path6 = await import("path");
626
- const modelPath = path6.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
627
- if (!existsSync6(modelPath)) {
628
- throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
629
- }
630
- const llama = await llamaCpp.getLlama();
631
- const model = await llama.loadModel({ modelPath });
632
- const context = await model.createEmbeddingContext();
633
- try {
634
- const embedding = await context.getEmbeddingFor(text);
635
- const vector = Array.from(embedding.vector);
636
- if (vector.length !== EMBEDDING_DIM) {
637
- throw new Error(
638
- `Embedding dimension mismatch: expected ${EMBEDDING_DIM}, got ${vector.length}.`
639
- );
640
- }
641
- return vector;
642
- } finally {
643
- await context.dispose();
644
- await model.dispose();
645
- }
646
- }
647
- var init_embedder = __esm({
648
- "src/lib/embedder.ts"() {
649
- "use strict";
650
- init_memory();
651
- init_exe_daemon_client();
652
- }
653
- });
654
-
655
- // src/lib/setup-wizard.ts
656
- init_config();
657
- import crypto2 from "crypto";
658
- import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync } from "fs";
659
- import os2 from "os";
660
- import path5 from "path";
661
- import { createInterface } from "readline";
662
-
663
- // src/lib/keychain.ts
664
- import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod } from "fs/promises";
665
- import { existsSync as existsSync2 } from "fs";
666
- import path2 from "path";
667
- import crypto from "crypto";
668
- var SERVICE = "exe-mem";
669
- var ACCOUNT = "master-key";
670
- function getKeyDir() {
671
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path2.join(process.env.HOME ?? "/tmp", ".exe-os");
672
- }
673
- function getKeyPath() {
674
- return path2.join(getKeyDir(), "master.key");
675
- }
676
- async function tryKeytar() {
677
- try {
678
- return await import("keytar");
679
- } catch {
680
- return null;
681
- }
682
- }
683
- async function getMasterKey() {
684
- const keytar = await tryKeytar();
685
- if (keytar) {
686
- try {
687
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
688
- if (stored) {
689
- return Buffer.from(stored, "base64");
690
- }
691
- } catch {
692
- }
693
- }
694
- const keyPath = getKeyPath();
695
- if (!existsSync2(keyPath)) {
696
- return null;
697
- }
698
- try {
699
- const content = await readFile2(keyPath, "utf-8");
700
- return Buffer.from(content.trim(), "base64");
701
- } catch {
702
- return null;
703
- }
704
- }
705
- async function setMasterKey(key) {
706
- const b64 = key.toString("base64");
707
- const keytar = await tryKeytar();
708
- if (keytar) {
709
- try {
710
- await keytar.setPassword(SERVICE, ACCOUNT, b64);
711
- return;
712
- } catch {
713
- }
714
- }
715
- const dir = getKeyDir();
716
- await mkdir2(dir, { recursive: true });
717
- const keyPath = getKeyPath();
718
- await writeFile2(keyPath, b64 + "\n", "utf-8");
719
- await chmod(keyPath, 384);
720
- }
721
-
722
- // src/lib/model-downloader.ts
723
- import { createWriteStream, createReadStream, existsSync as existsSync3, unlinkSync, renameSync as renameSync2 } from "fs";
724
- import { mkdir as mkdir3 } from "fs/promises";
725
- import { createHash } from "crypto";
726
- import path3 from "path";
727
- var GGUF_URL = "https://huggingface.co/jinaai/jina-embeddings-v5-text-small-text-matching-GGUF/resolve/main/v5-small-text-matching-Q4_K_M.gguf";
728
- var EXPECTED_SHA256 = "738555454772b436632c6bad5891aeaa38d414bd7d7185107caeb3b2d8f2d860";
729
- var EXPECTED_SIZE = 396836064;
730
- var LOCAL_FILENAME = "jina-embeddings-v5-small-q4_k_m.gguf";
731
- async function downloadModel(opts) {
732
- const { destDir, onProgress, fetchFn = globalThis.fetch } = opts;
733
- const destPath = path3.join(destDir, LOCAL_FILENAME);
734
- const tmpPath = destPath + ".tmp";
735
- await mkdir3(destDir, { recursive: true });
736
- if (existsSync3(destPath)) {
737
- const hash2 = await fileHash(destPath);
738
- if (hash2 === EXPECTED_SHA256) {
739
- return destPath;
740
- }
741
- }
742
- if (existsSync3(tmpPath)) unlinkSync(tmpPath);
743
- const response = await fetchFn(GGUF_URL, { redirect: "follow" });
744
- if (!response.ok || !response.body) {
745
- throw new Error(`Download failed: HTTP ${response.status}`);
746
- }
747
- const contentLength = Number(response.headers.get("content-length") ?? EXPECTED_SIZE);
748
- let downloaded = 0;
749
- const hash = createHash("sha256");
750
- const fileStream = createWriteStream(tmpPath);
751
- const reader = response.body.getReader();
752
- try {
753
- while (true) {
754
- const { done, value } = await reader.read();
755
- if (done) break;
756
- if (!fileStream.write(value)) {
757
- await new Promise((resolve) => fileStream.once("drain", resolve));
758
- }
759
- hash.update(value);
760
- downloaded += value.byteLength;
761
- onProgress?.(downloaded, contentLength);
762
- }
763
- } finally {
764
- fileStream.end();
765
- await new Promise((resolve, reject) => {
766
- fileStream.on("finish", resolve);
767
- fileStream.on("error", reject);
768
- });
769
- }
770
- const actualHash = hash.digest("hex");
771
- if (actualHash !== EXPECTED_SHA256) {
772
- unlinkSync(tmpPath);
773
- throw new Error(
774
- `SHA256 mismatch: expected ${EXPECTED_SHA256}, got ${actualHash}`
775
- );
776
- }
777
- renameSync2(tmpPath, destPath);
778
- return destPath;
779
- }
780
- async function fileHash(filePath) {
781
- return new Promise((resolve, reject) => {
782
- const hash = createHash("sha256");
783
- const stream = createReadStream(filePath);
784
- stream.on("data", (chunk) => hash.update(chunk));
785
- stream.on("end", () => resolve(hash.digest("hex")));
786
- stream.on("error", reject);
787
- });
788
- }
789
-
790
- // src/lib/setup-wizard.ts
791
- function ask(rl, prompt) {
792
- return new Promise((resolve) => {
793
- const doAsk = () => {
794
- rl.question(prompt, (answer) => {
795
- resolve(answer.trim());
796
- });
797
- };
798
- doAsk();
799
- });
800
- }
801
- async function validateModel(log) {
802
- log("Validating model...");
803
- const { embedDirect: embedDirect2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
804
- const result = await embedDirect2("test embedding");
805
- if (result.length !== 1024) {
806
- throw new Error(`Model produced ${result.length}-dim embeddings, expected 1024`);
807
- }
808
- log("Model validation passed (1024-dim embeddings confirmed).");
809
- }
810
- async function runSetupWizard(opts = {}) {
811
- const {
812
- skipModel: skipModel2 = false,
813
- skipModelValidation = false,
814
- log = (msg) => process.stderr.write(msg + "\n")
815
- } = opts;
816
- const rl = opts.createReadline ? opts.createReadline() : createInterface({ input: process.stdin, output: process.stderr });
817
- try {
818
- log("");
819
- log("=== exe-os Setup ===");
820
- log("");
821
- if (existsSync5(LEGACY_LANCE_PATH)) {
822
- log("\u26A0 Found v1.0 LanceDB at ~/.exe-os/local.lance");
823
- log(" v1.1 uses libSQL (SQLite). Your existing memories are not automatically migrated.");
824
- log(" The old directory will not be modified or deleted.");
825
- log("");
826
- }
827
- const existingKey = await getMasterKey();
828
- if (existingKey) {
829
- log("Encryption key already exists \u2014 skipping generation.");
830
- } else {
831
- log("Generating 256-bit encryption key...");
832
- const key = crypto2.randomBytes(32);
833
- await setMasterKey(key);
834
- log("Encryption key generated and stored securely.");
835
- }
836
- log("");
837
- log("How do you want to sync?");
838
- log("");
839
- log(" 1. Exe Cloud (Recommended)");
840
- log(" Paste your API key from askexe.com. $5/mo.");
841
- log(" E2EE \u2014 we can't read your data.");
842
- log("");
843
- log(" 2. Local only");
844
- log(" No sync. Free forever.");
845
- log("");
846
- const syncChoice = await ask(rl, "Choose [1/2]: ");
847
- let cloudConfig;
848
- if (syncChoice === "1") {
849
- log("");
850
- const input = await ask(rl, "Paste API key (exe_sk_...) or email for new key: ");
851
- if (input.startsWith("exe_sk_")) {
852
- log("Validating...");
853
- try {
854
- const r = await fetch("https://sync.askexe.com/auth/verify", {
855
- headers: { Authorization: `Bearer ${input}` }
856
- });
857
- if (r.ok) {
858
- cloudConfig = { apiKey: input, endpoint: "https://sync.askexe.com" };
859
- log("Cloud sync active.");
860
- } else {
861
- log("Invalid key. Re-run /exe-setup to try again.");
862
- }
863
- } catch {
864
- cloudConfig = { apiKey: input, endpoint: "https://sync.askexe.com" };
865
- log("Saved. Sync activates when online.");
866
- }
867
- } else if (input.includes("@")) {
868
- log("Requesting new API key...");
869
- try {
870
- await fetch("https://sync.askexe.com/auth/resend", {
871
- method: "POST",
872
- headers: { "Content-Type": "application/json" },
873
- body: JSON.stringify({ email: input })
874
- });
875
- log("Check your email for the API key, then re-run /exe-setup.");
876
- } catch {
877
- log("Could not reach server. Try again later.");
878
- }
879
- } else {
880
- log("Skipping cloud sync. Re-run /exe-setup to configure.");
881
- }
882
- } else {
883
- log("Running in local-only mode.");
884
- }
885
- log("");
886
- if (!skipModel2) {
887
- log("Note: jina-embeddings-v5-text-small is licensed CC-BY-NC-4.0 (non-commercial)");
888
- log("");
889
- await downloadModel({
890
- destDir: MODELS_DIR,
891
- onProgress: (downloaded, total) => {
892
- const pct = (downloaded / total * 100).toFixed(1);
893
- const dlMB = (downloaded / 1e6).toFixed(0);
894
- const totalMB = (total / 1e6).toFixed(0);
895
- process.stderr.write(`\rDownloading model: ${pct}% (${dlMB}/${totalMB} MB)`);
896
- }
897
- });
898
- process.stderr.write("\n");
899
- log("Model downloaded and verified.");
900
- }
901
- if (!skipModel2 && !skipModelValidation) {
902
- await validateModel(log);
903
- }
904
- const config = await loadConfig();
905
- if (cloudConfig) {
906
- config.cloud = cloudConfig;
907
- }
908
- await saveConfig(config);
909
- log("");
910
- try {
911
- const claudeJsonPath = path5.join(os2.homedir(), ".claude.json");
912
- let claudeJson = {};
913
- try {
914
- claudeJson = JSON.parse(readFileSync3(claudeJsonPath, "utf8"));
915
- } catch {
916
- }
917
- if (!claudeJson.projects) claudeJson.projects = {};
918
- const projects = claudeJson.projects;
919
- for (const dir of [process.cwd(), os2.homedir()]) {
920
- if (!projects[dir]) projects[dir] = {};
921
- projects[dir].hasTrustDialogAccepted = true;
922
- }
923
- writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
924
- } catch {
925
- }
926
- log("=== Setup Complete ===");
927
- log("Database: " + config.dbPath);
928
- if (cloudConfig) {
929
- log("Sync: Exe Cloud (E2EE)");
930
- } else {
931
- log("Sync: local-only");
932
- }
933
- log("Encryption: SQLCipher (local) + AES-256-GCM (sync)");
934
- if (!skipModel2) {
935
- log("Model: " + LOCAL_FILENAME);
936
- }
937
- log("");
938
- } finally {
939
- rl.close();
940
- }
941
- }
942
-
943
- // src/bin/setup.ts
944
- var args = process.argv.slice(2);
945
- var skipModel = args.includes("--skip-model");
946
- try {
947
- await runSetupWizard({ skipModel });
948
- } catch (err) {
949
- console.error("Setup failed:", err instanceof Error ? err.message : String(err));
950
- process.exit(1);
951
- }