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,503 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified File Content Display Module for Omnimancer.
|
|
3
|
+
|
|
4
|
+
This module integrates existing UI components to provide a cohesive interface
|
|
5
|
+
for displaying file content and modifications according to the designed interaction flow.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.syntax import Syntax
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
from rich.text import Text
|
|
19
|
+
|
|
20
|
+
from omnimancer.cli.approval_formatter import CLIApprovalFormatter
|
|
21
|
+
from omnimancer.cli.approval_prompt import CLIApprovalPrompt
|
|
22
|
+
|
|
23
|
+
from .approval_dialog import ApprovalDialog, DialogOptions
|
|
24
|
+
from .diff_renderer import (
|
|
25
|
+
DiffType,
|
|
26
|
+
EnhancedDiffRenderer,
|
|
27
|
+
FileChange,
|
|
28
|
+
FileChangeType,
|
|
29
|
+
)
|
|
30
|
+
from .rich_renderer import create_renderer
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DisplayMode(Enum):
|
|
36
|
+
"""Display modes for file content."""
|
|
37
|
+
|
|
38
|
+
FULL_CONTENT = "full"
|
|
39
|
+
PREVIEW = "preview"
|
|
40
|
+
DIFF = "diff"
|
|
41
|
+
SUMMARY = "summary"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class FileDisplayConfig:
|
|
46
|
+
"""Configuration for file content display."""
|
|
47
|
+
|
|
48
|
+
display_mode: DisplayMode = DisplayMode.FULL_CONTENT
|
|
49
|
+
syntax_highlighting: bool = True
|
|
50
|
+
show_line_numbers: bool = True
|
|
51
|
+
max_preview_lines: int = 100
|
|
52
|
+
max_content_size: int = 1024 * 1024 # 1MB
|
|
53
|
+
truncate_large_files: bool = True
|
|
54
|
+
diff_type: DiffType = DiffType.UNIFIED
|
|
55
|
+
show_risk_assessment: bool = True
|
|
56
|
+
show_file_stats: bool = True
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class UnifiedFileContentDisplay:
|
|
60
|
+
"""
|
|
61
|
+
Unified interface for displaying file content and modifications.
|
|
62
|
+
|
|
63
|
+
Integrates existing UI components to provide consistent file content
|
|
64
|
+
display across all operations (create, modify, delete, batch).
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
console: Optional[Console] = None,
|
|
70
|
+
config: Optional[FileDisplayConfig] = None,
|
|
71
|
+
):
|
|
72
|
+
"""
|
|
73
|
+
Initialize the unified file content display.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
console: Rich console for output
|
|
77
|
+
config: Display configuration
|
|
78
|
+
"""
|
|
79
|
+
self.console = console or Console()
|
|
80
|
+
self.config = config or FileDisplayConfig()
|
|
81
|
+
|
|
82
|
+
# Initialize component instances
|
|
83
|
+
self.diff_renderer = EnhancedDiffRenderer(console=self.console)
|
|
84
|
+
self.rich_renderer = create_renderer()
|
|
85
|
+
self.unified_approval_ui = CLIApprovalPrompt(console=self.console)
|
|
86
|
+
self.formatter = CLIApprovalFormatter(console=self.console)
|
|
87
|
+
|
|
88
|
+
# Dialog options for approval flow
|
|
89
|
+
self.dialog_options = DialogOptions(
|
|
90
|
+
show_diff=True,
|
|
91
|
+
show_risk_assessment=self.config.show_risk_assessment,
|
|
92
|
+
show_file_tree=True,
|
|
93
|
+
show_operation_details=True,
|
|
94
|
+
diff_type=self.config.diff_type,
|
|
95
|
+
syntax_highlighting=self.config.syntax_highlighting,
|
|
96
|
+
show_line_numbers=self.config.show_line_numbers,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
self.approval_dialog = ApprovalDialog(
|
|
100
|
+
renderer=self.rich_renderer,
|
|
101
|
+
diff_renderer=self.diff_renderer,
|
|
102
|
+
console=self.console,
|
|
103
|
+
options=self.dialog_options,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def display_file_creation(
|
|
107
|
+
self,
|
|
108
|
+
file_path: str,
|
|
109
|
+
content: str,
|
|
110
|
+
operation_context: Optional[Dict[str, Any]] = None,
|
|
111
|
+
) -> Dict[str, Any]:
|
|
112
|
+
"""
|
|
113
|
+
Display interface for new file creation.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
file_path: Path where file will be created
|
|
117
|
+
content: Content to be written to the file
|
|
118
|
+
operation_context: Additional context about the operation
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Display result with user decision if interactive
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
# Prepare review data for existing UI
|
|
125
|
+
review_data = {
|
|
126
|
+
"file_path": file_path,
|
|
127
|
+
"file_exists": False,
|
|
128
|
+
"current_content": None,
|
|
129
|
+
"new_content": content,
|
|
130
|
+
"operation": "create",
|
|
131
|
+
"encoding": "utf-8",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Add operation context if provided
|
|
135
|
+
if operation_context:
|
|
136
|
+
review_data.update(operation_context)
|
|
137
|
+
|
|
138
|
+
# Determine if content should be truncated
|
|
139
|
+
if (
|
|
140
|
+
self.config.truncate_large_files
|
|
141
|
+
and len(content) > self.config.max_content_size
|
|
142
|
+
):
|
|
143
|
+
review_data["truncated"] = True
|
|
144
|
+
review_data["original_size"] = len(content)
|
|
145
|
+
review_data["new_content"] = content[: self.config.max_content_size]
|
|
146
|
+
|
|
147
|
+
# Use unified approval UI for interactive review
|
|
148
|
+
if operation_context and operation_context.get("interactive", True):
|
|
149
|
+
current_model = operation_context.get("current_model", "Omnimancer AI")
|
|
150
|
+
return await self.unified_approval_ui.prompt_for_file_modification_approval(
|
|
151
|
+
review_data, current_model
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Non-interactive display
|
|
155
|
+
self._display_file_creation_summary(file_path, content)
|
|
156
|
+
return {"displayed": True, "interactive": False}
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.error(f"Error displaying file creation: {e}")
|
|
160
|
+
return {"error": str(e), "displayed": False}
|
|
161
|
+
|
|
162
|
+
async def display_file_modification(
|
|
163
|
+
self,
|
|
164
|
+
file_path: str,
|
|
165
|
+
current_content: str,
|
|
166
|
+
new_content: str,
|
|
167
|
+
operation_context: Optional[Dict[str, Any]] = None,
|
|
168
|
+
) -> Dict[str, Any]:
|
|
169
|
+
"""
|
|
170
|
+
Display interface for file modification with diff.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
file_path: Path to the file being modified
|
|
174
|
+
current_content: Current file content
|
|
175
|
+
new_content: Proposed new content
|
|
176
|
+
operation_context: Additional context about the operation
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Display result with user decision if interactive
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
# Create file change object for diff renderer
|
|
183
|
+
file_change = FileChange(
|
|
184
|
+
file_path=file_path,
|
|
185
|
+
change_type=FileChangeType.MODIFIED,
|
|
186
|
+
old_content=current_content,
|
|
187
|
+
new_content=new_content,
|
|
188
|
+
language=self._detect_language(file_path),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Generate diff
|
|
192
|
+
diff_output = self.diff_renderer.render_unified_diff(file_change)
|
|
193
|
+
|
|
194
|
+
# Prepare review data
|
|
195
|
+
review_data = {
|
|
196
|
+
"file_path": file_path,
|
|
197
|
+
"file_exists": True,
|
|
198
|
+
"current_content": current_content,
|
|
199
|
+
"new_content": new_content,
|
|
200
|
+
"diff": diff_output,
|
|
201
|
+
"operation": "modify",
|
|
202
|
+
"encoding": "utf-8",
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if operation_context:
|
|
206
|
+
review_data.update(operation_context)
|
|
207
|
+
|
|
208
|
+
# Use unified approval UI for interactive review
|
|
209
|
+
if operation_context and operation_context.get("interactive", True):
|
|
210
|
+
current_model = operation_context.get("current_model", "Omnimancer AI")
|
|
211
|
+
return await self.unified_approval_ui.prompt_for_file_modification_approval(
|
|
212
|
+
review_data, current_model
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Non-interactive display
|
|
216
|
+
self._display_file_modification_summary(file_path, file_change)
|
|
217
|
+
return {"displayed": True, "interactive": False}
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logger.error(f"Error displaying file modification: {e}")
|
|
221
|
+
return {"error": str(e), "displayed": False}
|
|
222
|
+
|
|
223
|
+
async def display_file_deletion(
|
|
224
|
+
self,
|
|
225
|
+
file_path: str,
|
|
226
|
+
content: str,
|
|
227
|
+
operation_context: Optional[Dict[str, Any]] = None,
|
|
228
|
+
) -> Dict[str, Any]:
|
|
229
|
+
"""
|
|
230
|
+
Display interface for file deletion.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
file_path: Path to the file being deleted
|
|
234
|
+
content: Current content of the file
|
|
235
|
+
operation_context: Additional context about the operation
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Display result with user decision if interactive
|
|
239
|
+
"""
|
|
240
|
+
try:
|
|
241
|
+
# Create warning panel for deletion
|
|
242
|
+
warning_text = Text()
|
|
243
|
+
warning_text.append("⚠️ FILE DELETION WARNING\n", style="bold red")
|
|
244
|
+
warning_text.append(f"File: {file_path}\n", style="yellow")
|
|
245
|
+
warning_text.append(f"Size: {len(content)} bytes\n", style="dim")
|
|
246
|
+
warning_text.append("This operation cannot be undone!", style="bold red")
|
|
247
|
+
|
|
248
|
+
warning_panel = Panel(
|
|
249
|
+
warning_text,
|
|
250
|
+
title="[bold red]Confirm Deletion[/bold red]",
|
|
251
|
+
border_style="red",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
self.console.print(warning_panel)
|
|
255
|
+
|
|
256
|
+
# Show file content preview
|
|
257
|
+
if self.config.display_mode == DisplayMode.PREVIEW:
|
|
258
|
+
preview_lines = min(
|
|
259
|
+
self.config.max_preview_lines, len(content.splitlines())
|
|
260
|
+
)
|
|
261
|
+
preview_content = "\n".join(content.splitlines()[:preview_lines])
|
|
262
|
+
if preview_lines < len(content.splitlines()):
|
|
263
|
+
preview_content += f"\n... ({len(content.splitlines()) - preview_lines} more lines)"
|
|
264
|
+
else:
|
|
265
|
+
preview_content = content
|
|
266
|
+
|
|
267
|
+
# Display content with syntax highlighting
|
|
268
|
+
language = self._detect_language(file_path)
|
|
269
|
+
syntax = Syntax(
|
|
270
|
+
preview_content,
|
|
271
|
+
language,
|
|
272
|
+
theme="monokai",
|
|
273
|
+
line_numbers=self.config.show_line_numbers,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
content_panel = Panel(
|
|
277
|
+
syntax,
|
|
278
|
+
title=f"[red]Content to be deleted[/red]",
|
|
279
|
+
border_style="red",
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
self.console.print(content_panel)
|
|
283
|
+
|
|
284
|
+
# Interactive confirmation if needed
|
|
285
|
+
if operation_context and operation_context.get("interactive", True):
|
|
286
|
+
from rich.prompt import Confirm
|
|
287
|
+
|
|
288
|
+
confirmed = Confirm.ask(
|
|
289
|
+
"Are you sure you want to delete this file?", default=False
|
|
290
|
+
)
|
|
291
|
+
return {"approved": confirmed, "interactive": True}
|
|
292
|
+
|
|
293
|
+
return {"displayed": True, "interactive": False}
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
logger.error(f"Error displaying file deletion: {e}")
|
|
297
|
+
return {"error": str(e), "displayed": False}
|
|
298
|
+
|
|
299
|
+
def display_batch_operations(
|
|
300
|
+
self,
|
|
301
|
+
operations: List[Dict[str, Any]],
|
|
302
|
+
operation_context: Optional[Dict[str, Any]] = None,
|
|
303
|
+
) -> Dict[str, Any]:
|
|
304
|
+
"""
|
|
305
|
+
Display interface for batch file operations.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
operations: List of file operations to display
|
|
309
|
+
operation_context: Additional context about the operations
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Display result with batch summary
|
|
313
|
+
"""
|
|
314
|
+
try:
|
|
315
|
+
# Create summary table
|
|
316
|
+
summary_table = Table(title="Batch File Operations Summary")
|
|
317
|
+
summary_table.add_column("Type", style="cyan")
|
|
318
|
+
summary_table.add_column("Count", style="magenta")
|
|
319
|
+
summary_table.add_column("Risk Level", style="yellow")
|
|
320
|
+
|
|
321
|
+
# Count operations by type
|
|
322
|
+
op_counts = {}
|
|
323
|
+
risk_levels = {}
|
|
324
|
+
|
|
325
|
+
for op in operations:
|
|
326
|
+
op_type = op.get("type", "unknown")
|
|
327
|
+
op_counts[op_type] = op_counts.get(op_type, 0) + 1
|
|
328
|
+
|
|
329
|
+
risk = op.get("risk_level", "low")
|
|
330
|
+
if risk not in risk_levels:
|
|
331
|
+
risk_levels[risk] = []
|
|
332
|
+
risk_levels[risk].append(op)
|
|
333
|
+
|
|
334
|
+
# Add rows to summary table
|
|
335
|
+
for op_type, count in op_counts.items():
|
|
336
|
+
risk_summary = self._get_risk_summary(operations, op_type)
|
|
337
|
+
summary_table.add_row(op_type.title(), str(count), risk_summary)
|
|
338
|
+
|
|
339
|
+
self.console.print(summary_table)
|
|
340
|
+
|
|
341
|
+
# Show detailed list if not too many operations
|
|
342
|
+
if len(operations) <= 10:
|
|
343
|
+
self._display_operation_list(operations)
|
|
344
|
+
else:
|
|
345
|
+
self.console.print(
|
|
346
|
+
f"\n[dim]Showing first 10 of {len(operations)} operations...[/dim]"
|
|
347
|
+
)
|
|
348
|
+
self._display_operation_list(operations[:10])
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
"displayed": True,
|
|
352
|
+
"operation_count": len(operations),
|
|
353
|
+
"types": list(op_counts.keys()),
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
except Exception as e:
|
|
357
|
+
logger.error(f"Error displaying batch operations: {e}")
|
|
358
|
+
return {"error": str(e), "displayed": False}
|
|
359
|
+
|
|
360
|
+
def _display_file_creation_summary(self, file_path: str, content: str):
|
|
361
|
+
"""Display non-interactive summary of file creation."""
|
|
362
|
+
info_text = Text()
|
|
363
|
+
info_text.append("📄 Creating new file\n", style="bold green")
|
|
364
|
+
info_text.append(f"Path: {file_path}\n", style="cyan")
|
|
365
|
+
info_text.append(f"Size: {len(content)} bytes\n", style="dim")
|
|
366
|
+
info_text.append(f"Lines: {len(content.splitlines())}\n", style="dim")
|
|
367
|
+
|
|
368
|
+
panel = Panel(info_text, title="File Creation", border_style="green")
|
|
369
|
+
self.console.print(panel)
|
|
370
|
+
|
|
371
|
+
def _display_file_modification_summary(
|
|
372
|
+
self, file_path: str, file_change: FileChange
|
|
373
|
+
):
|
|
374
|
+
"""Display non-interactive summary of file modification."""
|
|
375
|
+
info_text = Text()
|
|
376
|
+
info_text.append("✏️ Modifying file\n", style="bold yellow")
|
|
377
|
+
info_text.append(f"Path: {file_path}\n", style="cyan")
|
|
378
|
+
|
|
379
|
+
if file_change.lines_added or file_change.lines_removed:
|
|
380
|
+
info_text.append(
|
|
381
|
+
f"Lines added: +{file_change.lines_added}\n", style="green"
|
|
382
|
+
)
|
|
383
|
+
info_text.append(
|
|
384
|
+
f"Lines removed: -{file_change.lines_removed}\n", style="red"
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
panel = Panel(info_text, title="File Modification", border_style="yellow")
|
|
388
|
+
self.console.print(panel)
|
|
389
|
+
|
|
390
|
+
def _display_operation_list(self, operations: List[Dict[str, Any]]):
|
|
391
|
+
"""Display a list of operations."""
|
|
392
|
+
for i, op in enumerate(operations, 1):
|
|
393
|
+
op_type = op.get("type", "unknown")
|
|
394
|
+
file_path = op.get("file_path", "N/A")
|
|
395
|
+
risk = op.get("risk_level", "low")
|
|
396
|
+
|
|
397
|
+
# Choose icon based on operation type
|
|
398
|
+
icon = self._get_operation_icon(op_type)
|
|
399
|
+
risk_color = self._get_risk_color(risk)
|
|
400
|
+
|
|
401
|
+
self.console.print(
|
|
402
|
+
f"{i}. {icon} [{risk_color}]{op_type.title()}[/{risk_color}]: {file_path}"
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
def _detect_language(self, file_path: str) -> str:
|
|
406
|
+
"""Detect programming language from file extension."""
|
|
407
|
+
extension_map = {
|
|
408
|
+
".py": "python",
|
|
409
|
+
".js": "javascript",
|
|
410
|
+
".ts": "typescript",
|
|
411
|
+
".java": "java",
|
|
412
|
+
".cpp": "cpp",
|
|
413
|
+
".c": "c",
|
|
414
|
+
".cs": "csharp",
|
|
415
|
+
".rb": "ruby",
|
|
416
|
+
".go": "go",
|
|
417
|
+
".rs": "rust",
|
|
418
|
+
".php": "php",
|
|
419
|
+
".swift": "swift",
|
|
420
|
+
".kt": "kotlin",
|
|
421
|
+
".scala": "scala",
|
|
422
|
+
".r": "r",
|
|
423
|
+
".m": "matlab",
|
|
424
|
+
".jl": "julia",
|
|
425
|
+
".sh": "bash",
|
|
426
|
+
".ps1": "powershell",
|
|
427
|
+
".yaml": "yaml",
|
|
428
|
+
".yml": "yaml",
|
|
429
|
+
".json": "json",
|
|
430
|
+
".xml": "xml",
|
|
431
|
+
".html": "html",
|
|
432
|
+
".css": "css",
|
|
433
|
+
".scss": "scss",
|
|
434
|
+
".less": "less",
|
|
435
|
+
".sql": "sql",
|
|
436
|
+
".md": "markdown",
|
|
437
|
+
".rst": "rst",
|
|
438
|
+
".tex": "latex",
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
path = Path(file_path)
|
|
442
|
+
extension = path.suffix.lower()
|
|
443
|
+
return extension_map.get(extension, "text")
|
|
444
|
+
|
|
445
|
+
def _get_operation_icon(self, op_type: str) -> str:
|
|
446
|
+
"""Get icon for operation type."""
|
|
447
|
+
icons = {
|
|
448
|
+
"create": "📄",
|
|
449
|
+
"modify": "✏️",
|
|
450
|
+
"delete": "🗑️",
|
|
451
|
+
"move": "📦",
|
|
452
|
+
"copy": "📋",
|
|
453
|
+
"unknown": "❓",
|
|
454
|
+
}
|
|
455
|
+
return icons.get(op_type.lower(), "❓")
|
|
456
|
+
|
|
457
|
+
def _get_risk_color(self, risk_level: str) -> str:
|
|
458
|
+
"""Get color for risk level."""
|
|
459
|
+
colors = {
|
|
460
|
+
"low": "green",
|
|
461
|
+
"medium": "yellow",
|
|
462
|
+
"high": "orange1",
|
|
463
|
+
"critical": "red",
|
|
464
|
+
}
|
|
465
|
+
return colors.get(risk_level.lower(), "white")
|
|
466
|
+
|
|
467
|
+
def _get_risk_summary(self, operations: List[Dict[str, Any]], op_type: str) -> str:
|
|
468
|
+
"""Get risk summary for operations of a specific type."""
|
|
469
|
+
type_ops = [op for op in operations if op.get("type") == op_type]
|
|
470
|
+
if not type_ops:
|
|
471
|
+
return "N/A"
|
|
472
|
+
|
|
473
|
+
risk_counts = {}
|
|
474
|
+
for op in type_ops:
|
|
475
|
+
risk = op.get("risk_level", "low")
|
|
476
|
+
risk_counts[risk] = risk_counts.get(risk, 0) + 1
|
|
477
|
+
|
|
478
|
+
# Format risk summary
|
|
479
|
+
summary_parts = []
|
|
480
|
+
for risk in ["critical", "high", "medium", "low"]:
|
|
481
|
+
if risk in risk_counts:
|
|
482
|
+
color = self._get_risk_color(risk)
|
|
483
|
+
summary_parts.append(f"[{color}]{risk}: {risk_counts[risk]}[/{color}]")
|
|
484
|
+
|
|
485
|
+
return ", ".join(summary_parts) if summary_parts else "Unknown"
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# Factory function for easy instantiation
|
|
489
|
+
def create_file_content_display(
|
|
490
|
+
console: Optional[Console] = None,
|
|
491
|
+
config: Optional[FileDisplayConfig] = None,
|
|
492
|
+
) -> UnifiedFileContentDisplay:
|
|
493
|
+
"""
|
|
494
|
+
Create a unified file content display instance.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
console: Optional Rich console
|
|
498
|
+
config: Optional display configuration
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
UnifiedFileContentDisplay instance
|
|
502
|
+
"""
|
|
503
|
+
return UnifiedFileContentDisplay(console=console, config=config)
|