@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,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDesktopNotification Hook (T059)
|
|
3
|
+
*
|
|
4
|
+
* React hook for sending desktop notifications from the TUI.
|
|
5
|
+
* Detects node-notifier availability and provides graceful fallback.
|
|
6
|
+
*
|
|
7
|
+
* @module @vellum/cli
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
+
|
|
12
|
+
import { ICONS } from "../../utils/icons.js";
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Types
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Notification priority levels.
|
|
20
|
+
*/
|
|
21
|
+
export type NotificationPriority = "low" | "normal" | "high" | "urgent";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Notification type categorization.
|
|
25
|
+
*/
|
|
26
|
+
export type NotificationType =
|
|
27
|
+
| "task-complete"
|
|
28
|
+
| "permission-request"
|
|
29
|
+
| "error"
|
|
30
|
+
| "warning"
|
|
31
|
+
| "info";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options for a notification.
|
|
35
|
+
*/
|
|
36
|
+
export interface NotificationOptions {
|
|
37
|
+
/** Title of the notification */
|
|
38
|
+
readonly title: string;
|
|
39
|
+
/** Body message */
|
|
40
|
+
readonly message: string;
|
|
41
|
+
/** Optional subtitle (macOS) */
|
|
42
|
+
readonly subtitle?: string;
|
|
43
|
+
/** Notification type for categorization */
|
|
44
|
+
readonly type?: NotificationType;
|
|
45
|
+
/** Priority level */
|
|
46
|
+
readonly priority?: NotificationPriority;
|
|
47
|
+
/** Whether the notification should make a sound */
|
|
48
|
+
readonly sound?: boolean;
|
|
49
|
+
/** Time in ms after which to auto-dismiss (0 = no auto-dismiss) */
|
|
50
|
+
readonly timeout?: number;
|
|
51
|
+
/** Optional icon path */
|
|
52
|
+
readonly icon?: string;
|
|
53
|
+
/** Optional click action callback */
|
|
54
|
+
readonly onClick?: () => void;
|
|
55
|
+
/** Optional close action callback */
|
|
56
|
+
readonly onClose?: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Configuration for the notification hook.
|
|
61
|
+
*/
|
|
62
|
+
export interface UseDesktopNotificationOptions {
|
|
63
|
+
/** Whether notifications are enabled (default: true) */
|
|
64
|
+
readonly enabled?: boolean;
|
|
65
|
+
/** Default sound setting (default: true) */
|
|
66
|
+
readonly defaultSound?: boolean;
|
|
67
|
+
/** Minimum time between notifications in ms (default: 1000) */
|
|
68
|
+
readonly throttleMs?: number;
|
|
69
|
+
/** Application name for notifications */
|
|
70
|
+
readonly appName?: string;
|
|
71
|
+
/** Whether to show notifications only when terminal is not focused */
|
|
72
|
+
readonly onlyWhenUnfocused?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Return value of useDesktopNotification hook.
|
|
77
|
+
*/
|
|
78
|
+
export interface UseDesktopNotificationReturn {
|
|
79
|
+
/** Whether notifications are available (node-notifier detected) */
|
|
80
|
+
readonly isAvailable: boolean;
|
|
81
|
+
/** Whether notifications are currently enabled */
|
|
82
|
+
readonly isEnabled: boolean;
|
|
83
|
+
/** Send a notification */
|
|
84
|
+
readonly notify: (options: NotificationOptions) => void;
|
|
85
|
+
/** Notify when a long task completes */
|
|
86
|
+
readonly notifyTaskComplete: (taskName: string, duration?: number) => void;
|
|
87
|
+
/** Notify for a permission request */
|
|
88
|
+
readonly notifyPermissionRequest: (permission: string) => void;
|
|
89
|
+
/** Notify for an error */
|
|
90
|
+
readonly notifyError: (message: string) => void;
|
|
91
|
+
/** Toggle notifications on/off */
|
|
92
|
+
readonly toggle: () => void;
|
|
93
|
+
/** Enable notifications */
|
|
94
|
+
readonly enable: () => void;
|
|
95
|
+
/** Disable notifications */
|
|
96
|
+
readonly disable: () => void;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// =============================================================================
|
|
100
|
+
// Constants
|
|
101
|
+
// =============================================================================
|
|
102
|
+
|
|
103
|
+
/** Default throttle time between notifications */
|
|
104
|
+
const DEFAULT_THROTTLE_MS = 1000;
|
|
105
|
+
|
|
106
|
+
/** Default app name */
|
|
107
|
+
const DEFAULT_APP_NAME = "Vellum";
|
|
108
|
+
|
|
109
|
+
/** Icons for different notification types */
|
|
110
|
+
const TYPE_ICONS: Record<NotificationType, string> = {
|
|
111
|
+
"task-complete": ICONS.success,
|
|
112
|
+
"permission-request": "[Auth]",
|
|
113
|
+
error: ICONS.error,
|
|
114
|
+
warning: ICONS.warning,
|
|
115
|
+
info: ICONS.info,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// =============================================================================
|
|
119
|
+
// Node-Notifier Detection & Wrapper
|
|
120
|
+
// =============================================================================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Lazy-loaded notifier module.
|
|
124
|
+
*/
|
|
125
|
+
let notifierModule: NotifierModule | null = null;
|
|
126
|
+
let notifierChecked = false;
|
|
127
|
+
|
|
128
|
+
interface NotifierModule {
|
|
129
|
+
notify(
|
|
130
|
+
options: {
|
|
131
|
+
title?: string;
|
|
132
|
+
message?: string;
|
|
133
|
+
subtitle?: string;
|
|
134
|
+
sound?: boolean;
|
|
135
|
+
icon?: string;
|
|
136
|
+
timeout?: number;
|
|
137
|
+
appID?: string;
|
|
138
|
+
},
|
|
139
|
+
callback?: (err: Error | null, response: string) => void
|
|
140
|
+
): void;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Attempt to load node-notifier dynamically.
|
|
145
|
+
*/
|
|
146
|
+
async function loadNotifier(): Promise<NotifierModule | null> {
|
|
147
|
+
if (notifierChecked) {
|
|
148
|
+
return notifierModule;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
notifierChecked = true;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
// Dynamic import to avoid hard dependency
|
|
155
|
+
// Using a variable to prevent TypeScript from resolving the module at compile time
|
|
156
|
+
const moduleName = "node-notifier";
|
|
157
|
+
const module = await import(moduleName);
|
|
158
|
+
notifierModule = module.default ?? module;
|
|
159
|
+
return notifierModule;
|
|
160
|
+
} catch {
|
|
161
|
+
// node-notifier not available - that's fine
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if we're running in a focused terminal.
|
|
168
|
+
* This is a heuristic check - not 100% reliable.
|
|
169
|
+
*/
|
|
170
|
+
function isTerminalFocused(): boolean {
|
|
171
|
+
// Check if running interactively
|
|
172
|
+
if (!process.stdin.isTTY) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// In most cases, if we're running in a TTY and stdout is a TTY,
|
|
177
|
+
// the terminal is likely focused. This is imperfect but reasonable.
|
|
178
|
+
return process.stdout.isTTY ?? false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// =============================================================================
|
|
182
|
+
// Hook Implementation
|
|
183
|
+
// =============================================================================
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Hook for sending desktop notifications.
|
|
187
|
+
*
|
|
188
|
+
* Automatically detects if node-notifier is available and provides
|
|
189
|
+
* a graceful fallback when it's not. Useful for notifying users
|
|
190
|
+
* when long-running tasks complete or when permission is needed.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```tsx
|
|
194
|
+
* function LongTaskRunner() {
|
|
195
|
+
* const { notify, notifyTaskComplete, isAvailable } = useDesktopNotification({
|
|
196
|
+
* appName: 'Vellum',
|
|
197
|
+
* });
|
|
198
|
+
*
|
|
199
|
+
* const runTask = async () => {
|
|
200
|
+
* const start = Date.now();
|
|
201
|
+
* await performLongTask();
|
|
202
|
+
* notifyTaskComplete('Build', Date.now() - start);
|
|
203
|
+
* };
|
|
204
|
+
*
|
|
205
|
+
* return (
|
|
206
|
+
* <Box>
|
|
207
|
+
* <Text>Notifications: {isAvailable ? 'Available' : 'Not available'}</Text>
|
|
208
|
+
* </Box>
|
|
209
|
+
* );
|
|
210
|
+
* }
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export function useDesktopNotification(
|
|
214
|
+
options: UseDesktopNotificationOptions = {}
|
|
215
|
+
): UseDesktopNotificationReturn {
|
|
216
|
+
const {
|
|
217
|
+
enabled: initialEnabled = true,
|
|
218
|
+
defaultSound = true,
|
|
219
|
+
throttleMs = DEFAULT_THROTTLE_MS,
|
|
220
|
+
appName = DEFAULT_APP_NAME,
|
|
221
|
+
onlyWhenUnfocused = true,
|
|
222
|
+
} = options;
|
|
223
|
+
|
|
224
|
+
const [isAvailable, setIsAvailable] = useState(false);
|
|
225
|
+
const [isEnabled, setIsEnabled] = useState(initialEnabled);
|
|
226
|
+
const lastNotificationTime = useRef(0);
|
|
227
|
+
const notifierRef = useRef<NotifierModule | null>(null);
|
|
228
|
+
|
|
229
|
+
// Load notifier on mount
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
let mounted = true;
|
|
232
|
+
|
|
233
|
+
loadNotifier().then((notifier) => {
|
|
234
|
+
if (mounted && notifier) {
|
|
235
|
+
notifierRef.current = notifier;
|
|
236
|
+
setIsAvailable(true);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return () => {
|
|
241
|
+
mounted = false;
|
|
242
|
+
};
|
|
243
|
+
}, []);
|
|
244
|
+
|
|
245
|
+
// Core notification function
|
|
246
|
+
const notify = useCallback(
|
|
247
|
+
(notifOptions: NotificationOptions) => {
|
|
248
|
+
// Check if we should notify
|
|
249
|
+
if (!isEnabled || !isAvailable || !notifierRef.current) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Check focus state
|
|
254
|
+
if (onlyWhenUnfocused && isTerminalFocused()) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Throttle notifications
|
|
259
|
+
const now = Date.now();
|
|
260
|
+
if (now - lastNotificationTime.current < throttleMs) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
lastNotificationTime.current = now;
|
|
264
|
+
|
|
265
|
+
const {
|
|
266
|
+
title,
|
|
267
|
+
message,
|
|
268
|
+
subtitle,
|
|
269
|
+
type,
|
|
270
|
+
sound = defaultSound,
|
|
271
|
+
timeout,
|
|
272
|
+
icon,
|
|
273
|
+
onClick,
|
|
274
|
+
onClose,
|
|
275
|
+
} = notifOptions;
|
|
276
|
+
|
|
277
|
+
// Build notification title with type icon
|
|
278
|
+
const typeIcon = type ? TYPE_ICONS[type] : "";
|
|
279
|
+
const fullTitle = typeIcon ? `${typeIcon} ${title}` : title;
|
|
280
|
+
|
|
281
|
+
// Send notification
|
|
282
|
+
notifierRef.current.notify(
|
|
283
|
+
{
|
|
284
|
+
title: fullTitle,
|
|
285
|
+
message,
|
|
286
|
+
subtitle,
|
|
287
|
+
sound,
|
|
288
|
+
icon,
|
|
289
|
+
timeout,
|
|
290
|
+
appID: appName,
|
|
291
|
+
},
|
|
292
|
+
(err, response) => {
|
|
293
|
+
if (err) {
|
|
294
|
+
// Silently handle errors - notifications are non-critical
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Handle click/close callbacks based on response
|
|
299
|
+
if (response === "activate" || response === "clicked") {
|
|
300
|
+
onClick?.();
|
|
301
|
+
} else if (response === "dismissed" || response === "timeout") {
|
|
302
|
+
onClose?.();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
},
|
|
307
|
+
[isEnabled, isAvailable, onlyWhenUnfocused, throttleMs, defaultSound, appName]
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Convenience: Notify task completion
|
|
311
|
+
const notifyTaskComplete = useCallback(
|
|
312
|
+
(taskName: string, duration?: number) => {
|
|
313
|
+
const durationText = duration ? ` (${formatDuration(duration)})` : "";
|
|
314
|
+
notify({
|
|
315
|
+
title: "Task Complete",
|
|
316
|
+
message: `${taskName} finished${durationText}`,
|
|
317
|
+
type: "task-complete",
|
|
318
|
+
priority: "normal",
|
|
319
|
+
});
|
|
320
|
+
},
|
|
321
|
+
[notify]
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
// Convenience: Notify permission request
|
|
325
|
+
const notifyPermissionRequest = useCallback(
|
|
326
|
+
(permission: string) => {
|
|
327
|
+
notify({
|
|
328
|
+
title: "Permission Required",
|
|
329
|
+
message: `${permission} - please review and approve`,
|
|
330
|
+
type: "permission-request",
|
|
331
|
+
priority: "high",
|
|
332
|
+
sound: true,
|
|
333
|
+
});
|
|
334
|
+
},
|
|
335
|
+
[notify]
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
// Convenience: Notify error
|
|
339
|
+
const notifyError = useCallback(
|
|
340
|
+
(message: string) => {
|
|
341
|
+
notify({
|
|
342
|
+
title: "Error",
|
|
343
|
+
message,
|
|
344
|
+
type: "error",
|
|
345
|
+
priority: "urgent",
|
|
346
|
+
sound: true,
|
|
347
|
+
});
|
|
348
|
+
},
|
|
349
|
+
[notify]
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
// Toggle/enable/disable
|
|
353
|
+
const toggle = useCallback(() => {
|
|
354
|
+
setIsEnabled((prev) => !prev);
|
|
355
|
+
}, []);
|
|
356
|
+
|
|
357
|
+
const enable = useCallback(() => {
|
|
358
|
+
setIsEnabled(true);
|
|
359
|
+
}, []);
|
|
360
|
+
|
|
361
|
+
const disable = useCallback(() => {
|
|
362
|
+
setIsEnabled(false);
|
|
363
|
+
}, []);
|
|
364
|
+
|
|
365
|
+
return useMemo(
|
|
366
|
+
() => ({
|
|
367
|
+
isAvailable,
|
|
368
|
+
isEnabled,
|
|
369
|
+
notify,
|
|
370
|
+
notifyTaskComplete,
|
|
371
|
+
notifyPermissionRequest,
|
|
372
|
+
notifyError,
|
|
373
|
+
toggle,
|
|
374
|
+
enable,
|
|
375
|
+
disable,
|
|
376
|
+
}),
|
|
377
|
+
[
|
|
378
|
+
isAvailable,
|
|
379
|
+
isEnabled,
|
|
380
|
+
notify,
|
|
381
|
+
notifyTaskComplete,
|
|
382
|
+
notifyPermissionRequest,
|
|
383
|
+
notifyError,
|
|
384
|
+
toggle,
|
|
385
|
+
enable,
|
|
386
|
+
disable,
|
|
387
|
+
]
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// =============================================================================
|
|
392
|
+
// Utility Functions
|
|
393
|
+
// =============================================================================
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Format duration in milliseconds to human-readable string.
|
|
397
|
+
*/
|
|
398
|
+
function formatDuration(ms: number): string {
|
|
399
|
+
if (ms < 1000) {
|
|
400
|
+
return `${ms}ms`;
|
|
401
|
+
}
|
|
402
|
+
const seconds = Math.floor(ms / 1000);
|
|
403
|
+
if (seconds < 60) {
|
|
404
|
+
return `${seconds}s`;
|
|
405
|
+
}
|
|
406
|
+
const minutes = Math.floor(seconds / 60);
|
|
407
|
+
const remainingSeconds = seconds % 60;
|
|
408
|
+
if (minutes < 60) {
|
|
409
|
+
return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
|
|
410
|
+
}
|
|
411
|
+
const hours = Math.floor(minutes / 60);
|
|
412
|
+
const remainingMinutes = minutes % 60;
|
|
413
|
+
return `${hours}h ${remainingMinutes}m`;
|
|
414
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDiffMode Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for subscribing to diff view mode changes.
|
|
5
|
+
* Connects DiffView components to the global diff mode state.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/hooks/useDiffMode
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
|
+
import { getDiffMode, subscribeDiffMode } from "../../commands/diff-mode.js";
|
|
12
|
+
import type { DiffViewMode } from "../i18n/index.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Hook return type for diff mode state.
|
|
16
|
+
*/
|
|
17
|
+
export interface UseDiffModeReturn {
|
|
18
|
+
/** Current diff view mode */
|
|
19
|
+
readonly mode: DiffViewMode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* React hook for subscribing to diff view mode changes.
|
|
24
|
+
*
|
|
25
|
+
* @returns Current diff view mode that updates on change
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* function MyDiffComponent({ diff }: { diff: string }) {
|
|
30
|
+
* const { mode } = useDiffMode();
|
|
31
|
+
* return <DiffView diff={diff} mode={mode} />;
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function useDiffMode(): UseDiffModeReturn {
|
|
36
|
+
const [mode, setMode] = useState<DiffViewMode>(getDiffMode());
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
// Subscribe to mode changes and return unsubscribe function
|
|
40
|
+
return subscribeDiffMode(setMode);
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
return { mode };
|
|
44
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Change Statistics Hook
|
|
3
|
+
*
|
|
4
|
+
* Aggregates diff metadata from tool executions to provide
|
|
5
|
+
* cumulative statistics about file changes in the current session.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/hooks/useFileChangeStats
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useMemo } from "react";
|
|
11
|
+
import { useTools } from "../context/ToolsContext.js";
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Types
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Diff metadata from tool results (matches @vellum/core DiffMetadata)
|
|
19
|
+
*/
|
|
20
|
+
interface DiffMetadata {
|
|
21
|
+
/** Unified diff string */
|
|
22
|
+
diff: string;
|
|
23
|
+
/** Number of lines added */
|
|
24
|
+
additions: number;
|
|
25
|
+
/** Number of lines deleted */
|
|
26
|
+
deletions: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Aggregated file change statistics
|
|
31
|
+
*/
|
|
32
|
+
export interface FileChangeStats {
|
|
33
|
+
/** Total number of lines added across all files */
|
|
34
|
+
readonly additions: number;
|
|
35
|
+
/** Total number of lines deleted across all files */
|
|
36
|
+
readonly deletions: number;
|
|
37
|
+
/** Number of unique files modified */
|
|
38
|
+
readonly filesModified: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Type Guards
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Type guard for DiffMetadata
|
|
47
|
+
*/
|
|
48
|
+
function isDiffMetadata(value: unknown): value is DiffMetadata {
|
|
49
|
+
if (!value || typeof value !== "object") return false;
|
|
50
|
+
const obj = value as Record<string, unknown>;
|
|
51
|
+
return (
|
|
52
|
+
typeof obj.additions === "number" &&
|
|
53
|
+
typeof obj.deletions === "number" &&
|
|
54
|
+
typeof obj.diff === "string"
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// Hook
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Aggregates file change statistics from completed tool executions.
|
|
64
|
+
*
|
|
65
|
+
* Extracts `diffMeta` from tool results and accumulates additions/deletions.
|
|
66
|
+
* Also tracks unique file paths modified.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* const { additions, deletions, filesModified } = useFileChangeStats();
|
|
71
|
+
* // additions: 42, deletions: 15, filesModified: 3
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export function useFileChangeStats(): FileChangeStats {
|
|
75
|
+
const { executions } = useTools();
|
|
76
|
+
|
|
77
|
+
return useMemo(() => {
|
|
78
|
+
let additions = 0;
|
|
79
|
+
let deletions = 0;
|
|
80
|
+
const paths = new Set<string>();
|
|
81
|
+
|
|
82
|
+
for (const exec of executions) {
|
|
83
|
+
// Only count completed executions
|
|
84
|
+
if (exec.status !== "complete") continue;
|
|
85
|
+
|
|
86
|
+
// Tool results may have diffMeta
|
|
87
|
+
const result = exec.result as Record<string, unknown> | undefined;
|
|
88
|
+
if (!result) continue;
|
|
89
|
+
|
|
90
|
+
// Extract diff metadata if present
|
|
91
|
+
const diffMeta = result.diffMeta;
|
|
92
|
+
if (isDiffMetadata(diffMeta)) {
|
|
93
|
+
additions += diffMeta.additions;
|
|
94
|
+
deletions += diffMeta.deletions;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Track unique file paths
|
|
98
|
+
const path = result.path;
|
|
99
|
+
if (typeof path === "string" && path.length > 0) {
|
|
100
|
+
paths.add(path);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
additions,
|
|
106
|
+
deletions,
|
|
107
|
+
filesModified: paths.size,
|
|
108
|
+
};
|
|
109
|
+
}, [executions]);
|
|
110
|
+
}
|