openclacky 0.7.2 → 0.7.4

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/.clacky/skills/commit/SKILL.md +252 -74
  3. data/CHANGELOG.md +38 -0
  4. data/bin/openclacky +2 -0
  5. data/lib/clacky/agent/message_compressor_helper.rb +1 -1
  6. data/lib/clacky/agent/system_prompt_builder.rb +9 -8
  7. data/lib/clacky/agent/tool_executor.rb +4 -13
  8. data/lib/clacky/agent.rb +28 -7
  9. data/lib/clacky/cli.rb +22 -5
  10. data/lib/clacky/default_skills/new/SKILL.md +61 -30
  11. data/lib/clacky/default_skills/new/scripts/create_rails_project.sh +176 -0
  12. data/lib/clacky/default_skills/new/scripts/rails_env_checker.sh +389 -0
  13. data/lib/clacky/default_skills/skill-add/SKILL.md +251 -34
  14. data/lib/clacky/default_skills/skill-add/scripts/install_from_github.rb +189 -0
  15. data/lib/clacky/providers.rb +13 -11
  16. data/lib/clacky/tools/invoke_skill.rb +5 -1
  17. data/lib/clacky/tools/safe_shell.rb +2 -2
  18. data/lib/clacky/tools/shell.rb +48 -20
  19. data/lib/clacky/ui2/components/input_area.rb +27 -9
  20. data/lib/clacky/ui2/components/modal_component.rb +22 -2
  21. data/lib/clacky/ui2/components/welcome_banner.rb +33 -10
  22. data/lib/clacky/ui2/layout_manager.rb +107 -26
  23. data/lib/clacky/ui2/line_editor.rb +6 -5
  24. data/lib/clacky/ui2/screen_buffer.rb +0 -15
  25. data/lib/clacky/ui2/terminal_detector.rb +119 -0
  26. data/lib/clacky/ui2/theme_manager.rb +18 -0
  27. data/lib/clacky/ui2/themes/base_theme.rb +22 -1
  28. data/lib/clacky/ui2/themes/hacker_theme.rb +22 -18
  29. data/lib/clacky/ui2/themes/minimal_theme.rb +19 -15
  30. data/lib/clacky/ui2/ui_controller.rb +66 -15
  31. data/lib/clacky/ui2.rb +1 -0
  32. data/lib/clacky/utils/limit_stack.rb +5 -0
  33. data/lib/clacky/version.rb +1 -1
  34. metadata +5 -1
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "screen_buffer"
4
+ require_relative "../utils/limit_stack"
4
5
 
5
6
  module Clacky
6
7
  module UI2
@@ -16,6 +17,8 @@ module Clacky
16
17
  @output_row = 0 # Track current output row position
17
18
  @last_fixed_area_height = 0 # Track previous fixed area height to detect shrinkage
18
19
  @fullscreen_mode = false # Track if in fullscreen mode
20
+ @resize_pending = false # Flag to indicate resize is pending
21
+ @output_buffer = Utils::LimitStack.new(max_size: 500) # Buffer to store output lines with auto-rolling
19
22
 
20
23
  calculate_layout
21
24
  setup_resize_handler
@@ -88,6 +91,42 @@ module Clacky
88
91
  end
89
92
  end
90
93
 
94
+ # Re-render everything from scratch (useful after modal dialogs)
95
+ def rerender_all
96
+ @render_mutex.synchronize do
97
+ # Clear entire screen
98
+ screen.clear_screen
99
+
100
+ # Re-render output from buffer
101
+ render_output_from_buffer
102
+
103
+ # Re-render fixed areas at new positions
104
+ render_fixed_areas
105
+ screen.flush
106
+ end
107
+ end
108
+
109
+ # Render output area from buffer (clears and re-renders last N lines)
110
+ private def render_output_from_buffer
111
+ max_output_row = fixed_area_start_row
112
+
113
+ # Clear output area
114
+ (0...max_output_row).each do |row|
115
+ screen.move_cursor(row, 0)
116
+ screen.clear_line
117
+ end
118
+
119
+ # Re-render from buffer (show last N lines that fit)
120
+ @output_row = 0
121
+ visible_lines = [@output_buffer.size, max_output_row].min
122
+
123
+ @output_buffer.last(visible_lines).each do |line|
124
+ screen.move_cursor(@output_row, 0)
125
+ print line
126
+ @output_row += 1
127
+ end
128
+ end
129
+
91
130
  # Position cursor for inline input in output area
