openclacky 0.5.3 → 0.5.5

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.
@@ -0,0 +1,540 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "io/console"
4
+ require "pastel"
5
+ require "tty-screen"
6
+ require "tempfile"
7
+ require "base64"
8
+
9
+ module Clacky
10
+ module UI
11
+ # Enhanced input prompt with multi-line support and image paste
12
+ #
13
+ # Features:
14
+ # - Shift+Enter: Add new line
15
+ # - Enter: Submit message
16
+ # - Ctrl+V: Paste text or images from clipboard
17
+ # - Image preview and management
18
+ class EnhancedPrompt
19
+ attr_reader :images
20
+
21
+ def initialize
22
+ @pastel = Pastel.new
23
+ @images = [] # Array of image file paths
24
+ @paste_counter = 0 # Counter for paste operations
25
+ @paste_placeholders = {} # Map of placeholder text to actual pasted content
26
+ @last_input_time = nil # Track last input time for rapid input detection
27
+ @rapid_input_threshold = 0.01 # 10ms threshold for detecting paste-like rapid input
28
+ end
29
+
30
+ # Read user input with enhanced features
31
+ # @param prefix [String] Prompt prefix (default: "❯")
32
+ # @return [Hash, nil] { text: String, images: Array } or nil on EOF
33
+ def read_input(prefix: "❯")
34
+ @images = []
35
+ lines = []
36
+ cursor_pos = 0
37
+ line_index = 0
38
+ @last_ctrl_c_time = nil # Track when Ctrl+C was last pressed
39
+
40
+ loop do
41
+ # Display the prompt (simplified version)
42
+ display_simple_prompt(lines, prefix, line_index, cursor_pos)
43
+
44
+ # Read a single character/key
45
+ begin
46
+ key = read_key_with_rapid_detection
47
+ rescue Interrupt
48
+ return nil
49
+ end
50
+
51
+ # Handle buffered rapid input (system paste detection)
52
+ if key.is_a?(Hash) && key[:type] == :rapid_input
53
+ pasted_text = key[:text]
54
+ pasted_lines = pasted_text.split("\n")
55
+
56
+ if pasted_lines.size > 1
57
+ # Multi-line rapid input - use placeholder for display
58
+ @paste_counter += 1
59
+ placeholder = "[##{@paste_counter} Paste Text]"
60
+ @paste_placeholders[placeholder] = pasted_text
61
+
62
+ # Insert placeholder at cursor position
63
+ chars = (lines[line_index] || "").chars
64
+ placeholder_chars = placeholder.chars
65
+ chars.insert(cursor_pos, *placeholder_chars)
66
+ lines[line_index] = chars.join
67
+ cursor_pos += placeholder_chars.length
68
+ else
69
+ # Single line rapid input - insert at cursor (use chars for UTF-8)
70
+ chars = (lines[line_index] || "").chars
71
+ pasted_chars = pasted_text.chars
72
+ chars.insert(cursor_pos, *pasted_chars)
73
+ lines[line_index] = chars.join
74
+ cursor_pos += pasted_chars.length
75
+ end
76
+ next
77
+ end
78
+
79
+ case key
80
+ when "\n" # Shift+Enter - newline (Linux/Mac sends \n for Shift+Enter in some terminals)
81
+ # Add new line
82
+ if lines[line_index]
83
+ # Split current line at cursor (use chars for UTF-8)
84
+ chars = lines[line_index].chars
85
+ lines[line_index] = chars[0...cursor_pos].join
86
+ lines.insert(line_index + 1, chars[cursor_pos..-1].join || "")
87
+ else
88
+ lines.insert(line_index + 1, "")
89
+ end
90
+ line_index += 1
91
+ cursor_pos = 0
92
+
93
+ when "\r" # Enter - submit
94
+ # Submit if not empty
95
+ unless lines.join.strip.empty? && @images.empty?
96
+ clear_simple_prompt(lines.size)
97
+ # Replace placeholders with actual pasted content
98
+ final_text = expand_placeholders(lines.join("\n"))
99
+ return { text: final_text, images: @images.dup }
100
+ end
101
+
102
+ when "\u0003" # Ctrl+C
103
+ # Check if input is empty
104
+ has_content = lines.any? { |line| !line.strip.empty? } || @images.any?
105
+
106
+ if has_content
107
+ # Input has content - clear it on first Ctrl+C
108
+ current_time = Time.now.to_f
109
+ time_since_last = @last_ctrl_c_time ? (current_time - @last_ctrl_c_time) : Float::INFINITY
110
+
111
+ if time_since_last < 2.0 # Within 2 seconds of last Ctrl+C
112
+ # Second Ctrl+C within 2 seconds - exit
113
+ clear_simple_prompt(lines.size)
114
+ return nil
115
+ else
116
+ # First Ctrl+C - clear content
117
+ @last_ctrl_c_time = current_time
118
+ lines = []
119
+ @images = []
120
+ cursor_pos = 0
121
+ line_index = 0
122
+ @paste_counter = 0
123
+ @paste_placeholders = {}
124
+ end
125
+ else
126
+ # Input is empty - exit immediately
127
+ clear_simple_prompt(lines.size)
128
+ return nil
129
+ end
130
+
131
+ when "\u0016" # Ctrl+V - Paste
132
+ pasted = paste_from_clipboard
133
+ if pasted[:type] == :image
134
+ # Save image and add to list
135
+ @images << pasted[:path]
136
+ else
137
+ # Handle pasted text
138
+ pasted_text = pasted[:text]
139
+ pasted_lines = pasted_text.split("\n")
140
+
141
+ if pasted_lines.size > 1
142
+ # Multi-line paste - use placeholder for display
143
+ @paste_counter += 1
144
+ placeholder = "[##{@paste_counter} Paste Text]"
145
+ @paste_placeholders[placeholder] = pasted_text
146
+
147
+ # Insert placeholder at cursor position
148
+ chars = (lines[line_index] || "").chars
149
+ placeholder_chars = placeholder.chars
150
+ chars.insert(cursor_pos, *placeholder_chars)
151
+ lines[line_index] = chars.join
152
+ cursor_pos += placeholder_chars.length
153
+ else
154
+ # Single line paste - insert at cursor (use chars for UTF-8)
155
+ chars = (lines[line_index] || "").chars
156
+ pasted_chars = pasted_text.chars
157
+ chars.insert(cursor_pos, *pasted_chars)
158
+ lines[line_index] = chars.join
159
+ cursor_pos += pasted_chars.length
160
+ end
161
+ end
162
+
163
+ when "\u007F", "\b" # Backspace
164
+ if cursor_pos > 0
165
+ # Delete character before cursor (use chars for UTF-8)
166
+ chars = (lines[line_index] || "").chars
167
+ chars.delete_at(cursor_pos - 1)
168
+ lines[line_index] = chars.join
169
+ cursor_pos -= 1
170
+ elsif line_index > 0
171
+ # Join with previous line
172
+ prev_line = lines[line_index - 1]
173
+ current_line = lines[line_index]
174
+ lines.delete_at(line_index)
175
+ line_index -= 1
176
+ cursor_pos = prev_line.chars.length
177
+ lines[line_index] = prev_line + current_line
178
+ end
179
+
180
+ when "\e[A" # Up arrow
181
+ if line_index > 0
182
+ line_index -= 1
183
+ cursor_pos = [cursor_pos, (lines[line_index] || "").chars.length].min
184
+ end
185
+
186
+ when "\e[B" # Down arrow
187
+ if line_index < lines.size - 1
188
+ line_index += 1
189
+ cursor_pos = [cursor_pos, (lines[line_index] || "").chars.length].min
190
+ end
191
+
192
+ when "\e[C" # Right arrow
193
+ current_line = lines[line_index] || ""
194
+ cursor_pos = [cursor_pos + 1, current_line.chars.length].min
195
+
196
+ when "\e[D" # Left arrow
197
+ cursor_pos = [cursor_pos - 1, 0].max
198
+
199
+ when "\u0004" # Ctrl+D - Delete image by number
200
+ if @images.any?
201
+ print "\nEnter image number to delete (1-#{@images.size}): "
202
+ num = STDIN.gets.to_i
203
+ if num > 0 && num <= @images.size
204
+ @images.delete_at(num - 1)
205
+ end
206
+ end
207
+
208
+ else
209
+ # Regular character input - support UTF-8
210
+ if key.length >= 1 && key != "\e" && !key.start_with?("\e") && key.ord >= 32
211
+ lines[line_index] ||= ""
212
+ current_line = lines[line_index]
213
+
214
+ # Insert character at cursor position (using character index, not byte index)
215
+ chars = current_line.chars
216
+ chars.insert(cursor_pos, key)
217
+ lines[line_index] = chars.join
218
+ cursor_pos += 1
219
+ end
220
+ end
221
+
222
+ # Ensure we have at least one line
223
+ lines << "" if lines.empty?
224
+ end
225
+ end
226
+
227
+ private
228
+
229
+ # Display simplified prompt (just prefix and input, no box)
230
+ def display_simple_prompt(lines, prefix, line_index, cursor_pos)
231
+ # Clear previous display if exists
232
+ if @last_display_lines && @last_display_lines > 0
233
+ @last_display_lines.times do
234
+ print "\e[1A" # Move up one line
235
+ print "\e[2K" # Clear entire line
236
+ end
237
+ print "\r" # Move to beginning of line
238
+ end
239
+
240
+ lines_to_display = []
241
+
242
+ # Get terminal width for full-width separator
243
+ term_width = TTY::Screen.width
244
+
245
+ # Top separator line (full width)
246
+ lines_to_display << @pastel.dim("─" * term_width)
247
+
248
+ # Show images if any
249
+ if @images.any?
250
+ @images.each_with_index do |img_path, idx|
251
+ filename = File.basename(img_path)
252
+ filesize = File.exist?(img_path) ? format_filesize(File.size(img_path)) : "N/A"
253
+ lines_to_display << @pastel.dim("[Image #{idx + 1}] #{filename} (#{filesize})")
254
+ end
255
+ lines_to_display << ""
256
+ end
257
+
258
+ # Display input lines
259
+ display_lines = lines.empty? ? [""] : lines
260
+
261
+ display_lines.each_with_index do |line, idx|
262
+ if idx == 0
263
+ # First line with prefix
264
+ if idx == line_index
265
+ # Show cursor on this line
266
+ chars = line.chars
267
+ before_cursor = chars[0...cursor_pos].join
268
+ cursor_char = chars[cursor_pos] || " "
269
+ after_cursor = chars[(cursor_pos + 1)..-1]&.join || ""
270
+
271
+ line_display = "#{prefix} #{before_cursor}#{@pastel.on_white(@pastel.black(cursor_char))}#{after_cursor}"
272
+ lines_to_display << line_display
273
+ else
274
+ lines_to_display << "#{prefix} #{line}"
275
+ end
276
+ else
277
+ # Continuation lines (indented to align with first line content)
278
+ indent = " " * (prefix.length + 1)
279
+ if idx == line_index
280
+ # Show cursor on this line
281
+ chars = line.chars
282
+ before_cursor = chars[0...cursor_pos].join
283
+ cursor_char = chars[cursor_pos] || " "
284
+ after_cursor = chars[(cursor_pos + 1)..-1]&.join || ""
285
+
286
+ line_display = "#{indent}#{before_cursor}#{@pastel.on_white(@pastel.black(cursor_char))}#{after_cursor}"
287
+ lines_to_display << line_display
288
+ else
289
+ lines_to_display << "#{indent}#{line}"
290
+ end
291
+ end
292
+ end
293
+
294
+ # Bottom separator line (full width)
295
+ lines_to_display << @pastel.dim("─" * term_width)
296
+
297
+ # Output all lines
298
+ print lines_to_display.join("\n")
299
+ print "\n"
300
+
301
+ # Remember how many lines we displayed
302
+ @last_display_lines = lines_to_display.size
303
+ end
304
+
305
+ # Clear simple prompt display
306
+ def clear_simple_prompt(num_lines)
307
+ if @last_display_lines && @last_display_lines > 0
308
+ @last_display_lines.times do
309
+ print "\e[1A" # Move up one line
310
+ print "\e[2K" # Clear entire line
311
+ end
312
+ print "\r" # Move to beginning of line
313
+ end
314
+ end
315
+
316
+ # Expand placeholders to actual pasted content
317
+ def expand_placeholders(text)
318
+ result = text.dup
319
+ @paste_placeholders.each do |placeholder, actual_content|
320
+ result.gsub!(placeholder, actual_content)
321
+ end
322
+ result
323
+ end
324
+
325
+ # Read a single key press with escape sequence handling
326
+ # Handles UTF-8 multi-byte characters correctly
327
+ # Also detects rapid input (paste-like behavior)
328
+ def read_key_with_rapid_detection
329
+ $stdin.set_encoding('UTF-8')
330
+
331
+ current_time = Time.now.to_f
332
+ is_rapid_input = @last_input_time && (current_time - @last_input_time) < @rapid_input_threshold
333
+ @last_input_time = current_time
334
+
335
+ $stdin.raw do |io|
336
+ io.set_encoding('UTF-8') # Ensure IO encoding is UTF-8
337
+ c = io.getc
338
+
339
+ # Ensure character is UTF-8 encoded
340
+ c = c.force_encoding('UTF-8') if c.is_a?(String) && c.encoding != Encoding::UTF_8
341
+
342
+ # Handle escape sequences (arrow keys, special keys)
343
+ if c == "\e"
344
+ # Read the next 2 characters for escape sequences
345
+ begin
346
+ extra = io.read_nonblock(2)
347
+ extra = extra.force_encoding('UTF-8') if extra.encoding != Encoding::UTF_8
348
+ c = c + extra
349
+ rescue IO::WaitReadable, Errno::EAGAIN
350
+ # No more characters available
351
+ end
352
+ return c
353
+ end
354
+
355
+ # Check if there are more characters available using IO.select with timeout 0
356
+ has_more_input = IO.select([io], nil, nil, 0)
357
+
358
+ # If this is rapid input or there are more characters available
359
+ if is_rapid_input || has_more_input
360
+ # Buffer rapid input
361
+ buffer = c.to_s.dup
362
+ buffer.force_encoding('UTF-8')
363
+
364
+ # Keep reading available characters
365
+ loop do
366
+ begin
367
+ next_char = io.read_nonblock(1)
368
+ next_char = next_char.force_encoding('UTF-8') if next_char.encoding != Encoding::UTF_8
369
+ buffer << next_char
370
+
371
+ # Continue only if more characters are immediately available
372
+ break unless IO.select([io], nil, nil, 0)
373
+ rescue IO::WaitReadable, Errno::EAGAIN
374
+ break
375
+ end
376
+ end
377
+
378
+ # Ensure buffer is UTF-8
379
+ buffer.force_encoding('UTF-8')
380
+
381
+ # If we buffered multiple characters or newlines, treat as rapid input (paste)
382
+ if buffer.length > 1 || buffer.include?("\n") || buffer.include?("\r")
383
+ # Remove any trailing \r or \n from rapid input buffer
384
+ cleaned_buffer = buffer.gsub(/[\r\n]+\z/, '')
385
+ return { type: :rapid_input, text: cleaned_buffer } if cleaned_buffer.length > 0
386
+ end
387
+
388
+ # Single character rapid input, return as-is
389
+ return buffer[0] if buffer.length == 1
390
+ end
391
+
392
+ c
393
+ end
394
+ rescue Errno::EINTR
395
+ "\u0003" # Treat interrupt as Ctrl+C
396
+ end
397
+
398
+ # Legacy method for compatibility
399
+ def read_key
400
+ read_key_with_rapid_detection
401
+ end
402
+
403
+ # Paste from clipboard (cross-platform)
404
+ # @return [Hash] { type: :text/:image, text: String, path: String }
405
+ def paste_from_clipboard
406
+ case RbConfig::CONFIG["host_os"]
407
+ when /darwin/i
408
+ paste_from_clipboard_macos
409
+ when /linux/i
410
+ paste_from_clipboard_linux
411
+ when /mswin|mingw|cygwin/i
412
+ paste_from_clipboard_windows
413
+ else
414
+ { type: :text, text: "" }
415
+ end
416
+ end
417
+
418
+ # Paste from macOS clipboard
419
+ def paste_from_clipboard_macos
420
+ require 'shellwords'
421
+ require 'fileutils'
422
+
423
+ # First check if there's an image in clipboard
424
+ # Use osascript to check clipboard content type
425
+ has_image = system("osascript -e 'try' -e 'the clipboard as «class PNGf»' -e 'on error' -e 'return false' -e 'end try' >/dev/null 2>&1")
426
+
427
+ if has_image
428
+ # Create a persistent temporary file (won't be auto-deleted)
429
+ temp_dir = Dir.tmpdir
430
+ temp_filename = "clipboard-#{Time.now.to_i}-#{rand(10000)}.png"
431
+ temp_path = File.join(temp_dir, temp_filename)
432
+
433
+ # Extract image using osascript
434
+ script = <<~APPLESCRIPT
435
+ set png_data to the clipboard as «class PNGf»
436
+ set the_file to open for access POSIX file "#{temp_path}" with write permission
437
+ write png_data to the_file
438
+ close access the_file
439
+ APPLESCRIPT
440
+
441
+ success = system("osascript", "-e", script, out: File::NULL, err: File::NULL)
442
+
443
+ if success && File.exist?(temp_path) && File.size(temp_path) > 0
444
+ return { type: :image, path: temp_path }
445
+ end
446
+ end
447
+
448
+ # No image, try text - ensure UTF-8 encoding
449
+ text = `pbpaste 2>/dev/null`.to_s
450
+ text.force_encoding('UTF-8')
451
+ # Replace invalid UTF-8 sequences with replacement character
452
+ text = text.encode('UTF-8', invalid: :replace, undef: :replace)
453
+ { type: :text, text: text }
454
+ rescue => e
455
+ # Fallback to empty text on error
456
+ { type: :text, text: "" }
457
+ end
458
+
459
+ # Paste from Linux clipboard
460
+ def paste_from_clipboard_linux
461
+ require 'shellwords'
462
+
463
+ # Check if xclip is available
464
+ if system("which xclip >/dev/null 2>&1")
465
+ # Try to get image first
466
+ temp_file = Tempfile.new(["clipboard-", ".png"])
467
+ temp_file.close
468
+
469
+ # Try different image MIME types
470
+ ["image/png", "image/jpeg", "image/jpg"].each do |mime_type|
471
+ if system("xclip -selection clipboard -t #{mime_type} -o > #{Shellwords.escape(temp_file.path)} 2>/dev/null")
472
+ if File.size(temp_file.path) > 0
473
+ return { type: :image, path: temp_file.path }
474
+ end
475
+ end
476
+ end
477
+
478
+ # No image, get text - ensure UTF-8 encoding
479
+ text = `xclip -selection clipboard -o 2>/dev/null`.to_s
480
+ text.force_encoding('UTF-8')
481
+ text = text.encode('UTF-8', invalid: :replace, undef: :replace)
482
+ { type: :text, text: text }
483
+ elsif system("which xsel >/dev/null 2>&1")
484
+ # Fallback to xsel for text only
485
+ text = `xsel --clipboard --output 2>/dev/null`.to_s
486
+ text.force_encoding('UTF-8')
487
+ text = text.encode('UTF-8', invalid: :replace, undef: :replace)
488
+ { type: :text, text: text }
489
+ else
490
+ { type: :text, text: "" }
491
+ end
492
+ rescue => e
493
+ { type: :text, text: "" }
494
+ end
495
+
496
+ # Paste from Windows clipboard
497
+ def paste_from_clipboard_windows
498
+ # Try to get image using PowerShell
499
+ temp_file = Tempfile.new(["clipboard-", ".png"])
500
+ temp_file.close
501
+
502
+ ps_script = <<~POWERSHELL
503
+ Add-Type -AssemblyName System.Windows.Forms
504
+ $img = [Windows.Forms.Clipboard]::GetImage()
505
+ if ($img) {
506
+ $img.Save('#{temp_file.path.gsub("'", "''")}', [System.Drawing.Imaging.ImageFormat]::Png)
507
+ exit 0
508
+ } else {
509
+ exit 1
510
+ }
511
+ POWERSHELL
512
+
513
+ success = system("powershell", "-NoProfile", "-Command", ps_script, out: File::NULL, err: File::NULL)
514
+
515
+ if success && File.exist?(temp_file.path) && File.size(temp_file.path) > 0
516
+ return { type: :image, path: temp_file.path }
517
+ end
518
+
519
+ # No image, get text - ensure UTF-8 encoding
520
+ text = `powershell -NoProfile -Command "Get-Clipboard" 2>nul`.to_s
521
+ text.force_encoding('UTF-8')
522
+ text = text.encode('UTF-8', invalid: :replace, undef: :replace)
523
+ { type: :text, text: text }
524
+ rescue => e
525
+ { type: :text, text: "" }
526
+ end
527
+
528
+ # Format file size for display
529
+ def format_filesize(size)
530
+ if size < 1024
531
+ "#{size}B"
532
+ elsif size < 1024 * 1024
533
+ "#{(size / 1024.0).round(1)}KB"
534
+ else
535
+ "#{(size / 1024.0 / 1024.0).round(1)}MB"
536
+ end
537
+ end
538
+ end
539
+ end
540
+ end
@@ -45,14 +45,12 @@ module Clacky
45
45
  status_line = " " + parts.join(separator)
46
46
 
47
47
  puts status_line
48
- puts @pastel.dim("─" * [TTY::Screen.width, 80].min)
49
48
  end
50
49
 
51
50
  # Display minimal status for non-interactive mode
52
51
  def display_minimal(working_dir:, mode:)
53
52
  dir_display = shorten_path(working_dir)
54
53
  puts " #{@pastel.bright_cyan(dir_display)} #{@pastel.dim('│')} #{@pastel.yellow(mode)}"
55
- puts @pastel.dim("─" * [TTY::Screen.width, 80].min)
56
54
  end
57
55
 
58
56
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clacky
4
- VERSION = "0.5.3"
4
+ VERSION = "0.5.5"
5
5
  end
data/lib/clacky.rb CHANGED
@@ -3,15 +3,16 @@
3
3
  require_relative "clacky/version"
4
4
  require_relative "clacky/config"
5
5
  require_relative "clacky/client"
6
- require_relative "clacky/conversation"
7
6
 
8
7
  # Agent system
8
+ require_relative "clacky/model_pricing"
9
9
  require_relative "clacky/agent_config"
10
10
  require_relative "clacky/hook_manager"
11
11
  require_relative "clacky/tool_registry"
12
12
  require_relative "clacky/thinking_verbs"
13
13
  require_relative "clacky/progress_indicator"
14
14
  require_relative "clacky/session_manager"
15
+ require_relative "clacky/gitignore_parser"
15
16
  require_relative "clacky/utils/limit_stack"
16
17
  require_relative "clacky/utils/path_helper"
17
18
  require_relative "clacky/tools/base"
@@ -32,7 +33,7 @@ require_relative "clacky/agent"
32
33
 
33
34
  # UI components
34
35
  require_relative "clacky/ui/banner"
35
- require_relative "clacky/ui/prompt"
36
+ require_relative "clacky/ui/enhanced_prompt"
36
37
  require_relative "clacky/ui/statusbar"
37
38
  require_relative "clacky/ui/formatter"
38
39
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openclacky
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - windy
@@ -136,8 +136,9 @@ files:
136
136
  - lib/clacky/cli.rb
137
137
  - lib/clacky/client.rb
138
138
  - lib/clacky/config.rb
139
- - lib/clacky/conversation.rb
139
+ - lib/clacky/gitignore_parser.rb
140
140
  - lib/clacky/hook_manager.rb
141
+ - lib/clacky/model_pricing.rb
141
142
  - lib/clacky/progress_indicator.rb
142
143
  - lib/clacky/session_manager.rb
143
144
  - lib/clacky/thinking_verbs.rb
@@ -157,8 +158,8 @@ files:
157
158
  - lib/clacky/tools/write.rb
158
159
  - lib/clacky/trash_directory.rb
159
160
  - lib/clacky/ui/banner.rb
161
+ - lib/clacky/ui/enhanced_prompt.rb
160
162
  - lib/clacky/ui/formatter.rb
161
- - lib/clacky/ui/prompt.rb
162
163
  - lib/clacky/ui/statusbar.rb
163
164
  - lib/clacky/utils/arguments_parser.rb
164
165
  - lib/clacky/utils/limit_stack.rb
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Clacky
4
- class Conversation
5
- attr_reader :messages
6
-
7
- def initialize(api_key, model:, base_url:, max_tokens:)
8
- @client = Client.new(api_key, base_url: base_url)
9
- @model = model
10
- @max_tokens = max_tokens
11
- @messages = []
12
- end
13
-
14
- def send_message(content)
15
- # Add user message to history
16
- @messages << {
17
- role: "user",
18
- content: content
19
- }
20
-
21
- # Get response from Claude
22
- response_text = @client.send_messages(@messages, model: @model, max_tokens: @max_tokens)
23
-
24
- # Add assistant response to history
25
- @messages << {
26
- role: "assistant",
27
- content: response_text
28
- }
29
-
30
- response_text
31
- end
32
-
33
- def clear
34
- @messages = []
35
- end
36
-
37
- def history
38
- @messages.dup
39
- end
40
- end
41
- end