Coverage for src / osiris_cli / setup.py: 0%
110 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-27 17:41 +0200
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-27 17:41 +0200
1#!/usr/bin/env python3
2"""
3Setup and Configuration Module for Osiris CLI v4.0
4Minimal, clean setup wizards integrated into the live menu system
5"""
7import os
8import questionary
9from questionary import Style
10from rich.console import Console
11from pathlib import Path
12from typing import Optional
13from pydantic import SecretStr
15console = Console()
17# Minimal style for setup menus
18SETUP_STYLE = Style([
19 ('qmark', 'fg:#60a5fa'),
20 ('question', 'fg:#e5e7eb'),
21 ('answer', 'fg:#e5e7eb'),
22 ('pointer', 'fg:#60a5fa'),
23 ('highlighted', 'fg:#ffffff bold'),
24 ('selected', 'fg:#e5e7eb'),
25 ('separator', 'fg:#6b7280'),
26 ('instruction', 'fg:#9ca3af'),
27 ('text', 'fg:#d1d5db'),
28])
31async def auto_setup() -> bool:
32 """
33 One-click automatic setup - detects and configures best available provider
34 Returns True if successful
35 """
36 from .config import settings, KNOWN_PROVIDERS
37 from .client import get_async_client, get_client
39 console.print("\n[bold]Auto-Setup[/bold]")
40 console.print("[dim]Detecting available providers...[/dim]\n")
42 # Check for API keys in environment
43 env_keys = {
44 "openrouter": ["OPENROUTER_API_KEY"],
45 "openai": ["OPENAI_API_KEY"],
46 "gemini": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
47 "deepseek": ["DEEPSEEK_API_KEY"],
48 "together": ["TOGETHER_API_KEY"],
49 "mistral": ["MISTRAL_API_KEY"],
50 "perplexity": ["PERPLEXITY_API_KEY"],
51 "fireworks": ["FIREWORKS_API_KEY"],
52 "groq": ["GROQ_API_KEY"],
53 "ollama": [] # No key needed
54 }
56 available_providers = []
58 for provider, keys in env_keys.items():
59 if not keys: # Ollama
60 available_providers.append(provider)
61 else:
62 for key in keys:
63 if os.getenv(key):
64 available_providers.append(provider)
65 break
67 if available_providers:
68 # Prefer non-ollama providers
69 selected = next((p for p in available_providers if p != "ollama"), available_providers[0])
71 settings.provider = selected
72 settings.provider_base_url = None
74 models = KNOWN_PROVIDERS.get(selected, {}).get("models", [])
75 if models:
76 settings.default_model = models[0]
78 settings.save()
80 try:
81 get_async_client().reinitialize()
82 except Exception:
83 pass
84 try:
85 get_client().reinitialize()
86 except Exception:
87 pass
89 console.print(f"[green]✓[/green] Configured provider: {selected}")
90 console.print(f"[dim]Run /model to select a specific model[/dim]\n")
91 return True
92 else:
93 console.print("[yellow]No API keys found in environment[/yellow]")
94 console.print("[dim]Set an API key or use /setup for guided configuration[/dim]\n")
95 return False
98async def quick_setup() -> bool:
99 """
100 Interactive setup wizard - step-by-step configuration
101 Returns True if successful
102 """
103 from .config import settings, KNOWN_PROVIDERS
104 from .client import get_async_client, get_client
106 console.print("\n[bold]Quick Setup Wizard[/bold]\n")
108 try:
109 # Step 1: Provider selection
110 providers = [
111 {"name": "openrouter", "display": "OpenRouter (100+ models, free options)", "needs_key": True},
112 {"name": "openai", "display": "OpenAI (GPT-4, GPT-3.5)", "needs_key": True},
113 {"name": "gemini", "display": "Google Gemini", "needs_key": True},
114 {"name": "deepseek", "display": "DeepSeek (Reasoning)", "needs_key": True},
115 {"name": "together", "display": "Together AI", "needs_key": True},
116 {"name": "mistral", "display": "Mistral AI", "needs_key": True},
117 {"name": "perplexity", "display": "Perplexity (Sonar, Research)", "needs_key": True},
118 {"name": "fireworks", "display": "Fireworks AI", "needs_key": True},
119 {"name": "groq", "display": "Groq (Fast Inference)", "needs_key": True},
120 {"name": "ollama", "display": "Ollama (Local Models, no API key)", "needs_key": False},
121 ]
123 choices = [questionary.Choice(title=p["display"], value=p) for p in providers]
125 selected_provider = await questionary.select(
126 "Select AI Provider:",
127 choices=choices,
128 style=SETUP_STYLE,
129 instruction="(arrow keys, Enter to select)"
130 ).ask_async()
132 if not selected_provider:
133 return False
135 provider_id = selected_provider["name"]
136 settings.provider = provider_id
137 settings.provider_base_url = None
139 console.print(f"\n[green]✓[/green] Selected: {selected_provider['display']}")
141 # Step 2: API Key (if needed)
142 if selected_provider["needs_key"]:
143 console.print(f"\n[bold]API Key for {selected_provider['display']}[/bold]")
144 console.print(f"[dim]Get your API key from the provider's website[/dim]")
146 has_key = await questionary.confirm(
147 "Do you have an API key?",
148 default=False,
149 style=SETUP_STYLE
150 ).ask_async()
152 if has_key:
153 api_key = await questionary.password(
154 "Enter API key:",
155 style=SETUP_STYLE
156 ).ask_async()
158 if api_key:
159 # Save to environment variable
160 key_name = f"{provider_id.upper()}_API_KEY"
161 os.environ[key_name] = api_key
162 console.print(f"[green]✓[/green] API key configured for this session")
163 console.print(f"[dim]To persist, add to your shell: export {key_name}=your_key[/dim]")
164 setting_key = f"{provider_id}_api_key"
165 if hasattr(settings, setting_key):
166 setattr(settings, setting_key, SecretStr(api_key))
167 else:
168 console.print("[yellow]⚠[/yellow] You'll need an API key to use this provider")
169 console.print(f"[dim]Visit the provider's website to get one[/dim]")
171 # Step 3: Save settings
172 models = KNOWN_PROVIDERS.get(provider_id, {}).get("models", [])
173 if models:
174 settings.default_model = models[0]
176 settings.save()
178 try:
179 get_async_client().reinitialize()
180 except Exception:
181 pass
182 try:
183 get_client().reinitialize()
184 except Exception:
185 pass
187 console.print(f"\n[green]✓[/green] Setup complete!")
188 console.print(f"[dim]Provider: {provider_id}[/dim]")
189 console.print(f"[dim]Use /model to select a specific model[/dim]\n")
191 return True
193 except (KeyboardInterrupt, EOFError):
194 console.print("\n[yellow]Setup cancelled[/yellow]\n")
195 return False
198async def show_setup_menu():
199 """Main setup menu"""
200 choices = [
201 questionary.Choice(title="Auto-Setup (detect and configure automatically)", value="auto"),
202 questionary.Choice(title="Quick Setup (step-by-step wizard)", value="quick"),
203 questionary.Choice(title="Manual Configuration (use /provider and /model)", value="manual"),
204 questionary.Separator(),
205 questionary.Choice(title="Cancel", value=None),
206 ]
208 try:
209 result = await questionary.select(
210 "Setup Options:",
211 choices=choices,
212 style=SETUP_STYLE,
213 instruction="(arrow keys, Enter to select, Esc to cancel)"
214 ).ask_async()
216 if result == "auto":
217 await auto_setup()
218 elif result == "quick":
219 await quick_setup()
220 elif result == "manual":
221 console.print("\n[bold]Manual Configuration[/bold]")
222 console.print("[dim]Use these commands to configure:[/dim]")
223 console.print(" /provider - Select AI provider")
224 console.print(" /model - Select model")
225 console.print(" /yolo - Toggle autonomous mode\n")
227 except (KeyboardInterrupt, EOFError):
228 pass