aia 0.9.12 → 0.9.13

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b10d493c773b5eecca9050d03180d8d5a23b07b2e79b0e737b284b6629f3319a
4
- data.tar.gz: 10bef52c76f737db9e3d974f44690242b4c417a89e4c17808f243c281eb70817
3
+ metadata.gz: d5d9ca1e0f36d50857345bcad1268359e8435c16ba2c159d68482c070de59207
4
+ data.tar.gz: 20e10d41131b850e7320613f0959d8c94589e48422bc5ba948954c614c094c12
5
5
  SHA512:
6
- metadata.gz: b10021b2557e63cfd4cbf19eead313c993af06e175ab81b1ab0f334afce4f6fc9ce9c877c2dd2f9e0da3093c07996daba667786f1200f8e2f7470b62161dcc64
7
- data.tar.gz: de8952cc72bf776bae243d20fee31e60663f38f4d12a8a3497eb89002a63a2acf39ca4e67e97c344f9324106dc50b7d3c7e74e51154e74ccef238d909570316e
6
+ metadata.gz: bb7913215b44a686db90c9e9e4a69a00024611c0a28f7f06da4290ad7781ef9685288845ea17f3c4180f74badf4d0829a462596a0280aed91354b6d54a36f975
7
+ data.tar.gz: 1701b67da9e0aa8afa4cd1815422989544c191e50a38f58205984b1d96033d4a6dea27fe1b9efceb9307b231848fd9cc6440bd83203a9b03dfa7c5dbee700fd2
data/.envrc CHANGED
@@ -1,2 +1,4 @@
1
+ source_up
2
+
1
3
  export RR=`pwd`
2
4
  # PATH_add $RR/bin
data/.version CHANGED
@@ -1 +1 @@
1
- 0.9.12
1
+ 0.9.13
@@ -28,11 +28,26 @@ module AIA
28
28
  result = send_to_client(prompt)
29
29
  end
30
30
 
31
- unless result.is_a? String
32
- result = result.content
31
+ # Preserve token information if available for metrics
32
+ if result.is_a?(String)
33
+ { content: result, metrics: nil }
34
+ elsif result.respond_to?(:multi_model?) && result.multi_model?
35
+ # Handle multi-model response with metrics
36
+ {
37
+ content: result.content,
38
+ metrics: nil, # Individual model metrics handled separately
39
+ multi_metrics: result.metrics_list
40
+ }
41
+ else
42
+ {
43
+ content: result.content,
44
+ metrics: {
45
+ input_tokens: result.input_tokens,
46
+ output_tokens: result.output_tokens,
47
+ model_id: result.model_id
48
+ }
49
+ }
33
50
  end
34
-
35
- result
36
51
  end
37
52
 
38
53
 
@@ -250,6 +250,15 @@ module AIA
250
250
  config.completion = shell
251
251
  end
252
252
 
253
+ opts.on("--metrics", "Display token usage in chat mode") do
254
+ config.show_metrics = true
255
+ end
256
+
257
+ opts.on("--cost", "Include cost calculations with metrics (requires --metrics)") do
258
+ config.show_cost = true
259
+ config.show_metrics = true # Automatically enable metrics when cost is requested
260
+ end
261
+
253
262
  opts.on("--version", "Show version") do
254
263
  puts AIA::VERSION
255
264
  exit
@@ -310,9 +310,15 @@ module AIA
310
310
  prompt_parts << ""
311
311
 
312
312
  results.each do |model_name, result|
313
- next if result.to_s.start_with?("Error with")
313
+ # Extract content from RubyLLM::Message if needed
314
+ content = if result.respond_to?(:content)
315
+ result.content
316
+ else
317
+ result.to_s
318
+ end
319
+ next if content.start_with?("Error with")
314
320
  prompt_parts << "#{model_name}:"
315
- prompt_parts << result.to_s
321
+ prompt_parts << content
316
322
  prompt_parts << ""
317
323
  end
318
324
 
@@ -321,13 +327,64 @@ module AIA
321
327
  end
322
328
 
323
329
  def format_individual_responses(results)
