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
core/logging/setup.py ADDED
@@ -0,0 +1,208 @@
1
+ """Centralized logging configuration and setup.
2
+
3
+ This module handles all logging configuration for the application,
4
+ reading from the config system when available and providing sensible
5
+ defaults during bootstrap.
6
+ """
7
+
8
+ import logging
9
+ import logging.handlers
10
+ import threading
11
+ from pathlib import Path
12
+ from typing import Any, Dict, Optional
13
+
14
+
15
+ class CompactFormatter(logging.Formatter):
16
+ """Custom formatter that compacts level names and includes file location."""
17
+
18
+ def format(self, record):
19
+ # Map long level names to 4-char versions
20
+ level_mapping = {
21
+ 'WARNING': 'WARN',
22
+ 'CRITICAL': 'CRIT',
23
+ 'DEBUG': 'DEBG'
24
+ }
25
+ record.levelname = level_mapping.get(record.levelname, record.levelname)
26
+ return super().format(record)
27
+
28
+
29
+ class LoggingSetup:
30
+ """Centralized logging configuration manager."""
31
+
32
+ def __init__(self):
33
+ self._configured = False
34
+ self._current_config = {}
35
+
36
+ def setup_bootstrap_logging(self, log_dir: Optional[Path] = None):
37
+ """Setup minimal logging before config system is available.
38
+
39
+ Args:
40
+ log_dir: Optional log directory, defaults to .kollabor-cli/logs
41
+ """
42
+ if log_dir is None:
43
+ from ..utils.config_utils import get_config_directory
44
+ config_dir = get_config_directory()
45
+ log_dir = config_dir / "logs"
46
+
47
+ # Ensure log directory exists
48
+ log_dir.mkdir(parents=True, exist_ok=True)
49
+
50
+ # Setup with hardcoded defaults that match current behavior
51
+ log_file = log_dir / "kollabor.log"
52
+
53
+ handler = logging.handlers.TimedRotatingFileHandler(
54
+ filename=str(log_file),
55
+ when='D', # Daily rotation
56
+ interval=1,
57
+ backupCount=1,
58
+ encoding='utf-8'
59
+ )
60
+
61
+ # Add thread safety
62
+ handler.lock = threading.RLock()
63
+
64
+ # Use compact formatter
65
+ formatter = CompactFormatter(
66
+ "%(asctime)s - %(levelname)-4s - %(message)-100s - %(filename)s:%(lineno)04d"
67
+ )
68
+ handler.setFormatter(formatter)
69
+
70
+ # Configure logging
71
+ logging.basicConfig(
72
+ level=logging.INFO,
73
+ handlers=[
74
+ handler,
75
+ logging.NullHandler() # Suppress console output
76
+ ]
77
+ )
78
+
79
+ self._configured = True
80
+ self._current_config = {
81
+ 'level': 'INFO',
82
+ 'file': str(log_file),
83
+ 'format': 'compact'
84
+ }
85
+
86
+ # Apply formatter to all existing handlers
87
+ self._apply_formatter_to_all_loggers(formatter)
88
+
89
+ def setup_from_config(self, config: Dict[str, Any]):
90
+ """Setup logging from configuration system.
91
+
92
+ Args:
93
+ config: Configuration dictionary with logging settings
94
+ """
95
+ logging_config = config.get('logging', {})
96
+
97
+ # Extract configuration values
98
+ level = logging_config.get('level', 'INFO').upper()
99
+ log_file = logging_config.get('file', '.kollabor-cli/logs/kollabor.log')
100
+ format_type = logging_config.get('format_type', 'compact')
101
+ custom_format = logging_config.get('format', None)
102
+
103
+ # Convert string level to logging constant
104
+ numeric_level = getattr(logging, level, logging.INFO)
105
+
106
+ # Ensure log directory exists
107
+ log_path = Path(log_file)
108
+ log_path.parent.mkdir(parents=True, exist_ok=True)
109
+
110
+ # Create new handler
111
+ handler = logging.handlers.TimedRotatingFileHandler(
112
+ filename=str(log_path),
113
+ when='D',
114
+ interval=1,
115
+ backupCount=1,
116
+ encoding='utf-8'
117
+ )
118
+
119
+ # Add thread safety
120
+ handler.lock = threading.RLock()
121
+
122
+ # Choose formatter based on config
123
+ if format_type == 'compact':
124
+ formatter = CompactFormatter(
125
+ "%(asctime)s - %(levelname)-4s - %(message)-100s - %(filename)s:%(lineno)04d"
126
+ )
127
+ elif custom_format:
128
+ formatter = logging.Formatter(custom_format)
129
+ else:
130
+ # Standard format
131
+ formatter = logging.Formatter(
132
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
133
+ )
134
+
135
+ handler.setFormatter(formatter)
136
+
137
+ # Clear existing handlers and setup new one
138
+ root_logger = logging.getLogger()
139
+ for old_handler in root_logger.handlers[:]:
140
+ root_logger.removeHandler(old_handler)
141
+
142
+ logging.basicConfig(
143
+ level=numeric_level,
144
+ handlers=[
145
+ handler,
146
+ logging.NullHandler()
147
+ ],
148
+ force=True # Force reconfiguration
149
+ )
150
+
151
+ self._configured = True
152
+ self._current_config = {
153
+ 'level': level,
154
+ 'file': str(log_path),
155
+ 'format': format_type,
156
+ 'custom_format': custom_format
157
+ }
158
+
159
+ # Apply formatter to all existing loggers
160
+ self._apply_formatter_to_all_loggers(formatter)
161
+
162
+ logging.getLogger(__name__).info(
163
+ f"Logging reconfigured - Level: {level}, File: {log_file}, Format: {format_type}"
164
+ )
165
+
166
+ def _apply_formatter_to_all_loggers(self, formatter):
167
+ """Apply formatter to all existing loggers and handlers."""
168
+ # Apply to root logger handlers
169
+ for root_handler in logging.getLogger().handlers:
170
+ root_handler.setFormatter(formatter)
171
+
172
+ # Apply to all existing logger handlers
173
+ for logger_name in logging.Logger.manager.loggerDict:
174
+ existing_logger = logging.getLogger(logger_name)
175
+ for existing_handler in existing_logger.handlers:
176
+ existing_handler.setFormatter(formatter)
177
+
178
+ def get_current_config(self) -> Dict[str, Any]:
179
+ """Get current logging configuration."""
180
+ return self._current_config.copy()
181
+
182
+ def is_configured(self) -> bool:
183
+ """Check if logging has been configured."""
184
+ return self._configured
185
+
186
+
187
+ # Global instance
188
+ logging_setup = LoggingSetup()
189
+
190
+
191
+ def setup_bootstrap_logging(log_dir: Optional[Path] = None):
192
+ """Setup bootstrap logging before config system is available."""
193
+ logging_setup.setup_bootstrap_logging(log_dir)
194
+
195
+
196
+ def setup_from_config(config: Dict[str, Any]):
197
+ """Setup logging from configuration system."""
198
+ logging_setup.setup_from_config(config)
199
+
200
+
201
+ def get_current_config() -> Dict[str, Any]:
202
+ """Get current logging configuration."""
203
+ return logging_setup.get_current_config()
204
+
205
+
206
+ def is_configured() -> bool:
207
+ """Check if logging has been configured."""
208
+ return logging_setup.is_configured()
@@ -0,0 +1,5 @@
1
+ """Shared models for Kollabor CLI."""
2
+
3
+ from .base import ConversationMessage
4
+
5
+ __all__ = ['ConversationMessage']
core/models/base.py ADDED
@@ -0,0 +1,23 @@
1
+ """Shared data models for Kollabor CLI."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime
5
+ from typing import Any, Dict, Optional
6
+
7
+
8
+ @dataclass
9
+ class ConversationMessage:
10
+ """A single message in the conversation.
11
+
12
+ Attributes:
13
+ role: Role of the message sender (user, assistant, system, tool).
14
+ content: The message content.
15
+ timestamp: When the message was created.
16
+ metadata: Additional metadata.
17
+ thinking: Optional thinking process information.
18
+ """
19
+ role: str
20
+ content: str
21
+ timestamp: datetime = field(default_factory=datetime.now)
22
+ metadata: Dict[str, Any] = field(default_factory=dict)
23
+ thinking: Optional[str] = None
@@ -0,0 +1,13 @@
1
+ """Plugin subsystem for Kollabor CLI."""
2
+
3
+ from .registry import PluginRegistry
4
+ from .discovery import PluginDiscovery
5
+ from .factory import PluginFactory
6
+ from .collector import PluginStatusCollector
7
+
8
+ __all__ = [
9
+ 'PluginRegistry',
10
+ 'PluginDiscovery',
11
+ 'PluginFactory',
12
+ 'PluginStatusCollector'
13
+ ]
@@ -0,0 +1,212 @@
1
+ """Plugin status collector for aggregating status information from plugins."""
2
+
3
+ import logging
4
+ from typing import Any, Dict, List
5
+
6
+ from ..utils.plugin_utils import collect_plugin_status_safely
7
+ from ..utils.error_utils import safe_execute
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class PluginStatusCollector:
13
+ """Collects and aggregates status information from plugin instances.
14
+
15
+ This class is responsible for gathering status lines from all plugins,
16
+ organizing them by display areas, and providing aggregated status data.
17
+ """
18
+
19
+ def __init__(self):
20
+ """Initialize the plugin status collector."""
21
+ self.last_status_collection: Dict[str, Dict[str, List[str]]] = {}
22
+ self.collection_errors: Dict[str, str] = {}
23
+ logger.info("PluginStatusCollector initialized")
24
+
25
+ def collect_plugin_status(self, plugin_name: str, plugin_instance: Any) -> Dict[str, List[str]]:
26
+ """Collect status information from a single plugin.
27
+
28
+ Args:
29
+ plugin_name: Name of the plugin.
30
+ plugin_instance: The plugin instance to collect status from.
31
+
32
+ Returns:
33
+ Dictionary with areas A, B, C containing lists of status lines.
34
+ """
35
+ plugin_status = collect_plugin_status_safely(plugin_instance, plugin_name)
36
+
37
+ # Store the status for this plugin
38
+ self.last_status_collection[plugin_name] = plugin_status
39
+
40
+ # Clear any previous collection errors for this plugin
41
+ if plugin_name in self.collection_errors:
42
+ del self.collection_errors[plugin_name]
43
+
44
+ return plugin_status
45
+
46
+ def collect_all_status(self, plugin_instances: Dict[str, Any]) -> Dict[str, List[str]]:
47
+ """Collect status lines from all plugins organized by area.
48
+
49
+ Args:
50
+ plugin_instances: Dictionary of plugin instances.
51
+
52
+ Returns:
53
+ Dictionary with areas A, B, C containing aggregated status lines.
54
+ """
55
+ status_areas = {"A": [], "B": [], "C": []}
56
+
57
+ for plugin_name, plugin_instance in plugin_instances.items():
58
+ plugin_status = self.collect_plugin_status(plugin_name, plugin_instance)
59
+
60
+ # Merge the status from this plugin into our aggregated status
61
+ for area in ["A", "B", "C"]:
62
+ if area in plugin_status:
63
+ status_areas[area].extend(plugin_status[area])
64
+
65
+ #logger.debug(f"Collected status from {len(plugin_instances)} plugins")
66
+ return status_areas
67
+
68
+ def get_plugin_startup_info(self, plugin_name: str, plugin_class: type, config: Any) -> List[str]:
69
+ """Get startup information for a specific plugin.
70
+
71
+ Args:
72
+ plugin_name: Name of the plugin.
73
+ plugin_class: The plugin class.
74
+ config: Configuration manager instance.
75
+
76
+ Returns:
77
+ List of startup info strings, or empty list if no info available.
78
+ """
79
+ def get_startup_info():
80
+ return plugin_class.get_startup_info(config)
81
+
82
+ result = safe_execute(
83
+ get_startup_info,
84
+ f"getting startup info from plugin {plugin_name}",
85
+ default=[],
86
+ logger_instance=logger
87
+ )
88
+
89
+ return result if isinstance(result, list) else []
90
+
91
+ def collect_all_startup_info(self, plugin_classes: Dict[str, type], config: Any) -> Dict[str, List[str]]:
92
+ """Collect startup information from all plugin classes.
93
+
94
+ Args:
95
+ plugin_classes: Dictionary mapping plugin names to classes.
96
+ config: Configuration manager instance.
97
+
98
+ Returns:
99
+ Dictionary mapping plugin names to their startup info lists.
100
+ """
101
+ startup_info = {}
102
+
103
+ for plugin_name, plugin_class in plugin_classes.items():
104
+ info_list = self.get_plugin_startup_info(plugin_name, plugin_class, config)
105
+ if info_list:
106
+ startup_info[plugin_name] = info_list
107
+
108
+ logger.debug(f"Collected startup info from {len(startup_info)} plugins")
109
+ return startup_info
110
+
111
+ def get_status_summary(self) -> Dict[str, Any]:
112
+ """Get a summary of the last status collection.
113
+
114
+ Returns:
115
+ Dictionary with status collection summary.
116
+ """
117
+ total_lines = 0
118
+ areas_summary = {"A": 0, "B": 0, "C": 0}
119
+
120
+ for plugin_name, status in self.last_status_collection.items():
121
+ for area in ["A", "B", "C"]:
122
+ if area in status:
123
+ line_count = len(status[area])
124
+ areas_summary[area] += line_count
125
+ total_lines += line_count
126
+
127
+ return {
128
+ "total_plugins": len(self.last_status_collection),
129
+ "total_status_lines": total_lines,
130
+ "lines_per_area": areas_summary,
131
+ "collection_errors": len(self.collection_errors),
132
+ "plugins_with_status": [
133
+ name for name, status in self.last_status_collection.items()
134
+ if any(status.get(area) for area in ["A", "B", "C"])
135
+ ]
136
+ }
137
+
138
+ def get_plugin_status_details(self, plugin_name: str) -> Dict[str, Any]:
139
+ """Get detailed status information for a specific plugin.
140
+
141
+ Args:
142
+ plugin_name: Name of the plugin.
143
+
144
+ Returns:
145
+ Dictionary with detailed status information for the plugin.
146
+ """
147
+ if plugin_name not in self.last_status_collection:
148
+ return {
149
+ "plugin_name": plugin_name,
150
+ "status_collected": False,
151
+ "error": "No status collection found"
152
+ }
153
+
154
+ status = self.last_status_collection[plugin_name]
155
+
156
+ return {
157
+ "plugin_name": plugin_name,
158
+ "status_collected": True,
159
+ "areas": {
160
+ area: {
161
+ "line_count": len(lines),
162
+ "lines": lines
163
+ }
164
+ for area, lines in status.items()
165
+ },
166
+ "has_error": plugin_name in self.collection_errors,
167
+ "error": self.collection_errors.get(plugin_name)
168
+ }
169
+
170
+ def clear_status_history(self) -> None:
171
+ """Clear all stored status collection history."""
172
+ self.last_status_collection.clear()
173
+ self.collection_errors.clear()
174
+ logger.debug("Cleared status collection history")
175
+
176
+ def get_status_by_area(self, area: str) -> List[str]:
177
+ """Get all status lines for a specific area across all plugins.
178
+
179
+ Args:
180
+ area: The area to get status for ("A", "B", or "C").
181
+
182
+ Returns:
183
+ List of status lines for the specified area.
184
+ """
185
+ if area not in ["A", "B", "C"]:
186
+ logger.warning(f"Invalid status area: {area}")
187
+ return []
188
+
189
+ lines = []
190
+ for plugin_name, status in self.last_status_collection.items():
191
+ if area in status:
192
+ lines.extend(status[area])
193
+
194
+ return lines
195
+
196
+ def get_collector_stats(self) -> Dict[str, Any]:
197
+ """Get statistics about the collector's operations.
198
+
199
+ Returns:
200
+ Dictionary with collector statistics.
201
+ """
202
+ total_collections = len(self.last_status_collection)
203
+ successful_collections = total_collections - len(self.collection_errors)
204
+
205
+ return {
206
+ "total_collections": total_collections,
207
+ "successful_collections": successful_collections,
208
+ "collection_errors": len(self.collection_errors),
209
+ "plugins_tracked": list(self.last_status_collection.keys()),
210
+ "error_plugins": list(self.collection_errors.keys()),
211
+ "status_summary": self.get_status_summary()
212
+ }