aicodeman 1.19.7 → 1.20.0

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 (142) hide show
  1. package/dist/git-clone.d.ts +7 -0
  2. package/dist/git-clone.d.ts.map +1 -1
  3. package/dist/git-clone.js +10 -2
  4. package/dist/git-clone.js.map +1 -1
  5. package/dist/ralph-loop.d.ts.map +1 -1
  6. package/dist/ralph-loop.js +8 -1
  7. package/dist/ralph-loop.js.map +1 -1
  8. package/dist/session.d.ts +34 -0
  9. package/dist/session.d.ts.map +1 -1
  10. package/dist/session.js +61 -1
  11. package/dist/session.js.map +1 -1
  12. package/dist/tmux-manager.d.ts.map +1 -1
  13. package/dist/tmux-manager.js +11 -8
  14. package/dist/tmux-manager.js.map +1 -1
  15. package/dist/types/session.d.ts +8 -0
  16. package/dist/types/session.d.ts.map +1 -1
  17. package/dist/types/session.js.map +1 -1
  18. package/dist/types/update.d.ts +45 -0
  19. package/dist/types/update.d.ts.map +1 -1
  20. package/dist/utils/antigravity-cli-resolver.d.ts +4 -0
  21. package/dist/utils/antigravity-cli-resolver.d.ts.map +1 -1
  22. package/dist/utils/antigravity-cli-resolver.js +17 -29
  23. package/dist/utils/antigravity-cli-resolver.js.map +1 -1
  24. package/dist/utils/claude-cli-resolver.d.ts +1 -0
  25. package/dist/utils/claude-cli-resolver.d.ts.map +1 -1
  26. package/dist/utils/claude-cli-resolver.js +15 -28
  27. package/dist/utils/claude-cli-resolver.js.map +1 -1
  28. package/dist/utils/cli-executable-resolver.d.ts +98 -0
  29. package/dist/utils/cli-executable-resolver.d.ts.map +1 -0
  30. package/dist/utils/cli-executable-resolver.js +229 -0
  31. package/dist/utils/cli-executable-resolver.js.map +1 -0
  32. package/dist/utils/codex-cli-resolver.d.ts +1 -0
  33. package/dist/utils/codex-cli-resolver.d.ts.map +1 -1
  34. package/dist/utils/codex-cli-resolver.js +8 -30
  35. package/dist/utils/codex-cli-resolver.js.map +1 -1
  36. package/dist/utils/gemini-cli-resolver.d.ts +1 -0
  37. package/dist/utils/gemini-cli-resolver.d.ts.map +1 -1
  38. package/dist/utils/gemini-cli-resolver.js +8 -29
  39. package/dist/utils/gemini-cli-resolver.js.map +1 -1
  40. package/dist/utils/index.d.ts +6 -6
  41. package/dist/utils/index.d.ts.map +1 -1
  42. package/dist/utils/index.js +6 -6
  43. package/dist/utils/index.js.map +1 -1
  44. package/dist/utils/opencode-cli-resolver.d.ts +1 -0
  45. package/dist/utils/opencode-cli-resolver.d.ts.map +1 -1
  46. package/dist/utils/opencode-cli-resolver.js +8 -31
  47. package/dist/utils/opencode-cli-resolver.js.map +1 -1
  48. package/dist/utils/pi-cli-resolver.d.ts +12 -3
  49. package/dist/utils/pi-cli-resolver.d.ts.map +1 -1
  50. package/dist/utils/pi-cli-resolver.js +40 -55
  51. package/dist/utils/pi-cli-resolver.js.map +1 -1
  52. package/dist/web/approval-inbox.d.ts +45 -8
  53. package/dist/web/approval-inbox.d.ts.map +1 -1
  54. package/dist/web/approval-inbox.js +116 -28
  55. package/dist/web/approval-inbox.js.map +1 -1
  56. package/dist/web/public/admin-ui.js.gz +0 -0
  57. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  58. package/dist/web/public/app.56c520ff.js.gz +0 -0
  59. package/dist/web/public/approvals-ui.js.gz +0 -0
  60. package/dist/web/public/{constants.dc5d04bb.js → constants.5abbd823.js} +53 -0
  61. package/dist/web/public/constants.5abbd823.js.br +0 -0
  62. package/dist/web/public/constants.5abbd823.js.gz +0 -0
  63. package/dist/web/public/cron-ui.js.gz +0 -0
  64. package/dist/web/public/entrance-animations.js.gz +0 -0
  65. package/dist/web/public/home-sessions.js.gz +0 -0
  66. package/dist/web/public/i18n.196a992e.js +1 -0
  67. package/dist/web/public/i18n.196a992e.js.br +0 -0
  68. package/dist/web/public/i18n.196a992e.js.gz +0 -0
  69. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  70. package/dist/web/public/index.html +18 -5
  71. package/dist/web/public/index.html.br +0 -0
  72. package/dist/web/public/index.html.gz +0 -0
  73. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  74. package/dist/web/public/keyboard-accessory.0454a3e9.js.gz +0 -0
  75. package/dist/web/public/mobile-handlers.0f83a608.js.gz +0 -0
  76. package/dist/web/public/mobile-overview.js.gz +0 -0
  77. package/dist/web/public/mobile.49aeb17e.css.gz +0 -0
  78. package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
  79. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  80. package/dist/web/public/panels-ui.5e97a36e.js.gz +0 -0
  81. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  82. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  83. package/dist/web/public/readmymind-ui.js.gz +0 -0
  84. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  85. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  86. package/dist/web/public/session-lineage.js.gz +0 -0
  87. package/dist/web/public/session-ui.71a0ce9a.js.gz +0 -0
  88. package/dist/web/public/settings-ui.1a62e935.js +67 -0
  89. package/dist/web/public/settings-ui.1a62e935.js.br +0 -0
  90. package/dist/web/public/settings-ui.1a62e935.js.gz +0 -0
  91. package/dist/web/public/styles.68f9e118.css.gz +0 -0
  92. package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
  93. package/dist/web/public/sw.js.gz +0 -0
  94. package/dist/web/public/terminal-ui.5f4158b7.js +2 -0
  95. package/dist/web/public/terminal-ui.5f4158b7.js.br +0 -0
  96. package/dist/web/public/terminal-ui.5f4158b7.js.gz +0 -0
  97. package/dist/web/public/ultracode-panel.js.gz +0 -0
  98. package/dist/web/public/ultracode-windows.js.gz +0 -0
  99. package/dist/web/public/upload.html.gz +0 -0
  100. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  101. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  102. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  103. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  104. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  105. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  106. package/dist/web/public/vendor/xterm-predictive-echo.bd6882b8.js.gz +0 -0
  107. package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
  108. package/dist/web/public/vendor/xterm.css.gz +0 -0
  109. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  110. package/dist/web/public/voice-input.4ec670b0.js.gz +0 -0
  111. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  112. package/dist/web/public/webview-tabs.js.gz +0 -0
  113. package/dist/web/repo-status.d.ts +124 -0
  114. package/dist/web/repo-status.d.ts.map +1 -0
  115. package/dist/web/repo-status.js +353 -0
  116. package/dist/web/repo-status.js.map +1 -0
  117. package/dist/web/response-viewer-transcript.d.ts +12 -0
  118. package/dist/web/response-viewer-transcript.d.ts.map +1 -0
  119. package/dist/web/response-viewer-transcript.js +345 -0
  120. package/dist/web/response-viewer-transcript.js.map +1 -0
  121. package/dist/web/routes/session-routes.d.ts.map +1 -1
  122. package/dist/web/routes/session-routes.js +41 -22
  123. package/dist/web/routes/session-routes.js.map +1 -1
  124. package/dist/web/routes/system-routes.d.ts.map +1 -1
  125. package/dist/web/routes/system-routes.js +5 -0
  126. package/dist/web/routes/system-routes.js.map +1 -1
  127. package/dist/web/session-listener-wiring.d.ts +1 -0
  128. package/dist/web/session-listener-wiring.d.ts.map +1 -1
  129. package/dist/web/session-listener-wiring.js +26 -3
  130. package/dist/web/session-listener-wiring.js.map +1 -1
  131. package/package.json +1 -1
  132. package/dist/web/public/constants.dc5d04bb.js.br +0 -0
  133. package/dist/web/public/constants.dc5d04bb.js.gz +0 -0
  134. package/dist/web/public/i18n.6a46f014.js +0 -1
  135. package/dist/web/public/i18n.6a46f014.js.br +0 -0
  136. package/dist/web/public/i18n.6a46f014.js.gz +0 -0
  137. package/dist/web/public/settings-ui.b62c70a3.js +0 -67
  138. package/dist/web/public/settings-ui.b62c70a3.js.br +0 -0
  139. package/dist/web/public/settings-ui.b62c70a3.js.gz +0 -0
  140. package/dist/web/public/terminal-ui.4723f5d4.js +0 -4
  141. package/dist/web/public/terminal-ui.4723f5d4.js.br +0 -0
  142. package/dist/web/public/terminal-ui.4723f5d4.js.gz +0 -0
