@askexenow/exe-os 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +953 -105
- package/dist/bin/backfill-responses.js +952 -104
- package/dist/bin/backfill-vectors.js +956 -108
- package/dist/bin/cleanup-stale-review-tasks.js +802 -58
- package/dist/bin/cli.js +2292 -1070
- package/dist/bin/exe-agent-config.js +157 -101
- package/dist/bin/exe-agent.js +55 -29
- package/dist/bin/exe-assign.js +940 -92
- package/dist/bin/exe-boot.js +1424 -442
- package/dist/bin/exe-call.js +240 -141
- package/dist/bin/exe-cloud.js +198 -70
- package/dist/bin/exe-dispatch.js +951 -192
- package/dist/bin/exe-doctor.js +791 -51
- package/dist/bin/exe-export-behaviors.js +790 -42
- package/dist/bin/exe-forget.js +771 -31
- package/dist/bin/exe-gateway.js +1592 -521
- package/dist/bin/exe-heartbeat.js +850 -109
- package/dist/bin/exe-kill.js +783 -35
- package/dist/bin/exe-launch-agent.js +1030 -107
- package/dist/bin/exe-link.js +916 -110
- package/dist/bin/exe-new-employee.js +526 -217
- package/dist/bin/exe-pending-messages.js +1046 -62
- package/dist/bin/exe-pending-notifications.js +1318 -111
- package/dist/bin/exe-pending-reviews.js +1040 -72
- package/dist/bin/exe-rename.js +772 -59
- package/dist/bin/exe-review.js +772 -32
- package/dist/bin/exe-search.js +982 -128
- package/dist/bin/exe-session-cleanup.js +1180 -306
- package/dist/bin/exe-settings.js +185 -105
- package/dist/bin/exe-start-codex.js +886 -132
- package/dist/bin/exe-start-opencode.js +873 -119
- package/dist/bin/exe-status.js +803 -59
- package/dist/bin/exe-team.js +772 -32
- package/dist/bin/git-sweep.js +1046 -223
- package/dist/bin/graph-backfill.js +779 -31
- package/dist/bin/graph-export.js +785 -37
- package/dist/bin/install.js +632 -200
- package/dist/bin/scan-tasks.js +1055 -232
- package/dist/bin/setup.js +1419 -320
- package/dist/bin/shard-migrate.js +783 -35
- package/dist/bin/update.js +138 -49
- package/dist/bin/wiki-sync.js +782 -34
- package/dist/gateway/index.js +1444 -449
- package/dist/hooks/bug-report-worker.js +1141 -269
- package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
- package/dist/hooks/commit-complete.js +1044 -221
- package/dist/hooks/error-recall.js +989 -135
- package/dist/hooks/exe-heartbeat-hook.js +99 -75
- package/dist/hooks/ingest-worker.js +4176 -3226
- package/dist/hooks/ingest.js +920 -168
- package/dist/hooks/instructions-loaded.js +874 -70
- package/dist/hooks/notification.js +860 -56
- package/dist/hooks/post-compact.js +881 -73
- package/dist/hooks/pre-compact.js +1050 -227
- package/dist/hooks/pre-tool-use.js +1084 -159
- package/dist/hooks/prompt-ingest-worker.js +1089 -164
- package/dist/hooks/prompt-submit.js +1469 -515
- package/dist/hooks/response-ingest-worker.js +1104 -179
- package/dist/hooks/session-end.js +1085 -251
- package/dist/hooks/session-start.js +1241 -231
- package/dist/hooks/stop.js +935 -109
- package/dist/hooks/subagent-stop.js +881 -73
- package/dist/hooks/summary-worker.js +1323 -307
- package/dist/index.js +1449 -452
- package/dist/lib/agent-config.js +28 -6
- package/dist/lib/cloud-sync.js +909 -115
- package/dist/lib/config.js +30 -10
- package/dist/lib/consolidation.js +42 -9
- package/dist/lib/database.js +739 -33
- package/dist/lib/db-daemon-client.js +73 -19
- package/dist/lib/db.js +2359 -0
- package/dist/lib/device-registry.js +760 -47
- package/dist/lib/embedder.js +201 -73
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +290 -86
- package/dist/lib/exe-daemon-client.js +187 -83
- package/dist/lib/exe-daemon.js +1696 -616
- package/dist/lib/hybrid-search.js +982 -128
- package/dist/lib/identity.js +43 -13
- package/dist/lib/license.js +133 -48
- package/dist/lib/messaging.js +167 -80
- package/dist/lib/reminders.js +35 -5
- package/dist/lib/schedules.js +772 -32
- package/dist/lib/skill-learning.js +54 -7
- package/dist/lib/store.js +779 -31
- package/dist/lib/task-router.js +94 -73
- package/dist/lib/tasks.js +298 -225
- package/dist/lib/tmux-routing.js +246 -172
- package/dist/lib/token-spend.js +52 -14
- package/dist/mcp/server.js +2893 -850
- package/dist/mcp/tools/complete-reminder.js +35 -5
- package/dist/mcp/tools/create-reminder.js +35 -5
- package/dist/mcp/tools/create-task.js +507 -323
- package/dist/mcp/tools/deactivate-behavior.js +40 -10
- package/dist/mcp/tools/list-reminders.js +35 -5
- package/dist/mcp/tools/list-tasks.js +277 -104
- package/dist/mcp/tools/send-message.js +129 -56
- package/dist/mcp/tools/update-task.js +1864 -188
- package/dist/runtime/index.js +1083 -259
- package/dist/tui/App.js +1501 -434
- package/package.json +3 -2
package/dist/bin/setup.js
CHANGED
|
@@ -15,6 +15,44 @@ var __export = (target, all) => {
|
|
|
15
15
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
// src/lib/secure-files.ts
|
|
19
|
+
import { chmodSync, existsSync, mkdirSync } from "fs";
|
|
20
|
+
import { chmod, mkdir } from "fs/promises";
|
|
21
|
+
async function ensurePrivateDir(dirPath) {
|
|
22
|
+
await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
23
|
+
try {
|
|
24
|
+
await chmod(dirPath, PRIVATE_DIR_MODE);
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function ensurePrivateDirSync(dirPath) {
|
|
29
|
+
mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
30
|
+
try {
|
|
31
|
+
chmodSync(dirPath, PRIVATE_DIR_MODE);
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function enforcePrivateFile(filePath) {
|
|
36
|
+
try {
|
|
37
|
+
await chmod(filePath, PRIVATE_FILE_MODE);
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function enforcePrivateFileSync(filePath) {
|
|
42
|
+
try {
|
|
43
|
+
if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
|
|
48
|
+
var init_secure_files = __esm({
|
|
49
|
+
"src/lib/secure-files.ts"() {
|
|
50
|
+
"use strict";
|
|
51
|
+
PRIVATE_DIR_MODE = 448;
|
|
52
|
+
PRIVATE_FILE_MODE = 384;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
18
56
|
// src/lib/config.ts
|
|
19
57
|
var config_exports = {};
|
|
20
58
|
__export(config_exports, {
|
|
@@ -31,8 +69,8 @@ __export(config_exports, {
|
|
|
31
69
|
migrateConfig: () => migrateConfig,
|
|
32
70
|
saveConfig: () => saveConfig
|
|
33
71
|
});
|
|
34
|
-
import { readFile, writeFile
|
|
35
|
-
import { readFileSync, existsSync, renameSync } from "fs";
|
|
72
|
+
import { readFile, writeFile } from "fs/promises";
|
|
73
|
+
import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
|
|
36
74
|
import path from "path";
|
|
37
75
|
import os from "os";
|
|
38
76
|
function resolveDataDir() {
|
|
@@ -40,7 +78,7 @@ function resolveDataDir() {
|
|
|
40
78
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
41
79
|
const newDir = path.join(os.homedir(), ".exe-os");
|
|
42
80
|
const legacyDir = path.join(os.homedir(), ".exe-mem");
|
|
43
|
-
if (!
|
|
81
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
44
82
|
try {
|
|
45
83
|
renameSync(legacyDir, newDir);
|
|
46
84
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
@@ -103,9 +141,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
103
141
|
}
|
|
104
142
|
async function loadConfig() {
|
|
105
143
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
106
|
-
await
|
|
144
|
+
await ensurePrivateDir(dir);
|
|
107
145
|
const configPath = path.join(dir, "config.json");
|
|
108
|
-
if (!
|
|
146
|
+
if (!existsSync2(configPath)) {
|
|
109
147
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
110
148
|
}
|
|
111
149
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -118,6 +156,7 @@ async function loadConfig() {
|
|
|
118
156
|
`);
|
|
119
157
|
try {
|
|
120
158
|
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
159
|
+
await enforcePrivateFile(configPath);
|
|
121
160
|
} catch {
|
|
122
161
|
}
|
|
123
162
|
}
|
|
@@ -136,7 +175,7 @@ async function loadConfig() {
|
|
|
136
175
|
function loadConfigSync() {
|
|
137
176
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
138
177
|
const configPath = path.join(dir, "config.json");
|
|
139
|
-
if (!
|
|
178
|
+
if (!existsSync2(configPath)) {
|
|
140
179
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
141
180
|
}
|
|
142
181
|
try {
|
|
@@ -154,12 +193,10 @@ function loadConfigSync() {
|
|
|
154
193
|
}
|
|
155
194
|
async function saveConfig(config) {
|
|
156
195
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
157
|
-
await
|
|
196
|
+
await ensurePrivateDir(dir);
|
|
158
197
|
const configPath = path.join(dir, "config.json");
|
|
159
198
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
160
|
-
|
|
161
|
-
await chmod(configPath, 384);
|
|
162
|
-
}
|
|
199
|
+
await enforcePrivateFile(configPath);
|
|
163
200
|
}
|
|
164
201
|
async function loadConfigFrom(configPath) {
|
|
165
202
|
const raw = await readFile(configPath, "utf-8");
|
|
@@ -179,6 +216,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
|
|
|
179
216
|
var init_config = __esm({
|
|
180
217
|
"src/lib/config.ts"() {
|
|
181
218
|
"use strict";
|
|
219
|
+
init_secure_files();
|
|
182
220
|
EXE_AI_DIR = resolveDataDir();
|
|
183
221
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
184
222
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
@@ -265,7 +303,7 @@ __export(keychain_exports, {
|
|
|
265
303
|
setMasterKey: () => setMasterKey
|
|
266
304
|
});
|
|
267
305
|
import { readFile as readFile2, writeFile as writeFile2, unlink, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
|
|
268
|
-
import { existsSync as
|
|
306
|
+
import { existsSync as existsSync3 } from "fs";
|
|
269
307
|
import path2 from "path";
|
|
270
308
|
import os2 from "os";
|
|
271
309
|
function getKeyDir() {
|
|
@@ -293,7 +331,7 @@ async function getMasterKey() {
|
|
|
293
331
|
}
|
|
294
332
|
}
|
|
295
333
|
const keyPath = getKeyPath();
|
|
296
|
-
if (!
|
|
334
|
+
if (!existsSync3(keyPath)) {
|
|
297
335
|
process.stderr.write(
|
|
298
336
|
`[keychain] Key not found at ${keyPath} (HOME=${os2.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
299
337
|
`
|
|
@@ -336,7 +374,7 @@ async function deleteMasterKey() {
|
|
|
336
374
|
}
|
|
337
375
|
}
|
|
338
376
|
const keyPath = getKeyPath();
|
|
339
|
-
if (
|
|
377
|
+
if (existsSync3(keyPath)) {
|
|
340
378
|
await unlink(keyPath);
|
|
341
379
|
}
|
|
342
380
|
}
|
|
@@ -387,13 +425,50 @@ var init_memory = __esm({
|
|
|
387
425
|
}
|
|
388
426
|
});
|
|
389
427
|
|
|
428
|
+
// src/lib/daemon-auth.ts
|
|
429
|
+
import crypto from "crypto";
|
|
430
|
+
import path4 from "path";
|
|
431
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
432
|
+
function normalizeToken(token) {
|
|
433
|
+
if (!token) return null;
|
|
434
|
+
const trimmed = token.trim();
|
|
435
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
436
|
+
}
|
|
437
|
+
function readDaemonToken() {
|
|
438
|
+
try {
|
|
439
|
+
if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
|
|
440
|
+
return normalizeToken(readFileSync2(DAEMON_TOKEN_PATH, "utf8"));
|
|
441
|
+
} catch {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function ensureDaemonToken(seed) {
|
|
446
|
+
const existing = readDaemonToken();
|
|
447
|
+
if (existing) return existing;
|
|
448
|
+
const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
|
|
449
|
+
ensurePrivateDirSync(EXE_AI_DIR);
|
|
450
|
+
writeFileSync(DAEMON_TOKEN_PATH, `${token}
|
|
451
|
+
`, "utf8");
|
|
452
|
+
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
453
|
+
return token;
|
|
454
|
+
}
|
|
455
|
+
var DAEMON_TOKEN_PATH;
|
|
456
|
+
var init_daemon_auth = __esm({
|
|
457
|
+
"src/lib/daemon-auth.ts"() {
|
|
458
|
+
"use strict";
|
|
459
|
+
init_config();
|
|
460
|
+
init_secure_files();
|
|
461
|
+
DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
390
465
|
// src/lib/exe-daemon-client.ts
|
|
391
466
|
import net from "net";
|
|
392
467
|
import os3 from "os";
|
|
393
468
|
import { spawn } from "child_process";
|
|
394
469
|
import { randomUUID } from "crypto";
|
|
395
|
-
import { existsSync as
|
|
396
|
-
import
|
|
470
|
+
import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
|
|
471
|
+
import path5 from "path";
|
|
397
472
|
import { fileURLToPath } from "url";
|
|
398
473
|
function handleData(chunk) {
|
|
399
474
|
_buffer += chunk.toString();
|
|
@@ -421,9 +496,9 @@ function handleData(chunk) {
|
|
|
421
496
|
}
|
|
422
497
|
}
|
|
423
498
|
function cleanupStaleFiles() {
|
|
424
|
-
if (
|
|
499
|
+
if (existsSync6(PID_PATH)) {
|
|
425
500
|
try {
|
|
426
|
-
const pid = parseInt(
|
|
501
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
427
502
|
if (pid > 0) {
|
|
428
503
|
try {
|
|
429
504
|
process.kill(pid, 0);
|
|
@@ -444,11 +519,11 @@ function cleanupStaleFiles() {
|
|
|
444
519
|
}
|
|
445
520
|
}
|
|
446
521
|
function findPackageRoot() {
|
|
447
|
-
let dir =
|
|
448
|
-
const { root } =
|
|
522
|
+
let dir = path5.dirname(fileURLToPath(import.meta.url));
|
|
523
|
+
const { root } = path5.parse(dir);
|
|
449
524
|
while (dir !== root) {
|
|
450
|
-
if (
|
|
451
|
-
dir =
|
|
525
|
+
if (existsSync6(path5.join(dir, "package.json"))) return dir;
|
|
526
|
+
dir = path5.dirname(dir);
|
|
452
527
|
}
|
|
453
528
|
return null;
|
|
454
529
|
}
|
|
@@ -474,16 +549,17 @@ function spawnDaemon() {
|
|
|
474
549
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
475
550
|
return;
|
|
476
551
|
}
|
|
477
|
-
const daemonPath =
|
|
478
|
-
if (!
|
|
552
|
+
const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
553
|
+
if (!existsSync6(daemonPath)) {
|
|
479
554
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
480
555
|
`);
|
|
481
556
|
return;
|
|
482
557
|
}
|
|
483
558
|
const resolvedPath = daemonPath;
|
|
559
|
+
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
484
560
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
485
561
|
`);
|
|
486
|
-
const logPath =
|
|
562
|
+
const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
|
|
487
563
|
let stderrFd = "ignore";
|
|
488
564
|
try {
|
|
489
565
|
stderrFd = openSync(logPath, "a");
|
|
@@ -501,7 +577,8 @@ function spawnDaemon() {
|
|
|
501
577
|
TMUX_PANE: void 0,
|
|
502
578
|
// Prevents resolveExeSession() from scoping to one session
|
|
503
579
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
504
|
-
EXE_DAEMON_PID: PID_PATH
|
|
580
|
+
EXE_DAEMON_PID: PID_PATH,
|
|
581
|
+
[DAEMON_TOKEN_ENV]: daemonToken
|
|
505
582
|
}
|
|
506
583
|
});
|
|
507
584
|
child.unref();
|
|
@@ -611,13 +688,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
|
611
688
|
return;
|
|
612
689
|
}
|
|
613
690
|
const id = randomUUID();
|
|
691
|
+
const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
|
|
614
692
|
const timer = setTimeout(() => {
|
|
615
693
|
_pending.delete(id);
|
|
616
694
|
resolve({ error: "Request timeout" });
|
|
617
695
|
}, timeoutMs);
|
|
618
696
|
_pending.set(id, { resolve, timer });
|
|
619
697
|
try {
|
|
620
|
-
_socket.write(JSON.stringify({ id, ...payload }) + "\n");
|
|
698
|
+
_socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
|
|
621
699
|
} catch {
|
|
622
700
|
clearTimeout(timer);
|
|
623
701
|
_pending.delete(id);
|
|
@@ -634,74 +712,123 @@ async function pingDaemon() {
|
|
|
634
712
|
return null;
|
|
635
713
|
}
|
|
636
714
|
function killAndRespawnDaemon() {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
715
|
+
if (!acquireSpawnLock()) {
|
|
716
|
+
process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
|
|
717
|
+
if (_socket) {
|
|
718
|
+
_socket.destroy();
|
|
719
|
+
_socket = null;
|
|
720
|
+
}
|
|
721
|
+
_connected = false;
|
|
722
|
+
_buffer = "";
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
727
|
+
if (existsSync6(PID_PATH)) {
|
|
728
|
+
try {
|
|
729
|
+
const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
|
|
730
|
+
if (pid > 0) {
|
|
731
|
+
try {
|
|
732
|
+
process.kill(pid, "SIGKILL");
|
|
733
|
+
} catch {
|
|
734
|
+
}
|
|
645
735
|
}
|
|
736
|
+
} catch {
|
|
646
737
|
}
|
|
738
|
+
}
|
|
739
|
+
if (_socket) {
|
|
740
|
+
_socket.destroy();
|
|
741
|
+
_socket = null;
|
|
742
|
+
}
|
|
743
|
+
_connected = false;
|
|
744
|
+
_buffer = "";
|
|
745
|
+
try {
|
|
746
|
+
unlinkSync2(PID_PATH);
|
|
647
747
|
} catch {
|
|
648
748
|
}
|
|
749
|
+
try {
|
|
750
|
+
unlinkSync2(SOCKET_PATH);
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
753
|
+
spawnDaemon();
|
|
754
|
+
} finally {
|
|
755
|
+
releaseSpawnLock();
|
|
649
756
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
_socket = null;
|
|
653
|
-
}
|
|
654
|
-
_connected = false;
|
|
655
|
-
_buffer = "";
|
|
757
|
+
}
|
|
758
|
+
function isDaemonTooYoung() {
|
|
656
759
|
try {
|
|
657
|
-
|
|
760
|
+
const stat = statSync(PID_PATH);
|
|
761
|
+
return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
|
|
658
762
|
} catch {
|
|
763
|
+
return false;
|
|
659
764
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
765
|
+
}
|
|
766
|
+
async function retryThenRestart(doRequest, label) {
|
|
767
|
+
const result = await doRequest();
|
|
768
|
+
if (!result.error) {
|
|
769
|
+
_consecutiveFailures = 0;
|
|
770
|
+
return result;
|
|
771
|
+
}
|
|
772
|
+
_consecutiveFailures++;
|
|
773
|
+
for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
|
|
774
|
+
const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
|
|
775
|
+
process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
|
|
776
|
+
`);
|
|
777
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
778
|
+
if (!_connected) {
|
|
779
|
+
if (!await connectToSocket()) continue;
|
|
780
|
+
}
|
|
781
|
+
const retry = await doRequest();
|
|
782
|
+
if (!retry.error) {
|
|
783
|
+
_consecutiveFailures = 0;
|
|
784
|
+
return retry;
|
|
785
|
+
}
|
|
786
|
+
_consecutiveFailures++;
|
|
787
|
+
}
|
|
788
|
+
if (isDaemonTooYoung()) {
|
|
789
|
+
process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
|
|
790
|
+
`);
|
|
791
|
+
return { error: result.error };
|
|
663
792
|
}
|
|
664
|
-
|
|
793
|
+
process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
|
|
794
|
+
`);
|
|
795
|
+
killAndRespawnDaemon();
|
|
796
|
+
const start = Date.now();
|
|
797
|
+
let delay2 = 200;
|
|
798
|
+
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
799
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
800
|
+
if (await connectToSocket()) break;
|
|
801
|
+
delay2 = Math.min(delay2 * 2, 3e3);
|
|
802
|
+
}
|
|
803
|
+
if (!_connected) return { error: "Daemon restart failed" };
|
|
804
|
+
const final = await doRequest();
|
|
805
|
+
if (!final.error) _consecutiveFailures = 0;
|
|
806
|
+
return final;
|
|
665
807
|
}
|
|
666
808
|
async function embedViaClient(text, priority = "high") {
|
|
667
809
|
if (!_connected && !await connectEmbedDaemon()) return null;
|
|
668
810
|
_requestCount++;
|
|
669
811
|
if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
|
|
670
812
|
const health = await pingDaemon();
|
|
671
|
-
if (!health) {
|
|
813
|
+
if (!health && !isDaemonTooYoung()) {
|
|
672
814
|
process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
|
|
673
815
|
`);
|
|
674
816
|
killAndRespawnDaemon();
|
|
675
817
|
const start = Date.now();
|
|
676
|
-
let
|
|
818
|
+
let d = 200;
|
|
677
819
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
678
|
-
await new Promise((r) => setTimeout(r,
|
|
820
|
+
await new Promise((r) => setTimeout(r, d));
|
|
679
821
|
if (await connectToSocket()) break;
|
|
680
|
-
|
|
822
|
+
d = Math.min(d * 2, 3e3);
|
|
681
823
|
}
|
|
682
824
|
if (!_connected) return null;
|
|
683
825
|
}
|
|
684
826
|
}
|
|
685
|
-
const result = await
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
killAndRespawnDaemon();
|
|
691
|
-
const start = Date.now();
|
|
692
|
-
let delay2 = 200;
|
|
693
|
-
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
694
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
695
|
-
if (await connectToSocket()) break;
|
|
696
|
-
delay2 = Math.min(delay2 * 2, 3e3);
|
|
697
|
-
}
|
|
698
|
-
if (!_connected) return null;
|
|
699
|
-
const retry = await sendRequest([text], priority);
|
|
700
|
-
if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
|
|
701
|
-
process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
|
|
702
|
-
`);
|
|
703
|
-
}
|
|
704
|
-
return null;
|
|
827
|
+
const result = await retryThenRestart(
|
|
828
|
+
() => sendRequest([text], priority),
|
|
829
|
+
"Embed"
|
|
830
|
+
);
|
|
831
|
+
return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
|
|
705
832
|
}
|
|
706
833
|
function disconnectClient() {
|
|
707
834
|
if (_socket) {
|
|
@@ -719,22 +846,28 @@ function disconnectClient() {
|
|
|
719
846
|
function isClientConnected() {
|
|
720
847
|
return _connected;
|
|
721
848
|
}
|
|
722
|
-
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, MAX_BUFFER;
|
|
849
|
+
var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
|
|
723
850
|
var init_exe_daemon_client = __esm({
|
|
724
851
|
"src/lib/exe-daemon-client.ts"() {
|
|
725
852
|
"use strict";
|
|
726
853
|
init_config();
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
854
|
+
init_daemon_auth();
|
|
855
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
|
|
856
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
|
|
857
|
+
SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
730
858
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
731
859
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
732
860
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
861
|
+
DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
|
|
733
862
|
_socket = null;
|
|
734
863
|
_connected = false;
|
|
735
864
|
_buffer = "";
|
|
736
865
|
_requestCount = 0;
|
|
866
|
+
_consecutiveFailures = 0;
|
|
737
867
|
HEALTH_CHECK_INTERVAL = 100;
|
|
868
|
+
MAX_RETRIES_BEFORE_RESTART = 3;
|
|
869
|
+
RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
|
|
870
|
+
MIN_DAEMON_AGE_MS = 3e4;
|
|
738
871
|
_pending = /* @__PURE__ */ new Map();
|
|
739
872
|
MAX_BUFFER = 1e7;
|
|
740
873
|
}
|
|
@@ -777,10 +910,10 @@ async function disposeEmbedder() {
|
|
|
777
910
|
async function embedDirect(text) {
|
|
778
911
|
const llamaCpp = await import("node-llama-cpp");
|
|
779
912
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
780
|
-
const { existsSync:
|
|
781
|
-
const
|
|
782
|
-
const modelPath =
|
|
783
|
-
if (!
|
|
913
|
+
const { existsSync: existsSync16 } = await import("fs");
|
|
914
|
+
const path16 = await import("path");
|
|
915
|
+
const modelPath = path16.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
916
|
+
if (!existsSync16(modelPath)) {
|
|
784
917
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
785
918
|
}
|
|
786
919
|
const llama = await llamaCpp.getLlama();
|
|
@@ -825,9 +958,12 @@ __export(license_exports, {
|
|
|
825
958
|
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
826
959
|
validateLicense: () => validateLicense
|
|
827
960
|
});
|
|
828
|
-
import { readFileSync as
|
|
961
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
829
962
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
830
|
-
import
|
|
963
|
+
import { createRequire } from "module";
|
|
964
|
+
import { pathToFileURL } from "url";
|
|
965
|
+
import os4 from "os";
|
|
966
|
+
import path6 from "path";
|
|
831
967
|
import { jwtVerify, importSPKI } from "jose";
|
|
832
968
|
async function fetchRetry(url, init) {
|
|
833
969
|
try {
|
|
@@ -838,37 +974,37 @@ async function fetchRetry(url, init) {
|
|
|
838
974
|
}
|
|
839
975
|
}
|
|
840
976
|
function loadDeviceId() {
|
|
841
|
-
const deviceJsonPath =
|
|
977
|
+
const deviceJsonPath = path6.join(EXE_AI_DIR, "device.json");
|
|
842
978
|
try {
|
|
843
|
-
if (
|
|
844
|
-
const data = JSON.parse(
|
|
979
|
+
if (existsSync7(deviceJsonPath)) {
|
|
980
|
+
const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
|
|
845
981
|
if (data.deviceId) return data.deviceId;
|
|
846
982
|
}
|
|
847
983
|
} catch {
|
|
848
984
|
}
|
|
849
985
|
try {
|
|
850
|
-
if (
|
|
851
|
-
const id2 =
|
|
986
|
+
if (existsSync7(DEVICE_ID_PATH)) {
|
|
987
|
+
const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
|
|
852
988
|
if (id2) return id2;
|
|
853
989
|
}
|
|
854
990
|
} catch {
|
|
855
991
|
}
|
|
856
992
|
const id = randomUUID2();
|
|
857
|
-
|
|
858
|
-
|
|
993
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
994
|
+
writeFileSync2(DEVICE_ID_PATH, id, "utf8");
|
|
859
995
|
return id;
|
|
860
996
|
}
|
|
861
997
|
function loadLicense() {
|
|
862
998
|
try {
|
|
863
|
-
if (!
|
|
864
|
-
return
|
|
999
|
+
if (!existsSync7(LICENSE_PATH)) return null;
|
|
1000
|
+
return readFileSync4(LICENSE_PATH, "utf8").trim();
|
|
865
1001
|
} catch {
|
|
866
1002
|
return null;
|
|
867
1003
|
}
|
|
868
1004
|
}
|
|
869
1005
|
function saveLicense(apiKey) {
|
|
870
|
-
|
|
871
|
-
|
|
1006
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true });
|
|
1007
|
+
writeFileSync2(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
872
1008
|
}
|
|
873
1009
|
async function verifyLicenseJwt(token) {
|
|
874
1010
|
try {
|
|
@@ -894,8 +1030,8 @@ async function verifyLicenseJwt(token) {
|
|
|
894
1030
|
}
|
|
895
1031
|
async function getCachedLicense() {
|
|
896
1032
|
try {
|
|
897
|
-
if (!
|
|
898
|
-
const raw = JSON.parse(
|
|
1033
|
+
if (!existsSync7(CACHE_PATH)) return null;
|
|
1034
|
+
const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
|
|
899
1035
|
if (!raw.token || typeof raw.token !== "string") return null;
|
|
900
1036
|
return await verifyLicenseJwt(raw.token);
|
|
901
1037
|
} catch {
|
|
@@ -904,8 +1040,8 @@ async function getCachedLicense() {
|
|
|
904
1040
|
}
|
|
905
1041
|
function readCachedToken() {
|
|
906
1042
|
try {
|
|
907
|
-
if (!
|
|
908
|
-
const raw = JSON.parse(
|
|
1043
|
+
if (!existsSync7(CACHE_PATH)) return null;
|
|
1044
|
+
const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
|
|
909
1045
|
return typeof raw.token === "string" ? raw.token : null;
|
|
910
1046
|
} catch {
|
|
911
1047
|
return null;
|
|
@@ -939,56 +1075,130 @@ function getRawCachedPlan() {
|
|
|
939
1075
|
}
|
|
940
1076
|
function cacheResponse(token) {
|
|
941
1077
|
try {
|
|
942
|
-
|
|
1078
|
+
writeFileSync2(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
943
1079
|
} catch {
|
|
944
1080
|
}
|
|
945
1081
|
}
|
|
946
|
-
|
|
947
|
-
|
|
1082
|
+
function loadPrismaForLicense() {
|
|
1083
|
+
if (_prismaFailed) return null;
|
|
1084
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
1085
|
+
if (!dbUrl) {
|
|
1086
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os4.homedir(), "exe-db");
|
|
1087
|
+
if (!existsSync7(path6.join(exeDbRoot, "package.json"))) {
|
|
1088
|
+
_prismaFailed = true;
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
if (!_prismaPromise) {
|
|
1093
|
+
_prismaPromise = (async () => {
|
|
1094
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1095
|
+
if (explicitPath) {
|
|
1096
|
+
const mod2 = await import(pathToFileURL(explicitPath).href);
|
|
1097
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
1098
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
1099
|
+
return new Ctor2();
|
|
1100
|
+
}
|
|
1101
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os4.homedir(), "exe-db");
|
|
1102
|
+
const req = createRequire(path6.join(exeDbRoot, "package.json"));
|
|
1103
|
+
const entry = req.resolve("@prisma/client");
|
|
1104
|
+
const mod = await import(pathToFileURL(entry).href);
|
|
1105
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
1106
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
1107
|
+
return new Ctor();
|
|
1108
|
+
})().catch((err) => {
|
|
1109
|
+
_prismaFailed = true;
|
|
1110
|
+
_prismaPromise = null;
|
|
1111
|
+
throw err;
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
return _prismaPromise;
|
|
1115
|
+
}
|
|
1116
|
+
async function validateViaPostgres(apiKey) {
|
|
1117
|
+
const loader = loadPrismaForLicense();
|
|
1118
|
+
if (!loader) return null;
|
|
1119
|
+
try {
|
|
1120
|
+
const prisma = await loader;
|
|
1121
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1122
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
1123
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
1124
|
+
apiKey
|
|
1125
|
+
);
|
|
1126
|
+
if (!rows || rows.length === 0) return null;
|
|
1127
|
+
const row = rows[0];
|
|
1128
|
+
if (row.status !== "active") return null;
|
|
1129
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
1130
|
+
const plan = row.plan;
|
|
1131
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1132
|
+
return {
|
|
1133
|
+
valid: true,
|
|
1134
|
+
plan,
|
|
1135
|
+
email: row.email,
|
|
1136
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
1137
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
1138
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
1139
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
1140
|
+
};
|
|
1141
|
+
} catch {
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
948
1146
|
try {
|
|
949
1147
|
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
950
1148
|
method: "POST",
|
|
951
1149
|
headers: { "Content-Type": "application/json" },
|
|
952
|
-
body: JSON.stringify({ apiKey, deviceId
|
|
1150
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
953
1151
|
signal: AbortSignal.timeout(1e4)
|
|
954
1152
|
});
|
|
955
|
-
if (res.ok)
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1153
|
+
if (!res.ok) return null;
|
|
1154
|
+
const data = await res.json();
|
|
1155
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
1156
|
+
if (!data.valid) return null;
|
|
1157
|
+
if (data.token) {
|
|
1158
|
+
cacheResponse(data.token);
|
|
1159
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
1160
|
+
if (verified) return verified;
|
|
1161
|
+
}
|
|
1162
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
1163
|
+
return {
|
|
1164
|
+
valid: data.valid,
|
|
1165
|
+
plan: data.plan,
|
|
1166
|
+
email: data.email,
|
|
1167
|
+
expiresAt: data.expiresAt,
|
|
1168
|
+
deviceLimit: limits.devices,
|
|
1169
|
+
employeeLimit: limits.employees,
|
|
1170
|
+
memoryLimit: limits.memories
|
|
1171
|
+
};
|
|
1172
|
+
} catch {
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
async function validateLicense(apiKey, deviceId) {
|
|
1177
|
+
const did = deviceId ?? loadDeviceId();
|
|
1178
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
1179
|
+
if (pgResult) {
|
|
1180
|
+
try {
|
|
1181
|
+
writeFileSync2(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
1182
|
+
} catch {
|
|
1183
|
+
}
|
|
1184
|
+
return pgResult;
|
|
1185
|
+
}
|
|
1186
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
1187
|
+
if (cfResult) return cfResult;
|
|
1188
|
+
const cached = await getCachedLicense();
|
|
1189
|
+
if (cached) return cached;
|
|
1190
|
+
try {
|
|
1191
|
+
if (existsSync7(CACHE_PATH)) {
|
|
1192
|
+
const raw = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
|
|
1193
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
1194
|
+
return raw.pgLicense;
|
|
968
1195
|
}
|
|
969
|
-
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
970
|
-
return {
|
|
971
|
-
valid: data.valid,
|
|
972
|
-
plan: data.plan,
|
|
973
|
-
email: data.email,
|
|
974
|
-
expiresAt: data.expiresAt,
|
|
975
|
-
deviceLimit: limits.devices,
|
|
976
|
-
employeeLimit: limits.employees,
|
|
977
|
-
memoryLimit: limits.memories
|
|
978
|
-
};
|
|
979
1196
|
}
|
|
980
|
-
const cached = await getCachedLicense();
|
|
981
|
-
if (cached) return cached;
|
|
982
|
-
const raw = getRawCachedPlan();
|
|
983
|
-
if (raw) return raw;
|
|
984
|
-
return { ...FREE_LICENSE, valid: false, plan: "free" };
|
|
985
1197
|
} catch {
|
|
986
|
-
const cached = await getCachedLicense();
|
|
987
|
-
if (cached) return cached;
|
|
988
|
-
const rawFallback = getRawCachedPlan();
|
|
989
|
-
if (rawFallback) return rawFallback;
|
|
990
|
-
return { ...FREE_LICENSE, valid: false, error: "offline" };
|
|
991
1198
|
}
|
|
1199
|
+
const rawFallback = getRawCachedPlan();
|
|
1200
|
+
if (rawFallback) return rawFallback;
|
|
1201
|
+
return { ...FREE_LICENSE, valid: false };
|
|
992
1202
|
}
|
|
993
1203
|
function getCacheAgeMs() {
|
|
994
1204
|
try {
|
|
@@ -1003,9 +1213,9 @@ async function checkLicense() {
|
|
|
1003
1213
|
let key = loadLicense();
|
|
1004
1214
|
if (!key) {
|
|
1005
1215
|
try {
|
|
1006
|
-
const configPath =
|
|
1007
|
-
if (
|
|
1008
|
-
const raw = JSON.parse(
|
|
1216
|
+
const configPath = path6.join(EXE_AI_DIR, "config.json");
|
|
1217
|
+
if (existsSync7(configPath)) {
|
|
1218
|
+
const raw = JSON.parse(readFileSync4(configPath, "utf8"));
|
|
1009
1219
|
const cloud = raw.cloud;
|
|
1010
1220
|
if (cloud?.apiKey) {
|
|
1011
1221
|
key = cloud.apiKey;
|
|
@@ -1159,14 +1369,14 @@ function stopLicenseRevalidation() {
|
|
|
1159
1369
|
_revalTimer = null;
|
|
1160
1370
|
}
|
|
1161
1371
|
}
|
|
1162
|
-
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
|
|
1372
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
|
|
1163
1373
|
var init_license = __esm({
|
|
1164
1374
|
"src/lib/license.ts"() {
|
|
1165
1375
|
"use strict";
|
|
1166
1376
|
init_config();
|
|
1167
|
-
LICENSE_PATH =
|
|
1168
|
-
CACHE_PATH =
|
|
1169
|
-
DEVICE_ID_PATH =
|
|
1377
|
+
LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
|
|
1378
|
+
CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
|
|
1379
|
+
DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
|
|
1170
1380
|
API_BASE = "https://askexe.com/cloud";
|
|
1171
1381
|
RETRY_DELAY_MS = 500;
|
|
1172
1382
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
@@ -1190,6 +1400,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
1190
1400
|
employeeLimit: 1,
|
|
1191
1401
|
memoryLimit: 5e3
|
|
1192
1402
|
};
|
|
1403
|
+
_prismaPromise = null;
|
|
1404
|
+
_prismaFailed = false;
|
|
1193
1405
|
CACHE_MAX_AGE_MS = 36e5;
|
|
1194
1406
|
_revalTimer = null;
|
|
1195
1407
|
}
|
|
@@ -1203,13 +1415,13 @@ __export(crypto_exports, {
|
|
|
1203
1415
|
initSyncCrypto: () => initSyncCrypto,
|
|
1204
1416
|
isSyncCryptoInitialized: () => isSyncCryptoInitialized
|
|
1205
1417
|
});
|
|
1206
|
-
import
|
|
1418
|
+
import crypto2 from "crypto";
|
|
1207
1419
|
function initSyncCrypto(masterKey) {
|
|
1208
1420
|
if (masterKey.length !== 32) {
|
|
1209
1421
|
throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
|
|
1210
1422
|
}
|
|
1211
1423
|
_syncKey = Buffer.from(
|
|
1212
|
-
|
|
1424
|
+
crypto2.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
|
|
1213
1425
|
);
|
|
1214
1426
|
}
|
|
1215
1427
|
function isSyncCryptoInitialized() {
|
|
@@ -1223,8 +1435,8 @@ function requireSyncKey() {
|
|
|
1223
1435
|
}
|
|
1224
1436
|
function encryptSyncBlob(data) {
|
|
1225
1437
|
const key = requireSyncKey();
|
|
1226
|
-
const iv =
|
|
1227
|
-
const cipher =
|
|
1438
|
+
const iv = crypto2.randomBytes(IV_LENGTH);
|
|
1439
|
+
const cipher = crypto2.createCipheriv(ALGORITHM, key, iv);
|
|
1228
1440
|
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
1229
1441
|
const tag = cipher.getAuthTag();
|
|
1230
1442
|
return Buffer.concat([iv, encrypted, tag]).toString("base64");
|
|
@@ -1238,7 +1450,7 @@ function decryptSyncBlob(ciphertext) {
|
|
|
1238
1450
|
const iv = combined.subarray(0, IV_LENGTH);
|
|
1239
1451
|
const tag = combined.subarray(combined.length - TAG_LENGTH);
|
|
1240
1452
|
const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
|
|
1241
|
-
const decipher =
|
|
1453
|
+
const decipher = crypto2.createDecipheriv(ALGORITHM, key, iv);
|
|
1242
1454
|
decipher.setAuthTag(tag);
|
|
1243
1455
|
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
1244
1456
|
}
|
|
@@ -1309,6 +1521,120 @@ var init_db_retry = __esm({
|
|
|
1309
1521
|
}
|
|
1310
1522
|
});
|
|
1311
1523
|
|
|
1524
|
+
// src/lib/runtime-table.ts
|
|
1525
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
1526
|
+
var init_runtime_table = __esm({
|
|
1527
|
+
"src/lib/runtime-table.ts"() {
|
|
1528
|
+
"use strict";
|
|
1529
|
+
RUNTIME_TABLE = {
|
|
1530
|
+
codex: {
|
|
1531
|
+
binary: "codex",
|
|
1532
|
+
launchMode: "interactive",
|
|
1533
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
1534
|
+
inlineFlag: "--no-alt-screen",
|
|
1535
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
1536
|
+
defaultModel: "gpt-5.4"
|
|
1537
|
+
},
|
|
1538
|
+
opencode: {
|
|
1539
|
+
binary: "opencode",
|
|
1540
|
+
launchMode: "exec",
|
|
1541
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
1542
|
+
inlineFlag: "",
|
|
1543
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
1544
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
DEFAULT_RUNTIME = "claude";
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
// src/lib/agent-config.ts
|
|
1552
|
+
var agent_config_exports = {};
|
|
1553
|
+
__export(agent_config_exports, {
|
|
1554
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
1555
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
1556
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
1557
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
1558
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
1559
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
1560
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
1561
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
1562
|
+
setAgentRuntime: () => setAgentRuntime
|
|
1563
|
+
});
|
|
1564
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync8 } from "fs";
|
|
1565
|
+
import path7 from "path";
|
|
1566
|
+
function loadAgentConfig() {
|
|
1567
|
+
if (!existsSync8(AGENT_CONFIG_PATH)) return {};
|
|
1568
|
+
try {
|
|
1569
|
+
return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
|
|
1570
|
+
} catch {
|
|
1571
|
+
return {};
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
function saveAgentConfig(config) {
|
|
1575
|
+
const dir = path7.dirname(AGENT_CONFIG_PATH);
|
|
1576
|
+
ensurePrivateDirSync(dir);
|
|
1577
|
+
writeFileSync3(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1578
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
1579
|
+
}
|
|
1580
|
+
function getAgentRuntime(agentId) {
|
|
1581
|
+
const config = loadAgentConfig();
|
|
1582
|
+
const entry = config[agentId];
|
|
1583
|
+
if (entry) return entry;
|
|
1584
|
+
const orgDefault = config["default"];
|
|
1585
|
+
if (orgDefault) return orgDefault;
|
|
1586
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
1587
|
+
}
|
|
1588
|
+
function setAgentRuntime(agentId, runtime, model) {
|
|
1589
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
1590
|
+
if (!knownModels) {
|
|
1591
|
+
return {
|
|
1592
|
+
ok: false,
|
|
1593
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
if (!knownModels.includes(model)) {
|
|
1597
|
+
return {
|
|
1598
|
+
ok: false,
|
|
1599
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
const config = loadAgentConfig();
|
|
1603
|
+
config[agentId] = { runtime, model };
|
|
1604
|
+
saveAgentConfig(config);
|
|
1605
|
+
return { ok: true };
|
|
1606
|
+
}
|
|
1607
|
+
function clearAgentRuntime(agentId) {
|
|
1608
|
+
const config = loadAgentConfig();
|
|
1609
|
+
delete config[agentId];
|
|
1610
|
+
saveAgentConfig(config);
|
|
1611
|
+
}
|
|
1612
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
1613
|
+
var init_agent_config = __esm({
|
|
1614
|
+
"src/lib/agent-config.ts"() {
|
|
1615
|
+
"use strict";
|
|
1616
|
+
init_config();
|
|
1617
|
+
init_runtime_table();
|
|
1618
|
+
init_secure_files();
|
|
1619
|
+
AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
|
|
1620
|
+
KNOWN_RUNTIMES = {
|
|
1621
|
+
claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
1622
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
1623
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
1624
|
+
};
|
|
1625
|
+
RUNTIME_LABELS = {
|
|
1626
|
+
claude: "Claude Code (Anthropic)",
|
|
1627
|
+
codex: "Codex (OpenAI)",
|
|
1628
|
+
opencode: "OpenCode (open source)"
|
|
1629
|
+
};
|
|
1630
|
+
DEFAULT_MODELS = {
|
|
1631
|
+
claude: "claude-opus-4",
|
|
1632
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
1633
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
});
|
|
1637
|
+
|
|
1312
1638
|
// src/lib/employees.ts
|
|
1313
1639
|
var employees_exports = {};
|
|
1314
1640
|
__export(employees_exports, {
|
|
@@ -1324,6 +1650,7 @@ __export(employees_exports, {
|
|
|
1324
1650
|
getEmployeeByRole: () => getEmployeeByRole,
|
|
1325
1651
|
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
1326
1652
|
hasRole: () => hasRole,
|
|
1653
|
+
hireEmployee: () => hireEmployee,
|
|
1327
1654
|
isCoordinatorName: () => isCoordinatorName,
|
|
1328
1655
|
isCoordinatorRole: () => isCoordinatorRole,
|
|
1329
1656
|
isMultiInstance: () => isMultiInstance,
|
|
@@ -1336,10 +1663,10 @@ __export(employees_exports, {
|
|
|
1336
1663
|
validateEmployeeName: () => validateEmployeeName
|
|
1337
1664
|
});
|
|
1338
1665
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir4 } from "fs/promises";
|
|
1339
|
-
import { existsSync as
|
|
1666
|
+
import { existsSync as existsSync9, symlinkSync, readlinkSync, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
1340
1667
|
import { execSync } from "child_process";
|
|
1341
|
-
import
|
|
1342
|
-
import
|
|
1668
|
+
import path8 from "path";
|
|
1669
|
+
import os5 from "os";
|
|
1343
1670
|
function normalizeRole(role) {
|
|
1344
1671
|
return (role ?? "").trim().toLowerCase();
|
|
1345
1672
|
}
|
|
@@ -1375,7 +1702,7 @@ function validateEmployeeName(name) {
|
|
|
1375
1702
|
return { valid: true };
|
|
1376
1703
|
}
|
|
1377
1704
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
1378
|
-
if (!
|
|
1705
|
+
if (!existsSync9(employeesPath)) {
|
|
1379
1706
|
return [];
|
|
1380
1707
|
}
|
|
1381
1708
|
const raw = await readFile3(employeesPath, "utf-8");
|
|
@@ -1386,13 +1713,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
1386
1713
|
}
|
|
1387
1714
|
}
|
|
1388
1715
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
1389
|
-
await mkdir4(
|
|
1716
|
+
await mkdir4(path8.dirname(employeesPath), { recursive: true });
|
|
1390
1717
|
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1391
1718
|
}
|
|
1392
1719
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1393
|
-
if (!
|
|
1720
|
+
if (!existsSync9(employeesPath)) return [];
|
|
1394
1721
|
try {
|
|
1395
|
-
return JSON.parse(
|
|
1722
|
+
return JSON.parse(readFileSync6(employeesPath, "utf-8"));
|
|
1396
1723
|
} catch {
|
|
1397
1724
|
return [];
|
|
1398
1725
|
}
|
|
@@ -1434,6 +1761,52 @@ function addEmployee(employees, employee) {
|
|
|
1434
1761
|
}
|
|
1435
1762
|
return [...employees, normalized];
|
|
1436
1763
|
}
|
|
1764
|
+
function appendToCoordinatorTeam(employee) {
|
|
1765
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
1766
|
+
if (!coordinator) return;
|
|
1767
|
+
const idPath = path8.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
1768
|
+
if (!existsSync9(idPath)) return;
|
|
1769
|
+
const content = readFileSync6(idPath, "utf-8");
|
|
1770
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
1771
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
1772
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
1773
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
1774
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
1775
|
+
const entry = `
|
|
1776
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
1777
|
+
`;
|
|
1778
|
+
let updated;
|
|
1779
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
1780
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
1781
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
1782
|
+
} else {
|
|
1783
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
1784
|
+
}
|
|
1785
|
+
writeFileSync4(idPath, updated, "utf-8");
|
|
1786
|
+
}
|
|
1787
|
+
function capitalize(s) {
|
|
1788
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1789
|
+
}
|
|
1790
|
+
async function hireEmployee(employee) {
|
|
1791
|
+
const employees = await loadEmployees();
|
|
1792
|
+
const updated = addEmployee(employees, employee);
|
|
1793
|
+
await saveEmployees(updated);
|
|
1794
|
+
try {
|
|
1795
|
+
appendToCoordinatorTeam(employee);
|
|
1796
|
+
} catch {
|
|
1797
|
+
}
|
|
1798
|
+
try {
|
|
1799
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
1800
|
+
const config = loadAgentConfig2();
|
|
1801
|
+
const name = employee.name.toLowerCase();
|
|
1802
|
+
if (!config[name] && config["default"]) {
|
|
1803
|
+
config[name] = { ...config["default"] };
|
|
1804
|
+
saveAgentConfig2(config);
|
|
1805
|
+
}
|
|
1806
|
+
} catch {
|
|
1807
|
+
}
|
|
1808
|
+
return updated;
|
|
1809
|
+
}
|
|
1437
1810
|
async function normalizeRosterCase(rosterPath) {
|
|
1438
1811
|
const employees = await loadEmployees(rosterPath);
|
|
1439
1812
|
let changed = false;
|
|
@@ -1443,14 +1816,14 @@ async function normalizeRosterCase(rosterPath) {
|
|
|
1443
1816
|
emp.name = emp.name.toLowerCase();
|
|
1444
1817
|
changed = true;
|
|
1445
1818
|
try {
|
|
1446
|
-
const identityDir =
|
|
1447
|
-
const oldPath =
|
|
1448
|
-
const newPath =
|
|
1449
|
-
if (
|
|
1819
|
+
const identityDir = path8.join(os5.homedir(), ".exe-os", "identity");
|
|
1820
|
+
const oldPath = path8.join(identityDir, `${oldName}.md`);
|
|
1821
|
+
const newPath = path8.join(identityDir, `${emp.name}.md`);
|
|
1822
|
+
if (existsSync9(oldPath) && !existsSync9(newPath)) {
|
|
1450
1823
|
renameSync3(oldPath, newPath);
|
|
1451
|
-
} else if (
|
|
1452
|
-
const content =
|
|
1453
|
-
|
|
1824
|
+
} else if (existsSync9(oldPath) && oldPath !== newPath) {
|
|
1825
|
+
const content = readFileSync6(oldPath, "utf-8");
|
|
1826
|
+
writeFileSync4(newPath, content, "utf-8");
|
|
1454
1827
|
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
1455
1828
|
unlinkSync3(oldPath);
|
|
1456
1829
|
}
|
|
@@ -1480,7 +1853,7 @@ function registerBinSymlinks(name) {
|
|
|
1480
1853
|
errors.push("Could not find 'exe-os' in PATH");
|
|
1481
1854
|
return { created, skipped, errors };
|
|
1482
1855
|
}
|
|
1483
|
-
const binDir =
|
|
1856
|
+
const binDir = path8.dirname(exeBinPath);
|
|
1484
1857
|
let target;
|
|
1485
1858
|
try {
|
|
1486
1859
|
target = readlinkSync(exeBinPath);
|
|
@@ -1490,8 +1863,8 @@ function registerBinSymlinks(name) {
|
|
|
1490
1863
|
}
|
|
1491
1864
|
for (const suffix of ["", "-opencode"]) {
|
|
1492
1865
|
const linkName = `${name}${suffix}`;
|
|
1493
|
-
const linkPath =
|
|
1494
|
-
if (
|
|
1866
|
+
const linkPath = path8.join(binDir, linkName);
|
|
1867
|
+
if (existsSync9(linkPath)) {
|
|
1495
1868
|
skipped.push(linkName);
|
|
1496
1869
|
continue;
|
|
1497
1870
|
}
|
|
@@ -1504,15 +1877,601 @@ function registerBinSymlinks(name) {
|
|
|
1504
1877
|
}
|
|
1505
1878
|
return { created, skipped, errors };
|
|
1506
1879
|
}
|
|
1507
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
1880
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
1508
1881
|
var init_employees = __esm({
|
|
1509
1882
|
"src/lib/employees.ts"() {
|
|
1510
1883
|
"use strict";
|
|
1511
1884
|
init_config();
|
|
1512
|
-
EMPLOYEES_PATH =
|
|
1885
|
+
EMPLOYEES_PATH = path8.join(EXE_AI_DIR, "exe-employees.json");
|
|
1513
1886
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
1514
1887
|
COORDINATOR_ROLE = "COO";
|
|
1515
1888
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1889
|
+
IDENTITY_DIR = path8.join(EXE_AI_DIR, "identity");
|
|
1890
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
1891
|
+
}
|
|
1892
|
+
});
|
|
1893
|
+
|
|
1894
|
+
// src/lib/database-adapter.ts
|
|
1895
|
+
import os6 from "os";
|
|
1896
|
+
import path9 from "path";
|
|
1897
|
+
import { createRequire as createRequire2 } from "module";
|
|
1898
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1899
|
+
function quotedIdentifier(identifier) {
|
|
1900
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1901
|
+
}
|
|
1902
|
+
function unqualifiedTableName(name) {
|
|
1903
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
1904
|
+
const parts = raw.split(".");
|
|
1905
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
1906
|
+
}
|
|
1907
|
+
function stripTrailingSemicolon(sql) {
|
|
1908
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
1909
|
+
}
|
|
1910
|
+
function appendClause(sql, clause) {
|
|
1911
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
1912
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
1913
|
+
if (!returningMatch) {
|
|
1914
|
+
return `${trimmed}${clause}`;
|
|
1915
|
+
}
|
|
1916
|
+
const idx = returningMatch.index;
|
|
1917
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
1918
|
+
}
|
|
1919
|
+
function normalizeStatement(stmt) {
|
|
1920
|
+
if (typeof stmt === "string") {
|
|
1921
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
1922
|
+
}
|
|
1923
|
+
const sql = stmt.sql;
|
|
1924
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
1925
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
1926
|
+
}
|
|
1927
|
+
return { kind: "named", sql, args: stmt.args };
|
|
1928
|
+
}
|
|
1929
|
+
function rewriteBooleanLiterals(sql) {
|
|
1930
|
+
let out = sql;
|
|
1931
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1932
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
1933
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
1934
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
1935
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
1936
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
1937
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
1938
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
1939
|
+
}
|
|
1940
|
+
return out;
|
|
1941
|
+
}
|
|
1942
|
+
function rewriteInsertOrIgnore(sql) {
|
|
1943
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
1944
|
+
return sql;
|
|
1945
|
+
}
|
|
1946
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
1947
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
1948
|
+
}
|
|
1949
|
+
function rewriteInsertOrReplace(sql) {
|
|
1950
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
1951
|
+
if (!match) {
|
|
1952
|
+
return sql;
|
|
1953
|
+
}
|
|
1954
|
+
const rawTable = match[1];
|
|
1955
|
+
const rawColumns = match[2];
|
|
1956
|
+
const remainder = match[3];
|
|
1957
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
1958
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
1959
|
+
if (!conflictKeys?.length) {
|
|
1960
|
+
return sql;
|
|
1961
|
+
}
|
|
1962
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1963
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
1964
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
1965
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
1966
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
1967
|
+
}
|
|
1968
|
+
function rewriteSql(sql) {
|
|
1969
|
+
let out = sql;
|
|
1970
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
1971
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
1972
|
+
out = rewriteBooleanLiterals(out);
|
|
1973
|
+
out = rewriteInsertOrReplace(out);
|
|
1974
|
+
out = rewriteInsertOrIgnore(out);
|
|
1975
|
+
return stripTrailingSemicolon(out);
|
|
1976
|
+
}
|
|
1977
|
+
function toBoolean(value) {
|
|
1978
|
+
if (value === null || value === void 0) return value;
|
|
1979
|
+
if (typeof value === "boolean") return value;
|
|
1980
|
+
if (typeof value === "number") return value !== 0;
|
|
1981
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
1982
|
+
if (typeof value === "string") {
|
|
1983
|
+
const normalized = value.trim().toLowerCase();
|
|
1984
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
1985
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
1986
|
+
}
|
|
1987
|
+
return Boolean(value);
|
|
1988
|
+
}
|
|
1989
|
+
function countQuestionMarks(sql, end) {
|
|
1990
|
+
let count = 0;
|
|
1991
|
+
let inSingle = false;
|
|
1992
|
+
let inDouble = false;
|
|
1993
|
+
let inLineComment = false;
|
|
1994
|
+
let inBlockComment = false;
|
|
1995
|
+
for (let i = 0; i < end; i++) {
|
|
1996
|
+
const ch = sql[i];
|
|
1997
|
+
const next = sql[i + 1];
|
|
1998
|
+
if (inLineComment) {
|
|
1999
|
+
if (ch === "\n") inLineComment = false;
|
|
2000
|
+
continue;
|
|
2001
|
+
}
|
|
2002
|
+
if (inBlockComment) {
|
|
2003
|
+
if (ch === "*" && next === "/") {
|
|
2004
|
+
inBlockComment = false;
|
|
2005
|
+
i += 1;
|
|
2006
|
+
}
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
2010
|
+
inLineComment = true;
|
|
2011
|
+
i += 1;
|
|
2012
|
+
continue;
|
|
2013
|
+
}
|
|
2014
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
2015
|
+
inBlockComment = true;
|
|
2016
|
+
i += 1;
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
2020
|
+
inSingle = !inSingle;
|
|
2021
|
+
continue;
|
|
2022
|
+
}
|
|
2023
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
2024
|
+
inDouble = !inDouble;
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
2028
|
+
count += 1;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
return count;
|
|
2032
|
+
}
|
|
2033
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
2034
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
2035
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
2036
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
2037
|
+
for (const match of sql.matchAll(pattern)) {
|
|
2038
|
+
const matchText = match[0];
|
|
2039
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
2040
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
return indexes;
|
|
2044
|
+
}
|
|
2045
|
+
function coerceInsertBooleanArgs(sql, args2) {
|
|
2046
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
2047
|
+
if (!match) return;
|
|
2048
|
+
const rawTable = match[1];
|
|
2049
|
+
const rawColumns = match[2];
|
|
2050
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
2051
|
+
if (!boolColumns?.size) return;
|
|
2052
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
2053
|
+
for (const [index, column] of columns.entries()) {
|
|
2054
|
+
if (boolColumns.has(column) && index < args2.length) {
|
|
2055
|
+
args2[index] = toBoolean(args2[index]);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
function coerceUpdateBooleanArgs(sql, args2) {
|
|
2060
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
2061
|
+
if (!match) return;
|
|
2062
|
+
const rawTable = match[1];
|
|
2063
|
+
const setClause = match[2];
|
|
2064
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
2065
|
+
if (!boolColumns?.size) return;
|
|
2066
|
+
const assignments = setClause.split(",");
|
|
2067
|
+
let placeholderIndex = 0;
|
|
2068
|
+
for (const assignment of assignments) {
|
|
2069
|
+
if (!assignment.includes("?")) continue;
|
|
2070
|
+
placeholderIndex += 1;
|
|
2071
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
2072
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
2073
|
+
args2[placeholderIndex - 1] = toBoolean(args2[placeholderIndex - 1]);
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
function coerceBooleanArgs(sql, args2) {
|
|
2078
|
+
const nextArgs = [...args2];
|
|
2079
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
2080
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
2081
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
2082
|
+
for (const index of placeholderIndexes) {
|
|
2083
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
2084
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
return nextArgs;
|
|
2088
|
+
}
|
|
2089
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
2090
|
+
let out = "";
|
|
2091
|
+
let placeholder = 0;
|
|
2092
|
+
let inSingle = false;
|
|
2093
|
+
let inDouble = false;
|
|
2094
|
+
let inLineComment = false;
|
|
2095
|
+
let inBlockComment = false;
|
|
2096
|
+
for (let i = 0; i < sql.length; i++) {
|
|
2097
|
+
const ch = sql[i];
|
|
2098
|
+
const next = sql[i + 1];
|
|
2099
|
+
if (inLineComment) {
|
|
2100
|
+
out += ch;
|
|
2101
|
+
if (ch === "\n") inLineComment = false;
|
|
2102
|
+
continue;
|
|
2103
|
+
}
|
|
2104
|
+
if (inBlockComment) {
|
|
2105
|
+
out += ch;
|
|
2106
|
+
if (ch === "*" && next === "/") {
|
|
2107
|
+
out += next;
|
|
2108
|
+
inBlockComment = false;
|
|
2109
|
+
i += 1;
|
|
2110
|
+
}
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2113
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
2114
|
+
out += ch + next;
|
|
2115
|
+
inLineComment = true;
|
|
2116
|
+
i += 1;
|
|
2117
|
+
continue;
|
|
2118
|
+
}
|
|
2119
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
2120
|
+
out += ch + next;
|
|
2121
|
+
inBlockComment = true;
|
|
2122
|
+
i += 1;
|
|
2123
|
+
continue;
|
|
2124
|
+
}
|
|
2125
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
2126
|
+
inSingle = !inSingle;
|
|
2127
|
+
out += ch;
|
|
2128
|
+
continue;
|
|
2129
|
+
}
|
|
2130
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
2131
|
+
inDouble = !inDouble;
|
|
2132
|
+
out += ch;
|
|
2133
|
+
continue;
|
|
2134
|
+
}
|
|
2135
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
2136
|
+
placeholder += 1;
|
|
2137
|
+
out += `$${placeholder}`;
|
|
2138
|
+
continue;
|
|
2139
|
+
}
|
|
2140
|
+
out += ch;
|
|
2141
|
+
}
|
|
2142
|
+
return out;
|
|
2143
|
+
}
|
|
2144
|
+
function translateStatementForPostgres(stmt) {
|
|
2145
|
+
const normalized = normalizeStatement(stmt);
|
|
2146
|
+
if (normalized.kind === "named") {
|
|
2147
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
2148
|
+
}
|
|
2149
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
2150
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
2151
|
+
return {
|
|
2152
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
2153
|
+
args: coercedArgs
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
function shouldBypassPostgres(stmt) {
|
|
2157
|
+
const normalized = normalizeStatement(stmt);
|
|
2158
|
+
if (normalized.kind === "named") {
|
|
2159
|
+
return true;
|
|
2160
|
+
}
|
|
2161
|
+
return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
|
|
2162
|
+
}
|
|
2163
|
+
function shouldFallbackOnError(error) {
|
|
2164
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2165
|
+
return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
|
|
2166
|
+
}
|
|
2167
|
+
function isReadQuery(sql) {
|
|
2168
|
+
const trimmed = sql.trimStart();
|
|
2169
|
+
return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
|
|
2170
|
+
}
|
|
2171
|
+
function buildRow(row, columns) {
|
|
2172
|
+
const values = columns.map((column) => row[column]);
|
|
2173
|
+
return Object.assign(values, row);
|
|
2174
|
+
}
|
|
2175
|
+
function buildResultSet(rows, rowsAffected = 0) {
|
|
2176
|
+
const columns = rows[0] ? Object.keys(rows[0]) : [];
|
|
2177
|
+
const resultRows = rows.map((row) => buildRow(row, columns));
|
|
2178
|
+
return {
|
|
2179
|
+
columns,
|
|
2180
|
+
columnTypes: columns.map(() => ""),
|
|
2181
|
+
rows: resultRows,
|
|
2182
|
+
rowsAffected,
|
|
2183
|
+
lastInsertRowid: void 0,
|
|
2184
|
+
toJSON() {
|
|
2185
|
+
return {
|
|
2186
|
+
columns,
|
|
2187
|
+
columnTypes: columns.map(() => ""),
|
|
2188
|
+
rows,
|
|
2189
|
+
rowsAffected,
|
|
2190
|
+
lastInsertRowid: void 0
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
async function loadPrismaClient() {
|
|
2196
|
+
if (!prismaClientPromise) {
|
|
2197
|
+
prismaClientPromise = (async () => {
|
|
2198
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
2199
|
+
if (explicitPath) {
|
|
2200
|
+
const module2 = await import(pathToFileURL2(explicitPath).href);
|
|
2201
|
+
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
2202
|
+
if (!PrismaClient2) {
|
|
2203
|
+
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
2204
|
+
}
|
|
2205
|
+
return new PrismaClient2();
|
|
2206
|
+
}
|
|
2207
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os6.homedir(), "exe-db");
|
|
2208
|
+
const requireFromExeDb = createRequire2(path9.join(exeDbRoot, "package.json"));
|
|
2209
|
+
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
2210
|
+
const module = await import(pathToFileURL2(prismaEntry).href);
|
|
2211
|
+
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
2212
|
+
if (!PrismaClient) {
|
|
2213
|
+
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
2214
|
+
}
|
|
2215
|
+
return new PrismaClient();
|
|
2216
|
+
})();
|
|
2217
|
+
}
|
|
2218
|
+
return prismaClientPromise;
|
|
2219
|
+
}
|
|
2220
|
+
async function ensureCompatibilityViews(prisma) {
|
|
2221
|
+
if (!compatibilityBootstrapPromise) {
|
|
2222
|
+
compatibilityBootstrapPromise = (async () => {
|
|
2223
|
+
for (const mapping of VIEW_MAPPINGS) {
|
|
2224
|
+
const relation = mapping.source.replace(/"/g, "");
|
|
2225
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
2226
|
+
"SELECT to_regclass($1) AS regclass",
|
|
2227
|
+
relation
|
|
2228
|
+
);
|
|
2229
|
+
if (!rows[0]?.regclass) {
|
|
2230
|
+
continue;
|
|
2231
|
+
}
|
|
2232
|
+
await prisma.$executeRawUnsafe(
|
|
2233
|
+
`CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
})();
|
|
2237
|
+
}
|
|
2238
|
+
return compatibilityBootstrapPromise;
|
|
2239
|
+
}
|
|
2240
|
+
async function executeOnPrisma(executor, stmt) {
|
|
2241
|
+
const translated = translateStatementForPostgres(stmt);
|
|
2242
|
+
if (isReadQuery(translated.sql)) {
|
|
2243
|
+
const rows = await executor.$queryRawUnsafe(
|
|
2244
|
+
translated.sql,
|
|
2245
|
+
...translated.args
|
|
2246
|
+
);
|
|
2247
|
+
return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
|
|
2248
|
+
}
|
|
2249
|
+
const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
|
|
2250
|
+
return buildResultSet([], rowsAffected);
|
|
2251
|
+
}
|
|
2252
|
+
function splitSqlStatements(sql) {
|
|
2253
|
+
const parts = [];
|
|
2254
|
+
let current = "";
|
|
2255
|
+
let inSingle = false;
|
|
2256
|
+
let inDouble = false;
|
|
2257
|
+
let inLineComment = false;
|
|
2258
|
+
let inBlockComment = false;
|
|
2259
|
+
for (let i = 0; i < sql.length; i++) {
|
|
2260
|
+
const ch = sql[i];
|
|
2261
|
+
const next = sql[i + 1];
|
|
2262
|
+
if (inLineComment) {
|
|
2263
|
+
current += ch;
|
|
2264
|
+
if (ch === "\n") inLineComment = false;
|
|
2265
|
+
continue;
|
|
2266
|
+
}
|
|
2267
|
+
if (inBlockComment) {
|
|
2268
|
+
current += ch;
|
|
2269
|
+
if (ch === "*" && next === "/") {
|
|
2270
|
+
current += next;
|
|
2271
|
+
inBlockComment = false;
|
|
2272
|
+
i += 1;
|
|
2273
|
+
}
|
|
2274
|
+
continue;
|
|
2275
|
+
}
|
|
2276
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
2277
|
+
current += ch + next;
|
|
2278
|
+
inLineComment = true;
|
|
2279
|
+
i += 1;
|
|
2280
|
+
continue;
|
|
2281
|
+
}
|
|
2282
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
2283
|
+
current += ch + next;
|
|
2284
|
+
inBlockComment = true;
|
|
2285
|
+
i += 1;
|
|
2286
|
+
continue;
|
|
2287
|
+
}
|
|
2288
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
2289
|
+
inSingle = !inSingle;
|
|
2290
|
+
current += ch;
|
|
2291
|
+
continue;
|
|
2292
|
+
}
|
|
2293
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
2294
|
+
inDouble = !inDouble;
|
|
2295
|
+
current += ch;
|
|
2296
|
+
continue;
|
|
2297
|
+
}
|
|
2298
|
+
if (!inSingle && !inDouble && ch === ";") {
|
|
2299
|
+
if (current.trim()) {
|
|
2300
|
+
parts.push(current.trim());
|
|
2301
|
+
}
|
|
2302
|
+
current = "";
|
|
2303
|
+
continue;
|
|
2304
|
+
}
|
|
2305
|
+
current += ch;
|
|
2306
|
+
}
|
|
2307
|
+
if (current.trim()) {
|
|
2308
|
+
parts.push(current.trim());
|
|
2309
|
+
}
|
|
2310
|
+
return parts;
|
|
2311
|
+
}
|
|
2312
|
+
async function createPrismaDbAdapter(fallbackClient) {
|
|
2313
|
+
const prisma = await loadPrismaClient();
|
|
2314
|
+
await ensureCompatibilityViews(prisma);
|
|
2315
|
+
let closed = false;
|
|
2316
|
+
let adapter;
|
|
2317
|
+
const fallbackExecute = async (stmt, error) => {
|
|
2318
|
+
if (!fallbackClient) {
|
|
2319
|
+
if (error) throw error;
|
|
2320
|
+
throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
|
|
2321
|
+
}
|
|
2322
|
+
if (error) {
|
|
2323
|
+
process.stderr.write(
|
|
2324
|
+
`[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
2325
|
+
`
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
return fallbackClient.execute(stmt);
|
|
2329
|
+
};
|
|
2330
|
+
adapter = {
|
|
2331
|
+
async execute(stmt) {
|
|
2332
|
+
if (shouldBypassPostgres(stmt)) {
|
|
2333
|
+
return fallbackExecute(stmt);
|
|
2334
|
+
}
|
|
2335
|
+
try {
|
|
2336
|
+
return await executeOnPrisma(prisma, stmt);
|
|
2337
|
+
} catch (error) {
|
|
2338
|
+
if (shouldFallbackOnError(error)) {
|
|
2339
|
+
return fallbackExecute(stmt, error);
|
|
2340
|
+
}
|
|
2341
|
+
throw error;
|
|
2342
|
+
}
|
|
2343
|
+
},
|
|
2344
|
+
async batch(stmts, mode) {
|
|
2345
|
+
if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
|
|
2346
|
+
if (!fallbackClient) {
|
|
2347
|
+
throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
|
|
2348
|
+
}
|
|
2349
|
+
return fallbackClient.batch(stmts, mode);
|
|
2350
|
+
}
|
|
2351
|
+
try {
|
|
2352
|
+
if (prisma.$transaction) {
|
|
2353
|
+
return await prisma.$transaction(async (tx) => {
|
|
2354
|
+
const results2 = [];
|
|
2355
|
+
for (const stmt of stmts) {
|
|
2356
|
+
results2.push(await executeOnPrisma(tx, stmt));
|
|
2357
|
+
}
|
|
2358
|
+
return results2;
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
const results = [];
|
|
2362
|
+
for (const stmt of stmts) {
|
|
2363
|
+
results.push(await executeOnPrisma(prisma, stmt));
|
|
2364
|
+
}
|
|
2365
|
+
return results;
|
|
2366
|
+
} catch (error) {
|
|
2367
|
+
if (fallbackClient && shouldFallbackOnError(error)) {
|
|
2368
|
+
process.stderr.write(
|
|
2369
|
+
`[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
|
|
2370
|
+
`
|
|
2371
|
+
);
|
|
2372
|
+
return fallbackClient.batch(stmts, mode);
|
|
2373
|
+
}
|
|
2374
|
+
throw error;
|
|
2375
|
+
}
|
|
2376
|
+
},
|
|
2377
|
+
async migrate(stmts) {
|
|
2378
|
+
if (fallbackClient) {
|
|
2379
|
+
return fallbackClient.migrate(stmts);
|
|
2380
|
+
}
|
|
2381
|
+
return adapter.batch(stmts, "deferred");
|
|
2382
|
+
},
|
|
2383
|
+
async transaction(mode) {
|
|
2384
|
+
if (!fallbackClient) {
|
|
2385
|
+
throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
|
|
2386
|
+
}
|
|
2387
|
+
return fallbackClient.transaction(mode);
|
|
2388
|
+
},
|
|
2389
|
+
async executeMultiple(sql) {
|
|
2390
|
+
if (fallbackClient && shouldBypassPostgres(sql)) {
|
|
2391
|
+
return fallbackClient.executeMultiple(sql);
|
|
2392
|
+
}
|
|
2393
|
+
for (const statement of splitSqlStatements(sql)) {
|
|
2394
|
+
await adapter.execute(statement);
|
|
2395
|
+
}
|
|
2396
|
+
},
|
|
2397
|
+
async sync() {
|
|
2398
|
+
if (fallbackClient) {
|
|
2399
|
+
return fallbackClient.sync();
|
|
2400
|
+
}
|
|
2401
|
+
return { frame_no: 0, frames_synced: 0 };
|
|
2402
|
+
},
|
|
2403
|
+
close() {
|
|
2404
|
+
closed = true;
|
|
2405
|
+
prismaClientPromise = null;
|
|
2406
|
+
compatibilityBootstrapPromise = null;
|
|
2407
|
+
void prisma.$disconnect?.();
|
|
2408
|
+
},
|
|
2409
|
+
get closed() {
|
|
2410
|
+
return closed;
|
|
2411
|
+
},
|
|
2412
|
+
get protocol() {
|
|
2413
|
+
return "prisma-postgres";
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
return adapter;
|
|
2417
|
+
}
|
|
2418
|
+
var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
|
|
2419
|
+
var init_database_adapter = __esm({
|
|
2420
|
+
"src/lib/database-adapter.ts"() {
|
|
2421
|
+
"use strict";
|
|
2422
|
+
VIEW_MAPPINGS = [
|
|
2423
|
+
{ view: "memories", source: "memory.memory_records" },
|
|
2424
|
+
{ view: "tasks", source: "memory.tasks" },
|
|
2425
|
+
{ view: "behaviors", source: "memory.behaviors" },
|
|
2426
|
+
{ view: "entities", source: "memory.entities" },
|
|
2427
|
+
{ view: "relationships", source: "memory.relationships" },
|
|
2428
|
+
{ view: "entity_memories", source: "memory.entity_memories" },
|
|
2429
|
+
{ view: "entity_aliases", source: "memory.entity_aliases" },
|
|
2430
|
+
{ view: "notifications", source: "memory.notifications" },
|
|
2431
|
+
{ view: "messages", source: "memory.messages" },
|
|
2432
|
+
{ view: "users", source: "wiki.users" },
|
|
2433
|
+
{ view: "workspaces", source: "wiki.workspaces" },
|
|
2434
|
+
{ view: "workspace_users", source: "wiki.workspace_users" },
|
|
2435
|
+
{ view: "documents", source: "wiki.workspace_documents" },
|
|
2436
|
+
{ view: "chats", source: "wiki.workspace_chats" }
|
|
2437
|
+
];
|
|
2438
|
+
UPSERT_KEYS = {
|
|
2439
|
+
memories: ["id"],
|
|
2440
|
+
tasks: ["id"],
|
|
2441
|
+
behaviors: ["id"],
|
|
2442
|
+
entities: ["id"],
|
|
2443
|
+
relationships: ["id"],
|
|
2444
|
+
entity_aliases: ["alias"],
|
|
2445
|
+
notifications: ["id"],
|
|
2446
|
+
messages: ["id"],
|
|
2447
|
+
users: ["id"],
|
|
2448
|
+
workspaces: ["id"],
|
|
2449
|
+
workspace_users: ["id"],
|
|
2450
|
+
documents: ["id"],
|
|
2451
|
+
chats: ["id"]
|
|
2452
|
+
};
|
|
2453
|
+
BOOLEAN_COLUMNS_BY_TABLE = {
|
|
2454
|
+
memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
|
|
2455
|
+
behaviors: /* @__PURE__ */ new Set(["active"]),
|
|
2456
|
+
notifications: /* @__PURE__ */ new Set(["read"]),
|
|
2457
|
+
users: /* @__PURE__ */ new Set(["has_personal_memory"])
|
|
2458
|
+
};
|
|
2459
|
+
BOOLEAN_COLUMN_NAMES = new Set(
|
|
2460
|
+
Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
|
|
2461
|
+
);
|
|
2462
|
+
IMMEDIATE_FALLBACK_PATTERNS = [
|
|
2463
|
+
/\bPRAGMA\b/i,
|
|
2464
|
+
/\bsqlite_master\b/i,
|
|
2465
|
+
/(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
|
|
2466
|
+
/\bMATCH\b/i,
|
|
2467
|
+
/\bvector_distance_cos\s*\(/i,
|
|
2468
|
+
/\bjson_extract\s*\(/i,
|
|
2469
|
+
/\bjulianday\s*\(/i,
|
|
2470
|
+
/\bstrftime\s*\(/i,
|
|
2471
|
+
/\blast_insert_rowid\s*\(/i
|
|
2472
|
+
];
|
|
2473
|
+
prismaClientPromise = null;
|
|
2474
|
+
compatibilityBootstrapPromise = null;
|
|
1516
2475
|
}
|
|
1517
2476
|
});
|
|
1518
2477
|
|
|
@@ -1586,7 +2545,7 @@ __export(db_daemon_client_exports, {
|
|
|
1586
2545
|
createDaemonDbClient: () => createDaemonDbClient,
|
|
1587
2546
|
initDaemonDbClient: () => initDaemonDbClient
|
|
1588
2547
|
});
|
|
1589
|
-
function
|
|
2548
|
+
function normalizeStatement2(stmt) {
|
|
1590
2549
|
if (typeof stmt === "string") {
|
|
1591
2550
|
return { sql: stmt, args: [] };
|
|
1592
2551
|
}
|
|
@@ -1610,7 +2569,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1610
2569
|
if (!_useDaemon || !isClientConnected()) {
|
|
1611
2570
|
return fallbackClient.execute(stmt);
|
|
1612
2571
|
}
|
|
1613
|
-
const { sql, args: args2 } =
|
|
2572
|
+
const { sql, args: args2 } = normalizeStatement2(stmt);
|
|
1614
2573
|
const response = await sendDaemonRequest({
|
|
1615
2574
|
type: "db-execute",
|
|
1616
2575
|
sql,
|
|
@@ -1635,7 +2594,7 @@ function createDaemonDbClient(fallbackClient) {
|
|
|
1635
2594
|
if (!_useDaemon || !isClientConnected()) {
|
|
1636
2595
|
return fallbackClient.batch(stmts, mode);
|
|
1637
2596
|
}
|
|
1638
|
-
const statements = stmts.map(
|
|
2597
|
+
const statements = stmts.map(normalizeStatement2);
|
|
1639
2598
|
const response = await sendDaemonRequest({
|
|
1640
2599
|
type: "db-batch",
|
|
1641
2600
|
statements,
|
|
@@ -1730,6 +2689,18 @@ __export(database_exports, {
|
|
|
1730
2689
|
});
|
|
1731
2690
|
import { createClient } from "@libsql/client";
|
|
1732
2691
|
async function initDatabase(config) {
|
|
2692
|
+
if (_walCheckpointTimer) {
|
|
2693
|
+
clearInterval(_walCheckpointTimer);
|
|
2694
|
+
_walCheckpointTimer = null;
|
|
2695
|
+
}
|
|
2696
|
+
if (_daemonClient) {
|
|
2697
|
+
_daemonClient.close();
|
|
2698
|
+
_daemonClient = null;
|
|
2699
|
+
}
|
|
2700
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
2701
|
+
_adapterClient.close();
|
|
2702
|
+
}
|
|
2703
|
+
_adapterClient = null;
|
|
1733
2704
|
if (_client) {
|
|
1734
2705
|
_client.close();
|
|
1735
2706
|
_client = null;
|
|
@@ -1743,6 +2714,7 @@ async function initDatabase(config) {
|
|
|
1743
2714
|
}
|
|
1744
2715
|
_client = createClient(opts);
|
|
1745
2716
|
_resilientClient = wrapWithRetry(_client);
|
|
2717
|
+
_adapterClient = _resilientClient;
|
|
1746
2718
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
1747
2719
|
});
|
|
1748
2720
|
_client.execute("PRAGMA journal_mode = WAL").catch(() => {
|
|
@@ -1753,14 +2725,20 @@ async function initDatabase(config) {
|
|
|
1753
2725
|
});
|
|
1754
2726
|
}, 3e4);
|
|
1755
2727
|
_walCheckpointTimer.unref();
|
|
2728
|
+
if (process.env.DATABASE_URL) {
|
|
2729
|
+
_adapterClient = await createPrismaDbAdapter(_resilientClient);
|
|
2730
|
+
}
|
|
1756
2731
|
}
|
|
1757
2732
|
function isInitialized() {
|
|
1758
|
-
return _client !== null;
|
|
2733
|
+
return _adapterClient !== null || _client !== null;
|
|
1759
2734
|
}
|
|
1760
2735
|
function getClient() {
|
|
1761
|
-
if (!
|
|
2736
|
+
if (!_adapterClient) {
|
|
1762
2737
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
1763
2738
|
}
|
|
2739
|
+
if (process.env.DATABASE_URL) {
|
|
2740
|
+
return _adapterClient;
|
|
2741
|
+
}
|
|
1764
2742
|
if (process.env.EXE_IS_DAEMON === "1") {
|
|
1765
2743
|
return _resilientClient;
|
|
1766
2744
|
}
|
|
@@ -1770,6 +2748,7 @@ function getClient() {
|
|
|
1770
2748
|
return _resilientClient;
|
|
1771
2749
|
}
|
|
1772
2750
|
async function initDaemonClient() {
|
|
2751
|
+
if (process.env.DATABASE_URL) return;
|
|
1773
2752
|
if (process.env.EXE_IS_DAEMON === "1") return;
|
|
1774
2753
|
if (!_resilientClient) return;
|
|
1775
2754
|
try {
|
|
@@ -2066,6 +3045,7 @@ async function ensureSchema() {
|
|
|
2066
3045
|
project TEXT NOT NULL,
|
|
2067
3046
|
summary TEXT NOT NULL,
|
|
2068
3047
|
task_file TEXT,
|
|
3048
|
+
session_scope TEXT,
|
|
2069
3049
|
read INTEGER NOT NULL DEFAULT 0,
|
|
2070
3050
|
created_at TEXT NOT NULL
|
|
2071
3051
|
);
|
|
@@ -2074,7 +3054,7 @@ async function ensureSchema() {
|
|
|
2074
3054
|
ON notifications(read);
|
|
2075
3055
|
|
|
2076
3056
|
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
2077
|
-
ON notifications(agent_id);
|
|
3057
|
+
ON notifications(agent_id, session_scope);
|
|
2078
3058
|
|
|
2079
3059
|
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
2080
3060
|
ON notifications(task_file);
|
|
@@ -2112,6 +3092,7 @@ async function ensureSchema() {
|
|
|
2112
3092
|
target_agent TEXT NOT NULL,
|
|
2113
3093
|
target_project TEXT,
|
|
2114
3094
|
target_device TEXT NOT NULL DEFAULT 'local',
|
|
3095
|
+
session_scope TEXT,
|
|
2115
3096
|
content TEXT NOT NULL,
|
|
2116
3097
|
priority TEXT DEFAULT 'normal',
|
|
2117
3098
|
status TEXT DEFAULT 'pending',
|
|
@@ -2125,10 +3106,31 @@ async function ensureSchema() {
|
|
|
2125
3106
|
);
|
|
2126
3107
|
|
|
2127
3108
|
CREATE INDEX IF NOT EXISTS idx_messages_target
|
|
2128
|
-
ON messages(target_agent, status);
|
|
3109
|
+
ON messages(target_agent, session_scope, status);
|
|
2129
3110
|
|
|
2130
3111
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
|
|
2131
|
-
ON messages(target_agent, from_agent, server_seq);
|
|
3112
|
+
ON messages(target_agent, session_scope, from_agent, server_seq);
|
|
3113
|
+
`);
|
|
3114
|
+
try {
|
|
3115
|
+
await client.execute({
|
|
3116
|
+
sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
|
|
3117
|
+
args: []
|
|
3118
|
+
});
|
|
3119
|
+
} catch {
|
|
3120
|
+
}
|
|
3121
|
+
try {
|
|
3122
|
+
await client.execute({
|
|
3123
|
+
sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
|
|
3124
|
+
args: []
|
|
3125
|
+
});
|
|
3126
|
+
} catch {
|
|
3127
|
+
}
|
|
3128
|
+
await client.executeMultiple(`
|
|
3129
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
|
|
3130
|
+
ON notifications(agent_id, session_scope, read, created_at);
|
|
3131
|
+
|
|
3132
|
+
CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
|
|
3133
|
+
ON messages(target_agent, session_scope, status, created_at);
|
|
2132
3134
|
`);
|
|
2133
3135
|
try {
|
|
2134
3136
|
await client.execute({
|
|
@@ -2712,28 +3714,45 @@ async function ensureSchema() {
|
|
|
2712
3714
|
} catch {
|
|
2713
3715
|
}
|
|
2714
3716
|
}
|
|
3717
|
+
try {
|
|
3718
|
+
await client.execute({
|
|
3719
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
3720
|
+
args: []
|
|
3721
|
+
});
|
|
3722
|
+
} catch {
|
|
3723
|
+
}
|
|
2715
3724
|
}
|
|
2716
3725
|
async function disposeDatabase() {
|
|
3726
|
+
if (_walCheckpointTimer) {
|
|
3727
|
+
clearInterval(_walCheckpointTimer);
|
|
3728
|
+
_walCheckpointTimer = null;
|
|
3729
|
+
}
|
|
2717
3730
|
if (_daemonClient) {
|
|
2718
3731
|
_daemonClient.close();
|
|
2719
3732
|
_daemonClient = null;
|
|
2720
3733
|
}
|
|
3734
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
3735
|
+
_adapterClient.close();
|
|
3736
|
+
}
|
|
3737
|
+
_adapterClient = null;
|
|
2721
3738
|
if (_client) {
|
|
2722
3739
|
_client.close();
|
|
2723
3740
|
_client = null;
|
|
2724
3741
|
_resilientClient = null;
|
|
2725
3742
|
}
|
|
2726
3743
|
}
|
|
2727
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
|
|
3744
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2728
3745
|
var init_database = __esm({
|
|
2729
3746
|
"src/lib/database.ts"() {
|
|
2730
3747
|
"use strict";
|
|
2731
3748
|
init_db_retry();
|
|
2732
3749
|
init_employees();
|
|
3750
|
+
init_database_adapter();
|
|
2733
3751
|
_client = null;
|
|
2734
3752
|
_resilientClient = null;
|
|
2735
3753
|
_walCheckpointTimer = null;
|
|
2736
3754
|
_daemonClient = null;
|
|
3755
|
+
_adapterClient = null;
|
|
2737
3756
|
initTurso = initDatabase;
|
|
2738
3757
|
disposeTurso = disposeDatabase;
|
|
2739
3758
|
}
|
|
@@ -2778,8 +3797,8 @@ __export(crdt_sync_exports, {
|
|
|
2778
3797
|
rebuildFromDb: () => rebuildFromDb
|
|
2779
3798
|
});
|
|
2780
3799
|
import * as Y from "yjs";
|
|
2781
|
-
import { readFileSync as
|
|
2782
|
-
import
|
|
3800
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
3801
|
+
import path10 from "path";
|
|
2783
3802
|
import { homedir } from "os";
|
|
2784
3803
|
function getStatePath() {
|
|
2785
3804
|
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
@@ -2791,9 +3810,9 @@ function initCrdtDoc() {
|
|
|
2791
3810
|
if (doc) return doc;
|
|
2792
3811
|
doc = new Y.Doc();
|
|
2793
3812
|
const sp = getStatePath();
|
|
2794
|
-
if (
|
|
3813
|
+
if (existsSync10(sp)) {
|
|
2795
3814
|
try {
|
|
2796
|
-
const state =
|
|
3815
|
+
const state = readFileSync7(sp);
|
|
2797
3816
|
Y.applyUpdate(doc, new Uint8Array(state));
|
|
2798
3817
|
} catch {
|
|
2799
3818
|
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
@@ -2935,10 +3954,10 @@ function persistState() {
|
|
|
2935
3954
|
if (!doc) return;
|
|
2936
3955
|
try {
|
|
2937
3956
|
const sp = getStatePath();
|
|
2938
|
-
const dir =
|
|
2939
|
-
if (!
|
|
3957
|
+
const dir = path10.dirname(sp);
|
|
3958
|
+
if (!existsSync10(dir)) mkdirSync3(dir, { recursive: true });
|
|
2940
3959
|
const state = Y.encodeStateAsUpdate(doc);
|
|
2941
|
-
|
|
3960
|
+
writeFileSync5(sp, Buffer.from(state));
|
|
2942
3961
|
} catch {
|
|
2943
3962
|
}
|
|
2944
3963
|
}
|
|
@@ -2979,7 +3998,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
|
2979
3998
|
var init_crdt_sync = __esm({
|
|
2980
3999
|
"src/lib/crdt-sync.ts"() {
|
|
2981
4000
|
"use strict";
|
|
2982
|
-
DEFAULT_STATE_PATH =
|
|
4001
|
+
DEFAULT_STATE_PATH = path10.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
2983
4002
|
_statePathOverride = null;
|
|
2984
4003
|
doc = null;
|
|
2985
4004
|
}
|
|
@@ -3011,39 +4030,107 @@ __export(cloud_sync_exports, {
|
|
|
3011
4030
|
cloudSync: () => cloudSync,
|
|
3012
4031
|
mergeConfig: () => mergeConfig,
|
|
3013
4032
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
4033
|
+
pushToPostgres: () => pushToPostgres,
|
|
3014
4034
|
recordRosterDeletion: () => recordRosterDeletion
|
|
3015
4035
|
});
|
|
3016
|
-
import { readFileSync as
|
|
3017
|
-
import
|
|
3018
|
-
import
|
|
4036
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, readdirSync, mkdirSync as mkdirSync4, appendFileSync, unlinkSync as unlinkSync5, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
4037
|
+
import crypto3 from "crypto";
|
|
4038
|
+
import path11 from "path";
|
|
3019
4039
|
import { homedir as homedir2 } from "os";
|
|
3020
4040
|
function sqlSafe(v) {
|
|
3021
4041
|
return v === void 0 ? null : v;
|
|
3022
4042
|
}
|
|
3023
4043
|
function logError(msg) {
|
|
3024
4044
|
try {
|
|
3025
|
-
const logPath =
|
|
4045
|
+
const logPath = path11.join(homedir2(), ".exe-os", "workers.log");
|
|
3026
4046
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
3027
4047
|
`);
|
|
3028
4048
|
} catch {
|
|
3029
4049
|
}
|
|
3030
4050
|
}
|
|
4051
|
+
function loadPgClient() {
|
|
4052
|
+
if (_pgFailed) return null;
|
|
4053
|
+
const postgresUrl = process.env.DATABASE_URL;
|
|
4054
|
+
const configPath = path11.join(EXE_AI_DIR, "config.json");
|
|
4055
|
+
let cloudPostgresUrl;
|
|
4056
|
+
try {
|
|
4057
|
+
if (existsSync11(configPath)) {
|
|
4058
|
+
const cfg = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
4059
|
+
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
4060
|
+
if (cfg.cloud?.syncToPostgres === false) {
|
|
4061
|
+
_pgFailed = true;
|
|
4062
|
+
return null;
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
} catch {
|
|
4066
|
+
}
|
|
4067
|
+
const url = postgresUrl || cloudPostgresUrl;
|
|
4068
|
+
if (!url) {
|
|
4069
|
+
_pgFailed = true;
|
|
4070
|
+
return null;
|
|
4071
|
+
}
|
|
4072
|
+
if (!_pgPromise) {
|
|
4073
|
+
_pgPromise = (async () => {
|
|
4074
|
+
const { createRequire: createRequire3 } = await import("module");
|
|
4075
|
+
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
4076
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(homedir2(), "exe-db");
|
|
4077
|
+
const req = createRequire3(path11.join(exeDbRoot, "package.json"));
|
|
4078
|
+
const entry = req.resolve("@prisma/client");
|
|
4079
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
4080
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4081
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
4082
|
+
return new Ctor();
|
|
4083
|
+
})().catch(() => {
|
|
4084
|
+
_pgFailed = true;
|
|
4085
|
+
_pgPromise = null;
|
|
4086
|
+
throw new Error("pg_unavailable");
|
|
4087
|
+
});
|
|
4088
|
+
}
|
|
4089
|
+
return _pgPromise;
|
|
4090
|
+
}
|
|
4091
|
+
async function pushToPostgres(records) {
|
|
4092
|
+
const loader = loadPgClient();
|
|
4093
|
+
if (!loader) return 0;
|
|
4094
|
+
let prisma;
|
|
4095
|
+
try {
|
|
4096
|
+
prisma = await loader;
|
|
4097
|
+
} catch {
|
|
4098
|
+
return 0;
|
|
4099
|
+
}
|
|
4100
|
+
let inserted = 0;
|
|
4101
|
+
for (const rec of records) {
|
|
4102
|
+
try {
|
|
4103
|
+
await prisma.$executeRawUnsafe(
|
|
4104
|
+
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
4105
|
+
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
4106
|
+
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
4107
|
+
String(rec.id ?? ""),
|
|
4108
|
+
JSON.stringify(rec),
|
|
4109
|
+
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
4110
|
+
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
4111
|
+
);
|
|
4112
|
+
inserted++;
|
|
4113
|
+
} catch {
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
return inserted;
|
|
4117
|
+
}
|
|
3031
4118
|
async function withRosterLock(fn) {
|
|
3032
4119
|
try {
|
|
3033
4120
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3034
4121
|
closeSync2(fd);
|
|
3035
|
-
|
|
4122
|
+
writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3036
4123
|
} catch (err) {
|
|
3037
4124
|
if (err.code === "EEXIST") {
|
|
3038
4125
|
try {
|
|
3039
|
-
const ts = parseInt(
|
|
4126
|
+
const ts = parseInt(readFileSync8(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
3040
4127
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
3041
4128
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
3042
4129
|
}
|
|
3043
4130
|
unlinkSync5(ROSTER_LOCK_PATH);
|
|
3044
4131
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3045
4132
|
closeSync2(fd);
|
|
3046
|
-
|
|
4133
|
+
writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3047
4134
|
} catch (retryErr) {
|
|
3048
4135
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
3049
4136
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -3313,6 +4400,10 @@ async function cloudSync(config) {
|
|
|
3313
4400
|
const maxVersion = Number(records[records.length - 1].version);
|
|
3314
4401
|
const pushOk = await cloudPush(records, maxVersion, config);
|
|
3315
4402
|
if (!pushOk) break;
|
|
4403
|
+
try {
|
|
4404
|
+
await pushToPostgres(records);
|
|
4405
|
+
} catch {
|
|
4406
|
+
}
|
|
3316
4407
|
await client.execute({
|
|
3317
4408
|
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
3318
4409
|
args: [String(maxVersion)]
|
|
@@ -3417,8 +4508,8 @@ async function cloudSync(config) {
|
|
|
3417
4508
|
try {
|
|
3418
4509
|
const employees = await loadEmployees();
|
|
3419
4510
|
rosterResult.employees = employees.length;
|
|
3420
|
-
const idDir =
|
|
3421
|
-
if (
|
|
4511
|
+
const idDir = path11.join(EXE_AI_DIR, "identity");
|
|
4512
|
+
if (existsSync11(idDir)) {
|
|
3422
4513
|
rosterResult.identities = readdirSync(idDir).filter((f) => f.endsWith(".md")).length;
|
|
3423
4514
|
}
|
|
3424
4515
|
} catch {
|
|
@@ -3439,62 +4530,62 @@ async function cloudSync(config) {
|
|
|
3439
4530
|
function recordRosterDeletion(name) {
|
|
3440
4531
|
let deletions = [];
|
|
3441
4532
|
try {
|
|
3442
|
-
if (
|
|
3443
|
-
deletions = JSON.parse(
|
|
4533
|
+
if (existsSync11(ROSTER_DELETIONS_PATH)) {
|
|
4534
|
+
deletions = JSON.parse(readFileSync8(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
3444
4535
|
}
|
|
3445
4536
|
} catch {
|
|
3446
4537
|
}
|
|
3447
4538
|
if (!deletions.includes(name)) deletions.push(name);
|
|
3448
|
-
|
|
4539
|
+
writeFileSync6(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
3449
4540
|
}
|
|
3450
4541
|
function consumeRosterDeletions() {
|
|
3451
4542
|
try {
|
|
3452
|
-
if (!
|
|
3453
|
-
const deletions = JSON.parse(
|
|
3454
|
-
|
|
4543
|
+
if (!existsSync11(ROSTER_DELETIONS_PATH)) return [];
|
|
4544
|
+
const deletions = JSON.parse(readFileSync8(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
4545
|
+
writeFileSync6(ROSTER_DELETIONS_PATH, "[]");
|
|
3455
4546
|
return deletions;
|
|
3456
4547
|
} catch {
|
|
3457
4548
|
return [];
|
|
3458
4549
|
}
|
|
3459
4550
|
}
|
|
3460
4551
|
function buildRosterBlob(paths) {
|
|
3461
|
-
const rosterPath = paths?.rosterPath ??
|
|
3462
|
-
const identityDir = paths?.identityDir ??
|
|
3463
|
-
const configPath = paths?.configPath ??
|
|
4552
|
+
const rosterPath = paths?.rosterPath ?? path11.join(EXE_AI_DIR, "exe-employees.json");
|
|
4553
|
+
const identityDir = paths?.identityDir ?? path11.join(EXE_AI_DIR, "identity");
|
|
4554
|
+
const configPath = paths?.configPath ?? path11.join(EXE_AI_DIR, "config.json");
|
|
3464
4555
|
let roster = [];
|
|
3465
|
-
if (
|
|
4556
|
+
if (existsSync11(rosterPath)) {
|
|
3466
4557
|
try {
|
|
3467
|
-
roster = JSON.parse(
|
|
4558
|
+
roster = JSON.parse(readFileSync8(rosterPath, "utf-8"));
|
|
3468
4559
|
} catch {
|
|
3469
4560
|
}
|
|
3470
4561
|
}
|
|
3471
4562
|
const identities = {};
|
|
3472
|
-
if (
|
|
4563
|
+
if (existsSync11(identityDir)) {
|
|
3473
4564
|
for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
3474
4565
|
try {
|
|
3475
|
-
identities[file] =
|
|
4566
|
+
identities[file] = readFileSync8(path11.join(identityDir, file), "utf-8");
|
|
3476
4567
|
} catch {
|
|
3477
4568
|
}
|
|
3478
4569
|
}
|
|
3479
4570
|
}
|
|
3480
4571
|
let config;
|
|
3481
|
-
if (
|
|
4572
|
+
if (existsSync11(configPath)) {
|
|
3482
4573
|
try {
|
|
3483
|
-
config = JSON.parse(
|
|
4574
|
+
config = JSON.parse(readFileSync8(configPath, "utf-8"));
|
|
3484
4575
|
} catch {
|
|
3485
4576
|
}
|
|
3486
4577
|
}
|
|
3487
4578
|
let agentConfig;
|
|
3488
|
-
const agentConfigPath =
|
|
3489
|
-
if (
|
|
4579
|
+
const agentConfigPath = path11.join(EXE_AI_DIR, "agent-config.json");
|
|
4580
|
+
if (existsSync11(agentConfigPath)) {
|
|
3490
4581
|
try {
|
|
3491
|
-
agentConfig = JSON.parse(
|
|
4582
|
+
agentConfig = JSON.parse(readFileSync8(agentConfigPath, "utf-8"));
|
|
3492
4583
|
} catch {
|
|
3493
4584
|
}
|
|
3494
4585
|
}
|
|
3495
4586
|
const deletedNames = consumeRosterDeletions();
|
|
3496
4587
|
const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
|
|
3497
|
-
const hash =
|
|
4588
|
+
const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
3498
4589
|
return { roster, identities, config, agentConfig, deletedNames, version: hash };
|
|
3499
4590
|
}
|
|
3500
4591
|
async function cloudPushRoster(config) {
|
|
@@ -3564,23 +4655,24 @@ async function cloudPullRoster(config) {
|
|
|
3564
4655
|
}
|
|
3565
4656
|
}
|
|
3566
4657
|
function mergeConfig(remoteConfig, configPath) {
|
|
3567
|
-
const cfgPath = configPath ??
|
|
4658
|
+
const cfgPath = configPath ?? path11.join(EXE_AI_DIR, "config.json");
|
|
3568
4659
|
let local = {};
|
|
3569
|
-
if (
|
|
4660
|
+
if (existsSync11(cfgPath)) {
|
|
3570
4661
|
try {
|
|
3571
|
-
local = JSON.parse(
|
|
4662
|
+
local = JSON.parse(readFileSync8(cfgPath, "utf-8"));
|
|
3572
4663
|
} catch {
|
|
3573
4664
|
}
|
|
3574
4665
|
}
|
|
3575
4666
|
const merged = { ...remoteConfig, ...local };
|
|
3576
|
-
const dir =
|
|
3577
|
-
|
|
3578
|
-
|
|
4667
|
+
const dir = path11.dirname(cfgPath);
|
|
4668
|
+
ensurePrivateDirSync(dir);
|
|
4669
|
+
writeFileSync6(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
4670
|
+
enforcePrivateFileSync(cfgPath);
|
|
3579
4671
|
}
|
|
3580
4672
|
async function mergeRosterFromRemote(remote, paths) {
|
|
3581
4673
|
return withRosterLock(async () => {
|
|
3582
4674
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
3583
|
-
const identityDir = paths?.identityDir ??
|
|
4675
|
+
const identityDir = paths?.identityDir ?? path11.join(EXE_AI_DIR, "identity");
|
|
3584
4676
|
const localEmployees = await loadEmployees(rosterPath);
|
|
3585
4677
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
3586
4678
|
let added = 0;
|
|
@@ -3601,15 +4693,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3601
4693
|
) ?? lookupKey;
|
|
3602
4694
|
const remoteIdentity = remote.identities[matchedKey];
|
|
3603
4695
|
if (remoteIdentity) {
|
|
3604
|
-
if (!
|
|
3605
|
-
const idPath =
|
|
4696
|
+
if (!existsSync11(identityDir)) mkdirSync4(identityDir, { recursive: true });
|
|
4697
|
+
const idPath = path11.join(identityDir, `${remoteEmp.name}.md`);
|
|
3606
4698
|
let localIdentity = null;
|
|
3607
4699
|
try {
|
|
3608
|
-
localIdentity =
|
|
4700
|
+
localIdentity = existsSync11(idPath) ? readFileSync8(idPath, "utf-8") : null;
|
|
3609
4701
|
} catch {
|
|
3610
4702
|
}
|
|
3611
4703
|
if (localIdentity !== remoteIdentity) {
|
|
3612
|
-
|
|
4704
|
+
writeFileSync6(idPath, remoteIdentity, "utf-8");
|
|
3613
4705
|
identitiesUpdated++;
|
|
3614
4706
|
}
|
|
3615
4707
|
}
|
|
@@ -3635,16 +4727,18 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3635
4727
|
}
|
|
3636
4728
|
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
3637
4729
|
try {
|
|
3638
|
-
const agentConfigPath =
|
|
4730
|
+
const agentConfigPath = path11.join(EXE_AI_DIR, "agent-config.json");
|
|
3639
4731
|
let local = {};
|
|
3640
|
-
if (
|
|
4732
|
+
if (existsSync11(agentConfigPath)) {
|
|
3641
4733
|
try {
|
|
3642
|
-
local = JSON.parse(
|
|
4734
|
+
local = JSON.parse(readFileSync8(agentConfigPath, "utf-8"));
|
|
3643
4735
|
} catch {
|
|
3644
4736
|
}
|
|
3645
4737
|
}
|
|
3646
4738
|
const merged = { ...remote.agentConfig, ...local };
|
|
3647
|
-
|
|
4739
|
+
ensurePrivateDirSync(path11.dirname(agentConfigPath));
|
|
4740
|
+
writeFileSync6(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
4741
|
+
enforcePrivateFileSync(agentConfigPath);
|
|
3648
4742
|
} catch {
|
|
3649
4743
|
}
|
|
3650
4744
|
}
|
|
@@ -4068,7 +5162,7 @@ async function cloudPullDocuments(config) {
|
|
|
4068
5162
|
}
|
|
4069
5163
|
return { pulled };
|
|
4070
5164
|
}
|
|
4071
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
|
|
5165
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
|
|
4072
5166
|
var init_cloud_sync = __esm({
|
|
4073
5167
|
"src/lib/cloud-sync.ts"() {
|
|
4074
5168
|
"use strict";
|
|
@@ -4079,12 +5173,15 @@ var init_cloud_sync = __esm({
|
|
|
4079
5173
|
init_config();
|
|
4080
5174
|
init_crdt_sync();
|
|
4081
5175
|
init_employees();
|
|
5176
|
+
init_secure_files();
|
|
4082
5177
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
4083
5178
|
FETCH_TIMEOUT_MS = 3e4;
|
|
4084
5179
|
PUSH_BATCH_SIZE = 5e3;
|
|
4085
|
-
ROSTER_LOCK_PATH =
|
|
5180
|
+
ROSTER_LOCK_PATH = path11.join(EXE_AI_DIR, "roster-merge.lock");
|
|
4086
5181
|
LOCK_STALE_MS = 3e4;
|
|
4087
|
-
|
|
5182
|
+
_pgPromise = null;
|
|
5183
|
+
_pgFailed = false;
|
|
5184
|
+
ROSTER_DELETIONS_PATH = path11.join(EXE_AI_DIR, "roster-deletions.json");
|
|
4088
5185
|
}
|
|
4089
5186
|
});
|
|
4090
5187
|
|
|
@@ -4094,37 +5191,39 @@ __export(preferences_exports, {
|
|
|
4094
5191
|
loadPreferences: () => loadPreferences,
|
|
4095
5192
|
savePreferences: () => savePreferences
|
|
4096
5193
|
});
|
|
4097
|
-
import { existsSync as
|
|
4098
|
-
import
|
|
4099
|
-
import
|
|
4100
|
-
function loadPreferences(homeDir =
|
|
4101
|
-
const configPath =
|
|
4102
|
-
if (!
|
|
5194
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
5195
|
+
import path12 from "path";
|
|
5196
|
+
import os7 from "os";
|
|
5197
|
+
function loadPreferences(homeDir = os7.homedir()) {
|
|
5198
|
+
const configPath = path12.join(homeDir, ".exe-os", "config.json");
|
|
5199
|
+
if (!existsSync12(configPath)) return {};
|
|
4103
5200
|
try {
|
|
4104
|
-
const config = JSON.parse(
|
|
5201
|
+
const config = JSON.parse(readFileSync9(configPath, "utf-8"));
|
|
4105
5202
|
return config.preferences ?? {};
|
|
4106
5203
|
} catch {
|
|
4107
5204
|
return {};
|
|
4108
5205
|
}
|
|
4109
5206
|
}
|
|
4110
|
-
function savePreferences(prefs, homeDir =
|
|
4111
|
-
const configDir =
|
|
4112
|
-
const configPath =
|
|
4113
|
-
|
|
5207
|
+
function savePreferences(prefs, homeDir = os7.homedir()) {
|
|
5208
|
+
const configDir = path12.join(homeDir, ".exe-os");
|
|
5209
|
+
const configPath = path12.join(configDir, "config.json");
|
|
5210
|
+
ensurePrivateDirSync(configDir);
|
|
4114
5211
|
let config = {};
|
|
4115
|
-
if (
|
|
5212
|
+
if (existsSync12(configPath)) {
|
|
4116
5213
|
try {
|
|
4117
|
-
config = JSON.parse(
|
|
5214
|
+
config = JSON.parse(readFileSync9(configPath, "utf-8"));
|
|
4118
5215
|
} catch {
|
|
4119
5216
|
config = {};
|
|
4120
5217
|
}
|
|
4121
5218
|
}
|
|
4122
5219
|
config.preferences = prefs;
|
|
4123
|
-
|
|
5220
|
+
writeFileSync7(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
5221
|
+
enforcePrivateFileSync(configPath);
|
|
4124
5222
|
}
|
|
4125
5223
|
var init_preferences = __esm({
|
|
4126
5224
|
"src/lib/preferences.ts"() {
|
|
4127
5225
|
"use strict";
|
|
5226
|
+
init_secure_files();
|
|
4128
5227
|
}
|
|
4129
5228
|
});
|
|
4130
5229
|
|
|
@@ -4895,17 +5994,17 @@ __export(identity_exports, {
|
|
|
4895
5994
|
listIdentities: () => listIdentities,
|
|
4896
5995
|
updateIdentity: () => updateIdentity
|
|
4897
5996
|
});
|
|
4898
|
-
import { existsSync as
|
|
5997
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
4899
5998
|
import { readdirSync as readdirSync2 } from "fs";
|
|
4900
|
-
import
|
|
5999
|
+
import path13 from "path";
|
|
4901
6000
|
import { createHash as createHash2 } from "crypto";
|
|
4902
6001
|
function ensureDir() {
|
|
4903
|
-
if (!
|
|
4904
|
-
mkdirSync5(
|
|
6002
|
+
if (!existsSync13(IDENTITY_DIR2)) {
|
|
6003
|
+
mkdirSync5(IDENTITY_DIR2, { recursive: true });
|
|
4905
6004
|
}
|
|
4906
6005
|
}
|
|
4907
6006
|
function identityPath(agentId) {
|
|
4908
|
-
return
|
|
6007
|
+
return path13.join(IDENTITY_DIR2, `${agentId}.md`);
|
|
4909
6008
|
}
|
|
4910
6009
|
function parseFrontmatter(raw) {
|
|
4911
6010
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
@@ -4946,8 +6045,8 @@ function contentHash(content) {
|
|
|
4946
6045
|
}
|
|
4947
6046
|
function getIdentity(agentId) {
|
|
4948
6047
|
const filePath = identityPath(agentId);
|
|
4949
|
-
if (!
|
|
4950
|
-
const raw =
|
|
6048
|
+
if (!existsSync13(filePath)) return null;
|
|
6049
|
+
const raw = readFileSync10(filePath, "utf-8");
|
|
4951
6050
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
4952
6051
|
return {
|
|
4953
6052
|
agentId,
|
|
@@ -4961,7 +6060,7 @@ async function updateIdentity(agentId, content, updatedBy) {
|
|
|
4961
6060
|
ensureDir();
|
|
4962
6061
|
const filePath = identityPath(agentId);
|
|
4963
6062
|
const hash = contentHash(content);
|
|
4964
|
-
|
|
6063
|
+
writeFileSync8(filePath, content, "utf-8");
|
|
4965
6064
|
try {
|
|
4966
6065
|
const client = getClient();
|
|
4967
6066
|
await client.execute({
|
|
@@ -4978,7 +6077,7 @@ async function updateIdentity(agentId, content, updatedBy) {
|
|
|
4978
6077
|
}
|
|
4979
6078
|
function listIdentities() {
|
|
4980
6079
|
ensureDir();
|
|
4981
|
-
const files = readdirSync2(
|
|
6080
|
+
const files = readdirSync2(IDENTITY_DIR2).filter((f) => f.endsWith(".md"));
|
|
4982
6081
|
const results = [];
|
|
4983
6082
|
for (const file of files) {
|
|
4984
6083
|
const agentId = file.replace(".md", "");
|
|
@@ -5011,13 +6110,13 @@ ${teamLines.join("\n")}`);
|
|
|
5011
6110
|
}
|
|
5012
6111
|
return parts.join("\n\n");
|
|
5013
6112
|
}
|
|
5014
|
-
var
|
|
6113
|
+
var IDENTITY_DIR2;
|
|
5015
6114
|
var init_identity = __esm({
|
|
5016
6115
|
"src/lib/identity.ts"() {
|
|
5017
6116
|
"use strict";
|
|
5018
6117
|
init_config();
|
|
5019
6118
|
init_database();
|
|
5020
|
-
|
|
6119
|
+
IDENTITY_DIR2 = path13.join(EXE_AI_DIR, "identity");
|
|
5021
6120
|
}
|
|
5022
6121
|
});
|
|
5023
6122
|
|
|
@@ -5568,36 +6667,36 @@ __export(session_wrappers_exports, {
|
|
|
5568
6667
|
generateSessionWrappers: () => generateSessionWrappers
|
|
5569
6668
|
});
|
|
5570
6669
|
import {
|
|
5571
|
-
existsSync as
|
|
5572
|
-
readFileSync as
|
|
5573
|
-
writeFileSync as
|
|
6670
|
+
existsSync as existsSync14,
|
|
6671
|
+
readFileSync as readFileSync11,
|
|
6672
|
+
writeFileSync as writeFileSync9,
|
|
5574
6673
|
mkdirSync as mkdirSync6,
|
|
5575
|
-
chmodSync,
|
|
6674
|
+
chmodSync as chmodSync2,
|
|
5576
6675
|
readdirSync as readdirSync3,
|
|
5577
6676
|
unlinkSync as unlinkSync6
|
|
5578
6677
|
} from "fs";
|
|
5579
|
-
import
|
|
6678
|
+
import path14 from "path";
|
|
5580
6679
|
import { homedir as homedir3 } from "os";
|
|
5581
6680
|
function generateSessionWrappers(packageRoot, homeDir) {
|
|
5582
6681
|
const home = homeDir ?? homedir3();
|
|
5583
|
-
const binDir =
|
|
5584
|
-
const rosterPath =
|
|
6682
|
+
const binDir = path14.join(home, ".exe-os", "bin");
|
|
6683
|
+
const rosterPath = path14.join(home, ".exe-os", "exe-employees.json");
|
|
5585
6684
|
mkdirSync6(binDir, { recursive: true });
|
|
5586
|
-
const exeStartDst =
|
|
6685
|
+
const exeStartDst = path14.join(binDir, "exe-start");
|
|
5587
6686
|
const candidates = [
|
|
5588
|
-
|
|
5589
|
-
|
|
6687
|
+
path14.join(packageRoot, "dist", "bin", "exe-start.sh"),
|
|
6688
|
+
path14.join(packageRoot, "src", "bin", "exe-start.sh")
|
|
5590
6689
|
];
|
|
5591
6690
|
for (const src of candidates) {
|
|
5592
|
-
if (
|
|
5593
|
-
|
|
5594
|
-
|
|
6691
|
+
if (existsSync14(src)) {
|
|
6692
|
+
writeFileSync9(exeStartDst, readFileSync11(src));
|
|
6693
|
+
chmodSync2(exeStartDst, 493);
|
|
5595
6694
|
break;
|
|
5596
6695
|
}
|
|
5597
6696
|
}
|
|
5598
6697
|
let employees = [];
|
|
5599
6698
|
try {
|
|
5600
|
-
employees = JSON.parse(
|
|
6699
|
+
employees = JSON.parse(readFileSync11(rosterPath, "utf8"));
|
|
5601
6700
|
} catch {
|
|
5602
6701
|
return { created: 0, pathConfigured: false };
|
|
5603
6702
|
}
|
|
@@ -5607,9 +6706,9 @@ function generateSessionWrappers(packageRoot, homeDir) {
|
|
|
5607
6706
|
try {
|
|
5608
6707
|
for (const f of readdirSync3(binDir)) {
|
|
5609
6708
|
if (f === "exe-start") continue;
|
|
5610
|
-
const fPath =
|
|
6709
|
+
const fPath = path14.join(binDir, f);
|
|
5611
6710
|
try {
|
|
5612
|
-
const content =
|
|
6711
|
+
const content = readFileSync11(fPath, "utf8");
|
|
5613
6712
|
if (content.includes("exe-start")) {
|
|
5614
6713
|
unlinkSync6(fPath);
|
|
5615
6714
|
}
|
|
@@ -5624,31 +6723,31 @@ exec "${exeStartDst}" "$0" "$@"
|
|
|
5624
6723
|
`;
|
|
5625
6724
|
for (const emp of employees) {
|
|
5626
6725
|
for (let n = 1; n <= MAX_N; n++) {
|
|
5627
|
-
const wrapperPath =
|
|
5628
|
-
|
|
5629
|
-
|
|
6726
|
+
const wrapperPath = path14.join(binDir, `${emp.name}${n}`);
|
|
6727
|
+
writeFileSync9(wrapperPath, wrapperContent);
|
|
6728
|
+
chmodSync2(wrapperPath, 493);
|
|
5630
6729
|
created++;
|
|
5631
6730
|
}
|
|
5632
6731
|
}
|
|
5633
6732
|
const codexLauncherCandidates = [
|
|
5634
|
-
|
|
5635
|
-
|
|
6733
|
+
path14.join(packageRoot, "dist", "bin", "exe-start-codex.js"),
|
|
6734
|
+
path14.join(packageRoot, "src", "bin", "exe-start-codex.ts")
|
|
5636
6735
|
];
|
|
5637
6736
|
let codexLauncher = null;
|
|
5638
6737
|
for (const c of codexLauncherCandidates) {
|
|
5639
|
-
if (
|
|
6738
|
+
if (existsSync14(c)) {
|
|
5640
6739
|
codexLauncher = c;
|
|
5641
6740
|
break;
|
|
5642
6741
|
}
|
|
5643
6742
|
}
|
|
5644
6743
|
if (codexLauncher) {
|
|
5645
6744
|
for (const emp of employees) {
|
|
5646
|
-
const wrapperPath =
|
|
6745
|
+
const wrapperPath = path14.join(binDir, `${emp.name}-codex`);
|
|
5647
6746
|
const content = `#!/bin/bash
|
|
5648
6747
|
exec node "${codexLauncher}" --agent ${emp.name} "$@"
|
|
5649
6748
|
`;
|
|
5650
|
-
|
|
5651
|
-
|
|
6749
|
+
writeFileSync9(wrapperPath, content);
|
|
6750
|
+
chmodSync2(wrapperPath, 493);
|
|
5652
6751
|
created++;
|
|
5653
6752
|
}
|
|
5654
6753
|
}
|
|
@@ -5666,24 +6765,24 @@ export PATH="${binDir}:$PATH"
|
|
|
5666
6765
|
const shell = process.env.SHELL ?? "/bin/bash";
|
|
5667
6766
|
const profilePaths = [];
|
|
5668
6767
|
if (shell.includes("zsh")) {
|
|
5669
|
-
profilePaths.push(
|
|
6768
|
+
profilePaths.push(path14.join(home, ".zshrc"));
|
|
5670
6769
|
} else if (shell.includes("bash")) {
|
|
5671
|
-
profilePaths.push(
|
|
5672
|
-
profilePaths.push(
|
|
6770
|
+
profilePaths.push(path14.join(home, ".bashrc"));
|
|
6771
|
+
profilePaths.push(path14.join(home, ".bash_profile"));
|
|
5673
6772
|
} else {
|
|
5674
|
-
profilePaths.push(
|
|
6773
|
+
profilePaths.push(path14.join(home, ".profile"));
|
|
5675
6774
|
}
|
|
5676
6775
|
for (const profilePath of profilePaths) {
|
|
5677
6776
|
try {
|
|
5678
6777
|
let content = "";
|
|
5679
6778
|
try {
|
|
5680
|
-
content =
|
|
6779
|
+
content = readFileSync11(profilePath, "utf8");
|
|
5681
6780
|
} catch {
|
|
5682
6781
|
}
|
|
5683
6782
|
if (content.includes(".exe-os/bin")) {
|
|
5684
6783
|
return false;
|
|
5685
6784
|
}
|
|
5686
|
-
|
|
6785
|
+
writeFileSync9(profilePath, content + exportLine);
|
|
5687
6786
|
return true;
|
|
5688
6787
|
} catch {
|
|
5689
6788
|
continue;
|
|
@@ -5702,14 +6801,14 @@ var init_session_wrappers = __esm({
|
|
|
5702
6801
|
// src/lib/setup-wizard.ts
|
|
5703
6802
|
init_config();
|
|
5704
6803
|
init_keychain();
|
|
5705
|
-
import
|
|
5706
|
-
import { existsSync as
|
|
5707
|
-
import
|
|
5708
|
-
import
|
|
6804
|
+
import crypto4 from "crypto";
|
|
6805
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync10, unlinkSync as unlinkSync7 } from "fs";
|
|
6806
|
+
import os8 from "os";
|
|
6807
|
+
import path15 from "path";
|
|
5709
6808
|
import { createInterface } from "readline";
|
|
5710
6809
|
|
|
5711
6810
|
// src/lib/model-downloader.ts
|
|
5712
|
-
import { createWriteStream, createReadStream, existsSync as
|
|
6811
|
+
import { createWriteStream, createReadStream, existsSync as existsSync4, unlinkSync, renameSync as renameSync2 } from "fs";
|
|
5713
6812
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
5714
6813
|
import { createHash } from "crypto";
|
|
5715
6814
|
import path3 from "path";
|
|
@@ -5722,7 +6821,7 @@ async function downloadModel(opts) {
|
|
|
5722
6821
|
const destPath = path3.join(destDir, LOCAL_FILENAME);
|
|
5723
6822
|
const tmpPath = destPath + ".tmp";
|
|
5724
6823
|
await mkdir3(destDir, { recursive: true });
|
|
5725
|
-
if (
|
|
6824
|
+
if (existsSync4(destPath)) {
|
|
5726
6825
|
const hash = await fileHash(destPath);
|
|
5727
6826
|
if (hash === EXPECTED_SHA256) {
|
|
5728
6827
|
return destPath;
|
|
@@ -5734,7 +6833,7 @@ async function downloadModel(opts) {
|
|
|
5734
6833
|
let downloaded = 0;
|
|
5735
6834
|
for (let attempt = 1; attempt <= MAX_RETRIES2; attempt++) {
|
|
5736
6835
|
try {
|
|
5737
|
-
if (
|
|
6836
|
+
if (existsSync4(tmpPath)) unlinkSync(tmpPath);
|
|
5738
6837
|
const response = await fetchFn(GGUF_URL, {
|
|
5739
6838
|
redirect: "follow",
|
|
5740
6839
|
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
|
|
@@ -5779,7 +6878,7 @@ async function downloadModel(opts) {
|
|
|
5779
6878
|
process.stderr.write(`
|
|
5780
6879
|
Download attempt ${attempt} failed, retrying...
|
|
5781
6880
|
`);
|
|
5782
|
-
if (
|
|
6881
|
+
if (existsSync4(tmpPath)) unlinkSync(tmpPath);
|
|
5783
6882
|
}
|
|
5784
6883
|
}
|
|
5785
6884
|
}
|
|
@@ -5797,32 +6896,32 @@ async function fileHash(filePath) {
|
|
|
5797
6896
|
|
|
5798
6897
|
// src/lib/setup-wizard.ts
|
|
5799
6898
|
function findPackageRoot2() {
|
|
5800
|
-
let dir =
|
|
5801
|
-
const root =
|
|
6899
|
+
let dir = path15.dirname(new URL(import.meta.url).pathname);
|
|
6900
|
+
const root = path15.parse(dir).root;
|
|
5802
6901
|
while (dir !== root) {
|
|
5803
|
-
const pkgPath =
|
|
5804
|
-
if (
|
|
6902
|
+
const pkgPath = path15.join(dir, "package.json");
|
|
6903
|
+
if (existsSync15(pkgPath)) {
|
|
5805
6904
|
try {
|
|
5806
|
-
const pkg = JSON.parse(
|
|
6905
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
5807
6906
|
if (pkg.name === "@askexenow/exe-os" || pkg.name === "exe-os") return dir;
|
|
5808
6907
|
} catch {
|
|
5809
6908
|
}
|
|
5810
6909
|
}
|
|
5811
|
-
dir =
|
|
6910
|
+
dir = path15.dirname(dir);
|
|
5812
6911
|
}
|
|
5813
6912
|
return null;
|
|
5814
6913
|
}
|
|
5815
|
-
var SETUP_STATE_PATH =
|
|
6914
|
+
var SETUP_STATE_PATH = path15.join(os8.homedir(), ".exe-os", "setup-state.json");
|
|
5816
6915
|
function loadSetupState() {
|
|
5817
6916
|
try {
|
|
5818
|
-
return JSON.parse(
|
|
6917
|
+
return JSON.parse(readFileSync12(SETUP_STATE_PATH, "utf8"));
|
|
5819
6918
|
} catch {
|
|
5820
6919
|
return { completedSteps: [], startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5821
6920
|
}
|
|
5822
6921
|
}
|
|
5823
6922
|
function saveSetupState(state) {
|
|
5824
|
-
mkdirSync7(
|
|
5825
|
-
|
|
6923
|
+
mkdirSync7(path15.dirname(SETUP_STATE_PATH), { recursive: true });
|
|
6924
|
+
writeFileSync10(SETUP_STATE_PATH, JSON.stringify(state, null, 2));
|
|
5826
6925
|
}
|
|
5827
6926
|
function clearSetupState() {
|
|
5828
6927
|
try {
|
|
@@ -5841,10 +6940,10 @@ function ask(rl, prompt) {
|
|
|
5841
6940
|
});
|
|
5842
6941
|
}
|
|
5843
6942
|
function getAvailableMemoryGB() {
|
|
5844
|
-
return
|
|
6943
|
+
return os8.freemem() / (1024 * 1024 * 1024);
|
|
5845
6944
|
}
|
|
5846
6945
|
function getTotalMemoryGB() {
|
|
5847
|
-
return
|
|
6946
|
+
return os8.totalmem() / (1024 * 1024 * 1024);
|
|
5848
6947
|
}
|
|
5849
6948
|
function isLowMemory() {
|
|
5850
6949
|
return getAvailableMemoryGB() < 2;
|
|
@@ -5855,8 +6954,8 @@ async function validateModel(log) {
|
|
|
5855
6954
|
if (totalGB <= 8 || isLowMemory()) {
|
|
5856
6955
|
log(`System memory: ${totalGB.toFixed(0)}GB total, ${freeGB.toFixed(1)}GB free`);
|
|
5857
6956
|
log("Skipping in-memory model validation (low memory \u2014 will validate on first use).");
|
|
5858
|
-
const modelPath =
|
|
5859
|
-
if (
|
|
6957
|
+
const modelPath = path15.join(MODELS_DIR, LOCAL_FILENAME);
|
|
6958
|
+
if (existsSync15(modelPath)) {
|
|
5860
6959
|
const { statSync: statSync2 } = await import("fs");
|
|
5861
6960
|
const size = statSync2(modelPath).size;
|
|
5862
6961
|
if (size > 300 * 1e6) {
|
|
@@ -5947,7 +7046,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
5947
7046
|
if (state.completedSteps.length > 0) {
|
|
5948
7047
|
log(`Resuming setup from step ${Math.max(...state.completedSteps) + 1}...`);
|
|
5949
7048
|
}
|
|
5950
|
-
if (
|
|
7049
|
+
if (existsSync15(LEGACY_LANCE_PATH)) {
|
|
5951
7050
|
log("\u26A0 Found v1.0 LanceDB at ~/.exe-os/local.lance");
|
|
5952
7051
|
log(" v1.1 uses libSQL (SQLite). Your existing memories are not automatically migrated.");
|
|
5953
7052
|
log(" The old directory will not be modified or deleted.");
|
|
@@ -5959,7 +7058,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
5959
7058
|
log("Encryption key already exists \u2014 skipping generation.");
|
|
5960
7059
|
} else {
|
|
5961
7060
|
log("Generating 256-bit encryption key...");
|
|
5962
|
-
const key =
|
|
7061
|
+
const key = crypto4.randomBytes(32);
|
|
5963
7062
|
await setMasterKey(key);
|
|
5964
7063
|
log("Encryption key generated and stored securely.");
|
|
5965
7064
|
}
|
|
@@ -6111,19 +7210,19 @@ async function runSetupWizard(opts = {}) {
|
|
|
6111
7210
|
await saveConfig(config);
|
|
6112
7211
|
log("");
|
|
6113
7212
|
try {
|
|
6114
|
-
const claudeJsonPath =
|
|
7213
|
+
const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
|
|
6115
7214
|
let claudeJson = {};
|
|
6116
7215
|
try {
|
|
6117
|
-
claudeJson = JSON.parse(
|
|
7216
|
+
claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
|
|
6118
7217
|
} catch {
|
|
6119
7218
|
}
|
|
6120
7219
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
6121
7220
|
const projects = claudeJson.projects;
|
|
6122
|
-
for (const dir of [process.cwd(),
|
|
7221
|
+
for (const dir of [process.cwd(), os8.homedir()]) {
|
|
6123
7222
|
if (!projects[dir]) projects[dir] = {};
|
|
6124
7223
|
projects[dir].hasTrustDialogAccepted = true;
|
|
6125
7224
|
}
|
|
6126
|
-
|
|
7225
|
+
writeFileSync10(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
6127
7226
|
} catch {
|
|
6128
7227
|
}
|
|
6129
7228
|
state.completedSteps.push(5);
|
|
@@ -6137,7 +7236,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
6137
7236
|
const prefs = { ...existingPrefs };
|
|
6138
7237
|
log("=== Config Defaults ===");
|
|
6139
7238
|
log("");
|
|
6140
|
-
const ghosttyDetected =
|
|
7239
|
+
const ghosttyDetected = existsSync15(path15.join(os8.homedir(), ".config", "ghostty")) || existsSync15(path15.join(os8.homedir(), "Library", "Application Support", "com.mitchellh.ghostty"));
|
|
6141
7240
|
if (ghosttyDetected) {
|
|
6142
7241
|
const ghosttyAnswer = await ask(rl, "Detected Ghostty terminal. Use exe-os Ghostty defaults? (Y/n) ");
|
|
6143
7242
|
prefs.ghostty = ghosttyAnswer.toLowerCase() !== "n";
|
|
@@ -6184,7 +7283,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
6184
7283
|
let missingIdentities = [];
|
|
6185
7284
|
for (const emp of roster) {
|
|
6186
7285
|
const idPath = identityPath2(emp.name);
|
|
6187
|
-
if (!
|
|
7286
|
+
if (!existsSync15(idPath)) {
|
|
6188
7287
|
missingIdentities.push(emp.name);
|
|
6189
7288
|
}
|
|
6190
7289
|
}
|
|
@@ -6216,7 +7315,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
6216
7315
|
}
|
|
6217
7316
|
missingIdentities = [];
|
|
6218
7317
|
for (const emp of roster) {
|
|
6219
|
-
if (!
|
|
7318
|
+
if (!existsSync15(identityPath2(emp.name))) {
|
|
6220
7319
|
missingIdentities.push(emp.name);
|
|
6221
7320
|
}
|
|
6222
7321
|
}
|
|
@@ -6281,9 +7380,9 @@ async function runSetupWizard(opts = {}) {
|
|
|
6281
7380
|
const cooIdentityContent = getIdentityTemplate("coo");
|
|
6282
7381
|
if (cooIdentityContent) {
|
|
6283
7382
|
const cooIdPath = identityPath2(cooName);
|
|
6284
|
-
mkdirSync7(
|
|
7383
|
+
mkdirSync7(path15.dirname(cooIdPath), { recursive: true });
|
|
6285
7384
|
const replaced = cooIdentityContent.replace(/agent_id:\s*exe/g, `agent_id: ${cooName}`).replace(/\$\{agent_id\}/g, cooName);
|
|
6286
|
-
|
|
7385
|
+
writeFileSync10(cooIdPath, replaced, "utf-8");
|
|
6287
7386
|
}
|
|
6288
7387
|
registerBinSymlinks2(cooName);
|
|
6289
7388
|
createdEmployees.push({ name: cooName, role: "COO" });
|
|
@@ -6377,9 +7476,9 @@ async function runSetupWizard(opts = {}) {
|
|
|
6377
7476
|
const ctoIdentityContent = getIdentityTemplate("cto");
|
|
6378
7477
|
if (ctoIdentityContent) {
|
|
6379
7478
|
const ctoIdPath = identityPath2(ctoName);
|
|
6380
|
-
mkdirSync7(
|
|
7479
|
+
mkdirSync7(path15.dirname(ctoIdPath), { recursive: true });
|
|
6381
7480
|
const replaced = ctoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${ctoName}`).replace(/\$\{agent_id\}/g, ctoName);
|
|
6382
|
-
|
|
7481
|
+
writeFileSync10(ctoIdPath, replaced, "utf-8");
|
|
6383
7482
|
}
|
|
6384
7483
|
registerBinSymlinks2(ctoName);
|
|
6385
7484
|
createdEmployees.push({ name: ctoName, role: "CTO" });
|
|
@@ -6400,9 +7499,9 @@ async function runSetupWizard(opts = {}) {
|
|
|
6400
7499
|
const cmoIdentityContent = getIdentityTemplate("cmo");
|
|
6401
7500
|
if (cmoIdentityContent) {
|
|
6402
7501
|
const cmoIdPath = identityPath2(cmoName);
|
|
6403
|
-
mkdirSync7(
|
|
7502
|
+
mkdirSync7(path15.dirname(cmoIdPath), { recursive: true });
|
|
6404
7503
|
const replaced = cmoIdentityContent.replace(/agent_id:\s*\w+/g, `agent_id: ${cmoName}`).replace(/\$\{agent_id\}/g, cmoName);
|
|
6405
|
-
|
|
7504
|
+
writeFileSync10(cmoIdPath, replaced, "utf-8");
|
|
6406
7505
|
}
|
|
6407
7506
|
registerBinSymlinks2(cmoName);
|
|
6408
7507
|
createdEmployees.push({ name: cmoName, role: "CMO" });
|
|
@@ -6424,7 +7523,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
6424
7523
|
log(`Session shortcuts generated (${cooName}1, ${cooName}2, ...)`);
|
|
6425
7524
|
}
|
|
6426
7525
|
if (wrapResult.pathConfigured) {
|
|
6427
|
-
const binDir =
|
|
7526
|
+
const binDir = path15.join(os8.homedir(), ".exe-os", "bin");
|
|
6428
7527
|
process.env.PATH = `${binDir}:${process.env.PATH ?? ""}`;
|
|
6429
7528
|
pathJustConfigured = true;
|
|
6430
7529
|
}
|
|
@@ -6467,7 +7566,7 @@ async function runSetupWizard(opts = {}) {
|
|
|
6467
7566
|
const pkgRoot2 = findPackageRoot2();
|
|
6468
7567
|
if (pkgRoot2) {
|
|
6469
7568
|
try {
|
|
6470
|
-
version = JSON.parse(
|
|
7569
|
+
version = JSON.parse(readFileSync12(path15.join(pkgRoot2, "package.json"), "utf-8")).version;
|
|
6471
7570
|
} catch {
|
|
6472
7571
|
}
|
|
6473
7572
|
}
|