324
- output = []
330
+ # For metrics support, return a special structure if all results have token info
331
+ has_metrics = results.values.all? { |r| r.respond_to?(:input_tokens) && r.respond_to?(:output_tokens) }
332
+
333
+ if has_metrics && AIA.config.show_metrics
334
+ # Return structured data that preserves metrics for multi-model
335
+ format_multi_model_with_metrics(results)
336
+ else
337
+ # Original string formatting for non-metrics mode
338
+ output = []
339
+ results.each do |model_name, result|
340
+ output << "from: #{model_name}"
341
+ # Extract content from RubyLLM::Message if needed
342
+ content = if result.respond_to?(:content)
343
+ result.content
344
+ else
345
+ result.to_s
346
+ end
347
+ output << content
348
+ output << "" # Add blank line between results
349
+ end
350
+ output.join("\n")
351
+ end
352
+ end
353
+
354
+ def format_multi_model_with_metrics(results)
355
+ # Create a composite response that includes all model responses and metrics
356
+ formatted_content = []
357
+ metrics_data = []
358
+
325
359
  results.each do |model_name, result|
326
- output << "from: #{model_name}"
327
- output << result
328
- output << "" # Add blank line between results
360
+ formatted_content << "from: #{model_name}"
361
+ formatted_content << result.content
362
+ formatted_content << ""
363
+
364
+ # Collect metrics for each model
365
+ metrics_data << {
366
+ model_id: model_name,
367
+ input_tokens: result.input_tokens,
368
+ output_tokens: result.output_tokens
369
+ }
370
+ end
371
+
372
+ # Return a special MultiModelResponse that ChatProcessorService can handle
373
+ MultiModelResponse.new(formatted_content.join("\n"), metrics_data)
374
+ end
375
+
376
+ # Helper class to carry multi-model response with metrics
377
+ class MultiModelResponse
378
+ attr_reader :content, :metrics_list
379
+
380
+ def initialize(content, metrics_list)
381
+ @content = content
382
+ @metrics_list = metrics_list
383
+ end
384
+
385
+ def multi_model?
386
+ true
329
387
  end
330
- output.join("\n")
331
388
  end
332
389
 
333
390
 
@@ -480,7 +537,8 @@ module AIA
480
537
  chat_instance.ask(text_prompt, with: AIA.config.context_files)
481
538
  end
482
539
 
483
- response.content
540
+ # Return the full response object to preserve token information
541
+ response
484
542
  rescue StandardError => e
485
543
  e.message
486
544
  end
data/lib/aia/session.rb CHANGED
@@ -372,11 +372,34 @@ module AIA
372
372
  conversation = @context_manager.get_context
373
373
 
374
374
  @ui_presenter.display_thinking_animation
375
- response = @chat_processor.process_prompt(conversation)
375
+ response_data = @chat_processor.process_prompt(conversation)
376
+
377
+ # Handle new response format with metrics
378
+ if response_data.is_a?(Hash)
379
+ content = response_data[:content]
380
+ metrics = response_data[:metrics]
381
+ multi_metrics = response_data[:multi_metrics]
382
+ else
383
+ content = response_data
384
+ metrics = nil
385
+ multi_metrics = nil
386
+ end
376
387
 
377
- @ui_presenter.display_ai_response(response)
378
- @context_manager.add_to_context(role: "assistant", content: response)
379
- @chat_processor.speak(response)
388
+ @ui_presenter.display_ai_response(content)
389
+
390
+ # Display metrics if enabled and available (chat mode only)
391
+ if AIA.config.show_metrics
392
+ if multi_metrics
393
+ # Display metrics for each model in multi-model mode
394
+ @ui_presenter.display_multi_model_metrics(multi_metrics)
395
+ elsif metrics
396
+ # Display metrics for single model
397
+ @ui_presenter.display_token_metrics(metrics)
398
+ end
399
+ end
400
+
401
+ @context_manager.add_to_context(role: "assistant", content: content)
402
+ @chat_processor.speak(content)
380
403
 
381
404
  @ui_presenter.display_separator
382
405
  end
@@ -117,5 +117,211 @@ module AIA
117
117
  yield
118
118
  end
119
119
  end
