@askexenow/exe-os 0.9.31 → 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 +144 -16
  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 +148 -20
  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 +148 -20
  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 +180 -51
  48. package/dist/hooks/session-end.js +141 -13
  49. package/dist/hooks/session-start.js +154 -26
  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 +144 -16
  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 +695 -209
  62. package/dist/runtime/index.js +136 -8
  63. package/dist/tui/App.js +208 -31
  64. package/package.json +1 -1
@@ -1033,8 +1033,8 @@ function findPackageRoot() {
1033
1033
  function getAvailableMemoryGB() {
1034
1034
  if (process.platform === "darwin") {
1035
1035
  try {
1036
- const { execSync: execSync2 } = __require("child_process");
1037
- const vmstat = execSync2("vm_stat", { encoding: "utf8" });
1036
+ const { execSync: execSync3 } = __require("child_process");
1037
+ const vmstat = execSync3("vm_stat", { encoding: "utf8" });
1038
1038
  const pageSize = 16384;
1039
1039
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1040
1040
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -3290,6 +3290,7 @@ import { createHash } from "crypto";
3290
3290
  // src/lib/keychain.ts
3291
3291
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3292
3292
  import { existsSync as existsSync6 } from "fs";
3293
+ import { execSync as execSync2 } from "child_process";
3293
3294
  import path6 from "path";
3294
3295
  import os5 from "os";
3295
3296
  var SERVICE = "exe-mem";
@@ -3300,6 +3301,59 @@ function getKeyDir() {
3300
3301
  function getKeyPath() {
3301
3302
  return path6.join(getKeyDir(), "master.key");
3302
3303
  }
3304
+ function macKeychainGet() {
3305
+ if (process.platform !== "darwin") return null;
3306
+ try {
3307
+ return execSync2(
3308
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3309
+ { encoding: "utf-8", timeout: 5e3 }
3310
+ ).trim();
3311
+ } catch {
3312
+ return null;
3313
+ }
3314
+ }
3315
+ function macKeychainSet(value) {
3316
+ if (process.platform !== "darwin") return false;
3317
+ try {
3318
+ try {
3319
+ execSync2(
3320
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3321
+ { timeout: 5e3 }
3322
+ );
3323
+ } catch {
3324
+ }
3325
+ execSync2(
3326
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3327
+ { timeout: 5e3 }
3328
+ );
3329
+ return true;
3330
+ } catch {
3331
+ return false;
3332
+ }
3333
+ }
3334
+ function linuxSecretGet() {
3335
+ if (process.platform !== "linux") return null;
3336
+ try {
3337
+ return execSync2(
3338
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3339
+ { encoding: "utf-8", timeout: 5e3 }
3340
+ ).trim();
3341
+ } catch {
3342
+ return null;
3343
+ }
3344
+ }
3345
+ function linuxSecretSet(value) {
3346
+ if (process.platform !== "linux") return false;
3347
+ try {
3348
+ execSync2(
3349
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3350
+ { timeout: 5e3 }
3351
+ );
3352
+ return true;
3353
+ } catch {
3354
+ return false;
3355
+ }
3356
+ }
3303
3357
  async function tryKeytar() {
3304
3358
  try {
3305
3359
  return await import("keytar");
@@ -3307,13 +3361,64 @@ async function tryKeytar() {
3307
3361
  return null;
3308
3362
  }
3309
3363
  }
3364
+ var ENCRYPTED_PREFIX = "enc:";
3365
+ function deriveMachineKey() {
3366
+ try {
3367
+ const crypto3 = __require("crypto");
3368
+ const material = [
3369
+ os5.hostname(),
3370
+ os5.userInfo().username,
3371
+ os5.arch(),
3372
+ os5.platform(),
3373
+ // Machine ID on Linux (stable across reboots)
3374
+ process.platform === "linux" ? readMachineId() : ""
3375
+ ].join("|");
3376
+ return crypto3.createHash("sha256").update(material).digest();
3377
+ } catch {
3378
+ return null;
3379
+ }
3380
+ }
3381
+ function readMachineId() {
3382
+ try {
3383
+ const { readFileSync: readFileSync5 } = __require("fs");
3384
+ return readFileSync5("/etc/machine-id", "utf-8").trim();
3385
+ } catch {
3386
+ return "";
3387
+ }
3388
+ }
3389
+ function decryptWithMachineKey(encrypted, machineKey) {
3390
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3391
+ try {
3392
+ const crypto3 = __require("crypto");
3393
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3394
+ if (parts.length !== 3) return null;
3395
+ const [ivB64, tagB64, cipherB64] = parts;
3396
+ const iv = Buffer.from(ivB64, "base64");
3397
+ const authTag = Buffer.from(tagB64, "base64");
3398
+ const decipher = crypto3.createDecipheriv("aes-256-gcm", machineKey, iv);
3399
+ decipher.setAuthTag(authTag);
3400
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3401
+ decrypted += decipher.final("utf-8");
3402
+ return decrypted;
3403
+ } catch {
3404
+ return null;
3405
+ }
3406
+ }
3310
3407
  async function getMasterKey() {
3408
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3409
+ if (nativeValue) {
3410
+ return Buffer.from(nativeValue, "base64");
3411
+ }
3311
3412
  const keytar = await tryKeytar();
3312
3413
  if (keytar) {
3313
3414
  try {
3314
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3315
- if (stored) {
3316
- return Buffer.from(stored, "base64");
3415
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3416
+ if (keytarValue) {
3417
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3418
+ if (migrated) {
3419
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3420
+ }
3421
+ return Buffer.from(keytarValue, "base64");
3317
3422
  }
3318
3423
  } catch {
3319
3424
  }
@@ -3327,8 +3432,31 @@ async function getMasterKey() {
3327
3432
  return null;
3328
3433
  }
3329
3434
  try {
3330
- const content = await readFile3(keyPath, "utf-8");
3331
- return Buffer.from(content.trim(), "base64");
3435
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3436
+ let b64Value;
3437
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3438
+ const machineKey = deriveMachineKey();
3439
+ if (!machineKey) {
3440
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3441
+ return null;
3442
+ }
3443
+ const decrypted = decryptWithMachineKey(content, machineKey);
3444
+ if (!decrypted) {
3445
+ process.stderr.write(
3446
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3447
+ );
3448
+ return null;
3449
+ }
3450
+ b64Value = decrypted;
3451
+ } else {
3452
+ b64Value = content;
3453
+ }
3454
+ const key = Buffer.from(b64Value, "base64");
3455
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3456
+ if (migrated) {
3457
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3458
+ }
3459
+ return key;
3332
3460
  } catch (err) {
3333
3461
  process.stderr.write(
3334
3462
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -1033,8 +1033,8 @@ function findPackageRoot() {
1033
1033
  function getAvailableMemoryGB() {
1034
1034
  if (process.platform === "darwin") {
1035
1035
  try {
1036
- const { execSync: execSync2 } = __require("child_process");
1037
- const vmstat = execSync2("vm_stat", { encoding: "utf8" });
1036
+ const { execSync: execSync3 } = __require("child_process");
1037
+ const vmstat = execSync3("vm_stat", { encoding: "utf8" });
1038
1038
  const pageSize = 16384;
1039
1039
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1040
1040
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -3289,6 +3289,7 @@ import { createHash } from "crypto";
3289
3289
  // src/lib/keychain.ts
3290
3290
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3291
3291
  import { existsSync as existsSync6 } from "fs";
3292
+ import { execSync as execSync2 } from "child_process";
3292
3293
  import path6 from "path";
3293
3294
  import os5 from "os";
3294
3295
  var SERVICE = "exe-mem";
@@ -3299,6 +3300,59 @@ function getKeyDir() {
3299
3300
  function getKeyPath() {
3300
3301
  return path6.join(getKeyDir(), "master.key");
3301
3302
  }
3303
+ function macKeychainGet() {
3304
+ if (process.platform !== "darwin") return null;
3305
+ try {
3306
+ return execSync2(
3307
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3308
+ { encoding: "utf-8", timeout: 5e3 }
3309
+ ).trim();
3310
+ } catch {
3311
+ return null;
3312
+ }
3313
+ }
3314
+ function macKeychainSet(value) {
3315
+ if (process.platform !== "darwin") return false;
3316
+ try {
3317
+ try {
3318
+ execSync2(
3319
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3320
+ { timeout: 5e3 }
3321
+ );
3322
+ } catch {
3323
+ }
3324
+ execSync2(
3325
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3326
+ { timeout: 5e3 }
3327
+ );
3328
+ return true;
3329
+ } catch {
3330
+ return false;
3331
+ }
3332
+ }
3333
+ function linuxSecretGet() {
3334
+ if (process.platform !== "linux") return null;
3335
+ try {
3336
+ return execSync2(
3337
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3338
+ { encoding: "utf-8", timeout: 5e3 }
3339
+ ).trim();
3340
+ } catch {
3341
+ return null;
3342
+ }
3343
+ }
3344
+ function linuxSecretSet(value) {
3345
+ if (process.platform !== "linux") return false;
3346
+ try {
3347
+ execSync2(
3348
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3349
+ { timeout: 5e3 }
3350
+ );
3351
+ return true;
3352
+ } catch {
3353
+ return false;
3354
+ }
3355
+ }
3302
3356
  async function tryKeytar() {
3303
3357
  try {
3304
3358
  return await import("keytar");
@@ -3306,13 +3360,64 @@ async function tryKeytar() {
3306
3360
  return null;
3307
3361
  }
3308
3362
  }
3363
+ var ENCRYPTED_PREFIX = "enc:";
3364
+ function deriveMachineKey() {
3365
+ try {
3366
+ const crypto3 = __require("crypto");
3367
+ const material = [
3368
+ os5.hostname(),
3369
+ os5.userInfo().username,
3370
+ os5.arch(),
3371
+ os5.platform(),
3372
+ // Machine ID on Linux (stable across reboots)
3373
+ process.platform === "linux" ? readMachineId() : ""
3374
+ ].join("|");
3375
+ return crypto3.createHash("sha256").update(material).digest();
3376
+ } catch {
3377
+ return null;
3378
+ }
3379
+ }
3380
+ function readMachineId() {
3381
+ try {
3382
+ const { readFileSync: readFileSync5 } = __require("fs");
3383
+ return readFileSync5("/etc/machine-id", "utf-8").trim();
3384
+ } catch {
3385
+ return "";
3386
+ }
3387
+ }
3388
+ function decryptWithMachineKey(encrypted, machineKey) {
3389
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3390
+ try {
3391
+ const crypto3 = __require("crypto");
3392
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3393
+ if (parts.length !== 3) return null;
3394
+ const [ivB64, tagB64, cipherB64] = parts;
3395
+ const iv = Buffer.from(ivB64, "base64");
3396
+ const authTag = Buffer.from(tagB64, "base64");
3397
+ const decipher = crypto3.createDecipheriv("aes-256-gcm", machineKey, iv);
3398
+ decipher.setAuthTag(authTag);
3399
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3400
+ decrypted += decipher.final("utf-8");
3401
+ return decrypted;
3402
+ } catch {
3403
+ return null;
3404
+ }
3405
+ }
3309
3406
  async function getMasterKey() {
3407
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3408
+ if (nativeValue) {
3409
+ return Buffer.from(nativeValue, "base64");
3410
+ }
3310
3411
  const keytar = await tryKeytar();
3311
3412
  if (keytar) {
3312
3413
  try {
3313
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3314
- if (stored) {
3315
- return Buffer.from(stored, "base64");
3414
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3415
+ if (keytarValue) {
3416
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3417
+ if (migrated) {
3418
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3419
+ }
3420
+ return Buffer.from(keytarValue, "base64");
3316
3421
  }
3317
3422
  } catch {
3318
3423
  }
@@ -3326,8 +3431,31 @@ async function getMasterKey() {
3326
3431
  return null;
3327
3432
  }
3328
3433
  try {
3329
- const content = await readFile3(keyPath, "utf-8");
3330
- return Buffer.from(content.trim(), "base64");
3434
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3435
+ let b64Value;
3436
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3437
+ const machineKey = deriveMachineKey();
3438
+ if (!machineKey) {
3439
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3440
+ return null;
3441
+ }
3442
+ const decrypted = decryptWithMachineKey(content, machineKey);
3443
+ if (!decrypted) {
3444
+ process.stderr.write(
3445
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3446
+ );
3447
+ return null;
3448
+ }
3449
+ b64Value = decrypted;
3450
+ } else {
3451
+ b64Value = content;
3452
+ }
3453
+ const key = Buffer.from(b64Value, "base64");
3454
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3455
+ if (migrated) {
3456
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3457
+ }
3458
+ return key;
3331
3459
  } catch (err) {
3332
3460
  process.stderr.write(
3333
3461
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -1029,8 +1029,8 @@ function findPackageRoot() {
1029
1029
  function getAvailableMemoryGB() {
1030
1030
  if (process.platform === "darwin") {
1031
1031
  try {
1032
- const { execSync: execSync2 } = __require("child_process");
1033
- const vmstat = execSync2("vm_stat", { encoding: "utf8" });
1032
+ const { execSync: execSync3 } = __require("child_process");
1033
+ const vmstat = execSync3("vm_stat", { encoding: "utf8" });
1034
1034
  const pageSize = 16384;
1035
1035
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1036
1036
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -3277,6 +3277,7 @@ import { createHash } from "crypto";
3277
3277
  // src/lib/keychain.ts
3278
3278
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3279
3279
  import { existsSync as existsSync6 } from "fs";
3280
+ import { execSync as execSync2 } from "child_process";
3280
3281
  import path6 from "path";
3281
3282
  import os5 from "os";
3282
3283
  var SERVICE = "exe-mem";
@@ -3287,6 +3288,59 @@ function getKeyDir() {
3287
3288
  function getKeyPath() {
3288
3289
  return path6.join(getKeyDir(), "master.key");
3289
3290
  }
3291
+ function macKeychainGet() {
3292
+ if (process.platform !== "darwin") return null;
3293
+ try {
3294
+ return execSync2(
3295
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3296
+ { encoding: "utf-8", timeout: 5e3 }
3297
+ ).trim();
3298
+ } catch {
3299
+ return null;
3300
+ }
3301
+ }
3302
+ function macKeychainSet(value) {
3303
+ if (process.platform !== "darwin") return false;
3304
+ try {
3305
+ try {
3306
+ execSync2(
3307
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3308
+ { timeout: 5e3 }
3309
+ );
3310
+ } catch {
3311
+ }
3312
+ execSync2(
3313
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3314
+ { timeout: 5e3 }
3315
+ );
3316
+ return true;
3317
+ } catch {
3318
+ return false;
3319
+ }
3320
+ }
3321
+ function linuxSecretGet() {
3322
+ if (process.platform !== "linux") return null;
3323
+ try {
3324
+ return execSync2(
3325
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3326
+ { encoding: "utf-8", timeout: 5e3 }
3327
+ ).trim();
3328
+ } catch {
3329
+ return null;
3330
+ }
3331
+ }
3332
+ function linuxSecretSet(value) {
3333
+ if (process.platform !== "linux") return false;
3334
+ try {
3335
+ execSync2(
3336
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3337
+ { timeout: 5e3 }
3338
+ );
3339
+ return true;
3340
+ } catch {
3341
+ return false;
3342
+ }
3343
+ }
3290
3344
  async function tryKeytar() {
3291
3345
  try {
3292
3346
  return await import("keytar");
@@ -3294,13 +3348,64 @@ async function tryKeytar() {
3294
3348
  return null;
3295
3349
  }
3296
3350
  }
3351
+ var ENCRYPTED_PREFIX = "enc:";
3352
+ function deriveMachineKey() {
3353
+ try {
3354
+ const crypto2 = __require("crypto");
3355
+ const material = [
3356
+ os5.hostname(),
3357
+ os5.userInfo().username,
3358
+ os5.arch(),
3359
+ os5.platform(),
3360
+ // Machine ID on Linux (stable across reboots)
3361
+ process.platform === "linux" ? readMachineId() : ""
3362
+ ].join("|");
3363
+ return crypto2.createHash("sha256").update(material).digest();
3364
+ } catch {
3365
+ return null;
3366
+ }
3367
+ }
3368
+ function readMachineId() {
3369
+ try {
3370
+ const { readFileSync: readFileSync5 } = __require("fs");
3371
+ return readFileSync5("/etc/machine-id", "utf-8").trim();
3372
+ } catch {
3373
+ return "";
3374
+ }
3375
+ }
3376
+ function decryptWithMachineKey(encrypted, machineKey) {
3377
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3378
+ try {
3379
+ const crypto2 = __require("crypto");
3380
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3381
+ if (parts.length !== 3) return null;
3382
+ const [ivB64, tagB64, cipherB64] = parts;
3383
+ const iv = Buffer.from(ivB64, "base64");
3384
+ const authTag = Buffer.from(tagB64, "base64");
3385
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", machineKey, iv);
3386
+ decipher.setAuthTag(authTag);
3387
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3388
+ decrypted += decipher.final("utf-8");
3389
+ return decrypted;
3390
+ } catch {
3391
+ return null;
3392
+ }
3393
+ }
3297
3394
  async function getMasterKey() {
3395
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3396
+ if (nativeValue) {
3397
+ return Buffer.from(nativeValue, "base64");
3398
+ }
3298
3399
  const keytar = await tryKeytar();
3299
3400
  if (keytar) {
3300
3401
  try {
3301
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3302
- if (stored) {
3303
- return Buffer.from(stored, "base64");
3402
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3403
+ if (keytarValue) {
3404
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3405
+ if (migrated) {
3406
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3407
+ }
3408
+ return Buffer.from(keytarValue, "base64");
3304
3409
  }
3305
3410
  } catch {
3306
3411
  }
@@ -3314,8 +3419,31 @@ async function getMasterKey() {
3314
3419
  return null;
3315
3420
  }
3316
3421
  try {
3317
- const content = await readFile3(keyPath, "utf-8");
3318
- return Buffer.from(content.trim(), "base64");
3422
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3423
+ let b64Value;
3424
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3425
+ const machineKey = deriveMachineKey();
3426
+ if (!machineKey) {
3427
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3428
+ return null;
3429
+ }
3430
+ const decrypted = decryptWithMachineKey(content, machineKey);
3431
+ if (!decrypted) {
3432
+ process.stderr.write(
3433
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3434
+ );
3435
+ return null;
3436
+ }
3437
+ b64Value = decrypted;
3438
+ } else {
3439
+ b64Value = content;
3440
+ }
3441
+ const key = Buffer.from(b64Value, "base64");
3442
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3443
+ if (migrated) {
3444
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3445
+ }
3446
+ return key;
3319
3447
  } catch (err) {
3320
3448
  process.stderr.write(
3321
3449
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}