consolle 0.3.5 → 0.3.7
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/.gitignore +3 -1
- data/.version +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +43 -9
- data/lib/consolle/adapters/rails_console.rb +1 -1
- data/lib/consolle/cli.rb +39 -28
- data/lib/consolle/config.rb +78 -0
- data/lib/consolle/errors.rb +61 -2
- data/lib/consolle/server/console_supervisor.rb +82 -28
- data/lib/consolle/server/request_broker.rb +11 -4
- data/lib/consolle.rb +1 -0
- data/rule.ko.md +35 -4
- data/rule.md +35 -4
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a88f6019a32aaae300dee3a3e39b1dd1084ea1e2900171d6ee02f880923e071e
|
|
4
|
+
data.tar.gz: e9c468bc8095d8197e8f70cc4c0b75d0cc8da9ba3a7b0af45573300b755e1970
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e5c0615769edda8936547380360ed5a7beeaaeda7569ccf91322daa4658923a82cad4ad87e5ffabeca4c230e89f3d371ebcaa5eec58663b25172ee0ab8c2ba07
|
|
7
|
+
data.tar.gz: 83dd02825015e9f9e73ff48075436f459722cdc045ee4b0b8d90509c5ca91274c27af682549ef114598efcf21d90628fc460360f33a069134b0da63eeab94c81
|
data/.gitignore
CHANGED
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.7
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Consolle is a library that manages Rails console through PTY (Pseudo-Terminal).
|
|
|
8
8
|
- **Socket Server Architecture**: Stable client-server communication through Unix socket
|
|
9
9
|
- **Automatic Restart (Watchdog)**: Automatic recovery on process failure
|
|
10
10
|
- **Environment-specific Execution**: Supports Rails environments (development, test, production)
|
|
11
|
-
- **Timeout Handling**: Automatic termination of
|
|
11
|
+
- **Timeout Handling**: Automatic termination of long-running code with robust prompt recovery
|
|
12
12
|
- **Log Management**: Automatic management of execution history and session logs
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
@@ -50,17 +50,46 @@ cone restart
|
|
|
50
50
|
### Advanced Usage
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
# Start with specific environment
|
|
54
|
-
cone start
|
|
53
|
+
# Start with specific environment (use RAILS_ENV)
|
|
54
|
+
RAILS_ENV=test cone start
|
|
55
55
|
|
|
56
|
-
# Restart with environment change
|
|
57
|
-
cone restart
|
|
56
|
+
# Restart with environment change (use RAILS_ENV)
|
|
57
|
+
RAILS_ENV=production cone restart
|
|
58
58
|
|
|
59
59
|
# Force full server restart
|
|
60
60
|
cone restart --force
|
|
61
61
|
|
|
62
|
-
# Set timeout
|
|
63
|
-
cone exec "
|
|
62
|
+
# Set timeout (default: 60s; precedence: CONSOLLE_TIMEOUT > --timeout > defaults)
|
|
63
|
+
CONSOLLE_TIMEOUT=90 cone exec "long_running_task" # env wins
|
|
64
|
+
cone exec "long_running_task" --timeout 120 # falls back when env not set
|
|
65
|
+
|
|
66
|
+
# Pre-exec Ctrl-C (prompt separation)
|
|
67
|
+
By default (development/production), cone sends Ctrl-C before each `exec` and waits for the IRB prompt (up to 3 seconds) to ensure a clean state and avoid hanging on partial input. If the prompt does not return within 3 seconds, the console subprocess is force-restarted and the request fails with `SERVER_UNHEALTHY`, so the caller can retry.
|
|
68
|
+
|
|
69
|
+
Timeout precedence
|
|
70
|
+
- `CONSOLLE_TIMEOUT` (if set and > 0) overrides all other sources on both client and server.
|
|
71
|
+
- Otherwise, CLI `--timeout` is used.
|
|
72
|
+
- Otherwise, default of 60s applies.
|
|
73
|
+
|
|
74
|
+
- Per‑call control (CLI):
|
|
75
|
+
- Enable: `cone exec --pre-sigint 'code'`
|
|
76
|
+
- Disable: `cone exec --no-pre-sigint 'code'`
|
|
77
|
+
- Global (server‑wide) control via environment when starting the server:
|
|
78
|
+
- Disable: `CONSOLLE_DISABLE_PRE_SIGINT=1 cone start`
|
|
79
|
+
- Note: this env var is read by the server process at start time, not by `cone exec`.
|
|
80
|
+
|
|
81
|
+
# Timeout and interrupt behavior during execution
|
|
82
|
+
- On execution timeout, cone sends Ctrl‑C to interrupt and attempts prompt recovery. For local consoles, it also sends an OS‑level `SIGINT` as a fallback.
|
|
83
|
+
- After recovery, subsequent `cone exec` requests continue normally.
|
|
84
|
+
|
|
85
|
+
# Error codes
|
|
86
|
+
- `EXECUTION_TIMEOUT`: The executed code exceeded its timeout.
|
|
87
|
+
- `SERVER_UNHEALTHY`: Pre‑exec prompt did not return within 3 seconds; the console subprocess was restarted and the request failed.
|
|
88
|
+
|
|
89
|
+
# Examples
|
|
90
|
+
- Force an execution timeout quickly and verify recovery:
|
|
91
|
+
- `cone exec 'sleep 999' --timeout 2` → fails with `EXECUTION_TIMEOUT`
|
|
92
|
+
- `cone exec 'puts :after_timeout; :ok'` → should succeed (`:ok`)
|
|
64
93
|
|
|
65
94
|
# Verbose log output
|
|
66
95
|
cone -v exec "User.all"
|
|
@@ -120,7 +149,7 @@ Consolle consists of the following structure:
|
|
|
120
149
|
- Manages Rails console process through PTY
|
|
121
150
|
- Automatic restart (Watchdog) feature
|
|
122
151
|
- Environment variable settings and IRB automation configuration
|
|
123
|
-
- Timeout handling and Ctrl-C support
|
|
152
|
+
- Timeout handling and Ctrl-C support (pre-exec safety check + post-timeout recovery)
|
|
124
153
|
|
|
125
154
|
#### RequestBroker
|
|
126
155
|
- Ensures request order through serial queue
|
|
@@ -145,6 +174,11 @@ env = {
|
|
|
145
174
|
}
|
|
146
175
|
```
|
|
147
176
|
|
|
177
|
+
Additional environment variables
|
|
178
|
+
|
|
179
|
+
- `CONSOLLE_TIMEOUT`: Highest‑priority timeout (seconds). Overrides CLI `--timeout` and defaults on both client and server.
|
|
180
|
+
- `CONSOLLE_DISABLE_PRE_SIGINT=1`: Disable the pre‑exec Ctrl‑C prompt check at the server level (read when starting the server).
|
|
181
|
+
|
|
148
182
|
## File Locations
|
|
149
183
|
|
|
150
184
|
- **Socket file**: `{Rails.root}/tmp/cone/cone.socket`
|
|
@@ -187,4 +221,4 @@ ls ~/.cone/sessions/
|
|
|
187
221
|
|
|
188
222
|
- **Current version**: 0.1.0
|
|
189
223
|
- **Ruby version**: 3.0 or higher
|
|
190
|
-
- **Rails version**: 7.0 or higher
|
|
224
|
+
- **Rails version**: 7.0 or higher
|
data/lib/consolle/cli.rb
CHANGED
|
@@ -50,7 +50,7 @@ module Consolle
|
|
|
50
50
|
shell.say
|
|
51
51
|
shell.say 'EXAMPLES:', :yellow
|
|
52
52
|
shell.say " cone exec 'User.count' # Execute code in default session"
|
|
53
|
-
shell.say " cone start
|
|
53
|
+
shell.say " RAILS_ENV=production cone start # Start console in production"
|
|
54
54
|
shell.say " cone exec -t api 'Rails.env' # Execute code in 'api' session"
|
|
55
55
|
shell.say ' cone exec -f script.rb # Execute code from file'
|
|
56
56
|
shell.say
|
|
@@ -121,8 +121,8 @@ module Consolle
|
|
|
121
121
|
The console runs as a daemon and can be accessed through the exec command.
|
|
122
122
|
|
|
123
123
|
You can specify a custom session name with --target to run multiple consoles:
|
|
124
|
-
cone start --target api
|
|
125
|
-
cone start --target worker
|
|
124
|
+
RAILS_ENV=production cone start --target api
|
|
125
|
+
RAILS_ENV=development cone start --target worker
|
|
126
126
|
|
|
127
127
|
Custom console commands are supported for special environments:
|
|
128
128
|
cone start --command "kamal app exec -i 'bin/rails console'"
|
|
@@ -131,7 +131,7 @@ module Consolle
|
|
|
131
131
|
For SSH-based commands that require authentication (e.g., 1Password SSH agent):
|
|
132
132
|
cone start --command "kamal console" --wait-timeout 60
|
|
133
133
|
LONGDESC
|
|
134
|
-
|
|
134
|
+
# Rails environment is now controlled via RAILS_ENV, not a CLI option
|
|
135
135
|
method_option :command, type: :string, aliases: '-c', desc: 'Custom console command', default: 'bin/rails console'
|
|
136
136
|
method_option :wait_timeout, type: :numeric, aliases: '-w', desc: 'Timeout for console startup (seconds)', default: Consolle::DEFAULT_WAIT_TIMEOUT
|
|
137
137
|
def start
|
|
@@ -160,7 +160,7 @@ module Consolle
|
|
|
160
160
|
clear_session_info
|
|
161
161
|
end
|
|
162
162
|
|
|
163
|
-
adapter = create_rails_adapter(
|
|
163
|
+
adapter = create_rails_adapter(current_rails_env, options[:target], options[:command], options[:wait_timeout])
|
|
164
164
|
|
|
165
165
|
puts 'Starting Rails console...'
|
|
166
166
|
|
|
@@ -175,7 +175,7 @@ module Consolle
|
|
|
175
175
|
|
|
176
176
|
# Log session start
|
|
177
177
|
log_session_event(adapter.process_pid, 'session_start', {
|
|
178
|
-
rails_env:
|
|
178
|
+
rails_env: current_rails_env,
|
|
179
179
|
socket_path: adapter.socket_path
|
|
180
180
|
})
|
|
181
181
|
rescue StandardError => e
|
|
@@ -197,7 +197,7 @@ module Consolle
|
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
# Check if server is actually responsive
|
|
200
|
-
adapter = create_rails_adapter(
|
|
200
|
+
adapter = create_rails_adapter(current_rails_env, options[:target])
|
|
201
201
|
server_status = begin
|
|
202
202
|
adapter.get_status
|
|
203
203
|
rescue StandardError
|
|
@@ -255,7 +255,7 @@ module Consolle
|
|
|
255
255
|
# Check if process is alive
|
|
256
256
|
if info['process_pid'] && process_alive?(info['process_pid'])
|
|
257
257
|
# Try to get server status
|
|
258
|
-
|
|
258
|
+
adapter = create_rails_adapter(current_rails_env, name)
|
|
259
259
|
server_status = begin
|
|
260
260
|
adapter.get_status
|
|
261
261
|
rescue StandardError
|
|
@@ -390,13 +390,12 @@ module Consolle
|
|
|
390
390
|
cone restart -t api
|
|
391
391
|
cone restart --target worker --force
|
|
392
392
|
LONGDESC
|
|
393
|
-
method_option :rails_env, type: :string, aliases: '-e', desc: 'Rails environment', default: 'development'
|
|
394
393
|
method_option :force, type: :boolean, aliases: '-f', desc: 'Force restart the entire server'
|
|
395
394
|
def restart
|
|
396
395
|
ensure_rails_project!
|
|
397
396
|
validate_session_name!(options[:target])
|
|
398
397
|
|
|
399
|
-
adapter = create_rails_adapter(
|
|
398
|
+
adapter = create_rails_adapter(current_rails_env, options[:target])
|
|
400
399
|
|
|
401
400
|
if adapter.running?
|
|
402
401
|
# Check if environment needs to be changed
|
|
@@ -406,25 +405,20 @@ module Consolle
|
|
|
406
405
|
nil
|
|
407
406
|
end
|
|
408
407
|
current_env = current_status&.dig('rails_env') || 'development'
|
|
409
|
-
|
|
408
|
+
desired_env = current_rails_env
|
|
409
|
+
needs_full_restart = options[:force] || (current_env != desired_env)
|
|
410
410
|
|
|
411
411
|
if needs_full_restart
|
|
412
|
-
if current_env !=
|
|
413
|
-
puts "Environment change detected (#{current_env} -> #{
|
|
412
|
+
if current_env != desired_env
|
|
413
|
+
puts "Environment change detected (#{current_env} -> #{desired_env})"
|
|
414
414
|
puts 'Performing full server restart...'
|
|
415
415
|
else
|
|
416
416
|
puts 'Force restarting Rails console server...'
|
|
417
417
|
end
|
|
418
418
|
|
|
419
|
-
# Save current rails_env for start command
|
|
420
|
-
old_env = @rails_env
|
|
421
|
-
@rails_env = options[:rails_env]
|
|
422
|
-
|
|
423
419
|
stop
|
|
424
420
|
sleep 1
|
|
425
|
-
invoke(:start, [], {
|
|
426
|
-
|
|
427
|
-
@rails_env = old_env
|
|
421
|
+
invoke(:start, [], {})
|
|
428
422
|
else
|
|
429
423
|
puts 'Restarting Rails console subprocess...'
|
|
430
424
|
|
|
@@ -459,7 +453,7 @@ module Consolle
|
|
|
459
453
|
end
|
|
460
454
|
else
|
|
461
455
|
puts 'Rails console is not running. Starting it...'
|
|
462
|
-
invoke(:start
|
|
456
|
+
invoke(:start)
|
|
463
457
|
end
|
|
464
458
|
end
|
|
465
459
|
|
|
@@ -484,7 +478,8 @@ module Consolle
|
|
|
484
478
|
#{' '}
|
|
485
479
|
The console must be started first with 'cone start'.
|
|
486
480
|
LONGDESC
|
|
487
|
-
method_option :timeout, type: :numeric, desc: 'Timeout in seconds', default:
|
|
481
|
+
method_option :timeout, type: :numeric, desc: 'Timeout in seconds', default: 60
|
|
482
|
+
method_option :pre_sigint, type: :boolean, desc: 'Send Ctrl-C before executing code (experimental)'
|
|
488
483
|
method_option :file, type: :string, aliases: '-f', desc: 'Read Ruby code from FILE'
|
|
489
484
|
method_option :raw, type: :boolean, desc: 'Do not apply escape fixes for Claude Code (keep \\! as is)'
|
|
490
485
|
def exec(*code_parts)
|
|
@@ -548,7 +543,13 @@ module Consolle
|
|
|
548
543
|
puts "Executing: #{code}" if options[:verbose]
|
|
549
544
|
|
|
550
545
|
# Send code to socket
|
|
551
|
-
|
|
546
|
+
send_opts = { timeout: options[:timeout] }
|
|
547
|
+
send_opts[:pre_sigint] = options[:pre_sigint] unless options[:pre_sigint].nil?
|
|
548
|
+
result = send_code_to_socket(
|
|
549
|
+
session_info[:socket_path],
|
|
550
|
+
code,
|
|
551
|
+
**send_opts
|
|
552
|
+
)
|
|
552
553
|
|
|
553
554
|
# Log the request and response
|
|
554
555
|
log_session_activity(session_info[:process_pid], code, result)
|
|
@@ -583,6 +584,10 @@ module Consolle
|
|
|
583
584
|
|
|
584
585
|
private
|
|
585
586
|
|
|
587
|
+
def current_rails_env
|
|
588
|
+
ENV['RAILS_ENV'] || 'development'
|
|
589
|
+
end
|
|
590
|
+
|
|
586
591
|
def ensure_rails_project!
|
|
587
592
|
return if File.exist?('config/environment.rb') || File.exist?('config/application.rb')
|
|
588
593
|
|
|
@@ -621,21 +626,27 @@ module Consolle
|
|
|
621
626
|
File.join(Dir.pwd, 'tmp', 'cone', "#{target}.log")
|
|
622
627
|
end
|
|
623
628
|
|
|
624
|
-
def send_code_to_socket(socket_path, code, timeout:
|
|
629
|
+
def send_code_to_socket(socket_path, code, timeout: 60, pre_sigint: nil)
|
|
625
630
|
request_id = SecureRandom.uuid
|
|
626
631
|
# Ensure code is UTF-8 encoded
|
|
627
632
|
code = code.force_encoding('UTF-8') if code.respond_to?(:force_encoding)
|
|
628
633
|
|
|
634
|
+
# CONSOLLE_TIMEOUT takes highest priority on client side if present and > 0
|
|
635
|
+
env_timeout = ENV['CONSOLLE_TIMEOUT']&.to_i
|
|
636
|
+
effective_timeout = (env_timeout && env_timeout > 0) ? env_timeout : timeout
|
|
637
|
+
|
|
629
638
|
request = {
|
|
630
639
|
'action' => 'eval',
|
|
631
640
|
'code' => code,
|
|
632
|
-
'timeout' =>
|
|
641
|
+
'timeout' => effective_timeout,
|
|
633
642
|
'request_id' => request_id
|
|
634
643
|
}
|
|
644
|
+
# Include pre_sigint flag only when explicitly provided (true/false)
|
|
645
|
+
request['pre_sigint'] = pre_sigint unless pre_sigint.nil?
|
|
635
646
|
|
|
636
647
|
STDERR.puts "[DEBUG] Creating socket connection to: #{socket_path}" if ENV['DEBUG']
|
|
637
648
|
|
|
638
|
-
Timeout.timeout(
|
|
649
|
+
Timeout.timeout(effective_timeout + 5) do
|
|
639
650
|
socket = UNIXSocket.new(socket_path)
|
|
640
651
|
STDERR.puts "[DEBUG] Socket connected" if ENV['DEBUG']
|
|
641
652
|
|
|
@@ -680,8 +691,8 @@ module Consolle
|
|
|
680
691
|
JSON.parse(json_line) if json_line
|
|
681
692
|
end
|
|
682
693
|
rescue Timeout::Error
|
|
683
|
-
STDERR.puts "[DEBUG] Timeout occurred after #{
|
|
684
|
-
{ 'success' => false, 'error' => 'Timeout', 'message' => "Request timed out after #{
|
|
694
|
+
STDERR.puts "[DEBUG] Timeout occurred after #{effective_timeout} seconds" if ENV['DEBUG']
|
|
695
|
+
{ 'success' => false, 'error' => 'Timeout', 'message' => "Request timed out after #{effective_timeout} seconds" }
|
|
685
696
|
rescue StandardError => e
|
|
686
697
|
STDERR.puts "[DEBUG] Error: #{e.class}: #{e.message}" if ENV['DEBUG']
|
|
687
698
|
{ 'success' => false, 'error' => e.class.name, 'message' => e.message }
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Consolle
|
|
6
|
+
# Configuration loader for .consolle.yml
|
|
7
|
+
class Config
|
|
8
|
+
CONFIG_FILENAME = '.consolle.yml'
|
|
9
|
+
|
|
10
|
+
# Default prompt pattern that matches various console prompts
|
|
11
|
+
# - Custom sentinel: \u001E\u001F<CONSOLLE>\u001F\u001E
|
|
12
|
+
# - Rails app prompts: app(env)> or app(env):001>
|
|
13
|
+
# - IRB prompts: irb(main):001:0> or irb(main):001>
|
|
14
|
+
# - Generic prompts: >> or >
|
|
15
|
+
DEFAULT_PROMPT_PATTERN = /^[^\w]*(\u001E\u001F<CONSOLLE>\u001F\u001E|\w+[-_]?\w*\([^)]*\)(:\d+)?>|irb\([^)]+\):\d+:?\d*[>*]|>>|>)\s*$/
|
|
16
|
+
|
|
17
|
+
attr_reader :rails_root, :prompt_pattern, :raw_prompt_pattern
|
|
18
|
+
|
|
19
|
+
def initialize(rails_root)
|
|
20
|
+
@rails_root = rails_root
|
|
21
|
+
@config = load_config
|
|
22
|
+
@raw_prompt_pattern = @config['prompt_pattern']
|
|
23
|
+
@prompt_pattern = parse_prompt_pattern(@raw_prompt_pattern)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.load(rails_root)
|
|
27
|
+
new(rails_root)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Check if a custom prompt pattern is configured
|
|
31
|
+
def custom_prompt_pattern?
|
|
32
|
+
!@raw_prompt_pattern.nil?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Get human-readable description of expected prompt patterns
|
|
36
|
+
def prompt_pattern_description
|
|
37
|
+
if custom_prompt_pattern?
|
|
38
|
+
"Custom pattern: #{@raw_prompt_pattern}"
|
|
39
|
+
else
|
|
40
|
+
<<~DESC.strip
|
|
41
|
+
Default patterns:
|
|
42
|
+
- app(env)> or app(env):001> (Rails console)
|
|
43
|
+
- irb(main):001:0> (IRB)
|
|
44
|
+
- >> or > (Generic)
|
|
45
|
+
DESC
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def config_path
|
|
52
|
+
File.join(@rails_root, CONFIG_FILENAME)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def load_config
|
|
56
|
+
return {} unless File.exist?(config_path)
|
|
57
|
+
|
|
58
|
+
begin
|
|
59
|
+
YAML.safe_load(File.read(config_path)) || {}
|
|
60
|
+
rescue Psych::SyntaxError => e
|
|
61
|
+
warn "[Consolle] Warning: Failed to parse #{CONFIG_FILENAME}: #{e.message}"
|
|
62
|
+
{}
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def parse_prompt_pattern(pattern_string)
|
|
67
|
+
return DEFAULT_PROMPT_PATTERN if pattern_string.nil?
|
|
68
|
+
|
|
69
|
+
begin
|
|
70
|
+
Regexp.new(pattern_string)
|
|
71
|
+
rescue RegexpError => e
|
|
72
|
+
warn "[Consolle] Warning: Invalid prompt_pattern '#{pattern_string}': #{e.message}"
|
|
73
|
+
warn "[Consolle] Using default pattern instead."
|
|
74
|
+
DEFAULT_PROMPT_PATTERN
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/consolle/errors.rb
CHANGED
|
@@ -32,6 +32,63 @@ module Consolle
|
|
|
32
32
|
# Execution errors
|
|
33
33
|
class ExecutionError < Error; end
|
|
34
34
|
|
|
35
|
+
# Server/console health issues
|
|
36
|
+
class ServerUnhealthy < Error
|
|
37
|
+
def initialize(message = 'Console server is unhealthy')
|
|
38
|
+
super(message)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Prompt detection failure with diagnostic information
|
|
43
|
+
class PromptDetectionError < Error
|
|
44
|
+
attr_reader :received_output, :expected_patterns, :config_path
|
|
45
|
+
|
|
46
|
+
def initialize(timeout:, received_output:, expected_patterns:, config_path:)
|
|
47
|
+
@received_output = received_output
|
|
48
|
+
@expected_patterns = expected_patterns
|
|
49
|
+
@config_path = config_path
|
|
50
|
+
|
|
51
|
+
super(build_message(timeout))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def build_message(timeout)
|
|
57
|
+
# Extract last line that looks like a prompt (ends with > or similar)
|
|
58
|
+
lines = @received_output.to_s.lines.map(&:strip).reject(&:empty?)
|
|
59
|
+
potential_prompt = lines.reverse.find { |l| l.match?(/[>*]\s*$/) } || lines.last
|
|
60
|
+
|
|
61
|
+
<<~MSG.strip
|
|
62
|
+
Prompt not detected after #{timeout} seconds
|
|
63
|
+
|
|
64
|
+
Received output:
|
|
65
|
+
#{@received_output.to_s.lines.last(5).map { |l| l.strip }.join("\n ")}
|
|
66
|
+
|
|
67
|
+
Potential prompt found:
|
|
68
|
+
#{potential_prompt.inspect}
|
|
69
|
+
|
|
70
|
+
#{@expected_patterns}
|
|
71
|
+
|
|
72
|
+
To fix this, add to #{@config_path}:
|
|
73
|
+
prompt_pattern: '#{escape_for_yaml(potential_prompt)}'
|
|
74
|
+
|
|
75
|
+
Or set environment variable:
|
|
76
|
+
CONSOLLE_PROMPT_PATTERN='#{escape_for_yaml(potential_prompt)}' cone start
|
|
77
|
+
MSG
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def escape_for_yaml(str)
|
|
81
|
+
return '' if str.nil?
|
|
82
|
+
# Escape special regex characters and create a simple pattern
|
|
83
|
+
str.to_s
|
|
84
|
+
.gsub(/\e\[[\d;]*[a-zA-Z]/, '') # Remove ANSI codes
|
|
85
|
+
.gsub(/[\x00-\x1F]/, '') # Remove control characters
|
|
86
|
+
.strip
|
|
87
|
+
.gsub(/(\d+)/, '\d+') # Replace numbers with \d+
|
|
88
|
+
.gsub(/([().\[\]{}|*+?^$\\])/, '\\\\\1') # Escape regex special chars
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
35
92
|
# Syntax error in executed code
|
|
36
93
|
class SyntaxError < ExecutionError
|
|
37
94
|
def initialize(message)
|
|
@@ -72,7 +129,9 @@ module Consolle
|
|
|
72
129
|
'RuntimeError' => 'RUNTIME_ERROR',
|
|
73
130
|
'::RuntimeError' => 'RUNTIME_ERROR',
|
|
74
131
|
'StandardError' => 'STANDARD_ERROR',
|
|
75
|
-
'Exception' => 'EXCEPTION'
|
|
132
|
+
'Exception' => 'EXCEPTION',
|
|
133
|
+
'Consolle::Errors::ServerUnhealthy' => 'SERVER_UNHEALTHY',
|
|
134
|
+
'Consolle::Errors::PromptDetectionError' => 'PROMPT_DETECTION_ERROR'
|
|
76
135
|
}.freeze
|
|
77
136
|
|
|
78
137
|
def self.to_code(exception)
|
|
@@ -115,4 +174,4 @@ module Consolle
|
|
|
115
174
|
end
|
|
116
175
|
end
|
|
117
176
|
end
|
|
118
|
-
end
|
|
177
|
+
end
|
|
@@ -5,6 +5,7 @@ require 'timeout'
|
|
|
5
5
|
require 'fcntl'
|
|
6
6
|
require 'logger'
|
|
7
7
|
require_relative '../constants'
|
|
8
|
+
require_relative '../config'
|
|
8
9
|
require_relative '../errors'
|
|
9
10
|
|
|
10
11
|
# Ruby 3.4.0+ extracts base64 as a default gem
|
|
@@ -17,15 +18,13 @@ $VERBOSE = original_verbose
|
|
|
17
18
|
module Consolle
|
|
18
19
|
module Server
|
|
19
20
|
class ConsoleSupervisor
|
|
20
|
-
attr_reader :pid, :reader, :writer, :rails_root, :rails_env, :logger
|
|
21
|
+
attr_reader :pid, :reader, :writer, :rails_root, :rails_env, :logger, :config
|
|
21
22
|
|
|
22
23
|
RESTART_DELAY = 1 # seconds
|
|
23
24
|
MAX_RESTARTS = 5 # within 5 minutes
|
|
24
25
|
RESTART_WINDOW = 300 # 5 minutes
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
# Allow optional non-word characters before the prompt (e.g., Unicode symbols like ▽)
|
|
28
|
-
PROMPT_PATTERN = /^[^\w]*(\u001E\u001F<CONSOLLE>\u001F\u001E|\w+[-_]?\w*\([^)]*\)>|irb\([^)]+\):\d+:?\d*[>*]|>>|>)\s*$/
|
|
26
|
+
# Legacy constant for backward compatibility - use config.prompt_pattern instead
|
|
27
|
+
PROMPT_PATTERN = Consolle::Config::DEFAULT_PROMPT_PATTERN
|
|
29
28
|
CTRL_C = "\x03"
|
|
30
29
|
|
|
31
30
|
def initialize(rails_root:, rails_env: 'development', logger: nil, command: nil, wait_timeout: nil)
|
|
@@ -34,6 +33,7 @@ module Consolle
|
|
|
34
33
|
@command = command || 'bin/rails console'
|
|
35
34
|
@logger = logger || Logger.new(STDOUT)
|
|
36
35
|
@wait_timeout = wait_timeout || Consolle::DEFAULT_WAIT_TIMEOUT
|
|
36
|
+
@config = Consolle::Config.load(rails_root)
|
|
37
37
|
@pid = nil
|
|
38
38
|
@reader = nil
|
|
39
39
|
@writer = nil
|
|
@@ -47,10 +47,14 @@ module Consolle
|
|
|
47
47
|
start_watchdog
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
def eval(code, timeout: nil)
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
timeout
|
|
50
|
+
def eval(code, timeout: nil, pre_sigint: nil)
|
|
51
|
+
# CONSOLLE_TIMEOUT has highest priority if present and > 0
|
|
52
|
+
env_timeout = ENV['CONSOLLE_TIMEOUT']&.to_i
|
|
53
|
+
timeout = if env_timeout && env_timeout > 0
|
|
54
|
+
env_timeout
|
|
55
|
+
else
|
|
56
|
+
timeout || 60
|
|
57
|
+
end
|
|
54
58
|
@mutex.synchronize do
|
|
55
59
|
raise 'Console is not running' unless running?
|
|
56
60
|
|
|
@@ -60,22 +64,40 @@ module Consolle
|
|
|
60
64
|
# Check if this is a remote console
|
|
61
65
|
is_remote = @command.include?('ssh') || @command.include?('kamal') || @command.include?('docker')
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
# Decide whether to send pre-exec Ctrl-C
|
|
68
|
+
# Default: enabled for all consoles to avoid getting stuck mid-input.
|
|
69
|
+
# Opt-out via param or ENV `CONSOLLE_DISABLE_PRE_SIGINT=1`.
|
|
70
|
+
disable_pre_sigint = ENV['CONSOLLE_DISABLE_PRE_SIGINT'] == '1'
|
|
71
|
+
default_pre_sigint = (@rails_env != 'test')
|
|
72
|
+
do_pre_sigint = if pre_sigint.nil?
|
|
73
|
+
default_pre_sigint && !disable_pre_sigint
|
|
74
|
+
else
|
|
75
|
+
pre_sigint
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if do_pre_sigint
|
|
79
|
+
# Send Ctrl-C to ensure clean state before execution, then wait up to 3s for prompt.
|
|
80
|
+
# If prompt doesn't come back, consider server unhealthy, force-restart the subprocess,
|
|
81
|
+
# and return an error so the caller can retry after recovery.
|
|
65
82
|
@writer.write(CTRL_C)
|
|
66
83
|
@writer.flush
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# Wait for prompt after Ctrl-C
|
|
84
|
+
# Nudge IRB and force prompt emission with a trivial probe
|
|
85
|
+
@writer.puts "puts '__consolle_probe__'"
|
|
86
|
+
@writer.flush
|
|
72
87
|
begin
|
|
73
|
-
wait_for_prompt(timeout:
|
|
88
|
+
wait_for_prompt(timeout: 3, consume_all: true)
|
|
74
89
|
rescue Timeout::Error
|
|
75
|
-
|
|
90
|
+
logger.error '[ConsoleSupervisor] No prompt after pre-exec Ctrl-C (3s). Forcing console restart.'
|
|
91
|
+
# Forcefully stop subprocess so watchdog can restart
|
|
92
|
+
@process_mutex.synchronize do
|
|
93
|
+
stop_console
|
|
94
|
+
end
|
|
95
|
+
# Return an unhealthy error to the caller
|
|
96
|
+
err = Consolle::Errors::ServerUnhealthy.new('No prompt after pre-exec interrupt (3s); console restarted')
|
|
97
|
+
return build_error_response(err, execution_time: 0)
|
|
76
98
|
end
|
|
77
99
|
else
|
|
78
|
-
# For local consoles,
|
|
100
|
+
# For local consoles without pre-sigint, clear buffer only
|
|
79
101
|
clear_buffer
|
|
80
102
|
end
|
|
81
103
|
|
|
@@ -164,10 +186,28 @@ module Consolle
|
|
|
164
186
|
if Time.now > deadline
|
|
165
187
|
logger.debug "[ConsoleSupervisor] Timeout reached after #{Time.now - start_time}s, output so far: #{output.bytesize} bytes"
|
|
166
188
|
logger.debug "[ConsoleSupervisor] Output content: #{output.inspect}" if ENV['DEBUG']
|
|
167
|
-
# Timeout -
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
# Timeout - try to interrupt current execution and recover prompt
|
|
190
|
+
3.times do |i|
|
|
191
|
+
@writer.write(CTRL_C)
|
|
192
|
+
@writer.flush
|
|
193
|
+
logger.debug "[ConsoleSupervisor] Sent Ctrl-C (attempt #{i + 1})"
|
|
194
|
+
begin
|
|
195
|
+
wait_for_prompt(timeout: 1.0, consume_all: true)
|
|
196
|
+
logger.debug '[ConsoleSupervisor] Prompt recovered after Ctrl-C'
|
|
197
|
+
break
|
|
198
|
+
rescue Timeout::Error
|
|
199
|
+
# As a fallback for local consoles, send OS-level SIGINT to the subprocess
|
|
200
|
+
unless is_remote
|
|
201
|
+
begin
|
|
202
|
+
Process.kill('INT', @pid)
|
|
203
|
+
logger.warn '[ConsoleSupervisor] Sent OS-level SIGINT to subprocess'
|
|
204
|
+
rescue StandardError => e
|
|
205
|
+
logger.warn "[ConsoleSupervisor] Failed to send OS-level SIGINT: #{e.message}"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
# Final cleanup
|
|
171
211
|
clear_buffer
|
|
172
212
|
execution_time = Time.now - start_time
|
|
173
213
|
return build_timeout_response(timeout)
|
|
@@ -187,7 +227,7 @@ module Consolle
|
|
|
187
227
|
|
|
188
228
|
# Check if we got prompt back
|
|
189
229
|
clean = strip_ansi(output)
|
|
190
|
-
if clean.match?(
|
|
230
|
+
if clean.match?(prompt_pattern)
|
|
191
231
|
# Wait a bit for any trailing output
|
|
192
232
|
sleep 0.1
|
|
193
233
|
begin
|
|
@@ -260,6 +300,11 @@ module Consolle
|
|
|
260
300
|
end
|
|
261
301
|
end
|
|
262
302
|
|
|
303
|
+
# Returns the prompt pattern to use (custom from config or default)
|
|
304
|
+
def prompt_pattern
|
|
305
|
+
@config&.prompt_pattern || Consolle::Config::DEFAULT_PROMPT_PATTERN
|
|
306
|
+
end
|
|
307
|
+
|
|
263
308
|
def stop
|
|
264
309
|
@running = false
|
|
265
310
|
|
|
@@ -491,7 +536,16 @@ module Consolle
|
|
|
491
536
|
logger.error "[ConsoleSupervisor] Timeout reached. Current: #{current_time}, Deadline: #{deadline}, Elapsed: #{current_time - start_time}s"
|
|
492
537
|
logger.error "[ConsoleSupervisor] Output so far: #{output.inspect}"
|
|
493
538
|
logger.error "[ConsoleSupervisor] Stripped: #{strip_ansi(output).inspect}"
|
|
494
|
-
|
|
539
|
+
|
|
540
|
+
# Raise PromptDetectionError with diagnostic information
|
|
541
|
+
config_path = File.join(@rails_root || '.', Consolle::Config::CONFIG_FILENAME)
|
|
542
|
+
expected_desc = @config&.prompt_pattern_description || "Default prompt patterns"
|
|
543
|
+
raise Consolle::Errors::PromptDetectionError.new(
|
|
544
|
+
timeout: timeout,
|
|
545
|
+
received_output: strip_ansi(output),
|
|
546
|
+
expected_patterns: expected_desc,
|
|
547
|
+
config_path: config_path
|
|
548
|
+
)
|
|
495
549
|
end
|
|
496
550
|
|
|
497
551
|
# If we found prompt and consume_all is true, continue reading for a bit more
|
|
@@ -520,7 +574,7 @@ module Consolle
|
|
|
520
574
|
clean = strip_ansi(output)
|
|
521
575
|
# Check each line for prompt pattern
|
|
522
576
|
clean.lines.each do |line|
|
|
523
|
-
if line.match?(
|
|
577
|
+
if line.match?(prompt_pattern)
|
|
524
578
|
logger.info '[ConsoleSupervisor] Found prompt!'
|
|
525
579
|
prompt_found = true
|
|
526
580
|
end
|
|
@@ -607,7 +661,7 @@ module Consolle
|
|
|
607
661
|
# Wait for prompt after configuration with reasonable timeout
|
|
608
662
|
begin
|
|
609
663
|
wait_for_prompt(timeout: 2, consume_all: false)
|
|
610
|
-
rescue Timeout::Error
|
|
664
|
+
rescue Timeout::Error, Consolle::Errors::PromptDetectionError
|
|
611
665
|
# This can fail with some console types, but that's okay
|
|
612
666
|
logger.debug '[ConsoleSupervisor] No prompt after IRB configuration, continuing'
|
|
613
667
|
end
|
|
@@ -646,7 +700,7 @@ module Consolle
|
|
|
646
700
|
end
|
|
647
701
|
|
|
648
702
|
# Skip prompts (but not return values that start with =>)
|
|
649
|
-
next if line.match?(
|
|
703
|
+
next if line.match?(prompt_pattern) && !line.start_with?('=>')
|
|
650
704
|
|
|
651
705
|
# Skip common IRB configuration output patterns
|
|
652
706
|
if line.match?(/^(IRB\.conf|DISABLE_PRY_RAILS|Switch to inspect mode|Loading .*\.rb|nil)$/) ||
|
|
@@ -62,8 +62,10 @@ module Consolle
|
|
|
62
62
|
|
|
63
63
|
# Wait for response (with timeout)
|
|
64
64
|
begin
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
env_timeout = ENV['CONSOLLE_TIMEOUT']&.to_i
|
|
66
|
+
future_timeout = (env_timeout && env_timeout > 0) ? env_timeout : (request['timeout'] || 30)
|
|
67
|
+
logger.debug "[RequestBroker] Waiting for response: #{request_id}, timeout: #{future_timeout}" if ENV['DEBUG']
|
|
68
|
+
response = future.get(timeout: future_timeout)
|
|
67
69
|
logger.debug "[RequestBroker] Got response: #{request_id}" if ENV['DEBUG']
|
|
68
70
|
response
|
|
69
71
|
rescue Timeout::Error
|
|
@@ -157,7 +159,8 @@ module Consolle
|
|
|
157
159
|
|
|
158
160
|
def process_eval_request(request)
|
|
159
161
|
code = request['code']
|
|
160
|
-
|
|
162
|
+
env_timeout = ENV['CONSOLLE_TIMEOUT']&.to_i
|
|
163
|
+
timeout = (env_timeout && env_timeout > 0) ? env_timeout : (request['timeout'] || 30)
|
|
161
164
|
|
|
162
165
|
unless code
|
|
163
166
|
return {
|
|
@@ -168,7 +171,11 @@ module Consolle
|
|
|
168
171
|
end
|
|
169
172
|
|
|
170
173
|
# Execute through supervisor
|
|
171
|
-
|
|
174
|
+
if request.key?('pre_sigint')
|
|
175
|
+
result = @supervisor.eval(code, timeout: timeout, pre_sigint: request['pre_sigint'])
|
|
176
|
+
else
|
|
177
|
+
result = @supervisor.eval(code, timeout: timeout)
|
|
178
|
+
end
|
|
172
179
|
|
|
173
180
|
# Format response
|
|
174
181
|
if result[:success]
|
data/lib/consolle.rb
CHANGED
data/rule.ko.md
CHANGED
|
@@ -20,11 +20,11 @@ Cone은 디버깅, 데이터 탐색, 그리고 개발 보조 도구로 사용됩
|
|
|
20
20
|
|
|
21
21
|
## Cone 서버 시작과 중지
|
|
22
22
|
|
|
23
|
-
`start` 명령어로 cone을 시작할 수
|
|
23
|
+
`start` 명령어로 cone을 시작할 수 있습니다. 실행 환경 지정은 `RAILS_ENV` 환경변수를 사용합니다.
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
$ cone start # 서버 시작
|
|
27
|
-
$ cone start
|
|
26
|
+
$ cone start # 서버 시작 (RAILS_ENV가 없으면 development)
|
|
27
|
+
$ RAILS_ENV=test cone start # test 환경에서 console 시작
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
중지와 재시작 명령어도 제공합니다.
|
|
@@ -114,4 +114,35 @@ users.first
|
|
|
114
114
|
$ cone exec -f complex_task.rb
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
모든 방법은 세션 상태를 유지하므로 변수와 객체가 실행 간에 지속됩니다.
|
|
117
|
+
모든 방법은 세션 상태를 유지하므로 변수와 객체가 실행 간에 지속됩니다.
|
|
118
|
+
|
|
119
|
+
## 실행 안전장치 & 타임아웃
|
|
120
|
+
|
|
121
|
+
- 기본 타임아웃: 60초
|
|
122
|
+
- 타임아웃 우선순위: `CONSOLLE_TIMEOUT`(설정되고 0보다 클 때) > CLI `--timeout` > 기본값(60초)
|
|
123
|
+
- 사전 Ctrl‑C(프롬프트 분리):
|
|
124
|
+
- 매 `exec` 전에 Ctrl‑C를 보내고 IRB 프롬프트를 최대 3초 대기해 깨끗한 상태를 보장합니다.
|
|
125
|
+
- 3초 내 프롬프트가 돌아오지 않으면 콘솔 하위 프로세스를 재시작하고 요청은 `SERVER_UNHEALTHY`로 실패합니다.
|
|
126
|
+
- 서버 전역 비활성화: `CONSOLLE_DISABLE_PRE_SIGINT=1 cone start`
|
|
127
|
+
- 호출 단위 제어: `--pre-sigint` / `--no-pre-sigint`
|
|
128
|
+
|
|
129
|
+
### 예시
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# CLI로 타임아웃 지정(환경변수 미설정 시 유효)
|
|
133
|
+
cone exec 'heavy_task' --timeout 120
|
|
134
|
+
|
|
135
|
+
# 최우선 타임아웃(클라이언트·서버 모두 적용)
|
|
136
|
+
CONSOLLE_TIMEOUT=90 cone exec 'heavy_task'
|
|
137
|
+
|
|
138
|
+
# 타임아웃 이후 복구 확인
|
|
139
|
+
cone exec 'sleep 999' --timeout 2 # -> EXECUTION_TIMEOUT로 실패
|
|
140
|
+
cone exec "puts :after_timeout; :ok" # -> 정상 동작(프롬프트 복구)
|
|
141
|
+
|
|
142
|
+
# 호출 단위로 사전 Ctrl‑C 비활성화
|
|
143
|
+
cone exec --no-pre-sigint 'code'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 에러 코드
|
|
147
|
+
- `EXECUTION_TIMEOUT`: 실행한 코드가 타임아웃을 초과함
|
|
148
|
+
- `SERVER_UNHEALTHY`: 사전 프롬프트 확인(3초) 실패로 콘솔 재시작, 요청 실패
|
data/rule.md
CHANGED
|
@@ -20,11 +20,11 @@ Existing objects also reference old code, so you need to create new ones to use
|
|
|
20
20
|
|
|
21
21
|
## Starting and Stopping Cone Server
|
|
22
22
|
|
|
23
|
-
You can start cone with the `start` command
|
|
23
|
+
You can start cone with the `start` command. To select the Rails environment, set the `RAILS_ENV` environment variable.
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
$ cone start # Start server
|
|
27
|
-
$ cone start
|
|
26
|
+
$ cone start # Start server (uses RAILS_ENV or defaults to development)
|
|
27
|
+
$ RAILS_ENV=test cone start # Start console in test environment
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
It also provides stop and restart commands.
|
|
@@ -114,4 +114,35 @@ For complex multi-line code, save it in a file:
|
|
|
114
114
|
$ cone exec -f complex_task.rb
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
All methods maintain the session state, so variables and objects persist between executions.
|
|
117
|
+
All methods maintain the session state, so variables and objects persist between executions.
|
|
118
|
+
|
|
119
|
+
## Execution Safety & Timeouts
|
|
120
|
+
|
|
121
|
+
- Default timeout: 60s
|
|
122
|
+
- Timeout precedence: `CONSOLLE_TIMEOUT` (if set and > 0) > CLI `--timeout` > default (60s)
|
|
123
|
+
- Pre-exec Ctrl-C (prompt separation):
|
|
124
|
+
- Before each `exec`, cone sends Ctrl-C and waits up to 3 seconds for the IRB prompt to ensure a clean state.
|
|
125
|
+
- If the prompt does not return in 3 seconds, the console subprocess is restarted and the request fails with `SERVER_UNHEALTHY`.
|
|
126
|
+
- Disable globally for the server: `CONSOLLE_DISABLE_PRE_SIGINT=1 cone start`
|
|
127
|
+
- Per-call control: `--pre-sigint` / `--no-pre-sigint`
|
|
128
|
+
|
|
129
|
+
### Examples
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Set timeout via CLI (fallback when CONSOLLE_TIMEOUT is not set)
|
|
133
|
+
cone exec 'heavy_task' --timeout 120
|
|
134
|
+
|
|
135
|
+
# Highest priority timeout (applies on client and server)
|
|
136
|
+
CONSOLLE_TIMEOUT=90 cone exec 'heavy_task'
|
|
137
|
+
|
|
138
|
+
# Verify recovery after a timeout
|
|
139
|
+
cone exec 'sleep 999' --timeout 2 # -> fails with EXECUTION_TIMEOUT
|
|
140
|
+
cone exec "puts :after_timeout; :ok" # -> should succeed (prompt recovered)
|
|
141
|
+
|
|
142
|
+
# Disable pre-exec Ctrl-C for a single call
|
|
143
|
+
cone exec --no-pre-sigint 'code'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Error Codes
|
|
147
|
+
- `EXECUTION_TIMEOUT`: The executed code exceeded its timeout.
|
|
148
|
+
- `SERVER_UNHEALTHY`: The pre-exec prompt did not return within 3 seconds; the console subprocess was restarted and the request failed.
|
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.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- nacyot
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-12-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: logger
|
|
@@ -79,6 +79,7 @@ files:
|
|
|
79
79
|
- lib/consolle.rb
|
|
80
80
|
- lib/consolle/adapters/rails_console.rb
|
|
81
81
|
- lib/consolle/cli.rb
|
|
82
|
+
- lib/consolle/config.rb
|
|
82
83
|
- lib/consolle/constants.rb
|
|
83
84
|
- lib/consolle/errors.rb
|
|
84
85
|
- lib/consolle/server/console_socket_server.rb
|