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,267 @@
1
+ """Plugin utility functions for introspection and safe method calling."""
2
+
3
+ import inspect
4
+ import logging
5
+ from typing import Any, Callable, Dict, List, Optional, Type
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ def has_method(obj: Any, method_name: str) -> bool:
11
+ """Check if object has a callable method with given name.
12
+
13
+ Args:
14
+ obj: Object to check.
15
+ method_name: Name of method to look for.
16
+
17
+ Returns:
18
+ True if object has callable method, False otherwise.
19
+
20
+ Example:
21
+ >>> class TestPlugin:
22
+ ... def get_status(self): pass
23
+ >>> plugin = TestPlugin()
24
+ >>> has_method(plugin, "get_status")
25
+ True
26
+ >>> has_method(plugin, "missing_method")
27
+ False
28
+ """
29
+ return hasattr(obj, method_name) and callable(getattr(obj, method_name, None))
30
+
31
+
32
+ def safe_call_method(obj: Any, method_name: str, *args, **kwargs) -> Optional[Any]:
33
+ """Safely call a method on an object with error handling.
34
+
35
+ Args:
36
+ obj: Object to call method on.
37
+ method_name: Name of method to call.
38
+ *args: Positional arguments to pass to method.
39
+ **kwargs: Keyword arguments to pass to method.
40
+
41
+ Returns:
42
+ Method result or None if method doesn't exist or call failed.
43
+
44
+ Example:
45
+ >>> class TestPlugin:
46
+ ... def get_config(self, key="default"):
47
+ ... return {"key": key}
48
+ >>> plugin = TestPlugin()
49
+ >>> safe_call_method(plugin, "get_config", key="test")
50
+ {"key": "test"}
51
+ """
52
+ if not has_method(obj, method_name):
53
+ logger.debug(f"Object {type(obj).__name__} has no method '{method_name}'")
54
+ return None
55
+
56
+ try:
57
+ method = getattr(obj, method_name)
58
+ return method(*args, **kwargs)
59
+ except Exception as e:
60
+ logger.error(f"Failed to call {type(obj).__name__}.{method_name}: {e}")
61
+ return None
62
+
63
+
64
+ def get_plugin_metadata(plugin_class: Type) -> Dict[str, Any]:
65
+ """Extract metadata from a plugin class.
66
+
67
+ Args:
68
+ plugin_class: Plugin class to analyze.
69
+
70
+ Returns:
71
+ Dictionary with plugin metadata.
72
+
73
+ Example:
74
+ >>> class TestPlugin:
75
+ ... '''Test plugin for demo.'''
76
+ ... VERSION = "1.0.0"
77
+ ... def get_default_config(self): return {}
78
+ >>> get_plugin_metadata(TestPlugin)
79
+ {
80
+ "name": "TestPlugin",
81
+ "docstring": "Test plugin for demo.",
82
+ "version": "1.0.0",
83
+ "has_config": True,
84
+ "methods": ["get_default_config"]
85
+ }
86
+ """
87
+ metadata = {
88
+ "name": plugin_class.__name__,
89
+ "docstring": inspect.getdoc(plugin_class) or "",
90
+ "version": getattr(plugin_class, "VERSION", "unknown"),
91
+ "has_config": has_method(plugin_class, "get_default_config"),
92
+ "has_startup_info": has_method(plugin_class, "get_startup_info"),
93
+ "has_status": False,
94
+ "methods": []
95
+ }
96
+
97
+ # Check for common plugin methods
98
+ common_methods = [
99
+ "initialize", "register_hooks", "shutdown",
100
+ "get_default_config", "get_startup_info",
101
+ "get_status_line", "get_status_lines"
102
+ ]
103
+
104
+ for method_name in common_methods:
105
+ if has_method(plugin_class, method_name):
106
+ metadata["methods"].append(method_name)
107
+ if method_name.startswith("get_status"):
108
+ metadata["has_status"] = True
109
+
110
+ return metadata
111
+
112
+
113
+ def validate_plugin_interface(plugin_class: Type, required_methods: List[str] = None) -> Dict[str, Any]:
114
+ """Validate that plugin class implements required interface.
115
+
116
+ Args:
117
+ plugin_class: Plugin class to validate.
118
+ required_methods: List of required method names.
119
+
120
+ Returns:
121
+ Dictionary with validation results.
122
+
123
+ Example:
124
+ >>> class TestPlugin:
125
+ ... def initialize(self): pass
126
+ >>> validate_plugin_interface(TestPlugin, ["initialize", "shutdown"])
127
+ {
128
+ "valid": False,
129
+ "missing_methods": ["shutdown"],
130
+ "has_methods": ["initialize"],
131
+ "errors": []
132
+ }
133
+ """
134
+ if required_methods is None:
135
+ required_methods = ["initialize", "register_hooks", "shutdown"]
136
+
137
+ result = {
138
+ "valid": True,
139
+ "missing_methods": [],
140
+ "has_methods": [],
141
+ "errors": []
142
+ }
143
+
144
+ # Check for required methods
145
+ for method_name in required_methods:
146
+ if has_method(plugin_class, method_name):
147
+ result["has_methods"].append(method_name)
148
+ else:
149
+ result["missing_methods"].append(method_name)
150
+ result["valid"] = False
151
+
152
+ # Check constructor signature
153
+ try:
154
+ init_signature = inspect.signature(plugin_class.__init__)
155
+ params = list(init_signature.parameters.keys())
156
+
157
+ # Skip 'self' parameter
158
+ if params and params[0] == "self":
159
+ params = params[1:]
160
+
161
+ # Common expected parameters for plugins
162
+ expected_params = ["name", "state_manager", "event_bus", "renderer", "config"]
163
+ missing_params = [p for p in expected_params if p not in params]
164
+
165
+ if missing_params:
166
+ result["errors"].append(f"Constructor missing parameters: {missing_params}")
167
+
168
+ except Exception as e:
169
+ result["errors"].append(f"Failed to inspect constructor: {e}")
170
+
171
+ return result
172
+
173
+
174
+ def get_plugin_config_safely(plugin_class: Type) -> Dict[str, Any]:
175
+ """Safely get default configuration from plugin class.
176
+
177
+ Args:
178
+ plugin_class: Plugin class to get config from.
179
+
180
+ Returns:
181
+ Plugin's default configuration or empty dict if unavailable.
182
+ """
183
+ try:
184
+ if has_method(plugin_class, "get_default_config"):
185
+ config = plugin_class.get_default_config()
186
+ if isinstance(config, dict):
187
+ return config
188
+ else:
189
+ logger.warning(f"Plugin {plugin_class.__name__} returned non-dict config: {type(config)}")
190
+ else:
191
+ logger.debug(f"Plugin {plugin_class.__name__} has no get_default_config method")
192
+ except Exception as e:
193
+ logger.error(f"Failed to get config from plugin {plugin_class.__name__}: {e}")
194
+
195
+ return {}
196
+
197
+
198
+ def instantiate_plugin_safely(plugin_class: Type, **kwargs) -> Optional[Any]:
199
+ """Safely instantiate a plugin with error handling.
200
+
201
+ Args:
202
+ plugin_class: Plugin class to instantiate.
203
+ **kwargs: Arguments to pass to plugin constructor.
204
+
205
+ Returns:
206
+ Plugin instance or None if instantiation failed.
207
+ """
208
+ try:
209
+ # Validate that this looks like a plugin class
210
+ if not plugin_class.__name__.endswith('Plugin'):
211
+ logger.warning(f"Class {plugin_class.__name__} doesn't follow plugin naming convention")
212
+
213
+ # Try to instantiate
214
+ instance = plugin_class(**kwargs)
215
+ logger.info(f"Successfully instantiated plugin: {plugin_class.__name__}")
216
+ return instance
217
+
218
+ except Exception as e:
219
+ logger.error(f"Failed to instantiate plugin {plugin_class.__name__}: {e}")
220
+ return None
221
+
222
+
223
+ def collect_plugin_status_safely(plugin_instance: Any, plugin_name: str) -> Dict[str, List[str]]:
224
+ """Safely collect status information from plugin instance.
225
+
226
+ Args:
227
+ plugin_instance: Plugin instance to get status from.
228
+ plugin_name: Name of plugin (for logging).
229
+
230
+ Returns:
231
+ Dictionary with status areas A, B, C containing lists of status lines.
232
+ """
233
+ status_areas = {"A": [], "B": [], "C": []}
234
+
235
+ try:
236
+ # Try new format first: get_status_lines() returning dict
237
+ if has_method(plugin_instance, 'get_status_lines'):
238
+ plugin_status = safe_call_method(plugin_instance, 'get_status_lines')
239
+
240
+ if isinstance(plugin_status, dict):
241
+ # New format: plugin returns dict with areas
242
+ for area, lines in plugin_status.items():
243
+ if area in status_areas:
244
+ if isinstance(lines, list):
245
+ status_areas[area].extend([line for line in lines if line and line.strip()])
246
+ elif isinstance(lines, str) and lines.strip():
247
+ status_areas[area].append(lines.strip())
248
+ return status_areas
249
+ elif isinstance(plugin_status, list):
250
+ # Legacy format: plugin returns list, put in area A
251
+ status_areas["A"].extend([line for line in plugin_status if line and line.strip()])
252
+ return status_areas
253
+ elif isinstance(plugin_status, str) and plugin_status.strip():
254
+ # Legacy format: plugin returns string, put in area A
255
+ status_areas["A"].append(plugin_status.strip())
256
+ return status_areas
257
+
258
+ # Try legacy format: get_status_line() returning string
259
+ if has_method(plugin_instance, 'get_status_line'):
260
+ plugin_status = safe_call_method(plugin_instance, 'get_status_line')
261
+ if isinstance(plugin_status, str) and plugin_status.strip():
262
+ status_areas["A"].append(plugin_status.strip())
263
+
264
+ except Exception as e:
265
+ logger.warning(f"Failed to get status from plugin {plugin_name}: {e}")
266
+
267
+ return status_areas
@@ -0,0 +1,151 @@
1
+ """System prompt dynamic command renderer.
2
+
3
+ Processes <trender>command</trender> tags in system prompts by executing
4
+ the commands and replacing tags with their output.
5
+ """
6
+
7
+ import logging
8
+ import re
9
+ import subprocess
10
+ from typing import Dict, List, Tuple
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class PromptRenderer:
16
+ """Renders dynamic content in system prompts by executing commands."""
17
+
18
+ # Pattern to match <trender>command</trender> tags
19
+ # Excludes matches that are inside backticks (code examples)
20
+ TRENDER_PATTERN = re.compile(r'(?<!`)<trender>(.*?)</trender>(?!`)', re.DOTALL)
21
+
22
+ def __init__(self, timeout: int = 5):
23
+ """Initialize the prompt renderer.
24
+
25
+ Args:
26
+ timeout: Maximum seconds to wait for each command execution.
27
+ """
28
+ self.timeout = timeout
29
+ self._command_cache: Dict[str, str] = {}
30
+
31
+ def render(self, prompt_content: str) -> str:
32
+ """Render all <trender> tags in the prompt content.
33
+
34
+ Args:
35
+ prompt_content: System prompt content with <trender> tags.
36
+
37
+ Returns:
38
+ Processed prompt with commands replaced by their output.
39
+ """
40
+ if not prompt_content:
41
+ return prompt_content
42
+
43
+ # Find all trender tags
44
+ matches = list(self.TRENDER_PATTERN.finditer(prompt_content))
45
+
46
+ if not matches:
47
+ logger.debug("No <trender> tags found in system prompt")
48
+ return prompt_content
49
+
50
+ logger.info(f"Found {len(matches)} <trender> tag(s) to process")
51
+
52
+ # Process matches in reverse order to maintain string positions
53
+ result = prompt_content
54
+ for match in reversed(matches):
55
+ command = match.group(1).strip()
56
+ start_pos = match.start()
57
+ end_pos = match.end()
58
+
59
+ # Execute command and get output
60
+ output = self._execute_command(command)
61
+
62
+ # Replace the tag with the output
63
+ result = result[:start_pos] + output + result[end_pos:]
64
+
65
+ return result
66
+
67
+ def _execute_command(self, command: str) -> str:
68
+ """Execute a shell command and return its output.
69
+
70
+ Args:
71
+ command: Shell command to execute.
72
+
73
+ Returns:
74
+ Command output or error message.
75
+ """
76
+ # Check cache first
77
+ if command in self._command_cache:
78
+ logger.debug(f"Using cached output for: {command}")
79
+ return self._command_cache[command]
80
+
81
+ try:
82
+ logger.debug(f"Executing trender command: {command}")
83
+
84
+ result = subprocess.run(
85
+ command,
86
+ shell=True,
87
+ capture_output=True,
88
+ text=True,
89
+ timeout=self.timeout,
90
+ cwd="." # Execute in current directory
91
+ )
92
+
93
+ # Get output (prefer stdout, fallback to stderr)
94
+ output = result.stdout if result.stdout else result.stderr
95
+ output = output.strip()
96
+
97
+ if result.returncode != 0:
98
+ error_msg = f"[trender error: command exited with code {result.returncode}]"
99
+ if result.stderr:
100
+ error_msg += f"\n{result.stderr.strip()}"
101
+ logger.warning(f"Command failed: {command} (exit code: {result.returncode})")
102
+ output = error_msg
103
+
104
+ # Cache successful results
105
+ if result.returncode == 0:
106
+ self._command_cache[command] = output
107
+
108
+ logger.debug(f"Command output ({len(output)} chars): {output[:100]}")
109
+ return output
110
+
111
+ except subprocess.TimeoutExpired:
112
+ error_msg = f"[trender error: command timed out after {self.timeout}s]"
113
+ logger.error(f"Command timed out: {command}")
114
+ return error_msg
115
+
116
+ except Exception as e:
117
+ error_msg = f"[trender error: {type(e).__name__}: {str(e)}]"
118
+ logger.error(f"Failed to execute command '{command}': {e}")
119
+ return error_msg
120
+
121
+ def clear_cache(self):
122
+ """Clear the command output cache."""
123
+ self._command_cache.clear()
124
+ logger.debug("Cleared trender command cache")
125
+
126
+ def get_all_commands(self, prompt_content: str) -> List[str]:
127
+ """Extract all commands from trender tags without executing.
128
+
129
+ Args:
130
+ prompt_content: System prompt content with <trender> tags.
131
+
132
+ Returns:
133
+ List of commands found in trender tags.
134
+ """
135
+ matches = self.TRENDER_PATTERN.findall(prompt_content)
136
+ commands = [cmd.strip() for cmd in matches]
137
+ return commands
138
+
139
+
140
+ def render_system_prompt(prompt_content: str, timeout: int = 5) -> str:
141
+ """Convenience function to render a system prompt.
142
+
143
+ Args:
144
+ prompt_content: System prompt content with <trender> tags.
145
+ timeout: Maximum seconds to wait for each command execution.
146
+
147
+ Returns:
148
+ Processed prompt with commands replaced by their output.
149
+ """
150
+ renderer = PromptRenderer(timeout=timeout)
151
+ return renderer.render(prompt_content)