@askexenow/exe-os 0.9.30 → 0.9.32

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 (64) hide show
  1. package/dist/bin/backfill-conversations.js +135 -7
  2. package/dist/bin/backfill-responses.js +135 -7
  3. package/dist/bin/backfill-vectors.js +135 -7
  4. package/dist/bin/cleanup-stale-review-tasks.js +139 -11
  5. package/dist/bin/cli.js +812 -486
  6. package/dist/bin/exe-assign.js +135 -7
  7. package/dist/bin/exe-boot.js +422 -113
  8. package/dist/bin/exe-cloud.js +160 -9
  9. package/dist/bin/exe-dispatch.js +136 -8
  10. package/dist/bin/exe-doctor.js +255 -13
  11. package/dist/bin/exe-export-behaviors.js +136 -8
  12. package/dist/bin/exe-forget.js +136 -8
  13. package/dist/bin/exe-gateway.js +171 -24
  14. package/dist/bin/exe-heartbeat.js +141 -13
  15. package/dist/bin/exe-kill.js +140 -12
  16. package/dist/bin/exe-launch-agent.js +143 -15
  17. package/dist/bin/exe-link.js +357 -48
  18. package/dist/bin/exe-pending-messages.js +136 -8
  19. package/dist/bin/exe-pending-notifications.js +136 -8
  20. package/dist/bin/exe-pending-reviews.js +138 -10
  21. package/dist/bin/exe-review.js +136 -8
  22. package/dist/bin/exe-search.js +155 -20
  23. package/dist/bin/exe-session-cleanup.js +166 -38
  24. package/dist/bin/exe-start-codex.js +142 -14
  25. package/dist/bin/exe-start-opencode.js +140 -12
  26. package/dist/bin/exe-status.js +148 -20
  27. package/dist/bin/exe-team.js +136 -8
  28. package/dist/bin/git-sweep.js +138 -10
  29. package/dist/bin/graph-backfill.js +135 -7
  30. package/dist/bin/graph-export.js +136 -8
  31. package/dist/bin/intercom-check.js +153 -25
  32. package/dist/bin/scan-tasks.js +138 -10
  33. package/dist/bin/setup.js +447 -121
  34. package/dist/bin/shard-migrate.js +135 -7
  35. package/dist/gateway/index.js +151 -23
  36. package/dist/hooks/bug-report-worker.js +151 -23
  37. package/dist/hooks/codex-stop-task-finalizer.js +145 -17
  38. package/dist/hooks/commit-complete.js +138 -10
  39. package/dist/hooks/error-recall.js +159 -24
  40. package/dist/hooks/ingest.js +142 -14
  41. package/dist/hooks/instructions-loaded.js +136 -8
  42. package/dist/hooks/notification.js +136 -8
  43. package/dist/hooks/post-compact.js +136 -8
  44. package/dist/hooks/post-tool-combined.js +159 -24
  45. package/dist/hooks/pre-compact.js +136 -8
  46. package/dist/hooks/pre-tool-use.js +144 -16
  47. package/dist/hooks/prompt-submit.js +195 -55
  48. package/dist/hooks/session-end.js +141 -13
  49. package/dist/hooks/session-start.js +165 -30
  50. package/dist/hooks/stop.js +136 -8
  51. package/dist/hooks/subagent-stop.js +136 -8
  52. package/dist/hooks/summary-worker.js +374 -65
  53. package/dist/index.js +136 -8
  54. package/dist/lib/cloud-sync.js +355 -46
  55. package/dist/lib/consolidation.js +1 -0
  56. package/dist/lib/exe-daemon.js +469 -127
  57. package/dist/lib/hybrid-search.js +155 -20
  58. package/dist/lib/keychain.js +191 -7
  59. package/dist/lib/schedules.js +138 -10
  60. package/dist/lib/store.js +135 -7
  61. package/dist/mcp/server.js +706 -213
  62. package/dist/runtime/index.js +136 -8
  63. package/dist/tui/App.js +208 -31
  64. package/package.json +1 -1
