@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,440 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SnapshotCheckpointPanel Component
|
|
3
|
+
*
|
|
4
|
+
* React Ink component for managing file state checkpoints using the Snapshot system.
|
|
5
|
+
* Displays recent snapshots with keyboard navigation for viewing, restoring, and creating.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/Checkpoint/SnapshotCheckpointPanel
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { SnapshotInfo } from "@vellum/core";
|
|
11
|
+
import { Box, Text, useInput } from "ink";
|
|
12
|
+
import type React from "react";
|
|
13
|
+
import { useCallback, useMemo, useState } from "react";
|
|
14
|
+
import { useTheme } from "../../theme/index.js";
|
|
15
|
+
import { truncateToDisplayWidth } from "../../utils/index.js";
|
|
16
|
+
import { HotkeyHints } from "../common/HotkeyHints.js";
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Props for the SnapshotCheckpointPanel component.
|
|
24
|
+
*/
|
|
25
|
+
export interface SnapshotCheckpointPanelProps {
|
|
26
|
+
/** List of snapshots to display */
|
|
27
|
+
readonly snapshots: readonly SnapshotInfo[];
|
|
28
|
+
/** Whether snapshots are loading */
|
|
29
|
+
readonly isLoading: boolean;
|
|
30
|
+
/** Error message if any */
|
|
31
|
+
readonly error: string | null;
|
|
32
|
+
/** Whether the snapshot system is initialized */
|
|
33
|
+
readonly isInitialized: boolean;
|
|
34
|
+
/** Maximum height in lines */
|
|
35
|
+
readonly maxHeight?: number;
|
|
36
|
+
/** Whether the panel is focused for keyboard input */
|
|
37
|
+
readonly isFocused?: boolean;
|
|
38
|
+
/** Callback when restore is requested */
|
|
39
|
+
readonly onRestore?: (hash: string) => void;
|
|
40
|
+
/** Callback when diff is requested */
|
|
41
|
+
readonly onDiff?: (hash: string) => void;
|
|
42
|
+
/** Callback when new checkpoint is requested */
|
|
43
|
+
readonly onTakeCheckpoint?: () => void;
|
|
44
|
+
/** Callback to refresh the list */
|
|
45
|
+
readonly onRefresh?: () => void;
|
|
46
|
+
/** Callback when a snapshot is selected */
|
|
47
|
+
readonly onSelect?: (snapshot: SnapshotInfo | null) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Constants
|
|
52
|
+
// =============================================================================
|
|
53
|
+
|
|
54
|
+
/** Default maximum height */
|
|
55
|
+
const DEFAULT_MAX_HEIGHT = 12;
|
|
56
|
+
|
|
57
|
+
/** Number of items per page */
|
|
58
|
+
const PAGE_SIZE = 5;
|
|
59
|
+
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// Helper Functions
|
|
62
|
+
// =============================================================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Format relative time from a date.
|
|
66
|
+
*/
|
|
67
|
+
function formatRelativeTime(date: Date): string {
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const diff = now - date.getTime();
|
|
70
|
+
|
|
71
|
+
const seconds = Math.floor(diff / 1000);
|
|
72
|
+
const minutes = Math.floor(seconds / 60);
|
|
73
|
+
const hours = Math.floor(minutes / 60);
|
|
74
|
+
const days = Math.floor(hours / 24);
|
|
75
|
+
|
|
76
|
+
if (days > 0) {
|
|
77
|
+
return `${days}d ago`;
|
|
78
|
+
}
|
|
79
|
+
if (hours > 0) {
|
|
80
|
+
return `${hours}h ago`;
|
|
81
|
+
}
|
|
82
|
+
if (minutes > 0) {
|
|
83
|
+
return `${minutes}m ago`;
|
|
84
|
+
}
|
|
85
|
+
return "just now";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Truncate text to max width.
|
|
90
|
+
*/
|
|
91
|
+
function truncate(text: string, maxLength: number): string {
|
|
92
|
+
return truncateToDisplayWidth(text, maxLength);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get visible entries based on scroll position.
|
|
97
|
+
*/
|
|
98
|
+
function getVisibleEntries<T>(
|
|
99
|
+
entries: readonly T[],
|
|
100
|
+
scrollOffset: number,
|
|
101
|
+
maxVisible: number
|
|
102
|
+
): readonly T[] {
|
|
103
|
+
return entries.slice(scrollOffset, scrollOffset + maxVisible);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// =============================================================================
|
|
107
|
+
// SnapshotItem Component
|
|
108
|
+
// =============================================================================
|
|
109
|
+
|
|
110
|
+
interface SnapshotItemProps {
|
|
111
|
+
readonly snapshot: SnapshotInfo;
|
|
112
|
+
readonly index: number;
|
|
113
|
+
readonly isSelected: boolean;
|
|
114
|
+
readonly width: number;
|
|
115
|
+
readonly primaryColor: string;
|
|
116
|
+
readonly mutedColor: string;
|
|
117
|
+
readonly textColor: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function SnapshotItem({
|
|
121
|
+
snapshot,
|
|
122
|
+
index,
|
|
123
|
+
isSelected,
|
|
124
|
+
width,
|
|
125
|
+
primaryColor,
|
|
126
|
+
mutedColor,
|
|
127
|
+
textColor,
|
|
128
|
+
}: SnapshotItemProps): React.JSX.Element {
|
|
129
|
+
const indicator = isSelected ? "[>]" : `[${index + 1}]`;
|
|
130
|
+
const hashShort = snapshot.hash.slice(0, 7);
|
|
131
|
+
const timeStr = formatRelativeTime(snapshot.timestamp);
|
|
132
|
+
const message = snapshot.message ?? "(no message)";
|
|
133
|
+
const fileCount = snapshot.files.length;
|
|
134
|
+
|
|
135
|
+
// Calculate available width for message
|
|
136
|
+
const fixedWidth = 5 + 8 + 3 + 10 + 3; // indicator + hash + spacing + time + spacing
|
|
137
|
+
const messageWidth = Math.max(width - fixedWidth - 4, 10);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<Box flexDirection="column" paddingLeft={1}>
|
|
141
|
+
<Box flexDirection="row">
|
|
142
|
+
<Text color={isSelected ? primaryColor : mutedColor} bold={isSelected}>
|
|
143
|
+
{indicator}
|
|
144
|
+
</Text>
|
|
145
|
+
<Text> </Text>
|
|
146
|
+
<Text color={isSelected ? primaryColor : textColor} bold={isSelected}>
|
|
147
|
+
{hashShort}
|
|
148
|
+
</Text>
|
|
149
|
+
<Text color={mutedColor}> - </Text>
|
|
150
|
+
<Text color={mutedColor}>{timeStr}</Text>
|
|
151
|
+
<Text color={mutedColor}> - </Text>
|
|
152
|
+
<Text color={isSelected ? textColor : mutedColor} dimColor={!isSelected}>
|
|
153
|
+
"{truncate(message, messageWidth)}"
|
|
154
|
+
</Text>
|
|
155
|
+
</Box>
|
|
156
|
+
<Box paddingLeft={5}>
|
|
157
|
+
<Text color={mutedColor} dimColor>
|
|
158
|
+
Files: {fileCount} changed
|
|
159
|
+
</Text>
|
|
160
|
+
</Box>
|
|
161
|
+
</Box>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// =============================================================================
|
|
166
|
+
// Main Component
|
|
167
|
+
// =============================================================================
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* SnapshotCheckpointPanel - Displays file state checkpoints from the Snapshot system.
|
|
171
|
+
*
|
|
172
|
+
* Features:
|
|
173
|
+
* - List recent checkpoints (limit 10)
|
|
174
|
+
* - Show relative time, message, file count
|
|
175
|
+
* - Keyboard navigation: j/k or arrows
|
|
176
|
+
* - r = restore selected
|
|
177
|
+
* - d = show diff
|
|
178
|
+
* - n = take new checkpoint
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```tsx
|
|
182
|
+
* <SnapshotCheckpointPanel
|
|
183
|
+
* snapshots={snapshots}
|
|
184
|
+
* isLoading={false}
|
|
185
|
+
* error={null}
|
|
186
|
+
* isInitialized={true}
|
|
187
|
+
* isFocused={true}
|
|
188
|
+
* onRestore={(hash) => restore(hash)}
|
|
189
|
+
* onDiff={(hash) => showDiff(hash)}
|
|
190
|
+
* onTakeCheckpoint={() => takeCheckpoint()}
|
|
191
|
+
* />
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
export function SnapshotCheckpointPanel({
|
|
195
|
+
snapshots,
|
|
196
|
+
isLoading,
|
|
197
|
+
error,
|
|
198
|
+
isInitialized,
|
|
199
|
+
maxHeight = DEFAULT_MAX_HEIGHT,
|
|
200
|
+
isFocused = true,
|
|
201
|
+
onRestore,
|
|
202
|
+
onDiff,
|
|
203
|
+
onTakeCheckpoint,
|
|
204
|
+
onRefresh,
|
|
205
|
+
onSelect,
|
|
206
|
+
}: SnapshotCheckpointPanelProps): React.JSX.Element {
|
|
207
|
+
const { theme } = useTheme();
|
|
208
|
+
|
|
209
|
+
// State
|
|
210
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
211
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
212
|
+
|
|
213
|
+
// Colors from theme
|
|
214
|
+
const primaryColor = theme.brand.primary;
|
|
215
|
+
const mutedColor = theme.semantic.text.muted;
|
|
216
|
+
const textColor = theme.semantic.text.primary;
|
|
217
|
+
const borderColor = theme.semantic.border.default;
|
|
218
|
+
const errorColor = theme.colors.error;
|
|
219
|
+
|
|
220
|
+
// Calculate visible items (account for header and hints)
|
|
221
|
+
const maxVisible = Math.max(1, Math.floor((maxHeight - 5) / 2)); // Each item takes 2 lines
|
|
222
|
+
|
|
223
|
+
// Get visible snapshots
|
|
224
|
+
const visibleSnapshots = useMemo(
|
|
225
|
+
() => getVisibleEntries(snapshots, scrollOffset, maxVisible),
|
|
226
|
+
[snapshots, scrollOffset, maxVisible]
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Scroll indicators
|
|
230
|
+
const canScrollUp = scrollOffset > 0;
|
|
231
|
+
const canScrollDown = scrollOffset + maxVisible < snapshots.length;
|
|
232
|
+
|
|
233
|
+
// Hotkey hints
|
|
234
|
+
const hints = useMemo(
|
|
235
|
+
() => [
|
|
236
|
+
{ keys: "j/k", label: "navigate" },
|
|
237
|
+
{ keys: "r", label: "restore" },
|
|
238
|
+
{ keys: "d", label: "diff" },
|
|
239
|
+
{ keys: "n", label: "new" },
|
|
240
|
+
],
|
|
241
|
+
[]
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Navigate to a specific index.
|
|
246
|
+
*/
|
|
247
|
+
const navigateToIndex = useCallback(
|
|
248
|
+
(newIndex: number) => {
|
|
249
|
+
const maxIndex = Math.max(0, snapshots.length - 1);
|
|
250
|
+
const clampedIndex = Math.max(0, Math.min(newIndex, maxIndex));
|
|
251
|
+
setSelectedIndex(clampedIndex);
|
|
252
|
+
|
|
253
|
+
// Adjust scroll to keep selection visible
|
|
254
|
+
if (clampedIndex < scrollOffset) {
|
|
255
|
+
setScrollOffset(clampedIndex);
|
|
256
|
+
} else if (clampedIndex >= scrollOffset + maxVisible) {
|
|
257
|
+
setScrollOffset(clampedIndex - maxVisible + 1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Notify parent
|
|
261
|
+
const selectedSnapshot = snapshots[clampedIndex] ?? null;
|
|
262
|
+
onSelect?.(selectedSnapshot);
|
|
263
|
+
},
|
|
264
|
+
[snapshots, scrollOffset, maxVisible, onSelect]
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
// Handle keyboard input
|
|
268
|
+
useInput(
|
|
269
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Input handler must process multiple key bindings for navigation and actions
|
|
270
|
+
(input, key) => {
|
|
271
|
+
if (!isFocused) return;
|
|
272
|
+
|
|
273
|
+
// Navigation down
|
|
274
|
+
if (input === "j" || key.downArrow) {
|
|
275
|
+
navigateToIndex(selectedIndex + 1);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Navigation up
|
|
280
|
+
if (input === "k" || key.upArrow) {
|
|
281
|
+
navigateToIndex(selectedIndex - 1);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Page down
|
|
286
|
+
if (key.pageDown || (key.ctrl && input === "d")) {
|
|
287
|
+
navigateToIndex(selectedIndex + PAGE_SIZE);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Page up
|
|
292
|
+
if (key.pageUp || (key.ctrl && input === "u")) {
|
|
293
|
+
navigateToIndex(selectedIndex - PAGE_SIZE);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Restore
|
|
298
|
+
if (input === "r" && snapshots.length > 0) {
|
|
299
|
+
const snapshot = snapshots[selectedIndex];
|
|
300
|
+
if (snapshot) {
|
|
301
|
+
onRestore?.(snapshot.hash);
|
|
302
|
+
}
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Diff
|
|
307
|
+
if (input === "d" && snapshots.length > 0) {
|
|
308
|
+
const snapshot = snapshots[selectedIndex];
|
|
309
|
+
if (snapshot) {
|
|
310
|
+
onDiff?.(snapshot.hash);
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// New checkpoint
|
|
316
|
+
if (input === "n") {
|
|
317
|
+
onTakeCheckpoint?.();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Refresh
|
|
322
|
+
if (input === "R") {
|
|
323
|
+
onRefresh?.();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
{ isActive: isFocused }
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// Loading state
|
|
331
|
+
if (isLoading) {
|
|
332
|
+
return (
|
|
333
|
+
<Box flexDirection="column" borderStyle="single" borderColor={borderColor} paddingX={1}>
|
|
334
|
+
<Box>
|
|
335
|
+
<Text color={primaryColor} bold>
|
|
336
|
+
[*] Checkpoints
|
|
337
|
+
</Text>
|
|
338
|
+
</Box>
|
|
339
|
+
<Box paddingY={1}>
|
|
340
|
+
<Text color={mutedColor}>Loading...</Text>
|
|
341
|
+
</Box>
|
|
342
|
+
</Box>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Error state
|
|
347
|
+
if (error) {
|
|
348
|
+
return (
|
|
349
|
+
<Box flexDirection="column" borderStyle="single" borderColor={borderColor} paddingX={1}>
|
|
350
|
+
<Box>
|
|
351
|
+
<Text color={primaryColor} bold>
|
|
352
|
+
[*] Checkpoints
|
|
353
|
+
</Text>
|
|
354
|
+
</Box>
|
|
355
|
+
<Box paddingY={1}>
|
|
356
|
+
<Text color={errorColor}>Error: {error}</Text>
|
|
357
|
+
</Box>
|
|
358
|
+
<HotkeyHints hints={[{ keys: "n", label: "initialize" }]} />
|
|
359
|
+
</Box>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Not initialized state
|
|
364
|
+
if (!isInitialized) {
|
|
365
|
+
return (
|
|
366
|
+
<Box flexDirection="column" borderStyle="single" borderColor={borderColor} paddingX={1}>
|
|
367
|
+
<Box>
|
|
368
|
+
<Text color={primaryColor} bold>
|
|
369
|
+
[*] Checkpoints
|
|
370
|
+
</Text>
|
|
371
|
+
</Box>
|
|
372
|
+
<Box paddingY={1}>
|
|
373
|
+
<Text color={mutedColor}>Not initialized. Press 'n' to create first checkpoint.</Text>
|
|
374
|
+
</Box>
|
|
375
|
+
<HotkeyHints hints={[{ keys: "n", label: "new checkpoint" }]} />
|
|
376
|
+
</Box>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Empty state
|
|
381
|
+
if (snapshots.length === 0) {
|
|
382
|
+
return (
|
|
383
|
+
<Box flexDirection="column" borderStyle="single" borderColor={borderColor} paddingX={1}>
|
|
384
|
+
<Box>
|
|
385
|
+
<Text color={primaryColor} bold>
|
|
386
|
+
[*] Checkpoints (0)
|
|
387
|
+
</Text>
|
|
388
|
+
</Box>
|
|
389
|
+
<Box paddingY={1}>
|
|
390
|
+
<Text color={mutedColor}>No checkpoints yet. Press 'n' to create one.</Text>
|
|
391
|
+
</Box>
|
|
392
|
+
<HotkeyHints hints={[{ keys: "n", label: "new checkpoint" }]} />
|
|
393
|
+
</Box>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return (
|
|
398
|
+
<Box flexDirection="column" borderStyle="single" borderColor={borderColor} paddingX={1}>
|
|
399
|
+
{/* Header */}
|
|
400
|
+
<Box marginBottom={1}>
|
|
401
|
+
<Text color={primaryColor} bold>
|
|
402
|
+
[*] Checkpoints ({snapshots.length})
|
|
403
|
+
</Text>
|
|
404
|
+
{canScrollUp && <Text color={mutedColor}> [-] more above</Text>}
|
|
405
|
+
{canScrollDown && <Text color={mutedColor}> [+] more below</Text>}
|
|
406
|
+
</Box>
|
|
407
|
+
|
|
408
|
+
{/* Separator */}
|
|
409
|
+
<Box>
|
|
410
|
+
<Text color={borderColor}>{"─".repeat(40)}</Text>
|
|
411
|
+
</Box>
|
|
412
|
+
|
|
413
|
+
{/* Snapshot list */}
|
|
414
|
+
<Box flexDirection="column">
|
|
415
|
+
{visibleSnapshots.map((snapshot, visibleIdx) => {
|
|
416
|
+
const actualIndex = scrollOffset + visibleIdx;
|
|
417
|
+
return (
|
|
418
|
+
<SnapshotItem
|
|
419
|
+
key={snapshot.hash}
|
|
420
|
+
snapshot={snapshot}
|
|
421
|
+
index={actualIndex}
|
|
422
|
+
isSelected={actualIndex === selectedIndex}
|
|
423
|
+
width={60}
|
|
424
|
+
primaryColor={primaryColor}
|
|
425
|
+
mutedColor={mutedColor}
|
|
426
|
+
textColor={textColor}
|
|
427
|
+
/>
|
|
428
|
+
);
|
|
429
|
+
})}
|
|
430
|
+
</Box>
|
|
431
|
+
|
|
432
|
+
{/* Hints */}
|
|
433
|
+
<Box marginTop={1}>
|
|
434
|
+
<HotkeyHints hints={hints} />
|
|
435
|
+
</Box>
|
|
436
|
+
</Box>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export default SnapshotCheckpointPanel;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkpoint Components
|
|
3
|
+
*
|
|
4
|
+
* React Ink components for managing file state checkpoints.
|
|
5
|
+
* Uses the Snapshot system (shadow Git repository) for tracking file states.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/Checkpoint
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type { CheckpointDiffViewProps } from "./CheckpointDiffView.js";
|
|
11
|
+
export {
|
|
12
|
+
CheckpointDiffView,
|
|
13
|
+
default as CheckpointDiffViewDefault,
|
|
14
|
+
} from "./CheckpointDiffView.js";
|
|
15
|
+
export type { SnapshotCheckpointPanelProps } from "./SnapshotCheckpointPanel.js";
|
|
16
|
+
export {
|
|
17
|
+
default as SnapshotCheckpointPanelDefault,
|
|
18
|
+
SnapshotCheckpointPanel,
|
|
19
|
+
} from "./SnapshotCheckpointPanel.js";
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CostDisplay Component (Phase 35)
|
|
3
|
+
*
|
|
4
|
+
* React Ink component for displaying current session cost information.
|
|
5
|
+
* Shows token usage and cost in USD.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/components/CostDisplay
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CostBreakdown } from "@vellum/core";
|
|
11
|
+
import { formatCost, formatTokenCount } from "@vellum/core";
|
|
12
|
+
import { getIcons } from "@vellum/shared";
|
|
13
|
+
import { Box, Text } from "ink";
|
|
14
|
+
import { useTUITranslation } from "../i18n/index.js";
|
|
15
|
+
import { useTheme } from "../theme/index.js";
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Types
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Props for the CostDisplay component.
|
|
23
|
+
*/
|
|
24
|
+
export interface CostDisplayProps {
|
|
25
|
+
/** Total input tokens used */
|
|
26
|
+
readonly inputTokens: number;
|
|
27
|
+
/** Total output tokens generated */
|
|
28
|
+
readonly outputTokens: number;
|
|
29
|
+
/** Total cost in USD */
|
|
30
|
+
readonly totalCost: number;
|
|
31
|
+
/** Optional detailed cost breakdown */
|
|
32
|
+
readonly breakdown?: CostBreakdown;
|
|
33
|
+
/** Whether to show compact view (default: false) */
|
|
34
|
+
readonly compact?: boolean;
|
|
35
|
+
/** Whether to show breakdown details (default: false) */
|
|
36
|
+
readonly showBreakdown?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// CostDisplay Component
|
|
41
|
+
// =============================================================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* CostDisplay - Shows current session cost and token usage.
|
|
45
|
+
*
|
|
46
|
+
* Features:
|
|
47
|
+
* - Compact mode: Single line with total tokens and cost
|
|
48
|
+
* - Full mode: Multi-line with detailed breakdown
|
|
49
|
+
* - Color-coded based on cost magnitude
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* <CostDisplay
|
|
54
|
+
* inputTokens={1500}
|
|
55
|
+
* outputTokens={800}
|
|
56
|
+
* totalCost={0.0045}
|
|
57
|
+
* />
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function CostDisplay({
|
|
61
|
+
inputTokens,
|
|
62
|
+
outputTokens,
|
|
63
|
+
totalCost,
|
|
64
|
+
breakdown,
|
|
65
|
+
compact = false,
|
|
66
|
+
showBreakdown = false,
|
|
67
|
+
}: CostDisplayProps): React.JSX.Element {
|
|
68
|
+
const { theme } = useTheme();
|
|
69
|
+
const { t } = useTUITranslation();
|
|
70
|
+
const icons = getIcons();
|
|
71
|
+
|
|
72
|
+
// Determine cost color based on magnitude
|
|
73
|
+
const costColor = getCostColor(totalCost, theme);
|
|
74
|
+
|
|
75
|
+
// Total tokens
|
|
76
|
+
const totalTokens = inputTokens + outputTokens;
|
|
77
|
+
|
|
78
|
+
if (compact) {
|
|
79
|
+
return (
|
|
80
|
+
<Box>
|
|
81
|
+
<Text dimColor>{icons.cost} </Text>
|
|
82
|
+
<Text color={theme.colors.muted}>
|
|
83
|
+
{formatTokenCount(totalTokens)} {t("cost.tokens")}
|
|
84
|
+
</Text>
|
|
85
|
+
<Text dimColor> • </Text>
|
|
86
|
+
<Text color={costColor} bold>
|
|
87
|
+
{formatCost(totalCost)}
|
|
88
|
+
</Text>
|
|
89
|
+
</Box>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Box flexDirection="column" paddingX={1}>
|
|
95
|
+
<Box marginBottom={1}>
|
|
96
|
+
<Text color={theme.colors.primary} bold>
|
|
97
|
+
{icons.cost} {t("cost.sessionCost")}
|
|
98
|
+
</Text>
|
|
99
|
+
</Box>
|
|
100
|
+
|
|
101
|
+
{/* Token Summary */}
|
|
102
|
+
<Box flexDirection="column" marginLeft={2}>
|
|
103
|
+
<Box>
|
|
104
|
+
<Text color={theme.colors.muted}>{t("cost.input")}</Text>
|
|
105
|
+
<Text>{formatTokenCount(inputTokens)}</Text>
|
|
106
|
+
<Text color={theme.colors.muted}> {t("cost.tokens")}</Text>
|
|
107
|
+
</Box>
|
|
108
|
+
<Box>
|
|
109
|
+
<Text color={theme.colors.muted}>{t("cost.output")}</Text>
|
|
110
|
+
<Text>{formatTokenCount(outputTokens)}</Text>
|
|
111
|
+
<Text color={theme.colors.muted}> {t("cost.tokens")}</Text>
|
|
112
|
+
</Box>
|
|
113
|
+
<Box marginTop={1}>
|
|
114
|
+
<Text color={theme.colors.muted}>{t("cost.total")}</Text>
|
|
115
|
+
<Text bold>{formatTokenCount(totalTokens)}</Text>
|
|
116
|
+
<Text color={theme.colors.muted}> {t("cost.tokens")}</Text>
|
|
117
|
+
</Box>
|
|
118
|
+
</Box>
|
|
119
|
+
|
|
120
|
+
{/* Cost Breakdown */}
|
|
121
|
+
{showBreakdown && breakdown && (
|
|
122
|
+
<Box flexDirection="column" marginLeft={2} marginTop={1}>
|
|
123
|
+
<Text color={theme.colors.muted} dimColor>
|
|
124
|
+
─────────────────
|
|
125
|
+
</Text>
|
|
126
|
+
{breakdown.input > 0 && (
|
|
127
|
+
<Box>
|
|
128
|
+
<Text color={theme.colors.muted}>{t("cost.input")}</Text>
|
|
129
|
+
<Text>{formatCost(breakdown.input)}</Text>
|
|
130
|
+
</Box>
|
|
131
|
+
)}
|
|
132
|
+
{breakdown.output > 0 && (
|
|
133
|
+
<Box>
|
|
134
|
+
<Text color={theme.colors.muted}>{t("cost.output")}</Text>
|
|
135
|
+
<Text>{formatCost(breakdown.output)}</Text>
|
|
136
|
+
</Box>
|
|
137
|
+
)}
|
|
138
|
+
{breakdown.cacheRead > 0 && (
|
|
139
|
+
<Box>
|
|
140
|
+
<Text color={theme.colors.muted}>{t("cost.cacheRead")}</Text>
|
|
141
|
+
<Text>{formatCost(breakdown.cacheRead)}</Text>
|
|
142
|
+
</Box>
|
|
143
|
+
)}
|
|
144
|
+
{breakdown.cacheWrite > 0 && (
|
|
145
|
+
<Box>
|
|
146
|
+
<Text color={theme.colors.muted}>{t("cost.cacheWrite")}</Text>
|
|
147
|
+
<Text>{formatCost(breakdown.cacheWrite)}</Text>
|
|
148
|
+
</Box>
|
|
149
|
+
)}
|
|
150
|
+
{breakdown.reasoning > 0 && (
|
|
151
|
+
<Box>
|
|
152
|
+
<Text color={theme.colors.muted}>{t("cost.reasoning")}</Text>
|
|
153
|
+
<Text>{formatCost(breakdown.reasoning)}</Text>
|
|
154
|
+
</Box>
|
|
155
|
+
)}
|
|
156
|
+
</Box>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
{/* Total Cost */}
|
|
160
|
+
<Box marginLeft={2} marginTop={1}>
|
|
161
|
+
<Text color={theme.colors.muted} dimColor>
|
|
162
|
+
─────────────────
|
|
163
|
+
</Text>
|
|
164
|
+
</Box>
|
|
165
|
+
<Box marginLeft={2}>
|
|
166
|
+
<Text color={theme.colors.muted}>{t("cost.cost")}</Text>
|
|
167
|
+
<Text color={costColor} bold>
|
|
168
|
+
{formatCost(totalCost)}
|
|
169
|
+
</Text>
|
|
170
|
+
</Box>
|
|
171
|
+
</Box>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// =============================================================================
|
|
176
|
+
// Helper Functions
|
|
177
|
+
// =============================================================================
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get appropriate color for cost display based on magnitude.
|
|
181
|
+
*/
|
|
182
|
+
function getCostColor(cost: number, theme: ReturnType<typeof useTheme>["theme"]): string {
|
|
183
|
+
if (cost === 0) {
|
|
184
|
+
return theme.colors.muted;
|
|
185
|
+
}
|
|
186
|
+
if (cost < 0.01) {
|
|
187
|
+
return theme.colors.success; // Green - very low cost
|
|
188
|
+
}
|
|
189
|
+
if (cost < 0.1) {
|
|
190
|
+
return theme.colors.info; // Blue - moderate cost
|
|
191
|
+
}
|
|
192
|
+
if (cost < 1.0) {
|
|
193
|
+
return theme.colors.warning; // Yellow - getting expensive
|
|
194
|
+
}
|
|
195
|
+
return theme.colors.error; // Red - expensive
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// =============================================================================
|
|
199
|
+
// Compact Cost Badge
|
|
200
|
+
// =============================================================================
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Props for the CostBadge component.
|
|
204
|
+
*/
|
|
205
|
+
export interface CostBadgeProps {
|
|
206
|
+
/** Total cost in USD */
|
|
207
|
+
readonly cost: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* CostBadge - Minimal cost display for status bars.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```tsx
|
|
215
|
+
* <CostBadge cost={0.0045} />
|
|
216
|
+
* // Renders: $0.0045
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
export function CostBadge({ cost }: CostBadgeProps): React.JSX.Element {
|
|
220
|
+
const { theme } = useTheme();
|
|
221
|
+
const costColor = getCostColor(cost, theme);
|
|
222
|
+
|
|
223
|
+
return <Text color={costColor}>{formatCost(cost)}</Text>;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export default CostDisplay;
|