@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,483 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax Highlighter Service
|
|
3
|
+
*
|
|
4
|
+
* Provides Shiki-based syntax highlighting with ANSI output for terminal rendering.
|
|
5
|
+
* Uses lazy initialization and singleton pattern to minimize startup overhead.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/services/syntax-highlighter
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import chalk, { type ChalkInstance } from "chalk";
|
|
11
|
+
import {
|
|
12
|
+
type BundledLanguage,
|
|
13
|
+
type BundledTheme,
|
|
14
|
+
createHighlighter,
|
|
15
|
+
type HighlighterGeneric,
|
|
16
|
+
type ThemedToken,
|
|
17
|
+
} from "shiki";
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Types
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Supported languages for syntax highlighting
|
|
25
|
+
*/
|
|
26
|
+
export type SupportedLanguage =
|
|
27
|
+
| "typescript"
|
|
28
|
+
| "javascript"
|
|
29
|
+
| "python"
|
|
30
|
+
| "rust"
|
|
31
|
+
| "go"
|
|
32
|
+
| "json"
|
|
33
|
+
| "yaml"
|
|
34
|
+
| "bash"
|
|
35
|
+
| "markdown"
|
|
36
|
+
| "css"
|
|
37
|
+
| "html"
|
|
38
|
+
| "sql"
|
|
39
|
+
| "dockerfile"
|
|
40
|
+
| "toml"
|
|
41
|
+
| "tsx"
|
|
42
|
+
| "jsx";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Options for highlighting code
|
|
46
|
+
*/
|
|
47
|
+
export interface HighlightOptions {
|
|
48
|
+
/** Programming language (auto-detected if not provided) */
|
|
49
|
+
readonly lang?: string;
|
|
50
|
+
/** Include line numbers in output */
|
|
51
|
+
readonly lineNumbers?: boolean;
|
|
52
|
+
/** Lines to highlight (1-indexed) */
|
|
53
|
+
readonly highlightLines?: number[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Result of syntax highlighting
|
|
58
|
+
*/
|
|
59
|
+
export interface SyntaxHighlightResult {
|
|
60
|
+
/** ANSI-colored output string */
|
|
61
|
+
readonly output: string;
|
|
62
|
+
/** Detected or specified language */
|
|
63
|
+
readonly language: string;
|
|
64
|
+
/** Whether highlighting was successful */
|
|
65
|
+
readonly success: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// Constants
|
|
70
|
+
// =============================================================================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Default languages to preload
|
|
74
|
+
*/
|
|
75
|
+
const DEFAULT_LANGUAGES: BundledLanguage[] = [
|
|
76
|
+
"typescript",
|
|
77
|
+
"javascript",
|
|
78
|
+
"tsx",
|
|
79
|
+
"jsx",
|
|
80
|
+
"python",
|
|
81
|
+
"rust",
|
|
82
|
+
"go",
|
|
83
|
+
"json",
|
|
84
|
+
"yaml",
|
|
85
|
+
"bash",
|
|
86
|
+
"markdown",
|
|
87
|
+
"css",
|
|
88
|
+
"html",
|
|
89
|
+
"sql",
|
|
90
|
+
"dockerfile",
|
|
91
|
+
"toml",
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Theme to use for highlighting
|
|
96
|
+
*/
|
|
97
|
+
const THEME = "github-dark";
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Language aliases for common variations
|
|
101
|
+
*/
|
|
102
|
+
const LANGUAGE_ALIASES: Record<string, BundledLanguage> = {
|
|
103
|
+
js: "javascript",
|
|
104
|
+
ts: "typescript",
|
|
105
|
+
py: "python",
|
|
106
|
+
rb: "ruby",
|
|
107
|
+
sh: "bash",
|
|
108
|
+
shell: "bash",
|
|
109
|
+
zsh: "bash",
|
|
110
|
+
fish: "bash",
|
|
111
|
+
yml: "yaml",
|
|
112
|
+
md: "markdown",
|
|
113
|
+
rs: "rust",
|
|
114
|
+
golang: "go",
|
|
115
|
+
docker: "dockerfile",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* File extension to language mapping for auto-detection
|
|
120
|
+
*/
|
|
121
|
+
const EXTENSION_TO_LANGUAGE: Record<string, BundledLanguage> = {
|
|
122
|
+
".ts": "typescript",
|
|
123
|
+
".tsx": "tsx",
|
|
124
|
+
".js": "javascript",
|
|
125
|
+
".jsx": "jsx",
|
|
126
|
+
".mjs": "javascript",
|
|
127
|
+
".cjs": "javascript",
|
|
128
|
+
".py": "python",
|
|
129
|
+
".rs": "rust",
|
|
130
|
+
".go": "go",
|
|
131
|
+
".json": "json",
|
|
132
|
+
".yaml": "yaml",
|
|
133
|
+
".yml": "yaml",
|
|
134
|
+
".md": "markdown",
|
|
135
|
+
".css": "css",
|
|
136
|
+
".html": "html",
|
|
137
|
+
".htm": "html",
|
|
138
|
+
".sql": "sql",
|
|
139
|
+
".sh": "bash",
|
|
140
|
+
".bash": "bash",
|
|
141
|
+
".zsh": "bash",
|
|
142
|
+
".toml": "toml",
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Patterns for language auto-detection from code content
|
|
147
|
+
*/
|
|
148
|
+
const LANGUAGE_PATTERNS: Array<{ pattern: RegExp; lang: BundledLanguage }> = [
|
|
149
|
+
// TypeScript/JavaScript
|
|
150
|
+
{ pattern: /^import\s+.*\s+from\s+['"]|^export\s+(default\s+)?/m, lang: "typescript" },
|
|
151
|
+
{ pattern: /^const\s+\w+\s*:\s*\w+|^interface\s+\w+|^type\s+\w+\s*=/m, lang: "typescript" },
|
|
152
|
+
{ pattern: /^function\s+\w+|^const\s+\w+\s*=\s*\(|^let\s+|^var\s+/m, lang: "javascript" },
|
|
153
|
+
// Python
|
|
154
|
+
{ pattern: /^def\s+\w+\s*\(|^class\s+\w+|^import\s+\w+|^from\s+\w+\s+import/m, lang: "python" },
|
|
155
|
+
// Rust
|
|
156
|
+
{ pattern: /^fn\s+\w+|^struct\s+\w+|^impl\s+|^use\s+\w+::|^mod\s+\w+/m, lang: "rust" },
|
|
157
|
+
// Go
|
|
158
|
+
{ pattern: /^package\s+\w+|^func\s+\w+|^import\s*\(|^type\s+\w+\s+struct/m, lang: "go" },
|
|
159
|
+
// JSON
|
|
160
|
+
{ pattern: /^\s*\{[\s\S]*"[\w-]+":\s*[{"[\d]/m, lang: "json" },
|
|
161
|
+
// YAML
|
|
162
|
+
{ pattern: /^[\w-]+:\s*[|>]?\s*$/m, lang: "yaml" },
|
|
163
|
+
// Bash
|
|
164
|
+
{ pattern: /^#!\s*\/bin\/(ba)?sh|^#!/m, lang: "bash" },
|
|
165
|
+
{ pattern: /^\s*(if|for|while)\s+\[|^\s*echo\s+|^\s*export\s+\w+=/m, lang: "bash" },
|
|
166
|
+
// SQL
|
|
167
|
+
{ pattern: /^SELECT\s+|^INSERT\s+INTO|^CREATE\s+(TABLE|DATABASE)|^ALTER\s+TABLE/im, lang: "sql" },
|
|
168
|
+
// Dockerfile
|
|
169
|
+
{ pattern: /^FROM\s+\w+|^RUN\s+|^CMD\s+|^ENTRYPOINT\s+/m, lang: "dockerfile" },
|
|
170
|
+
// HTML
|
|
171
|
+
{ pattern: /^<!DOCTYPE\s+html|^<html|^<head|^<body/im, lang: "html" },
|
|
172
|
+
// CSS
|
|
173
|
+
{ pattern: /^[.#]?\w+\s*\{[\s\S]*:[^}]+\}/m, lang: "css" },
|
|
174
|
+
// Markdown
|
|
175
|
+
{ pattern: /^#\s+\w+|^\*\*\w+\*\*|^\[.+\]\(.+\)/m, lang: "markdown" },
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
// =============================================================================
|
|
179
|
+
// Singleton State
|
|
180
|
+
// =============================================================================
|
|
181
|
+
|
|
182
|
+
let highlighterInstance: HighlighterGeneric<BundledLanguage, BundledTheme> | null = null;
|
|
183
|
+
let initPromise: Promise<HighlighterGeneric<BundledLanguage, BundledTheme>> | null = null;
|
|
184
|
+
|
|
185
|
+
// =============================================================================
|
|
186
|
+
// Color Conversion
|
|
187
|
+
// =============================================================================
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Convert hex color to chalk color function
|
|
191
|
+
*/
|
|
192
|
+
function hexToChalk(hex: string | undefined): ChalkInstance {
|
|
193
|
+
if (!hex) return chalk;
|
|
194
|
+
|
|
195
|
+
// Remove # if present
|
|
196
|
+
const cleanHex = hex.replace(/^#/, "");
|
|
197
|
+
|
|
198
|
+
// Parse hex to RGB
|
|
199
|
+
const r = Number.parseInt(cleanHex.slice(0, 2), 16);
|
|
200
|
+
const g = Number.parseInt(cleanHex.slice(2, 4), 16);
|
|
201
|
+
const b = Number.parseInt(cleanHex.slice(4, 6), 16);
|
|
202
|
+
|
|
203
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
|
204
|
+
return chalk;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return chalk.rgb(r, g, b);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Convert Shiki tokens to ANSI-colored string
|
|
212
|
+
*/
|
|
213
|
+
function tokensToAnsi(tokens: ThemedToken[][]): string {
|
|
214
|
+
const lines: string[] = [];
|
|
215
|
+
|
|
216
|
+
for (const lineTokens of tokens) {
|
|
217
|
+
let line = "";
|
|
218
|
+
for (const token of lineTokens) {
|
|
219
|
+
const colorFn = hexToChalk(token.color);
|
|
220
|
+
// Apply font style if present
|
|
221
|
+
let styled = colorFn;
|
|
222
|
+
if (token.fontStyle) {
|
|
223
|
+
if (token.fontStyle & 1) styled = styled.italic;
|
|
224
|
+
if (token.fontStyle & 2) styled = styled.bold;
|
|
225
|
+
if (token.fontStyle & 4) styled = styled.underline;
|
|
226
|
+
}
|
|
227
|
+
line += styled(token.content);
|
|
228
|
+
}
|
|
229
|
+
lines.push(line);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return lines.join("\n");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// =============================================================================
|
|
236
|
+
// Public API
|
|
237
|
+
// =============================================================================
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Initialize the syntax highlighter
|
|
241
|
+
*
|
|
242
|
+
* Uses lazy loading - only initializes when first needed.
|
|
243
|
+
* Returns cached instance on subsequent calls.
|
|
244
|
+
*/
|
|
245
|
+
export async function initializeHighlighter(): Promise<
|
|
246
|
+
HighlighterGeneric<BundledLanguage, BundledTheme>
|
|
247
|
+
> {
|
|
248
|
+
// Return cached instance
|
|
249
|
+
if (highlighterInstance) {
|
|
250
|
+
return highlighterInstance;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Return pending initialization
|
|
254
|
+
if (initPromise) {
|
|
255
|
+
return initPromise;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Start initialization
|
|
259
|
+
initPromise = createHighlighter({
|
|
260
|
+
themes: [THEME],
|
|
261
|
+
langs: DEFAULT_LANGUAGES,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
highlighterInstance = await initPromise;
|
|
266
|
+
return highlighterInstance;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
initPromise = null;
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Detect language from code content
|
|
275
|
+
*/
|
|
276
|
+
export function detectLanguage(code: string, filename?: string): BundledLanguage | undefined {
|
|
277
|
+
// Try filename extension first
|
|
278
|
+
if (filename) {
|
|
279
|
+
const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
|
|
280
|
+
const langFromExt = EXTENSION_TO_LANGUAGE[ext];
|
|
281
|
+
if (langFromExt) return langFromExt;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Try content patterns
|
|
285
|
+
for (const { pattern, lang } of LANGUAGE_PATTERNS) {
|
|
286
|
+
if (pattern.test(code)) {
|
|
287
|
+
return lang;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Resolve language alias to canonical name
|
|
296
|
+
*/
|
|
297
|
+
export function resolveLanguage(lang: string): BundledLanguage {
|
|
298
|
+
const normalized = lang.toLowerCase().trim();
|
|
299
|
+
return LANGUAGE_ALIASES[normalized] ?? (normalized as BundledLanguage);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Check if a language is supported
|
|
304
|
+
*/
|
|
305
|
+
export function isLanguageSupported(lang: string): boolean {
|
|
306
|
+
const resolved = resolveLanguage(lang);
|
|
307
|
+
return DEFAULT_LANGUAGES.includes(resolved) || Object.values(LANGUAGE_ALIASES).includes(resolved);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Highlight code and return ANSI-colored string
|
|
312
|
+
*
|
|
313
|
+
* @param code - Source code to highlight
|
|
314
|
+
* @param options - Highlighting options
|
|
315
|
+
* @returns Highlighted code result
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```ts
|
|
319
|
+
* const result = await highlightCode('const x = 42;', { lang: 'typescript' });
|
|
320
|
+
* console.log(result.output);
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
export async function highlightCode(
|
|
324
|
+
code: string,
|
|
325
|
+
options: HighlightOptions = {}
|
|
326
|
+
): Promise<SyntaxHighlightResult> {
|
|
327
|
+
const { lang, lineNumbers = false, highlightLines = [] } = options;
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
const highlighter = await initializeHighlighter();
|
|
331
|
+
|
|
332
|
+
// Resolve language
|
|
333
|
+
let resolvedLang: BundledLanguage = "text" as BundledLanguage;
|
|
334
|
+
if (lang) {
|
|
335
|
+
resolvedLang = resolveLanguage(lang);
|
|
336
|
+
} else {
|
|
337
|
+
const detected = detectLanguage(code);
|
|
338
|
+
if (detected) resolvedLang = detected;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Get tokens
|
|
342
|
+
const tokens = highlighter.codeToTokensBase(code, {
|
|
343
|
+
lang: resolvedLang,
|
|
344
|
+
theme: THEME,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Convert to ANSI
|
|
348
|
+
let output = tokensToAnsi(tokens);
|
|
349
|
+
|
|
350
|
+
// Add line numbers if requested
|
|
351
|
+
if (lineNumbers) {
|
|
352
|
+
const lines = output.split("\n");
|
|
353
|
+
const width = String(lines.length).length;
|
|
354
|
+
const highlightSet = new Set(highlightLines);
|
|
355
|
+
|
|
356
|
+
output = lines
|
|
357
|
+
.map((line, i) => {
|
|
358
|
+
const num = i + 1;
|
|
359
|
+
const numStr = String(num).padStart(width, " ");
|
|
360
|
+
const prefix = highlightSet.has(num)
|
|
361
|
+
? chalk.yellow.bold(`▶ ${numStr} │ `)
|
|
362
|
+
: chalk.dim(` ${numStr} │ `);
|
|
363
|
+
return prefix + line;
|
|
364
|
+
})
|
|
365
|
+
.join("\n");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
output,
|
|
370
|
+
language: resolvedLang,
|
|
371
|
+
success: true,
|
|
372
|
+
};
|
|
373
|
+
} catch (_error) {
|
|
374
|
+
// Fallback to plain text on error
|
|
375
|
+
let output = code;
|
|
376
|
+
|
|
377
|
+
if (lineNumbers) {
|
|
378
|
+
const lines = code.split("\n");
|
|
379
|
+
const width = String(lines.length).length;
|
|
380
|
+
output = lines
|
|
381
|
+
.map((line, i) => {
|
|
382
|
+
const numStr = String(i + 1).padStart(width, " ");
|
|
383
|
+
return chalk.dim(` ${numStr} │ `) + line;
|
|
384
|
+
})
|
|
385
|
+
.join("\n");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
output,
|
|
390
|
+
language: lang ?? "text",
|
|
391
|
+
success: false,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Synchronous highlight using cached highlighter
|
|
398
|
+
*
|
|
399
|
+
* Returns null if highlighter is not yet initialized.
|
|
400
|
+
* Use this when you need synchronous rendering and can fall back to plain text.
|
|
401
|
+
*/
|
|
402
|
+
export function highlightCodeSync(
|
|
403
|
+
code: string,
|
|
404
|
+
options: HighlightOptions = {}
|
|
405
|
+
): SyntaxHighlightResult | null {
|
|
406
|
+
if (!highlighterInstance) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const { lang, lineNumbers = false, highlightLines = [] } = options;
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
// Resolve language
|
|
414
|
+
let resolvedLang: BundledLanguage = "text" as BundledLanguage;
|
|
415
|
+
if (lang) {
|
|
416
|
+
resolvedLang = resolveLanguage(lang);
|
|
417
|
+
} else {
|
|
418
|
+
const detected = detectLanguage(code);
|
|
419
|
+
if (detected) resolvedLang = detected;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Get tokens
|
|
423
|
+
const tokens = highlighterInstance.codeToTokensBase(code, {
|
|
424
|
+
lang: resolvedLang,
|
|
425
|
+
theme: THEME,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Convert to ANSI
|
|
429
|
+
let output = tokensToAnsi(tokens);
|
|
430
|
+
|
|
431
|
+
// Add line numbers if requested
|
|
432
|
+
if (lineNumbers) {
|
|
433
|
+
const lines = output.split("\n");
|
|
434
|
+
const width = String(lines.length).length;
|
|
435
|
+
const highlightSet = new Set(highlightLines);
|
|
436
|
+
|
|
437
|
+
output = lines
|
|
438
|
+
.map((line, i) => {
|
|
439
|
+
const num = i + 1;
|
|
440
|
+
const numStr = String(num).padStart(width, " ");
|
|
441
|
+
const prefix = highlightSet.has(num)
|
|
442
|
+
? chalk.yellow.bold(`▶ ${numStr} │ `)
|
|
443
|
+
: chalk.dim(` ${numStr} │ `);
|
|
444
|
+
return prefix + line;
|
|
445
|
+
})
|
|
446
|
+
.join("\n");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
output,
|
|
451
|
+
language: resolvedLang,
|
|
452
|
+
success: true,
|
|
453
|
+
};
|
|
454
|
+
} catch {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Check if the highlighter is initialized
|
|
461
|
+
*/
|
|
462
|
+
export function isHighlighterReady(): boolean {
|
|
463
|
+
return highlighterInstance !== null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Preload the highlighter in the background
|
|
468
|
+
*
|
|
469
|
+
* Call this early in app startup to warm up the cache.
|
|
470
|
+
*/
|
|
471
|
+
export function preloadHighlighter(): void {
|
|
472
|
+
// Fire and forget
|
|
473
|
+
initializeHighlighter().catch(() => {
|
|
474
|
+
// Silently ignore preload errors
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Get list of supported languages
|
|
480
|
+
*/
|
|
481
|
+
export function getSupportedLanguages(): readonly string[] {
|
|
482
|
+
return [...DEFAULT_LANGUAGES, ...Object.keys(LANGUAGE_ALIASES)];
|
|
483
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Slash Commands
|
|
3
|
+
*
|
|
4
|
+
* Provides TUI-specific slash command utilities.
|
|
5
|
+
* Re-exports commonly used command types and utilities.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/slash-commands
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Re-export command types from the commands module
|
|
11
|
+
export type { CommandContext, CommandResult, SlashCommand } from "../commands/types.js";
|
|
12
|
+
export { error, interactive, pending, success } from "../commands/types.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Theme
|
|
3
|
+
*
|
|
4
|
+
* Theme configuration and styling utilities for the Vellum TUI.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Re-export types from shared for convenience
|
|
8
|
+
export type { ThemeContextValue, ThemeName, VellumTheme } from "@vellum/shared";
|
|
9
|
+
// Theme provider and hook
|
|
10
|
+
export {
|
|
11
|
+
ThemeContext,
|
|
12
|
+
ThemeProvider,
|
|
13
|
+
type ThemeProviderProps,
|
|
14
|
+
useTheme,
|
|
15
|
+
} from "./provider.js";
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Provider and Hook
|
|
3
|
+
*
|
|
4
|
+
* Provides theming context for the Vellum TUI with support for
|
|
5
|
+
* preset themes and runtime theme switching.
|
|
6
|
+
*
|
|
7
|
+
* @module tui/theme/provider
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ThemeContextValue, ThemePreset, VellumTheme } from "@vellum/shared";
|
|
11
|
+
import { defaultTheme, getThemeOrDefault, type ThemeName, themes } from "@vellum/shared";
|
|
12
|
+
import React, {
|
|
13
|
+
createContext,
|
|
14
|
+
type ReactNode,
|
|
15
|
+
useCallback,
|
|
16
|
+
useContext,
|
|
17
|
+
useMemo,
|
|
18
|
+
useState,
|
|
19
|
+
} from "react";
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Context
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* React context for theme state
|
|
27
|
+
*
|
|
28
|
+
* Initialized as undefined to detect usage outside provider
|
|
29
|
+
*/
|
|
30
|
+
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
31
|
+
|
|
32
|
+
// =============================================================================
|
|
33
|
+
// Hook
|
|
34
|
+
// =============================================================================
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Hook to access the current theme
|
|
38
|
+
*
|
|
39
|
+
* Must be used within a ThemeProvider component.
|
|
40
|
+
*
|
|
41
|
+
* @returns The current theme context value
|
|
42
|
+
* @throws Error if used outside ThemeProvider
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* function MyComponent() {
|
|
47
|
+
* const { theme, setTheme } = useTheme();
|
|
48
|
+
* return <Box color={theme.colors.primary}>Hello</Box>;
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function useTheme(): ThemeContextValue {
|
|
53
|
+
const context = useContext(ThemeContext);
|
|
54
|
+
|
|
55
|
+
if (context === undefined) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"useTheme must be used within a ThemeProvider. " +
|
|
58
|
+
"Ensure your component is wrapped in <ThemeProvider>."
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return context;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Provider Props
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Props for the ThemeProvider component
|
|
71
|
+
*/
|
|
72
|
+
export interface ThemeProviderProps {
|
|
73
|
+
/**
|
|
74
|
+
* Initial theme - can be a theme name string or a VellumTheme object
|
|
75
|
+
*
|
|
76
|
+
* @default "dark"
|
|
77
|
+
*/
|
|
78
|
+
readonly theme?: ThemeName | ThemePreset | VellumTheme;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Children to render within the theme context
|
|
82
|
+
*/
|
|
83
|
+
readonly children: ReactNode;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// =============================================================================
|
|
87
|
+
// Helper Functions
|
|
88
|
+
// =============================================================================
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resolve a theme input to a VellumTheme object
|
|
92
|
+
*
|
|
93
|
+
* @param themeInput - Theme name, preset, or object
|
|
94
|
+
* @returns Resolved VellumTheme object
|
|
95
|
+
*/
|
|
96
|
+
function resolveTheme(themeInput: ThemeName | ThemePreset | VellumTheme | undefined): VellumTheme {
|
|
97
|
+
// Handle undefined - use default
|
|
98
|
+
if (themeInput === undefined) {
|
|
99
|
+
return defaultTheme;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Handle string theme names (both ThemeName and ThemePreset)
|
|
103
|
+
if (typeof themeInput === "string") {
|
|
104
|
+
return getThemeOrDefault(themeInput);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Handle VellumTheme objects - return as-is
|
|
108
|
+
return themeInput;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the opposite mode theme name
|
|
113
|
+
*
|
|
114
|
+
* @param currentTheme - Current theme
|
|
115
|
+
* @returns Theme name with opposite mode
|
|
116
|
+
*/
|
|
117
|
+
function getOppositeMode(currentTheme: VellumTheme): ThemeName {
|
|
118
|
+
const targetMode = currentTheme.mode === "dark" ? "light" : "dark";
|
|
119
|
+
|
|
120
|
+
// Try to find a theme with the same base name but different mode
|
|
121
|
+
// For example: dracula -> light, light -> dark
|
|
122
|
+
const themeNames = Object.keys(themes) as ThemeName[];
|
|
123
|
+
|
|
124
|
+
// First, look for a theme with opposite mode
|
|
125
|
+
const sameFamily = themeNames.find((name) => {
|
|
126
|
+
const theme = themes[name];
|
|
127
|
+
return theme.mode === targetMode;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return sameFamily ?? (targetMode === "dark" ? "dark" : "light");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// =============================================================================
|
|
134
|
+
// Provider Component
|
|
135
|
+
// =============================================================================
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Theme provider component
|
|
139
|
+
*
|
|
140
|
+
* Provides theme context to all child components, enabling access to
|
|
141
|
+
* the current theme via the useTheme hook.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```tsx
|
|
145
|
+
* // Using a theme name
|
|
146
|
+
* <ThemeProvider theme="dark">
|
|
147
|
+
* <App />
|
|
148
|
+
* </ThemeProvider>
|
|
149
|
+
*
|
|
150
|
+
* // Using a theme preset
|
|
151
|
+
* <ThemeProvider theme="dracula">
|
|
152
|
+
* <App />
|
|
153
|
+
* </ThemeProvider>
|
|
154
|
+
*
|
|
155
|
+
* // Using a custom theme object
|
|
156
|
+
* <ThemeProvider theme={customTheme}>
|
|
157
|
+
* <App />
|
|
158
|
+
* </ThemeProvider>
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export function ThemeProvider({
|
|
162
|
+
theme: initialTheme,
|
|
163
|
+
children,
|
|
164
|
+
}: ThemeProviderProps): React.JSX.Element {
|
|
165
|
+
// State for the current theme
|
|
166
|
+
const [currentTheme, setCurrentTheme] = useState<VellumTheme>(() => resolveTheme(initialTheme));
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Set theme by name or object
|
|
170
|
+
*/
|
|
171
|
+
const setTheme = useCallback((newTheme: ThemePreset | VellumTheme): void => {
|
|
172
|
+
const resolved = resolveTheme(newTheme);
|
|
173
|
+
setCurrentTheme(resolved);
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Toggle between dark and light modes
|
|
178
|
+
*/
|
|
179
|
+
const toggleMode = useCallback((): void => {
|
|
180
|
+
const oppositeName = getOppositeMode(currentTheme);
|
|
181
|
+
const oppositeTheme = themes[oppositeName];
|
|
182
|
+
setCurrentTheme(oppositeTheme);
|
|
183
|
+
}, [currentTheme]);
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Memoized context value
|
|
187
|
+
*/
|
|
188
|
+
const contextValue = useMemo<ThemeContextValue>(
|
|
189
|
+
() => ({
|
|
190
|
+
theme: currentTheme,
|
|
191
|
+
themeName: currentTheme.name,
|
|
192
|
+
setTheme,
|
|
193
|
+
toggleMode,
|
|
194
|
+
}),
|
|
195
|
+
[currentTheme, setTheme, toggleMode]
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
return <ThemeContext value={contextValue}>{children}</ThemeContext>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// =============================================================================
|
|
202
|
+
// Exports
|
|
203
|
+
// =============================================================================
|
|
204
|
+
|
|
205
|
+
export { ThemeContext };
|
|
206
|
+
export type { ThemeContextValue };
|