@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,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal Size Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for subscribing to terminal resize events.
|
|
5
|
+
* Provides reactive terminal dimensions with debouncing.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/hooks/useTerminalSize
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
+
import { isNarrowWidth } from "../utils/isNarrowWidth.js";
|
|
12
|
+
import { getMaxContentWidth, getTerminalHeight, getTerminalWidth } from "../utils/ui-sizing.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Terminal size state returned by useTerminalSize hook.
|
|
16
|
+
*/
|
|
17
|
+
export interface TerminalSize {
|
|
18
|
+
/** Terminal width in columns */
|
|
19
|
+
width: number;
|
|
20
|
+
/** Terminal height in rows */
|
|
21
|
+
height: number;
|
|
22
|
+
/** Whether terminal is narrow (<= 80 columns) */
|
|
23
|
+
isNarrow: boolean;
|
|
24
|
+
/** Maximum content width for responsive layouts */
|
|
25
|
+
maxContentWidth: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Options for useTerminalSize hook.
|
|
30
|
+
*/
|
|
31
|
+
export interface UseTerminalSizeOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Debounce delay in milliseconds for resize events.
|
|
34
|
+
* Set to 0 to disable debouncing.
|
|
35
|
+
* @default 100
|
|
36
|
+
*/
|
|
37
|
+
debounceMs?: number;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Initial width to use before first measurement.
|
|
41
|
+
* @default 80
|
|
42
|
+
*/
|
|
43
|
+
initialWidth?: number;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initial height to use before first measurement.
|
|
47
|
+
* @default 24
|
|
48
|
+
*/
|
|
49
|
+
initialHeight?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Hook for subscribing to terminal resize events.
|
|
54
|
+
*
|
|
55
|
+
* Provides reactive terminal dimensions that update when the
|
|
56
|
+
* terminal is resized. Includes computed values for narrow
|
|
57
|
+
* width detection and responsive content width.
|
|
58
|
+
*
|
|
59
|
+
* @param options - Configuration options
|
|
60
|
+
* @returns Terminal size state
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* function MyComponent() {
|
|
65
|
+
* const { width, height, isNarrow, maxContentWidth } = useTerminalDimensions();
|
|
66
|
+
*
|
|
67
|
+
* return (
|
|
68
|
+
* <Box width={maxContentWidth}>
|
|
69
|
+
* {isNarrow ? <CompactView /> : <FullView />}
|
|
70
|
+
* <Text>Terminal: {width}x{height}</Text>
|
|
71
|
+
* </Box>
|
|
72
|
+
* );
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* // With custom debounce
|
|
79
|
+
* const size = useTerminalDimensions({ debounceMs: 200 });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export function useTerminalDimensions(options: UseTerminalSizeOptions = {}): TerminalSize {
|
|
83
|
+
const { debounceMs = 100, initialWidth = 80, initialHeight = 24 } = options;
|
|
84
|
+
|
|
85
|
+
// Get initial dimensions
|
|
86
|
+
const [dimensions, setDimensions] = useState(() => ({
|
|
87
|
+
width: getTerminalWidth(initialWidth),
|
|
88
|
+
height: getTerminalHeight(initialHeight),
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
// Debounce timer ref
|
|
92
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
93
|
+
|
|
94
|
+
// Handler for resize events
|
|
95
|
+
const handleResize = useCallback(() => {
|
|
96
|
+
const newWidth = getTerminalWidth(initialWidth);
|
|
97
|
+
const newHeight = getTerminalHeight(initialHeight);
|
|
98
|
+
|
|
99
|
+
setDimensions((prev) => {
|
|
100
|
+
// Only update if dimensions actually changed
|
|
101
|
+
if (prev.width === newWidth && prev.height === newHeight) {
|
|
102
|
+
return prev;
|
|
103
|
+
}
|
|
104
|
+
return { width: newWidth, height: newHeight };
|
|
105
|
+
});
|
|
106
|
+
}, [initialWidth, initialHeight]);
|
|
107
|
+
|
|
108
|
+
// Debounced resize handler
|
|
109
|
+
const debouncedHandleResize = useCallback(() => {
|
|
110
|
+
if (timerRef.current) {
|
|
111
|
+
clearTimeout(timerRef.current);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (debounceMs === 0) {
|
|
115
|
+
handleResize();
|
|
116
|
+
} else {
|
|
117
|
+
timerRef.current = setTimeout(handleResize, debounceMs);
|
|
118
|
+
}
|
|
119
|
+
}, [handleResize, debounceMs]);
|
|
120
|
+
|
|
121
|
+
// Subscribe to resize events
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
// Initial measurement
|
|
124
|
+
handleResize();
|
|
125
|
+
|
|
126
|
+
// Subscribe to SIGWINCH (terminal resize signal)
|
|
127
|
+
process.stdout.on("resize", debouncedHandleResize);
|
|
128
|
+
|
|
129
|
+
return () => {
|
|
130
|
+
// Cleanup
|
|
131
|
+
process.stdout.off("resize", debouncedHandleResize);
|
|
132
|
+
if (timerRef.current) {
|
|
133
|
+
clearTimeout(timerRef.current);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}, [handleResize, debouncedHandleResize]);
|
|
137
|
+
|
|
138
|
+
// Compute derived values
|
|
139
|
+
const result = useMemo<TerminalSize>(
|
|
140
|
+
() => ({
|
|
141
|
+
width: dimensions.width,
|
|
142
|
+
height: dimensions.height,
|
|
143
|
+
isNarrow: isNarrowWidth(dimensions.width),
|
|
144
|
+
maxContentWidth: getMaxContentWidth(dimensions.width),
|
|
145
|
+
}),
|
|
146
|
+
[dimensions.width, dimensions.height]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Lightweight hook that only tracks narrow width state.
|
|
154
|
+
*
|
|
155
|
+
* Use this when you only need to know if the terminal is narrow,
|
|
156
|
+
* without the overhead of tracking full dimensions.
|
|
157
|
+
*
|
|
158
|
+
* @param debounceMs - Debounce delay for resize events
|
|
159
|
+
* @returns Whether terminal is narrow
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```tsx
|
|
163
|
+
* function MyComponent() {
|
|
164
|
+
* const isNarrow = useIsNarrowWidth();
|
|
165
|
+
* return isNarrow ? <CompactView /> : <FullView />;
|
|
166
|
+
* }
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export function useIsNarrowWidth(debounceMs = 100): boolean {
|
|
170
|
+
const [narrow, setNarrow] = useState(() => isNarrowWidth(getTerminalWidth(80)));
|
|
171
|
+
|
|
172
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
173
|
+
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
const handleResize = () => {
|
|
176
|
+
const newNarrow = isNarrowWidth(getTerminalWidth(80));
|
|
177
|
+
setNarrow((prev) => (prev === newNarrow ? prev : newNarrow));
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const debouncedHandleResize = () => {
|
|
181
|
+
if (timerRef.current) {
|
|
182
|
+
clearTimeout(timerRef.current);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (debounceMs === 0) {
|
|
186
|
+
handleResize();
|
|
187
|
+
} else {
|
|
188
|
+
timerRef.current = setTimeout(handleResize, debounceMs);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Initial check
|
|
193
|
+
handleResize();
|
|
194
|
+
|
|
195
|
+
process.stdout.on("resize", debouncedHandleResize);
|
|
196
|
+
|
|
197
|
+
return () => {
|
|
198
|
+
process.stdout.off("resize", debouncedHandleResize);
|
|
199
|
+
if (timerRef.current) {
|
|
200
|
+
clearTimeout(timerRef.current);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}, [debounceMs]);
|
|
204
|
+
|
|
205
|
+
return narrow;
|
|
206
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useToolApprovalController
|
|
3
|
+
*
|
|
4
|
+
* Bridges ToolsContext pending approvals to an optional AgentLoop instance.
|
|
5
|
+
*
|
|
6
|
+
* - Source of truth for UI is ToolsContext.
|
|
7
|
+
* - When a user approves/rejects, we update ToolsContext and (if provided)
|
|
8
|
+
* signal the AgentLoop to resume via grantPermission/denyPermission.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useCallback, useMemo } from "react";
|
|
12
|
+
import type { RiskLevel } from "../components/Tools/PermissionDialog.js";
|
|
13
|
+
import { useTools } from "../context/ToolsContext.js";
|
|
14
|
+
|
|
15
|
+
export interface PermissionGate {
|
|
16
|
+
grantPermission: () => void;
|
|
17
|
+
denyPermission: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface UseToolApprovalControllerOptions {
|
|
21
|
+
readonly agentLoop?: PermissionGate;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ToolApprovalViewModel {
|
|
25
|
+
readonly activeApproval: ReturnType<typeof useTools>["pendingApproval"][number] | null;
|
|
26
|
+
readonly activeRiskLevel: RiskLevel;
|
|
27
|
+
readonly approveActive: (mode?: "once" | "always") => void;
|
|
28
|
+
readonly rejectActive: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function inferRiskLevel(toolName: string): RiskLevel {
|
|
32
|
+
const normalized = toolName.toLowerCase();
|
|
33
|
+
|
|
34
|
+
// Conservative defaults: most tool calls are medium risk.
|
|
35
|
+
// Elevate for tools that can mutate the system or run commands.
|
|
36
|
+
if (
|
|
37
|
+
normalized.includes("bash") ||
|
|
38
|
+
normalized.includes("shell") ||
|
|
39
|
+
normalized.includes("exec") ||
|
|
40
|
+
normalized.includes("write") ||
|
|
41
|
+
normalized.includes("edit") ||
|
|
42
|
+
normalized.includes("delete")
|
|
43
|
+
) {
|
|
44
|
+
return "high";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return "medium";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function useToolApprovalController(
|
|
51
|
+
options: UseToolApprovalControllerOptions = {}
|
|
52
|
+
): ToolApprovalViewModel {
|
|
53
|
+
const { agentLoop } = options;
|
|
54
|
+
const { pendingApproval, respondToPermissionRequest } = useTools();
|
|
55
|
+
|
|
56
|
+
const activeApproval = pendingApproval[0] ?? null;
|
|
57
|
+
|
|
58
|
+
const activeRiskLevel = useMemo<RiskLevel>(() => {
|
|
59
|
+
if (!activeApproval) return "medium";
|
|
60
|
+
return inferRiskLevel(activeApproval.toolName);
|
|
61
|
+
}, [activeApproval]);
|
|
62
|
+
|
|
63
|
+
const approveActive = useCallback(
|
|
64
|
+
(mode: "once" | "always" = "once") => {
|
|
65
|
+
if (!activeApproval) return;
|
|
66
|
+
|
|
67
|
+
// Resolve any core permission prompt associated with this execution.
|
|
68
|
+
// (No-op if none is pending.)
|
|
69
|
+
respondToPermissionRequest(activeApproval.id, mode);
|
|
70
|
+
|
|
71
|
+
// If an AgentLoop is driving tool execution, resume it.
|
|
72
|
+
// AgentLoop only supports a single pending permission at a time.
|
|
73
|
+
agentLoop?.grantPermission();
|
|
74
|
+
},
|
|
75
|
+
[activeApproval, respondToPermissionRequest, agentLoop]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const rejectActive = useCallback(() => {
|
|
79
|
+
if (!activeApproval) return;
|
|
80
|
+
|
|
81
|
+
respondToPermissionRequest(activeApproval.id, "reject");
|
|
82
|
+
agentLoop?.denyPermission();
|
|
83
|
+
}, [activeApproval, respondToPermissionRequest, agentLoop]);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
activeApproval,
|
|
87
|
+
activeRiskLevel,
|
|
88
|
+
approveActive,
|
|
89
|
+
rejectActive,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useVim Hook (T041)
|
|
3
|
+
*
|
|
4
|
+
* React hook for Vim editing mode in the TUI.
|
|
5
|
+
* Provides modal editing with NORMAL, INSERT, VISUAL, and COMMAND modes.
|
|
6
|
+
*
|
|
7
|
+
* @module @vellum/cli
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useCallback, useState } from "react";
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Types
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Vim editing modes.
|
|
18
|
+
*/
|
|
19
|
+
export type VimMode = "NORMAL" | "INSERT" | "VISUAL" | "COMMAND";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Motion actions for cursor movement.
|
|
23
|
+
*/
|
|
24
|
+
export interface VimMotionAction {
|
|
25
|
+
type: "motion";
|
|
26
|
+
direction: "h" | "j" | "k" | "l" | "w" | "b" | "e" | "0" | "$";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Mode change actions.
|
|
31
|
+
*/
|
|
32
|
+
export interface VimModeAction {
|
|
33
|
+
type: "mode";
|
|
34
|
+
target: VimMode;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Edit actions (delete, yank, paste).
|
|
39
|
+
*/
|
|
40
|
+
export interface VimEditAction {
|
|
41
|
+
type: "delete" | "yank" | "paste";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Union of all Vim actions.
|
|
46
|
+
*/
|
|
47
|
+
export type VimAction = VimMotionAction | VimModeAction | VimEditAction;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Key modifiers for Vim key handling.
|
|
51
|
+
*/
|
|
52
|
+
export interface KeyModifiers {
|
|
53
|
+
ctrl?: boolean;
|
|
54
|
+
shift?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Return value of useVim hook.
|
|
59
|
+
*/
|
|
60
|
+
export interface UseVimReturn {
|
|
61
|
+
/** Whether Vim mode is enabled */
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
/** Current Vim mode */
|
|
64
|
+
mode: VimMode;
|
|
65
|
+
/** Toggle Vim mode on/off */
|
|
66
|
+
toggle: () => void;
|
|
67
|
+
/** Set the current Vim mode */
|
|
68
|
+
setMode: (mode: VimMode) => void;
|
|
69
|
+
/** Handle a key press and return the resulting action */
|
|
70
|
+
handleKey: (key: string, modifiers?: KeyModifiers) => VimAction | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// =============================================================================
|
|
74
|
+
// Key Mappings
|
|
75
|
+
// =============================================================================
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Motion keys in NORMAL and VISUAL modes.
|
|
79
|
+
*/
|
|
80
|
+
const MOTION_KEYS: Record<string, VimMotionAction["direction"]> = {
|
|
81
|
+
h: "h", // left
|
|
82
|
+
j: "j", // down
|
|
83
|
+
k: "k", // up
|
|
84
|
+
l: "l", // right
|
|
85
|
+
w: "w", // word forward
|
|
86
|
+
b: "b", // word backward
|
|
87
|
+
e: "e", // word end
|
|
88
|
+
"0": "0", // line start
|
|
89
|
+
$: "$", // line end
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Keys that trigger mode transitions from NORMAL mode.
|
|
94
|
+
*/
|
|
95
|
+
const MODE_TRANSITION_KEYS: Record<string, VimMode> = {
|
|
96
|
+
i: "INSERT", // insert before cursor
|
|
97
|
+
a: "INSERT", // insert after cursor (append)
|
|
98
|
+
I: "INSERT", // insert at line start
|
|
99
|
+
A: "INSERT", // insert at line end
|
|
100
|
+
o: "INSERT", // open line below
|
|
101
|
+
O: "INSERT", // open line above
|
|
102
|
+
v: "VISUAL", // visual mode
|
|
103
|
+
V: "VISUAL", // visual line mode
|
|
104
|
+
":": "COMMAND", // command mode
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Edit action keys in NORMAL mode.
|
|
109
|
+
*/
|
|
110
|
+
const EDIT_KEYS: Record<string, VimEditAction["type"]> = {
|
|
111
|
+
x: "delete", // delete character
|
|
112
|
+
d: "delete", // delete (with motion)
|
|
113
|
+
y: "yank", // yank (copy)
|
|
114
|
+
p: "paste", // paste after
|
|
115
|
+
P: "paste", // paste before
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// =============================================================================
|
|
119
|
+
// Hook Implementation
|
|
120
|
+
// =============================================================================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* React hook for Vim editing mode.
|
|
124
|
+
*
|
|
125
|
+
* Provides modal editing with NORMAL, INSERT, VISUAL, and COMMAND modes.
|
|
126
|
+
* Handles key presses and returns appropriate actions for the calling component
|
|
127
|
+
* to execute.
|
|
128
|
+
*
|
|
129
|
+
* @returns UseVimReturn object with Vim state and control functions
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```tsx
|
|
133
|
+
* function Editor() {
|
|
134
|
+
* const vim = useVim();
|
|
135
|
+
*
|
|
136
|
+
* const handleKeyPress = (key: string) => {
|
|
137
|
+
* const action = vim.handleKey(key);
|
|
138
|
+
* if (action?.type === 'motion') {
|
|
139
|
+
* moveCursor(action.direction);
|
|
140
|
+
* }
|
|
141
|
+
* };
|
|
142
|
+
*
|
|
143
|
+
* return (
|
|
144
|
+
* <Box>
|
|
145
|
+
* <Text>Mode: {vim.mode}</Text>
|
|
146
|
+
* <TextInput onKeyPress={handleKeyPress} />
|
|
147
|
+
* </Box>
|
|
148
|
+
* );
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export function useVim(): UseVimReturn {
|
|
153
|
+
const [enabled, setEnabled] = useState(false);
|
|
154
|
+
const [mode, setModeState] = useState<VimMode>("NORMAL");
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Toggle Vim mode on/off.
|
|
158
|
+
* When disabled, switches to INSERT mode.
|
|
159
|
+
* When enabled, switches to NORMAL mode.
|
|
160
|
+
*/
|
|
161
|
+
const toggle = useCallback(() => {
|
|
162
|
+
setEnabled((prev) => {
|
|
163
|
+
const next = !prev;
|
|
164
|
+
// When enabling, start in NORMAL mode
|
|
165
|
+
// When disabling, mode doesn't matter but reset to NORMAL
|
|
166
|
+
setModeState("NORMAL");
|
|
167
|
+
return next;
|
|
168
|
+
});
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Set the current Vim mode.
|
|
173
|
+
*/
|
|
174
|
+
const setMode = useCallback((newMode: VimMode) => {
|
|
175
|
+
setModeState(newMode);
|
|
176
|
+
}, []);
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Handle a key press in NORMAL mode.
|
|
180
|
+
*/
|
|
181
|
+
const handleNormalKey = useCallback((key: string, modifiers?: KeyModifiers): VimAction | null => {
|
|
182
|
+
// Check for Ctrl+c to exit to NORMAL (redundant but explicit)
|
|
183
|
+
if (modifiers?.ctrl && key === "c") {
|
|
184
|
+
return { type: "mode", target: "NORMAL" };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// In Vim mode we generally ignore Ctrl+<key> combos so global hotkeys can run.
|
|
188
|
+
// (Explicit Ctrl combos like Ctrl+C / Ctrl+[ are handled above or in other modes.)
|
|
189
|
+
if (modifiers?.ctrl) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Motion keys
|
|
194
|
+
if (key in MOTION_KEYS) {
|
|
195
|
+
const direction = MOTION_KEYS[key] as VimMotionAction["direction"];
|
|
196
|
+
return { type: "motion", direction };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Mode transition keys
|
|
200
|
+
if (key in MODE_TRANSITION_KEYS) {
|
|
201
|
+
const target = MODE_TRANSITION_KEYS[key] as VimMode;
|
|
202
|
+
setModeState(target);
|
|
203
|
+
return { type: "mode", target };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Edit keys
|
|
207
|
+
if (key in EDIT_KEYS) {
|
|
208
|
+
const editType = EDIT_KEYS[key] as VimEditAction["type"];
|
|
209
|
+
return { type: editType };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return null;
|
|
213
|
+
}, []);
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Handle a key press in INSERT mode.
|
|
217
|
+
*/
|
|
218
|
+
const handleInsertKey = useCallback((key: string, modifiers?: KeyModifiers): VimAction | null => {
|
|
219
|
+
// Escape or Ctrl+c returns to NORMAL mode
|
|
220
|
+
if (key === "escape" || (modifiers?.ctrl && key === "c")) {
|
|
221
|
+
setModeState("NORMAL");
|
|
222
|
+
return { type: "mode", target: "NORMAL" };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Ctrl+[ is equivalent to Escape
|
|
226
|
+
if (modifiers?.ctrl && key === "[") {
|
|
227
|
+
setModeState("NORMAL");
|
|
228
|
+
return { type: "mode", target: "NORMAL" };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// All other keys pass through in INSERT mode
|
|
232
|
+
return null;
|
|
233
|
+
}, []);
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Handle a key press in VISUAL mode.
|
|
237
|
+
*/
|
|
238
|
+
const handleVisualKey = useCallback((key: string, modifiers?: KeyModifiers): VimAction | null => {
|
|
239
|
+
// Escape or Ctrl+c returns to NORMAL mode
|
|
240
|
+
if (key === "escape" || (modifiers?.ctrl && key === "c")) {
|
|
241
|
+
setModeState("NORMAL");
|
|
242
|
+
return { type: "mode", target: "NORMAL" };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Ctrl+[ is equivalent to Escape
|
|
246
|
+
if (modifiers?.ctrl && key === "[") {
|
|
247
|
+
setModeState("NORMAL");
|
|
248
|
+
return { type: "mode", target: "NORMAL" };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Ignore other Ctrl+<key> combos so global hotkeys can run.
|
|
252
|
+
if (modifiers?.ctrl) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Motion keys work in VISUAL mode
|
|
257
|
+
if (key in MOTION_KEYS) {
|
|
258
|
+
const direction = MOTION_KEYS[key] as VimMotionAction["direction"];
|
|
259
|
+
return { type: "motion", direction };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// v toggles back to NORMAL
|
|
263
|
+
if (key === "v") {
|
|
264
|
+
setModeState("NORMAL");
|
|
265
|
+
return { type: "mode", target: "NORMAL" };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// y yanks selection and returns to NORMAL
|
|
269
|
+
if (key === "y") {
|
|
270
|
+
setModeState("NORMAL");
|
|
271
|
+
return { type: "yank" };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// d deletes selection and returns to NORMAL
|
|
275
|
+
if (key === "d" || key === "x") {
|
|
276
|
+
setModeState("NORMAL");
|
|
277
|
+
return { type: "delete" };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return null;
|
|
281
|
+
}, []);
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Handle a key press in COMMAND mode.
|
|
285
|
+
*/
|
|
286
|
+
const handleCommandKey = useCallback(
|
|
287
|
+
(key: string, modifiers?: KeyModifiers): VimAction | null => {
|
|
288
|
+
// Escape or Ctrl+c returns to NORMAL mode
|
|
289
|
+
if (key === "escape" || (modifiers?.ctrl && key === "c")) {
|
|
290
|
+
setModeState("NORMAL");
|
|
291
|
+
return { type: "mode", target: "NORMAL" };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Command mode passes through keys for command input
|
|
295
|
+
return null;
|
|
296
|
+
},
|
|
297
|
+
[]
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Handle a key press and return the resulting action.
|
|
302
|
+
* Returns null if the key should be passed through to the input.
|
|
303
|
+
*/
|
|
304
|
+
const handleKey = useCallback(
|
|
305
|
+
(key: string, modifiers?: KeyModifiers): VimAction | null => {
|
|
306
|
+
// If Vim mode is disabled, pass through all keys
|
|
307
|
+
if (!enabled) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
switch (mode) {
|
|
312
|
+
case "NORMAL":
|
|
313
|
+
return handleNormalKey(key, modifiers);
|
|
314
|
+
case "INSERT":
|
|
315
|
+
return handleInsertKey(key, modifiers);
|
|
316
|
+
case "VISUAL":
|
|
317
|
+
return handleVisualKey(key, modifiers);
|
|
318
|
+
case "COMMAND":
|
|
319
|
+
return handleCommandKey(key, modifiers);
|
|
320
|
+
default:
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
[enabled, mode, handleNormalKey, handleInsertKey, handleVisualKey, handleCommandKey]
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
enabled,
|
|
329
|
+
mode,
|
|
330
|
+
toggle,
|
|
331
|
+
setMode,
|
|
332
|
+
handleKey,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useWorkspace Hook
|
|
3
|
+
*
|
|
4
|
+
* Provides current workspace (working directory) information.
|
|
5
|
+
* Returns the directory name and full path for display in the header bar.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/hooks/useWorkspace
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { useMemo } from "react";
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Types
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Workspace information returned by the hook.
|
|
19
|
+
*/
|
|
20
|
+
export interface WorkspaceInfo {
|
|
21
|
+
/** Short name of the workspace directory */
|
|
22
|
+
readonly name: string;
|
|
23
|
+
/** Full absolute path to the workspace */
|
|
24
|
+
readonly path: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Hook Implementation
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Hook to get current workspace information.
|
|
33
|
+
*
|
|
34
|
+
* Uses process.cwd() to determine the current working directory.
|
|
35
|
+
* Memoized to avoid recalculation on every render.
|
|
36
|
+
*
|
|
37
|
+
* @returns WorkspaceInfo with name and path
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* function Header() {
|
|
42
|
+
* const { name, path } = useWorkspace();
|
|
43
|
+
* return <Text>{ name}</Text>;
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function useWorkspace(): WorkspaceInfo {
|
|
48
|
+
return useMemo(() => {
|
|
49
|
+
const cwd = process.cwd();
|
|
50
|
+
const name = path.basename(cwd);
|
|
51
|
+
return {
|
|
52
|
+
name,
|
|
53
|
+
path: cwd,
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
}
|