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.
- agents/__init__.py +2 -0
- agents/coder/__init__.py +0 -0
- agents/coder/agent.json +4 -0
- agents/coder/api-integration.md +2150 -0
- agents/coder/cli-pretty.md +765 -0
- agents/coder/code-review.md +1092 -0
- agents/coder/database-design.md +1525 -0
- agents/coder/debugging.md +1102 -0
- agents/coder/dependency-management.md +1397 -0
- agents/coder/git-workflow.md +1099 -0
- agents/coder/refactoring.md +1454 -0
- agents/coder/security-hardening.md +1732 -0
- agents/coder/system_prompt.md +1448 -0
- agents/coder/tdd.md +1367 -0
- agents/creative-writer/__init__.py +0 -0
- agents/creative-writer/agent.json +4 -0
- agents/creative-writer/character-development.md +1852 -0
- agents/creative-writer/dialogue-craft.md +1122 -0
- agents/creative-writer/plot-structure.md +1073 -0
- agents/creative-writer/revision-editing.md +1484 -0
- agents/creative-writer/system_prompt.md +690 -0
- agents/creative-writer/worldbuilding.md +2049 -0
- agents/data-analyst/__init__.py +30 -0
- agents/data-analyst/agent.json +4 -0
- agents/data-analyst/data-visualization.md +992 -0
- agents/data-analyst/exploratory-data-analysis.md +1110 -0
- agents/data-analyst/pandas-data-manipulation.md +1081 -0
- agents/data-analyst/sql-query-optimization.md +881 -0
- agents/data-analyst/statistical-analysis.md +1118 -0
- agents/data-analyst/system_prompt.md +928 -0
- agents/default/__init__.py +0 -0
- agents/default/agent.json +4 -0
- agents/default/dead-code.md +794 -0
- agents/default/explore-agent-system.md +585 -0
- agents/default/system_prompt.md +1448 -0
- agents/kollabor/__init__.py +0 -0
- agents/kollabor/analyze-plugin-lifecycle.md +175 -0
- agents/kollabor/analyze-terminal-rendering.md +388 -0
- agents/kollabor/code-review.md +1092 -0
- agents/kollabor/debug-mcp-integration.md +521 -0
- agents/kollabor/debug-plugin-hooks.md +547 -0
- agents/kollabor/debugging.md +1102 -0
- agents/kollabor/dependency-management.md +1397 -0
- agents/kollabor/git-workflow.md +1099 -0
- agents/kollabor/inspect-llm-conversation.md +148 -0
- agents/kollabor/monitor-event-bus.md +558 -0
- agents/kollabor/profile-performance.md +576 -0
- agents/kollabor/refactoring.md +1454 -0
- agents/kollabor/system_prompt copy.md +1448 -0
- agents/kollabor/system_prompt.md +757 -0
- agents/kollabor/trace-command-execution.md +178 -0
- agents/kollabor/validate-config.md +879 -0
- agents/research/__init__.py +0 -0
- agents/research/agent.json +4 -0
- agents/research/architecture-mapping.md +1099 -0
- agents/research/codebase-analysis.md +1077 -0
- agents/research/dependency-audit.md +1027 -0
- agents/research/performance-profiling.md +1047 -0
- agents/research/security-review.md +1359 -0
- agents/research/system_prompt.md +492 -0
- agents/technical-writer/__init__.py +0 -0
- agents/technical-writer/agent.json +4 -0
- agents/technical-writer/api-documentation.md +2328 -0
- agents/technical-writer/changelog-management.md +1181 -0
- agents/technical-writer/readme-writing.md +1360 -0
- agents/technical-writer/style-guide.md +1410 -0
- agents/technical-writer/system_prompt.md +653 -0
- agents/technical-writer/tutorial-creation.md +1448 -0
- core/__init__.py +0 -2
- core/application.py +343 -88
- core/cli.py +229 -10
- core/commands/menu_renderer.py +463 -59
- core/commands/registry.py +14 -9
- core/commands/system_commands.py +2461 -14
- core/config/loader.py +151 -37
- core/config/service.py +18 -6
- core/events/bus.py +29 -9
- core/events/executor.py +205 -75
- core/events/models.py +27 -8
- core/fullscreen/command_integration.py +20 -24
- core/fullscreen/components/__init__.py +10 -1
- core/fullscreen/components/matrix_components.py +1 -2
- core/fullscreen/components/space_shooter_components.py +654 -0
- core/fullscreen/plugin.py +5 -0
- core/fullscreen/renderer.py +52 -13
- core/fullscreen/session.py +52 -15
- core/io/__init__.py +29 -5
- core/io/buffer_manager.py +6 -1
- core/io/config_status_view.py +7 -29
- core/io/core_status_views.py +267 -347
- core/io/input/__init__.py +25 -0
- core/io/input/command_mode_handler.py +711 -0
- core/io/input/display_controller.py +128 -0
- core/io/input/hook_registrar.py +286 -0
- core/io/input/input_loop_manager.py +421 -0
- core/io/input/key_press_handler.py +502 -0
- core/io/input/modal_controller.py +1011 -0
- core/io/input/paste_processor.py +339 -0
- core/io/input/status_modal_renderer.py +184 -0
- core/io/input_errors.py +5 -1
- core/io/input_handler.py +211 -2452
- core/io/key_parser.py +7 -0
- core/io/layout.py +15 -3
- core/io/message_coordinator.py +111 -2
- core/io/message_renderer.py +129 -4
- core/io/status_renderer.py +147 -607
- core/io/terminal_renderer.py +97 -51
- core/io/terminal_state.py +21 -4
- core/io/visual_effects.py +816 -165
- core/llm/agent_manager.py +1063 -0
- core/llm/api_adapters/__init__.py +44 -0
- core/llm/api_adapters/anthropic_adapter.py +432 -0
- core/llm/api_adapters/base.py +241 -0
- core/llm/api_adapters/openai_adapter.py +326 -0
- core/llm/api_communication_service.py +167 -113
- core/llm/conversation_logger.py +322 -16
- core/llm/conversation_manager.py +556 -30
- core/llm/file_operations_executor.py +84 -32
- core/llm/llm_service.py +934 -103
- core/llm/mcp_integration.py +541 -57
- core/llm/message_display_service.py +135 -18
- core/llm/plugin_sdk.py +1 -2
- core/llm/profile_manager.py +1183 -0
- core/llm/response_parser.py +274 -56
- core/llm/response_processor.py +16 -3
- core/llm/tool_executor.py +6 -1
- core/logging/__init__.py +2 -0
- core/logging/setup.py +34 -6
- core/models/resume.py +54 -0
- core/plugins/__init__.py +4 -2
- core/plugins/base.py +127 -0
- core/plugins/collector.py +23 -161
- core/plugins/discovery.py +37 -3
- core/plugins/factory.py +6 -12
- core/plugins/registry.py +5 -17
- core/ui/config_widgets.py +128 -28
- core/ui/live_modal_renderer.py +2 -1
- core/ui/modal_actions.py +5 -0
- core/ui/modal_overlay_renderer.py +0 -60
- core/ui/modal_renderer.py +268 -7
- core/ui/modal_state_manager.py +29 -4
- core/ui/widgets/base_widget.py +7 -0
- core/updates/__init__.py +10 -0
- core/updates/version_check_service.py +348 -0
- core/updates/version_comparator.py +103 -0
- core/utils/config_utils.py +685 -526
- core/utils/plugin_utils.py +1 -1
- core/utils/session_naming.py +111 -0
- fonts/LICENSE +21 -0
- fonts/README.md +46 -0
- fonts/SymbolsNerdFont-Regular.ttf +0 -0
- fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
- fonts/__init__.py +44 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
- kollabor-0.4.15.dist-info/RECORD +228 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
- plugins/agent_orchestrator/__init__.py +39 -0
- plugins/agent_orchestrator/activity_monitor.py +181 -0
- plugins/agent_orchestrator/file_attacher.py +77 -0
- plugins/agent_orchestrator/message_injector.py +135 -0
- plugins/agent_orchestrator/models.py +48 -0
- plugins/agent_orchestrator/orchestrator.py +403 -0
- plugins/agent_orchestrator/plugin.py +976 -0
- plugins/agent_orchestrator/xml_parser.py +191 -0
- plugins/agent_orchestrator_plugin.py +9 -0
- plugins/enhanced_input/box_styles.py +1 -0
- plugins/enhanced_input/color_engine.py +19 -4
- plugins/enhanced_input/config.py +2 -2
- plugins/enhanced_input_plugin.py +61 -11
- plugins/fullscreen/__init__.py +6 -2
- plugins/fullscreen/example_plugin.py +1035 -222
- plugins/fullscreen/setup_wizard_plugin.py +592 -0
- plugins/fullscreen/space_shooter_plugin.py +131 -0
- plugins/hook_monitoring_plugin.py +436 -78
- plugins/query_enhancer_plugin.py +66 -30
- plugins/resume_conversation_plugin.py +1494 -0
- plugins/save_conversation_plugin.py +98 -32
- plugins/system_commands_plugin.py +70 -56
- plugins/tmux_plugin.py +154 -78
- plugins/workflow_enforcement_plugin.py +94 -92
- system_prompt/default.md +952 -886
- core/io/input_mode_manager.py +0 -402
- core/io/modal_interaction_handler.py +0 -315
- core/io/raw_input_processor.py +0 -946
- core/storage/__init__.py +0 -5
- core/storage/state_manager.py +0 -84
- core/ui/widget_integration.py +0 -222
- core/utils/key_reader.py +0 -171
- kollabor-0.4.9.dist-info/RECORD +0 -128
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
core/io/visual_effects.py
CHANGED
|
@@ -1,11 +1,195 @@
|
|
|
1
|
-
"""Visual effects system for terminal rendering.
|
|
1
|
+
"""Visual effects system for terminal rendering.
|
|
2
2
|
|
|
3
|
+
This module provides comprehensive visual effects for terminal rendering,
|
|
4
|
+
including gradient effects, shimmer animations, color palettes,
|
|
5
|
+
and banner generation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
3
9
|
import re
|
|
4
10
|
from dataclasses import dataclass, field
|
|
5
11
|
from enum import Enum
|
|
6
12
|
from typing import List, Tuple, Dict, Any
|
|
7
13
|
|
|
8
14
|
|
|
15
|
+
class ColorSupport(Enum):
|
|
16
|
+
"""Terminal color support levels."""
|
|
17
|
+
|
|
18
|
+
NONE = 0 # No color support
|
|
19
|
+
BASIC = 1 # 16 colors (4-bit)
|
|
20
|
+
EXTENDED = 2 # 256 colors (8-bit)
|
|
21
|
+
TRUE_COLOR = 3 # 16 million colors (24-bit RGB)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def detect_color_support() -> ColorSupport:
|
|
25
|
+
"""Detect terminal color support level.
|
|
26
|
+
|
|
27
|
+
Checks environment variables and terminal type to determine
|
|
28
|
+
the maximum color depth supported.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
ColorSupport level for current terminal.
|
|
32
|
+
"""
|
|
33
|
+
# Check for explicit no-color request
|
|
34
|
+
if os.environ.get("NO_COLOR") or os.environ.get("TERM") == "dumb":
|
|
35
|
+
return ColorSupport.NONE
|
|
36
|
+
|
|
37
|
+
# Check for explicit true color support
|
|
38
|
+
colorterm = os.environ.get("COLORTERM", "").lower()
|
|
39
|
+
if colorterm in ("truecolor", "24bit"):
|
|
40
|
+
return ColorSupport.TRUE_COLOR
|
|
41
|
+
|
|
42
|
+
# Check terminal type for known true color support
|
|
43
|
+
term = os.environ.get("TERM", "").lower()
|
|
44
|
+
term_program = os.environ.get("TERM_PROGRAM", "").lower()
|
|
45
|
+
|
|
46
|
+
# Terminals known to support true color
|
|
47
|
+
true_color_terms = (
|
|
48
|
+
"iterm.app",
|
|
49
|
+
"iterm2",
|
|
50
|
+
"vscode",
|
|
51
|
+
"hyper",
|
|
52
|
+
"alacritty",
|
|
53
|
+
"kitty",
|
|
54
|
+
"wezterm",
|
|
55
|
+
"rio",
|
|
56
|
+
)
|
|
57
|
+
if term_program in true_color_terms:
|
|
58
|
+
return ColorSupport.TRUE_COLOR
|
|
59
|
+
|
|
60
|
+
# Check TERM for true color indicators
|
|
61
|
+
if "truecolor" in term or "24bit" in term or "direct" in term:
|
|
62
|
+
return ColorSupport.TRUE_COLOR
|
|
63
|
+
|
|
64
|
+
# Modern terminal emulators with 256+ color support in TERM
|
|
65
|
+
if "256color" in term or "256" in term:
|
|
66
|
+
return ColorSupport.EXTENDED
|
|
67
|
+
|
|
68
|
+
# xterm and similar usually support 256 colors
|
|
69
|
+
if term.startswith(("xterm", "screen", "tmux", "rxvt")):
|
|
70
|
+
return ColorSupport.EXTENDED
|
|
71
|
+
|
|
72
|
+
# Apple Terminal.app - only 256 color, NOT true color
|
|
73
|
+
if term_program == "apple_terminal":
|
|
74
|
+
return ColorSupport.EXTENDED
|
|
75
|
+
|
|
76
|
+
# Basic color support for other terminals
|
|
77
|
+
if term:
|
|
78
|
+
return ColorSupport.BASIC
|
|
79
|
+
|
|
80
|
+
return ColorSupport.NONE
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# Global color support level - detected once at import
|
|
84
|
+
_COLOR_SUPPORT: ColorSupport | None = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_color_support() -> ColorSupport:
|
|
88
|
+
"""Get cached color support level.
|
|
89
|
+
|
|
90
|
+
Checks KOLLABOR_COLOR_MODE env var first for manual override:
|
|
91
|
+
- "truecolor" or "24bit" -> TRUE_COLOR
|
|
92
|
+
- "256" or "256color" -> EXTENDED
|
|
93
|
+
- "16" or "basic" -> BASIC
|
|
94
|
+
- "none" or "off" -> NONE
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
ColorSupport level for current terminal.
|
|
98
|
+
"""
|
|
99
|
+
global _COLOR_SUPPORT
|
|
100
|
+
if _COLOR_SUPPORT is None:
|
|
101
|
+
# Check for manual override
|
|
102
|
+
override = os.environ.get("KOLLABOR_COLOR_MODE", "").lower()
|
|
103
|
+
if override in ("truecolor", "24bit", "true"):
|
|
104
|
+
_COLOR_SUPPORT = ColorSupport.TRUE_COLOR
|
|
105
|
+
elif override in ("256", "256color", "extended"):
|
|
106
|
+
_COLOR_SUPPORT = ColorSupport.EXTENDED
|
|
107
|
+
elif override in ("16", "basic"):
|
|
108
|
+
_COLOR_SUPPORT = ColorSupport.BASIC
|
|
109
|
+
elif override in ("none", "off", "no"):
|
|
110
|
+
_COLOR_SUPPORT = ColorSupport.NONE
|
|
111
|
+
else:
|
|
112
|
+
_COLOR_SUPPORT = detect_color_support()
|
|
113
|
+
return _COLOR_SUPPORT
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def set_color_support(level: ColorSupport) -> None:
|
|
117
|
+
"""Manually set color support level.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
level: ColorSupport level to use.
|
|
121
|
+
"""
|
|
122
|
+
global _COLOR_SUPPORT
|
|
123
|
+
_COLOR_SUPPORT = level
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def reset_color_support() -> None:
|
|
127
|
+
"""Reset color support to re-detect on next call."""
|
|
128
|
+
global _COLOR_SUPPORT
|
|
129
|
+
_COLOR_SUPPORT = None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def rgb_to_256(r: int, g: int, b: int) -> int:
|
|
133
|
+
"""Convert RGB color to nearest 256-color palette index.
|
|
134
|
+
|
|
135
|
+
Uses the 6x6x6 color cube (indices 16-231) for colored values,
|
|
136
|
+
or grayscale ramp (indices 232-255) for near-gray colors.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
r: Red component (0-255)
|
|
140
|
+
g: Green component (0-255)
|
|
141
|
+
b: Blue component (0-255)
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
256-color palette index (0-255)
|
|
145
|
+
"""
|
|
146
|
+
# Check if color is near grayscale
|
|
147
|
+
if abs(r - g) < 10 and abs(g - b) < 10 and abs(r - b) < 10:
|
|
148
|
+
# Use grayscale ramp (232-255, 24 shades)
|
|
149
|
+
gray = (r + g + b) // 3
|
|
150
|
+
if gray < 8:
|
|
151
|
+
return 16 # black
|
|
152
|
+
if gray > 248:
|
|
153
|
+
return 231 # white
|
|
154
|
+
return 232 + ((gray - 8) * 24) // 240
|
|
155
|
+
|
|
156
|
+
# Use 6x6x6 color cube (indices 16-231)
|
|
157
|
+
# Each component maps to 0-5
|
|
158
|
+
r_idx = (r * 6) // 256
|
|
159
|
+
g_idx = (g * 6) // 256
|
|
160
|
+
b_idx = (b * 6) // 256
|
|
161
|
+
return 16 + (36 * r_idx) + (6 * g_idx) + b_idx
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def color_code(r: int, g: int, b: int, bold: bool = False, dim: bool = False) -> str:
|
|
165
|
+
"""Generate a foreground color escape code with automatic fallback.
|
|
166
|
+
|
|
167
|
+
Uses true color (24-bit) when supported, otherwise falls back
|
|
168
|
+
to 256-color palette.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
r: Red component (0-255)
|
|
172
|
+
g: Green component (0-255)
|
|
173
|
+
b: Blue component (0-255)
|
|
174
|
+
bold: Add bold attribute
|
|
175
|
+
dim: Add dim attribute
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
ANSI escape sequence for the color.
|
|
179
|
+
"""
|
|
180
|
+
prefix = ""
|
|
181
|
+
if bold:
|
|
182
|
+
prefix = "\033[1m"
|
|
183
|
+
elif dim:
|
|
184
|
+
prefix = "\033[2m"
|
|
185
|
+
|
|
186
|
+
if get_color_support() == ColorSupport.TRUE_COLOR:
|
|
187
|
+
return f"{prefix}\033[38;2;{r};{g};{b}m"
|
|
188
|
+
else:
|
|
189
|
+
idx = rgb_to_256(r, g, b)
|
|
190
|
+
return f"{prefix}\033[38;5;{idx}m"
|
|
191
|
+
|
|
192
|
+
|
|
9
193
|
class EffectType(Enum):
|
|
10
194
|
"""Types of visual effects."""
|
|
11
195
|
|
|
@@ -28,89 +212,127 @@ class EffectConfig:
|
|
|
28
212
|
colors: List[str] = field(default_factory=list)
|
|
29
213
|
|
|
30
214
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
#
|
|
215
|
+
# Color definitions as (r, g, b, modifier) tuples
|
|
216
|
+
# modifier: None=normal, 'bold'=bright, 'dim'=dim
|
|
217
|
+
_COLOR_DEFINITIONS = {
|
|
218
|
+
# Basic colors
|
|
219
|
+
"WHITE": (220, 220, 220, None),
|
|
220
|
+
"BRIGHT_WHITE": (255, 255, 255, "bold"),
|
|
221
|
+
"BLACK": (0, 0, 0, None),
|
|
222
|
+
# Red variants
|
|
223
|
+
"DIM_RED": (205, 49, 49, "dim"),
|
|
224
|
+
"RED": (205, 49, 49, None),
|
|
225
|
+
"BRIGHT_RED": (241, 76, 76, "bold"),
|
|
226
|
+
# Green variants
|
|
227
|
+
"DIM_GREEN": (13, 188, 121, "dim"),
|
|
228
|
+
"GREEN": (13, 188, 121, None),
|
|
229
|
+
"BRIGHT_GREEN": (35, 209, 139, "bold"),
|
|
230
|
+
# Yellow variants
|
|
231
|
+
"DIM_YELLOW": (229, 192, 123, "dim"),
|
|
232
|
+
"YELLOW": (229, 192, 123, None),
|
|
233
|
+
"BRIGHT_YELLOW": (245, 223, 77, "bold"),
|
|
234
|
+
# Blue variants
|
|
235
|
+
"DIM_BLUE": (36, 114, 200, "dim"),
|
|
236
|
+
"BLUE": (36, 114, 200, None),
|
|
237
|
+
"BRIGHT_BLUE": (59, 142, 234, "bold"),
|
|
238
|
+
"NORMAL_BLUE": (100, 149, 237, None),
|
|
239
|
+
# Magenta variants
|
|
240
|
+
"DIM_MAGENTA": (188, 63, 188, "dim"),
|
|
241
|
+
"MAGENTA": (188, 63, 188, None),
|
|
242
|
+
"BRIGHT_MAGENTA": (214, 112, 214, "bold"),
|
|
243
|
+
# Cyan variants
|
|
244
|
+
"DIM_CYAN": (17, 168, 205, "dim"),
|
|
245
|
+
"CYAN": (17, 168, 205, None),
|
|
246
|
+
"BRIGHT_CYAN": (41, 184, 219, "bold"),
|
|
247
|
+
# Grey variants
|
|
248
|
+
"DIM_GREY": (128, 128, 128, "dim"),
|
|
249
|
+
"GREY": (128, 128, 128, None),
|
|
250
|
+
"BRIGHT_GREY": (169, 169, 169, "bold"),
|
|
251
|
+
# Extended bright colors
|
|
252
|
+
"BRIGHT_CYAN_256": (0, 255, 255, "bold"),
|
|
253
|
+
"BRIGHT_BLUE_256": (94, 156, 255, "bold"),
|
|
254
|
+
"BRIGHT_GREEN_256": (90, 247, 142, "bold"),
|
|
255
|
+
"BRIGHT_YELLOW_256": (255, 231, 76, "bold"),
|
|
256
|
+
"BRIGHT_MAGENTA_256": (255, 92, 205, "bold"),
|
|
257
|
+
"BRIGHT_RED_256": (255, 85, 85, "bold"),
|
|
258
|
+
# Neon Minimal Palette - Lime
|
|
259
|
+
"LIME": (163, 230, 53, None),
|
|
260
|
+
"BRIGHT_LIME": (163, 230, 53, "bold"),
|
|
261
|
+
"LIME_LIGHT": (190, 242, 100, None),
|
|
262
|
+
"LIME_DARK": (132, 204, 22, None),
|
|
263
|
+
# Info: Cyan
|
|
264
|
+
"INFO_CYAN": (6, 182, 212, None),
|
|
265
|
+
"INFO_CYAN_LIGHT": (34, 211, 238, None),
|
|
266
|
+
"INFO_CYAN_DARK": (8, 145, 178, None),
|
|
267
|
+
# Warning: Gold
|
|
268
|
+
"WARNING_GOLD": (234, 179, 8, None),
|
|
269
|
+
"WARNING_GOLD_LIGHT": (253, 224, 71, None),
|
|
270
|
+
"WARNING_GOLD_DARK": (202, 138, 4, None),
|
|
271
|
+
# Error: Red
|
|
272
|
+
"ERROR_RED": (239, 68, 68, None),
|
|
273
|
+
"ERROR_RED_LIGHT": (248, 113, 113, None),
|
|
274
|
+
"ERROR_RED_DARK": (220, 38, 38, None),
|
|
275
|
+
# Muted: Steel
|
|
276
|
+
"MUTED_STEEL": (113, 113, 122, None),
|
|
277
|
+
"DIM_STEEL": (113, 113, 122, "dim"),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _make_color_code(r: int, g: int, b: int, modifier: str | None = None) -> str:
|
|
282
|
+
"""Generate escape code for a color with automatic fallback.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
r, g, b: RGB components (0-255)
|
|
286
|
+
modifier: 'bold', 'dim', or None
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
ANSI escape sequence appropriate for terminal capability.
|
|
290
|
+
"""
|
|
291
|
+
prefix = ""
|
|
292
|
+
if modifier == "bold":
|
|
293
|
+
prefix = "\033[1m"
|
|
294
|
+
elif modifier == "dim":
|
|
295
|
+
prefix = "\033[2m"
|
|
296
|
+
|
|
297
|
+
support = get_color_support()
|
|
298
|
+
|
|
299
|
+
if support == ColorSupport.NONE:
|
|
300
|
+
return prefix if prefix else ""
|
|
301
|
+
|
|
302
|
+
if support == ColorSupport.TRUE_COLOR:
|
|
303
|
+
return f"{prefix}\033[38;2;{r};{g};{b}m"
|
|
304
|
+
else:
|
|
305
|
+
# Use 256-color fallback
|
|
306
|
+
idx = rgb_to_256(r, g, b)
|
|
307
|
+
return f"{prefix}\033[38;5;{idx}m"
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class _ColorPaletteMeta(type):
|
|
311
|
+
"""Metaclass that generates color codes dynamically based on terminal support."""
|
|
312
|
+
|
|
313
|
+
def __getattr__(cls, name: str) -> str:
|
|
314
|
+
if name in _COLOR_DEFINITIONS:
|
|
315
|
+
r, g, b, modifier = _COLOR_DEFINITIONS[name]
|
|
316
|
+
return _make_color_code(r, g, b, modifier)
|
|
317
|
+
raise AttributeError(f"ColorPalette has no color '{name}'")
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class ColorPalette(metaclass=_ColorPaletteMeta):
|
|
321
|
+
"""Color palette with automatic terminal capability detection.
|
|
322
|
+
|
|
323
|
+
Colors are generated dynamically based on terminal support:
|
|
324
|
+
- TRUE_COLOR: Uses 24-bit RGB escape codes
|
|
325
|
+
- EXTENDED: Falls back to 256-color palette
|
|
326
|
+
- BASIC: Uses 16-color approximations
|
|
327
|
+
- NONE: Returns empty strings or just modifiers
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
# Standard modifiers (not affected by color support)
|
|
35
331
|
RESET = "\033[0m"
|
|
36
332
|
DIM = "\033[2m"
|
|
37
333
|
BRIGHT = "\033[1m"
|
|
38
334
|
|
|
39
|
-
#
|
|
40
|
-
WHITE = "\033[38;2;220;220;220m" # rgb(220, 220, 220)
|
|
41
|
-
BRIGHT_WHITE = "\033[1m\033[38;2;255;255;255m" # Bold + rgb(255, 255, 255)
|
|
42
|
-
BLACK = "\033[38;2;0;0;0m" # rgb(0, 0, 0)
|
|
43
|
-
|
|
44
|
-
# Red variants - RGB True Color
|
|
45
|
-
DIM_RED = "\033[2m\033[38;2;205;49;49m" # Dim + rgb(205, 49, 49)
|
|
46
|
-
RED = "\033[38;2;205;49;49m" # rgb(205, 49, 49)
|
|
47
|
-
BRIGHT_RED = "\033[1m\033[38;2;241;76;76m" # Bold + rgb(241, 76, 76)
|
|
48
|
-
|
|
49
|
-
# Green variants - RGB True Color
|
|
50
|
-
DIM_GREEN = "\033[2m\033[38;2;13;188;121m" # Dim + rgb(13, 188, 121)
|
|
51
|
-
GREEN = "\033[38;2;13;188;121m" # rgb(13, 188, 121)
|
|
52
|
-
BRIGHT_GREEN = "\033[1m\033[38;2;35;209;139m" # Bold + rgb(35, 209, 139)
|
|
53
|
-
|
|
54
|
-
# Yellow variants - RGB True Color
|
|
55
|
-
DIM_YELLOW = "\033[2m\033[38;2;229;192;123m" # Dim + rgb(229, 192, 123)
|
|
56
|
-
YELLOW = "\033[38;2;229;192;123m" # rgb(229, 192, 123)
|
|
57
|
-
BRIGHT_YELLOW = "\033[1m\033[38;2;245;223;77m" # Bold + rgb(245, 223, 77)
|
|
58
|
-
|
|
59
|
-
# Blue variants - RGB True Color
|
|
60
|
-
DIM_BLUE = "\033[2m\033[38;2;36;114;200m" # Dim + rgb(36, 114, 200)
|
|
61
|
-
BLUE = "\033[38;2;36;114;200m" # rgb(36, 114, 200)
|
|
62
|
-
BRIGHT_BLUE = "\033[1m\033[38;2;59;142;234m" # Bold + rgb(59, 142, 234)
|
|
63
|
-
NORMAL_BLUE = "\033[38;2;100;149;237m" # rgb(100, 149, 237) - cornflower blue
|
|
64
|
-
|
|
65
|
-
# Magenta variants - RGB True Color
|
|
66
|
-
DIM_MAGENTA = "\033[2m\033[38;2;188;63;188m" # Dim + rgb(188, 63, 188)
|
|
67
|
-
MAGENTA = "\033[38;2;188;63;188m" # rgb(188, 63, 188)
|
|
68
|
-
BRIGHT_MAGENTA = "\033[1m\033[38;2;214;112;214m" # Bold + rgb(214, 112, 214)
|
|
69
|
-
|
|
70
|
-
# Cyan variants - RGB True Color
|
|
71
|
-
DIM_CYAN = "\033[2m\033[38;2;17;168;205m" # Dim + rgb(17, 168, 205)
|
|
72
|
-
CYAN = "\033[38;2;17;168;205m" # rgb(17, 168, 205)
|
|
73
|
-
BRIGHT_CYAN = "\033[1m\033[38;2;41;184;219m" # Bold + rgb(41, 184, 219)
|
|
74
|
-
|
|
75
|
-
# Grey variants - RGB True Color
|
|
76
|
-
DIM_GREY = "\033[2m\033[38;2;128;128;128m" # Dim + rgb(128, 128, 128)
|
|
77
|
-
GREY = "\033[38;2;128;128;128m" # rgb(128, 128, 128)
|
|
78
|
-
BRIGHT_GREY = "\033[1m\033[38;2;169;169;169m" # Bold + rgb(169, 169, 169)
|
|
79
|
-
|
|
80
|
-
# Extended bright colors - RGB True Color (brighter versions)
|
|
81
|
-
BRIGHT_CYAN_256 = "\033[1m\033[38;2;0;255;255m" # Bold + rgb(0, 255, 255)
|
|
82
|
-
BRIGHT_BLUE_256 = "\033[1m\033[38;2;94;156;255m" # Bold + rgb(94, 156, 255)
|
|
83
|
-
BRIGHT_GREEN_256 = "\033[1m\033[38;2;90;247;142m" # Bold + rgb(90, 247, 142)
|
|
84
|
-
BRIGHT_YELLOW_256 = "\033[1m\033[38;2;255;231;76m" # Bold + rgb(255, 231, 76)
|
|
85
|
-
BRIGHT_MAGENTA_256 = "\033[1m\033[38;2;255;92;205m" # Bold + rgb(255, 92, 205)
|
|
86
|
-
BRIGHT_RED_256 = "\033[1m\033[38;2;255;85;85m" # Bold + rgb(255, 85, 85)
|
|
87
|
-
|
|
88
|
-
# Neon Minimal Palette - RGB True Color (24-bit)
|
|
89
|
-
# Primary: Lime Green #a3e635
|
|
90
|
-
LIME = "\033[38;2;163;230;53m"
|
|
91
|
-
LIME_LIGHT = "\033[38;2;190;242;100m"
|
|
92
|
-
LIME_DARK = "\033[38;2;132;204;22m"
|
|
93
|
-
|
|
94
|
-
# Info: Cyan #06b6d4
|
|
95
|
-
INFO_CYAN = "\033[38;2;6;182;212m"
|
|
96
|
-
INFO_CYAN_LIGHT = "\033[38;2;34;211;238m"
|
|
97
|
-
INFO_CYAN_DARK = "\033[38;2;8;145;178m"
|
|
98
|
-
|
|
99
|
-
# Warning: Gold #eab308
|
|
100
|
-
WARNING_GOLD = "\033[38;2;234;179;8m"
|
|
101
|
-
WARNING_GOLD_LIGHT = "\033[38;2;253;224;71m"
|
|
102
|
-
WARNING_GOLD_DARK = "\033[38;2;202;138;4m"
|
|
103
|
-
|
|
104
|
-
# Error: Bright Red #ef4444
|
|
105
|
-
ERROR_RED = "\033[38;2;239;68;68m"
|
|
106
|
-
ERROR_RED_LIGHT = "\033[38;2;248;113;113m"
|
|
107
|
-
ERROR_RED_DARK = "\033[38;2;220;38;38m"
|
|
108
|
-
|
|
109
|
-
# Muted: Steel #71717a
|
|
110
|
-
MUTED_STEEL = "\033[38;2;113;113;122m"
|
|
111
|
-
DIM_STEEL = "\033[2;38;2;113;113;122m"
|
|
112
|
-
|
|
113
|
-
# Grey gradient levels (256-color palette)
|
|
335
|
+
# Grey gradient levels (256-color palette indices)
|
|
114
336
|
GREY_LEVELS = [255, 254, 253, 252, 251, 250]
|
|
115
337
|
|
|
116
338
|
# Dim white gradient levels (bright white to subtle dim white)
|
|
@@ -119,19 +341,474 @@ class ColorPalette:
|
|
|
119
341
|
# Lime green gradient scheme RGB values for ultra-smooth gradients
|
|
120
342
|
DIM_SCHEME_COLORS = [
|
|
121
343
|
(190, 242, 100), # Bright lime (#bef264)
|
|
122
|
-
(175, 235, 80),
|
|
123
|
-
(163, 230, 53),
|
|
124
|
-
(145, 210, 45),
|
|
125
|
-
(132, 204, 22),
|
|
126
|
-
(115, 180, 18),
|
|
127
|
-
(100, 160, 15),
|
|
128
|
-
(115, 180, 18),
|
|
129
|
-
(132, 204, 22),
|
|
130
|
-
(163, 230, 53),
|
|
344
|
+
(175, 235, 80), # Light lime
|
|
345
|
+
(163, 230, 53), # Primary lime (#a3e635) - hero color!
|
|
346
|
+
(145, 210, 45), # Medium lime
|
|
347
|
+
(132, 204, 22), # Darker lime (#84cc16)
|
|
348
|
+
(115, 180, 18), # Deep lime
|
|
349
|
+
(100, 160, 15), # Strong lime
|
|
350
|
+
(115, 180, 18), # Deep lime (return)
|
|
351
|
+
(132, 204, 22), # Darker lime (return)
|
|
352
|
+
(163, 230, 53), # Primary lime (return)
|
|
131
353
|
(190, 242, 100), # Bright lime
|
|
132
354
|
]
|
|
133
355
|
|
|
134
356
|
|
|
357
|
+
# Powerline separator characters
|
|
358
|
+
class Powerline:
|
|
359
|
+
"""Powerline/Agnoster style separator characters."""
|
|
360
|
+
|
|
361
|
+
# Solid arrows
|
|
362
|
+
RIGHT = "\ue0b0" #
|
|
363
|
+
LEFT = "\ue0b2" #
|
|
364
|
+
|
|
365
|
+
# Thin arrows (for sub-segments)
|
|
366
|
+
RIGHT_THIN = "\ue0b1" #
|
|
367
|
+
LEFT_THIN = "\ue0b3" #
|
|
368
|
+
|
|
369
|
+
# Rounded
|
|
370
|
+
RIGHT_ROUND = "\ue0b4" #
|
|
371
|
+
LEFT_ROUND = "\ue0b6" #
|
|
372
|
+
|
|
373
|
+
# Flame/fire style
|
|
374
|
+
RIGHT_FLAME = "\ue0c0" #
|
|
375
|
+
LEFT_FLAME = "\ue0c2" #
|
|
376
|
+
|
|
377
|
+
# Pixelated
|
|
378
|
+
RIGHT_PIXEL = "\ue0c4" #
|
|
379
|
+
LEFT_PIXEL = "\ue0c6" #
|
|
380
|
+
|
|
381
|
+
# Ice/diagonal
|
|
382
|
+
RIGHT_ICE = "\ue0c8" #
|
|
383
|
+
LEFT_ICE = "\ue0ca" #
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def make_bg_color(r: int, g: int, b: int) -> str:
|
|
387
|
+
"""Create background color escape code.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
r, g, b: RGB values (0-255).
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
ANSI escape code for background color.
|
|
394
|
+
"""
|
|
395
|
+
support = get_color_support()
|
|
396
|
+
|
|
397
|
+
if support == ColorSupport.NONE:
|
|
398
|
+
return ""
|
|
399
|
+
|
|
400
|
+
if support == ColorSupport.TRUE_COLOR:
|
|
401
|
+
return f"\033[48;2;{r};{g};{b}m"
|
|
402
|
+
else:
|
|
403
|
+
# Use 256-color fallback
|
|
404
|
+
idx = rgb_to_256(r, g, b)
|
|
405
|
+
return f"\033[48;5;{idx}m"
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def make_fg_color(r: int, g: int, b: int) -> str:
|
|
409
|
+
"""Create foreground color escape code.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
r, g, b: RGB values (0-255).
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
ANSI escape code for foreground color.
|
|
416
|
+
"""
|
|
417
|
+
support = get_color_support()
|
|
418
|
+
|
|
419
|
+
if support == ColorSupport.NONE:
|
|
420
|
+
return ""
|
|
421
|
+
|
|
422
|
+
if support == ColorSupport.TRUE_COLOR:
|
|
423
|
+
return f"\033[38;2;{r};{g};{b}m"
|
|
424
|
+
else:
|
|
425
|
+
idx = rgb_to_256(r, g, b)
|
|
426
|
+
return f"\033[38;5;{idx}m"
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class AgnosterColors:
|
|
430
|
+
"""Signature color scheme for agnoster segments - lime and cyan."""
|
|
431
|
+
|
|
432
|
+
# Lime palette (RGB tuples)
|
|
433
|
+
LIME = (163, 230, 53)
|
|
434
|
+
LIME_DARK = (132, 204, 22)
|
|
435
|
+
LIME_DARKER = (100, 160, 15)
|
|
436
|
+
|
|
437
|
+
# Cyan palette
|
|
438
|
+
CYAN = (6, 182, 212)
|
|
439
|
+
CYAN_DARK = (8, 145, 178)
|
|
440
|
+
CYAN_LIGHT = (34, 211, 238)
|
|
441
|
+
|
|
442
|
+
# Neutral backgrounds
|
|
443
|
+
BG_DARK = (30, 30, 30)
|
|
444
|
+
BG_MID = (50, 50, 50)
|
|
445
|
+
BG_LIGHT = (70, 70, 70)
|
|
446
|
+
|
|
447
|
+
# Text colors
|
|
448
|
+
TEXT_DARK = (20, 20, 20)
|
|
449
|
+
TEXT_LIGHT = (240, 240, 240)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
class ShimmerEffect:
|
|
453
|
+
"""Handles shimmer animation effects."""
|
|
454
|
+
|
|
455
|
+
def __init__(self, speed: int = 3, wave_width: int = 4):
|
|
456
|
+
"""Initialize shimmer effect.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
speed: Animation speed (frames between updates).
|
|
460
|
+
wave_width: Width of shimmer wave in characters.
|
|
461
|
+
"""
|
|
462
|
+
self.speed = speed
|
|
463
|
+
self.wave_width = wave_width
|
|
464
|
+
self.frame_counter = 0
|
|
465
|
+
self.position = 0
|
|
466
|
+
|
|
467
|
+
def configure(self, speed: int, wave_width: int) -> None:
|
|
468
|
+
"""Configure shimmer parameters.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
speed: Animation speed.
|
|
472
|
+
wave_width: Wave width.
|
|
473
|
+
"""
|
|
474
|
+
self.speed = speed
|
|
475
|
+
self.wave_width = wave_width
|
|
476
|
+
|
|
477
|
+
def apply_shimmer(self, text: str) -> str:
|
|
478
|
+
"""Apply elegant wave shimmer effect to text.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
text: Text to apply shimmer to.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
Text with shimmer effect applied.
|
|
485
|
+
"""
|
|
486
|
+
if not text:
|
|
487
|
+
return text
|
|
488
|
+
|
|
489
|
+
# Update shimmer position
|
|
490
|
+
self.frame_counter = (self.frame_counter + 1) % self.speed
|
|
491
|
+
if self.frame_counter == 0:
|
|
492
|
+
self.position = (self.position + 1) % (len(text) + self.wave_width * 2)
|
|
493
|
+
|
|
494
|
+
result = []
|
|
495
|
+
for i, char in enumerate(text):
|
|
496
|
+
distance = abs(i - self.position)
|
|
497
|
+
|
|
498
|
+
if distance == 0:
|
|
499
|
+
# Center - bright cyan
|
|
500
|
+
result.append(
|
|
501
|
+
f"{ColorPalette.BRIGHT_CYAN}{char}{ColorPalette.RESET}"
|
|
502
|
+
)
|
|
503
|
+
elif distance == 1:
|
|
504
|
+
# Adjacent - bright blue
|
|
505
|
+
result.append(
|
|
506
|
+
f"{ColorPalette.BRIGHT_BLUE}{char}{ColorPalette.RESET}"
|
|
507
|
+
)
|
|
508
|
+
elif distance == 2:
|
|
509
|
+
# Second ring - normal blue
|
|
510
|
+
result.append(
|
|
511
|
+
f"{ColorPalette.NORMAL_BLUE}{char}{ColorPalette.RESET}"
|
|
512
|
+
)
|
|
513
|
+
elif distance <= self.wave_width:
|
|
514
|
+
# Edge - dim blue
|
|
515
|
+
result.append(f"{ColorPalette.DIM_BLUE}{char}{ColorPalette.RESET}")
|
|
516
|
+
else:
|
|
517
|
+
# Base - darker dim blue
|
|
518
|
+
result.append(f"\033[2;94m{char}{ColorPalette.RESET}")
|
|
519
|
+
|
|
520
|
+
return "".join(result)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
class PulseEffect:
|
|
524
|
+
"""Handles pulsing brightness animation effects."""
|
|
525
|
+
|
|
526
|
+
def __init__(self, speed: int = 3, pulse_width: int = 2):
|
|
527
|
+
"""Initialize pulse effect.
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
speed: Animation speed (frames between updates).
|
|
531
|
+
pulse_width: Number of brightness levels in pulse.
|
|
532
|
+
"""
|
|
533
|
+
self.speed = speed
|
|
534
|
+
self.pulse_width = pulse_width
|
|
535
|
+
self.frame_counter = 0
|
|
536
|
+
self.brightness_level = 0
|
|
537
|
+
self.direction = 1 # 1 for getting brighter, -1 for getting dimmer
|
|
538
|
+
|
|
539
|
+
def configure(self, speed: int, pulse_width: int) -> None:
|
|
540
|
+
"""Configure pulse parameters.
|
|
541
|
+
|
|
542
|
+
Args:
|
|
543
|
+
speed: Animation speed.
|
|
544
|
+
pulse_width: Pulse width.
|
|
545
|
+
"""
|
|
546
|
+
self.speed = speed
|
|
547
|
+
self.pulse_width = pulse_width
|
|
548
|
+
|
|
549
|
+
def apply_pulse(self, text: str) -> str:
|
|
550
|
+
"""Apply pulsing brightness effect to text.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
text: Text to apply pulse to.
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
Text with pulse effect applied.
|
|
557
|
+
"""
|
|
558
|
+
if not text:
|
|
559
|
+
return text
|
|
560
|
+
|
|
561
|
+
# Update pulse brightness
|
|
562
|
+
self.frame_counter = (self.frame_counter + 1) % self.speed
|
|
563
|
+
if self.frame_counter == 0:
|
|
564
|
+
# Move brightness level
|
|
565
|
+
self.brightness_level += self.direction
|
|
566
|
+
|
|
567
|
+
# Reverse direction at bounds
|
|
568
|
+
if self.brightness_level >= self.pulse_width:
|
|
569
|
+
self.direction = -1
|
|
570
|
+
self.brightness_level = self.pulse_width
|
|
571
|
+
elif self.brightness_level <= 0:
|
|
572
|
+
self.direction = 1
|
|
573
|
+
self.brightness_level = 0
|
|
574
|
+
|
|
575
|
+
# Determine color based on brightness level
|
|
576
|
+
if self.brightness_level == self.pulse_width:
|
|
577
|
+
# Peak brightness - bright yellow
|
|
578
|
+
color = ColorPalette.BRIGHT_YELLOW
|
|
579
|
+
elif self.brightness_level >= self.pulse_width * 2 // 3:
|
|
580
|
+
# Bright - yellow
|
|
581
|
+
color = ColorPalette.YELLOW
|
|
582
|
+
elif self.brightness_level >= self.pulse_width // 3:
|
|
583
|
+
# Medium - dim yellow
|
|
584
|
+
color = ColorPalette.DIM_YELLOW
|
|
585
|
+
else:
|
|
586
|
+
# Dim - dim grey
|
|
587
|
+
color = ColorPalette.DIM_GREY
|
|
588
|
+
|
|
589
|
+
result = []
|
|
590
|
+
for char in text:
|
|
591
|
+
result.append(f"{color}{char}{ColorPalette.RESET}")
|
|
592
|
+
|
|
593
|
+
return "".join(result)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
class ScrambleEffect:
|
|
597
|
+
"""Handles text scramble shimmer animation effects."""
|
|
598
|
+
|
|
599
|
+
# Special characters for scramble effect (matrix-style)
|
|
600
|
+
SCRAMBLE_CHARS = "!@#$%^&*()_+-=[]{}|;:,.<>?/~`0123456789abcdefghijklmnopqrstuvwxyz"
|
|
601
|
+
|
|
602
|
+
def __init__(self, speed: int = 2, window_size: int = 6):
|
|
603
|
+
"""Initialize scramble effect.
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
speed: Animation speed (frames between updates).
|
|
607
|
+
window_size: Size of scrambling window in characters.
|
|
608
|
+
"""
|
|
609
|
+
self.speed = speed
|
|
610
|
+
self.window_size = window_size
|
|
611
|
+
self.frame_counter = 0
|
|
612
|
+
self.position = 0
|
|
613
|
+
|
|
614
|
+
def configure(self, speed: int, window_size: int) -> None:
|
|
615
|
+
"""Configure scramble parameters.
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
speed: Animation speed.
|
|
619
|
+
window_size: Scramble window size.
|
|
620
|
+
"""
|
|
621
|
+
self.speed = speed
|
|
622
|
+
self.window_size = window_size
|
|
623
|
+
|
|
624
|
+
def _get_scramble_char(self, index: int) -> str:
|
|
625
|
+
"""Get a random scramble character.
|
|
626
|
+
|
|
627
|
+
Args:
|
|
628
|
+
index: Character position for deterministic randomness.
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
Random scramble character.
|
|
632
|
+
"""
|
|
633
|
+
import random
|
|
634
|
+
# Use index + frame for more chaotic scrambling
|
|
635
|
+
random.seed(index + self.position + self.frame_counter)
|
|
636
|
+
return random.choice(self.SCRAMBLE_CHARS)
|
|
637
|
+
|
|
638
|
+
def apply_scramble(self, text: str) -> str:
|
|
639
|
+
"""Apply text scramble shimmer effect.
|
|
640
|
+
|
|
641
|
+
Creates a moving window of scrambled characters that flows
|
|
642
|
+
through the text like a shimmer.
|
|
643
|
+
|
|
644
|
+
Args:
|
|
645
|
+
text: Text to apply effect to.
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
Text with scramble shimmer effect applied.
|
|
649
|
+
"""
|
|
650
|
+
if not text:
|
|
651
|
+
return text
|
|
652
|
+
|
|
653
|
+
# Update position like shimmer
|
|
654
|
+
self.frame_counter = (self.frame_counter + 1) % self.speed
|
|
655
|
+
if self.frame_counter == 0:
|
|
656
|
+
self.position = (self.position + 1) % (len(text) + self.window_size * 2)
|
|
657
|
+
|
|
658
|
+
result = []
|
|
659
|
+
for i, char in enumerate(text):
|
|
660
|
+
distance = abs(i - self.position)
|
|
661
|
+
|
|
662
|
+
if distance < self.window_size:
|
|
663
|
+
# Inside scramble window - show random character
|
|
664
|
+
scramble = self._get_scramble_char(i)
|
|
665
|
+
# More chaotic at center of window
|
|
666
|
+
if distance == 0:
|
|
667
|
+
# Center - bright cyan
|
|
668
|
+
result.append(
|
|
669
|
+
f"{ColorPalette.BRIGHT_CYAN}{scramble}{ColorPalette.RESET}"
|
|
670
|
+
)
|
|
671
|
+
elif distance < self.window_size // 2:
|
|
672
|
+
# Near center - cyan
|
|
673
|
+
result.append(
|
|
674
|
+
f"{ColorPalette.CYAN}{scramble}{ColorPalette.RESET}"
|
|
675
|
+
)
|
|
676
|
+
else:
|
|
677
|
+
# Edge - dim cyan
|
|
678
|
+
result.append(
|
|
679
|
+
f"{ColorPalette.DIM_CYAN}{scramble}{ColorPalette.RESET}"
|
|
680
|
+
)
|
|
681
|
+
else:
|
|
682
|
+
# Outside window - show actual character with green color
|
|
683
|
+
result.append(
|
|
684
|
+
f"{ColorPalette.BRIGHT_GREEN}{char}{ColorPalette.RESET}"
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
return "".join(result)
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
class AgnosterSegment:
|
|
691
|
+
"""Builder for powerline/agnoster style segments."""
|
|
692
|
+
|
|
693
|
+
def __init__(self):
|
|
694
|
+
"""Initialize empty segment list."""
|
|
695
|
+
self.segments: List[Tuple[Tuple[int, int, int], Tuple[int, int, int], str]] = []
|
|
696
|
+
|
|
697
|
+
def add(
|
|
698
|
+
self,
|
|
699
|
+
text: str,
|
|
700
|
+
bg: Tuple[int, int, int],
|
|
701
|
+
fg: Tuple[int, int, int] = AgnosterColors.TEXT_DARK
|
|
702
|
+
) -> "AgnosterSegment":
|
|
703
|
+
"""Add a segment.
|
|
704
|
+
|
|
705
|
+
Args:
|
|
706
|
+
text: Segment text content.
|
|
707
|
+
bg: Background color RGB tuple.
|
|
708
|
+
fg: Foreground (text) color RGB tuple.
|
|
709
|
+
|
|
710
|
+
Returns:
|
|
711
|
+
Self for chaining.
|
|
712
|
+
"""
|
|
713
|
+
self.segments.append((bg, fg, text))
|
|
714
|
+
return self
|
|
715
|
+
|
|
716
|
+
def add_lime(self, text: str, variant: str = "normal") -> "AgnosterSegment":
|
|
717
|
+
"""Add a lime-colored segment.
|
|
718
|
+
|
|
719
|
+
Args:
|
|
720
|
+
text: Segment text.
|
|
721
|
+
variant: "normal", "dark", or "darker".
|
|
722
|
+
|
|
723
|
+
Returns:
|
|
724
|
+
Self for chaining.
|
|
725
|
+
"""
|
|
726
|
+
bg_map = {
|
|
727
|
+
"normal": AgnosterColors.LIME,
|
|
728
|
+
"dark": AgnosterColors.LIME_DARK,
|
|
729
|
+
"darker": AgnosterColors.LIME_DARKER,
|
|
730
|
+
}
|
|
731
|
+
return self.add(text, bg_map.get(variant, AgnosterColors.LIME))
|
|
732
|
+
|
|
733
|
+
def add_cyan(self, text: str, variant: str = "normal") -> "AgnosterSegment":
|
|
734
|
+
"""Add a cyan-colored segment.
|
|
735
|
+
|
|
736
|
+
Args:
|
|
737
|
+
text: Segment text.
|
|
738
|
+
variant: "normal", "dark", or "light".
|
|
739
|
+
|
|
740
|
+
Returns:
|
|
741
|
+
Self for chaining.
|
|
742
|
+
"""
|
|
743
|
+
bg_map = {
|
|
744
|
+
"normal": AgnosterColors.CYAN,
|
|
745
|
+
"dark": AgnosterColors.CYAN_DARK,
|
|
746
|
+
"light": AgnosterColors.CYAN_LIGHT,
|
|
747
|
+
}
|
|
748
|
+
return self.add(text, bg_map.get(variant, AgnosterColors.CYAN))
|
|
749
|
+
|
|
750
|
+
def add_neutral(self, text: str, variant: str = "mid") -> "AgnosterSegment":
|
|
751
|
+
"""Add a neutral gray segment.
|
|
752
|
+
|
|
753
|
+
Args:
|
|
754
|
+
text: Segment text.
|
|
755
|
+
variant: "dark", "mid", or "light".
|
|
756
|
+
|
|
757
|
+
Returns:
|
|
758
|
+
Self for chaining.
|
|
759
|
+
"""
|
|
760
|
+
bg_map = {
|
|
761
|
+
"dark": AgnosterColors.BG_DARK,
|
|
762
|
+
"mid": AgnosterColors.BG_MID,
|
|
763
|
+
"light": AgnosterColors.BG_LIGHT,
|
|
764
|
+
}
|
|
765
|
+
fg = AgnosterColors.TEXT_LIGHT
|
|
766
|
+
return self.add(text, bg_map.get(variant, AgnosterColors.BG_MID), fg)
|
|
767
|
+
|
|
768
|
+
def render(self, separator: str = Powerline.RIGHT) -> str:
|
|
769
|
+
"""Render all segments with powerline separators.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
separator: Powerline separator character to use.
|
|
773
|
+
|
|
774
|
+
Returns:
|
|
775
|
+
Fully formatted powerline string.
|
|
776
|
+
"""
|
|
777
|
+
if not self.segments:
|
|
778
|
+
return ""
|
|
779
|
+
|
|
780
|
+
result = []
|
|
781
|
+
reset = ColorPalette.RESET
|
|
782
|
+
|
|
783
|
+
for i, (bg, fg, text) in enumerate(self.segments):
|
|
784
|
+
bg_code = make_bg_color(*bg)
|
|
785
|
+
fg_code = make_fg_color(*fg)
|
|
786
|
+
|
|
787
|
+
# Segment content with padding
|
|
788
|
+
result.append(f"{bg_code}{fg_code} {text} ")
|
|
789
|
+
|
|
790
|
+
# Add separator (arrow colored: fg=current_bg, bg=next_bg or transparent)
|
|
791
|
+
if i < len(self.segments) - 1:
|
|
792
|
+
next_bg = self.segments[i + 1][0]
|
|
793
|
+
sep_fg = make_fg_color(*bg) # Arrow color = current segment bg
|
|
794
|
+
sep_bg = make_bg_color(*next_bg) # Arrow bg = next segment bg
|
|
795
|
+
result.append(f"{sep_bg}{sep_fg}{separator}")
|
|
796
|
+
else:
|
|
797
|
+
# Last segment - arrow fades to transparent
|
|
798
|
+
sep_fg = make_fg_color(*bg)
|
|
799
|
+
result.append(f"{reset}{sep_fg}{separator}{reset}")
|
|
800
|
+
|
|
801
|
+
return "".join(result)
|
|
802
|
+
|
|
803
|
+
def render_minimal(self) -> str:
|
|
804
|
+
"""Render segments with thin separators (less prominent).
|
|
805
|
+
|
|
806
|
+
Returns:
|
|
807
|
+
Formatted string with thin separators.
|
|
808
|
+
"""
|
|
809
|
+
return self.render(Powerline.RIGHT_THIN)
|
|
810
|
+
|
|
811
|
+
|
|
135
812
|
class GradientRenderer:
|
|
136
813
|
"""Handles various gradient effects."""
|
|
137
814
|
|
|
@@ -203,6 +880,9 @@ class GradientRenderer:
|
|
|
203
880
|
def apply_dim_scheme_gradient(text: str) -> str:
|
|
204
881
|
"""Apply ultra-smooth gradient using dim color scheme.
|
|
205
882
|
|
|
883
|
+
Automatically uses 256-color fallback when true color
|
|
884
|
+
is not supported by the terminal.
|
|
885
|
+
|
|
206
886
|
Args:
|
|
207
887
|
text: Text to apply gradient to.
|
|
208
888
|
|
|
@@ -215,6 +895,7 @@ class GradientRenderer:
|
|
|
215
895
|
result = []
|
|
216
896
|
text_length = len(text)
|
|
217
897
|
color_rgb = ColorPalette.DIM_SCHEME_COLORS
|
|
898
|
+
use_true_color = get_color_support() == ColorSupport.TRUE_COLOR
|
|
218
899
|
|
|
219
900
|
for i, char in enumerate(text):
|
|
220
901
|
position = i / max(1, text_length - 1)
|
|
@@ -233,7 +914,14 @@ class GradientRenderer:
|
|
|
233
914
|
b = curr_rgb[2] + (next_rgb[2] - curr_rgb[2]) * t
|
|
234
915
|
|
|
235
916
|
r, g, b = int(r), int(g), int(b)
|
|
236
|
-
|
|
917
|
+
|
|
918
|
+
if use_true_color:
|
|
919
|
+
color_code = f"\033[38;2;{r};{g};{b}m"
|
|
920
|
+
else:
|
|
921
|
+
# Fallback to 256-color palette
|
|
922
|
+
color_idx = rgb_to_256(r, g, b)
|
|
923
|
+
color_code = f"\033[38;5;{color_idx}m"
|
|
924
|
+
|
|
237
925
|
result.append(f"{color_code}{char}")
|
|
238
926
|
|
|
239
927
|
result.append(ColorPalette.RESET)
|
|
@@ -243,6 +931,9 @@ class GradientRenderer:
|
|
|
243
931
|
def apply_custom_gradient(text: str, colors: List[Tuple[int, int, int]]) -> str:
|
|
244
932
|
"""Apply custom RGB gradient to text.
|
|
245
933
|
|
|
934
|
+
Automatically uses 256-color fallback when true color
|
|
935
|
+
is not supported by the terminal.
|
|
936
|
+
|
|
246
937
|
Args:
|
|
247
938
|
text: Text to apply gradient to.
|
|
248
939
|
colors: List of RGB color tuples for gradient stops.
|
|
@@ -255,6 +946,7 @@ class GradientRenderer:
|
|
|
255
946
|
|
|
256
947
|
result = []
|
|
257
948
|
text_length = len(text)
|
|
949
|
+
use_true_color = get_color_support() == ColorSupport.TRUE_COLOR
|
|
258
950
|
|
|
259
951
|
for i, char in enumerate(text):
|
|
260
952
|
position = i / max(1, text_length - 1)
|
|
@@ -273,81 +965,17 @@ class GradientRenderer:
|
|
|
273
965
|
b = curr_rgb[2] + (next_rgb[2] - curr_rgb[2]) * t
|
|
274
966
|
|
|
275
967
|
r, g, b = int(r), int(g), int(b)
|
|
276
|
-
color_code = f"\033[38;2;{r};{g};{b}m"
|
|
277
|
-
result.append(f"{color_code}{char}")
|
|
278
968
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
class ShimmerEffect:
|
|
284
|
-
"""Handles shimmer animation effects."""
|
|
285
|
-
|
|
286
|
-
def __init__(self, speed: int = 3, wave_width: int = 4):
|
|
287
|
-
"""Initialize shimmer effect.
|
|
288
|
-
|
|
289
|
-
Args:
|
|
290
|
-
speed: Animation speed (frames between updates).
|
|
291
|
-
wave_width: Width of shimmer wave in characters.
|
|
292
|
-
"""
|
|
293
|
-
self.speed = speed
|
|
294
|
-
self.wave_width = wave_width
|
|
295
|
-
self.frame_counter = 0
|
|
296
|
-
self.position = 0
|
|
297
|
-
|
|
298
|
-
def configure(self, speed: int, wave_width: int) -> None:
|
|
299
|
-
"""Configure shimmer parameters.
|
|
300
|
-
|
|
301
|
-
Args:
|
|
302
|
-
speed: Animation speed.
|
|
303
|
-
wave_width: Wave width.
|
|
304
|
-
"""
|
|
305
|
-
self.speed = speed
|
|
306
|
-
self.wave_width = wave_width
|
|
307
|
-
|
|
308
|
-
def apply_shimmer(self, text: str) -> str:
|
|
309
|
-
"""Apply elegant wave shimmer effect to text.
|
|
310
|
-
|
|
311
|
-
Args:
|
|
312
|
-
text: Text to apply shimmer to.
|
|
313
|
-
|
|
314
|
-
Returns:
|
|
315
|
-
Text with shimmer effect applied.
|
|
316
|
-
"""
|
|
317
|
-
if not text:
|
|
318
|
-
return text
|
|
319
|
-
|
|
320
|
-
# Update shimmer position
|
|
321
|
-
self.frame_counter = (self.frame_counter + 1) % self.speed
|
|
322
|
-
if self.frame_counter == 0:
|
|
323
|
-
self.position = (self.position + 1) % (len(text) + self.wave_width * 2)
|
|
324
|
-
|
|
325
|
-
result = []
|
|
326
|
-
for i, char in enumerate(text):
|
|
327
|
-
distance = abs(i - self.position)
|
|
328
|
-
|
|
329
|
-
if distance == 0:
|
|
330
|
-
# Center - bright cyan
|
|
331
|
-
result.append(
|
|
332
|
-
f"{ColorPalette.BRIGHT_CYAN}{char}{ColorPalette.RESET}"
|
|
333
|
-
)
|
|
334
|
-
elif distance == 1:
|
|
335
|
-
# Adjacent - bright blue
|
|
336
|
-
result.append(
|
|
337
|
-
f"{ColorPalette.BRIGHT_BLUE}{char}{ColorPalette.RESET}"
|
|
338
|
-
)
|
|
339
|
-
elif distance == 2:
|
|
340
|
-
# Second ring - normal blue
|
|
341
|
-
result.append(
|
|
342
|
-
f"{ColorPalette.NORMAL_BLUE}{char}{ColorPalette.RESET}"
|
|
343
|
-
)
|
|
344
|
-
elif distance <= self.wave_width:
|
|
345
|
-
# Edge - dim blue
|
|
346
|
-
result.append(f"{ColorPalette.DIM_BLUE}{char}{ColorPalette.RESET}")
|
|
969
|
+
if use_true_color:
|
|
970
|
+
color_code = f"\033[38;2;{r};{g};{b}m"
|
|
347
971
|
else:
|
|
348
|
-
#
|
|
349
|
-
|
|
972
|
+
# Fallback to 256-color palette
|
|
973
|
+
color_idx = rgb_to_256(r, g, b)
|
|
974
|
+
color_code = f"\033[38;5;{color_idx}m"
|
|
975
|
+
|
|
976
|
+
result.append(f"{color_code}{char}")
|
|
350
977
|
|
|
978
|
+
result.append(ColorPalette.RESET)
|
|
351
979
|
return "".join(result)
|
|
352
980
|
|
|
353
981
|
|
|
@@ -569,13 +1197,30 @@ class BannerRenderer:
|
|
|
569
1197
|
"\r ▀ ▀ ▀▀▀▀ ▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀▀ ▀ ▀▀ ▀ ▀ ▀ ▀▀▀ ",
|
|
570
1198
|
"\r ──────────────────────────────────────────────── ",
|
|
571
1199
|
]
|
|
572
|
-
|
|
1200
|
+
KOLLABOR_ASCII2 = [
|
|
573
1201
|
"\r ██╗ ██╔════════════════════════════════════════════╗",
|
|
574
1202
|
"\r ██║ ██╔╝ ║",
|
|
575
1203
|
"\r █████╔╝ █▀▀█ █ █ █▀▀█ █▀▀▄ █▀▀█ █▀▀█ █▀▀█ ▀█▀ ║",
|
|
576
1204
|
"\r ██╔═██╗ █ █ █ █ █▄▄█ █▀▀▄ █ █ █▄▄▀ █▄▄█ █ ║",
|
|
577
1205
|
"\r ██║ ██╗▀▀▀▀ ▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀▀ ▀ ▀▀ ▀ ▀ ▀ ▀▀▀ ║",
|
|
578
1206
|
"\r ╚═╝ ╚═╩════════════════════════════════════════════╝",
|
|
1207
|
+
]
|
|
1208
|
+
KOLLABOR_ASCII3 = [
|
|
1209
|
+
"\r ┌──────────────────────────────────────────────────┐",
|
|
1210
|
+
"\r │ ▄█─●─●─█▄ █ ▄▀ █▀▀█ █ █ █▀▀█ █▀▀▄ █▀▀█ █▀▀█ │",
|
|
1211
|
+
"\r │ ██ │ │ ██ █▀▄ █ █ █ █ █▄▄█ █▀▀▄ █ █ █▄▄▀ │",
|
|
1212
|
+
"\r │ ●──███──● ▀ ▀ ▀▀▀▀ ▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀▀ ▀ ▀▀ │",
|
|
1213
|
+
"\r │ ██ │ │ ██ Collaborative Intelligence │",
|
|
1214
|
+
"\r │ ▀█─●─●─█▀ │",
|
|
1215
|
+
"\r └──────────────────────────────────────────────────┘",
|
|
1216
|
+
]
|
|
1217
|
+
|
|
1218
|
+
KOLLABOR_ASCII = [
|
|
1219
|
+
"\r ╭──────────────────────────────────────────────────╮",
|
|
1220
|
+
"\r │ ▄█─●─●─█▄ █ ▄▀ █▀▀█ █ █ █▀▀█ █▀▀▄ █▀▀█ █▀▀█ │",
|
|
1221
|
+
"\r │ ●──███──● █▀▄ █ █ █ █ █▄▄█ █▀▀▄ █ █ █▄▄▀ │",
|
|
1222
|
+
"\r │ ▀█─●─●─█▀ █ █ █▄▄█ █▄▄ █▄▄ █ █ █▄▄▀ █▄▄█ █ █▄ │",
|
|
1223
|
+
"\r ╰──────────────────────────────────────────────────╯",
|
|
579
1224
|
]
|
|
580
1225
|
|
|
581
1226
|
@classmethod
|
|
@@ -608,6 +1253,8 @@ class VisualEffects:
|
|
|
608
1253
|
"""Initialize visual effects system."""
|
|
609
1254
|
self.gradient_renderer = GradientRenderer()
|
|
610
1255
|
self.shimmer_effect = ShimmerEffect()
|
|
1256
|
+
self.pulse_effect = PulseEffect()
|
|
1257
|
+
self.scramble_effect = ScrambleEffect()
|
|
611
1258
|
self.status_colorizer = StatusColorizer()
|
|
612
1259
|
self.banner_renderer = BannerRenderer()
|
|
613
1260
|
|
|
@@ -643,7 +1290,7 @@ class VisualEffects:
|
|
|
643
1290
|
|
|
644
1291
|
Args:
|
|
645
1292
|
text: Text to apply effect to.
|
|
646
|
-
effect_type: Type of effect ("shimmer", "dim", "
|
|
1293
|
+
effect_type: Type of effect ("shimmer", "pulse", "scramble", "dim", "none").
|
|
647
1294
|
|
|
648
1295
|
Returns:
|
|
649
1296
|
Text with thinking effect applied.
|
|
@@ -654,9 +1301,13 @@ class VisualEffects:
|
|
|
654
1301
|
|
|
655
1302
|
if effect_type == "shimmer":
|
|
656
1303
|
return self.shimmer_effect.apply_shimmer(text)
|
|
1304
|
+
elif effect_type == "pulse":
|
|
1305
|
+
return self.pulse_effect.apply_pulse(text)
|
|
1306
|
+
elif effect_type == "scramble":
|
|
1307
|
+
return self.scramble_effect.apply_scramble(text)
|
|
657
1308
|
elif effect_type == "dim":
|
|
658
1309
|
return f"{ColorPalette.DIM}{text}{ColorPalette.RESET}"
|
|
659
|
-
else:
|
|
1310
|
+
else: # none or normal
|
|
660
1311
|
return text
|
|
661
1312
|
|
|
662
1313
|
def apply_message_gradient(
|