@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,305 @@
1
+ /**
2
+ * Enhanced Loading Indicator Component
3
+ *
4
+ * Advanced loading indicator with elapsed time, cancel hints,
5
+ * and configurable show/hide delays. Inspired by Gemini CLI.
6
+ *
7
+ * @module tui/components/common/EnhancedLoadingIndicator
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import type React from "react";
12
+ import { useCallback, useEffect, useRef, useState } from "react";
13
+ import { useTheme } from "../../theme/index.js";
14
+ import { Spinner } from "./Spinner.js";
15
+
16
+ // =============================================================================
17
+ // Types
18
+ // =============================================================================
19
+
20
+ /**
21
+ * Props for the EnhancedLoadingIndicator component.
22
+ */
23
+ export interface EnhancedLoadingIndicatorProps {
24
+ /** Whether the loading indicator is active */
25
+ readonly isLoading: boolean;
26
+ /** Label to display alongside the spinner */
27
+ readonly label?: string;
28
+ /** Whether to show elapsed time (default: true) */
29
+ readonly showElapsedTime?: boolean;
30
+ /** Whether to show cancel hint (default: true) */
31
+ readonly showCancelHint?: boolean;
32
+ /** Custom cancel hint text */
33
+ readonly cancelHint?: string;
34
+ /** Delay before showing the indicator in ms (default: 0) */
35
+ readonly showDelay?: number;
36
+ /** Delay before hiding the indicator in ms (default: 0) */
37
+ readonly hideDelay?: number;
38
+ /** Spinner animation frames */
39
+ readonly spinnerFrames?: readonly string[];
40
+ /** Spinner color */
41
+ readonly spinnerColor?: string;
42
+ /** Content to display on the right side */
43
+ readonly rightContent?: React.ReactNode;
44
+ /** Whether the indicator is in a narrow layout */
45
+ readonly narrow?: boolean;
46
+ }
47
+
48
+ // =============================================================================
49
+ // Hooks
50
+ // =============================================================================
51
+
52
+ /**
53
+ * Hook to manage elapsed time with proper reset behavior.
54
+ *
55
+ * @param isActive - Whether the timer should be running
56
+ * @param resetKey - Key that triggers timer reset when changed
57
+ * @returns Elapsed time in seconds
58
+ */
59
+ export function useElapsedTime(isActive: boolean, resetKey?: unknown): number {
60
+ const [elapsedTime, setElapsedTime] = useState(0);
61
+ const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
62
+ const prevResetKeyRef = useRef(resetKey);
63
+ const prevIsActiveRef = useRef(isActive);
64
+
65
+ useEffect(() => {
66
+ let shouldResetTime = false;
67
+
68
+ // Reset on key change
69
+ if (prevResetKeyRef.current !== resetKey) {
70
+ shouldResetTime = true;
71
+ prevResetKeyRef.current = resetKey;
72
+ }
73
+
74
+ // Reset on transition from inactive to active
75
+ if (!prevIsActiveRef.current && isActive) {
76
+ shouldResetTime = true;
77
+ }
78
+
79
+ if (shouldResetTime) {
80
+ setElapsedTime(0);
81
+ }
82
+ prevIsActiveRef.current = isActive;
83
+
84
+ // Manage interval
85
+ if (isActive) {
86
+ // Clear previous interval
87
+ if (timerRef.current) {
88
+ clearInterval(timerRef.current);
89
+ }
90
+ timerRef.current = setInterval(() => {
91
+ setElapsedTime((prev) => prev + 1);
92
+ }, 1000);
93
+ } else {
94
+ if (timerRef.current) {
95
+ clearInterval(timerRef.current);
96
+ timerRef.current = null;
97
+ }
98
+ }
99
+
100
+ return () => {
101
+ if (timerRef.current) {
102
+ clearInterval(timerRef.current);
103
+ timerRef.current = null;
104
+ }
105
+ };
106
+ }, [isActive, resetKey]);
107
+
108
+ return elapsedTime;
109
+ }
110
+
111
+ /**
112
+ * Hook to manage show/hide delays for smooth transitions.
113
+ *
114
+ * @param isActive - Whether the element should be shown
115
+ * @param showDelay - Delay before showing in ms
116
+ * @param hideDelay - Delay before hiding in ms
117
+ * @returns Whether the element should be visible
118
+ */
119
+ export function useDelayedVisibility(isActive: boolean, showDelay = 0, hideDelay = 0): boolean {
120
+ const [isVisible, setIsVisible] = useState(isActive && showDelay === 0);
121
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
122
+
123
+ useEffect(() => {
124
+ // Clear any pending timeout
125
+ if (timeoutRef.current) {
126
+ clearTimeout(timeoutRef.current);
127
+ timeoutRef.current = null;
128
+ }
129
+
130
+ if (isActive) {
131
+ if (showDelay > 0) {
132
+ timeoutRef.current = setTimeout(() => {
133
+ setIsVisible(true);
134
+ }, showDelay);
135
+ } else {
136
+ setIsVisible(true);
137
+ }
138
+ } else {
139
+ if (hideDelay > 0) {
140
+ timeoutRef.current = setTimeout(() => {
141
+ setIsVisible(false);
142
+ }, hideDelay);
143
+ } else {
144
+ setIsVisible(false);
145
+ }
146
+ }
147
+
148
+ return () => {
149
+ if (timeoutRef.current) {
150
+ clearTimeout(timeoutRef.current);
151
+ timeoutRef.current = null;
152
+ }
153
+ };
154
+ }, [isActive, showDelay, hideDelay]);
155
+
156
+ return isVisible;
157
+ }
158
+
159
+ // =============================================================================
160
+ // Utility Functions
161
+ // =============================================================================
162
+
163
+ /**
164
+ * Format duration in a human-readable way.
165
+ *
166
+ * @param seconds - Duration in seconds
167
+ * @returns Formatted duration string
168
+ */
169
+ export function formatDuration(seconds: number): string {
170
+ if (seconds < 60) {
171
+ return `${seconds}s`;
172
+ }
173
+
174
+ const minutes = Math.floor(seconds / 60);
175
+ const remainingSeconds = seconds % 60;
176
+
177
+ if (minutes < 60) {
178
+ return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
179
+ }
180
+
181
+ const hours = Math.floor(minutes / 60);
182
+ const remainingMinutes = minutes % 60;
183
+
184
+ return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
185
+ }
186
+
187
+ // =============================================================================
188
+ // Component
189
+ // =============================================================================
190
+
191
+ /**
192
+ * EnhancedLoadingIndicator - Feature-rich loading indicator.
193
+ *
194
+ * Features:
195
+ * - Customizable spinner with label
196
+ * - Elapsed time display (auto-formatted)
197
+ * - Cancel hint with configurable text
198
+ * - Show/hide delays for smooth transitions
199
+ * - Responsive narrow layout support
200
+ *
201
+ * @example
202
+ * ```tsx
203
+ * // Basic usage
204
+ * <EnhancedLoadingIndicator isLoading={true} label="Processing..." />
205
+ *
206
+ * // With all features
207
+ * <EnhancedLoadingIndicator
208
+ * isLoading={isProcessing}
209
+ * label="Generating response..."
210
+ * showElapsedTime
211
+ * showCancelHint
212
+ * cancelHint="Press Esc to cancel"
213
+ * showDelay={200}
214
+ * />
215
+ *
216
+ * // Narrow layout
217
+ * <EnhancedLoadingIndicator
218
+ * isLoading={true}
219
+ * label="Working..."
220
+ * narrow
221
+ * />
222
+ * ```
223
+ */
224
+ export function EnhancedLoadingIndicator({
225
+ isLoading,
226
+ label = "Loading...",
227
+ showElapsedTime = true,
228
+ showCancelHint = true,
229
+ cancelHint = "Esc to cancel",
230
+ showDelay = 0,
231
+ hideDelay = 0,
232
+ spinnerFrames,
233
+ spinnerColor,
234
+ rightContent,
235
+ narrow = false,
236
+ }: EnhancedLoadingIndicatorProps): React.JSX.Element | null {
237
+ const { theme } = useTheme();
238
+
239
+ // Manage visibility with delays
240
+ const isVisible = useDelayedVisibility(isLoading, showDelay, hideDelay);
241
+
242
+ // Track elapsed time
243
+ const elapsedTime = useElapsedTime(isLoading);
244
+
245
+ // Build the time and cancel hint text
246
+ const metaContent = useCallback(() => {
247
+ const parts: string[] = [];
248
+
249
+ if (showCancelHint && cancelHint) {
250
+ parts.push(cancelHint);
251
+ }
252
+
253
+ if (showElapsedTime) {
254
+ parts.push(`⏱ ${formatDuration(elapsedTime)}`);
255
+ }
256
+
257
+ return parts.length > 0 ? `(${parts.join(", ")})` : null;
258
+ }, [showCancelHint, cancelHint, showElapsedTime, elapsedTime]);
259
+
260
+ if (!isVisible) {
261
+ return null;
262
+ }
263
+
264
+ const meta = metaContent();
265
+
266
+ if (narrow) {
267
+ // Narrow layout: stack vertically
268
+ return (
269
+ <Box flexDirection="column">
270
+ <Box>
271
+ <Spinner color={spinnerColor ?? theme.colors.info} frames={spinnerFrames} />
272
+ <Text color={theme.colors.info}> {label}</Text>
273
+ </Box>
274
+ {meta && (
275
+ <Box>
276
+ <Text dimColor>{meta}</Text>
277
+ </Box>
278
+ )}
279
+ {rightContent && <Box>{rightContent}</Box>}
280
+ </Box>
281
+ );
282
+ }
283
+
284
+ // Normal layout: horizontal
285
+ return (
286
+ <Box flexDirection="row" alignItems="center">
287
+ <Spinner color={spinnerColor ?? theme.colors.info} frames={spinnerFrames} />
288
+ <Text color={theme.colors.info}> {label}</Text>
289
+ {meta && (
290
+ <>
291
+ <Text> </Text>
292
+ <Text dimColor>{meta}</Text>
293
+ </>
294
+ )}
295
+ {rightContent && (
296
+ <>
297
+ <Box flexGrow={1} />
298
+ {rightContent}
299
+ </>
300
+ )}
301
+ </Box>
302
+ );
303
+ }
304
+
305
+ export default EnhancedLoadingIndicator;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * ErrorBoundary Component (Chain 20)
3
+ *
4
+ * React error boundary for graceful error handling in the TUI.
5
+ * Catches errors in child components and displays a fallback UI.
6
+ *
7
+ * @module tui/components/common/ErrorBoundary
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import { Component, type ReactNode } from "react";
12
+
13
+ // =============================================================================
14
+ // Types
15
+ // =============================================================================
16
+
17
+ /**
18
+ * Props for the ErrorBoundary component.
19
+ */
20
+ export interface ErrorBoundaryProps {
21
+ /** Child components to render */
22
+ readonly children: ReactNode;
23
+ /** Custom fallback UI to render on error */
24
+ readonly fallback?: ReactNode;
25
+ /** Callback when an error is caught */
26
+ readonly onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
27
+ /** Whether to show error details */
28
+ readonly showDetails?: boolean;
29
+ }
30
+
31
+ /**
32
+ * Internal state for the ErrorBoundary.
33
+ */
34
+ interface ErrorBoundaryState {
35
+ /** Whether an error has been caught */
36
+ hasError: boolean;
37
+ /** The caught error, if any */
38
+ error: Error | null;
39
+ }
40
+
41
+ // =============================================================================
42
+ // ErrorBoundary Component
43
+ // =============================================================================
44
+
45
+ /**
46
+ * ErrorBoundary - Catches JavaScript errors in child components.
47
+ *
48
+ * Features:
49
+ * - Catches errors in render, lifecycle methods, and constructors
50
+ * - Displays customizable fallback UI
51
+ * - Supports error callback for logging
52
+ * - Provides reset functionality
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * <ErrorBoundary onError={(err) => logError(err)}>
57
+ * <MyComponent />
58
+ * </ErrorBoundary>
59
+ *
60
+ * // With custom fallback
61
+ * <ErrorBoundary fallback={<Text>Something went wrong</Text>}>
62
+ * <MyComponent />
63
+ * </ErrorBoundary>
64
+ * ```
65
+ */
66
+ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
67
+ state: ErrorBoundaryState = {
68
+ hasError: false,
69
+ error: null,
70
+ };
71
+
72
+ /**
73
+ * Update state when an error is caught.
74
+ * Called during the "render" phase.
75
+ */
76
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
77
+ return {
78
+ hasError: true,
79
+ error,
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Log error information.
85
+ * Called during the "commit" phase.
86
+ */
87
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
88
+ this.props.onError?.(error, errorInfo);
89
+ }
90
+
91
+ /**
92
+ * Reset the error state.
93
+ * Can be called externally via ref or exposed through context.
94
+ */
95
+ reset = (): void => {
96
+ this.setState({
97
+ hasError: false,
98
+ error: null,
99
+ });
100
+ };
101
+
102
+ render(): ReactNode {
103
+ const { hasError, error } = this.state;
104
+ const { children, fallback, showDetails = true } = this.props;
105
+
106
+ if (hasError) {
107
+ // Use custom fallback if provided
108
+ if (fallback) {
109
+ return fallback;
110
+ }
111
+
112
+ // Default error UI
113
+ return (
114
+ <Box flexDirection="column" padding={1} borderStyle="round" borderColor="red">
115
+ <Text color="red" bold>
116
+ ! Something went wrong
117
+ </Text>
118
+ {showDetails && error && (
119
+ <Box marginTop={1}>
120
+ <Text color="gray" wrap="wrap">
121
+ {error.message}
122
+ </Text>
123
+ </Box>
124
+ )}
125
+ <Box marginTop={1}>
126
+ <Text color="cyan">Press 'r' to retry or Ctrl+C to exit</Text>
127
+ </Box>
128
+ </Box>
129
+ );
130
+ }
131
+
132
+ return children;
133
+ }
134
+ }
135
+
136
+ // =============================================================================
137
+ // Exports
138
+ // =============================================================================
139
+
140
+ export default ErrorBoundary;
@@ -0,0 +1,224 @@
1
+ /**
2
+ * GradientText Component
3
+ *
4
+ * Terminal-compatible gradient text implementation that applies
5
+ * color gradients by coloring each character segment individually.
6
+ *
7
+ * Uses theme brand colors by default for consistent Vellum styling.
8
+ *
9
+ * @module tui/components/common/GradientText
10
+ */
11
+
12
+ import { Text } from "ink";
13
+ import type React from "react";
14
+ import { memo, useMemo } from "react";
15
+ import { useTheme } from "../../theme/index.js";
16
+
17
+ // =============================================================================
18
+ // Types
19
+ // =============================================================================
20
+
21
+ /**
22
+ * Gradient direction for text coloring.
23
+ * - horizontal: Left to right gradient across text
24
+ * - vertical: Top to bottom (simulated by alternating chars)
25
+ */
26
+ export type GradientDirection = "horizontal" | "vertical";
27
+
28
+ /**
29
+ * Props for the GradientText component.
30
+ */
31
+ export interface GradientTextProps {
32
+ /** The text to display with gradient coloring */
33
+ readonly text: string;
34
+ /** Array of colors to use for the gradient (min 2 colors) */
35
+ readonly colors?: readonly string[];
36
+ /** Direction of the gradient (default: 'horizontal') */
37
+ readonly direction?: GradientDirection;
38
+ /** Whether to apply bold styling */
39
+ readonly bold?: boolean;
40
+ /** Whether to apply dim styling */
41
+ readonly dimColor?: boolean;
42
+ }
43
+
44
+ // =============================================================================
45
+ // Helper Functions
46
+ // =============================================================================
47
+
48
+ /**
49
+ * Split text into segments for gradient coloring.
50
+ * Each segment gets assigned a color from the gradient.
51
+ *
52
+ * @param text - The text to split
53
+ * @param colorCount - Number of colors in the gradient
54
+ * @returns Array of text segments
55
+ */
56
+ function splitIntoSegments(text: string, colorCount: number): string[] {
57
+ if (!text || colorCount <= 0) {
58
+ return [text];
59
+ }
60
+
61
+ // For very short text, just assign one color per character
62
+ if (text.length <= colorCount) {
63
+ return [...text];
64
+ }
65
+
66
+ // Calculate segment size (distribute chars evenly across colors)
67
+ const baseSegmentSize = Math.floor(text.length / colorCount);
68
+ const remainder = text.length % colorCount;
69
+
70
+ const segments: string[] = [];
71
+ let currentIndex = 0;
72
+
73
+ for (let i = 0; i < colorCount; i++) {
74
+ // Add 1 extra char to first 'remainder' segments to distribute evenly
75
+ const segmentSize = baseSegmentSize + (i < remainder ? 1 : 0);
76
+ const segment = text.slice(currentIndex, currentIndex + segmentSize);
77
+ if (segment) {
78
+ segments.push(segment);
79
+ }
80
+ currentIndex += segmentSize;
81
+ }
82
+
83
+ return segments;
84
+ }
85
+
86
+ /**
87
+ * Get a subset of colors for vertical gradient simulation.
88
+ * Alternates between first and last colors.
89
+ *
90
+ * @param colors - Full color array
91
+ * @returns Colors for vertical effect
92
+ */
93
+ function getVerticalColors(colors: readonly string[]): readonly string[] {
94
+ if (colors.length < 2) {
95
+ return colors;
96
+ }
97
+ // For vertical, alternate between start and end colors
98
+ return [colors[0]!, colors[colors.length - 1]!];
99
+ }
100
+
101
+ // =============================================================================
102
+ // Component
103
+ // =============================================================================
104
+
105
+ /**
106
+ * GradientText renders text with a color gradient effect.
107
+ *
108
+ * Terminal gradients work by coloring each character/segment individually.
109
+ * This component splits the text into segments and applies colors from
110
+ * the provided gradient array (or theme brand colors by default).
111
+ *
112
+ * @example
113
+ * ```tsx
114
+ * // Using theme brand colors (default)
115
+ * <GradientText text="VELLUM" />
116
+ *
117
+ * // Custom colors
118
+ * <GradientText
119
+ * text="Hello World"
120
+ * colors={['#ff0000', '#00ff00', '#0000ff']}
121
+ * />
122
+ *
123
+ * // Vertical gradient (alternating)
124
+ * <GradientText text="Status" direction="vertical" />
125
+ * ```
126
+ */
127
+ function GradientTextImpl({
128
+ text,
129
+ colors,
130
+ direction = "horizontal",
131
+ bold = false,
132
+ dimColor = false,
133
+ }: GradientTextProps): React.ReactElement {
134
+ const { theme } = useTheme();
135
+
136
+ // Use theme brand colors as default gradient
137
+ const gradientColors = useMemo((): readonly string[] => {
138
+ if (colors && colors.length >= 2) {
139
+ return colors;
140
+ }
141
+
142
+ // Default: theme brand gradient (goldenrod → peru → sienna)
143
+ return [
144
+ theme.brand.highlight, // Gold #FFD700
145
+ theme.brand.primary, // Goldenrod #DAA520
146
+ theme.brand.secondary, // Peru #CD853F
147
+ theme.brand.mid, // Sienna #A0522D
148
+ ] as const;
149
+ }, [colors, theme.brand]);
150
+
151
+ // Apply direction transformation
152
+ const effectiveColors = useMemo(() => {
153
+ return direction === "vertical" ? getVerticalColors(gradientColors) : gradientColors;
154
+ }, [direction, gradientColors]);
155
+
156
+ // Split text into gradient segments
157
+ const segments = useMemo(() => {
158
+ return splitIntoSegments(text, effectiveColors.length);
159
+ }, [text, effectiveColors.length]);
160
+
161
+ // Handle empty text
162
+ if (!text) {
163
+ return <Text>{""}</Text>;
164
+ }
165
+
166
+ // Render gradient segments
167
+ return (
168
+ <Text>
169
+ {segments.map((segment, index) => {
170
+ const color = effectiveColors[index % effectiveColors.length];
171
+ return (
172
+ <Text key={`${index}-${segment}`} color={color} bold={bold} dimColor={dimColor}>
173
+ {segment}
174
+ </Text>
175
+ );
176
+ })}
177
+ </Text>
178
+ );
179
+ }
180
+
181
+ /**
182
+ * Memoized GradientText component.
183
+ * Re-renders only when props actually change.
184
+ */
185
+ export const GradientText = memo(GradientTextImpl);
186
+
187
+ // =============================================================================
188
+ // Preset Gradients
189
+ // =============================================================================
190
+
191
+ /**
192
+ * Preset gradient configurations for common use cases.
193
+ * Import and spread into GradientText props.
194
+ */
195
+ export const GRADIENT_PRESETS = {
196
+ /** Warm gold/amber gradient (default brand) */
197
+ brand: undefined, // Uses default theme.brand colors
198
+
199
+ /** Success/positive gradient (green tones) */
200
+ success: ["#10b981", "#34d399", "#6ee7b7"] as const,
201
+
202
+ /** Warning gradient (amber/orange tones) */
203
+ warning: ["#f59e0b", "#fbbf24", "#fcd34d"] as const,
204
+
205
+ /** Error gradient (red tones) */
206
+ error: ["#ef4444", "#f87171", "#fca5a5"] as const,
207
+
208
+ /** Info gradient (blue tones) */
209
+ info: ["#3b82f6", "#60a5fa", "#93c5fd"] as const,
210
+
211
+ /** Purple/violet gradient (primary UI) */
212
+ primary: ["#7c3aed", "#8b5cf6", "#a78bfa"] as const,
213
+
214
+ /** Sunset gradient (warm spectrum) */
215
+ sunset: ["#f59e0b", "#ef4444", "#ec4899"] as const,
216
+
217
+ /** Ocean gradient (cool spectrum) */
218
+ ocean: ["#0ea5e9", "#3b82f6", "#6366f1"] as const,
219
+ } as const;
220
+
221
+ /**
222
+ * Type for preset gradient names.
223
+ */
224
+ export type GradientPreset = keyof typeof GRADIENT_PRESETS;