omnimancer-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. omnimancer/__init__.py +42 -0
  2. omnimancer/__main__.py +9 -0
  3. omnimancer/cli/__init__.py +8 -0
  4. omnimancer/cli/agent_manager.py +826 -0
  5. omnimancer/cli/agent_wizard.py +944 -0
  6. omnimancer/cli/approval_formatter.py +734 -0
  7. omnimancer/cli/approval_integration.py +734 -0
  8. omnimancer/cli/approval_prompt.py +1286 -0
  9. omnimancer/cli/batch_approval_display.py +617 -0
  10. omnimancer/cli/batch_approval_filters.py +729 -0
  11. omnimancer/cli/batch_operation_monitor.py +775 -0
  12. omnimancer/cli/commands/performance_cmd.py +624 -0
  13. omnimancer/cli/commands.py +757 -0
  14. omnimancer/cli/completion_handler.py +281 -0
  15. omnimancer/cli/config_setup_wizard.py +783 -0
  16. omnimancer/cli/dynamic_commands.py +371 -0
  17. omnimancer/cli/handlers/__init__.py +18 -0
  18. omnimancer/cli/handlers/agent_handler.py +865 -0
  19. omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
  20. omnimancer/cli/handlers/custom_agent_handler.py +352 -0
  21. omnimancer/cli/handlers/permissions_handler.py +1609 -0
  22. omnimancer/cli/interface.py +6695 -0
  23. omnimancer/cli/performance_dashboard.py +612 -0
  24. omnimancer/core/__init__.py +34 -0
  25. omnimancer/core/agent/__init__.py +256 -0
  26. omnimancer/core/agent/agent_config.py +715 -0
  27. omnimancer/core/agent/agent_switcher.py +668 -0
  28. omnimancer/core/agent/approval_context.py +460 -0
  29. omnimancer/core/agent/approval_dialog.py +902 -0
  30. omnimancer/core/agent/approval_interface.py +628 -0
  31. omnimancer/core/agent/approval_manager.py +760 -0
  32. omnimancer/core/agent/cli_integration.py +470 -0
  33. omnimancer/core/agent/config.py +845 -0
  34. omnimancer/core/agent/custom_agent_integration.py +654 -0
  35. omnimancer/core/agent/diff_renderer.py +706 -0
  36. omnimancer/core/agent/file_content_display.py +503 -0
  37. omnimancer/core/agent/file_modification_workflow.py +658 -0
  38. omnimancer/core/agent/file_system_manager.py +1790 -0
  39. omnimancer/core/agent/input_handler.py +614 -0
  40. omnimancer/core/agent/metrics_collector.py +771 -0
  41. omnimancer/core/agent/optimization_engine.py +729 -0
  42. omnimancer/core/agent/performance_monitor.py +717 -0
  43. omnimancer/core/agent/persona.py +870 -0
  44. omnimancer/core/agent/persona_validator.py +672 -0
  45. omnimancer/core/agent/program_executor.py +748 -0
  46. omnimancer/core/agent/proposed_changes_integration.py +663 -0
  47. omnimancer/core/agent/read_before_write_errors.py +492 -0
  48. omnimancer/core/agent/read_before_write_ui.py +647 -0
  49. omnimancer/core/agent/rich_renderer.py +763 -0
  50. omnimancer/core/agent/status_config.py +238 -0
  51. omnimancer/core/agent/status_core.py +446 -0
  52. omnimancer/core/agent/status_integrated_engine.py +540 -0
  53. omnimancer/core/agent/status_manager.py +878 -0
  54. omnimancer/core/agent/status_ui_components.py +535 -0
  55. omnimancer/core/agent/template_applicator.py +548 -0
  56. omnimancer/core/agent/terminal_status_listener.py +394 -0
  57. omnimancer/core/agent/token_tracker.py +679 -0
  58. omnimancer/core/agent/types.py +60 -0
  59. omnimancer/core/agent/web_client.py +745 -0
  60. omnimancer/core/agent/workflow_orchestrator.py +826 -0
  61. omnimancer/core/agent_engine.py +1625 -0
  62. omnimancer/core/agent_mode_manager.py +706 -0
  63. omnimancer/core/agent_progress_ui.py +545 -0
  64. omnimancer/core/chat_manager.py +141 -0
  65. omnimancer/core/config_manager.py +2199 -0
  66. omnimancer/core/config_migration.py +772 -0
  67. omnimancer/core/config_provider.py +1974 -0
  68. omnimancer/core/config_security_validator.py +393 -0
  69. omnimancer/core/config_validator.py +768 -0
  70. omnimancer/core/conversation_manager.py +352 -0
  71. omnimancer/core/engine.py +787 -0
  72. omnimancer/core/enhanced_input.py +523 -0
  73. omnimancer/core/env_loader.py +83 -0
  74. omnimancer/core/fallback_manager.py +568 -0
  75. omnimancer/core/health_monitor.py +462 -0
  76. omnimancer/core/history_manager.py +462 -0
  77. omnimancer/core/mcp_integration_layer.py +878 -0
  78. omnimancer/core/models.py +1889 -0
  79. omnimancer/core/provider_capabilities.py +403 -0
  80. omnimancer/core/provider_initializer.py +450 -0
  81. omnimancer/core/provider_registry.py +432 -0
  82. omnimancer/core/security/__init__.py +16 -0
  83. omnimancer/core/security/approval_workflow.py +406 -0
  84. omnimancer/core/security/audit_logger.py +511 -0
  85. omnimancer/core/security/permission_controller.py +511 -0
  86. omnimancer/core/security/sandbox_manager.py +379 -0
  87. omnimancer/core/security/security_manager.py +530 -0
  88. omnimancer/core/setup_wizard.py +345 -0
  89. omnimancer/core/setup_wizard_core.py +449 -0
  90. omnimancer/core/setup_wizard_guide_generator.py +376 -0
  91. omnimancer/core/setup_wizard_provider_setup.py +454 -0
  92. omnimancer/core/setup_wizard_ui.py +317 -0
  93. omnimancer/core/setup_wizard_validation.py +371 -0
  94. omnimancer/core/signal_handler.py +174 -0
  95. omnimancer/mcp/__init__.py +11 -0
  96. omnimancer/mcp/client.py +725 -0
  97. omnimancer/mcp/manager.py +649 -0
  98. omnimancer/providers/__init__.py +24 -0
  99. omnimancer/providers/azure.py +608 -0
  100. omnimancer/providers/base.py +262 -0
  101. omnimancer/providers/bedrock.py +711 -0
  102. omnimancer/providers/claude.py +426 -0
  103. omnimancer/providers/claude_code.py +406 -0
  104. omnimancer/providers/cohere.py +412 -0
  105. omnimancer/providers/factory.py +988 -0
  106. omnimancer/providers/gemini.py +708 -0
  107. omnimancer/providers/mistral.py +554 -0
  108. omnimancer/providers/ollama.py +510 -0
  109. omnimancer/providers/openai.py +362 -0
  110. omnimancer/providers/openrouter.py +709 -0
  111. omnimancer/providers/perplexity.py +476 -0
  112. omnimancer/providers/vertex.py +617 -0
  113. omnimancer/providers/xai.py +484 -0
  114. omnimancer/ui/__init__.py +6 -0
  115. omnimancer/ui/cancellation_handler.py +235 -0
  116. omnimancer/ui/progress_indicator.py +318 -0
  117. omnimancer/utils/__init__.py +22 -0
  118. omnimancer/utils/error_handler.py +589 -0
  119. omnimancer/utils/errors.py +667 -0
  120. omnimancer/utils/retry.py +238 -0
  121. omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
  122. omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
  123. omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
  124. omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
  125. omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  126. omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,454 @@
