@ai-devkit/agent-manager 0.37.0 → 0.39.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 (357) hide show
  1. package/dist/AgentManager.d.ts +0 -4
  2. package/dist/AgentManager.d.ts.map +1 -1
  3. package/dist/AgentManager.js +2 -2
  4. package/dist/AgentManager.js.map +1 -1
  5. package/dist/__tests__/capacity/anthropic.test.js +115 -0
  6. package/dist/__tests__/capacity/anthropic.test.js.map +1 -0
  7. package/dist/__tests__/capacity/boundaries.test.js +45 -0
  8. package/dist/__tests__/capacity/boundaries.test.js.map +1 -0
  9. package/dist/__tests__/capacity/claude.test.js +3 -3
  10. package/dist/__tests__/capacity/claude.test.js.map +1 -1
  11. package/dist/__tests__/capacity/codex.test.js +3 -1
  12. package/dist/__tests__/capacity/codex.test.js.map +1 -1
  13. package/dist/__tests__/capacity/openai.test.js +21 -12
  14. package/dist/__tests__/capacity/openai.test.js.map +1 -1
  15. package/dist/__tests__/capacity/wham.test.js +1 -1
  16. package/dist/__tests__/capacity/wham.test.js.map +1 -1
  17. package/dist/__tests__/capacity/zai.test.js +7 -8
  18. package/dist/__tests__/capacity/zai.test.js.map +1 -1
  19. package/dist/__tests__/harnesses/claude/durable/ClaudePrintAgent.integration.test.js +2 -1
  20. package/dist/__tests__/harnesses/claude/durable/ClaudePrintAgent.integration.test.js.map +1 -1
  21. package/dist/__tests__/harnesses/claude/durable/ClaudePrintRunner.test.js.map +1 -1
  22. package/dist/__tests__/harnesses/codex/durable/CodexCliProbe.test.js +7 -15
  23. package/dist/__tests__/harnesses/codex/durable/CodexCliProbe.test.js.map +1 -1
  24. package/dist/__tests__/harnesses/codex/durable/CodexPrintAgent.integration.test.js +3 -1
  25. package/dist/__tests__/harnesses/codex/durable/CodexPrintAgent.integration.test.js.map +1 -1
  26. package/dist/__tests__/harnesses/codex/durable/CodexPrintAgentService.test.js +11 -26
  27. package/dist/__tests__/harnesses/codex/durable/CodexPrintAgentService.test.js.map +1 -1
  28. package/dist/__tests__/harnesses/codex/durable/CodexPrintRunner.test.js +3 -7
  29. package/dist/__tests__/harnesses/codex/durable/CodexPrintRunner.test.js.map +1 -1
  30. package/dist/__tests__/harnesses/devin/DevinAdapter.test.js +597 -0
  31. package/dist/__tests__/harnesses/devin/DevinAdapter.test.js.map +1 -0
  32. package/dist/__tests__/harnesses/devin/DevinAgentMapper.test.js +91 -0
  33. package/dist/__tests__/harnesses/devin/DevinAgentMapper.test.js.map +1 -0
  34. package/dist/__tests__/harnesses/devin/DevinSessionLocator.test.js +192 -0
  35. package/dist/__tests__/harnesses/devin/DevinSessionLocator.test.js.map +1 -0
  36. package/dist/__tests__/harnesses/devin/DevinSessionParser.test.js +407 -0
  37. package/dist/__tests__/harnesses/devin/DevinSessionParser.test.js.map +1 -0
  38. package/dist/__tests__/harnesses/devin/fixtures.js +95 -0
  39. package/dist/__tests__/harnesses/devin/fixtures.js.map +1 -0
  40. package/dist/__tests__/harnesses/pi/durable/PiCliProbe.test.js +5 -11
  41. package/dist/__tests__/harnesses/pi/durable/PiCliProbe.test.js.map +1 -1
  42. package/dist/__tests__/harnesses/pi/durable/PiPrintAgentService.test.js +9 -19
  43. package/dist/__tests__/harnesses/pi/durable/PiPrintAgentService.test.js.map +1 -1
  44. package/dist/__tests__/harnesses/pi/durable/PiPrintRunner.test.js +3 -7
  45. package/dist/__tests__/harnesses/pi/durable/PiPrintRunner.test.js.map +1 -1
  46. package/dist/__tests__/{readiness → harnesses/readiness}/AgentReadiness.test.js +95 -11
  47. package/dist/__tests__/harnesses/readiness/AgentReadiness.test.js.map +1 -0
  48. package/dist/__tests__/harnesses/runtimeProfiles.test.js +89 -0
  49. package/dist/__tests__/harnesses/runtimeProfiles.test.js.map +1 -0
  50. package/dist/__tests__/print/DurableAgent.test.js +4 -6
  51. package/dist/__tests__/print/DurableAgent.test.js.map +1 -1
  52. package/dist/__tests__/print/DurableAgentRepository.test.js +1 -12
  53. package/dist/__tests__/print/DurableAgentRepository.test.js.map +1 -1
  54. package/dist/__tests__/runtime/ManagedAgentRuntime.test.js.map +1 -1
  55. package/dist/__tests__/terminal/GhosttyBackend.test.js +334 -0
  56. package/dist/__tests__/terminal/GhosttyBackend.test.js.map +1 -0
  57. package/dist/__tests__/terminal/TmuxManager.test.js +1 -1
  58. package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -1
  59. package/dist/adapters/AgentAdapter.d.ts +1 -1
  60. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  61. package/dist/adapters/AgentAdapter.js +2 -1
  62. package/dist/adapters/AgentAdapter.js.map +1 -1
  63. package/dist/capacity/index.d.ts +9 -19
  64. package/dist/capacity/index.d.ts.map +1 -1
  65. package/dist/capacity/index.js +11 -4
  66. package/dist/capacity/index.js.map +1 -1
  67. package/dist/capacity/providers/anthropic.d.ts +9 -0
  68. package/dist/capacity/providers/anthropic.d.ts.map +1 -0
  69. package/dist/capacity/{claude.js → providers/anthropic.js} +10 -128
  70. package/dist/capacity/providers/anthropic.js.map +1 -0
  71. package/dist/capacity/providers/openai-platform.d.ts +14 -0
  72. package/dist/capacity/providers/openai-platform.d.ts.map +1 -0
  73. package/dist/capacity/providers/openai-platform.js +120 -0
  74. package/dist/capacity/providers/openai-platform.js.map +1 -0
  75. package/dist/capacity/providers/openai-subscription.d.ts +20 -0
  76. package/dist/capacity/providers/openai-subscription.d.ts.map +1 -0
  77. package/dist/capacity/providers/openai-subscription.js +129 -0
  78. package/dist/capacity/providers/openai-subscription.js.map +1 -0
  79. package/dist/capacity/providers/zai.d.ts +8 -0
  80. package/dist/capacity/providers/zai.d.ts.map +1 -0
  81. package/dist/capacity/{zai.js → providers/zai.js} +17 -62
  82. package/dist/capacity/providers/zai.js.map +1 -0
  83. package/dist/capacity/sources/claude.d.ts +9 -0
  84. package/dist/capacity/sources/claude.d.ts.map +1 -0
  85. package/dist/capacity/sources/claude.js +17 -0
  86. package/dist/capacity/sources/claude.js.map +1 -0
  87. package/dist/capacity/{codex.d.ts → sources/codex.d.ts} +5 -9
  88. package/dist/capacity/sources/codex.d.ts.map +1 -0
  89. package/dist/capacity/{codex.js → sources/codex.js} +31 -84
  90. package/dist/capacity/sources/codex.js.map +1 -0
  91. package/dist/capacity/sources/pi.d.ts +30 -0
  92. package/dist/capacity/sources/pi.d.ts.map +1 -0
  93. package/dist/capacity/sources/pi.js +81 -0
  94. package/dist/capacity/sources/pi.js.map +1 -0
  95. package/dist/capacity/types.d.ts +2 -0
  96. package/dist/capacity/types.d.ts.map +1 -1
  97. package/dist/capacity/types.js +1 -1
  98. package/dist/capacity/types.js.map +1 -1
  99. package/dist/harnesses/claude/credentials.d.ts +10 -0
  100. package/dist/harnesses/claude/credentials.d.ts.map +1 -0
  101. package/dist/harnesses/claude/credentials.js +112 -0
  102. package/dist/harnesses/claude/credentials.js.map +1 -0
  103. package/dist/harnesses/claude/readiness.d.ts +3 -0
  104. package/dist/harnesses/claude/readiness.d.ts.map +1 -0
  105. package/dist/harnesses/claude/readiness.js +63 -0
  106. package/dist/harnesses/claude/readiness.js.map +1 -0
  107. package/dist/harnesses/codex/credentials.d.ts +15 -0
  108. package/dist/harnesses/codex/credentials.d.ts.map +1 -0
  109. package/dist/harnesses/codex/credentials.js +58 -0
  110. package/dist/harnesses/codex/credentials.js.map +1 -0
  111. package/dist/harnesses/codex/readiness.d.ts +3 -0
  112. package/dist/harnesses/codex/readiness.d.ts.map +1 -0
  113. package/dist/harnesses/codex/readiness.js +60 -0
  114. package/dist/harnesses/codex/readiness.js.map +1 -0
  115. package/dist/harnesses/copilot/readiness.d.ts +3 -0
  116. package/dist/harnesses/copilot/readiness.d.ts.map +1 -0
  117. package/dist/harnesses/copilot/readiness.js +39 -0
  118. package/dist/harnesses/copilot/readiness.js.map +1 -0
  119. package/dist/harnesses/devin/DevinAdapter.d.ts +39 -0
  120. package/dist/harnesses/devin/DevinAdapter.d.ts.map +1 -0
  121. package/dist/harnesses/devin/DevinAdapter.js +177 -0
  122. package/dist/harnesses/devin/DevinAdapter.js.map +1 -0
  123. package/dist/harnesses/devin/DevinAgentMapper.d.ts +11 -0
  124. package/dist/harnesses/devin/DevinAgentMapper.d.ts.map +1 -0
  125. package/dist/harnesses/devin/DevinAgentMapper.js +35 -0
  126. package/dist/harnesses/devin/DevinAgentMapper.js.map +1 -0
  127. package/dist/harnesses/devin/DevinSessionLocator.d.ts +46 -0
  128. package/dist/harnesses/devin/DevinSessionLocator.d.ts.map +1 -0
  129. package/dist/harnesses/devin/DevinSessionLocator.js +176 -0
  130. package/dist/harnesses/devin/DevinSessionLocator.js.map +1 -0
  131. package/dist/harnesses/devin/DevinSessionParser.d.ts +29 -0
  132. package/dist/harnesses/devin/DevinSessionParser.d.ts.map +1 -0
  133. package/dist/harnesses/devin/DevinSessionParser.js +166 -0
  134. package/dist/harnesses/devin/DevinSessionParser.js.map +1 -0
  135. package/dist/harnesses/devin/readiness.d.ts +3 -0
  136. package/dist/harnesses/devin/readiness.d.ts.map +1 -0
  137. package/dist/harnesses/devin/readiness.js +39 -0
  138. package/dist/harnesses/devin/readiness.js.map +1 -0
  139. package/dist/harnesses/index.d.ts.map +1 -1
  140. package/dist/harnesses/index.js +3 -1
  141. package/dist/harnesses/index.js.map +1 -1
  142. package/dist/harnesses/opencode/readiness.d.ts +3 -0
  143. package/dist/harnesses/opencode/readiness.d.ts.map +1 -0
  144. package/dist/harnesses/opencode/readiness.js +50 -0
  145. package/dist/harnesses/opencode/readiness.js.map +1 -0
  146. package/dist/harnesses/pi/credentials.d.ts +22 -0
  147. package/dist/harnesses/pi/credentials.d.ts.map +1 -0
  148. package/dist/harnesses/pi/credentials.js +98 -0
  149. package/dist/harnesses/pi/credentials.js.map +1 -0
  150. package/dist/harnesses/pi/readiness.d.ts +3 -0
  151. package/dist/harnesses/pi/readiness.d.ts.map +1 -0
  152. package/dist/harnesses/pi/readiness.js +96 -0
  153. package/dist/harnesses/pi/readiness.js.map +1 -0
  154. package/dist/harnesses/readiness/AgentReadiness.d.ts +6 -0
  155. package/dist/harnesses/readiness/AgentReadiness.d.ts.map +1 -0
  156. package/dist/harnesses/readiness/AgentReadiness.js +70 -0
  157. package/dist/harnesses/readiness/AgentReadiness.js.map +1 -0
  158. package/dist/harnesses/readiness/checks.d.ts +31 -0
  159. package/dist/harnesses/readiness/checks.d.ts.map +1 -0
  160. package/dist/harnesses/readiness/checks.js +270 -0
  161. package/dist/harnesses/readiness/checks.js.map +1 -0
  162. package/dist/{readiness/AgentReadiness.d.ts → harnesses/readiness/types.d.ts} +19 -10
  163. package/dist/harnesses/readiness/types.d.ts.map +1 -0
  164. package/dist/harnesses/readiness/types.js +4 -0
  165. package/dist/harnesses/readiness/types.js.map +1 -0
  166. package/dist/harnesses/runtimeProfiles.d.ts +11 -0
  167. package/dist/harnesses/runtimeProfiles.d.ts.map +1 -0
  168. package/dist/harnesses/runtimeProfiles.js +100 -0
  169. package/dist/harnesses/runtimeProfiles.js.map +1 -0
  170. package/dist/index.d.ts +16 -42
  171. package/dist/index.d.ts.map +1 -1
  172. package/dist/index.js +9 -23
  173. package/dist/index.js.map +1 -1
  174. package/dist/runtime/ManagedAgentRuntime.d.ts +3 -3
  175. package/dist/runtime/ManagedAgentRuntime.d.ts.map +1 -1
  176. package/dist/runtime/ManagedAgentRuntime.js +6 -20
  177. package/dist/runtime/ManagedAgentRuntime.js.map +1 -1
  178. package/dist/runtime/herdr/HerdrAgentDiscovery.d.ts.map +1 -1
  179. package/dist/runtime/herdr/HerdrAgentDiscovery.js +2 -43
  180. package/dist/runtime/herdr/HerdrAgentDiscovery.js.map +1 -1
  181. package/dist/runtime/tmux/PidPolling.d.ts +1 -1
  182. package/dist/runtime/tmux/PidPolling.d.ts.map +1 -1
  183. package/dist/runtime/tmux/PidPolling.js.map +1 -1
  184. package/dist/runtime/tmux/TmuxAgentRuntime.d.ts +1 -1
  185. package/dist/runtime/tmux/TmuxAgentRuntime.d.ts.map +1 -1
  186. package/dist/runtime/tmux/TmuxAgentRuntime.js +1 -1
  187. package/dist/runtime/tmux/TmuxAgentRuntime.js.map +1 -1
  188. package/dist/runtime/tmux/TmuxManager.d.ts +12 -0
  189. package/dist/runtime/tmux/TmuxManager.d.ts.map +1 -0
  190. package/dist/{terminal → runtime/tmux}/TmuxManager.js +9 -28
  191. package/dist/runtime/tmux/TmuxManager.js.map +1 -0
  192. package/dist/runtime/types.d.ts +1 -1
  193. package/dist/runtime/types.d.ts.map +1 -1
  194. package/dist/runtime/types.js.map +1 -1
  195. package/dist/terminal/TerminalFocusManager.d.ts +4 -32
  196. package/dist/terminal/TerminalFocusManager.d.ts.map +1 -1
  197. package/dist/terminal/TerminalFocusManager.js +17 -267
  198. package/dist/terminal/TerminalFocusManager.js.map +1 -1
  199. package/dist/terminal/TtyWriter.d.ts +1 -44
  200. package/dist/terminal/TtyWriter.d.ts.map +1 -1
  201. package/dist/terminal/TtyWriter.js +12 -347
  202. package/dist/terminal/TtyWriter.js.map +1 -1
  203. package/dist/terminal/backends/ghostty.d.ts +3 -0
  204. package/dist/terminal/backends/ghostty.d.ts.map +1 -0
  205. package/dist/terminal/backends/ghostty.js +161 -0
  206. package/dist/terminal/backends/ghostty.js.map +1 -0
  207. package/dist/terminal/backends/index.d.ts +5 -0
  208. package/dist/terminal/backends/index.d.ts.map +1 -0
  209. package/dist/terminal/backends/index.js +21 -0
  210. package/dist/terminal/backends/index.js.map +1 -0
  211. package/dist/terminal/backends/iterm2.d.ts +3 -0
  212. package/dist/terminal/backends/iterm2.d.ts.map +1 -0
  213. package/dist/terminal/backends/iterm2.js +135 -0
  214. package/dist/terminal/backends/iterm2.js.map +1 -0
  215. package/dist/terminal/backends/shared.d.ts +9 -0
  216. package/dist/terminal/backends/shared.d.ts.map +1 -0
  217. package/dist/terminal/backends/shared.js +38 -0
  218. package/dist/terminal/backends/shared.js.map +1 -0
  219. package/dist/terminal/backends/terminal-app.d.ts +3 -0
  220. package/dist/terminal/backends/terminal-app.d.ts.map +1 -0
  221. package/dist/terminal/backends/terminal-app.js +129 -0
  222. package/dist/terminal/backends/terminal-app.js.map +1 -0
  223. package/dist/terminal/backends/tmux.d.ts +3 -0
  224. package/dist/terminal/backends/tmux.d.ts.map +1 -0
  225. package/dist/terminal/backends/tmux.js +76 -0
  226. package/dist/terminal/backends/tmux.js.map +1 -0
  227. package/dist/terminal/backends/wezterm.d.ts +3 -0
  228. package/dist/terminal/backends/wezterm.d.ts.map +1 -0
  229. package/dist/terminal/backends/wezterm.js +71 -0
  230. package/dist/terminal/backends/wezterm.js.map +1 -0
  231. package/dist/terminal/types.d.ts +22 -0
  232. package/dist/terminal/types.d.ts.map +1 -0
  233. package/dist/terminal/types.js +11 -0
  234. package/dist/terminal/types.js.map +1 -0
  235. package/dist/utils/process.d.ts +2 -0
  236. package/dist/utils/process.d.ts.map +1 -1
  237. package/dist/utils/process.js +3 -0
  238. package/dist/utils/process.js.map +1 -1
  239. package/package.json +1 -1
  240. package/src/AgentManager.ts +1 -1
  241. package/src/__tests__/capacity/anthropic.test.ts +105 -0
  242. package/src/__tests__/capacity/boundaries.test.ts +36 -0
  243. package/src/__tests__/capacity/claude.test.ts +3 -8
  244. package/src/__tests__/capacity/codex.test.ts +12 -35
  245. package/src/__tests__/capacity/fixtures/openai-codex-auth.json +1 -1
  246. package/src/__tests__/capacity/openai.test.ts +15 -15
  247. package/src/__tests__/capacity/wham.test.ts +1 -1
  248. package/src/__tests__/capacity/zai.test.ts +27 -62
  249. package/src/__tests__/harnesses/claude/durable/ClaudePrintAgent.integration.test.ts +2 -2
  250. package/src/__tests__/harnesses/claude/durable/ClaudePrintRunner.test.ts +1 -1
  251. package/src/__tests__/harnesses/codex/durable/CodexCliProbe.test.ts +14 -19
  252. package/src/__tests__/harnesses/codex/durable/CodexPrintAgent.integration.test.ts +3 -3
  253. package/src/__tests__/harnesses/codex/durable/CodexPrintAgentService.test.ts +24 -30
  254. package/src/__tests__/harnesses/codex/durable/CodexPrintRunner.test.ts +4 -8
  255. package/src/__tests__/harnesses/devin/DevinAdapter.test.ts +292 -0
  256. package/src/__tests__/harnesses/devin/DevinAgentMapper.test.ts +88 -0
  257. package/src/__tests__/harnesses/devin/DevinSessionLocator.test.ts +139 -0
  258. package/src/__tests__/harnesses/devin/DevinSessionParser.test.ts +218 -0
  259. package/src/__tests__/harnesses/devin/fixtures.ts +144 -0
  260. package/src/__tests__/harnesses/pi/durable/PiCliProbe.test.ts +7 -11
  261. package/src/__tests__/harnesses/pi/durable/PiPrintAgentService.test.ts +19 -22
  262. package/src/__tests__/harnesses/pi/durable/PiPrintRunner.test.ts +7 -8
  263. package/src/__tests__/{readiness → harnesses/readiness}/AgentReadiness.test.ts +77 -9
  264. package/src/__tests__/harnesses/runtimeProfiles.test.ts +110 -0
  265. package/src/__tests__/print/DurableAgent.test.ts +4 -13
  266. package/src/__tests__/print/DurableAgentRepository.test.ts +1 -13
  267. package/src/__tests__/runtime/ManagedAgentRuntime.test.ts +1 -1
  268. package/src/__tests__/terminal/GhosttyBackend.test.ts +271 -0
  269. package/src/__tests__/terminal/TmuxManager.test.ts +1 -1
  270. package/src/adapters/AgentAdapter.ts +1 -0
  271. package/src/capacity/index.ts +29 -23
  272. package/src/capacity/{claude.ts → providers/anthropic.ts} +16 -131
  273. package/src/capacity/providers/openai-platform.ts +150 -0
  274. package/src/capacity/{wham.ts → providers/openai-subscription.ts} +86 -8
  275. package/src/capacity/{zai.ts → providers/zai.ts} +29 -111
  276. package/src/capacity/sources/claude.ts +30 -0
  277. package/src/capacity/{codex.ts → sources/codex.ts} +54 -124
  278. package/src/capacity/sources/pi.ts +131 -0
  279. package/src/capacity/types.ts +3 -0
  280. package/src/harnesses/claude/credentials.ts +122 -0
  281. package/src/harnesses/claude/readiness.ts +69 -0
  282. package/src/harnesses/codex/credentials.ts +81 -0
  283. package/src/harnesses/codex/readiness.ts +72 -0
  284. package/src/harnesses/copilot/readiness.ts +33 -0
  285. package/src/harnesses/devin/DevinAdapter.ts +224 -0
  286. package/src/harnesses/devin/DevinAgentMapper.ts +43 -0
  287. package/src/harnesses/devin/DevinSessionLocator.ts +230 -0
  288. package/src/harnesses/devin/DevinSessionParser.ts +236 -0
  289. package/src/harnesses/devin/readiness.ts +36 -0
  290. package/src/harnesses/index.ts +2 -0
  291. package/src/harnesses/opencode/readiness.ts +48 -0
  292. package/src/harnesses/pi/credentials.ts +129 -0
  293. package/src/harnesses/pi/readiness.ts +104 -0
  294. package/src/harnesses/readiness/AgentReadiness.ts +89 -0
  295. package/src/harnesses/readiness/checks.ts +336 -0
  296. package/src/harnesses/readiness/types.ts +90 -0
  297. package/src/{utils/agents.ts → harnesses/runtimeProfiles.ts} +35 -38
  298. package/src/index.ts +15 -128
  299. package/src/runtime/ManagedAgentRuntime.ts +7 -22
  300. package/src/runtime/herdr/HerdrAgentDiscovery.ts +3 -19
  301. package/src/runtime/tmux/PidPolling.ts +1 -1
  302. package/src/runtime/tmux/TmuxAgentRuntime.ts +1 -1
  303. package/src/{terminal → runtime/tmux}/TmuxManager.ts +9 -30
  304. package/src/runtime/types.ts +1 -1
  305. package/src/terminal/TerminalFocusManager.ts +19 -292
  306. package/src/terminal/TtyWriter.ts +15 -335
  307. package/src/terminal/backends/ghostty.ts +180 -0
  308. package/src/terminal/backends/index.ts +23 -0
  309. package/src/terminal/backends/iterm2.ts +126 -0
  310. package/src/terminal/backends/shared.ts +47 -0
  311. package/src/terminal/backends/terminal-app.ts +122 -0
  312. package/src/terminal/backends/tmux.ts +62 -0
  313. package/src/terminal/backends/wezterm.ts +65 -0
  314. package/src/terminal/types.ts +23 -0
  315. package/src/utils/process.ts +5 -0
  316. package/dist/__tests__/readiness/AgentReadiness.test.js.map +0 -1
  317. package/dist/__tests__/utils/agents.test.js +0 -93
  318. package/dist/__tests__/utils/agents.test.js.map +0 -1
  319. package/dist/capacity/claude.d.ts +0 -17
  320. package/dist/capacity/claude.d.ts.map +0 -1
  321. package/dist/capacity/claude.js.map +0 -1
  322. package/dist/capacity/codex.d.ts.map +0 -1
  323. package/dist/capacity/codex.js.map +0 -1
  324. package/dist/capacity/openai.d.ts +0 -47
  325. package/dist/capacity/openai.d.ts.map +0 -1
  326. package/dist/capacity/openai.js +0 -307
  327. package/dist/capacity/openai.js.map +0 -1
  328. package/dist/capacity/wham.d.ts +0 -16
  329. package/dist/capacity/wham.d.ts.map +0 -1
  330. package/dist/capacity/wham.js +0 -66
  331. package/dist/capacity/wham.js.map +0 -1
  332. package/dist/capacity/zai.d.ts +0 -14
  333. package/dist/capacity/zai.d.ts.map +0 -1
  334. package/dist/capacity/zai.js.map +0 -1
  335. package/dist/readiness/AgentReadiness.d.ts.map +0 -1
  336. package/dist/readiness/AgentReadiness.js +0 -614
  337. package/dist/readiness/AgentReadiness.js.map +0 -1
  338. package/dist/terminal/TmuxManager.d.ts +0 -28
  339. package/dist/terminal/TmuxManager.d.ts.map +0 -1
  340. package/dist/terminal/TmuxManager.js.map +0 -1
  341. package/dist/terminal/index.d.ts +0 -5
  342. package/dist/terminal/index.d.ts.map +0 -1
  343. package/dist/terminal/index.js +0 -5
  344. package/dist/terminal/index.js.map +0 -1
  345. package/dist/utils/agents.d.ts +0 -16
  346. package/dist/utils/agents.d.ts.map +0 -1
  347. package/dist/utils/agents.js +0 -117
  348. package/dist/utils/agents.js.map +0 -1
  349. package/dist/utils/index.d.ts +0 -8
  350. package/dist/utils/index.d.ts.map +0 -1
  351. package/dist/utils/index.js +0 -6
  352. package/dist/utils/index.js.map +0 -1
  353. package/src/__tests__/utils/agents.test.ts +0 -105
  354. package/src/capacity/openai.ts +0 -355
  355. package/src/readiness/AgentReadiness.ts +0 -730
  356. package/src/terminal/index.ts +0 -4
  357. package/src/utils/index.ts +0 -14
