@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
@@ -6586,6 +6586,15 @@ function readMachineId() {
6586
6586
  return "";
6587
6587
  }
6588
6588
  }
6589
+ function encryptWithMachineKey(plaintext, machineKey) {
6590
+ const crypto7 = __require("crypto");
6591
+ const iv = crypto7.randomBytes(12);
6592
+ const cipher = crypto7.createCipheriv("aes-256-gcm", machineKey, iv);
6593
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
6594
+ encrypted += cipher.final("base64");
6595
+ const authTag = cipher.getAuthTag().toString("base64");
6596
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
6597
+ }
6589
6598
  function decryptWithMachineKey(encrypted, machineKey) {
6590
6599
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
6591
6600
  try {
@@ -6604,6 +6613,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
6604
6613
  return null;
6605
6614
  }
6606
6615
  }
6616
+ async function writeMachineBoundFileFallback(b64) {
6617
+ const dir = getKeyDir();
6618
+ await mkdir4(dir, { recursive: true });
6619
+ const keyPath = getKeyPath();
6620
+ const machineKey = deriveMachineKey();
6621
+ if (machineKey) {
6622
+ const encrypted = encryptWithMachineKey(b64, machineKey);
6623
+ await writeFile5(keyPath, encrypted + "\n", "utf-8");
6624
+ await chmod2(keyPath, 384);
6625
+ return "encrypted";
6626
+ }
6627
+ await writeFile5(keyPath, b64 + "\n", "utf-8");
6628
+ await chmod2(keyPath, 384);
6629
+ return "plaintext";
6630
+ }
6607
6631
  async function getMasterKey() {
6608
6632
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
6609
6633
  if (nativeValue) {
@@ -6655,6 +6679,20 @@ async function getMasterKey() {
6655
6679
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
6656
6680
  if (migrated) {
6657
6681
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
6682
+ try {
6683
+ await unlink(keyPath);
6684
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
6685
+ } catch {
6686
+ }
6687
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
6688
+ const fallback = await writeMachineBoundFileFallback(b64Value);
6689
+ if (fallback === "encrypted") {
6690
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
6691
+ } else {
6692
+ process.stderr.write(
6693
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
6694
+ );
6695
+ }
6658
6696
  }
6659
6697
  return key;
6660
6698
  } catch (err) {
@@ -6872,6 +6910,7 @@ var init_memory_write_governor = __esm({
6872
6910
  // src/lib/shard-manager.ts
6873
6911
  var shard_manager_exports = {};
6874
6912
  __export(shard_manager_exports, {
6913
+ auditShardHealth: () => auditShardHealth,
6875
6914
  disposeShards: () => disposeShards,
6876
6915
  ensureShardSchema: () => ensureShardSchema,
6877
6916
  getOpenShardCount: () => getOpenShardCount,
@@ -6938,6 +6977,70 @@ function listShards() {
6938
6977
  if (!existsSync16(SHARDS_DIR)) return [];
6939
6978
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
6940
6979
  }
6980
+ async function auditShardHealth(options = {}) {
6981
+ if (!_encryptionKey) {
6982
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
6983
+ }
6984
+ const repair = options.repair === true;
6985
+ const dryRun2 = options.dryRun === true;
6986
+ const names = listShards();
6987
+ const shards = [];
6988
+ for (const name of names) {
6989
+ const dbPath = path19.join(SHARDS_DIR, `${name}.db`);
6990
+ const stat = statSync2(dbPath);
6991
+ const item = {
6992
+ name,
6993
+ path: dbPath,
6994
+ ok: false,
6995
+ unreadable: false,
6996
+ error: null,
6997
+ size: stat.size,
6998
+ mtime: stat.mtime.toISOString(),
6999
+ memoryCount: null
7000
+ };
7001
+ const client = createClient2({
7002
+ url: `file:${dbPath}`,
7003
+ encryptionKey: _encryptionKey
7004
+ });
7005
+ try {
7006
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
7007
+ const hasMemories = await client.execute(
7008
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
7009
+ );
7010
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
7011
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
7012
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
7013
+ }
7014
+ item.ok = true;
7015
+ } catch (err) {
7016
+ const message = err instanceof Error ? err.message : String(err);
7017
+ item.error = message;
7018
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
7019
+ if (item.unreadable && repair && !dryRun2) {
7020
+ client.close();
7021
+ _shards.delete(name);
7022
+ _shardLastAccess.delete(name);
7023
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7024
+ const archivedPath = path19.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
7025
+ renameSync4(dbPath, archivedPath);
7026
+ item.archivedPath = archivedPath;
7027
+ }
7028
+ } finally {
7029
+ try {
7030
+ client.close();
7031
+ } catch {
7032
+ }
7033
+ }
7034
+ shards.push(item);
7035
+ }
7036
+ return {
7037
+ total: shards.length,
7038
+ ok: shards.filter((s) => s.ok).length,
7039
+ unreadable: shards.filter((s) => s.unreadable).length,
7040
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
7041
+ shards
7042
+ };
7043
+ }
6941
7044
  async function ensureShardSchema(client) {
6942
7045
  await client.execute("PRAGMA journal_mode = WAL");
6943
7046
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -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 crypto3 = __require("crypto");
3364
+ const iv = crypto3.randomBytes(12);
3365
+ const cipher = crypto3.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) {
@@ -2897,6 +2897,15 @@ function readMachineId() {
2897
2897
  return "";
2898
2898
  }
2899
2899
  }
2900
+ function encryptWithMachineKey(plaintext, machineKey) {
2901
+ const crypto2 = __require("crypto");
2902
+ const iv = crypto2.randomBytes(12);
2903
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
2904
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2905
+ encrypted += cipher.final("base64");
2906
+ const authTag = cipher.getAuthTag().toString("base64");
2907
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2908
+ }
2900
2909
  function decryptWithMachineKey(encrypted, machineKey) {
2901
2910
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2902
2911
  try {
@@ -2915,6 +2924,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
2915
2924
  return null;
2916
2925
  }
2917
2926
  }
2927
+ async function writeMachineBoundFileFallback(b64) {
2928
+ const dir = getKeyDir();
2929
+ await mkdir3(dir, { recursive: true });
2930
+ const keyPath = getKeyPath();
2931
+ const machineKey = deriveMachineKey();
2932
+ if (machineKey) {
2933
+ const encrypted = encryptWithMachineKey(b64, machineKey);
2934
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
2935
+ await chmod2(keyPath, 384);
2936
+ return "encrypted";
2937
+ }
2938
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
2939
+ await chmod2(keyPath, 384);
2940
+ return "plaintext";
2941
+ }
2918
2942
  async function getMasterKey() {
2919
2943
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
2920
2944
  if (nativeValue) {
@@ -2966,6 +2990,20 @@ async function getMasterKey() {
2966
2990
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2967
2991
  if (migrated) {
2968
2992
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
2993
+ try {
2994
+ await unlink(keyPath);
2995
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
2996
+ } catch {
2997
+ }
2998
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
2999
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3000
+ if (fallback === "encrypted") {
3001
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3002
+ } else {
3003
+ process.stderr.write(
3004
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3005
+ );
3006
+ }
2969
3007
  }
2970
3008
  return key;
2971
3009
  } catch (err) {
@@ -3238,6 +3276,7 @@ var init_memory_write_governor = __esm({
3238
3276
  // src/lib/shard-manager.ts
3239
3277
  var shard_manager_exports = {};
3240
3278
  __export(shard_manager_exports, {
3279
+ auditShardHealth: () => auditShardHealth,
3241
3280
  disposeShards: () => disposeShards,
3242
3281
  ensureShardSchema: () => ensureShardSchema,
3243
3282
  getOpenShardCount: () => getOpenShardCount,
@@ -3304,6 +3343,70 @@ function listShards() {
3304
3343
  if (!existsSync7(SHARDS_DIR)) return [];
3305
3344
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3306
3345
  }
3346
+ async function auditShardHealth(options = {}) {
3347
+ if (!_encryptionKey) {
3348
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3349
+ }
3350
+ const repair = options.repair === true;
3351
+ const dryRun = options.dryRun === true;
3352
+ const names = listShards();
3353
+ const shards = [];
3354
+ for (const name of names) {
3355
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3356
+ const stat = statSync2(dbPath);
3357
+ const item = {
3358
+ name,
3359
+ path: dbPath,
3360
+ ok: false,
3361
+ unreadable: false,
3362
+ error: null,
3363
+ size: stat.size,
3364
+ mtime: stat.mtime.toISOString(),
3365
+ memoryCount: null
3366
+ };
3367
+ const client = createClient2({
3368
+ url: `file:${dbPath}`,
3369
+ encryptionKey: _encryptionKey
3370
+ });
3371
+ try {
3372
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3373
+ const hasMemories = await client.execute(
3374
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3375
+ );
3376
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3377
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3378
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3379
+ }
3380
+ item.ok = true;
3381
+ } catch (err) {
3382
+ const message = err instanceof Error ? err.message : String(err);
3383
+ item.error = message;
3384
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3385
+ if (item.unreadable && repair && !dryRun) {
3386
+ client.close();
3387
+ _shards.delete(name);
3388
+ _shardLastAccess.delete(name);
3389
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3390
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3391
+ renameSync3(dbPath, archivedPath);
3392
+ item.archivedPath = archivedPath;
3393
+ }
3394
+ } finally {
3395
+ try {
3396
+ client.close();
3397
+ } catch {
3398
+ }
3399
+ }
3400
+ shards.push(item);
3401
+ }
3402
+ return {
3403
+ total: shards.length,
3404
+ ok: shards.filter((s) => s.ok).length,
3405
+ unreadable: shards.filter((s) => s.unreadable).length,
3406
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3407
+ shards
3408
+ };
3409
+ }
3307
3410
  async function ensureShardSchema(client) {
3308
3411
  await client.execute("PRAGMA journal_mode = WAL");
3309
3412
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -2996,6 +2996,15 @@ function readMachineId() {
2996
2996
  return "";
2997
2997
  }
2998
2998
  }
2999
+ function encryptWithMachineKey(plaintext, machineKey) {
3000
+ const crypto8 = __require("crypto");
3001
+ const iv = crypto8.randomBytes(12);
3002
+ const cipher = crypto8.createCipheriv("aes-256-gcm", machineKey, iv);
3003
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3004
+ encrypted += cipher.final("base64");
3005
+ const authTag = cipher.getAuthTag().toString("base64");
3006
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3007
+ }
2999
3008
  function decryptWithMachineKey(encrypted, machineKey) {
3000
3009
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3001
3010
  try {
@@ -3014,6 +3023,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3014
3023
  return null;
3015
3024
  }
3016
3025
  }
3026
+ async function writeMachineBoundFileFallback(b64) {
3027
+ const dir = getKeyDir();
3028
+ await mkdir3(dir, { recursive: true });
3029
+ const keyPath = getKeyPath();
3030
+ const machineKey = deriveMachineKey();
3031
+ if (machineKey) {
3032
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3033
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3034
+ await chmod2(keyPath, 384);
3035
+ return "encrypted";
3036
+ }
3037
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3038
+ await chmod2(keyPath, 384);
3039
+ return "plaintext";
3040
+ }
3017
3041
  async function getMasterKey() {
3018
3042
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3019
3043
  if (nativeValue) {
@@ -3065,6 +3089,20 @@ async function getMasterKey() {
3065
3089
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3066
3090
  if (migrated) {
3067
3091
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3092
+ try {
3093
+ await unlink(keyPath);
3094
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3095
+ } catch {
3096
+ }
3097
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3098
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3099
+ if (fallback === "encrypted") {
3100
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3101
+ } else {
3102
+ process.stderr.write(
3103
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3104
+ );
3105
+ }
3068
3106
  }
3069
3107
  return key;
3070
3108
  } catch (err) {
@@ -3337,6 +3375,7 @@ var init_memory_write_governor = __esm({
3337
3375
  // src/lib/shard-manager.ts
3338
3376
  var shard_manager_exports = {};
3339
3377
  __export(shard_manager_exports, {
3378
+ auditShardHealth: () => auditShardHealth,
3340
3379
  disposeShards: () => disposeShards,
3341
3380
  ensureShardSchema: () => ensureShardSchema,
3342
3381
  getOpenShardCount: () => getOpenShardCount,
@@ -3403,6 +3442,70 @@ function listShards() {
3403
3442
  if (!existsSync7(SHARDS_DIR)) return [];
3404
3443
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3405
3444
  }
3445
+ async function auditShardHealth(options = {}) {
3446
+ if (!_encryptionKey) {
3447
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3448
+ }
3449
+ const repair = options.repair === true;
3450
+ const dryRun = options.dryRun === true;
3451
+ const names = listShards();
3452
+ const shards = [];
3453
+ for (const name of names) {
3454
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3455
+ const stat = statSync2(dbPath);
3456
+ const item = {
3457
+ name,
3458
+ path: dbPath,
3459
+ ok: false,
3460
+ unreadable: false,
3461
+ error: null,
3462
+ size: stat.size,
3463
+ mtime: stat.mtime.toISOString(),
3464
+ memoryCount: null
3465
+ };
3466
+ const client = createClient2({
3467
+ url: `file:${dbPath}`,
3468
+ encryptionKey: _encryptionKey
3469
+ });
3470
+ try {
3471
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3472
+ const hasMemories = await client.execute(
3473
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3474
+ );
3475
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3476
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3477
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3478
+ }
3479
+ item.ok = true;
3480
+ } catch (err) {
3481
+ const message = err instanceof Error ? err.message : String(err);
3482
+ item.error = message;
3483
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3484
+ if (item.unreadable && repair && !dryRun) {
3485
+ client.close();
3486
+ _shards.delete(name);
3487
+ _shardLastAccess.delete(name);
3488
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3489
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3490
+ renameSync3(dbPath, archivedPath);
3491
+ item.archivedPath = archivedPath;
3492
+ }
3493
+ } finally {
3494
+ try {
3495
+ client.close();
3496
+ } catch {
3497
+ }
3498
+ }
3499
+ shards.push(item);
3500
+ }
3501
+ return {
3502
+ total: shards.length,
3503
+ ok: shards.filter((s) => s.ok).length,
3504
+ unreadable: shards.filter((s) => s.unreadable).length,
3505
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3506
+ shards
3507
+ };
3508
+ }
3406
3509
  async function ensureShardSchema(client) {
3407
3510
  await client.execute("PRAGMA journal_mode = WAL");
3408
3511
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -6657,6 +6657,15 @@ function readMachineId() {
6657
6657
  return "";
6658
6658
  }
6659
6659
  }
6660
+ function encryptWithMachineKey(plaintext, machineKey) {
6661
+ const crypto7 = __require("crypto");
6662
+ const iv = crypto7.randomBytes(12);
6663
+ const cipher = crypto7.createCipheriv("aes-256-gcm", machineKey, iv);
6664
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
6665
+ encrypted += cipher.final("base64");
6666
+ const authTag = cipher.getAuthTag().toString("base64");
6667
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
6668
+ }
6660
6669
  function decryptWithMachineKey(encrypted, machineKey) {
6661
6670
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
6662
6671
  try {
@@ -6675,6 +6684,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
6675
6684
  return null;
6676
6685
  }
6677
6686
  }
6687
+ async function writeMachineBoundFileFallback(b64) {
6688
+ const dir = getKeyDir();
6689
+ await mkdir4(dir, { recursive: true });
6690
+ const keyPath = getKeyPath();
6691
+ const machineKey = deriveMachineKey();
6692
+ if (machineKey) {
6693
+ const encrypted = encryptWithMachineKey(b64, machineKey);
6694
+ await writeFile5(keyPath, encrypted + "\n", "utf-8");
6695
+ await chmod2(keyPath, 384);
6696
+ return "encrypted";
6697
+ }
6698
+ await writeFile5(keyPath, b64 + "\n", "utf-8");
6699
+ await chmod2(keyPath, 384);
6700
+ return "plaintext";
6701
+ }
6678
6702
  async function getMasterKey() {
6679
6703
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
6680
6704
  if (nativeValue) {
@@ -6726,6 +6750,20 @@ async function getMasterKey() {
6726
6750
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
6727
6751
  if (migrated) {
6728
6752
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
6753
+ try {
6754
+ await unlink(keyPath);
6755
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
6756
+ } catch {
6757
+ }
6758
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
6759
+ const fallback = await writeMachineBoundFileFallback(b64Value);
6760
+ if (fallback === "encrypted") {
6761
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
6762
+ } else {
6763
+ process.stderr.write(
6764
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
6765
+ );
6766
+ }
6729
6767
  }
6730
6768
  return key;
6731
6769
  } catch (err) {
@@ -6943,6 +6981,7 @@ var init_memory_write_governor = __esm({
6943
6981
  // src/lib/shard-manager.ts
6944
6982
  var shard_manager_exports = {};
6945
6983
  __export(shard_manager_exports, {
6984
+ auditShardHealth: () => auditShardHealth,
6946
6985
  disposeShards: () => disposeShards,
6947
6986
  ensureShardSchema: () => ensureShardSchema,
6948
6987
  getOpenShardCount: () => getOpenShardCount,
@@ -7009,6 +7048,70 @@ function listShards() {
7009
7048
  if (!existsSync16(SHARDS_DIR)) return [];
7010
7049
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
7011
7050
  }
7051
+ async function auditShardHealth(options = {}) {
7052
+ if (!_encryptionKey) {
7053
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
7054
+ }
7055
+ const repair = options.repair === true;
7056
+ const dryRun = options.dryRun === true;
7057
+ const names = listShards();
7058
+ const shards = [];
7059
+ for (const name of names) {
7060
+ const dbPath = path19.join(SHARDS_DIR, `${name}.db`);
7061
+ const stat = statSync2(dbPath);
7062
+ const item = {
7063
+ name,
7064
+ path: dbPath,
7065
+ ok: false,
7066
+ unreadable: false,
7067
+ error: null,
7068
+ size: stat.size,
7069
+ mtime: stat.mtime.toISOString(),
7070
+ memoryCount: null
7071
+ };
7072
+ const client = createClient2({
7073
+ url: `file:${dbPath}`,
7074
+ encryptionKey: _encryptionKey
7075
+ });
7076
+ try {
7077
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
7078
+ const hasMemories = await client.execute(
7079
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
7080
+ );
7081
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
7082
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
7083
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
7084
+ }
7085
+ item.ok = true;
7086
+ } catch (err) {
7087
+ const message = err instanceof Error ? err.message : String(err);
7088
+ item.error = message;
7089
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
7090
+ if (item.unreadable && repair && !dryRun) {
7091
+ client.close();
7092
+ _shards.delete(name);
7093
+ _shardLastAccess.delete(name);
7094
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7095
+ const archivedPath = path19.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
7096
+ renameSync4(dbPath, archivedPath);
7097
+ item.archivedPath = archivedPath;
7098
+ }
7099
+ } finally {
7100
+ try {
7101
+ client.close();
7102
+ } catch {
7103
+ }
7104
+ }
7105
+ shards.push(item);
7106
+ }
7107
+ return {
7108
+ total: shards.length,
7109
+ ok: shards.filter((s) => s.ok).length,
7110
+ unreadable: shards.filter((s) => s.unreadable).length,
7111
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
7112
+ shards
7113
+ };
7114
+ }
7012
7115
  async function ensureShardSchema(client) {
7013
7116
  await client.execute("PRAGMA journal_mode = WAL");
7014
7117
  await client.execute("PRAGMA busy_timeout = 30000");