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,1974 @@
1
+ """
2
+ Comprehensive Configuration Provider for Omnimancer.
3
+
4
+ This module consolidates configuration management, validation, compatibility,
5
+ and user interfaces into a single cohesive system that replaces the functionality
6
+ of multiple overlapping configuration modules.
7
+ """
8
+
9
+ import json
10
+ import logging
11
+ from dataclasses import asdict, dataclass
12
+ from datetime import datetime
13
+ from enum import Enum
14
+ from pathlib import Path
15
+ from typing import Any, Dict, List, Optional, Tuple
16
+
17
+ from .config_manager import ConfigManager
18
+ from .config_validator import ConfigValidator
19
+
20
+ # from .config_migration_helpers import ConfigMigrationHelper # Removed - over-engineered
21
+ from .models import (
22
+ Config,
23
+ ConfigTemplate,
24
+ ConfigTemplateManager,
25
+ ProviderConfig,
26
+ )
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class ConfigurationMode(Enum):
32
+ """Configuration modes for different user experience levels."""
33
+
34
+ SIMPLE = "simple"
35
+ ADVANCED = "advanced"
36
+ GUIDED = "guided"
37
+
38
+
39
+ class ConfigurationComplexity(Enum):
40
+ """Complexity levels for configuration options."""
41
+
42
+ BASIC = "basic"
43
+ INTERMEDIATE = "intermediate"
44
+ ADVANCED = "advanced"
45
+ EXPERT = "expert"
46
+
47
+
48
+ @dataclass
49
+ class ConfigurationContext:
50
+ """Context information for configuration operations."""
51
+
52
+ user_level: str = "beginner"
53
+ use_case: str = "general"
54
+ primary_providers: List[str] = None
55
+ preferred_mode: ConfigurationMode = ConfigurationMode.SIMPLE
56
+ show_experimental: bool = False
57
+
58
+ def __post_init__(self):
59
+ if self.primary_providers is None:
60
+ self.primary_providers = []
61
+
62
+
63
+ @dataclass
64
+ class ValidationReport:
65
+ """Comprehensive validation and compatibility report."""
66
+
67
+ is_valid: bool
68
+ is_compatible: bool
69
+ compatibility_level: str
70
+ total_issues: int
71
+ critical_errors: List[str]
72
+ warnings: List[str]
73
+ suggestions: List[str]
74
+ health_score: float # 0-100
75
+ format_info: Dict[str, Any]
76
+ provider_status: Dict[str, Dict[str, Any]]
77
+ performance_issues: List[str]
78
+ security_issues: List[str]
79
+ upgrade_recommendations: List[str]
80
+ migration_required: bool
81
+ backup_available: bool
82
+ timestamp: str
83
+
84
+ def __post_init__(self):
85
+ if not self.timestamp:
86
+ self.timestamp = datetime.now().isoformat()
87
+
88
+
89
+ @dataclass
90
+ class SimplifiedProviderConfig:
91
+ """Simplified provider configuration for simple mode."""
92
+
93
+ name: str
94
+ display_name: str
95
+ description: str
96
+ enabled: bool = True
97
+ api_key: Optional[str] = None
98
+ model: Optional[str] = None
99
+ use_case_fit: List[str] = None
100
+
101
+ def __post_init__(self):
102
+ if self.use_case_fit is None:
103
+ self.use_case_fit = ["general"]
104
+
105
+
106
+ @dataclass
107
+ class ProviderHealthCheck:
108
+ """Provider health check result."""
109
+
110
+ name: str
111
+ status: str # "healthy", "warning", "error", "unknown"
112
+ api_key_present: bool
113
+ model_valid: bool
114
+ connectivity_ok: bool
115
+ issues: List[str]
116
+ recommendations: List[str]
117
+ last_tested: str
118
+
119
+
120
+ @dataclass
121
+ class ConfigurationRecommendation:
122
+ """Recommendation for configuration based on user context."""
123
+
124
+ template_name: str
125
+ confidence: float
126
+ reasons: List[str]
127
+ providers: List[str]
128
+ estimated_cost: str
129
+ complexity: ConfigurationComplexity
130
+
131
+
132
+ @dataclass
133
+ class SimpleConfigOption:
134
+ """Represents a simple configuration option for users."""
135
+
136
+ key: str
137
+ display_name: str
138
+ description: str
139
+ option_type: str # "choice", "boolean", "text", "password"
140
+ default_value: Any = None
141
+ choices: Optional[List[Dict[str, str]]] = None
142
+ required: bool = True
143
+ complexity: ConfigurationComplexity = ConfigurationComplexity.BASIC
144
+ category: str = "General"
145
+
146
+ def __post_init__(self):
147
+ if self.choices is None:
148
+ self.choices = []
149
+
150
+
151
+ @dataclass
152
+ class SimpleConfigSection:
153
+ """A section of related configuration options."""
154
+
155
+ name: str
156
+ description: str
157
+ icon: str
158
+ options: List[SimpleConfigOption]
159
+ required: bool = True
160
+ expanded: bool = True
161
+
162
+
163
+ class ConfigurationProvider:
164
+ """
165
+ Comprehensive configuration provider that consolidates all configuration functionality.
166
+
167
+ This class provides:
168
+ - Configuration management and validation
169
+ - Compatibility checking and migration
170
+ - Simple and advanced user interfaces
171
+ - Provider health monitoring
172
+ - Template-based configuration
173
+ """
174
+
175
+ def __init__(self, config_manager: Optional[ConfigManager] = None):
176
+ """
177
+ Initialize the configuration provider.
178
+
179
+ Args:
180
+ config_manager: Optional ConfigManager instance
181
+ """
182
+ self.config_manager = config_manager or ConfigManager()
183
+ self.template_manager = ConfigTemplateManager()
184
+ self.base_validator = ConfigValidator()
185
+ # self.migration_helper = ConfigMigrationHelper(self.config_manager) # Removed - over-engineered
186
+
187
+ # Configuration state
188
+ self.current_mode = ConfigurationMode.SIMPLE
189
+ self.context = ConfigurationContext()
190
+ self.current_version = "2.0"
191
+ self.legacy_version = "1.0"
192
+
193
+ # Load mode preferences
194
+ self._load_mode_preferences()
195
+
196
+ # Initialize simple options
197
+ self.simple_sections = self._create_simple_sections()
198
+
199
+ # =============================================================================
200
+ # Mode Management
201
+ # =============================================================================
202
+
203
+ def set_configuration_mode(self, mode: ConfigurationMode) -> None:
204
+ """Set the configuration mode."""
205
+ self.current_mode = mode
206
+ self._save_mode_preferences()
207
+ logger.info(f"Configuration mode set to: {mode.value}")
208
+
209
+ def set_configuration_context(self, context: ConfigurationContext) -> None:
210
+ """Set the configuration context for personalized experience."""
211
+ self.context = context
212
+ self._save_mode_preferences()
213
+ logger.info(
214
+ f"Configuration context updated: use_case={context.use_case}, level={context.user_level}"
215
+ )
216
+
217
+ def get_available_modes(self) -> Dict[str, Dict[str, Any]]:
218
+ """Get information about available configuration modes."""
219
+ return {
220
+ ConfigurationMode.SIMPLE.value: {
221
+ "name": "Simple",
222
+ "description": "Template-based configuration with minimal technical details",
223
+ "target_audience": "Beginners and users who want quick setup",
224
+ "features": [
225
+ "Pre-configured templates for common use cases",
226
+ "Automatic provider selection and optimization",
227
+ "Simplified settings with explanations",
228
+ "One-click setup for popular configurations",
229
+ ],
230
+ },
231
+ ConfigurationMode.GUIDED.value: {
232
+ "name": "Guided",
233
+ "description": "Step-by-step configuration with recommendations",
234
+ "target_audience": "Users who want some control with guidance",
235
+ "features": [
236
+ "Interactive configuration wizard",
237
+ "Smart recommendations based on usage patterns",
238
+ "Explanations for configuration choices",
239
+ "Ability to customize templates",
240
+ ],
241
+ },
242
+ ConfigurationMode.ADVANCED.value: {
243
+ "name": "Advanced",
244
+ "description": "Full access to all configuration options",
245
+ "target_audience": "Power users and developers",
246
+ "features": [
247
+ "Complete configuration control",
248
+ "Access to all provider settings",
249
+ "Custom MCP server configuration",
250
+ "Advanced debugging options",
251
+ ],
252
+ },
253
+ }
254
+
255
+ # =============================================================================
256
+ # Validation & Compatibility
257
+ # =============================================================================
258
+
259
+ async def comprehensive_validation(
260
+ self, config_path: Optional[Path] = None
261
+ ) -> ValidationReport:
262
+ """Perform comprehensive configuration validation and compatibility checking."""
263
+ try:
264
+ # Detect configuration format and compatibility
265
+ format_info = self._detect_configuration_format(config_path)
266
+ is_compatible, comp_errors, comp_warnings = self._validate_compatibility(
267
+ config_path
268
+ )
269
+
270
+ # Load configuration safely
271
+ config = await self._load_config_safely(config_path)
272
+
273
+ # Perform base validation
274
+ base_errors = []
275
+ if config:
276
+ base_errors = self.base_validator.validate_config(config)
277
+
278
+ # Combine all errors and warnings
279
+ all_errors = comp_errors + base_errors
280
+ critical_errors = [e for e in all_errors if self._is_critical_error(e)]
281
+ all_warnings = comp_warnings
282
+
283
+ # Perform provider health checks
284
+ provider_status = (
285
+ await self._check_provider_health(config) if config else {}
286
+ )
287
+
288
+ # Check for performance and security issues
289
+ performance_issues = (
290
+ self._check_performance_issues(config, format_info) if config else []
291
+ )
292
+ security_issues = self._check_security_issues(config) if config else []
293
+
294
+ # Generate suggestions and upgrade recommendations
295
+ suggestions = self._generate_suggestions(
296
+ config, format_info, all_errors, all_warnings
297
+ )
298
+ upgrade_recommendations = self._generate_upgrade_recommendations(
299
+ format_info
300
+ )
301
+
302
+ # Calculate health score
303
+ health_score = self._calculate_health_score(
304
+ critical_errors,
305
+ all_warnings,
306
+ provider_status,
307
+ performance_issues,
308
+ security_issues,
309
+ )
310
+
311
+ # Check for migration and backup
312
+ migration_required = (
313
+ len(all_errors) > 3
314
+ or format_info.get("version", self.current_version)
315
+ < self.current_version
316
+ )
317
+ backup_available = self._check_backup_availability()
318
+
319
+ return ValidationReport(
320
+ is_valid=len(critical_errors) == 0,
321
+ is_compatible=is_compatible,
322
+ compatibility_level=format_info.get("compatibility_level", "unknown"),
323
+ total_issues=len(all_errors) + len(all_warnings),
324
+ critical_errors=critical_errors,
325
+ warnings=all_warnings,
326
+ suggestions=suggestions,
327
+ health_score=health_score,
328
+ format_info=format_info,
329
+ provider_status=provider_status,
330
+ performance_issues=performance_issues,
331
+ security_issues=security_issues,
332
+ upgrade_recommendations=upgrade_recommendations,
333
+ migration_required=migration_required,
334
+ backup_available=backup_available,
335
+ timestamp=datetime.now().isoformat(),
336
+ )
337
+
338
+ except Exception as e:
339
+ logger.error(f"Comprehensive validation failed: {e}")
340
+ return ValidationReport(
341
+ is_valid=False,
342
+ is_compatible=False,
343
+ compatibility_level="unknown",
344
+ total_issues=1,
345
+ critical_errors=[f"Validation failed: {str(e)}"],
346
+ warnings=[],
347
+ suggestions=["Check configuration file format and permissions"],
348
+ health_score=0.0,
349
+ format_info={},
350
+ provider_status={},
351
+ performance_issues=[],
352
+ security_issues=[],
353
+ upgrade_recommendations=[],
354
+ migration_required=True,
355
+ backup_available=False,
356
+ timestamp=datetime.now().isoformat(),
357
+ )
358
+
359
+ def _detect_configuration_format(
360
+ self, config_path: Optional[Path] = None
361
+ ) -> Dict[str, Any]:
362
+ """Detect the format and version of an existing configuration."""
363
+ if config_path is None:
364
+ config_path = self.config_manager.get_storage_path() / "config.json"
365
+
366
+ try:
367
+ if not config_path.exists():
368
+ return {
369
+ "version": "none",
370
+ "format_type": "none",
371
+ "detected_features": [],
372
+ "compatibility_level": "full",
373
+ "upgrade_available": False,
374
+ "issues": ["No configuration file found"],
375
+ }
376
+
377
+ with open(config_path, "r") as f:
378
+ config_data = json.load(f)
379
+
380
+ return self._analyze_config_format(config_data)
381
+
382
+ except Exception as e:
383
+ logger.error(f"Failed to detect configuration format: {e}")
384
+ return {
385
+ "version": "unknown",
386
+ "format_type": "unknown",
387
+ "detected_features": [],
388
+ "compatibility_level": "incompatible",
389
+ "upgrade_available": False,
390
+ "issues": [f"Failed to read configuration: {str(e)}"],
391
+ }
392
+
393
+ def _analyze_config_format(self, config_data: Dict[str, Any]) -> Dict[str, Any]:
394
+ """Analyze configuration data to determine format."""
395
+ detected_features = []
396
+ format_type = "legacy"
397
+ version = "1.0"
398
+ compatibility_level = "full"
399
+ upgrade_available = True
400
+ issues = []
401
+
402
+ # Check for version information
403
+ if "config_version" in config_data:
404
+ version = config_data["config_version"]
405
+ elif "_schema_version" in config_data:
406
+ version = config_data["_schema_version"]
407
+
408
+ # Detect features based on configuration structure
409
+ feature_checks = {
410
+ "providers": "providers",
411
+ "default_provider": "default_provider",
412
+ "mcp": "mcp",
413
+ "chat_settings": "chat_settings",
414
+ "configuration_mode": "configuration_mode",
415
+ "user_preferences": "user_preferences",
416
+ "template_source": "template_source",
417
+ }
418
+
419
+ for feature, key in feature_checks.items():
420
+ if key in config_data:
421
+ detected_features.append(feature)
422
+
423
+ # Determine format type
424
+ if "configuration_mode" in detected_features:
425
+ format_type = "advanced"
426
+ elif (
427
+ "user_preferences" in detected_features
428
+ or "template_source" in detected_features
429
+ ):
430
+ format_type = "simple"
431
+
432
+ # Check for issues
433
+ if "providers" in config_data and len(config_data["providers"]) > 5:
434
+ issues.append(
435
+ f"Large number of providers ({len(config_data['providers'])}) might benefit from simplification"
436
+ )
437
+
438
+ # Check for deprecated fields
439
+ deprecated_fields = [
440
+ "legacy_auth",
441
+ "old_mcp_format",
442
+ "deprecated_settings",
443
+ ]
444
+ for field in deprecated_fields:
445
+ if field in config_data:
446
+ issues.append(f"Deprecated field '{field}' found")
447
+ compatibility_level = "partial"
448
+
449
+ return {
450
+ "version": version,
451
+ "format_type": format_type,
452
+ "detected_features": detected_features,
453
+ "compatibility_level": compatibility_level,
454
+ "upgrade_available": upgrade_available and version < self.current_version,
455
+ "issues": issues,
456
+ }
457
+
458
+ def _validate_compatibility(
459
+ self, config_path: Optional[Path] = None
460
+ ) -> Tuple[bool, List[str], List[str]]:
461
+ """Validate configuration compatibility."""
462
+ errors = []
463
+ warnings = []
464
+
465
+ try:
466
+ format_info = self._detect_configuration_format(config_path)
467
+
468
+ if format_info["compatibility_level"] == "incompatible":
469
+ errors.append(
470
+ "Configuration format is incompatible and requires manual migration"
471
+ )
472
+ errors.extend(format_info["issues"])
473
+ return False, errors, warnings
474
+
475
+ elif format_info["compatibility_level"] == "partial":
476
+ warnings.append(
477
+ "Configuration has compatibility issues that may need attention"
478
+ )
479
+ warnings.extend(format_info["issues"])
480
+
481
+ # Load and validate configuration
482
+ if config_path is None:
483
+ config_path = self.config_manager.get_storage_path() / "config.json"
484
+
485
+ if not config_path.exists():
486
+ errors.append("Configuration file not found")
487
+ return False, errors, warnings
488
+
489
+ try:
490
+ config = self.config_manager.load_config()
491
+ validation_errors = self.base_validator.validate_config(config)
492
+
493
+ if validation_errors:
494
+ for error in validation_errors:
495
+ if any(
496
+ critical in error.lower()
497
+ for critical in ["missing", "required", "invalid"]
498
+ ):
499
+ errors.append(error)
500
+ else:
501
+ warnings.append(error)
502
+
503
+ except Exception as e:
504
+ errors.append(f"Configuration loading failed: {str(e)}")
505
+ return False, errors, warnings
506
+
507
+ if format_info["upgrade_available"]:
508
+ warnings.append(
509
+ f"Configuration upgrade available: v{format_info['version']} → v{self.current_version}"
510
+ )
511
+
512
+ is_compatible = len(errors) == 0
513
+ return is_compatible, errors, warnings
514
+
515
+ except Exception as e:
516
+ logger.error(f"Compatibility validation failed: {e}")
517
+ errors.append(f"Validation failed: {str(e)}")
518
+ return False, errors, warnings
519
+
520
+ async def _load_config_safely(
521
+ self, config_path: Optional[Path] = None
522
+ ) -> Optional[Config]:
523
+ """Safely load configuration without failing validation."""
524
+ try:
525
+ if config_path:
526
+ with open(config_path, "r") as f:
527
+ config_data = json.load(f)
528
+ return self.config_manager.load_config_from_dict(config_data)
529
+ else:
530
+ return self.config_manager.load_config()
531
+ except Exception as e:
532
+ logger.debug(f"Failed to load configuration: {e}")
533
+ return None
534
+
535
+ def _is_critical_error(self, error: str) -> bool:
536
+ """Determine if an error is critical."""
537
+ critical_keywords = [
538
+ "missing api key",
539
+ "no providers configured",
540
+ "default provider not configured",
541
+ "invalid storage path",
542
+ "configuration loading failed",
543
+ "incompatible",
544
+ ]
545
+ return any(keyword in error.lower() for keyword in critical_keywords)
546
+
547
+ async def _check_provider_health(self, config: Config) -> Dict[str, Dict[str, Any]]:
548
+ """Check health of all configured providers."""
549
+ provider_status = {}
550
+
551
+ for provider_name, provider_config in config.providers.items():
552
+ health_check = await self._check_single_provider_health(
553
+ provider_name, provider_config
554
+ )
555
+ provider_status[provider_name] = asdict(health_check)
556
+
557
+ return provider_status
558
+
559
+ async def _check_single_provider_health(
560
+ self, provider_name: str, provider_config: ProviderConfig
561
+ ) -> ProviderHealthCheck:
562
+ """Check health of a single provider."""
563
+ issues = []
564
+ recommendations = []
565
+ status = "healthy"
566
+
567
+ # Check API key
568
+ api_key_present = bool(provider_config.api_key)
569
+ if not api_key_present and provider_name not in [
570
+ "claude-code",
571
+ "ollama",
572
+ ]:
573
+ issues.append("API key missing")
574
+ recommendations.append("Add API key for this provider")
575
+ status = "error"
576
+
577
+ # Check model validity
578
+ model_valid = bool(provider_config.model)
579
+ if not model_valid:
580
+ issues.append("No model specified")
581
+ recommendations.append("Specify a valid model for this provider")
582
+ status = "error"
583
+
584
+ # Check model name validity
585
+ if model_valid and provider_config.model:
586
+ model_issues = self._validate_model_name(
587
+ provider_name, provider_config.model
588
+ )
589
+ if model_issues:
590
+ issues.extend(model_issues)
591
+ recommendations.append("Update to a supported model name")
592
+ if status == "healthy":
593
+ status = "warning"
594
+
595
+ # Check connectivity
596
+ connectivity_ok = api_key_present and model_valid
597
+ if not connectivity_ok and status == "healthy":
598
+ status = "warning"
599
+
600
+ # Check provider-specific issues
601
+ provider_specific_issues = self._check_provider_specific_issues(
602
+ provider_name, provider_config
603
+ )
604
+ if provider_specific_issues:
605
+ issues.extend(provider_specific_issues)
606
+ if status == "healthy":
607
+ status = "warning"
608
+
609
+ return ProviderHealthCheck(
610
+ name=provider_name,
611
+ status=status,
612
+ api_key_present=api_key_present,
613
+ model_valid=model_valid,
614
+ connectivity_ok=connectivity_ok,
615
+ issues=issues,
616
+ recommendations=recommendations,
617
+ last_tested=datetime.now().isoformat(),
618
+ )
619
+
620
+ def _validate_model_name(self, provider_name: str, model_name: str) -> List[str]:
621
+ """Validate model name for a provider."""
622
+ issues = []
623
+
624
+ known_models = {
625
+ "claude": [
626
+ "claude-3-sonnet-20240229",
627
+ "claude-3-haiku-20240307",
628
+ "claude-3-opus-20240229",
629
+ "claude-3-5-sonnet-20241022",
630
+ ],
631
+ "openai": [
632
+ "gpt-4",
633
+ "gpt-4-turbo",
634
+ "gpt-4o",
635
+ "gpt-4o-mini",
636
+ "gpt-3.5-turbo",
637
+ ],
638
+ "gemini": ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-1.0-pro"],
639
+ "perplexity": ["sonar-pro", "sonar", "deep-research"],
640
+ "mistral": ["mistral-small", "mistral-medium", "mistral-large"],
641
+ }
642
+
643
+ if provider_name in known_models:
644
+ valid_models = known_models[provider_name]
645
+ if not any(model in model_name for model in valid_models):
646
+ issues.append(
647
+ f"Model '{model_name}' may not be valid for {provider_name}"
648
+ )
649
+
650
+ return issues
651
+
652
+ def _check_provider_specific_issues(
653
+ self, provider_name: str, provider_config: ProviderConfig
654
+ ) -> List[str]:
655
+ """Check provider-specific configuration issues."""
656
+ issues = []
657
+
658
+ if provider_name.lower() == "azure":
659
+ if (
660
+ not hasattr(provider_config, "azure_endpoint")
661
+ or not provider_config.azure_endpoint
662
+ ):
663
+ issues.append("Azure endpoint not specified")
664
+ if (
665
+ not hasattr(provider_config, "azure_deployment")
666
+ or not provider_config.azure_deployment
667
+ ):
668
+ issues.append("Azure deployment not specified")
669
+ elif provider_name.lower() == "bedrock":
670
+ if (
671
+ not hasattr(provider_config, "aws_region")
672
+ or not provider_config.aws_region
673
+ ):
674
+ issues.append("AWS region not specified")
675
+ elif provider_name.lower() == "ollama":
676
+ if hasattr(provider_config, "base_url") and provider_config.base_url:
677
+ if not provider_config.base_url.startswith(("http://", "https://")):
678
+ issues.append("Invalid Ollama base URL format")
679
+
680
+ return issues
681
+
682
+ def _check_performance_issues(
683
+ self, config: Config, format_info: Dict[str, Any]
684
+ ) -> List[str]:
685
+ """Check for performance-related configuration issues."""
686
+ issues = []
687
+
688
+ if len(config.providers) > 8:
689
+ issues.append(
690
+ f"Large number of providers ({len(config.providers)}) may impact performance"
691
+ )
692
+
693
+ timeout_issues = sum(
694
+ 1
695
+ for provider_config in config.providers.values()
696
+ if not hasattr(provider_config, "timeout") or not provider_config.timeout
697
+ )
698
+
699
+ if timeout_issues > len(config.providers) / 2:
700
+ issues.append("Many providers missing timeout configurations")
701
+
702
+ if hasattr(config, "mcp") and config.mcp and hasattr(config.mcp, "servers"):
703
+ if len(config.mcp.servers) > 10:
704
+ issues.append(
705
+ f"Large number of MCP servers ({len(config.mcp.servers)}) may impact startup time"
706
+ )
707
+
708
+ if (
709
+ hasattr(config, "configuration_mode")
710
+ and config.configuration_mode == "simple"
711
+ ):
712
+ if len(config.providers) > 3:
713
+ issues.append("Simple mode with many providers may cause confusion")
714
+
715
+ return issues
716
+
717
+ def _check_security_issues(self, config: Config) -> List[str]:
718
+ """Check for security-related configuration issues."""
719
+ issues = []
720
+
721
+ for provider_name, provider_config in config.providers.items():
722
+ if provider_config.api_key:
723
+ if len(provider_config.api_key) < 10:
724
+ issues.append(f"Suspiciously short API key for {provider_name}")
725
+ elif provider_config.api_key.startswith(("demo", "test", "example")):
726
+ issues.append(f"Possible demo/test API key for {provider_name}")
727
+
728
+ if hasattr(config, "mcp") and config.mcp:
729
+ if hasattr(config.mcp, "auto_approve") and config.mcp.auto_approve:
730
+ issues.append("MCP auto-approve enabled - may pose security risk")
731
+
732
+ return issues
733
+
734
+ def _generate_suggestions(
735
+ self,
736
+ config: Optional[Config],
737
+ format_info: Dict[str, Any],
738
+ errors: List[str],
739
+ warnings: List[str],
740
+ ) -> List[str]:
741
+ """Generate helpful suggestions for configuration improvement."""
742
+ suggestions = []
743
+
744
+ if any("no providers" in error.lower() for error in errors):
745
+ suggestions.append(
746
+ "Run 'omnimancer config setup' to configure AI providers"
747
+ )
748
+
749
+ if any("api key" in error.lower() for error in errors):
750
+ suggestions.append("Add missing API keys for enabled providers")
751
+
752
+ if any("model" in error.lower() for error in errors):
753
+ suggestions.append("Specify valid models for all providers")
754
+
755
+ if format_info.get("upgrade_available", False):
756
+ suggestions.append(
757
+ f"Upgrade configuration from v{format_info.get('version')} to v{self.current_version}"
758
+ )
759
+
760
+ if (
761
+ format_info.get("format_type") == "legacy"
762
+ and len(format_info.get("detected_features", [])) > 5
763
+ ):
764
+ suggestions.append(
765
+ "Consider using simplified configuration mode for easier management"
766
+ )
767
+
768
+ if config and len(config.providers) == 1:
769
+ suggestions.append("Consider adding a backup provider for redundancy")
770
+ elif config and len(config.providers) > 5:
771
+ suggestions.append(
772
+ "Consider simplifying provider configuration for better performance"
773
+ )
774
+
775
+ if len(warnings) > 3:
776
+ suggestions.append("Review and address configuration warnings")
777
+
778
+ return suggestions
779
+
780
+ def _generate_upgrade_recommendations(
781
+ self, format_info: Dict[str, Any]
782
+ ) -> List[str]:
783
+ """Generate upgrade recommendations."""
784
+ recommendations = []
785
+
786
+ if format_info.get("upgrade_available", False):
787
+ compatibility_level = format_info.get("compatibility_level", "unknown")
788
+ if compatibility_level == "full":
789
+ recommendations.append(
790
+ "Automatic upgrade available - run 'omnimancer config migrate'"
791
+ )
792
+ elif compatibility_level == "partial":
793
+ recommendations.append(
794
+ "Assisted upgrade recommended - review configuration issues first"
795
+ )
796
+ else:
797
+ recommendations.append(
798
+ "Manual upgrade required - backup configuration before changes"
799
+ )
800
+
801
+ if (
802
+ "mcp" in format_info.get("detected_features", [])
803
+ and format_info.get("version", "2.0") < "2.0"
804
+ ):
805
+ recommendations.append(
806
+ "MCP configuration can be simplified in newer format"
807
+ )
808
+
809
+ if len(format_info.get("detected_features", [])) > 6:
810
+ recommendations.append(
811
+ "Configuration complexity can be reduced with template-based setup"
812
+ )
813
+
814
+ return recommendations
815
+
816
+ def _calculate_health_score(
817
+ self,
818
+ critical_errors: List[str],
819
+ warnings: List[str],
820
+ provider_status: Dict[str, Dict[str, Any]],
821
+ performance_issues: List[str],
822
+ security_issues: List[str],
823
+ ) -> float:
824
+ """Calculate overall configuration health score (0-100)."""
825
+ score = 100.0
826
+
827
+ score -= len(critical_errors) * 25
828
+ score -= len(warnings) * 5
829
+
830
+ for provider_name, status in provider_status.items():
831
+ if status["status"] == "error":
832
+ score -= 15
833
+ elif status["status"] == "warning":
834
+ score -= 5
835
+
836
+ score -= len(performance_issues) * 3
837
+ score -= len(security_issues) * 10
838
+
839
+ return max(0.0, min(100.0, score))
840
+
841
+ def generate_compatibility_report(self) -> str:
842
+ """Generate a comprehensive compatibility report."""
843
+ try:
844
+ # Get comprehensive validation report
845
+ import asyncio
846
+
847
+ validation_report = asyncio.run(self.comprehensive_validation())
848
+
849
+ report_lines = []
850
+ report_lines.append("# Configuration Compatibility Report")
851
+ report_lines.append("")
852
+
853
+ # Basic info
854
+ report_lines.append("## Configuration Status")
855
+ report_lines.append(
856
+ f"- Version: {validation_report.format_info.get('version', 'unknown')}"
857
+ )
858
+ report_lines.append(
859
+ f"- Format Type: {validation_report.format_info.get('format_type', 'unknown').title()}"
860
+ )
861
+ report_lines.append(
862
+ f"- Compatible: {'Yes' if validation_report.is_compatible else 'No'}"
863
+ )
864
+ report_lines.append(
865
+ f"- Valid: {'Yes' if validation_report.is_valid else 'No'}"
866
+ )
867
+ report_lines.append(
868
+ f"- Health Score: {validation_report.health_score:.1f}/100"
869
+ )
870
+ report_lines.append("")
871
+
872
+ # Issues
873
+ if validation_report.critical_errors:
874
+ report_lines.append("## Critical Errors")
875
+ for error in validation_report.critical_errors:
876
+ report_lines.append(f"- {error}")
877
+ report_lines.append("")
878
+
879
+ if validation_report.warnings:
880
+ report_lines.append("## Warnings")
881
+ for warning in validation_report.warnings:
882
+ report_lines.append(f"- {warning}")
883
+ report_lines.append("")
884
+
885
+ # Recommendations
886
+ if (
887
+ validation_report.suggestions
888
+ or validation_report.upgrade_recommendations
889
+ ):
890
+ report_lines.append("## Recommendations")
891
+ for suggestion in validation_report.suggestions:
892
+ report_lines.append(f"- {suggestion}")
893
+ for upgrade in validation_report.upgrade_recommendations:
894
+ report_lines.append(f"- {upgrade}")
895
+ report_lines.append("")
896
+
897
+ # Provider details
898
+ if validation_report.provider_status:
899
+ report_lines.append("## Provider Status")
900
+ for (
901
+ provider,
902
+ status,
903
+ ) in validation_report.provider_status.items():
904
+ status_icon = (
905
+ "✓"
906
+ if status["status"] == "healthy"
907
+ else "⚠" if status["status"] == "warning" else "✗"
908
+ )
909
+ report_lines.append(
910
+ f"- **{provider}**: {status_icon} {status['status'].title()}"
911
+ )
912
+
913
+ if status.get("issues"):
914
+ for issue in status["issues"]:
915
+ report_lines.append(f" - Issue: {issue}")
916
+
917
+ if status.get("recommendations"):
918
+ for rec in status["recommendations"]:
919
+ report_lines.append(f" - Recommendation: {rec}")
920
+
921
+ report_lines.append("")
922
+
923
+ # Migration info
924
+ if validation_report.migration_required:
925
+ report_lines.append("## Migration Recommended")
926
+ report_lines.append(
927
+ "Consider migrating to the latest configuration format for better features."
928
+ )
929
+ if validation_report.backup_available:
930
+ report_lines.append(
931
+ "Configuration backups are available for safe migration."
932
+ )
933
+ else:
934
+ report_lines.append(
935
+ "**Warning**: No configuration backups found. Create backup before migration."
936
+ )
937
+
938
+ return "\n".join(report_lines)
939
+
940
+ except Exception as e:
941
+ return f"Failed to generate compatibility report: {str(e)}"
942
+
943
+ def ensure_backward_compatibility(self) -> Tuple[bool, List[str]]:
944
+ """Ensure backward compatibility with existing configurations."""
945
+ messages = []
946
+
947
+ try:
948
+ # Get validation report
949
+ import asyncio
950
+
951
+ validation_report = asyncio.run(self.comprehensive_validation())
952
+
953
+ if validation_report.is_valid and validation_report.is_compatible:
954
+ messages.append("✓ Configuration is backward compatible")
955
+ return True, messages
956
+
957
+ # Attempt automatic fixes for compatibility issues
958
+ messages.append("Applying backward compatibility fixes...")
959
+
960
+ try:
961
+ config = self.config_manager.get_config()
962
+ fixed_issues = 0
963
+
964
+ # Fix deprecated model references
965
+ for name, provider_config in config.providers.items():
966
+ if name == "claude" and provider_config.model == "claude-v1":
967
+ provider_config.model = "claude-3-haiku-20240307"
968
+ messages.append(f"✓ Updated {name} model to current version")
969
+ fixed_issues += 1
970
+ elif name == "openai" and provider_config.model in [
971
+ "text-davinci-003",
972
+ "code-davinci-002",
973
+ ]:
974
+ provider_config.model = "gpt-4o-mini"
975
+ messages.append(f"✓ Updated {name} model to current version")
976
+ fixed_issues += 1
977
+
978
+ # Ensure configuration version is set
979
+ if not hasattr(config, "version"):
980
+ config.version = self.current_version
981
+ messages.append("✓ Added version information to configuration")
982
+ fixed_issues += 1
983
+
984
+ # Save fixes
985
+ if fixed_issues > 0:
986
+ self.config_manager.save_config(config)
987
+ messages.append(f"✓ Applied {fixed_issues} compatibility fixes")
988
+
989
+ # Final validation
990
+ final_report = asyncio.run(self.comprehensive_validation())
991
+ if final_report.is_valid and final_report.is_compatible:
992
+ messages.append("✅ Backward compatibility ensured")
993
+ return True, messages
994
+ else:
995
+ remaining_issues = final_report.total_issues
996
+ messages.append(f"⚠ {remaining_issues} issues remain")
997
+ messages.extend(
998
+ final_report.suggestions[:3]
999
+ ) # Show top 3 suggestions
1000
+ return False, messages
1001
+
1002
+ except Exception as e:
1003
+ messages.append(f"✗ Failed to apply fixes: {str(e)}")
1004
+ return False, messages
1005
+
1006
+ except Exception as e:
1007
+ logger.error(f"Backward compatibility check failed: {e}")
1008
+ messages.append(f"✗ Compatibility check failed: {str(e)}")
1009
+ return False, messages
1010
+
1011
+ def _check_backup_availability(self) -> bool:
1012
+ """Check if configuration backups are available."""
1013
+ try:
1014
+ storage_path = self.config_manager.get_storage_path()
1015
+ backup_dir = storage_path / "backups"
1016
+
1017
+ if backup_dir.exists():
1018
+ backup_files = list(backup_dir.glob("*.json"))
1019
+ return len(backup_files) > 0
1020
+
1021
+ return False
1022
+ except:
1023
+ return False
1024
+
1025
+ # =============================================================================
1026
+ # Simple Configuration Interface
1027
+ # =============================================================================
1028
+
1029
+ def _create_simple_sections(self) -> List[SimpleConfigSection]:
1030
+ """Create simplified configuration sections."""
1031
+ sections = []
1032
+
1033
+ # Quick Setup Section
1034
+ quick_setup_options = [
1035
+ SimpleConfigOption(
1036
+ key="use_case",
1037
+ display_name="What will you primarily use Omnimancer for?",
1038
+ description="Choose your main use case to get optimized settings",
1039
+ option_type="choice",
1040
+ choices=[
1041
+ {
1042
+ "value": "coding",
1043
+ "label": "Coding & Development",
1044
+ "description": "Writing code, debugging, technical documentation",
1045
+ },
1046
+ {
1047
+ "value": "research",
1048
+ "label": "Research & Analysis",
1049
+ "description": "Research papers, data analysis, fact-checking",
1050
+ },
1051
+ {
1052
+ "value": "creative",
1053
+ "label": "Creative Writing",
1054
+ "description": "Stories, articles, creative content",
1055
+ },
1056
+ {
1057
+ "value": "general",
1058
+ "label": "General Use",
1059
+ "description": "Mixed tasks, general assistance",
1060
+ },
1061
+ {
1062
+ "value": "performance",
1063
+ "label": "Fast & Economical",
1064
+ "description": "Quick responses, minimal costs",
1065
+ },
1066
+ ],
1067
+ default_value="general",
1068
+ category="Quick Setup",
1069
+ complexity=ConfigurationComplexity.BASIC,
1070
+ ),
1071
+ SimpleConfigOption(
1072
+ key="experience_level",
1073
+ display_name="How familiar are you with AI tools?",
1074
+ description="This helps us show the right level of detail",
1075
+ option_type="choice",
1076
+ choices=[
1077
+ {
1078
+ "value": "beginner",
1079
+ "label": "Beginner",
1080
+ "description": "New to AI tools, prefer simple options",
1081
+ },
1082
+ {
1083
+ "value": "intermediate",
1084
+ "label": "Some Experience",
1085
+ "description": "Used AI tools before, comfortable with basic settings",
1086
+ },
1087
+ {
1088
+ "value": "advanced",
1089
+ "label": "Experienced",
1090
+ "description": "Familiar with AI, want more control",
1091
+ },
1092
+ {
1093
+ "value": "expert",
1094
+ "label": "Expert",
1095
+ "description": "Power user, need full customization",
1096
+ },
1097
+ ],
1098
+ default_value="intermediate",
1099
+ category="Quick Setup",
1100
+ complexity=ConfigurationComplexity.BASIC,
1101
+ ),
1102
+ ]
1103
+
1104
+ sections.append(
1105
+ SimpleConfigSection(
1106
+ name="Quick Setup",
1107
+ description="Get started quickly with recommended settings",
1108
+ icon="⚡",
1109
+ options=quick_setup_options,
1110
+ required=True,
1111
+ expanded=True,
1112
+ )
1113
+ )
1114
+
1115
+ # AI Providers Section
1116
+ provider_options = [
1117
+ SimpleConfigOption(
1118
+ key="enable_claude",
1119
+ display_name="Anthropic Claude",
1120
+ description="Excellent reasoning and coding. Requires API key ($).",
1121
+ option_type="boolean",
1122
+ default_value=True,
1123
+ category="AI Providers",
1124
+ complexity=ConfigurationComplexity.BASIC,
1125
+ ),
1126
+ SimpleConfigOption(
1127
+ key="claude_api_key",
1128
+ display_name="Claude API Key",
1129
+ description="Get from https://console.anthropic.com",
1130
+ option_type="password",
1131
+ required=False,
1132
+ category="AI Providers",
1133
+ complexity=ConfigurationComplexity.BASIC,
1134
+ ),
1135
+ SimpleConfigOption(
1136
+ key="enable_openai",
1137
+ display_name="OpenAI GPT",
1138
+ description="Strong creative abilities and problem solving. Requires API key ($).",
1139
+ option_type="boolean",
1140
+ default_value=True,
1141
+ category="AI Providers",
1142
+ complexity=ConfigurationComplexity.BASIC,
1143
+ ),
1144
+ SimpleConfigOption(
1145
+ key="openai_api_key",
1146
+ display_name="OpenAI API Key",
1147
+ description="Get from https://platform.openai.com",
1148
+ option_type="password",
1149
+ required=False,
1150
+ category="AI Providers",
1151
+ complexity=ConfigurationComplexity.BASIC,
1152
+ ),
1153
+ ]
1154
+
1155
+ sections.append(
1156
+ SimpleConfigSection(
1157
+ name="AI Providers",
1158
+ description="Choose which AI services to use",
1159
+ icon="🤖",
1160
+ options=provider_options,
1161
+ required=True,
1162
+ expanded=False,
1163
+ )
1164
+ )
1165
+
1166
+ return sections
1167
+
1168
+ def get_simple_mode_providers(self) -> List[SimplifiedProviderConfig]:
1169
+ """Get simplified provider configurations for simple mode."""
1170
+ simplified_providers = []
1171
+
1172
+ provider_info = {
1173
+ "claude": {
1174
+ "display_name": "Anthropic Claude",
1175
+ "description": "Excellent for reasoning, coding, and analysis. Very capable and safe.",
1176
+ "use_case_fit": ["coding", "research", "creative", "general"],
1177
+ },
1178
+ "openai": {
1179
+ "display_name": "OpenAI GPT",
1180
+ "description": "Strong general capabilities, good for creative tasks and problem solving.",
1181
+ "use_case_fit": ["creative", "coding", "general"],
1182
+ },
1183
+ "gemini": {
1184
+ "display_name": "Google Gemini",
1185
+ "description": "Large context window, good for document analysis and multimodal tasks.",
1186
+ "use_case_fit": ["research", "general"],
1187
+ },
1188
+ "claude-code": {
1189
+ "display_name": "Claude Code (Free)",
1190
+ "description": "Free local access to Claude via Claude Code. No API costs.",
1191
+ "use_case_fit": ["coding", "general"],
1192
+ },
1193
+ }
1194
+
1195
+ try:
1196
+ current_config = self.config_manager.get_config()
1197
+ current_providers = current_config.providers
1198
+ except:
1199
+ current_providers = {}
1200
+
1201
+ for provider_name, info in provider_info.items():
1202
+ enabled = provider_name in current_providers
1203
+ api_key = None
1204
+ model = None
1205
+
1206
+ if enabled and provider_name in current_providers:
1207
+ provider_config = current_providers[provider_name]
1208
+ api_key = "***" if provider_config.api_key else None
1209
+ model = provider_config.model
1210
+
1211
+ simplified_providers.append(
1212
+ SimplifiedProviderConfig(
1213
+ name=provider_name,
1214
+ display_name=info["display_name"],
1215
+ description=info["description"],
1216
+ enabled=enabled,
1217
+ api_key=api_key,
1218
+ model=model,
1219
+ use_case_fit=info["use_case_fit"],
1220
+ )
1221
+ )
1222
+
1223
+ return simplified_providers
1224
+
1225
+ def get_quick_setup_templates(self) -> List[Dict[str, Any]]:
1226
+ """Get quick setup templates with simplified descriptions."""
1227
+ templates = []
1228
+
1229
+ # Get all templates and simplify them
1230
+ for (
1231
+ name,
1232
+ template,
1233
+ ) in self.template_manager.get_all_templates().items():
1234
+ simplified = {
1235
+ "name": name,
1236
+ "display_name": template.name.title(),
1237
+ "description": template.description,
1238
+ "icon": self._get_template_icon(name),
1239
+ "estimated_setup_time": self._get_estimated_setup_time(template),
1240
+ "cost_estimate": self._get_cost_estimate(template),
1241
+ "complexity": (
1242
+ "Simple" if name in ["general", "performance"] else "Moderate"
1243
+ ),
1244
+ "providers_needed": len(template.recommended_providers[:3]),
1245
+ "main_providers": template.recommended_providers[:2],
1246
+ "use_case": template.use_case,
1247
+ "recommended_for": self._get_recommended_for(template),
1248
+ }
1249
+ templates.append(simplified)
1250
+
1251
+ # Sort by complexity (simple first) and popularity
1252
+ templates.sort(
1253
+ key=lambda x: (
1254
+ 0 if x["complexity"] == "Simple" else 1,
1255
+ -x["providers_needed"],
1256
+ )
1257
+ )
1258
+
1259
+ return templates
1260
+
1261
+ def _get_template_icon(self, template_name: str) -> str:
1262
+ """Get an appropriate icon for a template."""
1263
+ icons = {
1264
+ "coding": "💻",
1265
+ "research": "🔬",
1266
+ "creative": "✏️",
1267
+ "general": "🌟",
1268
+ "performance": "⚡",
1269
+ }
1270
+ return icons.get(template_name, "🤖")
1271
+
1272
+ def _get_estimated_setup_time(self, template: ConfigTemplate) -> str:
1273
+ """Estimate setup time for a template."""
1274
+ provider_count = len(template.recommended_providers[:3])
1275
+
1276
+ if provider_count <= 1:
1277
+ return "2-3 minutes"
1278
+ elif provider_count == 2:
1279
+ return "5-7 minutes"
1280
+ else:
1281
+ return "8-12 minutes"
1282
+
1283
+ def _get_cost_estimate(self, template: ConfigTemplate) -> str:
1284
+ """Get cost estimate for a template."""
1285
+ providers = template.recommended_providers[:3]
1286
+
1287
+ # Check for free options
1288
+ free_providers = ["claude-code", "ollama"]
1289
+ has_free = any(p in free_providers for p in providers)
1290
+
1291
+ if has_free and len(providers) <= 2:
1292
+ return "Free option available"
1293
+ elif "performance" in template.name.lower():
1294
+ return "Low cost (~$5-15/month)"
1295
+ else:
1296
+ return "Moderate cost (~$15-40/month)"
1297
+
1298
+ def _get_recommended_for(self, template: ConfigTemplate) -> List[str]:
1299
+ """Get who this template is recommended for."""
1300
+ recommendations = {
1301
+ "coding": [
1302
+ "Software developers",
1303
+ "Students learning programming",
1304
+ "Technical writers",
1305
+ ],
1306
+ "research": ["Researchers", "Students", "Analysts", "Journalists"],
1307
+ "creative": [
1308
+ "Writers",
1309
+ "Content creators",
1310
+ "Marketing professionals",
1311
+ ],
1312
+ "general": [
1313
+ "General users",
1314
+ "Business professionals",
1315
+ "Personal use",
1316
+ ],
1317
+ "performance": [
1318
+ "Budget-conscious users",
1319
+ "High-volume usage",
1320
+ "Teams",
1321
+ ],
1322
+ }
1323
+ return recommendations.get(template.use_case, ["General users"])
1324
+
1325
+ def get_setup_wizard_steps(self, use_case: str = "general") -> List[Dict[str, Any]]:
1326
+ """Get step-by-step wizard steps for configuration."""
1327
+ steps = []
1328
+
1329
+ # Step 1: Welcome
1330
+ steps.append(
1331
+ {
1332
+ "step": 1,
1333
+ "title": "Welcome to Omnimancer",
1334
+ "description": "Let's set up Omnimancer for your needs",
1335
+ "type": "welcome",
1336
+ "content": {
1337
+ "message": "This quick setup will configure Omnimancer based on how you plan to use it.",
1338
+ "estimated_time": "5-10 minutes",
1339
+ "features": [
1340
+ "Choose your use case for optimized settings",
1341
+ "Select AI providers that fit your needs",
1342
+ "Set up API keys for your chosen services",
1343
+ "Enable helpful tools and features",
1344
+ ],
1345
+ },
1346
+ }
1347
+ )
1348
+
1349
+ # Step 2: Use Case Selection
1350
+ steps.append(
1351
+ {
1352
+ "step": 2,
1353
+ "title": "Choose Your Primary Use Case",
1354
+ "description": "This helps us recommend the best settings for you",
1355
+ "type": "single_choice",
1356
+ "option": {
1357
+ "key": "use_case",
1358
+ "display_name": "What will you primarily use Omnimancer for?",
1359
+ "description": "Choose your main use case to get optimized settings",
1360
+ "option_type": "choice",
1361
+ "choices": [
1362
+ {
1363
+ "value": "coding",
1364
+ "label": "Coding & Development",
1365
+ "description": "Writing code, debugging, technical documentation",
1366
+ },
1367
+ {
1368
+ "value": "research",
1369
+ "label": "Research & Analysis",
1370
+ "description": "Research papers, data analysis, fact-checking",
1371
+ },
1372
+ {
1373
+ "value": "creative",
1374
+ "label": "Creative Writing",
1375
+ "description": "Stories, articles, creative content",
1376
+ },
1377
+ {
1378
+ "value": "general",
1379
+ "label": "General Use",
1380
+ "description": "Mixed tasks, general assistance",
1381
+ },
1382
+ {
1383
+ "value": "performance",
1384
+ "label": "Fast & Economical",
1385
+ "description": "Quick responses, minimal costs",
1386
+ },
1387
+ ],
1388
+ "default_value": "general",
1389
+ },
1390
+ "help_text": "Don't worry - you can change this later or use Omnimancer for multiple purposes.",
1391
+ }
1392
+ )
1393
+
1394
+ # Step 3: Experience Level
1395
+ steps.append(
1396
+ {
1397
+ "step": 3,
1398
+ "title": "Your Experience Level",
1399
+ "description": "This helps us show the right amount of detail",
1400
+ "type": "single_choice",
1401
+ "option": {
1402
+ "key": "experience_level",
1403
+ "display_name": "How familiar are you with AI tools?",
1404
+ "description": "This helps us show the right level of detail",
1405
+ "option_type": "choice",
1406
+ "choices": [
1407
+ {
1408
+ "value": "beginner",
1409
+ "label": "Beginner",
1410
+ "description": "New to AI tools, prefer simple options",
1411
+ },
1412
+ {
1413
+ "value": "intermediate",
1414
+ "label": "Some Experience",
1415
+ "description": "Used AI tools before, comfortable with basic settings",
1416
+ },
1417
+ {
1418
+ "value": "advanced",
1419
+ "label": "Experienced",
1420
+ "description": "Familiar with AI, want more control",
1421
+ },
1422
+ {
1423
+ "value": "expert",
1424
+ "label": "Expert",
1425
+ "description": "Power user, need full customization",
1426
+ },
1427
+ ],
1428
+ "default_value": "intermediate",
1429
+ },
1430
+ }
1431
+ )
1432
+
1433
+ # Step 4: AI Providers
1434
+ steps.append(
1435
+ {
1436
+ "step": 4,
1437
+ "title": "Choose AI Providers",
1438
+ "description": "Select which AI services you'd like to use",
1439
+ "type": "provider_selection",
1440
+ "content": {
1441
+ "message": "We recommend starting with 1-2 providers. You can add more later.",
1442
+ "providers": self._get_simplified_provider_options(use_case),
1443
+ },
1444
+ }
1445
+ )
1446
+
1447
+ # Step 5: API Keys
1448
+ steps.append(
1449
+ {
1450
+ "step": 5,
1451
+ "title": "Add API Keys",
1452
+ "description": "Enter API keys for your selected providers",
1453
+ "type": "api_keys",
1454
+ "content": {
1455
+ "message": "API keys are encrypted and stored securely on your device.",
1456
+ "help_link": "Need help getting API keys?",
1457
+ "providers_with_keys": [
1458
+ "claude",
1459
+ "openai",
1460
+ "gemini",
1461
+ "perplexity",
1462
+ ],
1463
+ },
1464
+ }
1465
+ )
1466
+
1467
+ # Step 6: Tools and Features
1468
+ steps.append(
1469
+ {
1470
+ "step": 6,
1471
+ "title": "Enable Tools",
1472
+ "description": "Choose which capabilities to enable",
1473
+ "type": "feature_selection",
1474
+ "content": {
1475
+ "message": "These tools enhance Omnimancer's capabilities. All require your approval before use.",
1476
+ "features": [
1477
+ {
1478
+ "key": "web_search",
1479
+ "name": "Web Search",
1480
+ "description": "Search the internet for current information",
1481
+ "recommended": True,
1482
+ },
1483
+ {
1484
+ "key": "file_operations",
1485
+ "name": "File Operations",
1486
+ "description": "Read and write files (with your approval)",
1487
+ "recommended": True,
1488
+ },
1489
+ {
1490
+ "key": "code_tools",
1491
+ "name": "Coding Tools",
1492
+ "description": "Git, code analysis, and development tools",
1493
+ "recommended": use_case == "coding",
1494
+ },
1495
+ ],
1496
+ },
1497
+ }
1498
+ )
1499
+
1500
+ # Step 7: Review and Finish
1501
+ steps.append(
1502
+ {
1503
+ "step": 7,
1504
+ "title": "Review Your Configuration",
1505
+ "description": "Confirm your settings and complete setup",
1506
+ "type": "review",
1507
+ "content": {
1508
+ "message": "Review your configuration below. You can change any of these settings later.",
1509
+ "final_steps": [
1510
+ "Save your configuration",
1511
+ "Test your first AI conversation",
1512
+ "Explore additional features",
1513
+ ],
1514
+ },
1515
+ }
1516
+ )
1517
+
1518
+ return steps
1519
+
1520
+ def _get_simplified_provider_options(self, use_case: str) -> List[Dict[str, Any]]:
1521
+ """Get simplified provider options based on use case."""
1522
+ all_providers = self.get_simple_mode_providers()
1523
+
1524
+ # Filter and rank providers based on use case
1525
+ relevant_providers = []
1526
+ for provider in all_providers:
1527
+ if use_case in provider.use_case_fit or "general" in provider.use_case_fit:
1528
+ # Calculate relevance score
1529
+ relevance = 1.0
1530
+ if use_case in provider.use_case_fit:
1531
+ relevance += 0.5
1532
+ if provider.name in ["claude", "openai"]: # Popular choices
1533
+ relevance += 0.3
1534
+ if provider.name in ["claude-code", "ollama"]: # Free options
1535
+ relevance += 0.2
1536
+
1537
+ relevant_providers.append(
1538
+ {
1539
+ "provider": asdict(provider),
1540
+ "relevance": relevance,
1541
+ "setup_difficulty": self._get_provider_setup_difficulty(
1542
+ provider.name
1543
+ ),
1544
+ "recommended": relevance > 1.3,
1545
+ }
1546
+ )
1547
+
1548
+ # Sort by relevance
1549
+ relevant_providers.sort(key=lambda x: x["relevance"], reverse=True)
1550
+
1551
+ return relevant_providers[:5] # Return top 5 most relevant
1552
+
1553
+ def _get_provider_setup_difficulty(self, provider_name: str) -> str:
1554
+ """Get setup difficulty level for a provider."""
1555
+ difficulty_map = {
1556
+ "claude-code": "Easy (No API key needed)",
1557
+ "ollama": "Moderate (Local installation required)",
1558
+ "claude": "Easy (API key required)",
1559
+ "openai": "Easy (API key required)",
1560
+ "gemini": "Easy (Free tier available)",
1561
+ "perplexity": "Moderate (Subscription required)",
1562
+ }
1563
+ return difficulty_map.get(provider_name, "Easy")
1564
+
1565
+ def get_provider_setup_instructions(self, provider_name: str) -> Dict[str, Any]:
1566
+ """Get setup instructions for a specific provider."""
1567
+ instructions = {
1568
+ "claude": {
1569
+ "title": "Anthropic Claude Setup",
1570
+ "steps": [
1571
+ "Visit https://console.anthropic.com",
1572
+ "Sign up or log in to your account",
1573
+ "Go to the API Keys section",
1574
+ "Create a new API key",
1575
+ "Copy the key (starts with 'sk-ant-')",
1576
+ "Paste it in the API key field",
1577
+ ],
1578
+ "api_key_format": "sk-ant-...",
1579
+ "cost_info": "Pay-per-use pricing, free tier available",
1580
+ "recommended_models": [
1581
+ "claude-3-5-sonnet-20241022",
1582
+ "claude-3-haiku-20240307",
1583
+ ],
1584
+ },
1585
+ "openai": {
1586
+ "title": "OpenAI GPT Setup",
1587
+ "steps": [
1588
+ "Visit https://platform.openai.com",
1589
+ "Sign up or log in to your account",
1590
+ "Go to the API Keys section",
1591
+ "Create a new API key",
1592
+ "Copy the key (starts with 'sk-')",
1593
+ "Paste it in the API key field",
1594
+ ],
1595
+ "api_key_format": "sk-...",
1596
+ "cost_info": "Pay-per-use pricing, free tier available for new users",
1597
+ "recommended_models": ["gpt-4o", "gpt-4o-mini"],
1598
+ },
1599
+ "gemini": {
1600
+ "title": "Google Gemini Setup",
1601
+ "steps": [
1602
+ "Visit https://makersuite.google.com/app/apikey",
1603
+ "Sign in with your Google account",
1604
+ "Create a new API key",
1605
+ "Copy the key (starts with 'AIza')",
1606
+ "Paste it in the API key field",
1607
+ ],
1608
+ "api_key_format": "AIza...",
1609
+ "cost_info": "Free tier with generous limits, pay-per-use beyond limits",
1610
+ "recommended_models": ["gemini-1.5-pro", "gemini-1.5-flash"],
1611
+ },
1612
+ }
1613
+
1614
+ return instructions.get(
1615
+ provider_name,
1616
+ {
1617
+ "title": f"{provider_name} Setup",
1618
+ "steps": ["Refer to provider documentation for setup instructions"],
1619
+ "api_key_format": "Check provider documentation",
1620
+ "cost_info": "Varies by provider",
1621
+ },
1622
+ )
1623
+
1624
+ def get_configuration_preview(self, answers: Dict[str, Any]) -> Dict[str, Any]:
1625
+ """Generate a preview of what the configuration will look like."""
1626
+ preview = {
1627
+ "use_case": answers.get("use_case", "general"),
1628
+ "enabled_providers": [],
1629
+ "estimated_monthly_cost": "$0",
1630
+ "features_enabled": [],
1631
+ "setup_complete": False,
1632
+ }
1633
+
1634
+ # Calculate enabled providers and costs
1635
+ cost_estimates = {
1636
+ "claude": 15,
1637
+ "openai": 15,
1638
+ "gemini": 5,
1639
+ "perplexity": 20,
1640
+ }
1641
+ total_cost = 0
1642
+
1643
+ if answers.get("enable_claude") and answers.get("claude_api_key"):
1644
+ preview["enabled_providers"].append("Anthropic Claude")
1645
+ total_cost += cost_estimates.get("claude", 0)
1646
+
1647
+ if answers.get("enable_openai") and answers.get("openai_api_key"):
1648
+ preview["enabled_providers"].append("OpenAI GPT")
1649
+ total_cost += cost_estimates.get("openai", 0)
1650
+
1651
+ if answers.get("enable_gemini") and answers.get("gemini_api_key"):
1652
+ preview["enabled_providers"].append("Google Gemini")
1653
+ total_cost += cost_estimates.get("gemini", 0)
1654
+
1655
+ if answers.get("enable_claude_code"):
1656
+ preview["enabled_providers"].append("Claude Code (Free)")
1657
+
1658
+ # Update cost estimate
1659
+ if total_cost == 0:
1660
+ preview["estimated_monthly_cost"] = "Free"
1661
+ elif total_cost <= 10:
1662
+ preview["estimated_monthly_cost"] = f"~${total_cost}/month"
1663
+ else:
1664
+ preview["estimated_monthly_cost"] = (
1665
+ f"~${total_cost}-{total_cost + 20}/month"
1666
+ )
1667
+
1668
+ # Features
1669
+ if answers.get("enable_web_search"):
1670
+ preview["features_enabled"].append("Web Search")
1671
+ if answers.get("enable_file_operations"):
1672
+ preview["features_enabled"].append("File Operations")
1673
+ if answers.get("enable_code_tools"):
1674
+ preview["features_enabled"].append("Coding Tools")
1675
+
1676
+ # Check if setup would be complete
1677
+ is_valid, errors = self.validate_simple_answers(answers)
1678
+ preview["setup_complete"] = is_valid
1679
+ preview["missing_items"] = errors
1680
+
1681
+ return preview
1682
+
1683
+ def get_recommended_configuration(
1684
+ self, context: Optional[ConfigurationContext] = None
1685
+ ) -> ConfigurationRecommendation:
1686
+ """Get configuration recommendation based on user context."""
1687
+ ctx = context or self.context
1688
+
1689
+ if ctx.use_case == "coding":
1690
+ return ConfigurationRecommendation(
1691
+ template_name="coding",
1692
+ confidence=0.9,
1693
+ reasons=[
1694
+ "Optimized for software development",
1695
+ "Includes code-capable models",
1696
+ "Has essential development tools",
1697
+ ],
1698
+ providers=["claude", "openai", "claude-code"],
1699
+ estimated_cost="low",
1700
+ complexity=ConfigurationComplexity.BASIC,
1701
+ )
1702
+ elif ctx.use_case == "research":
1703
+ return ConfigurationRecommendation(
1704
+ template_name="research",
1705
+ confidence=0.9,
1706
+ reasons=[
1707
+ "Includes web search capabilities",
1708
+ "Large context models for document analysis",
1709
+ "Citation and fact-checking support",
1710
+ ],
1711
+ providers=["perplexity", "claude", "gemini"],
1712
+ estimated_cost="medium",
1713
+ complexity=ConfigurationComplexity.BASIC,
1714
+ )
1715
+ else:
1716
+ return ConfigurationRecommendation(
1717
+ template_name="general",
1718
+ confidence=0.7,
1719
+ reasons=[
1720
+ "Balanced configuration for general use",
1721
+ "Good mix of capabilities",
1722
+ "Suitable for various tasks",
1723
+ ],
1724
+ providers=["claude", "openai", "gemini"],
1725
+ estimated_cost="medium",
1726
+ complexity=ConfigurationComplexity.BASIC,
1727
+ )
1728
+
1729
+ def apply_template_configuration(
1730
+ self, template_name: str, api_keys: Dict[str, str] = None
1731
+ ) -> Tuple[bool, List[str]]:
1732
+ """Apply a template configuration in simple mode."""
1733
+ messages = []
1734
+
1735
+ try:
1736
+ template = self.template_manager.get_template(template_name)
1737
+ config = self.config_manager.get_config()
1738
+
1739
+ # Apply provider configurations
1740
+ for provider_name in template.recommended_providers[:3]:
1741
+ provider_config = (
1742
+ self.template_manager.create_provider_config_from_template(
1743
+ template, provider_name
1744
+ )
1745
+ )
1746
+
1747
+ if provider_config:
1748
+ if api_keys and provider_name in api_keys:
1749
+ provider_config.api_key = api_keys[provider_name]
1750
+ elif provider_name in ["claude-code", "ollama"]:
1751
+ provider_config.api_key = None
1752
+
1753
+ config.providers[provider_name] = provider_config
1754
+ messages.append(
1755
+ f"Configured {provider_name} with {provider_config.model}"
1756
+ )
1757
+
1758
+ if template.recommended_providers:
1759
+ config.default_provider = template.recommended_providers[0]
1760
+ messages.append(f"Set default provider to {config.default_provider}")
1761
+
1762
+ # Apply MCP configuration (simplified)
1763
+ mcp_config = self.template_manager.create_mcp_config_from_template(template)
1764
+ enabled_count = 0
1765
+ for server_name, server_config in mcp_config.servers.items():
1766
+ if enabled_count < 2:
1767
+ server_config.enabled = True
1768
+ enabled_count += 1
1769
+ else:
1770
+ server_config.enabled = False
1771
+
1772
+ config.mcp = mcp_config
1773
+ messages.append(f"Enabled {enabled_count} MCP tools")
1774
+
1775
+ self.config_manager.save_config(config)
1776
+ messages.append("Configuration saved successfully")
1777
+
1778
+ return True, messages
1779
+
1780
+ except Exception as e:
1781
+ logger.error(f"Failed to apply template configuration: {e}")
1782
+ return False, [f"Failed to apply template: {str(e)}"]
1783
+
1784
+ def validate_simple_answers(
1785
+ self, answers: Dict[str, Any]
1786
+ ) -> Tuple[bool, List[str]]:
1787
+ """Validate answers from the simple configuration interface."""
1788
+ errors = []
1789
+
1790
+ required_fields = ["use_case", "experience_level"]
1791
+ for field in required_fields:
1792
+ if field not in answers or not answers[field]:
1793
+ errors.append(f"{field.replace('_', ' ').title()} is required")
1794
+
1795
+ enabled_providers = []
1796
+ if answers.get("enable_claude") and answers.get("claude_api_key"):
1797
+ enabled_providers.append("claude")
1798
+ if answers.get("enable_openai") and answers.get("openai_api_key"):
1799
+ enabled_providers.append("openai")
1800
+ if answers.get("enable_claude_code"):
1801
+ enabled_providers.append("claude-code")
1802
+
1803
+ if not enabled_providers:
1804
+ errors.append("At least one AI provider must be enabled and configured")
1805
+
1806
+ # Validate API key formats
1807
+ if answers.get("claude_api_key"):
1808
+ key = answers["claude_api_key"]
1809
+ if not key.startswith("sk-ant-"):
1810
+ errors.append("Claude API key should start with 'sk-ant-'")
1811
+
1812
+ if answers.get("openai_api_key"):
1813
+ key = answers["openai_api_key"]
1814
+ if not key.startswith("sk-"):
1815
+ errors.append("OpenAI API key should start with 'sk-'")
1816
+
1817
+ return len(errors) == 0, errors
1818
+
1819
+ def generate_simple_configuration(
1820
+ self, answers: Dict[str, Any]
1821
+ ) -> Tuple[bool, Dict[str, Any], List[str]]:
1822
+ """Generate a complete configuration based on simple answers."""
1823
+ messages = []
1824
+
1825
+ try:
1826
+ use_case = answers.get("use_case", "general")
1827
+ experience_level = answers.get("experience_level", "intermediate")
1828
+
1829
+ context = ConfigurationContext(
1830
+ user_level=experience_level,
1831
+ use_case=use_case,
1832
+ preferred_mode=self.current_mode,
1833
+ )
1834
+ self.set_configuration_context(context)
1835
+
1836
+ recommendation = self.get_recommended_configuration(context)
1837
+ messages.append(
1838
+ f"Recommended template: {recommendation.template_name} (confidence: {recommendation.confidence:.0%})"
1839
+ )
1840
+
1841
+ api_keys = {}
1842
+ if answers.get("enable_claude") and answers.get("claude_api_key"):
1843
+ api_keys["claude"] = answers["claude_api_key"]
1844
+ messages.append("Claude API key provided")
1845
+
1846
+ if answers.get("enable_openai") and answers.get("openai_api_key"):
1847
+ api_keys["openai"] = answers["openai_api_key"]
1848
+ messages.append("OpenAI API key provided")
1849
+
1850
+ success, template_messages = self.apply_template_configuration(
1851
+ recommendation.template_name, api_keys
1852
+ )
1853
+
1854
+ if success:
1855
+ messages.extend(template_messages)
1856
+ config_dict = self.get_configuration_summary()
1857
+
1858
+ config_dict["user_preferences"] = {
1859
+ "use_case": use_case,
1860
+ "experience_level": experience_level,
1861
+ "tools_enabled": {
1862
+ "web_search": answers.get("enable_web_search", True),
1863
+ "file_operations": answers.get("enable_file_operations", True),
1864
+ "code_tools": answers.get("enable_code_tools", True),
1865
+ },
1866
+ }
1867
+
1868
+ messages.append("Configuration generated successfully")
1869
+ return True, config_dict, messages
1870
+ else:
1871
+ return False, {}, messages
1872
+
1873
+ except Exception as e:
1874
+ logger.error(f"Failed to generate simple configuration: {e}")
1875
+ return False, {}, [f"Configuration generation failed: {str(e)}"]
1876
+
1877
+ def get_configuration_summary(self) -> Dict[str, Any]:
1878
+ """Get a summary of the current configuration appropriate for the current mode."""
1879
+ try:
1880
+ config = self.config_manager.get_config()
1881
+
1882
+ summary = {
1883
+ "mode": self.current_mode.value,
1884
+ "context": asdict(self.context),
1885
+ "default_provider": config.default_provider,
1886
+ "enabled_providers": len(config.providers),
1887
+ "mcp_enabled": config.mcp.enabled,
1888
+ "enabled_mcp_servers": len(config.mcp.get_enabled_servers()),
1889
+ }
1890
+
1891
+ if self.current_mode == ConfigurationMode.SIMPLE:
1892
+ summary["providers"] = []
1893
+ for name, provider_config in list(config.providers.items())[:3]:
1894
+ summary["providers"].append(
1895
+ {
1896
+ "name": name,
1897
+ "model": provider_config.model,
1898
+ "enabled": provider_config.enabled,
1899
+ }
1900
+ )
1901
+
1902
+ summary["tools"] = list(config.mcp.get_enabled_servers().keys())[:3]
1903
+
1904
+ elif self.current_mode == ConfigurationMode.ADVANCED:
1905
+ summary.update(self.config_manager.get_config_summary())
1906
+
1907
+ return summary
1908
+
1909
+ except Exception as e:
1910
+ logger.error(f"Failed to get configuration summary: {e}")
1911
+ return {"error": str(e)}
1912
+
1913
+ # =============================================================================
1914
+ # Utility Methods
1915
+ # =============================================================================
1916
+
1917
+ def _load_mode_preferences(self) -> None:
1918
+ """Load mode preferences from storage."""
1919
+ try:
1920
+ storage_path = self.config_manager.get_storage_path()
1921
+ preferences_file = storage_path / "mode_preferences.json"
1922
+
1923
+ if preferences_file.exists():
1924
+ with open(preferences_file, "r") as f:
1925
+ data = json.load(f)
1926
+
1927
+ self.current_mode = ConfigurationMode(data.get("mode", "simple"))
1928
+
1929
+ context_data = data.get("context", {})
1930
+ self.context = ConfigurationContext(
1931
+ user_level=context_data.get("user_level", "beginner"),
1932
+ use_case=context_data.get("use_case", "general"),
1933
+ primary_providers=context_data.get("primary_providers", []),
1934
+ preferred_mode=ConfigurationMode(
1935
+ context_data.get("preferred_mode", "simple")
1936
+ ),
1937
+ show_experimental=context_data.get("show_experimental", False),
1938
+ )
1939
+
1940
+ except Exception as e:
1941
+ logger.debug(f"Could not load mode preferences: {e}")
1942
+
1943
+ def _save_mode_preferences(self) -> None:
1944
+ """Save mode preferences to storage."""
1945
+ try:
1946
+ storage_path = self.config_manager.get_storage_path()
1947
+ preferences_file = storage_path / "mode_preferences.json"
1948
+
1949
+ data = {
1950
+ "mode": self.current_mode.value,
1951
+ "context": asdict(self.context),
1952
+ "updated_at": datetime.now().isoformat(),
1953
+ }
1954
+
1955
+ with open(preferences_file, "w") as f:
1956
+ json.dump(data, f, indent=2)
1957
+
1958
+ except Exception as e:
1959
+ logger.debug(f"Could not save mode preferences: {e}")
1960
+
1961
+
1962
+ def create_configuration_provider(
1963
+ config_manager: Optional[ConfigManager] = None,
1964
+ ) -> ConfigurationProvider:
1965
+ """
1966
+ Create a configuration provider with default settings.
1967
+
1968
+ Args:
1969
+ config_manager: Optional ConfigManager instance
1970
+
1971
+ Returns:
1972
+ Configured ConfigurationProvider instance
1973
+ """
1974
+ return ConfigurationProvider(config_manager)