rails_console_ai 0.13.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/CHANGELOG.md +95 -0
- data/LICENSE +21 -0
- data/README.md +328 -0
- data/app/controllers/rails_console_ai/application_controller.rb +28 -0
- data/app/controllers/rails_console_ai/sessions_controller.rb +16 -0
- data/app/helpers/rails_console_ai/sessions_helper.rb +56 -0
- data/app/models/rails_console_ai/session.rb +23 -0
- data/app/views/layouts/rails_console_ai/application.html.erb +84 -0
- data/app/views/rails_console_ai/sessions/index.html.erb +57 -0
- data/app/views/rails_console_ai/sessions/show.html.erb +66 -0
- data/config/routes.rb +4 -0
- data/lib/generators/rails_console_ai/install_generator.rb +26 -0
- data/lib/generators/rails_console_ai/templates/initializer.rb +79 -0
- data/lib/rails_console_ai/channel/base.rb +23 -0
- data/lib/rails_console_ai/channel/console.rb +457 -0
- data/lib/rails_console_ai/channel/slack.rb +182 -0
- data/lib/rails_console_ai/configuration.rb +185 -0
- data/lib/rails_console_ai/console_methods.rb +277 -0
- data/lib/rails_console_ai/context_builder.rb +120 -0
- data/lib/rails_console_ai/conversation_engine.rb +1142 -0
- data/lib/rails_console_ai/engine.rb +5 -0
- data/lib/rails_console_ai/executor.rb +461 -0
- data/lib/rails_console_ai/providers/anthropic.rb +122 -0
- data/lib/rails_console_ai/providers/base.rb +118 -0
- data/lib/rails_console_ai/providers/bedrock.rb +171 -0
- data/lib/rails_console_ai/providers/local.rb +112 -0
- data/lib/rails_console_ai/providers/openai.rb +114 -0
- data/lib/rails_console_ai/railtie.rb +34 -0
- data/lib/rails_console_ai/repl.rb +65 -0
- data/lib/rails_console_ai/safety_guards.rb +207 -0
- data/lib/rails_console_ai/session_logger.rb +90 -0
- data/lib/rails_console_ai/slack_bot.rb +473 -0
- data/lib/rails_console_ai/storage/base.rb +27 -0
- data/lib/rails_console_ai/storage/file_storage.rb +63 -0
- data/lib/rails_console_ai/tools/code_tools.rb +126 -0
- data/lib/rails_console_ai/tools/memory_tools.rb +136 -0
- data/lib/rails_console_ai/tools/model_tools.rb +95 -0
- data/lib/rails_console_ai/tools/registry.rb +478 -0
- data/lib/rails_console_ai/tools/schema_tools.rb +60 -0
- data/lib/rails_console_ai/version.rb +3 -0
- data/lib/rails_console_ai.rb +214 -0
- data/lib/tasks/rails_console_ai.rake +7 -0
- metadata +152 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 98ae3edf9aa6ff90817a965a826abaac229c14cba7a76f5eb27f3fea0e1defc4
|
|
4
|
+
data.tar.gz: 410831834a4a68bcd23693ee185d437c76b648c533b9c2f9d89c83c2ce376d39
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ecec76ab83d0bc42a7216e1180d447bddf092556a5e600dad784da6826f613d7145e81e65bd768aafab1858a434a2193e6370315d5601cf668e23da0bcad40d9
|
|
7
|
+
data.tar.gz: eef36ae45dfda23ca47a95524afc7ebb76083a927162c79263c5d3c98a0726ec1bb20b5fcf2e29b03b2c480044484c6bec36dbac466035092e969a6d7d2e5747
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.13.0]
|
|
6
|
+
|
|
7
|
+
- Rename gem from `console_agent` to `rails_console_ai`
|
|
8
|
+
- Add AWS Bedrock provider
|
|
9
|
+
- Add prompt caching and cache-aware cost tracking for Anthropic
|
|
10
|
+
- Enable prompt caching for Bedrock
|
|
11
|
+
|
|
12
|
+
## [0.12.0]
|
|
13
|
+
|
|
14
|
+
- Add `slack_allowed_usernames` to restrict Slack channel access
|
|
15
|
+
|
|
16
|
+
## [0.11.0]
|
|
17
|
+
|
|
18
|
+
- Add Slack channel integration with system instructions and connection pooling
|
|
19
|
+
- Extract channels abstraction and ConversationEngine from Repl
|
|
20
|
+
- Add built-in safety guards with `/danger` bypass and progressive safety allowlists
|
|
21
|
+
- Add local model support with prompt truncation warnings
|
|
22
|
+
- Add `clear!` command to clear bot messages in thread
|
|
23
|
+
- Match code blocks in LLM results
|
|
24
|
+
- Fix long query display and add cost tracking to session viewer
|
|
25
|
+
- Strip quotes from session names when saving
|
|
26
|
+
|
|
27
|
+
## [0.10.0]
|
|
28
|
+
|
|
29
|
+
- Add `/expand` command to view previous results
|
|
30
|
+
- Exclude previous output from context; add tool for LLM to retrieve it on demand
|
|
31
|
+
- Show summarized info per LLM call in `/debug`
|
|
32
|
+
|
|
33
|
+
## [0.9.0]
|
|
34
|
+
|
|
35
|
+
- Add `/system` and `/context` commands to inspect what is being sent
|
|
36
|
+
- Omit huge output from tool results
|
|
37
|
+
- Don't cancel code execution on incorrect prompt answers
|
|
38
|
+
- Preserve code blocks when compacting; require manual `/compact`
|
|
39
|
+
- Fix authentication when neither method was applied
|
|
40
|
+
- Remove prompt to upgrade model on excessive tool calls
|
|
41
|
+
|
|
42
|
+
## [0.8.0]
|
|
43
|
+
|
|
44
|
+
- Add authentication function support so host apps can avoid using basic auth
|
|
45
|
+
- Add `/think` and `/cost` commands with Sonnet vs Opus support
|
|
46
|
+
- Gracefully handle token limit exceeded errors
|
|
47
|
+
|
|
48
|
+
## [0.7.0]
|
|
49
|
+
|
|
50
|
+
- Include binding variables and their classes in the Rails console context
|
|
51
|
+
- Add `ai_setup` command
|
|
52
|
+
- Add `/compact` mechanism for conversation management
|
|
53
|
+
- Catch errors and attempt to auto-fix them
|
|
54
|
+
|
|
55
|
+
## [0.6.0]
|
|
56
|
+
|
|
57
|
+
- Add core memory (`rails_console_ai.md`) that persists across sessions in the system prompt
|
|
58
|
+
- Add `ai_init` command to seed core memory
|
|
59
|
+
- Allow reading partial files
|
|
60
|
+
- Fix rspec hanging issues
|
|
61
|
+
|
|
62
|
+
## [0.5.0]
|
|
63
|
+
|
|
64
|
+
- Auto-accept single-step plans
|
|
65
|
+
- Support `>` shorthand to run code directly
|
|
66
|
+
- Add `script/release` for releases
|
|
67
|
+
|
|
68
|
+
## [0.4.0]
|
|
69
|
+
|
|
70
|
+
- Fix resuming sessions repeatedly
|
|
71
|
+
- Fix terminal flashing/loading in production (kubectl)
|
|
72
|
+
- Better escaping during thinking output
|
|
73
|
+
|
|
74
|
+
## [0.3.0]
|
|
75
|
+
|
|
76
|
+
- Add plan mechanism with "auto" execution mode
|
|
77
|
+
- Add session logging to DB with `/rails_console_ai` admin UI
|
|
78
|
+
- List and resume past sessions with pagination
|
|
79
|
+
- Add shift-tab for auto-execute mode
|
|
80
|
+
- Add usage display and debug toggle
|
|
81
|
+
- Store sessions incrementally; improved code segment display
|
|
82
|
+
|
|
83
|
+
## [0.2.0]
|
|
84
|
+
|
|
85
|
+
- Add memory system with individual file storage
|
|
86
|
+
- Add `ask_user` tool
|
|
87
|
+
- Add registry cache
|
|
88
|
+
- Fix REPL up-key and ctrl-a navigation
|
|
89
|
+
- Show tool usage and model processing info
|
|
90
|
+
- Add token count information and debug ability
|
|
91
|
+
- Use tools-based approach instead of sending everything at once
|
|
92
|
+
|
|
93
|
+
## [0.1.0]
|
|
94
|
+
|
|
95
|
+
- Initial implementation
|
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,328 @@
|
|
|
1
|
+
# RailsConsoleAI
|
|
2
|
+
|
|
3
|
+
Claude Code for your Rails Console.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
irb> ai "find the 5 most recent orders over $100"
|
|
7
|
+
Thinking...
|
|
8
|
+
-> list_tables
|
|
9
|
+
12 tables: users, orders, line_items, products...
|
|
10
|
+
-> describe_table("orders")
|
|
11
|
+
8 columns
|
|
12
|
+
|
|
13
|
+
Order.where("total > ?", 100).order(created_at: :desc).limit(5)
|
|
14
|
+
|
|
15
|
+
Execute? [y/N/edit/danger] y
|
|
16
|
+
=> [#<Order id: 4821, ...>, ...]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For complex tasks it builds multi-step plans, executing each step sequentially:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
ai> get the most recent salesforce token and count events via the API
|
|
23
|
+
Plan (2 steps):
|
|
24
|
+
1. Find the most recent active Salesforce OAuth2 token
|
|
25
|
+
token = Oauth2Token.where(provider: "salesforce", active: true)
|
|
26
|
+
.order(updated_at: :desc).first
|
|
27
|
+
2. Query event count via SOQL
|
|
28
|
+
api = SalesforceApi.new(step1)
|
|
29
|
+
api.query("SELECT COUNT(Id) FROM Event")
|
|
30
|
+
|
|
31
|
+
Accept plan? [y/N/a(uto)] a
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
No context needed from you — it figures out your app on its own.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
# Gemfile
|
|
40
|
+
gem 'rails_console_ai', group: :development
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
bundle install
|
|
45
|
+
rails generate rails_console_ai:install
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Set your API key in the generated initializer or via env var (`ANTHROPIC_API_KEY`):
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# config/initializers/rails_console_ai.rb
|
|
52
|
+
RailsConsoleAI.configure do |config|
|
|
53
|
+
config.api_key = 'sk-ant-...'
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Commands
|
|
58
|
+
|
|
59
|
+
| Command | What it does |
|
|
60
|
+
|---------|-------------|
|
|
61
|
+
| `ai "query"` | Ask, review generated code, confirm execution |
|
|
62
|
+
| `ai!` | Enter interactive mode (multi-turn conversation) |
|
|
63
|
+
| `ai? "query"` | Explain only, no execution |
|
|
64
|
+
| `ai_init` | Generate app guide for better AI context |
|
|
65
|
+
| `ai_setup` | Install session logging table |
|
|
66
|
+
| `ai_sessions` | List recent sessions |
|
|
67
|
+
| `ai_resume` | Resume a session by name or ID |
|
|
68
|
+
| `ai_memories` | Show stored memories |
|
|
69
|
+
| `ai_status` | Show current configuration |
|
|
70
|
+
|
|
71
|
+
### Interactive Mode
|
|
72
|
+
|
|
73
|
+
`ai!` starts a conversation. Slash commands available inside:
|
|
74
|
+
|
|
75
|
+
| Command | What it does |
|
|
76
|
+
|---------|-------------|
|
|
77
|
+
| `/auto` | Toggle auto-execute (skip confirmations) |
|
|
78
|
+
| `/danger` | Toggle safe mode off/on (allow side effects) |
|
|
79
|
+
| `/safe` | Show safety guard status |
|
|
80
|
+
| `/compact` | Compress history into a summary (saves tokens) |
|
|
81
|
+
| `/usage` | Show token stats |
|
|
82
|
+
| `/cost` | Show per-model cost breakdown |
|
|
83
|
+
| `/think` | Upgrade to thinking model (Opus) for the rest of the session |
|
|
84
|
+
| `/debug` | Toggle debug summaries (context stats, cost per call) |
|
|
85
|
+
| `/expand <id>` | Show full omitted output |
|
|
86
|
+
| `/context` | Show conversation history as sent to the LLM |
|
|
87
|
+
| `/system` | Show the system prompt |
|
|
88
|
+
| `/name <label>` | Name the session for easy resume |
|
|
89
|
+
|
|
90
|
+
Prefix input with `>` to run Ruby directly (no LLM round-trip). The result is added to conversation context.
|
|
91
|
+
|
|
92
|
+
Say "think harder" in any query to auto-upgrade to the thinking model for that session. After 5+ tool rounds, you'll also be prompted to switch.
|
|
93
|
+
|
|
94
|
+
## Features
|
|
95
|
+
|
|
96
|
+
- **Tool use** — AI introspects your schema, models, files, and code to write accurate queries
|
|
97
|
+
- **Multi-step plans** — complex tasks are broken into steps, executed sequentially with `step1`/`step2` references
|
|
98
|
+
- **Two-tier models** — defaults to Sonnet for speed/cost; `/think` upgrades to Opus when you need it
|
|
99
|
+
- **Cost tracking** — `/cost` shows per-model token usage and estimated spend
|
|
100
|
+
- **Memories** — AI saves what it learns about your app across sessions
|
|
101
|
+
- **App guide** — `ai_init` generates a guide injected into every system prompt
|
|
102
|
+
- **Sessions** — name, list, and resume interactive conversations (`ai_setup` to enable)
|
|
103
|
+
- **History compaction** — `/compact` summarizes long conversations to reduce cost and latency
|
|
104
|
+
- **Output trimming** — older execution outputs are automatically replaced with references; the LLM can recall them on demand via `recall_output`, and you can `/expand <id>` to see them
|
|
105
|
+
- **Debug mode** — `/debug` shows context breakdown, token counts, and per-call cost estimates before and after each LLM call
|
|
106
|
+
- **Safe mode** — configurable guards that block side effects (DB writes, HTTP mutations, email delivery) during AI code execution
|
|
107
|
+
|
|
108
|
+
## Safety Guards
|
|
109
|
+
|
|
110
|
+
Safety guards prevent AI-generated code from causing side effects. When a guard blocks an operation, the user is prompted to re-run with safe mode disabled.
|
|
111
|
+
|
|
112
|
+
### Built-in Guards
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
RailsConsoleAI.configure do |config|
|
|
116
|
+
config.use_builtin_safety_guard :database_writes # blocks INSERT/UPDATE/DELETE/DROP/etc.
|
|
117
|
+
config.use_builtin_safety_guard :http_mutations # blocks POST/PUT/PATCH/DELETE via Net::HTTP
|
|
118
|
+
config.use_builtin_safety_guard :mailers # disables ActionMailer delivery
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- **`:database_writes`** — intercepts the ActiveRecord connection adapter to block write SQL. Works on Rails 5+ with any database adapter.
|
|
123
|
+
- **`:http_mutations`** — intercepts `Net::HTTP#request` to block non-GET/HEAD/OPTIONS requests. Covers libraries built on Net::HTTP (HTTParty, RestClient, Faraday).
|
|
124
|
+
- **`:mailers`** — sets `ActionMailer::Base.perform_deliveries = false` during execution.
|
|
125
|
+
|
|
126
|
+
### Custom Guards
|
|
127
|
+
|
|
128
|
+
Write your own guards using the around-block pattern:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
RailsConsoleAI.configure do |config|
|
|
132
|
+
config.safety_guard :jobs do |&execute|
|
|
133
|
+
Sidekiq::Testing.fake! { execute.call }
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Raise `RailsConsoleAI::SafetyError` in your app code to trigger the safe mode prompt:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
raise RailsConsoleAI::SafetyError, "Stripe charge blocked"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Toggling Safe Mode
|
|
145
|
+
|
|
146
|
+
- **`/danger`** in interactive mode toggles all guards off/on for the session
|
|
147
|
+
- **`d`** at the `Execute? [y/N/edit/danger]` prompt disables guards for that single execution
|
|
148
|
+
- When a guard blocks an operation, the user is prompted: `Re-run with safe mode disabled? [y/N]`
|
|
149
|
+
|
|
150
|
+
## LLM Providers
|
|
151
|
+
|
|
152
|
+
RailsConsoleAI supports four LLM providers. Each uses a two-tier model system: a default model for speed/cost, and a thinking model activated via `/think` or by saying "think harder".
|
|
153
|
+
|
|
154
|
+
### Anthropic (default)
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
RailsConsoleAI.configure do |config|
|
|
158
|
+
config.provider = :anthropic
|
|
159
|
+
config.api_key = 'sk-ant-...' # or set ANTHROPIC_API_KEY env var
|
|
160
|
+
end
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Default model: `claude-sonnet-4-6`. Thinking model: `claude-opus-4-6`. Prompt caching is enabled automatically.
|
|
164
|
+
|
|
165
|
+
### OpenAI
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
RailsConsoleAI.configure do |config|
|
|
169
|
+
config.provider = :openai
|
|
170
|
+
config.api_key = 'sk-...' # or set OPENAI_API_KEY env var
|
|
171
|
+
end
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Default model: `gpt-5.3-codex`. OpenAI applies prompt caching automatically on their end for prompts over 1024 tokens.
|
|
175
|
+
|
|
176
|
+
### AWS Bedrock
|
|
177
|
+
|
|
178
|
+
Access frontier models (Claude, Mistral, DeepSeek, Llama) via your AWS account with pay-per-token pricing. No API key needed — authentication uses the AWS SDK credential chain (IAM roles, env vars, `~/.aws/credentials`).
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
# Gemfile
|
|
182
|
+
gem 'aws-sdk-bedrockruntime'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
RailsConsoleAI.configure do |config|
|
|
187
|
+
config.provider = :bedrock
|
|
188
|
+
config.bedrock_region = 'us-east-1'
|
|
189
|
+
# config.model = 'us.anthropic.claude-sonnet-4-6' # default
|
|
190
|
+
# config.thinking_model = 'us.anthropic.claude-opus-4-6-v1' # default
|
|
191
|
+
end
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Bedrock model IDs use the `us.` prefix for cross-region inference profiles (required for on-demand Anthropic models). Non-Anthropic models use their bare ID:
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
config.model = 'mistral.devstral-2-123b'
|
|
198
|
+
config.model = 'deepseek.v3.2'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Setup checklist:**
|
|
202
|
+
1. Add `aws-sdk-bedrockruntime` to your Gemfile (it is not a hard dependency of the gem)
|
|
203
|
+
2. Ensure AWS credentials are available to the SDK (env vars, IAM role, or `~/.aws/credentials`)
|
|
204
|
+
3. For Anthropic models, submit the use case form in the Bedrock console (one-time, per account)
|
|
205
|
+
4. The IAM role/user needs `bedrock:InvokeModel` permission
|
|
206
|
+
|
|
207
|
+
Prompt caching is automatically enabled for Anthropic models on Bedrock, reducing cost on multi-turn tool use conversations.
|
|
208
|
+
|
|
209
|
+
### Local (Ollama / vLLM / OpenAI-compatible)
|
|
210
|
+
|
|
211
|
+
Run against a local model server. No API key required.
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
RailsConsoleAI.configure do |config|
|
|
215
|
+
config.provider = :local
|
|
216
|
+
config.local_url = 'http://localhost:11434'
|
|
217
|
+
config.local_model = 'qwen2.5:7b'
|
|
218
|
+
# config.local_api_key = nil # if your server requires auth
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Timeout is automatically raised to 300s minimum for local models to account for slower inference.
|
|
223
|
+
|
|
224
|
+
## Configuration
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
RailsConsoleAI.configure do |config|
|
|
228
|
+
config.provider = :anthropic # :anthropic, :openai, :bedrock, :local
|
|
229
|
+
config.auto_execute = false # true to skip confirmations
|
|
230
|
+
config.session_logging = true # requires ai_setup
|
|
231
|
+
config.temperature = 0.2
|
|
232
|
+
config.timeout = 30 # HTTP timeout in seconds
|
|
233
|
+
config.max_tool_rounds = 200 # safety cap on tool-use loops
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Web UI Authentication
|
|
238
|
+
|
|
239
|
+
The engine mounts a session viewer at `/rails_console_ai`. By default it's open — you can protect it with basic auth or a custom authentication function.
|
|
240
|
+
|
|
241
|
+
### Basic Auth
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
RailsConsoleAI.configure do |config|
|
|
245
|
+
config.admin_username = 'admin'
|
|
246
|
+
config.admin_password = ENV['CONSOLE_AGENT_PASSWORD']
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Custom Authentication
|
|
251
|
+
|
|
252
|
+
For apps with their own auth system, pass a proc to `authenticate`. It runs in the controller context, so you have access to `session`, `request`, `redirect_to`, etc.
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
RailsConsoleAI.configure do |config|
|
|
256
|
+
config.authenticate = proc {
|
|
257
|
+
user = User.find_by(id: session[:user_id])
|
|
258
|
+
unless user&.admin?
|
|
259
|
+
redirect_to '/login'
|
|
260
|
+
end
|
|
261
|
+
}
|
|
262
|
+
end
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
When `authenticate` is set, `admin_username` / `admin_password` are ignored.
|
|
266
|
+
|
|
267
|
+
## Additional Channels
|
|
268
|
+
|
|
269
|
+
RailsConsoleAI can run through different channels beyond the Rails console. Each channel is a separate process that connects the same AI engine to a different interface.
|
|
270
|
+
|
|
271
|
+
### Slack
|
|
272
|
+
|
|
273
|
+
Run RailsConsoleAI as a Slack bot. Each Slack thread becomes an independent AI session with full tool use, multi-step plans, and safety guards always on.
|
|
274
|
+
|
|
275
|
+
#### Slack App Setup
|
|
276
|
+
|
|
277
|
+
1. Create a new app at https://api.slack.com/apps → **Create New App** → **From scratch**
|
|
278
|
+
|
|
279
|
+
2. **Enable Socket Mode** — Settings → Socket Mode → toggle ON. Generate an App-Level Token with the `connections:write` scope. Copy the `xapp-...` token.
|
|
280
|
+
|
|
281
|
+
3. **Bot Token Scopes** — OAuth & Permissions → Bot Token Scopes, add:
|
|
282
|
+
- `chat:write`
|
|
283
|
+
- `channels:history` (public channels)
|
|
284
|
+
- `groups:history` (private channels, optional)
|
|
285
|
+
- `im:history` (direct messages)
|
|
286
|
+
- `users:read`
|
|
287
|
+
|
|
288
|
+
4. **Event Subscriptions** — Event Subscriptions → toggle ON, then under "Subscribe to bot events" add:
|
|
289
|
+
- `message.channels` (public channels)
|
|
290
|
+
- `message.groups` (private channels, optional)
|
|
291
|
+
- `message.im` (direct messages)
|
|
292
|
+
|
|
293
|
+
5. **App Home** — Show Tabs → toggle **Messages Tab** ON and check **"Allow users to send Slash commands and messages from the messages tab"**
|
|
294
|
+
|
|
295
|
+
6. **Install to workspace** — Install App → Install to Workspace. Copy the `xoxb-...` Bot User OAuth Token.
|
|
296
|
+
|
|
297
|
+
7. **Invite the bot** to a channel with `/invite @YourBotName`, or DM it directly.
|
|
298
|
+
|
|
299
|
+
#### Configuration
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
RailsConsoleAI.configure do |config|
|
|
303
|
+
config.slack_bot_token = ENV['SLACK_BOT_TOKEN'] # xoxb-...
|
|
304
|
+
config.slack_app_token = ENV['SLACK_APP_TOKEN'] # xapp-...
|
|
305
|
+
|
|
306
|
+
# Optional: restrict to specific Slack channel IDs
|
|
307
|
+
# config.slack_channel_ids = 'C1234567890,C0987654321'
|
|
308
|
+
|
|
309
|
+
# Required: which users the bot responds to (by display name)
|
|
310
|
+
config.slack_allowed_usernames = ['alice', 'bob'] # or 'ALL' for everyone
|
|
311
|
+
end
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### Running
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
bundle exec rake rails_console_ai:slack
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
This starts a long-running process (run it separately from your web server). Each new message creates a session; threaded replies continue the conversation. The bot auto-executes code with safety guards always enabled — there is no `/danger` equivalent in Slack.
|
|
321
|
+
|
|
322
|
+
## Requirements
|
|
323
|
+
|
|
324
|
+
Ruby >= 2.5, Rails >= 5.0, Faraday >= 1.0. For Bedrock: `aws-sdk-bedrockruntime` (loaded lazily, not a hard dependency).
|
|
325
|
+
|
|
326
|
+
## License
|
|
327
|
+
|
|
328
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module RailsConsoleAI
|
|
2
|
+
class ApplicationController < ActionController::Base
|
|
3
|
+
protect_from_forgery with: :exception
|
|
4
|
+
|
|
5
|
+
before_action :rails_console_ai_authenticate!
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def rails_console_ai_authenticate!
|
|
10
|
+
if (auth = RailsConsoleAI.configuration.authenticate)
|
|
11
|
+
instance_exec(&auth)
|
|
12
|
+
else
|
|
13
|
+
username = RailsConsoleAI.configuration.admin_username
|
|
14
|
+
password = RailsConsoleAI.configuration.admin_password
|
|
15
|
+
|
|
16
|
+
unless username && password
|
|
17
|
+
head :unauthorized
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
authenticate_or_request_with_http_basic('RailsConsoleAI Admin') do |u, p|
|
|
22
|
+
ActiveSupport::SecurityUtils.secure_compare(u, username) &
|
|
23
|
+
ActiveSupport::SecurityUtils.secure_compare(p, password)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module RailsConsoleAI
|
|
2
|
+
class SessionsController < ApplicationController
|
|
3
|
+
PER_PAGE = 50
|
|
4
|
+
|
|
5
|
+
def index
|
|
6
|
+
@page = [params[:page].to_i, 1].max
|
|
7
|
+
@total = Session.count
|
|
8
|
+
@total_pages = (@total / PER_PAGE.to_f).ceil
|
|
9
|
+
@sessions = Session.recent.offset((@page - 1) * PER_PAGE).limit(PER_PAGE)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def show
|
|
13
|
+
@session = Session.find(params[:id])
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module RailsConsoleAI
|
|
2
|
+
module SessionsHelper
|
|
3
|
+
def estimated_cost(session)
|
|
4
|
+
pricing = Configuration::PRICING[session.model]
|
|
5
|
+
return nil unless pricing
|
|
6
|
+
|
|
7
|
+
(session.input_tokens * pricing[:input]) + (session.output_tokens * pricing[:output])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def format_cost(session)
|
|
11
|
+
cost = estimated_cost(session)
|
|
12
|
+
return '-' unless cost
|
|
13
|
+
|
|
14
|
+
cost < 0.01 ? "<$0.01" : "$#{'%.2f' % cost}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Convert ANSI escape codes to HTML spans for terminal-style rendering
|
|
18
|
+
def ansi_to_html(text)
|
|
19
|
+
return '' if text.nil? || text.empty?
|
|
20
|
+
|
|
21
|
+
color_map = {
|
|
22
|
+
'30' => '#000', '31' => '#e74c3c', '32' => '#2ecc71', '33' => '#f39c12',
|
|
23
|
+
'34' => '#3498db', '35' => '#9b59b6', '36' => '#1abc9c', '37' => '#ecf0f1',
|
|
24
|
+
'90' => '#888', '91' => '#ff6b6b', '92' => '#69db7c', '93' => '#ffd43b',
|
|
25
|
+
'94' => '#74c0fc', '95' => '#da77f2', '96' => '#63e6be', '97' => '#fff'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
escaped = h(text).to_str
|
|
29
|
+
|
|
30
|
+
# Process ANSI codes: colors, bold, dim, reset
|
|
31
|
+
escaped.gsub!(/\e\[([0-9;]+)m/) do
|
|
32
|
+
codes = $1.split(';')
|
|
33
|
+
if codes.include?('0') || $1 == '0'
|
|
34
|
+
'</span>'
|
|
35
|
+
else
|
|
36
|
+
styles = []
|
|
37
|
+
codes.each do |code|
|
|
38
|
+
case code
|
|
39
|
+
when '1' then styles << 'font-weight:bold'
|
|
40
|
+
when '2' then styles << 'opacity:0.6'
|
|
41
|
+
when '4' then styles << 'text-decoration:underline'
|
|
42
|
+
else
|
|
43
|
+
styles << "color:#{color_map[code]}" if color_map[code]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
styles.empty? ? '' : "<span style=\"#{styles.join(';')}\">"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Clean up any remaining escape sequences
|
|
51
|
+
escaped.gsub!(/\e\[[0-9;]*[A-Za-z]/, '')
|
|
52
|
+
|
|
53
|
+
escaped.html_safe
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module RailsConsoleAI
|
|
2
|
+
class Session < ActiveRecord::Base
|
|
3
|
+
self.table_name = 'rails_console_ai_sessions'
|
|
4
|
+
|
|
5
|
+
validates :query, presence: true
|
|
6
|
+
validates :conversation, presence: true
|
|
7
|
+
validates :mode, presence: true, inclusion: { in: %w[one_shot interactive explain slack] }
|
|
8
|
+
|
|
9
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
10
|
+
scope :named, ->(name) { where(name: name) }
|
|
11
|
+
scope :search, ->(q) { where("name LIKE ? OR query LIKE ?", "%#{q}%", "%#{q}%") }
|
|
12
|
+
|
|
13
|
+
def self.connection
|
|
14
|
+
klass = RailsConsoleAI.configuration.connection_class
|
|
15
|
+
if klass
|
|
16
|
+
klass = Object.const_get(klass) if klass.is_a?(String)
|
|
17
|
+
klass.connection
|
|
18
|
+
else
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>RailsConsoleAI Admin</title>
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<style>
|
|
7
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
10
|
+
background: #f5f5f5;
|
|
11
|
+
color: #333;
|
|
12
|
+
line-height: 1.6;
|
|
13
|
+
}
|
|
14
|
+
.header {
|
|
15
|
+
background: #1a1a2e;
|
|
16
|
+
color: #fff;
|
|
17
|
+
padding: 16px 24px;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: space-between;
|
|
21
|
+
}
|
|
22
|
+
.header h1 { font-size: 18px; font-weight: 600; }
|
|
23
|
+
.header a { color: #8be9fd; text-decoration: none; font-size: 14px; }
|
|
24
|
+
.container { max-width: 1200px; margin: 24px auto; padding: 0 24px; }
|
|
25
|
+
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
26
|
+
th { background: #f8f9fa; text-align: left; padding: 12px 16px; font-size: 13px; font-weight: 600; color: #555; border-bottom: 2px solid #e9ecef; }
|
|
27
|
+
td { padding: 10px 16px; border-bottom: 1px solid #f0f0f0; font-size: 14px; }
|
|
28
|
+
tr:hover td { background: #f8f9fa; }
|
|
29
|
+
a { color: #4a6fa5; text-decoration: none; }
|
|
30
|
+
a:hover { text-decoration: underline; }
|
|
31
|
+
.badge {
|
|
32
|
+
display: inline-block; padding: 2px 8px; border-radius: 12px;
|
|
33
|
+
font-size: 11px; font-weight: 600; text-transform: uppercase;
|
|
34
|
+
}
|
|
35
|
+
.badge-one_shot { background: #d4edda; color: #155724; }
|
|
36
|
+
.badge-interactive { background: #cce5ff; color: #004085; }
|
|
37
|
+
.badge-explain { background: #fff3cd; color: #856404; }
|
|
38
|
+
.meta-card {
|
|
39
|
+
background: #fff; border-radius: 8px; padding: 20px;
|
|
40
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 24px;
|
|
41
|
+
}
|
|
42
|
+
.meta-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }
|
|
43
|
+
.meta-item label { font-size: 11px; font-weight: 600; color: #888; text-transform: uppercase; display: block; }
|
|
44
|
+
.meta-item span { font-size: 14px; color: #333; }
|
|
45
|
+
.terminal {
|
|
46
|
+
background: #1e1e1e; border-radius: 8px; overflow: hidden;
|
|
47
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
48
|
+
}
|
|
49
|
+
.terminal pre {
|
|
50
|
+
background: transparent; color: #d4d4d4;
|
|
51
|
+
padding: 16px 20px; margin: 0;
|
|
52
|
+
font-family: "SF Mono", "Monaco", "Menlo", "Consolas", monospace;
|
|
53
|
+
font-size: 13px; line-height: 1.5;
|
|
54
|
+
overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;
|
|
55
|
+
}
|
|
56
|
+
.mono { font-family: "SF Mono", "Monaco", "Menlo", "Consolas", monospace; font-size: 13px; }
|
|
57
|
+
.query-cell { max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
58
|
+
.text-muted { color: #888; }
|
|
59
|
+
.back-link { margin-bottom: 16px; display: inline-block; }
|
|
60
|
+
.check { color: #28a745; }
|
|
61
|
+
.cross { color: #dc3545; }
|
|
62
|
+
.pagination {
|
|
63
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
64
|
+
margin-top: 16px; padding: 12px 0;
|
|
65
|
+
}
|
|
66
|
+
.pagination a {
|
|
67
|
+
padding: 6px 14px; background: #fff; border: 1px solid #ddd; border-radius: 4px;
|
|
68
|
+
font-size: 13px; color: #4a6fa5;
|
|
69
|
+
}
|
|
70
|
+
.pagination a:hover { background: #f8f9fa; text-decoration: none; }
|
|
71
|
+
.pagination .disabled { padding: 6px 14px; font-size: 13px; color: #ccc; }
|
|
72
|
+
.pagination .page-info { font-size: 13px; color: #888; }
|
|
73
|
+
</style>
|
|
74
|
+
</head>
|
|
75
|
+
<body>
|
|
76
|
+
<div class="header">
|
|
77
|
+
<h1>RailsConsoleAI Admin</h1>
|
|
78
|
+
<a href="<%= rails_console_ai.root_path %>">Sessions</a>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="container">
|
|
81
|
+
<%= yield %>
|
|
82
|
+
</div>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|