consolle 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "securerandom"
5
+ require "logger"
6
+
7
+ module Consolle
8
+ module Server
9
+ class RequestBroker
10
+ attr_reader :supervisor, :logger
11
+
12
+ def initialize(supervisor:, logger: nil)
13
+ @supervisor = supervisor
14
+ @logger = logger || Logger.new(STDOUT)
15
+ @queue = Queue.new
16
+ @running = false
17
+ @worker_thread = nil
18
+ @request_map = {}
19
+ @mutex = Mutex.new
20
+ end
21
+
22
+ def start
23
+ return if @running
24
+
25
+ @running = true
26
+ @worker_thread = start_worker
27
+ logger.info "[RequestBroker] Started"
28
+ end
29
+
30
+ def stop
31
+ return unless @running
32
+
33
+ @running = false
34
+
35
+ # Push poison pill to wake up worker
36
+ @queue.push(nil)
37
+
38
+ # Wait for worker to finish
39
+ @worker_thread&.join(5)
40
+
41
+ logger.info "[RequestBroker] Stopped"
42
+ end
43
+
44
+ def process_request(request)
45
+ request_id = request["request_id"] || SecureRandom.uuid
46
+
47
+ # Create future for response
48
+ future = RequestFuture.new
49
+
50
+ # Store in map
51
+ @mutex.synchronize do
52
+ @request_map[request_id] = future
53
+ end
54
+
55
+ # Queue request
56
+ @queue.push({
57
+ id: request_id,
58
+ request: request,
59
+ timestamp: Time.now
60
+ })
61
+
62
+ # Wait for response (with timeout)
63
+ begin
64
+ future.get(timeout: request["timeout"] || 30)
65
+ rescue Timeout::Error
66
+ {
67
+ "success" => false,
68
+ "error" => "RequestTimeout",
69
+ "message" => "Request timed out",
70
+ "request_id" => request_id
71
+ }
72
+ ensure
73
+ # Clean up
74
+ @mutex.synchronize do
75
+ @request_map.delete(request_id)
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def start_worker
83
+ Thread.new do
84
+ logger.info "[RequestBroker] Worker thread started"
85
+
86
+ while @running
87
+ begin
88
+ # Get next request
89
+ item = @queue.pop
90
+
91
+ # Check for poison pill
92
+ break if item.nil? && !@running
93
+ next if item.nil?
94
+
95
+ # Process request
96
+ process_item(item)
97
+ rescue StandardError => e
98
+ logger.error "[RequestBroker] Worker error: #{e.message}"
99
+ logger.error e.backtrace.join("\n")
100
+ end
101
+ end
102
+
103
+ logger.info "[RequestBroker] Worker thread stopped"
104
+ end
105
+ end
106
+
107
+ def process_item(item)
108
+ request_id = item[:id]
109
+ request = item[:request]
110
+
111
+ logger.debug "[RequestBroker] Processing request: #{request_id}"
112
+
113
+ # Get future
114
+ future = @mutex.synchronize { @request_map[request_id] }
115
+ return unless future
116
+
117
+ # Process based on request type
118
+ response = case request["action"]
119
+ when "eval", "exec"
120
+ process_eval_request(request)
121
+ when "status"
122
+ process_status_request
123
+ when "restart"
124
+ process_restart_request
125
+ else
126
+ {
127
+ "success" => false,
128
+ "error" => "UnknownAction",
129
+ "message" => "Unknown action: #{request["action"]}"
130
+ }
131
+ end
132
+
133
+ # Add request_id to response
134
+ response["request_id"] = request_id
135
+
136
+ # Set future result
137
+ future.set(response)
138
+
139
+ logger.debug "[RequestBroker] Request completed: #{request_id}"
140
+ rescue StandardError => e
141
+ logger.error "[RequestBroker] Error processing request #{request_id}: #{e.message}"
142
+
143
+ error_response = {
144
+ "success" => false,
145
+ "error" => e.class.name,
146
+ "message" => e.message,
147
+ "request_id" => request_id
148
+ }
149
+
150
+ future&.set(error_response)
151
+ end
152
+
153
+ def process_eval_request(request)
154
+ code = request["code"]
155
+ timeout = request["timeout"] || 30
156
+
157
+ unless code
158
+ return {
159
+ "success" => false,
160
+ "error" => "MissingParameter",
161
+ "message" => "Missing required parameter: code"
162
+ }
163
+ end
164
+
165
+ # Execute through supervisor
166
+ result = @supervisor.eval(code, timeout: timeout)
167
+
168
+ # Format response
169
+ if result[:success]
170
+ {
171
+ "success" => true,
172
+ "result" => result[:output],
173
+ "execution_time" => result[:execution_time]
174
+ }
175
+ else
176
+ {
177
+ "success" => false,
178
+ "error" => "ExecutionError",
179
+ "message" => result[:output]
180
+ }
181
+ end
182
+ end
183
+
184
+ def process_status_request
185
+ {
186
+ "success" => true,
187
+ "running" => @supervisor.running?,
188
+ "pid" => @supervisor.pid,
189
+ "rails_root" => @supervisor.rails_root,
190
+ "rails_env" => @supervisor.rails_env
191
+ }
192
+ end
193
+
194
+ def process_restart_request
195
+ begin
196
+ # Restart the Rails console subprocess
197
+ new_pid = @supervisor.restart
198
+
199
+ {
200
+ "success" => true,
201
+ "message" => "Rails console subprocess restarted",
202
+ "pid" => new_pid,
203
+ "rails_root" => @supervisor.rails_root,
204
+ "rails_env" => @supervisor.rails_env
205
+ }
206
+ rescue StandardError => e
207
+ logger.error "[RequestBroker] Restart failed: #{e.message}"
208
+ logger.error e.backtrace.join("\n")
209
+
210
+ {
211
+ "success" => false,
212
+ "error" => "RestartFailed",
213
+ "message" => "Failed to restart Rails console: #{e.message}"
214
+ }
215
+ end
216
+ end
217
+
218
+ # Simple future implementation
219
+ class RequestFuture
220
+ def initialize
221
+ @mutex = Mutex.new
222
+ @condition = ConditionVariable.new
223
+ @value = nil
224
+ @set = false
225
+ end
226
+
227
+ def set(value)
228
+ @mutex.synchronize do
229
+ @value = value
230
+ @set = true
231
+ @condition.signal
232
+ end
233
+ end
234
+
235
+ def get(timeout: nil)
236
+ @mutex.synchronize do
237
+ unless @set
238
+ @condition.wait(@mutex, timeout)
239
+ raise Timeout::Error, "Future timed out" unless @set
240
+ end
241
+ @value
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Consolle
4
+ VERSION = File.read(File.expand_path("../../../.version", __FILE__)).strip
5
+ end
data/lib/consolle.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "consolle/version"
4
+ require_relative "consolle/cli"
5
+
6
+ # Server components
7
+ require_relative "consolle/server/console_socket_server"
8
+ require_relative "consolle/server/console_supervisor"
9
+ require_relative "consolle/server/request_broker"
10
+
11
+ module Consolle
12
+ class Error < StandardError; end
13
+ end
data/mise/release.sh ADDED
@@ -0,0 +1,120 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ # Set default version increment if not provided
6
+ VERSION_INCREMENT="${1:-0.0.1}"
7
+
8
+ # Validate version increment format
9
+ if [[ ! "$VERSION_INCREMENT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
10
+ echo "Error: Version increment must be in format X.Y.Z (e.g., 0.0.1)"
11
+ exit 1
12
+ fi
13
+
14
+ # Change to the project root directory
15
+ cd "$(dirname "$0")/.."
16
+
17
+ # Check if git repository is clean
18
+ if [ -n "$(git status --porcelain)" ]; then
19
+ echo "Error: Git repository is not clean. Please commit or stash your changes."
20
+ git status --short
21
+ exit 1
22
+ fi
23
+
24
+ # Clean up old gem files
25
+ echo "Cleaning up old gem files..."
26
+ rm -f consolle-*.gem
27
+
28
+ # Read current version from .version file
29
+ if [ ! -f .version ]; then
30
+ echo "Error: .version file not found"
31
+ exit 1
32
+ fi
33
+
34
+ CURRENT_VERSION=$(cat .version)
35
+ echo "Current version: $CURRENT_VERSION"
36
+
37
+ # Parse version components
38
+ IFS='.' read -ra CURRENT_PARTS <<< "$CURRENT_VERSION"
39
+ IFS='.' read -ra INCREMENT_PARTS <<< "$VERSION_INCREMENT"
40
+
41
+ # Validate current version format
42
+ if [ ${#CURRENT_PARTS[@]} -ne 3 ]; then
43
+ echo "Error: Current version must be in format X.Y.Z"
44
+ exit 1
45
+ fi
46
+
47
+ # Calculate new version
48
+ MAJOR=$((CURRENT_PARTS[0] + INCREMENT_PARTS[0]))
49
+ MINOR=$((CURRENT_PARTS[1] + INCREMENT_PARTS[1]))
50
+ PATCH=$((CURRENT_PARTS[2] + INCREMENT_PARTS[2]))
51
+
52
+ # Handle carry-over
53
+ if [ $PATCH -ge 10 ]; then
54
+ MINOR=$((MINOR + PATCH / 10))
55
+ PATCH=$((PATCH % 10))
56
+ fi
57
+
58
+ if [ $MINOR -ge 10 ]; then
59
+ MAJOR=$((MAJOR + MINOR / 10))
60
+ MINOR=$((MINOR % 10))
61
+ fi
62
+
63
+ NEW_VERSION="$MAJOR.$MINOR.$PATCH"
64
+ echo "New version: $NEW_VERSION"
65
+
66
+ # Update .version file FIRST
67
+ echo "$NEW_VERSION" > .version
68
+
69
+ # Update Gemfile.lock with new version
70
+ echo "Updating Gemfile.lock..."
71
+ bundle install
72
+
73
+ # Build the gem with new version
74
+ echo "Building gem with new version..."
75
+ if gem build consolle.gemspec; then
76
+ echo "Gem built successfully: consolle-$NEW_VERSION.gem"
77
+
78
+ # Git operations
79
+ echo "Committing version bump..."
80
+ git add .version Gemfile.lock
81
+ git commit -m "Bump version to $NEW_VERSION"
82
+
83
+ echo "Creating git tag..."
84
+ git tag -a "v$NEW_VERSION" -m "Release version $NEW_VERSION"
85
+
86
+ echo "Pushing to remote..."
87
+ git push origin
88
+ git push origin "v$NEW_VERSION"
89
+
90
+ # Ask about publishing to RubyGems.org
91
+ echo ""
92
+ read -p "Publish to RubyGems.org? (y/N): " publish_confirm
93
+
94
+ if [[ "$publish_confirm" =~ ^[Yy]$ ]]; then
95
+ echo "Publishing to RubyGems.org..."
96
+ if gem push consolle-$NEW_VERSION.gem; then
97
+ echo "✓ Gem published successfully to RubyGems.org"
98
+ echo ""
99
+ echo "View your gem at: https://rubygems.org/gems/consolle"
100
+ else
101
+ echo "Error: RubyGems.org push failed"
102
+ echo "You can manually publish with:"
103
+ echo " gem push consolle-$NEW_VERSION.gem"
104
+ exit 1
105
+ fi
106
+ else
107
+ echo "Skipping RubyGems.org publishing."
108
+ echo "You can manually publish later with:"
109
+ echo " gem push consolle-$NEW_VERSION.gem"
110
+ fi
111
+
112
+ echo "Release complete!"
113
+ echo "Version $NEW_VERSION has been released."
114
+ else
115
+ echo "Error: Gem build failed with new version"
116
+ # Revert version change
117
+ echo "$CURRENT_VERSION" > .version
118
+ bundle install # Revert Gemfile.lock
119
+ exit 1
120
+ fi
data/rule.ko.md ADDED
@@ -0,0 +1,117 @@
1
+ # Cone 명령어 가이드
2
+
3
+ consolle는 Rails Console을 서버로 제공하는 래퍼인 `cone` 명령어를 제공합니다.
4
+
5
+ `cone` 명령어를 사용하여 Rails console 세션을 시작하고 Rails에서 코드를 실행할 수 있습니다.
6
+
7
+ Rails console과 마찬가지로 세션 내에서 실행한 결과는 유지되며, 명시적으로 종료해야만 사라집니다.
8
+
9
+ 사용 전에는 `status`로 상태를 확인하고, 작업 종료 후에는 `stop`해야 합니다.
10
+
11
+ ## Cone의 용도
12
+
13
+ Cone은 디버깅, 데이터 탐색, 그리고 개발 보조 도구로 사용됩니다.
14
+
15
+ 개발 보조 도구로 사용할 때는 수정한 코드의 반영 여부를 항상 의식해야 합니다.
16
+
17
+ 코드를 수정한 경우 서버를 재시작하거나 `reload!`를 사용해야 최신 코드가 반영됩니다.
18
+
19
+ 기존 객체 역시 이전 코드를 참조하므로, 새롭게 만들어야 최신 코드를 사용합니다.
20
+
21
+ ## Cone 서버 시작과 중지
22
+
23
+ `start` 명령어로 cone을 시작할 수 있으며, `-e`로 실행 환경을 지정할 수 있습니다.
24
+
25
+ ```bash
26
+ $ cone start # 서버 시작
27
+ $ cone start -e test # test 환경에서 console 시작
28
+ ```
29
+
30
+ 중지와 재시작 명령어도 제공합니다.
31
+
32
+ Cone은 한 번에 하나의 세션만 제공하며, 실행 환경을 변경하려면 반드시 중지 후 재시작해야 합니다.
33
+
34
+ ```bash
35
+ $ cone stop # 서버 중지
36
+ ```
37
+
38
+ 작업을 마치면 반드시 종료해 주세요.
39
+
40
+ ## Cone 서버 상태 확인
41
+
42
+ ```bash
43
+ $ cone status
44
+ ✓ Rails console is running
45
+ PID: 36384
46
+ Environment: test
47
+ Session: /Users/ben/syncthing/workspace/karrot-inhouse/ehr/tmp/cone/cone.socket
48
+ Ready for input: Yes
49
+ ```
50
+
51
+ ## 코드 실행
52
+
53
+ 코드를 평가하고 출력한 결과가 반환됩니다. 평가 결과는 `=> ` 접두사와 함께 출력됩니다.
54
+
55
+ ```bash
56
+ $ cone exec 'User.count'
57
+ => 1
58
+ ```
59
+
60
+ 변수를 사용하는 예제 (세션이 유지됩니다):
61
+
62
+ ```bash
63
+ $ cone exec 'u = User.last'
64
+ => #<User id: 1, email: "user@example.com", created_at: "2025-07-17 15:16:34.685972000 +0900", updated_at: "2025-07-17 15:16:34.685972000 +0900">
65
+
66
+ $ cone exec 'puts u'
67
+ #<User:0x00000001104bbd18>
68
+ => nil
69
+ ```
70
+
71
+ `-f` 옵션을 사용하여 Ruby 파일을 직접 실행할 수도 있습니다. Rails Runner와 달리 IRB 세션에서 실행됩니다.
72
+
73
+ ```bash
74
+ $ cone exec -f example.rb
75
+ ```
76
+
77
+ 디버깅을 위한 `-v` 옵션(Verbose 출력)이 제공됩니다.
78
+
79
+ ```bash
80
+ $ cone exec -v 'puts "hello, world"'
81
+ ```
82
+
83
+ ## 코드 입력 모범 사례
84
+
85
+ ### 홑따옴표 사용 (강력 권장)
86
+
87
+ `cone exec`에 코드를 전달할 때는 **항상 홑따옴표를 사용하세요**. 이는 모든 cone 사용자에게 권장되는 방법입니다:
88
+
89
+ ```bash
90
+ $ cone exec 'User.where(active: true).count'
91
+ $ cone exec 'puts "Hello, world!"'
92
+ ```
93
+
94
+ ### --raw 옵션 사용
95
+
96
+ **Claude Code 사용자 주의: --raw 옵션을 사용하지 마세요.** 이 옵션은 Claude Code 환경에서는 필요하지 않습니다.
97
+
98
+ ### 멀티라인 코드 지원
99
+
100
+ Cone은 멀티라인 코드 실행을 완벽하게 지원합니다. 멀티라인 코드를 실행하는 방법은 여러 가지가 있습니다:
101
+
102
+ #### 방법 1: 홑따옴표를 사용한 멀티라인 문자열
103
+ ```bash
104
+ $ cone exec '
105
+ users = User.active
106
+ puts "Active users: #{users.count}"
107
+ users.first
108
+ '
109
+ ```
110
+
111
+ #### 방법 2: 파일 사용
112
+ 복잡한 멀티라인 코드는 파일에 저장하세요:
113
+ ```bash
114
+ $ cone exec -f complex_task.rb
115
+ ```
116
+
117
+ 모든 방법은 세션 상태를 유지하므로 변수와 객체가 실행 간에 지속됩니다.
data/rule.md ADDED
@@ -0,0 +1,117 @@
1
+ # Cone Command Guide
2
+
3
+ consolle provides the `cone` command, a wrapper that serves Rails Console as a server.
4
+
5
+ Using the `cone` command, you can start a Rails console session and execute code in Rails.
6
+
7
+ Similar to Rails console, results executed within the session are maintained and only disappear when explicitly terminated.
8
+
9
+ Before use, check the status with `status`, and after finishing work, you should `stop` it.
10
+
11
+ ## Purpose of Cone
12
+
13
+ Cone is used for debugging, data exploration, and as a development assistant tool.
14
+
15
+ When using it as a development assistant tool, you must always be aware of whether modified code has been reflected.
16
+
17
+ When code is modified, you need to restart the server or use `reload!` to reflect the latest code.
18
+
19
+ Existing objects also reference old code, so you need to create new ones to use the latest code.
20
+
21
+ ## Starting and Stopping Cone Server
22
+
23
+ You can start cone with the `start` command and specify the execution environment with `-e`.
24
+
25
+ ```bash
26
+ $ cone start # Start server
27
+ $ cone start -e test # Start console in test environment
28
+ ```
29
+
30
+ It also provides stop and restart commands.
31
+
32
+ Cone provides only one session at a time, and to change the execution environment, you must stop and restart.
33
+
34
+ ```bash
35
+ $ cone stop # Stop server
36
+ ```
37
+
38
+ Always terminate when you finish your work.
39
+
40
+ ## Checking Cone Server Status
41
+
42
+ ```bash
43
+ $ cone status
44
+ ✓ Rails console is running
45
+ PID: 36384
46
+ Environment: test
47
+ Session: /Users/ben/syncthing/workspace/karrot-inhouse/ehr/tmp/cone/cone.socket
48
+ Ready for input: Yes
49
+ ```
50
+
51
+ ## Executing Code
52
+
53
+ The evaluated and output results of code are returned. The evaluation result is output with the `=> ` prefix.
54
+
55
+ ```bash
56
+ $ cone exec 'User.count'
57
+ => 1
58
+ ```
59
+
60
+ Example using variables (session is maintained):
61
+
62
+ ```bash
63
+ $ cone exec 'u = User.last'
64
+ => #<User id: 1, email: "user@example.com", created_at: "2025-07-17 15:16:34.685972000 +0900", updated_at: "2025-07-17 15:16:34.685972000 +0900">
65
+
66
+ $ cone exec 'puts u'
67
+ #<User:0x00000001104bbd18>
68
+ => nil
69
+ ```
70
+
71
+ You can also execute Ruby files directly using the `-f` option. Unlike Rails Runner, this is executed in an IRB session.
72
+
73
+ ```bash
74
+ $ cone exec -f example.rb
75
+ ```
76
+
77
+ A `-v` option (Verbose output) is provided for debugging.
78
+
79
+ ```bash
80
+ $ cone exec -v 'puts "hello, world"'
81
+ ```
82
+
83
+ ## Best Practices for Code Input
84
+
85
+ ### Using Single Quotes (Strongly Recommended)
86
+
87
+ **Always use single quotes** when passing code to `cone exec`. This is the recommended practice for all cone users:
88
+
89
+ ```bash
90
+ $ cone exec 'User.where(active: true).count'
91
+ $ cone exec 'puts "Hello, world!"'
92
+ ```
93
+
94
+ ### Using the --raw Option
95
+
96
+ **Note for Claude Code users: DO NOT use the --raw option.** This option is not needed in Claude Code environments.
97
+
98
+ ### Multi-line Code Support
99
+
100
+ Cone fully supports multi-line code execution. There are several ways to execute multi-line code:
101
+
102
+ #### Method 1: Multi-line String with Single Quotes
103
+ ```bash
104
+ $ cone exec '
105
+ users = User.active
106
+ puts "Active users: #{users.count}"
107
+ users.first
108
+ '
109
+ ```
110
+
111
+ #### Method 2: Using a File
112
+ For complex multi-line code, save it in a file:
113
+ ```bash
114
+ $ cone exec -f complex_task.rb
115
+ ```
116
+
117
+ All methods maintain the session state, so variables and objects persist between executions.