@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
@@ -3319,6 +3319,15 @@ function readMachineId() {
3319
3319
  return "";
3320
3320
  }
3321
3321
  }
3322
+ function encryptWithMachineKey(plaintext, machineKey) {
3323
+ const crypto2 = __require("crypto");
3324
+ const iv = crypto2.randomBytes(12);
3325
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3326
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3327
+ encrypted += cipher.final("base64");
3328
+ const authTag = cipher.getAuthTag().toString("base64");
3329
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3330
+ }
3322
3331
  function decryptWithMachineKey(encrypted, machineKey) {
3323
3332
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3324
3333
  try {
@@ -3337,6 +3346,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3337
3346
  return null;
3338
3347
  }
3339
3348
  }
3349
+ async function writeMachineBoundFileFallback(b64) {
3350
+ const dir = getKeyDir();
3351
+ await mkdir3(dir, { recursive: true });
3352
+ const keyPath = getKeyPath();
3353
+ const machineKey = deriveMachineKey();
3354
+ if (machineKey) {
3355
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3356
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3357
+ await chmod2(keyPath, 384);
3358
+ return "encrypted";
3359
+ }
3360
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3361
+ await chmod2(keyPath, 384);
3362
+ return "plaintext";
3363
+ }
3340
3364
  async function getMasterKey() {
3341
3365
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3342
3366
  if (nativeValue) {
@@ -3388,6 +3412,20 @@ async function getMasterKey() {
3388
3412
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3389
3413
  if (migrated) {
3390
3414
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3415
+ try {
3416
+ await unlink(keyPath);
3417
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3418
+ } catch {
3419
+ }
3420
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3421
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3422
+ if (fallback === "encrypted") {
3423
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3424
+ } else {
3425
+ process.stderr.write(
3426
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3427
+ );
3428
+ }
3391
3429
  }
3392
3430
  return key;
3393
3431
  } catch (err) {
@@ -3660,6 +3698,7 @@ var init_memory_write_governor = __esm({
3660
3698
  // src/lib/shard-manager.ts
3661
3699
  var shard_manager_exports = {};
3662
3700
  __export(shard_manager_exports, {
3701
+ auditShardHealth: () => auditShardHealth,
3663
3702
  disposeShards: () => disposeShards,
3664
3703
  ensureShardSchema: () => ensureShardSchema,
3665
3704
  getOpenShardCount: () => getOpenShardCount,
@@ -3726,6 +3765,70 @@ function listShards() {
3726
3765
  if (!existsSync12(SHARDS_DIR)) return [];
3727
3766
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3728
3767
  }
3768
+ async function auditShardHealth(options = {}) {
3769
+ if (!_encryptionKey) {
3770
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3771
+ }
3772
+ const repair = options.repair === true;
3773
+ const dryRun = options.dryRun === true;
3774
+ const names = listShards();
3775
+ const shards = [];
3776
+ for (const name of names) {
3777
+ const dbPath = path14.join(SHARDS_DIR, `${name}.db`);
3778
+ const stat = statSync2(dbPath);
3779
+ const item = {
3780
+ name,
3781
+ path: dbPath,
3782
+ ok: false,
3783
+ unreadable: false,
3784
+ error: null,
3785
+ size: stat.size,
3786
+ mtime: stat.mtime.toISOString(),
3787
+ memoryCount: null
3788
+ };
3789
+ const client = createClient2({
3790
+ url: `file:${dbPath}`,
3791
+ encryptionKey: _encryptionKey
3792
+ });
3793
+ try {
3794
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3795
+ const hasMemories = await client.execute(
3796
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3797
+ );
3798
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3799
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3800
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3801
+ }
3802
+ item.ok = true;
3803
+ } catch (err) {
3804
+ const message = err instanceof Error ? err.message : String(err);
3805
+ item.error = message;
3806
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3807
+ if (item.unreadable && repair && !dryRun) {
3808
+ client.close();
3809
+ _shards.delete(name);
3810
+ _shardLastAccess.delete(name);
3811
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3812
+ const archivedPath = path14.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3813
+ renameSync4(dbPath, archivedPath);
3814
+ item.archivedPath = archivedPath;
3815
+ }
3816
+ } finally {
3817
+ try {
3818
+ client.close();
3819
+ } catch {
3820
+ }
3821
+ }
3822
+ shards.push(item);
3823
+ }
3824
+ return {
3825
+ total: shards.length,
3826
+ ok: shards.filter((s) => s.ok).length,
3827
+ unreadable: shards.filter((s) => s.unreadable).length,
3828
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3829
+ shards
3830
+ };
3831
+ }
3729
3832
  async function ensureShardSchema(client) {
3730
3833
  await client.execute("PRAGMA journal_mode = WAL");
3731
3834
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3063,6 +3063,15 @@ function readMachineId() {
3063
3063
  return "";
3064
3064
  }
3065
3065
  }
3066
+ function encryptWithMachineKey(plaintext, machineKey) {
3067
+ const crypto4 = __require("crypto");
3068
+ const iv = crypto4.randomBytes(12);
3069
+ const cipher = crypto4.createCipheriv("aes-256-gcm", machineKey, iv);
3070
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3071
+ encrypted += cipher.final("base64");
3072
+ const authTag = cipher.getAuthTag().toString("base64");
3073
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3074
+ }
3066
3075
  function decryptWithMachineKey(encrypted, machineKey) {
3067
3076
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3068
3077
  try {
@@ -3081,6 +3090,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3081
3090
  return null;
3082
3091
  }
3083
3092
  }
3093
+ async function writeMachineBoundFileFallback(b64) {
3094
+ const dir = getKeyDir();
3095
+ await mkdir3(dir, { recursive: true });
3096
+ const keyPath = getKeyPath();
3097
+ const machineKey = deriveMachineKey();
3098
+ if (machineKey) {
3099
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3100
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3101
+ await chmod2(keyPath, 384);
3102
+ return "encrypted";
3103
+ }
3104
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3105
+ await chmod2(keyPath, 384);
3106
+ return "plaintext";
3107
+ }
3084
3108
  async function getMasterKey() {
3085
3109
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3086
3110
  if (nativeValue) {
@@ -3132,6 +3156,20 @@ async function getMasterKey() {
3132
3156
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3133
3157
  if (migrated) {
3134
3158
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3159
+ try {
3160
+ await unlink(keyPath);
3161
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3162
+ } catch {
3163
+ }
3164
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3165
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3166
+ if (fallback === "encrypted") {
3167
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3168
+ } else {
3169
+ process.stderr.write(
3170
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3171
+ );
3172
+ }
3135
3173
  }
3136
3174
  return key;
3137
3175
  } catch (err) {
@@ -3404,6 +3442,7 @@ var init_memory_write_governor = __esm({
3404
3442
  // src/lib/shard-manager.ts
3405
3443
  var shard_manager_exports = {};
3406
3444
  __export(shard_manager_exports, {
3445
+ auditShardHealth: () => auditShardHealth,
3407
3446
  disposeShards: () => disposeShards,
3408
3447
  ensureShardSchema: () => ensureShardSchema,
3409
3448
  getOpenShardCount: () => getOpenShardCount,
@@ -3470,6 +3509,70 @@ function listShards() {
3470
3509
  if (!existsSync7(SHARDS_DIR)) return [];
3471
3510
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3472
3511
  }
3512
+ async function auditShardHealth(options = {}) {
3513
+ if (!_encryptionKey) {
3514
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3515
+ }
3516
+ const repair = options.repair === true;
3517
+ const dryRun = options.dryRun === true;
3518
+ const names = listShards();
3519
+ const shards = [];
3520
+ for (const name of names) {
3521
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3522
+ const stat = statSync2(dbPath);
3523
+ const item = {
3524
+ name,
3525
+ path: dbPath,
3526
+ ok: false,
3527
+ unreadable: false,
3528
+ error: null,
3529
+ size: stat.size,
3530
+ mtime: stat.mtime.toISOString(),
3531
+ memoryCount: null
3532
+ };
3533
+ const client = createClient2({
3534
+ url: `file:${dbPath}`,
3535
+ encryptionKey: _encryptionKey
3536
+ });
3537
+ try {
3538
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3539
+ const hasMemories = await client.execute(
3540
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3541
+ );
3542
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3543
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3544
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3545
+ }
3546
+ item.ok = true;
3547
+ } catch (err) {
3548
+ const message = err instanceof Error ? err.message : String(err);
3549
+ item.error = message;
3550
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3551
+ if (item.unreadable && repair && !dryRun) {
3552
+ client.close();
3553
+ _shards.delete(name);
3554
+ _shardLastAccess.delete(name);
3555
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3556
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3557
+ renameSync3(dbPath, archivedPath);
3558
+ item.archivedPath = archivedPath;
3559
+ }
3560
+ } finally {
3561
+ try {
3562
+ client.close();
3563
+ } catch {
3564
+ }
3565
+ }
3566
+ shards.push(item);
3567
+ }
3568
+ return {
3569
+ total: shards.length,
3570
+ ok: shards.filter((s) => s.ok).length,
3571
+ unreadable: shards.filter((s) => s.unreadable).length,
3572
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3573
+ shards
3574
+ };
3575
+ }
3473
3576
  async function ensureShardSchema(client) {
3474
3577
  await client.execute("PRAGMA journal_mode = WAL");
3475
3578
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -6635,6 +6635,15 @@ function readMachineId() {
6635
6635
  return "";
6636
6636
  }
6637
6637
  }
6638
+ function encryptWithMachineKey(plaintext, machineKey) {
6639
+ const crypto7 = __require("crypto");
6640
+ const iv = crypto7.randomBytes(12);
6641
+ const cipher = crypto7.createCipheriv("aes-256-gcm", machineKey, iv);
6642
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
6643
+ encrypted += cipher.final("base64");
6644
+ const authTag = cipher.getAuthTag().toString("base64");
6645
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
6646
+ }
6638
6647
  function decryptWithMachineKey(encrypted, machineKey) {
6639
6648
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
6640
6649
  try {
@@ -6653,6 +6662,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
6653
6662
  return null;
6654
6663
  }
6655
6664
  }
6665
+ async function writeMachineBoundFileFallback(b64) {
6666
+ const dir = getKeyDir();
6667
+ await mkdir4(dir, { recursive: true });
6668
+ const keyPath = getKeyPath();
6669
+ const machineKey = deriveMachineKey();
6670
+ if (machineKey) {
6671
+ const encrypted = encryptWithMachineKey(b64, machineKey);
6672
+ await writeFile5(keyPath, encrypted + "\n", "utf-8");
6673
+ await chmod2(keyPath, 384);
6674
+ return "encrypted";
6675
+ }
6676
+ await writeFile5(keyPath, b64 + "\n", "utf-8");
6677
+ await chmod2(keyPath, 384);
6678
+ return "plaintext";
6679
+ }
6656
6680
  async function getMasterKey() {
6657
6681
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
6658
6682
  if (nativeValue) {
@@ -6704,6 +6728,20 @@ async function getMasterKey() {
6704
6728
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
6705
6729
  if (migrated) {
6706
6730
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
6731
+ try {
6732
+ await unlink(keyPath);
6733
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
6734
+ } catch {
6735
+ }
6736
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
6737
+ const fallback = await writeMachineBoundFileFallback(b64Value);
6738
+ if (fallback === "encrypted") {
6739
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
6740
+ } else {
6741
+ process.stderr.write(
6742
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
6743
+ );
6744
+ }
6707
6745
  }
6708
6746
  return key;
6709
6747
  } catch (err) {
@@ -6921,6 +6959,7 @@ var init_memory_write_governor = __esm({
6921
6959
  // src/lib/shard-manager.ts
6922
6960
  var shard_manager_exports = {};
6923
6961
  __export(shard_manager_exports, {
6962
+ auditShardHealth: () => auditShardHealth,
6924
6963
  disposeShards: () => disposeShards,
6925
6964
  ensureShardSchema: () => ensureShardSchema,
6926
6965
  getOpenShardCount: () => getOpenShardCount,
@@ -6987,6 +7026,70 @@ function listShards() {
6987
7026
  if (!existsSync16(SHARDS_DIR)) return [];
6988
7027
  return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
6989
7028
  }
7029
+ async function auditShardHealth(options = {}) {
7030
+ if (!_encryptionKey) {
7031
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
7032
+ }
7033
+ const repair = options.repair === true;
7034
+ const dryRun = options.dryRun === true;
7035
+ const names = listShards();
7036
+ const shards = [];
7037
+ for (const name of names) {
7038
+ const dbPath = path20.join(SHARDS_DIR, `${name}.db`);
7039
+ const stat = statSync2(dbPath);
7040
+ const item = {
7041
+ name,
7042
+ path: dbPath,
7043
+ ok: false,
7044
+ unreadable: false,
7045
+ error: null,
7046
+ size: stat.size,
7047
+ mtime: stat.mtime.toISOString(),
7048
+ memoryCount: null
7049
+ };
7050
+ const client = createClient2({
7051
+ url: `file:${dbPath}`,
7052
+ encryptionKey: _encryptionKey
7053
+ });
7054
+ try {
7055
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
7056
+ const hasMemories = await client.execute(
7057
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
7058
+ );
7059
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
7060
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
7061
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
7062
+ }
7063
+ item.ok = true;
7064
+ } catch (err) {
7065
+ const message = err instanceof Error ? err.message : String(err);
7066
+ item.error = message;
7067
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
7068
+ if (item.unreadable && repair && !dryRun) {
7069
+ client.close();
7070
+ _shards.delete(name);
7071
+ _shardLastAccess.delete(name);
7072
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7073
+ const archivedPath = path20.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
7074
+ renameSync4(dbPath, archivedPath);
7075
+ item.archivedPath = archivedPath;
7076
+ }
7077
+ } finally {
7078
+ try {
7079
+ client.close();
7080
+ } catch {
7081
+ }
7082
+ }
7083
+ shards.push(item);
7084
+ }
7085
+ return {
7086
+ total: shards.length,
7087
+ ok: shards.filter((s) => s.ok).length,
7088
+ unreadable: shards.filter((s) => s.unreadable).length,
7089
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
7090
+ shards
7091
+ };
7092
+ }
6990
7093
  async function ensureShardSchema(client) {
6991
7094
  await client.execute("PRAGMA journal_mode = WAL");
6992
7095
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3790,6 +3790,15 @@ function readMachineId() {
3790
3790
  return "";
3791
3791
  }
3792
3792
  }
3793
+ function encryptWithMachineKey(plaintext, machineKey) {
3794
+ const crypto2 = __require("crypto");
3795
+ const iv = crypto2.randomBytes(12);
3796
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3797
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3798
+ encrypted += cipher.final("base64");
3799
+ const authTag = cipher.getAuthTag().toString("base64");
3800
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3801
+ }
3793
3802
  function decryptWithMachineKey(encrypted, machineKey) {
3794
3803
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3795
3804
  try {
@@ -3808,6 +3817,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3808
3817
  return null;
3809
3818
  }
3810
3819
  }
3820
+ async function writeMachineBoundFileFallback(b64) {
3821
+ const dir = getKeyDir();
3822
+ await mkdir3(dir, { recursive: true });
3823
+ const keyPath = getKeyPath();
3824
+ const machineKey = deriveMachineKey();
3825
+ if (machineKey) {
3826
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3827
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3828
+ await chmod2(keyPath, 384);
3829
+ return "encrypted";
3830
+ }
3831
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3832
+ await chmod2(keyPath, 384);
3833
+ return "plaintext";
3834
+ }
3811
3835
  async function getMasterKey() {
3812
3836
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3813
3837
  if (nativeValue) {
@@ -3859,6 +3883,20 @@ async function getMasterKey() {
3859
3883
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3860
3884
  if (migrated) {
3861
3885
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3886
+ try {
3887
+ await unlink(keyPath);
3888
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3889
+ } catch {
3890
+ }
3891
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3892
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3893
+ if (fallback === "encrypted") {
3894
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3895
+ } else {
3896
+ process.stderr.write(
3897
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3898
+ );
3899
+ }
3862
3900
  }
3863
3901
  return key;
3864
3902
  } catch (err) {
@@ -4131,6 +4169,7 @@ var init_memory_write_governor = __esm({
4131
4169
  // src/lib/shard-manager.ts
4132
4170
  var shard_manager_exports = {};
4133
4171
  __export(shard_manager_exports, {
4172
+ auditShardHealth: () => auditShardHealth,
4134
4173
  disposeShards: () => disposeShards,
4135
4174
  ensureShardSchema: () => ensureShardSchema,
4136
4175
  getOpenShardCount: () => getOpenShardCount,
@@ -4197,6 +4236,70 @@ function listShards() {
4197
4236
  if (!existsSync13(SHARDS_DIR)) return [];
4198
4237
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4199
4238
  }
4239
+ async function auditShardHealth(options = {}) {
4240
+ if (!_encryptionKey) {
4241
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
4242
+ }
4243
+ const repair = options.repair === true;
4244
+ const dryRun = options.dryRun === true;
4245
+ const names = listShards();
4246
+ const shards = [];
4247
+ for (const name of names) {
4248
+ const dbPath = path15.join(SHARDS_DIR, `${name}.db`);
4249
+ const stat = statSync3(dbPath);
4250
+ const item = {
4251
+ name,
4252
+ path: dbPath,
4253
+ ok: false,
4254
+ unreadable: false,
4255
+ error: null,
4256
+ size: stat.size,
4257
+ mtime: stat.mtime.toISOString(),
4258
+ memoryCount: null
4259
+ };
4260
+ const client = createClient2({
4261
+ url: `file:${dbPath}`,
4262
+ encryptionKey: _encryptionKey
4263
+ });
4264
+ try {
4265
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
4266
+ const hasMemories = await client.execute(
4267
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
4268
+ );
4269
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
4270
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
4271
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
4272
+ }
4273
+ item.ok = true;
4274
+ } catch (err) {
4275
+ const message = err instanceof Error ? err.message : String(err);
4276
+ item.error = message;
4277
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
4278
+ if (item.unreadable && repair && !dryRun) {
4279
+ client.close();
4280
+ _shards.delete(name);
4281
+ _shardLastAccess.delete(name);
4282
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4283
+ const archivedPath = path15.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4284
+ renameSync4(dbPath, archivedPath);
4285
+ item.archivedPath = archivedPath;
4286
+ }
4287
+ } finally {
4288
+ try {
4289
+ client.close();
4290
+ } catch {
4291
+ }
4292
+ }
4293
+ shards.push(item);
4294
+ }
4295
+ return {
4296
+ total: shards.length,
4297
+ ok: shards.filter((s) => s.ok).length,
4298
+ unreadable: shards.filter((s) => s.unreadable).length,
4299
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
4300
+ shards
4301
+ };
4302
+ }
4200
4303
  async function ensureShardSchema(client) {
4201
4304
  await client.execute("PRAGMA journal_mode = WAL");
4202
4305
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3231,6 +3231,15 @@ function readMachineId() {
3231
3231
  return "";
3232
3232
  }
3233
3233
  }
3234
+ function encryptWithMachineKey(plaintext, machineKey) {
3235
+ const crypto9 = __require("crypto");
3236
+ const iv = crypto9.randomBytes(12);
3237
+ const cipher = crypto9.createCipheriv("aes-256-gcm", machineKey, iv);
3238
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3239
+ encrypted += cipher.final("base64");
3240
+ const authTag = cipher.getAuthTag().toString("base64");
3241
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3242
+ }
3234
3243
  function decryptWithMachineKey(encrypted, machineKey) {
3235
3244
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3236
3245
  try {
@@ -3249,6 +3258,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3249
3258
  return null;
3250
3259
  }
3251
3260
  }
3261
+ async function writeMachineBoundFileFallback(b64) {
3262
+ const dir = getKeyDir();
3263
+ await mkdir3(dir, { recursive: true });
3264
+ const keyPath = getKeyPath();
3265
+ const machineKey = deriveMachineKey();
3266
+ if (machineKey) {
3267
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3268
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3269
+ await chmod2(keyPath, 384);
3270
+ return "encrypted";
3271
+ }
3272
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3273
+ await chmod2(keyPath, 384);
3274
+ return "plaintext";
3275
+ }
3252
3276
  async function getMasterKey() {
3253
3277
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3254
3278
  if (nativeValue) {
@@ -3300,6 +3324,20 @@ async function getMasterKey() {
3300
3324
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3301
3325
  if (migrated) {
3302
3326
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3327
+ try {
3328
+ await unlink(keyPath);
3329
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3330
+ } catch {
3331
+ }
3332
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3333
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3334
+ if (fallback === "encrypted") {
3335
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3336
+ } else {
3337
+ process.stderr.write(
3338
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3339
+ );
3340
+ }
3303
3341
  }
3304
3342
  return key;
3305
3343
  } catch (err) {
@@ -3572,6 +3610,7 @@ var init_memory_write_governor = __esm({
3572
3610
  // src/lib/shard-manager.ts
3573
3611
  var shard_manager_exports = {};
3574
3612
  __export(shard_manager_exports, {
3613
+ auditShardHealth: () => auditShardHealth,
3575
3614
  disposeShards: () => disposeShards,
3576
3615
  ensureShardSchema: () => ensureShardSchema,
3577
3616
  getOpenShardCount: () => getOpenShardCount,
@@ -3638,6 +3677,70 @@ function listShards() {
3638
3677
  if (!existsSync8(SHARDS_DIR)) return [];
3639
3678
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3640
3679
  }
3680
+ async function auditShardHealth(options = {}) {
3681
+ if (!_encryptionKey) {
3682
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3683
+ }
3684
+ const repair = options.repair === true;
3685
+ const dryRun = options.dryRun === true;
3686
+ const names = listShards();
3687
+ const shards = [];
3688
+ for (const name of names) {
3689
+ const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
3690
+ const stat = statSync2(dbPath);
3691
+ const item = {
3692
+ name,
3693
+ path: dbPath,
3694
+ ok: false,
3695
+ unreadable: false,
3696
+ error: null,
3697
+ size: stat.size,
3698
+ mtime: stat.mtime.toISOString(),
3699
+ memoryCount: null
3700
+ };
3701
+ const client = createClient2({
3702
+ url: `file:${dbPath}`,
3703
+ encryptionKey: _encryptionKey
3704
+ });
3705
+ try {
3706
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3707
+ const hasMemories = await client.execute(
3708
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3709
+ );
3710
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3711
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3712
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3713
+ }
3714
+ item.ok = true;
3715
+ } catch (err) {
3716
+ const message = err instanceof Error ? err.message : String(err);
3717
+ item.error = message;
3718
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3719
+ if (item.unreadable && repair && !dryRun) {
3720
+ client.close();
3721
+ _shards.delete(name);
3722
+ _shardLastAccess.delete(name);
3723
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3724
+ const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3725
+ renameSync3(dbPath, archivedPath);
3726
+ item.archivedPath = archivedPath;
3727
+ }
3728
+ } finally {
3729
+ try {
3730
+ client.close();
3731
+ } catch {
3732
+ }
3733
+ }
3734
+ shards.push(item);
3735
+ }
3736
+ return {
3737
+ total: shards.length,
3738
+ ok: shards.filter((s) => s.ok).length,
3739
+ unreadable: shards.filter((s) => s.unreadable).length,
3740
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3741
+ shards
3742
+ };
3743
+ }
3641
3744
  async function ensureShardSchema(client) {
3642
3745
  await client.execute("PRAGMA journal_mode = WAL");
3643
3746
  await client.execute("PRAGMA busy_timeout = 30000");