@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
@@ -1128,8 +1128,8 @@ function findPackageRoot() {
1128
1128
  function getAvailableMemoryGB() {
1129
1129
  if (process.platform === "darwin") {
1130
1130
  try {
1131
- const { execSync: execSync10 } = __require("child_process");
1132
- const vmstat = execSync10("vm_stat", { encoding: "utf8" });
1131
+ const { execSync: execSync11 } = __require("child_process");
1132
+ const vmstat = execSync11("vm_stat", { encoding: "utf8" });
1133
1133
  const pageSize = 16384;
1134
1134
  const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1135
1135
  const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
@@ -2813,6 +2813,7 @@ var init_database = __esm({
2813
2813
  // src/lib/keychain.ts
2814
2814
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2815
2815
  import { existsSync as existsSync6 } from "fs";
2816
+ import { execSync as execSync2 } from "child_process";
2816
2817
  import path6 from "path";
2817
2818
  import os5 from "os";
2818
2819
  function getKeyDir() {
@@ -2821,6 +2822,59 @@ function getKeyDir() {
2821
2822
  function getKeyPath() {
2822
2823
  return path6.join(getKeyDir(), "master.key");
2823
2824
  }
2825
+ function macKeychainGet() {
2826
+ if (process.platform !== "darwin") return null;
2827
+ try {
2828
+ return execSync2(
2829
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
2830
+ { encoding: "utf-8", timeout: 5e3 }
2831
+ ).trim();
2832
+ } catch {
2833
+ return null;
2834
+ }
2835
+ }
2836
+ function macKeychainSet(value) {
2837
+ if (process.platform !== "darwin") return false;
2838
+ try {
2839
+ try {
2840
+ execSync2(
2841
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
2842
+ { timeout: 5e3 }
2843
+ );
2844
+ } catch {
2845
+ }
2846
+ execSync2(
2847
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
2848
+ { timeout: 5e3 }
2849
+ );
2850
+ return true;
2851
+ } catch {
2852
+ return false;
2853
+ }
2854
+ }
2855
+ function linuxSecretGet() {
2856
+ if (process.platform !== "linux") return null;
2857
+ try {
2858
+ return execSync2(
2859
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
2860
+ { encoding: "utf-8", timeout: 5e3 }
2861
+ ).trim();
2862
+ } catch {
2863
+ return null;
2864
+ }
2865
+ }
2866
+ function linuxSecretSet(value) {
2867
+ if (process.platform !== "linux") return false;
2868
+ try {
2869
+ execSync2(
2870
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
2871
+ { timeout: 5e3 }
2872
+ );
2873
+ return true;
2874
+ } catch {
2875
+ return false;
2876
+ }
2877
+ }
2824
2878
  async function tryKeytar() {
2825
2879
  try {
2826
2880
  return await import("keytar");
@@ -2828,13 +2882,63 @@ async function tryKeytar() {
2828
2882
  return null;
2829
2883
  }
2830
2884
  }
2885
+ function deriveMachineKey() {
2886
+ try {
2887
+ const crypto8 = __require("crypto");
2888
+ const material = [
2889
+ os5.hostname(),
2890
+ os5.userInfo().username,
2891
+ os5.arch(),
2892
+ os5.platform(),
2893
+ // Machine ID on Linux (stable across reboots)
2894
+ process.platform === "linux" ? readMachineId() : ""
2895
+ ].join("|");
2896
+ return crypto8.createHash("sha256").update(material).digest();
2897
+ } catch {
2898
+ return null;
2899
+ }
2900
+ }
2901
+ function readMachineId() {
2902
+ try {
2903
+ const { readFileSync: readFileSync14 } = __require("fs");
2904
+ return readFileSync14("/etc/machine-id", "utf-8").trim();
2905
+ } catch {
2906
+ return "";
2907
+ }
2908
+ }
2909
+ function decryptWithMachineKey(encrypted, machineKey) {
2910
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
2911
+ try {
2912
+ const crypto8 = __require("crypto");
2913
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
2914
+ if (parts.length !== 3) return null;
2915
+ const [ivB64, tagB64, cipherB64] = parts;
2916
+ const iv = Buffer.from(ivB64, "base64");
2917
+ const authTag = Buffer.from(tagB64, "base64");
2918
+ const decipher = crypto8.createDecipheriv("aes-256-gcm", machineKey, iv);
2919
+ decipher.setAuthTag(authTag);
2920
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
2921
+ decrypted += decipher.final("utf-8");
2922
+ return decrypted;
2923
+ } catch {
2924
+ return null;
2925
+ }
2926
+ }
2831
2927
  async function getMasterKey() {
2928
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
2929
+ if (nativeValue) {
2930
+ return Buffer.from(nativeValue, "base64");
2931
+ }
2832
2932
  const keytar = await tryKeytar();
2833
2933
  if (keytar) {
2834
2934
  try {
2835
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
2836
- if (stored) {
2837
- return Buffer.from(stored, "base64");
2935
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
2936
+ if (keytarValue) {
2937
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
2938
+ if (migrated) {
2939
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
2940
+ }
2941
+ return Buffer.from(keytarValue, "base64");
2838
2942
  }
2839
2943
  } catch {
2840
2944
  }
@@ -2848,8 +2952,31 @@ async function getMasterKey() {
2848
2952
  return null;
2849
2953
  }
2850
2954
  try {
2851
- const content = await readFile3(keyPath, "utf-8");
2852
- return Buffer.from(content.trim(), "base64");
2955
+ const content = (await readFile3(keyPath, "utf-8")).trim();
2956
+ let b64Value;
2957
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
2958
+ const machineKey = deriveMachineKey();
2959
+ if (!machineKey) {
2960
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
2961
+ return null;
2962
+ }
2963
+ const decrypted = decryptWithMachineKey(content, machineKey);
2964
+ if (!decrypted) {
2965
+ process.stderr.write(
2966
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
2967
+ );
2968
+ return null;
2969
+ }
2970
+ b64Value = decrypted;
2971
+ } else {
2972
+ b64Value = content;
2973
+ }
2974
+ const key = Buffer.from(b64Value, "base64");
2975
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
2976
+ if (migrated) {
2977
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
2978
+ }
2979
+ return key;
2853
2980
  } catch (err) {
2854
2981
  process.stderr.write(
2855
2982
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -2858,12 +2985,13 @@ async function getMasterKey() {
2858
2985
  return null;
2859
2986
  }
2860
2987
  }
2861
- var SERVICE, ACCOUNT;
2988
+ var SERVICE, ACCOUNT, ENCRYPTED_PREFIX;
2862
2989
  var init_keychain = __esm({
2863
2990
  "src/lib/keychain.ts"() {
2864
2991
  "use strict";
2865
2992
  SERVICE = "exe-mem";
2866
2993
  ACCOUNT = "master-key";
2994
+ ENCRYPTED_PREFIX = "enc:";
2867
2995
  }
2868
2996
  });
2869
2997
 
@@ -4129,7 +4257,7 @@ var init_session_registry = __esm({
4129
4257
  });
4130
4258
 
4131
4259
  // src/lib/session-key.ts
4132
- import { execSync as execSync2 } from "child_process";
4260
+ import { execSync as execSync3 } from "child_process";
4133
4261
  function normalizeCommand(command) {
4134
4262
  const trimmed = command.trim().toLowerCase();
4135
4263
  const parts = trimmed.split(/[\\/]/);
@@ -4148,7 +4276,7 @@ function resolveRuntimeProcess() {
4148
4276
  let pid = process.ppid;
4149
4277
  for (let i = 0; i < 10; i++) {
4150
4278
  try {
4151
- const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
4279
+ const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
4152
4280
  encoding: "utf8",
4153
4281
  timeout: 2e3
4154
4282
  }).trim();
@@ -4314,14 +4442,14 @@ var init_transport = __esm({
4314
4442
  });
4315
4443
 
4316
4444
  // src/lib/cc-agent-support.ts
4317
- import { execSync as execSync3 } from "child_process";
4445
+ import { execSync as execSync4 } from "child_process";
4318
4446
  function _resetCcAgentSupportCache() {
4319
4447
  _cachedSupport = null;
4320
4448
  }
4321
4449
  function claudeSupportsAgentFlag() {
4322
4450
  if (_cachedSupport !== null) return _cachedSupport;
4323
4451
  try {
4324
- const helpOutput = execSync3("claude --help 2>&1", {
4452
+ const helpOutput = execSync4("claude --help 2>&1", {
4325
4453
  encoding: "utf-8",
4326
4454
  timeout: 5e3
4327
4455
  });
@@ -4770,7 +4898,7 @@ var init_session_kill_telemetry = __esm({
4770
4898
  });
4771
4899
 
4772
4900
  // src/lib/project-name.ts
4773
- import { execSync as execSync4 } from "child_process";
4901
+ import { execSync as execSync5 } from "child_process";
4774
4902
  import path14 from "path";
4775
4903
  function getProjectName(cwd) {
4776
4904
  const dir = cwd ?? process.cwd();
@@ -4778,7 +4906,7 @@ function getProjectName(cwd) {
4778
4906
  try {
4779
4907
  let repoRoot;
4780
4908
  try {
4781
- const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
4909
+ const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
4782
4910
  cwd: dir,
4783
4911
  encoding: "utf8",
4784
4912
  timeout: 2e3,
@@ -4786,7 +4914,7 @@ function getProjectName(cwd) {
4786
4914
  }).trim();
4787
4915
  repoRoot = path14.dirname(gitCommonDir);
4788
4916
  } catch {
4789
- repoRoot = execSync4("git rev-parse --show-toplevel", {
4917
+ repoRoot = execSync5("git rev-parse --show-toplevel", {
4790
4918
  cwd: dir,
4791
4919
  encoding: "utf8",
4792
4920
  timeout: 2e3,
@@ -4893,7 +5021,7 @@ __export(tasks_crud_exports, {
4893
5021
  import crypto4 from "crypto";
4894
5022
  import path15 from "path";
4895
5023
  import os10 from "os";
4896
- import { execSync as execSync5 } from "child_process";
5024
+ import { execSync as execSync6 } from "child_process";
4897
5025
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
4898
5026
  import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
4899
5027
  async function writeCheckpoint(input) {
@@ -5238,14 +5366,14 @@ function isTmuxSessionAlive(identifier) {
5238
5366
  if (!identifier || identifier === "unknown") return true;
5239
5367
  try {
5240
5368
  if (identifier.startsWith("%")) {
5241
- const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
5369
+ const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
5242
5370
  timeout: 2e3,
5243
5371
  encoding: "utf8",
5244
5372
  stdio: ["pipe", "pipe", "pipe"]
5245
5373
  });
5246
5374
  return output.split("\n").some((l) => l.trim() === identifier);
5247
5375
  } else {
5248
- execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
5376
+ execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
5249
5377
  timeout: 2e3,
5250
5378
  stdio: ["pipe", "pipe", "pipe"]
5251
5379
  });
@@ -5254,7 +5382,7 @@ function isTmuxSessionAlive(identifier) {
5254
5382
  } catch {
5255
5383
  if (identifier.startsWith("%")) return true;
5256
5384
  try {
5257
- execSync5("tmux list-sessions", {
5385
+ execSync6("tmux list-sessions", {
5258
5386
  timeout: 2e3,
5259
5387
  stdio: ["pipe", "pipe", "pipe"]
5260
5388
  });
@@ -5269,12 +5397,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
5269
5397
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
5270
5398
  try {
5271
5399
  const since = new Date(taskCreatedAt).toISOString();
5272
- const branch = execSync5(
5400
+ const branch = execSync6(
5273
5401
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
5274
5402
  { encoding: "utf8", timeout: 3e3 }
5275
5403
  ).trim();
5276
5404
  const branchArg = branch && branch !== "HEAD" ? branch : "";
5277
- const commitCount = execSync5(
5405
+ const commitCount = execSync6(
5278
5406
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
5279
5407
  { encoding: "utf8", timeout: 5e3 }
5280
5408
  ).trim();
@@ -6872,7 +7000,7 @@ __export(tmux_routing_exports, {
6872
7000
  spawnEmployee: () => spawnEmployee,
6873
7001
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
6874
7002
  });
6875
- import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
7003
+ import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
6876
7004
  import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
6877
7005
  import path19 from "path";
6878
7006
  import os11 from "os";
@@ -7582,7 +7710,7 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
7582
7710
  let booted = false;
7583
7711
  for (let i = 0; i < 30; i++) {
7584
7712
  try {
7585
- execSync6("sleep 0.5");
7713
+ execSync7("sleep 0.5");
7586
7714
  } catch {
7587
7715
  }
7588
7716
  try {
@@ -7699,7 +7827,7 @@ __export(git_task_sweep_exports, {
7699
7827
  matchScore: () => matchScore,
7700
7828
  sweepTasks: () => sweepTasks
7701
7829
  });
7702
- import { execSync as execSync7 } from "child_process";
7830
+ import { execSync as execSync8 } from "child_process";
7703
7831
  function extractKeywords(text) {
7704
7832
  return text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length >= 3 && !STOP_WORDS.has(w));
7705
7833
  }
@@ -7728,7 +7856,7 @@ function matchScore(task, commitMessage, changedFiles) {
7728
7856
  function getRecentCommits(limit = DEFAULT_COMMIT_LIMIT) {
7729
7857
  try {
7730
7858
  const SEPARATOR = "<<SEP>>";
7731
- const output = execSync7(
7859
+ const output = execSync8(
7732
7860
  `git log --format="%h${SEPARATOR}%s${SEPARATOR}%aI" --name-only -n ${limit} -z`,
7733
7861
  { encoding: "utf8", timeout: 1e4 }
7734
7862
  );
@@ -7919,12 +8047,12 @@ __export(worktree_exports, {
7919
8047
  worktreeBranch: () => worktreeBranch,
7920
8048
  worktreePath: () => worktreePath
7921
8049
  });
7922
- import { execSync as execSync8 } from "child_process";
8050
+ import { execSync as execSync9 } from "child_process";
7923
8051
  import { existsSync as existsSync17, readFileSync as readFileSync13, appendFileSync as appendFileSync2, mkdirSync as mkdirSync8, realpathSync } from "fs";
7924
8052
  import path20 from "path";
7925
8053
  function getGitRoot(dir) {
7926
8054
  try {
7927
- const root = execSync8("git rev-parse --show-toplevel", {
8055
+ const root = execSync9("git rev-parse --show-toplevel", {
7928
8056
  cwd: dir,
7929
8057
  encoding: "utf-8",
7930
8058
  timeout: GIT_TIMEOUT_MS,
@@ -7937,7 +8065,7 @@ function getGitRoot(dir) {
7937
8065
  }
7938
8066
  function getMainRepoRoot(dir) {
7939
8067
  try {
7940
- const commonDir = execSync8(
8068
+ const commonDir = execSync9(
7941
8069
  "git rev-parse --path-format=absolute --git-common-dir",
7942
8070
  { cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
7943
8071
  ).trim();
@@ -7968,7 +8096,7 @@ function ensureWorktree(projectDir, employeeName, instance) {
7968
8096
  mkdirSync8(worktreesDir, { recursive: true });
7969
8097
  ensureGitignoreEntry(repoRoot, "/.worktrees/");
7970
8098
  try {
7971
- execSync8("git worktree prune", {
8099
+ execSync9("git worktree prune", {
7972
8100
  cwd: repoRoot,
7973
8101
  encoding: "utf-8",
7974
8102
  timeout: GIT_TIMEOUT_MS,
@@ -7979,14 +8107,14 @@ function ensureWorktree(projectDir, employeeName, instance) {
7979
8107
  const branchExists = branchExistsLocally(repoRoot, branch);
7980
8108
  try {
7981
8109
  if (branchExists) {
7982
- execSync8(`git worktree add "${wtPath}" "${branch}"`, {
8110
+ execSync9(`git worktree add "${wtPath}" "${branch}"`, {
7983
8111
  cwd: repoRoot,
7984
8112
  encoding: "utf-8",
7985
8113
  timeout: GIT_TIMEOUT_MS,
7986
8114
  stdio: ["pipe", "pipe", "pipe"]
7987
8115
  });
7988
8116
  } else {
7989
- execSync8(`git worktree add "${wtPath}" -b "${branch}" HEAD`, {
8117
+ execSync9(`git worktree add "${wtPath}" -b "${branch}" HEAD`, {
7990
8118
  cwd: repoRoot,
7991
8119
  encoding: "utf-8",
7992
8120
  timeout: GIT_TIMEOUT_MS,
@@ -8004,7 +8132,7 @@ function ensureWorktree(projectDir, employeeName, instance) {
8004
8132
  }
8005
8133
  function isWorktreeDirty(wtPath) {
8006
8134
  try {
8007
- const status = execSync8("git status --porcelain", {
8135
+ const status = execSync9("git status --porcelain", {
8008
8136
  cwd: wtPath,
8009
8137
  encoding: "utf-8",
8010
8138
  timeout: GIT_TIMEOUT_MS,
@@ -8034,7 +8162,7 @@ function cleanupWorktree(projectDir, employeeName, instance) {
8034
8162
  return { cleaned: false, reason: `branch ${branch} not merged into ${mainBranch}` };
8035
8163
  }
8036
8164
  try {
8037
- execSync8(`git worktree remove "${wtPath}"`, {
8165
+ execSync9(`git worktree remove "${wtPath}"`, {
8038
8166
  cwd: repoRoot,
8039
8167
  encoding: "utf-8",
8040
8168
  timeout: GIT_TIMEOUT_MS,
@@ -8047,7 +8175,7 @@ function cleanupWorktree(projectDir, employeeName, instance) {
8047
8175
  };
8048
8176
  }
8049
8177
  try {
8050
- execSync8(`git branch -d "${branch}"`, {
8178
+ execSync9(`git branch -d "${branch}"`, {
8051
8179
  cwd: repoRoot,
8052
8180
  encoding: "utf-8",
8053
8181
  timeout: GIT_TIMEOUT_MS,
@@ -8059,7 +8187,7 @@ function cleanupWorktree(projectDir, employeeName, instance) {
8059
8187
  }
8060
8188
  function branchExistsLocally(repoRoot, branch) {
8061
8189
  try {
8062
- execSync8(`git rev-parse --verify "refs/heads/${branch}"`, {
8190
+ execSync9(`git rev-parse --verify "refs/heads/${branch}"`, {
8063
8191
  cwd: repoRoot,
8064
8192
  encoding: "utf-8",
8065
8193
  timeout: GIT_TIMEOUT_MS,
@@ -8078,7 +8206,7 @@ function detectMainBranch(repoRoot) {
8078
8206
  }
8079
8207
  function isBranchMerged(repoRoot, branch, into) {
8080
8208
  try {
8081
- const merged = execSync8(`git branch --merged "${into}"`, {
8209
+ const merged = execSync9(`git branch --merged "${into}"`, {
8082
8210
  cwd: repoRoot,
8083
8211
  encoding: "utf-8",
8084
8212
  timeout: GIT_TIMEOUT_MS,
@@ -8125,7 +8253,7 @@ init_database();
8125
8253
  init_task_scope();
8126
8254
  init_project_name();
8127
8255
  import crypto7 from "crypto";
8128
- import { execSync as execSync9 } from "child_process";
8256
+ import { execSync as execSync10 } from "child_process";
8129
8257
  var agentName = process.argv[2];
8130
8258
  var exeSession = process.argv[3];
8131
8259
  if (!agentName) process.exit(0);
@@ -8298,7 +8426,7 @@ try {
8298
8426
  }
8299
8427
  const msg = `session-ended: ${agentName} session terminated. ${parts.join(". ")}`;
8300
8428
  try {
8301
- execSync9(`tmux send-keys -t ${JSON.stringify(exeSession)} ${JSON.stringify(msg)} Enter`, {
8429
+ execSync10(`tmux send-keys -t ${JSON.stringify(exeSession)} ${JSON.stringify(msg)} Enter`, {
8302
8430
  timeout: 3e3
8303
8431
  });
8304
8432
  } catch {
@@ -1051,8 +1051,8 @@ function findPackageRoot() {
1051
1051
  function getAvailableMemoryGB() {
1052
1052
  if (process.platform === "darwin") {
1053
1053
  try {
1054
- const { execSync: execSync5 } = __require("child_process");
1055
- const vmstat = execSync5("vm_stat", { encoding: "utf8" });
1054
+ const { execSync: execSync6 } = __require("child_process");
1055
+ const vmstat = execSync6("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;
@@ -3183,7 +3183,7 @@ var init_runtime_table = __esm({
3183
3183
  });
3184
3184
 
3185
3185
  // src/lib/session-key.ts
3186
- import { execSync as execSync2 } from "child_process";
3186
+ import { execSync as execSync3 } from "child_process";
3187
3187
  function normalizeCommand(command) {
3188
3188
  const trimmed = command.trim().toLowerCase();
3189
3189
  const parts = trimmed.split(/[\\/]/);
@@ -3202,7 +3202,7 @@ function resolveRuntimeProcess() {
3202
3202
  let pid = process.ppid;
3203
3203
  for (let i = 0; i < 10; i++) {
3204
3204
  try {
3205
- const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
3205
+ const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
3206
3206
  encoding: "utf8",
3207
3207
  timeout: 2e3
3208
3208
  }).trim();
@@ -3261,7 +3261,7 @@ __export(active_agent_exports, {
3261
3261
  writeActiveAgent: () => writeActiveAgent
3262
3262
  });
3263
3263
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
3264
- import { execSync as execSync3 } from "child_process";
3264
+ import { execSync as execSync4 } from "child_process";
3265
3265
  import path9 from "path";
3266
3266
  function isNameWithOptionalInstance(candidate, baseName) {
3267
3267
  if (candidate === baseName) return true;
@@ -3353,7 +3353,7 @@ function getActiveAgent() {
3353
3353
  } catch {
3354
3354
  }
3355
3355
  try {
3356
- const sessionName = execSync3(
3356
+ const sessionName = execSync4(
3357
3357
  "tmux display-message -p '#{session_name}' 2>/dev/null",
3358
3358
  { encoding: "utf8", timeout: 2e3 }
3359
3359
  ).trim();
@@ -3482,7 +3482,7 @@ import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4, readdi
3482
3482
  import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync6, copyFileSync, mkdirSync as mkdirSync6 } from "fs";
3483
3483
  import path12 from "path";
3484
3484
  import os9 from "os";
3485
- import { execSync as execSync4 } from "child_process";
3485
+ import { execSync as execSync5 } from "child_process";
3486
3486
  import { fileURLToPath as fileURLToPath2 } from "url";
3487
3487
  function resolvePackageRoot() {
3488
3488
  const thisFile = fileURLToPath2(import.meta.url);
@@ -3946,6 +3946,7 @@ import { createHash } from "crypto";
3946
3946
  // src/lib/keychain.ts
3947
3947
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3948
3948
  import { existsSync as existsSync6 } from "fs";
3949
+ import { execSync as execSync2 } from "child_process";
3949
3950
  import path6 from "path";
3950
3951
  import os5 from "os";
3951
3952
  var SERVICE = "exe-mem";
@@ -3956,6 +3957,59 @@ function getKeyDir() {
3956
3957
  function getKeyPath() {
3957
3958
  return path6.join(getKeyDir(), "master.key");
3958
3959
  }
3960
+ function macKeychainGet() {
3961
+ if (process.platform !== "darwin") return null;
3962
+ try {
3963
+ return execSync2(
3964
+ `security find-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w 2>/dev/null`,
3965
+ { encoding: "utf-8", timeout: 5e3 }
3966
+ ).trim();
3967
+ } catch {
3968
+ return null;
3969
+ }
3970
+ }
3971
+ function macKeychainSet(value) {
3972
+ if (process.platform !== "darwin") return false;
3973
+ try {
3974
+ try {
3975
+ execSync2(
3976
+ `security delete-generic-password -s "${SERVICE}" -a "${ACCOUNT}" 2>/dev/null`,
3977
+ { timeout: 5e3 }
3978
+ );
3979
+ } catch {
3980
+ }
3981
+ execSync2(
3982
+ `security add-generic-password -s "${SERVICE}" -a "${ACCOUNT}" -w "${value}"`,
3983
+ { timeout: 5e3 }
3984
+ );
3985
+ return true;
3986
+ } catch {
3987
+ return false;
3988
+ }
3989
+ }
3990
+ function linuxSecretGet() {
3991
+ if (process.platform !== "linux") return null;
3992
+ try {
3993
+ return execSync2(
3994
+ `secret-tool lookup service "${SERVICE}" account "${ACCOUNT}" 2>/dev/null`,
3995
+ { encoding: "utf-8", timeout: 5e3 }
3996
+ ).trim();
3997
+ } catch {
3998
+ return null;
3999
+ }
4000
+ }
4001
+ function linuxSecretSet(value) {
4002
+ if (process.platform !== "linux") return false;
4003
+ try {
4004
+ execSync2(
4005
+ `echo -n "${value}" | secret-tool store --label="exe-os master key" service "${SERVICE}" account "${ACCOUNT}"`,
4006
+ { timeout: 5e3 }
4007
+ );
4008
+ return true;
4009
+ } catch {
4010
+ return false;
4011
+ }
4012
+ }
3959
4013
  async function tryKeytar() {
3960
4014
  try {
3961
4015
  return await import("keytar");
@@ -3963,13 +4017,64 @@ async function tryKeytar() {
3963
4017
  return null;
3964
4018
  }
3965
4019
  }
4020
+ var ENCRYPTED_PREFIX = "enc:";
4021
+ function deriveMachineKey() {
4022
+ try {
4023
+ const crypto3 = __require("crypto");
4024
+ const material = [
4025
+ os5.hostname(),
4026
+ os5.userInfo().username,
4027
+ os5.arch(),
4028
+ os5.platform(),
4029
+ // Machine ID on Linux (stable across reboots)
4030
+ process.platform === "linux" ? readMachineId() : ""
4031
+ ].join("|");
4032
+ return crypto3.createHash("sha256").update(material).digest();
4033
+ } catch {
4034
+ return null;
4035
+ }
4036
+ }
4037
+ function readMachineId() {
4038
+ try {
4039
+ const { readFileSync: readFileSync10 } = __require("fs");
4040
+ return readFileSync10("/etc/machine-id", "utf-8").trim();
4041
+ } catch {
4042
+ return "";
4043
+ }
4044
+ }
4045
+ function decryptWithMachineKey(encrypted, machineKey) {
4046
+ if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
4047
+ try {
4048
+ const crypto3 = __require("crypto");
4049
+ const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(":");
4050
+ if (parts.length !== 3) return null;
4051
+ const [ivB64, tagB64, cipherB64] = parts;
4052
+ const iv = Buffer.from(ivB64, "base64");
4053
+ const authTag = Buffer.from(tagB64, "base64");
4054
+ const decipher = crypto3.createDecipheriv("aes-256-gcm", machineKey, iv);
4055
+ decipher.setAuthTag(authTag);
4056
+ let decrypted = decipher.update(cipherB64, "base64", "utf-8");
4057
+ decrypted += decipher.final("utf-8");
4058
+ return decrypted;
4059
+ } catch {
4060
+ return null;
4061
+ }
4062
+ }
3966
4063
  async function getMasterKey() {
4064
+ const nativeValue = macKeychainGet() ?? linuxSecretGet();
4065
+ if (nativeValue) {
4066
+ return Buffer.from(nativeValue, "base64");
4067
+ }
3967
4068
  const keytar = await tryKeytar();
3968
4069
  if (keytar) {
3969
4070
  try {
3970
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
3971
- if (stored) {
3972
- return Buffer.from(stored, "base64");
4071
+ const keytarValue = await keytar.getPassword(SERVICE, ACCOUNT);
4072
+ if (keytarValue) {
4073
+ const migrated = macKeychainSet(keytarValue) || linuxSecretSet(keytarValue);
4074
+ if (migrated) {
4075
+ process.stderr.write("[keychain] Migrated key from keytar to native keychain.\n");
4076
+ }
4077
+ return Buffer.from(keytarValue, "base64");
3973
4078
  }
3974
4079
  } catch {
3975
4080
  }
@@ -3983,8 +4088,31 @@ async function getMasterKey() {
3983
4088
  return null;
3984
4089
  }
3985
4090
  try {
3986
- const content = await readFile3(keyPath, "utf-8");
3987
- return Buffer.from(content.trim(), "base64");
4091
+ const content = (await readFile3(keyPath, "utf-8")).trim();
4092
+ let b64Value;
4093
+ if (content.startsWith(ENCRYPTED_PREFIX)) {
4094
+ const machineKey = deriveMachineKey();
4095
+ if (!machineKey) {
4096
+ process.stderr.write("[keychain] Cannot derive machine key to decrypt stored key.\n");
4097
+ return null;
4098
+ }
4099
+ const decrypted = decryptWithMachineKey(content, machineKey);
4100
+ if (!decrypted) {
4101
+ process.stderr.write(
4102
+ "[keychain] Key decryption failed \u2014 machine may have changed.\n Use your 24-word recovery phrase: exe-os link import\n"
4103
+ );
4104
+ return null;
4105
+ }
4106
+ b64Value = decrypted;
4107
+ } else {
4108
+ b64Value = content;
4109
+ }
4110
+ const key = Buffer.from(b64Value, "base64");
4111
+ const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
4112
+ if (migrated) {
4113
+ process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
4114
+ }
4115
+ return key;
3988
4116
  } catch (err) {
3989
4117
  process.stderr.write(
3990
4118
  `[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
@@ -4538,8 +4666,8 @@ async function main() {
4538
4666
  }
4539
4667
  if (!process.env[CODEX.apiKeyEnv]) {
4540
4668
  try {
4541
- const { execSync: execSync5 } = await import("child_process");
4542
- const status = execSync5("codex login status 2>&1", { encoding: "utf8", timeout: 5e3 }).trim();
4669
+ const { execSync: execSync6 } = await import("child_process");
4670
+ const status = execSync6("codex login status 2>&1", { encoding: "utf8", timeout: 5e3 }).trim();
4543
4671
  if (!status.toLowerCase().includes("logged in")) {
4544
4672
  process.stderr.write(
4545
4673
  `exe-start-codex: not authenticated. Run \`codex login\` or set ${CODEX.apiKeyEnv}.