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,429 @@
1
+ import { type MutableRefObject, useCallback, useEffect, useRef } from 'react'
2
+
3
+ import { TYPING_IDLE_MS } from '../config/timing.js'
4
+ import { attachedImageNotice } from '../domain/messages.js'
5
+ import { looksLikeSlashCommand } from '../domain/slash.js'
6
+ import type { GatewayClient } from '../gatewayClient.js'
7
+ import type {
8
+ InputDetectDropResponse,
9
+ PromptSubmitResponse,
10
+ SessionSteerResponse,
11
+ ShellExecResponse
12
+ } from '../gatewayTypes.js'
13
+ import { asRpcResult } from '../lib/rpc.js'
14
+ import { hasInterpolation, INTERPOLATION_RE } from '../protocol/interpolation.js'
15
+ import { PASTE_SNIPPET_RE } from '../protocol/paste.js'
16
+ import type { Msg } from '../types.js'
17
+
18
+ import type { ComposerActions, ComposerRefs, ComposerState, PasteSnippet } from './interfaces.js'
19
+ import { turnController } from './turnController.js'
20
+ import { getUiState, patchUiState } from './uiStore.js'
21
+
22
+ const DOUBLE_ENTER_MS = 450
23
+ const SESSION_BUSY_RE = /session busy|waiting for model response/i
24
+
25
+ const isSessionBusyError = (e: unknown) => e instanceof Error && SESSION_BUSY_RE.test(e.message)
26
+
27
+ const expandSnips = (snips: PasteSnippet[]) => {
28
+ const byLabel = new Map<string, string[]>()
29
+
30
+ for (const { label, text } of snips) {
31
+ const hit = byLabel.get(label)
32
+ hit ? hit.push(text) : byLabel.set(label, [text])
33
+ }
34
+
35
+ return (value: string) => value.replace(PASTE_SNIPPET_RE, tok => byLabel.get(tok)?.shift() ?? tok)
36
+ }
37
+
38
+ const spliceMatches = (text: string, matches: RegExpMatchArray[], results: string[]) =>
39
+ matches.reduceRight((acc, m, i) => acc.slice(0, m.index!) + results[i] + acc.slice(m.index! + m[0].length), text)
40
+
41
+ export function useSubmission(opts: UseSubmissionOptions) {
42
+ const {
43
+ appendMessage,
44
+ composerActions,
45
+ composerRefs,
46
+ composerState,
47
+ gw,
48
+ maybeGoodVibes,
49
+ setLastUserMsg,
50
+ slashRef,
51
+ submitRef,
52
+ sys
53
+ } = opts
54
+
55
+ const lastEmptyAt = useRef(0)
56
+ const typingIdleTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
57
+
58
+ useEffect(() => {
59
+ if (typingIdleTimer.current) {
60
+ clearTimeout(typingIdleTimer.current)
61
+ typingIdleTimer.current = null
62
+ }
63
+
64
+ if (!composerState.input && !composerState.inputBuf.length) {
65
+ turnController.relaxStreaming()
66
+
67
+ return
68
+ }
69
+
70
+ if (getUiState().busy) {
71
+ turnController.boostStreamingForTyping()
72
+ }
73
+
74
+ typingIdleTimer.current = setTimeout(() => {
75
+ typingIdleTimer.current = null
76
+ turnController.relaxStreaming()
77
+ }, TYPING_IDLE_MS)
78
+
79
+ return () => {
80
+ if (typingIdleTimer.current) {
81
+ clearTimeout(typingIdleTimer.current)
82
+ typingIdleTimer.current = null
83
+ }
84
+ }
85
+ }, [composerState.input, composerState.inputBuf])
86
+
87
+ const send = useCallback(
88
+ (text: string, showUserMessage = true) => {
89
+ const expand = expandSnips(composerState.pasteSnips)
90
+
91
+ const startSubmit = (displayText: string, submitText: string, showUserMessage = true) => {
92
+ const sid = getUiState().sid
93
+
94
+ if (!sid) {
95
+ return sys('session not ready yet')
96
+ }
97
+
98
+ turnController.clearStatusTimer()
99
+ maybeGoodVibes(submitText)
100
+ setLastUserMsg(text)
101
+
102
+ if (showUserMessage) {
103
+ appendMessage({ role: 'user', text: displayText })
104
+ }
105
+
106
+ patchUiState({ busy: true, status: 'running…' })
107
+ turnController.bufRef = ''
108
+ turnController.interrupted = false
109
+
110
+ gw.request<PromptSubmitResponse>('prompt.submit', { session_id: sid, text: submitText }).catch((e: Error) => {
111
+ if (isSessionBusyError(e)) {
112
+ composerActions.enqueue(submitText)
113
+ patchUiState({ busy: true, status: 'queued for next turn' })
114
+
115
+ return sys(`queued: "${submitText.slice(0, 50)}${submitText.length > 50 ? '…' : ''}"`)
116
+ }
117
+
118
+ sys(`error: ${e.message}`)
119
+ patchUiState({ busy: false, status: 'ready' })
120
+ })
121
+ }
122
+
123
+ const sid = getUiState().sid
124
+
125
+ if (!sid) {
126
+ return sys('session not ready yet')
127
+ }
128
+
129
+ // Always ask the backend whether this looks like a file drop.
130
+ // The backend's _detect_file_drop handles paths with spaces, quotes,
131
+ // Windows drive letters, and escaped characters correctly.
132
+ gw.request<InputDetectDropResponse>('input.detect_drop', { session_id: sid, text })
133
+ .then(r => {
134
+ if (!r?.matched) {
135
+ return startSubmit(text, expand(text), showUserMessage)
136
+ }
137
+
138
+ if (r.is_image) {
139
+ turnController.pushActivity(attachedImageNotice(r))
140
+ } else {
141
+ turnController.pushActivity(`detected file: ${r.name}`)
142
+ }
143
+
144
+ startSubmit(r.text || text, expand(r.text || text), showUserMessage)
145
+ })
146
+ .catch(() => startSubmit(text, expand(text), showUserMessage))
147
+ },
148
+ [appendMessage, composerActions, composerState.pasteSnips, gw, maybeGoodVibes, setLastUserMsg, sys]
149
+ )
150
+
151
+ const shellExec = useCallback(
152
+ (cmd: string) => {
153
+ appendMessage({ role: 'user', text: `!${cmd}` })
154
+ patchUiState({ busy: true, status: 'running…' })
155
+
156
+ gw.request<ShellExecResponse>('shell.exec', { command: cmd })
157
+ .then(raw => {
158
+ const r = asRpcResult<ShellExecResponse>(raw)
159
+
160
+ if (!r) {
161
+ return sys('error: invalid response: shell.exec')
162
+ }
163
+
164
+ const out = [r.stdout, r.stderr].filter(Boolean).join('\n').trim()
165
+
166
+ if (out) {
167
+ sys(out)
168
+ }
169
+
170
+ if (r.code !== 0 || !out) {
171
+ sys(`exit ${r.code}`)
172
+ }
173
+ })
174
+ .catch((e: Error) => sys(`error: ${e.message}`))
175
+ .finally(() => patchUiState({ busy: false, status: 'ready' }))
176
+ },
177
+ [appendMessage, gw, sys]
178
+ )
179
+
180
+ const interpolate = useCallback(
181
+ (text: string, then: (result: string) => void) => {
182
+ patchUiState({ status: 'interpolating…' })
183
+ const matches = [...text.matchAll(new RegExp(INTERPOLATION_RE.source, 'g'))]
184
+
185
+ Promise.all(
186
+ matches.map(m =>
187
+ gw
188
+ .request<ShellExecResponse>('shell.exec', { command: m[1]! })
189
+ .then(raw => {
190
+ const r = asRpcResult<ShellExecResponse>(raw)
191
+
192
+ return [r?.stdout, r?.stderr].filter(Boolean).join('\n').trim()
193
+ })
194
+ .catch(() => '(error)')
195
+ )
196
+ ).then(results => then(spliceMatches(text, matches, results)))
197
+ },
198
+ [gw]
199
+ )
200
+
201
+ const sendQueued = useCallback(
202
+ (text: string) => {
203
+ if (text.startsWith('!')) {
204
+ return shellExec(text.slice(1).trim())
205
+ }
206
+
207
+ if (hasInterpolation(text)) {
208
+ patchUiState({ busy: true })
209
+
210
+ return interpolate(text, send)
211
+ }
212
+
213
+ send(text)
214
+ },
215
+ [interpolate, send, shellExec]
216
+ )
217
+
218
+ // Honors `display.busy_input_mode` from config.yaml (CLI parity):
219
+ // - 'queue' (legacy): append to queueRef; drains on busy → false
220
+ // - 'steer' : inject into the current turn via session.steer; falls
221
+ // back to queue when steer is rejected (no agent / no
222
+ // tool window).
223
+ // - 'interrupt' (default): cancel the in-flight turn, then send the
224
+ // new text as a fresh prompt so it actually moves.
225
+ //
226
+ // `opts.fallbackToFront` controls whether a steer fallback re-inserts
227
+ // at the front of the queue (used by the queue-edit path to preserve
228
+ // a picked item's position); the mainline submit path always appends.
229
+ const handleBusyInput = useCallback(
230
+ (full: string, opts: { fallbackToFront?: boolean } = {}) => {
231
+ const live = getUiState()
232
+ const mode = live.busyInputMode
233
+
234
+ const fallback = (note: string) => {
235
+ if (opts.fallbackToFront) {
236
+ composerRefs.queueRef.current.unshift(full)
237
+ composerActions.syncQueue()
238
+ } else {
239
+ composerActions.enqueue(full)
240
+ }
241
+
242
+ sys(note)
243
+ }
244
+
245
+ if (mode === 'queue') {
246
+ return composerActions.enqueue(full)
247
+ }
248
+
249
+ if (mode === 'steer' && live.sid) {
250
+ gw.request<SessionSteerResponse>('session.steer', { session_id: live.sid, text: full })
251
+ .then(raw => {
252
+ const r = asRpcResult<SessionSteerResponse>(raw)
253
+
254
+ if (r?.status !== 'queued') {
255
+ fallback('steer rejected — message queued for next turn')
256
+ }
257
+ })
258
+ .catch(() => fallback('steer failed — message queued for next turn'))
259
+
260
+ return
261
+ }
262
+
263
+ // 'interrupt' (default): tear down the current turn, then send.
264
+ // `interruptTurn` fires `session.interrupt` without awaiting; if
265
+ // the gateway is still mid-response when `prompt.submit` lands,
266
+ // `send()`'s catch path re-queues with a "queued: ..." sys note
267
+ // (`isSessionBusyError`) — so a lost race degrades to queue
268
+ // semantics, not a dropped message.
269
+ if (live.sid) {
270
+ turnController.interruptTurn({ appendMessage, gw, sid: live.sid, sys })
271
+ }
272
+
273
+ if (hasInterpolation(full)) {
274
+ patchUiState({ busy: true })
275
+
276
+ return interpolate(full, send)
277
+ }
278
+
279
+ send(full)
280
+ },
281
+ [appendMessage, composerActions, composerRefs, gw, interpolate, send, sys]
282
+ )
283
+
284
+ const dispatchSubmission = useCallback(
285
+ (full: string) => {
286
+ if (!full.trim()) {
287
+ return
288
+ }
289
+
290
+ if (looksLikeSlashCommand(full)) {
291
+ appendMessage({ kind: 'slash', role: 'system', text: full })
292
+ composerActions.pushHistory(full)
293
+ slashRef.current(full)
294
+ composerActions.clearIn()
295
+
296
+ return
297
+ }
298
+
299
+ if (full.startsWith('!')) {
300
+ composerActions.clearIn()
301
+
302
+ return shellExec(full.slice(1).trim())
303
+ }
304
+
305
+ const live = getUiState()
306
+
307
+ if (!live.sid) {
308
+ composerActions.pushHistory(full)
309
+ composerActions.enqueue(full)
310
+ composerActions.clearIn()
311
+
312
+ return
313
+ }
314
+
315
+ const editIdx = composerRefs.queueEditRef.current
316
+ composerActions.clearIn()
317
+
318
+ if (editIdx !== null) {
319
+ composerActions.replaceQueue(editIdx, full)
320
+ const picked = composerRefs.queueRef.current.splice(editIdx, 1)[0]
321
+ composerActions.syncQueue()
322
+ composerActions.setQueueEdit(null)
323
+
324
+ if (!picked || !live.sid) {
325
+ return
326
+ }
327
+
328
+ if (getUiState().busy) {
329
+ // 'interrupt' / 'steer' should reach the live turn instead of
330
+ // silently going back to the queue. handleBusyInput resolves
331
+ // mode-specific behavior (interrupt-and-send, steer, or queue).
332
+ if (getUiState().busyInputMode === 'queue') {
333
+ composerRefs.queueRef.current.unshift(picked)
334
+
335
+ return composerActions.syncQueue()
336
+ }
337
+
338
+ return handleBusyInput(picked, { fallbackToFront: true })
339
+ }
340
+
341
+ return sendQueued(picked)
342
+ }
343
+
344
+ composerActions.pushHistory(full)
345
+
346
+ if (getUiState().busy) {
347
+ return handleBusyInput(full)
348
+ }
349
+
350
+ if (hasInterpolation(full)) {
351
+ patchUiState({ busy: true })
352
+
353
+ return interpolate(full, send)
354
+ }
355
+
356
+ send(full)
357
+ },
358
+ [appendMessage, composerActions, composerRefs, handleBusyInput, interpolate, send, sendQueued, shellExec, slashRef]
359
+ )
360
+
361
+ const submit = useCallback(
362
+ (value: string) => {
363
+ if (composerState.completions.length) {
364
+ const row = composerState.completions[composerState.compIdx]
365
+
366
+ if (row?.text) {
367
+ const text = value.startsWith('/') && row.text.startsWith('/') ? row.text.slice(1) : row.text
368
+ const next = value.slice(0, composerState.compReplace) + text
369
+
370
+ if (next !== value) {
371
+ return composerActions.setInput(next)
372
+ }
373
+ }
374
+ }
375
+
376
+ if (!value.trim() && !composerState.inputBuf.length) {
377
+ const live = getUiState()
378
+ const now = Date.now()
379
+ const doubleTap = now - lastEmptyAt.current < DOUBLE_ENTER_MS
380
+ lastEmptyAt.current = now
381
+
382
+ if (doubleTap && live.busy && live.sid) {
383
+ return turnController.interruptTurn({ appendMessage, gw, sid: live.sid, sys })
384
+ }
385
+
386
+ if (doubleTap && live.sid && composerRefs.queueRef.current.length) {
387
+ const next = composerActions.dequeue()
388
+
389
+ composerActions.syncQueue()
390
+
391
+ if (next) {
392
+ composerActions.setQueueEdit(null)
393
+ dispatchSubmission(next)
394
+ }
395
+ }
396
+
397
+ return
398
+ }
399
+
400
+ lastEmptyAt.current = 0
401
+
402
+ if (value.endsWith('\\')) {
403
+ composerActions.setInputBuf(prev => [...prev, value.slice(0, -1)])
404
+
405
+ return composerActions.setInput('')
406
+ }
407
+
408
+ dispatchSubmission([...composerState.inputBuf, value].join('\n'))
409
+ },
410
+ [appendMessage, composerActions, composerRefs, composerState, dispatchSubmission, gw, sys]
411
+ )
412
+
413
+ submitRef.current = submit
414
+
415
+ return { dispatchSubmission, send, sendQueued, submit }
416
+ }
417
+
418
+ export interface UseSubmissionOptions {
419
+ appendMessage: (msg: Msg) => void
420
+ composerActions: ComposerActions
421
+ composerRefs: ComposerRefs
422
+ composerState: ComposerState
423
+ gw: GatewayClient
424
+ maybeGoodVibes: (text: string) => void
425
+ setLastUserMsg: (value: string) => void
426
+ slashRef: MutableRefObject<(cmd: string) => boolean>
427
+ submitRef: MutableRefObject<(value: string) => void>
428
+ sys: (text: string) => void
429
+ }
package/src/app.tsx ADDED
@@ -0,0 +1,25 @@
1
+ import { useStore } from '@nanostores/react'
2
+
3
+ import { GatewayProvider } from './app/gatewayContext.js'
4
+ import { $uiState } from './app/uiStore.js'
5
+ import { useMainApp } from './app/useMainApp.js'
6
+ import { AppLayout } from './components/appLayout.js'
7
+ import type { GatewayClient } from './gatewayClient.js'
8
+
9
+ export function App({ gw }: { gw: GatewayClient }) {
10
+ const { appActions, appComposer, appProgress, appStatus, appTranscript, gateway } = useMainApp(gw)
11
+ const { mouseTracking } = useStore($uiState)
12
+
13
+ return (
14
+ <GatewayProvider value={gateway}>
15
+ <AppLayout
16
+ actions={appActions}
17
+ composer={appComposer}
18
+ mouseTracking={mouseTracking}
19
+ progress={appProgress}
20
+ status={appStatus}
21
+ transcript={appTranscript}
22
+ />
23
+ </GatewayProvider>
24
+ )
25
+ }
package/src/banner.ts ADDED
@@ -0,0 +1,93 @@
1
+ import type { ThemeColors } from './theme.js'
2
+
3
+ const RICH_RE = /\[(?:bold\s+)?(?:dim\s+)?(#(?:[0-9a-fA-F]{3,8}))\]([\s\S]*?)(\[\/\])/g
4
+
5
+ export function parseRichMarkup(markup: string): Line[] {
6
+ const lines: Line[] = []
7
+
8
+ for (const raw of markup.split('\n')) {
9
+ const trimmed = raw.trimEnd()
10
+
11
+ if (!trimmed) {
12
+ lines.push(['', ' '])
13
+
14
+ continue
15
+ }
16
+
17
+ const matches = [...trimmed.matchAll(RICH_RE)]
18
+
19
+ if (!matches.length) {
20
+ lines.push(['', trimmed])
21
+
22
+ continue
23
+ }
24
+
25
+ let cursor = 0
26
+
27
+ for (const m of matches) {
28
+ const before = trimmed.slice(cursor, m.index)
29
+
30
+ if (before) {
31
+ lines.push(['', before])
32
+ }
33
+
34
+ lines.push([m[1]!, m[2]!])
35
+ cursor = m.index! + m[0].length
36
+ }
37
+
38
+ if (cursor < trimmed.length) {
39
+ lines.push(['', trimmed.slice(cursor)])
40
+ }
41
+ }
42
+
43
+ return lines
44
+ }
45
+
46
+ const LOGO_ART = [
47
+ '██╗ ██╗███████╗██████╗ ███╗ ███╗███████╗███████╗ █████╗ ██████╗ ███████╗███╗ ██╗████████╗',
48
+ '██║ ██║██╔════╝██╔══██╗████╗ ████║██╔════╝██╔════╝ ██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝',
49
+ '███████║█████╗ ██████╔╝██╔████╔██║█████╗ ███████╗█████╗███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ',
50
+ '██╔══██║██╔══╝ ██╔══██╗██║╚██╔╝██║██╔══╝ ╚════██║╚════╝██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ',
51
+ '██║ ██║███████╗██║ ██║██║ ╚═╝ ██║███████╗███████║ ██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ',
52
+ '╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ '
53
+ ]
54
+
55
+ const CADUCEUS_ART = [
56
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⣀⣀⠀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
57
+ '⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⣿⣿⣇⠸⣿⣿⠇⣸⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀',
58
+ '⠀⢀⣠⣴⣶⠿⠋⣩⡿⣿⡿⠻⣿⡇⢠⡄⢸⣿⠟⢿⣿⢿⣍⠙⠿⣶⣦⣄⡀⠀',
59
+ '⠀⠀⠉⠉⠁⠶⠟⠋⠀⠉⠀⢀⣈⣁⡈⢁⣈⣁⡀⠀⠉⠀⠙⠻⠶⠈⠉⠉⠀⠀',
60
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⡿⠛⢁⡈⠛⢿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
61
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠿⣿⣦⣤⣈⠁⢠⣴⣿⠿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
62
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠻⢿⣿⣦⡉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
63
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢷⣦⣈⠛⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
64
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣴⠦⠈⠙⠿⣦⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
65
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣤⡈⠁⢤⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
66
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠷⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
67
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠑⢶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
68
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠁⢰⡆⠈⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
69
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⠈⣡⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
70
+ '⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀'
71
+ ]
72
+
73
+ const LOGO_GRADIENT = [0, 0, 1, 1, 2, 2] as const
74
+ const CADUC_GRADIENT = [2, 2, 1, 1, 0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 3] as const
75
+
76
+ const colorize = (art: string[], gradient: readonly number[], c: ThemeColors): Line[] => {
77
+ const p = [c.primary, c.accent, c.border, c.muted]
78
+
79
+ return art.map((text, i) => [p[gradient[i]!] ?? c.muted, text])
80
+ }
81
+
82
+ export const LOGO_WIDTH = Math.max(...LOGO_ART.map(line => line.length))
83
+ export const CADUCEUS_WIDTH = Math.max(...CADUCEUS_ART.map(line => line.length))
84
+
85
+ export const logo = (c: ThemeColors, customLogo?: string): Line[] =>
86
+ customLogo ? parseRichMarkup(customLogo) : colorize(LOGO_ART, LOGO_GRADIENT, c)
87
+
88
+ export const caduceus = (c: ThemeColors, customHero?: string): Line[] =>
89
+ customHero ? parseRichMarkup(customHero) : colorize(CADUCEUS_ART, CADUC_GRADIENT, c)
90
+
91
+ export const artWidth = (lines: Line[]) => lines.reduce((m, [, t]) => Math.max(m, t.length), 0)
92
+
93
+ type Line = [string, string]