@@ -6,20 +6,27 @@
6
6
  *
7
7
  * @module utils/antigravity-cli-resolver
8
8
  */
9
- import { execSync } from 'node:child_process';
10
- import { existsSync } from 'node:fs';
11
- import { dirname, join } from 'node:path';
9
+ import { join } from 'node:path';
12
10
  import { homedir } from 'node:os';
13
- import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js';
11
+ import { createCliExecutableResolver, formatCliNotFoundMessage, } from './cli-executable-resolver.js';
14
12
  /** Common directories where the Antigravity CLI binary may be installed */
15
13
  const ANTIGRAVITY_SEARCH_DIRS = [
16
14
  join(homedir(), '.local', 'bin'),
17
15
  join(homedir(), '.antigravity', 'bin'),
18
16
  '/usr/local/bin',
17
+ join(homedir(), '.bun', 'bin'),
18
+ join(homedir(), '.npm-global', 'bin'),
19
19
  join(homedir(), 'bin'),
20
20
  ];
21
- /** Cached directory containing the agy binary (empty string = searched but not found) */
22
- let _antigravityDir = null;
21
+ const ANTIGRAVITY_NOT_FOUND = 'Antigravity CLI not found. Install with: curl -fsSL https://antigravity.google/cli/install.sh | bash';
22
+ function createAntigravityResolver(host, now) {
23
+ return createCliExecutableResolver({ binary: 'agy', searchDirs: ANTIGRAVITY_SEARCH_DIRS, now }, host);
24
+ }
25
+ /** Creates an isolated Antigravity wrapper around an injected resolver host and clock. */
26
+ export function createAntigravityResolverForTest(host, now) {
27
+ return createAntigravityResolver(host, now);
28
+ }
29
+ const antigravityResolver = createAntigravityResolver();
23
30
  /**
24
31
  * Finds the directory containing the `agy` binary.
25
32
  * Checks `which agy` first, then falls back to common install locations.
@@ -27,29 +34,7 @@ let _antigravityDir = null;
27
34
  * @returns Directory path, or null if not found
28
35
  */
