@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,709 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Agents CLI Command Tests (T024)
|
|
3
|
+
*
|
|
4
|
+
* Integration tests for custom agents CLI commands:
|
|
5
|
+
* - list (T020)
|
|
6
|
+
* - create (T021)
|
|
7
|
+
* - validate (T022)
|
|
8
|
+
* - info (T023)
|
|
9
|
+
* - export (T020a)
|
|
10
|
+
* - import (T020b)
|
|
11
|
+
*
|
|
12
|
+
* @module cli/commands/custom-agents/__tests__
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as fs from "node:fs/promises";
|
|
16
|
+
import * as os from "node:os";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
|
|
19
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
CommandContext,
|
|
23
|
+
CommandError,
|
|
24
|
+
CommandInteractive,
|
|
25
|
+
CommandResult,
|
|
26
|
+
CommandSuccess,
|
|
27
|
+
ParsedArgs,
|
|
28
|
+
} from "../../types.js";
|
|
29
|
+
import { handleCreate } from "../create.js";
|
|
30
|
+
import { handleExport } from "../export.js";
|
|
31
|
+
import { handleImport } from "../import.js";
|
|
32
|
+
import { customAgentsCommand, executeCustomAgents, getCustomAgentsHelp } from "../index.js";
|
|
33
|
+
import { handleInfo } from "../info.js";
|
|
34
|
+
import { handleList, type ListJsonOutput } from "../list.js";
|
|
35
|
+
import { handleValidate } from "../validate.js";
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Test Setup
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
const TEST_DIR = path.join(os.tmpdir(), "vellum-custom-agents-test");
|
|
42
|
+
const AGENTS_DIR = path.join(TEST_DIR, ".vellum", "agents");
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Type assertion helpers for CommandResult
|
|
46
|
+
*/
|
|
47
|
+
function assertSuccess(result: CommandResult): asserts result is CommandSuccess {
|
|
48
|
+
expect(result.kind).toBe("success");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertError(result: CommandResult): asserts result is CommandError {
|
|
52
|
+
expect(result.kind).toBe("error");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function assertInteractive(result: CommandResult): asserts result is CommandInteractive {
|
|
56
|
+
expect(result.kind).toBe("interactive");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Valid agent YAML content
|
|
61
|
+
*/
|
|
62
|
+
const VALID_AGENT_YAML = `slug: test-agent
|
|
63
|
+
name: "Test Agent"
|
|
64
|
+
mode: code
|
|
65
|
+
description: "A test agent for unit tests"
|
|
66
|
+
icon: "🧪"
|
|
67
|
+
tags:
|
|
68
|
+
- test
|
|
69
|
+
- example
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Valid agent Markdown content
|
|
74
|
+
*/
|
|
75
|
+
const VALID_AGENT_MD = `---
|
|
76
|
+
slug: test-agent-md
|
|
77
|
+
name: "Test Agent MD"
|
|
78
|
+
mode: code
|
|
79
|
+
description: "A test agent in markdown format"
|
|
80
|
+
icon: "📝"
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
# Test Agent
|
|
84
|
+
|
|
85
|
+
You are a test agent.
|
|
86
|
+
|
|
87
|
+
## Instructions
|
|
88
|
+
|
|
89
|
+
Help with testing.
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Invalid agent (missing required fields)
|
|
94
|
+
*/
|
|
95
|
+
const INVALID_AGENT_YAML = `name: "Invalid Agent"
|
|
96
|
+
# Missing slug
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create mock CommandContext
|
|
101
|
+
*/
|
|
102
|
+
function createMockContext(overrides: Partial<ParsedArgs> = {}): CommandContext {
|
|
103
|
+
return {
|
|
104
|
+
session: {
|
|
105
|
+
id: "test-session",
|
|
106
|
+
provider: "anthropic",
|
|
107
|
+
cwd: TEST_DIR,
|
|
108
|
+
},
|
|
109
|
+
credentials: {
|
|
110
|
+
resolve: vi.fn(),
|
|
111
|
+
store: vi.fn(),
|
|
112
|
+
delete: vi.fn(),
|
|
113
|
+
list: vi.fn(),
|
|
114
|
+
} as unknown as CommandContext["credentials"],
|
|
115
|
+
toolRegistry: {
|
|
116
|
+
get: vi.fn(),
|
|
117
|
+
list: vi.fn(),
|
|
118
|
+
} as unknown as CommandContext["toolRegistry"],
|
|
119
|
+
parsedArgs: {
|
|
120
|
+
command: overrides.command ?? "custom-agents",
|
|
121
|
+
positional: overrides.positional ?? [],
|
|
122
|
+
named: overrides.named ?? {},
|
|
123
|
+
raw: overrides.raw ?? "/custom-agents",
|
|
124
|
+
},
|
|
125
|
+
emit: vi.fn(),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Write test agent file
|
|
131
|
+
*/
|
|
132
|
+
async function writeTestAgent(filename: string, content: string): Promise<string> {
|
|
133
|
+
const filePath = path.join(AGENTS_DIR, filename);
|
|
134
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
135
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
136
|
+
return filePath;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// =============================================================================
|
|
140
|
+
// Setup/Teardown
|
|
141
|
+
// =============================================================================
|
|
142
|
+
|
|
143
|
+
beforeEach(async () => {
|
|
144
|
+
// Create test directory
|
|
145
|
+
await fs.mkdir(AGENTS_DIR, { recursive: true });
|
|
146
|
+
// Mock cwd
|
|
147
|
+
vi.spyOn(process, "cwd").mockReturnValue(TEST_DIR);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
afterEach(async () => {
|
|
151
|
+
// Clean up test directory
|
|
152
|
+
try {
|
|
153
|
+
await fs.rm(TEST_DIR, { recursive: true, force: true });
|
|
154
|
+
} catch {
|
|
155
|
+
// Ignore errors during cleanup
|
|
156
|
+
}
|
|
157
|
+
vi.restoreAllMocks();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// =============================================================================
|
|
161
|
+
// T020: Command Definition Tests
|
|
162
|
+
// =============================================================================
|
|
163
|
+
|
|
164
|
+
describe("customAgentsCommand", () => {
|
|
165
|
+
describe("command definition", () => {
|
|
166
|
+
it("should have correct name and aliases", () => {
|
|
167
|
+
expect(customAgentsCommand.name).toBe("custom-agents");
|
|
168
|
+
expect(customAgentsCommand.aliases).toContain("ca");
|
|
169
|
+
expect(customAgentsCommand.aliases).toContain("custom-agent");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should be a builtin config command", () => {
|
|
173
|
+
expect(customAgentsCommand.kind).toBe("builtin");
|
|
174
|
+
expect(customAgentsCommand.category).toBe("config");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should define subcommand positional argument", () => {
|
|
178
|
+
const subArg = customAgentsCommand.positionalArgs?.find((a) => a.name === "subcommand");
|
|
179
|
+
expect(subArg).toBeDefined();
|
|
180
|
+
expect(subArg?.required).toBe(false);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should define expected named arguments", () => {
|
|
184
|
+
const namedArgs = customAgentsCommand.namedArgs ?? [];
|
|
185
|
+
expect(namedArgs.some((a) => a.name === "json")).toBe(true);
|
|
186
|
+
expect(namedArgs.some((a) => a.name === "global")).toBe(true);
|
|
187
|
+
expect(namedArgs.some((a) => a.name === "local")).toBe(true);
|
|
188
|
+
expect(namedArgs.some((a) => a.name === "template")).toBe(true);
|
|
189
|
+
expect(namedArgs.some((a) => a.name === "strict")).toBe(true);
|
|
190
|
+
expect(namedArgs.some((a) => a.name === "show-prompt")).toBe(true);
|
|
191
|
+
expect(namedArgs.some((a) => a.name === "output")).toBe(true);
|
|
192
|
+
expect(namedArgs.some((a) => a.name === "format")).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("help text", () => {
|
|
197
|
+
it("should show help when no subcommand provided", async () => {
|
|
198
|
+
const ctx = createMockContext({ positional: [] });
|
|
199
|
+
const result = await executeCustomAgents(ctx);
|
|
200
|
+
|
|
201
|
+
assertSuccess(result);
|
|
202
|
+
expect(result.message).toContain("Custom Agents Commands");
|
|
203
|
+
expect(result.message).toContain("list");
|
|
204
|
+
expect(result.message).toContain("create");
|
|
205
|
+
expect(result.message).toContain("validate");
|
|
206
|
+
expect(result.message).toContain("info");
|
|
207
|
+
expect(result.message).toContain("export");
|
|
208
|
+
expect(result.message).toContain("import");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should return same help from getCustomAgentsHelp", () => {
|
|
212
|
+
const help = getCustomAgentsHelp();
|
|
213
|
+
expect(help).toContain("Custom Agents Commands");
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// =============================================================================
|
|
219
|
+
// T020: List Command Tests
|
|
220
|
+
// =============================================================================
|
|
221
|
+
|
|
222
|
+
describe("handleList", () => {
|
|
223
|
+
it("should return success with empty list when no agents", async () => {
|
|
224
|
+
const result = await handleList({});
|
|
225
|
+
|
|
226
|
+
assertSuccess(result);
|
|
227
|
+
expect(result.message).toContain("(none)");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should list agents grouped by scope", async () => {
|
|
231
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
232
|
+
|
|
233
|
+
const result = await handleList({});
|
|
234
|
+
|
|
235
|
+
assertSuccess(result);
|
|
236
|
+
expect(result.message).toContain("test-agent");
|
|
237
|
+
expect(result.message).toContain("Project Agents");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should output JSON when --json flag is set", async () => {
|
|
241
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
242
|
+
|
|
243
|
+
const result = await handleList({ json: true });
|
|
244
|
+
|
|
245
|
+
assertSuccess(result);
|
|
246
|
+
const json = JSON.parse(result.message ?? "{}") as ListJsonOutput;
|
|
247
|
+
expect(json.success).toBe(true);
|
|
248
|
+
expect(json.agents.project).toBeDefined();
|
|
249
|
+
expect(json.agents.user).toBeDefined();
|
|
250
|
+
expect(json.agents.system).toBeDefined();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should filter to project scope with --local flag", async () => {
|
|
254
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
255
|
+
|
|
256
|
+
const result = await handleList({ local: true });
|
|
257
|
+
|
|
258
|
+
assertSuccess(result);
|
|
259
|
+
expect(result.message).toContain("Project Agents");
|
|
260
|
+
expect(result.message).not.toContain("User Agents");
|
|
261
|
+
expect(result.message).not.toContain("System Agents");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("should filter to user scope with --global flag", async () => {
|
|
265
|
+
const result = await handleList({ global: true });
|
|
266
|
+
|
|
267
|
+
assertSuccess(result);
|
|
268
|
+
expect(result.message).toContain("User Agents");
|
|
269
|
+
expect(result.message).not.toContain("Project Agents");
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// =============================================================================
|
|
274
|
+
// T021: Create Command Tests
|
|
275
|
+
// =============================================================================
|
|
276
|
+
|
|
277
|
+
describe("handleCreate", () => {
|
|
278
|
+
it("should require slug when no-interactive", async () => {
|
|
279
|
+
const result = await handleCreate(undefined, { noInteractive: true });
|
|
280
|
+
|
|
281
|
+
assertError(result);
|
|
282
|
+
expect(result.code).toBe("MISSING_ARGUMENT");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("should reject invalid slug", async () => {
|
|
286
|
+
const result = await handleCreate("Invalid_Slug!", {});
|
|
287
|
+
|
|
288
|
+
assertError(result);
|
|
289
|
+
expect(result.code).toBe("INVALID_ARGUMENT");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("should create agent with basic template", async () => {
|
|
293
|
+
const result = await handleCreate("my-test-agent", {
|
|
294
|
+
template: "basic",
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
assertSuccess(result);
|
|
298
|
+
expect(result.message).toContain("Created agent");
|
|
299
|
+
|
|
300
|
+
// Verify file was created
|
|
301
|
+
const filePath = path.join(AGENTS_DIR, "my-test-agent.md");
|
|
302
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
303
|
+
expect(content).toContain("slug: my-test-agent");
|
|
304
|
+
expect(content).toContain('name: "My Test Agent"');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("should create agent with advanced template", async () => {
|
|
308
|
+
const result = await handleCreate("advanced-agent", {
|
|
309
|
+
template: "advanced",
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
assertSuccess(result);
|
|
313
|
+
|
|
314
|
+
const filePath = path.join(AGENTS_DIR, "advanced-agent.md");
|
|
315
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
316
|
+
expect(content).toContain("toolGroups:");
|
|
317
|
+
expect(content).toContain("restrictions:");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should create agent with orchestrator template", async () => {
|
|
321
|
+
const result = await handleCreate("orchestrator-agent", {
|
|
322
|
+
template: "orchestrator",
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
assertSuccess(result);
|
|
326
|
+
|
|
327
|
+
const filePath = path.join(AGENTS_DIR, "orchestrator-agent.md");
|
|
328
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
329
|
+
expect(content).toContain("coordination:");
|
|
330
|
+
expect(content).toContain("level: orchestrator");
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("should reject duplicate slug", async () => {
|
|
334
|
+
// Create first agent
|
|
335
|
+
await handleCreate("duplicate-agent", {});
|
|
336
|
+
|
|
337
|
+
// Try to create again
|
|
338
|
+
const result = await handleCreate("duplicate-agent", {});
|
|
339
|
+
|
|
340
|
+
assertError(result);
|
|
341
|
+
expect(result.code).toBe("OPERATION_NOT_ALLOWED");
|
|
342
|
+
expect(result.message).toContain("already exists");
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("should reject unknown template", async () => {
|
|
346
|
+
const result = await handleCreate("my-agent", {
|
|
347
|
+
template: "unknown-template",
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
assertError(result);
|
|
351
|
+
expect(result.code).toBe("INVALID_ARGUMENT");
|
|
352
|
+
expect(result.message).toContain("Unknown template");
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// =============================================================================
|
|
357
|
+
// T022: Validate Command Tests
|
|
358
|
+
// =============================================================================
|
|
359
|
+
|
|
360
|
+
describe("handleValidate", () => {
|
|
361
|
+
it("should return success message when no agents to validate", async () => {
|
|
362
|
+
const result = await handleValidate({});
|
|
363
|
+
|
|
364
|
+
assertSuccess(result);
|
|
365
|
+
expect(result.message).toContain("No custom agents found");
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("should validate all agents", async () => {
|
|
369
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
370
|
+
|
|
371
|
+
const result = await handleValidate({});
|
|
372
|
+
|
|
373
|
+
assertSuccess(result);
|
|
374
|
+
expect(result.message).toContain("valid");
|
|
375
|
+
expect(result.message).toContain("test-agent");
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("should validate specific agent by slug", async () => {
|
|
379
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
380
|
+
|
|
381
|
+
const result = await handleValidate({ target: "test-agent" });
|
|
382
|
+
|
|
383
|
+
assertSuccess(result);
|
|
384
|
+
expect(result.message).toContain("test-agent");
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("should validate specific agent by file path", async () => {
|
|
388
|
+
const filePath = await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
389
|
+
|
|
390
|
+
const result = await handleValidate({ target: filePath });
|
|
391
|
+
|
|
392
|
+
assertSuccess(result);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("should report errors for invalid agent", async () => {
|
|
396
|
+
const filePath = await writeTestAgent("invalid-agent.yaml", INVALID_AGENT_YAML);
|
|
397
|
+
|
|
398
|
+
// Validate the specific file to catch parse errors
|
|
399
|
+
const result = await handleValidate({ target: filePath });
|
|
400
|
+
|
|
401
|
+
// Should fail validation due to missing slug
|
|
402
|
+
assertError(result);
|
|
403
|
+
expect(result.message).toContain("invalid");
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("should fail in strict mode with warnings", async () => {
|
|
407
|
+
// Create agent without description (generates warning)
|
|
408
|
+
const agentWithoutDesc = `slug: minimal-agent
|
|
409
|
+
name: "Minimal Agent"
|
|
410
|
+
`;
|
|
411
|
+
await writeTestAgent("minimal.yaml", agentWithoutDesc);
|
|
412
|
+
|
|
413
|
+
const result = await handleValidate({ strict: true });
|
|
414
|
+
|
|
415
|
+
// Should fail due to warnings in strict mode
|
|
416
|
+
assertError(result);
|
|
417
|
+
expect(result.message).toContain("warning");
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("should return error for non-existent agent", async () => {
|
|
421
|
+
const result = await handleValidate({ target: "non-existent" });
|
|
422
|
+
|
|
423
|
+
assertError(result);
|
|
424
|
+
expect(result.code).toBe("RESOURCE_NOT_FOUND");
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// =============================================================================
|
|
429
|
+
// T023: Info Command Tests
|
|
430
|
+
// =============================================================================
|
|
431
|
+
|
|
432
|
+
describe("handleInfo", () => {
|
|
433
|
+
it("should require slug", async () => {
|
|
434
|
+
const result = await handleInfo(undefined, {});
|
|
435
|
+
|
|
436
|
+
assertError(result);
|
|
437
|
+
expect(result.code).toBe("MISSING_ARGUMENT");
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("should show agent info", async () => {
|
|
441
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
442
|
+
|
|
443
|
+
const result = await handleInfo("test-agent", {});
|
|
444
|
+
|
|
445
|
+
assertSuccess(result);
|
|
446
|
+
expect(result.message).toContain("Test Agent");
|
|
447
|
+
expect(result.message).toContain("test-agent");
|
|
448
|
+
expect(result.message).toContain("code");
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("should output JSON when --json flag is set", async () => {
|
|
452
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
453
|
+
|
|
454
|
+
const result = await handleInfo("test-agent", { json: true });
|
|
455
|
+
|
|
456
|
+
assertSuccess(result);
|
|
457
|
+
const json = JSON.parse(result.message ?? "{}");
|
|
458
|
+
expect(json.success).toBe(true);
|
|
459
|
+
expect(json.agent.slug).toBe("test-agent");
|
|
460
|
+
expect(json.agent.name).toBe("Test Agent");
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("should show full system prompt with --show-prompt", async () => {
|
|
464
|
+
await writeTestAgent("test-agent.md", VALID_AGENT_MD);
|
|
465
|
+
|
|
466
|
+
const result = await handleInfo("test-agent-md", { showPrompt: true });
|
|
467
|
+
|
|
468
|
+
assertSuccess(result);
|
|
469
|
+
expect(result.message).toContain("You are a test agent");
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("should return error for non-existent agent", async () => {
|
|
473
|
+
const result = await handleInfo("non-existent", {});
|
|
474
|
+
|
|
475
|
+
assertError(result);
|
|
476
|
+
expect(result.code).toBe("RESOURCE_NOT_FOUND");
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// =============================================================================
|
|
481
|
+
// T020a: Export Command Tests
|
|
482
|
+
// =============================================================================
|
|
483
|
+
|
|
484
|
+
describe("handleExport", () => {
|
|
485
|
+
it("should require slug", async () => {
|
|
486
|
+
const result = await handleExport(undefined, {});
|
|
487
|
+
|
|
488
|
+
assertError(result);
|
|
489
|
+
expect(result.code).toBe("MISSING_ARGUMENT");
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("should export agent to stdout as YAML by default", async () => {
|
|
493
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
494
|
+
|
|
495
|
+
const result = await handleExport("test-agent", {});
|
|
496
|
+
|
|
497
|
+
assertSuccess(result);
|
|
498
|
+
expect(result.message).toContain("slug: test-agent");
|
|
499
|
+
expect(result.message).toContain("name: Test Agent");
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it("should export agent as JSON", async () => {
|
|
503
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
504
|
+
|
|
505
|
+
const result = await handleExport("test-agent", { format: "json" });
|
|
506
|
+
|
|
507
|
+
assertSuccess(result);
|
|
508
|
+
expect(result.message).toContain('"slug": "test-agent"');
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it("should export agent to file", async () => {
|
|
512
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
513
|
+
const outputPath = path.join(TEST_DIR, "exported.yaml");
|
|
514
|
+
|
|
515
|
+
const result = await handleExport("test-agent", { output: outputPath });
|
|
516
|
+
|
|
517
|
+
assertSuccess(result);
|
|
518
|
+
expect(result.message).toContain("Exported");
|
|
519
|
+
|
|
520
|
+
// Verify file was written
|
|
521
|
+
const content = await fs.readFile(outputPath, "utf-8");
|
|
522
|
+
expect(content).toContain("slug: test-agent");
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("should return error for non-existent agent", async () => {
|
|
526
|
+
const result = await handleExport("non-existent", {});
|
|
527
|
+
|
|
528
|
+
assertError(result);
|
|
529
|
+
expect(result.code).toBe("RESOURCE_NOT_FOUND");
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("should reject invalid format", async () => {
|
|
533
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
534
|
+
|
|
535
|
+
const result = await handleExport("test-agent", {
|
|
536
|
+
format: "xml" as "yaml",
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
assertError(result);
|
|
540
|
+
expect(result.code).toBe("INVALID_ARGUMENT");
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// =============================================================================
|
|
545
|
+
// T020b: Import Command Tests
|
|
546
|
+
// =============================================================================
|
|
547
|
+
|
|
548
|
+
describe("handleImport", () => {
|
|
549
|
+
it("should require file path", async () => {
|
|
550
|
+
const result = await handleImport({ file: "" });
|
|
551
|
+
|
|
552
|
+
assertError(result);
|
|
553
|
+
expect(result.code).toBe("MISSING_ARGUMENT");
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("should return error for non-existent file", async () => {
|
|
557
|
+
const result = await handleImport({ file: "/non/existent/file.yaml" });
|
|
558
|
+
|
|
559
|
+
assertError(result);
|
|
560
|
+
expect(result.code).toBe("FILE_NOT_FOUND");
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("should import valid YAML agent", async () => {
|
|
564
|
+
// Write source file outside agents directory
|
|
565
|
+
const sourceFile = path.join(TEST_DIR, "import-source.yaml");
|
|
566
|
+
await fs.writeFile(sourceFile, VALID_AGENT_YAML, "utf-8");
|
|
567
|
+
|
|
568
|
+
const result = await handleImport({ file: sourceFile });
|
|
569
|
+
|
|
570
|
+
assertSuccess(result);
|
|
571
|
+
expect(result.message).toContain("Imported");
|
|
572
|
+
|
|
573
|
+
// Verify file was created in agents directory
|
|
574
|
+
const destPath = path.join(AGENTS_DIR, "test-agent.md");
|
|
575
|
+
const exists = await fs
|
|
576
|
+
.access(destPath)
|
|
577
|
+
.then(() => true)
|
|
578
|
+
.catch(() => false);
|
|
579
|
+
expect(exists).toBe(true);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("should import valid Markdown agent", async () => {
|
|
583
|
+
const sourceFile = path.join(TEST_DIR, "import-source.md");
|
|
584
|
+
await fs.writeFile(sourceFile, VALID_AGENT_MD, "utf-8");
|
|
585
|
+
|
|
586
|
+
const result = await handleImport({ file: sourceFile });
|
|
587
|
+
|
|
588
|
+
assertSuccess(result);
|
|
589
|
+
expect(result.message).toContain("Imported");
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it("should reject invalid agent file", async () => {
|
|
593
|
+
const sourceFile = path.join(TEST_DIR, "invalid.yaml");
|
|
594
|
+
await fs.writeFile(sourceFile, INVALID_AGENT_YAML, "utf-8");
|
|
595
|
+
|
|
596
|
+
const result = await handleImport({ file: sourceFile });
|
|
597
|
+
|
|
598
|
+
assertError(result);
|
|
599
|
+
expect(result.code).toBe("INVALID_ARGUMENT");
|
|
600
|
+
expect(result.message.toLowerCase()).toContain("validation failed");
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it("should prompt for confirmation on existing agent", async () => {
|
|
604
|
+
// Create existing agent
|
|
605
|
+
await writeTestAgent("test-agent.yaml", VALID_AGENT_YAML);
|
|
606
|
+
|
|
607
|
+
// Try to import same slug
|
|
608
|
+
const sourceFile = path.join(TEST_DIR, "import-source.yaml");
|
|
609
|
+
await fs.writeFile(sourceFile, VALID_AGENT_YAML, "utf-8");
|
|
610
|
+
|
|
611
|
+
const result = await handleImport({ file: sourceFile });
|
|
612
|
+
|
|
613
|
+
// Should return interactive prompt
|
|
614
|
+
assertInteractive(result);
|
|
615
|
+
expect(result.prompt.inputType).toBe("confirm");
|
|
616
|
+
expect(result.prompt.message).toContain("already exists");
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// =============================================================================
|
|
621
|
+
// Subcommand Routing Tests
|
|
622
|
+
// =============================================================================
|
|
623
|
+
|
|
624
|
+
describe("executeCustomAgents routing", () => {
|
|
625
|
+
it("should route list subcommand", async () => {
|
|
626
|
+
const ctx = createMockContext({
|
|
627
|
+
positional: ["list"],
|
|
628
|
+
named: { json: false },
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
const result = await executeCustomAgents(ctx);
|
|
632
|
+
|
|
633
|
+
assertSuccess(result);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it("should route create subcommand with slug", async () => {
|
|
637
|
+
const ctx = createMockContext({
|
|
638
|
+
positional: ["create", "new-agent"],
|
|
639
|
+
named: { template: "basic" },
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const result = await executeCustomAgents(ctx);
|
|
643
|
+
|
|
644
|
+
assertSuccess(result);
|
|
645
|
+
expect(result.message).toContain("Created agent");
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
it("should route validate subcommand", async () => {
|
|
649
|
+
const ctx = createMockContext({
|
|
650
|
+
positional: ["validate"],
|
|
651
|
+
named: {},
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
const result = await executeCustomAgents(ctx);
|
|
655
|
+
|
|
656
|
+
assertSuccess(result);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it("should route info subcommand", async () => {
|
|
660
|
+
const ctx = createMockContext({
|
|
661
|
+
positional: ["info"],
|
|
662
|
+
named: {},
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
const result = await executeCustomAgents(ctx);
|
|
666
|
+
|
|
667
|
+
// Should error because slug is required
|
|
668
|
+
assertError(result);
|
|
669
|
+
expect(result.code).toBe("MISSING_ARGUMENT");
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it("should route export subcommand", async () => {
|
|
673
|
+
const ctx = createMockContext({
|
|
674
|
+
positional: ["export"],
|
|
675
|
+
named: {},
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
const result = await executeCustomAgents(ctx);
|
|
679
|
+
|
|
680
|
+
// Should error because slug is required
|
|
681
|
+
assertError(result);
|
|
682
|
+
expect(result.code).toBe("MISSING_ARGUMENT");
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
it("should route import subcommand", async () => {
|
|
686
|
+
const ctx = createMockContext({
|
|
687
|
+
positional: ["import"],
|
|
688
|
+
named: {},
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
const result = await executeCustomAgents(ctx);
|
|
692
|
+
|
|
693
|
+
// Should error because file is required
|
|
694
|
+
assertError(result);
|
|
695
|
+
expect(result.code).toBe("MISSING_ARGUMENT");
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it("should show help for unknown subcommand", async () => {
|
|
699
|
+
const ctx = createMockContext({
|
|
700
|
+
positional: ["unknown"],
|
|
701
|
+
named: {},
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
const result = await executeCustomAgents(ctx);
|
|
705
|
+
|
|
706
|
+
assertSuccess(result);
|
|
707
|
+
expect(result.message).toContain("Custom Agents Commands");
|
|
708
|
+
});
|
|
709
|
+
});
|