@butlerw/vellum 0.1.0

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 (446) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +411 -0
  3. package/__fixtures__/responses/code-generation.json +42 -0
  4. package/__fixtures__/responses/error-response.json +20 -0
  5. package/__fixtures__/responses/hello-world.json +32 -0
  6. package/dist/auth-6MCXESOH.js +26 -0
  7. package/dist/chunk-SECXJGWA.js +597 -0
  8. package/dist/index.js +34023 -0
  9. package/package.json +67 -0
  10. package/src/__tests__/commands.e2e.test.ts +728 -0
  11. package/src/__tests__/credentials.test.ts +713 -0
  12. package/src/__tests__/mode-e2e.test.ts +391 -0
  13. package/src/__tests__/tui-integration.test.tsx +1271 -0
  14. package/src/agents/__tests__/task-persistence.test.ts +235 -0
  15. package/src/agents/commands/delegate.ts +240 -0
  16. package/src/agents/commands/index.ts +10 -0
  17. package/src/agents/commands/resume.ts +335 -0
  18. package/src/agents/index.ts +29 -0
  19. package/src/agents/task-persistence.ts +272 -0
  20. package/src/agents/task-resumption.ts +242 -0
  21. package/src/app.tsx +4737 -0
  22. package/src/commands/__tests__/.gitkeep +1 -0
  23. package/src/commands/__tests__/agents.test.ts +606 -0
  24. package/src/commands/__tests__/auth.test.ts +626 -0
  25. package/src/commands/__tests__/autocomplete.test.ts +683 -0
  26. package/src/commands/__tests__/batch.test.ts +287 -0
  27. package/src/commands/__tests__/chain-pipe-parser.test.ts +654 -0
  28. package/src/commands/__tests__/completion.test.ts +238 -0
  29. package/src/commands/__tests__/core.test.ts +363 -0
  30. package/src/commands/__tests__/executor.test.ts +496 -0
  31. package/src/commands/__tests__/exit-codes.test.ts +220 -0
  32. package/src/commands/__tests__/init.test.ts +243 -0
  33. package/src/commands/__tests__/language.test.ts +353 -0
  34. package/src/commands/__tests__/mode-cli.test.ts +667 -0
  35. package/src/commands/__tests__/model.test.ts +277 -0
  36. package/src/commands/__tests__/parser.test.ts +493 -0
  37. package/src/commands/__tests__/performance.bench.ts +380 -0
  38. package/src/commands/__tests__/registry.test.ts +534 -0
  39. package/src/commands/__tests__/resume.test.ts +449 -0
  40. package/src/commands/__tests__/security.test.ts +845 -0
  41. package/src/commands/__tests__/stream-json.test.ts +372 -0
  42. package/src/commands/__tests__/user-commands.test.ts +597 -0
  43. package/src/commands/adapters.ts +267 -0
  44. package/src/commands/agent.ts +395 -0
  45. package/src/commands/agents/generate.ts +506 -0
  46. package/src/commands/agents/index.ts +272 -0
  47. package/src/commands/agents/show.ts +271 -0
  48. package/src/commands/agents/validate.ts +387 -0
  49. package/src/commands/auth.ts +883 -0
  50. package/src/commands/autocomplete.ts +480 -0
  51. package/src/commands/batch/command.ts +388 -0
  52. package/src/commands/batch/executor.ts +361 -0
  53. package/src/commands/batch/index.ts +12 -0
  54. package/src/commands/commit.ts +235 -0
  55. package/src/commands/completion/index.ts +371 -0
  56. package/src/commands/condense.ts +191 -0
  57. package/src/commands/config.ts +344 -0
  58. package/src/commands/context-provider.ts +173 -0
  59. package/src/commands/copy.ts +329 -0
  60. package/src/commands/core/clear.ts +38 -0
  61. package/src/commands/core/exit.ts +43 -0
  62. package/src/commands/core/help.ts +354 -0
  63. package/src/commands/core/index.ts +15 -0
  64. package/src/commands/cost.ts +179 -0
  65. package/src/commands/credentials.tsx +618 -0
  66. package/src/commands/custom-agents/__tests__/custom-agents.test.ts +709 -0
  67. package/src/commands/custom-agents/create.ts +377 -0
  68. package/src/commands/custom-agents/export.ts +135 -0
  69. package/src/commands/custom-agents/import.ts +199 -0
  70. package/src/commands/custom-agents/index.ts +372 -0
  71. package/src/commands/custom-agents/info.ts +318 -0
  72. package/src/commands/custom-agents/list.ts +267 -0
  73. package/src/commands/custom-agents/validate.ts +388 -0
  74. package/src/commands/diff-mode.ts +241 -0
  75. package/src/commands/env.ts +53 -0
  76. package/src/commands/executor.ts +579 -0
  77. package/src/commands/exit-codes.ts +202 -0
  78. package/src/commands/index.ts +701 -0
  79. package/src/commands/init/index.ts +15 -0
  80. package/src/commands/init/prompts.ts +366 -0
  81. package/src/commands/init/templates/commands-readme.md +80 -0
  82. package/src/commands/init/templates/example-command.md +79 -0
  83. package/src/commands/init/templates/example-skill.md +168 -0
  84. package/src/commands/init/templates/example-workflow.md +101 -0
  85. package/src/commands/init/templates/prompts-readme.md +52 -0
  86. package/src/commands/init/templates/rules-readme.md +63 -0
  87. package/src/commands/init/templates/skills-readme.md +83 -0
  88. package/src/commands/init/templates/workflows-readme.md +94 -0
  89. package/src/commands/init.ts +391 -0
  90. package/src/commands/install.ts +90 -0
  91. package/src/commands/language.ts +191 -0
  92. package/src/commands/loaders/.gitkeep +1 -0
  93. package/src/commands/lsp.ts +199 -0
  94. package/src/commands/markdown-commands.ts +253 -0
  95. package/src/commands/mcp.ts +588 -0
  96. package/src/commands/memory/export.ts +341 -0
  97. package/src/commands/memory/index.ts +148 -0
  98. package/src/commands/memory/list.ts +261 -0
  99. package/src/commands/memory/search.ts +346 -0
  100. package/src/commands/memory/utils.ts +15 -0
  101. package/src/commands/metrics.ts +75 -0
  102. package/src/commands/migrate/index.ts +16 -0
  103. package/src/commands/migrate/prompts.ts +477 -0
  104. package/src/commands/mode.ts +331 -0
  105. package/src/commands/model.ts +298 -0
  106. package/src/commands/onboard.ts +205 -0
  107. package/src/commands/open.ts +169 -0
  108. package/src/commands/output/stream-json.ts +373 -0
  109. package/src/commands/parser/chain-parser.ts +370 -0
  110. package/src/commands/parser/index.ts +29 -0
  111. package/src/commands/parser/pipe-parser.ts +480 -0
  112. package/src/commands/parser.ts +588 -0
  113. package/src/commands/persistence.ts +355 -0
  114. package/src/commands/progress.ts +18 -0
  115. package/src/commands/prompt/index.ts +17 -0
  116. package/src/commands/prompt/validate.ts +621 -0
  117. package/src/commands/prompt-priority.ts +401 -0
  118. package/src/commands/registry.ts +374 -0
  119. package/src/commands/sandbox/index.ts +131 -0
  120. package/src/commands/security/index.ts +21 -0
  121. package/src/commands/security/input-sanitizer.ts +168 -0
  122. package/src/commands/security/permission-checker.ts +456 -0
  123. package/src/commands/security/sensitive-data.ts +350 -0
  124. package/src/commands/session/delete.ts +38 -0
  125. package/src/commands/session/export.ts +39 -0
  126. package/src/commands/session/index.ts +26 -0
  127. package/src/commands/session/list.ts +26 -0
  128. package/src/commands/session/resume.ts +562 -0
  129. package/src/commands/session/search.ts +434 -0
  130. package/src/commands/session/show.ts +26 -0
  131. package/src/commands/settings.ts +368 -0
  132. package/src/commands/setup.ts +23 -0
  133. package/src/commands/shell/index.ts +16 -0
  134. package/src/commands/shell/setup.ts +422 -0
  135. package/src/commands/shell-init.ts +50 -0
  136. package/src/commands/shell-integration/index.ts +194 -0
  137. package/src/commands/skill.ts +1220 -0
  138. package/src/commands/spec.ts +558 -0
  139. package/src/commands/status.ts +246 -0
  140. package/src/commands/theme.ts +211 -0
  141. package/src/commands/think.ts +551 -0
  142. package/src/commands/trust.ts +211 -0
  143. package/src/commands/tutorial.ts +522 -0
  144. package/src/commands/types.ts +512 -0
  145. package/src/commands/update.ts +274 -0
  146. package/src/commands/usage.ts +213 -0
  147. package/src/commands/user-commands.ts +630 -0
  148. package/src/commands/utils.ts +142 -0
  149. package/src/commands/vim.ts +152 -0
  150. package/src/commands/workflow.ts +257 -0
  151. package/src/components/header.tsx +25 -0
  152. package/src/components/input.tsx +25 -0
  153. package/src/components/message-list.tsx +32 -0
  154. package/src/components/status-bar.tsx +23 -0
  155. package/src/index.tsx +614 -0
  156. package/src/onboarding/__tests__/tutorial.test.ts +740 -0
  157. package/src/onboarding/index.ts +69 -0
  158. package/src/onboarding/tips/index.ts +9 -0
  159. package/src/onboarding/tips/tip-engine.ts +459 -0
  160. package/src/onboarding/tutorial/index.ts +88 -0
  161. package/src/onboarding/tutorial/lessons/basics.ts +151 -0
  162. package/src/onboarding/tutorial/lessons/index.ts +151 -0
  163. package/src/onboarding/tutorial/lessons/modes.ts +230 -0
  164. package/src/onboarding/tutorial/lessons/tools.ts +172 -0
  165. package/src/onboarding/tutorial/progress-tracker.ts +350 -0
  166. package/src/onboarding/tutorial/storage.ts +249 -0
  167. package/src/onboarding/tutorial/tutorial-system.ts +462 -0
  168. package/src/onboarding/tutorial/types.ts +310 -0
  169. package/src/orchestrator-singleton.ts +129 -0
  170. package/src/shutdown.ts +33 -0
  171. package/src/test/e2e/assertions.ts +267 -0
  172. package/src/test/e2e/fixtures.ts +204 -0
  173. package/src/test/e2e/harness.ts +575 -0
  174. package/src/test/e2e/index.ts +57 -0
  175. package/src/test/e2e/types.ts +228 -0
  176. package/src/test/fixtures/__tests__/fake-response-loader.test.ts +314 -0
  177. package/src/test/fixtures/fake-response-loader.ts +314 -0
  178. package/src/test/fixtures/index.ts +20 -0
  179. package/src/tui/__tests__/mcp-panel.test.tsx +82 -0
  180. package/src/tui/__tests__/mcp-wiring.test.tsx +78 -0
  181. package/src/tui/__tests__/mode-components.test.tsx +395 -0
  182. package/src/tui/__tests__/permission-ask-flow.test.tsx +138 -0
  183. package/src/tui/__tests__/sidebar-panel-data.test.tsx +148 -0
  184. package/src/tui/__tests__/tools-panel-hotkeys.test.tsx +41 -0
  185. package/src/tui/adapters/agent-adapter.ts +1008 -0
  186. package/src/tui/adapters/index.ts +48 -0
  187. package/src/tui/adapters/message-adapter.ts +315 -0
  188. package/src/tui/adapters/persistence-bridge.ts +331 -0
  189. package/src/tui/adapters/session-adapter.ts +419 -0
  190. package/src/tui/buffered-stdout.ts +223 -0
  191. package/src/tui/components/AgentProgress.tsx +424 -0
  192. package/src/tui/components/Banner/AsciiArt.ts +160 -0
  193. package/src/tui/components/Banner/Banner.tsx +355 -0
  194. package/src/tui/components/Banner/ShimmerContext.tsx +131 -0
  195. package/src/tui/components/Banner/ShimmerText.tsx +193 -0
  196. package/src/tui/components/Banner/TypeWriterGradient.tsx +321 -0
  197. package/src/tui/components/Banner/index.ts +61 -0
  198. package/src/tui/components/Banner/useShimmer.ts +241 -0
  199. package/src/tui/components/ChatView.tsx +11 -0
  200. package/src/tui/components/Checkpoint/CheckpointDiffView.tsx +371 -0
  201. package/src/tui/components/Checkpoint/SnapshotCheckpointPanel.tsx +440 -0
  202. package/src/tui/components/Checkpoint/index.ts +19 -0
  203. package/src/tui/components/CostDisplay.tsx +226 -0
  204. package/src/tui/components/InitErrorBanner.tsx +122 -0
  205. package/src/tui/components/Input/Autocomplete.tsx +603 -0
  206. package/src/tui/components/Input/EnhancedCommandInput.tsx +471 -0
  207. package/src/tui/components/Input/HighlightedText.tsx +236 -0
  208. package/src/tui/components/Input/MentionAutocomplete.tsx +375 -0
  209. package/src/tui/components/Input/TextInput.tsx +1002 -0
  210. package/src/tui/components/Input/__tests__/Autocomplete.test.tsx +374 -0
  211. package/src/tui/components/Input/__tests__/TextInput.test.tsx +241 -0
  212. package/src/tui/components/Input/__tests__/highlight.test.ts +219 -0
  213. package/src/tui/components/Input/__tests__/slash-command-utils.test.ts +104 -0
  214. package/src/tui/components/Input/highlight.ts +362 -0
  215. package/src/tui/components/Input/index.ts +36 -0
  216. package/src/tui/components/Input/slash-command-utils.ts +135 -0
  217. package/src/tui/components/Layout.tsx +432 -0
  218. package/src/tui/components/McpPanel.tsx +137 -0
  219. package/src/tui/components/MemoryPanel.tsx +448 -0
  220. package/src/tui/components/Messages/CodeBlock.tsx +527 -0
  221. package/src/tui/components/Messages/DiffView.tsx +679 -0
  222. package/src/tui/components/Messages/ImageReference.tsx +89 -0
  223. package/src/tui/components/Messages/MarkdownBlock.tsx +228 -0
  224. package/src/tui/components/Messages/MarkdownRenderer.tsx +498 -0
  225. package/src/tui/components/Messages/MessageBubble.tsx +270 -0
  226. package/src/tui/components/Messages/MessageList.tsx +1719 -0
  227. package/src/tui/components/Messages/StreamingText.tsx +216 -0
  228. package/src/tui/components/Messages/ThinkingBlock.tsx +408 -0
  229. package/src/tui/components/Messages/ToolResultPreview.tsx +243 -0
  230. package/src/tui/components/Messages/__tests__/CodeBlock.test.tsx +296 -0
  231. package/src/tui/components/Messages/__tests__/DiffView.test.tsx +239 -0
  232. package/src/tui/components/Messages/__tests__/MarkdownRenderer.test.tsx +303 -0
  233. package/src/tui/components/Messages/__tests__/MessageBubble.test.tsx +268 -0
  234. package/src/tui/components/Messages/__tests__/MessageList.test.tsx +324 -0
  235. package/src/tui/components/Messages/__tests__/StreamingText.test.tsx +215 -0
  236. package/src/tui/components/Messages/index.ts +25 -0
  237. package/src/tui/components/ModeIndicator.tsx +177 -0
  238. package/src/tui/components/ModeSelector.tsx +216 -0
  239. package/src/tui/components/ModelSelector.tsx +339 -0
  240. package/src/tui/components/OnboardingWizard.tsx +670 -0
  241. package/src/tui/components/PhaseProgressIndicator.tsx +270 -0
  242. package/src/tui/components/RateLimitIndicator.tsx +82 -0
  243. package/src/tui/components/ScreenReaderLayout.tsx +295 -0
  244. package/src/tui/components/SettingsPanel.tsx +643 -0
  245. package/src/tui/components/Sidebar/SystemStatusPanel.tsx +284 -0
  246. package/src/tui/components/Sidebar/index.ts +9 -0
  247. package/src/tui/components/Status/ModelStatusBar.tsx +270 -0
  248. package/src/tui/components/Status/index.ts +12 -0
  249. package/src/tui/components/StatusBar/AgentModeIndicator.tsx +257 -0
  250. package/src/tui/components/StatusBar/ContextProgress.tsx +167 -0
  251. package/src/tui/components/StatusBar/FileChangesIndicator.tsx +62 -0
  252. package/src/tui/components/StatusBar/GitIndicator.tsx +89 -0
  253. package/src/tui/components/StatusBar/HeaderBar.tsx +126 -0
  254. package/src/tui/components/StatusBar/ModelIndicator.tsx +157 -0
  255. package/src/tui/components/StatusBar/PersistenceStatusIndicator.tsx +210 -0
  256. package/src/tui/components/StatusBar/ResilienceIndicator.tsx +106 -0
  257. package/src/tui/components/StatusBar/SandboxIndicator.tsx +167 -0
  258. package/src/tui/components/StatusBar/StatusBar.tsx +368 -0
  259. package/src/tui/components/StatusBar/ThinkingModeIndicator.tsx +170 -0
  260. package/src/tui/components/StatusBar/TokenBreakdown.tsx +246 -0
  261. package/src/tui/components/StatusBar/TokenCounter.tsx +135 -0
  262. package/src/tui/components/StatusBar/TrustModeIndicator.tsx +130 -0
  263. package/src/tui/components/StatusBar/WorkspaceIndicator.tsx +86 -0
  264. package/src/tui/components/StatusBar/__tests__/AgentModeIndicator.test.tsx +193 -0
  265. package/src/tui/components/StatusBar/__tests__/StatusBar.test.tsx +729 -0
  266. package/src/tui/components/StatusBar/index.ts +60 -0
  267. package/src/tui/components/TipBanner.tsx +115 -0
  268. package/src/tui/components/TodoItem.tsx +208 -0
  269. package/src/tui/components/TodoPanel.tsx +455 -0
  270. package/src/tui/components/Tools/ApprovalQueue.tsx +407 -0
  271. package/src/tui/components/Tools/OptionSelector.tsx +160 -0
  272. package/src/tui/components/Tools/PermissionDialog.tsx +286 -0
  273. package/src/tui/components/Tools/ToolParams.tsx +483 -0
  274. package/src/tui/components/Tools/ToolsPanel.tsx +178 -0
  275. package/src/tui/components/Tools/__tests__/PermissionDialog.test.tsx +510 -0
  276. package/src/tui/components/Tools/__tests__/ToolParams.test.tsx +432 -0
  277. package/src/tui/components/Tools/index.ts +21 -0
  278. package/src/tui/components/TrustPrompt.tsx +279 -0
  279. package/src/tui/components/UpdateBanner.tsx +166 -0
  280. package/src/tui/components/VimModeIndicator.tsx +112 -0
  281. package/src/tui/components/backtrack/BacktrackControls.tsx +402 -0
  282. package/src/tui/components/backtrack/index.ts +13 -0
  283. package/src/tui/components/common/AutoApprovalStatus.tsx +251 -0
  284. package/src/tui/components/common/CostWarning.tsx +294 -0
  285. package/src/tui/components/common/DynamicShortcutHints.tsx +209 -0
  286. package/src/tui/components/common/EnhancedLoadingIndicator.tsx +305 -0
  287. package/src/tui/components/common/ErrorBoundary.tsx +140 -0
  288. package/src/tui/components/common/GradientText.tsx +224 -0
  289. package/src/tui/components/common/HotkeyHelpModal.tsx +193 -0
  290. package/src/tui/components/common/HotkeyHints.tsx +70 -0
  291. package/src/tui/components/common/MaxSizedBox.tsx +354 -0
  292. package/src/tui/components/common/NewMessagesBadge.tsx +65 -0
  293. package/src/tui/components/common/ProtectedFileLegend.tsx +89 -0
  294. package/src/tui/components/common/ScrollIndicator.tsx +160 -0
  295. package/src/tui/components/common/Spinner.tsx +342 -0
  296. package/src/tui/components/common/StreamingIndicator.tsx +316 -0
  297. package/src/tui/components/common/VirtualizedList/VirtualizedList.tsx +428 -0
  298. package/src/tui/components/common/VirtualizedList/hooks/index.ts +19 -0
  299. package/src/tui/components/common/VirtualizedList/hooks/useBatchedScroll.ts +64 -0
  300. package/src/tui/components/common/VirtualizedList/hooks/useScrollAnchor.ts +290 -0
  301. package/src/tui/components/common/VirtualizedList/hooks/useVirtualization.ts +340 -0
  302. package/src/tui/components/common/VirtualizedList/index.ts +30 -0
  303. package/src/tui/components/common/VirtualizedList/types.ts +107 -0
  304. package/src/tui/components/common/__tests__/NewMessagesBadge.test.tsx +74 -0
  305. package/src/tui/components/common/__tests__/ScrollIndicator.test.tsx +193 -0
  306. package/src/tui/components/common/index.ts +110 -0
  307. package/src/tui/components/index.ts +79 -0
  308. package/src/tui/components/session/CheckpointPanel.tsx +323 -0
  309. package/src/tui/components/session/RollbackDialog.tsx +169 -0
  310. package/src/tui/components/session/SessionItem.tsx +136 -0
  311. package/src/tui/components/session/SessionListPanel.tsx +252 -0
  312. package/src/tui/components/session/SessionPicker.tsx +449 -0
  313. package/src/tui/components/session/SessionPreview.tsx +240 -0
  314. package/src/tui/components/session/__tests__/session.test.tsx +408 -0
  315. package/src/tui/components/session/index.ts +28 -0
  316. package/src/tui/components/session/types.ts +116 -0
  317. package/src/tui/components/theme/__tests__/tokens.test.ts +471 -0
  318. package/src/tui/components/theme/index.ts +227 -0
  319. package/src/tui/components/theme/tokens.ts +484 -0
  320. package/src/tui/config/defaults.ts +134 -0
  321. package/src/tui/config/index.ts +17 -0
  322. package/src/tui/context/AnimationContext.tsx +284 -0
  323. package/src/tui/context/AppContext.tsx +349 -0
  324. package/src/tui/context/BracketedPasteContext.tsx +372 -0
  325. package/src/tui/context/LspContext.tsx +192 -0
  326. package/src/tui/context/McpContext.tsx +325 -0
  327. package/src/tui/context/MessagesContext.tsx +870 -0
  328. package/src/tui/context/OverflowContext.tsx +213 -0
  329. package/src/tui/context/RateLimitContext.tsx +108 -0
  330. package/src/tui/context/ResilienceContext.tsx +275 -0
  331. package/src/tui/context/RootProvider.tsx +136 -0
  332. package/src/tui/context/ScrollContext.tsx +331 -0
  333. package/src/tui/context/ToolsContext.tsx +702 -0
  334. package/src/tui/context/__tests__/BracketedPasteContext.test.tsx +416 -0
  335. package/src/tui/context/index.ts +140 -0
  336. package/src/tui/enterprise-integration.ts +282 -0
  337. package/src/tui/hooks/__tests__/useBacktrack.test.tsx +138 -0
  338. package/src/tui/hooks/__tests__/useBracketedPaste.test.tsx +222 -0
  339. package/src/tui/hooks/__tests__/useCopyMode.test.tsx +336 -0
  340. package/src/tui/hooks/__tests__/useHotkeys.ctrl-input.test.tsx +96 -0
  341. package/src/tui/hooks/__tests__/useHotkeys.test.tsx +454 -0
  342. package/src/tui/hooks/__tests__/useInputHistory.test.tsx +660 -0
  343. package/src/tui/hooks/__tests__/useLineBuffer.test.ts +295 -0
  344. package/src/tui/hooks/__tests__/useModeController.test.ts +137 -0
  345. package/src/tui/hooks/__tests__/useModeShortcuts.test.tsx +142 -0
  346. package/src/tui/hooks/__tests__/useScrollController.test.ts +464 -0
  347. package/src/tui/hooks/__tests__/useVim.test.tsx +531 -0
  348. package/src/tui/hooks/index.ts +252 -0
  349. package/src/tui/hooks/useAgentLoop.ts +712 -0
  350. package/src/tui/hooks/useAlternateBuffer.ts +398 -0
  351. package/src/tui/hooks/useAnimatedScrollbar.ts +241 -0
  352. package/src/tui/hooks/useBacktrack.ts +443 -0
  353. package/src/tui/hooks/useBracketedPaste.ts +104 -0
  354. package/src/tui/hooks/useCollapsible.ts +240 -0
  355. package/src/tui/hooks/useCopyMode.ts +382 -0
  356. package/src/tui/hooks/useCostSummary.ts +75 -0
  357. package/src/tui/hooks/useDesktopNotification.ts +414 -0
  358. package/src/tui/hooks/useDiffMode.ts +44 -0
  359. package/src/tui/hooks/useFileChangeStats.ts +110 -0
  360. package/src/tui/hooks/useFileSuggestions.ts +284 -0
  361. package/src/tui/hooks/useFlickerDetector.ts +250 -0
  362. package/src/tui/hooks/useGitStatus.ts +200 -0
  363. package/src/tui/hooks/useHotkeys.ts +579 -0
  364. package/src/tui/hooks/useImagePaste.ts +114 -0
  365. package/src/tui/hooks/useInputHighlight.ts +145 -0
  366. package/src/tui/hooks/useInputHistory.ts +246 -0
  367. package/src/tui/hooks/useKeyboardScroll.ts +209 -0
  368. package/src/tui/hooks/useLineBuffer.ts +356 -0
  369. package/src/tui/hooks/useMentionAutocomplete.ts +235 -0
  370. package/src/tui/hooks/useModeController.ts +167 -0
  371. package/src/tui/hooks/useModeShortcuts.ts +196 -0
  372. package/src/tui/hooks/usePermissionHandler.ts +146 -0
  373. package/src/tui/hooks/usePersistence.ts +480 -0
  374. package/src/tui/hooks/usePersistenceShortcuts.ts +225 -0
  375. package/src/tui/hooks/usePlaceholderRotation.ts +143 -0
  376. package/src/tui/hooks/useProviderStatus.ts +270 -0
  377. package/src/tui/hooks/useRateLimitStatus.ts +90 -0
  378. package/src/tui/hooks/useScreenReader.ts +315 -0
  379. package/src/tui/hooks/useScrollController.ts +450 -0
  380. package/src/tui/hooks/useScrollEventBatcher.ts +185 -0
  381. package/src/tui/hooks/useSidebarPanelData.ts +115 -0
  382. package/src/tui/hooks/useSmoothScroll.ts +202 -0
  383. package/src/tui/hooks/useSnapshots.ts +300 -0
  384. package/src/tui/hooks/useStateAndRef.ts +50 -0
  385. package/src/tui/hooks/useTerminalSize.ts +206 -0
  386. package/src/tui/hooks/useToolApprovalController.ts +91 -0
  387. package/src/tui/hooks/useVim.ts +334 -0
  388. package/src/tui/hooks/useWorkspace.ts +56 -0
  389. package/src/tui/i18n/__tests__/init.test.ts +278 -0
  390. package/src/tui/i18n/__tests__/language-config.test.ts +199 -0
  391. package/src/tui/i18n/__tests__/locale-detection.test.ts +250 -0
  392. package/src/tui/i18n/__tests__/settings-integration.test.ts +262 -0
  393. package/src/tui/i18n/index.ts +72 -0
  394. package/src/tui/i18n/init.ts +131 -0
  395. package/src/tui/i18n/language-config.ts +106 -0
  396. package/src/tui/i18n/locale-detection.ts +173 -0
  397. package/src/tui/i18n/settings-integration.ts +557 -0
  398. package/src/tui/i18n/tui-namespace.ts +538 -0
  399. package/src/tui/i18n/types.ts +312 -0
  400. package/src/tui/index.ts +43 -0
  401. package/src/tui/lsp-integration.ts +409 -0
  402. package/src/tui/metrics-integration.ts +366 -0
  403. package/src/tui/plugins.ts +383 -0
  404. package/src/tui/resilience.ts +342 -0
  405. package/src/tui/sandbox-integration.ts +317 -0
  406. package/src/tui/services/clipboard.ts +348 -0
  407. package/src/tui/services/fuzzy-search.ts +441 -0
  408. package/src/tui/services/index.ts +72 -0
  409. package/src/tui/services/markdown-renderer.ts +565 -0
  410. package/src/tui/services/open-external.ts +247 -0
  411. package/src/tui/services/syntax-highlighter.ts +483 -0
  412. package/src/tui/slash-commands.ts +12 -0
  413. package/src/tui/theme/index.ts +15 -0
  414. package/src/tui/theme/provider.tsx +206 -0
  415. package/src/tui/tip-integration.ts +300 -0
  416. package/src/tui/types/__tests__/ink-extended.test.ts +121 -0
  417. package/src/tui/types/ink-extended.ts +87 -0
  418. package/src/tui/utils/__tests__/bracketedPaste.test.ts +231 -0
  419. package/src/tui/utils/__tests__/heightEstimator.test.ts +157 -0
  420. package/src/tui/utils/__tests__/text-width.test.ts +158 -0
  421. package/src/tui/utils/__tests__/textSanitizer.test.ts +266 -0
  422. package/src/tui/utils/__tests__/ui-sizing.test.ts +169 -0
  423. package/src/tui/utils/bracketedPaste.ts +107 -0
  424. package/src/tui/utils/cursor-manager.ts +131 -0
  425. package/src/tui/utils/detectTerminal.ts +596 -0
  426. package/src/tui/utils/findLastSafeSplitPoint.ts +92 -0
  427. package/src/tui/utils/heightEstimator.ts +198 -0
  428. package/src/tui/utils/index.ts +91 -0
  429. package/src/tui/utils/isNarrowWidth.ts +52 -0
  430. package/src/tui/utils/stdoutGuard.ts +90 -0
  431. package/src/tui/utils/synchronized-update.ts +70 -0
  432. package/src/tui/utils/text-width.ts +225 -0
  433. package/src/tui/utils/textSanitizer.ts +225 -0
  434. package/src/tui/utils/textUtils.ts +114 -0
  435. package/src/tui/utils/ui-sizing.ts +192 -0
  436. package/src/tui-blessed/app.ts +160 -0
  437. package/src/tui-blessed/index.ts +2 -0
  438. package/src/tui-blessed/neo-blessed.d.ts +6 -0
  439. package/src/tui-blessed/test.ts +21 -0
  440. package/src/tui-blessed/types.ts +14 -0
  441. package/src/utils/icons.ts +130 -0
  442. package/src/utils/index.ts +33 -0
  443. package/src/utils/resume-hint.ts +86 -0
  444. package/src/version.ts +1 -0
  445. package/tsconfig.json +8 -0
  446. package/vitest.config.ts +35 -0
