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.
- omnimancer/__init__.py +42 -0
- omnimancer/__main__.py +9 -0
- omnimancer/cli/__init__.py +8 -0
- omnimancer/cli/agent_manager.py +826 -0
- omnimancer/cli/agent_wizard.py +944 -0
- omnimancer/cli/approval_formatter.py +734 -0
- omnimancer/cli/approval_integration.py +734 -0
- omnimancer/cli/approval_prompt.py +1286 -0
- omnimancer/cli/batch_approval_display.py +617 -0
- omnimancer/cli/batch_approval_filters.py +729 -0
- omnimancer/cli/batch_operation_monitor.py +775 -0
- omnimancer/cli/commands/performance_cmd.py +624 -0
- omnimancer/cli/commands.py +757 -0
- omnimancer/cli/completion_handler.py +281 -0
- omnimancer/cli/config_setup_wizard.py +783 -0
- omnimancer/cli/dynamic_commands.py +371 -0
- omnimancer/cli/handlers/__init__.py +18 -0
- omnimancer/cli/handlers/agent_handler.py +865 -0
- omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
- omnimancer/cli/handlers/custom_agent_handler.py +352 -0
- omnimancer/cli/handlers/permissions_handler.py +1609 -0
- omnimancer/cli/interface.py +6695 -0
- omnimancer/cli/performance_dashboard.py +612 -0
- omnimancer/core/__init__.py +34 -0
- omnimancer/core/agent/__init__.py +256 -0
- omnimancer/core/agent/agent_config.py +715 -0
- omnimancer/core/agent/agent_switcher.py +668 -0
- omnimancer/core/agent/approval_context.py +460 -0
- omnimancer/core/agent/approval_dialog.py +902 -0
- omnimancer/core/agent/approval_interface.py +628 -0
- omnimancer/core/agent/approval_manager.py +760 -0
- omnimancer/core/agent/cli_integration.py +470 -0
- omnimancer/core/agent/config.py +845 -0
- omnimancer/core/agent/custom_agent_integration.py +654 -0
- omnimancer/core/agent/diff_renderer.py +706 -0
- omnimancer/core/agent/file_content_display.py +503 -0
- omnimancer/core/agent/file_modification_workflow.py +658 -0
- omnimancer/core/agent/file_system_manager.py +1790 -0
- omnimancer/core/agent/input_handler.py +614 -0
- omnimancer/core/agent/metrics_collector.py +771 -0
- omnimancer/core/agent/optimization_engine.py +729 -0
- omnimancer/core/agent/performance_monitor.py +717 -0
- omnimancer/core/agent/persona.py +870 -0
- omnimancer/core/agent/persona_validator.py +672 -0
- omnimancer/core/agent/program_executor.py +748 -0
- omnimancer/core/agent/proposed_changes_integration.py +663 -0
- omnimancer/core/agent/read_before_write_errors.py +492 -0
- omnimancer/core/agent/read_before_write_ui.py +647 -0
- omnimancer/core/agent/rich_renderer.py +763 -0
- omnimancer/core/agent/status_config.py +238 -0
- omnimancer/core/agent/status_core.py +446 -0
- omnimancer/core/agent/status_integrated_engine.py +540 -0
- omnimancer/core/agent/status_manager.py +878 -0
- omnimancer/core/agent/status_ui_components.py +535 -0
- omnimancer/core/agent/template_applicator.py +548 -0
- omnimancer/core/agent/terminal_status_listener.py +394 -0
- omnimancer/core/agent/token_tracker.py +679 -0
- omnimancer/core/agent/types.py +60 -0
- omnimancer/core/agent/web_client.py +745 -0
- omnimancer/core/agent/workflow_orchestrator.py +826 -0
- omnimancer/core/agent_engine.py +1625 -0
- omnimancer/core/agent_mode_manager.py +706 -0
- omnimancer/core/agent_progress_ui.py +545 -0
- omnimancer/core/chat_manager.py +141 -0
- omnimancer/core/config_manager.py +2199 -0
- omnimancer/core/config_migration.py +772 -0
- omnimancer/core/config_provider.py +1974 -0
- omnimancer/core/config_security_validator.py +393 -0
- omnimancer/core/config_validator.py +768 -0
- omnimancer/core/conversation_manager.py +352 -0
- omnimancer/core/engine.py +787 -0
- omnimancer/core/enhanced_input.py +523 -0
- omnimancer/core/env_loader.py +83 -0
- omnimancer/core/fallback_manager.py +568 -0
- omnimancer/core/health_monitor.py +462 -0
- omnimancer/core/history_manager.py +462 -0
- omnimancer/core/mcp_integration_layer.py +878 -0
- omnimancer/core/models.py +1889 -0
- omnimancer/core/provider_capabilities.py +403 -0
- omnimancer/core/provider_initializer.py +450 -0
- omnimancer/core/provider_registry.py +432 -0
- omnimancer/core/security/__init__.py +16 -0
- omnimancer/core/security/approval_workflow.py +406 -0
- omnimancer/core/security/audit_logger.py +511 -0
- omnimancer/core/security/permission_controller.py +511 -0
- omnimancer/core/security/sandbox_manager.py +379 -0
- omnimancer/core/security/security_manager.py +530 -0
- omnimancer/core/setup_wizard.py +345 -0
- omnimancer/core/setup_wizard_core.py +449 -0
- omnimancer/core/setup_wizard_guide_generator.py +376 -0
- omnimancer/core/setup_wizard_provider_setup.py +454 -0
- omnimancer/core/setup_wizard_ui.py +317 -0
- omnimancer/core/setup_wizard_validation.py +371 -0
- omnimancer/core/signal_handler.py +174 -0
- omnimancer/mcp/__init__.py +11 -0
- omnimancer/mcp/client.py +725 -0
- omnimancer/mcp/manager.py +649 -0
- omnimancer/providers/__init__.py +24 -0
- omnimancer/providers/azure.py +608 -0
- omnimancer/providers/base.py +262 -0
- omnimancer/providers/bedrock.py +711 -0
- omnimancer/providers/claude.py +426 -0
- omnimancer/providers/claude_code.py +406 -0
- omnimancer/providers/cohere.py +412 -0
- omnimancer/providers/factory.py +988 -0
- omnimancer/providers/gemini.py +708 -0
- omnimancer/providers/mistral.py +554 -0
- omnimancer/providers/ollama.py +510 -0
- omnimancer/providers/openai.py +362 -0
- omnimancer/providers/openrouter.py +709 -0
- omnimancer/providers/perplexity.py +476 -0
- omnimancer/providers/vertex.py +617 -0
- omnimancer/providers/xai.py +484 -0
- omnimancer/ui/__init__.py +6 -0
- omnimancer/ui/cancellation_handler.py +235 -0
- omnimancer/ui/progress_indicator.py +318 -0
- omnimancer/utils/__init__.py +22 -0
- omnimancer/utils/error_handler.py +589 -0
- omnimancer/utils/errors.py +667 -0
- omnimancer/utils/retry.py +238 -0
- omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
- omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
- omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
- omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
- omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cancellation handler for Omnimancer operations.
|
|
3
|
+
|
|
4
|
+
This module provides cancellation support for long-running operations
|
|
5
|
+
with user-friendly feedback and graceful operation termination.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Callable, Optional
|
|
11
|
+
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# Global registry for active cancellation handler
|
|
17
|
+
_active_cancellation_handler: Optional["CancellationHandler"] = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_active_cancellation_handler() -> Optional["CancellationHandler"]:
|
|
21
|
+
"""Get the currently active cancellation handler."""
|
|
22
|
+
return _active_cancellation_handler
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def set_active_cancellation_handler(handler: Optional["CancellationHandler"]):
|
|
26
|
+
"""Set the currently active cancellation handler."""
|
|
27
|
+
global _active_cancellation_handler
|
|
28
|
+
_active_cancellation_handler = handler
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CancellationHandler:
|
|
32
|
+
"""
|
|
33
|
+
Handles operation cancellation with user feedback.
|
|
34
|
+
|
|
35
|
+
This class provides a simple but effective cancellation system
|
|
36
|
+
that integrates with the existing signal handling infrastructure.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, console: Optional[Console] = None):
|
|
40
|
+
"""
|
|
41
|
+
Initialize the cancellation handler.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
console: Rich console instance for user feedback
|
|
45
|
+
"""
|
|
46
|
+
self.console = console or Console()
|
|
47
|
+
self.active_operation: Optional[asyncio.Task] = None
|
|
48
|
+
self.is_listening = False
|
|
49
|
+
self.status_display = None
|
|
50
|
+
self.is_paused = False
|
|
51
|
+
logger.debug("CancellationHandler initialized")
|
|
52
|
+
|
|
53
|
+
async def start_cancellable_operation(
|
|
54
|
+
self,
|
|
55
|
+
operation: Callable,
|
|
56
|
+
status_message: str = "Processing...",
|
|
57
|
+
cancellation_message: str = "Operation cancelled by user",
|
|
58
|
+
signal_handler=None,
|
|
59
|
+
status_callback: Optional[Callable[[], str]] = None,
|
|
60
|
+
) -> Any:
|
|
61
|
+
"""
|
|
62
|
+
Start an operation with cancellation support.
|
|
63
|
+
|
|
64
|
+
This method wraps an operation to provide cancellation feedback
|
|
65
|
+
and integrates with the signal handler for Ctrl+C support.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
operation: Async function to execute
|
|
69
|
+
status_message: Message to show during operation
|
|
70
|
+
cancellation_message: Message to show when cancelled
|
|
71
|
+
status_callback: Optional callback that returns current status
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Result of the operation
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
asyncio.CancelledError: If operation was cancelled
|
|
78
|
+
"""
|
|
79
|
+
logger.debug(f"Starting cancellable operation: {status_message}")
|
|
80
|
+
|
|
81
|
+
# Register this handler as active
|
|
82
|
+
set_active_cancellation_handler(self)
|
|
83
|
+
|
|
84
|
+
# Create the operation task
|
|
85
|
+
self.active_operation = asyncio.create_task(operation())
|
|
86
|
+
self.is_listening = True
|
|
87
|
+
|
|
88
|
+
# Register with signal handler if provided
|
|
89
|
+
if signal_handler:
|
|
90
|
+
signal_handler.register_operation(self.active_operation)
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
# Show status with cancellation instructions
|
|
94
|
+
# Note: ESC key cancellation is not yet implemented, using Ctrl+C only
|
|
95
|
+
# If we have a status callback, use dynamic status
|
|
96
|
+
if status_callback:
|
|
97
|
+
|
|
98
|
+
async def update_status():
|
|
99
|
+
while not self.active_operation.done():
|
|
100
|
+
current_status = status_callback()
|
|
101
|
+
self.console.status(
|
|
102
|
+
f"[bold green]{current_status} (Press Ctrl+C to cancel)",
|
|
103
|
+
spinner="dots",
|
|
104
|
+
refresh=True,
|
|
105
|
+
)
|
|
106
|
+
await asyncio.sleep(0.1)
|
|
107
|
+
|
|
108
|
+
# Run status updates in parallel with operation
|
|
109
|
+
status_task = asyncio.create_task(update_status())
|
|
110
|
+
try:
|
|
111
|
+
result = await self.active_operation
|
|
112
|
+
finally:
|
|
113
|
+
status_task.cancel()
|
|
114
|
+
try:
|
|
115
|
+
await status_task
|
|
116
|
+
except asyncio.CancelledError:
|
|
117
|
+
pass
|
|
118
|
+
else:
|
|
119
|
+
# Use static status message
|
|
120
|
+
self.status_display = self.console.status(
|
|
121
|
+
f"[bold green]{status_message} (Press Ctrl+C to cancel)",
|
|
122
|
+
spinner="dots",
|
|
123
|
+
)
|
|
124
|
+
with self.status_display:
|
|
125
|
+
result = await self.active_operation
|
|
126
|
+
|
|
127
|
+
logger.debug("Cancellable operation completed successfully")
|
|
128
|
+
return result
|
|
129
|
+
|
|
130
|
+
except asyncio.CancelledError:
|
|
131
|
+
logger.info("Operation cancelled by user")
|
|
132
|
+
self.console.print(f"[yellow]⚠️ {cancellation_message}[/yellow]")
|
|
133
|
+
raise
|
|
134
|
+
finally:
|
|
135
|
+
self.is_listening = False
|
|
136
|
+
self.active_operation = None
|
|
137
|
+
self.status_display = None
|
|
138
|
+
self.is_paused = False
|
|
139
|
+
# Unregister this handler
|
|
140
|
+
set_active_cancellation_handler(None)
|
|
141
|
+
|
|
142
|
+
def is_operation_active(self) -> bool:
|
|
143
|
+
"""
|
|
144
|
+
Check if an operation is currently active.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
True if an operation is running
|
|
148
|
+
"""
|
|
149
|
+
return self.active_operation is not None and not self.active_operation.done()
|
|
150
|
+
|
|
151
|
+
def cancel_active_operation(self) -> bool:
|
|
152
|
+
"""
|
|
153
|
+
Cancel the currently active operation.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
True if an operation was cancelled, False otherwise
|
|
157
|
+
"""
|
|
158
|
+
if self.is_operation_active():
|
|
159
|
+
logger.info("Cancelling active operation")
|
|
160
|
+
self.active_operation.cancel()
|
|
161
|
+
return True
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
def pause_status_display(self):
|
|
165
|
+
"""
|
|
166
|
+
Pause the status display to allow for user interaction.
|
|
167
|
+
|
|
168
|
+
This stops the spinner temporarily while preserving the operation.
|
|
169
|
+
"""
|
|
170
|
+
if self.status_display and not self.is_paused:
|
|
171
|
+
self.is_paused = True
|
|
172
|
+
try:
|
|
173
|
+
self.status_display.stop()
|
|
174
|
+
except:
|
|
175
|
+
pass
|
|
176
|
+
logger.debug("Status display paused for user interaction")
|
|
177
|
+
|
|
178
|
+
def resume_status_display(self):
|
|
179
|
+
"""
|
|
180
|
+
Resume the status display after user interaction.
|
|
181
|
+
"""
|
|
182
|
+
if self.status_display and self.is_paused and self.is_operation_active():
|
|
183
|
+
self.is_paused = False
|
|
184
|
+
try:
|
|
185
|
+
self.status_display.start()
|
|
186
|
+
except:
|
|
187
|
+
pass
|
|
188
|
+
logger.debug("Status display resumed after user interaction")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class EnhancedStatusDisplay:
|
|
192
|
+
"""
|
|
193
|
+
Enhanced status display with better cancellation feedback.
|
|
194
|
+
|
|
195
|
+
Provides improved visual feedback for operations that can be cancelled.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def __init__(self, console: Console):
|
|
199
|
+
"""
|
|
200
|
+
Initialize the enhanced status display.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
console: Rich console instance
|
|
204
|
+
"""
|
|
205
|
+
self.console = console
|
|
206
|
+
|
|
207
|
+
def show_thinking_with_cancel(self, message: str = "Thinking") -> None:
|
|
208
|
+
"""
|
|
209
|
+
Show a thinking indicator with cancellation instructions.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
message: Base message to display
|
|
213
|
+
"""
|
|
214
|
+
enhanced_message = (
|
|
215
|
+
f"[bold green]{message}... (Press Ctrl+C to cancel)[/bold green]"
|
|
216
|
+
)
|
|
217
|
+
return self.console.status(enhanced_message, spinner="dots")
|
|
218
|
+
|
|
219
|
+
def show_cancellation_notice(self, message: str = "Operation cancelled") -> None:
|
|
220
|
+
"""
|
|
221
|
+
Show a cancellation notice to the user.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
message: Cancellation message to display
|
|
225
|
+
"""
|
|
226
|
+
self.console.print(f"[yellow]⚠️ {message}[/yellow]")
|
|
227
|
+
|
|
228
|
+
def show_completion_notice(self, message: str = "Operation completed") -> None:
|
|
229
|
+
"""
|
|
230
|
+
Show a completion notice to the user.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
message: Completion message to display
|
|
234
|
+
"""
|
|
235
|
+
self.console.print(f"[green]✅ {message}[/green]")
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Progress indicator system for Omnimancer.
|
|
3
|
+
|
|
4
|
+
This module provides real-time progress indicators showing current operations
|
|
5
|
+
and tool usage during AI interactions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.live import Live
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class OperationType(Enum):
|
|
21
|
+
"""Types of operations that can be tracked."""
|
|
22
|
+
|
|
23
|
+
THINKING = "thinking"
|
|
24
|
+
READ = "read"
|
|
25
|
+
WRITE = "write"
|
|
26
|
+
BASH = "bash"
|
|
27
|
+
RESEARCH = "research"
|
|
28
|
+
GREP = "grep"
|
|
29
|
+
EDIT = "edit"
|
|
30
|
+
VALIDATE = "validate"
|
|
31
|
+
ANALYZE = "analyze"
|
|
32
|
+
GENERATE = "generate"
|
|
33
|
+
NETWORK = "network"
|
|
34
|
+
OTHER = "other"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class OperationInfo:
|
|
39
|
+
"""Information about a current operation."""
|
|
40
|
+
|
|
41
|
+
operation_type: OperationType
|
|
42
|
+
description: str
|
|
43
|
+
start_time: float = field(default_factory=time.time)
|
|
44
|
+
progress: Optional[float] = None # 0.0 to 1.0
|
|
45
|
+
details: Optional[str] = None
|
|
46
|
+
status: str = "running" # "running", "completed", "failed"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ProgressIndicator:
|
|
50
|
+
"""
|
|
51
|
+
Real-time progress indicator for Omnimancer operations.
|
|
52
|
+
|
|
53
|
+
Shows current operations, tool usage, and progress information
|
|
54
|
+
in a live-updating display.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# Operation type icons and colors
|
|
58
|
+
OPERATION_ICONS = {
|
|
59
|
+
OperationType.THINKING: ("🤔", "yellow"),
|
|
60
|
+
OperationType.READ: ("📖", "blue"),
|
|
61
|
+
OperationType.WRITE: ("✍️", "green"),
|
|
62
|
+
OperationType.BASH: ("⚙️", "magenta"),
|
|
63
|
+
OperationType.RESEARCH: ("🔍", "cyan"),
|
|
64
|
+
OperationType.GREP: ("🔎", "blue"),
|
|
65
|
+
OperationType.EDIT: ("📝", "green"),
|
|
66
|
+
OperationType.VALIDATE: ("✅", "green"),
|
|
67
|
+
OperationType.ANALYZE: ("🔬", "purple"),
|
|
68
|
+
OperationType.GENERATE: ("🏗️", "orange"),
|
|
69
|
+
OperationType.NETWORK: ("🌐", "cyan"),
|
|
70
|
+
OperationType.OTHER: ("⚡", "white"),
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def __init__(self, console: Console):
|
|
74
|
+
"""
|
|
75
|
+
Initialize the progress indicator.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
console: Rich Console instance
|
|
79
|
+
"""
|
|
80
|
+
self.console = console
|
|
81
|
+
self.current_operations: Dict[str, OperationInfo] = {}
|
|
82
|
+
self.operation_history: List[OperationInfo] = []
|
|
83
|
+
self.live_display: Optional[Live] = None
|
|
84
|
+
self.display_task: Optional[asyncio.Task] = None
|
|
85
|
+
self.enabled = True
|
|
86
|
+
self.max_history = 5
|
|
87
|
+
|
|
88
|
+
def start_operation(
|
|
89
|
+
self,
|
|
90
|
+
operation_id: str,
|
|
91
|
+
operation_type: OperationType,
|
|
92
|
+
description: str,
|
|
93
|
+
details: Optional[str] = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Start tracking a new operation.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
operation_id: Unique identifier for the operation
|
|
100
|
+
operation_type: Type of operation
|
|
101
|
+
description: Human-readable description
|
|
102
|
+
details: Optional additional details
|
|
103
|
+
"""
|
|
104
|
+
if not self.enabled:
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
operation = OperationInfo(
|
|
108
|
+
operation_type=operation_type,
|
|
109
|
+
description=description,
|
|
110
|
+
details=details,
|
|
111
|
+
)
|
|
112
|
+
self.current_operations[operation_id] = operation
|
|
113
|
+
|
|
114
|
+
# Start live display if this is the first operation
|
|
115
|
+
if len(self.current_operations) == 1 and self.live_display is None:
|
|
116
|
+
self.start_live_display()
|
|
117
|
+
else:
|
|
118
|
+
self._update_display()
|
|
119
|
+
|
|
120
|
+
def update_operation(
|
|
121
|
+
self,
|
|
122
|
+
operation_id: str,
|
|
123
|
+
progress: Optional[float] = None,
|
|
124
|
+
details: Optional[str] = None,
|
|
125
|
+
status: Optional[str] = None,
|
|
126
|
+
) -> None:
|
|
127
|
+
"""
|
|
128
|
+
Update an existing operation.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
operation_id: Operation identifier
|
|
132
|
+
progress: Progress as float 0.0 to 1.0
|
|
133
|
+
details: Updated details
|
|
134
|
+
status: Updated status
|
|
135
|
+
"""
|
|
136
|
+
if not self.enabled or operation_id not in self.current_operations:
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
operation = self.current_operations[operation_id]
|
|
140
|
+
if progress is not None:
|
|
141
|
+
operation.progress = progress
|
|
142
|
+
if details is not None:
|
|
143
|
+
operation.details = details
|
|
144
|
+
if status is not None:
|
|
145
|
+
operation.status = status
|
|
146
|
+
|
|
147
|
+
self._update_display()
|
|
148
|
+
|
|
149
|
+
def complete_operation(self, operation_id: str, status: str = "completed") -> None:
|
|
150
|
+
"""
|
|
151
|
+
Complete an operation and move it to history.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
operation_id: Operation identifier
|
|
155
|
+
status: Final status ("completed" or "failed")
|
|
156
|
+
"""
|
|
157
|
+
if not self.enabled or operation_id not in self.current_operations:
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
operation = self.current_operations.pop(operation_id)
|
|
161
|
+
operation.status = status
|
|
162
|
+
|
|
163
|
+
# Add to history (keep only recent ones)
|
|
164
|
+
self.operation_history.append(operation)
|
|
165
|
+
if len(self.operation_history) > self.max_history:
|
|
166
|
+
self.operation_history.pop(0)
|
|
167
|
+
|
|
168
|
+
# Stop live display if no more operations
|
|
169
|
+
if len(self.current_operations) == 0:
|
|
170
|
+
self.stop_live_display()
|
|
171
|
+
else:
|
|
172
|
+
self._update_display()
|
|
173
|
+
|
|
174
|
+
def clear_all_operations(self) -> None:
|
|
175
|
+
"""Clear all current operations."""
|
|
176
|
+
if not self.enabled:
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
# Move all current operations to history as completed
|
|
180
|
+
for operation_id, operation in self.current_operations.items():
|
|
181
|
+
operation.status = "completed"
|
|
182
|
+
self.operation_history.append(operation)
|
|
183
|
+
|
|
184
|
+
self.current_operations.clear()
|
|
185
|
+
|
|
186
|
+
# Trim history
|
|
187
|
+
if len(self.operation_history) > self.max_history:
|
|
188
|
+
self.operation_history = self.operation_history[-self.max_history :]
|
|
189
|
+
|
|
190
|
+
# Always stop live display when clearing all operations
|
|
191
|
+
self.stop_live_display()
|
|
192
|
+
|
|
193
|
+
def start_live_display(self) -> None:
|
|
194
|
+
"""Start the live updating display."""
|
|
195
|
+
if not self.enabled or self.live_display is not None:
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
layout = self._create_layout()
|
|
199
|
+
self.live_display = Live(
|
|
200
|
+
layout,
|
|
201
|
+
console=self.console,
|
|
202
|
+
auto_refresh=False, # Disable auto-refresh to prevent runaway updates
|
|
203
|
+
refresh_per_second=2, # Limit refresh rate
|
|
204
|
+
)
|
|
205
|
+
self.live_display.start()
|
|
206
|
+
|
|
207
|
+
def stop_live_display(self) -> None:
|
|
208
|
+
"""Stop the live updating display."""
|
|
209
|
+
if self.live_display:
|
|
210
|
+
self.live_display.stop()
|
|
211
|
+
self.live_display = None
|
|
212
|
+
|
|
213
|
+
def _create_layout(self) -> Panel:
|
|
214
|
+
"""Create the display layout."""
|
|
215
|
+
# Create table for current operations
|
|
216
|
+
current_table = Table(show_header=False, box=None, padding=(0, 1))
|
|
217
|
+
current_table.add_column("", style="bold")
|
|
218
|
+
current_table.add_column("", style="dim")
|
|
219
|
+
current_table.add_column("", style="")
|
|
220
|
+
|
|
221
|
+
for operation_id, operation in self.current_operations.items():
|
|
222
|
+
icon, color = self.OPERATION_ICONS[operation.operation_type]
|
|
223
|
+
elapsed = time.time() - operation.start_time
|
|
224
|
+
|
|
225
|
+
# Format time
|
|
226
|
+
if elapsed < 60:
|
|
227
|
+
time_str = f"{elapsed:.1f}s"
|
|
228
|
+
else:
|
|
229
|
+
time_str = f"{elapsed/60:.1f}m"
|
|
230
|
+
|
|
231
|
+
# Progress indicator
|
|
232
|
+
if operation.progress is not None:
|
|
233
|
+
progress_str = f" ({operation.progress:.0%})"
|
|
234
|
+
else:
|
|
235
|
+
progress_str = ""
|
|
236
|
+
|
|
237
|
+
# Operation details
|
|
238
|
+
details_str = f" - {operation.details}" if operation.details else ""
|
|
239
|
+
|
|
240
|
+
current_table.add_row(
|
|
241
|
+
f"[{color}]{icon}[/{color}]",
|
|
242
|
+
f"[{color}]{operation.operation_type.value.title()}[/{color}]",
|
|
243
|
+
f"{operation.description}{progress_str}{details_str} [{time_str}]",
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return Panel(
|
|
247
|
+
current_table,
|
|
248
|
+
title="🤖 Omnimancer Progress",
|
|
249
|
+
border_style="blue",
|
|
250
|
+
padding=(0, 1),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def _update_display(self) -> None:
|
|
254
|
+
"""Update the live display."""
|
|
255
|
+
if self.live_display:
|
|
256
|
+
self.live_display.update(self._create_layout())
|
|
257
|
+
|
|
258
|
+
def enable(self) -> None:
|
|
259
|
+
"""Enable progress tracking."""
|
|
260
|
+
self.enabled = True
|
|
261
|
+
|
|
262
|
+
def disable(self) -> None:
|
|
263
|
+
"""Disable progress tracking."""
|
|
264
|
+
self.enabled = False
|
|
265
|
+
self.stop_live_display()
|
|
266
|
+
|
|
267
|
+
def show_simple_status(self, message: str) -> None:
|
|
268
|
+
"""Show a simple status message without full progress tracking."""
|
|
269
|
+
if not self.enabled:
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
self.console.print(f"🤖 {message}", style="blue")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# Global progress indicator instance
|
|
276
|
+
_global_progress_indicator: Optional[ProgressIndicator] = None
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def get_progress_indicator() -> Optional[ProgressIndicator]:
|
|
280
|
+
"""Get the global progress indicator instance."""
|
|
281
|
+
return _global_progress_indicator
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def set_progress_indicator(indicator: ProgressIndicator) -> None:
|
|
285
|
+
"""Set the global progress indicator instance."""
|
|
286
|
+
global _global_progress_indicator
|
|
287
|
+
_global_progress_indicator = indicator
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def start_operation(
|
|
291
|
+
operation_id: str,
|
|
292
|
+
operation_type: OperationType,
|
|
293
|
+
description: str,
|
|
294
|
+
details: Optional[str] = None,
|
|
295
|
+
) -> None:
|
|
296
|
+
"""Global function to start tracking an operation."""
|
|
297
|
+
indicator = get_progress_indicator()
|
|
298
|
+
if indicator:
|
|
299
|
+
indicator.start_operation(operation_id, operation_type, description, details)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def update_operation(
|
|
303
|
+
operation_id: str,
|
|
304
|
+
progress: Optional[float] = None,
|
|
305
|
+
details: Optional[str] = None,
|
|
306
|
+
status: Optional[str] = None,
|
|
307
|
+
) -> None:
|
|
308
|
+
"""Global function to update an operation."""
|
|
309
|
+
indicator = get_progress_indicator()
|
|
310
|
+
if indicator:
|
|
311
|
+
indicator.update_operation(operation_id, progress, details, status)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def complete_operation(operation_id: str, status: str = "completed") -> None:
|
|
315
|
+
"""Global function to complete an operation."""
|
|
316
|
+
indicator = get_progress_indicator()
|
|
317
|
+
if indicator:
|
|
318
|
+
indicator.complete_operation(operation_id, status)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities module for Omnimancer.
|
|
3
|
+
|
|
4
|
+
This module contains utility functions and classes for error handling,
|
|
5
|
+
retry logic, encryption, and other common functionality.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .errors import (
|
|
9
|
+
AuthenticationError,
|
|
10
|
+
ConfigurationError,
|
|
11
|
+
OmnimancerError,
|
|
12
|
+
ProviderError,
|
|
13
|
+
RateLimitError,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"OmnimancerError",
|
|
18
|
+
"ConfigurationError",
|
|
19
|
+
"ProviderError",
|
|
20
|
+
"AuthenticationError",
|
|
21
|
+
"RateLimitError",
|
|
22
|
+
]
|