openclacky 0.5.6 → 0.6.0
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/CHANGELOG.md +43 -0
- data/docs/ui2-architecture.md +124 -0
- data/lib/clacky/agent.rb +245 -340
- data/lib/clacky/agent_config.rb +1 -7
- data/lib/clacky/cli.rb +156 -397
- data/lib/clacky/client.rb +68 -36
- data/lib/clacky/gitignore_parser.rb +26 -12
- data/lib/clacky/model_pricing.rb +6 -2
- data/lib/clacky/session_manager.rb +6 -2
- data/lib/clacky/tools/glob.rb +65 -9
- data/lib/clacky/tools/grep.rb +4 -120
- data/lib/clacky/tools/run_project.rb +5 -0
- data/lib/clacky/tools/safe_shell.rb +49 -13
- data/lib/clacky/tools/shell.rb +1 -49
- data/lib/clacky/tools/web_fetch.rb +2 -2
- data/lib/clacky/tools/web_search.rb +38 -26
- data/lib/clacky/ui2/README.md +214 -0
- data/lib/clacky/ui2/components/base_component.rb +163 -0
- data/lib/clacky/ui2/components/common_component.rb +89 -0
- data/lib/clacky/ui2/components/inline_input.rb +187 -0
- data/lib/clacky/ui2/components/input_area.rb +1029 -0
- data/lib/clacky/ui2/components/message_component.rb +76 -0
- data/lib/clacky/ui2/components/output_area.rb +112 -0
- data/lib/clacky/ui2/components/todo_area.rb +137 -0
- data/lib/clacky/ui2/components/tool_component.rb +106 -0
- data/lib/clacky/ui2/components/welcome_banner.rb +93 -0
- data/lib/clacky/ui2/layout_manager.rb +331 -0
- data/lib/clacky/ui2/line_editor.rb +201 -0
- data/lib/clacky/ui2/screen_buffer.rb +238 -0
- data/lib/clacky/ui2/theme_manager.rb +68 -0
- data/lib/clacky/ui2/themes/base_theme.rb +99 -0
- data/lib/clacky/ui2/themes/hacker_theme.rb +56 -0
- data/lib/clacky/ui2/themes/minimal_theme.rb +50 -0
- data/lib/clacky/ui2/ui_controller.rb +720 -0
- data/lib/clacky/ui2/view_renderer.rb +160 -0
- data/lib/clacky/ui2.rb +37 -0
- data/lib/clacky/utils/file_ignore_helper.rb +126 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky.rb +1 -6
- metadata +38 -6
- data/lib/clacky/ui/banner.rb +0 -155
- data/lib/clacky/ui/enhanced_prompt.rb +0 -786
- data/lib/clacky/ui/formatter.rb +0 -209
- data/lib/clacky/ui/statusbar.rb +0 -96
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-screen"
|
|
4
|
+
require "io/console"
|
|
5
|
+
|
|
6
|
+
module Clacky
|
|
7
|
+
module UI2
|
|
8
|
+
# ScreenBuffer manages terminal screen state and provides low-level rendering primitives
|
|
9
|
+
class ScreenBuffer
|
|
10
|
+
attr_reader :width, :height
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@width = TTY::Screen.width
|
|
14
|
+
@height = TTY::Screen.height
|
|
15
|
+
@buffer = []
|
|
16
|
+
@last_input_time = nil
|
|
17
|
+
@rapid_input_threshold = 0.01 # 10ms threshold for detecting paste-like rapid input
|
|
18
|
+
setup_resize_handler
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Move cursor to specific position (0-indexed)
|
|
22
|
+
# @param row [Integer] Row position
|
|
23
|
+
# @param col [Integer] Column position
|
|
24
|
+
def move_cursor(row, col)
|
|
25
|
+
print "\e[#{row + 1};#{col + 1}H"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Clear entire screen
|
|
29
|
+
def clear_screen
|
|
30
|
+
print "\e[2J"
|
|
31
|
+
move_cursor(0, 0)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Clear current line
|
|
35
|
+
def clear_line
|
|
36
|
+
print "\e[2K"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Clear from cursor to end of line
|
|
40
|
+
def clear_to_eol
|
|
41
|
+
print "\e[K"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Hide cursor
|
|
45
|
+
def hide_cursor
|
|
46
|
+
print "\e[?25l"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Show cursor
|
|
50
|
+
def show_cursor
|
|
51
|
+
print "\e[?25h"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Save cursor position
|
|
55
|
+
def save_cursor
|
|
56
|
+
print "\e[s"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Restore cursor position
|
|
60
|
+
def restore_cursor
|
|
61
|
+
print "\e[u"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Enable alternative screen buffer (like vim/less)
|
|
65
|
+
def enable_alt_screen
|
|
66
|
+
print "\e[?1049h"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Disable alternative screen buffer
|
|
70
|
+
def disable_alt_screen
|
|
71
|
+
print "\e[?1049l"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Set scroll region (DECSTBM - DEC Set Top and Bottom Margins)
|
|
75
|
+
# Content written in this region will scroll, content outside will stay fixed
|
|
76
|
+
# @param top [Integer] Top row (1-indexed)
|
|
77
|
+
# @param bottom [Integer] Bottom row (1-indexed)
|
|
78
|
+
def set_scroll_region(top, bottom)
|
|
79
|
+
print "\e[#{top};#{bottom}r"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Reset scroll region to full screen
|
|
83
|
+
def reset_scroll_region
|
|
84
|
+
print "\e[r"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Scroll the scroll region up by n lines
|
|
88
|
+
# @param n [Integer] Number of lines to scroll
|
|
89
|
+
def scroll_up(n = 1)
|
|
90
|
+
print "\e[#{n}S"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Scroll the scroll region down by n lines
|
|
94
|
+
# @param n [Integer] Number of lines to scroll
|
|
95
|
+
def scroll_down(n = 1)
|
|
96
|
+
print "\e[#{n}T"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Get current screen dimensions
|
|
100
|
+
def update_dimensions
|
|
101
|
+
@width = TTY::Screen.width
|
|
102
|
+
@height = TTY::Screen.height
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Enable raw mode (disable line buffering)
|
|
106
|
+
def enable_raw_mode
|
|
107
|
+
$stdin.raw!
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Disable raw mode
|
|
111
|
+
def disable_raw_mode
|
|
112
|
+
$stdin.cooked!
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Read a single character without echo
|
|
116
|
+
# @param timeout [Float] Timeout in seconds (nil for blocking)
|
|
117
|
+
# @return [String, nil] Character or nil if timeout
|
|
118
|
+
def read_char(timeout: nil)
|
|
119
|
+
if timeout
|
|
120
|
+
return nil unless IO.select([$stdin], nil, nil, timeout)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
$stdin.getc
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Read a key including special keys (arrows, etc.)
|
|
127
|
+
# @param timeout [Float] Timeout in seconds
|
|
128
|
+
# @return [Symbol, String, Hash, nil] Key symbol, character, or { type: :rapid_input, text: String }
|
|
129
|
+
def read_key(timeout: nil)
|
|
130
|
+
$stdin.set_encoding('UTF-8')
|
|
131
|
+
|
|
132
|
+
current_time = Time.now.to_f
|
|
133
|
+
is_rapid_input = @last_input_time && (current_time - @last_input_time) < @rapid_input_threshold
|
|
134
|
+
@last_input_time = current_time
|
|
135
|
+
|
|
136
|
+
char = read_char(timeout: timeout)
|
|
137
|
+
return nil unless char
|
|
138
|
+
|
|
139
|
+
# Ensure character is UTF-8 encoded
|
|
140
|
+
char = char.force_encoding('UTF-8') if char.is_a?(String) && char.encoding != Encoding::UTF_8
|
|
141
|
+
|
|
142
|
+
# Handle escape sequences for special keys
|
|
143
|
+
if char == "\e"
|
|
144
|
+
# Non-blocking read for escape sequence
|
|
145
|
+
char2 = read_char(timeout: 0.01)
|
|
146
|
+
return :escape unless char2
|
|
147
|
+
|
|
148
|
+
if char2 == "["
|
|
149
|
+
char3 = read_char(timeout: 0.01)
|
|
150
|
+
case char3
|
|
151
|
+
when "A" then return :up_arrow
|
|
152
|
+
when "B" then return :down_arrow
|
|
153
|
+
when "C" then return :right_arrow
|
|
154
|
+
when "D" then return :left_arrow
|
|
155
|
+
when "H" then return :home
|
|
156
|
+
when "F" then return :end
|
|
157
|
+
when "Z" then return :shift_tab
|
|
158
|
+
when "3"
|
|
159
|
+
char4 = read_char(timeout: 0.01)
|
|
160
|
+
return :delete if char4 == "~"
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Check if there are more characters available (for rapid input detection)
|
|
166
|
+
has_more_input = IO.select([$stdin], nil, nil, 0)
|
|
167
|
+
|
|
168
|
+
# If this is rapid input or there are more characters available
|
|
169
|
+
if is_rapid_input || has_more_input
|
|
170
|
+
buffer = char.to_s.dup
|
|
171
|
+
buffer.force_encoding('UTF-8')
|
|
172
|
+
|
|
173
|
+
# Keep reading available characters
|
|
174
|
+
loop do
|
|
175
|
+
break unless IO.select([$stdin], nil, nil, 0)
|
|
176
|
+
|
|
177
|
+
next_char = $stdin.getc
|
|
178
|
+
break unless next_char
|
|
179
|
+
|
|
180
|
+
next_char = next_char.force_encoding('UTF-8') if next_char.encoding != Encoding::UTF_8
|
|
181
|
+
buffer << next_char
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# If we buffered multiple characters or newlines, treat as rapid input (paste)
|
|
185
|
+
if buffer.length > 1 || buffer.include?("\n") || buffer.include?("\r")
|
|
186
|
+
# Remove any trailing \r or \n from rapid input buffer
|
|
187
|
+
cleaned_buffer = buffer.gsub(/[\r\n]+\z/, '')
|
|
188
|
+
return { type: :rapid_input, text: cleaned_buffer } if cleaned_buffer.length > 0
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Single character, continue to normal handling
|
|
192
|
+
char = buffer[0] if buffer.length == 1
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Handle control characters
|
|
196
|
+
case char
|
|
197
|
+
when "\r" then :enter
|
|
198
|
+
when "\n" then :newline # Shift+Enter sends \n
|
|
199
|
+
when "\u007F", "\b" then :backspace
|
|
200
|
+
when "\u0001" then :ctrl_a
|
|
201
|
+
when "\u0002" then :ctrl_b
|
|
202
|
+
when "\u0003" then :ctrl_c
|
|
203
|
+
when "\u0004" then :ctrl_d
|
|
204
|
+
when "\u0005" then :ctrl_e
|
|
205
|
+
when "\u0006" then :ctrl_f
|
|
206
|
+
when "\u000B" then :ctrl_k
|
|
207
|
+
when "\u000C" then :ctrl_l
|
|
208
|
+
when "\u0012" then :ctrl_r
|
|
209
|
+
when "\u0015" then :ctrl_u
|
|
210
|
+
when "\u0016" then :ctrl_v
|
|
211
|
+
when "\u0017" then :ctrl_w
|
|
212
|
+
else char
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Flush output
|
|
217
|
+
def flush
|
|
218
|
+
$stdout.flush
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
private
|
|
222
|
+
|
|
223
|
+
# Setup handler for terminal resize (SIGWINCH)
|
|
224
|
+
def setup_resize_handler
|
|
225
|
+
Signal.trap("WINCH") do
|
|
226
|
+
update_dimensions
|
|
227
|
+
@resize_callback&.call(@width, @height)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Register callback for resize events
|
|
232
|
+
# @param block [Proc] Callback to execute on resize
|
|
233
|
+
def on_resize(&block)
|
|
234
|
+
@resize_callback = block
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "themes/base_theme"
|
|
4
|
+
require_relative "themes/hacker_theme"
|
|
5
|
+
require_relative "themes/minimal_theme"
|
|
6
|
+
|
|
7
|
+
module Clacky
|
|
8
|
+
module UI2
|
|
9
|
+
# ThemeManager handles theme registration and switching
|
|
10
|
+
class ThemeManager
|
|
11
|
+
class << self
|
|
12
|
+
def instance
|
|
13
|
+
@instance ||= new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Delegate methods to instance
|
|
17
|
+
def current_theme
|
|
18
|
+
instance.current_theme
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def set_theme(name)
|
|
22
|
+
instance.set_theme(name)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def available_themes
|
|
26
|
+
instance.available_themes
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def register_theme(name, theme_class)
|
|
30
|
+
instance.register_theme(name, theme_class)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def initialize
|
|
35
|
+
@themes = {}
|
|
36
|
+
@current_theme = nil
|
|
37
|
+
register_default_themes
|
|
38
|
+
set_theme(:hacker)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def current_theme
|
|
42
|
+
@current_theme
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def set_theme(name)
|
|
46
|
+
name = name.to_sym
|
|
47
|
+
raise ArgumentError, "Unknown theme: #{name}" unless @themes.key?(name)
|
|
48
|
+
|
|
49
|
+
@current_theme = @themes[name].new
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def available_themes
|
|
53
|
+
@themes.keys
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def register_theme(name, theme_class)
|
|
57
|
+
@themes[name.to_sym] = theme_class
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def register_default_themes
|
|
63
|
+
register_theme(:hacker, Themes::HackerTheme)
|
|
64
|
+
register_theme(:minimal, Themes::MinimalTheme)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
module Clacky
|
|
6
|
+
module UI2
|
|
7
|
+
module Themes
|
|
8
|
+
# BaseTheme defines the interface for all themes
|
|
9
|
+
# Subclasses should override SYMBOLS and color methods
|
|
10
|
+
class BaseTheme
|
|
11
|
+
SYMBOLS = {
|
|
12
|
+
user: "[>>]",
|
|
13
|
+
assistant: "[<<]",
|
|
14
|
+
tool_call: "[=>]",
|
|
15
|
+
tool_result: "[<=]",
|
|
16
|
+
tool_denied: "[!!]",
|
|
17
|
+
tool_planned: "[??]",
|
|
18
|
+
tool_error: "[XX]",
|
|
19
|
+
thinking: "[..]",
|
|
20
|
+
success: "[OK]",
|
|
21
|
+
error: "[ER]",
|
|
22
|
+
warning: "[!!]",
|
|
23
|
+
info: "[--]",
|
|
24
|
+
task: "[##]",
|
|
25
|
+
progress: "[>>]",
|
|
26
|
+
file: "[F]",
|
|
27
|
+
command: "[C]",
|
|
28
|
+
cached: "[*]"
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
# Color schemes for different elements
|
|
32
|
+
# Each returns [symbol_color, text_color]
|
|
33
|
+
COLORS = {
|
|
34
|
+
user: [:bright_blue, :blue],
|
|
35
|
+
assistant: [:bright_green, :white],
|
|
36
|
+
tool_call: [:bright_cyan, :cyan],
|
|
37
|
+
tool_result: [:cyan, :white],
|
|
38
|
+
tool_denied: [:bright_yellow, :yellow],
|
|
39
|
+
tool_planned: [:bright_blue, :blue],
|
|
40
|
+
tool_error: [:bright_red, :red],
|
|
41
|
+
thinking: [:dim, :dim],
|
|
42
|
+
success: [:bright_green, :green],
|
|
43
|
+
error: [:bright_red, :red],
|
|
44
|
+
warning: [:bright_yellow, :yellow],
|
|
45
|
+
info: [:bright_white, :white],
|
|
46
|
+
task: [:bright_yellow, :white],
|
|
47
|
+
progress: [:bright_cyan, :cyan],
|
|
48
|
+
file: [:cyan, :white],
|
|
49
|
+
command: [:cyan, :white],
|
|
50
|
+
cached: [:cyan, :cyan]
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
53
|
+
def initialize
|
|
54
|
+
@pastel = Pastel.new
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def symbols
|
|
58
|
+
self.class::SYMBOLS
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def colors
|
|
62
|
+
self.class::COLORS
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def symbol(key)
|
|
66
|
+
symbols[key] || "[??]"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def symbol_color(key)
|
|
70
|
+
colors.dig(key, 0) || :white
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def text_color(key)
|
|
74
|
+
colors.dig(key, 1) || :white
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Format symbol with its color
|
|
78
|
+
# @param key [Symbol] Symbol key (e.g., :user, :assistant)
|
|
79
|
+
# @return [String] Colored symbol
|
|
80
|
+
def format_symbol(key)
|
|
81
|
+
@pastel.public_send(symbol_color(key), symbol(key))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Format text with color for given key
|
|
85
|
+
# @param text [String] Text to format
|
|
86
|
+
# @param key [Symbol] Color key (e.g., :user, :assistant)
|
|
87
|
+
# @return [String] Colored text
|
|
88
|
+
def format_text(text, key)
|
|
89
|
+
@pastel.public_send(text_color(key), text)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Theme name for display
|
|
93
|
+
def name
|
|
94
|
+
"base"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_theme"
|
|
4
|
+
|
|
5
|
+
module Clacky
|
|
6
|
+
module UI2
|
|
7
|
+
module Themes
|
|
8
|
+
# HackerTheme - Matrix/hacker-style with bracket symbols
|
|
9
|
+
class HackerTheme < BaseTheme
|
|
10
|
+
SYMBOLS = {
|
|
11
|
+
user: "[>>]",
|
|
12
|
+
assistant: "[<<]",
|
|
13
|
+
tool_call: "[=>]",
|
|
14
|
+
tool_result: "[<=]",
|
|
15
|
+
tool_denied: "[!!]",
|
|
16
|
+
tool_planned: "[??]",
|
|
17
|
+
tool_error: "[XX]",
|
|
18
|
+
thinking: "[..]",
|
|
19
|
+
success: "[OK]",
|
|
20
|
+
error: "[ER]",
|
|
21
|
+
warning: "[!!]",
|
|
22
|
+
info: "[--]",
|
|
23
|
+
task: "[##]",
|
|
24
|
+
progress: "[>>]",
|
|
25
|
+
file: "[F]",
|
|
26
|
+
command: "[C]",
|
|
27
|
+
cached: "[*]"
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
COLORS = {
|
|
31
|
+
user: [:bright_blue, :blue],
|
|
32
|
+
assistant: [:bright_green, :white],
|
|
33
|
+
tool_call: [:bright_cyan, :cyan],
|
|
34
|
+
tool_result: [:cyan, :white],
|
|
35
|
+
tool_denied: [:bright_yellow, :yellow],
|
|
36
|
+
tool_planned: [:bright_blue, :blue],
|
|
37
|
+
tool_error: [:bright_red, :red],
|
|
38
|
+
thinking: [:dim, :dim],
|
|
39
|
+
success: [:bright_green, :green],
|
|
40
|
+
error: [:bright_red, :red],
|
|
41
|
+
warning: [:bright_yellow, :yellow],
|
|
42
|
+
info: [:bright_white, :white],
|
|
43
|
+
task: [:bright_yellow, :white],
|
|
44
|
+
progress: [:bright_cyan, :cyan],
|
|
45
|
+
file: [:cyan, :white],
|
|
46
|
+
command: [:cyan, :white],
|
|
47
|
+
cached: [:cyan, :cyan]
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
def name
|
|
51
|
+
"hacker"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_theme"
|
|
4
|
+
|
|
5
|
+
module Clacky
|
|
6
|
+
module UI2
|
|
7
|
+
module Themes
|
|
8
|
+
# MinimalTheme - Clean, simple symbols
|
|
9
|
+
class MinimalTheme < BaseTheme
|
|
10
|
+
SYMBOLS = {
|
|
11
|
+
user: ">",
|
|
12
|
+
assistant: "<",
|
|
13
|
+
tool_call: "*",
|
|
14
|
+
tool_result: "-",
|
|
15
|
+
tool_denied: "!",
|
|
16
|
+
tool_planned: "?",
|
|
17
|
+
tool_error: "x",
|
|
18
|
+
thinking: ".",
|
|
19
|
+
success: "+",
|
|
20
|
+
error: "x",
|
|
21
|
+
warning: "!",
|
|
22
|
+
info: "-",
|
|
23
|
+
task: "#",
|
|
24
|
+
progress: ">"
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
COLORS = {
|
|
28
|
+
user: [:blue, :blue],
|
|
29
|
+
assistant: [:green, :white],
|
|
30
|
+
tool_call: [:cyan, :cyan],
|
|
31
|
+
tool_result: [:white, :white],
|
|
32
|
+
tool_denied: [:yellow, :yellow],
|
|
33
|
+
tool_planned: [:blue, :blue],
|
|
34
|
+
tool_error: [:red, :red],
|
|
35
|
+
thinking: [:dim, :dim],
|
|
36
|
+
success: [:green, :green],
|
|
37
|
+
error: [:red, :red],
|
|
38
|
+
warning: [:yellow, :yellow],
|
|
39
|
+
info: [:white, :white],
|
|
40
|
+
task: [:yellow, :white],
|
|
41
|
+
progress: [:cyan, :cyan]
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
def name
|
|
45
|
+
"minimal"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|