@askexenow/exe-os 0.9.65 → 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 +2263 -1203
- 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 +93 -5
- 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 +287 -176
- 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 +389 -155
- 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 +172 -77
- package/dist/index.js +24 -0
- package/dist/lib/agent-config.js +4 -0
- package/dist/lib/cloud-sync.js +27 -4
- 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 +1058 -453
- 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 +1022 -425
- 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,21 +3656,22 @@ __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 {
|
|
@@ -3626,12 +3682,12 @@ function isTruthyEnv(value) {
|
|
|
3626
3682
|
}
|
|
3627
3683
|
function loadPgClient() {
|
|
3628
3684
|
if (_pgFailed) return null;
|
|
3629
|
-
const configPath =
|
|
3685
|
+
const configPath = path11.join(EXE_AI_DIR, "config.json");
|
|
3630
3686
|
let cloudPostgresUrl;
|
|
3631
3687
|
let configEnabled = false;
|
|
3632
3688
|
try {
|
|
3633
|
-
if (
|
|
3634
|
-
const cfg = JSON.parse(
|
|
3689
|
+
if (existsSync11(configPath)) {
|
|
3690
|
+
const cfg = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
3635
3691
|
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
3636
3692
|
configEnabled = cfg.cloud?.syncToPostgres === true;
|
|
3637
3693
|
}
|
|
@@ -3658,8 +3714,8 @@ function loadPgClient() {
|
|
|
3658
3714
|
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
3659
3715
|
return new Ctor2();
|
|
3660
3716
|
}
|
|
3661
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
3662
|
-
const req = createRequire3(
|
|
3717
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(homedir2(), "exe-db");
|
|
3718
|
+
const req = createRequire3(path11.join(exeDbRoot, "package.json"));
|
|
3663
3719
|
const entry = req.resolve("@prisma/client");
|
|
3664
3720
|
const mod = await import(pathToFileURL3(entry).href);
|
|
3665
3721
|
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
@@ -3704,18 +3760,18 @@ async function withRosterLock(fn) {
|
|
|
3704
3760
|
try {
|
|
3705
3761
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3706
3762
|
closeSync2(fd);
|
|
3707
|
-
|
|
3763
|
+
writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3708
3764
|
} catch (err) {
|
|
3709
3765
|
if (err.code === "EEXIST") {
|
|
3710
3766
|
try {
|
|
3711
|
-
const ts = parseInt(
|
|
3767
|
+
const ts = parseInt(readFileSync8(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
3712
3768
|
if (Date.now() - ts < LOCK_STALE_MS) {
|
|
3713
3769
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
3714
3770
|
}
|
|
3715
3771
|
unlinkSync5(ROSTER_LOCK_PATH);
|
|
3716
3772
|
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
3717
3773
|
closeSync2(fd);
|
|
3718
|
-
|
|
3774
|
+
writeFileSync6(ROSTER_LOCK_PATH, String(Date.now()));
|
|
3719
3775
|
} catch (retryErr) {
|
|
3720
3776
|
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
3721
3777
|
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
@@ -3834,6 +3890,24 @@ async function cloudPull(sinceVersion, config) {
|
|
|
3834
3890
|
return { records: [], maxVersion: sinceVersion };
|
|
3835
3891
|
}
|
|
3836
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
|
+
}
|
|
3837
3911
|
async function cloudSync(config) {
|
|
3838
3912
|
if (!isSyncCryptoInitialized()) {
|
|
3839
3913
|
try {
|
|
@@ -3855,10 +3929,7 @@ async function cloudSync(config) {
|
|
|
3855
3929
|
throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
|
|
3856
3930
|
}
|
|
3857
3931
|
try {
|
|
3858
|
-
|
|
3859
|
-
if (String(relink.rows[0]?.value ?? "") === "1") {
|
|
3860
|
-
throw new Error("[cloud-sync] Paused after key rotation. Re-link/reupload cloud sync with the new recovery phrase before syncing.");
|
|
3861
|
-
}
|
|
3932
|
+
if (await getCloudRelinkRequired(client)) throw new Error(CLOUD_RELINK_REQUIRED_MESSAGE);
|
|
3862
3933
|
} catch (err) {
|
|
3863
3934
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3864
3935
|
if (msg.includes("Paused after key rotation")) throw err;
|
|
@@ -4108,8 +4179,8 @@ async function cloudSync(config) {
|
|
|
4108
4179
|
try {
|
|
4109
4180
|
const employees = await loadEmployees();
|
|
4110
4181
|
rosterResult.employees = employees.length;
|
|
4111
|
-
const idDir =
|
|
4112
|
-
if (
|
|
4182
|
+
const idDir = path11.join(EXE_AI_DIR, "identity");
|
|
4183
|
+
if (existsSync11(idDir)) {
|
|
4113
4184
|
rosterResult.identities = readdirSync2(idDir).filter((f) => f.endsWith(".md")).length;
|
|
4114
4185
|
}
|
|
4115
4186
|
} catch {
|
|
@@ -4122,7 +4193,7 @@ async function cloudSync(config) {
|
|
|
4122
4193
|
const backupSize = statSync3(latestBackup).size;
|
|
4123
4194
|
const MAX_CLOUD_BACKUP_BYTES = 50 * 1024 * 1024;
|
|
4124
4195
|
if (backupSize <= MAX_CLOUD_BACKUP_BYTES) {
|
|
4125
|
-
const backupData =
|
|
4196
|
+
const backupData = readFileSync8(latestBackup);
|
|
4126
4197
|
const deviceId = loadDeviceId() ?? "unknown";
|
|
4127
4198
|
const encrypted = encryptSyncBlob(backupData);
|
|
4128
4199
|
const backupRes = await fetchWithRetry(`${config.endpoint}/sync/push-db-backup`, {
|
|
@@ -4130,7 +4201,7 @@ async function cloudSync(config) {
|
|
|
4130
4201
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
|
|
4131
4202
|
body: JSON.stringify({
|
|
4132
4203
|
device_id: deviceId,
|
|
4133
|
-
filename:
|
|
4204
|
+
filename: path11.basename(latestBackup),
|
|
4134
4205
|
blob: encrypted,
|
|
4135
4206
|
size: backupData.length
|
|
4136
4207
|
})
|
|
@@ -4158,56 +4229,56 @@ async function cloudSync(config) {
|
|
|
4158
4229
|
function recordRosterDeletion(name) {
|
|
4159
4230
|
let deletions = [];
|
|
4160
4231
|
try {
|
|
4161
|
-
if (
|
|
4162
|
-
deletions = JSON.parse(
|
|
4232
|
+
if (existsSync11(ROSTER_DELETIONS_PATH)) {
|
|
4233
|
+
deletions = JSON.parse(readFileSync8(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
4163
4234
|
}
|
|
4164
4235
|
} catch {
|
|
4165
4236
|
}
|
|
4166
4237
|
if (!deletions.includes(name)) deletions.push(name);
|
|
4167
|
-
|
|
4238
|
+
writeFileSync6(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
4168
4239
|
}
|
|
4169
4240
|
function consumeRosterDeletions() {
|
|
4170
4241
|
try {
|
|
4171
|
-
if (!
|
|
4172
|
-
const deletions = JSON.parse(
|
|
4173
|
-
|
|
4242
|
+
if (!existsSync11(ROSTER_DELETIONS_PATH)) return [];
|
|
4243
|
+
const deletions = JSON.parse(readFileSync8(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
4244
|
+
writeFileSync6(ROSTER_DELETIONS_PATH, "[]");
|
|
4174
4245
|
return deletions;
|
|
4175
4246
|
} catch {
|
|
4176
4247
|
return [];
|
|
4177
4248
|
}
|
|
4178
4249
|
}
|
|
4179
4250
|
function buildRosterBlob(paths) {
|
|
4180
|
-
const rosterPath = paths?.rosterPath ??
|
|
4181
|
-
const identityDir = paths?.identityDir ??
|
|
4182
|
-
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");
|
|
4183
4254
|
let roster = [];
|
|
4184
|
-
if (
|
|
4255
|
+
if (existsSync11(rosterPath)) {
|
|
4185
4256
|
try {
|
|
4186
|
-
roster = JSON.parse(
|
|
4257
|
+
roster = JSON.parse(readFileSync8(rosterPath, "utf-8"));
|
|
4187
4258
|
} catch {
|
|
4188
4259
|
}
|
|
4189
4260
|
}
|
|
4190
4261
|
const identities = {};
|
|
4191
|
-
if (
|
|
4262
|
+
if (existsSync11(identityDir)) {
|
|
4192
4263
|
for (const file of readdirSync2(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
4193
4264
|
try {
|
|
4194
|
-
identities[file] =
|
|
4265
|
+
identities[file] = readFileSync8(path11.join(identityDir, file), "utf-8");
|
|
4195
4266
|
} catch {
|
|
4196
4267
|
}
|
|
4197
4268
|
}
|
|
4198
4269
|
}
|
|
4199
4270
|
let config;
|
|
4200
|
-
if (
|
|
4271
|
+
if (existsSync11(configPath)) {
|
|
4201
4272
|
try {
|
|
4202
|
-
config = JSON.parse(
|
|
4273
|
+
config = JSON.parse(readFileSync8(configPath, "utf-8"));
|
|
4203
4274
|
} catch {
|
|
4204
4275
|
}
|
|
4205
4276
|
}
|
|
4206
4277
|
let agentConfig;
|
|
4207
|
-
const agentConfigPath =
|
|
4208
|
-
if (
|
|
4278
|
+
const agentConfigPath = path11.join(EXE_AI_DIR, "agent-config.json");
|
|
4279
|
+
if (existsSync11(agentConfigPath)) {
|
|
4209
4280
|
try {
|
|
4210
|
-
agentConfig = JSON.parse(
|
|
4281
|
+
agentConfig = JSON.parse(readFileSync8(agentConfigPath, "utf-8"));
|
|
4211
4282
|
} catch {
|
|
4212
4283
|
}
|
|
4213
4284
|
}
|
|
@@ -4283,24 +4354,24 @@ async function cloudPullRoster(config) {
|
|
|
4283
4354
|
}
|
|
4284
4355
|
}
|
|
4285
4356
|
function mergeConfig(remoteConfig, configPath) {
|
|
4286
|
-
const cfgPath = configPath ??
|
|
4357
|
+
const cfgPath = configPath ?? path11.join(EXE_AI_DIR, "config.json");
|
|
4287
4358
|
let local = {};
|
|
4288
|
-
if (
|
|
4359
|
+
if (existsSync11(cfgPath)) {
|
|
4289
4360
|
try {
|
|
4290
|
-
local = JSON.parse(
|
|
4361
|
+
local = JSON.parse(readFileSync8(cfgPath, "utf-8"));
|
|
4291
4362
|
} catch {
|
|
4292
4363
|
}
|
|
4293
4364
|
}
|
|
4294
4365
|
const merged = { ...remoteConfig, ...local };
|
|
4295
|
-
const dir =
|
|
4366
|
+
const dir = path11.dirname(cfgPath);
|
|
4296
4367
|
ensurePrivateDirSync(dir);
|
|
4297
|
-
|
|
4368
|
+
writeFileSync6(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
4298
4369
|
enforcePrivateFileSync(cfgPath);
|
|
4299
4370
|
}
|
|
4300
4371
|
async function mergeRosterFromRemote(remote, paths) {
|
|
4301
4372
|
return withRosterLock(async () => {
|
|
4302
4373
|
const rosterPath = paths?.rosterPath ?? void 0;
|
|
4303
|
-
const identityDir = paths?.identityDir ??
|
|
4374
|
+
const identityDir = paths?.identityDir ?? path11.join(EXE_AI_DIR, "identity");
|
|
4304
4375
|
const localEmployees = await loadEmployees(rosterPath);
|
|
4305
4376
|
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
4306
4377
|
let added = 0;
|
|
@@ -4321,15 +4392,15 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
4321
4392
|
) ?? lookupKey;
|
|
4322
4393
|
const remoteIdentity = remote.identities[matchedKey];
|
|
4323
4394
|
if (remoteIdentity) {
|
|
4324
|
-
if (!
|
|
4325
|
-
const idPath =
|
|
4395
|
+
if (!existsSync11(identityDir)) mkdirSync6(identityDir, { recursive: true });
|
|
4396
|
+
const idPath = path11.join(identityDir, `${remoteEmp.name}.md`);
|
|
4326
4397
|
let localIdentity = null;
|
|
4327
4398
|
try {
|
|
4328
|
-
localIdentity =
|
|
4399
|
+
localIdentity = existsSync11(idPath) ? readFileSync8(idPath, "utf-8") : null;
|
|
4329
4400
|
} catch {
|
|
4330
4401
|
}
|
|
4331
4402
|
if (localIdentity !== remoteIdentity) {
|
|
4332
|
-
|
|
4403
|
+
writeFileSync6(idPath, remoteIdentity, "utf-8");
|
|
4333
4404
|
identitiesUpdated++;
|
|
4334
4405
|
}
|
|
4335
4406
|
}
|
|
@@ -4355,17 +4426,17 @@ async function mergeRosterFromRemote(remote, paths) {
|
|
|
4355
4426
|
}
|
|
4356
4427
|
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
4357
4428
|
try {
|
|
4358
|
-
const agentConfigPath =
|
|
4429
|
+
const agentConfigPath = path11.join(EXE_AI_DIR, "agent-config.json");
|
|
4359
4430
|
let local = {};
|
|
4360
|
-
if (
|
|
4431
|
+
if (existsSync11(agentConfigPath)) {
|
|
4361
4432
|
try {
|
|
4362
|
-
local = JSON.parse(
|
|
4433
|
+
local = JSON.parse(readFileSync8(agentConfigPath, "utf-8"));
|
|
4363
4434
|
} catch {
|
|
4364
4435
|
}
|
|
4365
4436
|
}
|
|
4366
4437
|
const merged = { ...remote.agentConfig, ...local };
|
|
4367
|
-
ensurePrivateDirSync(
|
|
4368
|
-
|
|
4438
|
+
ensurePrivateDirSync(path11.dirname(agentConfigPath));
|
|
4439
|
+
writeFileSync6(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
4369
4440
|
enforcePrivateFileSync(agentConfigPath);
|
|
4370
4441
|
} catch {
|
|
4371
4442
|
}
|
|
@@ -4790,7 +4861,7 @@ async function cloudPullDocuments(config) {
|
|
|
4790
4861
|
}
|
|
4791
4862
|
return { pulled };
|
|
4792
4863
|
}
|
|
4793
|
-
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;
|
|
4794
4865
|
var init_cloud_sync = __esm({
|
|
4795
4866
|
"src/lib/cloud-sync.ts"() {
|
|
4796
4867
|
"use strict";
|
|
@@ -4805,11 +4876,12 @@ var init_cloud_sync = __esm({
|
|
|
4805
4876
|
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
4806
4877
|
FETCH_TIMEOUT_MS = 3e4;
|
|
4807
4878
|
PUSH_BATCH_SIZE = 5e3;
|
|
4808
|
-
ROSTER_LOCK_PATH =
|
|
4879
|
+
ROSTER_LOCK_PATH = path11.join(EXE_AI_DIR, "roster-merge.lock");
|
|
4809
4880
|
LOCK_STALE_MS = 3e4;
|
|
4810
4881
|
_pgPromise = null;
|
|
4811
4882
|
_pgFailed = false;
|
|
4812
|
-
|
|
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");
|
|
4813
4885
|
}
|
|
4814
4886
|
});
|
|
4815
4887
|
|
|
@@ -4833,74 +4905,113 @@ function isMainModule(importMetaUrl) {
|
|
|
4833
4905
|
}
|
|
4834
4906
|
|
|
4835
4907
|
// src/bin/exe-link.ts
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
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}
|
|
4849
4944
|
`);
|
|
4850
|
-
}
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
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}
|
|
4854
4951
|
`);
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
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());
|
|
4867
4964
|
});
|
|
4965
|
+
});
|
|
4966
|
+
try {
|
|
4967
|
+
const key = await importMnemonic(mnemonic);
|
|
4968
|
+
await setMasterKey(key);
|
|
4969
|
+
console.log("Master key imported and stored securely.");
|
|
4868
4970
|
try {
|
|
4869
|
-
const
|
|
4870
|
-
await
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
const
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
initSyncCrypto2(key);
|
|
4879
|
-
const result = await cloudPullRoster2({ apiKey: config.cloud.apiKey, endpoint: config.cloud.endpoint });
|
|
4880
|
-
if (result.added > 0) {
|
|
4881
|
-
console.log(`Pulled ${result.added} employee(s) from Exe Cloud.`);
|
|
4882
|
-
}
|
|
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.`);
|
|
4883
4980
|
}
|
|
4884
|
-
} catch {
|
|
4885
4981
|
}
|
|
4886
|
-
|
|
4887
|
-
} catch (err) {
|
|
4888
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
4889
|
-
process.exit(1);
|
|
4982
|
+
} catch {
|
|
4890
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();
|
|
4891
4998
|
} else {
|
|
4892
4999
|
console.error("Usage:");
|
|
4893
|
-
console.error(" exe-link
|
|
4894
|
-
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");
|
|
4895
5004
|
process.exit(1);
|
|
4896
5005
|
}
|
|
4897
5006
|
}
|
|
4898
|
-
if (isMainModule(import.meta.url)) {
|
|
5007
|
+
if (isMainModule(import.meta.url) && process.argv[1]?.includes("exe-link")) {
|
|
4899
5008
|
main().catch((err) => {
|
|
4900
5009
|
console.error(err instanceof Error ? err.message : String(err));
|
|
4901
5010
|
process.exit(1);
|
|
4902
5011
|
});
|
|
4903
5012
|
}
|
|
4904
5013
|
export {
|
|
5014
|
+
assertLocalTerminalExportAllowed,
|
|
5015
|
+
isInteractiveTerminal,
|
|
4905
5016
|
main
|
|
4906
5017
|
};
|