@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,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Integration
|
|
3
|
+
*
|
|
4
|
+
* Wires MCP enterprise features (audit logging, server validation,
|
|
5
|
+
* tool blocking) to the TUI application when enterprise config is present.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/tui/enterprise-integration
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
// Audit logging
|
|
12
|
+
type AuditEvent,
|
|
13
|
+
type AuditEventType,
|
|
14
|
+
AuditLogger,
|
|
15
|
+
// Configuration
|
|
16
|
+
type FullEnterpriseConfig,
|
|
17
|
+
getAuditLogger,
|
|
18
|
+
getEnterpriseConfigPath,
|
|
19
|
+
getFullEnterpriseConfig,
|
|
20
|
+
initializeAuditLogger,
|
|
21
|
+
isEnterpriseMode,
|
|
22
|
+
loadFullEnterpriseConfig,
|
|
23
|
+
type ServerInfo,
|
|
24
|
+
type ServerValidationResult,
|
|
25
|
+
shutdownAuditLogger,
|
|
26
|
+
type ToolCallInfo,
|
|
27
|
+
type ToolValidationResult,
|
|
28
|
+
// Server validation
|
|
29
|
+
validateServer,
|
|
30
|
+
validateToolCall,
|
|
31
|
+
} from "@vellum/mcp";
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// Types
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
export interface EnterpriseIntegrationResult {
|
|
38
|
+
/** Whether enterprise mode is enabled */
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
/** Path to enterprise config file */
|
|
41
|
+
configPath: string;
|
|
42
|
+
/** Loaded enterprise configuration */
|
|
43
|
+
config: FullEnterpriseConfig | null;
|
|
44
|
+
/** Audit logger instance */
|
|
45
|
+
auditLogger: AuditLogger | null;
|
|
46
|
+
/** Error message if initialization failed */
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface EnterpriseHooks {
|
|
51
|
+
/** Hook called before tool execution for validation */
|
|
52
|
+
onBeforeToolCall: (tool: ToolCallInfo) => Promise<{ allowed: boolean; reason?: string }>;
|
|
53
|
+
/** Hook called after tool execution for audit logging */
|
|
54
|
+
onAfterToolCall: (tool: ToolCallInfo, result: unknown, durationMs: number) => Promise<void>;
|
|
55
|
+
/** Hook called to validate MCP server connection */
|
|
56
|
+
onServerConnect: (server: ServerInfo) => Promise<{ allowed: boolean; reason?: string }>;
|
|
57
|
+
/** Hook called on user authentication (if applicable) */
|
|
58
|
+
onUserAction: (action: string, metadata?: Record<string, unknown>) => Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// Enterprise Integration
|
|
63
|
+
// =============================================================================
|
|
64
|
+
|
|
65
|
+
let enterpriseConfig: FullEnterpriseConfig | null = null;
|
|
66
|
+
let enterpriseInitialized = false;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Load and initialize enterprise configuration.
|
|
70
|
+
*
|
|
71
|
+
* @returns Enterprise integration result
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* const enterprise = await initializeEnterprise();
|
|
76
|
+
* if (enterprise.enabled) {
|
|
77
|
+
* console.log("Enterprise mode active");
|
|
78
|
+
* console.log("Audit logging:", enterprise.config?.audit?.enabled);
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export async function initializeEnterprise(): Promise<EnterpriseIntegrationResult> {
|
|
83
|
+
const configPath = getEnterpriseConfigPath();
|
|
84
|
+
|
|
85
|
+
// Check if enterprise mode is available
|
|
86
|
+
if (!isEnterpriseMode()) {
|
|
87
|
+
return {
|
|
88
|
+
enabled: false,
|
|
89
|
+
configPath,
|
|
90
|
+
config: null,
|
|
91
|
+
auditLogger: null,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Load full enterprise configuration
|
|
97
|
+
const config = await loadFullEnterpriseConfig();
|
|
98
|
+
enterpriseConfig = config;
|
|
99
|
+
|
|
100
|
+
// Initialize audit logger if enabled
|
|
101
|
+
let auditLogger: AuditLogger | null = null;
|
|
102
|
+
if (config.audit?.enabled) {
|
|
103
|
+
auditLogger = await initializeAuditLogger({ config });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
enterpriseInitialized = true;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
enabled: true,
|
|
110
|
+
configPath,
|
|
111
|
+
config,
|
|
112
|
+
auditLogger,
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return {
|
|
116
|
+
enabled: false,
|
|
117
|
+
configPath,
|
|
118
|
+
config: null,
|
|
119
|
+
auditLogger: null,
|
|
120
|
+
error: error instanceof Error ? error.message : String(error),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get the current enterprise configuration.
|
|
127
|
+
*
|
|
128
|
+
* @returns Enterprise configuration or null if not in enterprise mode
|
|
129
|
+
*/
|
|
130
|
+
export function getEnterpriseConfig(): FullEnterpriseConfig | null {
|
|
131
|
+
return enterpriseConfig ?? getFullEnterpriseConfig();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if enterprise mode is currently active.
|
|
136
|
+
*
|
|
137
|
+
* @returns Whether enterprise mode is active
|
|
138
|
+
*/
|
|
139
|
+
export function isEnterpriseActive(): boolean {
|
|
140
|
+
return enterpriseInitialized && enterpriseConfig !== null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create enterprise hooks for tool and server validation.
|
|
145
|
+
*
|
|
146
|
+
* @returns Enterprise hooks object
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```typescript
|
|
150
|
+
* const hooks = createEnterpriseHooks();
|
|
151
|
+
*
|
|
152
|
+
* // Before tool execution
|
|
153
|
+
* const validation = await hooks.onBeforeToolCall({
|
|
154
|
+
* serverName: "my-server",
|
|
155
|
+
* toolName: "bash",
|
|
156
|
+
* arguments: { command: "ls -la" },
|
|
157
|
+
* });
|
|
158
|
+
* if (!validation.allowed) {
|
|
159
|
+
* console.error("Tool blocked:", validation.reason);
|
|
160
|
+
* }
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export function createEnterpriseHooks(): EnterpriseHooks {
|
|
164
|
+
return {
|
|
165
|
+
async onBeforeToolCall(tool: ToolCallInfo) {
|
|
166
|
+
if (!isEnterpriseActive() || !enterpriseConfig) {
|
|
167
|
+
return { allowed: true };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const result: ToolValidationResult = validateToolCall(tool, enterpriseConfig);
|
|
171
|
+
if (!result.allowed) {
|
|
172
|
+
// Audit log the blocked tool call
|
|
173
|
+
const logger = getAuditLogger();
|
|
174
|
+
if (logger) {
|
|
175
|
+
await logger.log({
|
|
176
|
+
eventType: "tool_blocked",
|
|
177
|
+
toolName: tool.toolName,
|
|
178
|
+
serverName: tool.serverName,
|
|
179
|
+
metadata: {
|
|
180
|
+
reason: result.reason ?? "Enterprise policy violation",
|
|
181
|
+
arguments: tool.arguments,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { allowed: result.allowed, reason: result.reason };
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
async onAfterToolCall(tool: ToolCallInfo, result: unknown, durationMs: number) {
|
|
191
|
+
if (!isEnterpriseActive()) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const logger = getAuditLogger();
|
|
196
|
+
if (logger) {
|
|
197
|
+
await logger.log({
|
|
198
|
+
eventType: "tool_call",
|
|
199
|
+
toolName: tool.toolName,
|
|
200
|
+
serverName: tool.serverName,
|
|
201
|
+
metadata: {
|
|
202
|
+
durationMs,
|
|
203
|
+
arguments: tool.arguments,
|
|
204
|
+
resultSummary:
|
|
205
|
+
typeof result === "object" && result !== null
|
|
206
|
+
? Object.keys(result).join(", ")
|
|
207
|
+
: typeof result,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
async onServerConnect(server: ServerInfo) {
|
|
214
|
+
if (!isEnterpriseActive() || !enterpriseConfig) {
|
|
215
|
+
return { allowed: true };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const result: ServerValidationResult = validateServer(server, enterpriseConfig);
|
|
219
|
+
if (!result.allowed) {
|
|
220
|
+
// Audit log the blocked server connection
|
|
221
|
+
const logger = getAuditLogger();
|
|
222
|
+
if (logger) {
|
|
223
|
+
await logger.log({
|
|
224
|
+
eventType: "server_blocked",
|
|
225
|
+
serverName: server.name,
|
|
226
|
+
metadata: {
|
|
227
|
+
reason: result.reason ?? "Server not in allowlist",
|
|
228
|
+
url: server.url,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return { allowed: result.allowed, reason: result.reason };
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
async onUserAction(action: string, metadata?: Record<string, unknown>) {
|
|
238
|
+
if (!isEnterpriseActive()) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const logger = getAuditLogger();
|
|
243
|
+
if (logger) {
|
|
244
|
+
await logger.log({
|
|
245
|
+
eventType: "config_change",
|
|
246
|
+
metadata: {
|
|
247
|
+
action,
|
|
248
|
+
...metadata,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Shutdown enterprise integration and flush audit logs.
|
|
258
|
+
*/
|
|
259
|
+
export async function shutdownEnterprise(): Promise<void> {
|
|
260
|
+
if (enterpriseInitialized) {
|
|
261
|
+
await shutdownAuditLogger();
|
|
262
|
+
enterpriseConfig = null;
|
|
263
|
+
enterpriseInitialized = false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// =============================================================================
|
|
268
|
+
// Exports
|
|
269
|
+
// =============================================================================
|
|
270
|
+
|
|
271
|
+
export {
|
|
272
|
+
type AuditEvent,
|
|
273
|
+
type AuditEventType,
|
|
274
|
+
AuditLogger,
|
|
275
|
+
type FullEnterpriseConfig,
|
|
276
|
+
type ServerInfo,
|
|
277
|
+
isEnterpriseMode,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Note: ToolCallInfo is intentionally not re-exported here to avoid name collision
|
|
281
|
+
// with the ToolCallInfo type from ./context/MessagesContext.js
|
|
282
|
+
// Import it directly from @vellum/mcp if needed for enterprise validation.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useBacktrack Hook Tests (T058)
|
|
3
|
+
*
|
|
4
|
+
* Verifies core backtrack/branching behavior:
|
|
5
|
+
* - initializes with a main branch
|
|
6
|
+
* - push/undo/redo update currentState
|
|
7
|
+
* - createBranch creates and switches to a new branch
|
|
8
|
+
* - switchBranch switches branches and restores that branch's latest snapshot
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { render } from "ink-testing-library";
|
|
12
|
+
import type React from "react";
|
|
13
|
+
import { describe, expect, it, vi } from "vitest";
|
|
14
|
+
import {
|
|
15
|
+
type UseBacktrackOptions,
|
|
16
|
+
type UseBacktrackReturn,
|
|
17
|
+
useBacktrack,
|
|
18
|
+
} from "../useBacktrack.js";
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// Test Helper Component
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
type TestState = { value: number };
|
|
25
|
+
|
|
26
|
+
interface TestHarnessProps {
|
|
27
|
+
options: UseBacktrackOptions<TestState>;
|
|
28
|
+
onHookReturn: (hookReturn: UseBacktrackReturn<TestState>) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function TestHarness({ options, onHookReturn }: TestHarnessProps): React.ReactElement {
|
|
32
|
+
const hookReturn = useBacktrack(options);
|
|
33
|
+
onHookReturn(hookReturn);
|
|
34
|
+
return null as unknown as React.ReactElement;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderHook(options: UseBacktrackOptions<TestState>) {
|
|
38
|
+
let hookReturn: UseBacktrackReturn<TestState> | null = null;
|
|
39
|
+
|
|
40
|
+
const setHookReturn = (r: UseBacktrackReturn<TestState>) => {
|
|
41
|
+
hookReturn = r;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const { rerender, unmount } = render(
|
|
45
|
+
<TestHarness options={options} onHookReturn={setHookReturn} />
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
get current() {
|
|
50
|
+
if (!hookReturn) throw new Error("Hook not initialized");
|
|
51
|
+
return hookReturn;
|
|
52
|
+
},
|
|
53
|
+
rerender: (newOptions?: UseBacktrackOptions<TestState>) => {
|
|
54
|
+
rerender(<TestHarness options={newOptions ?? options} onHookReturn={setHookReturn} />);
|
|
55
|
+
},
|
|
56
|
+
unmount,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// Tests
|
|
62
|
+
// =============================================================================
|
|
63
|
+
|
|
64
|
+
describe("useBacktrack", () => {
|
|
65
|
+
it("initializes with main branch and initial state", () => {
|
|
66
|
+
const result = renderHook({ initialState: { value: 0 } });
|
|
67
|
+
|
|
68
|
+
expect(result.current.backtrackState.currentBranch).toBe("Main");
|
|
69
|
+
expect(result.current.backtrackState.historyLength).toBe(1);
|
|
70
|
+
expect(result.current.branches).toHaveLength(1);
|
|
71
|
+
expect(result.current.currentState).toEqual({ value: 0 });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("push/undo/redo update currentState", () => {
|
|
75
|
+
const onStateChange = vi.fn();
|
|
76
|
+
const result = renderHook({ initialState: { value: 0 }, onStateChange });
|
|
77
|
+
|
|
78
|
+
result.current.push({ value: 1 }, "v1");
|
|
79
|
+
result.rerender();
|
|
80
|
+
expect(result.current.currentState).toEqual({ value: 1 });
|
|
81
|
+
|
|
82
|
+
result.current.undo();
|
|
83
|
+
result.rerender();
|
|
84
|
+
expect(result.current.currentState).toEqual({ value: 0 });
|
|
85
|
+
|
|
86
|
+
result.current.redo();
|
|
87
|
+
result.rerender();
|
|
88
|
+
expect(result.current.currentState).toEqual({ value: 1 });
|
|
89
|
+
|
|
90
|
+
// Callback is best-effort and may be called multiple times; assert at least once.
|
|
91
|
+
expect(onStateChange).toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("createBranch creates and switches to the new branch", () => {
|
|
95
|
+
const result = renderHook({ initialState: { value: 0 } });
|
|
96
|
+
|
|
97
|
+
result.current.push({ value: 1 }, "v1");
|
|
98
|
+
result.rerender();
|
|
99
|
+
|
|
100
|
+
const branchId = result.current.createBranch("Alt");
|
|
101
|
+
result.rerender();
|
|
102
|
+
|
|
103
|
+
expect(typeof branchId).toBe("string");
|
|
104
|
+
expect(result.current.backtrackState.currentBranch).toBe("Alt");
|
|
105
|
+
expect(result.current.branches.length).toBe(2);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("switchBranch switches branch and restores that branch's latest snapshot", () => {
|
|
109
|
+
const result = renderHook({ initialState: { value: 0 } });
|
|
110
|
+
|
|
111
|
+
// Main branch: 0 -> 1
|
|
112
|
+
result.current.push({ value: 1 }, "main-v1");
|
|
113
|
+
result.rerender();
|
|
114
|
+
|
|
115
|
+
// Create Alt branch from current point and diverge
|
|
116
|
+
const altId = result.current.createBranch("Alt");
|
|
117
|
+
result.rerender();
|
|
118
|
+
expect(result.current.backtrackState.currentBranch).toBe("Alt");
|
|
119
|
+
|
|
120
|
+
result.current.push({ value: 2 }, "alt-v2");
|
|
121
|
+
result.rerender();
|
|
122
|
+
expect(result.current.currentState).toEqual({ value: 2 });
|
|
123
|
+
|
|
124
|
+
// Switch back to main
|
|
125
|
+
result.current.switchBranch("main");
|
|
126
|
+
result.rerender();
|
|
127
|
+
|
|
128
|
+
expect(result.current.backtrackState.currentBranch).toBe("Main");
|
|
129
|
+
expect(result.current.currentState).toEqual({ value: 1 });
|
|
130
|
+
|
|
131
|
+
// Switch to Alt again
|
|
132
|
+
result.current.switchBranch(altId);
|
|
133
|
+
result.rerender();
|
|
134
|
+
|
|
135
|
+
expect(result.current.backtrackState.currentBranch).toBe("Alt");
|
|
136
|
+
expect(result.current.currentState).toEqual({ value: 2 });
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useBracketedPaste Hook Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the useBracketedPaste hook which manages bracketed paste mode lifecycle.
|
|
5
|
+
*
|
|
6
|
+
* @module @vellum/cli
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { render } from "ink-testing-library";
|
|
10
|
+
import type React from "react";
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
12
|
+
import { type UseBracketedPasteOptions, useBracketedPaste } from "../useBracketedPaste.js";
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Mocks
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
const originalWrite = process.stdout.write;
|
|
19
|
+
let writtenData: string[] = [];
|
|
20
|
+
|
|
21
|
+
// Track process event listeners
|
|
22
|
+
const processListeners: Map<string, Set<(...args: unknown[]) => void>> = new Map();
|
|
23
|
+
const originalOn = process.on.bind(process);
|
|
24
|
+
const originalOff = process.off.bind(process);
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
writtenData = [];
|
|
28
|
+
processListeners.clear();
|
|
29
|
+
|
|
30
|
+
// Mock stdout.write
|
|
31
|
+
process.stdout.write = vi.fn((data: string | Uint8Array) => {
|
|
32
|
+
writtenData.push(typeof data === "string" ? data : data.toString());
|
|
33
|
+
return true;
|
|
34
|
+
}) as typeof process.stdout.write;
|
|
35
|
+
|
|
36
|
+
// Mock process event listeners
|
|
37
|
+
process.on = vi.fn((event: string, listener: (...args: unknown[]) => void) => {
|
|
38
|
+
if (!processListeners.has(event)) {
|
|
39
|
+
processListeners.set(event, new Set());
|
|
40
|
+
}
|
|
41
|
+
processListeners.get(event)?.add(listener);
|
|
42
|
+
return process;
|
|
43
|
+
}) as typeof process.on;
|
|
44
|
+
|
|
45
|
+
process.off = vi.fn((event: string, listener: (...args: unknown[]) => void) => {
|
|
46
|
+
processListeners.get(event)?.delete(listener);
|
|
47
|
+
return process;
|
|
48
|
+
}) as typeof process.off;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
process.stdout.write = originalWrite;
|
|
53
|
+
process.on = originalOn;
|
|
54
|
+
process.off = originalOff;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// Test Helper Component
|
|
59
|
+
// =============================================================================
|
|
60
|
+
|
|
61
|
+
interface TestHarnessProps {
|
|
62
|
+
options?: UseBracketedPasteOptions;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function TestHarness({ options }: TestHarnessProps): React.ReactElement {
|
|
66
|
+
useBracketedPaste(options);
|
|
67
|
+
return null as unknown as React.ReactElement;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Render the hook and return controls.
|
|
72
|
+
*/
|
|
73
|
+
function renderHook(options?: UseBracketedPasteOptions) {
|
|
74
|
+
const { rerender, unmount } = render(<TestHarness options={options} />);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
rerender: (newOptions?: UseBracketedPasteOptions) => {
|
|
78
|
+
rerender(<TestHarness options={newOptions} />);
|
|
79
|
+
},
|
|
80
|
+
unmount,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// Tests
|
|
86
|
+
// =============================================================================
|
|
87
|
+
|
|
88
|
+
describe("useBracketedPaste", () => {
|
|
89
|
+
describe("enable on mount", () => {
|
|
90
|
+
it("enables bracketed paste mode on mount by default", () => {
|
|
91
|
+
renderHook();
|
|
92
|
+
|
|
93
|
+
expect(writtenData).toContain("\x1b[?2004h");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("enables bracketed paste mode when enabled=true", () => {
|
|
97
|
+
renderHook({ enabled: true });
|
|
98
|
+
|
|
99
|
+
expect(writtenData).toContain("\x1b[?2004h");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("does not enable bracketed paste mode when enabled=false", () => {
|
|
103
|
+
renderHook({ enabled: false });
|
|
104
|
+
|
|
105
|
+
expect(writtenData).not.toContain("\x1b[?2004h");
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("disable on unmount", () => {
|
|
110
|
+
it("disables bracketed paste mode on unmount", () => {
|
|
111
|
+
const { unmount } = renderHook();
|
|
112
|
+
writtenData = []; // Clear mount writes
|
|
113
|
+
|
|
114
|
+
unmount();
|
|
115
|
+
|
|
116
|
+
expect(writtenData).toContain("\x1b[?2004l");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("does not disable if never enabled", () => {
|
|
120
|
+
const { unmount } = renderHook({ enabled: false });
|
|
121
|
+
writtenData = [];
|
|
122
|
+
|
|
123
|
+
unmount();
|
|
124
|
+
|
|
125
|
+
expect(writtenData).not.toContain("\x1b[?2004l");
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("process event handlers", () => {
|
|
130
|
+
it("registers SIGINT handler on mount", () => {
|
|
131
|
+
renderHook();
|
|
132
|
+
|
|
133
|
+
expect(processListeners.has("SIGINT")).toBe(true);
|
|
134
|
+
expect(processListeners.get("SIGINT")?.size).toBeGreaterThan(0);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("registers SIGTERM handler on mount", () => {
|
|
138
|
+
renderHook();
|
|
139
|
+
|
|
140
|
+
expect(processListeners.has("SIGTERM")).toBe(true);
|
|
141
|
+
expect(processListeners.get("SIGTERM")?.size).toBeGreaterThan(0);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("registers beforeExit handler on mount", () => {
|
|
145
|
+
renderHook();
|
|
146
|
+
|
|
147
|
+
expect(processListeners.has("beforeExit")).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("registers exit handler on mount", () => {
|
|
151
|
+
renderHook();
|
|
152
|
+
|
|
153
|
+
expect(processListeners.has("exit")).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("removes event handlers on unmount", () => {
|
|
157
|
+
const { unmount } = renderHook();
|
|
158
|
+
|
|
159
|
+
// Get initial listener counts
|
|
160
|
+
const sigintBefore = processListeners.get("SIGINT")?.size ?? 0;
|
|
161
|
+
const sigtermBefore = processListeners.get("SIGTERM")?.size ?? 0;
|
|
162
|
+
|
|
163
|
+
unmount();
|
|
164
|
+
|
|
165
|
+
// Listeners should be removed
|
|
166
|
+
const sigintAfter = processListeners.get("SIGINT")?.size ?? 0;
|
|
167
|
+
const sigtermAfter = processListeners.get("SIGTERM")?.size ?? 0;
|
|
168
|
+
|
|
169
|
+
expect(sigintAfter).toBeLessThan(sigintBefore);
|
|
170
|
+
expect(sigtermAfter).toBeLessThan(sigtermBefore);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("does not register handlers when disabled", () => {
|
|
174
|
+
renderHook({ enabled: false });
|
|
175
|
+
|
|
176
|
+
// No handlers should be registered (or fewer than when enabled)
|
|
177
|
+
const sigintCount = processListeners.get("SIGINT")?.size ?? 0;
|
|
178
|
+
expect(sigintCount).toBe(0);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("enabled toggle", () => {
|
|
183
|
+
it("enables bracketed paste when enabled changes from false to true", () => {
|
|
184
|
+
const { rerender } = renderHook({ enabled: false });
|
|
185
|
+
expect(writtenData).not.toContain("\x1b[?2004h");
|
|
186
|
+
|
|
187
|
+
writtenData = [];
|
|
188
|
+
rerender({ enabled: true });
|
|
189
|
+
|
|
190
|
+
expect(writtenData).toContain("\x1b[?2004h");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("disables bracketed paste when enabled changes from true to false", () => {
|
|
194
|
+
const { rerender } = renderHook({ enabled: true });
|
|
195
|
+
expect(writtenData).toContain("\x1b[?2004h");
|
|
196
|
+
|
|
197
|
+
writtenData = [];
|
|
198
|
+
rerender({ enabled: false });
|
|
199
|
+
|
|
200
|
+
expect(writtenData).toContain("\x1b[?2004l");
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe("signal handlers cleanup behavior", () => {
|
|
205
|
+
it("signal handler calls disable sequence", () => {
|
|
206
|
+
renderHook();
|
|
207
|
+
|
|
208
|
+
// Get the SIGINT handler
|
|
209
|
+
const sigintHandlers = processListeners.get("SIGINT");
|
|
210
|
+
expect(sigintHandlers).toBeDefined();
|
|
211
|
+
expect(sigintHandlers?.size).toBeGreaterThan(0);
|
|
212
|
+
|
|
213
|
+
// Simulate SIGINT
|
|
214
|
+
writtenData = [];
|
|
215
|
+
const handler = sigintHandlers ? Array.from(sigintHandlers)[0] : undefined;
|
|
216
|
+
handler?.();
|
|
217
|
+
|
|
218
|
+
// Should have written disable sequence
|
|
219
|
+
expect(writtenData).toContain("\x1b[?2004l");
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|