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,331 @@
1
+ """Command registry for slash command management."""
2
+
3
+ import logging
4
+ from typing import Dict, List, Optional, Tuple
5
+ from collections import defaultdict
6
+
7
+ from ..events.models import CommandDefinition, CommandCategory
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class SlashCommandRegistry:
13
+ """Registry for managing slash command definitions.
14
+
15
+ Handles command registration, conflict detection, and organization
16
+ by plugin and category for efficient discovery and execution.
17
+ """
18
+
19
+ def __init__(self) -> None:
20
+ """Initialize the command registry."""
21
+ self._commands: Dict[str, CommandDefinition] = {}
22
+ self._aliases: Dict[str, str] = {} # alias -> command_name mapping
23
+ self._plugin_commands: Dict[str, List[str]] = defaultdict(list)
24
+ self._category_commands: Dict[CommandCategory, List[str]] = defaultdict(list)
25
+ self.logger = logger
26
+
27
+ def register_command(self, command_def: CommandDefinition) -> bool:
28
+ """Register a slash command with the registry.
29
+
30
+ Args:
31
+ command_def: Command definition to register.
32
+
33
+ Returns:
34
+ True if registration successful, False if conflicts exist.
35
+ """
36
+ try:
37
+ # Validate command definition
38
+ validation_errors = self._validate_command_definition(command_def)
39
+ if validation_errors:
40
+ self.logger.error(f"Command validation failed for {command_def.name}: {validation_errors}")
41
+ return False
42
+
43
+ # Check for name conflicts
44
+ if command_def.name in self._commands:
45
+ existing = self._commands[command_def.name]
46
+ self.logger.error(
47
+ f"Command name conflict: '{command_def.name}' already registered by plugin '{existing.plugin_name}'"
48
+ )
49
+ return False
50
+
51
+ # Check for alias conflicts
52
+ for alias in command_def.aliases:
53
+ if alias in self._commands or alias in self._aliases:
54
+ self.logger.error(f"Alias conflict: '{alias}' already in use")
55
+ return False
56
+
57
+ # Register the command
58
+ self._commands[command_def.name] = command_def
59
+
60
+ # Register aliases
61
+ for alias in command_def.aliases:
62
+ self._aliases[alias] = command_def.name
63
+
64
+ # Track by plugin
65
+ self._plugin_commands[command_def.plugin_name].append(command_def.name)
66
+
67
+ # Track by category
68
+ self._category_commands[command_def.category].append(command_def.name)
69
+
70
+ self.logger.info(
71
+ f"Registered command '{command_def.name}' from plugin '{command_def.plugin_name}' "
72
+ f"with {len(command_def.aliases)} aliases"
73
+ )
74
+ return True
75
+
76
+ except Exception as e:
77
+ self.logger.error(f"Error registering command '{command_def.name}': {e}")
78
+ return False
79
+
80
+ def unregister_command(self, command_name: str) -> bool:
81
+ """Unregister a command and its aliases.
82
+
83
+ Args:
84
+ command_name: Name of command to unregister.
85
+
86
+ Returns:
87
+ True if successfully unregistered, False if not found.
88
+ """
89
+ if command_name not in self._commands:
90
+ return False
91
+
92
+ try:
93
+ command_def = self._commands[command_name]
94
+
95
+ # Remove from main registry
96
+ del self._commands[command_name]
97
+
98
+ # Remove aliases
99
+ for alias in command_def.aliases:
100
+ if alias in self._aliases:
101
+ del self._aliases[alias]
102
+
103
+ # Remove from plugin tracking
104
+ if command_def.plugin_name in self._plugin_commands:
105
+ plugin_commands = self._plugin_commands[command_def.plugin_name]
106
+ if command_name in plugin_commands:
107
+ plugin_commands.remove(command_name)
108
+
109
+ # Remove from category tracking
110
+ category_commands = self._category_commands[command_def.category]
111
+ if command_name in category_commands:
112
+ category_commands.remove(command_name)
113
+
114
+ self.logger.info(f"Unregistered command '{command_name}'")
115
+ return True
116
+
117
+ except Exception as e:
118
+ self.logger.error(f"Error unregistering command '{command_name}': {e}")
119
+ return False
120
+
121
+ def unregister_plugin_commands(self, plugin_name: str) -> int:
122
+ """Unregister all commands from a specific plugin.
123
+
124
+ Args:
125
+ plugin_name: Name of plugin whose commands to unregister.
126
+
127
+ Returns:
128
+ Number of commands unregistered.
129
+ """
130
+ if plugin_name not in self._plugin_commands:
131
+ return 0
132
+
133
+ # Get list of commands to unregister (copy to avoid modification during iteration)
134
+ commands_to_remove = list(self._plugin_commands[plugin_name])
135
+
136
+ unregistered_count = 0
137
+ for command_name in commands_to_remove:
138
+ if self.unregister_command(command_name):
139
+ unregistered_count += 1
140
+
141
+ # Clean up empty plugin entry
142
+ if plugin_name in self._plugin_commands and not self._plugin_commands[plugin_name]:
143
+ del self._plugin_commands[plugin_name]
144
+
145
+ self.logger.info(f"Unregistered {unregistered_count} commands from plugin '{plugin_name}'")
146
+ return unregistered_count
147
+
148
+ def get_command(self, name_or_alias: str) -> Optional[CommandDefinition]:
149
+ """Get command definition by name or alias.
150
+
151
+ Args:
152
+ name_or_alias: Command name or alias to look up.
153
+
154
+ Returns:
155
+ Command definition if found, None otherwise.
156
+ """
157
+ # Direct name lookup
158
+ if name_or_alias in self._commands:
159
+ return self._commands[name_or_alias]
160
+
161
+ # Alias lookup
162
+ if name_or_alias in self._aliases:
163
+ command_name = self._aliases[name_or_alias]
164
+ return self._commands.get(command_name)
165
+
166
+ return None
167
+
168
+ def list_commands(self, include_hidden: bool = False) -> List[CommandDefinition]:
169
+ """List all registered commands.
170
+
171
+ Args:
172
+ include_hidden: Whether to include hidden commands.
173
+
174
+ Returns:
175
+ List of command definitions.
176
+ """
177
+ commands = list(self._commands.values())
178
+
179
+ if not include_hidden:
180
+ commands = [cmd for cmd in commands if not cmd.hidden]
181
+
182
+ # Sort by category, then by name
183
+ return sorted(commands, key=lambda cmd: (cmd.category.value, cmd.name))
184
+
185
+ def get_commands_by_plugin(self, plugin_name: str) -> List[CommandDefinition]:
186
+ """Get all commands registered by a specific plugin.
187
+
188
+ Args:
189
+ plugin_name: Name of plugin to get commands for.
190
+
191
+ Returns:
192
+ List of command definitions from the plugin.
193
+ """
194
+ if plugin_name not in self._plugin_commands:
195
+ return []
196
+
197
+ command_names = self._plugin_commands[plugin_name]
198
+ return [self._commands[name] for name in command_names if name in self._commands]
199
+
200
+ def get_commands_by_category(self, category: CommandCategory) -> List[CommandDefinition]:
201
+ """Get all commands in a specific category.
202
+
203
+ Args:
204
+ category: Category to get commands for.
205
+
206
+ Returns:
207
+ List of command definitions in the category.
208
+ """
209
+ if category not in self._category_commands:
210
+ return []
211
+
212
+ command_names = self._category_commands[category]
213
+ return [self._commands[name] for name in command_names if name in self._commands]
214
+
215
+ def get_plugin_categories(self) -> Dict[str, List[CommandCategory]]:
216
+ """Get categories used by each plugin.
217
+
218
+ Returns:
219
+ Dictionary mapping plugin names to their used categories.
220
+ """
221
+ plugin_categories = defaultdict(set)
222
+
223
+ for command_def in self._commands.values():
224
+ plugin_categories[command_def.plugin_name].add(command_def.category)
225
+
226
+ # Convert sets to sorted lists
227
+ return {
228
+ plugin: sorted(categories, key=lambda c: c.value)
229
+ for plugin, categories in plugin_categories.items()
230
+ }
231
+
232
+ def search_commands(self, query: str) -> List[CommandDefinition]:
233
+ """Search commands by name, description, or aliases.
234
+
235
+ Args:
236
+ query: Search query string.
237
+
238
+ Returns:
239
+ List of matching command definitions.
240
+ """
241
+ query_lower = query.lower()
242
+ prefix_matches = []
243
+ substring_matches = []
244
+
245
+ for command_def in self._commands.values():
246
+ # Skip hidden commands in search
247
+ if command_def.hidden:
248
+ continue
249
+
250
+ # Priority 1: Name starts with query (prefix match)
251
+ if command_def.name.lower().startswith(query_lower):
252
+ prefix_matches.append(command_def)
253
+ continue
254
+
255
+ # Priority 2: Alias starts with query (prefix match)
256
+ if any(alias.lower().startswith(query_lower) for alias in command_def.aliases):
257
+ prefix_matches.append(command_def)
258
+ continue
259
+
260
+ # Priority 3: Query is substring of name
261
+ if query_lower in command_def.name.lower():
262
+ substring_matches.append(command_def)
263
+ continue
264
+
265
+ # Priority 4: Query is substring of description
266
+ if query_lower in command_def.description.lower():
267
+ substring_matches.append(command_def)
268
+ continue
269
+
270
+ # Priority 5: Query is substring of alias
271
+ if any(query_lower in alias.lower() for alias in command_def.aliases):
272
+ substring_matches.append(command_def)
273
+ continue
274
+
275
+ # For command menu filtering, only return prefix matches
276
+ # Substring matches are available for full-text search if needed
277
+ matches = prefix_matches if prefix_matches else substring_matches
278
+ return sorted(matches, key=lambda cmd: cmd.name)
279
+
280
+ def get_registry_stats(self) -> Dict[str, int]:
281
+ """Get registry statistics for debugging.
282
+
283
+ Returns:
284
+ Dictionary with registry statistics.
285
+ """
286
+ return {
287
+ "total_commands": len(self._commands),
288
+ "total_aliases": len(self._aliases),
289
+ "plugins": len(self._plugin_commands),
290
+ "categories": len([cat for cat in self._category_commands if self._category_commands[cat]]),
291
+ "hidden_commands": len([cmd for cmd in self._commands.values() if cmd.hidden]),
292
+ "enabled_commands": len([cmd for cmd in self._commands.values() if cmd.enabled])
293
+ }
294
+
295
+ def _validate_command_definition(self, command_def: CommandDefinition) -> List[str]:
296
+ """Validate a command definition for correctness.
297
+
298
+ Args:
299
+ command_def: Command definition to validate.
300
+
301
+ Returns:
302
+ List of validation errors, empty if valid.
303
+ """
304
+ errors = []
305
+
306
+ # Validate name
307
+ if not command_def.name:
308
+ errors.append("Command name cannot be empty")
309
+ elif not command_def.name.replace('-', '').replace('_', '').isalnum():
310
+ errors.append(f"Invalid command name format: {command_def.name}")
311
+
312
+ # Validate description
313
+ if not command_def.description:
314
+ errors.append("Command description cannot be empty")
315
+
316
+ # Validate handler
317
+ if not callable(command_def.handler):
318
+ errors.append("Command handler must be callable")
319
+
320
+ # Validate plugin name
321
+ if not command_def.plugin_name:
322
+ errors.append("Plugin name cannot be empty")
323
+
324
+ # Validate aliases (allow special characters like ?)
325
+ for alias in command_def.aliases:
326
+ if not alias or len(alias.strip()) == 0:
327
+ errors.append(f"Empty alias not allowed")
328
+ elif not (alias.replace('-', '').replace('_', '').isalnum() or alias in ['?']):
329
+ errors.append(f"Invalid alias format: {alias}")
330
+
331
+ return errors