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,152 @@
1
+ """Plugin registry system for Kollabor CLI."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List, Type
5
+
6
+ import logging
7
+
8
+ from ..utils import deep_merge
9
+ from .discovery import PluginDiscovery
10
+ from .factory import PluginFactory
11
+ from .collector import PluginStatusCollector
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class PluginRegistry:
17
+ """Simplified registry coordinating plugin discovery, instantiation, and status collection.
18
+
19
+ This class coordinates between three specialized components:
20
+ - PluginDiscovery: File system scanning and module loading
21
+ - PluginFactory: Plugin instantiation with dependencies
22
+ - PluginStatusCollector: Status aggregation from plugin instances
23
+ """
24
+
25
+ def __init__(self, plugins_dir: Path) -> None:
26
+ """Initialize the plugin registry with specialized components.
27
+
28
+ Args:
29
+ plugins_dir: Directory containing plugin modules.
30
+ """
31
+ self.plugins_dir = plugins_dir
32
+ self.discovery = PluginDiscovery(plugins_dir)
33
+ self.factory = PluginFactory()
34
+ self.collector = PluginStatusCollector()
35
+ logger.info(f"Plugin registry initialized with specialized components: {plugins_dir}")
36
+
37
+ def discover_plugins(self) -> List[str]:
38
+ """Discover available plugins in the plugins directory.
39
+
40
+ Returns:
41
+ List of discovered plugin module names.
42
+ """
43
+ return self.discovery.scan_plugin_files()
44
+
45
+ def load_plugin(self, module_name: str) -> None:
46
+ """Load a plugin module and register its configuration.
47
+
48
+ Args:
49
+ module_name: Name of the plugin module to load.
50
+ """
51
+ self.discovery.load_module(module_name)
52
+
53
+ def load_all_plugins(self) -> None:
54
+ """Discover and load all available plugins."""
55
+ self.discovery.discover_and_load()
56
+ logger.info(f"Plugin registry loaded {len(self.discovery.loaded_classes)} plugins")
57
+
58
+ def get_merged_config(self) -> Dict[str, Any]:
59
+ """Get merged configuration from all registered plugins.
60
+
61
+ Returns:
62
+ Merged configuration dictionary from all plugins.
63
+ """
64
+ merged_config = {}
65
+ plugin_configs = self.discovery.get_all_configs()
66
+
67
+ for plugin_name, plugin_config in plugin_configs.items():
68
+ # Deep merge plugin config into merged_config
69
+ merged_config = deep_merge(merged_config, plugin_config)
70
+ logger.debug(f"Merged config from plugin: {plugin_name}")
71
+
72
+ return merged_config
73
+
74
+ def get_plugin_class(self, plugin_name: str) -> Type:
75
+ """Get a registered plugin class by name.
76
+
77
+ Args:
78
+ plugin_name: Name of the plugin class.
79
+
80
+ Returns:
81
+ Plugin class if found.
82
+
83
+ Raises:
84
+ KeyError: If plugin is not registered.
85
+ """
86
+ return self.discovery.get_plugin_class(plugin_name)
87
+
88
+ def get_plugin_startup_info(self, plugin_name: str, config) -> List[str]:
89
+ """Get startup information for a plugin.
90
+
91
+ Args:
92
+ plugin_name: Name of the plugin class.
93
+ config: Configuration manager instance.
94
+
95
+ Returns:
96
+ List of startup info strings, or empty list if no info available.
97
+ """
98
+ try:
99
+ plugin_class = self.discovery.get_plugin_class(plugin_name)
100
+ return self.collector.get_plugin_startup_info(plugin_name, plugin_class, config)
101
+ except KeyError:
102
+ logger.warning(f"Plugin {plugin_name} not found for startup info")
103
+ return []
104
+
105
+ def list_plugins(self) -> List[str]:
106
+ """Get list of registered plugin names.
107
+
108
+ Returns:
109
+ List of registered plugin names.
110
+ """
111
+ return list(self.discovery.loaded_classes.keys())
112
+
113
+ def instantiate_plugins(self, state_manager, event_bus, renderer, config) -> Dict[str, Any]:
114
+ """Create instances of all registered plugins that can be instantiated.
115
+
116
+ Args:
117
+ state_manager: State management system.
118
+ event_bus: Event bus for hook registration.
119
+ renderer: Terminal renderer.
120
+ config: Configuration manager.
121
+
122
+ Returns:
123
+ Dictionary mapping plugin names to their instances.
124
+ """
125
+ plugin_classes = self.discovery.loaded_classes
126
+ return self.factory.instantiate_all(
127
+ plugin_classes, state_manager, event_bus, renderer, config
128
+ )
129
+
130
+ def collect_status_lines(self, plugin_instances: Dict[str, Any]) -> Dict[str, List[str]]:
131
+ """Collect status lines from all plugins organized by area.
132
+
133
+ Args:
134
+ plugin_instances: Dictionary of plugin instances.
135
+
136
+ Returns:
137
+ Dictionary with areas A, B, C containing lists of status lines.
138
+ """
139
+ return self.collector.collect_all_status(plugin_instances)
140
+
141
+ def get_registry_stats(self) -> Dict[str, Any]:
142
+ """Get comprehensive statistics about the registry and its components.
143
+
144
+ Returns:
145
+ Dictionary with detailed registry statistics.
146
+ """
147
+ return {
148
+ "plugins_directory": str(self.plugins_dir),
149
+ "discovery_stats": self.discovery.get_discovery_stats(),
150
+ "factory_stats": self.factory.get_factory_stats(),
151
+ "collector_stats": self.collector.get_collector_stats()
152
+ }
@@ -0,0 +1,5 @@
1
+ """Storage subsystem for Kollabor CLI."""
2
+
3
+ from .state_manager import StateManager
4
+
5
+ __all__ = ['StateManager']
@@ -0,0 +1,84 @@
1
+ """State management system for Kollabor CLI."""
2
+
3
+ import json
4
+ import logging
5
+ import sqlite3
6
+ import threading
7
+ from typing import Any
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class StateManager:
13
+ """SQLite-based state management system.
14
+
15
+ Provides persistent storage for application and plugin state.
16
+ """
17
+
18
+ def __init__(self, db_path: str) -> None:
19
+ """Initialize the state manager.
20
+
21
+ Args:
22
+ db_path: Path to the SQLite database file.
23
+ """
24
+ self.db_path = db_path
25
+ self.conn = sqlite3.connect(db_path, check_same_thread=False)
26
+ self._lock = threading.Lock()
27
+ self._init_database()
28
+ logger.info(f"State manager initialized with database: {db_path}")
29
+
30
+ def _init_database(self) -> None:
31
+ """Initialize the database schema."""
32
+ cursor = self.conn.cursor()
33
+
34
+ # Main state table
35
+ cursor.execute('''
36
+ CREATE TABLE IF NOT EXISTS state (
37
+ key TEXT PRIMARY KEY,
38
+ value TEXT,
39
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
40
+ )
41
+ ''')
42
+
43
+ self.conn.commit()
44
+ logger.debug("Database schema initialized")
45
+
46
+ def set(self, key: str, value: Any) -> None:
47
+ """Set a state value.
48
+
49
+ Args:
50
+ key: State key.
51
+ value: Value to store (will be JSON serialized).
52
+ """
53
+ with self._lock:
54
+ cursor = self.conn.cursor()
55
+ cursor.execute('''
56
+ INSERT OR REPLACE INTO state (key, value, updated_at)
57
+ VALUES (?, ?, CURRENT_TIMESTAMP)
58
+ ''', (key, json.dumps(value)))
59
+ self.conn.commit()
60
+ logger.debug(f"Set state: {key}")
61
+
62
+ def get(self, key: str, default: Any = None) -> Any:
63
+ """Get a state value.
64
+
65
+ Args:
66
+ key: State key.
67
+ default: Default value if key not found.
68
+
69
+ Returns:
70
+ The stored value or default.
71
+ """
72
+ with self._lock:
73
+ cursor = self.conn.cursor()
74
+ cursor.execute('SELECT value FROM state WHERE key = ?', (key,))
75
+ row = cursor.fetchone()
76
+ if row:
77
+ return json.loads(row[0])
78
+ return default
79
+
80
+ def close(self) -> None:
81
+ """Close the database connection."""
82
+ if self.conn:
83
+ self.conn.close()
84
+ logger.info("Database connection closed")
core/ui/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Modal UI system for Kollabor CLI."""
2
+
3
+ from .modal_renderer import ModalRenderer
4
+ from .live_modal_renderer import LiveModalRenderer, LiveModalConfig
5
+
6
+ __all__ = ["ModalRenderer", "LiveModalRenderer", "LiveModalConfig"]
@@ -0,0 +1,176 @@
1
+ """Config persistence system for modal UI changes."""
2
+
3
+ import logging
4
+ from typing import Dict, Any, List, Optional
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class ConfigMerger:
10
+ """Handles saving widget changes to config.json using existing config system."""
11
+
12
+ @staticmethod
13
+ def apply_widget_changes(config_service, widget_changes: Dict[str, Any]) -> bool:
14
+ """Apply widget changes to config.json using existing config system.
15
+
16
+ Args:
17
+ config_service: ConfigService instance for managing configuration.
18
+ widget_changes: Dictionary mapping config paths to new values.
19
+
20
+ Returns:
21
+ True if all changes applied successfully, False otherwise.
22
+ """
23
+ try:
24
+ logger.info(f"Applying {len(widget_changes)} widget changes to config")
25
+
26
+ # Create backup before making changes
27
+ backup_path = config_service.backup_config(".pre-modal-changes")
28
+ if backup_path:
29
+ logger.debug(f"Config backup created: {backup_path}")
30
+
31
+ # Apply all changes atomically
32
+ success_count = 0
33
+ failed_changes = []
34
+
35
+ for path, value in widget_changes.items():
36
+ try:
37
+ # Use existing config system
38
+ if config_service.set(path, value):
39
+ success_count += 1
40
+ logger.debug(f"Successfully set {path} = {value}")
41
+ else:
42
+ failed_changes.append((path, value, "set_failed"))
43
+ logger.error(f"Failed to set config: {path} = {value}")
44
+ except Exception as e:
45
+ failed_changes.append((path, value, str(e)))
46
+ logger.error(f"Error setting {path}: {e}")
47
+
48
+ # Report results
49
+ if failed_changes:
50
+ logger.error(f"Failed to apply {len(failed_changes)} changes: {failed_changes}")
51
+ return False
52
+
53
+ logger.info(f"Successfully applied all {success_count} config changes")
54
+
55
+ # Notify plugins using existing event system
56
+ ConfigMerger.notify_plugins_config_changed(config_service, list(widget_changes.keys()))
57
+
58
+ return True
59
+
60
+ except Exception as e:
61
+ logger.error(f"Critical error applying widget changes: {e}")
62
+ return False
63
+
64
+ @staticmethod
65
+ def notify_plugins_config_changed(config_service, changed_paths: List[str]) -> None:
66
+ """Notify plugins their config changed using existing event system.
67
+
68
+ Args:
69
+ config_service: ConfigService instance.
70
+ changed_paths: List of config paths that changed.
71
+ """
72
+ try:
73
+ # Trigger the existing config reload callbacks
74
+ config_service._notify_reload_callbacks()
75
+ logger.debug(f"Notified plugins of config changes: {changed_paths}")
76
+ except Exception as e:
77
+ logger.error(f"Error notifying plugins of config changes: {e}")
78
+
79
+ @staticmethod
80
+ def collect_widget_changes(widgets: List[Any]) -> Dict[str, Any]:
81
+ """Collect all widget value changes.
82
+
83
+ Args:
84
+ widgets: List of widgets to collect changes from.
85
+
86
+ Returns:
87
+ Dictionary mapping config paths to new values.
88
+ """
89
+ changes = {}
90
+
91
+ for widget in widgets:
92
+ # Check if widget has a pending value that differs from current
93
+ if hasattr(widget, '_pending_value') and hasattr(widget, 'config_path'):
94
+ # Use get_value() method instead of current_value attribute
95
+ current_value = widget.get_value() if hasattr(widget, 'get_value') else None
96
+ pending_value = widget._pending_value
97
+
98
+ # Only include changes where value actually changed
99
+ # Note: pending_value being None means no change was made
100
+ if pending_value is not None and pending_value != current_value:
101
+ changes[widget.config_path] = pending_value
102
+ logger.debug(f"Collected change: {widget.config_path} = {pending_value} (was {current_value})")
103
+
104
+ logger.info(f"Collected {len(changes)} widget changes")
105
+ return changes
106
+
107
+ @staticmethod
108
+ def validate_config_changes(config_service, changes: Dict[str, Any]) -> Dict[str, Any]:
109
+ """Validate config changes before applying them.
110
+
111
+ Args:
112
+ config_service: ConfigService instance.
113
+ changes: Dictionary of proposed changes.
114
+
115
+ Returns:
116
+ Validation result with 'valid' boolean and 'errors' list.
117
+ """
118
+ validation_result = {
119
+ "valid": True,
120
+ "errors": [],
121
+ "warnings": []
122
+ }
123
+
124
+ try:
125
+ # Create a temporary config to test validation
126
+ original_config = config_service.config_manager.config.copy()
127
+
128
+ # Apply changes temporarily
129
+ for path, value in changes.items():
130
+ try:
131
+ from ..utils import safe_set
132
+ safe_set(config_service.config_manager.config, path, value)
133
+ except Exception as e:
134
+ validation_result["errors"].append(f"Invalid path {path}: {e}")
135
+ validation_result["valid"] = False
136
+
137
+ # Use existing validation
138
+ if validation_result["valid"]:
139
+ system_validation = config_service.validate_config()
140
+ if not system_validation["valid"]:
141
+ validation_result["valid"] = False
142
+ validation_result["errors"].extend(system_validation["errors"])
143
+ validation_result["warnings"].extend(system_validation.get("warnings", []))
144
+
145
+ # Restore original config
146
+ config_service.config_manager.config = original_config
147
+
148
+ except Exception as e:
149
+ validation_result["valid"] = False
150
+ validation_result["errors"].append(f"Validation error: {e}")
151
+
152
+ return validation_result
153
+
154
+ @staticmethod
155
+ def get_config_values(config_service, config_paths: List[str]) -> Dict[str, Any]:
156
+ """Get current values for config paths.
157
+
158
+ Args:
159
+ config_service: ConfigService instance.
160
+ config_paths: List of config paths to retrieve.
161
+
162
+ Returns:
163
+ Dictionary mapping paths to their current values.
164
+ """
165
+ values = {}
166
+
167
+ for path in config_paths:
168
+ try:
169
+ value = config_service.get(path)
170
+ values[path] = value
171
+ logger.debug(f"Retrieved config value: {path} = {value}")
172
+ except Exception as e:
173
+ logger.error(f"Error retrieving config {path}: {e}")
174
+ values[path] = None
175
+
176
+ return values