@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,498 @@
1
+ /**
2
+ * MarkdownRenderer Component (T020)
3
+ *
4
+ * Renders markdown content with theme-aware styling for TUI display.
5
+ * Supports headers, bold, italic, inline code, code blocks, lists, and links.
6
+ *
7
+ * @module tui/components/Messages/MarkdownRenderer
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import type React from "react";
12
+ import { useMemo } from "react";
13
+ import { useAnimation } from "../../context/AnimationContext.js";
14
+ import { useDiffMode } from "../../hooks/useDiffMode.js";
15
+ import { useTheme } from "../../theme/index.js";
16
+ import { sanitize } from "../../utils/textSanitizer.js";
17
+ import { DiffView } from "./DiffView.js";
18
+
19
+ // =============================================================================
20
+ // Types
21
+ // =============================================================================
22
+
23
+ /**
24
+ * Props for the MarkdownRenderer component.
25
+ */
26
+ export interface MarkdownRendererProps {
27
+ /** The markdown content to render */
28
+ readonly content: string;
29
+ /** Reduce spacing for compact display (default: false) */
30
+ readonly compact?: boolean;
31
+ /** Override base text color (optional) */
32
+ readonly textColor?: string;
33
+ /** Whether content is currently streaming (optional) */
34
+ readonly isStreaming?: boolean;
35
+ /** Cursor character while streaming (default: '▊') */
36
+ readonly cursorChar?: string;
37
+ /** Whether cursor should blink while streaming (default: true) */
38
+ readonly cursorBlink?: boolean;
39
+ }
40
+
41
+ /**
42
+ * Parsed markdown node types
43
+ */
44
+ type MarkdownNodeType =
45
+ | "text"
46
+ | "header"
47
+ | "bold"
48
+ | "italic"
49
+ | "inline-code"
50
+ | "code-block"
51
+ | "list-item"
52
+ | "link"
53
+ | "paragraph";
54
+
55
+ /**
56
+ * Parsed markdown node
57
+ */
58
+ interface MarkdownNode {
59
+ readonly type: MarkdownNodeType;
60
+ readonly content: string;
61
+ readonly level?: number; // For headers (1-6)
62
+ readonly language?: string; // For code blocks
63
+ readonly url?: string; // For links
64
+ readonly ordered?: boolean; // For list items
65
+ readonly index?: number; // For ordered list items
66
+ }
67
+
68
+ // =============================================================================
69
+ // Parsing Functions
70
+ // =============================================================================
71
+
72
+ /**
73
+ * Parse markdown content into nodes
74
+ */
75
+ function parseMarkdown(content: string): MarkdownNode[] {
76
+ const nodes: MarkdownNode[] = [];
77
+ const lines = content.split("\n");
78
+
79
+ let inCodeBlock = false;
80
+ let codeBlockLanguage = "";
81
+ let codeBlockContent: string[] = [];
82
+
83
+ for (const line of lines) {
84
+ // Code block start/end
85
+ if (line.startsWith("```")) {
86
+ if (inCodeBlock) {
87
+ // End code block
88
+ nodes.push({
89
+ type: "code-block",
90
+ content: codeBlockContent.join("\n"),
91
+ language: codeBlockLanguage,
92
+ });
93
+ inCodeBlock = false;
94
+ codeBlockContent = [];
95
+ codeBlockLanguage = "";
96
+ } else {
97
+ // Start code block
98
+ inCodeBlock = true;
99
+ codeBlockLanguage = line.slice(3).trim();
100
+ }
101
+ continue;
102
+ }
103
+
104
+ if (inCodeBlock) {
105
+ codeBlockContent.push(line);
106
+ continue;
107
+ }
108
+
109
+ // Empty line - paragraph break
110
+ if (line.trim() === "") {
111
+ continue;
112
+ }
113
+
114
+ // Headers
115
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
116
+ if (headerMatch?.[1] && headerMatch[2]) {
117
+ nodes.push({
118
+ type: "header",
119
+ content: headerMatch[2],
120
+ level: headerMatch[1].length,
121
+ });
122
+ continue;
123
+ }
124
+
125
+ // Unordered list items
126
+ const unorderedListMatch = line.match(/^(\s*)[-*+]\s+(.+)$/);
127
+ if (unorderedListMatch?.[2]) {
128
+ nodes.push({
129
+ type: "list-item",
130
+ content: unorderedListMatch[2],
131
+ ordered: false,
132
+ level: Math.floor((unorderedListMatch[1]?.length ?? 0) / 2),
133
+ });
134
+ continue;
135
+ }
136
+
137
+ // Ordered list items
138
+ const orderedListMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/);
139
+ if (orderedListMatch?.[2] && orderedListMatch[3]) {
140
+ nodes.push({
141
+ type: "list-item",
142
+ content: orderedListMatch[3],
143
+ ordered: true,
144
+ index: Number.parseInt(orderedListMatch[2], 10),
145
+ level: Math.floor((orderedListMatch[1]?.length ?? 0) / 2),
146
+ });
147
+ continue;
148
+ }
149
+
150
+ // Regular paragraph
151
+ nodes.push({
152
+ type: "paragraph",
153
+ content: line,
154
+ });
155
+ }
156
+
157
+ // Handle unclosed code block
158
+ if (inCodeBlock && codeBlockContent.length > 0) {
159
+ nodes.push({
160
+ type: "code-block",
161
+ content: codeBlockContent.join("\n"),
162
+ language: codeBlockLanguage,
163
+ });
164
+ }
165
+
166
+ return nodes;
167
+ }
168
+
169
+ /**
170
+ * Parse inline markdown elements (bold, italic, code, links)
171
+ */
172
+ interface InlineElement {
173
+ readonly type: "text" | "bold" | "italic" | "inline-code" | "link";
174
+ readonly content: string;
175
+ readonly url?: string;
176
+ }
177
+
178
+ /** Process a regex match and return the corresponding inline element */
179
+ function processInlineMatch(match: RegExpMatchArray): InlineElement {
180
+ if (match[1] || match[3]) {
181
+ return { type: "bold", content: match[2] ?? match[4] ?? "" };
182
+ }
183
+ if (match[5] || match[7]) {
184
+ return { type: "italic", content: match[6] ?? match[8] ?? "" };
185
+ }
186
+ if (match[9]) {
187
+ return { type: "inline-code", content: match[10] ?? "" };
188
+ }
189
+ if (match[11]) {
190
+ return { type: "link", content: match[12] ?? "", url: match[13] ?? "" };
191
+ }
192
+ return { type: "text", content: match[0] };
193
+ }
194
+
195
+ function parseInline(text: string): InlineElement[] {
196
+ const elements: InlineElement[] = [];
197
+ const inlineRegex =
198
+ /(\*\*(.+?)\*\*)|(__(.+?)__)|(\*(.+?)\*)|(_([^_]+)_)|(`([^`]+)`)|(\[([^\]]+)\]\(([^)]+)\))/g;
199
+
200
+ let lastIndex = 0;
201
+ const matches = [...text.matchAll(inlineRegex)];
202
+
203
+ for (const match of matches) {
204
+ const matchIndex = match.index ?? 0;
205
+
206
+ if (matchIndex > lastIndex) {
207
+ elements.push({ type: "text", content: text.slice(lastIndex, matchIndex) });
208
+ }
209
+
210
+ elements.push(processInlineMatch(match));
211
+ lastIndex = matchIndex + match[0].length;
212
+ }
213
+
214
+ if (lastIndex < text.length) {
215
+ elements.push({ type: "text", content: text.slice(lastIndex) });
216
+ }
217
+
218
+ if (elements.length === 0) {
219
+ elements.push({ type: "text", content: text });
220
+ }
221
+
222
+ return elements;
223
+ }
224
+
225
+ // =============================================================================
226
+ // Rendering Components
227
+ // =============================================================================
228
+
229
+ /**
230
+ * Render inline elements with appropriate styling
231
+ */
232
+ function InlineRenderer({
233
+ elements,
234
+ textColor,
235
+ codeColor,
236
+ codeBgColor,
237
+ linkColor,
238
+ }: {
239
+ readonly elements: InlineElement[];
240
+ readonly textColor: string;
241
+ readonly codeColor: string;
242
+ readonly codeBgColor: string;
243
+ readonly linkColor: string;
244
+ }): React.JSX.Element {
245
+ return (
246
+ <Text>
247
+ {elements.map((element, index) => {
248
+ // Use a composite key since elements are static after parsing
249
+ const elementKey = `${element.type}-${index}`;
250
+ switch (element.type) {
251
+ case "bold":
252
+ return (
253
+ <Text key={elementKey} bold color={textColor}>
254
+ {element.content}
255
+ </Text>
256
+ );
257
+ case "italic":
258
+ return (
259
+ <Text key={elementKey} italic color={textColor}>
260
+ {element.content}
261
+ </Text>
262
+ );
263
+ case "inline-code":
264
+ return (
265
+ <Text key={elementKey} backgroundColor={codeBgColor} color={codeColor}>
266
+ {` ${element.content} `}
267
+ </Text>
268
+ );
269
+ case "link":
270
+ return (
271
+ <Text key={elementKey} color={linkColor} underline>
272
+ {element.content}
273
+ {element.url && <Text color={codeColor}> ({element.url})</Text>}
274
+ </Text>
275
+ );
276
+ default:
277
+ return (
278
+ <Text key={elementKey} color={textColor}>
279
+ {element.content}
280
+ </Text>
281
+ );
282
+ }
283
+ })}
284
+ </Text>
285
+ );
286
+ }
287
+
288
+ /**
289
+ * Render a code block with basic styling
290
+ * Note: Full syntax highlighting is handled by CodeBlock component (T021)
291
+ */
292
+ function CodeBlockRenderer({
293
+ content,
294
+ language,
295
+ bgColor,
296
+ textColor,
297
+ mutedColor,
298
+ compact,
299
+ }: {
300
+ readonly content: string;
301
+ readonly language?: string;
302
+ readonly bgColor: string;
303
+ readonly textColor: string;
304
+ readonly mutedColor: string;
305
+ readonly compact: boolean;
306
+ }): React.JSX.Element {
307
+ return (
308
+ <Box
309
+ flexDirection="column"
310
+ marginTop={compact ? 0 : 1}
311
+ marginBottom={compact ? 0 : 1}
312
+ paddingX={1}
313
+ >
314
+ {language && (
315
+ <Text color={mutedColor} italic>
316
+ {language}
317
+ </Text>
318
+ )}
319
+ <Box borderStyle="single" borderColor={bgColor} paddingX={1}>
320
+ <Text color={textColor}>{content}</Text>
321
+ </Box>
322
+ </Box>
323
+ );
324
+ }
325
+
326
+ /**
327
+ * Detect if content looks like a unified diff.
328
+ * Checks for diff language marker, hunk headers (@@), or file headers (--- / +++).
329
+ */
330
+ function isDiffContent(content: string, language?: string): boolean {
331
+ if (language === "diff") {
332
+ return true;
333
+ }
334
+ // Check for unified diff patterns: @@ hunk headers or file headers
335
+ const lines = content.split("\n");
336
+ const hasHunkHeader = lines.some((line) => /^@@\s+-\d+/.test(line));
337
+ const hasFileHeaders =
338
+ lines.some((line) => line.startsWith("---")) && lines.some((line) => line.startsWith("+++"));
339
+ return hasHunkHeader || hasFileHeaders;
340
+ }
341
+
342
+ // =============================================================================
343
+ // Main Component
344
+ // =============================================================================
345
+
346
+ /**
347
+ * MarkdownRenderer displays markdown content with theme-aware styling.
348
+ *
349
+ * Features:
350
+ * - Headers (# ## ###) with bold styling and size indication
351
+ * - Bold (**text**) with bold styling
352
+ * - Italic (*text* or _text_) with dim styling
353
+ * - Inline code (`code`) with inverse styling
354
+ * - Code blocks (```) with border and optional language label
355
+ * - Lists (- or 1.) with proper indentation
356
+ * - Links [text](url) with underline styling
357
+ *
358
+ * @example
359
+ * ```tsx
360
+ * // Basic usage
361
+ * <MarkdownRenderer content="# Hello **world**" />
362
+ *
363
+ * // Compact mode
364
+ * <MarkdownRenderer content={markdown} compact />
365
+ * ```
366
+ */
367
+ export function MarkdownRenderer({
368
+ content,
369
+ compact = false,
370
+ textColor: textColorOverride,
371
+ isStreaming = false,
372
+ cursorChar = "▊",
373
+ cursorBlink = true,
374
+ }: MarkdownRendererProps): React.JSX.Element {
375
+ const { theme } = useTheme();
376
+ const { mode: diffMode } = useDiffMode();
377
+ const { frame, isPaused } = useAnimation();
378
+
379
+ // Theme colors
380
+ const textColor = textColorOverride ?? theme.semantic.text.primary;
381
+ const mutedColor = theme.semantic.text.muted;
382
+ const codeColor = theme.semantic.syntax.keyword;
383
+ const codeBgColor = theme.semantic.background.code;
384
+ const linkColor = theme.colors.info;
385
+
386
+ // Sanitize content before parsing (normalize line endings, strip dangerous ANSI)
387
+ const sanitizedContent = useMemo(() => sanitize(content), [content]);
388
+
389
+ // Parse markdown content
390
+ const nodes = useMemo(() => parseMarkdown(sanitizedContent), [sanitizedContent]);
391
+
392
+ const cursorVisible = useMemo(() => {
393
+ if (!isStreaming || !cursorBlink) return true;
394
+ if (isPaused) return true;
395
+ return Math.floor(frame / 4) % 2 === 0;
396
+ }, [frame, isPaused, isStreaming, cursorBlink]);
397
+
398
+ const cursor = isStreaming && cursorVisible ? cursorChar : "";
399
+ const lastNodeIndex = nodes.length - 1;
400
+
401
+ if (nodes.length === 0) {
402
+ return <Box flexDirection="column">{cursor && <Text color={textColor}>{cursor}</Text>}</Box>;
403
+ }
404
+
405
+ return (
406
+ <Box flexDirection="column">
407
+ {/* biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Markdown node renderer with many node types */}
408
+ {nodes.map((node, index) => {
409
+ const key = `${node.type}-${index}`;
410
+ const isLastNode = index === lastNodeIndex;
411
+
412
+ switch (node.type) {
413
+ case "header": {
414
+ // Headers: bold, with visual indicator for level
415
+ const prefix = "─".repeat(Math.max(1, 4 - (node.level ?? 1)));
416
+ return (
417
+ <Box key={key} marginTop={compact ? 0 : 1} marginBottom={compact ? 0 : 1}>
418
+ <Text bold color={textColor}>
419
+ {prefix} {node.content}
420
+ {isLastNode && cursor}
421
+ </Text>
422
+ </Box>
423
+ );
424
+ }
425
+
426
+ case "code-block": {
427
+ // Check if this is a diff and render with DiffView
428
+ if (isDiffContent(node.content, node.language)) {
429
+ return (
430
+ <Box key={key} marginTop={compact ? 0 : 1} marginBottom={compact ? 0 : 1}>
431
+ <DiffView diff={node.content} compact={compact} mode={diffMode} />
432
+ {isLastNode && cursor && <Text color={textColor}>{cursor}</Text>}
433
+ </Box>
434
+ );
435
+ }
436
+ return (
437
+ <Box key={key} marginTop={compact ? 0 : 1} marginBottom={compact ? 0 : 1}>
438
+ <CodeBlockRenderer
439
+ content={node.content}
440
+ language={node.language}
441
+ bgColor={mutedColor}
442
+ textColor={textColor}
443
+ mutedColor={mutedColor}
444
+ compact={compact}
445
+ />
446
+ {isLastNode && cursor && <Text color={textColor}>{cursor}</Text>}
447
+ </Box>
448
+ );
449
+ }
450
+
451
+ case "list-item": {
452
+ const indent = " ".repeat(node.level ?? 0);
453
+ const bullet = node.ordered ? `${node.index ?? 1}.` : "•";
454
+ const inlineElements = parseInline(node.content);
455
+
456
+ return (
457
+ <Box key={key} marginLeft={1}>
458
+ <Text>
459
+ <Text color={mutedColor}>
460
+ {indent}
461
+ {bullet}{" "}
462
+ </Text>
463
+ <InlineRenderer
464
+ elements={inlineElements}
465
+ textColor={textColor}
466
+ codeColor={codeColor}
467
+ codeBgColor={codeBgColor}
468
+ linkColor={linkColor}
469
+ />
470
+ {isLastNode && cursor && <Text color={textColor}>{cursor}</Text>}
471
+ </Text>
472
+ </Box>
473
+ );
474
+ }
475
+
476
+ default: {
477
+ const inlineElements = parseInline(node.content);
478
+
479
+ return (
480
+ <Box key={key} marginBottom={compact ? 0 : 0}>
481
+ <Text wrap="wrap">
482
+ <InlineRenderer
483
+ elements={inlineElements}
484
+ textColor={textColor}
485
+ codeColor={codeColor}
486
+ codeBgColor={codeBgColor}
487
+ linkColor={linkColor}
488
+ />
489
+ {isLastNode && cursor && <Text color={textColor}>{cursor}</Text>}
490
+ </Text>
491
+ </Box>
492
+ );
493
+ }
494
+ }
495
+ })}
496
+ </Box>
497
+ );
498
+ }