@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
@@ -2729,6 +2729,7 @@ var init_database = __esm({
2729
2729
  // src/lib/shard-manager.ts
2730
2730
  var shard_manager_exports = {};
2731
2731
  __export(shard_manager_exports, {
2732
+ auditShardHealth: () => auditShardHealth,
2732
2733
  disposeShards: () => disposeShards,
2733
2734
  ensureShardSchema: () => ensureShardSchema,
2734
2735
  getOpenShardCount: () => getOpenShardCount,
@@ -2795,6 +2796,70 @@ function listShards() {
2795
2796
  if (!existsSync7(SHARDS_DIR)) return [];
2796
2797
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2797
2798
  }
2799
+ async function auditShardHealth(options = {}) {
2800
+ if (!_encryptionKey) {
2801
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
2802
+ }
2803
+ const repair = options.repair === true;
2804
+ const dryRun = options.dryRun === true;
2805
+ const names = listShards();
2806
+ const shards = [];
2807
+ for (const name of names) {
2808
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2809
+ const stat2 = statSync2(dbPath);
2810
+ const item = {
2811
+ name,
2812
+ path: dbPath,
2813
+ ok: false,
2814
+ unreadable: false,
2815
+ error: null,
2816
+ size: stat2.size,
2817
+ mtime: stat2.mtime.toISOString(),
2818
+ memoryCount: null
2819
+ };
2820
+ const client = createClient2({
2821
+ url: `file:${dbPath}`,
2822
+ encryptionKey: _encryptionKey
2823
+ });
2824
+ try {
2825
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
2826
+ const hasMemories = await client.execute(
2827
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
2828
+ );
2829
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
2830
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
2831
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
2832
+ }
2833
+ item.ok = true;
2834
+ } catch (err) {
2835
+ const message = err instanceof Error ? err.message : String(err);
2836
+ item.error = message;
2837
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
2838
+ if (item.unreadable && repair && !dryRun) {
2839
+ client.close();
2840
+ _shards.delete(name);
2841
+ _shardLastAccess.delete(name);
2842
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2843
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
2844
+ renameSync3(dbPath, archivedPath);
2845
+ item.archivedPath = archivedPath;
2846
+ }
2847
+ } finally {
2848
+ try {
2849
+ client.close();
2850
+ } catch {
2851
+ }
2852
+ }
2853
+ shards.push(item);
2854
+ }
2855
+ return {
2856
+ total: shards.length,
2857
+ ok: shards.filter((s) => s.ok).length,
2858
+ unreadable: shards.filter((s) => s.unreadable).length,
2859
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
2860
+ shards
2861
+ };
2862
+ }
2798
2863
  async function ensureShardSchema(client) {
2799
2864
  await client.execute("PRAGMA journal_mode = WAL");
2800
2865
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3443,6 +3508,15 @@ function readMachineId() {
3443
3508
  return "";
3444
3509
  }
3445
3510
  }
3511
+ function encryptWithMachineKey(plaintext, machineKey) {
3512
+ const crypto3 = __require("crypto");
3513
+ const iv = crypto3.randomBytes(12);
3514
+ const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
3515
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3516
+ encrypted += cipher.final("base64");
3517
+ const authTag = cipher.getAuthTag().toString("base64");
3518
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3519
+ }
3446
3520
  function decryptWithMachineKey(encrypted, machineKey) {
3447
3521
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3448
3522
  try {
@@ -3461,6 +3535,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3461
3535
  return null;
3462
3536
  }
3463
3537
  }
3538
+ async function writeMachineBoundFileFallback(b64) {
3539
+ const dir = getKeyDir();
3540
+ await mkdir3(dir, { recursive: true });
3541
+ const keyPath = getKeyPath();
3542
+ const machineKey = deriveMachineKey();
3543
+ if (machineKey) {
3544
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3545
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3546
+ await chmod2(keyPath, 384);
3547
+ return "encrypted";
3548
+ }
3549
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3550
+ await chmod2(keyPath, 384);
3551
+ return "plaintext";
3552
+ }
3464
3553
  async function getMasterKey() {
3465
3554
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3466
3555
  if (nativeValue) {
@@ -3512,6 +3601,20 @@ async function getMasterKey() {
3512
3601
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3513
3602
  if (migrated) {
3514
3603
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3604
+ try {
3605
+ await unlink(keyPath);
3606
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3607
+ } catch {
3608
+ }
3609
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3610
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3611
+ if (fallback === "encrypted") {
3612
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3613
+ } else {
3614
+ process.stderr.write(
3615
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3616
+ );
3617
+ }
3515
3618
  }
3516
3619
  return key;
3517
3620
  } catch (err) {
@@ -2729,6 +2729,7 @@ var init_database = __esm({
2729
2729
  // src/lib/shard-manager.ts
2730
2730
  var shard_manager_exports = {};
2731
2731
  __export(shard_manager_exports, {
2732
+ auditShardHealth: () => auditShardHealth,
2732
2733
  disposeShards: () => disposeShards,
2733
2734
  ensureShardSchema: () => ensureShardSchema,
2734
2735
  getOpenShardCount: () => getOpenShardCount,
@@ -2795,6 +2796,70 @@ function listShards() {
2795
2796
  if (!existsSync7(SHARDS_DIR)) return [];
2796
2797
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2797
2798
  }
2799
+ async function auditShardHealth(options = {}) {
2800
+ if (!_encryptionKey) {
2801
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
2802
+ }
2803
+ const repair = options.repair === true;
2804
+ const dryRun = options.dryRun === true;
2805
+ const names = listShards();
2806
+ const shards = [];
2807
+ for (const name of names) {
2808
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2809
+ const stat2 = statSync2(dbPath);
2810
+ const item = {
2811
+ name,
2812
+ path: dbPath,
2813
+ ok: false,
2814
+ unreadable: false,
2815
+ error: null,
2816
+ size: stat2.size,
2817
+ mtime: stat2.mtime.toISOString(),
2818
+ memoryCount: null
2819
+ };
2820
+ const client = createClient2({
2821
+ url: `file:${dbPath}`,
2822
+ encryptionKey: _encryptionKey
2823
+ });
2824
+ try {
2825
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
2826
+ const hasMemories = await client.execute(
2827
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
2828
+ );
2829
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
2830
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
2831
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
2832
+ }
2833
+ item.ok = true;
2834
+ } catch (err) {
2835
+ const message = err instanceof Error ? err.message : String(err);
2836
+ item.error = message;
2837
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
2838
+ if (item.unreadable && repair && !dryRun) {
2839
+ client.close();
2840
+ _shards.delete(name);
2841
+ _shardLastAccess.delete(name);
2842
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2843
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
2844
+ renameSync3(dbPath, archivedPath);
2845
+ item.archivedPath = archivedPath;
2846
+ }
2847
+ } finally {
2848
+ try {
2849
+ client.close();
2850
+ } catch {
2851
+ }
2852
+ }
2853
+ shards.push(item);
2854
+ }
2855
+ return {
2856
+ total: shards.length,
2857
+ ok: shards.filter((s) => s.ok).length,
2858
+ unreadable: shards.filter((s) => s.unreadable).length,
2859
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
2860
+ shards
2861
+ };
2862
+ }
2798
2863
  async function ensureShardSchema(client) {
2799
2864
  await client.execute("PRAGMA journal_mode = WAL");
2800
2865
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3442,6 +3507,15 @@ function readMachineId() {
3442
3507
  return "";
3443
3508
  }
3444
3509
  }
3510
+ function encryptWithMachineKey(plaintext, machineKey) {
3511
+ const crypto3 = __require("crypto");
3512
+ const iv = crypto3.randomBytes(12);
3513
+ const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
3514
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3515
+ encrypted += cipher.final("base64");
3516
+ const authTag = cipher.getAuthTag().toString("base64");
3517
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3518
+ }
3445
3519
  function decryptWithMachineKey(encrypted, machineKey) {
3446
3520
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3447
3521
  try {
@@ -3460,6 +3534,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3460
3534
  return null;
3461
3535
  }
3462
3536
  }
3537
+ async function writeMachineBoundFileFallback(b64) {
3538
+ const dir = getKeyDir();
3539
+ await mkdir3(dir, { recursive: true });
3540
+ const keyPath = getKeyPath();
3541
+ const machineKey = deriveMachineKey();
3542
+ if (machineKey) {
3543
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3544
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3545
+ await chmod2(keyPath, 384);
3546
+ return "encrypted";
3547
+ }
3548
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3549
+ await chmod2(keyPath, 384);
3550
+ return "plaintext";
3551
+ }
3463
3552
  async function getMasterKey() {
3464
3553
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3465
3554
  if (nativeValue) {
@@ -3511,6 +3600,20 @@ async function getMasterKey() {
3511
3600
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3512
3601
  if (migrated) {
3513
3602
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3603
+ try {
3604
+ await unlink(keyPath);
3605
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3606
+ } catch {
3607
+ }
3608
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3609
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3610
+ if (fallback === "encrypted") {
3611
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3612
+ } else {
3613
+ process.stderr.write(
3614
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3615
+ );
3616
+ }
3514
3617
  }
3515
3618
  return key;
3516
3619
  } catch (err) {
@@ -2725,6 +2725,7 @@ var init_database = __esm({
2725
2725
  // src/lib/shard-manager.ts
2726
2726
  var shard_manager_exports = {};
2727
2727
  __export(shard_manager_exports, {
2728
+ auditShardHealth: () => auditShardHealth,
2728
2729
  disposeShards: () => disposeShards,
2729
2730
  ensureShardSchema: () => ensureShardSchema,
2730
2731
  getOpenShardCount: () => getOpenShardCount,
@@ -2791,6 +2792,70 @@ function listShards() {
2791
2792
  if (!existsSync7(SHARDS_DIR)) return [];
2792
2793
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2793
2794
  }
2795
+ async function auditShardHealth(options = {}) {
2796
+ if (!_encryptionKey) {
2797
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
2798
+ }
2799
+ const repair = options.repair === true;
2800
+ const dryRun = options.dryRun === true;
2801
+ const names = listShards();
2802
+ const shards = [];
2803
+ for (const name of names) {
2804
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2805
+ const stat = statSync2(dbPath);
2806
+ const item = {
2807
+ name,
2808
+ path: dbPath,
2809
+ ok: false,
2810
+ unreadable: false,
2811
+ error: null,
2812
+ size: stat.size,
2813
+ mtime: stat.mtime.toISOString(),
2814
+ memoryCount: null
2815
+ };
2816
+ const client = createClient2({
2817
+ url: `file:${dbPath}`,
2818
+ encryptionKey: _encryptionKey
2819
+ });
2820
+ try {
2821
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
2822
+ const hasMemories = await client.execute(
2823
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
2824
+ );
2825
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
2826
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
2827
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
2828
+ }
2829
+ item.ok = true;
2830
+ } catch (err) {
2831
+ const message = err instanceof Error ? err.message : String(err);
2832
+ item.error = message;
2833
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
2834
+ if (item.unreadable && repair && !dryRun) {
2835
+ client.close();
2836
+ _shards.delete(name);
2837
+ _shardLastAccess.delete(name);
2838
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2839
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
2840
+ renameSync3(dbPath, archivedPath);
2841
+ item.archivedPath = archivedPath;
2842
+ }
2843
+ } finally {
2844
+ try {
2845
+ client.close();
2846
+ } catch {
2847
+ }
2848
+ }
2849
+ shards.push(item);
2850
+ }
2851
+ return {
2852
+ total: shards.length,
2853
+ ok: shards.filter((s) => s.ok).length,
2854
+ unreadable: shards.filter((s) => s.unreadable).length,
2855
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
2856
+ shards
2857
+ };
2858
+ }
2794
2859
  async function ensureShardSchema(client) {
2795
2860
  await client.execute("PRAGMA journal_mode = WAL");
2796
2861
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3430,6 +3495,15 @@ function readMachineId() {
3430
3495
  return "";
3431
3496
  }
3432
3497
  }
3498
+ function encryptWithMachineKey(plaintext, machineKey) {
3499
+ const crypto2 = __require("crypto");
3500
+ const iv = crypto2.randomBytes(12);
3501
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3502
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3503
+ encrypted += cipher.final("base64");
3504
+ const authTag = cipher.getAuthTag().toString("base64");
3505
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3506
+ }
3433
3507
  function decryptWithMachineKey(encrypted, machineKey) {
3434
3508
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3435
3509
  try {
@@ -3448,6 +3522,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3448
3522
  return null;
3449
3523
  }
3450
3524
  }
3525
+ async function writeMachineBoundFileFallback(b64) {
3526
+ const dir = getKeyDir();
3527
+ await mkdir3(dir, { recursive: true });
3528
+ const keyPath = getKeyPath();
3529
+ const machineKey = deriveMachineKey();
3530
+ if (machineKey) {
3531
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3532
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3533
+ await chmod2(keyPath, 384);
3534
+ return "encrypted";
3535
+ }
3536
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3537
+ await chmod2(keyPath, 384);
3538
+ return "plaintext";
3539
+ }
3451
3540
  async function getMasterKey() {
3452
3541
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3453
3542
  if (nativeValue) {
@@ -3499,6 +3588,20 @@ async function getMasterKey() {
3499
3588
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3500
3589
  if (migrated) {
3501
3590
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3591
+ try {
3592
+ await unlink(keyPath);
3593
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3594
+ } catch {
3595
+ }
3596
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3597
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3598
+ if (fallback === "encrypted") {
3599
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3600
+ } else {
3601
+ process.stderr.write(
3602
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3603
+ );
3604
+ }
3502
3605
  }
3503
3606
  return key;
3504
3607
  } catch (err) {
@@ -2908,6 +2908,15 @@ function readMachineId() {
2908
2908
  return "";
2909
2909
  }
2910
2910
  }
2911
+ function encryptWithMachineKey(plaintext, machineKey) {
2912
+ const crypto2 = __require("crypto");
2913
+ const iv = crypto2.randomBytes(12);
2914
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
2915
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2916
+ encrypted += cipher.final("base64");
2917
+ const authTag = cipher.getAuthTag().toString("base64");
2918
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2919
+ }
2911
2920
  function decryptWithMachineKey(encrypted, machineKey) {
2912
2921
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2913
2922
  try {
@@ -2926,6 +2935,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
2926
2935
  return null;
2927
2936
  }
2928
2937
  }
2938
+ async function writeMachineBoundFileFallback(b64) {
2939
+ const dir = getKeyDir();
2940
+ await mkdir3(dir, { recursive: true });
2941
+ const keyPath = getKeyPath();
2942
+ const machineKey = deriveMachineKey();
2943
+ if (machineKey) {
2944
+ const encrypted = encryptWithMachineKey(b64, machineKey);
2945
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
2946
+ await chmod2(keyPath, 384);
2947
+ return "encrypted";
2948
+ }
2949
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
2950
+ await chmod2(keyPath, 384);
2951
+ return "plaintext";
2952
+ }
2929
2953
  async function getMasterKey() {
2930
2954
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
2931
2955
  if (nativeValue) {
@@ -2977,6 +3001,20 @@ async function getMasterKey() {
2977
3001
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2978
3002
  if (migrated) {
2979
3003
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3004
+ try {
3005
+ await unlink(keyPath);
3006
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3007
+ } catch {
3008
+ }
3009
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3010
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3011
+ if (fallback === "encrypted") {
3012
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3013
+ } else {
3014
+ process.stderr.write(
3015
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3016
+ );
3017
+ }
2980
3018
  }
2981
3019
  return key;
2982
3020
  } catch (err) {
@@ -3249,6 +3287,7 @@ var init_memory_write_governor = __esm({
3249
3287
  // src/lib/shard-manager.ts
3250
3288
  var shard_manager_exports = {};
3251
3289
  __export(shard_manager_exports, {
3290
+ auditShardHealth: () => auditShardHealth,
3252
3291
  disposeShards: () => disposeShards,
3253
3292
  ensureShardSchema: () => ensureShardSchema,
3254
3293
  getOpenShardCount: () => getOpenShardCount,
@@ -3315,6 +3354,70 @@ function listShards() {
3315
3354
  if (!existsSync7(SHARDS_DIR)) return [];
3316
3355
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3317
3356
  }
3357
+ async function auditShardHealth(options = {}) {
3358
+ if (!_encryptionKey) {
3359
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3360
+ }
3361
+ const repair = options.repair === true;
3362
+ const dryRun = options.dryRun === true;
3363
+ const names = listShards();
3364
+ const shards = [];
3365
+ for (const name of names) {
3366
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3367
+ const stat = statSync2(dbPath);
3368
+ const item = {
3369
+ name,
3370
+ path: dbPath,
3371
+ ok: false,
3372
+ unreadable: false,
3373
+ error: null,
3374
+ size: stat.size,
3375
+ mtime: stat.mtime.toISOString(),
3376
+ memoryCount: null
3377
+ };
3378
+ const client = createClient2({
3379
+ url: `file:${dbPath}`,
3380
+ encryptionKey: _encryptionKey
3381
+ });
3382
+ try {
3383
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3384
+ const hasMemories = await client.execute(
3385
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3386
+ );
3387
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3388
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3389
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3390
+ }
3391
+ item.ok = true;
3392
+ } catch (err) {
3393
+ const message = err instanceof Error ? err.message : String(err);
3394
+ item.error = message;
3395
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3396
+ if (item.unreadable && repair && !dryRun) {
3397
+ client.close();
3398
+ _shards.delete(name);
3399
+ _shardLastAccess.delete(name);
3400
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3401
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3402
+ renameSync3(dbPath, archivedPath);
3403
+ item.archivedPath = archivedPath;
3404
+ }
3405
+ } finally {
3406
+ try {
3407
+ client.close();
3408
+ } catch {
3409
+ }
3410
+ }
3411
+ shards.push(item);
3412
+ }
3413
+ return {
3414
+ total: shards.length,
3415
+ ok: shards.filter((s) => s.ok).length,
3416
+ unreadable: shards.filter((s) => s.unreadable).length,
3417
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3418
+ shards
3419
+ };
3420
+ }
3318
3421
  async function ensureShardSchema(client) {
3319
3422
  await client.execute("PRAGMA journal_mode = WAL");
3320
3423
  await client.execute("PRAGMA busy_timeout = 30000");