@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
@@ -6651,6 +6651,15 @@ function readMachineId() {
6651
6651
  return "";
6652
6652
  }
6653
6653
  }
6654
+ function encryptWithMachineKey(plaintext, machineKey) {
6655
+ const crypto7 = __require("crypto");
6656
+ const iv = crypto7.randomBytes(12);
6657
+ const cipher = crypto7.createCipheriv("aes-256-gcm", machineKey, iv);
6658
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
6659
+ encrypted += cipher.final("base64");
6660
+ const authTag = cipher.getAuthTag().toString("base64");
6661
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
6662
+ }
6654
6663
  function decryptWithMachineKey(encrypted, machineKey) {
6655
6664
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
6656
6665
  try {
@@ -6669,6 +6678,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
6669
6678
  return null;
6670
6679
  }
6671
6680
  }
6681
+ async function writeMachineBoundFileFallback(b64) {
6682
+ const dir = getKeyDir();
6683
+ await mkdir4(dir, { recursive: true });
6684
+ const keyPath = getKeyPath();
6685
+ const machineKey = deriveMachineKey();
6686
+ if (machineKey) {
6687
+ const encrypted = encryptWithMachineKey(b64, machineKey);
6688
+ await writeFile5(keyPath, encrypted + "\n", "utf-8");
6689
+ await chmod2(keyPath, 384);
6690
+ return "encrypted";
6691
+ }
6692
+ await writeFile5(keyPath, b64 + "\n", "utf-8");
6693
+ await chmod2(keyPath, 384);
6694
+ return "plaintext";
6695
+ }
6672
6696
  async function getMasterKey() {
6673
6697
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
6674
6698
  if (nativeValue) {
@@ -6720,6 +6744,20 @@ async function getMasterKey() {
6720
6744
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
6721
6745
  if (migrated) {
6722
6746
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
6747
+ try {
6748
+ await unlink(keyPath);
6749
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
6750
+ } catch {
6751
+ }
6752
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
6753
+ const fallback = await writeMachineBoundFileFallback(b64Value);
6754
+ if (fallback === "encrypted") {
6755
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
6756
+ } else {
6757
+ process.stderr.write(
6758
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
6759
+ );
6760
+ }
6723
6761
  }
6724
6762
  return key;
6725
6763
  } catch (err) {
@@ -6937,6 +6975,7 @@ var init_memory_write_governor = __esm({
6937
6975
  // src/lib/shard-manager.ts
6938
6976
  var shard_manager_exports = {};
6939
6977
  __export(shard_manager_exports, {
6978
+ auditShardHealth: () => auditShardHealth,
6940
6979
  disposeShards: () => disposeShards,
6941
6980
  ensureShardSchema: () => ensureShardSchema,
6942
6981
  getOpenShardCount: () => getOpenShardCount,
@@ -7003,6 +7042,70 @@ function listShards() {
7003
7042
  if (!existsSync16(SHARDS_DIR)) return [];
7004
7043
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
7005
7044
  }
7045
+ async function auditShardHealth(options = {}) {
7046
+ if (!_encryptionKey) {
7047
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
7048
+ }
7049
+ const repair = options.repair === true;
7050
+ const dryRun = options.dryRun === true;
7051
+ const names = listShards();
7052
+ const shards = [];
7053
+ for (const name of names) {
7054
+ const dbPath = path19.join(SHARDS_DIR, `${name}.db`);
7055
+ const stat = statSync2(dbPath);
7056
+ const item = {
7057
+ name,
7058
+ path: dbPath,
7059
+ ok: false,
7060
+ unreadable: false,
7061
+ error: null,
7062
+ size: stat.size,
7063
+ mtime: stat.mtime.toISOString(),
7064
+ memoryCount: null
7065
+ };
7066
+ const client = createClient2({
7067
+ url: `file:${dbPath}`,
7068
+ encryptionKey: _encryptionKey
7069
+ });
7070
+ try {
7071
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
7072
+ const hasMemories = await client.execute(
7073
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
7074
+ );
7075
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
7076
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
7077
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
7078
+ }
7079
+ item.ok = true;
7080
+ } catch (err) {
7081
+ const message = err instanceof Error ? err.message : String(err);
7082
+ item.error = message;
7083
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
7084
+ if (item.unreadable && repair && !dryRun) {
7085
+ client.close();
7086
+ _shards.delete(name);
7087
+ _shardLastAccess.delete(name);
7088
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7089
+ const archivedPath = path19.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
7090
+ renameSync4(dbPath, archivedPath);
7091
+ item.archivedPath = archivedPath;
7092
+ }
7093
+ } finally {
7094
+ try {
7095
+ client.close();
7096
+ } catch {
7097
+ }
7098
+ }
7099
+ shards.push(item);
7100
+ }
7101
+ return {
7102
+ total: shards.length,
7103
+ ok: shards.filter((s) => s.ok).length,
7104
+ unreadable: shards.filter((s) => s.unreadable).length,
7105
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
7106
+ shards
7107
+ };
7108
+ }
7006
7109
  async function ensureShardSchema(client) {
7007
7110
  await client.execute("PRAGMA journal_mode = WAL");
7008
7111
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -2888,6 +2888,15 @@ function readMachineId() {
2888
2888
  return "";
2889
2889
  }
2890
2890
  }
2891
+ function encryptWithMachineKey(plaintext, machineKey) {
2892
+ const crypto4 = __require("crypto");
2893
+ const iv = crypto4.randomBytes(12);
2894
+ const cipher = crypto4.createCipheriv("aes-256-gcm", machineKey, iv);
2895
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2896
+ encrypted += cipher.final("base64");
2897
+ const authTag = cipher.getAuthTag().toString("base64");
2898
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2899
+ }
2891
2900
  function decryptWithMachineKey(encrypted, machineKey) {
2892
2901
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2893
2902
  try {
@@ -2906,6 +2915,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
2906
2915
  return null;
2907
2916
  }
2908
2917
  }
2918
+ async function writeMachineBoundFileFallback(b64) {
2919
+ const dir = getKeyDir();
2920
+ await mkdir3(dir, { recursive: true });
2921
+ const keyPath = getKeyPath();
2922
+ const machineKey = deriveMachineKey();
2923
+ if (machineKey) {
2924
+ const encrypted = encryptWithMachineKey(b64, machineKey);
2925
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
2926
+ await chmod2(keyPath, 384);
2927
+ return "encrypted";
2928
+ }
2929
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
2930
+ await chmod2(keyPath, 384);
2931
+ return "plaintext";
2932
+ }
2909
2933
  async function getMasterKey() {
2910
2934
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
2911
2935
  if (nativeValue) {
@@ -2957,6 +2981,20 @@ async function getMasterKey() {
2957
2981
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2958
2982
  if (migrated) {
2959
2983
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
2984
+ try {
2985
+ await unlink(keyPath);
2986
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
2987
+ } catch {
2988
+ }
2989
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
2990
+ const fallback = await writeMachineBoundFileFallback(b64Value);
2991
+ if (fallback === "encrypted") {
2992
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
2993
+ } else {
2994
+ process.stderr.write(
2995
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
2996
+ );
2997
+ }
2960
2998
  }
2961
2999
  return key;
2962
3000
  } catch (err) {
@@ -3229,6 +3267,7 @@ var init_memory_write_governor = __esm({
3229
3267
  // src/lib/shard-manager.ts
3230
3268
  var shard_manager_exports = {};
3231
3269
  __export(shard_manager_exports, {
3270
+ auditShardHealth: () => auditShardHealth,
3232
3271
  disposeShards: () => disposeShards,
3233
3272
  ensureShardSchema: () => ensureShardSchema,
3234
3273
  getOpenShardCount: () => getOpenShardCount,
@@ -3295,6 +3334,70 @@ function listShards() {
3295
3334
  if (!existsSync7(SHARDS_DIR)) return [];
3296
3335
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3297
3336
  }
3337
+ async function auditShardHealth(options = {}) {
3338
+ if (!_encryptionKey) {
3339
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3340
+ }
3341
+ const repair = options.repair === true;
3342
+ const dryRun = options.dryRun === true;
3343
+ const names = listShards();
3344
+ const shards = [];
3345
+ for (const name of names) {
3346
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3347
+ const stat = statSync2(dbPath);
3348
+ const item = {
3349
+ name,
3350
+ path: dbPath,
3351
+ ok: false,
3352
+ unreadable: false,
3353
+ error: null,
3354
+ size: stat.size,
3355
+ mtime: stat.mtime.toISOString(),
3356
+ memoryCount: null
3357
+ };
3358
+ const client = createClient2({
3359
+ url: `file:${dbPath}`,
3360
+ encryptionKey: _encryptionKey
3361
+ });
3362
+ try {
3363
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3364
+ const hasMemories = await client.execute(
3365
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3366
+ );
3367
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3368
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3369
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3370
+ }
3371
+ item.ok = true;
3372
+ } catch (err) {
3373
+ const message = err instanceof Error ? err.message : String(err);
3374
+ item.error = message;
3375
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3376
+ if (item.unreadable && repair && !dryRun) {
3377
+ client.close();
3378
+ _shards.delete(name);
3379
+ _shardLastAccess.delete(name);
3380
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3381
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3382
+ renameSync3(dbPath, archivedPath);
3383
+ item.archivedPath = archivedPath;
3384
+ }
3385
+ } finally {
3386
+ try {
3387
+ client.close();
3388
+ } catch {
3389
+ }
3390
+ }
3391
+ shards.push(item);
3392
+ }
3393
+ return {
3394
+ total: shards.length,
3395
+ ok: shards.filter((s) => s.ok).length,
3396
+ unreadable: shards.filter((s) => s.unreadable).length,
3397
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3398
+ shards
3399
+ };
3400
+ }
3298
3401
  async function ensureShardSchema(client) {
3299
3402
  await client.execute("PRAGMA journal_mode = WAL");
3300
3403
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3065,6 +3065,15 @@ function readMachineId() {
3065
3065
  return "";
3066
3066
  }
3067
3067
  }
3068
+ function encryptWithMachineKey(plaintext, machineKey) {
3069
+ const crypto3 = __require("crypto");
3070
+ const iv = crypto3.randomBytes(12);
3071
+ const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
3072
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3073
+ encrypted += cipher.final("base64");
3074
+ const authTag = cipher.getAuthTag().toString("base64");
3075
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3076
+ }
3068
3077
  function decryptWithMachineKey(encrypted, machineKey) {
3069
3078
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3070
3079
  try {
@@ -3083,6 +3092,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3083
3092
  return null;
3084
3093
  }
3085
3094
  }
3095
+ async function writeMachineBoundFileFallback(b64) {
3096
+ const dir = getKeyDir();
3097
+ await mkdir3(dir, { recursive: true });
3098
+ const keyPath = getKeyPath();
3099
+ const machineKey = deriveMachineKey();
3100
+ if (machineKey) {
3101
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3102
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3103
+ await chmod2(keyPath, 384);
3104
+ return "encrypted";
3105
+ }
3106
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3107
+ await chmod2(keyPath, 384);
3108
+ return "plaintext";
3109
+ }
3086
3110
  async function getMasterKey() {
3087
3111
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3088
3112
  if (nativeValue) {
@@ -3134,6 +3158,20 @@ async function getMasterKey() {
3134
3158
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3135
3159
  if (migrated) {
3136
3160
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3161
+ try {
3162
+ await unlink(keyPath);
3163
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3164
+ } catch {
3165
+ }
3166
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3167
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3168
+ if (fallback === "encrypted") {
3169
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3170
+ } else {
3171
+ process.stderr.write(
3172
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3173
+ );
3174
+ }
3137
3175
  }
3138
3176
  return key;
3139
3177
  } catch (err) {
@@ -3406,6 +3444,7 @@ var init_memory_write_governor = __esm({
3406
3444
  // src/lib/shard-manager.ts
3407
3445
  var shard_manager_exports = {};
3408
3446
  __export(shard_manager_exports, {
3447
+ auditShardHealth: () => auditShardHealth,
3409
3448
  disposeShards: () => disposeShards,
3410
3449
  ensureShardSchema: () => ensureShardSchema,
3411
3450
  getOpenShardCount: () => getOpenShardCount,
@@ -3472,6 +3511,70 @@ function listShards() {
3472
3511
  if (!existsSync10(SHARDS_DIR)) return [];
3473
3512
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3474
3513
  }
3514
+ async function auditShardHealth(options = {}) {
3515
+ if (!_encryptionKey) {
3516
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3517
+ }
3518
+ const repair = options.repair === true;
3519
+ const dryRun = options.dryRun === true;
3520
+ const names = listShards();
3521
+ const shards = [];
3522
+ for (const name of names) {
3523
+ const dbPath = path11.join(SHARDS_DIR, `${name}.db`);
3524
+ const stat = statSync2(dbPath);
3525
+ const item = {
3526
+ name,
3527
+ path: dbPath,
3528
+ ok: false,
3529
+ unreadable: false,
3530
+ error: null,
3531
+ size: stat.size,
3532
+ mtime: stat.mtime.toISOString(),
3533
+ memoryCount: null
3534
+ };
3535
+ const client = createClient2({
3536
+ url: `file:${dbPath}`,
3537
+ encryptionKey: _encryptionKey
3538
+ });
3539
+ try {
3540
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3541
+ const hasMemories = await client.execute(
3542
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3543
+ );
3544
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3545
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3546
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3547
+ }
3548
+ item.ok = true;
3549
+ } catch (err) {
3550
+ const message = err instanceof Error ? err.message : String(err);
3551
+ item.error = message;
3552
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3553
+ if (item.unreadable && repair && !dryRun) {
3554
+ client.close();
3555
+ _shards.delete(name);
3556
+ _shardLastAccess.delete(name);
3557
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3558
+ const archivedPath = path11.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3559
+ renameSync3(dbPath, archivedPath);
3560
+ item.archivedPath = archivedPath;
3561
+ }
3562
+ } finally {
3563
+ try {
3564
+ client.close();
3565
+ } catch {
3566
+ }
3567
+ }
3568
+ shards.push(item);
3569
+ }
3570
+ return {
3571
+ total: shards.length,
3572
+ ok: shards.filter((s) => s.ok).length,
3573
+ unreadable: shards.filter((s) => s.unreadable).length,
3574
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3575
+ shards
3576
+ };
3577
+ }
3475
3578
  async function ensureShardSchema(client) {
3476
3579
  await client.execute("PRAGMA journal_mode = WAL");
3477
3580
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -2899,6 +2899,15 @@ function readMachineId() {
2899
2899
  return "";
2900
2900
  }
2901
2901
  }
2902
+ function encryptWithMachineKey(plaintext, machineKey) {
2903
+ const crypto2 = __require("crypto");
2904
+ const iv = crypto2.randomBytes(12);
2905
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
2906
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2907
+ encrypted += cipher.final("base64");
2908
+ const authTag = cipher.getAuthTag().toString("base64");
2909
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2910
+ }
2902
2911
  function decryptWithMachineKey(encrypted, machineKey) {
2903
2912
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2904
2913
  try {
@@ -2917,6 +2926,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
2917
2926
  return null;
2918
2927
  }
2919
2928
  }
2929
+ async function writeMachineBoundFileFallback(b64) {
2930
+ const dir = getKeyDir();
2931
+ await mkdir3(dir, { recursive: true });
2932
+ const keyPath = getKeyPath();
2933
+ const machineKey = deriveMachineKey();
2934
+ if (machineKey) {
2935
+ const encrypted = encryptWithMachineKey(b64, machineKey);
2936
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
2937
+ await chmod2(keyPath, 384);
2938
+ return "encrypted";
2939
+ }
2940
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
2941
+ await chmod2(keyPath, 384);
2942
+ return "plaintext";
2943
+ }
2920
2944
  async function getMasterKey() {
2921
2945
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
2922
2946
  if (nativeValue) {
@@ -2968,6 +2992,20 @@ async function getMasterKey() {
2968
2992
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2969
2993
  if (migrated) {
2970
2994
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
2995
+ try {
2996
+ await unlink(keyPath);
2997
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
2998
+ } catch {
2999
+ }
3000
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3001
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3002
+ if (fallback === "encrypted") {
3003
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3004
+ } else {
3005
+ process.stderr.write(
3006
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3007
+ );
3008
+ }
2971
3009
  }
2972
3010
  return key;
2973
3011
  } catch (err) {
@@ -3240,6 +3278,7 @@ var init_memory_write_governor = __esm({
3240
3278
  // src/lib/shard-manager.ts
3241
3279
  var shard_manager_exports = {};
3242
3280
  __export(shard_manager_exports, {
3281
+ auditShardHealth: () => auditShardHealth,
3243
3282
  disposeShards: () => disposeShards,
3244
3283
  ensureShardSchema: () => ensureShardSchema,
3245
3284
  getOpenShardCount: () => getOpenShardCount,
@@ -3306,6 +3345,70 @@ function listShards() {
3306
3345
  if (!existsSync8(SHARDS_DIR)) return [];
3307
3346
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3308
3347
  }
3348
+ async function auditShardHealth(options = {}) {
3349
+ if (!_encryptionKey) {
3350
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3351
+ }
3352
+ const repair = options.repair === true;
3353
+ const dryRun = options.dryRun === true;
3354
+ const names = listShards();
3355
+ const shards = [];
3356
+ for (const name of names) {
3357
+ const dbPath = path9.join(SHARDS_DIR, `${name}.db`);
3358
+ const stat = statSync2(dbPath);
3359
+ const item = {
3360
+ name,
3361
+ path: dbPath,
3362
+ ok: false,
3363
+ unreadable: false,
3364
+ error: null,
3365
+ size: stat.size,
3366
+ mtime: stat.mtime.toISOString(),
3367
+ memoryCount: null
3368
+ };
3369
+ const client = createClient2({
3370
+ url: `file:${dbPath}`,
3371
+ encryptionKey: _encryptionKey
3372
+ });
3373
+ try {
3374
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3375
+ const hasMemories = await client.execute(
3376
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3377
+ );
3378
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3379
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3380
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3381
+ }
3382
+ item.ok = true;
3383
+ } catch (err) {
3384
+ const message = err instanceof Error ? err.message : String(err);
3385
+ item.error = message;
3386
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3387
+ if (item.unreadable && repair && !dryRun) {
3388
+ client.close();
3389
+ _shards.delete(name);
3390
+ _shardLastAccess.delete(name);
3391
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3392
+ const archivedPath = path9.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3393
+ renameSync3(dbPath, archivedPath);
3394
+ item.archivedPath = archivedPath;
3395
+ }
3396
+ } finally {
3397
+ try {
3398
+ client.close();
3399
+ } catch {
3400
+ }
3401
+ }
3402
+ shards.push(item);
3403
+ }
3404
+ return {
3405
+ total: shards.length,
3406
+ ok: shards.filter((s) => s.ok).length,
3407
+ unreadable: shards.filter((s) => s.unreadable).length,
3408
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3409
+ shards
3410
+ };
3411
+ }
3309
3412
  async function ensureShardSchema(client) {
3310
3413
  await client.execute("PRAGMA journal_mode = WAL");
3311
3414
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -2899,6 +2899,15 @@ function readMachineId() {
2899
2899
  return "";
2900
2900
  }
2901
2901
  }
2902
+ function encryptWithMachineKey(plaintext, machineKey) {
2903
+ const crypto2 = __require("crypto");
2904
+ const iv = crypto2.randomBytes(12);
2905
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
2906
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2907
+ encrypted += cipher.final("base64");
2908
+ const authTag = cipher.getAuthTag().toString("base64");
2909
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2910
+ }
2902
2911
  function decryptWithMachineKey(encrypted, machineKey) {
2903
2912
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2904
2913
  try {
@@ -2917,6 +2926,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
2917
2926
  return null;
2918
2927
  }
2919
2928
  }
2929
+ async function writeMachineBoundFileFallback(b64) {
2930
+ const dir = getKeyDir();
2931
+ await mkdir3(dir, { recursive: true });
2932
+ const keyPath = getKeyPath();
2933
+ const machineKey = deriveMachineKey();
2934
+ if (machineKey) {
2935
+ const encrypted = encryptWithMachineKey(b64, machineKey);
2936
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
2937
+ await chmod2(keyPath, 384);
2938
+ return "encrypted";
2939
+ }
2940
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
2941
+ await chmod2(keyPath, 384);
2942
+ return "plaintext";
2943
+ }
2920
2944
  async function getMasterKey() {
2921
2945
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
2922
2946
  if (nativeValue) {
@@ -2968,6 +2992,20 @@ async function getMasterKey() {
2968
2992
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2969
2993
  if (migrated) {
2970
2994
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
2995
+ try {
2996
+ await unlink(keyPath);
2997
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
2998
+ } catch {
2999
+ }
3000
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3001
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3002
+ if (fallback === "encrypted") {
3003
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3004
+ } else {
3005
+ process.stderr.write(
3006
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3007
+ );
3008
+ }
2971
3009
  }
2972
3010
  return key;
2973
3011
  } catch (err) {
@@ -3240,6 +3278,7 @@ var init_memory_write_governor = __esm({
3240
3278
  // src/lib/shard-manager.ts
3241
3279
  var shard_manager_exports = {};
3242
3280
  __export(shard_manager_exports, {
3281
+ auditShardHealth: () => auditShardHealth,
3243
3282
  disposeShards: () => disposeShards,
3244
3283
  ensureShardSchema: () => ensureShardSchema,
3245
3284
  getOpenShardCount: () => getOpenShardCount,
@@ -3306,6 +3345,70 @@ function listShards() {
3306
3345
  if (!existsSync7(SHARDS_DIR)) return [];
3307
3346
  return readdirSync2(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3308
3347
  }
3348
+ async function auditShardHealth(options = {}) {
3349
+ if (!_encryptionKey) {
3350
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3351
+ }
3352
+ const repair = options.repair === true;
3353
+ const dryRun = options.dryRun === true;
3354
+ const names = listShards();
3355
+ const shards = [];
3356
+ for (const name of names) {
3357
+ const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
3358
+ const stat = statSync2(dbPath);
3359
+ const item = {
3360
+ name,
3361
+ path: dbPath,
3362
+ ok: false,
3363
+ unreadable: false,
3364
+ error: null,
3365
+ size: stat.size,
3366
+ mtime: stat.mtime.toISOString(),
3367
+ memoryCount: null
3368
+ };
3369
+ const client = createClient2({
3370
+ url: `file:${dbPath}`,
3371
+ encryptionKey: _encryptionKey
3372
+ });
3373
+ try {
3374
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3375
+ const hasMemories = await client.execute(
3376
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3377
+ );
3378
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3379
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3380
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3381
+ }
3382
+ item.ok = true;
3383
+ } catch (err) {
3384
+ const message = err instanceof Error ? err.message : String(err);
3385
+ item.error = message;
3386
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3387
+ if (item.unreadable && repair && !dryRun) {
3388
+ client.close();
3389
+ _shards.delete(name);
3390
+ _shardLastAccess.delete(name);
3391
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3392
+ const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3393
+ renameSync3(dbPath, archivedPath);
3394
+ item.archivedPath = archivedPath;
3395
+ }
3396
+ } finally {
3397
+ try {
3398
+ client.close();
3399
+ } catch {
3400
+ }
3401
+ }
3402
+ shards.push(item);
3403
+ }
3404
+ return {
3405
+ total: shards.length,
3406
+ ok: shards.filter((s) => s.ok).length,
3407
+ unreadable: shards.filter((s) => s.unreadable).length,
3408
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3409
+ shards
3410
+ };
3411
+ }
3309
3412
  async function ensureShardSchema(client) {
3310
3413
  await client.execute("PRAGMA journal_mode = WAL");
3311
3414
  await client.execute("PRAGMA busy_timeout = 30000");