kollabor 0.4.9__py3-none-any.whl → 0.4.15__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 (192) hide show
  1. agents/__init__.py +2 -0
  2. agents/coder/__init__.py +0 -0
  3. agents/coder/agent.json +4 -0
  4. agents/coder/api-integration.md +2150 -0
  5. agents/coder/cli-pretty.md +765 -0
  6. agents/coder/code-review.md +1092 -0
  7. agents/coder/database-design.md +1525 -0
  8. agents/coder/debugging.md +1102 -0
  9. agents/coder/dependency-management.md +1397 -0
  10. agents/coder/git-workflow.md +1099 -0
  11. agents/coder/refactoring.md +1454 -0
  12. agents/coder/security-hardening.md +1732 -0
  13. agents/coder/system_prompt.md +1448 -0
  14. agents/coder/tdd.md +1367 -0
  15. agents/creative-writer/__init__.py +0 -0
  16. agents/creative-writer/agent.json +4 -0
  17. agents/creative-writer/character-development.md +1852 -0
  18. agents/creative-writer/dialogue-craft.md +1122 -0
  19. agents/creative-writer/plot-structure.md +1073 -0
  20. agents/creative-writer/revision-editing.md +1484 -0
  21. agents/creative-writer/system_prompt.md +690 -0
  22. agents/creative-writer/worldbuilding.md +2049 -0
  23. agents/data-analyst/__init__.py +30 -0
  24. agents/data-analyst/agent.json +4 -0
  25. agents/data-analyst/data-visualization.md +992 -0
  26. agents/data-analyst/exploratory-data-analysis.md +1110 -0
  27. agents/data-analyst/pandas-data-manipulation.md +1081 -0
  28. agents/data-analyst/sql-query-optimization.md +881 -0
  29. agents/data-analyst/statistical-analysis.md +1118 -0
  30. agents/data-analyst/system_prompt.md +928 -0
  31. agents/default/__init__.py +0 -0
  32. agents/default/agent.json +4 -0
  33. agents/default/dead-code.md +794 -0
  34. agents/default/explore-agent-system.md +585 -0
  35. agents/default/system_prompt.md +1448 -0
  36. agents/kollabor/__init__.py +0 -0
  37. agents/kollabor/analyze-plugin-lifecycle.md +175 -0
  38. agents/kollabor/analyze-terminal-rendering.md +388 -0
  39. agents/kollabor/code-review.md +1092 -0
  40. agents/kollabor/debug-mcp-integration.md +521 -0
  41. agents/kollabor/debug-plugin-hooks.md +547 -0
  42. agents/kollabor/debugging.md +1102 -0
  43. agents/kollabor/dependency-management.md +1397 -0
  44. agents/kollabor/git-workflow.md +1099 -0
  45. agents/kollabor/inspect-llm-conversation.md +148 -0
  46. agents/kollabor/monitor-event-bus.md +558 -0
  47. agents/kollabor/profile-performance.md +576 -0
  48. agents/kollabor/refactoring.md +1454 -0
  49. agents/kollabor/system_prompt copy.md +1448 -0
  50. agents/kollabor/system_prompt.md +757 -0
  51. agents/kollabor/trace-command-execution.md +178 -0
  52. agents/kollabor/validate-config.md +879 -0
  53. agents/research/__init__.py +0 -0
  54. agents/research/agent.json +4 -0
  55. agents/research/architecture-mapping.md +1099 -0
  56. agents/research/codebase-analysis.md +1077 -0
  57. agents/research/dependency-audit.md +1027 -0
  58. agents/research/performance-profiling.md +1047 -0
  59. agents/research/security-review.md +1359 -0
  60. agents/research/system_prompt.md +492 -0
  61. agents/technical-writer/__init__.py +0 -0
  62. agents/technical-writer/agent.json +4 -0
  63. agents/technical-writer/api-documentation.md +2328 -0
  64. agents/technical-writer/changelog-management.md +1181 -0
  65. agents/technical-writer/readme-writing.md +1360 -0
  66. agents/technical-writer/style-guide.md +1410 -0
  67. agents/technical-writer/system_prompt.md +653 -0
  68. agents/technical-writer/tutorial-creation.md +1448 -0
  69. core/__init__.py +0 -2
  70. core/application.py +343 -88
  71. core/cli.py +229 -10
  72. core/commands/menu_renderer.py +463 -59
  73. core/commands/registry.py +14 -9
  74. core/commands/system_commands.py +2461 -14
  75. core/config/loader.py +151 -37
  76. core/config/service.py +18 -6
  77. core/events/bus.py +29 -9
  78. core/events/executor.py +205 -75
  79. core/events/models.py +27 -8
  80. core/fullscreen/command_integration.py +20 -24
  81. core/fullscreen/components/__init__.py +10 -1
  82. core/fullscreen/components/matrix_components.py +1 -2
  83. core/fullscreen/components/space_shooter_components.py +654 -0
  84. core/fullscreen/plugin.py +5 -0
  85. core/fullscreen/renderer.py +52 -13
  86. core/fullscreen/session.py +52 -15
  87. core/io/__init__.py +29 -5
  88. core/io/buffer_manager.py +6 -1
  89. core/io/config_status_view.py +7 -29
  90. core/io/core_status_views.py +267 -347
  91. core/io/input/__init__.py +25 -0
  92. core/io/input/command_mode_handler.py +711 -0
  93. core/io/input/display_controller.py +128 -0
  94. core/io/input/hook_registrar.py +286 -0
  95. core/io/input/input_loop_manager.py +421 -0
  96. core/io/input/key_press_handler.py +502 -0
  97. core/io/input/modal_controller.py +1011 -0
  98. core/io/input/paste_processor.py +339 -0
  99. core/io/input/status_modal_renderer.py +184 -0
  100. core/io/input_errors.py +5 -1
  101. core/io/input_handler.py +211 -2452
  102. core/io/key_parser.py +7 -0
  103. core/io/layout.py +15 -3
  104. core/io/message_coordinator.py +111 -2
  105. core/io/message_renderer.py +129 -4
  106. core/io/status_renderer.py +147 -607
  107. core/io/terminal_renderer.py +97 -51
  108. core/io/terminal_state.py +21 -4
  109. core/io/visual_effects.py +816 -165
  110. core/llm/agent_manager.py +1063 -0
  111. core/llm/api_adapters/__init__.py +44 -0
  112. core/llm/api_adapters/anthropic_adapter.py +432 -0
  113. core/llm/api_adapters/base.py +241 -0
  114. core/llm/api_adapters/openai_adapter.py +326 -0
  115. core/llm/api_communication_service.py +167 -113
  116. core/llm/conversation_logger.py +322 -16
  117. core/llm/conversation_manager.py +556 -30
  118. core/llm/file_operations_executor.py +84 -32
  119. core/llm/llm_service.py +934 -103
  120. core/llm/mcp_integration.py +541 -57
  121. core/llm/message_display_service.py +135 -18
  122. core/llm/plugin_sdk.py +1 -2
  123. core/llm/profile_manager.py +1183 -0
  124. core/llm/response_parser.py +274 -56
  125. core/llm/response_processor.py +16 -3
  126. core/llm/tool_executor.py +6 -1
  127. core/logging/__init__.py +2 -0
  128. core/logging/setup.py +34 -6
  129. core/models/resume.py +54 -0
  130. core/plugins/__init__.py +4 -2
  131. core/plugins/base.py +127 -0
  132. core/plugins/collector.py +23 -161
  133. core/plugins/discovery.py +37 -3
  134. core/plugins/factory.py +6 -12
  135. core/plugins/registry.py +5 -17
  136. core/ui/config_widgets.py +128 -28
  137. core/ui/live_modal_renderer.py +2 -1
  138. core/ui/modal_actions.py +5 -0
  139. core/ui/modal_overlay_renderer.py +0 -60
  140. core/ui/modal_renderer.py +268 -7
  141. core/ui/modal_state_manager.py +29 -4
  142. core/ui/widgets/base_widget.py +7 -0
  143. core/updates/__init__.py +10 -0
  144. core/updates/version_check_service.py +348 -0
  145. core/updates/version_comparator.py +103 -0
  146. core/utils/config_utils.py +685 -526
  147. core/utils/plugin_utils.py +1 -1
  148. core/utils/session_naming.py +111 -0
  149. fonts/LICENSE +21 -0
  150. fonts/README.md +46 -0
  151. fonts/SymbolsNerdFont-Regular.ttf +0 -0
  152. fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
  153. fonts/__init__.py +44 -0
  154. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
  155. kollabor-0.4.15.dist-info/RECORD +228 -0
  156. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
  157. plugins/agent_orchestrator/__init__.py +39 -0
  158. plugins/agent_orchestrator/activity_monitor.py +181 -0
  159. plugins/agent_orchestrator/file_attacher.py +77 -0
  160. plugins/agent_orchestrator/message_injector.py +135 -0
  161. plugins/agent_orchestrator/models.py +48 -0
  162. plugins/agent_orchestrator/orchestrator.py +403 -0
  163. plugins/agent_orchestrator/plugin.py +976 -0
  164. plugins/agent_orchestrator/xml_parser.py +191 -0
  165. plugins/agent_orchestrator_plugin.py +9 -0
  166. plugins/enhanced_input/box_styles.py +1 -0
  167. plugins/enhanced_input/color_engine.py +19 -4
  168. plugins/enhanced_input/config.py +2 -2
  169. plugins/enhanced_input_plugin.py +61 -11
  170. plugins/fullscreen/__init__.py +6 -2
  171. plugins/fullscreen/example_plugin.py +1035 -222
  172. plugins/fullscreen/setup_wizard_plugin.py +592 -0
  173. plugins/fullscreen/space_shooter_plugin.py +131 -0
  174. plugins/hook_monitoring_plugin.py +436 -78
  175. plugins/query_enhancer_plugin.py +66 -30
  176. plugins/resume_conversation_plugin.py +1494 -0
  177. plugins/save_conversation_plugin.py +98 -32
  178. plugins/system_commands_plugin.py +70 -56
  179. plugins/tmux_plugin.py +154 -78
  180. plugins/workflow_enforcement_plugin.py +94 -92
  181. system_prompt/default.md +952 -886
  182. core/io/input_mode_manager.py +0 -402
  183. core/io/modal_interaction_handler.py +0 -315
  184. core/io/raw_input_processor.py +0 -946
  185. core/storage/__init__.py +0 -5
  186. core/storage/state_manager.py +0 -84
  187. core/ui/widget_integration.py +0 -222
  188. core/utils/key_reader.py +0 -171
  189. kollabor-0.4.9.dist-info/RECORD +0 -128
  190. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
  191. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
  192. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,421 @@
