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,724 @@
1
+ /**
2
+ * OSC (Operating System Command) Types and Parser
3
+ */
4
+
5
+ import { Buffer } from 'buffer'
6
+
7
+ import { env as envModule, supportsOsc52Clipboard } from '../../utils/env.js'
8
+ import { execFileNoThrow } from '../../utils/execFileNoThrow.js'
9
+
10
+ import { BEL, ESC, ESC_TYPE, SEP } from './ansi.js'
11
+ import type { Action, Color, TabStatusAction } from './types.js'
12
+
13
+ export const OSC_PREFIX = ESC + String.fromCharCode(ESC_TYPE.OSC)
14
+ const ENV_ON_RE = /^(?:1|true|yes|on)$/i
15
+ const ENV_OFF_RE = /^(?:0|false|no|off)$/i
16
+
17
+ /** String Terminator (ESC \) - alternative to BEL for terminating OSC */
18
+ export const ST = ESC + '\\'
19
+
20
+ /** Generate an OSC sequence: ESC ] p1;p2;...;pN <terminator>
21
+ * Uses ST terminator for Kitty (avoids beeps), BEL for others */
22
+ export function osc(...parts: (string | number)[]): string {
23
+ const terminator = envModule.terminal === 'kitty' ? ST : BEL
24
+
25
+ return `${OSC_PREFIX}${parts.join(SEP)}${terminator}`
26
+ }
27
+
28
+ /**
29
+ * Wrap an escape sequence for terminal multiplexer passthrough.
30
+ * tmux and GNU screen intercept escape sequences; DCS passthrough
31
+ * tunnels them to the outer terminal unmodified.
32
+ *
33
+ * tmux 3.3+ gates this behind `allow-passthrough` (default off). When off,
34
+ * tmux silently drops the whole DCS — no junk, no worse than unwrapped OSC.
35
+ * Users who want passthrough set it in their .tmux.conf; we don't mutate it.
36
+ *
37
+ * Do NOT wrap BEL: raw \x07 triggers tmux's bell-action (window flag);
38
+ * wrapped \x07 is opaque DCS payload and tmux never sees the bell.
39
+ */
40
+ export function wrapForMultiplexer(sequence: string): string {
41
+ if (process.env['TMUX']) {
42
+ const escaped = sequence.replaceAll('\x1b', '\x1b\x1b')
43
+
44
+ return `\x1bPtmux;${escaped}\x1b\\`
45
+ }
46
+
47
+ if (process.env['STY']) {
48
+ return `\x1bP${sequence}\x1b\\`
49
+ }
50
+
51
+ return sequence
52
+ }
53
+
54
+ /**
55
+ * Which path setClipboard() will take, based on env state. Synchronous so
56
+ * callers can show an honest toast without awaiting the copy itself.
57
+ *
58
+ * - 'native': pbcopy (or equivalent) will run — high-confidence system
59
+ * clipboard write. tmux buffer may also be loaded as a bonus.
60
+ * - 'tmux-buffer': tmux load-buffer will run, but no native tool — paste
61
+ * with prefix+] works. System clipboard depends on tmux's set-clipboard
62
+ * option + outer terminal OSC 52 support; can't know from here.
63
+ * - 'osc52': only the raw OSC 52 sequence will be written to stdout.
64
+ * Best-effort; iTerm2 disables OSC 52 by default.
65
+ *
66
+ * pbcopy gating uses SSH_CONNECTION specifically, not SSH_TTY — tmux panes
67
+ * inherit SSH_TTY forever even after local reattach, but SSH_CONNECTION is
68
+ * in tmux's default update-environment set and gets cleared.
69
+ */
70
+ export type ClipboardPath = 'native' | 'tmux-buffer' | 'osc52'
71
+
72
+ export function getClipboardPath(): ClipboardPath {
73
+ const nativeAvailable = process.platform === 'darwin' && !process.env['SSH_CONNECTION']
74
+
75
+ if (nativeAvailable) {
76
+ return 'native'
77
+ }
78
+
79
+ if (process.env['TMUX']) {
80
+ return 'tmux-buffer'
81
+ }
82
+
83
+ return 'osc52'
84
+ }
85
+
86
+ export function shouldEmitClipboardSequence(env: NodeJS.ProcessEnv = process.env): boolean {
87
+ const override = (
88
+ env.NASTECH_TUI_FORCE_OSC52 ??
89
+ env.NASTECH_TUI_CLIPBOARD_OSC52 ??
90
+ env.NASTECH_TUI_COPY_OSC52 ??
91
+ ''
92
+ ).trim()
93
+
94
+ if (ENV_ON_RE.test(override)) {
95
+ return true
96
+ }
97
+
98
+ if (ENV_OFF_RE.test(override)) {
99
+ return false
100
+ }
101
+
102
+ return !!env['SSH_CONNECTION'] || (!env['TMUX'] && !env['STY'])
103
+ }
104
+
105
+ /**
106
+ * Decide whether setClipboard() should also fire the native clipboard tool
107
+ * (pbcopy / wl-copy / xclip / xsel / clip.exe) as a safety net alongside
108
+ * OSC 52 / tmux load-buffer.
109
+ *
110
+ * The default is "yes, native fires" — it's the historical safety net for
111
+ * terminals where OSC 52 may not work (iTerm2 disables OSC 52 by default,
112
+ * Apple_Terminal / GNOME Terminal / xterm coverage is patchy). The two
113
+ * cases where we suppress it:
114
+ *
115
+ * 1. SSH session: native tools would write to the *remote* machine's
116
+ * clipboard. OSC 52 (which travels back over the pty to the user's
117
+ * local terminal) is the right path. Existing behaviour.
118
+ *
119
+ * 2. Allowlisted OSC-52-capable terminal AND we're actually going to
120
+ * emit an OSC 52 sequence AND we're not inside tmux/screen. On these
121
+ * terminals (Ghostty / kitty / WezTerm / Windows Terminal / VS Code)
122
+ * the OSC 52 write is reliable on its own, and racing it with a
123
+ * native tool is destructive — wl-copy on Wayland in particular
124
+ * wipes the clipboard during its existence-probe and forks a daemon
125
+ * that races the terminal's own write (~30% empty-clipboard rate
126
+ * reported on Ghostty + Wayland; symptom: ctrl+shift+c works on the
127
+ * 3rd attempt).
128
+ *
129
+ * The TMUX/STY guard is important: detectTerminal() in utils/env.ts
130
+ * prefers TERM_PROGRAM over TMUX, so a tmux session inside Ghostty
131
+ * reports terminal='ghostty'. But inside tmux setClipboard() doesn't
132
+ * emit raw OSC 52 — it goes through tmux load-buffer (which loads
133
+ * the tmux paste buffer and, with -w, asks tmux to forward an OSC 52
134
+ * to the OUTER terminal via its own emission path). The native
135
+ * safety net is still useful there because tmux load-buffer's
136
+ * outer-terminal forwarding depends on `set -g set-clipboard` and
137
+ * `allow-passthrough`, which many users don't have configured.
138
+ *
139
+ * The OSC-52-will-emit guard matters too: if the user has set
140
+ * NASTECH_TUI_FORCE_OSC52=0, no OSC 52 sequence will be written. If
141
+ * we ALSO skip native, the clipboard write becomes a no-op. So skip
142
+ * native only when OSC 52 will actually carry the data.
143
+ */
144
+ export function shouldUseNativeClipboard(
145
+ env: NodeJS.ProcessEnv = process.env,
146
+ terminal: string | null = envModule.terminal
147
+ ): boolean {
148
+ // Over SSH the native tools would write to the wrong machine's clipboard.
149
+ if (env.SSH_CONNECTION) {
150
+ return false
151
+ }
152
+
153
+ // Inside tmux/screen, OSC 52 is normally suppressed and we rely on
154
+ // tmux load-buffer instead — so the wl-copy/OSC-52 race usually doesn't
155
+ // apply. Even when NASTECH_TUI_FORCE_OSC52=1 forces a tmux-passthrough
156
+ // OSC 52 emission, we keep native enabled as a safety net: tmux's
157
+ // outer-terminal forwarding depends on `allow-passthrough` in the
158
+ // user's tmux config, so a forced OSC 52 may silently never reach the
159
+ // host terminal. Native (pbcopy/wl-copy/xclip) covers that gap.
160
+ if (env.TMUX || env.STY) {
161
+ return true
162
+ }
163
+
164
+ // If OSC 52 won't actually emit (user override or env state), the
165
+ // native tool is the only path left — keep it on.
166
+ if (!shouldEmitClipboardSequence(env)) {
167
+ return true
168
+ }
169
+
170
+ // OSC 52 is going to emit AND the terminal is in the allowlist of
171
+ // terminals where OSC 52 alone is reliable: skip native to avoid the
172
+ // wl-copy race documented above.
173
+ return !supportsOsc52Clipboard(terminal)
174
+ }
175
+
176
+ /**
177
+ * Wrap a payload in tmux's DCS passthrough: ESC P tmux ; <payload> ESC \
178
+ * tmux forwards the payload to the outer terminal, bypassing its own parser.
179
+ * Inner ESCs must be doubled. Requires `set -g allow-passthrough on` in
180
+ * ~/.tmux.conf; without it, tmux silently drops the whole DCS (no regression).
181
+ */
182
+ function tmuxPassthrough(payload: string): string {
183
+ return `${ESC}Ptmux;${payload.replaceAll(ESC, ESC + ESC)}${ST}`
184
+ }
185
+
186
+ /**
187
+ * Load text into tmux's paste buffer via `tmux load-buffer`.
188
+ * -w (tmux 3.2+) propagates to the outer terminal's clipboard via tmux's
189
+ * own OSC 52 emission. -w is dropped for iTerm2: tmux's OSC 52 emission
190
+ * crashes the iTerm2 session over SSH.
191
+ *
192
+ * Returns true if the buffer was loaded successfully.
193
+ */
194
+ export async function tmuxLoadBuffer(text: string): Promise<boolean> {
195
+ if (!process.env['TMUX']) {
196
+ return false
197
+ }
198
+
199
+ const args = process.env['LC_TERMINAL'] === 'iTerm2' ? ['load-buffer', '-'] : ['load-buffer', '-w', '-']
200
+
201
+ const { code } = await execFileNoThrow('tmux', args, {
202
+ input: text,
203
+ useCwd: false,
204
+ timeout: 2000
205
+ })
206
+
207
+ return code === 0
208
+ }
209
+
210
+ /**
211
+ * OSC 52 clipboard write: ESC ] 52 ; c ; <base64> BEL/ST
212
+ * 'c' selects the clipboard (vs 'p' for primary selection on X11).
213
+ *
214
+ * When inside tmux ($TMUX set), `tmux load-buffer -w -` is the primary
215
+ * path. tmux's buffer is always reachable — works over SSH, survives
216
+ * detach/reattach, immune to stale env vars. The -w flag (tmux 3.2+) tells
217
+ * tmux to also propagate to the outer terminal via its own OSC 52 path,
218
+ * which tmux wraps correctly for the attached client. On older tmux, -w is
219
+ * ignored and the buffer is still loaded. -w is dropped for iTerm2 (#22432)
220
+ * because tmux's own OSC 52 emission (empty selection param: ESC]52;;b64)
221
+ * crashes iTerm2 over SSH.
222
+ *
223
+ * After load-buffer succeeds, we ALSO return a DCS-passthrough-wrapped
224
+ * OSC 52 for the caller to write to stdout. Our sequence uses explicit `c`
225
+ * (not tmux's crashy empty-param variant), so it sidesteps the #22432 path.
226
+ * With `allow-passthrough on` + an OSC-52-capable outer terminal, selection
227
+ * reaches the system clipboard; with either off, tmux silently drops the
228
+ * DCS and prefix+] still works. See Greg Smith's "free pony" in
229
+ * https://anthropic.slack.com/archives/C07VBSHV7EV/p1773177228548119.
230
+ *
231
+ * If load-buffer fails entirely, fall through to raw OSC 52.
232
+ *
233
+ * Outside tmux, write raw OSC 52 to stdout (caller handles the write).
234
+ *
235
+ * Local (no SSH_CONNECTION): also shell out to a native clipboard utility.
236
+ * OSC 52 and tmux -w both depend on terminal settings — iTerm2 disables
237
+ * OSC 52 by default, VS Code shows a permission prompt on first use. Native
238
+ * utilities (pbcopy/wl-copy/xclip/xsel/clip.exe) always work locally. Over
239
+ * SSH these would write to the remote clipboard — OSC 52 is the right path there.
240
+ *
241
+ * Returns { sequence, success }:
242
+ * - `sequence` is the bytes to write to stdout (raw OSC 52 outside tmux,
243
+ * DCS-wrapped inside; empty string when we shouldn't emit).
244
+ * - `success` is true when we believe SOME path reached the clipboard:
245
+ * native tool fired (local), tmux buffer loaded, or an OSC 52 sequence
246
+ * was emitted to the terminal. False only when no path was taken at
247
+ * all (headless Linux with no tmux + osc52 suppressed, effectively).
248
+ * This is best-effort — pbcopy/xclip are fire-and-forget, and OSC 52
249
+ * depends on the outer terminal honoring the sequence — but it lets
250
+ * callers distinguish "nothing attempted" from "attempted".
251
+ */
252
+ export type ClipboardResult = {
253
+ sequence: string
254
+ success: boolean
255
+ }
256
+
257
+ export async function setClipboard(text: string): Promise<ClipboardResult> {
258
+ const b64 = Buffer.from(text, 'utf8').toString('base64')
259
+ const raw = osc(OSC.CLIPBOARD, 'c', b64)
260
+ const emitSequence = shouldEmitClipboardSequence(process.env)
261
+
262
+ // Native safety net — fire FIRST, before the tmux await, so a quick
263
+ // focus-switch after selecting doesn't race pbcopy. Previously this ran
264
+ // AFTER awaiting tmux load-buffer, adding ~50-100ms of subprocess latency
265
+ // before pbcopy even started — fast cmd+tab → paste would beat it
266
+ // (https://anthropic.slack.com/archives/C07VBSHV7EV/p1773943921788829).
267
+ // Skipped entirely on terminals with first-class OSC 52 support (see
268
+ // `shouldUseNativeClipboard()` above): running wl-copy/xclip/pbcopy in
269
+ // parallel with OSC 52 on those terminals can corrupt the clipboard.
270
+ // wl-copy on Wayland is the worst offender — `probeLinuxCopy()` runs it
271
+ // with empty stdin to check if the binary exists (which destructively
272
+ // wipes the clipboard), and the subsequent real invocation forks a
273
+ // background daemon that races the terminal's own OSC 52 write plus its
274
+ // own prior daemon's SIGTERM. On Ghostty + Wayland this produced a ~30%
275
+ // clipboard-empty rate (symptom: user had to press ctrl+shift+c three
276
+ // times before the selection landed). Native still fires inside
277
+ // tmux/screen — we primarily rely on tmux load-buffer there rather
278
+ // than raw OSC 52, so the wl-copy race usually doesn't apply, and
279
+ // native is kept as a safety net because tmux passthrough forwarding
280
+ // depends on the user's `allow-passthrough` config (note: when
281
+ // NASTECH_TUI_FORCE_OSC52=1 we DO additionally emit a tmux-passthrough
282
+ // OSC 52, but it can be silently dropped without that setting).
283
+ // Native also fires when the user has disabled OSC 52 emission via
284
+ // NASTECH_TUI_FORCE_OSC52=0 (otherwise the clipboard write becomes a
285
+ // complete no-op). Fire-and-forget, but `nativeAttempted` tells us
286
+ // whether ANY native path will be tried.
287
+ const nativeAttempted = shouldUseNativeClipboard(process.env, envModule.terminal) && copyNative(text)
288
+
289
+ const tmuxBufferLoaded = await tmuxLoadBuffer(text)
290
+
291
+ // Inner OSC uses BEL directly (not osc()) — ST's ESC would need doubling
292
+ // too, and BEL works everywhere for OSC 52.
293
+ const sequence = emitSequence ? (tmuxBufferLoaded ? tmuxPassthrough(`${ESC}]52;c;${b64}${BEL}`) : raw) : ''
294
+
295
+ // Success if any path was taken. Native and tmux are fire-and-forget,
296
+ // so we can't truly confirm the clipboard was written — but if native
297
+ // was attempted OR tmux buffer loaded OR we emitted OSC 52, the user's
298
+ // paste is likely to work. The only false case is "we did literally
299
+ // nothing" (e.g. local-in-tmux with osc52 suppressed and tmux buffer
300
+ // load failed), in which case reporting failure to the user is honest.
301
+ const success = nativeAttempted || tmuxBufferLoaded || sequence.length > 0
302
+
303
+ return { sequence, success }
304
+ }
305
+
306
+ // Linux clipboard tool: undefined = not yet probed, null = none available.
307
+ // Probe order: wl-copy (Wayland) → xclip (X11) → xsel (X11 fallback).
308
+ // Cached after first attempt so repeated mouse-ups skip the probe chain.
309
+ let linuxCopy: 'wl-copy' | 'xclip' | 'xsel' | null | undefined
310
+
311
+ /** Per-tool copy arguments: wl-copy reads stdin, xclip/xsel need clipboard flags. */
312
+ function linuxCopyArgs(tool: 'wl-copy' | 'xclip' | 'xsel'): string[] {
313
+ switch (tool) {
314
+ case 'wl-copy':
315
+ return []
316
+ case 'xclip':
317
+ return ['-selection', 'clipboard']
318
+ case 'xsel':
319
+ return ['--clipboard', '--input']
320
+ }
321
+ }
322
+
323
+ /** Internal: probe once and cache — wl-copy first, then xclip, then xsel. */
324
+ async function probeLinuxCopy(): Promise<'wl-copy' | 'xclip' | 'xsel' | null> {
325
+ // resolveOnExit: wl-copy daemonizes and the daemon inherits stdio pipes,
326
+ // so 'close' never fires and the await would hang past the timeout.
327
+ // 'exit' fires on the immediate child's exit — what we actually care about.
328
+ const opts = { useCwd: false, timeout: 500, resolveOnExit: true }
329
+
330
+ const r = await execFileNoThrow('wl-copy', [], opts)
331
+
332
+ if (r.code === 0) {
333
+ return 'wl-copy'
334
+ }
335
+
336
+ const r2 = await execFileNoThrow('xclip', linuxCopyArgs('xclip'), opts)
337
+
338
+ if (r2.code === 0) {
339
+ return 'xclip'
340
+ }
341
+
342
+ const r3 = await execFileNoThrow('xsel', linuxCopyArgs('xsel'), opts)
343
+
344
+ return r3.code === 0 ? 'xsel' : null
345
+ }
346
+
347
+ /**
348
+ * Shell out to a native clipboard utility as a safety net for OSC 52.
349
+ * Only called when not in an SSH session (over SSH, these would write to
350
+ * the remote machine's clipboard — OSC 52 is the right path there).
351
+ * Fire-and-forget: failures are silent since OSC 52 may have succeeded.
352
+ *
353
+ * Returns true when a native copy path was (or will be) attempted — i.e.
354
+ * we'll spawn pbcopy on macOS, clip on Windows, or a known-working Linux
355
+ * tool. Returns false only when we know no native tool is viable (Linux
356
+ * without DISPLAY/WAYLAND_DISPLAY, or previously-probed-to-null). The
357
+ * return value is used to decide whether to tell the user the copy
358
+ * succeeded — spawning is best-effort but good enough to claim success.
359
+ *
360
+ * Linux behaviour: if DISPLAY and WAYLAND_DISPLAY are both unset, native
361
+ * clipboard tools cannot work (they need a display server). In that case
362
+ * we skip probing entirely and treat linuxCopy as permanently null.
363
+ */
364
+ function copyNative(text: string): boolean {
365
+ // resolveOnExit: pbcopy/wl-copy/xclip/xsel/clip all daemonize or hold
366
+ // the system selection live in a forked process. Without resolveOnExit,
367
+ // the inherited stdio pipes keep node from seeing 'close' → the
368
+ // fire-and-forget await never resolves and the actual copy never runs.
369
+ const opts = { input: text, useCwd: false, timeout: 2000, resolveOnExit: true }
370
+
371
+ switch (process.platform) {
372
+ case 'darwin':
373
+ void execFileNoThrow('pbcopy', [], opts)
374
+
375
+ return true
376
+ case 'linux': {
377
+ // If we already probed (success or hard-fail), short-circuit.
378
+ if (linuxCopy !== undefined) {
379
+ if (linuxCopy === null) {
380
+ // No working native tool — skip silently.
381
+ return false
382
+ }
383
+
384
+ // linuxCopy is a known-working tool; fire-and-forget.
385
+ void execFileNoThrow(linuxCopy, linuxCopyArgs(linuxCopy), opts)
386
+
387
+ return true
388
+ }
389
+
390
+ // No display server → native tools will fail immediately. Cache null.
391
+ if (!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
392
+ linuxCopy = null
393
+
394
+ return false
395
+ }
396
+ // First call: probe in the background and cache the result for future copies.
397
+ // We don't await — this is fire-and-forget. Treat as an attempt:
398
+ // the probe will discover a tool and spawn it. If probing finds
399
+ // nothing, the NEXT copy will short-circuit above.
400
+ void (async () => {
401
+ const winner = await probeLinuxCopy()
402
+ linuxCopy = winner
403
+
404
+ // Actually perform the copy with the discovered tool.
405
+ if (winner) {
406
+ void execFileNoThrow(winner, linuxCopyArgs(winner), opts)
407
+ }
408
+ })()
409
+
410
+ return true
411
+ }
412
+
413
+ case 'win32':
414
+ // clip.exe is always available on Windows. Unicode handling is
415
+ // imperfect (system locale encoding) but good enough for a fallback.
416
+ void execFileNoThrow('clip', [], opts)
417
+
418
+ return true
419
+ }
420
+
421
+ return false
422
+ }
423
+
424
+ /** @internal test-only */
425
+ export function _resetLinuxCopyCache(): void {
426
+ linuxCopy = undefined
427
+ }
428
+
429
+ /**
430
+ * OSC command numbers
431
+ */
432
+ export const OSC = {
433
+ SET_TITLE_AND_ICON: 0,
434
+ SET_ICON: 1,
435
+ SET_TITLE: 2,
436
+ SET_COLOR: 4,
437
+ SET_CWD: 7,
438
+ HYPERLINK: 8,
439
+ ITERM2: 9, // iTerm2 proprietary sequences
440
+ SET_FG_COLOR: 10,
441
+ SET_BG_COLOR: 11,
442
+ SET_CURSOR_COLOR: 12,
443
+ CLIPBOARD: 52,
444
+ KITTY: 99, // Kitty notification protocol
445
+ RESET_COLOR: 104,
446
+ RESET_FG_COLOR: 110,
447
+ RESET_BG_COLOR: 111,
448
+ RESET_CURSOR_COLOR: 112,
449
+ SEMANTIC_PROMPT: 133,
450
+ GHOSTTY: 777, // Ghostty notification protocol
451
+ TAB_STATUS: 21337 // Tab status extension
452
+ } as const
453
+
454
+ /**
455
+ * Parse an OSC sequence into an action
456
+ *
457
+ * @param content - The sequence content (without ESC ] and terminator)
458
+ */
459
+ export function parseOSC(content: string): Action | null {
460
+ const semicolonIdx = content.indexOf(';')
461
+ const command = semicolonIdx >= 0 ? content.slice(0, semicolonIdx) : content
462
+ const data = semicolonIdx >= 0 ? content.slice(semicolonIdx + 1) : ''
463
+
464
+ const commandNum = parseInt(command, 10)
465
+
466
+ // Window/icon title
467
+ if (commandNum === OSC.SET_TITLE_AND_ICON) {
468
+ return { type: 'title', action: { type: 'both', title: data } }
469
+ }
470
+
471
+ if (commandNum === OSC.SET_ICON) {
472
+ return { type: 'title', action: { type: 'iconName', name: data } }
473
+ }
474
+
475
+ if (commandNum === OSC.SET_TITLE) {
476
+ return { type: 'title', action: { type: 'windowTitle', title: data } }
477
+ }
478
+
479
+ // Hyperlinks (OSC 8)
480
+ if (commandNum === OSC.HYPERLINK) {
481
+ const parts = data.split(';')
482
+ const paramsStr = parts[0] ?? ''
483
+ const url = parts.slice(1).join(';')
484
+
485
+ if (url === '') {
486
+ return { type: 'link', action: { type: 'end' } }
487
+ }
488
+
489
+ const params: Record<string, string> = {}
490
+
491
+ if (paramsStr) {
492
+ for (const pair of paramsStr.split(':')) {
493
+ const eqIdx = pair.indexOf('=')
494
+
495
+ if (eqIdx >= 0) {
496
+ params[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1)
497
+ }
498
+ }
499
+ }
500
+
501
+ return {
502
+ type: 'link',
503
+ action: {
504
+ type: 'start',
505
+ url,
506
+ params: Object.keys(params).length > 0 ? params : undefined
507
+ }
508
+ }
509
+ }
510
+
511
+ // Tab status (OSC 21337)
512
+ if (commandNum === OSC.TAB_STATUS) {
513
+ return { type: 'tabStatus', action: parseTabStatus(data) }
514
+ }
515
+
516
+ return { type: 'unknown', sequence: `\x1b]${content}` }
517
+ }
518
+
519
+ /**
520
+ * Parse an XParseColor-style color spec into an RGB Color.
521
+ * Accepts `#RRGGBB` and `rgb:R/G/B` (1–4 hex digits per component, scaled
522
+ * to 8-bit). Returns null on parse failure.
523
+ */
524
+ export function parseOscColor(spec: string): Color | null {
525
+ const hex = spec.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i)
526
+
527
+ if (hex) {
528
+ return {
529
+ type: 'rgb',
530
+ r: parseInt(hex[1]!, 16),
531
+ g: parseInt(hex[2]!, 16),
532
+ b: parseInt(hex[3]!, 16)
533
+ }
534
+ }
535
+
536
+ const rgb = spec.match(/^rgb:([0-9a-f]{1,4})\/([0-9a-f]{1,4})\/([0-9a-f]{1,4})$/i)
537
+
538
+ if (rgb) {
539
+ // XParseColor: N hex digits → value / (16^N - 1), scale to 0-255
540
+ const scale = (s: string) => Math.round((parseInt(s, 16) / (16 ** s.length - 1)) * 255)
541
+
542
+ return {
543
+ type: 'rgb',
544
+ r: scale(rgb[1]!),
545
+ g: scale(rgb[2]!),
546
+ b: scale(rgb[3]!)
547
+ }
548
+ }
549
+
550
+ return null
551
+ }
552
+
553
+ /**
554
+ * Parse OSC 21337 payload: `key=value;key=value;...` with `\;` and `\\`
555
+ * escapes inside values. Bare key or `key=` clears that field; unknown
556
+ * keys are ignored.
557
+ */
558
+ function parseTabStatus(data: string): TabStatusAction {
559
+ const action: TabStatusAction = {}
560
+
561
+ for (const [key, value] of splitTabStatusPairs(data)) {
562
+ switch (key) {
563
+ case 'indicator':
564
+ action.indicator = value === '' ? null : parseOscColor(value)
565
+
566
+ break
567
+
568
+ case 'status':
569
+ action.status = value === '' ? null : value
570
+
571
+ break
572
+
573
+ case 'status-color':
574
+ action.statusColor = value === '' ? null : parseOscColor(value)
575
+
576
+ break
577
+ }
578
+ }
579
+
580
+ return action
581
+ }
582
+
583
+ /** Split `k=v;k=v` honoring `\;` and `\\` escapes. Yields [key, unescapedValue]. */
584
+ function* splitTabStatusPairs(data: string): Generator<[string, string]> {
585
+ let key = ''
586
+ let val = ''
587
+ let inVal = false
588
+ let esc = false
589
+
590
+ for (const c of data) {
591
+ if (esc) {
592
+ if (inVal) {
593
+ val += c
594
+ } else {
595
+ key += c
596
+ }
597
+
598
+ esc = false
599
+ } else if (c === '\\') {
600
+ esc = true
601
+ } else if (c === ';') {
602
+ yield [key, val]
603
+ key = ''
604
+ val = ''
605
+ inVal = false
606
+ } else if (c === '=' && !inVal) {
607
+ inVal = true
608
+ } else if (inVal) {
609
+ val += c
610
+ } else {
611
+ key += c
612
+ }
613
+ }
614
+
615
+ if (key || inVal) {
616
+ yield [key, val]
617
+ }
618
+ }
619
+
620
+ // Output generators
621
+
622
+ /** Start a hyperlink (OSC 8). Auto-assigns an id= param derived from the URL
623
+ * so terminals group wrapped lines of the same link together (the spec says
624
+ * cells with matching URI *and* nonempty id are joined; without an id each
625
+ * wrapped line is a separate link — inconsistent hover, partial tooltips).
626
+ * Empty url = close sequence (empty params per spec). */
627
+ export function link(url: string, params?: Record<string, string>): string {
628
+ if (!url) {
629
+ return LINK_END
630
+ }
631
+
632
+ const p = { id: osc8Id(url), ...params }
633
+
634
+ const paramStr = Object.entries(p)
635
+ .map(([k, v]) => `${k}=${v}`)
636
+ .join(':')
637
+
638
+ return osc(OSC.HYPERLINK, paramStr, url)
639
+ }
640
+
641
+ function osc8Id(url: string): string {
642
+ let h = 0
643
+
644
+ for (let i = 0; i < url.length; i++) {
645
+ h = ((h << 5) - h + url.charCodeAt(i)) | 0
646
+ }
647
+
648
+ return (h >>> 0).toString(36)
649
+ }
650
+
651
+ /** End a hyperlink (OSC 8) */
652
+ export const LINK_END = osc(OSC.HYPERLINK, '', '')
653
+
654
+ // iTerm2 OSC 9 subcommands
655
+
656
+ /** iTerm2 OSC 9 subcommand numbers */
657
+ export const ITERM2 = {
658
+ NOTIFY: 0,
659
+ BADGE: 2,
660
+ PROGRESS: 4
661
+ } as const
662
+
663
+ /** Progress operation codes (for use with ITERM2.PROGRESS) */
664
+ export const PROGRESS = {
665
+ CLEAR: 0,
666
+ SET: 1,
667
+ ERROR: 2,
668
+ INDETERMINATE: 3
669
+ } as const
670
+
671
+ /**
672
+ * Clear iTerm2 progress bar sequence (OSC 9;4;0;BEL)
673
+ * Uses BEL terminator since this is for cleanup (not runtime notification)
674
+ * and we want to ensure it's always sent regardless of terminal type.
675
+ */
676
+ export const CLEAR_ITERM2_PROGRESS = `${OSC_PREFIX}${OSC.ITERM2};${ITERM2.PROGRESS};${PROGRESS.CLEAR};${BEL}`
677
+
678
+ /**
679
+ * Clear terminal title sequence (OSC 0 with empty string + BEL).
680
+ * Uses BEL terminator for cleanup — safe on all terminals.
681
+ */
682
+ export const CLEAR_TERMINAL_TITLE = `${OSC_PREFIX}${OSC.SET_TITLE_AND_ICON};${BEL}`
683
+
684
+ /** Clear all three OSC 21337 tab-status fields. Used on exit. */
685
+ export const CLEAR_TAB_STATUS = osc(OSC.TAB_STATUS, 'indicator=;status=;status-color=')
686
+
687
+ /**
688
+ * Gate for emitting OSC 21337 (tab-status indicator). Ant-only while the
689
+ * spec is unstable. Terminals that don't recognize it discard silently, so
690
+ * emission is safe unconditionally — we don't gate on terminal detection
691
+ * since support is expected across several terminals.
692
+ *
693
+ * Callers must wrap output with wrapForMultiplexer() so tmux/screen
694
+ * DCS-passthrough carries the sequence to the outer terminal.
695
+ */
696
+ export function supportsTabStatus(): boolean {
697
+ return process.env.USER_TYPE === 'ant'
698
+ }
699
+
700
+ /**
701
+ * Emit an OSC 21337 tab-status sequence. Omitted fields are left unchanged
702
+ * by the receiving terminal; `null` sends an empty value to clear.
703
+ * `;` and `\` in status text are escaped per the spec.
704
+ */
705
+ export function tabStatus(fields: TabStatusAction): string {
706
+ const parts: string[] = []
707
+
708
+ const rgb = (c: Color) =>
709
+ c.type === 'rgb' ? `#${[c.r, c.g, c.b].map(n => n.toString(16).padStart(2, '0')).join('')}` : ''
710
+
711
+ if ('indicator' in fields) {
712
+ parts.push(`indicator=${fields.indicator ? rgb(fields.indicator) : ''}`)
713
+ }
714
+
715
+ if ('status' in fields) {
716
+ parts.push(`status=${fields.status?.replaceAll('\\', '\\\\').replaceAll(';', '\\;') ?? ''}`)
717
+ }
718
+
719
+ if ('statusColor' in fields) {
720
+ parts.push(`status-color=${fields.statusColor ? rgb(fields.statusColor) : ''}`)
721
+ }
722
+
723
+ return osc(OSC.TAB_STATUS, parts.join(';'))
724
+ }