92
131
  # @param inline_input [Components::InlineInput] InlineInput component
93
132
  def position_inline_input_cursor(inline_input)
@@ -178,6 +217,9 @@ module Clacky
178
217
 
179
218
  # Reset output position to beginning
180
219
  @output_row = 0
220
+
221
+ # Clear the output buffer so re-renders don't restore old content
222
+ @output_buffer.clear
181
223
 
182
224
  # Re-render fixed areas to ensure they stay in place
183
225
  render_fixed_areas
@@ -226,6 +268,11 @@ module Clacky
226
268
  screen.clear_line
227
269
  end
228
270
 
271
+ # Remove old lines from buffer
272
+ old_line_count.times do
273
+ @output_buffer.pop if @output_buffer.size > 0
274
+ end
275
+
229
276
  # Re-render the content
230
277
  lines = content.split("\n", -1)
231
278
  current_row = start_row
@@ -233,6 +280,8 @@ module Clacky
233
280
  lines.each_with_index do |line, idx|
234
281
  screen.move_cursor(current_row, 0)
235
282
  print line
283
+ # Add updated line to buffer
284
+ @output_buffer << line
236
285
  current_row += 1
237
286
  end
238
287
 
@@ -272,6 +321,11 @@ module Clacky
272
321
  screen.clear_line
273
322
  end
274
323
 
324
+ # Also remove from output buffer to prevent re-rendering
325
+ line_count.times do
326
+ @output_buffer.pop if @output_buffer.size > 0
327
+ end
328
+
275
329
  # Update output_row
276
330
  @output_row = start_row
277
331
 
@@ -294,24 +348,29 @@ module Clacky
294
348
  end
295
349
 
296
350
  # Handle window resize
297
- def handle_resize
298
- old_gap_row = @gap_row
299
-
351
+ private def handle_resize
352
+ # Update terminal dimensions and recalculate layout
300
353
  screen.update_dimensions
301
354
  calculate_layout
302
355
 
303
- # Adjust @output_row if it exceeds new layout
304
- # After resize, @output_row should not exceed fixed_area_start_row
305
- max_allowed = fixed_area_start_row
306
- @output_row = [@output_row, max_allowed].min
307
-
308
- # Clear old fixed area and some lines above (terminal may have wrapped content)
309
- clear_start = [old_gap_row - 5, 0].max
310
- (clear_start...screen.height).each do |row|
311
- screen.move_cursor(row, 0)
312
- screen.clear_line
356
+ # Clear entire screen
357
+ screen.clear_screen
358
+
359
+ # Re-render all output from buffer
360
+ @output_row = 0
361
+ max_output_row = fixed_area_start_row
362
+
363
+ # Calculate how many lines we can show from the end of buffer
364
+ visible_lines = [@output_buffer.size, max_output_row].min
365
+
366
+ # Render the last N lines that fit in the output area
367
+ @output_buffer.last(visible_lines).each do |line|
368
+ screen.move_cursor(@output_row, 0)
369
+ print line
370
+ @output_row += 1
313
371
  end
314
-
372
+
373
+ # Re-render fixed areas at new positions
315
374
  render_fixed_areas
316
375
  screen.flush
317
376
  end
@@ -320,6 +379,9 @@ module Clacky
320
379
  # Handles scrolling when reaching fixed area
321
380
  # @param line [String] Single line to write (should not contain newlines)
322
381
  def write_output_line(line)
382
+ # Add to buffer for potential re-rendering
383
+ @output_buffer << line
384
+
323
385
  # Calculate where fixed area starts (this is where output area ends)
324
386
  max_output_row = fixed_area_start_row
325
387
 
@@ -461,15 +523,12 @@ module Clacky
461
523
  todo_row = gap_row + 1
462
524
  input_row = todo_row + (@todo_area&.height || 0)
463
525
 
464
- # If fixed area shrank, clear the extra lines at the top to remove residual content
465
- if @last_fixed_area_height > current_fixed_height
466
- height_diff = @last_fixed_area_height - current_fixed_height
467
- old_start_row = screen.height - @last_fixed_area_height
468
- # Clear the extra lines that are no longer part of fixed area
469
- (old_start_row...(old_start_row + height_diff)).each do |row|
470
- screen.move_cursor(row, 0)
471
- screen.clear_line
472
- end
526
+ # Detect height changes and re-render output area if needed
527
+ if @last_fixed_area_height > 0 && @last_fixed_area_height != current_fixed_height
528
+ # Fixed area height changed - re-render output area from buffer
529
+ # This prevents output content from being hidden when fixed area grows
530
+ # (e.g., multi-line input, command suggestions appearing)
531
+ render_output_from_buffer
473
532
  end
