nastech-tui 0.0.1

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 (424) hide show
  1. package/.prettierrc +11 -0
  2. package/README.md +346 -0
  3. package/eslint.config.mjs +111 -0
  4. package/package.json +51 -0
  5. package/packages/nastech-ink/ambient.d.ts +83 -0
  6. package/packages/nastech-ink/index.d.ts +40 -0
  7. package/packages/nastech-ink/index.js +1 -0
  8. package/packages/nastech-ink/nastech-ink/ambient.d.ts +83 -0
  9. package/packages/nastech-ink/nastech-ink/index.d.ts +40 -0
  10. package/packages/nastech-ink/nastech-ink/index.js +1 -0
  11. package/packages/nastech-ink/nastech-ink/package.json +54 -0
  12. package/packages/nastech-ink/nastech-ink/src/bootstrap/state.ts +9 -0
  13. package/packages/nastech-ink/nastech-ink/src/entry-exports.ts +32 -0
  14. package/packages/nastech-ink/nastech-ink/src/hooks/use-stderr.ts +15 -0
  15. package/packages/nastech-ink/nastech-ink/src/hooks/use-stdout.ts +15 -0
  16. package/packages/nastech-ink/nastech-ink/src/ink/Ansi.tsx +435 -0
  17. package/packages/nastech-ink/nastech-ink/src/ink/app-mouse.test.ts +123 -0
  18. package/packages/nastech-ink/nastech-ink/src/ink/bidi.ts +145 -0
  19. package/packages/nastech-ink/nastech-ink/src/ink/cache-eviction.ts +45 -0
  20. package/packages/nastech-ink/nastech-ink/src/ink/clearTerminal.ts +68 -0
  21. package/packages/nastech-ink/nastech-ink/src/ink/colorize.test.ts +60 -0
  22. package/packages/nastech-ink/nastech-ink/src/ink/colorize.ts +277 -0
  23. package/packages/nastech-ink/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
  24. package/packages/nastech-ink/nastech-ink/src/ink/components/App.tsx +830 -0
  25. package/packages/nastech-ink/nastech-ink/src/ink/components/AppContext.ts +20 -0
  26. package/packages/nastech-ink/nastech-ink/src/ink/components/Box.tsx +294 -0
  27. package/packages/nastech-ink/nastech-ink/src/ink/components/Button.tsx +236 -0
  28. package/packages/nastech-ink/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
  29. package/packages/nastech-ink/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
  30. package/packages/nastech-ink/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
  31. package/packages/nastech-ink/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
  32. package/packages/nastech-ink/nastech-ink/src/ink/components/Link.tsx +38 -0
  33. package/packages/nastech-ink/nastech-ink/src/ink/components/Newline.tsx +43 -0
  34. package/packages/nastech-ink/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
  35. package/packages/nastech-ink/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
  36. package/packages/nastech-ink/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
  37. package/packages/nastech-ink/nastech-ink/src/ink/components/Spacer.tsx +23 -0
  38. package/packages/nastech-ink/nastech-ink/src/ink/components/StdinContext.ts +25 -0
  39. package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
  40. package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
  41. package/packages/nastech-ink/nastech-ink/src/ink/components/Text.test.ts +38 -0
  42. package/packages/nastech-ink/nastech-ink/src/ink/components/Text.tsx +336 -0
  43. package/packages/nastech-ink/nastech-ink/src/ink/constants.ts +6 -0
  44. package/packages/nastech-ink/nastech-ink/src/ink/cursor.ts +5 -0
  45. package/packages/nastech-ink/nastech-ink/src/ink/devtools.ts +2 -0
  46. package/packages/nastech-ink/nastech-ink/src/ink/dom.ts +495 -0
  47. package/packages/nastech-ink/nastech-ink/src/ink/events/click-event.ts +38 -0
  48. package/packages/nastech-ink/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
  49. package/packages/nastech-ink/nastech-ink/src/ink/events/dispatcher.ts +242 -0
  50. package/packages/nastech-ink/nastech-ink/src/ink/events/emitter.ts +40 -0
  51. package/packages/nastech-ink/nastech-ink/src/ink/events/event-handlers.ts +84 -0
  52. package/packages/nastech-ink/nastech-ink/src/ink/events/event.ts +11 -0
  53. package/packages/nastech-ink/nastech-ink/src/ink/events/focus-event.ts +18 -0
  54. package/packages/nastech-ink/nastech-ink/src/ink/events/input-event.ts +176 -0
  55. package/packages/nastech-ink/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
  56. package/packages/nastech-ink/nastech-ink/src/ink/events/mouse-event.ts +18 -0
  57. package/packages/nastech-ink/nastech-ink/src/ink/events/paste-event.ts +10 -0
  58. package/packages/nastech-ink/nastech-ink/src/ink/events/resize-event.ts +12 -0
  59. package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-event.ts +107 -0
  60. package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
  61. package/packages/nastech-ink/nastech-ink/src/ink/focus.ts +219 -0
  62. package/packages/nastech-ink/nastech-ink/src/ink/frame.ts +124 -0
  63. package/packages/nastech-ink/nastech-ink/src/ink/get-max-width.ts +27 -0
  64. package/packages/nastech-ink/nastech-ink/src/ink/global.d.ts +1 -0
  65. package/packages/nastech-ink/nastech-ink/src/ink/hit-test.test.ts +38 -0
  66. package/packages/nastech-ink/nastech-ink/src/ink/hit-test.ts +224 -0
  67. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
  68. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-app.ts +9 -0
  69. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
  70. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
  71. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
  72. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-input.ts +95 -0
  73. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
  74. package/packages/nastech-ink/package.json +57 -0
  75. package/packages/nastech-ink/src/bootstrap/state.ts +9 -0
  76. package/packages/nastech-ink/src/entry-exports.ts +32 -0
  77. package/packages/nastech-ink/src/hooks/use-stderr.ts +15 -0
  78. package/packages/nastech-ink/src/hooks/use-stdout.ts +15 -0
  79. package/packages/nastech-ink/src/ink/Ansi.tsx +435 -0
  80. package/packages/nastech-ink/src/ink/app-mouse.test.ts +123 -0
  81. package/packages/nastech-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
  82. package/packages/nastech-ink/src/ink/bidi.ts +145 -0
  83. package/packages/nastech-ink/src/ink/cache-eviction.ts +45 -0
  84. package/packages/nastech-ink/src/ink/clearTerminal.ts +68 -0
  85. package/packages/nastech-ink/src/ink/colorize.test.ts +60 -0
  86. package/packages/nastech-ink/src/ink/colorize.ts +277 -0
  87. package/packages/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
  88. package/packages/nastech-ink/src/ink/components/App.tsx +855 -0
  89. package/packages/nastech-ink/src/ink/components/AppContext.ts +20 -0
  90. package/packages/nastech-ink/src/ink/components/Box.tsx +294 -0
  91. package/packages/nastech-ink/src/ink/components/Button.tsx +236 -0
  92. package/packages/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
  93. package/packages/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
  94. package/packages/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
  95. package/packages/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
  96. package/packages/nastech-ink/src/ink/components/Link.tsx +38 -0
  97. package/packages/nastech-ink/src/ink/components/Newline.tsx +43 -0
  98. package/packages/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
  99. package/packages/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
  100. package/packages/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
  101. package/packages/nastech-ink/src/ink/components/Spacer.tsx +23 -0
  102. package/packages/nastech-ink/src/ink/components/StdinContext.ts +25 -0
  103. package/packages/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
  104. package/packages/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
  105. package/packages/nastech-ink/src/ink/components/Text.test.ts +38 -0
  106. package/packages/nastech-ink/src/ink/components/Text.tsx +336 -0
  107. package/packages/nastech-ink/src/ink/constants.ts +6 -0
  108. package/packages/nastech-ink/src/ink/cursor.ts +5 -0
  109. package/packages/nastech-ink/src/ink/devtools.ts +2 -0
  110. package/packages/nastech-ink/src/ink/dom.ts +495 -0
  111. package/packages/nastech-ink/src/ink/events/click-event.ts +38 -0
  112. package/packages/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
  113. package/packages/nastech-ink/src/ink/events/dispatcher.ts +242 -0
  114. package/packages/nastech-ink/src/ink/events/emitter.ts +40 -0
  115. package/packages/nastech-ink/src/ink/events/event-handlers.ts +84 -0
  116. package/packages/nastech-ink/src/ink/events/event.ts +11 -0
  117. package/packages/nastech-ink/src/ink/events/focus-event.ts +18 -0
  118. package/packages/nastech-ink/src/ink/events/input-event.ts +176 -0
  119. package/packages/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
  120. package/packages/nastech-ink/src/ink/events/mouse-event.ts +18 -0
  121. package/packages/nastech-ink/src/ink/events/paste-event.ts +10 -0
  122. package/packages/nastech-ink/src/ink/events/resize-event.ts +12 -0
  123. package/packages/nastech-ink/src/ink/events/terminal-event.ts +107 -0
  124. package/packages/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
  125. package/packages/nastech-ink/src/ink/focus.ts +219 -0
  126. package/packages/nastech-ink/src/ink/frame.ts +124 -0
  127. package/packages/nastech-ink/src/ink/get-max-width.ts +27 -0
  128. package/packages/nastech-ink/src/ink/global.d.ts +1 -0
  129. package/packages/nastech-ink/src/ink/hit-test.test.ts +38 -0
  130. package/packages/nastech-ink/src/ink/hit-test.ts +224 -0
  131. package/packages/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
  132. package/packages/nastech-ink/src/ink/hooks/use-app.ts +9 -0
  133. package/packages/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
  134. package/packages/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
  135. package/packages/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
  136. package/packages/nastech-ink/src/ink/hooks/use-input.ts +95 -0
  137. package/packages/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
  138. package/packages/nastech-ink/src/ink/hooks/use-search-highlight.ts +56 -0
  139. package/packages/nastech-ink/src/ink/hooks/use-selection.ts +101 -0
  140. package/packages/nastech-ink/src/ink/hooks/use-stdin.ts +9 -0
  141. package/packages/nastech-ink/src/ink/hooks/use-tab-status.ts +71 -0
  142. package/packages/nastech-ink/src/ink/hooks/use-terminal-focus.ts +18 -0
  143. package/packages/nastech-ink/src/ink/hooks/use-terminal-title.ts +34 -0
  144. package/packages/nastech-ink/src/ink/hooks/use-terminal-viewport.ts +100 -0
  145. package/packages/nastech-ink/src/ink/hyperlinkHover.ts +52 -0
  146. package/packages/nastech-ink/src/ink/ink-cursor-advance.test.ts +234 -0
  147. package/packages/nastech-ink/src/ink/ink-resize.test.ts +50 -0
  148. package/packages/nastech-ink/src/ink/ink.tsx +2705 -0
  149. package/packages/nastech-ink/src/ink/instances.ts +10 -0
  150. package/packages/nastech-ink/src/ink/layout/engine.ts +6 -0
  151. package/packages/nastech-ink/src/ink/layout/geometry.ts +98 -0
  152. package/packages/nastech-ink/src/ink/layout/node.ts +145 -0
  153. package/packages/nastech-ink/src/ink/layout/yoga.ts +313 -0
  154. package/packages/nastech-ink/src/ink/line-width-cache.ts +38 -0
  155. package/packages/nastech-ink/src/ink/log-update.test.ts +223 -0
  156. package/packages/nastech-ink/src/ink/log-update.ts +752 -0
  157. package/packages/nastech-ink/src/ink/lru.ts +14 -0
  158. package/packages/nastech-ink/src/ink/measure-element.ts +23 -0
  159. package/packages/nastech-ink/src/ink/measure-text.ts +50 -0
  160. package/packages/nastech-ink/src/ink/node-cache.ts +53 -0
  161. package/packages/nastech-ink/src/ink/optimizer.ts +99 -0
  162. package/packages/nastech-ink/src/ink/output.ts +845 -0
  163. package/packages/nastech-ink/src/ink/parse-keypress.test.ts +133 -0
  164. package/packages/nastech-ink/src/ink/parse-keypress.ts +848 -0
  165. package/packages/nastech-ink/src/ink/reconciler.ts +382 -0
  166. package/packages/nastech-ink/src/ink/render-border.ts +206 -0
  167. package/packages/nastech-ink/src/ink/render-node-to-output.ts +1582 -0
  168. package/packages/nastech-ink/src/ink/render-to-screen.ts +236 -0
  169. package/packages/nastech-ink/src/ink/renderer.ts +169 -0
  170. package/packages/nastech-ink/src/ink/root.ts +204 -0
  171. package/packages/nastech-ink/src/ink/screen.ts +1590 -0
  172. package/packages/nastech-ink/src/ink/searchHighlight.ts +91 -0
  173. package/packages/nastech-ink/src/ink/selection.test.ts +82 -0
  174. package/packages/nastech-ink/src/ink/selection.ts +1143 -0
  175. package/packages/nastech-ink/src/ink/squash-text-nodes.ts +74 -0
  176. package/packages/nastech-ink/src/ink/stringWidth.ts +341 -0
  177. package/packages/nastech-ink/src/ink/styles.ts +750 -0
  178. package/packages/nastech-ink/src/ink/supports-hyperlinks.ts +51 -0
  179. package/packages/nastech-ink/src/ink/tabstops.ts +44 -0
  180. package/packages/nastech-ink/src/ink/terminal-focus-state.ts +52 -0
  181. package/packages/nastech-ink/src/ink/terminal-querier.ts +222 -0
  182. package/packages/nastech-ink/src/ink/terminal.test.ts +15 -0
  183. package/packages/nastech-ink/src/ink/terminal.ts +299 -0
  184. package/packages/nastech-ink/src/ink/termio/ansi.ts +75 -0
  185. package/packages/nastech-ink/src/ink/termio/csi.ts +334 -0
  186. package/packages/nastech-ink/src/ink/termio/dec.ts +99 -0
  187. package/packages/nastech-ink/src/ink/termio/esc.ts +69 -0
  188. package/packages/nastech-ink/src/ink/termio/osc.test.ts +191 -0
  189. package/packages/nastech-ink/src/ink/termio/osc.ts +724 -0
  190. package/packages/nastech-ink/src/ink/termio/parser.ts +467 -0
  191. package/packages/nastech-ink/src/ink/termio/sgr.ts +362 -0
  192. package/packages/nastech-ink/src/ink/termio/tokenize.test.ts +185 -0
  193. package/packages/nastech-ink/src/ink/termio/tokenize.ts +350 -0
  194. package/packages/nastech-ink/src/ink/termio/types.ts +230 -0
  195. package/packages/nastech-ink/src/ink/termio.ts +42 -0
  196. package/packages/nastech-ink/src/ink/useTerminalNotification.ts +110 -0
  197. package/packages/nastech-ink/src/ink/warn.ts +15 -0
  198. package/packages/nastech-ink/src/ink/widest-line.ts +22 -0
  199. package/packages/nastech-ink/src/ink/wrap-text.test.ts +17 -0
  200. package/packages/nastech-ink/src/ink/wrap-text.ts +144 -0
  201. package/packages/nastech-ink/src/ink/wrapAnsi.ts +13 -0
  202. package/packages/nastech-ink/src/native-ts/yoga-layout/enums.ts +112 -0
  203. package/packages/nastech-ink/src/native-ts/yoga-layout/index.ts +2326 -0
  204. package/packages/nastech-ink/src/utils/debug.ts +6 -0
  205. package/packages/nastech-ink/src/utils/earlyInput.ts +131 -0
  206. package/packages/nastech-ink/src/utils/env.ts +66 -0
  207. package/packages/nastech-ink/src/utils/envUtils.ts +13 -0
  208. package/packages/nastech-ink/src/utils/execFileNoThrow.test.ts +146 -0
  209. package/packages/nastech-ink/src/utils/execFileNoThrow.ts +115 -0
  210. package/packages/nastech-ink/src/utils/fullscreen.ts +3 -0
  211. package/packages/nastech-ink/src/utils/intl.ts +87 -0
  212. package/packages/nastech-ink/src/utils/log.ts +7 -0
  213. package/packages/nastech-ink/src/utils/semver.ts +57 -0
  214. package/packages/nastech-ink/src/utils/sliceAnsi.ts +106 -0
  215. package/packages/nastech-ink/text-input.d.ts +2 -0
  216. package/packages/nastech-ink/text-input.js +1 -0
  217. package/scripts/build.mjs +61 -0
  218. package/scripts/profile-tui.mjs +121 -0
  219. package/src/__tests__/activeSessionSwitcher.test.ts +157 -0
  220. package/src/__tests__/appChromeStatusRule.test.tsx +84 -0
  221. package/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
  222. package/src/__tests__/approvalAction.test.ts +50 -0
  223. package/src/__tests__/asCommandDispatch.test.ts +27 -0
  224. package/src/__tests__/blockLayout.test.ts +122 -0
  225. package/src/__tests__/clipboard.test.ts +369 -0
  226. package/src/__tests__/constants.test.ts +53 -0
  227. package/src/__tests__/createGatewayEventHandler.test.ts +1091 -0
  228. package/src/__tests__/createSlashHandler.test.ts +822 -0
  229. package/src/__tests__/creditsCommand.test.ts +144 -0
  230. package/src/__tests__/cursorDriftRegression.test.ts +114 -0
  231. package/src/__tests__/details.test.ts +115 -0
  232. package/src/__tests__/emoji.test.ts +64 -0
  233. package/src/__tests__/externalLink.test.ts +144 -0
  234. package/src/__tests__/forceTruecolor.test.ts +191 -0
  235. package/src/__tests__/gatewayClient.test.ts +394 -0
  236. package/src/__tests__/gatewayRecovery.test.ts +47 -0
  237. package/src/__tests__/markdown.test.ts +331 -0
  238. package/src/__tests__/mathUnicode.test.ts +293 -0
  239. package/src/__tests__/memoryMonitor.test.ts +102 -0
  240. package/src/__tests__/messageLine.test.ts +19 -0
  241. package/src/__tests__/messages.test.ts +92 -0
  242. package/src/__tests__/orchestratorPromptSession.test.ts +64 -0
  243. package/src/__tests__/osc52.test.ts +67 -0
  244. package/src/__tests__/parentLog.test.ts +75 -0
  245. package/src/__tests__/paths.test.ts +70 -0
  246. package/src/__tests__/platform.test.ts +556 -0
  247. package/src/__tests__/precisionWheel.test.ts +44 -0
  248. package/src/__tests__/prompt.test.ts +31 -0
  249. package/src/__tests__/providers.test.ts +65 -0
  250. package/src/__tests__/reasoning.test.ts +76 -0
  251. package/src/__tests__/rpc.test.ts +27 -0
  252. package/src/__tests__/scroll.test.ts +99 -0
  253. package/src/__tests__/slashParity.test.ts +123 -0
  254. package/src/__tests__/spawnHistoryStore.test.ts +46 -0
  255. package/src/__tests__/stateIsolation.test.ts +46 -0
  256. package/src/__tests__/statusBarTicker.test.ts +18 -0
  257. package/src/__tests__/statusRule.test.ts +32 -0
  258. package/src/__tests__/streamingMarkdown.test.ts +121 -0
  259. package/src/__tests__/subagentTree.test.ts +407 -0
  260. package/src/__tests__/syntax.test.ts +45 -0
  261. package/src/__tests__/terminalModes.test.ts +39 -0
  262. package/src/__tests__/terminalParity.test.ts +77 -0
  263. package/src/__tests__/terminalSetup.test.ts +386 -0
  264. package/src/__tests__/termux.test.ts +35 -0
  265. package/src/__tests__/termuxComposerLayout.test.ts +40 -0
  266. package/src/__tests__/text.test.ts +233 -0
  267. package/src/__tests__/textInputBurstInput.test.ts +40 -0
  268. package/src/__tests__/textInputCursorSourceOfTruth.test.ts +50 -0
  269. package/src/__tests__/textInputFastEcho.test.ts +200 -0
  270. package/src/__tests__/textInputLineNav.test.ts +55 -0
  271. package/src/__tests__/textInputPassThrough.test.ts +59 -0
  272. package/src/__tests__/textInputRightClick.test.ts +48 -0
  273. package/src/__tests__/textInputWrap.test.ts +151 -0
  274. package/src/__tests__/theme.test.ts +311 -0
  275. package/src/__tests__/turnControllerNotice.test.ts +43 -0
  276. package/src/__tests__/turnStore.test.ts +66 -0
  277. package/src/__tests__/useCompletion.test.ts +35 -0
  278. package/src/__tests__/useComposerState.test.ts +59 -0
  279. package/src/__tests__/useConfigSync.test.ts +460 -0
  280. package/src/__tests__/useInputHandlers.test.ts +77 -0
  281. package/src/__tests__/useQueue.test.ts +28 -0
  282. package/src/__tests__/useSessionLifecycle.test.ts +60 -0
  283. package/src/__tests__/useVirtualHistoryHeights.test.ts +39 -0
  284. package/src/__tests__/viewport.test.ts +58 -0
  285. package/src/__tests__/viewportStore.test.ts +85 -0
  286. package/src/__tests__/virtualHeights.test.ts +96 -0
  287. package/src/__tests__/virtualHistoryClamp.test.ts +19 -0
  288. package/src/__tests__/virtualHistoryOffsetCache.test.ts +282 -0
  289. package/src/__tests__/wheelAccel.test.ts +138 -0
  290. package/src/app/createGatewayEventHandler.ts +833 -0
  291. package/src/app/createSlashHandler.ts +130 -0
  292. package/src/app/delegationStore.ts +77 -0
  293. package/src/app/gatewayContext.tsx +19 -0
  294. package/src/app/gatewayRecovery.ts +35 -0
  295. package/src/app/inputSelectionStore.ts +15 -0
  296. package/src/app/interfaces.ts +394 -0
  297. package/src/app/overlayStore.ts +53 -0
  298. package/src/app/scroll.ts +71 -0
  299. package/src/app/setupHandoff.ts +54 -0
  300. package/src/app/slash/commands/core.ts +648 -0
  301. package/src/app/slash/commands/credits.ts +57 -0
  302. package/src/app/slash/commands/debug.ts +48 -0
  303. package/src/app/slash/commands/ops.ts +717 -0
  304. package/src/app/slash/commands/session.ts +554 -0
  305. package/src/app/slash/commands/setup.ts +20 -0
  306. package/src/app/slash/registry.ts +20 -0
  307. package/src/app/slash/types.ts +21 -0
  308. package/src/app/spawnHistoryStore.ts +159 -0
  309. package/src/app/turnController.ts +866 -0
  310. package/src/app/turnStore.ts +85 -0
  311. package/src/app/uiStore.ts +44 -0
  312. package/src/app/useComposerState.ts +367 -0
  313. package/src/app/useConfigSync.ts +288 -0
  314. package/src/app/useInputHandlers.ts +576 -0
  315. package/src/app/useLongRunToolCharms.ts +69 -0
  316. package/src/app/useMainApp.ts +1039 -0
  317. package/src/app/useSessionLifecycle.ts +366 -0
  318. package/src/app/useSubmission.ts +429 -0
  319. package/src/app.tsx +25 -0
  320. package/src/banner.ts +93 -0
  321. package/src/components/activeSessionSwitcher.tsx +635 -0
  322. package/src/components/agentsOverlay.tsx +1073 -0
  323. package/src/components/appChrome.tsx +554 -0
  324. package/src/components/appLayout.tsx +444 -0
  325. package/src/components/appOverlays.tsx +254 -0
  326. package/src/components/branding.tsx +466 -0
  327. package/src/components/fpsOverlay.tsx +30 -0
  328. package/src/components/helpHint.tsx +73 -0
  329. package/src/components/markdown.tsx +1119 -0
  330. package/src/components/maskedPrompt.tsx +34 -0
  331. package/src/components/messageLine.tsx +237 -0
  332. package/src/components/modelPicker.tsx +527 -0
  333. package/src/components/overlayControls.tsx +50 -0
  334. package/src/components/pluginsHub.tsx +238 -0
  335. package/src/components/prompts.tsx +276 -0
  336. package/src/components/queuedMessages.tsx +64 -0
  337. package/src/components/sessionPicker.tsx +227 -0
  338. package/src/components/skillsHub.tsx +308 -0
  339. package/src/components/streamingAssistant.tsx +110 -0
  340. package/src/components/streamingMarkdown.tsx +174 -0
  341. package/src/components/textInput.tsx +1340 -0
  342. package/src/components/themed.tsx +30 -0
  343. package/src/components/thinking.tsx +1224 -0
  344. package/src/components/todoPanel.tsx +93 -0
  345. package/src/config/env.ts +64 -0
  346. package/src/config/limits.ts +13 -0
  347. package/src/config/timing.ts +6 -0
  348. package/src/content/charms.ts +1 -0
  349. package/src/content/faces.ts +17 -0
  350. package/src/content/fortunes.ts +30 -0
  351. package/src/content/hotkeys.ts +37 -0
  352. package/src/content/placeholders.ts +13 -0
  353. package/src/content/setup.ts +17 -0
  354. package/src/content/verbs.ts +38 -0
  355. package/src/domain/blockLayout.ts +146 -0
  356. package/src/domain/details.ts +76 -0
  357. package/src/domain/messages.ts +91 -0
  358. package/src/domain/paths.ts +16 -0
  359. package/src/domain/providers.ts +11 -0
  360. package/src/domain/roles.ts +9 -0
  361. package/src/domain/slash.ts +10 -0
  362. package/src/domain/usage.ts +3 -0
  363. package/src/domain/viewport.ts +51 -0
  364. package/src/entry.tsx +104 -0
  365. package/src/gatewayClient.ts +730 -0
  366. package/src/gatewayTypes.ts +568 -0
  367. package/src/hooks/useCompletion.ts +112 -0
  368. package/src/hooks/useGitBranch.ts +72 -0
  369. package/src/hooks/useInputHistory.ts +11 -0
  370. package/src/hooks/useQueue.ts +76 -0
  371. package/src/hooks/useVirtualHistory.ts +554 -0
  372. package/src/lib/circularBuffer.ts +48 -0
  373. package/src/lib/clipboard.ts +182 -0
  374. package/src/lib/editor.test.ts +74 -0
  375. package/src/lib/editor.ts +47 -0
  376. package/src/lib/emoji.ts +55 -0
  377. package/src/lib/externalCli.ts +16 -0
  378. package/src/lib/externalLink.ts +435 -0
  379. package/src/lib/forceTruecolor.ts +60 -0
  380. package/src/lib/fpsStore.ts +51 -0
  381. package/src/lib/fuzzy.test.ts +109 -0
  382. package/src/lib/fuzzy.ts +177 -0
  383. package/src/lib/gracefulExit.ts +47 -0
  384. package/src/lib/history.ts +82 -0
  385. package/src/lib/inputMetrics.ts +203 -0
  386. package/src/lib/liveProgress.test.ts +116 -0
  387. package/src/lib/liveProgress.ts +79 -0
  388. package/src/lib/mathUnicode.ts +770 -0
  389. package/src/lib/memory.test.ts +155 -0
  390. package/src/lib/memory.ts +188 -0
  391. package/src/lib/memoryMonitor.ts +109 -0
  392. package/src/lib/messages.test.ts +29 -0
  393. package/src/lib/messages.ts +8 -0
  394. package/src/lib/openExternalUrl.test.ts +217 -0
  395. package/src/lib/openExternalUrl.ts +158 -0
  396. package/src/lib/osc52.ts +73 -0
  397. package/src/lib/parentLog.ts +57 -0
  398. package/src/lib/perfPane.tsx +107 -0
  399. package/src/lib/platform.ts +409 -0
  400. package/src/lib/precisionWheel.ts +48 -0
  401. package/src/lib/prompt.ts +35 -0
  402. package/src/lib/reasoning.ts +55 -0
  403. package/src/lib/rpc.ts +41 -0
  404. package/src/lib/subagentTree.ts +355 -0
  405. package/src/lib/syntax.ts +117 -0
  406. package/src/lib/terminalModes.ts +51 -0
  407. package/src/lib/terminalParity.ts +78 -0
  408. package/src/lib/terminalSetup.ts +444 -0
  409. package/src/lib/termux.ts +29 -0
  410. package/src/lib/text.test.ts +18 -0
  411. package/src/lib/text.ts +339 -0
  412. package/src/lib/todo.test.ts +21 -0
  413. package/src/lib/todo.ts +9 -0
  414. package/src/lib/viewportStore.ts +124 -0
  415. package/src/lib/virtualHeights.ts +145 -0
  416. package/src/lib/wheelAccel.ts +190 -0
  417. package/src/protocol/interpolation.ts +3 -0
  418. package/src/protocol/paste.ts +1 -0
  419. package/src/theme.ts +589 -0
  420. package/src/types/nastech-ink.d.ts +176 -0
  421. package/src/types.ts +212 -0
  422. package/tsconfig.build.json +9 -0
  423. package/tsconfig.json +19 -0
  424. package/vitest.config.ts +7 -0
