@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,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useModeController Hook (T003)
|
|
3
|
+
*
|
|
4
|
+
* Manages render mode decisions for adaptive message list rendering.
|
|
5
|
+
* Dynamically switches between Static, Windowed, and Virtualized modes
|
|
6
|
+
* based on content height vs available viewport.
|
|
7
|
+
*
|
|
8
|
+
* @module tui/hooks/useModeController
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useMemo } from "react";
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Types
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Available rendering modes for message list.
|
|
19
|
+
*/
|
|
20
|
+
export type RenderMode = "static" | "windowed" | "virtualized";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Reason for the current mode selection.
|
|
24
|
+
*/
|
|
25
|
+
export type ModeReason =
|
|
26
|
+
| "content-fits" // Content fits in viewport
|
|
27
|
+
| "content-exceeds-viewport" // Content exceeds viewport but not virtual threshold
|
|
28
|
+
| "content-very-large" // Content exceeds virtual threshold
|
|
29
|
+
| "forced"; // Mode was forced via config
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configuration for mode controller.
|
|
33
|
+
*/
|
|
34
|
+
export interface ModeControllerConfig {
|
|
35
|
+
/** Multiplier for static threshold (default: 1.2 = 120% of viewport) */
|
|
36
|
+
readonly staticMultiplier?: number;
|
|
37
|
+
/** Multiplier for virtual threshold (default: 5.0 = 500% of viewport) */
|
|
38
|
+
readonly virtualMultiplier?: number;
|
|
39
|
+
/** Force a specific mode, bypassing auto-detection */
|
|
40
|
+
readonly forceMode?: RenderMode;
|
|
41
|
+
/** Minimum window size in lines (default: 10) */
|
|
42
|
+
readonly minWindowSize?: number;
|
|
43
|
+
/** Maximum window size as ratio of available height (default: 0.8) */
|
|
44
|
+
readonly maxWindowSizeRatio?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Input for useModeController hook.
|
|
49
|
+
*/
|
|
50
|
+
export interface UseModeControllerInput {
|
|
51
|
+
/** Available height in lines for content */
|
|
52
|
+
readonly availableHeight: number;
|
|
53
|
+
/** Total estimated height of all content in lines */
|
|
54
|
+
readonly totalContentHeight: number;
|
|
55
|
+
/** Configuration options */
|
|
56
|
+
readonly config?: ModeControllerConfig;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* State returned by useModeController.
|
|
61
|
+
*/
|
|
62
|
+
export interface ModeControllerState {
|
|
63
|
+
/** Current render mode */
|
|
64
|
+
readonly mode: RenderMode;
|
|
65
|
+
/** Recommended window size for windowed mode */
|
|
66
|
+
readonly windowSize: number;
|
|
67
|
+
/** Reason for the current mode selection */
|
|
68
|
+
readonly modeReason: ModeReason;
|
|
69
|
+
/** Computed static threshold (content height below this = static mode) */
|
|
70
|
+
readonly staticThreshold: number;
|
|
71
|
+
/** Computed virtual threshold (content height above this = virtualized mode) */
|
|
72
|
+
readonly virtualThreshold: number;
|
|
73
|
+
/** Whether mode is auto-detected (true) or forced (false) */
|
|
74
|
+
readonly isAutoMode: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// =============================================================================
|
|
78
|
+
// Constants
|
|
79
|
+
// =============================================================================
|
|
80
|
+
|
|
81
|
+
const DEFAULT_STATIC_MULTIPLIER = 1.2;
|
|
82
|
+
const DEFAULT_VIRTUAL_MULTIPLIER = 5.0;
|
|
83
|
+
const DEFAULT_MIN_WINDOW_SIZE = 10;
|
|
84
|
+
const DEFAULT_MAX_WINDOW_SIZE_RATIO = 0.8;
|
|
85
|
+
|
|
86
|
+
// =============================================================================
|
|
87
|
+
// Hook Implementation
|
|
88
|
+
// =============================================================================
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Hook for managing render mode decisions based on content vs viewport.
|
|
92
|
+
*
|
|
93
|
+
* Mode selection logic:
|
|
94
|
+
* - Static: totalContentHeight ≤ availableHeight × staticMultiplier
|
|
95
|
+
* - Windowed: staticThreshold < totalContentHeight ≤ virtualThreshold
|
|
96
|
+
* - Virtualized: totalContentHeight > virtualThreshold
|
|
97
|
+
*
|
|
98
|
+
* @param input - Input parameters including viewport and content heights
|
|
99
|
+
* @returns Mode controller state with recommended settings
|
|
100
|
+
*/
|
|
101
|
+
export function useModeController(input: UseModeControllerInput): ModeControllerState {
|
|
102
|
+
const { availableHeight, totalContentHeight, config = {} } = input;
|
|
103
|
+
|
|
104
|
+
const {
|
|
105
|
+
staticMultiplier = DEFAULT_STATIC_MULTIPLIER,
|
|
106
|
+
virtualMultiplier = DEFAULT_VIRTUAL_MULTIPLIER,
|
|
107
|
+
forceMode,
|
|
108
|
+
minWindowSize = DEFAULT_MIN_WINDOW_SIZE,
|
|
109
|
+
maxWindowSizeRatio = DEFAULT_MAX_WINDOW_SIZE_RATIO,
|
|
110
|
+
} = config;
|
|
111
|
+
|
|
112
|
+
return useMemo(() => {
|
|
113
|
+
// Compute thresholds from available height
|
|
114
|
+
const staticThreshold = Math.max(1, availableHeight * staticMultiplier);
|
|
115
|
+
const virtualThreshold = Math.max(1, availableHeight * virtualMultiplier);
|
|
116
|
+
|
|
117
|
+
// Calculate window size with min constraint and max ratio
|
|
118
|
+
const windowSize = Math.max(minWindowSize, Math.floor(availableHeight * maxWindowSizeRatio));
|
|
119
|
+
|
|
120
|
+
// Determine if auto mode (not forced)
|
|
121
|
+
const isAutoMode = !forceMode;
|
|
122
|
+
|
|
123
|
+
// Handle forced mode
|
|
124
|
+
if (forceMode) {
|
|
125
|
+
return {
|
|
126
|
+
mode: forceMode,
|
|
127
|
+
windowSize,
|
|
128
|
+
modeReason: "forced" as const,
|
|
129
|
+
staticThreshold,
|
|
130
|
+
virtualThreshold,
|
|
131
|
+
isAutoMode,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Auto-detect mode based on content height vs thresholds
|
|
136
|
+
let mode: RenderMode;
|
|
137
|
+
let modeReason: ModeReason;
|
|
138
|
+
|
|
139
|
+
if (totalContentHeight <= staticThreshold) {
|
|
140
|
+
mode = "static";
|
|
141
|
+
modeReason = "content-fits";
|
|
142
|
+
} else if (totalContentHeight <= virtualThreshold) {
|
|
143
|
+
mode = "windowed";
|
|
144
|
+
modeReason = "content-exceeds-viewport";
|
|
145
|
+
} else {
|
|
146
|
+
mode = "virtualized";
|
|
147
|
+
modeReason = "content-very-large";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
mode,
|
|
152
|
+
windowSize,
|
|
153
|
+
modeReason,
|
|
154
|
+
staticThreshold,
|
|
155
|
+
virtualThreshold,
|
|
156
|
+
isAutoMode,
|
|
157
|
+
};
|
|
158
|
+
}, [
|
|
159
|
+
availableHeight,
|
|
160
|
+
totalContentHeight,
|
|
161
|
+
staticMultiplier,
|
|
162
|
+
virtualMultiplier,
|
|
163
|
+
forceMode,
|
|
164
|
+
minWindowSize,
|
|
165
|
+
maxWindowSizeRatio,
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useModeShortcuts Hook (T046)
|
|
3
|
+
*
|
|
4
|
+
* React hook for handling keyboard shortcuts to switch coding modes.
|
|
5
|
+
* Provides Alt+1/2/3 shortcuts for quick mode switching.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/hooks/useModeShortcuts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CodingMode, ModeManager } from "@vellum/core";
|
|
11
|
+
import { CODING_MODES } from "@vellum/core";
|
|
12
|
+
import { useInput } from "ink";
|
|
13
|
+
import { useCallback, useRef } from "react";
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Options for the useModeShortcuts hook.
|
|
21
|
+
*/
|
|
22
|
+
export interface UseModeShortcutsOptions {
|
|
23
|
+
/** The ModeManager instance to use for switching */
|
|
24
|
+
readonly modeManager: ModeManager | null;
|
|
25
|
+
/** Whether shortcuts are enabled (default: true) */
|
|
26
|
+
readonly enabled?: boolean;
|
|
27
|
+
/** Callback when a mode switch is attempted */
|
|
28
|
+
readonly onModeSwitch?: (mode: CodingMode, success: boolean) => void;
|
|
29
|
+
/** Callback when a mode switch fails */
|
|
30
|
+
readonly onError?: (mode: CodingMode, error: string) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Return value of useModeShortcuts hook.
|
|
35
|
+
*/
|
|
36
|
+
export interface UseModeShortcutsReturn {
|
|
37
|
+
/** Get the shortcut key for a mode */
|
|
38
|
+
readonly getShortcut: (mode: CodingMode) => string;
|
|
39
|
+
/** Manually trigger a mode switch */
|
|
40
|
+
readonly switchMode: (mode: CodingMode) => Promise<boolean>;
|
|
41
|
+
/** Whether shortcuts are currently active */
|
|
42
|
+
readonly isActive: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Constants
|
|
47
|
+
// =============================================================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Keyboard shortcut mapping for modes.
|
|
51
|
+
* Alt+1 = vibe, Alt+2 = plan, Alt+3 = spec
|
|
52
|
+
*/
|
|
53
|
+
const MODE_SHORTCUTS: Record<string, CodingMode> = {
|
|
54
|
+
"1": "vibe",
|
|
55
|
+
"2": "plan",
|
|
56
|
+
"3": "spec",
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Reverse mapping: mode to shortcut key.
|
|
61
|
+
*/
|
|
62
|
+
const SHORTCUT_FOR_MODE: Record<CodingMode, string> = {
|
|
63
|
+
vibe: "Alt+1",
|
|
64
|
+
plan: "Alt+2",
|
|
65
|
+
spec: "Alt+3",
|
|
66
|
+
} as const;
|
|
67
|
+
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// Hook Implementation
|
|
70
|
+
// =============================================================================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* useModeShortcuts - Hook for handling mode switching keyboard shortcuts.
|
|
74
|
+
*
|
|
75
|
+
* Provides Alt+1/2/3 shortcuts for quick mode switching:
|
|
76
|
+
* - Alt+1: Switch to vibe mode (fast autonomous)
|
|
77
|
+
* - Alt+2: Switch to plan mode (plan-then-execute)
|
|
78
|
+
* - Alt+3: Switch to spec mode (6-phase workflow)
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```tsx
|
|
82
|
+
* function MyComponent() {
|
|
83
|
+
* const { modeManager } = useApp();
|
|
84
|
+
*
|
|
85
|
+
* const { getShortcut, isActive } = useModeShortcuts({
|
|
86
|
+
* modeManager,
|
|
87
|
+
* onModeSwitch: (mode, success) => {
|
|
88
|
+
* console.log(`Switched to ${mode}: ${success}`);
|
|
89
|
+
* },
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* return (
|
|
93
|
+
* <Text>
|
|
94
|
+
* Press {getShortcut('vibe')} for vibe mode
|
|
95
|
+
* </Text>
|
|
96
|
+
* );
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function useModeShortcuts({
|
|
101
|
+
modeManager,
|
|
102
|
+
enabled = true,
|
|
103
|
+
onModeSwitch,
|
|
104
|
+
onError,
|
|
105
|
+
}: UseModeShortcutsOptions): UseModeShortcutsReturn {
|
|
106
|
+
// Track whether we're currently processing a switch
|
|
107
|
+
const isSwitchingRef = useRef(false);
|
|
108
|
+
|
|
109
|
+
// Check if shortcuts are active
|
|
110
|
+
const isActive = enabled && modeManager !== null;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Attempt to switch to a mode.
|
|
114
|
+
*/
|
|
115
|
+
const switchMode = useCallback(
|
|
116
|
+
async (mode: CodingMode): Promise<boolean> => {
|
|
117
|
+
if (!modeManager) {
|
|
118
|
+
onError?.(mode, "Mode manager not initialized");
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Prevent concurrent switches
|
|
123
|
+
if (isSwitchingRef.current) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if already in target mode
|
|
128
|
+
if (modeManager.getCurrentMode() === mode) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
isSwitchingRef.current = true;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const result = await modeManager.switchMode(mode);
|
|
136
|
+
|
|
137
|
+
if (result.success) {
|
|
138
|
+
onModeSwitch?.(mode, true);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
onError?.(mode, result.reason ?? "Unknown error");
|
|
143
|
+
onModeSwitch?.(mode, false);
|
|
144
|
+
return false;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
147
|
+
onError?.(mode, errorMessage);
|
|
148
|
+
onModeSwitch?.(mode, false);
|
|
149
|
+
return false;
|
|
150
|
+
} finally {
|
|
151
|
+
isSwitchingRef.current = false;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
[modeManager, onModeSwitch, onError]
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Handle keyboard input for mode shortcuts.
|
|
159
|
+
*/
|
|
160
|
+
useInput(
|
|
161
|
+
useCallback(
|
|
162
|
+
(input: string, key) => {
|
|
163
|
+
// Only handle Alt+number combinations
|
|
164
|
+
if (!key.meta) return;
|
|
165
|
+
|
|
166
|
+
// Check if input matches a mode shortcut
|
|
167
|
+
const targetMode = MODE_SHORTCUTS[input];
|
|
168
|
+
if (targetMode && CODING_MODES.includes(targetMode)) {
|
|
169
|
+
// Fire and forget - don't await in input handler
|
|
170
|
+
void switchMode(targetMode);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
[switchMode]
|
|
174
|
+
),
|
|
175
|
+
{ isActive }
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get the shortcut key combination for a mode.
|
|
180
|
+
*/
|
|
181
|
+
const getShortcut = useCallback((mode: CodingMode): string => {
|
|
182
|
+
return SHORTCUT_FOR_MODE[mode];
|
|
183
|
+
}, []);
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
getShortcut,
|
|
187
|
+
switchMode,
|
|
188
|
+
isActive,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// =============================================================================
|
|
193
|
+
// Exports
|
|
194
|
+
// =============================================================================
|
|
195
|
+
|
|
196
|
+
export type { CodingMode };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePermissionHandler Hook (T037)
|
|
3
|
+
*
|
|
4
|
+
* React hook that creates a PermissionAskHandler for use with the permission system.
|
|
5
|
+
* This hook manages permission dialog state and provides a handler function
|
|
6
|
+
* that can be passed to createDefaultPermissionChecker.
|
|
7
|
+
*
|
|
8
|
+
* @module @vellum/cli
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
AskContext,
|
|
13
|
+
PermissionAskHandler,
|
|
14
|
+
PermissionInfo,
|
|
15
|
+
PermissionResponse,
|
|
16
|
+
} from "@vellum/core";
|
|
17
|
+
import { useCallback, useRef, useState } from "react";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Pending permission request waiting for user response.
|
|
21
|
+
*/
|
|
22
|
+
export interface PendingPermission {
|
|
23
|
+
/** Unique ID for this request */
|
|
24
|
+
id: string;
|
|
25
|
+
/** Permission info from the permission system */
|
|
26
|
+
info: PermissionInfo;
|
|
27
|
+
/** Context from the permission system */
|
|
28
|
+
context: AskContext;
|
|
29
|
+
/** Resolve function to return the user's response */
|
|
30
|
+
resolve: (result: PermissionResponse | undefined) => void;
|
|
31
|
+
/** Timestamp when the request was created */
|
|
32
|
+
timestamp: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Return value of usePermissionHandler hook.
|
|
37
|
+
*/
|
|
38
|
+
export interface UsePermissionHandlerReturn {
|
|
39
|
+
/** Current pending permission request (if any) */
|
|
40
|
+
pendingPermission: PendingPermission | null;
|
|
41
|
+
/** Whether a permission dialog should be shown */
|
|
42
|
+
isDialogVisible: boolean;
|
|
43
|
+
/** Handler function to pass to the permission system */
|
|
44
|
+
handler: PermissionAskHandler;
|
|
45
|
+
/** Respond to the current permission request */
|
|
46
|
+
respond: (response: PermissionResponse) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let requestIdCounter = 0;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Hook for managing permission prompts in the CLI.
|
|
53
|
+
*
|
|
54
|
+
* Creates a PermissionAskHandler that can be passed to the permission system.
|
|
55
|
+
* When a permission check returns "ask", this handler is called and the hook
|
|
56
|
+
* updates state to show a dialog.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* function App() {
|
|
61
|
+
* const { pendingPermission, isDialogVisible, handler, respond } = usePermissionHandler();
|
|
62
|
+
*
|
|
63
|
+
* // Pass handler to permission system
|
|
64
|
+
* useEffect(() => {
|
|
65
|
+
* const checker = createDefaultPermissionChecker({ askHandler: handler });
|
|
66
|
+
* // ... use checker with ToolExecutor
|
|
67
|
+
* }, [handler]);
|
|
68
|
+
*
|
|
69
|
+
* return (
|
|
70
|
+
* <Box>
|
|
71
|
+
* {isDialogVisible && pendingPermission && (
|
|
72
|
+
* <PermissionDialog
|
|
73
|
+
* toolName={pendingPermission.info.toolName}
|
|
74
|
+
* onResponse={respond}
|
|
75
|
+
* isActive={true}
|
|
76
|
+
* />
|
|
77
|
+
* )}
|
|
78
|
+
* </Box>
|
|
79
|
+
* );
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function usePermissionHandler(): UsePermissionHandlerReturn {
|
|
84
|
+
const [pendingPermission, setPendingPermission] = useState<PendingPermission | null>(null);
|
|
85
|
+
const pendingRef = useRef<PendingPermission | null>(null);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Handler function for the permission system.
|
|
89
|
+
* This is called when a permission check returns "ask".
|
|
90
|
+
*/
|
|
91
|
+
const handler: PermissionAskHandler = useCallback(
|
|
92
|
+
async (info: PermissionInfo, context: AskContext): Promise<PermissionResponse | undefined> => {
|
|
93
|
+
return new Promise<PermissionResponse | undefined>((resolve) => {
|
|
94
|
+
const id = `perm-${++requestIdCounter}`;
|
|
95
|
+
const pending: PendingPermission = {
|
|
96
|
+
id,
|
|
97
|
+
info,
|
|
98
|
+
context,
|
|
99
|
+
resolve,
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
pendingRef.current = pending;
|
|
104
|
+
setPendingPermission(pending);
|
|
105
|
+
|
|
106
|
+
// Handle timeout from the context signal
|
|
107
|
+
context.signal.addEventListener("abort", () => {
|
|
108
|
+
if (pendingRef.current?.id === id) {
|
|
109
|
+
// Timeout - return undefined to let the service handle it
|
|
110
|
+
resolve(undefined);
|
|
111
|
+
pendingRef.current = null;
|
|
112
|
+
setPendingPermission(null);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
[]
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Respond to the current permission request.
|
|
122
|
+
*/
|
|
123
|
+
const respond = useCallback((response: PermissionResponse) => {
|
|
124
|
+
const pending = pendingRef.current;
|
|
125
|
+
if (!pending) {
|
|
126
|
+
console.warn("[usePermissionHandler] No pending permission to respond to");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Resolve the promise with the response directly
|
|
131
|
+
pending.resolve(response);
|
|
132
|
+
|
|
133
|
+
// Clear state
|
|
134
|
+
pendingRef.current = null;
|
|
135
|
+
setPendingPermission(null);
|
|
136
|
+
}, []);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
pendingPermission,
|
|
140
|
+
isDialogVisible: pendingPermission !== null,
|
|
141
|
+
handler,
|
|
142
|
+
respond,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export default usePermissionHandler;
|