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
@@ -0,0 +1,289 @@
1
+ """Hook registry for managing hook registration and lifecycle."""
2
+
3
+ import logging
4
+ from collections import defaultdict
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from .models import EventType, Hook, HookStatus
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class HookRegistry:
13
+ """Manages hook registration, organization, and status tracking.
14
+
15
+ This class is responsible for maintaining the registry of hooks,
16
+ organizing them by event type and priority, and tracking their status.
17
+ """
18
+
19
+ def __init__(self):
20
+ """Initialize the hook registry."""
21
+ self.hooks: Dict[EventType, List[Hook]] = defaultdict(list)
22
+ self.hook_status: Dict[str, HookStatus] = {}
23
+ self.hook_metadata: Dict[str, Dict[str, Any]] = {}
24
+ logger.info("HookRegistry initialized")
25
+
26
+ def register_hook(self, hook: Hook) -> bool:
27
+ """Register a hook with the registry.
28
+
29
+ Args:
30
+ hook: The hook to register.
31
+
32
+ Returns:
33
+ True if registration successful, False otherwise.
34
+ """
35
+ try:
36
+ hook_key = f"{hook.plugin_name}.{hook.name}"
37
+
38
+ # Check for duplicate registration
39
+ if hook_key in self.hook_status:
40
+ logger.warning(f"Hook {hook_key} already registered, updating registration")
41
+ self._remove_hook_from_lists(hook_key)
42
+
43
+ # Add hook to appropriate event type list
44
+ self.hooks[hook.event_type].append(hook)
45
+
46
+ # Sort hooks by priority (highest first)
47
+ self.hooks[hook.event_type].sort(key=lambda h: h.priority, reverse=True)
48
+
49
+ # Track hook status and metadata
50
+ self.hook_status[hook_key] = HookStatus.PENDING
51
+ self.hook_metadata[hook_key] = {
52
+ "event_type": hook.event_type.value,
53
+ "priority": hook.priority,
54
+ "plugin_name": hook.plugin_name,
55
+ "enabled": hook.enabled,
56
+ "timeout": hook.timeout,
57
+ "error_action": hook.error_action,
58
+ "registration_order": len(self.hook_status)
59
+ }
60
+
61
+ logger.debug(f"Registered hook: {hook_key} for {hook.event_type.value} (priority: {hook.priority})")
62
+ return True
63
+
64
+ except Exception as e:
65
+ logger.error(f"Failed to register hook {hook.plugin_name}.{hook.name}: {e}")
66
+ return False
67
+
68
+ def unregister_hook(self, plugin_name: str, hook_name: str) -> bool:
69
+ """Unregister a hook from the registry.
70
+
71
+ Args:
72
+ plugin_name: Name of the plugin that owns the hook.
73
+ hook_name: Name of the hook.
74
+
75
+ Returns:
76
+ True if unregistration successful, False otherwise.
77
+ """
78
+ hook_key = f"{plugin_name}.{hook_name}"
79
+
80
+ if hook_key not in self.hook_status:
81
+ logger.warning(f"Hook {hook_key} not found for unregistration")
82
+ return False
83
+
84
+ try:
85
+ # Remove from hook lists
86
+ removed = self._remove_hook_from_lists(hook_key)
87
+
88
+ if removed:
89
+ # Clean up status and metadata
90
+ del self.hook_status[hook_key]
91
+ del self.hook_metadata[hook_key]
92
+ logger.info(f"Unregistered hook: {hook_key}")
93
+ return True
94
+ else:
95
+ logger.warning(f"Hook {hook_key} not found in any event type lists")
96
+ return False
97
+
98
+ except Exception as e:
99
+ logger.error(f"Failed to unregister hook {hook_key}: {e}")
100
+ return False
101
+
102
+ def get_hooks_for_event(self, event_type: EventType) -> List[Hook]:
103
+ """Get all hooks registered for a specific event type.
104
+
105
+ Args:
106
+ event_type: The event type to get hooks for.
107
+
108
+ Returns:
109
+ List of hooks sorted by priority (highest first).
110
+ """
111
+ hooks = self.hooks.get(event_type, [])
112
+
113
+ # Filter out disabled hooks
114
+ enabled_hooks = [hook for hook in hooks if hook.enabled]
115
+
116
+ #logger.debug(f"Retrieved {len(enabled_hooks)} enabled hooks for {event_type.value}")
117
+ return enabled_hooks
118
+
119
+ def update_hook_status(self, hook_key: str, status: str) -> bool:
120
+ """Update the status of a registered hook.
121
+
122
+ Args:
123
+ hook_key: Key identifying the hook (plugin_name.hook_name).
124
+ status: New status for the hook.
125
+
126
+ Returns:
127
+ True if update successful, False otherwise.
128
+ """
129
+ try:
130
+ if hook_key not in self.hook_status:
131
+ logger.warning(f"Cannot update status for unknown hook: {hook_key}")
132
+ return False
133
+
134
+ # Convert string status to HookStatus enum
135
+ status_mapping = {
136
+ "pending": HookStatus.PENDING,
137
+ "working": HookStatus.WORKING,
138
+ "completed": HookStatus.COMPLETED,
139
+ "failed": HookStatus.FAILED,
140
+ "timeout": HookStatus.TIMEOUT
141
+ }
142
+
143
+ if status not in status_mapping:
144
+ logger.error(f"Invalid status '{status}' for hook {hook_key}")
145
+ return False
146
+
147
+ old_status = self.hook_status[hook_key]
148
+ self.hook_status[hook_key] = status_mapping[status]
149
+
150
+ return True
151
+
152
+ except Exception as e:
153
+ logger.error(f"Failed to update status for hook {hook_key}: {e}")
154
+ return False
155
+
156
+ def get_hook_status_summary(self) -> Dict[str, Any]:
157
+ """Get a summary of all hook statuses.
158
+
159
+ Returns:
160
+ Dictionary with hook status statistics and details.
161
+ """
162
+ status_counts = defaultdict(int)
163
+ hook_details = {}
164
+
165
+ for hook_key, status in self.hook_status.items():
166
+ status_counts[status.value] += 1
167
+
168
+ metadata = self.hook_metadata.get(hook_key, {})
169
+ hook_details[hook_key] = {
170
+ "status": status.value,
171
+ "event_type": metadata.get("event_type", "unknown"),
172
+ "priority": metadata.get("priority", 0),
173
+ "enabled": metadata.get("enabled", True),
174
+ "plugin_name": metadata.get("plugin_name", "unknown")
175
+ }
176
+
177
+ return {
178
+ "total_hooks": len(self.hook_status),
179
+ "status_counts": dict(status_counts),
180
+ "hook_details": hook_details
181
+ }
182
+
183
+ def enable_hook(self, plugin_name: str, hook_name: str) -> bool:
184
+ """Enable a registered hook.
185
+
186
+ Args:
187
+ plugin_name: Name of the plugin that owns the hook.
188
+ hook_name: Name of the hook.
189
+
190
+ Returns:
191
+ True if hook was enabled, False otherwise.
192
+ """
193
+ return self._set_hook_enabled(plugin_name, hook_name, True)
194
+
195
+ def disable_hook(self, plugin_name: str, hook_name: str) -> bool:
196
+ """Disable a registered hook.
197
+
198
+ Args:
199
+ plugin_name: Name of the plugin that owns the hook.
200
+ hook_name: Name of the hook.
201
+
202
+ Returns:
203
+ True if hook was disabled, False otherwise.
204
+ """
205
+ return self._set_hook_enabled(plugin_name, hook_name, False)
206
+
207
+ def _set_hook_enabled(self, plugin_name: str, hook_name: str, enabled: bool) -> bool:
208
+ """Set the enabled state of a hook.
209
+
210
+ Args:
211
+ plugin_name: Name of the plugin that owns the hook.
212
+ hook_name: Name of the hook.
213
+ enabled: Whether to enable or disable the hook.
214
+
215
+ Returns:
216
+ True if state was changed, False otherwise.
217
+ """
218
+ hook_key = f"{plugin_name}.{hook_name}"
219
+
220
+ # Find the hook in the registry
221
+ hook_found = False
222
+ for event_type, hooks in self.hooks.items():
223
+ for hook in hooks:
224
+ if f"{hook.plugin_name}.{hook.name}" == hook_key:
225
+ hook.enabled = enabled
226
+ hook_found = True
227
+
228
+ # Update metadata
229
+ if hook_key in self.hook_metadata:
230
+ self.hook_metadata[hook_key]["enabled"] = enabled
231
+
232
+ action = "enabled" if enabled else "disabled"
233
+ logger.info(f"Hook {hook_key} {action}")
234
+ break
235
+ if hook_found:
236
+ break
237
+
238
+ if not hook_found:
239
+ logger.warning(f"Hook {hook_key} not found for enable/disable")
240
+ return False
241
+
242
+ return True
243
+
244
+ def _remove_hook_from_lists(self, hook_key: str) -> bool:
245
+ """Remove a hook from all event type lists.
246
+
247
+ Args:
248
+ hook_key: Key identifying the hook to remove.
249
+
250
+ Returns:
251
+ True if hook was found and removed, False otherwise.
252
+ """
253
+ removed = False
254
+
255
+ for event_type, hooks in self.hooks.items():
256
+ # Find and remove hook from this event type's list
257
+ for i, hook in enumerate(hooks):
258
+ if f"{hook.plugin_name}.{hook.name}" == hook_key:
259
+ del hooks[i]
260
+ removed = True
261
+ break
262
+
263
+ return removed
264
+
265
+ def get_registry_stats(self) -> Dict[str, Any]:
266
+ """Get comprehensive registry statistics.
267
+
268
+ Returns:
269
+ Dictionary with detailed registry statistics.
270
+ """
271
+ event_type_counts = {}
272
+ priority_distribution = defaultdict(int)
273
+ plugin_counts = defaultdict(int)
274
+
275
+ for event_type, hooks in self.hooks.items():
276
+ event_type_counts[event_type.value] = len(hooks)
277
+
278
+ for hook in hooks:
279
+ priority_distribution[hook.priority] += 1
280
+ plugin_counts[hook.plugin_name] += 1
281
+
282
+ return {
283
+ "total_hooks": len(self.hook_status),
284
+ "event_types": len(self.hooks),
285
+ "hooks_per_event_type": event_type_counts,
286
+ "priority_distribution": dict(priority_distribution),
287
+ "hooks_per_plugin": dict(plugin_counts),
288
+ "status_summary": self.get_hook_status_summary()
289
+ }
@@ -0,0 +1,19 @@
1
+ """Full-screen plugin framework for terminal takeover.
2
+
3
+ This module provides a reusable framework for plugins to take complete
4
+ terminal control using the proven modal system architecture.
5
+ """
6
+
7
+ from .manager import FullScreenManager
8
+ from .renderer import FullScreenRenderer
9
+ from .plugin import FullScreenPlugin
10
+ from .session import FullScreenSession
11
+ from .command_integration import FullScreenCommandIntegrator
12
+
13
+ __all__ = [
14
+ "FullScreenManager",
15
+ "FullScreenRenderer",
16
+ "FullScreenPlugin",
17
+ "FullScreenSession",
18
+ "FullScreenCommandIntegrator"
19
+ ]
@@ -0,0 +1,290 @@
1
+ """Fullscreen plugin command integration system.
2
+
3
+ This module handles automatic discovery and registration of slash commands
4
+ for fullscreen plugins, enabling dynamic plugin-to-command mapping.
5
+ """
6
+
7
+ import logging
8
+ from pathlib import Path
9
+ from typing import Dict, List, Optional, Type
10
+ import importlib.util
11
+ import sys
12
+
13
+ from ..events.models import CommandDefinition, CommandMode, CommandCategory
14
+ from ..commands.registry import SlashCommandRegistry
15
+ from .plugin import FullScreenPlugin
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class FullScreenCommandIntegrator:
21
+ """Integrates fullscreen plugins with the slash command system.
22
+
23
+ This class:
24
+ - Discovers fullscreen plugins in plugins/fullscreen/
25
+ - Auto-registers slash commands based on plugin metadata
26
+ - Handles dynamic plugin loading/unloading
27
+ - Maps commands to plugin execution
28
+ """
29
+
30
+ def __init__(self, command_registry: SlashCommandRegistry, event_bus):
31
+ """Initialize the fullscreen command integrator.
32
+
33
+ Args:
34
+ command_registry: Slash command registry for registration
35
+ event_bus: Event bus for communication
36
+ """
37
+ self.command_registry = command_registry
38
+ self.event_bus = event_bus
39
+ self.registered_plugins: Dict[str, Type[FullScreenPlugin]] = {}
40
+ self.plugin_instances: Dict[str, FullScreenPlugin] = {}
41
+ self._fullscreen_manager = None
42
+
43
+ logger.info("FullScreen command integrator initialized")
44
+
45
+ def discover_and_register_plugins(self, plugins_dir: Path) -> int:
46
+ """Discover and register all fullscreen plugins.
47
+
48
+ Args:
49
+ plugins_dir: Base plugins directory
50
+
51
+ Returns:
52
+ Number of plugins registered
53
+ """
54
+ fullscreen_dir = plugins_dir / "fullscreen"
55
+ if not fullscreen_dir.exists():
56
+ logger.info("No fullscreen plugins directory found")
57
+ return 0
58
+
59
+ registered_count = 0
60
+
61
+ # Scan for Python files in fullscreen directory
62
+ for plugin_file in fullscreen_dir.glob("*.py"):
63
+ if plugin_file.name.startswith("__"):
64
+ continue
65
+
66
+ try:
67
+ plugin_class = self._load_plugin_class(plugin_file)
68
+ if plugin_class:
69
+ if self._register_plugin_commands(plugin_class):
70
+ registered_count += 1
71
+ logger.info(f"Registered commands for plugin: {plugin_class.__name__}")
72
+ except Exception as e:
73
+ logger.error(f"Failed to load plugin {plugin_file.name}: {e}")
74
+
75
+ logger.info(f"Discovered and registered {registered_count} fullscreen plugins")
76
+ return registered_count
77
+
78
+ def _load_plugin_class(self, plugin_file: Path) -> Optional[Type[FullScreenPlugin]]:
79
+ """Load a plugin class from a Python file.
80
+
81
+ Args:
82
+ plugin_file: Path to the plugin Python file
83
+
84
+ Returns:
85
+ Plugin class or None if not found/invalid
86
+ """
87
+ try:
88
+ # Create module spec and load
89
+ module_name = f"plugins.fullscreen.{plugin_file.stem}"
90
+ spec = importlib.util.spec_from_file_location(module_name, plugin_file)
91
+ if not spec or not spec.loader:
92
+ return None
93
+
94
+ module = importlib.util.module_from_spec(spec)
95
+ sys.modules[module_name] = module
96
+ spec.loader.exec_module(module)
97
+
98
+ # Find FullScreenPlugin subclass
99
+ for attr_name in dir(module):
100
+ attr = getattr(module, attr_name)
101
+ if (isinstance(attr, type) and
102
+ issubclass(attr, FullScreenPlugin) and
103
+ attr != FullScreenPlugin):
104
+ return attr
105
+
106
+ logger.warning(f"No FullScreenPlugin subclass found in {plugin_file.name}")
107
+ return None
108
+
109
+ except Exception as e:
110
+ logger.error(f"Error loading plugin class from {plugin_file}: {e}")
111
+ return None
112
+
113
+ def _register_plugin_commands(self, plugin_class: Type[FullScreenPlugin]) -> bool:
114
+ """Register slash commands for a plugin class.
115
+
116
+ Args:
117
+ plugin_class: The plugin class to register commands for
118
+
119
+ Returns:
120
+ True if registration successful
121
+ """
122
+ try:
123
+ # Create temporary instance to get metadata
124
+ temp_instance = plugin_class()
125
+ metadata = temp_instance.metadata
126
+
127
+ if not metadata:
128
+ logger.warning(f"Plugin {plugin_class.__name__} has no metadata")
129
+ return False
130
+
131
+ # Store plugin class for later instantiation
132
+ self.registered_plugins[metadata.name] = plugin_class
133
+
134
+ # Register primary command (plugin name)
135
+ primary_command = CommandDefinition(
136
+ name=metadata.name,
137
+ aliases=metadata.aliases or [],
138
+ description=metadata.description,
139
+ category=CommandCategory.SYSTEM,
140
+ mode=CommandMode.INSTANT,
141
+ handler=self._create_plugin_handler(metadata.name),
142
+ icon=metadata.icon,
143
+ plugin_name="fullscreen_integrator"
144
+ )
145
+
146
+ success = self.command_registry.register_command(primary_command)
147
+ if not success:
148
+ logger.error(f"Failed to register primary command for {metadata.name}")
149
+ return False
150
+
151
+ # Register alias commands if any
152
+ for alias in (metadata.aliases or []):
153
+ alias_command = CommandDefinition(
154
+ name=alias,
155
+ aliases=[],
156
+ description=f"{metadata.description} (alias)",
157
+ category=CommandCategory.SYSTEM,
158
+ mode=CommandMode.INSTANT,
159
+ handler=self._create_plugin_handler(metadata.name),
160
+ icon=metadata.icon,
161
+ plugin_name="fullscreen_integrator"
162
+ )
163
+
164
+ alias_success = self.command_registry.register_command(alias_command)
165
+ if alias_success:
166
+ logger.debug(f"Registered alias command: {alias} -> {metadata.name}")
167
+ else:
168
+ logger.warning(f"Failed to register alias command: {alias}")
169
+
170
+ return True
171
+
172
+ except Exception as e:
173
+ logger.error(f"Error registering commands for {plugin_class.__name__}: {e}")
174
+ return False
175
+
176
+ def _create_plugin_handler(self, plugin_name: str):
177
+ """Create a command handler for a specific plugin.
178
+
179
+ Args:
180
+ plugin_name: Name of the plugin to handle
181
+
182
+ Returns:
183
+ Async command handler function
184
+ """
185
+ async def handler(command):
186
+ """Handle command execution for fullscreen plugin."""
187
+ try:
188
+ # Get or create fullscreen manager
189
+ if not self._fullscreen_manager:
190
+ from . import FullScreenManager
191
+ self._fullscreen_manager = FullScreenManager(self.event_bus, None)
192
+
193
+ # Get or create plugin instance
194
+ if plugin_name not in self.plugin_instances:
195
+ plugin_class = self.registered_plugins.get(plugin_name)
196
+ if not plugin_class:
197
+ raise ValueError(f"Plugin class not found: {plugin_name}")
198
+
199
+ plugin_instance = plugin_class()
200
+ self.plugin_instances[plugin_name] = plugin_instance
201
+ self._fullscreen_manager.register_plugin(plugin_instance)
202
+ logger.debug(f"Created and registered plugin instance: {plugin_name}")
203
+
204
+ # Launch the plugin
205
+ success = await self._fullscreen_manager.launch_plugin(plugin_name)
206
+
207
+ if success:
208
+ from ..events.models import CommandResult
209
+ return CommandResult(
210
+ success=True,
211
+ message="", # No message to avoid display artifacts
212
+ display_type="success"
213
+ )
214
+ else:
215
+ from ..events.models import CommandResult
216
+ return CommandResult(
217
+ success=False,
218
+ message=f"Failed to launch {plugin_name} plugin",
219
+ display_type="error"
220
+ )
221
+
222
+ except Exception as e:
223
+ logger.error(f"Error executing plugin {plugin_name}: {e}")
224
+ from ..events.models import CommandResult
225
+ return CommandResult(
226
+ success=False,
227
+ message=f"Plugin error: {str(e)}",
228
+ display_type="error"
229
+ )
230
+
231
+ return handler
232
+
233
+ def unregister_plugin(self, plugin_name: str) -> bool:
234
+ """Unregister a plugin and its commands.
235
+
236
+ Args:
237
+ plugin_name: Name of plugin to unregister
238
+
239
+ Returns:
240
+ True if successful
241
+ """
242
+ try:
243
+ # Remove from our tracking
244
+ if plugin_name in self.registered_plugins:
245
+ plugin_class = self.registered_plugins[plugin_name]
246
+ temp_instance = plugin_class()
247
+ metadata = temp_instance.metadata
248
+
249
+ # Unregister all commands for this plugin
250
+ self.command_registry.unregister_command(metadata.name)
251
+ for alias in (metadata.aliases or []):
252
+ self.command_registry.unregister_command(alias)
253
+
254
+ del self.registered_plugins[plugin_name]
255
+
256
+ if plugin_name in self.plugin_instances:
257
+ del self.plugin_instances[plugin_name]
258
+
259
+ logger.info(f"Unregistered plugin: {plugin_name}")
260
+ return True
261
+
262
+ return False
263
+
264
+ except Exception as e:
265
+ logger.error(f"Error unregistering plugin {plugin_name}: {e}")
266
+ return False
267
+
268
+ def get_registered_plugins(self) -> List[str]:
269
+ """Get list of registered plugin names.
270
+
271
+ Returns:
272
+ List of plugin names
273
+ """
274
+ return list(self.registered_plugins.keys())
275
+
276
+ def reload_plugins(self, plugins_dir: Path) -> int:
277
+ """Reload all fullscreen plugins from directory.
278
+
279
+ Args:
280
+ plugins_dir: Base plugins directory
281
+
282
+ Returns:
283
+ Number of plugins reloaded
284
+ """
285
+ # Unregister all current plugins
286
+ for plugin_name in list(self.registered_plugins.keys()):
287
+ self.unregister_plugin(plugin_name)
288
+
289
+ # Re-discover and register
290
+ return self.discover_and_register_plugins(plugins_dir)
@@ -0,0 +1,12 @@
1
+ """Full-screen plugin components and utilities."""
2
+
3
+ from .drawing import DrawingPrimitives
4
+ from .animation import AnimationFramework
5
+ from .matrix_components import MatrixColumn, MatrixRenderer
6
+
7
+ __all__ = [
8
+ "DrawingPrimitives",
9
+ "AnimationFramework",
10
+ "MatrixColumn",
11
+ "MatrixRenderer"
12
+ ]