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