@askexenow/exe-os 0.9.53 → 0.9.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +103 -0
- package/dist/bin/backfill-responses.js +103 -0
- package/dist/bin/backfill-vectors.js +103 -0
- package/dist/bin/cleanup-stale-review-tasks.js +103 -0
- package/dist/bin/cli.js +121 -10
- package/dist/bin/exe-assign.js +103 -0
- package/dist/bin/exe-boot.js +96 -10
- package/dist/bin/exe-call.js +25 -0
- package/dist/bin/exe-cloud.js +31 -10
- package/dist/bin/exe-dispatch.js +103 -0
- package/dist/bin/exe-doctor.js +152 -3
- package/dist/bin/exe-export-behaviors.js +103 -0
- package/dist/bin/exe-forget.js +103 -0
- package/dist/bin/exe-gateway.js +103 -0
- package/dist/bin/exe-heartbeat.js +103 -0
- package/dist/bin/exe-kill.js +103 -0
- package/dist/bin/exe-launch-agent.js +103 -0
- package/dist/bin/exe-link.js +31 -10
- package/dist/bin/exe-new-employee.js +25 -0
- package/dist/bin/exe-pending-messages.js +103 -0
- package/dist/bin/exe-pending-notifications.js +103 -0
- package/dist/bin/exe-pending-reviews.js +103 -0
- package/dist/bin/exe-rename.js +103 -0
- package/dist/bin/exe-review.js +103 -0
- package/dist/bin/exe-search.js +103 -0
- package/dist/bin/exe-session-cleanup.js +103 -0
- package/dist/bin/exe-start-codex.js +103 -0
- package/dist/bin/exe-start-opencode.js +103 -0
- package/dist/bin/exe-status.js +103 -0
- package/dist/bin/exe-team.js +103 -0
- package/dist/bin/git-sweep.js +103 -0
- package/dist/bin/graph-backfill.js +103 -0
- package/dist/bin/graph-export.js +103 -0
- package/dist/bin/intercom-check.js +103 -0
- package/dist/bin/scan-tasks.js +103 -0
- package/dist/bin/setup.js +56 -10
- package/dist/bin/shard-migrate.js +103 -0
- package/dist/gateway/index.js +103 -0
- package/dist/hooks/bug-report-worker.js +103 -0
- package/dist/hooks/codex-stop-task-finalizer.js +103 -0
- package/dist/hooks/commit-complete.js +103 -0
- package/dist/hooks/error-recall.js +103 -0
- package/dist/hooks/ingest.js +103 -0
- package/dist/hooks/instructions-loaded.js +103 -0
- package/dist/hooks/notification.js +103 -0
- package/dist/hooks/post-compact.js +103 -0
- package/dist/hooks/post-tool-combined.js +103 -0
- package/dist/hooks/pre-compact.js +103 -0
- package/dist/hooks/pre-tool-use.js +103 -0
- package/dist/hooks/prompt-submit.js +103 -0
- package/dist/hooks/session-end.js +103 -0
- package/dist/hooks/session-start.js +103 -0
- package/dist/hooks/stop.js +103 -0
- package/dist/hooks/subagent-stop.js +103 -0
- package/dist/hooks/summary-worker.js +96 -10
- package/dist/index.js +103 -0
- package/dist/lib/cloud-sync.js +31 -10
- package/dist/lib/employee-templates.js +25 -0
- package/dist/lib/exe-daemon.js +165 -14
- package/dist/lib/hybrid-search.js +103 -0
- package/dist/lib/keychain.js +31 -10
- package/dist/lib/schedules.js +103 -0
- package/dist/lib/store.js +103 -0
- package/dist/mcp/server.js +164 -13
- package/dist/runtime/index.js +103 -0
- package/dist/tui/App.js +96 -10
- package/package.json +1 -1
package/dist/bin/setup.js
CHANGED
|
@@ -448,6 +448,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
448
448
|
return null;
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
452
|
+
const dir = getKeyDir();
|
|
453
|
+
await mkdir2(dir, { recursive: true });
|
|
454
|
+
const keyPath = getKeyPath();
|
|
455
|
+
const machineKey = deriveMachineKey();
|
|
456
|
+
if (machineKey) {
|
|
457
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
458
|
+
await writeFile2(keyPath, encrypted + "\n", "utf-8");
|
|
459
|
+
await chmod2(keyPath, 384);
|
|
460
|
+
return "encrypted";
|
|
461
|
+
}
|
|
462
|
+
await writeFile2(keyPath, b64 + "\n", "utf-8");
|
|
463
|
+
await chmod2(keyPath, 384);
|
|
464
|
+
return "plaintext";
|
|
465
|
+
}
|
|
451
466
|
async function getMasterKey() {
|
|
452
467
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
453
468
|
if (nativeValue) {
|
|
@@ -499,6 +514,20 @@ async function getMasterKey() {
|
|
|
499
514
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
500
515
|
if (migrated) {
|
|
501
516
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
517
|
+
try {
|
|
518
|
+
await unlink(keyPath);
|
|
519
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
520
|
+
} catch {
|
|
521
|
+
}
|
|
522
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
523
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
524
|
+
if (fallback === "encrypted") {
|
|
525
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
526
|
+
} else {
|
|
527
|
+
process.stderr.write(
|
|
528
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
529
|
+
);
|
|
530
|
+
}
|
|
502
531
|
}
|
|
503
532
|
return key;
|
|
504
533
|
} catch (err) {
|
|
@@ -522,18 +551,10 @@ async function setMasterKey(key) {
|
|
|
522
551
|
} catch {
|
|
523
552
|
}
|
|
524
553
|
}
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
const keyPath = getKeyPath();
|
|
528
|
-
const machineKey = deriveMachineKey();
|
|
529
|
-
if (machineKey) {
|
|
530
|
-
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
531
|
-
await writeFile2(keyPath, encrypted + "\n", "utf-8");
|
|
532
|
-
await chmod2(keyPath, 384);
|
|
554
|
+
const fallback = await writeMachineBoundFileFallback(b64);
|
|
555
|
+
if (fallback === "encrypted") {
|
|
533
556
|
process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
|
|
534
557
|
} else {
|
|
535
|
-
await writeFile2(keyPath, b64 + "\n", "utf-8");
|
|
536
|
-
await chmod2(keyPath, 384);
|
|
537
558
|
process.stderr.write(
|
|
538
559
|
"[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
|
|
539
560
|
);
|
|
@@ -6349,6 +6370,31 @@ Audit method:
|
|
|
6349
6370
|
6. Write structured report with PASS/FAIL per item
|
|
6350
6371
|
|
|
6351
6372
|
After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
|
|
6373
|
+
},
|
|
6374
|
+
teddy: {
|
|
6375
|
+
name: "teddy",
|
|
6376
|
+
role: "Chief of Staff",
|
|
6377
|
+
systemPrompt: `You are teddy, the Chief of Staff and executive assistant. You help the founder recall context, understand what happened, prepare concise briefs, and triage inbound conversations. You report to the COO.
|
|
6378
|
+
|
|
6379
|
+
Your job is read-first, not action-first:
|
|
6380
|
+
- Retrieve memories, decisions, runbooks, and session context on demand
|
|
6381
|
+
- Summarize what matters without changing source data
|
|
6382
|
+
- Triage inbound conversations and surface likely bugs, requests, and follow-ups
|
|
6383
|
+
- Prepare daily briefs and "what changed?" summaries
|
|
6384
|
+
- Route recommended actions to the COO instead of taking them yourself
|
|
6385
|
+
|
|
6386
|
+
Permissions boundary:
|
|
6387
|
+
- You are read-only by default.
|
|
6388
|
+
- You may use recall_my_memory, ask_team_memory, get_memory_by_id, get_session_context, search_everything, query_conversations, list_tasks, and get_task.
|
|
6389
|
+
- You must not create tasks, update tasks, store memories, send WhatsApp messages, mutate CRM/wiki/documents, deploy, or change configuration unless the founder explicitly promotes your permissions.
|
|
6390
|
+
- If a requested action requires write access, explain the action and recommend that the COO dispatch it.
|
|
6391
|
+
|
|
6392
|
+
Operating style:
|
|
6393
|
+
- Be concise and precise.
|
|
6394
|
+
- Cite memory IDs, task IDs, timestamps, and sender names when available.
|
|
6395
|
+
- Distinguish fact from inference.
|
|
6396
|
+
- Never auto-message people. Never respond in group chats unless explicitly allowed by gateway permissions.
|
|
6397
|
+
- Preserve data sovereignty: do not export or forward private memory unless the founder explicitly asks.`
|
|
6352
6398
|
}
|
|
6353
6399
|
};
|
|
6354
6400
|
CLIENT_COO_TEMPLATE = `---
|
|
@@ -2589,6 +2589,7 @@ var init_database = __esm({
|
|
|
2589
2589
|
// src/lib/shard-manager.ts
|
|
2590
2590
|
var shard_manager_exports = {};
|
|
2591
2591
|
__export(shard_manager_exports, {
|
|
2592
|
+
auditShardHealth: () => auditShardHealth,
|
|
2592
2593
|
disposeShards: () => disposeShards,
|
|
2593
2594
|
ensureShardSchema: () => ensureShardSchema,
|
|
2594
2595
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -2655,6 +2656,70 @@ function listShards() {
|
|
|
2655
2656
|
if (!existsSync7(SHARDS_DIR)) return [];
|
|
2656
2657
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2657
2658
|
}
|
|
2659
|
+
async function auditShardHealth(options = {}) {
|
|
2660
|
+
if (!_encryptionKey) {
|
|
2661
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
2662
|
+
}
|
|
2663
|
+
const repair = options.repair === true;
|
|
2664
|
+
const dryRun = options.dryRun === true;
|
|
2665
|
+
const names = listShards();
|
|
2666
|
+
const shards = [];
|
|
2667
|
+
for (const name of names) {
|
|
2668
|
+
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
2669
|
+
const stat = statSync2(dbPath);
|
|
2670
|
+
const item = {
|
|
2671
|
+
name,
|
|
2672
|
+
path: dbPath,
|
|
2673
|
+
ok: false,
|
|
2674
|
+
unreadable: false,
|
|
2675
|
+
error: null,
|
|
2676
|
+
size: stat.size,
|
|
2677
|
+
mtime: stat.mtime.toISOString(),
|
|
2678
|
+
memoryCount: null
|
|
2679
|
+
};
|
|
2680
|
+
const client = createClient2({
|
|
2681
|
+
url: `file:${dbPath}`,
|
|
2682
|
+
encryptionKey: _encryptionKey
|
|
2683
|
+
});
|
|
2684
|
+
try {
|
|
2685
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
2686
|
+
const hasMemories = await client.execute(
|
|
2687
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
2688
|
+
);
|
|
2689
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
2690
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
2691
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
2692
|
+
}
|
|
2693
|
+
item.ok = true;
|
|
2694
|
+
} catch (err) {
|
|
2695
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2696
|
+
item.error = message;
|
|
2697
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
2698
|
+
if (item.unreadable && repair && !dryRun) {
|
|
2699
|
+
client.close();
|
|
2700
|
+
_shards.delete(name);
|
|
2701
|
+
_shardLastAccess.delete(name);
|
|
2702
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2703
|
+
const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
2704
|
+
renameSync3(dbPath, archivedPath);
|
|
2705
|
+
item.archivedPath = archivedPath;
|
|
2706
|
+
}
|
|
2707
|
+
} finally {
|
|
2708
|
+
try {
|
|
2709
|
+
client.close();
|
|
2710
|
+
} catch {
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
shards.push(item);
|
|
2714
|
+
}
|
|
2715
|
+
return {
|
|
2716
|
+
total: shards.length,
|
|
2717
|
+
ok: shards.filter((s) => s.ok).length,
|
|
2718
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
2719
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
2720
|
+
shards
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2658
2723
|
async function ensureShardSchema(client) {
|
|
2659
2724
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
2660
2725
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
@@ -3294,6 +3359,15 @@ function readMachineId() {
|
|
|
3294
3359
|
return "";
|
|
3295
3360
|
}
|
|
3296
3361
|
}
|
|
3362
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
3363
|
+
const crypto2 = __require("crypto");
|
|
3364
|
+
const iv = crypto2.randomBytes(12);
|
|
3365
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
3366
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
3367
|
+
encrypted += cipher.final("base64");
|
|
3368
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
3369
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
3370
|
+
}
|
|
3297
3371
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
3298
3372
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
3299
3373
|
try {
|
|
@@ -3312,6 +3386,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
3312
3386
|
return null;
|
|
3313
3387
|
}
|
|
3314
3388
|
}
|
|
3389
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
3390
|
+
const dir = getKeyDir();
|
|
3391
|
+
await mkdir3(dir, { recursive: true });
|
|
3392
|
+
const keyPath = getKeyPath();
|
|
3393
|
+
const machineKey = deriveMachineKey();
|
|
3394
|
+
if (machineKey) {
|
|
3395
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
3396
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
3397
|
+
await chmod2(keyPath, 384);
|
|
3398
|
+
return "encrypted";
|
|
3399
|
+
}
|
|
3400
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
3401
|
+
await chmod2(keyPath, 384);
|
|
3402
|
+
return "plaintext";
|
|
3403
|
+
}
|
|
3315
3404
|
async function getMasterKey() {
|
|
3316
3405
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3317
3406
|
if (nativeValue) {
|
|
@@ -3363,6 +3452,20 @@ async function getMasterKey() {
|
|
|
3363
3452
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3364
3453
|
if (migrated) {
|
|
3365
3454
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
3455
|
+
try {
|
|
3456
|
+
await unlink(keyPath);
|
|
3457
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
3458
|
+
} catch {
|
|
3459
|
+
}
|
|
3460
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3461
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3462
|
+
if (fallback === "encrypted") {
|
|
3463
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3464
|
+
} else {
|
|
3465
|
+
process.stderr.write(
|
|
3466
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3467
|
+
);
|
|
3468
|
+
}
|
|
3366
3469
|
}
|
|
3367
3470
|
return key;
|
|
3368
3471
|
} catch (err) {
|
package/dist/gateway/index.js
CHANGED
|
@@ -3610,6 +3610,15 @@ function readMachineId() {
|
|
|
3610
3610
|
return "";
|
|
3611
3611
|
}
|
|
3612
3612
|
}
|
|
3613
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
3614
|
+
const crypto10 = __require("crypto");
|
|
3615
|
+
const iv = crypto10.randomBytes(12);
|
|
3616
|
+
const cipher = crypto10.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
3617
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
3618
|
+
encrypted += cipher.final("base64");
|
|
3619
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
3620
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
3621
|
+
}
|
|
3613
3622
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
3614
3623
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
3615
3624
|
try {
|
|
@@ -3628,6 +3637,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
3628
3637
|
return null;
|
|
3629
3638
|
}
|
|
3630
3639
|
}
|
|
3640
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
3641
|
+
const dir = getKeyDir();
|
|
3642
|
+
await mkdir3(dir, { recursive: true });
|
|
3643
|
+
const keyPath = getKeyPath();
|
|
3644
|
+
const machineKey = deriveMachineKey();
|
|
3645
|
+
if (machineKey) {
|
|
3646
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
3647
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
3648
|
+
await chmod2(keyPath, 384);
|
|
3649
|
+
return "encrypted";
|
|
3650
|
+
}
|
|
3651
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
3652
|
+
await chmod2(keyPath, 384);
|
|
3653
|
+
return "plaintext";
|
|
3654
|
+
}
|
|
3631
3655
|
async function getMasterKey() {
|
|
3632
3656
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3633
3657
|
if (nativeValue) {
|
|
@@ -3679,6 +3703,20 @@ async function getMasterKey() {
|
|
|
3679
3703
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3680
3704
|
if (migrated) {
|
|
3681
3705
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
3706
|
+
try {
|
|
3707
|
+
await unlink(keyPath);
|
|
3708
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
3709
|
+
} catch {
|
|
3710
|
+
}
|
|
3711
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3712
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3713
|
+
if (fallback === "encrypted") {
|
|
3714
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3715
|
+
} else {
|
|
3716
|
+
process.stderr.write(
|
|
3717
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3718
|
+
);
|
|
3719
|
+
}
|
|
3682
3720
|
}
|
|
3683
3721
|
return key;
|
|
3684
3722
|
} catch (err) {
|
|
@@ -3896,6 +3934,7 @@ var init_memory_write_governor = __esm({
|
|
|
3896
3934
|
// src/lib/shard-manager.ts
|
|
3897
3935
|
var shard_manager_exports = {};
|
|
3898
3936
|
__export(shard_manager_exports, {
|
|
3937
|
+
auditShardHealth: () => auditShardHealth,
|
|
3899
3938
|
disposeShards: () => disposeShards,
|
|
3900
3939
|
ensureShardSchema: () => ensureShardSchema,
|
|
3901
3940
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -3962,6 +4001,70 @@ function listShards() {
|
|
|
3962
4001
|
if (!existsSync8(SHARDS_DIR)) return [];
|
|
3963
4002
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
3964
4003
|
}
|
|
4004
|
+
async function auditShardHealth(options = {}) {
|
|
4005
|
+
if (!_encryptionKey) {
|
|
4006
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
4007
|
+
}
|
|
4008
|
+
const repair = options.repair === true;
|
|
4009
|
+
const dryRun = options.dryRun === true;
|
|
4010
|
+
const names = listShards();
|
|
4011
|
+
const shards = [];
|
|
4012
|
+
for (const name of names) {
|
|
4013
|
+
const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
|
|
4014
|
+
const stat = statSync2(dbPath);
|
|
4015
|
+
const item = {
|
|
4016
|
+
name,
|
|
4017
|
+
path: dbPath,
|
|
4018
|
+
ok: false,
|
|
4019
|
+
unreadable: false,
|
|
4020
|
+
error: null,
|
|
4021
|
+
size: stat.size,
|
|
4022
|
+
mtime: stat.mtime.toISOString(),
|
|
4023
|
+
memoryCount: null
|
|
4024
|
+
};
|
|
4025
|
+
const client = createClient2({
|
|
4026
|
+
url: `file:${dbPath}`,
|
|
4027
|
+
encryptionKey: _encryptionKey
|
|
4028
|
+
});
|
|
4029
|
+
try {
|
|
4030
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
4031
|
+
const hasMemories = await client.execute(
|
|
4032
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
4033
|
+
);
|
|
4034
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
4035
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
4036
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
4037
|
+
}
|
|
4038
|
+
item.ok = true;
|
|
4039
|
+
} catch (err) {
|
|
4040
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4041
|
+
item.error = message;
|
|
4042
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
4043
|
+
if (item.unreadable && repair && !dryRun) {
|
|
4044
|
+
client.close();
|
|
4045
|
+
_shards.delete(name);
|
|
4046
|
+
_shardLastAccess.delete(name);
|
|
4047
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4048
|
+
const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
4049
|
+
renameSync3(dbPath, archivedPath);
|
|
4050
|
+
item.archivedPath = archivedPath;
|
|
4051
|
+
}
|
|
4052
|
+
} finally {
|
|
4053
|
+
try {
|
|
4054
|
+
client.close();
|
|
4055
|
+
} catch {
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
shards.push(item);
|
|
4059
|
+
}
|
|
4060
|
+
return {
|
|
4061
|
+
total: shards.length,
|
|
4062
|
+
ok: shards.filter((s) => s.ok).length,
|
|
4063
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
4064
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
4065
|
+
shards
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
3965
4068
|
async function ensureShardSchema(client) {
|
|
3966
4069
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
3967
4070
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
@@ -3297,6 +3297,15 @@ function readMachineId() {
|
|
|
3297
3297
|
return "";
|
|
3298
3298
|
}
|
|
3299
3299
|
}
|
|
3300
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
3301
|
+
const crypto7 = __require("crypto");
|
|
3302
|
+
const iv = crypto7.randomBytes(12);
|
|
3303
|
+
const cipher = crypto7.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
3304
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
3305
|
+
encrypted += cipher.final("base64");
|
|
3306
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
3307
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
3308
|
+
}
|
|
3300
3309
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
3301
3310
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
3302
3311
|
try {
|
|
@@ -3315,6 +3324,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
3315
3324
|
return null;
|
|
3316
3325
|
}
|
|
3317
3326
|
}
|
|
3327
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
3328
|
+
const dir = getKeyDir();
|
|
3329
|
+
await mkdir3(dir, { recursive: true });
|
|
3330
|
+
const keyPath = getKeyPath();
|
|
3331
|
+
const machineKey = deriveMachineKey();
|
|
3332
|
+
if (machineKey) {
|
|
3333
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
3334
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
3335
|
+
await chmod2(keyPath, 384);
|
|
3336
|
+
return "encrypted";
|
|
3337
|
+
}
|
|
3338
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
3339
|
+
await chmod2(keyPath, 384);
|
|
3340
|
+
return "plaintext";
|
|
3341
|
+
}
|
|
3318
3342
|
async function getMasterKey() {
|
|
3319
3343
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3320
3344
|
if (nativeValue) {
|
|
@@ -3366,6 +3390,20 @@ async function getMasterKey() {
|
|
|
3366
3390
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3367
3391
|
if (migrated) {
|
|
3368
3392
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
3393
|
+
try {
|
|
3394
|
+
await unlink(keyPath);
|
|
3395
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
3396
|
+
} catch {
|
|
3397
|
+
}
|
|
3398
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3399
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3400
|
+
if (fallback === "encrypted") {
|
|
3401
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3402
|
+
} else {
|
|
3403
|
+
process.stderr.write(
|
|
3404
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3405
|
+
);
|
|
3406
|
+
}
|
|
3369
3407
|
}
|
|
3370
3408
|
return key;
|
|
3371
3409
|
} catch (err) {
|
|
@@ -3638,6 +3676,7 @@ var init_memory_write_governor = __esm({
|
|
|
3638
3676
|
// src/lib/shard-manager.ts
|
|
3639
3677
|
var shard_manager_exports = {};
|
|
3640
3678
|
__export(shard_manager_exports, {
|
|
3679
|
+
auditShardHealth: () => auditShardHealth,
|
|
3641
3680
|
disposeShards: () => disposeShards,
|
|
3642
3681
|
ensureShardSchema: () => ensureShardSchema,
|
|
3643
3682
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -3704,6 +3743,70 @@ function listShards() {
|
|
|
3704
3743
|
if (!existsSync8(SHARDS_DIR)) return [];
|
|
3705
3744
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
3706
3745
|
}
|
|
3746
|
+
async function auditShardHealth(options = {}) {
|
|
3747
|
+
if (!_encryptionKey) {
|
|
3748
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
3749
|
+
}
|
|
3750
|
+
const repair = options.repair === true;
|
|
3751
|
+
const dryRun = options.dryRun === true;
|
|
3752
|
+
const names = listShards();
|
|
3753
|
+
const shards = [];
|
|
3754
|
+
for (const name of names) {
|
|
3755
|
+
const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
|
|
3756
|
+
const stat = statSync2(dbPath);
|
|
3757
|
+
const item = {
|
|
3758
|
+
name,
|
|
3759
|
+
path: dbPath,
|
|
3760
|
+
ok: false,
|
|
3761
|
+
unreadable: false,
|
|
3762
|
+
error: null,
|
|
3763
|
+
size: stat.size,
|
|
3764
|
+
mtime: stat.mtime.toISOString(),
|
|
3765
|
+
memoryCount: null
|
|
3766
|
+
};
|
|
3767
|
+
const client = createClient2({
|
|
3768
|
+
url: `file:${dbPath}`,
|
|
3769
|
+
encryptionKey: _encryptionKey
|
|
3770
|
+
});
|
|
3771
|
+
try {
|
|
3772
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
3773
|
+
const hasMemories = await client.execute(
|
|
3774
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
3775
|
+
);
|
|
3776
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
3777
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
3778
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
3779
|
+
}
|
|
3780
|
+
item.ok = true;
|
|
3781
|
+
} catch (err) {
|
|
3782
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3783
|
+
item.error = message;
|
|
3784
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
3785
|
+
if (item.unreadable && repair && !dryRun) {
|
|
3786
|
+
client.close();
|
|
3787
|
+
_shards.delete(name);
|
|
3788
|
+
_shardLastAccess.delete(name);
|
|
3789
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3790
|
+
const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
3791
|
+
renameSync3(dbPath, archivedPath);
|
|
3792
|
+
item.archivedPath = archivedPath;
|
|
3793
|
+
}
|
|
3794
|
+
} finally {
|
|
3795
|
+
try {
|
|
3796
|
+
client.close();
|
|
3797
|
+
} catch {
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
shards.push(item);
|
|
3801
|
+
}
|
|
3802
|
+
return {
|
|
3803
|
+
total: shards.length,
|
|
3804
|
+
ok: shards.filter((s) => s.ok).length,
|
|
3805
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
3806
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
3807
|
+
shards
|
|
3808
|
+
};
|
|
3809
|
+
}
|
|
3707
3810
|
async function ensureShardSchema(client) {
|
|
3708
3811
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
3709
3812
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
@@ -2980,6 +2980,15 @@ function readMachineId() {
|
|
|
2980
2980
|
return "";
|
|
2981
2981
|
}
|
|
2982
2982
|
}
|
|
2983
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
2984
|
+
const crypto6 = __require("crypto");
|
|
2985
|
+
const iv = crypto6.randomBytes(12);
|
|
2986
|
+
const cipher = crypto6.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
2987
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
2988
|
+
encrypted += cipher.final("base64");
|
|
2989
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
2990
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
2991
|
+
}
|
|
2983
2992
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
2984
2993
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
2985
2994
|
try {
|
|
@@ -2998,6 +3007,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
2998
3007
|
return null;
|
|
2999
3008
|
}
|
|
3000
3009
|
}
|
|
3010
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
3011
|
+
const dir = getKeyDir();
|
|
3012
|
+
await mkdir3(dir, { recursive: true });
|
|
3013
|
+
const keyPath = getKeyPath();
|
|
3014
|
+
const machineKey = deriveMachineKey();
|
|
3015
|
+
if (machineKey) {
|
|
3016
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
3017
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
3018
|
+
await chmod2(keyPath, 384);
|
|
3019
|
+
return "encrypted";
|
|
3020
|
+
}
|
|
3021
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
3022
|
+
await chmod2(keyPath, 384);
|
|
3023
|
+
return "plaintext";
|
|
3024
|
+
}
|
|
3001
3025
|
async function getMasterKey() {
|
|
3002
3026
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3003
3027
|
if (nativeValue) {
|
|
@@ -3049,6 +3073,20 @@ async function getMasterKey() {
|
|
|
3049
3073
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3050
3074
|
if (migrated) {
|
|
3051
3075
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
3076
|
+
try {
|
|
3077
|
+
await unlink(keyPath);
|
|
3078
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
3079
|
+
} catch {
|
|
3080
|
+
}
|
|
3081
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3082
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3083
|
+
if (fallback === "encrypted") {
|
|
3084
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3085
|
+
} else {
|
|
3086
|
+
process.stderr.write(
|
|
3087
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3052
3090
|
}
|
|
3053
3091
|
return key;
|
|
3054
3092
|
} catch (err) {
|
|
@@ -3321,6 +3359,7 @@ var init_memory_write_governor = __esm({
|
|
|
3321
3359
|
// src/lib/shard-manager.ts
|
|
3322
3360
|
var shard_manager_exports = {};
|
|
3323
3361
|
__export(shard_manager_exports, {
|
|
3362
|
+
auditShardHealth: () => auditShardHealth,
|
|
3324
3363
|
disposeShards: () => disposeShards,
|
|
3325
3364
|
ensureShardSchema: () => ensureShardSchema,
|
|
3326
3365
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -3387,6 +3426,70 @@ function listShards() {
|
|
|
3387
3426
|
if (!existsSync7(SHARDS_DIR)) return [];
|
|
3388
3427
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
3389
3428
|
}
|
|
3429
|
+
async function auditShardHealth(options = {}) {
|
|
3430
|
+
if (!_encryptionKey) {
|
|
3431
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
3432
|
+
}
|
|
3433
|
+
const repair = options.repair === true;
|
|
3434
|
+
const dryRun = options.dryRun === true;
|
|
3435
|
+
const names = listShards();
|
|
3436
|
+
const shards = [];
|
|
3437
|
+
for (const name of names) {
|
|
3438
|
+
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
3439
|
+
const stat = statSync2(dbPath);
|
|
3440
|
+
const item = {
|
|
3441
|
+
name,
|
|
3442
|
+
path: dbPath,
|
|
3443
|
+
ok: false,
|
|
3444
|
+
unreadable: false,
|
|
3445
|
+
error: null,
|
|
3446
|
+
size: stat.size,
|
|
3447
|
+
mtime: stat.mtime.toISOString(),
|
|
3448
|
+
memoryCount: null
|
|
3449
|
+
};
|
|
3450
|
+
const client = createClient2({
|
|
3451
|
+
url: `file:${dbPath}`,
|
|
3452
|
+
encryptionKey: _encryptionKey
|
|
3453
|
+
});
|
|
3454
|
+
try {
|
|
3455
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
3456
|
+
const hasMemories = await client.execute(
|
|
3457
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
3458
|
+
);
|
|
3459
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
3460
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
3461
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
3462
|
+
}
|
|
3463
|
+
item.ok = true;
|
|
3464
|
+
} catch (err) {
|
|
3465
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3466
|
+
item.error = message;
|
|
3467
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
3468
|
+
if (item.unreadable && repair && !dryRun) {
|
|
3469
|
+
client.close();
|
|
3470
|
+
_shards.delete(name);
|
|
3471
|
+
_shardLastAccess.delete(name);
|
|
3472
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3473
|
+
const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
3474
|
+
renameSync3(dbPath, archivedPath);
|
|
3475
|
+
item.archivedPath = archivedPath;
|
|
3476
|
+
}
|
|
3477
|
+
} finally {
|
|
3478
|
+
try {
|
|
3479
|
+
client.close();
|
|
3480
|
+
} catch {
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
shards.push(item);
|
|
3484
|
+
}
|
|
3485
|
+
return {
|
|
3486
|
+
total: shards.length,
|
|
3487
|
+
ok: shards.filter((s) => s.ok).length,
|
|
3488
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
3489
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
3490
|
+
shards
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3390
3493
|
async function ensureShardSchema(client) {
|
|
3391
3494
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
3392
3495
|
await client.execute("PRAGMA busy_timeout = 30000");
|