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,617 @@
1
+ """
2
+ Batch Approval Display Components for Omnimancer CLI.
3
+
4
+ This module provides Rich-based UI components for displaying batch approval
5
+ information including action lists, summaries, and risk assessments.
6
+ """
7
+
8
+ import logging
9
+ from dataclasses import dataclass
10
+ from datetime import datetime
11
+ from enum import Enum
12
+ from typing import Dict, List, Optional
13
+
14
+ from rich.console import Console, Group
15
+ from rich.panel import Panel
16
+ from rich.progress import BarColumn, Progress, TextColumn, TimeElapsedColumn
17
+ from rich.table import Table
18
+ from rich.text import Text
19
+
20
+ from ..core.agent.approval_manager import BatchApprovalRequest, ChangePreview
21
+ from ..core.agent.types import Operation, OperationType
22
+ from ..core.security.approval_workflow import ApprovalStatus
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class DisplayMode(Enum):
28
+ """Display modes for batch approval interface."""
29
+
30
+ COMPACT = "compact"
31
+ DETAILED = "detailed"
32
+ SUMMARY_ONLY = "summary"
33
+
34
+
35
+ @dataclass
36
+ class BatchDisplayConfig:
37
+ """Configuration for batch approval display."""
38
+
39
+ mode: DisplayMode = DisplayMode.DETAILED
40
+ show_risk_colors: bool = True
41
+ show_line_numbers: bool = True
42
+ max_preview_length: int = 100
43
+ items_per_page: int = 10
44
+ show_timestamps: bool = True
45
+ compact_operation_names: bool = True
46
+
47
+
48
+ class BatchApprovalPanel:
49
+ """
50
+ Rich-based UI components for displaying batch approval information.
51
+
52
+ Provides structured display of batch approval requests including
53
+ action lists, summaries, risk assessments, and interactive elements.
54
+ """
55
+
56
+ def __init__(
57
+ self,
58
+ console: Optional[Console] = None,
59
+ config: Optional[BatchDisplayConfig] = None,
60
+ ):
61
+ """
62
+ Initialize batch approval panel.
63
+
64
+ Args:
65
+ console: Rich console for output
66
+ config: Display configuration settings
67
+ """
68
+ self.console = console or Console()
69
+ self.config = config or BatchDisplayConfig()
70
+
71
+ def render_batch_overview(self, batch_request: BatchApprovalRequest) -> Panel:
72
+ """
73
+ Render overview panel for batch approval request.
74
+
75
+ Args:
76
+ batch_request: Batch request to display
77
+
78
+ Returns:
79
+ Rich Panel containing batch overview
80
+ """
81
+ # Calculate batch statistics
82
+ total_operations = len(batch_request.operations)
83
+ approved_count = len(batch_request.approved_operations)
84
+ pending_count = total_operations - approved_count
85
+
86
+ # Risk level distribution
87
+ risk_counts = self._calculate_risk_distribution(batch_request.previews)
88
+
89
+ # Create overview table
90
+ overview_table = Table.grid(padding=1)
91
+ overview_table.add_column("Label", style="bold cyan")
92
+ overview_table.add_column("Value", style="bright_white")
93
+
94
+ # Basic info
95
+ overview_table.add_row("Batch ID:", batch_request.id[:8] + "...")
96
+ overview_table.add_row(
97
+ "Created:", batch_request.created_at.strftime("%Y-%m-%d %H:%M:%S")
98
+ )
99
+ overview_table.add_row("Status:", self._format_status(batch_request.status))
100
+
101
+ if batch_request.expires_at:
102
+ time_remaining = batch_request.expires_at - datetime.now()
103
+ if time_remaining.total_seconds() > 0:
104
+ overview_table.add_row(
105
+ "Expires:", f"{time_remaining.seconds // 60} minutes"
106
+ )
107
+ else:
108
+ overview_table.add_row("Expires:", "[red]EXPIRED[/red]")
109
+
110
+ # Operation counts
111
+ overview_table.add_row("Total Operations:", str(total_operations))
112
+ if approved_count > 0:
113
+ overview_table.add_row("Approved:", f"[green]{approved_count}[/green]")
114
+ if pending_count > 0:
115
+ overview_table.add_row("Pending:", f"[yellow]{pending_count}[/yellow]")
116
+
117
+ # Risk distribution
118
+ if risk_counts:
119
+ overview_table.add_row("", "") # Separator
120
+ overview_table.add_row("Risk Distribution:", "")
121
+ for risk_level, count in risk_counts.items():
122
+ color = self._get_risk_color(risk_level)
123
+ overview_table.add_row(
124
+ f" {risk_level.title()}:", f"[{color}]{count}[/{color}]"
125
+ )
126
+
127
+ return Panel(
128
+ overview_table,
129
+ title="[bold]Batch Approval Overview[/bold]",
130
+ title_align="left",
131
+ border_style="blue",
132
+ )
133
+
134
+ def render_operations_table(
135
+ self,
136
+ batch_request: BatchApprovalRequest,
137
+ page: int = 0,
138
+ show_approved: bool = True,
139
+ ) -> Panel:
140
+ """
141
+ Render table of operations in the batch.
142
+
143
+ Args:
144
+ batch_request: Batch request to display
145
+ page: Page number for pagination
146
+ show_approved: Whether to show already approved operations
147
+
148
+ Returns:
149
+ Rich Panel containing operations table
150
+ """
151
+ operations = batch_request.operations
152
+ previews = batch_request.previews
153
+
154
+ # Filter operations if needed
155
+ if not show_approved:
156
+ filtered_ops = []
157
+ filtered_previews = []
158
+ for i, (op, preview) in enumerate(zip(operations, previews)):
159
+ if i not in batch_request.approved_operations:
160
+ filtered_ops.append(op)
161
+ filtered_previews.append(preview)
162
+ operations = filtered_ops
163
+ previews = filtered_previews
164
+
165
+ # Pagination
166
+ start_idx = page * self.config.items_per_page
167
+ end_idx = min(start_idx + self.config.items_per_page, len(operations))
168
+
169
+ if start_idx >= len(operations):
170
+ return Panel(
171
+ "[yellow]No operations to display on this page.[/yellow]",
172
+ title="Operations",
173
+ border_style="yellow",
174
+ )
175
+
176
+ # Create operations table
177
+ table = Table(show_header=True, header_style="bold magenta")
178
+
179
+ if self.config.show_line_numbers:
180
+ table.add_column("#", style="dim", width=4)
181
+
182
+ table.add_column("Type", style="cyan", width=15)
183
+ table.add_column("Target", style="bright_white", width=30)
184
+ table.add_column("Risk", width=8, justify="center")
185
+ table.add_column("Status", width=10, justify="center")
186
+
187
+ if self.config.mode == DisplayMode.DETAILED:
188
+ table.add_column("Description", style="dim", width=40)
189
+
190
+ # Add rows
191
+ for i in range(start_idx, end_idx):
192
+ operation = operations[i]
193
+ preview = previews[i] if i < len(previews) else None
194
+
195
+ # Determine status
196
+ original_idx = (
197
+ i if show_approved else self._get_original_index(batch_request, i)
198
+ )
199
+ is_approved = original_idx in batch_request.approved_operations
200
+ status = (
201
+ "[green]✓ Approved[/green]"
202
+ if is_approved
203
+ else "[yellow]○ Pending[/yellow]"
204
+ )
205
+
206
+ # Format operation type
207
+ op_type = self._format_operation_type(operation.type)
208
+
209
+ # Format target
210
+ target = self._format_target(operation)
211
+
212
+ # Risk assessment
213
+ risk_display = (
214
+ self._format_risk_assessment(preview) if preview else "Unknown"
215
+ )
216
+
217
+ # Build row
218
+ row = []
219
+ if self.config.show_line_numbers:
220
+ row.append(str(i + 1))
221
+
222
+ row.extend([op_type, target, risk_display, status])
223
+
224
+ if self.config.mode == DisplayMode.DETAILED:
225
+ description = operation.description or "No description"
226
+ if len(description) > self.config.max_preview_length:
227
+ description = description[: self.config.max_preview_length] + "..."
228
+ row.append(description)
229
+
230
+ table.add_row(*row)
231
+
232
+ # Panel title with pagination info
233
+ total_ops = len(operations)
234
+ page_info = (
235
+ f"Page {page + 1} of {(total_ops - 1) // self.config.items_per_page + 1}"
236
+ if total_ops > self.config.items_per_page
237
+ else ""
238
+ )
239
+ title = f"[bold]Operations ({start_idx + 1}-{end_idx} of {total_ops})[/bold]"
240
+ if page_info:
241
+ title += f" - {page_info}"
242
+
243
+ return Panel(
244
+ table,
245
+ title=title,
246
+ title_align="left",
247
+ border_style="green" if show_approved else "yellow",
248
+ )
249
+
250
+ def render_action_details(
251
+ self, operation: Operation, preview: Optional[ChangePreview] = None
252
+ ) -> Panel:
253
+ """
254
+ Render detailed view of a single operation.
255
+
256
+ Args:
257
+ operation: Operation to display
258
+ preview: Optional change preview
259
+
260
+ Returns:
261
+ Rich Panel with operation details
262
+ """
263
+ details_group = []
264
+
265
+ # Operation information
266
+ info_table = Table.grid(padding=1)
267
+ info_table.add_column("Label", style="bold cyan")
268
+ info_table.add_column("Value", style="bright_white")
269
+
270
+ info_table.add_row("Type:", self._format_operation_type(operation.type))
271
+ info_table.add_row("Description:", operation.description or "No description")
272
+ info_table.add_row(
273
+ "Requires Approval:",
274
+ "Yes" if operation.requires_approval else "No",
275
+ )
276
+ info_table.add_row("Reversible:", "Yes" if operation.reversible else "No")
277
+
278
+ if operation.data:
279
+ info_table.add_row("", "") # Separator
280
+ info_table.add_row("Operation Data:", "")
281
+ for key, value in operation.data.items():
282
+ display_value = str(value)
283
+ if len(display_value) > 100:
284
+ display_value = display_value[:100] + "..."
285
+ info_table.add_row(f" {key}:", display_value)
286
+
287
+ details_group.append(
288
+ Panel(info_table, title="Operation Details", border_style="blue")
289
+ )
290
+
291
+ # Preview information
292
+ if preview:
293
+ preview_table = Table.grid(padding=1)
294
+ preview_table.add_column("Label", style="bold cyan")
295
+ preview_table.add_column("Value", style="bright_white")
296
+
297
+ preview_table.add_row(
298
+ "Change Type:",
299
+ preview.change_type.value.replace("_", " ").title(),
300
+ )
301
+ preview_table.add_row(
302
+ "Risk Assessment:", self._format_risk_assessment(preview)
303
+ )
304
+ preview_table.add_row("Reversible:", "Yes" if preview.reversible else "No")
305
+
306
+ if preview.metadata:
307
+ preview_table.add_row("", "") # Separator
308
+ preview_table.add_row("Metadata:", "")
309
+ for key, value in preview.metadata.items():
310
+ preview_table.add_row(f" {key}:", str(value))
311
+
312
+ details_group.append(
313
+ Panel(
314
+ preview_table,
315
+ title="Change Preview",
316
+ border_style="yellow",
317
+ )
318
+ )
319
+
320
+ # Show diff if available
321
+ if preview.diff:
322
+ diff_text = preview.diff
323
+ if len(diff_text) > 500: # Limit diff display
324
+ diff_text = diff_text[:500] + "\n\n... (diff truncated)"
325
+
326
+ details_group.append(
327
+ Panel(
328
+ Text(diff_text, style="dim"),
329
+ title="Changes",
330
+ border_style="magenta",
331
+ )
332
+ )
333
+
334
+ return Panel(
335
+ Group(*details_group),
336
+ title=f"[bold]Action Details - {operation.type.value}[/bold]",
337
+ title_align="left",
338
+ border_style="white",
339
+ )
340
+
341
+ def render_batch_summary(self, batch_request: BatchApprovalRequest) -> Panel:
342
+ """
343
+ Render summary of batch operations and risk assessment.
344
+
345
+ Args:
346
+ batch_request: Batch request to summarize
347
+
348
+ Returns:
349
+ Rich Panel with batch summary
350
+ """
351
+ summary_parts = []
352
+
353
+ # Operation type breakdown
354
+ type_counts = {}
355
+ for operation in batch_request.operations:
356
+ type_name = operation.type.value
357
+ type_counts[type_name] = type_counts.get(type_name, 0) + 1
358
+
359
+ type_table = Table(show_header=True, header_style="bold")
360
+ type_table.add_column("Operation Type", style="cyan")
361
+ type_table.add_column("Count", style="green", justify="right")
362
+
363
+ for op_type, count in sorted(type_counts.items()):
364
+ formatted_type = op_type.replace("_", " ").title()
365
+ type_table.add_row(formatted_type, str(count))
366
+
367
+ summary_parts.append(
368
+ Panel(type_table, title="Operation Types", border_style="cyan")
369
+ )
370
+
371
+ # Risk assessment summary
372
+ risk_counts = self._calculate_risk_distribution(batch_request.previews)
373
+ overall_risk = self._assess_overall_risk(risk_counts)
374
+
375
+ risk_table = Table(show_header=True, header_style="bold")
376
+ risk_table.add_column("Risk Level", style="yellow")
377
+ risk_table.add_column("Count", style="green", justify="right")
378
+ risk_table.add_column("Percentage", style="blue", justify="right")
379
+
380
+ total_with_risk = sum(risk_counts.values())
381
+ for risk_level, count in risk_counts.items():
382
+ if count > 0:
383
+ percentage = (
384
+ (count / total_with_risk) * 100 if total_with_risk > 0 else 0
385
+ )
386
+ color = self._get_risk_color(risk_level)
387
+ risk_table.add_row(
388
+ f"[{color}]{risk_level.title()}[/{color}]",
389
+ str(count),
390
+ f"{percentage:.1f}%",
391
+ )
392
+
393
+ risk_panel = Panel(
394
+ risk_table,
395
+ title=f"Risk Assessment - Overall: {overall_risk}",
396
+ border_style="red",
397
+ )
398
+ summary_parts.append(risk_panel)
399
+
400
+ return Panel(
401
+ Group(*summary_parts),
402
+ title="[bold]Batch Summary[/bold]",
403
+ title_align="left",
404
+ border_style="blue",
405
+ )
406
+
407
+ def render_progress_display(self, batch_request: BatchApprovalRequest) -> Panel:
408
+ """
409
+ Render progress display for batch processing.
410
+
411
+ Args:
412
+ batch_request: Batch request to show progress for
413
+
414
+ Returns:
415
+ Rich Panel with progress information
416
+ """
417
+ total_operations = len(batch_request.operations)
418
+ approved_count = len(batch_request.approved_operations)
419
+
420
+ # Create progress bar
421
+ progress = Progress(
422
+ TextColumn("[bold blue]Processing batch...", justify="right"),
423
+ BarColumn(bar_width=None),
424
+ TextColumn("[progress.percentage]{task.percentage:>3.1f}%"),
425
+ TextColumn("•"),
426
+ TextColumn("{task.completed}/{task.total} operations"),
427
+ TimeElapsedColumn(),
428
+ )
429
+
430
+ progress.add_task(
431
+ "batch_progress", total=total_operations, completed=approved_count
432
+ )
433
+
434
+ return Panel(
435
+ progress,
436
+ title=f"[bold]Batch Progress[/bold]",
437
+ title_align="left",
438
+ border_style="green",
439
+ )
440
+
441
+ # Helper methods
442
+
443
+ def _calculate_risk_distribution(
444
+ self, previews: List[ChangePreview]
445
+ ) -> Dict[str, int]:
446
+ """Calculate distribution of risk levels in previews."""
447
+ risk_counts = {
448
+ "low": 0,
449
+ "medium": 0,
450
+ "high": 0,
451
+ "critical": 0,
452
+ "unknown": 0,
453
+ }
454
+
455
+ for preview in previews:
456
+ if preview.risk_assessment:
457
+ risk_level = self._extract_risk_level(preview.risk_assessment)
458
+ risk_counts[risk_level] = risk_counts.get(risk_level, 0) + 1
459
+ else:
460
+ risk_counts["unknown"] += 1
461
+
462
+ return risk_counts
463
+
464
+ def _extract_risk_level(self, risk_assessment: str) -> str:
465
+ """Extract risk level from risk assessment string."""
466
+ risk_lower = risk_assessment.lower()
467
+ if "critical" in risk_lower:
468
+ return "critical"
469
+ elif "high" in risk_lower:
470
+ return "high"
471
+ elif "medium" in risk_lower:
472
+ return "medium"
473
+ elif "low" in risk_lower:
474
+ return "low"
475
+ else:
476
+ return "unknown"
477
+
478
+ def _assess_overall_risk(self, risk_counts: Dict[str, int]) -> str:
479
+ """Assess overall risk level based on distribution."""
480
+ if risk_counts.get("critical", 0) > 0:
481
+ return "[red]CRITICAL[/red]"
482
+ elif risk_counts.get("high", 0) > 0:
483
+ return "[red]HIGH[/red]"
484
+ elif risk_counts.get("medium", 0) > 0:
485
+ return "[yellow]MEDIUM[/yellow]"
486
+ elif risk_counts.get("low", 0) > 0:
487
+ return "[green]LOW[/green]"
488
+ else:
489
+ return "[dim]UNKNOWN[/dim]"
490
+
491
+ def _get_risk_color(self, risk_level: str) -> str:
492
+ """Get color for risk level."""
493
+ color_map = {
494
+ "low": "green",
495
+ "medium": "yellow",
496
+ "high": "red",
497
+ "critical": "bright_red",
498
+ "unknown": "dim",
499
+ }
500
+ return color_map.get(risk_level.lower(), "white")
501
+
502
+ def _format_status(self, status: ApprovalStatus) -> str:
503
+ """Format approval status with colors."""
504
+ status_colors = {
505
+ ApprovalStatus.PENDING: "yellow",
506
+ ApprovalStatus.APPROVED: "green",
507
+ ApprovalStatus.DENIED: "red",
508
+ ApprovalStatus.EXPIRED: "dim",
509
+ }
510
+ color = status_colors.get(status, "white")
511
+ return f"[{color}]{status.value.title()}[/{color}]"
512
+
513
+ def _format_operation_type(self, op_type: OperationType) -> str:
514
+ """Format operation type for display."""
515
+ if self.config.compact_operation_names:
516
+ # Use shorter names
517
+ type_map = {
518
+ OperationType.FILE_READ: "Read",
519
+ OperationType.FILE_WRITE: "Write",
520
+ OperationType.FILE_DELETE: "Delete",
521
+ OperationType.DIRECTORY_CREATE: "MkDir",
522
+ OperationType.DIRECTORY_DELETE: "RmDir",
523
+ OperationType.COMMAND_EXECUTE: "Exec",
524
+ OperationType.WEB_REQUEST: "Web",
525
+ OperationType.MCP_TOOL_CALL: "MCP",
526
+ }
527
+ return type_map.get(op_type, op_type.value)
528
+ else:
529
+ return op_type.value.replace("_", " ").title()
530
+
531
+ def _format_target(self, operation: Operation) -> str:
532
+ """Format operation target for display."""
533
+ if operation.data:
534
+ # Extract meaningful target from operation data
535
+ if "path" in operation.data:
536
+ path = operation.data["path"]
537
+ if len(path) > 25:
538
+ return f"...{path[-25:]}"
539
+ return path
540
+ elif "url" in operation.data:
541
+ url = operation.data["url"]
542
+ if len(url) > 25:
543
+ return f"...{url[-25:]}"
544
+ return url
545
+ elif "command" in operation.data:
546
+ cmd = operation.data["command"]
547
+ if len(cmd) > 25:
548
+ return f"...{cmd[-25:]}"
549
+ return cmd
550
+
551
+ return (
552
+ operation.description[:25] + "..."
553
+ if len(operation.description or "") > 25
554
+ else (operation.description or "No target")
555
+ )
556
+
557
+ def _format_risk_assessment(self, preview: Optional[ChangePreview]) -> str:
558
+ """Format risk assessment for display."""
559
+ if not preview or not preview.risk_assessment:
560
+ return "[dim]Unknown[/dim]"
561
+
562
+ risk_level = self._extract_risk_level(preview.risk_assessment)
563
+ color = self._get_risk_color(risk_level)
564
+ return f"[{color}]{risk_level.title()}[/{color}]"
565
+
566
+ def _get_original_index(
567
+ self, batch_request: BatchApprovalRequest, filtered_index: int
568
+ ) -> int:
569
+ """Get original index when operations are filtered."""
570
+ # This would need more sophisticated logic if we implement complex filtering
571
+ # For now, assume simple filtering where we just skip approved items
572
+ current_filtered = 0
573
+ for i, operation in enumerate(batch_request.operations):
574
+ if i not in batch_request.approved_operations:
575
+ if current_filtered == filtered_index:
576
+ return i
577
+ current_filtered += 1
578
+ return filtered_index
579
+
580
+
581
+ def create_batch_approval_panel(
582
+ console: Optional[Console] = None,
583
+ config: Optional[BatchDisplayConfig] = None,
584
+ ) -> BatchApprovalPanel:
585
+ """
586
+ Create a batch approval panel with default configuration.
587
+
588
+ Args:
589
+ console: Rich console for output
590
+ config: Display configuration settings
591
+
592
+ Returns:
593
+ Configured BatchApprovalPanel instance
594
+ """
595
+ return BatchApprovalPanel(console=console, config=config)
596
+
597
+
598
+ def create_compact_batch_panel(
599
+ console: Optional[Console] = None,
600
+ ) -> BatchApprovalPanel:
601
+ """
602
+ Create a batch approval panel optimized for compact display.
603
+
604
+ Args:
605
+ console: Rich console for output
606
+
607
+ Returns:
608
+ BatchApprovalPanel configured for compact mode
609
+ """
610
+ config = BatchDisplayConfig(
611
+ mode=DisplayMode.COMPACT,
612
+ show_line_numbers=False,
613
+ max_preview_length=50,
614
+ items_per_page=20,
615
+ compact_operation_names=True,
616
+ )
617
+ return BatchApprovalPanel(console=console, config=config)