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,460 @@
1
+ """
2
+ Approval Context and Data Structures for Omnimancer Agent Operations.
3
+
4
+ This module defines the data structures and context objects used
5
+ throughout the approval workflow system.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from datetime import datetime
10
+ from enum import Enum
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional, Union
13
+
14
+ from .rich_renderer import RiskLevel
15
+
16
+
17
+ class OperationStatus(Enum):
18
+ """Status of an operation."""
19
+
20
+ PENDING = "pending"
21
+ APPROVED = "approved"
22
+ DENIED = "denied"
23
+ EXECUTING = "executing"
24
+ COMPLETED = "completed"
25
+ FAILED = "failed"
26
+ CANCELLED = "cancelled"
27
+
28
+
29
+ class FileOperationType(Enum):
30
+ """Types of file operations."""
31
+
32
+ READ = "read"
33
+ WRITE = "write"
34
+ CREATE = "create"
35
+ DELETE = "delete"
36
+ MOVE = "move"
37
+ COPY = "copy"
38
+ MODIFY = "modify"
39
+
40
+
41
+ @dataclass
42
+ class FileChange:
43
+ """Represents a file change in an operation."""
44
+
45
+ path: Path
46
+ operation: FileOperationType
47
+ content_preview: Optional[str] = None
48
+ size_bytes: Optional[int] = None
49
+ permissions: Optional[str] = None
50
+ backup_path: Optional[Path] = None
51
+
52
+ @property
53
+ def relative_path(self) -> str:
54
+ """Get relative path as string."""
55
+ return str(self.path)
56
+
57
+
58
+ @dataclass
59
+ class SecurityFlags:
60
+ """Security-related flags for operations."""
61
+
62
+ requires_sudo: bool = False
63
+ modifies_system_files: bool = False
64
+ accesses_network: bool = False
65
+ executes_code: bool = False
66
+ modifies_permissions: bool = False
67
+ creates_processes: bool = False
68
+ accesses_sensitive_data: bool = False
69
+
70
+ def get_risk_factors(self) -> List[str]:
71
+ """Get list of security risk factors."""
72
+ factors = []
73
+
74
+ if self.requires_sudo:
75
+ factors.append("Requires elevated privileges")
76
+ if self.modifies_system_files:
77
+ factors.append("Modifies system files")
78
+ if self.accesses_network:
79
+ factors.append("Makes network requests")
80
+ if self.executes_code:
81
+ factors.append("Executes external code")
82
+ if self.modifies_permissions:
83
+ factors.append("Changes file permissions")
84
+ if self.creates_processes:
85
+ factors.append("Creates new processes")
86
+ if self.accesses_sensitive_data:
87
+ factors.append("Accesses sensitive data")
88
+
89
+ return factors
90
+
91
+
92
+ @dataclass
93
+ class OperationDetails:
94
+ """Detailed information about an operation requiring approval."""
95
+
96
+ # Core operation information
97
+ operation_type: str
98
+ target: Optional[str] = None
99
+ description: Optional[str] = None
100
+
101
+ # Risk assessment
102
+ risk_level: Union[RiskLevel, int, str] = RiskLevel.MEDIUM
103
+ risk_factors: List[str] = field(default_factory=list)
104
+ security_flags: SecurityFlags = field(default_factory=SecurityFlags)
105
+
106
+ # Command/execution details
107
+ command: Optional[str] = None
108
+ arguments: List[str] = field(default_factory=list)
109
+ working_directory: Optional[str] = None
110
+ environment_vars: Dict[str, str] = field(default_factory=dict)
111
+
112
+ # File operations
113
+ files_affected: List[Path] = field(default_factory=list)
114
+ file_changes: List[FileChange] = field(default_factory=list)
115
+
116
+ # Timing and resources
117
+ estimated_time: Optional[float] = None
118
+ max_memory_mb: Optional[int] = None
119
+ max_disk_mb: Optional[int] = None
120
+
121
+ # Operation metadata
122
+ operation_id: Optional[str] = None
123
+ parent_operation_id: Optional[str] = None
124
+ batch_id: Optional[str] = None
125
+ metadata: Dict[str, Any] = field(default_factory=dict)
126
+
127
+ def __post_init__(self):
128
+ """Post-initialization processing."""
129
+ # Auto-populate risk factors from security flags
130
+ if not self.risk_factors and hasattr(self.security_flags, "get_risk_factors"):
131
+ self.risk_factors = self.security_flags.get_risk_factors()
132
+
133
+ # Ensure paths are Path objects
134
+ self.files_affected = [
135
+ Path(f) if not isinstance(f, Path) else f for f in self.files_affected
136
+ ]
137
+
138
+ def add_file_change(
139
+ self,
140
+ path: Union[str, Path],
141
+ operation: FileOperationType,
142
+ content_preview: Optional[str] = None,
143
+ ) -> None:
144
+ """Add a file change to this operation."""
145
+ file_change = FileChange(
146
+ path=Path(path) if not isinstance(path, Path) else path,
147
+ operation=operation,
148
+ content_preview=content_preview,
149
+ )
150
+ self.file_changes.append(file_change)
151
+
152
+ # Also add to files_affected if not already present
153
+ if file_change.path not in self.files_affected:
154
+ self.files_affected.append(file_change.path)
155
+
156
+ def get_total_files_affected(self) -> int:
157
+ """Get total number of files affected."""
158
+ return len(set(self.files_affected + [fc.path for fc in self.file_changes]))
159
+
160
+ def get_operation_summary(self) -> str:
161
+ """Get a brief summary of the operation."""
162
+ if self.description:
163
+ return self.description
164
+
165
+ summary_parts = [self.operation_type]
166
+
167
+ if self.target:
168
+ summary_parts.append(f"on {self.target}")
169
+ elif self.files_affected:
170
+ if len(self.files_affected) == 1:
171
+ summary_parts.append(f"on {self.files_affected[0]}")
172
+ else:
173
+ summary_parts.append(f"on {len(self.files_affected)} files")
174
+
175
+ return " ".join(summary_parts)
176
+
177
+
178
+ @dataclass
179
+ class ApprovalDecision:
180
+ """Represents a user's approval decision."""
181
+
182
+ approved: bool
183
+ reason: Optional[str] = None
184
+ timestamp: datetime = field(default_factory=datetime.now)
185
+ user_id: Optional[str] = None
186
+ session_id: Optional[str] = None
187
+ additional_data: Dict[str, Any] = field(default_factory=dict)
188
+
189
+ # Decision metadata
190
+ response_time_seconds: Optional[float] = None
191
+ approval_method: Optional[str] = None # "manual", "auto", "cached", etc.
192
+ confidence_level: Optional[float] = None # 0.0 - 1.0
193
+
194
+ def to_dict(self) -> Dict[str, Any]:
195
+ """Convert to dictionary for serialization."""
196
+ return {
197
+ "approved": self.approved,
198
+ "reason": self.reason,
199
+ "timestamp": self.timestamp.isoformat(),
200
+ "user_id": self.user_id,
201
+ "session_id": self.session_id,
202
+ "additional_data": self.additional_data,
203
+ "response_time_seconds": self.response_time_seconds,
204
+ "approval_method": self.approval_method,
205
+ "confidence_level": self.confidence_level,
206
+ }
207
+
208
+ @classmethod
209
+ def from_dict(cls, data: Dict[str, Any]) -> "ApprovalDecision":
210
+ """Create from dictionary."""
211
+ timestamp = (
212
+ datetime.fromisoformat(data["timestamp"])
213
+ if "timestamp" in data
214
+ else datetime.now()
215
+ )
216
+
217
+ return cls(
218
+ approved=data["approved"],
219
+ reason=data.get("reason"),
220
+ timestamp=timestamp,
221
+ user_id=data.get("user_id"),
222
+ session_id=data.get("session_id"),
223
+ additional_data=data.get("additional_data", {}),
224
+ response_time_seconds=data.get("response_time_seconds"),
225
+ approval_method=data.get("approval_method"),
226
+ confidence_level=data.get("confidence_level"),
227
+ )
228
+
229
+
230
+ @dataclass
231
+ class ApprovalContext:
232
+ """Complete context for an approval request."""
233
+
234
+ # Core identification
235
+ context_id: str = field(
236
+ default_factory=lambda: f"ctx_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
237
+ )
238
+ agent_name: str = "Omnimancer Agent"
239
+ timestamp: datetime = field(default_factory=datetime.now)
240
+
241
+ # Operation details
242
+ operation_details: OperationDetails = field(
243
+ default_factory=lambda: OperationDetails(operation_type="unknown")
244
+ )
245
+
246
+ # Content for display
247
+ diff_content: Optional[str] = None
248
+ log_content: Optional[str] = None
249
+ preview_content: Optional[str] = None
250
+
251
+ # Context and history
252
+ previous_operations: List[OperationDetails] = field(default_factory=list)
253
+ related_context: Dict[str, Any] = field(default_factory=dict)
254
+
255
+ # Session information
256
+ session_id: Optional[str] = None
257
+ user_id: Optional[str] = None
258
+ conversation_history: List[str] = field(default_factory=list)
259
+
260
+ # Approval workflow state
261
+ status: OperationStatus = OperationStatus.PENDING
262
+ decision: Optional[ApprovalDecision] = None
263
+ auto_approval_eligible: bool = False
264
+ approval_timeout_seconds: Optional[int] = None
265
+
266
+ # Display customization
267
+ display_options: Dict[str, Any] = field(default_factory=dict)
268
+
269
+ def add_conversation_entry(self, message: str) -> None:
270
+ """Add an entry to the conversation history."""
271
+ timestamp = datetime.now().strftime("%H:%M:%S")
272
+ self.conversation_history.append(f"[{timestamp}] {message}")
273
+
274
+ def add_related_context(self, key: str, value: Any) -> None:
275
+ """Add related context information."""
276
+ self.related_context[key] = value
277
+
278
+ def set_decision(self, decision: ApprovalDecision) -> None:
279
+ """Set the approval decision and update status."""
280
+ self.decision = decision
281
+ self.status = (
282
+ OperationStatus.APPROVED if decision.approved else OperationStatus.DENIED
283
+ )
284
+
285
+ def get_context_summary(self) -> Dict[str, Any]:
286
+ """Get a summary of the context for logging."""
287
+ return {
288
+ "context_id": self.context_id,
289
+ "agent_name": self.agent_name,
290
+ "operation_type": self.operation_details.operation_type,
291
+ "risk_level": str(self.operation_details.risk_level),
292
+ "files_affected": len(self.operation_details.files_affected),
293
+ "status": self.status.value,
294
+ "approved": self.decision.approved if self.decision else None,
295
+ "timestamp": self.timestamp.isoformat(),
296
+ }
297
+
298
+ def is_completed(self) -> bool:
299
+ """Check if the approval process is completed."""
300
+ return self.status in [
301
+ OperationStatus.APPROVED,
302
+ OperationStatus.DENIED,
303
+ OperationStatus.CANCELLED,
304
+ ]
305
+
306
+ def to_dict(self) -> Dict[str, Any]:
307
+ """Convert to dictionary for serialization."""
308
+ return {
309
+ "context_id": self.context_id,
310
+ "agent_name": self.agent_name,
311
+ "timestamp": self.timestamp.isoformat(),
312
+ "operation_details": {
313
+ "operation_type": self.operation_details.operation_type,
314
+ "target": self.operation_details.target,
315
+ "description": self.operation_details.description,
316
+ "risk_level": str(self.operation_details.risk_level),
317
+ "risk_factors": self.operation_details.risk_factors,
318
+ "command": self.operation_details.command,
319
+ "arguments": self.operation_details.arguments,
320
+ "files_affected": [
321
+ str(f) for f in self.operation_details.files_affected
322
+ ],
323
+ "metadata": self.operation_details.metadata,
324
+ },
325
+ "diff_content": self.diff_content,
326
+ "status": self.status.value,
327
+ "decision": self.decision.to_dict() if self.decision else None,
328
+ "session_id": self.session_id,
329
+ "user_id": self.user_id,
330
+ "conversation_history": self.conversation_history,
331
+ "related_context": self.related_context,
332
+ }
333
+
334
+
335
+ # Utility functions for creating common contexts
336
+
337
+
338
+ def create_file_operation_context(
339
+ operation_type: str,
340
+ file_path: Union[str, Path],
341
+ content: Optional[str] = None,
342
+ agent_name: str = "Omnimancer Agent",
343
+ ) -> ApprovalContext:
344
+ """Create approval context for file operations."""
345
+ file_path = Path(file_path) if not isinstance(file_path, Path) else file_path
346
+
347
+ # Determine file operation type
348
+ file_op_type = FileOperationType.MODIFY
349
+ if "create" in operation_type.lower():
350
+ file_op_type = FileOperationType.CREATE
351
+ elif "delete" in operation_type.lower():
352
+ file_op_type = FileOperationType.DELETE
353
+ elif "read" in operation_type.lower():
354
+ file_op_type = FileOperationType.READ
355
+ elif "write" in operation_type.lower():
356
+ file_op_type = FileOperationType.WRITE
357
+
358
+ # Create operation details
359
+ operation_details = OperationDetails(
360
+ operation_type=operation_type,
361
+ target=str(file_path),
362
+ description=f"{operation_type} on {file_path.name}",
363
+ files_affected=[file_path],
364
+ )
365
+
366
+ # Add file change
367
+ operation_details.add_file_change(
368
+ file_path,
369
+ file_op_type,
370
+ content[:200] + "..." if content and len(content) > 200 else content,
371
+ )
372
+
373
+ # Create context
374
+ context = ApprovalContext(
375
+ agent_name=agent_name,
376
+ operation_details=operation_details,
377
+ preview_content=content,
378
+ )
379
+
380
+ return context
381
+
382
+
383
+ def create_command_execution_context(
384
+ command: str,
385
+ arguments: Optional[List[str]] = None,
386
+ working_directory: Optional[str] = None,
387
+ agent_name: str = "Omnimancer Agent",
388
+ ) -> ApprovalContext:
389
+ """Create approval context for command execution."""
390
+ arguments = arguments or []
391
+
392
+ # Assess security flags
393
+ security_flags = SecurityFlags()
394
+
395
+ # Basic security assessment based on command
396
+ if command in ["sudo", "su", "doas"]:
397
+ security_flags.requires_sudo = True
398
+
399
+ if any(
400
+ sys_path in " ".join([command] + arguments)
401
+ for sys_path in ["/etc", "/usr/bin", "/sys"]
402
+ ):
403
+ security_flags.modifies_system_files = True
404
+
405
+ if any(net_cmd in command for net_cmd in ["curl", "wget", "nc", "ssh"]):
406
+ security_flags.accesses_network = True
407
+
408
+ if any(
409
+ exec_cmd in command for exec_cmd in ["eval", "exec", "python", "bash", "sh"]
410
+ ):
411
+ security_flags.executes_code = True
412
+
413
+ # Create operation details
414
+ operation_details = OperationDetails(
415
+ operation_type="command_execute",
416
+ target=command,
417
+ description=f"Execute: {command} {' '.join(arguments)}",
418
+ command=command,
419
+ arguments=arguments,
420
+ working_directory=working_directory,
421
+ security_flags=security_flags,
422
+ )
423
+
424
+ # Create context
425
+ context = ApprovalContext(
426
+ agent_name=agent_name, operation_details=operation_details
427
+ )
428
+
429
+ return context
430
+
431
+
432
+ def create_web_request_context(
433
+ url: str,
434
+ method: str = "GET",
435
+ headers: Optional[Dict[str, str]] = None,
436
+ agent_name: str = "Omnimancer Agent",
437
+ ) -> ApprovalContext:
438
+ """Create approval context for web requests."""
439
+ # Create security flags
440
+ security_flags = SecurityFlags(accesses_network=True)
441
+
442
+ # Additional assessment based on URL
443
+ if any(sensitive in url.lower() for sensitive in ["admin", "api", "auth", "login"]):
444
+ security_flags.accesses_sensitive_data = True
445
+
446
+ # Create operation details
447
+ operation_details = OperationDetails(
448
+ operation_type="web_request",
449
+ target=url,
450
+ description=f"{method} request to {url}",
451
+ security_flags=security_flags,
452
+ metadata={"method": method, "headers": headers or {}, "url": url},
453
+ )
454
+
455
+ # Create context
456
+ context = ApprovalContext(
457
+ agent_name=agent_name, operation_details=operation_details
458
+ )
459
+
460
+ return context