nova-terminal-ai 0.3.4
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/README.md +84 -0
- package/bin/nova +38 -0
- package/bin/nova.js +11 -0
- package/dist/commands/SmartCompletion.d.ts +71 -0
- package/dist/commands/SmartCompletion.d.ts.map +1 -0
- package/dist/commands/SmartCompletion.js +377 -0
- package/dist/commands/SmartCompletion.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/packages/cli/src/commands/SmartCompletion.d.ts +71 -0
- package/dist/packages/cli/src/commands/SmartCompletion.d.ts.map +1 -0
- package/dist/packages/cli/src/commands/SmartCompletion.js +377 -0
- package/dist/packages/cli/src/commands/SmartCompletion.js.map +1 -0
- package/dist/packages/cli/src/index.d.ts +2 -0
- package/dist/packages/cli/src/index.d.ts.map +1 -0
- package/dist/packages/cli/src/index.js +5 -0
- package/dist/packages/cli/src/index.js.map +1 -0
- package/dist/packages/cli/src/startup/IFlowRepl.d.ts +50 -0
- package/dist/packages/cli/src/startup/IFlowRepl.d.ts.map +1 -0
- package/dist/packages/cli/src/startup/IFlowRepl.js +178 -0
- package/dist/packages/cli/src/startup/IFlowRepl.js.map +1 -0
- package/dist/packages/cli/src/startup/InkBasedRepl.d.ts +151 -0
- package/dist/packages/cli/src/startup/InkBasedRepl.d.ts.map +1 -0
- package/dist/packages/cli/src/startup/InkBasedRepl.js +1415 -0
- package/dist/packages/cli/src/startup/InkBasedRepl.js.map +1 -0
- package/dist/packages/cli/src/startup/InteractiveRepl.d.ts +141 -0
- package/dist/packages/cli/src/startup/InteractiveRepl.d.ts.map +1 -0
- package/dist/packages/cli/src/startup/InteractiveRepl.js +2561 -0
- package/dist/packages/cli/src/startup/InteractiveRepl.js.map +1 -0
- package/dist/packages/cli/src/startup/NovaApp.d.ts +57 -0
- package/dist/packages/cli/src/startup/NovaApp.d.ts.map +1 -0
- package/dist/packages/cli/src/startup/NovaApp.js +1978 -0
- package/dist/packages/cli/src/startup/NovaApp.js.map +1 -0
- package/dist/packages/cli/src/startup/index.d.ts +5 -0
- package/dist/packages/cli/src/startup/index.d.ts.map +1 -0
- package/dist/packages/cli/src/startup/index.js +4 -0
- package/dist/packages/cli/src/startup/index.js.map +1 -0
- package/dist/packages/cli/src/startup/parseArgs.d.ts +47 -0
- package/dist/packages/cli/src/startup/parseArgs.d.ts.map +1 -0
- package/dist/packages/cli/src/startup/parseArgs.js +262 -0
- package/dist/packages/cli/src/startup/parseArgs.js.map +1 -0
- package/dist/packages/cli/src/ui/IFlowDropdown.d.ts +63 -0
- package/dist/packages/cli/src/ui/IFlowDropdown.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/IFlowDropdown.js +362 -0
- package/dist/packages/cli/src/ui/IFlowDropdown.js.map +1 -0
- package/dist/packages/cli/src/ui/ModernReplUI.d.ts +55 -0
- package/dist/packages/cli/src/ui/ModernReplUI.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/ModernReplUI.js +207 -0
- package/dist/packages/cli/src/ui/ModernReplUI.js.map +1 -0
- package/dist/packages/cli/src/ui/SimpleSelector2.d.ts +28 -0
- package/dist/packages/cli/src/ui/SimpleSelector2.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/SimpleSelector2.js +181 -0
- package/dist/packages/cli/src/ui/SimpleSelector2.js.map +1 -0
- package/dist/packages/cli/src/ui/components/ActiveCursor.d.ts +128 -0
- package/dist/packages/cli/src/ui/components/ActiveCursor.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/ActiveCursor.js +273 -0
- package/dist/packages/cli/src/ui/components/ActiveCursor.js.map +1 -0
- package/dist/packages/cli/src/ui/components/ConfirmDialog.d.ts +51 -0
- package/dist/packages/cli/src/ui/components/ConfirmDialog.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/ConfirmDialog.js +147 -0
- package/dist/packages/cli/src/ui/components/ConfirmDialog.js.map +1 -0
- package/dist/packages/cli/src/ui/components/ErrorPanel.d.ts +33 -0
- package/dist/packages/cli/src/ui/components/ErrorPanel.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/ErrorPanel.js +309 -0
- package/dist/packages/cli/src/ui/components/ErrorPanel.js.map +1 -0
- package/dist/packages/cli/src/ui/components/InkAppRunner.d.ts +18 -0
- package/dist/packages/cli/src/ui/components/InkAppRunner.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/InkAppRunner.js +33 -0
- package/dist/packages/cli/src/ui/components/InkAppRunner.js.map +1 -0
- package/dist/packages/cli/src/ui/components/InkComponents.d.ts +126 -0
- package/dist/packages/cli/src/ui/components/InkComponents.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/InkComponents.js +216 -0
- package/dist/packages/cli/src/ui/components/InkComponents.js.map +1 -0
- package/dist/packages/cli/src/ui/components/NovaInkApp.d.ts +11 -0
- package/dist/packages/cli/src/ui/components/NovaInkApp.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/NovaInkApp.js +148 -0
- package/dist/packages/cli/src/ui/components/NovaInkApp.js.map +1 -0
- package/dist/packages/cli/src/ui/components/ProgressBar.d.ts +65 -0
- package/dist/packages/cli/src/ui/components/ProgressBar.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/ProgressBar.js +135 -0
- package/dist/packages/cli/src/ui/components/ProgressBar.js.map +1 -0
- package/dist/packages/cli/src/ui/components/ProgressIndicator.d.ts +41 -0
- package/dist/packages/cli/src/ui/components/ProgressIndicator.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/ProgressIndicator.js +235 -0
- package/dist/packages/cli/src/ui/components/ProgressIndicator.js.map +1 -0
- package/dist/packages/cli/src/ui/components/QuickActions.d.ts +36 -0
- package/dist/packages/cli/src/ui/components/QuickActions.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/QuickActions.js +328 -0
- package/dist/packages/cli/src/ui/components/QuickActions.js.map +1 -0
- package/dist/packages/cli/src/ui/components/SimpleErrorPanel.d.ts +16 -0
- package/dist/packages/cli/src/ui/components/SimpleErrorPanel.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/SimpleErrorPanel.js +193 -0
- package/dist/packages/cli/src/ui/components/SimpleErrorPanel.js.map +1 -0
- package/dist/packages/cli/src/ui/components/StatusBar.d.ts +30 -0
- package/dist/packages/cli/src/ui/components/StatusBar.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/StatusBar.js +154 -0
- package/dist/packages/cli/src/ui/components/StatusBar.js.map +1 -0
- package/dist/packages/cli/src/ui/components/ThinkingBlockRenderer.d.ts +109 -0
- package/dist/packages/cli/src/ui/components/ThinkingBlockRenderer.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/ThinkingBlockRenderer.js +335 -0
- package/dist/packages/cli/src/ui/components/ThinkingBlockRenderer.js.map +1 -0
- package/dist/packages/cli/src/ui/components/ThinkingContentDisplay.d.ts +59 -0
- package/dist/packages/cli/src/ui/components/ThinkingContentDisplay.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/ThinkingContentDisplay.js +172 -0
- package/dist/packages/cli/src/ui/components/ThinkingContentDisplay.js.map +1 -0
- package/dist/packages/cli/src/ui/components/TodoProgressPanel.d.ts +91 -0
- package/dist/packages/cli/src/ui/components/TodoProgressPanel.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/TodoProgressPanel.js +284 -0
- package/dist/packages/cli/src/ui/components/TodoProgressPanel.js.map +1 -0
- package/dist/packages/cli/src/ui/components/ToolCallStatusDisplay.d.ts +89 -0
- package/dist/packages/cli/src/ui/components/ToolCallStatusDisplay.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/ToolCallStatusDisplay.js +246 -0
- package/dist/packages/cli/src/ui/components/ToolCallStatusDisplay.js.map +1 -0
- package/dist/packages/cli/src/ui/components/UserMessageHighlight.d.ts +48 -0
- package/dist/packages/cli/src/ui/components/UserMessageHighlight.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/UserMessageHighlight.js +196 -0
- package/dist/packages/cli/src/ui/components/UserMessageHighlight.js.map +1 -0
- package/dist/packages/cli/src/ui/components/index.d.ts +17 -0
- package/dist/packages/cli/src/ui/components/index.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/components/index.js +18 -0
- package/dist/packages/cli/src/ui/components/index.js.map +1 -0
- package/dist/packages/cli/src/ui/ink-prototype.d.ts +3 -0
- package/dist/packages/cli/src/ui/ink-prototype.d.ts.map +1 -0
- package/dist/packages/cli/src/ui/ink-prototype.js +160 -0
- package/dist/packages/cli/src/ui/ink-prototype.js.map +1 -0
- package/dist/packages/cli/src/utils/CliUI.d.ts +163 -0
- package/dist/packages/cli/src/utils/CliUI.d.ts.map +1 -0
- package/dist/packages/cli/src/utils/CliUI.js +292 -0
- package/dist/packages/cli/src/utils/CliUI.js.map +1 -0
- package/dist/packages/cli/src/utils/CompletionHelper.d.ts +112 -0
- package/dist/packages/cli/src/utils/CompletionHelper.d.ts.map +1 -0
- package/dist/packages/cli/src/utils/CompletionHelper.js +304 -0
- package/dist/packages/cli/src/utils/CompletionHelper.js.map +1 -0
- package/dist/packages/cli/src/utils/EnhancedCompleter.d.ts +107 -0
- package/dist/packages/cli/src/utils/EnhancedCompleter.d.ts.map +1 -0
- package/dist/packages/cli/src/utils/EnhancedCompleter.js +428 -0
- package/dist/packages/cli/src/utils/EnhancedCompleter.js.map +1 -0
- package/dist/packages/cli/src/utils/ErrorEnhancer.d.ts +103 -0
- package/dist/packages/cli/src/utils/ErrorEnhancer.d.ts.map +1 -0
- package/dist/packages/cli/src/utils/ErrorEnhancer.js +350 -0
- package/dist/packages/cli/src/utils/ErrorEnhancer.js.map +1 -0
- package/dist/packages/cli/src/utils/OutputFormatter.d.ts +65 -0
- package/dist/packages/cli/src/utils/OutputFormatter.d.ts.map +1 -0
- package/dist/packages/cli/src/utils/OutputFormatter.js +145 -0
- package/dist/packages/cli/src/utils/OutputFormatter.js.map +1 -0
- package/dist/packages/cli/src/utils/index.d.ts +5 -0
- package/dist/packages/cli/src/utils/index.d.ts.map +1 -0
- package/dist/packages/cli/src/utils/index.js +8 -0
- package/dist/packages/cli/src/utils/index.js.map +1 -0
- package/dist/packages/cli/tsconfig.tsbuildinfo +1 -0
- package/dist/packages/core/src/agents/AgentOrchestrator.d.ts +147 -0
- package/dist/packages/core/src/agents/AgentOrchestrator.d.ts.map +1 -0
- package/dist/packages/core/src/agents/AgentOrchestrator.js +358 -0
- package/dist/packages/core/src/agents/AgentOrchestrator.js.map +1 -0
- package/dist/packages/core/src/agents/index.d.ts +3 -0
- package/dist/packages/core/src/agents/index.d.ts.map +1 -0
- package/dist/packages/core/src/agents/index.js +5 -0
- package/dist/packages/core/src/agents/index.js.map +1 -0
- package/dist/packages/core/src/analysis/ProjectAnalyzer.d.ts +95 -0
- package/dist/packages/core/src/analysis/ProjectAnalyzer.d.ts.map +1 -0
- package/dist/packages/core/src/analysis/ProjectAnalyzer.js +656 -0
- package/dist/packages/core/src/analysis/ProjectAnalyzer.js.map +1 -0
- package/dist/packages/core/src/audit/AuditLogger.d.ts +140 -0
- package/dist/packages/core/src/audit/AuditLogger.d.ts.map +1 -0
- package/dist/packages/core/src/audit/AuditLogger.js +357 -0
- package/dist/packages/core/src/audit/AuditLogger.js.map +1 -0
- package/dist/packages/core/src/audit/index.d.ts +3 -0
- package/dist/packages/core/src/audit/index.d.ts.map +1 -0
- package/dist/packages/core/src/audit/index.js +5 -0
- package/dist/packages/core/src/audit/index.js.map +1 -0
- package/dist/packages/core/src/auth/AuthManager.d.ts +38 -0
- package/dist/packages/core/src/auth/AuthManager.d.ts.map +1 -0
- package/dist/packages/core/src/auth/AuthManager.js +120 -0
- package/dist/packages/core/src/auth/AuthManager.js.map +1 -0
- package/dist/packages/core/src/auth/index.d.ts +3 -0
- package/dist/packages/core/src/auth/index.d.ts.map +1 -0
- package/dist/packages/core/src/auth/index.js +2 -0
- package/dist/packages/core/src/auth/index.js.map +1 -0
- package/dist/packages/core/src/config/ConfigManager.d.ts +47 -0
- package/dist/packages/core/src/config/ConfigManager.d.ts.map +1 -0
- package/dist/packages/core/src/config/ConfigManager.js +1197 -0
- package/dist/packages/core/src/config/ConfigManager.js.map +1 -0
- package/dist/packages/core/src/config/index.d.ts +2 -0
- package/dist/packages/core/src/config/index.d.ts.map +1 -0
- package/dist/packages/core/src/config/index.js +2 -0
- package/dist/packages/core/src/config/index.js.map +1 -0
- package/dist/packages/core/src/context/ContextBuilder.d.ts +39 -0
- package/dist/packages/core/src/context/ContextBuilder.d.ts.map +1 -0
- package/dist/packages/core/src/context/ContextBuilder.js +132 -0
- package/dist/packages/core/src/context/ContextBuilder.js.map +1 -0
- package/dist/packages/core/src/context/ContextCompressor.d.ts +147 -0
- package/dist/packages/core/src/context/ContextCompressor.d.ts.map +1 -0
- package/dist/packages/core/src/context/ContextCompressor.js +451 -0
- package/dist/packages/core/src/context/ContextCompressor.js.map +1 -0
- package/dist/packages/core/src/context/LayeredMemoryManager.d.ts +160 -0
- package/dist/packages/core/src/context/LayeredMemoryManager.d.ts.map +1 -0
- package/dist/packages/core/src/context/LayeredMemoryManager.js +505 -0
- package/dist/packages/core/src/context/LayeredMemoryManager.js.map +1 -0
- package/dist/packages/core/src/context/MemoryDiscovery.d.ts +33 -0
- package/dist/packages/core/src/context/MemoryDiscovery.d.ts.map +1 -0
- package/dist/packages/core/src/context/MemoryDiscovery.js +146 -0
- package/dist/packages/core/src/context/MemoryDiscovery.js.map +1 -0
- package/dist/packages/core/src/context/defaultSystemPrompt.d.ts +12 -0
- package/dist/packages/core/src/context/defaultSystemPrompt.d.ts.map +1 -0
- package/dist/packages/core/src/context/defaultSystemPrompt.js +32 -0
- package/dist/packages/core/src/context/defaultSystemPrompt.js.map +1 -0
- package/dist/packages/core/src/context/index.d.ts +9 -0
- package/dist/packages/core/src/context/index.d.ts.map +1 -0
- package/dist/packages/core/src/context/index.js +5 -0
- package/dist/packages/core/src/context/index.js.map +1 -0
- package/dist/packages/core/src/extensions/SkillGenerator.d.ts +77 -0
- package/dist/packages/core/src/extensions/SkillGenerator.d.ts.map +1 -0
- package/dist/packages/core/src/extensions/SkillGenerator.js +323 -0
- package/dist/packages/core/src/extensions/SkillGenerator.js.map +1 -0
- package/dist/packages/core/src/extensions/SkillInstaller.d.ts +74 -0
- package/dist/packages/core/src/extensions/SkillInstaller.d.ts.map +1 -0
- package/dist/packages/core/src/extensions/SkillInstaller.js +216 -0
- package/dist/packages/core/src/extensions/SkillInstaller.js.map +1 -0
- package/dist/packages/core/src/extensions/SkillRegistry.d.ts +99 -0
- package/dist/packages/core/src/extensions/SkillRegistry.d.ts.map +1 -0
- package/dist/packages/core/src/extensions/SkillRegistry.js +263 -0
- package/dist/packages/core/src/extensions/SkillRegistry.js.map +1 -0
- package/dist/packages/core/src/extensions/SkillValidator.d.ts +51 -0
- package/dist/packages/core/src/extensions/SkillValidator.d.ts.map +1 -0
- package/dist/packages/core/src/extensions/SkillValidator.js +465 -0
- package/dist/packages/core/src/extensions/SkillValidator.js.map +1 -0
- package/dist/packages/core/src/extensions/index.d.ts +11 -0
- package/dist/packages/core/src/extensions/index.d.ts.map +1 -0
- package/dist/packages/core/src/extensions/index.js +8 -0
- package/dist/packages/core/src/extensions/index.js.map +1 -0
- package/dist/packages/core/src/index.d.ts +14 -0
- package/dist/packages/core/src/index.d.ts.map +1 -0
- package/dist/packages/core/src/index.js +30 -0
- package/dist/packages/core/src/index.js.map +1 -0
- package/dist/packages/core/src/mcp/McpManager.d.ts +94 -0
- package/dist/packages/core/src/mcp/McpManager.d.ts.map +1 -0
- package/dist/packages/core/src/mcp/McpManager.js +494 -0
- package/dist/packages/core/src/mcp/McpManager.js.map +1 -0
- package/dist/packages/core/src/mcp/index.d.ts +2 -0
- package/dist/packages/core/src/mcp/index.d.ts.map +1 -0
- package/dist/packages/core/src/mcp/index.js +3 -0
- package/dist/packages/core/src/mcp/index.js.map +1 -0
- package/dist/packages/core/src/model/ModelClient.d.ts +40 -0
- package/dist/packages/core/src/model/ModelClient.d.ts.map +1 -0
- package/dist/packages/core/src/model/ModelClient.js +163 -0
- package/dist/packages/core/src/model/ModelClient.js.map +1 -0
- package/dist/packages/core/src/model/ModelConnectionTester.d.ts +58 -0
- package/dist/packages/core/src/model/ModelConnectionTester.d.ts.map +1 -0
- package/dist/packages/core/src/model/ModelConnectionTester.js +311 -0
- package/dist/packages/core/src/model/ModelConnectionTester.js.map +1 -0
- package/dist/packages/core/src/model/ModelValidator.d.ts +39 -0
- package/dist/packages/core/src/model/ModelValidator.d.ts.map +1 -0
- package/dist/packages/core/src/model/ModelValidator.js +296 -0
- package/dist/packages/core/src/model/ModelValidator.js.map +1 -0
- package/dist/packages/core/src/model/index.d.ts +7 -0
- package/dist/packages/core/src/model/index.d.ts.map +1 -0
- package/dist/packages/core/src/model/index.js +5 -0
- package/dist/packages/core/src/model/index.js.map +1 -0
- package/dist/packages/core/src/model/providers/AnthropicProvider.d.ts +25 -0
- package/dist/packages/core/src/model/providers/AnthropicProvider.d.ts.map +1 -0
- package/dist/packages/core/src/model/providers/AnthropicProvider.js +258 -0
- package/dist/packages/core/src/model/providers/AnthropicProvider.js.map +1 -0
- package/dist/packages/core/src/model/providers/CodingPlanProvider.d.ts +66 -0
- package/dist/packages/core/src/model/providers/CodingPlanProvider.d.ts.map +1 -0
- package/dist/packages/core/src/model/providers/CodingPlanProvider.js +161 -0
- package/dist/packages/core/src/model/providers/CodingPlanProvider.js.map +1 -0
- package/dist/packages/core/src/model/providers/OllamaCloudProvider.d.ts +38 -0
- package/dist/packages/core/src/model/providers/OllamaCloudProvider.d.ts.map +1 -0
- package/dist/packages/core/src/model/providers/OllamaCloudProvider.js +365 -0
- package/dist/packages/core/src/model/providers/OllamaCloudProvider.js.map +1 -0
- package/dist/packages/core/src/model/providers/OllamaManager.d.ts +72 -0
- package/dist/packages/core/src/model/providers/OllamaManager.d.ts.map +1 -0
- package/dist/packages/core/src/model/providers/OllamaManager.js +144 -0
- package/dist/packages/core/src/model/providers/OllamaManager.js.map +1 -0
- package/dist/packages/core/src/model/providers/OllamaProvider.d.ts +23 -0
- package/dist/packages/core/src/model/providers/OllamaProvider.d.ts.map +1 -0
- package/dist/packages/core/src/model/providers/OllamaProvider.js +56 -0
- package/dist/packages/core/src/model/providers/OllamaProvider.js.map +1 -0
- package/dist/packages/core/src/model/providers/OpenAICompatibleProvider.d.ts +53 -0
- package/dist/packages/core/src/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
- package/dist/packages/core/src/model/providers/OpenAICompatibleProvider.js +383 -0
- package/dist/packages/core/src/model/providers/OpenAICompatibleProvider.js.map +1 -0
- package/dist/packages/core/src/model/providers/OpenAIProvider.d.ts +12 -0
- package/dist/packages/core/src/model/providers/OpenAIProvider.d.ts.map +1 -0
- package/dist/packages/core/src/model/providers/OpenAIProvider.js +19 -0
- package/dist/packages/core/src/model/providers/OpenAIProvider.js.map +1 -0
- package/dist/packages/core/src/model/providers/index.d.ts +13 -0
- package/dist/packages/core/src/model/providers/index.d.ts.map +1 -0
- package/dist/packages/core/src/model/providers/index.js +7 -0
- package/dist/packages/core/src/model/providers/index.js.map +1 -0
- package/dist/packages/core/src/model/types.d.ts +81 -0
- package/dist/packages/core/src/model/types.d.ts.map +1 -0
- package/dist/packages/core/src/model/types.js +12 -0
- package/dist/packages/core/src/model/types.js.map +1 -0
- package/dist/packages/core/src/security/ApprovalManager.d.ts +56 -0
- package/dist/packages/core/src/security/ApprovalManager.d.ts.map +1 -0
- package/dist/packages/core/src/security/ApprovalManager.js +122 -0
- package/dist/packages/core/src/security/ApprovalManager.js.map +1 -0
- package/dist/packages/core/src/security/FileFilter.d.ts +47 -0
- package/dist/packages/core/src/security/FileFilter.d.ts.map +1 -0
- package/dist/packages/core/src/security/FileFilter.js +111 -0
- package/dist/packages/core/src/security/FileFilter.js.map +1 -0
- package/dist/packages/core/src/security/HookExecutor.d.ts +37 -0
- package/dist/packages/core/src/security/HookExecutor.d.ts.map +1 -0
- package/dist/packages/core/src/security/HookExecutor.js +142 -0
- package/dist/packages/core/src/security/HookExecutor.js.map +1 -0
- package/dist/packages/core/src/security/SandboxExecutor.d.ts +90 -0
- package/dist/packages/core/src/security/SandboxExecutor.d.ts.map +1 -0
- package/dist/packages/core/src/security/SandboxExecutor.js +345 -0
- package/dist/packages/core/src/security/SandboxExecutor.js.map +1 -0
- package/dist/packages/core/src/security/index.d.ts +8 -0
- package/dist/packages/core/src/security/index.d.ts.map +1 -0
- package/dist/packages/core/src/security/index.js +6 -0
- package/dist/packages/core/src/security/index.js.map +1 -0
- package/dist/packages/core/src/session/AgentLoop.d.ts +75 -0
- package/dist/packages/core/src/session/AgentLoop.d.ts.map +1 -0
- package/dist/packages/core/src/session/AgentLoop.js +381 -0
- package/dist/packages/core/src/session/AgentLoop.js.map +1 -0
- package/dist/packages/core/src/session/SessionManager.d.ts +96 -0
- package/dist/packages/core/src/session/SessionManager.d.ts.map +1 -0
- package/dist/packages/core/src/session/SessionManager.js +390 -0
- package/dist/packages/core/src/session/SessionManager.js.map +1 -0
- package/dist/packages/core/src/session/index.d.ts +4 -0
- package/dist/packages/core/src/session/index.d.ts.map +1 -0
- package/dist/packages/core/src/session/index.js +3 -0
- package/dist/packages/core/src/session/index.js.map +1 -0
- package/dist/packages/core/src/telemetry/Telemetry.d.ts +25 -0
- package/dist/packages/core/src/telemetry/Telemetry.d.ts.map +1 -0
- package/dist/packages/core/src/telemetry/Telemetry.js +73 -0
- package/dist/packages/core/src/telemetry/Telemetry.js.map +1 -0
- package/dist/packages/core/src/telemetry/TelemetryService.d.ts +152 -0
- package/dist/packages/core/src/telemetry/TelemetryService.d.ts.map +1 -0
- package/dist/packages/core/src/telemetry/TelemetryService.js +410 -0
- package/dist/packages/core/src/telemetry/TelemetryService.js.map +1 -0
- package/dist/packages/core/src/telemetry/index.d.ts +5 -0
- package/dist/packages/core/src/telemetry/index.d.ts.map +1 -0
- package/dist/packages/core/src/telemetry/index.js +3 -0
- package/dist/packages/core/src/telemetry/index.js.map +1 -0
- package/dist/packages/core/src/testing/AutoFixer.d.ts +98 -0
- package/dist/packages/core/src/testing/AutoFixer.d.ts.map +1 -0
- package/dist/packages/core/src/testing/AutoFixer.js +262 -0
- package/dist/packages/core/src/testing/AutoFixer.js.map +1 -0
- package/dist/packages/core/src/testing/ErrorAnalyzer.d.ts +74 -0
- package/dist/packages/core/src/testing/ErrorAnalyzer.d.ts.map +1 -0
- package/dist/packages/core/src/testing/ErrorAnalyzer.js +407 -0
- package/dist/packages/core/src/testing/ErrorAnalyzer.js.map +1 -0
- package/dist/packages/core/src/testing/TestRunner.d.ts +57 -0
- package/dist/packages/core/src/testing/TestRunner.d.ts.map +1 -0
- package/dist/packages/core/src/testing/TestRunner.js +205 -0
- package/dist/packages/core/src/testing/TestRunner.js.map +1 -0
- package/dist/packages/core/src/testing/agent-cli-tests.d.ts +2 -0
- package/dist/packages/core/src/testing/agent-cli-tests.d.ts.map +1 -0
- package/dist/packages/core/src/testing/agent-cli-tests.js +493 -0
- package/dist/packages/core/src/testing/agent-cli-tests.js.map +1 -0
- package/dist/packages/core/src/testing/index.d.ts +7 -0
- package/dist/packages/core/src/testing/index.d.ts.map +1 -0
- package/dist/packages/core/src/testing/index.js +8 -0
- package/dist/packages/core/src/testing/index.js.map +1 -0
- package/dist/packages/core/src/tools/ToolRegistry.d.ts +72 -0
- package/dist/packages/core/src/tools/ToolRegistry.d.ts.map +1 -0
- package/dist/packages/core/src/tools/ToolRegistry.js +208 -0
- package/dist/packages/core/src/tools/ToolRegistry.js.map +1 -0
- package/dist/packages/core/src/tools/impl/EditFileTool.d.ts +3 -0
- package/dist/packages/core/src/tools/impl/EditFileTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/EditFileTool.js +76 -0
- package/dist/packages/core/src/tools/impl/EditFileTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/FileProcessor.d.ts +85 -0
- package/dist/packages/core/src/tools/impl/FileProcessor.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/FileProcessor.js +512 -0
- package/dist/packages/core/src/tools/impl/FileProcessor.js.map +1 -0
- package/dist/packages/core/src/tools/impl/FileProcessorTool.d.ts +151 -0
- package/dist/packages/core/src/tools/impl/FileProcessorTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/FileProcessorTool.js +100 -0
- package/dist/packages/core/src/tools/impl/FileProcessorTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/ImageProcessorTool.d.ts +43 -0
- package/dist/packages/core/src/tools/impl/ImageProcessorTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/ImageProcessorTool.js +104 -0
- package/dist/packages/core/src/tools/impl/ImageProcessorTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/ListDirectoryTool.d.ts +3 -0
- package/dist/packages/core/src/tools/impl/ListDirectoryTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/ListDirectoryTool.js +120 -0
- package/dist/packages/core/src/tools/impl/ListDirectoryTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/MemoryTool.d.ts +4 -0
- package/dist/packages/core/src/tools/impl/MemoryTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/MemoryTool.js +80 -0
- package/dist/packages/core/src/tools/impl/MemoryTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/ReadFileTool.d.ts +3 -0
- package/dist/packages/core/src/tools/impl/ReadFileTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/ReadFileTool.js +223 -0
- package/dist/packages/core/src/tools/impl/ReadFileTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/SearchContentTool.d.ts +3 -0
- package/dist/packages/core/src/tools/impl/SearchContentTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/SearchContentTool.js +65 -0
- package/dist/packages/core/src/tools/impl/SearchContentTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/SearchFileTool.d.ts +3 -0
- package/dist/packages/core/src/tools/impl/SearchFileTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/SearchFileTool.js +48 -0
- package/dist/packages/core/src/tools/impl/SearchFileTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/ShellTool.d.ts +3 -0
- package/dist/packages/core/src/tools/impl/ShellTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/ShellTool.js +92 -0
- package/dist/packages/core/src/tools/impl/ShellTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/TaskTool.d.ts +51 -0
- package/dist/packages/core/src/tools/impl/TaskTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/TaskTool.js +172 -0
- package/dist/packages/core/src/tools/impl/TaskTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/TodoTool.d.ts +31 -0
- package/dist/packages/core/src/tools/impl/TodoTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/TodoTool.js +102 -0
- package/dist/packages/core/src/tools/impl/TodoTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/WebFetchTool.d.ts +3 -0
- package/dist/packages/core/src/tools/impl/WebFetchTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/WebFetchTool.js +77 -0
- package/dist/packages/core/src/tools/impl/WebFetchTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/WebSearchTool.d.ts +3 -0
- package/dist/packages/core/src/tools/impl/WebSearchTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/WebSearchTool.js +67 -0
- package/dist/packages/core/src/tools/impl/WebSearchTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/WriteFileTool.d.ts +3 -0
- package/dist/packages/core/src/tools/impl/WriteFileTool.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/WriteFileTool.js +41 -0
- package/dist/packages/core/src/tools/impl/WriteFileTool.js.map +1 -0
- package/dist/packages/core/src/tools/impl/index.d.ts +14 -0
- package/dist/packages/core/src/tools/impl/index.d.ts.map +1 -0
- package/dist/packages/core/src/tools/impl/index.js +21 -0
- package/dist/packages/core/src/tools/impl/index.js.map +1 -0
- package/dist/packages/core/src/tools/index.d.ts +16 -0
- package/dist/packages/core/src/tools/index.d.ts.map +1 -0
- package/dist/packages/core/src/tools/index.js +21 -0
- package/dist/packages/core/src/tools/index.js.map +1 -0
- package/dist/packages/core/src/tools/schemas/execution.d.ts +41 -0
- package/dist/packages/core/src/tools/schemas/execution.d.ts.map +1 -0
- package/dist/packages/core/src/tools/schemas/execution.js +42 -0
- package/dist/packages/core/src/tools/schemas/execution.js.map +1 -0
- package/dist/packages/core/src/tools/schemas/file.d.ts +113 -0
- package/dist/packages/core/src/tools/schemas/file.d.ts.map +1 -0
- package/dist/packages/core/src/tools/schemas/file.js +116 -0
- package/dist/packages/core/src/tools/schemas/file.js.map +1 -0
- package/dist/packages/core/src/tools/schemas/fileProcessorSchema.d.ts +278 -0
- package/dist/packages/core/src/tools/schemas/fileProcessorSchema.d.ts.map +1 -0
- package/dist/packages/core/src/tools/schemas/fileProcessorSchema.js +61 -0
- package/dist/packages/core/src/tools/schemas/fileProcessorSchema.js.map +1 -0
- package/dist/packages/core/src/tools/schemas/index.d.ts +8 -0
- package/dist/packages/core/src/tools/schemas/index.d.ts.map +1 -0
- package/dist/packages/core/src/tools/schemas/index.js +11 -0
- package/dist/packages/core/src/tools/schemas/index.js.map +1 -0
- package/dist/packages/core/src/tools/schemas/memory.d.ts +50 -0
- package/dist/packages/core/src/tools/schemas/memory.d.ts.map +1 -0
- package/dist/packages/core/src/tools/schemas/memory.js +51 -0
- package/dist/packages/core/src/tools/schemas/memory.js.map +1 -0
- package/dist/packages/core/src/tools/schemas/orchestration.d.ts +41 -0
- package/dist/packages/core/src/tools/schemas/orchestration.d.ts.map +1 -0
- package/dist/packages/core/src/tools/schemas/orchestration.js +44 -0
- package/dist/packages/core/src/tools/schemas/orchestration.js.map +1 -0
- package/dist/packages/core/src/tools/schemas/search.d.ts +111 -0
- package/dist/packages/core/src/tools/schemas/search.d.ts.map +1 -0
- package/dist/packages/core/src/tools/schemas/search.js +110 -0
- package/dist/packages/core/src/tools/schemas/search.js.map +1 -0
- package/dist/packages/core/src/tools/schemas/todo.d.ts +29 -0
- package/dist/packages/core/src/tools/schemas/todo.d.ts.map +1 -0
- package/dist/packages/core/src/tools/schemas/todo.js +32 -0
- package/dist/packages/core/src/tools/schemas/todo.js.map +1 -0
- package/dist/packages/core/src/tools/schemas/web.d.ts +84 -0
- package/dist/packages/core/src/tools/schemas/web.d.ts.map +1 -0
- package/dist/packages/core/src/tools/schemas/web.js +85 -0
- package/dist/packages/core/src/tools/schemas/web.js.map +1 -0
- package/dist/packages/core/src/types/config.d.ts +212 -0
- package/dist/packages/core/src/types/config.d.ts.map +1 -0
- package/dist/packages/core/src/types/config.js +5 -0
- package/dist/packages/core/src/types/config.js.map +1 -0
- package/dist/packages/core/src/types/errors.d.ts +92 -0
- package/dist/packages/core/src/types/errors.d.ts.map +1 -0
- package/dist/packages/core/src/types/errors.js +172 -0
- package/dist/packages/core/src/types/errors.js.map +1 -0
- package/dist/packages/core/src/types/index.d.ts +5 -0
- package/dist/packages/core/src/types/index.d.ts.map +1 -0
- package/dist/packages/core/src/types/index.js +8 -0
- package/dist/packages/core/src/types/index.js.map +1 -0
- package/dist/packages/core/src/types/session.d.ts +141 -0
- package/dist/packages/core/src/types/session.d.ts.map +1 -0
- package/dist/packages/core/src/types/session.js +16 -0
- package/dist/packages/core/src/types/session.js.map +1 -0
- package/dist/packages/core/src/types/tools.d.ts +126 -0
- package/dist/packages/core/src/types/tools.d.ts.map +1 -0
- package/dist/packages/core/src/types/tools.js +26 -0
- package/dist/packages/core/src/types/tools.js.map +1 -0
- package/dist/packages/core/src/utils/CheckpointManager.d.ts +100 -0
- package/dist/packages/core/src/utils/CheckpointManager.d.ts.map +1 -0
- package/dist/packages/core/src/utils/CheckpointManager.js +255 -0
- package/dist/packages/core/src/utils/CheckpointManager.js.map +1 -0
- package/dist/packages/core/src/utils/Logger.d.ts +29 -0
- package/dist/packages/core/src/utils/Logger.d.ts.map +1 -0
- package/dist/packages/core/src/utils/Logger.js +77 -0
- package/dist/packages/core/src/utils/Logger.js.map +1 -0
- package/dist/packages/core/src/utils/RetryManager.d.ts +125 -0
- package/dist/packages/core/src/utils/RetryManager.d.ts.map +1 -0
- package/dist/packages/core/src/utils/RetryManager.js +348 -0
- package/dist/packages/core/src/utils/RetryManager.js.map +1 -0
- package/dist/packages/core/src/utils/TokenCounter.d.ts +73 -0
- package/dist/packages/core/src/utils/TokenCounter.d.ts.map +1 -0
- package/dist/packages/core/src/utils/TokenCounter.js +338 -0
- package/dist/packages/core/src/utils/TokenCounter.js.map +1 -0
- package/dist/packages/core/src/utils/VectorMemoryStore.d.ts +110 -0
- package/dist/packages/core/src/utils/VectorMemoryStore.d.ts.map +1 -0
- package/dist/packages/core/src/utils/VectorMemoryStore.js +320 -0
- package/dist/packages/core/src/utils/VectorMemoryStore.js.map +1 -0
- package/dist/packages/core/src/utils/helpers.d.ts +24 -0
- package/dist/packages/core/src/utils/helpers.d.ts.map +1 -0
- package/dist/packages/core/src/utils/helpers.js +77 -0
- package/dist/packages/core/src/utils/helpers.js.map +1 -0
- package/dist/packages/core/src/utils/index.d.ts +11 -0
- package/dist/packages/core/src/utils/index.d.ts.map +1 -0
- package/dist/packages/core/src/utils/index.js +7 -0
- package/dist/packages/core/src/utils/index.js.map +1 -0
- package/dist/startup/IFlowRepl.d.ts +50 -0
- package/dist/startup/IFlowRepl.d.ts.map +1 -0
- package/dist/startup/IFlowRepl.js +178 -0
- package/dist/startup/IFlowRepl.js.map +1 -0
- package/dist/startup/InkBasedRepl.d.ts +151 -0
- package/dist/startup/InkBasedRepl.d.ts.map +1 -0
- package/dist/startup/InkBasedRepl.js +1415 -0
- package/dist/startup/InkBasedRepl.js.map +1 -0
- package/dist/startup/InteractiveRepl.d.ts +141 -0
- package/dist/startup/InteractiveRepl.d.ts.map +1 -0
- package/dist/startup/InteractiveRepl.js +2561 -0
- package/dist/startup/InteractiveRepl.js.map +1 -0
- package/dist/startup/NovaApp.d.ts +57 -0
- package/dist/startup/NovaApp.d.ts.map +1 -0
- package/dist/startup/NovaApp.js +1978 -0
- package/dist/startup/NovaApp.js.map +1 -0
- package/dist/startup/index.d.ts +5 -0
- package/dist/startup/index.d.ts.map +1 -0
- package/dist/startup/index.js +4 -0
- package/dist/startup/index.js.map +1 -0
- package/dist/startup/parseArgs.d.ts +47 -0
- package/dist/startup/parseArgs.d.ts.map +1 -0
- package/dist/startup/parseArgs.js +262 -0
- package/dist/startup/parseArgs.js.map +1 -0
- package/dist/ui/IFlowDropdown.d.ts +63 -0
- package/dist/ui/IFlowDropdown.d.ts.map +1 -0
- package/dist/ui/IFlowDropdown.js +362 -0
- package/dist/ui/IFlowDropdown.js.map +1 -0
- package/dist/ui/ModernReplUI.d.ts +55 -0
- package/dist/ui/ModernReplUI.d.ts.map +1 -0
- package/dist/ui/ModernReplUI.js +207 -0
- package/dist/ui/ModernReplUI.js.map +1 -0
- package/dist/ui/SimpleSelector2.d.ts +28 -0
- package/dist/ui/SimpleSelector2.d.ts.map +1 -0
- package/dist/ui/SimpleSelector2.js +181 -0
- package/dist/ui/SimpleSelector2.js.map +1 -0
- package/dist/ui/components/ActiveCursor.d.ts +128 -0
- package/dist/ui/components/ActiveCursor.d.ts.map +1 -0
- package/dist/ui/components/ActiveCursor.js +273 -0
- package/dist/ui/components/ActiveCursor.js.map +1 -0
- package/dist/ui/components/ConfirmDialog.d.ts +51 -0
- package/dist/ui/components/ConfirmDialog.d.ts.map +1 -0
- package/dist/ui/components/ConfirmDialog.js +147 -0
- package/dist/ui/components/ConfirmDialog.js.map +1 -0
- package/dist/ui/components/ErrorPanel.d.ts +33 -0
- package/dist/ui/components/ErrorPanel.d.ts.map +1 -0
- package/dist/ui/components/ErrorPanel.js +309 -0
- package/dist/ui/components/ErrorPanel.js.map +1 -0
- package/dist/ui/components/InkAppRunner.d.ts +18 -0
- package/dist/ui/components/InkAppRunner.d.ts.map +1 -0
- package/dist/ui/components/InkAppRunner.js +33 -0
- package/dist/ui/components/InkAppRunner.js.map +1 -0
- package/dist/ui/components/InkComponents.d.ts +126 -0
- package/dist/ui/components/InkComponents.d.ts.map +1 -0
- package/dist/ui/components/InkComponents.js +216 -0
- package/dist/ui/components/InkComponents.js.map +1 -0
- package/dist/ui/components/NovaInkApp.d.ts +11 -0
- package/dist/ui/components/NovaInkApp.d.ts.map +1 -0
- package/dist/ui/components/NovaInkApp.js +148 -0
- package/dist/ui/components/NovaInkApp.js.map +1 -0
- package/dist/ui/components/ProgressBar.d.ts +65 -0
- package/dist/ui/components/ProgressBar.d.ts.map +1 -0
- package/dist/ui/components/ProgressBar.js +135 -0
- package/dist/ui/components/ProgressBar.js.map +1 -0
- package/dist/ui/components/ProgressIndicator.d.ts +41 -0
- package/dist/ui/components/ProgressIndicator.d.ts.map +1 -0
- package/dist/ui/components/ProgressIndicator.js +235 -0
- package/dist/ui/components/ProgressIndicator.js.map +1 -0
- package/dist/ui/components/QuickActions.d.ts +36 -0
- package/dist/ui/components/QuickActions.d.ts.map +1 -0
- package/dist/ui/components/QuickActions.js +328 -0
- package/dist/ui/components/QuickActions.js.map +1 -0
- package/dist/ui/components/SimpleErrorPanel.d.ts +16 -0
- package/dist/ui/components/SimpleErrorPanel.d.ts.map +1 -0
- package/dist/ui/components/SimpleErrorPanel.js +193 -0
- package/dist/ui/components/SimpleErrorPanel.js.map +1 -0
- package/dist/ui/components/StatusBar.d.ts +30 -0
- package/dist/ui/components/StatusBar.d.ts.map +1 -0
- package/dist/ui/components/StatusBar.js +154 -0
- package/dist/ui/components/StatusBar.js.map +1 -0
- package/dist/ui/components/ThinkingBlockRenderer.d.ts +109 -0
- package/dist/ui/components/ThinkingBlockRenderer.d.ts.map +1 -0
- package/dist/ui/components/ThinkingBlockRenderer.js +335 -0
- package/dist/ui/components/ThinkingBlockRenderer.js.map +1 -0
- package/dist/ui/components/ThinkingContentDisplay.d.ts +59 -0
- package/dist/ui/components/ThinkingContentDisplay.d.ts.map +1 -0
- package/dist/ui/components/ThinkingContentDisplay.js +172 -0
- package/dist/ui/components/ThinkingContentDisplay.js.map +1 -0
- package/dist/ui/components/TodoProgressPanel.d.ts +91 -0
- package/dist/ui/components/TodoProgressPanel.d.ts.map +1 -0
- package/dist/ui/components/TodoProgressPanel.js +284 -0
- package/dist/ui/components/TodoProgressPanel.js.map +1 -0
- package/dist/ui/components/ToolCallStatusDisplay.d.ts +89 -0
- package/dist/ui/components/ToolCallStatusDisplay.d.ts.map +1 -0
- package/dist/ui/components/ToolCallStatusDisplay.js +246 -0
- package/dist/ui/components/ToolCallStatusDisplay.js.map +1 -0
- package/dist/ui/components/UserMessageHighlight.d.ts +48 -0
- package/dist/ui/components/UserMessageHighlight.d.ts.map +1 -0
- package/dist/ui/components/UserMessageHighlight.js +196 -0
- package/dist/ui/components/UserMessageHighlight.js.map +1 -0
- package/dist/ui/components/index.d.ts +17 -0
- package/dist/ui/components/index.d.ts.map +1 -0
- package/dist/ui/components/index.js +18 -0
- package/dist/ui/components/index.js.map +1 -0
- package/dist/ui/ink-prototype.d.ts +3 -0
- package/dist/ui/ink-prototype.d.ts.map +1 -0
- package/dist/ui/ink-prototype.js +160 -0
- package/dist/ui/ink-prototype.js.map +1 -0
- package/dist/utils/CliUI.d.ts +163 -0
- package/dist/utils/CliUI.d.ts.map +1 -0
- package/dist/utils/CliUI.js +292 -0
- package/dist/utils/CliUI.js.map +1 -0
- package/dist/utils/CompletionHelper.d.ts +112 -0
- package/dist/utils/CompletionHelper.d.ts.map +1 -0
- package/dist/utils/CompletionHelper.js +304 -0
- package/dist/utils/CompletionHelper.js.map +1 -0
- package/dist/utils/EnhancedCompleter.d.ts +107 -0
- package/dist/utils/EnhancedCompleter.d.ts.map +1 -0
- package/dist/utils/EnhancedCompleter.js +428 -0
- package/dist/utils/EnhancedCompleter.js.map +1 -0
- package/dist/utils/ErrorEnhancer.d.ts +103 -0
- package/dist/utils/ErrorEnhancer.d.ts.map +1 -0
- package/dist/utils/ErrorEnhancer.js +350 -0
- package/dist/utils/ErrorEnhancer.js.map +1 -0
- package/dist/utils/OutputFormatter.d.ts +65 -0
- package/dist/utils/OutputFormatter.d.ts.map +1 -0
- package/dist/utils/OutputFormatter.js +145 -0
- package/dist/utils/OutputFormatter.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +99 -0
|
@@ -0,0 +1,2561 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// InteractiveRepl - Interactive read-eval-print loop with rich UX v3
|
|
3
|
+
// Features: multi-line input, @file references, !shell, /init, /memory,
|
|
4
|
+
// /history, session auto-persist, /model switch
|
|
5
|
+
// ============================================================================
|
|
6
|
+
import * as readline from 'node:readline';
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import * as os from 'node:os';
|
|
10
|
+
import { execSync, spawn } from 'node:child_process';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import { AgentLoop } from '../../../core/src/session/AgentLoop.js';
|
|
13
|
+
import { buildSystemPrompt } from '../../../core/src/context/defaultSystemPrompt.js';
|
|
14
|
+
import { ThinkingBlockRenderer } from '../ui/components/ThinkingBlockRenderer.js';
|
|
15
|
+
import { TodoProgressPanel } from '../ui/components/TodoProgressPanel.js';
|
|
16
|
+
import { UserMessageHighlight } from '../ui/components/UserMessageHighlight.js';
|
|
17
|
+
import { OllamaManager } from '../../../core/src/model/providers/OllamaManager.js';
|
|
18
|
+
import { EnhancedCompleter } from '../utils/EnhancedCompleter.js';
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
const MODE_LABELS = {
|
|
21
|
+
auto: { label: 'AUTO', color: chalk.green.bold, description: 'Full autonomous - no approval needed', approvalMode: 'yolo' },
|
|
22
|
+
plan: { label: 'PLAN', color: chalk.yellow.bold, description: 'Plan first, then confirm each action', approvalMode: 'plan' },
|
|
23
|
+
ask: { label: 'ASK', color: chalk.cyan.bold, description: 'Answer only, no file changes', approvalMode: 'plan' },
|
|
24
|
+
};
|
|
25
|
+
const MODES = ['auto', 'plan', 'ask'];
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Box drawing chars & color palette (enhanced UI)
|
|
28
|
+
// ============================================================================
|
|
29
|
+
const BOX = {
|
|
30
|
+
tl: '╭', tr: '╮', bl: '╰', br: '╯',
|
|
31
|
+
h: '─', v: '│',
|
|
32
|
+
ht: '├', htr: '┤', cross: '┼',
|
|
33
|
+
arrow: '›', bullet: '•', check: '✓', crossX: '✗', dot: '·',
|
|
34
|
+
diamond: '◆', star: '★', circle: '○', circleFull: '●',
|
|
35
|
+
spinner: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
|
|
36
|
+
hThick: '━', vThick: '┃',
|
|
37
|
+
arrowRight: '→', arrowLeft: '←', arrowUp: '↑', arrowDown: '↓',
|
|
38
|
+
};
|
|
39
|
+
const C = {
|
|
40
|
+
brand: chalk.hex('#7C3AED').bold,
|
|
41
|
+
brandLight: chalk.hex('#A78BFA'),
|
|
42
|
+
brandDim: chalk.hex('#7C3AED').dim,
|
|
43
|
+
success: chalk.hex('#10B981'),
|
|
44
|
+
successDim: chalk.hex('#10B981').dim,
|
|
45
|
+
warning: chalk.hex('#F59E0B'),
|
|
46
|
+
warningDim: chalk.hex('#F59E0B').dim,
|
|
47
|
+
error: chalk.hex('#EF4444'),
|
|
48
|
+
errorDim: chalk.hex('#EF4444').dim,
|
|
49
|
+
info: chalk.hex('#3B82F6'),
|
|
50
|
+
infoDim: chalk.hex('#3B82F6').dim,
|
|
51
|
+
primary: chalk.white,
|
|
52
|
+
muted: chalk.gray,
|
|
53
|
+
dim: chalk.hex('#6B7280'),
|
|
54
|
+
toolName: chalk.hex('#22D3EE'),
|
|
55
|
+
toolOk: chalk.hex('#6EE7B7'),
|
|
56
|
+
toolErr: chalk.hex('#FCA5A5'),
|
|
57
|
+
turnLine: chalk.hex('#374151').dim,
|
|
58
|
+
accent: chalk.hex('#F472B6'),
|
|
59
|
+
subtle: chalk.hex('#4B5563'),
|
|
60
|
+
};
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// InteractiveRepl
|
|
63
|
+
// ============================================================================
|
|
64
|
+
export class InteractiveRepl {
|
|
65
|
+
modelClient;
|
|
66
|
+
sessionManager;
|
|
67
|
+
toolRegistry;
|
|
68
|
+
approvalManager;
|
|
69
|
+
authManager;
|
|
70
|
+
config;
|
|
71
|
+
configManager;
|
|
72
|
+
cwd;
|
|
73
|
+
contextCompressor;
|
|
74
|
+
mcpManager;
|
|
75
|
+
skillRegistry;
|
|
76
|
+
rl = null;
|
|
77
|
+
currentLoop = null;
|
|
78
|
+
sessionId = null;
|
|
79
|
+
restoreSessionId;
|
|
80
|
+
resizeTimer = null;
|
|
81
|
+
// ---- UX state ----
|
|
82
|
+
mode = 'auto';
|
|
83
|
+
showThinking = true;
|
|
84
|
+
compactMode = true;
|
|
85
|
+
processing = false;
|
|
86
|
+
// ---- Multi-line input state ----
|
|
87
|
+
multilineBuffer = [];
|
|
88
|
+
isMultiline = false;
|
|
89
|
+
// ---- Components ----
|
|
90
|
+
thinkingRenderer;
|
|
91
|
+
todoProgressPanel;
|
|
92
|
+
userMessageHighlight;
|
|
93
|
+
// ---- Streaming state for current task ----
|
|
94
|
+
activeToolCalls = new Map();
|
|
95
|
+
toolCallOrder = [];
|
|
96
|
+
currentTurn = 0;
|
|
97
|
+
spinnerTimer = null;
|
|
98
|
+
spinnerFrame = 0;
|
|
99
|
+
currentTaskTokens = 0;
|
|
100
|
+
_pendingSkillInject = null;
|
|
101
|
+
// ---- Enhanced completer ----
|
|
102
|
+
enhancedCompleter = null;
|
|
103
|
+
inputHistory = [];
|
|
104
|
+
constructor(options) {
|
|
105
|
+
this.modelClient = options.modelClient;
|
|
106
|
+
this.sessionManager = options.sessionManager;
|
|
107
|
+
this.toolRegistry = options.toolRegistry;
|
|
108
|
+
this.approvalManager = options.approvalManager;
|
|
109
|
+
this.authManager = options.authManager;
|
|
110
|
+
this.config = options.config;
|
|
111
|
+
this.configManager = options.configManager;
|
|
112
|
+
this.cwd = options.cwd;
|
|
113
|
+
this.contextCompressor = options.contextCompressor;
|
|
114
|
+
this.mcpManager = options.mcpManager;
|
|
115
|
+
this.skillRegistry = options.skillRegistry;
|
|
116
|
+
this.restoreSessionId = options.restoreSessionId;
|
|
117
|
+
this.thinkingRenderer = new ThinkingBlockRenderer({
|
|
118
|
+
expanded: false,
|
|
119
|
+
maxPreviewLines: 4,
|
|
120
|
+
maxLineLength: 80,
|
|
121
|
+
showStreamingPreview: false,
|
|
122
|
+
});
|
|
123
|
+
// Initialize TODO progress panel for fixed-position task display
|
|
124
|
+
this.todoProgressPanel = new TodoProgressPanel({
|
|
125
|
+
showPriority: true,
|
|
126
|
+
compact: true,
|
|
127
|
+
});
|
|
128
|
+
// Initialize user message highlighter
|
|
129
|
+
this.userMessageHighlight = new UserMessageHighlight({
|
|
130
|
+
highlightColor: 'purple',
|
|
131
|
+
showTimestamp: true,
|
|
132
|
+
});
|
|
133
|
+
// Initialize enhanced completer with empty arrays (will be populated in start())
|
|
134
|
+
this.enhancedCompleter = new EnhancedCompleter({
|
|
135
|
+
configManager: this.configManager,
|
|
136
|
+
cwd: this.cwd,
|
|
137
|
+
history: this.inputHistory,
|
|
138
|
+
skills: [],
|
|
139
|
+
mcpServers: this.mcpManager ? this.mcpManager.listServers().map(s => ({ name: s.name, status: s.connected ? 'connected' : 'disconnected' })) : [],
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// ========================================================================
|
|
143
|
+
// Lifecycle
|
|
144
|
+
// ========================================================================
|
|
145
|
+
async start() {
|
|
146
|
+
this.printBanner();
|
|
147
|
+
// Update skills list now that we're in async context
|
|
148
|
+
if (this.skillRegistry && this.enhancedCompleter) {
|
|
149
|
+
const skills = await this.skillRegistry.list();
|
|
150
|
+
this.enhancedCompleter.updateSkills(skills.map(s => s.metadata.name));
|
|
151
|
+
}
|
|
152
|
+
// Restore or create session
|
|
153
|
+
if (this.restoreSessionId) {
|
|
154
|
+
const existing = this.sessionManager.get(this.restoreSessionId);
|
|
155
|
+
this.sessionId = existing ? this.restoreSessionId : this.createInitialSession();
|
|
156
|
+
if (existing) {
|
|
157
|
+
const msgs = this.sessionManager.getMessages(this.sessionId);
|
|
158
|
+
console.log(C.info(` Restored session: ${String(this.sessionId).slice(0, 8)} — ${msgs.length} messages`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
this.sessionId = this.createInitialSession();
|
|
163
|
+
}
|
|
164
|
+
this.rl = readline.createInterface({
|
|
165
|
+
input: process.stdin,
|
|
166
|
+
output: process.stdout,
|
|
167
|
+
prompt: '',
|
|
168
|
+
historySize: 200,
|
|
169
|
+
removeHistoryDuplicates: true,
|
|
170
|
+
});
|
|
171
|
+
this.approvalManager.setHandler(this.handleApproval.bind(this));
|
|
172
|
+
// Cancel running agent loop
|
|
173
|
+
const cancelRunningLoop = () => {
|
|
174
|
+
if (this.currentLoop?.isActive()) {
|
|
175
|
+
this.currentLoop.cancel();
|
|
176
|
+
this.processing = false;
|
|
177
|
+
this.thinkingRenderer.cancel();
|
|
178
|
+
this.stopSpinner();
|
|
179
|
+
// Also persist on cancel
|
|
180
|
+
if (this.sessionId)
|
|
181
|
+
this.sessionManager.persist(this.sessionId);
|
|
182
|
+
process.stdout.write('\n');
|
|
183
|
+
this.printLine('cancelled', 'warning');
|
|
184
|
+
this.printPrompt();
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
};
|
|
189
|
+
// Ctrl+C to cancel running task
|
|
190
|
+
process.on('SIGINT', () => {
|
|
191
|
+
if (!cancelRunningLoop()) {
|
|
192
|
+
console.log(C.muted('\n Use /quit or Ctrl+D to exit'));
|
|
193
|
+
this.printPrompt();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
// Handle terminal resize - redraw banner and status
|
|
197
|
+
let resizeTimer = null;
|
|
198
|
+
process.stdout.on('resize', () => {
|
|
199
|
+
if (!this.processing) {
|
|
200
|
+
// Debounce resize events to avoid flickering
|
|
201
|
+
if (resizeTimer) {
|
|
202
|
+
clearTimeout(resizeTimer);
|
|
203
|
+
}
|
|
204
|
+
resizeTimer = setTimeout(() => {
|
|
205
|
+
console.clear();
|
|
206
|
+
this.printBanner();
|
|
207
|
+
}, 100);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// Custom input loop with boxed input area
|
|
211
|
+
this.runInputLoop();
|
|
212
|
+
this.rl.on('close', () => {
|
|
213
|
+
if (this.sessionId)
|
|
214
|
+
this.sessionManager.persist(this.sessionId);
|
|
215
|
+
if (this.resizeTimer) {
|
|
216
|
+
clearTimeout(this.resizeTimer);
|
|
217
|
+
this.resizeTimer = null;
|
|
218
|
+
}
|
|
219
|
+
console.log(C.muted('\nGoodbye!'));
|
|
220
|
+
process.exit(0);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/** Custom input loop with boxed input area */
|
|
224
|
+
async runInputLoop() {
|
|
225
|
+
while (this.rl) {
|
|
226
|
+
// Print input box header
|
|
227
|
+
this.printInputBox();
|
|
228
|
+
// Get user input
|
|
229
|
+
const input = await this.askInput();
|
|
230
|
+
// Close input box
|
|
231
|
+
this.printInputBoxFooter();
|
|
232
|
+
if (!input)
|
|
233
|
+
continue;
|
|
234
|
+
// Process input
|
|
235
|
+
this.processing = true;
|
|
236
|
+
await this.dispatchInput(input.trim());
|
|
237
|
+
this.processing = false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/** Ask for input with proper prompt */
|
|
241
|
+
askInput() {
|
|
242
|
+
return new Promise((resolve) => {
|
|
243
|
+
if (!this.rl)
|
|
244
|
+
return resolve('');
|
|
245
|
+
// Set simple prompt inside the box
|
|
246
|
+
this.rl.setPrompt(C.brand(BOX.v) + ' ');
|
|
247
|
+
this.rl.prompt();
|
|
248
|
+
// Tab completion state
|
|
249
|
+
let currentInput = '';
|
|
250
|
+
let completionsShown = false;
|
|
251
|
+
// Enable raw mode for Tab key detection
|
|
252
|
+
const wasRaw = process.stdin.isRaw;
|
|
253
|
+
if (process.stdin.isTTY) {
|
|
254
|
+
process.stdin.setRawMode(true);
|
|
255
|
+
}
|
|
256
|
+
const cleanup = () => {
|
|
257
|
+
if (process.stdin.isTTY) {
|
|
258
|
+
process.stdin.setRawMode(wasRaw ?? false);
|
|
259
|
+
}
|
|
260
|
+
process.stdin.off('data', onKeypress);
|
|
261
|
+
this.rl?.off('line', onLine);
|
|
262
|
+
};
|
|
263
|
+
const onKeypress = (buffer) => {
|
|
264
|
+
const key = buffer.toString();
|
|
265
|
+
// Tab key - show completions
|
|
266
|
+
if (key === '\t') {
|
|
267
|
+
const completions = this.enhancedCompleter?.getCompletions(currentInput) || [];
|
|
268
|
+
if (completions.length > 0) {
|
|
269
|
+
process.stdout.write('\n');
|
|
270
|
+
this.showEnhancedCompletions(completions);
|
|
271
|
+
// Reprint prompt and current input
|
|
272
|
+
process.stdout.write(C.brand(BOX.v) + ' ' + currentInput);
|
|
273
|
+
completionsShown = true;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Ctrl+C - cancel
|
|
277
|
+
else if (key === '\x03') {
|
|
278
|
+
cleanup();
|
|
279
|
+
resolve('');
|
|
280
|
+
}
|
|
281
|
+
// Backspace - update currentInput
|
|
282
|
+
else if (key === '\x7f' || key === '\b') {
|
|
283
|
+
if (currentInput.length > 0) {
|
|
284
|
+
currentInput = currentInput.slice(0, -1);
|
|
285
|
+
}
|
|
286
|
+
completionsShown = false;
|
|
287
|
+
}
|
|
288
|
+
// Regular character
|
|
289
|
+
else if (key.length === 1 && key >= ' ' && key <= '~') {
|
|
290
|
+
currentInput += key;
|
|
291
|
+
completionsShown = false;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const onLine = (line) => {
|
|
295
|
+
cleanup();
|
|
296
|
+
// Multi-line mode: lines ending with \ continue input
|
|
297
|
+
if (line.endsWith('\\')) {
|
|
298
|
+
this.isMultiline = true;
|
|
299
|
+
this.multilineBuffer.push(line.slice(0, -1));
|
|
300
|
+
// Continue reading without resolving
|
|
301
|
+
process.stdout.write(C.dim(' ' + BOX.arrowDown + ' '));
|
|
302
|
+
this.rl?.prompt();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (this.isMultiline) {
|
|
306
|
+
this.multilineBuffer.push(line);
|
|
307
|
+
// Check if empty line ends multiline mode
|
|
308
|
+
if (line.trim() === '') {
|
|
309
|
+
const fullInput = this.multilineBuffer.join('\n').trim();
|
|
310
|
+
this.multilineBuffer = [];
|
|
311
|
+
this.isMultiline = false;
|
|
312
|
+
resolve(fullInput);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
process.stdout.write(C.dim(' ' + BOX.arrowDown + ' '));
|
|
316
|
+
this.rl?.prompt();
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
resolve(line);
|
|
321
|
+
};
|
|
322
|
+
this.rl.on('line', onLine);
|
|
323
|
+
process.stdin.on('data', onKeypress);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
/** Get all REPL commands for completion */
|
|
327
|
+
getAllReplCommands() {
|
|
328
|
+
return [
|
|
329
|
+
{ text: '/help', description: 'Show help' },
|
|
330
|
+
{ text: '/quit', description: 'Exit nova' },
|
|
331
|
+
{ text: '/clear', description: 'Clear conversation' },
|
|
332
|
+
{ text: '/status', description: 'Session info' },
|
|
333
|
+
{ text: '/model', description: 'Switch model' },
|
|
334
|
+
{ text: '/mode', description: 'Change mode (auto/plan/ask)' },
|
|
335
|
+
{ text: '/init', description: 'Generate NOVA.md' },
|
|
336
|
+
{ text: '/memory', description: 'Manage memory' },
|
|
337
|
+
{ text: '/history', description: 'Session history' },
|
|
338
|
+
{ text: '/mcp', description: 'MCP servers' },
|
|
339
|
+
{ text: '/skills', description: 'Available skills' },
|
|
340
|
+
{ text: '/theme', description: 'Switch color theme' },
|
|
341
|
+
{ text: '/checkpoint', description: 'File snapshots' },
|
|
342
|
+
{ text: '/image', description: 'Add image to chat' },
|
|
343
|
+
{ text: '/ollama', description: 'Ollama status' },
|
|
344
|
+
{ text: '/thinking', description: 'Toggle thinking' },
|
|
345
|
+
{ text: '/compact', description: 'Toggle compact mode' },
|
|
346
|
+
];
|
|
347
|
+
}
|
|
348
|
+
/** Get completions for REPL input */
|
|
349
|
+
getReplCompletions(input) {
|
|
350
|
+
const commands = this.getAllReplCommands();
|
|
351
|
+
const partial = input.toLowerCase();
|
|
352
|
+
return commands.filter(cmd => cmd.text.toLowerCase().startsWith(partial));
|
|
353
|
+
}
|
|
354
|
+
/** Display completions */
|
|
355
|
+
showCompletions(completions) {
|
|
356
|
+
const maxLen = Math.max(...completions.map(c => c.text.length));
|
|
357
|
+
for (const c of completions.slice(0, 10)) {
|
|
358
|
+
console.log(` ${C.info(c.text.padEnd(maxLen + 2))} ${C.dim(c.description)}`);
|
|
359
|
+
}
|
|
360
|
+
if (completions.length > 10) {
|
|
361
|
+
console.log(C.dim(` ... and ${completions.length - 10} more`));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/** Display enhanced completions with type icons */
|
|
365
|
+
showEnhancedCompletions(completions) {
|
|
366
|
+
const typeIcons = {
|
|
367
|
+
command: '⌘',
|
|
368
|
+
model: '🤖',
|
|
369
|
+
file: '📄',
|
|
370
|
+
directory: '📁',
|
|
371
|
+
history: '📜',
|
|
372
|
+
option: '⚙️',
|
|
373
|
+
skill: '🎯',
|
|
374
|
+
mcp: '🔌',
|
|
375
|
+
};
|
|
376
|
+
const typeColors = {
|
|
377
|
+
command: C.info,
|
|
378
|
+
model: C.brand,
|
|
379
|
+
file: C.muted,
|
|
380
|
+
directory: C.success,
|
|
381
|
+
history: C.subtle,
|
|
382
|
+
option: C.warning,
|
|
383
|
+
skill: C.accent,
|
|
384
|
+
mcp: C.toolName,
|
|
385
|
+
};
|
|
386
|
+
const maxLen = Math.max(...completions.map(c => c.displayText.length));
|
|
387
|
+
const limit = Math.min(completions.length, 12);
|
|
388
|
+
for (const c of completions.slice(0, limit)) {
|
|
389
|
+
const icon = typeIcons[c.type] || '•';
|
|
390
|
+
const color = typeColors[c.type] || C.primary;
|
|
391
|
+
const text = c.displayText.padEnd(maxLen + 2);
|
|
392
|
+
console.log(` ${icon} ${color(text)}${C.dim(c.description)}`);
|
|
393
|
+
}
|
|
394
|
+
if (completions.length > limit) {
|
|
395
|
+
console.log(C.dim(` ... and ${completions.length - limit} more`));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/** Route a single trimmed line to the correct handler */
|
|
399
|
+
async dispatchInput(input) {
|
|
400
|
+
// /command
|
|
401
|
+
if (input.startsWith('/')) {
|
|
402
|
+
await this.handleCommand(input);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
// !shell command
|
|
406
|
+
if (input.startsWith('!')) {
|
|
407
|
+
await this.handleShellCommand(input.slice(1).trim());
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
// Regular input (may contain @file references)
|
|
411
|
+
await this.processInput(input);
|
|
412
|
+
}
|
|
413
|
+
// ========================================================================
|
|
414
|
+
// @ File reference expansion
|
|
415
|
+
// ========================================================================
|
|
416
|
+
/**
|
|
417
|
+
* Expand @path references in user input.
|
|
418
|
+
* @src/App.tsx → inlines file content
|
|
419
|
+
* @src/components/ → inlines directory listing + all files under 50KB total
|
|
420
|
+
*/
|
|
421
|
+
async expandAtReferences(input) {
|
|
422
|
+
// Match @word/path.ext or @path patterns (not email addresses)
|
|
423
|
+
const atPattern = /@([\w./\-\\]+)/g;
|
|
424
|
+
const matches = [...input.matchAll(atPattern)];
|
|
425
|
+
if (matches.length === 0)
|
|
426
|
+
return input;
|
|
427
|
+
let result = input;
|
|
428
|
+
const injections = [];
|
|
429
|
+
for (const match of matches) {
|
|
430
|
+
const refPath = match[1];
|
|
431
|
+
if (!refPath)
|
|
432
|
+
continue;
|
|
433
|
+
const absPath = path.isAbsolute(refPath)
|
|
434
|
+
? refPath
|
|
435
|
+
: path.resolve(this.cwd, refPath);
|
|
436
|
+
try {
|
|
437
|
+
if (!fs.existsSync(absPath)) {
|
|
438
|
+
injections.push(`[@ ${refPath}: file not found]`);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const stat = fs.statSync(absPath);
|
|
442
|
+
if (stat.isDirectory()) {
|
|
443
|
+
// Directory: list contents and include small files
|
|
444
|
+
const files = this.listDirRecursive(absPath, 3, 30);
|
|
445
|
+
const fileList = files.join('\n');
|
|
446
|
+
let content = `\n\`\`\`\n# Directory: ${refPath}\n${fileList}\n\`\`\`\n`;
|
|
447
|
+
// Include content of small text files (limit to 20 files, 100KB total)
|
|
448
|
+
let totalSize = 0;
|
|
449
|
+
let fileCount = 0;
|
|
450
|
+
for (const f of files) {
|
|
451
|
+
if (fileCount >= 20)
|
|
452
|
+
break;
|
|
453
|
+
const fullPath = path.join(absPath, f);
|
|
454
|
+
try {
|
|
455
|
+
if (!fs.statSync(fullPath).isFile())
|
|
456
|
+
continue;
|
|
457
|
+
const size = fs.statSync(fullPath).size;
|
|
458
|
+
if (size > 50000 || totalSize + size > 100000)
|
|
459
|
+
continue;
|
|
460
|
+
const ext = path.extname(f).slice(1);
|
|
461
|
+
if (!this.isTextFile(ext))
|
|
462
|
+
continue;
|
|
463
|
+
const fileContent = fs.readFileSync(fullPath, 'utf-8');
|
|
464
|
+
content += `\n\`\`\`${ext}\n# ${f}\n${fileContent}\n\`\`\`\n`;
|
|
465
|
+
totalSize += size;
|
|
466
|
+
fileCount++;
|
|
467
|
+
}
|
|
468
|
+
catch { /* skip */ }
|
|
469
|
+
}
|
|
470
|
+
injections.push(content);
|
|
471
|
+
console.log(C.info(` @ ${refPath} → directory (${files.length} files)`));
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
// Single file
|
|
475
|
+
const size = stat.size;
|
|
476
|
+
if (size > 200 * 1024) {
|
|
477
|
+
injections.push(`[@ ${refPath}: file too large (${(size / 1024).toFixed(0)} KB), please be more specific]`);
|
|
478
|
+
console.log(C.warning(` @ ${refPath} → too large (${(size / 1024).toFixed(0)} KB)`));
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
const ext = path.extname(refPath).slice(1);
|
|
482
|
+
const fileContent = fs.readFileSync(absPath, 'utf-8');
|
|
483
|
+
injections.push(`\n\`\`\`${ext}\n# ${refPath}\n${fileContent}\n\`\`\`\n`);
|
|
484
|
+
console.log(C.info(` @ ${refPath} → ${(size / 1024).toFixed(1)} KB`));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch (err) {
|
|
488
|
+
injections.push(`[@ ${refPath}: error reading file — ${err.message}]`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Append all injected content below the original input
|
|
492
|
+
if (injections.length > 0) {
|
|
493
|
+
result = input + '\n\n' + injections.join('\n');
|
|
494
|
+
}
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
isTextFile(ext) {
|
|
498
|
+
const TEXT_EXTS = new Set([
|
|
499
|
+
'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'json', 'yaml', 'yml', 'toml', 'ini',
|
|
500
|
+
'md', 'txt', 'html', 'css', 'scss', 'less', 'sh', 'bash', 'zsh', 'fish',
|
|
501
|
+
'py', 'rb', 'go', 'rs', 'java', 'c', 'cpp', 'h', 'hpp', 'cs', 'php', 'swift',
|
|
502
|
+
'vue', 'svelte', 'astro', 'graphql', 'sql', 'env', 'gitignore', 'lock',
|
|
503
|
+
]);
|
|
504
|
+
return TEXT_EXTS.has(ext.toLowerCase());
|
|
505
|
+
}
|
|
506
|
+
listDirRecursive(dir, maxDepth, maxFiles) {
|
|
507
|
+
const results = [];
|
|
508
|
+
const walk = (current, depth, prefix) => {
|
|
509
|
+
if (depth > maxDepth || results.length >= maxFiles)
|
|
510
|
+
return;
|
|
511
|
+
try {
|
|
512
|
+
const entries = fs.readdirSync(current).filter((e) => !e.startsWith('.') && e !== 'node_modules' && e !== 'dist' && e !== '__pycache__');
|
|
513
|
+
for (const e of entries) {
|
|
514
|
+
if (results.length >= maxFiles)
|
|
515
|
+
break;
|
|
516
|
+
const fullPath = path.join(current, e);
|
|
517
|
+
const rel = prefix ? `${prefix}/${e}` : e;
|
|
518
|
+
results.push(rel);
|
|
519
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
520
|
+
walk(fullPath, depth + 1, rel);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
catch { /* skip */ }
|
|
525
|
+
};
|
|
526
|
+
walk(dir, 0, '');
|
|
527
|
+
return results;
|
|
528
|
+
}
|
|
529
|
+
// ========================================================================
|
|
530
|
+
// Prompt & Banner
|
|
531
|
+
// ========================================================================
|
|
532
|
+
getPromptText() {
|
|
533
|
+
const modeInfo = MODE_LABELS[this.mode];
|
|
534
|
+
const modelShort = this.modelClient.getModel().split('/').pop() || this.modelClient.getModel();
|
|
535
|
+
if (this.isMultiline) {
|
|
536
|
+
// Multi-line mode indicator
|
|
537
|
+
return C.dim(' ' + BOX.arrowDown + ' ');
|
|
538
|
+
}
|
|
539
|
+
// Compact prompt: [MODE] model ›
|
|
540
|
+
const modeBadge = modeInfo.color(`[${modeInfo.label}]`);
|
|
541
|
+
const modelPart = C.muted(modelShort);
|
|
542
|
+
return `\n${modeBadge} ${modelPart} ${C.brand(BOX.arrowRight)} `;
|
|
543
|
+
}
|
|
544
|
+
printPrompt() {
|
|
545
|
+
if (this.rl) {
|
|
546
|
+
const promptText = this.getPromptText();
|
|
547
|
+
this.rl.setPrompt(promptText);
|
|
548
|
+
this.rl.prompt();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
/** Get terminal width with min/max constraints */
|
|
552
|
+
getTermWidth(min = 40, max = 120) {
|
|
553
|
+
const cols = process.stdout.columns || 80;
|
|
554
|
+
return Math.max(min, Math.min(cols - 4, max));
|
|
555
|
+
}
|
|
556
|
+
/** Print input box frame before prompt */
|
|
557
|
+
printInputBox() {
|
|
558
|
+
const modeInfo = MODE_LABELS[this.mode];
|
|
559
|
+
const modelShort = this.modelClient.getModel().split('/').pop() || this.modelClient.getModel();
|
|
560
|
+
const w = this.getTermWidth(40, 100);
|
|
561
|
+
// Get session stats for context usage
|
|
562
|
+
let contextInfo = '';
|
|
563
|
+
if (this.sessionId) {
|
|
564
|
+
const stats = this.sessionManager.getStats(this.sessionId);
|
|
565
|
+
if (stats) {
|
|
566
|
+
const totalTokens = (stats.totalInputTokens || 0) + (stats.totalOutputTokens || 0);
|
|
567
|
+
const maxContext = this.config.core.maxTokens * 8 || 128000;
|
|
568
|
+
const pct = Math.min(100, Math.round((totalTokens / maxContext) * 100));
|
|
569
|
+
const pctColor = pct > 80 ? C.error : pct > 50 ? C.warning : C.success;
|
|
570
|
+
contextInfo = pctColor(`${pct}%`) + C.dim(' ctx');
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
// Input box header
|
|
574
|
+
const modeBadge = modeInfo.color(`[${modeInfo.label}]`);
|
|
575
|
+
const headerText = `${modeBadge} ${C.muted(modelShort)}${contextInfo ? ' ' + contextInfo : ''}`;
|
|
576
|
+
const visibleLen = modeInfo.label.length + 2 + 1 + modelShort.length + (contextInfo ? 8 : 0);
|
|
577
|
+
const headerPadding = Math.max(0, w - visibleLen - 3);
|
|
578
|
+
console.log('');
|
|
579
|
+
console.log(C.brand(BOX.tl) + C.brand(BOX.hThick.repeat(w)) + C.brand(BOX.tr));
|
|
580
|
+
console.log(C.brand(BOX.v) + ' ' + headerText + ' '.repeat(headerPadding) + C.brand(BOX.v));
|
|
581
|
+
console.log(C.brand(BOX.ht) + C.brandDim(BOX.h.repeat(w)) + C.brand(BOX.htr));
|
|
582
|
+
}
|
|
583
|
+
/** Print input box footer after user submits */
|
|
584
|
+
printInputBoxFooter() {
|
|
585
|
+
const w = this.getTermWidth(40, 100);
|
|
586
|
+
console.log(C.brand(BOX.bl) + C.brand(BOX.hThick.repeat(w)) + C.brand(BOX.br));
|
|
587
|
+
}
|
|
588
|
+
/** Print compact status bar after AI response */
|
|
589
|
+
printStatusBar() {
|
|
590
|
+
const modelShort = this.modelClient.getModel().split('/').pop() || this.modelClient.getModel();
|
|
591
|
+
const modeInfo = MODE_LABELS[this.mode];
|
|
592
|
+
let contextInfo = '';
|
|
593
|
+
if (this.sessionId) {
|
|
594
|
+
const stats = this.sessionManager.getStats(this.sessionId);
|
|
595
|
+
if (stats) {
|
|
596
|
+
const totalTokens = (stats.totalInputTokens || 0) + (stats.totalOutputTokens || 0);
|
|
597
|
+
const maxContext = this.config.core.maxTokens * 8 || 128000;
|
|
598
|
+
const pct = Math.min(100, Math.round((totalTokens / maxContext) * 100));
|
|
599
|
+
const pctColor = pct > 80 ? C.error : pct > 50 ? C.warning : C.success;
|
|
600
|
+
contextInfo = pctColor(`${pct}%`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
const parts = [
|
|
604
|
+
C.muted('Model:') + ' ' + C.primary(modelShort),
|
|
605
|
+
C.muted('Mode:') + ' ' + modeInfo.color(modeInfo.label),
|
|
606
|
+
contextInfo ? C.muted('Context:') + ' ' + contextInfo : '',
|
|
607
|
+
].filter(Boolean);
|
|
608
|
+
console.log(C.dim(' ' + BOX.h.repeat(4) + ' ') + parts.join(C.dim(' · ')) + C.dim(' ' + BOX.h.repeat(4)));
|
|
609
|
+
}
|
|
610
|
+
printBanner() {
|
|
611
|
+
const modeInfo = MODE_LABELS[this.mode];
|
|
612
|
+
const modelShort = this.modelClient.getModel().split('/').pop() || this.modelClient.getModel();
|
|
613
|
+
const termCols = process.stdout.columns || 80;
|
|
614
|
+
// Use a reasonable max width, but don't exceed terminal width
|
|
615
|
+
const w = Math.min(termCols - 4, 76);
|
|
616
|
+
const hr = C.brandDim(BOX.h.repeat(w));
|
|
617
|
+
const hrThick = C.brand(BOX.hThick.repeat(w));
|
|
618
|
+
const vl = C.brandDim(BOX.v);
|
|
619
|
+
// Simple compact header instead of big ASCII art
|
|
620
|
+
console.log('');
|
|
621
|
+
console.log(C.brand(BOX.tl) + hrThick + C.brand(BOX.tr));
|
|
622
|
+
// Compact logo line
|
|
623
|
+
const logoLine = C.brand(' NOVA ') + C.brandLight('CLI') + C.dim(' · AI-powered terminal assistant');
|
|
624
|
+
const logoPadding = Math.max(0, w - 38); // 38 = visible chars in logoLine (without ANSI codes)
|
|
625
|
+
console.log(vl + logoLine + ' '.repeat(logoPadding) + vl);
|
|
626
|
+
console.log(C.brand(BOX.ht) + hr + C.brand(BOX.htr));
|
|
627
|
+
// Status line 1: Model | Dir
|
|
628
|
+
const modelLabel = C.dim('Model: ');
|
|
629
|
+
const modelVal = C.primary(modelShort);
|
|
630
|
+
const dirLabel = C.dim('Dir: ');
|
|
631
|
+
const dirVal = C.muted(this.cwd.length > 40 ? '...' + this.cwd.slice(-37) : this.cwd);
|
|
632
|
+
const line1 = ` ${modelLabel}${modelVal} ${C.dim(BOX.v)} ${dirLabel}${dirVal}`;
|
|
633
|
+
console.log(vl + line1 + ' '.repeat(Math.max(0, w - 10 - modelShort.length)) + vl);
|
|
634
|
+
// Status line 2: Mode | Session
|
|
635
|
+
const modeLabel = C.dim('Mode: ');
|
|
636
|
+
const modeVal = modeInfo.color(modeInfo.label);
|
|
637
|
+
const sessLabel = C.dim('Session: ');
|
|
638
|
+
const sessionText = this.restoreSessionId ? this.restoreSessionId.slice(0, 8) : 'new';
|
|
639
|
+
const sessVal = C.muted(sessionText);
|
|
640
|
+
const line2 = ` ${modeLabel}${modeVal} ${C.dim(BOX.v)} ${sessLabel}${sessVal}`;
|
|
641
|
+
console.log(vl + line2 + ' '.repeat(Math.max(0, w - 24)) + vl);
|
|
642
|
+
// Status line 3: MCP
|
|
643
|
+
let mcpStatus = C.dim('○');
|
|
644
|
+
let mcpText = 'none';
|
|
645
|
+
if (this.mcpManager) {
|
|
646
|
+
const statuses = this.mcpManager.listServers();
|
|
647
|
+
if (statuses.length > 0) {
|
|
648
|
+
const connected = statuses.filter((s) => s.connected).length;
|
|
649
|
+
const total = statuses.length;
|
|
650
|
+
mcpStatus = connected === total ? C.success(BOX.check) : connected > 0 ? C.warning('◐') : C.error(BOX.crossX);
|
|
651
|
+
mcpText = `${connected}/${total}`;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const mcpLabel = C.dim('MCP: ');
|
|
655
|
+
const line3 = ` ${mcpLabel}${mcpStatus} ${C.muted(mcpText)}`;
|
|
656
|
+
console.log(vl + line3 + ' '.repeat(Math.max(0, w - 16)) + vl);
|
|
657
|
+
console.log(C.brand(BOX.ht) + hr + C.brand(BOX.htr));
|
|
658
|
+
// Commands help - compact
|
|
659
|
+
const cmdLine = C.dim(' Commands: ') +
|
|
660
|
+
C.primary('/help') + C.dim(', ') +
|
|
661
|
+
C.primary('/mode') + C.dim(', ') +
|
|
662
|
+
C.primary('/model') + C.dim(', ') +
|
|
663
|
+
C.primary('/init') + C.dim(', ') +
|
|
664
|
+
C.primary('/quit');
|
|
665
|
+
console.log(vl + cmdLine + ' '.repeat(Math.max(0, w - 52)) + vl);
|
|
666
|
+
// Shortcuts
|
|
667
|
+
const shortcutLine = C.dim(' Shortcuts: ') +
|
|
668
|
+
C.info('@file') + C.dim(' inject, ') +
|
|
669
|
+
C.info('!cmd') + C.dim(' shell, ') +
|
|
670
|
+
C.info('\\') + C.dim(' multiline');
|
|
671
|
+
console.log(vl + shortcutLine + ' '.repeat(Math.max(0, w - 52)) + vl);
|
|
672
|
+
// Bottom border
|
|
673
|
+
console.log(C.brand(BOX.bl) + hrThick + C.brand(BOX.br));
|
|
674
|
+
console.log('');
|
|
675
|
+
}
|
|
676
|
+
// ========================================================================
|
|
677
|
+
// UI Helpers
|
|
678
|
+
// ========================================================================
|
|
679
|
+
printLine(label, type = 'muted') {
|
|
680
|
+
const colors = { info: C.info, success: C.success, warning: C.warning, error: C.error, muted: C.muted };
|
|
681
|
+
const color = colors[type];
|
|
682
|
+
const w = this.getTermWidth(40, 80);
|
|
683
|
+
const padded = ` ${label} `;
|
|
684
|
+
const left = Math.floor((w - padded.length) / 2);
|
|
685
|
+
const right = w - padded.length - left;
|
|
686
|
+
console.log(C.dim(BOX.h.repeat(left)) + color(padded) + C.dim(BOX.h.repeat(right)));
|
|
687
|
+
}
|
|
688
|
+
startSpinner(msg) {
|
|
689
|
+
this.stopSpinner();
|
|
690
|
+
this.spinnerFrame = 0;
|
|
691
|
+
this.spinnerTimer = setInterval(() => {
|
|
692
|
+
const frame = BOX.spinner[this.spinnerFrame % BOX.spinner.length];
|
|
693
|
+
process.stdout.write(`\r${C.brand(frame)} ${C.muted(msg)}`);
|
|
694
|
+
this.spinnerFrame++;
|
|
695
|
+
}, 80);
|
|
696
|
+
}
|
|
697
|
+
stopSpinner() {
|
|
698
|
+
if (this.spinnerTimer) {
|
|
699
|
+
clearInterval(this.spinnerTimer);
|
|
700
|
+
this.spinnerTimer = null;
|
|
701
|
+
const clearWidth = Math.min(60, (process.stdout.columns || 80) - 1);
|
|
702
|
+
process.stdout.write('\r' + ' '.repeat(clearWidth) + '\r');
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
// ========================================================================
|
|
706
|
+
// !shell direct execution
|
|
707
|
+
// ========================================================================
|
|
708
|
+
async handleShellCommand(cmd) {
|
|
709
|
+
if (!cmd) {
|
|
710
|
+
console.log(C.muted(' Usage: !<command> e.g. !ls, !git status, !npm test'));
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
console.log('');
|
|
714
|
+
console.log(C.muted(` $ ${cmd}`));
|
|
715
|
+
const startTime = Date.now();
|
|
716
|
+
try {
|
|
717
|
+
// Use spawn for streaming output
|
|
718
|
+
const isWin = process.platform === 'win32';
|
|
719
|
+
const shell = isWin ? 'powershell.exe' : '/bin/sh';
|
|
720
|
+
const shellArgs = isWin ? ['-Command', cmd] : ['-c', cmd];
|
|
721
|
+
await new Promise((resolve, reject) => {
|
|
722
|
+
const child = spawn(shell, shellArgs, {
|
|
723
|
+
cwd: this.cwd,
|
|
724
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
725
|
+
});
|
|
726
|
+
child.stdout?.on('data', (chunk) => process.stdout.write(C.primary(chunk.toString())));
|
|
727
|
+
child.stderr?.on('data', (chunk) => process.stderr.write(C.warning(chunk.toString())));
|
|
728
|
+
child.on('close', (code) => {
|
|
729
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
730
|
+
if (code === 0) {
|
|
731
|
+
console.log('');
|
|
732
|
+
console.log(C.success(` ✓ exit 0`) + C.dim(` (${duration}s)`));
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
console.log('');
|
|
736
|
+
console.log(C.error(` ✗ exit ${code}`) + C.dim(` (${duration}s)`));
|
|
737
|
+
}
|
|
738
|
+
resolve();
|
|
739
|
+
});
|
|
740
|
+
child.on('error', reject);
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
catch (err) {
|
|
744
|
+
console.log(C.error(` Error: ${err.message}`));
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// ========================================================================
|
|
748
|
+
// Agent loop processing
|
|
749
|
+
// ========================================================================
|
|
750
|
+
async processInput(input) {
|
|
751
|
+
if (!this.sessionId)
|
|
752
|
+
return;
|
|
753
|
+
this.processing = true;
|
|
754
|
+
this.activeToolCalls.clear();
|
|
755
|
+
this.toolCallOrder = [];
|
|
756
|
+
this.currentTurn = 0;
|
|
757
|
+
this.currentTaskTokens = 0;
|
|
758
|
+
// Clear any previous TODO panel
|
|
759
|
+
this.todoProgressPanel.hide();
|
|
760
|
+
// ESC key listener: cancel running agent loop
|
|
761
|
+
const onEscKey = (buf) => {
|
|
762
|
+
// ESC = \x1b, but arrow keys also start with \x1b[ so we check the raw byte
|
|
763
|
+
if (buf.length === 1 && buf[0] === 0x1b) {
|
|
764
|
+
if (this.currentLoop?.isActive()) {
|
|
765
|
+
this.currentLoop.cancel();
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
process.stdin.on('data', onEscKey);
|
|
770
|
+
// Expand @file references
|
|
771
|
+
const expandedInput = await this.expandAtReferences(input);
|
|
772
|
+
// Show user message with highlighted box (distinctive from AI responses)
|
|
773
|
+
this.userMessageHighlight.render(input);
|
|
774
|
+
const modePrefix = this.getModePrefix();
|
|
775
|
+
// Skill injection
|
|
776
|
+
let skillPrefix = '';
|
|
777
|
+
if (this._pendingSkillInject) {
|
|
778
|
+
const skillName = this._pendingSkillInject.metadata.name;
|
|
779
|
+
skillPrefix = `[SKILL: ${skillName}]\n${this._pendingSkillInject.content}\n[/SKILL]\n\n`;
|
|
780
|
+
console.log(C.info(` ⚡ Skill "${skillName}" injected`));
|
|
781
|
+
this._pendingSkillInject = null;
|
|
782
|
+
}
|
|
783
|
+
const fullInput = [modePrefix, skillPrefix, expandedInput].filter(Boolean).join('\n\n');
|
|
784
|
+
try {
|
|
785
|
+
const effectiveApprovalMode = this.getEffectiveApprovalMode();
|
|
786
|
+
// Get model config to check for built-in search capability
|
|
787
|
+
const modelConfigResult = this.configManager.getModelConfig(this.modelClient.getModel());
|
|
788
|
+
const systemPrompt = buildSystemPrompt({
|
|
789
|
+
workingDirectory: this.cwd,
|
|
790
|
+
model: this.modelClient.getModel(),
|
|
791
|
+
approvalMode: effectiveApprovalMode,
|
|
792
|
+
supportsBuiltinSearch: modelConfigResult?.model?.supportsBuiltinSearch,
|
|
793
|
+
});
|
|
794
|
+
const agentLoop = new AgentLoop({
|
|
795
|
+
modelClient: this.modelClient,
|
|
796
|
+
sessionManager: this.sessionManager,
|
|
797
|
+
toolRegistry: this.toolRegistry,
|
|
798
|
+
systemPrompt,
|
|
799
|
+
contextCompressor: this.contextCompressor,
|
|
800
|
+
maxContextTokens: (this.config.core.maxTokens || 16384) * 8,
|
|
801
|
+
onTextDelta: (text) => {
|
|
802
|
+
this.stopSpinner();
|
|
803
|
+
process.stdout.write(text);
|
|
804
|
+
},
|
|
805
|
+
onToolStart: (name, toolCallId, input) => {
|
|
806
|
+
this.stopSpinner();
|
|
807
|
+
if (this.thinkingRenderer.isRendering()) {
|
|
808
|
+
this.thinkingRenderer.cancel();
|
|
809
|
+
process.stdout.write('\n');
|
|
810
|
+
}
|
|
811
|
+
// Summarize tool input for display
|
|
812
|
+
const inputSummary = input ? this.summarizeToolInput(name, input) : '';
|
|
813
|
+
const state = {
|
|
814
|
+
name, toolCallId,
|
|
815
|
+
startTime: Date.now(),
|
|
816
|
+
input: inputSummary, result: '',
|
|
817
|
+
isError: false, isComplete: false,
|
|
818
|
+
lineIndex: this.toolCallOrder.length,
|
|
819
|
+
};
|
|
820
|
+
this.activeToolCalls.set(toolCallId, state);
|
|
821
|
+
this.toolCallOrder.push(toolCallId);
|
|
822
|
+
// Start spinner for this tool
|
|
823
|
+
this.startToolSpinner(state);
|
|
824
|
+
},
|
|
825
|
+
onToolComplete: (name, toolCallId, result) => {
|
|
826
|
+
this.stopSpinner();
|
|
827
|
+
const state = this.activeToolCalls.get(toolCallId);
|
|
828
|
+
if (state) {
|
|
829
|
+
state.result = typeof result.content === 'string' ? result.content : JSON.stringify(result.content);
|
|
830
|
+
state.isError = !!result.isError;
|
|
831
|
+
state.isComplete = true;
|
|
832
|
+
this.printToolLine(state);
|
|
833
|
+
if (name === 'todo' && !result.isError) {
|
|
834
|
+
this.printTodoPanel(state.result);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
},
|
|
838
|
+
onThinkingStart: () => {
|
|
839
|
+
if (!this.showThinking)
|
|
840
|
+
return;
|
|
841
|
+
this.stopSpinner();
|
|
842
|
+
this.thinkingRenderer.start();
|
|
843
|
+
},
|
|
844
|
+
onThinkingDelta: (delta) => {
|
|
845
|
+
if (!this.showThinking)
|
|
846
|
+
return;
|
|
847
|
+
this.thinkingRenderer.append(delta);
|
|
848
|
+
},
|
|
849
|
+
onThinkingEnd: () => {
|
|
850
|
+
if (!this.showThinking)
|
|
851
|
+
return;
|
|
852
|
+
this.thinkingRenderer.complete();
|
|
853
|
+
},
|
|
854
|
+
onApprovalRequired: this.handleApproval.bind(this),
|
|
855
|
+
onTurnStart: (turn) => {
|
|
856
|
+
this.currentTurn = turn;
|
|
857
|
+
this.startSpinner(`turn ${turn} — thinking...`);
|
|
858
|
+
},
|
|
859
|
+
onTurnEnd: () => {
|
|
860
|
+
this.stopSpinner();
|
|
861
|
+
},
|
|
862
|
+
onContextCompress: (orig, result, action) => {
|
|
863
|
+
console.log(C.muted(`\n ${BOX.arrow} context compressed: ${orig} → ${result} tokens (${action})`));
|
|
864
|
+
},
|
|
865
|
+
});
|
|
866
|
+
this.currentLoop = agentLoop;
|
|
867
|
+
const startTime = Date.now();
|
|
868
|
+
const result = await agentLoop.runStream(this.sessionId, fullInput);
|
|
869
|
+
const duration = Date.now() - startTime;
|
|
870
|
+
this.currentLoop = null;
|
|
871
|
+
this.processing = false;
|
|
872
|
+
this.stopSpinner();
|
|
873
|
+
// Auto-persist session after every turn
|
|
874
|
+
this.sessionManager.persist(this.sessionId);
|
|
875
|
+
console.log('');
|
|
876
|
+
const totalTokens = result.totalInputTokens + result.totalOutputTokens;
|
|
877
|
+
// Compact completion summary with icons
|
|
878
|
+
const summaryParts = [
|
|
879
|
+
`${C.success(BOX.check)} ${C.muted(`${result.turnsCompleted} turn${result.turnsCompleted > 1 ? 's' : ''}`)}`,
|
|
880
|
+
`${C.info(BOX.diamond)} ${C.muted(`${totalTokens.toLocaleString()} tok`)}`,
|
|
881
|
+
`${C.accent(BOX.star)} ${C.muted(`${(duration / 1000).toFixed(1)}s`)}`,
|
|
882
|
+
];
|
|
883
|
+
console.log(C.dim(' ' + BOX.h.repeat(4)) + ' ' +
|
|
884
|
+
C.success('Done') + ' ' +
|
|
885
|
+
summaryParts.join(C.dim(' · ')) + ' ' +
|
|
886
|
+
C.dim(BOX.h.repeat(4)));
|
|
887
|
+
}
|
|
888
|
+
catch (err) {
|
|
889
|
+
this.currentLoop = null;
|
|
890
|
+
this.processing = false;
|
|
891
|
+
this.thinkingRenderer.cancel();
|
|
892
|
+
this.stopSpinner();
|
|
893
|
+
if (err && typeof err === 'object' && 'name' in err && err.name === 'CancelledError') {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
console.log('');
|
|
897
|
+
this.printLine('error', 'error');
|
|
898
|
+
console.error(C.error(` ${err.message}`));
|
|
899
|
+
}
|
|
900
|
+
finally {
|
|
901
|
+
process.stdin.off('data', onEscKey);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
// ========================================================================
|
|
905
|
+
// Tool call display (collapsible panel)
|
|
906
|
+
// ========================================================================
|
|
907
|
+
/** Summarize tool input into a one-line preview */
|
|
908
|
+
summarizeToolInput(name, input) {
|
|
909
|
+
switch (name) {
|
|
910
|
+
case 'read_file':
|
|
911
|
+
case 'list_directory':
|
|
912
|
+
return String(input.file_path || input.path || input.target_directory || '').replace(/\\/g, '/').split('/').slice(-2).join('/');
|
|
913
|
+
case 'write_file':
|
|
914
|
+
return String(input.file_path || input.path || '').replace(/\\/g, '/').split('/').slice(-2).join('/') + ' (write)';
|
|
915
|
+
case 'edit_file':
|
|
916
|
+
case 'replace_in_file':
|
|
917
|
+
return String(input.file_path || '').replace(/\\/g, '/').split('/').slice(-2).join('/') + ' (edit)';
|
|
918
|
+
case 'execute_command':
|
|
919
|
+
return String(input.command || '').slice(0, 60);
|
|
920
|
+
case 'search_file':
|
|
921
|
+
return String(input.pattern || '') + ' in ' + String(input.path || '.').split('/').pop();
|
|
922
|
+
case 'search_content':
|
|
923
|
+
return '"' + String(input.pattern || '').slice(0, 40) + '"';
|
|
924
|
+
case 'web_search':
|
|
925
|
+
return '"' + String(input.query || '').slice(0, 40) + '"';
|
|
926
|
+
default:
|
|
927
|
+
// Generic: show first meaningful value
|
|
928
|
+
const vals = Object.values(input).filter((v) => typeof v === 'string' && v.length > 0);
|
|
929
|
+
return vals.length > 0 ? vals[0].slice(0, 50) : '';
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
/** Start a spinner animation for a running tool */
|
|
933
|
+
startToolSpinner(state) {
|
|
934
|
+
const idx = this.toolCallOrder.indexOf(state.toolCallId) + 1;
|
|
935
|
+
const idxStr = idx.toString().padStart(2, '0');
|
|
936
|
+
const inputPreview = state.input ? ' ' + C.dim(state.input) : '';
|
|
937
|
+
// Print the tool start line
|
|
938
|
+
process.stdout.write('\n' +
|
|
939
|
+
C.dim(' ' + BOX.arrow + ' ') +
|
|
940
|
+
C.toolName(state.name) +
|
|
941
|
+
C.dim(` #${idxStr}`) +
|
|
942
|
+
inputPreview +
|
|
943
|
+
' ');
|
|
944
|
+
// Start inline spinner
|
|
945
|
+
this.spinnerFrame = 0;
|
|
946
|
+
const chars = BOX.spinner;
|
|
947
|
+
this.spinnerTimer = setInterval(() => {
|
|
948
|
+
const elapsed = Date.now() - state.startTime;
|
|
949
|
+
const elapsedStr = elapsed < 1000 ? `${elapsed}ms` : `${(elapsed / 1000).toFixed(1)}s`;
|
|
950
|
+
const frame = chars[this.spinnerFrame % chars.length];
|
|
951
|
+
process.stdout.write(`\r ${C.dim(frame)} ${C.toolName(state.name)}${C.dim(` #${idxStr}`)}${inputPreview} ${C.dim(elapsedStr)}`);
|
|
952
|
+
this.spinnerFrame++;
|
|
953
|
+
}, 80);
|
|
954
|
+
}
|
|
955
|
+
/** Print a finalized tool line (success or error) */
|
|
956
|
+
printToolLine(state) {
|
|
957
|
+
const duration = Date.now() - state.startTime;
|
|
958
|
+
const durationStr = duration < 1000 ? `${duration}ms` : `${(duration / 1000).toFixed(1)}s`;
|
|
959
|
+
const idx = this.toolCallOrder.indexOf(state.toolCallId) + 1;
|
|
960
|
+
const idxStr = idx.toString().padStart(2, '0');
|
|
961
|
+
const inputPreview = state.input ? ' ' + C.dim(state.input) : '';
|
|
962
|
+
const icon = state.isError ? C.error(BOX.crossX) : C.success(BOX.check);
|
|
963
|
+
const nameColor = state.isError ? C.toolErr : C.toolName;
|
|
964
|
+
// Result preview (one line)
|
|
965
|
+
let resultPreview = '';
|
|
966
|
+
if (state.isError) {
|
|
967
|
+
resultPreview = ' ' + C.error.dim(state.result.slice(0, 60).replace(/\n/g, ' '));
|
|
968
|
+
}
|
|
969
|
+
else if (state.result.length > 0) {
|
|
970
|
+
// Show first line of result as preview
|
|
971
|
+
const firstLine = state.result.split('\n')[0].slice(0, 60);
|
|
972
|
+
resultPreview = ' ' + C.dim(firstLine);
|
|
973
|
+
}
|
|
974
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
975
|
+
console.log(C.dim(' ' + (state.isError ? BOX.crossX : BOX.check) + ' ') +
|
|
976
|
+
nameColor(state.name) +
|
|
977
|
+
C.dim(` #${idxStr}`) +
|
|
978
|
+
inputPreview +
|
|
979
|
+
' ' +
|
|
980
|
+
(state.isError ? C.errorDim(durationStr) : C.successDim(durationStr)) +
|
|
981
|
+
resultPreview);
|
|
982
|
+
}
|
|
983
|
+
printTodoPanel(result) {
|
|
984
|
+
if (!result || result === 'No tasks tracked.' || result === 'All tasks cleared.') {
|
|
985
|
+
this.todoProgressPanel.hide();
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
const lines = result.split('\n').filter((l) => l.trim());
|
|
989
|
+
if (lines.length === 0) {
|
|
990
|
+
this.todoProgressPanel.hide();
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
// Parse TODO items from the result string
|
|
994
|
+
const todos = [];
|
|
995
|
+
let idx = 0;
|
|
996
|
+
for (const line of lines) {
|
|
997
|
+
const pendingMatch = line.match(/^○\s+\[pending\s*\]\s+(.+)/);
|
|
998
|
+
const inProgressMatch = line.match(/^◉\s+\[in_progress\s*\]\s+(.+)/);
|
|
999
|
+
const completedMatch = line.match(/^●\s+\[completed\s*\]\s+(.+)/);
|
|
1000
|
+
const failedMatch = line.match(/^✗\s+\[failed\s*\]\s+(.+)/);
|
|
1001
|
+
// Detect priority from task text (high/medium/low keywords)
|
|
1002
|
+
const detectPriority = (text) => {
|
|
1003
|
+
if (/high|critical|urgent|重要/i.test(text))
|
|
1004
|
+
return 'high';
|
|
1005
|
+
if (/low|minor|minor|低/i.test(text))
|
|
1006
|
+
return 'low';
|
|
1007
|
+
if (/medium|normal|中/i.test(text))
|
|
1008
|
+
return 'medium';
|
|
1009
|
+
return undefined;
|
|
1010
|
+
};
|
|
1011
|
+
if (completedMatch) {
|
|
1012
|
+
todos.push({
|
|
1013
|
+
id: String(idx++),
|
|
1014
|
+
task: completedMatch[1].trim(),
|
|
1015
|
+
status: 'completed',
|
|
1016
|
+
priority: detectPriority(completedMatch[1]),
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
else if (inProgressMatch) {
|
|
1020
|
+
todos.push({
|
|
1021
|
+
id: String(idx++),
|
|
1022
|
+
task: inProgressMatch[1].trim(),
|
|
1023
|
+
status: 'in_progress',
|
|
1024
|
+
priority: detectPriority(inProgressMatch[1]),
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
else if (pendingMatch) {
|
|
1028
|
+
todos.push({
|
|
1029
|
+
id: String(idx++),
|
|
1030
|
+
task: pendingMatch[1].trim(),
|
|
1031
|
+
status: 'pending',
|
|
1032
|
+
priority: detectPriority(pendingMatch[1]),
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
else if (failedMatch) {
|
|
1036
|
+
todos.push({
|
|
1037
|
+
id: String(idx++),
|
|
1038
|
+
task: failedMatch[1].trim(),
|
|
1039
|
+
status: 'failed',
|
|
1040
|
+
priority: detectPriority(failedMatch[1]),
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
// Update and show the fixed-position TODO panel
|
|
1045
|
+
if (todos.length > 0) {
|
|
1046
|
+
this.todoProgressPanel.setTodos(todos);
|
|
1047
|
+
this.todoProgressPanel.show();
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
renderProgressBar(pct, width) {
|
|
1051
|
+
const filled = Math.round((pct / 100) * width);
|
|
1052
|
+
const empty = width - filled;
|
|
1053
|
+
const bar = C.success('█'.repeat(filled)) + C.dim('░'.repeat(empty));
|
|
1054
|
+
return C.muted('[') + bar + C.muted(']') + C.muted(` ${pct}%`);
|
|
1055
|
+
}
|
|
1056
|
+
// ========================================================================
|
|
1057
|
+
// Mode & toggle operations
|
|
1058
|
+
// ========================================================================
|
|
1059
|
+
cycleMode() {
|
|
1060
|
+
const idx = MODES.indexOf(this.mode);
|
|
1061
|
+
const nextIdx = (idx + 1) % MODES.length;
|
|
1062
|
+
this.mode = MODES[nextIdx] ?? 'auto';
|
|
1063
|
+
const info = MODE_LABELS[this.mode];
|
|
1064
|
+
console.log(C.dim(' ' + BOX.arrowRight) + ' ' +
|
|
1065
|
+
C.muted('Mode: ') + info.color(info.label) + ' ' +
|
|
1066
|
+
C.dim('·') + ' ' + C.muted(info.description));
|
|
1067
|
+
}
|
|
1068
|
+
toggleThinking() {
|
|
1069
|
+
this.showThinking = !this.showThinking;
|
|
1070
|
+
const status = this.showThinking ? C.success('ON') : C.error('OFF');
|
|
1071
|
+
const icon = this.showThinking ? C.success(BOX.check) : C.error(BOX.crossX);
|
|
1072
|
+
console.log(C.dim(` ${icon} Thinking: ${status}`));
|
|
1073
|
+
}
|
|
1074
|
+
toggleCompact() {
|
|
1075
|
+
this.compactMode = !this.compactMode;
|
|
1076
|
+
this.thinkingRenderer.setExpanded(!this.compactMode);
|
|
1077
|
+
const status = this.compactMode ? C.success('compact') : C.info('verbose');
|
|
1078
|
+
console.log(C.dim(` ${BOX.diamond} Display: ${status}`));
|
|
1079
|
+
}
|
|
1080
|
+
// ========================================================================
|
|
1081
|
+
// Commands
|
|
1082
|
+
// ========================================================================
|
|
1083
|
+
async handleCommand(cmd) {
|
|
1084
|
+
const parts = cmd.slice(1).split(/\s+/);
|
|
1085
|
+
const command = parts[0];
|
|
1086
|
+
const arg = parts.slice(1).join(' ');
|
|
1087
|
+
switch (command) {
|
|
1088
|
+
case 'quit':
|
|
1089
|
+
case 'exit':
|
|
1090
|
+
case 'q':
|
|
1091
|
+
if (this.sessionId)
|
|
1092
|
+
this.sessionManager.persist(this.sessionId);
|
|
1093
|
+
console.log(C.muted('Goodbye!'));
|
|
1094
|
+
process.exit(0);
|
|
1095
|
+
case 'help':
|
|
1096
|
+
case 'h':
|
|
1097
|
+
case '?':
|
|
1098
|
+
this.printHelp();
|
|
1099
|
+
break;
|
|
1100
|
+
case 'clear':
|
|
1101
|
+
case 'reset':
|
|
1102
|
+
if (this.sessionId)
|
|
1103
|
+
this.sessionManager.persist(this.sessionId);
|
|
1104
|
+
this.sessionId = this.createInitialSession();
|
|
1105
|
+
console.log(C.muted(' Conversation cleared. New session started.'));
|
|
1106
|
+
break;
|
|
1107
|
+
case 'model':
|
|
1108
|
+
await this.handleModelCommand(arg);
|
|
1109
|
+
break;
|
|
1110
|
+
case 'mode':
|
|
1111
|
+
if (arg && MODES.includes(arg)) {
|
|
1112
|
+
this.mode = arg;
|
|
1113
|
+
const info = MODE_LABELS[this.mode];
|
|
1114
|
+
console.log(C.muted(' Mode: ') + info.color(info.label) + C.muted(` — ${info.description}`));
|
|
1115
|
+
console.log(C.muted(` Approval: `) + C.info(info.approvalMode));
|
|
1116
|
+
}
|
|
1117
|
+
else {
|
|
1118
|
+
this.cycleMode();
|
|
1119
|
+
}
|
|
1120
|
+
break;
|
|
1121
|
+
case 'thinking':
|
|
1122
|
+
this.toggleThinking();
|
|
1123
|
+
break;
|
|
1124
|
+
case 'compact':
|
|
1125
|
+
this.toggleCompact();
|
|
1126
|
+
break;
|
|
1127
|
+
case 'tools': {
|
|
1128
|
+
const tools = this.toolRegistry.getEnabledToolNames();
|
|
1129
|
+
const stats = this.toolRegistry.getStats();
|
|
1130
|
+
console.log(C.muted(` Tools (${stats.enabled}): `) + C.primary(tools.slice(0, 10).join(', ') + (tools.length > 10 ? '...' : '')));
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
case 'mcp':
|
|
1134
|
+
await this.handleMcpCommand(arg);
|
|
1135
|
+
break;
|
|
1136
|
+
case 'ollama':
|
|
1137
|
+
await this.handleOllamaCommand(arg);
|
|
1138
|
+
break;
|
|
1139
|
+
case 'skills':
|
|
1140
|
+
await this.handleSkillsCommand(arg);
|
|
1141
|
+
break;
|
|
1142
|
+
case 'theme':
|
|
1143
|
+
await this.handleThemeCommand(arg);
|
|
1144
|
+
break;
|
|
1145
|
+
case 'image':
|
|
1146
|
+
await this.handleImageCommand(arg);
|
|
1147
|
+
break;
|
|
1148
|
+
case 'checkpoint':
|
|
1149
|
+
await this.handleCheckpointCommand(arg);
|
|
1150
|
+
break;
|
|
1151
|
+
case 'init':
|
|
1152
|
+
await this.handleInitCommand(arg);
|
|
1153
|
+
break;
|
|
1154
|
+
case 'memory':
|
|
1155
|
+
await this.handleMemoryCommand(arg);
|
|
1156
|
+
break;
|
|
1157
|
+
case 'history':
|
|
1158
|
+
await this.handleHistoryCommand(arg);
|
|
1159
|
+
break;
|
|
1160
|
+
case 'compress':
|
|
1161
|
+
await this.handleCompressCommand();
|
|
1162
|
+
break;
|
|
1163
|
+
case 'status':
|
|
1164
|
+
if (this.sessionId) {
|
|
1165
|
+
const stats = this.sessionManager.getStats(this.sessionId);
|
|
1166
|
+
const modeInfo = MODE_LABELS[this.mode];
|
|
1167
|
+
console.log(C.muted(' Session: ') + C.primary(stats?.id?.slice(0, 8) || ''));
|
|
1168
|
+
console.log(C.muted(' Mode: ') + modeInfo.color(modeInfo.label));
|
|
1169
|
+
console.log(C.muted(' Turns: ') + C.primary(String(stats?.turnCount || 0)));
|
|
1170
|
+
console.log(C.muted(' Tokens: ') + C.primary(`${stats?.totalInputTokens || 0} in / ${stats?.totalOutputTokens || 0} out`));
|
|
1171
|
+
console.log(C.muted(' Msgs: ') + C.primary(String(stats?.messageCount || 0)));
|
|
1172
|
+
}
|
|
1173
|
+
break;
|
|
1174
|
+
default:
|
|
1175
|
+
console.log(C.warning(` Unknown command: /${command}. Type /help for help.`));
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
// ========================================================================
|
|
1179
|
+
// Helper: Prompt for API key
|
|
1180
|
+
// ========================================================================
|
|
1181
|
+
async promptForApiKey(providerName, providerType) {
|
|
1182
|
+
const readline = await import('node:readline');
|
|
1183
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1184
|
+
const envKey = `${providerName.toUpperCase()}_API_KEY`;
|
|
1185
|
+
console.log('');
|
|
1186
|
+
console.log(C.warning(` No API key found for "${providerName}"`));
|
|
1187
|
+
console.log(C.muted(` You can also set it via: export ${envKey}=<your-key>`));
|
|
1188
|
+
console.log('');
|
|
1189
|
+
const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
|
|
1190
|
+
try {
|
|
1191
|
+
const answer = await question(` Enter ${providerName} API key (or press Enter to skip): `);
|
|
1192
|
+
rl.close();
|
|
1193
|
+
return answer.trim() || null;
|
|
1194
|
+
}
|
|
1195
|
+
catch {
|
|
1196
|
+
rl.close();
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
// ========================================================================
|
|
1201
|
+
// /model <id> — switch model at runtime (with interactive selector)
|
|
1202
|
+
// ========================================================================
|
|
1203
|
+
async handleModelCommand(arg) {
|
|
1204
|
+
if (!arg) {
|
|
1205
|
+
// Show interactive model selector
|
|
1206
|
+
await this.showModelSelector();
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
try {
|
|
1210
|
+
this.modelClient.updateOptions({ model: arg });
|
|
1211
|
+
console.log(C.success(` ✓ Switched to model: `) + C.primary(arg));
|
|
1212
|
+
// Save to global config
|
|
1213
|
+
const config = this.configManager.getConfig();
|
|
1214
|
+
config.core.defaultModel = arg;
|
|
1215
|
+
await this.configManager.save(config);
|
|
1216
|
+
// Update session config too
|
|
1217
|
+
if (this.sessionId) {
|
|
1218
|
+
const cfg = this.sessionManager.getConfig(this.sessionId);
|
|
1219
|
+
cfg.model = arg;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
catch (err) {
|
|
1223
|
+
console.log(C.error(` Failed to switch model: ${err.message}`));
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
/** Get all available models across all providers */
|
|
1227
|
+
getAvailableModels(showAll = false) {
|
|
1228
|
+
const currentModel = this.modelClient.getModel();
|
|
1229
|
+
const models = [];
|
|
1230
|
+
const config = this.configManager.getConfig();
|
|
1231
|
+
// Collect models from configured providers only
|
|
1232
|
+
for (const [providerKey, providerConfig] of Object.entries(config.models.providers)) {
|
|
1233
|
+
// Check if provider is actually configured
|
|
1234
|
+
const hasCreds = this.authManager?.hasCredentials(providerKey) || false;
|
|
1235
|
+
const providerType = providerConfig.type;
|
|
1236
|
+
const hasBaseUrl = !!providerConfig.baseUrl;
|
|
1237
|
+
// Determine if provider is configured
|
|
1238
|
+
let configured = false;
|
|
1239
|
+
if (providerType === 'ollama') {
|
|
1240
|
+
// Local Ollama - check if baseUrl is configured (default localhost)
|
|
1241
|
+
configured = hasBaseUrl || true; // Always show if type is ollama
|
|
1242
|
+
}
|
|
1243
|
+
else if (providerType === 'ollama-cloud') {
|
|
1244
|
+
// Ollama Cloud - needs baseUrl
|
|
1245
|
+
configured = hasBaseUrl;
|
|
1246
|
+
}
|
|
1247
|
+
else if (providerType === 'custom') {
|
|
1248
|
+
// Custom providers need baseUrl
|
|
1249
|
+
configured = hasBaseUrl;
|
|
1250
|
+
}
|
|
1251
|
+
else if (['anthropic', 'openai', 'google', 'deepseek', 'qwen', 'glm', 'moonshot', 'baichuan', 'minimax', 'yi', 'siliconflow', 'groq', 'mistral', 'together', 'perplexity', 'coding-plan-alibaba', 'github'].includes(providerType)) {
|
|
1252
|
+
// Cloud providers need API keys
|
|
1253
|
+
configured = hasCreds;
|
|
1254
|
+
}
|
|
1255
|
+
else {
|
|
1256
|
+
// Unknown type - check for credentials
|
|
1257
|
+
configured = hasCreds || hasBaseUrl;
|
|
1258
|
+
}
|
|
1259
|
+
// Skip unconfigured providers unless showAll is true
|
|
1260
|
+
if (!showAll && !configured)
|
|
1261
|
+
continue;
|
|
1262
|
+
// Add models from this provider
|
|
1263
|
+
for (const [modelId, modelConfig] of Object.entries(providerConfig.models)) {
|
|
1264
|
+
models.push({
|
|
1265
|
+
id: `${providerKey}/${modelId}`,
|
|
1266
|
+
name: modelConfig.name || modelId,
|
|
1267
|
+
provider: providerKey,
|
|
1268
|
+
configured,
|
|
1269
|
+
providerType: providerType,
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
// Also add common aliases for configured providers
|
|
1274
|
+
if (config.models.aliases) {
|
|
1275
|
+
for (const [alias, targetId] of Object.entries(config.models.aliases)) {
|
|
1276
|
+
// Find which provider this alias belongs to
|
|
1277
|
+
for (const [providerKey, providerConfig] of Object.entries(config.models.providers)) {
|
|
1278
|
+
if (providerConfig.models[targetId]) {
|
|
1279
|
+
const hasCreds = this.authManager?.hasCredentials(providerKey) || false;
|
|
1280
|
+
const providerType = providerConfig.type;
|
|
1281
|
+
const hasBaseUrl = !!providerConfig.baseUrl;
|
|
1282
|
+
let configured = false;
|
|
1283
|
+
if (providerType === 'ollama') {
|
|
1284
|
+
configured = true;
|
|
1285
|
+
}
|
|
1286
|
+
else if (providerType === 'ollama-cloud' || providerType === 'custom') {
|
|
1287
|
+
configured = hasBaseUrl;
|
|
1288
|
+
}
|
|
1289
|
+
else {
|
|
1290
|
+
configured = hasCreds;
|
|
1291
|
+
}
|
|
1292
|
+
if (configured && !models.find(m => m.id === alias)) {
|
|
1293
|
+
models.push({
|
|
1294
|
+
id: alias,
|
|
1295
|
+
name: providerConfig.models[targetId]?.name || alias,
|
|
1296
|
+
provider: providerKey,
|
|
1297
|
+
configured,
|
|
1298
|
+
providerType: providerType,
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
break;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return models;
|
|
1307
|
+
}
|
|
1308
|
+
/** Interactive model selector with keyboard navigation - Windows compatible */
|
|
1309
|
+
async showModelSelector() {
|
|
1310
|
+
const models = this.getAvailableModels(false); // Only show configured models
|
|
1311
|
+
const currentModel = this.modelClient.getModel();
|
|
1312
|
+
if (models.length === 0) {
|
|
1313
|
+
console.log(C.warning(' No models available for current provider.'));
|
|
1314
|
+
console.log(C.muted(' Current model: ') + C.primary(currentModel));
|
|
1315
|
+
console.log(C.dim(' Usage: /model <model-id>'));
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
// Sort models, current model first
|
|
1319
|
+
models.sort((a, b) => {
|
|
1320
|
+
if (a.id === currentModel)
|
|
1321
|
+
return -1;
|
|
1322
|
+
if (b.id === currentModel)
|
|
1323
|
+
return 1;
|
|
1324
|
+
return a.id.localeCompare(b.id);
|
|
1325
|
+
});
|
|
1326
|
+
let selectedIndex = models.findIndex(m => m.id === currentModel);
|
|
1327
|
+
if (selectedIndex < 0)
|
|
1328
|
+
selectedIndex = 0;
|
|
1329
|
+
// Render function - clears screen and redraws everything
|
|
1330
|
+
const renderModels = (selected) => {
|
|
1331
|
+
// Clear the terminal for a clean redraw
|
|
1332
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
1333
|
+
// Print header
|
|
1334
|
+
console.log('');
|
|
1335
|
+
console.log(C.brand(` ╭──────────────────────────────────────────────────────────╮`));
|
|
1336
|
+
console.log(C.brand(` │ `) + C.primary('Model Selector').padEnd(56) + C.brand(`│`));
|
|
1337
|
+
console.log(C.brand(` │ `) + C.dim(`Current: ${currentModel}`).padEnd(56) + C.brand(`│`));
|
|
1338
|
+
console.log(C.brand(` │ `) + C.muted(`↑↓ Navigate | Enter Select | Esc Cancel`).padEnd(56) + C.brand(`│`));
|
|
1339
|
+
console.log(C.brand(` ├──────────────────────────────────────────────────────────┤`));
|
|
1340
|
+
// Render models
|
|
1341
|
+
for (let i = 0; i < models.length; i++) {
|
|
1342
|
+
const m = models[i];
|
|
1343
|
+
const isSelected = i === selected;
|
|
1344
|
+
const isCurrent = m.id === currentModel;
|
|
1345
|
+
// Configuration status
|
|
1346
|
+
const statusIcon = m.configured ? '✓' : '⚠';
|
|
1347
|
+
const statusColor = m.configured ? C.success : C.warning;
|
|
1348
|
+
const statusText = m.configured ? '' : C.warning(' [needs setup]');
|
|
1349
|
+
// Selection indicator
|
|
1350
|
+
const prefix = isSelected ? C.brand('→ ') : ' ';
|
|
1351
|
+
// Format line
|
|
1352
|
+
let modelDisplay = m.name;
|
|
1353
|
+
if (isCurrent) {
|
|
1354
|
+
modelDisplay = C.success(`${m.name}`) + C.dim(` (${m.id})`);
|
|
1355
|
+
}
|
|
1356
|
+
else if (isSelected) {
|
|
1357
|
+
modelDisplay = C.primary(m.name) + C.dim(` (${m.id})`);
|
|
1358
|
+
}
|
|
1359
|
+
else {
|
|
1360
|
+
modelDisplay = C.muted(m.name) + C.dim(` (${m.id})`);
|
|
1361
|
+
}
|
|
1362
|
+
console.log(C.brand(` │ `) + prefix + statusColor(statusIcon) + ' ' + modelDisplay + statusText + C.brand(`│`));
|
|
1363
|
+
}
|
|
1364
|
+
// Print footer
|
|
1365
|
+
console.log(C.brand(` ╰──────────────────────────────────────────────────────────╯`));
|
|
1366
|
+
};
|
|
1367
|
+
// Initial render
|
|
1368
|
+
renderModels(selectedIndex);
|
|
1369
|
+
// Handle keyboard input
|
|
1370
|
+
return new Promise((resolve) => {
|
|
1371
|
+
// Set stdin to raw mode for key detection
|
|
1372
|
+
const wasRaw = process.stdin.isRaw;
|
|
1373
|
+
if (process.stdin.isTTY) {
|
|
1374
|
+
process.stdin.setRawMode(true);
|
|
1375
|
+
}
|
|
1376
|
+
process.stdin.resume();
|
|
1377
|
+
const cleanup = () => {
|
|
1378
|
+
if (process.stdin.isTTY) {
|
|
1379
|
+
process.stdin.setRawMode(wasRaw ?? false);
|
|
1380
|
+
}
|
|
1381
|
+
process.stdin.off('data', onData);
|
|
1382
|
+
};
|
|
1383
|
+
const onData = (buffer) => {
|
|
1384
|
+
const key = buffer.toString();
|
|
1385
|
+
// Up arrow
|
|
1386
|
+
if (key === '\x1b[A' || key === 'k') {
|
|
1387
|
+
selectedIndex = (selectedIndex - 1 + models.length) % models.length;
|
|
1388
|
+
renderModels(selectedIndex);
|
|
1389
|
+
}
|
|
1390
|
+
// Down arrow
|
|
1391
|
+
else if (key === '\x1b[B' || key === 'j') {
|
|
1392
|
+
selectedIndex = (selectedIndex + 1) % models.length;
|
|
1393
|
+
renderModels(selectedIndex);
|
|
1394
|
+
}
|
|
1395
|
+
// Enter
|
|
1396
|
+
else if (key === '\r' || key === '\n') {
|
|
1397
|
+
cleanup();
|
|
1398
|
+
const selected = models[selectedIndex];
|
|
1399
|
+
// Clear and show result
|
|
1400
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
1401
|
+
console.log('');
|
|
1402
|
+
// Check if model is configured
|
|
1403
|
+
const providerName = selected.id.split(':')[0];
|
|
1404
|
+
const hasCreds = this.authManager?.hasCredentials(providerName);
|
|
1405
|
+
const isOllama = providerName === 'ollama' || providerName === 'ollama-cloud';
|
|
1406
|
+
// Handle model switching with proper error handling
|
|
1407
|
+
const switchModel = async () => {
|
|
1408
|
+
try {
|
|
1409
|
+
if (!hasCreds && !isOllama && this.authManager) {
|
|
1410
|
+
// Model not configured - prompt for setup
|
|
1411
|
+
console.log(C.warning(` Model "${selected.name}" is not configured.`));
|
|
1412
|
+
console.log(C.muted(' Please provide API credentials to use this model.'));
|
|
1413
|
+
console.log('');
|
|
1414
|
+
// Prompt for API key
|
|
1415
|
+
const apiKey = await this.promptForApiKey(providerName, providerName);
|
|
1416
|
+
if (apiKey) {
|
|
1417
|
+
await this.authManager.setCredentials({ provider: providerName, apiKey });
|
|
1418
|
+
console.log('');
|
|
1419
|
+
console.log(C.success(` ✓ Configuration saved. Switching to ${selected.name}...`));
|
|
1420
|
+
}
|
|
1421
|
+
else {
|
|
1422
|
+
console.log(C.error(' ✗ Configuration cancelled or failed.'));
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
else if (selected.id === currentModel) {
|
|
1427
|
+
console.log(C.muted(' Model unchanged: ') + C.primary(currentModel));
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
// Switch model
|
|
1431
|
+
this.modelClient.updateOptions({ model: selected.id });
|
|
1432
|
+
console.log(C.success(` ✓ Switched to: `) + C.primary(selected.id));
|
|
1433
|
+
// Save to global config
|
|
1434
|
+
const config = this.configManager.getConfig();
|
|
1435
|
+
config.core.defaultModel = selected.id;
|
|
1436
|
+
await this.configManager.save(config);
|
|
1437
|
+
if (this.sessionId) {
|
|
1438
|
+
const cfg = this.sessionManager.getConfig(this.sessionId);
|
|
1439
|
+
cfg.model = selected.id;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
catch (err) {
|
|
1443
|
+
console.log(C.error(` Error switching model: ${err.message}`));
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
// Execute switch and then resolve
|
|
1447
|
+
switchModel().then(() => resolve()).catch(err => {
|
|
1448
|
+
console.log(C.error(` Error: ${err.message}`));
|
|
1449
|
+
resolve();
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
// Escape or Ctrl+C
|
|
1453
|
+
else if (key === '\x1b' || key === '\x03') {
|
|
1454
|
+
cleanup();
|
|
1455
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
1456
|
+
console.log('');
|
|
1457
|
+
console.log(C.muted(' Cancelled'));
|
|
1458
|
+
resolve();
|
|
1459
|
+
}
|
|
1460
|
+
// Number key for quick select (1-9)
|
|
1461
|
+
else if (key >= '1' && key <= '9') {
|
|
1462
|
+
const num = parseInt(key, 10) - 1;
|
|
1463
|
+
if (num < models.length) {
|
|
1464
|
+
selectedIndex = num;
|
|
1465
|
+
renderModels(selectedIndex);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
process.stdin.on('data', onData);
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
// ========================================================================
|
|
1473
|
+
// /init — generate NOVA.md project memory file
|
|
1474
|
+
// ========================================================================
|
|
1475
|
+
async handleInitCommand(arg) {
|
|
1476
|
+
const targetDir = arg ? path.resolve(this.cwd, arg) : this.cwd;
|
|
1477
|
+
const novaFile = path.join(targetDir, 'NOVA.md');
|
|
1478
|
+
if (fs.existsSync(novaFile)) {
|
|
1479
|
+
console.log(C.warning(` NOVA.md already exists at: ${novaFile}`));
|
|
1480
|
+
console.log(C.muted(' Use /init --force to regenerate'));
|
|
1481
|
+
if (!arg.includes('--force'))
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
console.log('');
|
|
1485
|
+
console.log(C.brand(' Scanning project structure...'));
|
|
1486
|
+
// Gather project info
|
|
1487
|
+
const scanResult = this.scanProjectForInit(targetDir);
|
|
1488
|
+
const content = this.generateNovaMd(scanResult, targetDir);
|
|
1489
|
+
fs.writeFileSync(novaFile, content, 'utf-8');
|
|
1490
|
+
console.log(C.success(` ✓ NOVA.md created at ${novaFile}`));
|
|
1491
|
+
console.log(C.muted(` This file helps the AI understand your project.`));
|
|
1492
|
+
console.log(C.muted(` Edit it to add custom instructions and context.`));
|
|
1493
|
+
console.log('');
|
|
1494
|
+
console.log(C.dim(' Preview:'));
|
|
1495
|
+
const preview = content.split('\n').slice(0, 20).join('\n');
|
|
1496
|
+
console.log(C.dim(preview));
|
|
1497
|
+
if (content.split('\n').length > 20)
|
|
1498
|
+
console.log(C.dim(' ...'));
|
|
1499
|
+
}
|
|
1500
|
+
scanProjectForInit(dir) {
|
|
1501
|
+
const result = {};
|
|
1502
|
+
// Package.json
|
|
1503
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
1504
|
+
if (fs.existsSync(pkgPath)) {
|
|
1505
|
+
try {
|
|
1506
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
1507
|
+
result.name = pkg.name;
|
|
1508
|
+
result.version = pkg.version;
|
|
1509
|
+
result.description = pkg.description;
|
|
1510
|
+
result.scripts = pkg.scripts;
|
|
1511
|
+
result.dependencies = Object.keys(pkg.dependencies || {}).slice(0, 20);
|
|
1512
|
+
result.devDependencies = Object.keys(pkg.devDependencies || {}).slice(0, 20);
|
|
1513
|
+
result.packageManager = pkg.packageManager;
|
|
1514
|
+
result.type = pkg.type;
|
|
1515
|
+
}
|
|
1516
|
+
catch { /* skip */ }
|
|
1517
|
+
}
|
|
1518
|
+
// Detect project type
|
|
1519
|
+
const indicators = {
|
|
1520
|
+
typescript: fs.existsSync(path.join(dir, 'tsconfig.json')),
|
|
1521
|
+
react: fs.existsSync(path.join(dir, 'src', 'App.tsx')) || fs.existsSync(path.join(dir, 'src', 'App.jsx')),
|
|
1522
|
+
nextjs: fs.existsSync(path.join(dir, 'next.config.js')) || fs.existsSync(path.join(dir, 'next.config.ts')),
|
|
1523
|
+
vite: fs.existsSync(path.join(dir, 'vite.config.ts')) || fs.existsSync(path.join(dir, 'vite.config.js')),
|
|
1524
|
+
monorepo: fs.existsSync(path.join(dir, 'pnpm-workspace.yaml')) || fs.existsSync(path.join(dir, 'turbo.json')),
|
|
1525
|
+
python: fs.existsSync(path.join(dir, 'pyproject.toml')) || fs.existsSync(path.join(dir, 'requirements.txt')),
|
|
1526
|
+
rust: fs.existsSync(path.join(dir, 'Cargo.toml')),
|
|
1527
|
+
go: fs.existsSync(path.join(dir, 'go.mod')),
|
|
1528
|
+
docker: fs.existsSync(path.join(dir, 'Dockerfile')),
|
|
1529
|
+
git: fs.existsSync(path.join(dir, '.git')),
|
|
1530
|
+
};
|
|
1531
|
+
result.indicators = indicators;
|
|
1532
|
+
// Top-level structure
|
|
1533
|
+
try {
|
|
1534
|
+
const entries = fs.readdirSync(dir).filter((e) => !e.startsWith('.') && e !== 'node_modules');
|
|
1535
|
+
result.topLevel = entries;
|
|
1536
|
+
}
|
|
1537
|
+
catch { /* skip */ }
|
|
1538
|
+
// README
|
|
1539
|
+
const readmePath = path.join(dir, 'README.md');
|
|
1540
|
+
if (fs.existsSync(readmePath)) {
|
|
1541
|
+
try {
|
|
1542
|
+
result.readme = fs.readFileSync(readmePath, 'utf-8').slice(0, 1000);
|
|
1543
|
+
}
|
|
1544
|
+
catch { /* skip */ }
|
|
1545
|
+
}
|
|
1546
|
+
// Git remote
|
|
1547
|
+
try {
|
|
1548
|
+
const remote = execSync('git remote get-url origin 2>/dev/null', { cwd: dir, encoding: 'utf-8', timeout: 3000 }).trim();
|
|
1549
|
+
result.gitRemote = remote;
|
|
1550
|
+
}
|
|
1551
|
+
catch { /* skip */ }
|
|
1552
|
+
return result;
|
|
1553
|
+
}
|
|
1554
|
+
generateNovaMd(scan, dir) {
|
|
1555
|
+
const name = scan.name || path.basename(dir);
|
|
1556
|
+
const date = new Date().toISOString().split('T')[0];
|
|
1557
|
+
const indicators = (scan.indicators || {});
|
|
1558
|
+
const tech = [];
|
|
1559
|
+
if (indicators.typescript)
|
|
1560
|
+
tech.push('TypeScript');
|
|
1561
|
+
if (indicators.react)
|
|
1562
|
+
tech.push('React');
|
|
1563
|
+
if (indicators.nextjs)
|
|
1564
|
+
tech.push('Next.js');
|
|
1565
|
+
if (indicators.vite)
|
|
1566
|
+
tech.push('Vite');
|
|
1567
|
+
if (indicators.monorepo)
|
|
1568
|
+
tech.push('Monorepo');
|
|
1569
|
+
if (indicators.python)
|
|
1570
|
+
tech.push('Python');
|
|
1571
|
+
if (indicators.rust)
|
|
1572
|
+
tech.push('Rust');
|
|
1573
|
+
if (indicators.go)
|
|
1574
|
+
tech.push('Go');
|
|
1575
|
+
if (indicators.docker)
|
|
1576
|
+
tech.push('Docker');
|
|
1577
|
+
const scripts = scan.scripts;
|
|
1578
|
+
const scriptLines = scripts ? Object.entries(scripts).map(([k, v]) => `- \`${k}\`: ${v}`).join('\n') : '';
|
|
1579
|
+
const deps = scan.dependencies || [];
|
|
1580
|
+
const devDeps = scan.devDependencies || [];
|
|
1581
|
+
return `# NOVA.md — Project Memory
|
|
1582
|
+
|
|
1583
|
+
> Auto-generated on ${date}. Edit this file to customize AI behavior for this project.
|
|
1584
|
+
|
|
1585
|
+
## Project Overview
|
|
1586
|
+
|
|
1587
|
+
**Name**: ${name}
|
|
1588
|
+
**Version**: ${scan.version || 'unknown'}
|
|
1589
|
+
**Description**: ${scan.description || '(add a description here)'}
|
|
1590
|
+
**Location**: ${dir}
|
|
1591
|
+
${scan.gitRemote ? `**Repository**: ${scan.gitRemote}` : ''}
|
|
1592
|
+
|
|
1593
|
+
## Technology Stack
|
|
1594
|
+
|
|
1595
|
+
${tech.length > 0 ? tech.map((t) => `- ${t}`).join('\n') : '- (detect automatically)'}
|
|
1596
|
+
|
|
1597
|
+
## Key Commands
|
|
1598
|
+
|
|
1599
|
+
${scriptLines || '- (add your build/test/dev commands here)'}
|
|
1600
|
+
|
|
1601
|
+
## Dependencies
|
|
1602
|
+
|
|
1603
|
+
${deps.length > 0 ? `Main: ${deps.slice(0, 10).join(', ')}` : ''}
|
|
1604
|
+
${devDeps.length > 0 ? `Dev: ${devDeps.slice(0, 10).join(', ')}` : ''}
|
|
1605
|
+
|
|
1606
|
+
## Project Structure
|
|
1607
|
+
|
|
1608
|
+
\`\`\`
|
|
1609
|
+
${(scan.topLevel || []).slice(0, 20).join('\n')}
|
|
1610
|
+
\`\`\`
|
|
1611
|
+
|
|
1612
|
+
## Coding Conventions
|
|
1613
|
+
|
|
1614
|
+
<!-- Add your project-specific conventions here -->
|
|
1615
|
+
- (e.g., Use single quotes for strings)
|
|
1616
|
+
- (e.g., Always add JSDoc comments to exported functions)
|
|
1617
|
+
- (e.g., Test files go in __tests__ directories)
|
|
1618
|
+
|
|
1619
|
+
## Important Notes for AI
|
|
1620
|
+
|
|
1621
|
+
<!-- Add any special instructions, context, or warnings for the AI assistant -->
|
|
1622
|
+
- Working directory: ${dir}
|
|
1623
|
+
- (e.g., Never commit directly to main)
|
|
1624
|
+
- (e.g., Use pnpm, not npm)
|
|
1625
|
+
|
|
1626
|
+
## File Reference
|
|
1627
|
+
|
|
1628
|
+
<!-- Add paths to key files the AI should know about -->
|
|
1629
|
+
<!-- Example: @src/types/index.ts — Core type definitions -->
|
|
1630
|
+
|
|
1631
|
+
---
|
|
1632
|
+
*Edit this file to add project-specific context. The AI reads NOVA.md automatically at the start of each session.*
|
|
1633
|
+
`;
|
|
1634
|
+
}
|
|
1635
|
+
// ========================================================================
|
|
1636
|
+
// /memory — manage persistent notes
|
|
1637
|
+
// ========================================================================
|
|
1638
|
+
get memoryFile() {
|
|
1639
|
+
return path.join(os.homedir(), '.nova', 'memory.md');
|
|
1640
|
+
}
|
|
1641
|
+
async handleMemoryCommand(arg) {
|
|
1642
|
+
const parts = arg.trim().split(/\s+/);
|
|
1643
|
+
const sub = parts[0];
|
|
1644
|
+
if (!sub || sub === 'show' || sub === 'list') {
|
|
1645
|
+
// Show memory
|
|
1646
|
+
if (!fs.existsSync(this.memoryFile)) {
|
|
1647
|
+
console.log(C.muted(' No memory file yet. Use /memory add <text> to create entries.'));
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
const content = fs.readFileSync(this.memoryFile, 'utf-8');
|
|
1651
|
+
console.log('');
|
|
1652
|
+
console.log(C.brand(' Nova Memory'));
|
|
1653
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
1654
|
+
content.split('\n').forEach((line) => {
|
|
1655
|
+
if (line.startsWith('## '))
|
|
1656
|
+
console.log(C.brand(' ' + line));
|
|
1657
|
+
else if (line.startsWith('- '))
|
|
1658
|
+
console.log(C.muted(' ' + line));
|
|
1659
|
+
else
|
|
1660
|
+
console.log(C.dim(' ' + line));
|
|
1661
|
+
});
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
if (sub === 'add') {
|
|
1665
|
+
const text = parts.slice(1).join(' ').trim();
|
|
1666
|
+
if (!text) {
|
|
1667
|
+
console.log(C.warning(' Usage: /memory add <text>'));
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
this.ensureDir(path.dirname(this.memoryFile));
|
|
1671
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
1672
|
+
const entry = `- [${timestamp}] ${text}\n`;
|
|
1673
|
+
if (!fs.existsSync(this.memoryFile)) {
|
|
1674
|
+
fs.writeFileSync(this.memoryFile, `# Nova Memory\n\n## Notes\n\n${entry}`, 'utf-8');
|
|
1675
|
+
}
|
|
1676
|
+
else {
|
|
1677
|
+
fs.appendFileSync(this.memoryFile, entry, 'utf-8');
|
|
1678
|
+
}
|
|
1679
|
+
console.log(C.success(` ✓ Memory saved: "${text}"`));
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
if (sub === 'clear') {
|
|
1683
|
+
if (fs.existsSync(this.memoryFile)) {
|
|
1684
|
+
fs.writeFileSync(this.memoryFile, '# Nova Memory\n\n', 'utf-8');
|
|
1685
|
+
console.log(C.warning(' Memory cleared.'));
|
|
1686
|
+
}
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
if (sub === 'edit') {
|
|
1690
|
+
const editor = process.env.EDITOR || process.env.VISUAL || (process.platform === 'win32' ? 'notepad' : 'nano');
|
|
1691
|
+
this.ensureDir(path.dirname(this.memoryFile));
|
|
1692
|
+
if (!fs.existsSync(this.memoryFile)) {
|
|
1693
|
+
fs.writeFileSync(this.memoryFile, '# Nova Memory\n\n## Notes\n\n', 'utf-8');
|
|
1694
|
+
}
|
|
1695
|
+
console.log(C.muted(` Opening ${this.memoryFile} in ${editor}...`));
|
|
1696
|
+
try {
|
|
1697
|
+
execSync(`${editor} "${this.memoryFile}"`, { stdio: 'inherit' });
|
|
1698
|
+
}
|
|
1699
|
+
catch {
|
|
1700
|
+
console.log(C.muted(` Memory file: ${this.memoryFile}`));
|
|
1701
|
+
}
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
console.log(C.muted(' Usage: /memory [show|add <text>|clear|edit]'));
|
|
1705
|
+
}
|
|
1706
|
+
// ========================================================================
|
|
1707
|
+
// /history — browse and restore previous sessions
|
|
1708
|
+
// ========================================================================
|
|
1709
|
+
async handleHistoryCommand(arg) {
|
|
1710
|
+
const parts = arg.trim().split(/\s+/);
|
|
1711
|
+
const sub = parts[0];
|
|
1712
|
+
if (!sub || sub === 'list') {
|
|
1713
|
+
const sessions = this.sessionManager.listPersistedSessions(20);
|
|
1714
|
+
if (sessions.length === 0) {
|
|
1715
|
+
console.log(C.muted(' No saved sessions.'));
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
console.log('');
|
|
1719
|
+
console.log(C.brand(' Session History'));
|
|
1720
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
1721
|
+
sessions.forEach((s, idx) => {
|
|
1722
|
+
const date = new Date(s.updatedAt).toLocaleString();
|
|
1723
|
+
const id = s.id.slice(0, 8);
|
|
1724
|
+
const turns = s.turnCount;
|
|
1725
|
+
const tokens = (s.totalInputTokens + s.totalOutputTokens).toLocaleString();
|
|
1726
|
+
const title = (s.title || 'New session').slice(0, 50);
|
|
1727
|
+
const isCurrent = this.sessionId && s.id === String(this.sessionId);
|
|
1728
|
+
const marker = isCurrent ? C.success(' ← current') : '';
|
|
1729
|
+
console.log(` ${C.muted(String(idx + 1).padStart(2) + '.')} ${C.primary(title)}${marker}\n` +
|
|
1730
|
+
` ${C.dim(id + ' ' + date + ' ' + turns + ' turns ' + tokens + ' tok')}`);
|
|
1731
|
+
});
|
|
1732
|
+
console.log('');
|
|
1733
|
+
console.log(C.dim(' /history restore <n> — switch to session n'));
|
|
1734
|
+
console.log(C.dim(' /history delete <n> — delete session n'));
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
if (sub === 'restore') {
|
|
1738
|
+
const n = parseInt(parts[1], 10);
|
|
1739
|
+
const sessions = this.sessionManager.listPersistedSessions(20);
|
|
1740
|
+
if (isNaN(n) || n < 1 || n > sessions.length) {
|
|
1741
|
+
console.log(C.warning(` Invalid index. Use /history to see sessions.`));
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
// Save current first
|
|
1745
|
+
if (this.sessionId)
|
|
1746
|
+
this.sessionManager.persist(this.sessionId);
|
|
1747
|
+
const target = sessions[n - 1];
|
|
1748
|
+
const restored = this.sessionManager.loadFromDisk(target.id);
|
|
1749
|
+
if (restored) {
|
|
1750
|
+
this.sessionId = restored.id;
|
|
1751
|
+
const msgs = this.sessionManager.getMessages(this.sessionId);
|
|
1752
|
+
console.log(C.success(` ✓ Restored session ${target.id.slice(0, 8)} — ${msgs.length} messages`));
|
|
1753
|
+
console.log(C.muted(` Title: "${target.title}"`));
|
|
1754
|
+
}
|
|
1755
|
+
else {
|
|
1756
|
+
console.log(C.error(' Failed to restore session.'));
|
|
1757
|
+
}
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
if (sub === 'delete') {
|
|
1761
|
+
const n = parseInt(parts[1], 10);
|
|
1762
|
+
const sessions = this.sessionManager.listPersistedSessions(20);
|
|
1763
|
+
if (isNaN(n) || n < 1 || n > sessions.length) {
|
|
1764
|
+
console.log(C.warning(` Invalid index.`));
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
const target = sessions[n - 1];
|
|
1768
|
+
const deleted = this.sessionManager.deletePersisted(target.id);
|
|
1769
|
+
if (deleted) {
|
|
1770
|
+
console.log(C.success(` ✓ Deleted session ${target.id.slice(0, 8)}`));
|
|
1771
|
+
}
|
|
1772
|
+
else {
|
|
1773
|
+
console.log(C.error(' Failed to delete session.'));
|
|
1774
|
+
}
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
console.log(C.muted(' Usage: /history [list|restore <n>|delete <n>]'));
|
|
1778
|
+
}
|
|
1779
|
+
// ========================================================================
|
|
1780
|
+
// /compress — manually trigger context compression
|
|
1781
|
+
// ========================================================================
|
|
1782
|
+
async handleCompressCommand() {
|
|
1783
|
+
if (!this.sessionId || !this.contextCompressor) {
|
|
1784
|
+
console.log(C.warning(' No active session or context compressor not initialized.'));
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
const msgs = this.sessionManager.getMessages(this.sessionId);
|
|
1788
|
+
const before = msgs.length;
|
|
1789
|
+
console.log(C.info(` Compressing context (${before} messages)...`));
|
|
1790
|
+
// Trigger via a lightweight session flush
|
|
1791
|
+
this.sessionManager.persist(this.sessionId);
|
|
1792
|
+
console.log(C.success(` ✓ Context snapshot saved. Session: ${String(this.sessionId).slice(0, 8)}`));
|
|
1793
|
+
}
|
|
1794
|
+
printHelp() {
|
|
1795
|
+
const w = 60;
|
|
1796
|
+
const hr = C.brandDim(BOX.h.repeat(w));
|
|
1797
|
+
const hrThick = C.brand(BOX.hThick.repeat(w));
|
|
1798
|
+
const vl = C.brandDim(BOX.v);
|
|
1799
|
+
const cmd = (name, desc) => ` ${C.info(name.padEnd(18))} ${C.muted(desc)}`;
|
|
1800
|
+
const section = (title) => `\n${vl} ${C.brandLight(BOX.diamond)} ${C.brand(title)}${' '.repeat(w - title.length - 4)}${vl}`;
|
|
1801
|
+
console.log('');
|
|
1802
|
+
console.log(C.brand(BOX.tl) + hrThick + C.brand(BOX.tr));
|
|
1803
|
+
console.log(`${vl} ${C.brand.bold('Nova CLI Commands')}${' '.repeat(w - 18)}${vl}`);
|
|
1804
|
+
console.log(C.brand(BOX.ht) + hr + C.brand(BOX.htr));
|
|
1805
|
+
// Navigation
|
|
1806
|
+
console.log(section('Navigation'));
|
|
1807
|
+
console.log(`${vl}${cmd('/help', 'Show this help')}${' '.repeat(w - 28)}${vl}`);
|
|
1808
|
+
console.log(`${vl}${cmd('/quit', 'Exit (session auto-saved)')}${' '.repeat(w - 38)}${vl}`);
|
|
1809
|
+
console.log(`${vl}${cmd('/clear', 'Clear conversation & start new')}${' '.repeat(w - 38)}${vl}`);
|
|
1810
|
+
// Session
|
|
1811
|
+
console.log(section('Session'));
|
|
1812
|
+
console.log(`${vl}${cmd('/status', 'Show session info & stats')}${' '.repeat(w - 33)}${vl}`);
|
|
1813
|
+
console.log(`${vl}${cmd('/history', 'List previous sessions')}${' '.repeat(w - 30)}${vl}`);
|
|
1814
|
+
console.log(`${vl}${cmd('/history restore', 'Switch to session n')}${' '.repeat(w - 33)}${vl}`);
|
|
1815
|
+
console.log(`${vl}${cmd('/history delete', 'Delete session n')}${' '.repeat(w - 30)}${vl}`);
|
|
1816
|
+
// Model
|
|
1817
|
+
console.log(section('Model'));
|
|
1818
|
+
console.log(`${vl}${cmd('/model', 'Show current model')}${' '.repeat(w - 26)}${vl}`);
|
|
1819
|
+
console.log(`${vl}${cmd('/model <id>', 'Switch model')}${' '.repeat(w - 26)}${vl}`);
|
|
1820
|
+
// Mode
|
|
1821
|
+
const currentMode = MODE_LABELS[this.mode].label;
|
|
1822
|
+
console.log(section(`Mode ${C.dim('(current: ' + currentMode + ')')}`));
|
|
1823
|
+
console.log(`${vl} ${C.info('/mode'.padEnd(18))} ${C.muted('Cycle:')} ${C.success('AUTO')} ${C.dim('→')} ${C.warning('PLAN')} ${C.dim('→')} ${C.info('ASK')}${' '.repeat(w - 46)}${vl}`);
|
|
1824
|
+
console.log(`${vl} ${C.info('/mode auto'.padEnd(18))} ${C.success('AUTO')} ${C.dim('— full autonomous, no approval')}${' '.repeat(w - 49)}${vl}`);
|
|
1825
|
+
console.log(`${vl} ${C.info('/mode plan'.padEnd(18))} ${C.warning('PLAN')} ${C.dim('— confirm before each tool')}${' '.repeat(w - 47)}${vl}`);
|
|
1826
|
+
console.log(`${vl} ${C.info('/mode ask'.padEnd(18))} ${C.info('ASK')} ${C.dim('— read-only, answer only')}${' '.repeat(w - 45)}${vl}`);
|
|
1827
|
+
// Memory
|
|
1828
|
+
console.log(section('Memory'));
|
|
1829
|
+
console.log(`${vl}${cmd('/init', 'Generate NOVA.md project file')}${' '.repeat(w - 36)}${vl}`);
|
|
1830
|
+
console.log(`${vl}${cmd('/memory', 'Show persistent notes')}${' '.repeat(w - 29)}${vl}`);
|
|
1831
|
+
console.log(`${vl}${cmd('/memory add', 'Add a note')}${' '.repeat(w - 22)}${vl}`);
|
|
1832
|
+
// Extensions
|
|
1833
|
+
console.log(section('Extensions'));
|
|
1834
|
+
console.log(`${vl}${cmd('/mcp', 'MCP servers & tools')}${' '.repeat(w - 27)}${vl}`);
|
|
1835
|
+
console.log(`${vl}${cmd('/skills', 'Available skills')}${' '.repeat(w - 26)}${vl}`);
|
|
1836
|
+
console.log(`${vl}${cmd('/skills use', 'Inject skill into next msg')}${' '.repeat(w - 36)}${vl}`);
|
|
1837
|
+
console.log(`${vl}${cmd('/theme', 'Switch color theme')}${' '.repeat(w - 26)}${vl}`);
|
|
1838
|
+
console.log(`${vl}${cmd('/checkpoint', 'File snapshots & rollback')}${' '.repeat(w - 35)}${vl}`);
|
|
1839
|
+
console.log(`${vl}${cmd('/image', 'Add image to chat')}${' '.repeat(w - 26)}${vl}`);
|
|
1840
|
+
// Ollama
|
|
1841
|
+
console.log(section('Ollama (Local Models)'));
|
|
1842
|
+
console.log(`${vl}${cmd('/ollama', 'Show status & models')}${' '.repeat(w - 29)}${vl}`);
|
|
1843
|
+
console.log(`${vl}${cmd('/ollama pull <n>', 'Download a model')}${' '.repeat(w - 32)}${vl}`);
|
|
1844
|
+
console.log(`${vl}${cmd('/ollama list', 'List installed models')}${' '.repeat(w - 33)}${vl}`);
|
|
1845
|
+
// Shortcuts
|
|
1846
|
+
console.log(section('Shortcuts'));
|
|
1847
|
+
console.log(`${vl} ${C.info('@file.ts'.padEnd(18))} ${C.muted('Inject file content')}${' '.repeat(w - 35)}${vl}`);
|
|
1848
|
+
console.log(`${vl} ${C.info('!command'.padEnd(18))} ${C.muted('Run shell command')}${' '.repeat(w - 33)}${vl}`);
|
|
1849
|
+
console.log(`${vl} ${C.info('line\\'.padEnd(18))} ${C.muted('Multi-line input')}${' '.repeat(w - 32)}${vl}`);
|
|
1850
|
+
console.log(C.brand(BOX.bl) + hrThick + C.brand(BOX.br));
|
|
1851
|
+
console.log('');
|
|
1852
|
+
}
|
|
1853
|
+
// ========================================================================
|
|
1854
|
+
// MCP command handler
|
|
1855
|
+
// ========================================================================
|
|
1856
|
+
async handleMcpCommand(subcommand) {
|
|
1857
|
+
if (!this.mcpManager) {
|
|
1858
|
+
console.log(C.warning(' No MCP manager initialized.'));
|
|
1859
|
+
console.log(C.muted(' Add MCP servers to your config (~/.nova/config.yaml):'));
|
|
1860
|
+
console.log('');
|
|
1861
|
+
console.log(C.dim(' mcp:'));
|
|
1862
|
+
console.log(C.dim(' filesystem:'));
|
|
1863
|
+
console.log(C.dim(' command: npx'));
|
|
1864
|
+
console.log(C.dim(' args: [-y, "@modelcontextprotocol/server-filesystem", /path/to/dir]'));
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
const statuses = this.mcpManager.listServers();
|
|
1868
|
+
if (statuses.length === 0) {
|
|
1869
|
+
console.log(C.muted(' No MCP servers configured.'));
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
if (!subcommand || subcommand === 'status') {
|
|
1873
|
+
console.log('');
|
|
1874
|
+
console.log(C.brand(' MCP Servers'));
|
|
1875
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
1876
|
+
for (const s of statuses) {
|
|
1877
|
+
const statusIcon = s.connected ? C.success(BOX.check) : C.error(BOX.cross);
|
|
1878
|
+
const statusStr = s.connected
|
|
1879
|
+
? C.success('connected')
|
|
1880
|
+
: C.error(`disconnected${s.lastError ? ': ' + s.lastError.slice(0, 40) : ''}`);
|
|
1881
|
+
console.log(` ${statusIcon} ${C.primary(s.name.padEnd(20))} ${statusStr}`);
|
|
1882
|
+
if (s.connected) {
|
|
1883
|
+
console.log(C.dim(` ${s.toolCount} tool${s.toolCount !== 1 ? 's' : ''}`) +
|
|
1884
|
+
(s.resourceCount > 0 ? C.dim(`, ${s.resourceCount} resource${s.resourceCount !== 1 ? 's' : ''}`) : ''));
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
const connected = statuses.filter((s) => s.connected).length;
|
|
1888
|
+
console.log('');
|
|
1889
|
+
console.log(C.muted(` ${connected}/${statuses.length} servers connected`));
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
if (subcommand === 'tools') {
|
|
1893
|
+
const allTools = this.toolRegistry.getEnabledToolNames().filter((n) => n.includes('__'));
|
|
1894
|
+
if (allTools.length === 0) {
|
|
1895
|
+
console.log(C.muted(' No MCP tools available.'));
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
console.log('');
|
|
1899
|
+
console.log(C.brand(' MCP Tools'));
|
|
1900
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
1901
|
+
for (const t of allTools) {
|
|
1902
|
+
const [ns, toolName] = t.split('__');
|
|
1903
|
+
console.log(` ${C.info(ns.padEnd(16))} ${C.primary(toolName)}`);
|
|
1904
|
+
}
|
|
1905
|
+
console.log('');
|
|
1906
|
+
console.log(C.muted(` ${allTools.length} MCP tool${allTools.length !== 1 ? 's' : ''} available`));
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
console.log(C.warning(` Unknown MCP subcommand: ${subcommand}`));
|
|
1910
|
+
console.log(C.muted(' Usage: /mcp [status|tools]'));
|
|
1911
|
+
}
|
|
1912
|
+
// ========================================================================
|
|
1913
|
+
// Skills command handler
|
|
1914
|
+
// ========================================================================
|
|
1915
|
+
async handleSkillsCommand(subcommand) {
|
|
1916
|
+
if (!this.skillRegistry) {
|
|
1917
|
+
console.log(C.warning(' Skills system not initialized.'));
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
const parts = (subcommand || '').split(/\s+/).filter(Boolean);
|
|
1921
|
+
const cmd = parts[0];
|
|
1922
|
+
const skillName = parts.slice(1).join(' ');
|
|
1923
|
+
if (!cmd || cmd === 'list') {
|
|
1924
|
+
const skills = await this.skillRegistry.list();
|
|
1925
|
+
if (skills.length === 0) {
|
|
1926
|
+
console.log(C.muted(' No skills found.'));
|
|
1927
|
+
console.log(C.muted(' Add SKILL.md files to ~/.nova/skills/ to create skills.'));
|
|
1928
|
+
console.log(C.dim(' /skills install superpowers — install popular skills'));
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
console.log('');
|
|
1932
|
+
console.log(C.brand(' Available Skills'));
|
|
1933
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
1934
|
+
for (const skill of skills) {
|
|
1935
|
+
const m = skill.metadata;
|
|
1936
|
+
const autoTag = m.autoGenerated ? C.dim(' [auto]') : '';
|
|
1937
|
+
const tags = m.tags.length > 0 ? C.dim(` (${m.tags.slice(0, 3).join(', ')})`) : '';
|
|
1938
|
+
console.log(` ${C.toolName(m.name.padEnd(22))} ${C.muted(m.description.slice(0, 35))}${autoTag}${tags}`);
|
|
1939
|
+
}
|
|
1940
|
+
console.log('');
|
|
1941
|
+
console.log(C.muted(` ${skills.length} skill${skills.length !== 1 ? 's' : ''} available`));
|
|
1942
|
+
console.log(C.dim(' /skills use <name> — inject skill into next message'));
|
|
1943
|
+
console.log(C.dim(' /skills info <name> — show skill details'));
|
|
1944
|
+
console.log(C.dim(' /skills install <repo> — install from GitHub'));
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
// Install skills from GitHub
|
|
1948
|
+
if (cmd === 'install') {
|
|
1949
|
+
await this.handleSkillsInstall(skillName);
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
if (cmd === 'info' && skillName) {
|
|
1953
|
+
const skill = await this.skillRegistry.get(skillName);
|
|
1954
|
+
if (!skill) {
|
|
1955
|
+
console.log(C.error(` Skill "${skillName}" not found.`));
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
const m = skill.metadata;
|
|
1959
|
+
console.log('');
|
|
1960
|
+
console.log(C.brand(' ' + m.name));
|
|
1961
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
1962
|
+
console.log(C.muted(' Description: ') + C.primary(m.description));
|
|
1963
|
+
console.log(C.muted(' Version: ') + C.primary(m.version));
|
|
1964
|
+
if (m.author)
|
|
1965
|
+
console.log(C.muted(' Author: ') + C.primary(m.author));
|
|
1966
|
+
if (m.tags.length > 0)
|
|
1967
|
+
console.log(C.muted(' Tags: ') + C.primary(m.tags.join(', ')));
|
|
1968
|
+
console.log('');
|
|
1969
|
+
const preview = skill.content.split('\n').slice(0, 10).join('\n');
|
|
1970
|
+
console.log(C.dim(preview));
|
|
1971
|
+
if (skill.content.split('\n').length > 10)
|
|
1972
|
+
console.log(C.dim(' ...'));
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
if (cmd === 'use' && skillName) {
|
|
1976
|
+
const skill = await this.skillRegistry.get(skillName);
|
|
1977
|
+
if (!skill) {
|
|
1978
|
+
console.log(C.error(` Skill "${skillName}" not found.`));
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
this._pendingSkillInject = skill;
|
|
1982
|
+
console.log(C.success(` Skill "${skillName}" will be injected into your next message.`));
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
console.log(C.warning(` Unknown skills subcommand.`));
|
|
1986
|
+
console.log(C.muted(' Usage: /skills [list|use <name>|info <name>|install <repo>]'));
|
|
1987
|
+
}
|
|
1988
|
+
// ========================================================================
|
|
1989
|
+
// /skills install — Install skills from GitHub
|
|
1990
|
+
// ========================================================================
|
|
1991
|
+
async handleSkillsInstall(repoArg) {
|
|
1992
|
+
if (!repoArg) {
|
|
1993
|
+
console.log('');
|
|
1994
|
+
console.log(C.brand(' Install Skills from GitHub'));
|
|
1995
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
1996
|
+
console.log(C.muted(' Install skills from GitHub repositories.'));
|
|
1997
|
+
console.log('');
|
|
1998
|
+
console.log(C.info(' Popular repositories:'));
|
|
1999
|
+
console.log(C.dim(' • superpowers — Agentic skills (TDD, debugging, review)'));
|
|
2000
|
+
console.log(C.dim(' • owner/repo — Any GitHub repository'));
|
|
2001
|
+
console.log('');
|
|
2002
|
+
console.log(C.dim(' Usage:'));
|
|
2003
|
+
console.log(C.primary(' /skills install superpowers'));
|
|
2004
|
+
console.log(C.primary(' /skills install obra/superpowers'));
|
|
2005
|
+
console.log(C.primary(' /skills install https://github.com/owner/repo'));
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
// Import installer
|
|
2009
|
+
const { SkillInstaller, POPULAR_SKILL_REPOS } = await import('../../../core/src/extensions/SkillInstaller.js');
|
|
2010
|
+
const installer = new SkillInstaller();
|
|
2011
|
+
// Resolve shorthand
|
|
2012
|
+
const source = POPULAR_SKILL_REPOS[repoArg]?.url || repoArg;
|
|
2013
|
+
console.log(C.muted(` Installing from: ${source}`));
|
|
2014
|
+
console.log('');
|
|
2015
|
+
try {
|
|
2016
|
+
const installed = await installer.install({ source, force: false });
|
|
2017
|
+
if (installed.length === 0) {
|
|
2018
|
+
console.log(C.warning(' No new skills installed.'));
|
|
2019
|
+
console.log(C.dim(' Use --force to overwrite existing skills.'));
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
console.log('');
|
|
2023
|
+
console.log(C.success(` ✓ Installed ${installed.length} skill${installed.length !== 1 ? 's' : ''}:`));
|
|
2024
|
+
for (const skill of installed) {
|
|
2025
|
+
console.log(C.primary(` • ${skill.name}`));
|
|
2026
|
+
}
|
|
2027
|
+
console.log('');
|
|
2028
|
+
console.log(C.dim(' Reload skills with: /skills list'));
|
|
2029
|
+
console.log(C.dim(' Use a skill with: /skills use <name>'));
|
|
2030
|
+
// Reinitialize skill registry
|
|
2031
|
+
if (this.skillRegistry) {
|
|
2032
|
+
await this.skillRegistry.initialize();
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
catch (err) {
|
|
2036
|
+
console.log(C.error(` Failed to install: ${err.message}`));
|
|
2037
|
+
console.log(C.dim(' Make sure git is installed and you have internet access.'));
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
// ========================================================================
|
|
2041
|
+
// /theme — Switch color theme
|
|
2042
|
+
// ========================================================================
|
|
2043
|
+
async handleThemeCommand(arg) {
|
|
2044
|
+
const themes = {
|
|
2045
|
+
dark: {
|
|
2046
|
+
brand: '#7C3AED',
|
|
2047
|
+
brandLight: '#A78BFA',
|
|
2048
|
+
success: '#10B981',
|
|
2049
|
+
warning: '#F59E0B',
|
|
2050
|
+
error: '#EF4444',
|
|
2051
|
+
info: '#3B82F6',
|
|
2052
|
+
accent: '#F472B6',
|
|
2053
|
+
},
|
|
2054
|
+
light: {
|
|
2055
|
+
brand: '#6366F1',
|
|
2056
|
+
brandLight: '#818CF8',
|
|
2057
|
+
success: '#16A34A',
|
|
2058
|
+
warning: '#D97706',
|
|
2059
|
+
error: '#DC2626',
|
|
2060
|
+
info: '#2563EB',
|
|
2061
|
+
accent: '#EC4899',
|
|
2062
|
+
},
|
|
2063
|
+
neon: {
|
|
2064
|
+
brand: '#FF00FF',
|
|
2065
|
+
brandLight: '#FF66FF',
|
|
2066
|
+
success: '#00FF00',
|
|
2067
|
+
warning: '#FFFF00',
|
|
2068
|
+
error: '#FF0000',
|
|
2069
|
+
info: '#00FFFF',
|
|
2070
|
+
accent: '#FF00AA',
|
|
2071
|
+
},
|
|
2072
|
+
ocean: {
|
|
2073
|
+
brand: '#0891B2',
|
|
2074
|
+
brandLight: '#06B6D4',
|
|
2075
|
+
success: '#10B981',
|
|
2076
|
+
warning: '#F59E0B',
|
|
2077
|
+
error: '#EF4444',
|
|
2078
|
+
info: '#0EA5E9',
|
|
2079
|
+
accent: '#8B5CF6',
|
|
2080
|
+
},
|
|
2081
|
+
};
|
|
2082
|
+
if (!arg) {
|
|
2083
|
+
// Show current theme and available themes
|
|
2084
|
+
console.log('');
|
|
2085
|
+
console.log(C.brand(' Available Themes'));
|
|
2086
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
2087
|
+
Object.keys(themes).forEach((name) => {
|
|
2088
|
+
const isCurrent = name === 'dark'; // Default theme
|
|
2089
|
+
const marker = isCurrent ? C.success('●') : C.dim('○');
|
|
2090
|
+
console.log(` ${marker} ${C.primary(name.padEnd(12))} ${C.dim(name === 'dark' ? '(default)' : '')}`);
|
|
2091
|
+
});
|
|
2092
|
+
console.log('');
|
|
2093
|
+
console.log(C.dim(' /theme <name> — switch theme'));
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
const themeName = arg.toLowerCase();
|
|
2097
|
+
if (!themes[themeName]) {
|
|
2098
|
+
console.log(C.error(` Unknown theme: ${arg}`));
|
|
2099
|
+
console.log(C.muted(` Available: ${Object.keys(themes).join(', ')}`));
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
// Note: In a real implementation, we would:
|
|
2103
|
+
// 1. Save theme preference to ~/.nova/theme.json
|
|
2104
|
+
// 2. Update the C color object dynamically
|
|
2105
|
+
// 3. Redraw the UI with new colors
|
|
2106
|
+
console.log(C.success(` ✓ Theme switched to: ${themeName}`));
|
|
2107
|
+
console.log(C.muted(' Note: Theme will be fully applied after restart'));
|
|
2108
|
+
}
|
|
2109
|
+
// ========================================================================
|
|
2110
|
+
// /image — Add image to conversation
|
|
2111
|
+
// ========================================================================
|
|
2112
|
+
async handleImageCommand(arg) {
|
|
2113
|
+
if (!arg) {
|
|
2114
|
+
console.log(C.error(' Usage: /image <path-or-url> [description]'));
|
|
2115
|
+
console.log(C.muted(' Example: /image ./screenshot.png "Error message"'));
|
|
2116
|
+
console.log(C.muted(' Example: /image https://example.com/chart.png'));
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
const parts = arg.split(/\s+/);
|
|
2120
|
+
const imagePath = parts[0];
|
|
2121
|
+
const description = parts.slice(1).join(' ');
|
|
2122
|
+
try {
|
|
2123
|
+
let imageData;
|
|
2124
|
+
let mediaType;
|
|
2125
|
+
// Handle URL
|
|
2126
|
+
if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
|
|
2127
|
+
console.log(C.muted(` Fetching image from URL...`));
|
|
2128
|
+
// URL image support - note: this requires additional implementation
|
|
2129
|
+
// For now, we'll just acknowledge the URL
|
|
2130
|
+
console.log(C.warning(' URL image support is limited in this version'));
|
|
2131
|
+
console.log(C.muted(` Please download the image and use local path instead`));
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
// Handle local file
|
|
2135
|
+
else {
|
|
2136
|
+
const fullPath = require('path').resolve(this.cwd, imagePath);
|
|
2137
|
+
console.log(C.muted(` Reading image: ${fullPath}`));
|
|
2138
|
+
const fs = require('node:fs');
|
|
2139
|
+
if (!fs.existsSync(fullPath)) {
|
|
2140
|
+
console.log(C.error(` File not found: ${imagePath}`));
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
// Read and encode image
|
|
2144
|
+
const imageBuffer = fs.readFileSync(fullPath);
|
|
2145
|
+
imageData = imageBuffer.toString('base64');
|
|
2146
|
+
// Determine media type from extension
|
|
2147
|
+
const ext = require('path').extname(fullPath).toLowerCase();
|
|
2148
|
+
const mimeTypes = {
|
|
2149
|
+
'.png': 'image/png',
|
|
2150
|
+
'.jpg': 'image/jpeg',
|
|
2151
|
+
'.jpeg': 'image/jpeg',
|
|
2152
|
+
'.gif': 'image/gif',
|
|
2153
|
+
'.webp': 'image/webp',
|
|
2154
|
+
'.svg': 'image/svg+xml',
|
|
2155
|
+
'.bmp': 'image/bmp',
|
|
2156
|
+
};
|
|
2157
|
+
mediaType = mimeTypes[ext] || 'image/jpeg';
|
|
2158
|
+
}
|
|
2159
|
+
// Add image to session
|
|
2160
|
+
if (!this.sessionId) {
|
|
2161
|
+
console.log(C.error(' No active session'));
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
// Create image content block
|
|
2165
|
+
const imageContent = {
|
|
2166
|
+
type: 'image',
|
|
2167
|
+
source: {
|
|
2168
|
+
type: 'base64',
|
|
2169
|
+
media_type: mediaType,
|
|
2170
|
+
data: imageData,
|
|
2171
|
+
},
|
|
2172
|
+
};
|
|
2173
|
+
// Add to session messages
|
|
2174
|
+
this.sessionManager.addMessage(this.sessionId, 'user', [
|
|
2175
|
+
{ type: 'text', text: description || `Image: ${imagePath}` },
|
|
2176
|
+
imageContent,
|
|
2177
|
+
]);
|
|
2178
|
+
console.log(C.success(` ✓ Image added to conversation`));
|
|
2179
|
+
console.log(C.muted(` Path: ${imagePath}`));
|
|
2180
|
+
console.log(C.muted(` Size: ${(imageData.length / 1024).toFixed(1)} KB`));
|
|
2181
|
+
console.log(C.muted(` Type: ${mediaType}`));
|
|
2182
|
+
}
|
|
2183
|
+
catch (err) {
|
|
2184
|
+
console.log(C.error(` Failed to add image: ${err.message}`));
|
|
2185
|
+
console.log(C.muted(` Make sure the file is a valid image (PNG, JPG, GIF, etc.)`));
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
// ========================================================================
|
|
2189
|
+
// /checkpoint — File snapshot and rollback management
|
|
2190
|
+
// ========================================================================
|
|
2191
|
+
async handleCheckpointCommand(arg) {
|
|
2192
|
+
const { CheckpointManager } = await import('../../../core/src/utils/CheckpointManager.js');
|
|
2193
|
+
const manager = new CheckpointManager(this.cwd, this.config);
|
|
2194
|
+
const parts = arg.split(/\s+/).filter(Boolean);
|
|
2195
|
+
const cmd = parts[0];
|
|
2196
|
+
const subArg = parts.slice(1).join(' ');
|
|
2197
|
+
if (!cmd || cmd === 'list') {
|
|
2198
|
+
const checkpoints = await manager.list();
|
|
2199
|
+
if (checkpoints.length === 0) {
|
|
2200
|
+
console.log(C.muted(' No checkpoints found.'));
|
|
2201
|
+
console.log(C.muted(' Create one with: /checkpoint create <name> [files...]'));
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
console.log('');
|
|
2205
|
+
console.log(C.brand(' Checkpoints'));
|
|
2206
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
2207
|
+
for (const cp of checkpoints.slice(0, 10)) {
|
|
2208
|
+
const date = new Date(cp.timestamp).toLocaleString();
|
|
2209
|
+
const fileCount = cp.files.length;
|
|
2210
|
+
const idShort = cp.id.slice(0, 8);
|
|
2211
|
+
console.log(` ${C.info(idShort)} ${C.primary(cp.name.padEnd(20))} ${C.dim(date)} ${C.muted(`(${fileCount} files)`)}`);
|
|
2212
|
+
}
|
|
2213
|
+
if (checkpoints.length > 10) {
|
|
2214
|
+
console.log(C.dim(` ... and ${checkpoints.length - 10} more`));
|
|
2215
|
+
}
|
|
2216
|
+
console.log('');
|
|
2217
|
+
console.log(C.dim(' /checkpoint create <name> [pattern] — create snapshot'));
|
|
2218
|
+
console.log(C.dim(' /checkpoint restore <id> — restore snapshot'));
|
|
2219
|
+
console.log(C.dim(' /checkpoint diff <id> — show differences'));
|
|
2220
|
+
console.log(C.dim(' /checkpoint delete <id> — delete snapshot'));
|
|
2221
|
+
console.log(C.dim(' /checkpoint stats — show statistics'));
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
if (cmd === 'create') {
|
|
2225
|
+
if (!subArg) {
|
|
2226
|
+
console.log(C.error(' Usage: /checkpoint create <name> [file-pattern]'));
|
|
2227
|
+
console.log(C.muted(' Example: /checkpoint create "before-refactor" "src/**/*.ts"'));
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
const nameMatch = subArg.match(/^"([^"]+)"(?:\s+(.+))?$/);
|
|
2231
|
+
if (!nameMatch) {
|
|
2232
|
+
console.log(C.error(' Invalid format. Use: /checkpoint create "name" [pattern]'));
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
const name = nameMatch[1];
|
|
2236
|
+
const pattern = nameMatch[2] || '**/*';
|
|
2237
|
+
console.log(C.muted(` Creating checkpoint "${name}"...`));
|
|
2238
|
+
try {
|
|
2239
|
+
const checkpoint = await manager.create(name, [pattern], `Created via CLI`);
|
|
2240
|
+
console.log(C.success(` ✓ Checkpoint created: ${checkpoint.id.slice(0, 8)}`));
|
|
2241
|
+
console.log(C.muted(` Files: ${checkpoint.files.length}`));
|
|
2242
|
+
}
|
|
2243
|
+
catch (err) {
|
|
2244
|
+
console.log(C.error(` Failed to create checkpoint: ${err}`));
|
|
2245
|
+
}
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
if (cmd === 'restore') {
|
|
2249
|
+
if (!subArg) {
|
|
2250
|
+
console.log(C.error(' Usage: /checkpoint restore <id>'));
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
const checkpointId = subArg;
|
|
2254
|
+
const checkpoint = await manager.load(checkpointId);
|
|
2255
|
+
if (!checkpoint) {
|
|
2256
|
+
console.log(C.error(` Checkpoint not found: ${checkpointId}`));
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
console.log(C.warning(` ⚠ This will overwrite current files with checkpoint version`));
|
|
2260
|
+
console.log(C.muted(` Checkpoint: ${checkpoint.name}`));
|
|
2261
|
+
console.log(C.muted(` Files: ${checkpoint.files.length}`));
|
|
2262
|
+
console.log(C.muted(` Created: ${new Date(checkpoint.timestamp).toLocaleString()}`));
|
|
2263
|
+
// In a real implementation, we'd use ConfirmDialog here
|
|
2264
|
+
const { ConfirmDialog } = await import('../ui/components/ConfirmDialog.js');
|
|
2265
|
+
const dialog = new ConfirmDialog();
|
|
2266
|
+
const confirmed = await dialog.danger('Restore this checkpoint?');
|
|
2267
|
+
if (!confirmed) {
|
|
2268
|
+
console.log(C.muted(' Restore cancelled.'));
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
try {
|
|
2272
|
+
await manager.restore(checkpointId);
|
|
2273
|
+
console.log(C.success(` ✓ Checkpoint restored successfully`));
|
|
2274
|
+
}
|
|
2275
|
+
catch (err) {
|
|
2276
|
+
console.log(C.error(` Failed to restore checkpoint: ${err}`));
|
|
2277
|
+
}
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
if (cmd === 'diff') {
|
|
2281
|
+
if (!subArg) {
|
|
2282
|
+
console.log(C.error(' Usage: /checkpoint diff <id>'));
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
try {
|
|
2286
|
+
const differences = await manager.diff(subArg);
|
|
2287
|
+
if (differences.length === 0) {
|
|
2288
|
+
console.log(C.muted(' No differences from checkpoint.'));
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
console.log('');
|
|
2292
|
+
console.log(C.brand(' Differences'));
|
|
2293
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
2294
|
+
for (const diff of differences) {
|
|
2295
|
+
const icon = diff.status === 'modified' ? '◉' : diff.status === 'deleted' ? '✗' : '✚';
|
|
2296
|
+
const color = diff.status === 'modified' ? chalk.yellow : diff.status === 'deleted' ? chalk.red : chalk.green;
|
|
2297
|
+
console.log(` ${color(icon)} ${diff.path} ${chalk.dim(`(${diff.status})`)}`);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
catch (err) {
|
|
2301
|
+
console.log(C.error(` Failed to show diff: ${err}`));
|
|
2302
|
+
}
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
if (cmd === 'delete') {
|
|
2306
|
+
if (!subArg) {
|
|
2307
|
+
console.log(C.error(' Usage: /checkpoint delete <id>'));
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
2310
|
+
const checkpoint = await manager.load(subArg);
|
|
2311
|
+
if (!checkpoint) {
|
|
2312
|
+
console.log(C.error(` Checkpoint not found: ${subArg}`));
|
|
2313
|
+
return;
|
|
2314
|
+
}
|
|
2315
|
+
console.log(C.warning(` ⚠ Delete checkpoint "${checkpoint.name}"?`));
|
|
2316
|
+
const { ConfirmDialog } = await import('../ui/components/ConfirmDialog.js');
|
|
2317
|
+
const dialog = new ConfirmDialog();
|
|
2318
|
+
const confirmed = await dialog.warning('This cannot be undone');
|
|
2319
|
+
if (!confirmed) {
|
|
2320
|
+
console.log(C.muted(' Delete cancelled.'));
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
const success = await manager.delete(subArg);
|
|
2324
|
+
if (success) {
|
|
2325
|
+
console.log(C.success(` ✓ Checkpoint deleted`));
|
|
2326
|
+
}
|
|
2327
|
+
else {
|
|
2328
|
+
console.log(C.error(` Failed to delete checkpoint`));
|
|
2329
|
+
}
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
if (cmd === 'stats') {
|
|
2333
|
+
const stats = await manager.stats();
|
|
2334
|
+
console.log('');
|
|
2335
|
+
console.log(C.brand(' Checkpoint Statistics'));
|
|
2336
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
2337
|
+
console.log(`${C.muted(' Total:')} ${C.primary(stats.totalCheckpoints)}`);
|
|
2338
|
+
console.log(`${C.muted(' Size:')} ${C.primary(this.formatBytes(stats.totalSize))}`);
|
|
2339
|
+
if (stats.oldest) {
|
|
2340
|
+
console.log(`${C.muted(' Oldest:')} ${C.dim(new Date(stats.oldest).toLocaleDateString())}`);
|
|
2341
|
+
}
|
|
2342
|
+
if (stats.newest) {
|
|
2343
|
+
console.log(`${C.muted(' Newest:')} ${C.dim(new Date(stats.newest).toLocaleDateString())}`);
|
|
2344
|
+
}
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
console.log(C.error(` Unknown checkpoint command: ${cmd}`));
|
|
2348
|
+
console.log(C.muted(' Usage: /checkpoint [list|create|restore|diff|delete|stats]'));
|
|
2349
|
+
}
|
|
2350
|
+
formatBytes(bytes) {
|
|
2351
|
+
if (bytes === 0)
|
|
2352
|
+
return '0 B';
|
|
2353
|
+
const k = 1024;
|
|
2354
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
2355
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
2356
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
2357
|
+
}
|
|
2358
|
+
// ========================================================================
|
|
2359
|
+
// /ollama — Ollama status and model management
|
|
2360
|
+
// ========================================================================
|
|
2361
|
+
async handleOllamaCommand(subcommand) {
|
|
2362
|
+
const ollamaCreds = this.authManager?.getCredentials('ollama');
|
|
2363
|
+
const baseUrl = ollamaCreds?.baseUrl || process.env.OLLAMA_HOST || 'http://localhost:11434';
|
|
2364
|
+
const manager = new OllamaManager(baseUrl);
|
|
2365
|
+
const parts = (subcommand || '').split(/\s+/).filter(Boolean);
|
|
2366
|
+
const cmd = parts[0];
|
|
2367
|
+
const arg = parts.slice(1).join(' ');
|
|
2368
|
+
if (!cmd || cmd === 'status') {
|
|
2369
|
+
console.log('');
|
|
2370
|
+
console.log(C.brand(' Ollama Status'));
|
|
2371
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
2372
|
+
const isRunning = await manager.ping();
|
|
2373
|
+
if (isRunning) {
|
|
2374
|
+
try {
|
|
2375
|
+
const version = await manager.version();
|
|
2376
|
+
console.log(C.success(` ${BOX.check} Running`) + C.dim(` (v${version})`));
|
|
2377
|
+
}
|
|
2378
|
+
catch {
|
|
2379
|
+
console.log(C.success(` ${BOX.check} Running`));
|
|
2380
|
+
}
|
|
2381
|
+
console.log(C.muted(` Host: ${baseUrl}`));
|
|
2382
|
+
const models = await manager.listModels();
|
|
2383
|
+
if (models.length > 0) {
|
|
2384
|
+
console.log(C.muted(` Models: ${models.length} installed`));
|
|
2385
|
+
for (const m of models.slice(0, 5)) {
|
|
2386
|
+
const sizeGB = (m.size / 1024 / 1024 / 1024).toFixed(1);
|
|
2387
|
+
console.log(C.dim(` ${BOX.bullet} ${m.name} (${sizeGB} GB)`));
|
|
2388
|
+
}
|
|
2389
|
+
if (models.length > 5) {
|
|
2390
|
+
console.log(C.dim(` ... and ${models.length - 5} more`));
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
else {
|
|
2394
|
+
console.log(C.warning(` No models installed`));
|
|
2395
|
+
console.log(C.dim(' Use /ollama pull <model> to download a model'));
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
else {
|
|
2399
|
+
console.log(C.error(` ${BOX.crossX} Not running`));
|
|
2400
|
+
console.log(C.muted(` Host: ${baseUrl}`));
|
|
2401
|
+
console.log('');
|
|
2402
|
+
console.log(C.warning(' Start Ollama:'));
|
|
2403
|
+
console.log(C.dim(' ollama serve'));
|
|
2404
|
+
console.log('');
|
|
2405
|
+
console.log(C.warning(' Install Ollama:'));
|
|
2406
|
+
console.log(C.dim(' https://ollama.com'));
|
|
2407
|
+
}
|
|
2408
|
+
console.log('');
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
if (cmd === 'list') {
|
|
2412
|
+
if (!(await manager.ping())) {
|
|
2413
|
+
console.log(C.error(` Ollama is not running at ${baseUrl}`));
|
|
2414
|
+
console.log(C.dim(' Start Ollama first: ollama serve'));
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
const models = await manager.listModels();
|
|
2418
|
+
if (models.length === 0) {
|
|
2419
|
+
console.log(C.muted(' No models installed.'));
|
|
2420
|
+
console.log(C.dim(' Pull one with: /ollama pull <model-name>'));
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
console.log('');
|
|
2424
|
+
console.log(C.brand(' Installed Ollama Models'));
|
|
2425
|
+
console.log(C.dim(' ' + BOX.h.repeat(58)));
|
|
2426
|
+
for (const m of models) {
|
|
2427
|
+
const sizeGB = (m.size / 1024 / 1024 / 1024).toFixed(1);
|
|
2428
|
+
const family = m.details?.family || 'unknown';
|
|
2429
|
+
const params = m.details?.parameter_size || '';
|
|
2430
|
+
console.log(` ${C.toolName(m.name)}`);
|
|
2431
|
+
console.log(C.dim(` ${family} ${params} ${sizeGB} GB`));
|
|
2432
|
+
}
|
|
2433
|
+
console.log('');
|
|
2434
|
+
console.log(C.muted(` ${models.length} model(s)`));
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
if (cmd === 'pull' && arg) {
|
|
2438
|
+
if (!(await manager.ping())) {
|
|
2439
|
+
console.log(C.error(' Ollama is not running.'));
|
|
2440
|
+
console.log(C.dim(' Start Ollama first: ollama serve'));
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
console.log(C.info(` Pulling model: ${arg}`));
|
|
2444
|
+
console.log(C.dim(' This may take a while...'));
|
|
2445
|
+
try {
|
|
2446
|
+
await manager.pullModel(arg, (status) => {
|
|
2447
|
+
process.stdout.write(`\r ${C.muted(status)} `);
|
|
2448
|
+
});
|
|
2449
|
+
console.log('');
|
|
2450
|
+
console.log(C.success(` ${BOX.check} Model "${arg}" pulled successfully`));
|
|
2451
|
+
console.log(C.dim(` Use: /model ${arg}`));
|
|
2452
|
+
}
|
|
2453
|
+
catch (err) {
|
|
2454
|
+
console.log('');
|
|
2455
|
+
console.log(C.error(` Failed to pull model: ${err.message}`));
|
|
2456
|
+
}
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
if (cmd === 'run' && arg) {
|
|
2460
|
+
console.log(C.info(` Running model: ${arg}`));
|
|
2461
|
+
console.log(C.dim(' Note: Use "ollama run" in terminal for interactive session'));
|
|
2462
|
+
console.log(C.dim(' Switching model for Nova CLI...'));
|
|
2463
|
+
try {
|
|
2464
|
+
// Just switch to the model instead of running interactively
|
|
2465
|
+
this.modelClient.updateOptions({ model: arg });
|
|
2466
|
+
console.log(C.success(` ✓ Switched to Ollama model: ${arg}`));
|
|
2467
|
+
// Save to config
|
|
2468
|
+
const config = this.configManager.getConfig();
|
|
2469
|
+
config.core.defaultModel = arg;
|
|
2470
|
+
await this.configManager.save(config);
|
|
2471
|
+
}
|
|
2472
|
+
catch (err) {
|
|
2473
|
+
console.log(C.error(` Failed to switch model: ${err.message}`));
|
|
2474
|
+
}
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
// Unknown subcommand - show help
|
|
2478
|
+
console.log(C.warning(` Unknown subcommand: ${cmd}`));
|
|
2479
|
+
console.log('');
|
|
2480
|
+
console.log(C.muted(' Usage: /ollama [status|list|pull <model>]'));
|
|
2481
|
+
console.log(C.dim(' /ollama — show status and installed models'));
|
|
2482
|
+
console.log(C.dim(' /ollama list — list all installed models'));
|
|
2483
|
+
console.log(C.dim(' /ollama pull <n> — download a model'));
|
|
2484
|
+
}
|
|
2485
|
+
// ========================================================================
|
|
2486
|
+
// Approval handler
|
|
2487
|
+
// ========================================================================
|
|
2488
|
+
async handleApproval(request) {
|
|
2489
|
+
const effectiveMode = this.getEffectiveApprovalMode();
|
|
2490
|
+
if (effectiveMode === 'yolo' || effectiveMode === 'accepting_edits') {
|
|
2491
|
+
return { requestId: request.id, approved: true };
|
|
2492
|
+
}
|
|
2493
|
+
this.stopSpinner();
|
|
2494
|
+
return new Promise((resolve) => {
|
|
2495
|
+
console.log('');
|
|
2496
|
+
console.log(C.warning.bold(' ⚠ Approval Required'));
|
|
2497
|
+
console.log(C.muted(' Tool: ') + C.toolName(request.toolName));
|
|
2498
|
+
console.log(C.muted(' Risk: ') + (request.risk === 'critical' ? C.error(request.risk) :
|
|
2499
|
+
request.risk === 'high' ? C.warning(request.risk) :
|
|
2500
|
+
C.muted(request.risk)));
|
|
2501
|
+
if (request.description) {
|
|
2502
|
+
const desc = request.description.replace(`Tool "${request.toolName}" with input: `, '');
|
|
2503
|
+
const preview = desc.slice(0, 80);
|
|
2504
|
+
console.log(C.muted(' Input: ') + C.dim(preview));
|
|
2505
|
+
}
|
|
2506
|
+
console.log('');
|
|
2507
|
+
this.rl?.question(C.warning(' Allow? [y/N/a(ll)] '), (answer) => {
|
|
2508
|
+
const a = answer.trim().toLowerCase();
|
|
2509
|
+
if (a === 'a' || a === 'all') {
|
|
2510
|
+
// Switch to yolo for remainder of this task
|
|
2511
|
+
this.mode = 'auto';
|
|
2512
|
+
console.log(C.success(' Auto-approved for this task.'));
|
|
2513
|
+
resolve({ requestId: request.id, approved: true });
|
|
2514
|
+
}
|
|
2515
|
+
else {
|
|
2516
|
+
const approved = a === 'y' || a === 'yes';
|
|
2517
|
+
if (!approved)
|
|
2518
|
+
console.log(C.error(' Denied.'));
|
|
2519
|
+
resolve({ requestId: request.id, approved });
|
|
2520
|
+
}
|
|
2521
|
+
});
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
// ========================================================================
|
|
2525
|
+
// Helpers
|
|
2526
|
+
// ========================================================================
|
|
2527
|
+
getModePrefix() {
|
|
2528
|
+
switch (this.mode) {
|
|
2529
|
+
case 'plan':
|
|
2530
|
+
return '[PLAN MODE] First analyze and create a step-by-step plan. Wait for confirmation before executing.';
|
|
2531
|
+
case 'ask':
|
|
2532
|
+
return '[ASK MODE] Only answer questions. Do NOT modify files or execute commands.';
|
|
2533
|
+
default:
|
|
2534
|
+
return '';
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
getEffectiveApprovalMode() {
|
|
2538
|
+
return MODE_LABELS[this.mode].approvalMode;
|
|
2539
|
+
}
|
|
2540
|
+
createInitialSession() {
|
|
2541
|
+
const session = this.sessionManager.create({
|
|
2542
|
+
workingDirectory: this.cwd,
|
|
2543
|
+
model: this.modelClient.getModel(),
|
|
2544
|
+
maxTokens: this.config.core.maxTokens,
|
|
2545
|
+
temperature: this.config.core.temperature,
|
|
2546
|
+
approvalMode: this.getEffectiveApprovalMode(),
|
|
2547
|
+
streaming: true,
|
|
2548
|
+
maxTurns: this.config.core.maxTurns,
|
|
2549
|
+
});
|
|
2550
|
+
return session.id;
|
|
2551
|
+
}
|
|
2552
|
+
getTimeStr() {
|
|
2553
|
+
return new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
2554
|
+
}
|
|
2555
|
+
ensureDir(dirPath) {
|
|
2556
|
+
if (!fs.existsSync(dirPath)) {
|
|
2557
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
//# sourceMappingURL=InteractiveRepl.js.map
|