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
core/io/key_parser.py ADDED
@@ -0,0 +1,344 @@
1
+ import logging
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from typing import Optional, Dict
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class KeyType(Enum):
10
+ """Types of keyboard input."""
11
+
12
+ PRINTABLE = "printable"
13
+ CONTROL = "control"
14
+ SPECIAL = "special"
15
+ EXTENDED = "extended"
16
+
17
+
18
+ @dataclass
19
+ class KeyPress:
20
+ """Represents a parsed key press event.
21
+
22
+ Attributes:
23
+ name: Human-readable key name.
24
+ code: Character code or sequence.
25
+ char: Printable character (if applicable).
26
+ type: Type of key press.
27
+ modifiers: Any modifier keys pressed.
28
+ """
29
+
30
+ name: str
31
+ code: int | str
32
+ char: Optional[str] = None
33
+ type: KeyType = KeyType.PRINTABLE
34
+ modifiers: Dict[str, bool] = None
35
+
36
+ def __post_init__(self):
37
+ if self.modifiers is None:
38
+ self.modifiers = {
39
+ "ctrl": False,
40
+ "alt": False,
41
+ "shift": False,
42
+ "cmd": False,
43
+ }
44
+
45
+
46
+ class KeyParser:
47
+ """Parses terminal keyboard input into structured KeyPress events.
48
+
49
+ Handles single character input, control characters, and extended sequences
50
+ like arrow keys and function keys.
51
+ """
52
+
53
+ # Control character mappings
54
+ CONTROL_KEYS = {
55
+ 1: "Ctrl+A",
56
+ 2: "Ctrl+B",
57
+ 3: "Ctrl+C",
58
+ 4: "Ctrl+D",
59
+ 5: "Ctrl+E",
60
+ 6: "Ctrl+F",
61
+ 7: "Ctrl+G",
62
+ 8: "Ctrl+H",
63
+ 9: "Tab",
64
+ 10: "Ctrl+J",
65
+ 11: "Ctrl+K",
66
+ 12: "Ctrl+L",
67
+ 13: "Enter",
68
+ 14: "Ctrl+N",
69
+ 15: "Ctrl+O",
70
+ 16: "Ctrl+P",
71
+ 17: "Ctrl+Q",
72
+ 18: "Ctrl+R",
73
+ 19: "Ctrl+S",
74
+ 20: "Ctrl+T",
75
+ 21: "Ctrl+U",
76
+ 22: "Ctrl+V",
77
+ 23: "Ctrl+W",
78
+ 24: "Ctrl+X",
79
+ 25: "Ctrl+Y",
80
+ 26: "Ctrl+Z",
81
+ 27: "Escape",
82
+ 127: "Backspace",
83
+ }
84
+
85
+ # Extended sequence mappings (ESC sequences)
86
+ ESCAPE_SEQUENCES = {
87
+ # Basic arrow keys
88
+ "[A": "ArrowUp",
89
+ "[B": "ArrowDown",
90
+ "[C": "ArrowRight",
91
+ "[D": "ArrowLeft",
92
+ # Alt/Option+arrow keys (modifier 3)
93
+ "[1;3A": "Alt+ArrowUp",
94
+ "[1;3B": "Alt+ArrowDown",
95
+ "[1;3C": "Alt+ArrowRight",
96
+ "[1;3D": "Alt+ArrowLeft",
97
+ # Ctrl+arrow keys (common terminal sequences)
98
+ "[1;5A": "Ctrl+ArrowUp",
99
+ "[1;5B": "Ctrl+ArrowDown",
100
+ "[1;5C": "Ctrl+ArrowRight",
101
+ "[1;5D": "Ctrl+ArrowLeft",
102
+ # Alternative Ctrl+arrow sequences (some terminals)
103
+ "O5A": "Ctrl+ArrowUp",
104
+ "O5B": "Ctrl+ArrowDown",
105
+ "O5C": "Ctrl+ArrowRight",
106
+ "O5D": "Ctrl+ArrowLeft",
107
+ # Cmd+arrow keys (macOS sequences)
108
+ "[1;9A": "Cmd+ArrowUp",
109
+ "[1;9B": "Cmd+ArrowDown",
110
+ "[1;9C": "Cmd+ArrowRight",
111
+ "[1;9D": "Cmd+ArrowLeft",
112
+ # Shift+Tab (BackTab)
113
+ "[Z": "Shift+Tab",
114
+ # Other navigation keys
115
+ "[H": "Home",
116
+ "[F": "End",
117
+ "[3~": "Delete",
118
+ "[5~": "PageUp",
119
+ "[6~": "PageDown",
120
+ # Function keys
121
+ "OP": "F1",
122
+ "OQ": "F2",
123
+ "OR": "F3",
124
+ "OS": "F4",
125
+ "[15~": "F5",
126
+ "[17~": "F6",
127
+ "[18~": "F7",
128
+ "[19~": "F8",
129
+ "[20~": "F9",
130
+ "[21~": "F10",
131
+ "[23~": "F11",
132
+ "[24~": "F12",
133
+ # Bracketed paste
134
+ "[200~": "BracketedPasteStart",
135
+ "[201~": "BracketedPasteEnd",
136
+ }
137
+
138
+ def __init__(self):
139
+ """Initialize the key parser."""
140
+ self._escape_buffer = ""
141
+ self._in_escape_sequence = False
142
+
143
+ def parse_char(self, char: str) -> Optional[KeyPress]:
144
+ """Parse a single character into a KeyPress event.
145
+
146
+ Args:
147
+ char: Single character from terminal input.
148
+
149
+ Returns:
150
+ KeyPress event or None if part of incomplete sequence.
151
+ """
152
+ if not char:
153
+ return None
154
+
155
+ char_code = ord(char)
156
+
157
+ # Handle escape sequences
158
+ if self._in_escape_sequence:
159
+ return self._parse_escape_sequence(char)
160
+
161
+ # Start of escape sequence
162
+ if char_code == 27: # ESC
163
+ self._in_escape_sequence = True
164
+ self._escape_buffer = ""
165
+ # Return None to wait for potential sequence, but we'll handle standalone ESC elsewhere
166
+ return None
167
+
168
+ # Control characters
169
+ if char_code in self.CONTROL_KEYS:
170
+ name = self.CONTROL_KEYS[char_code]
171
+ return KeyPress(
172
+ name=name,
173
+ code=char_code,
174
+ type=KeyType.CONTROL,
175
+ modifiers=self._parse_modifiers(name),
176
+ )
177
+
178
+ # Printable characters
179
+ if 32 <= char_code <= 126:
180
+ return KeyPress(
181
+ name=char, code=char_code, char=char, type=KeyType.PRINTABLE
182
+ )
183
+
184
+ # Unknown/special characters
185
+ return KeyPress(
186
+ name=f"Key{char_code}",
187
+ code=char_code,
188
+ char=char,
189
+ type=KeyType.SPECIAL,
190
+ )
191
+
192
+ def _parse_escape_sequence(self, char: str) -> Optional[KeyPress]:
193
+ """Parse characters within an escape sequence.
194
+
195
+ Args:
196
+ char: Character in the escape sequence.
197
+
198
+ Returns:
199
+ KeyPress event when sequence is complete, None otherwise.
200
+ """
201
+ self._escape_buffer += char
202
+ logger.debug(
203
+ f"Escape sequence buffer: '{self._escape_buffer}' (char: '{char}', ord: {ord(char)})"
204
+ )
205
+
206
+ # Check for complete sequence
207
+ if self._escape_buffer in self.ESCAPE_SEQUENCES:
208
+ name = self.ESCAPE_SEQUENCES[self._escape_buffer]
209
+
210
+ # Determine key type and modifiers
211
+ if "Ctrl+" in name or "Cmd+" in name or "Alt+" in name:
212
+ key_type = KeyType.CONTROL
213
+ modifiers = self._parse_modifiers(name)
214
+ else:
215
+ key_type = KeyType.EXTENDED
216
+ modifiers = {
217
+ "ctrl": False,
218
+ "alt": False,
219
+ "shift": False,
220
+ "cmd": False,
221
+ }
222
+
223
+ key_press = KeyPress(
224
+ name=name,
225
+ code=f"ESC{self._escape_buffer}",
226
+ type=key_type,
227
+ modifiers=modifiers,
228
+ )
229
+ self._reset_escape_state()
230
+ logger.debug(
231
+ f"Escape sequence '{self._escape_buffer}' → {name} (type: {key_type})"
232
+ )
233
+ return key_press
234
+
235
+ # Check for incomplete sequence that could still match
236
+ possible_matches = [
237
+ seq
238
+ for seq in self.ESCAPE_SEQUENCES.keys()
239
+ if seq.startswith(self._escape_buffer)
240
+ ]
241
+
242
+ if possible_matches:
243
+ # Still building sequence
244
+ return None
245
+
246
+ # Check if this is Alt+key (ESC followed by a single printable char)
247
+ # This is how many terminals send Alt+key combinations
248
+ if len(self._escape_buffer) == 1:
249
+ alt_char = self._escape_buffer
250
+ char_code = ord(alt_char)
251
+ if 32 <= char_code <= 126: # Printable character
252
+ logger.debug(
253
+ f"Alt+key detected: Alt+{alt_char}"
254
+ )
255
+ result = KeyPress(
256
+ name=f"Alt+{alt_char}",
257
+ code=f"ESC{alt_char}",
258
+ char=alt_char,
259
+ type=KeyType.CONTROL,
260
+ modifiers={"ctrl": False, "alt": True, "shift": False, "cmd": False},
261
+ )
262
+ self._reset_escape_state()
263
+ return result
264
+
265
+ # Invalid sequence - treat as separate keys
266
+ logger.debug(
267
+ f" Unknown escape sequence: '{self._escape_buffer}' - treating as ESC+{self._escape_buffer}"
268
+ )
269
+ result = KeyPress(
270
+ name=f"ESC+{self._escape_buffer}",
271
+ code=f"ESC{self._escape_buffer}",
272
+ type=KeyType.SPECIAL,
273
+ )
274
+ self._reset_escape_state()
275
+ return result
276
+
277
+ def _parse_modifiers(self, key_name: str) -> Dict[str, bool]:
278
+ """Parse modifier keys from key name.
279
+
280
+ Args:
281
+ key_name: Name of the key.
282
+
283
+ Returns:
284
+ Dictionary of modifier states.
285
+ """
286
+ modifiers = {"ctrl": False, "alt": False, "shift": False, "cmd": False}
287
+
288
+ if "Ctrl+" in key_name:
289
+ modifiers["ctrl"] = True
290
+ if "Alt+" in key_name:
291
+ modifiers["alt"] = True
292
+ if "Shift+" in key_name:
293
+ modifiers["shift"] = True
294
+ if "Cmd+" in key_name:
295
+ modifiers["cmd"] = True
296
+
297
+ return modifiers
298
+
299
+ def _reset_escape_state(self):
300
+ """Reset escape sequence parsing state."""
301
+ self._in_escape_sequence = False
302
+ self._escape_buffer = ""
303
+
304
+ def check_for_standalone_escape(self) -> Optional[KeyPress]:
305
+ """Check if we have a standalone ESC key (no following sequence).
306
+
307
+ This should be called when we detect that input has paused
308
+ and we're in the middle of an escape sequence with empty buffer.
309
+
310
+ Returns:
311
+ KeyPress for standalone ESC if detected, None otherwise.
312
+ """
313
+ if self._in_escape_sequence and self._escape_buffer == "":
314
+ # We have a standalone ESC key
315
+ self._reset_escape_state()
316
+ return KeyPress(name="Escape", code=27, type=KeyType.CONTROL)
317
+ return None
318
+
319
+ def is_printable_char(self, key_press: KeyPress) -> bool:
320
+ """Check if key press represents a printable character.
321
+
322
+ Args:
323
+ key_press: The key press to check.
324
+
325
+ Returns:
326
+ True if the key press is a printable character.
327
+ """
328
+ return (
329
+ key_press.type == KeyType.PRINTABLE
330
+ and key_press.char is not None
331
+ and 32 <= ord(key_press.char) <= 126
332
+ )
333
+
334
+ def is_control_key(self, key_press: KeyPress, control_name: str) -> bool:
335
+ """Check if key press matches a specific control key.
336
+
337
+ Args:
338
+ key_press: The key press to check.
339
+ control_name: Name of control key (e.g., "Enter", "Ctrl+C").
340
+
341
+ Returns:
342
+ True if key press matches the control key.
343
+ """
344
+ return key_press.type == KeyType.CONTROL and key_press.name == control_name