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

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""" 

6 

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 

14 

15console = Console() 

16 

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]) 

29 

30 

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 

38 

39 console.print("\n[bold]Auto-Setup[/bold]") 

40 console.print("[dim]Detecting available providers...[/dim]\n") 

41 

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 } 

55 

56 available_providers = [] 

57 

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 

66 

67 if available_providers: 

68 # Prefer non-ollama providers 

69 selected = next((p for p in available_providers if p != "ollama"), available_providers[0]) 

70 

71 settings.provider = selected 

72 settings.provider_base_url = None 

73 

74 models = KNOWN_PROVIDERS.get(selected, {}).get("models", []) 

75 if models: 

76 settings.default_model = models[0] 

77 

78 settings.save() 

79 

80 try: 

81 get_async_client().reinitialize() 

82 except Exception: 

83 pass 

84 try: 

85 get_client().reinitialize() 

86 except Exception: 

87 pass 

88 

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 

96 

97 

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 

105 

106 console.print("\n[bold]Quick Setup Wizard[/bold]\n") 

107 

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 ] 

122 

123 choices = [questionary.Choice(title=p["display"], value=p) for p in providers] 

124 

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() 

131 

132 if not selected_provider: 

133 return False 

134 

135 provider_id = selected_provider["name"] 

136 settings.provider = provider_id 

137 settings.provider_base_url = None 

138 

139 console.print(f"\n[green]✓[/green] Selected: {selected_provider['display']}") 

140 

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]") 

145 

146 has_key = await questionary.confirm( 

147 "Do you have an API key?", 

148 default=False, 

149 style=SETUP_STYLE 

150 ).ask_async() 

151 

152 if has_key: 

153 api_key = await questionary.password( 

154 "Enter API key:", 

155 style=SETUP_STYLE 

156 ).ask_async() 

157 

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]") 

170 

171 # Step 3: Save settings 

172 models = KNOWN_PROVIDERS.get(provider_id, {}).get("models", []) 

173 if models: 

174 settings.default_model = models[0] 

175 

176 settings.save() 

177 

178 try: 

179 get_async_client().reinitialize() 

180 except Exception: 

181 pass 

182 try: 

183 get_client().reinitialize() 

184 except Exception: 

185 pass 

186 

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") 

190 

191 return True 

192 

193 except (KeyboardInterrupt, EOFError): 

194 console.print("\n[yellow]Setup cancelled[/yellow]\n") 

195 return False 

196 

197 

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 ] 

207 

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() 

215 

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") 

226 

227 except (KeyboardInterrupt, EOFError): 

228 pass