@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,579 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useHotkeys Hook (T042)
|
|
3
|
+
*
|
|
4
|
+
* React hook for managing keyboard shortcuts in the TUI.
|
|
5
|
+
* Provides a unified system for hotkey registration with support for
|
|
6
|
+
* modifier keys (Ctrl, Shift, Alt) and scoped handlers.
|
|
7
|
+
*
|
|
8
|
+
* @module @vellum/cli
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Key } from "ink";
|
|
12
|
+
import { useInput } from "ink";
|
|
13
|
+
import { useCallback, useMemo } from "react";
|
|
14
|
+
import { useApp } from "../context/AppContext.js";
|
|
15
|
+
import type { ExtendedKey } from "../types/ink-extended.js";
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Types
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Scope for hotkey activation.
|
|
23
|
+
* - global: Active regardless of focused area
|
|
24
|
+
* - input: Active only when input area is focused
|
|
25
|
+
* - messages: Active only when message area is focused
|
|
26
|
+
*/
|
|
27
|
+
export type HotkeyScope = "global" | "input" | "messages" | "tools";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Definition for a single hotkey binding.
|
|
31
|
+
*/
|
|
32
|
+
export interface HotkeyDefinition {
|
|
33
|
+
/** The key to match (e.g., 'c', 'l', 'v', 'f1') */
|
|
34
|
+
readonly key: string;
|
|
35
|
+
/** Whether Ctrl modifier is required */
|
|
36
|
+
readonly ctrl?: boolean;
|
|
37
|
+
/** Whether Shift modifier is required */
|
|
38
|
+
readonly shift?: boolean;
|
|
39
|
+
/** Whether Alt/Option modifier is required */
|
|
40
|
+
readonly alt?: boolean;
|
|
41
|
+
/** Handler function to execute when hotkey is triggered */
|
|
42
|
+
readonly handler: () => void;
|
|
43
|
+
/** Human-readable description for help display */
|
|
44
|
+
readonly description?: string;
|
|
45
|
+
/** Scope where the hotkey is active (default: 'global') */
|
|
46
|
+
readonly scope?: HotkeyScope;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Options for the useHotkeys hook.
|
|
51
|
+
*/
|
|
52
|
+
export interface UseHotkeysOptions {
|
|
53
|
+
/** Whether hotkey handling is enabled (default: true) */
|
|
54
|
+
readonly enabled?: boolean;
|
|
55
|
+
/** Override scope for all hotkeys in this hook */
|
|
56
|
+
readonly scope?: HotkeyScope;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Return value of useHotkeys hook.
|
|
61
|
+
*/
|
|
62
|
+
export interface UseHotkeysReturn {
|
|
63
|
+
/** Get all registered hotkey definitions */
|
|
64
|
+
readonly hotkeys: ReadonlyArray<HotkeyDefinition>;
|
|
65
|
+
/** Check if a key combination matches any hotkey */
|
|
66
|
+
readonly matchHotkey: (
|
|
67
|
+
key: string,
|
|
68
|
+
modifiers: { ctrl?: boolean; shift?: boolean; alt?: boolean }
|
|
69
|
+
) => HotkeyDefinition | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// =============================================================================
|
|
73
|
+
// Key Normalization
|
|
74
|
+
// =============================================================================
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Normalize a key string to a consistent format.
|
|
78
|
+
* Handles special keys and case normalization.
|
|
79
|
+
*/
|
|
80
|
+
function normalizeKey(key: string): string {
|
|
81
|
+
// Handle special keys
|
|
82
|
+
const specialKeys: Record<string, string> = {
|
|
83
|
+
escape: "escape",
|
|
84
|
+
esc: "escape",
|
|
85
|
+
return: "return",
|
|
86
|
+
enter: "return",
|
|
87
|
+
tab: "tab",
|
|
88
|
+
backspace: "backspace",
|
|
89
|
+
delete: "delete",
|
|
90
|
+
up: "up",
|
|
91
|
+
down: "down",
|
|
92
|
+
left: "left",
|
|
93
|
+
right: "right",
|
|
94
|
+
pageup: "pageup",
|
|
95
|
+
pagedown: "pagedown",
|
|
96
|
+
home: "home",
|
|
97
|
+
end: "end",
|
|
98
|
+
f1: "f1",
|
|
99
|
+
f2: "f2",
|
|
100
|
+
f3: "f3",
|
|
101
|
+
f4: "f4",
|
|
102
|
+
f5: "f5",
|
|
103
|
+
f6: "f6",
|
|
104
|
+
f7: "f7",
|
|
105
|
+
f8: "f8",
|
|
106
|
+
f9: "f9",
|
|
107
|
+
f10: "f10",
|
|
108
|
+
f11: "f11",
|
|
109
|
+
f12: "f12",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const normalized = key.toLowerCase();
|
|
113
|
+
return specialKeys[normalized] ?? normalized;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get the ink key flag for a special key.
|
|
118
|
+
*/
|
|
119
|
+
function getInkKeyFlag(inkKey: Key, normalizedHotkey: string): boolean | undefined {
|
|
120
|
+
const inkKeyMap: Record<string, boolean | undefined> = {
|
|
121
|
+
return: inkKey.return,
|
|
122
|
+
escape: inkKey.escape,
|
|
123
|
+
tab: inkKey.tab,
|
|
124
|
+
backspace: inkKey.backspace,
|
|
125
|
+
delete: inkKey.delete,
|
|
126
|
+
up: inkKey.upArrow,
|
|
127
|
+
down: inkKey.downArrow,
|
|
128
|
+
left: inkKey.leftArrow,
|
|
129
|
+
right: inkKey.rightArrow,
|
|
130
|
+
pageup: inkKey.pageUp,
|
|
131
|
+
pagedown: inkKey.pageDown,
|
|
132
|
+
};
|
|
133
|
+
return inkKeyMap[normalizedHotkey];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if the key component matches (ignoring modifiers).
|
|
138
|
+
*/
|
|
139
|
+
function keyMatches(normalizedInput: string, normalizedHotkey: string, inkKey: Key): boolean {
|
|
140
|
+
// Windows/Ink may deliver Ctrl+\\ as the raw control character 0x1C (File Separator)
|
|
141
|
+
// instead of a literal "\\" input key. Treat that control byte as "\\" for matching.
|
|
142
|
+
if (normalizedHotkey === "\\" && normalizedInput === "\x1c") {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Direct string match
|
|
147
|
+
if (normalizedInput === normalizedHotkey) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Check for special key flags from Ink
|
|
152
|
+
const inkFlag = getInkKeyFlag(inkKey, normalizedHotkey);
|
|
153
|
+
return inkFlag === true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Check if modifiers match exactly.
|
|
158
|
+
*/
|
|
159
|
+
function modifiersMatch(inkKey: Key, hotkey: HotkeyDefinition): boolean {
|
|
160
|
+
const ctrlRequired = hotkey.ctrl ?? false;
|
|
161
|
+
const shiftRequired = hotkey.shift ?? false;
|
|
162
|
+
const altRequired = hotkey.alt ?? false;
|
|
163
|
+
|
|
164
|
+
const ctrlPressed = inkKey.ctrl ?? false;
|
|
165
|
+
const shiftPressed = inkKey.shift ?? false;
|
|
166
|
+
const altPressed = inkKey.meta ?? false; // Ink uses 'meta' for Alt
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
ctrlRequired === ctrlPressed && shiftRequired === shiftPressed && altRequired === altPressed
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check if a key event matches a hotkey definition.
|
|
175
|
+
*/
|
|
176
|
+
function matchesHotkey(inputKey: string, inkKey: Key, hotkey: HotkeyDefinition): boolean {
|
|
177
|
+
const normalizedInput = normalizeKey(inputKey);
|
|
178
|
+
const normalizedHotkey = normalizeKey(hotkey.key);
|
|
179
|
+
|
|
180
|
+
// Check key matches first
|
|
181
|
+
if (!keyMatches(normalizedInput, normalizedHotkey, inkKey)) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Then check modifiers
|
|
186
|
+
// If Ink delivers Ctrl+\\ as \x1c without setting the ctrl modifier, interpret \x1c as Ctrl+\\.
|
|
187
|
+
const inkKeyWithCtrlBackslashFix: Key =
|
|
188
|
+
normalizedHotkey === "\\" && normalizedInput === "\x1c" ? { ...inkKey, ctrl: true } : inkKey;
|
|
189
|
+
|
|
190
|
+
return modifiersMatch(inkKeyWithCtrlBackslashFix, hotkey);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// =============================================================================
|
|
194
|
+
// Hook Implementation
|
|
195
|
+
// =============================================================================
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* useHotkeys hook for managing keyboard shortcuts.
|
|
199
|
+
*
|
|
200
|
+
* Provides a declarative way to register hotkeys with support for:
|
|
201
|
+
* - Modifier keys (Ctrl, Shift, Alt)
|
|
202
|
+
* - Scoped handlers (global, input, messages)
|
|
203
|
+
* - Help text generation
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```tsx
|
|
207
|
+
* function MyComponent() {
|
|
208
|
+
* const { dispatch } = useApp();
|
|
209
|
+
*
|
|
210
|
+
* useHotkeys([
|
|
211
|
+
* {
|
|
212
|
+
* key: 'c',
|
|
213
|
+
* ctrl: true,
|
|
214
|
+
* handler: () => process.exit(0),
|
|
215
|
+
* description: 'Exit application',
|
|
216
|
+
* scope: 'global',
|
|
217
|
+
* },
|
|
218
|
+
* {
|
|
219
|
+
* key: 'v',
|
|
220
|
+
* ctrl: true,
|
|
221
|
+
* handler: () => dispatch({ type: 'TOGGLE_VIM_MODE' }),
|
|
222
|
+
* description: 'Toggle Vim mode',
|
|
223
|
+
* scope: 'input',
|
|
224
|
+
* },
|
|
225
|
+
* ]);
|
|
226
|
+
*
|
|
227
|
+
* return <Box>...</Box>;
|
|
228
|
+
* }
|
|
229
|
+
* ```
|
|
230
|
+
*
|
|
231
|
+
* @param hotkeys - Array of hotkey definitions to register
|
|
232
|
+
* @param options - Configuration options
|
|
233
|
+
* @returns Object with hotkey utilities
|
|
234
|
+
*/
|
|
235
|
+
export function useHotkeys(
|
|
236
|
+
hotkeys: ReadonlyArray<HotkeyDefinition>,
|
|
237
|
+
options: UseHotkeysOptions = {}
|
|
238
|
+
): UseHotkeysReturn {
|
|
239
|
+
const { enabled = true, scope: optionsScope } = options;
|
|
240
|
+
const { state } = useApp();
|
|
241
|
+
|
|
242
|
+
// Memoize the hotkey list with scope overrides applied
|
|
243
|
+
const resolvedHotkeys = useMemo(
|
|
244
|
+
() =>
|
|
245
|
+
hotkeys.map((hotkey) => ({
|
|
246
|
+
...hotkey,
|
|
247
|
+
scope: optionsScope ?? hotkey.scope ?? "global",
|
|
248
|
+
})),
|
|
249
|
+
[hotkeys, optionsScope]
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Match a key combination against registered hotkeys
|
|
253
|
+
const matchHotkey = useCallback(
|
|
254
|
+
(
|
|
255
|
+
key: string,
|
|
256
|
+
modifiers: { ctrl?: boolean; shift?: boolean; alt?: boolean }
|
|
257
|
+
): HotkeyDefinition | null => {
|
|
258
|
+
const inkKey = {
|
|
259
|
+
ctrl: modifiers.ctrl ?? false,
|
|
260
|
+
shift: modifiers.shift ?? false,
|
|
261
|
+
meta: modifiers.alt ?? false,
|
|
262
|
+
escape: key.toLowerCase() === "escape",
|
|
263
|
+
return: key === "\r" || key.toLowerCase() === "return",
|
|
264
|
+
tab: key === "\t" || key.toLowerCase() === "tab",
|
|
265
|
+
backspace: key === "\x7f" || key.toLowerCase() === "backspace",
|
|
266
|
+
delete: key.toLowerCase() === "delete",
|
|
267
|
+
upArrow: key.toLowerCase() === "up",
|
|
268
|
+
downArrow: key.toLowerCase() === "down",
|
|
269
|
+
leftArrow: key.toLowerCase() === "left",
|
|
270
|
+
rightArrow: key.toLowerCase() === "right",
|
|
271
|
+
pageUp: key.toLowerCase() === "pageup",
|
|
272
|
+
pageDown: key.toLowerCase() === "pagedown",
|
|
273
|
+
home: key.toLowerCase() === "home",
|
|
274
|
+
end: key.toLowerCase() === "end",
|
|
275
|
+
} as ExtendedKey;
|
|
276
|
+
|
|
277
|
+
for (const hotkey of resolvedHotkeys) {
|
|
278
|
+
if (matchesHotkey(key, inkKey, hotkey)) {
|
|
279
|
+
return hotkey;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return null;
|
|
284
|
+
},
|
|
285
|
+
[resolvedHotkeys]
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Check if a hotkey should be active based on current scope
|
|
289
|
+
const isScopeActive = useCallback(
|
|
290
|
+
(hotkeyScope: HotkeyScope): boolean => {
|
|
291
|
+
if (hotkeyScope === "global") {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
// Map focusedArea to hotkey scope
|
|
295
|
+
const focusedArea = state.focusedArea;
|
|
296
|
+
if (hotkeyScope === "input" && focusedArea === "input") {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
if (hotkeyScope === "messages" && focusedArea === "messages") {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
if (hotkeyScope === "tools" && focusedArea === "tools") {
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
},
|
|
307
|
+
[state.focusedArea]
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Handle keyboard input via Ink's useInput
|
|
311
|
+
useInput(
|
|
312
|
+
(input, key) => {
|
|
313
|
+
if (!enabled) return;
|
|
314
|
+
|
|
315
|
+
// Find matching hotkey
|
|
316
|
+
for (const hotkey of resolvedHotkeys) {
|
|
317
|
+
if (matchesHotkey(input, key, hotkey)) {
|
|
318
|
+
// Check scope
|
|
319
|
+
if (isScopeActive(hotkey.scope as HotkeyScope)) {
|
|
320
|
+
hotkey.handler();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
{ isActive: enabled }
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
hotkeys: resolvedHotkeys,
|
|
331
|
+
matchHotkey,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// =============================================================================
|
|
336
|
+
// Standard Hotkey Presets
|
|
337
|
+
// =============================================================================
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Standard hotkey definitions for common TUI operations.
|
|
341
|
+
* Use these as a starting point and customize as needed.
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* ```tsx
|
|
345
|
+
* import { useHotkeys, createStandardHotkeys } from './useHotkeys.js';
|
|
346
|
+
*
|
|
347
|
+
* function App() {
|
|
348
|
+
* const { dispatch } = useApp();
|
|
349
|
+
*
|
|
350
|
+
* useHotkeys(createStandardHotkeys({
|
|
351
|
+
* onInterrupt: () => process.exit(0),
|
|
352
|
+
* onClearScreen: () => console.clear(),
|
|
353
|
+
* onToggleVim: () => dispatch({ type: 'TOGGLE_VIM_MODE' }),
|
|
354
|
+
* onShowHelp: () => setShowHelp(true),
|
|
355
|
+
* }));
|
|
356
|
+
*
|
|
357
|
+
* return <Box>...</Box>;
|
|
358
|
+
* }
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
export interface StandardHotkeyHandlers {
|
|
362
|
+
/** Ctrl+C: Interrupt/cancel current operation */
|
|
363
|
+
readonly onInterrupt?: () => void;
|
|
364
|
+
/** Ctrl+L: Clear the screen */
|
|
365
|
+
readonly onClearScreen?: () => void;
|
|
366
|
+
/** Ctrl+V: Toggle Vim editing mode */
|
|
367
|
+
readonly onToggleVim?: () => void;
|
|
368
|
+
/** Ctrl+Y: Accept suggestion */
|
|
369
|
+
readonly onAcceptSuggestion?: () => void;
|
|
370
|
+
/** Ctrl+T: Toggle thinking display */
|
|
371
|
+
readonly onToggleThinking?: () => void;
|
|
372
|
+
/** F1: Show help */
|
|
373
|
+
readonly onShowHelp?: () => void;
|
|
374
|
+
/** Ctrl+Shift+1: Switch to trust mode 1 (paranoid) */
|
|
375
|
+
readonly onTrustMode1?: () => void;
|
|
376
|
+
/** Ctrl+Shift+2: Switch to trust mode 2 (cautious) */
|
|
377
|
+
readonly onTrustMode2?: () => void;
|
|
378
|
+
/** Ctrl+Shift+3: Switch to trust mode 3 (balanced) */
|
|
379
|
+
readonly onTrustMode3?: () => void;
|
|
380
|
+
/** Ctrl+Shift+4: Switch to trust mode 4 (trusting) */
|
|
381
|
+
readonly onTrustMode4?: () => void;
|
|
382
|
+
/** Ctrl+Shift+5: Switch to trust mode 5 (yolo) */
|
|
383
|
+
readonly onTrustMode5?: () => void;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Create standard hotkey definitions with provided handlers.
|
|
388
|
+
*
|
|
389
|
+
* @param handlers - Object with handler functions for standard hotkeys
|
|
390
|
+
* @returns Array of hotkey definitions
|
|
391
|
+
*/
|
|
392
|
+
export function createStandardHotkeys(
|
|
393
|
+
handlers: StandardHotkeyHandlers
|
|
394
|
+
): ReadonlyArray<HotkeyDefinition> {
|
|
395
|
+
const hotkeys: HotkeyDefinition[] = [];
|
|
396
|
+
|
|
397
|
+
if (handlers.onInterrupt) {
|
|
398
|
+
hotkeys.push({
|
|
399
|
+
key: "c",
|
|
400
|
+
ctrl: true,
|
|
401
|
+
handler: handlers.onInterrupt,
|
|
402
|
+
description: "Interrupt/cancel",
|
|
403
|
+
scope: "global",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (handlers.onClearScreen) {
|
|
408
|
+
hotkeys.push({
|
|
409
|
+
key: "l",
|
|
410
|
+
ctrl: true,
|
|
411
|
+
handler: handlers.onClearScreen,
|
|
412
|
+
description: "Clear screen",
|
|
413
|
+
scope: "global",
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (handlers.onToggleVim) {
|
|
418
|
+
hotkeys.push({
|
|
419
|
+
key: "v",
|
|
420
|
+
ctrl: true,
|
|
421
|
+
handler: handlers.onToggleVim,
|
|
422
|
+
description: "Toggle Vim mode",
|
|
423
|
+
scope: "input",
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (handlers.onAcceptSuggestion) {
|
|
428
|
+
hotkeys.push({
|
|
429
|
+
key: "y",
|
|
430
|
+
ctrl: true,
|
|
431
|
+
handler: handlers.onAcceptSuggestion,
|
|
432
|
+
description: "Accept suggestion",
|
|
433
|
+
scope: "input",
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (handlers.onToggleThinking) {
|
|
438
|
+
hotkeys.push({
|
|
439
|
+
key: "t",
|
|
440
|
+
ctrl: true,
|
|
441
|
+
handler: handlers.onToggleThinking,
|
|
442
|
+
description: "Toggle thinking display",
|
|
443
|
+
scope: "global",
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (handlers.onShowHelp) {
|
|
448
|
+
hotkeys.push({
|
|
449
|
+
key: "f1",
|
|
450
|
+
handler: handlers.onShowHelp,
|
|
451
|
+
description: "Show help",
|
|
452
|
+
scope: "global",
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Trust mode hotkeys (Ctrl+Shift+1-5)
|
|
457
|
+
if (handlers.onTrustMode1) {
|
|
458
|
+
hotkeys.push({
|
|
459
|
+
key: "!",
|
|
460
|
+
ctrl: true,
|
|
461
|
+
shift: true,
|
|
462
|
+
handler: handlers.onTrustMode1,
|
|
463
|
+
description: "Paranoid mode",
|
|
464
|
+
scope: "global",
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (handlers.onTrustMode2) {
|
|
469
|
+
hotkeys.push({
|
|
470
|
+
key: "@",
|
|
471
|
+
ctrl: true,
|
|
472
|
+
shift: true,
|
|
473
|
+
handler: handlers.onTrustMode2,
|
|
474
|
+
description: "Cautious mode",
|
|
475
|
+
scope: "global",
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (handlers.onTrustMode3) {
|
|
480
|
+
hotkeys.push({
|
|
481
|
+
key: "#",
|
|
482
|
+
ctrl: true,
|
|
483
|
+
shift: true,
|
|
484
|
+
handler: handlers.onTrustMode3,
|
|
485
|
+
description: "Balanced mode",
|
|
486
|
+
scope: "global",
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (handlers.onTrustMode4) {
|
|
491
|
+
hotkeys.push({
|
|
492
|
+
key: "$",
|
|
493
|
+
ctrl: true,
|
|
494
|
+
shift: true,
|
|
495
|
+
handler: handlers.onTrustMode4,
|
|
496
|
+
description: "Trusting mode",
|
|
497
|
+
scope: "global",
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (handlers.onTrustMode5) {
|
|
502
|
+
hotkeys.push({
|
|
503
|
+
key: "%",
|
|
504
|
+
ctrl: true,
|
|
505
|
+
shift: true,
|
|
506
|
+
handler: handlers.onTrustMode5,
|
|
507
|
+
description: "YOLO mode",
|
|
508
|
+
scope: "global",
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return hotkeys;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Format hotkey for display in help text.
|
|
517
|
+
*
|
|
518
|
+
* @param hotkey - Hotkey definition to format
|
|
519
|
+
* @returns Formatted string like "Ctrl+Shift+V"
|
|
520
|
+
*/
|
|
521
|
+
export function formatHotkey(hotkey: HotkeyDefinition): string {
|
|
522
|
+
const parts: string[] = [];
|
|
523
|
+
|
|
524
|
+
if (hotkey.ctrl) parts.push("Ctrl");
|
|
525
|
+
if (hotkey.shift) parts.push("Shift");
|
|
526
|
+
if (hotkey.alt) parts.push("Alt");
|
|
527
|
+
|
|
528
|
+
// Format the key
|
|
529
|
+
const key = hotkey.key.length === 1 ? hotkey.key.toUpperCase() : hotkey.key;
|
|
530
|
+
parts.push(key);
|
|
531
|
+
|
|
532
|
+
return parts.join("+");
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Generate help text from hotkey definitions.
|
|
537
|
+
*
|
|
538
|
+
* @param hotkeys - Array of hotkey definitions
|
|
539
|
+
* @returns Formatted help text string
|
|
540
|
+
*/
|
|
541
|
+
export function generateHotkeyHelp(hotkeys: ReadonlyArray<HotkeyDefinition>): string {
|
|
542
|
+
const lines: string[] = ["Keyboard Shortcuts:", ""];
|
|
543
|
+
|
|
544
|
+
// Group by scope
|
|
545
|
+
const byScope = new Map<HotkeyScope, HotkeyDefinition[]>();
|
|
546
|
+
|
|
547
|
+
for (const hotkey of hotkeys) {
|
|
548
|
+
const scope = (hotkey.scope ?? "global") as HotkeyScope;
|
|
549
|
+
if (!byScope.has(scope)) {
|
|
550
|
+
byScope.set(scope, []);
|
|
551
|
+
}
|
|
552
|
+
const scopeArray = byScope.get(scope);
|
|
553
|
+
if (scopeArray) {
|
|
554
|
+
scopeArray.push(hotkey);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Format each scope
|
|
559
|
+
const scopeLabels: Record<HotkeyScope, string> = {
|
|
560
|
+
global: "Global",
|
|
561
|
+
input: "Input Area",
|
|
562
|
+
messages: "Messages Area",
|
|
563
|
+
tools: "Tools Area",
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
for (const [scope, scopeHotkeys] of byScope) {
|
|
567
|
+
lines.push(` ${scopeLabels[scope]}:`);
|
|
568
|
+
|
|
569
|
+
for (const hotkey of scopeHotkeys) {
|
|
570
|
+
const formatted = formatHotkey(hotkey);
|
|
571
|
+
const description = hotkey.description ?? "No description";
|
|
572
|
+
lines.push(` ${formatted.padEnd(20)} ${description}`);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
lines.push("");
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return lines.join("\n");
|
|
579
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Paste Hook
|
|
3
|
+
*
|
|
4
|
+
* Handles pasting images from clipboard into the TUI.
|
|
5
|
+
* Placeholder implementation - to be expanded.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/hooks/useImagePaste
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useCallback, useState } from "react";
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Types
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
export interface PastedImage {
|
|
17
|
+
/** Image data as base64 */
|
|
18
|
+
data: string;
|
|
19
|
+
/** MIME type of the image */
|
|
20
|
+
mimeType: string;
|
|
21
|
+
/** Image dimensions */
|
|
22
|
+
width?: number;
|
|
23
|
+
height?: number;
|
|
24
|
+
/** File name if available */
|
|
25
|
+
filename?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UseImagePasteOptions {
|
|
29
|
+
/** Whether image paste is enabled */
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
/** Callback when image is pasted */
|
|
32
|
+
onImagePaste?: (image: PastedImage) => void;
|
|
33
|
+
/** Maximum image size in bytes */
|
|
34
|
+
maxSize?: number;
|
|
35
|
+
/** Allowed MIME types */
|
|
36
|
+
allowedTypes?: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface UseImagePasteResult {
|
|
40
|
+
/** Last pasted image */
|
|
41
|
+
pastedImage: PastedImage | null;
|
|
42
|
+
/** Whether paste is in progress */
|
|
43
|
+
isPasting: boolean;
|
|
44
|
+
/** Any error that occurred */
|
|
45
|
+
error: Error | null;
|
|
46
|
+
/** Clear the pasted image */
|
|
47
|
+
clear: () => void;
|
|
48
|
+
/** Handle paste event manually */
|
|
49
|
+
handlePaste: (data: string, mimeType: string) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Hook
|
|
54
|
+
// =============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Hook to handle image pasting from clipboard
|
|
58
|
+
*/
|
|
59
|
+
export function useImagePaste(options: UseImagePasteOptions = {}): UseImagePasteResult {
|
|
60
|
+
const { enabled = true, onImagePaste, maxSize, allowedTypes } = options;
|
|
61
|
+
const [pastedImage, setPastedImage] = useState<PastedImage | null>(null);
|
|
62
|
+
const [isPasting, setIsPasting] = useState(false);
|
|
63
|
+
const [error, setError] = useState<Error | null>(null);
|
|
64
|
+
|
|
65
|
+
const clear = useCallback(() => {
|
|
66
|
+
setPastedImage(null);
|
|
67
|
+
setError(null);
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
const handlePaste = useCallback(
|
|
71
|
+
(data: string, mimeType: string) => {
|
|
72
|
+
if (!enabled) return;
|
|
73
|
+
|
|
74
|
+
setIsPasting(true);
|
|
75
|
+
setError(null);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Validate MIME type
|
|
79
|
+
if (allowedTypes && !allowedTypes.includes(mimeType)) {
|
|
80
|
+
throw new Error(`Unsupported image type: ${mimeType}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Validate size
|
|
84
|
+
const sizeInBytes = (data.length * 3) / 4; // Approximate base64 decode size
|
|
85
|
+
if (maxSize && sizeInBytes > maxSize) {
|
|
86
|
+
throw new Error(`Image too large: ${sizeInBytes} bytes (max: ${maxSize})`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const image: PastedImage = {
|
|
90
|
+
data,
|
|
91
|
+
mimeType,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
setPastedImage(image);
|
|
95
|
+
onImagePaste?.(image);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
98
|
+
} finally {
|
|
99
|
+
setIsPasting(false);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
[enabled, onImagePaste, maxSize, allowedTypes]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
pastedImage,
|
|
107
|
+
isPasting,
|
|
108
|
+
error,
|
|
109
|
+
clear,
|
|
110
|
+
handlePaste,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default useImagePaste;
|