@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.
- package/LICENSE +21 -0
- package/README.md +411 -0
- package/__fixtures__/responses/code-generation.json +42 -0
- package/__fixtures__/responses/error-response.json +20 -0
- package/__fixtures__/responses/hello-world.json +32 -0
- package/dist/auth-6MCXESOH.js +26 -0
- package/dist/chunk-SECXJGWA.js +597 -0
- package/dist/index.js +34023 -0
- package/package.json +67 -0
- package/src/__tests__/commands.e2e.test.ts +728 -0
- package/src/__tests__/credentials.test.ts +713 -0
- package/src/__tests__/mode-e2e.test.ts +391 -0
- package/src/__tests__/tui-integration.test.tsx +1271 -0
- package/src/agents/__tests__/task-persistence.test.ts +235 -0
- package/src/agents/commands/delegate.ts +240 -0
- package/src/agents/commands/index.ts +10 -0
- package/src/agents/commands/resume.ts +335 -0
- package/src/agents/index.ts +29 -0
- package/src/agents/task-persistence.ts +272 -0
- package/src/agents/task-resumption.ts +242 -0
- package/src/app.tsx +4737 -0
- package/src/commands/__tests__/.gitkeep +1 -0
- package/src/commands/__tests__/agents.test.ts +606 -0
- package/src/commands/__tests__/auth.test.ts +626 -0
- package/src/commands/__tests__/autocomplete.test.ts +683 -0
- package/src/commands/__tests__/batch.test.ts +287 -0
- package/src/commands/__tests__/chain-pipe-parser.test.ts +654 -0
- package/src/commands/__tests__/completion.test.ts +238 -0
- package/src/commands/__tests__/core.test.ts +363 -0
- package/src/commands/__tests__/executor.test.ts +496 -0
- package/src/commands/__tests__/exit-codes.test.ts +220 -0
- package/src/commands/__tests__/init.test.ts +243 -0
- package/src/commands/__tests__/language.test.ts +353 -0
- package/src/commands/__tests__/mode-cli.test.ts +667 -0
- package/src/commands/__tests__/model.test.ts +277 -0
- package/src/commands/__tests__/parser.test.ts +493 -0
- package/src/commands/__tests__/performance.bench.ts +380 -0
- package/src/commands/__tests__/registry.test.ts +534 -0
- package/src/commands/__tests__/resume.test.ts +449 -0
- package/src/commands/__tests__/security.test.ts +845 -0
- package/src/commands/__tests__/stream-json.test.ts +372 -0
- package/src/commands/__tests__/user-commands.test.ts +597 -0
- package/src/commands/adapters.ts +267 -0
- package/src/commands/agent.ts +395 -0
- package/src/commands/agents/generate.ts +506 -0
- package/src/commands/agents/index.ts +272 -0
- package/src/commands/agents/show.ts +271 -0
- package/src/commands/agents/validate.ts +387 -0
- package/src/commands/auth.ts +883 -0
- package/src/commands/autocomplete.ts +480 -0
- package/src/commands/batch/command.ts +388 -0
- package/src/commands/batch/executor.ts +361 -0
- package/src/commands/batch/index.ts +12 -0
- package/src/commands/commit.ts +235 -0
- package/src/commands/completion/index.ts +371 -0
- package/src/commands/condense.ts +191 -0
- package/src/commands/config.ts +344 -0
- package/src/commands/context-provider.ts +173 -0
- package/src/commands/copy.ts +329 -0
- package/src/commands/core/clear.ts +38 -0
- package/src/commands/core/exit.ts +43 -0
- package/src/commands/core/help.ts +354 -0
- package/src/commands/core/index.ts +15 -0
- package/src/commands/cost.ts +179 -0
- package/src/commands/credentials.tsx +618 -0
- package/src/commands/custom-agents/__tests__/custom-agents.test.ts +709 -0
- package/src/commands/custom-agents/create.ts +377 -0
- package/src/commands/custom-agents/export.ts +135 -0
- package/src/commands/custom-agents/import.ts +199 -0
- package/src/commands/custom-agents/index.ts +372 -0
- package/src/commands/custom-agents/info.ts +318 -0
- package/src/commands/custom-agents/list.ts +267 -0
- package/src/commands/custom-agents/validate.ts +388 -0
- package/src/commands/diff-mode.ts +241 -0
- package/src/commands/env.ts +53 -0
- package/src/commands/executor.ts +579 -0
- package/src/commands/exit-codes.ts +202 -0
- package/src/commands/index.ts +701 -0
- package/src/commands/init/index.ts +15 -0
- package/src/commands/init/prompts.ts +366 -0
- package/src/commands/init/templates/commands-readme.md +80 -0
- package/src/commands/init/templates/example-command.md +79 -0
- package/src/commands/init/templates/example-skill.md +168 -0
- package/src/commands/init/templates/example-workflow.md +101 -0
- package/src/commands/init/templates/prompts-readme.md +52 -0
- package/src/commands/init/templates/rules-readme.md +63 -0
- package/src/commands/init/templates/skills-readme.md +83 -0
- package/src/commands/init/templates/workflows-readme.md +94 -0
- package/src/commands/init.ts +391 -0
- package/src/commands/install.ts +90 -0
- package/src/commands/language.ts +191 -0
- package/src/commands/loaders/.gitkeep +1 -0
- package/src/commands/lsp.ts +199 -0
- package/src/commands/markdown-commands.ts +253 -0
- package/src/commands/mcp.ts +588 -0
- package/src/commands/memory/export.ts +341 -0
- package/src/commands/memory/index.ts +148 -0
- package/src/commands/memory/list.ts +261 -0
- package/src/commands/memory/search.ts +346 -0
- package/src/commands/memory/utils.ts +15 -0
- package/src/commands/metrics.ts +75 -0
- package/src/commands/migrate/index.ts +16 -0
- package/src/commands/migrate/prompts.ts +477 -0
- package/src/commands/mode.ts +331 -0
- package/src/commands/model.ts +298 -0
- package/src/commands/onboard.ts +205 -0
- package/src/commands/open.ts +169 -0
- package/src/commands/output/stream-json.ts +373 -0
- package/src/commands/parser/chain-parser.ts +370 -0
- package/src/commands/parser/index.ts +29 -0
- package/src/commands/parser/pipe-parser.ts +480 -0
- package/src/commands/parser.ts +588 -0
- package/src/commands/persistence.ts +355 -0
- package/src/commands/progress.ts +18 -0
- package/src/commands/prompt/index.ts +17 -0
- package/src/commands/prompt/validate.ts +621 -0
- package/src/commands/prompt-priority.ts +401 -0
- package/src/commands/registry.ts +374 -0
- package/src/commands/sandbox/index.ts +131 -0
- package/src/commands/security/index.ts +21 -0
- package/src/commands/security/input-sanitizer.ts +168 -0
- package/src/commands/security/permission-checker.ts +456 -0
- package/src/commands/security/sensitive-data.ts +350 -0
- package/src/commands/session/delete.ts +38 -0
- package/src/commands/session/export.ts +39 -0
- package/src/commands/session/index.ts +26 -0
- package/src/commands/session/list.ts +26 -0
- package/src/commands/session/resume.ts +562 -0
- package/src/commands/session/search.ts +434 -0
- package/src/commands/session/show.ts +26 -0
- package/src/commands/settings.ts +368 -0
- package/src/commands/setup.ts +23 -0
- package/src/commands/shell/index.ts +16 -0
- package/src/commands/shell/setup.ts +422 -0
- package/src/commands/shell-init.ts +50 -0
- package/src/commands/shell-integration/index.ts +194 -0
- package/src/commands/skill.ts +1220 -0
- package/src/commands/spec.ts +558 -0
- package/src/commands/status.ts +246 -0
- package/src/commands/theme.ts +211 -0
- package/src/commands/think.ts +551 -0
- package/src/commands/trust.ts +211 -0
- package/src/commands/tutorial.ts +522 -0
- package/src/commands/types.ts +512 -0
- package/src/commands/update.ts +274 -0
- package/src/commands/usage.ts +213 -0
- package/src/commands/user-commands.ts +630 -0
- package/src/commands/utils.ts +142 -0
- package/src/commands/vim.ts +152 -0
- package/src/commands/workflow.ts +257 -0
- package/src/components/header.tsx +25 -0
- package/src/components/input.tsx +25 -0
- package/src/components/message-list.tsx +32 -0
- package/src/components/status-bar.tsx +23 -0
- package/src/index.tsx +614 -0
- package/src/onboarding/__tests__/tutorial.test.ts +740 -0
- package/src/onboarding/index.ts +69 -0
- package/src/onboarding/tips/index.ts +9 -0
- package/src/onboarding/tips/tip-engine.ts +459 -0
- package/src/onboarding/tutorial/index.ts +88 -0
- package/src/onboarding/tutorial/lessons/basics.ts +151 -0
- package/src/onboarding/tutorial/lessons/index.ts +151 -0
- package/src/onboarding/tutorial/lessons/modes.ts +230 -0
- package/src/onboarding/tutorial/lessons/tools.ts +172 -0
- package/src/onboarding/tutorial/progress-tracker.ts +350 -0
- package/src/onboarding/tutorial/storage.ts +249 -0
- package/src/onboarding/tutorial/tutorial-system.ts +462 -0
- package/src/onboarding/tutorial/types.ts +310 -0
- package/src/orchestrator-singleton.ts +129 -0
- package/src/shutdown.ts +33 -0
- package/src/test/e2e/assertions.ts +267 -0
- package/src/test/e2e/fixtures.ts +204 -0
- package/src/test/e2e/harness.ts +575 -0
- package/src/test/e2e/index.ts +57 -0
- package/src/test/e2e/types.ts +228 -0
- package/src/test/fixtures/__tests__/fake-response-loader.test.ts +314 -0
- package/src/test/fixtures/fake-response-loader.ts +314 -0
- package/src/test/fixtures/index.ts +20 -0
- package/src/tui/__tests__/mcp-panel.test.tsx +82 -0
- package/src/tui/__tests__/mcp-wiring.test.tsx +78 -0
- package/src/tui/__tests__/mode-components.test.tsx +395 -0
- package/src/tui/__tests__/permission-ask-flow.test.tsx +138 -0
- package/src/tui/__tests__/sidebar-panel-data.test.tsx +148 -0
- package/src/tui/__tests__/tools-panel-hotkeys.test.tsx +41 -0
- package/src/tui/adapters/agent-adapter.ts +1008 -0
- package/src/tui/adapters/index.ts +48 -0
- package/src/tui/adapters/message-adapter.ts +315 -0
- package/src/tui/adapters/persistence-bridge.ts +331 -0
- package/src/tui/adapters/session-adapter.ts +419 -0
- package/src/tui/buffered-stdout.ts +223 -0
- package/src/tui/components/AgentProgress.tsx +424 -0
- package/src/tui/components/Banner/AsciiArt.ts +160 -0
- package/src/tui/components/Banner/Banner.tsx +355 -0
- package/src/tui/components/Banner/ShimmerContext.tsx +131 -0
- package/src/tui/components/Banner/ShimmerText.tsx +193 -0
- package/src/tui/components/Banner/TypeWriterGradient.tsx +321 -0
- package/src/tui/components/Banner/index.ts +61 -0
- package/src/tui/components/Banner/useShimmer.ts +241 -0
- package/src/tui/components/ChatView.tsx +11 -0
- package/src/tui/components/Checkpoint/CheckpointDiffView.tsx +371 -0
- package/src/tui/components/Checkpoint/SnapshotCheckpointPanel.tsx +440 -0
- package/src/tui/components/Checkpoint/index.ts +19 -0
- package/src/tui/components/CostDisplay.tsx +226 -0
- package/src/tui/components/InitErrorBanner.tsx +122 -0
- package/src/tui/components/Input/Autocomplete.tsx +603 -0
- package/src/tui/components/Input/EnhancedCommandInput.tsx +471 -0
- package/src/tui/components/Input/HighlightedText.tsx +236 -0
- package/src/tui/components/Input/MentionAutocomplete.tsx +375 -0
- package/src/tui/components/Input/TextInput.tsx +1002 -0
- package/src/tui/components/Input/__tests__/Autocomplete.test.tsx +374 -0
- package/src/tui/components/Input/__tests__/TextInput.test.tsx +241 -0
- package/src/tui/components/Input/__tests__/highlight.test.ts +219 -0
- package/src/tui/components/Input/__tests__/slash-command-utils.test.ts +104 -0
- package/src/tui/components/Input/highlight.ts +362 -0
- package/src/tui/components/Input/index.ts +36 -0
- package/src/tui/components/Input/slash-command-utils.ts +135 -0
- package/src/tui/components/Layout.tsx +432 -0
- package/src/tui/components/McpPanel.tsx +137 -0
- package/src/tui/components/MemoryPanel.tsx +448 -0
- package/src/tui/components/Messages/CodeBlock.tsx +527 -0
- package/src/tui/components/Messages/DiffView.tsx +679 -0
- package/src/tui/components/Messages/ImageReference.tsx +89 -0
- package/src/tui/components/Messages/MarkdownBlock.tsx +228 -0
- package/src/tui/components/Messages/MarkdownRenderer.tsx +498 -0
- package/src/tui/components/Messages/MessageBubble.tsx +270 -0
- package/src/tui/components/Messages/MessageList.tsx +1719 -0
- package/src/tui/components/Messages/StreamingText.tsx +216 -0
- package/src/tui/components/Messages/ThinkingBlock.tsx +408 -0
- package/src/tui/components/Messages/ToolResultPreview.tsx +243 -0
- package/src/tui/components/Messages/__tests__/CodeBlock.test.tsx +296 -0
- package/src/tui/components/Messages/__tests__/DiffView.test.tsx +239 -0
- package/src/tui/components/Messages/__tests__/MarkdownRenderer.test.tsx +303 -0
- package/src/tui/components/Messages/__tests__/MessageBubble.test.tsx +268 -0
- package/src/tui/components/Messages/__tests__/MessageList.test.tsx +324 -0
- package/src/tui/components/Messages/__tests__/StreamingText.test.tsx +215 -0
- package/src/tui/components/Messages/index.ts +25 -0
- package/src/tui/components/ModeIndicator.tsx +177 -0
- package/src/tui/components/ModeSelector.tsx +216 -0
- package/src/tui/components/ModelSelector.tsx +339 -0
- package/src/tui/components/OnboardingWizard.tsx +670 -0
- package/src/tui/components/PhaseProgressIndicator.tsx +270 -0
- package/src/tui/components/RateLimitIndicator.tsx +82 -0
- package/src/tui/components/ScreenReaderLayout.tsx +295 -0
- package/src/tui/components/SettingsPanel.tsx +643 -0
- package/src/tui/components/Sidebar/SystemStatusPanel.tsx +284 -0
- package/src/tui/components/Sidebar/index.ts +9 -0
- package/src/tui/components/Status/ModelStatusBar.tsx +270 -0
- package/src/tui/components/Status/index.ts +12 -0
- package/src/tui/components/StatusBar/AgentModeIndicator.tsx +257 -0
- package/src/tui/components/StatusBar/ContextProgress.tsx +167 -0
- package/src/tui/components/StatusBar/FileChangesIndicator.tsx +62 -0
- package/src/tui/components/StatusBar/GitIndicator.tsx +89 -0
- package/src/tui/components/StatusBar/HeaderBar.tsx +126 -0
- package/src/tui/components/StatusBar/ModelIndicator.tsx +157 -0
- package/src/tui/components/StatusBar/PersistenceStatusIndicator.tsx +210 -0
- package/src/tui/components/StatusBar/ResilienceIndicator.tsx +106 -0
- package/src/tui/components/StatusBar/SandboxIndicator.tsx +167 -0
- package/src/tui/components/StatusBar/StatusBar.tsx +368 -0
- package/src/tui/components/StatusBar/ThinkingModeIndicator.tsx +170 -0
- package/src/tui/components/StatusBar/TokenBreakdown.tsx +246 -0
- package/src/tui/components/StatusBar/TokenCounter.tsx +135 -0
- package/src/tui/components/StatusBar/TrustModeIndicator.tsx +130 -0
- package/src/tui/components/StatusBar/WorkspaceIndicator.tsx +86 -0
- package/src/tui/components/StatusBar/__tests__/AgentModeIndicator.test.tsx +193 -0
- package/src/tui/components/StatusBar/__tests__/StatusBar.test.tsx +729 -0
- package/src/tui/components/StatusBar/index.ts +60 -0
- package/src/tui/components/TipBanner.tsx +115 -0
- package/src/tui/components/TodoItem.tsx +208 -0
- package/src/tui/components/TodoPanel.tsx +455 -0
- package/src/tui/components/Tools/ApprovalQueue.tsx +407 -0
- package/src/tui/components/Tools/OptionSelector.tsx +160 -0
- package/src/tui/components/Tools/PermissionDialog.tsx +286 -0
- package/src/tui/components/Tools/ToolParams.tsx +483 -0
- package/src/tui/components/Tools/ToolsPanel.tsx +178 -0
- package/src/tui/components/Tools/__tests__/PermissionDialog.test.tsx +510 -0
- package/src/tui/components/Tools/__tests__/ToolParams.test.tsx +432 -0
- package/src/tui/components/Tools/index.ts +21 -0
- package/src/tui/components/TrustPrompt.tsx +279 -0
- package/src/tui/components/UpdateBanner.tsx +166 -0
- package/src/tui/components/VimModeIndicator.tsx +112 -0
- package/src/tui/components/backtrack/BacktrackControls.tsx +402 -0
- package/src/tui/components/backtrack/index.ts +13 -0
- package/src/tui/components/common/AutoApprovalStatus.tsx +251 -0
- package/src/tui/components/common/CostWarning.tsx +294 -0
- package/src/tui/components/common/DynamicShortcutHints.tsx +209 -0
- package/src/tui/components/common/EnhancedLoadingIndicator.tsx +305 -0
- package/src/tui/components/common/ErrorBoundary.tsx +140 -0
- package/src/tui/components/common/GradientText.tsx +224 -0
- package/src/tui/components/common/HotkeyHelpModal.tsx +193 -0
- package/src/tui/components/common/HotkeyHints.tsx +70 -0
- package/src/tui/components/common/MaxSizedBox.tsx +354 -0
- package/src/tui/components/common/NewMessagesBadge.tsx +65 -0
- package/src/tui/components/common/ProtectedFileLegend.tsx +89 -0
- package/src/tui/components/common/ScrollIndicator.tsx +160 -0
- package/src/tui/components/common/Spinner.tsx +342 -0
- package/src/tui/components/common/StreamingIndicator.tsx +316 -0
- package/src/tui/components/common/VirtualizedList/VirtualizedList.tsx +428 -0
- package/src/tui/components/common/VirtualizedList/hooks/index.ts +19 -0
- package/src/tui/components/common/VirtualizedList/hooks/useBatchedScroll.ts +64 -0
- package/src/tui/components/common/VirtualizedList/hooks/useScrollAnchor.ts +290 -0
- package/src/tui/components/common/VirtualizedList/hooks/useVirtualization.ts +340 -0
- package/src/tui/components/common/VirtualizedList/index.ts +30 -0
- package/src/tui/components/common/VirtualizedList/types.ts +107 -0
- package/src/tui/components/common/__tests__/NewMessagesBadge.test.tsx +74 -0
- package/src/tui/components/common/__tests__/ScrollIndicator.test.tsx +193 -0
- package/src/tui/components/common/index.ts +110 -0
- package/src/tui/components/index.ts +79 -0
- package/src/tui/components/session/CheckpointPanel.tsx +323 -0
- package/src/tui/components/session/RollbackDialog.tsx +169 -0
- package/src/tui/components/session/SessionItem.tsx +136 -0
- package/src/tui/components/session/SessionListPanel.tsx +252 -0
- package/src/tui/components/session/SessionPicker.tsx +449 -0
- package/src/tui/components/session/SessionPreview.tsx +240 -0
- package/src/tui/components/session/__tests__/session.test.tsx +408 -0
- package/src/tui/components/session/index.ts +28 -0
- package/src/tui/components/session/types.ts +116 -0
- package/src/tui/components/theme/__tests__/tokens.test.ts +471 -0
- package/src/tui/components/theme/index.ts +227 -0
- package/src/tui/components/theme/tokens.ts +484 -0
- package/src/tui/config/defaults.ts +134 -0
- package/src/tui/config/index.ts +17 -0
- package/src/tui/context/AnimationContext.tsx +284 -0
- package/src/tui/context/AppContext.tsx +349 -0
- package/src/tui/context/BracketedPasteContext.tsx +372 -0
- package/src/tui/context/LspContext.tsx +192 -0
- package/src/tui/context/McpContext.tsx +325 -0
- package/src/tui/context/MessagesContext.tsx +870 -0
- package/src/tui/context/OverflowContext.tsx +213 -0
- package/src/tui/context/RateLimitContext.tsx +108 -0
- package/src/tui/context/ResilienceContext.tsx +275 -0
- package/src/tui/context/RootProvider.tsx +136 -0
- package/src/tui/context/ScrollContext.tsx +331 -0
- package/src/tui/context/ToolsContext.tsx +702 -0
- package/src/tui/context/__tests__/BracketedPasteContext.test.tsx +416 -0
- package/src/tui/context/index.ts +140 -0
- package/src/tui/enterprise-integration.ts +282 -0
- package/src/tui/hooks/__tests__/useBacktrack.test.tsx +138 -0
- package/src/tui/hooks/__tests__/useBracketedPaste.test.tsx +222 -0
- package/src/tui/hooks/__tests__/useCopyMode.test.tsx +336 -0
- package/src/tui/hooks/__tests__/useHotkeys.ctrl-input.test.tsx +96 -0
- package/src/tui/hooks/__tests__/useHotkeys.test.tsx +454 -0
- package/src/tui/hooks/__tests__/useInputHistory.test.tsx +660 -0
- package/src/tui/hooks/__tests__/useLineBuffer.test.ts +295 -0
- package/src/tui/hooks/__tests__/useModeController.test.ts +137 -0
- package/src/tui/hooks/__tests__/useModeShortcuts.test.tsx +142 -0
- package/src/tui/hooks/__tests__/useScrollController.test.ts +464 -0
- package/src/tui/hooks/__tests__/useVim.test.tsx +531 -0
- package/src/tui/hooks/index.ts +252 -0
- package/src/tui/hooks/useAgentLoop.ts +712 -0
- package/src/tui/hooks/useAlternateBuffer.ts +398 -0
- package/src/tui/hooks/useAnimatedScrollbar.ts +241 -0
- package/src/tui/hooks/useBacktrack.ts +443 -0
- package/src/tui/hooks/useBracketedPaste.ts +104 -0
- package/src/tui/hooks/useCollapsible.ts +240 -0
- package/src/tui/hooks/useCopyMode.ts +382 -0
- package/src/tui/hooks/useCostSummary.ts +75 -0
- package/src/tui/hooks/useDesktopNotification.ts +414 -0
- package/src/tui/hooks/useDiffMode.ts +44 -0
- package/src/tui/hooks/useFileChangeStats.ts +110 -0
- package/src/tui/hooks/useFileSuggestions.ts +284 -0
- package/src/tui/hooks/useFlickerDetector.ts +250 -0
- package/src/tui/hooks/useGitStatus.ts +200 -0
- package/src/tui/hooks/useHotkeys.ts +579 -0
- package/src/tui/hooks/useImagePaste.ts +114 -0
- package/src/tui/hooks/useInputHighlight.ts +145 -0
- package/src/tui/hooks/useInputHistory.ts +246 -0
- package/src/tui/hooks/useKeyboardScroll.ts +209 -0
- package/src/tui/hooks/useLineBuffer.ts +356 -0
- package/src/tui/hooks/useMentionAutocomplete.ts +235 -0
- package/src/tui/hooks/useModeController.ts +167 -0
- package/src/tui/hooks/useModeShortcuts.ts +196 -0
- package/src/tui/hooks/usePermissionHandler.ts +146 -0
- package/src/tui/hooks/usePersistence.ts +480 -0
- package/src/tui/hooks/usePersistenceShortcuts.ts +225 -0
- package/src/tui/hooks/usePlaceholderRotation.ts +143 -0
- package/src/tui/hooks/useProviderStatus.ts +270 -0
- package/src/tui/hooks/useRateLimitStatus.ts +90 -0
- package/src/tui/hooks/useScreenReader.ts +315 -0
- package/src/tui/hooks/useScrollController.ts +450 -0
- package/src/tui/hooks/useScrollEventBatcher.ts +185 -0
- package/src/tui/hooks/useSidebarPanelData.ts +115 -0
- package/src/tui/hooks/useSmoothScroll.ts +202 -0
- package/src/tui/hooks/useSnapshots.ts +300 -0
- package/src/tui/hooks/useStateAndRef.ts +50 -0
- package/src/tui/hooks/useTerminalSize.ts +206 -0
- package/src/tui/hooks/useToolApprovalController.ts +91 -0
- package/src/tui/hooks/useVim.ts +334 -0
- package/src/tui/hooks/useWorkspace.ts +56 -0
- package/src/tui/i18n/__tests__/init.test.ts +278 -0
- package/src/tui/i18n/__tests__/language-config.test.ts +199 -0
- package/src/tui/i18n/__tests__/locale-detection.test.ts +250 -0
- package/src/tui/i18n/__tests__/settings-integration.test.ts +262 -0
- package/src/tui/i18n/index.ts +72 -0
- package/src/tui/i18n/init.ts +131 -0
- package/src/tui/i18n/language-config.ts +106 -0
- package/src/tui/i18n/locale-detection.ts +173 -0
- package/src/tui/i18n/settings-integration.ts +557 -0
- package/src/tui/i18n/tui-namespace.ts +538 -0
- package/src/tui/i18n/types.ts +312 -0
- package/src/tui/index.ts +43 -0
- package/src/tui/lsp-integration.ts +409 -0
- package/src/tui/metrics-integration.ts +366 -0
- package/src/tui/plugins.ts +383 -0
- package/src/tui/resilience.ts +342 -0
- package/src/tui/sandbox-integration.ts +317 -0
- package/src/tui/services/clipboard.ts +348 -0
- package/src/tui/services/fuzzy-search.ts +441 -0
- package/src/tui/services/index.ts +72 -0
- package/src/tui/services/markdown-renderer.ts +565 -0
- package/src/tui/services/open-external.ts +247 -0
- package/src/tui/services/syntax-highlighter.ts +483 -0
- package/src/tui/slash-commands.ts +12 -0
- package/src/tui/theme/index.ts +15 -0
- package/src/tui/theme/provider.tsx +206 -0
- package/src/tui/tip-integration.ts +300 -0
- package/src/tui/types/__tests__/ink-extended.test.ts +121 -0
- package/src/tui/types/ink-extended.ts +87 -0
- package/src/tui/utils/__tests__/bracketedPaste.test.ts +231 -0
- package/src/tui/utils/__tests__/heightEstimator.test.ts +157 -0
- package/src/tui/utils/__tests__/text-width.test.ts +158 -0
- package/src/tui/utils/__tests__/textSanitizer.test.ts +266 -0
- package/src/tui/utils/__tests__/ui-sizing.test.ts +169 -0
- package/src/tui/utils/bracketedPaste.ts +107 -0
- package/src/tui/utils/cursor-manager.ts +131 -0
- package/src/tui/utils/detectTerminal.ts +596 -0
- package/src/tui/utils/findLastSafeSplitPoint.ts +92 -0
- package/src/tui/utils/heightEstimator.ts +198 -0
- package/src/tui/utils/index.ts +91 -0
- package/src/tui/utils/isNarrowWidth.ts +52 -0
- package/src/tui/utils/stdoutGuard.ts +90 -0
- package/src/tui/utils/synchronized-update.ts +70 -0
- package/src/tui/utils/text-width.ts +225 -0
- package/src/tui/utils/textSanitizer.ts +225 -0
- package/src/tui/utils/textUtils.ts +114 -0
- package/src/tui/utils/ui-sizing.ts +192 -0
- package/src/tui-blessed/app.ts +160 -0
- package/src/tui-blessed/index.ts +2 -0
- package/src/tui-blessed/neo-blessed.d.ts +6 -0
- package/src/tui-blessed/test.ts +21 -0
- package/src/tui-blessed/types.ts +14 -0
- package/src/utils/icons.ts +130 -0
- package/src/utils/index.ts +33 -0
- package/src/utils/resume-hint.ts +86 -0
- package/src/version.ts +1 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +35 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Banner Component
|
|
3
|
+
*
|
|
4
|
+
* Main ASCII art banner with gradient colors and shimmer animation.
|
|
5
|
+
* Features ancient parchment/scroll styling for the Vellum brand.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/Banner/Banner
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Box, Text, useStdout } from "ink";
|
|
11
|
+
import Gradient from "ink-gradient";
|
|
12
|
+
import type React from "react";
|
|
13
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
14
|
+
import { useTheme } from "../../theme/index.js";
|
|
15
|
+
import { selectAsciiArt } from "./AsciiArt.js";
|
|
16
|
+
import { interpolateColor } from "./ShimmerText.js";
|
|
17
|
+
import { TypeWriterGradient } from "./TypeWriterGradient.js";
|
|
18
|
+
import { useShimmer } from "./useShimmer.js";
|
|
19
|
+
|
|
20
|
+
// Note: useShimmer & interpolateColor still used by HeaderBanner
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Types
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Props for the Banner component.
|
|
28
|
+
*/
|
|
29
|
+
export interface BannerProps {
|
|
30
|
+
/** Custom ASCII art override (uses responsive selection by default) */
|
|
31
|
+
readonly customArt?: string;
|
|
32
|
+
/** Whether to show version text */
|
|
33
|
+
readonly showVersion?: boolean;
|
|
34
|
+
/** Version string to display */
|
|
35
|
+
readonly version?: string;
|
|
36
|
+
/** Whether shimmer animation is enabled (default: true) */
|
|
37
|
+
readonly animated?: boolean;
|
|
38
|
+
/** Shimmer cycle duration in milliseconds (default: 3000) */
|
|
39
|
+
readonly cycleDuration?: number;
|
|
40
|
+
/** Shimmer update interval in milliseconds (default: 100) */
|
|
41
|
+
readonly updateInterval?: number;
|
|
42
|
+
/** Callback when banner fade-out completes */
|
|
43
|
+
readonly onComplete?: () => void;
|
|
44
|
+
/** Duration to display before fading (ms, default: 2000) */
|
|
45
|
+
readonly displayDuration?: number;
|
|
46
|
+
/** Whether to auto-hide after displayDuration */
|
|
47
|
+
readonly autoHide?: boolean;
|
|
48
|
+
/** Number of animation cycles before stopping (default: infinite) */
|
|
49
|
+
readonly cycles?: number;
|
|
50
|
+
/** Whether to show typewriter effect on startup (default: true) */
|
|
51
|
+
readonly typewriter?: boolean;
|
|
52
|
+
/** Typewriter speed: lines/sec for line mode, chars/sec for char mode (default: 50 lines/sec) */
|
|
53
|
+
readonly typewriterSpeed?: number;
|
|
54
|
+
/** Typewriter mode: 'line' reveals whole lines, 'char' reveals characters (default: 'line') */
|
|
55
|
+
readonly typewriterMode?: "line" | "char";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// Constants
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
/** Interpolated steps per gradient segment for smoother shimmer shifts. */
|
|
63
|
+
const GRADIENT_STEPS_PER_SEGMENT = 12;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Build gradient steps from theme brand colors.
|
|
67
|
+
* Used for shimmer animation in HeaderBanner.
|
|
68
|
+
*/
|
|
69
|
+
function buildGradientSteps(colors: readonly string[], stepsPerSegment: number): string[] {
|
|
70
|
+
if (colors.length === 0) return [];
|
|
71
|
+
if (colors.length === 1) return [colors[0] ?? "#000000"];
|
|
72
|
+
|
|
73
|
+
const steps: string[] = [];
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < colors.length - 1; i += 1) {
|
|
76
|
+
const start = colors[i] ?? "#000000";
|
|
77
|
+
const end = colors[i + 1] ?? start;
|
|
78
|
+
for (let step = 0; step < stepsPerSegment; step += 1) {
|
|
79
|
+
const t = step / stepsPerSegment;
|
|
80
|
+
steps.push(interpolateColor(start, end, t));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
steps.push(colors[colors.length - 1] ?? "#000000");
|
|
85
|
+
return steps;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get parchment gradient colors from theme brand.
|
|
90
|
+
* Returns a mutable array for compatibility with ink-gradient.
|
|
91
|
+
*/
|
|
92
|
+
function getParchmentGradient(brand: {
|
|
93
|
+
accent: string;
|
|
94
|
+
mid: string;
|
|
95
|
+
secondary: string;
|
|
96
|
+
primary: string;
|
|
97
|
+
highlight: string;
|
|
98
|
+
light: string;
|
|
99
|
+
}): string[] {
|
|
100
|
+
return [
|
|
101
|
+
brand.accent, // Saddle Brown
|
|
102
|
+
brand.mid, // Sienna
|
|
103
|
+
brand.secondary, // Peru
|
|
104
|
+
brand.primary, // Goldenrod
|
|
105
|
+
brand.highlight, // Gold
|
|
106
|
+
brand.light, // Lemon Chiffon
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// =============================================================================
|
|
111
|
+
// Sub-Components
|
|
112
|
+
// =============================================================================
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Static version display.
|
|
116
|
+
*/
|
|
117
|
+
interface VersionDisplayProps {
|
|
118
|
+
readonly version: string;
|
|
119
|
+
readonly brand: {
|
|
120
|
+
accent: string;
|
|
121
|
+
secondary: string;
|
|
122
|
+
primary: string;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function VersionDisplay({ version, brand }: VersionDisplayProps): React.JSX.Element {
|
|
127
|
+
return (
|
|
128
|
+
<Box marginTop={1} justifyContent="center">
|
|
129
|
+
<Text color={brand.accent}>v{version}</Text>
|
|
130
|
+
<Text color={brand.secondary}> | </Text>
|
|
131
|
+
<Text color={brand.primary} italic>
|
|
132
|
+
AI-Powered Coding Assistant
|
|
133
|
+
</Text>
|
|
134
|
+
</Box>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// =============================================================================
|
|
139
|
+
// Main Component
|
|
140
|
+
// =============================================================================
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Banner displays the Vellum ASCII art logo with gradient and shimmer effects.
|
|
144
|
+
*
|
|
145
|
+
* Features:
|
|
146
|
+
* - Responsive ASCII art selection based on terminal width
|
|
147
|
+
* - Smooth gradient colors (brown to gold parchment theme)
|
|
148
|
+
* - Animated shimmer sweep effect
|
|
149
|
+
* - Optional version display
|
|
150
|
+
* - Auto-hide capability with callback
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```tsx
|
|
154
|
+
* // Basic usage
|
|
155
|
+
* <Banner />
|
|
156
|
+
*
|
|
157
|
+
* // With version and auto-hide
|
|
158
|
+
* <Banner
|
|
159
|
+
* showVersion
|
|
160
|
+
* version="1.0.0"
|
|
161
|
+
* autoHide
|
|
162
|
+
* displayDuration={3000}
|
|
163
|
+
* onComplete={() => setShowBanner(false)}
|
|
164
|
+
* />
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export function Banner({
|
|
168
|
+
customArt,
|
|
169
|
+
showVersion = false,
|
|
170
|
+
version = "0.1.0",
|
|
171
|
+
// Note: animated, cycleDuration, updateInterval, cycles kept for API compat
|
|
172
|
+
// but shimmer effect is removed - typing goes directly to static gradient
|
|
173
|
+
onComplete,
|
|
174
|
+
displayDuration = 2000,
|
|
175
|
+
autoHide = false,
|
|
176
|
+
typewriter = true,
|
|
177
|
+
typewriterSpeed,
|
|
178
|
+
typewriterMode = "char",
|
|
179
|
+
}: BannerProps): React.JSX.Element | null {
|
|
180
|
+
const { stdout } = useStdout();
|
|
181
|
+
const { theme } = useTheme();
|
|
182
|
+
const [visible, setVisible] = useState(true);
|
|
183
|
+
const [opacity, setOpacity] = useState(1);
|
|
184
|
+
const [typingComplete, setTypingComplete] = useState(!typewriter);
|
|
185
|
+
|
|
186
|
+
// Refs for nested timer cleanup
|
|
187
|
+
const step1Ref = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
188
|
+
const step2Ref = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
189
|
+
|
|
190
|
+
// Get gradient colors from theme
|
|
191
|
+
const parchmentGradient = useMemo(() => getParchmentGradient(theme.brand), [theme.brand]);
|
|
192
|
+
|
|
193
|
+
// Get terminal dimensions for responsive art selection
|
|
194
|
+
const terminalWidth = stdout?.columns ?? 80;
|
|
195
|
+
|
|
196
|
+
// Select appropriate ASCII art
|
|
197
|
+
const asciiArt = useMemo(() => {
|
|
198
|
+
return customArt ?? selectAsciiArt(terminalWidth);
|
|
199
|
+
}, [customArt, terminalWidth]);
|
|
200
|
+
|
|
201
|
+
// Auto-calculate typewriter speed if not provided
|
|
202
|
+
// Target: typing completes at 80% of displayDuration for comfortable viewing
|
|
203
|
+
const autoTypewriterSpeed = useMemo(() => {
|
|
204
|
+
if (typewriterSpeed !== undefined) return typewriterSpeed;
|
|
205
|
+
const charCount = asciiArt.length;
|
|
206
|
+
const typingTimeMs = displayDuration * 0.8; // 80% of display time
|
|
207
|
+
const typingTimeSec = typingTimeMs / 1000;
|
|
208
|
+
// Calculate chars/sec, minimum 500 to avoid being too slow
|
|
209
|
+
return Math.max(500, Math.ceil(charCount / typingTimeSec));
|
|
210
|
+
}, [typewriterSpeed, asciiArt.length, displayDuration]);
|
|
211
|
+
|
|
212
|
+
// Auto-hide timer with smooth transition
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
if (!autoHide) return;
|
|
215
|
+
|
|
216
|
+
// Use simple timeout - start hiding 300ms before end for quicker transition
|
|
217
|
+
const hideTimer = setTimeout(() => {
|
|
218
|
+
// Quick fade: 0.7 -> 0 in 300ms total
|
|
219
|
+
setOpacity(0.7);
|
|
220
|
+
|
|
221
|
+
step1Ref.current = setTimeout(() => {
|
|
222
|
+
setOpacity(0.3);
|
|
223
|
+
}, 100);
|
|
224
|
+
|
|
225
|
+
step2Ref.current = setTimeout(() => {
|
|
226
|
+
setOpacity(0);
|
|
227
|
+
setVisible(false);
|
|
228
|
+
onComplete?.();
|
|
229
|
+
}, 200);
|
|
230
|
+
}, displayDuration - 300);
|
|
231
|
+
|
|
232
|
+
return () => {
|
|
233
|
+
clearTimeout(hideTimer);
|
|
234
|
+
if (step1Ref.current) clearTimeout(step1Ref.current);
|
|
235
|
+
if (step2Ref.current) clearTimeout(step2Ref.current);
|
|
236
|
+
};
|
|
237
|
+
}, [autoHide, displayDuration, onComplete]);
|
|
238
|
+
|
|
239
|
+
if (!visible) return null;
|
|
240
|
+
|
|
241
|
+
// Determine which phase we're in: typing or static gradient (no shimmer)
|
|
242
|
+
const isTypingPhase = typewriter && !typingComplete;
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<Box
|
|
246
|
+
flexDirection="column"
|
|
247
|
+
alignItems="center"
|
|
248
|
+
justifyContent="center"
|
|
249
|
+
paddingX={1}
|
|
250
|
+
paddingY={1}
|
|
251
|
+
>
|
|
252
|
+
{isTypingPhase ? (
|
|
253
|
+
<TypeWriterGradient
|
|
254
|
+
text={asciiArt}
|
|
255
|
+
speed={autoTypewriterSpeed}
|
|
256
|
+
colors={parchmentGradient}
|
|
257
|
+
showCursor
|
|
258
|
+
mode={typewriterMode}
|
|
259
|
+
onComplete={() => setTypingComplete(true)}
|
|
260
|
+
/>
|
|
261
|
+
) : (
|
|
262
|
+
<Gradient colors={parchmentGradient}>{asciiArt}</Gradient>
|
|
263
|
+
)}
|
|
264
|
+
|
|
265
|
+
{showVersion && version ? <VersionDisplay version={version} brand={theme.brand} /> : null}
|
|
266
|
+
|
|
267
|
+
{/* Loading indicator - hide when fading out */}
|
|
268
|
+
{opacity >= 0.5 && (
|
|
269
|
+
<Box marginTop={1}>
|
|
270
|
+
<Text color={theme.brand.accent}>Initializing...</Text>
|
|
271
|
+
</Box>
|
|
272
|
+
)}
|
|
273
|
+
</Box>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Compact banner for narrow terminals or inline use.
|
|
279
|
+
*/
|
|
280
|
+
export function CompactBanner(): React.JSX.Element {
|
|
281
|
+
const { theme } = useTheme();
|
|
282
|
+
const parchmentGradient = useMemo(() => getParchmentGradient(theme.brand), [theme.brand]);
|
|
283
|
+
return (
|
|
284
|
+
<Box>
|
|
285
|
+
<Gradient colors={parchmentGradient}>{"◇ VELLUM ◇"}</Gradient>
|
|
286
|
+
</Box>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Minimal text-only banner.
|
|
292
|
+
*/
|
|
293
|
+
export function MinimalBanner(): React.JSX.Element {
|
|
294
|
+
const { theme } = useTheme();
|
|
295
|
+
return (
|
|
296
|
+
<Text color={theme.brand.primary} bold>
|
|
297
|
+
VELLUM
|
|
298
|
+
</Text>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// =============================================================================
|
|
303
|
+
// Header Banner (Compact CLI Header)
|
|
304
|
+
// =============================================================================
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Props for the HeaderBanner component.
|
|
308
|
+
*/
|
|
309
|
+
export interface HeaderBannerProps {
|
|
310
|
+
/** Whether shimmer animation is enabled (default: true) */
|
|
311
|
+
readonly animated?: boolean;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Compact header banner with animated gradient for use at CLI top.
|
|
316
|
+
* Displays a single-line parchment-styled logo with continuous shimmer.
|
|
317
|
+
*/
|
|
318
|
+
export function HeaderBanner({ animated = true }: HeaderBannerProps): React.JSX.Element {
|
|
319
|
+
const { theme } = useTheme();
|
|
320
|
+
const { position } = useShimmer({
|
|
321
|
+
cycleDuration: 3000,
|
|
322
|
+
enabled: animated,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Get gradient colors from theme
|
|
326
|
+
const parchmentGradient = useMemo(() => getParchmentGradient(theme.brand), [theme.brand]);
|
|
327
|
+
|
|
328
|
+
// Build gradient steps for shimmer animation
|
|
329
|
+
const gradientSteps = useMemo(
|
|
330
|
+
() => buildGradientSteps(parchmentGradient, GRADIENT_STEPS_PER_SEGMENT),
|
|
331
|
+
[parchmentGradient]
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Calculate discrete color shift to reduce re-computation
|
|
335
|
+
const colorShift = Math.floor(position * gradientSteps.length) % gradientSteps.length;
|
|
336
|
+
|
|
337
|
+
// Shift gradient colors based on discrete colorShift value
|
|
338
|
+
const shiftedColors = useMemo(() => {
|
|
339
|
+
if (colorShift === 0) return gradientSteps;
|
|
340
|
+
return [...gradientSteps.slice(colorShift), ...gradientSteps.slice(0, colorShift)];
|
|
341
|
+
}, [colorShift, gradientSteps]);
|
|
342
|
+
|
|
343
|
+
// Compact ASCII art for header (single line with decorations)
|
|
344
|
+
const headerArt = "═══╣ ◊ ╠═══ V E L L U M ═══╣ ◊ ╠═══";
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<Box justifyContent="center" width="100%">
|
|
348
|
+
{animated ? (
|
|
349
|
+
<Gradient colors={shiftedColors}>{headerArt}</Gradient>
|
|
350
|
+
) : (
|
|
351
|
+
<Gradient colors={parchmentGradient}>{headerArt}</Gradient>
|
|
352
|
+
)}
|
|
353
|
+
</Box>
|
|
354
|
+
);
|
|
355
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShimmerContext - Shared shimmer state provider
|
|
3
|
+
*
|
|
4
|
+
* Provides a single shared shimmer timer for all consumers,
|
|
5
|
+
* eliminating multiple independent timers that can cause animation flickering.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/Banner/ShimmerContext
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createContext, type ReactNode, useContext } from "react";
|
|
11
|
+
import { type ShimmerState, useShimmer } from "./useShimmer.js";
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Types
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Context value extends ShimmerState for shared shimmer state
|
|
19
|
+
*/
|
|
20
|
+
type ShimmerContextValue = ShimmerState;
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Context
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default shimmer context value (inactive state)
|
|
28
|
+
*/
|
|
29
|
+
const defaultShimmerValue: ShimmerContextValue = {
|
|
30
|
+
position: 0,
|
|
31
|
+
intensity: 0,
|
|
32
|
+
cycleCount: 0,
|
|
33
|
+
isComplete: false,
|
|
34
|
+
isActive: false,
|
|
35
|
+
pause: () => {},
|
|
36
|
+
resume: () => {},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const ShimmerContext = createContext<ShimmerContextValue>(defaultShimmerValue);
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Provider
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Props for the ShimmerProvider component.
|
|
47
|
+
*/
|
|
48
|
+
export interface ShimmerProviderProps {
|
|
49
|
+
/** Child components that will share the shimmer state */
|
|
50
|
+
readonly children: ReactNode;
|
|
51
|
+
/** Whether shimmer animation is enabled (default: true) */
|
|
52
|
+
readonly enabled?: boolean;
|
|
53
|
+
/** Maximum number of cycles before stopping (undefined = infinite) */
|
|
54
|
+
readonly maxCycles?: number;
|
|
55
|
+
/** Duration of one complete shimmer cycle in milliseconds (default: 3000) */
|
|
56
|
+
readonly cycleDuration?: number;
|
|
57
|
+
/** Update interval in milliseconds (default: 100 for smoother motion) */
|
|
58
|
+
readonly updateInterval?: number;
|
|
59
|
+
/** Callback when max cycles completed */
|
|
60
|
+
readonly onComplete?: () => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* ShimmerProvider creates a single shared shimmer timer for all child components.
|
|
65
|
+
*
|
|
66
|
+
* Use this to wrap multiple shimmer-consuming components to ensure they all
|
|
67
|
+
* animate in sync and share a single timer instead of each running their own.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```tsx
|
|
71
|
+
* <ShimmerProvider enabled maxCycles={3} cycleDuration={3000}>
|
|
72
|
+
* <Banner />
|
|
73
|
+
* <HeaderBanner />
|
|
74
|
+
* <Header />
|
|
75
|
+
* </ShimmerProvider>
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function ShimmerProvider({
|
|
79
|
+
children,
|
|
80
|
+
enabled = true,
|
|
81
|
+
maxCycles,
|
|
82
|
+
cycleDuration = 3000,
|
|
83
|
+
updateInterval = 100,
|
|
84
|
+
onComplete,
|
|
85
|
+
}: ShimmerProviderProps): React.JSX.Element {
|
|
86
|
+
const shimmer = useShimmer({
|
|
87
|
+
enabled,
|
|
88
|
+
maxCycles,
|
|
89
|
+
cycleDuration,
|
|
90
|
+
updateInterval,
|
|
91
|
+
onComplete,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return <ShimmerContext.Provider value={shimmer}>{children}</ShimmerContext.Provider>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// Hook
|
|
99
|
+
// =============================================================================
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Hook to access shared shimmer state from ShimmerProvider.
|
|
103
|
+
*
|
|
104
|
+
* If used outside a ShimmerProvider, returns default inactive state.
|
|
105
|
+
* For standalone shimmer (not sharing timer), use useShimmer() directly instead.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* function AnimatedComponent() {
|
|
110
|
+
* const { position, intensity, isComplete } = useSharedShimmer();
|
|
111
|
+
* // Use position (0-1) and intensity (0-1) for animation
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export function useSharedShimmer(): ShimmerContextValue {
|
|
116
|
+
return useContext(ShimmerContext);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Hook to check if component is inside a ShimmerProvider.
|
|
121
|
+
* Useful for components that want to optionally use shared state.
|
|
122
|
+
*/
|
|
123
|
+
export function useIsInShimmerProvider(): boolean {
|
|
124
|
+
const context = useContext(ShimmerContext);
|
|
125
|
+
// Check if we have the actual shimmer functions (not default no-ops)
|
|
126
|
+
return (
|
|
127
|
+
context.isActive !== defaultShimmerValue.isActive ||
|
|
128
|
+
context.position !== defaultShimmerValue.position ||
|
|
129
|
+
context.cycleCount !== defaultShimmerValue.cycleCount
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShimmerText Component for Banner
|
|
3
|
+
*
|
|
4
|
+
* Renders text with a flowing shimmer/glow effect.
|
|
5
|
+
* Uses the useShimmer hook for animation state.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/Banner/ShimmerText
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Text } from "ink";
|
|
11
|
+
import React, { useMemo } from "react";
|
|
12
|
+
import { calculateShimmerIntensity, type ShimmerConfig, useShimmer } from "./useShimmer.js";
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Types
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Props for the BannerShimmerText component.
|
|
20
|
+
*/
|
|
21
|
+
export interface BannerShimmerTextProps {
|
|
22
|
+
/** Text content to display with shimmer effect */
|
|
23
|
+
readonly children: string;
|
|
24
|
+
/** Base color (default: '#8B4513' - saddle brown) */
|
|
25
|
+
readonly baseColor?: string;
|
|
26
|
+
/** Highlight color for shimmer peak (default: '#FFD700' - gold) */
|
|
27
|
+
readonly highlightColor?: string;
|
|
28
|
+
/** Shimmer animation configuration */
|
|
29
|
+
readonly shimmerConfig?: ShimmerConfig;
|
|
30
|
+
/** Whether shimmer is enabled (default: true) */
|
|
31
|
+
readonly enabled?: boolean;
|
|
32
|
+
/** Width of the shimmer effect (0-1, default: 0.15) */
|
|
33
|
+
readonly shimmerWidth?: number;
|
|
34
|
+
/** Whether text should be bold */
|
|
35
|
+
readonly bold?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Color Interpolation
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse hex color to RGB components.
|
|
44
|
+
*/
|
|
45
|
+
function hexToRgb(hex: string): { r: number; g: number; b: number } {
|
|
46
|
+
const cleaned = hex.replace("#", "");
|
|
47
|
+
const bigint = parseInt(cleaned, 16);
|
|
48
|
+
return {
|
|
49
|
+
r: (bigint >> 16) & 255,
|
|
50
|
+
g: (bigint >> 8) & 255,
|
|
51
|
+
b: bigint & 255,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Convert RGB to hex string.
|
|
57
|
+
*/
|
|
58
|
+
function rgbToHex(r: number, g: number, b: number): string {
|
|
59
|
+
const toHex = (n: number) => Math.round(n).toString(16).padStart(2, "0");
|
|
60
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Interpolate between two colors based on intensity (0-1).
|
|
65
|
+
*/
|
|
66
|
+
export function interpolateColor(
|
|
67
|
+
baseColor: string,
|
|
68
|
+
highlightColor: string,
|
|
69
|
+
intensity: number
|
|
70
|
+
): string {
|
|
71
|
+
const base = hexToRgb(baseColor);
|
|
72
|
+
const highlight = hexToRgb(highlightColor);
|
|
73
|
+
|
|
74
|
+
const r = base.r + (highlight.r - base.r) * intensity;
|
|
75
|
+
const g = base.g + (highlight.g - base.g) * intensity;
|
|
76
|
+
const b = base.b + (highlight.b - base.b) * intensity;
|
|
77
|
+
|
|
78
|
+
return rgbToHex(r, g, b);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// Character-Level Shimmer Component
|
|
83
|
+
// =============================================================================
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Single character with shimmer color applied.
|
|
87
|
+
* Memoized to prevent unnecessary re-renders.
|
|
88
|
+
*/
|
|
89
|
+
interface ShimmerCharProps {
|
|
90
|
+
readonly char: string;
|
|
91
|
+
readonly color: string;
|
|
92
|
+
readonly bold?: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const ShimmerChar = React.memo(function ShimmerChar({
|
|
96
|
+
char,
|
|
97
|
+
color,
|
|
98
|
+
bold,
|
|
99
|
+
}: ShimmerCharProps): React.JSX.Element {
|
|
100
|
+
return (
|
|
101
|
+
<Text color={color} bold={bold}>
|
|
102
|
+
{char}
|
|
103
|
+
</Text>
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// =============================================================================
|
|
108
|
+
// Main Component
|
|
109
|
+
// =============================================================================
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* BannerShimmerText renders text with a flowing shimmer effect.
|
|
113
|
+
*
|
|
114
|
+
* The shimmer sweeps from left to right, creating a glow effect
|
|
115
|
+
* that transitions from the base color to the highlight color.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```tsx
|
|
119
|
+
* <BannerShimmerText
|
|
120
|
+
* baseColor="#8B4513"
|
|
121
|
+
* highlightColor="#FFD700"
|
|
122
|
+
* >
|
|
123
|
+
* VELLUM
|
|
124
|
+
* </BannerShimmerText>
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export function BannerShimmerText({
|
|
128
|
+
children,
|
|
129
|
+
baseColor = "#8B4513",
|
|
130
|
+
highlightColor = "#FFD700",
|
|
131
|
+
shimmerConfig,
|
|
132
|
+
enabled = true,
|
|
133
|
+
shimmerWidth = 0.15,
|
|
134
|
+
bold = false,
|
|
135
|
+
}: BannerShimmerTextProps): React.JSX.Element {
|
|
136
|
+
const { position } = useShimmer({
|
|
137
|
+
...shimmerConfig,
|
|
138
|
+
enabled,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Calculate color for each character based on shimmer position
|
|
142
|
+
const coloredChars = useMemo(() => {
|
|
143
|
+
const chars = children.split("");
|
|
144
|
+
const totalChars = chars.length;
|
|
145
|
+
|
|
146
|
+
return chars.map((char, index) => {
|
|
147
|
+
// Skip whitespace - render as-is
|
|
148
|
+
if (char === " " || char === "\n" || char === "\t") {
|
|
149
|
+
return { char, color: baseColor, isWhitespace: true };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const intensity = calculateShimmerIntensity(index, totalChars, position, shimmerWidth);
|
|
153
|
+
|
|
154
|
+
const color = interpolateColor(baseColor, highlightColor, intensity);
|
|
155
|
+
return { char, color, isWhitespace: false };
|
|
156
|
+
});
|
|
157
|
+
}, [children, position, baseColor, highlightColor, shimmerWidth]);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<Text>
|
|
161
|
+
{coloredChars.map((item, index) => (
|
|
162
|
+
<ShimmerChar
|
|
163
|
+
key={`${index}-${item.char}`}
|
|
164
|
+
char={item.char}
|
|
165
|
+
color={item.isWhitespace ? baseColor : item.color}
|
|
166
|
+
bold={bold}
|
|
167
|
+
/>
|
|
168
|
+
))}
|
|
169
|
+
</Text>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Multi-line shimmer text that processes each line.
|
|
175
|
+
*/
|
|
176
|
+
export interface MultiLineShimmerProps extends Omit<BannerShimmerTextProps, "children"> {
|
|
177
|
+
/** Lines of text to render */
|
|
178
|
+
readonly lines: string[];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function MultiLineShimmer({ lines, ...props }: MultiLineShimmerProps): React.JSX.Element {
|
|
182
|
+
return (
|
|
183
|
+
<>
|
|
184
|
+
{lines.map((line, index) => (
|
|
185
|
+
// Using index as key is acceptable here since lines array is static and order never changes
|
|
186
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: lines are static banner content that never reorders
|
|
187
|
+
<BannerShimmerText key={index} {...props}>
|
|
188
|
+
{line}
|
|
189
|
+
</BannerShimmerText>
|
|
190
|
+
))}
|
|
191
|
+
</>
|
|
192
|
+
);
|
|
193
|
+
}
|