@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,713 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests for CLI Credential Commands
|
|
3
|
+
*
|
|
4
|
+
* Tests CLI commands and slash commands for credential management:
|
|
5
|
+
* - `vellum credentials list` - Show all stored credentials
|
|
6
|
+
* - `vellum credentials add <provider>` - Add credential interactively
|
|
7
|
+
* - `vellum credentials remove <provider>` - Remove credential
|
|
8
|
+
* - `/credentials` - Show credential status
|
|
9
|
+
* - `/auth` - Unified authentication command (set, clear, status)
|
|
10
|
+
*
|
|
11
|
+
* @module cli/__tests__/credentials
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
Credential,
|
|
16
|
+
CredentialRef,
|
|
17
|
+
CredentialSource,
|
|
18
|
+
CredentialStore,
|
|
19
|
+
CredentialStoreError,
|
|
20
|
+
} from "@vellum/core";
|
|
21
|
+
import { Err, Ok, type Result } from "@vellum/core";
|
|
22
|
+
import { describe, expect, it } from "vitest";
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
authSlashCommands,
|
|
26
|
+
executeSlashCommand,
|
|
27
|
+
findSlashCommand,
|
|
28
|
+
getSlashCommandHelp,
|
|
29
|
+
isSlashCommand,
|
|
30
|
+
parseSlashCommand,
|
|
31
|
+
type SlashCommandContext,
|
|
32
|
+
} from "../commands/auth.js";
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// Mock Credential Store
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* In-memory mock credential store for testing
|
|
40
|
+
*/
|
|
41
|
+
class MockCredentialStore implements CredentialStore {
|
|
42
|
+
readonly name: CredentialSource;
|
|
43
|
+
readonly priority: number;
|
|
44
|
+
readonly readOnly: boolean;
|
|
45
|
+
private credentials: Map<string, Credential> = new Map();
|
|
46
|
+
private available: boolean = true;
|
|
47
|
+
|
|
48
|
+
constructor(options: {
|
|
49
|
+
name: CredentialSource;
|
|
50
|
+
priority?: number;
|
|
51
|
+
readOnly?: boolean;
|
|
52
|
+
available?: boolean;
|
|
53
|
+
initialCredentials?: Credential[];
|
|
54
|
+
}) {
|
|
55
|
+
this.name = options.name;
|
|
56
|
+
this.priority = options.priority ?? 50;
|
|
57
|
+
this.readOnly = options.readOnly ?? false;
|
|
58
|
+
this.available = options.available ?? true;
|
|
59
|
+
|
|
60
|
+
if (options.initialCredentials) {
|
|
61
|
+
for (const cred of options.initialCredentials) {
|
|
62
|
+
this.credentials.set(this.makeKey(cred.provider), cred);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private makeKey(provider: string, key?: string): string {
|
|
68
|
+
return key ? `${provider}:${key}` : provider;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setAvailable(available: boolean): void {
|
|
72
|
+
this.available = available;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async isAvailable(): Promise<Result<boolean, CredentialStoreError>> {
|
|
76
|
+
return Ok(this.available);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async get(
|
|
80
|
+
provider: string,
|
|
81
|
+
key?: string
|
|
82
|
+
): Promise<Result<Credential | null, CredentialStoreError>> {
|
|
83
|
+
const credential = this.credentials.get(this.makeKey(provider, key));
|
|
84
|
+
return Ok(credential ?? null);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async set(credential: Credential): Promise<Result<void, CredentialStoreError>> {
|
|
88
|
+
if (this.readOnly) {
|
|
89
|
+
return Err({
|
|
90
|
+
code: "READ_ONLY",
|
|
91
|
+
message: "Store is read-only",
|
|
92
|
+
store: this.name,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
this.credentials.set(this.makeKey(credential.provider), credential);
|
|
96
|
+
return Ok(undefined);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async delete(provider: string, key?: string): Promise<Result<boolean, CredentialStoreError>> {
|
|
100
|
+
if (this.readOnly) {
|
|
101
|
+
return Err({
|
|
102
|
+
code: "READ_ONLY",
|
|
103
|
+
message: "Store is read-only",
|
|
104
|
+
store: this.name,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const deleted = this.credentials.delete(this.makeKey(provider, key));
|
|
108
|
+
return Ok(deleted);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async exists(provider: string, key?: string): Promise<Result<boolean, CredentialStoreError>> {
|
|
112
|
+
return Ok(this.credentials.has(this.makeKey(provider, key)));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async list(provider?: string): Promise<Result<readonly CredentialRef[], CredentialStoreError>> {
|
|
116
|
+
const refs: CredentialRef[] = [];
|
|
117
|
+
for (const cred of this.credentials.values()) {
|
|
118
|
+
if (!provider || cred.provider === provider) {
|
|
119
|
+
refs.push({
|
|
120
|
+
id: cred.id,
|
|
121
|
+
provider: cred.provider,
|
|
122
|
+
type: cred.type,
|
|
123
|
+
source: cred.source,
|
|
124
|
+
maskedHint: `${cred.value.substring(0, 4)}****`,
|
|
125
|
+
metadata: cred.metadata,
|
|
126
|
+
createdAt: cred.createdAt,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return Ok(refs);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Test helpers
|
|
134
|
+
getAll(): Credential[] {
|
|
135
|
+
return Array.from(this.credentials.values());
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
clear(): void {
|
|
139
|
+
this.credentials.clear();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// =============================================================================
|
|
144
|
+
// Mock Credential Manager
|
|
145
|
+
// =============================================================================
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Mock CredentialManager for testing CLI commands
|
|
149
|
+
*/
|
|
150
|
+
class MockCredentialManager {
|
|
151
|
+
private stores: MockCredentialStore[];
|
|
152
|
+
private preferredWriteStore: CredentialSource;
|
|
153
|
+
|
|
154
|
+
constructor(options: {
|
|
155
|
+
stores: MockCredentialStore[];
|
|
156
|
+
preferredWriteStore?: CredentialSource;
|
|
157
|
+
}) {
|
|
158
|
+
this.stores = options.stores;
|
|
159
|
+
this.preferredWriteStore = options.preferredWriteStore ?? "keychain";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async resolve(
|
|
163
|
+
provider: string,
|
|
164
|
+
key?: string
|
|
165
|
+
): Promise<Result<Credential | null, CredentialStoreError>> {
|
|
166
|
+
for (const store of this.stores) {
|
|
167
|
+
const availResult = await store.isAvailable();
|
|
168
|
+
if (!availResult.ok || !availResult.value) continue;
|
|
169
|
+
|
|
170
|
+
const result = await store.get(provider, key);
|
|
171
|
+
if (result.ok && result.value) {
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return Ok(null);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async store(input: {
|
|
179
|
+
provider: string;
|
|
180
|
+
type: string;
|
|
181
|
+
value: string;
|
|
182
|
+
metadata?: { label?: string };
|
|
183
|
+
}): Promise<Result<{ source: CredentialSource }, CredentialStoreError>> {
|
|
184
|
+
// Find preferred store if available
|
|
185
|
+
let store: MockCredentialStore | undefined;
|
|
186
|
+
|
|
187
|
+
// Try preferred store first
|
|
188
|
+
const preferredStore = this.stores.find(
|
|
189
|
+
(s) => s.name === this.preferredWriteStore && !s.readOnly
|
|
190
|
+
);
|
|
191
|
+
if (preferredStore) {
|
|
192
|
+
const availResult = await preferredStore.isAvailable();
|
|
193
|
+
if (availResult.ok && availResult.value) {
|
|
194
|
+
store = preferredStore;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fall back to any available writable store
|
|
199
|
+
if (!store) {
|
|
200
|
+
for (const s of this.stores) {
|
|
201
|
+
if (s.readOnly) continue;
|
|
202
|
+
const availResult = await s.isAvailable();
|
|
203
|
+
if (availResult.ok && availResult.value) {
|
|
204
|
+
store = s;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!store) {
|
|
211
|
+
return Err({
|
|
212
|
+
code: "STORE_UNAVAILABLE",
|
|
213
|
+
message: "No writable store available",
|
|
214
|
+
store: "runtime",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const credential: Credential = {
|
|
219
|
+
id: `${store.name}:${input.provider}`,
|
|
220
|
+
provider: input.provider,
|
|
221
|
+
type: input.type as
|
|
222
|
+
| "api_key"
|
|
223
|
+
| "oauth_token"
|
|
224
|
+
| "bearer_token"
|
|
225
|
+
| "service_account"
|
|
226
|
+
| "certificate",
|
|
227
|
+
value: input.value,
|
|
228
|
+
source: store.name,
|
|
229
|
+
metadata: input.metadata ?? {},
|
|
230
|
+
createdAt: new Date(),
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const result = await store.set(credential);
|
|
234
|
+
if (!result.ok) return result as Result<never, CredentialStoreError>;
|
|
235
|
+
|
|
236
|
+
return Ok({ source: store.name });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async delete(provider: string, key?: string): Promise<Result<number, CredentialStoreError>> {
|
|
240
|
+
let count = 0;
|
|
241
|
+
for (const store of this.stores) {
|
|
242
|
+
if (store.readOnly) continue;
|
|
243
|
+
const result = await store.delete(provider, key);
|
|
244
|
+
if (result.ok && result.value) count++;
|
|
245
|
+
}
|
|
246
|
+
return Ok(count);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async exists(provider: string): Promise<Result<boolean, CredentialStoreError>> {
|
|
250
|
+
const result = await this.resolve(provider);
|
|
251
|
+
return Ok(result.ok && result.value !== null);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async list(provider?: string): Promise<Result<readonly CredentialRef[], CredentialStoreError>> {
|
|
255
|
+
const allRefs: CredentialRef[] = [];
|
|
256
|
+
for (const store of this.stores) {
|
|
257
|
+
const availResult = await store.isAvailable();
|
|
258
|
+
if (!availResult.ok || !availResult.value) continue;
|
|
259
|
+
|
|
260
|
+
const result = await store.list(provider);
|
|
261
|
+
if (result.ok) {
|
|
262
|
+
allRefs.push(...result.value);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return Ok(allRefs);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async getStoreAvailability(): Promise<Record<string, boolean>> {
|
|
269
|
+
const availability: Record<string, boolean> = {};
|
|
270
|
+
for (const store of this.stores) {
|
|
271
|
+
const result = await store.isAvailable();
|
|
272
|
+
availability[store.name] = result.ok && result.value;
|
|
273
|
+
}
|
|
274
|
+
return availability;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// =============================================================================
|
|
279
|
+
// Test Fixtures
|
|
280
|
+
// =============================================================================
|
|
281
|
+
|
|
282
|
+
function createTestCredential(provider: string, source: CredentialSource = "keychain"): Credential {
|
|
283
|
+
return {
|
|
284
|
+
id: `${source}:${provider}`,
|
|
285
|
+
provider,
|
|
286
|
+
type: "api_key",
|
|
287
|
+
value: `sk-${provider}-test-key-12345`,
|
|
288
|
+
source,
|
|
289
|
+
metadata: { label: `${provider} API Key` },
|
|
290
|
+
createdAt: new Date(),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function createMockStores(options?: {
|
|
295
|
+
keychainAvailable?: boolean;
|
|
296
|
+
envCredentials?: Credential[];
|
|
297
|
+
keychainCredentials?: Credential[];
|
|
298
|
+
fileCredentials?: Credential[];
|
|
299
|
+
}): MockCredentialStore[] {
|
|
300
|
+
return [
|
|
301
|
+
new MockCredentialStore({
|
|
302
|
+
name: "env",
|
|
303
|
+
priority: 100,
|
|
304
|
+
readOnly: true,
|
|
305
|
+
initialCredentials: options?.envCredentials,
|
|
306
|
+
}),
|
|
307
|
+
new MockCredentialStore({
|
|
308
|
+
name: "keychain",
|
|
309
|
+
priority: 75,
|
|
310
|
+
readOnly: false,
|
|
311
|
+
available: options?.keychainAvailable ?? true,
|
|
312
|
+
initialCredentials: options?.keychainCredentials,
|
|
313
|
+
}),
|
|
314
|
+
new MockCredentialStore({
|
|
315
|
+
name: "file",
|
|
316
|
+
priority: 50,
|
|
317
|
+
readOnly: false,
|
|
318
|
+
initialCredentials: options?.fileCredentials,
|
|
319
|
+
}),
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function createMockContext(options?: {
|
|
324
|
+
currentProvider?: string;
|
|
325
|
+
stores?: MockCredentialStore[];
|
|
326
|
+
keychainAvailable?: boolean;
|
|
327
|
+
envCredentials?: Credential[];
|
|
328
|
+
keychainCredentials?: Credential[];
|
|
329
|
+
fileCredentials?: Credential[];
|
|
330
|
+
}): SlashCommandContext {
|
|
331
|
+
const stores =
|
|
332
|
+
options?.stores ??
|
|
333
|
+
createMockStores({
|
|
334
|
+
keychainAvailable: options?.keychainAvailable,
|
|
335
|
+
envCredentials: options?.envCredentials,
|
|
336
|
+
keychainCredentials: options?.keychainCredentials,
|
|
337
|
+
fileCredentials: options?.fileCredentials,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
currentProvider: options?.currentProvider,
|
|
342
|
+
credentialManager: new MockCredentialManager({
|
|
343
|
+
stores,
|
|
344
|
+
preferredWriteStore: "keychain",
|
|
345
|
+
}) as unknown as SlashCommandContext["credentialManager"],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// =============================================================================
|
|
350
|
+
// Slash Command Parsing Tests
|
|
351
|
+
// =============================================================================
|
|
352
|
+
|
|
353
|
+
describe("Slash Command Parsing", () => {
|
|
354
|
+
describe("isSlashCommand", () => {
|
|
355
|
+
it("returns true for input starting with /", () => {
|
|
356
|
+
expect(isSlashCommand("/auth")).toBe(true);
|
|
357
|
+
expect(isSlashCommand("/auth set anthropic")).toBe(true);
|
|
358
|
+
expect(isSlashCommand("/credentials")).toBe(true);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("returns true for input with leading whitespace", () => {
|
|
362
|
+
expect(isSlashCommand(" /auth")).toBe(true);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("returns false for non-slash input", () => {
|
|
366
|
+
expect(isSlashCommand("login")).toBe(false);
|
|
367
|
+
expect(isSlashCommand("help")).toBe(false);
|
|
368
|
+
expect(isSlashCommand("")).toBe(false);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe("parseSlashCommand", () => {
|
|
373
|
+
it("parses command without arguments", () => {
|
|
374
|
+
const result = parseSlashCommand("/credentials");
|
|
375
|
+
expect(result).toEqual({ command: "credentials", args: [] });
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("parses command with single argument", () => {
|
|
379
|
+
const result = parseSlashCommand("/credentials anthropic");
|
|
380
|
+
expect(result).toEqual({ command: "credentials", args: ["anthropic"] });
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("parses command with multiple arguments", () => {
|
|
384
|
+
const result = parseSlashCommand("/help credentials details");
|
|
385
|
+
expect(result).toEqual({ command: "help", args: ["credentials", "details"] });
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("normalizes command to lowercase", () => {
|
|
389
|
+
const result = parseSlashCommand("/CREDENTIALS");
|
|
390
|
+
expect(result).toEqual({ command: "credentials", args: [] });
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("handles multiple spaces between arguments", () => {
|
|
394
|
+
const result = parseSlashCommand("/credentials anthropic extra");
|
|
395
|
+
expect(result).toEqual({ command: "credentials", args: ["anthropic", "extra"] });
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("returns null for non-slash input", () => {
|
|
399
|
+
expect(parseSlashCommand("credentials")).toBeNull();
|
|
400
|
+
expect(parseSlashCommand("")).toBeNull();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe("findSlashCommand", () => {
|
|
405
|
+
it("finds command by name", () => {
|
|
406
|
+
const cmd = findSlashCommand("credentials");
|
|
407
|
+
expect(cmd).toBeDefined();
|
|
408
|
+
expect(cmd?.name).toBe("credentials");
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("finds command by alias", () => {
|
|
412
|
+
const cmd = findSlashCommand("creds");
|
|
413
|
+
expect(cmd).toBeDefined();
|
|
414
|
+
expect(cmd?.name).toBe("credentials");
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it("returns undefined for unknown command", () => {
|
|
418
|
+
const cmd = findSlashCommand("unknown");
|
|
419
|
+
expect(cmd).toBeUndefined();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("is case-insensitive", () => {
|
|
423
|
+
const cmd = findSlashCommand("CREDENTIALS");
|
|
424
|
+
expect(cmd).toBeDefined();
|
|
425
|
+
expect(cmd?.name).toBe("credentials");
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// =============================================================================
|
|
431
|
+
// /credentials Command Tests
|
|
432
|
+
// =============================================================================
|
|
433
|
+
|
|
434
|
+
describe("/credentials Command", () => {
|
|
435
|
+
describe("Output Format", () => {
|
|
436
|
+
it("shows header and dividers", async () => {
|
|
437
|
+
const context = createMockContext();
|
|
438
|
+
const result = await executeSlashCommand("/credentials", context);
|
|
439
|
+
|
|
440
|
+
expect(result.success).toBe(true);
|
|
441
|
+
expect(result.message).toContain("🔐 Credential Status");
|
|
442
|
+
expect(result.message).toContain("━");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("shows storage backend status", async () => {
|
|
446
|
+
const context = createMockContext({
|
|
447
|
+
keychainAvailable: true,
|
|
448
|
+
});
|
|
449
|
+
const result = await executeSlashCommand("/credentials", context);
|
|
450
|
+
|
|
451
|
+
expect(result.message).toContain("📦 Storage Backends:");
|
|
452
|
+
expect(result.message).toContain("✓ keychain");
|
|
453
|
+
expect(result.message).toContain("✓ file");
|
|
454
|
+
expect(result.message).toContain("✓ env");
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it("shows unavailable backends with ✗", async () => {
|
|
458
|
+
const stores = createMockStores({ keychainAvailable: false });
|
|
459
|
+
stores.find((s) => s.name === "keychain")?.setAvailable(false);
|
|
460
|
+
const context = createMockContext({ stores });
|
|
461
|
+
|
|
462
|
+
const result = await executeSlashCommand("/credentials", context);
|
|
463
|
+
|
|
464
|
+
expect(result.message).toContain("✗ keychain");
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
describe("Credential Listing", () => {
|
|
469
|
+
it("shows message when no credentials", async () => {
|
|
470
|
+
const context = createMockContext();
|
|
471
|
+
const result = await executeSlashCommand("/credentials", context);
|
|
472
|
+
|
|
473
|
+
expect(result.message).toContain("No credentials stored");
|
|
474
|
+
// Note: The legacy slash command handler still references /login in output
|
|
475
|
+
// The enhanced authCommand uses /auth set. This is expected behavior.
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it("lists stored credentials with masked values", async () => {
|
|
479
|
+
const context = createMockContext({
|
|
480
|
+
keychainCredentials: [
|
|
481
|
+
createTestCredential("anthropic", "keychain"),
|
|
482
|
+
createTestCredential("openai", "keychain"),
|
|
483
|
+
],
|
|
484
|
+
});
|
|
485
|
+
const result = await executeSlashCommand("/credentials", context);
|
|
486
|
+
|
|
487
|
+
expect(result.message).toContain("🔑 Credentials:");
|
|
488
|
+
expect(result.message).toContain("anthropic (keychain)");
|
|
489
|
+
expect(result.message).toContain("openai (keychain)");
|
|
490
|
+
expect(result.message).toContain("sk-a****");
|
|
491
|
+
expect(result.message).toContain("[api_key]");
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it("filters by provider when specified", async () => {
|
|
495
|
+
const context = createMockContext({
|
|
496
|
+
keychainCredentials: [createTestCredential("anthropic"), createTestCredential("openai")],
|
|
497
|
+
});
|
|
498
|
+
const result = await executeSlashCommand("/credentials anthropic", context);
|
|
499
|
+
|
|
500
|
+
expect(result.message).toContain("anthropic");
|
|
501
|
+
expect(result.message).not.toContain("openai");
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("shows message when filtered provider not found", async () => {
|
|
505
|
+
const context = createMockContext({
|
|
506
|
+
keychainCredentials: [createTestCredential("anthropic")],
|
|
507
|
+
});
|
|
508
|
+
const result = await executeSlashCommand("/credentials google", context);
|
|
509
|
+
|
|
510
|
+
expect(result.message).toContain("No credential found for google");
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
describe("Data Property", () => {
|
|
515
|
+
it("includes availability in data", async () => {
|
|
516
|
+
const context = createMockContext();
|
|
517
|
+
const result = await executeSlashCommand("/credentials", context);
|
|
518
|
+
|
|
519
|
+
expect(result.data?.availability).toBeDefined();
|
|
520
|
+
expect(result.data?.availability).toHaveProperty("keychain");
|
|
521
|
+
expect(result.data?.availability).toHaveProperty("file");
|
|
522
|
+
expect(result.data?.availability).toHaveProperty("env");
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("includes credentials array in data", async () => {
|
|
526
|
+
const context = createMockContext({
|
|
527
|
+
keychainCredentials: [createTestCredential("anthropic")],
|
|
528
|
+
});
|
|
529
|
+
const result = await executeSlashCommand("/credentials", context);
|
|
530
|
+
|
|
531
|
+
expect(result.data?.credentials).toBeDefined();
|
|
532
|
+
expect(Array.isArray(result.data?.credentials)).toBe(true);
|
|
533
|
+
const creds = result.data?.credentials as unknown[];
|
|
534
|
+
expect(creds).toHaveLength(1);
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
describe("Aliases", () => {
|
|
539
|
+
it("works with /creds alias", async () => {
|
|
540
|
+
const context = createMockContext();
|
|
541
|
+
const result = await executeSlashCommand("/creds", context);
|
|
542
|
+
|
|
543
|
+
expect(result.success).toBe(true);
|
|
544
|
+
expect(result.message).toContain("Credential Status");
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it("works with /keys alias", async () => {
|
|
548
|
+
const context = createMockContext();
|
|
549
|
+
const result = await executeSlashCommand("/keys", context);
|
|
550
|
+
|
|
551
|
+
expect(result.success).toBe(true);
|
|
552
|
+
expect(result.message).toContain("Credential Status");
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// =============================================================================
|
|
558
|
+
// Unknown Command Handling Tests
|
|
559
|
+
// =============================================================================
|
|
560
|
+
|
|
561
|
+
describe("Unknown Command Handling", () => {
|
|
562
|
+
it("shows error for unknown command", async () => {
|
|
563
|
+
const context = createMockContext();
|
|
564
|
+
const result = await executeSlashCommand("/unknown", context);
|
|
565
|
+
|
|
566
|
+
expect(result.success).toBe(false);
|
|
567
|
+
expect(result.message).toContain("Unknown command");
|
|
568
|
+
expect(result.message).toContain("/unknown");
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it("lists available commands on unknown", async () => {
|
|
572
|
+
const context = createMockContext();
|
|
573
|
+
const result = await executeSlashCommand("/notreal", context);
|
|
574
|
+
|
|
575
|
+
expect(result.message).toContain("Available:");
|
|
576
|
+
expect(result.message).toContain("/credentials");
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it("provides help for specific command", async () => {
|
|
580
|
+
const context = createMockContext();
|
|
581
|
+
const result = await executeSlashCommand("/help credentials", context);
|
|
582
|
+
|
|
583
|
+
expect(result.success).toBe(true);
|
|
584
|
+
expect(result.message).toContain("credentials");
|
|
585
|
+
expect(result.message).toContain("Usage:");
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// =============================================================================
|
|
590
|
+
// Help System Tests
|
|
591
|
+
// =============================================================================
|
|
592
|
+
|
|
593
|
+
describe("getSlashCommandHelp", () => {
|
|
594
|
+
it("lists all available commands", () => {
|
|
595
|
+
const help = getSlashCommandHelp();
|
|
596
|
+
|
|
597
|
+
expect(help).toContain("Available Commands:");
|
|
598
|
+
expect(help).toContain("/credentials");
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it("shows aliases for commands", () => {
|
|
602
|
+
const help = getSlashCommandHelp();
|
|
603
|
+
|
|
604
|
+
expect(help).toContain("Aliases:");
|
|
605
|
+
expect(help).toContain("/creds");
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it("shows usage patterns", () => {
|
|
609
|
+
const help = getSlashCommandHelp();
|
|
610
|
+
|
|
611
|
+
expect(help).toContain("Usage:");
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// =============================================================================
|
|
616
|
+
// Command Registry Tests
|
|
617
|
+
// =============================================================================
|
|
618
|
+
|
|
619
|
+
describe("authSlashCommands Registry", () => {
|
|
620
|
+
it("contains credentials command", () => {
|
|
621
|
+
const cmd = authSlashCommands.find((c) => c.name === "credentials");
|
|
622
|
+
expect(cmd).toBeDefined();
|
|
623
|
+
expect(cmd?.aliases).toContain("creds");
|
|
624
|
+
expect(cmd?.aliases).toContain("keys");
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it("all commands have required properties", () => {
|
|
628
|
+
for (const cmd of authSlashCommands) {
|
|
629
|
+
expect(cmd.name).toBeDefined();
|
|
630
|
+
expect(cmd.description).toBeDefined();
|
|
631
|
+
expect(cmd.usage).toBeDefined();
|
|
632
|
+
expect(cmd.handler).toBeDefined();
|
|
633
|
+
expect(typeof cmd.handler).toBe("function");
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// =============================================================================
|
|
639
|
+
// Edge Cases and Error Handling
|
|
640
|
+
// =============================================================================
|
|
641
|
+
|
|
642
|
+
describe("Edge Cases", () => {
|
|
643
|
+
describe("Provider Name Handling", () => {
|
|
644
|
+
it("normalizes provider names to lowercase in credentials display", async () => {
|
|
645
|
+
const stores = createMockStores({
|
|
646
|
+
keychainCredentials: [createTestCredential("anthropic")],
|
|
647
|
+
});
|
|
648
|
+
const context = createMockContext({ stores });
|
|
649
|
+
|
|
650
|
+
const result = await executeSlashCommand("/credentials", context);
|
|
651
|
+
expect(result.message).toContain("anthropic");
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
describe("Multiple Credentials", () => {
|
|
656
|
+
it("handles multiple credentials from same provider", async () => {
|
|
657
|
+
const stores = createMockStores({
|
|
658
|
+
keychainCredentials: [createTestCredential("anthropic")],
|
|
659
|
+
fileCredentials: [createTestCredential("anthropic", "file")],
|
|
660
|
+
});
|
|
661
|
+
const context = createMockContext({ stores });
|
|
662
|
+
|
|
663
|
+
const result = await executeSlashCommand("/credentials", context);
|
|
664
|
+
// Both should be listed
|
|
665
|
+
expect(result.data?.credentials).toBeDefined();
|
|
666
|
+
const creds = result.data?.credentials as { provider: string }[];
|
|
667
|
+
const anthropicCreds = creds.filter((c) => c.provider === "anthropic");
|
|
668
|
+
expect(anthropicCreds).toHaveLength(2);
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// =============================================================================
|
|
674
|
+
// Integration Scenarios
|
|
675
|
+
// =============================================================================
|
|
676
|
+
|
|
677
|
+
describe("Integration Scenarios", () => {
|
|
678
|
+
it("displays credentials from multiple sources", async () => {
|
|
679
|
+
const stores = createMockStores({
|
|
680
|
+
keychainCredentials: [createTestCredential("anthropic")],
|
|
681
|
+
fileCredentials: [createTestCredential("openai", "file")],
|
|
682
|
+
envCredentials: [createTestCredential("google", "env")],
|
|
683
|
+
});
|
|
684
|
+
const context = createMockContext({ stores });
|
|
685
|
+
|
|
686
|
+
// Verify all are listed
|
|
687
|
+
const credsResult = await executeSlashCommand("/credentials", context);
|
|
688
|
+
expect(credsResult.message).toContain("anthropic");
|
|
689
|
+
expect(credsResult.message).toContain("openai");
|
|
690
|
+
expect(credsResult.message).toContain("google");
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it("filters credentials by provider", async () => {
|
|
694
|
+
const stores = createMockStores({
|
|
695
|
+
keychainCredentials: [createTestCredential("anthropic"), createTestCredential("openai")],
|
|
696
|
+
});
|
|
697
|
+
const context = createMockContext({ stores });
|
|
698
|
+
|
|
699
|
+
const result = await executeSlashCommand("/credentials anthropic", context);
|
|
700
|
+
expect(result.message).toContain("anthropic");
|
|
701
|
+
expect(result.message).not.toContain("openai");
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it("shows store availability status", async () => {
|
|
705
|
+
const stores = createMockStores({ keychainAvailable: false });
|
|
706
|
+
stores.find((s) => s.name === "keychain")?.setAvailable(false);
|
|
707
|
+
const context = createMockContext({ stores });
|
|
708
|
+
|
|
709
|
+
const result = await executeSlashCommand("/credentials", context);
|
|
710
|
+
expect(result.message).toContain("✗ keychain");
|
|
711
|
+
expect(result.message).toContain("✓ file");
|
|
712
|
+
});
|
|
713
|
+
});
|