@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/lib/cloud-sync.js
CHANGED
|
@@ -977,8 +977,8 @@ function findPackageRoot() {
|
|
|
977
977
|
function getAvailableMemoryGB() {
|
|
978
978
|
if (process.platform === "darwin") {
|
|
979
979
|
try {
|
|
980
|
-
const { execSync:
|
|
981
|
-
const vmstat =
|
|
980
|
+
const { execSync: execSync3 } = __require("child_process");
|
|
981
|
+
const vmstat = execSync3("vm_stat", { encoding: "utf8" });
|
|
982
982
|
const pageSize = 16384;
|
|
983
983
|
const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
|
|
984
984
|
const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
|
|
@@ -2747,6 +2747,7 @@ __export(keychain_exports, {
|
|
|
2747
2747
|
});
|
|
2748
2748
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
2749
2749
|
import { existsSync as existsSync8 } from "fs";
|
|
2750
|
+
import { execSync as execSync2 } from "child_process";
|
|
2750
2751
|
import path8 from "path";
|
|
2751
2752
|
import os6 from "os";
|
|
2752
2753
|
function getKeyDir() {
|
|
@@ -2755,6 +2756,83 @@ function getKeyDir() {
|
|
|
2755
2756
|
function getKeyPath() {
|
|
2756
2757
|
return path8.join(getKeyDir(), "master.key");
|
|
2757
2758
|
}
|
|
2759
|
+
function macKeychainGet() {
|
|
2760
|
+
if (process.platform !== "darwin") return null;
|
|
2761
|
+
try {
|
|
2762
|
+
return execSync2(
|
|
2763
|
+
`security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
|
|
2764
|
+
{ encoding: "utf-8", timeout: 5e3 }
|
|
2765
|
+
).trim();
|
|
2766
|
+
} catch {
|
|
2767
|
+
return null;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
function macKeychainSet(value) {
|
|
2771
|
+
if (process.platform !== "darwin") return false;
|
|
2772
|
+
try {
|
|
2773
|
+
try {
|
|
2774
|
+
execSync2(
|
|
2775
|
+
`security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
2776
|
+
{ timeout: 5e3 }
|
|
2777
|
+
);
|
|
2778
|
+
} catch {
|
|
2779
|
+
}
|
|
2780
|
+
execSync2(
|
|
2781
|
+
`security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
|
|
2782
|
+
{ timeout: 5e3 }
|
|
2783
|
+
);
|
|
2784
|
+
return true;
|
|
2785
|
+
} catch {
|
|
2786
|
+
return false;
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
function macKeychainDelete() {
|
|
2790
|
+
if (process.platform !== "darwin") return false;
|
|
2791
|
+
try {
|
|
2792
|
+
execSync2(
|
|
2793
|
+
`security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
|
|
2794
|
+
{ timeout: 5e3 }
|
|
2795
|
+
);
|
|
2796
|
+
return true;
|
|
2797
|
+
} catch {
|
|
2798
|
+
return false;
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
function linuxSecretGet() {
|
|
2802
|
+
if (process.platform !== "linux") return null;
|
|
2803
|
+
try {
|
|
2804
|
+
return execSync2(
|
|
2805
|
+
`secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
|
|
2806
|
+
{ encoding: "utf-8", timeout: 5e3 }
|
|
2807
|
+
).trim();
|
|
2808
|
+
} catch {
|
|
2809
|
+
return null;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
function linuxSecretSet(value) {
|
|
2813
|
+
if (process.platform !== "linux") return false;
|
|
2814
|
+
try {
|
|
2815
|
+
execSync2(
|
|
2816
|
+
`echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
|
|
2817
|
+
{ timeout: 5e3 }
|
|
2818
|
+
);
|
|
2819
|
+
return true;
|
|
2820
|
+
} catch {
|
|
2821
|
+
return false;
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
function linuxSecretDelete() {
|
|
2825
|
+
if (process.platform !== "linux") return false;
|
|
2826
|
+
try {
|
|
2827
|
+
execSync2(
|
|
2828
|
+
`secret-tool clear service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
|
|
2829
|
+
{ timeout: 5e3 }
|
|
2830
|
+
);
|
|
2831
|
+
return true;
|
|
2832
|
+
} catch {
|
|
2833
|
+
return false;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2758
2836
|
async function tryKeytar() {
|
|
2759
2837
|
try {
|
|
2760
2838
|
return await import("keytar");
|
|
@@ -2762,13 +2840,72 @@ async function tryKeytar() {
|
|
|
2762
2840
|
return null;
|
|
2763
2841
|
}
|
|
2764
2842
|
}
|
|
2843
|
+
function deriveMachineKey() {
|
|
2844
|
+
try {
|
|
2845
|
+
const crypto4 = __require("crypto");
|
|
2846
|
+
const material = [
|
|
2847
|
+
os6.hostname(),
|
|
2848
|
+
os6.userInfo().username,
|
|
2849
|
+
os6.arch(),
|
|
2850
|
+
os6.platform(),
|
|
2851
|
+
// Machine ID on Linux (stable across reboots)
|
|
2852
|
+
process.platform === "linux" ? readMachineId() : ""
|
|
2853
|
+
].join("|");
|
|
2854
|
+
return crypto4.createHash("sha256").update(material).digest();
|
|
2855
|
+
} catch {
|
|
2856
|
+
return null;
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
function readMachineId() {
|
|
2860
|
+
try {
|
|
2861
|
+
const { readFileSync: readFileSync8 } = __require("fs");
|
|
2862
|
+
return readFileSync8("/etc/machine-id", "utf-8").trim();
|
|
2863
|
+
} catch {
|
|
2864
|
+
return "";
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
2868
|
+
const crypto4 = __require("crypto");
|
|
2869
|
+
const iv = crypto4.randomBytes(12);
|
|
2870
|
+
const cipher = crypto4.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
2871
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
2872
|
+
encrypted += cipher.final("base64");
|
|
2873
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
2874
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
2875
|
+
}
|
|
2876
|
+
function decryptWithMachineKey(encrypted, machineKey) {
|
|
2877
|
+
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
2878
|
+
try {
|
|
2879
|
+
const crypto4 = __require("crypto");
|
|
2880
|
+
const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
|
|
2881
|
+
if (parts.length !== 3) return null;
|
|
2882
|
+
const [ivB64, tagB64, cipherB64] = parts;
|
|
2883
|
+
const iv = Buffer.from(ivB64, "base64");
|
|
2884
|
+
const authTag = Buffer.from(tagB64, "base64");
|
|
2885
|
+
const decipher = crypto4.createDecipheriv("aes-256-gcm", machineKey, iv);
|
|
2886
|
+
decipher.setAuthTag(authTag);
|
|
2887
|
+
let decrypted = decipher.update(cipherB64, "base64", "utf-8");
|
|
2888
|
+
decrypted += decipher.final("utf-8");
|
|
2889
|
+
return decrypted;
|
|
2890
|
+
} catch {
|
|
2891
|
+
return null;
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2765
2894
|
async function getMasterKey() {
|
|
2895
|
+
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
2896
|
+
if (nativeValue) {
|
|
2897
|
+
return Buffer.from(nativeValue, "base64");
|
|
2898
|
+
}
|
|
2766
2899
|
const keytar = await tryKeytar();
|
|
2767
2900
|
if (keytar) {
|
|
2768
2901
|
try {
|
|
2769
|
-
const
|
|
2770
|
-
if (
|
|
2771
|
-
|
|
2902
|
+
const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
|
|
2903
|
+
if (keytarValue) {
|
|
2904
|
+
const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
|
|
2905
|
+
if (migrated) {
|
|
2906
|
+
process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
|
|
2907
|
+
}
|
|
2908
|
+
return Buffer.from(keytarValue, "base64");
|
|
2772
2909
|
}
|
|
2773
2910
|
} catch {
|
|
2774
2911
|
}
|
|
@@ -2782,8 +2919,31 @@ async function getMasterKey() {
|
|
|
2782
2919
|
return null;
|
|
2783
2920
|
}
|
|
2784
2921
|
try {
|
|
2785
|
-
const content = await readFile3(keyPath, "utf-8");
|
|
2786
|
-
|
|
2922
|
+
const content = (await readFile3(keyPath, "utf-8")).trim();
|
|
2923
|
+
let b64Value;
|
|
2924
|
+
if (content.startsWith(ENCRYPTED_PREFIX)) {
|
|
2925
|
+
const machineKey = deriveMachineKey();
|
|
2926
|
+
if (!machineKey) {
|
|
2927
|
+
process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
|
|
2928
|
+
return null;
|
|
2929
|
+
}
|
|
2930
|
+
const decrypted = decryptWithMachineKey(content, machineKey);
|
|
2931
|
+
if (!decrypted) {
|
|
2932
|
+
process.stderr.write(
|
|
2933
|
+
"[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
|
|
2934
|
+
);
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
b64Value = decrypted;
|
|
2938
|
+
} else {
|
|
2939
|
+
b64Value = content;
|
|
2940
|
+
}
|
|
2941
|
+
const key = Buffer.from(b64Value, "base64");
|
|
2942
|
+
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
2943
|
+
if (migrated) {
|
|
2944
|
+
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
2945
|
+
}
|
|
2946
|
+
return key;
|
|
2787
2947
|
} catch (err) {
|
|
2788
2948
|
process.stderr.write(
|
|
2789
2949
|
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -2794,6 +2954,9 @@ async function getMasterKey() {
|
|
|
2794
2954
|
}
|
|
2795
2955
|
async function setMasterKey(key) {
|
|
2796
2956
|
const b64 = key.toString("base64");
|
|
2957
|
+
if (macKeychainSet(b64) || linuxSecretSet(b64)) {
|
|
2958
|
+
return;
|
|
2959
|
+
}
|
|
2797
2960
|
const keytar = await tryKeytar();
|
|
2798
2961
|
if (keytar) {
|
|
2799
2962
|
try {
|
|
@@ -2805,10 +2968,23 @@ async function setMasterKey(key) {
|
|
|
2805
2968
|
const dir = getKeyDir();
|
|
2806
2969
|
await mkdir3(dir, { recursive: true });
|
|
2807
2970
|
const keyPath = getKeyPath();
|
|
2808
|
-
|
|
2809
|
-
|
|
2971
|
+
const machineKey = deriveMachineKey();
|
|
2972
|
+
if (machineKey) {
|
|
2973
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
2974
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
2975
|
+
await chmod2(keyPath, 384);
|
|
2976
|
+
process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
|
|
2977
|
+
} else {
|
|
2978
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
2979
|
+
await chmod2(keyPath, 384);
|
|
2980
|
+
process.stderr.write(
|
|
2981
|
+
"[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2810
2984
|
}
|
|
2811
2985
|
async function deleteMasterKey() {
|
|
2986
|
+
macKeychainDelete();
|
|
2987
|
+
linuxSecretDelete();
|
|
2812
2988
|
const keytar = await tryKeytar();
|
|
2813
2989
|
if (keytar) {
|
|
2814
2990
|
try {
|
|
@@ -2850,20 +3026,124 @@ async function importMnemonic(mnemonic) {
|
|
|
2850
3026
|
const entropy = mnemonicToEntropy(trimmed);
|
|
2851
3027
|
return Buffer.from(entropy, "hex");
|
|
2852
3028
|
}
|
|
2853
|
-
var SERVICE, ACCOUNT;
|
|
3029
|
+
var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
|
|
2854
3030
|
var init_keychain = __esm({
|
|
2855
3031
|
"src/lib/keychain.ts"() {
|
|
2856
3032
|
"use strict";
|
|
2857
3033
|
SERVICE = "exe-mem";
|
|
2858
3034
|
ACCOUNT = "master-key";
|
|
3035
|
+
ENCRYPTED_PREFIX = "enc:";
|
|
3036
|
+
}
|
|
3037
|
+
});
|
|
3038
|
+
|
|
3039
|
+
// src/lib/db-backup.ts
|
|
3040
|
+
var db_backup_exports = {};
|
|
3041
|
+
__export(db_backup_exports, {
|
|
3042
|
+
createBackup: () => createBackup,
|
|
3043
|
+
findActiveDb: () => findActiveDb,
|
|
3044
|
+
getBackupDir: () => getBackupDir,
|
|
3045
|
+
getLatestBackup: () => getLatestBackup,
|
|
3046
|
+
hasBackupToday: () => hasBackupToday,
|
|
3047
|
+
listBackups: () => listBackups,
|
|
3048
|
+
rotateBackups: () => rotateBackups
|
|
3049
|
+
});
|
|
3050
|
+
import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync, unlinkSync as unlinkSync4, statSync as statSync2 } from "fs";
|
|
3051
|
+
import path9 from "path";
|
|
3052
|
+
function findActiveDb() {
|
|
3053
|
+
for (const name of DB_NAMES) {
|
|
3054
|
+
const p = path9.join(EXE_AI_DIR, name);
|
|
3055
|
+
if (existsSync9(p)) return p;
|
|
3056
|
+
}
|
|
3057
|
+
return null;
|
|
3058
|
+
}
|
|
3059
|
+
function createBackup(reason = "manual") {
|
|
3060
|
+
const dbPath = findActiveDb();
|
|
3061
|
+
if (!dbPath) return null;
|
|
3062
|
+
mkdirSync4(BACKUP_DIR, { recursive: true });
|
|
3063
|
+
const dbName = path9.basename(dbPath, ".db");
|
|
3064
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
3065
|
+
const backupName = `${dbName}-${reason}-${timestamp}.db`;
|
|
3066
|
+
const backupPath = path9.join(BACKUP_DIR, backupName);
|
|
3067
|
+
copyFileSync(dbPath, backupPath);
|
|
3068
|
+
const walPath = dbPath + "-wal";
|
|
3069
|
+
if (existsSync9(walPath)) {
|
|
3070
|
+
try {
|
|
3071
|
+
copyFileSync(walPath, backupPath + "-wal");
|
|
3072
|
+
} catch {
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
const shmPath = dbPath + "-shm";
|
|
3076
|
+
if (existsSync9(shmPath)) {
|
|
3077
|
+
try {
|
|
3078
|
+
copyFileSync(shmPath, backupPath + "-shm");
|
|
3079
|
+
} catch {
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
return backupPath;
|
|
3083
|
+
}
|
|
3084
|
+
function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
|
|
3085
|
+
if (!existsSync9(BACKUP_DIR)) return 0;
|
|
3086
|
+
const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
|
|
3087
|
+
let deleted = 0;
|
|
3088
|
+
try {
|
|
3089
|
+
const files = readdirSync(BACKUP_DIR);
|
|
3090
|
+
for (const file of files) {
|
|
3091
|
+
if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
|
|
3092
|
+
const filePath = path9.join(BACKUP_DIR, file);
|
|
3093
|
+
try {
|
|
3094
|
+
const stat = statSync2(filePath);
|
|
3095
|
+
if (stat.mtimeMs < cutoff) {
|
|
3096
|
+
unlinkSync4(filePath);
|
|
3097
|
+
deleted++;
|
|
3098
|
+
}
|
|
3099
|
+
} catch {
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
} catch {
|
|
3103
|
+
}
|
|
3104
|
+
return deleted;
|
|
3105
|
+
}
|
|
3106
|
+
function listBackups() {
|
|
3107
|
+
if (!existsSync9(BACKUP_DIR)) return [];
|
|
3108
|
+
try {
|
|
3109
|
+
const files = readdirSync(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
|
|
3110
|
+
return files.map((name) => {
|
|
3111
|
+
const p = path9.join(BACKUP_DIR, name);
|
|
3112
|
+
const stat = statSync2(p);
|
|
3113
|
+
return { path: p, name, size: stat.size, date: stat.mtime };
|
|
3114
|
+
}).sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
3115
|
+
} catch {
|
|
3116
|
+
return [];
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
function hasBackupToday(reason) {
|
|
3120
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3121
|
+
const backups = listBackups();
|
|
3122
|
+
return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
|
|
3123
|
+
}
|
|
3124
|
+
function getLatestBackup() {
|
|
3125
|
+
const backups = listBackups();
|
|
3126
|
+
return backups.length > 0 ? backups[0].path : null;
|
|
3127
|
+
}
|
|
3128
|
+
function getBackupDir() {
|
|
3129
|
+
return BACKUP_DIR;
|
|
3130
|
+
}
|
|
3131
|
+
var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
|
|
3132
|
+
var init_db_backup = __esm({
|
|
3133
|
+
"src/lib/db-backup.ts"() {
|
|
3134
|
+
"use strict";
|
|
3135
|
+
init_config();
|
|
3136
|
+
BACKUP_DIR = path9.join(EXE_AI_DIR, "backups");
|
|
3137
|
+
DEFAULT_KEEP_DAYS = 3;
|
|
3138
|
+
DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
|
|
2859
3139
|
}
|
|
2860
3140
|
});
|
|
2861
3141
|
|
|
2862
3142
|
// src/lib/cloud-sync.ts
|
|
2863
3143
|
init_database();
|
|
2864
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as
|
|
3144
|
+
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";
|
|
2865
3145
|
import crypto3 from "crypto";
|
|
2866
|
-
import
|
|
3146
|
+
import path10 from "path";
|
|
2867
3147
|
import { homedir as homedir2 } from "os";
|
|
2868
3148
|
|
|
2869
3149
|
// src/lib/crypto.ts
|
|
@@ -2971,7 +3251,7 @@ function sqlSafe(v) {
|
|
|
2971
3251
|
}
|
|
2972
3252
|
function logError(msg) {
|
|
2973
3253
|
try {
|
|
2974
|
-
const logPath =
|
|
3254
|
+
const logPath = path10.join(homedir2(), ".exe-os", "workers.log");
|
|
2975
3255
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
2976
3256
|
`);
|
|
2977
3257
|
} catch {
|
|
@@ -2980,17 +3260,17 @@ function logError(msg) {
|
|
|
2980
3260
|
var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
2981
3261
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
2982
3262
|
var PUSH_BATCH_SIZE = 5e3;
|
|
2983
|
-
var ROSTER_LOCK_PATH =
|
|
3263
|
+
var ROSTER_LOCK_PATH = path10.join(EXE_AI_DIR, "roster-merge.lock");
|
|
2984
3264
|
var LOCK_STALE_MS = 3e4;
|
|
2985
3265
|
var _pgPromise = null;
|
|
2986
3266
|
var _pgFailed = false;
|
|
2987
3267
|
function loadPgClient() {
|
|
2988
3268
|
if (_pgFailed) return null;
|
|
2989
3269
|
const postgresUrl = process.env.DATABASE_URL;
|
|
2990
|
-
const configPath =
|
|
3270
|
+
const configPath = path10.join(EXE_AI_DIR, "config.json");
|
|
2991
3271
|
let cloudPostgresUrl;
|
|
2992
3272
|
try {
|
|
2993
|
-
if (
|
|
3273
|
+
if (existsSync10(configPath)) {
|
|
2994
3274
|
const cfg = JSON.parse(readFileSync7(configPath, "utf8"));
|
|
2995
3275
|
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
2996
3276
|
if (cfg.cloud?.syncToPostgres === false) {
|
|
@@ -3009,8 +3289,8 @@ function loadPgClient() {
|
|
|
3009
3289
|
_pgPromise = (async () => {
|
|
3010
3290
|
const { createRequire: createRequire3 } = await import("module");
|
|
3011
3291
|
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
3012
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
3013
|
-
const req = createRequire3(
|
|
3292
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(homedir2(), "exe-db");
|
|
3293
|
+
const req = createRequire3(path10.join(exeDbRoot, "package.json"));
|
|
3014
3294
|
const entry = req.resolve("@prisma/client");
|
|
3015
3295
|
const mod = await import(pathToFileURL3(entry).href);
|
|
3016
3296
|
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
@@ -3063,7 +3343,7 @@ async function withRosterLock(fn) {
|
|
|
3063
3343
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
3064
3344
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
3065
3345
|
}
|
|
3066
|
-
|
|
3346
|
+
unlinkSync5(ROSTER_LOCK_PATH);
|
|
3067
3347
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3068
3348
|
closeSync2(fd);
|
|
3069
3349
|
writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
|
|
@@ -3079,7 +3359,7 @@ async function withRosterLock(fn) {
|
|
|
3079
3359
|
return await fn();
|
|
3080
3360
|
} finally {
|
|
3081
3361
|
try {
|
|
3082
|
-
|
|
3362
|
+
unlinkSync5(ROSTER_LOCK_PATH);
|
|
3083
3363
|
} catch {
|
|
3084
3364
|
}
|
|
3085
3365
|
}
|
|
@@ -3450,13 +3730,42 @@ async function cloudSync(config) {
|
|
|
3450
3730
|
try {
|
|
3451
3731
|
const employees = await loadEmployees();
|
|
3452
3732
|
rosterResult.employees = employees.length;
|
|
3453
|
-
const idDir =
|
|
3454
|
-
if (
|
|
3455
|
-
rosterResult.identities =
|
|
3733
|
+
const idDir = path10.join(EXE_AI_DIR, "identity");
|
|
3734
|
+
if (existsSync10(idDir)) {
|
|
3735
|
+
rosterResult.identities = readdirSync2(idDir).filter((f) => f.endsWith(".md")).length;
|
|
3456
3736
|
}
|
|
3457
3737
|
} catch {
|
|
3458
3738
|
}
|
|
3459
3739
|
const totalMemories = await countRows("SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL");
|
|
3740
|
+
try {
|
|
3741
|
+
const { getLatestBackup: getLatestBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
|
|
3742
|
+
const { statSync: statFile } = await import("fs");
|
|
3743
|
+
const latestBackup = getLatestBackup2();
|
|
3744
|
+
if (latestBackup) {
|
|
3745
|
+
const backupSize = statFile(latestBackup).size;
|
|
3746
|
+
const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
|
|
3747
|
+
if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
|
|
3748
|
+
const backupData = readFileSync7(latestBackup);
|
|
3749
|
+
const deviceId = loadDeviceId() ?? "unknown";
|
|
3750
|
+
const encrypted = encryptSyncBlob(backupData);
|
|
3751
|
+
const backupRes = await fetchWithRetry(`${config.endpoint}/sync/push-db-backup`, {
|
|
3752
|
+
method: "POST",
|
|
3753
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
|
|
3754
|
+
body: JSON.stringify({
|
|
3755
|
+
device_id: deviceId,
|
|
3756
|
+
filename: path10.basename(latestBackup),
|
|
3757
|
+
blob: encrypted,
|
|
3758
|
+
size: backupData.length
|
|
3759
|
+
})
|
|
3760
|
+
});
|
|
3761
|
+
if (backupRes && !backupRes.ok) {
|
|
3762
|
+
logError(`[cloud-sync] DB backup upload failed: ${backupRes.status}`);
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
} catch (err) {
|
|
3767
|
+
logError(`[cloud-sync] DB backup upload error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3768
|
+
}
|
|
3460
3769
|
return {
|
|
3461
3770
|
pushed,
|
|
3462
3771
|
pulled,
|
|
@@ -3469,11 +3778,11 @@ async function cloudSync(config) {
|
|
|
3469
3778
|
roster: rosterResult
|
|
3470
3779
|
};
|
|
3471
3780
|
}
|
|
3472
|
-
var ROSTER_DELETIONS_PATH =
|
|
3781
|
+
var ROSTER_DELETIONS_PATH = path10.join(EXE_AI_DIR, "roster-deletions.json");
|
|
3473
3782
|
function recordRosterDeletion(name) {
|
|
3474
3783
|
let deletions = [];
|
|
3475
3784
|
try {
|
|
3476
|
-
if (
|
|
3785
|
+
if (existsSync10(ROSTER_DELETIONS_PATH)) {
|
|
3477
3786
|
deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
3478
3787
|
}
|
|
3479
3788
|
} catch {
|
|
@@ -3483,7 +3792,7 @@ function recordRosterDeletion(name) {
|
|
|
3483
3792
|
}
|
|
3484
3793
|
function consumeRosterDeletions() {
|
|
3485
3794
|
try {
|
|
3486
|
-
if (!
|
|
3795
|
+
if (!existsSync10(ROSTER_DELETIONS_PATH)) return [];
|
|
3487
3796
|
const deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
3488
3797
|
writeFileSync5(ROSTER_DELETIONS_PATH, "[]");
|
|
3489
3798
|
return deletions;
|
|
@@ -3492,35 +3801,35 @@ function consumeRosterDeletions() {
|
|
|
3492
3801
|
}
|
|
3493
3802
|
}
|
|
3494
3803
|
function buildRosterBlob(paths) {
|
|
3495
|
-
const rosterPath = paths?.rosterPath ??
|
|
3496
|
-
const identityDir = paths?.identityDir ??
|
|
3497
|
-
const configPath = paths?.configPath ??
|
|
3804
|
+
const rosterPath = paths?.rosterPath ?? path10.join(EXE_AI_DIR, "exe-employees.json");
|
|
3805
|
+
const identityDir = paths?.identityDir ?? path10.join(EXE_AI_DIR, "identity");
|
|
3806
|
+
const configPath = paths?.configPath ?? path10.join(EXE_AI_DIR, "config.json");
|
|
3498
3807
|
let roster = [];
|
|
3499
|
-
if (
|
|
3808
|
+
if (existsSync10(rosterPath)) {
|
|
3500
3809
|
try {
|
|
3501
3810
|
roster = JSON.parse(readFileSync7(rosterPath, "utf-8"));
|
|
3502
3811
|
} catch {
|
|
3503
3812
|
}
|
|
3504
3813
|
}
|
|
3505
3814
|
const identities = {};
|
|
3506
|
-
if (
|
|
3507
|
-
for (const file of
|
|
3815
|
+
if (existsSync10(identityDir)) {
|
|
3816
|
+
for (const file of readdirSync2(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
3508
3817
|
try {
|
|
3509
|
-
identities[file] = readFileSync7(
|
|
3818
|
+
identities[file] = readFileSync7(path10.join(identityDir, file), "utf-8");
|
|
3510
3819
|
} catch {
|
|
3511
3820
|
}
|
|
3512
3821
|
}
|
|
3513
3822
|
}
|
|
3514
3823
|
let config;
|
|
3515
|
-
if (
|
|
3824
|
+
if (existsSync10(configPath)) {
|
|
3516
3825
|
try {
|
|
3517
3826
|
config = JSON.parse(readFileSync7(configPath, "utf-8"));
|
|
3518
3827
|
} catch {
|
|
3519
3828
|
}
|
|
3520
3829
|
}
|
|
3521
3830
|
let agentConfig;
|
|
3522
|
-
const agentConfigPath =
|
|
3523
|
-
if (
|
|
3831
|
+
const agentConfigPath = path10.join(EXE_AI_DIR, "agent-config.json");
|
|
3832
|
+
if (existsSync10(agentConfigPath)) {
|
|
3524
3833
|
try {
|
|
3525
3834
|
agentConfig = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
|
|
3526
3835
|
} catch {
|
|
@@ -3598,16 +3907,16 @@ async function cloudPullRoster(config) {
|
|
|
3598
3907
|
}
|
|
3599
3908
|
}
|
|
3600
3909
|
function mergeConfig(remoteConfig, configPath) {
|
|
3601
|
-
const cfgPath = configPath ??
|
|
3910
|
+
const cfgPath = configPath ?? path10.join(EXE_AI_DIR, "config.json");
|
|
3602
3911
|
let local = {};
|
|
3603
|
-
if (
|
|
3912
|
+
if (existsSync10(cfgPath)) {
|
|
3604
3913
|
try {
|
|
3605
3914
|
local = JSON.parse(readFileSync7(cfgPath, "utf-8"));
|
|
3606
3915
|
} catch {
|
|
3607
3916
|
}
|
|
3608
3917
|
}
|
|
3609
3918
|
const merged = { ...remoteConfig, ...local };
|
|
3610
|
-
const dir =
|
|
3919
|
+
const dir = path10.dirname(cfgPath);
|
|
3611
3920
|
ensurePrivateDirSync(dir);
|
|
3612
3921
|
writeFileSync5(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
3613
3922
|
enforcePrivateFileSync(cfgPath);
|
|
@@ -3615,7 +3924,7 @@ function mergeConfig(remoteConfig, configPath) {
|
|
|
3615
3924
|
async function mergeRosterFromRemote(remote, paths) {
|
|
3616
3925
|
return withRosterLock(async () => {
|
|
3617
3926
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
3618
|
-
const identityDir = paths?.identityDir ??
|
|
3927
|
+
const identityDir = paths?.identityDir ?? path10.join(EXE_AI_DIR, "identity");
|
|
3619
3928
|
const localEmployees = await loadEmployees(rosterPath);
|
|
3620
3929
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
3621
3930
|
let added = 0;
|
|
@@ -3636,11 +3945,11 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3636
3945
|
) ?? lookupKey;
|
|
3637
3946
|
const remoteIdentity = remote.identities[matchedKey];
|
|
3638
3947
|
if (remoteIdentity) {
|
|
3639
|
-
if (!
|
|
3640
|
-
const idPath =
|
|
3948
|
+
if (!existsSync10(identityDir)) mkdirSync5(identityDir, { recursive: true });
|
|
3949
|
+
const idPath = path10.join(identityDir, `${remoteEmp.name}.md`);
|
|
3641
3950
|
let localIdentity = null;
|
|
3642
3951
|
try {
|
|
3643
|
-
localIdentity =
|
|
3952
|
+
localIdentity = existsSync10(idPath) ? readFileSync7(idPath, "utf-8") : null;
|
|
3644
3953
|
} catch {
|
|
3645
3954
|
}
|
|
3646
3955
|
if (localIdentity !== remoteIdentity) {
|
|
@@ -3670,16 +3979,16 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
3670
3979
|
}
|
|
3671
3980
|
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
3672
3981
|
try {
|
|
3673
|
-
const agentConfigPath =
|
|
3982
|
+
const agentConfigPath = path10.join(EXE_AI_DIR, "agent-config.json");
|
|
3674
3983
|
let local = {};
|
|
3675
|
-
if (
|
|
3984
|
+
if (existsSync10(agentConfigPath)) {
|
|
3676
3985
|
try {
|
|
3677
3986
|
local = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
|
|
3678
3987
|
} catch {
|
|
3679
3988
|
}
|
|
3680
3989
|
}
|
|
3681
3990
|
const merged = { ...remote.agentConfig, ...local };
|
|
3682
|
-
ensurePrivateDirSync(
|
|
3991
|
+
ensurePrivateDirSync(path10.dirname(agentConfigPath));
|
|
3683
3992
|
writeFileSync5(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
3684
3993
|
enforcePrivateFileSync(agentConfigPath);
|
|
3685
3994
|
} catch {
|
|
@@ -555,6 +555,7 @@ import { createHash } from "crypto";
|
|
|
555
555
|
// src/lib/keychain.ts
|
|
556
556
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
557
557
|
import { existsSync as existsSync5 } from "fs";
|
|
558
|
+
import { execSync as execSync2 } from "child_process";
|
|
558
559
|
import path5 from "path";
|
|
559
560
|
import os4 from "os";
|
|
560
561
|
|