console_agent 0.0.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 528fd68c058820f080d3b6d2aec7767ac48944e17ae6f2c117d279fbec4c8070
4
- data.tar.gz: cf28f6caa721b229536bb591c1c5ecfbb51b1dbc55cee342bccfdaabbca0e804
3
+ metadata.gz: '09b1eab5b0fd84fb27fe9af623c234da290779f84d5972d4a67bfe000d581ab2'
4
+ data.tar.gz: 344f3afbc67adc4d87c5c2fb4e11ccc902fca071776a4a3aa6cb160783e22998
5
5
  SHA512:
6
- metadata.gz: 9127d4e6c9eea9cb56cd4484906fd80c97f07d2c276f8d65f5906338011d03c39a53e5c1a5bfc3e1cbd3ecb69a4b6ddd9716a1aa8416b091c056ad5f267f21b2
7
- data.tar.gz: 567dea6f8c8f8213ad3ed4b5cafa9519f3ecbbaa5825355cbee1be8a56981565dd3ec4905285ad7bdae8c6c5ea312550396ce1dfd2fe1c98df3e29d7d43b5a3e
6
+ metadata.gz: 73a5c9bd000e30b3cfa63c06efbb0f9619849ec56ca8b4b8f1c71cd7eae29439f9b4ef8d10725950aa8fb813159674820706fa5883784060464f8c9959c914aa
7
+ data.tar.gz: 4498692aab0860b717b7d309e252cb1b2130b7946a55421f0edd76c3b3469da27c6f8426667e23cf0a2fcab66a356896573f19b819c8ba603f08f50a5e60647d
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Frank Cort
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,235 @@
1
+ # ConsoleAgent
2
+
3
+ An AI-powered assistant for your Rails console. Ask questions in plain English, get executable Ruby code.
4
+
5
+ ConsoleAgent connects your `rails console` to an LLM (Claude or GPT) and gives it tools to introspect your app's database schema, models, and source code. It figures out what it needs on its own, so you don't pay for 50K tokens of context on every query.
6
+
7
+ ## Quick Start
8
+
9
+ Add to your Gemfile:
10
+
11
+ ```ruby
12
+ gem 'console_agent', group: :development
13
+ ```
14
+
15
+ Run the install generator:
16
+
17
+ ```bash
18
+ bundle install
19
+ rails generate console_agent:install
20
+ ```
21
+
22
+ Set your API key and open a console:
23
+
24
+ ```bash
25
+ export ANTHROPIC_API_KEY=sk-ant-...
26
+ rails console
27
+ ```
28
+
29
+ Then:
30
+
31
+ ```ruby
32
+ ai "show me all users who signed up this week"
33
+ ```
34
+
35
+ ## Console Commands
36
+
37
+ | Command | Description |
38
+ |---------|-------------|
39
+ | `ai "query"` | Ask a question, review generated code, confirm before executing |
40
+ | `ai! "query"` | Ask a question and enter interactive mode for follow-ups |
41
+ | `ai!` | Enter interactive mode (type `exit` to leave) |
42
+ | `ai? "query"` | Explain only — shows code but never executes |
43
+ | `ai_status` | Print current configuration summary |
44
+
45
+ ### Example Session
46
+
47
+ ```
48
+ irb> ai "find the 5 most recent orders over $100"
49
+ Thinking...
50
+ -> list_tables
51
+ 12 tables: users, orders, line_items, products...
52
+ -> describe_table("orders")
53
+ 8 columns
54
+ -> describe_model("Order")
55
+ 4 associations, 2 validations
56
+
57
+ You can query recent high-value orders like this:
58
+
59
+ Order.where("total > ?", 100).order(created_at: :desc).limit(5)
60
+
61
+ [tokens in: 1,240 | out: 85 | total: 1,325]
62
+ Execute? [y/N/edit] y
63
+ => [#<Order id: 4821, ...>, ...]
64
+ ```
65
+
66
+ ### Interactive Mode
67
+
68
+ ```
69
+ irb> ai!
70
+ ConsoleAgent interactive mode. Type 'exit' or 'quit' to leave.
71
+ ai> show me all tables
72
+ Thinking...
73
+ -> list_tables
74
+ 12 tables: users, orders, line_items, products...
75
+ ...
76
+ ai> now count orders by status
77
+ ...
78
+ ai> exit
79
+ [session totals — in: 3,200 | out: 410 | total: 3,610]
80
+ Left ConsoleAgent interactive mode.
81
+ ```
82
+
83
+ ### Configuration Status
84
+
85
+ ```
86
+ irb> ai_status
87
+ [ConsoleAgent v0.1.0]
88
+ Provider: anthropic
89
+ Model: claude-opus-4-6
90
+ API key: sk-ant-...a3b4
91
+ Context mode: smart
92
+ Max tokens: 4096
93
+ Temperature: 0.2
94
+ Timeout: 30s
95
+ Max tool rounds:10
96
+ Auto-execute: false
97
+ Debug: false
98
+ ```
99
+
100
+ ## Configuration
101
+
102
+ The install generator creates `config/initializers/console_agent.rb`:
103
+
104
+ ```ruby
105
+ ConsoleAgent.configure do |config|
106
+ # LLM provider: :anthropic or :openai
107
+ config.provider = :anthropic
108
+
109
+ # API key (or set ANTHROPIC_API_KEY / OPENAI_API_KEY env var)
110
+ # config.api_key = 'sk-...'
111
+
112
+ # Model override (defaults: claude-opus-4-6 for Anthropic, gpt-5.3-codex for OpenAI)
113
+ # config.model = 'claude-opus-4-6'
114
+
115
+ # Context mode:
116
+ # :smart - (default) LLM uses tools to fetch schema/model/code on demand
117
+ # :full - sends all schema, models, and routes every time
118
+ config.context_mode = :smart
119
+
120
+ # Max tokens for LLM response
121
+ config.max_tokens = 4096
122
+
123
+ # Temperature (0.0 - 1.0)
124
+ config.temperature = 0.2
125
+
126
+ # Auto-execute generated code without confirmation
127
+ config.auto_execute = false
128
+
129
+ # Max tool-use rounds per query in :smart mode
130
+ config.max_tool_rounds = 10
131
+
132
+ # HTTP timeout in seconds
133
+ config.timeout = 30
134
+
135
+ # Debug mode: prints full API requests/responses and tool calls
136
+ # config.debug = true
137
+ end
138
+ ```
139
+
140
+ You can also change settings at runtime in the console:
141
+
142
+ ```ruby
143
+ ConsoleAgent.configure { |c| c.debug = true }
144
+ ConsoleAgent.configure { |c| c.provider = :openai }
145
+ ENV['OPENAI_API_KEY'] = 'sk-...'
146
+ ```
147
+
148
+ ## Context Modes
149
+
150
+ ### Smart Mode (default)
151
+
152
+ The LLM gets a minimal system prompt and uses tools to look up what it needs:
153
+
154
+ - **list_tables** / **describe_table** — database schema on demand
155
+ - **list_models** / **describe_model** — ActiveRecord associations, validations
156
+ - **list_files** / **read_file** / **search_code** — browse app source code
157
+
158
+ This keeps token usage low (~1-3K per query) even for large apps with hundreds of tables.
159
+
160
+ ### Full Mode
161
+
162
+ Sends the entire database schema, all model details, and a route summary in every request. Simple but expensive for large apps (~50K+ tokens). Useful if you want everything available without tool-call round trips.
163
+
164
+ ```ruby
165
+ ConsoleAgent.configure { |c| c.context_mode = :full }
166
+ ```
167
+
168
+ ## Tools Available in Smart Mode
169
+
170
+ | Tool | Description |
171
+ |------|-------------|
172
+ | `list_tables` | All database table names |
173
+ | `describe_table` | Columns, types, and indexes for one table |
174
+ | `list_models` | All model names with association names |
175
+ | `describe_model` | Associations, validations, scopes for one model |
176
+ | `list_files` | Ruby files in a directory |
177
+ | `read_file` | Read a source file (capped at 200 lines) |
178
+ | `search_code` | Grep for a pattern across Ruby files |
179
+
180
+ The LLM decides which tools to call based on your question. You can see the tool calls happening in real time.
181
+
182
+ ## Providers
183
+
184
+ ### Anthropic (default)
185
+
186
+ Uses the Claude Messages API. Set `ANTHROPIC_API_KEY` or `config.api_key`.
187
+
188
+ ### OpenAI
189
+
190
+ Uses the Chat Completions API. Set `OPENAI_API_KEY` or `config.api_key`.
191
+
192
+ ```ruby
193
+ ConsoleAgent.configure do |config|
194
+ config.provider = :openai
195
+ config.model = 'gpt-5.3-codex' # optional, this is the default
196
+ end
197
+ ```
198
+
199
+ ## Docker Setup
200
+
201
+ If your Rails app runs in Docker, mount the gem source as a volume.
202
+
203
+ In `docker-compose.yml`:
204
+
205
+ ```yaml
206
+ volumes:
207
+ - /path/to/console_agent:/console_agent
208
+ ```
209
+
210
+ In `Gemfile`:
211
+
212
+ ```ruby
213
+ gem 'console_agent', path: '/console_agent', group: :development
214
+ ```
215
+
216
+ For Docker builds (where the gem source isn't available yet), add a stub to your Dockerfile before `bundle install`:
217
+
218
+ ```dockerfile
219
+ RUN mkdir -p /console_agent/lib/console_agent && \
220
+ echo "module ConsoleAgent; VERSION = '0.1.0'; end" > /console_agent/lib/console_agent/version.rb && \
221
+ echo "require 'console_agent/version'" > /console_agent/lib/console_agent.rb && \
222
+ printf "require_relative 'lib/console_agent/version'\nGem::Specification.new do |s|\n s.name = 'console_agent'\n s.version = ConsoleAgent::VERSION\n s.summary = 'stub'\n s.authors = ['x']\n s.files = ['lib/console_agent.rb']\n s.add_dependency 'rails', '>= 5.0'\n s.add_dependency 'faraday', '>= 1.0'\nend\n" > /console_agent/console_agent.gemspec
223
+ ```
224
+
225
+ The volume mount overwrites the stub at runtime with the real source.
226
+
227
+ ## Requirements
228
+
229
+ - Ruby >= 2.5
230
+ - Rails >= 5.0
231
+ - Faraday >= 1.0
232
+
233
+ ## License
234
+
235
+ MIT
@@ -0,0 +1,58 @@
1
+ module ConsoleAgent
2
+ class Configuration
3
+ PROVIDERS = %i[anthropic openai].freeze
4
+ CONTEXT_MODES = %i[full smart].freeze
5
+
6
+ attr_accessor :provider, :api_key, :model, :max_tokens,
7
+ :auto_execute, :context_mode, :temperature,
8
+ :timeout, :debug, :max_tool_rounds
9
+
10
+ def initialize
11
+ @provider = :anthropic
12
+ @api_key = nil
13
+ @model = nil
14
+ @max_tokens = 4096
15
+ @auto_execute = false
16
+ @context_mode = :smart
17
+ @temperature = 0.2
18
+ @timeout = 30
19
+ @debug = false
20
+ @max_tool_rounds = 10
21
+ end
22
+
23
+ def resolved_api_key
24
+ return @api_key if @api_key && !@api_key.empty?
25
+
26
+ case @provider
27
+ when :anthropic
28
+ ENV['ANTHROPIC_API_KEY']
29
+ when :openai
30
+ ENV['OPENAI_API_KEY']
31
+ end
32
+ end
33
+
34
+ def resolved_model
35
+ return @model if @model && !@model.empty?
36
+
37
+ case @provider
38
+ when :anthropic
39
+ 'claude-opus-4-6'
40
+ when :openai
41
+ 'gpt-5.3-codex'
42
+ end
43
+ end
44
+
45
+ def validate!
46
+ unless PROVIDERS.include?(@provider)
47
+ raise ConfigurationError, "Unknown provider: #{@provider}. Valid: #{PROVIDERS.join(', ')}"
48
+ end
49
+
50
+ unless resolved_api_key
51
+ env_var = @provider == :anthropic ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'
52
+ raise ConfigurationError, "No API key. Set config.api_key or #{env_var} env var."
53
+ end
54
+ end
55
+ end
56
+
57
+ class ConfigurationError < StandardError; end
58
+ end
@@ -0,0 +1,88 @@
1
+ module ConsoleAgent
2
+ module ConsoleMethods
3
+ def ai_status
4
+ ConsoleAgent.status
5
+ end
6
+
7
+ def ai(query = nil)
8
+ if query.nil?
9
+ $stderr.puts "\e[33mUsage: ai \"your question here\"\e[0m"
10
+ $stderr.puts "\e[33m ai \"query\" - ask + confirm execution\e[0m"
11
+ $stderr.puts "\e[33m ai! \"query\" - enter interactive mode (or ai! with no args)\e[0m"
12
+ $stderr.puts "\e[33m ai? \"query\" - explain only, no execution\e[0m"
13
+ $stderr.puts "\e[33m ai_status - show current configuration\e[0m"
14
+ return nil
15
+ end
16
+
17
+ require 'console_agent/context_builder'
18
+ require 'console_agent/providers/base'
19
+ require 'console_agent/executor'
20
+ require 'console_agent/repl'
21
+
22
+ repl = Repl.new(__console_agent_binding)
23
+ repl.one_shot(query.to_s)
24
+ rescue => e
25
+ $stderr.puts "\e[31mConsoleAgent error: #{e.message}\e[0m"
26
+ nil
27
+ end
28
+
29
+ def ai!(query = nil)
30
+ require 'console_agent/context_builder'
31
+ require 'console_agent/providers/base'
32
+ require 'console_agent/executor'
33
+ require 'console_agent/repl'
34
+
35
+ repl = Repl.new(__console_agent_binding)
36
+
37
+ if query
38
+ repl.one_shot(query.to_s)
39
+ else
40
+ repl.interactive
41
+ end
42
+ rescue => e
43
+ $stderr.puts "\e[31mConsoleAgent error: #{e.message}\e[0m"
44
+ nil
45
+ end
46
+
47
+ def ai?(query = nil)
48
+ unless query
49
+ $stderr.puts "\e[33mUsage: ai? \"your question here\" - explain without executing\e[0m"
50
+ return nil
51
+ end
52
+
53
+ require 'console_agent/context_builder'
54
+ require 'console_agent/providers/base'
55
+ require 'console_agent/executor'
56
+ require 'console_agent/repl'
57
+
58
+ repl = Repl.new(__console_agent_binding)
59
+ repl.explain(query.to_s)
60
+ rescue => e
61
+ $stderr.puts "\e[31mConsoleAgent error: #{e.message}\e[0m"
62
+ nil
63
+ end
64
+
65
+ private
66
+
67
+ def __console_agent_binding
68
+ # Try IRB workspace binding
69
+ if defined?(IRB) && IRB.respond_to?(:CurrentContext)
70
+ ctx = IRB.CurrentContext rescue nil
71
+ if ctx && ctx.respond_to?(:workspace) && ctx.workspace.respond_to?(:binding)
72
+ return ctx.workspace.binding
73
+ end
74
+ end
75
+
76
+ # Try Pry binding
77
+ if defined?(Pry) && respond_to?(:pry_instance, true)
78
+ pry_inst = pry_instance rescue nil
79
+ if pry_inst && pry_inst.respond_to?(:current_binding)
80
+ return pry_inst.current_binding
81
+ end
82
+ end
83
+
84
+ # Fallback
85
+ TOPLEVEL_BINDING
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,200 @@
1
+ module ConsoleAgent
2
+ class ContextBuilder
3
+ def initialize(config = ConsoleAgent.configuration)
4
+ @config = config
5
+ end
6
+
7
+ def build
8
+ case @config.context_mode
9
+ when :smart
10
+ build_smart
11
+ else
12
+ build_full
13
+ end
14
+ rescue => e
15
+ ConsoleAgent.logger.warn("ConsoleAgent: context build error: #{e.message}")
16
+ system_instructions + "\n\n" + environment_context
17
+ end
18
+
19
+ def build_full
20
+ parts = []
21
+ parts << system_instructions
22
+ parts << environment_context
23
+ parts << schema_context
24
+ parts << models_context
25
+ parts << routes_context
26
+ parts.compact.join("\n\n")
27
+ end
28
+
29
+ def build_smart
30
+ parts = []
31
+ parts << smart_system_instructions
32
+ parts << environment_context
33
+ parts.compact.join("\n\n")
34
+ end
35
+
36
+ private
37
+
38
+ def smart_system_instructions
39
+ <<~PROMPT.strip
40
+ You are a Ruby on Rails console assistant. The user is in a `rails console` session.
41
+ You help them query data, debug issues, and understand their application.
42
+
43
+ You have tools available to introspect the app's database schema, models, and source code.
44
+ Use them as needed to write accurate queries. For example, call list_tables to see what
45
+ tables exist, then describe_table to get column details for the ones you need.
46
+
47
+ You also have an ask_user tool to ask the console user clarifying questions. Use it when
48
+ you need specific information to write accurate code — such as which user they are, which
49
+ record to target, or what value to use.
50
+
51
+ RULES:
52
+ - Give ONE concise answer. Do not offer multiple alternatives or variations.
53
+ - Respond with a single ```ruby code block that directly answers the question.
54
+ - Include a brief one-line explanation before the code block.
55
+ - Use the app's actual model names, associations, and schema.
56
+ - Prefer ActiveRecord query interface over raw SQL.
57
+ - For destructive operations, add a comment warning.
58
+ - NEVER use placeholder values like YOUR_USER_ID or YOUR_EMAIL in code. If you need
59
+ a specific value from the user, call the ask_user tool to get it first.
60
+ - Keep code concise and idiomatic.
61
+ - Use tools to look up schema/model details rather than guessing column names.
62
+ PROMPT
63
+ end
64
+
65
+ def system_instructions
66
+ <<~PROMPT.strip
67
+ You are a Ruby on Rails console assistant. The user is in a `rails console` session.
68
+ You help them query data, debug issues, and understand their application.
69
+
70
+ RULES:
71
+ - Give ONE concise answer. Do not offer multiple alternatives or variations.
72
+ - Respond with a single ```ruby code block that directly answers the question.
73
+ - Include a brief one-line explanation before the code block.
74
+ - Use the app's actual model names, associations, and schema.
75
+ - Prefer ActiveRecord query interface over raw SQL.
76
+ - For destructive operations, add a comment warning.
77
+ - If the request is ambiguous, ask a clarifying question instead of guessing.
78
+ - Keep code concise and idiomatic.
79
+ PROMPT
80
+ end
81
+
82
+ def environment_context
83
+ lines = ["## Environment"]
84
+ lines << "- Ruby #{RUBY_VERSION}"
85
+ lines << "- Rails #{Rails.version}" if defined?(Rails) && Rails.respond_to?(:version)
86
+
87
+ if defined?(ActiveRecord::Base) && ActiveRecord::Base.connected?
88
+ adapter = ActiveRecord::Base.connection.adapter_name rescue 'unknown'
89
+ lines << "- Database adapter: #{adapter}"
90
+ end
91
+
92
+ if defined?(Bundler)
93
+ key_gems = %w[devise cancancan pundit sidekiq delayed_job resque
94
+ paperclip carrierwave activestorage shrine
95
+ pg mysql2 sqlite3 mongoid]
96
+ loaded = key_gems.select { |g| Gem.loaded_specs.key?(g) }
97
+ lines << "- Key gems: #{loaded.join(', ')}" unless loaded.empty?
98
+ end
99
+
100
+ lines.join("\n")
101
+ end
102
+
103
+ def schema_context
104
+ return nil unless defined?(ActiveRecord::Base) && ActiveRecord::Base.connected?
105
+
106
+ conn = ActiveRecord::Base.connection
107
+ tables = conn.tables.sort
108
+ return nil if tables.empty?
109
+
110
+ lines = ["## Database Schema"]
111
+ tables.each do |table|
112
+ next if table == 'schema_migrations' || table == 'ar_internal_metadata'
113
+
114
+ cols = conn.columns(table).map { |c| "#{c.name}:#{c.type}" }
115
+ lines << "- #{table} (#{cols.join(', ')})"
116
+ end
117
+ lines.join("\n")
118
+ rescue => e
119
+ ConsoleAgent.logger.debug("ConsoleAgent: schema introspection failed: #{e.message}")
120
+ nil
121
+ end
122
+
123
+ def models_context
124
+ return nil unless defined?(ActiveRecord::Base)
125
+
126
+ eager_load_app!
127
+ base_class = defined?(ApplicationRecord) ? ApplicationRecord : ActiveRecord::Base
128
+ models = ObjectSpace.each_object(Class).select { |c|
129
+ c < base_class && !c.abstract_class? && c.name && !c.name.start_with?('HABTM_')
130
+ }.sort_by(&:name)
131
+
132
+ return nil if models.empty?
133
+
134
+ lines = ["## Models"]
135
+ models.each do |model|
136
+ parts = [model.name]
137
+
138
+ assocs = model.reflect_on_all_associations.map { |a| "#{a.macro} :#{a.name}" }
139
+ parts << " associations: #{assocs.join(', ')}" unless assocs.empty?
140
+
141
+ scopes = model.methods.grep(/^_scope_/).map { |m| m.to_s.sub('_scope_', '') } rescue []
142
+ if scopes.empty?
143
+ # Alternative: check singleton methods that return ActiveRecord::Relation
144
+ scope_names = (model.singleton_methods - base_class.singleton_methods).select { |m|
145
+ m != :all && !m.to_s.start_with?('_') && !m.to_s.start_with?('find')
146
+ }.first(10)
147
+ # We won't list these to avoid noise — scopes are hard to detect reliably
148
+ end
149
+
150
+ validations = model.validators.map { |v|
151
+ attrs = v.attributes.join(', ')
152
+ "#{v.class.name.demodulize.underscore.sub('_validator', '')} on #{attrs}"
153
+ }.uniq.first(5) rescue []
154
+ parts << " validations: #{validations.join('; ')}" unless validations.empty?
155
+
156
+ lines << "- #{parts.join("\n")}"
157
+ end
158
+ lines.join("\n")
159
+ rescue => e
160
+ ConsoleAgent.logger.debug("ConsoleAgent: model introspection failed: #{e.message}")
161
+ nil
162
+ end
163
+
164
+ def routes_context
165
+ return nil unless defined?(Rails) && Rails.respond_to?(:application)
166
+
167
+ routes = Rails.application.routes.routes
168
+ return nil if routes.empty?
169
+
170
+ lines = ["## Routes (summary)"]
171
+ count = 0
172
+ routes.each do |route|
173
+ next if route.internal?
174
+ path = route.path.spec.to_s.sub('(.:format)', '')
175
+ verb = route.verb.to_s
176
+ next if verb.empty?
177
+ action = route.defaults[:controller].to_s + '#' + route.defaults[:action].to_s
178
+ lines << "- #{verb} #{path} -> #{action}"
179
+ count += 1
180
+ break if count >= 50
181
+ end
182
+ lines << "- ... (#{routes.size - count} more routes)" if routes.size > count
183
+
184
+ lines.join("\n")
185
+ rescue => e
186
+ ConsoleAgent.logger.debug("ConsoleAgent: route introspection failed: #{e.message}")
187
+ nil
188
+ end
189
+
190
+ def eager_load_app!
191
+ return unless defined?(Rails) && Rails.respond_to?(:application)
192
+
193
+ if Rails.application.respond_to?(:eager_load!)
194
+ Rails.application.eager_load!
195
+ end
196
+ rescue => e
197
+ ConsoleAgent.logger.debug("ConsoleAgent: eager_load failed: #{e.message}")
198
+ end
199
+ end
200
+ end