@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
@@ -3382,6 +3382,15 @@ function readMachineId() {
3382
3382
  return "";
3383
3383
  }
3384
3384
  }
3385
+ function encryptWithMachineKey(plaintext, machineKey) {
3386
+ const crypto3 = __require("crypto");
3387
+ const iv = crypto3.randomBytes(12);
3388
+ const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
3389
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3390
+ encrypted += cipher.final("base64");
3391
+ const authTag = cipher.getAuthTag().toString("base64");
3392
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3393
+ }
3385
3394
  function decryptWithMachineKey(encrypted, machineKey) {
3386
3395
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3387
3396
  try {
@@ -3400,6 +3409,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3400
3409
  return null;
3401
3410
  }
3402
3411
  }
3412
+ async function writeMachineBoundFileFallback(b64) {
3413
+ const dir = getKeyDir();
3414
+ await mkdir3(dir, { recursive: true });
3415
+ const keyPath = getKeyPath();
3416
+ const machineKey = deriveMachineKey();
3417
+ if (machineKey) {
3418
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3419
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3420
+ await chmod2(keyPath, 384);
3421
+ return "encrypted";
3422
+ }
3423
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3424
+ await chmod2(keyPath, 384);
3425
+ return "plaintext";
3426
+ }
3403
3427
  async function getMasterKey() {
3404
3428
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3405
3429
  if (nativeValue) {
@@ -3451,6 +3475,20 @@ async function getMasterKey() {
3451
3475
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3452
3476
  if (migrated) {
3453
3477
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3478
+ try {
3479
+ await unlink(keyPath);
3480
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3481
+ } catch {
3482
+ }
3483
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3484
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3485
+ if (fallback === "encrypted") {
3486
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3487
+ } else {
3488
+ process.stderr.write(
3489
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3490
+ );
3491
+ }
3454
3492
  }
3455
3493
  return key;
3456
3494
  } catch (err) {
@@ -3723,6 +3761,7 @@ var init_memory_write_governor = __esm({
3723
3761
  // src/lib/shard-manager.ts
3724
3762
  var shard_manager_exports = {};
3725
3763
  __export(shard_manager_exports, {
3764
+ auditShardHealth: () => auditShardHealth,
3726
3765
  disposeShards: () => disposeShards,
3727
3766
  ensureShardSchema: () => ensureShardSchema,
3728
3767
  getOpenShardCount: () => getOpenShardCount,
@@ -3789,6 +3828,70 @@ function listShards() {
3789
3828
  if (!existsSync13(SHARDS_DIR)) return [];
3790
3829
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3791
3830
  }
3831
+ async function auditShardHealth(options = {}) {
3832
+ if (!_encryptionKey) {
3833
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3834
+ }
3835
+ const repair = options.repair === true;
3836
+ const dryRun = options.dryRun === true;
3837
+ const names = listShards();
3838
+ const shards = [];
3839
+ for (const name of names) {
3840
+ const dbPath = path14.join(SHARDS_DIR, `${name}.db`);
3841
+ const stat = statSync2(dbPath);
3842
+ const item = {
3843
+ name,
3844
+ path: dbPath,
3845
+ ok: false,
3846
+ unreadable: false,
3847
+ error: null,
3848
+ size: stat.size,
3849
+ mtime: stat.mtime.toISOString(),
3850
+ memoryCount: null
3851
+ };
3852
+ const client = createClient2({
3853
+ url: `file:${dbPath}`,
3854
+ encryptionKey: _encryptionKey
3855
+ });
3856
+ try {
3857
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3858
+ const hasMemories = await client.execute(
3859
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3860
+ );
3861
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3862
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3863
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3864
+ }
3865
+ item.ok = true;
3866
+ } catch (err) {
3867
+ const message = err instanceof Error ? err.message : String(err);
3868
+ item.error = message;
3869
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3870
+ if (item.unreadable && repair && !dryRun) {
3871
+ client.close();
3872
+ _shards.delete(name);
3873
+ _shardLastAccess.delete(name);
3874
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3875
+ const archivedPath = path14.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3876
+ renameSync4(dbPath, archivedPath);
3877
+ item.archivedPath = archivedPath;
3878
+ }
3879
+ } finally {
3880
+ try {
3881
+ client.close();
3882
+ } catch {
3883
+ }
3884
+ }
3885
+ shards.push(item);
3886
+ }
3887
+ return {
3888
+ total: shards.length,
3889
+ ok: shards.filter((s) => s.ok).length,
3890
+ unreadable: shards.filter((s) => s.unreadable).length,
3891
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3892
+ shards
3893
+ };
3894
+ }
3792
3895
  async function ensureShardSchema(client) {
3793
3896
  await client.execute("PRAGMA journal_mode = WAL");
3794
3897
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3476,6 +3476,15 @@ function readMachineId() {
3476
3476
  return "";
3477
3477
  }
3478
3478
  }
3479
+ function encryptWithMachineKey(plaintext, machineKey) {
3480
+ const crypto3 = __require("crypto");
3481
+ const iv = crypto3.randomBytes(12);
3482
+ const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
3483
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3484
+ encrypted += cipher.final("base64");
3485
+ const authTag = cipher.getAuthTag().toString("base64");
3486
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3487
+ }
3479
3488
  function decryptWithMachineKey(encrypted, machineKey) {
3480
3489
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3481
3490
  try {
@@ -3494,6 +3503,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3494
3503
  return null;
3495
3504
  }
3496
3505
  }
3506
+ async function writeMachineBoundFileFallback(b64) {
3507
+ const dir = getKeyDir();
3508
+ await mkdir3(dir, { recursive: true });
3509
+ const keyPath = getKeyPath();
3510
+ const machineKey = deriveMachineKey();
3511
+ if (machineKey) {
3512
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3513
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3514
+ await chmod2(keyPath, 384);
3515
+ return "encrypted";
3516
+ }
3517
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3518
+ await chmod2(keyPath, 384);
3519
+ return "plaintext";
3520
+ }
3497
3521
  async function getMasterKey() {
3498
3522
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3499
3523
  if (nativeValue) {
@@ -3545,6 +3569,20 @@ async function getMasterKey() {
3545
3569
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3546
3570
  if (migrated) {
3547
3571
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3572
+ try {
3573
+ await unlink(keyPath);
3574
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3575
+ } catch {
3576
+ }
3577
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3578
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3579
+ if (fallback === "encrypted") {
3580
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3581
+ } else {
3582
+ process.stderr.write(
3583
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3584
+ );
3585
+ }
3548
3586
  }
3549
3587
  return key;
3550
3588
  } catch (err) {
@@ -3762,6 +3800,7 @@ var init_memory_write_governor = __esm({
3762
3800
  // src/lib/shard-manager.ts
3763
3801
  var shard_manager_exports = {};
3764
3802
  __export(shard_manager_exports, {
3803
+ auditShardHealth: () => auditShardHealth,
3765
3804
  disposeShards: () => disposeShards,
3766
3805
  ensureShardSchema: () => ensureShardSchema,
3767
3806
  getOpenShardCount: () => getOpenShardCount,
@@ -3828,6 +3867,70 @@ function listShards() {
3828
3867
  if (!existsSync14(SHARDS_DIR)) return [];
3829
3868
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3830
3869
  }
3870
+ async function auditShardHealth(options = {}) {
3871
+ if (!_encryptionKey) {
3872
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3873
+ }
3874
+ const repair = options.repair === true;
3875
+ const dryRun = options.dryRun === true;
3876
+ const names = listShards();
3877
+ const shards = [];
3878
+ for (const name of names) {
3879
+ const dbPath = path15.join(SHARDS_DIR, `${name}.db`);
3880
+ const stat = statSync2(dbPath);
3881
+ const item = {
3882
+ name,
3883
+ path: dbPath,
3884
+ ok: false,
3885
+ unreadable: false,
3886
+ error: null,
3887
+ size: stat.size,
3888
+ mtime: stat.mtime.toISOString(),
3889
+ memoryCount: null
3890
+ };
3891
+ const client = createClient2({
3892
+ url: `file:${dbPath}`,
3893
+ encryptionKey: _encryptionKey
3894
+ });
3895
+ try {
3896
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3897
+ const hasMemories = await client.execute(
3898
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3899
+ );
3900
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3901
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3902
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3903
+ }
3904
+ item.ok = true;
3905
+ } catch (err) {
3906
+ const message = err instanceof Error ? err.message : String(err);
3907
+ item.error = message;
3908
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3909
+ if (item.unreadable && repair && !dryRun) {
3910
+ client.close();
3911
+ _shards.delete(name);
3912
+ _shardLastAccess.delete(name);
3913
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3914
+ const archivedPath = path15.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3915
+ renameSync4(dbPath, archivedPath);
3916
+ item.archivedPath = archivedPath;
3917
+ }
3918
+ } finally {
3919
+ try {
3920
+ client.close();
3921
+ } catch {
3922
+ }
3923
+ }
3924
+ shards.push(item);
3925
+ }
3926
+ return {
3927
+ total: shards.length,
3928
+ ok: shards.filter((s) => s.ok).length,
3929
+ unreadable: shards.filter((s) => s.unreadable).length,
3930
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3931
+ shards
3932
+ };
3933
+ }
3831
3934
  async function ensureShardSchema(client) {
3832
3935
  await client.execute("PRAGMA journal_mode = WAL");
3833
3936
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3007,6 +3007,15 @@ function readMachineId() {
3007
3007
  return "";
3008
3008
  }
3009
3009
  }
3010
+ function encryptWithMachineKey(plaintext, machineKey) {
3011
+ const crypto2 = __require("crypto");
3012
+ const iv = crypto2.randomBytes(12);
3013
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3014
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3015
+ encrypted += cipher.final("base64");
3016
+ const authTag = cipher.getAuthTag().toString("base64");
3017
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3018
+ }
3010
3019
  function decryptWithMachineKey(encrypted, machineKey) {
3011
3020
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3012
3021
  try {
@@ -3025,6 +3034,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3025
3034
  return null;
3026
3035
  }
3027
3036
  }
3037
+ async function writeMachineBoundFileFallback(b64) {
3038
+ const dir = getKeyDir();
3039
+ await mkdir3(dir, { recursive: true });
3040
+ const keyPath = getKeyPath();
3041
+ const machineKey = deriveMachineKey();
3042
+ if (machineKey) {
3043
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3044
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3045
+ await chmod2(keyPath, 384);
3046
+ return "encrypted";
3047
+ }
3048
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3049
+ await chmod2(keyPath, 384);
3050
+ return "plaintext";
3051
+ }
3028
3052
  async function getMasterKey() {
3029
3053
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3030
3054
  if (nativeValue) {
@@ -3076,6 +3100,20 @@ async function getMasterKey() {
3076
3100
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3077
3101
  if (migrated) {
3078
3102
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3103
+ try {
3104
+ await unlink(keyPath);
3105
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3106
+ } catch {
3107
+ }
3108
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3109
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3110
+ if (fallback === "encrypted") {
3111
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3112
+ } else {
3113
+ process.stderr.write(
3114
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3115
+ );
3116
+ }
3079
3117
  }
3080
3118
  return key;
3081
3119
  } catch (err) {
@@ -3348,6 +3386,7 @@ var init_memory_write_governor = __esm({
3348
3386
  // src/lib/shard-manager.ts
3349
3387
  var shard_manager_exports = {};
3350
3388
  __export(shard_manager_exports, {
3389
+ auditShardHealth: () => auditShardHealth,
3351
3390
  disposeShards: () => disposeShards,
3352
3391
  ensureShardSchema: () => ensureShardSchema,
3353
3392
  getOpenShardCount: () => getOpenShardCount,
@@ -3414,6 +3453,70 @@ function listShards() {
3414
3453
  if (!existsSync7(SHARDS_DIR)) return [];
3415
3454
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3416
3455
  }
3456
+ async function auditShardHealth(options = {}) {
3457
+ if (!_encryptionKey) {
3458
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3459
+ }
3460
+ const repair = options.repair === true;
3461
+ const dryRun = options.dryRun === true;
3462
+ const names = listShards();
3463
+ const shards = [];
3464
+ for (const name of names) {
3465
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3466
+ const stat = statSync2(dbPath);
3467
+ const item = {
3468
+ name,
3469
+ path: dbPath,
3470
+ ok: false,
3471
+ unreadable: false,
3472
+ error: null,
3473
+ size: stat.size,
3474
+ mtime: stat.mtime.toISOString(),
3475
+ memoryCount: null
3476
+ };
3477
+ const client = createClient2({
3478
+ url: `file:${dbPath}`,
3479
+ encryptionKey: _encryptionKey
3480
+ });
3481
+ try {
3482
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3483
+ const hasMemories = await client.execute(
3484
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3485
+ );
3486
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3487
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3488
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3489
+ }
3490
+ item.ok = true;
3491
+ } catch (err) {
3492
+ const message = err instanceof Error ? err.message : String(err);
3493
+ item.error = message;
3494
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3495
+ if (item.unreadable && repair && !dryRun) {
3496
+ client.close();
3497
+ _shards.delete(name);
3498
+ _shardLastAccess.delete(name);
3499
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3500
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3501
+ renameSync3(dbPath, archivedPath);
3502
+ item.archivedPath = archivedPath;
3503
+ }
3504
+ } finally {
3505
+ try {
3506
+ client.close();
3507
+ } catch {
3508
+ }
3509
+ }
3510
+ shards.push(item);
3511
+ }
3512
+ return {
3513
+ total: shards.length,
3514
+ ok: shards.filter((s) => s.ok).length,
3515
+ unreadable: shards.filter((s) => s.unreadable).length,
3516
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3517
+ shards
3518
+ };
3519
+ }
3417
3520
  async function ensureShardSchema(client) {
3418
3521
  await client.execute("PRAGMA journal_mode = WAL");
3419
3522
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -2911,6 +2911,15 @@ function readMachineId() {
2911
2911
  return "";
2912
2912
  }
2913
2913
  }
2914
+ function encryptWithMachineKey(plaintext, machineKey) {
2915
+ const crypto2 = __require("crypto");
2916
+ const iv = crypto2.randomBytes(12);
2917
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
2918
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2919
+ encrypted += cipher.final("base64");
2920
+ const authTag = cipher.getAuthTag().toString("base64");
2921
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2922
+ }
2914
2923
  function decryptWithMachineKey(encrypted, machineKey) {
2915
2924
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2916
2925
  try {
@@ -2929,6 +2938,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
2929
2938
  return null;
2930
2939
  }
2931
2940
  }
2941
+ async function writeMachineBoundFileFallback(b64) {
2942
+ const dir = getKeyDir();
2943
+ await mkdir3(dir, { recursive: true });
2944
+ const keyPath = getKeyPath();
2945
+ const machineKey = deriveMachineKey();
2946
+ if (machineKey) {
2947
+ const encrypted = encryptWithMachineKey(b64, machineKey);
2948
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
2949
+ await chmod2(keyPath, 384);
2950
+ return "encrypted";
2951
+ }
2952
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
2953
+ await chmod2(keyPath, 384);
2954
+ return "plaintext";
2955
+ }
2932
2956
  async function getMasterKey() {
2933
2957
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
2934
2958
  if (nativeValue) {
@@ -2980,6 +3004,20 @@ async function getMasterKey() {
2980
3004
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2981
3005
  if (migrated) {
2982
3006
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3007
+ try {
3008
+ await unlink(keyPath);
3009
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3010
+ } catch {
3011
+ }
3012
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3013
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3014
+ if (fallback === "encrypted") {
3015
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3016
+ } else {
3017
+ process.stderr.write(
3018
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3019
+ );
3020
+ }
2983
3021
  }
2984
3022
  return key;
2985
3023
  } catch (err) {
@@ -3252,6 +3290,7 @@ var init_memory_write_governor = __esm({
3252
3290
  // src/lib/shard-manager.ts
3253
3291
  var shard_manager_exports = {};
3254
3292
  __export(shard_manager_exports, {
3293
+ auditShardHealth: () => auditShardHealth,
3255
3294
  disposeShards: () => disposeShards,
3256
3295
  ensureShardSchema: () => ensureShardSchema,
3257
3296
  getOpenShardCount: () => getOpenShardCount,
@@ -3318,6 +3357,70 @@ function listShards() {
3318
3357
  if (!existsSync7(SHARDS_DIR)) return [];
3319
3358
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3320
3359
  }
3360
+ async function auditShardHealth(options = {}) {
3361
+ if (!_encryptionKey) {
3362
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3363
+ }
3364
+ const repair = options.repair === true;
3365
+ const dryRun = options.dryRun === true;
3366
+ const names = listShards();
3367
+ const shards = [];
3368
+ for (const name of names) {
3369
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3370
+ const stat = statSync2(dbPath);
3371
+ const item = {
3372
+ name,
3373
+ path: dbPath,
3374
+ ok: false,
3375
+ unreadable: false,
3376
+ error: null,
3377
+ size: stat.size,
3378
+ mtime: stat.mtime.toISOString(),
3379
+ memoryCount: null
3380
+ };
3381
+ const client = createClient2({
3382
+ url: `file:${dbPath}`,
3383
+ encryptionKey: _encryptionKey
3384
+ });
3385
+ try {
3386
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3387
+ const hasMemories = await client.execute(
3388
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3389
+ );
3390
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3391
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3392
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3393
+ }
3394
+ item.ok = true;
3395
+ } catch (err) {
3396
+ const message = err instanceof Error ? err.message : String(err);
3397
+ item.error = message;
3398
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3399
+ if (item.unreadable && repair && !dryRun) {
3400
+ client.close();
3401
+ _shards.delete(name);
3402
+ _shardLastAccess.delete(name);
3403
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3404
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3405
+ renameSync3(dbPath, archivedPath);
3406
+ item.archivedPath = archivedPath;
3407
+ }
3408
+ } finally {
3409
+ try {
3410
+ client.close();
3411
+ } catch {
3412
+ }
3413
+ }
3414
+ shards.push(item);
3415
+ }
3416
+ return {
3417
+ total: shards.length,
3418
+ ok: shards.filter((s) => s.ok).length,
3419
+ unreadable: shards.filter((s) => s.unreadable).length,
3420
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3421
+ shards
3422
+ };
3423
+ }
3321
3424
  async function ensureShardSchema(client) {
3322
3425
  await client.execute("PRAGMA journal_mode = WAL");
3323
3426
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -2886,6 +2886,15 @@ function readMachineId() {
2886
2886
  return "";
2887
2887
  }
2888
2888
  }
2889
+ function encryptWithMachineKey(plaintext, machineKey) {
2890
+ const crypto3 = __require("crypto");
2891
+ const iv = crypto3.randomBytes(12);
2892
+ const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
2893
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2894
+ encrypted += cipher.final("base64");
2895
+ const authTag = cipher.getAuthTag().toString("base64");
2896
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2897
+ }
2889
2898
  function decryptWithMachineKey(encrypted, machineKey) {
2890
2899
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2891
2900
  try {
@@ -2904,6 +2913,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
2904
2913
  return null;
2905
2914
  }
2906
2915
  }
2916
+ async function writeMachineBoundFileFallback(b64) {
2917
+ const dir = getKeyDir();
2918
+ await mkdir3(dir, { recursive: true });
2919
+ const keyPath = getKeyPath();
2920
+ const machineKey = deriveMachineKey();
2921
+ if (machineKey) {
2922
+ const encrypted = encryptWithMachineKey(b64, machineKey);
2923
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
2924
+ await chmod2(keyPath, 384);
2925
+ return "encrypted";
2926
+ }
2927
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
2928
+ await chmod2(keyPath, 384);
2929
+ return "plaintext";
2930
+ }
2907
2931
  async function getMasterKey() {
2908
2932
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
2909
2933
  if (nativeValue) {
@@ -2955,6 +2979,20 @@ async function getMasterKey() {
2955
2979
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2956
2980
  if (migrated) {
2957
2981
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
2982
+ try {
2983
+ await unlink(keyPath);
2984
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
2985
+ } catch {
2986
+ }
2987
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
2988
+ const fallback = await writeMachineBoundFileFallback(b64Value);
2989
+ if (fallback === "encrypted") {
2990
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
2991
+ } else {
2992
+ process.stderr.write(
2993
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
2994
+ );
2995
+ }
2958
2996
  }
2959
2997
  return key;
2960
2998
  } catch (err) {
@@ -3227,6 +3265,7 @@ var init_memory_write_governor = __esm({
3227
3265
  // src/lib/shard-manager.ts
3228
3266
  var shard_manager_exports = {};
3229
3267
  __export(shard_manager_exports, {
3268
+ auditShardHealth: () => auditShardHealth,
3230
3269
  disposeShards: () => disposeShards,
3231
3270
  ensureShardSchema: () => ensureShardSchema,
3232
3271
  getOpenShardCount: () => getOpenShardCount,
@@ -3293,6 +3332,70 @@ function listShards() {
3293
3332
  if (!existsSync7(SHARDS_DIR)) return [];
3294
3333
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3295
3334
  }
3335
+ async function auditShardHealth(options = {}) {
3336
+ if (!_encryptionKey) {
3337
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3338
+ }
3339
+ const repair = options.repair === true;
3340
+ const dryRun = options.dryRun === true;
3341
+ const names = listShards();
3342
+ const shards = [];
3343
+ for (const name of names) {
3344
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3345
+ const stat = statSync2(dbPath);
3346
+ const item = {
3347
+ name,
3348
+ path: dbPath,
3349
+ ok: false,
3350
+ unreadable: false,
3351
+ error: null,
3352
+ size: stat.size,
3353
+ mtime: stat.mtime.toISOString(),
3354
+ memoryCount: null
3355
+ };
3356
+ const client = createClient2({
3357
+ url: `file:${dbPath}`,
3358
+ encryptionKey: _encryptionKey
3359
+ });
3360
+ try {
3361
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3362
+ const hasMemories = await client.execute(
3363
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3364
+ );
3365
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3366
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3367
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3368
+ }
3369
+ item.ok = true;
3370
+ } catch (err) {
3371
+ const message = err instanceof Error ? err.message : String(err);
3372
+ item.error = message;
3373
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3374
+ if (item.unreadable && repair && !dryRun) {
3375
+ client.close();
3376
+ _shards.delete(name);
3377
+ _shardLastAccess.delete(name);
3378
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3379
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3380
+ renameSync3(dbPath, archivedPath);
3381
+ item.archivedPath = archivedPath;
3382
+ }
3383
+ } finally {
3384
+ try {
3385
+ client.close();
3386
+ } catch {
3387
+ }
3388
+ }
3389
+ shards.push(item);
3390
+ }
3391
+ return {
3392
+ total: shards.length,
3393
+ ok: shards.filter((s) => s.ok).length,
3394
+ unreadable: shards.filter((s) => s.unreadable).length,
3395
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3396
+ shards
3397
+ };
3398
+ }
3296
3399
  async function ensureShardSchema(client) {
3297
3400
  await client.execute("PRAGMA journal_mode = WAL");
3298
3401
  await client.execute("PRAGMA busy_timeout = 30000");