@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,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePersistence Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for managing session persistence with auto-save,
|
|
5
|
+
* checkpoints, and rollback support. Integrates with @vellum/core
|
|
6
|
+
* PersistenceManager for advanced persistence features.
|
|
7
|
+
*
|
|
8
|
+
* @module tui/hooks/usePersistence
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { PersistenceManager, Session, SessionCheckpoint, StorageManager } from "@vellum/core";
|
|
12
|
+
import { PersistenceManager as PersistenceManagerClass } from "@vellum/core";
|
|
13
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
14
|
+
import {
|
|
15
|
+
createPersistenceBridge,
|
|
16
|
+
type PersistenceBridge,
|
|
17
|
+
type PersistenceBridgeCallbacks,
|
|
18
|
+
} from "../adapters/persistence-bridge.js";
|
|
19
|
+
import {
|
|
20
|
+
type SessionStorage,
|
|
21
|
+
type UseSessionAdapterOptions,
|
|
22
|
+
type UseSessionAdapterReturn,
|
|
23
|
+
useSessionAdapter,
|
|
24
|
+
} from "../adapters/session-adapter.js";
|
|
25
|
+
import { useMessages } from "../context/MessagesContext.js";
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Types
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Persistence status for UI display
|
|
33
|
+
*/
|
|
34
|
+
export type PersistenceStatus = "idle" | "saving" | "saved" | "error";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Options for the usePersistence hook
|
|
38
|
+
*/
|
|
39
|
+
export interface UsePersistenceOptions {
|
|
40
|
+
/** Session ID for persistence */
|
|
41
|
+
readonly sessionId: string;
|
|
42
|
+
/** Storage implementation for basic persistence */
|
|
43
|
+
readonly storage: SessionStorage;
|
|
44
|
+
/** Whether to enable advanced persistence features */
|
|
45
|
+
readonly enableAdvancedPersistence?: boolean;
|
|
46
|
+
/** Storage manager for advanced persistence (required if enableAdvancedPersistence=true) */
|
|
47
|
+
readonly storageManager?: StorageManager;
|
|
48
|
+
/** PersistenceManager instance (optional, created if storageManager provided) */
|
|
49
|
+
readonly persistenceManager?: PersistenceManager;
|
|
50
|
+
/** Whether to auto-save on message changes */
|
|
51
|
+
readonly autoSave?: boolean;
|
|
52
|
+
/** Debounce delay for auto-save in milliseconds */
|
|
53
|
+
readonly saveDebounceMs?: number;
|
|
54
|
+
/** Whether to auto-load session on mount */
|
|
55
|
+
readonly autoLoad?: boolean;
|
|
56
|
+
/** Callback when save completes */
|
|
57
|
+
readonly onSave?: (session: Session) => void;
|
|
58
|
+
/** Callback when save fails */
|
|
59
|
+
readonly onError?: (error: Error) => void;
|
|
60
|
+
/** Callback when checkpoint is created */
|
|
61
|
+
readonly onCheckpointCreated?: (checkpointId: string) => void;
|
|
62
|
+
/** Callback when rollback completes */
|
|
63
|
+
readonly onRollbackComplete?: (success: boolean) => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Return value of the usePersistence hook
|
|
68
|
+
*/
|
|
69
|
+
export interface UsePersistenceReturn extends UseSessionAdapterReturn {
|
|
70
|
+
/** Current persistence status */
|
|
71
|
+
readonly status: PersistenceStatus;
|
|
72
|
+
/** Number of unsaved messages */
|
|
73
|
+
readonly unsavedCount: number;
|
|
74
|
+
/** Whether auto-save is running */
|
|
75
|
+
readonly autoSaveRunning: boolean;
|
|
76
|
+
/** Timestamp of last successful save */
|
|
77
|
+
readonly lastSavedAt: Date | null;
|
|
78
|
+
/** All checkpoints for current session */
|
|
79
|
+
readonly checkpoints: readonly SessionCheckpoint[];
|
|
80
|
+
/** Create a new checkpoint */
|
|
81
|
+
readonly createCheckpoint: (description?: string) => Promise<string | null>;
|
|
82
|
+
/** Rollback to a checkpoint */
|
|
83
|
+
readonly rollbackToCheckpoint: (checkpointId: string) => Promise<boolean>;
|
|
84
|
+
/** Delete a checkpoint */
|
|
85
|
+
readonly deleteCheckpoint: (checkpointId: string) => Promise<boolean>;
|
|
86
|
+
/** Get messages that will be lost on rollback */
|
|
87
|
+
readonly getMessagesToLose: (checkpointId: string) => number;
|
|
88
|
+
/** Whether advanced persistence is enabled */
|
|
89
|
+
readonly isAdvancedEnabled: boolean;
|
|
90
|
+
/** Force an immediate save */
|
|
91
|
+
readonly forceSave: () => Promise<void>;
|
|
92
|
+
/** Start auto-save timer */
|
|
93
|
+
readonly startAutoSave: () => void;
|
|
94
|
+
/** Stop auto-save timer */
|
|
95
|
+
readonly stopAutoSave: () => void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// Hook Implementation
|
|
100
|
+
// =============================================================================
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* usePersistence - Hook for session persistence management.
|
|
104
|
+
*
|
|
105
|
+
* Combines basic session adapter functionality with advanced
|
|
106
|
+
* persistence features when PersistenceManager is available.
|
|
107
|
+
*
|
|
108
|
+
* Features:
|
|
109
|
+
* - Basic: Auto-save, manual save, load, clear
|
|
110
|
+
* - Advanced: Checkpoints, rollback, incremental sync
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```tsx
|
|
114
|
+
* function MyComponent() {
|
|
115
|
+
* const persistence = usePersistence({
|
|
116
|
+
* sessionId: 'session-123',
|
|
117
|
+
* storage: sessionStorage,
|
|
118
|
+
* enableAdvancedPersistence: true,
|
|
119
|
+
* storageManager: storageManager,
|
|
120
|
+
* onSave: (session) => console.log('Saved:', session.metadata.id),
|
|
121
|
+
* });
|
|
122
|
+
*
|
|
123
|
+
* return (
|
|
124
|
+
* <Box>
|
|
125
|
+
* <Text>Status: {persistence.status}</Text>
|
|
126
|
+
* <Text>Unsaved: {persistence.unsavedCount}</Text>
|
|
127
|
+
* <Button onClick={() => persistence.createCheckpoint('Manual save')}>
|
|
128
|
+
* Create Checkpoint
|
|
129
|
+
* </Button>
|
|
130
|
+
* </Box>
|
|
131
|
+
* );
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function usePersistence(options: UsePersistenceOptions): UsePersistenceReturn {
|
|
136
|
+
const {
|
|
137
|
+
sessionId,
|
|
138
|
+
storage,
|
|
139
|
+
enableAdvancedPersistence = false,
|
|
140
|
+
storageManager,
|
|
141
|
+
persistenceManager: externalPersistenceManager,
|
|
142
|
+
autoSave = true,
|
|
143
|
+
saveDebounceMs = 500,
|
|
144
|
+
autoLoad = true,
|
|
145
|
+
onSave,
|
|
146
|
+
onError,
|
|
147
|
+
onCheckpointCreated,
|
|
148
|
+
onRollbackComplete,
|
|
149
|
+
} = options;
|
|
150
|
+
|
|
151
|
+
// Get messages context
|
|
152
|
+
const { messages, setMessages } = useMessages();
|
|
153
|
+
|
|
154
|
+
// ==========================================================================
|
|
155
|
+
// State
|
|
156
|
+
// ==========================================================================
|
|
157
|
+
|
|
158
|
+
const [status, setStatus] = useState<PersistenceStatus>("idle");
|
|
159
|
+
const [unsavedCount, setUnsavedCount] = useState(0);
|
|
160
|
+
const [autoSaveRunning, setAutoSaveRunning] = useState(false);
|
|
161
|
+
const [lastSavedAt, setLastSavedAt] = useState<Date | null>(null);
|
|
162
|
+
const [checkpoints, setCheckpoints] = useState<readonly SessionCheckpoint[]>([]);
|
|
163
|
+
|
|
164
|
+
// ==========================================================================
|
|
165
|
+
// Refs
|
|
166
|
+
// ==========================================================================
|
|
167
|
+
|
|
168
|
+
const persistenceManagerRef = useRef<PersistenceManager | null>(
|
|
169
|
+
externalPersistenceManager ?? null
|
|
170
|
+
);
|
|
171
|
+
const bridgeRef = useRef<PersistenceBridge | null>(null);
|
|
172
|
+
const previousMessageCountRef = useRef(0);
|
|
173
|
+
|
|
174
|
+
// ==========================================================================
|
|
175
|
+
// Basic Session Adapter
|
|
176
|
+
// ==========================================================================
|
|
177
|
+
|
|
178
|
+
const sessionAdapterOptions: UseSessionAdapterOptions = useMemo(
|
|
179
|
+
() => ({
|
|
180
|
+
sessionId,
|
|
181
|
+
storage,
|
|
182
|
+
autoSave: autoSave && !enableAdvancedPersistence,
|
|
183
|
+
saveDebounceMs,
|
|
184
|
+
autoLoad,
|
|
185
|
+
}),
|
|
186
|
+
[sessionId, storage, autoSave, enableAdvancedPersistence, saveDebounceMs, autoLoad]
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const sessionAdapter = useSessionAdapter(sessionAdapterOptions);
|
|
190
|
+
|
|
191
|
+
// ==========================================================================
|
|
192
|
+
// Advanced Persistence Setup
|
|
193
|
+
// ==========================================================================
|
|
194
|
+
|
|
195
|
+
const isAdvancedEnabled = enableAdvancedPersistence && storageManager !== undefined;
|
|
196
|
+
|
|
197
|
+
// Create PersistenceManager and Bridge when advanced mode is enabled
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
if (!isAdvancedEnabled || !storageManager) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Use external manager or create new one
|
|
204
|
+
const manager =
|
|
205
|
+
externalPersistenceManager ??
|
|
206
|
+
new PersistenceManagerClass(storageManager, {
|
|
207
|
+
autoSaveEnabled: autoSave,
|
|
208
|
+
autoSaveIntervalSecs: Math.ceil(saveDebounceMs / 1000),
|
|
209
|
+
maxUnsavedMessages: 5,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
persistenceManagerRef.current = manager;
|
|
213
|
+
|
|
214
|
+
// Create bridge with callbacks
|
|
215
|
+
const callbacks: PersistenceBridgeCallbacks = {
|
|
216
|
+
onSave: (session) => {
|
|
217
|
+
setStatus("saved");
|
|
218
|
+
setLastSavedAt(new Date());
|
|
219
|
+
setUnsavedCount(0);
|
|
220
|
+
setCheckpoints(session.checkpoints);
|
|
221
|
+
onSave?.(session);
|
|
222
|
+
},
|
|
223
|
+
onError: (error) => {
|
|
224
|
+
setStatus("error");
|
|
225
|
+
onError?.(error);
|
|
226
|
+
},
|
|
227
|
+
onCheckpointCreated: (checkpointId) => {
|
|
228
|
+
const session = manager.currentSession;
|
|
229
|
+
if (session) {
|
|
230
|
+
setCheckpoints(session.checkpoints);
|
|
231
|
+
}
|
|
232
|
+
onCheckpointCreated?.(checkpointId);
|
|
233
|
+
},
|
|
234
|
+
onRollbackComplete: (success) => {
|
|
235
|
+
if (success) {
|
|
236
|
+
const session = manager.currentSession;
|
|
237
|
+
if (session) {
|
|
238
|
+
setCheckpoints(session.checkpoints);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
onRollbackComplete?.(success);
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const bridge = createPersistenceBridge({
|
|
246
|
+
persistence: manager,
|
|
247
|
+
callbacks,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
bridgeRef.current = bridge;
|
|
251
|
+
|
|
252
|
+
// Start auto-save if enabled
|
|
253
|
+
if (autoSave) {
|
|
254
|
+
manager.startAutoSave();
|
|
255
|
+
setAutoSaveRunning(true);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return () => {
|
|
259
|
+
bridge.dispose();
|
|
260
|
+
bridgeRef.current = null;
|
|
261
|
+
|
|
262
|
+
// Only dispose manager if we created it
|
|
263
|
+
if (!externalPersistenceManager) {
|
|
264
|
+
manager.dispose();
|
|
265
|
+
}
|
|
266
|
+
persistenceManagerRef.current = null;
|
|
267
|
+
setAutoSaveRunning(false);
|
|
268
|
+
};
|
|
269
|
+
}, [
|
|
270
|
+
isAdvancedEnabled,
|
|
271
|
+
storageManager,
|
|
272
|
+
externalPersistenceManager,
|
|
273
|
+
autoSave,
|
|
274
|
+
saveDebounceMs,
|
|
275
|
+
onSave,
|
|
276
|
+
onError,
|
|
277
|
+
onCheckpointCreated,
|
|
278
|
+
onRollbackComplete,
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
// Track unsaved message count
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
if (!isAdvancedEnabled) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const manager = persistenceManagerRef.current;
|
|
288
|
+
if (!manager) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const newCount = messages.length - previousMessageCountRef.current;
|
|
293
|
+
if (newCount > 0) {
|
|
294
|
+
setUnsavedCount((prev) => prev + newCount);
|
|
295
|
+
setStatus("idle");
|
|
296
|
+
}
|
|
297
|
+
previousMessageCountRef.current = messages.length;
|
|
298
|
+
}, [messages.length, isAdvancedEnabled]);
|
|
299
|
+
|
|
300
|
+
// ==========================================================================
|
|
301
|
+
// Checkpoint Operations
|
|
302
|
+
// ==========================================================================
|
|
303
|
+
|
|
304
|
+
const createCheckpoint = useCallback(
|
|
305
|
+
async (description?: string): Promise<string | null> => {
|
|
306
|
+
if (!isAdvancedEnabled) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const bridge = bridgeRef.current;
|
|
311
|
+
if (!bridge) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
setStatus("saving");
|
|
316
|
+
const checkpointId = await bridge.createCheckpoint(description);
|
|
317
|
+
if (!checkpointId) {
|
|
318
|
+
setStatus("error");
|
|
319
|
+
}
|
|
320
|
+
return checkpointId;
|
|
321
|
+
},
|
|
322
|
+
[isAdvancedEnabled]
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const rollbackToCheckpoint = useCallback(
|
|
326
|
+
async (checkpointId: string): Promise<boolean> => {
|
|
327
|
+
if (!isAdvancedEnabled) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const bridge = bridgeRef.current;
|
|
332
|
+
const manager = persistenceManagerRef.current;
|
|
333
|
+
if (!bridge || !manager) {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
setStatus("saving");
|
|
338
|
+
const success = await bridge.rollbackToCheckpoint(checkpointId);
|
|
339
|
+
|
|
340
|
+
if (success) {
|
|
341
|
+
// Update UI messages from rolled-back session
|
|
342
|
+
const uiMessages = bridge.loadAsUIMessages();
|
|
343
|
+
setMessages(uiMessages);
|
|
344
|
+
previousMessageCountRef.current = uiMessages.length;
|
|
345
|
+
setStatus("saved");
|
|
346
|
+
} else {
|
|
347
|
+
setStatus("error");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return success;
|
|
351
|
+
},
|
|
352
|
+
[isAdvancedEnabled, setMessages]
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
const deleteCheckpoint = useCallback(
|
|
356
|
+
async (checkpointId: string): Promise<boolean> => {
|
|
357
|
+
if (!isAdvancedEnabled) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const manager = persistenceManagerRef.current;
|
|
362
|
+
if (!manager) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const success = await manager.deleteCheckpoint(checkpointId);
|
|
368
|
+
if (success) {
|
|
369
|
+
setCheckpoints(manager.getCheckpoints());
|
|
370
|
+
}
|
|
371
|
+
return success;
|
|
372
|
+
} catch {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
[isAdvancedEnabled]
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const getMessagesToLose = useCallback(
|
|
380
|
+
(checkpointId: string): number => {
|
|
381
|
+
const checkpoint = checkpoints.find((cp) => cp.id === checkpointId);
|
|
382
|
+
if (!checkpoint) {
|
|
383
|
+
return 0;
|
|
384
|
+
}
|
|
385
|
+
return messages.length - checkpoint.messageIndex;
|
|
386
|
+
},
|
|
387
|
+
[checkpoints, messages.length]
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// ==========================================================================
|
|
391
|
+
// Save Operations
|
|
392
|
+
// ==========================================================================
|
|
393
|
+
|
|
394
|
+
const forceSave = useCallback(async (): Promise<void> => {
|
|
395
|
+
if (isAdvancedEnabled) {
|
|
396
|
+
const manager = persistenceManagerRef.current;
|
|
397
|
+
if (!manager) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
setStatus("saving");
|
|
402
|
+
try {
|
|
403
|
+
await manager.save();
|
|
404
|
+
setStatus("saved");
|
|
405
|
+
setLastSavedAt(new Date());
|
|
406
|
+
setUnsavedCount(0);
|
|
407
|
+
} catch {
|
|
408
|
+
setStatus("error");
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
await sessionAdapter.saveSession();
|
|
412
|
+
}
|
|
413
|
+
}, [isAdvancedEnabled, sessionAdapter]);
|
|
414
|
+
|
|
415
|
+
const startAutoSave = useCallback((): void => {
|
|
416
|
+
if (!isAdvancedEnabled) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const manager = persistenceManagerRef.current;
|
|
421
|
+
if (manager) {
|
|
422
|
+
manager.startAutoSave();
|
|
423
|
+
setAutoSaveRunning(true);
|
|
424
|
+
}
|
|
425
|
+
}, [isAdvancedEnabled]);
|
|
426
|
+
|
|
427
|
+
const stopAutoSave = useCallback((): void => {
|
|
428
|
+
if (!isAdvancedEnabled) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const manager = persistenceManagerRef.current;
|
|
433
|
+
if (manager) {
|
|
434
|
+
manager.stopAutoSave();
|
|
435
|
+
setAutoSaveRunning(false);
|
|
436
|
+
}
|
|
437
|
+
}, [isAdvancedEnabled]);
|
|
438
|
+
|
|
439
|
+
// ==========================================================================
|
|
440
|
+
// Return Value
|
|
441
|
+
// ==========================================================================
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
// Basic session adapter methods
|
|
445
|
+
saveSession: forceSave,
|
|
446
|
+
loadSession: sessionAdapter.loadSession,
|
|
447
|
+
clearSession: sessionAdapter.clearSession,
|
|
448
|
+
isSaving: sessionAdapter.isSaving || status === "saving",
|
|
449
|
+
isLoading: sessionAdapter.isLoading,
|
|
450
|
+
error: sessionAdapter.error,
|
|
451
|
+
|
|
452
|
+
// Advanced persistence state
|
|
453
|
+
status,
|
|
454
|
+
unsavedCount,
|
|
455
|
+
autoSaveRunning,
|
|
456
|
+
lastSavedAt,
|
|
457
|
+
checkpoints,
|
|
458
|
+
|
|
459
|
+
// Checkpoint operations
|
|
460
|
+
createCheckpoint,
|
|
461
|
+
rollbackToCheckpoint,
|
|
462
|
+
deleteCheckpoint,
|
|
463
|
+
getMessagesToLose,
|
|
464
|
+
|
|
465
|
+
// Control methods
|
|
466
|
+
isAdvancedEnabled,
|
|
467
|
+
forceSave,
|
|
468
|
+
startAutoSave,
|
|
469
|
+
stopAutoSave,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// =============================================================================
|
|
474
|
+
// Exports
|
|
475
|
+
// =============================================================================
|
|
476
|
+
|
|
477
|
+
export type {
|
|
478
|
+
PersistenceBridge,
|
|
479
|
+
PersistenceBridgeCallbacks,
|
|
480
|
+
} from "../adapters/persistence-bridge.js";
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePersistenceShortcuts Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for handling keyboard shortcuts for persistence operations.
|
|
5
|
+
* Provides Ctrl+S, Ctrl+Shift+C, and Ctrl+Z shortcuts for save,
|
|
6
|
+
* checkpoint, and rollback panel operations.
|
|
7
|
+
*
|
|
8
|
+
* @module tui/hooks/usePersistenceShortcuts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useInput } from "ink";
|
|
12
|
+
import { useCallback, useRef } from "react";
|
|
13
|
+
import type { UsePersistenceReturn } from "./usePersistence.js";
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Options for the usePersistenceShortcuts hook
|
|
21
|
+
*/
|
|
22
|
+
export interface UsePersistenceShortcutsOptions {
|
|
23
|
+
/** Persistence hook instance */
|
|
24
|
+
readonly persistence: UsePersistenceReturn | null;
|
|
25
|
+
/** Whether shortcuts are enabled */
|
|
26
|
+
readonly enabled?: boolean;
|
|
27
|
+
/** Callback when save is triggered */
|
|
28
|
+
readonly onSave?: () => void;
|
|
29
|
+
/** Callback when checkpoint is created */
|
|
30
|
+
readonly onCheckpointCreated?: (checkpointId: string) => void;
|
|
31
|
+
/** Callback to open checkpoint panel */
|
|
32
|
+
readonly onOpenCheckpointPanel?: () => void;
|
|
33
|
+
/** Callback when an error occurs */
|
|
34
|
+
readonly onError?: (error: string) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Return value of the usePersistenceShortcuts hook
|
|
39
|
+
*/
|
|
40
|
+
export interface UsePersistenceShortcutsReturn {
|
|
41
|
+
/** Manually trigger a save */
|
|
42
|
+
readonly triggerSave: () => Promise<void>;
|
|
43
|
+
/** Manually trigger checkpoint creation */
|
|
44
|
+
readonly triggerCheckpoint: (description?: string) => Promise<string | null>;
|
|
45
|
+
/** Manually open checkpoint/rollback panel */
|
|
46
|
+
readonly openCheckpointPanel: () => void;
|
|
47
|
+
/** Whether shortcuts are currently active */
|
|
48
|
+
readonly isActive: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// =============================================================================
|
|
52
|
+
// Constants
|
|
53
|
+
// =============================================================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Keyboard shortcut descriptions for help display
|
|
57
|
+
*/
|
|
58
|
+
export const PERSISTENCE_SHORTCUTS = {
|
|
59
|
+
save: "Ctrl+S",
|
|
60
|
+
checkpoint: "Ctrl+Shift+C",
|
|
61
|
+
rollbackPanel: "Ctrl+Shift+Z",
|
|
62
|
+
} as const;
|
|
63
|
+
|
|
64
|
+
// =============================================================================
|
|
65
|
+
// Hook Implementation
|
|
66
|
+
// =============================================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* usePersistenceShortcuts - Hook for persistence keyboard shortcuts.
|
|
70
|
+
*
|
|
71
|
+
* Shortcuts:
|
|
72
|
+
* - Ctrl+S: Save session immediately
|
|
73
|
+
* - Ctrl+Shift+C: Create a new checkpoint
|
|
74
|
+
* - Ctrl+Z: Open checkpoint/rollback panel
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* function MyComponent() {
|
|
79
|
+
* const persistence = usePersistence({ ... });
|
|
80
|
+
* const [showPanel, setShowPanel] = useState(false);
|
|
81
|
+
*
|
|
82
|
+
* const { isActive } = usePersistenceShortcuts({
|
|
83
|
+
* persistence,
|
|
84
|
+
* onSave: () => console.log('Saved'),
|
|
85
|
+
* onCheckpointCreated: (id) => console.log('Checkpoint:', id),
|
|
86
|
+
* onOpenCheckpointPanel: () => setShowPanel(true),
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* return (
|
|
90
|
+
* <Box>
|
|
91
|
+
* <Text>Shortcuts active: {isActive ? 'Yes' : 'No'}</Text>
|
|
92
|
+
* </Box>
|
|
93
|
+
* );
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function usePersistenceShortcuts({
|
|
98
|
+
persistence,
|
|
99
|
+
enabled = true,
|
|
100
|
+
onSave,
|
|
101
|
+
onCheckpointCreated,
|
|
102
|
+
onOpenCheckpointPanel,
|
|
103
|
+
onError,
|
|
104
|
+
}: UsePersistenceShortcutsOptions): UsePersistenceShortcutsReturn {
|
|
105
|
+
// Prevent concurrent operations
|
|
106
|
+
const isOperatingRef = useRef(false);
|
|
107
|
+
|
|
108
|
+
// Check if shortcuts are active
|
|
109
|
+
const isActive = enabled && persistence !== null;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Trigger a save operation
|
|
113
|
+
*/
|
|
114
|
+
const triggerSave = useCallback(async (): Promise<void> => {
|
|
115
|
+
if (!persistence || isOperatingRef.current) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
isOperatingRef.current = true;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await persistence.forceSave();
|
|
123
|
+
onSave?.();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
onError?.(error instanceof Error ? error.message : "Save failed");
|
|
126
|
+
} finally {
|
|
127
|
+
isOperatingRef.current = false;
|
|
128
|
+
}
|
|
129
|
+
}, [persistence, onSave, onError]);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Trigger checkpoint creation
|
|
133
|
+
*/
|
|
134
|
+
const triggerCheckpoint = useCallback(
|
|
135
|
+
async (description?: string): Promise<string | null> => {
|
|
136
|
+
if (!persistence || isOperatingRef.current) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!persistence.isAdvancedEnabled) {
|
|
141
|
+
onError?.("Checkpoints require advanced persistence mode");
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
isOperatingRef.current = true;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const checkpointId = await persistence.createCheckpoint(description);
|
|
149
|
+
if (checkpointId) {
|
|
150
|
+
onCheckpointCreated?.(checkpointId);
|
|
151
|
+
} else {
|
|
152
|
+
onError?.("Failed to create checkpoint");
|
|
153
|
+
}
|
|
154
|
+
return checkpointId;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
onError?.(error instanceof Error ? error.message : "Checkpoint creation failed");
|
|
157
|
+
return null;
|
|
158
|
+
} finally {
|
|
159
|
+
isOperatingRef.current = false;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
[persistence, onCheckpointCreated, onError]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Open the checkpoint/rollback panel
|
|
167
|
+
*/
|
|
168
|
+
const openCheckpointPanel = useCallback((): void => {
|
|
169
|
+
if (!persistence) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!persistence.isAdvancedEnabled) {
|
|
174
|
+
onError?.("Checkpoints require advanced persistence mode");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
onOpenCheckpointPanel?.();
|
|
179
|
+
}, [persistence, onOpenCheckpointPanel, onError]);
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Handle keyboard input
|
|
183
|
+
*/
|
|
184
|
+
useInput(
|
|
185
|
+
(input, key) => {
|
|
186
|
+
if (!isActive || isOperatingRef.current) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Ctrl+S: Save
|
|
191
|
+
if (key.ctrl && input === "s") {
|
|
192
|
+
void triggerSave();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Ctrl+Shift+C: Create checkpoint
|
|
197
|
+
// Note: In terminal, Ctrl+Shift+C might be captured as just 'C' with ctrl
|
|
198
|
+
if (key.ctrl && key.shift && (input === "c" || input === "C")) {
|
|
199
|
+
void triggerCheckpoint();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Ctrl+Shift+Z: Open rollback panel
|
|
204
|
+
// Changed from Ctrl+Z to avoid conflict with undo shortcut
|
|
205
|
+
// Only trigger if advanced persistence is enabled
|
|
206
|
+
if (
|
|
207
|
+
key.ctrl &&
|
|
208
|
+
key.shift &&
|
|
209
|
+
(input === "z" || input === "Z") &&
|
|
210
|
+
persistence?.isAdvancedEnabled
|
|
211
|
+
) {
|
|
212
|
+
openCheckpointPanel();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
{ isActive }
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
triggerSave,
|
|
221
|
+
triggerCheckpoint,
|
|
222
|
+
openCheckpointPanel,
|
|
223
|
+
isActive,
|
|
224
|
+
};
|
|
225
|
+
}
|