@@ -1411,8 +1411,8 @@ function findPackageRoot() {
1411
1411
  function getAvailableMemoryGB() {
1412
1412
  if (process.platform === "darwin") {
1413
1413
  try {
1414
- const { execSync: execSync6 } = __require("child_process");
1415
- const vmstat = execSync6("vm_stat", { encoding: "utf8" });
1414
+ const { execSync: execSync7 } = __require("child_process");
1415
+ const vmstat = execSync7("vm_stat", { encoding: "utf8" });
1416
1416
  const pageSize = 16384;
1417
1417
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1418
1418
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -2947,6 +2947,7 @@ var init_database = __esm({
2947
2947
  // src/lib/keychain.ts
2948
2948
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2949
2949
  import { existsSync as existsSync9 } from "fs";
2950
+ import { execSync as execSync4 } from "child_process";
2950
2951
  import path10 from "path";
2951
2952
  import os6 from "os";
2952
2953
  function getKeyDir() {
@@ -2955,6 +2956,59 @@ function getKeyDir() {
2955
2956
  function getKeyPath() {
2956
2957
  return path10.join(getKeyDir(), "master.key");
2957
2958
  }
2959
+ function macKeychainGet() {
2960
+ if (process.platform !== "darwin") return null;
2961
+ try {
2962
+ return execSync4(
2963
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
2964
+ { encoding: "utf-8", timeout: 5e3 }
2965
+ ).trim();
2966
+ } catch {
2967
+ return null;
2968
+ }
2969
+ }
2970
+ function macKeychainSet(value) {
2971
+ if (process.platform !== "darwin") return false;
2972
+ try {
2973
+ try {
2974
+ execSync4(
2975
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
2976
+ { timeout: 5e3 }
2977
+ );
2978
+ } catch {
2979
+ }
2980
+ execSync4(
2981
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
2982
+ { timeout: 5e3 }
2983
+ );
2984
+ return true;
2985
+ } catch {
2986
+ return false;
2987
+ }
2988
+ }
2989
+ function linuxSecretGet() {
2990
+ if (process.platform !== "linux") return null;
2991
+ try {
2992
+ return execSync4(
2993
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
2994
+ { encoding: "utf-8", timeout: 5e3 }
2995
+ ).trim();
2996
+ } catch {
2997
+ return null;
2998
+ }
2999
+ }
3000
+ function linuxSecretSet(value) {
3001
+ if (process.platform !== "linux") return false;
3002
+ try {
3003
+ execSync4(
3004
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3005
+ { timeout: 5e3 }
3006
+ );
3007
+ return true;
3008
+ } catch {
3009
+ return false;
3010
+ }
3011
+ }
2958
3012
  async function tryKeytar() {
2959
3013
  try {
2960
3014
  return await import("keytar");
@@ -2962,13 +3016,63 @@ async function tryKeytar() {
2962
3016
  return null;
2963
3017
  }
2964
3018
  }
3019
+ function deriveMachineKey() {
3020
+ try {
3021
+ const crypto3 = __require("crypto");
3022
+ const material = [
3023
+ os6.hostname(),
3024
+ os6.userInfo().username,
3025
+ os6.arch(),
3026
+ os6.platform(),
3027
+ // Machine ID on Linux (stable across reboots)
3028
+ process.platform === "linux" ? readMachineId() : ""
3029
+ ].join("|");
3030
+ return crypto3.createHash("sha256").update(material).digest();
3031
+ } catch {
3032
+ return null;
3033
+ }
3034
+ }
3035
+ function readMachineId() {
3036
+ try {
3037
+ const { readFileSync: readFileSync9 } = __require("fs");
3038
+ return readFileSync9("/etc/machine-id", "utf-8").trim();
3039
+ } catch {
3040
+ return "";
3041
+ }
3042
+ }
3043
+ function decryptWithMachineKey(encrypted, machineKey) {
3044
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3045
+ try {
3046
+ const crypto3 = __require("crypto");
3047
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3048
+ if (parts.length !== 3) return null;
3049
+ const [ivB64, tagB64, cipherB64] = parts;
3050
+ const iv = Buffer.from(ivB64, "base64");
3051
+ const authTag = Buffer.from(tagB64, "base64");
3052
+ const decipher = crypto3.createDecipheriv("aes-256-gcm", machineKey, iv);
3053
+ decipher.setAuthTag(authTag);
3054
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3055
+ decrypted += decipher.final("utf-8");
3056
+ return decrypted;
3057
+ } catch {
3058
+ return null;
3059
+ }
3060
+ }
2965
3061
  async function getMasterKey() {
3062
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3063
+ if (nativeValue) {
3064
+ return Buffer.from(nativeValue, "base64");
3065
+ }
2966
3066
  const keytar = await tryKeytar();
2967
3067
  if (keytar) {
2968
3068
  try {
2969
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
2970
- if (stored) {
2971
- return Buffer.from(stored, "base64");
3069
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3070
+ if (keytarValue) {
3071
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3072
+ if (migrated) {
3073
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3074
+ }
3075
+ return Buffer.from(keytarValue, "base64");
2972
3076
  }
2973
3077
  } catch {
2974
3078
  }
@@ -2982,8 +3086,31 @@ async function getMasterKey() {
2982
3086
  return null;
2983
3087
  }
2984
3088
  try {
2985
- const content = await readFile3(keyPath, "utf-8");
2986
- return Buffer.from(content.trim(), "base64");
3089
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3090
+ let b64Value;
3091
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3092
+ const machineKey = deriveMachineKey();
3093
+ if (!machineKey) {
3094
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3095
+ return null;
3096
+ }
3097
+ const decrypted = decryptWithMachineKey(content, machineKey);
3098
+ if (!decrypted) {
3099
+ process.stderr.write(
3100
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3101
+ );
3102
+ return null;
3103
+ }
3104
+ b64Value = decrypted;
3105
+ } else {
3106
+ b64Value = content;
3107
+ }
3108
+ const key = Buffer.from(b64Value, "base64");
3109
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3110
+ if (migrated) {
3111
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3112
+ }
3113
+ return key;
2987
3114
  } catch (err) {
2988
3115
  process.stderr.write(
2989
3116
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -2992,12 +3119,13 @@ async function getMasterKey() {
2992
3119
  return null;
2993
3120
  }
2994
3121
  }
2995
- var SERVICE, ACCOUNT;
3122
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
2996
3123
  var init_keychain = __esm({
2997
3124
  "src/lib/keychain.ts"() {
2998
3125
  "use strict";
2999
3126
  SERVICE = "exe-mem";
3000
3127
  ACCOUNT = "master-key";
3128
+ ENCRYPTED_PREFIX = "enc:";
3001
3129
  }
3002
3130
  });
3003
3131
 
@@ -4235,11 +4363,11 @@ __export(git_staleness_exports, {
4235
4363
  detectStaleFiles: () => detectStaleFiles,
4236
4364
  recordFileRead: () => recordFileRead
4237
4365
  });
4238
- import { execSync as execSync4 } from "child_process";
4366
+ import { execSync as execSync5 } from "child_process";
4239
4367
  import path12 from "path";
4240
4368
  function getHeadCommit(cwd) {
4241
4369
  try {
4242
- return execSync4("git rev-parse --short HEAD", {
4370
+ return execSync5("git rev-parse --short HEAD", {
4243
4371
  cwd,
4244
4372
  timeout: GIT_TIMEOUT_MS,
4245
4373
  encoding: "utf-8"
@@ -4302,7 +4430,7 @@ async function detectStaleFiles(agentId, cwd) {
4302
4430
  const readAt = String(record.read_at ?? "");
4303
4431
  if (!filePath || !readAt) continue;
4304
4432
  try {
4305
- const gitSummary = execSync4(
4433
+ const gitSummary = execSync5(
4306
4434
  `git log -1 --oneline --after=${JSON.stringify(readAt)} --format="%h %an: %s" -- ${JSON.stringify(filePath)}`,
4307
4435
  {
4308
4436
  cwd,
@@ -4539,7 +4667,7 @@ __export(project_name_exports, {
4539
4667
  _resetCache: () => _resetCache,
4540
4668
  getProjectName: () => getProjectName
4541
4669
  });
4542
- import { execSync as execSync5 } from "child_process";
4670
+ import { execSync as execSync6 } from "child_process";
4543
4671
  import path13 from "path";
4544
4672
  function getProjectName(cwd) {
4545
4673
  const dir = cwd ?? process.cwd();
@@ -4547,7 +4675,7 @@ function getProjectName(cwd) {
4547
4675
  try {
4548
4676
  let repoRoot;
4549
4677
  try {
4550
- const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
4678
+ const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
4551
4679
  cwd: dir,
4552
4680
  encoding: "utf8",
4553
4681
  timeout: 2e3,
@@ -4555,7 +4683,7 @@ function getProjectName(cwd) {
4555
4683
  }).trim();
4556
4684
  repoRoot = path13.dirname(gitCommonDir);
4557
4685
  } catch {
4558
- repoRoot = execSync5("git rev-parse --show-toplevel", {
4686
+ repoRoot = execSync6("git rev-parse --show-toplevel", {
4559
4687
  cwd: dir,
4560
4688
  encoding: "utf8",
4561
4689
  timeout: 2e3,
@@ -1042,8 +1042,8 @@ function findPackageRoot() {
1042
1042
  function getAvailableMemoryGB() {
1043
1043
  if (process.platform === "darwin") {
1044
1044
  try {
1045
- const { execSync: execSync4 } = __require("child_process");
1046
- const vmstat = execSync4("vm_stat", { encoding: "utf8" });
1045
+ const { execSync: execSync5 } = __require("child_process");
1046
+ const vmstat = execSync5("vm_stat", { encoding: "utf8" });
1047
1047
  const pageSize = 16384;
1048
1048
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1049
1049
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -2782,6 +2782,7 @@ var init_database = __esm({
2782
2782
  // src/lib/keychain.ts
2783
2783
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2784
2784
  import { existsSync as existsSync7 } from "fs";
2785
+ import { execSync as execSync4 } from "child_process";
2785
2786
  import path8 from "path";
2786
2787
  import os5 from "os";
2787
2788
  function getKeyDir() {
@@ -2790,6 +2791,59 @@ function getKeyDir() {
2790
2791
  function getKeyPath() {
2791
2792
  return path8.join(getKeyDir(), "master.key");
2792
2793
  }
2794
+ function macKeychainGet() {
2795
+ if (process.platform !== "darwin") return null;
2796
+ try {
2797
+ return execSync4(
2798
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
2799
+ { encoding: "utf-8", timeout: 5e3 }
2800
+ ).trim();
2801
+ } catch {
2802
+ return null;
2803
+ }
2804
+ }
2805
+ function macKeychainSet(value) {
2806
+ if (process.platform !== "darwin") return false;
2807
+ try {
2808
+ try {
2809
+ execSync4(
2810
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
2811
+ { timeout: 5e3 }
2812
+ );
2813
+ } catch {
2814
+ }
2815
+ execSync4(
2816
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
2817
+ { timeout: 5e3 }
2818
+ );
2819
+ return true;
2820
+ } catch {
2821
+ return false;
2822
+ }
2823
+ }
2824
+ function linuxSecretGet() {
2825
+ if (process.platform !== "linux") return null;
2826
+ try {
2827
+ return execSync4(
2828
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
2829
+ { encoding: "utf-8", timeout: 5e3 }
2830
+ ).trim();
2831
+ } catch {
2832
+ return null;
2833
+ }
2834
+ }
2835
+ function linuxSecretSet(value) {
2836
+ if (process.platform !== "linux") return false;
2837
+ try {
2838
+ execSync4(
2839
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
2840
+ { timeout: 5e3 }
2841
+ );
2842
+ return true;
2843
+ } catch {
2844
+ return false;
2845
+ }
2846
+ }
2793
2847
  async function tryKeytar() {
2794
2848
  try {
2795
2849
  return await import("keytar");
@@ -2797,13 +2851,63 @@ async function tryKeytar() {
2797
2851
  return null;
2798
2852
  }
2799
2853
  }
2854
+ function deriveMachineKey() {
2855
+ try {
2856
+ const crypto2 = __require("crypto");
2857
+ const material = [
2858
+ os5.hostname(),
2859
+ os5.userInfo().username,
2860
+ os5.arch(),
2861
+ os5.platform(),
2862
+ // Machine ID on Linux (stable across reboots)
2863
+ process.platform === "linux" ? readMachineId() : ""
2864
+ ].join("|");
2865
+ return crypto2.createHash("sha256").update(material).digest();
2866
+ } catch {
2867
+ return null;
2868
+ }
2869
+ }
2870
+ function readMachineId() {
2871
+ try {
2872
+ const { readFileSync: readFileSync7 } = __require("fs");
2873
+ return readFileSync7("/etc/machine-id", "utf-8").trim();
2874
+ } catch {
2875
+ return "";
2876
+ }
2877
+ }
2878
+ function decryptWithMachineKey(encrypted, machineKey) {
2879
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2880
+ try {
2881
+ const crypto2 = __require("crypto");
2882
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
2883
+ if (parts.length !== 3) return null;
2884
+ const [ivB64, tagB64, cipherB64] = parts;
2885
+ const iv = Buffer.from(ivB64, "base64");
2886
+ const authTag = Buffer.from(tagB64, "base64");
2887
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", machineKey, iv);
2888
+ decipher.setAuthTag(authTag);
2889
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
2890
+ decrypted += decipher.final("utf-8");
2891
+ return decrypted;
2892
+ } catch {
2893
+ return null;
2894
+ }
2895
+ }
2800
2896
  async function getMasterKey() {
2897
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
2898
+ if (nativeValue) {
2899
+ return Buffer.from(nativeValue, "base64");
2900
+ }
2801
2901
  const keytar = await tryKeytar();
2802
2902
  if (keytar) {
2803
2903
  try {
2804
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
2805
- if (stored) {
2806
- return Buffer.from(stored, "base64");
2904
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
2905
+ if (keytarValue) {
2906
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
2907
+ if (migrated) {
2908
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
2909
+ }
2910
+ return Buffer.from(keytarValue, "base64");
2807
2911
  }
2808
2912
  } catch {
2809
2913
  }
@@ -2817,8 +2921,31 @@ async function getMasterKey() {
2817
2921
  return null;
2818
2922
  }
2819
2923
  try {
2820
- const content = await readFile3(keyPath, "utf-8");
2821
- return Buffer.from(content.trim(), "base64");
2924
+ const content = (await readFile3(keyPath, "utf-8")).trim();
2925
+ let b64Value;
2926
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
2927
+ const machineKey = deriveMachineKey();
2928
+ if (!machineKey) {
2929
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
2930
+ return null;
2931
+ }
2932
+ const decrypted = decryptWithMachineKey(content, machineKey);
2933
+ if (!decrypted) {
2934
+ process.stderr.write(
2935
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
2936
+ );
2937
+ return null;
2938
+ }
2939
+ b64Value = decrypted;
2940
+ } else {
2941
+ b64Value = content;
2942
+ }
2943
+ const key = Buffer.from(b64Value, "base64");
2944
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2945
+ if (migrated) {
2946
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
2947
+ }
2948
+ return key;
2822
2949
  } catch (err) {
2823
2950
  process.stderr.write(
2824
2951
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -2827,12 +2954,13 @@ async function getMasterKey() {
2827
2954
  return null;
2828
2955
  }
2829
2956
  }
2830
- var SERVICE, ACCOUNT;
2957
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
2831
2958
  var init_keychain = __esm({
2832
2959
  "src/lib/keychain.ts"() {
2833
2960
  "use strict";
2834
2961
  SERVICE = "exe-mem";
2835
2962
  ACCOUNT = "master-key";
2963
+ ENCRYPTED_PREFIX = "enc:";
2836
2964
  }
2837
2965
  });
2838
2966
 
@@ -1042,8 +1042,8 @@ function findPackageRoot() {
1042
1042
  function getAvailableMemoryGB() {
1043
1043
  if (process.platform === "darwin") {
1044
1044
  try {
1045
- const { execSync: execSync4 } = __require("child_process");
1046
- const vmstat = execSync4("vm_stat", { encoding: "utf8" });
1045
+ const { execSync: execSync5 } = __require("child_process");
1046
+ const vmstat = execSync5("vm_stat", { encoding: "utf8" });
1047
1047
  const pageSize = 16384;
1048
1048
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1049
1049
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -2782,6 +2782,7 @@ var init_database = __esm({
2782
2782
  // src/lib/keychain.ts
2783
2783
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2784
2784
  import { existsSync as existsSync6 } from "fs";
2785
+ import { execSync as execSync4 } from "child_process";
2785
2786
  import path7 from "path";
2786
2787
  import os5 from "os";
2787
2788
  function getKeyDir() {
@@ -2790,6 +2791,59 @@ function getKeyDir() {
2790
2791
  function getKeyPath() {
2791
2792
  return path7.join(getKeyDir(), "master.key");
2792
2793
  }
2794
+ function macKeychainGet() {
2795
+ if (process.platform !== "darwin") return null;
2796
+ try {
2797
+ return execSync4(
2798
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
2799
+ { encoding: "utf-8", timeout: 5e3 }
2800
+ ).trim();
2801
+ } catch {
2802
+ return null;
2803
+ }
2804
+ }
2805
+ function macKeychainSet(value) {
2806
+ if (process.platform !== "darwin") return false;
2807
+ try {
2808
+ try {
2809
+ execSync4(
2810
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
2811
+ { timeout: 5e3 }
2812
+ );
2813
+ } catch {
2814
+ }
2815
+ execSync4(
2816
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
2817
+ { timeout: 5e3 }
2818
+ );
2819
+ return true;
2820
+ } catch {
2821
+ return false;
2822
+ }
2823
+ }
2824
+ function linuxSecretGet() {
2825
+ if (process.platform !== "linux") return null;
2826
+ try {
2827
+ return execSync4(
2828
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
2829
+ { encoding: "utf-8", timeout: 5e3 }
2830
+ ).trim();
2831
+ } catch {
2832
+ return null;
2833
+ }
2834
+ }
2835
+ function linuxSecretSet(value) {
2836
+ if (process.platform !== "linux") return false;
2837
+ try {
2838
+ execSync4(
2839
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
2840
+ { timeout: 5e3 }
2841
+ );
2842
+ return true;
2843
+ } catch {
2844
+ return false;
2845
+ }
2846
+ }
2793
2847
  async function tryKeytar() {
2794
2848
  try {
2795
2849
  return await import("keytar");
@@ -2797,13 +2851,63 @@ async function tryKeytar() {
2797
2851
  return null;
2798
2852
  }
2799
2853
  }
2854
+ function deriveMachineKey() {
2855
+ try {
2856
+ const crypto2 = __require("crypto");
2857
+ const material = [
2858
+ os5.hostname(),
2859
+ os5.userInfo().username,
2860
+ os5.arch(),
2861
+ os5.platform(),
2862
+ // Machine ID on Linux (stable across reboots)
2863
+ process.platform === "linux" ? readMachineId() : ""
2864
+ ].join("|");
2865
+ return crypto2.createHash("sha256").update(material).digest();
2866
+ } catch {
2867
+ return null;
2868
+ }
2869
+ }
2870
+ function readMachineId() {
2871
+ try {
2872
+ const { readFileSync: readFileSync6 } = __require("fs");
2873
+ return readFileSync6("/etc/machine-id", "utf-8").trim();
2874
+ } catch {
2875
+ return "";
2876
+ }
2877
+ }
2878
+ function decryptWithMachineKey(encrypted, machineKey) {
2879
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2880
+ try {
2881
+ const crypto2 = __require("crypto");
2882
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
2883
+ if (parts.length !== 3) return null;
2884
+ const [ivB64, tagB64, cipherB64] = parts;
2885
+ const iv = Buffer.from(ivB64, "base64");
2886
+ const authTag = Buffer.from(tagB64, "base64");
2887
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", machineKey, iv);
2888
+ decipher.setAuthTag(authTag);
2889
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
2890
+ decrypted += decipher.final("utf-8");
2891
+ return decrypted;
2892
+ } catch {
2893
+ return null;
2894
+ }
2895
+ }
2800
2896
  async function getMasterKey() {
2897
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
2898
+ if (nativeValue) {
2899
+ return Buffer.from(nativeValue, "base64");
2900
+ }
2801
2901
  const keytar = await tryKeytar();
2802
2902
  if (keytar) {
2803
2903
  try {
2804
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
2805
- if (stored) {
2806
- return Buffer.from(stored, "base64");
2904
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
2905
+ if (keytarValue) {
2906
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
2907
+ if (migrated) {
2908
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
2909
+ }
2910
+ return Buffer.from(keytarValue, "base64");
2807
2911
  }
2808
2912
  } catch {
2809
2913
  }
@@ -2817,8 +2921,31 @@ async function getMasterKey() {
2817
2921
  return null;
2818
2922
  }
2819
2923
  try {
2820
- const content = await readFile3(keyPath, "utf-8");
2821
- return Buffer.from(content.trim(), "base64");
2924
+ const content = (await readFile3(keyPath, "utf-8")).trim();
2925
+ let b64Value;
2926
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
2927
+ const machineKey = deriveMachineKey();
2928
+ if (!machineKey) {
2929
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
2930
+ return null;
2931
+ }
2932
+ const decrypted = decryptWithMachineKey(content, machineKey);
2933
+ if (!decrypted) {
2934
+ process.stderr.write(
2935
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
2936
+ );
2937
+ return null;
2938
+ }
2939
+ b64Value = decrypted;
2940
+ } else {
2941
+ b64Value = content;
2942
+ }
2943
+ const key = Buffer.from(b64Value, "base64");
2944
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2945
+ if (migrated) {
2946
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
2947
+ }
2948
+ return key;
2822
2949
  } catch (err) {
2823
2950
  process.stderr.write(
2824
2951
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -2827,12 +2954,13 @@ async function getMasterKey() {
2827
2954
  return null;
2828
2955
  }
2829
2956
  }
2830
- var SERVICE, ACCOUNT;
2957
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
2831
2958
  var init_keychain = __esm({
2832
2959
  "src/lib/keychain.ts"() {
2833
2960
  "use strict";
2834
2961
  SERVICE = "exe-mem";
2835
2962
  ACCOUNT = "master-key";
2963
+ ENCRYPTED_PREFIX = "enc:";
2836
2964
  }
2837
2965
  });
2838
2966