@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
@@ -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;
@@ -3243,6 +3243,109 @@ var init_worker_gate = __esm({
3243
3243
  }
3244
3244
  });
3245
3245
 
3246
+ // src/lib/db-backup.ts
3247
+ var db_backup_exports = {};
3248
+ __export(db_backup_exports, {
3249
+ createBackup: () => createBackup,
3250
+ findActiveDb: () => findActiveDb,
3251
+ getBackupDir: () => getBackupDir,
3252
+ getLatestBackup: () => getLatestBackup,
3253
+ hasBackupToday: () => hasBackupToday,
3254
+ listBackups: () => listBackups,
3255
+ rotateBackups: () => rotateBackups
3256
+ });
3257
+ import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync3, unlinkSync as unlinkSync4, statSync as statSync2 } from "fs";
3258
+ import path9 from "path";
3259
+ function findActiveDb() {
3260
+ for (const name of DB_NAMES) {
3261
+ const p = path9.join(EXE_AI_DIR, name);
3262
+ if (existsSync9(p)) return p;
3263
+ }
3264
+ return null;
3265
+ }
3266
+ function createBackup(reason = "manual") {
3267
+ const dbPath = findActiveDb();
3268
+ if (!dbPath) return null;
3269
+ mkdirSync4(BACKUP_DIR, { recursive: true });
3270
+ const dbName = path9.basename(dbPath, ".db");
3271
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
3272
+ const backupName = `${dbName}-${reason}-${timestamp}.db`;
3273
+ const backupPath = path9.join(BACKUP_DIR, backupName);
3274
+ copyFileSync(dbPath, backupPath);
3275
+ const walPath = dbPath + "-wal";
3276
+ if (existsSync9(walPath)) {
3277
+ try {
3278
+ copyFileSync(walPath, backupPath + "-wal");
3279
+ } catch {
3280
+ }
3281
+ }
3282
+ const shmPath = dbPath + "-shm";
3283
+ if (existsSync9(shmPath)) {
3284
+ try {
3285
+ copyFileSync(shmPath, backupPath + "-shm");
3286
+ } catch {
3287
+ }
3288
+ }
3289
+ return backupPath;
3290
+ }
3291
+ function rotateBackups(keepDays = DEFAULT_KEEP_DAYS) {
3292
+ if (!existsSync9(BACKUP_DIR)) return 0;
3293
+ const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1e3;
3294
+ let deleted = 0;
3295
+ try {
3296
+ const files = readdirSync3(BACKUP_DIR);
3297
+ for (const file of files) {
3298
+ if (!file.endsWith(".db") && !file.endsWith(".db-wal") && !file.endsWith(".db-shm")) continue;
3299
+ const filePath = path9.join(BACKUP_DIR, file);
3300
+ try {
3301
+ const stat = statSync2(filePath);
3302
+ if (stat.mtimeMs < cutoff) {
3303
+ unlinkSync4(filePath);
3304
+ deleted++;
3305
+ }
3306
+ } catch {
3307
+ }
3308
+ }
3309
+ } catch {
3310
+ }
3311
+ return deleted;
3312
+ }
3313
+ function listBackups() {
3314
+ if (!existsSync9(BACKUP_DIR)) return [];
3315
+ try {
3316
+ const files = readdirSync3(BACKUP_DIR).filter((f) => f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm"));
3317
+ return files.map((name) => {
3318
+ const p = path9.join(BACKUP_DIR, name);
3319
+ const stat = statSync2(p);
3320
+ return { path: p, name, size: stat.size, date: stat.mtime };
3321
+ }).sort((a, b) => b.date.getTime() - a.date.getTime());
3322
+ } catch {
3323
+ return [];
3324
+ }
3325
+ }
3326
+ function hasBackupToday(reason) {
3327
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3328
+ const backups = listBackups();
3329
+ return backups.some((b) => b.name.includes(reason) && b.name.includes(today.replace(/-/g, "-")));
3330
+ }
3331
+ function getLatestBackup() {
3332
+ const backups = listBackups();
3333
+ return backups.length > 0 ? backups[0].path : null;
3334
+ }
3335
+ function getBackupDir() {
3336
+ return BACKUP_DIR;
3337
+ }
3338
+ var BACKUP_DIR, DEFAULT_KEEP_DAYS, DB_NAMES;
3339
+ var init_db_backup = __esm({
3340
+ "src/lib/db-backup.ts"() {
3341
+ "use strict";
3342
+ init_config();
3343
+ BACKUP_DIR = path9.join(EXE_AI_DIR, "backups");
3344
+ DEFAULT_KEEP_DAYS = 3;
3345
+ DB_NAMES = ["memories.db", "exe-mem.db", "exe-os.db", "exe.db"];
3346
+ }
3347
+ });
3348
+
3246
3349
  // src/bin/exe-doctor.ts
3247
3350
  import os6 from "os";
3248
3351
 
@@ -3254,6 +3357,7 @@ import { createHash } from "crypto";
3254
3357
  // src/lib/keychain.ts
3255
3358
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3256
3359
  import { existsSync as existsSync6 } from "fs";
3360
+ import { execSync as execSync2 } from "child_process";
3257
3361
  import path6 from "path";
3258
3362
  import os5 from "os";
3259
3363
  var SERVICE = "exe-mem";
@@ -3264,6 +3368,59 @@ function getKeyDir() {
3264
3368
  function getKeyPath() {
3265
3369
  return path6.join(getKeyDir(), "master.key");
3266
3370
  }
3371
+ function macKeychainGet() {
3372
+ if (process.platform !== "darwin") return null;
3373
+ try {
3374
+ return execSync2(
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
+ execSync2(
3387
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3388
+ { timeout: 5e3 }
3389
+ );
3390
+ } catch {
3391
+ }
3392
+ execSync2(
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 execSync2(
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
+ execSync2(
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
+ }
3267
3424
  async function tryKeytar() {
3268
3425
  try {
3269
3426
  return await import("keytar");
@@ -3271,13 +3428,64 @@ async function tryKeytar() {
3271
3428
  return null;
3272
3429
  }
3273
3430
  }
3431
+ var ENCRYPTED_PREFIX = "enc:";
3432
+ function deriveMachineKey() {
3433
+ try {
3434
+ const crypto2 = __require("crypto");
3435
+ const material = [
3436
+ os5.hostname(),
3437
+ os5.userInfo().username,
3438
+ os5.arch(),
3439
+ os5.platform(),
3440
+ // Machine ID on Linux (stable across reboots)
3441
+ process.platform === "linux" ? readMachineId() : ""
3442
+ ].join("|");
3443
+ return crypto2.createHash("sha256").update(material).digest();
3444
+ } catch {
3445
+ return null;
3446
+ }
3447
+ }
3448
+ function readMachineId() {
3449
+ try {
3450
+ const { readFileSync: readFileSync6 } = __require("fs");
3451
+ return readFileSync6("/etc/machine-id", "utf-8").trim();
3452
+ } catch {
3453
+ return "";
3454
+ }
3455
+ }
3456
+ function decryptWithMachineKey(encrypted, machineKey) {
3457
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
3458
+ try {
3459
+ const crypto2 = __require("crypto");
3460
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
3461
+ if (parts.length !== 3) return null;
3462
+ const [ivB64, tagB64, cipherB64] = parts;
3463
+ const iv = Buffer.from(ivB64, "base64");
3464
+ const authTag = Buffer.from(tagB64, "base64");
3465
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", machineKey, iv);
3466
+ decipher.setAuthTag(authTag);
3467
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
3468
+ decrypted += decipher.final("utf-8");
3469
+ return decrypted;
3470
+ } catch {
3471
+ return null;
3472
+ }
3473
+ }
3274
3474
  async function getMasterKey() {
3475
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
3476
+ if (nativeValue) {
3477
+ return Buffer.from(nativeValue, "base64");
3478
+ }
3275
3479
  const keytar = await tryKeytar();
3276
3480
  if (keytar) {
3277
3481
  try {
3278
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3279
- if (stored) {
3280
- return Buffer.from(stored, "base64");
3482
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
3483
+ if (keytarValue) {
3484
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
3485
+ if (migrated) {
3486
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
3487
+ }
3488
+ return Buffer.from(keytarValue, "base64");
3281
3489
  }
3282
3490
  } catch {
3283
3491
  }
@@ -3291,8 +3499,31 @@ async function getMasterKey() {
3291
3499
  return null;
3292
3500
  }
3293
3501
  try {
3294
- const content = await readFile3(keyPath, "utf-8");
3295
- return Buffer.from(content.trim(), "base64");
3502
+ const content = (await readFile3(keyPath, "utf-8")).trim();
3503
+ let b64Value;
3504
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
3505
+ const machineKey = deriveMachineKey();
3506
+ if (!machineKey) {
3507
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
3508
+ return null;
3509
+ }
3510
+ const decrypted = decryptWithMachineKey(content, machineKey);
3511
+ if (!decrypted) {
3512
+ process.stderr.write(
3513
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
3514
+ );
3515
+ return null;
3516
+ }
3517
+ b64Value = decrypted;
3518
+ } else {
3519
+ b64Value = content;
3520
+ }
3521
+ const key = Buffer.from(b64Value, "base64");
3522
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
3523
+ if (migrated) {
3524
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
3525
+ }
3526
+ return key;
3296
3527
  } catch (err) {
3297
3528
  process.stderr.write(
3298
3529
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -3458,9 +3689,9 @@ function isMainModule(importMetaUrl) {
3458
3689
  }
3459
3690
 
3460
3691
  // src/bin/exe-doctor.ts
3461
- import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
3692
+ import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
3462
3693
  import { spawn as spawn2 } from "child_process";
3463
- import path9 from "path";
3694
+ import path10 from "path";
3464
3695
  import { randomUUID as randomUUID3 } from "crypto";
3465
3696
 
3466
3697
  // src/lib/conflict-detector.ts
@@ -3864,7 +4095,7 @@ async function auditOrphanedProjects(client) {
3864
4095
  for (const row of result.rows) {
3865
4096
  const name = row.project_name;
3866
4097
  const count = Number(row.cnt);
3867
- const exists = existsSync9(path9.join(home, name)) || existsSync9(path9.join(home, "..", name)) || existsSync9(path9.join(process.cwd(), "..", name));
4098
+ const exists = existsSync10(path10.join(home, name)) || existsSync10(path10.join(home, "..", name)) || existsSync10(path10.join(process.cwd(), "..", name));
3868
4099
  if (!exists) {
3869
4100
  orphans.push({ project_name: name, count });
3870
4101
  }
@@ -3872,13 +4103,13 @@ async function auditOrphanedProjects(client) {
3872
4103
  return orphans;
3873
4104
  }
3874
4105
  function auditHookHealth() {
3875
- const logPath = path9.join(
4106
+ const logPath = path10.join(
3876
4107
  process.env.HOME ?? process.env.USERPROFILE ?? "",
3877
4108
  ".exe-os",
3878
4109
  "logs",
3879
4110
  "hooks.log"
3880
4111
  );
3881
- if (!existsSync9(logPath)) {
4112
+ if (!existsSync10(logPath)) {
3882
4113
  return { logExists: false, totalLines: 0, errorsLastHour: 0, topPatterns: [] };
3883
4114
  }
3884
4115
  let content;
@@ -4096,7 +4327,7 @@ async function fixNullVectors() {
4096
4327
  }
4097
4328
  }
4098
4329
  const npmRoot = (await import("child_process")).execSync("npm root -g", { encoding: "utf8" }).trim();
4099
- const backfillPath = path9.join(npmRoot, "exe-os", "dist", "bin", "backfill-vectors.js");
4330
+ const backfillPath = path10.join(npmRoot, "exe-os", "dist", "bin", "backfill-vectors.js");
4100
4331
  return new Promise((resolve, reject) => {
4101
4332
  const child = spawn2("node", [backfillPath], { stdio: "inherit" });
4102
4333
  if (child.pid) registerWorkerPid2(child.pid);
@@ -4208,6 +4439,17 @@ async function main(argv = process.argv.slice(2)) {
4208
4439
  console.log(`
4209
4440
  ${mode} Applying repairs...
4210
4441
  `);
4442
+ if (!flags.dryRun) {
4443
+ try {
4444
+ const { createBackup: createBackup2 } = await Promise.resolve().then(() => (init_db_backup(), db_backup_exports));
4445
+ const backupPath = createBackup2("pre-fix");
4446
+ if (backupPath) {
4447
+ console.log(` Backup created: ${backupPath.split("/").pop()}`);
4448
+ }
4449
+ } catch {
4450
+ console.log(" Warning: backup failed \u2014 proceeding with fix anyway");
4451
+ }
4452
+ }
4211
4453
  if (report.nullVectors > 0) {
4212
4454
  console.log(`${mode} Backfilling ${fmtNum(report.nullVectors)} null vectors...`);
4213
4455
  if (!flags.dryRun) {
@@ -1040,8 +1040,8 @@ function findPackageRoot() {
1040
1040
  function getAvailableMemoryGB() {
1041
1041
  if (process.platform === "darwin") {
1042
1042
  try {
1043
- const { execSync: execSync2 } = __require("child_process");
1044
- const vmstat = execSync2("vm_stat", { encoding: "utf8" });
1043
+ const { execSync: execSync3 } = __require("child_process");
1044
+ const vmstat = execSync3("vm_stat", { encoding: "utf8" });
1045
1045
  const pageSize = 16384;
1046
1046
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1047
1047
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -2780,6 +2780,7 @@ var init_database = __esm({
2780
2780
  // src/lib/keychain.ts
2781
2781
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2782
2782
  import { existsSync as existsSync6 } from "fs";
2783
+ import { execSync as execSync2 } from "child_process";
2783
2784
  import path6 from "path";
2784
2785
  import os5 from "os";
2785
2786
  function getKeyDir() {
@@ -2788,6 +2789,59 @@ function getKeyDir() {
2788
2789
  function getKeyPath() {
2789
2790
  return path6.join(getKeyDir(), "master.key");
2790
2791
  }
2792
+ function macKeychainGet() {
2793
+ if (process.platform !== "darwin") return null;
2794
+ try {
2795
+ return execSync2(
2796
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
2797
+ { encoding: "utf-8", timeout: 5e3 }
2798
+ ).trim();
2799
+ } catch {
2800
+ return null;
2801
+ }
2802
+ }
2803
+ function macKeychainSet(value) {
2804
+ if (process.platform !== "darwin") return false;
2805
+ try {
2806
+ try {
2807
+ execSync2(
2808
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
2809
+ { timeout: 5e3 }
2810
+ );
2811
+ } catch {
2812
+ }
2813
+ execSync2(
2814
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
2815
+ { timeout: 5e3 }
2816
+ );
2817
+ return true;
2818
+ } catch {
2819
+ return false;
2820
+ }
2821
+ }
2822
+ function linuxSecretGet() {
2823
+ if (process.platform !== "linux") return null;
2824
+ try {
2825
+ return execSync2(
2826
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
2827
+ { encoding: "utf-8", timeout: 5e3 }
2828
+ ).trim();
2829
+ } catch {
2830
+ return null;
2831
+ }
2832
+ }
2833
+ function linuxSecretSet(value) {
2834
+ if (process.platform !== "linux") return false;
2835
+ try {
2836
+ execSync2(
2837
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
2838
+ { timeout: 5e3 }
2839
+ );
2840
+ return true;
2841
+ } catch {
2842
+ return false;
2843
+ }
2844
+ }
2791
2845
  async function tryKeytar() {
2792
2846
  try {
2793
2847
  return await import("keytar");
@@ -2795,13 +2849,63 @@ async function tryKeytar() {
2795
2849
  return null;
2796
2850
  }
2797
2851
  }
2852
+ function deriveMachineKey() {
2853
+ try {
2854
+ const crypto3 = __require("crypto");
2855
+ const material = [
2856
+ os5.hostname(),
2857
+ os5.userInfo().username,
2858
+ os5.arch(),
2859
+ os5.platform(),
2860
+ // Machine ID on Linux (stable across reboots)
2861
+ process.platform === "linux" ? readMachineId() : ""
2862
+ ].join("|");
2863
+ return crypto3.createHash("sha256").update(material).digest();
2864
+ } catch {
2865
+ return null;
2866
+ }
2867
+ }
2868
+ function readMachineId() {
2869
+ try {
2870
+ const { readFileSync: readFileSync5 } = __require("fs");
2871
+ return readFileSync5("/etc/machine-id", "utf-8").trim();
2872
+ } catch {
2873
+ return "";
2874
+ }
2875
+ }
2876
+ function decryptWithMachineKey(encrypted, machineKey) {
2877
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2878
+ try {
2879
+ const crypto3 = __require("crypto");
2880
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
2881
+ if (parts.length !== 3) return null;
2882
+ const [ivB64, tagB64, cipherB64] = parts;
2883
+ const iv = Buffer.from(ivB64, "base64");
2884
+ const authTag = Buffer.from(tagB64, "base64");
2885
+ const decipher = crypto3.createDecipheriv("aes-256-gcm", machineKey, iv);
2886
+ decipher.setAuthTag(authTag);
2887
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
2888
+ decrypted += decipher.final("utf-8");
2889
+ return decrypted;
2890
+ } catch {
2891
+ return null;
2892
+ }
2893
+ }
2798
2894
  async function getMasterKey() {
2895
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
2896
+ if (nativeValue) {
2897
+ return Buffer.from(nativeValue, "base64");
2898
+ }
2799
2899
  const keytar = await tryKeytar();
2800
2900
  if (keytar) {
2801
2901
  try {
2802
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
2803
- if (stored) {
2804
- return Buffer.from(stored, "base64");
2902
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
2903
+ if (keytarValue) {
2904
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
2905
+ if (migrated) {
2906
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
2907
+ }
2908
+ return Buffer.from(keytarValue, "base64");
2805
2909
  }
2806
2910
  } catch {
2807
2911
  }
@@ -2815,8 +2919,31 @@ async function getMasterKey() {
2815
2919
  return null;
2816
2920
  }
2817
2921
  try {
2818
- const content = await readFile3(keyPath, "utf-8");
2819
- return Buffer.from(content.trim(), "base64");
2922
+ const content = (await readFile3(keyPath, "utf-8")).trim();
2923
+ let b64Value;
2924
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
2925
+ const machineKey = deriveMachineKey();
2926
+ if (!machineKey) {
2927
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
2928
+ return null;
2929
+ }
2930
+ const decrypted = decryptWithMachineKey(content, machineKey);
2931
+ if (!decrypted) {
2932
+ process.stderr.write(
2933
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
2934
+ );
2935
+ return null;
2936
+ }
2937
+ b64Value = decrypted;
2938
+ } else {
2939
+ b64Value = content;
2940
+ }
2941
+ const key = Buffer.from(b64Value, "base64");
2942
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2943
+ if (migrated) {
2944
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
2945
+ }
2946
+ return key;
2820
2947
  } catch (err) {
2821
2948
  process.stderr.write(
2822
2949
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -2825,12 +2952,13 @@ async function getMasterKey() {
2825
2952
  return null;
2826
2953
  }
2827
2954
  }
2828
- var SERVICE, ACCOUNT;
2955
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
2829
2956
  var init_keychain = __esm({
2830
2957
  "src/lib/keychain.ts"() {
2831
2958
  "use strict";
2832
2959
  SERVICE = "exe-mem";
2833
2960
  ACCOUNT = "master-key";
2961
+ ENCRYPTED_PREFIX = "enc:";
2834
2962
  }
2835
2963
  });
2836
2964