120
+
121
+ def display_token_metrics(metrics)
122
+ return unless metrics
123
+
124
+ output_lines = []
125
+ output_lines << "═" * 55
126
+ output_lines << "Model: #{metrics[:model_id]}"
127
+
128
+ if AIA.config.show_cost
129
+ output_lines.concat(format_metrics_with_cost(metrics))
130
+ else
131
+ output_lines.concat(format_metrics_basic(metrics))
132
+ end
133
+
134
+ output_lines << "═" * 55
135
+
136
+ # Output to STDOUT
137
+ output_lines.each { |line| puts line }
138
+
139
+ # Also write to file if configured
140
+ if AIA.config.out_file && !AIA.config.out_file.nil?
141
+ File.open(AIA.config.out_file, 'a') do |file|
142
+ output_lines.each { |line| file.puts line }
143
+ end
144
+ end
145
+ end
146
+
147
+ def display_multi_model_metrics(metrics_list)
148
+ return unless metrics_list && !metrics_list.empty?
149
+
150
+ output_lines = []
151
+
152
+ # Determine table width based on whether costs are shown
153
+ if AIA.config.show_cost
154
+ table_width = 80
155
+ else
156
+ table_width = 60
157
+ end
158
+
159
+ output_lines << "═" * table_width
160
+ output_lines << "Multi-Model Token Usage"
161
+ output_lines << "─" * table_width
162
+
163
+ # Build header row
164
+ if AIA.config.show_cost
165
+ output_lines << sprintf("%-20s %10s %10s %10s %12s %10s",
166
+ "Model", "Input", "Output", "Total", "Cost", "x1000")
167
+ output_lines << "─" * table_width
168
+ else
169
+ output_lines << sprintf("%-20s %10s %10s %10s",
170
+ "Model", "Input", "Output", "Total")
171
+ output_lines << "─" * table_width
172
+ end
173
+
174
+ # Process each model
175
+ total_input = 0
176
+ total_output = 0
177
+ total_cost = 0.0
178
+
179
+ metrics_list.each do |metrics|
180
+ model_name = metrics[:model_id]
181
+ # Truncate model name if too long
182
+ model_name = model_name[0..17] + ".." if model_name.length > 19
183
+
184
+ input_tokens = metrics[:input_tokens] || 0
185
+ output_tokens = metrics[:output_tokens] || 0
186
+ total_tokens = input_tokens + output_tokens
187
+
188
+ if AIA.config.show_cost
189
+ cost_data = calculate_cost(metrics)
190
+ if cost_data[:available]
191
+ cost_str = "$#{'%.5f' % cost_data[:total_cost]}"
192
+ x1000_str = "$#{'%.2f' % (cost_data[:total_cost] * 1000)}"
193
+ total_cost += cost_data[:total_cost]
194
+ else
195
+ cost_str = "N/A"
196
+ x1000_str = "N/A"
197
+ end
198
+
199
+ output_lines << sprintf("%-20s %10d %10d %10d %12s %10s",
200
+ model_name, input_tokens, output_tokens, total_tokens, cost_str, x1000_str)
201
+ else
202
+ output_lines << sprintf("%-20s %10d %10d %10d",
203
+ model_name, input_tokens, output_tokens, total_tokens)
204
+ end
205
+
206
+ total_input += input_tokens
207
+ total_output += output_tokens
208
+ end
209
+
210
+ # Display totals row
211
+ output_lines << "─" * table_width
212
+ total_tokens = total_input + total_output
213
+
214
+ if AIA.config.show_cost && total_cost > 0
215
+ cost_str = "$#{'%.5f' % total_cost}"
216
+ x1000_str = "$#{'%.2f' % (total_cost * 1000)}"
217
+ output_lines << sprintf("%-20s %10d %10d %10d %12s %10s",
218
+ "TOTAL", total_input, total_output, total_tokens, cost_str, x1000_str)
219
+ else
220
+ output_lines << sprintf("%-20s %10d %10d %10d",
221
+ "TOTAL", total_input, total_output, total_tokens)
222
+ end
223
+
224
+ output_lines << "═" * table_width
225
+
226
+ # Output to STDOUT
227
+ output_lines.each { |line| puts line }
228
+
229
+ # Also write to file if configured
230
+ if AIA.config.out_file && !AIA.config.out_file.nil?
231
+ File.open(AIA.config.out_file, 'a') do |file|
232
+ output_lines.each { |line| file.puts line }
233
+ end
234
+ end
235
+ end
236
+
237
+ private
238
+
239
+ def display_metrics_basic(metrics)
240
+ puts "Input tokens: #{metrics[:input_tokens] || 'N/A'}"
241
+ puts "Output tokens: #{metrics[:output_tokens] || 'N/A'}"
242
+
243
+ if metrics[:input_tokens] && metrics[:output_tokens]
244
+ total = metrics[:input_tokens] + metrics[:output_tokens]
245
+ puts "Total tokens: #{total}"
246
+ end
247
+ end
248
+
249
+ def format_metrics_basic(metrics)
250
+ lines = []
251
+ lines << "Input tokens: #{metrics[:input_tokens] || 'N/A'}"
252
+ lines << "Output tokens: #{metrics[:output_tokens] || 'N/A'}"
253
+
254
+ if metrics[:input_tokens] && metrics[:output_tokens]
255
+ total = metrics[:input_tokens] + metrics[:output_tokens]
256
+ lines << "Total tokens: #{total}"
257
+ end
258
+
259
+ lines
260
+ end
261
+
262
+ def display_metrics_with_cost(metrics)
263
+ cost_data = calculate_cost(metrics)
264
+
265
+ if cost_data[:available]
266
+ puts "Input tokens: #{metrics[:input_tokens]} ($#{'%.5f' % cost_data[:input_cost]})"
267
+ puts "Output tokens: #{metrics[:output_tokens]} ($#{'%.5f' % cost_data[:output_cost]})"
268
+ puts "Total cost: $#{'%.5f' % cost_data[:total_cost]}"
269
+ puts "Cost x1000: $#{'%.2f' % (cost_data[:total_cost] * 1000)}"
270
+ else
271
+ puts "Input tokens: #{metrics[:input_tokens] || 'N/A'}"
272
+ puts "Output tokens: #{metrics[:output_tokens] || 'N/A'}"
273
+ total = (metrics[:input_tokens] || 0) + (metrics[:output_tokens] || 0)
274
+ puts "Total tokens: #{total}"
275
+ puts "Cost: N/A (pricing not available)"
276
+ end
277
+ end
278
+
279
+ def format_metrics_with_cost(metrics)
280
+ lines = []
281
+ cost_data = calculate_cost(metrics)
282
+
283
+ if cost_data[:available]
284
+ lines << "Input tokens: #{metrics[:input_tokens]} ($#{'%.5f' % cost_data[:input_cost]})"
285
+ lines << "Output tokens: #{metrics[:output_tokens]} ($#{'%.5f' % cost_data[:output_cost]})"
286
+ lines << "Total cost: $#{'%.5f' % cost_data[:total_cost]}"
287
+ lines << "Cost x1000: $#{'%.2f' % (cost_data[:total_cost] * 1000)}"
288
+ else
289
+ lines << "Input tokens: #{metrics[:input_tokens] || 'N/A'}"
290
+ lines << "Output tokens: #{metrics[:output_tokens] || 'N/A'}"
291
+ total = (metrics[:input_tokens] || 0) + (metrics[:output_tokens] || 0)
292
+ lines << "Total tokens: #{total}"
293
+ lines << "Cost: N/A (pricing not available)"
294
+ end
295
+
296
+ lines
297
+ end
298
+
299
+ def calculate_cost(metrics)
300
+ return { available: false } unless metrics[:model_id] && metrics[:input_tokens] && metrics[:output_tokens]
301
+
302
+ # Look up model info from RubyLLM
303
+ begin
304
+ model_info = RubyLLM::Models.find(metrics[:model_id])
305
+ return { available: false } unless model_info
306
+
307
+ input_price = model_info.input_price_per_million
308
+ output_price = model_info.output_price_per_million
309
+
310
+ return { available: false } unless input_price && output_price
311
+
312
+ input_cost = metrics[:input_tokens] * input_price / 1_000_000.0
313
+ output_cost = metrics[:output_tokens] * output_price / 1_000_000.0
314
+ total_cost = input_cost + output_cost
315
+
316
+ {
317
+ available: true,
318
+ input_cost: input_cost,
319
+ output_cost: output_cost,
320
+ total_cost: total_cost
321
+ }
322
+ rescue StandardError => e
323
+ { available: false, error: e.message }
324
+ end
325
+ end
120
326
  end
