@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
@@ -6844,6 +6844,15 @@ function readMachineId() {
6844
6844
  return "";
6845
6845
  }
6846
6846
  }
6847
+ function encryptWithMachineKey(plaintext, machineKey) {
6848
+ const crypto7 = __require("crypto");
6849
+ const iv = crypto7.randomBytes(12);
6850
+ const cipher = crypto7.createCipheriv("aes-256-gcm", machineKey, iv);
6851
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
6852
+ encrypted += cipher.final("base64");
6853
+ const authTag = cipher.getAuthTag().toString("base64");
6854
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
6855
+ }
6847
6856
  function decryptWithMachineKey(encrypted, machineKey) {
6848
6857
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
6849
6858
  try {
@@ -6862,6 +6871,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
6862
6871
  return null;
6863
6872
  }
6864
6873
  }
6874
+ async function writeMachineBoundFileFallback(b64) {
6875
+ const dir = getKeyDir();
6876
+ await mkdir4(dir, { recursive: true });
6877
+ const keyPath = getKeyPath();
6878
+ const machineKey = deriveMachineKey();
6879
+ if (machineKey) {
6880
+ const encrypted = encryptWithMachineKey(b64, machineKey);
6881
+ await writeFile5(keyPath, encrypted + "\n", "utf-8");
6882
+ await chmod2(keyPath, 384);
6883
+ return "encrypted";
6884
+ }
6885
+ await writeFile5(keyPath, b64 + "\n", "utf-8");
6886
+ await chmod2(keyPath, 384);
6887
+ return "plaintext";
6888
+ }
6865
6889
  async function getMasterKey() {
6866
6890
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
6867
6891
  if (nativeValue) {
@@ -6913,6 +6937,20 @@ async function getMasterKey() {
6913
6937
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
6914
6938
  if (migrated) {
6915
6939
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
6940
+ try {
6941
+ await unlink(keyPath);
6942
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
6943
+ } catch {
6944
+ }
6945
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
6946
+ const fallback = await writeMachineBoundFileFallback(b64Value);
6947
+ if (fallback === "encrypted") {
6948
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
6949
+ } else {
6950
+ process.stderr.write(
6951
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
6952
+ );
6953
+ }
6916
6954
  }
6917
6955
  return key;
6918
6956
  } catch (err) {
@@ -7130,6 +7168,7 @@ var init_memory_write_governor = __esm({
7130
7168
  // src/lib/shard-manager.ts
7131
7169
  var shard_manager_exports = {};
7132
7170
  __export(shard_manager_exports, {
7171
+ auditShardHealth: () => auditShardHealth,
7133
7172
  disposeShards: () => disposeShards,
7134
7173
  ensureShardSchema: () => ensureShardSchema,
7135
7174
  getOpenShardCount: () => getOpenShardCount,
@@ -7196,6 +7235,70 @@ function listShards() {
7196
7235
  if (!existsSync17(SHARDS_DIR)) return [];
7197
7236
  return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
7198
7237
  }
7238
+ async function auditShardHealth(options = {}) {
7239
+ if (!_encryptionKey) {
7240
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
7241
+ }
7242
+ const repair = options.repair === true;
7243
+ const dryRun = options.dryRun === true;
7244
+ const names = listShards();
7245
+ const shards = [];
7246
+ for (const name of names) {
7247
+ const dbPath = path21.join(SHARDS_DIR, `${name}.db`);
7248
+ const stat = statSync2(dbPath);
7249
+ const item = {
7250
+ name,
7251
+ path: dbPath,
7252
+ ok: false,
7253
+ unreadable: false,
7254
+ error: null,
7255
+ size: stat.size,
7256
+ mtime: stat.mtime.toISOString(),
7257
+ memoryCount: null
7258
+ };
7259
+ const client = createClient2({
7260
+ url: `file:${dbPath}`,
7261
+ encryptionKey: _encryptionKey
7262
+ });
7263
+ try {
7264
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
7265
+ const hasMemories = await client.execute(
7266
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
7267
+ );
7268
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
7269
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
7270
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
7271
+ }
7272
+ item.ok = true;
7273
+ } catch (err) {
7274
+ const message = err instanceof Error ? err.message : String(err);
7275
+ item.error = message;
7276
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
7277
+ if (item.unreadable && repair && !dryRun) {
7278
+ client.close();
7279
+ _shards.delete(name);
7280
+ _shardLastAccess.delete(name);
7281
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7282
+ const archivedPath = path21.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
7283
+ renameSync4(dbPath, archivedPath);
7284
+ item.archivedPath = archivedPath;
7285
+ }
7286
+ } finally {
7287
+ try {
7288
+ client.close();
7289
+ } catch {
7290
+ }
7291
+ }
7292
+ shards.push(item);
7293
+ }
7294
+ return {
7295
+ total: shards.length,
7296
+ ok: shards.filter((s) => s.ok).length,
7297
+ unreadable: shards.filter((s) => s.unreadable).length,
7298
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
7299
+ shards
7300
+ };
7301
+ }
7199
7302
  async function ensureShardSchema(client) {
7200
7303
  await client.execute("PRAGMA journal_mode = WAL");
7201
7304
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3297,6 +3297,15 @@ function readMachineId() {
3297
3297
  return "";
3298
3298
  }
3299
3299
  }
3300
+ function encryptWithMachineKey(plaintext, machineKey) {
3301
+ const crypto3 = __require("crypto");
3302
+ const iv = crypto3.randomBytes(12);
3303
+ const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
3304
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3305
+ encrypted += cipher.final("base64");
3306
+ const authTag = cipher.getAuthTag().toString("base64");
3307
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3308
+ }
3300
3309
  function decryptWithMachineKey(encrypted, machineKey) {
3301
3310
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3302
3311
  try {
@@ -3315,6 +3324,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3315
3324
  return null;
3316
3325
  }
3317
3326
  }
3327
+ async function writeMachineBoundFileFallback(b64) {
3328
+ const dir = getKeyDir();
3329
+ await mkdir3(dir, { recursive: true });
3330
+ const keyPath = getKeyPath();
3331
+ const machineKey = deriveMachineKey();
3332
+ if (machineKey) {
3333
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3334
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3335
+ await chmod2(keyPath, 384);
3336
+ return "encrypted";
3337
+ }
3338
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3339
+ await chmod2(keyPath, 384);
3340
+ return "plaintext";
3341
+ }
3318
3342
  async function getMasterKey() {
3319
3343
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3320
3344
  if (nativeValue) {
@@ -3366,6 +3390,20 @@ async function getMasterKey() {
3366
3390
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3367
3391
  if (migrated) {
3368
3392
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3393
+ try {
3394
+ await unlink(keyPath);
3395
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3396
+ } catch {
3397
+ }
3398
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3399
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3400
+ if (fallback === "encrypted") {
3401
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3402
+ } else {
3403
+ process.stderr.write(
3404
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3405
+ );
3406
+ }
3369
3407
  }
3370
3408
  return key;
3371
3409
  } catch (err) {
@@ -3638,6 +3676,7 @@ var init_memory_write_governor = __esm({
3638
3676
  // src/lib/shard-manager.ts
3639
3677
  var shard_manager_exports = {};
3640
3678
  __export(shard_manager_exports, {
3679
+ auditShardHealth: () => auditShardHealth,
3641
3680
  disposeShards: () => disposeShards,
3642
3681
  ensureShardSchema: () => ensureShardSchema,
3643
3682
  getOpenShardCount: () => getOpenShardCount,
@@ -3704,6 +3743,70 @@ function listShards() {
3704
3743
  if (!existsSync8(SHARDS_DIR)) return [];
3705
3744
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3706
3745
  }
3746
+ async function auditShardHealth(options = {}) {
3747
+ if (!_encryptionKey) {
3748
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3749
+ }
3750
+ const repair = options.repair === true;
3751
+ const dryRun = options.dryRun === true;
3752
+ const names = listShards();
3753
+ const shards = [];
3754
+ for (const name of names) {
3755
+ const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
3756
+ const stat = statSync2(dbPath);
3757
+ const item = {
3758
+ name,
3759
+ path: dbPath,
3760
+ ok: false,
3761
+ unreadable: false,
3762
+ error: null,
3763
+ size: stat.size,
3764
+ mtime: stat.mtime.toISOString(),
3765
+ memoryCount: null
3766
+ };
3767
+ const client = createClient2({
3768
+ url: `file:${dbPath}`,
3769
+ encryptionKey: _encryptionKey
3770
+ });
3771
+ try {
3772
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3773
+ const hasMemories = await client.execute(
3774
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3775
+ );
3776
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3777
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3778
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3779
+ }
3780
+ item.ok = true;
3781
+ } catch (err) {
3782
+ const message = err instanceof Error ? err.message : String(err);
3783
+ item.error = message;
3784
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3785
+ if (item.unreadable && repair && !dryRun) {
3786
+ client.close();
3787
+ _shards.delete(name);
3788
+ _shardLastAccess.delete(name);
3789
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3790
+ const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3791
+ renameSync3(dbPath, archivedPath);
3792
+ item.archivedPath = archivedPath;
3793
+ }
3794
+ } finally {
3795
+ try {
3796
+ client.close();
3797
+ } catch {
3798
+ }
3799
+ }
3800
+ shards.push(item);
3801
+ }
3802
+ return {
3803
+ total: shards.length,
3804
+ ok: shards.filter((s) => s.ok).length,
3805
+ unreadable: shards.filter((s) => s.unreadable).length,
3806
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3807
+ shards
3808
+ };
3809
+ }
3707
3810
  async function ensureShardSchema(client) {
3708
3811
  await client.execute("PRAGMA journal_mode = WAL");
3709
3812
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3345,6 +3345,15 @@ function readMachineId() {
3345
3345
  return "";
3346
3346
  }
3347
3347
  }
3348
+ function encryptWithMachineKey(plaintext, machineKey) {
3349
+ const crypto2 = __require("crypto");
3350
+ const iv = crypto2.randomBytes(12);
3351
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3352
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3353
+ encrypted += cipher.final("base64");
3354
+ const authTag = cipher.getAuthTag().toString("base64");
3355
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3356
+ }
3348
3357
  function decryptWithMachineKey(encrypted, machineKey) {
3349
3358
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3350
3359
  try {
@@ -3363,6 +3372,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3363
3372
  return null;
3364
3373
  }
3365
3374
  }
3375
+ async function writeMachineBoundFileFallback(b64) {
3376
+ const dir = getKeyDir();
3377
+ await mkdir3(dir, { recursive: true });
3378
+ const keyPath = getKeyPath();
3379
+ const machineKey = deriveMachineKey();
3380
+ if (machineKey) {
3381
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3382
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3383
+ await chmod2(keyPath, 384);
3384
+ return "encrypted";
3385
+ }
3386
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3387
+ await chmod2(keyPath, 384);
3388
+ return "plaintext";
3389
+ }
3366
3390
  async function getMasterKey() {
3367
3391
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3368
3392
  if (nativeValue) {
@@ -3414,6 +3438,20 @@ async function getMasterKey() {
3414
3438
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3415
3439
  if (migrated) {
3416
3440
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3441
+ try {
3442
+ await unlink(keyPath);
3443
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3444
+ } catch {
3445
+ }
3446
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3447
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3448
+ if (fallback === "encrypted") {
3449
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3450
+ } else {
3451
+ process.stderr.write(
3452
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3453
+ );
3454
+ }
3417
3455
  }
3418
3456
  return key;
3419
3457
  } catch (err) {
@@ -3686,6 +3724,7 @@ var init_memory_write_governor = __esm({
3686
3724
  // src/lib/shard-manager.ts
3687
3725
  var shard_manager_exports = {};
3688
3726
  __export(shard_manager_exports, {
3727
+ auditShardHealth: () => auditShardHealth,
3689
3728
  disposeShards: () => disposeShards,
3690
3729
  ensureShardSchema: () => ensureShardSchema,
3691
3730
  getOpenShardCount: () => getOpenShardCount,
@@ -3752,6 +3791,70 @@ function listShards() {
3752
3791
  if (!existsSync12(SHARDS_DIR)) return [];
3753
3792
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3754
3793
  }
3794
+ async function auditShardHealth(options = {}) {
3795
+ if (!_encryptionKey) {
3796
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3797
+ }
3798
+ const repair = options.repair === true;
3799
+ const dryRun = options.dryRun === true;
3800
+ const names = listShards();
3801
+ const shards = [];
3802
+ for (const name of names) {
3803
+ const dbPath = path14.join(SHARDS_DIR, `${name}.db`);
3804
+ const stat = statSync2(dbPath);
3805
+ const item = {
3806
+ name,
3807
+ path: dbPath,
3808
+ ok: false,
3809
+ unreadable: false,
3810
+ error: null,
3811
+ size: stat.size,
3812
+ mtime: stat.mtime.toISOString(),
3813
+ memoryCount: null
3814
+ };
3815
+ const client = createClient2({
3816
+ url: `file:${dbPath}`,
3817
+ encryptionKey: _encryptionKey
3818
+ });
3819
+ try {
3820
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3821
+ const hasMemories = await client.execute(
3822
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3823
+ );
3824
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3825
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3826
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3827
+ }
3828
+ item.ok = true;
3829
+ } catch (err) {
3830
+ const message = err instanceof Error ? err.message : String(err);
3831
+ item.error = message;
3832
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3833
+ if (item.unreadable && repair && !dryRun) {
3834
+ client.close();
3835
+ _shards.delete(name);
3836
+ _shardLastAccess.delete(name);
3837
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3838
+ const archivedPath = path14.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3839
+ renameSync4(dbPath, archivedPath);
3840
+ item.archivedPath = archivedPath;
3841
+ }
3842
+ } finally {
3843
+ try {
3844
+ client.close();
3845
+ } catch {
3846
+ }
3847
+ }
3848
+ shards.push(item);
3849
+ }
3850
+ return {
3851
+ total: shards.length,
3852
+ ok: shards.filter((s) => s.ok).length,
3853
+ unreadable: shards.filter((s) => s.unreadable).length,
3854
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3855
+ shards
3856
+ };
3857
+ }
3755
3858
  async function ensureShardSchema(client) {
3756
3859
  await client.execute("PRAGMA journal_mode = WAL");
3757
3860
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3326,6 +3326,15 @@ function readMachineId() {
3326
3326
  return "";
3327
3327
  }
3328
3328
  }
3329
+ function encryptWithMachineKey(plaintext, machineKey) {
3330
+ const crypto2 = __require("crypto");
3331
+ const iv = crypto2.randomBytes(12);
3332
+ const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
3333
+ let encrypted = cipher.update(plaintext, "utf-8", "base64");
3334
+ encrypted += cipher.final("base64");
3335
+ const authTag = cipher.getAuthTag().toString("base64");
3336
+ return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
3337
+ }
3329
3338
  function decryptWithMachineKey(encrypted, machineKey) {
3330
3339
  if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3331
3340
  try {
@@ -3344,6 +3353,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3344
3353
  return null;
3345
3354
  }
3346
3355
  }
3356
+ async function writeMachineBoundFileFallback(b64) {
3357
+ const dir = getKeyDir();
3358
+ await mkdir3(dir, { recursive: true });
3359
+ const keyPath = getKeyPath();
3360
+ const machineKey = deriveMachineKey();
3361
+ if (machineKey) {
3362
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3363
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3364
+ await chmod2(keyPath, 384);
3365
+ return "encrypted";
3366
+ }
3367
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3368
+ await chmod2(keyPath, 384);
3369
+ return "plaintext";
3370
+ }
3347
3371
  async function getMasterKey() {
3348
3372
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3349
3373
  if (nativeValue) {
@@ -3395,6 +3419,20 @@ async function getMasterKey() {
3395
3419
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3396
3420
  if (migrated) {
3397
3421
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3422
+ try {
3423
+ await unlink(keyPath);
3424
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3425
+ } catch {
3426
+ }
3427
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3428
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3429
+ if (fallback === "encrypted") {
3430
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3431
+ } else {
3432
+ process.stderr.write(
3433
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3434
+ );
3435
+ }
3398
3436
  }
3399
3437
  return key;
3400
3438
  } catch (err) {
@@ -3667,6 +3705,7 @@ var init_memory_write_governor = __esm({
3667
3705
  // src/lib/shard-manager.ts
3668
3706
  var shard_manager_exports = {};
3669
3707
  __export(shard_manager_exports, {
3708
+ auditShardHealth: () => auditShardHealth,
3670
3709
  disposeShards: () => disposeShards,
3671
3710
  ensureShardSchema: () => ensureShardSchema,
3672
3711
  getOpenShardCount: () => getOpenShardCount,
@@ -3733,6 +3772,70 @@ function listShards() {
3733
3772
  if (!existsSync12(SHARDS_DIR)) return [];
3734
3773
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3735
3774
  }
3775
+ async function auditShardHealth(options = {}) {
3776
+ if (!_encryptionKey) {
3777
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3778
+ }
3779
+ const repair = options.repair === true;
3780
+ const dryRun = options.dryRun === true;
3781
+ const names = listShards();
3782
+ const shards = [];
3783
+ for (const name of names) {
3784
+ const dbPath = path14.join(SHARDS_DIR, `${name}.db`);
3785
+ const stat = statSync2(dbPath);
3786
+ const item = {
3787
+ name,
3788
+ path: dbPath,
3789
+ ok: false,
3790
+ unreadable: false,
3791
+ error: null,
3792
+ size: stat.size,
3793
+ mtime: stat.mtime.toISOString(),
3794
+ memoryCount: null
3795
+ };
3796
+ const client = createClient2({
3797
+ url: `file:${dbPath}`,
3798
+ encryptionKey: _encryptionKey
3799
+ });
3800
+ try {
3801
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3802
+ const hasMemories = await client.execute(
3803
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3804
+ );
3805
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3806
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3807
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3808
+ }
3809
+ item.ok = true;
3810
+ } catch (err) {
3811
+ const message = err instanceof Error ? err.message : String(err);
3812
+ item.error = message;
3813
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3814
+ if (item.unreadable && repair && !dryRun) {
3815
+ client.close();
3816
+ _shards.delete(name);
3817
+ _shardLastAccess.delete(name);
3818
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3819
+ const archivedPath = path14.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3820
+ renameSync4(dbPath, archivedPath);
3821
+ item.archivedPath = archivedPath;
3822
+ }
3823
+ } finally {
3824
+ try {
3825
+ client.close();
3826
+ } catch {
3827
+ }
3828
+ }
3829
+ shards.push(item);
3830
+ }
3831
+ return {
3832
+ total: shards.length,
3833
+ ok: shards.filter((s) => s.ok).length,
3834
+ unreadable: shards.filter((s) => s.unreadable).length,
3835
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3836
+ shards
3837
+ };
3838
+ }
3736
3839
  async function ensureShardSchema(client) {
3737
3840
  await client.execute("PRAGMA journal_mode = WAL");
3738
3841
  await client.execute("PRAGMA busy_timeout = 30000");
@@ -3017,6 +3017,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
3017
3017
  return null;
3018
3018
  }
3019
3019
  }
3020
+ async function writeMachineBoundFileFallback(b64) {
3021
+ const dir = getKeyDir();
3022
+ await mkdir3(dir, { recursive: true });
3023
+ const keyPath = getKeyPath();
3024
+ const machineKey = deriveMachineKey();
3025
+ if (machineKey) {
3026
+ const encrypted = encryptWithMachineKey(b64, machineKey);
3027
+ await writeFile3(keyPath, encrypted + "\n", "utf-8");
3028
+ await chmod2(keyPath, 384);
3029
+ return "encrypted";
3030
+ }
3031
+ await writeFile3(keyPath, b64 + "\n", "utf-8");
3032
+ await chmod2(keyPath, 384);
3033
+ return "plaintext";
3034
+ }
3020
3035
  async function getMasterKey() {
3021
3036
  const nativeValue = macKeychainGet() ?? linuxSecretGet();
3022
3037
  if (nativeValue) {
@@ -3068,6 +3083,20 @@ async function getMasterKey() {
3068
3083
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3069
3084
  if (migrated) {
3070
3085
  process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3086
+ try {
3087
+ await unlink(keyPath);
3088
+ process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
3089
+ } catch {
3090
+ }
3091
+ } else if (!content.startsWith(ENCRYPTED_PREFIX)) {
3092
+ const fallback = await writeMachineBoundFileFallback(b64Value);
3093
+ if (fallback === "encrypted") {
3094
+ process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
3095
+ } else {
3096
+ process.stderr.write(
3097
+ "[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
3098
+ );
3099
+ }
3071
3100
  }
3072
3101
  return key;
3073
3102
  } catch (err) {
@@ -3091,18 +3120,10 @@ async function setMasterKey(key) {
3091
3120
  } catch {
3092
3121
  }
3093
3122
  }
3094
- const dir = getKeyDir();
3095
- await mkdir3(dir, { recursive: true });
3096
- const keyPath = getKeyPath();
3097
- const machineKey = deriveMachineKey();
3098
- if (machineKey) {
3099
- const encrypted = encryptWithMachineKey(b64, machineKey);
3100
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
3101
- await chmod2(keyPath, 384);
3123
+ const fallback = await writeMachineBoundFileFallback(b64);
3124
+ if (fallback === "encrypted") {
3102
3125
  process.stderr.write("[keychain] Key stored encrypted (machine-bound).\n");
3103
3126
  } else {
3104
- await writeFile3(keyPath, b64 + "\n", "utf-8");
3105
- await chmod2(keyPath, 384);
3106
3127
  process.stderr.write(
3107
3128
  "[keychain] WARNING: Key stored in plaintext file \u2014 no OS keychain available.\n"
3108
3129
  );
@@ -3220,6 +3241,7 @@ var init_state_bus = __esm({
3220
3241
  // src/lib/shard-manager.ts
3221
3242
  var shard_manager_exports = {};
3222
3243
  __export(shard_manager_exports, {
3244
+ auditShardHealth: () => auditShardHealth,
3223
3245
  disposeShards: () => disposeShards,
3224
3246
  ensureShardSchema: () => ensureShardSchema,
3225
3247
  getOpenShardCount: () => getOpenShardCount,
@@ -3286,6 +3308,70 @@ function listShards() {
3286
3308
  if (!existsSync7(SHARDS_DIR)) return [];
3287
3309
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
3288
3310
  }
3311
+ async function auditShardHealth(options = {}) {
3312
+ if (!_encryptionKey) {
3313
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
3314
+ }
3315
+ const repair = options.repair === true;
3316
+ const dryRun = options.dryRun === true;
3317
+ const names = listShards();
3318
+ const shards = [];
3319
+ for (const name of names) {
3320
+ const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
3321
+ const stat = statSync2(dbPath);
3322
+ const item = {
3323
+ name,
3324
+ path: dbPath,
3325
+ ok: false,
3326
+ unreadable: false,
3327
+ error: null,
3328
+ size: stat.size,
3329
+ mtime: stat.mtime.toISOString(),
3330
+ memoryCount: null
3331
+ };
3332
+ const client = createClient2({
3333
+ url: `file:${dbPath}`,
3334
+ encryptionKey: _encryptionKey
3335
+ });
3336
+ try {
3337
+ await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
3338
+ const hasMemories = await client.execute(
3339
+ "SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
3340
+ );
3341
+ if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
3342
+ const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
3343
+ item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
3344
+ }
3345
+ item.ok = true;
3346
+ } catch (err) {
3347
+ const message = err instanceof Error ? err.message : String(err);
3348
+ item.error = message;
3349
+ item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
3350
+ if (item.unreadable && repair && !dryRun) {
3351
+ client.close();
3352
+ _shards.delete(name);
3353
+ _shardLastAccess.delete(name);
3354
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3355
+ const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
3356
+ renameSync3(dbPath, archivedPath);
3357
+ item.archivedPath = archivedPath;
3358
+ }
3359
+ } finally {
3360
+ try {
3361
+ client.close();
3362
+ } catch {
3363
+ }
3364
+ }
3365
+ shards.push(item);
3366
+ }
3367
+ return {
3368
+ total: shards.length,
3369
+ ok: shards.filter((s) => s.ok).length,
3370
+ unreadable: shards.filter((s) => s.unreadable).length,
3371
+ archived: shards.filter((s) => Boolean(s.archivedPath)).length,
3372
+ shards
3373
+ };
3374
+ }
3289
3375
  async function ensureShardSchema(client) {
3290
3376
  await client.execute("PRAGMA journal_mode = WAL");
3291
3377
  await client.execute("PRAGMA busy_timeout = 30000");