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.
@@ -177,26 +177,40 @@ module Clacky
177
177
  # Track current row, scroll when reaching fixed area
178
178
  # @param content [String] Content to append
179
179
  def append_output(content)
180
- return if content.nil? || content.empty?
180
+ return if content.nil?
181
181
 
182
182
  @render_mutex.synchronize do
183
183
  max_output_row = fixed_area_start_row - 1
184
184
 
185
+ # Special handling for empty string - just add a blank line
186
+ if content.empty?
187
+ print "\n"
188
+ @output_row += 1
189
+ render_fixed_areas
190
+ screen.flush
191
+ return
192
+ end
193
+
185
194
  content.split("\n").each do |line|
186
- # If at max row, need to scroll before outputting
187
- if @output_row > max_output_row
188
- # Move to bottom of screen and print newline to trigger scroll
189
- screen.move_cursor(screen.height - 1, 0)
190
- print "\n"
191
- # Stay at max_output_row for next output
192
- @output_row = max_output_row
195
+ # Wrap long lines to prevent display issues
196
+ wrapped_lines = wrap_long_line(line)
197
+
198
+ wrapped_lines.each do |wrapped_line|
199
+ # If at max row, need to scroll before outputting
200
+ if @output_row > max_output_row
201
+ # Move to bottom of screen and print newline to trigger scroll
202
+ screen.move_cursor(screen.height - 1, 0)
203
+ print "\n"
204
+ # Stay at max_output_row for next output
205
+ @output_row = max_output_row
206
+ end
207
+
208
+ # Output line at current position
209
+ screen.move_cursor(@output_row, 0)
210
+ screen.clear_line
211
+ output_area.append(wrapped_line)
212
+ @output_row += 1
193
213
  end
194
-
195
- # Output line at current position
196
- screen.move_cursor(@output_row, 0)
197
- screen.clear_line
198
- output_area.append(line)
199
- @output_row += 1
200
214
  end
201
215
 
202
216
  # Re-render fixed areas at screen bottom
@@ -268,6 +282,98 @@ module Clacky
268
282
 
269
283
  private
270
284
 