1
+ """
2
+ Provider setup methods for the setup wizard.
3
+
4
+ This module contains all provider configuration and setup methods,
5
+ including provider selection, API key management, and model selection.
6
+ """
7
+
8
+ from typing import Any, Dict, Optional
9
+
10
+ from rich.console import Console
11
+ from rich.prompt import Confirm, Prompt
12
+ from rich.table import Table
13
+
14
+ from .models import ProviderConfig
15
+ from .setup_wizard_ui import SetupWizardUI
16
+
17
+
18
+ class SetupWizardProviderSetup:
19
+ """Provider setup methods for the setup wizard."""
20
+
21
+ def __init__(self, console: Console, provider_info: Dict[str, Dict[str, Any]]):
22
+ """
23
+ Initialize the provider setup component.
24
+
25
+ Args:
26
+ console: Rich console instance
27
+ provider_info: Dictionary of provider information
28
+ """
29
+ self.console = console
30
+ self.provider_info = provider_info
31
+ self.ui = SetupWizardUI(console, provider_info)
32
+
33
+ def select_provider(self) -> Optional[str]:
34
+ """
35
+ Interactive provider selection with descriptions.
36
+
37
+ Returns:
38
+ Selected provider name or None if cancelled
39
+ """
40
+ self.console.print("[bold]Available AI Providers:[/bold]\n")
41
+
42
+ # Create provider selection table
43
+ table = Table(show_header=True, header_style="bold magenta")
44
+ table.add_column("#", style="dim", width=3)
45
+ table.add_column("Provider", style="bold")
46
+ table.add_column("Description", style="dim")
47
+ table.add_column("Best For", style="green")
48
+
49
+ providers = list(self.provider_info.keys())
50
+ for i, provider_key in enumerate(providers, 1):
51
+ info = self.provider_info[provider_key]
52
+ strengths = ", ".join(info["strengths"][:2]) # Show first 2 strengths
53
+ table.add_row(str(i), info["name"], info["description"], strengths)
54
+
55
+ self.console.print(table)
56
+ self.console.print()
57
+
58
+ # Get user selection
59
+ while True:
60
+ try:
61
+ choice = Prompt.ask(
62
+ "Select a provider",
63
+ choices=[str(i) for i in range(1, len(providers) + 1)] + ["q"],
64
+ default="1",
65
+ )
66
+
67
+ if choice.lower() == "q":
68
+ return None
69
+
70
+ provider_index = int(choice) - 1
71
+ selected_provider = providers[provider_index]
72
+
73
+ # Show detailed provider information
74
+ self.ui.show_provider_details(selected_provider)
75
+
76
+ if Confirm.ask(
77
+ f"Configure {self.provider_info[selected_provider]['name']}?"
78
+ ):
79
+ return selected_provider
80
+
81
+ except (ValueError, IndexError):
82
+ self.console.print("[red]Invalid selection. Please try again.[/red]")
83
+
84
+ async def configure_provider(self, provider_name: str) -> Optional[ProviderConfig]:
85
+ """
86
+ Configure a specific provider with validation.
87
+
88
+ Args:
89
+ provider_name: Name of the provider to configure
90
+
91
+ Returns:
92
+ Configured ProviderConfig or None if failed
93
+ """
94
+ info = self.provider_info[provider_name]
95
+
96
+ self.console.print(f"\n[bold]Configuring {info['name']}[/bold]")
97
+ self.console.print(f"[dim]{info['setup_notes']}[/dim]")
98
+ self.console.print(f"[dim]Get your API key from: {info['api_key_url']}[/dim]\n")
99
+
100
+ # Get API key (if required)
101
+ api_key = None
102
+ if provider_name not in [
103
+ "ollama",
104
+ "claude-code",
105
+ ]: # Ollama and Claude Code don't need API key
106
+ api_key = self._get_api_key(provider_name, info)
107
+ if not api_key:
108
+ return None
109
+ elif provider_name == "claude-code":
110
+ # Claude Code uses local authentication, set a placeholder
111
+ api_key = "local"
112
+
113
+ # Select model
114
+ model = self._select_model(provider_name, info)
115
+ if not model:
116
+ return None
117
+
118
+ # Get additional settings
119
+ additional_settings = self._get_additional_settings(provider_name)
120
+
121
+ # Create provider config
122
+ config_data = {
123
+ "api_key": api_key,
124
+ "model": model,
125
+ **additional_settings,
126
+ }
127
+
128
+ return ProviderConfig(**config_data)
129
+
130
+ def _get_api_key(self, provider_name: str, info: Dict[str, Any]) -> Optional[str]:
131
+ """
132
+ Get and validate API key from user.
133
+
134
+ Args:
135
+ provider_name: Name of the provider
136
+ info: Provider information dictionary
137
+
138
+ Returns:
139
+ Valid API key or None if cancelled
140
+ """
141
+ while True:
142
+ api_key = Prompt.ask(f"Enter your {info['name']} API key", password=True)
143
+
144
+ if not api_key:
145
+ if not Confirm.ask("API key is required. Continue without it?"):
146
+ continue
147
+ return None
148
+
149
+ # Basic format validation
150
+ if info["api_key_prefix"] and not api_key.startswith(
151
+ info["api_key_prefix"]
152
+ ):
153
+ self.console.print(
154
+ f"[yellow]⚠️ API key should start with '{info['api_key_prefix']}'[/yellow]"
155
+ )
156
+ if not Confirm.ask("Continue with this API key anyway?"):
157
+ continue
158
+
159
+ return api_key
160
+
161
+ def _select_model(self, provider_name: str, info: Dict[str, Any]) -> Optional[str]:
162
+ """
163
+ Select model for the provider.
164
+
165
+ Args:
166
+ provider_name: Name of the provider
167
+ info: Provider information dictionary
168
+
169
+ Returns:
170
+ Selected model name or None if cancelled
171
+ """
172
+ models = info["models"]
173
+
174
+ # Allow custom model IDs for all providers
175
+ self.console.print(f"\n[bold]Model options for {info['name']}:[/bold]")
176
+ self.console.print("You can either:")
177
+ self.console.print(" 1. Select from common models")
178
+ self.console.print(" 2. Enter a custom model ID")
179
+ self.console.print()
180
+
181
+ choice = Prompt.ask("Choose option", choices=["1", "2"], default="1")
182
+
183
+ if choice == "2":
184
+ self.console.print(
185
+ f"\n[bold cyan]Custom Model ID Entry for {info['name']}:[/bold cyan]"
186
+ )
187
+
188
+ # Provider-specific examples
189
+ if provider_name == "bedrock":
190
+ self.console.print("Examples:")
191
+ self.console.print(
192
+ " • Model ID: [dim]anthropic.claude-opus-4-20250514-v1:0[/dim]"
193
+ )
194
+ self.console.print(
195
+ " • Inference Profile: [dim]us.anthropic.claude-opus-4-20250514-v1:0[/dim]"
196
+ )
197
+ self.console.print(
198
+ " • Foundation ARN: [dim]arn:aws:bedrock:us-west-2::foundation-model/meta.llama4-maverick-17b-instruct-v1:0[/dim]"
199
+ )
200
+ self.console.print(
201
+ " • Inference Profile ARN: [dim]arn:aws:bedrock:us-west-2::inference-profile/us.meta.llama4-maverick-17b-instruct-v1:0[/dim]"
202
+ )
203
+ elif provider_name == "openai":
204
+ self.console.print("Examples:")
205
+ self.console.print(" • [dim]gpt-4o[/dim]")
206
+ self.console.print(" • [dim]gpt-4o-mini[/dim]")
207
+ self.console.print(" • [dim]o1-preview[/dim]")
208
+ self.console.print(" • [dim]gpt-4-turbo[/dim]")
209
+ elif provider_name == "claude":
210
+ self.console.print("Examples:")
211
+ self.console.print(" • [dim]claude-sonnet-4-20250514[/dim]")
212
+ self.console.print(" • [dim]claude-opus-4-20250514[/dim]")
213
+ self.console.print(" • [dim]claude-3-5-sonnet-20241022[/dim]")
214
+ elif provider_name == "gemini":
215
+ self.console.print("Examples:")
216
+ self.console.print(" • [dim]gemini-1.5-pro[/dim]")
217
+ self.console.print(" • [dim]gemini-1.5-flash[/dim]")
218
+ self.console.print(" • [dim]gemini-2.0-flash-exp[/dim]")
219
+ elif provider_name == "openrouter":
220
+ self.console.print("Examples:")
221
+ self.console.print(" • [dim]anthropic/claude-3.5-sonnet[/dim]")
222
+ self.console.print(" • [dim]openai/gpt-4o[/dim]")
223
+ self.console.print(" • [dim]meta-llama/llama-3.1-70b-instruct[/dim]")
224
+ elif provider_name == "ollama":
225
+ self.console.print("Examples:")
226
+ self.console.print(" • [dim]llama3:8b[/dim]")
227
+ self.console.print(" • [dim]mistral:7b[/dim]")
228
+ self.console.print(" • [dim]codellama:13b[/dim]")
229
+ elif provider_name == "perplexity":
230
+ self.console.print("Examples:")
231
+ self.console.print(" • [dim]llama-3.1-sonar-large-128k-online[/dim]")
232
+ self.console.print(" • [dim]llama-3.1-sonar-small-128k-online[/dim]")
233
+ elif provider_name == "xai":
234
+ self.console.print("Examples:")
235
+ self.console.print(" • [dim]grok-beta[/dim]")
236
+ self.console.print(" • [dim]grok-vision-beta[/dim]")
237
+ elif provider_name == "mistral":
238
+ self.console.print("Examples:")
239
+ self.console.print(" • [dim]mistral-large-latest[/dim]")
240
+ self.console.print(" • [dim]mistral-small-latest[/dim]")
241
+ self.console.print(" • [dim]open-mistral-7b[/dim]")
242
+ elif provider_name == "cohere":
243
+ self.console.print("Examples:")
244
+ self.console.print(" • [dim]command-r[/dim]")
245
+ self.console.print(" • [dim]command-r-plus[/dim]")
246
+ self.console.print(" • [dim]command[/dim]")
247
+ elif provider_name == "azure":
248
+ self.console.print("Examples:")
249
+ self.console.print(" • [dim]gpt-4[/dim] (your deployment name)")
250
+ self.console.print(" • [dim]gpt-35-turbo[/dim] (your deployment name)")
251
+ self.console.print(
252
+ " • [dim]my-custom-model[/dim] (your deployment name)"
253
+ )
254
+ elif provider_name == "vertex":
255
+ self.console.print("Examples:")
256
+ self.console.print(" • [dim]gemini-1.5-pro[/dim]")
257
+ self.console.print(" • [dim]gemini-1.5-flash[/dim]")
258
+ self.console.print(" • [dim]gemini-pro[/dim]")
259
+ elif provider_name == "claude-code":
260
+ self.console.print("Examples:")
261
+ self.console.print(" • [dim]claude-code-sonnet[/dim]")
262
+ self.console.print(" • [dim]claude-code-opus[/dim]")
263
+ self.console.print(" • [dim]claude-code-haiku[/dim]")
264
+
265
+ self.console.print()
266
+
267
+ custom_model = Prompt.ask("Enter custom model ID")
268
+ if custom_model:
269
+ return custom_model
270
+ else:
271
+ self.console.print(
272
+ "[yellow]No model entered, falling back to common models...[/yellow]"
273
+ )
274
+
275
+ self.console.print(f"\n[bold]Common models for {info['name']}:[/bold]")
276
+
277
+ # Create model selection table
278
+ table = Table(show_header=True, header_style="bold magenta")
279
+ table.add_column("#", style="dim", width=3)
280
+ table.add_column("Model", style="bold")
281
+ table.add_column("Description", style="dim")
282
+
283
+ # Get model descriptions (simplified for now)
284
+ model_descriptions = {
285
+ "claude-3-5-sonnet-20241022": "Latest Claude model with enhanced capabilities",
286
+ "claude-3-haiku-20240307": "Fast and efficient Claude model",
287
+ "claude-3-opus-20240229": "Most capable Claude model",
288
+ "gpt-4o": "Latest GPT-4 with vision and tool use",
289
+ "gpt-4o-mini": "Efficient GPT-4 variant",
290
+ "gpt-4-turbo": "Enhanced GPT-4 with larger context",
291
+ "gemini-1.5-pro": "Advanced Gemini with large context",
292
+ "gemini-1.5-flash": "Fast Gemini variant",
293
+ "sonar-pro": "Advanced search-enabled model",
294
+ "grok-3": "Latest Grok model",
295
+ "mistral-small-3.1": "Efficient Mistral model",
296
+ "command-r": "Cohere's reasoning model",
297
+ "llama3.2": "Meta's latest Llama model",
298
+ # Bedrock specific models
299
+ "anthropic.claude-3-sonnet-20240229-v1:0": "Claude 3 Sonnet on Bedrock",
300
+ "anthropic.claude-3-haiku-20240307-v1:0": "Claude 3 Haiku on Bedrock",
301
+ "meta.llama3-1-8b-instruct-v1:0": "Llama 3.1 8B on Bedrock",
302
+ "arn:aws:bedrock:us-west-2::foundation-model/meta.llama4-maverick-17b-instruct-v1:0": "Meta Llama 4 Maverick (ARN)",
303
+ }
304
+
305
+ for i, model in enumerate(models, 1):
306
+ description = model_descriptions.get(model, "High-quality AI model")
307
+ table.add_row(str(i), model, description)
308
+
309
+ # Add custom option for non-Bedrock providers
310
+ if provider_name != "bedrock":
311
+ table.add_row(str(len(models) + 1), "[Custom]", "Enter custom model name")
312
+
313
+ self.console.print(table)
314
+
315
+ # Get user selection
316
+ while True:
317
+ try:
318
+ max_choice = len(models) + (1 if provider_name != "bedrock" else 0)
319
+ choice = Prompt.ask(
320
+ "Select a model",
321
+ choices=[str(i) for i in range(1, max_choice + 1)],
322
+ default="1",
323
+ )
324
+
325
+ choice_num = int(choice)
326
+
327
+ # Handle custom option for non-Bedrock providers
328
+ if provider_name != "bedrock" and choice_num == len(models) + 1:
329
+ custom_model = Prompt.ask("Enter custom model name")
330
+ if custom_model:
331
+ return custom_model
332
+ else:
333
+ continue
334
+
335
+ model_index = choice_num - 1
336
+ return models[model_index]
337
+
338
+ except (ValueError, IndexError):
339
+ self.console.print("[red]Invalid selection. Please try again.[/red]")
340
+
341
+ def _get_additional_settings(self, provider_name: str) -> Dict[str, Any]:
342
+ """
343
+ Get additional provider-specific settings.
344
+
345
+ Args:
346
+ provider_name: Name of the provider
347
+
348
+ Returns:
349
+ Dictionary of additional settings
350
+ """
351
+ settings = {}
352
+
353
+ # Common settings
354
+ if Confirm.ask("Configure advanced settings?", default=False):
355
+ # Temperature
356
+ temp_str = Prompt.ask("Temperature (0.0-1.0)", default="0.7")
357
+ try:
358
+ settings["temperature"] = float(temp_str)
359
+ except ValueError:
360
+ settings["temperature"] = 0.7
361
+
362
+ # Max tokens
363
+ tokens_str = Prompt.ask("Max tokens", default="4096")
364
+ try:
365
+ settings["max_tokens"] = int(tokens_str)
366
+ except ValueError:
367
+ settings["max_tokens"] = 4096
368
+
369
+ # Provider-specific settings
370
+ if provider_name == "ollama":
371
+ base_url = Prompt.ask("Ollama server URL", default="http://localhost:11434")
372
+ settings["base_url"] = base_url
373
+ elif provider_name == "bedrock":
374
+ # AWS region selection is required for Bedrock
375
+ regions = [
376
+ "us-east-1",
377
+ "us-west-2",
378
+ "eu-west-1",
379
+ "eu-central-1",
380
+ "ap-southeast-1",
381
+ "ap-northeast-1",
382
+ "ap-south-1",
383
+ ]
384
+
385
+ self.console.print("\n[bold]Available AWS Regions:[/bold]")
386
+ for i, region in enumerate(regions, 1):
387
+ self.console.print(f" {i}. {region}")
388
+
389
+ while True:
390
+ region_choice = Prompt.ask(
391
+ f"Select AWS region [1-{len(regions)}]", default="1"
392
+ )
393
+ try:
394
+ region_idx = int(region_choice) - 1
395
+ if 0 <= region_idx < len(regions):
396
+ settings["aws_region"] = regions[region_idx]
397
+ break
398
+ else:
399
+ self.console.print(
400
+ "[red]Invalid selection. Please try again.[/red]"
401
+ )
402
+ except ValueError:
403
+ self.console.print("[red]Please enter a number.[/red]")
404
+
405
+ return settings
406
+
407
+ def save_configuration(
408
+ self,
409
+ provider_name: str,
410
+ provider_config: ProviderConfig,
411
+ config_manager,
412
+ ) -> None:
413
+ """
414
+ Save the configuration, adding the provider without overwriting existing ones.
415
+
416
+ Args:
417
+ provider_name: Name of the provider
418
+ provider_config: Provider configuration to save
419
+ config_manager: Configuration manager instance
420
+ """
421
+ try:
422
+ # Check if this is the first provider being configured
423
+ config = config_manager.get_config()
424
+ is_first_provider = not config.providers
425
+
426
+ # Add or update the provider configuration
427
+ config_manager.set_provider_config(provider_name, provider_config)
428
+
429
+ # Set as default provider if it's the first one or if user confirms
430
+ if is_first_provider:
431
+ config_manager.set_default_provider(provider_name)
432
+ self.console.print(
433
+ f"[green]✅ {self.provider_info[provider_name]['name']} set as default provider![/green]"
434
+ )
435
+ else:
436
+ # Ask if user wants to set this as the default provider
437
+ if Confirm.ask(
438
+ f"Set {self.provider_info[provider_name]['name']} as your default provider?",
439
+ default=False,
440
+ ):
441
+ config_manager.set_default_provider(provider_name)
442
+ self.console.print(
443
+ f"[green]✅ {self.provider_info[provider_name]['name']} set as default provider![/green]"
444
+ )
445
+ else:
446
+ self.console.print(
447
+ f"[green]✅ {self.provider_info[provider_name]['name']} added to your providers![/green]"
448
+ )
449
+
450
+ self.console.print("[green]✅ Configuration saved successfully![/green]")
451
+
452
+ except Exception as e:
453
+ self.console.print(f"[red]❌ Failed to save configuration: {e}[/red]")
454
+ raise