121
327
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.12
4
+ version: 0.9.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: 0.5.7
74
+ version: 0.5.8
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - ">="
80
80
  - !ruby/object:Gem::Version
81
- version: 0.5.7
81
+ version: 0.5.8
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: ruby_llm
84
84
  requirement: !ruby/object:Gem::Requirement
@@ -177,20 +177,6 @@ dependencies:
177
177
  - - ">="
178
178
  - !ruby/object:Gem::Version
179
179
  version: '0'
180
- - !ruby/object:Gem::Dependency
181
- name: versionaire
182
- requirement: !ruby/object:Gem::Requirement
183
- requirements:
184
- - - ">="
185
- - !ruby/object:Gem::Version
186
- version: '0'
187
- type: :runtime
188
- prerelease: false
189
- version_requirements: !ruby/object:Gem::Requirement
190
- requirements:
191
- - - ">="
192
- - !ruby/object:Gem::Version
193
- version: '0'
194
180
  - !ruby/object:Gem::Dependency
195
181
  name: word_wrapper
196
182
  requirement: !ruby/object:Gem::Requirement
@@ -329,7 +315,6 @@ files:
329
315
  - LICENSE
330
316
  - README.md
331
317
  - Rakefile
332
- - _notes.txt
333
318
  - bin/aia
