@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.
- package/README.md +178 -79
- package/package.json +1 -1
- package/dist/bin/backfill-responses.js +0 -1912
- package/dist/bin/backfill-vectors.js +0 -1642
- package/dist/bin/cleanup-stale-review-tasks.js +0 -1339
- package/dist/bin/cli.js +0 -18800
- package/dist/bin/exe-agent.js +0 -1858
- package/dist/bin/exe-assign.js +0 -1957
- package/dist/bin/exe-boot.js +0 -6460
- package/dist/bin/exe-call.js +0 -197
- package/dist/bin/exe-cloud.js +0 -850
- package/dist/bin/exe-dispatch.js +0 -1146
- package/dist/bin/exe-doctor.js +0 -1657
- package/dist/bin/exe-export-behaviors.js +0 -1494
- package/dist/bin/exe-forget.js +0 -1627
- package/dist/bin/exe-gateway.js +0 -7732
- package/dist/bin/exe-healthcheck.js +0 -207
- package/dist/bin/exe-heartbeat.js +0 -1647
- package/dist/bin/exe-kill.js +0 -1479
- package/dist/bin/exe-launch-agent.js +0 -1704
- package/dist/bin/exe-link.js +0 -192
- package/dist/bin/exe-new-employee.js +0 -852
- package/dist/bin/exe-pending-messages.js +0 -1446
- package/dist/bin/exe-pending-notifications.js +0 -1321
- package/dist/bin/exe-pending-reviews.js +0 -1468
- package/dist/bin/exe-repo-drift.js +0 -95
- package/dist/bin/exe-review.js +0 -1590
- package/dist/bin/exe-search.js +0 -2651
- package/dist/bin/exe-session-cleanup.js +0 -3173
- package/dist/bin/exe-settings.js +0 -354
- package/dist/bin/exe-status.js +0 -1532
- package/dist/bin/exe-team.js +0 -1324
- package/dist/bin/git-sweep.js +0 -2185
- package/dist/bin/graph-backfill.js +0 -1968
- package/dist/bin/graph-export.js +0 -1604
- package/dist/bin/install.js +0 -656
- package/dist/bin/list-providers.js +0 -140
- package/dist/bin/scan-tasks.js +0 -1820
- package/dist/bin/setup.js +0 -951
- package/dist/bin/shard-migrate.js +0 -1494
- package/dist/bin/update.js +0 -95
- package/dist/bin/wiki-sync.js +0 -1514
- package/dist/gateway/index.js +0 -8848
- package/dist/hooks/bug-report-worker.js +0 -2743
- package/dist/hooks/commit-complete.js +0 -2108
- package/dist/hooks/error-recall.js +0 -2861
- package/dist/hooks/exe-heartbeat-hook.js +0 -232
- package/dist/hooks/ingest-worker.js +0 -4793
- package/dist/hooks/ingest.js +0 -684
- package/dist/hooks/instructions-loaded.js +0 -1880
- package/dist/hooks/notification.js +0 -1726
- package/dist/hooks/post-compact.js +0 -1751
- package/dist/hooks/pre-compact.js +0 -1746
- package/dist/hooks/pre-tool-use.js +0 -2191
- package/dist/hooks/prompt-ingest-worker.js +0 -2126
- package/dist/hooks/prompt-submit.js +0 -4693
- package/dist/hooks/response-ingest-worker.js +0 -1936
- package/dist/hooks/session-end.js +0 -1752
- package/dist/hooks/session-start.js +0 -2795
- package/dist/hooks/stop.js +0 -1835
- package/dist/hooks/subagent-stop.js +0 -1726
- package/dist/hooks/summary-worker.js +0 -2661
- package/dist/index.js +0 -11834
- package/dist/lib/cloud-sync.js +0 -495
- package/dist/lib/config.js +0 -222
- package/dist/lib/consolidation.js +0 -476
- package/dist/lib/crypto.js +0 -51
- package/dist/lib/database.js +0 -730
- package/dist/lib/device-registry.js +0 -900
- package/dist/lib/embedder.js +0 -632
- package/dist/lib/employee-templates.js +0 -543
- package/dist/lib/employees.js +0 -177
- package/dist/lib/error-detector.js +0 -156
- package/dist/lib/exe-daemon-client.js +0 -451
- package/dist/lib/exe-daemon.js +0 -8285
- package/dist/lib/file-grep.js +0 -199
- package/dist/lib/hybrid-search.js +0 -1819
- package/dist/lib/identity-templates.js +0 -320
- package/dist/lib/identity.js +0 -223
- package/dist/lib/keychain.js +0 -145
- package/dist/lib/license.js +0 -377
- package/dist/lib/messaging.js +0 -1376
- package/dist/lib/reminders.js +0 -63
- package/dist/lib/schedules.js +0 -1396
- package/dist/lib/session-registry.js +0 -52
- package/dist/lib/skill-learning.js +0 -477
- package/dist/lib/status-brief.js +0 -235
- package/dist/lib/store.js +0 -1551
- package/dist/lib/task-router.js +0 -62
- package/dist/lib/tasks.js +0 -2456
- package/dist/lib/tmux-routing.js +0 -2836
- package/dist/lib/tmux-status.js +0 -261
- package/dist/lib/tmux-transport.js +0 -83
- package/dist/lib/transport.js +0 -128
- package/dist/lib/ws-auth.js +0 -19
- package/dist/lib/ws-client.js +0 -160
- package/dist/mcp/server.js +0 -10538
- package/dist/mcp/tools/complete-reminder.js +0 -67
- package/dist/mcp/tools/create-reminder.js +0 -52
- package/dist/mcp/tools/create-task.js +0 -1853
- package/dist/mcp/tools/deactivate-behavior.js +0 -263
- package/dist/mcp/tools/list-reminders.js +0 -62
- package/dist/mcp/tools/list-tasks.js +0 -463
- package/dist/mcp/tools/send-message.js +0 -1382
- package/dist/mcp/tools/update-task.js +0 -1692
- package/dist/runtime/index.js +0 -6809
- 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
|
-
}
|