@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
|
+
* Command Executor
|
|
3
|
+
*
|
|
4
|
+
* Executes slash commands with:
|
|
5
|
+
* - Command resolution via registry
|
|
6
|
+
* - Argument validation against command definitions
|
|
7
|
+
* - Unknown command handling with suggestions
|
|
8
|
+
* - Context creation via provider pattern
|
|
9
|
+
*
|
|
10
|
+
* @module cli/commands/executor
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { CommandParser, isParseError } from "./parser.js";
|
|
14
|
+
import type { CommandRegistry } from "./registry.js";
|
|
15
|
+
import type {
|
|
16
|
+
ArgType,
|
|
17
|
+
CommandContext,
|
|
18
|
+
CommandError,
|
|
19
|
+
CommandErrorCode,
|
|
20
|
+
CommandResult,
|
|
21
|
+
NamedArg,
|
|
22
|
+
ParsedArgs,
|
|
23
|
+
PositionalArg,
|
|
24
|
+
SlashCommand,
|
|
25
|
+
} from "./types.js";
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// T018: CommandContextProvider Interface
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Provider interface for creating CommandContext instances
|
|
33
|
+
*
|
|
34
|
+
* Abstracts context creation to allow dependency injection and testing.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const contextProvider: CommandContextProvider = {
|
|
39
|
+
* createContext(parsedArgs, signal) {
|
|
40
|
+
* return {
|
|
41
|
+
* session: currentSession,
|
|
42
|
+
* credentials: credentialManager,
|
|
43
|
+
* toolRegistry: tools,
|
|
44
|
+
* parsedArgs,
|
|
45
|
+
* signal,
|
|
46
|
+
* emit: eventEmitter.emit.bind(eventEmitter),
|
|
47
|
+
* };
|
|
48
|
+
* },
|
|
49
|
+
* };
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export interface CommandContextProvider {
|
|
53
|
+
/**
|
|
54
|
+
* Create a CommandContext for command execution
|
|
55
|
+
*
|
|
56
|
+
* @param parsedArgs - Parsed command arguments
|
|
57
|
+
* @param signal - Optional abort signal for cancellation
|
|
58
|
+
* @returns CommandContext with all dependencies
|
|
59
|
+
*/
|
|
60
|
+
createContext(parsedArgs: ParsedArgs, signal?: AbortSignal): CommandContext;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// T020: Levenshtein Distance for Suggestions
|
|
65
|
+
// =============================================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Initialize Levenshtein distance matrix
|
|
69
|
+
*/
|
|
70
|
+
function initLevenshteinMatrix(rows: number, cols: number): number[][] {
|
|
71
|
+
const matrix: number[][] = [];
|
|
72
|
+
for (let i = 0; i < rows; i++) {
|
|
73
|
+
const row: number[] = [];
|
|
74
|
+
for (let j = 0; j < cols; j++) {
|
|
75
|
+
if (i === 0) {
|
|
76
|
+
row.push(j);
|
|
77
|
+
} else if (j === 0) {
|
|
78
|
+
row.push(i);
|
|
79
|
+
} else {
|
|
80
|
+
row.push(0);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
matrix.push(row);
|
|
84
|
+
}
|
|
85
|
+
return matrix;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Fill Levenshtein distance matrix with computed values
|
|
90
|
+
*/
|
|
91
|
+
function fillLevenshteinMatrix(matrix: number[][], a: string, b: string): void {
|
|
92
|
+
const rows = a.length + 1;
|
|
93
|
+
const cols = b.length + 1;
|
|
94
|
+
|
|
95
|
+
for (let i = 1; i < rows; i++) {
|
|
96
|
+
const currentRow = matrix[i];
|
|
97
|
+
const prevRow = matrix[i - 1];
|
|
98
|
+
if (!currentRow || !prevRow) continue;
|
|
99
|
+
|
|
100
|
+
for (let j = 1; j < cols; j++) {
|
|
101
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
102
|
+
const deletion = (prevRow[j] ?? 0) + 1;
|
|
103
|
+
const insertion = (currentRow[j - 1] ?? 0) + 1;
|
|
104
|
+
const substitution = (prevRow[j - 1] ?? 0) + cost;
|
|
105
|
+
currentRow[j] = Math.min(deletion, insertion, substitution);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Calculate Levenshtein distance between two strings
|
|
112
|
+
*
|
|
113
|
+
* Used for finding similar command names when a command is not found.
|
|
114
|
+
*
|
|
115
|
+
* @param a - First string
|
|
116
|
+
* @param b - Second string
|
|
117
|
+
* @returns Edit distance (minimum edits to transform a to b)
|
|
118
|
+
*/
|
|
119
|
+
function levenshtein(a: string, b: string): number {
|
|
120
|
+
const rows = a.length + 1;
|
|
121
|
+
const cols = b.length + 1;
|
|
122
|
+
|
|
123
|
+
const matrix = initLevenshteinMatrix(rows, cols);
|
|
124
|
+
fillLevenshteinMatrix(matrix, a, b);
|
|
125
|
+
|
|
126
|
+
return matrix[a.length]?.[b.length] ?? 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Find similar commands using Levenshtein distance
|
|
131
|
+
*
|
|
132
|
+
* @param target - Command name that wasn't found
|
|
133
|
+
* @param commands - List of available commands
|
|
134
|
+
* @param maxSuggestions - Maximum suggestions to return (default: 3)
|
|
135
|
+
* @param maxDistance - Maximum edit distance to consider (default: 3)
|
|
136
|
+
* @returns Array of similar command names, sorted by similarity
|
|
137
|
+
*/
|
|
138
|
+
function findSimilarCommands(
|
|
139
|
+
target: string,
|
|
140
|
+
commands: SlashCommand[],
|
|
141
|
+
maxSuggestions = 3,
|
|
142
|
+
maxDistance = 3
|
|
143
|
+
): string[] {
|
|
144
|
+
const targetLower = target.toLowerCase();
|
|
145
|
+
|
|
146
|
+
const scored: Array<{ name: string; distance: number }> = [];
|
|
147
|
+
|
|
148
|
+
for (const cmd of commands) {
|
|
149
|
+
// Check main name
|
|
150
|
+
const nameLower = cmd.name.toLowerCase();
|
|
151
|
+
const nameDistance = levenshtein(targetLower, nameLower);
|
|
152
|
+
|
|
153
|
+
// Also consider prefix match (e.g., "hel" for "help")
|
|
154
|
+
const isPrefixMatch = nameLower.startsWith(targetLower) || targetLower.startsWith(nameLower);
|
|
155
|
+
const adjustedDistance = isPrefixMatch ? Math.min(nameDistance, 1) : nameDistance;
|
|
156
|
+
|
|
157
|
+
if (adjustedDistance <= maxDistance) {
|
|
158
|
+
scored.push({ name: cmd.name, distance: adjustedDistance });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check aliases
|
|
162
|
+
if (cmd.aliases) {
|
|
163
|
+
for (const alias of cmd.aliases) {
|
|
164
|
+
const aliasLower = alias.toLowerCase();
|
|
165
|
+
const aliasDistance = levenshtein(targetLower, aliasLower);
|
|
166
|
+
const aliasIsPrefixMatch =
|
|
167
|
+
aliasLower.startsWith(targetLower) || targetLower.startsWith(aliasLower);
|
|
168
|
+
const adjustedAliasDistance = aliasIsPrefixMatch
|
|
169
|
+
? Math.min(aliasDistance, 1)
|
|
170
|
+
: aliasDistance;
|
|
171
|
+
|
|
172
|
+
if (adjustedAliasDistance <= maxDistance) {
|
|
173
|
+
scored.push({ name: cmd.name, distance: adjustedAliasDistance });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Sort by distance (ascending) and remove duplicates
|
|
180
|
+
const seen = new Set<string>();
|
|
181
|
+
return scored
|
|
182
|
+
.sort((a, b) => a.distance - b.distance)
|
|
183
|
+
.filter((item) => {
|
|
184
|
+
if (seen.has(item.name)) return false;
|
|
185
|
+
seen.add(item.name);
|
|
186
|
+
return true;
|
|
187
|
+
})
|
|
188
|
+
.slice(0, maxSuggestions)
|
|
189
|
+
.map((item) => item.name);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// =============================================================================
|
|
193
|
+
// T021: Argument Validation
|
|
194
|
+
// =============================================================================
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Validation error details
|
|
198
|
+
*/
|
|
199
|
+
interface ValidationError {
|
|
200
|
+
readonly code: CommandErrorCode;
|
|
201
|
+
readonly message: string;
|
|
202
|
+
readonly argName: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Validate a value against an expected type
|
|
207
|
+
*
|
|
208
|
+
* @param value - Value to validate
|
|
209
|
+
* @param expectedType - Expected ArgType
|
|
210
|
+
* @returns true if valid, false otherwise
|
|
211
|
+
*/
|
|
212
|
+
function validateArgType(value: unknown, expectedType: ArgType): boolean {
|
|
213
|
+
if (value === undefined || value === null) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
switch (expectedType) {
|
|
218
|
+
case "string":
|
|
219
|
+
return typeof value === "string";
|
|
220
|
+
|
|
221
|
+
case "number": {
|
|
222
|
+
if (typeof value === "number") return !Number.isNaN(value);
|
|
223
|
+
if (typeof value === "string") {
|
|
224
|
+
const num = Number(value);
|
|
225
|
+
return !Number.isNaN(num);
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
case "boolean": {
|
|
231
|
+
if (typeof value === "boolean") return true;
|
|
232
|
+
if (typeof value === "string") {
|
|
233
|
+
const lower = value.toLowerCase();
|
|
234
|
+
return lower === "true" || lower === "false" || lower === "1" || lower === "0";
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
case "path":
|
|
240
|
+
// Path is validated as a non-empty string
|
|
241
|
+
// Actual file existence checking is done by the command itself
|
|
242
|
+
return typeof value === "string" && value.length > 0;
|
|
243
|
+
|
|
244
|
+
default:
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Convert a value to the expected type
|
|
251
|
+
*
|
|
252
|
+
* @param value - Value to convert
|
|
253
|
+
* @param expectedType - Expected ArgType
|
|
254
|
+
* @returns Converted value
|
|
255
|
+
*/
|
|
256
|
+
function coerceArgType(value: string | boolean, expectedType: ArgType): unknown {
|
|
257
|
+
if (expectedType === "number" && typeof value === "string") {
|
|
258
|
+
return Number(value);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (expectedType === "boolean") {
|
|
262
|
+
if (typeof value === "boolean") return value;
|
|
263
|
+
if (typeof value === "string") {
|
|
264
|
+
const lower = value.toLowerCase();
|
|
265
|
+
return lower === "true" || lower === "1";
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Validate positional arguments against command definition
|
|
274
|
+
*
|
|
275
|
+
* @param positional - Parsed positional arguments
|
|
276
|
+
* @param argDefs - Command's positional argument definitions
|
|
277
|
+
* @returns ValidationError if invalid, undefined if valid
|
|
278
|
+
*/
|
|
279
|
+
function validatePositionalArgs(
|
|
280
|
+
positional: readonly string[],
|
|
281
|
+
argDefs: readonly PositionalArg[]
|
|
282
|
+
): ValidationError | undefined {
|
|
283
|
+
for (let i = 0; i < argDefs.length; i++) {
|
|
284
|
+
const def = argDefs[i];
|
|
285
|
+
if (!def) continue;
|
|
286
|
+
|
|
287
|
+
const value = positional[i];
|
|
288
|
+
|
|
289
|
+
// Check required
|
|
290
|
+
if (def.required && value === undefined && def.default === undefined) {
|
|
291
|
+
return {
|
|
292
|
+
code: "MISSING_ARGUMENT",
|
|
293
|
+
message: `Missing required argument: ${def.name}`,
|
|
294
|
+
argName: def.name,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Check type if value provided
|
|
299
|
+
if (value !== undefined && !validateArgType(value, def.type)) {
|
|
300
|
+
return {
|
|
301
|
+
code: "ARGUMENT_TYPE_ERROR",
|
|
302
|
+
message: `Invalid type for argument '${def.name}': expected ${def.type}`,
|
|
303
|
+
argName: def.name,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Validate named arguments against command definition
|
|
313
|
+
*
|
|
314
|
+
* @param named - Parsed named arguments
|
|
315
|
+
* @param argDefs - Command's named argument definitions
|
|
316
|
+
* @returns ValidationError if invalid, undefined if valid
|
|
317
|
+
*/
|
|
318
|
+
function validateNamedArgs(
|
|
319
|
+
named: ReadonlyMap<string, string | boolean>,
|
|
320
|
+
argDefs: readonly NamedArg[]
|
|
321
|
+
): ValidationError | undefined {
|
|
322
|
+
for (const def of argDefs) {
|
|
323
|
+
const value = named.get(def.name) ?? named.get(def.shorthand ?? "");
|
|
324
|
+
|
|
325
|
+
// Check required
|
|
326
|
+
if (def.required && value === undefined && def.default === undefined) {
|
|
327
|
+
return {
|
|
328
|
+
code: "MISSING_ARGUMENT",
|
|
329
|
+
message: `Missing required argument: --${def.name}`,
|
|
330
|
+
argName: def.name,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Check type if value provided
|
|
335
|
+
if (value !== undefined && !validateArgType(value, def.type)) {
|
|
336
|
+
return {
|
|
337
|
+
code: "ARGUMENT_TYPE_ERROR",
|
|
338
|
+
message: `Invalid type for argument '--${def.name}': expected ${def.type}`,
|
|
339
|
+
argName: def.name,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Build ParsedArgs with type coercion and defaults applied
|
|
349
|
+
*
|
|
350
|
+
* @param command - Command being executed
|
|
351
|
+
* @param parsedCommand - Parsed command from parser
|
|
352
|
+
* @returns ParsedArgs with resolved values
|
|
353
|
+
*/
|
|
354
|
+
function buildParsedArgs(
|
|
355
|
+
command: SlashCommand,
|
|
356
|
+
parsedCommand: {
|
|
357
|
+
command: string;
|
|
358
|
+
positional: readonly string[];
|
|
359
|
+
named: ReadonlyMap<string, string | boolean>;
|
|
360
|
+
raw: string;
|
|
361
|
+
}
|
|
362
|
+
): ParsedArgs {
|
|
363
|
+
// Build positional args with defaults and type coercion
|
|
364
|
+
const positionalValues: unknown[] = [];
|
|
365
|
+
const positionalDefs = command.positionalArgs ?? [];
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < positionalDefs.length; i++) {
|
|
368
|
+
const def = positionalDefs[i];
|
|
369
|
+
if (!def) continue;
|
|
370
|
+
|
|
371
|
+
const rawValue = parsedCommand.positional[i];
|
|
372
|
+
|
|
373
|
+
if (rawValue !== undefined) {
|
|
374
|
+
positionalValues.push(coerceArgType(rawValue, def.type));
|
|
375
|
+
} else if (def.default !== undefined) {
|
|
376
|
+
positionalValues.push(def.default);
|
|
377
|
+
} else {
|
|
378
|
+
positionalValues.push(undefined);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Add any extra positional args beyond the definition
|
|
383
|
+
for (let i = positionalDefs.length; i < parsedCommand.positional.length; i++) {
|
|
384
|
+
positionalValues.push(parsedCommand.positional[i]);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Build named args with defaults and type coercion
|
|
388
|
+
const namedValues: Record<string, unknown> = {};
|
|
389
|
+
const namedDefs = command.namedArgs ?? [];
|
|
390
|
+
|
|
391
|
+
// First, set defaults
|
|
392
|
+
for (const def of namedDefs) {
|
|
393
|
+
if (def.default !== undefined) {
|
|
394
|
+
namedValues[def.name] = def.default;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Then, apply provided values with type coercion
|
|
399
|
+
for (const [key, value] of parsedCommand.named.entries()) {
|
|
400
|
+
// Find the definition for this arg (by name or shorthand)
|
|
401
|
+
const def = namedDefs.find((d) => d.name === key || d.shorthand === key);
|
|
402
|
+
|
|
403
|
+
if (def) {
|
|
404
|
+
namedValues[def.name] = coerceArgType(value, def.type);
|
|
405
|
+
} else {
|
|
406
|
+
// Unknown flag, pass through as-is
|
|
407
|
+
namedValues[key] = value;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
command: parsedCommand.command,
|
|
413
|
+
positional: positionalValues,
|
|
414
|
+
named: namedValues,
|
|
415
|
+
raw: parsedCommand.raw,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// =============================================================================
|
|
420
|
+
// T019: CommandExecutor Class
|
|
421
|
+
// =============================================================================
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Command executor for slash commands
|
|
425
|
+
*
|
|
426
|
+
* Orchestrates command execution:
|
|
427
|
+
* 1. Parse input string
|
|
428
|
+
* 2. Resolve command from registry
|
|
429
|
+
* 3. Validate arguments
|
|
430
|
+
* 4. Create execution context
|
|
431
|
+
* 5. Execute command handler
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* ```typescript
|
|
435
|
+
* const executor = new CommandExecutor(registry, contextProvider);
|
|
436
|
+
*
|
|
437
|
+
* // Execute a command
|
|
438
|
+
* const result = await executor.execute('/help');
|
|
439
|
+
*
|
|
440
|
+
* // With abort signal
|
|
441
|
+
* const controller = new AbortController();
|
|
442
|
+
* const result = await executor.execute('/long-task', controller.signal);
|
|
443
|
+
* ```
|
|
444
|
+
*/
|
|
445
|
+
export class CommandExecutor {
|
|
446
|
+
private readonly registry: CommandRegistry;
|
|
447
|
+
private readonly contextProvider: CommandContextProvider;
|
|
448
|
+
private readonly parser: CommandParser;
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Create a new CommandExecutor
|
|
452
|
+
*
|
|
453
|
+
* @param registry - Command registry for command lookup
|
|
454
|
+
* @param contextProvider - Provider for creating execution contexts
|
|
455
|
+
*/
|
|
456
|
+
constructor(registry: CommandRegistry, contextProvider: CommandContextProvider) {
|
|
457
|
+
this.registry = registry;
|
|
458
|
+
this.contextProvider = contextProvider;
|
|
459
|
+
this.parser = new CommandParser();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Execute a command from input string
|
|
464
|
+
*
|
|
465
|
+
* @param input - Raw command input (e.g., "/help --verbose")
|
|
466
|
+
* @param signal - Optional abort signal for cancellation
|
|
467
|
+
* @returns Command execution result
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* ```typescript
|
|
471
|
+
* const result = await executor.execute('/login anthropic --store keychain');
|
|
472
|
+
*
|
|
473
|
+
* switch (result.kind) {
|
|
474
|
+
* case 'success':
|
|
475
|
+
* console.log('Command succeeded:', result.message);
|
|
476
|
+
* break;
|
|
477
|
+
* case 'error':
|
|
478
|
+
* console.error(`[${result.code}] ${result.message}`);
|
|
479
|
+
* break;
|
|
480
|
+
* }
|
|
481
|
+
* ```
|
|
482
|
+
*/
|
|
483
|
+
async execute(input: string, signal?: AbortSignal): Promise<CommandResult> {
|
|
484
|
+
// Step 1: Parse input
|
|
485
|
+
const parseResult = this.parser.parse(input);
|
|
486
|
+
|
|
487
|
+
if (isParseError(parseResult)) {
|
|
488
|
+
return {
|
|
489
|
+
kind: "error",
|
|
490
|
+
code: parseResult.code,
|
|
491
|
+
message: parseResult.message,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Step 2: Get command from registry
|
|
496
|
+
const command = this.registry.get(parseResult.command);
|
|
497
|
+
|
|
498
|
+
if (!command) {
|
|
499
|
+
return this.createUnknownCommandError(parseResult.command);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Step 3: Validate arguments
|
|
503
|
+
const validationError = this.validateArguments(command, parseResult);
|
|
504
|
+
|
|
505
|
+
if (validationError) {
|
|
506
|
+
return {
|
|
507
|
+
kind: "error",
|
|
508
|
+
code: validationError.code,
|
|
509
|
+
message: validationError.message,
|
|
510
|
+
helpCommand: `/help ${command.name}`,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Step 4: Build parsed args with type coercion
|
|
515
|
+
const parsedArgs = buildParsedArgs(command, parseResult);
|
|
516
|
+
|
|
517
|
+
// Step 5: Create context
|
|
518
|
+
const context = this.contextProvider.createContext(parsedArgs, signal);
|
|
519
|
+
|
|
520
|
+
// Step 6: Execute command
|
|
521
|
+
try {
|
|
522
|
+
return await command.execute(context);
|
|
523
|
+
} catch (error) {
|
|
524
|
+
return {
|
|
525
|
+
kind: "error",
|
|
526
|
+
code: "INTERNAL_ERROR",
|
|
527
|
+
message: error instanceof Error ? error.message : "Unknown error occurred",
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Create error for unknown command with suggestions
|
|
534
|
+
*/
|
|
535
|
+
private createUnknownCommandError(commandName: string): CommandError {
|
|
536
|
+
const allCommands = this.registry.list();
|
|
537
|
+
const suggestions = findSimilarCommands(commandName, allCommands);
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
kind: "error",
|
|
541
|
+
code: "COMMAND_NOT_FOUND",
|
|
542
|
+
message: `Unknown command: /${commandName}`,
|
|
543
|
+
suggestions: suggestions.length > 0 ? suggestions.map((s) => `/${s}`) : undefined,
|
|
544
|
+
helpCommand: "/help",
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Validate arguments against command definition
|
|
550
|
+
*/
|
|
551
|
+
private validateArguments(
|
|
552
|
+
command: SlashCommand,
|
|
553
|
+
parsedCommand: {
|
|
554
|
+
positional: readonly string[];
|
|
555
|
+
named: ReadonlyMap<string, string | boolean>;
|
|
556
|
+
}
|
|
557
|
+
): ValidationError | undefined {
|
|
558
|
+
// Validate positional args
|
|
559
|
+
if (command.positionalArgs) {
|
|
560
|
+
const positionalError = validatePositionalArgs(
|
|
561
|
+
parsedCommand.positional,
|
|
562
|
+
command.positionalArgs
|
|
563
|
+
);
|
|
564
|
+
if (positionalError) {
|
|
565
|
+
return positionalError;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Validate named args
|
|
570
|
+
if (command.namedArgs) {
|
|
571
|
+
const namedError = validateNamedArgs(parsedCommand.named, command.namedArgs);
|
|
572
|
+
if (namedError) {
|
|
573
|
+
return namedError;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return undefined;
|
|
578
|
+
}
|
|
579
|
+
}
|