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.
- core/__init__.py +18 -0
- core/application.py +578 -0
- core/cli.py +193 -0
- core/commands/__init__.py +43 -0
- core/commands/executor.py +277 -0
- core/commands/menu_renderer.py +319 -0
- core/commands/parser.py +186 -0
- core/commands/registry.py +331 -0
- core/commands/system_commands.py +479 -0
- core/config/__init__.py +7 -0
- core/config/llm_task_config.py +110 -0
- core/config/loader.py +501 -0
- core/config/manager.py +112 -0
- core/config/plugin_config_manager.py +346 -0
- core/config/plugin_schema.py +424 -0
- core/config/service.py +399 -0
- core/effects/__init__.py +1 -0
- core/events/__init__.py +12 -0
- core/events/bus.py +129 -0
- core/events/executor.py +154 -0
- core/events/models.py +258 -0
- core/events/processor.py +176 -0
- core/events/registry.py +289 -0
- core/fullscreen/__init__.py +19 -0
- core/fullscreen/command_integration.py +290 -0
- core/fullscreen/components/__init__.py +12 -0
- core/fullscreen/components/animation.py +258 -0
- core/fullscreen/components/drawing.py +160 -0
- core/fullscreen/components/matrix_components.py +177 -0
- core/fullscreen/manager.py +302 -0
- core/fullscreen/plugin.py +204 -0
- core/fullscreen/renderer.py +282 -0
- core/fullscreen/session.py +324 -0
- core/io/__init__.py +52 -0
- core/io/buffer_manager.py +362 -0
- core/io/config_status_view.py +272 -0
- core/io/core_status_views.py +410 -0
- core/io/input_errors.py +313 -0
- core/io/input_handler.py +2655 -0
- core/io/input_mode_manager.py +402 -0
- core/io/key_parser.py +344 -0
- core/io/layout.py +587 -0
- core/io/message_coordinator.py +204 -0
- core/io/message_renderer.py +601 -0
- core/io/modal_interaction_handler.py +315 -0
- core/io/raw_input_processor.py +946 -0
- core/io/status_renderer.py +845 -0
- core/io/terminal_renderer.py +586 -0
- core/io/terminal_state.py +551 -0
- core/io/visual_effects.py +734 -0
- core/llm/__init__.py +26 -0
- core/llm/api_communication_service.py +863 -0
- core/llm/conversation_logger.py +473 -0
- core/llm/conversation_manager.py +414 -0
- core/llm/file_operations_executor.py +1401 -0
- core/llm/hook_system.py +402 -0
- core/llm/llm_service.py +1629 -0
- core/llm/mcp_integration.py +386 -0
- core/llm/message_display_service.py +450 -0
- core/llm/model_router.py +214 -0
- core/llm/plugin_sdk.py +396 -0
- core/llm/response_parser.py +848 -0
- core/llm/response_processor.py +364 -0
- core/llm/tool_executor.py +520 -0
- core/logging/__init__.py +19 -0
- core/logging/setup.py +208 -0
- core/models/__init__.py +5 -0
- core/models/base.py +23 -0
- core/plugins/__init__.py +13 -0
- core/plugins/collector.py +212 -0
- core/plugins/discovery.py +386 -0
- core/plugins/factory.py +263 -0
- core/plugins/registry.py +152 -0
- core/storage/__init__.py +5 -0
- core/storage/state_manager.py +84 -0
- core/ui/__init__.py +6 -0
- core/ui/config_merger.py +176 -0
- core/ui/config_widgets.py +369 -0
- core/ui/live_modal_renderer.py +276 -0
- core/ui/modal_actions.py +162 -0
- core/ui/modal_overlay_renderer.py +373 -0
- core/ui/modal_renderer.py +591 -0
- core/ui/modal_state_manager.py +443 -0
- core/ui/widget_integration.py +222 -0
- core/ui/widgets/__init__.py +27 -0
- core/ui/widgets/base_widget.py +136 -0
- core/ui/widgets/checkbox.py +85 -0
- core/ui/widgets/dropdown.py +140 -0
- core/ui/widgets/label.py +78 -0
- core/ui/widgets/slider.py +185 -0
- core/ui/widgets/text_input.py +224 -0
- core/utils/__init__.py +11 -0
- core/utils/config_utils.py +656 -0
- core/utils/dict_utils.py +212 -0
- core/utils/error_utils.py +275 -0
- core/utils/key_reader.py +171 -0
- core/utils/plugin_utils.py +267 -0
- core/utils/prompt_renderer.py +151 -0
- kollabor-0.4.9.dist-info/METADATA +298 -0
- kollabor-0.4.9.dist-info/RECORD +128 -0
- kollabor-0.4.9.dist-info/WHEEL +5 -0
- kollabor-0.4.9.dist-info/entry_points.txt +2 -0
- kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
- kollabor-0.4.9.dist-info/top_level.txt +4 -0
- kollabor_cli_main.py +20 -0
- plugins/__init__.py +1 -0
- plugins/enhanced_input/__init__.py +18 -0
- plugins/enhanced_input/box_renderer.py +103 -0
- plugins/enhanced_input/box_styles.py +142 -0
- plugins/enhanced_input/color_engine.py +165 -0
- plugins/enhanced_input/config.py +150 -0
- plugins/enhanced_input/cursor_manager.py +72 -0
- plugins/enhanced_input/geometry.py +81 -0
- plugins/enhanced_input/state.py +130 -0
- plugins/enhanced_input/text_processor.py +115 -0
- plugins/enhanced_input_plugin.py +385 -0
- plugins/fullscreen/__init__.py +9 -0
- plugins/fullscreen/example_plugin.py +327 -0
- plugins/fullscreen/matrix_plugin.py +132 -0
- plugins/hook_monitoring_plugin.py +1299 -0
- plugins/query_enhancer_plugin.py +350 -0
- plugins/save_conversation_plugin.py +502 -0
- plugins/system_commands_plugin.py +93 -0
- plugins/tmux_plugin.py +795 -0
- plugins/workflow_enforcement_plugin.py +629 -0
- system_prompt/default.md +1286 -0
- system_prompt/default_win.md +265 -0
- 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
|