@@ -0,0 +1,224 @@
1
+ import type { DOMElement } from './dom.js'
2
+ import { ClickEvent } from './events/click-event.js'
3
+ import type { EventHandlerProps } from './events/event-handlers.js'
4
+ import { MouseEvent } from './events/mouse-event.js'
5
+ import { nodeCache } from './node-cache.js'
6
+
7
+ function hitTestAbsoluteDescendants(node: DOMElement, col: number, row: number): DOMElement | null {
8
+ for (let i = node.childNodes.length - 1; i >= 0; i--) {
9
+ const child = node.childNodes[i]!
10
+
11
+ if (child.nodeName === '#text') {
12
+ continue
13
+ }
14
+
15
+ if (!nodeCache.get(child)) {
16
+ continue
17
+ }
18
+
19
+ if (child.style.position === 'absolute') {
20
+ const hit = hitTest(child, col, row)
21
+
22
+ if (hit) {
23
+ return hit
24
+ }
25
+ }
26
+
27
+ const nestedHit = hitTestAbsoluteDescendants(child, col, row)
28
+
29
+ if (nestedHit) {
30
+ return nestedHit
31
+ }
32
+ }
33
+
34
+ return null
35
+ }
36
+
37
+ /**
38
+ * Find the deepest DOM element whose rendered rect contains (col, row).
39
+ *
40
+ * Uses the nodeCache populated by renderNodeToOutput — rects are in screen
41
+ * coordinates with all offsets (including scrollTop translation) already
42
+ * applied. Children are traversed in reverse so later siblings (painted on
43
+ * top) win. Nodes not in nodeCache (not rendered this frame, or lacking a
44
+ * yogaNode) are skipped along with their subtrees.
45
+ *
46
+ * Returns the hit node even if it has no onClick — dispatchClick walks up
47
+ * via parentNode to find handlers.
48
+ */
49
+ export function hitTest(node: DOMElement, col: number, row: number): DOMElement | null {
50
+ const rect = nodeCache.get(node)
51
+
52
+ if (!rect) {
53
+ return null
54
+ }
55
+
56
+ const inside = col >= rect.x && col < rect.x + rect.width && row >= rect.y && row < rect.y + rect.height
57
+
58
+ if (!inside) {
59
+ return hitTestAbsoluteDescendants(node, col, row)
60
+ }
61
+
62
+ // Later siblings paint on top; reversed traversal returns topmost hit.
63
+ for (let i = node.childNodes.length - 1; i >= 0; i--) {
64
+ const child = node.childNodes[i]!
65
+
66
+ if (child.nodeName === '#text') {
67
+ continue
68
+ }
69
+
70
+ const hit = hitTest(child, col, row)
71
+
72
+ if (hit) {
73
+ return hit
74
+ }
75
+ }
76
+
77
+ return node
78
+ }
79
+
80
+ /**
81
+ * Hit-test the root at (col, row) and bubble a ClickEvent from the deepest
82
+ * containing node up through parentNode. Only nodes with an onClick handler
83
+ * fire. Stops when a handler calls stopImmediatePropagation(). Returns
84
+ * true if at least one onClick handler fired.
85
+ */
86
+ export function dispatchClick(root: DOMElement, col: number, row: number, cellIsBlank = false): boolean {
87
+ let target: DOMElement | undefined = hitTest(root, col, row) ?? undefined
88
+
89
+ if (!target) {
90
+ return false
91
+ }
92
+
93
+ // Click-to-focus: find the closest focusable ancestor and focus it.
94
+ // root is always ink-root, which owns the FocusManager.
95
+ if (root.focusManager) {
96
+ let focusTarget: DOMElement | undefined = target
97
+
98
+ while (focusTarget) {
99
+ if (typeof focusTarget.attributes['tabIndex'] === 'number') {
100
+ root.focusManager.handleClickFocus(focusTarget)
101
+
102
+ break
103
+ }
104
+
105
+ focusTarget = focusTarget.parentNode
106
+ }
107
+ }
108
+
109
+ const event = new ClickEvent(col, row, cellIsBlank)
110
+ let handled = false
111
+
112
+ while (target) {
113
+ const handler = target._eventHandlers?.onClick as ((event: ClickEvent) => void) | undefined
114
+
115
+ if (handler) {
116
+ handled = true
117
+ const rect = nodeCache.get(target)
118
+
119
+ if (rect) {
120
+ event.localCol = col - rect.x
121
+ event.localRow = row - rect.y
122
+ }
123
+
124
+ handler(event)
125
+
126
+ if (event.didStopImmediatePropagation()) {
127
+ return true
128
+ }
129
+ }
130
+
131
+ target = target.parentNode
132
+ }
133
+
134
+ return handled
135
+ }
136
+
137
+ type MouseHandler = 'onMouseDown' | 'onMouseUp' | 'onMouseDrag'
138
+
139
+ export function dispatchMouse(
140
+ root: DOMElement,
141
+ col: number,
142
+ row: number,
143
+ handlerName: MouseHandler,
144
+ button: number,
145
+ cellIsBlank = false,
146
+ target?: DOMElement
147
+ ): DOMElement | undefined {
148
+ let node: DOMElement | undefined = target ?? hitTest(root, col, row) ?? undefined
149
+
150
+ if (!node) {
151
+ return undefined
152
+ }
153
+
154
+ const event = new MouseEvent(col, row, cellIsBlank, button)
155
+ let handled: DOMElement | undefined
156
+
157
+ while (node) {
158
+ const handler = node._eventHandlers?.[handlerName] as ((event: MouseEvent) => void) | undefined
159
+
160
+ if (handler) {
161
+ handled ??= node
162
+ const rect = nodeCache.get(node)
163
+
164
+ if (rect) {
165
+ event.localCol = col - rect.x
166
+ event.localRow = row - rect.y
167
+ }
168
+
169
+ handler(event)
170
+
171
+ if (event.didStopImmediatePropagation()) {
172
+ return handled
173
+ }
174
+ }
175
+
176
+ node = node.parentNode
177
+ }
178
+
179
+ return handled
180
+ }
181
+
182
+ /**
183
+ * Fire onMouseEnter/onMouseLeave as the pointer moves. Like DOM
184
+ * mouseenter/mouseleave: does NOT bubble — moving between children does
185
+ * not re-fire on the parent. Walks up from the hit node collecting every
186
+ * ancestor with a hover handler; diffs against the previous hovered set;
187
+ * fires leave on the nodes exited, enter on the nodes entered.
188
+ *
189
+ * Mutates `hovered` in place so the caller (App instance) can hold it
190
+ * across calls. Clears the set when the hit is null (cursor moved into a
191
+ * non-rendered gap or off the root rect).
192
+ */
193
+ export function dispatchHover(root: DOMElement, col: number, row: number, hovered: Set<DOMElement>): void {
194
+ const next = new Set<DOMElement>()
195
+ let node: DOMElement | undefined = hitTest(root, col, row) ?? undefined
196
+
197
+ while (node) {
198
+ const h = node._eventHandlers as EventHandlerProps | undefined
199
+
200
+ if (h?.onMouseEnter || h?.onMouseLeave) {
201
+ next.add(node)
202
+ }
203
+
204
+ node = node.parentNode
205
+ }
206
+
207
+ for (const old of hovered) {
208
+ if (!next.has(old)) {
209
+ hovered.delete(old)
210
+
211
+ // Skip handlers on detached nodes (removed between mouse events)
212
+ if (old.parentNode) {
213
+ ;(old._eventHandlers as EventHandlerProps | undefined)?.onMouseLeave?.()
214
+ }
215
+ }
216
+ }
217
+
218
+ for (const n of next) {
219
+ if (!hovered.has(n)) {
220
+ hovered.add(n)
221
+ ;(n._eventHandlers as EventHandlerProps | undefined)?.onMouseEnter?.()
222
+ }
223
+ }
224
+ }
@@ -0,0 +1,62 @@
1
+ import { useContext, useEffect, useState } from 'react'
2
+
3
+ import { ClockContext } from '../components/ClockContext.js'
4
+ import type { DOMElement } from '../dom.js'
5
+
6
+ import { useTerminalViewport } from './use-terminal-viewport.js'
7
+
8
+ /**
9
+ * Hook for synchronized animations that pause when offscreen.
10
+ *
11
+ * Returns a ref to attach to the animated element and the current animation time.
12
+ * All instances share the same clock, so animations stay in sync.
13
+ * The clock only runs when at least one keepAlive subscriber exists.
14
+ *
15
+ * Pass `null` to pause — unsubscribes from the clock so no ticks fire.
16
+ * Time freezes at the last value and resumes from the current clock time
17
+ * when a number is passed again.
18
+ *
19
+ * @param intervalMs - How often to update, or null to pause
20
+ * @returns [ref, time] - Ref to attach to element, elapsed time in ms
21
+ *
22
+ * @example
23
+ * function Spinner() {
24
+ * const [ref, time] = useAnimationFrame(120)
25
+ * const frame = Math.floor(time / 120) % FRAMES.length
26
+ * return <Box ref={ref}>{FRAMES[frame]}</Box>
27
+ * }
28
+ *
29
+ * The clock automatically slows when the terminal is blurred,
30
+ * so consumers don't need to handle focus state.
31
+ */
32
+ export function useAnimationFrame(
33
+ intervalMs: number | null = 16
34
+ ): [ref: (element: DOMElement | null) => void, time: number] {
35
+ const clock = useContext(ClockContext)
36
+ const [viewportRef, { isVisible }] = useTerminalViewport()
37
+ const [time, setTime] = useState(() => clock?.now() ?? 0)
38
+
39
+ const active = isVisible && intervalMs !== null
40
+
41
+ useEffect(() => {
42
+ if (!clock || !active) {
43
+ return
44
+ }
45
+
46
+ let lastUpdate = clock.now()
47
+
48
+ const onChange = (): void => {
49
+ const now = clock.now()
50
+
51
+ if (now - lastUpdate >= intervalMs!) {
52
+ lastUpdate = now
53
+ setTime(now)
54
+ }
55
+ }
56
+
57
+ // keepAlive: true — visible animations drive the clock
58
+ return clock.subscribe(onChange, true)
59
+ }, [clock, intervalMs, active])
60
+
61
+ return [viewportRef, time]
62
+ }
@@ -0,0 +1,9 @@
1
+ import { useContext } from 'react'
2
+
3
+ import AppContext from '../components/AppContext.js'
4
+
5
+ /**
6
+ * `useApp` is a React hook, which exposes a method to manually exit the app (unmount).
7
+ */
8
+ const useApp = () => useContext(AppContext)
9
+ export default useApp
@@ -0,0 +1,33 @@
1
+ import { useContext } from 'react'
2
+
3
+ import CursorAdvanceContext, { type CursorAdvanceNotifier } from '../components/CursorAdvanceContext.js'
4
+
5
+ /**
6
+ * Returns a function that notifies Ink the physical terminal cursor was
7
+ * advanced out-of-band (e.g. by a direct stdout.write from the
8
+ * TextInput fast-echo bypass).
9
+ *
10
+ * Calling the returned function updates two pieces of Ink state:
11
+ *
12
+ * - `displayCursor` — the cached parked-cursor position log-update
13
+ * uses as the relative-move basis for the next frame. Skipped on
14
+ * alt-screen, where every frame's CSI H resets the cursor anyway.
15
+ *
16
+ * - The active `cursorDeclaration` — the target the cursor parks at
17
+ * after every frame. Bumped on BOTH main- and alt-screen, because
18
+ * onRender's alt-screen park branch emits an absolute CUP from
19
+ * this value and a stale declaration there is still visibly wrong.
20
+ * The next React commit that publishes a fresh declaration
21
+ * supersedes the bump.
22
+ *
23
+ * The caller is responsible for the stdout write itself; this hook
24
+ * only reports the resulting cursor delta. Pass `dx` and optional
25
+ * `dy` in terminal cells (positive = moved right/down, negative =
26
+ * moved left/up).
27
+ *
28
+ * If the host isn't an Ink render root (test stubs, non-Ink renderer)
29
+ * the returned callback is a safe no-op.
30
+ */
31
+ export function useCursorAdvance(): CursorAdvanceNotifier {
32
+ return useContext(CursorAdvanceContext)
33
+ }
@@ -0,0 +1,75 @@
1
+ import { useCallback, useContext, useLayoutEffect, useRef } from 'react'
2
+
3
+ import CursorDeclarationContext from '../components/CursorDeclarationContext.js'
4
+ import type { DOMElement } from '../dom.js'
5
+
6
+ /**
7
+ * Declares where the terminal cursor should be parked after each frame.
8
+ *
9
+ * Terminal emulators render IME preedit text at the physical cursor
10
+ * position, and screen readers / screen magnifiers track the native
11
+ * cursor — so parking it at the text input's caret makes CJK input
12
+ * appear inline and lets accessibility tools follow the input.
13
+ *
14
+ * Returns a ref callback to attach to the Box that contains the input.
15
+ * The declared (line, column) is interpreted relative to that Box's
16
+ * nodeCache rect (populated by renderNodeToOutput).
17
+ *
18
+ * Timing: Both ref attach and useLayoutEffect fire in React's layout
19
+ * phase — after resetAfterCommit calls scheduleRender. scheduleRender
20
+ * defers onRender via queueMicrotask, so onRender runs AFTER layout
21
+ * effects commit and reads the fresh declaration on the first frame
22
+ * (no one-keystroke lag). Test env uses onImmediateRender (synchronous,
23
+ * no microtask), so tests compensate by calling ink.onRender()
24
+ * explicitly after render.
25
+ */
26
+ export function useDeclaredCursor({
27
+ line,
28
+ column,
29
+ active
30
+ }: {
31
+ line: number
32
+ column: number
33
+ active: boolean
34
+ }): (element: DOMElement | null) => void {
35
+ const setCursorDeclaration = useContext(CursorDeclarationContext)
36
+ const nodeRef = useRef<DOMElement | null>(null)
37
+
38
+ const setNode = useCallback((node: DOMElement | null) => {
39
+ nodeRef.current = node
40
+ }, [])
41
+
42
+ // When active, set unconditionally. When inactive, clear conditionally
43
+ // (only if the currently-declared node is ours). The node-identity check
44
+ // handles two hazards:
45
+ // 1. A memo()ized active instance elsewhere (e.g. the search input in
46
+ // a memo'd Footer) doesn't re-render this commit — an inactive
47
+ // instance re-rendering here must not clobber it.
48
+ // 2. Sibling handoff (menu focus moving between list items) — when
49
+ // focus moves opposite to sibling order, the newly-inactive item's
50
+ // effect runs AFTER the newly-active item's set. Without the node
51
+ // check it would clobber.
52
+ // No dep array: must re-declare every commit so the active instance
53
+ // re-claims the declaration after another instance's unmount-cleanup or
54
+ // sibling handoff nulls it.
55
+ useLayoutEffect(() => {
56
+ const node = nodeRef.current
57
+
58
+ if (active && node) {
59
+ setCursorDeclaration({ relativeX: column, relativeY: line, node })
60
+ } else {
61
+ setCursorDeclaration(null, node)
62
+ }
63
+ })
64
+
65
+ // Clear on unmount (conditionally — another instance may own by then).
66
+ // Separate effect with empty deps so cleanup only fires once — not on
67
+ // every line/column change, which would transiently null between commits.
68
+ useLayoutEffect(() => {
69
+ return () => {
70
+ setCursorDeclaration(null, nodeRef.current)
71
+ }
72
+ }, [setCursorDeclaration])
73
+
74
+ return setNode
75
+ }
@@ -0,0 +1,27 @@
1
+ import { useCallback } from 'react'
2
+
3
+ import instances from '../instances.js'
4
+
5
+ export type RunExternalProcess = () => Promise<void>
6
+
7
+ export async function withInkSuspended(run: RunExternalProcess): Promise<void> {
8
+ const ink = instances.get(process.stdout)
9
+
10
+ if (!ink) {
11
+ await run()
12
+
13
+ return
14
+ }
15
+
16
+ ink.enterAlternateScreen()
17
+
18
+ try {
19
+ await run()
20
+ } finally {
21
+ ink.exitAlternateScreen()
22
+ }
23
+ }
24
+
25
+ export function useExternalProcess(): (run: RunExternalProcess) => Promise<void> {
26
+ return useCallback((run: RunExternalProcess) => withInkSuspended(run), [])
27
+ }
@@ -0,0 +1,95 @@
1
+ import { useEffect, useLayoutEffect } from 'react'
2
+ import { useEventCallback } from 'usehooks-ts'
3
+
4
+ import type { InputEvent, Key } from '../events/input-event.js'
5
+
6
+ import useStdin from './use-stdin.js'
7
+
8
+ type Handler = (input: string, key: Key, event: InputEvent) => void
9
+
10
+ type Options = {
11
+ /**
12
+ * Enable or disable capturing of user input.
13
+ * Useful when there are multiple useInput hooks used at once to avoid handling the same input several times.
14
+ *
15
+ * @default true
16
+ */
17
+ isActive?: boolean
18
+ }
19
+
20
+ /**
21
+ * This hook is used for handling user input.
22
+ * It's a more convenient alternative to using `StdinContext` and listening to `data` events.
23
+ * The callback you pass to `useInput` is called for each character when user enters any input.
24
+ * However, if user pastes text and it's more than one character, the callback will be called only once and the whole string will be passed as `input`.
25
+ *
26
+ * ```
27
+ * import {useInput} from 'ink';
28
+ *
29
+ * const UserInput = () => {
30
+ * useInput((input, key) => {
31
+ * if (input === 'q') {
32
+ * // Exit program
33
+ * }
34
+ *
35
+ * if (key.leftArrow) {
36
+ * // Left arrow key pressed
37
+ * }
38
+ * });
39
+ *
40
+ * return …
41
+ * };
42
+ * ```
43
+ */
44
+ const useInput = (inputHandler: Handler, options: Options = {}) => {
45
+ const { setRawMode, exitOnCtrlC, inputEmitter } = useStdin()
46
+
47
+ // useLayoutEffect (not useEffect) so that raw mode is enabled synchronously
48
+ // during React's commit phase, before render() returns. With useEffect, raw
49
+ // mode setup is deferred to the next event loop tick via React's scheduler,
50
+ // leaving the terminal in cooked mode — keystrokes echo and the cursor is
51
+ // visible until the effect fires.
52
+ useLayoutEffect(() => {
53
+ if (options.isActive === false) {
54
+ return
55
+ }
56
+
57
+ setRawMode(true)
58
+
59
+ return () => {
60
+ setRawMode(false)
61
+ }
62
+ }, [options.isActive, setRawMode])
63
+
64
+ // Register the listener once on mount so its slot in the EventEmitter's
65
+ // listener array is stable. If isActive were in the effect's deps, the
66
+ // listener would re-append on false→true, moving it behind listeners
67
+ // that registered while it was inactive — breaking
68
+ // stopImmediatePropagation() ordering. useEventCallback keeps the
69
+ // reference stable while reading latest isActive/inputHandler from
70
+ // closure (it syncs via useLayoutEffect, so it's compiler-safe).
71
+ const handleData = useEventCallback((event: InputEvent) => {
72
+ if (options.isActive === false) {
73
+ return
74
+ }
75
+
76
+ const { input, key } = event
77
+
78
+ // If app is not supposed to exit on Ctrl+C, then let input listener handle it
79
+ // Note: discreteUpdates is called at the App level when emitting events,
80
+ // so all listeners are already within a high-priority update context.
81
+ if (!(input === 'c' && key.ctrl) || !exitOnCtrlC) {
82
+ inputHandler(input, key, event)
83
+ }
84
+ })
85
+
86
+ useEffect(() => {
87
+ inputEmitter?.on('input', handleData)
88
+
89
+ return () => {
90
+ inputEmitter?.removeListener('input', handleData)
91
+ }
92
+ }, [inputEmitter, handleData])
93
+ }
94
+
95
+ export default useInput
@@ -0,0 +1,71 @@
1
+ import { useContext, useEffect, useRef, useState } from 'react'
2
+
3
+ import { ClockContext } from '../components/ClockContext.js'
4
+
5
+ /**
6
+ * Returns the clock time, updating at the given interval.
7
+ * Subscribes as non-keepAlive — won't keep the clock alive on its own,
8
+ * but updates whenever a keepAlive subscriber (e.g. the spinner)
9
+ * is driving the clock.
10
+ *
11
+ * Use this to drive pure time-based computations (shimmer position,
12
+ * frame index) from the shared clock.
13
+ */
14
+ export function useAnimationTimer(intervalMs: number): number {
15
+ const clock = useContext(ClockContext)
16
+ const [time, setTime] = useState(() => clock?.now() ?? 0)
17
+
18
+ useEffect(() => {
19
+ if (!clock) {
20
+ return
21
+ }
22
+
23
+ let lastUpdate = clock.now()
24
+
25
+ const onChange = (): void => {
26
+ const now = clock.now()
27
+
28
+ if (now - lastUpdate >= intervalMs) {
29
+ lastUpdate = now
30
+ setTime(now)
31
+ }
32
+ }
33
+
34
+ return clock.subscribe(onChange, false)
35
+ }, [clock, intervalMs])
36
+
37
+ return time
38
+ }
39
+
40
+ /**
41
+ * Interval hook backed by the shared Clock.
42
+ *
43
+ * Unlike `useInterval` from `usehooks-ts` (which creates its own setInterval),
44
+ * this piggybacks on the single shared clock so all timers consolidate into
45
+ * one wake-up. Pass `null` for intervalMs to pause.
46
+ */
47
+ export function useInterval(callback: () => void, intervalMs: number | null): void {
48
+ const callbackRef = useRef(callback)
49
+ callbackRef.current = callback
50
+
51
+ const clock = useContext(ClockContext)
52
+
53
+ useEffect(() => {
54
+ if (!clock || intervalMs === null) {
55
+ return
56
+ }
57
+
58
+ let lastUpdate = clock.now()
59
+
60
+ const onChange = (): void => {
61
+ const now = clock.now()
62
+
63
+ if (now - lastUpdate >= intervalMs) {
64
+ lastUpdate = now
65
+ callbackRef.current()
66
+ }
67
+ }
68
+
69
+ return clock.subscribe(onChange, false)
70
+ }, [clock, intervalMs])
71
+ }
@@ -0,0 +1,56 @@
1
+ import { useContext, useMemo } from 'react'
2
+
3
+ import StdinContext from '../components/StdinContext.js'
4
+ import type { DOMElement } from '../dom.js'
5
+ import instances from '../instances.js'
6
+ import type { MatchPosition } from '../render-to-screen.js'
7
+
8
+ /**
9
+ * Set the search highlight query on the Ink instance. Non-empty → all
10
+ * visible occurrences are inverted on the next frame (SGR 7, screen-buffer
11
+ * overlay, same damage machinery as selection). Empty → clears.
12
+ *
13
+ * This is a screen-space highlight — it matches the RENDERED text, not the
14
+ * source message text. Works for anything visible (bash output, file paths,
15
+ * error messages) regardless of where it came from in the message tree. A
16
+ * query that matched in source but got truncated/ellipsized in rendering
17
+ * won't highlight; that's acceptable — we highlight what you see.
18
+ */
19
+ export function useSearchHighlight(): {
20
+ setQuery: (query: string) => void
21
+ /** Paint an existing DOM subtree (from the MAIN tree) to a fresh
22
+ * Screen at its natural height, scan. Element-relative positions
23
+ * (row 0 = element top). Zero context duplication — the element
24
+ * IS the one built with all real providers. */
25
+ scanElement: (el: DOMElement) => MatchPosition[]
26
+ /** Position-based CURRENT highlight. Every frame writes yellow at
27
+ * positions[currentIdx] + rowOffset. The scan-highlight (inverse on
28
+ * all matches) still runs — this overlays on top. rowOffset tracks
29
+ * scroll; positions stay stable (message-relative). null clears. */
30
+ setPositions: (
31
+ state: {
32
+ positions: MatchPosition[]
33
+ rowOffset: number
34
+ currentIdx: number
35
+ } | null
36
+ ) => void
37
+ } {
38
+ useContext(StdinContext) // anchor to App subtree for hook rules
39
+ const ink = instances.get(process.stdout)
40
+
41
+ return useMemo(() => {
42
+ if (!ink) {
43
+ return {
44
+ setQuery: () => {},
45
+ scanElement: () => [],
46
+ setPositions: () => {}
47
+ }
48
+ }
49
+
50
+ return {
51
+ setQuery: (query: string) => ink.setSearchHighlight(query),
52
+ scanElement: (el: DOMElement) => ink.scanElementSubtree(el),
53
+ setPositions: state => ink.setSearchPositions(state)
54
+ }
55
+ }, [ink])
56
+ }