@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,626 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Commands Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for auth migration including:
|
|
5
|
+
* - /login returns interactive prompt for API key
|
|
6
|
+
* - /login --store keychain uses specified store
|
|
7
|
+
* - /logout without --force returns confirmation prompt
|
|
8
|
+
* - /logout --force returns success
|
|
9
|
+
* - /credentials lists available stores
|
|
10
|
+
*
|
|
11
|
+
* @module cli/commands/__tests__/auth
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, expect, it, vi } from "vitest";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
fromSlashCommandResult,
|
|
18
|
+
type LegacySlashCommandResult,
|
|
19
|
+
toSlashCommandResult,
|
|
20
|
+
wrapLegacyHandler,
|
|
21
|
+
} from "../adapters.js";
|
|
22
|
+
import { authCommand, credentialsCommand } from "../auth.js";
|
|
23
|
+
import type {
|
|
24
|
+
CommandContext,
|
|
25
|
+
CommandError,
|
|
26
|
+
CommandInteractive,
|
|
27
|
+
CommandResult,
|
|
28
|
+
CommandSuccess,
|
|
29
|
+
ParsedArgs,
|
|
30
|
+
} from "../types.js";
|
|
31
|
+
import { extractCommandName, isSlashCommand, maskValue, parseCommandInput } from "../utils.js";
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// Test Fixtures
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Mock context options for testing
|
|
39
|
+
*/
|
|
40
|
+
interface MockContextOptions {
|
|
41
|
+
parsedArgs?: Partial<ParsedArgs>;
|
|
42
|
+
session?: Partial<CommandContext["session"]>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create a mock CommandContext for testing
|
|
47
|
+
*/
|
|
48
|
+
function createMockContext(options: MockContextOptions = {}): CommandContext {
|
|
49
|
+
const { parsedArgs = {}, session = {} } = options;
|
|
50
|
+
return {
|
|
51
|
+
session: {
|
|
52
|
+
id: session.id ?? "test-session",
|
|
53
|
+
provider: session.provider ?? "anthropic",
|
|
54
|
+
cwd: session.cwd ?? "/test",
|
|
55
|
+
},
|
|
56
|
+
credentials: {
|
|
57
|
+
resolve: vi.fn(),
|
|
58
|
+
store: vi.fn().mockResolvedValue({
|
|
59
|
+
ok: true,
|
|
60
|
+
value: { source: "keychain", provider: "anthropic" },
|
|
61
|
+
}),
|
|
62
|
+
delete: vi.fn().mockResolvedValue({ ok: true, value: 1 }),
|
|
63
|
+
list: vi.fn().mockResolvedValue({
|
|
64
|
+
ok: true,
|
|
65
|
+
value: [
|
|
66
|
+
{ provider: "anthropic", source: "keychain", type: "api_key", maskedHint: "sk-1...xyz" },
|
|
67
|
+
],
|
|
68
|
+
}),
|
|
69
|
+
exists: vi.fn().mockResolvedValue({ ok: true, value: false }),
|
|
70
|
+
getStoreAvailability: vi.fn().mockResolvedValue({
|
|
71
|
+
env: true,
|
|
72
|
+
keychain: true,
|
|
73
|
+
"encrypted-file": false,
|
|
74
|
+
}),
|
|
75
|
+
} as unknown as CommandContext["credentials"],
|
|
76
|
+
toolRegistry: {
|
|
77
|
+
get: vi.fn(),
|
|
78
|
+
list: vi.fn(),
|
|
79
|
+
} as unknown as CommandContext["toolRegistry"],
|
|
80
|
+
parsedArgs: {
|
|
81
|
+
command: parsedArgs.command ?? "test",
|
|
82
|
+
positional: parsedArgs.positional ?? [],
|
|
83
|
+
named: parsedArgs.named ?? {},
|
|
84
|
+
raw: parsedArgs.raw ?? "/test",
|
|
85
|
+
},
|
|
86
|
+
emit: vi.fn(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// =============================================================================
|
|
91
|
+
// T032: toSlashCommandResult Tests
|
|
92
|
+
// =============================================================================
|
|
93
|
+
|
|
94
|
+
describe("toSlashCommandResult", () => {
|
|
95
|
+
it("should convert success result to legacy format", () => {
|
|
96
|
+
const result: CommandSuccess = {
|
|
97
|
+
kind: "success",
|
|
98
|
+
message: "Operation completed",
|
|
99
|
+
data: { foo: "bar" },
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const legacy = toSlashCommandResult(result);
|
|
103
|
+
|
|
104
|
+
expect(legacy.success).toBe(true);
|
|
105
|
+
expect(legacy.message).toBe("Operation completed");
|
|
106
|
+
expect(legacy.data).toEqual({ foo: "bar" });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should convert error result to legacy format", () => {
|
|
110
|
+
const result: CommandError = {
|
|
111
|
+
kind: "error",
|
|
112
|
+
code: "INVALID_ARGUMENT",
|
|
113
|
+
message: "Invalid input",
|
|
114
|
+
suggestions: ["Try again"],
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const legacy = toSlashCommandResult(result);
|
|
118
|
+
|
|
119
|
+
expect(legacy.success).toBe(false);
|
|
120
|
+
expect(legacy.message).toBe("Invalid input");
|
|
121
|
+
expect(legacy.data?.code).toBe("INVALID_ARGUMENT");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should convert interactive result to legacy promptForInput", () => {
|
|
125
|
+
const handler = vi.fn().mockResolvedValue({ kind: "success" });
|
|
126
|
+
const result: CommandInteractive = {
|
|
127
|
+
kind: "interactive",
|
|
128
|
+
prompt: {
|
|
129
|
+
inputType: "password",
|
|
130
|
+
message: "Enter API key",
|
|
131
|
+
placeholder: "sk-...",
|
|
132
|
+
provider: "anthropic",
|
|
133
|
+
handler,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const legacy = toSlashCommandResult(result);
|
|
138
|
+
|
|
139
|
+
expect(legacy.success).toBe(true);
|
|
140
|
+
expect(legacy.message).toBe("Enter API key");
|
|
141
|
+
expect(legacy.promptForInput).toBeDefined();
|
|
142
|
+
expect(legacy.promptForInput?.type).toBe("api_key");
|
|
143
|
+
expect(legacy.promptForInput?.provider).toBe("anthropic");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should handle confirm interactive without promptForInput", () => {
|
|
147
|
+
const result: CommandInteractive = {
|
|
148
|
+
kind: "interactive",
|
|
149
|
+
prompt: {
|
|
150
|
+
inputType: "confirm",
|
|
151
|
+
message: "Are you sure?",
|
|
152
|
+
handler: vi.fn(),
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const legacy = toSlashCommandResult(result);
|
|
157
|
+
|
|
158
|
+
expect(legacy.success).toBe(true);
|
|
159
|
+
expect(legacy.message).toBe("Are you sure?");
|
|
160
|
+
expect(legacy.promptForInput).toBeUndefined();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// =============================================================================
|
|
165
|
+
// T033: fromSlashCommandResult Tests
|
|
166
|
+
// =============================================================================
|
|
167
|
+
|
|
168
|
+
describe("fromSlashCommandResult", () => {
|
|
169
|
+
it("should convert legacy success to new format", () => {
|
|
170
|
+
const legacy: LegacySlashCommandResult = {
|
|
171
|
+
success: true,
|
|
172
|
+
message: "Done",
|
|
173
|
+
data: { key: "value" },
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const result = fromSlashCommandResult(legacy);
|
|
177
|
+
|
|
178
|
+
expect(result.kind).toBe("success");
|
|
179
|
+
expect((result as CommandSuccess).message).toBe("Done");
|
|
180
|
+
expect((result as CommandSuccess).data).toEqual({ key: "value" });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should convert legacy error to new format", () => {
|
|
184
|
+
const legacy: LegacySlashCommandResult = {
|
|
185
|
+
success: false,
|
|
186
|
+
message: "Failed",
|
|
187
|
+
data: { code: "INVALID_ARGUMENT" },
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const result = fromSlashCommandResult(legacy);
|
|
191
|
+
|
|
192
|
+
expect(result.kind).toBe("error");
|
|
193
|
+
expect((result as CommandError).code).toBe("INVALID_ARGUMENT");
|
|
194
|
+
expect((result as CommandError).message).toBe("Failed");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should convert legacy promptForInput to interactive", async () => {
|
|
198
|
+
const onSubmit = vi.fn().mockResolvedValue({ success: true, message: "Saved" });
|
|
199
|
+
const legacy: LegacySlashCommandResult = {
|
|
200
|
+
success: true,
|
|
201
|
+
message: "Enter API key",
|
|
202
|
+
promptForInput: {
|
|
203
|
+
type: "api_key",
|
|
204
|
+
provider: "anthropic",
|
|
205
|
+
placeholder: "sk-...",
|
|
206
|
+
onSubmit,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const result = fromSlashCommandResult(legacy);
|
|
211
|
+
|
|
212
|
+
expect(result.kind).toBe("interactive");
|
|
213
|
+
const interactive = result as CommandInteractive;
|
|
214
|
+
expect(interactive.prompt.inputType).toBe("password");
|
|
215
|
+
expect(interactive.prompt.message).toBe("Enter API key");
|
|
216
|
+
expect(interactive.prompt.provider).toBe("anthropic");
|
|
217
|
+
|
|
218
|
+
// Test handler calls through to onSubmit
|
|
219
|
+
const handlerResult = await interactive.prompt.handler("test-key");
|
|
220
|
+
expect(onSubmit).toHaveBeenCalledWith("test-key");
|
|
221
|
+
expect(handlerResult.kind).toBe("success");
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// =============================================================================
|
|
226
|
+
// T034A: wrapLegacyHandler Tests
|
|
227
|
+
// =============================================================================
|
|
228
|
+
|
|
229
|
+
describe("wrapLegacyHandler", () => {
|
|
230
|
+
it("should wrap legacy handler and convert result", async () => {
|
|
231
|
+
const legacyHandler = vi.fn().mockResolvedValue({
|
|
232
|
+
success: true,
|
|
233
|
+
message: "Legacy success",
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const wrappedHandler = wrapLegacyHandler(legacyHandler);
|
|
237
|
+
const ctx = createMockContext({
|
|
238
|
+
parsedArgs: { positional: ["anthropic"] },
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const result = await wrappedHandler(ctx);
|
|
242
|
+
|
|
243
|
+
expect(legacyHandler).toHaveBeenCalledWith(
|
|
244
|
+
["anthropic"],
|
|
245
|
+
expect.objectContaining({
|
|
246
|
+
currentProvider: "anthropic",
|
|
247
|
+
})
|
|
248
|
+
);
|
|
249
|
+
expect(result.kind).toBe("success");
|
|
250
|
+
expect((result as CommandSuccess).message).toBe("Legacy success");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should pass credentials from context to legacy handler", async () => {
|
|
254
|
+
const legacyHandler = vi.fn().mockResolvedValue({
|
|
255
|
+
success: true,
|
|
256
|
+
message: "Done",
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const wrappedHandler = wrapLegacyHandler(legacyHandler);
|
|
260
|
+
const ctx = createMockContext();
|
|
261
|
+
|
|
262
|
+
await wrappedHandler(ctx);
|
|
263
|
+
|
|
264
|
+
expect(legacyHandler).toHaveBeenCalledWith(
|
|
265
|
+
[],
|
|
266
|
+
expect.objectContaining({
|
|
267
|
+
credentialManager: ctx.credentials,
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// =============================================================================
|
|
274
|
+
// T034B: isSlashCommand Tests
|
|
275
|
+
// =============================================================================
|
|
276
|
+
|
|
277
|
+
describe("isSlashCommand", () => {
|
|
278
|
+
it("should return true for valid slash commands", () => {
|
|
279
|
+
expect(isSlashCommand("/help")).toBe(true);
|
|
280
|
+
expect(isSlashCommand("/login")).toBe(true);
|
|
281
|
+
expect(isSlashCommand(" /login")).toBe(true);
|
|
282
|
+
expect(isSlashCommand("/a")).toBe(true);
|
|
283
|
+
expect(isSlashCommand("//comment")).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("should return false for empty or whitespace", () => {
|
|
287
|
+
expect(isSlashCommand("")).toBe(false);
|
|
288
|
+
expect(isSlashCommand(" ")).toBe(false);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("should return false for just slash", () => {
|
|
292
|
+
expect(isSlashCommand("/")).toBe(false);
|
|
293
|
+
expect(isSlashCommand(" / ")).toBe(false);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("should return false for non-slash input", () => {
|
|
297
|
+
expect(isSlashCommand("hello")).toBe(false);
|
|
298
|
+
expect(isSlashCommand("hello /world")).toBe(false);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("should handle edge cases", () => {
|
|
302
|
+
expect(isSlashCommand(null as unknown as string)).toBe(false);
|
|
303
|
+
expect(isSlashCommand(undefined as unknown as string)).toBe(false);
|
|
304
|
+
expect(isSlashCommand("/ space")).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe("extractCommandName", () => {
|
|
309
|
+
it("should extract command name from slash command", () => {
|
|
310
|
+
expect(extractCommandName("/help")).toBe("help");
|
|
311
|
+
expect(extractCommandName("/login anthropic")).toBe("login");
|
|
312
|
+
expect(extractCommandName(" /logout ")).toBe("logout");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("should return null for non-slash input", () => {
|
|
316
|
+
expect(extractCommandName("hello")).toBeNull();
|
|
317
|
+
expect(extractCommandName("")).toBeNull();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe("parseCommandInput", () => {
|
|
322
|
+
it("should parse command and args", () => {
|
|
323
|
+
const result = parseCommandInput("/login anthropic --store keychain");
|
|
324
|
+
|
|
325
|
+
expect(result).toEqual({
|
|
326
|
+
command: "login",
|
|
327
|
+
args: ["anthropic", "--store", "keychain"],
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("should return null for invalid input", () => {
|
|
332
|
+
expect(parseCommandInput("not a command")).toBeNull();
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe("maskValue", () => {
|
|
337
|
+
it("should mask long values", () => {
|
|
338
|
+
expect(maskValue("sk-1234567890abcdef")).toBe("sk-1...cdef");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("should fully mask short values", () => {
|
|
342
|
+
expect(maskValue("short")).toBe("*****");
|
|
343
|
+
expect(maskValue("ab")).toBe("****");
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// =============================================================================
|
|
348
|
+
// T034: Enhanced Credentials Command Tests
|
|
349
|
+
// =============================================================================
|
|
350
|
+
|
|
351
|
+
describe("credentialsCommand", () => {
|
|
352
|
+
it("should have correct metadata", () => {
|
|
353
|
+
expect(credentialsCommand.name).toBe("credentials");
|
|
354
|
+
expect(credentialsCommand.kind).toBe("builtin");
|
|
355
|
+
expect(credentialsCommand.category).toBe("auth");
|
|
356
|
+
expect(credentialsCommand.aliases).toContain("creds");
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("should list available stores", async () => {
|
|
360
|
+
const ctx = createMockContext({
|
|
361
|
+
parsedArgs: { positional: [], named: {} },
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const result = await credentialsCommand.execute(ctx);
|
|
365
|
+
|
|
366
|
+
expect(result.kind).toBe("success");
|
|
367
|
+
const success = result as CommandSuccess;
|
|
368
|
+
expect(success.message).toContain("Storage Backends");
|
|
369
|
+
expect(success.message).toContain("keychain");
|
|
370
|
+
expect(success.data).toHaveProperty("availability");
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("should list credentials", async () => {
|
|
374
|
+
const ctx = createMockContext({
|
|
375
|
+
parsedArgs: { positional: [], named: {} },
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const result = await credentialsCommand.execute(ctx);
|
|
379
|
+
|
|
380
|
+
expect(result.kind).toBe("success");
|
|
381
|
+
const success = result as CommandSuccess;
|
|
382
|
+
expect(success.message).toContain("anthropic");
|
|
383
|
+
expect(success.data).toHaveProperty("credentials");
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("should filter by provider when specified", async () => {
|
|
387
|
+
const ctx = createMockContext({
|
|
388
|
+
parsedArgs: { positional: ["anthropic"], named: {} },
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
await credentialsCommand.execute(ctx);
|
|
392
|
+
|
|
393
|
+
expect(ctx.credentials.list).toHaveBeenCalledWith("anthropic");
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it("should show message when no credentials found", async () => {
|
|
397
|
+
const ctx = createMockContext({
|
|
398
|
+
parsedArgs: { positional: [], named: {} },
|
|
399
|
+
});
|
|
400
|
+
(ctx.credentials.list as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
401
|
+
ok: true,
|
|
402
|
+
value: [],
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const result = await credentialsCommand.execute(ctx);
|
|
406
|
+
|
|
407
|
+
expect(result.kind).toBe("success");
|
|
408
|
+
const success = result as CommandSuccess;
|
|
409
|
+
expect(success.message).toContain("No credentials stored");
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// =============================================================================
|
|
414
|
+
// T035: Unified Auth Command Tests
|
|
415
|
+
// =============================================================================
|
|
416
|
+
|
|
417
|
+
describe("authCommand", () => {
|
|
418
|
+
it("should have correct metadata", () => {
|
|
419
|
+
expect(authCommand.name).toBe("auth");
|
|
420
|
+
expect(authCommand.kind).toBe("builtin");
|
|
421
|
+
expect(authCommand.category).toBe("auth");
|
|
422
|
+
expect(authCommand.subcommands).toHaveLength(3);
|
|
423
|
+
expect(authCommand.subcommands?.map((s) => s.name)).toEqual(["status", "set", "clear"]);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe("/auth status", () => {
|
|
427
|
+
it("should show authentication status with /auth", async () => {
|
|
428
|
+
const ctx = createMockContext({
|
|
429
|
+
parsedArgs: { positional: [], named: {} },
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const result = await authCommand.execute(ctx);
|
|
433
|
+
|
|
434
|
+
expect(result.kind).toBe("success");
|
|
435
|
+
const success = result as CommandSuccess;
|
|
436
|
+
expect(success.message).toContain("Authentication Status");
|
|
437
|
+
expect(success.message).toContain("Storage Backends");
|
|
438
|
+
expect(success.message).toContain("Configured Providers");
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("should show authentication status with /auth status", async () => {
|
|
442
|
+
const ctx = createMockContext({
|
|
443
|
+
parsedArgs: { positional: ["status"], named: {} },
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const result = await authCommand.execute(ctx);
|
|
447
|
+
|
|
448
|
+
expect(result.kind).toBe("success");
|
|
449
|
+
const success = result as CommandSuccess;
|
|
450
|
+
expect(success.message).toContain("Authentication Status");
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("should filter by provider with /auth status <provider>", async () => {
|
|
454
|
+
const ctx = createMockContext({
|
|
455
|
+
parsedArgs: { positional: ["status", "anthropic"], named: {} },
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const result = await authCommand.execute(ctx);
|
|
459
|
+
|
|
460
|
+
expect(result.kind).toBe("success");
|
|
461
|
+
expect(ctx.credentials.list).toHaveBeenCalledWith("anthropic");
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
describe("/auth set", () => {
|
|
466
|
+
it("should return interactive prompt for API key", async () => {
|
|
467
|
+
const ctx = createMockContext({
|
|
468
|
+
parsedArgs: { positional: ["set", "anthropic"], named: {} },
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
const result = await authCommand.execute(ctx);
|
|
472
|
+
|
|
473
|
+
expect(result.kind).toBe("interactive");
|
|
474
|
+
const interactive = result as CommandInteractive;
|
|
475
|
+
expect(interactive.prompt.inputType).toBe("password");
|
|
476
|
+
expect(interactive.prompt.message.toLowerCase()).toContain("anthropic");
|
|
477
|
+
expect(interactive.prompt.provider).toBe("anthropic");
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it("should return error when no provider specified", async () => {
|
|
481
|
+
const ctx = createMockContext({
|
|
482
|
+
parsedArgs: { positional: ["set"], named: {} },
|
|
483
|
+
session: { id: "test", provider: "", cwd: "/" },
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
const result = await authCommand.execute(ctx);
|
|
487
|
+
|
|
488
|
+
expect(result.kind).toBe("error");
|
|
489
|
+
expect((result as CommandError).code).toBe("MISSING_ARGUMENT");
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("should use session provider when not specified", async () => {
|
|
493
|
+
const ctx = createMockContext({
|
|
494
|
+
parsedArgs: { positional: ["set"], named: {} },
|
|
495
|
+
session: { provider: "openai" },
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
const result = await authCommand.execute(ctx);
|
|
499
|
+
|
|
500
|
+
expect(result.kind).toBe("interactive");
|
|
501
|
+
const interactive = result as CommandInteractive;
|
|
502
|
+
expect(interactive.prompt.provider).toBe("openai");
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
describe("/auth clear", () => {
|
|
507
|
+
it("should return confirmation prompt without --force", async () => {
|
|
508
|
+
const ctx = createMockContext({
|
|
509
|
+
parsedArgs: { positional: ["clear", "anthropic"], named: { force: false } },
|
|
510
|
+
});
|
|
511
|
+
(ctx.credentials.exists as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
512
|
+
ok: true,
|
|
513
|
+
value: true,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
const result = await authCommand.execute(ctx);
|
|
517
|
+
|
|
518
|
+
expect(result.kind).toBe("interactive");
|
|
519
|
+
const interactive = result as CommandInteractive;
|
|
520
|
+
expect(interactive.prompt.inputType).toBe("confirm");
|
|
521
|
+
expect(interactive.prompt.message).toContain("Are you sure");
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it("should delete immediately with --force", async () => {
|
|
525
|
+
const ctx = createMockContext({
|
|
526
|
+
parsedArgs: { positional: ["clear", "anthropic"], named: { force: true } },
|
|
527
|
+
});
|
|
528
|
+
(ctx.credentials.exists as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
529
|
+
ok: true,
|
|
530
|
+
value: true,
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const result = await authCommand.execute(ctx);
|
|
534
|
+
|
|
535
|
+
expect(result.kind).toBe("success");
|
|
536
|
+
expect(ctx.credentials.delete).toHaveBeenCalledWith("anthropic");
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("should return error when no provider specified", async () => {
|
|
540
|
+
const ctx = createMockContext({
|
|
541
|
+
parsedArgs: { positional: ["clear"], named: {} },
|
|
542
|
+
session: { id: "test", provider: "", cwd: "/" },
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
const result = await authCommand.execute(ctx);
|
|
546
|
+
|
|
547
|
+
expect(result.kind).toBe("error");
|
|
548
|
+
expect((result as CommandError).code).toBe("MISSING_ARGUMENT");
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it("should return error when credential not found", async () => {
|
|
552
|
+
const ctx = createMockContext({
|
|
553
|
+
parsedArgs: { positional: ["clear", "unknown"], named: { force: true } },
|
|
554
|
+
});
|
|
555
|
+
(ctx.credentials.exists as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
556
|
+
ok: true,
|
|
557
|
+
value: false,
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
const result = await authCommand.execute(ctx);
|
|
561
|
+
|
|
562
|
+
expect(result.kind).toBe("error");
|
|
563
|
+
expect((result as CommandError).code).toBe("CREDENTIAL_NOT_FOUND");
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
describe("subcommand aliases", () => {
|
|
568
|
+
it("should handle 'add' as alias for 'set'", async () => {
|
|
569
|
+
const ctx = createMockContext({
|
|
570
|
+
parsedArgs: { positional: ["add", "anthropic"], named: {} },
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
const result = await authCommand.execute(ctx);
|
|
574
|
+
|
|
575
|
+
expect(result.kind).toBe("interactive");
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it("should handle 'remove' as alias for 'clear'", async () => {
|
|
579
|
+
const ctx = createMockContext({
|
|
580
|
+
parsedArgs: { positional: ["remove", "anthropic"], named: { force: true } },
|
|
581
|
+
});
|
|
582
|
+
(ctx.credentials.exists as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
583
|
+
ok: true,
|
|
584
|
+
value: true,
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const result = await authCommand.execute(ctx);
|
|
588
|
+
|
|
589
|
+
expect(result.kind).toBe("success");
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// =============================================================================
|
|
595
|
+
// Integration: Adapter Round-Trip Tests
|
|
596
|
+
// =============================================================================
|
|
597
|
+
|
|
598
|
+
describe("Adapter round-trip", () => {
|
|
599
|
+
it("should preserve success data through conversion", () => {
|
|
600
|
+
const original: CommandResult = {
|
|
601
|
+
kind: "success",
|
|
602
|
+
message: "Test message",
|
|
603
|
+
data: { key: "value" },
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const legacy = toSlashCommandResult(original);
|
|
607
|
+
const converted = fromSlashCommandResult(legacy);
|
|
608
|
+
|
|
609
|
+
expect(converted.kind).toBe("success");
|
|
610
|
+
expect((converted as CommandSuccess).message).toBe("Test message");
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it("should preserve error code through conversion", () => {
|
|
614
|
+
const original: CommandResult = {
|
|
615
|
+
kind: "error",
|
|
616
|
+
code: "AUTHENTICATION_FAILED",
|
|
617
|
+
message: "Auth error",
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const legacy = toSlashCommandResult(original);
|
|
621
|
+
const converted = fromSlashCommandResult(legacy);
|
|
622
|
+
|
|
623
|
+
expect(converted.kind).toBe("error");
|
|
624
|
+
expect((converted as CommandError).code).toBe("AUTHENTICATION_FAILED");
|
|
625
|
+
});
|
|
626
|
+
});
|