@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,883 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Slash Commands
|
|
3
|
+
*
|
|
4
|
+
* Provides slash commands for credential management in TUI:
|
|
5
|
+
* - /auth - Unified authentication command (set, delete, list)
|
|
6
|
+
* - /credentials - Show credential status
|
|
7
|
+
*
|
|
8
|
+
* Supports both legacy interface (SlashCommandResult) and
|
|
9
|
+
* enhanced interface (SlashCommand from types.ts) for backward compatibility.
|
|
10
|
+
*
|
|
11
|
+
* @module cli/commands/auth
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
CredentialManager,
|
|
16
|
+
type CredentialRef,
|
|
17
|
+
EncryptedFileStore,
|
|
18
|
+
EnvCredentialStore,
|
|
19
|
+
KeychainStore,
|
|
20
|
+
} from "@vellum/core";
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
CommandContext,
|
|
24
|
+
CommandResult,
|
|
25
|
+
SlashCommand as EnhancedSlashCommand,
|
|
26
|
+
} from "./types.js";
|
|
27
|
+
import { error, interactive, success } from "./types.js";
|
|
28
|
+
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Types
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Result of a slash command execution
|
|
35
|
+
* @deprecated Use CommandResult from ./types.ts for new commands
|
|
36
|
+
*/
|
|
37
|
+
export interface SlashCommandResult {
|
|
38
|
+
/** Whether the command succeeded */
|
|
39
|
+
success: boolean;
|
|
40
|
+
/** Message to display to user */
|
|
41
|
+
message: string;
|
|
42
|
+
/** Additional data (for programmatic use) */
|
|
43
|
+
data?: Record<string, unknown>;
|
|
44
|
+
/** Whether to prompt for input */
|
|
45
|
+
promptForInput?: {
|
|
46
|
+
type: "api_key";
|
|
47
|
+
provider: string;
|
|
48
|
+
placeholder: string;
|
|
49
|
+
onSubmit: (value: string) => Promise<SlashCommandResult>;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Slash command handler function type
|
|
55
|
+
*/
|
|
56
|
+
export type SlashCommandHandler = (
|
|
57
|
+
args: string[],
|
|
58
|
+
context: SlashCommandContext
|
|
59
|
+
) => Promise<SlashCommandResult>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Context provided to slash command handlers
|
|
63
|
+
*/
|
|
64
|
+
export interface SlashCommandContext {
|
|
65
|
+
/** Current provider (from chat session) */
|
|
66
|
+
currentProvider?: string;
|
|
67
|
+
/** Credential manager instance */
|
|
68
|
+
credentialManager: CredentialManager;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Registered slash command
|
|
73
|
+
*/
|
|
74
|
+
export interface SlashCommand {
|
|
75
|
+
/** Command name (without slash) */
|
|
76
|
+
name: string;
|
|
77
|
+
/** Command aliases */
|
|
78
|
+
aliases?: string[];
|
|
79
|
+
/** Command description */
|
|
80
|
+
description: string;
|
|
81
|
+
/** Usage pattern */
|
|
82
|
+
usage: string;
|
|
83
|
+
/** Handler function */
|
|
84
|
+
handler: SlashCommandHandler;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// =============================================================================
|
|
88
|
+
// Credential Manager Factory
|
|
89
|
+
// =============================================================================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a credential manager instance with default stores
|
|
93
|
+
*/
|
|
94
|
+
export async function createCredentialManager(): Promise<CredentialManager> {
|
|
95
|
+
const stores = [
|
|
96
|
+
new EnvCredentialStore(),
|
|
97
|
+
new KeychainStore(),
|
|
98
|
+
new EncryptedFileStore({
|
|
99
|
+
filePath: `${process.env.HOME ?? process.env.USERPROFILE}/.vellum/credentials.enc`,
|
|
100
|
+
password: process.env.VELLUM_CREDENTIAL_PASSWORD ?? "vellum-default-key",
|
|
101
|
+
}),
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
return new CredentialManager(stores, {
|
|
105
|
+
preferredWriteStore: "keychain",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// =============================================================================
|
|
110
|
+
// Slash Command Handlers
|
|
111
|
+
// =============================================================================
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* /credentials command handler
|
|
115
|
+
*
|
|
116
|
+
* Shows credential status for all or a specific provider.
|
|
117
|
+
* Usage: /credentials [provider]
|
|
118
|
+
*/
|
|
119
|
+
async function handleCredentials(
|
|
120
|
+
args: string[],
|
|
121
|
+
context: SlashCommandContext
|
|
122
|
+
): Promise<SlashCommandResult> {
|
|
123
|
+
const filterProvider = args[0]?.toLowerCase();
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// Get store availability
|
|
127
|
+
const availability = await context.credentialManager.getStoreAvailability();
|
|
128
|
+
|
|
129
|
+
// List credentials
|
|
130
|
+
const listResult = await context.credentialManager.list(filterProvider);
|
|
131
|
+
|
|
132
|
+
if (!listResult.ok) {
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
message: `❌ Failed to list credentials: ${listResult.error.message}`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const credentials = listResult.value;
|
|
140
|
+
|
|
141
|
+
// Build message
|
|
142
|
+
const lines: string[] = [];
|
|
143
|
+
lines.push("🔐 Credential Status");
|
|
144
|
+
lines.push("━".repeat(40));
|
|
145
|
+
|
|
146
|
+
// Store availability
|
|
147
|
+
lines.push("\n📦 Storage Backends:");
|
|
148
|
+
for (const [store, available] of Object.entries(availability)) {
|
|
149
|
+
lines.push(` ${available ? "✓" : "✗"} ${store}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Credentials
|
|
153
|
+
lines.push("\n🔑 Credentials:");
|
|
154
|
+
if (credentials.length === 0) {
|
|
155
|
+
if (filterProvider) {
|
|
156
|
+
lines.push(` No credential found for ${filterProvider}`);
|
|
157
|
+
} else {
|
|
158
|
+
lines.push(" No credentials stored");
|
|
159
|
+
lines.push(" Use /auth set <provider> to add one");
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
for (const cred of credentials) {
|
|
163
|
+
const maskedValue = cred.maskedHint ?? "***";
|
|
164
|
+
lines.push(` • ${cred.provider} (${cred.source}): ${maskedValue} [${cred.type}]`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
lines.push("━".repeat(40));
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
success: true,
|
|
172
|
+
message: lines.join("\n"),
|
|
173
|
+
data: {
|
|
174
|
+
availability,
|
|
175
|
+
credentials: credentials.map((c: CredentialRef) => ({
|
|
176
|
+
provider: c.provider,
|
|
177
|
+
source: c.source,
|
|
178
|
+
type: c.type,
|
|
179
|
+
maskedHint: c.maskedHint,
|
|
180
|
+
})),
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
} catch (err) {
|
|
184
|
+
return {
|
|
185
|
+
success: false,
|
|
186
|
+
message: `❌ Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// =============================================================================
|
|
192
|
+
// Command Registry
|
|
193
|
+
// =============================================================================
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* All registered slash commands for authentication
|
|
197
|
+
* @deprecated Use enhancedAuthCommands for new code
|
|
198
|
+
*/
|
|
199
|
+
export const authSlashCommands: SlashCommand[] = [
|
|
200
|
+
{
|
|
201
|
+
name: "credentials",
|
|
202
|
+
aliases: ["creds", "keys"],
|
|
203
|
+
description: "Show credential status",
|
|
204
|
+
usage: "/credentials [provider]",
|
|
205
|
+
handler: handleCredentials,
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
// =============================================================================
|
|
210
|
+
// T034: Enhanced Auth Commands (New Interface)
|
|
211
|
+
// =============================================================================
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Credentials command with enhanced interface
|
|
215
|
+
*
|
|
216
|
+
* Displays available stores and credential status.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```
|
|
220
|
+
* /credentials # Show all credentials
|
|
221
|
+
* /credentials anthropic # Show specific provider
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
export const credentialsCommand: EnhancedSlashCommand = {
|
|
225
|
+
name: "credentials",
|
|
226
|
+
description: "Show credential status and available stores",
|
|
227
|
+
kind: "builtin",
|
|
228
|
+
category: "auth",
|
|
229
|
+
aliases: ["creds", "keys"],
|
|
230
|
+
positionalArgs: [
|
|
231
|
+
{
|
|
232
|
+
name: "provider",
|
|
233
|
+
type: "string",
|
|
234
|
+
description: "Filter by provider name",
|
|
235
|
+
required: false,
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
namedArgs: [],
|
|
239
|
+
examples: ["/credentials", "/credentials anthropic", "/creds"],
|
|
240
|
+
execute: async (ctx: CommandContext): Promise<CommandResult> => {
|
|
241
|
+
const filterProvider = ctx.parsedArgs.positional[0] as string | undefined;
|
|
242
|
+
const normalizedFilter = filterProvider?.toLowerCase();
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
// Get store availability
|
|
246
|
+
const availability = await ctx.credentials.getStoreAvailability();
|
|
247
|
+
|
|
248
|
+
// List credentials
|
|
249
|
+
const listResult = await ctx.credentials.list(normalizedFilter);
|
|
250
|
+
|
|
251
|
+
if (!listResult.ok) {
|
|
252
|
+
return error(
|
|
253
|
+
"INTERNAL_ERROR",
|
|
254
|
+
`❌ Failed to list credentials: ${listResult.error.message}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const credentials = listResult.value;
|
|
259
|
+
|
|
260
|
+
// Build message
|
|
261
|
+
const lines: string[] = [];
|
|
262
|
+
lines.push("🔐 Credential Status");
|
|
263
|
+
lines.push("━".repeat(40));
|
|
264
|
+
|
|
265
|
+
// Store availability
|
|
266
|
+
lines.push("\n📦 Storage Backends:");
|
|
267
|
+
for (const [store, available] of Object.entries(availability)) {
|
|
268
|
+
lines.push(` ${available ? "✓" : "✗"} ${store}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Credentials
|
|
272
|
+
lines.push("\n🔑 Credentials:");
|
|
273
|
+
if (credentials.length === 0) {
|
|
274
|
+
if (normalizedFilter) {
|
|
275
|
+
lines.push(` No credential found for ${normalizedFilter}`);
|
|
276
|
+
} else {
|
|
277
|
+
lines.push(" No credentials stored");
|
|
278
|
+
lines.push(" Use /auth set <provider> to add one");
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
for (const cred of credentials) {
|
|
282
|
+
const maskedValue = cred.maskedHint ?? "***";
|
|
283
|
+
lines.push(` • ${cred.provider} (${cred.source}): ${maskedValue} [${cred.type}]`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
lines.push("━".repeat(40));
|
|
288
|
+
|
|
289
|
+
return success(lines.join("\n"), {
|
|
290
|
+
availability,
|
|
291
|
+
credentials: credentials.map((c: CredentialRef) => ({
|
|
292
|
+
provider: c.provider,
|
|
293
|
+
source: c.source,
|
|
294
|
+
type: c.type,
|
|
295
|
+
maskedHint: c.maskedHint,
|
|
296
|
+
})),
|
|
297
|
+
});
|
|
298
|
+
} catch (err) {
|
|
299
|
+
return error(
|
|
300
|
+
"INTERNAL_ERROR",
|
|
301
|
+
`❌ Error: ${err instanceof Error ? err.message : String(err)}`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// =============================================================================
|
|
308
|
+
// T035: Unified Auth Command
|
|
309
|
+
// =============================================================================
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Unified auth command with subcommands
|
|
313
|
+
*
|
|
314
|
+
* Provides a single entry point for all authentication operations:
|
|
315
|
+
* - /auth (or /auth status) - Show authentication status
|
|
316
|
+
* - /auth set [provider] - Add or update API credential
|
|
317
|
+
* - /auth clear [provider] - Remove credential
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* ```
|
|
321
|
+
* /auth # Show current auth status
|
|
322
|
+
* /auth status # Same as /auth
|
|
323
|
+
* /auth set anthropic # Add/update credential for anthropic
|
|
324
|
+
* /auth clear openai # Remove credential for openai
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
export const authCommand: EnhancedSlashCommand = {
|
|
328
|
+
name: "auth",
|
|
329
|
+
description: "Manage API credentials (status, set, clear)",
|
|
330
|
+
kind: "builtin",
|
|
331
|
+
category: "auth",
|
|
332
|
+
aliases: [],
|
|
333
|
+
subcommands: [
|
|
334
|
+
{
|
|
335
|
+
name: "status",
|
|
336
|
+
description: "Show current authentication status (default)",
|
|
337
|
+
aliases: ["st", "list"],
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "set",
|
|
341
|
+
description: "Add or update API credential for a provider",
|
|
342
|
+
aliases: ["add", "login"],
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: "clear",
|
|
346
|
+
description: "Remove credential for a provider",
|
|
347
|
+
aliases: ["remove", "delete", "logout"],
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
positionalArgs: [
|
|
351
|
+
{
|
|
352
|
+
name: "subcommand",
|
|
353
|
+
type: "string",
|
|
354
|
+
description: "Subcommand: status, set, clear (default: status)",
|
|
355
|
+
required: false,
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: "provider",
|
|
359
|
+
type: "string",
|
|
360
|
+
description: "Provider name (e.g., anthropic, openai)",
|
|
361
|
+
required: false,
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
namedArgs: [
|
|
365
|
+
{
|
|
366
|
+
name: "store",
|
|
367
|
+
shorthand: "s",
|
|
368
|
+
type: "string",
|
|
369
|
+
description: "Credential store to use (keychain, encrypted-file, env)",
|
|
370
|
+
required: false,
|
|
371
|
+
default: "keychain",
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "force",
|
|
375
|
+
shorthand: "f",
|
|
376
|
+
type: "boolean",
|
|
377
|
+
description: "Skip confirmation prompt for clear",
|
|
378
|
+
required: false,
|
|
379
|
+
default: false,
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
examples: [
|
|
383
|
+
"/auth",
|
|
384
|
+
"/auth status",
|
|
385
|
+
"/auth set anthropic",
|
|
386
|
+
"/auth set openai --store keychain",
|
|
387
|
+
"/auth clear anthropic",
|
|
388
|
+
"/auth clear openai --force",
|
|
389
|
+
],
|
|
390
|
+
execute: async (ctx: CommandContext): Promise<CommandResult> => {
|
|
391
|
+
const args = ctx.parsedArgs.positional;
|
|
392
|
+
const subcommand = (args[0] as string | undefined)?.toLowerCase() ?? "status";
|
|
393
|
+
const provider =
|
|
394
|
+
(args[1] as string | undefined) ??
|
|
395
|
+
// If subcommand is a provider name (not a known subcommand), treat it as provider
|
|
396
|
+
(![
|
|
397
|
+
"status",
|
|
398
|
+
"st",
|
|
399
|
+
"list",
|
|
400
|
+
"set",
|
|
401
|
+
"add",
|
|
402
|
+
"login",
|
|
403
|
+
"clear",
|
|
404
|
+
"remove",
|
|
405
|
+
"delete",
|
|
406
|
+
"logout",
|
|
407
|
+
].includes(subcommand)
|
|
408
|
+
? subcommand
|
|
409
|
+
: undefined);
|
|
410
|
+
const store = ctx.parsedArgs.named.store as string | undefined;
|
|
411
|
+
const force = ctx.parsedArgs.named.force === true;
|
|
412
|
+
|
|
413
|
+
// Determine actual subcommand (handle case where provider was passed as first arg)
|
|
414
|
+
const actualSubcommand = [
|
|
415
|
+
"status",
|
|
416
|
+
"st",
|
|
417
|
+
"list",
|
|
418
|
+
"set",
|
|
419
|
+
"add",
|
|
420
|
+
"login",
|
|
421
|
+
"clear",
|
|
422
|
+
"remove",
|
|
423
|
+
"delete",
|
|
424
|
+
"logout",
|
|
425
|
+
].includes(subcommand)
|
|
426
|
+
? subcommand
|
|
427
|
+
: "status";
|
|
428
|
+
|
|
429
|
+
// Route to appropriate handler
|
|
430
|
+
switch (actualSubcommand) {
|
|
431
|
+
case "set":
|
|
432
|
+
case "add":
|
|
433
|
+
case "login":
|
|
434
|
+
return authSet(ctx.credentials, provider ?? ctx.session.provider, store);
|
|
435
|
+
|
|
436
|
+
case "clear":
|
|
437
|
+
case "remove":
|
|
438
|
+
case "delete":
|
|
439
|
+
case "logout":
|
|
440
|
+
return authClear(ctx.credentials, provider ?? ctx.session.provider, force);
|
|
441
|
+
default:
|
|
442
|
+
return authStatus(ctx.credentials, provider);
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* /auth status - Show authentication status
|
|
449
|
+
*/
|
|
450
|
+
async function authStatus(
|
|
451
|
+
credentials: CredentialManager,
|
|
452
|
+
filterProvider?: string
|
|
453
|
+
): Promise<CommandResult> {
|
|
454
|
+
const normalizedFilter = filterProvider?.toLowerCase();
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
// Get store availability
|
|
458
|
+
const availability = await credentials.getStoreAvailability();
|
|
459
|
+
|
|
460
|
+
// List credentials
|
|
461
|
+
const listResult = await credentials.list(normalizedFilter);
|
|
462
|
+
|
|
463
|
+
if (!listResult.ok) {
|
|
464
|
+
return error("INTERNAL_ERROR", `❌ Failed to list credentials: ${listResult.error.message}`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const credentialsList = listResult.value;
|
|
468
|
+
|
|
469
|
+
// Build message
|
|
470
|
+
const lines: string[] = [];
|
|
471
|
+
lines.push("🔐 Authentication Status");
|
|
472
|
+
lines.push("━".repeat(40));
|
|
473
|
+
|
|
474
|
+
// Store availability
|
|
475
|
+
lines.push("\n📦 Storage Backends:");
|
|
476
|
+
for (const [store, available] of Object.entries(availability)) {
|
|
477
|
+
lines.push(` ${available ? "✓" : "✗"} ${store}`);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Credentials
|
|
481
|
+
lines.push("\n🔑 Configured Providers:");
|
|
482
|
+
if (credentialsList.length === 0) {
|
|
483
|
+
if (normalizedFilter) {
|
|
484
|
+
lines.push(` No credential found for ${normalizedFilter}`);
|
|
485
|
+
} else {
|
|
486
|
+
lines.push(" No credentials stored");
|
|
487
|
+
lines.push(" Use /auth set <provider> to add one");
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
for (const cred of credentialsList) {
|
|
491
|
+
const maskedValue = cred.maskedHint ?? "***";
|
|
492
|
+
lines.push(` • ${cred.provider} (${cred.source}): ${maskedValue} [${cred.type}]`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
lines.push(`\n${"━".repeat(40)}`);
|
|
497
|
+
lines.push("💡 Commands: /auth set <provider> | /auth clear <provider>");
|
|
498
|
+
|
|
499
|
+
return success(lines.join("\n"), {
|
|
500
|
+
availability,
|
|
501
|
+
credentials: credentialsList.map((c: CredentialRef) => ({
|
|
502
|
+
provider: c.provider,
|
|
503
|
+
source: c.source,
|
|
504
|
+
type: c.type,
|
|
505
|
+
maskedHint: c.maskedHint,
|
|
506
|
+
})),
|
|
507
|
+
});
|
|
508
|
+
} catch (err) {
|
|
509
|
+
return error("INTERNAL_ERROR", `❌ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Provider-specific API key format hints
|
|
515
|
+
*/
|
|
516
|
+
const PROVIDER_KEY_HINTS: Record<
|
|
517
|
+
string,
|
|
518
|
+
{ formatHint: string; helpText: string; documentationUrl?: string }
|
|
519
|
+
> = {
|
|
520
|
+
anthropic: {
|
|
521
|
+
formatHint: "sk-ant-api03-...",
|
|
522
|
+
helpText: "Your Anthropic key starts with sk-ant-",
|
|
523
|
+
documentationUrl: "https://console.anthropic.com/settings/keys",
|
|
524
|
+
},
|
|
525
|
+
openai: {
|
|
526
|
+
formatHint: "sk-proj-...",
|
|
527
|
+
helpText: "Your OpenAI key starts with sk-proj- or sk-",
|
|
528
|
+
documentationUrl: "https://platform.openai.com/api-keys",
|
|
529
|
+
},
|
|
530
|
+
google: {
|
|
531
|
+
formatHint: "AIza...",
|
|
532
|
+
helpText: "Your Google AI key starts with AIza",
|
|
533
|
+
documentationUrl: "https://aistudio.google.com/apikey",
|
|
534
|
+
},
|
|
535
|
+
gemini: {
|
|
536
|
+
formatHint: "AIza...",
|
|
537
|
+
helpText: "Your Gemini key starts with AIza",
|
|
538
|
+
documentationUrl: "https://aistudio.google.com/apikey",
|
|
539
|
+
},
|
|
540
|
+
bedrock: {
|
|
541
|
+
formatHint: "AKIA...",
|
|
542
|
+
helpText: "Enter your AWS access key ID",
|
|
543
|
+
documentationUrl: "https://docs.aws.amazon.com/bedrock/latest/userguide/setting-up.html",
|
|
544
|
+
},
|
|
545
|
+
cohere: {
|
|
546
|
+
formatHint: "...",
|
|
547
|
+
helpText: "Your Cohere API key",
|
|
548
|
+
documentationUrl: "https://dashboard.cohere.com/api-keys",
|
|
549
|
+
},
|
|
550
|
+
mistral: {
|
|
551
|
+
formatHint: "...",
|
|
552
|
+
helpText: "Your Mistral API key",
|
|
553
|
+
documentationUrl: "https://console.mistral.ai/api-keys",
|
|
554
|
+
},
|
|
555
|
+
groq: {
|
|
556
|
+
formatHint: "gsk_...",
|
|
557
|
+
helpText: "Your Groq key starts with gsk_",
|
|
558
|
+
documentationUrl: "https://console.groq.com/keys",
|
|
559
|
+
},
|
|
560
|
+
fireworks: {
|
|
561
|
+
formatHint: "fw_...",
|
|
562
|
+
helpText: "Your Fireworks key starts with fw_",
|
|
563
|
+
documentationUrl: "https://fireworks.ai/account/api-keys",
|
|
564
|
+
},
|
|
565
|
+
together: {
|
|
566
|
+
formatHint: "...",
|
|
567
|
+
helpText: "Your Together AI API key",
|
|
568
|
+
documentationUrl: "https://api.together.xyz/settings/api-keys",
|
|
569
|
+
},
|
|
570
|
+
perplexity: {
|
|
571
|
+
formatHint: "pplx-...",
|
|
572
|
+
helpText: "Your Perplexity key starts with pplx-",
|
|
573
|
+
documentationUrl: "https://www.perplexity.ai/settings/api",
|
|
574
|
+
},
|
|
575
|
+
deepseek: {
|
|
576
|
+
formatHint: "sk-...",
|
|
577
|
+
helpText: "Your DeepSeek API key",
|
|
578
|
+
documentationUrl: "https://platform.deepseek.com/api_keys",
|
|
579
|
+
},
|
|
580
|
+
openrouter: {
|
|
581
|
+
formatHint: "sk-or-...",
|
|
582
|
+
helpText: "Your OpenRouter key starts with sk-or-",
|
|
583
|
+
documentationUrl: "https://openrouter.ai/keys",
|
|
584
|
+
},
|
|
585
|
+
ollama: {
|
|
586
|
+
formatHint: "(optional)",
|
|
587
|
+
helpText: "Ollama typically runs locally without an API key",
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Get provider-specific hints, with fallback for unknown providers
|
|
593
|
+
*/
|
|
594
|
+
function getProviderHints(provider: string): {
|
|
595
|
+
formatHint: string;
|
|
596
|
+
helpText: string;
|
|
597
|
+
documentationUrl?: string;
|
|
598
|
+
} {
|
|
599
|
+
return (
|
|
600
|
+
PROVIDER_KEY_HINTS[provider] ?? {
|
|
601
|
+
formatHint: "...",
|
|
602
|
+
helpText: `Enter your ${provider} API key`,
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* /auth set - Add or update API credential
|
|
609
|
+
*/
|
|
610
|
+
async function authSet(
|
|
611
|
+
credentials: CredentialManager,
|
|
612
|
+
provider: string | undefined,
|
|
613
|
+
store?: string
|
|
614
|
+
): Promise<CommandResult> {
|
|
615
|
+
if (!provider) {
|
|
616
|
+
return error(
|
|
617
|
+
"MISSING_ARGUMENT",
|
|
618
|
+
"❌ Provider required. Usage: /auth set <provider>\n Example: /auth set anthropic"
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const normalizedProvider = provider.toLowerCase();
|
|
623
|
+
|
|
624
|
+
// Check if credential already exists
|
|
625
|
+
const existsResult = await credentials.exists(normalizedProvider);
|
|
626
|
+
const alreadyExists = existsResult.ok && existsResult.value;
|
|
627
|
+
|
|
628
|
+
// Get provider-specific hints
|
|
629
|
+
const hints = getProviderHints(normalizedProvider);
|
|
630
|
+
|
|
631
|
+
// Capitalize provider name for display
|
|
632
|
+
const displayName = normalizedProvider.charAt(0).toUpperCase() + normalizedProvider.slice(1);
|
|
633
|
+
|
|
634
|
+
const title = alreadyExists
|
|
635
|
+
? `🔐 Update API Key for ${displayName}`
|
|
636
|
+
: `🔐 Set API Key for ${displayName}`;
|
|
637
|
+
|
|
638
|
+
const promptMessage = `${displayName} API Key:`;
|
|
639
|
+
|
|
640
|
+
return interactive({
|
|
641
|
+
inputType: "password",
|
|
642
|
+
message: promptMessage,
|
|
643
|
+
placeholder: hints.formatHint,
|
|
644
|
+
provider: normalizedProvider,
|
|
645
|
+
title,
|
|
646
|
+
helpText: hints.helpText,
|
|
647
|
+
formatHint: hints.formatHint,
|
|
648
|
+
documentationUrl: hints.documentationUrl,
|
|
649
|
+
handler: async (value: string): Promise<CommandResult> => {
|
|
650
|
+
if (!value.trim()) {
|
|
651
|
+
return error("INVALID_ARGUMENT", "❌ API key cannot be empty");
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
const result = await credentials.store(
|
|
656
|
+
{
|
|
657
|
+
provider: normalizedProvider,
|
|
658
|
+
type: "api_key",
|
|
659
|
+
value: value.trim(),
|
|
660
|
+
metadata: {
|
|
661
|
+
label: `${normalizedProvider} API Key`,
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
store as "keychain" | "file" | undefined
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
if (!result.ok) {
|
|
668
|
+
return error("INTERNAL_ERROR", `❌ Failed to save credential: ${result.error.message}`);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return success(`✅ Credential for ${normalizedProvider} saved to ${result.value.source}`, {
|
|
672
|
+
provider: normalizedProvider,
|
|
673
|
+
source: result.value.source,
|
|
674
|
+
});
|
|
675
|
+
} catch (err) {
|
|
676
|
+
return error(
|
|
677
|
+
"INTERNAL_ERROR",
|
|
678
|
+
`❌ Error: ${err instanceof Error ? err.message : String(err)}`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
onCancel: () => ({
|
|
683
|
+
kind: "error" as const,
|
|
684
|
+
code: "COMMAND_ABORTED" as const,
|
|
685
|
+
message: "Credential setup cancelled",
|
|
686
|
+
}),
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* /auth clear - Remove credential
|
|
692
|
+
*/
|
|
693
|
+
async function authClear(
|
|
694
|
+
credentials: CredentialManager,
|
|
695
|
+
provider: string | undefined,
|
|
696
|
+
force: boolean
|
|
697
|
+
): Promise<CommandResult> {
|
|
698
|
+
if (!provider) {
|
|
699
|
+
return error(
|
|
700
|
+
"MISSING_ARGUMENT",
|
|
701
|
+
"❌ Provider required. Usage: /auth clear <provider>\n Example: /auth clear anthropic"
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const normalizedProvider = provider.toLowerCase();
|
|
706
|
+
|
|
707
|
+
// Check if credential exists
|
|
708
|
+
const existsResult = await credentials.exists(normalizedProvider);
|
|
709
|
+
if (!existsResult.ok || !existsResult.value) {
|
|
710
|
+
return error("CREDENTIAL_NOT_FOUND", `⚠️ No credential found for ${normalizedProvider}`);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// If not forced, return confirmation prompt
|
|
714
|
+
if (!force) {
|
|
715
|
+
return interactive({
|
|
716
|
+
inputType: "confirm",
|
|
717
|
+
message: `Are you sure you want to remove credential for ${normalizedProvider}?`,
|
|
718
|
+
handler: async (value: string): Promise<CommandResult> => {
|
|
719
|
+
if (value.toLowerCase() !== "yes" && value.toLowerCase() !== "y") {
|
|
720
|
+
return error("COMMAND_ABORTED", "Credential removal cancelled");
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return performAuthClear(credentials, normalizedProvider);
|
|
724
|
+
},
|
|
725
|
+
onCancel: () => ({
|
|
726
|
+
kind: "error" as const,
|
|
727
|
+
code: "COMMAND_ABORTED" as const,
|
|
728
|
+
message: "Credential removal cancelled",
|
|
729
|
+
}),
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Forced clear - delete immediately
|
|
734
|
+
return performAuthClear(credentials, normalizedProvider);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Perform the actual credential clear operation
|
|
739
|
+
*/
|
|
740
|
+
async function performAuthClear(
|
|
741
|
+
credentials: CredentialManager,
|
|
742
|
+
provider: string
|
|
743
|
+
): Promise<CommandResult> {
|
|
744
|
+
try {
|
|
745
|
+
const result = await credentials.delete(provider);
|
|
746
|
+
|
|
747
|
+
if (!result.ok) {
|
|
748
|
+
return error("INTERNAL_ERROR", `❌ Failed to remove credential: ${result.error.message}`);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (result.value === 0) {
|
|
752
|
+
return error("CREDENTIAL_NOT_FOUND", `⚠️ No credential found for ${provider}`);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return success(`✅ Credential for ${provider} removed from ${result.value} store(s)`, {
|
|
756
|
+
provider,
|
|
757
|
+
deletedCount: result.value,
|
|
758
|
+
});
|
|
759
|
+
} catch (err) {
|
|
760
|
+
return error("INTERNAL_ERROR", `❌ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* All enhanced auth commands using the new SlashCommand interface
|
|
766
|
+
*/
|
|
767
|
+
export const enhancedAuthCommands: EnhancedSlashCommand[] = [authCommand, credentialsCommand];
|
|
768
|
+
|
|
769
|
+
// =============================================================================
|
|
770
|
+
// Command Dispatcher
|
|
771
|
+
// =============================================================================
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Check if input is a slash command
|
|
775
|
+
*/
|
|
776
|
+
export function isSlashCommand(input: string): boolean {
|
|
777
|
+
return input.trim().startsWith("/");
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Parse slash command input
|
|
782
|
+
*/
|
|
783
|
+
export function parseSlashCommand(input: string): { command: string; args: string[] } | null {
|
|
784
|
+
const trimmed = input.trim();
|
|
785
|
+
if (!trimmed.startsWith("/")) {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
790
|
+
const command = parts[0]?.toLowerCase() ?? "";
|
|
791
|
+
const args = parts.slice(1);
|
|
792
|
+
|
|
793
|
+
return { command, args };
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Find a slash command by name or alias
|
|
798
|
+
*/
|
|
799
|
+
export function findSlashCommand(name: string): SlashCommand | undefined {
|
|
800
|
+
const lowerName = name.toLowerCase();
|
|
801
|
+
|
|
802
|
+
return authSlashCommands.find(
|
|
803
|
+
(cmd) => cmd.name === lowerName || cmd.aliases?.includes(lowerName)
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Execute a slash command
|
|
809
|
+
*/
|
|
810
|
+
export async function executeSlashCommand(
|
|
811
|
+
input: string,
|
|
812
|
+
context: Partial<SlashCommandContext> = {}
|
|
813
|
+
): Promise<SlashCommandResult> {
|
|
814
|
+
const parsed = parseSlashCommand(input);
|
|
815
|
+
|
|
816
|
+
if (!parsed) {
|
|
817
|
+
return {
|
|
818
|
+
success: false,
|
|
819
|
+
message: "Invalid slash command format",
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const command = findSlashCommand(parsed.command);
|
|
824
|
+
|
|
825
|
+
if (!command) {
|
|
826
|
+
// Check if it's a help request
|
|
827
|
+
if (parsed.command === "help" && parsed.args[0]) {
|
|
828
|
+
const helpCmd = findSlashCommand(parsed.args[0]);
|
|
829
|
+
if (helpCmd) {
|
|
830
|
+
return {
|
|
831
|
+
success: true,
|
|
832
|
+
message: `📖 ${helpCmd.name}\n ${helpCmd.description}\n Usage: ${helpCmd.usage}`,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Unknown command - show available commands
|
|
838
|
+
const available = authSlashCommands.map((c) => `/${c.name}`).join(", ");
|
|
839
|
+
return {
|
|
840
|
+
success: false,
|
|
841
|
+
message: `❓ Unknown command: /${parsed.command}\n Available: ${available}`,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Create credential manager if not provided
|
|
846
|
+
const credentialManager = context.credentialManager ?? (await createCredentialManager());
|
|
847
|
+
|
|
848
|
+
const fullContext: SlashCommandContext = {
|
|
849
|
+
currentProvider: context.currentProvider,
|
|
850
|
+
credentialManager,
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
return command.handler(parsed.args, fullContext);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Get help for all slash commands
|
|
858
|
+
*/
|
|
859
|
+
export function getSlashCommandHelp(): string {
|
|
860
|
+
const lines: string[] = [];
|
|
861
|
+
lines.push("📖 Available Commands:");
|
|
862
|
+
lines.push("━".repeat(40));
|
|
863
|
+
|
|
864
|
+
for (const cmd of authSlashCommands) {
|
|
865
|
+
lines.push(`\n/${cmd.name}`);
|
|
866
|
+
if (cmd.aliases?.length) {
|
|
867
|
+
lines.push(` Aliases: ${cmd.aliases.map((a) => `/${a}`).join(", ")}`);
|
|
868
|
+
}
|
|
869
|
+
lines.push(` ${cmd.description}`);
|
|
870
|
+
lines.push(` Usage: ${cmd.usage}`);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
lines.push(`\n${"━".repeat(40)}`);
|
|
874
|
+
lines.push("Tip: Use /help <command> for detailed help");
|
|
875
|
+
|
|
876
|
+
return lines.join("\n");
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// =============================================================================
|
|
880
|
+
// Exports
|
|
881
|
+
// =============================================================================
|
|
882
|
+
|
|
883
|
+
export { handleCredentials };
|