kollabor 0.4.9__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 (128) hide show
  1. core/__init__.py +18 -0
  2. core/application.py +578 -0
  3. core/cli.py +193 -0
  4. core/commands/__init__.py +43 -0
  5. core/commands/executor.py +277 -0
  6. core/commands/menu_renderer.py +319 -0
  7. core/commands/parser.py +186 -0
  8. core/commands/registry.py +331 -0
  9. core/commands/system_commands.py +479 -0
  10. core/config/__init__.py +7 -0
  11. core/config/llm_task_config.py +110 -0
  12. core/config/loader.py +501 -0
  13. core/config/manager.py +112 -0
  14. core/config/plugin_config_manager.py +346 -0
  15. core/config/plugin_schema.py +424 -0
  16. core/config/service.py +399 -0
  17. core/effects/__init__.py +1 -0
  18. core/events/__init__.py +12 -0
  19. core/events/bus.py +129 -0
  20. core/events/executor.py +154 -0
  21. core/events/models.py +258 -0
  22. core/events/processor.py +176 -0
  23. core/events/registry.py +289 -0
  24. core/fullscreen/__init__.py +19 -0
  25. core/fullscreen/command_integration.py +290 -0
  26. core/fullscreen/components/__init__.py +12 -0
  27. core/fullscreen/components/animation.py +258 -0
  28. core/fullscreen/components/drawing.py +160 -0
  29. core/fullscreen/components/matrix_components.py +177 -0
  30. core/fullscreen/manager.py +302 -0
  31. core/fullscreen/plugin.py +204 -0
  32. core/fullscreen/renderer.py +282 -0
  33. core/fullscreen/session.py +324 -0
  34. core/io/__init__.py +52 -0
  35. core/io/buffer_manager.py +362 -0
  36. core/io/config_status_view.py +272 -0
  37. core/io/core_status_views.py +410 -0
  38. core/io/input_errors.py +313 -0
  39. core/io/input_handler.py +2655 -0
  40. core/io/input_mode_manager.py +402 -0
  41. core/io/key_parser.py +344 -0
  42. core/io/layout.py +587 -0
  43. core/io/message_coordinator.py +204 -0
  44. core/io/message_renderer.py +601 -0
  45. core/io/modal_interaction_handler.py +315 -0
  46. core/io/raw_input_processor.py +946 -0
  47. core/io/status_renderer.py +845 -0
  48. core/io/terminal_renderer.py +586 -0
  49. core/io/terminal_state.py +551 -0
  50. core/io/visual_effects.py +734 -0
  51. core/llm/__init__.py +26 -0
  52. core/llm/api_communication_service.py +863 -0
  53. core/llm/conversation_logger.py +473 -0
  54. core/llm/conversation_manager.py +414 -0
  55. core/llm/file_operations_executor.py +1401 -0
  56. core/llm/hook_system.py +402 -0
  57. core/llm/llm_service.py +1629 -0
  58. core/llm/mcp_integration.py +386 -0
  59. core/llm/message_display_service.py +450 -0
  60. core/llm/model_router.py +214 -0
  61. core/llm/plugin_sdk.py +396 -0
  62. core/llm/response_parser.py +848 -0
  63. core/llm/response_processor.py +364 -0
  64. core/llm/tool_executor.py +520 -0
  65. core/logging/__init__.py +19 -0
  66. core/logging/setup.py +208 -0
  67. core/models/__init__.py +5 -0
  68. core/models/base.py +23 -0
  69. core/plugins/__init__.py +13 -0
  70. core/plugins/collector.py +212 -0
  71. core/plugins/discovery.py +386 -0
  72. core/plugins/factory.py +263 -0
  73. core/plugins/registry.py +152 -0
  74. core/storage/__init__.py +5 -0
  75. core/storage/state_manager.py +84 -0
  76. core/ui/__init__.py +6 -0
  77. core/ui/config_merger.py +176 -0
  78. core/ui/config_widgets.py +369 -0
  79. core/ui/live_modal_renderer.py +276 -0
  80. core/ui/modal_actions.py +162 -0
  81. core/ui/modal_overlay_renderer.py +373 -0
  82. core/ui/modal_renderer.py +591 -0
  83. core/ui/modal_state_manager.py +443 -0
  84. core/ui/widget_integration.py +222 -0
  85. core/ui/widgets/__init__.py +27 -0
  86. core/ui/widgets/base_widget.py +136 -0
  87. core/ui/widgets/checkbox.py +85 -0
  88. core/ui/widgets/dropdown.py +140 -0
  89. core/ui/widgets/label.py +78 -0
  90. core/ui/widgets/slider.py +185 -0
  91. core/ui/widgets/text_input.py +224 -0
  92. core/utils/__init__.py +11 -0
  93. core/utils/config_utils.py +656 -0
  94. core/utils/dict_utils.py +212 -0
  95. core/utils/error_utils.py +275 -0
  96. core/utils/key_reader.py +171 -0
  97. core/utils/plugin_utils.py +267 -0
  98. core/utils/prompt_renderer.py +151 -0
  99. kollabor-0.4.9.dist-info/METADATA +298 -0
  100. kollabor-0.4.9.dist-info/RECORD +128 -0
  101. kollabor-0.4.9.dist-info/WHEEL +5 -0
  102. kollabor-0.4.9.dist-info/entry_points.txt +2 -0
  103. kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
  104. kollabor-0.4.9.dist-info/top_level.txt +4 -0
  105. kollabor_cli_main.py +20 -0
  106. plugins/__init__.py +1 -0
  107. plugins/enhanced_input/__init__.py +18 -0
  108. plugins/enhanced_input/box_renderer.py +103 -0
  109. plugins/enhanced_input/box_styles.py +142 -0
  110. plugins/enhanced_input/color_engine.py +165 -0
  111. plugins/enhanced_input/config.py +150 -0
  112. plugins/enhanced_input/cursor_manager.py +72 -0
  113. plugins/enhanced_input/geometry.py +81 -0
  114. plugins/enhanced_input/state.py +130 -0
  115. plugins/enhanced_input/text_processor.py +115 -0
  116. plugins/enhanced_input_plugin.py +385 -0
  117. plugins/fullscreen/__init__.py +9 -0
  118. plugins/fullscreen/example_plugin.py +327 -0
  119. plugins/fullscreen/matrix_plugin.py +132 -0
  120. plugins/hook_monitoring_plugin.py +1299 -0
  121. plugins/query_enhancer_plugin.py +350 -0
  122. plugins/save_conversation_plugin.py +502 -0
  123. plugins/system_commands_plugin.py +93 -0
  124. plugins/tmux_plugin.py +795 -0
  125. plugins/workflow_enforcement_plugin.py +629 -0
  126. system_prompt/default.md +1286 -0
  127. system_prompt/default_win.md +265 -0
  128. system_prompt/example_with_trender.md +47 -0
