consolle 0.3.1 → 0.3.3

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: 985e37c4ac0dab7d0404e36898e75e23e4337a9d5e57b379f4e4f6b57e6461b2
4
- data.tar.gz: fe8fa63ff1d743359391c81a0d3f1a472d494f72eb86a88d6be1e852d1f57e5f
3
+ metadata.gz: b736640d04dff94d8b7999c5c1d0546e76476fa09261645041ce42315a4a81a1
4
+ data.tar.gz: 05e6672b68f0391004e292c9e73c61d6b0ce87e344ceed9318af4b65e54fd814
5
5
  SHA512:
6
- metadata.gz: d490d26a53d2db34d1943c162dcbeea8b359632c36846ddf803afcee7b6d93f11cc1e759e9e06b66b743d2fd38c2d8ba71a39ac5623318f106227229bf977ad6
7
- data.tar.gz: 94610670b7059fe04fff2fb143b63502c29171b3f435c06f808897f74cbd05e16732efcf89ea9a27e4944680501d0b7ed93879ce10e5b67b02e57349ada15815
6
+ metadata.gz: '06863ea17a179c399c3e7b34187c636d73f6830da923c04516871102caa02df810799074b141b746f403b81aebcbe22a3ce3eb1e170592e43eeffb8a260a97ef'
7
+ data.tar.gz: ae90cbf05848abe0b29d4bd860287a18fe83b6907d404aba230505803a5d48d92cbc422d7c717bb3d46ee89777eebbcff3845f14877fd92b747f04b394474863
data/.version CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.3.3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- consolle (0.3.1)
4
+ consolle (0.3.3)
5
5
  logger (~> 1.0)
6
6
  thor (~> 1.0)
7
7
 
data/lib/consolle/cli.rb CHANGED
@@ -556,7 +556,8 @@ module Consolle
556
556
  if result['success']
557
557
  # Always print result, even if empty (multiline code often returns empty string)
558
558
  puts result['result'] unless result['result'].nil?
559
- puts "Execution time: #{result['execution_time']}s" if options[:verbose] && result['execution_time']
559
+ # Always show execution time when available
560
+ puts "Execution time: #{result['execution_time'].round(3)}s" if result['execution_time']
560
561
  else
561
562
  # Display error information
562
563
  if result['error_code']
@@ -572,6 +573,10 @@ module Consolle
572
573
 
573
574
  puts result['message']
574
575
  puts result['backtrace']&.join("\n") if options[:verbose] && result['backtrace']
576
+
577
+ # Show execution time for errors too
578
+ puts "Execution time: #{result['execution_time'].round(3)}s" if result['execution_time']
579
+
575
580
  exit 1
576
581
  end
577
582
  end
@@ -618,6 +623,9 @@ module Consolle
618
623
 
619
624
  def send_code_to_socket(socket_path, code, timeout: 15)
620
625
  request_id = SecureRandom.uuid
626
+ # Ensure code is UTF-8 encoded
627
+ code = code.force_encoding('UTF-8') if code.respond_to?(:force_encoding)
628
+
621
629
  request = {
622
630
  'action' => 'eval',
623
631
  'code' => code,
@@ -625,23 +633,42 @@ module Consolle
625
633
  'request_id' => request_id
626
634
  }
627
635
 
636
+ STDERR.puts "[DEBUG] Creating socket connection to: #{socket_path}" if ENV['DEBUG']
637
+
628
638
  Timeout.timeout(timeout + 5) do
629
639
  socket = UNIXSocket.new(socket_path)
640
+ STDERR.puts "[DEBUG] Socket connected" if ENV['DEBUG']
630
641
 
631
- # Send request as single line JSON
632
- socket.write(JSON.generate(request))
642
+ # Send request as single line JSON with UTF-8 encoding
643
+ json_data = JSON.generate(request)
644
+ STDERR.puts "[DEBUG] JSON data size: #{json_data.bytesize} bytes" if ENV['DEBUG']
645
+
646
+ # Debug: Check for newlines in JSON
647
+ if ENV['DEBUG'] && json_data.include?("\n")
648
+ STDERR.puts "[DEBUG] WARNING: JSON contains literal newline!"
649
+ File.write("/tmp/cone_debug.json", json_data) if ENV['DEBUG_SAVE']
650
+ end
651
+
652
+ STDERR.puts "[DEBUG] Sending request..." if ENV['DEBUG']
653
+
654
+ socket.write(json_data)
633
655
  socket.write("\n")
634
656
  socket.flush
657
+
658
+ STDERR.puts "[DEBUG] Request sent, waiting for response..." if ENV['DEBUG']
635
659
 
636
660
  # Read response
637
661
  response_data = socket.gets
662
+ STDERR.puts "[DEBUG] Response received: #{response_data&.bytesize} bytes" if ENV['DEBUG']
638
663
  socket.close