285
+ # Wrap a long line into multiple lines based on terminal width
286
+ # Considers display width of multi-byte characters (e.g., Chinese characters)
287
+ # @param line [String] Line to wrap
288
+ # @return [Array<String>] Array of wrapped lines
289
+ def wrap_long_line(line)
290
+ return [""] if line.nil? || line.empty?
291
+
292
+ max_width = screen.width
293
+ return [line] if max_width <= 0
294
+
295
+ # Strip ANSI codes for width calculation
296
+ visible_line = line.gsub(/\e\[[0-9;]*m/, '')
297
+
298
+ # Check if line needs wrapping
299
+ display_width = calculate_display_width(visible_line)
300
+ return [line] if display_width <= max_width
301
+
302
+ # Line needs wrapping - split by considering display width
303
+ wrapped = []
304
+ current_line = ""
305
+ current_width = 0
306
+ ansi_codes = [] # Track ANSI codes to carry over
307
+
308
+ # Extract ANSI codes and text segments
309
+ segments = line.split(/(\e\[[0-9;]*m)/)
310
+
311
+ segments.each do |segment|
312
+ if segment =~ /^\e\[[0-9;]*m$/
313
+ # ANSI code - add to current codes
314
+ ansi_codes << segment
315
+ current_line += segment
316
+ else
317
+ # Text segment - process character by character
318
+ segment.each_char do |char|
319
+ char_width = char_display_width(char)
320
+
321
+ if current_width + char_width > max_width && !current_line.empty?
322
+ # Complete current line
323
+ wrapped << current_line
324
+ # Start new line with carried-over ANSI codes
325
+ current_line = ansi_codes.join
326
+ current_width = 0
327
+ end
328
+
329
+ current_line += char
330
+ current_width += char_width
331
+ end
332
+ end
333
+ end
334
+
335
+ # Add remaining content
336
+ wrapped << current_line unless current_line.empty? || current_line == ansi_codes.join
337
+
338
+ wrapped.empty? ? [""] : wrapped
339
+ end
340
+
341
+ # Calculate display width of a single character
342
+ # @param char [String] Single character
343
+ # @return [Integer] Display width (1 or 2)
344
+ def char_display_width(char)
345
+ code = char.ord
346
+ # East Asian Wide and Fullwidth characters take 2 columns
347
+ if (code >= 0x1100 && code <= 0x115F) ||
348
+ (code >= 0x2329 && code <= 0x232A) ||
349
+ (code >= 0x2E80 && code <= 0x303E) ||
350
+ (code >= 0x3040 && code <= 0xA4CF) ||
351
+ (code >= 0xAC00 && code <= 0xD7A3) ||
352
+ (code >= 0xF900 && code <= 0xFAFF) ||
353
+ (code >= 0xFE10 && code <= 0xFE19) ||
354
+ (code >= 0xFE30 && code <= 0xFE6F) ||
355
+ (code >= 0xFF00 && code <= 0xFF60) ||
356
+ (code >= 0xFFE0 && code <= 0xFFE6) ||
357
+ (code >= 0x1F300 && code <= 0x1F9FF) ||
358
+ (code >= 0x20000 && code <= 0x2FFFD) ||
359
+ (code >= 0x30000 && code <= 0x3FFFD)
360
+ 2
361
+ else
362
+ 1
363
+ end
364
+ end
365
+
366
+ # Calculate display width of a string (considering multi-byte characters)
367
+ # @param text [String] Text to calculate
368
+ # @return [Integer] Display width
369
+ def calculate_display_width(text)
370
+ width = 0
371
+ text.each_char do |char|
372
+ width += char_display_width(char)
373
+ end
374
+ width
375
+ end
376
+
271
377
  # Calculate fixed area height (gap + todo + input)
272
378
  def fixed_area_height
273
379
  todo_height = @todo_area&.height || 0
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-markdown"
4
+ require_relative "theme_manager"
5
+
6
+ module Clacky
7
+ module UI2
8
+ # MarkdownRenderer handles rendering Markdown content with syntax highlighting
9
+ module MarkdownRenderer
10
+ class << self
11
+ # Render markdown content with theme-aware colors
12
+ # @param content [String] Markdown content to render
13
+ # @return [String] Rendered content with ANSI colors
14
+ def render(content)
15
+ return content if content.nil? || content.empty?
16
+
17
+ # Get current theme colors
18
+ theme = ThemeManager.current_theme
19
+
20
+ # Configure tty-markdown colors based on current theme
21
+ # tty-markdown uses Pastel internally, we can configure symbols
22
+ parsed = TTY::Markdown.parse(content,
23
+ colors: theme_colors,
24
+ width: TTY::Screen.width - 4 # Leave some margin
25
+ )
26
+
27
+ parsed
28
+ rescue StandardError => e
29
+ # Fallback to plain content if rendering fails
30
+ content
31
+ end
32
+
33
+ # Check if content looks like markdown
34
+ # @param content [String] Content to check
35
+ # @return [Boolean] true if content appears to be markdown
36
+ def markdown?(content)
37
+ return false if content.nil? || content.empty?
38
+
39
+ # Check for common markdown patterns
40
+ content.match?(/^#+ /) || # Headers
41
+ content.match?(/```/) || # Code blocks
42
+ content.match?(/^\s*[-*+] /) || # Unordered lists
43
+ content.match?(/^\s*\d+\. /) || # Ordered lists
44
+ content.match?(/\[.+\]\(.+\)/) || # Links
45
+ content.match?(/^\s*> /) || # Blockquotes
46
+ content.match?(/\*\*.+\*\*/) || # Bold
47
+ content.match?(/`.+`/) || # Inline code
48
+ content.match?(/^\s*\|.+\|/) || # Tables
49
+ content.match?(/^---+$/) # Horizontal rules
50
+ end
51
+
52
+ private
53
+
54
+ # Get theme-aware colors for markdown rendering
55
+ # @return [Hash] Color configuration for tty-markdown
56
+ def theme_colors
57
+ theme = ThemeManager.current_theme
58
+
59
+ # Map our theme colors to tty-markdown's expected format
60
+ {
61
+ # Headers use info color (cyan/blue)
62
+ header: theme.colors[:info],
63
+ # Code blocks use dim color
64
+ code: theme.colors[:thinking],
65
+ # Links use success color (green)
66
+ link: theme.colors[:success],
67
+ # Lists use default text color
68
+ list: :bright_white,
69
+ # Strong/bold use bright white
70
+ strong: :bright_white,
71
+ # Emphasis/italic use white
72
+ em: :white,
73
+ # Note/blockquote use dim color
74
+ note: theme.colors[:thinking],
75
+ }
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -171,14 +171,33 @@ module Clacky
171
171
  buffer.force_encoding('UTF-8')
172
172
 
173
173
  # Keep reading available characters
174
+ loop_count = 0
175
+ empty_checks = 0
176
+
174
177
  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
178
+ # Check if there's data available immediately
179
+ has_data = IO.select([$stdin], nil, nil, 0)
180
+
181
+ if has_data
182
+ next_char = $stdin.getc
183
+ break unless next_char
184
+
185
+ next_char = next_char.force_encoding('UTF-8') if next_char.encoding != Encoding::UTF_8
186
+ buffer << next_char
187
+ loop_count += 1
188
+ empty_checks = 0 # Reset empty check counter
189
+ else
190
+ # No immediate data, but wait a bit to see if more is coming
191
+ # This handles the case where paste data arrives in chunks
192
+ empty_checks += 1
193
+ if empty_checks == 1
194
+ # First empty check - wait 10ms for more data
195
+ sleep 0.01
196
+ else
197
+ # Second empty check - really no more data
198
+ break
199
+ end
200
+ end
182
201
  end
183
202
 
184
203
  # If we buffered multiple characters or newlines, treat as rapid input (paste)
@@ -5,71 +5,43 @@ require "pastel"
5
5
  module Clacky
6
6
  module UI2
7
7
  module Themes
8
- # BaseTheme defines the interface for all themes
9
- # Subclasses should override SYMBOLS and color methods
8
+ # BaseTheme defines the abstract interface for all themes
9
+ # Subclasses MUST define SYMBOLS and COLORS constants
10
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
11
  def initialize
54
12
  @pastel = Pastel.new
13
+ validate_theme_definition!
55
14
  end
56
15
 
16
+ # Get all symbols defined by this theme
17
+ # @return [Hash] Symbol definitions
57
18
  def symbols
58
19
  self.class::SYMBOLS
59
20
  end
60
21
 
22
+ # Get all colors defined by this theme
23
+ # @return [Hash] Color definitions
61
24
  def colors
62
25
  self.class::COLORS
63
26
  end
64
27
 
28
+ # Get symbol for a specific key
29
+ # @param key [Symbol] Symbol key
30
+ # @return [String] Symbol string
65
31
  def symbol(key)
66
32
  symbols[key] || "[??]"
67
33
  end
68
34
 
35
+ # Get symbol color for a specific key
36
+ # @param key [Symbol] Color key
37
+ # @return [Symbol] Pastel color method name
69
38
  def symbol_color(key)
70
39
  colors.dig(key, 0) || :white
71
40
  end
72
41
 
42
+ # Get text color for a specific key
43
+ # @param key [Symbol] Color key
44
+ # @return [Symbol] Pastel color method name
73
45
  def text_color(key)
74
46
  colors.dig(key, 1) || :white
75
47
  end
@@ -89,9 +61,23 @@ module Clacky
89
61
  @pastel.public_send(text_color(key), text)
90
62
  end
91
63
 
92
- # Theme name for display
64
+ # Theme name for display (subclasses should override)
65
+ # @return [String] Theme name
93
66
  def name
94
- "base"
67
+ raise NotImplementedError, "Subclass must implement #name method"
68
+ end
69
+
70
+ private
71
+
72
+ # Validate that subclass has defined required constants
73
+ def validate_theme_definition!
74
+ unless self.class.const_defined?(:SYMBOLS)
75
+ raise NotImplementedError, "Theme #{self.class.name} must define SYMBOLS constant"
76
+ end
77
+
78
+ unless self.class.const_defined?(:COLORS)
79
+ raise NotImplementedError, "Theme #{self.class.name} must define COLORS constant"
80
+ end
95
81
  end
96
82
  end
97
83
  end
@@ -16,6 +16,7 @@ module Clacky
16
16
  tool_planned: "[??]",
17
17
  tool_error: "[XX]",
18
18
  thinking: "[..]",
19
+ working: "[..]",
19
20
  success: "[OK]",
20
21
  error: "[ER]",
21
22
  warning: "[!!]",
@@ -36,6 +37,7 @@ module Clacky
36
37
  tool_planned: [:bright_blue, :blue],
37
38
  tool_error: [:bright_red, :red],
38
39
  thinking: [:dim, :dim],
40
+ working: [:bright_yellow, :yellow],
39
41
  success: [:bright_green, :green],
40
42
  error: [:bright_red, :red],
41
43
  warning: [:bright_yellow, :yellow],
@@ -16,6 +16,7 @@ module Clacky
16
16
  tool_planned: "?",
17
17
  tool_error: "x",
18
18
  thinking: ".",
19
+ working: ".",
19
20
  success: "+",
20
21
  error: "x",
21
22
  warning: "!",
@@ -33,6 +34,7 @@ module Clacky
33
34
  tool_planned: [:blue, :blue],
34
35
  tool_error: [:red, :red],
35
36
  thinking: [:dim, :dim],
37
+ working: [:bright_yellow, :yellow],
36
38
  success: [:green, :green],
37
39
  error: [:red, :red],
38
40
  warning: [:yellow, :yellow],
@@ -59,7 +59,8 @@ module Clacky
59
59
  end
60
60
 
61
61
  # Initialize screen and show banner (separate from input loop)
62
- def initialize_and_show_banner
62
+ # @param recent_user_messages [Array<String>, nil] Recent user messages when loading session
63
+ def initialize_and_show_banner(recent_user_messages: nil)
63
64
  @running = true
64
65
 
65
66
  # Set session bar data before initializing screen
@@ -73,8 +74,12 @@ module Clacky
73
74
 
74
75
  @layout.initialize_screen
75
76
 
76
- # Display welcome banner
77
- display_welcome_banner
77
+ # Display welcome banner or session history
78
+ if recent_user_messages && !recent_user_messages.empty?
79
+ display_session_history(recent_user_messages)
80
+ else
81
+ display_welcome_banner
82
+ end
78
83
  end
79
84
 
80
85
  # Start input loop (separate from initialization)
@@ -344,6 +349,10 @@ module Clacky
344
349
  def show_complete(iterations:, cost:, duration: nil, cache_stats: nil)
345
350
  # Update status back to 'idle' when task is complete
346
351
  update_sessionbar(status: 'idle')
352
+
353
+ # Clear user tip when agent stops working
354
+ @input_area.clear_user_tip
355
+ @layout.render_input
347
356
 
348
357
  # Only show completion message for complex tasks (>5 iterations)
349
358
  return if iterations <= 5
@@ -351,7 +360,7 @@ module Clacky
351
360
  cache_tokens = cache_stats&.dig(:cache_read_input_tokens)
352
361
  cache_requests = cache_stats&.dig(:total_requests)
353
362
  cache_hits = cache_stats&.dig(:cache_hit_requests)
354
-
363
+
355
364
  output = @renderer.render_task_complete(
356
365
  iterations: iterations,
357
366
  cost: cost,
@@ -365,7 +374,8 @@ module Clacky
365
374
 
366
375
  # Show progress indicator with dynamic elapsed time
367
376
  # @param message [String] Progress message (optional, will use random thinking verb if nil)
368
- def show_progress(message = nil)
377
+ # @param prefix_newline [Boolean] Whether to add a blank line before progress (default: true)
378
+ def show_progress(message = nil, prefix_newline: true)
369
379
  # Stop any existing progress thread
370
380
  stop_progress_thread
371
381
 
@@ -375,8 +385,9 @@ module Clacky
375
385
  @progress_message = message || Clacky::THINKING_VERBS.sample
376
386
  @progress_start_time = Time.now
377
387
 
378
- # Show initial progress
379
- output = @renderer.render_progress("#{@progress_message}… (ctrl+c to interrupt)")
388
+ # Show initial progress (yellow, active)
389
+ append_output("") if prefix_newline
390
+ output = @renderer.render_working("#{@progress_message}… (ctrl+c to interrupt)")
380
391
  append_output(output)
381
392
 
382
393
  # Start background thread to update elapsed time
@@ -386,7 +397,7 @@ module Clacky
386
397
  next unless @progress_start_time
387
398
 
388
399
  elapsed = (Time.now - @progress_start_time).to_i
389
- update_progress_line(@renderer.render_progress("#{@progress_message}… (ctrl+c to interrupt · #{elapsed}s)"))
400
+ update_progress_line(@renderer.render_working("#{@progress_message}… (ctrl+c to interrupt · #{elapsed}s)"))
390
401
  end
391
402
  rescue => e
392
403
  # Silently handle thread errors
@@ -395,8 +406,19 @@ module Clacky
395
406
 
396
407
  # Clear progress indicator
397
408
  def clear_progress
409
+ # Calculate elapsed time before stopping
410
+ elapsed_time = @progress_start_time ? (Time.now - @progress_start_time).to_i : 0
411
+
412
+ # Stop the progress thread
398
413
  stop_progress_thread
399
- clear_progress_line
414
+
415
+ # Update the final progress line to gray (stopped state)
416
+ if @progress_message && elapsed_time > 0
417
+ final_output = @renderer.render_progress("#{@progress_message}… (#{elapsed_time}s)")
418
+ update_progress_line(final_output)
419
+ else
420
+ clear_progress_line
421
+ end
400
422
  end
401
423
 
402
424
  # Stop the progress update thread
@@ -410,8 +432,9 @@ module Clacky
410
432
 
411
433
  # Show info message
412
434
  # @param message [String] Info message
413
- def show_info(message)
414
- output = @renderer.render_system_message(message)
435
+ # @param prefix_newline [Boolean] Whether to add newline before message (default: true)
436
+ def show_info(message, prefix_newline: true)
437
+ output = @renderer.render_system_message(message, prefix_newline: prefix_newline)
415
438
  append_output(output)
416
439
  end
417
440
 
@@ -432,11 +455,17 @@ module Clacky
432
455
  # Set workspace status to idle (called when agent stops working)
433
456
  def set_idle_status
434
457
  update_sessionbar(status: 'idle')
458
+ # Clear user tip when agent stops working
459
+ @input_area.clear_user_tip
460
+ @layout.render_input
435
461
  end
436
462
 
437
463
  # Set workspace status to working (called when agent starts working)
438
464
  def set_working_status
439
465
  update_sessionbar(status: 'working')
466
+ # Show a random user tip with 40% probability when agent starts working
467
+ @input_area.show_user_tip(probability: 0.4)
468
+ @layout.render_input
440
469
  end
441
470
 
442
471
  # Show help text
@@ -484,7 +513,7 @@ module Clacky
484
513
 
485
514
  # Create InlineInput with styled prompt
486
515
  inline_input = Components::InlineInput.new(
487
- prompt: theme.format_text(" (y/n, or provide feedback): ", :thinking),
516
+ prompt: " Press Enter to approve, 'n' to reject, or provide feedback: ",
488
517
  default: nil
489
518
  )
490
519
  @inline_input = inline_input
@@ -599,6 +628,35 @@ module Clacky
599
628
  append_output(content)
600
629
  end
601
630
 
631
+ # Display recent user messages when loading session
632
+ # @param user_messages [Array<String>] Array of recent user message texts
633
+ def display_session_history(user_messages)
634
+ theme = ThemeManager.current_theme
635
+
636
+ # Show logo banner only
637
+ append_output(@welcome_banner.render_logo)
638
+
639
+ # Show simple header
640
+ append_output(theme.format_text("Recent conversation:", :info))
641
+
642
+ # Display each user message with numbering
643
+ user_messages.each_with_index do |msg, index|
644
+ # Truncate long messages
645
+ display_msg = if msg.length > 140
646
+ "#{msg[0..137]}..."
647
+ else
648
+ msg
649
+ end
650
+
651
+ # Show with number and indentation
652
+ append_output(" #{index + 1}. #{display_msg}")
653
+ end
654
+
655
+ # Bottom spacing and continuation prompt
656
+ append_output("")
657
+ append_output(theme.format_text("Session restored. Feel free to continue with your next task.", :success))
658
+ end
659
+
602
660
  # Main input loop
603
661
  def input_loop
604
662
  @layout.screen.enable_raw_mode
@@ -3,6 +3,7 @@
3
3
  require_relative "components/message_component"
4
4
  require_relative "components/tool_component"
5
5
  require_relative "components/common_component"
6
+ require_relative "markdown_renderer"
6
7
 
7
8
  module Clacky
8
9
  module UI2
@@ -31,9 +32,16 @@ module Clacky
31
32
  # @param timestamp [Time, nil] Optional timestamp
32
33
  # @return [String] Rendered message
33
34
  def render_assistant_message(content, timestamp: nil)
35
+ # Render markdown if content contains markdown syntax
36
+ rendered_content = if MarkdownRenderer.markdown?(content)
37
+ MarkdownRenderer.render(content)
38
+ else
39
+ content
40
+ end
41
+
34
42
  @message_component.render(
35
43
  role: "assistant",
36
- content: content,
44
+ content: rendered_content,
37
45
  timestamp: timestamp
38
46
  )
39
47
  end
@@ -41,12 +49,14 @@ module Clacky
41
49
  # Render a system message
42
50
  # @param content [String] Message content
43
51
  # @param timestamp [Time, nil] Optional timestamp
52
+ # @param prefix_newline [Boolean] Whether to add newline before message
44
53
  # @return [String] Rendered message
45
- def render_system_message(content, timestamp: nil)
54
+ def render_system_message(content, timestamp: nil, prefix_newline: true)
46
55
  @message_component.render(
47
56
  role: "system",
48
57
  content: content,
49
- timestamp: timestamp
58
+ timestamp: timestamp,
59
+ prefix_newline: prefix_newline
50
60
  )
51
61
  end
52
62
 
@@ -108,13 +118,20 @@ module Clacky
108
118
  @common_component.render_thinking
109
119
  end
110
120
 
111
- # Render progress message
121
+ # Render progress message (stopped state, gray)
112
122
  # @param message [String] Progress message
113
123
  # @return [String] Progress indicator
114
124
  def render_progress(message)
115
125
  @common_component.render_progress(message)
116
126
  end
117
127
 
128
+ # Render working message (active state, yellow)
129
+ # @param message [String] Progress message
130
+ # @return [String] Working indicator
131
+ def render_working(message)
132
+ @common_component.render_working(message)
133
+ end
134
+
118
135
  # Render success message
119
136
  # @param message [String] Success message
120
137
  # @return [String] Success message
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clacky
4
- VERSION = "0.6.0"
4
+ VERSION = "0.6.1"
5
5
  end