@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,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
|
+
}
|