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
data/lib/clacky/ui/formatter.rb
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "pastel"
|
|
4
|
-
|
|
5
|
-
module Clacky
|
|
6
|
-
module UI
|
|
7
|
-
# Matrix/hacker-style output formatter
|
|
8
|
-
class Formatter
|
|
9
|
-
# Hacker-style symbols (no emoji)
|
|
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
|
-
}.freeze
|
|
26
|
-
|
|
27
|
-
def initialize
|
|
28
|
-
@pastel = Pastel.new
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Format user message
|
|
32
|
-
def user_message(content)
|
|
33
|
-
symbol = @pastel.bright_blue(SYMBOLS[:user])
|
|
34
|
-
text = @pastel.blue(content)
|
|
35
|
-
print "\n#{symbol} #{text}"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Format assistant message
|
|
39
|
-
def assistant_message(content)
|
|
40
|
-
return if content.nil? || content.empty?
|
|
41
|
-
|
|
42
|
-
symbol = @pastel.bright_green(SYMBOLS[:assistant])
|
|
43
|
-
text = @pastel.white(content)
|
|
44
|
-
puts "\n#{symbol} #{text}"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Format tool call
|
|
48
|
-
def tool_call(formatted_call)
|
|
49
|
-
symbol = @pastel.bright_cyan(SYMBOLS[:tool_call])
|
|
50
|
-
text = @pastel.cyan(formatted_call)
|
|
51
|
-
puts "\n#{symbol} #{text}"
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Format tool result
|
|
55
|
-
def tool_result(summary)
|
|
56
|
-
symbol = @pastel.cyan(SYMBOLS[:tool_result])
|
|
57
|
-
text = @pastel.white(summary)
|
|
58
|
-
puts "#{symbol} #{text}"
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Format tool denied
|
|
62
|
-
def tool_denied(tool_name)
|
|
63
|
-
symbol = @pastel.bright_yellow(SYMBOLS[:tool_denied])
|
|
64
|
-
text = @pastel.yellow("Tool denied: #{tool_name}")
|
|
65
|
-
puts "\n#{symbol} #{text}"
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Format tool planned
|
|
69
|
-
def tool_planned(tool_name)
|
|
70
|
-
symbol = @pastel.bright_blue(SYMBOLS[:tool_planned])
|
|
71
|
-
text = @pastel.blue("Planned: #{tool_name}")
|
|
72
|
-
puts "\n#{symbol} #{text}"
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Format tool error
|
|
76
|
-
def tool_error(error_message)
|
|
77
|
-
symbol = @pastel.bright_red(SYMBOLS[:tool_error])
|
|
78
|
-
text = @pastel.red("Error: #{error_message}")
|
|
79
|
-
puts "\n#{symbol} #{text}"
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Format thinking indicator
|
|
83
|
-
def thinking
|
|
84
|
-
symbol = @pastel.dim(SYMBOLS[:thinking])
|
|
85
|
-
# Output symbol on the same line, progress indicator will follow
|
|
86
|
-
print "\n#{symbol} "
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Format success message
|
|
90
|
-
def success(message)
|
|
91
|
-
symbol = @pastel.bright_green(SYMBOLS[:success])
|
|
92
|
-
text = @pastel.green(message)
|
|
93
|
-
puts "#{symbol} #{text}"
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Format error message
|
|
97
|
-
def error(message)
|
|
98
|
-
symbol = @pastel.bright_red(SYMBOLS[:error])
|
|
99
|
-
text = @pastel.red(message)
|
|
100
|
-
puts "#{symbol} #{text}"
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Format warning message
|
|
104
|
-
def warning(message)
|
|
105
|
-
symbol = @pastel.bright_yellow(SYMBOLS[:warning])
|
|
106
|
-
text = @pastel.yellow(message)
|
|
107
|
-
puts "#{symbol} #{text}"
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Format info message
|
|
111
|
-
def info(message)
|
|
112
|
-
symbol = @pastel.bright_white(SYMBOLS[:info])
|
|
113
|
-
text = @pastel.white(message)
|
|
114
|
-
puts "#{symbol} #{text}"
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Format TODO status with progress bar
|
|
118
|
-
def todo_status(todos)
|
|
119
|
-
return if todos.empty?
|
|
120
|
-
|
|
121
|
-
completed = todos.count { |t| t[:status] == "completed" }
|
|
122
|
-
total = todos.size
|
|
123
|
-
|
|
124
|
-
# Build progress bar with hacker style
|
|
125
|
-
progress_bar = todos.map { |t|
|
|
126
|
-
t[:status] == "completed" ? @pastel.green("█") : @pastel.dim("░")
|
|
127
|
-
}.join
|
|
128
|
-
|
|
129
|
-
# Check if all completed
|
|
130
|
-
if completed == total
|
|
131
|
-
symbol = @pastel.bright_green(SYMBOLS[:success])
|
|
132
|
-
puts "\n#{symbol} Tasks [#{completed}/#{total}]: #{progress_bar} #{@pastel.bright_green('COMPLETE')}"
|
|
133
|
-
return
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Find current and next tasks
|
|
137
|
-
current_task = todos.find { |t| t[:status] == "pending" }
|
|
138
|
-
next_task_index = todos.index(current_task)
|
|
139
|
-
next_task = next_task_index && todos[next_task_index + 1]
|
|
140
|
-
|
|
141
|
-
symbol = @pastel.bright_yellow(SYMBOLS[:task])
|
|
142
|
-
puts "\n#{symbol} Tasks [#{completed}/#{total}]: #{progress_bar}"
|
|
143
|
-
|
|
144
|
-
if current_task
|
|
145
|
-
puts " #{@pastel.cyan('→')} Next: ##{current_task[:id]} - #{current_task[:task]}"
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
if next_task && next_task[:status] == "pending"
|
|
149
|
-
puts " #{@pastel.dim('⇢')} After: ##{next_task[:id]} - #{next_task[:task]}"
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
# Format iteration indicator
|
|
154
|
-
def iteration(number)
|
|
155
|
-
symbol = @pastel.dim(SYMBOLS[:progress])
|
|
156
|
-
text = @pastel.dim("Iteration #{number}")
|
|
157
|
-
puts "\n#{symbol} #{text}"
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# Format separator
|
|
161
|
-
def separator(char = "─", width: 80)
|
|
162
|
-
puts @pastel.dim(char * width)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Format section header
|
|
166
|
-
def section_header(title)
|
|
167
|
-
puts
|
|
168
|
-
separator("═")
|
|
169
|
-
puts @pastel.bright_white(title.center(80))
|
|
170
|
-
separator("═")
|
|
171
|
-
puts
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# Format confirmation prompt for tool use
|
|
175
|
-
def tool_confirmation_prompt(formatted_call)
|
|
176
|
-
symbol = @pastel.bright_yellow("[??]")
|
|
177
|
-
puts "\n#{symbol} #{@pastel.yellow(formatted_call)}"
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Format conversation history message
|
|
181
|
-
def history_message(role, content, index, total)
|
|
182
|
-
case role
|
|
183
|
-
when "user"
|
|
184
|
-
symbol = @pastel.blue(SYMBOLS[:user])
|
|
185
|
-
text = @pastel.white(truncate(content, 150))
|
|
186
|
-
puts "#{symbol} You: #{text}"
|
|
187
|
-
when "assistant"
|
|
188
|
-
symbol = @pastel.green(SYMBOLS[:assistant])
|
|
189
|
-
text = @pastel.white(truncate(content, 200))
|
|
190
|
-
puts "#{symbol} Assistant: #{text}"
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
private
|
|
195
|
-
|
|
196
|
-
def truncate(content, max_length)
|
|
197
|
-
return "" if content.nil? || content.empty?
|
|
198
|
-
|
|
199
|
-
cleaned = content.strip.gsub(/\s+/, ' ')
|
|
200
|
-
|
|
201
|
-
if cleaned.length > max_length
|
|
202
|
-
cleaned[0...max_length] + "..."
|
|
203
|
-
else
|
|
204
|
-
cleaned
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
end
|
|
209
|
-
end
|
data/lib/clacky/ui/statusbar.rb
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "pastel"
|
|
4
|
-
require "tty-screen"
|
|
5
|
-
|
|
6
|
-
module Clacky
|
|
7
|
-
module UI
|
|
8
|
-
# Status bar showing session information
|
|
9
|
-
class StatusBar
|
|
10
|
-
def initialize
|
|
11
|
-
@pastel = Pastel.new
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Display session status bar
|
|
15
|
-
# @param working_dir [String] Current working directory
|
|
16
|
-
# @param mode [String] Permission mode
|
|
17
|
-
# @param model [String] AI model name
|
|
18
|
-
# @param tasks [Integer] Number of completed tasks (optional)
|
|
19
|
-
# @param cost [Float] Total cost (optional)
|
|
20
|
-
def display(working_dir:, mode:, model:, tasks: nil, cost: nil)
|
|
21
|
-
parts = []
|
|
22
|
-
|
|
23
|
-
# Working directory (shortened if too long)
|
|
24
|
-
dir_display = shorten_path(working_dir)
|
|
25
|
-
parts << @pastel.bright_cyan(dir_display)
|
|
26
|
-
|
|
27
|
-
# Permission mode
|
|
28
|
-
mode_color = mode_color_for(mode)
|
|
29
|
-
parts << @pastel.public_send(mode_color, mode)
|
|
30
|
-
|
|
31
|
-
# Model
|
|
32
|
-
parts << @pastel.bright_white(model)
|
|
33
|
-
|
|
34
|
-
# Optional: tasks and cost
|
|
35
|
-
if tasks
|
|
36
|
-
parts << @pastel.yellow("#{tasks} tasks")
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
if cost
|
|
40
|
-
parts << @pastel.yellow("$#{cost.round(4)}")
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Join with separator
|
|
44
|
-
separator = @pastel.dim(" │ ")
|
|
45
|
-
status_line = " " + parts.join(separator)
|
|
46
|
-
|
|
47
|
-
puts status_line
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Display minimal status for non-interactive mode
|
|
51
|
-
def display_minimal(working_dir:, mode:)
|
|
52
|
-
dir_display = shorten_path(working_dir)
|
|
53
|
-
puts " #{@pastel.bright_cyan(dir_display)} #{@pastel.dim('│')} #{@pastel.yellow(mode)}"
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
def shorten_path(path)
|
|
59
|
-
return path if path.length <= 40
|
|
60
|
-
|
|
61
|
-
# Replace home directory with ~
|
|
62
|
-
home = ENV['HOME']
|
|
63
|
-
if home && path.start_with?(home)
|
|
64
|
-
path = path.sub(home, '~')
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# If still too long, show last parts
|
|
68
|
-
if path.length > 40
|
|
69
|
-
parts = path.split('/')
|
|
70
|
-
if parts.length > 3
|
|
71
|
-
".../" + parts[-3..-1].join('/')
|
|
72
|
-
else
|
|
73
|
-
path[0..40] + "..."
|
|
74
|
-
end
|
|
75
|
-
else
|
|
76
|
-
path
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def mode_color_for(mode)
|
|
81
|
-
case mode.to_s
|
|
82
|
-
when /auto_approve/
|
|
83
|
-
:bright_red
|
|
84
|
-
when /confirm_safes/
|
|
85
|
-
:bright_yellow
|
|
86
|
-
when /confirm_edits/
|
|
87
|
-
:bright_green
|
|
88
|
-
when /plan_only/
|
|
89
|
-
:bright_blue
|
|
90
|
-
else
|
|
91
|
-
:white
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|