474
533
 
475
534
  # Update last height for next comparison
@@ -555,12 +614,34 @@ module Clacky
555
614
  end
556
615
 
557
616
  # Setup handler for window resize
558
- def setup_resize_handler
617
+ # Note: Signal handlers run in trap context where many operations are restricted
618
+ private def setup_resize_handler
559
619
  Signal.trap("WINCH") do
620
+ # Simply set a flag - actual resize handling happens in main thread
621
+ @resize_pending = true
622
+ end
623
+ rescue ArgumentError => e
624
+ # Signal already trapped (shouldn't happen now)
625
+ warn "WINCH signal already trapped: #{e.message}"
626
+ end
627
+
628
+ # Check and process pending resize (should be called from main thread periodically)
629
+ def process_pending_resize
630
+ return unless @resize_pending
631
+
632
+ @resize_pending = false
633
+ handle_resize_safely
634
+ end
635
+
636
+ # Thread-safe wrapper for handle_resize
637
+ private def handle_resize_safely
638
+ @render_mutex.synchronize do
560
639
  handle_resize
561
640
  end
562
- rescue ArgumentError
563
- # Signal already trapped, ignore
641
+ rescue => e
642
+ # Catch and log errors to prevent resize from crashing the app
643
+ warn "Resize error: #{e.message}"
644
+ warn e.backtrace.first(5).join("\n") if e.backtrace
564
645
  end
565
646
  end
566
647
  end
@@ -334,7 +334,7 @@ module Clacky
334
334
  # @param line [String] Full line text
335
335
  # @param segment_start [Integer] Start position of segment in line (char index)
336
336
  # @param segment_end [Integer] End position of segment in line (char index)
337
- # @return [String] Rendered segment with cursor if applicable
337
+ # @return [String] Rendered segment with cursor if applicable (without text color, only cursor highlight)
338
338
  def render_line_segment_with_cursor(line, segment_start, segment_end)
339
339
  chars = line.chars
340
340
  segment_chars = chars[segment_start...segment_end]
@@ -347,14 +347,15 @@ module Clacky
347
347
  cursor_char = segment_chars[cursor_pos_in_segment] || " "
348
348
  after_cursor = segment_chars[(cursor_pos_in_segment + 1)..-1]&.join || ""
349
349
 
350
- "#{@pastel.white(before_cursor)}#{@pastel.on_white(@pastel.black(cursor_char))}#{@pastel.white(after_cursor)}"
350
+ # Only apply cursor highlight, let subclasses apply text color
351
+ "#{before_cursor}#{@pastel.on_white(@pastel.black(cursor_char))}#{after_cursor}"
351
352
  elsif @cursor_position == segment_end && segment_end == line.length
352
353
  # Cursor is at the very end of the line, show it in last segment
353
354
  segment_text = segment_chars.join
354
- "#{@pastel.white(segment_text)}#{@pastel.on_white(@pastel.black(' '))}"
355
+ "#{segment_text}#{@pastel.on_white(@pastel.black(' '))}"
355
356
  else
356
- # Cursor is not in this segment, just format normally
357
- @pastel.white(segment_chars.join)
357
+ # Cursor is not in this segment, return plain text without color
358
+ segment_chars.join
358
359
  end
359
360
  end
360
361
  end
@@ -15,7 +15,6 @@ module Clacky
15
15
  @buffer = []
16
16
  @last_input_time = nil
17
17
  @rapid_input_threshold = 0.01 # 10ms threshold for detecting paste-like rapid input
18
- setup_resize_handler
19
18
  end
20
19
 
21
20
  # Move cursor to specific position (0-indexed)
@@ -240,20 +239,6 @@ module Clacky
240
239
  end
241
240
 
242
241
  private
243
-
244
- # Setup handler for terminal resize (SIGWINCH)
245
- def setup_resize_handler
246
- Signal.trap("WINCH") do
247
- update_dimensions
248
- @resize_callback&.call(@width, @height)
249
- end
250
- end
251
-
252
- # Register callback for resize events
253
- # @param block [Proc] Callback to execute on resize
254
- def on_resize(&block)
255
- @resize_callback = block
256
- end
257
242
  end