334
319
  - docs/advanced-prompting.md
335
320
  - docs/assets/images/aia.png
data/_notes.txt DELETED
@@ -1,231 +0,0 @@
1
-
2
- --- 2025-02-01 18:01:36 -0600
3
- I have no idea where I left off in this branch. The objective is to replace all the back-end processes with AiClient.
4
-
5
- Tests are failing.
6
-
7
- Make a few changes. It seems to be working in its basic modes.
8
-
9
- --- 2025-02-21 20:13:19 -0600
10
- Implemented Stark's clean slate protocol
11
-
12
-
13
-
14
-
15
-
16
- --- 2025-03-29 21:39:46 -0500
17
- starting the refactor to take advantage of the new capability of the PromptMananger gem.
18
-
19
- lib/aia/chat_processor_service.rb
20
-
21
-
22
-
23
- --- 2025-04-03 22:17:11 -0500
24
- i have been tring to get multi-line input to work in the chat mode but have run into all kinds of problems. I think it would be best just to invoke the users editor for that kind of operation. Alo I am not sure but I thing the same ask method is used for getting values for parameters. changes may have been committed but they should be reversed back to the original and start over.
25
-
26
- def get_multiline_input
27
- input_lines = []
28
- current_line = ""
29
- last_key_time = Time.now
30
- waiting_printed = 0 # Track number of WAITING characters printed
31
-
32
- STDIN.raw! # Enable raw mode for immediate keypress detection
33
- begin
34
- loop do
35
- begin
36
- r, _, _ = IO.select([STDIN], nil, nil, 0.1)
37
- if r
38
- char = STDIN.getc
39
- last_key_time = Time.now
40
- # Clear waiting characters when user types again
41
- if waiting_printed > 0
42
- print WAITING_ERASE * waiting_printed # Erase all waiting characters
43
- $stdout.flush
44
- waiting_printed = 0
45
- end
46
- else
47
- if (Time.now - last_key_time >= KEYPRESS_TIMEUT) &&
48
- waiting_printed == 0 &&
49
- (!input_lines.empty? || !current_line.empty?)
50
- print WAITING
51
- $stdout.flush
52
- waiting_printed = 1 # Record one '?' printed
53
- end
54
- next
55
- end
56
-
57
- rescue Interrupt
58
- puts "\nInput cancelled. Discarding current input; please start over."
59
- input_lines = []
60
- current_line = ""
61
- waiting_printed = 0
62
- last_key_time = Time.now
63
- next
64
- end
65
-
66
- break if char.nil? # Handle EOF (Ctrl+D)
67
-
68
- if char == "\r" || char == "\n"
69
- if current_line.empty? && !input_lines.empty?
70
- break # Two Enters in a row submits
71
- else
72
- input_lines << current_line
73
- current_line = ""
74
- waiting_printed = 0 # Reset waiting on new line
75
- print "\n\r"
76
- $stdout.flush
77
- end
78
-
79
- elsif char == "\x04" # Ctrl+D
80
- break
81
-
82
- elsif char == "\x08" || char == "\x7F" # Backspace or Delete
83
- if !current_line.empty?
84
- current_line.chop!
85
- print WAITING_ERASE
86
- $stdout.flush
87
- elsif waiting_printed > 0
88
- # Clear one waiting character if current_line is empty
89
- print "\b \b"
90
- $stdout.flush
91
- waiting_printed -= 1
92
- end
93
-
94
- else
95
- current_line << char
96
- print char
97
- $stdout.flush
98
- end
99
- end
100
-
101
- ensure
102
- STDIN.cooked! # Restore terminal to normal mode
103
- end
104
-
105
- input_lines << current_line unless current_line.empty?
106
-
107
- # Handle single-line special case
108
- if input_lines.size == 1
109
- if special_first_line_processing(input_lines.first)
110
- # If special (starts with "//"), return immediately as if double return was pressed
111
- return input_lines.first
112
- else
113
- # If not special, keep as is and return the full input
114
- return input_lines.join("\n")
115
- end
116
- end
117
-
118
- input_lines.join("\n").tap do |result|
119
- puts "\n" if result.empty? # Clean up display if no input
120
- end
121
-
122
- rescue EOFError
123
- input_lines.join("\n")
124
- end
125
-
126
-
127
-
128
- --- 2025-04-03 22:18:18 -0500
129
- by using subl -w for multi-line input in chat mode that gives us the ability to write ERB for chat input.
130
-
131
- def get_multiline_input
132
- input_lines = []
133
- current_line = ""
134
- last_key_time = Time.now
135
- waiting_printed = 0 # Track number of WAITING characters printed
136
-
137
- STDIN.raw! # Enable raw mode for immediate keypress detection
138
- begin
139
- loop do
140
- begin
141
- r, _, _ = IO.select([STDIN], nil, nil, 0.1)
142
- if r
143
- char = STDIN.getc
144
- last_key_time = Time.now
145
- # Clear waiting characters when user types again
146
- if waiting_printed > 0
147
- print WAITING_ERASE * waiting_printed # Erase all waiting characters
148
- $stdout.flush
149
- waiting_printed = 0
150
- end
151
- else
152
- if (Time.now - last_key_time >= KEYPRESS_TIMEUT) &&
153
- waiting_printed == 0 &&
154
- (!input_lines.empty? || !current_line.empty?)
155
- print WAITING
156
- $stdout.flush
157
- waiting_printed = 1 # Record one '?' printed
158
- end
159
- next
160
- end
161
-
162
- rescue Interrupt
163
- puts "\nInput cancelled. Discarding current input; please start over."
164
- input_lines = []
165
- current_line = ""
166
- waiting_printed = 0
167
- last_key_time = Time.now
168
- next
169
- end
170
-
171
- break if char.nil? # Handle EOF (Ctrl+D)
172
-
173
- if char == "\r" || char == "\n"
174
- if current_line.empty? && !input_lines.empty?
175
- break # Two Enters in a row submits
176
- else
177
- input_lines << current_line
178
- current_line = ""
179
- waiting_printed = 0 # Reset waiting on new line
180
- print "\n\r"
181
- $stdout.flush
182
- end
183
-
184
- elsif char == "\x04" # Ctrl+D
185
- break
186
-
187
- elsif char == "\x08" || char == "\x7F" # Backspace or Delete
188
- if !current_line.empty?
189
- current_line.chop!
190
- print WAITING_ERASE
191
- $stdout.flush
192
- elsif waiting_printed > 0
193
- # Clear one waiting character if current_line is empty
194
- print "\b \b"
195
- $stdout.flush
196
- waiting_printed -= 1
197
- end
198
-
199
- else
200
- current_line << char
201
- print char
202
- $stdout.flush
203
- end
204
- end
205
-
206
- ensure
207
- STDIN.cooked! # Restore terminal to normal mode
208
- end
209
-
210
- input_lines << current_line unless current_line.empty?
211
-
212
- # Handle single-line special case
213
- if input_lines.size == 1
214
- if special_first_line_processing(input_lines.first)
215
- # If special (starts with "//"), return immediately as if double return was pressed
216
- return input_lines.first
217
- else
218
- # If not special, keep as is and return the full input
219
- return input_lines.join("\n")
220
- end
221
- end
222
-
223
- input_lines.join("\n").tap do |result|
224
- puts "\n" if result.empty? # Clean up display if no input
225
- end
226
-
227
- rescue EOFError
228
- input_lines.join("\n")
229
- end
230
-
231
-