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,393 @@
1
+ """
2
+ Security-focused configuration validator for Omnimancer.
3
+
4
+ This module ensures that critical security settings cannot be accidentally disabled
5
+ and validates configuration against security best practices.
6
+ """
7
+
8
+ import logging
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Tuple
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class SecurityValidationError(Exception):
16
+ """Raised when a security validation fails."""
17
+
18
+ pass
19
+
20
+
21
+ class ConfigSecurityValidator:
22
+ """Validates Omnimancer configurations for security compliance."""
23
+
24
+ def __init__(self):
25
+ self.critical_security_settings = self._get_critical_security_settings()
26
+ self.recommended_security_settings = self._get_recommended_security_settings()
27
+ self.dangerous_combinations = self._get_dangerous_combinations()
28
+
29
+ def _get_critical_security_settings(self) -> Dict[str, Any]:
30
+ """Settings that must never be disabled for security reasons."""
31
+ return {
32
+ "api_key_encryption_enabled": True,
33
+ "secure_storage_enabled": True,
34
+ "mcp.sandbox_mode": True,
35
+ "mcp.allow_dangerous_tools": False,
36
+ }
37
+
38
+ def _get_recommended_security_settings(self) -> Dict[str, Any]:
39
+ """Recommended security settings that should be enabled by default."""
40
+ return {
41
+ "provider_health_check_enabled": True,
42
+ "request_cache_enabled": True, # Helps with rate limiting
43
+ "mcp.log_tool_calls": True, # Security auditing
44
+ "debug_mode": False, # Don't leak info in production
45
+ "telemetry_enabled": False, # Privacy
46
+ "mcp.max_tool_execution_time": 300, # Prevent runaway tools
47
+ "mcp.max_concurrent_servers": 10, # Resource limiting
48
+ }
49
+
50
+ def _get_dangerous_combinations(self) -> List[Dict[str, Any]]:
51
+ """Combinations of settings that create security risks."""
52
+ return [
53
+ {
54
+ "description": "Debug mode with telemetry enabled can leak sensitive information",
55
+ "conditions": {"debug_mode": True, "telemetry_enabled": True},
56
+ "severity": "high",
57
+ },
58
+ {
59
+ "description": "MCP sandbox disabled with dangerous tools allowed is extremely risky",
60
+ "conditions": {
61
+ "mcp.sandbox_mode": False,
62
+ "mcp.allow_dangerous_tools": True,
63
+ },
64
+ "severity": "critical",
65
+ },
66
+ {
67
+ "description": "No health checks with fallback disabled can cause reliability issues",
68
+ "conditions": {
69
+ "provider_health_check_enabled": False,
70
+ "provider_fallback_enabled": False,
71
+ },
72
+ "severity": "medium",
73
+ },
74
+ ]
75
+
76
+ def validate_security(
77
+ self, config: Dict[str, Any], strict_mode: bool = True
78
+ ) -> Tuple[bool, List[str], List[str]]:
79
+ """
80
+ Validate configuration for security compliance.
81
+
82
+ Args:
83
+ config: Configuration to validate
84
+ strict_mode: If True, treat security warnings as errors
85
+
86
+ Returns:
87
+ Tuple of (is_valid, errors, warnings)
88
+ """
89
+ errors = []
90
+ warnings = []
91
+
92
+ # Check critical security settings
93
+ critical_errors = self._validate_critical_settings(config)
94
+ errors.extend(critical_errors)
95
+
96
+ # Check recommended security settings
97
+ recommended_warnings = self._validate_recommended_settings(config)
98
+ if strict_mode:
99
+ errors.extend(recommended_warnings)
100
+ else:
101
+ warnings.extend(recommended_warnings)
102
+
103
+ # Check dangerous combinations
104
+ combination_issues = self._validate_dangerous_combinations(config)
105
+ for issue in combination_issues:
106
+ if issue["severity"] in ["critical", "high"]:
107
+ errors.append(issue["message"])
108
+ else:
109
+ warnings.append(issue["message"])
110
+
111
+ # Validate API key security
112
+ api_key_issues = self._validate_api_key_security(config)
113
+ errors.extend(api_key_issues)
114
+
115
+ # Check file permissions
116
+ permission_warnings = self._validate_file_permissions(config)
117
+ warnings.extend(permission_warnings)
118
+
119
+ is_valid = len(errors) == 0
120
+ return is_valid, errors, warnings
121
+
122
+ def _validate_critical_settings(self, config: Dict[str, Any]) -> List[str]:
123
+ """Validate critical security settings that must never be disabled."""
124
+ errors = []
125
+
126
+ for (
127
+ setting_path,
128
+ required_value,
129
+ ) in self.critical_security_settings.items():
130
+ current_value = self._get_nested_value(config, setting_path)
131
+
132
+ if current_value != required_value:
133
+ errors.append(
134
+ f"CRITICAL: Security setting '{setting_path}' must be set to {required_value}, "
135
+ f"but is currently {current_value}"
136
+ )
137
+
138
+ return errors
139
+
140
+ def _validate_recommended_settings(self, config: Dict[str, Any]) -> List[str]:
141
+ """Validate recommended security settings."""
142
+ warnings = []
143
+
144
+ for (
145
+ setting_path,
146
+ recommended_value,
147
+ ) in self.recommended_security_settings.items():
148
+ current_value = self._get_nested_value(config, setting_path)
149
+
150
+ if current_value != recommended_value:
151
+ warnings.append(
152
+ f"RECOMMENDED: Security setting '{setting_path}' should be set to {recommended_value}, "
153
+ f"but is currently {current_value}"
154
+ )
155
+
156
+ return warnings
157
+
158
+ def _validate_dangerous_combinations(
159
+ self, config: Dict[str, Any]
160
+ ) -> List[Dict[str, Any]]:
161
+ """Check for dangerous combinations of settings."""
162
+ issues = []
163
+
164
+ for combination in self.dangerous_combinations:
165
+ all_conditions_met = True
166
+
167
+ for setting_path, required_value in combination["conditions"].items():
168
+ current_value = self._get_nested_value(config, setting_path)
169
+ if current_value != required_value:
170
+ all_conditions_met = False
171
+ break
172
+
173
+ if all_conditions_met:
174
+ issues.append(
175
+ {
176
+ "message": f"{combination['severity'].upper()}: {combination['description']}",
177
+ "severity": combination["severity"],
178
+ }
179
+ )
180
+
181
+ return issues
182
+
183
+ def _validate_api_key_security(self, config: Dict[str, Any]) -> List[str]:
184
+ """Validate API key security practices."""
185
+ errors = []
186
+
187
+ # Check if API keys are stored in plain text (should be encrypted)
188
+ if "providers" in config:
189
+ for provider_name, provider_config in config["providers"].items():
190
+ api_key = provider_config.get("api_key")
191
+
192
+ if api_key and isinstance(api_key, str):
193
+ # Check for obviously insecure patterns
194
+ if api_key in [
195
+ "test",
196
+ "demo",
197
+ "placeholder",
198
+ "your-api-key-here",
199
+ ]:
200
+ errors.append(
201
+ f"Provider '{provider_name}' has placeholder API key"
202
+ )
203
+
204
+ # Check if it looks like a real key but isn't encrypted
205
+ if api_key.startswith(
206
+ ("sk-", "api-", "key-")
207
+ ) and not api_key.startswith("encrypted:"):
208
+ # This might be a plain text key in an optimized config
209
+ # In production, these should be encrypted
210
+ pass # Don't warn for optimized configs as encryption happens at runtime
211
+
212
+ return errors
213
+
214
+ def _validate_file_permissions(self, config: Dict[str, Any]) -> List[str]:
215
+ """Check file permissions for configuration and storage paths."""
216
+ warnings = []
217
+
218
+ storage_path = config.get("storage_path")
219
+ if storage_path:
220
+ storage_dir = Path(storage_path)
221
+
222
+ if storage_dir.exists():
223
+ # Check if directory is readable by others (Unix-like systems)
224
+ try:
225
+ stat_info = storage_dir.stat()
226
+ mode = stat_info.st_mode
227
+
228
+ # Check if group/others have read access (simplified check)
229
+ if mode & 0o044: # Group or others can read
230
+ warnings.append(
231
+ f"Storage directory '{storage_path}' may be readable by other users. "
232
+ f"Consider restricting permissions with: chmod 700 '{storage_path}'"
233
+ )
234
+ except Exception:
235
+ # Permission check failed, probably Windows or insufficient permissions
236
+ pass
237
+
238
+ return warnings
239
+
240
+ def _get_nested_value(self, config: Dict[str, Any], path: str) -> Any:
241
+ """Get a nested configuration value using dot notation."""
242
+ keys = path.split(".")
243
+ current = config
244
+
245
+ for key in keys:
246
+ if isinstance(current, dict) and key in current:
247
+ current = current[key]
248
+ else:
249
+ return None
250
+
251
+ return current
252
+
253
+ def fix_security_issues(
254
+ self, config: Dict[str, Any]
255
+ ) -> Tuple[Dict[str, Any], List[str]]:
256
+ """
257
+ Automatically fix security issues in configuration.
258
+
259
+ Args:
260
+ config: Configuration to fix
261
+
262
+ Returns:
263
+ Tuple of (fixed_config, list of changes made)
264
+ """
265
+ fixed_config = config.copy()
266
+ changes = []
267
+
268
+ # Fix critical security settings
269
+ for (
270
+ setting_path,
271
+ required_value,
272
+ ) in self.critical_security_settings.items():
273
+ current_value = self._get_nested_value(fixed_config, setting_path)
274
+
275
+ if current_value != required_value:
276
+ self._set_nested_value(fixed_config, setting_path, required_value)
277
+ changes.append(
278
+ f"Fixed critical setting: {setting_path} = {required_value}"
279
+ )
280
+
281
+ # Apply recommended security settings
282
+ for (
283
+ setting_path,
284
+ recommended_value,
285
+ ) in self.recommended_security_settings.items():
286
+ current_value = self._get_nested_value(fixed_config, setting_path)
287
+
288
+ if current_value is None: # Only set if not explicitly configured
289
+ self._set_nested_value(fixed_config, setting_path, recommended_value)
290
+ changes.append(
291
+ f"Applied recommended setting: {setting_path} = {recommended_value}"
292
+ )
293
+
294
+ return fixed_config, changes
295
+
296
+ def _set_nested_value(self, config: Dict[str, Any], path: str, value: Any) -> None:
297
+ """Set a nested configuration value using dot notation."""
298
+ keys = path.split(".")
299
+ current = config
300
+
301
+ # Navigate to parent
302
+ for key in keys[:-1]:
303
+ if key not in current or not isinstance(current[key], dict):
304
+ current[key] = {}
305
+ current = current[key]
306
+
307
+ # Set final value
308
+ current[keys[-1]] = value
309
+
310
+ def generate_security_report(self, config: Dict[str, Any]) -> Dict[str, Any]:
311
+ """
312
+ Generate a comprehensive security report for a configuration.
313
+
314
+ Args:
315
+ config: Configuration to analyze
316
+
317
+ Returns:
318
+ Security report dictionary
319
+ """
320
+ is_valid, errors, warnings = self.validate_security(config, strict_mode=False)
321
+
322
+ report = {
323
+ "timestamp": str(Path().resolve()),
324
+ "overall_status": (
325
+ "SECURE"
326
+ if is_valid and len(warnings) == 0
327
+ else "INSECURE" if not is_valid else "NEEDS_ATTENTION"
328
+ ),
329
+ "critical_errors": len([e for e in errors if "CRITICAL:" in e]),
330
+ "security_errors": len(errors),
331
+ "security_warnings": len(warnings),
332
+ "errors": errors,
333
+ "warnings": warnings,
334
+ "recommendations": [],
335
+ }
336
+
337
+ # Add specific recommendations
338
+ if report["critical_errors"] > 0:
339
+ report["recommendations"].append(
340
+ "URGENT: Fix critical security issues immediately. "
341
+ "These settings compromise system security."
342
+ )
343
+
344
+ if report["security_warnings"] > 0:
345
+ report["recommendations"].append(
346
+ "Review security warnings and consider applying recommended settings."
347
+ )
348
+
349
+ if report["overall_status"] == "SECURE":
350
+ report["recommendations"].append(
351
+ "Configuration follows security best practices. "
352
+ "Continue monitoring for changes."
353
+ )
354
+
355
+ # Security score (0-100)
356
+ len(self.critical_security_settings) + len(self.recommended_security_settings)
357
+ actual_issues = len(errors) + len(warnings)
358
+ report["security_score"] = max(0, 100 - (actual_issues * 10))
359
+
360
+ return report
361
+
362
+
363
+ def validate_config_security(
364
+ config: Dict[str, Any], strict_mode: bool = True
365
+ ) -> Tuple[bool, List[str], List[str]]:
366
+ """
367
+ Convenience function to validate configuration security.
368
+
369
+ Args:
370
+ config: Configuration to validate
371
+ strict_mode: If True, treat warnings as errors
372
+
373
+ Returns:
374
+ Tuple of (is_valid, errors, warnings)
375
+ """
376
+ validator = ConfigSecurityValidator()
377
+ return validator.validate_security(config, strict_mode)
378
+
379
+
380
+ def fix_config_security_issues(
381
+ config: Dict[str, Any],
382
+ ) -> Tuple[Dict[str, Any], List[str]]:
383
+ """
384
+ Convenience function to fix security issues in configuration.
385
+
386
+ Args:
387
+ config: Configuration to fix
388
+
389
+ Returns:
390
+ Tuple of (fixed_config, list of changes)
391
+ """
392
+ validator = ConfigSecurityValidator()
393
+ return validator.fix_security_issues(config)