@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,630 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Command Loader (CLI Adapter)
|
|
3
|
+
*
|
|
4
|
+
* Adapts @vellum/core's UserCommandLoader for CLI's CommandRegistry.
|
|
5
|
+
* This provides the same public API while leveraging Core's comprehensive
|
|
6
|
+
* implementation including:
|
|
7
|
+
* - YAML command support (.yaml, .yml)
|
|
8
|
+
* - TypeScript command support (.ts, .mts)
|
|
9
|
+
* - Trust verification for security
|
|
10
|
+
* - Project and user directory support
|
|
11
|
+
*
|
|
12
|
+
* Also maintains backward compatibility with JavaScript command files (.js, .mjs)
|
|
13
|
+
* for CLI-only usage.
|
|
14
|
+
*
|
|
15
|
+
* @module cli/commands/user-commands
|
|
16
|
+
* @see @vellum/core/command/user-command-loader
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as fs from "node:fs/promises";
|
|
20
|
+
import * as os from "node:os";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
import { pathToFileURL } from "node:url";
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
type UserCommand as CoreUserCommand,
|
|
26
|
+
type UserCommandArgs as CoreUserCommandArgs,
|
|
27
|
+
type UserCommandContext as CoreUserCommandContext,
|
|
28
|
+
type UserCommandDefinition as CoreUserCommandDefinition,
|
|
29
|
+
type UserCommandLoadResult as CoreUserCommandLoadResult,
|
|
30
|
+
type UserCommandResult as CoreUserCommandResult,
|
|
31
|
+
createUserCommandLoader,
|
|
32
|
+
getTypeScriptCommandTemplate,
|
|
33
|
+
type UserCommandLoaderOptions,
|
|
34
|
+
type YamlPromptCommand,
|
|
35
|
+
type YamlShellCommand,
|
|
36
|
+
} from "@vellum/core";
|
|
37
|
+
|
|
38
|
+
import type { CommandRegistry } from "./registry.js";
|
|
39
|
+
import type { CommandCategory, CommandContext, CommandResult, SlashCommand } from "./types.js";
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Re-export Core Types (for backward compatibility)
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* User command definition format
|
|
47
|
+
*
|
|
48
|
+
* This is the structure users export from their command files.
|
|
49
|
+
* Re-exported from @vellum/core for backward compatibility.
|
|
50
|
+
*/
|
|
51
|
+
export type UserCommandDefinition = CoreUserCommandDefinition;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Simplified args passed to user commands
|
|
55
|
+
* Re-exported from @vellum/core for backward compatibility.
|
|
56
|
+
*/
|
|
57
|
+
export type UserCommandArgs = CoreUserCommandArgs;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Simplified context passed to user commands
|
|
61
|
+
* Re-exported from @vellum/core for backward compatibility.
|
|
62
|
+
*/
|
|
63
|
+
export type UserCommandContext = CoreUserCommandContext;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Result from user command execution
|
|
67
|
+
* Re-exported from @vellum/core for backward compatibility.
|
|
68
|
+
*/
|
|
69
|
+
export type UserCommandResult = CoreUserCommandResult;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Validation error for user commands
|
|
73
|
+
*/
|
|
74
|
+
export interface UserCommandValidationError {
|
|
75
|
+
/** File path that failed validation */
|
|
76
|
+
file: string;
|
|
77
|
+
/** Error message */
|
|
78
|
+
error: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Result of loading user commands
|
|
83
|
+
*/
|
|
84
|
+
export interface UserCommandLoadResult {
|
|
85
|
+
/** Successfully loaded commands */
|
|
86
|
+
commands: SlashCommand[];
|
|
87
|
+
/** Validation errors encountered */
|
|
88
|
+
errors: UserCommandValidationError[];
|
|
89
|
+
/** Total files scanned */
|
|
90
|
+
scanned: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// =============================================================================
|
|
94
|
+
// Constants
|
|
95
|
+
// =============================================================================
|
|
96
|
+
|
|
97
|
+
/** Default commands directory under user home */
|
|
98
|
+
const COMMANDS_DIR = "commands";
|
|
99
|
+
|
|
100
|
+
/** JavaScript extensions (CLI backward compatibility) */
|
|
101
|
+
const JS_EXTENSIONS = [".js", ".mjs"] as const;
|
|
102
|
+
|
|
103
|
+
/** Valid command categories */
|
|
104
|
+
const VALID_CATEGORIES: CommandCategory[] = [
|
|
105
|
+
"system",
|
|
106
|
+
"workflow",
|
|
107
|
+
"auth",
|
|
108
|
+
"session",
|
|
109
|
+
"navigation",
|
|
110
|
+
"tools",
|
|
111
|
+
"config",
|
|
112
|
+
"debug",
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
// =============================================================================
|
|
116
|
+
// Adapter Functions
|
|
117
|
+
// =============================================================================
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Convert Core's UserCommand to CLI's SlashCommand
|
|
121
|
+
*
|
|
122
|
+
* Handles all command types:
|
|
123
|
+
* - YAML shell commands: Execute shell and return result
|
|
124
|
+
* - YAML prompt commands: Return prompt for injection
|
|
125
|
+
* - TypeScript commands: Execute with full context
|
|
126
|
+
*/
|
|
127
|
+
function adaptToSlashCommand(command: CoreUserCommand): SlashCommand {
|
|
128
|
+
const baseCommand: Omit<SlashCommand, "execute"> = {
|
|
129
|
+
name: command.name,
|
|
130
|
+
description: command.description,
|
|
131
|
+
kind: "user",
|
|
132
|
+
category: (command.category as CommandCategory) ?? "tools",
|
|
133
|
+
aliases: command.aliases,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
...baseCommand,
|
|
138
|
+
execute: createExecuteHandler(command),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Create execution handler based on command type
|
|
144
|
+
*/
|
|
145
|
+
function createExecuteHandler(
|
|
146
|
+
command: CoreUserCommand
|
|
147
|
+
): (ctx: CommandContext) => Promise<CommandResult> {
|
|
148
|
+
return async (ctx: CommandContext): Promise<CommandResult> => {
|
|
149
|
+
// Build args for user command
|
|
150
|
+
const args: CoreUserCommandArgs = {
|
|
151
|
+
raw: ctx.parsedArgs.raw,
|
|
152
|
+
positional: ctx.parsedArgs.positional.map((p) => String(p)),
|
|
153
|
+
named: ctx.parsedArgs.named as Record<string, string | boolean>,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Build context for user command
|
|
157
|
+
const userCtx: CoreUserCommandContext = {
|
|
158
|
+
cwd: ctx.session.cwd,
|
|
159
|
+
sessionId: ctx.session.id,
|
|
160
|
+
provider: ctx.session.provider,
|
|
161
|
+
homeDir: os.homedir(),
|
|
162
|
+
env: {}, // Security: Don't expose full env
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
// Handle YAML shell commands
|
|
167
|
+
if (command.type === "yaml" && command.commandType === "shell") {
|
|
168
|
+
const shellCmd = command as YamlShellCommand;
|
|
169
|
+
return {
|
|
170
|
+
kind: "success",
|
|
171
|
+
message: `Shell command: ${shellCmd.shell}`,
|
|
172
|
+
data: { shell: shellCmd.shell, args },
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Handle YAML prompt commands
|
|
177
|
+
if (command.type === "yaml" && command.commandType === "prompt") {
|
|
178
|
+
const promptCmd = command as YamlPromptCommand;
|
|
179
|
+
return {
|
|
180
|
+
kind: "success",
|
|
181
|
+
message: promptCmd.prompt,
|
|
182
|
+
data: { prompt: promptCmd.prompt },
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Handle TypeScript commands
|
|
187
|
+
if (command.type === "typescript" && command.execute) {
|
|
188
|
+
const result = await command.execute(args, userCtx);
|
|
189
|
+
|
|
190
|
+
if (result.success) {
|
|
191
|
+
// Handle prompt injection
|
|
192
|
+
if (result.prompt) {
|
|
193
|
+
return {
|
|
194
|
+
kind: "success",
|
|
195
|
+
message: result.prompt,
|
|
196
|
+
data: { prompt: result.prompt, ...((result.data as object) ?? {}) },
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Handle shell command
|
|
201
|
+
if (result.shell) {
|
|
202
|
+
return {
|
|
203
|
+
kind: "success",
|
|
204
|
+
message: `Shell command: ${result.shell}`,
|
|
205
|
+
data: { shell: result.shell, ...((result.data as object) ?? {}) },
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
kind: "success",
|
|
211
|
+
message: result.message,
|
|
212
|
+
data: result.data,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
kind: "error",
|
|
218
|
+
code: "INTERNAL_ERROR",
|
|
219
|
+
message: result.error ?? "Command failed",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Untrusted TypeScript command
|
|
224
|
+
if (command.type === "typescript" && !command.trusted) {
|
|
225
|
+
return {
|
|
226
|
+
kind: "error",
|
|
227
|
+
code: "PERMISSION_DENIED",
|
|
228
|
+
message: `Command '${command.name}' requires trust verification. Run /trust to approve.`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
kind: "error",
|
|
234
|
+
code: "INTERNAL_ERROR",
|
|
235
|
+
message: `Unknown command type: ${command.type}`,
|
|
236
|
+
};
|
|
237
|
+
} catch (err) {
|
|
238
|
+
return {
|
|
239
|
+
kind: "error",
|
|
240
|
+
code: "INTERNAL_ERROR",
|
|
241
|
+
message: err instanceof Error ? err.message : String(err),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Convert Core's load result to CLI's format
|
|
249
|
+
*/
|
|
250
|
+
function adaptLoadResult(coreResult: CoreUserCommandLoadResult): UserCommandLoadResult {
|
|
251
|
+
// Convert trusted commands to SlashCommands
|
|
252
|
+
const commands = coreResult.commands.map(adaptToSlashCommand);
|
|
253
|
+
|
|
254
|
+
// Also include pending trust commands (they'll show permission error when executed)
|
|
255
|
+
const pendingCommands = coreResult.pendingTrust.map(adaptToSlashCommand);
|
|
256
|
+
|
|
257
|
+
// Convert error format
|
|
258
|
+
const errors: UserCommandValidationError[] = coreResult.errors.map((e) => ({
|
|
259
|
+
file: e.file,
|
|
260
|
+
error: e.error,
|
|
261
|
+
}));
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
commands: [...commands, ...pendingCommands],
|
|
265
|
+
errors,
|
|
266
|
+
scanned: coreResult.scanned,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// =============================================================================
|
|
271
|
+
// JavaScript Command Loader (Backward Compatibility)
|
|
272
|
+
// =============================================================================
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Load a JavaScript command file directly (backward compatibility).
|
|
276
|
+
* Core loader only supports .ts/.yaml, so we handle .js files here.
|
|
277
|
+
*/
|
|
278
|
+
async function loadJsCommandFile(filePath: string): Promise<CoreUserCommandDefinition> {
|
|
279
|
+
const fileUrl = pathToFileURL(filePath).href;
|
|
280
|
+
const module = await import(fileUrl);
|
|
281
|
+
const definition = module.default;
|
|
282
|
+
|
|
283
|
+
if (!definition) {
|
|
284
|
+
throw new Error("No default export found");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return definition as CoreUserCommandDefinition;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Validate a JavaScript command definition
|
|
292
|
+
*/
|
|
293
|
+
function validateJsDefinition(
|
|
294
|
+
definition: unknown,
|
|
295
|
+
_filePath: string
|
|
296
|
+
): { valid: true } | { valid: false; error: string } {
|
|
297
|
+
if (!definition || typeof definition !== "object") {
|
|
298
|
+
return { valid: false, error: "Invalid command definition: not an object" };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const def = definition as Record<string, unknown>;
|
|
302
|
+
|
|
303
|
+
// Check required fields
|
|
304
|
+
if (typeof def.name !== "string" || def.name.trim() === "") {
|
|
305
|
+
return { valid: false, error: "Missing or invalid 'name' field" };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (typeof def.description !== "string" || def.description.trim() === "") {
|
|
309
|
+
return { valid: false, error: "Missing or invalid 'description' field" };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (typeof def.execute !== "function") {
|
|
313
|
+
return { valid: false, error: "Missing or invalid 'execute' function" };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Validate name format (should start with /)
|
|
317
|
+
const name = def.name as string;
|
|
318
|
+
if (!name.startsWith("/")) {
|
|
319
|
+
return {
|
|
320
|
+
valid: false,
|
|
321
|
+
error: `Command name must start with '/': got '${name}'`,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Validate aliases if present
|
|
326
|
+
if (def.aliases !== undefined) {
|
|
327
|
+
if (!Array.isArray(def.aliases)) {
|
|
328
|
+
return { valid: false, error: "'aliases' must be an array" };
|
|
329
|
+
}
|
|
330
|
+
for (const alias of def.aliases) {
|
|
331
|
+
if (typeof alias !== "string") {
|
|
332
|
+
return { valid: false, error: "All aliases must be strings" };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Validate category if present
|
|
338
|
+
if (def.category !== undefined) {
|
|
339
|
+
if (!VALID_CATEGORIES.includes(def.category as CommandCategory)) {
|
|
340
|
+
return {
|
|
341
|
+
valid: false,
|
|
342
|
+
error: `Invalid category '${def.category}'. Must be one of: ${VALID_CATEGORIES.join(", ")}`,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return { valid: true };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Convert a JavaScript command definition to SlashCommand
|
|
352
|
+
*/
|
|
353
|
+
function convertJsToSlashCommand(definition: CoreUserCommandDefinition): SlashCommand {
|
|
354
|
+
// Strip leading / from name for internal use
|
|
355
|
+
const name = definition.name.startsWith("/") ? definition.name.slice(1) : definition.name;
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
name,
|
|
359
|
+
description: definition.description,
|
|
360
|
+
kind: "user",
|
|
361
|
+
category: (definition.category as CommandCategory) ?? "tools",
|
|
362
|
+
aliases: definition.aliases,
|
|
363
|
+
execute: async (ctx: CommandContext): Promise<CommandResult> => {
|
|
364
|
+
// Build simplified args for user command
|
|
365
|
+
const args: CoreUserCommandArgs = {
|
|
366
|
+
raw: ctx.parsedArgs.raw,
|
|
367
|
+
positional: ctx.parsedArgs.positional.map((p) => String(p)),
|
|
368
|
+
named: ctx.parsedArgs.named as Record<string, string | boolean>,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// Build simplified context for user command
|
|
372
|
+
const userCtx: CoreUserCommandContext = {
|
|
373
|
+
cwd: ctx.session.cwd,
|
|
374
|
+
sessionId: ctx.session.id,
|
|
375
|
+
provider: ctx.session.provider,
|
|
376
|
+
homeDir: os.homedir(),
|
|
377
|
+
env: {},
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
const result = await definition.execute(args, userCtx);
|
|
382
|
+
|
|
383
|
+
if (result.success) {
|
|
384
|
+
return {
|
|
385
|
+
kind: "success",
|
|
386
|
+
message: result.message,
|
|
387
|
+
data: result.data,
|
|
388
|
+
};
|
|
389
|
+
} else {
|
|
390
|
+
return {
|
|
391
|
+
kind: "error",
|
|
392
|
+
code: "INTERNAL_ERROR",
|
|
393
|
+
message: result.error ?? "Command failed",
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
} catch (err) {
|
|
397
|
+
return {
|
|
398
|
+
kind: "error",
|
|
399
|
+
code: "INTERNAL_ERROR",
|
|
400
|
+
message: err instanceof Error ? err.message : String(err),
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// =============================================================================
|
|
408
|
+
// UserCommandLoader Class
|
|
409
|
+
// =============================================================================
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Loads and validates user-defined commands
|
|
413
|
+
*
|
|
414
|
+
* Adapts @vellum/core's UserCommandLoader for CLI's CommandRegistry.
|
|
415
|
+
* Scans both project (.vellum/commands/) and user (~/.vellum/commands/)
|
|
416
|
+
* directories for command files.
|
|
417
|
+
*
|
|
418
|
+
* Supports:
|
|
419
|
+
* - JavaScript files (.js, .mjs) - loaded directly for backward compatibility
|
|
420
|
+
* - TypeScript files (.ts, .mts) - via Core loader with trust verification
|
|
421
|
+
* - YAML files (.yaml, .yml) - via Core loader
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* ```typescript
|
|
425
|
+
* const loader = new UserCommandLoader();
|
|
426
|
+
* const result = await loader.load();
|
|
427
|
+
*
|
|
428
|
+
* if (result.errors.length > 0) {
|
|
429
|
+
* console.warn('Some commands failed to load:', result.errors);
|
|
430
|
+
* }
|
|
431
|
+
*
|
|
432
|
+
* for (const cmd of result.commands) {
|
|
433
|
+
* registry.register(cmd);
|
|
434
|
+
* }
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
export class UserCommandLoader {
|
|
438
|
+
/** Base directory for user commands */
|
|
439
|
+
private readonly commandsDir: string;
|
|
440
|
+
/** Core loader options */
|
|
441
|
+
private readonly coreOptions: UserCommandLoaderOptions;
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Create a new UserCommandLoader
|
|
445
|
+
*
|
|
446
|
+
* @param baseDir - Optional base directory (defaults to ~/.vellum)
|
|
447
|
+
*/
|
|
448
|
+
constructor(baseDir?: string) {
|
|
449
|
+
const vellumDir = baseDir ?? path.join(os.homedir(), ".vellum");
|
|
450
|
+
this.commandsDir = path.join(vellumDir, COMMANDS_DIR);
|
|
451
|
+
|
|
452
|
+
// Configure core loader
|
|
453
|
+
this.coreOptions = {
|
|
454
|
+
cwd: process.cwd(),
|
|
455
|
+
loadUserCommands: true,
|
|
456
|
+
userHomeDir: baseDir ? path.dirname(baseDir) : os.homedir(),
|
|
457
|
+
autoTrust: false, // Require explicit trust for TypeScript commands
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// If baseDir is specified, we're in test mode
|
|
461
|
+
if (baseDir) {
|
|
462
|
+
this.coreOptions.cwd = baseDir;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Get the commands directory path
|
|
468
|
+
*/
|
|
469
|
+
getCommandsDir(): string {
|
|
470
|
+
return this.commandsDir;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Load all user commands from the commands directory
|
|
475
|
+
*
|
|
476
|
+
* @returns Load result with commands and any errors
|
|
477
|
+
*/
|
|
478
|
+
async load(): Promise<UserCommandLoadResult> {
|
|
479
|
+
// Check if directory exists first (backward compatibility)
|
|
480
|
+
const dirExists = await this.directoryExists();
|
|
481
|
+
if (!dirExists) {
|
|
482
|
+
return {
|
|
483
|
+
commands: [],
|
|
484
|
+
errors: [],
|
|
485
|
+
scanned: 0,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Load JavaScript files directly (backward compatibility)
|
|
490
|
+
const jsResult = await this.loadJavaScriptCommands();
|
|
491
|
+
|
|
492
|
+
// Use Core's loader for TypeScript and YAML
|
|
493
|
+
const coreLoader = createUserCommandLoader(this.coreOptions);
|
|
494
|
+
const coreResult = await coreLoader.load();
|
|
495
|
+
const coreAdapted = adaptLoadResult(coreResult);
|
|
496
|
+
|
|
497
|
+
// Merge results (JS commands + Core commands)
|
|
498
|
+
return {
|
|
499
|
+
commands: [...jsResult.commands, ...coreAdapted.commands],
|
|
500
|
+
errors: [...jsResult.errors, ...coreAdapted.errors],
|
|
501
|
+
scanned: jsResult.scanned + coreAdapted.scanned,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Load JavaScript command files directly (backward compatibility)
|
|
507
|
+
*/
|
|
508
|
+
private async loadJavaScriptCommands(): Promise<UserCommandLoadResult> {
|
|
509
|
+
const result: UserCommandLoadResult = {
|
|
510
|
+
commands: [],
|
|
511
|
+
errors: [],
|
|
512
|
+
scanned: 0,
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
try {
|
|
516
|
+
const entries = await fs.readdir(this.commandsDir, { withFileTypes: true });
|
|
517
|
+
|
|
518
|
+
for (const entry of entries) {
|
|
519
|
+
if (!entry.isFile()) continue;
|
|
520
|
+
|
|
521
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
522
|
+
if (!JS_EXTENSIONS.includes(ext as (typeof JS_EXTENSIONS)[number])) {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
result.scanned++;
|
|
527
|
+
const filePath = path.join(this.commandsDir, entry.name);
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
const definition = await loadJsCommandFile(filePath);
|
|
531
|
+
const validation = validateJsDefinition(definition, filePath);
|
|
532
|
+
|
|
533
|
+
if (validation.valid) {
|
|
534
|
+
const command = convertJsToSlashCommand(definition);
|
|
535
|
+
result.commands.push(command);
|
|
536
|
+
} else {
|
|
537
|
+
result.errors.push({
|
|
538
|
+
file: filePath,
|
|
539
|
+
error: validation.error,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
} catch (err) {
|
|
543
|
+
result.errors.push({
|
|
544
|
+
file: filePath,
|
|
545
|
+
error: err instanceof Error ? err.message : String(err),
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
} catch {
|
|
550
|
+
// Directory doesn't exist or can't be read
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return result;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Check if the commands directory exists
|
|
558
|
+
*/
|
|
559
|
+
async directoryExists(): Promise<boolean> {
|
|
560
|
+
try {
|
|
561
|
+
const stat = await fs.stat(this.commandsDir);
|
|
562
|
+
return stat.isDirectory();
|
|
563
|
+
} catch {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// =============================================================================
|
|
570
|
+
// Registry Integration
|
|
571
|
+
// =============================================================================
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Load and register all user commands
|
|
575
|
+
*
|
|
576
|
+
* @param registry - Command registry to register commands in
|
|
577
|
+
* @param options - Optional configuration
|
|
578
|
+
* @returns Load result with commands and errors
|
|
579
|
+
*
|
|
580
|
+
* @example
|
|
581
|
+
* ```typescript
|
|
582
|
+
* const registry = new CommandRegistry();
|
|
583
|
+
* const result = await registerUserCommands(registry);
|
|
584
|
+
*
|
|
585
|
+
* console.log(`Loaded ${result.commands.length} user commands`);
|
|
586
|
+
* if (result.errors.length > 0) {
|
|
587
|
+
* console.warn('Failed to load:', result.errors);
|
|
588
|
+
* }
|
|
589
|
+
* ```
|
|
590
|
+
*/
|
|
591
|
+
export async function registerUserCommands(
|
|
592
|
+
registry: CommandRegistry,
|
|
593
|
+
options?: { baseDir?: string }
|
|
594
|
+
): Promise<UserCommandLoadResult> {
|
|
595
|
+
const loader = new UserCommandLoader(options?.baseDir);
|
|
596
|
+
const result = await loader.load();
|
|
597
|
+
|
|
598
|
+
// Register all successfully loaded commands
|
|
599
|
+
for (const command of result.commands) {
|
|
600
|
+
registry.register(command);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Create the commands directory if it doesn't exist
|
|
608
|
+
*
|
|
609
|
+
* @param baseDir - Optional base directory (defaults to ~/.vellum)
|
|
610
|
+
* @returns Path to the commands directory
|
|
611
|
+
*/
|
|
612
|
+
export async function ensureCommandsDirectory(baseDir?: string): Promise<string> {
|
|
613
|
+
const vellumDir = baseDir ?? path.join(os.homedir(), ".vellum");
|
|
614
|
+
const commandsDir = path.join(vellumDir, COMMANDS_DIR);
|
|
615
|
+
|
|
616
|
+
await fs.mkdir(commandsDir, { recursive: true });
|
|
617
|
+
|
|
618
|
+
return commandsDir;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Get example command template
|
|
623
|
+
*
|
|
624
|
+
* Returns TypeScript command template from @vellum/core
|
|
625
|
+
*
|
|
626
|
+
* @returns Example command file content
|
|
627
|
+
*/
|
|
628
|
+
export function getCommandTemplate(): string {
|
|
629
|
+
return getTypeScriptCommandTemplate();
|
|
630
|
+
}
|