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,142 @@
1
+ """Box drawing styles for enhanced input plugin."""
2
+
3
+ import random
4
+ from dataclasses import dataclass
5
+ from typing import Dict, List
6
+
7
+
8
+ @dataclass
9
+ class BoxStyle:
10
+ """Represents a box drawing style with all character components."""
11
+
12
+ top_left: str
13
+ top_right: str
14
+ bottom_left: str
15
+ bottom_right: str
16
+ horizontal: str
17
+ vertical: str
18
+
19
+
20
+ class BoxStyleRegistry:
21
+ """Registry for managing and providing box drawing styles."""
22
+
23
+ def __init__(self):
24
+ """Initialize the registry with all available styles."""
25
+ self._styles = self._build_styles()
26
+ self._curated_random_styles = [
27
+ # User's confirmed favorites
28
+ "dots_only", "brackets", "dotted", "dashed", "square",
29
+ # Clean classics
30
+ "rounded", "double", "thick", "underline", "minimal",
31
+ # Sophisticated mixed-weight styles
32
+ "mixed_weight", "typography", "sophisticated", "editorial",
33
+ "clean_corners", "refined", "gradient_line",
34
+ # Clean minimal lines
35
+ "lines_only", "thick_lines", "double_lines"
36
+ ]
37
+
38
+ def get_style(self, name: str) -> BoxStyle:
39
+ """Get a style by name.
40
+
41
+ Args:
42
+ name: Style name.
43
+
44
+ Returns:
45
+ BoxStyle object.
46
+
47
+ Raises:
48
+ KeyError: If style doesn't exist.
49
+ """
50
+ if name not in self._styles:
51
+ raise KeyError(f"Unknown box style: {name}")
52
+ return self._styles[name]
53
+
54
+ def get_random_style(self) -> BoxStyle:
55
+ """Get a random style from curated collection.
56
+
57
+ Returns:
58
+ Random BoxStyle object.
59
+ """
60
+ style_name = random.choice(self._curated_random_styles)
61
+ return self.get_style(style_name)
62
+
63
+ def get_random_style_name(self) -> str:
64
+ """Get a random style name from curated collection.
65
+
66
+ Returns:
67
+ Random style name.
68
+ """
69
+ return random.choice(self._curated_random_styles)
70
+
71
+ def list_styles(self) -> List[str]:
72
+ """Get list of all available style names.
73
+
74
+ Returns:
75
+ List of style names.
76
+ """
77
+ return list(self._styles.keys())
78
+
79
+ def register_style(self, name: str, style: BoxStyle) -> None:
80
+ """Register a new style.
81
+
82
+ Args:
83
+ name: Style name.
84
+ style: BoxStyle object.
85
+ """
86
+ self._styles[name] = style
87
+
88
+ def _build_styles(self) -> Dict[str, BoxStyle]:
89
+ """Build the complete styles dictionary.
90
+
91
+ Returns:
92
+ Dictionary of all box styles.
93
+ """
94
+ return {
95
+ # Classic box styles
96
+ "rounded": BoxStyle("╭", "╮", "╰", "╯", "─", "│"),
97
+ "square": BoxStyle("┌", "┐", "└", "┘", "─", "│"),
98
+ "double": BoxStyle("╔", "╗", "╚", "╝", "═", "║"),
99
+ "thick": BoxStyle("┏", "┓", "┗", "┛", "━", "┃"),
100
+ "dotted": BoxStyle("┌", "┐", "└", "┘", "┄", "┆"),
101
+ "dashed": BoxStyle("┌", "┐", "└", "┘", "┅", "┇"),
102
+
103
+ # Minimal styles
104
+ "minimal": BoxStyle(" ", " ", " ", " ", "─", " "),
105
+ "brackets": BoxStyle("⌜", "⌝", "⌞", "⌟", " ", " "),
106
+ "underline": BoxStyle("", "", "", "", "_", ""),
107
+
108
+ # Line-only styles
109
+ "lines_only": BoxStyle("", "", "", "", "─", ""),
110
+ "thick_lines": BoxStyle("", "", "", "", "━", ""),
111
+ "double_lines": BoxStyle("", "", "", "", "═", ""),
112
+ "dots_only": BoxStyle("", "", "", "", "┄", ""),
113
+ "gradient_line": BoxStyle("", "", "", "", "▁", ""),
114
+
115
+ # Decorative styles
116
+ "stars": BoxStyle("★", "★", "★", "★", "✦", "✧"),
117
+ "arrows": BoxStyle("↖", "↗", "↙", "↘", "→", "↕"),
118
+ "diamonds": BoxStyle("◆", "◆", "◆", "◆", "◇", "◈"),
119
+ "circles": BoxStyle("●", "●", "●", "●", "○", "◐"),
120
+ "waves": BoxStyle("~", "~", "~", "~", "≈", "∿"),
121
+
122
+ # Mixed weight styles
123
+ "mixed_weight": BoxStyle("┏", "┓", "┗", "┛", "┄", "│"),
124
+ "typography": BoxStyle("┌", "┐", "└", "┘", "━", "┆"),
125
+ "sophisticated": BoxStyle("╭", "╮", "╰", "╯", "━", "┆"),
126
+ "editorial": BoxStyle("┌", "┐", "└", "┘", "─", ""),
127
+ "clean_corners": BoxStyle("┌", "┐", "└", "┘", "", ""),
128
+ "refined": BoxStyle("╭", "╮", "╰", "╯", "", ""),
129
+
130
+ # Futuristic styles
131
+ "neon": BoxStyle("▓", "▓", "▓", "▓", "▔", "▐"),
132
+ "cyber": BoxStyle("◢", "◣", "◥", "◤", "▬", "▌"),
133
+ "matrix": BoxStyle("╔", "╗", "╚", "╝", "▓", "▓"),
134
+ "holo": BoxStyle("◊", "◊", "◊", "◊", "◈", "◈"),
135
+ "quantum": BoxStyle("⟨", "⟩", "⟨", "⟩", "⟷", "⟁"),
136
+ "neural": BoxStyle("⊙", "⊙", "⊙", "⊙", "⊚", "⊜"),
137
+ "plasma": BoxStyle("◬", "◭", "◪", "◫", "◯", "◎"),
138
+ "circuit": BoxStyle("┫", "┣", "┳", "┻", "╋", "╂"),
139
+ "laser": BoxStyle("", "", "", "", "▇", ""),
140
+ "scan": BoxStyle("", "", "", "", "▓", ""),
141
+ "energy": BoxStyle("", "", "", "", "-", ""),
142
+ }
@@ -0,0 +1,165 @@
1
+ """Color and gradient engine for enhanced input plugin."""
2
+
3
+ from typing import List, Tuple
4
+
5
+
6
+ class ColorEngine:
7
+ """Handles all color and gradient operations for enhanced input rendering."""
8
+
9
+ def __init__(self, config):
10
+ """Initialize color engine.
11
+
12
+ Args:
13
+ config: InputConfig object with plugin configuration.
14
+ """
15
+ self.config = config
16
+
17
+ def apply_color(self, text: str, color_type: str) -> str:
18
+ """Apply color formatting to text.
19
+
20
+ Args:
21
+ text: Text to color.
22
+ color_type: Color type ('border', 'text', 'placeholder').
23
+
24
+ Returns:
25
+ Colored text with ANSI codes.
26
+ """
27
+ # Check if gradient mode is enabled
28
+ gradient_mode = self.config.gradient_mode
29
+
30
+ if gradient_mode:
31
+ return self._apply_gradient_color(text, color_type)
32
+ else:
33
+ return self._apply_standard_color(text, color_type)
34
+
35
+ def _apply_gradient_color(self, text: str, color_type: str) -> str:
36
+ """Apply gradient coloring to text.
37
+
38
+ Args:
39
+ text: Text to color.
40
+ color_type: Color type.
41
+
42
+ Returns:
43
+ Text with gradient colors.
44
+ """
45
+ gradient_colors = self.config.gradient_colors
46
+
47
+ # Apply gradient based on color type
48
+ if color_type == 'border' and self.config.border_gradient:
49
+ return self.apply_gradient(text, gradient_colors)
50
+ elif color_type == 'text' and self.config.text_gradient:
51
+ return self.apply_gradient(text, gradient_colors)
52
+ elif color_type == 'placeholder':
53
+ # Always apply dim to placeholder regardless of gradient
54
+ return f"\033[2m{text}\033[0m"
55
+
56
+ return text
57
+
58
+ def _apply_standard_color(self, text: str, color_type: str) -> str:
59
+ """Apply standard color formatting.
60
+
61
+ Args:
62
+ text: Text to color.
63
+ color_type: Color type.
64
+
65
+ Returns:
66
+ Colored text with ANSI codes.
67
+ """
68
+ color_config = getattr(self.config, f'{color_type}_color', 'default')
69
+
70
+ if color_config == 'dim':
71
+ return f"\033[2m{text}\033[0m"
72
+ elif color_config == 'bright':
73
+ return f"\033[1m{text}\033[0m"
74
+ elif color_config == 'default':
75
+ return text
76
+ else:
77
+ return text
78
+
79
+ def apply_gradient(self, text: str, gradient_colors: List[str], is_background: bool = False) -> str:
80
+ """Apply gradient colors to text.
81
+
82
+ Args:
83
+ text: Text to apply gradient to.
84
+ gradient_colors: List of hex color strings.
85
+ is_background: Whether to apply as background colors.
86
+
87
+ Returns:
88
+ Text with gradient ANSI codes.
89
+ """
90
+ if len(gradient_colors) < 2 or len(text) == 0:
91
+ return text
92
+
93
+ # Convert hex colors to RGB
94
+ rgb_colors = [self._hex_to_rgb(color) for color in gradient_colors]
95
+
96
+ # Apply gradient character by character
97
+ result = ""
98
+ text_length = len(text)
99
+
100
+ for i, char in enumerate(text):
101
+ # Calculate position in gradient (0.0 to 1.0)
102
+ position = i / max(1, text_length - 1)
103
+
104
+ # Find which color segment we're in
105
+ segment_size = 1.0 / (len(rgb_colors) - 1)
106
+ segment_index = min(int(position / segment_size), len(rgb_colors) - 2)
107
+ local_position = (position - segment_index * segment_size) / segment_size
108
+
109
+ # Interpolate between colors
110
+ color1 = rgb_colors[segment_index]
111
+ color2 = rgb_colors[segment_index + 1]
112
+ interpolated = self._interpolate_color(color1, color2, local_position)
113
+
114
+ # Apply color to character
115
+ ansi_code = self._rgb_to_ansi(*interpolated, is_background)
116
+ result += f"{ansi_code}{char}"
117
+
118
+ # Reset color at the end
119
+ result += "\033[0m"
120
+ return result
121
+
122
+ def _hex_to_rgb(self, hex_color: str) -> Tuple[int, int, int]:
123
+ """Convert hex color to RGB tuple.
124
+
125
+ Args:
126
+ hex_color: Hex color string (e.g., '#1e3a8a').
127
+
128
+ Returns:
129
+ RGB tuple (r, g, b).
130
+ """
131
+ hex_color = hex_color.lstrip('#')
132
+ return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
133
+
134
+ def _rgb_to_ansi(self, r: int, g: int, b: int, is_background: bool = False) -> str:
135
+ """Convert RGB to ANSI escape code.
136
+
137
+ Args:
138
+ r: Red component (0-255).
139
+ g: Green component (0-255).
140
+ b: Blue component (0-255).
141
+ is_background: Whether this is a background color.
142
+
143
+ Returns:
144
+ ANSI escape sequence.
145
+ """
146
+ if is_background:
147
+ return f"\033[48;2;{r};{g};{b}m"
148
+ else:
149
+ return f"\033[38;2;{r};{g};{b}m"
150
+
151
+ def _interpolate_color(self, color1: Tuple[int, int, int], color2: Tuple[int, int, int], factor: float) -> Tuple[int, int, int]:
152
+ """Interpolate between two RGB colors.
153
+
154
+ Args:
155
+ color1: First RGB color tuple.
156
+ color2: Second RGB color tuple.
157
+ factor: Interpolation factor (0.0 to 1.0).
158
+
159
+ Returns:
160
+ Interpolated RGB tuple.
161
+ """
162
+ r = int(color1[0] + (color2[0] - color1[0]) * factor)
163
+ g = int(color1[1] + (color2[1] - color1[1]) * factor)
164
+ b = int(color1[2] + (color2[2] - color1[2]) * factor)
165
+ return (r, g, b)
@@ -0,0 +1,150 @@
1
+ """Configuration management for Enhanced Input Plugin."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Dict, List, TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from core.config import ConfigManager
8
+
9
+
10
+ @dataclass
11
+ class InputConfig:
12
+ """Type-safe configuration for Enhanced Input Plugin."""
13
+
14
+ # Core settings
15
+ enabled: bool = True
16
+ style: str = "rounded"
17
+ width_mode: str = "auto"
18
+ placeholder: str = "Type your message here..."
19
+ show_placeholder: bool = True
20
+
21
+ # Dimensions
22
+ min_width: int = 60
23
+ max_width: int = 120
24
+ min_height: int = 3
25
+ max_height: int = 10
26
+
27
+ # Dynamic behavior
28
+ dynamic_sizing: bool = True
29
+ wrap_text: bool = True
30
+ randomize_style: bool = False
31
+ randomize_interval: float = 5.0
32
+
33
+ # Cursor settings
34
+ cursor_blink_rate: float = 0.5
35
+
36
+ # Status display
37
+ show_status: bool = True
38
+
39
+ # Color settings
40
+ border_color: str = "dim"
41
+ text_color: str = "dim"
42
+ placeholder_color: str = "dim"
43
+ gradient_mode: bool = True
44
+ gradient_colors: List[str] = None
45
+ border_gradient: bool = True
46
+ text_gradient: bool = True
47
+
48
+
49
+ def __post_init__(self):
50
+ """Initialize default gradient colors if not provided."""
51
+ if self.gradient_colors is None:
52
+ self.gradient_colors = ["#333333", "#999999", "#222222"]
53
+
54
+ @classmethod
55
+ def from_config_manager(cls, config: 'ConfigManager') -> 'InputConfig':
56
+ """Create InputConfig from ConfigManager.
57
+
58
+ Args:
59
+ config: Configuration manager instance.
60
+
61
+ Returns:
62
+ InputConfig instance with values from config.
63
+ """
64
+ colors_config = config.get("plugins.enhanced_input.colors", {})
65
+
66
+ # Ensure colors_config is a dictionary
67
+ if not isinstance(colors_config, dict):
68
+ colors_config = {} # Fallback to empty dict
69
+
70
+ return cls(
71
+ enabled=config.get("plugins.enhanced_input.enabled", True),
72
+ style=config.get("plugins.enhanced_input.style", "rounded"),
73
+ width_mode=config.get("plugins.enhanced_input.width", "auto"),
74
+ placeholder=config.get("plugins.enhanced_input.placeholder", "Type your message here..."),
75
+ show_placeholder=config.get("plugins.enhanced_input.show_placeholder", True),
76
+
77
+ min_width=config.get("plugins.enhanced_input.min_width", 60),
78
+ max_width=config.get("plugins.enhanced_input.max_width", 120),
79
+ min_height=config.get("plugins.enhanced_input.min_height", 3),
80
+ max_height=config.get("plugins.enhanced_input.max_height", 10),
81
+
82
+ dynamic_sizing=config.get("plugins.enhanced_input.dynamic_sizing", True),
83
+ wrap_text=config.get("plugins.enhanced_input.wrap_text", True),
84
+ randomize_style=config.get("plugins.enhanced_input.randomize_style", False),
85
+ randomize_interval=config.get("plugins.enhanced_input.randomize_interval", 5.0),
86
+
87
+ cursor_blink_rate=config.get("plugins.enhanced_input.cursor_blink_rate", 0.5),
88
+ show_status=config.get("plugins.enhanced_input.show_status", True),
89
+
90
+ border_color=colors_config.get("border", "dim"),
91
+ text_color=colors_config.get("text", "dim"),
92
+ placeholder_color=colors_config.get("placeholder", "dim"),
93
+ gradient_mode=colors_config.get("gradient_mode", True),
94
+ gradient_colors=colors_config.get("gradient_colors", ["#333333", "#999999", "#222222"]),
95
+ border_gradient=colors_config.get("border_gradient", True),
96
+ text_gradient=colors_config.get("text_gradient", True)
97
+ )
98
+
99
+ def get_color_config(self) -> Dict[str, Any]:
100
+ """Get color configuration as dictionary for ColorEngine.
101
+
102
+ Returns:
103
+ Dictionary with color configuration.
104
+ """
105
+ return {
106
+ "border": self.border_color,
107
+ "text": self.text_color,
108
+ "placeholder": self.placeholder_color,
109
+ "gradient_mode": self.gradient_mode,
110
+ "gradient_colors": self.gradient_colors,
111
+ "border_gradient": self.border_gradient,
112
+ "text_gradient": self.text_gradient
113
+ }
114
+
115
+ def get_default_config_dict(self) -> Dict[str, Any]:
116
+ """Get default configuration as dictionary for plugin registration.
117
+
118
+ Returns:
119
+ Default configuration dictionary.
120
+ """
121
+ return {
122
+ "plugins": {
123
+ "enhanced_input": {
124
+ "enabled": self.enabled,
125
+ "style": self.style,
126
+ "width": self.width_mode,
127
+ "placeholder": self.placeholder,
128
+ "show_placeholder": self.show_placeholder,
129
+ "min_width": self.min_width,
130
+ "max_width": self.max_width,
131
+ "randomize_style": self.randomize_style,
132
+ "randomize_interval": self.randomize_interval,
133
+ "dynamic_sizing": self.dynamic_sizing,
134
+ "min_height": self.min_height,
135
+ "max_height": self.max_height,
136
+ "wrap_text": self.wrap_text,
137
+ "colors": {
138
+ "border": self.border_color,
139
+ "text": self.text_color,
140
+ "placeholder": self.placeholder_color,
141
+ "gradient_mode": self.gradient_mode,
142
+ "gradient_colors": self.gradient_colors,
143
+ "border_gradient": self.border_gradient,
144
+ "text_gradient": self.text_gradient
145
+ },
146
+ "cursor_blink_rate": self.cursor_blink_rate,
147
+ "show_status": self.show_status
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,72 @@
1
+ """Cursor management for enhanced input plugin."""
2
+
3
+ import time
4
+
5
+
6
+ class CursorManager:
7
+ """Manages cursor positioning, blinking, and rendering."""
8
+
9
+ def __init__(self, config):
10
+ """Initialize cursor manager.
11
+
12
+ Args:
13
+ config: InputConfig object with plugin configuration.
14
+ """
15
+ self.config = config
16
+ self.cursor_visible = True
17
+ self.last_blink_time = 0
18
+ self.blink_interval = config.cursor_blink_rate
19
+
20
+ def insert_cursor(self, text: str, cursor_position: int, cursor_char: str = None) -> str:
21
+ """Insert cursor character at the specified position.
22
+
23
+ Args:
24
+ text: Text to insert cursor into.
25
+ cursor_position: Position to insert cursor (0-based).
26
+ cursor_char: Optional cursor character to use. If None, uses get_cursor_char().
27
+
28
+ Returns:
29
+ Text with cursor inserted.
30
+ """
31
+ if cursor_char is None:
32
+ cursor_char = self.get_cursor_char()
33
+ cursor_pos = max(0, min(cursor_position, len(text)))
34
+ return text[:cursor_pos] + cursor_char + text[cursor_pos:]
35
+
36
+ def get_cursor_char(self) -> str:
37
+ """Get the current cursor character.
38
+
39
+ Returns:
40
+ Cursor character ("▌" or " " based on blink state).
41
+ """
42
+ return "▌" if self.cursor_visible else " "
43
+
44
+ def update_blink_state(self, input_is_active: bool) -> None:
45
+ """Update cursor blinking state.
46
+
47
+ Args:
48
+ input_is_active: Whether input is currently active.
49
+ """
50
+ if not input_is_active:
51
+ self.cursor_visible = True
52
+ return
53
+
54
+ current_time = time.time()
55
+ if current_time - self.last_blink_time >= self.blink_interval:
56
+ self.cursor_visible = not self.cursor_visible
57
+ self.last_blink_time = current_time
58
+
59
+ def is_input_active(self, renderer) -> bool:
60
+ """Determine if input is currently active.
61
+
62
+ Args:
63
+ renderer: Terminal renderer object.
64
+
65
+ Returns:
66
+ True if input is active (not writing messages or thinking).
67
+ """
68
+ writing_messages = getattr(renderer, 'writing_messages', False)
69
+ thinking_active = getattr(renderer, 'thinking_active', False)
70
+ conversation_active = getattr(renderer, 'conversation_active', True)
71
+
72
+ return not writing_messages and not thinking_active and conversation_active
@@ -0,0 +1,81 @@
1
+ """Geometry calculations for enhanced input plugin."""
2
+
3
+ import shutil
4
+ from typing import List
5
+
6
+
7
+ class GeometryCalculator:
8
+ """Handles all layout and dimension calculations for enhanced input boxes."""
9
+
10
+ def __init__(self, config):
11
+ """Initialize geometry calculator.
12
+
13
+ Args:
14
+ config: InputConfig object with plugin configuration.
15
+ """
16
+ self.config = config
17
+
18
+ def calculate_box_width(self) -> int:
19
+ """Calculate the optimal box width.
20
+
21
+ Returns:
22
+ Box width in characters.
23
+ """
24
+ width_mode = self.config.width_mode
25
+
26
+ if width_mode == "auto":
27
+ terminal_width = self._get_terminal_width()
28
+ # Leave some margin on both sides
29
+ proposed_width = terminal_width - 4
30
+ # Apply min/max constraints
31
+ min_width = self.config.min_width
32
+ max_width = self.config.max_width
33
+ return max(min_width, min(max_width, proposed_width))
34
+ else:
35
+ # Fixed width mode - use configured width or sensible default
36
+ return getattr(self.config, 'fixed_width', 80)
37
+
38
+ def calculate_box_height(self, content_lines: List[str]) -> int:
39
+ """Calculate the box height based on content.
40
+
41
+ Args:
42
+ content_lines: List of content lines.
43
+
44
+ Returns:
45
+ Total box height including borders.
46
+ """
47
+ dynamic_sizing = self.config.dynamic_sizing
48
+
49
+ if not dynamic_sizing:
50
+ return 3 # Fixed: top border + content + bottom border
51
+
52
+ content_height = len(content_lines)
53
+ total_height = content_height + 2 # Add top and bottom borders
54
+
55
+ # Apply min/max constraints
56
+ min_height = self.config.min_height
57
+ max_height = self.config.max_height
58
+ return max(min_height, min(max_height, total_height))
59
+
60
+ def calculate_content_width(self, box_width: int) -> int:
61
+ """Calculate content area width.
62
+
63
+ Args:
64
+ box_width: Total box width.
65
+
66
+ Returns:
67
+ Content width (excluding borders and spaces).
68
+ """
69
+ # Width breakdown: │ + space + content + space + │ = box_width
70
+ return box_width - 4 # 2 border chars + 2 spaces
71
+
72
+ def _get_terminal_width(self) -> int:
73
+ """Get the current terminal width.
74
+
75
+ Returns:
76
+ Terminal width in columns.
77
+ """
78
+ try:
79
+ return shutil.get_terminal_size().columns
80
+ except:
81
+ return 80 # Default fallback