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,402 @@
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
+ from .buffer_manager import BufferManager
8
+ from ..commands.menu_renderer import CommandMenuRenderer
9
+ from ..commands.parser import SlashCommandParser
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class InputModeManager:
15
+ """Handles input mode management and command mode processing.
16
+
17
+ This component is responsible for:
18
+ - Mode state management (enter/exit command modes)
19
+ - Command mode keypress handling
20
+ - Menu navigation and command execution coordination
21
+ - Mode transitions and display updates
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ buffer_manager: BufferManager,
27
+ command_menu_renderer: CommandMenuRenderer,
28
+ slash_parser: SlashCommandParser,
29
+ event_bus,
30
+ renderer,
31
+ config,
32
+ ) -> None:
33
+ """Initialize the input mode manager.
34
+
35
+ Args:
36
+ buffer_manager: Buffer manager for text operations.
37
+ command_menu_renderer: Command menu rendering system.
38
+ slash_parser: Slash command parser.
39
+ event_bus: Event bus for emitting input events.
40
+ renderer: Terminal renderer for updating input display.
41
+ config: Configuration manager for input settings.
42
+ """
43
+ self.buffer_manager = buffer_manager
44
+ self.command_menu_renderer = command_menu_renderer
45
+ self.slash_parser = slash_parser
46
+ self.event_bus = event_bus
47
+ self.renderer = renderer
48
+ self.config = config
49
+
50
+ # Mode state
51
+ self.command_mode = CommandMode.NORMAL
52
+ self.command_menu_active = False
53
+ self.selected_command_index = 0
54
+ self.current_status_modal_config = None
55
+
56
+ # Callbacks for delegation back to InputHandler
57
+ self.on_mode_change: Optional[Callable] = None
58
+ self.on_command_execute: Optional[Callable] = None
59
+ self.on_display_update: Optional[Callable] = None
60
+ self.on_event_emit: Optional[Callable] = None
61
+ self.get_available_commands: Optional[Callable] = None
62
+ self.filter_commands: Optional[Callable] = None
63
+ self.execute_command: Optional[Callable] = None
64
+
65
+ logger.info("Input mode manager initialized")
66
+
67
+ def set_callbacks(
68
+ self,
69
+ on_mode_change: Callable,
70
+ on_command_execute: Callable,
71
+ on_display_update: Callable,
72
+ on_event_emit: Callable,
73
+ get_available_commands: Callable,
74
+ filter_commands: Callable,
75
+ execute_command: Callable,
76
+ ) -> None:
77
+ """Set callbacks for delegation back to InputHandler."""
78
+ self.on_mode_change = on_mode_change
79
+ self.on_command_execute = on_command_execute
80
+ self.on_display_update = on_display_update
81
+ self.on_event_emit = on_event_emit
82
+ self.get_available_commands = get_available_commands
83
+ self.filter_commands = filter_commands
84
+ self.execute_command = execute_command
85
+
86
+ # Mode State Management Methods (Phase 2A)
87
+ # =========================================
88
+
89
+ async def _enter_command_mode(self) -> None:
90
+ """Enter slash command mode and show command menu."""
91
+ try:
92
+ logger.info("🎯 Entering slash command mode")
93
+ self.command_mode = CommandMode.MENU_POPUP
94
+ self.command_menu_active = True
95
+
96
+ # Reset selection to first command
97
+ self.selected_command_index = 0
98
+
99
+ # Add the '/' character to buffer for visual feedback
100
+ self.buffer_manager.insert_char("/")
101
+
102
+ # Show command menu via renderer
103
+ available_commands = self.get_available_commands()
104
+ self.command_menu_renderer.show_command_menu(available_commands, "")
105
+
106
+ # Emit command menu show event
107
+ await self.on_event_emit(
108
+ EventType.COMMAND_MENU_SHOW,
109
+ {"available_commands": available_commands, "filter_text": ""},
110
+ "commands",
111
+ )
112
+
113
+ # Update display to show command mode
114
+ await self.on_display_update(force_render=True)
115
+
116
+ logger.info("Command menu activated")
117
+
118
+ except Exception as e:
119
+ logger.error(f"Error entering command mode: {e}")
120
+ await self._exit_command_mode()
121
+
122
+ async def _exit_command_mode(self) -> None:
123
+ """Exit command mode and restore normal input."""
124
+ try:
125
+ import traceback
126
+
127
+ logger.info("🚪 Exiting slash command mode")
128
+ logger.info(
129
+ f"🚪 Exit called from: {traceback.format_stack()[-2].strip()}"
130
+ )
131
+
132
+ # Hide command menu via renderer
133
+ self.command_menu_renderer.hide_menu()
134
+
135
+ # Emit command menu hide event
136
+ if self.command_menu_active:
137
+ await self.on_event_emit(
138
+ EventType.COMMAND_MENU_HIDE,
139
+ {"reason": "manual_exit"},
140
+ "commands",
141
+ )
142
+
143
+ self.command_mode = CommandMode.NORMAL
144
+ self.command_menu_active = False
145
+
146
+ # Clear command buffer (remove the '/' and any partial command)
147
+ self.buffer_manager.clear()
148
+
149
+ # Update display
150
+ await self.on_display_update(force_render=True)
151
+
152
+ logger.info("Returned to normal input mode")
153
+
154
+ except Exception as e:
155
+ logger.error(f"Error exiting command mode: {e}")
156
+
157
+ async def _enter_status_modal_mode(self, ui_config) -> None:
158
+ """Enter status modal mode - modal confined to status area.
159
+
160
+ Args:
161
+ ui_config: Status modal configuration.
162
+ """
163
+ try:
164
+ # Set status modal mode
165
+ self.command_mode = CommandMode.STATUS_MODAL
166
+ self.current_status_modal_config = ui_config
167
+ logger.info(f"Entered status modal mode: {ui_config.title}")
168
+
169
+ # Unlike full modals, status modals don't take over the screen
170
+ # They just appear in the status area via the renderer
171
+ await self.on_display_update(force_render=True)
172
+
173
+ except Exception as e:
174
+ logger.error(f"Error entering status modal mode: {e}")
175
+ await self._exit_command_mode()
176
+
177
+ async def _exit_status_modal_mode(self) -> None:
178
+ """Exit status modal mode and return to normal input."""
179
+ try:
180
+ logger.info("Exiting status modal mode...")
181
+ self.command_mode = CommandMode.NORMAL
182
+ self.current_status_modal_config = None
183
+ logger.info("Status modal mode exited successfully")
184
+
185
+ # Refresh display to remove the status modal
186
+ await self.on_display_update(force_render=True)
187
+ logger.info("Display updated after status modal exit")
188
+
189
+ except Exception as e:
190
+ logger.error(f"Error exiting status modal mode: {e}")
191
+ self.command_mode = CommandMode.NORMAL
192
+
193
+ # Command Processing Methods (Phase 2B)
194
+ # =====================================
195
+
196
+ async def _handle_menu_popup_keypress(self, key_press: KeyPress) -> bool:
197
+ """Handle KeyPress during menu popup mode with arrow key navigation.
198
+
199
+ Args:
200
+ key_press: Parsed key press to process.
201
+
202
+ Returns:
203
+ True if key was handled.
204
+ """
205
+ try:
206
+ # Handle arrow key navigation
207
+ if key_press.name == "ArrowUp":
208
+ await self._navigate_menu("up")
209
+ return True
210
+ elif key_press.name == "ArrowDown":
211
+ await self._navigate_menu("down")
212
+ return True
213
+ elif key_press.name == "Enter":
214
+ await self._execute_selected_command()
215
+ return True
216
+ elif key_press.name == "Escape":
217
+ await self._exit_command_mode()
218
+ return True
219
+
220
+ # Handle printable characters (for filtering)
221
+ elif key_press.char and key_press.char.isprintable():
222
+ # Insert character for command filtering (routed from RawInputProcessor)
223
+ self.buffer_manager.insert_char(key_press.char)
224
+ await self._update_command_filter()
225
+ return True
226
+
227
+ # Handle backspace/delete
228
+ elif key_press.name in ["Backspace", "Delete"]:
229
+ # If buffer only has '/', exit command mode
230
+ if len(self.buffer_manager.content) <= 1:
231
+ await self._exit_command_mode()
232
+ return True
233
+ else:
234
+ # Remove character and update command filter
235
+ self.buffer_manager.delete_char()
236
+ await self._update_command_filter()
237
+ return True
238
+
239
+ # Other keys not handled
240
+ return False
241
+
242
+ except Exception as e:
243
+ logger.error(f"Error handling menu popup keypress: {e}")
244
+ await self._exit_command_mode()
245
+ return False
246
+
247
+ async def _update_command_filter(self) -> None:
248
+ """Update command menu based on current buffer content."""
249
+ try:
250
+ # Get current input (minus the leading '/')
251
+ current_input = self.buffer_manager.content
252
+ filter_text = (
253
+ current_input[1:] if current_input.startswith("/") else current_input
254
+ )
255
+
256
+ # Update menu renderer with filtered commands
257
+ filtered_commands = self.filter_commands(filter_text)
258
+
259
+ # Reset selection when filtering
260
+ self.selected_command_index = 0
261
+ self.command_menu_renderer.set_selected_index(
262
+ self.selected_command_index
263
+ )
264
+ self.command_menu_renderer.filter_commands(
265
+ filtered_commands, filter_text
266
+ )
267
+
268
+ # Emit filter update event
269
+ await self.on_event_emit(
270
+ EventType.COMMAND_MENU_FILTER,
271
+ {
272
+ "filter_text": filter_text,
273
+ "available_commands": self.get_available_commands(),
274
+ "filtered_commands": filtered_commands,
275
+ },
276
+ "commands",
277
+ )
278
+
279
+ # Update display
280
+ await self.on_display_update(force_render=True)
281
+
282
+ except Exception as e:
283
+ logger.error(f"Error updating command filter: {e}")
284
+
285
+ async def _navigate_menu(self, direction: str) -> None:
286
+ """Navigate the command menu up or down.
287
+
288
+ Args:
289
+ direction: "up" or "down"
290
+ """
291
+ try:
292
+ # Get current filtered commands
293
+ current_input = self.buffer_manager.content
294
+ filter_text = (
295
+ current_input[1:] if current_input.startswith("/") else current_input
296
+ )
297
+ filtered_commands = self.filter_commands(filter_text)
298
+
299
+ if not filtered_commands:
300
+ return
301
+
302
+ # Update selection index
303
+ if direction == "up":
304
+ self.selected_command_index = max(0, self.selected_command_index - 1)
305
+ elif direction == "down":
306
+ self.selected_command_index = min(
307
+ len(filtered_commands) - 1, self.selected_command_index + 1
308
+ )
309
+
310
+ # Update menu renderer with new selection (don't reset selection during navigation)
311
+ self.command_menu_renderer.set_selected_index(
312
+ self.selected_command_index
313
+ )
314
+ self.command_menu_renderer.filter_commands(
315
+ filtered_commands, filter_text, reset_selection=False
316
+ )
317
+
318
+ # Note: No need to call _update_display - filter_commands already renders the menu
319
+
320
+ except Exception as e:
321
+ logger.error(f"Error navigating menu: {e}")
322
+
323
+ async def _execute_selected_command(self) -> None:
324
+ """Execute the currently selected command."""
325
+ try:
326
+ # Get the selected command from the menu
327
+ selected_command = self.command_menu_renderer.get_selected_command()
328
+ if not selected_command:
329
+ logger.warning("No command selected")
330
+ await self._exit_command_mode()
331
+ return
332
+
333
+ # Create command string from selected command
334
+ command_string = f"/{selected_command['name']}"
335
+
336
+ # Parse the command
337
+ command = self.slash_parser.parse_command(command_string)
338
+ if command:
339
+ logger.info(f"🚀 Executing selected command: {command.name}")
340
+
341
+ # Exit command mode first
342
+ await self._exit_command_mode()
343
+
344
+ # Execute the command using delegation
345
+ result = await self.execute_command(command)
346
+
347
+ # Handle the result
348
+ if result.success:
349
+ logger.info(f"✅ Command {command.name} completed successfully")
350
+
351
+ # Modal display is handled by event bus trigger, not here
352
+ if result.message:
353
+ # Display success message in status area
354
+ logger.info(f"Command result: {result.message}")
355
+ # TODO: Display in status area
356
+ else:
357
+ logger.warning(
358
+ f"❌ Command {command.name} failed: {result.message}"
359
+ )
360
+ # TODO: Display error message in status area
361
+ else:
362
+ logger.warning("Failed to parse selected command")
363
+ await self._exit_command_mode()
364
+
365
+ except Exception as e:
366
+ logger.error(f"Error executing command: {e}")
367
+ await self._exit_command_mode()
368
+
369
+ # Public Interface Methods
370
+ # =======================
371
+
372
+ async def handle_mode_transition(self, new_mode: CommandMode, **kwargs) -> None:
373
+ """Handle transition to a new command mode."""
374
+ if new_mode == CommandMode.MENU_POPUP:
375
+ await self._enter_command_mode()
376
+ elif new_mode == CommandMode.STATUS_MODAL:
377
+ ui_config = kwargs.get("ui_config")
378
+ await self._enter_status_modal_mode(ui_config)
379
+ elif new_mode == CommandMode.NORMAL:
380
+ if self.command_mode == CommandMode.MENU_POPUP:
381
+ await self._exit_command_mode()
382
+ elif self.command_mode == CommandMode.STATUS_MODAL:
383
+ await self._exit_status_modal_mode()
384
+
385
+ async def handle_command_mode_keypress(self, key_press: KeyPress) -> bool:
386
+ """Handle keypress while in command mode."""
387
+ if self.command_mode == CommandMode.MENU_POPUP:
388
+ result = await self._handle_menu_popup_keypress(key_press)
389
+ # Notify mode change callback if mode changed
390
+ if hasattr(self, "on_mode_change") and self.on_mode_change:
391
+ await self.on_mode_change(self.command_mode)
392
+ return result
393
+ elif self.command_mode == CommandMode.STATUS_MODAL:
394
+ # For now, delegate status modal handling back to InputHandler
395
+ # This will be refined in future phases
396
+ return False
397
+
398
+ return False
399
+
400
+ def get_current_mode(self) -> CommandMode:
401
+ """Get the current command mode."""
402
+ return self.command_mode