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,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)