@askexenow/exe-os 0.9.30 → 0.9.32
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 +135 -7
- package/dist/bin/backfill-responses.js +135 -7
- package/dist/bin/backfill-vectors.js +135 -7
- package/dist/bin/cleanup-stale-review-tasks.js +139 -11
- package/dist/bin/cli.js +812 -486
- package/dist/bin/exe-assign.js +135 -7
- package/dist/bin/exe-boot.js +422 -113
- package/dist/bin/exe-cloud.js +160 -9
- package/dist/bin/exe-dispatch.js +136 -8
- package/dist/bin/exe-doctor.js +255 -13
- package/dist/bin/exe-export-behaviors.js +136 -8
- package/dist/bin/exe-forget.js +136 -8
- package/dist/bin/exe-gateway.js +171 -24
- package/dist/bin/exe-heartbeat.js +141 -13
- package/dist/bin/exe-kill.js +140 -12
- package/dist/bin/exe-launch-agent.js +143 -15
- package/dist/bin/exe-link.js +357 -48
- package/dist/bin/exe-pending-messages.js +136 -8
- package/dist/bin/exe-pending-notifications.js +136 -8
- package/dist/bin/exe-pending-reviews.js +138 -10
- package/dist/bin/exe-review.js +136 -8
- package/dist/bin/exe-search.js +155 -20
- package/dist/bin/exe-session-cleanup.js +166 -38
- package/dist/bin/exe-start-codex.js +142 -14
- package/dist/bin/exe-start-opencode.js +140 -12
- package/dist/bin/exe-status.js +148 -20
- package/dist/bin/exe-team.js +136 -8
- package/dist/bin/git-sweep.js +138 -10
- package/dist/bin/graph-backfill.js +135 -7
- package/dist/bin/graph-export.js +136 -8
- package/dist/bin/intercom-check.js +153 -25
- package/dist/bin/scan-tasks.js +138 -10
- package/dist/bin/setup.js +447 -121
- package/dist/bin/shard-migrate.js +135 -7
- package/dist/gateway/index.js +151 -23
- package/dist/hooks/bug-report-worker.js +151 -23
- package/dist/hooks/codex-stop-task-finalizer.js +145 -17
- package/dist/hooks/commit-complete.js +138 -10
- package/dist/hooks/error-recall.js +159 -24
- package/dist/hooks/ingest.js +142 -14
- package/dist/hooks/instructions-loaded.js +136 -8
- package/dist/hooks/notification.js +136 -8
- package/dist/hooks/post-compact.js +136 -8
- package/dist/hooks/post-tool-combined.js +159 -24
- package/dist/hooks/pre-compact.js +136 -8
- package/dist/hooks/pre-tool-use.js +144 -16
- package/dist/hooks/prompt-submit.js +195 -55
- package/dist/hooks/session-end.js +141 -13
- package/dist/hooks/session-start.js +165 -30
- package/dist/hooks/stop.js +136 -8
- package/dist/hooks/subagent-stop.js +136 -8
- package/dist/hooks/summary-worker.js +374 -65
- package/dist/index.js +136 -8
- package/dist/lib/cloud-sync.js +355 -46
- package/dist/lib/consolidation.js +1 -0
- package/dist/lib/exe-daemon.js +469 -127
- package/dist/lib/hybrid-search.js +155 -20
- package/dist/lib/keychain.js +191 -7
- package/dist/lib/schedules.js +138 -10
- package/dist/lib/store.js +135 -7
- package/dist/mcp/server.js +706 -213
- package/dist/runtime/index.js +136 -8
- package/dist/tui/App.js +208 -31
- package/package.json +1 -1
package/dist/bin/exe-link.js
CHANGED
|
@@ -26,6 +26,7 @@ __export(keychain_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
|
|
28
28
|
import { existsSync } from "fs";
|
|
29
|
+
import { execSync } from "child_process";
|
|
29
30
|
import path from "path";
|
|
30
31
|
import os from "os";
|
|
31
32
|
function getKeyDir() {
|
|
@@ -34,6 +35,83 @@ function getKeyDir() {
|
|
|
34
35
|
function getKeyPath() {
|
|
35
36
|
return path.join(getKeyDir(), "master.key");
|
|
36
37
|
}
|
|
38
|
+
function macKeychainGet() {
|
|
39
|
+
if (process.platform !== "darwin") return null;
|
|
40
|
+
try {
|
|
41
|
+
return execSync(
|
|
42
|
+
`security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
|
|
43
|
+
{ encoding: "utf-8", timeout: 5e3 }
|
|
44
|
+
).trim();
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function macKeychainSet(value) {
|
|
50
|
+
if (process.platform !== "darwin") return false;
|
|
51
|
+
try {
|
|
52
|
+
try {
|
|
53
|
+
execSync(
|
|
54
|
+
`security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
55
|
+
{ timeout: 5e3 }
|
|
56
|
+
);
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
execSync(
|
|
60
|
+
`security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
|
|
61
|
+
{ timeout: 5e3 }
|
|
62
|
+
);
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function macKeychainDelete() {
|
|
69
|
+
if (process.platform !== "darwin") return false;
|
|
70
|
+
try {
|
|
71
|
+
execSync(
|
|
72
|
+
`security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
73
|
+
{ timeout: 5e3 }
|
|
74
|
+
);
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function linuxSecretGet() {
|
|
81
|
+
if (process.platform !== "linux") return null;
|
|
82
|
+
try {
|
|
83
|
+
return execSync(
|
|
84
|
+
`secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
|
|
85
|
+
{ encoding: "utf-8", timeout: 5e3 }
|
|
86
|
+
).trim();
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function linuxSecretSet(value) {
|
|
92
|
+
if (process.platform !== "linux") return false;
|
|
93
|
+
try {
|
|
94
|
+
execSync(
|
|
95
|
+
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
|
|
96
|
+
{ timeout: 5e3 }
|
|
97
|
+
);
|
|
98
|
+
return true;
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function linuxSecretDelete() {
|
|
104
|
+
if (process.platform !== "linux") return false;
|
|
105
|
+
try {
|
|
106
|
+
execSync(
|
|
107
|
+
`secret-tool clear service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
|
|
108
|
+
{ timeout: 5e3 }
|
|
109
|
+
);
|
|
110
|
+
return true;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
37
115
|
async function tryKeytar() {
|
|
38
116
|
try {
|
|
39
117
|
return await import("keytar");
|
|
@@ -41,13 +119,72 @@ async function tryKeytar() {
|
|
|
41
119
|
return null;
|
|
42
120
|
}
|
|
43
121
|
}
|
|
122
|
+
function deriveMachineKey() {
|
|
123
|
+
try {
|
|
124
|
+
const crypto4 = __require("crypto");
|
|
125
|
+
const material = [
|
|
126
|
+
os.hostname(),
|
|
127
|
+
os.userInfo().username,
|
|
128
|
+
os.arch(),
|
|
129
|
+
os.platform(),
|
|
130
|
+
// Machine ID on Linux (stable across reboots)
|
|
131
|
+
process.platform === "linux" ? readMachineId() : ""
|
|
132
|
+
].join("|");
|
|
133
|
+
return crypto4.createHash("sha256").update(material).digest();
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function readMachineId() {
|
|
139
|
+
try {
|
|
140
|
+
const { readFileSync: readFileSync8 } = __require("fs");
|
|
141
|
+
return readFileSync8("/etc/machine-id", "utf-8").trim();
|
|
142
|
+
} catch {
|
|
143
|
+
return "";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
147
|
+
const crypto4 = __require("crypto");
|
|
148
|
+
const iv = crypto4.randomBytes(12);
|
|
149
|
+
const cipher = crypto4.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
150
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
151
|
+
encrypted += cipher.final("base64");
|
|
152
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
153
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
154
|
+
}
|
|
155
|
+
function decryptWithMachineKey(encrypted, machineKey) {
|
|
156
|
+
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
157
|
+
try {
|
|
158
|
+
const crypto4 = __require("crypto");
|
|
159
|
+
const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
|
|
160
|
+
if (parts.length !== 3) return null;
|
|
161
|
+
const [ivB64, tagB64, cipherB64] = parts;
|
|
162
|
+
const iv = Buffer.from(ivB64, "base64");
|
|
163
|
+
const authTag = Buffer.from(tagB64, "base64");
|
|
164
|
+
const decipher = crypto4.createDecipheriv("aes-256-gcm", machineKey, iv);
|
|
165
|
+
decipher.setAuthTag(authTag);
|
|
166
|
+
let decrypted = decipher.update(cipherB64, "base64", "utf-8");
|
|
167
|
+
decrypted += decipher.final("utf-8");
|
|
168
|
+
return decrypted;
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
44
173
|
async function getMasterKey() {
|
|
174
|
+
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
175
|
+
if (nativeValue) {
|
|
176
|
+
return Buffer.from(nativeValue, "base64");
|
|
177
|
+
}
|
|
45
178
|
const keytar = await tryKeytar();
|
|
46
179
|
if (keytar) {
|
|
47
180
|
try {
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
|
|
181
|
+
const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
182
|
+
if (keytarValue) {
|
|
183
|
+
const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
|
|
184
|
+
if (migrated) {
|
|
185
|
+
process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
|
|
186
|
+
}
|
|
187
|
+
return Buffer.from(keytarValue, "base64");
|
|
51
188
|
}
|
|
52
189
|
} catch {
|
|
53
190
|
}
|
|
@@ -61,8 +198,31 @@ async function getMasterKey() {
|
|
|
61
198
|
return null;
|
|
62
199
|
}
|
|
63
200
|
try {
|
|
64
|
-
const content = await readFile(keyPath, "utf-8");
|
|
65
|
-
|
|
201
|
+
const content = (await readFile(keyPath, "utf-8")).trim();
|
|
202
|
+
let b64Value;
|
|
203
|
+
if (content.startsWith(ENCRYPTED_PREFIX)) {
|
|
204
|
+
const machineKey = deriveMachineKey();
|
|
205
|
+
if (!machineKey) {
|
|
206
|
+
process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
const decrypted = decryptWithMachineKey(content, machineKey);
|
|
210
|
+
if (!decrypted) {
|
|
211
|
+
process.stderr.write(
|
|
212
|
+
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
|
|
213
|
+
);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
b64Value = decrypted;
|
|
217
|
+
} else {
|
|
218
|
+
b64Value = content;
|
|
219
|
+
}
|
|
220
|
+
const key = Buffer.from(b64Value, "base64");
|
|
221
|
+
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
222
|
+
if (migrated) {
|
|
223
|
+
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
224
|
+
}
|
|
225
|
+
return key;
|
|
66
226
|
} catch (err) {
|
|
67
227
|
process.stderr.write(
|
|
68
228
|
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -73,6 +233,9 @@ async function getMasterKey() {
|
|
|
73
233
|
}
|
|
74
234
|
async function setMasterKey(key) {
|
|
75
235
|
const b64 = key.toString("base64");
|
|
236
|
+
if (macKeychainSet(b64) || linuxSecretSet(b64)) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
76
239
|
const keytar = await tryKeytar();
|
|
77
240
|
if (keytar) {
|
|
78
241
|
try {
|
|
@@ -84,10 +247,23 @@ async function setMasterKey(key) {
|
|
|
84
247
|
const dir = getKeyDir();
|
|
85
248
|
await mkdir(dir, { recursive: true });
|
|
86
249
|
const keyPath = getKeyPath();
|
|
87
|
-
|
|
88
|
-
|
|
250
|
+
const machineKey = deriveMachineKey();
|
|
251
|
+
if (machineKey) {
|
|
252
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
253
|
+
await writeFile(keyPath, encrypted + "\n", "utf-8");
|
|
254
|
+
await chmod(keyPath, 384);
|
|
255
|
+
process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
|
|
256
|
+
} else {
|
|
257
|
+
await writeFile(keyPath, b64 + "\n", "utf-8");
|
|
258
|
+
await chmod(keyPath, 384);
|
|
259
|
+
process.stderr.write(
|
|
260
|
+
"[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
|
|
261
|
+
);
|
|
262
|
+
}
|
|
89
263
|
}
|
|
90
264
|
async function deleteMasterKey() {
|
|
265
|
+
macKeychainDelete();
|
|
266
|
+
linuxSecretDelete();
|
|
91
267
|
const keytar = await tryKeytar();
|
|
92
268
|
if (keytar) {
|
|
93
269
|
try {
|
|
@@ -129,12 +305,13 @@ async function importMnemonic(mnemonic) {
|
|
|
129
305
|
const entropy = mnemonicToEntropy(trimmed);
|
|
130
306
|
return Buffer.from(entropy, "hex");
|
|
131
307
|
}
|
|
132
|
-
var SERVICE, ACCOUNT;
|
|
308
|
+
var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
|
|
133
309
|
var init_keychain = __esm({
|
|
134
310
|
"src/lib/keychain.ts"() {
|
|
135
311
|
"use strict";
|
|
136
312
|
SERVICE = "exe-mem";
|
|
137
313
|
ACCOUNT = "master-key";
|
|
314
|
+
ENCRYPTED_PREFIX = "enc:";
|
|
138
315
|
}
|
|
139
316
|
});
|
|
140
317
|
|
|
@@ -533,7 +710,7 @@ var init_db_retry = __esm({
|
|
|
533
710
|
// src/lib/employees.ts
|
|
534
711
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
535
712
|
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
536
|
-
import { execSync } from "child_process";
|
|
713
|
+
import { execSync as execSync2 } from "child_process";
|
|
537
714
|
import path3 from "path";
|
|
538
715
|
import os3 from "os";
|
|
539
716
|
function normalizeRole(role) {
|
|
@@ -573,7 +750,7 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
573
750
|
}
|
|
574
751
|
function findExeBin() {
|
|
575
752
|
try {
|
|
576
|
-
return
|
|
753
|
+
return execSync2(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
577
754
|
} catch {
|
|
578
755
|
return null;
|
|
579
756
|
}
|
|
@@ -1321,8 +1498,8 @@ function findPackageRoot() {
|
|
|
1321
1498
|
function getAvailableMemoryGB() {
|
|
1322
1499
|
if (process.platform === "darwin") {
|
|
1323
1500
|
try {
|
|
1324
|
-
const { execSync:
|
|
1325
|
-
const vmstat =
|
|
1501
|
+
const { execSync: execSync3 } = __require("child_process");
|
|
1502
|
+
const vmstat = execSync3("vm_stat", { encoding: "utf8" });
|
|
1326
1503
|
const pageSize = 16384;
|
|
1327
1504
|
const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
|
|
1328
1505
|
const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
|
|
@@ -3140,6 +3317,109 @@ var init_crdt_sync = __esm({
|
|
|
3140
3317
|
}
|
|
3141
3318
|
});
|
|
3142
3319
|
|
|
3320
|
+
// src/lib/db-backup.ts
|
|
3321
|
+
var db_backup_exports = {};
|
|
3322
|
+
__export(db_backup_exports, {
|
|
3323
|
+
createBackup: () => createBackup,
|
|
3324
|
+
findActiveDb: () => findActiveDb,
|
|
3325
|
+
getBackupDir: () => getBackupDir,
|
|
3326
|
+
getLatestBackup: () => getLatestBackup,
|
|
3327
|
+
hasBackupToday: () => hasBackupToday,
|
|
3328
|
+
listBackups: () => listBackups,
|
|
3329
|
+
rotateBackups: () => rotateBackups
|
|
3330
|
+
});
|
|
3331
|
+
import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync, unlinkSync as unlinkSync4, statSync as statSync2 } from "fs";
|
|
3332
|
+
import path9 from "path";
|
|
3333
|
+
function findActiveDb() {
|
|
3334
|
+
for (const name of DB_NAMES) {
|
|
3335
|
+
const p = path9.join(EXE_AI_DIR, name);
|
|
3336
|
+
if (existsSync9(p)) return p;
|
|
3337
|
+
}
|
|
3338
|
+
return null;
|
|
3339
|
+
}
|
|
3340
|
+
function createBackup(reason = "manual") {
|
|
3341
|
+
const dbPath = findActiveDb();
|
|
3342
|
+
if (!dbPath) return null;
|
|
3343
|
+
mkdirSync4(BACKUP_DIR, { recursive: true });
|
|
3344
|
+
const dbName = path9.basename(dbPath, ".db");
|
|
3345
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
3346
|
+
const backupName = `${dbName}-${reason}-${timestamp}.db`;
|
|
3347
|
+
const backupPath = path9.join(BACKUP_DIR, backupName);
|
|
3348
|
+
copyFileSync(dbPath, backupPath);
|
|
3349
|
+
const walPath = dbPath + "-wal";
|
|
3350
|
+
if (existsSync9(walPath)) {
|
|
3351
|
+
try {
|
|
3352
|
+
copyFileSync(walPath, backupPath + "-wal");
|
|
3353
|
+
} catch {
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
const shmPath = dbPath + "-shm";
|
|
3357
|
+
if (existsSync9(shmPath)) {
|
|
3358
|
+
try {
|
|
3359
|
+
copyFileSync(shmPath, backupPath + "-shm");
|
|
3360
|
+
} catch {
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
return backupPath;
|
|
3364
|
+
}
|
|
3365
|
+
function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
|
|
3366
|
+
if (!existsSync9(BACKUP_DIR)) return 0;
|
|
3367
|
+
const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
|
|
3368
|
+
let deleted = 0;
|
|
3369
|
+
try {
|
|
3370
|
+
const files = readdirSync(BACKUP_DIR);
|
|
3371
|
+
for (const file of files) {
|
|
3372
|
+
if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
|
|
3373
|
+
const filePath = path9.join(BACKUP_DIR, file);
|
|
3374
|
+
try {
|
|
3375
|
+
const stat = statSync2(filePath);
|
|
3376
|
+
if (stat.mtimeMs < cutoff) {
|
|
3377
|
+
unlinkSync4(filePath);
|
|
3378
|
+
deleted++;
|
|
3379
|
+
}
|
|
3380
|
+
} catch {
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
} catch {
|
|
3384
|
+
}
|
|
3385
|
+
return deleted;
|
|
3386
|
+
}
|
|
3387
|
+
function listBackups() {
|
|
3388
|
+
if (!existsSync9(BACKUP_DIR)) return [];
|
|
3389
|
+
try {
|
|
3390
|
+
const files = readdirSync(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
|
|
3391
|
+
return files.map((name) => {
|
|
3392
|
+
const p = path9.join(BACKUP_DIR, name);
|
|
3393
|
+
const stat = statSync2(p);
|
|
3394
|
+
return { path: p, name, size: stat.size, date: stat.mtime };
|
|
3395
|
+
}).sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
3396
|
+
} catch {
|
|
3397
|
+
return [];
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
function hasBackupToday(reason) {
|
|
3401
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3402
|
+
const backups = listBackups();
|
|
3403
|
+
return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
|
|
3404
|
+
}
|
|
3405
|
+
function getLatestBackup() {
|
|
3406
|
+
const backups = listBackups();
|
|
3407
|
+
return backups.length > 0 ? backups[0].path : null;
|
|
3408
|
+
}
|
|
3409
|
+
function getBackupDir() {
|
|
3410
|
+
return BACKUP_DIR;
|
|
3411
|
+
}
|
|
3412
|
+
var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
|
|
3413
|
+
var init_db_backup = __esm({
|
|
3414
|
+
"src/lib/db-backup.ts"() {
|
|
3415
|
+
"use strict";
|
|
3416
|
+
init_config();
|
|
3417
|
+
BACKUP_DIR = path9.join(EXE_AI_DIR, "backups");
|
|
3418
|
+
DEFAULT_KEEP_DAYS = 3;
|
|
3419
|
+
DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
|
|
3420
|
+
}
|
|
3421
|
+
});
|
|
3422
|
+
|
|
3143
3423
|
// src/lib/cloud-sync.ts
|
|
3144
3424
|
var cloud_sync_exports = {};
|
|
3145
3425
|
__export(cloud_sync_exports, {
|
|
@@ -3169,16 +3449,16 @@ __export(cloud_sync_exports, {
|
|
|
3169
3449
|
pushToPostgres: () => pushToPostgres,
|
|
3170
3450
|
recordRosterDeletion: () => recordRosterDeletion
|
|
3171
3451
|
});
|
|
3172
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as
|
|
3452
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, readdirSync as readdirSync2, mkdirSync as mkdirSync5, appendFileSync, unlinkSync as unlinkSync5, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
3173
3453
|
import crypto3 from "crypto";
|
|
3174
|
-
import
|
|
3454
|
+
import path10 from "path";
|
|
3175
3455
|
import { homedir as homedir2 } from "os";
|
|
3176
3456
|
function sqlSafe(v) {
|
|
3177
3457
|
return v === void 0 ? null : v;
|
|
3178
3458
|
}
|
|
3179
3459
|
function logError(msg) {
|
|
3180
3460
|
try {
|
|
3181
|
-
const logPath =
|
|
3461
|
+
const logPath = path10.join(homedir2(), ".exe-os", "workers.log");
|
|
3182
3462
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
3183
3463
|
`);
|
|
3184
3464
|
} catch {
|
|
@@ -3187,10 +3467,10 @@ function logError(msg) {
|
|
|
3187
3467
|
function loadPgClient() {
|
|
3188
3468
|
if (_pgFailed) return null;
|
|
3189
3469
|
const postgresUrl = process.env.DATABASE_URL;
|
|
3190
|
-
const configPath =
|
|
3470
|
+
const configPath = path10.join(EXE_AI_DIR, "config.json");
|
|
3191
3471
|
let cloudPostgresUrl;
|
|
3192
3472
|
try {
|
|
3193
|
-
if (
|
|
3473
|
+
if (existsSync10(configPath)) {
|
|
3194
3474
|
const cfg = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
3195
3475
|
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
3196
3476
|
if (cfg.cloud?.syncToPostgres === false) {
|
|
@@ -3209,8 +3489,8 @@ function loadPgClient() {
|
|
|
3209
3489
|
_pgPromise = (async () => {
|
|
3210
3490
|
const { createRequire: createRequire3 } = await import("module");
|
|
3211
3491
|
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
3212
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
3213
|
-
const req = createRequire3(
|
|
3492
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(homedir2(), "exe-db");
|
|
3493
|
+
const req = createRequire3(path10.join(exeDbRoot, "package.json"));
|
|
3214
3494
|
const entry = req.resolve("@prisma/client");
|
|
3215
3495
|
const mod = await import(pathToFileURL3(entry).href);
|
|
3216
3496
|
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
@@ -3263,7 +3543,7 @@ async function withRosterLock(fn) {
|
|
|
3263
3543
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
3264
3544
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
3265
3545
|
}
|
|
3266
|
-
|
|
3546
|
+
unlinkSync5(ROSTER_LOCK_PATH);
|
|
3267
3547
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3268
3548
|
closeSync2(fd);
|
|
3269
3549
|
writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
|
|
@@ -3279,7 +3559,7 @@ async function withRosterLock(fn) {
|
|
|
3279
3559
|
return await fn();
|
|
3280
3560
|
} finally {
|
|
3281
3561
|
try {
|
|
3282
|
-
|
|
3562
|
+
unlinkSync5(ROSTER_LOCK_PATH);
|
|
3283
3563
|
} catch {
|
|
3284
3564
|
}
|
|
3285
3565
|
}
|
|
@@ -3650,13 +3930,42 @@ async function cloudSync(config) {
|
|
|
3650
3930
|
try {
|
|
3651
3931
|
const employees = await loadEmployees();
|
|
3652
3932
|
rosterResult.employees = employees.length;
|
|
3653
|
-
const idDir =
|
|
3654
|
-
if (
|
|
3655
|
-
rosterResult.identities =
|
|
3933
|
+
const idDir = path10.join(EXE_AI_DIR, "identity");
|
|
3934
|
+
if (existsSync10(idDir)) {
|
|
3935
|
+
rosterResult.identities = readdirSync2(idDir).filter((f) => f.endsWith(".md")).length;
|
|
3656
3936
|
}
|
|
3657
3937
|
} catch {
|
|
3658
3938
|
}
|
|
3659
3939
|
const totalMemories = await countRows("SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL");
|
|
3940
|
+
try {
|
|
3941
|
+
const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
|
|
3942
|
+
const { statSync: statFile } = await import("fs");
|
|
3943
|
+
const latestBackup = getLatestBackup2();
|
|
3944
|
+
if (latestBackup) {
|
|
3945
|
+
const backupSize = statFile(latestBackup).size;
|
|
3946
|
+
const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
|
|
3947
|
+
if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
|
|
3948
|
+
const backupData = readFileSync7(latestBackup);
|
|
3949
|
+
const deviceId = loadDeviceId() ?? "unknown";
|
|
3950
|
+
const encrypted = encryptSyncBlob(backupData);
|
|
3951
|
+
const backupRes = await fetchWithRetry(`${config.endpoint}/sync/push-db-backup`, {
|
|
3952
|
+
method: "POST",
|
|
3953
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
|
|
3954
|
+
body: JSON.stringify({
|
|
3955
|
+
device_id: deviceId,
|
|
3956
|
+
filename: path10.basename(latestBackup),
|
|
3957
|
+
blob: encrypted,
|
|
3958
|
+
size: backupData.length
|
|
3959
|
+
})
|
|
3960
|
+
});
|
|
3961
|
+
if (backupRes && !backupRes.ok) {
|
|
3962
|
+
logError(`[cloud-sync] DB backup upload failed: ${backupRes.status}`);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
} catch (err) {
|
|
3967
|
+
logError(`[cloud-sync] DB backup upload error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3968
|
+
}
|
|
3660
3969
|
return {
|
|
3661
3970
|
pushed,
|
|
3662
3971
|
pulled,
|
|
@@ -3672,7 +3981,7 @@ async function cloudSync(config) {
|
|
|
3672
3981
|
function recordRosterDeletion(name) {
|
|
3673
3982
|
let deletions = [];
|
|
3674
3983
|
try {
|
|
3675
|
-
if (
|
|
3984
|
+
if (existsSync10(ROSTER_DELETIONS_PATH)) {
|
|
3676
3985
|
deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
3677
3986
|
}
|
|
3678
3987
|
} catch {
|
|
@@ -3682,7 +3991,7 @@ function recordRosterDeletion(name) {
|
|
|
3682
3991
|
}
|
|
3683
3992
|
function consumeRosterDeletions() {
|
|
3684
3993
|
try {
|
|
3685
|
-
if (!
|
|
3994
|
+
if (!existsSync10(ROSTER_DELETIONS_PATH)) return [];
|
|
3686
3995
|
const deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
3687
3996
|
writeFileSync5(ROSTER_DELETIONS_PATH, "[]");
|
|
3688
3997
|
return deletions;
|
|
@@ -3691,35 +4000,35 @@ function consumeRosterDeletions() {
|
|
|
3691
4000
|
}
|
|
3692
4001
|
}
|
|
3693
4002
|
function buildRosterBlob(paths) {
|
|
3694
|
-
const rosterPath = paths?.rosterPath ??
|
|
3695
|
-
const identityDir = paths?.identityDir ??
|
|
3696
|
-
const configPath = paths?.configPath ??
|
|
4003
|
+
const rosterPath = paths?.rosterPath ?? path10.join(EXE_AI_DIR, "exe-employees.json");
|
|
4004
|
+
const identityDir = paths?.identityDir ?? path10.join(EXE_AI_DIR, "identity");
|
|
4005
|
+
const configPath = paths?.configPath ?? path10.join(EXE_AI_DIR, "config.json");
|
|
3697
4006
|
let roster = [];
|
|
3698
|
-
if (
|
|
4007
|
+
if (existsSync10(rosterPath)) {
|
|
3699
4008
|
try {
|
|
3700
4009
|
roster = JSON.parse(readFileSync7(rosterPath, "utf-8"));
|
|
3701
4010
|
} catch {
|
|
3702
4011
|
}
|
|
3703
4012
|
}
|
|
3704
4013
|
const identities = {};
|
|
3705
|
-
if (
|
|
3706
|
-
for (const file of
|
|
4014
|
+
if (existsSync10(identityDir)) {
|
|
4015
|
+
for (const file of readdirSync2(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
3707
4016
|
try {
|
|
3708
|
-
identities[file] = readFileSync7(
|
|
4017
|
+
identities[file] = readFileSync7(path10.join(identityDir, file), "utf-8");
|
|
3709
4018
|
} catch {
|
|
3710
4019
|
}
|
|
3711
4020
|
}
|
|
3712
4021
|
}
|
|
3713
4022
|
let config;
|
|
3714
|
-
if (
|
|
4023
|
+
if (existsSync10(configPath)) {
|
|
3715
4024
|
try {
|
|
3716
4025
|
config = JSON.parse(readFileSync7(configPath, "utf-8"));
|
|
3717
4026
|
} catch {
|
|
3718
4027
|
}
|
|
3719
4028
|
}
|
|
3720
4029
|
let agentConfig;
|
|
3721
|
-
const agentConfigPath =
|
|
3722
|
-
if (
|
|
4030
|
+
const agentConfigPath = path10.join(EXE_AI_DIR, "agent-config.json");
|
|
4031
|
+
if (existsSync10(agentConfigPath)) {
|
|
3723
4032
|
try {
|
|
3724
4033
|
agentConfig = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
|
|
3725
4034
|
} catch {
|
|
@@ -3797,16 +4106,16 @@ async function cloudPullRoster(config) {
|
|
|
3797
4106
|
}
|
|
3798
4107
|
}
|
|
3799
4108
|
function mergeConfig(remoteConfig, configPath) {
|
|
3800
|
-
const cfgPath = configPath ??
|
|
4109
|
+
const cfgPath = configPath ?? path10.join(EXE_AI_DIR, "config.json");
|
|
3801
4110
|
let local = {};
|
|
3802
|
-
if (
|
|
4111
|
+
if (existsSync10(cfgPath)) {
|
|
3803
4112
|
try {
|
|
3804
4113
|
local = JSON.parse(readFileSync7(cfgPath, "utf-8"));
|
|
3805
4114
|
} catch {
|
|
3806
4115
|
}
|
|
3807
4116
|
}
|
|
3808
4117
|
const merged = { ...remoteConfig, ...local };
|
|
3809
|
-
const dir =
|
|
4118
|
+
const dir = path10.dirname(cfgPath);
|
|
3810
4119
|
ensurePrivateDirSync(dir);
|
|
3811
4120
|
writeFileSync5(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
3812
4121
|
enforcePrivateFileSync(cfgPath);
|
|
@@ -3814,7 +4123,7 @@ function mergeConfig(remoteConfig, configPath) {
|
|
|
3814
4123
|
async function mergeRosterFromRemote(remote, paths) {
|
|
3815
4124
|
return withRosterLock(async () => {
|
|
3816
4125
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
3817
|
-
const identityDir = paths?.identityDir ??
|
|
4126
|
+
const identityDir = paths?.identityDir ?? path10.join(EXE_AI_DIR, "identity");
|
|
3818
4127
|
const localEmployees = await loadEmployees(rosterPath);
|
|
3819
4128
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
3820
4129
|
let added = 0;
|
|
@@ -3835,11 +4144,11 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3835
4144
|
) ?? lookupKey;
|
|
3836
4145
|
const remoteIdentity = remote.identities[matchedKey];
|
|
3837
4146
|
if (remoteIdentity) {
|
|
3838
|
-
if (!
|
|
3839
|
-
const idPath =
|
|
4147
|
+
if (!existsSync10(identityDir)) mkdirSync5(identityDir, { recursive: true });
|
|
4148
|
+
const idPath = path10.join(identityDir, `${remoteEmp.name}.md`);
|
|
3840
4149
|
let localIdentity = null;
|
|
3841
4150
|
try {
|
|
3842
|
-
localIdentity =
|
|
4151
|
+
localIdentity = existsSync10(idPath) ? readFileSync7(idPath, "utf-8") : null;
|
|
3843
4152
|
} catch {
|
|
3844
4153
|
}
|
|
3845
4154
|
if (localIdentity !== remoteIdentity) {
|
|
@@ -3869,16 +4178,16 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3869
4178
|
}
|
|
3870
4179
|
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
3871
4180
|
try {
|
|
3872
|
-
const agentConfigPath =
|
|
4181
|
+
const agentConfigPath = path10.join(EXE_AI_DIR, "agent-config.json");
|
|
3873
4182
|
let local = {};
|
|
3874
|
-
if (
|
|
4183
|
+
if (existsSync10(agentConfigPath)) {
|
|
3875
4184
|
try {
|
|
3876
4185
|
local = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
|
|
3877
4186
|
} catch {
|
|
3878
4187
|
}
|
|
3879
4188
|
}
|
|
3880
4189
|
const merged = { ...remote.agentConfig, ...local };
|
|
3881
|
-
ensurePrivateDirSync(
|
|
4190
|
+
ensurePrivateDirSync(path10.dirname(agentConfigPath));
|
|
3882
4191
|
writeFileSync5(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
3883
4192
|
enforcePrivateFileSync(agentConfigPath);
|
|
3884
4193
|
} catch {
|
|
@@ -4319,11 +4628,11 @@ var init_cloud_sync = __esm({
|
|
|
4319
4628
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
4320
4629
|
FETCH_TIMEOUT_MS = 3e4;
|
|
4321
4630
|
PUSH_BATCH_SIZE = 5e3;
|
|
4322
|
-
ROSTER_LOCK_PATH =
|
|
4631
|
+
ROSTER_LOCK_PATH = path10.join(EXE_AI_DIR, "roster-merge.lock");
|
|
4323
4632
|
LOCK_STALE_MS = 3e4;
|
|
4324
4633
|
_pgPromise = null;
|
|
4325
4634
|
_pgFailed = false;
|
|
4326
|
-
ROSTER_DELETIONS_PATH =
|
|
4635
|
+
ROSTER_DELETIONS_PATH = path10.join(EXE_AI_DIR, "roster-deletions.json");
|
|
4327
4636
|
}
|
|
4328
4637
|
});
|
|
4329
4638
|
|