29
36
  export function resolveAntigravityDir() {
30
- if (_antigravityDir !== null)
31
- return _antigravityDir || null;
32
- try {
33
- const result = execSync('which agy', {
34
- encoding: 'utf-8',
35
- timeout: EXEC_TIMEOUT_MS,
36
- }).trim();
37
- if (result && existsSync(result)) {
38
- _antigravityDir = dirname(result);
39
- return _antigravityDir;
40
- }
41
- }
42
- catch {
43
- // agy not in PATH, will check common locations
44
- }
45
- for (const dir of ANTIGRAVITY_SEARCH_DIRS) {
46
- if (existsSync(join(dir, 'agy'))) {
47
- _antigravityDir = dir;
48
- return _antigravityDir;
49
- }
50
- }
51
- _antigravityDir = '';
52
- return null;
37
+ return antigravityResolver.resolve()?.directory ?? null;
53
38
  }
54
39
  /**
55
40
  * Check if the Antigravity CLI is available on the system.
@@ -57,4 +42,7 @@ export function resolveAntigravityDir() {
57
42
  export function isAntigravityAvailable() {
58
43
  return resolveAntigravityDir() !== null;
59
44
  }
45
+ export function getAntigravityNotFoundMessage() {
46
+ return formatCliNotFoundMessage(ANTIGRAVITY_NOT_FOUND, antigravityResolver.diagnostics());
47
+ }
60
48
  //# sourceMappingURL=antigravity-cli-resolver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"antigravity-cli-resolver.js","sourceRoot":"","sources":["../../src/utils/antigravity-cli-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,2EAA2E;AAC3E,MAAM,uBAAuB,GAAG;IAC9B,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC;IAChC,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,EAAE,KAAK,CAAC;IACtC,gBAAgB;IAChB,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC;CACvB,CAAC;AAEF,yFAAyF;AACzF,IAAI,eAAe,GAAkB,IAAI,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB;IACnC,IAAI,eAAe,KAAK,IAAI;QAAE,OAAO,eAAe,IAAI,IAAI,CAAC;IAE7D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,EAAE;YACnC,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,eAAe;SACzB,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,MAAM,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACjC,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;YAClC,OAAO,eAAe,CAAC;QACzB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,uBAAuB,EAAE,CAAC;QAC1C,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YACjC,eAAe,GAAG,GAAG,CAAC;YACtB,OAAO,eAAe,CAAC;QACzB,CAAC;IACH,CAAC;IAED,eAAe,GAAG,EAAE,CAAC;IACrB,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,qBAAqB,EAAE,KAAK,IAAI,CAAC;AAC1C,CAAC"}
1
+ {"version":3,"file":"antigravity-cli-resolver.js","sourceRoot":"","sources":["../../src/utils/antigravity-cli-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,GAEzB,MAAM,8BAA8B,CAAC;AAEtC,2EAA2E;AAC3E,MAAM,uBAAuB,GAAG;IAC9B,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC;IAChC,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,EAAE,KAAK,CAAC;IACtC,gBAAgB;IAChB,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC;IAC9B,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,KAAK,CAAC;IACrC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC;CACvB,CAAC;AAEF,MAAM,qBAAqB,GACzB,sGAAsG,CAAC;AAEzG,SAAS,yBAAyB,CAAC,IAAsB,EAAE,GAAkB;IAC3E,OAAO,2BAA2B,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,uBAAuB,EAAE,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;AACxG,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,gCAAgC,CAAC,IAAqB,EAAE,GAAkB;IACxF,OAAO,yBAAyB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,mBAAmB,GAAG,yBAAyB,EAAE,CAAC;AAExD;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO,mBAAmB,CAAC,OAAO,EAAE,EAAE,SAAS,IAAI,IAAI,CAAC;AAC1D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,qBAAqB,EAAE,KAAK,IAAI,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,6BAA6B;IAC3C,OAAO,wBAAwB,CAAC,qBAAqB,EAAE,mBAAmB,CAAC,WAAW,EAAE,CAAC,CAAC;AAC5F,CAAC"}
@@ -21,6 +21,7 @@ export declare function isClaudeAvailable(): boolean;
21
21
  * @returns Directory path, or null if not found
22
22
  */
23
23
  export declare function findClaudeDir(): string | null;
24
+ export declare function getClaudeNotFoundMessage(): string;
24
25
  /**
25
26
  * Returns an absolute path to the `claude` binary, falling back to the bare
26
27
  * name `'claude'` when it cannot be located (so PATH resolution still gets a
@@ -1 +1 @@
1
- {"version":3,"file":"claude-cli-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/claude-cli-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoBH;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAwB7C;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAG5C;AAKD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAazC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,aAAa,EAAE,MAAM,CAAC;CACvB;AAOD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGlE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,uBAAuB,EAC9B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,MAAM,GAAG,IAAI,GACzB,MAAM,GAAG,IAAI,CAoBf;AAmBD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAOnD"}
1
+ {"version":3,"file":"claude-cli-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/claude-cli-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoBH;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAE7C;AAED,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAG5C;AAKD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAezC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,aAAa,EAAE,MAAM,CAAC;CACvB;AAOD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGlE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,uBAAuB,EAC9B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,MAAM,GAAG,IAAI,GACzB,MAAM,GAAG,IAAI,CAoBf;AAsBD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAOnD"}
@@ -7,11 +7,11 @@
7
7
  *
8
8
  * @module utils/claude-cli-resolver
9
9
  */
10
- import { execSync, execFileSync } from 'node:child_process';
11
- import { existsSync } from 'node:fs';
12
- import { delimiter, dirname, join } from 'node:path';
10
+ import { execFileSync } from 'node:child_process';
11
+ import { delimiter, join } from 'node:path';
13
12
  import { homedir } from 'node:os';
14
13
  import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js';
14
+ import { createCliExecutableResolver, formatCliNotFoundMessage } from './cli-executable-resolver.js';
15
15
  /** Common directories where the Claude CLI binary may be installed */
