@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
@@ -1050,8 +1050,8 @@ function findPackageRoot() {
1050
1050
  function getAvailableMemoryGB() {
1051
1051
  if (process.platform === "darwin") {
1052
1052
  try {
1053
- const { execSync: execSync4 } = __require("child_process");
1054
- const vmstat = execSync4("vm_stat", { encoding: "utf8" });
1053
+ const { execSync: execSync5 } = __require("child_process");
1054
+ const vmstat = execSync5("vm_stat", { encoding: "utf8" });
1055
1055
  const pageSize = 16384;
1056
1056
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1057
1057
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -3199,6 +3199,7 @@ var init_task_scope = __esm({
3199
3199
  // src/lib/keychain.ts
3200
3200
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3201
3201
  import { existsSync as existsSync11 } from "fs";
3202
+ import { execSync as execSync4 } from "child_process";
3202
3203
  import path12 from "path";
3203
3204
  import os9 from "os";
3204
3205
  function getKeyDir() {
@@ -3207,6 +3208,59 @@ function getKeyDir() {
3207
3208
  function getKeyPath() {
3208
3209
  return path12.join(getKeyDir(), "master.key");
3209
3210
  }
3211
+ function macKeychainGet() {
3212
+ if (process.platform !== "darwin") return null;
3213
+ try {
3214
+ return execSync4(
3215
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3216
+ { encoding: "utf-8", timeout: 5e3 }
3217
+ ).trim();
3218
+ } catch {
3219
+ return null;
3220
+ }
3221
+ }
3222
+ function macKeychainSet(value) {
3223
+ if (process.platform !== "darwin") return false;
3224
+ try {
3225
+ try {
3226
+ execSync4(
3227
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3228
+ { timeout: 5e3 }
3229
+ );
3230
+ } catch {
3231
+ }
3232
+ execSync4(
3233
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3234
+ { timeout: 5e3 }
3235
+ );
3236
+ return true;
3237
+ } catch {
3238
+ return false;
3239
+ }
3240
+ }
3241
+ function linuxSecretGet() {
3242
+ if (process.platform !== "linux") return null;
3243
+ try {
3244
+ return execSync4(
3245
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3246
+ { encoding: "utf-8", timeout: 5e3 }
3247
+ ).trim();
3248
+ } catch {
3249
+ return null;
3250
+ }
3251
+ }
3252
+ function linuxSecretSet(value) {
3253
+ if (process.platform !== "linux") return false;
3254
+ try {
3255
+ execSync4(
3256
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3257
+ { timeout: 5e3 }
3258
+ );
3259
+ return true;
3260
+ } catch {
3261
+ return false;
3262
+ }
3263
+ }
3210
3264
  async function tryKeytar() {
3211
3265
  try {
3212
3266
  return await import("keytar");
@@ -3214,13 +3268,63 @@ async function tryKeytar() {
3214
3268
  return null;
3215
3269
  }
3216
3270
  }
3271
+ function deriveMachineKey() {
3272
+ try {
3273
+ const crypto3 = __require("crypto");
3274
+ const material = [
3275
+ os9.hostname(),
3276
+ os9.userInfo().username,
3277
+ os9.arch(),
3278
+ os9.platform(),
3279
+ // Machine ID on Linux (stable across reboots)
3280
+ process.platform === "linux" ? readMachineId() : ""
3281
+ ].join("|");
3282
+ return crypto3.createHash("sha256").update(material).digest();
3283
+ } catch {
3284
+ return null;
3285
+ }
3286
+ }
3287
+ function readMachineId() {
3288
+ try {
3289
+ const { readFileSync: readFileSync10 } = __require("fs");
3290
+ return readFileSync10("/etc/machine-id", "utf-8").trim();
3291
+ } catch {
3292
+ return "";
3293
+ }
3294
+ }
3295
+ function decryptWithMachineKey(encrypted, machineKey) {
3296
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3297
+ try {
3298
+ const crypto3 = __require("crypto");
3299
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3300
+ if (parts.length !== 3) return null;
3301
+ const [ivB64, tagB64, cipherB64] = parts;
3302
+ const iv = Buffer.from(ivB64, "base64");
3303
+ const authTag = Buffer.from(tagB64, "base64");
3304
+ const decipher = crypto3.createDecipheriv("aes-256-gcm", machineKey, iv);
3305
+ decipher.setAuthTag(authTag);
3306
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3307
+ decrypted += decipher.final("utf-8");
3308
+ return decrypted;
3309
+ } catch {
3310
+ return null;
3311
+ }
3312
+ }
3217
3313
  async function getMasterKey() {
3314
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3315
+ if (nativeValue) {
3316
+ return Buffer.from(nativeValue, "base64");
3317
+ }
3218
3318
  const keytar = await tryKeytar();
3219
3319
  if (keytar) {
3220
3320
  try {
3221
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3222
- if (stored) {
3223
- return Buffer.from(stored, "base64");
3321
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3322
+ if (keytarValue) {
3323
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3324
+ if (migrated) {
3325
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3326
+ }
3327
+ return Buffer.from(keytarValue, "base64");
3224
3328
  }
3225
3329
  } catch {
3226
3330
  }
@@ -3234,8 +3338,31 @@ async function getMasterKey() {
3234
3338
  return null;
3235
3339
  }
3236
3340
  try {
3237
- const content = await readFile3(keyPath, "utf-8");
3238
- return Buffer.from(content.trim(), "base64");
3341
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3342
+ let b64Value;
3343
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3344
+ const machineKey = deriveMachineKey();
3345
+ if (!machineKey) {
3346
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3347
+ return null;
3348
+ }
3349
+ const decrypted = decryptWithMachineKey(content, machineKey);
3350
+ if (!decrypted) {
3351
+ process.stderr.write(
3352
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3353
+ );
3354
+ return null;
3355
+ }
3356
+ b64Value = decrypted;
3357
+ } else {
3358
+ b64Value = content;
3359
+ }
3360
+ const key = Buffer.from(b64Value, "base64");
3361
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3362
+ if (migrated) {
3363
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3364
+ }
3365
+ return key;
3239
3366
  } catch (err) {
3240
3367
  process.stderr.write(
3241
3368
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -3244,12 +3371,13 @@ async function getMasterKey() {
3244
3371
  return null;
3245
3372
  }
3246
3373
  }
3247
- var SERVICE, ACCOUNT;
3374
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
3248
3375
  var init_keychain = __esm({
3249
3376
  "src/lib/keychain.ts"() {
3250
3377
  "use strict";
3251
3378
  SERVICE = "exe-mem";
3252
3379
  ACCOUNT = "master-key";
3380
+ ENCRYPTED_PREFIX = "enc:";
3253
3381
  }
3254
3382
  });
3255
3383
 
@@ -1051,8 +1051,8 @@ function findPackageRoot() {
1051
1051
  function getAvailableMemoryGB() {
1052
1052
  if (process.platform === "darwin") {
1053
1053
  try {
1054
- const { execSync: execSync4 } = __require("child_process");
1055
- const vmstat = execSync4("vm_stat", { encoding: "utf8" });
1054
+ const { execSync: execSync5 } = __require("child_process");
1055
+ const vmstat = execSync5("vm_stat", { encoding: "utf8" });
1056
1056
  const pageSize = 16384;
1057
1057
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1058
1058
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -3265,6 +3265,7 @@ var init_notifications = __esm({
3265
3265
  // src/lib/keychain.ts
3266
3266
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3267
3267
  import { existsSync as existsSync12 } from "fs";
3268
+ import { execSync as execSync4 } from "child_process";
3268
3269
  import path13 from "path";
3269
3270
  import os10 from "os";
3270
3271
  function getKeyDir() {
@@ -3273,6 +3274,59 @@ function getKeyDir() {
3273
3274
  function getKeyPath() {
3274
3275
  return path13.join(getKeyDir(), "master.key");
3275
3276
  }
3277
+ function macKeychainGet() {
3278
+ if (process.platform !== "darwin") return null;
3279
+ try {
3280
+ return execSync4(
3281
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3282
+ { encoding: "utf-8", timeout: 5e3 }
3283
+ ).trim();
3284
+ } catch {
3285
+ return null;
3286
+ }
3287
+ }
3288
+ function macKeychainSet(value) {
3289
+ if (process.platform !== "darwin") return false;
3290
+ try {
3291
+ try {
3292
+ execSync4(
3293
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3294
+ { timeout: 5e3 }
3295
+ );
3296
+ } catch {
3297
+ }
3298
+ execSync4(
3299
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3300
+ { timeout: 5e3 }
3301
+ );
3302
+ return true;
3303
+ } catch {
3304
+ return false;
3305
+ }
3306
+ }
3307
+ function linuxSecretGet() {
3308
+ if (process.platform !== "linux") return null;
3309
+ try {
3310
+ return execSync4(
3311
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3312
+ { encoding: "utf-8", timeout: 5e3 }
3313
+ ).trim();
3314
+ } catch {
3315
+ return null;
3316
+ }
3317
+ }
3318
+ function linuxSecretSet(value) {
3319
+ if (process.platform !== "linux") return false;
3320
+ try {
3321
+ execSync4(
3322
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3323
+ { timeout: 5e3 }
3324
+ );
3325
+ return true;
3326
+ } catch {
3327
+ return false;
3328
+ }
3329
+ }
3276
3330
  async function tryKeytar() {
3277
3331
  try {
3278
3332
  return await import("keytar");
@@ -3280,13 +3334,63 @@ async function tryKeytar() {
3280
3334
  return null;
3281
3335
  }
3282
3336
  }
3337
+ function deriveMachineKey() {
3338
+ try {
3339
+ const crypto3 = __require("crypto");
3340
+ const material = [
3341
+ os10.hostname(),
3342
+ os10.userInfo().username,
3343
+ os10.arch(),
3344
+ os10.platform(),
3345
+ // Machine ID on Linux (stable across reboots)
3346
+ process.platform === "linux" ? readMachineId() : ""
3347
+ ].join("|");
3348
+ return crypto3.createHash("sha256").update(material).digest();
3349
+ } catch {
3350
+ return null;
3351
+ }
3352
+ }
3353
+ function readMachineId() {
3354
+ try {
3355
+ const { readFileSync: readFileSync11 } = __require("fs");
3356
+ return readFileSync11("/etc/machine-id", "utf-8").trim();
3357
+ } catch {
3358
+ return "";
3359
+ }
3360
+ }
3361
+ function decryptWithMachineKey(encrypted, machineKey) {
3362
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3363
+ try {
3364
+ const crypto3 = __require("crypto");
3365
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3366
+ if (parts.length !== 3) return null;
3367
+ const [ivB64, tagB64, cipherB64] = parts;
3368
+ const iv = Buffer.from(ivB64, "base64");
3369
+ const authTag = Buffer.from(tagB64, "base64");
3370
+ const decipher = crypto3.createDecipheriv("aes-256-gcm", machineKey, iv);
3371
+ decipher.setAuthTag(authTag);
3372
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3373
+ decrypted += decipher.final("utf-8");
3374
+ return decrypted;
3375
+ } catch {
3376
+ return null;
3377
+ }
3378
+ }
3283
3379
  async function getMasterKey() {
3380
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3381
+ if (nativeValue) {
3382
+ return Buffer.from(nativeValue, "base64");
3383
+ }
3284
3384
  const keytar = await tryKeytar();
3285
3385
  if (keytar) {
3286
3386
  try {
3287
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3288
- if (stored) {
3289
- return Buffer.from(stored, "base64");
3387
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3388
+ if (keytarValue) {
3389
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3390
+ if (migrated) {
3391
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3392
+ }
3393
+ return Buffer.from(keytarValue, "base64");
3290
3394
  }
3291
3395
  } catch {
3292
3396
  }
@@ -3300,8 +3404,31 @@ async function getMasterKey() {
3300
3404
  return null;
3301
3405
  }
3302
3406
  try {
3303
- const content = await readFile3(keyPath, "utf-8");
3304
- return Buffer.from(content.trim(), "base64");
3407
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3408
+ let b64Value;
3409
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3410
+ const machineKey = deriveMachineKey();
3411
+ if (!machineKey) {
3412
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3413
+ return null;
3414
+ }
3415
+ const decrypted = decryptWithMachineKey(content, machineKey);
3416
+ if (!decrypted) {
3417
+ process.stderr.write(
3418
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3419
+ );
3420
+ return null;
3421
+ }
3422
+ b64Value = decrypted;
3423
+ } else {
3424
+ b64Value = content;
3425
+ }
3426
+ const key = Buffer.from(b64Value, "base64");
3427
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3428
+ if (migrated) {
3429
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3430
+ }
3431
+ return key;
3305
3432
  } catch (err) {
3306
3433
  process.stderr.write(
3307
3434
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -3310,12 +3437,13 @@ async function getMasterKey() {
3310
3437
  return null;
3311
3438
  }
3312
3439
  }
3313
- var SERVICE, ACCOUNT;
3440
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
3314
3441
  var init_keychain = __esm({
3315
3442
  "src/lib/keychain.ts"() {
3316
3443
  "use strict";
3317
3444
  SERVICE = "exe-mem";
3318
3445
  ACCOUNT = "master-key";
3446
+ ENCRYPTED_PREFIX = "enc:";
3319
3447
  }
3320
3448
  });
3321
3449
 
@@ -1051,8 +1051,8 @@ function findPackageRoot() {
1051
1051
  function getAvailableMemoryGB() {
1052
1052
  if (process.platform === "darwin") {
1053
1053
  try {
1054
- const { execSync: execSync4 } = __require("child_process");
1055
- const vmstat = execSync4("vm_stat", { encoding: "utf8" });
1054
+ const { execSync: execSync5 } = __require("child_process");
1055
+ const vmstat = execSync5("vm_stat", { encoding: "utf8" });
1056
1056
  const pageSize = 16384;
1057
1057
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1058
1058
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -3359,6 +3359,7 @@ var init_tasks_review = __esm({
3359
3359
  // src/lib/keychain.ts
3360
3360
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3361
3361
  import { existsSync as existsSync13 } from "fs";
3362
+ import { execSync as execSync4 } from "child_process";
3362
3363
  import path14 from "path";
3363
3364
  import os10 from "os";
3364
3365
  function getKeyDir() {
@@ -3367,6 +3368,59 @@ function getKeyDir() {
3367
3368
  function getKeyPath() {
3368
3369
  return path14.join(getKeyDir(), "master.key");
3369
3370
  }
3371
+ function macKeychainGet() {
3372
+ if (process.platform !== "darwin") return null;
3373
+ try {
3374
+ return execSync4(
3375
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3376
+ { encoding: "utf-8", timeout: 5e3 }
3377
+ ).trim();
3378
+ } catch {
3379
+ return null;
3380
+ }
3381
+ }
3382
+ function macKeychainSet(value) {
3383
+ if (process.platform !== "darwin") return false;
3384
+ try {
3385
+ try {
3386
+ execSync4(
3387
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3388
+ { timeout: 5e3 }
3389
+ );
3390
+ } catch {
3391
+ }
3392
+ execSync4(
3393
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3394
+ { timeout: 5e3 }
3395
+ );
3396
+ return true;
3397
+ } catch {
3398
+ return false;
3399
+ }
3400
+ }
3401
+ function linuxSecretGet() {
3402
+ if (process.platform !== "linux") return null;
3403
+ try {
3404
+ return execSync4(
3405
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3406
+ { encoding: "utf-8", timeout: 5e3 }
3407
+ ).trim();
3408
+ } catch {
3409
+ return null;
3410
+ }
3411
+ }
3412
+ function linuxSecretSet(value) {
3413
+ if (process.platform !== "linux") return false;
3414
+ try {
3415
+ execSync4(
3416
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
3417
+ { timeout: 5e3 }
3418
+ );
3419
+ return true;
3420
+ } catch {
3421
+ return false;
3422
+ }
3423
+ }
3370
3424
  async function tryKeytar() {
3371
3425
  try {
3372
3426
  return await import("keytar");
@@ -3374,13 +3428,63 @@ async function tryKeytar() {
3374
3428
  return null;
3375
3429
  }
3376
3430
  }
3431
+ function deriveMachineKey() {
3432
+ try {
3433
+ const crypto3 = __require("crypto");
3434
+ const material = [
3435
+ os10.hostname(),
3436
+ os10.userInfo().username,
3437
+ os10.arch(),
3438
+ os10.platform(),
3439
+ // Machine ID on Linux (stable across reboots)
3440
+ process.platform === "linux" ? readMachineId() : ""
3441
+ ].join("|");
3442
+ return crypto3.createHash("sha256").update(material).digest();
3443
+ } catch {
3444
+ return null;
3445
+ }
3446
+ }
3447
+ function readMachineId() {
3448
+ try {
3449
+ const { readFileSync: readFileSync11 } = __require("fs");
3450
+ return readFileSync11("/etc/machine-id", "utf-8").trim();
3451
+ } catch {
3452
+ return "";
3453
+ }
3454
+ }
3455
+ function decryptWithMachineKey(encrypted, machineKey) {
3456
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3457
+ try {
3458
+ const crypto3 = __require("crypto");
3459
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3460
+ if (parts.length !== 3) return null;
3461
+ const [ivB64, tagB64, cipherB64] = parts;
3462
+ const iv = Buffer.from(ivB64, "base64");
3463
+ const authTag = Buffer.from(tagB64, "base64");
3464
+ const decipher = crypto3.createDecipheriv("aes-256-gcm", machineKey, iv);
3465
+ decipher.setAuthTag(authTag);
3466
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3467
+ decrypted += decipher.final("utf-8");
3468
+ return decrypted;
3469
+ } catch {
3470
+ return null;
3471
+ }
3472
+ }
3377
3473
  async function getMasterKey() {
3474
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3475
+ if (nativeValue) {
3476
+ return Buffer.from(nativeValue, "base64");
3477
+ }
3378
3478
  const keytar = await tryKeytar();
3379
3479
  if (keytar) {
3380
3480
  try {
3381
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3382
- if (stored) {
3383
- return Buffer.from(stored, "base64");
3481
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3482
+ if (keytarValue) {
3483
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3484
+ if (migrated) {
3485
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3486
+ }
3487
+ return Buffer.from(keytarValue, "base64");
3384
3488
  }
3385
3489
  } catch {
3386
3490
  }
@@ -3394,8 +3498,31 @@ async function getMasterKey() {
3394
3498
  return null;
3395
3499
  }
3396
3500
  try {
3397
- const content = await readFile3(keyPath, "utf-8");
3398
- return Buffer.from(content.trim(), "base64");
3501
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3502
+ let b64Value;
3503
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3504
+ const machineKey = deriveMachineKey();
3505
+ if (!machineKey) {
3506
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3507
+ return null;
3508
+ }
3509
+ const decrypted = decryptWithMachineKey(content, machineKey);
3510
+ if (!decrypted) {
3511
+ process.stderr.write(
3512
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3513
+ );
3514
+ return null;
3515
+ }
3516
+ b64Value = decrypted;
3517
+ } else {
3518
+ b64Value = content;
3519
+ }
3520
+ const key = Buffer.from(b64Value, "base64");
3521
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3522
+ if (migrated) {
3523
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3524
+ }
3525
+ return key;
3399
3526
  } catch (err) {
3400
3527
  process.stderr.write(
3401
3528
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -3404,12 +3531,13 @@ async function getMasterKey() {
3404
3531
  return null;
3405
3532
  }
3406
3533
  }
3407
- var SERVICE, ACCOUNT;
3534
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
3408
3535
  var init_keychain = __esm({
3409
3536
  "src/lib/keychain.ts"() {
3410
3537
  "use strict";
3411
3538
  SERVICE = "exe-mem";
3412
3539
  ACCOUNT = "master-key";
3540
+ ENCRYPTED_PREFIX = "enc:";
3413
3541
  }
3414
3542
  });
3415
3543
 
@@ -4675,8 +4803,8 @@ async function main() {
4675
4803
  let sessionScope = process.env.EXE_SESSION ? extractRootExe(process.env.EXE_SESSION) ?? void 0 : void 0;
4676
4804
  if (!sessionScope) {
4677
4805
  try {
4678
- const { execSync: execSync4 } = await import("child_process");
4679
- const tmuxSession = execSync4("tmux display-message -p '#{session_name}'", { encoding: "utf8", timeout: 2e3 }).trim();
4806
+ const { execSync: execSync5 } = await import("child_process");
4807
+ const tmuxSession = execSync5("tmux display-message -p '#{session_name}'", { encoding: "utf8", timeout: 2e3 }).trim();
4680
4808
  if (isExeSession(tmuxSession)) sessionScope = tmuxSession;
4681
4809
  } catch {
4682
4810
  }