258
243
  end
259
244
  end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clacky
4
+ module UI2
5
+ # TerminalDetector - Detect terminal background color before UI starts
6
+ class TerminalDetector
7
+ # Detect if terminal has dark background
8
+ # Uses multiple strategies to determine background color
9
+ # @return [Boolean] true if dark background, false if light background
10
+ def self.detect_dark_background
11
+ # Strategy 1: Check $COLORFGBG environment variable (fast, set by some terminals)
12
+ if ENV.key?('COLORFGBG')
13
+ # Format is like "15;0" where second number is background ANSI code
14
+ # 0-7 are dark, 8-15 are light
15
+ parts = ENV['COLORFGBG'].split(';')
16
+ if parts.size >= 2
17
+ bg_code = parts.last.to_i
18
+ if bg_code >= 0 && bg_code <= 15
19
+ return bg_code < 8
20
+ end
21
+ end
22
+ end
23
+
24
+ # Strategy 2: Query terminal background using OSC 11 sequence
25
+ begin
26
+ rgb = query_terminal_background_color
27
+ if rgb
28
+ # Calculate luma (perceived brightness): 0.0 (black) to 1.0 (white)
29
+ # Formula: 0.299*R + 0.587*G + 0.114*B
30
+ luma = (0.299 * rgb[:r] + 0.587 * rgb[:g] + 0.114 * rgb[:b]) / 255.0
31
+ return luma < 0.5
32
+ end
33
+ rescue => e
34
+ # Silently fall through to default
35
+ end
36
+
37
+ # Default: assume dark background (most common for terminals)
38
+ true
39
+ end
40
+
41
+ # Query terminal background color using OSC 11 sequence
42
+ # This should be called BEFORE UI starts to avoid interference
43
+ # @return [Hash, nil] RGB hash like {r: 26, g: 43, b: 60} or nil if failed
44
+ def self.query_terminal_background_color
45
+ require 'io/console'
46
+
47
+ # Only works on TTY
48
+ return nil unless $stdin.tty?
49
+ return nil unless $stdout.tty?
50
+
51
+ old_state = nil
52
+ begin
53
+ # Save current terminal state
54
+ old_state = $stdin.raw!
55
+
56
+ # Clear any pending input first
57
+ while IO.select([$stdin], nil, nil, 0)
58
+ $stdin.read_nonblock(1000) rescue break
59
+ end
60
+
61
+ # Send OSC 11 query: ESC ] 11 ; ? ST
62
+ # Use ST terminator (ESC \) instead of BEL for better compatibility
63
+ $stdout.print "\e]11;?\e\\\\"
64
+ $stdout.flush
65
+
66
+ # Read response with timeout (terminal should respond quickly)
67
+ response = String.new # Use String.new to create mutable string
68
+ timeout = 0.1 # 100ms timeout
69
+ start_time = Time.now
70
+
71
+ while Time.now - start_time < timeout
72
+ if IO.select([$stdin], nil, nil, 0.01)
73
+ char = $stdin.read_nonblock(1) rescue nil
74
+ break unless char
75
+ response << char
76
+
77
+ # Look for complete response pattern
78
+ # Response format: ESC ] 11 ; rgb:RRRR/GGGG/BBBB BEL or ST
79
+ if response.match?(/\e\]11;rgb:[0-9a-fA-F]+\/[0-9a-fA-F]+\/[0-9a-fA-F]+(\e\\|\a)/)
80
+ break
81
+ end
82
+
83
+ # Safety: stop if response gets too long (probably garbage)
84
+ break if response.length > 100
85
+ end
86
+ end
87
+
88
+ # Parse response: look for rgb:RRRR/GGGG/BBBB
89
+ # Example: ]11;rgb:1a2b/3c4d/5e6f or ]11;rgb:ffff/ffff/ffff
90
+ if response =~ /rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)/
91
+ r_hex, g_hex, b_hex = $1, $2, $3
92
+ # Take first 2 hex digits (terminals may return 4 or 2 hex digits per channel)
93
+ r = r_hex[0, 2].to_i(16)
94
+ g = g_hex[0, 2].to_i(16)
95
+ b = b_hex[0, 2].to_i(16)
96
+ return { r: r, g: g, b: b }
97
+ end
98
+
99
+ nil
100
+ rescue => e
101
+ # If anything goes wrong, return nil to fall back to default
102
+ nil
103
+ ensure
104
+ # Make sure we restore terminal state even if error occurs
105
+ old_state.restore if old_state rescue nil
106
+
107
+ # Clear any remaining input to prevent leakage
108
+ begin
109
+ while IO.select([$stdin], nil, nil, 0)
110
+ $stdin.read_nonblock(1000) rescue break
111
+ end
112
+ rescue
113
+ # Ignore cleanup errors
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -34,10 +34,26 @@ module Clacky
34
34
  def initialize
