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
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
|