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,162 @@
1
+ """Modal action handlers for save/cancel functionality."""
2
+
3
+ import logging
4
+ from typing import Dict, Any, List
5
+ from .config_merger import ConfigMerger
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class ModalActionHandler:
11
+ """Handles modal actions like save/cancel with config persistence."""
12
+
13
+ def __init__(self, config_service):
14
+ """Initialize action handler with config service.
15
+
16
+ Args:
17
+ config_service: ConfigService instance for persistence.
18
+ """
19
+ self.config_service = config_service
20
+
21
+ async def handle_action(self, action: str, widgets: List[Any]) -> Dict[str, Any]:
22
+ """Handle modal action (save/cancel).
23
+
24
+ Args:
25
+ action: Action to perform ("save" or "cancel").
26
+ widgets: List of widgets to collect values from.
27
+
28
+ Returns:
29
+ Action result with success status and message.
30
+ """
31
+ try:
32
+ if action == "save":
33
+ return await self._handle_save_action(widgets)
34
+ elif action == "cancel":
35
+ return await self._handle_cancel_action()
36
+ else:
37
+ return {
38
+ "success": False,
39
+ "message": f"Unknown action: {action}",
40
+ "action": action
41
+ }
42
+
43
+ except Exception as e:
44
+ logger.error(f"Error handling modal action {action}: {e}")
45
+ return {
46
+ "success": False,
47
+ "message": f"Error handling action: {str(e)}",
48
+ "action": action
49
+ }
50
+
51
+ async def _handle_save_action(self, widgets: List[Any]) -> Dict[str, Any]:
52
+ """Handle save action with config persistence.
53
+
54
+ Args:
55
+ widgets: List of widgets to save values from.
56
+
57
+ Returns:
58
+ Save action result.
59
+ """
60
+ try:
61
+ logger.info(f"=== SAVE ACTION: Processing {len(widgets)} widgets ===")
62
+ for i, w in enumerate(widgets):
63
+ logger.info(f" Widget {i}: {w.__class__.__name__} path={getattr(w, 'config_path', 'N/A')} pending={getattr(w, '_pending_value', 'N/A')}")
64
+
65
+ # Collect widget changes
66
+ changes = ConfigMerger.collect_widget_changes(widgets)
67
+ logger.info(f"=== SAVE ACTION: Collected {len(changes)} changes: {changes} ===")
68
+
69
+ if not changes:
70
+ return {
71
+ "success": True,
72
+ "message": "No changes to save",
73
+ "action": "save",
74
+ "changes_count": 0
75
+ }
76
+
77
+ # Validate changes before applying
78
+ validation = ConfigMerger.validate_config_changes(self.config_service, changes)
79
+ if not validation["valid"]:
80
+ error_msg = f"Invalid configuration: {', '.join(validation['errors'])}"
81
+ logger.error(error_msg)
82
+ return {
83
+ "success": False,
84
+ "message": error_msg,
85
+ "action": "save",
86
+ "validation_errors": validation["errors"]
87
+ }
88
+
89
+ # Apply changes
90
+ success = ConfigMerger.apply_widget_changes(self.config_service, changes)
91
+
92
+ if success:
93
+ logger.info(f"Successfully saved {len(changes)} configuration changes")
94
+ return {
95
+ "success": True,
96
+ "message": f"Saved {len(changes)} configuration changes",
97
+ "action": "save",
98
+ "changes_count": len(changes),
99
+ "changes": changes
100
+ }
101
+ else:
102
+ return {
103
+ "success": False,
104
+ "message": "Failed to save configuration changes",
105
+ "action": "save",
106
+ "changes_count": len(changes)
107
+ }
108
+
109
+ except Exception as e:
110
+ logger.error(f"Error in save action: {e}")
111
+ return {
112
+ "success": False,
113
+ "message": f"Save failed: {str(e)}",
114
+ "action": "save"
115
+ }
116
+
117
+ async def _handle_cancel_action(self) -> Dict[str, Any]:
118
+ """Handle cancel action (no persistence).
119
+
120
+ Returns:
121
+ Cancel action result.
122
+ """
123
+ logger.info("Modal cancelled - no changes saved")
124
+ return {
125
+ "success": True,
126
+ "message": "Configuration changes cancelled",
127
+ "action": "cancel"
128
+ }
129
+
130
+ def get_save_confirmation_message(self, changes: Dict[str, Any]) -> str:
131
+ """Generate save confirmation message.
132
+
133
+ Args:
134
+ changes: Dictionary of config changes.
135
+
136
+ Returns:
137
+ Formatted confirmation message.
138
+ """
139
+ if not changes:
140
+ return "No changes to save"
141
+
142
+ change_list = []
143
+ for path, value in changes.items():
144
+ # Truncate long values for display
145
+ display_value = str(value)
146
+ if len(display_value) > 30:
147
+ display_value = display_value[:27] + "..."
148
+ change_list.append(f"{path} = {display_value}")
149
+
150
+ changes_text = "\n".join(f" • {change}" for change in change_list[:5])
151
+ if len(changes) > 5:
152
+ changes_text += f"\n ... and {len(changes) - 5} more"
153
+
154
+ return f"Save {len(changes)} configuration changes?\n\n{changes_text}"
155
+
156
+ def get_cancel_confirmation_message(self) -> str:
157
+ """Generate cancel confirmation message.
158
+
159
+ Returns:
160
+ Formatted confirmation message.
161
+ """
162
+ return "Cancel configuration changes?\n\nAll unsaved changes will be lost."
@@ -0,0 +1,373 @@
1
+ """Pure modal overlay renderer that bypasses chat message system.
2
+
3
+ This renderer provides true modal overlay functionality by:
4
+ 1. Using direct terminal output (no conversation buffer)
5
+ 2. Saving/restoring terminal state
6
+ 3. Implementing proper screen buffer management
7
+ 4. Providing isolated modal display that never accumulates in chat
8
+ """
9
+
10
+ import logging
11
+ from typing import List, Dict, Any, Optional, Tuple
12
+ from dataclasses import dataclass
13
+
14
+ from ..io.visual_effects import ColorPalette
15
+ from ..io.terminal_state import TerminalState
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class ModalState:
22
+ """Represents saved terminal state for modal restoration."""
23
+ cursor_position: Tuple[int, int]
24
+ screen_lines: List[str]
25
+ cursor_visible: bool
26
+ terminal_size: Tuple[int, int]
27
+
28
+
29
+ class ModalOverlayRenderer:
30
+ """Pure modal overlay renderer with terminal state isolation.
31
+
32
+ This class provides modal display functionality that:
33
+ - Never interacts with conversation buffer or chat pipeline
34
+ - Uses direct terminal control sequences for overlay rendering
35
+ - Maintains complete isolation from message accumulation systems
36
+ - Implements proper save/restore of terminal state
37
+ """
38
+
39
+ def __init__(self, terminal_state: TerminalState):
40
+ """Initialize modal overlay renderer.
41
+
42
+ Args:
43
+ terminal_state: TerminalState instance for direct terminal control.
44
+ """
45
+ self.terminal_state = terminal_state
46
+ self.modal_active = False
47
+ self.saved_state: Optional[ModalState] = None
48
+ self.modal_lines: List[str] = []
49
+ self.modal_position = (0, 0) # (row, col) for modal positioning
50
+
51
+ def show_modal_overlay(self, modal_lines: List[str],
52
+ center_position: bool = True) -> bool:
53
+ """Display modal as true overlay without affecting conversation.
54
+
55
+ Args:
56
+ modal_lines: List of modal content lines to display.
57
+ center_position: Whether to center modal on screen.
58
+
59
+ Returns:
60
+ True if modal was displayed successfully.
61
+ """
62
+ try:
63
+ # Save current terminal state
64
+ if not self._save_terminal_state():
65
+ logger.error("Failed to save terminal state")
66
+ return False
67
+
68
+ # Calculate modal position
69
+ if center_position:
70
+ self.modal_position = self._calculate_center_position(modal_lines)
71
+
72
+ # Clear any existing modal content
73
+ self._clear_modal_area()
74
+
75
+ # Render modal overlay using direct terminal output
76
+ self._render_modal_direct(modal_lines)
77
+
78
+ # Store modal state
79
+ self.modal_lines = modal_lines.copy()
80
+ self.modal_active = True
81
+
82
+ logger.info(f"Modal overlay displayed with {len(modal_lines)} lines")
83
+ return True
84
+
85
+ except Exception as e:
86
+ logger.error(f"Failed to show modal overlay: {e}")
87
+ return False
88
+
89
+ def refresh_modal_display(self) -> bool:
90
+ """Refresh modal display without accumulation.
91
+
92
+ This method re-renders the modal content without any interaction
93
+ with conversation buffers or message systems.
94
+
95
+ Returns:
96
+ True if refresh was successful.
97
+ """
98
+ if not self.modal_active or not self.modal_lines:
99
+ return False
100
+
101
+ try:
102
+ # Clear current modal area
103
+ self._clear_modal_area()
104
+
105
+ # Re-render modal content directly
106
+ self._render_modal_direct(self.modal_lines)
107
+
108
+ logger.debug("Modal display refreshed without accumulation")
109
+ return True
110
+
111
+ except Exception as e:
112
+ logger.error(f"Failed to refresh modal display: {e}")
113
+ return False
114
+
115
+ def hide_modal_overlay(self) -> bool:
116
+ """Hide modal overlay and restore terminal state.
117
+
118
+ Returns:
119
+ True if modal was hidden successfully.
120
+ """
121
+ try:
122
+ if not self.modal_active:
123
+ return True
124
+
125
+ # Clear modal area
126
+ self._clear_modal_area()
127
+
128
+ # Restore terminal state if saved
129
+ if self.saved_state:
130
+ self._restore_terminal_state()
131
+
132
+ # Reset modal state
133
+ self.modal_active = False
134
+ self.modal_lines = []
135
+ self.saved_state = None
136
+
137
+ logger.info("Modal overlay hidden and terminal state restored")
138
+ return True
139
+
140
+ except Exception as e:
141
+ logger.error(f"Failed to hide modal overlay: {e}")
142
+ return False
143
+
144
+ def update_modal_content(self, new_lines: List[str]) -> bool:
145
+ """Update modal content and refresh display.
146
+
147
+ Args:
148
+ new_lines: New modal content lines.
149
+
150
+ Returns:
151
+ True if update was successful.
152
+ """
153
+ if not self.modal_active:
154
+ return False
155
+
156
+ try:
157
+ # Update content and refresh display
158
+ self.modal_lines = new_lines.copy()
159
+ return self.refresh_modal_display()
160
+
161
+ except Exception as e:
162
+ logger.error(f"Failed to update modal content: {e}")
163
+ return False
164
+
165
+ def _save_terminal_state(self) -> bool:
166
+ """Save current terminal state for restoration.
167
+
168
+ Returns:
169
+ True if state was saved successfully.
170
+ """
171
+ try:
172
+ # Get current terminal size
173
+ width, height = self.terminal_state.get_size()
174
+
175
+ # Create saved state (simplified - real implementation would capture screen)
176
+ self.saved_state = ModalState(
177
+ cursor_position=(0, 0), # Would query actual cursor position
178
+ screen_lines=[], # Would capture current screen content
179
+ cursor_visible=not self.terminal_state._cursor_hidden,
180
+ terminal_size=(width, height)
181
+ )
182
+
183
+ logger.debug("Terminal state saved for modal")
184
+ return True
185
+
186
+ except Exception as e:
187
+ logger.error(f"Failed to save terminal state: {e}")
188
+ return False
189
+
190
+ def _restore_terminal_state(self) -> bool:
191
+ """Restore terminal state from saved state.
192
+
193
+ Returns:
194
+ True if state was restored successfully.
195
+ """
196
+ if not self.saved_state:
197
+ return False
198
+
199
+ try:
200
+ # Restore cursor visibility
201
+ if self.saved_state.cursor_visible:
202
+ self.terminal_state.show_cursor()
203
+ else:
204
+ self.terminal_state.hide_cursor()
205
+
206
+ logger.debug("Terminal state restored after modal")
207
+ return True
208
+
209
+ except Exception as e:
210
+ logger.error(f"Failed to restore terminal state: {e}")
211
+ return False
212
+
213
+ def _calculate_center_position(self, modal_lines: List[str]) -> Tuple[int, int]:
214
+ """Calculate center position for modal on screen.
215
+
216
+ Args:
217
+ modal_lines: Modal content lines.
218
+
219
+ Returns:
220
+ Tuple of (row, col) for modal position.
221
+ """
222
+ width, height = self.terminal_state.get_size()
223
+
224
+ # Calculate modal dimensions
225
+ modal_height = len(modal_lines)
226
+ modal_width = max(len(line) for line in modal_lines) if modal_lines else 0
227
+
228
+ # Center position
229
+ start_row = max(0, (height - modal_height) // 2)
230
+ start_col = max(0, (width - modal_width) // 2)
231
+
232
+ return (start_row, start_col)
233
+
234
+ def _clear_modal_area(self) -> bool:
235
+ """Clear the modal display area.
236
+
237
+ Returns:
238
+ True if area was cleared successfully.
239
+ """
240
+ try:
241
+ if not self.modal_lines:
242
+ return True
243
+
244
+ # Move to modal position and clear each line
245
+ row, col = self.modal_position
246
+
247
+ for i, line in enumerate(self.modal_lines):
248
+ # Move to line position
249
+ self.terminal_state.write_raw(f"\033[{row + i + 1};{col + 1}H")
250
+ # Clear the line content (overwrite with spaces)
251
+ spaces = " " * len(line)
252
+ self.terminal_state.write_raw(spaces)
253
+
254
+ logger.debug(f"Cleared modal area at position {self.modal_position}")
255
+ return True
256
+
257
+ except Exception as e:
258
+ logger.error(f"Failed to clear modal area: {e}")
259
+ return False
260
+
261
+ def _render_modal_direct(self, modal_lines: List[str]) -> bool:
262
+ """Render modal content using direct terminal output.
263
+
264
+ This method completely bypasses the message system and writes
265
+ directly to terminal using escape sequences.
266
+
267
+ Args:
268
+ modal_lines: Modal content lines to render.
269
+
270
+ Returns:
271
+ True if rendering was successful.
272
+ """
273
+ try:
274
+ if not modal_lines:
275
+ return True
276
+
277
+ row, col = self.modal_position
278
+
279
+ # Render each line directly to terminal
280
+ for i, line in enumerate(modal_lines):
281
+ # Move cursor to position
282
+ terminal_row = row + i + 1 # 1-based positioning
283
+ terminal_col = col + 1 # 1-based positioning
284
+
285
+ # Use ANSI escape sequence to position cursor
286
+ self.terminal_state.write_raw(f"\033[{terminal_row};{terminal_col}H")
287
+
288
+ # Write line content directly
289
+ self.terminal_state.write_raw(line)
290
+
291
+ # Hide cursor for clean modal display
292
+ self.terminal_state.hide_cursor()
293
+
294
+ logger.debug(f"Modal rendered directly at position {self.modal_position}")
295
+ return True
296
+
297
+ except Exception as e:
298
+ logger.error(f"Failed to render modal directly: {e}")
299
+ return False
300
+
301
+ def get_modal_status(self) -> Dict[str, Any]:
302
+ """Get current modal overlay status.
303
+
304
+ Returns:
305
+ Dictionary with modal status information.
306
+ """
307
+ return {
308
+ "modal_active": self.modal_active,
309
+ "modal_lines_count": len(self.modal_lines),
310
+ "modal_position": self.modal_position,
311
+ "has_saved_state": self.saved_state is not None,
312
+ "terminal_size": self.terminal_state.get_size()
313
+ }
314
+
315
+
316
+ class ModalDisplayCoordinator:
317
+ """Coordinates modal display with input system without chat interference.
318
+
319
+ This coordinator ensures modal display updates happen through
320
+ the overlay system rather than the conversation pipeline.
321
+ """
322
+
323
+ def __init__(self, modal_overlay_renderer: ModalOverlayRenderer):
324
+ """Initialize modal display coordinator.
325
+
326
+ Args:
327
+ modal_overlay_renderer: ModalOverlayRenderer instance.
328
+ """
329
+ self.overlay_renderer = modal_overlay_renderer
330
+ self.event_handlers = {}
331
+
332
+ def register_modal_event_handler(self, event_type: str, handler) -> None:
333
+ """Register event handler for modal interactions.
334
+
335
+ Args:
336
+ event_type: Type of event to handle.
337
+ handler: Event handler function.
338
+ """
339
+ self.event_handlers[event_type] = handler
340
+
341
+ def handle_modal_widget_change(self, widget_data: Dict[str, Any]) -> bool:
342
+ """Handle widget state change in modal.
343
+
344
+ Args:
345
+ widget_data: Widget state change information.
346
+
347
+ Returns:
348
+ True if change was handled successfully.
349
+ """
350
+ try:
351
+ # Trigger modal refresh through overlay system (not chat system)
352
+ return self.overlay_renderer.refresh_modal_display()
353
+
354
+ except Exception as e:
355
+ logger.error(f"Failed to handle modal widget change: {e}")
356
+ return False
357
+
358
+ def handle_modal_navigation(self, navigation_data: Dict[str, Any]) -> bool:
359
+ """Handle navigation in modal (arrow keys, tab, etc.).
360
+
361
+ Args:
362
+ navigation_data: Navigation event information.
363
+
364
+ Returns:
365
+ True if navigation was handled successfully.
366
+ """
367
+ try:
368
+ # Process navigation and refresh modal display
369
+ return self.overlay_renderer.refresh_modal_display()
370
+
371
+ except Exception as e:
372
+ logger.error(f"Failed to handle modal navigation: {e}")
373
+ return False