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,527 @@
1
+ import { Box, Text, useInput, useStdout } from '@nastechai/ink'
2
+ import { useEffect, useMemo, useState } from 'react'
3
+
4
+ import { providerDisplayNames } from '../domain/providers.js'
5
+ import { TUI_SESSION_MODEL_FLAG } from '../domain/slash.js'
6
+ import type { GatewayClient } from '../gatewayClient.js'
7
+ import type { ModelOptionProvider, ModelOptionsResponse } from '../gatewayTypes.js'
8
+ import { asRpcResult, rpcErrorMessage } from '../lib/rpc.js'
9
+ import type { Theme } from '../theme.js'
10
+
11
+ import { OverlayHint, useOverlayKeys, windowItems } from './overlayControls.js'
12
+
13
+ const VISIBLE = 12
14
+ const MIN_WIDTH = 40
15
+ const MAX_WIDTH = 90
16
+
17
+ type Stage = 'provider' | 'key' | 'model' | 'disconnect'
18
+
19
+ export function ModelPicker({ allowPersistGlobal = true, gw, onCancel, onSelect, sessionId, t }: ModelPickerProps) {
20
+ const [providers, setProviders] = useState<ModelOptionProvider[]>([])
21
+ const [currentModel, setCurrentModel] = useState('')
22
+ const [err, setErr] = useState('')
23
+ const [loading, setLoading] = useState(true)
24
+ const [persistGlobal, setPersistGlobal] = useState(false)
25
+ const [providerIdx, setProviderIdx] = useState(0)
26
+ const [modelIdx, setModelIdx] = useState(0)
27
+ const [stage, setStage] = useState<Stage>('provider')
28
+ const [keyInput, setKeyInput] = useState('')
29
+ const [keySaving, setKeySaving] = useState(false)
30
+ const [keyError, setKeyError] = useState('')
31
+
32
+ const { stdout } = useStdout()
33
+ // Pin the picker to a stable width so the FloatBox parent (which shrinks-
34
+ // to-fit with alignSelf="flex-start") doesn't resize as long provider /
35
+ // model names scroll into view, and so `wrap="truncate-end"` on each row
36
+ // has an actual constraint to truncate against.
37
+ const width = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, (stdout?.columns ?? 80) - 6))
38
+
39
+ useEffect(() => {
40
+ gw.request<ModelOptionsResponse>('model.options', sessionId ? { session_id: sessionId } : {})
41
+ .then(raw => {
42
+ const r = asRpcResult<ModelOptionsResponse>(raw)
43
+
44
+ if (!r) {
45
+ setErr('invalid response: model.options')
46
+ setLoading(false)
47
+
48
+ return
49
+ }
50
+
51
+ const next = r.providers ?? []
52
+ setProviders(next)
53
+ setCurrentModel(String(r.model ?? ''))
54
+ setProviderIdx(
55
+ Math.max(
56
+ 0,
57
+ next.findIndex(p => p.is_current)
58
+ )
59
+ )
60
+ setModelIdx(0)
61
+ setStage('provider')
62
+ setErr('')
63
+ setLoading(false)
64
+ })
65
+ .catch((e: unknown) => {
66
+ setErr(rpcErrorMessage(e))
67
+ setLoading(false)
68
+ })
69
+ }, [gw, sessionId])
70
+
71
+ const provider = providers[providerIdx]
72
+ const models = provider?.models ?? []
73
+ const names = useMemo(() => providerDisplayNames(providers), [providers])
74
+
75
+ const back = () => {
76
+ if (stage === 'model' || stage === 'key' || stage === 'disconnect') {
77
+ setStage('provider')
78
+ setModelIdx(0)
79
+ setKeyInput('')
80
+ setKeyError('')
81
+ setKeySaving(false)
82
+
83
+ return
84
+ }
85
+
86
+ onCancel()
87
+ }
88
+
89
+ useOverlayKeys({ onBack: back, onClose: onCancel })
90
+
91
+ useInput((ch, key) => {
92
+ // Key entry stage handles its own input
93
+ if (stage === 'key') {
94
+ if (keySaving) {
95
+ return
96
+ }
97
+
98
+ if (key.return) {
99
+ if (!keyInput.trim()) {
100
+ return
101
+ }
102
+
103
+ setKeySaving(true)
104
+ setKeyError('')
105
+ gw.request<{ provider?: ModelOptionProvider }>('model.save_key', {
106
+ slug: provider?.slug,
107
+ api_key: keyInput.trim(),
108
+ ...(sessionId ? { session_id: sessionId } : {})
109
+ })
110
+ .then(raw => {
111
+ const r = asRpcResult<{ provider?: ModelOptionProvider }>(raw)
112
+
113
+ if (!r?.provider) {
114
+ setKeyError('failed to save key')
115
+ setKeySaving(false)
116
+
117
+ return
118
+ }
119
+
120
+ // Update the provider in our list with fresh data
121
+ setProviders(prev => prev.map(p => (p.slug === r.provider!.slug ? r.provider! : p)))
122
+ setKeyInput('')
123
+ setKeySaving(false)
124
+ setStage('model')
125
+ setModelIdx(0)
126
+ })
127
+ .catch((e: unknown) => {
128
+ setKeyError(rpcErrorMessage(e))
129
+ setKeySaving(false)
130
+ })
131
+
132
+ return
133
+ }
134
+
135
+ if (key.backspace || key.delete) {
136
+ setKeyInput(v => v.slice(0, -1))
137
+
138
+ return
139
+ }
140
+
141
+ // ctrl+u clears input
142
+ if (ch === '\u0015') {
143
+ setKeyInput('')
144
+
145
+ return
146
+ }
147
+
148
+ if (ch && !key.ctrl && !key.meta) {
149
+ setKeyInput(v => v + ch)
150
+ }
151
+
152
+ return
153
+ }
154
+
155
+ // Disconnect confirmation stage
156
+ if (stage === 'disconnect') {
157
+ if (ch.toLowerCase() === 'y' || key.return) {
158
+ if (!provider) {
159
+ setStage('provider')
160
+
161
+ return
162
+ }
163
+
164
+ setKeySaving(true)
165
+ gw.request<{ disconnected?: boolean }>('model.disconnect', {
166
+ slug: provider.slug,
167
+ ...(sessionId ? { session_id: sessionId } : {})
168
+ })
169
+ .then(raw => {
170
+ const r = asRpcResult<{ disconnected?: boolean }>(raw)
171
+
172
+ if (r?.disconnected) {
173
+ // Mark provider as unauthenticated in local state
174
+ setProviders(prev =>
175
+ prev.map(p =>
176
+ p.slug === provider.slug
177
+ ? {
178
+ ...p,
179
+ authenticated: false,
180
+ models: [],
181
+ total_models: 0,
182
+ warning: p.key_env ? `paste ${p.key_env} to activate` : 'run `nastech model` to configure'
183
+ }
184
+ : p
185
+ )
186
+ )
187
+ }
188
+
189
+ setKeySaving(false)
190
+ setStage('provider')
191
+ })
192
+ .catch(() => {
193
+ setKeySaving(false)
194
+ setStage('provider')
195
+ })
196
+
197
+ return
198
+ }
199
+
200
+ if (ch.toLowerCase() === 'n' || key.escape) {
201
+ setStage('provider')
202
+
203
+ return
204
+ }
205
+
206
+ return
207
+ }
208
+
209
+ const count = stage === 'provider' ? providers.length : models.length
210
+ const sel = stage === 'provider' ? providerIdx : modelIdx
211
+ const setSel = stage === 'provider' ? setProviderIdx : setModelIdx
212
+
213
+ if (key.upArrow && sel > 0) {
214
+ setSel(v => v - 1)
215
+
216
+ return
217
+ }
218
+
219
+ if (key.downArrow && sel < count - 1) {
220
+ setSel(v => v + 1)
221
+
222
+ return
223
+ }
224
+
225
+ if (key.return) {
226
+ if (stage === 'provider') {
227
+ if (!provider) {
228
+ return
229
+ }
230
+
231
+ if (provider.authenticated === false) {
232
+ // api_key providers: prompt for key inline
233
+ if (provider.auth_type === 'api_key' && provider.key_env) {
234
+ setStage('key')
235
+ setKeyInput('')
236
+ setKeyError('')
237
+ }
238
+
239
+ // Other auth types: no-op (warning shown tells them to run nastech model)
240
+ return
241
+ }
242
+
243
+ setStage('model')
244
+ setModelIdx(0)
245
+
246
+ return
247
+ }
248
+
249
+ const model = models[modelIdx]
250
+
251
+ if (provider && model) {
252
+ onSelect(
253
+ `${model} --provider ${provider.slug}${allowPersistGlobal && persistGlobal ? ' --global' : ` ${TUI_SESSION_MODEL_FLAG}`}`
254
+ )
255
+ } else {
256
+ setStage('provider')
257
+ }
258
+
259
+ return
260
+ }
261
+
262
+ if (allowPersistGlobal && ch.toLowerCase() === 'g') {
263
+ setPersistGlobal(v => !v)
264
+
265
+ return
266
+ }
267
+
268
+ // Disconnect: only in provider stage, only for authenticated providers
269
+ if (ch.toLowerCase() === 'd' && stage === 'provider' && provider?.authenticated !== false) {
270
+ setStage('disconnect')
271
+
272
+ return
273
+ }
274
+ })
275
+
276
+ if (loading) {
277
+ return <Text color={t.color.muted}>loading models…</Text>
278
+ }
279
+
280
+ if (err) {
281
+ return (
282
+ <Box flexDirection="column">
283
+ <Text color={t.color.label}>error: {err}</Text>
284
+ <OverlayHint t={t}>Esc/q cancel</OverlayHint>
285
+ </Box>
286
+ )
287
+ }
288
+
289
+ if (!providers.length) {
290
+ return (
291
+ <Box flexDirection="column">
292
+ <Text color={t.color.muted}>no providers available</Text>
293
+ <OverlayHint t={t}>Esc/q cancel</OverlayHint>
294
+ </Box>
295
+ )
296
+ }
297
+
298
+ // ── Key entry stage ──────────────────────────────────────────────────
299
+ if (stage === 'key' && provider) {
300
+ const masked = keyInput ? '•'.repeat(Math.min(keyInput.length, 40)) : ''
301
+
302
+ return (
303
+ <Box flexDirection="column" width={width}>
304
+ <Text bold color={t.color.accent} wrap="truncate-end">
305
+ Configure {provider.name}
306
+ </Text>
307
+
308
+ <Text color={t.color.muted} wrap="truncate-end">
309
+ Paste your API key below (saved to ~/.nastech/.env)
310
+ </Text>
311
+
312
+ <Text color={t.color.muted} wrap="truncate-end">
313
+ {' '}
314
+ </Text>
315
+
316
+ <Text color={t.color.muted} wrap="truncate-end">
317
+ {provider.key_env}:
318
+ </Text>
319
+
320
+ <Text color={t.color.accent} wrap="truncate-end">
321
+ {' '}
322
+ {masked || '(empty)'}
323
+ {keySaving ? '' : '▎'}
324
+ </Text>
325
+
326
+ <Text color={t.color.muted} wrap="truncate-end">
327
+ {' '}
328
+ </Text>
329
+
330
+ {keyError ? (
331
+ <Text color={t.color.label} wrap="truncate-end">
332
+ error: {keyError}
333
+ </Text>
334
+ ) : keySaving ? (
335
+ <Text color={t.color.muted} wrap="truncate-end">
336
+ saving…
337
+ </Text>
338
+ ) : (
339
+ <Text color={t.color.muted} wrap="truncate-end">
340
+ {' '}
341
+ </Text>
342
+ )}
343
+
344
+ <OverlayHint t={t}>Enter save · Ctrl+U clear · Esc back</OverlayHint>
345
+ </Box>
346
+ )
347
+ }
348
+
349
+ // ── Disconnect confirmation stage ─────────────────────────────────────
350
+ if (stage === 'disconnect' && provider) {
351
+ return (
352
+ <Box flexDirection="column" width={width}>
353
+ <Text bold color={t.color.accent} wrap="truncate-end">
354
+ Disconnect {provider.name}?
355
+ </Text>
356
+
357
+ <Text color={t.color.muted} wrap="truncate-end">
358
+ {' '}
359
+ </Text>
360
+
361
+ <Text color={t.color.muted} wrap="truncate-end">
362
+ This removes saved credentials for {provider.name}.
363
+ </Text>
364
+
365
+ <Text color={t.color.muted} wrap="truncate-end">
366
+ You can re-authenticate later by selecting it again.
367
+ </Text>
368
+
369
+ <Text color={t.color.muted} wrap="truncate-end">
370
+ {' '}
371
+ </Text>
372
+
373
+ {keySaving ? (
374
+ <Text color={t.color.muted} wrap="truncate-end">
375
+ disconnecting…
376
+ </Text>
377
+ ) : (
378
+ <OverlayHint t={t}>y/Enter confirm · n/Esc cancel</OverlayHint>
379
+ )}
380
+ </Box>
381
+ )
382
+ }
383
+
384
+ // ── Provider selection stage ─────────────────────────────────────────
385
+ if (stage === 'provider') {
386
+ const rows = providers.map((p, i) => {
387
+ const authMark = p.authenticated === false ? '○' : p.is_current ? '*' : '●'
388
+ const modelCount = p.total_models ?? p.models?.length ?? 0
389
+ const suffix =
390
+ p.authenticated === false ? (p.auth_type === 'api_key' ? '(no key)' : '(needs setup)') : `${modelCount} models`
391
+
392
+ return `${authMark} ${names[i]} · ${suffix}`
393
+ })
394
+
395
+ const { items, offset } = windowItems(rows, providerIdx, VISIBLE)
396
+
397
+ return (
398
+ <Box flexDirection="column" width={width}>
399
+ <Text bold color={t.color.accent} wrap="truncate-end">
400
+ Select provider (step 1/2)
401
+ </Text>
402
+
403
+ <Text color={t.color.muted} wrap="truncate-end">
404
+ Full model IDs on the next step · Enter to continue
405
+ </Text>
406
+
407
+ <Text color={t.color.muted} wrap="truncate-end">
408
+ Current: {currentModel || '(unknown)'}
409
+ </Text>
410
+ <Text color={t.color.label} wrap="truncate-end">
411
+ {provider?.warning ? `warning: ${provider.warning}` : ' '}
412
+ </Text>
413
+ <Text color={t.color.muted} wrap="truncate-end">
414
+ {offset > 0 ? ` ↑ ${offset} more` : ' '}
415
+ </Text>
416
+
417
+ {Array.from({ length: VISIBLE }, (_, i) => {
418
+ const row = items[i]
419
+ const idx = offset + i
420
+ const p = providers[idx]
421
+ const dimmed = p?.authenticated === false
422
+
423
+ return row ? (
424
+ <Text
425
+ bold={providerIdx === idx}
426
+ color={providerIdx === idx ? t.color.accent : dimmed ? t.color.label : t.color.muted}
427
+ inverse={providerIdx === idx}
428
+ key={providers[idx]?.slug ?? `row-${idx}`}
429
+ wrap="truncate-end"
430
+ >
431
+ {providerIdx === idx ? '▸ ' : ' '}
432
+ {idx + 1}. {row}
433
+ </Text>
434
+ ) : (
435
+ <Text color={t.color.muted} key={`pad-${i}`} wrap="truncate-end">
436
+ {' '}
437
+ </Text>
438
+ )
439
+ })}
440
+
441
+ <Text color={t.color.muted} wrap="truncate-end">
442
+ {offset + VISIBLE < rows.length ? ` ↓ ${rows.length - offset - VISIBLE} more` : ' '}
443
+ </Text>
444
+
445
+ <Text color={t.color.muted} wrap="truncate-end">
446
+ persist: {allowPersistGlobal ? (persistGlobal ? 'global' : 'session') : 'session'}
447
+ {allowPersistGlobal ? ' · g toggle' : ' only'}
448
+ </Text>
449
+ <OverlayHint t={t}>↑/↓ select · Enter choose · d disconnect · Esc/q cancel</OverlayHint>
450
+ </Box>
451
+ )
452
+ }
453
+
454
+ // ── Model selection stage ────────────────────────────────────────────
455
+ const { items, offset } = windowItems(models, modelIdx, VISIBLE)
456
+
457
+ return (
458
+ <Box flexDirection="column" width={width}>
459
+ <Text bold color={t.color.accent} wrap="truncate-end">
460
+ Select model (step 2/2)
461
+ </Text>
462
+
463
+ <Text color={t.color.muted} wrap="truncate-end">
464
+ {names[providerIdx] || '(unknown provider)'} · Esc back
465
+ </Text>
466
+ <Text color={t.color.label} wrap="truncate-end">
467
+ {provider?.warning ? `warning: ${provider.warning}` : ' '}
468
+ </Text>
469
+ <Text color={t.color.muted} wrap="truncate-end">
470
+ {offset > 0 ? ` ↑ ${offset} more` : ' '}
471
+ </Text>
472
+
473
+ {Array.from({ length: VISIBLE }, (_, i) => {
474
+ const row = items[i]
475
+ const idx = offset + i
476
+
477
+ if (!row) {
478
+ return !models.length && i === 0 ? (
479
+ <Text color={t.color.muted} key="empty" wrap="truncate-end">
480
+ no models listed for this provider
481
+ </Text>
482
+ ) : (
483
+ <Text color={t.color.muted} key={`pad-${i}`} wrap="truncate-end">
484
+ {' '}
485
+ </Text>
486
+ )
487
+ }
488
+
489
+ const prefix = modelIdx === idx ? '▸ ' : row === currentModel ? '* ' : ' '
490
+
491
+ return (
492
+ <Text
493
+ bold={modelIdx === idx}
494
+ color={modelIdx === idx ? t.color.accent : t.color.muted}
495
+ inverse={modelIdx === idx}
496
+ key={`${provider?.slug ?? 'prov'}:${idx}:${row}`}
497
+ wrap="truncate-end"
498
+ >
499
+ {prefix}
500
+ {idx + 1}. {row}
501
+ </Text>
502
+ )
503
+ })}
504
+
505
+ <Text color={t.color.muted} wrap="truncate-end">
506
+ {offset + VISIBLE < models.length ? ` ↓ ${models.length - offset - VISIBLE} more` : ' '}
507
+ </Text>
508
+
509
+ <Text color={t.color.muted} wrap="truncate-end">
510
+ persist: {allowPersistGlobal ? (persistGlobal ? 'global' : 'session') : 'session'}
511
+ {allowPersistGlobal ? ' · g toggle' : ' only'}
512
+ </Text>
513
+ <OverlayHint t={t}>
514
+ {models.length ? '↑/↓ select · Enter switch · Esc back · q close' : 'Enter/Esc back · q close'}
515
+ </OverlayHint>
516
+ </Box>
517
+ )
518
+ }
519
+
520
+ interface ModelPickerProps {
521
+ allowPersistGlobal?: boolean
522
+ gw: GatewayClient
523
+ onCancel: () => void
524
+ onSelect: (value: string) => void
525
+ sessionId: string | null
526
+ t: Theme
527
+ }
@@ -0,0 +1,50 @@
1
+ import { Text, useInput } from '@nastechai/ink'
2
+
3
+ import type { Theme } from '../theme.js'
4
+
5
+ export function useOverlayKeys({ disabled = false, onBack, onClose }: OverlayKeysOptions) {
6
+ useInput((ch, key) => {
7
+ if (disabled) {
8
+ return
9
+ }
10
+
11
+ if (ch === 'q') {
12
+ return onClose()
13
+ }
14
+
15
+ if (key.escape) {
16
+ return onBack ? onBack() : onClose()
17
+ }
18
+ })
19
+ }
20
+
21
+ export function OverlayHint({ children, t }: OverlayHintProps) {
22
+ return (
23
+ <Text color={t.color.muted} wrap="truncate-end">
24
+ {children}
25
+ </Text>
26
+ )
27
+ }
28
+
29
+ export const windowOffset = (count: number, selected: number, visible: number) =>
30
+ Math.max(0, Math.min(selected - Math.floor(visible / 2), count - visible))
31
+
32
+ export function windowItems<T>(items: T[], selected: number, visible: number) {
33
+ const offset = windowOffset(items.length, selected, visible)
34
+
35
+ return {
36
+ items: items.slice(offset, offset + visible),
37
+ offset
38
+ }
39
+ }
40
+
41
+ interface OverlayHintProps {
42
+ children: string
43
+ t: Theme
44
+ }
45
+
46
+ interface OverlayKeysOptions {
47
+ disabled?: boolean
48
+ onBack?: () => void
49
+ onClose: () => void
50
+ }