@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,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamingText Component Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the StreamingText component which displays text with an animated
|
|
5
|
+
* blinking cursor while streaming.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/Messages/__tests__/StreamingText.test
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { render } from "ink-testing-library";
|
|
11
|
+
import { act } from "react";
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
13
|
+
import { AnimationProvider } from "../../../context/AnimationContext.js";
|
|
14
|
+
import { StreamingText } from "../StreamingText.js";
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Test Setup
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
vi.useFakeTimers();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.useRealTimers();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Tests
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
describe("StreamingText", () => {
|
|
33
|
+
describe("rendering", () => {
|
|
34
|
+
it("renders text content", () => {
|
|
35
|
+
const { lastFrame } = render(<StreamingText content="Hello world" isStreaming={false} />);
|
|
36
|
+
|
|
37
|
+
expect(lastFrame()).toContain("Hello world");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("renders empty content", () => {
|
|
41
|
+
const { lastFrame } = render(<StreamingText content="" isStreaming={false} />);
|
|
42
|
+
|
|
43
|
+
// Should render without cursor when not streaming
|
|
44
|
+
expect(lastFrame()).toBe("");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("cursor behavior", () => {
|
|
49
|
+
it("shows cursor when streaming", () => {
|
|
50
|
+
// Disable typewriter effect for immediate content display test
|
|
51
|
+
const { lastFrame } = render(
|
|
52
|
+
<StreamingText content="Typing..." isStreaming={true} typewriterEffect={false} />
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(lastFrame()).toContain("Typing...");
|
|
56
|
+
expect(lastFrame()).toContain("▊"); // Default cursor
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("hides cursor when not streaming", () => {
|
|
60
|
+
const { lastFrame } = render(<StreamingText content="Complete" isStreaming={false} />);
|
|
61
|
+
|
|
62
|
+
expect(lastFrame()).toContain("Complete");
|
|
63
|
+
expect(lastFrame()).not.toContain("▊");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("uses custom cursor character", () => {
|
|
67
|
+
// Disable typewriter effect for immediate content display test
|
|
68
|
+
const { lastFrame } = render(
|
|
69
|
+
<StreamingText
|
|
70
|
+
content="Custom"
|
|
71
|
+
isStreaming={true}
|
|
72
|
+
cursorChar="_"
|
|
73
|
+
typewriterEffect={false}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(lastFrame()).toContain("Custom");
|
|
78
|
+
expect(lastFrame()).toContain("_");
|
|
79
|
+
expect(lastFrame()).not.toContain("▊");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("blinks cursor when animation frames advance", async () => {
|
|
83
|
+
const { lastFrame } = render(
|
|
84
|
+
<AnimationProvider tickInterval={100}>
|
|
85
|
+
<StreamingText
|
|
86
|
+
content="Blinking"
|
|
87
|
+
isStreaming={true}
|
|
88
|
+
cursorBlink={true}
|
|
89
|
+
typewriterEffect={false}
|
|
90
|
+
/>
|
|
91
|
+
</AnimationProvider>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Initially cursor is visible
|
|
95
|
+
expect(lastFrame()).toContain("▊");
|
|
96
|
+
|
|
97
|
+
// After 4 ticks (frame=4), cursor should be hidden
|
|
98
|
+
await act(async () => {
|
|
99
|
+
vi.advanceTimersByTime(400);
|
|
100
|
+
});
|
|
101
|
+
expect(lastFrame()).not.toContain("▊");
|
|
102
|
+
|
|
103
|
+
// After 4 more ticks (frame=8), cursor should be visible again
|
|
104
|
+
await act(async () => {
|
|
105
|
+
vi.advanceTimersByTime(400);
|
|
106
|
+
});
|
|
107
|
+
expect(lastFrame()).toContain("▊");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("does not blink cursor when cursorBlink is false", () => {
|
|
111
|
+
const { lastFrame } = render(
|
|
112
|
+
<AnimationProvider tickInterval={100}>
|
|
113
|
+
<StreamingText
|
|
114
|
+
content="No blink"
|
|
115
|
+
isStreaming={true}
|
|
116
|
+
cursorBlink={false}
|
|
117
|
+
typewriterEffect={false}
|
|
118
|
+
/>
|
|
119
|
+
</AnimationProvider>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Cursor is visible
|
|
123
|
+
expect(lastFrame()).toContain("▊");
|
|
124
|
+
|
|
125
|
+
// After 500ms, cursor should still be visible (no blinking)
|
|
126
|
+
act(() => {
|
|
127
|
+
vi.advanceTimersByTime(500);
|
|
128
|
+
});
|
|
129
|
+
expect(lastFrame()).toContain("▊");
|
|
130
|
+
|
|
131
|
+
// After another 500ms, still visible
|
|
132
|
+
act(() => {
|
|
133
|
+
vi.advanceTimersByTime(500);
|
|
134
|
+
});
|
|
135
|
+
expect(lastFrame()).toContain("▊");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("onComplete callback", () => {
|
|
140
|
+
it("calls onComplete when streaming changes from true to false", () => {
|
|
141
|
+
const onComplete = vi.fn();
|
|
142
|
+
|
|
143
|
+
const { rerender } = render(
|
|
144
|
+
<StreamingText content="Streaming..." isStreaming={true} onComplete={onComplete} />
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Not called while streaming
|
|
148
|
+
expect(onComplete).not.toHaveBeenCalled();
|
|
149
|
+
|
|
150
|
+
// Update to not streaming
|
|
151
|
+
act(() => {
|
|
152
|
+
rerender(<StreamingText content="Done!" isStreaming={false} onComplete={onComplete} />);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Now onComplete should be called
|
|
156
|
+
expect(onComplete).toHaveBeenCalledTimes(1);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("does not call onComplete when initially not streaming", () => {
|
|
160
|
+
const onComplete = vi.fn();
|
|
161
|
+
|
|
162
|
+
render(<StreamingText content="Not streaming" isStreaming={false} onComplete={onComplete} />);
|
|
163
|
+
|
|
164
|
+
// Should not be called on initial render when not streaming
|
|
165
|
+
expect(onComplete).not.toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("does not call onComplete when streaming remains true", () => {
|
|
169
|
+
const onComplete = vi.fn();
|
|
170
|
+
|
|
171
|
+
const { rerender } = render(
|
|
172
|
+
<StreamingText content="Still streaming..." isStreaming={true} onComplete={onComplete} />
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Update content but keep streaming
|
|
176
|
+
act(() => {
|
|
177
|
+
rerender(
|
|
178
|
+
<StreamingText content="More content..." isStreaming={true} onComplete={onComplete} />
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(onComplete).not.toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("works without onComplete callback", () => {
|
|
186
|
+
const { rerender } = render(<StreamingText content="Streaming..." isStreaming={true} />);
|
|
187
|
+
|
|
188
|
+
// Should not throw when onComplete is undefined
|
|
189
|
+
expect(() => {
|
|
190
|
+
act(() => {
|
|
191
|
+
rerender(<StreamingText content="Done!" isStreaming={false} />);
|
|
192
|
+
});
|
|
193
|
+
}).not.toThrow();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("animation cleanup", () => {
|
|
198
|
+
it("cleans up animation interval on unmount", () => {
|
|
199
|
+
const clearIntervalSpy = vi.spyOn(global, "clearInterval");
|
|
200
|
+
|
|
201
|
+
const { unmount } = render(
|
|
202
|
+
<AnimationProvider tickInterval={100}>
|
|
203
|
+
<StreamingText content="Test" isStreaming={true} cursorBlink={true} />
|
|
204
|
+
</AnimationProvider>
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
act(() => {
|
|
208
|
+
unmount();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(clearIntervalSpy).toHaveBeenCalled();
|
|
212
|
+
clearIntervalSpy.mockRestore();
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Components
|
|
3
|
+
*
|
|
4
|
+
* Components for displaying messages and conversation history in the Vellum TUI.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { CodeBlock, type CodeBlockProps } from "./CodeBlock.js";
|
|
8
|
+
export { DiffView, type DiffViewProps } from "./DiffView.js";
|
|
9
|
+
export { MarkdownBlock, type MarkdownBlockProps, MarkdownBlockSync } from "./MarkdownBlock.js";
|
|
10
|
+
export { MarkdownRenderer, type MarkdownRendererProps } from "./MarkdownRenderer.js";
|
|
11
|
+
export { MessageBubble, type MessageBubbleProps } from "./MessageBubble.js";
|
|
12
|
+
export { MessageList, type MessageListProps } from "./MessageList.js";
|
|
13
|
+
export { StreamingText, type StreamingTextProps } from "./StreamingText.js";
|
|
14
|
+
export {
|
|
15
|
+
CompactThinkingIndicator,
|
|
16
|
+
type CompactThinkingIndicatorProps,
|
|
17
|
+
ThinkingBlock,
|
|
18
|
+
type ThinkingBlockProps,
|
|
19
|
+
} from "./ThinkingBlock.js";
|
|
20
|
+
export {
|
|
21
|
+
SHELL_TOOL_MAX_LINES,
|
|
22
|
+
TOOL_RESULT_MAX_LINES,
|
|
23
|
+
ToolResultPreview,
|
|
24
|
+
type ToolResultPreviewProps,
|
|
25
|
+
} from "./ToolResultPreview.js";
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModeIndicator Component (T043, T044)
|
|
3
|
+
*
|
|
4
|
+
* TUI component for displaying the current coding mode with visual styling.
|
|
5
|
+
* Shows mode icon, name, and optional spec phase progress.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/ModeIndicator
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CodingMode, SpecPhase } from "@vellum/core";
|
|
11
|
+
import { SPEC_PHASE_CONFIG, SPEC_PHASES } from "@vellum/core";
|
|
12
|
+
import { getIcons } from "@vellum/shared";
|
|
13
|
+
import { Box, Text } from "ink";
|
|
14
|
+
import type React from "react";
|
|
15
|
+
import { useMemo } from "react";
|
|
16
|
+
import { useTheme } from "../theme/index.js";
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Props for the ModeIndicator component.
|
|
24
|
+
*/
|
|
25
|
+
export interface ModeIndicatorProps {
|
|
26
|
+
/** The current coding mode */
|
|
27
|
+
readonly mode: CodingMode;
|
|
28
|
+
/** Current spec phase (only for spec mode, 1-6) */
|
|
29
|
+
readonly specPhase?: number;
|
|
30
|
+
/** Whether to show in compact mode (icon only) */
|
|
31
|
+
readonly compact?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// Constants
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get mode icons for each coding mode.
|
|
40
|
+
* Uses centralized icon system with auto-detection.
|
|
41
|
+
*/
|
|
42
|
+
function getModeIcons(): Record<CodingMode, string> {
|
|
43
|
+
const icons = getIcons();
|
|
44
|
+
return {
|
|
45
|
+
vibe: icons.vibe,
|
|
46
|
+
plan: icons.plan,
|
|
47
|
+
spec: icons.spec,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Mode display names.
|
|
53
|
+
*/
|
|
54
|
+
const MODE_NAMES: Record<CodingMode, string> = {
|
|
55
|
+
vibe: "vibe",
|
|
56
|
+
plan: "plan",
|
|
57
|
+
spec: "spec",
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Mode colors mapped to theme semantic colors.
|
|
62
|
+
* - vibe: green (success) - fast, autonomous
|
|
63
|
+
* - plan: blue (info) - structured planning
|
|
64
|
+
* - spec: purple (primary) - detailed specification
|
|
65
|
+
*/
|
|
66
|
+
const MODE_COLOR_KEYS: Record<CodingMode, "success" | "info" | "primary"> = {
|
|
67
|
+
vibe: "success",
|
|
68
|
+
plan: "info",
|
|
69
|
+
spec: "primary",
|
|
70
|
+
} as const;
|
|
71
|
+
|
|
72
|
+
// =============================================================================
|
|
73
|
+
// Helper Functions
|
|
74
|
+
// =============================================================================
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get the color for a coding mode from the theme.
|
|
78
|
+
*/
|
|
79
|
+
function getModeColor(mode: CodingMode, theme: ReturnType<typeof useTheme>["theme"]): string {
|
|
80
|
+
const colorKey = MODE_COLOR_KEYS[mode];
|
|
81
|
+
return theme.colors[colorKey];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get the phase name from phase number.
|
|
86
|
+
*/
|
|
87
|
+
function getPhaseName(phaseNumber: number): string {
|
|
88
|
+
const phase = SPEC_PHASES[phaseNumber - 1] as SpecPhase | undefined;
|
|
89
|
+
if (!phase) {
|
|
90
|
+
return `Phase ${phaseNumber}`;
|
|
91
|
+
}
|
|
92
|
+
return SPEC_PHASE_CONFIG[phase].name;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// =============================================================================
|
|
96
|
+
// ModeIndicator Component
|
|
97
|
+
// =============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* ModeIndicator - Displays the current coding mode with visual styling.
|
|
101
|
+
*
|
|
102
|
+
* Features:
|
|
103
|
+
* - Mode icon (using centralized icon system)
|
|
104
|
+
* - Color-coded mode name (green, blue, purple)
|
|
105
|
+
* - Spec phase progress indicator when in spec mode
|
|
106
|
+
* - Compact mode for space-constrained layouts
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```tsx
|
|
110
|
+
* // Basic usage
|
|
111
|
+
* <ModeIndicator mode="vibe" />
|
|
112
|
+
*
|
|
113
|
+
* // With spec phase
|
|
114
|
+
* <ModeIndicator mode="spec" specPhase={3} />
|
|
115
|
+
*
|
|
116
|
+
* // Compact mode
|
|
117
|
+
* <ModeIndicator mode="plan" compact />
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function ModeIndicator({
|
|
121
|
+
mode,
|
|
122
|
+
specPhase,
|
|
123
|
+
compact = false,
|
|
124
|
+
}: ModeIndicatorProps): React.ReactElement {
|
|
125
|
+
const { theme } = useTheme();
|
|
126
|
+
|
|
127
|
+
// Get mode display properties
|
|
128
|
+
const modeIcons = getModeIcons();
|
|
129
|
+
const icon = modeIcons[mode];
|
|
130
|
+
const name = MODE_NAMES[mode];
|
|
131
|
+
const color = getModeColor(mode, theme);
|
|
132
|
+
|
|
133
|
+
// Build phase display for spec mode
|
|
134
|
+
const phaseDisplay = useMemo(() => {
|
|
135
|
+
if (mode !== "spec" || specPhase === undefined) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const totalPhases = SPEC_PHASES.length;
|
|
140
|
+
const validPhase = Math.max(1, Math.min(specPhase, totalPhases));
|
|
141
|
+
|
|
142
|
+
if (compact) {
|
|
143
|
+
return ` (${validPhase}/${totalPhases})`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const phaseName = getPhaseName(validPhase);
|
|
147
|
+
return ` (${validPhase}/${totalPhases}: ${phaseName})`;
|
|
148
|
+
}, [mode, specPhase, compact]);
|
|
149
|
+
|
|
150
|
+
// Compact mode: icon only with optional phase
|
|
151
|
+
if (compact) {
|
|
152
|
+
return (
|
|
153
|
+
<Box>
|
|
154
|
+
<Text color={color}>
|
|
155
|
+
{icon}
|
|
156
|
+
{phaseDisplay}
|
|
157
|
+
</Text>
|
|
158
|
+
</Box>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Full mode: icon + name + phase
|
|
163
|
+
return (
|
|
164
|
+
<Box>
|
|
165
|
+
<Text color={color}>
|
|
166
|
+
{icon} {name}
|
|
167
|
+
{phaseDisplay}
|
|
168
|
+
</Text>
|
|
169
|
+
</Box>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// =============================================================================
|
|
174
|
+
// Exports
|
|
175
|
+
// =============================================================================
|
|
176
|
+
|
|
177
|
+
export type { CodingMode };
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModeSelector Component (T045)
|
|
3
|
+
*
|
|
4
|
+
* TUI component for selecting coding modes with keyboard navigation.
|
|
5
|
+
* Renders three selectable options (vibe, plan, spec) with visual feedback.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/ModeSelector
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CodingMode } from "@vellum/core";
|
|
11
|
+
import { BUILTIN_CODING_MODES, CODING_MODES } from "@vellum/core";
|
|
12
|
+
import { getIcons } from "@vellum/shared";
|
|
13
|
+
import { Box, Text, useInput } from "ink";
|
|
14
|
+
import type React from "react";
|
|
15
|
+
import { useCallback, useState } from "react";
|
|
16
|
+
import { useTUITranslation } from "../i18n/index.js";
|
|
17
|
+
import { useTheme } from "../theme/index.js";
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Types
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Props for the ModeSelector component.
|
|
25
|
+
*/
|
|
26
|
+
export interface ModeSelectorProps {
|
|
27
|
+
/** The currently active mode */
|
|
28
|
+
readonly currentMode: CodingMode;
|
|
29
|
+
/** Callback when a mode is selected */
|
|
30
|
+
readonly onSelect: (mode: CodingMode) => void;
|
|
31
|
+
/** Whether the selector is focused/active */
|
|
32
|
+
readonly isActive?: boolean;
|
|
33
|
+
/** Whether to show mode descriptions */
|
|
34
|
+
readonly showDescriptions?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Constants
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get mode icons for visual identification.
|
|
43
|
+
* Uses centralized icon system with auto-detection.
|
|
44
|
+
*/
|
|
45
|
+
function getModeIcons(): Record<CodingMode, string> {
|
|
46
|
+
const icons = getIcons();
|
|
47
|
+
return {
|
|
48
|
+
vibe: icons.vibe,
|
|
49
|
+
plan: icons.plan,
|
|
50
|
+
spec: icons.spec,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Keyboard shortcuts for quick mode selection.
|
|
56
|
+
*/
|
|
57
|
+
const MODE_SHORTCUTS: Record<CodingMode, string> = {
|
|
58
|
+
vibe: "1",
|
|
59
|
+
plan: "2",
|
|
60
|
+
spec: "3",
|
|
61
|
+
} as const;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Mode colors mapped to theme semantic colors.
|
|
65
|
+
*/
|
|
66
|
+
const MODE_COLOR_KEYS: Record<CodingMode, "success" | "info" | "primary"> = {
|
|
67
|
+
vibe: "success",
|
|
68
|
+
plan: "info",
|
|
69
|
+
spec: "primary",
|
|
70
|
+
} as const;
|
|
71
|
+
|
|
72
|
+
// =============================================================================
|
|
73
|
+
// ModeSelector Component
|
|
74
|
+
// =============================================================================
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* ModeSelector - Interactive component for selecting coding modes.
|
|
78
|
+
*
|
|
79
|
+
* Features:
|
|
80
|
+
* - Arrow key navigation (up/down or j/k)
|
|
81
|
+
* - Number shortcuts (1, 2, 3)
|
|
82
|
+
* - Enter to confirm selection
|
|
83
|
+
* - Visual indication of current mode
|
|
84
|
+
* - Highlight of focused option
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```tsx
|
|
88
|
+
* function MyComponent() {
|
|
89
|
+
* const [mode, setMode] = useState<CodingMode>('vibe');
|
|
90
|
+
*
|
|
91
|
+
* return (
|
|
92
|
+
* <ModeSelector
|
|
93
|
+
* currentMode={mode}
|
|
94
|
+
* onSelect={setMode}
|
|
95
|
+
* isActive
|
|
96
|
+
* />
|
|
97
|
+
* );
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function ModeSelector({
|
|
102
|
+
currentMode,
|
|
103
|
+
onSelect,
|
|
104
|
+
isActive = true,
|
|
105
|
+
showDescriptions = true,
|
|
106
|
+
}: ModeSelectorProps): React.ReactElement {
|
|
107
|
+
const { theme } = useTheme();
|
|
108
|
+
const { t } = useTUITranslation();
|
|
109
|
+
const modes = CODING_MODES;
|
|
110
|
+
|
|
111
|
+
// Track focused index for keyboard navigation
|
|
112
|
+
const [focusedIndex, setFocusedIndex] = useState(() => modes.indexOf(currentMode));
|
|
113
|
+
|
|
114
|
+
// Handle keyboard input
|
|
115
|
+
useInput(
|
|
116
|
+
useCallback(
|
|
117
|
+
(input: string, key) => {
|
|
118
|
+
if (!isActive) return;
|
|
119
|
+
|
|
120
|
+
// Arrow navigation
|
|
121
|
+
if (key.upArrow || input === "k") {
|
|
122
|
+
setFocusedIndex((prev) => (prev > 0 ? prev - 1 : modes.length - 1));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (key.downArrow || input === "j") {
|
|
127
|
+
setFocusedIndex((prev) => (prev < modes.length - 1 ? prev + 1 : 0));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Confirm selection
|
|
132
|
+
if (key.return) {
|
|
133
|
+
const selectedMode = modes[focusedIndex];
|
|
134
|
+
if (selectedMode) {
|
|
135
|
+
onSelect(selectedMode);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Number shortcuts
|
|
141
|
+
const modeIndex = parseInt(input, 10) - 1;
|
|
142
|
+
if (modeIndex >= 0 && modeIndex < modes.length) {
|
|
143
|
+
const selectedMode = modes[modeIndex];
|
|
144
|
+
if (selectedMode) {
|
|
145
|
+
setFocusedIndex(modeIndex);
|
|
146
|
+
onSelect(selectedMode);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
[isActive, focusedIndex, modes, onSelect]
|
|
151
|
+
),
|
|
152
|
+
{ isActive }
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<Box flexDirection="column" paddingX={1}>
|
|
157
|
+
<Box marginBottom={1}>
|
|
158
|
+
<Text bold>{t("modeSelector.title")}</Text>
|
|
159
|
+
</Box>
|
|
160
|
+
|
|
161
|
+
{modes.map((mode, index) => {
|
|
162
|
+
const isFocused = index === focusedIndex && isActive;
|
|
163
|
+
const isCurrent = mode === currentMode;
|
|
164
|
+
const modeIcons = getModeIcons();
|
|
165
|
+
const icon = modeIcons[mode];
|
|
166
|
+
const shortcut = MODE_SHORTCUTS[mode];
|
|
167
|
+
const colorKey = MODE_COLOR_KEYS[mode];
|
|
168
|
+
const color = theme.colors[colorKey];
|
|
169
|
+
const config = BUILTIN_CODING_MODES[mode];
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<Box key={mode} flexDirection="column">
|
|
173
|
+
<Box>
|
|
174
|
+
{/* Focus indicator */}
|
|
175
|
+
<Text color={isFocused ? color : undefined}>{isFocused ? "❯ " : " "}</Text>
|
|
176
|
+
|
|
177
|
+
{/* Shortcut key */}
|
|
178
|
+
<Text dimColor>[{shortcut}]</Text>
|
|
179
|
+
<Text> </Text>
|
|
180
|
+
|
|
181
|
+
{/* Mode icon and name */}
|
|
182
|
+
<Text color={color} bold={isCurrent}>
|
|
183
|
+
{icon} {mode}
|
|
184
|
+
</Text>
|
|
185
|
+
|
|
186
|
+
{/* Current indicator */}
|
|
187
|
+
{isCurrent && (
|
|
188
|
+
<Text color={theme.semantic.text.muted}> {t("modeSelector.current")}</Text>
|
|
189
|
+
)}
|
|
190
|
+
</Box>
|
|
191
|
+
|
|
192
|
+
{/* Description (when enabled) */}
|
|
193
|
+
{showDescriptions && (
|
|
194
|
+
<Box marginLeft={6}>
|
|
195
|
+
<Text dimColor wrap="truncate-end">
|
|
196
|
+
{config.description}
|
|
197
|
+
</Text>
|
|
198
|
+
</Box>
|
|
199
|
+
)}
|
|
200
|
+
</Box>
|
|
201
|
+
);
|
|
202
|
+
})}
|
|
203
|
+
|
|
204
|
+
{/* Help text */}
|
|
205
|
+
<Box marginTop={1}>
|
|
206
|
+
<Text dimColor>{t("modeSelector.keybindings")}</Text>
|
|
207
|
+
</Box>
|
|
208
|
+
</Box>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// =============================================================================
|
|
213
|
+
// Exports
|
|
214
|
+
// =============================================================================
|
|
215
|
+
|
|
216
|
+
export type { CodingMode };
|