openclacky 0.6.0 → 0.6.1
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 +28 -0
- data/lib/clacky/agent.rb +137 -12
- data/lib/clacky/cli.rb +15 -5
- data/lib/clacky/tools/glob.rb +2 -2
- data/lib/clacky/tools/grep.rb +2 -2
- data/lib/clacky/tools/run_project.rb +5 -5
- data/lib/clacky/tools/safe_shell.rb +101 -8
- data/lib/clacky/tools/shell.rb +2 -2
- data/lib/clacky/tools/todo_manager.rb +50 -3
- data/lib/clacky/tools/trash_manager.rb +1 -1
- data/lib/clacky/tools/web_fetch.rb +2 -2
- data/lib/clacky/tools/web_search.rb +2 -2
- data/lib/clacky/ui2/components/common_component.rb +14 -5
- data/lib/clacky/ui2/components/input_area.rb +124 -29
- data/lib/clacky/ui2/components/message_component.rb +7 -3
- data/lib/clacky/ui2/components/output_area.rb +1 -1
- data/lib/clacky/ui2/components/todo_area.rb +38 -45
- data/lib/clacky/ui2/components/welcome_banner.rb +10 -0
- data/lib/clacky/ui2/layout_manager.rb +120 -14
- data/lib/clacky/ui2/markdown_renderer.rb +80 -0
- data/lib/clacky/ui2/screen_buffer.rb +26 -7
- data/lib/clacky/ui2/themes/base_theme.rb +32 -46
- data/lib/clacky/ui2/themes/hacker_theme.rb +2 -0
- data/lib/clacky/ui2/themes/minimal_theme.rb +2 -0
- data/lib/clacky/ui2/ui_controller.rb +70 -12
- data/lib/clacky/ui2/view_renderer.rb +21 -4
- data/lib/clacky/version.rb +1 -1
- metadata +16 -1
|
@@ -149,11 +149,11 @@ module Clacky
|
|
|
149
149
|
|
|
150
150
|
def format_result(result)
|
|
151
151
|
if result[:error]
|
|
152
|
-
"
|
|
152
|
+
"[Error] #{result[:error]}"
|
|
153
153
|
else
|
|
154
154
|
title = result[:title] || 'Untitled'
|
|
155
155
|
display_title = title.length > 40 ? "#{title[0..37]}..." : title
|
|
156
|
-
"
|
|
156
|
+
"[OK] Fetched: #{display_title}"
|
|
157
157
|
end
|
|
158
158
|
end
|
|
159
159
|
end
|
|
@@ -139,10 +139,10 @@ module Clacky
|
|
|
139
139
|
|
|
140
140
|
def format_result(result)
|
|
141
141
|
if result[:error]
|
|
142
|
-
"
|
|
142
|
+
"[Error] #{result[:error]}"
|
|
143
143
|
else
|
|
144
144
|
count = result[:count] || 0
|
|
145
|
-
"
|
|
145
|
+
"[OK] Found #{count} results"
|
|
146
146
|
end
|
|
147
147
|
end
|
|
148
148
|
end
|
|
@@ -15,7 +15,7 @@ module Clacky
|
|
|
15
15
|
"#{symbol} #{text}"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
# Render progress indicator
|
|
18
|
+
# Render progress indicator (stopped state, gray)
|
|
19
19
|
# @param message [String] Progress message
|
|
20
20
|
# @return [String] Progress indicator
|
|
21
21
|
def render_progress(message)
|
|
@@ -24,6 +24,15 @@ module Clacky
|
|
|
24
24
|
"#{symbol} #{text}"
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Render working indicator (active state, yellow)
|
|
28
|
+
# @param message [String] Progress message
|
|
29
|
+
# @return [String] Working indicator
|
|
30
|
+
def render_working(message)
|
|
31
|
+
symbol = format_symbol(:working)
|
|
32
|
+
text = format_text(message, :working)
|
|
33
|
+
"#{symbol} #{text}"
|
|
34
|
+
end
|
|
35
|
+
|
|
27
36
|
# Render success message
|
|
28
37
|
# @param message [String] Success message
|
|
29
38
|
# @return [String] Success message
|
|
@@ -65,22 +74,22 @@ module Clacky
|
|
|
65
74
|
lines << @pastel.dim("─" * 60)
|
|
66
75
|
lines << render_success("Task Complete")
|
|
67
76
|
lines << ""
|
|
68
|
-
|
|
77
|
+
|
|
69
78
|
# Display each stat on a separate line
|
|
70
79
|
lines << " Iterations: #{iterations}"
|
|
71
80
|
lines << " Cost: $#{cost.round(4)}"
|
|
72
81
|
lines << " Duration: #{duration.round(1)}s" if duration
|
|
73
|
-
|
|
82
|
+
|
|
74
83
|
# Display cache information if available
|
|
75
84
|
if cache_tokens && cache_tokens > 0
|
|
76
85
|
lines << " Cache Tokens: #{cache_tokens} tokens"
|
|
77
86
|
end
|
|
78
|
-
|
|
87
|
+
|
|
79
88
|
if cache_requests && cache_requests > 0
|
|
80
89
|
hit_rate = cache_hits > 0 ? ((cache_hits.to_f / cache_requests) * 100).round(1) : 0
|
|
81
90
|
lines << " Cache Requests: #{cache_requests} (#{cache_hits} hits, #{hit_rate}% hit rate)"
|
|
82
91
|
end
|
|
83
|
-
|
|
92
|
+
|
|
84
93
|
lines.join("\n")
|
|
85
94
|
end
|
|
86
95
|
end
|
|
@@ -13,6 +13,16 @@ module Clacky
|
|
|
13
13
|
class InputArea
|
|
14
14
|
include LineEditor
|
|
15
15
|
|
|
16
|
+
# User tips pool - can be extended with more tips over time
|
|
17
|
+
USER_TIPS = [
|
|
18
|
+
"Shift+Tab to toggle permission mode (confirm_safes ⇄ auto_approve)",
|
|
19
|
+
"Ctrl+C to interrupt AI execution or clear input",
|
|
20
|
+
"Shift+Enter to create multi-line input",
|
|
21
|
+
"Ctrl+V to paste images (supports up to 3 images)",
|
|
22
|
+
"Ctrl+D to delete pasted images",
|
|
23
|
+
"Use /clear to restart session, /help for commands"
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
16
26
|
attr_accessor :row
|
|
17
27
|
attr_reader :cursor_position, :line_index, :images, :tips_message, :tips_type
|
|
18
28
|
|
|
@@ -36,6 +46,11 @@ module Clacky
|
|
|
36
46
|
@tips_timer = nil
|
|
37
47
|
@last_render_row = nil
|
|
38
48
|
|
|
49
|
+
# User tip (usage suggestion) - separate from system tips
|
|
50
|
+
@user_tip = nil
|
|
51
|
+
@user_tip_timer = nil
|
|
52
|
+
@user_tip_count = 0
|
|
53
|
+
|
|
39
54
|
# Paused state - when InlineInput is active
|
|
40
55
|
@paused = false
|
|
41
56
|
|
|
@@ -88,6 +103,7 @@ module Clacky
|
|
|
88
103
|
|
|
89
104
|
height += 1 # Bottom separator
|
|
90
105
|
height += 1 if @tips_message
|
|
106
|
+
height += 1 if @user_tip
|
|
91
107
|
height
|
|
92
108
|
end
|
|
93
109
|
|
|
@@ -242,6 +258,14 @@ module Clacky
|
|
|
242
258
|
current_row += 1
|
|
243
259
|
end
|
|
244
260
|
|
|
261
|
+
# User tip (if any)
|
|
262
|
+
if @user_tip
|
|
263
|
+
move_cursor(current_row, 0)
|
|
264
|
+
content = format_user_tip(@user_tip)
|
|
265
|
+
print_with_padding(content)
|
|
266
|
+
current_row += 1
|
|
267
|
+
end
|
|
268
|
+
|
|
245
269
|
# Position cursor at current edit position
|
|
246
270
|
position_cursor(start_row)
|
|
247
271
|
flush
|
|
@@ -334,6 +358,58 @@ module Clacky
|
|
|
334
358
|
@tips_message = nil
|
|
335
359
|
end
|
|
336
360
|
|
|
361
|
+
# Show a random user tip with probability and auto-rotation (max 3 tips)
|
|
362
|
+
# @param probability [Float] Probability of showing tip (0.0 to 1.0, default: 0.4)
|
|
363
|
+
# @param rotation_interval [Integer] Seconds between tip rotation (default: 12)
|
|
364
|
+
# @param max_tips [Integer] Maximum number of tips to show before stopping (default: 3)
|
|
365
|
+
def show_user_tip(probability: 0.4, rotation_interval: 12, max_tips: 3)
|
|
366
|
+
# Random chance to show tip
|
|
367
|
+
return unless rand < probability
|
|
368
|
+
|
|
369
|
+
# Stop existing timer if any
|
|
370
|
+
stop_user_tip_timer
|
|
371
|
+
|
|
372
|
+
# Reset counter and pick first random tip
|
|
373
|
+
@user_tip_count = 1
|
|
374
|
+
@user_tip = USER_TIPS.sample
|
|
375
|
+
|
|
376
|
+
# Start rotation timer (will show max_tips total)
|
|
377
|
+
@user_tip_timer = Thread.new do
|
|
378
|
+
while @user_tip_count < max_tips
|
|
379
|
+
sleep rotation_interval
|
|
380
|
+
@user_tip_count += 1
|
|
381
|
+
|
|
382
|
+
# Pick a different tip
|
|
383
|
+
old_tip = @user_tip
|
|
384
|
+
loop do
|
|
385
|
+
@user_tip = USER_TIPS.sample
|
|
386
|
+
break if @user_tip != old_tip || USER_TIPS.size == 1
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# After showing max_tips, wait then clear
|
|
391
|
+
sleep rotation_interval
|
|
392
|
+
@user_tip = nil
|
|
393
|
+
@user_tip_count = 0
|
|
394
|
+
rescue => e
|
|
395
|
+
# Silently handle thread errors
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Clear user tip and stop rotation
|
|
400
|
+
def clear_user_tip
|
|
401
|
+
stop_user_tip_timer
|
|
402
|
+
@user_tip = nil
|
|
403
|
+
@user_tip_count = 0
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
private def stop_user_tip_timer
|
|
407
|
+
if @user_tip_timer&.alive?
|
|
408
|
+
@user_tip_timer.kill
|
|
409
|
+
@user_tip_timer = nil
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
337
413
|
# Pause input area (when InlineInput is active)
|
|
338
414
|
def pause
|
|
339
415
|
@paused = true
|
|
@@ -583,18 +659,23 @@ module Clacky
|
|
|
583
659
|
|
|
584
660
|
# Handle commands (with or without slash)
|
|
585
661
|
if text.start_with?('/')
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
662
|
+
# Check if it's a command (single slash followed by English letters only)
|
|
663
|
+
# Paths like /xxx/xxxx should not be treated as commands
|
|
664
|
+
if text =~ /^\/([a-zA-Z]+)$/
|
|
665
|
+
case text
|
|
666
|
+
when '/clear'
|
|
667
|
+
clear
|
|
668
|
+
return { action: :clear_output }
|
|
669
|
+
when '/help'
|
|
670
|
+
return { action: :help }
|
|
671
|
+
when '/exit', '/quit'
|
|
672
|
+
return { action: :exit }
|
|
673
|
+
else
|
|
674
|
+
set_tips("Unknown command: #{text} (Available: /clear, /help, /exit)", type: :warning)
|
|
675
|
+
return { action: nil }
|
|
676
|
+
end
|
|
597
677
|
end
|
|
678
|
+
# If it's not a command pattern (e.g., /xxx/xxxx), treat as normal input
|
|
598
679
|
elsif text == '?'
|
|
599
680
|
return { action: :help }
|
|
600
681
|
elsif text == 'exit' || text == 'quit'
|
|
@@ -888,15 +969,15 @@ module Clacky
|
|
|
888
969
|
|
|
889
970
|
# Workspace status with animation
|
|
890
971
|
if @sessionbar_info[:status]
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
parts << "#{status_indicator} #{
|
|
972
|
+
status_color = status_color_for(@sessionbar_info[:status])
|
|
973
|
+
status_indicator = get_status_indicator(@sessionbar_info[:status], status_color)
|
|
974
|
+
parts << "#{status_indicator} #{@pastel.public_send(status_color, @sessionbar_info[:status])}"
|
|
894
975
|
end
|
|
895
976
|
|
|
896
977
|
# Working directory (shortened if too long)
|
|
897
978
|
if @sessionbar_info[:working_dir]
|
|
898
979
|
dir_display = shorten_path(@sessionbar_info[:working_dir])
|
|
899
|
-
parts << @pastel.
|
|
980
|
+
parts << @pastel.dim(@pastel.cyan(dir_display))
|
|
900
981
|
end
|
|
901
982
|
|
|
902
983
|
# Permission mode
|
|
@@ -907,15 +988,15 @@ module Clacky
|
|
|
907
988
|
|
|
908
989
|
# Model
|
|
909
990
|
if @sessionbar_info[:model]
|
|
910
|
-
parts << @pastel.
|
|
991
|
+
parts << @pastel.dim(@pastel.white(@sessionbar_info[:model]))
|
|
911
992
|
end
|
|
912
993
|
|
|
913
994
|
# Tasks count
|
|
914
|
-
parts << @pastel.
|
|
995
|
+
parts << @pastel.dim(@pastel.white("#{@sessionbar_info[:tasks]} tasks"))
|
|
915
996
|
|
|
916
997
|
# Cost
|
|
917
998
|
cost_display = format("$%.1f", @sessionbar_info[:cost])
|
|
918
|
-
parts << @pastel.
|
|
999
|
+
parts << @pastel.dim(@pastel.white(cost_display))
|
|
919
1000
|
|
|
920
1001
|
session_line = " " + parts.join(separator)
|
|
921
1002
|
print_with_padding(session_line)
|
|
@@ -946,30 +1027,30 @@ module Clacky
|
|
|
946
1027
|
def mode_color_for(mode)
|
|
947
1028
|
case mode.to_s
|
|
948
1029
|
when /auto_approve/
|
|
949
|
-
:
|
|
1030
|
+
:magenta
|
|
950
1031
|
when /confirm_safes/
|
|
951
|
-
:
|
|
1032
|
+
:cyan
|
|
952
1033
|
when /confirm_edits/
|
|
953
|
-
:
|
|
1034
|
+
:green
|
|
954
1035
|
when /plan_only/
|
|
955
|
-
:
|
|
1036
|
+
:blue
|
|
956
1037
|
else
|
|
957
1038
|
:white
|
|
958
1039
|
end
|
|
959
1040
|
end
|
|
960
1041
|
|
|
961
|
-
def
|
|
1042
|
+
def status_color_for(status)
|
|
962
1043
|
case status.to_s.downcase
|
|
963
1044
|
when 'idle'
|
|
964
|
-
:
|
|
1045
|
+
:cyan # Use darker cyan for idle state
|
|
965
1046
|
when 'working'
|
|
966
|
-
:
|
|
1047
|
+
:yellow # Use yellow to highlight working state
|
|
967
1048
|
else
|
|
968
|
-
:
|
|
1049
|
+
:cyan
|
|
969
1050
|
end
|
|
970
1051
|
end
|
|
971
1052
|
|
|
972
|
-
def get_status_indicator(status)
|
|
1053
|
+
def get_status_indicator(status, color)
|
|
973
1054
|
case status.to_s.downcase
|
|
974
1055
|
when 'working'
|
|
975
1056
|
# Update animation frame if enough time has passed
|
|
@@ -978,9 +1059,9 @@ module Clacky
|
|
|
978
1059
|
@animation_frame = (@animation_frame + 1) % @working_frames.length
|
|
979
1060
|
@last_animation_update = now
|
|
980
1061
|
end
|
|
981
|
-
@working_frames[@animation_frame]
|
|
1062
|
+
@pastel.public_send(color, @working_frames[@animation_frame])
|
|
982
1063
|
else
|
|
983
|
-
"●" # Idle indicator
|
|
1064
|
+
@pastel.public_send(color, "●") # Idle indicator with same color as text
|
|
984
1065
|
end
|
|
985
1066
|
end
|
|
986
1067
|
|
|
@@ -1012,6 +1093,20 @@ module Clacky
|
|
|
1012
1093
|
end
|
|
1013
1094
|
end
|
|
1014
1095
|
|
|
1096
|
+
# Format user tip (usage suggestion) with lightbulb icon
|
|
1097
|
+
# @param tip [String] Tip message
|
|
1098
|
+
# @return [String] Formatted tip with styling
|
|
1099
|
+
def format_user_tip(tip)
|
|
1100
|
+
# Limit message length to prevent line wrapping
|
|
1101
|
+
max_length = @width - 5 # Reserve space for icon and margins
|
|
1102
|
+
if tip.length > max_length
|
|
1103
|
+
tip = tip[0...(max_length - 3)] + "..."
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
# Use lightbulb icon and dim cyan color for subtle appearance
|
|
1107
|
+
@pastel.dim(@pastel.cyan("💡 #{tip}"))
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1015
1110
|
def move_cursor(row, col)
|
|
1016
1111
|
print "\e[#{row + 1};#{col + 1}H"
|
|
1017
1112
|
end
|
|
@@ -13,12 +13,14 @@ module Clacky
|
|
|
13
13
|
# - :content [String] Message content
|
|
14
14
|
# - :timestamp [Time, nil] Optional timestamp
|
|
15
15
|
# - :images [Array<String>] Optional image paths (for user messages)
|
|
16
|
+
# - :prefix_newline [Boolean] Whether to add newline before message (for system messages)
|
|
16
17
|
# @return [String] Rendered message
|
|
17
18
|
def render(data)
|
|
18
19
|
role = data[:role]
|
|
19
20
|
content = data[:content]
|
|
20
21
|
timestamp = data[:timestamp]
|
|
21
22
|
images = data[:images] || []
|
|
23
|
+
prefix_newline = data.fetch(:prefix_newline, true)
|
|
22
24
|
|
|
23
25
|
case role
|
|
24
26
|
when "user"
|
|
@@ -26,7 +28,7 @@ module Clacky
|
|
|
26
28
|
when "assistant"
|
|
27
29
|
render_assistant_message(content, timestamp)
|
|
28
30
|
else
|
|
29
|
-
render_system_message(content, timestamp)
|
|
31
|
+
render_system_message(content, timestamp, prefix_newline)
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
|
|
@@ -62,13 +64,15 @@ module Clacky
|
|
|
62
64
|
# Render system message
|
|
63
65
|
# @param content [String] Message content
|
|
64
66
|
# @param timestamp [Time, nil] Optional timestamp
|
|
67
|
+
# @param prefix_newline [Boolean] Whether to add newline before message
|
|
65
68
|
# @return [String] Rendered message
|
|
66
|
-
def render_system_message(content, timestamp = nil)
|
|
69
|
+
private def render_system_message(content, timestamp = nil, prefix_newline = true)
|
|
67
70
|
symbol = format_symbol(:info)
|
|
68
71
|
text = format_text(content, :info)
|
|
69
72
|
time_str = timestamp ? @pastel.dim("[#{format_timestamp(timestamp)}]") : ""
|
|
70
73
|
|
|
71
|
-
"\n
|
|
74
|
+
prefix = prefix_newline ? "\n" : ""
|
|
75
|
+
"#{prefix}#{symbol} #{text} #{time_str}".rstrip
|
|
72
76
|
end
|
|
73
77
|
end
|
|
74
78
|
end
|
|
@@ -21,7 +21,7 @@ module Clacky
|
|
|
21
21
|
# Multi-line handling is done by LayoutManager
|
|
22
22
|
# @param content [String] Single line content to append
|
|
23
23
|
def append(content)
|
|
24
|
-
return if content.nil?
|
|
24
|
+
return if content.nil?
|
|
25
25
|
|
|
26
26
|
update_width
|
|
27
27
|
print wrap_line(content)
|
|
@@ -10,7 +10,7 @@ module Clacky
|
|
|
10
10
|
attr_accessor :height
|
|
11
11
|
attr_reader :todos
|
|
12
12
|
|
|
13
|
-
MAX_DISPLAY_TASKS =
|
|
13
|
+
MAX_DISPLAY_TASKS = 3 # Show current + next 2 tasks
|
|
14
14
|
|
|
15
15
|
def initialize
|
|
16
16
|
@todos = []
|
|
@@ -27,12 +27,11 @@ module Clacky
|
|
|
27
27
|
@completed_count = @todos.count { |t| t[:status] == "completed" }
|
|
28
28
|
@total_count = @todos.size
|
|
29
29
|
|
|
30
|
-
#
|
|
31
|
-
if @pending_todos.empty?
|
|
30
|
+
# Calculate height: 0 if no pending, otherwise 1 line per task (up to MAX_DISPLAY_TASKS)
|
|
31
|
+
if @pending_todos.empty?
|
|
32
32
|
@height = 0
|
|
33
33
|
else
|
|
34
|
-
|
|
35
|
-
@height = 1 + display_count
|
|
34
|
+
@height = [@pending_todos.size, MAX_DISPLAY_TASKS].min
|
|
36
35
|
end
|
|
37
36
|
end
|
|
38
37
|
|
|
@@ -48,21 +47,37 @@ module Clacky
|
|
|
48
47
|
|
|
49
48
|
update_width
|
|
50
49
|
|
|
51
|
-
# Render
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
50
|
+
# Render each task on separate line
|
|
51
|
+
tasks_to_show = @pending_todos.take(MAX_DISPLAY_TASKS)
|
|
52
|
+
|
|
53
|
+
tasks_to_show.each_with_index do |task, index|
|
|
54
|
+
move_cursor(start_row + index, 0)
|
|
55
|
+
|
|
56
|
+
# Build the line content
|
|
57
|
+
line_content = if index == 0
|
|
58
|
+
# First line: Task [2/4]: #3 - Current task description
|
|
59
|
+
progress = "#{@completed_count}/#{@total_count}"
|
|
60
|
+
prefix = "Task [#{progress}]: "
|
|
61
|
+
task_text = "##{task[:id]} - #{task[:task]}"
|
|
62
|
+
available_width = @width - prefix.length - 2
|
|
63
|
+
truncated_task = truncate_text(task_text, available_width)
|
|
64
|
+
|
|
65
|
+
"#{@pastel.cyan(prefix)}#{truncated_task}"
|
|
66
|
+
else
|
|
67
|
+
# Subsequent lines: -> Next: #4 - Next task description
|
|
68
|
+
label = index == 1 ? "Next" : "After"
|
|
69
|
+
prefix = "-> #{label}: "
|
|
70
|
+
task_text = "##{task[:id]} - #{task[:task]}"
|
|
71
|
+
available_width = @width - prefix.length - 2
|
|
72
|
+
truncated_task = truncate_text(task_text, available_width)
|
|
73
|
+
|
|
74
|
+
"#{@pastel.dim(prefix)}#{@pastel.dim(truncated_task)}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Use carriage return and print content directly (overwrite existing content)
|
|
78
|
+
print "\r#{line_content}"
|
|
79
|
+
# Clear any remaining characters from previous render if line is shorter
|
|
80
|
+
clear_to_end_of_line
|
|
66
81
|
end
|
|
67
82
|
|
|
68
83
|
flush
|
|
@@ -79,28 +94,6 @@ module Clacky
|
|
|
79
94
|
|
|
80
95
|
private
|
|
81
96
|
|
|
82
|
-
# Render header line with progress bar
|
|
83
|
-
def render_header
|
|
84
|
-
progress = "#{@completed_count}/#{@total_count}"
|
|
85
|
-
progress_bar = render_progress_bar(@completed_count, @total_count)
|
|
86
|
-
|
|
87
|
-
"#{@pastel.cyan("[##]")} Tasks [#{progress}]: #{progress_bar}"
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Render a simple progress bar
|
|
91
|
-
def render_progress_bar(completed, total)
|
|
92
|
-
return "" if total == 0
|
|
93
|
-
|
|
94
|
-
bar_width = 10
|
|
95
|
-
filled = total > 0 ? (completed.to_f / total * bar_width).round : 0
|
|
96
|
-
empty = bar_width - filled
|
|
97
|
-
|
|
98
|
-
filled_bar = @pastel.green("█" * filled)
|
|
99
|
-
empty_bar = @pastel.dim("░" * empty)
|
|
100
|
-
|
|
101
|
-
"#{filled_bar}#{empty_bar}"
|
|
102
|
-
end
|
|
103
|
-
|
|
104
97
|
# Truncate text to fit width
|
|
105
98
|
def truncate_text(text, max_width)
|
|
106
99
|
return "" if text.nil?
|
|
@@ -122,9 +115,9 @@ module Clacky
|
|
|
122
115
|
print "\e[#{row + 1};#{col + 1}H"
|
|
123
116
|
end
|
|
124
117
|
|
|
125
|
-
# Clear
|
|
126
|
-
def
|
|
127
|
-
print "\e[
|
|
118
|
+
# Clear from cursor to end of line
|
|
119
|
+
def clear_to_end_of_line
|
|
120
|
+
print "\e[0K"
|
|
128
121
|
end
|
|
129
122
|
|
|
130
123
|
# Flush output
|
|
@@ -29,6 +29,16 @@ module Clacky
|
|
|
29
29
|
@pastel = Pastel.new
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# Render only the logo (ASCII art)
|
|
33
|
+
# @return [String] Formatted logo only
|
|
34
|
+
def render_logo
|
|
35
|
+
lines = []
|
|
36
|
+
lines << ""
|
|
37
|
+
lines << @pastel.bright_green(LOGO)
|
|
38
|
+
lines << ""
|
|
39
|
+
lines.join("\n")
|
|
40
|
+
end
|
|
41
|
+
|
|
32
42
|
# Render startup banner
|
|
33
43
|
# @return [String] Formatted startup banner
|
|
34
44
|
def render_startup
|