@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,321 @@
1
+ /**
2
+ * TypeWriterGradient Component
3
+ *
4
+ * Renders text with a typewriter effect where characters appear one by one,
5
+ * each colored according to its position in a gradient.
6
+ * Shows a blinking cursor at the end during typing.
7
+ *
8
+ * @module tui/components/Banner/TypeWriterGradient
9
+ */
10
+
11
+ import { Box, Text } from "ink";
12
+ import type React from "react";
13
+ import { memo, useEffect, useMemo, useRef, useState } from "react";
14
+ import { useAnimation } from "../../context/AnimationContext.js";
15
+ import { interpolateColor } from "./ShimmerText.js";
16
+
17
+ // =============================================================================
18
+ // Types
19
+ // =============================================================================
20
+
21
+ /**
22
+ * Typing mode for the typewriter effect.
23
+ * - 'line': Reveal one line at a time (faster for multi-line ASCII art)
24
+ * - 'char': Reveal one character at a time (classic typewriter feel)
25
+ */
26
+ export type TypeWriterMode = "line" | "char";
27
+
28
+ /**
29
+ * Props for the TypeWriterGradient component.
30
+ */
31
+ export interface TypeWriterGradientProps {
32
+ /** Text content to display with typewriter effect */
33
+ readonly text: string;
34
+ /** Speed: chars/sec for 'char' mode, lines/sec for 'line' mode (default: 3000 chars/sec or 50 lines/sec) */
35
+ readonly speed?: number;
36
+ /** Gradient colors array (start to end) */
37
+ readonly colors: readonly string[];
38
+ /** Callback when typing completes */
39
+ readonly onComplete?: () => void;
40
+ /** Whether to show blinking cursor (default: true) */
41
+ readonly showCursor?: boolean;
42
+ /** Initial delay before typing starts in ms (default: 100) */
43
+ readonly initialDelay?: number;
44
+ /** Typing mode: 'line' for fast multi-line reveal, 'char' for classic typewriter (default: 'line') */
45
+ readonly mode?: TypeWriterMode;
46
+ }
47
+
48
+ // =============================================================================
49
+ // Constants
50
+ // =============================================================================
51
+
52
+ /** Default typing speed for char mode (chars per second) - very fast for ASCII art */
53
+ const DEFAULT_CHAR_SPEED = 5000;
54
+
55
+ /** Default typing speed for line mode (lines per second) */
56
+ const DEFAULT_LINE_SPEED = 50;
57
+
58
+ /** Frame interval for chunk-based typing (targeting ~60fps) */
59
+ const FRAME_INTERVAL_MS = 16;
60
+
61
+ /** Cursor character */
62
+ const CURSOR_CHAR = "█";
63
+
64
+ // =============================================================================
65
+ // Color Calculation
66
+ // =============================================================================
67
+
68
+ /**
69
+ * Build interpolated gradient steps for smoother color transitions.
70
+ */
71
+ function buildGradientSteps(colors: readonly string[], stepsPerSegment: number): string[] {
72
+ if (colors.length === 0) return [];
73
+ if (colors.length === 1) return [colors[0] ?? "#000000"];
74
+
75
+ const steps: string[] = [];
76
+
77
+ for (let i = 0; i < colors.length - 1; i += 1) {
78
+ const start = colors[i] ?? "#000000";
79
+ const end = colors[i + 1] ?? start;
80
+ for (let step = 0; step < stepsPerSegment; step += 1) {
81
+ const t = step / stepsPerSegment;
82
+ steps.push(interpolateColor(start, end, t));
83
+ }
84
+ }
85
+
86
+ steps.push(colors[colors.length - 1] ?? "#000000");
87
+ return steps;
88
+ }
89
+
90
+ /**
91
+ * Get color for a character based on its position in the total text.
92
+ * Uses interpolated gradient for smooth transitions.
93
+ */
94
+ function getCharColor(charIndex: number, totalChars: number, gradientSteps: string[]): string {
95
+ if (totalChars <= 1 || gradientSteps.length === 0) {
96
+ return gradientSteps[0] ?? "#FFFFFF";
97
+ }
98
+
99
+ // Map character position (0 to totalChars-1) to gradient position (0 to 1)
100
+ const position = charIndex / (totalChars - 1);
101
+ const stepIndex = Math.min(
102
+ Math.floor(position * (gradientSteps.length - 1)),
103
+ gradientSteps.length - 1
104
+ );
105
+
106
+ return gradientSteps[stepIndex] ?? gradientSteps[0] ?? "#FFFFFF";
107
+ }
108
+
109
+ // =============================================================================
110
+ // Sub-Components
111
+ // =============================================================================
112
+
113
+ /**
114
+ * Single colored character.
115
+ * Memoized to prevent unnecessary re-renders.
116
+ */
117
+ interface ColoredCharProps {
118
+ readonly char: string;
119
+ readonly color: string;
120
+ }
121
+
122
+ const ColoredChar = memo(function ColoredChar({
123
+ char,
124
+ color,
125
+ }: ColoredCharProps): React.JSX.Element {
126
+ return <Text color={color}>{char}</Text>;
127
+ });
128
+
129
+ /**
130
+ * Blinking cursor component.
131
+ */
132
+ interface CursorProps {
133
+ readonly visible: boolean;
134
+ readonly color: string;
135
+ }
136
+
137
+ const Cursor = memo(function Cursor({ visible, color }: CursorProps): React.JSX.Element | null {
138
+ if (!visible) return null;
139
+ return <Text color={color}>{CURSOR_CHAR}</Text>;
140
+ });
141
+
142
+ // =============================================================================
143
+ // Main Component
144
+ // =============================================================================
145
+
146
+ /**
147
+ * TypeWriterGradient displays text with a typewriter effect.
148
+ *
149
+ * Characters appear one by one from left to right, with each character
150
+ * colored according to its position in the provided gradient.
151
+ * A blinking cursor appears at the end during typing.
152
+ *
153
+ * @example
154
+ * ```tsx
155
+ * <TypeWriterGradient
156
+ * text={asciiArt}
157
+ * speed={200}
158
+ * colors={['#8B4513', '#DAA520', '#FFD700']}
159
+ * showCursor
160
+ * onComplete={() => setTypingDone(true)}
161
+ * />
162
+ * ```
163
+ */
164
+ export const TypeWriterGradient = memo(function TypeWriterGradient({
165
+ text,
166
+ speed,
167
+ colors,
168
+ onComplete,
169
+ showCursor = true,
170
+ initialDelay = 100,
171
+ mode = "line",
172
+ }: TypeWriterGradientProps): React.JSX.Element {
173
+ // Split text into lines upfront for line mode
174
+ const allLines = useMemo(() => text.split("\n"), [text]);
175
+ const totalLines = allLines.length;
176
+ const totalChars = text.length;
177
+
178
+ // Resolve speed based on mode
179
+ const effectiveSpeed = speed ?? (mode === "line" ? DEFAULT_LINE_SPEED : DEFAULT_CHAR_SPEED);
180
+
181
+ // State for visible content (lines for line mode, chars for char mode)
182
+ const [visibleCount, setVisibleCount] = useState(0);
183
+ const [cursorVisible, setCursorVisible] = useState(true);
184
+ const [isComplete, setIsComplete] = useState(false);
185
+
186
+ // Refs for cleanup
187
+ const typingIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
188
+ const initialDelayRef = useRef<ReturnType<typeof setTimeout> | null>(null);
189
+ const completeCalledRef = useRef(false);
190
+
191
+ // Pre-compute gradient steps for performance
192
+ const gradientSteps = useMemo(() => buildGradientSteps(colors, 8), [colors]);
193
+
194
+ // Total items to reveal (lines or chars depending on mode)
195
+ const totalItems = mode === "line" ? totalLines : totalChars;
196
+
197
+ // Typing effect with chunk-based rendering
198
+ useEffect(() => {
199
+ // Clear any existing intervals
200
+ if (typingIntervalRef.current) {
201
+ clearInterval(typingIntervalRef.current);
202
+ }
203
+ if (initialDelayRef.current) {
204
+ clearTimeout(initialDelayRef.current);
205
+ }
206
+
207
+ // Start after initial delay
208
+ initialDelayRef.current = setTimeout(() => {
209
+ // Calculate items per frame for chunk-based typing
210
+ // For line mode: speed=50 lines/sec at 60fps = ~0.8 lines/frame (at least 1)
211
+ // For char mode: speed=3000 chars/sec at 60fps = ~48 chars/frame
212
+ const itemsPerFrame = Math.max(1, Math.ceil(effectiveSpeed / (1000 / FRAME_INTERVAL_MS)));
213
+
214
+ typingIntervalRef.current = setInterval(() => {
215
+ setVisibleCount((prev) => {
216
+ const next = prev + itemsPerFrame;
217
+
218
+ // Check completion
219
+ if (next >= totalItems) {
220
+ if (typingIntervalRef.current) {
221
+ clearInterval(typingIntervalRef.current);
222
+ typingIntervalRef.current = null;
223
+ }
224
+ setIsComplete(true);
225
+ if (!completeCalledRef.current) {
226
+ completeCalledRef.current = true;
227
+ // Small delay before calling onComplete to let final render happen
228
+ setTimeout(() => onComplete?.(), 50);
229
+ }
230
+ return totalItems;
231
+ }
232
+
233
+ return next;
234
+ });
235
+ }, FRAME_INTERVAL_MS);
236
+ }, initialDelay);
237
+
238
+ return () => {
239
+ if (typingIntervalRef.current) {
240
+ clearInterval(typingIntervalRef.current);
241
+ }
242
+ if (initialDelayRef.current) {
243
+ clearTimeout(initialDelayRef.current);
244
+ }
245
+ };
246
+ }, [effectiveSpeed, totalItems, initialDelay, onComplete]);
247
+
248
+ // Use global animation context for cursor blink to prevent flickering
249
+ const { frame, isPaused } = useAnimation();
250
+
251
+ // Derive cursor visibility from animation frame instead of independent timer
252
+ const derivedCursorVisible = useMemo(() => {
253
+ if (!showCursor || isComplete) return false;
254
+ // Show cursor when animation is paused (e.g., input focused)
255
+ if (isPaused) return true;
256
+ // Toggle every ~4 frames (~500ms blink cycle)
257
+ return Math.floor(frame / 4) % 2 === 0;
258
+ }, [frame, isPaused, showCursor, isComplete]);
259
+
260
+ // Sync local state with derived value
261
+ useEffect(() => {
262
+ setCursorVisible(derivedCursorVisible);
263
+ }, [derivedCursorVisible]);
264
+
265
+ // Build visible lines based on mode
266
+ const visibleLines = useMemo(() => {
267
+ if (mode === "line") {
268
+ // Line mode: show complete lines up to visibleCount
269
+ return allLines.slice(0, visibleCount);
270
+ }
271
+ // Char mode: slice text and split into lines
272
+ const visibleText = text.slice(0, visibleCount);
273
+ return visibleText.split("\n");
274
+ }, [mode, allLines, text, visibleCount]);
275
+
276
+ // Calculate visible char count for gradient positioning
277
+ const visibleCharCount = useMemo(() => {
278
+ if (mode === "line") {
279
+ // Sum chars in visible lines + newlines
280
+ return visibleLines.reduce((acc, line, idx) => acc + line.length + (idx > 0 ? 1 : 0), 0);
281
+ }
282
+ return visibleCount;
283
+ }, [mode, visibleLines, visibleCount]);
284
+
285
+ // Get cursor color (color at current position)
286
+ const cursorColor = useMemo(() => {
287
+ if (visibleCharCount === 0) return gradientSteps[0] ?? "#FFD700";
288
+ return getCharColor(visibleCharCount - 1, totalChars, gradientSteps);
289
+ }, [visibleCharCount, totalChars, gradientSteps]);
290
+
291
+ return (
292
+ <Box flexDirection="column">
293
+ {visibleLines.map((line, lineIndex) => {
294
+ // Calculate character offset for this line (for gradient coloring)
295
+ const lineStartIndex = visibleLines
296
+ .slice(0, lineIndex)
297
+ .reduce((acc, l) => acc + l.length + 1, 0);
298
+ const isLastLine = lineIndex === visibleLines.length - 1;
299
+ const lineKey = `line-${lineIndex}`;
300
+
301
+ return (
302
+ <Box key={lineKey} flexDirection="row">
303
+ {line.split("").map((char, charIndex) => {
304
+ const globalIndex = lineStartIndex + charIndex;
305
+ const color = getCharColor(globalIndex, totalChars, gradientSteps);
306
+ const charKey = `char-${globalIndex}`;
307
+
308
+ return <ColoredChar key={charKey} char={char} color={color} />;
309
+ })}
310
+ {/* Show cursor at end of last line during typing */}
311
+ {isLastLine && showCursor && !isComplete && (
312
+ <Cursor visible={cursorVisible} color={cursorColor} />
313
+ )}
314
+ </Box>
315
+ );
316
+ })}
317
+ </Box>
318
+ );
319
+ });
320
+
321
+ export default TypeWriterGradient;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Banner Module
3
+ *
4
+ * Exports all banner-related components for the Vellum CLI.
5
+ *
6
+ * @module tui/components/Banner
7
+ */
8
+
9
+ // ASCII Art definitions
10
+ export {
11
+ ASCII_VARIANTS,
12
+ type AsciiVariant,
13
+ SCROLL_BOTTOM,
14
+ SCROLL_TOP,
15
+ selectAsciiArt,
16
+ VELLUM_LARGE,
17
+ VELLUM_MEDIUM,
18
+ VELLUM_MINIMAL,
19
+ VELLUM_SMALL,
20
+ } from "./AsciiArt.js";
21
+
22
+ // Main Banner component
23
+ export {
24
+ Banner,
25
+ type BannerProps,
26
+ CompactBanner,
27
+ HeaderBanner,
28
+ type HeaderBannerProps,
29
+ MinimalBanner,
30
+ } from "./Banner.js";
31
+ // Shimmer context for shared timer
32
+ export {
33
+ ShimmerProvider,
34
+ type ShimmerProviderProps,
35
+ useIsInShimmerProvider,
36
+ useSharedShimmer,
37
+ } from "./ShimmerContext.js";
38
+
39
+ // Shimmer text component
40
+ export {
41
+ BannerShimmerText,
42
+ type BannerShimmerTextProps,
43
+ interpolateColor,
44
+ MultiLineShimmer,
45
+ type MultiLineShimmerProps,
46
+ } from "./ShimmerText.js";
47
+
48
+ // TypeWriter gradient component
49
+ export {
50
+ TypeWriterGradient,
51
+ type TypeWriterGradientProps,
52
+ type TypeWriterMode,
53
+ } from "./TypeWriterGradient.js";
54
+
55
+ // Shimmer animation hook
56
+ export {
57
+ calculateShimmerIntensity,
58
+ type ShimmerConfig,
59
+ type ShimmerState,
60
+ useShimmer,
61
+ } from "./useShimmer.js";
@@ -0,0 +1,241 @@
1
+ /**
2
+ * useShimmer Hook
3
+ *
4
+ * Provides smooth shimmer/glow animation effect with customizable parameters.
5
+ * Uses cosine function for smooth transitions.
6
+ *
7
+ * @module tui/components/Banner/useShimmer
8
+ */
9
+
10
+ import { useEffect, useRef, useState } from "react";
11
+
12
+ // =============================================================================
13
+ // Types
14
+ // =============================================================================
15
+
16
+ /**
17
+ * Configuration options for the shimmer animation.
18
+ */
19
+ export interface ShimmerConfig {
20
+ /** Duration of one complete shimmer cycle in milliseconds (default: 3000) */
21
+ readonly cycleDuration?: number;
22
+ /** Update interval in milliseconds (default: 100) */
23
+ readonly updateInterval?: number;
24
+ /** Whether animation is enabled (default: true) */
25
+ readonly enabled?: boolean;
26
+ /** Maximum number of cycles before stopping (undefined = infinite) */
27
+ readonly maxCycles?: number;
28
+ /** Callback when max cycles reached */
29
+ readonly onComplete?: () => void;
30
+ }
31
+
32
+ /**
33
+ * Return value from the useShimmer hook.
34
+ */
35
+ export interface ShimmerState {
36
+ /** Current position of shimmer (0 to 1) */
37
+ readonly position: number;
38
+ /** Current intensity at shimmer center (0 to 1) */
39
+ readonly intensity: number;
40
+ /** Whether shimmer is currently active */
41
+ readonly isActive: boolean;
42
+ /** Pause the shimmer animation */
43
+ readonly pause: () => void;
44
+ /** Resume the shimmer animation */
45
+ readonly resume: () => void;
46
+ /** Current cycle count (0-based) */
47
+ readonly cycleCount: number;
48
+ /** Whether max cycles completed */
49
+ readonly isComplete: boolean;
50
+ }
51
+
52
+ // =============================================================================
53
+ // Constants
54
+ // =============================================================================
55
+
56
+ /** Default shimmer cycle duration (3 seconds - slower for smoother effect) */
57
+ const DEFAULT_CYCLE_DURATION = 3000;
58
+
59
+ /** Default update interval (100ms = 10fps for smoother motion without aggressive redraws) */
60
+ const DEFAULT_UPDATE_INTERVAL = resolveDefaultUpdateInterval();
61
+
62
+ const SHIMMER_DEBUG_ENABLED =
63
+ process.env.VELLUM_TUI_SHIMMER_DEBUG === "1" || process.env.VELLUM_TUI_SHIMMER_DEBUG === "true";
64
+
65
+ function resolveDefaultUpdateInterval(): number {
66
+ const intervalOverride = Number(process.env.VELLUM_TUI_SHIMMER_INTERVAL_MS);
67
+ if (Number.isFinite(intervalOverride) && intervalOverride > 0) {
68
+ return Math.round(intervalOverride);
69
+ }
70
+
71
+ const fpsOverride = Number(process.env.VELLUM_TUI_SHIMMER_FPS);
72
+ if (Number.isFinite(fpsOverride) && fpsOverride > 0) {
73
+ return Math.max(16, Math.round(1000 / fpsOverride));
74
+ }
75
+
76
+ return 100;
77
+ }
78
+
79
+ function debugShimmer(message: string): void {
80
+ if (!SHIMMER_DEBUG_ENABLED) return;
81
+ process.stderr.write(`[shimmer] ${message}\n`);
82
+ }
83
+
84
+ // =============================================================================
85
+ // Hook Implementation
86
+ // =============================================================================
87
+
88
+ /**
89
+ * Hook for creating smooth shimmer animations.
90
+ *
91
+ * The shimmer effect sweeps from left to right using a cosine function
92
+ * for smooth acceleration and deceleration at the edges.
93
+ *
94
+ * @example
95
+ * ```tsx
96
+ * const { position, intensity } = useShimmer({ cycleDuration: 2000 });
97
+ *
98
+ * // Use position (0-1) to determine where shimmer highlight is
99
+ * // Use intensity (0-1) to determine brightness at that position
100
+ * ```
101
+ */
102
+ export function useShimmer(config: ShimmerConfig = {}): ShimmerState {
103
+ const {
104
+ cycleDuration = DEFAULT_CYCLE_DURATION,
105
+ updateInterval = DEFAULT_UPDATE_INTERVAL,
106
+ enabled = true,
107
+ maxCycles,
108
+ onComplete,
109
+ } = config;
110
+
111
+ const [position, setPosition] = useState(0);
112
+ const [isActive, setIsActive] = useState(enabled);
113
+ const [cycleCount, setCycleCount] = useState(0);
114
+ const [isComplete, setIsComplete] = useState(false);
115
+
116
+ const positionRef = useRef(0);
117
+ const cycleCountRef = useRef(0);
118
+ const lastTickRef = useRef<number | null>(null);
119
+ const lastDebugRef = useRef<number>(0);
120
+
121
+ // Track onComplete callback in ref to avoid effect re-runs
122
+ const onCompleteRef = useRef(onComplete);
123
+ onCompleteRef.current = onComplete;
124
+
125
+ useEffect(() => {
126
+ if (!isActive || isComplete) return;
127
+
128
+ lastTickRef.current = Date.now();
129
+
130
+ const timer = setInterval(() => {
131
+ const now = Date.now();
132
+ const lastTick = lastTickRef.current ?? now;
133
+ lastTickRef.current = now;
134
+
135
+ const deltaMs = now - lastTick;
136
+ const step = deltaMs / cycleDuration;
137
+ let nextPosition = positionRef.current + step;
138
+
139
+ if (nextPosition >= 1) {
140
+ const cyclesCompleted = Math.floor(nextPosition);
141
+ const updatedCycleCount = cycleCountRef.current + cyclesCompleted;
142
+ cycleCountRef.current = updatedCycleCount;
143
+ setCycleCount(updatedCycleCount);
144
+
145
+ if (maxCycles !== undefined && updatedCycleCount >= maxCycles) {
146
+ clearInterval(timer);
147
+ setIsActive(false);
148
+ setIsComplete(true);
149
+ // Set final position to a nice resting state (golden ratio position)
150
+ positionRef.current = 0.618;
151
+ setPosition(0.618);
152
+ onCompleteRef.current?.();
153
+ return;
154
+ }
155
+
156
+ nextPosition = nextPosition % 1;
157
+ }
158
+
159
+ positionRef.current = nextPosition;
160
+ setPosition(nextPosition);
161
+
162
+ if (SHIMMER_DEBUG_ENABLED) {
163
+ const lastDebug = lastDebugRef.current;
164
+ if (now - lastDebug >= 1000) {
165
+ lastDebugRef.current = now;
166
+ debugShimmer(
167
+ `deltaMs=${deltaMs.toFixed(1)} position=${nextPosition.toFixed(3)} cycle=${cycleCountRef.current}`
168
+ );
169
+ }
170
+ }
171
+ }, updateInterval);
172
+
173
+ return () => clearInterval(timer);
174
+ }, [isActive, cycleDuration, updateInterval, maxCycles, isComplete]);
175
+
176
+ useEffect(() => {
177
+ if (enabled && !isComplete) {
178
+ setIsActive(true);
179
+ return;
180
+ }
181
+
182
+ if (!enabled) {
183
+ setIsActive(false);
184
+ }
185
+ }, [enabled, isComplete]);
186
+
187
+ // Calculate intensity using cosine for smooth falloff
188
+ // Peak intensity at current position, smoothly fading around it
189
+ const intensity = Math.cos(position * Math.PI * 2 - Math.PI) * 0.5 + 0.5;
190
+
191
+ const pause = () => setIsActive(false);
192
+ const resume = () => {
193
+ if (!isComplete) setIsActive(true);
194
+ };
195
+
196
+ return {
197
+ position,
198
+ intensity,
199
+ isActive,
200
+ pause,
201
+ resume,
202
+ cycleCount,
203
+ isComplete,
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Calculate shimmer intensity at a specific character position.
209
+ *
210
+ * @param charIndex - Index of the character (0-based)
211
+ * @param totalChars - Total number of characters
212
+ * @param shimmerPosition - Current shimmer position (0-1)
213
+ * @param shimmerWidth - Width of shimmer effect (0-1, default 0.15)
214
+ * @returns Intensity value from 0 to 1
215
+ */
216
+ export function calculateShimmerIntensity(
217
+ charIndex: number,
218
+ totalChars: number,
219
+ shimmerPosition: number,
220
+ shimmerWidth: number = 0.15
221
+ ): number {
222
+ if (totalChars === 0) return 0;
223
+
224
+ // Normalize character position to 0-1 range
225
+ const charPosition = charIndex / totalChars;
226
+
227
+ // Calculate distance from shimmer center
228
+ const distance = Math.abs(charPosition - shimmerPosition);
229
+
230
+ // Handle wrap-around (shimmer at edge affects chars on opposite side)
231
+ const wrappedDistance = Math.min(distance, 1 - distance);
232
+
233
+ // If outside shimmer width, no effect
234
+ if (wrappedDistance > shimmerWidth) return 0;
235
+
236
+ // Cosine falloff for smooth intensity gradient
237
+ const normalizedDistance = wrappedDistance / shimmerWidth;
238
+ const intensity = Math.cos(normalizedDistance * Math.PI * 0.5);
239
+
240
+ return Math.max(0, intensity);
241
+ }
@@ -0,0 +1,11 @@
1
+ import { Box } from "ink";
2
+
3
+ // Define props for the ChatView component based on what will be moved from app.tsx
4
+
5
+ export function ChatView() {
6
+ return (
7
+ <Box flexDirection="column" flexGrow={1}>
8
+ {/* MessageList and EnhancedCommandInput will be moved here */}
9
+ </Box>
10
+ );
11
+ }