openhorse 0.1.5

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 (587) hide show
  1. package/README.md +460 -0
  2. package/bin/openhorse +56 -0
  3. package/dist/agents/coder.d.ts +12 -0
  4. package/dist/agents/coder.d.ts.map +1 -0
  5. package/dist/agents/coder.js +48 -0
  6. package/dist/agents/coder.js.map +1 -0
  7. package/dist/agents/leader.d.ts +12 -0
  8. package/dist/agents/leader.d.ts.map +1 -0
  9. package/dist/agents/leader.js +49 -0
  10. package/dist/agents/leader.js.map +1 -0
  11. package/dist/agents/router.d.ts +59 -0
  12. package/dist/agents/router.d.ts.map +1 -0
  13. package/dist/agents/router.js +193 -0
  14. package/dist/agents/router.js.map +1 -0
  15. package/dist/cli-ink.d.ts +7 -0
  16. package/dist/cli-ink.d.ts.map +1 -0
  17. package/dist/cli-ink.js +135 -0
  18. package/dist/cli-ink.js.map +1 -0
  19. package/dist/cli.d.ts +7 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +638 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/commands/index.d.ts +12 -0
  24. package/dist/commands/index.d.ts.map +1 -0
  25. package/dist/commands/index.js +1095 -0
  26. package/dist/commands/index.js.map +1 -0
  27. package/dist/commands/parser.d.ts +26 -0
  28. package/dist/commands/parser.d.ts.map +1 -0
  29. package/dist/commands/parser.js +61 -0
  30. package/dist/commands/parser.js.map +1 -0
  31. package/dist/commands/types.d.ts +61 -0
  32. package/dist/commands/types.d.ts.map +1 -0
  33. package/dist/commands/types.js +32 -0
  34. package/dist/commands/types.js.map +1 -0
  35. package/dist/core/agent.d.ts +66 -0
  36. package/dist/core/agent.d.ts.map +1 -0
  37. package/dist/core/agent.js +55 -0
  38. package/dist/core/agent.js.map +1 -0
  39. package/dist/core/brain.d.ts +41 -0
  40. package/dist/core/brain.d.ts.map +1 -0
  41. package/dist/core/brain.js +92 -0
  42. package/dist/core/brain.js.map +1 -0
  43. package/dist/core/cost-tracker.d.ts +107 -0
  44. package/dist/core/cost-tracker.d.ts.map +1 -0
  45. package/dist/core/cost-tracker.js +199 -0
  46. package/dist/core/cost-tracker.js.map +1 -0
  47. package/dist/core/strategy-tracker.d.ts +61 -0
  48. package/dist/core/strategy-tracker.d.ts.map +1 -0
  49. package/dist/core/strategy-tracker.js +124 -0
  50. package/dist/core/strategy-tracker.js.map +1 -0
  51. package/dist/framework/index.d.ts +20 -0
  52. package/dist/framework/index.d.ts.map +1 -0
  53. package/dist/framework/index.js +29 -0
  54. package/dist/framework/index.js.map +1 -0
  55. package/dist/framework/prompt.d.ts +40 -0
  56. package/dist/framework/prompt.d.ts.map +1 -0
  57. package/dist/framework/prompt.js +112 -0
  58. package/dist/framework/prompt.js.map +1 -0
  59. package/dist/framework/query.d.ts +86 -0
  60. package/dist/framework/query.d.ts.map +1 -0
  61. package/dist/framework/query.js +177 -0
  62. package/dist/framework/query.js.map +1 -0
  63. package/dist/framework/store.d.ts +64 -0
  64. package/dist/framework/store.d.ts.map +1 -0
  65. package/dist/framework/store.js +78 -0
  66. package/dist/framework/store.js.map +1 -0
  67. package/dist/framework/tool-state.d.ts +22 -0
  68. package/dist/framework/tool-state.d.ts.map +1 -0
  69. package/dist/framework/tool-state.js +40 -0
  70. package/dist/framework/tool-state.js.map +1 -0
  71. package/dist/framework/tool.d.ts +88 -0
  72. package/dist/framework/tool.d.ts.map +1 -0
  73. package/dist/framework/tool.js +60 -0
  74. package/dist/framework/tool.js.map +1 -0
  75. package/dist/harness/harness.d.ts +96 -0
  76. package/dist/harness/harness.d.ts.map +1 -0
  77. package/dist/harness/harness.js +224 -0
  78. package/dist/harness/harness.js.map +1 -0
  79. package/dist/harness/safety.d.ts +89 -0
  80. package/dist/harness/safety.d.ts.map +1 -0
  81. package/dist/harness/safety.js +178 -0
  82. package/dist/harness/safety.js.map +1 -0
  83. package/dist/index.d.ts +30 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +57 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/init.d.ts +178 -0
  88. package/dist/init.d.ts.map +1 -0
  89. package/dist/init.js +473 -0
  90. package/dist/init.js.map +1 -0
  91. package/dist/ink/bidi.d.ts +16 -0
  92. package/dist/ink/bidi.d.ts.map +1 -0
  93. package/dist/ink/bidi.js +124 -0
  94. package/dist/ink/bidi.js.map +1 -0
  95. package/dist/ink/clearTerminal.d.ts +14 -0
  96. package/dist/ink/clearTerminal.d.ts.map +1 -0
  97. package/dist/ink/clearTerminal.js +63 -0
  98. package/dist/ink/clearTerminal.js.map +1 -0
  99. package/dist/ink/colorize.d.ts +17 -0
  100. package/dist/ink/colorize.d.ts.map +1 -0
  101. package/dist/ink/colorize.js +208 -0
  102. package/dist/ink/colorize.js.map +1 -0
  103. package/dist/ink/components/App.d.ts +73 -0
  104. package/dist/ink/components/App.d.ts.map +1 -0
  105. package/dist/ink/components/App.js +646 -0
  106. package/dist/ink/components/App.js.map +1 -0
  107. package/dist/ink/components/AppContext.d.ts +12 -0
  108. package/dist/ink/components/AppContext.d.ts.map +1 -0
  109. package/dist/ink/components/AppContext.js +14 -0
  110. package/dist/ink/components/AppContext.js.map +1 -0
  111. package/dist/ink/components/Box.d.ts +44 -0
  112. package/dist/ink/components/Box.d.ts.map +1 -0
  113. package/dist/ink/components/Box.js +191 -0
  114. package/dist/ink/components/Box.js.map +1 -0
  115. package/dist/ink/components/ClockContext.d.ts +9 -0
  116. package/dist/ink/components/ClockContext.d.ts.map +1 -0
  117. package/dist/ink/components/ClockContext.js +146 -0
  118. package/dist/ink/components/ClockContext.js.map +1 -0
  119. package/dist/ink/components/CursorDeclarationContext.d.ts +23 -0
  120. package/dist/ink/components/CursorDeclarationContext.d.ts.map +1 -0
  121. package/dist/ink/components/CursorDeclarationContext.js +6 -0
  122. package/dist/ink/components/CursorDeclarationContext.js.map +1 -0
  123. package/dist/ink/components/ErrorOverview.d.ts +6 -0
  124. package/dist/ink/components/ErrorOverview.d.ts.map +1 -0
  125. package/dist/ink/components/ErrorOverview.js +27 -0
  126. package/dist/ink/components/ErrorOverview.js.map +1 -0
  127. package/dist/ink/components/Newline.d.ts +13 -0
  128. package/dist/ink/components/Newline.d.ts.map +1 -0
  129. package/dist/ink/components/Newline.js +36 -0
  130. package/dist/ink/components/Newline.js.map +1 -0
  131. package/dist/ink/components/NoSelect.d.ts +20 -0
  132. package/dist/ink/components/NoSelect.d.ts.map +1 -0
  133. package/dist/ink/components/NoSelect.js +63 -0
  134. package/dist/ink/components/NoSelect.js.map +1 -0
  135. package/dist/ink/components/RawAnsi.d.ts +17 -0
  136. package/dist/ink/components/RawAnsi.d.ts.map +1 -0
  137. package/dist/ink/components/RawAnsi.js +52 -0
  138. package/dist/ink/components/RawAnsi.js.map +1 -0
  139. package/dist/ink/components/Spacer.d.ts +6 -0
  140. package/dist/ink/components/Spacer.d.ts.map +1 -0
  141. package/dist/ink/components/Spacer.js +26 -0
  142. package/dist/ink/components/Spacer.js.map +1 -0
  143. package/dist/ink/components/StdinContext.d.ts +28 -0
  144. package/dist/ink/components/StdinContext.d.ts.map +1 -0
  145. package/dist/ink/components/StdinContext.js +19 -0
  146. package/dist/ink/components/StdinContext.js.map +1 -0
  147. package/dist/ink/components/TerminalFocusContext.d.ts +10 -0
  148. package/dist/ink/components/TerminalFocusContext.d.ts.map +1 -0
  149. package/dist/ink/components/TerminalFocusContext.js +81 -0
  150. package/dist/ink/components/TerminalFocusContext.js.map +1 -0
  151. package/dist/ink/components/TerminalSizeContext.d.ts +6 -0
  152. package/dist/ink/components/TerminalSizeContext.d.ts.map +1 -0
  153. package/dist/ink/components/TerminalSizeContext.js +6 -0
  154. package/dist/ink/components/TerminalSizeContext.js.map +1 -0
  155. package/dist/ink/components/Text.d.ts +56 -0
  156. package/dist/ink/components/Text.d.ts.map +1 -0
  157. package/dist/ink/components/Text.js +201 -0
  158. package/dist/ink/components/Text.js.map +1 -0
  159. package/dist/ink/constants.d.ts +2 -0
  160. package/dist/ink/constants.d.ts.map +1 -0
  161. package/dist/ink/constants.js +6 -0
  162. package/dist/ink/constants.js.map +1 -0
  163. package/dist/ink/devtools.d.ts +3 -0
  164. package/dist/ink/devtools.d.ts.map +1 -0
  165. package/dist/ink/devtools.js +5 -0
  166. package/dist/ink/devtools.js.map +1 -0
  167. package/dist/ink/dom.d.ts +77 -0
  168. package/dist/ink/dom.d.ts.map +1 -0
  169. package/dist/ink/dom.js +322 -0
  170. package/dist/ink/dom.js.map +1 -0
  171. package/dist/ink/events/click-event.d.ts +32 -0
  172. package/dist/ink/events/click-event.d.ts.map +1 -0
  173. package/dist/ink/events/click-event.js +30 -0
  174. package/dist/ink/events/click-event.js.map +1 -0
  175. package/dist/ink/events/dispatcher.d.ts +41 -0
  176. package/dist/ink/events/dispatcher.d.ts.map +1 -0
  177. package/dist/ink/events/dispatcher.js +181 -0
  178. package/dist/ink/events/dispatcher.js.map +1 -0
  179. package/dist/ink/events/emitter.d.ts +6 -0
  180. package/dist/ink/events/emitter.d.ts.map +1 -0
  181. package/dist/ink/events/emitter.js +36 -0
  182. package/dist/ink/events/emitter.js.map +1 -0
  183. package/dist/ink/events/event-handlers.d.ts +47 -0
  184. package/dist/ink/events/event-handlers.d.ts.map +1 -0
  185. package/dist/ink/events/event-handlers.js +34 -0
  186. package/dist/ink/events/event-handlers.js.map +1 -0
  187. package/dist/ink/events/event.d.ts +6 -0
  188. package/dist/ink/events/event.d.ts.map +1 -0
  189. package/dist/ink/events/event.js +16 -0
  190. package/dist/ink/events/event.js.map +1 -0
  191. package/dist/ink/events/focus-event.d.ts +14 -0
  192. package/dist/ink/events/focus-event.d.ts.map +1 -0
  193. package/dist/ink/events/focus-event.js +20 -0
  194. package/dist/ink/events/focus-event.js.map +1 -0
  195. package/dist/ink/events/input-event.d.ts +31 -0
  196. package/dist/ink/events/input-event.d.ts.map +1 -0
  197. package/dist/ink/events/input-event.js +163 -0
  198. package/dist/ink/events/input-event.js.map +1 -0
  199. package/dist/ink/events/keyboard-event.d.ts +20 -0
  200. package/dist/ink/events/keyboard-event.d.ts.map +1 -0
  201. package/dist/ink/events/keyboard-event.js +44 -0
  202. package/dist/ink/events/keyboard-event.js.map +1 -0
  203. package/dist/ink/events/terminal-event.d.ts +55 -0
  204. package/dist/ink/events/terminal-event.d.ts.map +1 -0
  205. package/dist/ink/events/terminal-event.js +79 -0
  206. package/dist/ink/events/terminal-event.js.map +1 -0
  207. package/dist/ink/events/terminal-focus-event.d.ts +14 -0
  208. package/dist/ink/events/terminal-focus-event.d.ts.map +1 -0
  209. package/dist/ink/events/terminal-focus-event.js +19 -0
  210. package/dist/ink/events/terminal-focus-event.js.map +1 -0
  211. package/dist/ink/focus.d.ts +44 -0
  212. package/dist/ink/focus.d.ts.map +1 -0
  213. package/dist/ink/focus.js +164 -0
  214. package/dist/ink/focus.js.map +1 -0
  215. package/dist/ink/frame.d.ts +95 -0
  216. package/dist/ink/frame.d.ts.map +1 -0
  217. package/dist/ink/frame.js +35 -0
  218. package/dist/ink/frame.js.map +1 -0
  219. package/dist/ink/get-max-width.d.ts +18 -0
  220. package/dist/ink/get-max-width.d.ts.map +1 -0
  221. package/dist/ink/get-max-width.js +26 -0
  222. package/dist/ink/get-max-width.js.map +1 -0
  223. package/dist/ink/hit-test.d.ts +34 -0
  224. package/dist/ink/hit-test.d.ts.map +1 -0
  225. package/dist/ink/hit-test.js +119 -0
  226. package/dist/ink/hit-test.js.map +1 -0
  227. package/dist/ink/hooks/use-animation-frame.d.ts +27 -0
  228. package/dist/ink/hooks/use-animation-frame.d.ts.map +1 -0
  229. package/dist/ink/hooks/use-animation-frame.js +52 -0
  230. package/dist/ink/hooks/use-animation-frame.js.map +1 -0
  231. package/dist/ink/hooks/use-app.d.ts +6 -0
  232. package/dist/ink/hooks/use-app.d.ts.map +1 -0
  233. package/dist/ink/hooks/use-app.js +13 -0
  234. package/dist/ink/hooks/use-app.js.map +1 -0
  235. package/dist/ink/hooks/use-declared-cursor.d.ts +27 -0
  236. package/dist/ink/hooks/use-declared-cursor.d.ts.map +1 -0
  237. package/dist/ink/hooks/use-declared-cursor.js +67 -0
  238. package/dist/ink/hooks/use-declared-cursor.js.map +1 -0
  239. package/dist/ink/hooks/use-input.d.ts +38 -0
  240. package/dist/ink/hooks/use-input.d.ts.map +1 -0
  241. package/dist/ink/hooks/use-input.js +86 -0
  242. package/dist/ink/hooks/use-input.js.map +1 -0
  243. package/dist/ink/hooks/use-interval.d.ts +19 -0
  244. package/dist/ink/hooks/use-interval.d.ts.map +1 -0
  245. package/dist/ink/hooks/use-interval.js +59 -0
  246. package/dist/ink/hooks/use-interval.js.map +1 -0
  247. package/dist/ink/hooks/use-search-highlight.d.ts +31 -0
  248. package/dist/ink/hooks/use-search-highlight.d.ts.map +1 -0
  249. package/dist/ink/hooks/use-search-highlight.js +39 -0
  250. package/dist/ink/hooks/use-search-highlight.js.map +1 -0
  251. package/dist/ink/hooks/use-selection.d.ts +40 -0
  252. package/dist/ink/hooks/use-selection.d.ts.map +1 -0
  253. package/dist/ink/hooks/use-selection.js +68 -0
  254. package/dist/ink/hooks/use-selection.js.map +1 -0
  255. package/dist/ink/hooks/use-stdin.d.ts +6 -0
  256. package/dist/ink/hooks/use-stdin.d.ts.map +1 -0
  257. package/dist/ink/hooks/use-stdin.js +13 -0
  258. package/dist/ink/hooks/use-stdin.js.map +1 -0
  259. package/dist/ink/hooks/use-tab-status.d.ts +14 -0
  260. package/dist/ink/hooks/use-tab-status.d.ts.map +1 -0
  261. package/dist/ink/hooks/use-tab-status.js +61 -0
  262. package/dist/ink/hooks/use-tab-status.js.map +1 -0
  263. package/dist/ink/hooks/use-terminal-focus.d.ts +11 -0
  264. package/dist/ink/hooks/use-terminal-focus.d.ts.map +1 -0
  265. package/dist/ink/hooks/use-terminal-focus.js +22 -0
  266. package/dist/ink/hooks/use-terminal-focus.js.map +1 -0
  267. package/dist/ink/hooks/use-terminal-title.d.ts +13 -0
  268. package/dist/ink/hooks/use-terminal-title.d.ts.map +1 -0
  269. package/dist/ink/hooks/use-terminal-title.js +36 -0
  270. package/dist/ink/hooks/use-terminal-title.js.map +1 -0
  271. package/dist/ink/hooks/use-terminal-viewport.d.ts +30 -0
  272. package/dist/ink/hooks/use-terminal-viewport.d.ts.map +1 -0
  273. package/dist/ink/hooks/use-terminal-viewport.js +81 -0
  274. package/dist/ink/hooks/use-terminal-viewport.js.map +1 -0
  275. package/dist/ink/index.d.ts +54 -0
  276. package/dist/ink/index.d.ts.map +1 -0
  277. package/dist/ink/index.js +111 -0
  278. package/dist/ink/index.js.map +1 -0
  279. package/dist/ink/ink.d.ts +371 -0
  280. package/dist/ink/ink.d.ts.map +1 -0
  281. package/dist/ink/ink.js +1686 -0
  282. package/dist/ink/ink.js.map +1 -0
  283. package/dist/ink/instances.d.ts +4 -0
  284. package/dist/ink/instances.d.ts.map +1 -0
  285. package/dist/ink/instances.js +10 -0
  286. package/dist/ink/instances.js.map +1 -0
  287. package/dist/ink/line-width-cache.d.ts +2 -0
  288. package/dist/ink/line-width-cache.d.ts.map +1 -0
  289. package/dist/ink/line-width-cache.js +23 -0
  290. package/dist/ink/line-width-cache.js.map +1 -0
  291. package/dist/ink/log-update.d.ts +18 -0
  292. package/dist/ink/log-update.d.ts.map +1 -0
  293. package/dist/ink/log-update.js +637 -0
  294. package/dist/ink/log-update.js.map +1 -0
  295. package/dist/ink/measure-element.d.ts +17 -0
  296. package/dist/ink/measure-element.d.ts.map +1 -0
  297. package/dist/ink/measure-element.js +11 -0
  298. package/dist/ink/measure-element.js.map +1 -0
  299. package/dist/ink/measure-text.d.ts +7 -0
  300. package/dist/ink/measure-text.d.ts.map +1 -0
  301. package/dist/ink/measure-text.js +38 -0
  302. package/dist/ink/measure-text.js.map +1 -0
  303. package/dist/ink/node-cache.d.ts +21 -0
  304. package/dist/ink/node-cache.d.ts.map +1 -0
  305. package/dist/ink/node-cache.js +36 -0
  306. package/dist/ink/node-cache.js.map +1 -0
  307. package/dist/ink/optimizer.d.ts +16 -0
  308. package/dist/ink/optimizer.d.ts.map +1 -0
  309. package/dist/ink/optimizer.js +85 -0
  310. package/dist/ink/optimizer.js.map +1 -0
  311. package/dist/ink/output.d.ts +120 -0
  312. package/dist/ink/output.d.ts.map +1 -0
  313. package/dist/ink/output.js +559 -0
  314. package/dist/ink/output.js.map +1 -0
  315. package/dist/ink/parse-keypress.d.ts +112 -0
  316. package/dist/ink/parse-keypress.d.ts.map +1 -0
  317. package/dist/ink/parse-keypress.js +719 -0
  318. package/dist/ink/parse-keypress.js.map +1 -0
  319. package/dist/ink/parse-keypress.test.d.ts +2 -0
  320. package/dist/ink/parse-keypress.test.d.ts.map +1 -0
  321. package/dist/ink/parse-keypress.test.js +37 -0
  322. package/dist/ink/parse-keypress.test.js.map +1 -0
  323. package/dist/ink/reconciler.d.ts +12 -0
  324. package/dist/ink/reconciler.d.ts.map +1 -0
  325. package/dist/ink/reconciler.js +456 -0
  326. package/dist/ink/reconciler.js.map +1 -0
  327. package/dist/ink/render-border.d.ts +25 -0
  328. package/dist/ink/render-border.d.ts.map +1 -0
  329. package/dist/ink/render-border.js +141 -0
  330. package/dist/ink/render-border.js.map +1 -0
  331. package/dist/ink/render-node-to-output.d.ts +46 -0
  332. package/dist/ink/render-node-to-output.d.ts.map +1 -0
  333. package/dist/ink/render-node-to-output.js +1257 -0
  334. package/dist/ink/render-node-to-output.js.map +1 -0
  335. package/dist/ink/render-to-screen.d.ts +47 -0
  336. package/dist/ink/render-to-screen.d.ts.map +1 -0
  337. package/dist/ink/render-to-screen.js +213 -0
  338. package/dist/ink/render-to-screen.js.map +1 -0
  339. package/dist/ink/renderer.d.ts +15 -0
  340. package/dist/ink/renderer.d.ts.map +1 -0
  341. package/dist/ink/renderer.js +169 -0
  342. package/dist/ink/renderer.js.map +1 -0
  343. package/dist/ink/root.d.ts +75 -0
  344. package/dist/ink/root.d.ts.map +1 -0
  345. package/dist/ink/root.js +89 -0
  346. package/dist/ink/root.js.map +1 -0
  347. package/dist/ink/screen.d.ts +270 -0
  348. package/dist/ink/screen.d.ts.map +1 -0
  349. package/dist/ink/screen.js +1194 -0
  350. package/dist/ink/screen.js.map +1 -0
  351. package/dist/ink/searchHighlight.d.ts +21 -0
  352. package/dist/ink/searchHighlight.d.ts.map +1 -0
  353. package/dist/ink/searchHighlight.js +82 -0
  354. package/dist/ink/searchHighlight.js.map +1 -0
  355. package/dist/ink/selection.d.ts +230 -0
  356. package/dist/ink/selection.d.ts.map +1 -0
  357. package/dist/ink/selection.js +814 -0
  358. package/dist/ink/selection.js.map +1 -0
  359. package/dist/ink/squash-text-nodes.d.ts +23 -0
  360. package/dist/ink/squash-text-nodes.d.ts.map +1 -0
  361. package/dist/ink/squash-text-nodes.js +60 -0
  362. package/dist/ink/squash-text-nodes.js.map +1 -0
  363. package/dist/ink/stringWidth.d.ts +2 -0
  364. package/dist/ink/stringWidth.d.ts.map +1 -0
  365. package/dist/ink/stringWidth.js +207 -0
  366. package/dist/ink/stringWidth.js.map +1 -0
  367. package/dist/ink/styles.d.ts +307 -0
  368. package/dist/ink/styles.d.ts.map +1 -0
  369. package/dist/ink/styles.js +302 -0
  370. package/dist/ink/styles.js.map +1 -0
  371. package/dist/ink/supports-hyperlinks.d.ts +14 -0
  372. package/dist/ink/supports-hyperlinks.d.ts.map +1 -0
  373. package/dist/ink/supports-hyperlinks.js +48 -0
  374. package/dist/ink/supports-hyperlinks.js.map +1 -0
  375. package/dist/ink/tabstops.d.ts +2 -0
  376. package/dist/ink/tabstops.d.ts.map +1 -0
  377. package/dist/ink/tabstops.js +43 -0
  378. package/dist/ink/tabstops.js.map +1 -0
  379. package/dist/ink/terminal-focus-state.d.ts +7 -0
  380. package/dist/ink/terminal-focus-state.d.ts.map +1 -0
  381. package/dist/ink/terminal-focus-state.js +43 -0
  382. package/dist/ink/terminal-focus-state.js.map +1 -0
  383. package/dist/ink/terminal-querier.d.ts +130 -0
  384. package/dist/ink/terminal-querier.d.ts.map +1 -0
  385. package/dist/ink/terminal-querier.js +184 -0
  386. package/dist/ink/terminal-querier.js.map +1 -0
  387. package/dist/ink/terminal.d.ts +56 -0
  388. package/dist/ink/terminal.d.ts.map +1 -0
  389. package/dist/ink/terminal.js +249 -0
  390. package/dist/ink/terminal.js.map +1 -0
  391. package/dist/ink/termio/csi.d.ts +166 -0
  392. package/dist/ink/termio/csi.d.ts.map +1 -0
  393. package/dist/ink/termio/csi.js +285 -0
  394. package/dist/ink/termio/csi.js.map +1 -0
  395. package/dist/ink/termio/dec.d.ts +38 -0
  396. package/dist/ink/termio/dec.d.ts.map +1 -0
  397. package/dist/ink/termio/dec.js +59 -0
  398. package/dist/ink/termio/dec.js.map +1 -0
  399. package/dist/ink/termio.d.ts +25 -0
  400. package/dist/ink/termio.d.ts.map +1 -0
  401. package/dist/ink/termio.js +32 -0
  402. package/dist/ink/termio.js.map +1 -0
  403. package/dist/ink/useTerminalNotification.d.ts +27 -0
  404. package/dist/ink/useTerminalNotification.d.ts.map +1 -0
  405. package/dist/ink/useTerminalNotification.js +62 -0
  406. package/dist/ink/useTerminalNotification.js.map +1 -0
  407. package/dist/ink/warn.d.ts +2 -0
  408. package/dist/ink/warn.d.ts.map +1 -0
  409. package/dist/ink/warn.js +14 -0
  410. package/dist/ink/warn.js.map +1 -0
  411. package/dist/ink/widest-line.d.ts +2 -0
  412. package/dist/ink/widest-line.d.ts.map +1 -0
  413. package/dist/ink/widest-line.js +18 -0
  414. package/dist/ink/widest-line.js.map +1 -0
  415. package/dist/ink/wrap-text.d.ts +3 -0
  416. package/dist/ink/wrap-text.d.ts.map +1 -0
  417. package/dist/ink/wrap-text.js +61 -0
  418. package/dist/ink/wrap-text.js.map +1 -0
  419. package/dist/ink/wrapAnsi.d.ts +8 -0
  420. package/dist/ink/wrapAnsi.d.ts.map +1 -0
  421. package/dist/ink/wrapAnsi.js +13 -0
  422. package/dist/ink/wrapAnsi.js.map +1 -0
  423. package/dist/memory/embeddings.d.ts +33 -0
  424. package/dist/memory/embeddings.d.ts.map +1 -0
  425. package/dist/memory/embeddings.js +123 -0
  426. package/dist/memory/embeddings.js.map +1 -0
  427. package/dist/memory/index.d.ts +14 -0
  428. package/dist/memory/index.d.ts.map +1 -0
  429. package/dist/memory/index.js +31 -0
  430. package/dist/memory/index.js.map +1 -0
  431. package/dist/memory/prompts.d.ts +12 -0
  432. package/dist/memory/prompts.d.ts.map +1 -0
  433. package/dist/memory/prompts.js +117 -0
  434. package/dist/memory/prompts.js.map +1 -0
  435. package/dist/memory/semantic-search.d.ts +45 -0
  436. package/dist/memory/semantic-search.d.ts.map +1 -0
  437. package/dist/memory/semantic-search.js +112 -0
  438. package/dist/memory/semantic-search.js.map +1 -0
  439. package/dist/memory/storage.d.ts +79 -0
  440. package/dist/memory/storage.d.ts.map +1 -0
  441. package/dist/memory/storage.js +285 -0
  442. package/dist/memory/storage.js.map +1 -0
  443. package/dist/memory/store.d.ts +103 -0
  444. package/dist/memory/store.d.ts.map +1 -0
  445. package/dist/memory/store.js +226 -0
  446. package/dist/memory/store.js.map +1 -0
  447. package/dist/memory/types.d.ts +40 -0
  448. package/dist/memory/types.d.ts.map +1 -0
  449. package/dist/memory/types.js +71 -0
  450. package/dist/memory/types.js.map +1 -0
  451. package/dist/memory/validation.d.ts +28 -0
  452. package/dist/memory/validation.d.ts.map +1 -0
  453. package/dist/memory/validation.js +147 -0
  454. package/dist/memory/validation.js.map +1 -0
  455. package/dist/memory/vector-store.d.ts +49 -0
  456. package/dist/memory/vector-store.d.ts.map +1 -0
  457. package/dist/memory/vector-store.js +259 -0
  458. package/dist/memory/vector-store.js.map +1 -0
  459. package/dist/screens/REPL.d.ts +21 -0
  460. package/dist/screens/REPL.d.ts.map +1 -0
  461. package/dist/screens/REPL.js +139 -0
  462. package/dist/screens/REPL.js.map +1 -0
  463. package/dist/services/agent-runner.d.ts +68 -0
  464. package/dist/services/agent-runner.d.ts.map +1 -0
  465. package/dist/services/agent-runner.js +186 -0
  466. package/dist/services/agent-runner.js.map +1 -0
  467. package/dist/services/atomic-write.d.ts +12 -0
  468. package/dist/services/atomic-write.d.ts.map +1 -0
  469. package/dist/services/atomic-write.js +27 -0
  470. package/dist/services/atomic-write.js.map +1 -0
  471. package/dist/services/config-dir.d.ts +55 -0
  472. package/dist/services/config-dir.d.ts.map +1 -0
  473. package/dist/services/config-dir.js +136 -0
  474. package/dist/services/config-dir.js.map +1 -0
  475. package/dist/services/config.d.ts +54 -0
  476. package/dist/services/config.d.ts.map +1 -0
  477. package/dist/services/config.js +98 -0
  478. package/dist/services/config.js.map +1 -0
  479. package/dist/services/file-glob.d.ts +26 -0
  480. package/dist/services/file-glob.d.ts.map +1 -0
  481. package/dist/services/file-glob.js +134 -0
  482. package/dist/services/file-glob.js.map +1 -0
  483. package/dist/services/global-config.d.ts +109 -0
  484. package/dist/services/global-config.d.ts.map +1 -0
  485. package/dist/services/global-config.js +211 -0
  486. package/dist/services/global-config.js.map +1 -0
  487. package/dist/services/llm.d.ts +139 -0
  488. package/dist/services/llm.d.ts.map +1 -0
  489. package/dist/services/llm.js +376 -0
  490. package/dist/services/llm.js.map +1 -0
  491. package/dist/services/memory.d.ts +39 -0
  492. package/dist/services/memory.d.ts.map +1 -0
  493. package/dist/services/memory.js +88 -0
  494. package/dist/services/memory.js.map +1 -0
  495. package/dist/services/session-storage.d.ts +136 -0
  496. package/dist/services/session-storage.d.ts.map +1 -0
  497. package/dist/services/session-storage.js +304 -0
  498. package/dist/services/session-storage.js.map +1 -0
  499. package/dist/services/task-manager.d.ts +154 -0
  500. package/dist/services/task-manager.d.ts.map +1 -0
  501. package/dist/services/task-manager.js +288 -0
  502. package/dist/services/task-manager.js.map +1 -0
  503. package/dist/services/yaml-config.d.ts +57 -0
  504. package/dist/services/yaml-config.d.ts.map +1 -0
  505. package/dist/services/yaml-config.js +176 -0
  506. package/dist/services/yaml-config.js.map +1 -0
  507. package/dist/skills/discovery.d.ts +22 -0
  508. package/dist/skills/discovery.d.ts.map +1 -0
  509. package/dist/skills/discovery.js +75 -0
  510. package/dist/skills/discovery.js.map +1 -0
  511. package/dist/skills/index.d.ts +10 -0
  512. package/dist/skills/index.d.ts.map +1 -0
  513. package/dist/skills/index.js +26 -0
  514. package/dist/skills/index.js.map +1 -0
  515. package/dist/skills/loader.d.ts +48 -0
  516. package/dist/skills/loader.d.ts.map +1 -0
  517. package/dist/skills/loader.js +231 -0
  518. package/dist/skills/loader.js.map +1 -0
  519. package/dist/skills/registry.d.ts +37 -0
  520. package/dist/skills/registry.d.ts.map +1 -0
  521. package/dist/skills/registry.js +134 -0
  522. package/dist/skills/registry.js.map +1 -0
  523. package/dist/skills/types.d.ts +89 -0
  524. package/dist/skills/types.d.ts.map +1 -0
  525. package/dist/skills/types.js +24 -0
  526. package/dist/skills/types.js.map +1 -0
  527. package/dist/tools/bash_security.d.ts +64 -0
  528. package/dist/tools/bash_security.d.ts.map +1 -0
  529. package/dist/tools/bash_security.js +291 -0
  530. package/dist/tools/bash_security.js.map +1 -0
  531. package/dist/tools/index.d.ts +23 -0
  532. package/dist/tools/index.d.ts.map +1 -0
  533. package/dist/tools/index.js +965 -0
  534. package/dist/tools/index.js.map +1 -0
  535. package/dist/tools/mcp.d.ts +72 -0
  536. package/dist/tools/mcp.d.ts.map +1 -0
  537. package/dist/tools/mcp.js +422 -0
  538. package/dist/tools/mcp.js.map +1 -0
  539. package/dist/tools/plan.d.ts +18 -0
  540. package/dist/tools/plan.d.ts.map +1 -0
  541. package/dist/tools/plan.js +75 -0
  542. package/dist/tools/plan.js.map +1 -0
  543. package/dist/tools/todo.d.ts +18 -0
  544. package/dist/tools/todo.d.ts.map +1 -0
  545. package/dist/tools/todo.js +153 -0
  546. package/dist/tools/todo.js.map +1 -0
  547. package/dist/tools/web.d.ts +13 -0
  548. package/dist/tools/web.d.ts.map +1 -0
  549. package/dist/tools/web.js +377 -0
  550. package/dist/tools/web.js.map +1 -0
  551. package/dist/ui/box.d.ts +56 -0
  552. package/dist/ui/box.d.ts.map +1 -0
  553. package/dist/ui/box.js +241 -0
  554. package/dist/ui/box.js.map +1 -0
  555. package/dist/ui/command-panel.d.ts +55 -0
  556. package/dist/ui/command-panel.d.ts.map +1 -0
  557. package/dist/ui/command-panel.js +212 -0
  558. package/dist/ui/command-panel.js.map +1 -0
  559. package/dist/ui/file-completion.d.ts +62 -0
  560. package/dist/ui/file-completion.d.ts.map +1 -0
  561. package/dist/ui/file-completion.js +209 -0
  562. package/dist/ui/file-completion.js.map +1 -0
  563. package/dist/ui/markdown.d.ts +12 -0
  564. package/dist/ui/markdown.d.ts.map +1 -0
  565. package/dist/ui/markdown.js +215 -0
  566. package/dist/ui/markdown.js.map +1 -0
  567. package/dist/ui/multiline-input.d.ts +47 -0
  568. package/dist/ui/multiline-input.d.ts.map +1 -0
  569. package/dist/ui/multiline-input.js +94 -0
  570. package/dist/ui/multiline-input.js.map +1 -0
  571. package/dist/ui/status-bar.d.ts +33 -0
  572. package/dist/ui/status-bar.d.ts.map +1 -0
  573. package/dist/ui/status-bar.js +96 -0
  574. package/dist/ui/status-bar.js.map +1 -0
  575. package/dist/ui/stream-markdown.d.ts +35 -0
  576. package/dist/ui/stream-markdown.d.ts.map +1 -0
  577. package/dist/ui/stream-markdown.js +138 -0
  578. package/dist/ui/stream-markdown.js.map +1 -0
  579. package/dist/ui/suggestions.d.ts +35 -0
  580. package/dist/ui/suggestions.d.ts.map +1 -0
  581. package/dist/ui/suggestions.js +118 -0
  582. package/dist/ui/suggestions.js.map +1 -0
  583. package/dist/ui/tool-preview.d.ts +34 -0
  584. package/dist/ui/tool-preview.d.ts.map +1 -0
  585. package/dist/ui/tool-preview.js +128 -0
  586. package/dist/ui/tool-preview.js.map +1 -0
  587. package/package.json +93 -0
