rails_agent_server 0.1.0
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +192 -0
- data/Rakefile +10 -0
- data/exe/rails_agent_server +6 -0
- data/lib/rails_agent_server/cli.rb +98 -0
- data/lib/rails_agent_server/server.rb +227 -0
- data/lib/rails_agent_server/version.rb +5 -0
- data/lib/rails_agent_server.rb +9 -0
- data/sig/rails_repl_server.rbs +4 -0
- metadata +55 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 67c2f8f4c1a4d5f62de047f40c1bf076549b7f303b1dad62b8459919cd73ed87
|
|
4
|
+
data.tar.gz: 4ae4c453471fb620d461742684b379fc0ba4e28d62b77d662f704bc039b10a54
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 414387d2240dd9736ae51d8149949e0adb72d75eac81b39dcd50da9cbf03ff29424cbc281d4477fe10ab211541e45b6d7e48b5d512d3ce20389f50508e768f45
|
|
7
|
+
data.tar.gz: 325d73826e71519c811aac9b91206d1f1f702006d578ddc40618abac28d18ca7411da1dc5db54d8bc13bba68036f0bc2ad96491bf9bb3f0c621122ddef29f33a
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andy Waite
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Rails Agent Server
|
|
2
|
+
|
|
3
|
+
A persistent Rails server for AI agents that avoids boot overhead for repeated queries. Intended for AI agents like Claude Code that need fast Rails runner access without waiting for Rails to boot on every request.
|
|
4
|
+
|
|
5
|
+
## Why This Gem?
|
|
6
|
+
|
|
7
|
+
When using AI coding assistants or automation tools with Rails applications, the agent often needs to run many small queries via `bin/rails runner` to understand the runtime behaviour or state. Using `bin/rails runner` for each query means booting Rails every time, which can typically take 5-10 seconds per query.
|
|
8
|
+
|
|
9
|
+
Rails Agent Server starts a persistent background server that keeps Rails loaded in memory. The first request takes the normal Rails boot time, but subsequent requests are instant.
|
|
10
|
+
|
|
11
|
+
### Why Not `bin/rails console`?
|
|
12
|
+
|
|
13
|
+
AI agents can't easily interact with `bin/rails console` because:
|
|
14
|
+
|
|
15
|
+
- **Interactive TTY requirement**: Rails console expects an interactive terminal (TTY) and won't accept input from standard pipes
|
|
16
|
+
- **No request/response protocol**: There's no simple way to send a command and receive just its result back
|
|
17
|
+
- **Session complexity**: Managing an interactive console session requires handling readline, prompt detection, and terminal control sequences
|
|
18
|
+
- **Output parsing**: Console output includes prompts, formatting, and IRB metadata that's difficult to parse programmatically
|
|
19
|
+
|
|
20
|
+
Rails Agent Server provides a simple request/response interface over Unix sockets, making it trivial for AI agents to execute code and get clean results.
|
|
21
|
+
|
|
22
|
+
### Why Not Spring?
|
|
23
|
+
|
|
24
|
+
Spring is Rails' official application preloader and is a viable alternative for this use case. However, some projects prefer to avoid Spring for various reasons:
|
|
25
|
+
|
|
26
|
+
- **Simplicity**: Spring can sometimes cause confusion with stale code or require manual intervention (`spring stop`)
|
|
27
|
+
- **Compatibility**: Some projects have experienced issues with Spring in certain environments or with specific gems
|
|
28
|
+
|
|
29
|
+
If Spring works well for your project, you can use `bin/spring rails runner` instead. Rails Agent Server is for teams that prefer an alternative approach or have disabled Spring.
|
|
30
|
+
|
|
31
|
+
### Why Not MCP (Model Context Protocol)?
|
|
32
|
+
|
|
33
|
+
MCP servers provide a structured way for AI agents to interact with systems through defined tools and resources. While MCP is excellent for complex, multi-step workflows and standardized interfaces, Rails Agent Server is preferable when:
|
|
34
|
+
|
|
35
|
+
- **Simplicity**: You just need to run Rails code quickly without defining MCP tools and schemas
|
|
36
|
+
- **Flexibility**: AI agents can execute arbitrary Rails code without being limited to predefined tool operations
|
|
37
|
+
- **Setup**: No need to configure MCP server definitions, transport layers, or client-server communication
|
|
38
|
+
- **Performance**: Direct command execution is faster than MCP's request/response protocol overhead
|
|
39
|
+
- **Token efficiency**: MCP can consume many tokens for structured tool schemas and responses
|
|
40
|
+
- **Existing workflows**: Works with agents that already know how to run shell commands
|
|
41
|
+
|
|
42
|
+
Rails Agent Server is a lightweight alternative that lets AI agents treat your Rails app like a fast REPL, while MCP is better suited for building formalized integrations with specific capabilities.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
Add this line to your application's Gemfile:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
gem 'rails_agent_server', group: :development
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
And then execute:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bundle install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Or install it yourself as:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
gem install rails_agent_server
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Agent Setup
|
|
65
|
+
|
|
66
|
+
Add this section to your project's `CLAUDE.md` or equivalent:
|
|
67
|
+
|
|
68
|
+
```markdown
|
|
69
|
+
## Rails Console Access
|
|
70
|
+
|
|
71
|
+
This project uses `rails_agent_server` for fast Rails runner access without boot overhead.
|
|
72
|
+
|
|
73
|
+
When you need to query the database or run Rails code:
|
|
74
|
+
- Use `rails_agent_server 'YourCode.here'` instead of `bin/rails runner`
|
|
75
|
+
- First request auto-starts a persistent server (takes ~5 seconds)
|
|
76
|
+
- Subsequent requests are instant (no Rails boot time)
|
|
77
|
+
- Server stays running in background until you run `rails_agent_server stop`
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
rails_agent_server 'User.count'
|
|
81
|
+
rails_agent_server 'Post.where(published: true).count'
|
|
82
|
+
rails_agent_server 'User.find_by(email: "test@example.com")&.name'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Usage
|
|
86
|
+
|
|
87
|
+
### Basic Commands
|
|
88
|
+
|
|
89
|
+
These commands are designed to be used by AI agents (like Claude Code) or automation tools, and not intended for manual use.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Run a Ruby expression (auto-starts server if needed)
|
|
93
|
+
rails_agent_server 'User.count'
|
|
94
|
+
|
|
95
|
+
# Run code that prints output
|
|
96
|
+
rails_agent_server 'puts User.pluck(:email).join(", ")'
|
|
97
|
+
|
|
98
|
+
# Run a script file
|
|
99
|
+
rails_agent_server /path/to/script.rb
|
|
100
|
+
|
|
101
|
+
# Server management
|
|
102
|
+
rails_agent_server status # Check if server is running
|
|
103
|
+
rails_agent_server start # Manually start the server
|
|
104
|
+
rails_agent_server stop # Stop the background server
|
|
105
|
+
rails_agent_server restart # Restart the background server
|
|
106
|
+
rails_agent_server help # Show help
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Examples
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Database queries
|
|
113
|
+
rails_agent_server 'User.count'
|
|
114
|
+
rails_agent_server 'Post.where(published: true).pluck(:title)'
|
|
115
|
+
rails_agent_server 'User.find_by(email: "test@example.com")&.name'
|
|
116
|
+
|
|
117
|
+
# Inspect schema
|
|
118
|
+
rails_agent_server 'ActiveRecord::Base.connection.tables'
|
|
119
|
+
rails_agent_server 'User.column_names'
|
|
120
|
+
|
|
121
|
+
# Complex operations
|
|
122
|
+
rails_agent_server 'User.group(:status).count'
|
|
123
|
+
rails_agent_server 'Rails.cache.clear; "Cache cleared"'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## How It Works
|
|
127
|
+
|
|
128
|
+
1. **First Request**: When you run `rails_agent_server` for the first time, it:
|
|
129
|
+
- Spawns a background server process
|
|
130
|
+
- Loads your Rails environment once
|
|
131
|
+
- Creates a Unix socket for communication
|
|
132
|
+
- Stores the PID for management
|
|
133
|
+
|
|
134
|
+
2. **Subsequent Requests**: Each request:
|
|
135
|
+
- Connects to the existing Unix socket
|
|
136
|
+
- Sends code to execute
|
|
137
|
+
- Receives the result instantly
|
|
138
|
+
- No Rails boot time required
|
|
139
|
+
|
|
140
|
+
3. **Server Management**: The server:
|
|
141
|
+
- Runs in the background until explicitly stopped
|
|
142
|
+
- Captures both printed output and expression results
|
|
143
|
+
- Handles errors gracefully
|
|
144
|
+
- Cleans up socket and PID files on exit
|
|
145
|
+
|
|
146
|
+
## File Locations
|
|
147
|
+
|
|
148
|
+
By default, the server creates these files in your Rails application:
|
|
149
|
+
|
|
150
|
+
- **Socket**: `tmp/rails_agent_server.sock` - Unix socket for communication
|
|
151
|
+
- **PID file**: `tmp/pids/rails_agent_server.pid` - Process ID for management
|
|
152
|
+
- **Log file**: `log/rails_agent_server.log` - Server output and errors
|
|
153
|
+
|
|
154
|
+
If not in a Rails directory, files are created in `/tmp/`.
|
|
155
|
+
|
|
156
|
+
## Performance
|
|
157
|
+
|
|
158
|
+
- **First request**: ~5-10 seconds (Rails boot time)
|
|
159
|
+
- **Subsequent requests**: ~50-200ms (no boot overhead)
|
|
160
|
+
- **Memory**: One Rails process running in background (~200-500MB depending on your app)
|
|
161
|
+
|
|
162
|
+
## When to Restart
|
|
163
|
+
|
|
164
|
+
You should restart the server when:
|
|
165
|
+
- You've changed model files or schema
|
|
166
|
+
- You've updated initializers
|
|
167
|
+
- You've modified environment configuration
|
|
168
|
+
- The server is returning stale data
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
rails_agent_server restart
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Limitations
|
|
175
|
+
|
|
176
|
+
- The server may need to be restarted to pick up some code changes
|
|
177
|
+
- Only one server runs per Rails application (shared socket file)
|
|
178
|
+
- Requires Unix sockets (macOS, Linux, WSL)
|
|
179
|
+
|
|
180
|
+
## Development
|
|
181
|
+
|
|
182
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
183
|
+
|
|
184
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
185
|
+
|
|
186
|
+
## Contributing
|
|
187
|
+
|
|
188
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/andyw8/rails_agent_server.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "server"
|
|
4
|
+
|
|
5
|
+
module RailsAgentServer
|
|
6
|
+
class CLI
|
|
7
|
+
attr_reader :server
|
|
8
|
+
|
|
9
|
+
def initialize(argv = ARGV)
|
|
10
|
+
@argv = argv
|
|
11
|
+
@server = Server.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
case @argv[0]
|
|
16
|
+
when "stop"
|
|
17
|
+
server.stop
|
|
18
|
+
when "restart"
|
|
19
|
+
server.restart
|
|
20
|
+
when "status"
|
|
21
|
+
server.status
|
|
22
|
+
when "start"
|
|
23
|
+
server.start
|
|
24
|
+
puts "Rails agent server started"
|
|
25
|
+
when "--help", "-h", "help", nil
|
|
26
|
+
print_help
|
|
27
|
+
else
|
|
28
|
+
execute_code
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def execute_code
|
|
35
|
+
code = if @argv[0] && File.exist?(@argv[0])
|
|
36
|
+
File.read(@argv[0])
|
|
37
|
+
else
|
|
38
|
+
@argv.join(" ")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if code.empty?
|
|
42
|
+
$stderr.puts "Error: No code provided"
|
|
43
|
+
print_help
|
|
44
|
+
exit 1
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
puts server.execute(code)
|
|
49
|
+
rescue Errno::ENOENT
|
|
50
|
+
$stderr.puts "Error: Could not connect to Rails agent server"
|
|
51
|
+
exit 1
|
|
52
|
+
rescue => e
|
|
53
|
+
$stderr.puts "Error: #{e.message}"
|
|
54
|
+
exit 1
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def print_help
|
|
59
|
+
puts <<~HELP
|
|
60
|
+
Rails Agent Server - A persistent Rails server for AI agents
|
|
61
|
+
|
|
62
|
+
Usage:
|
|
63
|
+
rails_agent_server 'User.count' # Run a Ruby expression
|
|
64
|
+
rails_agent_server /path/to/script.rb # Run a script file
|
|
65
|
+
rails_agent_server start # Start the server
|
|
66
|
+
rails_agent_server stop # Stop the server
|
|
67
|
+
rails_agent_server restart # Restart the server
|
|
68
|
+
rails_agent_server status # Check server status
|
|
69
|
+
rails_agent_server help # Show this help
|
|
70
|
+
|
|
71
|
+
The server auto-starts on first use if not already running.
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
rails_agent_server 'User.count'
|
|
75
|
+
rails_agent_server 'puts User.pluck(:email).join(", ")'
|
|
76
|
+
rails_agent_server 'ActiveRecord::Base.connection.tables'
|
|
77
|
+
rails_agent_server script.rb
|
|
78
|
+
|
|
79
|
+
For Claude Code or AI agents, add this to your CLAUDE.md:
|
|
80
|
+
|
|
81
|
+
## Rails Console Access
|
|
82
|
+
|
|
83
|
+
This project uses rails_agent_server for fast Rails console access without boot overhead.
|
|
84
|
+
|
|
85
|
+
When you need to query the database or run Rails code:
|
|
86
|
+
- Use `rails_agent_server 'YourCode.here'` instead of `bin/rails runner`
|
|
87
|
+
- First request auto-starts a persistent server (takes ~5 seconds)
|
|
88
|
+
- Subsequent requests are instant (no Rails boot time)
|
|
89
|
+
- Server stays running in background until you run `rails_agent_server stop`
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
rails_agent_server 'User.count'
|
|
93
|
+
rails_agent_server 'Post.where(published: true).count'
|
|
94
|
+
rails_agent_server 'User.find_by(email: "test@example.com")&.name'
|
|
95
|
+
HELP
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "stringio"
|
|
6
|
+
|
|
7
|
+
module RailsAgentServer
|
|
8
|
+
class Server
|
|
9
|
+
attr_reader :socket_path, :pid_path, :log_path
|
|
10
|
+
|
|
11
|
+
def initialize(socket_path: nil, pid_path: nil, log_path: nil)
|
|
12
|
+
@socket_path = socket_path || default_socket_path
|
|
13
|
+
@pid_path = pid_path || default_pid_path
|
|
14
|
+
@log_path = log_path || default_log_path
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def start
|
|
18
|
+
return if running?
|
|
19
|
+
|
|
20
|
+
puts "Starting Rails agent server (this will take a few seconds)..."
|
|
21
|
+
|
|
22
|
+
pid = spawn(
|
|
23
|
+
RbConfig.ruby, "-r", "rails_agent_server/server",
|
|
24
|
+
"-e", "RailsAgentServer::Server.new(socket_path: '#{socket_path}', pid_path: '#{pid_path}', log_path: '#{log_path}').run",
|
|
25
|
+
out: log_path, err: log_path
|
|
26
|
+
)
|
|
27
|
+
Process.detach(pid)
|
|
28
|
+
|
|
29
|
+
wait_for_server
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def stop
|
|
33
|
+
if File.exist?(pid_path)
|
|
34
|
+
pid = File.read(pid_path).strip.to_i
|
|
35
|
+
begin
|
|
36
|
+
Process.kill("TERM", pid)
|
|
37
|
+
puts "Stopped Rails agent server (PID: #{pid})"
|
|
38
|
+
rescue Errno::ESRCH
|
|
39
|
+
puts "Rails agent server is not running (stale PID file)"
|
|
40
|
+
cleanup_files
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
puts "Rails agent server is not running"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def restart
|
|
48
|
+
stop if running?
|
|
49
|
+
cleanup_files
|
|
50
|
+
sleep 0.5
|
|
51
|
+
start
|
|
52
|
+
puts "Rails agent server restarted"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def status
|
|
56
|
+
if running?
|
|
57
|
+
pid = File.read(pid_path).strip
|
|
58
|
+
puts "Rails agent server is running (PID: #{pid})"
|
|
59
|
+
true
|
|
60
|
+
else
|
|
61
|
+
puts "Rails agent server is not running"
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def running?
|
|
67
|
+
return false unless File.exist?(pid_path)
|
|
68
|
+
|
|
69
|
+
pid = File.read(pid_path).strip.to_i
|
|
70
|
+
Process.kill(0, pid)
|
|
71
|
+
true
|
|
72
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def run
|
|
77
|
+
load_rails_environment
|
|
78
|
+
|
|
79
|
+
FileUtils.rm_f(socket_path)
|
|
80
|
+
server = UNIXServer.new(socket_path)
|
|
81
|
+
File.write(pid_path, Process.pid.to_s)
|
|
82
|
+
|
|
83
|
+
$stdout.puts "Rails agent server listening on #{socket_path} (PID: #{Process.pid})"
|
|
84
|
+
|
|
85
|
+
setup_signal_handlers
|
|
86
|
+
at_exit { cleanup_files }
|
|
87
|
+
|
|
88
|
+
loop do
|
|
89
|
+
client = server.accept
|
|
90
|
+
handle_client(client)
|
|
91
|
+
end
|
|
92
|
+
rescue => e
|
|
93
|
+
$stderr.puts "Server error: #{e.class}: #{e.message}"
|
|
94
|
+
$stderr.puts e.backtrace.join("\n")
|
|
95
|
+
cleanup_files
|
|
96
|
+
raise
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def execute(code)
|
|
100
|
+
start unless running?
|
|
101
|
+
|
|
102
|
+
socket = UNIXSocket.new(socket_path)
|
|
103
|
+
socket.write(code)
|
|
104
|
+
socket.close_write
|
|
105
|
+
response = socket.read
|
|
106
|
+
socket.close
|
|
107
|
+
response
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def default_socket_path
|
|
113
|
+
if defined?(Rails) && Rails.root
|
|
114
|
+
Rails.root.join("tmp", "rails_agent_server.sock").to_s
|
|
115
|
+
else
|
|
116
|
+
"/tmp/rails_agent_server.sock"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def default_pid_path
|
|
121
|
+
if defined?(Rails) && Rails.root
|
|
122
|
+
Rails.root.join("tmp", "pids", "rails_agent_server.pid").to_s
|
|
123
|
+
else
|
|
124
|
+
"/tmp/rails_agent_server.pid"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def default_log_path
|
|
129
|
+
if defined?(Rails) && Rails.root
|
|
130
|
+
Rails.root.join("log", "rails_agent_server.log").to_s
|
|
131
|
+
else
|
|
132
|
+
"/tmp/rails_agent_server.log"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def wait_for_server(timeout: 30)
|
|
137
|
+
(timeout * 2).times do
|
|
138
|
+
return if File.exist?(socket_path)
|
|
139
|
+
sleep 0.5
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
abort "Timed out waiting for Rails agent server to start. Check #{log_path}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def cleanup_files
|
|
146
|
+
FileUtils.rm_f(socket_path)
|
|
147
|
+
FileUtils.rm_f(pid_path)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def setup_signal_handlers
|
|
151
|
+
trap("INT") { exit }
|
|
152
|
+
trap("TERM") { exit }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def load_rails_environment
|
|
156
|
+
return if defined?(Rails)
|
|
157
|
+
|
|
158
|
+
rails_root = find_rails_root
|
|
159
|
+
abort "Not in a Rails application directory" unless rails_root
|
|
160
|
+
|
|
161
|
+
environment_path = File.join(rails_root, "config", "environment.rb")
|
|
162
|
+
abort "Rails environment not found at #{environment_path}" unless File.exist?(environment_path)
|
|
163
|
+
|
|
164
|
+
# Ensure tmp/pids directory exists
|
|
165
|
+
FileUtils.mkdir_p(File.dirname(pid_path))
|
|
166
|
+
|
|
167
|
+
require environment_path
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def find_rails_root
|
|
171
|
+
current_dir = Dir.pwd
|
|
172
|
+
until current_dir == "/"
|
|
173
|
+
config_path = File.join(current_dir, "config", "environment.rb")
|
|
174
|
+
return current_dir if File.exist?(config_path)
|
|
175
|
+
current_dir = File.dirname(current_dir)
|
|
176
|
+
end
|
|
177
|
+
nil
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def handle_client(client)
|
|
181
|
+
code = client.read
|
|
182
|
+
|
|
183
|
+
output = StringIO.new
|
|
184
|
+
result = nil
|
|
185
|
+
error = nil
|
|
186
|
+
|
|
187
|
+
begin
|
|
188
|
+
old_stdout = $stdout
|
|
189
|
+
$stdout = output
|
|
190
|
+
result = eval(code, TOPLEVEL_BINDING) # rubocop:disable Security/Eval
|
|
191
|
+
$stdout = old_stdout
|
|
192
|
+
rescue => e
|
|
193
|
+
$stdout = old_stdout
|
|
194
|
+
error = format_error(e)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
response = build_response(output.string, result, error)
|
|
198
|
+
client.write(response)
|
|
199
|
+
client.close
|
|
200
|
+
rescue => e
|
|
201
|
+
client.write("Server error: #{e.class}: #{e.message}")
|
|
202
|
+
client.close
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def format_error(exception)
|
|
206
|
+
message = "#{exception.class}: #{exception.message}"
|
|
207
|
+
if exception.backtrace
|
|
208
|
+
backtrace = exception.backtrace.first(5).join("\n ")
|
|
209
|
+
message += "\n #{backtrace}"
|
|
210
|
+
end
|
|
211
|
+
message
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def build_response(printed_output, result, error)
|
|
215
|
+
response = +""
|
|
216
|
+
response << printed_output unless printed_output.empty?
|
|
217
|
+
|
|
218
|
+
if error
|
|
219
|
+
response << error
|
|
220
|
+
elsif printed_output.empty?
|
|
221
|
+
response << result.inspect
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
response
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails_agent_server
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andy Waite
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Rails Agent Server provides a persistent background server for running
|
|
13
|
+
Rails code without the overhead of booting Rails for each request. Perfect for AI
|
|
14
|
+
agents and automation tools that need fast Rails console access.
|
|
15
|
+
email:
|
|
16
|
+
- andyw8@users.noreply.github.com
|
|
17
|
+
executables:
|
|
18
|
+
- rails_agent_server
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- LICENSE.txt
|
|
23
|
+
- README.md
|
|
24
|
+
- Rakefile
|
|
25
|
+
- exe/rails_agent_server
|
|
26
|
+
- lib/rails_agent_server.rb
|
|
27
|
+
- lib/rails_agent_server/cli.rb
|
|
28
|
+
- lib/rails_agent_server/server.rb
|
|
29
|
+
- lib/rails_agent_server/version.rb
|
|
30
|
+
- sig/rails_repl_server.rbs
|
|
31
|
+
homepage: https://github.com/andyw8/rails_agent_server
|
|
32
|
+
licenses:
|
|
33
|
+
- MIT
|
|
34
|
+
metadata:
|
|
35
|
+
homepage_uri: https://github.com/andyw8/rails_agent_server
|
|
36
|
+
source_code_uri: https://github.com/andyw8/rails_agent_server
|
|
37
|
+
rdoc_options: []
|
|
38
|
+
require_paths:
|
|
39
|
+
- lib
|
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
|
+
requirements:
|
|
42
|
+
- - ">="
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: 3.2.0
|
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '0'
|
|
50
|
+
requirements: []
|
|
51
|
+
rubygems_version: 3.6.9
|
|
52
|
+
specification_version: 4
|
|
53
|
+
summary: A persistent Rails server for AI agents that avoids boot overhead for repeated
|
|
54
|
+
queries
|
|
55
|
+
test_files: []
|