@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,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Integration
|
|
3
|
+
*
|
|
4
|
+
* Wires the @vellum/sandbox package to shell tool execution,
|
|
5
|
+
* providing secure command execution with platform-specific sandboxing.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/tui/sandbox-integration
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
detectSandboxBackend,
|
|
12
|
+
type SandboxConfig,
|
|
13
|
+
type SandboxExecutionOptions,
|
|
14
|
+
SandboxExecutor,
|
|
15
|
+
type SandboxResult,
|
|
16
|
+
} from "@vellum/sandbox";
|
|
17
|
+
|
|
18
|
+
import { SANDBOX_DENIED_PATHS, SANDBOX_PERMISSIONS, SANDBOX_RESOURCES } from "./config/index.js";
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// Types
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
export interface SandboxIntegrationOptions {
|
|
25
|
+
/** Working directory for command execution */
|
|
26
|
+
workingDirectory?: string;
|
|
27
|
+
/** Allow network access in sandbox */
|
|
28
|
+
allowNetwork?: boolean;
|
|
29
|
+
/** Allow file system access */
|
|
30
|
+
allowFileSystem?: boolean;
|
|
31
|
+
/** Maximum execution time in milliseconds */
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
/** Maximum output size in bytes */
|
|
34
|
+
maxOutputBytes?: number;
|
|
35
|
+
/** Environment variables */
|
|
36
|
+
environment?: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SandboxedExecutionResult {
|
|
40
|
+
/** Whether execution succeeded */
|
|
41
|
+
success: boolean;
|
|
42
|
+
/** Exit code (null if terminated) */
|
|
43
|
+
exitCode: number | null;
|
|
44
|
+
/** Standard output */
|
|
45
|
+
stdout: string;
|
|
46
|
+
/** Standard error */
|
|
47
|
+
stderr: string;
|
|
48
|
+
/** Execution duration in milliseconds */
|
|
49
|
+
durationMs: number;
|
|
50
|
+
/** Whether the command was terminated */
|
|
51
|
+
terminated: boolean;
|
|
52
|
+
/** Reason for termination if terminated */
|
|
53
|
+
terminationReason?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// Sandbox Integration
|
|
58
|
+
// =============================================================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Global sandbox executor instance
|
|
62
|
+
*/
|
|
63
|
+
let sandboxExecutor: SandboxExecutor | null = null;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initialize the sandbox executor with the given options.
|
|
67
|
+
*
|
|
68
|
+
* @param options - Sandbox configuration options
|
|
69
|
+
* @returns The initialized sandbox executor
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const executor = initializeSandbox({
|
|
74
|
+
* workingDirectory: process.cwd(),
|
|
75
|
+
* allowNetwork: false,
|
|
76
|
+
* allowFileSystem: true,
|
|
77
|
+
* });
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function initializeSandbox(options: SandboxIntegrationOptions = {}): SandboxExecutor {
|
|
81
|
+
const {
|
|
82
|
+
workingDirectory = process.cwd(),
|
|
83
|
+
allowNetwork = SANDBOX_PERMISSIONS.ALLOW_NETWORK,
|
|
84
|
+
allowFileSystem = SANDBOX_PERMISSIONS.ALLOW_FILE_SYSTEM,
|
|
85
|
+
timeoutMs = SANDBOX_RESOURCES.TIMEOUT_MS,
|
|
86
|
+
maxOutputBytes = SANDBOX_RESOURCES.MAX_OUTPUT_BYTES,
|
|
87
|
+
environment = {},
|
|
88
|
+
} = options;
|
|
89
|
+
|
|
90
|
+
const config: SandboxConfig = {
|
|
91
|
+
id: `sandbox-${Date.now()}`,
|
|
92
|
+
strategy: "subprocess",
|
|
93
|
+
workingDir: workingDirectory,
|
|
94
|
+
environment,
|
|
95
|
+
enableAudit: SANDBOX_PERMISSIONS.ENABLE_AUDIT,
|
|
96
|
+
resources: {
|
|
97
|
+
cpuTimeMs: timeoutMs,
|
|
98
|
+
wallTimeMs: timeoutMs + SANDBOX_RESOURCES.WALL_TIME_BUFFER_MS,
|
|
99
|
+
memoryBytes: SANDBOX_RESOURCES.MEMORY_BYTES,
|
|
100
|
+
maxFileDescriptors: SANDBOX_RESOURCES.MAX_FILE_DESCRIPTORS,
|
|
101
|
+
maxProcesses: SANDBOX_RESOURCES.MAX_PROCESSES,
|
|
102
|
+
maxOutputBytes,
|
|
103
|
+
maxFileSizeBytes: SANDBOX_RESOURCES.MAX_FILE_SIZE_BYTES,
|
|
104
|
+
},
|
|
105
|
+
network: {
|
|
106
|
+
allowNetwork,
|
|
107
|
+
allowedHosts: [],
|
|
108
|
+
allowedPorts: [],
|
|
109
|
+
blockDns: !allowNetwork,
|
|
110
|
+
},
|
|
111
|
+
filesystem: {
|
|
112
|
+
rootDir: workingDirectory,
|
|
113
|
+
readOnlyPaths: [],
|
|
114
|
+
readWritePaths: allowFileSystem ? [workingDirectory] : [],
|
|
115
|
+
deniedPaths: [...SANDBOX_DENIED_PATHS],
|
|
116
|
+
useOverlay: SANDBOX_PERMISSIONS.USE_OVERLAY,
|
|
117
|
+
maxDiskUsageBytes: SANDBOX_RESOURCES.MAX_DISK_USAGE_BYTES,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const backend = detectSandboxBackend();
|
|
122
|
+
sandboxExecutor = new SandboxExecutor(config, backend);
|
|
123
|
+
|
|
124
|
+
return sandboxExecutor;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get the current sandbox executor, initializing if needed.
|
|
129
|
+
*
|
|
130
|
+
* @returns The sandbox executor instance
|
|
131
|
+
*/
|
|
132
|
+
export function getSandboxExecutor(): SandboxExecutor {
|
|
133
|
+
if (!sandboxExecutor) {
|
|
134
|
+
sandboxExecutor = initializeSandbox();
|
|
135
|
+
}
|
|
136
|
+
return sandboxExecutor;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Execute a command in the sandbox.
|
|
141
|
+
*
|
|
142
|
+
* @param command - The command to execute
|
|
143
|
+
* @param args - Command arguments
|
|
144
|
+
* @param options - Execution options
|
|
145
|
+
* @returns The execution result
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* const result = await executeSandboxed("ls", ["-la"], {
|
|
150
|
+
* cwd: "/project",
|
|
151
|
+
* timeoutMs: 5000,
|
|
152
|
+
* });
|
|
153
|
+
* if (result.success) {
|
|
154
|
+
* console.log(result.stdout);
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export async function executeSandboxed(
|
|
159
|
+
command: string,
|
|
160
|
+
args: string[] = [],
|
|
161
|
+
options: SandboxExecutionOptions = {}
|
|
162
|
+
): Promise<SandboxedExecutionResult> {
|
|
163
|
+
const executor = getSandboxExecutor();
|
|
164
|
+
|
|
165
|
+
const result: SandboxResult = await executor.execute(command, args, options);
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
success: result.exitCode === 0 && !result.terminated,
|
|
169
|
+
exitCode: result.exitCode,
|
|
170
|
+
stdout: result.stdout,
|
|
171
|
+
stderr: result.stderr,
|
|
172
|
+
durationMs: result.durationMs,
|
|
173
|
+
terminated: result.terminated,
|
|
174
|
+
terminationReason: result.terminationReason,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Execute a shell command string in the sandbox.
|
|
180
|
+
*
|
|
181
|
+
* @param shellCommand - The shell command string to execute
|
|
182
|
+
* @param options - Execution options
|
|
183
|
+
* @returns The execution result
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* const result = await executeShellCommand("echo $HOME && pwd", {
|
|
188
|
+
* timeoutMs: 10000,
|
|
189
|
+
* });
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
export async function executeShellCommand(
|
|
193
|
+
shellCommand: string,
|
|
194
|
+
options: SandboxExecutionOptions = {}
|
|
195
|
+
): Promise<SandboxedExecutionResult> {
|
|
196
|
+
const shell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
|
|
197
|
+
const shellArgs = process.platform === "win32" ? ["/c", shellCommand] : ["-c", shellCommand];
|
|
198
|
+
|
|
199
|
+
return executeSandboxed(shell, shellArgs, options);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Cleanup the sandbox executor.
|
|
204
|
+
* Should be called on application shutdown.
|
|
205
|
+
*/
|
|
206
|
+
export async function cleanupSandbox(): Promise<void> {
|
|
207
|
+
if (sandboxExecutor) {
|
|
208
|
+
await sandboxExecutor.cleanup();
|
|
209
|
+
sandboxExecutor = null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Create a sandboxed executor bound to a specific working directory.
|
|
215
|
+
*
|
|
216
|
+
* @param workingDirectory - The working directory
|
|
217
|
+
* @param options - Additional sandbox options
|
|
218
|
+
* @returns An object with bound execute functions
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```typescript
|
|
222
|
+
* const sandbox = createBoundSandbox("/project/dir");
|
|
223
|
+
* await sandbox.execute("npm", ["install"]);
|
|
224
|
+
* await sandbox.shell("npm run build && npm test");
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
export function createBoundSandbox(
|
|
228
|
+
workingDirectory: string,
|
|
229
|
+
options: Omit<SandboxIntegrationOptions, "workingDirectory"> = {}
|
|
230
|
+
): {
|
|
231
|
+
execute: (
|
|
232
|
+
command: string,
|
|
233
|
+
args?: string[],
|
|
234
|
+
execOptions?: Omit<SandboxExecutionOptions, "cwd">
|
|
235
|
+
) => Promise<SandboxedExecutionResult>;
|
|
236
|
+
shell: (
|
|
237
|
+
shellCommand: string,
|
|
238
|
+
execOptions?: Omit<SandboxExecutionOptions, "cwd">
|
|
239
|
+
) => Promise<SandboxedExecutionResult>;
|
|
240
|
+
cleanup: () => Promise<void>;
|
|
241
|
+
} {
|
|
242
|
+
const timeoutMs = options.timeoutMs ?? SANDBOX_RESOURCES.TIMEOUT_MS;
|
|
243
|
+
const allowNetwork = options.allowNetwork ?? SANDBOX_PERMISSIONS.ALLOW_NETWORK;
|
|
244
|
+
const allowFileSystem = options.allowFileSystem ?? SANDBOX_PERMISSIONS.ALLOW_FILE_SYSTEM;
|
|
245
|
+
const maxOutputBytes = options.maxOutputBytes ?? SANDBOX_RESOURCES.MAX_OUTPUT_BYTES;
|
|
246
|
+
|
|
247
|
+
// Create a dedicated executor for this directory
|
|
248
|
+
const config: SandboxConfig = {
|
|
249
|
+
id: `sandbox-bound-${Date.now()}`,
|
|
250
|
+
strategy: "subprocess",
|
|
251
|
+
workingDir: workingDirectory,
|
|
252
|
+
environment: options.environment ?? {},
|
|
253
|
+
enableAudit: SANDBOX_PERMISSIONS.ENABLE_AUDIT,
|
|
254
|
+
resources: {
|
|
255
|
+
cpuTimeMs: timeoutMs,
|
|
256
|
+
wallTimeMs: timeoutMs + SANDBOX_RESOURCES.WALL_TIME_BUFFER_MS,
|
|
257
|
+
memoryBytes: SANDBOX_RESOURCES.MEMORY_BYTES,
|
|
258
|
+
maxFileDescriptors: SANDBOX_RESOURCES.MAX_FILE_DESCRIPTORS,
|
|
259
|
+
maxProcesses: SANDBOX_RESOURCES.MAX_PROCESSES,
|
|
260
|
+
maxOutputBytes,
|
|
261
|
+
maxFileSizeBytes: SANDBOX_RESOURCES.MAX_FILE_SIZE_BYTES,
|
|
262
|
+
},
|
|
263
|
+
network: {
|
|
264
|
+
allowNetwork,
|
|
265
|
+
allowedHosts: [],
|
|
266
|
+
allowedPorts: [],
|
|
267
|
+
blockDns: !allowNetwork,
|
|
268
|
+
},
|
|
269
|
+
filesystem: {
|
|
270
|
+
rootDir: workingDirectory,
|
|
271
|
+
readOnlyPaths: [],
|
|
272
|
+
readWritePaths: allowFileSystem ? [workingDirectory] : [],
|
|
273
|
+
deniedPaths: [],
|
|
274
|
+
useOverlay: SANDBOX_PERMISSIONS.USE_OVERLAY,
|
|
275
|
+
maxDiskUsageBytes: SANDBOX_RESOURCES.MAX_DISK_USAGE_BYTES,
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const backend = detectSandboxBackend();
|
|
280
|
+
const executor = new SandboxExecutor(config, backend);
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
execute: async (command, args = [], execOptions = {}) => {
|
|
284
|
+
const result = await executor.execute(command, args, {
|
|
285
|
+
...execOptions,
|
|
286
|
+
cwd: workingDirectory,
|
|
287
|
+
});
|
|
288
|
+
return {
|
|
289
|
+
success: result.exitCode === 0 && !result.terminated,
|
|
290
|
+
exitCode: result.exitCode,
|
|
291
|
+
stdout: result.stdout,
|
|
292
|
+
stderr: result.stderr,
|
|
293
|
+
durationMs: result.durationMs,
|
|
294
|
+
terminated: result.terminated,
|
|
295
|
+
terminationReason: result.terminationReason,
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
shell: async (shellCommand, execOptions = {}) => {
|
|
299
|
+
const shell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
|
|
300
|
+
const shellArgs = process.platform === "win32" ? ["/c", shellCommand] : ["-c", shellCommand];
|
|
301
|
+
const result = await executor.execute(shell, shellArgs, {
|
|
302
|
+
...execOptions,
|
|
303
|
+
cwd: workingDirectory,
|
|
304
|
+
});
|
|
305
|
+
return {
|
|
306
|
+
success: result.exitCode === 0 && !result.terminated,
|
|
307
|
+
exitCode: result.exitCode,
|
|
308
|
+
stdout: result.stdout,
|
|
309
|
+
stderr: result.stderr,
|
|
310
|
+
durationMs: result.durationMs,
|
|
311
|
+
terminated: result.terminated,
|
|
312
|
+
terminationReason: result.terminationReason,
|
|
313
|
+
};
|
|
314
|
+
},
|
|
315
|
+
cleanup: () => executor.cleanup(),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clipboard Service
|
|
3
|
+
*
|
|
4
|
+
* Provides cross-platform clipboard access with error handling
|
|
5
|
+
* for environments that don't support clipboard operations
|
|
6
|
+
* (e.g., WSL, SSH sessions, headless servers).
|
|
7
|
+
*
|
|
8
|
+
* @module tui/services/clipboard
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import clipboard from "clipboardy";
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Types
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Clipboard history entry
|
|
19
|
+
*/
|
|
20
|
+
export interface ClipboardHistoryEntry {
|
|
21
|
+
/** The copied text content */
|
|
22
|
+
readonly text: string;
|
|
23
|
+
/** Timestamp when the copy occurred */
|
|
24
|
+
readonly timestamp: number;
|
|
25
|
+
/** Optional label describing the content */
|
|
26
|
+
readonly label?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Result of a clipboard operation
|
|
31
|
+
*/
|
|
32
|
+
export type ClipboardResult<T = void> =
|
|
33
|
+
| { success: true; data: T }
|
|
34
|
+
| { success: false; error: string };
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Configuration
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
/** Maximum number of history entries to keep */
|
|
41
|
+
const MAX_HISTORY_SIZE = 10;
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Module State
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
/** Clipboard history storage */
|
|
48
|
+
const history: ClipboardHistoryEntry[] = [];
|
|
49
|
+
|
|
50
|
+
/** Cached support status (null = not yet checked) */
|
|
51
|
+
let supportedCache: boolean | null = null;
|
|
52
|
+
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// Support Detection
|
|
55
|
+
// =============================================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if clipboard operations are supported in the current environment.
|
|
59
|
+
*
|
|
60
|
+
* Clipboard may not be available in:
|
|
61
|
+
* - WSL without X server or clipboard integration
|
|
62
|
+
* - SSH sessions without forwarding
|
|
63
|
+
* - Headless servers
|
|
64
|
+
* - Docker containers without display
|
|
65
|
+
*
|
|
66
|
+
* @returns true if clipboard operations are likely to work
|
|
67
|
+
*/
|
|
68
|
+
export function isSupported(): boolean {
|
|
69
|
+
if (supportedCache !== null) {
|
|
70
|
+
return supportedCache;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check environment variables that indicate lack of display
|
|
74
|
+
const noDisplay =
|
|
75
|
+
!process.env.DISPLAY &&
|
|
76
|
+
!process.env.WAYLAND_DISPLAY &&
|
|
77
|
+
process.platform !== "win32" &&
|
|
78
|
+
process.platform !== "darwin";
|
|
79
|
+
|
|
80
|
+
// Check for WSL without Windows clipboard access
|
|
81
|
+
const isWsl =
|
|
82
|
+
process.platform === "linux" &&
|
|
83
|
+
(process.env.WSL_DISTRO_NAME !== undefined || process.env.WSL_INTEROP !== undefined);
|
|
84
|
+
|
|
85
|
+
// WSL usually has access to clip.exe/powershell.exe for clipboard
|
|
86
|
+
// so we'll optimistically allow it
|
|
87
|
+
if (isWsl) {
|
|
88
|
+
supportedCache = true;
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// No display on Linux = likely no clipboard
|
|
93
|
+
if (noDisplay) {
|
|
94
|
+
supportedCache = false;
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Windows and macOS generally support clipboard
|
|
99
|
+
supportedCache = true;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Reset the cached support status.
|
|
105
|
+
* Useful for testing or when environment changes.
|
|
106
|
+
*/
|
|
107
|
+
export function resetSupportCache(): void {
|
|
108
|
+
supportedCache = null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// =============================================================================
|
|
112
|
+
// Core Operations
|
|
113
|
+
// =============================================================================
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Copy text to the system clipboard.
|
|
117
|
+
*
|
|
118
|
+
* @param text - The text to copy
|
|
119
|
+
* @param label - Optional label for history (e.g., "code block", "file content")
|
|
120
|
+
* @returns Result indicating success or failure with error message
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* const result = await copy("Hello, World!", "greeting");
|
|
125
|
+
* if (result.success) {
|
|
126
|
+
* console.log("Copied successfully");
|
|
127
|
+
* } else {
|
|
128
|
+
* console.error("Copy failed:", result.error);
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export async function copy(text: string, label?: string): Promise<ClipboardResult> {
|
|
133
|
+
if (!text) {
|
|
134
|
+
return { success: false, error: "Nothing to copy" };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!isSupported()) {
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
error: "Clipboard not supported in this environment",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await clipboard.write(text);
|
|
146
|
+
|
|
147
|
+
// Add to history
|
|
148
|
+
addToHistory(text, label);
|
|
149
|
+
|
|
150
|
+
return { success: true, data: undefined };
|
|
151
|
+
} catch (err) {
|
|
152
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
153
|
+
|
|
154
|
+
// Provide helpful error messages for common issues
|
|
155
|
+
if (message.includes("xsel") || message.includes("xclip")) {
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
error: "Clipboard requires xsel or xclip on Linux. Install with: sudo apt install xclip",
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { success: false, error: `Copy failed: ${message}` };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Synchronous copy operation (best-effort).
|
|
168
|
+
* Falls back gracefully if async fails.
|
|
169
|
+
*
|
|
170
|
+
* @param text - The text to copy
|
|
171
|
+
* @param label - Optional label for history
|
|
172
|
+
* @returns true if copy likely succeeded
|
|
173
|
+
*/
|
|
174
|
+
export function copySync(text: string, label?: string): boolean {
|
|
175
|
+
if (!text || !isSupported()) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
clipboard.writeSync(text);
|
|
181
|
+
addToHistory(text, label);
|
|
182
|
+
return true;
|
|
183
|
+
} catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Read text from the system clipboard.
|
|
190
|
+
*
|
|
191
|
+
* @returns Result with clipboard content or error
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```typescript
|
|
195
|
+
* const result = await paste();
|
|
196
|
+
* if (result.success) {
|
|
197
|
+
* console.log("Clipboard content:", result.data);
|
|
198
|
+
* }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
export async function paste(): Promise<ClipboardResult<string>> {
|
|
202
|
+
if (!isSupported()) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: "Clipboard not supported in this environment",
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const text = await clipboard.read();
|
|
211
|
+
return { success: true, data: text };
|
|
212
|
+
} catch (err) {
|
|
213
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
214
|
+
return { success: false, error: `Paste failed: ${message}` };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Synchronous paste operation.
|
|
220
|
+
*
|
|
221
|
+
* @returns Clipboard content or empty string on failure
|
|
222
|
+
*/
|
|
223
|
+
export function pasteSync(): string {
|
|
224
|
+
if (!isSupported()) {
|
|
225
|
+
return "";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
return clipboard.readSync();
|
|
230
|
+
} catch {
|
|
231
|
+
return "";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// =============================================================================
|
|
236
|
+
// History Management
|
|
237
|
+
// =============================================================================
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Add an entry to the clipboard history.
|
|
241
|
+
*
|
|
242
|
+
* @param text - The copied text
|
|
243
|
+
* @param label - Optional descriptive label
|
|
244
|
+
*/
|
|
245
|
+
function addToHistory(text: string, label?: string): void {
|
|
246
|
+
const entry: ClipboardHistoryEntry = {
|
|
247
|
+
text,
|
|
248
|
+
timestamp: Date.now(),
|
|
249
|
+
label,
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Add to front (most recent first)
|
|
253
|
+
history.unshift(entry);
|
|
254
|
+
|
|
255
|
+
// Trim to max size
|
|
256
|
+
if (history.length > MAX_HISTORY_SIZE) {
|
|
257
|
+
history.length = MAX_HISTORY_SIZE;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Get the clipboard history.
|
|
263
|
+
*
|
|
264
|
+
* @param limit - Maximum entries to return (default: all)
|
|
265
|
+
* @returns Array of history entries, most recent first
|
|
266
|
+
*/
|
|
267
|
+
export function getHistory(limit?: number): readonly ClipboardHistoryEntry[] {
|
|
268
|
+
if (limit !== undefined && limit > 0) {
|
|
269
|
+
return history.slice(0, limit);
|
|
270
|
+
}
|
|
271
|
+
return [...history];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get the most recent clipboard entry.
|
|
276
|
+
*
|
|
277
|
+
* @returns The most recent entry or undefined if history is empty
|
|
278
|
+
*/
|
|
279
|
+
export function getLastEntry(): ClipboardHistoryEntry | undefined {
|
|
280
|
+
return history[0];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Clear the clipboard history.
|
|
285
|
+
*/
|
|
286
|
+
export function clearHistory(): void {
|
|
287
|
+
history.length = 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get the number of entries in history.
|
|
292
|
+
*/
|
|
293
|
+
export function getHistorySize(): number {
|
|
294
|
+
return history.length;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// =============================================================================
|
|
298
|
+
// Convenience Functions
|
|
299
|
+
// =============================================================================
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Copy text and return a user-friendly status message.
|
|
303
|
+
*
|
|
304
|
+
* @param text - Text to copy
|
|
305
|
+
* @param description - What was copied (for the message)
|
|
306
|
+
* @returns Status message suitable for display
|
|
307
|
+
*/
|
|
308
|
+
export async function copyWithMessage(
|
|
309
|
+
text: string,
|
|
310
|
+
description: string
|
|
311
|
+
): Promise<{ success: boolean; message: string }> {
|
|
312
|
+
const result = await copy(text, description);
|
|
313
|
+
|
|
314
|
+
if (result.success) {
|
|
315
|
+
return {
|
|
316
|
+
success: true,
|
|
317
|
+
message: `📋 Copied ${description} (${text.length} chars)`,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
success: false,
|
|
323
|
+
message: `❌ ${result.error}`,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// =============================================================================
|
|
328
|
+
// Default Export
|
|
329
|
+
// =============================================================================
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Clipboard service object for convenient access
|
|
333
|
+
*/
|
|
334
|
+
export const clipboardService = {
|
|
335
|
+
copy,
|
|
336
|
+
copySync,
|
|
337
|
+
paste,
|
|
338
|
+
pasteSync,
|
|
339
|
+
isSupported,
|
|
340
|
+
getHistory,
|
|
341
|
+
getLastEntry,
|
|
342
|
+
clearHistory,
|
|
343
|
+
getHistorySize,
|
|
344
|
+
copyWithMessage,
|
|
345
|
+
resetSupportCache,
|
|
346
|
+
} as const;
|
|
347
|
+
|
|
348
|
+
export default clipboardService;
|