@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,527 @@
1
+ /**
2
+ * CodeBlock Component (T021)
3
+ *
4
+ * Renders code with syntax highlighting, line numbers, and optional copy button.
5
+ * Supports Shiki-based highlighting (async) with regex-based fallback.
6
+ *
7
+ * @module tui/components/Messages/CodeBlock
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import type React from "react";
12
+ import { useCallback, useEffect, useMemo, useState } from "react";
13
+ import { useTUITranslation } from "../../i18n/index.js";
14
+ import {
15
+ highlightCodeSync,
16
+ isHighlighterReady,
17
+ preloadHighlighter,
18
+ } from "../../services/syntax-highlighter.js";
19
+ import { useTheme } from "../../theme/index.js";
20
+
21
+ // =============================================================================
22
+ // Types
23
+ // =============================================================================
24
+
25
+ /**
26
+ * Props for the CodeBlock component.
27
+ */
28
+ export interface CodeBlockProps {
29
+ /** The source code to display */
30
+ readonly code: string;
31
+ /** Programming language for syntax highlighting */
32
+ readonly language?: string;
33
+ /** Show line numbers on the left (default: false) */
34
+ readonly showLineNumbers?: boolean;
35
+ /** Show copy button in header (default: false) */
36
+ readonly showCopyButton?: boolean;
37
+ /** Maximum height in lines before scrolling (default: unlimited) */
38
+ readonly maxHeight?: number;
39
+ /** Line numbers to highlight (1-indexed) */
40
+ readonly highlight?: number[];
41
+ /** Use Shiki for highlighting (default: true if available) */
42
+ readonly useShiki?: boolean;
43
+ /** Callback when copy button is clicked */
44
+ readonly onCopy?: (code: string) => void;
45
+ }
46
+
47
+ /**
48
+ * Token types for syntax highlighting
49
+ */
50
+ type TokenType =
51
+ | "keyword"
52
+ | "string"
53
+ | "number"
54
+ | "comment"
55
+ | "function"
56
+ | "variable"
57
+ | "type"
58
+ | "operator"
59
+ | "punctuation"
60
+ | "plain";
61
+
62
+ /**
63
+ * A token with its type and content
64
+ */
65
+ interface Token {
66
+ readonly type: TokenType;
67
+ readonly content: string;
68
+ }
69
+
70
+ // =============================================================================
71
+ // Language Definitions
72
+ // =============================================================================
73
+
74
+ /**
75
+ * Language-specific patterns for syntax highlighting
76
+ */
77
+ interface LanguagePatterns {
78
+ readonly keywords: RegExp;
79
+ readonly strings: RegExp;
80
+ readonly numbers: RegExp;
81
+ readonly comments: RegExp;
82
+ readonly functions?: RegExp;
83
+ readonly types?: RegExp;
84
+ readonly operators?: RegExp;
85
+ }
86
+
87
+ /**
88
+ * Language pattern definitions
89
+ */
90
+ const LANGUAGE_PATTERNS: Record<string, LanguagePatterns> = {
91
+ javascript: {
92
+ keywords:
93
+ /\b(const|let|var|function|return|if|else|for|while|do|switch|case|break|continue|new|this|class|extends|import|export|from|default|async|await|try|catch|finally|throw|typeof|instanceof|in|of|null|undefined|true|false|void)\b/g,
94
+ strings: /(["'`])(?:(?!\1)[^\\]|\\.)*?\1/g,
95
+ numbers: /\b\d+\.?\d*(?:[eE][+-]?\d+)?\b/g,
96
+ comments: /(\/\/.*$)|(\/\*[\s\S]*?\*\/)/gm,
97
+ functions: /\b([a-zA-Z_]\w*)\s*(?=\()/g,
98
+ types: /\b([A-Z][a-zA-Z0-9]*)\b/g,
99
+ operators: /[+\-*/%=<>!&|^~?:]+/g,
100
+ },
101
+ typescript: {
102
+ keywords:
103
+ /\b(const|let|var|function|return|if|else|for|while|do|switch|case|break|continue|new|this|class|extends|implements|import|export|from|default|async|await|try|catch|finally|throw|typeof|instanceof|in|of|null|undefined|true|false|void|type|interface|enum|namespace|abstract|private|protected|public|static|readonly|as|is|keyof|infer|never|unknown|any)\b/g,
104
+ strings: /(["'`])(?:(?!\1)[^\\]|\\.)*?\1/g,
105
+ numbers: /\b\d+\.?\d*(?:[eE][+-]?\d+)?\b/g,
106
+ comments: /(\/\/.*$)|(\/\*[\s\S]*?\*\/)/gm,
107
+ functions: /\b([a-zA-Z_]\w*)\s*(?=\()/g,
108
+ types: /\b([A-Z][a-zA-Z0-9]*)\b/g,
109
+ operators: /[+\-*/%=<>!&|^~?:]+/g,
110
+ },
111
+ python: {
112
+ keywords:
113
+ /\b(def|class|return|if|elif|else|for|while|break|continue|pass|import|from|as|try|except|finally|raise|with|lambda|yield|global|nonlocal|assert|True|False|None|and|or|not|in|is|async|await)\b/g,
114
+ strings: /("""[\s\S]*?"""|'''[\s\S]*?'''|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,
115
+ numbers: /\b\d+\.?\d*(?:[eE][+-]?\d+)?[jJ]?\b/g,
116
+ comments: /#.*$/gm,
117
+ functions: /\b([a-zA-Z_]\w*)\s*(?=\()/g,
118
+ types: /\b([A-Z][a-zA-Z0-9]*)\b/g,
119
+ operators: /[+\-*/%=<>!&|^~@]+/g,
120
+ },
121
+ bash: {
122
+ keywords:
123
+ /\b(if|then|else|elif|fi|case|esac|for|while|until|do|done|in|function|return|exit|local|export|source|alias|unalias|set|unset|readonly|declare|typeset|shift|trap|exec|eval|test)\b/g,
124
+ strings: /(["'])(?:(?!\1)[^\\]|\\.)*?\1/g,
125
+ numbers: /\b\d+\b/g,
126
+ comments: /#.*$/gm,
127
+ functions: /\b([a-zA-Z_]\w*)\s*\(\)/g,
128
+ operators: /[|&;><]+/g,
129
+ },
130
+ json: {
131
+ keywords: /\b(true|false|null)\b/g,
132
+ strings: /"(?:[^"\\]|\\.)*"/g,
133
+ numbers: /-?\b\d+\.?\d*(?:[eE][+-]?\d+)?\b/g,
134
+ comments: /(?!)/g, // JSON has no comments
135
+ operators: /[{}[\]:,]/g,
136
+ },
137
+ shell: {
138
+ keywords:
139
+ /\b(if|then|else|elif|fi|case|esac|for|while|until|do|done|in|function|return|exit|local|export|source|alias|unalias|set|unset|readonly|declare|typeset|shift|trap|exec|eval|test)\b/g,
140
+ strings: /(["'])(?:(?!\1)[^\\]|\\.)*?\1/g,
141
+ numbers: /\b\d+\b/g,
142
+ comments: /#.*$/gm,
143
+ functions: /\b([a-zA-Z_]\w*)\s*\(\)/g,
144
+ operators: /[|&;><]+/g,
145
+ },
146
+ };
147
+
148
+ // Language aliases
149
+ const LANGUAGE_ALIASES: Record<string, string> = {
150
+ js: "javascript",
151
+ jsx: "javascript",
152
+ ts: "typescript",
153
+ tsx: "typescript",
154
+ py: "python",
155
+ sh: "bash",
156
+ zsh: "bash",
157
+ fish: "bash",
158
+ };
159
+
160
+ // =============================================================================
161
+ // Tokenizer
162
+ // =============================================================================
163
+
164
+ /**
165
+ * Tokenize a line of code for syntax highlighting
166
+ */
167
+ function tokenizeLine(line: string, language: string | undefined): Token[] {
168
+ if (!line) {
169
+ return [{ type: "plain", content: "" }];
170
+ }
171
+
172
+ // Normalize language name
173
+ const normalizedLang = language?.toLowerCase() ?? "";
174
+ const resolvedLang = LANGUAGE_ALIASES[normalizedLang] ?? normalizedLang;
175
+ const patterns = LANGUAGE_PATTERNS[resolvedLang];
176
+
177
+ // No patterns for this language - return plain text
178
+ if (!patterns) {
179
+ return [{ type: "plain", content: line }];
180
+ }
181
+
182
+ // Track which parts of the line have been tokenized
183
+ const tokens: Array<{ start: number; end: number; type: TokenType; content: string }> = [];
184
+
185
+ // Helper to add matches
186
+ const addMatches = (regex: RegExp, type: TokenType) => {
187
+ // Reset regex state
188
+ regex.lastIndex = 0;
189
+ const matches = line.matchAll(regex);
190
+ for (const match of matches) {
191
+ tokens.push({
192
+ start: match.index ?? 0,
193
+ end: (match.index ?? 0) + match[0].length,
194
+ type,
195
+ content: match[0],
196
+ });
197
+ }
198
+ };
199
+
200
+ // Order matters: comments first (they can contain anything)
201
+ addMatches(patterns.comments, "comment");
202
+ addMatches(patterns.strings, "string");
203
+ addMatches(patterns.numbers, "number");
204
+ addMatches(patterns.keywords, "keyword");
205
+ if (patterns.functions) addMatches(patterns.functions, "function");
206
+ if (patterns.types) addMatches(patterns.types, "type");
207
+ if (patterns.operators) addMatches(patterns.operators, "operator");
208
+
209
+ // Sort by start position
210
+ tokens.sort((a, b) => a.start - b.start);
211
+
212
+ // Remove overlapping tokens (keep first match - higher priority)
213
+ const nonOverlapping: typeof tokens = [];
214
+ for (const token of tokens) {
215
+ const hasOverlap = nonOverlapping.some(
216
+ (existing) =>
217
+ (token.start >= existing.start && token.start < existing.end) ||
218
+ (token.end > existing.start && token.end <= existing.end)
219
+ );
220
+ if (!hasOverlap) {
221
+ nonOverlapping.push(token);
222
+ }
223
+ }
224
+
225
+ // Build final token list with plain text gaps
226
+ const result: Token[] = [];
227
+ let lastEnd = 0;
228
+
229
+ for (const token of nonOverlapping) {
230
+ // Add plain text before this token
231
+ if (token.start > lastEnd) {
232
+ result.push({
233
+ type: "plain",
234
+ content: line.slice(lastEnd, token.start),
235
+ });
236
+ }
237
+ result.push({
238
+ type: token.type,
239
+ content: token.content,
240
+ });
241
+ lastEnd = token.end;
242
+ }
243
+
244
+ // Add remaining plain text
245
+ if (lastEnd < line.length) {
246
+ result.push({
247
+ type: "plain",
248
+ content: line.slice(lastEnd),
249
+ });
250
+ }
251
+
252
+ return result.length > 0 ? result : [{ type: "plain", content: line }];
253
+ }
254
+
255
+ // =============================================================================
256
+ // Sub-components
257
+ // =============================================================================
258
+
259
+ /**
260
+ * Render a single token with syntax highlighting
261
+ */
262
+ interface TokenRendererProps {
263
+ readonly token: Token;
264
+ }
265
+
266
+ function TokenRenderer({ token }: TokenRendererProps): React.ReactElement {
267
+ const { theme } = useTheme();
268
+ const syntax = theme.semantic.syntax;
269
+
270
+ const colorMap: Record<TokenType, string> = {
271
+ keyword: syntax.keyword,
272
+ string: syntax.string,
273
+ number: syntax.number,
274
+ comment: syntax.comment,
275
+ function: syntax.function,
276
+ variable: syntax.variable,
277
+ type: syntax.type,
278
+ operator: syntax.operator,
279
+ punctuation: syntax.punctuation,
280
+ plain: theme.semantic.text.primary,
281
+ };
282
+
283
+ return <Text color={colorMap[token.type]}>{token.content}</Text>;
284
+ }
285
+
286
+ /**
287
+ * Render a single line of code
288
+ */
289
+ interface CodeLineProps {
290
+ readonly lineNumber: number;
291
+ readonly content: string;
292
+ readonly language: string | undefined;
293
+ readonly showLineNumbers: boolean;
294
+ readonly isHighlighted: boolean;
295
+ readonly lineNumberWidth: number;
296
+ }
297
+
298
+ function CodeLine({
299
+ lineNumber,
300
+ content,
301
+ language,
302
+ showLineNumbers,
303
+ isHighlighted,
304
+ lineNumberWidth,
305
+ }: CodeLineProps): React.ReactElement {
306
+ const { theme } = useTheme();
307
+ const tokens = useMemo(() => tokenizeLine(content, language), [content, language]);
308
+
309
+ return (
310
+ <Box>
311
+ {showLineNumbers && (
312
+ <Box width={lineNumberWidth + 1} marginRight={1}>
313
+ <Text
314
+ color={isHighlighted ? theme.colors.warning : theme.semantic.text.muted}
315
+ dimColor={!isHighlighted}
316
+ >
317
+ {String(lineNumber).padStart(lineNumberWidth, " ")}
318
+ </Text>
319
+ <Text color={theme.semantic.border.muted}>│</Text>
320
+ </Box>
321
+ )}
322
+ <Box flexGrow={1}>
323
+ {isHighlighted && (
324
+ <Text backgroundColor={theme.semantic.background.elevated} color={theme.colors.warning}>
325
+ {"▶ "}
326
+ </Text>
327
+ )}
328
+ {tokens.map((token, index) => (
329
+ <TokenRenderer key={`${lineNumber}-${index}-${token.type}`} token={token} />
330
+ ))}
331
+ </Box>
332
+ </Box>
333
+ );
334
+ }
335
+
336
+ /**
337
+ * Code block header with language label and copy button
338
+ */
339
+ interface CodeHeaderProps {
340
+ readonly language: string | undefined;
341
+ readonly showCopyButton: boolean;
342
+ readonly onCopy?: () => void;
343
+ }
344
+
345
+ function CodeHeader({ language, showCopyButton }: CodeHeaderProps): React.ReactElement | null {
346
+ const { theme } = useTheme();
347
+ const { t } = useTUITranslation();
348
+
349
+ if (!language && !showCopyButton) {
350
+ return null;
351
+ }
352
+
353
+ return (
354
+ <Box
355
+ borderStyle="single"
356
+ borderColor={theme.semantic.border.default}
357
+ borderBottom={false}
358
+ paddingX={1}
359
+ justifyContent="space-between"
360
+ >
361
+ {language ? (
362
+ <Text color={theme.semantic.text.secondary} bold>
363
+ {language}
364
+ </Text>
365
+ ) : (
366
+ <Text> </Text>
367
+ )}
368
+ {showCopyButton && <Text color={theme.semantic.text.muted}>{t("code.copy")}</Text>}
369
+ </Box>
370
+ );
371
+ }
372
+
373
+ // =============================================================================
374
+ // Main Component
375
+ // =============================================================================
376
+
377
+ /**
378
+ * CodeBlock - Renders code with syntax highlighting
379
+ *
380
+ * Features:
381
+ * - Shiki-based syntax highlighting (when available)
382
+ * - Regex-based fallback highlighting for common languages
383
+ * - Optional line numbers
384
+ * - Line highlighting support
385
+ * - Copy button (when clipboard API available)
386
+ * - Scrollable for long code (respects maxHeight)
387
+ *
388
+ * @example
389
+ * ```tsx
390
+ * <CodeBlock
391
+ * code={sourceCode}
392
+ * language="typescript"
393
+ * showLineNumbers
394
+ * highlight={[5, 6, 7]}
395
+ * />
396
+ * ```
397
+ */
398
+ export function CodeBlock({
399
+ code,
400
+ language,
401
+ showLineNumbers = false,
402
+ showCopyButton = false,
403
+ maxHeight,
404
+ highlight = [],
405
+ useShiki = true,
406
+ onCopy,
407
+ }: CodeBlockProps): React.ReactElement {
408
+ const { theme } = useTheme();
409
+ const [shikiOutput, setShikiOutput] = useState<string | null>(null);
410
+ const [highlighterReady, setHighlighterReady] = useState(isHighlighterReady());
411
+
412
+ // Preload highlighter on mount
413
+ useEffect(() => {
414
+ if (useShiki && !highlighterReady) {
415
+ preloadHighlighter();
416
+ // Check periodically if highlighter is ready
417
+ const interval = setInterval(() => {
418
+ if (isHighlighterReady()) {
419
+ setHighlighterReady(true);
420
+ clearInterval(interval);
421
+ }
422
+ }, 100);
423
+ return () => clearInterval(interval);
424
+ }
425
+ }, [useShiki, highlighterReady]);
426
+
427
+ // Try Shiki highlighting when ready
428
+ useEffect(() => {
429
+ if (useShiki && highlighterReady) {
430
+ const result = highlightCodeSync(code, { lang: language });
431
+ if (result?.success) {
432
+ setShikiOutput(result.output);
433
+ }
434
+ }
435
+ }, [code, language, useShiki, highlighterReady]);
436
+
437
+ // Parse code into lines (for fallback rendering)
438
+ const lines = useMemo(() => {
439
+ const result = code.split("\n");
440
+ // Remove trailing empty line if present
441
+ if (result.length > 0 && result[result.length - 1] === "") {
442
+ result.pop();
443
+ }
444
+ return result;
445
+ }, [code]);
446
+
447
+ // Calculate line number width
448
+ const lineNumberWidth = useMemo(() => String(lines.length).length, [lines.length]);
449
+
450
+ // Set of highlighted lines for O(1) lookup
451
+ const highlightSet = useMemo(() => new Set(highlight), [highlight]);
452
+
453
+ // Apply maxHeight constraint
454
+ const displayLines = useMemo(() => {
455
+ if (maxHeight !== undefined && lines.length > maxHeight) {
456
+ return lines.slice(0, maxHeight);
457
+ }
458
+ return lines;
459
+ }, [lines, maxHeight]);
460
+
461
+ const hasMoreLines = maxHeight !== undefined && lines.length > maxHeight;
462
+
463
+ // Determine if header should be shown
464
+ const showHeader = Boolean(language) || showCopyButton;
465
+
466
+ // Handle copy
467
+ const handleCopy = useCallback(() => {
468
+ onCopy?.(code);
469
+ }, [code, onCopy]);
470
+
471
+ // Use Shiki output if available, otherwise fall back to regex highlighting
472
+ const useShikiOutput = shikiOutput !== null && !showLineNumbers && highlight.length === 0;
473
+
474
+ // For Shiki output with maxHeight, we need to truncate
475
+ const shikiLines = useMemo(() => {
476
+ if (!shikiOutput) return [];
477
+ const result = shikiOutput.split("\n");
478
+ if (maxHeight !== undefined && result.length > maxHeight) {
479
+ return result.slice(0, maxHeight);
480
+ }
481
+ return result;
482
+ }, [shikiOutput, maxHeight]);
483
+
484
+ return (
485
+ <Box flexDirection="column">
486
+ {showHeader && (
487
+ <CodeHeader language={language} showCopyButton={showCopyButton} onCopy={handleCopy} />
488
+ )}
489
+ <Box
490
+ flexDirection="column"
491
+ borderStyle="single"
492
+ borderColor={theme.semantic.border.default}
493
+ borderTop={!showHeader}
494
+ paddingX={1}
495
+ >
496
+ {useShikiOutput
497
+ ? // Shiki-highlighted output (pre-colored ANSI)
498
+ // biome-ignore lint/suspicious/noArrayIndexKey: shiki lines are stable ANSI output from highlighter
499
+ shikiLines.map((line, index) => <Text key={`shiki-line-${index}`}>{line}</Text>)
500
+ : // Fallback regex highlighting
501
+ displayLines.map((line, index) => {
502
+ const lineNumber = index + 1;
503
+ return (
504
+ <CodeLine
505
+ key={`line-${lineNumber}`}
506
+ lineNumber={lineNumber}
507
+ content={line}
508
+ language={language}
509
+ showLineNumbers={showLineNumbers}
510
+ isHighlighted={highlightSet.has(lineNumber)}
511
+ lineNumberWidth={lineNumberWidth}
512
+ />
513
+ );
514
+ })}
515
+ {hasMoreLines && (
516
+ <Box marginTop={0}>
517
+ <Text color={theme.semantic.text.muted} dimColor>
518
+ ... {lines.length - (maxHeight ?? 0)} more lines
519
+ </Text>
520
+ </Box>
521
+ )}
522
+ </Box>
523
+ </Box>
524
+ );
525
+ }
526
+
527
+ export default CodeBlock;