@@ -0,0 +1,1686 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.drainStdin = drainStdin;
40
+ const auto_bind_1 = __importDefault(require("auto-bind"));
41
+ const fs_1 = require("fs");
42
+ const noop_js_1 = __importDefault(require("lodash-es/noop.js"));
43
+ const throttle_js_1 = __importDefault(require("lodash-es/throttle.js"));
44
+ const react_1 = __importDefault(require("react"));
45
+ const constants_js_1 = require("react-reconciler/constants.js");
46
+ const signal_exit_1 = require("signal-exit");
47
+ const state_js_1 = require("src/bootstrap/state.js");
48
+ const index_js_1 = require("src/native-ts/yoga-layout/index.js");
49
+ const debug_js_1 = require("src/utils/debug.js");
50
+ const log_js_1 = require("src/utils/log.js");
51
+ const util_1 = require("util");
52
+ const colorize_js_1 = require("./colorize.js");
53
+ const App_js_1 = __importDefault(require("./components/App.js"));
54
+ const constants_js_2 = require("./constants.js");
55
+ const dom = __importStar(require("./dom.js"));
56
+ const keyboard_event_js_1 = require("./events/keyboard-event.js");
57
+ const focus_js_1 = require("./focus.js");
58
+ const frame_js_1 = require("./frame.js");
59
+ const hit_test_js_1 = require("./hit-test.js");
60
+ const instances_js_1 = __importDefault(require("./instances.js"));
61
+ const log_update_js_1 = require("./log-update.js");
62
+ const node_cache_js_1 = require("./node-cache.js");
63
+ const optimizer_js_1 = require("./optimizer.js");
64
+ const output_js_1 = __importDefault(require("./output.js"));
65
+ const reconciler_js_1 = __importStar(require("./reconciler.js"));
66
+ const render_node_to_output_js_1 = __importStar(require("./render-node-to-output.js"));
67
+ const render_to_screen_js_1 = require("./render-to-screen.js");
68
+ const renderer_js_1 = __importDefault(require("./renderer.js"));
69
+ const screen_js_1 = require("./screen.js");
70
+ const searchHighlight_js_1 = require("./searchHighlight.js");
71
+ const selection_js_1 = require("./selection.js");
72
+ const terminal_js_1 = require("./terminal.js");
73
+ const csi_js_1 = require("./termio/csi.js");
74
+ const dec_js_1 = require("./termio/dec.js");
75
+ const osc_js_1 = require("./termio/osc.js");
76
+ const useTerminalNotification_js_1 = require("./useTerminalNotification.js");
77
+ // Alt-screen: renderer.ts sets cursor.visible = !isTTY || screen.height===0,
78
+ // which is always false in alt-screen (TTY + content fills screen).
79
+ // Reusing a frozen object saves 1 allocation per frame.
80
+ const ALT_SCREEN_ANCHOR_CURSOR = Object.freeze({
81
+ x: 0,
82
+ y: 0,
83
+ visible: false
84
+ });
85
+ const CURSOR_HOME_PATCH = Object.freeze({
86
+ type: 'stdout',
87
+ content: csi_js_1.CURSOR_HOME
88
+ });
89
+ const ERASE_THEN_HOME_PATCH = Object.freeze({
90
+ type: 'stdout',
91
+ content: csi_js_1.ERASE_SCREEN + csi_js_1.CURSOR_HOME
92
+ });
93
+ // Cached per-Ink-instance, invalidated on resize. frame.cursor.y for
94
+ // alt-screen is always terminalRows - 1 (renderer.ts).
95
+ function makeAltScreenParkPatch(terminalRows) {
96
+ return Object.freeze({
97
+ type: 'stdout',
98
+ content: (0, csi_js_1.cursorPosition)(terminalRows, 1)
99
+ });
100
+ }
101
+ class Ink {
102
+ constructor(options) {
103
+ this.options = options;
104
+ // Ignore last render after unmounting a tree to prevent empty output before exit
105
+ this.isUnmounted = false;
106
+ this.isPaused = false;
107
+ this.currentNode = null;
108
+ this.lastPoolResetTime = performance.now();
109
+ this.drainTimer = null;
110
+ this.lastYogaCounters = {
111
+ ms: 0,
112
+ visited: 0,
113
+ measured: 0,
114
+ cacheHits: 0,
115
+ live: 0
116
+ };
117
+ // Text selection state (alt-screen only). Owned here so the overlay
118
+ // pass in onRender can read it and App.tsx can update it from mouse
119
+ // events. Public so instances.get() callers can access.
120
+ this.selection = (0, selection_js_1.createSelectionState)();
121
+ // Search highlight query (alt-screen only). Setter below triggers
122
+ // scheduleRender; applySearchHighlight in onRender inverts matching cells.
123
+ this.searchHighlightQuery = '';
124
+ // Position-based highlight. VML scans positions ONCE (via
125
+ // scanElementSubtree, when the target message is mounted), stores them
126
+ // message-relative, sets this for every-frame apply. rowOffset =
127
+ // message's current screen-top. currentIdx = which position is
128
+ // "current" (yellow). null clears. Positions are known upfront —
129
+ // navigation is index arithmetic, no scan-feedback loop.
130
+ this.searchPositions = null;
131
+ // React-land subscribers for selection state changes (useHasSelection).
132
+ // Fired alongside the terminal repaint whenever the selection mutates
133
+ // so UI (e.g. footer hints) can react to selection appearing/clearing.
134
+ this.selectionListeners = new Set();
135
+ // DOM nodes currently under the pointer (mode-1003 motion). Held here
136
+ // so App.tsx's handleMouseEvent is stateless — dispatchHover diffs
137
+ // against this set and mutates it in place.
138
+ this.hoveredNodes = new Set();
139
+ // Set by <AlternateScreen> via setAltScreenActive(). Controls the
140
+ // renderer's cursor.y clamping (keeps cursor in-viewport to avoid
141
+ // LF-induced scroll when screen.height === terminalRows) and gates
142
+ // alt-screen-aware SIGCONT/resize/unmount handling.
143
+ this.altScreenActive = false;
144
+ // Set alongside altScreenActive so SIGCONT resume knows whether to
145
+ // re-enable mouse tracking (not all <AlternateScreen> uses want it).
146
+ this.altScreenMouseTracking = false;
147
+ // True when the previous frame's screen buffer cannot be trusted for
148
+ // blit — selection overlay mutated it, resetFramesForAltScreen()
149
+ // replaced it with blanks, or forceRedraw() reset it to 0×0. Forces
150
+ // one full-render frame; steady-state frames after clear it and regain
151
+ // the blit + narrow-damage fast path.
152
+ this.prevFrameContaminated = false;
153
+ // Set by handleResize: prepend ERASE_SCREEN to the next onRender's patches
154
+ // INSIDE the BSU/ESU block so clear+paint is atomic. Writing ERASE_SCREEN
155
+ // synchronously in handleResize would leave the screen blank for the ~80ms
156
+ // render() takes; deferring into the atomic block means old content stays
157
+ // visible until the new frame is fully ready.
158
+ this.needsEraseBeforePaint = false;
159
+ // Native cursor positioning: a component (via useDeclaredCursor) declares
160
+ // where the terminal cursor should be parked after each frame. Terminal
161
+ // emulators render IME preedit text at the physical cursor position, and
162
+ // screen readers / screen magnifiers track it — so parking at the text
163
+ // input's caret makes CJK input appear inline and lets a11y tools follow.
164
+ this.cursorDeclaration = null;
165
+ // Main-screen: physical cursor position after the declared-cursor move,
166
+ // tracked separately from frame.cursor (which must stay at content-bottom
167
+ // for log-update's relative-move invariants). Alt-screen doesn't need
168
+ // this — every frame begins with CSI H. null = no move emitted last frame.
169
+ this.displayCursor = null;
170
+ this.reportRenderError = (label, error) => {
171
+ const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
172
+ (0, debug_js_1.logForDebugging)(`[Ink:${label}] ${message}`, {
173
+ level: 'error',
174
+ });
175
+ (0, log_js_1.logError)(error);
176
+ try {
177
+ this.options.stderr.write(`[Ink:${label}] ${message}\n`);
178
+ }
179
+ catch {
180
+ // Best-effort fallback only.
181
+ }
182
+ };
183
+ this.handleResume = () => {
184
+ if (!this.options.stdout.isTTY) {
185
+ return;
186
+ }
187
+ // Alt screen: after SIGCONT, content is stale (shell may have written
188
+ // to main screen, switching focus away) and mouse tracking was
189
+ // disabled by handleSuspend.
190
+ if (this.altScreenActive) {
191
+ this.reenterAltScreen();
192
+ return;
193
+ }
194
+ // Main screen: start fresh to prevent clobbering terminal content
195
+ this.frontFrame = (0, frame_js_1.emptyFrame)(this.frontFrame.viewport.height, this.frontFrame.viewport.width, this.stylePool, this.charPool, this.hyperlinkPool);
196
+ this.backFrame = (0, frame_js_1.emptyFrame)(this.backFrame.viewport.height, this.backFrame.viewport.width, this.stylePool, this.charPool, this.hyperlinkPool);
197
+ this.log.reset();
198
+ // Physical cursor position is unknown after the shell took over during
199
+ // suspend. Clear displayCursor so the next frame's cursor preamble
200
+ // doesn't emit a relative move from a stale park position.
201
+ this.displayCursor = null;
202
+ };
203
+ // NOT debounced. A debounce opens a window where stdout.columns is NEW
204
+ // but this.terminalColumns/Yoga are OLD — any scheduleRender during that
205
+ // window (spinner, clock) makes log-update detect a width change and
206
+ // clear the screen, then the debounce fires and clears again (double
207
+ // blank→paint flicker). useVirtualScroll's height scaling already bounds
208
+ // the per-resize cost; synchronous handling keeps dimensions consistent.
209
+ this.handleResize = () => {
210
+ const cols = this.options.stdout.columns || 80;
211
+ const rows = this.options.stdout.rows || 24;
212
+ // Terminals often emit 2+ resize events for one user action (window
213
+ // settling). Same-dimension events are no-ops; skip to avoid redundant
214
+ // frame resets and renders.
215
+ if (cols === this.terminalColumns && rows === this.terminalRows)
216
+ return;
217
+ this.terminalColumns = cols;
218
+ this.terminalRows = rows;
219
+ this.altScreenParkPatch = makeAltScreenParkPatch(this.terminalRows);
220
+ // Alt screen: reset frame buffers so the next render repaints from
221
+ // scratch (prevFrameContaminated → every cell written, wrapped in
222
+ // BSU/ESU — old content stays visible until the new frame swaps
223
+ // atomically). Re-assert mouse tracking (some emulators reset it on
224
+ // resize). Do NOT write ENTER_ALT_SCREEN: iTerm2 treats ?1049h as a
225
+ // buffer clear even when already in alt — that's the blank flicker.
226
+ // Self-healing re-entry (if something kicked us out of alt) is handled
227
+ // by handleResume (SIGCONT) and the sleep-wake detector; resize itself
228
+ // doesn't exit alt-screen. Do NOT write ERASE_SCREEN: render() below
229
+ // can take ~80ms; erasing first leaves the screen blank that whole time.
230
+ if (this.altScreenActive && !this.isPaused && this.options.stdout.isTTY) {
231
+ if (this.altScreenMouseTracking) {
232
+ this.options.stdout.write(dec_js_1.ENABLE_MOUSE_TRACKING);
233
+ }
234
+ this.resetFramesForAltScreen();
235
+ this.needsEraseBeforePaint = true;
236
+ }
237
+ // Re-render the React tree with updated props so the context value changes.
238
+ // React's commit phase will call onComputeLayout() to recalculate yoga layout
239
+ // with the new dimensions, then call onRender() to render the updated frame.
240
+ // We don't call scheduleRender() here because that would render before the
241
+ // layout is updated, causing a mismatch between viewport and content dimensions.
242
+ if (this.currentNode !== null) {
243
+ this.render(this.currentNode);
244
+ }
245
+ };
246
+ this.resolveExitPromise = () => { };
247
+ this.rejectExitPromise = () => { };
248
+ this.unsubscribeExit = () => { };
249
+ /**
250
+ * Re-assert terminal modes after a gap (>5s stdin silence or event-loop
251
+ * stall). Catches tmux detach→attach, ssh reconnect, and laptop
252
+ * sleep/wake — none of which send SIGCONT. The terminal may reset DEC
253
+ * private modes on reconnect; this method restores them.
254
+ *
255
+ * Always re-asserts extended key reporting and mouse tracking. Mouse
256
+ * tracking is idempotent (DEC private mode set-when-set is a no-op). The
257
+ * Kitty keyboard protocol is NOT — CSI >1u is a stack push, so we pop
258
+ * first to keep depth balanced (pop on empty stack is a no-op per spec,
259
+ * so after a terminal reset this still restores depth 0→1). Without the
260
+ * pop, each >5s idle gap adds a stack entry, and the single pop on exit
261
+ * or suspend can't drain them — the shell is left in CSI u mode where
262
+ * Ctrl+C/Ctrl+D leak as escape sequences. The alt-screen
263
+ * re-entry (ERASE_SCREEN + frame reset) is NOT idempotent — it blanks the
264
+ * screen — so it's opt-in via includeAltScreen. The stdin-gap caller fires
265
+ * on ordinary >5s idle + keypress and must not erase; the event-loop stall
266
+ * detector fires on genuine sleep/wake and opts in. tmux attach / ssh
267
+ * reconnect typically send a resize, which already covers alt-screen via
268
+ * handleResize.
269
+ */
270
+ this.reassertTerminalModes = (includeAltScreen = false) => {
271
+ if (!this.options.stdout.isTTY)
272
+ return;
273
+ // Don't touch the terminal during an editor handoff — re-enabling kitty
274
+ // keyboard here would undo enterAlternateScreen's disable and nano would
275
+ // start seeing CSI-u sequences again.
276
+ if (this.isPaused)
277
+ return;
278
+ // Extended keys — re-assert if enabled (App.tsx enables these on
279
+ // allowlisted terminals at raw-mode entry; a terminal reset clears them).
280
+ // Pop-before-push keeps Kitty stack depth at 1 instead of accumulating
281
+ // on each call.
282
+ if ((0, terminal_js_1.supportsExtendedKeys)()) {
283
+ this.options.stdout.write(csi_js_1.DISABLE_KITTY_KEYBOARD + csi_js_1.ENABLE_KITTY_KEYBOARD + csi_js_1.ENABLE_MODIFY_OTHER_KEYS);
284
+ }
285
+ if (!this.altScreenActive)
286
+ return;
287
+ // Mouse tracking — idempotent, safe to re-assert on every stdin gap.
288
+ if (this.altScreenMouseTracking) {
289
+ this.options.stdout.write(dec_js_1.ENABLE_MOUSE_TRACKING);
290
+ }
291
+ // Alt-screen re-entry — destructive (ERASE_SCREEN). Only for callers that
292
+ // have a strong signal the terminal actually dropped mode 1049.
293
+ if (includeAltScreen) {
294
+ this.reenterAltScreen();
295
+ }
296
+ };
297
+ // Methods to properly suspend stdin for external editor usage
298
+ // This is needed to prevent Ink from swallowing keystrokes when an external editor is active
299
+ this.stdinListeners = [];
300
+ this.wasRawMode = false;
301
+ this.setCursorDeclaration = (decl, clearIfNode) => {
302
+ if (decl === null && clearIfNode !== undefined && this.cursorDeclaration?.node !== clearIfNode) {
303
+ return;
304
+ }
305
+ this.cursorDeclaration = decl;
306
+ };
307
+ (0, auto_bind_1.default)(this);
308
+ if (this.options.patchConsole) {
309
+ this.restoreConsole = this.patchConsole();
310
+ this.restoreStderr = this.patchStderr();
311
+ }
312
+ this.terminal = {
313
+ stdout: options.stdout,
314
+ stderr: options.stderr
315
+ };
316
+ this.terminalColumns = options.stdout.columns || 80;
317
+ this.terminalRows = options.stdout.rows || 24;
318
+ this.altScreenParkPatch = makeAltScreenParkPatch(this.terminalRows);
319
+ this.stylePool = new screen_js_1.StylePool();
320
+ this.charPool = new screen_js_1.CharPool();
321
+ this.hyperlinkPool = new screen_js_1.HyperlinkPool();
322
+ this.frontFrame = (0, frame_js_1.emptyFrame)(this.terminalRows, this.terminalColumns, this.stylePool, this.charPool, this.hyperlinkPool);
323
+ this.backFrame = (0, frame_js_1.emptyFrame)(this.terminalRows, this.terminalColumns, this.stylePool, this.charPool, this.hyperlinkPool);
324
+ this.log = new log_update_js_1.LogUpdate({
325
+ isTTY: options.stdout.isTTY || false,
326
+ stylePool: this.stylePool
327
+ });
328
+ // scheduleRender is called from the reconciler's resetAfterCommit, which
329
+ // runs BEFORE React's layout phase (ref attach + useLayoutEffect). Any
330
+ // state set in layout effects — notably the cursorDeclaration from
331
+ // useDeclaredCursor — would lag one commit behind if we rendered
332
+ // synchronously. Deferring to a microtask runs onRender after layout
333
+ // effects have committed, so the native cursor tracks the caret without
334
+ // a one-keystroke lag. Same event-loop tick, so throughput is unchanged.
335
+ // Test env uses onImmediateRender (direct onRender, no throttle) so
336
+ // existing synchronous lastFrame() tests are unaffected.
337
+ const deferredRender = () => queueMicrotask(this.onRender);
338
+ this.scheduleRender = (0, throttle_js_1.default)(deferredRender, constants_js_2.FRAME_INTERVAL_MS, {
339
+ leading: true,
340
+ trailing: true
341
+ });
342
+ // Ignore last render after unmounting a tree to prevent empty output before exit
343
+ this.isUnmounted = false;
344
+ // Unmount when process exits
345
+ this.unsubscribeExit = (0, signal_exit_1.onExit)(this.unmount, {
346
+ alwaysLast: false
347
+ });
348
+ if (options.stdout.isTTY) {
349
+ options.stdout.on('resize', this.handleResize);
350
+ process.on('SIGCONT', this.handleResume);
351
+ this.unsubscribeTTYHandlers = () => {
352
+ options.stdout.off('resize', this.handleResize);
353
+ process.off('SIGCONT', this.handleResume);
354
+ };
355
+ }
356
+ this.rootNode = dom.createNode('ink-root');
357
+ this.focusManager = new focus_js_1.FocusManager((target, event) => reconciler_js_1.dispatcher.dispatchDiscrete(target, event));
358
+ this.rootNode.focusManager = this.focusManager;
359
+ this.renderer = (0, renderer_js_1.default)(this.rootNode, this.stylePool);
360
+ this.rootNode.onRender = this.scheduleRender;
361
+ this.rootNode.onImmediateRender = this.onRender;
362
+ this.rootNode.onComputeLayout = () => {
363
+ // Calculate layout during React's commit phase so useLayoutEffect hooks
364
+ // have access to fresh layout data
365
+ // Guard against accessing freed Yoga nodes after unmount
366
+ if (this.isUnmounted) {
367
+ return;
368
+ }
369
+ if (this.rootNode.yogaNode) {
370
+ const t0 = performance.now();
371
+ this.rootNode.yogaNode.setWidth(this.terminalColumns);
372
+ this.rootNode.yogaNode.calculateLayout(this.terminalColumns);
373
+ const ms = performance.now() - t0;
374
+ (0, reconciler_js_1.recordYogaMs)(ms);
375
+ const c = (0, index_js_1.getYogaCounters)();
376
+ this.lastYogaCounters = {
377
+ ms,
378
+ ...c
379
+ };
380
+ }
381
+ };
382
+ // @ts-expect-error @types/react-reconciler@0.32.3 declares 11 args with transitionCallbacks,
383
+ // but react-reconciler 0.33.0 source only accepts 10 args (no transitionCallbacks)
384
+ this.container = reconciler_js_1.default.createContainer(this.rootNode, constants_js_1.LegacyRoot, null, false, null, 'id', noop_js_1.default,
385
+ // onUncaughtError
386
+ error => {
387
+ this.reportRenderError('uncaught', error);
388
+ },
389
+ // onCaughtError
390
+ error => {
391
+ this.reportRenderError('caught', error);
392
+ },
393
+ // onRecoverableError
394
+ error => {
395
+ this.reportRenderError('recoverable', error);
396
+ });
397
+ if ("production" === 'development') {
398
+ reconciler_js_1.default.injectIntoDevTools({
399
+ bundleType: 0,
400
+ // Reporting React DOM's version, not Ink's
401
+ // See https://github.com/facebook/react/issues/16666#issuecomment-532639905
402
+ version: '16.13.1',
403
+ rendererPackageName: 'ink'
404
+ });
405
+ }
406
+ }
407
+ /**
408
+ * Pause Ink and hand the terminal over to an external TUI (e.g. git
409
+ * commit editor). In non-fullscreen mode this enters the alt screen;
410
+ * in fullscreen mode we're already in alt so we just clear it.
411
+ * Call `exitAlternateScreen()` when done to restore Ink.
412
+ */
413
+ enterAlternateScreen() {
414
+ this.pause();
415
+ this.suspendStdin();
416
+ this.options.stdout.write(
417
+ // Disable extended key reporting first — editors that don't speak
418
+ // CSI-u (e.g. nano) show "Unknown sequence" for every Ctrl-<key> if
419
+ // kitty/modifyOtherKeys stays active. exitAlternateScreen re-enables.
420
+ csi_js_1.DISABLE_KITTY_KEYBOARD + csi_js_1.DISABLE_MODIFY_OTHER_KEYS + (this.altScreenMouseTracking ? dec_js_1.DISABLE_MOUSE_TRACKING : '') + (
421
+ // disable mouse (no-op if off)
422
+ this.altScreenActive ? '' : '\x1b[?1049h') +
423
+ // enter alt (already in alt if fullscreen)
424
+ '\x1b[?1004l' +
425
+ // disable focus reporting
426
+ '\x1b[0m' +
427
+ // reset attributes
428
+ '\x1b[?25h' +
429
+ // show cursor
430
+ '\x1b[2J' +
431
+ // clear screen
432
+ '\x1b[H' // cursor home
433
+ );
434
+ }
435
+ /**
436
+ * Resume Ink after an external TUI handoff with a full repaint.
437
+ * In non-fullscreen mode this exits the alt screen back to main;
438
+ * in fullscreen mode we re-enter alt and clear + repaint.
439
+ *
440
+ * The re-enter matters: terminal editors (vim, nano, less) write
441
+ * smcup/rmcup (?1049h/?1049l), so even though we started in alt,
442
+ * the editor's rmcup on exit drops us to main screen. Without
443
+ * re-entering, the 2J below wipes the user's main-screen scrollback
444
+ * and subsequent renders land in main — native terminal scroll
445
+ * returns, fullscreen scroll is dead.
446
+ */
447
+ exitAlternateScreen() {
448
+ this.options.stdout.write((this.altScreenActive ? dec_js_1.ENTER_ALT_SCREEN : '') +
449
+ // re-enter alt — vim's rmcup dropped us to main
450
+ '\x1b[2J' +
451
+ // clear screen (now alt if fullscreen)
452
+ '\x1b[H' + (
453
+ // cursor home
454
+ this.altScreenMouseTracking ? dec_js_1.ENABLE_MOUSE_TRACKING : '') + (
455
+ // re-enable mouse (skip if CLAUDE_CODE_DISABLE_MOUSE)
456
+ this.altScreenActive ? '' : '\x1b[?1049l') +
457
+ // exit alt (non-fullscreen only)
458
+ '\x1b[?25l' // hide cursor (Ink manages)
459
+ );
460
+ this.resumeStdin();
461
+ if (this.altScreenActive) {
462
+ this.resetFramesForAltScreen();
463
+ }
464
+ else {
465
+ this.repaint();
466
+ }
467
+ this.resume();
468
+ // Re-enable focus reporting and extended key reporting — terminal
469
+ // editors (vim, nano, etc.) write their own modifyOtherKeys level on
470
+ // entry and reset it on exit, leaving us unable to distinguish
471
+ // ctrl+shift+<letter> from ctrl+<letter>. Pop-before-push keeps the
472
+ // Kitty stack balanced (a well-behaved editor restores our entry, so
473
+ // without the pop we'd accumulate depth on each editor round-trip).
474
+ this.options.stdout.write('\x1b[?1004h' + ((0, terminal_js_1.supportsExtendedKeys)() ? csi_js_1.DISABLE_KITTY_KEYBOARD + csi_js_1.ENABLE_KITTY_KEYBOARD + csi_js_1.ENABLE_MODIFY_OTHER_KEYS : ''));
475
+ }
476
+ onRender() {
477
+ if (this.isUnmounted || this.isPaused) {
478
+ return;
479
+ }
480
+ // Entering a render cancels any pending drain tick — this render will
481
+ // handle the drain (and re-schedule below if needed). Prevents a
482
+ // wheel-event-triggered render AND a drain-timer render both firing.
483
+ if (this.drainTimer !== null) {
484
+ clearTimeout(this.drainTimer);
485
+ this.drainTimer = null;
486
+ }
487
+ // Flush deferred interaction-time update before rendering so we call
488
+ // Date.now() at most once per frame instead of once per keypress.
489
+ // Done before the render to avoid dirtying state that would trigger
490
+ // an extra React re-render cycle.
491
+ (0, state_js_1.flushInteractionTime)();
492
+ const renderStart = performance.now();
493
+ const terminalWidth = this.options.stdout.columns || 80;
494
+ const terminalRows = this.options.stdout.rows || 24;
495
+ const frame = this.renderer({
496
+ frontFrame: this.frontFrame,
497
+ backFrame: this.backFrame,
498
+ isTTY: this.options.stdout.isTTY,
499
+ terminalWidth,
500
+ terminalRows,
501
+ altScreen: this.altScreenActive,
502
+ prevFrameContaminated: this.prevFrameContaminated
503
+ });
504
+ const rendererMs = performance.now() - renderStart;
505
+ // Sticky/auto-follow scrolled the ScrollBox this frame. Translate the
506
+ // selection by the same delta so the highlight stays anchored to the
507
+ // TEXT (native terminal behavior — the selection walks up the screen
508
+ // as content scrolls, eventually clipping at the top). frontFrame
509
+ // still holds the PREVIOUS frame's screen (swap is at ~500 below), so
510
+ // captureScrolledRows reads the rows that are about to scroll out
511
+ // before they're overwritten — the text stays copyable until the
512
+ // selection scrolls entirely off. During drag, focus tracks the mouse
513
+ // (screen-local) so only anchor shifts — selection grows toward the
514
+ // mouse as the anchor walks up. After release, both ends are text-
515
+ // anchored and move as a block.
516
+ const follow = (0, render_node_to_output_js_1.consumeFollowScroll)();
517
+ if (follow && this.selection.anchor &&
518
+ // Only translate if the selection is ON scrollbox content. Selections
519
+ // in the footer/prompt/StickyPromptHeader are on static text — the
520
+ // scroll doesn't move what's under them. Without this guard, a
521
+ // footer selection would be shifted by -delta then clamped to
522
+ // viewportBottom, teleporting it into the scrollbox. Mirror the
523
+ // bounds check the deleted check() in ScrollKeybindingHandler had.
524
+ this.selection.anchor.row >= follow.viewportTop && this.selection.anchor.row <= follow.viewportBottom) {
525
+ const { delta, viewportTop, viewportBottom } = follow;
526
+ // captureScrolledRows and shift* are a pair: capture grabs rows about
527
+ // to scroll off, shift moves the selection endpoint so the same rows
528
+ // won't intersect again next frame. Capturing without shifting leaves
529
+ // the endpoint in place, so the SAME viewport rows re-intersect every
530
+ // frame and scrolledOffAbove grows without bound — getSelectedText
531
+ // then returns ever-growing text on each re-copy. Keep capture inside
532
+ // each shift branch so the pairing can't be broken by a new guard.
533
+ if (this.selection.isDragging) {
534
+ if ((0, selection_js_1.hasSelection)(this.selection)) {
535
+ (0, selection_js_1.captureScrolledRows)(this.selection, this.frontFrame.screen, viewportTop, viewportTop + delta - 1, 'above');
536
+ }
537
+ (0, selection_js_1.shiftAnchor)(this.selection, -delta, viewportTop, viewportBottom);
538
+ }
539
+ else if (
540
+ // Flag-3 guard: the anchor check above only proves ONE endpoint is
541
+ // on scrollbox content. A drag from row 3 (scrollbox) into the
542
+ // footer at row 6, then release, leaves focus outside the viewport
543
+ // — shiftSelectionForFollow would clamp it to viewportBottom,
544
+ // teleporting the highlight from static footer into the scrollbox.
545
+ // Symmetric check: require BOTH ends inside to translate. A
546
+ // straddling selection falls through to NEITHER shift NOR capture:
547
+ // the footer endpoint pins the selection, text scrolls away under
548
+ // the highlight, and getSelectedText reads the CURRENT screen
549
+ // contents — no accumulation. Dragging branch doesn't need this:
550
+ // shiftAnchor ignores focus, and the anchor DOES shift (so capture
551
+ // is correct there even when focus is in the footer).
552
+ !this.selection.focus || this.selection.focus.row >= viewportTop && this.selection.focus.row <= viewportBottom) {
553
+ if ((0, selection_js_1.hasSelection)(this.selection)) {
554
+ (0, selection_js_1.captureScrolledRows)(this.selection, this.frontFrame.screen, viewportTop, viewportTop + delta - 1, 'above');
555
+ }
556
+ const cleared = (0, selection_js_1.shiftSelectionForFollow)(this.selection, -delta, viewportTop, viewportBottom);
557
+ // Auto-clear (both ends overshot minRow) must notify React-land
558
+ // so useHasSelection re-renders and the footer copy/escape hint
559
+ // disappears. notifySelectionChange() would recurse into onRender;
560
+ // fire the listeners directly — they schedule a React update for
561
+ // LATER, they don't re-enter this frame.
562
+ if (cleared)
563
+ for (const cb of this.selectionListeners)
564
+ cb();
565
+ }
566
+ }
567
+ // Selection overlay: invert cell styles in the screen buffer itself,
568
+ // so the diff picks up selection as ordinary cell changes and
569
+ // LogUpdate remains a pure diff engine.
570
+ //
571
+ // Full-screen damage (PR #20120) is a correctness backstop for the
572
+ // sibling-resize bleed: when flexbox siblings resize between frames
573
+ // (spinner appears → bottom grows → scrollbox shrinks), the
574
+ // cached-clear + clip-and-cull + setCellAt damage union can miss
575
+ // transition cells at the boundary. But that only happens when layout
576
+ // actually SHIFTS — didLayoutShift() tracks exactly this (any node's
577
+ // cached yoga position/size differs from current, or a child was
578
+ // removed). Steady-state frames (spinner rotate, clock tick, text
579
+ // stream into fixed-height box) don't shift layout, so normal damage
580
+ // bounds are correct and diffEach only compares the damaged region.
581
+ //
582
+ // Selection also requires full damage: overlay writes via setCellStyleId
583
+ // which doesn't track damage, and prev-frame overlay cells need to be
584
+ // compared when selection moves/clears. prevFrameContaminated covers
585
+ // the frame-after-selection-clears case.
586
+ let selActive = false;
587
+ let hlActive = false;
588
+ if (this.altScreenActive) {
589
+ selActive = (0, selection_js_1.hasSelection)(this.selection);
590
+ if (selActive) {
591
+ (0, selection_js_1.applySelectionOverlay)(frame.screen, this.selection, this.stylePool);
592
+ }
593
+ // Scan-highlight: inverse on ALL visible matches (less/vim style).
594
+ // Position-highlight (below) overlays CURRENT (yellow) on top.
595
+ hlActive = (0, searchHighlight_js_1.applySearchHighlight)(frame.screen, this.searchHighlightQuery, this.stylePool);
596
+ // Position-based CURRENT: write yellow at positions[currentIdx] +
597
+ // rowOffset. No scanning — positions came from a prior scan when
598
+ // the message first mounted. Message-relative + rowOffset = screen.
599
+ if (this.searchPositions) {
600
+ const sp = this.searchPositions;
601
+ const posApplied = (0, render_to_screen_js_1.applyPositionedHighlight)(frame.screen, this.stylePool, sp.positions, sp.rowOffset, sp.currentIdx);
602
+ hlActive = hlActive || posApplied;
603
+ }
604
+ }
605
+ // Full-damage backstop: applies on BOTH alt-screen and main-screen.
606
+ // Layout shifts (spinner appears, status line resizes) can leave stale
607
+ // cells at sibling boundaries that per-node damage tracking misses.
608
+ // Selection/highlight overlays write via setCellStyleId which doesn't
609
+ // track damage. prevFrameContaminated covers the cleanup frame.
610
+ if ((0, render_node_to_output_js_1.didLayoutShift)() || selActive || hlActive || this.prevFrameContaminated) {
611
+ frame.screen.damage = {
612
+ x: 0,
613
+ y: 0,
614
+ width: frame.screen.width,
615
+ height: frame.screen.height
616
+ };
617
+ }
618
+ // Alt-screen: anchor the physical cursor to (0,0) before every diff.
619
+ // All cursor moves in log-update are RELATIVE to prev.cursor; if tmux
620
+ // (or any emulator) perturbs the physical cursor out-of-band (status
621
+ // bar refresh, pane redraw, Cmd+K wipe), the relative moves drift and
622
+ // content creeps up 1 row/frame. CSI H resets the physical cursor;
623
+ // passing prev.cursor=(0,0) makes the diff compute from the same spot.
624
+ // Self-healing against any external cursor manipulation. Main-screen
625
+ // can't do this — cursor.y tracks scrollback rows CSI H can't reach.
626
+ // The CSI H write is deferred until after the diff is computed so we
627
+ // can skip it for empty diffs (no writes → physical cursor unused).
628
+ let prevFrame = this.frontFrame;
629
+ if (this.altScreenActive) {
630
+ prevFrame = {
631
+ ...this.frontFrame,
632
+ cursor: ALT_SCREEN_ANCHOR_CURSOR
633
+ };
634
+ }
635
+ const tDiff = performance.now();
636
+ const rewriteMainScreen = !this.altScreenActive && (0, terminal_js_1.shouldUseMainScreenRewrite)();
637
+ const diff = this.log.render(prevFrame, frame, this.altScreenActive,
638
+ // DECSTBM needs BSU/ESU atomicity — without it the outer terminal
639
+ // renders the scrolled-but-not-yet-repainted intermediate state.
640
+ // tmux is the main case (re-emits DECSTBM with its own timing and
641
+ // doesn't implement DEC 2026, so SYNC_OUTPUT_SUPPORTED is false).
642
+ terminal_js_1.SYNC_OUTPUT_SUPPORTED, rewriteMainScreen);
643
+ const diffMs = performance.now() - tDiff;
644
+ // Swap buffers
645
+ this.backFrame = this.frontFrame;
646
+ this.frontFrame = frame;
647
+ // Periodically reset char/hyperlink pools to prevent unbounded growth
648
+ // during long sessions. 5 minutes is infrequent enough that the O(cells)
649
+ // migration cost is negligible. Reuses renderStart to avoid extra clock call.
650
+ if (renderStart - this.lastPoolResetTime > 5 * 60 * 1000) {
651
+ this.resetPools();
652
+ this.lastPoolResetTime = renderStart;
653
+ }
654
+ const flickers = [];
655
+ for (const patch of diff) {
656
+ if (patch.type === 'clearTerminal') {
657
+ flickers.push({
658
+ desiredHeight: frame.screen.height,
659
+ availableHeight: frame.viewport.height,
660
+ reason: patch.reason
661
+ });
662
+ if ((0, reconciler_js_1.isDebugRepaintsEnabled)() && patch.debug) {
663
+ const chain = dom.findOwnerChainAtRow(this.rootNode, patch.debug.triggerY);
664
+ (0, debug_js_1.logForDebugging)(`[REPAINT] full reset · ${patch.reason} · row ${patch.debug.triggerY}\n` + ` prev: "${patch.debug.prevLine}"\n` + ` next: "${patch.debug.nextLine}"\n` + ` culprit: ${chain.length ? chain.join(' < ') : '(no owner chain captured)'}`, {
665
+ level: 'warn'
666
+ });
667
+ }
668
+ }
669
+ }
670
+ const tOptimize = performance.now();
671
+ const optimized = (0, optimizer_js_1.optimize)(diff);
672
+ const optimizeMs = performance.now() - tOptimize;
673
+ const hasDiff = optimized.length > 0;
674
+ if (this.altScreenActive && hasDiff) {
675
+ // Prepend CSI H to anchor the physical cursor to (0,0) so
676
+ // log-update's relative moves compute from a known spot (self-healing
677
+ // against out-of-band cursor drift, see the ALT_SCREEN_ANCHOR_CURSOR
678
+ // comment above). Append CSI row;1 H to park the cursor at the bottom
679
+ // row (where the prompt input is) — without this, the cursor ends
680
+ // wherever the last diff write landed (a different row every frame),
681
+ // making iTerm2's cursor guide flicker as it chases the cursor.
682
+ // BSU/ESU protects content atomicity but iTerm2's guide tracks cursor
683
+ // position independently. Parking at bottom (not 0,0) keeps the guide
684
+ // where the user's attention is.
685
+ //
686
+ // After resize, prepend ERASE_SCREEN too. The diff only writes cells
687
+ // that changed; cells where new=blank and prev-buffer=blank get skipped
688
+ // — but the physical terminal still has stale content there (shorter
689
+ // lines at new width leave old-width text tails visible). ERASE inside
690
+ // BSU/ESU is atomic: old content stays visible until the whole
691
+ // erase+paint lands, then swaps in one go. Writing ERASE_SCREEN
692
+ // synchronously in handleResize would blank the screen for the ~80ms
693
+ // render() takes.
694
+ if (this.needsEraseBeforePaint) {
695
+ this.needsEraseBeforePaint = false;
696
+ optimized.unshift(ERASE_THEN_HOME_PATCH);
697
+ }
698
+ else {
699
+ optimized.unshift(CURSOR_HOME_PATCH);
700
+ }
701
+ optimized.push(this.altScreenParkPatch);
702
+ }
703
+ // Native cursor positioning: park the terminal cursor at the declared
704
+ // position so IME preedit text renders inline and screen readers /
705
+ // magnifiers can follow the input. nodeCache holds the absolute screen
706
+ // rect populated by renderNodeToOutput this frame (including scrollTop
707
+ // translation) — if the declared node didn't render (stale declaration
708
+ // after remount, or scrolled out of view), it won't be in the cache
709
+ // and no move is emitted.
710
+ const decl = this.cursorDeclaration;
711
+ const rect = decl !== null ? node_cache_js_1.nodeCache.get(decl.node) : undefined;
712
+ const target = decl !== null && rect !== undefined ? {
713
+ x: rect.x + decl.relativeX,
714
+ y: rect.y + decl.relativeY
715
+ } : null;
716
+ const parked = this.displayCursor;
717
+ // Preserve the empty-diff zero-write fast path: skip all cursor writes
718
+ // when nothing rendered AND the park target is unchanged.
719
+ const targetMoved = target !== null && (parked === null || parked.x !== target.x || parked.y !== target.y);
720
+ if (hasDiff || targetMoved || target === null && parked !== null) {
721
+ // Main-screen preamble: log-update's relative moves assume the
722
+ // physical cursor is at prevFrame.cursor. If last frame parked it
723
+ // elsewhere, move back before the diff runs. Alt-screen's CSI H
724
+ // already resets to (0,0) so no preamble needed.
725
+ if (parked !== null && !this.altScreenActive && hasDiff) {
726
+ const pdx = prevFrame.cursor.x - parked.x;
727
+ const pdy = prevFrame.cursor.y - parked.y;
728
+ if (pdx !== 0 || pdy !== 0) {
729
+ optimized.unshift({
730
+ type: 'stdout',
731
+ content: (0, csi_js_1.cursorMove)(pdx, pdy)
732
+ });
733
+ }
734
+ }
735
+ if (target !== null) {
736
+ if (this.altScreenActive) {
737
+ // Absolute CUP (1-indexed); next frame's CSI H resets regardless.
738
+ // Emitted after altScreenParkPatch so the declared position wins.
739
+ const row = Math.min(Math.max(target.y + 1, 1), terminalRows);
740
+ const col = Math.min(Math.max(target.x + 1, 1), terminalWidth);
741
+ optimized.push({
742
+ type: 'stdout',
743
+ content: (0, csi_js_1.cursorPosition)(row, col)
744
+ });
745
+ }
746
+ else {
747
+ // After the diff (or preamble), cursor is at frame.cursor. If no
748
+ // diff AND previously parked, it's still at the old park position
749
+ // (log-update wrote nothing). Otherwise it's at frame.cursor.
750
+ const from = !hasDiff && parked !== null ? parked : {
751
+ x: frame.cursor.x,
752
+ y: frame.cursor.y
753
+ };
754
+ const dx = target.x - from.x;
755
+ const dy = target.y - from.y;
756
+ if (dx !== 0 || dy !== 0) {
757
+ optimized.push({
758
+ type: 'stdout',
759
+ content: (0, csi_js_1.cursorMove)(dx, dy)
760
+ });
761
+ }
762
+ }
763
+ this.displayCursor = target;
764
+ }
765
+ else {
766
+ // Declaration cleared (input blur, unmount). Restore physical cursor
767
+ // to frame.cursor before forgetting the park position — otherwise
768
+ // displayCursor=null lies about where the cursor is, and the NEXT
769
+ // frame's preamble (or log-update's relative moves) computes from a
770
+ // wrong spot. The preamble above handles hasDiff; this handles
771
+ // !hasDiff (e.g. accessibility mode where blur doesn't change
772
+ // renderedValue since invert is identity).
773
+ if (parked !== null && !this.altScreenActive && !hasDiff) {
774
+ const rdx = frame.cursor.x - parked.x;
775
+ const rdy = frame.cursor.y - parked.y;
776
+ if (rdx !== 0 || rdy !== 0) {
777
+ optimized.push({
778
+ type: 'stdout',
779
+ content: (0, csi_js_1.cursorMove)(rdx, rdy)
780
+ });
781
+ }
782
+ }
783
+ this.displayCursor = null;
784
+ }
785
+ }
786
+ const tWrite = performance.now();
787
+ const skipSyncMarkers = this.altScreenActive ? !terminal_js_1.SYNC_OUTPUT_SUPPORTED : rewriteMainScreen || (0, terminal_js_1.shouldSkipMainScreenSyncMarkers)();
788
+ (0, terminal_js_1.writeDiffToTerminal)(this.terminal, optimized, skipSyncMarkers);
789
+ const writeMs = performance.now() - tWrite;
790
+ // Update blit safety for the NEXT frame. The frame just rendered
791
+ // becomes frontFrame (= next frame's prevScreen). If we applied the
792
+ // selection overlay, that buffer has inverted cells. selActive/hlActive
793
+ // are only ever true in alt-screen; in main-screen this is false→false.
794
+ this.prevFrameContaminated = selActive || hlActive;
795
+ // A ScrollBox has pendingScrollDelta left to drain — schedule the next
796
+ // frame. MUST NOT call this.scheduleRender() here: we're inside a
797
+ // trailing-edge throttle invocation, timerId is undefined, and lodash's
798
+ // debounce sees timeSinceLastCall >= wait (last call was at the start
799
+ // of this window) → leadingEdge fires IMMEDIATELY → double render ~0.1ms
800
+ // apart → jank. Use a plain timeout. If a wheel event arrives first,
801
+ // its scheduleRender path fires a render which clears this timer at
802
+ // the top of onRender — no double.
803
+ //
804
+ // Drain frames are cheap (DECSTBM + ~10 patches, ~200 bytes) so run at
805
+ // quarter interval (~250fps, setTimeout practical floor) for max scroll
806
+ // speed. Regular renders stay at FRAME_INTERVAL_MS via the throttle.
807
+ if (frame.scrollDrainPending) {
808
+ this.drainTimer = setTimeout(() => this.onRender(), constants_js_2.FRAME_INTERVAL_MS >> 2);
809
+ }
810
+ const yogaMs = (0, reconciler_js_1.getLastYogaMs)();
811
+ const commitMs = (0, reconciler_js_1.getLastCommitMs)();
812
+ const yc = this.lastYogaCounters;
813
+ // Reset so drain-only frames (no React commit) don't repeat stale values.
814
+ (0, reconciler_js_1.resetProfileCounters)();
815
+ this.lastYogaCounters = {
816
+ ms: 0,
817
+ visited: 0,
818
+ measured: 0,
819
+ cacheHits: 0,
820
+ live: 0
821
+ };
822
+ this.options.onFrame?.({
823
+ durationMs: performance.now() - renderStart,
824
+ phases: {
825
+ renderer: rendererMs,
826
+ diff: diffMs,
827
+ optimize: optimizeMs,
828
+ write: writeMs,
829
+ patches: diff.length,
830
+ yoga: yogaMs,
831
+ commit: commitMs,
832
+ yogaVisited: yc.visited,
833
+ yogaMeasured: yc.measured,
834
+ yogaCacheHits: yc.cacheHits,
835
+ yogaLive: yc.live
836
+ },
837
+ flickers
838
+ });
839
+ }
840
+ pause() {
841
+ // Flush pending React updates and render before pausing.
842
+ // @ts-expect-error flushSyncFromReconciler exists in react-reconciler 0.31 but not in @types/react-reconciler
843
+ reconciler_js_1.default.flushSyncFromReconciler();
844
+ this.onRender();
845
+ this.isPaused = true;
846
+ }
847
+ resume() {
848
+ this.isPaused = false;
849
+ this.onRender();
850
+ }
851
+ /**
852
+ * Reset frame buffers so the next render writes the full screen from scratch.
853
+ * Call this before resume() when the terminal content has been corrupted by
854
+ * an external process (e.g. tmux, shell, full-screen TUI).
855
+ */
856
+ repaint() {
857
+ this.frontFrame = (0, frame_js_1.emptyFrame)(this.frontFrame.viewport.height, this.frontFrame.viewport.width, this.stylePool, this.charPool, this.hyperlinkPool);
858
+ this.backFrame = (0, frame_js_1.emptyFrame)(this.backFrame.viewport.height, this.backFrame.viewport.width, this.stylePool, this.charPool, this.hyperlinkPool);
859
+ this.log.reset();
860
+ // Physical cursor position is unknown after external terminal corruption.
861
+ // Clear displayCursor so the cursor preamble doesn't emit a stale
862
+ // relative move from where we last parked it.
863
+ this.displayCursor = null;
864
+ }
865
+ /**
866
+ * Clear the physical terminal and force a full redraw.
867
+ *
868
+ * The traditional readline ctrl+l — clears the visible screen and
869
+ * redraws the current content. Also the recovery path when the terminal
870
+ * was cleared externally (macOS Cmd+K) and Ink's diff engine thinks
871
+ * unchanged cells don't need repainting. Scrollback is preserved.
872
+ */
873
+ forceRedraw() {
874
+ if (!this.options.stdout.isTTY || this.isUnmounted || this.isPaused)
875
+ return;
876
+ this.options.stdout.write(csi_js_1.ERASE_SCREEN + csi_js_1.CURSOR_HOME);
877
+ if (this.altScreenActive) {
878
+ this.resetFramesForAltScreen();
879
+ }
880
+ else {
881
+ this.repaint();
882
+ // repaint() resets frontFrame to 0×0. Without this flag the next
883
+ // frame's blit optimization copies from that empty screen and the
884
+ // diff sees no content. onRender resets the flag at frame end.
885
+ this.prevFrameContaminated = true;
886
+ }
887
+ this.onRender();
888
+ }
889
+ /**
890
+ * Mark the previous frame as untrustworthy for blit, forcing the next
891
+ * render to do a full-damage diff instead of the per-node fast path.
892
+ *
893
+ * Lighter than forceRedraw() — no screen clear, no extra write. Call
894
+ * from a useLayoutEffect cleanup when unmounting a tall overlay: the
895
+ * blit fast path can copy stale cells from the overlay frame into rows
896
+ * the shrunken layout no longer reaches, leaving a ghost title/divider.
897
+ * onRender resets the flag at frame end so it's one-shot.
898
+ */
899
+ invalidatePrevFrame() {
900
+ this.prevFrameContaminated = true;
901
+ }
902
+ /**
903
+ * Called by the <AlternateScreen> component on mount/unmount.
904
+ * Controls cursor.y clamping in the renderer and gates alt-screen-aware
905
+ * behavior in SIGCONT/resize/unmount handlers. Repaints on change so
906
+ * the first alt-screen frame (and first main-screen frame on exit) is
907
+ * a full redraw with no stale diff state.
908
+ */
909
+ setAltScreenActive(active, mouseTracking = false) {
910
+ if (this.altScreenActive === active)
911
+ return;
912
+ this.altScreenActive = active;
913
+ this.altScreenMouseTracking = active && mouseTracking;
914
+ if (active) {
915
+ this.resetFramesForAltScreen();
916
+ }
917
+ else {
918
+ this.repaint();
919
+ }
920
+ }
921
+ get isAltScreenActive() {
922
+ return this.altScreenActive;
923
+ }
924
+ /**
925
+ * Mark this instance as unmounted so future unmount() calls early-return.
926
+ * Called by gracefulShutdown's cleanupTerminalModes() after it has sent
927
+ * EXIT_ALT_SCREEN but before the remaining terminal-reset sequences.
928
+ * Without this, signal-exit's deferred ink.unmount() (triggered by
929
+ * process.exit()) runs the full unmount path: onRender() + writeSync
930
+ * cleanup block + updateContainerSync → AlternateScreen unmount cleanup.
931
+ * The result is 2-3 redundant EXIT_ALT_SCREEN sequences landing on the
932
+ * main screen AFTER printResumeHint(), which tmux (at least) interprets
933
+ * as restoring the saved cursor position — clobbering the resume hint.
934
+ */
935
+ detachForShutdown() {
936
+ this.isUnmounted = true;
937
+ // Cancel any pending throttled render so it doesn't fire between
938
+ // cleanupTerminalModes() and process.exit() and write to main screen.
939
+ this.scheduleRender.cancel?.();
940
+ // Restore stdin from raw mode. unmount() used to do this via React
941
+ // unmount (App.componentWillUnmount → handleSetRawMode(false)) but we're
942
+ // short-circuiting that path. Must use this.options.stdin — NOT
943
+ // process.stdin — because getStdinOverride() may have opened /dev/tty
944
+ // when stdin is piped.
945
+ const stdin = this.options.stdin;
946
+ this.drainStdin();
947
+ if (stdin.isTTY && stdin.isRaw && stdin.setRawMode) {
948
+ stdin.setRawMode(false);
949
+ }
950
+ }
951
+ /** @see drainStdin */
952
+ drainStdin() {
953
+ drainStdin(this.options.stdin);
954
+ }
955
+ /**
956
+ * Re-enter alt-screen, clear, home, re-enable mouse tracking, and reset
957
+ * frame buffers so the next render repaints from scratch. Self-heal for
958
+ * SIGCONT, resize, and stdin-gap/event-loop-stall (sleep/wake) — any of
959
+ * which can leave the terminal in main-screen mode while altScreenActive
960
+ * stays true. ENTER_ALT_SCREEN is a terminal-side no-op if already in alt.
961
+ */
962
+ reenterAltScreen() {
963
+ this.options.stdout.write(dec_js_1.ENTER_ALT_SCREEN + csi_js_1.ERASE_SCREEN + csi_js_1.CURSOR_HOME + (this.altScreenMouseTracking ? dec_js_1.ENABLE_MOUSE_TRACKING : ''));
964
+ this.resetFramesForAltScreen();
965
+ }
966
+ /**
967
+ * Seed prev/back frames with full-size BLANK screens (rows×cols of empty
968
+ * cells, not 0×0). In alt-screen mode, next.screen.height is always
969
+ * terminalRows; if prev.screen.height is 0 (emptyFrame's default),
970
+ * log-update sees heightDelta > 0 ('growing') and calls renderFrameSlice,
971
+ * whose trailing per-row CR+LF at the last row scrolls the alt screen,
972
+ * permanently desyncing the virtual and physical cursors by 1 row.
973
+ *
974
+ * With a rows×cols blank prev, heightDelta === 0 → standard diffEach
975
+ * → moveCursorTo (CSI cursorMove, no LF, no scroll).
976
+ *
977
+ * viewport.height = rows + 1 matches the renderer's alt-screen output,
978
+ * preventing a spurious resize trigger on the first frame. cursor.y = 0
979
+ * matches the physical cursor after ENTER_ALT_SCREEN + CSI H (home).
980
+ */
981
+ resetFramesForAltScreen() {
982
+ const rows = this.terminalRows;
983
+ const cols = this.terminalColumns;
984
+ const blank = () => ({
985
+ screen: (0, screen_js_1.createScreen)(cols, rows, this.stylePool, this.charPool, this.hyperlinkPool),
986
+ viewport: {
987
+ width: cols,
988
+ height: rows + 1
989
+ },
990
+ cursor: {
991
+ x: 0,
992
+ y: 0,
993
+ visible: true
994
+ }
995
+ });
996
+ this.frontFrame = blank();
997
+ this.backFrame = blank();
998
+ this.log.reset();
999
+ // Defense-in-depth: alt-screen skips the cursor preamble anyway (CSI H
1000
+ // resets), but a stale displayCursor would be misleading if we later
1001
+ // exit to main-screen without an intervening render.
1002
+ this.displayCursor = null;
1003
+ // Fresh frontFrame is blank rows×cols — blitting from it would copy
1004
+ // blanks over content. Next alt-screen frame must full-render.
1005
+ this.prevFrameContaminated = true;
1006
+ }
1007
+ /**
1008
+ * Copy the current selection to the clipboard without clearing the
1009
+ * highlight. Matches iTerm2's copy-on-select behavior where the selected
1010
+ * region stays visible after the automatic copy.
1011
+ */
1012
+ copySelectionNoClear() {
1013
+ if (!(0, selection_js_1.hasSelection)(this.selection))
1014
+ return '';
1015
+ const text = (0, selection_js_1.getSelectedText)(this.selection, this.frontFrame.screen);
1016
+ if (text) {
1017
+ // Raw OSC 52, or DCS-passthrough-wrapped OSC 52 inside tmux (tmux
1018
+ // drops it silently unless allow-passthrough is on — no regression).
1019
+ void (0, osc_js_1.setClipboard)(text).then(raw => {
1020
+ if (raw)
1021
+ this.options.stdout.write(raw);
1022
+ });
1023
+ }
1024
+ return text;
1025
+ }
1026
+ /**
1027
+ * Copy the current text selection to the system clipboard via OSC 52
1028
+ * and clear the selection. Returns the copied text (empty if no selection).
1029
+ */
1030
+ copySelection() {
1031
+ if (!(0, selection_js_1.hasSelection)(this.selection))
1032
+ return '';
1033
+ const text = this.copySelectionNoClear();
1034
+ (0, selection_js_1.clearSelection)(this.selection);
1035
+ this.notifySelectionChange();
1036
+ return text;
1037
+ }
1038
+ /** Clear the current text selection without copying. */
1039
+ clearTextSelection() {
1040
+ if (!(0, selection_js_1.hasSelection)(this.selection))
1041
+ return;
1042
+ (0, selection_js_1.clearSelection)(this.selection);
1043
+ this.notifySelectionChange();
1044
+ }
1045
+ /**
1046
+ * Set the search highlight query. Non-empty → all visible occurrences
1047
+ * are inverted (SGR 7) on the next frame; first one also underlined.
1048
+ * Empty → clears (prevFrameContaminated handles the frame after). Same
1049
+ * damage-tracking machinery as selection — setCellStyleId doesn't track
1050
+ * damage, so the overlay forces full-frame damage while active.
1051
+ */
1052
+ setSearchHighlight(query) {
1053
+ if (this.searchHighlightQuery === query)
1054
+ return;
1055
+ this.searchHighlightQuery = query;
1056
+ this.scheduleRender();
1057
+ }
1058
+ /** Paint an EXISTING DOM subtree to a fresh Screen at its natural
1059
+ * height, scan for query. Returns positions relative to the element's
1060
+ * bounding box (row 0 = element top).
1061
+ *
1062
+ * The element comes from the MAIN tree — built with all real
1063
+ * providers, yoga already computed. We paint it to a fresh buffer
1064
+ * with offsets so it lands at (0,0). Same paint path as the main
1065
+ * render. Zero drift. No second React root, no context bridge.
1066
+ *
1067
+ * ~1-2ms (paint only, no reconcile — the DOM is already built). */
1068
+ scanElementSubtree(el) {
1069
+ if (!this.searchHighlightQuery || !el.yogaNode)
1070
+ return [];
1071
+ const width = Math.ceil(el.yogaNode.getComputedWidth());
1072
+ const height = Math.ceil(el.yogaNode.getComputedHeight());
1073
+ if (width <= 0 || height <= 0)
1074
+ return [];
1075
+ // renderNodeToOutput adds el's OWN computedLeft/Top to offsetX/Y.
1076
+ // Passing -elLeft/-elTop nets to 0 → paints at (0,0) in our buffer.
1077
+ const elLeft = el.yogaNode.getComputedLeft();
1078
+ const elTop = el.yogaNode.getComputedTop();
1079
+ const screen = (0, screen_js_1.createScreen)(width, height, this.stylePool, this.charPool, this.hyperlinkPool);
1080
+ const output = new output_js_1.default({
1081
+ width,
1082
+ height,
1083
+ stylePool: this.stylePool,
1084
+ screen
1085
+ });
1086
+ (0, render_node_to_output_js_1.default)(el, output, {
1087
+ offsetX: -elLeft,
1088
+ offsetY: -elTop,
1089
+ prevScreen: undefined
1090
+ });
1091
+ const rendered = output.get();
1092
+ // renderNodeToOutput wrote our offset positions to nodeCache —
1093
+ // corrupts the main render (it'd blit from wrong coords). Mark the
1094
+ // subtree dirty so the next main render repaints + re-caches
1095
+ // correctly. One extra paint of this message, but correct > fast.
1096
+ dom.markDirty(el);
1097
+ const positions = (0, render_to_screen_js_1.scanPositions)(rendered, this.searchHighlightQuery);
1098
+ (0, debug_js_1.logForDebugging)(`scanElementSubtree: q='${this.searchHighlightQuery}' ` + `el=${width}x${height}@(${elLeft},${elTop}) n=${positions.length} ` + `[${positions.slice(0, 10).map(p => `${p.row}:${p.col}`).join(',')}` + `${positions.length > 10 ? ',…' : ''}]`);
1099
+ return positions;
1100
+ }
1101
+ /** Set the position-based highlight state. Every frame, writes CURRENT
1102
+ * style at positions[currentIdx] + rowOffset. null clears. The scan-
1103
+ * highlight (inverse on all matches) still runs — this overlays yellow
1104
+ * on top. rowOffset changes as the user scrolls (= message's current
1105
+ * screen-top); positions stay stable (message-relative). */
1106
+ setSearchPositions(state) {
1107
+ this.searchPositions = state;
1108
+ this.scheduleRender();
1109
+ }
1110
+ /**
1111
+ * Set the selection highlight background color. Replaces the per-cell
1112
+ * SGR-7 inverse with a solid theme-aware bg (matches native terminal
1113
+ * selection). Accepts the same color formats as Text backgroundColor
1114
+ * (rgb(), ansi:name, #hex, ansi256()) — colorize() routes through
1115
+ * chalk so the tmux/xterm.js level clamps in colorize.ts apply and
1116
+ * the emitted SGR is correct for the current terminal.
1117
+ *
1118
+ * Called by React-land once theme is known (ScrollKeybindingHandler's
1119
+ * useEffect watching useTheme). Before that call, withSelectionBg
1120
+ * falls back to withInverse so selection still renders on the first
1121
+ * frame; the effect fires before any mouse input so the fallback is
1122
+ * unobservable in practice.
1123
+ */
1124
+ setSelectionBgColor(color) {
1125
+ // Wrap a NUL marker, then split on it to extract the open/close SGR.
1126
+ // colorize returns the input unchanged if the color string is bad —
1127
+ // no NUL-split then, so fall through to null (inverse fallback).
1128
+ const wrapped = (0, colorize_js_1.colorize)('\0', color, 'background');
1129
+ const nul = wrapped.indexOf('\0');
1130
+ if (nul <= 0 || nul === wrapped.length - 1) {
1131
+ this.stylePool.setSelectionBg(null);
1132
+ return;
1133
+ }
1134
+ this.stylePool.setSelectionBg({
1135
+ type: 'ansi',
1136
+ code: wrapped.slice(0, nul),
1137
+ endCode: wrapped.slice(nul + 1) // always \x1b[49m for bg
1138
+ });
1139
+ // No scheduleRender: this is called from a React effect that already
1140
+ // runs inside the render cycle, and the bg only matters once a
1141
+ // selection exists (which itself triggers a full-damage frame).
1142
+ }
1143
+ /**
1144
+ * Capture text from rows about to scroll out of the viewport during
1145
+ * drag-to-scroll. Must be called BEFORE the ScrollBox scrolls so the
1146
+ * screen buffer still holds the outgoing content. Accumulated into
1147
+ * the selection state and joined back in by getSelectedText.
1148
+ */
1149
+ captureScrolledRows(firstRow, lastRow, side) {
1150
+ (0, selection_js_1.captureScrolledRows)(this.selection, this.frontFrame.screen, firstRow, lastRow, side);
1151
+ }
1152
+ /**
1153
+ * Shift anchor AND focus by dRow, clamped to [minRow, maxRow]. Used by
1154
+ * keyboard scroll handlers (PgUp/PgDn etc.) so the highlight tracks the
1155
+ * content instead of disappearing. Unlike shiftAnchor (drag-to-scroll),
1156
+ * this moves BOTH endpoints — the user isn't holding the mouse at one
1157
+ * edge. Supplies screen.width for the col-reset-on-clamp boundary.
1158
+ */
1159
+ shiftSelectionForScroll(dRow, minRow, maxRow) {
1160
+ const hadSel = (0, selection_js_1.hasSelection)(this.selection);
1161
+ (0, selection_js_1.shiftSelection)(this.selection, dRow, minRow, maxRow, this.frontFrame.screen.width);
1162
+ // shiftSelection clears when both endpoints overshoot the same edge
1163
+ // (Home/g/End/G page-jump past the selection). Notify subscribers so
1164
+ // useHasSelection updates. Safe to call notifySelectionChange here —
1165
+ // this runs from keyboard handlers, not inside onRender().
1166
+ if (hadSel && !(0, selection_js_1.hasSelection)(this.selection)) {
1167
+ this.notifySelectionChange();
1168
+ }
1169
+ }
1170
+ /**
1171
+ * Keyboard selection extension (shift+arrow/home/end). Moves focus;
1172
+ * anchor stays fixed so the highlight grows or shrinks relative to it.
1173
+ * Left/right wrap across row boundaries — native macOS text-edit
1174
+ * behavior: shift+left at col 0 wraps to end of the previous row.
1175
+ * Up/down clamp at viewport edges (no scroll-to-extend yet). Drops to
1176
+ * char mode. No-op outside alt-screen or without an active selection.
1177
+ */
1178
+ moveSelectionFocus(move) {
1179
+ if (!this.altScreenActive)
1180
+ return;
1181
+ const { focus } = this.selection;
1182
+ if (!focus)
1183
+ return;
1184
+ const { width, height } = this.frontFrame.screen;
1185
+ const maxCol = width - 1;
1186
+ const maxRow = height - 1;
1187
+ let { col, row } = focus;
1188
+ switch (move) {
1189
+ case 'left':
1190
+ if (col > 0)
1191
+ col--;
1192
+ else if (row > 0) {
1193
+ col = maxCol;
1194
+ row--;
1195
+ }
1196
+ break;
1197
+ case 'right':
1198
+ if (col < maxCol)
1199
+ col++;
1200
+ else if (row < maxRow) {
1201
+ col = 0;
1202
+ row++;
1203
+ }
1204
+ break;
1205
+ case 'up':
1206
+ if (row > 0)
1207
+ row--;
1208
+ break;
1209
+ case 'down':
1210
+ if (row < maxRow)
1211
+ row++;
1212
+ break;
1213
+ case 'lineStart':
1214
+ col = 0;
1215
+ break;
1216
+ case 'lineEnd':
1217
+ col = maxCol;
1218
+ break;
1219
+ }
1220
+ if (col === focus.col && row === focus.row)
1221
+ return;
1222
+ (0, selection_js_1.moveFocus)(this.selection, col, row);
1223
+ this.notifySelectionChange();
1224
+ }
1225
+ /** Whether there is an active text selection. */
1226
+ hasTextSelection() {
1227
+ return (0, selection_js_1.hasSelection)(this.selection);
1228
+ }
1229
+ /**
1230
+ * Subscribe to selection state changes. Fires whenever the selection
1231
+ * is started, updated, cleared, or copied. Returns an unsubscribe fn.
1232
+ */
1233
+ subscribeToSelectionChange(cb) {
1234
+ this.selectionListeners.add(cb);
1235
+ return () => this.selectionListeners.delete(cb);
1236
+ }
1237
+ notifySelectionChange() {
1238
+ this.onRender();
1239
+ for (const cb of this.selectionListeners)
1240
+ cb();
1241
+ }
1242
+ /**
1243
+ * Hit-test the rendered DOM tree at (col, row) and bubble a ClickEvent
1244
+ * from the deepest hit node up through ancestors with onClick handlers.
1245
+ * Returns true if a DOM handler consumed the click. Gated on
1246
+ * altScreenActive — clicks only make sense with a fixed viewport where
1247
+ * nodeCache rects map 1:1 to terminal cells (no scrollback offset).
1248
+ */
1249
+ dispatchClick(col, row) {
1250
+ if (!this.altScreenActive)
1251
+ return false;
1252
+ const blank = (0, screen_js_1.isEmptyCellAt)(this.frontFrame.screen, col, row);
1253
+ return (0, hit_test_js_1.dispatchClick)(this.rootNode, col, row, blank);
1254
+ }
1255
+ dispatchHover(col, row) {
1256
+ if (!this.altScreenActive)
1257
+ return;
1258
+ (0, hit_test_js_1.dispatchHover)(this.rootNode, col, row, this.hoveredNodes);
1259
+ }
1260
+ dispatchKeyboardEvent(parsedKey) {
1261
+ const target = this.focusManager.activeElement ?? this.rootNode;
1262
+ const event = new keyboard_event_js_1.KeyboardEvent(parsedKey);
1263
+ reconciler_js_1.dispatcher.dispatchDiscrete(target, event);
1264
+ // Tab cycling is the default action — only fires if no handler
1265
+ // called preventDefault(). Mirrors browser behavior.
1266
+ if (!event.defaultPrevented && parsedKey.name === 'tab' && !parsedKey.ctrl && !parsedKey.meta) {
1267
+ if (parsedKey.shift) {
1268
+ this.focusManager.focusPrevious(this.rootNode);
1269
+ }
1270
+ else {
1271
+ this.focusManager.focusNext(this.rootNode);
1272
+ }
1273
+ }
1274
+ }
1275
+ /**
1276
+ * Look up the URL at (col, row) in the current front frame. Checks for
1277
+ * an OSC 8 hyperlink first, then falls back to scanning the row for a
1278
+ * plain-text URL (mouse tracking intercepts the terminal's native
1279
+ * Cmd+Click URL detection, so we replicate it). This is a pure lookup
1280
+ * with no side effects — call it synchronously at click time so the
1281
+ * result reflects the screen the user actually clicked on, then defer
1282
+ * the browser-open action via a timer.
1283
+ */
1284
+ getHyperlinkAt(col, row) {
1285
+ if (!this.altScreenActive)
1286
+ return undefined;
1287
+ const screen = this.frontFrame.screen;
1288
+ const cell = (0, screen_js_1.cellAt)(screen, col, row);
1289
+ let url = cell?.hyperlink;
1290
+ // SpacerTail cells (right half of wide/CJK/emoji chars) store the
1291
+ // hyperlink on the head cell at col-1.
1292
+ if (!url && cell?.width === 2 /* CellWidth.SpacerTail */ && col > 0) {
1293
+ url = (0, screen_js_1.cellAt)(screen, col - 1, row)?.hyperlink;
1294
+ }
1295
+ return url ?? (0, selection_js_1.findPlainTextUrlAt)(screen, col, row);
1296
+ }
1297
+ /**
1298
+ * Stable prototype wrapper for onHyperlinkClick. Passed to <App> as
1299
+ * onOpenHyperlink so the prop is a bound method (autoBind'd) that reads
1300
+ * the mutable field at call time — not the undefined-at-render value.
1301
+ */
1302
+ openHyperlink(url) {
1303
+ this.onHyperlinkClick?.(url);
1304
+ }
1305
+ /**
1306
+ * Handle a double- or triple-click at (col, row): select the word or
1307
+ * line under the cursor by reading the current screen buffer. Called on
1308
+ * PRESS (not release) so the highlight appears immediately and drag can
1309
+ * extend the selection word-by-word / line-by-line. Falls back to
1310
+ * char-mode startSelection if the click lands on a noSelect cell.
1311
+ */
1312
+ handleMultiClick(col, row, count) {
1313
+ if (!this.altScreenActive)
1314
+ return;
1315
+ const screen = this.frontFrame.screen;
1316
+ // selectWordAt/selectLineAt no-op on noSelect/out-of-bounds. Seed with
1317
+ // a char-mode selection so the press still starts a drag even if the
1318
+ // word/line scan finds nothing selectable.
1319
+ (0, selection_js_1.startSelection)(this.selection, col, row);
1320
+ if (count === 2)
1321
+ (0, selection_js_1.selectWordAt)(this.selection, screen, col, row);
1322
+ else
1323
+ (0, selection_js_1.selectLineAt)(this.selection, screen, row);
1324
+ // Ensure hasSelection is true so release doesn't re-dispatch onClickAt.
1325
+ // selectWordAt no-ops on noSelect; selectLineAt no-ops out-of-bounds.
1326
+ if (!this.selection.focus)
1327
+ this.selection.focus = this.selection.anchor;
1328
+ this.notifySelectionChange();
1329
+ }
1330
+ /**
1331
+ * Handle a drag-motion at (col, row). In char mode updates focus to the
1332
+ * exact cell. In word/line mode snaps to word/line boundaries so the
1333
+ * selection extends by word/line like native macOS. Gated on
1334
+ * altScreenActive for the same reason as dispatchClick.
1335
+ */
1336
+ handleSelectionDrag(col, row) {
1337
+ if (!this.altScreenActive)
1338
+ return;
1339
+ const sel = this.selection;
1340
+ if (sel.anchorSpan) {
1341
+ (0, selection_js_1.extendSelection)(sel, this.frontFrame.screen, col, row);
1342
+ }
1343
+ else {
1344
+ (0, selection_js_1.updateSelection)(sel, col, row);
1345
+ }
1346
+ this.notifySelectionChange();
1347
+ }
1348
+ suspendStdin() {
1349
+ const stdin = this.options.stdin;
1350
+ if (!stdin.isTTY) {
1351
+ return;
1352
+ }
1353
+ // Store and remove all 'readable' event listeners temporarily
1354
+ // This prevents Ink from consuming stdin while the editor is active
1355
+ const readableListeners = stdin.listeners('readable');
1356
+ (0, debug_js_1.logForDebugging)(`[stdin] suspendStdin: removing ${readableListeners.length} readable listener(s), wasRawMode=${stdin.isRaw ?? false}`);
1357
+ readableListeners.forEach(listener => {
1358
+ this.stdinListeners.push({
1359
+ event: 'readable',
1360
+ listener: listener
1361
+ });
1362
+ stdin.removeListener('readable', listener);
1363
+ });
1364
+ // If raw mode is enabled, disable it temporarily
1365
+ const stdinWithRaw = stdin;
1366
+ if (stdinWithRaw.isRaw && stdinWithRaw.setRawMode) {
1367
+ stdinWithRaw.setRawMode(false);
1368
+ this.wasRawMode = true;
1369
+ }
1370
+ }
1371
+ resumeStdin() {
1372
+ const stdin = this.options.stdin;
1373
+ if (!stdin.isTTY) {
1374
+ return;
1375
+ }
1376
+ // Re-attach all the stored listeners
1377
+ if (this.stdinListeners.length === 0 && !this.wasRawMode) {
1378
+ (0, debug_js_1.logForDebugging)('[stdin] resumeStdin: called with no stored listeners and wasRawMode=false (possible desync)', {
1379
+ level: 'warn'
1380
+ });
1381
+ }
1382
+ (0, debug_js_1.logForDebugging)(`[stdin] resumeStdin: re-attaching ${this.stdinListeners.length} listener(s), wasRawMode=${this.wasRawMode}`);
1383
+ this.stdinListeners.forEach(({ event, listener }) => {
1384
+ stdin.addListener(event, listener);
1385
+ });
1386
+ this.stdinListeners = [];
1387
+ // Re-enable raw mode if it was enabled before
1388
+ if (this.wasRawMode) {
1389
+ const stdinWithRaw = stdin;
1390
+ if (stdinWithRaw.setRawMode) {
1391
+ stdinWithRaw.setRawMode(true);
1392
+ }
1393
+ this.wasRawMode = false;
1394
+ }
1395
+ }
1396
+ // Stable identity for TerminalWriteContext. An inline arrow here would
1397
+ // change on every render() call (initial mount + each resize), which
1398
+ // cascades through useContext → <AlternateScreen>'s useLayoutEffect dep
1399
+ // array → spurious exit+re-enter of the alt screen on every SIGWINCH.
1400
+ writeRaw(data) {
1401
+ this.options.stdout.write(data);
1402
+ }
1403
+ render(node) {
1404
+ (0, debug_js_1.logForDebugging)('[Ink:render] start');
1405
+ this.currentNode = node;
1406
+ const tree = <App_js_1.default stdin={this.options.stdin} stdout={this.options.stdout} stderr={this.options.stderr} exitOnCtrlC={this.options.exitOnCtrlC} onExit={this.unmount} terminalColumns={this.terminalColumns} terminalRows={this.terminalRows} selection={this.selection} onSelectionChange={this.notifySelectionChange} onClickAt={this.dispatchClick} onHoverAt={this.dispatchHover} getHyperlinkAt={this.getHyperlinkAt} onOpenHyperlink={this.openHyperlink} onMultiClick={this.handleMultiClick} onSelectionDrag={this.handleSelectionDrag} onStdinResume={this.reassertTerminalModes} onCursorDeclaration={this.setCursorDeclaration} dispatchKeyboardEvent={this.dispatchKeyboardEvent}>
1407
+ <useTerminalNotification_js_1.TerminalWriteProvider value={this.writeRaw}>
1408
+ {node}
1409
+ </useTerminalNotification_js_1.TerminalWriteProvider>
1410
+ </App_js_1.default>;
1411
+ // @ts-expect-error updateContainerSync exists in react-reconciler but not in @types/react-reconciler
1412
+ reconciler_js_1.default.updateContainerSync(tree, this.container, null, noop_js_1.default);
1413
+ // @ts-expect-error flushSyncWork exists in react-reconciler but not in @types/react-reconciler
1414
+ reconciler_js_1.default.flushSyncWork();
1415
+ (0, debug_js_1.logForDebugging)('[Ink:render] updateContainer complete');
1416
+ }
1417
+ unmount(error) {
1418
+ if (this.isUnmounted) {
1419
+ return;
1420
+ }
1421
+ this.onRender();
1422
+ this.unsubscribeExit();
1423
+ if (typeof this.restoreConsole === 'function') {
1424
+ this.restoreConsole();
1425
+ }
1426
+ this.restoreStderr?.();
1427
+ this.unsubscribeTTYHandlers?.();
1428
+ // Non-TTY environments don't handle erasing ansi escapes well, so it's better to
1429
+ // only render last frame of non-static output
1430
+ const diff = this.log.renderPreviousOutput_DEPRECATED(this.frontFrame);
1431
+ (0, terminal_js_1.writeDiffToTerminal)(this.terminal, (0, optimizer_js_1.optimize)(diff));
1432
+ // Clean up terminal modes synchronously before process exit.
1433
+ // React's componentWillUnmount won't run in time when process.exit() is called,
1434
+ // so we must reset terminal modes here to prevent escape sequence leakage.
1435
+ // Use writeSync to stdout (fd 1) to ensure writes complete before exit.
1436
+ // We unconditionally send all disable sequences because terminal detection
1437
+ // may not work correctly (e.g., in tmux, screen) and these are no-ops on
1438
+ // terminals that don't support them.
1439
+ /* eslint-disable custom-rules/no-sync-fs -- process exiting; async writes would be dropped */
1440
+ if (this.options.stdout.isTTY) {
1441
+ if (this.altScreenActive) {
1442
+ // <AlternateScreen>'s unmount effect won't run during signal-exit.
1443
+ // Exit alt screen FIRST so other cleanup sequences go to the main screen.
1444
+ (0, fs_1.writeSync)(1, dec_js_1.EXIT_ALT_SCREEN);
1445
+ }
1446
+ // Disable mouse tracking — unconditional because altScreenActive can be
1447
+ // stale if AlternateScreen's unmount (which flips the flag) raced a
1448
+ // blocked event loop + SIGINT. No-op if tracking was never enabled.
1449
+ (0, fs_1.writeSync)(1, dec_js_1.DISABLE_MOUSE_TRACKING);
1450
+ // Drain stdin so in-flight mouse events don't leak to the shell
1451
+ this.drainStdin();
1452
+ // Disable extended key reporting (both kitty and modifyOtherKeys)
1453
+ (0, fs_1.writeSync)(1, csi_js_1.DISABLE_MODIFY_OTHER_KEYS);
1454
+ (0, fs_1.writeSync)(1, csi_js_1.DISABLE_KITTY_KEYBOARD);
1455
+ // Disable focus events (DECSET 1004)
1456
+ (0, fs_1.writeSync)(1, dec_js_1.DFE);
1457
+ // Disable bracketed paste mode
1458
+ (0, fs_1.writeSync)(1, dec_js_1.DBP);
1459
+ // Show cursor
1460
+ (0, fs_1.writeSync)(1, dec_js_1.SHOW_CURSOR);
1461
+ // Clear iTerm2 progress bar
1462
+ (0, fs_1.writeSync)(1, osc_js_1.CLEAR_ITERM2_PROGRESS);
1463
+ // Clear tab status (OSC 21337) so a stale dot doesn't linger
1464
+ if ((0, osc_js_1.supportsTabStatus)())
1465
+ (0, fs_1.writeSync)(1, (0, osc_js_1.wrapForMultiplexer)(osc_js_1.CLEAR_TAB_STATUS));
1466
+ }
1467
+ /* eslint-enable custom-rules/no-sync-fs */
1468
+ this.isUnmounted = true;
1469
+ // Cancel any pending throttled renders to prevent accessing freed Yoga nodes
1470
+ this.scheduleRender.cancel?.();
1471
+ if (this.drainTimer !== null) {
1472
+ clearTimeout(this.drainTimer);
1473
+ this.drainTimer = null;
1474
+ }
1475
+ // @ts-expect-error updateContainerSync exists in react-reconciler but not in @types/react-reconciler
1476
+ reconciler_js_1.default.updateContainerSync(null, this.container, null, noop_js_1.default);
1477
+ // @ts-expect-error flushSyncWork exists in react-reconciler but not in @types/react-reconciler
1478
+ reconciler_js_1.default.flushSyncWork();
1479
+ instances_js_1.default.delete(this.options.stdout);
1480
+ // Free the root yoga node, then clear its reference. Children are already
1481
+ // freed by the reconciler's removeChildFromContainer; using .free() (not
1482
+ // .freeRecursive()) avoids double-freeing them.
1483
+ this.rootNode.yogaNode?.free();
1484
+ this.rootNode.yogaNode = undefined;
1485
+ if (error instanceof Error) {
1486
+ this.rejectExitPromise(error);
1487
+ }
1488
+ else {
1489
+ this.resolveExitPromise();
1490
+ }
1491
+ }
1492
+ async waitUntilExit() {
1493
+ this.exitPromise || (this.exitPromise = new Promise((resolve, reject) => {
1494
+ this.resolveExitPromise = resolve;
1495
+ this.rejectExitPromise = reject;
1496
+ }));
1497
+ return this.exitPromise;
1498
+ }
1499
+ resetLineCount() {
1500
+ if (this.options.stdout.isTTY) {
1501
+ // Swap so old front becomes back (for screen reuse), then reset front
1502
+ this.backFrame = this.frontFrame;
1503
+ this.frontFrame = (0, frame_js_1.emptyFrame)(this.frontFrame.viewport.height, this.frontFrame.viewport.width, this.stylePool, this.charPool, this.hyperlinkPool);
1504
+ this.log.reset();
1505
+ // frontFrame is reset, so frame.cursor on the next render is (0,0).
1506
+ // Clear displayCursor so the preamble doesn't compute a stale delta.
1507
+ this.displayCursor = null;
1508
+ }
1509
+ }
1510
+ /**
1511
+ * Replace char/hyperlink pools with fresh instances to prevent unbounded
1512
+ * growth during long sessions. Migrates the front frame's screen IDs into
1513
+ * the new pools so diffing remains correct. The back frame doesn't need
1514
+ * migration — resetScreen zeros it before any reads.
1515
+ *
1516
+ * Call between conversation turns or periodically.
1517
+ */
1518
+ resetPools() {
1519
+ this.charPool = new screen_js_1.CharPool();
1520
+ this.hyperlinkPool = new screen_js_1.HyperlinkPool();
1521
+ (0, screen_js_1.migrateScreenPools)(this.frontFrame.screen, this.charPool, this.hyperlinkPool);
1522
+ // Back frame's data is zeroed by resetScreen before reads, but its pool
1523
+ // references are used by the renderer to intern new characters. Point
1524
+ // them at the new pools so the next frame's IDs are comparable.
1525
+ this.backFrame.screen.charPool = this.charPool;
1526
+ this.backFrame.screen.hyperlinkPool = this.hyperlinkPool;
1527
+ }
1528
+ patchConsole() {
1529
+ // biome-ignore lint/suspicious/noConsole: intentionally patching global console
1530
+ const con = console;
1531
+ const originals = {};
1532
+ const toDebug = (...args) => (0, debug_js_1.logForDebugging)(`console.log: ${(0, util_1.format)(...args)}`);
1533
+ const toError = (...args) => (0, log_js_1.logError)(new Error(`console.error: ${(0, util_1.format)(...args)}`));
1534
+ for (const m of CONSOLE_STDOUT_METHODS) {
1535
+ originals[m] = con[m];
1536
+ con[m] = toDebug;
1537
+ }
1538
+ for (const m of CONSOLE_STDERR_METHODS) {
1539
+ originals[m] = con[m];
1540
+ con[m] = toError;
1541
+ }
1542
+ originals.assert = con.assert;
1543
+ con.assert = (condition, ...args) => {
1544
+ if (!condition)
1545
+ toError(...args);
1546
+ };
1547
+ return () => Object.assign(con, originals);
1548
+ }
1549
+ /**
1550
+ * Intercept process.stderr.write so stray writes (config.ts, hooks.ts,
1551
+ * third-party deps) don't corrupt the alt-screen buffer. patchConsole only
1552
+ * hooks console.* methods — direct stderr writes bypass it, land at the
1553
+ * parked cursor, scroll the alt-screen, and desync frontFrame from the
1554
+ * physical terminal. Next diff writes only changed-in-React cells at
1555
+ * absolute coords → interleaved garbage.
1556
+ *
1557
+ * Swallows the write (routes text to the debug log) and, in alt-screen,
1558
+ * forces a full-damage repaint as a defensive recovery. Not patching
1559
+ * process.stdout — Ink itself writes there.
1560
+ */
1561
+ patchStderr() {
1562
+ const stderr = process.stderr;
1563
+ const originalWrite = stderr.write;
1564
+ let reentered = false;
1565
+ const intercept = (chunk, encodingOrCb, cb) => {
1566
+ const callback = typeof encodingOrCb === 'function' ? encodingOrCb : cb;
1567
+ // Reentrancy guard: logForDebugging → writeToStderr → here. Pass
1568
+ // through to the original so --debug-to-stderr still works and we
1569
+ // don't stack-overflow.
1570
+ if (reentered) {
1571
+ const encoding = typeof encodingOrCb === 'string' ? encodingOrCb : undefined;
1572
+ return originalWrite.call(stderr, chunk, encoding, callback);
1573
+ }
1574
+ reentered = true;
1575
+ try {
1576
+ const text = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8');
1577
+ (0, debug_js_1.logForDebugging)(`[stderr] ${text}`, {
1578
+ level: 'warn'
1579
+ });
1580
+ if (this.altScreenActive && !this.isUnmounted && !this.isPaused) {
1581
+ this.prevFrameContaminated = true;
1582
+ this.scheduleRender();
1583
+ }
1584
+ }
1585
+ finally {
1586
+ reentered = false;
1587
+ callback?.();
1588
+ }
1589
+ return true;
1590
+ };
1591
+ stderr.write = intercept;
1592
+ return () => {
1593
+ if (stderr.write === intercept) {
1594
+ stderr.write = originalWrite;
1595
+ }
1596
+ };
1597
+ }
1598
+ }
1599
+ exports.default = Ink;
1600
+ /**
1601
+ * Discard pending stdin bytes so in-flight escape sequences (mouse tracking
1602
+ * reports, bracketed-paste markers) don't leak to the shell after exit.
1603
+ *
1604
+ * Two layers of trickiness:
1605
+ *
1606
+ * 1. setRawMode is termios, not fcntl — the stdin fd stays blocking, so
1607
+ * readSync on it would hang forever. Node doesn't expose fcntl, so we
1608
+ * open /dev/tty fresh with O_NONBLOCK (all fds to the controlling
1609
+ * terminal share one line-discipline input queue).
1610
+ *
1611
+ * 2. By the time forceExit calls this, detachForShutdown has already put
1612
+ * the TTY back in cooked (canonical) mode. Canonical mode line-buffers
1613
+ * input until newline, so O_NONBLOCK reads return EAGAIN even when
1614
+ * mouse bytes are sitting in the buffer. We briefly re-enter raw mode
1615
+ * so reads return any available bytes, then restore cooked mode.
1616
+ *
1617
+ * Safe to call multiple times. Call as LATE as possible in the exit path:
1618
+ * DISABLE_MOUSE_TRACKING has terminal round-trip latency, so events can
1619
+ * arrive for a few ms after it's written.
1620
+ */
1621
+ /* eslint-disable custom-rules/no-sync-fs -- must be sync; called from signal handler / unmount */
1622
+ function drainStdin(stdin = process.stdin) {
1623
+ if (!stdin.isTTY)
1624
+ return;
1625
+ // Drain Node's stream buffer (bytes libuv already pulled in). read()
1626
+ // returns null when empty — never blocks.
1627
+ try {
1628
+ while (stdin.read() !== null) {
1629
+ /* discard */
1630
+ }
1631
+ }
1632
+ catch {
1633
+ /* stream may be destroyed */
1634
+ }
1635
+ // No /dev/tty on Windows; CONIN$ doesn't support O_NONBLOCK semantics.
1636
+ // Windows Terminal also doesn't buffer mouse reports the same way.
1637
+ if (process.platform === 'win32')
1638
+ return;
1639
+ // termios is per-device: flip stdin to raw so canonical-mode line
1640
+ // buffering doesn't hide partial input from the non-blocking read.
1641
+ // Restored in the finally block.
1642
+ const tty = stdin;
1643
+ const wasRaw = tty.isRaw === true;
1644
+ // Drain the kernel TTY buffer via a fresh O_NONBLOCK fd. Bounded at 64
1645
+ // reads (64KB) — a real mouse burst is a few hundred bytes; the cap
1646
+ // guards against a terminal that ignores O_NONBLOCK.
1647
+ let fd = -1;
1648
+ try {
1649
+ // setRawMode inside try: on revoked TTY (SIGHUP/SSH disconnect) the
1650
+ // ioctl throws EBADF — same recovery path as openSync/readSync below.
1651
+ if (!wasRaw)
1652
+ tty.setRawMode?.(true);
1653
+ fd = (0, fs_1.openSync)('/dev/tty', fs_1.constants.O_RDONLY | fs_1.constants.O_NONBLOCK);
1654
+ const buf = Buffer.alloc(1024);
1655
+ for (let i = 0; i < 64; i++) {
1656
+ if ((0, fs_1.readSync)(fd, buf, 0, buf.length, null) <= 0)
1657
+ break;
1658
+ }
1659
+ }
1660
+ catch {
1661
+ // EAGAIN (buffer empty — expected), ENXIO/ENOENT (no controlling tty),
1662
+ // EBADF/EIO (TTY revoked — SIGHUP, SSH disconnect)
1663
+ }
1664
+ finally {
1665
+ if (fd >= 0) {
1666
+ try {
1667
+ (0, fs_1.closeSync)(fd);
1668
+ }
1669
+ catch {
1670
+ /* ignore */
1671
+ }
1672
+ }
1673
+ if (!wasRaw) {
1674
+ try {
1675
+ tty.setRawMode?.(false);
1676
+ }
1677
+ catch {
1678
+ /* TTY may be gone */
1679
+ }
1680
+ }
1681
+ }
1682
+ }
1683
+ /* eslint-enable custom-rules/no-sync-fs */
1684
+ const CONSOLE_STDOUT_METHODS = ['log', 'info', 'debug', 'dir', 'dirxml', 'count', 'countReset', 'group', 'groupCollapsed', 'groupEnd', 'table', 'time', 'timeEnd', 'timeLog'];
1685
+ const CONSOLE_STDERR_METHODS = ['warn', 'error', 'trace'];
1686
+ //# sourceMappingURL=ink.js.map