@@ -0,0 +1,596 @@
1
+ /**
2
+ * Terminal Detection Utilities (T060)
3
+ *
4
+ * Detects terminal capabilities for optimal TUI rendering.
5
+ * Supports various terminal emulators and gracefully degrades
6
+ * for unsupported features.
7
+ *
8
+ * @module tui/utils/detectTerminal
9
+ */
10
+
11
+ // =============================================================================
12
+ // Types
13
+ // =============================================================================
14
+
15
+ /**
16
+ * Terminal type enumeration.
17
+ */
18
+ export type TerminalType =
19
+ | "iterm2"
20
+ | "kitty"
21
+ | "wezterm"
22
+ | "windows-terminal"
23
+ | "vscode"
24
+ | "alacritty"
25
+ | "hyper"
26
+ | "terminal-app" // macOS Terminal.app
27
+ | "gnome-terminal"
28
+ | "konsole"
29
+ | "xterm"
30
+ | "mintty" // Git Bash, Cygwin
31
+ | "conemu"
32
+ | "cmd"
33
+ | "powershell"
34
+ | "unknown";
35
+
36
+ /**
37
+ * Terminal capabilities detected from the environment.
38
+ */
39
+ export interface TerminalCapabilities {
40
+ /** Terminal type/emulator name */
41
+ readonly terminalType: TerminalType;
42
+ /** Supports true color (24-bit) */
43
+ readonly trueColor: boolean;
44
+ /** Supports 256 colors */
45
+ readonly color256: boolean;
46
+ /** Supports Unicode characters */
47
+ readonly unicode: boolean;
48
+ /** Supports Sixel graphics */
49
+ readonly sixel: boolean;
50
+ /** Supports Kitty graphics protocol */
51
+ readonly kittyGraphics: boolean;
52
+ /** Supports iTerm2 inline images */
53
+ readonly iterm2Images: boolean;
54
+ /** Supports alternate screen buffer */
55
+ readonly alternateBuffer: boolean;
56
+ /** Supports mouse events */
57
+ readonly mouseSupport: boolean;
58
+ /** Supports bracketed paste mode */
59
+ readonly bracketedPaste: boolean;
60
+ /** Supports hyperlinks (OSC 8) */
61
+ readonly hyperlinks: boolean;
62
+ /** Supports styled underlines */
63
+ readonly styledUnderlines: boolean;
64
+ /** Terminal width in columns */
65
+ readonly columns: number;
66
+ /** Terminal height in rows */
67
+ readonly rows: number;
68
+ /** Whether running in CI environment */
69
+ readonly isCI: boolean;
70
+ /** Whether running in a TTY */
71
+ readonly isTTY: boolean;
72
+ }
73
+
74
+ /**
75
+ * Options for terminal detection.
76
+ */
77
+ export interface DetectTerminalOptions {
78
+ /** Force specific terminal type (for testing) */
79
+ readonly forceTerminal?: TerminalType;
80
+ /** Force specific color level (for testing) */
81
+ readonly forceColorLevel?: 0 | 1 | 2 | 3;
82
+ /** Override environment variables */
83
+ readonly env?: Record<string, string | undefined>;
84
+ }
85
+
86
+ // =============================================================================
87
+ // Constants
88
+ // =============================================================================
89
+
90
+ /** Default capabilities for unknown terminals */
91
+ const DEFAULT_CAPABILITIES: TerminalCapabilities = {
92
+ terminalType: "unknown",
93
+ trueColor: false,
94
+ color256: false,
95
+ unicode: false,
96
+ sixel: false,
97
+ kittyGraphics: false,
98
+ iterm2Images: false,
99
+ alternateBuffer: true,
100
+ mouseSupport: false,
101
+ bracketedPaste: false,
102
+ hyperlinks: false,
103
+ styledUnderlines: false,
104
+ columns: 80,
105
+ rows: 24,
106
+ isCI: false,
107
+ isTTY: false,
108
+ };
109
+
110
+ /** CI environment variable names */
111
+ const CI_ENV_VARS = [
112
+ "CI",
113
+ "CONTINUOUS_INTEGRATION",
114
+ "BUILD_NUMBER",
115
+ "GITHUB_ACTIONS",
116
+ "GITLAB_CI",
117
+ "CIRCLECI",
118
+ "TRAVIS",
119
+ "JENKINS_URL",
120
+ "TEAMCITY_VERSION",
121
+ "BUILDKITE",
122
+ "TF_BUILD", // Azure Pipelines
123
+ ];
124
+
125
+ // =============================================================================
126
+ // Terminal Type Detection
127
+ // =============================================================================
128
+
129
+ interface TerminalEnvVars {
130
+ termProgram: string;
131
+ term: string;
132
+ lcTerminal: string;
133
+ wtSession: string | undefined;
134
+ kitty: string | undefined;
135
+ wezterm: string | undefined;
136
+ alacritty: string | undefined;
137
+ conemu: string | undefined;
138
+ vscodeTerminal: boolean;
139
+ }
140
+
141
+ /** Extract terminal-related environment variables */
142
+ function extractTerminalEnvVars(env: Record<string, string | undefined>): TerminalEnvVars {
143
+ return {
144
+ termProgram: env.TERM_PROGRAM?.toLowerCase() ?? "",
145
+ term: env.TERM?.toLowerCase() ?? "",
146
+ lcTerminal: env.LC_TERMINAL?.toLowerCase() ?? "",
147
+ wtSession: env.WT_SESSION,
148
+ kitty: env.KITTY_WINDOW_ID,
149
+ wezterm: env.WEZTERM_PANE,
150
+ alacritty: env.ALACRITTY_WINDOW_ID,
151
+ conemu: env.ConEmuANSI,
152
+ vscodeTerminal: Boolean(env.VSCODE_INJECTION || env.TERM_PROGRAM === "vscode"),
153
+ };
154
+ }
155
+
156
+ /** Detect modern GUI terminal emulators */
157
+ function detectModernTerminal(vars: TerminalEnvVars): TerminalType | null {
158
+ if (vars.termProgram === "iterm.app" || vars.lcTerminal === "iterm2") return "iterm2";
159
+ if (vars.kitty || vars.termProgram === "kitty" || vars.term === "xterm-kitty") return "kitty";
160
+ if (vars.wezterm || vars.termProgram === "wezterm") return "wezterm";
161
+ if (vars.wtSession) return "windows-terminal";
162
+ if (vars.vscodeTerminal || vars.termProgram === "vscode") return "vscode";
163
+ if (vars.alacritty || vars.termProgram === "alacritty") return "alacritty";
164
+ if (vars.termProgram === "hyper") return "hyper";
165
+ return null;
166
+ }
167
+
168
+ /** Detect platform-specific terminals */
169
+ function detectPlatformTerminal(
170
+ vars: TerminalEnvVars,
171
+ env: Record<string, string | undefined>
172
+ ): TerminalType | null {
173
+ if (vars.termProgram === "apple_terminal") return "terminal-app";
174
+ if (env.GNOME_TERMINAL_SCREEN || env.VTE_VERSION) return "gnome-terminal";
175
+ if (env.KONSOLE_VERSION) return "konsole";
176
+ if (vars.conemu) return "conemu";
177
+ if (vars.term.includes("mintty") || env.MSYSTEM) return "mintty";
178
+ if (vars.term.includes("xterm")) return "xterm";
179
+ if (env.PROMPT && !env.SHELL) return "cmd";
180
+ if (env.PSModulePath && !vars.wtSession) return "powershell";
181
+ return null;
182
+ }
183
+
184
+ /**
185
+ * Detect the terminal type from environment variables.
186
+ */
187
+ function detectTerminalType(env: Record<string, string | undefined>): TerminalType {
188
+ const vars = extractTerminalEnvVars(env);
189
+
190
+ const modernTerminal = detectModernTerminal(vars);
191
+ if (modernTerminal) return modernTerminal;
192
+
193
+ const platformTerminal = detectPlatformTerminal(vars, env);
194
+ if (platformTerminal) return platformTerminal;
195
+
196
+ return "unknown";
197
+ }
198
+
199
+ // =============================================================================
200
+ // Color Support Detection
201
+ // =============================================================================
202
+
203
+ /** Check forced color settings from environment */
204
+ function checkForcedColorLevel(env: Record<string, string | undefined>): 0 | 1 | 2 | 3 | null {
205
+ if ("NO_COLOR" in env) return 0;
206
+
207
+ const forceColor = env.FORCE_COLOR;
208
+ if (forceColor === "0" || forceColor === "false") return 0;
209
+ if (forceColor === "1" || forceColor === "true") return 1;
210
+ if (forceColor === "2") return 2;
211
+ if (forceColor === "3") return 3;
212
+
213
+ const colorTerm = env.COLORTERM?.toLowerCase() ?? "";
214
+ if (colorTerm === "truecolor" || colorTerm === "24bit") return 3;
215
+
216
+ return null;
217
+ }
218
+
219
+ /** Get color level for specific terminal type */
220
+ function getTerminalColorLevel(
221
+ terminalType: TerminalType,
222
+ env: Record<string, string | undefined>
223
+ ): 0 | 1 | 2 | 3 {
224
+ // Modern terminals with true color
225
+ const trueColorTerminals: TerminalType[] = [
226
+ "iterm2",
227
+ "kitty",
228
+ "wezterm",
229
+ "windows-terminal",
230
+ "vscode",
231
+ "alacritty",
232
+ "hyper",
233
+ "mintty",
234
+ "conemu",
235
+ ];
236
+ if (trueColorTerminals.includes(terminalType)) return 3;
237
+
238
+ // VTE-based terminals
239
+ if (terminalType === "gnome-terminal" || terminalType === "konsole") {
240
+ const vteVersion = parseInt(env.VTE_VERSION ?? "0", 10);
241
+ return vteVersion >= 3600 ? 3 : 2;
242
+ }
243
+
244
+ if (terminalType === "terminal-app") return 2;
245
+
246
+ if (terminalType === "xterm") {
247
+ const term = env.TERM ?? "";
248
+ if (term.includes("256color")) return 2;
249
+ if (term.includes("truecolor") || term.includes("24bit")) return 3;
250
+ return 1;
251
+ }
252
+
253
+ if (terminalType === "cmd" || terminalType === "powershell") {
254
+ return process.platform === "win32" ? 2 : 1;
255
+ }
256
+
257
+ return 0;
258
+ }
259
+
260
+ /** Fallback color detection from TERM variable */
261
+ function detectColorFromTerm(env: Record<string, string | undefined>): 0 | 1 | 2 | 3 {
262
+ const term = env.TERM ?? "";
263
+ if (term.includes("256color")) return 2;
264
+ if (term.includes("color")) return 1;
265
+ return 0;
266
+ }
267
+
268
+ /**
269
+ * Detect color support level.
270
+ * Returns: 0 = no color, 1 = basic (16), 2 = 256, 3 = truecolor (16M)
271
+ */
272
+ function detectColorLevel(
273
+ env: Record<string, string | undefined>,
274
+ terminalType: TerminalType
275
+ ): 0 | 1 | 2 | 3 {
276
+ const forcedLevel = checkForcedColorLevel(env);
277
+ if (forcedLevel !== null) return forcedLevel;
278
+
279
+ if (terminalType !== "unknown") {
280
+ return getTerminalColorLevel(terminalType, env);
281
+ }
282
+
283
+ return detectColorFromTerm(env);
284
+ }
285
+
286
+ // =============================================================================
287
+ // Feature Detection
288
+ // =============================================================================
289
+
290
+ /**
291
+ * Detect Unicode support.
292
+ */
293
+ function detectUnicodeSupport(
294
+ env: Record<string, string | undefined>,
295
+ terminalType: TerminalType
296
+ ): boolean {
297
+ // Check locale settings
298
+ const lang = env.LANG ?? "";
299
+ const lcAll = env.LC_ALL ?? "";
300
+ const lcCtype = env.LC_CTYPE ?? "";
301
+
302
+ const hasUtf8Locale =
303
+ lang.toLowerCase().includes("utf") ||
304
+ lcAll.toLowerCase().includes("utf") ||
305
+ lcCtype.toLowerCase().includes("utf");
306
+
307
+ if (hasUtf8Locale) {
308
+ return true;
309
+ }
310
+
311
+ // Modern terminals generally support Unicode
312
+ const modernTerminals: TerminalType[] = [
313
+ "iterm2",
314
+ "kitty",
315
+ "wezterm",
316
+ "windows-terminal",
317
+ "vscode",
318
+ "alacritty",
319
+ "hyper",
320
+ "gnome-terminal",
321
+ "konsole",
322
+ ];
323
+
324
+ return modernTerminals.includes(terminalType);
325
+ }
326
+
327
+ /**
328
+ * Detect Sixel graphics support.
329
+ */
330
+ function detectSixelSupport(terminalType: TerminalType): boolean {
331
+ // Terminals known to support Sixel
332
+ const sixelTerminals: TerminalType[] = [
333
+ "kitty", // Optional
334
+ "wezterm",
335
+ "mintty",
336
+ ];
337
+
338
+ return sixelTerminals.includes(terminalType);
339
+ }
340
+
341
+ /**
342
+ * Detect Kitty graphics protocol support.
343
+ */
344
+ function detectKittyGraphicsSupport(terminalType: TerminalType): boolean {
345
+ return terminalType === "kitty" || terminalType === "wezterm";
346
+ }
347
+
348
+ /**
349
+ * Detect iTerm2 inline images support.
350
+ */
351
+ function detectITerm2ImagesSupport(terminalType: TerminalType): boolean {
352
+ return terminalType === "iterm2" || terminalType === "wezterm";
353
+ }
354
+
355
+ /**
356
+ * Detect hyperlink (OSC 8) support.
357
+ */
358
+ function detectHyperlinkSupport(terminalType: TerminalType): boolean {
359
+ const hyperlinkTerminals: TerminalType[] = [
360
+ "iterm2",
361
+ "kitty",
362
+ "wezterm",
363
+ "windows-terminal",
364
+ "vscode",
365
+ "gnome-terminal",
366
+ "konsole",
367
+ "alacritty",
368
+ "hyper",
369
+ ];
370
+
371
+ return hyperlinkTerminals.includes(terminalType);
372
+ }
373
+
374
+ /**
375
+ * Detect styled underlines support.
376
+ */
377
+ function detectStyledUnderlines(terminalType: TerminalType): boolean {
378
+ const styledUnderlineTerminals: TerminalType[] = [
379
+ "iterm2",
380
+ "kitty",
381
+ "wezterm",
382
+ "vscode",
383
+ "gnome-terminal", // VTE-based
384
+ "konsole",
385
+ ];
386
+
387
+ return styledUnderlineTerminals.includes(terminalType);
388
+ }
389
+
390
+ /**
391
+ * Detect if running in CI environment.
392
+ */
393
+ function detectCI(env: Record<string, string | undefined>): boolean {
394
+ return CI_ENV_VARS.some((varName) => varName in env);
395
+ }
396
+
397
+ /**
398
+ * Detect PowerShell running on ConPTY-backed terminals.
399
+ * Used to disable alternate buffer by default due to clear/scroll issues.
400
+ */
401
+ export function isConptyTerminal(
402
+ env: Record<string, string | undefined> = process.env as Record<string, string | undefined>
403
+ ): boolean {
404
+ if (process.platform !== "win32") {
405
+ return false;
406
+ }
407
+
408
+ const termProgram = env.TERM_PROGRAM?.toLowerCase() ?? "";
409
+ return Boolean(
410
+ env.WT_SESSION || env.VSCODE_INJECTION || env.VSCODE_GIT_IPC_HANDLE || termProgram === "vscode"
411
+ );
412
+ }
413
+
414
+ export function isPowerShellConptyTerminal(
415
+ env: Record<string, string | undefined> = process.env as Record<string, string | undefined>
416
+ ): boolean {
417
+ if (!isConptyTerminal(env)) {
418
+ return false;
419
+ }
420
+
421
+ const isPowerShell = Boolean(env.PSModulePath);
422
+ return isPowerShell;
423
+ }
424
+
425
+ // =============================================================================
426
+ // Main Detection Function
427
+ // =============================================================================
428
+
429
+ /**
430
+ * Detect terminal capabilities.
431
+ *
432
+ * Analyzes the environment to determine what features the current
433
+ * terminal supports. Use this to conditionally enable/disable
434
+ * TUI features based on terminal capabilities.
435
+ *
436
+ * @example
437
+ * ```tsx
438
+ * const capabilities = detectTerminal();
439
+ *
440
+ * // Use true color if available
441
+ * const color = capabilities.trueColor
442
+ * ? '#ff6b6b'
443
+ * : 'red';
444
+ *
445
+ * // Show fancy Unicode if supported
446
+ * const bullet = capabilities.unicode ? '●' : '*';
447
+ *
448
+ * // Disable animations in CI
449
+ * const animate = !capabilities.isCI;
450
+ * ```
451
+ */
452
+ export function detectTerminal(options: DetectTerminalOptions = {}): TerminalCapabilities {
453
+ const { forceTerminal, forceColorLevel, env: envOverride } = options;
454
+
455
+ // Use overrides or process.env
456
+ const env = envOverride ?? (process.env as Record<string, string | undefined>);
457
+
458
+ // Get terminal dimensions
459
+ const columns = process.stdout.columns || DEFAULT_CAPABILITIES.columns;
460
+ const rows = process.stdout.rows || DEFAULT_CAPABILITIES.rows;
461
+
462
+ // Check if running in TTY
463
+ const isTTY = process.stdout.isTTY ?? false;
464
+
465
+ // Detect terminal type
466
+ const terminalType = forceTerminal ?? detectTerminalType(env);
467
+
468
+ // Detect color level
469
+ const colorLevel = forceColorLevel ?? detectColorLevel(env, terminalType);
470
+
471
+ // Detect other capabilities
472
+ const isCI = detectCI(env);
473
+ const unicode = detectUnicodeSupport(env, terminalType);
474
+ const sixel = detectSixelSupport(terminalType);
475
+ const kittyGraphics = detectKittyGraphicsSupport(terminalType);
476
+ const iterm2Images = detectITerm2ImagesSupport(terminalType);
477
+ const hyperlinks = detectHyperlinkSupport(terminalType);
478
+ const styledUnderlines = detectStyledUnderlines(terminalType);
479
+
480
+ return {
481
+ terminalType,
482
+ trueColor: colorLevel >= 3,
483
+ color256: colorLevel >= 2,
484
+ unicode,
485
+ sixel,
486
+ kittyGraphics,
487
+ iterm2Images,
488
+ alternateBuffer: isTTY && !isCI && !isConptyTerminal(env),
489
+ mouseSupport: isTTY && !isCI,
490
+ bracketedPaste: isTTY,
491
+ hyperlinks,
492
+ styledUnderlines,
493
+ columns,
494
+ rows,
495
+ isCI,
496
+ isTTY,
497
+ };
498
+ }
499
+
500
+ // =============================================================================
501
+ // Utility Functions
502
+ // =============================================================================
503
+
504
+ /**
505
+ * Get a human-readable name for the terminal type.
506
+ */
507
+ export function getTerminalName(type: TerminalType): string {
508
+ const names: Record<TerminalType, string> = {
509
+ iterm2: "iTerm2",
510
+ kitty: "Kitty",
511
+ wezterm: "WezTerm",
512
+ "windows-terminal": "Windows Terminal",
513
+ vscode: "VS Code",
514
+ alacritty: "Alacritty",
515
+ hyper: "Hyper",
516
+ "terminal-app": "Terminal.app",
517
+ "gnome-terminal": "GNOME Terminal",
518
+ konsole: "Konsole",
519
+ xterm: "XTerm",
520
+ mintty: "MinTTY",
521
+ conemu: "ConEmu",
522
+ cmd: "Command Prompt",
523
+ powershell: "PowerShell",
524
+ unknown: "Unknown Terminal",
525
+ };
526
+
527
+ return names[type];
528
+ }
529
+
530
+ /**
531
+ * Create a degraded color based on capabilities.
532
+ */
533
+ export function degradeColor(
534
+ capabilities: TerminalCapabilities,
535
+ trueColor: string,
536
+ color256: number,
537
+ basicColor: string
538
+ ): string {
539
+ if (capabilities.trueColor) {
540
+ return trueColor;
541
+ }
542
+ if (capabilities.color256) {
543
+ return `\x1b[38;5;${color256}m`;
544
+ }
545
+ return basicColor;
546
+ }
547
+
548
+ /**
549
+ * Get appropriate symbol based on Unicode support.
550
+ */
551
+ export function getSymbol(
552
+ capabilities: TerminalCapabilities,
553
+ unicode: string,
554
+ ascii: string
555
+ ): string {
556
+ return capabilities.unicode ? unicode : ascii;
557
+ }
558
+
559
+ /**
560
+ * Create a hyperlink if supported, otherwise return plain text.
561
+ */
562
+ export function createHyperlink(
563
+ capabilities: TerminalCapabilities,
564
+ url: string,
565
+ text: string
566
+ ): string {
567
+ if (capabilities.hyperlinks) {
568
+ return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
569
+ }
570
+ return text;
571
+ }
572
+
573
+ // =============================================================================
574
+ // Singleton Instance
575
+ // =============================================================================
576
+
577
+ let cachedCapabilities: TerminalCapabilities | null = null;
578
+
579
+ /**
580
+ * Get cached terminal capabilities (singleton).
581
+ * Use this for performance when you don't need to re-detect.
582
+ */
583
+ export function getTerminalCapabilities(): TerminalCapabilities {
584
+ if (!cachedCapabilities) {
585
+ cachedCapabilities = detectTerminal();
586
+ }
587
+ return cachedCapabilities;
588
+ }
589
+
590
+ /**
591
+ * Clear the cached terminal capabilities.
592
+ * Useful for testing or when terminal conditions change.
593
+ */
594
+ export function clearTerminalCapabilitiesCache(): void {
595
+ cachedCapabilities = null;
596
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Safe Split Point Detection for Markdown Text
3
+ *
4
+ * Finds safe points to split streaming markdown text without
5
+ * breaking code blocks or creating awkward visual splits.
6
+ *
7
+ * Pattern adapted from Gemini CLI: useGeminiStream.ts
8
+ *
9
+ * @module tui/utils/findLastSafeSplitPoint
10
+ */
11
+
12
+ /**
13
+ * Detect if an index is inside a code block.
14
+ *
15
+ * Searches backwards from the given index to find any opening ```.
16
+ * If found, checks if there's a closing ``` after it before our index.
17
+ *
18
+ * @param content - The text to analyze
19
+ * @param index - The index to check
20
+ * @returns true if inside a code block
21
+ */
22
+ function isIndexInsideCodeBlock(content: string, index: number): boolean {
23
+ // Find all code block markers before the index
24
+ const textBefore = content.slice(0, index);
25
+ const markers = [...textBefore.matchAll(/```/g)];
26
+
27
+ // If odd number of markers, we're inside a code block
28
+ return markers.length % 2 !== 0;
29
+ }
30
+
31
+ /**
32
+ * Find the start of the enclosing code block (if any).
33
+ *
34
+ * @param content - The text to analyze
35
+ * @param index - The index to check
36
+ * @returns Start index of enclosing code block, or -1 if not inside one
37
+ */
38
+ function findEnclosingCodeBlockStart(content: string, index: number): number {
39
+ if (!isIndexInsideCodeBlock(content, index)) {
40
+ return -1;
41
+ }
42
+
43
+ // Find the last opening ``` before the index
44
+ const textBefore = content.slice(0, index);
45
+ const lastOpening = textBefore.lastIndexOf("```");
46
+ return lastOpening;
47
+ }
48
+
49
+ /**
50
+ * Find the last safe point to split markdown text.
51
+ *
52
+ * Newline-gated strategy (like Codex/Gemini CLI):
53
+ * - Only splits at double newlines (\n\n) - paragraph boundaries
54
+ * - Never splits inside code blocks
55
+ * - No arbitrary character limit - waits for natural break points
56
+ *
57
+ * This prevents visual jitter from mid-sentence or mid-paragraph splits.
58
+ *
59
+ * @param text - The text to analyze
60
+ * @param _minLength - Deprecated, kept for API compatibility (ignored)
61
+ * @returns Split index after \n\n, or -1 if no safe split found
62
+ */
63
+ export function findLastSafeSplitPoint(text: string, _minLength = 0): number {
64
+ // Check if we're currently inside a code block
65
+ const enclosingBlockStart = findEnclosingCodeBlockStart(text, text.length);
66
+ if (enclosingBlockStart !== -1) {
67
+ // End of content is inside a code block - split right before it
68
+ return enclosingBlockStart;
69
+ }
70
+
71
+ // Search for the last double newline (\n\n) not inside a code block
72
+ let searchStartIndex = text.length;
73
+ while (searchStartIndex >= 0) {
74
+ const dnlIndex = text.lastIndexOf("\n\n", searchStartIndex);
75
+ if (dnlIndex === -1) {
76
+ // No more double newlines found
77
+ break;
78
+ }
79
+
80
+ const potentialSplitPoint = dnlIndex + 2;
81
+ if (!isIndexInsideCodeBlock(text, potentialSplitPoint)) {
82
+ return potentialSplitPoint;
83
+ }
84
+
85
+ // If potentialSplitPoint was inside a code block,
86
+ // search before the \n\n we just found
87
+ searchStartIndex = dnlIndex - 1;
88
+ }
89
+
90
+ // No safe split point found - don't split
91
+ return -1;
92
+ }