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,93 @@
1
+ import { Box, Text } from '@nastechai/ink'
2
+ import { memo, useState } from 'react'
3
+
4
+ import { countPendingTodos } from '../lib/liveProgress.js'
5
+ import { todoGlyph, todoTone } from '../lib/todo.js'
6
+ import type { Theme } from '../theme.js'
7
+ import type { TodoItem } from '../types.js'
8
+
9
+ const rowColor = (t: Theme, status: TodoItem['status']) => {
10
+ const tone = todoTone(status)
11
+
12
+ return tone === 'active' ? t.color.text : tone === 'body' ? t.color.statusFg : t.color.muted
13
+ }
14
+
15
+ export const TodoPanel = memo(function TodoPanel({
16
+ collapsed,
17
+ defaultCollapsed = false,
18
+ incomplete = false,
19
+ onToggle,
20
+ t,
21
+ todos
22
+ }: {
23
+ collapsed?: boolean
24
+ defaultCollapsed?: boolean
25
+ incomplete?: boolean
26
+ onToggle?: () => void
27
+ t: Theme
28
+ todos: TodoItem[]
29
+ }) {
30
+ // Fallback local state for archived todos in transcript where there's no
31
+ // external controller. Live TodoPanel passes collapsed+onToggle from the
32
+ // turn store so clicks still work there.
33
+ const [localCollapsed, setLocalCollapsed] = useState(defaultCollapsed)
34
+ const isControlled = typeof collapsed === 'boolean'
35
+ const effectiveCollapsed = isControlled ? collapsed : localCollapsed
36
+
37
+ const handleToggle = () => {
38
+ if (onToggle) {
39
+ onToggle()
40
+
41
+ return
42
+ }
43
+
44
+ if (!isControlled) {
45
+ setLocalCollapsed(v => !v)
46
+ }
47
+ }
48
+
49
+ if (!todos.length) {
50
+ return null
51
+ }
52
+
53
+ const done = todos.filter(todo => todo.status === 'completed').length
54
+ const pending = countPendingTodos(todos)
55
+
56
+ return (
57
+ <Box flexDirection="column" marginBottom={1}>
58
+ <Box onClick={handleToggle}>
59
+ <Text color={t.color.muted}>
60
+ <Text color={t.color.accent}>{effectiveCollapsed ? '▸ ' : '▾ '}</Text>
61
+ <Text bold color={t.color.text}>
62
+ Todo
63
+ </Text>{' '}
64
+ <Text color={t.color.statusFg} dim>
65
+ ({done}/{todos.length})
66
+ </Text>
67
+ {incomplete && pending > 0 && (
68
+ <Text color={t.color.muted} dim>
69
+ {' '}
70
+ · incomplete · {pending} still {pending === 1 ? 'pending' : 'pending/in_progress'}
71
+ </Text>
72
+ )}
73
+ </Text>
74
+ </Box>
75
+
76
+ {!effectiveCollapsed && (
77
+ <Box flexDirection="column" marginLeft={2}>
78
+ {todos.map(todo => {
79
+ const tone = todoTone(todo.status)
80
+ const color = rowColor(t, todo.status)
81
+
82
+ return (
83
+ <Text color={color} dim={tone === 'dim'} key={todo.id}>
84
+ <Text color={color}>{todoGlyph(todo.status)} </Text>
85
+ {todo.content}
86
+ </Text>
87
+ )
88
+ })}
89
+ </Box>
90
+ )}
91
+ </Box>
92
+ )
93
+ })
@@ -0,0 +1,64 @@
1
+ import type { MouseTrackingMode } from '@nastechai/ink'
2
+ import { isTermuxTuiMode } from '../lib/termux.js'
3
+
4
+ const truthy = (v?: string) => /^(?:1|true|yes|on)$/i.test((v ?? '').trim())
5
+ const falsy = (v?: string) => /^(?:0|false|no|off)$/i.test((v ?? '').trim())
6
+
7
+ const parseToggle = (v?: string): boolean | null => {
8
+ const raw = (v ?? '').trim()
9
+
10
+ if (!raw) {
11
+ return null
12
+ }
13
+
14
+ if (truthy(raw)) {
15
+ return true
16
+ }
17
+
18
+ if (falsy(raw)) {
19
+ return false
20
+ }
21
+
22
+ return null
23
+ }
24
+
25
+ export const TERMUX_TUI_MODE = isTermuxTuiMode()
26
+
27
+ export const STARTUP_RESUME_ID = (process.env.NASTECH_TUI_RESUME ?? '').trim()
28
+ export const STARTUP_QUERY = (process.env.NASTECH_TUI_QUERY ?? '').trim()
29
+ export const STARTUP_IMAGE = (process.env.NASTECH_TUI_IMAGE ?? '').trim()
30
+
31
+ // Mouse tracking mode resolution at startup. Per-mode selection (off|wheel|
32
+ // buttons|all) lives in display.mouse_tracking in config.yaml — these env
33
+ // vars only set the boot-time default before that config is applied.
34
+ //
35
+ // Precedence (highest first):
36
+ //
37
+ // - NASTECH_TUI_MOUSE_TRACKING (truthy/falsy) explicitly overrides everything.
38
+ // This is the "force a value" knob and intentionally beats the legacy
39
+ // kill-switch and the Termux default.
40
+ // - NASTECH_TUI_DISABLE_MOUSE=1 forces mouse off — the legacy kill switch.
41
+ // - On Termux the default is mouse off so touch selection isn't intercepted
42
+ // by terminal mouse protocols. Desktop defaults to 'all' to preserve prior
43
+ // behavior.
44
+ const mouseTrackingOverride = parseToggle(process.env.NASTECH_TUI_MOUSE_TRACKING)
45
+ const mouseTrackingDisabledLegacy = truthy(process.env.NASTECH_TUI_DISABLE_MOUSE)
46
+ const resolvedBootMouseEnabled =
47
+ mouseTrackingOverride ?? (TERMUX_TUI_MODE ? false : !mouseTrackingDisabledLegacy)
48
+ export const MOUSE_TRACKING: MouseTrackingMode = resolvedBootMouseEnabled ? 'all' : 'off'
49
+
50
+ export const NO_CONFIRM_DESTRUCTIVE = truthy(process.env.NASTECH_TUI_NO_CONFIRM)
51
+
52
+ const inlineOverride = parseToggle(process.env.NASTECH_TUI_INLINE)
53
+
54
+ // Skip AlternateScreen — TUI renders into the primary buffer so the host
55
+ // terminal's native scrollback captures whatever scrolls off the top.
56
+ //
57
+ // On Termux we default this on: users often background/foreground the app,
58
+ // and primary-buffer rendering makes long-thread review and copy/paste much
59
+ // less fragile. Override explicitly with NASTECH_TUI_INLINE=0/1.
60
+ export const INLINE_MODE = inlineOverride ?? TERMUX_TUI_MODE
61
+
62
+ // Live FPS counter overlay, fed by ink's onFrame (real render rate, not a
63
+ // synthetic timer).
64
+ export const SHOW_FPS = truthy(process.env.NASTECH_TUI_FPS)
@@ -0,0 +1,13 @@
1
+ export const LARGE_PASTE = { lines: 5 }
2
+
3
+ export const LIVE_RENDER_MAX_CHARS = 16_000
4
+ export const LIVE_RENDER_MAX_LINES = 240
5
+
6
+ export const LONG_MSG = 300
7
+ export const MAX_HISTORY = 800
8
+ export const THINKING_COT_MAX = 160
9
+
10
+ // Rows per wheel event (pre-accel). 1 keeps Ink's DECSTBM fast path live
11
+ // (each scroll < viewport-1) and produces smooth motion. wheelAccel.ts
12
+ // ramps this on sustained scrolls.
13
+ export const WHEEL_SCROLL_STEP = 1
@@ -0,0 +1,6 @@
1
+ export const STREAM_BATCH_MS = 16
2
+ export const STREAM_IDLE_BATCH_MS = 16
3
+ export const STREAM_SCROLL_BATCH_MS = 96
4
+ export const STREAM_TYPING_BATCH_MS = 80
5
+ export const TYPING_IDLE_MS = 250
6
+ export const REASONING_PULSE_MS = 700
@@ -0,0 +1 @@
1
+ export const LONG_RUN_CHARMS = ['still cooking…', 'polishing edges…', 'asking the void nicely…']
@@ -0,0 +1,17 @@
1
+ export const FACES = [
2
+ '(。•́︿•̀。)',
3
+ '(◔_◔)',
4
+ '(¬‿¬)',
5
+ '( •_•)>⌐■-■',
6
+ '(⌐■_■)',
7
+ '(´・_・`)',
8
+ '◉_◉',
9
+ '(°ロ°)',
10
+ '( ˘⌣˘)♡',
11
+ 'ヽ(>∀<☆)☆',
12
+ '٩(๑❛ᴗ❛๑)۶',
13
+ '(⊙_⊙)',
14
+ '(¬_¬)',
15
+ '( ͡° ͜ʖ ͡°)',
16
+ 'ಠ_ಠ'
17
+ ]
@@ -0,0 +1,30 @@
1
+ const FORTUNES = [
2
+ 'you are one clean refactor away from clarity',
3
+ 'a tiny rename today prevents a huge bug tomorrow',
4
+ 'your next commit message will be immaculate',
5
+ 'the edge case you are ignoring is already solved in your head',
6
+ 'minimal diff, maximal calm',
7
+ 'today favors bold deletions over new abstractions',
8
+ 'the right helper is already in your codebase',
9
+ 'you will ship before overthinking catches up',
10
+ 'tests are about to save your future self',
11
+ 'your instincts are correctly suspicious of that one branch'
12
+ ]
13
+
14
+ const LEGENDARY = [
15
+ 'legendary drop: one-line fix, first try',
16
+ 'legendary drop: every flaky test passes cleanly',
17
+ 'legendary drop: your diff teaches by itself'
18
+ ]
19
+
20
+ const hash = (s: string) => [...s].reduce((h, c) => Math.imul(h ^ c.charCodeAt(0), 16777619), 2166136261) >>> 0
21
+
22
+ const fromScore = (n: number) => {
23
+ const rare = n % 20 === 0
24
+ const bag = rare ? LEGENDARY : FORTUNES
25
+
26
+ return `${rare ? '🌟' : '🔮'} ${bag[n % bag.length]}`
27
+ }
28
+
29
+ export const randomFortune = () => fromScore(Math.floor(Math.random() * 0x7fffffff))
30
+ export const dailyFortune = (seed: null | string) => fromScore(hash(`${seed || 'anon'}|${new Date().toDateString()}`))
@@ -0,0 +1,37 @@
1
+ import { isMac, isRemoteShell } from '../lib/platform.js'
2
+
3
+ const action = isMac ? 'Cmd' : 'Ctrl'
4
+ const paste = isMac ? 'Cmd' : 'Alt'
5
+
6
+ const copyHotkeys: [string, string][] = isMac
7
+ ? [
8
+ ['Cmd+C', 'copy selection'],
9
+ ['Ctrl+C', 'interrupt / clear draft / exit']
10
+ ]
11
+ : isRemoteShell()
12
+ ? [
13
+ ['Cmd+C', 'copy selection when forwarded by the terminal'],
14
+ ['Ctrl+C', 'copy selection / interrupt / clear draft / exit']
15
+ ]
16
+ : [['Ctrl+C', 'copy selection / interrupt / clear draft / exit']]
17
+
18
+ export const HOTKEYS: [string, string][] = [
19
+ ...copyHotkeys,
20
+ [action + '+D', 'exit'],
21
+ [action + '+G / Alt+G', 'open $EDITOR (Alt+G fallback for VSCode/Cursor)'],
22
+ [action + '+L', 'redraw / repaint'],
23
+ [paste + '+V / /paste', 'paste text; /paste attaches clipboard image'],
24
+ ['Tab', 'apply completion'],
25
+ ['↑/↓', 'completions / queue edit / history'],
26
+ ['Ctrl+X', 'open live session switcher (deletes queued message while editing)'],
27
+ [action + '+A/E', 'home / end of line'],
28
+ [action + '+Z / ' + action + '+Y', 'undo / redo input edits'],
29
+ [action + '+W', 'delete word'],
30
+ [action + '+U/K', 'delete to start / end'],
31
+ [action + '+←/→', 'jump word'],
32
+ ['Home/End', 'start / end of line'],
33
+ ['Shift+Enter / Alt+Enter', 'insert newline'],
34
+ ['\\+Enter', 'multi-line continuation (fallback)'],
35
+ ['!<cmd>', 'run a shell command (e.g. !ls, !git status)'],
36
+ ['{!<cmd>}', 'interpolate shell output inline (e.g. "branch is {!git branch --show-current}")']
37
+ ]
@@ -0,0 +1,13 @@
1
+ import { pick } from '../lib/text.js'
2
+
3
+ export const PLACEHOLDERS = [
4
+ 'Ask me anything…',
5
+ 'Try "explain this codebase"',
6
+ 'Try "write a test for…"',
7
+ 'Try "refactor the auth module"',
8
+ 'Try "/help" for commands',
9
+ 'Try "fix the lint errors"',
10
+ 'Try "how does the config loader work?"'
11
+ ]
12
+
13
+ export const PLACEHOLDER = pick(PLACEHOLDERS)
@@ -0,0 +1,17 @@
1
+ import type { PanelSection } from '../types.js'
2
+
3
+ export const SETUP_REQUIRED_TITLE = 'Setup Required'
4
+
5
+ export const buildSetupRequiredSections = (): PanelSection[] => [
6
+ {
7
+ text: 'NasTech needs a model provider before the TUI can start a session.'
8
+ },
9
+ {
10
+ rows: [
11
+ ['/model', 'configure provider + model in-place'],
12
+ ['/setup', 'run full first-time setup wizard in-place'],
13
+ ['Ctrl+C', 'exit and run `nastech setup` manually']
14
+ ],
15
+ title: 'Actions'
16
+ }
17
+ ]
@@ -0,0 +1,38 @@
1
+ export const TOOL_VERBS: Record<string, string> = {
2
+ browser: 'browsing',
3
+ clarify: 'asking',
4
+ create_file: 'creating',
5
+ delegate_task: 'delegating',
6
+ delete_file: 'deleting',
7
+ execute_code: 'executing',
8
+ image_generate: 'generating',
9
+ list_files: 'listing',
10
+ memory: 'remembering',
11
+ patch: 'patching',
12
+ read_file: 'reading',
13
+ run_command: 'running',
14
+ search_code: 'searching',
15
+ search_files: 'searching',
16
+ terminal: 'terminal',
17
+ web_extract: 'extracting',
18
+ web_search: 'searching',
19
+ write_file: 'writing'
20
+ }
21
+
22
+ export const VERBS = [
23
+ 'pondering',
24
+ 'contemplating',
25
+ 'musing',
26
+ 'cogitating',
27
+ 'ruminating',
28
+ 'deliberating',
29
+ 'mulling',
30
+ 'reflecting',
31
+ 'processing',
32
+ 'reasoning',
33
+ 'analyzing',
34
+ 'computing',
35
+ 'synthesizing',
36
+ 'formulating',
37
+ 'brainstorming'
38
+ ]
@@ -0,0 +1,146 @@
1
+ import type { DetailsMode, Msg, SectionVisibility } from '../types.js'
2
+
3
+ import { sectionMode } from './details.js'
4
+
5
+ /**
6
+ * Visual group a transcript block belongs to. Blocks in the same group render
7
+ * flush; a single blank line opens at each group boundary. So a run of tool
8
+ * trails (or model paragraphs) reads as one section and the eye only catches a
9
+ * gap where the *kind* of content actually changes — the boundary between
10
+ * reasoning/tool trails, model prose, and notes/errors.
11
+ *
12
+ * user — the human turn (owns its separator + margins in MessageLine)
13
+ * model — assistant prose, the model's voice
14
+ * trail — reasoning + tool-call trails (the agent's working area)
15
+ * note — system notes and errors (a quieter band)
16
+ * diff — inline patch segments (an island, owns its own margins)
17
+ * slash — slash-command echoes (owns its margin)
18
+ * intro — banner / panels (rendered out-of-band, never gapped here)
19
+ */
20
+ export type BlockGroup = 'diff' | 'intro' | 'model' | 'note' | 'slash' | 'trail' | 'user'
21
+
22
+ export const messageGroup = (msg: Pick<Msg, 'kind' | 'role'>): BlockGroup => {
23
+ switch (msg.kind) {
24
+ case 'intro':
25
+ case 'panel':
26
+ return 'intro'
27
+ case 'slash':
28
+ return 'slash'
29
+ case 'diff':
30
+ return 'diff'
31
+ case 'trail':
32
+ return 'trail'
33
+ }
34
+
35
+ if (msg.role === 'user') {
36
+ return 'user'
37
+ }
38
+
39
+ // Assistant prose is the model's voice; system notes/errors are their own
40
+ // band. (No runtime block uses role 'tool' — tool *results* fold into
41
+ // trails — so a stray 'tool' falls through to the note band harmlessly.)
42
+ return msg.role === 'assistant' ? 'model' : 'note'
43
+ }
44
+
45
+ // Groups whose leading gap is already owned by their own chrome in
46
+ // MessageLine (the turn separator + top margin for user, the top margin for
47
+ // slash, the top+bottom margins for diff) or that are painted out-of-band
48
+ // (intro). The grouping primitive only spaces the model working area —
49
+ // model prose, reasoning/tool trails, and notes/errors.
50
+ const SELF_SPACED: ReadonlySet<BlockGroup> = new Set(['diff', 'intro', 'slash', 'user'])
51
+
52
+ // Groups that already paint a trailing blank line beneath themselves
53
+ // (marginBottom in MessageLine), so the block that follows must not add its
54
+ // own leading gap or the single boundary would become a double gap.
55
+ const PAINTS_TRAILING_GAP: ReadonlySet<BlockGroup> = new Set(['diff', 'user'])
56
+
57
+ /**
58
+ * Whether `cur` renders one blank line above it, given the block rendered
59
+ * directly above it (`prev`). True only where the visual group changes, and
60
+ * only for the model-working-area bands (model / trail / note) — user, slash,
61
+ * diff, and intro keep their existing spacing.
62
+ *
63
+ * Streaming-safe by construction: the result depends on the *predecessor's*
64
+ * group, never on `cur`'s own (live, changing) content. The actively-streaming
65
+ * assistant block therefore computes the same gap while it streams as the
66
+ * settled segment does once it flushes, so the live area never jumps.
67
+ */
68
+ export const hasLeadGap = (
69
+ prev: Pick<Msg, 'kind' | 'role'> | undefined,
70
+ cur: Pick<Msg, 'kind' | 'role'>
71
+ ): boolean => {
72
+ const group = messageGroup(cur)
73
+
74
+ if (SELF_SPACED.has(group)) {
75
+ return false
76
+ }
77
+
78
+ if (!prev) {
79
+ return false
80
+ }
81
+
82
+ const prevGroup = messageGroup(prev)
83
+
84
+ return prevGroup !== group && !PAINTS_TRAILING_GAP.has(prevGroup)
85
+ }
86
+
87
+ export interface DetailsCtx {
88
+ commandOverride?: boolean
89
+ detailsMode: DetailsMode
90
+ sections?: SectionVisibility
91
+ }
92
+
93
+ const trailAllHidden = (ctx: DetailsCtx): boolean =>
94
+ sectionMode('thinking', ctx.detailsMode, ctx.sections, ctx.commandOverride) === 'hidden' &&
95
+ sectionMode('tools', ctx.detailsMode, ctx.sections, ctx.commandOverride) === 'hidden' &&
96
+ sectionMode('activity', ctx.detailsMode, ctx.sections, ctx.commandOverride) === 'hidden'
97
+
98
+ /**
99
+ * Whether a settled transcript block paints anything. A trail renders nothing
100
+ * when it has no reasoning/tools/todos to show (e.g. the finalDetails segment
101
+ * that carries only a token tally) or when every section it does have is hidden
102
+ * (`/details hidden`); every other block draws at least one row. A block that
103
+ * renders nothing is *transparent* to grouping: the block below it draws its
104
+ * boundary against the nearest visible block instead (see prevRenderedMsg), so
105
+ * a hidden or content-less trail never leaves a floating blank line, doubles
106
+ * the gap after a user prompt, or pads the space above the final reply. In the
107
+ * default/collapsed modes content-bearing trails always render, so this is a
108
+ * no-op there.
109
+ */
110
+ export const blockRenders = (msg: Pick<Msg, 'kind' | 'thinking' | 'todos' | 'tools'>, ctx: DetailsCtx): boolean => {
111
+ if (msg.kind !== 'trail') {
112
+ return true
113
+ }
114
+
115
+ if (msg.todos?.length) {
116
+ return true
117
+ }
118
+
119
+ if (!(msg.tools?.length || msg.thinking?.trim())) {
120
+ return false
121
+ }
122
+
123
+ return !trailAllHidden(ctx)
124
+ }
125
+
126
+ /**
127
+ * The nearest block above `index` that actually renders, resolved through a
128
+ * lazy accessor so it works over either the virtualized history rows or the
129
+ * live block list. This is the grouping predecessor — using it (instead of the
130
+ * literal previous row) keeps hidden trails from interrupting the rhythm.
131
+ */
132
+ export const prevRenderedMsg = (
133
+ msgAt: (i: number) => Msg | undefined,
134
+ index: number,
135
+ ctx: DetailsCtx
136
+ ): Msg | undefined => {
137
+ for (let i = index - 1; i >= 0; i--) {
138
+ const candidate = msgAt(i)
139
+
140
+ if (candidate && blockRenders(candidate, ctx)) {
141
+ return candidate
142
+ }
143
+ }
144
+
145
+ return undefined
146
+ }
@@ -0,0 +1,76 @@
1
+ import type { DetailsMode, SectionName, SectionVisibility } from '../types.js'
2
+
3
+ const MODES = ['hidden', 'collapsed', 'expanded'] as const
4
+
5
+ export const SECTION_NAMES = ['thinking', 'tools', 'subagents', 'activity'] as const
6
+
7
+ // Out-of-the-box per-section defaults — applied when the user hasn't pinned
8
+ // an explicit override and layered ABOVE the global details_mode:
9
+ //
10
+ // - thinking / tools: expanded — stream open so the turn reads like a
11
+ // live transcript (reasoning + tool calls side by side) instead of a
12
+ // wall of chevrons the user has to click every turn.
13
+ // - activity: hidden — ambient meta (gateway hints, terminal-parity
14
+ // nudges, background notifications) is noise for typical use. Tool
15
+ // failures still render inline on the failing tool row, and ambient
16
+ // errors/warnings surface via the floating-alert backstop when every
17
+ // panel resolves to hidden.
18
+ // - subagents: not set — falls through to the global details_mode so
19
+ // Spawn trees stay under a chevron until a delegation actually happens.
20
+ //
21
+ // Opt out of any of these with `display.sections.<name>` in config.yaml
22
+ // or at runtime via `/details <name> collapsed|hidden`.
23
+ const SECTION_DEFAULTS: SectionVisibility = {
24
+ thinking: 'expanded',
25
+ tools: 'expanded',
26
+ activity: 'hidden'
27
+ }
28
+
29
+ const THINKING_FALLBACK: Record<string, DetailsMode> = {
30
+ collapsed: 'collapsed',
31
+ full: 'expanded',
32
+ truncated: 'collapsed'
33
+ }
34
+
35
+ const norm = (v: unknown) =>
36
+ String(v ?? '')
37
+ .trim()
38
+ .toLowerCase()
39
+
40
+ export const parseDetailsMode = (v: unknown): DetailsMode | null => MODES.find(m => m === norm(v)) ?? null
41
+
42
+ export const isSectionName = (v: unknown): v is SectionName =>
43
+ typeof v === 'string' && (SECTION_NAMES as readonly string[]).includes(v)
44
+
45
+ export const resolveDetailsMode = (d?: { details_mode?: unknown; thinking_mode?: unknown } | null): DetailsMode =>
46
+ parseDetailsMode(d?.details_mode) ?? THINKING_FALLBACK[norm(d?.thinking_mode)] ?? 'collapsed'
47
+
48
+ // Build SectionVisibility from a free-form blob. Unknown section names and
49
+ // invalid modes are dropped silently — partial overrides are intentional, so
50
+ // missing keys fall through to SECTION_DEFAULTS / global at lookup time.
51
+ export const resolveSections = (raw: unknown): SectionVisibility =>
52
+ raw && typeof raw === 'object' && !Array.isArray(raw)
53
+ ? (Object.fromEntries(
54
+ Object.entries(raw as Record<string, unknown>)
55
+ .map(([k, v]) => [k, parseDetailsMode(v)] as const)
56
+ .filter(([k, m]) => !!m && isSectionName(k))
57
+ ) as SectionVisibility)
58
+ : {}
59
+
60
+ // Effective mode for one section: explicit override → global command mode →
61
+ // built-in live-stream defaults → global config mode.
62
+ //
63
+ // The `commandOverride` flag is set for in-session `/details <mode>` changes.
64
+ // That command should immediately apply to every section, including sections
65
+ // with built-in defaults like thinking/tools=expanded and activity=hidden. On
66
+ // startup/config sync we keep those defaults layered above the persisted global
67
+ // config so the TUI still opens live reasoning/tools by default unless the user
68
+ // pins explicit per-section overrides.
69
+ export const sectionMode = (
70
+ name: SectionName,
71
+ global: DetailsMode,
72
+ sections?: SectionVisibility,
73
+ commandOverride = false
74
+ ): DetailsMode => sections?.[name] ?? (commandOverride ? global : (SECTION_DEFAULTS[name] ?? global))
75
+
76
+ export const nextDetailsMode = (m: DetailsMode): DetailsMode => MODES[(MODES.indexOf(m) + 1) % MODES.length]!
@@ -0,0 +1,91 @@
1
+ import { LONG_MSG } from '../config/limits.js'
2
+ import { buildToolTrailLine, fmtK } from '../lib/text.js'
3
+ import type { Msg, SessionInfo } from '../types.js'
4
+
5
+ export const introMsg = (info: SessionInfo): Msg => ({ info, kind: 'intro', role: 'system', text: '' })
6
+
7
+ export const imageTokenMeta = (info?: ImageMeta | null) => {
8
+ const { width, height, token_estimate: t } = info ?? {}
9
+
10
+ return [width && height ? `${width}x${height}` : '', (t ?? 0) > 0 ? `~${fmtK(t!)} tok` : '']
11
+ .filter(Boolean)
12
+ .join(' · ')
13
+ }
14
+
15
+ export const attachedImageNotice = (info?: ({ name?: string } & ImageMeta) | null) => {
16
+ const meta = imageTokenMeta(info)
17
+ const label = info?.name ? `📎 Attached image: ${info.name}` : '📎 Attached image'
18
+
19
+ return `${label}${meta ? ` · ${meta}` : ''}`
20
+ }
21
+
22
+ export const userDisplay = (text: string) => {
23
+ if (text.length <= LONG_MSG) {
24
+ return text
25
+ }
26
+
27
+ const first = text.split('\n')[0]?.trim() ?? ''
28
+ const words = first.split(/\s+/).filter(Boolean)
29
+ const prefix = (words.length > 1 ? words.slice(0, 4).join(' ') : first).slice(0, 80)
30
+
31
+ return `${prefix || '(message)'} [long message]`
32
+ }
33
+
34
+ export const toTranscriptMessages = (rows: unknown): Msg[] => {
35
+ if (!Array.isArray(rows)) {
36
+ return []
37
+ }
38
+
39
+ const out: Msg[] = []
40
+ let pending: string[] = []
41
+
42
+ for (const row of rows) {
43
+ if (!row || typeof row !== 'object') {
44
+ continue
45
+ }
46
+
47
+ const { context, name, role, text } = row as TranscriptRow
48
+
49
+ if (role === 'tool') {
50
+ pending.push(buildToolTrailLine(name ?? 'tool', context ?? ''))
51
+
52
+ continue
53
+ }
54
+
55
+ if (typeof text !== 'string' || !text.trim()) {
56
+ continue
57
+ }
58
+
59
+ if (role === 'assistant') {
60
+ out.push({ role, text, ...(pending.length && { tools: pending }) })
61
+ pending = []
62
+ } else if (role === 'user' || role === 'system') {
63
+ out.push({ role, text })
64
+ pending = []
65
+ }
66
+ }
67
+
68
+ return out
69
+ }
70
+
71
+ export const fmtDuration = (ms: number) => {
72
+ const t = Math.max(0, Math.floor(ms / 1000))
73
+ const h = Math.floor(t / 3600)
74
+ const m = Math.floor((t % 3600) / 60)
75
+ const s = t % 60
76
+
77
+ return h > 0 ? `${h}h ${m}m` : m > 0 ? `${m}m ${s}s` : `${s}s`
78
+ }
79
+
80
+ interface ImageMeta {
81
+ height?: number
82
+ token_estimate?: number
83
+ width?: number
84
+ }
85
+
86
+ interface TranscriptRow {
87
+ context?: string
88
+ name?: string
89
+ role?: string
90
+ text?: string
91
+ }
@@ -0,0 +1,16 @@
1
+ export const shortCwd = (cwd: string, max = 28) => {
2
+ const h = process.env.HOME
3
+ const p = h && cwd.startsWith(h) ? `~${cwd.slice(h.length)}` : cwd
4
+
5
+ return p.length <= max ? p : `…${p.slice(-(max - 1))}`
6
+ }
7
+
8
+ export const fmtCwdBranch = (cwd: string, branch: null | string, max = 40) => {
9
+ if (!branch) {
10
+ return shortCwd(cwd, max)
11
+ }
12
+
13
+ const tag = ` (${branch.length > 16 ? `…${branch.slice(-15)}` : branch})`
14
+
15
+ return `${shortCwd(cwd, Math.max(8, max - tag.length))}${tag}`
16
+ }