639
664
 
640
- JSON.parse(response_data)
665
+ JSON.parse(response_data) if response_data
641
666
  end
642
667
  rescue Timeout::Error
668
+ STDERR.puts "[DEBUG] Timeout occurred after #{timeout} seconds" if ENV['DEBUG']
643
669
  { 'success' => false, 'error' => 'Timeout', 'message' => "Request timed out after #{timeout} seconds" }
644
670
  rescue StandardError => e
671
+ STDERR.puts "[DEBUG] Error: #{e.class}: #{e.message}" if ENV['DEBUG']
645
672
  { 'success' => false, 'error' => e.class.name, 'message' => e.message }
646
673
  end
647
674
 
@@ -146,18 +146,24 @@ module Consolle
146
146
  def handle_client(client)
147
147
  Thread.new do
148
148
  # Read request
149
+ logger.debug "[ConsoleSocketServer] Waiting for request data..." if ENV['DEBUG']
149
150
  request_data = client.gets
151
+ logger.debug "[ConsoleSocketServer] Received data: #{request_data&.bytesize} bytes" if ENV['DEBUG']
150
152
  return unless request_data
151
153
 
152
154
  request = JSON.parse(request_data)
153
155
  logger.debug "[ConsoleSocketServer] Request: #{request.inspect}"
156
+ logger.debug "[ConsoleSocketServer] Code size: #{request['code']&.bytesize} bytes" if ENV['DEBUG'] && request['code']
154
157
 
155
158
  # Process through broker
156
159
  response = @broker.process_request(request)
157
160
 
158
161
  # Send response
159
162
  begin
160
- client.write(JSON.generate(response))
163
+ # Ensure response is properly encoded as UTF-8
164
+ response_json = JSON.generate(response)
165
+ response_json = response_json.force_encoding('UTF-8')
166
+ client.write(response_json)
161
167
  client.write("\n")
162
168
  client.flush
163
169
  rescue Errno::EPIPE
@@ -171,7 +177,8 @@ module Consolle
171
177
  'error' => 'InvalidRequest',
172
178
  'message' => "Invalid JSON: #{e.message}"
173
179
  }
174
- client.write(JSON.generate(error_response))
180
+ response_json = JSON.generate(error_response).force_encoding('UTF-8')
181
+ client.write(response_json)
175
182
  client.write("\n")
176
183
  rescue Errno::EPIPE
177
184
  logger.debug '[ConsoleSocketServer] Client disconnected while sending JSON parse error'
@@ -187,7 +194,8 @@ module Consolle
187
194
  'error' => e.class.name,
188
195
  'message' => e.message
189
196
  }
190
- client.write(JSON.generate(error_response))
197
+ response_json = JSON.generate(error_response).force_encoding('UTF-8')
198
+ client.write(response_json)
191
199
  client.write("\n")
192
200
  rescue Errno::EPIPE
193
201
  # Client disconnected while sending error response
@@ -51,6 +51,9 @@ module Consolle
51
51
  @mutex.synchronize do
52
52
  raise 'Console is not running' unless running?
53
53
 
54
+ # Record start time for execution measurement
55
+ start_time = Time.now
56
+
54
57
  # Check if this is a remote console
55
58
  is_remote = @command.include?('ssh') || @command.include?('kamal') || @command.include?('docker')
56
59
 
@@ -95,14 +98,59 @@ module Consolle
95
98
  code.encode('UTF-8')
96
99
  end
97
100
  end
