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.
- core/__init__.py +18 -0
- core/application.py +578 -0
- core/cli.py +193 -0
- core/commands/__init__.py +43 -0
- core/commands/executor.py +277 -0
- core/commands/menu_renderer.py +319 -0
- core/commands/parser.py +186 -0
- core/commands/registry.py +331 -0
- core/commands/system_commands.py +479 -0
- core/config/__init__.py +7 -0
- core/config/llm_task_config.py +110 -0
- core/config/loader.py +501 -0
- core/config/manager.py +112 -0
- core/config/plugin_config_manager.py +346 -0
- core/config/plugin_schema.py +424 -0
- core/config/service.py +399 -0
- core/effects/__init__.py +1 -0
- core/events/__init__.py +12 -0
- core/events/bus.py +129 -0
- core/events/executor.py +154 -0
- core/events/models.py +258 -0
- core/events/processor.py +176 -0
- core/events/registry.py +289 -0
- core/fullscreen/__init__.py +19 -0
- core/fullscreen/command_integration.py +290 -0
- core/fullscreen/components/__init__.py +12 -0
- core/fullscreen/components/animation.py +258 -0
- core/fullscreen/components/drawing.py +160 -0
- core/fullscreen/components/matrix_components.py +177 -0
- core/fullscreen/manager.py +302 -0
- core/fullscreen/plugin.py +204 -0
- core/fullscreen/renderer.py +282 -0
- core/fullscreen/session.py +324 -0
- core/io/__init__.py +52 -0
- core/io/buffer_manager.py +362 -0
- core/io/config_status_view.py +272 -0
- core/io/core_status_views.py +410 -0
- core/io/input_errors.py +313 -0
- core/io/input_handler.py +2655 -0
- core/io/input_mode_manager.py +402 -0
- core/io/key_parser.py +344 -0
- core/io/layout.py +587 -0
- core/io/message_coordinator.py +204 -0
- core/io/message_renderer.py +601 -0
- core/io/modal_interaction_handler.py +315 -0
- core/io/raw_input_processor.py +946 -0
- core/io/status_renderer.py +845 -0
- core/io/terminal_renderer.py +586 -0
- core/io/terminal_state.py +551 -0
- core/io/visual_effects.py +734 -0
- core/llm/__init__.py +26 -0
- core/llm/api_communication_service.py +863 -0
- core/llm/conversation_logger.py +473 -0
- core/llm/conversation_manager.py +414 -0
- core/llm/file_operations_executor.py +1401 -0
- core/llm/hook_system.py +402 -0
- core/llm/llm_service.py +1629 -0
- core/llm/mcp_integration.py +386 -0
- core/llm/message_display_service.py +450 -0
- core/llm/model_router.py +214 -0
- core/llm/plugin_sdk.py +396 -0
- core/llm/response_parser.py +848 -0
- core/llm/response_processor.py +364 -0
- core/llm/tool_executor.py +520 -0
- core/logging/__init__.py +19 -0
- core/logging/setup.py +208 -0
- core/models/__init__.py +5 -0
- core/models/base.py +23 -0
- core/plugins/__init__.py +13 -0
- core/plugins/collector.py +212 -0
- core/plugins/discovery.py +386 -0
- core/plugins/factory.py +263 -0
- core/plugins/registry.py +152 -0
- core/storage/__init__.py +5 -0
- core/storage/state_manager.py +84 -0
- core/ui/__init__.py +6 -0
- core/ui/config_merger.py +176 -0
- core/ui/config_widgets.py +369 -0
- core/ui/live_modal_renderer.py +276 -0
- core/ui/modal_actions.py +162 -0
- core/ui/modal_overlay_renderer.py +373 -0
- core/ui/modal_renderer.py +591 -0
- core/ui/modal_state_manager.py +443 -0
- core/ui/widget_integration.py +222 -0
- core/ui/widgets/__init__.py +27 -0
- core/ui/widgets/base_widget.py +136 -0
- core/ui/widgets/checkbox.py +85 -0
- core/ui/widgets/dropdown.py +140 -0
- core/ui/widgets/label.py +78 -0
- core/ui/widgets/slider.py +185 -0
- core/ui/widgets/text_input.py +224 -0
- core/utils/__init__.py +11 -0
- core/utils/config_utils.py +656 -0
- core/utils/dict_utils.py +212 -0
- core/utils/error_utils.py +275 -0
- core/utils/key_reader.py +171 -0
- core/utils/plugin_utils.py +267 -0
- core/utils/prompt_renderer.py +151 -0
- kollabor-0.4.9.dist-info/METADATA +298 -0
- kollabor-0.4.9.dist-info/RECORD +128 -0
- kollabor-0.4.9.dist-info/WHEEL +5 -0
- kollabor-0.4.9.dist-info/entry_points.txt +2 -0
- kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
- kollabor-0.4.9.dist-info/top_level.txt +4 -0
- kollabor_cli_main.py +20 -0
- plugins/__init__.py +1 -0
- plugins/enhanced_input/__init__.py +18 -0
- plugins/enhanced_input/box_renderer.py +103 -0
- plugins/enhanced_input/box_styles.py +142 -0
- plugins/enhanced_input/color_engine.py +165 -0
- plugins/enhanced_input/config.py +150 -0
- plugins/enhanced_input/cursor_manager.py +72 -0
- plugins/enhanced_input/geometry.py +81 -0
- plugins/enhanced_input/state.py +130 -0
- plugins/enhanced_input/text_processor.py +115 -0
- plugins/enhanced_input_plugin.py +385 -0
- plugins/fullscreen/__init__.py +9 -0
- plugins/fullscreen/example_plugin.py +327 -0
- plugins/fullscreen/matrix_plugin.py +132 -0
- plugins/hook_monitoring_plugin.py +1299 -0
- plugins/query_enhancer_plugin.py +350 -0
- plugins/save_conversation_plugin.py +502 -0
- plugins/system_commands_plugin.py +93 -0
- plugins/tmux_plugin.py +795 -0
- plugins/workflow_enforcement_plugin.py +629 -0
- system_prompt/default.md +1286 -0
- system_prompt/default_win.md +265 -0
- system_prompt/example_with_trender.md +47 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Full-screen manager for plugin coordination and modal integration."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Dict, List, Optional, Any
|
|
6
|
+
|
|
7
|
+
from ..events.models import EventType
|
|
8
|
+
from .plugin import FullScreenPlugin, PluginMetadata
|
|
9
|
+
from .session import FullScreenSession
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FullScreenManager:
|
|
15
|
+
"""Manages full-screen plugins and their integration with the modal system.
|
|
16
|
+
|
|
17
|
+
This class coordinates between plugins, the event bus, and the modal system
|
|
18
|
+
to provide seamless full-screen experiences that properly pause the main
|
|
19
|
+
application interface.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, event_bus, terminal_renderer):
|
|
23
|
+
"""Initialize the full-screen manager.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
event_bus: Event bus for modal system integration.
|
|
27
|
+
terminal_renderer: Main terminal renderer (for compatibility).
|
|
28
|
+
"""
|
|
29
|
+
self.event_bus = event_bus
|
|
30
|
+
self.terminal_renderer = terminal_renderer
|
|
31
|
+
|
|
32
|
+
# Plugin registry
|
|
33
|
+
self.plugins: Dict[str, FullScreenPlugin] = {}
|
|
34
|
+
self.plugin_aliases: Dict[str, str] = {}
|
|
35
|
+
|
|
36
|
+
# Session management
|
|
37
|
+
self.current_session: Optional[FullScreenSession] = None
|
|
38
|
+
self.session_history: List[Dict[str, Any]] = []
|
|
39
|
+
|
|
40
|
+
logger.info("FullScreenManager initialized")
|
|
41
|
+
|
|
42
|
+
def register_plugin(self, plugin: FullScreenPlugin) -> bool:
|
|
43
|
+
"""Register a full-screen plugin.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
plugin: The plugin to register.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
True if registration was successful, False otherwise.
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
if plugin.name in self.plugins:
|
|
53
|
+
logger.warning(f"Plugin {plugin.name} already registered, overriding")
|
|
54
|
+
|
|
55
|
+
# Register main name
|
|
56
|
+
self.plugins[plugin.name] = plugin
|
|
57
|
+
|
|
58
|
+
# Register aliases
|
|
59
|
+
for alias in plugin.metadata.aliases:
|
|
60
|
+
if alias in self.plugin_aliases:
|
|
61
|
+
logger.warning(f"Alias {alias} already registered, overriding")
|
|
62
|
+
self.plugin_aliases[alias] = plugin.name
|
|
63
|
+
|
|
64
|
+
logger.info(f"Registered plugin: {plugin.name} with {len(plugin.metadata.aliases)} aliases")
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.error(f"Failed to register plugin {plugin.name}: {e}")
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
def unregister_plugin(self, name: str) -> bool:
|
|
72
|
+
"""Unregister a plugin.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
name: Name of the plugin to unregister.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
True if unregistration was successful, False otherwise.
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
if name not in self.plugins:
|
|
82
|
+
logger.warning(f"Plugin {name} not found for unregistration")
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
plugin = self.plugins[name]
|
|
86
|
+
|
|
87
|
+
# Remove aliases
|
|
88
|
+
for alias in plugin.metadata.aliases:
|
|
89
|
+
if alias in self.plugin_aliases:
|
|
90
|
+
del self.plugin_aliases[alias]
|
|
91
|
+
|
|
92
|
+
# Remove main registration
|
|
93
|
+
del self.plugins[name]
|
|
94
|
+
|
|
95
|
+
logger.info(f"Unregistered plugin: {name}")
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.error(f"Failed to unregister plugin {name}: {e}")
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
def get_plugin(self, name: str) -> Optional[FullScreenPlugin]:
|
|
103
|
+
"""Get a plugin by name or alias.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
name: Plugin name or alias.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Plugin instance if found, None otherwise.
|
|
110
|
+
"""
|
|
111
|
+
# Check direct name first
|
|
112
|
+
if name in self.plugins:
|
|
113
|
+
return self.plugins[name]
|
|
114
|
+
|
|
115
|
+
# Check aliases
|
|
116
|
+
if name in self.plugin_aliases:
|
|
117
|
+
actual_name = self.plugin_aliases[name]
|
|
118
|
+
return self.plugins.get(actual_name)
|
|
119
|
+
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
def list_plugins(self) -> List[PluginMetadata]:
|
|
123
|
+
"""List all registered plugins.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of plugin metadata.
|
|
127
|
+
"""
|
|
128
|
+
return [plugin.metadata for plugin in self.plugins.values()]
|
|
129
|
+
|
|
130
|
+
async def launch_plugin(self, name: str, **kwargs) -> bool:
|
|
131
|
+
"""Launch a full-screen plugin with modal integration.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
name: Plugin name or alias to launch.
|
|
135
|
+
**kwargs: Additional arguments for plugin configuration.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
True if plugin launched successfully, False otherwise.
|
|
139
|
+
"""
|
|
140
|
+
try:
|
|
141
|
+
# Check if session is already active
|
|
142
|
+
if self.current_session and self.current_session.running:
|
|
143
|
+
logger.warning("Cannot launch plugin - session already active")
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
# Get plugin
|
|
147
|
+
plugin = self.get_plugin(name)
|
|
148
|
+
if not plugin:
|
|
149
|
+
logger.error(f"Plugin not found: {name}")
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
logger.info(f"Launching full-screen plugin: {plugin.name}")
|
|
153
|
+
|
|
154
|
+
# Enter modal mode using the proven modal system
|
|
155
|
+
await self._enter_modal_mode(plugin)
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
# Create and run session
|
|
159
|
+
logger.info(f"Creating session for plugin: {plugin.name}")
|
|
160
|
+
self.current_session = FullScreenSession(plugin, self.event_bus, **kwargs)
|
|
161
|
+
logger.info(f"Running session for plugin: {plugin.name}")
|
|
162
|
+
success = await self.current_session.run()
|
|
163
|
+
logger.info(f"Session completed with success: {success}")
|
|
164
|
+
|
|
165
|
+
# Record session in history
|
|
166
|
+
self._record_session(plugin, success)
|
|
167
|
+
|
|
168
|
+
return success
|
|
169
|
+
|
|
170
|
+
finally:
|
|
171
|
+
# Always exit modal mode
|
|
172
|
+
await self._exit_modal_mode(plugin)
|
|
173
|
+
self.current_session = None
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.error(f"Failed to launch plugin {name}: {e}")
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
async def stop_current_session(self) -> bool:
|
|
180
|
+
"""Stop the currently running session.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
True if session was stopped, False if no session active.
|
|
184
|
+
"""
|
|
185
|
+
if not self.current_session:
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
self.current_session.stop()
|
|
190
|
+
return True
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.error(f"Error stopping session: {e}")
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
def is_session_active(self) -> bool:
|
|
196
|
+
"""Check if a full-screen session is currently active.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
True if session is active, False otherwise.
|
|
200
|
+
"""
|
|
201
|
+
return (self.current_session is not None and
|
|
202
|
+
self.current_session.running)
|
|
203
|
+
|
|
204
|
+
def get_current_plugin(self) -> Optional[FullScreenPlugin]:
|
|
205
|
+
"""Get the currently running plugin.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Current plugin if session is active, None otherwise.
|
|
209
|
+
"""
|
|
210
|
+
if self.current_session:
|
|
211
|
+
return self.current_session.plugin
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
async def _enter_modal_mode(self, plugin: FullScreenPlugin):
|
|
215
|
+
"""Enter modal mode for full-screen plugin.
|
|
216
|
+
|
|
217
|
+
Uses the same modal system as /config command for consistent behavior.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
plugin: The plugin being launched.
|
|
221
|
+
"""
|
|
222
|
+
try:
|
|
223
|
+
# Emit MODAL_TRIGGER event (same as Matrix effect)
|
|
224
|
+
await self.event_bus.emit_with_hooks(
|
|
225
|
+
EventType.MODAL_TRIGGER,
|
|
226
|
+
{
|
|
227
|
+
"trigger_source": "fullscreen_plugin",
|
|
228
|
+
"plugin_name": plugin.name,
|
|
229
|
+
"mode": "fullscreen",
|
|
230
|
+
"fullscreen_plugin": True
|
|
231
|
+
},
|
|
232
|
+
"fullscreen_manager"
|
|
233
|
+
)
|
|
234
|
+
logger.info(f"🎯 Entered modal mode for plugin: {plugin.name}")
|
|
235
|
+
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error(f"Failed to enter modal mode for {plugin.name}: {e}")
|
|
238
|
+
raise
|
|
239
|
+
|
|
240
|
+
async def _exit_modal_mode(self, plugin: FullScreenPlugin):
|
|
241
|
+
"""Exit modal mode after plugin finishes.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
plugin: The plugin that finished.
|
|
245
|
+
"""
|
|
246
|
+
try:
|
|
247
|
+
# Emit MODAL_HIDE event
|
|
248
|
+
await self.event_bus.emit_with_hooks(
|
|
249
|
+
EventType.MODAL_HIDE,
|
|
250
|
+
{
|
|
251
|
+
"source": "fullscreen_plugin",
|
|
252
|
+
"plugin_name": plugin.name,
|
|
253
|
+
"completed": True
|
|
254
|
+
},
|
|
255
|
+
"fullscreen_manager"
|
|
256
|
+
)
|
|
257
|
+
logger.info(f"🔄 Exited modal mode for plugin: {plugin.name}")
|
|
258
|
+
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.error(f"Failed to exit modal mode for {plugin.name}: {e}")
|
|
261
|
+
|
|
262
|
+
def _record_session(self, plugin: FullScreenPlugin, success: bool):
|
|
263
|
+
"""Record session in history.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
plugin: The plugin that ran.
|
|
267
|
+
success: Whether the session completed successfully.
|
|
268
|
+
"""
|
|
269
|
+
session_record = {
|
|
270
|
+
"plugin_name": plugin.name,
|
|
271
|
+
"success": success,
|
|
272
|
+
"stats": self.current_session.get_stats() if self.current_session else None,
|
|
273
|
+
"timestamp": asyncio.get_event_loop().time()
|
|
274
|
+
}
|
|
275
|
+
self.session_history.append(session_record)
|
|
276
|
+
|
|
277
|
+
# Keep only last 100 sessions
|
|
278
|
+
if len(self.session_history) > 100:
|
|
279
|
+
self.session_history = self.session_history[-100:]
|
|
280
|
+
|
|
281
|
+
def get_session_history(self) -> List[Dict[str, Any]]:
|
|
282
|
+
"""Get session execution history.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
List of session records.
|
|
286
|
+
"""
|
|
287
|
+
return self.session_history.copy()
|
|
288
|
+
|
|
289
|
+
def get_manager_stats(self) -> Dict[str, Any]:
|
|
290
|
+
"""Get manager statistics.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Dictionary with manager statistics.
|
|
294
|
+
"""
|
|
295
|
+
return {
|
|
296
|
+
"plugins_registered": len(self.plugins),
|
|
297
|
+
"aliases_registered": len(self.plugin_aliases),
|
|
298
|
+
"session_active": self.is_session_active(),
|
|
299
|
+
"current_plugin": self.get_current_plugin().name if self.get_current_plugin() else None,
|
|
300
|
+
"total_sessions": len(self.session_history),
|
|
301
|
+
"successful_sessions": sum(1 for s in self.session_history if s["success"])
|
|
302
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Base class for full-screen plugins."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
|
|
9
|
+
from ..io.key_parser import KeyPress
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class PluginMetadata:
|
|
16
|
+
"""Metadata for a full-screen plugin."""
|
|
17
|
+
name: str
|
|
18
|
+
description: str = ""
|
|
19
|
+
version: str = "1.0.0"
|
|
20
|
+
author: str = ""
|
|
21
|
+
category: str = "general"
|
|
22
|
+
icon: str = ""
|
|
23
|
+
aliases: list = field(default_factory=list)
|
|
24
|
+
settings: Dict[str, Any] = field(default_factory=dict)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FullScreenPlugin(ABC):
|
|
28
|
+
"""Base class for all full-screen plugins.
|
|
29
|
+
|
|
30
|
+
This class provides the standard interface that all full-screen plugins
|
|
31
|
+
must implement. It handles the lifecycle, rendering, and input management
|
|
32
|
+
for plugins that take complete terminal control.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, metadata: PluginMetadata):
|
|
36
|
+
"""Initialize the full-screen plugin.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
metadata: Plugin metadata including name, description, etc.
|
|
40
|
+
"""
|
|
41
|
+
self.metadata = metadata
|
|
42
|
+
self.renderer: Optional['FullScreenRenderer'] = None
|
|
43
|
+
self.running = False
|
|
44
|
+
self.initialized = False
|
|
45
|
+
|
|
46
|
+
# Plugin state
|
|
47
|
+
self.start_time = 0.0
|
|
48
|
+
self.frame_count = 0
|
|
49
|
+
self.last_frame_time = 0.0
|
|
50
|
+
|
|
51
|
+
logger.info(f"Initialized full-screen plugin: {metadata.name}")
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def name(self) -> str:
|
|
55
|
+
"""Get the plugin name."""
|
|
56
|
+
return self.metadata.name
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def description(self) -> str:
|
|
60
|
+
"""Get the plugin description."""
|
|
61
|
+
return self.metadata.description
|
|
62
|
+
|
|
63
|
+
async def initialize(self, renderer: 'FullScreenRenderer') -> bool:
|
|
64
|
+
"""Initialize the plugin with a renderer.
|
|
65
|
+
|
|
66
|
+
This method is called once when the plugin is loaded. Override
|
|
67
|
+
this method to set up any resources your plugin needs.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
renderer: The full-screen renderer instance.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if initialization was successful, False otherwise.
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
self.renderer = renderer
|
|
77
|
+
self.initialized = True
|
|
78
|
+
logger.info(f"Plugin {self.name} initialized successfully")
|
|
79
|
+
return True
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.error(f"Failed to initialize plugin {self.name}: {e}")
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
async def render_frame(self, delta_time: float) -> bool:
|
|
86
|
+
"""Render a single frame.
|
|
87
|
+
|
|
88
|
+
This method is called every frame while the plugin is active.
|
|
89
|
+
Override this method to implement your plugin's visual output.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
delta_time: Time elapsed since last frame in seconds.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
True to continue running, False to exit the plugin.
|
|
96
|
+
"""
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
async def handle_input(self, key_press: KeyPress) -> bool:
|
|
101
|
+
"""Handle user input.
|
|
102
|
+
|
|
103
|
+
This method is called whenever the user presses a key while
|
|
104
|
+
your plugin is active. Override this method to handle input.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
key_press: The key that was pressed.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
True to exit the plugin, False to continue running.
|
|
111
|
+
"""
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
async def on_start(self):
|
|
115
|
+
"""Called when the plugin starts running.
|
|
116
|
+
|
|
117
|
+
Override this method to perform any setup that should happen
|
|
118
|
+
when the plugin begins execution (after initialization).
|
|
119
|
+
"""
|
|
120
|
+
self.running = True
|
|
121
|
+
self.start_time = asyncio.get_event_loop().time()
|
|
122
|
+
self.frame_count = 0
|
|
123
|
+
logger.info(f"Plugin {self.name} started")
|
|
124
|
+
|
|
125
|
+
async def on_stop(self):
|
|
126
|
+
"""Called when the plugin stops running.
|
|
127
|
+
|
|
128
|
+
Override this method to perform any cleanup that should happen
|
|
129
|
+
when the plugin finishes execution.
|
|
130
|
+
"""
|
|
131
|
+
self.running = False
|
|
132
|
+
logger.info(f"Plugin {self.name} stopped")
|
|
133
|
+
|
|
134
|
+
async def cleanup(self):
|
|
135
|
+
"""Clean up plugin resources.
|
|
136
|
+
|
|
137
|
+
This method is called when the plugin is being unloaded.
|
|
138
|
+
Override this method to clean up any resources your plugin allocated.
|
|
139
|
+
"""
|
|
140
|
+
self.initialized = False
|
|
141
|
+
self.renderer = None
|
|
142
|
+
logger.info(f"Plugin {self.name} cleaned up")
|
|
143
|
+
|
|
144
|
+
def get_runtime_stats(self) -> Dict[str, Any]:
|
|
145
|
+
"""Get runtime statistics for the plugin.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Dictionary with runtime statistics.
|
|
149
|
+
"""
|
|
150
|
+
current_time = asyncio.get_event_loop().time()
|
|
151
|
+
runtime = current_time - self.start_time if self.running else 0
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
"name": self.name,
|
|
155
|
+
"running": self.running,
|
|
156
|
+
"initialized": self.initialized,
|
|
157
|
+
"runtime_seconds": runtime,
|
|
158
|
+
"frame_count": self.frame_count,
|
|
159
|
+
"fps": self.frame_count / runtime if runtime > 0 else 0
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
def update_frame_stats(self):
|
|
163
|
+
"""Update frame statistics. Called by the framework."""
|
|
164
|
+
self.frame_count += 1
|
|
165
|
+
self.last_frame_time = asyncio.get_event_loop().time()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class ExamplePlugin(FullScreenPlugin):
|
|
169
|
+
"""Example plugin implementation for reference."""
|
|
170
|
+
|
|
171
|
+
def __init__(self):
|
|
172
|
+
"""Initialize the example plugin."""
|
|
173
|
+
metadata = PluginMetadata(
|
|
174
|
+
name="example",
|
|
175
|
+
description="Example full-screen plugin for demonstration",
|
|
176
|
+
author="Framework",
|
|
177
|
+
category="demo"
|
|
178
|
+
)
|
|
179
|
+
super().__init__(metadata)
|
|
180
|
+
|
|
181
|
+
async def render_frame(self, delta_time: float) -> bool:
|
|
182
|
+
"""Render example content."""
|
|
183
|
+
if not self.renderer:
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
# Clear screen and show example content
|
|
187
|
+
self.renderer.clear_screen()
|
|
188
|
+
|
|
189
|
+
# Center message
|
|
190
|
+
width, height = self.renderer.get_terminal_size()
|
|
191
|
+
message = f"Example Plugin - Frame {self.frame_count}"
|
|
192
|
+
x = (width - len(message)) // 2
|
|
193
|
+
y = height // 2
|
|
194
|
+
|
|
195
|
+
self.renderer.write_at(x, y, message)
|
|
196
|
+
self.renderer.write_at(x - 5, y + 2, "Press 'q' or ESC to exit")
|
|
197
|
+
|
|
198
|
+
self.update_frame_stats()
|
|
199
|
+
return True
|
|
200
|
+
|
|
201
|
+
async def handle_input(self, key_press: KeyPress) -> bool:
|
|
202
|
+
"""Handle input for example plugin."""
|
|
203
|
+
# Exit on 'q' or ESC
|
|
204
|
+
return key_press.char in ['q', '\x1b'] or key_press.name == "Escape"
|