35
35
  @themes = {}
36
36
  @current_theme = nil
37
+ @is_dark_background = nil # Store detected background mode
37
38
  register_default_themes
38
39
  set_theme(:hacker)
39
40
  end
40
41
 
42
+ # Set the detected terminal background mode
43
+ # This should be called BEFORE UI starts (from CLI)
44
+ # @param is_dark [Boolean] true if dark background, false if light
45
+ def set_background_mode(is_dark)
46
+ @is_dark_background = is_dark
47
+ # Pass to current theme if already initialized
48
+ @current_theme&.set_background_mode(is_dark)
49
+ end
50
+
51
+ # Get the detected background mode
52
+ # @return [Boolean, nil] true if dark, false if light, nil if not detected
53
+ def dark_background?
54
+ @is_dark_background
55
+ end
56
+
41
57
  def current_theme
42
58
  @current_theme
43
59
  end
@@ -47,6 +63,8 @@ module Clacky
47
63
  raise ArgumentError, "Unknown theme: #{name}" unless @themes.key?(name)
48
64
 
49
65
  @current_theme = @themes[name].new
66
+ # Pass background mode to new theme if already detected
67
+ @current_theme.set_background_mode(@is_dark_background) unless @is_dark_background.nil?
50
68
  end
51
69
 
52
70
  def available_themes
@@ -10,9 +10,16 @@ module Clacky
10
10
  class BaseTheme
11
11
  def initialize
12
12
  @pastel = Pastel.new
13
+ @is_dark_background = nil # Will be set by ThemeManager
13
14
  validate_theme_definition!
14
15
  end
15
16
 
17
+ # Set background mode (called by ThemeManager after detection)
18
+ # @param is_dark [Boolean] true if dark background, false if light
19
+ def set_background_mode(is_dark)
20
+ @is_dark_background = is_dark
21
+ end
22
+
16
23
  # Get all symbols defined by this theme
17
24
  # @return [Hash] Symbol definitions
18
25
  def symbols
@@ -40,10 +47,16 @@ module Clacky
40
47
  end
41
48
 
42
49
  # Get text color for a specific key
50
+ # Automatically selects appropriate color based on terminal background
51
+ # Color format: [symbol_color, dark_bg_text_color, light_bg_text_color]
43
52
  # @param key [Symbol] Color key
44
53
  # @return [Symbol] Pastel color method name
45
54
  def text_color(key)
46
- colors.dig(key, 1) || :white
55
+ color_def = colors[key]
56
+ return :white unless color_def
57
+
58
+ # Use index 1 for dark background, index 2 for light background
59
+ dark_background? ? color_def[1] : color_def[2]
47
60
  end
48
61
 
49
62
  # Format symbol with its color
@@ -67,6 +80,14 @@ module Clacky
67
80
  raise NotImplementedError, "Subclass must implement #name method"
68
81
  end
69
82
 
83
+ # Check if terminal has dark background
84
+ # Uses pre-detected value from ThemeManager, or defaults to true
85
+ # @return [Boolean] true if dark background, false if light background
86
+ def dark_background?
87
+ # Use pre-detected value if available, otherwise default to dark
88
+ @is_dark_background.nil? ? true : @is_dark_background
89
+ end
90
+
70
91
  private
71
92
 
72
93
  # Validate that subclass has defined required constants
@@ -29,24 +29,28 @@ module Clacky
29
29
  }.freeze
30
30
 
