@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,588 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Parser Module
|
|
3
|
+
*
|
|
4
|
+
* Tokenizes and parses slash command input into structured command data.
|
|
5
|
+
* Handles:
|
|
6
|
+
* - Command name extraction (from / prefix)
|
|
7
|
+
* - Positional arguments
|
|
8
|
+
* - Named arguments (flags: --flag, -f)
|
|
9
|
+
* - Quote handling (double quotes with escapes, single quotes literal)
|
|
10
|
+
*
|
|
11
|
+
* @module cli/commands/parser
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { CommandErrorCode } from "./types.js";
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// T013: Token Types and Tokenizer
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Token type discriminator
|
|
22
|
+
*/
|
|
23
|
+
export type TokenType = "command" | "string" | "flag" | "value" | "whitespace";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Token produced by the tokenizer
|
|
27
|
+
*/
|
|
28
|
+
export interface Token {
|
|
29
|
+
/** Token type for discrimination */
|
|
30
|
+
readonly type: TokenType;
|
|
31
|
+
/** Token value */
|
|
32
|
+
readonly value: string;
|
|
33
|
+
/** Start position in input (0-indexed) */
|
|
34
|
+
readonly start: number;
|
|
35
|
+
/** End position in input (exclusive) */
|
|
36
|
+
readonly end: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Tokenizer namespace for command input
|
|
41
|
+
*
|
|
42
|
+
* Breaks input into typed tokens for parsing.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const tokens = Tokenizer.tokenize('/help --verbose');
|
|
47
|
+
* // [
|
|
48
|
+
* // { type: 'command', value: 'help', start: 0, end: 5 },
|
|
49
|
+
* // { type: 'whitespace', value: ' ', start: 5, end: 6 },
|
|
50
|
+
* // { type: 'flag', value: '--verbose', start: 6, end: 15 },
|
|
51
|
+
* // ]
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
// biome-ignore lint/complexity/noStaticOnlyClass: Tokenizer provides a logical grouping for tokenization functionality
|
|
55
|
+
export class Tokenizer {
|
|
56
|
+
/**
|
|
57
|
+
* Tokenize input string into Token array
|
|
58
|
+
*
|
|
59
|
+
* @param input - Raw input string
|
|
60
|
+
* @returns Array of tokens with type discrimination
|
|
61
|
+
* @throws Never throws - returns empty array for empty input
|
|
62
|
+
*/
|
|
63
|
+
static tokenize(input: string): Token[] {
|
|
64
|
+
const ctx = new TokenizerContext(input);
|
|
65
|
+
|
|
66
|
+
// Handle leading slash for command
|
|
67
|
+
if (input.startsWith("/")) {
|
|
68
|
+
ctx.tokenizeCommand();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Process rest of input
|
|
72
|
+
while (ctx.position < input.length) {
|
|
73
|
+
ctx.tokenizeNext();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return ctx.tokens;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if character is whitespace
|
|
82
|
+
*/
|
|
83
|
+
function isWhitespace(char: string | undefined): boolean {
|
|
84
|
+
return char === " " || char === "\t" || char === "\n" || char === "\r";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Internal tokenizer context for managing state
|
|
89
|
+
*/
|
|
90
|
+
class TokenizerContext {
|
|
91
|
+
readonly input: string;
|
|
92
|
+
readonly tokens: Token[] = [];
|
|
93
|
+
position = 0;
|
|
94
|
+
|
|
95
|
+
constructor(input: string) {
|
|
96
|
+
this.input = input;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Current character at position */
|
|
100
|
+
get char(): string | undefined {
|
|
101
|
+
return this.input[this.position];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Next character */
|
|
105
|
+
get nextChar(): string | undefined {
|
|
106
|
+
return this.input[this.position + 1];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Tokenize the command name after / */
|
|
110
|
+
tokenizeCommand(): void {
|
|
111
|
+
this.position = 1; // Skip the slash
|
|
112
|
+
const commandStart = this.position;
|
|
113
|
+
|
|
114
|
+
while (this.position < this.input.length && !isWhitespace(this.char)) {
|
|
115
|
+
this.position++;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (this.position > commandStart) {
|
|
119
|
+
this.tokens.push({
|
|
120
|
+
type: "command",
|
|
121
|
+
value: this.input.slice(commandStart, this.position),
|
|
122
|
+
start: 0, // Include the slash in position
|
|
123
|
+
end: this.position,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Tokenize the next token */
|
|
129
|
+
tokenizeNext(): void {
|
|
130
|
+
// Whitespace
|
|
131
|
+
if (isWhitespace(this.char)) {
|
|
132
|
+
this.tokenizeWhitespace();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Long flag (--flag)
|
|
137
|
+
if (this.char === "-" && this.nextChar === "-") {
|
|
138
|
+
this.tokenizeLongFlag();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Short flag (-f)
|
|
143
|
+
if (
|
|
144
|
+
this.char === "-" &&
|
|
145
|
+
this.nextChar &&
|
|
146
|
+
!isWhitespace(this.nextChar) &&
|
|
147
|
+
this.nextChar !== "-"
|
|
148
|
+
) {
|
|
149
|
+
this.tokenizeShortFlag();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Quoted string
|
|
154
|
+
if (this.char === '"' || this.char === "'") {
|
|
155
|
+
this.tokenizeQuotedString();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Unquoted value
|
|
160
|
+
this.tokenizeValue();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private tokenizeWhitespace(): void {
|
|
164
|
+
const start = this.position;
|
|
165
|
+
while (this.position < this.input.length && isWhitespace(this.char)) {
|
|
166
|
+
this.position++;
|
|
167
|
+
}
|
|
168
|
+
this.tokens.push({
|
|
169
|
+
type: "whitespace",
|
|
170
|
+
value: this.input.slice(start, this.position),
|
|
171
|
+
start,
|
|
172
|
+
end: this.position,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private tokenizeLongFlag(): void {
|
|
177
|
+
const start = this.position;
|
|
178
|
+
this.position += 2;
|
|
179
|
+
while (this.position < this.input.length && !isWhitespace(this.char) && this.char !== "=") {
|
|
180
|
+
this.position++;
|
|
181
|
+
}
|
|
182
|
+
this.tokens.push({
|
|
183
|
+
type: "flag",
|
|
184
|
+
value: this.input.slice(start, this.position),
|
|
185
|
+
start,
|
|
186
|
+
end: this.position,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Handle --flag=value
|
|
190
|
+
if (this.char === "=") {
|
|
191
|
+
this.position++; // Skip =
|
|
192
|
+
this.tokenizeFlagValue();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private tokenizeShortFlag(): void {
|
|
197
|
+
const start = this.position;
|
|
198
|
+
this.position += 2; // -f
|
|
199
|
+
this.tokens.push({
|
|
200
|
+
type: "flag",
|
|
201
|
+
value: this.input.slice(start, this.position),
|
|
202
|
+
start,
|
|
203
|
+
end: this.position,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private tokenizeFlagValue(): void {
|
|
208
|
+
const valueStart = this.position;
|
|
209
|
+
// Value might be quoted
|
|
210
|
+
if (this.char === '"' || this.char === "'") {
|
|
211
|
+
const quoteResult = readQuotedString(this.input, this.position);
|
|
212
|
+
this.tokens.push({
|
|
213
|
+
type: "value",
|
|
214
|
+
value: quoteResult.value,
|
|
215
|
+
start: valueStart,
|
|
216
|
+
end: quoteResult.end,
|
|
217
|
+
});
|
|
218
|
+
this.position = quoteResult.end;
|
|
219
|
+
} else {
|
|
220
|
+
while (this.position < this.input.length && !isWhitespace(this.char)) {
|
|
221
|
+
this.position++;
|
|
222
|
+
}
|
|
223
|
+
this.tokens.push({
|
|
224
|
+
type: "value",
|
|
225
|
+
value: this.input.slice(valueStart, this.position),
|
|
226
|
+
start: valueStart,
|
|
227
|
+
end: this.position,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private tokenizeQuotedString(): void {
|
|
233
|
+
const start = this.position;
|
|
234
|
+
const quoteResult = readQuotedString(this.input, this.position);
|
|
235
|
+
this.tokens.push({
|
|
236
|
+
type: "string",
|
|
237
|
+
value: quoteResult.value,
|
|
238
|
+
start,
|
|
239
|
+
end: quoteResult.end,
|
|
240
|
+
});
|
|
241
|
+
this.position = quoteResult.end;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private tokenizeValue(): void {
|
|
245
|
+
const start = this.position;
|
|
246
|
+
while (
|
|
247
|
+
this.position < this.input.length &&
|
|
248
|
+
!isWhitespace(this.char) &&
|
|
249
|
+
this.char !== '"' &&
|
|
250
|
+
this.char !== "'"
|
|
251
|
+
) {
|
|
252
|
+
this.position++;
|
|
253
|
+
}
|
|
254
|
+
if (this.position > start) {
|
|
255
|
+
this.tokens.push({
|
|
256
|
+
type: "value",
|
|
257
|
+
value: this.input.slice(start, this.position),
|
|
258
|
+
start,
|
|
259
|
+
end: this.position,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Read a quoted string from input
|
|
267
|
+
*
|
|
268
|
+
* @param input - Full input string
|
|
269
|
+
* @param start - Position of opening quote
|
|
270
|
+
* @returns Object with parsed value and end position
|
|
271
|
+
*/
|
|
272
|
+
function readQuotedString(
|
|
273
|
+
input: string,
|
|
274
|
+
start: number
|
|
275
|
+
): { value: string; end: number; error?: boolean } {
|
|
276
|
+
const quoteChar = input[start];
|
|
277
|
+
const isDouble = quoteChar === '"';
|
|
278
|
+
let result = "";
|
|
279
|
+
let i = start + 1;
|
|
280
|
+
|
|
281
|
+
while (i < input.length) {
|
|
282
|
+
const char = input[i];
|
|
283
|
+
|
|
284
|
+
// End of quoted string
|
|
285
|
+
if (char === quoteChar) {
|
|
286
|
+
return { value: result, end: i + 1 };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// T014: Escape handling for double quotes only
|
|
290
|
+
if (isDouble && char === "\\") {
|
|
291
|
+
const nextChar = input[i + 1];
|
|
292
|
+
switch (nextChar) {
|
|
293
|
+
case "n":
|
|
294
|
+
result += "\n";
|
|
295
|
+
i += 2;
|
|
296
|
+
continue;
|
|
297
|
+
case "t":
|
|
298
|
+
result += "\t";
|
|
299
|
+
i += 2;
|
|
300
|
+
continue;
|
|
301
|
+
case "\\":
|
|
302
|
+
result += "\\";
|
|
303
|
+
i += 2;
|
|
304
|
+
continue;
|
|
305
|
+
case '"':
|
|
306
|
+
result += '"';
|
|
307
|
+
i += 2;
|
|
308
|
+
continue;
|
|
309
|
+
default:
|
|
310
|
+
// Unknown escape, keep backslash
|
|
311
|
+
result += char;
|
|
312
|
+
i++;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Regular character (including backslash in single quotes)
|
|
318
|
+
result += char;
|
|
319
|
+
i++;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Unclosed quote - return what we have with error flag
|
|
323
|
+
return { value: result, end: i, error: true };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// =============================================================================
|
|
327
|
+
// T015: ParseResult Types
|
|
328
|
+
// =============================================================================
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Successfully parsed command
|
|
332
|
+
*/
|
|
333
|
+
export interface ParsedCommand {
|
|
334
|
+
/** Command name (lowercase, without leading slash) */
|
|
335
|
+
readonly command: string;
|
|
336
|
+
/** Positional arguments in order */
|
|
337
|
+
readonly positional: readonly string[];
|
|
338
|
+
/** Named arguments (flags) */
|
|
339
|
+
readonly named: ReadonlyMap<string, string | boolean>;
|
|
340
|
+
/** Original raw input */
|
|
341
|
+
readonly raw: string;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Parse error with details
|
|
346
|
+
*/
|
|
347
|
+
export interface ParseError {
|
|
348
|
+
/** Error discriminator */
|
|
349
|
+
readonly error: true;
|
|
350
|
+
/** Standardized error code */
|
|
351
|
+
readonly code: CommandErrorCode;
|
|
352
|
+
/** Human-readable error message */
|
|
353
|
+
readonly message: string;
|
|
354
|
+
/** Position in input where error occurred */
|
|
355
|
+
readonly position?: number;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Result of parsing a command
|
|
360
|
+
*/
|
|
361
|
+
export type ParseResult = ParsedCommand | ParseError;
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Type guard for ParseError
|
|
365
|
+
*/
|
|
366
|
+
export function isParseError(result: ParseResult): result is ParseError {
|
|
367
|
+
return "error" in result && result.error === true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Type guard for ParsedCommand
|
|
372
|
+
*/
|
|
373
|
+
export function isParsedCommand(result: ParseResult): result is ParsedCommand {
|
|
374
|
+
return !isParseError(result);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// =============================================================================
|
|
378
|
+
// T015 & T016: CommandParser
|
|
379
|
+
// =============================================================================
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Command parser for slash commands
|
|
383
|
+
*
|
|
384
|
+
* Parses command strings into structured ParsedCommand objects.
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```typescript
|
|
388
|
+
* const parser = new CommandParser();
|
|
389
|
+
*
|
|
390
|
+
* const result = parser.parse('/login provider --store keychain');
|
|
391
|
+
* if (!isParseError(result)) {
|
|
392
|
+
* console.log(result.command); // 'login'
|
|
393
|
+
* console.log(result.positional); // ['provider']
|
|
394
|
+
* console.log(result.named.get('store')); // 'keychain'
|
|
395
|
+
* }
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
export class CommandParser {
|
|
399
|
+
/**
|
|
400
|
+
* Parse a command input string
|
|
401
|
+
*
|
|
402
|
+
* @param input - Raw command input (should start with /)
|
|
403
|
+
* @returns ParseResult - either ParsedCommand or ParseError
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```typescript
|
|
407
|
+
* // Basic command
|
|
408
|
+
* parser.parse('/help'); // { command: 'help', positional: [], named: Map {} }
|
|
409
|
+
*
|
|
410
|
+
* // With arguments
|
|
411
|
+
* parser.parse('/login "my provider" --store keychain');
|
|
412
|
+
* // { command: 'login', positional: ['my provider'], named: Map { 'store' => 'keychain' } }
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
parse(input: string): ParseResult {
|
|
416
|
+
const trimmed = input.trim();
|
|
417
|
+
|
|
418
|
+
// Empty input
|
|
419
|
+
if (!trimmed) {
|
|
420
|
+
return {
|
|
421
|
+
error: true,
|
|
422
|
+
code: "INVALID_ARGUMENT",
|
|
423
|
+
message: "Empty command input",
|
|
424
|
+
position: 0,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Must start with /
|
|
429
|
+
if (!trimmed.startsWith("/")) {
|
|
430
|
+
return {
|
|
431
|
+
error: true,
|
|
432
|
+
code: "INVALID_ARGUMENT",
|
|
433
|
+
message: "Command must start with /",
|
|
434
|
+
position: 0,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Check for unclosed quotes before tokenizing
|
|
439
|
+
const quoteError = this.checkQuotes(trimmed);
|
|
440
|
+
if (quoteError) {
|
|
441
|
+
return quoteError;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Tokenize
|
|
445
|
+
const tokens = Tokenizer.tokenize(trimmed);
|
|
446
|
+
|
|
447
|
+
// Filter out whitespace tokens
|
|
448
|
+
const significantTokens = tokens.filter((t) => t.type !== "whitespace");
|
|
449
|
+
|
|
450
|
+
// Must have at least command
|
|
451
|
+
if (significantTokens.length === 0) {
|
|
452
|
+
return {
|
|
453
|
+
error: true,
|
|
454
|
+
code: "INVALID_ARGUMENT",
|
|
455
|
+
message: "No command specified",
|
|
456
|
+
position: 0,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// First token should be command (safe: checked length > 0 above)
|
|
461
|
+
const commandToken = significantTokens.at(0);
|
|
462
|
+
if (!commandToken || commandToken.type !== "command") {
|
|
463
|
+
return {
|
|
464
|
+
error: true,
|
|
465
|
+
code: "INVALID_ARGUMENT",
|
|
466
|
+
message: "Invalid command format",
|
|
467
|
+
position: 0,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const command = commandToken.value.toLowerCase();
|
|
472
|
+
const { positional, named } = this.processTokens(significantTokens);
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
command,
|
|
476
|
+
positional,
|
|
477
|
+
named,
|
|
478
|
+
raw: input,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Process tokens into positional and named arguments
|
|
484
|
+
*/
|
|
485
|
+
private processTokens(tokens: Token[]): {
|
|
486
|
+
positional: string[];
|
|
487
|
+
named: Map<string, string | boolean>;
|
|
488
|
+
} {
|
|
489
|
+
const positional: string[] = [];
|
|
490
|
+
const named = new Map<string, string | boolean>();
|
|
491
|
+
|
|
492
|
+
let i = 1; // Skip command token
|
|
493
|
+
while (i < tokens.length) {
|
|
494
|
+
const token = tokens.at(i);
|
|
495
|
+
if (!token) {
|
|
496
|
+
i++;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// T016: Flag handling
|
|
501
|
+
if (token.type === "flag") {
|
|
502
|
+
i = this.processFlag(tokens, i, named);
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// String or value = positional argument
|
|
507
|
+
if (token.type === "string" || token.type === "value") {
|
|
508
|
+
positional.push(token.value);
|
|
509
|
+
i++;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Unknown token type - skip
|
|
514
|
+
i++;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return { positional, named };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Process a flag token and its potential value
|
|
522
|
+
*/
|
|
523
|
+
private processFlag(
|
|
524
|
+
tokens: Token[],
|
|
525
|
+
index: number,
|
|
526
|
+
named: Map<string, string | boolean>
|
|
527
|
+
): number {
|
|
528
|
+
const token = tokens.at(index);
|
|
529
|
+
if (!token) return index + 1;
|
|
530
|
+
|
|
531
|
+
const flagName = token.value.startsWith("--") ? token.value.slice(2) : token.value.slice(1);
|
|
532
|
+
|
|
533
|
+
// Look ahead for value
|
|
534
|
+
const nextToken = tokens[index + 1];
|
|
535
|
+
|
|
536
|
+
if (nextToken && (nextToken.type === "value" || nextToken.type === "string")) {
|
|
537
|
+
// Flag with value
|
|
538
|
+
named.set(flagName, nextToken.value);
|
|
539
|
+
return index + 2;
|
|
540
|
+
}
|
|
541
|
+
// Boolean flag
|
|
542
|
+
named.set(flagName, true);
|
|
543
|
+
return index + 1;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Check for unclosed quotes in input
|
|
548
|
+
*/
|
|
549
|
+
private checkQuotes(input: string): ParseError | null {
|
|
550
|
+
let inQuote = false;
|
|
551
|
+
let quoteChar = "";
|
|
552
|
+
let quoteStart = -1;
|
|
553
|
+
|
|
554
|
+
for (let i = 0; i < input.length; i++) {
|
|
555
|
+
const char = input[i];
|
|
556
|
+
|
|
557
|
+
if (!inQuote) {
|
|
558
|
+
if (char === '"' || char === "'") {
|
|
559
|
+
inQuote = true;
|
|
560
|
+
quoteChar = char;
|
|
561
|
+
quoteStart = i;
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
// Check for escape in double quotes
|
|
565
|
+
if (quoteChar === '"' && char === "\\") {
|
|
566
|
+
i++; // Skip escaped character
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
if (char === quoteChar) {
|
|
570
|
+
inQuote = false;
|
|
571
|
+
quoteChar = "";
|
|
572
|
+
quoteStart = -1;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (inQuote) {
|
|
578
|
+
return {
|
|
579
|
+
error: true,
|
|
580
|
+
code: "INVALID_ARGUMENT",
|
|
581
|
+
message: `Unclosed ${quoteChar === '"' ? "double" : "single"} quote`,
|
|
582
|
+
position: quoteStart,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
}
|