@askexenow/exe-os 0.9.64 → 0.9.66
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/deploy/stack-manifests/v0.9.json +4 -4
- package/dist/bin/backfill-conversations.js +22 -0
- package/dist/bin/backfill-responses.js +22 -0
- package/dist/bin/backfill-vectors.js +22 -0
- package/dist/bin/cleanup-stale-review-tasks.js +22 -0
- package/dist/bin/cli.js +2280 -1199
- package/dist/bin/exe-agent-config.js +4 -0
- package/dist/bin/exe-agent.js +16 -0
- package/dist/bin/exe-assign.js +22 -0
- package/dist/bin/exe-boot.js +116 -7
- package/dist/bin/exe-call.js +16 -0
- package/dist/bin/exe-cloud.js +6671 -464
- package/dist/bin/exe-dispatch.js +24 -0
- package/dist/bin/exe-doctor.js +2845 -1223
- package/dist/bin/exe-export-behaviors.js +24 -0
- package/dist/bin/exe-forget.js +22 -0
- package/dist/bin/exe-gateway.js +24 -0
- package/dist/bin/exe-heartbeat.js +23 -0
- package/dist/bin/exe-kill.js +22 -0
- package/dist/bin/exe-launch-agent.js +24 -0
- package/dist/bin/exe-link.js +310 -178
- package/dist/bin/exe-new-employee.js +127 -1
- package/dist/bin/exe-pending-messages.js +22 -0
- package/dist/bin/exe-pending-notifications.js +22 -0
- package/dist/bin/exe-pending-reviews.js +22 -0
- package/dist/bin/exe-rename.js +22 -0
- package/dist/bin/exe-review.js +22 -0
- package/dist/bin/exe-search.js +24 -0
- package/dist/bin/exe-session-cleanup.js +24 -0
- package/dist/bin/exe-settings.js +10 -0
- package/dist/bin/exe-start-codex.js +135 -1
- package/dist/bin/exe-start-opencode.js +149 -1
- package/dist/bin/exe-status.js +22 -0
- package/dist/bin/exe-team.js +22 -0
- package/dist/bin/git-sweep.js +24 -0
- package/dist/bin/graph-backfill.js +22 -0
- package/dist/bin/graph-export.js +22 -0
- package/dist/bin/install.js +115 -1
- package/dist/bin/intercom-check.js +24 -0
- package/dist/bin/scan-tasks.js +24 -0
- package/dist/bin/setup.js +412 -157
- package/dist/bin/shard-migrate.js +22 -0
- package/dist/bin/update.js +4 -0
- package/dist/gateway/index.js +24 -0
- package/dist/hooks/bug-report-worker.js +135 -42
- package/dist/hooks/codex-stop-task-finalizer.js +24 -0
- package/dist/hooks/commit-complete.js +24 -0
- package/dist/hooks/error-recall.js +24 -0
- package/dist/hooks/exe-heartbeat-hook.js +4 -0
- package/dist/hooks/ingest-worker.js +4 -0
- package/dist/hooks/ingest.js +23 -0
- package/dist/hooks/instructions-loaded.js +22 -0
- package/dist/hooks/notification.js +22 -0
- package/dist/hooks/post-compact.js +22 -0
- package/dist/hooks/post-tool-combined.js +24 -0
- package/dist/hooks/pre-compact.js +260 -109
- package/dist/hooks/pre-tool-use.js +22 -0
- package/dist/hooks/prompt-submit.js +24 -0
- package/dist/hooks/session-end.js +161 -122
- package/dist/hooks/session-start.js +142 -0
- package/dist/hooks/stop.js +23 -0
- package/dist/hooks/subagent-stop.js +22 -0
- package/dist/hooks/summary-worker.js +195 -79
- package/dist/index.js +24 -0
- package/dist/lib/agent-config.js +4 -0
- package/dist/lib/cloud-sync.js +50 -6
- package/dist/lib/config.js +12 -0
- package/dist/lib/consolidation.js +4 -0
- package/dist/lib/database.js +4 -0
- package/dist/lib/db-daemon-client.js +4 -0
- package/dist/lib/db.js +4 -0
- package/dist/lib/device-registry.js +4 -0
- package/dist/lib/embedder.js +12 -0
- package/dist/lib/employee-templates.js +16 -0
- package/dist/lib/employees.js +4 -0
- package/dist/lib/exe-daemon-client.js +4 -0
- package/dist/lib/exe-daemon.js +1144 -480
- package/dist/lib/hybrid-search.js +24 -0
- package/dist/lib/identity.js +4 -0
- package/dist/lib/license.js +4 -0
- package/dist/lib/messaging.js +4 -0
- package/dist/lib/reminders.js +4 -0
- package/dist/lib/schedules.js +22 -0
- package/dist/lib/skill-learning.js +12 -0
- package/dist/lib/status-brief.js +39 -0
- package/dist/lib/store.js +22 -0
- package/dist/lib/task-router.js +4 -0
- package/dist/lib/tasks.js +12 -0
- package/dist/lib/tmux-routing.js +12 -0
- package/dist/lib/token-spend.js +4 -0
- package/dist/mcp/server.js +1045 -427
- package/dist/mcp/tools/complete-reminder.js +4 -0
- package/dist/mcp/tools/create-reminder.js +4 -0
- package/dist/mcp/tools/create-task.js +12 -0
- package/dist/mcp/tools/deactivate-behavior.js +4 -0
- package/dist/mcp/tools/list-reminders.js +4 -0
- package/dist/mcp/tools/list-tasks.js +4 -0
- package/dist/mcp/tools/send-message.js +4 -0
- package/dist/mcp/tools/update-task.js +12 -0
- package/dist/runtime/index.js +24 -0
- package/dist/tui/App.js +24 -0
- package/package.json +3 -2
- package/src/commands/exe/cloud.md +15 -8
- package/src/commands/exe/link.md +7 -6
- package/stack.release.json +2 -2
package/dist/bin/exe-link.js
CHANGED
|
@@ -137,8 +137,8 @@ function deriveMachineKey() {
|
|
|
137
137
|
}
|
|
138
138
|
function readMachineId() {
|
|
139
139
|
try {
|
|
140
|
-
const { readFileSync:
|
|
141
|
-
return
|
|
140
|
+
const { readFileSync: readFileSync9 } = __require("fs");
|
|
141
|
+
return readFileSync9("/etc/machine-id", "utf-8").trim();
|
|
142
142
|
} catch {
|
|
143
143
|
return "";
|
|
144
144
|
}
|
|
@@ -460,6 +460,11 @@ function normalizeAutoUpdate(raw) {
|
|
|
460
460
|
const userAU = raw.autoUpdate ?? {};
|
|
461
461
|
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
462
462
|
}
|
|
463
|
+
function normalizeOrchestration(raw) {
|
|
464
|
+
const defaultOrg = DEFAULT_CONFIG.orchestration;
|
|
465
|
+
const userOrg = raw.orchestration ?? {};
|
|
466
|
+
raw.orchestration = { ...defaultOrg, ...userOrg };
|
|
467
|
+
}
|
|
463
468
|
async function loadConfig() {
|
|
464
469
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
465
470
|
await ensurePrivateDir(dir);
|
|
@@ -484,6 +489,7 @@ async function loadConfig() {
|
|
|
484
489
|
normalizeScalingRoadmap(migratedCfg);
|
|
485
490
|
normalizeSessionLifecycle(migratedCfg);
|
|
486
491
|
normalizeAutoUpdate(migratedCfg);
|
|
492
|
+
normalizeOrchestration(migratedCfg);
|
|
487
493
|
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
488
494
|
if (config.dbPath.startsWith("~")) {
|
|
489
495
|
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
@@ -507,6 +513,7 @@ function loadConfigSync() {
|
|
|
507
513
|
normalizeScalingRoadmap(migratedCfg);
|
|
508
514
|
normalizeSessionLifecycle(migratedCfg);
|
|
509
515
|
normalizeAutoUpdate(migratedCfg);
|
|
516
|
+
normalizeOrchestration(migratedCfg);
|
|
510
517
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
511
518
|
} catch {
|
|
512
519
|
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
@@ -528,6 +535,7 @@ async function loadConfigFrom(configPath) {
|
|
|
528
535
|
normalizeScalingRoadmap(migratedCfg);
|
|
529
536
|
normalizeSessionLifecycle(migratedCfg);
|
|
530
537
|
normalizeAutoUpdate(migratedCfg);
|
|
538
|
+
normalizeOrchestration(migratedCfg);
|
|
531
539
|
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
532
540
|
} catch {
|
|
533
541
|
return { ...DEFAULT_CONFIG };
|
|
@@ -599,6 +607,10 @@ var init_config = __esm({
|
|
|
599
607
|
checkOnBoot: true,
|
|
600
608
|
autoInstall: false,
|
|
601
609
|
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
610
|
+
},
|
|
611
|
+
orchestration: {
|
|
612
|
+
phase: "phase_1_coo",
|
|
613
|
+
phaseSetBy: "default"
|
|
602
614
|
}
|
|
603
615
|
};
|
|
604
616
|
CONFIG_MIGRATIONS = [
|
|
@@ -614,6 +626,47 @@ var init_config = __esm({
|
|
|
614
626
|
}
|
|
615
627
|
});
|
|
616
628
|
|
|
629
|
+
// src/lib/key-backup-status.ts
|
|
630
|
+
var key_backup_status_exports = {};
|
|
631
|
+
__export(key_backup_status_exports, {
|
|
632
|
+
getKeyBackupStatus: () => getKeyBackupStatus,
|
|
633
|
+
keyBackupMarkerPath: () => keyBackupMarkerPath,
|
|
634
|
+
markKeyBackupConfirmed: () => markKeyBackupConfirmed
|
|
635
|
+
});
|
|
636
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
637
|
+
import path3 from "path";
|
|
638
|
+
function keyBackupMarkerPath() {
|
|
639
|
+
return path3.join(EXE_AI_DIR, "key-backup-confirmed.json");
|
|
640
|
+
}
|
|
641
|
+
function getKeyBackupStatus() {
|
|
642
|
+
const marker = keyBackupMarkerPath();
|
|
643
|
+
if (!existsSync4(marker)) return { exists: false };
|
|
644
|
+
try {
|
|
645
|
+
const parsed = JSON.parse(readFileSync2(marker, "utf8"));
|
|
646
|
+
return {
|
|
647
|
+
exists: true,
|
|
648
|
+
confirmedAt: parsed.confirmedAt,
|
|
649
|
+
source: parsed.source
|
|
650
|
+
};
|
|
651
|
+
} catch {
|
|
652
|
+
return { exists: true };
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function markKeyBackupConfirmed(source) {
|
|
656
|
+
mkdirSync2(EXE_AI_DIR, { recursive: true, mode: 448 });
|
|
657
|
+
writeFileSync(
|
|
658
|
+
keyBackupMarkerPath(),
|
|
659
|
+
JSON.stringify({ confirmedAt: (/* @__PURE__ */ new Date()).toISOString(), source }, null, 2) + "\n",
|
|
660
|
+
{ mode: 384 }
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
var init_key_backup_status = __esm({
|
|
664
|
+
"src/lib/key-backup-status.ts"() {
|
|
665
|
+
"use strict";
|
|
666
|
+
init_config();
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
|
|
617
670
|
// src/lib/crypto.ts
|
|
618
671
|
var crypto_exports = {};
|
|
619
672
|
__export(crypto_exports, {
|
|
@@ -730,9 +783,9 @@ var init_db_retry = __esm({
|
|
|
730
783
|
|
|
731
784
|
// src/lib/employees.ts
|
|
732
785
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
733
|
-
import { existsSync as
|
|
786
|
+
import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
734
787
|
import { execSync as execSync2 } from "child_process";
|
|
735
|
-
import
|
|
788
|
+
import path4 from "path";
|
|
736
789
|
import os3 from "os";
|
|
737
790
|
function normalizeRole(role) {
|
|
738
791
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -747,7 +800,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
|
747
800
|
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
748
801
|
}
|
|
749
802
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
750
|
-
if (!
|
|
803
|
+
if (!existsSync5(employeesPath)) {
|
|
751
804
|
return [];
|
|
752
805
|
}
|
|
753
806
|
const raw = await readFile3(employeesPath, "utf-8");
|
|
@@ -758,13 +811,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
758
811
|
}
|
|
759
812
|
}
|
|
760
813
|
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
761
|
-
await mkdir3(
|
|
814
|
+
await mkdir3(path4.dirname(employeesPath), { recursive: true });
|
|
762
815
|
await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
763
816
|
}
|
|
764
817
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
765
|
-
if (!
|
|
818
|
+
if (!existsSync5(employeesPath)) return [];
|
|
766
819
|
try {
|
|
767
|
-
return JSON.parse(
|
|
820
|
+
return JSON.parse(readFileSync3(employeesPath, "utf-8"));
|
|
768
821
|
} catch {
|
|
769
822
|
return [];
|
|
770
823
|
}
|
|
@@ -785,7 +838,7 @@ function registerBinSymlinks(name) {
|
|
|
785
838
|
errors.push("Could not find 'exe-os' in PATH");
|
|
786
839
|
return { created, skipped, errors };
|
|
787
840
|
}
|
|
788
|
-
const binDir =
|
|
841
|
+
const binDir = path4.dirname(exeBinPath);
|
|
789
842
|
let target;
|
|
790
843
|
try {
|
|
791
844
|
target = readlinkSync(exeBinPath);
|
|
@@ -795,8 +848,8 @@ function registerBinSymlinks(name) {
|
|
|
795
848
|
}
|
|
796
849
|
for (const suffix of ["", "-opencode"]) {
|
|
797
850
|
const linkName = `${name}${suffix}`;
|
|
798
|
-
const linkPath =
|
|
799
|
-
if (
|
|
851
|
+
const linkPath = path4.join(binDir, linkName);
|
|
852
|
+
if (existsSync5(linkPath)) {
|
|
800
853
|
skipped.push(linkName);
|
|
801
854
|
continue;
|
|
802
855
|
}
|
|
@@ -814,16 +867,16 @@ var init_employees = __esm({
|
|
|
814
867
|
"src/lib/employees.ts"() {
|
|
815
868
|
"use strict";
|
|
816
869
|
init_config();
|
|
817
|
-
EMPLOYEES_PATH =
|
|
870
|
+
EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
818
871
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
819
872
|
COORDINATOR_ROLE = "COO";
|
|
820
|
-
IDENTITY_DIR =
|
|
873
|
+
IDENTITY_DIR = path4.join(EXE_AI_DIR, "identity");
|
|
821
874
|
}
|
|
822
875
|
});
|
|
823
876
|
|
|
824
877
|
// src/lib/database-adapter.ts
|
|
825
878
|
import os4 from "os";
|
|
826
|
-
import
|
|
879
|
+
import path5 from "path";
|
|
827
880
|
import { createRequire } from "module";
|
|
828
881
|
import { pathToFileURL } from "url";
|
|
829
882
|
function quotedIdentifier(identifier) {
|
|
@@ -1134,8 +1187,8 @@ async function loadPrismaClient() {
|
|
|
1134
1187
|
}
|
|
1135
1188
|
return new PrismaClient2();
|
|
1136
1189
|
}
|
|
1137
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
1138
|
-
const requireFromExeDb = createRequire(
|
|
1190
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path5.join(os4.homedir(), "exe-db");
|
|
1191
|
+
const requireFromExeDb = createRequire(path5.join(exeDbRoot, "package.json"));
|
|
1139
1192
|
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1140
1193
|
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1141
1194
|
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
@@ -1416,8 +1469,8 @@ var init_memory = __esm({
|
|
|
1416
1469
|
|
|
1417
1470
|
// src/lib/daemon-auth.ts
|
|
1418
1471
|
import crypto2 from "crypto";
|
|
1419
|
-
import
|
|
1420
|
-
import { existsSync as
|
|
1472
|
+
import path6 from "path";
|
|
1473
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1421
1474
|
function normalizeToken(token) {
|
|
1422
1475
|
if (!token) return null;
|
|
1423
1476
|
const trimmed = token.trim();
|
|
@@ -1425,8 +1478,8 @@ function normalizeToken(token) {
|
|
|
1425
1478
|
}
|
|
1426
1479
|
function readDaemonToken() {
|
|
1427
1480
|
try {
|
|
1428
|
-
if (!
|
|
1429
|
-
return normalizeToken(
|
|
1481
|
+
if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
|
|
1482
|
+
return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
|
|
1430
1483
|
} catch {
|
|
1431
1484
|
return null;
|
|
1432
1485
|
}
|
|
@@ -1436,7 +1489,7 @@ function ensureDaemonToken(seed) {
|
|
|
1436
1489
|
if (existing) return existing;
|
|
1437
1490
|
const token = normalizeToken(seed) ?? crypto2.randomBytes(32).toString("hex");
|
|
1438
1491
|
ensurePrivateDirSync(EXE_AI_DIR);
|
|
1439
|
-
|
|
1492
|
+
writeFileSync3(DAEMON_TOKEN_PATH, `${token}
|
|
1440
1493
|
`, "utf8");
|
|
1441
1494
|
enforcePrivateFileSync(DAEMON_TOKEN_PATH);
|
|
1442
1495
|
return token;
|
|
@@ -1447,7 +1500,7 @@ var init_daemon_auth = __esm({
|
|
|
1447
1500
|
"use strict";
|
|
1448
1501
|
init_config();
|
|
1449
1502
|
init_secure_files();
|
|
1450
|
-
DAEMON_TOKEN_PATH =
|
|
1503
|
+
DAEMON_TOKEN_PATH = path6.join(EXE_AI_DIR, "exed.token");
|
|
1451
1504
|
}
|
|
1452
1505
|
});
|
|
1453
1506
|
|
|
@@ -1456,8 +1509,8 @@ import net from "net";
|
|
|
1456
1509
|
import os5 from "os";
|
|
1457
1510
|
import { spawn } from "child_process";
|
|
1458
1511
|
import { randomUUID } from "crypto";
|
|
1459
|
-
import { existsSync as
|
|
1460
|
-
import
|
|
1512
|
+
import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
|
|
1513
|
+
import path7 from "path";
|
|
1461
1514
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1462
1515
|
function handleData(chunk) {
|
|
1463
1516
|
_buffer += chunk.toString();
|
|
@@ -1485,9 +1538,9 @@ function handleData(chunk) {
|
|
|
1485
1538
|
}
|
|
1486
1539
|
}
|
|
1487
1540
|
function cleanupStaleFiles() {
|
|
1488
|
-
if (
|
|
1541
|
+
if (existsSync7(PID_PATH)) {
|
|
1489
1542
|
try {
|
|
1490
|
-
const pid = parseInt(
|
|
1543
|
+
const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
|
|
1491
1544
|
if (pid > 0) {
|
|
1492
1545
|
try {
|
|
1493
1546
|
process.kill(pid, 0);
|
|
@@ -1508,11 +1561,11 @@ function cleanupStaleFiles() {
|
|
|
1508
1561
|
}
|
|
1509
1562
|
}
|
|
1510
1563
|
function findPackageRoot() {
|
|
1511
|
-
let dir =
|
|
1512
|
-
const { root } =
|
|
1564
|
+
let dir = path7.dirname(fileURLToPath2(import.meta.url));
|
|
1565
|
+
const { root } = path7.parse(dir);
|
|
1513
1566
|
while (dir !== root) {
|
|
1514
|
-
if (
|
|
1515
|
-
dir =
|
|
1567
|
+
if (existsSync7(path7.join(dir, "package.json"))) return dir;
|
|
1568
|
+
dir = path7.dirname(dir);
|
|
1516
1569
|
}
|
|
1517
1570
|
return null;
|
|
1518
1571
|
}
|
|
@@ -1559,8 +1612,8 @@ function spawnDaemon() {
|
|
|
1559
1612
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
1560
1613
|
return;
|
|
1561
1614
|
}
|
|
1562
|
-
const daemonPath =
|
|
1563
|
-
if (!
|
|
1615
|
+
const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1616
|
+
if (!existsSync7(daemonPath)) {
|
|
1564
1617
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
1565
1618
|
`);
|
|
1566
1619
|
return;
|
|
@@ -1569,7 +1622,7 @@ function spawnDaemon() {
|
|
|
1569
1622
|
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
1570
1623
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
1571
1624
|
`);
|
|
1572
|
-
const logPath =
|
|
1625
|
+
const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
|
|
1573
1626
|
let stderrFd = "ignore";
|
|
1574
1627
|
try {
|
|
1575
1628
|
stderrFd = openSync(logPath, "a");
|
|
@@ -1719,9 +1772,9 @@ var init_exe_daemon_client = __esm({
|
|
|
1719
1772
|
"use strict";
|
|
1720
1773
|
init_config();
|
|
1721
1774
|
init_daemon_auth();
|
|
1722
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
1723
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
1724
|
-
SPAWN_LOCK_PATH =
|
|
1775
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
|
|
1776
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
|
|
1777
|
+
SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
1725
1778
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
1726
1779
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
1727
1780
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -3209,32 +3262,32 @@ var init_compress = __esm({
|
|
|
3209
3262
|
});
|
|
3210
3263
|
|
|
3211
3264
|
// src/lib/license.ts
|
|
3212
|
-
import { readFileSync as
|
|
3265
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
3213
3266
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3214
3267
|
import { createRequire as createRequire2 } from "module";
|
|
3215
3268
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3216
3269
|
import os6 from "os";
|
|
3217
|
-
import
|
|
3270
|
+
import path8 from "path";
|
|
3218
3271
|
import { jwtVerify, importSPKI } from "jose";
|
|
3219
3272
|
function loadDeviceId() {
|
|
3220
|
-
const deviceJsonPath =
|
|
3273
|
+
const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
|
|
3221
3274
|
try {
|
|
3222
|
-
if (
|
|
3223
|
-
const data = JSON.parse(
|
|
3275
|
+
if (existsSync8(deviceJsonPath)) {
|
|
3276
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
3224
3277
|
if (data.deviceId) return data.deviceId;
|
|
3225
3278
|
}
|
|
3226
3279
|
} catch {
|
|
3227
3280
|
}
|
|
3228
3281
|
try {
|
|
3229
|
-
if (
|
|
3230
|
-
const id2 =
|
|
3282
|
+
if (existsSync8(DEVICE_ID_PATH)) {
|
|
3283
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
3231
3284
|
if (id2) return id2;
|
|
3232
3285
|
}
|
|
3233
3286
|
} catch {
|
|
3234
3287
|
}
|
|
3235
3288
|
const id = randomUUID2();
|
|
3236
|
-
|
|
3237
|
-
|
|
3289
|
+
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
3290
|
+
writeFileSync4(DEVICE_ID_PATH, id, "utf8");
|
|
3238
3291
|
return id;
|
|
3239
3292
|
}
|
|
3240
3293
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
|
|
@@ -3242,9 +3295,9 @@ var init_license = __esm({
|
|
|
3242
3295
|
"src/lib/license.ts"() {
|
|
3243
3296
|
"use strict";
|
|
3244
3297
|
init_config();
|
|
3245
|
-
LICENSE_PATH =
|
|
3246
|
-
CACHE_PATH =
|
|
3247
|
-
DEVICE_ID_PATH =
|
|
3298
|
+
LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
|
|
3299
|
+
CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
|
|
3300
|
+
DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
|
|
3248
3301
|
}
|
|
3249
3302
|
});
|
|
3250
3303
|
|
|
@@ -3267,8 +3320,8 @@ __export(crdt_sync_exports, {
|
|
|
3267
3320
|
rebuildFromDb: () => rebuildFromDb
|
|
3268
3321
|
});
|
|
3269
3322
|
import * as Y from "yjs";
|
|
3270
|
-
import { readFileSync as
|
|
3271
|
-
import
|
|
3323
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
3324
|
+
import path9 from "path";
|
|
3272
3325
|
import { homedir } from "os";
|
|
3273
3326
|
function getStatePath() {
|
|
3274
3327
|
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
@@ -3280,9 +3333,9 @@ function initCrdtDoc() {
|
|
|
3280
3333
|
if (doc) return doc;
|
|
3281
3334
|
doc = new Y.Doc();
|
|
3282
3335
|
const sp = getStatePath();
|
|
3283
|
-
if (
|
|
3336
|
+
if (existsSync9(sp)) {
|
|
3284
3337
|
try {
|
|
3285
|
-
const state =
|
|
3338
|
+
const state = readFileSync7(sp);
|
|
3286
3339
|
Y.applyUpdate(doc, new Uint8Array(state));
|
|
3287
3340
|
} catch {
|
|
3288
3341
|
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
@@ -3424,10 +3477,10 @@ function persistState() {
|
|
|
3424
3477
|
if (!doc) return;
|
|
3425
3478
|
try {
|
|
3426
3479
|
const sp = getStatePath();
|
|
3427
|
-
const dir =
|
|
3428
|
-
if (!
|
|
3480
|
+
const dir = path9.dirname(sp);
|
|
3481
|
+
if (!existsSync9(dir)) mkdirSync4(dir, { recursive: true });
|
|
3429
3482
|
const state = Y.encodeStateAsUpdate(doc);
|
|
3430
|
-
|
|
3483
|
+
writeFileSync5(sp, Buffer.from(state));
|
|
3431
3484
|
} catch {
|
|
3432
3485
|
}
|
|
3433
3486
|
}
|
|
@@ -3468,7 +3521,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
|
3468
3521
|
var init_crdt_sync = __esm({
|
|
3469
3522
|
"src/lib/crdt-sync.ts"() {
|
|
3470
3523
|
"use strict";
|
|
3471
|
-
DEFAULT_STATE_PATH =
|
|
3524
|
+
DEFAULT_STATE_PATH = path9.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
3472
3525
|
_statePathOverride = null;
|
|
3473
3526
|
doc = null;
|
|
3474
3527
|
}
|
|
@@ -3485,33 +3538,33 @@ __export(db_backup_exports, {
|
|
|
3485
3538
|
listBackups: () => listBackups,
|
|
3486
3539
|
rotateBackups: () => rotateBackups
|
|
3487
3540
|
});
|
|
3488
|
-
import { copyFileSync, existsSync as
|
|
3489
|
-
import
|
|
3541
|
+
import { copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync, unlinkSync as unlinkSync4, statSync as statSync2 } from "fs";
|
|
3542
|
+
import path10 from "path";
|
|
3490
3543
|
function findActiveDb() {
|
|
3491
3544
|
for (const name of DB_NAMES) {
|
|
3492
|
-
const p =
|
|
3493
|
-
if (
|
|
3545
|
+
const p = path10.join(EXE_AI_DIR, name);
|
|
3546
|
+
if (existsSync10(p)) return p;
|
|
3494
3547
|
}
|
|
3495
3548
|
return null;
|
|
3496
3549
|
}
|
|
3497
3550
|
function createBackup(reason = "manual") {
|
|
3498
3551
|
const dbPath = findActiveDb();
|
|
3499
3552
|
if (!dbPath) return null;
|
|
3500
|
-
|
|
3501
|
-
const dbName =
|
|
3553
|
+
mkdirSync5(BACKUP_DIR, { recursive: true });
|
|
3554
|
+
const dbName = path10.basename(dbPath, ".db");
|
|
3502
3555
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
3503
3556
|
const backupName = `${dbName}-${reason}-${timestamp}.db`;
|
|
3504
|
-
const backupPath =
|
|
3557
|
+
const backupPath = path10.join(BACKUP_DIR, backupName);
|
|
3505
3558
|
copyFileSync(dbPath, backupPath);
|
|
3506
3559
|
const walPath = dbPath + "-wal";
|
|
3507
|
-
if (
|
|
3560
|
+
if (existsSync10(walPath)) {
|
|
3508
3561
|
try {
|
|
3509
3562
|
copyFileSync(walPath, backupPath + "-wal");
|
|
3510
3563
|
} catch {
|
|
3511
3564
|
}
|
|
3512
3565
|
}
|
|
3513
3566
|
const shmPath = dbPath + "-shm";
|
|
3514
|
-
if (
|
|
3567
|
+
if (existsSync10(shmPath)) {
|
|
3515
3568
|
try {
|
|
3516
3569
|
copyFileSync(shmPath, backupPath + "-shm");
|
|
3517
3570
|
} catch {
|
|
@@ -3520,14 +3573,14 @@ function createBackup(reason = "manual") {
|
|
|
3520
3573
|
return backupPath;
|
|
3521
3574
|
}
|
|
3522
3575
|
function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
|
|
3523
|
-
if (!
|
|
3576
|
+
if (!existsSync10(BACKUP_DIR)) return 0;
|
|
3524
3577
|
const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
|
|
3525
3578
|
let deleted = 0;
|
|
3526
3579
|
try {
|
|
3527
3580
|
const files = readdirSync(BACKUP_DIR);
|
|
3528
3581
|
for (const file of files) {
|
|
3529
3582
|
if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
|
|
3530
|
-
const filePath =
|
|
3583
|
+
const filePath = path10.join(BACKUP_DIR, file);
|
|
3531
3584
|
try {
|
|
3532
3585
|
const stat = statSync2(filePath);
|
|
3533
3586
|
if (stat.mtimeMs < cutoff) {
|
|
@@ -3542,11 +3595,11 @@ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
|
|
|
3542
3595
|
return deleted;
|
|
3543
3596
|
}
|
|
3544
3597
|
function listBackups() {
|
|
3545
|
-
if (!
|
|
3598
|
+
if (!existsSync10(BACKUP_DIR)) return [];
|
|
3546
3599
|
try {
|
|
3547
3600
|
const files = readdirSync(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
|
|
3548
3601
|
return files.map((name) => {
|
|
3549
|
-
const p =
|
|
3602
|
+
const p = path10.join(BACKUP_DIR, name);
|
|
3550
3603
|
const stat = statSync2(p);
|
|
3551
3604
|
return { path: p, name, size: stat.size, date: stat.mtime };
|
|
3552
3605
|
}).sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
@@ -3571,7 +3624,7 @@ var init_db_backup = __esm({
|
|
|
3571
3624
|
"src/lib/db-backup.ts"() {
|
|
3572
3625
|
"use strict";
|
|
3573
3626
|
init_config();
|
|
3574
|
-
BACKUP_DIR =
|
|
3627
|
+
BACKUP_DIR = path10.join(EXE_AI_DIR, "backups");
|
|
3575
3628
|
DEFAULT_KEEP_DAYS = 3;
|
|
3576
3629
|
DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
|
|
3577
3630
|
}
|
|
@@ -3580,8 +3633,10 @@ var init_db_backup = __esm({
|
|
|
3580
3633
|
// src/lib/cloud-sync.ts
|
|
3581
3634
|
var cloud_sync_exports = {};
|
|
3582
3635
|
__export(cloud_sync_exports, {
|
|
3636
|
+
CLOUD_RELINK_REQUIRED_MESSAGE: () => CLOUD_RELINK_REQUIRED_MESSAGE,
|
|
3583
3637
|
assertSecureEndpoint: () => assertSecureEndpoint,
|
|
3584
3638
|
buildRosterBlob: () => buildRosterBlob,
|
|
3639
|
+
clearCloudRelinkRequired: () => clearCloudRelinkRequired,
|
|
3585
3640
|
cloudPull: () => cloudPull,
|
|
3586
3641
|
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
3587
3642
|
cloudPullBlob: () => cloudPullBlob,
|
|
@@ -3601,53 +3656,66 @@ __export(cloud_sync_exports, {
|
|
|
3601
3656
|
cloudPushRoster: () => cloudPushRoster,
|
|
3602
3657
|
cloudPushTasks: () => cloudPushTasks,
|
|
3603
3658
|
cloudSync: () => cloudSync,
|
|
3659
|
+
getCloudRelinkRequired: () => getCloudRelinkRequired,
|
|
3604
3660
|
mergeConfig: () => mergeConfig,
|
|
3605
3661
|
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
3606
3662
|
pushToPostgres: () => pushToPostgres,
|
|
3607
3663
|
recordRosterDeletion: () => recordRosterDeletion
|
|
3608
3664
|
});
|
|
3609
|
-
import { readFileSync as
|
|
3665
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, readdirSync as readdirSync2, mkdirSync as mkdirSync6, appendFileSync, unlinkSync as unlinkSync5, openSync as openSync2, closeSync as closeSync2, statSync as statSync3 } from "fs";
|
|
3610
3666
|
import crypto3 from "crypto";
|
|
3611
|
-
import
|
|
3667
|
+
import path11 from "path";
|
|
3612
3668
|
import { homedir as homedir2 } from "os";
|
|
3613
3669
|
function sqlSafe(v) {
|
|
3614
3670
|
return v === void 0 ? null : v;
|
|
3615
3671
|
}
|
|
3616
3672
|
function logError(msg) {
|
|
3617
3673
|
try {
|
|
3618
|
-
const logPath =
|
|
3674
|
+
const logPath = path11.join(homedir2(), ".exe-os", "workers.log");
|
|
3619
3675
|
appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
3620
3676
|
`);
|
|
3621
3677
|
} catch {
|
|
3622
3678
|
}
|
|
3623
3679
|
}
|
|
3680
|
+
function isTruthyEnv(value) {
|
|
3681
|
+
return /^(1|true|yes|on)$/i.test(value ?? "");
|
|
3682
|
+
}
|
|
3624
3683
|
function loadPgClient() {
|
|
3625
3684
|
if (_pgFailed) return null;
|
|
3626
|
-
const
|
|
3627
|
-
const configPath = path10.join(EXE_AI_DIR, "config.json");
|
|
3685
|
+
const configPath = path11.join(EXE_AI_DIR, "config.json");
|
|
3628
3686
|
let cloudPostgresUrl;
|
|
3687
|
+
let configEnabled = false;
|
|
3629
3688
|
try {
|
|
3630
|
-
if (
|
|
3631
|
-
const cfg = JSON.parse(
|
|
3689
|
+
if (existsSync11(configPath)) {
|
|
3690
|
+
const cfg = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
3632
3691
|
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
3633
|
-
|
|
3634
|
-
_pgFailed = true;
|
|
3635
|
-
return null;
|
|
3636
|
-
}
|
|
3692
|
+
configEnabled = cfg.cloud?.syncToPostgres === true;
|
|
3637
3693
|
}
|
|
3638
3694
|
} catch {
|
|
3639
3695
|
}
|
|
3640
|
-
const
|
|
3696
|
+
const envEnabled = isTruthyEnv(process.env.EXE_CLOUD_SYNC_TO_POSTGRES);
|
|
3697
|
+
if (!envEnabled && !configEnabled) {
|
|
3698
|
+
return null;
|
|
3699
|
+
}
|
|
3700
|
+
const url = process.env.DATABASE_URL || cloudPostgresUrl;
|
|
3641
3701
|
if (!url) {
|
|
3642
3702
|
_pgFailed = true;
|
|
3643
3703
|
return null;
|
|
3644
3704
|
}
|
|
3645
3705
|
if (!_pgPromise) {
|
|
3646
3706
|
_pgPromise = (async () => {
|
|
3707
|
+
if (!process.env.DATABASE_URL) process.env.DATABASE_URL = url;
|
|
3647
3708
|
const { createRequire: createRequire3 } = await import("module");
|
|
3648
3709
|
const { pathToFileURL: pathToFileURL3 } = await import("url");
|
|
3649
|
-
const
|
|
3650
|
-
|
|
3710
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
3711
|
+
if (explicitPath) {
|
|
3712
|
+
const mod2 = await import(pathToFileURL3(explicitPath).href);
|
|
3713
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
3714
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
3715
|
+
return new Ctor2();
|
|
3716
|
+
}
|
|
3717
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(homedir2(), "exe-db");
|
|
3718
|
+
const req = createRequire3(path11.join(exeDbRoot, "package.json"));
|
|
3651
3719
|
const entry = req.resolve("@prisma/client");
|
|
3652
3720
|
const mod = await import(pathToFileURL3(entry).href);
|
|
3653
3721
|
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
@@ -3692,18 +3760,18 @@ async function withRosterLock(fn) {
|
|
|
3692
3760
|
try {
|
|
3693
3761
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3694
3762
|
closeSync2(fd);
|
|
3695
|
-
|
|
3763
|
+
writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3696
3764
|
} catch (err) {
|
|
3697
3765
|
if (err.code === "EEXIST") {
|
|
3698
3766
|
try {
|
|
3699
|
-
const ts = parseInt(
|
|
3767
|
+
const ts = parseInt(readFileSync8(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
3700
3768
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
3701
3769
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
3702
3770
|
}
|
|
3703
3771
|
unlinkSync5(ROSTER_LOCK_PATH);
|
|
3704
3772
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3705
3773
|
closeSync2(fd);
|
|
3706
|
-
|
|
3774
|
+
writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3707
3775
|
} catch (retryErr) {
|
|
3708
3776
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
3709
3777
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -3822,6 +3890,24 @@ async function cloudPull(sinceVersion, config) {
|
|
|
3822
3890
|
return { records: [], maxVersion: sinceVersion };
|
|
3823
3891
|
}
|
|
3824
3892
|
}
|
|
3893
|
+
async function getCloudRelinkRequired(client = getClient()) {
|
|
3894
|
+
try {
|
|
3895
|
+
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
3896
|
+
const relink = await client.execute("SELECT value FROM sync_meta WHERE key = 'cloud_relink_required' LIMIT 1");
|
|
3897
|
+
return String(relink.rows[0]?.value ?? "") === "1";
|
|
3898
|
+
} catch {
|
|
3899
|
+
return false;
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
async function clearCloudRelinkRequired(client = getClient()) {
|
|
3903
|
+
await client.execute("CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
|
|
3904
|
+
await client.execute("INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relink_required', '0')");
|
|
3905
|
+
await client.execute({
|
|
3906
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('cloud_relinked_at', ?)",
|
|
3907
|
+
args: [(/* @__PURE__ */ new Date()).toISOString()]
|
|
3908
|
+
});
|
|
3909
|
+
await client.execute("DELETE FROM sync_meta WHERE key IN ('last_cloud_pull_version', 'last_cloud_push_version')");
|
|
3910
|
+
}
|
|
3825
3911
|
async function cloudSync(config) {
|
|
3826
3912
|
if (!isSyncCryptoInitialized()) {
|
|
3827
3913
|
try {
|
|
@@ -3842,6 +3928,12 @@ async function cloudSync(config) {
|
|
|
3842
3928
|
} catch {
|
|
3843
3929
|
throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
|
|
3844
3930
|
}
|
|
3931
|
+
try {
|
|
3932
|
+
if (await getCloudRelinkRequired(client)) throw new Error(CLOUD_RELINK_REQUIRED_MESSAGE);
|
|
3933
|
+
} catch (err) {
|
|
3934
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3935
|
+
if (msg.includes("Paused after key rotation")) throw err;
|
|
3936
|
+
}
|
|
3845
3937
|
try {
|
|
3846
3938
|
const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
3847
3939
|
await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
@@ -4087,8 +4179,8 @@ async function cloudSync(config) {
|
|
|
4087
4179
|
try {
|
|
4088
4180
|
const employees = await loadEmployees();
|
|
4089
4181
|
rosterResult.employees = employees.length;
|
|
4090
|
-
const idDir =
|
|
4091
|
-
if (
|
|
4182
|
+
const idDir = path11.join(EXE_AI_DIR, "identity");
|
|
4183
|
+
if (existsSync11(idDir)) {
|
|
4092
4184
|
rosterResult.identities = readdirSync2(idDir).filter((f) => f.endsWith(".md")).length;
|
|
4093
4185
|
}
|
|
4094
4186
|
} catch {
|
|
@@ -4101,7 +4193,7 @@ async function cloudSync(config) {
|
|
|
4101
4193
|
const backupSize = statSync3(latestBackup).size;
|
|
4102
4194
|
const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
|
|
4103
4195
|
if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
|
|
4104
|
-
const backupData =
|
|
4196
|
+
const backupData = readFileSync8(latestBackup);
|
|
4105
4197
|
const deviceId = loadDeviceId() ?? "unknown";
|
|
4106
4198
|
const encrypted = encryptSyncBlob(backupData);
|
|
4107
4199
|
const backupRes = await fetchWithRetry(`${config.endpoint}/sync/push-db-backup`, {
|
|
@@ -4109,7 +4201,7 @@ async function cloudSync(config) {
|
|
|
4109
4201
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
|
|
4110
4202
|
body: JSON.stringify({
|
|
4111
4203
|
device_id: deviceId,
|
|
4112
|
-
filename:
|
|
4204
|
+
filename: path11.basename(latestBackup),
|
|
4113
4205
|
blob: encrypted,
|
|
4114
4206
|
size: backupData.length
|
|
4115
4207
|
})
|
|
@@ -4137,56 +4229,56 @@ async function cloudSync(config) {
|
|
|
4137
4229
|
function recordRosterDeletion(name) {
|
|
4138
4230
|
let deletions = [];
|
|
4139
4231
|
try {
|
|
4140
|
-
if (
|
|
4141
|
-
deletions = JSON.parse(
|
|
4232
|
+
if (existsSync11(ROSTER_DELETIONS_PATH)) {
|
|
4233
|
+
deletions = JSON.parse(readFileSync8(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
4142
4234
|
}
|
|
4143
4235
|
} catch {
|
|
4144
4236
|
}
|
|
4145
4237
|
if (!deletions.includes(name)) deletions.push(name);
|
|
4146
|
-
|
|
4238
|
+
writeFileSync6(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
4147
4239
|
}
|
|
4148
4240
|
function consumeRosterDeletions() {
|
|
4149
4241
|
try {
|
|
4150
|
-
if (!
|
|
4151
|
-
const deletions = JSON.parse(
|
|
4152
|
-
|
|
4242
|
+
if (!existsSync11(ROSTER_DELETIONS_PATH)) return [];
|
|
4243
|
+
const deletions = JSON.parse(readFileSync8(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
4244
|
+
writeFileSync6(ROSTER_DELETIONS_PATH, "[]");
|
|
4153
4245
|
return deletions;
|
|
4154
4246
|
} catch {
|
|
4155
4247
|
return [];
|
|
4156
4248
|
}
|
|
4157
4249
|
}
|
|
4158
4250
|
function buildRosterBlob(paths) {
|
|
4159
|
-
const rosterPath = paths?.rosterPath ??
|
|
4160
|
-
const identityDir = paths?.identityDir ??
|
|
4161
|
-
const configPath = paths?.configPath ??
|
|
4251
|
+
const rosterPath = paths?.rosterPath ?? path11.join(EXE_AI_DIR, "exe-employees.json");
|
|
4252
|
+
const identityDir = paths?.identityDir ?? path11.join(EXE_AI_DIR, "identity");
|
|
4253
|
+
const configPath = paths?.configPath ?? path11.join(EXE_AI_DIR, "config.json");
|
|
4162
4254
|
let roster = [];
|
|
4163
|
-
if (
|
|
4255
|
+
if (existsSync11(rosterPath)) {
|
|
4164
4256
|
try {
|
|
4165
|
-
roster = JSON.parse(
|
|
4257
|
+
roster = JSON.parse(readFileSync8(rosterPath, "utf-8"));
|
|
4166
4258
|
} catch {
|
|
4167
4259
|
}
|
|
4168
4260
|
}
|
|
4169
4261
|
const identities = {};
|
|
4170
|
-
if (
|
|
4262
|
+
if (existsSync11(identityDir)) {
|
|
4171
4263
|
for (const file of readdirSync2(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
4172
4264
|
try {
|
|
4173
|
-
identities[file] =
|
|
4265
|
+
identities[file] = readFileSync8(path11.join(identityDir, file), "utf-8");
|
|
4174
4266
|
} catch {
|
|
4175
4267
|
}
|
|
4176
4268
|
}
|
|
4177
4269
|
}
|
|
4178
4270
|
let config;
|
|
4179
|
-
if (
|
|
4271
|
+
if (existsSync11(configPath)) {
|
|
4180
4272
|
try {
|
|
4181
|
-
config = JSON.parse(
|
|
4273
|
+
config = JSON.parse(readFileSync8(configPath, "utf-8"));
|
|
4182
4274
|
} catch {
|
|
4183
4275
|
}
|
|
4184
4276
|
}
|
|
4185
4277
|
let agentConfig;
|
|
4186
|
-
const agentConfigPath =
|
|
4187
|
-
if (
|
|
4278
|
+
const agentConfigPath = path11.join(EXE_AI_DIR, "agent-config.json");
|
|
4279
|
+
if (existsSync11(agentConfigPath)) {
|
|
4188
4280
|
try {
|
|
4189
|
-
agentConfig = JSON.parse(
|
|
4281
|
+
agentConfig = JSON.parse(readFileSync8(agentConfigPath, "utf-8"));
|
|
4190
4282
|
} catch {
|
|
4191
4283
|
}
|
|
4192
4284
|
}
|
|
@@ -4262,24 +4354,24 @@ async function cloudPullRoster(config) {
|
|
|
4262
4354
|
}
|
|
4263
4355
|
}
|
|
4264
4356
|
function mergeConfig(remoteConfig, configPath) {
|
|
4265
|
-
const cfgPath = configPath ??
|
|
4357
|
+
const cfgPath = configPath ?? path11.join(EXE_AI_DIR, "config.json");
|
|
4266
4358
|
let local = {};
|
|
4267
|
-
if (
|
|
4359
|
+
if (existsSync11(cfgPath)) {
|
|
4268
4360
|
try {
|
|
4269
|
-
local = JSON.parse(
|
|
4361
|
+
local = JSON.parse(readFileSync8(cfgPath, "utf-8"));
|
|
4270
4362
|
} catch {
|
|
4271
4363
|
}
|
|
4272
4364
|
}
|
|
4273
4365
|
const merged = { ...remoteConfig, ...local };
|
|
4274
|
-
const dir =
|
|
4366
|
+
const dir = path11.dirname(cfgPath);
|
|
4275
4367
|
ensurePrivateDirSync(dir);
|
|
4276
|
-
|
|
4368
|
+
writeFileSync6(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
4277
4369
|
enforcePrivateFileSync(cfgPath);
|
|
4278
4370
|
}
|
|
4279
4371
|
async function mergeRosterFromRemote(remote, paths) {
|
|
4280
4372
|
return withRosterLock(async () => {
|
|
4281
4373
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
4282
|
-
const identityDir = paths?.identityDir ??
|
|
4374
|
+
const identityDir = paths?.identityDir ?? path11.join(EXE_AI_DIR, "identity");
|
|
4283
4375
|
const localEmployees = await loadEmployees(rosterPath);
|
|
4284
4376
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
4285
4377
|
let added = 0;
|
|
@@ -4300,15 +4392,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
4300
4392
|
) ?? lookupKey;
|
|
4301
4393
|
const remoteIdentity = remote.identities[matchedKey];
|
|
4302
4394
|
if (remoteIdentity) {
|
|
4303
|
-
if (!
|
|
4304
|
-
const idPath =
|
|
4395
|
+
if (!existsSync11(identityDir)) mkdirSync6(identityDir, { recursive: true });
|
|
4396
|
+
const idPath = path11.join(identityDir, `${remoteEmp.name}.md`);
|
|
4305
4397
|
let localIdentity = null;
|
|
4306
4398
|
try {
|
|
4307
|
-
localIdentity =
|
|
4399
|
+
localIdentity = existsSync11(idPath) ? readFileSync8(idPath, "utf-8") : null;
|
|
4308
4400
|
} catch {
|
|
4309
4401
|
}
|
|
4310
4402
|
if (localIdentity !== remoteIdentity) {
|
|
4311
|
-
|
|
4403
|
+
writeFileSync6(idPath, remoteIdentity, "utf-8");
|
|
4312
4404
|
identitiesUpdated++;
|
|
4313
4405
|
}
|
|
4314
4406
|
}
|
|
@@ -4334,17 +4426,17 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
4334
4426
|
}
|
|
4335
4427
|
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
4336
4428
|
try {
|
|
4337
|
-
const agentConfigPath =
|
|
4429
|
+
const agentConfigPath = path11.join(EXE_AI_DIR, "agent-config.json");
|
|
4338
4430
|
let local = {};
|
|
4339
|
-
if (
|
|
4431
|
+
if (existsSync11(agentConfigPath)) {
|
|
4340
4432
|
try {
|
|
4341
|
-
local = JSON.parse(
|
|
4433
|
+
local = JSON.parse(readFileSync8(agentConfigPath, "utf-8"));
|
|
4342
4434
|
} catch {
|
|
4343
4435
|
}
|
|
4344
4436
|
}
|
|
4345
4437
|
const merged = { ...remote.agentConfig, ...local };
|
|
4346
|
-
ensurePrivateDirSync(
|
|
4347
|
-
|
|
4438
|
+
ensurePrivateDirSync(path11.dirname(agentConfigPath));
|
|
4439
|
+
writeFileSync6(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
4348
4440
|
enforcePrivateFileSync(agentConfigPath);
|
|
4349
4441
|
} catch {
|
|
4350
4442
|
}
|
|
@@ -4769,7 +4861,7 @@ async function cloudPullDocuments(config) {
|
|
|
4769
4861
|
}
|
|
4770
4862
|
return { pulled };
|
|
4771
4863
|
}
|
|
4772
|
-
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
|
|
4864
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, CLOUD_RELINK_REQUIRED_MESSAGE, ROSTER_DELETIONS_PATH;
|
|
4773
4865
|
var init_cloud_sync = __esm({
|
|
4774
4866
|
"src/lib/cloud-sync.ts"() {
|
|
4775
4867
|
"use strict";
|
|
@@ -4784,11 +4876,12 @@ var init_cloud_sync = __esm({
|
|
|
4784
4876
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
4785
4877
|
FETCH_TIMEOUT_MS = 3e4;
|
|
4786
4878
|
PUSH_BATCH_SIZE = 5e3;
|
|
4787
|
-
ROSTER_LOCK_PATH =
|
|
4879
|
+
ROSTER_LOCK_PATH = path11.join(EXE_AI_DIR, "roster-merge.lock");
|
|
4788
4880
|
LOCK_STALE_MS = 3e4;
|
|
4789
4881
|
_pgPromise = null;
|
|
4790
4882
|
_pgFailed = false;
|
|
4791
|
-
|
|
4883
|
+
CLOUD_RELINK_REQUIRED_MESSAGE = "[cloud-sync] Paused after key rotation. Run `exe-os cloud relink --dry-run` for the safe relink checklist.";
|
|
4884
|
+
ROSTER_DELETIONS_PATH = path11.join(EXE_AI_DIR, "roster-deletions.json");
|
|
4792
4885
|
}
|
|
4793
4886
|
});
|
|
4794
4887
|
|
|
@@ -4812,74 +4905,113 @@ function isMainModule(importMetaUrl) {
|
|
|
4812
4905
|
}
|
|
4813
4906
|
|
|
4814
4907
|
// src/bin/exe-link.ts
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4908
|
+
function isInteractiveTerminal() {
|
|
4909
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
|
|
4910
|
+
}
|
|
4911
|
+
function assertLocalTerminalExportAllowed() {
|
|
4912
|
+
if (!isInteractiveTerminal()) {
|
|
4913
|
+
throw new Error(
|
|
4914
|
+
"Refusing to reveal the recovery phrase outside a local interactive terminal.\nRun this yourself in Terminal: exe-os link export --local-terminal-only"
|
|
4915
|
+
);
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
async function printStatus() {
|
|
4919
|
+
const key = await getMasterKey();
|
|
4920
|
+
if (key) {
|
|
4921
|
+
console.log("Master key: FOUND");
|
|
4922
|
+
console.log(`Key length: ${key.length} bytes`);
|
|
4923
|
+
console.log("Recovery export: available from a local interactive terminal");
|
|
4924
|
+
} else {
|
|
4925
|
+
console.log("Master key: MISSING");
|
|
4926
|
+
console.log("Checked normal storage paths: OS keychain/keytar/file fallback");
|
|
4927
|
+
process.exitCode = 1;
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
async function exportKey(argv = process.argv.slice(2)) {
|
|
4931
|
+
const key = await getMasterKey();
|
|
4932
|
+
if (!key) {
|
|
4933
|
+
console.error("No master key found. Run /exe-setup first or import your recovery phrase with exe-os link import.");
|
|
4934
|
+
process.exit(1);
|
|
4935
|
+
}
|
|
4936
|
+
const showFull = argv.includes("--show-full") || argv.includes("--local-terminal-only");
|
|
4937
|
+
if (showFull) {
|
|
4938
|
+
assertLocalTerminalExportAllowed();
|
|
4939
|
+
}
|
|
4940
|
+
const mnemonic = await exportMnemonic(key);
|
|
4941
|
+
console.log("Your 24-word recovery phrase:\n");
|
|
4942
|
+
if (showFull) {
|
|
4943
|
+
console.log(` ${mnemonic}
|
|
4828
4944
|
`);
|
|
4829
|
-
}
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4945
|
+
const { markKeyBackupConfirmed: markKeyBackupConfirmed2 } = await Promise.resolve().then(() => (init_key_backup_status(), key_backup_status_exports));
|
|
4946
|
+
markKeyBackupConfirmed2("exe-os link export --local-terminal-only");
|
|
4947
|
+
} else {
|
|
4948
|
+
const words = mnemonic.split(" ");
|
|
4949
|
+
const masked = words.length > 4 ? [...words.slice(0, 2), ...words.slice(2, -2).map(() => "****"), ...words.slice(-2)].join(" ") : mnemonic;
|
|
4950
|
+
console.log(` ${masked}
|
|
4833
4951
|
`);
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4952
|
+
console.log("To reveal full phrase locally: exe-os link export --local-terminal-only\n");
|
|
4953
|
+
}
|
|
4954
|
+
console.log("Write this down and enter it on your new device with /exe-link import.");
|
|
4955
|
+
console.log("Anyone with this phrase can decrypt your memories.");
|
|
4956
|
+
console.log("\u26A0 Clear your terminal history after copying.");
|
|
4957
|
+
}
|
|
4958
|
+
async function importKey() {
|
|
4959
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
4960
|
+
const mnemonic = await new Promise((resolve) => {
|
|
4961
|
+
rl.question("Paste your 24-word recovery phrase: ", (answer) => {
|
|
4962
|
+
rl.close();
|
|
4963
|
+
resolve(answer.trim());
|
|
4846
4964
|
});
|
|
4965
|
+
});
|
|
4966
|
+
try {
|
|
4967
|
+
const key = await importMnemonic(mnemonic);
|
|
4968
|
+
await setMasterKey(key);
|
|
4969
|
+
console.log("Master key imported and stored securely.");
|
|
4847
4970
|
try {
|
|
4848
|
-
const
|
|
4849
|
-
await
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
const
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
initSyncCrypto2(key);
|
|
4858
|
-
const result = await cloudPullRoster2({ apiKey: config.cloud.apiKey, endpoint: config.cloud.endpoint });
|
|
4859
|
-
if (result.added > 0) {
|
|
4860
|
-
console.log(`Pulled ${result.added} employee(s) from Exe Cloud.`);
|
|
4861
|
-
}
|
|
4971
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
4972
|
+
const { initSyncCrypto: initSyncCrypto2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
|
|
4973
|
+
const { cloudPullRoster: cloudPullRoster2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
|
|
4974
|
+
const config = await loadConfig2();
|
|
4975
|
+
if (config.cloud?.apiKey && config.cloud?.endpoint) {
|
|
4976
|
+
initSyncCrypto2(key);
|
|
4977
|
+
const result = await cloudPullRoster2({ apiKey: config.cloud.apiKey, endpoint: config.cloud.endpoint });
|
|
4978
|
+
if (result.added > 0) {
|
|
4979
|
+
console.log(`Pulled ${result.added} employee(s) from Exe Cloud.`);
|
|
4862
4980
|
}
|
|
4863
|
-
} catch {
|
|
4864
4981
|
}
|
|
4865
|
-
|
|
4866
|
-
} catch (err) {
|
|
4867
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
4868
|
-
process.exit(1);
|
|
4982
|
+
} catch {
|
|
4869
4983
|
}
|
|
4984
|
+
console.log("Run /exe-setup to configure sync and download the embedding model.");
|
|
4985
|
+
} catch (err) {
|
|
4986
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
4987
|
+
process.exit(1);
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
async function main(argv = process.argv.slice(2)) {
|
|
4991
|
+
const mode = argv[0];
|
|
4992
|
+
if (mode === "status") {
|
|
4993
|
+
await printStatus();
|
|
4994
|
+
} else if (mode === "export") {
|
|
4995
|
+
await exportKey(argv);
|
|
4996
|
+
} else if (mode === "import") {
|
|
4997
|
+
await importKey();
|
|
4870
4998
|
} else {
|
|
4871
4999
|
console.error("Usage:");
|
|
4872
|
-
console.error(" exe-link
|
|
4873
|
-
console.error(" exe-link
|
|
5000
|
+
console.error(" exe-link status \u2014 verify key availability without printing it");
|
|
5001
|
+
console.error(" exe-link export \u2014 show masked 24-word mnemonic");
|
|
5002
|
+
console.error(" exe-link export --local-terminal-only \u2014 reveal full phrase in local Terminal only");
|
|
5003
|
+
console.error(" exe-link import \u2014 paste mnemonic to import key on new device");
|
|
4874
5004
|
process.exit(1);
|
|
4875
5005
|
}
|
|
4876
5006
|
}
|
|
4877
|
-
if (isMainModule(import.meta.url)) {
|
|
5007
|
+
if (isMainModule(import.meta.url) && process.argv[1]?.includes("exe-link")) {
|
|
4878
5008
|
main().catch((err) => {
|
|
4879
5009
|
console.error(err instanceof Error ? err.message : String(err));
|
|
4880
5010
|
process.exit(1);
|
|
4881
5011
|
});
|
|
4882
5012
|
}
|
|
4883
5013
|
export {
|
|
5014
|
+
assertLocalTerminalExportAllowed,
|
|
5015
|
+
isInteractiveTerminal,
|
|
4884
5016
|
main
|
|
4885
5017
|
};
|