core/cli.py ADDED
@@ -0,0 +1,193 @@
1
+ """CLI entry point for kollab command."""
2
+
3
+ import asyncio
4
+ import argparse
5
+ import logging
6
+ import sys
7
+ import re
8
+ from pathlib import Path
9
+ from importlib.metadata import version, PackageNotFoundError
10
+
11
+ # Import from the same directory
12
+ from .application import TerminalLLMChat
13
+ from .logging import setup_bootstrap_logging
14
+
15
+ # Get version from package metadata (set in pyproject.toml)
16
+ try:
17
+ __version__ = version("kollabor")
18
+ except PackageNotFoundError:
19
+ __version__ = "0.4.7" # Fallback for development mode
20
+
21
+
22
+ def parse_timeout(timeout_str: str) -> int:
23
+ """Parse timeout string into seconds.
24
+
25
+ Args:
26
+ timeout_str: Timeout string like "30s", "2min", "1h"
27
+
28
+ Returns:
29
+ Timeout in seconds
30
+
31
+ Raises:
32
+ ValueError: If timeout format is invalid
33
+ """
34
+ timeout_str = timeout_str.strip().lower()
35
+
36
+ # Match pattern like "30s", "2min", "1h"
37
+ match = re.match(
38
+ r"^(\d+(?:\.\d+)?)(s|sec|second|seconds|m|min|minute|minutes|h|hour|hours)$",
39
+ timeout_str,
40
+ )
41
+ if not match:
42
+ raise ValueError(
43
+ f"Invalid timeout format: {timeout_str}. Use format like '30s', '2min', or '1h'"
44
+ )
45
+
46
+ value = float(match.group(1))
47
+ unit = match.group(2)
48
+
49
+ # Convert to seconds
50
+ if unit in ("s", "sec", "second", "seconds"):
51
+ return int(value)
52
+ elif unit in ("m", "min", "minute", "minutes"):
53
+ return int(value * 60)
54
+ elif unit in ("h", "hour", "hours"):
55
+ return int(value * 3600)
56
+ else:
57
+ raise ValueError(f"Unknown time unit: {unit}")
58
+
59
+
60
+ def parse_arguments():
61
+ """Parse command-line arguments.
62
+
63
+ Returns:
64
+ Parsed arguments namespace
65
+ """
66
+ parser = argparse.ArgumentParser(
67
+ description="Kollab - Terminal-based LLM chat interface",
68
+ formatter_class=argparse.RawDescriptionHelpFormatter,
69
+ epilog="""
70
+ Examples:
71
+ kollab # Start interactive mode
72
+ kollab "what is 1+1?" # Pipe mode with query
73
+ kollab -p "what is 1+1?" # Pipe mode with query
74
+ kollab --timeout 30s "complex query" # Custom timeout (30 seconds)
75
+ kollab --timeout 5min "long task" # Custom timeout (5 minutes)
76
+ echo "hello" | kollab -p # Pipe input from stdin
77
+ cat file.txt | kollab -p --timeout 1h # Process file with 1 hour timeout
78
+ """,
79
+ )
80
+
81
+ parser.add_argument(
82
+ "-v",
83
+ "--version",
84
+ action="version",
85
+ version=f"%(prog)s {__version__}",
86
+ help="Show version number and exit",
87
+ )
88
+
89
+ parser.add_argument(
90
+ "query",
91
+ nargs="?",
92
+ help="Query to process in pipe mode (if not provided, reads from stdin)",
93
+ )
94
+
95
+ parser.add_argument(
96
+ "-p",
97
+ "--pipe",
98
+ action="store_true",
99
+ help="Pipe mode: process input and exit (automatically enabled if query is provided)",
100
+ )
101
+
102
+ parser.add_argument(
103
+ "--timeout",
104
+ type=str,
105
+ default="2min",
106
+ help="Timeout for pipe mode processing (e.g., 30s, 2min, 1h). Default: 2min",
107
+ )
108
+
109
+ return parser.parse_args()
110
+
111
+
112
+ async def async_main() -> None:
113
+ """Main async entry point for the application with proper error handling."""
114
+ # Setup bootstrap logging before application starts
115
+ setup_bootstrap_logging()
116
+ logger = logging.getLogger(__name__)
117
+
118
+ args = parse_arguments()
119
+
120
+ # Determine if we're in pipe mode and what the input is
121
+ piped_input = None
122
+
123
+ # If query argument is provided, use it (automatically enables pipe mode)
124
+ if args.query:
125
+ piped_input = args.query.strip()
126
+ # Otherwise, check if stdin is being piped or -p flag is set
127
+ elif args.pipe or not sys.stdin.isatty():
128
+ # Read from stdin
129
+ piped_input = sys.stdin.read().strip()
130
+ if not piped_input:
131
+ print("Error: No input received from pipe", file=sys.stderr)
132
+ sys.exit(1)
133
+
134
+ app = None
135
+ try:
136
+ logger.info("Creating application instance...")
137
+ app = TerminalLLMChat()
138
+ logger.info("Starting application...")
139
+
140
+ if piped_input:
141
+ # Parse timeout for pipe mode
142
+ try:
143
+ timeout_seconds = parse_timeout(args.timeout)
144
+ except ValueError as e:
145
+ print(f"Error: {e}", file=sys.stderr)
146
+ sys.exit(1)
147
+
148
+ # Pipe mode: send input and exit after response
149
+ await app.start_pipe_mode(piped_input, timeout=timeout_seconds)
150
+ else:
151
+ # Interactive mode
152
+ await app.start()
153
+ except KeyboardInterrupt:
154
+ # print("\n\nApplication interrupted by user")
155
+ logger.info("Application interrupted by user")
156
+ except Exception as e:
157
+ print(f"\n\nApplication failed to start: {e}")
158
+ logger.error(f"Application startup failed: {type(e).__name__}: {e}")
159
+ # Print helpful error message for common issues
160
+ if "permission" in str(e).lower():
161
+ print(
162
+ "\nTip: Check file permissions and try running with appropriate privileges"
163
+ )
164
+ elif "already in use" in str(e).lower():
165
+ print(
166
+ "\nTip: Another instance may be running. Try closing other applications."
167
+ )
168
+ elif "not found" in str(e).lower():
169
+ print("\nTip: Check that all required dependencies are installed.")
170
+ raise # Re-raise for full traceback in debug mode
171
+ finally:
172
+ # Ensure cleanup happens even if startup fails (skip if in pipe mode and already cleaned up)
173
+ pipe_mode = getattr(app, "pipe_mode", False) if app else False
174
+ if app and not app._startup_complete and not pipe_mode:
175
+ logger.info("Performing emergency cleanup after startup failure...")
176
+ try:
177
+ await app.cleanup()
178
+ except Exception as cleanup_error:
179
+ logger.error(f"Emergency cleanup failed: {cleanup_error}")
180
+ # print("Warning: Some resources may not have been cleaned up properly")
181
+
182
+
183
+ def cli_main() -> None:
184
+ """Synchronous entry point for pip-installed CLI command."""
185
+ try:
186
+ asyncio.run(async_main())
187
+ except KeyboardInterrupt:
188
+ # print("\n\nExited cleanly!")
189
+ pass
190
+ except Exception as e:
191
+ print(f"\n\nFatal error: {e}")
192
+ # Exit with error code for scripts that depend on it
193
+ sys.exit(1)
@@ -0,0 +1,43 @@
1
+ """Slash command system for Kollabor CLI.
2
+
3
+ This module provides a comprehensive slash command system that integrates
4
+ with the EventBus architecture and plugin system.
5
+
6
+ Key Components:
7
+ - Parser: Detects and parses slash commands from user input
8
+ - Registry: Manages command registration and discovery
9
+ - Executor: Executes commands with proper error handling
10
+ - UI: Handles command menus and status area interactions
11
+
12
+ Example Usage:
13
+ # Register a command in a plugin
14
+ self.register_command(
15
+ name="save",
16
+ handler=self.handle_save,
17
+ description="Save conversation to file",
18
+ mode=CommandMode.INLINE_INPUT
19
+ )
20
+
21
+ # Execute a command
22
+ result = await command_executor.execute(command, event_bus)
23
+ """
24
+
25
+ from ..events.models import (
26
+ CommandMode,
27
+ CommandCategory,
28
+ CommandDefinition,
29
+ SlashCommand,
30
+ CommandResult,
31
+ UIConfig,
32
+ ParameterDefinition
33
+ )
34
+
35
+ __all__ = [
36
+ "CommandMode",
37
+ "CommandCategory",
38
+ "CommandDefinition",
39
+ "SlashCommand",
40
+ "CommandResult",
41
+ "UIConfig",
42
+ "ParameterDefinition"
43
+ ]
@@ -0,0 +1,277 @@
1
+ """Command executor for slash command execution."""
2
+
3
+ import asyncio
4
+ import logging
5
+ from typing import Dict, Any
6
+
7
+ from ..events.models import SlashCommand, CommandResult, EventType, CommandMode
8
+ from .registry import SlashCommandRegistry
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class SlashCommandExecutor:
14
+ """Executes slash commands with proper event integration.
15
+
16
+ Handles command execution, error handling, and event bus integration
17
+ for all command modes and types.
18
+ """
19
+
20
+ def __init__(self, command_registry: SlashCommandRegistry) -> None:
21
+ """Initialize the command executor.
22
+
23
+ Args:
24
+ command_registry: Registry containing command definitions.
25
+ """
26
+ self.command_registry = command_registry
27
+ self.logger = logger
28
+
29
+ async def execute_command(self, command: SlashCommand, event_bus) -> CommandResult:
30
+ """Execute a slash command.
31
+
32
+ Args:
33
+ command: Parsed slash command to execute.
34
+ event_bus: Event bus for command lifecycle events.
35
+
36
+ Returns:
37
+ Command execution result.
38
+ """
39
+ try:
40
+ # Emit command detection event
41
+ await event_bus.emit_with_hooks(
42
+ EventType.SLASH_COMMAND_DETECTED,
43
+ {
44
+ "command_name": command.name,
45
+ "args": command.args,
46
+ "raw_input": command.raw_input
47
+ },
48
+ "commands"
49
+ )
50
+
51
+ # Look up command definition
52
+ command_def = self.command_registry.get_command(command.name)
53
+ if not command_def:
54
+ error_result = CommandResult(
55
+ success=False,
56
+ message=f"Unknown command: /{command.name}",
57
+ display_type="error"
58
+ )
59
+ await self._emit_command_error(event_bus, command, "command_not_found", error_result)
60
+ return error_result
61
+
62
+ # Check if command is enabled
63
+ if not command_def.enabled:
64
+ error_result = CommandResult(
65
+ success=False,
66
+ message=f"Command /{command.name} is currently disabled",
67
+ display_type="warning"
68
+ )
69
+ await self._emit_command_error(event_bus, command, "command_disabled", error_result)
70
+ return error_result
71
+
72
+ # Emit command execution start event
73
+ await event_bus.emit_with_hooks(
74
+ EventType.SLASH_COMMAND_EXECUTE,
75
+ {
76
+ "command": command,
77
+ "command_def": command_def,
78
+ "mode": command_def.mode.value
79
+ },
80
+ "commands"
81
+ )
82
+
83
+ # Execute the command handler
84
+ self.logger.info(f"Executing command /{command.name} from plugin {command_def.plugin_name}")
85
+
86
+ try:
87
+ # Call the command handler
88
+ if asyncio.iscoroutinefunction(command_def.handler):
89
+ result = await command_def.handler(command)
90
+ else:
91
+ result = command_def.handler(command)
92
+
93
+ # Ensure result is a CommandResult
94
+ if not isinstance(result, CommandResult):
95
+ result = CommandResult(
96
+ success=True,
97
+ message=str(result) if result is not None else "Command completed successfully"
98
+ )
99
+
100
+ # Emit command completion event
101
+ await event_bus.emit_with_hooks(
102
+ EventType.SLASH_COMMAND_COMPLETE,
103
+ {
104
+ "command": command,
105
+ "command_def": command_def,
106
+ "result": result
107
+ },
108
+ "commands"
109
+ )
110
+
111
+ # Handle modal UI configs
112
+ if result.ui_config and result.ui_config.type == "modal":
113
+ await self._trigger_modal_mode(result.ui_config, event_bus)
114
+ elif result.ui_config and result.ui_config.type == "status_modal":
115
+ await self._trigger_status_modal_mode(result.ui_config, event_bus)
116
+ elif result.status_ui:
117
+ # Handle status_ui component (e.g., from /status command)
118
+ await self._display_status_ui(result.status_ui, event_bus)
119
+ else:
120
+ # Handle non-modal command output display
121
+ await self._display_command_result(result, event_bus)
122
+
123
+ self.logger.info(f"Command /{command.name} completed successfully")
124
+ return result
125
+
126
+ except Exception as handler_error:
127
+ error_result = CommandResult(
128
+ success=False,
129
+ message=f"Command /{command.name} failed: {str(handler_error)}",
130
+ display_type="error",
131
+ data={"error": str(handler_error)}
132
+ )
133
+
134
+ await self._emit_command_error(event_bus, command, "handler_error", error_result)
135
+ self.logger.error(f"Command /{command.name} handler failed: {handler_error}")
136
+ return error_result
137
+
138
+ except Exception as e:
139
+ error_result = CommandResult(
140
+ success=False,
141
+ message=f"Internal error executing command /{command.name}",
142
+ display_type="error",
143
+ data={"error": str(e)}
144
+ )
145
+
146
+ await self._emit_command_error(event_bus, command, "internal_error", error_result)
147
+ self.logger.error(f"Internal error executing command /{command.name}: {e}")
148
+ return error_result
149
+
150
+ async def _emit_command_error(self, event_bus, command: SlashCommand, error_type: str, result: CommandResult) -> None:
151
+ """Emit command error event.
152
+
153
+ Args:
154
+ event_bus: Event bus for error events.
155
+ command: Command that failed.
156
+ error_type: Type of error that occurred.
157
+ result: Error result details.
158
+ """
159
+ try:
160
+ await event_bus.emit_with_hooks(
161
+ EventType.SLASH_COMMAND_ERROR,
162
+ {
163
+ "command": command,
164
+ "error_type": error_type,
165
+ "result": result
166
+ },
167
+ "commands"
168
+ )
169
+ except Exception as e:
170
+ self.logger.error(f"Failed to emit command error event: {e}")
171
+
172
+ def get_execution_stats(self) -> Dict[str, Any]:
173
+ """Get execution statistics for monitoring.
174
+
175
+ Returns:
176
+ Dictionary with execution statistics.
177
+ """
178
+ # In a full implementation, this would track execution metrics
179
+ return {
180
+ "registry_stats": self.command_registry.get_registry_stats()
181
+ }
182
+
183
+ async def _display_command_result(self, result: CommandResult, event_bus):
184
+ """Display non-modal command result through event bus.
185
+
186
+ Args:
187
+ result: Command result to display.
188
+ event_bus: Event bus for display events.
189
+ """
190
+ try:
191
+ if result.message:
192
+ # Emit command output event for display
193
+ await event_bus.emit_with_hooks(
194
+ EventType.COMMAND_OUTPUT_DISPLAY,
195
+ {
196
+ "message": result.message,
197
+ "display_type": result.display_type or "info",
198
+ "success": result.success,
199
+ "data": result.data
200
+ },
201
+ "command_output"
202
+ )
203
+ self.logger.info(f"Command output displayed: {result.display_type}")
204
+ except Exception as e:
205
+ self.logger.error(f"Error displaying command result: {e}")
206
+
207
+ async def _trigger_modal_mode(self, ui_config, event_bus):
208
+ """Trigger modal mode through event bus.
209
+
210
+ Args:
211
+ ui_config: UI configuration for the modal.
212
+ event_bus: Event bus for modal events.
213
+ """
214
+ try:
215
+ # Emit modal trigger event that input handler will listen for
216
+ await event_bus.emit_with_hooks(
217
+ EventType.MODAL_TRIGGER, # Use dedicated modal trigger event
218
+ {
219
+ "ui_config": ui_config,
220
+ "action": "show_modal"
221
+ },
222
+ "modal_trigger"
223
+ )
224
+ self.logger.info("Modal UI config triggered through event bus")
225
+ except Exception as e:
226
+ self.logger.error(f"Error triggering modal mode: {e}")
227
+
228
+ async def _trigger_status_modal_mode(self, ui_config, event_bus):
229
+ """Trigger status modal mode through event bus.
230
+
231
+ Args:
232
+ ui_config: UI configuration for the status modal.
233
+ event_bus: Event bus for modal events.
234
+ """
235
+ try:
236
+ # Emit status modal trigger event that input handler will listen for
237
+ await event_bus.emit_with_hooks(
238
+ EventType.STATUS_MODAL_TRIGGER, # New event type for status modals
239
+ {
240
+ "ui_config": ui_config,
241
+ "action": "show_status_modal"
242
+ },
243
+ "status_modal_trigger"
244
+ )
245
+ self.logger.info("Status modal UI config triggered through event bus")
246
+ except Exception as e:
247
+ self.logger.error(f"Error triggering status modal mode: {e}")
248
+
249
+ async def _display_status_ui(self, status_ui, event_bus):
250
+ """Display status UI component through event bus.
251
+
252
+ Args:
253
+ status_ui: Status UI component with render() method.
254
+ event_bus: Event bus for display events.
255
+ """
256
+ try:
257
+ # Render the status UI if it has a render method
258
+ if hasattr(status_ui, 'render'):
259
+ lines = status_ui.render()
260
+ message = '\n'.join(lines) if isinstance(lines, list) else str(lines)
261
+ else:
262
+ message = str(status_ui)
263
+
264
+ # Emit status display event
265
+ await event_bus.emit_with_hooks(
266
+ EventType.COMMAND_OUTPUT_DISPLAY,
267
+ {
268
+ "message": message,
269
+ "display_type": "info",
270
+ "success": True,
271
+ "is_status_ui": True
272
+ },
273
+ "status_display"
274
+ )
275
+ self.logger.info("Status UI displayed")
276
+ except Exception as e:
277
+ self.logger.error(f"Error displaying status UI: {e}")