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,51 @@
1
+ import supportsHyperlinksLib from 'supports-hyperlinks'
2
+
3
+ // Additional terminals that support OSC 8 hyperlinks but aren't detected by supports-hyperlinks.
4
+ // Checked against both TERM_PROGRAM and LC_TERMINAL (the latter is preserved inside tmux).
5
+ export const ADDITIONAL_HYPERLINK_TERMINALS = ['ghostty', 'Hyper', 'kitty', 'alacritty', 'iTerm.app', 'iTerm2']
6
+
7
+ type EnvLike = Record<string, string | undefined>
8
+
9
+ type SupportsHyperlinksOptions = {
10
+ env?: EnvLike
11
+ stdoutSupported?: boolean
12
+ }
13
+
14
+ /**
15
+ * Returns whether stdout supports OSC 8 hyperlinks.
16
+ * Extends the supports-hyperlinks library with additional terminal detection.
17
+ * @param options Optional overrides for testing (env, stdoutSupported)
18
+ */
19
+ export function supportsHyperlinks(options?: SupportsHyperlinksOptions): boolean {
20
+ const stdoutSupported = options?.stdoutSupported ?? supportsHyperlinksLib.stdout
21
+
22
+ if (stdoutSupported) {
23
+ return true
24
+ }
25
+
26
+ const env = options?.env ?? process.env
27
+
28
+ // Check for additional terminals not detected by supports-hyperlinks
29
+ const termProgram = env['TERM_PROGRAM']
30
+
31
+ if (termProgram && ADDITIONAL_HYPERLINK_TERMINALS.includes(termProgram)) {
32
+ return true
33
+ }
34
+
35
+ // LC_TERMINAL is set by some terminals (e.g. iTerm2) and preserved inside tmux,
36
+ // where TERM_PROGRAM is overwritten to 'tmux'.
37
+ const lcTerminal = env['LC_TERMINAL']
38
+
39
+ if (lcTerminal && ADDITIONAL_HYPERLINK_TERMINALS.includes(lcTerminal)) {
40
+ return true
41
+ }
42
+
43
+ // Kitty sets TERM=xterm-kitty
44
+ const term = env['TERM']
45
+
46
+ if (term?.includes('kitty')) {
47
+ return true
48
+ }
49
+
50
+ return false
51
+ }
@@ -0,0 +1,44 @@
1
+ // Tab expansion, inspired by Ghostty's Tabstops.zig
2
+ // Uses 8-column intervals (POSIX default, hardcoded in terminals like Ghostty)
3
+
4
+ import { stringWidth } from './stringWidth.js'
5
+ import { createTokenizer } from './termio/tokenize.js'
6
+
7
+ const DEFAULT_TAB_INTERVAL = 8
8
+
9
+ export function expandTabs(text: string, interval = DEFAULT_TAB_INTERVAL): string {
10
+ if (!text.includes('\t')) {
11
+ return text
12
+ }
13
+
14
+ const tokenizer = createTokenizer()
15
+ const tokens = tokenizer.feed(text)
16
+ tokens.push(...tokenizer.flush())
17
+
18
+ let result = ''
19
+ let column = 0
20
+
21
+ for (const token of tokens) {
22
+ if (token.type === 'sequence') {
23
+ result += token.value
24
+ } else {
25
+ const parts = token.value.split(/(\t|\n)/)
26
+
27
+ for (const part of parts) {
28
+ if (part === '\t') {
29
+ const spaces = interval - (column % interval)
30
+ result += ' '.repeat(spaces)
31
+ column += spaces
32
+ } else if (part === '\n') {
33
+ result += part
34
+ column = 0
35
+ } else {
36
+ result += part
37
+ column += stringWidth(part)
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ return result
44
+ }
@@ -0,0 +1,52 @@
1
+ // Terminal focus state signal — non-React access to DECSET 1004 focus events.
2
+ // 'unknown' is the default for terminals that don't support focus reporting;
3
+ // consumers treat 'unknown' identically to 'focused' (no throttling).
4
+ // Subscribers are notified synchronously when focus changes, used by
5
+ // TerminalFocusProvider to avoid polling.
6
+ export type TerminalFocusState = 'focused' | 'blurred' | 'unknown'
7
+
8
+ let focusState: TerminalFocusState = 'unknown'
9
+ const resolvers: Set<() => void> = new Set()
10
+ const subscribers: Set<() => void> = new Set()
11
+
12
+ export function setTerminalFocused(v: boolean): void {
13
+ focusState = v ? 'focused' : 'blurred'
14
+
15
+ // Notify useSyncExternalStore subscribers
16
+ for (const cb of subscribers) {
17
+ cb()
18
+ }
19
+
20
+ if (!v) {
21
+ for (const resolve of resolvers) {
22
+ resolve()
23
+ }
24
+
25
+ resolvers.clear()
26
+ }
27
+ }
28
+
29
+ export function getTerminalFocused(): boolean {
30
+ return focusState !== 'blurred'
31
+ }
32
+
33
+ export function getTerminalFocusState(): TerminalFocusState {
34
+ return focusState
35
+ }
36
+
37
+ // For useSyncExternalStore
38
+ export function subscribeTerminalFocus(cb: () => void): () => void {
39
+ subscribers.add(cb)
40
+
41
+ return () => {
42
+ subscribers.delete(cb)
43
+ }
44
+ }
45
+
46
+ export function resetTerminalFocusState(): void {
47
+ focusState = 'unknown'
48
+
49
+ for (const cb of subscribers) {
50
+ cb()
51
+ }
52
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Query the terminal and await responses without timeouts.
3
+ *
4
+ * Terminal queries (DECRQM, DA1, OSC 11, etc.) share the stdin stream
5
+ * with keyboard input. Response sequences are syntactically
6
+ * distinguishable from key events, so the input parser recognizes them
7
+ * and dispatches them here.
8
+ *
9
+ * To avoid timeouts, each query batch is terminated by a DA1 sentinel
10
+ * (CSI c) — every terminal since VT100 responds to DA1, and terminals
11
+ * answer queries in order. So: if your query's response arrives before
12
+ * DA1's, the terminal supports it; if DA1 arrives first, it doesn't.
13
+ *
14
+ * Usage:
15
+ * const [sync, grapheme] = await Promise.all([
16
+ * querier.send(decrqm(2026)),
17
+ * querier.send(decrqm(2027)),
18
+ * querier.flush(),
19
+ * ])
20
+ * // sync and grapheme are DECRPM responses or undefined if unsupported
21
+ */
22
+
23
+ import type { TerminalResponse } from './parse-keypress.js'
24
+ import { csi } from './termio/csi.js'
25
+ import { osc } from './termio/osc.js'
26
+
27
+ /** A terminal query: an outbound request sequence paired with a matcher
28
+ * that recognizes the expected inbound response. Built by `decrqm()`,
29
+ * `oscColor()`, `kittyKeyboard()`, etc. */
30
+ export type TerminalQuery<T extends TerminalResponse = TerminalResponse> = {
31
+ /** Escape sequence to write to stdout */
32
+ request: string
33
+ /** Recognizes the expected response in the inbound stream */
34
+ match: (r: TerminalResponse) => r is T
35
+ }
36
+
37
+ type DecrpmResponse = Extract<TerminalResponse, { type: 'decrpm' }>
38
+ type Da1Response = Extract<TerminalResponse, { type: 'da1' }>
39
+ type Da2Response = Extract<TerminalResponse, { type: 'da2' }>
40
+ type KittyResponse = Extract<TerminalResponse, { type: 'kittyKeyboard' }>
41
+ type CursorPosResponse = Extract<TerminalResponse, { type: 'cursorPosition' }>
42
+ type OscResponse = Extract<TerminalResponse, { type: 'osc' }>
43
+ type XtversionResponse = Extract<TerminalResponse, { type: 'xtversion' }>
44
+
45
+ // -- Query builders --
46
+
47
+ /** DECRQM: request DEC private mode status (CSI ? mode $ p).
48
+ * Terminal replies with DECRPM (CSI ? mode ; status $ y) or ignores. */
49
+ export function decrqm(mode: number): TerminalQuery<DecrpmResponse> {
50
+ return {
51
+ request: csi(`?${mode}$p`),
52
+ match: (r): r is DecrpmResponse => r.type === 'decrpm' && r.mode === mode
53
+ }
54
+ }
55
+
56
+ /** Primary Device Attributes query (CSI c). Every terminal answers this —
57
+ * used internally by flush() as a universal sentinel. Call directly if
58
+ * you want the DA1 params. */
59
+ export function da1(): TerminalQuery<Da1Response> {
60
+ return {
61
+ request: csi('c'),
62
+ match: (r): r is Da1Response => r.type === 'da1'
63
+ }
64
+ }
65
+
66
+ /** Secondary Device Attributes query (CSI > c). Returns terminal version. */
67
+ export function da2(): TerminalQuery<Da2Response> {
68
+ return {
69
+ request: csi('>c'),
70
+ match: (r): r is Da2Response => r.type === 'da2'
71
+ }
72
+ }
73
+
74
+ /** Query current Kitty keyboard protocol flags (CSI ? u).
75
+ * Terminal replies with CSI ? flags u or ignores. */
76
+ export function kittyKeyboard(): TerminalQuery<KittyResponse> {
77
+ return {
78
+ request: csi('?u'),
79
+ match: (r): r is KittyResponse => r.type === 'kittyKeyboard'
80
+ }
81
+ }
82
+
83
+ /** DECXCPR: request cursor position with DEC-private marker (CSI ? 6 n).
84
+ * Terminal replies with CSI ? row ; col R. The `?` marker is critical —
85
+ * the plain DSR form (CSI 6 n → CSI row;col R) is ambiguous with
86
+ * modified F3 keys (Shift+F3 = CSI 1;2 R, etc.). */
87
+ export function cursorPosition(): TerminalQuery<CursorPosResponse> {
88
+ return {
89
+ request: csi('?6n'),
90
+ match: (r): r is CursorPosResponse => r.type === 'cursorPosition'
91
+ }
92
+ }
93
+
94
+ /** OSC dynamic color query (e.g. OSC 11 for bg color, OSC 10 for fg).
95
+ * The `?` data slot asks the terminal to reply with the current value. */
96
+ export function oscColor(code: number): TerminalQuery<OscResponse> {
97
+ return {
98
+ request: osc(code, '?'),
99
+ match: (r): r is OscResponse => r.type === 'osc' && r.code === code
100
+ }
101
+ }
102
+
103
+ /** XTVERSION: request terminal name/version (CSI > 0 q).
104
+ * Terminal replies with DCS > | name ST (e.g. "xterm.js(5.5.0)") or ignores.
105
+ * This survives SSH — the query goes through the pty, not the environment,
106
+ * so it identifies the *client* terminal even when TERM_PROGRAM isn't
107
+ * forwarded. Used to detect xterm.js for wheel-scroll compensation. */
108
+ export function xtversion(): TerminalQuery<XtversionResponse> {
109
+ return {
110
+ request: csi('>0q'),
111
+ match: (r): r is XtversionResponse => r.type === 'xtversion'
112
+ }
113
+ }
114
+
115
+ // -- Querier --
116
+
117
+ /** Sentinel request sequence (DA1). Kept internal; flush() writes it. */
118
+ const SENTINEL = csi('c')
119
+
120
+ type Pending =
121
+ | {
122
+ kind: 'query'
123
+ match: (r: TerminalResponse) => boolean
124
+ resolve: (r: TerminalResponse | undefined) => void
125
+ }
126
+ | { kind: 'sentinel'; resolve: () => void }
127
+
128
+ export class TerminalQuerier {
129
+ /**
130
+ * Interleaved queue of queries and sentinels in send order. Terminals
131
+ * respond in order, so each flush() barrier only drains queries queued
132
+ * before it — concurrent batches from independent callers stay isolated.
133
+ */
134
+ private queue: Pending[] = []
135
+
136
+ constructor(private stdout: NodeJS.WriteStream) {}
137
+
138
+ /**
139
+ * Send a query and wait for its response.
140
+ *
141
+ * Resolves with the response when `query.match` matches an incoming
142
+ * TerminalResponse, or with `undefined` when a flush() sentinel arrives
143
+ * before any matching response (meaning the terminal ignored the query).
144
+ *
145
+ * Never rejects; never times out on its own. If you never call flush()
146
+ * and the terminal doesn't respond, the promise remains pending.
147
+ */
148
+ send<T extends TerminalResponse>(query: TerminalQuery<T>): Promise<T | undefined> {
149
+ return new Promise(resolve => {
150
+ this.queue.push({
151
+ kind: 'query',
152
+ match: query.match,
153
+ resolve: r => resolve(r as T | undefined)
154
+ })
155
+ this.stdout.write(query.request)
156
+ })
157
+ }
158
+
159
+ /**
160
+ * Send the DA1 sentinel. Resolves when DA1's response arrives.
161
+ *
162
+ * As a side effect, all queries still pending when DA1 arrives are
163
+ * resolved with `undefined` (terminal didn't respond → doesn't support
164
+ * the query). This is the barrier that makes send() timeout-free.
165
+ *
166
+ * Safe to call with no pending queries — still waits for a round-trip.
167
+ */
168
+ flush(): Promise<void> {
169
+ return new Promise(resolve => {
170
+ this.queue.push({ kind: 'sentinel', resolve })
171
+ this.stdout.write(SENTINEL)
172
+ })
173
+ }
174
+
175
+ /**
176
+ * Dispatch a response parsed from stdin. Called by App.tsx's
177
+ * processKeysInBatch for every `kind: 'response'` item.
178
+ *
179
+ * Matching strategy:
180
+ * - First, try to match a pending query (FIFO, first match wins).
181
+ * This lets callers send(da1()) explicitly if they want the DA1
182
+ * params — a separate DA1 write means the terminal sends TWO DA1
183
+ * responses. The first matches the explicit query; the second
184
+ * (unmatched) fires the sentinel.
185
+ * - Otherwise, if this is a DA1, fire the FIRST pending sentinel:
186
+ * resolve any queries queued before that sentinel with undefined
187
+ * (the terminal answered DA1 without answering them → unsupported)
188
+ * and signal its flush() completion. Only draining up to the first
189
+ * sentinel keeps later batches intact when multiple callers have
190
+ * concurrent queries in flight.
191
+ * - Unsolicited responses (no match, no sentinel) are silently dropped.
192
+ */
193
+ onResponse(r: TerminalResponse): void {
194
+ const idx = this.queue.findIndex(p => p.kind === 'query' && p.match(r))
195
+
196
+ if (idx !== -1) {
197
+ const [q] = this.queue.splice(idx, 1)
198
+
199
+ if (q?.kind === 'query') {
200
+ q.resolve(r)
201
+ }
202
+
203
+ return
204
+ }
205
+
206
+ if (r.type === 'da1') {
207
+ const s = this.queue.findIndex(p => p.kind === 'sentinel')
208
+
209
+ if (s === -1) {
210
+ return
211
+ }
212
+
213
+ for (const p of this.queue.splice(0, s + 1)) {
214
+ if (p.kind === 'query') {
215
+ p.resolve(undefined)
216
+ } else {
217
+ p.resolve()
218
+ }
219
+ }
220
+ }
221
+ }
222
+ }
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { needsAltScreenResizeScrollbackClear } from './terminal.js'
4
+
5
+ describe('terminal resize quirks', () => {
6
+ it('uses a deeper alt-screen resize clear for Apple Terminal', () => {
7
+ expect(needsAltScreenResizeScrollbackClear({ TERM_PROGRAM: 'Apple_Terminal' })).toBe(true)
8
+ expect(needsAltScreenResizeScrollbackClear({ TERM_PROGRAM: ' Apple_Terminal ' })).toBe(true)
9
+ })
10
+
11
+ it('keeps the normal resize repaint path for modern terminals', () => {
12
+ expect(needsAltScreenResizeScrollbackClear({ TERM_PROGRAM: 'vscode' })).toBe(false)
13
+ expect(needsAltScreenResizeScrollbackClear({ TERM_PROGRAM: 'iTerm.app' })).toBe(false)
14
+ })
15
+ })
@@ -0,0 +1,299 @@
1
+ import type { Writable } from 'stream'
2
+
3
+ import { coerce } from 'semver'
4
+
5
+ import { env } from '../utils/env.js'
6
+ import { gte } from '../utils/semver.js'
7
+
8
+ import { getClearTerminalSequence } from './clearTerminal.js'
9
+ import type { Diff } from './frame.js'
10
+ import { cursorMove, cursorTo, eraseLines } from './termio/csi.js'
11
+ import { BSU, ESU, HIDE_CURSOR, SHOW_CURSOR } from './termio/dec.js'
12
+ import { link } from './termio/osc.js'
13
+
14
+ export type Progress = {
15
+ state: 'running' | 'completed' | 'error' | 'indeterminate'
16
+ percentage?: number
17
+ }
18
+
19
+ /**
20
+ * Checks if the terminal supports OSC 9;4 progress reporting.
21
+ * Supported terminals:
22
+ * - ConEmu (Windows) - all versions
23
+ * - Ghostty 1.2.0+
24
+ * - iTerm2 3.6.6+
25
+ *
26
+ * Note: Windows Terminal interprets OSC 9;4 as notifications, not progress.
27
+ */
28
+ export function isProgressReportingAvailable(): boolean {
29
+ // Only available if we have a TTY (not piped)
30
+ if (!process.stdout.isTTY) {
31
+ return false
32
+ }
33
+
34
+ // Explicitly exclude Windows Terminal, which interprets OSC 9;4 as
35
+ // notifications rather than progress indicators
36
+ if (process.env.WT_SESSION) {
37
+ return false
38
+ }
39
+
40
+ // ConEmu supports OSC 9;4 for progress (all versions)
41
+ if (process.env.ConEmuANSI || process.env.ConEmuPID || process.env.ConEmuTask) {
42
+ return true
43
+ }
44
+
45
+ const version = coerce(process.env.TERM_PROGRAM_VERSION)
46
+
47
+ if (!version) {
48
+ return false
49
+ }
50
+
51
+ // Ghostty 1.2.0+ supports OSC 9;4 for progress
52
+ // https://ghostty.org/docs/install/release-notes/1-2-0
53
+ if (process.env.TERM_PROGRAM === 'ghostty') {
54
+ return gte(version.version, '1.2.0')
55
+ }
56
+
57
+ // iTerm2 3.6.6+ supports OSC 9;4 for progress
58
+ // https://iterm2.com/downloads.html
59
+ if (process.env.TERM_PROGRAM === 'iTerm.app') {
60
+ return gte(version.version, '3.6.6')
61
+ }
62
+
63
+ return false
64
+ }
65
+
66
+ /**
67
+ * Checks if the terminal supports DEC mode 2026 (synchronized output).
68
+ * When supported, BSU/ESU sequences prevent visible flicker during redraws.
69
+ */
70
+ export function isSynchronizedOutputSupported(): boolean {
71
+ // tmux parses and proxies every byte but doesn't implement DEC 2026.
72
+ // BSU/ESU pass through to the outer terminal but tmux has already
73
+ // broken atomicity by chunking. Skip to save 16 bytes/frame + parser work.
74
+ if (process.env.TMUX) {
75
+ return false
76
+ }
77
+
78
+ const termProgram = process.env.TERM_PROGRAM
79
+ const term = process.env.TERM
80
+
81
+ // Modern terminals with known DEC 2026 support
82
+ if (
83
+ termProgram === 'iTerm.app' ||
84
+ termProgram === 'WezTerm' ||
85
+ termProgram === 'WarpTerminal' ||
86
+ termProgram === 'ghostty' ||
87
+ termProgram === 'contour' ||
88
+ termProgram === 'vscode' ||
89
+ termProgram === 'alacritty'
90
+ ) {
91
+ return true
92
+ }
93
+
94
+ // kitty sets TERM=xterm-kitty or KITTY_WINDOW_ID
95
+ if (term?.includes('kitty') || process.env.KITTY_WINDOW_ID) {
96
+ return true
97
+ }
98
+
99
+ // Ghostty may set TERM=xterm-ghostty without TERM_PROGRAM
100
+ if (term === 'xterm-ghostty') {
101
+ return true
102
+ }
103
+
104
+ // foot sets TERM=foot or TERM=foot-extra
105
+ if (term?.startsWith('foot')) {
106
+ return true
107
+ }
108
+
109
+ // Alacritty may set TERM containing 'alacritty'
110
+ if (term?.includes('alacritty')) {
111
+ return true
112
+ }
113
+
114
+ // Zed uses the alacritty_terminal crate which supports DEC 2026
115
+ if (process.env.ZED_TERM) {
116
+ return true
117
+ }
118
+
119
+ // Windows Terminal
120
+ if (process.env.WT_SESSION) {
121
+ return true
122
+ }
123
+
124
+ // VTE-based terminals (GNOME Terminal, Tilix, etc.) since VTE 0.68
125
+ const vteVersion = process.env.VTE_VERSION
126
+
127
+ if (vteVersion) {
128
+ const version = parseInt(vteVersion, 10)
129
+
130
+ if (version >= 6800) {
131
+ return true
132
+ }
133
+ }
134
+
135
+ return false
136
+ }
137
+
138
+ // -- XTVERSION-detected terminal name (populated async at startup) --
139
+ //
140
+ // TERM_PROGRAM is not forwarded over SSH by default, so env-based detection
141
+ // fails when the process runs remotely inside a VS Code integrated terminal.
142
+ // XTVERSION (CSI > 0 q → DCS > | name ST) goes through the pty — the query
143
+ // reaches the *client* terminal and the reply comes back through stdin.
144
+ // App.tsx fires the query when raw mode enables; setXtversionName() is called
145
+ // from the response handler. Readers should treat undefined as "not yet known"
146
+ // and fall back to env-var detection.
147
+
148
+ let xtversionName: string | undefined
149
+
150
+ /** Record the XTVERSION response. Called once from App.tsx when the reply
151
+ * arrives on stdin. No-op if already set (defend against re-probe). */
152
+ export function setXtversionName(name: string): void {
153
+ if (xtversionName === undefined) {
154
+ xtversionName = name
155
+ }
156
+ }
157
+
158
+ /** True if running in an xterm.js-based terminal (VS Code, Cursor, Windsurf
159
+ * integrated terminals). Combines TERM_PROGRAM env check (fast, sync, but
160
+ * not forwarded over SSH) with the XTVERSION probe result (async, survives
161
+ * SSH — query/reply goes through the pty). Early calls may miss the probe
162
+ * reply — call lazily (e.g. in an event handler) if SSH detection matters. */
163
+ export function isXtermJs(): boolean {
164
+ if (process.env.TERM_PROGRAM === 'vscode') {
165
+ return true
166
+ }
167
+
168
+ return xtversionName?.startsWith('xterm.js') ?? false
169
+ }
170
+
171
+ export function needsAltScreenResizeScrollbackClear(env: NodeJS.ProcessEnv = process.env): boolean {
172
+ return (env.TERM_PROGRAM ?? '').trim() === 'Apple_Terminal'
173
+ }
174
+
175
+ // Terminals known to correctly implement the Kitty keyboard protocol
176
+ // (CSI >1u) and/or xterm modifyOtherKeys (CSI >4;2m) for ctrl+shift+<letter>
177
+ // disambiguation. We previously enabled unconditionally (#23350), assuming
178
+ // terminals silently ignore unknown CSI — but some terminals honor the enable
179
+ // and emit codepoints our input parser doesn't handle (notably over SSH and
180
+ // in xterm.js-based terminals like VS Code). tmux is allowlisted because it
181
+ // accepts modifyOtherKeys and doesn't forward the kitty sequence to the outer
182
+ // terminal.
183
+ const EXTENDED_KEYS_TERMINALS = ['iTerm.app', 'kitty', 'WezTerm', 'ghostty', 'tmux', 'windows-terminal', 'vscode']
184
+
185
+ /** True if this terminal correctly handles extended key reporting
186
+ * (Kitty keyboard protocol + xterm modifyOtherKeys). */
187
+ export function supportsExtendedKeys(): boolean {
188
+ return EXTENDED_KEYS_TERMINALS.includes(env.terminal ?? '')
189
+ }
190
+
191
+ /** True if the terminal scrolls the viewport when it receives cursor-up
192
+ * sequences that reach above the visible area. On Windows, conhost's
193
+ * SetConsoleCursorPosition follows the cursor into scrollback
194
+ * (microsoft/terminal#14774), yanking users to the top of their buffer
195
+ * mid-stream. WT_SESSION catches WSL-in-Windows-Terminal where platform
196
+ * is linux but output still routes through conhost. */
197
+ export function hasCursorUpViewportYankBug(): boolean {
198
+ return process.platform === 'win32' || !!process.env.WT_SESSION
199
+ }
200
+
201
+ // Computed once at module load — terminal capabilities don't change mid-session.
202
+ // Exported so callers can pass a sync-skip hint gated to specific modes.
203
+ export const SYNC_OUTPUT_SUPPORTED = isSynchronizedOutputSupported()
204
+
205
+ export type Terminal = {
206
+ stdout: Writable
207
+ stderr: Writable
208
+ }
209
+
210
+ export function writeDiffToTerminal(
211
+ terminal: Terminal,
212
+ diff: Diff,
213
+ skipSyncMarkers = false,
214
+ onDrain?: () => void
215
+ ): { bytes: number; backpressure: boolean } {
216
+ // No output if there are no patches
217
+ if (diff.length === 0) {
218
+ return { bytes: 0, backpressure: false }
219
+ }
220
+
221
+ // BSU/ESU wrapping is opt-out to keep main-screen behavior unchanged.
222
+ // Callers pass skipSyncMarkers=true when the terminal doesn't support
223
+ // DEC 2026 (e.g. tmux) AND the cost matters (high-frequency alt-screen).
224
+ const useSync = !skipSyncMarkers
225
+
226
+ // Buffer all writes into a single string to avoid multiple write calls
227
+ let buffer = useSync ? BSU : ''
228
+
229
+ for (const patch of diff) {
230
+ switch (patch.type) {
231
+ case 'stdout':
232
+ buffer += patch.content
233
+
234
+ break
235
+
236
+ case 'clear':
237
+ if (patch.count > 0) {
238
+ buffer += eraseLines(patch.count)
239
+ }
240
+
241
+ break
242
+
243
+ case 'clearTerminal':
244
+ buffer += getClearTerminalSequence()
245
+
246
+ break
247
+
248
+ case 'cursorHide':
249
+ buffer += HIDE_CURSOR
250
+
251
+ break
252
+
253
+ case 'cursorShow':
254
+ buffer += SHOW_CURSOR
255
+
256
+ break
257
+
258
+ case 'cursorMove':
259
+ buffer += cursorMove(patch.x, patch.y)
260
+
261
+ break
262
+
263
+ case 'cursorTo':
264
+ buffer += cursorTo(patch.col)
265
+
266
+ break
267
+
268
+ case 'carriageReturn':
269
+ buffer += '\r'
270
+
271
+ break
272
+
273
+ case 'hyperlink':
274
+ buffer += link(patch.uri)
275
+
276
+ break
277
+
278
+ case 'styleStr':
279
+ buffer += patch.str
280
+
281
+ break
282
+ }
283
+ }
284
+
285
+ // Add synchronized update end and flush buffer
286
+ if (useSync) {
287
+ buffer += ESU
288
+ }
289
+
290
+ // Node's Writable.write returns false when the internal buffer is full
291
+ // (backpressure). On a slow terminal parser that's the tell: we're
292
+ // producing bytes faster than the outer terminal can consume them.
293
+ // The 2-arg form attaches a drain callback that fires once the chunk
294
+ // is actually flushed to the OS socket/pipe — giving us end-to-end
295
+ // drain timing, not just "queued in Node".
296
+ const wrote = onDrain ? terminal.stdout.write(buffer, () => onDrain()) : terminal.stdout.write(buffer)
297
+
298
+ return { bytes: Buffer.byteLength(buffer, 'utf8'), backpressure: !wrote }
299
+ }