Files

1 line
239 KiB
JSON

{"repo": "agnath18k/lumo", "files": {"pkg/executor/config.go": {"c2e36247b5a15bd6b9d62d1d60d9491543919823": {"t": 1785746075.1903503, "n": 32594, "h": "5db3b7c8e2df7cb3", "c": "package executor\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/agnath18K/lumo/pkg/ai\"\n\t\"github.com/agnath18K/lumo/pkg/config\"\n\t\"github.com/agnath18K/lumo/pkg/nlp\"\n)\n\n// getCurrentModel returns the current model based on the provider\nfunc getCurrentModel(cfg *config.Config) string {\n\tif cfg.AIProvider == \"gemini\" {\n\t\treturn cfg.GeminiModel\n\t}\n\treturn cfg.OpenAIModel\n}\n\n// executeConfigCommand handles configuration commands\nfunc (e *Executor) executeConfigCommand(cmd *nlp.Command) (*Result, error) {\n\t// Split the command into parts\n\tparts := strings.Fields(cmd.Intent)\n\tif len(parts) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🔧 Lumo Configuration ─────────────────╮\n\n Commands:\n • config:provider list List available AI providers\n • config:provider show Show current AI provider\n • config:provider set <provider> Set AI provider (gemini/openai/ollama)\n\n • config:model list List available models\n • config:model show Show current model\n • config:model set <model> Set model for current provider\n\n • config:key show Show current API key status\n • config:key set <provider> <key> Set API key for provider\n • config:key remove <provider> Remove API key for provider\n\n • config:ollama show Show current Ollama URL\n • config:ollama set <url> Set Ollama URL\n • config:ollama test Test connection to Ollama server\n\n • config:mode show Show current input mode\n • config:mode ai Set AI-first mode (default)\n • config:mode command Set command-first mode\n\n • config:server show Show current server settings\n • config:server quiet on/off Enable/disable server log messages\n\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\t// Handle different configuration commands\n\tswitch parts[0] {\n\tcase \"provider\":\n\t\treturn e.handleProviderConfig(parts[1:], cmd)\n\tcase \"model\":\n\t\treturn e.handleModelConfig(parts[1:], cmd)\n\tcase \"key\":\n\t\treturn e.handleKeyConfig(parts[1:], cmd)\n\tcase \"ollama\":\n\t\treturn e.handleOllamaConfig(parts[1:], cmd)\n\tcase \"compatible\":\n\t\treturn e.handleCompatibleConfig(parts[1:], cmd)\n\tcase \"mode\":\n\t\treturn e.handleModeConfig(parts[1:], cmd)\n\tcase \"server\":\n\t\treturn e.handleServerConfig(parts[1:], cmd)\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown configuration command: %s\\nUse 'config:' for help.\", parts[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleProviderConfig handles provider configuration commands\nfunc (e *Executor) handleProviderConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing provider command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// List available providers\n\t\toutput := `\n╭─────────────── 🐦 Available AI Providers ───────────────╮\n\n • gemini (Google's Gemini AI models)\n • openai (OpenAI's GPT models)\n • ollama (Local Ollama models)\n • claude (Anthropic's Claude models)\n • openai-compatible (Any OpenAI-compatible API: xAI, DeepSeek, Mistral, Groq, OpenRouter)\n\n Current provider: ` + e.config.AIProvider + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current provider\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current AI provider: %s\", e.config.AIProvider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set provider\n\t\tconst providerHint = \"Use 'gemini', 'openai', 'ollama', 'claude', or 'openai-compatible'.\"\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. \" + providerHint,\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tswitch provider {\n\t\tcase \"gemini\", \"openai\", \"ollama\", \"claude\", \"openai-compatible\":\n\t\t\t// valid\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. %s\", provider, providerHint),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if API key is set for the provider (not needed for Ollama)\n\t\tif provider == \"gemini\" && e.config.GeminiAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for Gemini. Please set an API key first with 'config:key set gemini <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"openai\" && e.config.OpenAIAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for OpenAI. Please set an API key first with 'config:key set openai <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"claude\" && e.config.ClaudeAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for Claude. Please set an API key first with 'config:key set claude <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"openai-compatible\" && e.config.CompatibleAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for the OpenAI-compatible provider. Set one with 'config:key set openai-compatible <key>' and the endpoint with 'config:compatible set-url <url>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\t// Ollama doesn't need an API key, but we should check if the URL is accessible\n\t\tif provider == \"ollama\" {\n\t\t\t// Try to connect to the Ollama server\n\t\t\tclient := &http.Client{\n\t\t\t\tTimeout: 5 * time.Second,\n\t\t\t}\n\t\t\t_, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Cannot connect to Ollama server at %s. Please make sure Ollama is running and accessible.\", e.config.OllamaURL),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t}\n\n\t\t// Set the provider\n\t\te.config.AIProvider = provider\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Reinitialize the AI client with the new provider\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\tcase \"ollama\":\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\tcase \"claude\":\n\t\t\te.aiClient = ai.NewClaudeClient(e.config.ClaudeAPIKey, e.config.ClaudeModel)\n\t\tcase \"openai-compatible\":\n\t\t\te.aiClient = ai.NewOpenAICompatibleClient(e.config.CompatibleAPIKey, e.config.CompatibleModel, e.config.CompatibleBaseURL)\n\t\tdefault: // Default to OpenAI\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"AI provider set to: %s\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown provider command: %s. Use 'show' or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModelConfig handles model configuration commands\nfunc (e *Executor) handleModelConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing model command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// Use the dedicated model list handler\n\t\treturn e.handleModelList(cmd)\n\tcase \"unused\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tvar output string\n\t\tif false {\n\t\t\toutput = `\n╭─────────────── 🐦 Available Gemini Models ───────────────╮\n\n • gemini-2.0-flash-lite (Fast, efficient for most queries)\n • gemini-2.0-flash (Balanced performance and quality)\n • gemini-2.0-pro (High quality, more capabilities)\n\n Current model: ` + e.config.GeminiModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t} else {\n\t\t\toutput = `\n╭─────────────── 🐦 Available OpenAI Models ───────────────╮\n\n • gpt-3.5-turbo (Fast, cost-effective)\n • gpt-4o (Advanced capabilities, slower)\n • gpt-4o-mini (Balanced performance and quality)\n\n Current model: ` + e.config.OpenAIModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current model\n\t\tvar currentModel string\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tcurrentModel = e.config.GeminiModel\n\t\tcase \"ollama\":\n\t\t\tcurrentModel = e.config.OllamaModel\n\t\tcase \"claude\":\n\t\t\tcurrentModel = e.config.ClaudeModel\n\t\tcase \"openai-compatible\":\n\t\t\tcurrentModel = e.config.CompatibleModel\n\t\tdefault: // OpenAI\n\t\t\tcurrentModel = e.config.OpenAIModel\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current %s model: %s\", e.config.AIProvider, currentModel),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set model\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing model name. Use 'config:model list' to see available models.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tmodel := args[1]\n\n\t\t// Note appended when a model isn't in the curated suggestion list.\n\t\t// Model IDs change frequently, so any non-empty string is accepted —\n\t\t// the suggestion lists are guidance, not a gate.\n\t\tvar note string\n\t\tnotInList := func(suggestions []string) string {\n\t\t\tfor _, s := range suggestions {\n\t\t\t\tif model == s {\n\t\t\t\t\treturn \"\"\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Sprintf(\" (not in the suggested list — make sure it's a valid %s model)\", e.config.AIProvider)\n\t\t}\n\n\t\t// Apply the model based on provider. Ollama keeps its live server check\n\t\t// since the model must actually be pulled locally.\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tnote = notInList([]string{\"gemini-2.5-pro\", \"gemini-2.5-flash\", \"gemini-2.5-flash-lite\"})\n\t\t\te.config.GeminiModel = model\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\n\t\tcase \"ollama\":\n\t\t\t// For Ollama, we need to check if the model exists locally\n\t\t\tollamaClient := ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t\tmodels, err := ollamaClient.ListModels()\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error checking Ollama models: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range models {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid or unavailable Ollama model: %s. Use 'config:model list' to see available models or 'ollama pull %s' to download it.\", model, model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\te.config.OllamaModel = model\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\n\t\tcase \"claude\":\n\t\t\tnote = notInList([]string{\"claude-opus-4-8\", \"claude-sonnet-4-6\", \"claude-haiku-4-5\", \"claude-fable-5\"})\n\t\t\te.config.ClaudeModel = model\n\t\t\te.aiClient = ai.NewClaudeClient(e.config.ClaudeAPIKey, e.config.ClaudeModel)\n\n\t\tcase \"openai-compatible\":\n\t\t\t// Any model the configured endpoint serves is valid.\n\t\t\te.config.CompatibleModel = model\n\t\t\te.aiClient = ai.NewOpenAICompatibleClient(e.config.CompatibleAPIKey, e.config.CompatibleModel, e.config.CompatibleBaseURL)\n\n\t\tdefault: // OpenAI\n\t\t\tnote = notInList([]string{\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4.1\"})\n\t\t\te.config.OpenAIModel = model\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s model set to: %s%s\", e.config.AIProvider, model, note),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown model command: %s. Use 'list', 'show', or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleCompatibleConfig handles base-URL configuration for the OpenAI-compatible provider.\nfunc (e *Executor) handleCompatibleConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing compatible command. Use 'show' or 'set-url'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"OpenAI-compatible base URL: %s\\nModel: %s\\nExamples: https://api.x.ai/v1 (xAI), https://api.deepseek.com/v1 (DeepSeek), https://api.mistral.ai/v1 (Mistral), https://api.groq.com/openai/v1 (Groq), https://openrouter.ai/api/v1 (OpenRouter)\",\n\t\t\t\te.config.CompatibleBaseURL, e.config.CompatibleModel),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set-url\":\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing URL. Usage: config:compatible set-url <url>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\turl := args[1]\n\t\tif !strings.HasPrefix(url, \"http://\") && !strings.HasPrefix(url, \"https://\") {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Invalid URL format. URL must start with http:// or https://\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\turl = strings.TrimSuffix(url, \"/\")\n\t\te.config.CompatibleBaseURL = url\n\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// If this is the current provider, reinitialize the client\n\t\tif e.config.AIProvider == \"openai-compatible\" {\n\t\t\te.aiClient = ai.NewOpenAICompatibleClient(e.config.CompatibleAPIKey, e.config.CompatibleModel, e.config.CompatibleBaseURL)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"OpenAI-compatible base URL set to: %s\", url),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown compatible command: %s. Use 'show' or 'set-url'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleOllamaConfig handles Ollama URL configuration commands\nfunc (e *Executor) handleOllamaConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing Ollama command. Use 'show', 'set', or 'test'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current Ollama URL\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current Ollama URL: %s\", e.config.OllamaURL),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set Ollama URL\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing URL. Usage: config:ollama set <url>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\turl := args[1]\n\n\t\t// Basic URL validation\n\t\tif !strings.HasPrefix(url, \"http://\") && !strings.HasPrefix(url, \"https://\") {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Invalid URL format. URL must start with http:// or https://\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove trailing slash if present\n\t\turl = strings.TrimSuffix(url, \"/\")\n\n\t\t// Set the URL\n\t\te.config.OllamaURL = url\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// If Ollama is the current provider, reinitialize the client\n\t\tif e.config.AIProvider == \"ollama\" {\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Ollama URL set to: %s\", url),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"test\":\n\t\t// Test connection to Ollama server\n\t\tclient := &http.Client{\n\t\t\tTimeout: 5 * time.Second,\n\t\t}\n\t\tresp, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Cannot connect to Ollama server at %s\\nError: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Ollama server returned status code %d\", resp.StatusCode),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Try to parse the response to get available models\n\t\tvar modelsResponse struct {\n\t\t\tModels []struct {\n\t\t\t\tName string `json:\"name\"`\n\t\t\t} `json:\"models\"`\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't read response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\terr = json.Unmarshal(body, &modelsResponse)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't parse response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Count available models\n\t\tmodelCount := len(modelsResponse.Models)\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"✅ Successfully connected to Ollama server at %s\\nFound %d available models\", e.config.OllamaURL, modelCount),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown Ollama command: %s. Use 'show', 'set', or 'test'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModeConfig handles input mode configuration commands\nfunc (e *Executor) handleModeConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing mode command. Use 'show', 'ai', or 'command'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current mode\n\t\tmodeStr := \"AI-first\"\n\t\tif e.config.CommandFirstMode {\n\t\t\tmodeStr = \"Command-first\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔧 Input Mode ─────────────────────────╮\n\n Current input mode: %s\n\n • AI-first mode: Treats all input as AI queries by default\n unless it starts with a specific command prefix.\n\n • Command-first mode: Treats input as shell commands if it\n looks like a command, otherwise as an AI query.\n\n╰──────────────────────────────────────────────────────────╯\n`, modeStr)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"ai\":\n\t\t// Set AI-first mode\n\t\te.config.CommandFirstMode = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to AI-first. Lumo will now treat all input as AI queries by default unless it starts with a specific command prefix.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"command\":\n\t\t// Set Command-first mode\n\t\te.config.CommandFirstMode = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to Command-first. Lumo will now treat input as shell commands if it looks like a command, otherwise as an AI query.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown mode command: %s. Use 'show', 'ai', or 'command'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleServerConfig handles server configuration commands\nfunc (e *Executor) handleServerConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🖥️ Server Configuration ─────────────────╮\n\n Commands:\n • config:server show Show current server settings\n • config:server enable Enable the REST server\n • config:server disable Disable the REST server\n • config:server port <port> Set the server port\n • config:server quiet on Enable quiet mode (suppress logs)\n • config:server quiet off Disable quiet mode (show logs)\n • config:server auth enable Enable authentication\n • config:server auth disable Disable authentication\n • config:server auth password Change the admin password\n\n Configure these settings in ~/.config/lumo/config.json\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current server settings\n\t\tenabledStr := \"Disabled\"\n\t\tif e.config.EnableServer {\n\t\t\tenabledStr = \"Enabled\"\n\t\t}\n\n\t\tquietStr := \"Disabled\"\n\t\tif e.config.ServerQuietOutput {\n\t\t\tquietStr = \"Enabled\"\n\t\t}\n\n\t\tauthStr := \"Disabled\"\n\t\tif e.config.EnableAuth {\n\t\t\tauthStr = \"Enabled\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🖥️ Server Settings ───────────────────╮\n\n • Server Status: %s\n • Server Port: %d\n • Quiet Output: %s\n • Authentication: %s\n • Token Expiration: %d hours\n • Refresh Token Expiration: %d days\n\n Configure these settings in ~/.config/lumo/config.json\n or use the commands below.\n\n Commands:\n • config:server enable Enable the REST server\n • config:server disable Disable the REST server\n • config:server port <port> Set the server port\n • config:server quiet on Enable quiet mode\n • config:server quiet off Disable quiet mode\n • config:server auth enable Enable authentication\n • config:server auth disable Disable authentication\n • config:server auth password Change the admin password\n╰──────────────────────────────────────────────────────────╯\n`, enabledStr, e.config.ServerPort, quietStr, authStr, e.config.TokenExpirationHours, e.config.RefreshExpirationDays)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"enable\":\n\t\t// Enable the server\n\t\te.config.EnableServer = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Server has been enabled. The REST server will now start when Lumo is executed.\\n\\nNOTE: The server will be accessible on port \" + fmt.Sprintf(\"%d\", e.config.ServerPort) + \". Make sure your firewall is configured appropriately.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"disable\":\n\t\t// Disable the server\n\t\te.config.EnableServer = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Server has been disabled. The REST server will not start when Lumo is executed.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"port\":\n\t\t// Set the server port\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing port number. Usage: config:server port <port>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tport, err := strconv.Atoi(args[1])\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid port number: %s\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif port < 1024 || port > 65535 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Port number must be between 1024 and 65535\"),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\te.config.ServerPort = port\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Server port set to %d\", port),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"quiet\":\n\t\t// Set quiet mode\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing argument. Usage: config:server quiet on|off\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tswitch strings.ToLower(args[1]) {\n\t\tcase \"on\", \"true\", \"yes\", \"1\":\n\t\t\te.config.ServerQuietOutput = true\n\t\tcase \"off\", \"false\", \"no\", \"0\":\n\t\t\te.config.ServerQuietOutput = false\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid value: %s. Use 'on' or 'off'.\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tquietStr := \"enabled\"\n\t\tif !e.config.ServerQuietOutput {\n\t\t\tquietStr = \"disabled\"\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Server quiet mode %s\", quietStr),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"auth\":\n\t\t// Handle authentication settings\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing argument. Usage: config:server auth enable|disable|password\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tswitch strings.ToLower(args[1]) {\n\t\tcase \"enable\", \"on\", \"true\", \"yes\", \"1\":\n\t\t\te.config.EnableAuth = true\n\t\t\tif err := e.config.Save(); err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Authentication enabled for the REST server.\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tcase \"disable\", \"off\", \"false\", \"no\", \"0\":\n\t\t\te.config.EnableAuth = false\n\t\t\tif err := e.config.Save(); err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Authentication disabled for the REST server. Warning: This makes your API endpoints publicly accessible!\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tcase \"password\":\n\t\t\t// This would be handled by a separate command that prompts for the password\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"To change the password, please use the web interface or the REST API directly.\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid value: %s. Use 'enable', 'disable', or 'password'.\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown server command: %s. Use 'show', 'enable', 'disable', 'port', 'quiet', or 'auth'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleKeyConfig handles API key configuration commands\nfunc (e *Executor) handleKeyConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing key command. Use 'show', 'set', or 'remove'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Use the dedicated key status handler\n\t\treturn e.handleKeyStatus(cmd)\n\tcase \"unused_show\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tgeminiStatus := \"Not set\"\n\t\tif e.config.GeminiAPIKey != \"\" {\n\t\t\tgeminiStatus = \"Set\"\n\t\t}\n\n\t\topenaiStatus := \"Not set\"\n\t\tif e.config.OpenAIAPIKey != \"\" {\n\t\t\topenaiStatus = \"Set\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔑 API Key Status ─────────────────────╮\n\n • Gemini API Key: %s\n • OpenAI API Key: %s\n\n Current provider: %s\n\n╰──────────────────────────────────────────────────────────╯\n`, geminiStatus, openaiStatus, e.config.AIProvider)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set API key\n\t\tconst keyProviderHint = \"Use 'gemini', 'openai', 'claude', or 'openai-compatible'. Note: Ollama doesn't require an API key.\"\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. \" + keyProviderHint,\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif len(args) < 3 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing API key. Usage: config:key set <provider> <key>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tapiKey := args[2]\n\n\t\t// Set the API key, reinitializing the live client if it's the active provider.\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.config.GeminiAPIKey = apiKey\n\t\t\tif e.config.AIProvider == \"gemini\" {\n\t\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\t\t}\n\t\tcase \"openai\":\n\t\t\te.config.OpenAIAPIKey = apiKey\n\t\t\tif e.config.AIProvider == \"openai\" {\n\t\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t\t}\n\t\tcase \"claude\":\n\t\t\te.config.ClaudeAPIKey = apiKey\n\t\t\tif e.config.AIProvider == \"claude\" {\n\t\t\t\te.aiClient = ai.NewClaudeClient(e.config.ClaudeAPIKey, e.config.ClaudeModel)\n\t\t\t}\n\t\tcase \"openai-compatible\":\n\t\t\te.config.CompatibleAPIKey = apiKey\n\t\t\tif e.config.AIProvider == \"openai-compatible\" {\n\t\t\t\te.aiClient = ai.NewOpenAICompatibleClient(e.config.CompatibleAPIKey, e.config.CompatibleModel, e.config.CompatibleBaseURL)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. %s\", provider, keyProviderHint),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been set.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"remove\":\n\t\t// Remove API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini', 'openai', 'claude', or 'openai-compatible'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\n\t\tswitch provider {\n\t\tcase \"gemini\", \"openai\", \"claude\", \"openai-compatible\":\n\t\t\t// valid\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini', 'openai', 'claude', or 'openai-compatible'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if this is the current provider\n\t\tif e.config.AIProvider == provider {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Cannot remove API key for the current provider. Switch providers first with 'config:provider set'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove the API key\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.config.GeminiAPIKey = \"\"\n\t\tcase \"openai\":\n\t\t\te.config.OpenAIAPIKey = \"\"\n\t\tcase \"claude\":\n\t\t\te.config.ClaudeAPIKey = \"\"\n\t\tcase \"openai-compatible\":\n\t\t\te.config.CompatibleAPIKey = \"\"\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been removed.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown key command: %s. Use 'show', 'set', or 'remove'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n"}, "1daefb4ec895440abd8fbedcbda7f69f79a8b823": {"t": 1785759560.4280586, "n": 32594, "h": "5db3b7c8e2df7cb3", "c": "package executor\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/agnath18K/lumo/pkg/ai\"\n\t\"github.com/agnath18K/lumo/pkg/config\"\n\t\"github.com/agnath18K/lumo/pkg/nlp\"\n)\n\n// getCurrentModel returns the current model based on the provider\nfunc getCurrentModel(cfg *config.Config) string {\n\tif cfg.AIProvider == \"gemini\" {\n\t\treturn cfg.GeminiModel\n\t}\n\treturn cfg.OpenAIModel\n}\n\n// executeConfigCommand handles configuration commands\nfunc (e *Executor) executeConfigCommand(cmd *nlp.Command) (*Result, error) {\n\t// Split the command into parts\n\tparts := strings.Fields(cmd.Intent)\n\tif len(parts) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🔧 Lumo Configuration ─────────────────╮\n\n Commands:\n • config:provider list List available AI providers\n • config:provider show Show current AI provider\n • config:provider set <provider> Set AI provider (gemini/openai/ollama)\n\n • config:model list List available models\n • config:model show Show current model\n • config:model set <model> Set model for current provider\n\n • config:key show Show current API key status\n • config:key set <provider> <key> Set API key for provider\n • config:key remove <provider> Remove API key for provider\n\n • config:ollama show Show current Ollama URL\n • config:ollama set <url> Set Ollama URL\n • config:ollama test Test connection to Ollama server\n\n • config:mode show Show current input mode\n • config:mode ai Set AI-first mode (default)\n • config:mode command Set command-first mode\n\n • config:server show Show current server settings\n • config:server quiet on/off Enable/disable server log messages\n\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\t// Handle different configuration commands\n\tswitch parts[0] {\n\tcase \"provider\":\n\t\treturn e.handleProviderConfig(parts[1:], cmd)\n\tcase \"model\":\n\t\treturn e.handleModelConfig(parts[1:], cmd)\n\tcase \"key\":\n\t\treturn e.handleKeyConfig(parts[1:], cmd)\n\tcase \"ollama\":\n\t\treturn e.handleOllamaConfig(parts[1:], cmd)\n\tcase \"compatible\":\n\t\treturn e.handleCompatibleConfig(parts[1:], cmd)\n\tcase \"mode\":\n\t\treturn e.handleModeConfig(parts[1:], cmd)\n\tcase \"server\":\n\t\treturn e.handleServerConfig(parts[1:], cmd)\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown configuration command: %s\\nUse 'config:' for help.\", parts[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleProviderConfig handles provider configuration commands\nfunc (e *Executor) handleProviderConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing provider command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// List available providers\n\t\toutput := `\n╭─────────────── 🐦 Available AI Providers ───────────────╮\n\n • gemini (Google's Gemini AI models)\n • openai (OpenAI's GPT models)\n • ollama (Local Ollama models)\n • claude (Anthropic's Claude models)\n • openai-compatible (Any OpenAI-compatible API: xAI, DeepSeek, Mistral, Groq, OpenRouter)\n\n Current provider: ` + e.config.AIProvider + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current provider\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current AI provider: %s\", e.config.AIProvider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set provider\n\t\tconst providerHint = \"Use 'gemini', 'openai', 'ollama', 'claude', or 'openai-compatible'.\"\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. \" + providerHint,\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tswitch provider {\n\t\tcase \"gemini\", \"openai\", \"ollama\", \"claude\", \"openai-compatible\":\n\t\t\t// valid\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. %s\", provider, providerHint),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if API key is set for the provider (not needed for Ollama)\n\t\tif provider == \"gemini\" && e.config.GeminiAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for Gemini. Please set an API key first with 'config:key set gemini <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"openai\" && e.config.OpenAIAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for OpenAI. Please set an API key first with 'config:key set openai <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"claude\" && e.config.ClaudeAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for Claude. Please set an API key first with 'config:key set claude <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"openai-compatible\" && e.config.CompatibleAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for the OpenAI-compatible provider. Set one with 'config:key set openai-compatible <key>' and the endpoint with 'config:compatible set-url <url>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\t// Ollama doesn't need an API key, but we should check if the URL is accessible\n\t\tif provider == \"ollama\" {\n\t\t\t// Try to connect to the Ollama server\n\t\t\tclient := &http.Client{\n\t\t\t\tTimeout: 5 * time.Second,\n\t\t\t}\n\t\t\t_, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Cannot connect to Ollama server at %s. Please make sure Ollama is running and accessible.\", e.config.OllamaURL),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t}\n\n\t\t// Set the provider\n\t\te.config.AIProvider = provider\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Reinitialize the AI client with the new provider\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\tcase \"ollama\":\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\tcase \"claude\":\n\t\t\te.aiClient = ai.NewClaudeClient(e.config.ClaudeAPIKey, e.config.ClaudeModel)\n\t\tcase \"openai-compatible\":\n\t\t\te.aiClient = ai.NewOpenAICompatibleClient(e.config.CompatibleAPIKey, e.config.CompatibleModel, e.config.CompatibleBaseURL)\n\t\tdefault: // Default to OpenAI\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"AI provider set to: %s\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown provider command: %s. Use 'show' or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModelConfig handles model configuration commands\nfunc (e *Executor) handleModelConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing model command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// Use the dedicated model list handler\n\t\treturn e.handleModelList(cmd)\n\tcase \"unused\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tvar output string\n\t\tif false {\n\t\t\toutput = `\n╭─────────────── 🐦 Available Gemini Models ───────────────╮\n\n • gemini-2.0-flash-lite (Fast, efficient for most queries)\n • gemini-2.0-flash (Balanced performance and quality)\n • gemini-2.0-pro (High quality, more capabilities)\n\n Current model: ` + e.config.GeminiModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t} else {\n\t\t\toutput = `\n╭─────────────── 🐦 Available OpenAI Models ───────────────╮\n\n • gpt-3.5-turbo (Fast, cost-effective)\n • gpt-4o (Advanced capabilities, slower)\n • gpt-4o-mini (Balanced performance and quality)\n\n Current model: ` + e.config.OpenAIModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current model\n\t\tvar currentModel string\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tcurrentModel = e.config.GeminiModel\n\t\tcase \"ollama\":\n\t\t\tcurrentModel = e.config.OllamaModel\n\t\tcase \"claude\":\n\t\t\tcurrentModel = e.config.ClaudeModel\n\t\tcase \"openai-compatible\":\n\t\t\tcurrentModel = e.config.CompatibleModel\n\t\tdefault: // OpenAI\n\t\t\tcurrentModel = e.config.OpenAIModel\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current %s model: %s\", e.config.AIProvider, currentModel),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set model\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing model name. Use 'config:model list' to see available models.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tmodel := args[1]\n\n\t\t// Note appended when a model isn't in the curated suggestion list.\n\t\t// Model IDs change frequently, so any non-empty string is accepted —\n\t\t// the suggestion lists are guidance, not a gate.\n\t\tvar note string\n\t\tnotInList := func(suggestions []string) string {\n\t\t\tfor _, s := range suggestions {\n\t\t\t\tif model == s {\n\t\t\t\t\treturn \"\"\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Sprintf(\" (not in the suggested list — make sure it's a valid %s model)\", e.config.AIProvider)\n\t\t}\n\n\t\t// Apply the model based on provider. Ollama keeps its live server check\n\t\t// since the model must actually be pulled locally.\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tnote = notInList([]string{\"gemini-2.5-pro\", \"gemini-2.5-flash\", \"gemini-2.5-flash-lite\"})\n\t\t\te.config.GeminiModel = model\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\n\t\tcase \"ollama\":\n\t\t\t// For Ollama, we need to check if the model exists locally\n\t\t\tollamaClient := ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t\tmodels, err := ollamaClient.ListModels()\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error checking Ollama models: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range models {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid or unavailable Ollama model: %s. Use 'config:model list' to see available models or 'ollama pull %s' to download it.\", model, model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\te.config.OllamaModel = model\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\n\t\tcase \"claude\":\n\t\t\tnote = notInList([]string{\"claude-opus-4-8\", \"claude-sonnet-4-6\", \"claude-haiku-4-5\", \"claude-fable-5\"})\n\t\t\te.config.ClaudeModel = model\n\t\t\te.aiClient = ai.NewClaudeClient(e.config.ClaudeAPIKey, e.config.ClaudeModel)\n\n\t\tcase \"openai-compatible\":\n\t\t\t// Any model the configured endpoint serves is valid.\n\t\t\te.config.CompatibleModel = model\n\t\t\te.aiClient = ai.NewOpenAICompatibleClient(e.config.CompatibleAPIKey, e.config.CompatibleModel, e.config.CompatibleBaseURL)\n\n\t\tdefault: // OpenAI\n\t\t\tnote = notInList([]string{\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4.1\"})\n\t\t\te.config.OpenAIModel = model\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s model set to: %s%s\", e.config.AIProvider, model, note),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown model command: %s. Use 'list', 'show', or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleCompatibleConfig handles base-URL configuration for the OpenAI-compatible provider.\nfunc (e *Executor) handleCompatibleConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing compatible command. Use 'show' or 'set-url'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"OpenAI-compatible base URL: %s\\nModel: %s\\nExamples: https://api.x.ai/v1 (xAI), https://api.deepseek.com/v1 (DeepSeek), https://api.mistral.ai/v1 (Mistral), https://api.groq.com/openai/v1 (Groq), https://openrouter.ai/api/v1 (OpenRouter)\",\n\t\t\t\te.config.CompatibleBaseURL, e.config.CompatibleModel),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set-url\":\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing URL. Usage: config:compatible set-url <url>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\turl := args[1]\n\t\tif !strings.HasPrefix(url, \"http://\") && !strings.HasPrefix(url, \"https://\") {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Invalid URL format. URL must start with http:// or https://\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\turl = strings.TrimSuffix(url, \"/\")\n\t\te.config.CompatibleBaseURL = url\n\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// If this is the current provider, reinitialize the client\n\t\tif e.config.AIProvider == \"openai-compatible\" {\n\t\t\te.aiClient = ai.NewOpenAICompatibleClient(e.config.CompatibleAPIKey, e.config.CompatibleModel, e.config.CompatibleBaseURL)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"OpenAI-compatible base URL set to: %s\", url),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown compatible command: %s. Use 'show' or 'set-url'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleOllamaConfig handles Ollama URL configuration commands\nfunc (e *Executor) handleOllamaConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing Ollama command. Use 'show', 'set', or 'test'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current Ollama URL\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current Ollama URL: %s\", e.config.OllamaURL),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set Ollama URL\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing URL. Usage: config:ollama set <url>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\turl := args[1]\n\n\t\t// Basic URL validation\n\t\tif !strings.HasPrefix(url, \"http://\") && !strings.HasPrefix(url, \"https://\") {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Invalid URL format. URL must start with http:// or https://\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove trailing slash if present\n\t\turl = strings.TrimSuffix(url, \"/\")\n\n\t\t// Set the URL\n\t\te.config.OllamaURL = url\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// If Ollama is the current provider, reinitialize the client\n\t\tif e.config.AIProvider == \"ollama\" {\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Ollama URL set to: %s\", url),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"test\":\n\t\t// Test connection to Ollama server\n\t\tclient := &http.Client{\n\t\t\tTimeout: 5 * time.Second,\n\t\t}\n\t\tresp, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Cannot connect to Ollama server at %s\\nError: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Ollama server returned status code %d\", resp.StatusCode),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Try to parse the response to get available models\n\t\tvar modelsResponse struct {\n\t\t\tModels []struct {\n\t\t\t\tName string `json:\"name\"`\n\t\t\t} `json:\"models\"`\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't read response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\terr = json.Unmarshal(body, &modelsResponse)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't parse response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Count available models\n\t\tmodelCount := len(modelsResponse.Models)\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"✅ Successfully connected to Ollama server at %s\\nFound %d available models\", e.config.OllamaURL, modelCount),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown Ollama command: %s. Use 'show', 'set', or 'test'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModeConfig handles input mode configuration commands\nfunc (e *Executor) handleModeConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing mode command. Use 'show', 'ai', or 'command'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current mode\n\t\tmodeStr := \"AI-first\"\n\t\tif e.config.CommandFirstMode {\n\t\t\tmodeStr = \"Command-first\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔧 Input Mode ─────────────────────────╮\n\n Current input mode: %s\n\n • AI-first mode: Treats all input as AI queries by default\n unless it starts with a specific command prefix.\n\n • Command-first mode: Treats input as shell commands if it\n looks like a command, otherwise as an AI query.\n\n╰──────────────────────────────────────────────────────────╯\n`, modeStr)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"ai\":\n\t\t// Set AI-first mode\n\t\te.config.CommandFirstMode = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to AI-first. Lumo will now treat all input as AI queries by default unless it starts with a specific command prefix.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"command\":\n\t\t// Set Command-first mode\n\t\te.config.CommandFirstMode = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to Command-first. Lumo will now treat input as shell commands if it looks like a command, otherwise as an AI query.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown mode command: %s. Use 'show', 'ai', or 'command'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleServerConfig handles server configuration commands\nfunc (e *Executor) handleServerConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🖥️ Server Configuration ─────────────────╮\n\n Commands:\n • config:server show Show current server settings\n • config:server enable Enable the REST server\n • config:server disable Disable the REST server\n • config:server port <port> Set the server port\n • config:server quiet on Enable quiet mode (suppress logs)\n • config:server quiet off Disable quiet mode (show logs)\n • config:server auth enable Enable authentication\n • config:server auth disable Disable authentication\n • config:server auth password Change the admin password\n\n Configure these settings in ~/.config/lumo/config.json\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current server settings\n\t\tenabledStr := \"Disabled\"\n\t\tif e.config.EnableServer {\n\t\t\tenabledStr = \"Enabled\"\n\t\t}\n\n\t\tquietStr := \"Disabled\"\n\t\tif e.config.ServerQuietOutput {\n\t\t\tquietStr = \"Enabled\"\n\t\t}\n\n\t\tauthStr := \"Disabled\"\n\t\tif e.config.EnableAuth {\n\t\t\tauthStr = \"Enabled\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🖥️ Server Settings ───────────────────╮\n\n • Server Status: %s\n • Server Port: %d\n • Quiet Output: %s\n • Authentication: %s\n • Token Expiration: %d hours\n • Refresh Token Expiration: %d days\n\n Configure these settings in ~/.config/lumo/config.json\n or use the commands below.\n\n Commands:\n • config:server enable Enable the REST server\n • config:server disable Disable the REST server\n • config:server port <port> Set the server port\n • config:server quiet on Enable quiet mode\n • config:server quiet off Disable quiet mode\n • config:server auth enable Enable authentication\n • config:server auth disable Disable authentication\n • config:server auth password Change the admin password\n╰──────────────────────────────────────────────────────────╯\n`, enabledStr, e.config.ServerPort, quietStr, authStr, e.config.TokenExpirationHours, e.config.RefreshExpirationDays)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"enable\":\n\t\t// Enable the server\n\t\te.config.EnableServer = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Server has been enabled. The REST server will now start when Lumo is executed.\\n\\nNOTE: The server will be accessible on port \" + fmt.Sprintf(\"%d\", e.config.ServerPort) + \". Make sure your firewall is configured appropriately.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"disable\":\n\t\t// Disable the server\n\t\te.config.EnableServer = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Server has been disabled. The REST server will not start when Lumo is executed.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"port\":\n\t\t// Set the server port\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing port number. Usage: config:server port <port>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tport, err := strconv.Atoi(args[1])\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid port number: %s\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif port < 1024 || port > 65535 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Port number must be between 1024 and 65535\"),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\te.config.ServerPort = port\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Server port set to %d\", port),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"quiet\":\n\t\t// Set quiet mode\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing argument. Usage: config:server quiet on|off\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tswitch strings.ToLower(args[1]) {\n\t\tcase \"on\", \"true\", \"yes\", \"1\":\n\t\t\te.config.ServerQuietOutput = true\n\t\tcase \"off\", \"false\", \"no\", \"0\":\n\t\t\te.config.ServerQuietOutput = false\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid value: %s. Use 'on' or 'off'.\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tquietStr := \"enabled\"\n\t\tif !e.config.ServerQuietOutput {\n\t\t\tquietStr = \"disabled\"\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Server quiet mode %s\", quietStr),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"auth\":\n\t\t// Handle authentication settings\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing argument. Usage: config:server auth enable|disable|password\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tswitch strings.ToLower(args[1]) {\n\t\tcase \"enable\", \"on\", \"true\", \"yes\", \"1\":\n\t\t\te.config.EnableAuth = true\n\t\t\tif err := e.config.Save(); err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Authentication enabled for the REST server.\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tcase \"disable\", \"off\", \"false\", \"no\", \"0\":\n\t\t\te.config.EnableAuth = false\n\t\t\tif err := e.config.Save(); err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Authentication disabled for the REST server. Warning: This makes your API endpoints publicly accessible!\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tcase \"password\":\n\t\t\t// This would be handled by a separate command that prompts for the password\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"To change the password, please use the web interface or the REST API directly.\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid value: %s. Use 'enable', 'disable', or 'password'.\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown server command: %s. Use 'show', 'enable', 'disable', 'port', 'quiet', or 'auth'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleKeyConfig handles API key configuration commands\nfunc (e *Executor) handleKeyConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing key command. Use 'show', 'set', or 'remove'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Use the dedicated key status handler\n\t\treturn e.handleKeyStatus(cmd)\n\tcase \"unused_show\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tgeminiStatus := \"Not set\"\n\t\tif e.config.GeminiAPIKey != \"\" {\n\t\t\tgeminiStatus = \"Set\"\n\t\t}\n\n\t\topenaiStatus := \"Not set\"\n\t\tif e.config.OpenAIAPIKey != \"\" {\n\t\t\topenaiStatus = \"Set\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔑 API Key Status ─────────────────────╮\n\n • Gemini API Key: %s\n • OpenAI API Key: %s\n\n Current provider: %s\n\n╰──────────────────────────────────────────────────────────╯\n`, geminiStatus, openaiStatus, e.config.AIProvider)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set API key\n\t\tconst keyProviderHint = \"Use 'gemini', 'openai', 'claude', or 'openai-compatible'. Note: Ollama doesn't require an API key.\"\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. \" + keyProviderHint,\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif len(args) < 3 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing API key. Usage: config:key set <provider> <key>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tapiKey := args[2]\n\n\t\t// Set the API key, reinitializing the live client if it's the active provider.\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.config.GeminiAPIKey = apiKey\n\t\t\tif e.config.AIProvider == \"gemini\" {\n\t\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\t\t}\n\t\tcase \"openai\":\n\t\t\te.config.OpenAIAPIKey = apiKey\n\t\t\tif e.config.AIProvider == \"openai\" {\n\t\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t\t}\n\t\tcase \"claude\":\n\t\t\te.config.ClaudeAPIKey = apiKey\n\t\t\tif e.config.AIProvider == \"claude\" {\n\t\t\t\te.aiClient = ai.NewClaudeClient(e.config.ClaudeAPIKey, e.config.ClaudeModel)\n\t\t\t}\n\t\tcase \"openai-compatible\":\n\t\t\te.config.CompatibleAPIKey = apiKey\n\t\t\tif e.config.AIProvider == \"openai-compatible\" {\n\t\t\t\te.aiClient = ai.NewOpenAICompatibleClient(e.config.CompatibleAPIKey, e.config.CompatibleModel, e.config.CompatibleBaseURL)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. %s\", provider, keyProviderHint),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been set.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"remove\":\n\t\t// Remove API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini', 'openai', 'claude', or 'openai-compatible'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\n\t\tswitch provider {\n\t\tcase \"gemini\", \"openai\", \"claude\", \"openai-compatible\":\n\t\t\t// valid\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini', 'openai', 'claude', or 'openai-compatible'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if this is the current provider\n\t\tif e.config.AIProvider == provider {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Cannot remove API key for the current provider. Switch providers first with 'config:provider set'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove the API key\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.config.GeminiAPIKey = \"\"\n\t\tcase \"openai\":\n\t\t\te.config.OpenAIAPIKey = \"\"\n\t\tcase \"claude\":\n\t\t\te.config.ClaudeAPIKey = \"\"\n\t\tcase \"openai-compatible\":\n\t\t\te.config.CompatibleAPIKey = \"\"\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been removed.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown key command: %s. Use 'show', 'set', or 'remove'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n"}, "8482b16e3d021eccc1ff19ef8c95dca21971a523": {"t": 1785759562.327178, "n": 28430, "h": "dab7645d70c9cafd", "c": "package executor\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/agnath18K/lumo/pkg/ai\"\n\t\"github.com/agnath18K/lumo/pkg/config\"\n\t\"github.com/agnath18K/lumo/pkg/nlp\"\n)\n\n// getCurrentModel returns the current model based on the provider\nfunc getCurrentModel(cfg *config.Config) string {\n\tif cfg.AIProvider == \"gemini\" {\n\t\treturn cfg.GeminiModel\n\t}\n\treturn cfg.OpenAIModel\n}\n\n// executeConfigCommand handles configuration commands\nfunc (e *Executor) executeConfigCommand(cmd *nlp.Command) (*Result, error) {\n\t// Split the command into parts\n\tparts := strings.Fields(cmd.Intent)\n\tif len(parts) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🔧 Lumo Configuration ─────────────────╮\n\n Commands:\n • config:provider list List available AI providers\n • config:provider show Show current AI provider\n • config:provider set <provider> Set AI provider (gemini/openai/ollama)\n\n • config:model list List available models\n • config:model show Show current model\n • config:model set <model> Set model for current provider\n\n • config:key show Show current API key status\n • config:key set <provider> <key> Set API key for provider\n • config:key remove <provider> Remove API key for provider\n\n • config:ollama show Show current Ollama URL\n • config:ollama set <url> Set Ollama URL\n • config:ollama test Test connection to Ollama server\n\n • config:mode show Show current input mode\n • config:mode ai Set AI-first mode (default)\n • config:mode command Set command-first mode\n\n • config:server show Show current server settings\n • config:server quiet on/off Enable/disable server log messages\n\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\t// Handle different configuration commands\n\tswitch parts[0] {\n\tcase \"provider\":\n\t\treturn e.handleProviderConfig(parts[1:], cmd)\n\tcase \"model\":\n\t\treturn e.handleModelConfig(parts[1:], cmd)\n\tcase \"key\":\n\t\treturn e.handleKeyConfig(parts[1:], cmd)\n\tcase \"ollama\":\n\t\treturn e.handleOllamaConfig(parts[1:], cmd)\n\tcase \"mode\":\n\t\treturn e.handleModeConfig(parts[1:], cmd)\n\tcase \"server\":\n\t\treturn e.handleServerConfig(parts[1:], cmd)\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown configuration command: %s\\nUse 'config:' for help.\", parts[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleProviderConfig handles provider configuration commands\nfunc (e *Executor) handleProviderConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing provider command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// List available providers\n\t\toutput := `\n╭─────────────── 🐦 Available AI Providers ───────────────╮\n\n • gemini (Google's Gemini AI models)\n • openai (OpenAI's GPT models)\n • ollama (Local Ollama models)\n\n Current provider: ` + e.config.AIProvider + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current provider\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current AI provider: %s\", e.config.AIProvider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set provider\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini', 'openai', or 'ollama'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tif provider != \"gemini\" && provider != \"openai\" && provider != \"ollama\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini', 'openai', or 'ollama'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if API key is set for the provider (not needed for Ollama)\n\t\tif provider == \"gemini\" && e.config.GeminiAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for Gemini. Please set an API key first with 'config:key set gemini <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"openai\" && e.config.OpenAIAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for OpenAI. Please set an API key first with 'config:key set openai <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\t// Ollama doesn't need an API key, but we should check if the URL is accessible\n\t\tif provider == \"ollama\" {\n\t\t\t// Try to connect to the Ollama server\n\t\t\tclient := &http.Client{\n\t\t\t\tTimeout: 5 * time.Second,\n\t\t\t}\n\t\t\t_, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Cannot connect to Ollama server at %s. Please make sure Ollama is running and accessible.\", e.config.OllamaURL),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t}\n\n\t\t// Set the provider\n\t\te.config.AIProvider = provider\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Reinitialize the AI client with the new provider\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\tcase \"ollama\":\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\tdefault: // Default to OpenAI\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"AI provider set to: %s\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown provider command: %s. Use 'show' or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModelConfig handles model configuration commands\nfunc (e *Executor) handleModelConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing model command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// Use the dedicated model list handler\n\t\treturn e.handleModelList(cmd)\n\tcase \"unused\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tvar output string\n\t\tif false {\n\t\t\toutput = `\n╭─────────────── 🐦 Available Gemini Models ───────────────╮\n\n • gemini-2.0-flash-lite (Fast, efficient for most queries)\n • gemini-2.0-flash (Balanced performance and quality)\n • gemini-2.0-pro (High quality, more capabilities)\n\n Current model: ` + e.config.GeminiModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t} else {\n\t\t\toutput = `\n╭─────────────── 🐦 Available OpenAI Models ───────────────╮\n\n • gpt-3.5-turbo (Fast, cost-effective)\n • gpt-4o (Advanced capabilities, slower)\n • gpt-4o-mini (Balanced performance and quality)\n\n Current model: ` + e.config.OpenAIModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current model\n\t\tvar currentModel string\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tcurrentModel = e.config.GeminiModel\n\t\tcase \"ollama\":\n\t\t\tcurrentModel = e.config.OllamaModel\n\t\tdefault: // OpenAI\n\t\t\tcurrentModel = e.config.OpenAIModel\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current %s model: %s\", e.config.AIProvider, currentModel),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set model\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing model name. Use 'config:model list' to see available models.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tmodel := args[1]\n\n\t\t// Validate model based on provider\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tvalidModels := []string{\"gemini-2.5-pro\", \"gemini-2.5-flash\", \"gemini-2.5-flash-lite\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid Gemini model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.GeminiModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\n\t\tcase \"ollama\":\n\t\t\t// For Ollama, we need to check if the model exists\n\t\t\tollamaClient := ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t\tmodels, err := ollamaClient.ListModels()\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error checking Ollama models: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range models {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid or unavailable Ollama model: %s. Use 'config:model list' to see available models or 'ollama pull %s' to download it.\", model, model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OllamaModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\n\t\tdefault: // OpenAI\n\t\t\tvalidModels := []string{\"gpt-3.5-turbo\", \"gpt-4o\", \"gpt-4o-mini\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid OpenAI model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OpenAIModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s model set to: %s\", e.config.AIProvider, model),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown model command: %s. Use 'list', 'show', or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleOllamaConfig handles Ollama URL configuration commands\nfunc (e *Executor) handleOllamaConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing Ollama command. Use 'show', 'set', or 'test'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current Ollama URL\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current Ollama URL: %s\", e.config.OllamaURL),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set Ollama URL\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing URL. Usage: config:ollama set <url>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\turl := args[1]\n\n\t\t// Basic URL validation\n\t\tif !strings.HasPrefix(url, \"http://\") && !strings.HasPrefix(url, \"https://\") {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Invalid URL format. URL must start with http:// or https://\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove trailing slash if present\n\t\turl = strings.TrimSuffix(url, \"/\")\n\n\t\t// Set the URL\n\t\te.config.OllamaURL = url\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// If Ollama is the current provider, reinitialize the client\n\t\tif e.config.AIProvider == \"ollama\" {\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Ollama URL set to: %s\", url),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"test\":\n\t\t// Test connection to Ollama server\n\t\tclient := &http.Client{\n\t\t\tTimeout: 5 * time.Second,\n\t\t}\n\t\tresp, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Cannot connect to Ollama server at %s\\nError: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Ollama server returned status code %d\", resp.StatusCode),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Try to parse the response to get available models\n\t\tvar modelsResponse struct {\n\t\t\tModels []struct {\n\t\t\t\tName string `json:\"name\"`\n\t\t\t} `json:\"models\"`\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't read response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\terr = json.Unmarshal(body, &modelsResponse)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't parse response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Count available models\n\t\tmodelCount := len(modelsResponse.Models)\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"✅ Successfully connected to Ollama server at %s\\nFound %d available models\", e.config.OllamaURL, modelCount),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown Ollama command: %s. Use 'show', 'set', or 'test'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModeConfig handles input mode configuration commands\nfunc (e *Executor) handleModeConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing mode command. Use 'show', 'ai', or 'command'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current mode\n\t\tmodeStr := \"AI-first\"\n\t\tif e.config.CommandFirstMode {\n\t\t\tmodeStr = \"Command-first\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔧 Input Mode ─────────────────────────╮\n\n Current input mode: %s\n\n • AI-first mode: Treats all input as AI queries by default\n unless it starts with a specific command prefix.\n\n • Command-first mode: Treats input as shell commands if it\n looks like a command, otherwise as an AI query.\n\n╰──────────────────────────────────────────────────────────╯\n`, modeStr)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"ai\":\n\t\t// Set AI-first mode\n\t\te.config.CommandFirstMode = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to AI-first. Lumo will now treat all input as AI queries by default unless it starts with a specific command prefix.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"command\":\n\t\t// Set Command-first mode\n\t\te.config.CommandFirstMode = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to Command-first. Lumo will now treat input as shell commands if it looks like a command, otherwise as an AI query.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown mode command: %s. Use 'show', 'ai', or 'command'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleServerConfig handles server configuration commands\nfunc (e *Executor) handleServerConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🖥️ Server Configuration ─────────────────╮\n\n Commands:\n • config:server show Show current server settings\n • config:server enable Enable the REST server\n • config:server disable Disable the REST server\n • config:server port <port> Set the server port\n • config:server quiet on Enable quiet mode (suppress logs)\n • config:server quiet off Disable quiet mode (show logs)\n • config:server auth enable Enable authentication\n • config:server auth disable Disable authentication\n • config:server auth password Change the admin password\n\n Configure these settings in ~/.config/lumo/config.json\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current server settings\n\t\tenabledStr := \"Disabled\"\n\t\tif e.config.EnableServer {\n\t\t\tenabledStr = \"Enabled\"\n\t\t}\n\n\t\tquietStr := \"Disabled\"\n\t\tif e.config.ServerQuietOutput {\n\t\t\tquietStr = \"Enabled\"\n\t\t}\n\n\t\tauthStr := \"Disabled\"\n\t\tif e.config.EnableAuth {\n\t\t\tauthStr = \"Enabled\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🖥️ Server Settings ───────────────────╮\n\n • Server Status: %s\n • Server Port: %d\n • Quiet Output: %s\n • Authentication: %s\n • Token Expiration: %d hours\n • Refresh Token Expiration: %d days\n\n Configure these settings in ~/.config/lumo/config.json\n or use the commands below.\n\n Commands:\n • config:server enable Enable the REST server\n • config:server disable Disable the REST server\n • config:server port <port> Set the server port\n • config:server quiet on Enable quiet mode\n • config:server quiet off Disable quiet mode\n • config:server auth enable Enable authentication\n • config:server auth disable Disable authentication\n • config:server auth password Change the admin password\n╰──────────────────────────────────────────────────────────╯\n`, enabledStr, e.config.ServerPort, quietStr, authStr, e.config.TokenExpirationHours, e.config.RefreshExpirationDays)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"enable\":\n\t\t// Enable the server\n\t\te.config.EnableServer = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Server has been enabled. The REST server will now start when Lumo is executed.\\n\\nNOTE: The server will be accessible on port \" + fmt.Sprintf(\"%d\", e.config.ServerPort) + \". Make sure your firewall is configured appropriately.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"disable\":\n\t\t// Disable the server\n\t\te.config.EnableServer = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Server has been disabled. The REST server will not start when Lumo is executed.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"port\":\n\t\t// Set the server port\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing port number. Usage: config:server port <port>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tport, err := strconv.Atoi(args[1])\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid port number: %s\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif port < 1024 || port > 65535 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Port number must be between 1024 and 65535\"),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\te.config.ServerPort = port\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Server port set to %d\", port),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"quiet\":\n\t\t// Set quiet mode\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing argument. Usage: config:server quiet on|off\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tswitch strings.ToLower(args[1]) {\n\t\tcase \"on\", \"true\", \"yes\", \"1\":\n\t\t\te.config.ServerQuietOutput = true\n\t\tcase \"off\", \"false\", \"no\", \"0\":\n\t\t\te.config.ServerQuietOutput = false\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid value: %s. Use 'on' or 'off'.\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tquietStr := \"enabled\"\n\t\tif !e.config.ServerQuietOutput {\n\t\t\tquietStr = \"disabled\"\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Server quiet mode %s\", quietStr),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"auth\":\n\t\t// Handle authentication settings\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing argument. Usage: config:server auth enable|disable|password\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tswitch strings.ToLower(args[1]) {\n\t\tcase \"enable\", \"on\", \"true\", \"yes\", \"1\":\n\t\t\te.config.EnableAuth = true\n\t\t\tif err := e.config.Save(); err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Authentication enabled for the REST server.\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tcase \"disable\", \"off\", \"false\", \"no\", \"0\":\n\t\t\te.config.EnableAuth = false\n\t\t\tif err := e.config.Save(); err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Authentication disabled for the REST server. Warning: This makes your API endpoints publicly accessible!\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tcase \"password\":\n\t\t\t// This would be handled by a separate command that prompts for the password\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"To change the password, please use the web interface or the REST API directly.\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid value: %s. Use 'enable', 'disable', or 'password'.\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown server command: %s. Use 'show', 'enable', 'disable', 'port', 'quiet', or 'auth'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleKeyConfig handles API key configuration commands\nfunc (e *Executor) handleKeyConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing key command. Use 'show', 'set', or 'remove'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Use the dedicated key status handler\n\t\treturn e.handleKeyStatus(cmd)\n\tcase \"unused_show\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tgeminiStatus := \"Not set\"\n\t\tif e.config.GeminiAPIKey != \"\" {\n\t\t\tgeminiStatus = \"Set\"\n\t\t}\n\n\t\topenaiStatus := \"Not set\"\n\t\tif e.config.OpenAIAPIKey != \"\" {\n\t\t\topenaiStatus = \"Set\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔑 API Key Status ─────────────────────╮\n\n • Gemini API Key: %s\n • OpenAI API Key: %s\n\n Current provider: %s\n\n╰──────────────────────────────────────────────────────────╯\n`, geminiStatus, openaiStatus, e.config.AIProvider)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif len(args) < 3 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing API key. Usage: config:key set <provider> <key>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tapiKey := args[2]\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Set the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"gemini\" {\n\t\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\t\t}\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"openai\" {\n\t\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t\t}\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been set.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"remove\":\n\t\t// Remove API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if this is the current provider\n\t\tif e.config.AIProvider == provider {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Cannot remove API key for the current provider. Switch providers first with 'config:provider set'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = \"\"\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = \"\"\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been removed.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown key command: %s. Use 'show', 'set', or 'remove'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n"}, "17acf17d8e6d6c498a9676affad08f26def34040": {"t": 1785759564.0127954, "n": 28430, "h": "3cff6599fdcd3a1b", "c": "package executor\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/agnath18K/lumo/pkg/ai\"\n\t\"github.com/agnath18K/lumo/pkg/config\"\n\t\"github.com/agnath18K/lumo/pkg/nlp\"\n)\n\n// getCurrentModel returns the current model based on the provider\nfunc getCurrentModel(cfg *config.Config) string {\n\tif cfg.AIProvider == \"gemini\" {\n\t\treturn cfg.GeminiModel\n\t}\n\treturn cfg.OpenAIModel\n}\n\n// executeConfigCommand handles configuration commands\nfunc (e *Executor) executeConfigCommand(cmd *nlp.Command) (*Result, error) {\n\t// Split the command into parts\n\tparts := strings.Fields(cmd.Intent)\n\tif len(parts) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🔧 Lumo Configuration ─────────────────╮\n\n Commands:\n • config:provider list List available AI providers\n • config:provider show Show current AI provider\n • config:provider set <provider> Set AI provider (gemini/openai/ollama)\n\n • config:model list List available models\n • config:model show Show current model\n • config:model set <model> Set model for current provider\n\n • config:key show Show current API key status\n • config:key set <provider> <key> Set API key for provider\n • config:key remove <provider> Remove API key for provider\n\n • config:ollama show Show current Ollama URL\n • config:ollama set <url> Set Ollama URL\n • config:ollama test Test connection to Ollama server\n\n • config:mode show Show current input mode\n • config:mode ai Set AI-first mode (default)\n • config:mode command Set command-first mode\n\n • config:server show Show current server settings\n • config:server quiet on/off Enable/disable server log messages\n\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\t// Handle different configuration commands\n\tswitch parts[0] {\n\tcase \"provider\":\n\t\treturn e.handleProviderConfig(parts[1:], cmd)\n\tcase \"model\":\n\t\treturn e.handleModelConfig(parts[1:], cmd)\n\tcase \"key\":\n\t\treturn e.handleKeyConfig(parts[1:], cmd)\n\tcase \"ollama\":\n\t\treturn e.handleOllamaConfig(parts[1:], cmd)\n\tcase \"mode\":\n\t\treturn e.handleModeConfig(parts[1:], cmd)\n\tcase \"server\":\n\t\treturn e.handleServerConfig(parts[1:], cmd)\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown configuration command: %s\\nUse 'config:' for help.\", parts[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleProviderConfig handles provider configuration commands\nfunc (e *Executor) handleProviderConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing provider command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// List available providers\n\t\toutput := `\n╭─────────────── 🐦 Available AI Providers ───────────────╮\n\n • gemini (Google's Gemini AI models)\n • openai (OpenAI's GPT models)\n • ollama (Local Ollama models)\n\n Current provider: ` + e.config.AIProvider + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current provider\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current AI provider: %s\", e.config.AIProvider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set provider\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini', 'openai', or 'ollama'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tif provider != \"gemini\" && provider != \"openai\" && provider != \"ollama\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini', 'openai', or 'ollama'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if API key is set for the provider (not needed for Ollama)\n\t\tif provider == \"gemini\" && e.config.GeminiAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for Gemini. Please set an API key first with 'config:key set gemini <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"openai\" && e.config.OpenAIAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for OpenAI. Please set an API key first with 'config:key set openai <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\t// Ollama doesn't need an API key, but we should check if the URL is accessible\n\t\tif provider == \"ollama\" {\n\t\t\t// Try to connect to the Ollama server\n\t\t\tclient := &http.Client{\n\t\t\t\tTimeout: 5 * time.Second,\n\t\t\t}\n\t\t\t_, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Cannot connect to Ollama server at %s. Please make sure Ollama is running and accessible.\", e.config.OllamaURL),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t}\n\n\t\t// Set the provider\n\t\te.config.AIProvider = provider\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Reinitialize the AI client with the new provider\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\tcase \"ollama\":\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\tdefault: // Default to OpenAI\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"AI provider set to: %s\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown provider command: %s. Use 'show' or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModelConfig handles model configuration commands\nfunc (e *Executor) handleModelConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing model command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// Use the dedicated model list handler\n\t\treturn e.handleModelList(cmd)\n\tcase \"unused\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tvar output string\n\t\tif false {\n\t\t\toutput = `\n╭─────────────── 🐦 Available Gemini Models ───────────────╮\n\n • gemini-2.0-flash-lite (Fast, efficient for most queries)\n • gemini-2.0-flash (Balanced performance and quality)\n • gemini-2.0-pro (High quality, more capabilities)\n\n Current model: ` + e.config.GeminiModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t} else {\n\t\t\toutput = `\n╭─────────────── 🐦 Available OpenAI Models ───────────────╮\n\n • gpt-3.5-turbo (Fast, cost-effective)\n • gpt-4o (Advanced capabilities, slower)\n • gpt-4o-mini (Balanced performance and quality)\n\n Current model: ` + e.config.OpenAIModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current model\n\t\tvar currentModel string\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tcurrentModel = e.config.GeminiModel\n\t\tcase \"ollama\":\n\t\t\tcurrentModel = e.config.OllamaModel\n\t\tdefault: // OpenAI\n\t\t\tcurrentModel = e.config.OpenAIModel\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current %s model: %s\", e.config.AIProvider, currentModel),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set model\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing model name. Use 'config:model list' to see available models.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tmodel := args[1]\n\n\t\t// Validate model based on provider\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tvalidModels := []string{\"gemini-2.0-flash-lite\", \"gemini-2.0-flash\", \"gemini-2.0-pro\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid Gemini model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.GeminiModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\n\t\tcase \"ollama\":\n\t\t\t// For Ollama, we need to check if the model exists\n\t\t\tollamaClient := ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t\tmodels, err := ollamaClient.ListModels()\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error checking Ollama models: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range models {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid or unavailable Ollama model: %s. Use 'config:model list' to see available models or 'ollama pull %s' to download it.\", model, model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OllamaModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\n\t\tdefault: // OpenAI\n\t\t\tvalidModels := []string{\"gpt-3.5-turbo\", \"gpt-4o\", \"gpt-4o-mini\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid OpenAI model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OpenAIModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s model set to: %s\", e.config.AIProvider, model),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown model command: %s. Use 'list', 'show', or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleOllamaConfig handles Ollama URL configuration commands\nfunc (e *Executor) handleOllamaConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing Ollama command. Use 'show', 'set', or 'test'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current Ollama URL\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current Ollama URL: %s\", e.config.OllamaURL),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set Ollama URL\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing URL. Usage: config:ollama set <url>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\turl := args[1]\n\n\t\t// Basic URL validation\n\t\tif !strings.HasPrefix(url, \"http://\") && !strings.HasPrefix(url, \"https://\") {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Invalid URL format. URL must start with http:// or https://\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove trailing slash if present\n\t\turl = strings.TrimSuffix(url, \"/\")\n\n\t\t// Set the URL\n\t\te.config.OllamaURL = url\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// If Ollama is the current provider, reinitialize the client\n\t\tif e.config.AIProvider == \"ollama\" {\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Ollama URL set to: %s\", url),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"test\":\n\t\t// Test connection to Ollama server\n\t\tclient := &http.Client{\n\t\t\tTimeout: 5 * time.Second,\n\t\t}\n\t\tresp, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Cannot connect to Ollama server at %s\\nError: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Ollama server returned status code %d\", resp.StatusCode),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Try to parse the response to get available models\n\t\tvar modelsResponse struct {\n\t\t\tModels []struct {\n\t\t\t\tName string `json:\"name\"`\n\t\t\t} `json:\"models\"`\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't read response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\terr = json.Unmarshal(body, &modelsResponse)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't parse response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Count available models\n\t\tmodelCount := len(modelsResponse.Models)\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"✅ Successfully connected to Ollama server at %s\\nFound %d available models\", e.config.OllamaURL, modelCount),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown Ollama command: %s. Use 'show', 'set', or 'test'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModeConfig handles input mode configuration commands\nfunc (e *Executor) handleModeConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing mode command. Use 'show', 'ai', or 'command'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current mode\n\t\tmodeStr := \"AI-first\"\n\t\tif e.config.CommandFirstMode {\n\t\t\tmodeStr = \"Command-first\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔧 Input Mode ─────────────────────────╮\n\n Current input mode: %s\n\n • AI-first mode: Treats all input as AI queries by default\n unless it starts with a specific command prefix.\n\n • Command-first mode: Treats input as shell commands if it\n looks like a command, otherwise as an AI query.\n\n╰──────────────────────────────────────────────────────────╯\n`, modeStr)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"ai\":\n\t\t// Set AI-first mode\n\t\te.config.CommandFirstMode = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to AI-first. Lumo will now treat all input as AI queries by default unless it starts with a specific command prefix.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"command\":\n\t\t// Set Command-first mode\n\t\te.config.CommandFirstMode = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to Command-first. Lumo will now treat input as shell commands if it looks like a command, otherwise as an AI query.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown mode command: %s. Use 'show', 'ai', or 'command'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleServerConfig handles server configuration commands\nfunc (e *Executor) handleServerConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🖥️ Server Configuration ─────────────────╮\n\n Commands:\n • config:server show Show current server settings\n • config:server enable Enable the REST server\n • config:server disable Disable the REST server\n • config:server port <port> Set the server port\n • config:server quiet on Enable quiet mode (suppress logs)\n • config:server quiet off Disable quiet mode (show logs)\n • config:server auth enable Enable authentication\n • config:server auth disable Disable authentication\n • config:server auth password Change the admin password\n\n Configure these settings in ~/.config/lumo/config.json\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current server settings\n\t\tenabledStr := \"Disabled\"\n\t\tif e.config.EnableServer {\n\t\t\tenabledStr = \"Enabled\"\n\t\t}\n\n\t\tquietStr := \"Disabled\"\n\t\tif e.config.ServerQuietOutput {\n\t\t\tquietStr = \"Enabled\"\n\t\t}\n\n\t\tauthStr := \"Disabled\"\n\t\tif e.config.EnableAuth {\n\t\t\tauthStr = \"Enabled\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🖥️ Server Settings ───────────────────╮\n\n • Server Status: %s\n • Server Port: %d\n • Quiet Output: %s\n • Authentication: %s\n • Token Expiration: %d hours\n • Refresh Token Expiration: %d days\n\n Configure these settings in ~/.config/lumo/config.json\n or use the commands below.\n\n Commands:\n • config:server enable Enable the REST server\n • config:server disable Disable the REST server\n • config:server port <port> Set the server port\n • config:server quiet on Enable quiet mode\n • config:server quiet off Disable quiet mode\n • config:server auth enable Enable authentication\n • config:server auth disable Disable authentication\n • config:server auth password Change the admin password\n╰──────────────────────────────────────────────────────────╯\n`, enabledStr, e.config.ServerPort, quietStr, authStr, e.config.TokenExpirationHours, e.config.RefreshExpirationDays)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"enable\":\n\t\t// Enable the server\n\t\te.config.EnableServer = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Server has been enabled. The REST server will now start when Lumo is executed.\\n\\nNOTE: The server will be accessible on port \" + fmt.Sprintf(\"%d\", e.config.ServerPort) + \". Make sure your firewall is configured appropriately.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"disable\":\n\t\t// Disable the server\n\t\te.config.EnableServer = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Server has been disabled. The REST server will not start when Lumo is executed.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"port\":\n\t\t// Set the server port\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing port number. Usage: config:server port <port>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tport, err := strconv.Atoi(args[1])\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid port number: %s\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif port < 1024 || port > 65535 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Port number must be between 1024 and 65535\"),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\te.config.ServerPort = port\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Server port set to %d\", port),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"quiet\":\n\t\t// Set quiet mode\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing argument. Usage: config:server quiet on|off\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tswitch strings.ToLower(args[1]) {\n\t\tcase \"on\", \"true\", \"yes\", \"1\":\n\t\t\te.config.ServerQuietOutput = true\n\t\tcase \"off\", \"false\", \"no\", \"0\":\n\t\t\te.config.ServerQuietOutput = false\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid value: %s. Use 'on' or 'off'.\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tquietStr := \"enabled\"\n\t\tif !e.config.ServerQuietOutput {\n\t\t\tquietStr = \"disabled\"\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Server quiet mode %s\", quietStr),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"auth\":\n\t\t// Handle authentication settings\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing argument. Usage: config:server auth enable|disable|password\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tswitch strings.ToLower(args[1]) {\n\t\tcase \"enable\", \"on\", \"true\", \"yes\", \"1\":\n\t\t\te.config.EnableAuth = true\n\t\t\tif err := e.config.Save(); err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Authentication enabled for the REST server.\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tcase \"disable\", \"off\", \"false\", \"no\", \"0\":\n\t\t\te.config.EnableAuth = false\n\t\t\tif err := e.config.Save(); err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Authentication disabled for the REST server. Warning: This makes your API endpoints publicly accessible!\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tcase \"password\":\n\t\t\t// This would be handled by a separate command that prompts for the password\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"To change the password, please use the web interface or the REST API directly.\",\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid value: %s. Use 'enable', 'disable', or 'password'.\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown server command: %s. Use 'show', 'enable', 'disable', 'port', 'quiet', or 'auth'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleKeyConfig handles API key configuration commands\nfunc (e *Executor) handleKeyConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing key command. Use 'show', 'set', or 'remove'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Use the dedicated key status handler\n\t\treturn e.handleKeyStatus(cmd)\n\tcase \"unused_show\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tgeminiStatus := \"Not set\"\n\t\tif e.config.GeminiAPIKey != \"\" {\n\t\t\tgeminiStatus = \"Set\"\n\t\t}\n\n\t\topenaiStatus := \"Not set\"\n\t\tif e.config.OpenAIAPIKey != \"\" {\n\t\t\topenaiStatus = \"Set\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔑 API Key Status ─────────────────────╮\n\n • Gemini API Key: %s\n • OpenAI API Key: %s\n\n Current provider: %s\n\n╰──────────────────────────────────────────────────────────╯\n`, geminiStatus, openaiStatus, e.config.AIProvider)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif len(args) < 3 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing API key. Usage: config:key set <provider> <key>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tapiKey := args[2]\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Set the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"gemini\" {\n\t\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\t\t}\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"openai\" {\n\t\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t\t}\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been set.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"remove\":\n\t\t// Remove API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if this is the current provider\n\t\tif e.config.AIProvider == provider {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Cannot remove API key for the current provider. Switch providers first with 'config:provider set'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = \"\"\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = \"\"\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been removed.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown key command: %s. Use 'show', 'set', or 'remove'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n"}, "b11c06a0509bb68b27b59e7bea7678d9dd663396": {"t": 1785759565.5113528, "n": 24482, "h": "5d063ce7636fd2fa", "c": "package executor\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/agnath18K/lumo/pkg/ai\"\n\t\"github.com/agnath18K/lumo/pkg/config\"\n\t\"github.com/agnath18K/lumo/pkg/nlp\"\n)\n\n// getCurrentModel returns the current model based on the provider\nfunc getCurrentModel(cfg *config.Config) string {\n\tif cfg.AIProvider == \"gemini\" {\n\t\treturn cfg.GeminiModel\n\t}\n\treturn cfg.OpenAIModel\n}\n\n// executeConfigCommand handles configuration commands\nfunc (e *Executor) executeConfigCommand(cmd *nlp.Command) (*Result, error) {\n\t// Split the command into parts\n\tparts := strings.Fields(cmd.Intent)\n\tif len(parts) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🔧 Lumo Configuration ─────────────────╮\n\n Commands:\n • config:provider list List available AI providers\n • config:provider show Show current AI provider\n • config:provider set <provider> Set AI provider (gemini/openai/ollama)\n\n • config:model list List available models\n • config:model show Show current model\n • config:model set <model> Set model for current provider\n\n • config:key show Show current API key status\n • config:key set <provider> <key> Set API key for provider\n • config:key remove <provider> Remove API key for provider\n\n • config:ollama show Show current Ollama URL\n • config:ollama set <url> Set Ollama URL\n • config:ollama test Test connection to Ollama server\n\n • config:mode show Show current input mode\n • config:mode ai Set AI-first mode (default)\n • config:mode command Set command-first mode\n\n • config:server show Show current server settings\n • config:server quiet on/off Enable/disable server log messages\n\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\t// Handle different configuration commands\n\tswitch parts[0] {\n\tcase \"provider\":\n\t\treturn e.handleProviderConfig(parts[1:], cmd)\n\tcase \"model\":\n\t\treturn e.handleModelConfig(parts[1:], cmd)\n\tcase \"key\":\n\t\treturn e.handleKeyConfig(parts[1:], cmd)\n\tcase \"ollama\":\n\t\treturn e.handleOllamaConfig(parts[1:], cmd)\n\tcase \"mode\":\n\t\treturn e.handleModeConfig(parts[1:], cmd)\n\tcase \"server\":\n\t\treturn e.handleServerConfig(parts[1:], cmd)\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown configuration command: %s\\nUse 'config:' for help.\", parts[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleProviderConfig handles provider configuration commands\nfunc (e *Executor) handleProviderConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing provider command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// List available providers\n\t\toutput := `\n╭─────────────── 🐦 Available AI Providers ───────────────╮\n\n • gemini (Google's Gemini AI models)\n • openai (OpenAI's GPT models)\n • ollama (Local Ollama models)\n\n Current provider: ` + e.config.AIProvider + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current provider\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current AI provider: %s\", e.config.AIProvider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set provider\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini', 'openai', or 'ollama'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tif provider != \"gemini\" && provider != \"openai\" && provider != \"ollama\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini', 'openai', or 'ollama'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if API key is set for the provider (not needed for Ollama)\n\t\tif provider == \"gemini\" && e.config.GeminiAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for Gemini. Please set an API key first with 'config:key set gemini <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"openai\" && e.config.OpenAIAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for OpenAI. Please set an API key first with 'config:key set openai <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\t// Ollama doesn't need an API key, but we should check if the URL is accessible\n\t\tif provider == \"ollama\" {\n\t\t\t// Try to connect to the Ollama server\n\t\t\tclient := &http.Client{\n\t\t\t\tTimeout: 5 * time.Second,\n\t\t\t}\n\t\t\t_, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Cannot connect to Ollama server at %s. Please make sure Ollama is running and accessible.\", e.config.OllamaURL),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t}\n\n\t\t// Set the provider\n\t\te.config.AIProvider = provider\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Reinitialize the AI client with the new provider\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\tcase \"ollama\":\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\tdefault: // Default to OpenAI\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"AI provider set to: %s\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown provider command: %s. Use 'show' or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModelConfig handles model configuration commands\nfunc (e *Executor) handleModelConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing model command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// Use the dedicated model list handler\n\t\treturn e.handleModelList(cmd)\n\tcase \"unused\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tvar output string\n\t\tif false {\n\t\t\toutput = `\n╭─────────────── 🐦 Available Gemini Models ───────────────╮\n\n • gemini-2.0-flash-lite (Fast, efficient for most queries)\n • gemini-2.0-flash (Balanced performance and quality)\n • gemini-2.0-pro (High quality, more capabilities)\n\n Current model: ` + e.config.GeminiModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t} else {\n\t\t\toutput = `\n╭─────────────── 🐦 Available OpenAI Models ───────────────╮\n\n • gpt-3.5-turbo (Fast, cost-effective)\n • gpt-4o (Advanced capabilities, slower)\n • gpt-4o-mini (Balanced performance and quality)\n\n Current model: ` + e.config.OpenAIModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current model\n\t\tvar currentModel string\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tcurrentModel = e.config.GeminiModel\n\t\tcase \"ollama\":\n\t\t\tcurrentModel = e.config.OllamaModel\n\t\tdefault: // OpenAI\n\t\t\tcurrentModel = e.config.OpenAIModel\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current %s model: %s\", e.config.AIProvider, currentModel),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set model\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing model name. Use 'config:model list' to see available models.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tmodel := args[1]\n\n\t\t// Validate model based on provider\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tvalidModels := []string{\"gemini-2.0-flash-lite\", \"gemini-2.0-flash\", \"gemini-2.0-pro\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid Gemini model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.GeminiModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\n\t\tcase \"ollama\":\n\t\t\t// For Ollama, we need to check if the model exists\n\t\t\tollamaClient := ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t\tmodels, err := ollamaClient.ListModels()\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error checking Ollama models: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range models {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid or unavailable Ollama model: %s. Use 'config:model list' to see available models or 'ollama pull %s' to download it.\", model, model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OllamaModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\n\t\tdefault: // OpenAI\n\t\t\tvalidModels := []string{\"gpt-3.5-turbo\", \"gpt-4o\", \"gpt-4o-mini\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid OpenAI model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OpenAIModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s model set to: %s\", e.config.AIProvider, model),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown model command: %s. Use 'list', 'show', or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleOllamaConfig handles Ollama URL configuration commands\nfunc (e *Executor) handleOllamaConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing Ollama command. Use 'show', 'set', or 'test'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current Ollama URL\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current Ollama URL: %s\", e.config.OllamaURL),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set Ollama URL\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing URL. Usage: config:ollama set <url>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\turl := args[1]\n\n\t\t// Basic URL validation\n\t\tif !strings.HasPrefix(url, \"http://\") && !strings.HasPrefix(url, \"https://\") {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Invalid URL format. URL must start with http:// or https://\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove trailing slash if present\n\t\turl = strings.TrimSuffix(url, \"/\")\n\n\t\t// Set the URL\n\t\te.config.OllamaURL = url\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// If Ollama is the current provider, reinitialize the client\n\t\tif e.config.AIProvider == \"ollama\" {\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Ollama URL set to: %s\", url),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"test\":\n\t\t// Test connection to Ollama server\n\t\tclient := &http.Client{\n\t\t\tTimeout: 5 * time.Second,\n\t\t}\n\t\tresp, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Cannot connect to Ollama server at %s\\nError: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Ollama server returned status code %d\", resp.StatusCode),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Try to parse the response to get available models\n\t\tvar modelsResponse struct {\n\t\t\tModels []struct {\n\t\t\t\tName string `json:\"name\"`\n\t\t\t} `json:\"models\"`\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't read response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\terr = json.Unmarshal(body, &modelsResponse)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't parse response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Count available models\n\t\tmodelCount := len(modelsResponse.Models)\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"✅ Successfully connected to Ollama server at %s\\nFound %d available models\", e.config.OllamaURL, modelCount),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown Ollama command: %s. Use 'show', 'set', or 'test'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModeConfig handles input mode configuration commands\nfunc (e *Executor) handleModeConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing mode command. Use 'show', 'ai', or 'command'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current mode\n\t\tmodeStr := \"AI-first\"\n\t\tif e.config.CommandFirstMode {\n\t\t\tmodeStr = \"Command-first\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔧 Input Mode ─────────────────────────╮\n\n Current input mode: %s\n\n • AI-first mode: Treats all input as AI queries by default\n unless it starts with a specific command prefix.\n\n • Command-first mode: Treats input as shell commands if it\n looks like a command, otherwise as an AI query.\n\n╰──────────────────────────────────────────────────────────╯\n`, modeStr)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"ai\":\n\t\t// Set AI-first mode\n\t\te.config.CommandFirstMode = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to AI-first. Lumo will now treat all input as AI queries by default unless it starts with a specific command prefix.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"command\":\n\t\t// Set Command-first mode\n\t\te.config.CommandFirstMode = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to Command-first. Lumo will now treat input as shell commands if it looks like a command, otherwise as an AI query.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown mode command: %s. Use 'show', 'ai', or 'command'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleServerConfig handles server configuration commands\nfunc (e *Executor) handleServerConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing server command. Use 'show', 'enable', 'disable', or 'quiet'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current server settings\n\t\tenabledStr := \"Disabled\"\n\t\tif e.config.EnableServer {\n\t\t\tenabledStr = \"Enabled\"\n\t\t}\n\n\t\tquietStr := \"Disabled\"\n\t\tif e.config.ServerQuietOutput {\n\t\t\tquietStr = \"Enabled\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🖥️ Server Settings ───────────────────╮\n\n • Server Status: %s\n • Server Port: %d\n • Quiet Output: %s\n\n Configure these settings in ~/.config/lumo/config.json\n or use the commands below.\n\n╰──────────────────────────────────────────────────────────╯\n`, enabledStr, e.config.ServerPort, quietStr)\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"enable\":\n\t\t// Enable the server\n\t\te.config.EnableServer = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Server has been enabled. The REST server will now start when Lumo is executed.\\n\\nNOTE: The server will be accessible on port \" + fmt.Sprintf(\"%d\", e.config.ServerPort) + \". Make sure your firewall is configured appropriately.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"disable\":\n\t\t// Disable the server\n\t\te.config.EnableServer = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Server has been disabled. The REST server will not start when Lumo is executed.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"quiet\":\n\t\t// Set quiet mode\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing argument. Use 'on' or 'off'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tswitch args[1] {\n\t\tcase \"on\":\n\t\t\t// Enable quiet mode\n\t\t\te.config.ServerQuietOutput = true\n\t\tcase \"off\":\n\t\t\t// Disable quiet mode\n\t\t\te.config.ServerQuietOutput = false\n\t\tdefault:\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid argument: %s. Use 'on' or 'off'.\", args[1]),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tquietStr := \"disabled\"\n\t\tif e.config.ServerQuietOutput {\n\t\t\tquietStr = \"enabled\"\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Server quiet output %s. Server log messages will %s be displayed.\", quietStr, map[bool]string{true: \"not\", false: \"\"}[e.config.ServerQuietOutput]),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown server command: %s. Use 'show', 'enable', 'disable', or 'quiet'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleKeyConfig handles API key configuration commands\nfunc (e *Executor) handleKeyConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing key command. Use 'show', 'set', or 'remove'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Use the dedicated key status handler\n\t\treturn e.handleKeyStatus(cmd)\n\tcase \"unused_show\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tgeminiStatus := \"Not set\"\n\t\tif e.config.GeminiAPIKey != \"\" {\n\t\t\tgeminiStatus = \"Set\"\n\t\t}\n\n\t\topenaiStatus := \"Not set\"\n\t\tif e.config.OpenAIAPIKey != \"\" {\n\t\t\topenaiStatus = \"Set\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔑 API Key Status ─────────────────────╮\n\n • Gemini API Key: %s\n • OpenAI API Key: %s\n\n Current provider: %s\n\n╰──────────────────────────────────────────────────────────╯\n`, geminiStatus, openaiStatus, e.config.AIProvider)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif len(args) < 3 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing API key. Usage: config:key set <provider> <key>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tapiKey := args[2]\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Set the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"gemini\" {\n\t\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\t\t}\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"openai\" {\n\t\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t\t}\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been set.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"remove\":\n\t\t// Remove API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if this is the current provider\n\t\tif e.config.AIProvider == provider {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Cannot remove API key for the current provider. Switch providers first with 'config:provider set'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = \"\"\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = \"\"\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been removed.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown key command: %s. Use 'show', 'set', or 'remove'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n"}, "99fe09ad3f234252ec38db762bfc063b8c7456e5": {"t": 1785759567.2221506, "n": 20843, "h": "75449e684b24ffba", "c": "package executor\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/agnath18K/lumo/pkg/ai\"\n\t\"github.com/agnath18K/lumo/pkg/config\"\n\t\"github.com/agnath18K/lumo/pkg/nlp\"\n)\n\n// getCurrentModel returns the current model based on the provider\nfunc getCurrentModel(cfg *config.Config) string {\n\tif cfg.AIProvider == \"gemini\" {\n\t\treturn cfg.GeminiModel\n\t}\n\treturn cfg.OpenAIModel\n}\n\n// executeConfigCommand handles configuration commands\nfunc (e *Executor) executeConfigCommand(cmd *nlp.Command) (*Result, error) {\n\t// Split the command into parts\n\tparts := strings.Fields(cmd.Intent)\n\tif len(parts) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🔧 Lumo Configuration ─────────────────╮\n\n Commands:\n • config:provider list List available AI providers\n • config:provider show Show current AI provider\n • config:provider set <provider> Set AI provider (gemini/openai/ollama)\n\n • config:model list List available models\n • config:model show Show current model\n • config:model set <model> Set model for current provider\n\n • config:key show Show current API key status\n • config:key set <provider> <key> Set API key for provider\n • config:key remove <provider> Remove API key for provider\n\n • config:ollama show Show current Ollama URL\n • config:ollama set <url> Set Ollama URL\n • config:ollama test Test connection to Ollama server\n\n • config:mode show Show current input mode\n • config:mode ai Set AI-first mode (default)\n • config:mode command Set command-first mode\n\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\t// Handle different configuration commands\n\tswitch parts[0] {\n\tcase \"provider\":\n\t\treturn e.handleProviderConfig(parts[1:], cmd)\n\tcase \"model\":\n\t\treturn e.handleModelConfig(parts[1:], cmd)\n\tcase \"key\":\n\t\treturn e.handleKeyConfig(parts[1:], cmd)\n\tcase \"ollama\":\n\t\treturn e.handleOllamaConfig(parts[1:], cmd)\n\tcase \"mode\":\n\t\treturn e.handleModeConfig(parts[1:], cmd)\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown configuration command: %s\\nUse 'config:' for help.\", parts[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleProviderConfig handles provider configuration commands\nfunc (e *Executor) handleProviderConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing provider command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// List available providers\n\t\toutput := `\n╭─────────────── 🐦 Available AI Providers ───────────────╮\n\n • gemini (Google's Gemini AI models)\n • openai (OpenAI's GPT models)\n • ollama (Local Ollama models)\n\n Current provider: ` + e.config.AIProvider + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current provider\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current AI provider: %s\", e.config.AIProvider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set provider\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini', 'openai', or 'ollama'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tif provider != \"gemini\" && provider != \"openai\" && provider != \"ollama\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini', 'openai', or 'ollama'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if API key is set for the provider (not needed for Ollama)\n\t\tif provider == \"gemini\" && e.config.GeminiAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for Gemini. Please set an API key first with 'config:key set gemini <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"openai\" && e.config.OpenAIAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for OpenAI. Please set an API key first with 'config:key set openai <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\t// Ollama doesn't need an API key, but we should check if the URL is accessible\n\t\tif provider == \"ollama\" {\n\t\t\t// Try to connect to the Ollama server\n\t\t\tclient := &http.Client{\n\t\t\t\tTimeout: 5 * time.Second,\n\t\t\t}\n\t\t\t_, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Cannot connect to Ollama server at %s. Please make sure Ollama is running and accessible.\", e.config.OllamaURL),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t}\n\n\t\t// Set the provider\n\t\te.config.AIProvider = provider\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Reinitialize the AI client with the new provider\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\tcase \"ollama\":\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\tdefault: // Default to OpenAI\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"AI provider set to: %s\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown provider command: %s. Use 'show' or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModelConfig handles model configuration commands\nfunc (e *Executor) handleModelConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing model command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// Use the dedicated model list handler\n\t\treturn e.handleModelList(cmd)\n\tcase \"unused\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tvar output string\n\t\tif false {\n\t\t\toutput = `\n╭─────────────── 🐦 Available Gemini Models ───────────────╮\n\n • gemini-2.0-flash-lite (Fast, efficient for most queries)\n • gemini-2.0-flash (Balanced performance and quality)\n • gemini-2.0-pro (High quality, more capabilities)\n\n Current model: ` + e.config.GeminiModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t} else {\n\t\t\toutput = `\n╭─────────────── 🐦 Available OpenAI Models ───────────────╮\n\n • gpt-3.5-turbo (Fast, cost-effective)\n • gpt-4o (Advanced capabilities, slower)\n • gpt-4o-mini (Balanced performance and quality)\n\n Current model: ` + e.config.OpenAIModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current model\n\t\tvar currentModel string\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tcurrentModel = e.config.GeminiModel\n\t\tcase \"ollama\":\n\t\t\tcurrentModel = e.config.OllamaModel\n\t\tdefault: // OpenAI\n\t\t\tcurrentModel = e.config.OpenAIModel\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current %s model: %s\", e.config.AIProvider, currentModel),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set model\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing model name. Use 'config:model list' to see available models.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tmodel := args[1]\n\n\t\t// Validate model based on provider\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tvalidModels := []string{\"gemini-2.0-flash-lite\", \"gemini-2.0-flash\", \"gemini-2.0-pro\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid Gemini model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.GeminiModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\n\t\tcase \"ollama\":\n\t\t\t// For Ollama, we need to check if the model exists\n\t\t\tollamaClient := ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t\tmodels, err := ollamaClient.ListModels()\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error checking Ollama models: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range models {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid or unavailable Ollama model: %s. Use 'config:model list' to see available models or 'ollama pull %s' to download it.\", model, model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OllamaModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\n\t\tdefault: // OpenAI\n\t\t\tvalidModels := []string{\"gpt-3.5-turbo\", \"gpt-4o\", \"gpt-4o-mini\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid OpenAI model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OpenAIModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s model set to: %s\", e.config.AIProvider, model),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown model command: %s. Use 'list', 'show', or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleOllamaConfig handles Ollama URL configuration commands\nfunc (e *Executor) handleOllamaConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing Ollama command. Use 'show', 'set', or 'test'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current Ollama URL\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current Ollama URL: %s\", e.config.OllamaURL),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set Ollama URL\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing URL. Usage: config:ollama set <url>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\turl := args[1]\n\n\t\t// Basic URL validation\n\t\tif !strings.HasPrefix(url, \"http://\") && !strings.HasPrefix(url, \"https://\") {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Invalid URL format. URL must start with http:// or https://\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove trailing slash if present\n\t\turl = strings.TrimSuffix(url, \"/\")\n\n\t\t// Set the URL\n\t\te.config.OllamaURL = url\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// If Ollama is the current provider, reinitialize the client\n\t\tif e.config.AIProvider == \"ollama\" {\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Ollama URL set to: %s\", url),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"test\":\n\t\t// Test connection to Ollama server\n\t\tclient := &http.Client{\n\t\t\tTimeout: 5 * time.Second,\n\t\t}\n\t\tresp, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Cannot connect to Ollama server at %s\\nError: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Ollama server returned status code %d\", resp.StatusCode),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Try to parse the response to get available models\n\t\tvar modelsResponse struct {\n\t\t\tModels []struct {\n\t\t\t\tName string `json:\"name\"`\n\t\t\t} `json:\"models\"`\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't read response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\terr = json.Unmarshal(body, &modelsResponse)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't parse response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Count available models\n\t\tmodelCount := len(modelsResponse.Models)\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"✅ Successfully connected to Ollama server at %s\\nFound %d available models\", e.config.OllamaURL, modelCount),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown Ollama command: %s. Use 'show', 'set', or 'test'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModeConfig handles input mode configuration commands\nfunc (e *Executor) handleModeConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing mode command. Use 'show', 'ai', or 'command'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current mode\n\t\tmodeStr := \"AI-first\"\n\t\tif e.config.CommandFirstMode {\n\t\t\tmodeStr = \"Command-first\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔧 Input Mode ─────────────────────────╮\n\n Current input mode: %s\n\n • AI-first mode: Treats all input as AI queries by default\n unless it starts with a specific command prefix.\n\n • Command-first mode: Treats input as shell commands if it\n looks like a command, otherwise as an AI query.\n\n╰──────────────────────────────────────────────────────────╯\n`, modeStr)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"ai\":\n\t\t// Set AI-first mode\n\t\te.config.CommandFirstMode = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to AI-first. Lumo will now treat all input as AI queries by default unless it starts with a specific command prefix.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"command\":\n\t\t// Set Command-first mode\n\t\te.config.CommandFirstMode = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to Command-first. Lumo will now treat input as shell commands if it looks like a command, otherwise as an AI query.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown mode command: %s. Use 'show', 'ai', or 'command'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleKeyConfig handles API key configuration commands\nfunc (e *Executor) handleKeyConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing key command. Use 'show', 'set', or 'remove'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Use the dedicated key status handler\n\t\treturn e.handleKeyStatus(cmd)\n\tcase \"unused_show\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tgeminiStatus := \"Not set\"\n\t\tif e.config.GeminiAPIKey != \"\" {\n\t\t\tgeminiStatus = \"Set\"\n\t\t}\n\n\t\topenaiStatus := \"Not set\"\n\t\tif e.config.OpenAIAPIKey != \"\" {\n\t\t\topenaiStatus = \"Set\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔑 API Key Status ─────────────────────╮\n\n • Gemini API Key: %s\n • OpenAI API Key: %s\n\n Current provider: %s\n\n╰──────────────────────────────────────────────────────────╯\n`, geminiStatus, openaiStatus, e.config.AIProvider)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif len(args) < 3 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing API key. Usage: config:key set <provider> <key>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tapiKey := args[2]\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Set the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"gemini\" {\n\t\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\t\t}\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"openai\" {\n\t\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t\t}\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been set.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"remove\":\n\t\t// Remove API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if this is the current provider\n\t\tif e.config.AIProvider == provider {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Cannot remove API key for the current provider. Switch providers first with 'config:provider set'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = \"\"\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = \"\"\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been removed.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown key command: %s. Use 'show', 'set', or 'remove'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n"}, "43c76d629f509388468853ff72b0801e35683761": {"t": 1785759568.6702843, "n": 20840, "h": "68f3ce74780f06fc", "c": "package executor\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/agnath18/lumo/pkg/ai\"\n\t\"github.com/agnath18/lumo/pkg/config\"\n\t\"github.com/agnath18/lumo/pkg/nlp\"\n)\n\n// getCurrentModel returns the current model based on the provider\nfunc getCurrentModel(cfg *config.Config) string {\n\tif cfg.AIProvider == \"gemini\" {\n\t\treturn cfg.GeminiModel\n\t}\n\treturn cfg.OpenAIModel\n}\n\n// executeConfigCommand handles configuration commands\nfunc (e *Executor) executeConfigCommand(cmd *nlp.Command) (*Result, error) {\n\t// Split the command into parts\n\tparts := strings.Fields(cmd.Intent)\n\tif len(parts) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🔧 Lumo Configuration ─────────────────╮\n\n Commands:\n • config:provider list List available AI providers\n • config:provider show Show current AI provider\n • config:provider set <provider> Set AI provider (gemini/openai/ollama)\n\n • config:model list List available models\n • config:model show Show current model\n • config:model set <model> Set model for current provider\n\n • config:key show Show current API key status\n • config:key set <provider> <key> Set API key for provider\n • config:key remove <provider> Remove API key for provider\n\n • config:ollama show Show current Ollama URL\n • config:ollama set <url> Set Ollama URL\n • config:ollama test Test connection to Ollama server\n\n • config:mode show Show current input mode\n • config:mode ai Set AI-first mode (default)\n • config:mode command Set command-first mode\n\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\t// Handle different configuration commands\n\tswitch parts[0] {\n\tcase \"provider\":\n\t\treturn e.handleProviderConfig(parts[1:], cmd)\n\tcase \"model\":\n\t\treturn e.handleModelConfig(parts[1:], cmd)\n\tcase \"key\":\n\t\treturn e.handleKeyConfig(parts[1:], cmd)\n\tcase \"ollama\":\n\t\treturn e.handleOllamaConfig(parts[1:], cmd)\n\tcase \"mode\":\n\t\treturn e.handleModeConfig(parts[1:], cmd)\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown configuration command: %s\\nUse 'config:' for help.\", parts[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleProviderConfig handles provider configuration commands\nfunc (e *Executor) handleProviderConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing provider command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// List available providers\n\t\toutput := `\n╭─────────────── 🐦 Available AI Providers ───────────────╮\n\n • gemini (Google's Gemini AI models)\n • openai (OpenAI's GPT models)\n • ollama (Local Ollama models)\n\n Current provider: ` + e.config.AIProvider + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current provider\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current AI provider: %s\", e.config.AIProvider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set provider\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini', 'openai', or 'ollama'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tif provider != \"gemini\" && provider != \"openai\" && provider != \"ollama\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini', 'openai', or 'ollama'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if API key is set for the provider (not needed for Ollama)\n\t\tif provider == \"gemini\" && e.config.GeminiAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for Gemini. Please set an API key first with 'config:key set gemini <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"openai\" && e.config.OpenAIAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for OpenAI. Please set an API key first with 'config:key set openai <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\t// Ollama doesn't need an API key, but we should check if the URL is accessible\n\t\tif provider == \"ollama\" {\n\t\t\t// Try to connect to the Ollama server\n\t\t\tclient := &http.Client{\n\t\t\t\tTimeout: 5 * time.Second,\n\t\t\t}\n\t\t\t_, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Cannot connect to Ollama server at %s. Please make sure Ollama is running and accessible.\", e.config.OllamaURL),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t}\n\n\t\t// Set the provider\n\t\te.config.AIProvider = provider\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Reinitialize the AI client with the new provider\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\tcase \"ollama\":\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\tdefault: // Default to OpenAI\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"AI provider set to: %s\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown provider command: %s. Use 'show' or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModelConfig handles model configuration commands\nfunc (e *Executor) handleModelConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing model command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// Use the dedicated model list handler\n\t\treturn e.handleModelList(cmd)\n\tcase \"unused\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tvar output string\n\t\tif false {\n\t\t\toutput = `\n╭─────────────── 🐦 Available Gemini Models ───────────────╮\n\n • gemini-2.0-flash-lite (Fast, efficient for most queries)\n • gemini-2.0-flash (Balanced performance and quality)\n • gemini-2.0-pro (High quality, more capabilities)\n\n Current model: ` + e.config.GeminiModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t} else {\n\t\t\toutput = `\n╭─────────────── 🐦 Available OpenAI Models ───────────────╮\n\n • gpt-3.5-turbo (Fast, cost-effective)\n • gpt-4o (Advanced capabilities, slower)\n • gpt-4o-mini (Balanced performance and quality)\n\n Current model: ` + e.config.OpenAIModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current model\n\t\tvar currentModel string\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tcurrentModel = e.config.GeminiModel\n\t\tcase \"ollama\":\n\t\t\tcurrentModel = e.config.OllamaModel\n\t\tdefault: // OpenAI\n\t\t\tcurrentModel = e.config.OpenAIModel\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current %s model: %s\", e.config.AIProvider, currentModel),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set model\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing model name. Use 'config:model list' to see available models.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tmodel := args[1]\n\n\t\t// Validate model based on provider\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tvalidModels := []string{\"gemini-2.0-flash-lite\", \"gemini-2.0-flash\", \"gemini-2.0-pro\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid Gemini model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.GeminiModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\n\t\tcase \"ollama\":\n\t\t\t// For Ollama, we need to check if the model exists\n\t\t\tollamaClient := ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t\tmodels, err := ollamaClient.ListModels()\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error checking Ollama models: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range models {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid or unavailable Ollama model: %s. Use 'config:model list' to see available models or 'ollama pull %s' to download it.\", model, model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OllamaModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\n\t\tdefault: // OpenAI\n\t\t\tvalidModels := []string{\"gpt-3.5-turbo\", \"gpt-4o\", \"gpt-4o-mini\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid OpenAI model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OpenAIModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s model set to: %s\", e.config.AIProvider, model),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown model command: %s. Use 'list', 'show', or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleOllamaConfig handles Ollama URL configuration commands\nfunc (e *Executor) handleOllamaConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing Ollama command. Use 'show', 'set', or 'test'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current Ollama URL\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current Ollama URL: %s\", e.config.OllamaURL),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set Ollama URL\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing URL. Usage: config:ollama set <url>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\turl := args[1]\n\n\t\t// Basic URL validation\n\t\tif !strings.HasPrefix(url, \"http://\") && !strings.HasPrefix(url, \"https://\") {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Invalid URL format. URL must start with http:// or https://\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove trailing slash if present\n\t\turl = strings.TrimSuffix(url, \"/\")\n\n\t\t// Set the URL\n\t\te.config.OllamaURL = url\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// If Ollama is the current provider, reinitialize the client\n\t\tif e.config.AIProvider == \"ollama\" {\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Ollama URL set to: %s\", url),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"test\":\n\t\t// Test connection to Ollama server\n\t\tclient := &http.Client{\n\t\t\tTimeout: 5 * time.Second,\n\t\t}\n\t\tresp, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Cannot connect to Ollama server at %s\\nError: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Ollama server returned status code %d\", resp.StatusCode),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Try to parse the response to get available models\n\t\tvar modelsResponse struct {\n\t\t\tModels []struct {\n\t\t\t\tName string `json:\"name\"`\n\t\t\t} `json:\"models\"`\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't read response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\terr = json.Unmarshal(body, &modelsResponse)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't parse response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Count available models\n\t\tmodelCount := len(modelsResponse.Models)\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"✅ Successfully connected to Ollama server at %s\\nFound %d available models\", e.config.OllamaURL, modelCount),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown Ollama command: %s. Use 'show', 'set', or 'test'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModeConfig handles input mode configuration commands\nfunc (e *Executor) handleModeConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing mode command. Use 'show', 'ai', or 'command'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current mode\n\t\tmodeStr := \"AI-first\"\n\t\tif e.config.CommandFirstMode {\n\t\t\tmodeStr = \"Command-first\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔧 Input Mode ─────────────────────────╮\n\n Current input mode: %s\n\n • AI-first mode: Treats all input as AI queries by default\n unless it starts with a specific command prefix.\n\n • Command-first mode: Treats input as shell commands if it\n looks like a command, otherwise as an AI query.\n\n╰──────────────────────────────────────────────────────────╯\n`, modeStr)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"ai\":\n\t\t// Set AI-first mode\n\t\te.config.CommandFirstMode = false\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to AI-first. Lumo will now treat all input as AI queries by default unless it starts with a specific command prefix.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tcase \"command\":\n\t\t// Set Command-first mode\n\t\te.config.CommandFirstMode = true\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: \"Input mode set to Command-first. Lumo will now treat input as shell commands if it looks like a command, otherwise as an AI query.\",\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown mode command: %s. Use 'show', 'ai', or 'command'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleKeyConfig handles API key configuration commands\nfunc (e *Executor) handleKeyConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing key command. Use 'show', 'set', or 'remove'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Use the dedicated key status handler\n\t\treturn e.handleKeyStatus(cmd)\n\tcase \"unused_show\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tgeminiStatus := \"Not set\"\n\t\tif e.config.GeminiAPIKey != \"\" {\n\t\t\tgeminiStatus = \"Set\"\n\t\t}\n\n\t\topenaiStatus := \"Not set\"\n\t\tif e.config.OpenAIAPIKey != \"\" {\n\t\t\topenaiStatus = \"Set\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔑 API Key Status ─────────────────────╮\n\n • Gemini API Key: %s\n • OpenAI API Key: %s\n\n Current provider: %s\n\n╰──────────────────────────────────────────────────────────╯\n`, geminiStatus, openaiStatus, e.config.AIProvider)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif len(args) < 3 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing API key. Usage: config:key set <provider> <key>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tapiKey := args[2]\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Set the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"gemini\" {\n\t\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\t\t}\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"openai\" {\n\t\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t\t}\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been set.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"remove\":\n\t\t// Remove API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if this is the current provider\n\t\tif e.config.AIProvider == provider {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Cannot remove API key for the current provider. Switch providers first with 'config:provider set'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = \"\"\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = \"\"\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been removed.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown key command: %s. Use 'show', 'set', or 'remove'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n"}, "4d58b5ea8dd59ad6ca06472b6cb76f181f6cdfd2": {"t": 1785759570.391492, "n": 18360, "h": "dac39a4fdd64c381", "c": "package executor\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/agnath18/lumo/pkg/ai\"\n\t\"github.com/agnath18/lumo/pkg/config\"\n\t\"github.com/agnath18/lumo/pkg/nlp\"\n)\n\n// getCurrentModel returns the current model based on the provider\nfunc getCurrentModel(cfg *config.Config) string {\n\tif cfg.AIProvider == \"gemini\" {\n\t\treturn cfg.GeminiModel\n\t}\n\treturn cfg.OpenAIModel\n}\n\n// executeConfigCommand handles configuration commands\nfunc (e *Executor) executeConfigCommand(cmd *nlp.Command) (*Result, error) {\n\t// Split the command into parts\n\tparts := strings.Fields(cmd.Intent)\n\tif len(parts) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: `\n╭─────────────────── 🔧 Lumo Configuration ─────────────────╮\n\n Commands:\n • config:provider list List available AI providers\n • config:provider show Show current AI provider\n • config:provider set <provider> Set AI provider (gemini/openai/ollama)\n\n • config:model list List available models\n • config:model show Show current model\n • config:model set <model> Set model for current provider\n\n • config:key show Show current API key status\n • config:key set <provider> <key> Set API key for provider\n • config:key remove <provider> Remove API key for provider\n\n • config:ollama show Show current Ollama URL\n • config:ollama set <url> Set Ollama URL\n • config:ollama test Test connection to Ollama server\n\n╰──────────────────────────────────────────────────────────╯\n`,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\t// Handle different configuration commands\n\tswitch parts[0] {\n\tcase \"provider\":\n\t\treturn e.handleProviderConfig(parts[1:], cmd)\n\tcase \"model\":\n\t\treturn e.handleModelConfig(parts[1:], cmd)\n\tcase \"key\":\n\t\treturn e.handleKeyConfig(parts[1:], cmd)\n\tcase \"ollama\":\n\t\treturn e.handleOllamaConfig(parts[1:], cmd)\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown configuration command: %s\\nUse 'config:' for help.\", parts[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleProviderConfig handles provider configuration commands\nfunc (e *Executor) handleProviderConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing provider command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// List available providers\n\t\toutput := `\n╭─────────────── 🐦 Available AI Providers ───────────────╮\n\n • gemini (Google's Gemini AI models)\n • openai (OpenAI's GPT models)\n • ollama (Local Ollama models)\n\n Current provider: ` + e.config.AIProvider + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current provider\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current AI provider: %s\", e.config.AIProvider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set provider\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini', 'openai', or 'ollama'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tif provider != \"gemini\" && provider != \"openai\" && provider != \"ollama\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini', 'openai', or 'ollama'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if API key is set for the provider (not needed for Ollama)\n\t\tif provider == \"gemini\" && e.config.GeminiAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for Gemini. Please set an API key first with 'config:key set gemini <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tif provider == \"openai\" && e.config.OpenAIAPIKey == \"\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"No API key set for OpenAI. Please set an API key first with 'config:key set openai <key>'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\t// Ollama doesn't need an API key, but we should check if the URL is accessible\n\t\tif provider == \"ollama\" {\n\t\t\t// Try to connect to the Ollama server\n\t\t\tclient := &http.Client{\n\t\t\t\tTimeout: 5 * time.Second,\n\t\t\t}\n\t\t\t_, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Cannot connect to Ollama server at %s. Please make sure Ollama is running and accessible.\", e.config.OllamaURL),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t}\n\n\t\t// Set the provider\n\t\te.config.AIProvider = provider\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Reinitialize the AI client with the new provider\n\t\tswitch provider {\n\t\tcase \"gemini\":\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\tcase \"ollama\":\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\tdefault: // Default to OpenAI\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"AI provider set to: %s\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown provider command: %s. Use 'show' or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleModelConfig handles model configuration commands\nfunc (e *Executor) handleModelConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing model command. Use 'list', 'show', or 'set'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"list\":\n\t\t// Use the dedicated model list handler\n\t\treturn e.handleModelList(cmd)\n\tcase \"unused\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tvar output string\n\t\tif false {\n\t\t\toutput = `\n╭─────────────── 🐦 Available Gemini Models ───────────────╮\n\n • gemini-2.0-flash-lite (Fast, efficient for most queries)\n • gemini-2.0-flash (Balanced performance and quality)\n • gemini-2.0-pro (High quality, more capabilities)\n\n Current model: ` + e.config.GeminiModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t} else {\n\t\t\toutput = `\n╭─────────────── 🐦 Available OpenAI Models ───────────────╮\n\n • gpt-3.5-turbo (Fast, cost-effective)\n • gpt-4o (Advanced capabilities, slower)\n • gpt-4o-mini (Balanced performance and quality)\n\n Current model: ` + e.config.OpenAIModel + `\n\n╰──────────────────────────────────────────────────────────╯\n`\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"show\":\n\t\t// Show current model\n\t\tvar currentModel string\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tcurrentModel = e.config.GeminiModel\n\t\tcase \"ollama\":\n\t\t\tcurrentModel = e.config.OllamaModel\n\t\tdefault: // OpenAI\n\t\t\tcurrentModel = e.config.OpenAIModel\n\t\t}\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current %s model: %s\", e.config.AIProvider, currentModel),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set model\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing model name. Use 'config:model list' to see available models.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tmodel := args[1]\n\n\t\t// Validate model based on provider\n\t\tswitch e.config.AIProvider {\n\t\tcase \"gemini\":\n\t\t\tvalidModels := []string{\"gemini-2.0-flash-lite\", \"gemini-2.0-flash\", \"gemini-2.0-pro\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid Gemini model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.GeminiModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\n\t\tcase \"ollama\":\n\t\t\t// For Ollama, we need to check if the model exists\n\t\t\tollamaClient := ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t\tmodels, err := ollamaClient.ListModels()\n\t\t\tif err != nil {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Error checking Ollama models: %v\", err),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range models {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid or unavailable Ollama model: %s. Use 'config:model list' to see available models or 'ollama pull %s' to download it.\", model, model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OllamaModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\n\t\tdefault: // OpenAI\n\t\t\tvalidModels := []string{\"gpt-3.5-turbo\", \"gpt-4o\", \"gpt-4o-mini\"}\n\t\t\tisValid := false\n\t\t\tfor _, validModel := range validModels {\n\t\t\t\tif model == validModel {\n\t\t\t\t\tisValid = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !isValid {\n\t\t\t\treturn &Result{\n\t\t\t\t\tOutput: fmt.Sprintf(\"Invalid OpenAI model: %s. Use 'config:model list' to see available models.\", model),\n\t\t\t\t\tIsError: true,\n\t\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t\t}, nil\n\t\t\t}\n\n\t\t\t// Set the model\n\t\t\te.config.OpenAIModel = model\n\n\t\t\t// Reinitialize the AI client with the new model\n\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s model set to: %s\", e.config.AIProvider, model),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown model command: %s. Use 'list', 'show', or 'set'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleOllamaConfig handles Ollama URL configuration commands\nfunc (e *Executor) handleOllamaConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing Ollama command. Use 'show', 'set', or 'test'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Show current Ollama URL\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Current Ollama URL: %s\", e.config.OllamaURL),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set Ollama URL\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing URL. Usage: config:ollama set <url>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\turl := args[1]\n\n\t\t// Basic URL validation\n\t\tif !strings.HasPrefix(url, \"http://\") && !strings.HasPrefix(url, \"https://\") {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Invalid URL format. URL must start with http:// or https://\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove trailing slash if present\n\t\turl = strings.TrimSuffix(url, \"/\")\n\n\t\t// Set the URL\n\t\te.config.OllamaURL = url\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// If Ollama is the current provider, reinitialize the client\n\t\tif e.config.AIProvider == \"ollama\" {\n\t\t\te.aiClient = ai.NewOllamaClient(e.config.OllamaURL, e.config.OllamaModel)\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Ollama URL set to: %s\", url),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"test\":\n\t\t// Test connection to Ollama server\n\t\tclient := &http.Client{\n\t\t\tTimeout: 5 * time.Second,\n\t\t}\n\t\tresp, err := client.Get(e.config.OllamaURL + \"/api/tags\")\n\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Cannot connect to Ollama server at %s\\nError: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"❌ Ollama server returned status code %d\", resp.StatusCode),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Try to parse the response to get available models\n\t\tvar modelsResponse struct {\n\t\t\tModels []struct {\n\t\t\t\tName string `json:\"name\"`\n\t\t\t} `json:\"models\"`\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't read response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\terr = json.Unmarshal(body, &modelsResponse)\n\t\tif err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"✅ Connected to Ollama server at %s, but couldn't parse response: %v\", e.config.OllamaURL, err),\n\t\t\t\tIsError: false,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Count available models\n\t\tmodelCount := len(modelsResponse.Models)\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"✅ Successfully connected to Ollama server at %s\\nFound %d available models\", e.config.OllamaURL, modelCount),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown Ollama command: %s. Use 'show', 'set', or 'test'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n\n// handleKeyConfig handles API key configuration commands\nfunc (e *Executor) handleKeyConfig(args []string, cmd *nlp.Command) (*Result, error) {\n\tif len(args) == 0 {\n\t\treturn &Result{\n\t\t\tOutput: \"Missing key command. Use 'show', 'set', or 'remove'.\",\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n\n\tswitch args[0] {\n\tcase \"show\":\n\t\t// Use the dedicated key status handler\n\t\treturn e.handleKeyStatus(cmd)\n\tcase \"unused_show\":\n\t\t// This case is never used, just here to keep the old code structure\n\t\tgeminiStatus := \"Not set\"\n\t\tif e.config.GeminiAPIKey != \"\" {\n\t\t\tgeminiStatus = \"Set\"\n\t\t}\n\n\t\topenaiStatus := \"Not set\"\n\t\tif e.config.OpenAIAPIKey != \"\" {\n\t\t\topenaiStatus = \"Set\"\n\t\t}\n\n\t\toutput := fmt.Sprintf(`\n╭─────────────────── 🔑 API Key Status ─────────────────────╮\n\n • Gemini API Key: %s\n • OpenAI API Key: %s\n\n Current provider: %s\n\n╰──────────────────────────────────────────────────────────╯\n`, geminiStatus, openaiStatus, e.config.AIProvider)\n\n\t\treturn &Result{\n\t\t\tOutput: output,\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"set\":\n\t\t// Set API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tif len(args) < 3 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing API key. Usage: config:key set <provider> <key>\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\t\tapiKey := args[2]\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Set the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"gemini\" {\n\t\t\t\te.aiClient = ai.NewGeminiClient(e.config.GeminiAPIKey, e.config.GeminiModel)\n\t\t\t}\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = apiKey\n\n\t\t\t// If this is the current provider, reinitialize the client\n\t\t\tif e.config.AIProvider == \"openai\" {\n\t\t\t\te.aiClient = ai.NewOpenAIClient(e.config.OpenAIAPIKey, e.config.OpenAIModel)\n\t\t\t}\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been set.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tcase \"remove\":\n\t\t// Remove API key\n\t\tif len(args) < 2 {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Missing provider name. Use 'gemini' or 'openai'. Note: Ollama doesn't require an API key.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\tprovider := strings.ToLower(args[1])\n\n\t\tif provider != \"gemini\" && provider != \"openai\" {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Invalid provider: %s. Use 'gemini' or 'openai'.\", provider),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Check if this is the current provider\n\t\tif e.config.AIProvider == provider {\n\t\t\treturn &Result{\n\t\t\t\tOutput: \"Cannot remove API key for the current provider. Switch providers first with 'config:provider set'.\",\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\t// Remove the API key\n\t\tif provider == \"gemini\" {\n\t\t\te.config.GeminiAPIKey = \"\"\n\t\t} else {\n\t\t\te.config.OpenAIAPIKey = \"\"\n\t\t}\n\n\t\t// Save the configuration\n\t\tif err := e.config.Save(); err != nil {\n\t\t\treturn &Result{\n\t\t\t\tOutput: fmt.Sprintf(\"Error saving configuration: %v\", err),\n\t\t\t\tIsError: true,\n\t\t\t\tCommandRun: cmd.RawInput,\n\t\t\t}, nil\n\t\t}\n\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"%s API key has been removed.\", provider),\n\t\t\tIsError: false,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\tdefault:\n\t\treturn &Result{\n\t\t\tOutput: fmt.Sprintf(\"Unknown key command: %s. Use 'show', 'set', or 'remove'.\", args[0]),\n\t\t\tIsError: true,\n\t\t\tCommandRun: cmd.RawInput,\n\t\t}, nil\n\t}\n}\n"}}}}