16
16
  const CLAUDE_SEARCH_DIRS = [
17
17
  join(homedir(), '.local', 'bin'),
@@ -20,8 +20,8 @@ const CLAUDE_SEARCH_DIRS = [
20
20
  join(homedir(), '.npm-global', 'bin'),
21
21
  join(homedir(), 'bin'),
22
22
  ];
23
- /** Cached directory containing the claude binary (empty string = searched but not found) */
24
- let _claudeDir = null;
23
+ const claudeResolver = createCliExecutableResolver({ binary: 'claude', searchDirs: CLAUDE_SEARCH_DIRS });
24
+ const CLAUDE_NOT_FOUND = 'Claude CLI not found. Install it with: curl -fsSL https://claude.ai/install.sh | bash';
25
25
  /**
26
26
  * Returns true if the Claude CLI binary can be located (via `which` or one of
27
27
  * the common install directories). Mirrors `isGeminiAvailable`/`isAntigravityAvailable`/`isOpenCodeAvailable`/
@@ -38,28 +38,10 @@ export function isClaudeAvailable() {
38
38
  * @returns Directory path, or null if not found
39
39
  */
40
40
  export function findClaudeDir() {
41
- if (_claudeDir !== null)
42
- return _claudeDir || null;
43
- // Try `which` first (respects current PATH)
44
- try {
45
- const result = execSync('which claude', { encoding: 'utf-8', timeout: EXEC_TIMEOUT_MS }).trim();
46
- if (result && existsSync(result)) {
47
- _claudeDir = dirname(result);
48
- return _claudeDir;
49
- }
50
- }
51
- catch {
52
- // Claude not in PATH, will check common locations
53
- }
54
- // Fallback: check common installation directories
55
- for (const dir of CLAUDE_SEARCH_DIRS) {
56
- if (existsSync(join(dir, 'claude'))) {
57
- _claudeDir = dir;
58
- return _claudeDir;
59
- }
60
- }
61
- _claudeDir = ''; // mark as searched, not found
62
- return null;
41
+ return claudeResolver.resolve()?.directory ?? null;
42
+ }
43
+ export function getClaudeNotFoundMessage() {
44
+ return formatCliNotFoundMessage(CLAUDE_NOT_FOUND, claudeResolver.diagnostics());
63
45
  }
64
46
  /**
65
47
  * Returns an absolute path to the `claude` binary, falling back to the bare
@@ -89,7 +71,9 @@ export function getAugmentedPath() {
89
71
  return _augmentedPath;
90
72
  const currentPath = process.env.PATH || '';
91
73
  const claudeDir = findClaudeDir();
92
- if (claudeDir && !currentPath.split(delimiter).includes(claudeDir)) {
74
+ if (!claudeDir)
75
+ return currentPath;
76
+ if (!currentPath.split(delimiter).includes(claudeDir)) {
93
77
  _augmentedPath = `${claudeDir}${delimiter}${currentPath}`;
94
78
  return _augmentedPath;
95
79
  }
@@ -159,6 +143,9 @@ function probeClaudeCliVersion() {
159
143
  encoding: 'utf-8',
160
144
  timeout: EXEC_TIMEOUT_MS,
161
145
  env: { ...process.env, PATH: getAugmentedPath() },
146
+ // execFileSync's timeout only SENDS the signal and then keeps waiting; a
147
+ // child that ignores SIGTERM would block the server thread permanently.
148
+ killSignal: 'SIGKILL',
162
149
  });
163
150
  const match = out.match(/(\d+\.\d+\.\d+)/);
164
151
  return match ? match[1] : null;
@@ -1 +1 @@
1
- {"version":3,"file":"claude-cli-resolver.js","sourceRoot":"","sources":["../../src/utils/claude-cli-resolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude-cli-resolver.js","sourceRoot":"","sources":["../../src/utils/claude-cli-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @fileoverview Shared CLI executable resolution for the per-CLI resolvers.
3
+ *
4
+ * One lookup chain behind all six *-cli-resolver modules (claude, opencode,
5
+ * codex, gemini, antigravity, pi): the server process PATH first, then the
6
+ * CLI's common install directories in order, then — last, because it is the
7
+ * only step that spawns anything — an interactive login shell, which is what
8
+ * finds nvm/Homebrew/user-npm installs when Codeman runs as a systemd/launchd
9
+ * service with a minimal PATH (launchd hands a job `/usr/bin:/bin:/usr/sbin:/sbin`).
10
+ *
11
+ * Caching is asymmetric, same shape as `resolveClaudeCliVersion` in
12
+ * claude-cli-resolver.ts: a successful resolution is cached for the process
13
+ * lifetime, a MISS is negative-cached and retried only after a doubling backoff
14
+ * (`cliResolveRetryDelayMs`). The callers are request-facing (the per-CLI
15
+ * status endpoints in system-routes.ts, the availability gates in
16
+ * session-routes.ts, and tmux-manager's spawn path), and the login-shell probe
17
+ * is a SYNCHRONOUS spawn bounded by `EXEC_TIMEOUT_MS` — without the negative
18
+ * cache, a missing CLI re-ran the whole chain and stalled the event loop for up
19
+ * to 5s on every request, forever.
20
+ *
21
+ * Test hermeticity: under vitest (`process.env.VITEST`) the production host
22
+ * short-circuits — IO primitives that were not injected become inert stubs, so
23
+ * a suite can never scan the machine's PATH or spawn login shells (the same
24
+ * rule as `IS_TEST_MODE` in tmux-manager and the VITEST gate in
25
+ * `getClaudeCliVersion`). Tests opt back in through the injection hooks
26
+ * (`runCommand`/`isExecutableFile` fakes do no real IO by construction) or, for
27
+ * fixtures that need the real filesystem predicate against their own temp
28
+ * files, via `allowRealIoUnderVitest`.
29
+ *
30
+ * @module utils/cli-executable-resolver
31
+ */
32
+ /**
33
+ * How long to wait before re-running the resolution chain after `failures`
34
+ * consecutive misses: 1min, 2min, 4min… capped at 5min. Mirrors
35
+ * `claudeVersionRetryDelayMs` in claude-cli-resolver.ts. Exported for tests.
36
+ */
37
+ export declare function cliResolveRetryDelayMs(failures: number): number;
38
+ export type CliResolutionSource = 'process-path' | 'common-directory' | 'login-shell';
39
+ export interface CliResolutionDiagnostics {
40
+ binary: string;
41
+ processPath: string;
42
+ shellPath: string;
43
+ shellArgs: string[];
44
+ searchDirs: string[];
45
+ }
46
+ export interface CliResolverHost {
47
+ processPath: string;
48
+ shellPath: string;
49
+ shellArgs: string[];
50
+ findOnProcessPath(binary: string): string | null;
51
+ findInLoginShell(binary: string): string | null;
52
+ exists(path: string): boolean;
53
+ }
54
+ export interface CandidateValidation<T> {
55
+ accepted: boolean;
56
+ metadata?: T;
57
+ }
58
+ export interface CliResolution<T = undefined> {
59
+ binaryPath: string;
60
+ directory: string;
61
+ source: CliResolutionSource;
62
+ metadata?: T;
63
+ }
64
+ export interface CliExecutableResolver<T = undefined> {
65
+ resolve(): CliResolution<T> | null;
66
+ diagnostics(): CliResolutionDiagnostics;
67
+ }
68
+ export interface CliResolverCommandOptions {
69
+ encoding: 'utf8';
70
+ timeout: number;
71
+ stdio: ['ignore', 'pipe', 'ignore'];
72
+ killSignal: 'SIGKILL';
73
+ }
74
+ export type CliResolverCommandRunner = (file: string, args: string[], options: CliResolverCommandOptions) => string;
75
+ export interface ProductionCliResolverHostOptions {
76
+ processPath?: string;
77
+ shellPath?: string;
78
+ shellArgs?: string[];
79
+ runCommand?: CliResolverCommandRunner;
80
+ isExecutableFile?: (path: string) => boolean;
81
+ /**
82
+ * Test-only escape hatch: keep the REAL IO primitives even under vitest.
83
+ * For tests that exercise `isExecutableRegularFile` against their own temp
84
+ * fixtures. Such a test must still inject `runCommand` if it can reach the
85
+ * login-shell step, or it would spawn a real interactive shell.
86
+ */
87
+ allowRealIoUnderVitest?: boolean;
88
+ }
89
+ export declare function createProductionCliResolverHost(options?: ProductionCliResolverHostOptions): CliResolverHost;
90
+ export declare function createCliExecutableResolver<T = undefined>(options: {
91
+ binary: string;
92
+ searchDirs: string[];
93
+ validateCandidate?: (path: string) => CandidateValidation<T>;
94
+ /** Clock injection for tests driving the failure backoff. Defaults to `Date.now`. */
95
+ now?: () => number;
96
+ }, host?: CliResolverHost): CliExecutableResolver<T>;
97
+ export declare function formatCliNotFoundMessage(base: string, diagnostics: CliResolutionDiagnostics): string;
98
+ //# sourceMappingURL=cli-executable-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-executable-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/cli-executable-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAwBH;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,kBAAkB,GAAG,aAAa,CAAC;AAEtF,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACjD,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAChD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,CAAC,CAAC;CACd;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,SAAS;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC,GAAG,SAAS;IAClD,OAAO,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACnC,WAAW,IAAI,wBAAwB,CAAC;CACzC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACpC,UAAU,EAAE,SAAS,CAAC;CACvB;AAED,MAAM,MAAM,wBAAwB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,yBAAyB,KAAK,MAAM,CAAC;AAEpH,MAAM,WAAW,gCAAgC;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,wBAAwB,CAAC;IACtC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAiCD,wBAAgB,+BAA+B,CAAC,OAAO,GAAE,gCAAqC,GAAG,eAAe,CAiD/G;AAED,wBAAgB,2BAA2B,CAAC,CAAC,GAAG,SAAS,EACvD,OAAO,EAAE;IACP,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAC7D,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,EACD,IAAI,GAAE,eAAmD,GACxD,qBAAqB,CAAC,CAAC,CAAC,CA6D1B;AAgBD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,wBAAwB,GAAG,MAAM,CAQpG"}
@@ -0,0 +1,229 @@
1
+ /**
2
+ * @fileoverview Shared CLI executable resolution for the per-CLI resolvers.
3
+ *
4
+ * One lookup chain behind all six *-cli-resolver modules (claude, opencode,
5
+ * codex, gemini, antigravity, pi): the server process PATH first, then the
6
+ * CLI's common install directories in order, then — last, because it is the
7
+ * only step that spawns anything — an interactive login shell, which is what
8
+ * finds nvm/Homebrew/user-npm installs when Codeman runs as a systemd/launchd
9
+ * service with a minimal PATH (launchd hands a job `/usr/bin:/bin:/usr/sbin:/sbin`).
10
+ *
11
+ * Caching is asymmetric, same shape as `resolveClaudeCliVersion` in
12
+ * claude-cli-resolver.ts: a successful resolution is cached for the process
13
+ * lifetime, a MISS is negative-cached and retried only after a doubling backoff
14
+ * (`cliResolveRetryDelayMs`). The callers are request-facing (the per-CLI
15
+ * status endpoints in system-routes.ts, the availability gates in
16
+ * session-routes.ts, and tmux-manager's spawn path), and the login-shell probe
17
+ * is a SYNCHRONOUS spawn bounded by `EXEC_TIMEOUT_MS` — without the negative
18
+ * cache, a missing CLI re-ran the whole chain and stalled the event loop for up
19
+ * to 5s on every request, forever.
20
+ *
21
+ * Test hermeticity: under vitest (`process.env.VITEST`) the production host
22
+ * short-circuits — IO primitives that were not injected become inert stubs, so
23
+ * a suite can never scan the machine's PATH or spawn login shells (the same
24
+ * rule as `IS_TEST_MODE` in tmux-manager and the VITEST gate in
25
+ * `getClaudeCliVersion`). Tests opt back in through the injection hooks
26
+ * (`runCommand`/`isExecutableFile` fakes do no real IO by construction) or, for
27
+ * fixtures that need the real filesystem predicate against their own temp
28
+ * files, via `allowRealIoUnderVitest`.
29
+ *
30
+ * @module utils/cli-executable-resolver
31
+ */
32
+ import { execFileSync } from 'node:child_process';
33
+ import { accessSync, constants, statSync } from 'node:fs';
34
+ import { basename, delimiter, dirname, isAbsolute, join } from 'node:path';
35
+ import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js';
36
+ import { loginShellArgs, resolveLocalShell } from './shell-resolver.js';
37
+ const SAFE_BINARY_NAME = /^[a-z0-9][a-z0-9._-]*$/i;
38
+ const LOGIN_SHELL_BEGIN_MARKER = '__CODEMAN_CLI_RESOLVE_BEGIN__';
39
+ const LOGIN_SHELL_END_MARKER = '__CODEMAN_CLI_RESOLVE_END__';
40
+ /** Maximum rendered length of each bounded diagnostic field, excluding its label. */
41
+ const DIAGNOSTIC_FIELD_MAX_LENGTH = 1024;
42
+ /** First retry window after a full-chain resolution miss. */
43
+ const RESOLVE_RETRY_BASE_MS = 60_000;
44
+ /**
45
+ * Ceiling for the doubling backoff. Deliberately shorter than the 15min cap on
46
+ * the claude version probe: that one is cosmetic, while this gates the Run
47
+ * flow, and "installing a CLI while the server is running is picked up without
48
+ * a restart" should stay true within minutes.
49
+ */
50
+ const RESOLVE_RETRY_MAX_MS = 5 * 60_000;
51
+ /**
52
+ * How long to wait before re-running the resolution chain after `failures`
53
+ * consecutive misses: 1min, 2min, 4min… capped at 5min. Mirrors
54
+ * `claudeVersionRetryDelayMs` in claude-cli-resolver.ts. Exported for tests.
55
+ */
56
+ export function cliResolveRetryDelayMs(failures) {
57
+ if (failures <= 0)
58
+ return 0;
59
+ return Math.min(RESOLVE_RETRY_BASE_MS * 2 ** (failures - 1), RESOLVE_RETRY_MAX_MS);
60
+ }
61
+ function isExecutableRegularFile(path) {
62
+ try {
63
+ if (!statSync(path).isFile())
64
+ return false;
65
+ accessSync(path, constants.X_OK);
66
+ return true;
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
72
+ function parseLoginShellResult(output, binary) {
73
+ const lines = output.split(/\r?\n/).map((line) => line.trim());
74
+ const begin = lines.indexOf(LOGIN_SHELL_BEGIN_MARKER);
75
+ if (begin === -1)
76
+ return null;
77
+ const end = lines.indexOf(LOGIN_SHELL_END_MARKER, begin + 1);
78
+ if (end === -1)
79
+ return null;
80
+ for (const candidate of lines.slice(begin + 1, end)) {
81
+ if (isAbsolute(candidate) && basename(candidate) === binary)
82
+ return candidate;
83
+ }
84
+ return null;
85
+ }
86
+ function loginShellCommand(binary) {
87
+ return [
88
+ `printf '%s\\n' '${LOGIN_SHELL_BEGIN_MARKER}'`,
89
+ `command -v -- ${binary}`,
90
+ `printf '%s\\n' '${LOGIN_SHELL_END_MARKER}'`,
91
+ ].join('; ');
92
+ }
93
+ export function createProductionCliResolverHost(options = {}) {
94
+ const shellPath = options.shellPath ?? resolveLocalShell();
95
+ const shellArgs = options.shellArgs ?? loginShellArgs(shellPath).trim().split(/\s+/).filter(Boolean);
96
+ const processPath = options.processPath ?? process.env.PATH ?? '';
97
+ // Hermeticity gate (see @fileoverview): under vitest, any IO primitive the
98
+ // caller did not inject is replaced by an inert stub. The suites must never
99
+ // depend on — or execute — whatever happens to be installed on the machine
100
+ // running them, and route tests hitting the per-CLI status endpoints would
101
+ // otherwise scan the real PATH and spawn real login shells on CI.
102
+ const inert = Boolean(process.env.VITEST) && options.allowRealIoUnderVitest !== true;
103
+ const isExecutableFile = options.isExecutableFile ?? (inert ? () => false : isExecutableRegularFile);
104
+ const runCommand = options.runCommand ?? (inert ? () => '' : (file, args, commandOptions) => execFileSync(file, args, commandOptions));
105
+ const run = (file, args) => {
106
+ try {
107
+ return runCommand(file, args, {
108
+ encoding: 'utf8',
109
+ timeout: EXEC_TIMEOUT_MS,
110
+ stdio: ['ignore', 'pipe', 'ignore'],
111
+ // SIGKILL is load-bearing: execFileSync's `timeout` only SENDS the kill
112
+ // signal and then keeps waiting for the child to exit. Interactive bash
113
+ // ignores SIGTERM (the default), so a login shell stuck in a blocking
114
+ // .bash_profile would survive the timeout and block the server forever.
115
+ killSignal: 'SIGKILL',
116
+ });
117
+ }
118
+ catch {
119
+ return '';
120
+ }
121
+ };
122
+ return {
123
+ processPath,
124
+ shellPath,
125
+ shellArgs: [...shellArgs],
126
+ findOnProcessPath: (binary) => {
127
+ if (!SAFE_BINARY_NAME.test(binary))
128
+ return null;
129
+ for (const directory of processPath.split(delimiter).filter(Boolean)) {
130
+ const candidate = join(directory, binary);
131
+ if (isAbsolute(candidate) && isExecutableFile(candidate))
132
+ return candidate;
133
+ }
134
+ return null;
135
+ },
136
+ findInLoginShell: (binary) => {
137
+ if (!SAFE_BINARY_NAME.test(binary))
138
+ return null;
139
+ const candidate = parseLoginShellResult(run(shellPath, [...shellArgs, '-c', loginShellCommand(binary)]), binary);
140
+ return candidate && isExecutableFile(candidate) ? candidate : null;
141
+ },
142
+ exists: isExecutableFile,
143
+ };
144
+ }
145
+ export function createCliExecutableResolver(options, host = createProductionCliResolverHost()) {
146
+ if (!SAFE_BINARY_NAME.test(options.binary)) {
147
+ throw new Error(`Unsafe CLI binary name: ${options.binary}`);
148
+ }
149
+ const now = options.now ?? Date.now;
150
+ /** Successful resolution, cached for the process lifetime. */
151
+ let cached = null;
152
+ /** Consecutive full-chain misses (drives the retry backoff). */
153
+ let failures = 0;
154
+ /** Timestamp of the most recent miss. */
155
+ let lastFailureAt = 0;
156
+ const accept = (path, source) => {
157
+ if (!path || !isAbsolute(path) || !host.exists(path))
158
+ return null;
159
+ const validation = options.validateCandidate?.(path) ?? { accepted: true };
160
+ if (!validation.accepted)
161
+ return null;
162
+ return {
163
+ binaryPath: path,
164
+ directory: dirname(path),
165
+ source,
166
+ metadata: validation.metadata,
167
+ };
168
+ };
169
+ return {
170
+ resolve() {
171
+ if (cached)
172
+ return cached;
173
+ // Negative cache: a miss is remembered and the chain — whose login-shell
174
+ // tail is a synchronous 5s-bounded spawn — is not re-run until the
175
+ // backoff elapses. Without this, every status poll and Run click against
176
+ // a missing CLI froze the event loop for the full probe, forever.
177
+ if (failures > 0 && now() - lastFailureAt < cliResolveRetryDelayMs(failures))
178
+ return null;
179
+ cached = accept(host.findOnProcessPath(options.binary), 'process-path');
180
+ if (!cached) {
181
+ for (const dir of options.searchDirs) {
182
+ cached = accept(join(dir, options.binary), 'common-directory');
183
+ if (cached)
184
+ break;
185
+ }
186
+ }
187
+ if (!cached) {
188
+ cached = accept(host.findInLoginShell(options.binary), 'login-shell');
189
+ }
190
+ if (cached) {
191
+ failures = 0;
192
+ lastFailureAt = 0;
193
+ return cached;
194
+ }
195
+ failures += 1;
196
+ lastFailureAt = now();
197
+ return null;
198
+ },
199
+ diagnostics: () => ({
200
+ binary: options.binary,
201
+ processPath: host.processPath,
202
+ shellPath: host.shellPath,
203
+ shellArgs: [...host.shellArgs],
204
+ searchDirs: [...options.searchDirs],
205
+ }),
206
+ };
207
+ }
208
+ function sanitizeDiagnosticField(value, emptyMarker) {
209
+ const flattened = Array.from(value, (character) => {
210
+ const codePoint = character.codePointAt(0) ?? 0;
211
+ const isControl = codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f);
212
+ return isControl || codePoint === 0x2028 || codePoint === 0x2029 ? ' ' : character;
213
+ })
214
+ .join('')
215
+ .replace(/ +/g, ' ')
216
+ .trim();
217
+ if (!flattened)
218
+ return emptyMarker;
219
+ if (flattened.length <= DIAGNOSTIC_FIELD_MAX_LENGTH)
220
+ return flattened;
221
+ return `${flattened.slice(0, DIAGNOSTIC_FIELD_MAX_LENGTH - 1)}…`;
222
+ }
223
+ export function formatCliNotFoundMessage(base, diagnostics) {
224
+ const processPath = sanitizeDiagnosticField(diagnostics.processPath, '(empty)');
225
+ const shell = sanitizeDiagnosticField([diagnostics.shellPath, ...diagnostics.shellArgs].filter(Boolean).join(' '), '(none)');
226
+ const dirs = sanitizeDiagnosticField(diagnostics.searchDirs.join(', '), '(none)');
227
+ return `${base}\nServer PATH: ${processPath}\nLogin shell: ${shell}\nChecked directories: ${dirs}`;
228
+ }
229
+ //# sourceMappingURL=cli-executable-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-executable-resolver.js","sourceRoot":"","sources":["../../src/utils/cli-executable-resolver.ts"],"names":[],"mappings":"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"}
@@ -18,4 +18,5 @@ export declare function resolveCodexDir(): string | null;
18
18
  * Check if Codex CLI is available on the system.
19
19
  */
20
20
  export declare function isCodexAvailable(): boolean;
21
+ export declare function getCodexNotFoundMessage(): string;
21
22
  //# sourceMappingURL=codex-cli-resolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex-cli-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/codex-cli-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAqBH;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CA0B/C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
1
+ {"version":3,"file":"codex-cli-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/codex-cli-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmBH;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAE/C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD"}
@@ -6,11 +6,9 @@
6
6
  *
7
7
  * @module utils/codex-cli-resolver
8
8
  */
9
- import { execSync } from 'node:child_process';
10
- import { existsSync } from 'node:fs';
11
- import { dirname, join } from 'node:path';
9
+ import { join } from 'node:path';
12
10
  import { homedir } from 'node:os';
13
- import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js';
11
+ import { createCliExecutableResolver, formatCliNotFoundMessage } from './cli-executable-resolver.js';
14
12
  /** Common directories where the Codex CLI binary may be installed */
15
13
  const CODEX_SEARCH_DIRS = [
16
14
  join(homedir(), '.codex', 'bin'), // Default install location
@@ -20,8 +18,8 @@ const CODEX_SEARCH_DIRS = [
20
18
  join(homedir(), '.npm-global', 'bin'), // npm global
21
19
  join(homedir(), 'bin'), // User bin
22
20
  ];
23
- /** Cached directory containing the codex binary (empty string = searched but not found) */
24
- let _codexDir = null;
21
+ const codexResolver = createCliExecutableResolver({ binary: 'codex', searchDirs: CODEX_SEARCH_DIRS });
22
+ const CODEX_NOT_FOUND = 'Codex CLI not found. Install with: npm install -g @openai/codex';
25
23
  /**
26
24
  * Finds the directory containing the `codex` binary.
27
25
  * Checks `which codex` first, then falls back to common install locations.
@@ -30,30 +28,7 @@ let _codexDir = null;
30
28
  * @returns Directory path, or null if not found
31
29
  */
32
30
  export function resolveCodexDir() {
33
- if (_codexDir !== null)
34
- return _codexDir || null;
35
- // Try `which` first (respects current PATH)
36
- try {
37
- const result = execSync('which codex', {
38
- encoding: 'utf-8',
39
- timeout: EXEC_TIMEOUT_MS,
40
- }).trim();
41
- if (result && existsSync(result)) {
42
- _codexDir = dirname(result);
43
- return _codexDir;
44
- }
45
- }
46
- catch {
47
- // Codex not in PATH, will check common locations
48
- }
49
- for (const dir of CODEX_SEARCH_DIRS) {
50
- if (existsSync(join(dir, 'codex'))) {
51
- _codexDir = dir;
52
- return _codexDir;
53
- }
54
- }
55
- _codexDir = ''; // mark as searched, not found
56
- return null;
31
+ return codexResolver.resolve()?.directory ?? null;
57
32
  }
58
33
  /**
59
34
  * Check if Codex CLI is available on the system.
@@ -61,4 +36,7 @@ export function resolveCodexDir() {
61
36
  export function isCodexAvailable() {
62
37
  return resolveCodexDir() !== null;
63
38
  }
39
+ export function getCodexNotFoundMessage() {
40
+ return formatCliNotFoundMessage(CODEX_NOT_FOUND, codexResolver.diagnostics());
41
+ }
64
42
  //# sourceMappingURL=codex-cli-resolver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex-cli-resolver.js","sourceRoot":"","sources":["../../src/utils/codex-cli-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,qEAAqE;AACrE,MAAM,iBAAiB,GAAG;IACxB,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,2BAA2B;IAC7D,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,+BAA+B;IACjE,gBAAgB,EAAE,oBAAoB;IACtC,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa;IAC7C,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,aAAa;IACpD,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,EAAE,WAAW;CACpC,CAAC;AAEF,2FAA2F;AAC3F,IAAI,SAAS,GAAkB,IAAI,CAAC;AAEpC;;;;;;GAMG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,SAAS,IAAI,IAAI,CAAC;IAEjD,4CAA4C;IAC5C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,EAAE;YACrC,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,eAAe;SACzB,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,MAAM,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACjC,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;YAC5B,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iDAAiD;IACnD,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACpC,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;YACnC,SAAS,GAAG,GAAG,CAAC;YAChB,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,SAAS,GAAG,EAAE,CAAC,CAAC,8BAA8B;IAC9C,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,eAAe,EAAE,KAAK,IAAI,CAAC;AACpC,CAAC"}
1
+ {"version":3,"file":"codex-cli-resolver.js","sourceRoot":"","sources":["../../src/utils/codex-cli-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAErG,qEAAqE;AACrE,MAAM,iBAAiB,GAAG;IACxB,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,2BAA2B;IAC7D,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,+BAA+B;IACjE,gBAAgB,EAAE,oBAAoB;IACtC,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa;IAC7C,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,aAAa;IACpD,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,EAAE,WAAW;CACpC,CAAC;AAEF,MAAM,aAAa,GAAG,2BAA2B,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC,CAAC;AACtG,MAAM,eAAe,GAAG,iEAAiE,CAAC;AAE1F;;;;;;GAMG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,aAAa,CAAC,OAAO,EAAE,EAAE,SAAS,IAAI,IAAI,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,eAAe,EAAE,KAAK,IAAI,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,uBAAuB;IACrC,OAAO,wBAAwB,CAAC,eAAe,EAAE,aAAa,CAAC,WAAW,EAAE,CAAC,CAAC;AAChF,CAAC"}
@@ -17,4 +17,5 @@ export declare function resolveGeminiDir(): string | null;
17
17
  * Check if Gemini CLI is available on the system.
18
18
  */
19
19
  export declare function isGeminiAvailable(): boolean;
20
+ export declare function getGeminiNotFoundMessage(): string;
20
21
  //# sourceMappingURL=gemini-cli-resolver.d.ts.map