@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,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCollapsible Hook
|
|
3
|
+
*
|
|
4
|
+
* Generic collapsible state management for TUI components.
|
|
5
|
+
* Supports keyboard toggles, animation timing, and state persistence.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/hooks/useCollapsible
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useInput } from "ink";
|
|
11
|
+
import { useCallback, useEffect, useState } from "react";
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Types
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for the useCollapsible hook.
|
|
19
|
+
*/
|
|
20
|
+
export interface UseCollapsibleOptions {
|
|
21
|
+
/** Initial collapsed state (default: true) */
|
|
22
|
+
readonly initialCollapsed?: boolean;
|
|
23
|
+
/** Key to toggle collapse state (default: none - manual toggle only) */
|
|
24
|
+
readonly toggleKey?: string;
|
|
25
|
+
/** Whether keyboard toggle is enabled (default: true if toggleKey provided) */
|
|
26
|
+
readonly keyboardEnabled?: boolean;
|
|
27
|
+
/** Unique ID for persistence (if provided, state persists across renders) */
|
|
28
|
+
readonly persistenceId?: string;
|
|
29
|
+
/** Animation duration in ms for expand/collapse (default: 0 - instant) */
|
|
30
|
+
readonly animationDuration?: number;
|
|
31
|
+
/** Callback when state changes */
|
|
32
|
+
readonly onToggle?: (collapsed: boolean) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Return value of useCollapsible hook.
|
|
37
|
+
*/
|
|
38
|
+
export interface UseCollapsibleReturn {
|
|
39
|
+
/** Whether the content is currently collapsed */
|
|
40
|
+
readonly isCollapsed: boolean;
|
|
41
|
+
/** Toggle the collapsed state */
|
|
42
|
+
readonly toggle: () => void;
|
|
43
|
+
/** Expand the content */
|
|
44
|
+
readonly expand: () => void;
|
|
45
|
+
/** Collapse the content */
|
|
46
|
+
readonly collapse: () => void;
|
|
47
|
+
/** Set collapsed state directly */
|
|
48
|
+
readonly setCollapsed: (collapsed: boolean) => void;
|
|
49
|
+
/** Whether an animation is in progress */
|
|
50
|
+
readonly isAnimating: boolean;
|
|
51
|
+
/** Animation progress (0-1) for custom animations */
|
|
52
|
+
readonly animationProgress: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Storage for Persistence
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
/** In-memory storage for collapsible states */
|
|
60
|
+
const collapsibleStateStorage = new Map<string, boolean>();
|
|
61
|
+
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// Hook Implementation
|
|
64
|
+
// =============================================================================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Hook for managing collapsible state with optional keyboard toggle and persistence.
|
|
68
|
+
*
|
|
69
|
+
* @param options - Configuration options
|
|
70
|
+
* @returns Collapsible state and controls
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* // Basic usage with keyboard toggle
|
|
75
|
+
* const { isCollapsed, toggle } = useCollapsible({
|
|
76
|
+
* initialCollapsed: true,
|
|
77
|
+
* toggleKey: 't',
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* // With persistence
|
|
81
|
+
* const { isCollapsed, toggle } = useCollapsible({
|
|
82
|
+
* initialCollapsed: true,
|
|
83
|
+
* toggleKey: 't',
|
|
84
|
+
* persistenceId: 'thinking-block-1',
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
87
|
+
* // With callback
|
|
88
|
+
* const { isCollapsed } = useCollapsible({
|
|
89
|
+
* toggleKey: 't',
|
|
90
|
+
* onToggle: (collapsed) => console.log('Collapsed:', collapsed),
|
|
91
|
+
* });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export function useCollapsible(options: UseCollapsibleOptions = {}): UseCollapsibleReturn {
|
|
95
|
+
const {
|
|
96
|
+
initialCollapsed = true,
|
|
97
|
+
toggleKey,
|
|
98
|
+
keyboardEnabled = Boolean(toggleKey),
|
|
99
|
+
persistenceId,
|
|
100
|
+
animationDuration = 0,
|
|
101
|
+
onToggle,
|
|
102
|
+
} = options;
|
|
103
|
+
|
|
104
|
+
// Initialize from persistence if available
|
|
105
|
+
const getInitialState = (): boolean => {
|
|
106
|
+
if (persistenceId && collapsibleStateStorage.has(persistenceId)) {
|
|
107
|
+
return collapsibleStateStorage.get(persistenceId) ?? initialCollapsed;
|
|
108
|
+
}
|
|
109
|
+
return initialCollapsed;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const [isCollapsed, setIsCollapsed] = useState(getInitialState);
|
|
113
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
114
|
+
const [animationProgress, setAnimationProgress] = useState(isCollapsed ? 0 : 1);
|
|
115
|
+
|
|
116
|
+
// Update persistence when state changes
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (persistenceId) {
|
|
119
|
+
collapsibleStateStorage.set(persistenceId, isCollapsed);
|
|
120
|
+
}
|
|
121
|
+
}, [isCollapsed, persistenceId]);
|
|
122
|
+
|
|
123
|
+
// Animation effect
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (animationDuration <= 0) {
|
|
126
|
+
setAnimationProgress(isCollapsed ? 0 : 1);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setIsAnimating(true);
|
|
131
|
+
// Capture current progress at effect start (before animation begins)
|
|
132
|
+
const startProgress = isCollapsed ? 1 : 0; // Start from opposite of target
|
|
133
|
+
const targetProgress = isCollapsed ? 0 : 1;
|
|
134
|
+
const startTime = Date.now();
|
|
135
|
+
|
|
136
|
+
// Store timer ref for cleanup
|
|
137
|
+
const animationTimerRef = { current: null as ReturnType<typeof setTimeout> | null };
|
|
138
|
+
|
|
139
|
+
const animate = () => {
|
|
140
|
+
const elapsed = Date.now() - startTime;
|
|
141
|
+
const progress = Math.min(elapsed / animationDuration, 1);
|
|
142
|
+
|
|
143
|
+
// Ease-out cubic for smooth deceleration
|
|
144
|
+
const eased = 1 - (1 - progress) ** 3;
|
|
145
|
+
const currentProgress = startProgress + (targetProgress - startProgress) * eased;
|
|
146
|
+
|
|
147
|
+
setAnimationProgress(currentProgress);
|
|
148
|
+
|
|
149
|
+
if (progress < 1) {
|
|
150
|
+
animationTimerRef.current = setTimeout(animate, 16); // ~60fps
|
|
151
|
+
} else {
|
|
152
|
+
setIsAnimating(false);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
animationTimerRef.current = setTimeout(animate, 0);
|
|
157
|
+
|
|
158
|
+
return () => {
|
|
159
|
+
if (animationTimerRef.current) {
|
|
160
|
+
clearTimeout(animationTimerRef.current);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}, [isCollapsed, animationDuration]);
|
|
164
|
+
|
|
165
|
+
// Toggle function
|
|
166
|
+
const toggle = useCallback(() => {
|
|
167
|
+
setIsCollapsed((prev) => {
|
|
168
|
+
const next = !prev;
|
|
169
|
+
onToggle?.(next);
|
|
170
|
+
return next;
|
|
171
|
+
});
|
|
172
|
+
}, [onToggle]);
|
|
173
|
+
|
|
174
|
+
// Expand function
|
|
175
|
+
const expand = useCallback(() => {
|
|
176
|
+
if (isCollapsed) {
|
|
177
|
+
setIsCollapsed(false);
|
|
178
|
+
onToggle?.(false);
|
|
179
|
+
}
|
|
180
|
+
}, [isCollapsed, onToggle]);
|
|
181
|
+
|
|
182
|
+
// Collapse function
|
|
183
|
+
const collapse = useCallback(() => {
|
|
184
|
+
if (!isCollapsed) {
|
|
185
|
+
setIsCollapsed(true);
|
|
186
|
+
onToggle?.(true);
|
|
187
|
+
}
|
|
188
|
+
}, [isCollapsed, onToggle]);
|
|
189
|
+
|
|
190
|
+
// Direct setter
|
|
191
|
+
const setCollapsed = useCallback(
|
|
192
|
+
(collapsed: boolean) => {
|
|
193
|
+
if (collapsed !== isCollapsed) {
|
|
194
|
+
setIsCollapsed(collapsed);
|
|
195
|
+
onToggle?.(collapsed);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
[isCollapsed, onToggle]
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Keyboard toggle handler
|
|
202
|
+
useInput(
|
|
203
|
+
(input, _key) => {
|
|
204
|
+
if (keyboardEnabled && toggleKey && input.toLowerCase() === toggleKey.toLowerCase()) {
|
|
205
|
+
toggle();
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
{ isActive: keyboardEnabled }
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
isCollapsed,
|
|
213
|
+
toggle,
|
|
214
|
+
expand,
|
|
215
|
+
collapse,
|
|
216
|
+
setCollapsed,
|
|
217
|
+
isAnimating,
|
|
218
|
+
animationProgress,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// =============================================================================
|
|
223
|
+
// Utility Functions
|
|
224
|
+
// =============================================================================
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Clear persisted collapsible state for a given ID.
|
|
228
|
+
*/
|
|
229
|
+
export function clearCollapsibleState(persistenceId: string): void {
|
|
230
|
+
collapsibleStateStorage.delete(persistenceId);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Clear all persisted collapsible states.
|
|
235
|
+
*/
|
|
236
|
+
export function clearAllCollapsibleStates(): void {
|
|
237
|
+
collapsibleStateStorage.clear();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export default useCollapsible;
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCopyMode Hook (T055)
|
|
3
|
+
*
|
|
4
|
+
* React hook for terminal text copy selection mode.
|
|
5
|
+
* Provides visual selection mode with keyboard navigation and clipboard copy.
|
|
6
|
+
*
|
|
7
|
+
* @module @vellum/cli
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { exec } from "node:child_process";
|
|
11
|
+
import { useCallback, useState } from "react";
|
|
12
|
+
|
|
13
|
+
import { getActiveStdout } from "../buffered-stdout.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* State for copy mode selection.
|
|
17
|
+
*/
|
|
18
|
+
export interface CopyModeState {
|
|
19
|
+
/** Whether copy mode is active */
|
|
20
|
+
active: boolean;
|
|
21
|
+
/** Starting line of selection (0-indexed) */
|
|
22
|
+
startLine: number;
|
|
23
|
+
/** Ending line of selection (0-indexed) */
|
|
24
|
+
endLine: number;
|
|
25
|
+
/** Starting column of selection (0-indexed) */
|
|
26
|
+
startCol: number;
|
|
27
|
+
/** Ending column of selection (0-indexed) */
|
|
28
|
+
endCol: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Return value of useCopyMode hook.
|
|
33
|
+
*/
|
|
34
|
+
export interface UseCopyModeReturn {
|
|
35
|
+
/** Current copy mode state */
|
|
36
|
+
state: CopyModeState;
|
|
37
|
+
/** Enter visual selection mode */
|
|
38
|
+
enterCopyMode: () => void;
|
|
39
|
+
/** Exit visual selection mode */
|
|
40
|
+
exitCopyMode: () => void;
|
|
41
|
+
/** Expand selection in a direction */
|
|
42
|
+
expandSelection: (direction: "up" | "down" | "left" | "right") => void;
|
|
43
|
+
/** Copy the selected content to clipboard */
|
|
44
|
+
copySelection: (content: string[][]) => Promise<void>;
|
|
45
|
+
/** Check if a position is within the selection */
|
|
46
|
+
isInSelection: (line: number, col: number) => boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initial state for copy mode.
|
|
51
|
+
*/
|
|
52
|
+
const initialState: CopyModeState = {
|
|
53
|
+
active: false,
|
|
54
|
+
startLine: 0,
|
|
55
|
+
endLine: 0,
|
|
56
|
+
startCol: 0,
|
|
57
|
+
endCol: 0,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Copy text to system clipboard using platform-specific methods.
|
|
62
|
+
*
|
|
63
|
+
* Supports:
|
|
64
|
+
* - macOS: pbcopy
|
|
65
|
+
* - Windows: clip (via PowerShell for Unicode support)
|
|
66
|
+
* - Linux: xclip or xsel
|
|
67
|
+
* - Fallback: OSC 52 escape sequence for terminal emulators
|
|
68
|
+
*
|
|
69
|
+
* @param text - Text to copy to clipboard
|
|
70
|
+
* @returns Promise that resolves when copy is complete
|
|
71
|
+
*/
|
|
72
|
+
async function copyToClipboard(text: string): Promise<void> {
|
|
73
|
+
if (!text) return;
|
|
74
|
+
|
|
75
|
+
const platform = process.platform;
|
|
76
|
+
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
let command: string;
|
|
79
|
+
const encoding: BufferEncoding = "utf8";
|
|
80
|
+
|
|
81
|
+
switch (platform) {
|
|
82
|
+
case "darwin":
|
|
83
|
+
command = "pbcopy";
|
|
84
|
+
break;
|
|
85
|
+
case "win32":
|
|
86
|
+
// Use PowerShell for proper Unicode support on Windows
|
|
87
|
+
command = `powershell -NoProfile -Command "$input | Set-Clipboard"`;
|
|
88
|
+
break;
|
|
89
|
+
case "linux":
|
|
90
|
+
// Try xclip first, fallback to xsel
|
|
91
|
+
command = "xclip -selection clipboard";
|
|
92
|
+
break;
|
|
93
|
+
default:
|
|
94
|
+
// For other platforms, try OSC 52 escape sequence
|
|
95
|
+
try {
|
|
96
|
+
const base64Text = Buffer.from(text, "utf8").toString("base64");
|
|
97
|
+
getActiveStdout().write(`\x1b]52;c;${base64Text}\x07`);
|
|
98
|
+
resolve();
|
|
99
|
+
return;
|
|
100
|
+
} catch {
|
|
101
|
+
reject(new Error(`Unsupported platform: ${platform}`));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const child = exec(command, { encoding }, (error) => {
|
|
107
|
+
if (error) {
|
|
108
|
+
// On Linux, try xsel as fallback
|
|
109
|
+
if (platform === "linux") {
|
|
110
|
+
const fallback = exec("xsel --clipboard --input", { encoding }, (fallbackError) => {
|
|
111
|
+
if (fallbackError) {
|
|
112
|
+
// Last resort: OSC 52
|
|
113
|
+
try {
|
|
114
|
+
const base64Text = Buffer.from(text, "utf8").toString("base64");
|
|
115
|
+
getActiveStdout().write(`\x1b]52;c;${base64Text}\x07`);
|
|
116
|
+
resolve();
|
|
117
|
+
} catch {
|
|
118
|
+
reject(fallbackError);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
resolve();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
fallback.stdin?.write(text);
|
|
125
|
+
fallback.stdin?.end();
|
|
126
|
+
} else {
|
|
127
|
+
reject(error);
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
resolve();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
child.stdin?.write(text);
|
|
135
|
+
child.stdin?.end();
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Extract selected text from 2D content array.
|
|
141
|
+
*
|
|
142
|
+
* @param content - 2D array of characters (content[line][col])
|
|
143
|
+
* @param startLine - Starting line index
|
|
144
|
+
* @param endLine - Ending line index
|
|
145
|
+
* @param startCol - Starting column index
|
|
146
|
+
* @param endCol - Ending column index
|
|
147
|
+
* @returns Selected text as a string
|
|
148
|
+
*/
|
|
149
|
+
function extractSelection(
|
|
150
|
+
content: string[][],
|
|
151
|
+
startLine: number,
|
|
152
|
+
endLine: number,
|
|
153
|
+
startCol: number,
|
|
154
|
+
endCol: number
|
|
155
|
+
): string {
|
|
156
|
+
if (content.length === 0) return "";
|
|
157
|
+
|
|
158
|
+
// Normalize bounds (ensure start <= end)
|
|
159
|
+
const minLine = Math.min(startLine, endLine);
|
|
160
|
+
const maxLine = Math.max(startLine, endLine);
|
|
161
|
+
const minCol = Math.min(startCol, endCol);
|
|
162
|
+
const maxCol = Math.max(startCol, endCol);
|
|
163
|
+
|
|
164
|
+
// Clamp to valid ranges
|
|
165
|
+
const clampedMinLine = Math.max(0, Math.min(minLine, content.length - 1));
|
|
166
|
+
const clampedMaxLine = Math.max(0, Math.min(maxLine, content.length - 1));
|
|
167
|
+
|
|
168
|
+
const lines: string[] = [];
|
|
169
|
+
|
|
170
|
+
for (let line = clampedMinLine; line <= clampedMaxLine; line++) {
|
|
171
|
+
const lineContent = content[line] ?? [];
|
|
172
|
+
if (lineContent.length === 0) {
|
|
173
|
+
lines.push("");
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const lineMinCol = Math.max(0, Math.min(minCol, lineContent.length - 1));
|
|
178
|
+
const lineMaxCol = Math.max(0, Math.min(maxCol, lineContent.length - 1));
|
|
179
|
+
|
|
180
|
+
// For single line selection, use exact columns
|
|
181
|
+
// For multi-line, first line uses startCol to end, middle lines use full line, last line uses start to endCol
|
|
182
|
+
if (clampedMinLine === clampedMaxLine) {
|
|
183
|
+
// Single line: exact column range
|
|
184
|
+
lines.push(lineContent.slice(lineMinCol, lineMaxCol + 1).join(""));
|
|
185
|
+
} else if (line === clampedMinLine) {
|
|
186
|
+
// First line: from minCol to end
|
|
187
|
+
lines.push(lineContent.slice(lineMinCol).join(""));
|
|
188
|
+
} else if (line === clampedMaxLine) {
|
|
189
|
+
// Last line: from start to maxCol
|
|
190
|
+
lines.push(lineContent.slice(0, lineMaxCol + 1).join(""));
|
|
191
|
+
} else {
|
|
192
|
+
// Middle lines: full line
|
|
193
|
+
lines.push(lineContent.join(""));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return lines.join("\n");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Hook for managing terminal text copy selection mode.
|
|
202
|
+
*
|
|
203
|
+
* Provides visual selection mode with keyboard navigation and clipboard copy.
|
|
204
|
+
* Enter with 'v' key, extend with arrow keys, copy with 'y', exit with Escape.
|
|
205
|
+
*
|
|
206
|
+
* @returns Copy mode state and control functions
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```tsx
|
|
210
|
+
* function TerminalOutput() {
|
|
211
|
+
* const { state, enterCopyMode, expandSelection, copySelection, isInSelection } = useCopyMode();
|
|
212
|
+
*
|
|
213
|
+
* useInput((input, key) => {
|
|
214
|
+
* if (!state.active && input === 'v') {
|
|
215
|
+
* enterCopyMode();
|
|
216
|
+
* } else if (state.active) {
|
|
217
|
+
* if (key.upArrow) expandSelection('up');
|
|
218
|
+
* if (key.downArrow) expandSelection('down');
|
|
219
|
+
* if (key.leftArrow) expandSelection('left');
|
|
220
|
+
* if (key.rightArrow) expandSelection('right');
|
|
221
|
+
* if (input === 'y') {
|
|
222
|
+
* copySelection(content);
|
|
223
|
+
* }
|
|
224
|
+
* }
|
|
225
|
+
* });
|
|
226
|
+
*
|
|
227
|
+
* return (
|
|
228
|
+
* <Box>
|
|
229
|
+
* {lines.map((line, lineNum) => (
|
|
230
|
+
* <Text key={lineNum}>
|
|
231
|
+
* {line.map((char, colNum) => (
|
|
232
|
+
* <Text
|
|
233
|
+
* key={colNum}
|
|
234
|
+
* inverse={state.active && isInSelection(lineNum, colNum)}
|
|
235
|
+
* >
|
|
236
|
+
* {char}
|
|
237
|
+
* </Text>
|
|
238
|
+
* ))}
|
|
239
|
+
* </Text>
|
|
240
|
+
* ))}
|
|
241
|
+
* </Box>
|
|
242
|
+
* );
|
|
243
|
+
* }
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
export function useCopyMode(): UseCopyModeReturn {
|
|
247
|
+
const [state, setState] = useState<CopyModeState>(initialState);
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Enter visual selection mode.
|
|
251
|
+
* Selection starts at position (0, 0).
|
|
252
|
+
*/
|
|
253
|
+
const enterCopyMode = useCallback(() => {
|
|
254
|
+
setState({
|
|
255
|
+
active: true,
|
|
256
|
+
startLine: 0,
|
|
257
|
+
endLine: 0,
|
|
258
|
+
startCol: 0,
|
|
259
|
+
endCol: 0,
|
|
260
|
+
});
|
|
261
|
+
}, []);
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Exit visual selection mode.
|
|
265
|
+
* Resets all selection state.
|
|
266
|
+
*/
|
|
267
|
+
const exitCopyMode = useCallback(() => {
|
|
268
|
+
setState(initialState);
|
|
269
|
+
}, []);
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Expand selection in the specified direction.
|
|
273
|
+
* Moves the end position while keeping start position fixed.
|
|
274
|
+
*/
|
|
275
|
+
const expandSelection = useCallback(
|
|
276
|
+
(direction: "up" | "down" | "left" | "right") => {
|
|
277
|
+
if (!state.active) return;
|
|
278
|
+
|
|
279
|
+
setState((prev) => {
|
|
280
|
+
const next = { ...prev };
|
|
281
|
+
|
|
282
|
+
switch (direction) {
|
|
283
|
+
case "up":
|
|
284
|
+
next.endLine = Math.max(0, prev.endLine - 1);
|
|
285
|
+
break;
|
|
286
|
+
case "down":
|
|
287
|
+
next.endLine = prev.endLine + 1;
|
|
288
|
+
break;
|
|
289
|
+
case "left":
|
|
290
|
+
next.endCol = Math.max(0, prev.endCol - 1);
|
|
291
|
+
break;
|
|
292
|
+
case "right":
|
|
293
|
+
next.endCol = prev.endCol + 1;
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return next;
|
|
298
|
+
});
|
|
299
|
+
},
|
|
300
|
+
[state.active]
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Copy the selected content to clipboard.
|
|
305
|
+
* Automatically exits copy mode after successful copy.
|
|
306
|
+
*
|
|
307
|
+
* @param content - 2D array where content[line][col] is a character
|
|
308
|
+
*/
|
|
309
|
+
const copySelection = useCallback(
|
|
310
|
+
async (content: string[][]): Promise<void> => {
|
|
311
|
+
if (!state.active) return;
|
|
312
|
+
|
|
313
|
+
const selectedText = extractSelection(
|
|
314
|
+
content,
|
|
315
|
+
state.startLine,
|
|
316
|
+
state.endLine,
|
|
317
|
+
state.startCol,
|
|
318
|
+
state.endCol
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
if (selectedText) {
|
|
322
|
+
await copyToClipboard(selectedText);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Exit copy mode after copy
|
|
326
|
+
setState(initialState);
|
|
327
|
+
},
|
|
328
|
+
[state]
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Check if a position is within the current selection.
|
|
333
|
+
* Handles both forward and backward selections.
|
|
334
|
+
*
|
|
335
|
+
* @param line - Line index to check
|
|
336
|
+
* @param col - Column index to check
|
|
337
|
+
* @returns True if the position is within the selection
|
|
338
|
+
*/
|
|
339
|
+
const isInSelection = useCallback(
|
|
340
|
+
(line: number, col: number): boolean => {
|
|
341
|
+
if (!state.active) return false;
|
|
342
|
+
|
|
343
|
+
// Normalize selection bounds
|
|
344
|
+
const minLine = Math.min(state.startLine, state.endLine);
|
|
345
|
+
const maxLine = Math.max(state.startLine, state.endLine);
|
|
346
|
+
const minCol = Math.min(state.startCol, state.endCol);
|
|
347
|
+
const maxCol = Math.max(state.startCol, state.endCol);
|
|
348
|
+
|
|
349
|
+
// Check if line is in range
|
|
350
|
+
if (line < minLine || line > maxLine) return false;
|
|
351
|
+
|
|
352
|
+
// For single line selection, check exact column range
|
|
353
|
+
if (minLine === maxLine) {
|
|
354
|
+
return col >= minCol && col <= maxCol;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// For multi-line selection:
|
|
358
|
+
// - First line: from minCol to end
|
|
359
|
+
// - Middle lines: entire line
|
|
360
|
+
// - Last line: from start to maxCol
|
|
361
|
+
if (line === minLine) {
|
|
362
|
+
return col >= minCol;
|
|
363
|
+
}
|
|
364
|
+
if (line === maxLine) {
|
|
365
|
+
return col <= maxCol;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Middle line: all columns are selected
|
|
369
|
+
return true;
|
|
370
|
+
},
|
|
371
|
+
[state]
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
state,
|
|
376
|
+
enterCopyMode,
|
|
377
|
+
exitCopyMode,
|
|
378
|
+
expandSelection,
|
|
379
|
+
copySelection,
|
|
380
|
+
isInSelection,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost Summary Hook
|
|
3
|
+
*
|
|
4
|
+
* Provides aggregated cost information for the current session.
|
|
5
|
+
* Placeholder implementation - to be expanded.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/hooks/useCostSummary
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Types
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
export interface CostSummary {
|
|
17
|
+
/** Total cost in USD */
|
|
18
|
+
totalCost: number;
|
|
19
|
+
/** Total input tokens */
|
|
20
|
+
inputTokens: number;
|
|
21
|
+
/** Total output tokens */
|
|
22
|
+
outputTokens: number;
|
|
23
|
+
/** Number of API calls */
|
|
24
|
+
apiCalls: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface UseCostSummaryResult {
|
|
28
|
+
/** Current cost summary */
|
|
29
|
+
summary: CostSummary;
|
|
30
|
+
/** Whether data is loading */
|
|
31
|
+
isLoading: boolean;
|
|
32
|
+
/** Any error that occurred */
|
|
33
|
+
error: Error | null;
|
|
34
|
+
/** Reset the cost summary */
|
|
35
|
+
reset: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Hook
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
const defaultSummary: CostSummary = {
|
|
43
|
+
totalCost: 0,
|
|
44
|
+
inputTokens: 0,
|
|
45
|
+
outputTokens: 0,
|
|
46
|
+
apiCalls: 0,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Hook to track and display cost summary
|
|
51
|
+
*/
|
|
52
|
+
export function useCostSummary(): UseCostSummaryResult {
|
|
53
|
+
const [summary, setSummary] = useState<CostSummary>(defaultSummary);
|
|
54
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
55
|
+
const [error, setError] = useState<Error | null>(null);
|
|
56
|
+
|
|
57
|
+
const reset = () => {
|
|
58
|
+
setSummary(defaultSummary);
|
|
59
|
+
setError(null);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
// Placeholder: In real implementation, subscribe to cost events
|
|
64
|
+
setIsLoading(false);
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
summary,
|
|
69
|
+
isLoading,
|
|
70
|
+
error,
|
|
71
|
+
reset,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default useCostSummary;
|