kollabor 0.4.9__py3-none-any.whl → 0.4.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. agents/__init__.py +2 -0
  2. agents/coder/__init__.py +0 -0
  3. agents/coder/agent.json +4 -0
  4. agents/coder/api-integration.md +2150 -0
  5. agents/coder/cli-pretty.md +765 -0
  6. agents/coder/code-review.md +1092 -0
  7. agents/coder/database-design.md +1525 -0
  8. agents/coder/debugging.md +1102 -0
  9. agents/coder/dependency-management.md +1397 -0
  10. agents/coder/git-workflow.md +1099 -0
  11. agents/coder/refactoring.md +1454 -0
  12. agents/coder/security-hardening.md +1732 -0
  13. agents/coder/system_prompt.md +1448 -0
  14. agents/coder/tdd.md +1367 -0
  15. agents/creative-writer/__init__.py +0 -0
  16. agents/creative-writer/agent.json +4 -0
  17. agents/creative-writer/character-development.md +1852 -0
  18. agents/creative-writer/dialogue-craft.md +1122 -0
  19. agents/creative-writer/plot-structure.md +1073 -0
  20. agents/creative-writer/revision-editing.md +1484 -0
  21. agents/creative-writer/system_prompt.md +690 -0
  22. agents/creative-writer/worldbuilding.md +2049 -0
  23. agents/data-analyst/__init__.py +30 -0
  24. agents/data-analyst/agent.json +4 -0
  25. agents/data-analyst/data-visualization.md +992 -0
  26. agents/data-analyst/exploratory-data-analysis.md +1110 -0
  27. agents/data-analyst/pandas-data-manipulation.md +1081 -0
  28. agents/data-analyst/sql-query-optimization.md +881 -0
  29. agents/data-analyst/statistical-analysis.md +1118 -0
  30. agents/data-analyst/system_prompt.md +928 -0
  31. agents/default/__init__.py +0 -0
  32. agents/default/agent.json +4 -0
  33. agents/default/dead-code.md +794 -0
  34. agents/default/explore-agent-system.md +585 -0
  35. agents/default/system_prompt.md +1448 -0
  36. agents/kollabor/__init__.py +0 -0
  37. agents/kollabor/analyze-plugin-lifecycle.md +175 -0
  38. agents/kollabor/analyze-terminal-rendering.md +388 -0
  39. agents/kollabor/code-review.md +1092 -0
  40. agents/kollabor/debug-mcp-integration.md +521 -0
  41. agents/kollabor/debug-plugin-hooks.md +547 -0
  42. agents/kollabor/debugging.md +1102 -0
  43. agents/kollabor/dependency-management.md +1397 -0
  44. agents/kollabor/git-workflow.md +1099 -0
  45. agents/kollabor/inspect-llm-conversation.md +148 -0
  46. agents/kollabor/monitor-event-bus.md +558 -0
  47. agents/kollabor/profile-performance.md +576 -0
  48. agents/kollabor/refactoring.md +1454 -0
  49. agents/kollabor/system_prompt copy.md +1448 -0
  50. agents/kollabor/system_prompt.md +757 -0
  51. agents/kollabor/trace-command-execution.md +178 -0
  52. agents/kollabor/validate-config.md +879 -0
  53. agents/research/__init__.py +0 -0
  54. agents/research/agent.json +4 -0
  55. agents/research/architecture-mapping.md +1099 -0
  56. agents/research/codebase-analysis.md +1077 -0
  57. agents/research/dependency-audit.md +1027 -0
  58. agents/research/performance-profiling.md +1047 -0
  59. agents/research/security-review.md +1359 -0
  60. agents/research/system_prompt.md +492 -0
  61. agents/technical-writer/__init__.py +0 -0
  62. agents/technical-writer/agent.json +4 -0
  63. agents/technical-writer/api-documentation.md +2328 -0
  64. agents/technical-writer/changelog-management.md +1181 -0
  65. agents/technical-writer/readme-writing.md +1360 -0
  66. agents/technical-writer/style-guide.md +1410 -0
  67. agents/technical-writer/system_prompt.md +653 -0
  68. agents/technical-writer/tutorial-creation.md +1448 -0
  69. core/__init__.py +0 -2
  70. core/application.py +343 -88
  71. core/cli.py +229 -10
  72. core/commands/menu_renderer.py +463 -59
  73. core/commands/registry.py +14 -9
  74. core/commands/system_commands.py +2461 -14
  75. core/config/loader.py +151 -37
  76. core/config/service.py +18 -6
  77. core/events/bus.py +29 -9
  78. core/events/executor.py +205 -75
  79. core/events/models.py +27 -8
  80. core/fullscreen/command_integration.py +20 -24
  81. core/fullscreen/components/__init__.py +10 -1
  82. core/fullscreen/components/matrix_components.py +1 -2
  83. core/fullscreen/components/space_shooter_components.py +654 -0
  84. core/fullscreen/plugin.py +5 -0
  85. core/fullscreen/renderer.py +52 -13
  86. core/fullscreen/session.py +52 -15
  87. core/io/__init__.py +29 -5
  88. core/io/buffer_manager.py +6 -1
  89. core/io/config_status_view.py +7 -29
  90. core/io/core_status_views.py +267 -347
  91. core/io/input/__init__.py +25 -0
  92. core/io/input/command_mode_handler.py +711 -0
  93. core/io/input/display_controller.py +128 -0
  94. core/io/input/hook_registrar.py +286 -0
  95. core/io/input/input_loop_manager.py +421 -0
  96. core/io/input/key_press_handler.py +502 -0
  97. core/io/input/modal_controller.py +1011 -0
  98. core/io/input/paste_processor.py +339 -0
  99. core/io/input/status_modal_renderer.py +184 -0
  100. core/io/input_errors.py +5 -1
  101. core/io/input_handler.py +211 -2452
  102. core/io/key_parser.py +7 -0
  103. core/io/layout.py +15 -3
  104. core/io/message_coordinator.py +111 -2
  105. core/io/message_renderer.py +129 -4
  106. core/io/status_renderer.py +147 -607
  107. core/io/terminal_renderer.py +97 -51
  108. core/io/terminal_state.py +21 -4
  109. core/io/visual_effects.py +816 -165
  110. core/llm/agent_manager.py +1063 -0
  111. core/llm/api_adapters/__init__.py +44 -0
  112. core/llm/api_adapters/anthropic_adapter.py +432 -0
  113. core/llm/api_adapters/base.py +241 -0
  114. core/llm/api_adapters/openai_adapter.py +326 -0
  115. core/llm/api_communication_service.py +167 -113
  116. core/llm/conversation_logger.py +322 -16
  117. core/llm/conversation_manager.py +556 -30
  118. core/llm/file_operations_executor.py +84 -32
  119. core/llm/llm_service.py +934 -103
  120. core/llm/mcp_integration.py +541 -57
  121. core/llm/message_display_service.py +135 -18
  122. core/llm/plugin_sdk.py +1 -2
  123. core/llm/profile_manager.py +1183 -0
  124. core/llm/response_parser.py +274 -56
  125. core/llm/response_processor.py +16 -3
  126. core/llm/tool_executor.py +6 -1
  127. core/logging/__init__.py +2 -0
  128. core/logging/setup.py +34 -6
  129. core/models/resume.py +54 -0
  130. core/plugins/__init__.py +4 -2
  131. core/plugins/base.py +127 -0
  132. core/plugins/collector.py +23 -161
  133. core/plugins/discovery.py +37 -3
  134. core/plugins/factory.py +6 -12
  135. core/plugins/registry.py +5 -17
  136. core/ui/config_widgets.py +128 -28
  137. core/ui/live_modal_renderer.py +2 -1
  138. core/ui/modal_actions.py +5 -0
  139. core/ui/modal_overlay_renderer.py +0 -60
  140. core/ui/modal_renderer.py +268 -7
  141. core/ui/modal_state_manager.py +29 -4
  142. core/ui/widgets/base_widget.py +7 -0
  143. core/updates/__init__.py +10 -0
  144. core/updates/version_check_service.py +348 -0
  145. core/updates/version_comparator.py +103 -0
  146. core/utils/config_utils.py +685 -526
  147. core/utils/plugin_utils.py +1 -1
  148. core/utils/session_naming.py +111 -0
  149. fonts/LICENSE +21 -0
  150. fonts/README.md +46 -0
  151. fonts/SymbolsNerdFont-Regular.ttf +0 -0
  152. fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
  153. fonts/__init__.py +44 -0
  154. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
  155. kollabor-0.4.15.dist-info/RECORD +228 -0
  156. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
  157. plugins/agent_orchestrator/__init__.py +39 -0
  158. plugins/agent_orchestrator/activity_monitor.py +181 -0
  159. plugins/agent_orchestrator/file_attacher.py +77 -0
  160. plugins/agent_orchestrator/message_injector.py +135 -0
  161. plugins/agent_orchestrator/models.py +48 -0
  162. plugins/agent_orchestrator/orchestrator.py +403 -0
  163. plugins/agent_orchestrator/plugin.py +976 -0
  164. plugins/agent_orchestrator/xml_parser.py +191 -0
  165. plugins/agent_orchestrator_plugin.py +9 -0
  166. plugins/enhanced_input/box_styles.py +1 -0
  167. plugins/enhanced_input/color_engine.py +19 -4
  168. plugins/enhanced_input/config.py +2 -2
  169. plugins/enhanced_input_plugin.py +61 -11
  170. plugins/fullscreen/__init__.py +6 -2
  171. plugins/fullscreen/example_plugin.py +1035 -222
  172. plugins/fullscreen/setup_wizard_plugin.py +592 -0
  173. plugins/fullscreen/space_shooter_plugin.py +131 -0
  174. plugins/hook_monitoring_plugin.py +436 -78
  175. plugins/query_enhancer_plugin.py +66 -30
  176. plugins/resume_conversation_plugin.py +1494 -0
  177. plugins/save_conversation_plugin.py +98 -32
  178. plugins/system_commands_plugin.py +70 -56
  179. plugins/tmux_plugin.py +154 -78
  180. plugins/workflow_enforcement_plugin.py +94 -92
  181. system_prompt/default.md +952 -886
  182. core/io/input_mode_manager.py +0 -402
  183. core/io/modal_interaction_handler.py +0 -315
  184. core/io/raw_input_processor.py +0 -946
  185. core/storage/__init__.py +0 -5
  186. core/storage/state_manager.py +0 -84
  187. core/ui/widget_integration.py +0 -222
  188. core/utils/key_reader.py +0 -171
  189. kollabor-0.4.9.dist-info/RECORD +0 -128
  190. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
  191. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
  192. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
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
- class ColorPalette:
32
- """Color palette definitions for various effects - RGB True Color (24-bit)."""
33
-
34
- # Standard colors
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
- # Basic colors - RGB True Color
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), # Light lime
123
- (163, 230, 53), # Primary lime (#a3e635) - hero color!
124
- (145, 210, 45), # Medium lime
125
- (132, 204, 22), # Darker lime (#84cc16)
126
- (115, 180, 18), # Deep lime
127
- (100, 160, 15), # Strong lime
128
- (115, 180, 18), # Deep lime (return)
129
- (132, 204, 22), # Darker lime (return)
130
- (163, 230, 53), # Primary lime (return)
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
- color_code = f"\033[38;2;{r};{g};{b}m"
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
- result.append(ColorPalette.RESET)
280
- return "".join(result)
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
- # Base - darker dim blue
349
- result.append(f"\033[2;94m{char}{ColorPalette.RESET}")
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
- KOLLABOR_ASCII = [
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", "normal").
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(