@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,565 @@
1
+ /**
2
+ * Markdown Renderer Service
3
+ *
4
+ * Provides marked-based Markdown parsing with ANSI terminal output.
5
+ * Integrates with the syntax-highlighter service for code block highlighting.
6
+ *
7
+ * @module tui/services/markdown-renderer
8
+ */
9
+
10
+ import chalk from "chalk";
11
+ import { Marked, type Tokens } from "marked";
12
+ import { highlightCode, highlightCodeSync, isHighlighterReady } from "./syntax-highlighter.js";
13
+
14
+ // =============================================================================
15
+ // Types
16
+ // =============================================================================
17
+
18
+ /**
19
+ * Options for markdown rendering
20
+ */
21
+ export interface MarkdownRenderOptions {
22
+ /** Whether to use compact mode (less spacing) */
23
+ readonly compact?: boolean;
24
+ /** Custom colors for rendering */
25
+ readonly colors?: Partial<MarkdownColors>;
26
+ /** Whether to include syntax highlighting for code blocks */
27
+ readonly syntaxHighlight?: boolean;
28
+ }
29
+
30
+ /**
31
+ * Color configuration for markdown elements
32
+ */
33
+ export interface MarkdownColors {
34
+ /** Heading color */
35
+ readonly heading: string;
36
+ /** Bold text color */
37
+ readonly bold: string;
38
+ /** Italic text color */
39
+ readonly italic: string;
40
+ /** Inline code color */
41
+ readonly inlineCode: string;
42
+ /** Inline code background */
43
+ readonly inlineCodeBg: string;
44
+ /** Link text color */
45
+ readonly link: string;
46
+ /** Link URL color */
47
+ readonly linkUrl: string;
48
+ /** Blockquote color */
49
+ readonly blockquote: string;
50
+ /** List bullet color */
51
+ readonly listBullet: string;
52
+ /** Horizontal rule color */
53
+ readonly hr: string;
54
+ /** Code block border color */
55
+ readonly codeBlockBorder: string;
56
+ /** Code language label color */
57
+ readonly codeLanguage: string;
58
+ }
59
+
60
+ // =============================================================================
61
+ // Constants
62
+ // =============================================================================
63
+
64
+ /**
65
+ * Default colors for markdown rendering
66
+ */
67
+ const DEFAULT_COLORS: MarkdownColors = {
68
+ heading: "#58a6ff", // GitHub blue
69
+ bold: "#ffffff",
70
+ italic: "#8b949e",
71
+ inlineCode: "#f0883e",
72
+ inlineCodeBg: "#161b22",
73
+ link: "#58a6ff",
74
+ linkUrl: "#8b949e",
75
+ blockquote: "#8b949e",
76
+ listBullet: "#8b949e",
77
+ hr: "#30363d",
78
+ codeBlockBorder: "#30363d",
79
+ codeLanguage: "#8b949e",
80
+ };
81
+
82
+ /**
83
+ * Heading level prefixes for visual hierarchy
84
+ */
85
+ const HEADING_PREFIXES: Record<number, string> = {
86
+ 1: "═══",
87
+ 2: "───",
88
+ 3: "──",
89
+ 4: "─",
90
+ 5: "·",
91
+ 6: "·",
92
+ };
93
+
94
+ // =============================================================================
95
+ // Rendering State
96
+ // =============================================================================
97
+
98
+ /** Store for pending async code blocks during sync render */
99
+ const pendingCodeBlocks: Map<string, { code: string; lang: string }> = new Map();
100
+
101
+ // =============================================================================
102
+ // Marked Renderer Factory
103
+ // =============================================================================
104
+
105
+ /**
106
+ * Create a marked renderer with ANSI terminal output
107
+ */
108
+ function createTerminalRenderer(
109
+ options: MarkdownRenderOptions = {},
110
+ codeBlockResolver?: (id: string, code: string, lang: string) => string
111
+ ): {
112
+ renderer: Partial<import("marked").RendererObject>;
113
+ parseInline: (tokens: Tokens.Generic[]) => string;
114
+ } {
115
+ const colors = { ...DEFAULT_COLORS, ...options.colors };
116
+ const compact = options.compact ?? false;
117
+ const syntaxHighlight = options.syntaxHighlight ?? true;
118
+
119
+ const colorFn = {
120
+ heading: chalk.hex(colors.heading),
121
+ bold: chalk.hex(colors.bold).bold,
122
+ italic: chalk.hex(colors.italic).italic,
123
+ inlineCode: chalk.hex(colors.inlineCode),
124
+ inlineCodeBg: chalk.bgHex(colors.inlineCodeBg).hex(colors.inlineCode),
125
+ link: chalk.hex(colors.link).underline,
126
+ linkUrl: chalk.hex(colors.linkUrl).dim,
127
+ blockquote: chalk.hex(colors.blockquote),
128
+ listBullet: chalk.hex(colors.listBullet),
129
+ hr: chalk.hex(colors.hr),
130
+ codeBlockBorder: chalk.hex(colors.codeBlockBorder),
131
+ codeLanguage: chalk.hex(colors.codeLanguage).italic,
132
+ };
133
+
134
+ // Inline token parser
135
+ const parseInline = (tokens: Tokens.Generic[]): string => {
136
+ return tokens
137
+ .map((token) => {
138
+ switch (token.type) {
139
+ case "text":
140
+ return (token as Tokens.Text).text;
141
+ case "strong":
142
+ return colorFn.bold(parseInline((token as Tokens.Strong).tokens));
143
+ case "em":
144
+ return colorFn.italic(parseInline((token as Tokens.Em).tokens));
145
+ case "codespan":
146
+ return colorFn.inlineCodeBg(` ${(token as Tokens.Codespan).text} `);
147
+ case "link": {
148
+ const linkToken = token as Tokens.Link;
149
+ const text = parseInline(linkToken.tokens);
150
+ return `${colorFn.link(text)} ${colorFn.linkUrl(`(${linkToken.href})`)}`;
151
+ }
152
+ case "image": {
153
+ const imgToken = token as Tokens.Image;
154
+ return colorFn.linkUrl(`[image: ${imgToken.text || imgToken.href}]`);
155
+ }
156
+ case "br":
157
+ return "\n";
158
+ case "del":
159
+ return chalk.strikethrough(parseInline((token as Tokens.Del).tokens));
160
+ case "escape":
161
+ return (token as Tokens.Escape).text;
162
+ default:
163
+ // Fallback for unknown inline tokens
164
+ if ("text" in token && typeof token.text === "string") {
165
+ return token.text;
166
+ }
167
+ if ("tokens" in token && Array.isArray(token.tokens)) {
168
+ return parseInline(token.tokens as Tokens.Generic[]);
169
+ }
170
+ return "";
171
+ }
172
+ })
173
+ .join("");
174
+ };
175
+
176
+ const spacing = compact ? "\n" : "\n\n";
177
+
178
+ const renderer: Partial<import("marked").RendererObject> = {
179
+ // Headings: bold + colored with prefix
180
+ heading({ tokens, depth }: Tokens.Heading): string {
181
+ const text = parseInline(tokens);
182
+ const prefix = HEADING_PREFIXES[depth] ?? "·";
183
+ return colorFn.heading.bold(`${prefix} ${text}`) + spacing;
184
+ },
185
+
186
+ // Paragraphs
187
+ paragraph({ tokens }: Tokens.Paragraph): string {
188
+ return parseInline(tokens) + spacing;
189
+ },
190
+
191
+ // Bold text
192
+ strong({ tokens }: Tokens.Strong): string {
193
+ return colorFn.bold(parseInline(tokens));
194
+ },
195
+
196
+ // Italic text
197
+ em({ tokens }: Tokens.Em): string {
198
+ return colorFn.italic(parseInline(tokens));
199
+ },
200
+
201
+ // Inline code
202
+ codespan({ text }: Tokens.Codespan): string {
203
+ return colorFn.inlineCodeBg(` ${text} `);
204
+ },
205
+
206
+ // Code blocks with syntax highlighting
207
+ code({ text, lang }: Tokens.Code): string {
208
+ const language = lang || "text";
209
+
210
+ if (syntaxHighlight && codeBlockResolver) {
211
+ // Async mode: return placeholder only - formatting applied after highlighting
212
+ const id = `code-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
213
+ pendingCodeBlocks.set(id, { code: text, lang: language });
214
+ return codeBlockResolver(id, text, language);
215
+ }
216
+
217
+ // Sync mode: format immediately
218
+ const languageLabel = colorFn.codeLanguage(language);
219
+ const border = colorFn.codeBlockBorder("│");
220
+
221
+ let highlightedCode: string;
222
+
223
+ if (syntaxHighlight && isHighlighterReady()) {
224
+ // Sync mode: use cached highlighter
225
+ const result = highlightCodeSync(text, { lang: language });
226
+ highlightedCode = result?.output ?? text;
227
+ } else {
228
+ // Fallback: no highlighting
229
+ highlightedCode = text;
230
+ }
231
+
232
+ // Format code block with border
233
+ const lines = highlightedCode.split("\n");
234
+ const formattedLines = lines.map((line) => `${border} ${line}`).join("\n");
235
+ const topBorder = `${colorFn.codeBlockBorder("┌─")} ${languageLabel}`;
236
+ const bottomBorder = colorFn.codeBlockBorder("└─");
237
+
238
+ return `${topBorder}\n${formattedLines}\n${bottomBorder}${spacing}`;
239
+ },
240
+
241
+ // Links
242
+ link({ href, tokens }: Tokens.Link): string {
243
+ const text = parseInline(tokens);
244
+ return `${colorFn.link(text)} ${colorFn.linkUrl(`(${href})`)}`;
245
+ },
246
+
247
+ // Images
248
+ image({ href, text }: Tokens.Image): string {
249
+ return colorFn.linkUrl(`[image: ${text || href}]`);
250
+ },
251
+
252
+ // Blockquotes
253
+ blockquote({ tokens }: Tokens.Blockquote): string {
254
+ // Parse block content, then prefix each line
255
+ const content = tokens
256
+ .map((token) => {
257
+ if (token.type === "paragraph") {
258
+ return parseInline((token as Tokens.Paragraph).tokens);
259
+ }
260
+ return "";
261
+ })
262
+ .join("\n");
263
+ const lines = content.split("\n");
264
+ const prefixed = lines.map((line) => colorFn.blockquote(`│ ${line}`)).join("\n");
265
+ return prefixed + spacing;
266
+ },
267
+
268
+ // Unordered lists
269
+ list({ items, ordered, start }: Tokens.List): string {
270
+ const startNum = typeof start === "number" ? start : 1;
271
+ const result = items
272
+ .map((item, index) => {
273
+ const bullet = ordered
274
+ ? colorFn.listBullet(`${startNum + index}.`)
275
+ : colorFn.listBullet("•");
276
+ const indent = " ".repeat(0); // Top-level indent
277
+ const content = item.tokens
278
+ .map((token) => {
279
+ if (token.type === "text") {
280
+ // Handle loose list items
281
+ if ("tokens" in token && Array.isArray(token.tokens)) {
282
+ return parseInline(token.tokens as Tokens.Generic[]);
283
+ }
284
+ return (token as Tokens.Text).text;
285
+ }
286
+ if (token.type === "paragraph") {
287
+ return parseInline((token as Tokens.Paragraph).tokens);
288
+ }
289
+ if (token.type === "list") {
290
+ // Nested list - indent
291
+ const nestedItems = (token as Tokens.List).items
292
+ .map((nestedItem, nestedIndex) => {
293
+ const nestedBullet = (token as Tokens.List).ordered
294
+ ? colorFn.listBullet(`${nestedIndex + 1}.`)
295
+ : colorFn.listBullet("◦");
296
+ const nestedContent = nestedItem.tokens
297
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Nested list rendering requires this complexity
298
+ .map((t) => {
299
+ if (t.type === "text") {
300
+ if ("tokens" in t && Array.isArray(t.tokens)) {
301
+ return parseInline(t.tokens as Tokens.Generic[]);
302
+ }
303
+ return (t as Tokens.Text).text;
304
+ }
305
+ if (t.type === "paragraph") {
306
+ return parseInline((t as Tokens.Paragraph).tokens);
307
+ }
308
+ return "";
309
+ })
310
+ .join("");
311
+ return ` ${nestedBullet} ${nestedContent}`;
312
+ })
313
+ .join("\n");
314
+ return `\n${nestedItems}`;
315
+ }
316
+ return "";
317
+ })
318
+ .join("");
319
+ return `${indent}${bullet} ${content}`;
320
+ })
321
+ .join("\n");
322
+ return result + spacing;
323
+ },
324
+
325
+ // List items (handled by list)
326
+ listitem({ tokens }: Tokens.ListItem): string {
327
+ return tokens
328
+ .map((token) => {
329
+ if (token.type === "text") {
330
+ if ("tokens" in token && Array.isArray(token.tokens)) {
331
+ return parseInline(token.tokens as Tokens.Generic[]);
332
+ }
333
+ return (token as Tokens.Text).text;
334
+ }
335
+ if (token.type === "paragraph") {
336
+ return parseInline((token as Tokens.Paragraph).tokens);
337
+ }
338
+ return "";
339
+ })
340
+ .join("");
341
+ },
342
+
343
+ // Horizontal rules
344
+ hr(): string {
345
+ return colorFn.hr("─".repeat(40)) + spacing;
346
+ },
347
+
348
+ // Line breaks
349
+ br(): string {
350
+ return "\n";
351
+ },
352
+
353
+ // HTML (strip tags, show as plain text)
354
+ html(token: Tokens.HTML | Tokens.Tag): string {
355
+ // Strip HTML tags for terminal display
356
+ const text = token.text;
357
+ return text.replace(/<[^>]*>/g, "") + (text.includes("\n") ? "" : "");
358
+ },
359
+
360
+ // Tables
361
+ table({ header, rows }: Tokens.Table): string {
362
+ const headerCells = header.map((cell) => parseInline(cell.tokens));
363
+ const headerRow = colorFn.bold(headerCells.join(" │ "));
364
+ const separator = colorFn.hr("─".repeat(headerCells.join(" │ ").length));
365
+ const bodyRows = rows.map((row) => row.map((cell) => parseInline(cell.tokens)).join(" │ "));
366
+ return `${headerRow}\n${separator}\n${bodyRows.join("\n")}${spacing}`;
367
+ },
368
+
369
+ // Table row (handled by table)
370
+ tablerow({ text }: Tokens.TableRow): string {
371
+ return text;
372
+ },
373
+
374
+ // Table cell (handled by table)
375
+ tablecell({ tokens }: Tokens.TableCell): string {
376
+ return parseInline(tokens);
377
+ },
378
+
379
+ // Strikethrough
380
+ del({ tokens }: Tokens.Del): string {
381
+ return chalk.strikethrough(parseInline(tokens));
382
+ },
383
+
384
+ // Text
385
+ text(token: Tokens.Text | Tokens.Escape): string {
386
+ if ("tokens" in token && Array.isArray(token.tokens)) {
387
+ return parseInline(token.tokens as Tokens.Generic[]);
388
+ }
389
+ return token.text;
390
+ },
391
+ };
392
+
393
+ return { renderer, parseInline };
394
+ }
395
+
396
+ // =============================================================================
397
+ // Public API
398
+ // =============================================================================
399
+
400
+ /**
401
+ * Render markdown to ANSI terminal string (async version)
402
+ *
403
+ * Uses Shiki for syntax highlighting of code blocks.
404
+ *
405
+ * @param markdown - The markdown string to render
406
+ * @param options - Rendering options
407
+ * @returns Promise resolving to ANSI-formatted string
408
+ *
409
+ * @example
410
+ * ```ts
411
+ * const output = await renderMarkdown('# Hello **world**');
412
+ * console.log(output);
413
+ * ```
414
+ */
415
+ export async function renderMarkdown(
416
+ markdown: string,
417
+ options: MarkdownRenderOptions = {}
418
+ ): Promise<string> {
419
+ // Collect code blocks for async highlighting
420
+ const codeBlocks: Map<string, { code: string; lang: string }> = new Map();
421
+ const colors = { ...DEFAULT_COLORS, ...options.colors };
422
+ const spacing = options.compact ? "\n" : "\n\n";
423
+
424
+ const colorFn = {
425
+ codeBlockBorder: chalk.hex(colors.codeBlockBorder),
426
+ codeLanguage: chalk.hex(colors.codeLanguage).italic,
427
+ };
428
+
429
+ const { renderer } = createTerminalRenderer(options, (id, code, lang) => {
430
+ codeBlocks.set(id, { code, lang });
431
+ // Return just the placeholder - formatting will be applied after highlighting
432
+ return `__CODE_BLOCK_${id}__`;
433
+ });
434
+
435
+ const marked = new Marked({ renderer, async: false, gfm: true, breaks: true });
436
+ let result = marked.parse(markdown) as string;
437
+
438
+ // Highlight code blocks asynchronously
439
+ if (options.syntaxHighlight !== false && codeBlocks.size > 0) {
440
+ const highlights = await Promise.all(
441
+ Array.from(codeBlocks.entries()).map(async ([id, { code, lang }]) => {
442
+ const highlighted = await highlightCode(code, { lang });
443
+ return { id, output: highlighted.output, lang };
444
+ })
445
+ );
446
+
447
+ // Replace placeholders with highlighted code (with formatting)
448
+ for (const { id, output, lang } of highlights) {
449
+ const border = colorFn.codeBlockBorder("│");
450
+ const lines = output.split("\n");
451
+ const formattedLines = lines.map((line) => `${border} ${line}`).join("\n");
452
+ const topBorder = `${colorFn.codeBlockBorder("┌─")} ${colorFn.codeLanguage(lang)}`;
453
+ const bottomBorder = colorFn.codeBlockBorder("└─");
454
+ const codeBlock = `${topBorder}\n${formattedLines}\n${bottomBorder}${spacing}`;
455
+
456
+ result = result.replace(`__CODE_BLOCK_${id}__`, codeBlock);
457
+ }
458
+ }
459
+
460
+ // Clean up extra newlines
461
+ return result.replace(/\n{3,}/g, "\n\n").trim();
462
+ }
463
+
464
+ /**
465
+ * Render markdown to ANSI terminal string (sync version)
466
+ *
467
+ * Uses cached Shiki highlighter if available, otherwise falls back to plain text.
468
+ * Prefer the async version for initial renders.
469
+ *
470
+ * @param markdown - The markdown string to render
471
+ * @param options - Rendering options
472
+ * @returns ANSI-formatted string
473
+ *
474
+ * @example
475
+ * ```ts
476
+ * const output = renderMarkdownSync('# Hello **world**');
477
+ * console.log(output);
478
+ * ```
479
+ */
480
+ export function renderMarkdownSync(markdown: string, options: MarkdownRenderOptions = {}): string {
481
+ const { renderer } = createTerminalRenderer(options);
482
+ const marked = new Marked({ renderer, async: false, gfm: true, breaks: true });
483
+ const result = marked.parse(markdown) as string;
484
+ return result.replace(/\n{3,}/g, "\n\n").trim();
485
+ }
486
+
487
+ /**
488
+ * Render markdown without syntax highlighting (fast sync version)
489
+ *
490
+ * Useful for real-time streaming where highlighting latency is unacceptable.
491
+ *
492
+ * @param markdown - The markdown string to render
493
+ * @param options - Rendering options (syntaxHighlight is ignored)
494
+ * @returns ANSI-formatted string
495
+ */
496
+ export function renderMarkdownPlain(
497
+ markdown: string,
498
+ options: Omit<MarkdownRenderOptions, "syntaxHighlight"> = {}
499
+ ): string {
500
+ return renderMarkdownSync(markdown, { ...options, syntaxHighlight: false });
501
+ }
502
+
503
+ /**
504
+ * Check if a string contains markdown formatting
505
+ *
506
+ * @param text - The text to check
507
+ * @returns True if the text appears to contain markdown
508
+ */
509
+ export function containsMarkdown(text: string): boolean {
510
+ // Check for common markdown patterns
511
+ const patterns = [
512
+ /^#{1,6}\s+/m, // Headers
513
+ /\*\*[^*]+\*\*/, // Bold
514
+ /\*[^*]+\*/, // Italic
515
+ /__[^_]+__/, // Bold (underscore)
516
+ /_[^_]+_/, // Italic (underscore)
517
+ /`[^`]+`/, // Inline code
518
+ /```[\s\S]*?```/, // Code blocks
519
+ /^\s*[-*+]\s+/m, // Unordered lists
520
+ /^\s*\d+\.\s+/m, // Ordered lists
521
+ /\[[^\]]+\]\([^)]+\)/, // Links
522
+ /^>\s+/m, // Blockquotes
523
+ /^---+$/m, // Horizontal rules
524
+ /^\|.*\|$/m, // Tables
525
+ ];
526
+
527
+ return patterns.some((pattern) => pattern.test(text));
528
+ }
529
+
530
+ /**
531
+ * Strip markdown formatting from text
532
+ *
533
+ * @param markdown - The markdown string
534
+ * @returns Plain text without markdown formatting
535
+ */
536
+ export function stripMarkdown(markdown: string): string {
537
+ return (
538
+ markdown
539
+ // Remove code blocks
540
+ .replace(/```[\s\S]*?```/g, "")
541
+ // Remove inline code
542
+ .replace(/`([^`]+)`/g, "$1")
543
+ // Remove bold/italic
544
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
545
+ .replace(/\*([^*]+)\*/g, "$1")
546
+ .replace(/__([^_]+)__/g, "$1")
547
+ .replace(/_([^_]+)_/g, "$1")
548
+ // Remove links
549
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
550
+ // Remove images
551
+ .replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1")
552
+ // Remove headers
553
+ .replace(/^#{1,6}\s+/gm, "")
554
+ // Remove blockquotes
555
+ .replace(/^>\s+/gm, "")
556
+ // Remove horizontal rules
557
+ .replace(/^---+$/gm, "")
558
+ // Remove list markers
559
+ .replace(/^\s*[-*+]\s+/gm, "")
560
+ .replace(/^\s*\d+\.\s+/gm, "")
561
+ // Clean up whitespace
562
+ .replace(/\n{3,}/g, "\n\n")
563
+ .trim()
564
+ );
565
+ }