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,386 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import {
4
+ configureDetectedTerminalKeybindings,
5
+ configureTerminalKeybindings,
6
+ detectVSCodeLikeTerminal,
7
+ getVSCodeStyleConfigDir,
8
+ shouldPromptForTerminalSetup,
9
+ stripJsonComments
10
+ } from '../lib/terminalSetup.js'
11
+
12
+ describe('terminalSetup helpers', () => {
13
+ it('detects VS Code family terminals from environment', () => {
14
+ expect(detectVSCodeLikeTerminal({ CURSOR_TRACE_ID: 'x' } as NodeJS.ProcessEnv)).toBe('cursor')
15
+ expect(detectVSCodeLikeTerminal({ VSCODE_GIT_ASKPASS_MAIN: '/tmp/windsurf' } as NodeJS.ProcessEnv)).toBe('windsurf')
16
+ expect(detectVSCodeLikeTerminal({ TERM_PROGRAM: 'vscode' } as NodeJS.ProcessEnv)).toBe('vscode')
17
+ expect(detectVSCodeLikeTerminal({} as NodeJS.ProcessEnv)).toBeNull()
18
+ })
19
+
20
+ it('computes VS Code style config dirs cross-platform', () => {
21
+ expect(getVSCodeStyleConfigDir('Code', 'darwin', {} as NodeJS.ProcessEnv, '/home/me')).toBe(
22
+ '/home/me/Library/Application Support/Code/User'
23
+ )
24
+ expect(getVSCodeStyleConfigDir('Code', 'linux', {} as NodeJS.ProcessEnv, '/home/me')).toBe(
25
+ '/home/me/.config/Code/User'
26
+ )
27
+ expect(
28
+ getVSCodeStyleConfigDir(
29
+ 'Code',
30
+ 'win32',
31
+ { APPDATA: 'C:/Users/me/AppData/Roaming' } as NodeJS.ProcessEnv,
32
+ '/home/me'
33
+ )
34
+ ).toBe('C:/Users/me/AppData/Roaming/Code/User')
35
+ })
36
+
37
+ it('strips line comments from keybindings JSON', () => {
38
+ expect(stripJsonComments('// comment\n[{"key":"shift+enter"}]')).toBe('\n[{"key":"shift+enter"}]')
39
+ })
40
+
41
+ it('strips inline comments and block comments', () => {
42
+ expect(stripJsonComments('[{"key":"a"} // inline\n]')).toBe('[{"key":"a"} \n]')
43
+ expect(stripJsonComments('[/* block */{"key":"a"}]')).toBe('[{"key":"a"}]')
44
+ })
45
+
46
+ it('removes trailing commas before ] or }', () => {
47
+ expect(JSON.parse(stripJsonComments('[{"key":"a"},]'))).toEqual([{ key: 'a' }])
48
+ expect(JSON.parse(stripJsonComments('[{"key":"a",}]'))).toEqual([{ key: 'a' }])
49
+ })
50
+
51
+ it('preserves comment-like sequences inside strings', () => {
52
+ const input = '[{"key":"a","args":{"text":"// not a comment"}}]'
53
+ expect(JSON.parse(stripJsonComments(input))).toEqual([{ key: 'a', args: { text: '// not a comment' } }])
54
+ })
55
+
56
+ it('handles unterminated block comments gracefully', () => {
57
+ const input = '[{"key":"a"} /* never closed'
58
+ const stripped = stripJsonComments(input)
59
+ // The unterminated comment is consumed to end-of-file; the remainder is parseable
60
+ expect(stripped).toBe('[{"key":"a"} ')
61
+ })
62
+ })
63
+
64
+ describe('configureTerminalKeybindings', () => {
65
+ it('writes missing bindings into a VS Code style keybindings file', async () => {
66
+ const mkdir = vi.fn().mockResolvedValue(undefined)
67
+ const readFile = vi.fn().mockRejectedValue(Object.assign(new Error('missing'), { code: 'ENOENT' }))
68
+ const writeFile = vi.fn().mockResolvedValue(undefined)
69
+ const copyFile = vi.fn().mockResolvedValue(undefined)
70
+
71
+ const result = await configureTerminalKeybindings('vscode', {
72
+ fileOps: { copyFile, mkdir, readFile, writeFile },
73
+ homeDir: '/Users/me',
74
+ platform: 'darwin'
75
+ })
76
+
77
+ expect(result.success).toBe(true)
78
+ expect(result.requiresRestart).toBe(true)
79
+ expect(writeFile).toHaveBeenCalledTimes(1)
80
+ expect(copyFile).not.toHaveBeenCalled() // no existing file to back up
81
+ const written = writeFile.mock.calls[0]?.[1] as string
82
+ expect(written).toContain('cmd+c')
83
+ expect(written).toContain('terminalTextSelected')
84
+ expect(written).toContain('\\u001b[99;13u')
85
+ expect(written).toContain('shift+enter')
86
+ expect(written).toContain('cmd+enter')
87
+ expect(written).toContain('cmd+z')
88
+ })
89
+
90
+ it('only adds the Cmd+C forwarding binding on macOS', async () => {
91
+ const mkdir = vi.fn().mockResolvedValue(undefined)
92
+ const readFile = vi.fn().mockRejectedValue(Object.assign(new Error('missing'), { code: 'ENOENT' }))
93
+ const writeFile = vi.fn().mockResolvedValue(undefined)
94
+ const copyFile = vi.fn().mockResolvedValue(undefined)
95
+
96
+ const result = await configureTerminalKeybindings('vscode', {
97
+ fileOps: { copyFile, mkdir, readFile, writeFile },
98
+ homeDir: '/home/me',
99
+ platform: 'linux'
100
+ })
101
+
102
+ expect(result.success).toBe(true)
103
+ const written = writeFile.mock.calls[0]?.[1] as string
104
+ expect(written).not.toContain('cmd+c')
105
+ expect(written).not.toContain('terminalTextSelected')
106
+ expect(written).not.toContain('\\u001b[99;13u')
107
+ expect(written).toContain('shift+enter')
108
+ })
109
+
110
+ it('reports conflicts without overwriting existing bindings', async () => {
111
+ const mkdir = vi.fn().mockResolvedValue(undefined)
112
+
113
+ const readFile = vi.fn().mockResolvedValue(
114
+ JSON.stringify([
115
+ {
116
+ key: 'cmd+z',
117
+ command: 'something.else',
118
+ when: 'terminalFocus',
119
+ args: { text: 'noop' }
120
+ }
121
+ ])
122
+ )
123
+
124
+ const writeFile = vi.fn().mockResolvedValue(undefined)
125
+ const copyFile = vi.fn().mockResolvedValue(undefined)
126
+
127
+ const result = await configureTerminalKeybindings('cursor', {
128
+ fileOps: { copyFile, mkdir, readFile, writeFile },
129
+ homeDir: '/Users/me',
130
+ platform: 'darwin'
131
+ })
132
+
133
+ expect(result.success).toBe(false)
134
+ expect(result.message).toContain('cmd+z')
135
+ expect(writeFile).not.toHaveBeenCalled()
136
+ expect(copyFile).not.toHaveBeenCalled() // no backup when not writing
137
+ })
138
+
139
+ it('flags a global (when-less) binding on the same key as a conflict', async () => {
140
+ // A user's keybindings.json `cmd+c` with no `when` clause is global —
141
+ // it overlaps any context, including our terminal scope. We must NOT
142
+ // silently add a terminal-scoped cmd+c that would shadow it.
143
+ const mkdir = vi.fn().mockResolvedValue(undefined)
144
+
145
+ const readFile = vi.fn().mockResolvedValue(
146
+ JSON.stringify([
147
+ {
148
+ key: 'cmd+c',
149
+ command: 'myExtension.smartCopy'
150
+ }
151
+ ])
152
+ )
153
+
154
+ const writeFile = vi.fn().mockResolvedValue(undefined)
155
+ const copyFile = vi.fn().mockResolvedValue(undefined)
156
+
157
+ const result = await configureTerminalKeybindings('vscode', {
158
+ fileOps: { copyFile, mkdir, readFile, writeFile },
159
+ homeDir: '/Users/me',
160
+ platform: 'darwin'
161
+ })
162
+
163
+ expect(result.success).toBe(false)
164
+ expect(result.message).toContain('cmd+c')
165
+ expect(writeFile).not.toHaveBeenCalled()
166
+ })
167
+
168
+ it('flags an overlapping terminal-context binding as a conflict', async () => {
169
+ // Existing `cmd+c` scoped to plain `terminalFocus` overlaps with our
170
+ // `terminalFocus && terminalTextSelected` — both fire when the
171
+ // terminal is focused with text selected, so the existing binding
172
+ // would shadow ours. Treat as a conflict even though the strings
173
+ // aren't identical.
174
+ const mkdir = vi.fn().mockResolvedValue(undefined)
175
+
176
+ const readFile = vi.fn().mockResolvedValue(
177
+ JSON.stringify([
178
+ {
179
+ key: 'cmd+c',
180
+ command: 'workbench.action.terminal.copySelection',
181
+ when: 'terminalFocus'
182
+ }
183
+ ])
184
+ )
185
+
186
+ const writeFile = vi.fn().mockResolvedValue(undefined)
187
+ const copyFile = vi.fn().mockResolvedValue(undefined)
188
+
189
+ const result = await configureTerminalKeybindings('vscode', {
190
+ fileOps: { copyFile, mkdir, readFile, writeFile },
191
+ homeDir: '/Users/me',
192
+ platform: 'darwin'
193
+ })
194
+
195
+ expect(result.success).toBe(false)
196
+ expect(result.message).toContain('cmd+c')
197
+ expect(writeFile).not.toHaveBeenCalled()
198
+ })
199
+
200
+ it('does not flag a negated terminalTextSelected binding as a conflict', async () => {
201
+ // A binding scoped to "terminal focused but no selected text" is
202
+ // logically disjoint from our copy-forwarding binding, which requires
203
+ // terminalTextSelected.
204
+ const mkdir = vi.fn().mockResolvedValue(undefined)
205
+
206
+ const readFile = vi.fn().mockResolvedValue(
207
+ JSON.stringify([
208
+ {
209
+ key: 'cmd+c',
210
+ command: 'workbench.action.terminal.sendSequence',
211
+ when: 'terminalFocus && !terminalTextSelected',
212
+ args: { text: '\u0003' }
213
+ }
214
+ ])
215
+ )
216
+
217
+ const writeFile = vi.fn().mockResolvedValue(undefined)
218
+ const copyFile = vi.fn().mockResolvedValue(undefined)
219
+
220
+ const result = await configureTerminalKeybindings('vscode', {
221
+ fileOps: { copyFile, mkdir, readFile, writeFile },
222
+ homeDir: '/Users/me',
223
+ platform: 'darwin'
224
+ })
225
+
226
+ expect(result.success).toBe(true)
227
+ expect(writeFile).toHaveBeenCalledTimes(1)
228
+ })
229
+
230
+ it('does not flag a disjoint-when binding on the same key as a conflict', async () => {
231
+ // VS Code allows multiple bindings for the same key when their `when`
232
+ // clauses don't overlap. A user's pre-existing cmd+c binding scoped to
233
+ // editor focus should NOT block our terminal-scoped cmd+c binding.
234
+ const mkdir = vi.fn().mockResolvedValue(undefined)
235
+
236
+ const readFile = vi.fn().mockResolvedValue(
237
+ JSON.stringify([
238
+ {
239
+ key: 'cmd+c',
240
+ command: 'editor.action.clipboardCopyAction',
241
+ when: 'editorFocus'
242
+ }
243
+ ])
244
+ )
245
+
246
+ const writeFile = vi.fn().mockResolvedValue(undefined)
247
+ const copyFile = vi.fn().mockResolvedValue(undefined)
248
+
249
+ const result = await configureTerminalKeybindings('vscode', {
250
+ fileOps: { copyFile, mkdir, readFile, writeFile },
251
+ homeDir: '/Users/me',
252
+ platform: 'darwin'
253
+ })
254
+
255
+ expect(result.success).toBe(true)
256
+ expect(writeFile).toHaveBeenCalledTimes(1)
257
+ })
258
+
259
+ it('backs up existing keybindings.json only when writing changes', async () => {
260
+ const mkdir = vi.fn().mockResolvedValue(undefined)
261
+ const readFile = vi.fn().mockResolvedValue(JSON.stringify([]))
262
+ const writeFile = vi.fn().mockResolvedValue(undefined)
263
+ const copyFile = vi.fn().mockResolvedValue(undefined)
264
+
265
+ const result = await configureTerminalKeybindings('vscode', {
266
+ fileOps: { copyFile, mkdir, readFile, writeFile },
267
+ homeDir: '/Users/me',
268
+ platform: 'darwin'
269
+ })
270
+
271
+ expect(result.success).toBe(true)
272
+ expect(writeFile).toHaveBeenCalledTimes(1)
273
+ expect(copyFile).toHaveBeenCalledTimes(1) // backup created before writing
274
+ })
275
+
276
+ it('reports error when keybindings.json is not readable (EACCES)', async () => {
277
+ const mkdir = vi.fn().mockResolvedValue(undefined)
278
+ const readFile = vi.fn().mockRejectedValue(Object.assign(new Error('permission denied'), { code: 'EACCES' }))
279
+ const writeFile = vi.fn().mockResolvedValue(undefined)
280
+ const copyFile = vi.fn().mockResolvedValue(undefined)
281
+
282
+ const result = await configureTerminalKeybindings('vscode', {
283
+ fileOps: { copyFile, mkdir, readFile, writeFile },
284
+ homeDir: '/Users/me',
285
+ platform: 'darwin'
286
+ })
287
+
288
+ expect(result.success).toBe(false)
289
+ expect(result.message).toContain('Failed to read')
290
+ expect(writeFile).not.toHaveBeenCalled()
291
+ })
292
+
293
+ it('auto-detects the current IDE terminal', async () => {
294
+ const mkdir = vi.fn().mockResolvedValue(undefined)
295
+ const readFile = vi.fn().mockRejectedValue(Object.assign(new Error('missing'), { code: 'ENOENT' }))
296
+ const writeFile = vi.fn().mockResolvedValue(undefined)
297
+ const copyFile = vi.fn().mockResolvedValue(undefined)
298
+
299
+ const result = await configureDetectedTerminalKeybindings({
300
+ env: { CURSOR_TRACE_ID: 'trace' } as NodeJS.ProcessEnv,
301
+ fileOps: { copyFile, mkdir, readFile, writeFile },
302
+ homeDir: '/Users/me',
303
+ platform: 'darwin'
304
+ })
305
+
306
+ expect(result.success).toBe(true)
307
+ expect(writeFile).toHaveBeenCalled()
308
+ })
309
+
310
+ it('refuses to configure IDE bindings from an SSH session', async () => {
311
+ const result = await configureDetectedTerminalKeybindings({
312
+ env: { SSH_CONNECTION: '1 2 3 4', TERM_PROGRAM: 'vscode' } as NodeJS.ProcessEnv,
313
+ homeDir: '/Users/me',
314
+ platform: 'darwin'
315
+ })
316
+
317
+ expect(result.success).toBe(false)
318
+ expect(result.message).toContain('local machine')
319
+ })
320
+
321
+ it('prompts for setup when bindings are missing and suppresses prompt when complete', async () => {
322
+ const readMissing = vi.fn().mockRejectedValue(Object.assign(new Error('missing'), { code: 'ENOENT' }))
323
+ await expect(
324
+ shouldPromptForTerminalSetup({
325
+ env: { TERM_PROGRAM: 'vscode' } as NodeJS.ProcessEnv,
326
+ fileOps: { readFile: readMissing }
327
+ })
328
+ ).resolves.toBe(true)
329
+
330
+ const readComplete = vi.fn().mockResolvedValue(
331
+ JSON.stringify([
332
+ {
333
+ key: 'cmd+c',
334
+ command: 'workbench.action.terminal.sendSequence',
335
+ when: 'terminalFocus && terminalTextSelected',
336
+ args: { text: '\u001b[99;13u' }
337
+ },
338
+ {
339
+ key: 'shift+enter',
340
+ command: 'workbench.action.terminal.sendSequence',
341
+ when: 'terminalFocus',
342
+ args: { text: '\\\r\n' }
343
+ },
344
+ {
345
+ key: 'ctrl+enter',
346
+ command: 'workbench.action.terminal.sendSequence',
347
+ when: 'terminalFocus',
348
+ args: { text: '\\\r\n' }
349
+ },
350
+ {
351
+ key: 'cmd+enter',
352
+ command: 'workbench.action.terminal.sendSequence',
353
+ when: 'terminalFocus',
354
+ args: { text: '\\\r\n' }
355
+ },
356
+ {
357
+ key: 'cmd+z',
358
+ command: 'workbench.action.terminal.sendSequence',
359
+ when: 'terminalFocus',
360
+ args: { text: '\u001b[122;9u' }
361
+ },
362
+ {
363
+ key: 'shift+cmd+z',
364
+ command: 'workbench.action.terminal.sendSequence',
365
+ when: 'terminalFocus',
366
+ args: { text: '\u001b[122;10u' }
367
+ }
368
+ ])
369
+ )
370
+
371
+ await expect(
372
+ shouldPromptForTerminalSetup({
373
+ env: { TERM_PROGRAM: 'vscode' } as NodeJS.ProcessEnv,
374
+ fileOps: { readFile: readComplete }
375
+ })
376
+ ).resolves.toBe(false)
377
+ })
378
+
379
+ it('suppresses terminal setup prompts inside SSH sessions', async () => {
380
+ await expect(
381
+ shouldPromptForTerminalSetup({
382
+ env: { SSH_CONNECTION: '1 2 3 4', TERM_PROGRAM: 'vscode' } as NodeJS.ProcessEnv
383
+ })
384
+ ).resolves.toBe(false)
385
+ })
386
+ })
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { isTermuxEnv, isTermuxTuiMode } from '../lib/termux.js'
4
+
5
+ describe('isTermuxEnv', () => {
6
+ it('detects TERMUX_VERSION marker', () => {
7
+ expect(isTermuxEnv({ TERMUX_VERSION: '0.118.0' } as NodeJS.ProcessEnv)).toBe(true)
8
+ })
9
+
10
+ it('detects Termux PREFIX path marker', () => {
11
+ expect(
12
+ isTermuxEnv({ PREFIX: '/data/data/com.termux/files/usr' } as NodeJS.ProcessEnv)
13
+ ).toBe(true)
14
+ })
15
+
16
+ it('returns false for generic Linux envs', () => {
17
+ expect(isTermuxEnv({ PREFIX: '/usr' } as NodeJS.ProcessEnv)).toBe(false)
18
+ })
19
+ })
20
+
21
+ describe('isTermuxTuiMode', () => {
22
+ it('defaults to true inside Termux', () => {
23
+ expect(isTermuxTuiMode({ TERMUX_VERSION: '0.118.0' } as NodeJS.ProcessEnv)).toBe(true)
24
+ })
25
+
26
+ it('allows explicit opt-out override', () => {
27
+ expect(
28
+ isTermuxTuiMode({ TERMUX_VERSION: '0.118.0', NASTECH_TUI_TERMUX_MODE: '0' } as NodeJS.ProcessEnv)
29
+ ).toBe(false)
30
+ })
31
+
32
+ it('stays false outside Termux even if override is set', () => {
33
+ expect(isTermuxTuiMode({ NASTECH_TUI_TERMUX_MODE: '1', PREFIX: '/usr' } as NodeJS.ProcessEnv)).toBe(false)
34
+ })
35
+ })
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { stableComposerColumns, transcriptBodyWidth } from '../lib/inputMetrics.js'
4
+ import { composerPromptText } from '../lib/prompt.js'
5
+
6
+ describe('Termux composer prompt + width guards', () => {
7
+ it('uses a single-cell ASCII prompt marker in Termux mode', () => {
8
+ expect(composerPromptText('❯', 'coder', false, true, 50)).toBe('>')
9
+ })
10
+
11
+ it('suppresses profile prefixes on narrow Termux panes', () => {
12
+ expect(composerPromptText('❯', 'upstr', false, true, 72)).toBe('>')
13
+ })
14
+
15
+ it('keeps profile context on very wide Termux panes', () => {
16
+ expect(composerPromptText('❯', 'upstr', false, true, 120)).toBe('upstr >')
17
+ })
18
+
19
+ it('reserves fewer columns for gutter on narrow Termux widths', () => {
20
+ // 32 columns after prompt: desktop reserves 2 for transcript scrollbar,
21
+ // Termux keeps those 2 columns for the active composer.
22
+ expect(stableComposerColumns(40, 8, false)).toBe(28)
23
+ expect(stableComposerColumns(40, 8, true)).toBe(30)
24
+
25
+ // With ample room, Termux still reserves the gutter for alignment.
26
+ expect(stableComposerColumns(60, 8, true)).toBe(48)
27
+ })
28
+
29
+ it('never over-allocates transcript body width on narrow panes', () => {
30
+ // Old behavior hard-minned to 20 columns and overflowed narrow layouts.
31
+ expect(transcriptBodyWidth(24, 'assistant', '>', true)).toBe(19)
32
+ expect(transcriptBodyWidth(24, 'user', 'upstr >', true)).toBe(14)
33
+ expect(transcriptBodyWidth(10, 'user', '>', true)).toBeGreaterThanOrEqual(1)
34
+ })
35
+
36
+ it('keeps legacy desktop floor outside Termux mode', () => {
37
+ expect(transcriptBodyWidth(24, 'assistant', '>')).toBe(20)
38
+ expect(transcriptBodyWidth(24, 'user', 'upstr >')).toBe(20)
39
+ })
40
+ })
@@ -0,0 +1,233 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import {
4
+ boundedLiveRenderText,
5
+ buildToolTrailLine,
6
+ buildVerboseToolTrailLine,
7
+ edgePreview,
8
+ estimateRows,
9
+ estimateTokensRough,
10
+ fmtK,
11
+ hasAnsi,
12
+ isToolTrailResultLine,
13
+ lastCotTrailIndex,
14
+ parseToolTrailResultLine,
15
+ pasteTokenLabel,
16
+ sameToolTrailGroup,
17
+ sanitizeAnsiForRender,
18
+ splitToolDuration,
19
+ stripAnsi,
20
+ thinkingPreview
21
+ } from '../lib/text.js'
22
+
23
+ describe('isToolTrailResultLine', () => {
24
+ it('detects completion markers', () => {
25
+ expect(isToolTrailResultLine('foo ✓')).toBe(true)
26
+ expect(isToolTrailResultLine('foo ✗')).toBe(true)
27
+ expect(isToolTrailResultLine('drafting x…')).toBe(false)
28
+ })
29
+ })
30
+
31
+ describe('buildToolTrailLine', () => {
32
+ it('puts completion duration inline before the result marker', () => {
33
+ const line = buildToolTrailLine('read_file', 'x', false, '', 0.94)
34
+
35
+ expect(line).toBe('Read File("x") (0.9s) ✓')
36
+ expect(parseToolTrailResultLine(line)).toEqual({ call: 'Read File("x") (0.9s)', detail: '', mark: '✓' })
37
+ expect(splitToolDuration('Read File("x") (0.9s)')).toEqual({ label: 'Read File("x")', duration: ' (0.9s)' })
38
+ })
39
+ })
40
+
41
+ describe('buildVerboseToolTrailLine', () => {
42
+ it('preserves multiline args and result details', () => {
43
+ const line = buildVerboseToolTrailLine(
44
+ 'terminal',
45
+ 'npm test',
46
+ false,
47
+ 1.25,
48
+ '{\n "cmd": "npm test"\n}',
49
+ 'first line\nsecond :: line'
50
+ )
51
+
52
+ expect(line).toContain('Args:\n{')
53
+ expect(line).toContain('Result:\nfirst line\nsecond :: line')
54
+ expect(parseToolTrailResultLine(line)).toEqual({
55
+ call: 'Terminal("npm test") (1.3s)',
56
+ detail: 'Args:\n{\n "cmd": "npm test"\n}\nResult:\nfirst line\nsecond :: line',
57
+ mark: '✓'
58
+ })
59
+ })
60
+
61
+ it('labels verbose failures as errors', () => {
62
+ const line = buildVerboseToolTrailLine('terminal', 'npm test', true, 0.5, undefined, 'command failed')
63
+
64
+ expect(line).toContain('Error:\ncommand failed')
65
+ expect(line).not.toContain('Result:\ncommand failed')
66
+ expect(parseToolTrailResultLine(line)).toEqual({
67
+ call: 'Terminal("npm test") (0.5s)',
68
+ detail: 'Error:\ncommand failed',
69
+ mark: '✗'
70
+ })
71
+ })
72
+ })
73
+
74
+ describe('lastCotTrailIndex', () => {
75
+ it('finds last non-result line', () => {
76
+ expect(lastCotTrailIndex(['a ✓', 'thinking…'])).toBe(1)
77
+ expect(lastCotTrailIndex(['only result ✓'])).toBe(-1)
78
+ })
79
+ })
80
+
81
+ describe('sameToolTrailGroup', () => {
82
+ it('matches bare check lines', () => {
83
+ expect(sameToolTrailGroup('searching', 'searching ✓')).toBe(true)
84
+ expect(sameToolTrailGroup('searching', 'searching ✗')).toBe(true)
85
+ })
86
+
87
+ it('matches contextual lines', () => {
88
+ expect(sameToolTrailGroup('searching', 'searching: * ✓')).toBe(true)
89
+ expect(sameToolTrailGroup('searching', 'searching: foo ✓')).toBe(true)
90
+ })
91
+
92
+ it('rejects other tools', () => {
93
+ expect(sameToolTrailGroup('searching', 'reading ✓')).toBe(false)
94
+ expect(sameToolTrailGroup('searching', 'searching extra ✓')).toBe(false)
95
+ })
96
+ })
97
+
98
+ describe('fmtK', () => {
99
+ it('keeps small numbers plain', () => {
100
+ expect(fmtK(999)).toBe('999')
101
+ })
102
+
103
+ it('formats thousands as lowercase k', () => {
104
+ expect(fmtK(1000)).toBe('1k')
105
+ expect(fmtK(1500)).toBe('1.5k')
106
+ })
107
+
108
+ it('formats millions and billions with lowercase suffixes', () => {
109
+ expect(fmtK(1_000_000)).toBe('1m')
110
+ expect(fmtK(1_000_000_000)).toBe('1b')
111
+ })
112
+ })
113
+
114
+ describe('estimateTokensRough', () => {
115
+ it('uses 4 chars per token rounding up', () => {
116
+ expect(estimateTokensRough('')).toBe(0)
117
+ expect(estimateTokensRough('a')).toBe(1)
118
+ expect(estimateTokensRough('abcd')).toBe(1)
119
+ expect(estimateTokensRough('abcde')).toBe(2)
120
+ })
121
+ })
122
+
123
+ describe('ANSI sanitizers', () => {
124
+ const ESC = String.fromCharCode(27)
125
+ const BEL = String.fromCharCode(7)
126
+
127
+ it('strips CSI/OSC/control bytes from plain previews', () => {
128
+ const sample = `A${ESC}[31mB${ESC}[39m${ESC}[2J${ESC}]0;title${BEL}C${ESC}[?25lD`
129
+
130
+ expect(stripAnsi(sample)).toBe('ABCD')
131
+ })
132
+
133
+ it('strips incomplete CSI prefixes and carriage returns', () => {
134
+ const sample = `A${ESC}[31mB${ESC}[12;${ESC}[CD\rE`
135
+
136
+ expect(stripAnsi(sample)).toBe('ABDE')
137
+ })
138
+
139
+ it('keeps SGR color spans but removes cursor controls for Ansi rendering', () => {
140
+ const sample = `A${ESC}[31mB${ESC}[39m${ESC}[2J${ESC}]0;title${BEL}${ESC}[?25lC`
141
+
142
+ expect(sanitizeAnsiForRender(sample)).toBe(`A${ESC}[31mB${ESC}[39mC`)
143
+ })
144
+
145
+ it('keeps valid SGR while removing dangling CSI and carriage returns', () => {
146
+ const sample = `A${ESC}[31mB${ESC}[12;${ESC}[39mC\rD`
147
+
148
+ expect(sanitizeAnsiForRender(sample)).toBe(`A${ESC}[31mB${ESC}[39mCD`)
149
+ })
150
+
151
+ it('strips multi-byte non-CSI ESC sequences without leaving trailing bytes', () => {
152
+ const sample = `A${ESC}(0B${ESC}%GC${ESC})0D`
153
+
154
+ expect(stripAnsi(sample)).toBe('ABCD')
155
+ expect(sanitizeAnsiForRender(sample)).toBe('ABCD')
156
+ })
157
+
158
+ it('detects non-CSI escape prefixes too', () => {
159
+ expect(hasAnsi(`ok${ESC}Ppayload${ESC}\\`)).toBe(true)
160
+ })
161
+ })
162
+
163
+ describe('thinkingPreview', () => {
164
+ it('adds paragraph breaks before markdown thinking headings', () => {
165
+ const raw =
166
+ '**Considering user instructions**\nI need to answer.**Planning tool execution**\nI can run tools.**Determining weather search parameters**\nUse SF.'
167
+
168
+ expect(thinkingPreview(raw, 'full')).toBe(
169
+ '**Considering user instructions**\nI need to answer.\n\n**Planning tool execution**\nI can run tools.\n\n**Determining weather search parameters**\nUse SF.'
170
+ )
171
+ })
172
+ })
173
+
174
+ describe('boundedLiveRenderText', () => {
175
+ it('preserves short live text verbatim', () => {
176
+ expect(boundedLiveRenderText('one\ntwo', { maxChars: 100, maxLines: 10 })).toBe('one\ntwo')
177
+ })
178
+
179
+ it('keeps the live tail by character budget', () => {
180
+ const out = boundedLiveRenderText('abcdefghij', { maxChars: 4, maxLines: 10 })
181
+
182
+ expect(out).toContain('ghij')
183
+ expect(out).toContain('omitted')
184
+ expect(out).not.toContain('abcdef')
185
+ })
186
+
187
+ it('keeps the live tail by line budget', () => {
188
+ const out = boundedLiveRenderText(['a', 'b', 'c', 'd'].join('\n'), { maxChars: 100, maxLines: 2 })
189
+
190
+ expect(out).toContain('c\nd')
191
+ expect(out).toContain('omitted 2 lines')
192
+ expect(out).not.toContain('a\nb')
193
+ })
194
+ })
195
+
196
+ describe('edgePreview', () => {
197
+ it('keeps both ends for long text', () => {
198
+ expect(edgePreview('Vampire Bondage ropes slipped from her neck, still stained with blood', 8, 18)).toBe(
199
+ 'Vampire.. stained with blood'
200
+ )
201
+ })
202
+ })
203
+
204
+ describe('pasteTokenLabel', () => {
205
+ it('builds readable long-paste labels with counts', () => {
206
+ const label = pasteTokenLabel('Vampire Bondage ropes slipped from her neck, still stained with blood', 250)
207
+ expect(label.startsWith('[[ ')).toBe(true)
208
+ expect(label).toContain('[250 lines]')
209
+ expect(label.endsWith(' ]]')).toBe(true)
210
+ })
211
+ })
212
+
213
+ describe('estimateRows', () => {
214
+ it('handles tilde code fences', () => {
215
+ const md = ['~~~markdown', '# heading', '~~~'].join('\n')
216
+
217
+ expect(estimateRows(md, 40)).toBeGreaterThanOrEqual(2)
218
+ })
219
+
220
+ it('handles checklist bullets as list rows', () => {
221
+ const md = ['- [x] done', '- [ ] todo'].join('\n')
222
+
223
+ expect(estimateRows(md, 40)).toBe(2)
224
+ })
225
+
226
+ it('keeps intraword underscores when sizing snake_case identifiers', () => {
227
+ const w = 80
228
+ const snake = 'look at test_case_with_underscores now'
229
+ const plain = 'look at test case with underscores now'
230
+
231
+ expect(estimateRows(snake, w)).toBe(estimateRows(plain, w))
232
+ })
233
+ })