consolle 0.3.2 → 0.3.4
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/.version +1 -1
- data/Gemfile.lock +1 -1
- data/lib/consolle/adapters/rails_console.rb +1 -1
- data/lib/consolle/cli.rb +50 -8
- data/lib/consolle/server/console_socket_server.rb +11 -3
- data/lib/consolle/server/console_supervisor.rb +94 -16
- data/lib/consolle/server/request_broker.rb +7 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4cf7b0621257f76801c7a5f4f064c511be503aadc56abf6a396d0fac9017506a
|
|
4
|
+
data.tar.gz: 3b393a2a0e389ebf93172ed7f1d38edb77fd1daa9eaf56f759200919683c1a04
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 992c65047ff3db88c005465133723c75d20841e13519c39e6cbae63f63abb2f61da8f11ea624763fa9d0bd220d27cd58ba8420769634a339e02a860706966bf5
|
|
7
|
+
data.tar.gz: 5459c740c89fcf7df8250348cea205cb4d8d6db52d6b50e2e3b695efa8998fa421936e363c2fc3559e6f666c4cd0f4b403dcab023c162984194cb8e31eddef6a
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.4
|
data/Gemfile.lock
CHANGED
data/lib/consolle/cli.rb
CHANGED
|
@@ -484,7 +484,7 @@ module Consolle
|
|
|
484
484
|
#{' '}
|
|
485
485
|
The console must be started first with 'cone start'.
|
|
486
486
|
LONGDESC
|
|
487
|
-
method_option :timeout, type: :numeric, desc: 'Timeout in seconds', default:
|
|
487
|
+
method_option :timeout, type: :numeric, desc: 'Timeout in seconds', default: 30
|
|
488
488
|
method_option :file, type: :string, aliases: '-f', desc: 'Read Ruby code from FILE'
|
|
489
489
|
method_option :raw, type: :boolean, desc: 'Do not apply escape fixes for Claude Code (keep \\! as is)'
|
|
490
490
|
def exec(*code_parts)
|
|
@@ -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
|
-
|
|
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
|
|
@@ -616,8 +621,11 @@ module Consolle
|
|
|
616
621
|
File.join(Dir.pwd, 'tmp', 'cone', "#{target}.log")
|
|
617
622
|
end
|
|
618
623
|
|
|
619
|
-
def send_code_to_socket(socket_path, code, timeout:
|
|
624
|
+
def send_code_to_socket(socket_path, code, timeout: 30)
|
|
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,57 @@ 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
|
-
|
|
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
|
-
# Read response
|
|
637
|
-
response_data =
|
|
660
|
+
# Read response - handle large responses by reading all available data
|
|
661
|
+
response_data = ''
|
|
662
|
+
begin
|
|
663
|
+
# Read until we get a newline (end of JSON response)
|
|
664
|
+
while (chunk = socket.read_nonblock(65536)) # Read in 64KB chunks
|
|
665
|
+
response_data << chunk
|
|
666
|
+
break if response_data.include?("\n")
|
|
667
|
+
end
|
|
668
|
+
rescue IO::WaitReadable
|
|
669
|
+
IO.select([socket], nil, nil, 1)
|
|
670
|
+
retry if response_data.empty? || !response_data.include?("\n")
|
|
671
|
+
rescue EOFError
|
|
672
|
+
# Server closed connection
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
STDERR.puts "[DEBUG] Response received: #{response_data&.bytesize} bytes" if ENV['DEBUG']
|
|
638
676
|
socket.close
|
|
639
677
|
|
|
640
|
-
JSON
|
|
678
|
+
# Extract just the first line (the JSON response)
|
|
679
|
+
json_line = response_data.split("\n").first
|
|
680
|
+
JSON.parse(json_line) if json_line
|
|
641
681
|
end
|
|
642
682
|
rescue Timeout::Error
|
|
683
|
+
STDERR.puts "[DEBUG] Timeout occurred after #{timeout} seconds" if ENV['DEBUG']
|
|
643
684
|
{ 'success' => false, 'error' => 'Timeout', 'message' => "Request timed out after #{timeout} seconds" }
|
|
644
685
|
rescue StandardError => e
|
|
686
|
+
STDERR.puts "[DEBUG] Error: #{e.class}: #{e.message}" if ENV['DEBUG']
|
|
645
687
|
{ 'success' => false, 'error' => e.class.name, 'message' => e.message }
|
|
646
688
|
end
|
|
647
689
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -47,10 +47,16 @@ module Consolle
|
|
|
47
47
|
start_watchdog
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
def eval(code, timeout:
|
|
50
|
+
def eval(code, timeout: nil)
|
|
51
|
+
# Allow timeout to be configured via environment variable
|
|
52
|
+
default_timeout = ENV['CONSOLLE_TIMEOUT'] ? ENV['CONSOLLE_TIMEOUT'].to_i : 30
|
|
53
|
+
timeout ||= default_timeout
|
|
51
54
|
@mutex.synchronize do
|
|
52
55
|
raise 'Console is not running' unless running?
|
|
53
56
|
|
|
57
|
+
# Record start time for execution measurement
|
|
58
|
+
start_time = Time.now
|
|
59
|
+
|
|
54
60
|
# Check if this is a remote console
|
|
55
61
|
is_remote = @command.include?('ssh') || @command.include?('kamal') || @command.include?('docker')
|
|
56
62
|
|
|
@@ -95,14 +101,59 @@ module Consolle
|
|
|
95
101
|
code.encode('UTF-8')
|
|
96
102
|
end
|
|
97
103
|
end
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
# For large code, use temporary file approach
|
|
105
|
+
if utf8_code.bytesize > 1000
|
|
106
|
+
logger.debug "[ConsoleSupervisor] Large code (#{utf8_code.bytesize} bytes), using temporary file approach"
|
|
107
|
+
|
|
108
|
+
# Create temp file with unique name
|
|
109
|
+
require 'tempfile'
|
|
110
|
+
require 'securerandom'
|
|
111
|
+
|
|
112
|
+
temp_filename = "consolle_temp_#{SecureRandom.hex(8)}.rb"
|
|
113
|
+
temp_path = if defined?(Rails) && Rails.root
|
|
114
|
+
Rails.root.join('tmp', temp_filename).to_s
|
|
115
|
+
else
|
|
116
|
+
File.join(Dir.tmpdir, temp_filename)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Write code to temp file
|
|
120
|
+
File.write(temp_path, utf8_code)
|
|
121
|
+
logger.debug "[ConsoleSupervisor] Wrote code to temp file: #{temp_path}"
|
|
122
|
+
|
|
123
|
+
# Load and execute the file with timeout
|
|
124
|
+
eval_command = <<~RUBY.strip
|
|
125
|
+
begin
|
|
126
|
+
require 'timeout'
|
|
127
|
+
_temp_file = '#{temp_path}'
|
|
128
|
+
Timeout.timeout(#{timeout - 1}) do
|
|
129
|
+
load _temp_file
|
|
130
|
+
end
|
|
131
|
+
rescue Timeout::Error => e
|
|
132
|
+
puts "Timeout::Error: Code execution timed out after #{timeout - 1} seconds"
|
|
133
|
+
nil
|
|
134
|
+
rescue Exception => e
|
|
135
|
+
puts "\#{e.class}: \#{e.message}"
|
|
136
|
+
puts e.backtrace.first(5).join("\\n") if e.backtrace
|
|
137
|
+
nil
|
|
138
|
+
ensure
|
|
139
|
+
File.unlink(_temp_file) if File.exist?(_temp_file)
|
|
140
|
+
end
|
|
141
|
+
RUBY
|
|
142
|
+
|
|
143
|
+
@writer.puts eval_command
|
|
144
|
+
@writer.flush
|
|
145
|
+
else
|
|
146
|
+
# For smaller code, use Base64 encoding to avoid escaping issues
|
|
147
|
+
encoded_code = Base64.strict_encode64(utf8_code)
|
|
148
|
+
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"
|
|
149
|
+
logger.debug "[ConsoleSupervisor] Small code (#{encoded_code.bytesize} bytes), using direct Base64 approach"
|
|
150
|
+
@writer.puts eval_command
|
|
151
|
+
@writer.flush
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
logger.debug "[ConsoleSupervisor] Code preview (first 100 chars): #{utf8_code[0..100].inspect}" if ENV['DEBUG']
|
|
155
|
+
|
|
156
|
+
logger.debug "[ConsoleSupervisor] Command sent at #{Time.now}, waiting for response..."
|
|
106
157
|
|
|
107
158
|
# Collect output
|
|
108
159
|
output = +''
|
|
@@ -111,17 +162,21 @@ module Consolle
|
|
|
111
162
|
begin
|
|
112
163
|
loop do
|
|
113
164
|
if Time.now > deadline
|
|
165
|
+
logger.debug "[ConsoleSupervisor] Timeout reached after #{Time.now - start_time}s, output so far: #{output.bytesize} bytes"
|
|
166
|
+
logger.debug "[ConsoleSupervisor] Output content: #{output.inspect}" if ENV['DEBUG']
|
|
114
167
|
# Timeout - send Ctrl-C
|
|
115
168
|
@writer.write(CTRL_C)
|
|
116
169
|
@writer.flush
|
|
117
170
|
sleep 0.5
|
|
118
171
|
clear_buffer
|
|
172
|
+
execution_time = Time.now - start_time
|
|
119
173
|
return build_timeout_response(timeout)
|
|
120
174
|
end
|
|
121
175
|
|
|
122
176
|
begin
|
|
123
177
|
chunk = @reader.read_nonblock(4096)
|
|
124
178
|
output << chunk
|
|
179
|
+
logger.debug "[ConsoleSupervisor] Got #{chunk.bytesize} bytes, total output: #{output.bytesize} bytes" if ENV['DEBUG']
|
|
125
180
|
|
|
126
181
|
# Respond to cursor position request during command execution
|
|
127
182
|
if chunk.include?("\e[6n")
|
|
@@ -143,18 +198,30 @@ module Consolle
|
|
|
143
198
|
break
|
|
144
199
|
end
|
|
145
200
|
rescue IO::WaitReadable
|
|
201
|
+
logger.debug "[ConsoleSupervisor] Waiting for data... (#{Time.now - start_time}s elapsed, output size: #{output.bytesize})" if ENV['DEBUG']
|
|
146
202
|
IO.select([@reader], nil, nil, 0.1)
|
|
147
203
|
rescue Errno::EIO
|
|
148
204
|
# PTY can throw EIO when no data available
|
|
149
205
|
IO.select([@reader], nil, nil, 0.1)
|
|
150
206
|
rescue EOFError
|
|
207
|
+
execution_time = Time.now - start_time
|
|
151
208
|
return build_error_response(
|
|
152
209
|
EOFError.new('Console terminated'),
|
|
153
|
-
execution_time:
|
|
210
|
+
execution_time: execution_time
|
|
154
211
|
)
|
|
155
212
|
end
|
|
156
213
|
end
|
|
157
214
|
|
|
215
|
+
# Check if output is too large and truncate if necessary
|
|
216
|
+
max_output_size = 100_000 # 100KB limit for output
|
|
217
|
+
truncated = false
|
|
218
|
+
|
|
219
|
+
if output.bytesize > max_output_size
|
|
220
|
+
logger.warn "[ConsoleSupervisor] Output too large (#{output.bytesize} bytes), truncating to #{max_output_size} bytes"
|
|
221
|
+
output = output[0...max_output_size]
|
|
222
|
+
truncated = true
|
|
223
|
+
end
|
|
224
|
+
|
|
158
225
|
# Parse and return result
|
|
159
226
|
parsed_result = parse_output(output, eval_command)
|
|
160
227
|
|
|
@@ -162,15 +229,22 @@ module Consolle
|
|
|
162
229
|
logger.debug "[ConsoleSupervisor] Raw output: #{output.inspect}"
|
|
163
230
|
logger.debug "[ConsoleSupervisor] Parsed result: #{parsed_result.inspect}"
|
|
164
231
|
|
|
232
|
+
# Calculate execution time
|
|
233
|
+
execution_time = Time.now - start_time
|
|
234
|
+
|
|
165
235
|
# Check if the output contains an error
|
|
166
236
|
if parsed_result.is_a?(Hash) && parsed_result[:error]
|
|
167
|
-
build_error_response(parsed_result[:exception], execution_time:
|
|
237
|
+
build_error_response(parsed_result[:exception], execution_time: execution_time)
|
|
168
238
|
else
|
|
169
|
-
{ success: true, output: parsed_result, execution_time:
|
|
239
|
+
result = { success: true, output: parsed_result, execution_time: execution_time }
|
|
240
|
+
result[:truncated] = true if truncated
|
|
241
|
+
result[:truncated_at] = max_output_size if truncated
|
|
242
|
+
result
|
|
170
243
|
end
|
|
171
244
|
rescue StandardError => e
|
|
172
245
|
logger.error "[ConsoleSupervisor] Eval error: #{e.message}"
|
|
173
|
-
|
|
246
|
+
execution_time = Time.now - start_time
|
|
247
|
+
build_error_response(e, execution_time: execution_time)
|
|
174
248
|
end
|
|
175
249
|
end
|
|
176
250
|
end
|
|
@@ -257,8 +331,10 @@ module Consolle
|
|
|
257
331
|
# Disable pry-rails (force IRB instead of Pry)
|
|
258
332
|
'DISABLE_PRY_RAILS' => '1',
|
|
259
333
|
|
|
260
|
-
# Completely disable pager
|
|
334
|
+
# Completely disable pager (critical for automation)
|
|
261
335
|
'PAGER' => 'cat', # Set pager to cat (immediate output)
|
|
336
|
+
'GEM_PAGER' => 'cat', # Disable gem pager
|
|
337
|
+
'IRB_PAGER' => 'cat', # Ruby 3.3+ specific pager setting
|
|
262
338
|
'NO_PAGER' => '1', # Pager disable flag
|
|
263
339
|
'LESS' => '', # Clear less pager options
|
|
264
340
|
|
|
@@ -484,6 +560,9 @@ module Consolle
|
|
|
484
560
|
|
|
485
561
|
# Send IRB configuration commands to disable interactive features
|
|
486
562
|
irb_commands = [
|
|
563
|
+
# CRITICAL: Disable pager first to prevent hanging on large outputs
|
|
564
|
+
'IRB.conf[:USE_PAGER] = false', # Disable pager completely
|
|
565
|
+
|
|
487
566
|
# Configure custom prompt mode to eliminate continuation prompts
|
|
488
567
|
'IRB.conf[:PROMPT][:CONSOLLE] = { ' \
|
|
489
568
|
'AUTO_INDENT: false, ' \
|
|
@@ -494,8 +573,7 @@ module Consolle
|
|
|
494
573
|
'RETURN: "=> %s\\n" }',
|
|
495
574
|
'IRB.conf[:PROMPT_MODE] = :CONSOLLE',
|
|
496
575
|
|
|
497
|
-
# Disable interactive features
|
|
498
|
-
'IRB.conf[:USE_PAGER] = false', # Disable pager
|
|
576
|
+
# Disable other interactive features
|
|
499
577
|
'IRB.conf[:USE_COLORIZE] = false', # Disable color output
|
|
500
578
|
'IRB.conf[:USE_AUTOCOMPLETE] = false', # Disable autocompletion
|
|
501
579
|
'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
|
-
|
|
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.
|
|
4
|
+
version: 0.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- nacyot
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-07
|
|
10
|
+
date: 2025-08-07 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: logger
|