98
- encoded_code = Base64.strict_encode64(utf8_code)
99
-
100
- # Use eval to execute the Base64-decoded code with exception handling
101
- # Ensure decoded string is properly handled as UTF-8
102
- eval_command = "begin; eval(Base64.decode64('#{encoded_code}').force_encoding('UTF-8'), IRB.CurrentContext.workspace.binding); rescue Exception => e; puts \"\#{e.class}: \#{e.message}\"; nil; end"
103
- logger.debug '[ConsoleSupervisor] Sending eval command (Base64 encoded)'
104
- @writer.puts eval_command
105
- @writer.flush
101
+ # For large code, use temporary file approach
102
+ if utf8_code.bytesize > 1000
103
+ logger.debug "[ConsoleSupervisor] Large code (#{utf8_code.bytesize} bytes), using temporary file approach"
104
+
105
+ # Create temp file with unique name
106
+ require 'tempfile'
107
+ require 'securerandom'
108
+
109
+ temp_filename = "consolle_temp_#{SecureRandom.hex(8)}.rb"
110
+ temp_path = if defined?(Rails) && Rails.root
111
+ Rails.root.join('tmp', temp_filename).to_s
112
+ else
113
+ File.join(Dir.tmpdir, temp_filename)
114
+ end
115
+
116
+ # Write code to temp file
117
+ File.write(temp_path, utf8_code)
118
+ logger.debug "[ConsoleSupervisor] Wrote code to temp file: #{temp_path}"
119
+
120
+ # Load and execute the file with timeout
121
+ eval_command = <<~RUBY.strip
122
+ begin
123
+ require 'timeout'
124
+ _temp_file = '#{temp_path}'
125
+ Timeout.timeout(#{timeout - 1}) do
126
+ load _temp_file
127
+ end
128
+ rescue Timeout::Error => e
129
+ puts "Timeout::Error: Code execution timed out after #{timeout - 1} seconds"
130
+ nil
131
+ rescue Exception => e
132
+ puts "\#{e.class}: \#{e.message}"
133
+ puts e.backtrace.first(5).join("\\n") if e.backtrace
134
+ nil
135
+ ensure
136
+ File.unlink(_temp_file) if File.exist?(_temp_file)
137
+ end
138
+ RUBY
139
+
140
+ @writer.puts eval_command
141
+ @writer.flush
142
+ else
143
+ # For smaller code, use Base64 encoding to avoid escaping issues
144
+ encoded_code = Base64.strict_encode64(utf8_code)
145
+ eval_command = "begin; require 'timeout'; Timeout.timeout(#{timeout - 1}) { eval(Base64.decode64('#{encoded_code}').force_encoding('UTF-8'), IRB.CurrentContext.workspace.binding) }; rescue Timeout::Error => e; puts \"Timeout::Error: Code execution timed out after #{timeout - 1} seconds\"; nil; rescue Exception => e; puts \"\#{e.class}: \#{e.message}\"; nil; end"
146
+ logger.debug "[ConsoleSupervisor] Small code (#{encoded_code.bytesize} bytes), using direct Base64 approach"
147
+ @writer.puts eval_command
148
+ @writer.flush
149
+ end
150
+
151
+ logger.debug "[ConsoleSupervisor] Code preview (first 100 chars): #{utf8_code[0..100].inspect}" if ENV['DEBUG']
152
+
153
+ logger.debug "[ConsoleSupervisor] Command sent at #{Time.now}, waiting for response..."
106
154
 
107
155
  # Collect output
108
156
  output = +''
@@ -111,17 +159,28 @@ module Consolle
111
159
  begin
112
160
  loop do
113
161
  if Time.now > deadline
162
+ logger.debug "[ConsoleSupervisor] Timeout reached after #{Time.now - start_time}s, output so far: #{output.bytesize} bytes"
163
+ logger.debug "[ConsoleSupervisor] Output content: #{output.inspect}" if ENV['DEBUG']
114
164
  # Timeout - send Ctrl-C
115
165
  @writer.write(CTRL_C)
116
166
  @writer.flush
117
167
  sleep 0.5
118
168
  clear_buffer
169
+ execution_time = Time.now - start_time
119
170
  return build_timeout_response(timeout)
120
171
  end
121
172
 
122
173
  begin
123
174
  chunk = @reader.read_nonblock(4096)
124
175
  output << chunk
176
+ logger.debug "[ConsoleSupervisor] Got #{chunk.bytesize} bytes, total output: #{output.bytesize} bytes" if ENV['DEBUG']
177
+
178
+ # Respond to cursor position request during command execution
179
+ if chunk.include?("\e[6n")
180
+ logger.debug "[ConsoleSupervisor] Detected cursor position request during eval, sending response"
181
+ @writer.write("\e[1;1R")
182
+ @writer.flush
183
+ end
125
184
 
126
185
  # Check if we got prompt back
127
186
  clean = strip_ansi(output)
@@ -136,14 +195,16 @@ module Consolle
136
195
  break
137
196
  end
138
197
  rescue IO::WaitReadable
198
+ logger.debug "[ConsoleSupervisor] Waiting for data... (#{Time.now - start_time}s elapsed, output size: #{output.bytesize})" if ENV['DEBUG']
139
199
  IO.select([@reader], nil, nil, 0.1)
140
200
  rescue Errno::EIO
141
201
  # PTY can throw EIO when no data available
142
202
  IO.select([@reader], nil, nil, 0.1)
143
203
  rescue EOFError
204
+ execution_time = Time.now - start_time
144
205
  return build_error_response(
145
206
  EOFError.new('Console terminated'),
146
- execution_time: nil
207
+ execution_time: execution_time
147
208
  )
148
209
  end
149
210
  end
@@ -155,15 +216,19 @@ module Consolle
155
216
  logger.debug "[ConsoleSupervisor] Raw output: #{output.inspect}"
156
217
  logger.debug "[ConsoleSupervisor] Parsed result: #{parsed_result.inspect}"
157
218
 
219
+ # Calculate execution time
220
+ execution_time = Time.now - start_time
221
+
158
222
  # Check if the output contains an error
159
223
  if parsed_result.is_a?(Hash) && parsed_result[:error]
160
- build_error_response(parsed_result[:exception], execution_time: nil)
224
+ build_error_response(parsed_result[:exception], execution_time: execution_time)
161
225
  else
162
- { success: true, output: parsed_result, execution_time: nil }
226
+ { success: true, output: parsed_result, execution_time: execution_time }
163
227
  end
164
228
  rescue StandardError => e
165
229
  logger.error "[ConsoleSupervisor] Eval error: #{e.message}"
166
- build_error_response(e, execution_time: nil)
230
+ execution_time = Time.now - start_time
231
+ build_error_response(e, execution_time: execution_time)
167
232
  end
168
233
  end
169
234
  end
@@ -250,8 +315,10 @@ module Consolle
250
315
  # Disable pry-rails (force IRB instead of Pry)
251
316
  'DISABLE_PRY_RAILS' => '1',
252
317
 
253
- # Completely disable pager
318
+ # Completely disable pager (critical for automation)
254
319
  'PAGER' => 'cat', # Set pager to cat (immediate output)
320
+ 'GEM_PAGER' => 'cat', # Disable gem pager
321
+ 'IRB_PAGER' => 'cat', # Ruby 3.3+ specific pager setting
255
322
  'NO_PAGER' => '1', # Pager disable flag
256
323
  'LESS' => '', # Clear less pager options
257
324
 
@@ -456,7 +523,13 @@ module Consolle
456
523
  3.times do |i|
457
524
  begin
458
525
  loop do
459
- @reader.read_nonblock(4096)
526
+ chunk = @reader.read_nonblock(4096)
527
+ # Respond to cursor position request even while clearing buffer
528
+ if chunk.include?("\e[6n")
529
+ logger.debug "[ConsoleSupervisor] Detected cursor position request during clear_buffer, sending response"
530
+ @writer.write("\e[1;1R")
531
+ @writer.flush
532
+ end
460
533
  end
461
534
  rescue IO::WaitReadable, Errno::EIO
462
535
  # Buffer cleared for this iteration
@@ -471,6 +544,9 @@ module Consolle
471
544
 
472
545
  # Send IRB configuration commands to disable interactive features
473
546
  irb_commands = [
547
+ # CRITICAL: Disable pager first to prevent hanging on large outputs
548
+ 'IRB.conf[:USE_PAGER] = false', # Disable pager completely
549
+
474
550
  # Configure custom prompt mode to eliminate continuation prompts
475
551
  'IRB.conf[:PROMPT][:CONSOLLE] = { ' \
476
552
  'AUTO_INDENT: false, ' \
@@ -481,8 +557,7 @@ module Consolle
481
557
  'RETURN: "=> %s\\n" }',
482
558
  'IRB.conf[:PROMPT_MODE] = :CONSOLLE',
483
559
 
484
- # Disable interactive features
485
- 'IRB.conf[:USE_PAGER] = false', # Disable pager
560
+ # Disable other interactive features
486
561
  'IRB.conf[:USE_COLORIZE] = false', # Disable color output
487
562
  'IRB.conf[:USE_AUTOCOMPLETE] = false', # Disable autocompletion
488
563
  'IRB.conf[:USE_MULTILINE] = false', # Disable multiline editor to process code at once
@@ -42,6 +42,7 @@ module Consolle
42
42
 
43
43
  def process_request(request)
44
44
  request_id = request['request_id'] || SecureRandom.uuid
45
+ logger.debug "[RequestBroker] Received request: #{request_id}, action: #{request['action']}" if ENV['DEBUG']
45
46
 
46
47
  # Create future for response
47
48
  future = RequestFuture.new
@@ -57,11 +58,16 @@ module Consolle
57
58
  request: request,
58
59
  timestamp: Time.now
59
60
  })
61
+ logger.debug "[RequestBroker] Queued request: #{request_id}, queue size: #{@queue.size}" if ENV['DEBUG']
60
62
 
61
63
  # Wait for response (with timeout)
62
64
  begin
63
- future.get(timeout: request['timeout'] || 30)
65
+ logger.debug "[RequestBroker] Waiting for response: #{request_id}, timeout: #{request['timeout'] || 30}" if ENV['DEBUG']
66
+ response = future.get(timeout: request['timeout'] || 30)
67
+ logger.debug "[RequestBroker] Got response: #{request_id}" if ENV['DEBUG']
68
+ response
64
69
  rescue Timeout::Error
70
+ logger.debug "[RequestBroker] Request timed out: #{request_id}" if ENV['DEBUG']
65
71
  {
66
72
  'success' => false,
67
73
  'error' => 'RequestTimeout',
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: consolle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - nacyot
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-07-26 00:00:00.000000000 Z
10
+ date: 2025-08-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: logger