@@ -1,346 +1,26 @@
1
- import { execFile } from "child_process";
2
- import { promisify } from "util";
3
- import type { TerminalLocation } from "./TerminalFocusManager.js";
4
- import { TerminalType } from "./TerminalFocusManager.js";
5
- import { escapeAppleScript } from "../utils/applescript.js";
1
+ import { TERMINAL_BACKENDS, terminalBackendFor } from "./backends/index.js";
2
+ import type { TerminalLocation } from "./types.js";
6
3
 
7
- const execFileAsync = promisify(execFile);
8
-
9
- /**
10
- * Carriage return byte (0x0d). Sent as a fixed discrete argv element with
11
- * `--no-paste` to deliver Enter literally (shell equivalent: $'\x0d').
12
- */
13
- const CARRIAGE_RETURN = "\x0d";
14
-
15
- /**
16
- * Escape byte (0x1b). Recognized by `sendKey` and translated to the
17
- * backend-native representation (`Escape` for tmux, `key code 53` for
18
- * AppleScript, the literal byte for WezTerm).
19
- */
20
- const ESCAPE_BYTE = "\x1b";
4
+ const SUPPORTED = `Supported: ${TERMINAL_BACKENDS.map((backend) => backend.label).join(", ")}.`;
21
5
 
22
6
  export class TtyWriter {
23
- /**
24
- * Send a message as keyboard input to a terminal session.
25
- *
26
- * Dispatches to the correct mechanism based on terminal type:
27
- * - tmux: `tmux send-keys`
28
- * - iTerm2: Two separate AppleScript `write text` calls (text then newline)
29
- * - Terminal.app: Two separate AppleScript `do script` calls (text then newline)
30
- *
31
- * All AppleScript is executed via `execFile('osascript', ['-e', script])`
32
- * to avoid shell interpolation and command injection.
33
- *
34
- * @param location Terminal location from TerminalFocusManager.findTerminal()
35
- * @param message Text to send
36
- * @throws Error if terminal type is unsupported or send fails
37
- */
38
7
  static async send(location: TerminalLocation, message: string): Promise<void> {
39
- switch (location.type) {
40
- case TerminalType.TMUX:
41
- return TtyWriter.sendViaTmux(location.identifier, message);
42
- case TerminalType.WEZTERM:
43
- return TtyWriter.sendViaWezterm(location.identifier, message);
44
- case TerminalType.ITERM2:
45
- return TtyWriter.sendViaITerm2(location.tty, message);
46
- case TerminalType.TERMINAL_APP:
47
- return TtyWriter.sendViaTerminalApp(location.tty, message);
48
- default:
49
- throw new Error(
50
- `Cannot send input: unsupported terminal type "${location.type}". ` +
51
- "Supported: tmux, WezTerm, iTerm2, Terminal.app.",
52
- );
8
+ const backend = terminalBackendFor(location.type);
9
+ if (!backend) {
10
+ throw new Error(
11
+ `Cannot send input: unsupported terminal type "${location.type}". ${SUPPORTED}`,
12
+ );
53
13
  }
14
+ await backend.send(location, message);
54
15
  }
55
16
 
56
- /**
57
- * Send a single raw key (e.g. "1", "Enter", "Up") to the terminal as a
58
- * keystroke — bypassing bracketed paste and without auto-appending Enter.
59
- *
60
- * Use this when the target TUI distinguishes between typed text and raw
61
- * keypresses (e.g. an `AskUserQuestion` picker that selects on digit-key
62
- * press, not on a pasted digit followed by Enter).
63
- *
64
- * - tmux: `tmux send-keys -t <id> <key>` — direct keystroke, no paste buffer.
65
- * - WezTerm: `wezterm cli send-text --pane-id <id> --no-paste <key>`.
66
- * - iTerm2 / Terminal.app: AppleScript via System Events. Requires
67
- * Accessibility permissions.
68
- */
69
17
  static async sendKey(location: TerminalLocation, key: string): Promise<void> {
70
- switch (location.type) {
71
- case TerminalType.TMUX:
72
- return TtyWriter.sendKeyViaTmux(location.identifier, key);
73
- case TerminalType.WEZTERM:
74
- return TtyWriter.sendKeyViaWezterm(location.identifier, key);
75
- case TerminalType.ITERM2:
76
- return TtyWriter.sendKeyViaITerm2(location.tty, key);
77
- case TerminalType.TERMINAL_APP:
78
- return TtyWriter.sendKeyViaTerminalApp(location.tty, key);
79
- default:
80
- throw new Error(
81
- `Cannot send key: unsupported terminal type "${location.type}". ` +
82
- "Supported: tmux, WezTerm, iTerm2, Terminal.app.",
83
- );
84
- }
85
- }
86
-
87
- private static async sendKeyViaTmux(identifier: string, key: string): Promise<void> {
88
- // tmux send-keys interprets named keys (Enter, Up, Escape, ...) and
89
- // passes literals through. No bracketed paste, no auto-Enter.
90
- const arg = key === ESCAPE_BYTE ? "Escape" : key;
91
- await execFileAsync("tmux", ["send-keys", "-t", identifier, arg]);
92
- }
93
-
94
- private static async sendKeyViaWezterm(paneId: string, key: string): Promise<void> {
95
- // --no-paste delivers the key bytes literally outside bracketed-paste
96
- // markers; the TUI sees a raw keystroke. For Esc (`\x1b`), wezterm
97
- // accepts the byte directly.
98
- await execFileAsync("wezterm", ["cli", "send-text", "--pane-id", paneId, "--no-paste", key]);
99
- }
100
-
101
- private static async sendKeyViaITerm2(tty: string, key: string): Promise<void> {
102
- // Focus the target session, then press the key via System Events so the
103
- // inner TUI sees a raw keystroke (not a bracketed-paste text run).
104
- const action = appleScriptKeyAction(key);
105
- const script = `
106
- tell application "iTerm"
107
- set targetSession to missing value
108
- repeat with w in windows
109
- repeat with t in tabs of w
110
- repeat with s in sessions of t
111
- if tty of s is "${tty}" then
112
- set targetSession to s
113
- set frontmost of w to true
114
- tell t to select
115
- tell s to select
116
- exit repeat
117
- end if
118
- end repeat
119
- if targetSession is not missing value then exit repeat
120
- end repeat
121
- if targetSession is not missing value then exit repeat
122
- end repeat
123
- if targetSession is missing value then return "not_found"
124
- activate
125
- end tell
126
- tell application "System Events" to ${action}
127
- return "ok"`;
128
-
129
- const { stdout } = await execFileAsync("osascript", ["-e", script]);
130
- if (stdout.trim() !== "ok") {
131
- throw new Error(`iTerm2 session not found for TTY ${tty}`);
18
+ const backend = terminalBackendFor(location.type);
19
+ if (!backend) {
20
+ throw new Error(
21
+ `Cannot send key: unsupported terminal type "${location.type}". ${SUPPORTED}`,
22
+ );
132
23
  }
24
+ await backend.sendKey(location, key);
133
25
  }
134
-
135
- private static async sendKeyViaTerminalApp(tty: string, key: string): Promise<void> {
136
- const action = appleScriptKeyAction(key);
137
- const script = `
138
- tell application "Terminal"
139
- set targetTab to missing value
140
- set targetWindow to missing value
141
- repeat with w in windows
142
- repeat with i from 1 to count of tabs of w
143
- set t to tab i of w
144
- if tty of t is "${tty}" then
145
- set targetTab to t
146
- set targetWindow to w
147
- exit repeat
148
- end if
149
- end repeat
150
- if targetTab is not missing value then exit repeat
151
- end repeat
152
- if targetTab is missing value then return "not_found"
153
- set selected of targetTab to true
154
- set frontmost of targetWindow to true
155
- activate
156
- end tell
157
- tell application "System Events" to ${action}
158
- return "ok"`;
159
-
160
- const { stdout } = await execFileAsync("osascript", ["-e", script]);
161
- if (stdout.trim() !== "ok") {
162
- throw new Error(`Terminal.app tab not found for TTY ${tty}`);
163
- }
164
- }
165
-
166
- private static async sendViaWezterm(paneId: string, message: string): Promise<void> {
167
- // Two explicit CLI calls, mirroring the text-then-Enter convention used
168
- // by tmux / iTerm2 / Terminal.app so a bracketed-paste-aware TUI still
169
- // sees Enter as a submit.
170
- //
171
- // Step 1 (text): write the message to stdin so prompt contents are not
172
- // exposed through process arguments. execFile still spawns wezterm
173
- // directly (no shell), so shell metacharacters remain inert.
174
- // Step 2 (Enter): pass a fixed carriage return (0x0d) as a discrete
175
- // argv element (the JS char '\x0d') with --no-paste, so the CR is
176
- // delivered literally rather than wrapped in paste brackets. The
177
- // equivalent shell command is:
178
- // wezterm cli send-text --pane-id <id> --no-paste $'\x0d'
179
- // (ANSI-C quoting, note the leading $).
180
- await TtyWriter.execFileWithInput(
181
- "wezterm",
182
- ["cli", "send-text", "--pane-id", paneId],
183
- message,
184
- );
185
- await new Promise((resolve) => setTimeout(resolve, 150));
186
- await execFileAsync("wezterm", [
187
- "cli",
188
- "send-text",
189
- "--pane-id",
190
- paneId,
191
- "--no-paste",
192
- CARRIAGE_RETURN,
193
- ]);
194
- }
195
-
196
- private static async sendViaTmux(identifier: string, message: string): Promise<void> {
197
- // Paste the message body using tmux bracketed paste, then send Enter as
198
- // a separate key so the inner TUI treats it as submission rather than
199
- // pasted content.
200
- const bufferName = `ai-devkit-send-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
201
- await TtyWriter.execFileWithInput("tmux", ["load-buffer", "-b", bufferName, "-"], message);
202
- await execFileAsync("tmux", ["paste-buffer", "-t", identifier, "-b", bufferName, "-p", "-d"]);
203
- await new Promise((resolve) => setTimeout(resolve, 150));
204
- await execFileAsync("tmux", ["send-keys", "-t", identifier, "Enter"]);
205
- }
206
-
207
- private static async execFileWithInput(
208
- command: string,
209
- args: string[],
210
- input: string,
211
- ): Promise<void> {
212
- await new Promise<void>((resolve, reject) => {
213
- const child = execFile(command, args, (error) => {
214
- if (error) {
215
- reject(error);
216
- return;
217
- }
218
- resolve();
219
- });
220
- if (!child.stdin) {
221
- reject(new Error(`Cannot write stdin to ${command}`));
222
- return;
223
- }
224
- child.stdin.end(input);
225
- });
226
- }
227
-
228
- /**
229
- * Build an AppleScript that finds an iTerm2 session by TTY and runs a
230
- * command against it. The `sessionCommand` is inserted inside a
231
- * `tell targetSession` block.
232
- */
233
- private static iterm2SessionScript(tty: string, sessionCommand: string): string {
234
- return `
235
- tell application "iTerm"
236
- set targetSession to missing value
237
- repeat with w in windows
238
- repeat with t in tabs of w
239
- repeat with s in sessions of t
240
- if tty of s is "${tty}" then
241
- set targetSession to s
242
- exit repeat
243
- end if
244
- end repeat
245
- if targetSession is not missing value then exit repeat
246
- end repeat
247
- if targetSession is not missing value then exit repeat
248
- end repeat
249
- if targetSession is missing value then return "not_found"
250
- tell targetSession to ${sessionCommand}
251
- end tell
252
- return "ok"`;
253
- }
254
-
255
- private static async sendViaITerm2(tty: string, message: string): Promise<void> {
256
- const escaped = escapeAppleScript(message);
257
- // Send text and Enter as two separate write text calls so the newline
258
- // is delivered outside the bracketed paste sequence of the message body.
259
- // iTerm2 appends the newline after the paste-end marker (\e[201~), so
260
- // the inner TUI (Claude Code, Codex) sees it as a real submit action.
261
- const textScript = TtyWriter.iterm2SessionScript(tty, `write text "${escaped}" newline no`);
262
-
263
- const { stdout: textResult } = await execFileAsync("osascript", ["-e", textScript]);
264
- if (textResult.trim() !== "ok") {
265
- throw new Error(`iTerm2 session not found for TTY ${tty}`);
266
- }
267
-
268
- // Wait for the paste to complete before sending Enter separately
269
- await new Promise((resolve) => setTimeout(resolve, 150));
270
-
271
- const enterScript = TtyWriter.iterm2SessionScript(tty, 'write text "" newline yes');
272
- const { stdout: enterResult } = await execFileAsync("osascript", ["-e", enterScript]);
273
- if (enterResult.trim() !== "ok") {
274
- throw new Error(`iTerm2 session disappeared before Enter could be sent for TTY ${tty}`);
275
- }
276
- }
277
-
278
- private static async sendViaTerminalApp(tty: string, message: string): Promise<void> {
279
- const escaped = escapeAppleScript(message);
280
- // Use Terminal.app's `do script` to send text to the correct tab by TTY.
281
- // We avoid System Events `keystroke` + `key code 36` because it requires
282
- // accessibility permissions and unreliably delivers the Return key.
283
- //
284
- // `do script` with `in` targets a specific tab without opening a new one.
285
- // We send text and Enter as two separate calls so the newline arrives
286
- // outside of bracketed paste mode — same pattern as iTerm2 and tmux.
287
- const textScript = `
288
- tell application "Terminal"
289
- set targetTab to missing value
290
- repeat with w in windows
291
- repeat with i from 1 to count of tabs of w
292
- set t to tab i of w
293
- if tty of t is "${tty}" then
294
- set targetTab to t
295
- exit repeat
296
- end if
297
- end repeat
298
- if targetTab is not missing value then exit repeat
299
- end repeat
300
- if targetTab is missing value then return "not_found"
301
- do script "${escaped}" in targetTab
302
- end tell
303
- return "ok"`;
304
-
305
- const { stdout: textResult } = await execFileAsync("osascript", ["-e", textScript]);
306
- if (textResult.trim() !== "ok") {
307
- throw new Error(`Terminal.app tab not found for TTY ${tty}`);
308
- }
309
-
310
- // Wait for the text to be delivered before sending Enter
311
- await new Promise((resolve) => setTimeout(resolve, 150));
312
-
313
- const enterScript = `
314
- tell application "Terminal"
315
- set targetTab to missing value
316
- repeat with w in windows
317
- repeat with i from 1 to count of tabs of w
318
- set t to tab i of w
319
- if tty of t is "${tty}" then
320
- set targetTab to t
321
- exit repeat
322
- end if
323
- end repeat
324
- if targetTab is not missing value then exit repeat
325
- end repeat
326
- if targetTab is missing value then return "not_found"
327
- do script "" in targetTab
328
- end tell
329
- return "ok"`;
330
-
331
- const { stdout: enterResult } = await execFileAsync("osascript", ["-e", enterScript]);
332
- if (enterResult.trim() !== "ok") {
333
- throw new Error(`Terminal.app tab disappeared before Enter could be sent for TTY ${tty}`);
334
- }
335
- }
336
- }
337
-
338
- /**
339
- * AppleScript `keystroke` only delivers typeable characters; non-typeable
340
- * keys (Esc, arrows, F-keys, …) must be sent via `key code <N>`. Add more
341
- * mappings here as new special keys are needed.
342
- */
343
- function appleScriptKeyAction(key: string): string {
344
- if (key === ESCAPE_BYTE) return "key code 53";
345
- return `keystroke "${escapeAppleScript(key)}"`;
346
26
  }
@@ -0,0 +1,180 @@
1
+ import { execFile } from "child_process";
2
+ import { writeFile } from "fs/promises";
3
+ import { promisify } from "util";
4
+ import { escapeAppleScript } from "../../utils/applescript.js";
5
+ import { getProcessCwdsAsync } from "../../utils/process.js";
6
+ import { TerminalType, type TerminalBackend, type TerminalLocation } from "../types.js";
7
+ import { CARRIAGE_RETURN, isProcessName, sleep } from "./shared.js";
8
+
9
+ const execFileAsync = promisify(execFile);
10
+
11
+ /** Separates fields in AppleScript output; never part of an id, title or path. */
12
+ const FIELD_SEPARATOR = "\x1f";
13
+ const MARKER_POLL_ATTEMPTS = 10;
14
+ const MARKER_POLL_INTERVAL_MS = 50;
15
+
16
+ interface GhosttyTerminal {
17
+ id: string;
18
+ name: string;
19
+ workingDirectory: string;
20
+ }
21
+
22
+ interface ProcessRow {
23
+ pid: number;
24
+ ppid: number;
25
+ tty: string;
26
+ comm: string;
27
+ }
28
+
29
+ async function listProcesses(): Promise<ProcessRow[]> {
30
+ const { stdout } = await execFileAsync("ps", ["-Axo", "pid=,ppid=,tty=,comm="]);
31
+ const rows: ProcessRow[] = [];
32
+ for (const line of stdout.split("\n")) {
33
+ const match = line.trim().match(/^(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/);
34
+ if (match) {
35
+ rows.push({ pid: Number(match[1]), ppid: Number(match[2]), tty: match[3], comm: match[4] });
36
+ }
37
+ }
38
+ return rows;
39
+ }
40
+
41
+ async function listTerminals(): Promise<GhosttyTerminal[]> {
42
+ const script = `
43
+ tell application "Ghostty"
44
+ set out to ""
45
+ repeat with t in terminals
46
+ set out to out & (id of t) & (character id 31) & (name of t) & (character id 31) & (working directory of t) & linefeed
47
+ end repeat
48
+ return out
49
+ end tell`;
50
+ const { stdout } = await execFileAsync("osascript", ["-e", script]);
51
+ return stdout
52
+ .split("\n")
53
+ .map((line) => line.split(FIELD_SEPARATOR))
54
+ .filter((fields) => fields.length === 3 && fields[0])
55
+ .map(([id, name, workingDirectory]) => ({ id, name, workingDirectory }));
56
+ }
57
+
58
+ /** Run an AppleScript `command` with `t` bound to the located terminal; false if it is gone. */
59
+ async function onTerminal(location: TerminalLocation, command: string): Promise<boolean> {
60
+ const script = `
61
+ tell application "Ghostty"
62
+ set matches to (every terminal whose id is "${escapeAppleScript(location.identifier)}")
63
+ if (count of matches) is 0 then return "not_found"
64
+ set t to item 1 of matches
65
+ ${command}
66
+ end tell
67
+ return "ok"`;
68
+ const { stdout } = await execFileAsync("osascript", ["-e", script]);
69
+ return stdout.trim() === "ok";
70
+ }
71
+
72
+ /** Set the window title of whatever terminal owns `tty` (OSC 2). */
73
+ async function writeTitle(tty: string, title: string): Promise<void> {
74
+ // eslint-disable-next-line no-control-regex
75
+ const safe = title.replace(/[\x00-\x1f\x7f]/g, "");
76
+ await writeFile(tty, `\x1b]2;${safe}\x07`, { flag: "a" });
77
+ }
78
+
79
+ /**
80
+ * Ghostty's AppleScript terminals expose an id, title and working directory,
81
+ * but not their TTY. Find the one owning `tty` without guessing:
82
+ * 1. only TTYs whose session was started by a ghostty process qualify;
83
+ * 2. if exactly one terminal's working directory matches a process on this
84
+ * TTY, and no other Ghostty TTY has a process there, that is it;
85
+ * 3. otherwise briefly set a unique title on the TTY, find the terminal now
86
+ * showing it, and restore its previous title.
87
+ */
88
+ async function findTerminalId(tty: string): Promise<string | null> {
89
+ const processes = await listProcesses();
90
+ const ghosttyPids = new Set(
91
+ processes.filter((p) => isProcessName(p.comm, "ghostty")).map((p) => p.pid),
92
+ );
93
+ if (ghosttyPids.size === 0) return null;
94
+
95
+ const ttyName = tty.replace(/^\/dev\//, "");
96
+ const ghosttyTtys = new Set(
97
+ processes.filter((p) => ghosttyPids.has(p.ppid) && p.tty !== "??").map((p) => p.tty),
98
+ );
99
+ if (!ghosttyTtys.has(ttyName)) return null;
100
+
101
+ const terminals = await listTerminals();
102
+ const sessionProcesses = processes.filter((p) => ghosttyTtys.has(p.tty));
103
+ const cwdByPid = await getProcessCwdsAsync(sessionProcesses.map((p) => p.pid));
104
+ const ownDirs = new Set<string>();
105
+ const otherDirs = new Set<string>();
106
+ for (const p of sessionProcesses) {
107
+ const cwd = cwdByPid.get(p.pid);
108
+ if (cwd) (p.tty === ttyName ? ownDirs : otherDirs).add(cwd);
109
+ }
110
+
111
+ // Trust the directory only when exactly one terminal is in a folder no other Ghostty TTY uses.
112
+ const byDirectory = terminals.filter((t) => ownDirs.has(t.workingDirectory));
113
+ if (byDirectory.length === 1 && !otherDirs.has(byDirectory[0].workingDirectory)) {
114
+ return byDirectory[0].id;
115
+ }
116
+
117
+ const marker = `ai-devkit-${process.pid}-${Date.now()}`;
118
+ await writeTitle(tty, marker);
119
+ for (let attempt = 0; attempt < MARKER_POLL_ATTEMPTS; attempt++) {
120
+ const marked = (await listTerminals()).find((t) => t.name === marker);
121
+ if (marked) {
122
+ const previous = terminals.find((t) => t.id === marked.id)?.name ?? "";
123
+ await writeTitle(tty, previous);
124
+ return marked.id;
125
+ }
126
+ await sleep(MARKER_POLL_INTERVAL_MS);
127
+ }
128
+ return null;
129
+ }
130
+
131
+ /**
132
+ * A Ghostty `text:` action that writes `bytes` to the terminal as typed.
133
+ * Unlike `send key`, it does not go through the keyboard layout or input
134
+ * method, which can turn a digit key into another character.
135
+ */
136
+ function textAction(bytes: string): string {
137
+ const escaped = Array.from(bytes, (char) => {
138
+ if (char === "\\") return "\\\\";
139
+ const code = char.charCodeAt(0);
140
+ return code >= 0x20 && code < 0x7f ? char : `\\x${code.toString(16).padStart(2, "0")}`;
141
+ }).join("");
142
+ return `perform action "${escapeAppleScript(`text:${escaped}`)}" on t`;
143
+ }
144
+
145
+ export const ghosttyBackend: TerminalBackend = {
146
+ type: TerminalType.GHOSTTY,
147
+ label: "Ghostty",
148
+
149
+ async find(tty) {
150
+ try {
151
+ const id = await findTerminalId(tty);
152
+ return id ? { type: TerminalType.GHOSTTY, identifier: id, tty } : null;
153
+ } catch {
154
+ // Ghostty not installed, not scriptable, or the TTY is not writable
155
+ return null;
156
+ }
157
+ },
158
+
159
+ async focus(location) {
160
+ return onTerminal(location, "focus t\n activate");
161
+ },
162
+
163
+ async send(location, message) {
164
+ if (!(await onTerminal(location, `input text "${escapeAppleScript(message)}" to t`))) {
165
+ throw new Error(`Ghostty terminal not found for TTY ${location.tty}`);
166
+ }
167
+ await sleep(150);
168
+ if (!(await onTerminal(location, textAction(CARRIAGE_RETURN)))) {
169
+ throw new Error(
170
+ `Ghostty terminal disappeared before Enter could be sent for TTY ${location.tty}`,
171
+ );
172
+ }
173
+ },
174
+
175
+ async sendKey(location, key) {
176
+ if (!(await onTerminal(location, textAction(key)))) {
177
+ throw new Error(`Ghostty terminal not found for TTY ${location.tty}`);
178
+ }
179
+ },
180
+ };
@@ -0,0 +1,23 @@
1
+ import type { TerminalBackend } from "../types.js";
2
+ import { TerminalType } from "../types.js";
3
+ import { ghosttyBackend } from "./ghostty.js";
4
+ import { iterm2Backend } from "./iterm2.js";
5
+ import { terminalAppBackend } from "./terminal-app.js";
6
+ import { tmuxBackend } from "./tmux.js";
7
+ import { weztermBackend } from "./wezterm.js";
8
+
9
+ export const TERMINAL_BACKENDS: readonly TerminalBackend[] = [
10
+ tmuxBackend,
11
+ weztermBackend,
12
+ ghosttyBackend,
13
+ iterm2Backend,
14
+ terminalAppBackend,
15
+ ];
16
+
17
+ const BACKENDS_BY_TYPE = new Map<TerminalType, TerminalBackend>(
18
+ TERMINAL_BACKENDS.map((backend) => [backend.type, backend]),
19
+ );
20
+
21
+ export function terminalBackendFor(type: TerminalType): TerminalBackend | undefined {
22
+ return BACKENDS_BY_TYPE.get(type);
23
+ }
@@ -0,0 +1,126 @@
1
+ import { execFile } from "child_process";
2
+ import { promisify } from "util";
3
+ import { escapeAppleScript } from "../../utils/applescript.js";
4
+ import { TerminalType, type TerminalBackend } from "../types.js";
5
+ import { appleScriptKeyAction, isProcessRunning, sleep } from "./shared.js";
6
+
7
+ const execFileAsync = promisify(execFile);
8
+
9
+ function sessionScript(tty: string, sessionCommand: string): string {
10
+ return `
11
+ tell application "iTerm"
12
+ set targetSession to missing value
13
+ repeat with w in windows
14
+ repeat with t in tabs of w
15
+ repeat with s in sessions of t
16
+ if tty of s is "${escapeAppleScript(tty)}" then
17
+ set targetSession to s
18
+ exit repeat
19
+ end if
20
+ end repeat
21
+ if targetSession is not missing value then exit repeat
22
+ end repeat
23
+ if targetSession is not missing value then exit repeat
24
+ end repeat
25
+ if targetSession is missing value then return "not_found"
26
+ tell targetSession to ${sessionCommand}
27
+ end tell
28
+ return "ok"`;
29
+ }
30
+
31
+ export const iterm2Backend: TerminalBackend = {
32
+ type: TerminalType.ITERM2,
33
+ label: "iTerm2",
34
+
35
+ async find(tty) {
36
+ try {
37
+ if (!(await isProcessRunning("iTerm2"))) return null;
38
+ const script = `
39
+ tell application "iTerm"
40
+ repeat with w in windows
41
+ repeat with t in tabs of w
42
+ repeat with s in sessions of t
43
+ if tty of s is "${escapeAppleScript(tty)}" then
44
+ return "found"
45
+ end if
46
+ end repeat
47
+ end repeat
48
+ end repeat
49
+ end tell
50
+ `;
51
+ const { stdout } = await execFileAsync("osascript", ["-e", script]);
52
+ return stdout.trim() === "found" ? { type: TerminalType.ITERM2, identifier: tty, tty } : null;
53
+ } catch {
54
+ return null;
55
+ }
56
+ },
57
+
58
+ async focus(location) {
59
+ const script = `
60
+ tell application "iTerm"
61
+ activate
62
+ repeat with w in windows
63
+ repeat with t in tabs of w
64
+ repeat with s in sessions of t
65
+ if tty of s is "${escapeAppleScript(location.tty)}" then
66
+ select s
67
+ return "true"
68
+ end if
69
+ end repeat
70
+ end repeat
71
+ end repeat
72
+ end tell
73
+ `;
74
+ const { stdout } = await execFileAsync("osascript", ["-e", script]);
75
+ return stdout.trim() === "true";
76
+ },
77
+
78
+ async send(location, message) {
79
+ const textScript = sessionScript(
80
+ location.tty,
81
+ `write text "${escapeAppleScript(message)}" newline no`,
82
+ );
83
+ const { stdout: textResult } = await execFileAsync("osascript", ["-e", textScript]);
84
+ if (textResult.trim() !== "ok") {
85
+ throw new Error(`iTerm2 session not found for TTY ${location.tty}`);
86
+ }
87
+ await sleep(150);
88
+ const enterScript = sessionScript(location.tty, 'write text "" newline yes');
89
+ const { stdout: enterResult } = await execFileAsync("osascript", ["-e", enterScript]);
90
+ if (enterResult.trim() !== "ok") {
91
+ throw new Error(
92
+ `iTerm2 session disappeared before Enter could be sent for TTY ${location.tty}`,
93
+ );
94
+ }
95
+ },
96
+
97
+ async sendKey(location, key) {
98
+ const script = `
99
+ tell application "iTerm"
100
+ set targetSession to missing value
101
+ repeat with w in windows
102
+ repeat with t in tabs of w
103
+ repeat with s in sessions of t
104
+ if tty of s is "${escapeAppleScript(location.tty)}" then
105
+ set targetSession to s
106
+ set frontmost of w to true
107
+ tell t to select
108
+ tell s to select
109
+ exit repeat
110
+ end if
111
+ end repeat
112
+ if targetSession is not missing value then exit repeat
113
+ end repeat
114
+ if targetSession is not missing value then exit repeat
115
+ end repeat
116
+ if targetSession is missing value then return "not_found"
117
+ activate
118
+ end tell
119
+ tell application "System Events" to ${appleScriptKeyAction(key)}
120
+ return "ok"`;
121
+ const { stdout } = await execFileAsync("osascript", ["-e", script]);
122
+ if (stdout.trim() !== "ok") {
123
+ throw new Error(`iTerm2 session not found for TTY ${location.tty}`);
124
+ }
125
+ },
126
+ };