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
package/src/theme.ts ADDED
@@ -0,0 +1,589 @@
1
+ export interface ThemeColors {
2
+ primary: string
3
+ accent: string
4
+ border: string
5
+ text: string
6
+ muted: string
7
+ completionBg: string
8
+ completionCurrentBg: string
9
+ completionMetaBg: string
10
+ completionMetaCurrentBg: string
11
+
12
+ label: string
13
+ ok: string
14
+ error: string
15
+ warn: string
16
+
17
+ prompt: string
18
+ sessionLabel: string
19
+ sessionBorder: string
20
+
21
+ statusBg: string
22
+ statusFg: string
23
+ statusGood: string
24
+ statusWarn: string
25
+ statusBad: string
26
+ statusCritical: string
27
+ selectionBg: string
28
+
29
+ diffAdded: string
30
+ diffRemoved: string
31
+ diffAddedWord: string
32
+ diffRemovedWord: string
33
+
34
+ shellDollar: string
35
+ }
36
+
37
+ export interface ThemeBrand {
38
+ name: string
39
+ icon: string
40
+ prompt: string
41
+ welcome: string
42
+ goodbye: string
43
+ tool: string
44
+ helpHeader: string
45
+ }
46
+
47
+ export interface Theme {
48
+ color: ThemeColors
49
+ brand: ThemeBrand
50
+ bannerLogo: string
51
+ bannerHero: string
52
+ }
53
+
54
+ // ── Color math ───────────────────────────────────────────────────────
55
+
56
+ function parseHex(h: string): [number, number, number] | null {
57
+ const m = /^#?([0-9a-f]{6})$/i.exec(h)
58
+
59
+ if (!m) {
60
+ return null
61
+ }
62
+
63
+ const n = parseInt(m[1]!, 16)
64
+
65
+ return [(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]
66
+ }
67
+
68
+ function mix(a: string, b: string, t: number) {
69
+ const pa = parseHex(a)
70
+ const pb = parseHex(b)
71
+
72
+ if (!pa || !pb) {
73
+ return a
74
+ }
75
+
76
+ const lerp = (i: 0 | 1 | 2) => Math.round(pa[i] + (pb[i] - pa[i]) * t)
77
+
78
+ return '#' + ((1 << 24) | (lerp(0) << 16) | (lerp(1) << 8) | lerp(2)).toString(16).slice(1)
79
+ }
80
+
81
+ const XTERM_6_LEVELS = [0, 95, 135, 175, 215, 255] as const
82
+ const ANSI_LIGHT_MAX_LUMINANCE = 0.72
83
+ const ANSI_LIGHT_TARGET_LUMINANCE = 0.34
84
+ const ANSI_LIGHT_MIN_SATURATION = 0.22
85
+ const ANSI_MUTED_BUCKET = 245
86
+
87
+ const ANSI_NORMALIZED_FOREGROUNDS: readonly (keyof ThemeColors)[] = [
88
+ 'text',
89
+ 'label',
90
+ 'ok',
91
+ 'error',
92
+ 'warn',
93
+ 'prompt',
94
+ 'statusFg',
95
+ 'statusGood',
96
+ 'statusWarn',
97
+ 'statusBad',
98
+ 'statusCritical',
99
+ 'shellDollar'
100
+ ]
101
+
102
+ const ANSI_MUTED_FOREGROUNDS: readonly (keyof ThemeColors)[] = ['muted', 'sessionLabel', 'sessionBorder']
103
+
104
+ function xtermEightBitRgb(colorNumber: number): [number, number, number] {
105
+ if (colorNumber >= 232) {
106
+ const value = 8 + (colorNumber - 232) * 10
107
+
108
+ return [value, value, value]
109
+ }
110
+
111
+ if (colorNumber >= 16) {
112
+ const offset = colorNumber - 16
113
+
114
+ return [
115
+ XTERM_6_LEVELS[Math.floor(offset / 36) % 6]!,
116
+ XTERM_6_LEVELS[Math.floor(offset / 6) % 6]!,
117
+ XTERM_6_LEVELS[offset % 6]!
118
+ ]
119
+ }
120
+
121
+ return [0, 0, 0]
122
+ }
123
+
124
+ function channelLuminance(value: number): number {
125
+ const normalized = value / 255
126
+
127
+ return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4
128
+ }
129
+
130
+ function relativeLuminance(red: number, green: number, blue: number): number {
131
+ return 0.2126 * channelLuminance(red) + 0.7152 * channelLuminance(green) + 0.0722 * channelLuminance(blue)
132
+ }
133
+
134
+ function rgbToHsl(red: number, green: number, blue: number): [number, number, number] {
135
+ const rn = red / 255
136
+ const gn = green / 255
137
+ const bn = blue / 255
138
+ const max = Math.max(rn, gn, bn)
139
+ const min = Math.min(rn, gn, bn)
140
+ const lightness = (max + min) / 2
141
+
142
+ if (max === min) {
143
+ return [0, 0, lightness]
144
+ }
145
+
146
+ const delta = max - min
147
+ const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min)
148
+
149
+ const hue =
150
+ max === rn
151
+ ? (gn - bn) / delta + (gn < bn ? 6 : 0)
152
+ : max === gn
153
+ ? (bn - rn) / delta + 2
154
+ : (rn - gn) / delta + 4
155
+
156
+ return [hue / 6, saturation, lightness]
157
+ }
158
+
159
+ function circularDistance(a: number, b: number): number {
160
+ const distance = Math.abs(a - b)
161
+
162
+ return Math.min(distance, 1 - distance)
163
+ }
164
+
165
+ // Mirrors @nastechai/ink's colorize.ts. Keep local: app code compiles from
166
+ // ui-tui/src, while @nastechai/ink is bundled separately from packages/.
167
+ function richEightBitColorNumber(red: number, green: number, blue: number): number {
168
+ const [, saturation, lightness] = rgbToHsl(red, green, blue)
169
+
170
+ if (saturation < 0.15) {
171
+ const gray = Math.round(lightness * 25)
172
+
173
+ return gray === 0 ? 16 : gray === 25 ? 231 : 231 + gray
174
+ }
175
+
176
+ const sixRed = red < 95 ? red / 95 : 1 + (red - 95) / 40
177
+ const sixGreen = green < 95 ? green / 95 : 1 + (green - 95) / 40
178
+ const sixBlue = blue < 95 ? blue / 95 : 1 + (blue - 95) / 40
179
+
180
+ return 16 + 36 * Math.round(sixRed) + 6 * Math.round(sixGreen) + Math.round(sixBlue)
181
+ }
182
+
183
+ function bestReadableAnsiColor(red: number, green: number, blue: number): number {
184
+ const [hue, saturation, lightness] = rgbToHsl(red, green, blue)
185
+ let bestColor = richEightBitColorNumber(red, green, blue)
186
+ let bestScore = Number.POSITIVE_INFINITY
187
+
188
+ for (let colorNumber = 16; colorNumber <= 255; colorNumber += 1) {
189
+ const [candidateRed, candidateGreen, candidateBlue] = xtermEightBitRgb(colorNumber)
190
+ const candidateLuminance = relativeLuminance(candidateRed, candidateGreen, candidateBlue)
191
+
192
+ if (candidateLuminance > ANSI_LIGHT_MAX_LUMINANCE) {
193
+ continue
194
+ }
195
+
196
+ const [candidateHue, candidateSaturation, candidateLightness] = rgbToHsl(
197
+ candidateRed,
198
+ candidateGreen,
199
+ candidateBlue
200
+ )
201
+
202
+ const saturationFloorPenalty =
203
+ candidateSaturation < ANSI_LIGHT_MIN_SATURATION ? (ANSI_LIGHT_MIN_SATURATION - candidateSaturation) * 3 : 0
204
+
205
+ const score =
206
+ circularDistance(candidateHue, hue) * 4 +
207
+ Math.abs(candidateSaturation - Math.max(ANSI_LIGHT_MIN_SATURATION, saturation)) * 0.8 +
208
+ Math.abs(candidateLightness - Math.min(lightness, ANSI_LIGHT_TARGET_LUMINANCE)) * 2 +
209
+ saturationFloorPenalty
210
+
211
+ if (score < bestScore) {
212
+ bestColor = colorNumber
213
+ bestScore = score
214
+ }
215
+ }
216
+
217
+ return bestColor
218
+ }
219
+
220
+ function normalizeAnsiForeground(color: string): string {
221
+ const rgb = parseHex(color)
222
+
223
+ if (!rgb) {
224
+ return color
225
+ }
226
+
227
+ const richAnsi = richEightBitColorNumber(rgb[0], rgb[1], rgb[2])
228
+ const richRgb = xtermEightBitRgb(richAnsi)
229
+
230
+ const ansi = relativeLuminance(richRgb[0], richRgb[1], richRgb[2]) > ANSI_LIGHT_MAX_LUMINANCE
231
+ ? bestReadableAnsiColor(rgb[0], rgb[1], rgb[2])
232
+ : richAnsi
233
+
234
+ return `ansi256(${ansi})`
235
+ }
236
+
237
+ // ── Defaults ─────────────────────────────────────────────────────────
238
+
239
+ const BRAND: ThemeBrand = {
240
+ name: 'NasTech Agent',
241
+ icon: '⚕',
242
+ prompt: '❯',
243
+ welcome: 'Type your message or /help for commands.',
244
+ goodbye: 'Goodbye! ⚕',
245
+ tool: '┊',
246
+ helpHeader: '(^_^)? Commands'
247
+ }
248
+
249
+ const cleanPromptSymbol = (s: string | undefined, fallback: string) => {
250
+ const cleaned = String(s ?? '')
251
+ .replace(/\s+/g, ' ')
252
+ .trim()
253
+
254
+ return cleaned || fallback
255
+ }
256
+
257
+ export const DARK_THEME: Theme = {
258
+ color: {
259
+ primary: '#FFD700',
260
+ accent: '#FFBF00',
261
+ border: '#CD7F32',
262
+ text: '#FFF8DC',
263
+ muted: '#CC9B1F',
264
+ // Bumped from the old `#B8860B` darkgoldenrod (~53% luminance) which
265
+ // read as barely-visible on dark terminals for long body text. The
266
+ // new value sits ~60% luminance — readable without losing the "muted /
267
+ // secondary" semantic. Field labels still use `label` (65%) which
268
+ // stays brighter so hierarchy holds.
269
+ completionBg: '#1a1a2e',
270
+ completionCurrentBg: '#333355',
271
+ completionMetaBg: '#1a1a2e',
272
+ completionMetaCurrentBg: '#333355',
273
+
274
+ label: '#DAA520',
275
+ ok: '#4caf50',
276
+ error: '#ef5350',
277
+ warn: '#ffa726',
278
+
279
+ prompt: '#FFF8DC',
280
+ // sessionLabel/sessionBorder intentionally track the `dim` value — they
281
+ // are "same role, same colour" by design. fromSkin's banner_dim fallback
282
+ // relies on this pairing (#11300).
283
+ sessionLabel: '#CC9B1F',
284
+ sessionBorder: '#CC9B1F',
285
+
286
+ statusBg: '#1a1a2e',
287
+ statusFg: '#C0C0C0',
288
+ statusGood: '#8FBC8F',
289
+ statusWarn: '#FFD700',
290
+ statusBad: '#FF8C00',
291
+ statusCritical: '#FF6B6B',
292
+ selectionBg: '#3a3a55',
293
+
294
+ diffAdded: 'rgb(220,255,220)',
295
+ diffRemoved: 'rgb(255,220,220)',
296
+ diffAddedWord: 'rgb(36,138,61)',
297
+ diffRemovedWord: 'rgb(207,34,46)',
298
+ shellDollar: '#4dabf7'
299
+ },
300
+
301
+ brand: BRAND,
302
+
303
+ bannerLogo: '',
304
+ bannerHero: ''
305
+ }
306
+
307
+ // Light-terminal palette: darker golds/ambers that stay legible on white
308
+ // backgrounds. Same shape as DARK_THEME so `fromSkin` still layers on top
309
+ // cleanly (#11300).
310
+ export const LIGHT_THEME: Theme = {
311
+ color: {
312
+ primary: '#8B6914',
313
+ accent: '#A0651C',
314
+ border: '#7A4F1F',
315
+ text: '#3D2F13',
316
+ muted: '#7A5A0F',
317
+ completionBg: '#F5F5F5',
318
+ completionCurrentBg: mix('#F5F5F5', '#A0651C', 0.25),
319
+ completionMetaBg: '#F5F5F5',
320
+ completionMetaCurrentBg: mix('#F5F5F5', '#A0651C', 0.25),
321
+
322
+ label: '#7A5A0F',
323
+ ok: '#2E7D32',
324
+ error: '#C62828',
325
+ warn: '#E65100',
326
+
327
+ prompt: '#2B2014',
328
+ sessionLabel: '#7A5A0F',
329
+ sessionBorder: '#7A5A0F',
330
+
331
+ statusBg: '#F5F5F5',
332
+ statusFg: '#333333',
333
+ statusGood: '#2E7D32',
334
+ statusWarn: '#8B6914',
335
+ statusBad: '#D84315',
336
+ statusCritical: '#B71C1C',
337
+ selectionBg: '#D4E4F7',
338
+
339
+ diffAdded: 'rgb(200,240,200)',
340
+ diffRemoved: 'rgb(240,200,200)',
341
+ diffAddedWord: 'rgb(27,94,32)',
342
+ diffRemovedWord: 'rgb(183,28,28)',
343
+ shellDollar: '#1565C0'
344
+ },
345
+
346
+ brand: BRAND,
347
+
348
+ bannerLogo: '',
349
+ bannerHero: ''
350
+ }
351
+
352
+ const TRUE_RE = /^(?:1|true|yes|on)$/
353
+ const FALSE_RE = /^(?:0|false|no|off)$/
354
+
355
+ // TERM_PROGRAM fallback allow-list for terminals whose default profile is
356
+ // light and which may not expose COLORFGBG. This currently includes Apple
357
+ // Terminal. Explicit NASTECH_TUI_THEME / COLORFGBG signals above still win,
358
+ // so dark Apple Terminal profiles that advertise a dark background stay dark.
359
+ const LIGHT_DEFAULT_TERM_PROGRAMS = new Set<string>(['Apple_Terminal'])
360
+
361
+ // Best-effort RGB → luminance check. Currently only accepts a 3- or
362
+ // 6-digit hex value (with or without a leading `#`); the env var name
363
+ // `NASTECH_TUI_BACKGROUND` is intentionally generic so a future OSC11
364
+ // query helper can cache its answer there too, but additional formats
365
+ // (rgb()/hsl()/named colours) would need explicit parsing here first.
366
+ const LUMA_LIGHT_THRESHOLD = 0.6
367
+
368
+ // Strict allow-list: parseInt(..., 16) silently truncates at the first
369
+ // non-hex character (e.g. `fffgff` would parse as `fff` and yield a
370
+ // false-positive "white" reading), so reject anything that doesn't match
371
+ // the canonical 3- or 6-digit shape up front.
372
+ const HEX_3_RE = /^[0-9a-f]{3}$/
373
+ const HEX_6_RE = /^[0-9a-f]{6}$/
374
+
375
+ function backgroundLuminance(raw: string): null | number {
376
+ const v = raw.trim().toLowerCase()
377
+
378
+ if (!v) {
379
+ return null
380
+ }
381
+
382
+ const hex = v.startsWith('#') ? v.slice(1) : v
383
+
384
+ const rgb = HEX_6_RE.test(hex)
385
+ ? [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)]
386
+ : HEX_3_RE.test(hex)
387
+ ? [parseInt(hex[0]! + hex[0]!, 16), parseInt(hex[1]! + hex[1]!, 16), parseInt(hex[2]! + hex[2]!, 16)]
388
+ : null
389
+
390
+ if (!rgb) {
391
+ return null
392
+ }
393
+
394
+ // Rec. 709 luma — close enough for "is this background bright".
395
+ return (0.2126 * rgb[0]! + 0.7152 * rgb[1]! + 0.0722 * rgb[2]!) / 255
396
+ }
397
+
398
+ // Pick light vs dark with ordered, explainable signals (#11300):
399
+ //
400
+ // 1. `NASTECH_TUI_LIGHT` boolean — `1`/`true`/`yes`/`on` → light;
401
+ // `0`/`false`/`no`/`off` → dark. Either explicit value wins
402
+ // regardless of any later signal.
403
+ // 2. `NASTECH_TUI_THEME` named override — `light` / `dark` win over
404
+ // every signal below.
405
+ // 3. `NASTECH_TUI_BACKGROUND` hex hint (3- or 6-digit) — luminance
406
+ // ≥ LUMA_LIGHT_THRESHOLD → light.
407
+ // 4. `COLORFGBG` last field — XFCE / rxvt / Terminal.app emit
408
+ // slot 7 or 15 on light profiles; 0–15 ranges are otherwise
409
+ // treated as authoritatively dark so the TERM_PROGRAM
410
+ // allow-list below cannot override an explicit dark profile.
411
+ // 5. `TERM_PROGRAM` light-default allow-list.
412
+ //
413
+ // Anything we can't decide stays dark — the default NasTech palette
414
+ // is the dark one.
415
+ export function detectLightMode(
416
+ env: NodeJS.ProcessEnv = process.env,
417
+ // Injectable so tests can prove the COLORFGBG-over-TERM_PROGRAM
418
+ // precedence rule even though the production allow-list is empty.
419
+ lightDefaultTermPrograms: ReadonlySet<string> = LIGHT_DEFAULT_TERM_PROGRAMS
420
+ ): boolean {
421
+ const lightFlag = (env.NASTECH_TUI_LIGHT ?? '').trim().toLowerCase()
422
+
423
+ if (TRUE_RE.test(lightFlag)) {
424
+ return true
425
+ }
426
+
427
+ if (FALSE_RE.test(lightFlag)) {
428
+ return false
429
+ }
430
+
431
+ const themeFlag = (env.NASTECH_TUI_THEME ?? '').trim().toLowerCase()
432
+
433
+ if (themeFlag === 'light') {
434
+ return true
435
+ }
436
+
437
+ if (themeFlag === 'dark') {
438
+ return false
439
+ }
440
+
441
+ const bgHint = backgroundLuminance(env.NASTECH_TUI_BACKGROUND ?? '')
442
+
443
+ if (bgHint !== null) {
444
+ return bgHint >= LUMA_LIGHT_THRESHOLD
445
+ }
446
+
447
+ const colorfgbg = (env.COLORFGBG ?? '').trim()
448
+
449
+ if (colorfgbg) {
450
+ // Validate as a decimal integer before coercing — `Number('')` is 0,
451
+ // so a malformed `COLORFGBG='15;'` would otherwise look like an
452
+ // authoritative dark slot and incorrectly block the TERM_PROGRAM
453
+ // allow-list. Anything that isn't pure digits falls through.
454
+ const lastField = colorfgbg.split(';').at(-1) ?? ''
455
+
456
+ if (/^\d+$/.test(lastField)) {
457
+ const bg = Number(lastField)
458
+
459
+ if (bg === 7 || bg === 15) {
460
+ return true
461
+ }
462
+
463
+ // Slots 0–6 and 8–14 are the dark half of the 0–15 ANSI range.
464
+ // When COLORFGBG is set we trust it as authoritative — a non-light
465
+ // value here shouldn't get overridden by the TERM_PROGRAM allow-list.
466
+ if (bg >= 0 && bg < 16) {
467
+ return false
468
+ }
469
+ }
470
+ }
471
+
472
+ const termProgram = (env.TERM_PROGRAM ?? '').trim()
473
+
474
+ return lightDefaultTermPrograms.has(termProgram)
475
+ }
476
+
477
+ function shouldNormalizeAnsiLightTheme(env: NodeJS.ProcessEnv = process.env, isLight = detectLightMode(env)): boolean {
478
+ const colorTerm = (env.COLORTERM ?? '').trim().toLowerCase()
479
+ const termProgram = (env.TERM_PROGRAM ?? '').trim()
480
+
481
+ return termProgram === 'Apple_Terminal' && colorTerm !== 'truecolor' && colorTerm !== '24bit' && isLight
482
+ }
483
+
484
+ export function normalizeThemeForAnsiLightTerminal(
485
+ theme: Theme,
486
+ env: NodeJS.ProcessEnv = process.env,
487
+ isLight = detectLightMode(env)
488
+ ): Theme {
489
+ if (!shouldNormalizeAnsiLightTheme(env, isLight)) {
490
+ return theme
491
+ }
492
+
493
+ const color = { ...theme.color }
494
+
495
+ for (const key of ANSI_NORMALIZED_FOREGROUNDS) {
496
+ color[key] = normalizeAnsiForeground(color[key])
497
+ }
498
+
499
+ for (const key of ANSI_MUTED_FOREGROUNDS) {
500
+ color[key] = `ansi256(${ANSI_MUTED_BUCKET})`
501
+ }
502
+
503
+ return { ...theme, color }
504
+ }
505
+
506
+ const DEFAULT_LIGHT_MODE = detectLightMode()
507
+
508
+ export const DEFAULT_THEME: Theme = normalizeThemeForAnsiLightTerminal(
509
+ DEFAULT_LIGHT_MODE ? LIGHT_THEME : DARK_THEME,
510
+ process.env,
511
+ DEFAULT_LIGHT_MODE
512
+ )
513
+
514
+ // ── Skin → Theme ─────────────────────────────────────────────────────
515
+
516
+ export function fromSkin(
517
+ colors: Record<string, string>,
518
+ branding: Record<string, string>,
519
+ bannerLogo = '',
520
+ bannerHero = '',
521
+ toolPrefix = '',
522
+ helpHeader = ''
523
+ ): Theme {
524
+ const d = DEFAULT_THEME
525
+ const c = (k: string) => colors[k]
526
+ const hasSkinColors = Object.keys(colors).length > 0
527
+
528
+ const accent = c('ui_accent') ?? c('banner_accent') ?? d.color.accent
529
+ const bannerAccent = c('banner_accent') ?? c('banner_title') ?? d.color.accent
530
+ const muted = c('banner_dim') ?? d.color.muted
531
+ const completionBg = c('completion_menu_bg') ?? d.color.completionBg
532
+
533
+ const completionCurrentBg =
534
+ c('completion_menu_current_bg') ??
535
+ (hasSkinColors ? mix(completionBg, bannerAccent, 0.25) : d.color.completionCurrentBg)
536
+
537
+ const completionMetaBg = c('completion_menu_meta_bg') ?? completionBg
538
+ const completionMetaCurrentBg = c('completion_menu_meta_current_bg') ?? completionCurrentBg
539
+
540
+ return normalizeThemeForAnsiLightTerminal({
541
+ color: {
542
+ primary: c('ui_primary') ?? c('banner_title') ?? d.color.primary,
543
+ accent,
544
+ border: c('ui_border') ?? c('banner_border') ?? d.color.border,
545
+ text: c('ui_text') ?? c('banner_text') ?? d.color.text,
546
+ muted,
547
+ completionBg,
548
+ completionCurrentBg,
549
+ completionMetaBg,
550
+ completionMetaCurrentBg,
551
+
552
+ label: c('ui_label') ?? d.color.label,
553
+ ok: c('ui_ok') ?? d.color.ok,
554
+ error: c('ui_error') ?? d.color.error,
555
+ warn: c('ui_warn') ?? d.color.warn,
556
+
557
+ prompt: c('prompt') ?? c('banner_text') ?? d.color.prompt,
558
+ sessionLabel: c('session_label') ?? muted,
559
+ sessionBorder: c('session_border') ?? muted,
560
+
561
+ statusBg: d.color.statusBg,
562
+ statusFg: d.color.statusFg,
563
+ statusGood: c('ui_ok') ?? d.color.statusGood,
564
+ statusWarn: c('ui_warn') ?? d.color.statusWarn,
565
+ statusBad: d.color.statusBad,
566
+ statusCritical: d.color.statusCritical,
567
+ selectionBg: c('selection_bg') ?? c('completion_menu_current_bg') ?? (hasSkinColors ? completionCurrentBg : d.color.selectionBg),
568
+
569
+ diffAdded: d.color.diffAdded,
570
+ diffRemoved: d.color.diffRemoved,
571
+ diffAddedWord: d.color.diffAddedWord,
572
+ diffRemovedWord: d.color.diffRemovedWord,
573
+ shellDollar: c('shell_dollar') ?? d.color.shellDollar
574
+ },
575
+
576
+ brand: {
577
+ name: branding.agent_name ?? d.brand.name,
578
+ icon: d.brand.icon,
579
+ prompt: cleanPromptSymbol(branding.prompt_symbol, d.brand.prompt),
580
+ welcome: branding.welcome ?? d.brand.welcome,
581
+ goodbye: branding.goodbye ?? d.brand.goodbye,
582
+ tool: toolPrefix || d.brand.tool,
583
+ helpHeader: branding.help_header ?? (helpHeader || d.brand.helpHeader)
584
+ },
585
+
586
+ bannerLogo,
587
+ bannerHero
588
+ }, process.env, DEFAULT_LIGHT_MODE)
589
+ }