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,315 @@
1
+ import logging
2
+ from typing import Optional, Callable
3
+
4
+ from ..events import EventType
5
+ from ..events.models import CommandMode
6
+ from .key_parser import KeyPress
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class ModalInteractionHandler:
12
+ """Handles modal interactions, widget navigation, and fullscreen sessions.
13
+
14
+ This component is responsible for:
15
+ - Modal keypress handling and widget interactions
16
+ - Fullscreen session management
17
+ - Modal display coordination and rendering
18
+ - Modal exit and cleanup procedures
19
+ """
20
+
21
+ def __init__(self, event_bus, renderer, config) -> None:
22
+ """Initialize the modal interaction handler.
23
+
24
+ Args:
25
+ event_bus: Event bus for emitting modal events.
26
+ renderer: Terminal renderer for display updates.
27
+ config: Configuration manager for modal settings.
28
+ """
29
+ self.event_bus = event_bus
30
+ self.renderer = renderer
31
+ self.config = config
32
+
33
+ # Modal state
34
+ self.command_mode = CommandMode.NORMAL
35
+ self.modal_renderer = None
36
+ self._fullscreen_session_active = False
37
+ self._pending_save_confirm = False # For modal save confirmation
38
+
39
+ # Callbacks for delegation back to InputHandler
40
+ self.on_mode_change: Optional[Callable] = None
41
+ self.on_display_update: Optional[Callable] = None
42
+ self.on_event_emit: Optional[Callable] = None
43
+
44
+ logger.info("Modal interaction handler initialized")
45
+
46
+ def set_callbacks(
47
+ self,
48
+ on_mode_change: Callable,
49
+ on_display_update: Callable,
50
+ on_event_emit: Callable,
51
+ ) -> None:
52
+ """Set callbacks for delegation back to InputHandler."""
53
+ self.on_mode_change = on_mode_change
54
+ self.on_display_update = on_display_update
55
+ self.on_event_emit = on_event_emit
56
+
57
+ # Modal Processing Methods
58
+ # =======================
59
+
60
+ async def _handle_modal_keypress(self, key_press: KeyPress) -> bool:
61
+ """Handle KeyPress during modal mode.
62
+
63
+ Args:
64
+ key_press: Parsed key press to process.
65
+
66
+ Returns:
67
+ True if key was handled.
68
+ """
69
+ try:
70
+ # CRITICAL FIX: Check if this is a fullscreen plugin session first
71
+ if (
72
+ hasattr(self, "_fullscreen_session_active")
73
+ and self._fullscreen_session_active
74
+ ):
75
+ # SIMPLE SOLUTION: Check for exit keys directly
76
+ if key_press.char in ["q", "\x1b"] or key_press.name == "Escape":
77
+ # Exit fullscreen mode immediately
78
+ self._fullscreen_session_active = False
79
+ self.command_mode = CommandMode.NORMAL
80
+ await self.on_display_update(force_render=True)
81
+ return True
82
+
83
+ # Route input to fullscreen session through event bus
84
+ await self.on_event_emit(
85
+ EventType.FULLSCREEN_INPUT,
86
+ {"key_press": key_press, "source": "input_handler"},
87
+ "input_handler",
88
+ )
89
+ return True
90
+
91
+ # Initialize modal renderer if needed
92
+ if not self.modal_renderer:
93
+ logger.warning(
94
+ "Modal keypress received but no modal renderer active"
95
+ )
96
+ await self._exit_modal_mode()
97
+ return True
98
+
99
+ # Handle save confirmation if active
100
+ if self._pending_save_confirm:
101
+ handled = await self._handle_save_confirmation(key_press)
102
+ if handled:
103
+ return True
104
+
105
+ # Handle navigation and widget interaction
106
+ logger.info(f"🔍 Modal processing key: {key_press.name}")
107
+
108
+ nav_handled = self.modal_renderer._handle_widget_navigation(key_press)
109
+ logger.info(f"🎯 Widget navigation handled: {nav_handled}")
110
+ if nav_handled:
111
+ # Re-render modal with updated focus
112
+ await self._refresh_modal_display()
113
+ return True
114
+
115
+ input_handled = self.modal_renderer._handle_widget_input(key_press)
116
+ logger.info(f"🎯 Widget input handled: {input_handled}")
117
+ if input_handled:
118
+ # Re-render modal with updated widget state
119
+ await self._refresh_modal_display()
120
+ return True
121
+
122
+ if key_press.name == "Escape":
123
+ logger.info("🚪 Processing Escape key for modal exit")
124
+ # Check for unsaved changes
125
+ if self.modal_renderer and self._has_pending_modal_changes():
126
+ self._pending_save_confirm = True
127
+ await self._show_save_confirmation()
128
+ return True
129
+ await self._exit_modal_mode()
130
+ return True
131
+ elif key_press.name == "Ctrl+S":
132
+ logger.info("💾 Processing Ctrl+S for modal save")
133
+ await self._save_and_exit_modal()
134
+ return True
135
+ elif key_press.name == "Enter":
136
+ logger.info(
137
+ "🔴 ENTER KEY HIJACKED - This should not happen if widget handled it!"
138
+ )
139
+ # Try to save modal changes and exit
140
+ await self._save_and_exit_modal()
141
+ return True
142
+
143
+ return True
144
+ except Exception as e:
145
+ logger.error(f"Error handling modal keypress: {e}")
146
+ await self._exit_modal_mode()
147
+ return False
148
+
149
+ async def _exit_modal_mode(self) -> None:
150
+ """Exit modal mode using existing patterns."""
151
+ try:
152
+ # CRITICAL FIX: Complete terminal state restoration
153
+ # Clear active area to remove modal artifacts
154
+ self.renderer.clear_active_area()
155
+
156
+ # Clear any buffered modal content that might persist
157
+ if hasattr(self.renderer, "message_renderer"):
158
+ if hasattr(self.renderer.message_renderer, "buffer"):
159
+ self.renderer.message_renderer.buffer.clear_buffer()
160
+
161
+ # CRITICAL FIX: Properly close modal with alternate buffer restoration
162
+ if self.modal_renderer:
163
+ # FIRST: Close modal and restore terminal state (alternate buffer)
164
+ _ = self.modal_renderer.close_modal()
165
+
166
+ # THEN: Reset modal renderer widgets
167
+ self.modal_renderer.widgets = []
168
+ self.modal_renderer.focused_widget_index = 0
169
+ self.modal_renderer = None
170
+
171
+ # Return to normal mode
172
+ self.command_mode = CommandMode.NORMAL
173
+
174
+ # Complete display restoration with force refresh
175
+ self.renderer.clear_active_area()
176
+ await self.on_display_update(force_render=True)
177
+
178
+ # Ensure cursor is properly positioned
179
+ # Note: cursor management handled by terminal_state
180
+
181
+ except Exception as e:
182
+ logger.error(f"Error exiting modal mode: {e}")
183
+ self.command_mode = CommandMode.NORMAL
184
+ self.modal_renderer = None
185
+ # Emergency cleanup
186
+ self.renderer.clear_active_area()
187
+
188
+ async def _refresh_modal_display(self) -> None:
189
+ """Refresh modal display after widget interactions."""
190
+ try:
191
+ if self.modal_renderer and hasattr(
192
+ self.modal_renderer, "current_ui_config"
193
+ ):
194
+
195
+ # CRITICAL FIX: Force complete display clearing to prevent duplication
196
+ # Clear active area completely before refresh
197
+ self.renderer.clear_active_area()
198
+
199
+ # Clear any message buffers that might accumulate content
200
+ if hasattr(self.renderer, "message_renderer"):
201
+ if hasattr(self.renderer.message_renderer, "buffer"):
202
+ self.renderer.message_renderer.buffer.clear_buffer()
203
+ # Also clear any accumulated messages in the renderer
204
+ if hasattr(self.renderer.message_renderer, "clear_messages"):
205
+ self.renderer.message_renderer.clear_messages()
206
+
207
+ # Re-render the modal with current widget states (preserve widgets!)
208
+ modal_lines = self.modal_renderer._render_modal_box(
209
+ self.modal_renderer.current_ui_config,
210
+ preserve_widgets=True,
211
+ )
212
+ # FIXED: Use state_manager.render_modal_content() instead of _render_modal_lines()
213
+ # to avoid re-calling prepare_modal_display() which causes buffer switching
214
+ if self.modal_renderer.state_manager:
215
+ self.modal_renderer.state_manager.render_modal_content(
216
+ modal_lines
217
+ )
218
+ else:
219
+ # Fallback to old method if state_manager not available
220
+ await self.modal_renderer._render_modal_lines(modal_lines)
221
+ else:
222
+ pass
223
+ except Exception as e:
224
+ logger.error(f"Error refreshing modal display: {e}")
225
+
226
+ async def _save_and_exit_modal(self) -> None:
227
+ """Save modal changes and exit modal mode."""
228
+ try:
229
+ if self.modal_renderer and hasattr(
230
+ self.modal_renderer, "action_handler"
231
+ ):
232
+ # Get widget values and save them using proper action handler interface
233
+ result = await self.modal_renderer.action_handler.handle_action(
234
+ "save", self.modal_renderer.widgets
235
+ )
236
+ if result.get("success"):
237
+ pass
238
+ else:
239
+ logger.warning(
240
+ f"Failed to save modal changes: {result.get('message', 'Unknown error')}"
241
+ )
242
+
243
+ await self._exit_modal_mode()
244
+ except Exception as e:
245
+ logger.error(f"Error saving and exiting modal: {e}")
246
+ await self._exit_modal_mode()
247
+
248
+ def _has_pending_modal_changes(self) -> bool:
249
+ """Check if there are unsaved changes in modal widgets."""
250
+ if not self.modal_renderer or not self.modal_renderer.widgets:
251
+ return False
252
+ for widget in self.modal_renderer.widgets:
253
+ if hasattr(widget, '_pending_value') and widget._pending_value is not None:
254
+ # Check if pending value differs from current config value
255
+ current = widget.get_value() if hasattr(widget, 'get_value') else None
256
+ if widget._pending_value != current:
257
+ return True
258
+ return False
259
+
260
+ async def _show_save_confirmation(self) -> None:
261
+ """Show save confirmation prompt in modal."""
262
+ # Update modal footer to show confirmation prompt
263
+ if self.modal_renderer:
264
+ self.modal_renderer._save_confirm_active = True
265
+ await self._refresh_modal_display()
266
+
267
+ async def _handle_save_confirmation(self, key_press: KeyPress) -> bool:
268
+ """Handle y/n input for save confirmation."""
269
+ if key_press.char and key_press.char.lower() == 'y':
270
+ logger.info("💾 User confirmed save")
271
+ self._pending_save_confirm = False
272
+ if self.modal_renderer:
273
+ self.modal_renderer._save_confirm_active = False
274
+ await self._save_and_exit_modal()
275
+ return True
276
+ elif key_press.char and key_press.char.lower() == 'n':
277
+ logger.info("🚫 User declined save")
278
+ self._pending_save_confirm = False
279
+ if self.modal_renderer:
280
+ self.modal_renderer._save_confirm_active = False
281
+ await self._exit_modal_mode()
282
+ return True
283
+ elif key_press.name == "Escape":
284
+ # Cancel confirmation, stay in modal
285
+ logger.info("↩️ User cancelled confirmation")
286
+ self._pending_save_confirm = False
287
+ if self.modal_renderer:
288
+ self.modal_renderer._save_confirm_active = False
289
+ await self._refresh_modal_display()
290
+ return True
291
+ return False
292
+
293
+ # Fullscreen Session Management
294
+ # ============================
295
+
296
+ def set_fullscreen_session_active(self, active: bool) -> None:
297
+ """Set fullscreen session state."""
298
+ self._fullscreen_session_active = active
299
+
300
+ def is_fullscreen_session_active(self) -> bool:
301
+ """Check if fullscreen session is active."""
302
+ return self._fullscreen_session_active
303
+
304
+ # Public Interface Methods
305
+ # =======================
306
+
307
+ async def handle_modal_keypress(self, key_press: KeyPress) -> bool:
308
+ """Public interface for modal keypress handling."""
309
+ if self.command_mode == CommandMode.MODAL:
310
+ return await self._handle_modal_keypress(key_press)
311
+ return False
312
+
313
+ def get_current_mode(self) -> CommandMode:
314
+ """Get the current command mode."""
315
+ return self.command_mode