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.
- checksums.yaml +4 -4
- data/.clacky/skills/commit/SKILL.md +252 -74
- data/CHANGELOG.md +38 -0
- data/bin/openclacky +2 -0
- data/lib/clacky/agent/message_compressor_helper.rb +1 -1
- data/lib/clacky/agent/system_prompt_builder.rb +9 -8
- data/lib/clacky/agent/tool_executor.rb +4 -13
- data/lib/clacky/agent.rb +28 -7
- data/lib/clacky/cli.rb +22 -5
- data/lib/clacky/default_skills/new/SKILL.md +61 -30
- data/lib/clacky/default_skills/new/scripts/create_rails_project.sh +176 -0
- data/lib/clacky/default_skills/new/scripts/rails_env_checker.sh +389 -0
- data/lib/clacky/default_skills/skill-add/SKILL.md +251 -34
- data/lib/clacky/default_skills/skill-add/scripts/install_from_github.rb +189 -0
- data/lib/clacky/providers.rb +13 -11
- data/lib/clacky/tools/invoke_skill.rb +5 -1
- data/lib/clacky/tools/safe_shell.rb +2 -2
- data/lib/clacky/tools/shell.rb +48 -20
- data/lib/clacky/ui2/components/input_area.rb +27 -9
- data/lib/clacky/ui2/components/modal_component.rb +22 -2
- data/lib/clacky/ui2/components/welcome_banner.rb +33 -10
- data/lib/clacky/ui2/layout_manager.rb +107 -26
- data/lib/clacky/ui2/line_editor.rb +6 -5
- data/lib/clacky/ui2/screen_buffer.rb +0 -15
- data/lib/clacky/ui2/terminal_detector.rb +119 -0
- data/lib/clacky/ui2/theme_manager.rb +18 -0
- data/lib/clacky/ui2/themes/base_theme.rb +22 -1
- data/lib/clacky/ui2/themes/hacker_theme.rb +22 -18
- data/lib/clacky/ui2/themes/minimal_theme.rb +19 -15
- data/lib/clacky/ui2/ui_controller.rb +66 -15
- data/lib/clacky/ui2.rb +1 -0
- data/lib/clacky/utils/limit_stack.rb +5 -0
- data/lib/clacky/version.rb +1 -1
- 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
|
-
|
|
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
|
-
#
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
#
|
|
465
|
-
if @last_fixed_area_height > current_fixed_height
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
#
|
|
469
|
-
|
|
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
|
-
|
|
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
|
|
563
|
-
#
|
|
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
|
-
|
|
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
|
-
"#{
|
|
355
|
+
"#{segment_text}#{@pastel.on_white(@pastel.black(' '))}"
|
|
355
356
|
else
|
|
356
|
-
# Cursor is not in this segment,
|
|
357
|
-
|
|
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
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|