@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.
Files changed (67) hide show
  1. package/dist/bin/backfill-conversations.js +103 -0
  2. package/dist/bin/backfill-responses.js +103 -0
  3. package/dist/bin/backfill-vectors.js +103 -0
  4. package/dist/bin/cleanup-stale-review-tasks.js +103 -0
  5. package/dist/bin/cli.js +121 -10
  6. package/dist/bin/exe-assign.js +103 -0
  7. package/dist/bin/exe-boot.js +96 -10
  8. package/dist/bin/exe-call.js +25 -0
  9. package/dist/bin/exe-cloud.js +31 -10
  10. package/dist/bin/exe-dispatch.js +103 -0
  11. package/dist/bin/exe-doctor.js +152 -3
  12. package/dist/bin/exe-export-behaviors.js +103 -0
  13. package/dist/bin/exe-forget.js +103 -0
  14. package/dist/bin/exe-gateway.js +103 -0
  15. package/dist/bin/exe-heartbeat.js +103 -0
  16. package/dist/bin/exe-kill.js +103 -0
  17. package/dist/bin/exe-launch-agent.js +103 -0
  18. package/dist/bin/exe-link.js +31 -10
  19. package/dist/bin/exe-new-employee.js +25 -0
  20. package/dist/bin/exe-pending-messages.js +103 -0
  21. package/dist/bin/exe-pending-notifications.js +103 -0
  22. package/dist/bin/exe-pending-reviews.js +103 -0
  23. package/dist/bin/exe-rename.js +103 -0
  24. package/dist/bin/exe-review.js +103 -0
  25. package/dist/bin/exe-search.js +103 -0
  26. package/dist/bin/exe-session-cleanup.js +103 -0
  27. package/dist/bin/exe-start-codex.js +103 -0
  28. package/dist/bin/exe-start-opencode.js +103 -0
  29. package/dist/bin/exe-status.js +103 -0
  30. package/dist/bin/exe-team.js +103 -0
  31. package/dist/bin/git-sweep.js +103 -0
  32. package/dist/bin/graph-backfill.js +103 -0
  33. package/dist/bin/graph-export.js +103 -0
  34. package/dist/bin/intercom-check.js +103 -0
  35. package/dist/bin/scan-tasks.js +103 -0
  36. package/dist/bin/setup.js +56 -10
  37. package/dist/bin/shard-migrate.js +103 -0
  38. package/dist/gateway/index.js +103 -0
  39. package/dist/hooks/bug-report-worker.js +103 -0
  40. package/dist/hooks/codex-stop-task-finalizer.js +103 -0
  41. package/dist/hooks/commit-complete.js +103 -0
  42. package/dist/hooks/error-recall.js +103 -0
  43. package/dist/hooks/ingest.js +103 -0
  44. package/dist/hooks/instructions-loaded.js +103 -0
  45. package/dist/hooks/notification.js +103 -0
  46. package/dist/hooks/post-compact.js +103 -0
  47. package/dist/hooks/post-tool-combined.js +103 -0
  48. package/dist/hooks/pre-compact.js +103 -0
  49. package/dist/hooks/pre-tool-use.js +103 -0
  50. package/dist/hooks/prompt-submit.js +103 -0
  51. package/dist/hooks/session-end.js +103 -0
  52. package/dist/hooks/session-start.js +103 -0
  53. package/dist/hooks/stop.js +103 -0
  54. package/dist/hooks/subagent-stop.js +103 -0
  55. package/dist/hooks/summary-worker.js +96 -10
  56. package/dist/index.js +103 -0
  57. package/dist/lib/cloud-sync.js +31 -10
  58. package/dist/lib/employee-templates.js +25 -0
  59. package/dist/lib/exe-daemon.js +165 -14
  60. package/dist/lib/hybrid-search.js +103 -0
  61. package/dist/lib/keychain.js +31 -10
  62. package/dist/lib/schedules.js +103 -0
  63. package/dist/lib/store.js +103 -0
  64. package/dist/mcp/server.js +164 -13
  65. package/dist/runtime/index.js +103 -0
  66. package/dist/tui/App.js +96 -10
  67. 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 dir = getKeyDir();
526
- await mkdir2(dir, { recursive: true });
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) {
@@ -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");