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,406 @@
1
+ """Approval workflow for high-risk agent operations."""
2
+
3
+ import asyncio
4
+ import uuid
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime, timedelta
7
+ from enum import Enum
8
+ from typing import Any, Callable, Dict, List, Optional
9
+
10
+
11
+ class ApprovalStatus(Enum):
12
+ """Status of approval requests."""
13
+
14
+ PENDING = "pending"
15
+ APPROVED = "approved"
16
+ DENIED = "denied"
17
+ EXPIRED = "expired"
18
+ CANCELLED = "cancelled"
19
+
20
+
21
+ class RiskLevel(Enum):
22
+ """Risk levels for operations."""
23
+
24
+ LOW = "low"
25
+ MEDIUM = "medium"
26
+ HIGH = "high"
27
+ CRITICAL = "critical"
28
+
29
+
30
+ @dataclass
31
+ class ApprovalRequest:
32
+ """Represents an approval request for a risky operation."""
33
+
34
+ id: str = field(default_factory=lambda: str(uuid.uuid4()))
35
+ operation_type: str = ""
36
+ description: str = ""
37
+ risk_level: RiskLevel = RiskLevel.MEDIUM
38
+ requested_by: str = "agent"
39
+ requested_at: datetime = field(default_factory=datetime.now)
40
+ expires_at: Optional[datetime] = None
41
+ status: ApprovalStatus = ApprovalStatus.PENDING
42
+ approver: Optional[str] = None
43
+ approved_at: Optional[datetime] = None
44
+ denial_reason: Optional[str] = None
45
+ metadata: Dict[str, Any] = field(default_factory=dict)
46
+
47
+ def is_expired(self) -> bool:
48
+ """Check if the approval request has expired."""
49
+ if self.expires_at and datetime.now() > self.expires_at:
50
+ return True
51
+ return False
52
+
53
+ def time_remaining(self) -> Optional[timedelta]:
54
+ """Get time remaining before expiration."""
55
+ if self.expires_at:
56
+ remaining = self.expires_at - datetime.now()
57
+ return remaining if remaining.total_seconds() > 0 else timedelta(0)
58
+ return None
59
+
60
+
61
+ class ApprovalWorkflow:
62
+ """Manages approval workflows for high-risk operations."""
63
+
64
+ def __init__(
65
+ self,
66
+ default_expiry_minutes: int = 30,
67
+ auto_approve_low_risk: bool = True,
68
+ ):
69
+ self.pending_requests: Dict[str, ApprovalRequest] = {}
70
+ self.completed_requests: Dict[str, ApprovalRequest] = {}
71
+ self.default_expiry_minutes = default_expiry_minutes
72
+ self.auto_approve_low_risk = auto_approve_low_risk
73
+ self.approval_handlers: Dict[RiskLevel, List[Callable]] = {
74
+ level: [] for level in RiskLevel
75
+ }
76
+ self.risk_assessment_rules = self._get_default_risk_rules()
77
+
78
+ def _get_default_risk_rules(self) -> Dict[str, RiskLevel]:
79
+ """Get default risk assessment rules for operation types."""
80
+ return {
81
+ # Low risk operations
82
+ "file_read": RiskLevel.LOW,
83
+ "file_list": RiskLevel.LOW,
84
+ "system_info": RiskLevel.LOW,
85
+ "network_get": RiskLevel.LOW,
86
+ # Medium risk operations
87
+ "file_write": RiskLevel.MEDIUM,
88
+ "file_create": RiskLevel.MEDIUM,
89
+ "network_post": RiskLevel.MEDIUM,
90
+ "command_execute": RiskLevel.MEDIUM,
91
+ # High risk operations
92
+ "file_delete": RiskLevel.HIGH,
93
+ "system_config_change": RiskLevel.HIGH,
94
+ "package_install": RiskLevel.HIGH,
95
+ "service_control": RiskLevel.HIGH,
96
+ # Critical risk operations
97
+ "system_admin": RiskLevel.CRITICAL,
98
+ "credential_access": RiskLevel.CRITICAL,
99
+ "network_server": RiskLevel.CRITICAL,
100
+ "data_export": RiskLevel.CRITICAL,
101
+ }
102
+
103
+ def assess_risk_level(
104
+ self, operation_type: str, metadata: Optional[Dict[str, Any]] = None
105
+ ) -> RiskLevel:
106
+ """Assess the risk level of an operation."""
107
+
108
+ # Base risk from operation type
109
+ base_risk = self.risk_assessment_rules.get(operation_type, RiskLevel.MEDIUM)
110
+
111
+ if not metadata:
112
+ return base_risk
113
+
114
+ # Risk escalation factors
115
+ risk_factors = []
116
+
117
+ # Check for sensitive paths
118
+ if "path" in metadata:
119
+ path = metadata["path"].lower()
120
+ sensitive_patterns = [
121
+ ".ssh",
122
+ ".env",
123
+ "password",
124
+ "secret",
125
+ "credential",
126
+ "/etc",
127
+ "/system",
128
+ "config",
129
+ "settings",
130
+ ]
131
+ if any(pattern in path for pattern in sensitive_patterns):
132
+ risk_factors.append("sensitive_path")
133
+
134
+ # Check for dangerous commands
135
+ if "command" in metadata:
136
+ command = metadata["command"].lower()
137
+ dangerous_commands = [
138
+ "rm -rf",
139
+ "sudo",
140
+ "chmod 777",
141
+ "curl",
142
+ "wget",
143
+ "nc",
144
+ "netcat",
145
+ "ssh",
146
+ "scp",
147
+ ]
148
+ if any(cmd in command for cmd in dangerous_commands):
149
+ risk_factors.append("dangerous_command")
150
+
151
+ # Check for network operations
152
+ if "network" in metadata and metadata["network"]:
153
+ risk_factors.append("network_operation")
154
+
155
+ # Check for external URLs
156
+ if "url" in metadata:
157
+ url = metadata["url"]
158
+ if not any(
159
+ domain in url for domain in ["localhost", "127.0.0.1", "0.0.0.0"]
160
+ ):
161
+ risk_factors.append("external_network")
162
+
163
+ # Escalate risk based on factors
164
+ if len(risk_factors) >= 3:
165
+ return RiskLevel.CRITICAL
166
+ elif len(risk_factors) >= 2:
167
+ return RiskLevel.HIGH
168
+ elif len(risk_factors) >= 1:
169
+ # Escalate by one level
170
+ levels = [
171
+ RiskLevel.LOW,
172
+ RiskLevel.MEDIUM,
173
+ RiskLevel.HIGH,
174
+ RiskLevel.CRITICAL,
175
+ ]
176
+ current_index = levels.index(base_risk)
177
+ return levels[min(current_index + 1, len(levels) - 1)]
178
+
179
+ return base_risk
180
+
181
+ async def request_approval(
182
+ self,
183
+ operation_type: str,
184
+ description: str,
185
+ metadata: Optional[Dict[str, Any]] = None,
186
+ timeout_minutes: Optional[int] = None,
187
+ ) -> ApprovalRequest:
188
+ """Request approval for an operation."""
189
+
190
+ # Assess risk level
191
+ risk_level = self.assess_risk_level(operation_type, metadata)
192
+
193
+ # Create approval request
194
+ request = ApprovalRequest(
195
+ operation_type=operation_type,
196
+ description=description,
197
+ risk_level=risk_level,
198
+ metadata=metadata or {},
199
+ )
200
+
201
+ # Set expiration
202
+ expiry_minutes = timeout_minutes or self.default_expiry_minutes
203
+ request.expires_at = datetime.now() + timedelta(minutes=expiry_minutes)
204
+
205
+ # Auto-approve low risk operations if configured
206
+ if self.auto_approve_low_risk and risk_level == RiskLevel.LOW:
207
+ request.status = ApprovalStatus.APPROVED
208
+ request.approver = "auto_approval"
209
+ request.approved_at = datetime.now()
210
+ self.completed_requests[request.id] = request
211
+ return request
212
+
213
+ # Add to pending requests
214
+ self.pending_requests[request.id] = request
215
+
216
+ # Trigger approval handlers
217
+ await self._trigger_approval_handlers(request)
218
+
219
+ return request
220
+
221
+ async def _trigger_approval_handlers(self, request: ApprovalRequest) -> None:
222
+ """Trigger approval handlers for a request."""
223
+
224
+ handlers = self.approval_handlers.get(request.risk_level, [])
225
+ for handler in handlers:
226
+ try:
227
+ if asyncio.iscoroutinefunction(handler):
228
+ await handler(request)
229
+ else:
230
+ handler(request)
231
+ except Exception as e:
232
+ print(f"Error in approval handler: {e}")
233
+
234
+ def approve_request(
235
+ self, request_id: str, approver: str, reason: Optional[str] = None
236
+ ) -> bool:
237
+ """Approve a pending request."""
238
+
239
+ if request_id not in self.pending_requests:
240
+ return False
241
+
242
+ request = self.pending_requests[request_id]
243
+
244
+ # Check if expired
245
+ if request.is_expired():
246
+ request.status = ApprovalStatus.EXPIRED
247
+ self.completed_requests[request_id] = request
248
+ del self.pending_requests[request_id]
249
+ return False
250
+
251
+ # Approve the request
252
+ request.status = ApprovalStatus.APPROVED
253
+ request.approver = approver
254
+ request.approved_at = datetime.now()
255
+ if reason:
256
+ request.metadata["approval_reason"] = reason
257
+
258
+ # Move to completed
259
+ self.completed_requests[request_id] = request
260
+ del self.pending_requests[request_id]
261
+
262
+ return True
263
+
264
+ def deny_request(self, request_id: str, approver: str, reason: str) -> bool:
265
+ """Deny a pending request."""
266
+
267
+ if request_id not in self.pending_requests:
268
+ return False
269
+
270
+ request = self.pending_requests[request_id]
271
+
272
+ # Deny the request
273
+ request.status = ApprovalStatus.DENIED
274
+ request.approver = approver
275
+ request.approved_at = datetime.now()
276
+ request.denial_reason = reason
277
+
278
+ # Move to completed
279
+ self.completed_requests[request_id] = request
280
+ del self.pending_requests[request_id]
281
+
282
+ return True
283
+
284
+ def cancel_request(self, request_id: str) -> bool:
285
+ """Cancel a pending request."""
286
+
287
+ if request_id not in self.pending_requests:
288
+ return False
289
+
290
+ request = self.pending_requests[request_id]
291
+ request.status = ApprovalStatus.CANCELLED
292
+
293
+ # Move to completed
294
+ self.completed_requests[request_id] = request
295
+ del self.pending_requests[request_id]
296
+
297
+ return True
298
+
299
+ async def wait_for_approval(
300
+ self, request_id: str, check_interval: float = 1.0
301
+ ) -> ApprovalStatus:
302
+ """Wait for an approval request to be resolved."""
303
+
304
+ while request_id in self.pending_requests:
305
+ request = self.pending_requests[request_id]
306
+
307
+ # Check if expired
308
+ if request.is_expired():
309
+ request.status = ApprovalStatus.EXPIRED
310
+ self.completed_requests[request_id] = request
311
+ del self.pending_requests[request_id]
312
+ return ApprovalStatus.EXPIRED
313
+
314
+ # Wait before checking again
315
+ await asyncio.sleep(check_interval)
316
+
317
+ # Request was resolved
318
+ if request_id in self.completed_requests:
319
+ return self.completed_requests[request_id].status
320
+
321
+ return ApprovalStatus.CANCELLED
322
+
323
+ def get_pending_requests(
324
+ self, risk_level: Optional[RiskLevel] = None
325
+ ) -> List[ApprovalRequest]:
326
+ """Get all pending approval requests."""
327
+
328
+ requests = list(self.pending_requests.values())
329
+
330
+ if risk_level:
331
+ requests = [r for r in requests if r.risk_level == risk_level]
332
+
333
+ return sorted(requests, key=lambda r: r.requested_at)
334
+
335
+ def get_request(self, request_id: str) -> Optional[ApprovalRequest]:
336
+ """Get an approval request by ID."""
337
+
338
+ if request_id in self.pending_requests:
339
+ return self.pending_requests[request_id]
340
+ elif request_id in self.completed_requests:
341
+ return self.completed_requests[request_id]
342
+
343
+ return None
344
+
345
+ def cleanup_expired_requests(self) -> List[str]:
346
+ """Clean up expired requests and return their IDs."""
347
+
348
+ expired_ids = []
349
+
350
+ for request_id, request in list(self.pending_requests.items()):
351
+ if request.is_expired():
352
+ request.status = ApprovalStatus.EXPIRED
353
+ self.completed_requests[request_id] = request
354
+ del self.pending_requests[request_id]
355
+ expired_ids.append(request_id)
356
+
357
+ return expired_ids
358
+
359
+ def add_approval_handler(
360
+ self, risk_level: RiskLevel, handler: Callable[[ApprovalRequest], Any]
361
+ ) -> None:
362
+ """Add an approval handler for a specific risk level."""
363
+
364
+ self.approval_handlers[risk_level].append(handler)
365
+
366
+ def remove_approval_handler(
367
+ self, risk_level: RiskLevel, handler: Callable[[ApprovalRequest], Any]
368
+ ) -> bool:
369
+ """Remove an approval handler."""
370
+
371
+ try:
372
+ self.approval_handlers[risk_level].remove(handler)
373
+ return True
374
+ except ValueError:
375
+ return False
376
+
377
+ def get_approval_stats(self) -> Dict[str, Any]:
378
+ """Get approval workflow statistics."""
379
+
380
+ pending_count = len(self.pending_requests)
381
+ completed_count = len(self.completed_requests)
382
+
383
+ # Count by status
384
+ status_counts = {}
385
+ for request in self.completed_requests.values():
386
+ status = request.status.value
387
+ status_counts[status] = status_counts.get(status, 0) + 1
388
+
389
+ # Count by risk level
390
+ risk_counts = {}
391
+ all_requests = list(self.pending_requests.values()) + list(
392
+ self.completed_requests.values()
393
+ )
394
+ for request in all_requests:
395
+ risk = request.risk_level.value
396
+ risk_counts[risk] = risk_counts.get(risk, 0) + 1
397
+
398
+ return {
399
+ "pending_requests": pending_count,
400
+ "completed_requests": completed_count,
401
+ "total_requests": pending_count + completed_count,
402
+ "status_breakdown": status_counts,
403
+ "risk_level_breakdown": risk_counts,
404
+ "auto_approve_enabled": self.auto_approve_low_risk,
405
+ "default_expiry_minutes": self.default_expiry_minutes,
406
+ }