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,502 @@
1
+ """Save conversation plugin for exporting chat transcripts.
2
+
3
+ Provides /save command to export conversations to file or clipboard
4
+ in various formats (transcript, markdown, jsonl).
5
+ """
6
+
7
+ import logging
8
+ import subprocess
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+ from typing import Any, Dict, Optional
12
+
13
+ from core.events.models import CommandDefinition, CommandCategory, CommandMode
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class SaveConversationPlugin:
19
+ """Plugin for saving conversations to file or clipboard."""
20
+
21
+ def __init__(self, name: str = "save_conversation", state_manager=None, event_bus=None,
22
+ renderer=None, config=None) -> None:
23
+ """Initialize the save conversation plugin.
24
+
25
+ Args:
26
+ name: Plugin name (default: "save_conversation")
27
+ state_manager: State manager instance
28
+ event_bus: Event bus instance
29
+ renderer: Terminal renderer instance
30
+ config: Configuration manager instance
31
+ """
32
+ self.name = name
33
+ self.version = "1.0.0"
34
+ self.description = "Save conversations to file or clipboard"
35
+ self.enabled = True
36
+ self.logger = logger
37
+
38
+ # Store injected dependencies
39
+ self.state_manager = state_manager
40
+ self.event_bus = event_bus
41
+ self.renderer = renderer
42
+ self.config_manager = config
43
+
44
+ # References to be set during initialize()
45
+ self.command_registry = None
46
+ self.llm_service = None
47
+ self.config = None
48
+
49
+ async def initialize(self, event_bus, config, **kwargs) -> None:
50
+ """Initialize the plugin and register commands.
51
+
52
+ Args:
53
+ event_bus: Application event bus.
54
+ config: Configuration manager.
55
+ **kwargs: Additional initialization parameters.
56
+ """
57
+ try:
58
+ self.config = config
59
+ self.command_registry = kwargs.get('command_registry')
60
+ self.llm_service = kwargs.get('llm_service')
61
+
62
+ if not self.command_registry:
63
+ self.logger.warning("No command registry provided, /save not registered")
64
+ return
65
+
66
+ if not self.llm_service:
67
+ self.logger.warning("No LLM service provided, /save may not work")
68
+
69
+ # Register the /save command
70
+ self._register_commands()
71
+
72
+ self.logger.info("Save conversation plugin initialized successfully")
73
+
74
+ except Exception as e:
75
+ self.logger.error(f"Error initializing save conversation plugin: {e}")
76
+ raise
77
+
78
+ def _register_commands(self) -> None:
79
+ """Register all plugin commands."""
80
+ save_command = CommandDefinition(
81
+ name="save",
82
+ description="Save conversation to file or clipboard",
83
+ handler=self._handle_save_command,
84
+ plugin_name=self.name,
85
+ aliases=["export", "transcript"],
86
+ mode=CommandMode.INSTANT,
87
+ category=CommandCategory.CONVERSATION,
88
+ icon="[SAVE]"
89
+ )
90
+
91
+ self.command_registry.register_command(save_command)
92
+ self.logger.info("Registered /save command")
93
+
94
+ async def _handle_save_command(self, command) -> str:
95
+ """Handle the /save command.
96
+
97
+ Args:
98
+ command: SlashCommand object with parsed command data.
99
+
100
+ Returns:
101
+ Status message about the save operation.
102
+ """
103
+ try:
104
+ # Parse arguments: /save [format] [destination]
105
+ # Formats: transcript (default), markdown, jsonl
106
+ # Destinations: file (default), clipboard, both
107
+
108
+ args = command.args if hasattr(command, 'args') else []
109
+
110
+ # Get configuration
111
+ save_format = self.config.get("plugins.save_conversation.default_format", "transcript")
112
+ save_to = self.config.get("plugins.save_conversation.default_destination", "file")
113
+ auto_timestamp = self.config.get("plugins.save_conversation.auto_timestamp", True)
114
+ output_dir = self.config.get("plugins.save_conversation.output_directory", "logs/transcripts")
115
+
116
+ # Parse command arguments
117
+ if len(args) >= 1:
118
+ save_format = args[0].lower()
119
+ if len(args) >= 2:
120
+ save_to = args[1].lower()
121
+
122
+ # Validate format
123
+ if save_format not in ["transcript", "markdown", "jsonl", "raw"]:
124
+ return f"Error: Invalid format '{save_format}'. Use: transcript, markdown, jsonl, or raw"
125
+
126
+ # Validate destination
127
+ if save_to not in ["file", "clipboard", "both"]:
128
+ return f"Error: Invalid destination '{save_to}'. Use: file, clipboard, or both"
129
+
130
+ # Get conversation content
131
+ if not self.llm_service:
132
+ return "Error: LLM service not available"
133
+
134
+ # Get messages from llm_service conversation_history
135
+ conversation_history = self.llm_service.conversation_history
136
+ if not conversation_history:
137
+ return "No conversation to save"
138
+
139
+ # Convert ConversationMessage objects to dict format
140
+ # Preserves EXACT content as sent to/received from API
141
+ messages = []
142
+ for msg in conversation_history:
143
+ msg_dict = {
144
+ "role": msg.role,
145
+ "content": msg.content, # Exact content - no processing
146
+ }
147
+ # Use actual timestamp from message if available
148
+ if hasattr(msg, 'timestamp') and msg.timestamp:
149
+ msg_dict["timestamp"] = msg.timestamp.isoformat() if hasattr(msg.timestamp, 'isoformat') else str(msg.timestamp)
150
+ else:
151
+ msg_dict["timestamp"] = datetime.now().isoformat()
152
+
153
+ # Include thinking if present (for debugging)
154
+ if hasattr(msg, 'thinking') and msg.thinking:
155
+ msg_dict["thinking"] = msg.thinking
156
+
157
+ messages.append(msg_dict)
158
+
159
+ # Format the conversation
160
+ formatted_content = self._format_conversation(messages, save_format)
161
+
162
+ # Save to file
163
+ saved_path = None
164
+ if save_to in ["file", "both"]:
165
+ saved_path = self._save_to_file(formatted_content, output_dir, save_format, auto_timestamp)
166
+
167
+ # Copy to clipboard
168
+ if save_to in ["clipboard", "both"]:
169
+ self._copy_to_clipboard(formatted_content)
170
+
171
+ # Return status message
172
+ if save_to == "both":
173
+ return f"Conversation saved to {saved_path} and copied to clipboard"
174
+ elif save_to == "clipboard":
175
+ return f"Conversation copied to clipboard ({len(messages)} messages)"
176
+ else:
177
+ return f"Conversation saved to {saved_path}"
178
+
179
+ except Exception as e:
180
+ self.logger.error(f"Error handling /save command: {e}")
181
+ return f"Error saving conversation: {str(e)}"
182
+
183
+ def _format_conversation(self, messages, format_type: str) -> str:
184
+ """Format conversation messages based on requested format.
185
+
186
+ Args:
187
+ messages: List of conversation messages.
188
+ format_type: Format type (transcript, markdown, jsonl, raw).
189
+
190
+ Returns:
191
+ Formatted conversation string.
192
+ """
193
+ if format_type == "raw":
194
+ return self._format_as_raw_api(messages)
195
+ elif format_type == "transcript":
196
+ return self._format_as_transcript(messages)
197
+ elif format_type == "markdown":
198
+ return self._format_as_markdown(messages)
199
+ elif format_type == "jsonl":
200
+ return self._format_as_jsonl(messages)
201
+ else:
202
+ return self._format_as_transcript(messages)
203
+
204
+ def _format_as_transcript(self, messages) -> str:
205
+ """Format messages as raw transcript.
206
+
207
+ Args:
208
+ messages: List of conversation messages.
209
+
210
+ Returns:
211
+ Transcript formatted string.
212
+ """
213
+ lines = []
214
+
215
+ for msg in messages:
216
+ role = msg.get("role", "unknown")
217
+ content = msg.get("content", "")
218
+
219
+ # Map role to section header
220
+ if role == "system":
221
+ lines.append("--- system_prompt ---")
222
+ elif role == "user":
223
+ lines.append("\n--- user ---")
224
+ elif role == "assistant":
225
+ lines.append("\n--- llm ---")
226
+ else:
227
+ lines.append(f"\n--- {role} ---")
228
+
229
+ lines.append(content)
230
+
231
+ return "\n".join(lines)
232
+
233
+ def _format_as_markdown(self, messages) -> str:
234
+ """Format messages as markdown.
235
+
236
+ Args:
237
+ messages: List of conversation messages.
238
+
239
+ Returns:
240
+ Markdown formatted string.
241
+ """
242
+ lines = ["# Conversation Transcript", ""]
243
+
244
+ # Add metadata
245
+ if messages:
246
+ first_timestamp = messages[0].get("timestamp", "")
247
+ last_timestamp = messages[-1].get("timestamp", "")
248
+ lines.append(f"**Started:** {first_timestamp}")
249
+ lines.append(f"**Ended:** {last_timestamp}")
250
+ lines.append(f"**Messages:** {len(messages)}")
251
+ lines.append("")
252
+ lines.append("---")
253
+ lines.append("")
254
+
255
+ for i, msg in enumerate(messages):
256
+ role = msg.get("role", "unknown")
257
+ content = msg.get("content", "")
258
+ timestamp = msg.get("timestamp", "")
259
+
260
+ # Format based on role
261
+ if role == "system":
262
+ lines.append("## System Prompt")
263
+ lines.append("")
264
+ lines.append(f"```\n{content}\n```")
265
+ elif role == "user":
266
+ lines.append(f"## User Message {i+1}")
267
+ if timestamp:
268
+ lines.append(f"*{timestamp}*")
269
+ lines.append("")
270
+ lines.append(content)
271
+ elif role == "assistant":
272
+ lines.append(f"## Assistant Response {i+1}")
273
+ if timestamp:
274
+ lines.append(f"*{timestamp}*")
275
+ lines.append("")
276
+ lines.append(content)
277
+ else:
278
+ lines.append(f"## {role.title()} {i+1}")
279
+ lines.append("")
280
+ lines.append(content)
281
+
282
+ lines.append("")
283
+ lines.append("---")
284
+ lines.append("")
285
+
286
+ return "\n".join(lines)
287
+
288
+ def _format_as_jsonl(self, messages) -> str:
289
+ """Format messages as JSONL (JSON Lines).
290
+
291
+ Args:
292
+ messages: List of conversation messages.
293
+
294
+ Returns:
295
+ JSONL formatted string.
296
+ """
297
+ import json
298
+ lines = []
299
+
300
+ for msg in messages:
301
+ lines.append(json.dumps(msg))
302
+
303
+ return "\n".join(lines)
304
+
305
+ def _format_as_raw_api(self, messages) -> str:
306
+ """Format messages as exact API payload JSON.
307
+
308
+ This format mirrors EXACTLY what is sent to and received from the LLM API.
309
+ Useful for debugging, replay, and verification.
310
+
311
+ Args:
312
+ messages: List of conversation messages.
313
+
314
+ Returns:
315
+ JSON formatted string matching API payload structure.
316
+ """
317
+ import json
318
+
319
+ # Build the exact payload structure sent to the API
320
+ api_messages = []
321
+ for msg in messages:
322
+ api_messages.append({
323
+ "role": msg.get("role"),
324
+ "content": msg.get("content")
325
+ })
326
+
327
+ # Get model info from config if available
328
+ model = "unknown"
329
+ temperature = 0.7
330
+ if self.config:
331
+ model = self.config.get("core.llm.model", "unknown")
332
+ temperature = self.config.get("core.llm.temperature", 0.7)
333
+
334
+ payload = {
335
+ "model": model,
336
+ "messages": api_messages,
337
+ "temperature": temperature,
338
+ "metadata": {
339
+ "exported_at": datetime.now().isoformat(),
340
+ "message_count": len(messages),
341
+ "format": "raw_api_payload"
342
+ }
343
+ }
344
+
345
+ return json.dumps(payload, indent=2, ensure_ascii=False)
346
+
347
+ def _save_to_file(self, content: str, output_dir: str, format_type: str, auto_timestamp: bool) -> Path:
348
+ """Save content to file.
349
+
350
+ Args:
351
+ content: Content to save.
352
+ output_dir: Output directory path.
353
+ format_type: Format type for file extension.
354
+ auto_timestamp: Whether to add timestamp to filename.
355
+
356
+ Returns:
357
+ Path to saved file.
358
+ """
359
+ # Create output directory
360
+ from core.utils.config_utils import get_config_directory
361
+ config_dir = get_config_directory()
362
+
363
+ # Handle relative paths
364
+ if not output_dir.startswith('/'):
365
+ save_dir = config_dir / output_dir
366
+ else:
367
+ save_dir = Path(output_dir)
368
+
369
+ save_dir.mkdir(parents=True, exist_ok=True)
370
+
371
+ # Generate filename
372
+ if auto_timestamp:
373
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
374
+ filename = f"conversation_{timestamp}"
375
+ else:
376
+ filename = "conversation"
377
+
378
+ # Add extension based on format
379
+ if format_type == "raw":
380
+ filename += ".json"
381
+ elif format_type == "jsonl":
382
+ filename += ".jsonl"
383
+ elif format_type == "markdown":
384
+ filename += ".md"
385
+ else:
386
+ filename += ".txt"
387
+
388
+ filepath = save_dir / filename
389
+
390
+ # Write to file
391
+ filepath.write_text(content, encoding='utf-8')
392
+
393
+ self.logger.info(f"Saved conversation to: {filepath}")
394
+ return filepath
395
+
396
+ def _copy_to_clipboard(self, content: str) -> bool:
397
+ """Copy content to system clipboard.
398
+
399
+ Args:
400
+ content: Content to copy.
401
+
402
+ Returns:
403
+ True if successful, False otherwise.
404
+ """
405
+ try:
406
+ # Try pbcopy (macOS)
407
+ try:
408
+ process = subprocess.Popen(
409
+ ['pbcopy'],
410
+ stdin=subprocess.PIPE,
411
+ stdout=subprocess.PIPE,
412
+ stderr=subprocess.PIPE
413
+ )
414
+ process.communicate(input=content.encode('utf-8'))
415
+ self.logger.info("Copied to clipboard using pbcopy")
416
+ return True
417
+ except FileNotFoundError:
418
+ pass
419
+
420
+ # Try xclip (Linux)
421
+ try:
422
+ process = subprocess.Popen(
423
+ ['xclip', '-selection', 'clipboard'],
424
+ stdin=subprocess.PIPE,
425
+ stdout=subprocess.PIPE,
426
+ stderr=subprocess.PIPE
427
+ )
428
+ process.communicate(input=content.encode('utf-8'))
429
+ self.logger.info("Copied to clipboard using xclip")
430
+ return True
431
+ except FileNotFoundError:
432
+ pass
433
+
434
+ # Try xsel (Linux alternative)
435
+ try:
436
+ process = subprocess.Popen(
437
+ ['xsel', '--clipboard', '--input'],
438
+ stdin=subprocess.PIPE,
439
+ stdout=subprocess.PIPE,
440
+ stderr=subprocess.PIPE
441
+ )
442
+ process.communicate(input=content.encode('utf-8'))
443
+ self.logger.info("Copied to clipboard using xsel")
444
+ return True
445
+ except FileNotFoundError:
446
+ pass
447
+
448
+ # Try wl-copy (Wayland)
449
+ try:
450
+ process = subprocess.Popen(
451
+ ['wl-copy'],
452
+ stdin=subprocess.PIPE,
453
+ stdout=subprocess.PIPE,
454
+ stderr=subprocess.PIPE
455
+ )
456
+ process.communicate(input=content.encode('utf-8'))
457
+ self.logger.info("Copied to clipboard using wl-copy")
458
+ return True
459
+ except FileNotFoundError:
460
+ pass
461
+
462
+ self.logger.warning("No clipboard utility found (pbcopy, xclip, xsel, wl-copy)")
463
+ return False
464
+
465
+ except Exception as e:
466
+ self.logger.error(f"Error copying to clipboard: {e}")
467
+ return False
468
+
469
+ async def shutdown(self) -> None:
470
+ """Shutdown the plugin and cleanup resources."""
471
+ try:
472
+ self.logger.info("Save conversation plugin shutdown completed")
473
+ except Exception as e:
474
+ self.logger.error(f"Error shutting down save conversation plugin: {e}")
475
+
476
+ async def register_hooks(self) -> None:
477
+ """Register event hooks for the plugin."""
478
+ # This plugin doesn't need hooks, just commands
479
+ pass
480
+
481
+ def get_status_line(self) -> str:
482
+ """Get status line information for the plugin.
483
+
484
+ Returns:
485
+ Status line string.
486
+ """
487
+ return "Save: /save"
488
+
489
+ @staticmethod
490
+ def get_default_config() -> Dict[str, Any]:
491
+ """Get default configuration for save conversation plugin."""
492
+ return {
493
+ "plugins": {
494
+ "save_conversation": {
495
+ "enabled": True,
496
+ "default_format": "transcript",
497
+ "default_destination": "file",
498
+ "auto_timestamp": True,
499
+ "output_directory": "logs/transcripts"
500
+ }
501
+ }
502
+ }
@@ -0,0 +1,93 @@
1
+ """System commands plugin for core application functionality."""
2
+
3
+ import logging
4
+ from typing import Dict, Any
5
+ from core.commands.system_commands import SystemCommandsPlugin as CoreSystemCommandsPlugin
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class SystemCommandsPlugin:
11
+ """Plugin wrapper for system commands integration.
12
+
13
+ Provides the system commands as a plugin that gets loaded
14
+ during application initialization.
15
+ """
16
+
17
+ def __init__(self) -> None:
18
+ """Initialize the system commands plugin wrapper."""
19
+ self.name = "system_commands"
20
+ self.version = "1.0.0"
21
+ self.description = "Core system commands (/help, /config, /status, etc.)"
22
+ self.enabled = True
23
+ self.system_commands = None
24
+ self.logger = logger
25
+
26
+ async def initialize(self, event_bus, config, **kwargs) -> None:
27
+ """Initialize the plugin and register system commands.
28
+
29
+ Args:
30
+ event_bus: Application event bus.
31
+ config: Configuration manager.
32
+ **kwargs: Additional initialization parameters.
33
+ """
34
+ try:
35
+ # Get command registry from input handler if available
36
+ command_registry = kwargs.get('command_registry')
37
+ if not command_registry:
38
+ self.logger.warning("No command registry provided, system commands not registered")
39
+ return
40
+
41
+ # Create and initialize system commands
42
+ self.system_commands = CoreSystemCommandsPlugin(
43
+ command_registry=command_registry,
44
+ event_bus=event_bus,
45
+ config_manager=config
46
+ )
47
+
48
+ # Register all system commands
49
+ self.system_commands.register_commands()
50
+
51
+ self.logger.info("System commands plugin initialized successfully")
52
+
53
+ except Exception as e:
54
+ self.logger.error(f"Error initializing system commands plugin: {e}")
55
+ raise
56
+
57
+ async def shutdown(self) -> None:
58
+ """Shutdown the plugin and cleanup resources."""
59
+ try:
60
+ if self.system_commands:
61
+ # Unregister commands would happen here if needed
62
+ self.logger.info("System commands plugin shutdown completed")
63
+
64
+ except Exception as e:
65
+ self.logger.error(f"Error shutting down system commands plugin: {e}")
66
+
67
+ def get_status_line(self) -> str:
68
+ """Get status line information for the plugin.
69
+
70
+ Returns:
71
+ Status line string.
72
+ """
73
+ if self.system_commands:
74
+ return "System commands active"
75
+ return "System commands inactive"
76
+
77
+ @staticmethod
78
+ def get_default_config() -> Dict[str, Any]:
79
+ """Get default configuration for system commands plugin."""
80
+ return {
81
+ "plugins": {
82
+ "system_commands": {
83
+ "enabled": True
84
+ }
85
+ }
86
+ }
87
+
88
+ async def register_hooks(self) -> None:
89
+ """Register event hooks for the plugin.
90
+
91
+ System commands don't need additional hooks beyond command registration.
92
+ """
93
+ pass