1
+ """Input loop manager for Kollabor CLI.
2
+
3
+ This module handles the main input processing loop, platform-specific I/O,
4
+ and coordinates between raw terminal input and character processing.
5
+ """
6
+
7
+ import asyncio
8
+ import logging
9
+ import sys
10
+ from typing import Any, Callable, Optional, TYPE_CHECKING
11
+
12
+ from .paste_processor import PasteProcessor
13
+ from ..input_errors import InputErrorHandler, ErrorType, ErrorSeverity
14
+
15
+ if TYPE_CHECKING:
16
+ from ..key_parser import KeyParser, KeyPress
17
+ from ...events.models import CommandMode
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Platform-specific imports
22
+ IS_WINDOWS = sys.platform == "win32"
23
+
24
+ if IS_WINDOWS:
25
+ import msvcrt
26
+ else:
27
+ import select
28
+
29
+
30
+ class InputLoopManager:
31
+ """Manages the main input processing loop.
32
+
33
+ Responsibilities:
34
+ - Main input loop execution
35
+ - Platform-specific input checking (Windows/Unix)
36
+ - Chunk reading and routing
37
+ - Start/stop lifecycle
38
+ - Error handling and recovery
39
+ - Windows extended key mapping (arrow keys, F1-F12, etc.)
40
+ """
41
+
42
+ # Windows extended key code mapping to ANSI escape sequences
43
+ WIN_KEY_MAP = {
44
+ 72: b"\x1b[A", # ArrowUp
45
+ 80: b"\x1b[B", # ArrowDown
46
+ 75: b"\x1b[D", # ArrowLeft
47
+ 77: b"\x1b[C", # ArrowRight
48
+ 71: b"\x1b[H", # Home
49
+ 79: b"\x1b[F", # End
50
+ 73: b"\x1b[5~", # PageUp
51
+ 81: b"\x1b[6~", # PageDown
52
+ 82: b"\x1b[2~", # Insert
53
+ 83: b"\x1b[3~", # Delete
54
+ 59: b"\x1bOP", # F1
55
+ 60: b"\x1bOQ", # F2
56
+ 61: b"\x1bOR", # F3
57
+ 62: b"\x1bOS", # F4
58
+ 63: b"\x1b[15~", # F5
59
+ 64: b"\x1b[17~", # F6
60
+ 65: b"\x1b[18~", # F7
61
+ 66: b"\x1b[19~", # F8
62
+ 67: b"\x1b[20~", # F9
63
+ 68: b"\x1b[21~", # F10
64
+ 133: b"\x1b[23~", # F11
65
+ 134: b"\x1b[24~", # F12
66
+ }
67
+
68
+ def __init__(
69
+ self,
70
+ renderer: Any,
71
+ key_parser: "KeyParser",
72
+ error_handler: InputErrorHandler,
73
+ paste_processor: PasteProcessor,
74
+ config: Any,
75
+ ) -> None:
76
+ """Initialize the input loop manager.
77
+
78
+ Args:
79
+ renderer: Terminal renderer for raw mode control.
80
+ key_parser: Key parser for escape sequence handling.
81
+ error_handler: Error handler for error recovery.
82
+ paste_processor: Paste processor for paste detection.
83
+ config: Configuration manager for timing settings.
84
+ """
85
+ self.renderer = renderer
86
+ self.key_parser = key_parser
87
+ self.error_handler = error_handler
88
+ self.paste_processor = paste_processor
89
+ self.config = config
90
+
91
+ # State
92
+ self.running = False
93
+
94
+ # Config values
95
+ self.polling_delay = config.get("input.polling_delay", 0.01)
96
+ self.error_delay = config.get("input.error_delay", 0.1)
97
+
98
+ # Callbacks (set by parent)
99
+ self._process_character_callback: Optional[Callable] = None
100
+ self._handle_key_press_callback: Optional[Callable] = None
101
+ self._handle_command_mode_keypress_callback: Optional[Callable] = None
102
+ self._handle_live_modal_keypress_callback: Optional[Callable] = None
103
+ self._register_hooks_callback: Optional[Callable] = None
104
+ self._get_command_mode_callback: Optional[Callable] = None
105
+
106
+ # Reference to buffer_manager for cleanup error context
107
+ self._buffer_manager: Optional[Any] = None
108
+
109
+ def set_callbacks(
110
+ self,
111
+ process_character: Callable,
112
+ handle_key_press: Callable,
113
+ handle_command_mode_keypress: Callable,
114
+ handle_live_modal_keypress: Callable,
115
+ register_hooks: Callable,
116
+ get_command_mode: Callable,
117
+ ) -> None:
118
+ """Set callback functions for input processing.
119
+
120
+ Args:
121
+ process_character: Callback for processing individual characters.
122
+ handle_key_press: Callback for handling parsed key presses.
123
+ handle_command_mode_keypress: Callback for command mode key handling.
124
+ handle_live_modal_keypress: Callback for live modal key handling.
125
+ register_hooks: Callback to register all hooks.
126
+ get_command_mode: Callback to get current command mode.
127
+ """
128
+ self._process_character_callback = process_character
129
+ self._handle_key_press_callback = handle_key_press
130
+ self._handle_command_mode_keypress_callback = handle_command_mode_keypress
131
+ self._handle_live_modal_keypress_callback = handle_live_modal_keypress
132
+ self._register_hooks_callback = register_hooks
133
+ self._get_command_mode_callback = get_command_mode
134
+
135
+ def set_buffer_manager(self, buffer_manager: Any) -> None:
136
+ """Set buffer manager reference for error context.
137
+
138
+ Args:
139
+ buffer_manager: Buffer manager instance.
140
+ """
141
+ self._buffer_manager = buffer_manager
142
+
143
+ async def start(self) -> None:
144
+ """Start the input handling loop."""
145
+ self.running = True
146
+ self.renderer.enter_raw_mode()
147
+
148
+ # Check if raw mode worked
149
+ if (
150
+ getattr(
151
+ self.renderer.terminal_state.current_mode,
152
+ "value",
153
+ self.renderer.terminal_state.current_mode,
154
+ )
155
+ != "raw"
156
+ ):
157
+ logger.warning("Raw mode failed - using fallback ESC detection")
158
+
159
+ # Register all hooks via callback
160
+ if self._register_hooks_callback:
161
+ await self._register_hooks_callback()
162
+
163
+ logger.info("Input handler started")
164
+ await self._input_loop()
165
+
166
+ async def stop(self) -> None:
167
+ """Stop the input handling loop with cleanup."""
168
+ self.running = False
169
+ await self.cleanup()
170
+ self.renderer.exit_raw_mode()
171
+ logger.info("Input handler stopped")
172
+
173
+ async def _input_loop(self) -> None:
174
+ """Main input processing loop with enhanced error handling."""
175
+ from ...events.models import CommandMode
176
+
177
+ while self.running:
178
+ try:
179
+ # Platform-specific input checking
180
+ has_input = await self._check_input_available()
181
+
182
+ if has_input:
183
+ # Read input data
184
+ chunk = await self._read_input_chunk()
185
+
186
+ if not chunk:
187
+ await asyncio.sleep(self.polling_delay)
188
+ continue
189
+
190
+ # Check if this is an escape sequence (arrow keys, etc.)
191
+ if self._is_escape_sequence(chunk):
192
+ # Escape sequence - process character by character
193
+ logger.debug(
194
+ f"Processing escape sequence "
195
+ f"character-by-character: {repr(chunk)}"
196
+ )
197
+ for char in chunk:
198
+ if self._process_character_callback:
199
+ await self._process_character_callback(char)
200
+ elif len(chunk) > 10 and not self._is_in_modal_mode():
201
+ # PRIMARY PASTE DETECTION:
202
+ # Large chunk detection (ONLY when not in modal mode)
203
+ # Skip paste detection in modals to allow normal pasting into form fields
204
+ await self._handle_paste_chunk(chunk)
205
+ else:
206
+ # Normal input - process each character individually
207
+ # This also handles paste in modal mode (chars go to form fields)
208
+ logger.info(
209
+ f"Processing normal input "
210
+ f"character-by-character: {repr(chunk)}"
211
+ )
212
+ for char in chunk:
213
+ if self._process_character_callback:
214
+ await self._process_character_callback(char)
215
+ else:
216
+ # No input available - check for standalone ESC key
217
+ esc_key = self.key_parser.check_for_standalone_escape()
218
+ if esc_key:
219
+ logger.info("DETECTED STANDALONE ESC KEY!")
220
+ await self._route_escape_key(esc_key)
221
+
222
+ await asyncio.sleep(self.polling_delay)
223
+
224
+ except KeyboardInterrupt:
225
+ logger.info("Ctrl+C received")
226
+ raise
227
+ except OSError as e:
228
+ await self.error_handler.handle_error(
229
+ ErrorType.IO_ERROR,
230
+ f"I/O error in input loop: {e}",
231
+ ErrorSeverity.HIGH,
232
+ {"buffer_manager": self._buffer_manager},
233
+ )
234
+ await asyncio.sleep(self.error_delay)
235
+ except Exception as e:
236
+ await self.error_handler.handle_error(
237
+ ErrorType.SYSTEM_ERROR,
238
+ f"Unexpected error in input loop: {e}",
239
+ ErrorSeverity.MEDIUM,
240
+ {"buffer_manager": self._buffer_manager},
241
+ )
242
+ await asyncio.sleep(self.error_delay)
243
+
244
+ async def _route_escape_key(self, esc_key: "KeyPress") -> None:
245
+ """Route escape key to correct handler based on mode.
246
+
247
+ Args:
248
+ esc_key: The parsed escape key press.
249
+ """
250
+ from ...events.models import CommandMode
251
+
252
+ command_mode = None
253
+ if self._get_command_mode_callback:
254
+ command_mode = self._get_command_mode_callback()
255
+
256
+ if command_mode in (CommandMode.MODAL, CommandMode.STATUS_MODAL):
257
+ if self._handle_command_mode_keypress_callback:
258
+ await self._handle_command_mode_keypress_callback(esc_key)
259
+ elif command_mode == CommandMode.LIVE_MODAL:
260
+ if self._handle_live_modal_keypress_callback:
261
+ await self._handle_live_modal_keypress_callback(esc_key)
262
+ else:
263
+ if self._handle_key_press_callback:
264
+ await self._handle_key_press_callback(esc_key)
265
+
266
+ async def _handle_paste_chunk(self, chunk: str) -> None:
267
+ """Handle a large chunk as pasted content.
268
+
269
+ Args:
270
+ chunk: The input chunk to process as paste.
271
+ """
272
+ import time
273
+
274
+ current_time = time.time()
275
+
276
+ # Check if this continues the current paste (within 100ms)
277
+ if self.paste_processor.should_merge_paste(current_time, threshold=0.1):
278
+ # Merge with existing paste
279
+ self.paste_processor.append_to_current_paste(chunk, current_time)
280
+ # Update the placeholder to show new size
281
+ await self.paste_processor.update_paste_placeholder()
282
+ else:
283
+ # New paste - store immediately
284
+ paste_id = self.paste_processor.start_new_paste(chunk, current_time)
285
+ # Create placeholder immediately
286
+ await self.paste_processor.create_paste_placeholder(paste_id)
287
+
288
+ def _is_escape_sequence(self, text: str) -> bool:
289
+ """Check if input is an escape sequence that should bypass paste detection.
290
+
291
+ Args:
292
+ text: Input text to check.
293
+
294
+ Returns:
295
+ True if text is an escape sequence, False otherwise.
296
+ """
297
+ if not text:
298
+ return False
299
+ # Common escape sequences start with ESC (\x1b)
300
+ return text.startswith("\x1b")
301
+
302
+ def _is_in_modal_mode(self) -> bool:
303
+ """Check if we're currently in a modal/fullscreen mode.
304
+
305
+ Paste detection is disabled in modal mode to allow normal
306
+ pasting into form fields and text inputs.
307
+
308
+ Returns:
309
+ True if in modal mode, False otherwise.
310
+ """
311
+ from ...events.models import CommandMode
312
+
313
+ if not self._get_command_mode_callback:
314
+ return False
315
+
316
+ command_mode = self._get_command_mode_callback()
317
+ # Disable paste detection for all modal types
318
+ return command_mode in (
319
+ CommandMode.MODAL,
320
+ CommandMode.STATUS_MODAL,
321
+ CommandMode.LIVE_MODAL,
322
+ )
323
+
324
+ async def _check_input_available(self) -> bool:
325
+ """Check if input is available (cross-platform).
326
+
327
+ Returns:
328
+ True if input is available, False otherwise.
329
+ """
330
+ if IS_WINDOWS:
331
+ # Windows: Use msvcrt.kbhit() to check for available input
332
+ return msvcrt.kbhit()
333
+ else:
334
+ # Unix: Use select with timeout
335
+ return bool(select.select([sys.stdin], [], [], self.polling_delay)[0])
336
+
337
+ async def _read_input_chunk(self) -> str:
338
+ """Read available input data (cross-platform).
339
+
340
+ Returns:
341
+ Decoded input string, or empty string if no input.
342
+ """
343
+ import os
344
+
345
+ if IS_WINDOWS:
346
+ return await self._read_windows_input()
347
+ else:
348
+ return await self._read_unix_input()
349
+
350
+ async def _read_windows_input(self) -> str:
351
+ """Read input on Windows platform.
352
+
353
+ Returns:
354
+ Decoded input string.
355
+ """
356
+ chunk = b""
357
+ while msvcrt.kbhit():
358
+ char = msvcrt.getch()
359
+ char_code = char[0] if isinstance(char, bytes) else ord(char)
360
+
361
+ # Handle Windows extended keys (arrow keys, function keys, etc.)
362
+ # Extended keys are prefixed with 0x00 or 0xE0 (224)
363
+ if char_code in (0, 224):
364
+ # Read the actual key code
365
+ ext_char = msvcrt.getch()
366
+ ext_code = ext_char[0] if isinstance(ext_char, bytes) else ord(ext_char)
367
+ # Map Windows extended key codes to ANSI escape sequences
368
+ if ext_code in self.WIN_KEY_MAP:
369
+ chunk += self.WIN_KEY_MAP[ext_code]
370
+ else:
371
+ logger.debug(f"Unknown Windows extended key: {ext_code}")
372
+ else:
373
+ chunk += char
374
+
375
+ # Small delay to allow for more input
376
+ await asyncio.sleep(0.001)
377
+ # Check if there's more data immediately available
378
+ if not msvcrt.kbhit():
379
+ break
380
+
381
+ return chunk.decode("utf-8", errors="ignore") if chunk else ""
382
+
383
+ async def _read_unix_input(self) -> str:
384
+ """Read input on Unix platform.
385
+
386
+ Returns:
387
+ Decoded input string.
388
+ """
389
+ import os
390
+
391
+ chunk = b""
392
+ while True:
393
+ try:
394
+ # Read in 8KB chunks
395
+ more_data = os.read(0, 8192)
396
+ if not more_data:
397
+ break
398
+ chunk += more_data
399
+ # Check if more data is immediately available
400
+ if not select.select([sys.stdin], [], [], 0.001)[0]:
401
+ break # No more data waiting
402
+ except OSError:
403
+ break # No more data available
404
+
405
+ return chunk.decode("utf-8", errors="ignore") if chunk else ""
406
+
407
+ async def cleanup(self) -> None:
408
+ """Perform cleanup operations."""
409
+ try:
410
+ # Clear old errors
411
+ cleared_errors = self.error_handler.clear_old_errors()
412
+ if cleared_errors > 0:
413
+ logger.info(f"Cleaned up {cleared_errors} old errors")
414
+
415
+ # Reset parser state
416
+ self.key_parser._reset_escape_state()
417
+
418
+ logger.debug("Input handler cleanup completed")
419
+
420
+ except Exception as e:
421
+ logger.error(f"Error during cleanup: {e}")