@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
@@ -2930,6 +2930,15 @@ function readMachineId() {
2930
2930
  return "";
2931
2931
  }
2932
2932
  }
2933
+ function encryptWithMachineKey(plaintext, machineKey) {
2934
+ const crypto8 = __require("crypto");
2935
+ const iv = crypto8.randomBytes(12);
2936
+ const cipher = crypto8.createCipheriv("aes-256-gcm", machineKey, iv);
2937
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2938
+ encrypted += cipher.final("base64");
2939
+ const authTag = cipher.getAuthTag().toString("base64");
2940
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2941
+ }
2933
2942
  function decryptWithMachineKey(encrypted, machineKey) {
2934
2943
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2935
2944
  try {
@@ -2948,6 +2957,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
2948
2957
  return null;
2949
2958
  }
2950
2959
  }
2960
+ async function writeMachineBoundFileFallback(b64) {
2961
+ const dir = getKeyDir();
2962
+ await mkdir3(dir, { recursive: true });
2963
+ const keyPath = getKeyPath();
2964
+ const machineKey = deriveMachineKey();
2965
+ if (machineKey) {
2966
+ const encrypted = encryptWithMachineKey(b64, machineKey);
2967
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
2968
+ await chmod2(keyPath, 384);
2969
+ return "encrypted";
2970
+ }
2971
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
2972
+ await chmod2(keyPath, 384);
2973
+ return "plaintext";
2974
+ }
2951
2975
  async function getMasterKey() {
2952
2976
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
2953
2977
  if (nativeValue) {
@@ -2999,6 +3023,20 @@ async function getMasterKey() {
2999
3023
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3000
3024
  if (migrated) {
3001
3025
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3026
+ try {
3027
+ await unlink(keyPath);
3028
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3029
+ } catch {
3030
+ }
3031
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3032
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3033
+ if (fallback === "encrypted") {
3034
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3035
+ } else {
3036
+ process.stderr.write(
3037
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3038
+ );
3039
+ }
3002
3040
  }
3003
3041
  return key;
3004
3042
  } catch (err) {
@@ -3271,6 +3309,7 @@ var init_memory_write_governor = __esm({
3271
3309
  // src/lib/shard-manager.ts
3272
3310
  var shard_manager_exports = {};
3273
3311
  __export(shard_manager_exports, {
3312
+ auditShardHealth: () => auditShardHealth,
3274
3313
  disposeShards: () => disposeShards,
3275
3314
  ensureShardSchema: () => ensureShardSchema,
3276
3315
  getOpenShardCount: () => getOpenShardCount,
@@ -3337,6 +3376,70 @@ function listShards() {
3337
3376
  if (!existsSync7(SHARDS_DIR)) return [];
3338
3377
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3339
3378
  }
3379
+ async function auditShardHealth(options = {}) {
3380
+ if (!_encryptionKey) {
3381
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3382
+ }
3383
+ const repair = options.repair === true;
3384
+ const dryRun = options.dryRun === true;
3385
+ const names = listShards();
3386
+ const shards = [];
3387
+ for (const name of names) {
3388
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3389
+ const stat = statSync2(dbPath);
3390
+ const item = {
3391
+ name,
3392
+ path: dbPath,
3393
+ ok: false,
3394
+ unreadable: false,
3395
+ error: null,
3396
+ size: stat.size,
3397
+ mtime: stat.mtime.toISOString(),
3398
+ memoryCount: null
3399
+ };
3400
+ const client = createClient2({
3401
+ url: `file:${dbPath}`,
3402
+ encryptionKey: _encryptionKey
3403
+ });
3404
+ try {
3405
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3406
+ const hasMemories = await client.execute(
3407
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3408
+ );
3409
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3410
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3411
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3412
+ }
3413
+ item.ok = true;
3414
+ } catch (err) {
3415
+ const message = err instanceof Error ? err.message : String(err);
3416
+ item.error = message;
3417
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3418
+ if (item.unreadable && repair && !dryRun) {
3419
+ client.close();
3420
+ _shards.delete(name);
3421
+ _shardLastAccess.delete(name);
3422
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3423
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3424
+ renameSync3(dbPath, archivedPath);
3425
+ item.archivedPath = archivedPath;
3426
+ }
3427
+ } finally {
3428
+ try {
3429
+ client.close();
3430
+ } catch {
3431
+ }
3432
+ }
3433
+ shards.push(item);
3434
+ }
3435
+ return {
3436
+ total: shards.length,
3437
+ ok: shards.filter((s) => s.ok).length,
3438
+ unreadable: shards.filter((s) => s.unreadable).length,
3439
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3440
+ shards
3441
+ };
3442
+ }
3340
3443
  async function ensureShardSchema(client) {
3341
3444
  await client.execute("PRAGMA journal_mode = WAL");
3342
3445
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -2666,6 +2666,7 @@ var init_database = __esm({
2666
2666
  // src/lib/shard-manager.ts
2667
2667
  var shard_manager_exports = {};
2668
2668
  __export(shard_manager_exports, {
2669
+ auditShardHealth: () => auditShardHealth,
2669
2670
  disposeShards: () => disposeShards,
2670
2671
  ensureShardSchema: () => ensureShardSchema,
2671
2672
  getOpenShardCount: () => getOpenShardCount,
@@ -2732,6 +2733,70 @@ function listShards() {
2732
2733
  if (!existsSync7(SHARDS_DIR)) return [];
2733
2734
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2734
2735
  }
2736
+ async function auditShardHealth(options = {}) {
2737
+ if (!_encryptionKey) {
2738
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
2739
+ }
2740
+ const repair = options.repair === true;
2741
+ const dryRun = options.dryRun === true;
2742
+ const names = listShards();
2743
+ const shards = [];
2744
+ for (const name of names) {
2745
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2746
+ const stat = statSync2(dbPath);
2747
+ const item = {
2748
+ name,
2749
+ path: dbPath,
2750
+ ok: false,
2751
+ unreadable: false,
2752
+ error: null,
2753
+ size: stat.size,
2754
+ mtime: stat.mtime.toISOString(),
2755
+ memoryCount: null
2756
+ };
2757
+ const client = createClient2({
2758
+ url: `file:${dbPath}`,
2759
+ encryptionKey: _encryptionKey
2760
+ });
2761
+ try {
2762
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
2763
+ const hasMemories = await client.execute(
2764
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
2765
+ );
2766
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
2767
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
2768
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
2769
+ }
2770
+ item.ok = true;
2771
+ } catch (err) {
2772
+ const message = err instanceof Error ? err.message : String(err);
2773
+ item.error = message;
2774
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
2775
+ if (item.unreadable && repair && !dryRun) {
2776
+ client.close();
2777
+ _shards.delete(name);
2778
+ _shardLastAccess.delete(name);
2779
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2780
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
2781
+ renameSync3(dbPath, archivedPath);
2782
+ item.archivedPath = archivedPath;
2783
+ }
2784
+ } finally {
2785
+ try {
2786
+ client.close();
2787
+ } catch {
2788
+ }
2789
+ }
2790
+ shards.push(item);
2791
+ }
2792
+ return {
2793
+ total: shards.length,
2794
+ ok: shards.filter((s) => s.ok).length,
2795
+ unreadable: shards.filter((s) => s.unreadable).length,
2796
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
2797
+ shards
2798
+ };
2799
+ }
2735
2800
  async function ensureShardSchema(client) {
2736
2801
  await client.execute("PRAGMA journal_mode = WAL");
2737
2802
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -4171,6 +4236,15 @@ function readMachineId() {
4171
4236
  return "";
4172
4237
  }
4173
4238
  }
4239
+ function encryptWithMachineKey(plaintext, machineKey) {
4240
+ const crypto3 = __require("crypto");
4241
+ const iv = crypto3.randomBytes(12);
4242
+ const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
4243
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
4244
+ encrypted += cipher.final("base64");
4245
+ const authTag = cipher.getAuthTag().toString("base64");
4246
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
4247
+ }
4174
4248
  function decryptWithMachineKey(encrypted, machineKey) {
4175
4249
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
4176
4250
  try {
@@ -4189,6 +4263,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
4189
4263
  return null;
4190
4264
  }
4191
4265
  }
4266
+ async function writeMachineBoundFileFallback(b64) {
4267
+ const dir = getKeyDir();
4268
+ await mkdir3(dir, { recursive: true });
4269
+ const keyPath = getKeyPath();
4270
+ const machineKey = deriveMachineKey();
4271
+ if (machineKey) {
4272
+ const encrypted = encryptWithMachineKey(b64, machineKey);
4273
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
4274
+ await chmod2(keyPath, 384);
4275
+ return "encrypted";
4276
+ }
4277
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
4278
+ await chmod2(keyPath, 384);
4279
+ return "plaintext";
4280
+ }
4192
4281
  async function getMasterKey() {
4193
4282
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
4194
4283
  if (nativeValue) {
@@ -4240,6 +4329,20 @@ async function getMasterKey() {
4240
4329
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
4241
4330
  if (migrated) {
4242
4331
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
4332
+ try {
4333
+ await unlink(keyPath);
4334
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
4335
+ } catch {
4336
+ }
4337
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
4338
+ const fallback = await writeMachineBoundFileFallback(b64Value);
4339
+ if (fallback === "encrypted") {
4340
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
4341
+ } else {
4342
+ process.stderr.write(
4343
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
4344
+ );
4345
+ }
4243
4346
  }
4244
4347
  return key;
4245
4348
  } catch (err) {
@@ -2666,6 +2666,7 @@ var init_database = __esm({
2666
2666
  // src/lib/shard-manager.ts
2667
2667
  var shard_manager_exports = {};
2668
2668
  __export(shard_manager_exports, {
2669
+ auditShardHealth: () => auditShardHealth,
2669
2670
  disposeShards: () => disposeShards,
2670
2671
  ensureShardSchema: () => ensureShardSchema,
2671
2672
  getOpenShardCount: () => getOpenShardCount,
@@ -2732,6 +2733,70 @@ function listShards() {
2732
2733
  if (!existsSync7(SHARDS_DIR)) return [];
2733
2734
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2734
2735
  }
2736
+ async function auditShardHealth(options = {}) {
2737
+ if (!_encryptionKey) {
2738
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
2739
+ }
2740
+ const repair = options.repair === true;
2741
+ const dryRun = options.dryRun === true;
2742
+ const names = listShards();
2743
+ const shards = [];
2744
+ for (const name of names) {
2745
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
2746
+ const stat = statSync2(dbPath);
2747
+ const item = {
2748
+ name,
2749
+ path: dbPath,
2750
+ ok: false,
2751
+ unreadable: false,
2752
+ error: null,
2753
+ size: stat.size,
2754
+ mtime: stat.mtime.toISOString(),
2755
+ memoryCount: null
2756
+ };
2757
+ const client = createClient2({
2758
+ url: `file:${dbPath}`,
2759
+ encryptionKey: _encryptionKey
2760
+ });
2761
+ try {
2762
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
2763
+ const hasMemories = await client.execute(
2764
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
2765
+ );
2766
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
2767
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
2768
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
2769
+ }
2770
+ item.ok = true;
2771
+ } catch (err) {
2772
+ const message = err instanceof Error ? err.message : String(err);
2773
+ item.error = message;
2774
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
2775
+ if (item.unreadable && repair && !dryRun) {
2776
+ client.close();
2777
+ _shards.delete(name);
2778
+ _shardLastAccess.delete(name);
2779
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2780
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
2781
+ renameSync3(dbPath, archivedPath);
2782
+ item.archivedPath = archivedPath;
2783
+ }
2784
+ } finally {
2785
+ try {
2786
+ client.close();
2787
+ } catch {
2788
+ }
2789
+ }
2790
+ shards.push(item);
2791
+ }
2792
+ return {
2793
+ total: shards.length,
2794
+ ok: shards.filter((s) => s.ok).length,
2795
+ unreadable: shards.filter((s) => s.unreadable).length,
2796
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
2797
+ shards
2798
+ };
2799
+ }
2735
2800
  async function ensureShardSchema(client) {
2736
2801
  await client.execute("PRAGMA journal_mode = WAL");
2737
2802
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -4036,6 +4101,15 @@ function readMachineId() {
4036
4101
  return "";
4037
4102
  }
4038
4103
  }
4104
+ function encryptWithMachineKey(plaintext, machineKey) {
4105
+ const crypto3 = __require("crypto");
4106
+ const iv = crypto3.randomBytes(12);
4107
+ const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
4108
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
4109
+ encrypted += cipher.final("base64");
4110
+ const authTag = cipher.getAuthTag().toString("base64");
4111
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
4112
+ }
4039
4113
  function decryptWithMachineKey(encrypted, machineKey) {
4040
4114
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
4041
4115
  try {
@@ -4054,6 +4128,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
4054
4128
  return null;
4055
4129
  }
4056
4130
  }
4131
+ async function writeMachineBoundFileFallback(b64) {
4132
+ const dir = getKeyDir();
4133
+ await mkdir3(dir, { recursive: true });
4134
+ const keyPath = getKeyPath();
4135
+ const machineKey = deriveMachineKey();
4136
+ if (machineKey) {
4137
+ const encrypted = encryptWithMachineKey(b64, machineKey);
4138
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
4139
+ await chmod2(keyPath, 384);
4140
+ return "encrypted";
4141
+ }
4142
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
4143
+ await chmod2(keyPath, 384);
4144
+ return "plaintext";
4145
+ }
4057
4146
  async function getMasterKey() {
4058
4147
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
4059
4148
  if (nativeValue) {
@@ -4105,6 +4194,20 @@ async function getMasterKey() {
4105
4194
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
4106
4195
  if (migrated) {
4107
4196
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
4197
+ try {
4198
+ await unlink(keyPath);
4199
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
4200
+ } catch {
4201
+ }
4202
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
4203
+ const fallback = await writeMachineBoundFileFallback(b64Value);
4204
+ if (fallback === "encrypted") {
4205
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
4206
+ } else {
4207
+ process.stderr.write(
4208
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
4209
+ );
4210
+ }
4108
4211
  }
4109
4212
  return key;
4110
4213
  } catch (err) {
@@ -2919,6 +2919,15 @@ function readMachineId() {
2919
2919
  return "";
2920
2920
  }
2921
2921
  }
2922
+ function encryptWithMachineKey(plaintext, machineKey) {
2923
+ const crypto2 = __require("crypto");
2924
+ const iv = crypto2.randomBytes(12);
2925
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
2926
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2927
+ encrypted += cipher.final("base64");
2928
+ const authTag = cipher.getAuthTag().toString("base64");
2929
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2930
+ }
2922
2931
  function decryptWithMachineKey(encrypted, machineKey) {
2923
2932
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2924
2933
  try {
@@ -2937,6 +2946,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
2937
2946
  return null;
2938
2947
  }
2939
2948
  }
2949
+ async function writeMachineBoundFileFallback(b64) {
2950
+ const dir = getKeyDir();
2951
+ await mkdir3(dir, { recursive: true });
2952
+ const keyPath = getKeyPath();
2953
+ const machineKey = deriveMachineKey();
2954
+ if (machineKey) {
2955
+ const encrypted = encryptWithMachineKey(b64, machineKey);
2956
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
2957
+ await chmod2(keyPath, 384);
2958
+ return "encrypted";
2959
+ }
2960
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
2961
+ await chmod2(keyPath, 384);
2962
+ return "plaintext";
2963
+ }
2940
2964
  async function getMasterKey() {
2941
2965
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
2942
2966
  if (nativeValue) {
@@ -2988,6 +3012,20 @@ async function getMasterKey() {
2988
3012
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2989
3013
  if (migrated) {
2990
3014
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3015
+ try {
3016
+ await unlink(keyPath);
3017
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3018
+ } catch {
3019
+ }
3020
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3021
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3022
+ if (fallback === "encrypted") {
3023
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3024
+ } else {
3025
+ process.stderr.write(
3026
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3027
+ );
3028
+ }
2991
3029
  }
2992
3030
  return key;
2993
3031
  } catch (err) {
@@ -3260,6 +3298,7 @@ var init_memory_write_governor = __esm({
3260
3298
  // src/lib/shard-manager.ts
3261
3299
  var shard_manager_exports = {};
3262
3300
  __export(shard_manager_exports, {
3301
+ auditShardHealth: () => auditShardHealth,
3263
3302
  disposeShards: () => disposeShards,
3264
3303
  ensureShardSchema: () => ensureShardSchema,
3265
3304
  getOpenShardCount: () => getOpenShardCount,
@@ -3326,6 +3365,70 @@ function listShards() {
3326
3365
  if (!existsSync7(SHARDS_DIR)) return [];
3327
3366
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3328
3367
  }
3368
+ async function auditShardHealth(options = {}) {
3369
+ if (!_encryptionKey) {
3370
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3371
+ }
3372
+ const repair = options.repair === true;
3373
+ const dryRun = options.dryRun === true;
3374
+ const names = listShards();
3375
+ const shards = [];
3376
+ for (const name of names) {
3377
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3378
+ const stat = statSync2(dbPath);
3379
+ const item = {
3380
+ name,
3381
+ path: dbPath,
3382
+ ok: false,
3383
+ unreadable: false,
3384
+ error: null,
3385
+ size: stat.size,
3386
+ mtime: stat.mtime.toISOString(),
3387
+ memoryCount: null
3388
+ };
3389
+ const client = createClient2({
3390
+ url: `file:${dbPath}`,
3391
+ encryptionKey: _encryptionKey
3392
+ });
3393
+ try {
3394
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3395
+ const hasMemories = await client.execute(
3396
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3397
+ );
3398
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3399
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3400
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3401
+ }
3402
+ item.ok = true;
3403
+ } catch (err) {
3404
+ const message = err instanceof Error ? err.message : String(err);
3405
+ item.error = message;
3406
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3407
+ if (item.unreadable && repair && !dryRun) {
3408
+ client.close();
3409
+ _shards.delete(name);
3410
+ _shardLastAccess.delete(name);
3411
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3412
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3413
+ renameSync3(dbPath, archivedPath);
3414
+ item.archivedPath = archivedPath;
3415
+ }
3416
+ } finally {
3417
+ try {
3418
+ client.close();
3419
+ } catch {
3420
+ }
3421
+ }
3422
+ shards.push(item);
3423
+ }
3424
+ return {
3425
+ total: shards.length,
3426
+ ok: shards.filter((s) => s.ok).length,
3427
+ unreadable: shards.filter((s) => s.unreadable).length,
3428
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3429
+ shards
3430
+ };
3431
+ }
3329
3432
  async function ensureShardSchema(client) {
3330
3433
  await client.execute("PRAGMA journal_mode = WAL");
3331
3434
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -2908,6 +2908,15 @@ function readMachineId() {
2908
2908
  return "";
2909
2909
  }
2910
2910
  }
2911
+ function encryptWithMachineKey(plaintext, machineKey) {
2912
+ const crypto2 = __require("crypto");
2913
+ const iv = crypto2.randomBytes(12);
2914
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
2915
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2916
+ encrypted += cipher.final("base64");
2917
+ const authTag = cipher.getAuthTag().toString("base64");
2918
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2919
+ }
2911
2920
  function decryptWithMachineKey(encrypted, machineKey) {
2912
2921
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2913
2922
  try {
@@ -2926,6 +2935,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
2926
2935
  return null;
2927
2936
  }
2928
2937
  }
2938
+ async function writeMachineBoundFileFallback(b64) {
2939
+ const dir = getKeyDir();
2940
+ await mkdir3(dir, { recursive: true });
2941
+ const keyPath = getKeyPath();
2942
+ const machineKey = deriveMachineKey();
2943
+ if (machineKey) {
2944
+ const encrypted = encryptWithMachineKey(b64, machineKey);
2945
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
2946
+ await chmod2(keyPath, 384);
2947
+ return "encrypted";
2948
+ }
2949
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
2950
+ await chmod2(keyPath, 384);
2951
+ return "plaintext";
2952
+ }
2929
2953
  async function getMasterKey() {
2930
2954
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
2931
2955
  if (nativeValue) {
@@ -2977,6 +3001,20 @@ async function getMasterKey() {
2977
3001
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2978
3002
  if (migrated) {
2979
3003
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3004
+ try {
3005
+ await unlink(keyPath);
3006
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3007
+ } catch {
3008
+ }
3009
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3010
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3011
+ if (fallback === "encrypted") {
3012
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3013
+ } else {
3014
+ process.stderr.write(
3015
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3016
+ );
3017
+ }
2980
3018
  }
2981
3019
  return key;
2982
3020
  } catch (err) {
@@ -3249,6 +3287,7 @@ var init_memory_write_governor = __esm({
3249
3287
  // src/lib/shard-manager.ts
3250
3288
  var shard_manager_exports = {};
3251
3289
  __export(shard_manager_exports, {
3290
+ auditShardHealth: () => auditShardHealth,
3252
3291
  disposeShards: () => disposeShards,
3253
3292
  ensureShardSchema: () => ensureShardSchema,
3254
3293
  getOpenShardCount: () => getOpenShardCount,
@@ -3315,6 +3354,70 @@ function listShards() {
3315
3354
  if (!existsSync7(SHARDS_DIR)) return [];
3316
3355
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3317
3356
  }
3357
+ async function auditShardHealth(options = {}) {
3358
+ if (!_encryptionKey) {
3359
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3360
+ }
3361
+ const repair = options.repair === true;
3362
+ const dryRun = options.dryRun === true;
3363
+ const names = listShards();
3364
+ const shards = [];
3365
+ for (const name of names) {
3366
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3367
+ const stat = statSync2(dbPath);
3368
+ const item = {
3369
+ name,
3370
+ path: dbPath,
3371
+ ok: false,
3372
+ unreadable: false,
3373
+ error: null,
3374
+ size: stat.size,
3375
+ mtime: stat.mtime.toISOString(),
3376
+ memoryCount: null
3377
+ };
3378
+ const client = createClient2({
3379
+ url: `file:${dbPath}`,
3380
+ encryptionKey: _encryptionKey
3381
+ });
3382
+ try {
3383
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3384
+ const hasMemories = await client.execute(
3385
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3386
+ );
3387
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3388
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3389
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3390
+ }
3391
+ item.ok = true;
3392
+ } catch (err) {
3393
+ const message = err instanceof Error ? err.message : String(err);
3394
+ item.error = message;
3395
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3396
+ if (item.unreadable && repair && !dryRun) {
3397
+ client.close();
3398
+ _shards.delete(name);
3399
+ _shardLastAccess.delete(name);
3400
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3401
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3402
+ renameSync3(dbPath, archivedPath);
3403
+ item.archivedPath = archivedPath;
3404
+ }
3405
+ } finally {
3406
+ try {
3407
+ client.close();
3408
+ } catch {
3409
+ }
3410
+ }
3411
+ shards.push(item);
3412
+ }
3413
+ return {
3414
+ total: shards.length,
3415
+ ok: shards.filter((s) => s.ok).length,
3416
+ unreadable: shards.filter((s) => s.unreadable).length,
3417
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3418
+ shards
3419
+ };
3420
+ }
3318
3421
  async function ensureShardSchema(client) {
3319
3422
  await client.execute("PRAGMA journal_mode = WAL");
3320
3423
  await client.execute("PRAGMA busy_timeout = 30000");