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,379 @@
1
+ """Sandbox manager for isolating and limiting agent operations."""
2
+
3
+ import os
4
+ import resource
5
+ import shutil
6
+ import subprocess
7
+ import tempfile
8
+ import threading
9
+ import time
10
+ from contextlib import contextmanager
11
+ from typing import Any, Callable, Dict, List, Optional
12
+
13
+ import psutil
14
+
15
+
16
+ class ResourceLimits:
17
+ """Resource limits for sandboxed operations."""
18
+
19
+ def __init__(
20
+ self,
21
+ max_memory_mb: int = 512,
22
+ max_cpu_seconds: int = 30,
23
+ max_file_size_mb: int = 100,
24
+ max_open_files: int = 100,
25
+ max_processes: int = 10,
26
+ timeout_seconds: int = 60,
27
+ ):
28
+ self.max_memory_mb = max_memory_mb
29
+ self.max_cpu_seconds = max_cpu_seconds
30
+ self.max_file_size_mb = max_file_size_mb
31
+ self.max_open_files = max_open_files
32
+ self.max_processes = max_processes
33
+ self.timeout_seconds = timeout_seconds
34
+
35
+
36
+ class SandboxedProcess:
37
+ """Represents a process running in a sandbox."""
38
+
39
+ def __init__(
40
+ self, process: subprocess.Popen, temp_dir: str, limits: ResourceLimits
41
+ ):
42
+ self.process = process
43
+ self.temp_dir = temp_dir
44
+ self.limits = limits
45
+ self.start_time = time.time()
46
+ self.monitor_thread = None
47
+ self.terminated = False
48
+
49
+ def is_running(self) -> bool:
50
+ """Check if process is still running."""
51
+ return self.process.poll() is None and not self.terminated
52
+
53
+ def terminate(self) -> None:
54
+ """Terminate the sandboxed process."""
55
+ if not self.terminated:
56
+ self.terminated = True
57
+ try:
58
+ if self.process.poll() is None:
59
+ self.process.terminate()
60
+ # Give it a moment to terminate gracefully
61
+ try:
62
+ self.process.wait(timeout=5)
63
+ except subprocess.TimeoutExpired:
64
+ self.process.kill()
65
+ except (ProcessLookupError, OSError):
66
+ pass
67
+
68
+ def cleanup(self) -> None:
69
+ """Clean up temporary resources."""
70
+ self.terminate()
71
+ if os.path.exists(self.temp_dir):
72
+ try:
73
+ shutil.rmtree(self.temp_dir)
74
+ except (OSError, PermissionError):
75
+ pass
76
+
77
+
78
+ class SandboxManager:
79
+ """Manages sandboxed execution of agent operations."""
80
+
81
+ def __init__(self, default_limits: Optional[ResourceLimits] = None):
82
+ self.default_limits = default_limits or ResourceLimits()
83
+ self.active_processes: Dict[int, SandboxedProcess] = {}
84
+ self.temp_base_dir = tempfile.gettempdir()
85
+
86
+ def create_sandbox_environment(
87
+ self, limits: Optional[ResourceLimits] = None
88
+ ) -> str:
89
+ """Create a temporary sandbox directory."""
90
+ limits = limits or self.default_limits
91
+
92
+ # Create temporary directory for sandbox
93
+ temp_dir = tempfile.mkdtemp(prefix="omnimancer_sandbox_")
94
+
95
+ # Set up basic directory structure
96
+ os.makedirs(os.path.join(temp_dir, "workspace"), exist_ok=True)
97
+ os.makedirs(os.path.join(temp_dir, "output"), exist_ok=True)
98
+
99
+ # Create sandbox info file
100
+ info_file = os.path.join(temp_dir, "sandbox_info.txt")
101
+ with open(info_file, "w") as f:
102
+ f.write(f"Sandbox created at: {time.ctime()}\n")
103
+ f.write(f"Memory limit: {limits.max_memory_mb} MB\n")
104
+ f.write(f"CPU limit: {limits.max_cpu_seconds} seconds\n")
105
+ f.write(f"Timeout: {limits.timeout_seconds} seconds\n")
106
+
107
+ return temp_dir
108
+
109
+ def execute_sandboxed_command(
110
+ self,
111
+ command: List[str],
112
+ working_dir: Optional[str] = None,
113
+ env_vars: Optional[Dict[str, str]] = None,
114
+ limits: Optional[ResourceLimits] = None,
115
+ input_data: Optional[str] = None,
116
+ ) -> Dict[str, Any]:
117
+ """Execute a command in a sandboxed environment."""
118
+
119
+ limits = limits or self.default_limits
120
+ sandbox_dir = self.create_sandbox_environment(limits)
121
+
122
+ try:
123
+ # Prepare environment
124
+ sandbox_env = os.environ.copy()
125
+ if env_vars:
126
+ sandbox_env.update(env_vars)
127
+
128
+ # Restrict environment variables
129
+ sandbox_env = self._filter_environment_variables(sandbox_env)
130
+
131
+ # Set working directory
132
+ work_dir = working_dir or os.path.join(sandbox_dir, "workspace")
133
+
134
+ # Prepare process arguments
135
+ process_args = {
136
+ "args": command,
137
+ "cwd": work_dir,
138
+ "env": sandbox_env,
139
+ "stdin": subprocess.PIPE if input_data else None,
140
+ "stdout": subprocess.PIPE,
141
+ "stderr": subprocess.PIPE,
142
+ "text": True,
143
+ "preexec_fn": self._setup_process_limits(limits),
144
+ }
145
+
146
+ # Start process
147
+ process = subprocess.Popen(**process_args)
148
+ sandboxed_proc = SandboxedProcess(process, sandbox_dir, limits)
149
+
150
+ # Track the process
151
+ self.active_processes[process.pid] = sandboxed_proc
152
+
153
+ # Start monitoring
154
+ sandboxed_proc.monitor_thread = threading.Thread(
155
+ target=self._monitor_process, args=(sandboxed_proc,)
156
+ )
157
+ sandboxed_proc.monitor_thread.start()
158
+
159
+ # Execute and wait for completion
160
+ try:
161
+ stdout, stderr = process.communicate(
162
+ input=input_data, timeout=limits.timeout_seconds
163
+ )
164
+ return_code = process.returncode
165
+
166
+ except subprocess.TimeoutExpired:
167
+ sandboxed_proc.terminate()
168
+ return {
169
+ "success": False,
170
+ "return_code": -1,
171
+ "stdout": "",
172
+ "stderr": "Command timed out",
173
+ "sandbox_dir": sandbox_dir,
174
+ }
175
+
176
+ return {
177
+ "success": return_code == 0,
178
+ "return_code": return_code,
179
+ "stdout": stdout or "",
180
+ "stderr": stderr or "",
181
+ "sandbox_dir": sandbox_dir,
182
+ }
183
+
184
+ except Exception as e:
185
+ return {
186
+ "success": False,
187
+ "return_code": -1,
188
+ "stdout": "",
189
+ "stderr": f"Sandbox execution error: {str(e)}",
190
+ "sandbox_dir": sandbox_dir,
191
+ }
192
+
193
+ finally:
194
+ # Clean up
195
+ if process.pid in self.active_processes:
196
+ self.active_processes[process.pid].cleanup()
197
+ del self.active_processes[process.pid]
198
+
199
+ def _setup_process_limits(self, limits: ResourceLimits) -> Callable:
200
+ """Create a function to set up process resource limits."""
201
+
202
+ def setup_limits():
203
+ try:
204
+ # Set memory limit (in bytes)
205
+ memory_limit = limits.max_memory_mb * 1024 * 1024
206
+ resource.setrlimit(resource.RLIMIT_AS, (memory_limit, memory_limit))
207
+
208
+ # Set CPU time limit (in seconds)
209
+ resource.setrlimit(
210
+ resource.RLIMIT_CPU,
211
+ (limits.max_cpu_seconds, limits.max_cpu_seconds),
212
+ )
213
+
214
+ # Set file size limit (in bytes)
215
+ file_size_limit = limits.max_file_size_mb * 1024 * 1024
216
+ resource.setrlimit(
217
+ resource.RLIMIT_FSIZE, (file_size_limit, file_size_limit)
218
+ )
219
+
220
+ # Set maximum number of open files
221
+ resource.setrlimit(
222
+ resource.RLIMIT_NOFILE,
223
+ (limits.max_open_files, limits.max_open_files),
224
+ )
225
+
226
+ # Set maximum number of processes
227
+ resource.setrlimit(
228
+ resource.RLIMIT_NPROC,
229
+ (limits.max_processes, limits.max_processes),
230
+ )
231
+
232
+ except (OSError, ValueError) as e:
233
+ # Log the error but don't fail the process creation
234
+ print(f"Warning: Could not set all resource limits: {e}")
235
+
236
+ return setup_limits
237
+
238
+ def _monitor_process(self, sandboxed_proc: SandboxedProcess) -> None:
239
+ """Monitor a sandboxed process for resource violations."""
240
+
241
+ while sandboxed_proc.is_running():
242
+ try:
243
+ # Check if process still exists
244
+ if sandboxed_proc.process.poll() is not None:
245
+ break
246
+
247
+ # Get process info
248
+ try:
249
+ proc_info = psutil.Process(sandboxed_proc.process.pid)
250
+
251
+ # Check memory usage
252
+ memory_mb = proc_info.memory_info().rss / (1024 * 1024)
253
+ if memory_mb > sandboxed_proc.limits.max_memory_mb:
254
+ print(
255
+ f"Process {sandboxed_proc.process.pid} exceeded memory limit"
256
+ )
257
+ sandboxed_proc.terminate()
258
+ break
259
+
260
+ # Check CPU time
261
+ cpu_time = proc_info.cpu_times().user + proc_info.cpu_times().system
262
+ if cpu_time > sandboxed_proc.limits.max_cpu_seconds:
263
+ print(
264
+ f"Process {sandboxed_proc.process.pid} exceeded CPU time limit"
265
+ )
266
+ sandboxed_proc.terminate()
267
+ break
268
+
269
+ # Check total runtime
270
+ runtime = time.time() - sandboxed_proc.start_time
271
+ if runtime > sandboxed_proc.limits.timeout_seconds:
272
+ print(
273
+ f"Process {sandboxed_proc.process.pid} exceeded runtime limit"
274
+ )
275
+ sandboxed_proc.terminate()
276
+ break
277
+
278
+ except psutil.NoSuchProcess:
279
+ # Process already terminated
280
+ break
281
+
282
+ # Sleep before next check
283
+ time.sleep(1.0)
284
+
285
+ except Exception as e:
286
+ print(f"Error monitoring process: {e}")
287
+ break
288
+
289
+ def _filter_environment_variables(self, env: Dict[str, str]) -> Dict[str, str]:
290
+ """Filter environment variables to remove sensitive ones."""
291
+
292
+ # List of sensitive environment variable patterns
293
+ sensitive_patterns = [
294
+ "PASSWORD",
295
+ "SECRET",
296
+ "TOKEN",
297
+ "KEY",
298
+ "CREDENTIAL",
299
+ "AWS_",
300
+ "AZURE_",
301
+ "GCP_",
302
+ "GOOGLE_",
303
+ "SSH_",
304
+ "HOME",
305
+ "USER",
306
+ "USERNAME",
307
+ ]
308
+
309
+ filtered_env = {}
310
+ for key, value in env.items():
311
+ # Keep only safe environment variables
312
+ if not any(pattern in key.upper() for pattern in sensitive_patterns):
313
+ filtered_env[key] = value
314
+
315
+ # Add minimal required variables
316
+ filtered_env.update(
317
+ {
318
+ "PATH": "/usr/local/bin:/usr/bin:/bin",
319
+ "LANG": "en_US.UTF-8",
320
+ "LC_ALL": "en_US.UTF-8",
321
+ }
322
+ )
323
+
324
+ return filtered_env
325
+
326
+ @contextmanager
327
+ def sandbox_context(self, limits: Optional[ResourceLimits] = None):
328
+ """Context manager for sandbox operations."""
329
+
330
+ sandbox_dir = self.create_sandbox_environment(limits)
331
+ try:
332
+ yield sandbox_dir
333
+ finally:
334
+ if os.path.exists(sandbox_dir):
335
+ try:
336
+ shutil.rmtree(sandbox_dir)
337
+ except (OSError, PermissionError):
338
+ pass
339
+
340
+ def cleanup_all_sandboxes(self) -> None:
341
+ """Clean up all active sandboxes."""
342
+
343
+ for proc in list(self.active_processes.values()):
344
+ proc.cleanup()
345
+ self.active_processes.clear()
346
+
347
+ def get_active_process_count(self) -> int:
348
+ """Get number of active sandboxed processes."""
349
+ return len(self.active_processes)
350
+
351
+ def get_sandbox_info(self, process_id: int) -> Optional[Dict[str, Any]]:
352
+ """Get information about a sandboxed process."""
353
+
354
+ if process_id not in self.active_processes:
355
+ return None
356
+
357
+ proc = self.active_processes[process_id]
358
+ try:
359
+ proc_info = psutil.Process(process_id)
360
+ return {
361
+ "pid": process_id,
362
+ "is_running": proc.is_running(),
363
+ "start_time": proc.start_time,
364
+ "runtime": time.time() - proc.start_time,
365
+ "memory_mb": proc_info.memory_info().rss / (1024 * 1024),
366
+ "cpu_percent": proc_info.cpu_percent(),
367
+ "sandbox_dir": proc.temp_dir,
368
+ "limits": {
369
+ "max_memory_mb": proc.limits.max_memory_mb,
370
+ "max_cpu_seconds": proc.limits.max_cpu_seconds,
371
+ "timeout_seconds": proc.limits.timeout_seconds,
372
+ },
373
+ }
374
+ except psutil.NoSuchProcess:
375
+ return {
376
+ "pid": process_id,
377
+ "is_running": False,
378
+ "sandbox_dir": proc.temp_dir,
379
+ }