31
31
  COLORS = {
32
- user: [:white, :white],
33
- assistant: [:bright_green, :white],
34
- tool_call: [:bright_cyan, :cyan],
35
- tool_result: [:bright_cyan, :cyan],
36
- tool_denied: [:bright_yellow, :yellow],
37
- tool_planned: [:bright_cyan, :cyan],
38
- tool_error: [:bright_red, :red],
39
- thinking: [:dim, :dim],
40
- working: [:bright_yellow, :yellow],
41
- success: [:bright_green, :green],
42
- error: [:bright_red, :red],
43
- warning: [:bright_yellow, :yellow],
44
- info: [:bright_white, :white],
45
- task: [:bright_yellow, :white],
46
- progress: [:bright_cyan, :cyan],
47
- file: [:cyan, :white],
48
- command: [:cyan, :white],
49
- cached: [:cyan, :cyan]
32
+ # Format: [symbol_color, dark_bg_text, light_bg_text]
33
+ user: [:bright_black, :white, :black], # User prompt and input
34
+ assistant: [:bright_green, :bright_black, :bright_black], # AI response - keep green hacker style
35
+ tool_call: [:bright_cyan, :cyan, :cyan], # Tool execution
36
+ tool_result: [:bright_cyan, :bright_black, :bright_black], # Tool output
37
+ tool_denied: [:bright_yellow, :yellow, :yellow], # Denied actions
38
+ tool_planned: [:bright_cyan, :cyan, :cyan], # Planned actions
39
+ tool_error: [:bright_red, :red, :red], # Errors
40
+ thinking: [:bright_black, :bright_black, :bright_black], # Thinking status
41
+ working: [:bright_yellow, :yellow, :yellow], # Working status
42
+ success: [:bright_green, :green, :green], # Success messages
43
+ error: [:bright_red, :red, :red], # Error messages
44
+ warning: [:bright_yellow, :yellow, :yellow], # Warnings
45
+ info: [:bright_black, :bright_black, :bright_black], # Info messages
46
+ task: [:bright_yellow, :bright_black, :bright_black], # Task items
47
+ progress: [:bright_cyan, :cyan, :cyan], # Progress indicators
48
+ file: [:cyan, :bright_black, :bright_black], # File references
49
+ command: [:cyan, :bright_black, :bright_black], # Command references
50
+ cached: [:cyan, :cyan, :cyan], # Cached indicators
51
+ # Status bar colors
52
+ statusbar_path: [:bright_black, :bright_black, :bright_black], # Path
53
+ statusbar_secondary: [:bright_black, :bright_black, :bright_black] # Model/tasks/cost
50
54
  }.freeze
51
55
 
52
56
  def name
@@ -26,21 +26,25 @@ module Clacky
26
26
  }.freeze
27
27
 
28
28
  COLORS = {
29
- user: [:white, :white],
30
- assistant: [:green, :white],
31
- tool_call: [:cyan, :cyan],
32
- tool_result: [:cyan, :cyan],
33
- tool_denied: [:yellow, :yellow],
34
- tool_planned: [:cyan, :cyan],
35
- tool_error: [:red, :red],
36
- thinking: [:dim, :dim],
37
- working: [:bright_yellow, :yellow],
38
- success: [:green, :green],
39
- error: [:red, :red],
40
- warning: [:yellow, :yellow],
41
- info: [:white, :white],
42
- task: [:yellow, :white],
43
- progress: [:cyan, :cyan]
29
+ # Format: [symbol_color, dark_bg_text, light_bg_text]
30
+ user: [:bright_black, :bright_black, :black], # User prompt and input
31
+ assistant: [:green, :bright_black, :bright_black], # AI response
32
+ tool_call: [:cyan, :cyan, :cyan], # Tool execution
33
+ tool_result: [:cyan, :bright_black, :bright_black], # Tool output
34
+ tool_denied: [:yellow, :yellow, :yellow], # Denied actions
35
+ tool_planned: [:cyan, :cyan, :cyan], # Planned actions
36
+ tool_error: [:red, :red, :red], # Errors
37
+ thinking: [:bright_black, :bright_black, :bright_black], # Thinking status
38
+ working: [:bright_yellow, :yellow, :yellow], # Working status
39
+ success: [:green, :green, :green], # Success messages
40
+ error: [:red, :red, :red], # Error messages
41
+ warning: [:yellow, :yellow, :yellow], # Warnings
42
+ info: [:bright_black, :bright_black, :bright_black], # Info messages
43
+ task: [:yellow, :bright_black, :bright_black], # Task items
44
+ progress: [:cyan, :cyan, :cyan], # Progress indicators
45
+ # Status bar colors
46
+ statusbar_path: [:bright_black, :bright_black, :bright_black], # Path
47
+ statusbar_secondary: [:bright_black, :bright_black, :bright_black] # Model/tasks/cost
44
48
  }.freeze
45
49
 
46
50
  def name