@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,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tip Engine Integration
|
|
3
|
+
*
|
|
4
|
+
* Wires the TipEngine to the TUI application for contextual tips
|
|
5
|
+
* and progressive disclosure of features.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/tui/tip-integration
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useCallback, useMemo, useState } from "react";
|
|
11
|
+
import type { Tip, TipContext, TipState } from "../onboarding/index.js";
|
|
12
|
+
import { BUILTIN_TIPS, createTipEngine, TipEngine } from "../onboarding/index.js";
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Types
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
export interface TipIntegrationOptions {
|
|
19
|
+
/** Enable tips display */
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
/** Maximum tips to show per session */
|
|
22
|
+
maxTipsPerSession?: number;
|
|
23
|
+
/** Minimum interval between tips (ms) */
|
|
24
|
+
tipIntervalMs?: number;
|
|
25
|
+
/** Categories to prioritize */
|
|
26
|
+
priorityCategories?: string[];
|
|
27
|
+
/** Callback when a tip is shown */
|
|
28
|
+
onTipShown?: (tip: Tip) => void;
|
|
29
|
+
/** Callback when a tip is dismissed */
|
|
30
|
+
onTipDismissed?: (tipId: string) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TipIntegrationState {
|
|
34
|
+
/** Current tip to display (null if none) */
|
|
35
|
+
currentTip: Tip | null;
|
|
36
|
+
/** Whether tips are enabled */
|
|
37
|
+
tipsEnabled: boolean;
|
|
38
|
+
/** Number of tips shown this session */
|
|
39
|
+
tipsShownCount: number;
|
|
40
|
+
/** Whether a tip is currently visible */
|
|
41
|
+
tipVisible: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface TipIntegrationActions {
|
|
45
|
+
/** Show a contextual tip based on current context */
|
|
46
|
+
showTip: (context: TipContext) => Tip | null;
|
|
47
|
+
/** Dismiss the current tip */
|
|
48
|
+
dismissTip: () => void;
|
|
49
|
+
/** Permanently dismiss a tip (won't show again) */
|
|
50
|
+
dismissTipPermanently: (tipId: string) => void;
|
|
51
|
+
/** Enable or disable tips */
|
|
52
|
+
setTipsEnabled: (enabled: boolean) => void;
|
|
53
|
+
/** Get a specific tip by ID */
|
|
54
|
+
getTip: (tipId: string) => Tip | undefined;
|
|
55
|
+
/** Check if a tip has been shown */
|
|
56
|
+
hasTipBeenShown: (tipId: string) => boolean;
|
|
57
|
+
/** Reset tip states (for testing) */
|
|
58
|
+
resetTips: () => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type UseTipEngineResult = TipIntegrationState & TipIntegrationActions;
|
|
62
|
+
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// Default Configuration
|
|
65
|
+
// =============================================================================
|
|
66
|
+
|
|
67
|
+
const DEFAULT_OPTIONS: Required<TipIntegrationOptions> = {
|
|
68
|
+
enabled: true,
|
|
69
|
+
maxTipsPerSession: 10,
|
|
70
|
+
tipIntervalMs: 60000, // 1 minute between tips
|
|
71
|
+
priorityCategories: ["shortcuts", "features"],
|
|
72
|
+
onTipShown: () => {},
|
|
73
|
+
onTipDismissed: () => {},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// Hook Implementation
|
|
78
|
+
// =============================================================================
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* React hook for tip engine integration.
|
|
82
|
+
*
|
|
83
|
+
* @param options - Tip integration options
|
|
84
|
+
* @returns Tip integration state and actions
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```tsx
|
|
88
|
+
* function AppContent() {
|
|
89
|
+
* const {
|
|
90
|
+
* currentTip,
|
|
91
|
+
* showTip,
|
|
92
|
+
* dismissTip,
|
|
93
|
+
* tipsEnabled,
|
|
94
|
+
* } = useTipEngine({ enabled: true });
|
|
95
|
+
*
|
|
96
|
+
* // Show contextual tips based on user actions
|
|
97
|
+
* useEffect(() => {
|
|
98
|
+
* showTip({
|
|
99
|
+
* currentScreen: "main",
|
|
100
|
+
* featureUsageCount: 5,
|
|
101
|
+
* recentCommands: ["help", "mode"],
|
|
102
|
+
* });
|
|
103
|
+
* }, [showTip]);
|
|
104
|
+
*
|
|
105
|
+
* return (
|
|
106
|
+
* <>
|
|
107
|
+
* {currentTip && (
|
|
108
|
+
* <TipBanner tip={currentTip} onDismiss={dismissTip} />
|
|
109
|
+
* )}
|
|
110
|
+
* <MainContent />
|
|
111
|
+
* </>
|
|
112
|
+
* );
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export function useTipEngine(options: TipIntegrationOptions = {}): UseTipEngineResult {
|
|
117
|
+
// Memoize config to prevent re-renders from changing object reference
|
|
118
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: We intentionally use specific properties to prevent re-renders when only callbacks change
|
|
119
|
+
const config = useMemo(
|
|
120
|
+
() => ({ ...DEFAULT_OPTIONS, ...options }),
|
|
121
|
+
[options.enabled, options.maxTipsPerSession, options.tipIntervalMs]
|
|
122
|
+
);
|
|
123
|
+
const { onTipShown, onTipDismissed } = options;
|
|
124
|
+
|
|
125
|
+
// Create tip engine instance
|
|
126
|
+
const tipEngine = useMemo(() => createTipEngine(), []);
|
|
127
|
+
|
|
128
|
+
// State
|
|
129
|
+
const [currentTip, setCurrentTip] = useState<Tip | null>(null);
|
|
130
|
+
const [tipsEnabled, setTipsEnabled] = useState(config.enabled);
|
|
131
|
+
const [tipsShownCount, setTipsShownCount] = useState(0);
|
|
132
|
+
const [tipVisible, setTipVisible] = useState(false);
|
|
133
|
+
const [lastTipTime, setLastTipTime] = useState(0);
|
|
134
|
+
const [permanentlyDismissed, setPermanentlyDismissed] = useState<Set<string>>(new Set());
|
|
135
|
+
|
|
136
|
+
// Show a tip based on context
|
|
137
|
+
const showTip = useCallback(
|
|
138
|
+
(context: TipContext): Tip | null => {
|
|
139
|
+
if (!tipsEnabled) return null;
|
|
140
|
+
if (tipsShownCount >= config.maxTipsPerSession) return null;
|
|
141
|
+
|
|
142
|
+
// Check tip interval
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
if (now - lastTipTime < config.tipIntervalMs) return null;
|
|
145
|
+
|
|
146
|
+
// Get a matching tip
|
|
147
|
+
const tip = tipEngine.getTip(context);
|
|
148
|
+
|
|
149
|
+
if (tip && !permanentlyDismissed.has(tip.id)) {
|
|
150
|
+
setCurrentTip(tip);
|
|
151
|
+
setTipVisible(true);
|
|
152
|
+
setTipsShownCount((prev) => prev + 1);
|
|
153
|
+
setLastTipTime(now);
|
|
154
|
+
onTipShown?.(tip);
|
|
155
|
+
return tip;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return null;
|
|
159
|
+
},
|
|
160
|
+
[
|
|
161
|
+
tipsEnabled,
|
|
162
|
+
tipsShownCount,
|
|
163
|
+
lastTipTime,
|
|
164
|
+
tipEngine,
|
|
165
|
+
permanentlyDismissed,
|
|
166
|
+
config.maxTipsPerSession,
|
|
167
|
+
config.tipIntervalMs,
|
|
168
|
+
onTipShown,
|
|
169
|
+
]
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Dismiss current tip
|
|
173
|
+
const dismissTip = useCallback(() => {
|
|
174
|
+
if (currentTip) {
|
|
175
|
+
onTipDismissed?.(currentTip.id);
|
|
176
|
+
}
|
|
177
|
+
setCurrentTip(null);
|
|
178
|
+
setTipVisible(false);
|
|
179
|
+
}, [currentTip, onTipDismissed]);
|
|
180
|
+
|
|
181
|
+
// Permanently dismiss a tip
|
|
182
|
+
const dismissTipPermanently = useCallback(
|
|
183
|
+
(tipId: string) => {
|
|
184
|
+
setPermanentlyDismissed((prev) => new Set([...prev, tipId]));
|
|
185
|
+
tipEngine.dismissTip(tipId);
|
|
186
|
+
if (currentTip?.id === tipId) {
|
|
187
|
+
dismissTip();
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
[tipEngine, currentTip, dismissTip]
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Get a specific tip
|
|
194
|
+
const getTip = useCallback(
|
|
195
|
+
(tipId: string): Tip | undefined => {
|
|
196
|
+
return tipEngine.getAllTips().find((t) => t.id === tipId);
|
|
197
|
+
},
|
|
198
|
+
[tipEngine]
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Check if a tip has been shown
|
|
202
|
+
const hasTipBeenShown = useCallback(
|
|
203
|
+
(tipId: string): boolean => {
|
|
204
|
+
const state = tipEngine.getTipState(tipId);
|
|
205
|
+
return state?.showCount !== undefined && state.showCount > 0;
|
|
206
|
+
},
|
|
207
|
+
[tipEngine]
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// Reset tips (for testing)
|
|
211
|
+
const resetTips = useCallback(() => {
|
|
212
|
+
tipEngine.resetAllStates();
|
|
213
|
+
setCurrentTip(null);
|
|
214
|
+
setTipVisible(false);
|
|
215
|
+
setTipsShownCount(0);
|
|
216
|
+
setPermanentlyDismissed(new Set());
|
|
217
|
+
}, [tipEngine]);
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
// State
|
|
221
|
+
currentTip,
|
|
222
|
+
tipsEnabled,
|
|
223
|
+
tipsShownCount,
|
|
224
|
+
tipVisible,
|
|
225
|
+
// Actions
|
|
226
|
+
showTip,
|
|
227
|
+
dismissTip,
|
|
228
|
+
dismissTipPermanently,
|
|
229
|
+
setTipsEnabled,
|
|
230
|
+
getTip,
|
|
231
|
+
hasTipBeenShown,
|
|
232
|
+
resetTips,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// =============================================================================
|
|
237
|
+
// Context Builders
|
|
238
|
+
// =============================================================================
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Build a tip context from application state.
|
|
242
|
+
*
|
|
243
|
+
* @param options - Context building options
|
|
244
|
+
* @returns TipContext for tip matching
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```typescript
|
|
248
|
+
* const context = buildTipContext({
|
|
249
|
+
* screen: "main",
|
|
250
|
+
* mode: "vibe",
|
|
251
|
+
* command: "/help",
|
|
252
|
+
* featuresUsedCount: 10,
|
|
253
|
+
* });
|
|
254
|
+
* const tip = tipEngine.getTip(context);
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
export function buildTipContext(options: {
|
|
258
|
+
screen?: string;
|
|
259
|
+
mode?: string;
|
|
260
|
+
command?: string;
|
|
261
|
+
featuresUsedCount?: number;
|
|
262
|
+
sessionDuration?: number;
|
|
263
|
+
hasError?: boolean;
|
|
264
|
+
experienceLevel?: "new" | "beginner" | "intermediate" | "advanced";
|
|
265
|
+
custom?: Record<string, unknown>;
|
|
266
|
+
}): TipContext {
|
|
267
|
+
return {
|
|
268
|
+
screen: options.screen ?? "main",
|
|
269
|
+
mode: options.mode,
|
|
270
|
+
command: options.command,
|
|
271
|
+
featuresUsedCount: options.featuresUsedCount ?? 0,
|
|
272
|
+
sessionDuration: options.sessionDuration,
|
|
273
|
+
hasError: options.hasError,
|
|
274
|
+
experienceLevel: options.experienceLevel ?? "new",
|
|
275
|
+
custom: options.custom,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get tips relevant to a specific feature or command.
|
|
281
|
+
*
|
|
282
|
+
* @param tipEngine - TipEngine instance
|
|
283
|
+
* @param feature - Feature or command name
|
|
284
|
+
* @returns Relevant tips
|
|
285
|
+
*/
|
|
286
|
+
export function getTipsForFeature(tipEngine: TipEngine, feature: string): Tip[] {
|
|
287
|
+
const allTips = tipEngine.getAllTips();
|
|
288
|
+
return allTips.filter(
|
|
289
|
+
(tip) =>
|
|
290
|
+
tip.trigger.commands?.includes(feature) ||
|
|
291
|
+
tip.relatedLessonId === feature ||
|
|
292
|
+
tip.content.toLowerCase().includes(feature.toLowerCase())
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// =============================================================================
|
|
297
|
+
// Exports
|
|
298
|
+
// =============================================================================
|
|
299
|
+
|
|
300
|
+
export { BUILTIN_TIPS, createTipEngine, TipEngine, type Tip, type TipContext, type TipState };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Extended Ink Key Utilities
|
|
3
|
+
*/
|
|
4
|
+
import type { Key } from "ink";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { type ExtendedKey, extendKey, isEndKey, isHomeKey } from "../ink-extended.js";
|
|
7
|
+
|
|
8
|
+
describe("ink-extended", () => {
|
|
9
|
+
// Base key object for testing
|
|
10
|
+
const baseKey: Key = {
|
|
11
|
+
upArrow: false,
|
|
12
|
+
downArrow: false,
|
|
13
|
+
leftArrow: false,
|
|
14
|
+
rightArrow: false,
|
|
15
|
+
pageDown: false,
|
|
16
|
+
pageUp: false,
|
|
17
|
+
return: false,
|
|
18
|
+
escape: false,
|
|
19
|
+
ctrl: false,
|
|
20
|
+
shift: false,
|
|
21
|
+
tab: false,
|
|
22
|
+
backspace: false,
|
|
23
|
+
delete: false,
|
|
24
|
+
meta: false,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe("extendKey", () => {
|
|
28
|
+
it("extends key object with home and end properties", () => {
|
|
29
|
+
const result = extendKey("a", baseKey);
|
|
30
|
+
expect(result).toHaveProperty("home");
|
|
31
|
+
expect(result).toHaveProperty("end");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("preserves original key properties", () => {
|
|
35
|
+
const key: Key = { ...baseKey, ctrl: true, shift: true };
|
|
36
|
+
const result = extendKey("a", key);
|
|
37
|
+
expect(result.ctrl).toBe(true);
|
|
38
|
+
expect(result.shift).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("detects Home key (CSI H)", () => {
|
|
42
|
+
const result = extendKey("\x1b[H", baseKey);
|
|
43
|
+
expect(result.home).toBe(true);
|
|
44
|
+
expect(result.end).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("detects Home key (CSI 1 ~)", () => {
|
|
48
|
+
const result = extendKey("\x1b[1~", baseKey);
|
|
49
|
+
expect(result.home).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("detects Home key (SS3 H)", () => {
|
|
53
|
+
const result = extendKey("\x1bOH", baseKey);
|
|
54
|
+
expect(result.home).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("detects End key (CSI F)", () => {
|
|
58
|
+
const result = extendKey("\x1b[F", baseKey);
|
|
59
|
+
expect(result.end).toBe(true);
|
|
60
|
+
expect(result.home).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("detects End key (CSI 4 ~)", () => {
|
|
64
|
+
const result = extendKey("\x1b[4~", baseKey);
|
|
65
|
+
expect(result.end).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("detects End key (SS3 F)", () => {
|
|
69
|
+
const result = extendKey("\x1bOF", baseKey);
|
|
70
|
+
expect(result.end).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("returns false for both when regular input", () => {
|
|
74
|
+
const result = extendKey("a", baseKey);
|
|
75
|
+
expect(result.home).toBe(false);
|
|
76
|
+
expect(result.end).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns false for both on empty string", () => {
|
|
80
|
+
const result = extendKey("", baseKey);
|
|
81
|
+
expect(result.home).toBe(false);
|
|
82
|
+
expect(result.end).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("isHomeKey", () => {
|
|
87
|
+
it("returns true for Home sequences", () => {
|
|
88
|
+
expect(isHomeKey("\x1b[H")).toBe(true);
|
|
89
|
+
expect(isHomeKey("\x1b[1~")).toBe(true);
|
|
90
|
+
expect(isHomeKey("\x1bOH")).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("returns false for non-Home input", () => {
|
|
94
|
+
expect(isHomeKey("a")).toBe(false);
|
|
95
|
+
expect(isHomeKey("\x1b[F")).toBe(false); // End key
|
|
96
|
+
expect(isHomeKey("")).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("isEndKey", () => {
|
|
101
|
+
it("returns true for End sequences", () => {
|
|
102
|
+
expect(isEndKey("\x1b[F")).toBe(true);
|
|
103
|
+
expect(isEndKey("\x1b[4~")).toBe(true);
|
|
104
|
+
expect(isEndKey("\x1bOF")).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("returns false for non-End input", () => {
|
|
108
|
+
expect(isEndKey("a")).toBe(false);
|
|
109
|
+
expect(isEndKey("\x1b[H")).toBe(false); // Home key
|
|
110
|
+
expect(isEndKey("")).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("ExtendedKey type", () => {
|
|
115
|
+
it("is assignable from extendKey result", () => {
|
|
116
|
+
const result: ExtendedKey = extendKey("a", baseKey);
|
|
117
|
+
expect(result.home).toBeDefined();
|
|
118
|
+
expect(result.end).toBeDefined();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended Key type for ink's useInput hook.
|
|
3
|
+
*
|
|
4
|
+
* The @jrichman/ink fork (and some versions of ink) don't include `home` and `end`
|
|
5
|
+
* properties in the Key type. This module provides an extended type and helper
|
|
6
|
+
* function to detect these keys from raw input.
|
|
7
|
+
*
|
|
8
|
+
* @module ink-extended
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Key } from "ink";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extended Key type that includes home and end key detection.
|
|
15
|
+
* These are not present in the base ink Key type but are commonly needed
|
|
16
|
+
* for navigation in TUI applications.
|
|
17
|
+
*/
|
|
18
|
+
export interface ExtendedKey extends Key {
|
|
19
|
+
/** Home key was pressed (jump to beginning) */
|
|
20
|
+
home: boolean;
|
|
21
|
+
/** End key was pressed (jump to end) */
|
|
22
|
+
end: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ANSI escape sequences for Home and End keys.
|
|
27
|
+
* Different terminal emulators may send different sequences.
|
|
28
|
+
*/
|
|
29
|
+
const HOME_SEQUENCES = [
|
|
30
|
+
"\x1b[H", // CSI H
|
|
31
|
+
"\x1b[1~", // CSI 1 ~
|
|
32
|
+
"\x1bOH", // SS3 H (application mode)
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const END_SEQUENCES = [
|
|
36
|
+
"\x1b[F", // CSI F
|
|
37
|
+
"\x1b[4~", // CSI 4 ~
|
|
38
|
+
"\x1bOF", // SS3 F (application mode)
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extends the ink Key object with home and end key detection.
|
|
43
|
+
* Call this with the raw input string and the key object from useInput.
|
|
44
|
+
*
|
|
45
|
+
* @param input - The raw input string from useInput
|
|
46
|
+
* @param key - The Key object from useInput
|
|
47
|
+
* @returns Extended key object with home and end properties
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```tsx
|
|
51
|
+
* import { useInput } from 'ink';
|
|
52
|
+
* import { extendKey } from '../types/ink-extended';
|
|
53
|
+
*
|
|
54
|
+
* useInput((input, key) => {
|
|
55
|
+
* const extKey = extendKey(input, key);
|
|
56
|
+
* if (extKey.home) {
|
|
57
|
+
* // Handle Home key
|
|
58
|
+
* }
|
|
59
|
+
* if (extKey.end) {
|
|
60
|
+
* // Handle End key
|
|
61
|
+
* }
|
|
62
|
+
* });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function extendKey(input: string, key: Key): ExtendedKey {
|
|
66
|
+
return {
|
|
67
|
+
...key,
|
|
68
|
+
home: HOME_SEQUENCES.includes(input),
|
|
69
|
+
end: END_SEQUENCES.includes(input),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if the input represents a Home key press.
|
|
75
|
+
* Use this for simple boolean checks without needing the full extended key.
|
|
76
|
+
*/
|
|
77
|
+
export function isHomeKey(input: string): boolean {
|
|
78
|
+
return HOME_SEQUENCES.includes(input);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if the input represents an End key press.
|
|
83
|
+
* Use this for simple boolean checks without needing the full extended key.
|
|
84
|
+
*/
|
|
85
|
+
export function isEndKey(input: string): boolean {
|
|
86
|
+
return END_SEQUENCES.includes(input);
|
|
87
|
+
}
|