@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
@@ -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");
@@ -3510,6 +3575,15 @@ function readMachineId() {
3510
3575
  return "";
3511
3576
  }
3512
3577
  }
3578
+ function encryptWithMachineKey(plaintext, machineKey) {
3579
+ const crypto2 = __require("crypto");
3580
+ const iv = crypto2.randomBytes(12);
3581
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3582
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3583
+ encrypted += cipher.final("base64");
3584
+ const authTag = cipher.getAuthTag().toString("base64");
3585
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3586
+ }
3513
3587
  function decryptWithMachineKey(encrypted, machineKey) {
3514
3588
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3515
3589
  try {
@@ -3528,6 +3602,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3528
3602
  return null;
3529
3603
  }
3530
3604
  }
3605
+ async function writeMachineBoundFileFallback(b64) {
3606
+ const dir = getKeyDir();
3607
+ await mkdir3(dir, { recursive: true });
3608
+ const keyPath = getKeyPath();
3609
+ const machineKey = deriveMachineKey();
3610
+ if (machineKey) {
3611
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3612
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3613
+ await chmod2(keyPath, 384);
3614
+ return "encrypted";
3615
+ }
3616
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3617
+ await chmod2(keyPath, 384);
3618
+ return "plaintext";
3619
+ }
3531
3620
  async function getMasterKey() {
3532
3621
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3533
3622
  if (nativeValue) {
@@ -3579,6 +3668,20 @@ async function getMasterKey() {
3579
3668
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3580
3669
  if (migrated) {
3581
3670
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3671
+ try {
3672
+ await unlink(keyPath);
3673
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3674
+ } catch {
3675
+ }
3676
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3677
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3678
+ if (fallback === "encrypted") {
3679
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3680
+ } else {
3681
+ process.stderr.write(
3682
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3683
+ );
3684
+ }
3582
3685
  }
3583
3686
  return key;
3584
3687
  } catch (err) {
@@ -4212,15 +4315,31 @@ function auditHookHealth() {
4212
4315
  const topPatterns = [...patternCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([pattern, count]) => ({ pattern, count }));
4213
4316
  return { logExists: true, totalLines, errorsLastHour, topPatterns };
4214
4317
  }
4318
+ async function auditShards() {
4319
+ try {
4320
+ const { auditShardHealth: auditShardHealth2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4321
+ const report = await auditShardHealth2();
4322
+ return {
4323
+ total: report.total,
4324
+ ok: report.ok,
4325
+ unreadable: report.unreadable,
4326
+ archived: report.archived,
4327
+ unreadableNames: report.shards.filter((s) => s.unreadable).map((s) => s.name)
4328
+ };
4329
+ } catch {
4330
+ return { total: 0, ok: 0, unreadable: 0, archived: 0, unreadableNames: [] };
4331
+ }
4332
+ }
4215
4333
  async function runAudit(client, flags) {
4216
4334
  const runConflicts = flags.conflicts || process.env.EXE_AUDIT_CONFLICTS === "1";
4217
- const [stats, nullVectors, duplicates, bloated, fts, orphanedProjects] = await Promise.all([
4335
+ const [stats, nullVectors, duplicates, bloated, fts, orphanedProjects, shards] = await Promise.all([
4218
4336
  auditStats(client, flags),
4219
4337
  auditNullVectors(client, flags),
4220
4338
  auditDuplicates(client, flags),
4221
4339
  auditBloated(client, flags),
4222
4340
  auditFts(client),
4223
- auditOrphanedProjects(client)
4341
+ auditOrphanedProjects(client),
4342
+ auditShards()
4224
4343
  ]);
4225
4344
  let conflicts;
4226
4345
  if (runConflicts) {
@@ -4240,7 +4359,7 @@ async function runAudit(client, flags) {
4240
4359
  }
4241
4360
  const duplicateCount = duplicates.reduce((sum, d) => sum + d.delete_ids.length, 0);
4242
4361
  const hookHealth = auditHookHealth();
4243
- return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth };
4362
+ return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth, shards };
4244
4363
  }
4245
4364
  function indicator(value, warn) {
4246
4365
  if (value === 0) return "\u{1F7E2}";
@@ -4319,6 +4438,15 @@ function formatReport(report, flags) {
4319
4438
  lines.push(` ${p.count}x: ${p.pattern}`);
4320
4439
  }
4321
4440
  }
4441
+ const sh = report.shards;
4442
+ if (sh.total > 0) {
4443
+ if (sh.unreadable === 0) {
4444
+ lines.push(`\u{1F7E2} Shards: ${fmtNum(sh.ok)} / ${fmtNum(sh.total)} readable`);
4445
+ } else {
4446
+ const names = sh.unreadableNames.slice(0, 5).join(", ");
4447
+ lines.push(`\u{1F534} Shards: ${fmtNum(sh.unreadable)} unreadable (${names}${sh.unreadableNames.length > 5 ? ", ..." : ""})`);
4448
+ }
4449
+ }
4322
4450
  lines.push("");
4323
4451
  if (flags.verbose) {
4324
4452
  if (report.duplicates.length > 0) {
@@ -4366,6 +4494,9 @@ function formatReport(report, flags) {
4366
4494
  if (!report.fts.inSync) {
4367
4495
  recs.push("Run --fix to rebuild FTS index");
4368
4496
  }
4497
+ if (report.shards.unreadable > 0) {
4498
+ recs.push(`Run --fix to archive ${fmtNum(report.shards.unreadable)} unreadable shard(s); global DB remains authoritative`);
4499
+ }
4369
4500
  if (report.orphanedProjects.length > 0) {
4370
4501
  const names = report.orphanedProjects.map((o) => `"${o.project_name}"`).join(", ");
4371
4502
  recs.push(`Consider /exe-forget for orphaned project${report.orphanedProjects.length > 1 ? "s" : ""} ${names}`);
@@ -4471,6 +4602,17 @@ async function fixBloated(client, bloated, dryRun) {
4471
4602
  }
4472
4603
  return chunksCreated;
4473
4604
  }
4605
+ async function fixShards(dryRun) {
4606
+ const { auditShardHealth: auditShardHealth2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
4607
+ const report = await auditShardHealth2({ repair: true, dryRun });
4608
+ return {
4609
+ total: report.total,
4610
+ ok: report.ok,
4611
+ unreadable: report.unreadable,
4612
+ archived: report.archived,
4613
+ unreadableNames: report.shards.filter((s) => s.unreadable).map((s) => s.name)
4614
+ };
4615
+ }
4474
4616
  function splitAtSentences(text, maxChunkSize) {
4475
4617
  if (text.length <= maxChunkSize) return [text];
4476
4618
  const chunks = [];
@@ -4546,6 +4688,11 @@ ${mode} Applying repairs...
4546
4688
  console.log(" Done.");
4547
4689
  }
4548
4690
  }
4691
+ if (report.shards.unreadable > 0) {
4692
+ console.log(`${mode} Archiving ${fmtNum(report.shards.unreadable)} unreadable shard(s)...`);
4693
+ const fixed = await fixShards(flags.dryRun);
4694
+ console.log(` ${flags.dryRun ? "Would archive" : "Archived"} ${fmtNum(flags.dryRun ? fixed.unreadable : fixed.archived)} shard(s).`);
4695
+ }
4549
4696
  console.log(`
4550
4697
  ${mode} Complete.`);
4551
4698
  }
@@ -4564,10 +4711,12 @@ export {
4564
4711
  auditHookHealth,
4565
4712
  auditNullVectors,
4566
4713
  auditOrphanedProjects,
4714
+ auditShards,
4567
4715
  auditStats,
4568
4716
  fixBloated,
4569
4717
  fixDuplicates,
4570
4718
  fixNullVectors,
4719
+ fixShards,
4571
4720
  formatReport,
4572
4721
  main,
4573
4722
  parseFlags,
@@ -2963,6 +2963,15 @@ function readMachineId() {
2963
2963
  return "";
2964
2964
  }
2965
2965
  }
2966
+ function encryptWithMachineKey(plaintext, machineKey) {
2967
+ const crypto3 = __require("crypto");
2968
+ const iv = crypto3.randomBytes(12);
2969
+ const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
2970
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
2971
+ encrypted += cipher.final("base64");
2972
+ const authTag = cipher.getAuthTag().toString("base64");
2973
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
2974
+ }
2966
2975
  function decryptWithMachineKey(encrypted, machineKey) {
2967
2976
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2968
2977
  try {
@@ -2981,6 +2990,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
2981
2990
  return null;
2982
2991
  }
2983
2992
  }
2993
+ async function writeMachineBoundFileFallback(b64) {
2994
+ const dir = getKeyDir();
2995
+ await mkdir3(dir, { recursive: true });
2996
+ const keyPath = getKeyPath();
2997
+ const machineKey = deriveMachineKey();
2998
+ if (machineKey) {
2999
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3000
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3001
+ await chmod2(keyPath, 384);
3002
+ return "encrypted";
3003
+ }
3004
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3005
+ await chmod2(keyPath, 384);
3006
+ return "plaintext";
3007
+ }
2984
3008
  async function getMasterKey() {
2985
3009
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
2986
3010
  if (nativeValue) {
@@ -3032,6 +3056,20 @@ async function getMasterKey() {
3032
3056
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3033
3057
  if (migrated) {
3034
3058
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3059
+ try {
3060
+ await unlink(keyPath);
3061
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3062
+ } catch {
3063
+ }
3064
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3065
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3066
+ if (fallback === "encrypted") {
3067
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3068
+ } else {
3069
+ process.stderr.write(
3070
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3071
+ );
3072
+ }
3035
3073
  }
3036
3074
  return key;
3037
3075
  } catch (err) {
@@ -3304,6 +3342,7 @@ var init_memory_write_governor = __esm({
3304
3342
  // src/lib/shard-manager.ts
3305
3343
  var shard_manager_exports = {};
3306
3344
  __export(shard_manager_exports, {
3345
+ auditShardHealth: () => auditShardHealth,
3307
3346
  disposeShards: () => disposeShards,
3308
3347
  ensureShardSchema: () => ensureShardSchema,
3309
3348
  getOpenShardCount: () => getOpenShardCount,
@@ -3370,6 +3409,70 @@ function listShards() {
3370
3409
  if (!existsSync7(SHARDS_DIR)) return [];
3371
3410
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3372
3411
  }
3412
+ async function auditShardHealth(options = {}) {
3413
+ if (!_encryptionKey) {
3414
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3415
+ }
3416
+ const repair = options.repair === true;
3417
+ const dryRun = options.dryRun === true;
3418
+ const names = listShards();
3419
+ const shards = [];
3420
+ for (const name of names) {
3421
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3422
+ const stat = statSync2(dbPath);
3423
+ const item = {
3424
+ name,
3425
+ path: dbPath,
3426
+ ok: false,
3427
+ unreadable: false,
3428
+ error: null,
3429
+ size: stat.size,
3430
+ mtime: stat.mtime.toISOString(),
3431
+ memoryCount: null
3432
+ };
3433
+ const client = createClient2({
3434
+ url: `file:${dbPath}`,
3435
+ encryptionKey: _encryptionKey
3436
+ });
3437
+ try {
3438
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3439
+ const hasMemories = await client.execute(
3440
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3441
+ );
3442
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3443
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3444
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3445
+ }
3446
+ item.ok = true;
3447
+ } catch (err) {
3448
+ const message = err instanceof Error ? err.message : String(err);
3449
+ item.error = message;
3450
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3451
+ if (item.unreadable && repair && !dryRun) {
3452
+ client.close();
3453
+ _shards.delete(name);
3454
+ _shardLastAccess.delete(name);
3455
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3456
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3457
+ renameSync3(dbPath, archivedPath);
3458
+ item.archivedPath = archivedPath;
3459
+ }
3460
+ } finally {
3461
+ try {
3462
+ client.close();
3463
+ } catch {
3464
+ }
3465
+ }
3466
+ shards.push(item);
3467
+ }
3468
+ return {
3469
+ total: shards.length,
3470
+ ok: shards.filter((s) => s.ok).length,
3471
+ unreadable: shards.filter((s) => s.unreadable).length,
3472
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3473
+ shards
3474
+ };
3475
+ }
3373
3476
  async function ensureShardSchema(client) {
3374
3477
  await client.execute("PRAGMA journal_mode = WAL");
3375
3478
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -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");
@@ -3611,6 +3611,15 @@ function readMachineId() {
3611
3611
  return "";
3612
3612
  }
3613
3613
  }
3614
+ function encryptWithMachineKey(plaintext, machineKey) {
3615
+ const crypto10 = __require("crypto");
3616
+ const iv = crypto10.randomBytes(12);
3617
+ const cipher = crypto10.createCipheriv("aes-256-gcm", machineKey, iv);
3618
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3619
+ encrypted += cipher.final("base64");
3620
+ const authTag = cipher.getAuthTag().toString("base64");
3621
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3622
+ }
3614
3623
  function decryptWithMachineKey(encrypted, machineKey) {
3615
3624
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3616
3625
  try {
@@ -3629,6 +3638,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3629
3638
  return null;
3630
3639
  }
3631
3640
  }
3641
+ async function writeMachineBoundFileFallback(b64) {
3642
+ const dir = getKeyDir();
3643
+ await mkdir3(dir, { recursive: true });
3644
+ const keyPath = getKeyPath();
3645
+ const machineKey = deriveMachineKey();
3646
+ if (machineKey) {
3647
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3648
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3649
+ await chmod2(keyPath, 384);
3650
+ return "encrypted";
3651
+ }
3652
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3653
+ await chmod2(keyPath, 384);
3654
+ return "plaintext";
3655
+ }
3632
3656
  async function getMasterKey() {
3633
3657
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3634
3658
  if (nativeValue) {
@@ -3680,6 +3704,20 @@ async function getMasterKey() {
3680
3704
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3681
3705
  if (migrated) {
3682
3706
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3707
+ try {
3708
+ await unlink(keyPath);
3709
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3710
+ } catch {
3711
+ }
3712
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3713
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3714
+ if (fallback === "encrypted") {
3715
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3716
+ } else {
3717
+ process.stderr.write(
3718
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3719
+ );
3720
+ }
3683
3721
  }
3684
3722
  return key;
3685
3723
  } catch (err) {
@@ -3897,6 +3935,7 @@ var init_memory_write_governor = __esm({
3897
3935
  // src/lib/shard-manager.ts
3898
3936
  var shard_manager_exports = {};
3899
3937
  __export(shard_manager_exports, {
3938
+ auditShardHealth: () => auditShardHealth,
3900
3939
  disposeShards: () => disposeShards,
3901
3940
  ensureShardSchema: () => ensureShardSchema,
3902
3941
  getOpenShardCount: () => getOpenShardCount,
@@ -3963,6 +4002,70 @@ function listShards() {
3963
4002
  if (!existsSync8(SHARDS_DIR)) return [];
3964
4003
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3965
4004
  }
4005
+ async function auditShardHealth(options = {}) {
4006
+ if (!_encryptionKey) {
4007
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
4008
+ }
4009
+ const repair = options.repair === true;
4010
+ const dryRun = options.dryRun === true;
4011
+ const names = listShards();
4012
+ const shards = [];
4013
+ for (const name of names) {
4014
+ const dbPath = path9.join(SHARDS_DIR, `${name}.db`);
4015
+ const stat = statSync2(dbPath);
4016
+ const item = {
4017
+ name,
4018
+ path: dbPath,
4019
+ ok: false,
4020
+ unreadable: false,
4021
+ error: null,
4022
+ size: stat.size,
4023
+ mtime: stat.mtime.toISOString(),
4024
+ memoryCount: null
4025
+ };
4026
+ const client = createClient2({
4027
+ url: `file:${dbPath}`,
4028
+ encryptionKey: _encryptionKey
4029
+ });
4030
+ try {
4031
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
4032
+ const hasMemories = await client.execute(
4033
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
4034
+ );
4035
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
4036
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
4037
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
4038
+ }
4039
+ item.ok = true;
4040
+ } catch (err) {
4041
+ const message = err instanceof Error ? err.message : String(err);
4042
+ item.error = message;
4043
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
4044
+ if (item.unreadable && repair && !dryRun) {
4045
+ client.close();
4046
+ _shards.delete(name);
4047
+ _shardLastAccess.delete(name);
4048
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4049
+ const archivedPath = path9.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4050
+ renameSync3(dbPath, archivedPath);
4051
+ item.archivedPath = archivedPath;
4052
+ }
4053
+ } finally {
4054
+ try {
4055
+ client.close();
4056
+ } catch {
4057
+ }
4058
+ }
4059
+ shards.push(item);
4060
+ }
4061
+ return {
4062
+ total: shards.length,
4063
+ ok: shards.filter((s) => s.ok).length,
4064
+ unreadable: shards.filter((s) => s.unreadable).length,
4065
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
4066
+ shards
4067
+ };
4068
+ }
3966
4069
  async function ensureShardSchema(client) {
3967
4070
  await client.execute("PRAGMA journal_mode = WAL");
3968
4071
  await client.execute("PRAGMA busy_timeout = 30000");