console_agent 0.2.0 → 0.3.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 +4 -4
- data/README.md +117 -231
- data/app/controllers/console_agent/application_controller.rb +21 -0
- data/app/controllers/console_agent/sessions_controller.rb +16 -0
- data/app/helpers/console_agent/sessions_helper.rb +42 -0
- data/app/models/console_agent/session.rb +23 -0
- data/app/views/console_agent/sessions/index.html.erb +57 -0
- data/app/views/console_agent/sessions/show.html.erb +56 -0
- data/app/views/layouts/console_agent/application.html.erb +83 -0
- data/config/routes.rb +4 -0
- data/lib/console_agent/configuration.rb +8 -4
- data/lib/console_agent/console_methods.rb +125 -5
- data/lib/console_agent/context_builder.rb +12 -132
- data/lib/console_agent/engine.rb +5 -0
- data/lib/console_agent/executor.rb +19 -1
- data/lib/console_agent/repl.rb +299 -34
- data/lib/console_agent/session_logger.rb +79 -0
- data/lib/console_agent/tools/registry.rb +156 -2
- data/lib/console_agent/version.rb +1 -1
- data/lib/console_agent.rb +125 -3
- data/lib/generators/console_agent/templates/initializer.rb +14 -6
- metadata +11 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 661ab1a997f36d2b10ea9649aa41367aca039c859c3e2afa01c3cbf8b8dd9345
|
|
4
|
+
data.tar.gz: 5cfe513d78a1785b7199fb0ebbbc70e825092e49e4051caf390e3ccb7ccf23c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d7fd63a81886c7abbf4f82db677b6b4bb9151760c378901306e789b3619d7ca31b743f7f4a14a9a28335e775877243b0c4ff8b8bd6cc6acee0c8a02c270eccc
|
|
7
|
+
data.tar.gz: 3e0308bd89e0421a4a29dbe8c7b7be6afd584a504559a456f830e1c9a268517d131d6dcf00417347f5f3ea80a434bf9211e0515771fc3e43686f0117bf401f9b
|
data/README.md
CHANGED
|
@@ -1,60 +1,40 @@
|
|
|
1
1
|
# ConsoleAgent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Claude Code, embedded in your Rails console.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Ask questions in plain English. The AI explores your schema, models, and source code on its own, then writes and runs the Ruby code for you.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Quick Start
|
|
10
|
-
|
|
11
|
-
Add to your Gemfile:
|
|
7
|
+
## Install
|
|
12
8
|
|
|
13
9
|
```ruby
|
|
10
|
+
# Gemfile
|
|
14
11
|
gem 'console_agent', group: :development
|
|
15
12
|
```
|
|
16
13
|
|
|
17
|
-
Run the install generator:
|
|
18
|
-
|
|
19
14
|
```bash
|
|
20
15
|
bundle install
|
|
21
16
|
rails generate console_agent:install
|
|
22
17
|
```
|
|
23
18
|
|
|
24
|
-
Set your API key (
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
# Option A: environment variable
|
|
28
|
-
export ANTHROPIC_API_KEY=sk-ant-...
|
|
29
|
-
|
|
30
|
-
# Option B: in the initializer
|
|
31
|
-
# config.api_key = 'sk-ant-...'
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Open a console and go:
|
|
19
|
+
Set your API key in the generated initializer or as an env var (`ANTHROPIC_API_KEY`):
|
|
35
20
|
|
|
36
21
|
```ruby
|
|
37
|
-
|
|
38
|
-
|
|
22
|
+
# config/initializers/console_agent.rb
|
|
23
|
+
ConsoleAgent.configure do |config|
|
|
24
|
+
config.api_key = 'sk-ant-...'
|
|
25
|
+
end
|
|
39
26
|
```
|
|
40
27
|
|
|
41
|
-
|
|
28
|
+
To set up session logging (OPTIONAL), create the table from the console:
|
|
42
29
|
|
|
43
30
|
```ruby
|
|
44
|
-
ConsoleAgent.
|
|
31
|
+
ConsoleAgent.setup!
|
|
32
|
+
# => ConsoleAgent: created console_agent_sessions table.
|
|
45
33
|
```
|
|
46
34
|
|
|
47
|
-
|
|
35
|
+
To reset the table (e.g. after upgrading), run `ConsoleAgent.teardown!` then `ConsoleAgent.setup!`.
|
|
48
36
|
|
|
49
|
-
|
|
50
|
-
|---------|-------------|
|
|
51
|
-
| `ai "query"` | Ask a question, review generated code, confirm before executing |
|
|
52
|
-
| `ai! "query"` | Ask a question and enter interactive mode for follow-ups |
|
|
53
|
-
| `ai!` | Enter interactive mode (type `exit` to leave) |
|
|
54
|
-
| `ai? "query"` | Explain only — shows code but never executes |
|
|
55
|
-
| `ai_status` | Print current configuration summary |
|
|
56
|
-
|
|
57
|
-
### Example Session
|
|
37
|
+
## Usage
|
|
58
38
|
|
|
59
39
|
```
|
|
60
40
|
irb> ai "find the 5 most recent orders over $100"
|
|
@@ -63,272 +43,178 @@ irb> ai "find the 5 most recent orders over $100"
|
|
|
63
43
|
12 tables: users, orders, line_items, products...
|
|
64
44
|
-> describe_table("orders")
|
|
65
45
|
8 columns
|
|
66
|
-
-> describe_model("Order")
|
|
67
|
-
4 associations, 2 validations
|
|
68
|
-
|
|
69
|
-
You can query recent high-value orders like this:
|
|
70
46
|
|
|
71
47
|
Order.where("total > ?", 100).order(created_at: :desc).limit(5)
|
|
72
48
|
|
|
73
|
-
[tokens in: 1,240 | out: 85 | total: 1,325]
|
|
74
49
|
Execute? [y/N/edit] y
|
|
75
50
|
=> [#<Order id: 4821, ...>, ...]
|
|
76
51
|
```
|
|
77
52
|
|
|
78
|
-
|
|
53
|
+
The AI calls tools behind the scenes to learn your app — schema, models, associations, source code — so it writes accurate queries without you providing any context.
|
|
79
54
|
|
|
80
|
-
|
|
81
|
-
irb> ai!
|
|
82
|
-
ConsoleAgent interactive mode. Type 'exit' or 'quit' to leave.
|
|
83
|
-
ai> show me all tables
|
|
84
|
-
Thinking...
|
|
85
|
-
-> list_tables
|
|
86
|
-
12 tables: users, orders, line_items, products...
|
|
87
|
-
...
|
|
88
|
-
ai> now count orders by status
|
|
89
|
-
...
|
|
90
|
-
ai> exit
|
|
91
|
-
[session totals — in: 3,200 | out: 410 | total: 3,610]
|
|
92
|
-
Left ConsoleAgent interactive mode.
|
|
93
|
-
```
|
|
55
|
+
### Commands
|
|
94
56
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
Provider: anthropic
|
|
101
|
-
Model: claude-opus-4-6
|
|
102
|
-
API key: sk-ant-...a3b4
|
|
103
|
-
Context mode: smart
|
|
104
|
-
Max tokens: 4096
|
|
105
|
-
Temperature: 0.2
|
|
106
|
-
Timeout: 30s
|
|
107
|
-
Max tool rounds:10
|
|
108
|
-
Auto-execute: false
|
|
109
|
-
Debug: false
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## Configuration
|
|
113
|
-
|
|
114
|
-
The install generator creates `config/initializers/console_agent.rb`:
|
|
115
|
-
|
|
116
|
-
```ruby
|
|
117
|
-
ConsoleAgent.configure do |config|
|
|
118
|
-
# LLM provider: :anthropic or :openai
|
|
119
|
-
config.provider = :anthropic
|
|
120
|
-
|
|
121
|
-
# API key (or set ANTHROPIC_API_KEY / OPENAI_API_KEY env var)
|
|
122
|
-
# config.api_key = 'sk-...'
|
|
123
|
-
|
|
124
|
-
# Model override (defaults: claude-opus-4-6 for Anthropic, gpt-5.3-codex for OpenAI)
|
|
125
|
-
# config.model = 'claude-opus-4-6'
|
|
126
|
-
|
|
127
|
-
# Context mode:
|
|
128
|
-
# :smart - (default) LLM uses tools to fetch schema/model/code on demand
|
|
129
|
-
# :full - sends all schema, models, and routes every time
|
|
130
|
-
config.context_mode = :smart
|
|
131
|
-
|
|
132
|
-
# Max tokens for LLM response
|
|
133
|
-
config.max_tokens = 4096
|
|
134
|
-
|
|
135
|
-
# Temperature (0.0 - 1.0)
|
|
136
|
-
config.temperature = 0.2
|
|
137
|
-
|
|
138
|
-
# Auto-execute generated code without confirmation
|
|
139
|
-
config.auto_execute = false
|
|
140
|
-
|
|
141
|
-
# Max tool-use rounds per query in :smart mode
|
|
142
|
-
config.max_tool_rounds = 10
|
|
143
|
-
|
|
144
|
-
# HTTP timeout in seconds
|
|
145
|
-
config.timeout = 30
|
|
57
|
+
| Command | What it does |
|
|
58
|
+
|---------|-------------|
|
|
59
|
+
| `ai "query"` | One-shot: ask, review code, confirm |
|
|
60
|
+
| `ai! "query"` | Interactive: ask and keep chatting |
|
|
61
|
+
| `ai? "query"` | Explain only, never executes |
|
|
146
62
|
|
|
147
|
-
|
|
148
|
-
# config.debug = true
|
|
149
|
-
end
|
|
150
|
-
```
|
|
63
|
+
### Multi-Step Plans
|
|
151
64
|
|
|
152
|
-
|
|
65
|
+
For complex tasks, the AI builds a plan and executes it step by step:
|
|
153
66
|
|
|
154
|
-
```ruby
|
|
155
|
-
ConsoleAgent.configure { |c| c.api_key = 'sk-ant-...' }
|
|
156
|
-
ConsoleAgent.configure { |c| c.debug = true }
|
|
157
|
-
ConsoleAgent.configure { |c| c.provider = :openai; c.api_key = 'sk-...' }
|
|
158
67
|
```
|
|
68
|
+
ai> get the most recent salesforce token and count events via the API
|
|
69
|
+
Thinking...
|
|
70
|
+
-> describe_table("oauth2_tokens")
|
|
71
|
+
28 columns
|
|
72
|
+
-> read_file("lib/salesforce_api.rb")
|
|
73
|
+
202 lines
|
|
159
74
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
- **list_models** / **describe_model** — ActiveRecord associations, validations
|
|
168
|
-
- **list_files** / **read_file** / **search_code** — browse app source code
|
|
169
|
-
|
|
170
|
-
This keeps token usage low (~1-3K per query) even for large apps with hundreds of tables.
|
|
171
|
-
|
|
172
|
-
### Full Mode
|
|
75
|
+
Plan (2 steps):
|
|
76
|
+
1. Find the most recent active Salesforce OAuth2 token
|
|
77
|
+
token = Oauth2Token.where(provider: "salesforce", active: true)
|
|
78
|
+
.order(updated_at: :desc).first
|
|
79
|
+
2. Query event count via SOQL
|
|
80
|
+
api = SalesforceApi.new(step1)
|
|
81
|
+
api.query("SELECT COUNT(Id) FROM Event")
|
|
173
82
|
|
|
174
|
-
|
|
83
|
+
Accept plan? [y/N/a(uto)] a
|
|
84
|
+
Step 1/2: Find the most recent active Salesforce OAuth2 token
|
|
85
|
+
...
|
|
86
|
+
=> #<Oauth2Token id: 1, provider: "salesforce", ...>
|
|
175
87
|
|
|
176
|
-
|
|
177
|
-
|
|
88
|
+
Step 2/2: Query event count via SOQL
|
|
89
|
+
...
|
|
90
|
+
=> [{"expr0"=>42}]
|
|
178
91
|
```
|
|
179
92
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
| Tool | Description |
|
|
183
|
-
|------|-------------|
|
|
184
|
-
| `list_tables` | All database table names |
|
|
185
|
-
| `describe_table` | Columns, types, and indexes for one table |
|
|
186
|
-
| `list_models` | All model names with association names |
|
|
187
|
-
| `describe_model` | Associations, validations, scopes for one model |
|
|
188
|
-
| `list_files` | Ruby files in a directory |
|
|
189
|
-
| `read_file` | Read a source file (capped at 200 lines) |
|
|
190
|
-
| `search_code` | Grep for a pattern across Ruby files |
|
|
191
|
-
| `ask_user` | Ask the console user a clarifying question |
|
|
192
|
-
| `save_memory` | Persist a learned fact for future sessions |
|
|
193
|
-
| `recall_memories` | Search saved memories |
|
|
194
|
-
| `load_skill` | Load full instructions for a skill |
|
|
195
|
-
|
|
196
|
-
The LLM decides which tools to call based on your question. You can see the tool calls happening in real time.
|
|
93
|
+
Each step's return value is available to later steps as `step1`, `step2`, etc.
|
|
197
94
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
95
|
+
Plan prompt options:
|
|
96
|
+
- **y** — accept, then confirm each step one at a time
|
|
97
|
+
- **a** — accept and auto-run all steps (stays in manual mode for future queries)
|
|
98
|
+
- **N** — decline; you're asked what to change and the AI revises
|
|
201
99
|
|
|
202
100
|
### Memories
|
|
203
101
|
|
|
204
|
-
|
|
102
|
+
The AI remembers what it learns about your codebase across sessions:
|
|
205
103
|
|
|
206
104
|
```
|
|
207
|
-
ai> how
|
|
208
|
-
Thinking...
|
|
209
|
-
-> search_code("shard")
|
|
210
|
-
Found 50 matches
|
|
105
|
+
ai> how does sharding work?
|
|
211
106
|
-> read_file("config/initializers/sharding.rb")
|
|
212
|
-
202 lines
|
|
213
107
|
-> save_memory("Sharding architecture")
|
|
214
|
-
Memory saved
|
|
108
|
+
Memory saved
|
|
215
109
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
User.count
|
|
219
|
-
|
|
220
|
-
[tokens in: 2,340 | out: 120 | total: 2,460]
|
|
110
|
+
This app uses database-per-shard. User.count returns the current shard only.
|
|
221
111
|
```
|
|
222
112
|
|
|
223
|
-
Next
|
|
224
|
-
|
|
225
|
-
```
|
|
226
|
-
ai> count users on this shard
|
|
227
|
-
Thinking...
|
|
113
|
+
Next time, it already knows — no re-reading files, fewer tokens.
|
|
228
114
|
|
|
229
|
-
|
|
115
|
+
### Interactive Mode
|
|
230
116
|
|
|
231
|
-
[tokens in: 580 | out: 45 | total: 625]
|
|
232
117
|
```
|
|
118
|
+
irb> ai!
|
|
119
|
+
ConsoleAgent interactive mode. Type 'exit' to leave.
|
|
120
|
+
Auto-execute: OFF (Shift-Tab or /auto to toggle)
|
|
233
121
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
created_at: "2026-02-27T10:30:00Z"
|
|
122
|
+
ai> show me all tables
|
|
123
|
+
...
|
|
124
|
+
ai> count orders by status
|
|
125
|
+
...
|
|
126
|
+
ai> /auto
|
|
127
|
+
Auto-execute: ON
|
|
128
|
+
ai> delete cancelled orders older than 90 days
|
|
129
|
+
...
|
|
130
|
+
ai> exit
|
|
244
131
|
```
|
|
245
132
|
|
|
246
|
-
|
|
133
|
+
Toggle `/auto` to skip confirmation prompts. `/debug` shows raw API traffic. `/usage` shows token stats.
|
|
247
134
|
|
|
248
|
-
###
|
|
135
|
+
### Sessions
|
|
249
136
|
|
|
250
|
-
|
|
137
|
+
Sessions are saved automatically when session logging is enabled. You can name, list, and resume them.
|
|
251
138
|
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
139
|
+
```
|
|
140
|
+
ai> /name sf_user_123_calendar
|
|
141
|
+
Session named: sf_user_123_calendar
|
|
142
|
+
ai> exit
|
|
143
|
+
Session #42 saved.
|
|
144
|
+
Resume with: ai_resume "sf_user_123_calendar"
|
|
145
|
+
Left ConsoleAgent interactive mode.
|
|
146
|
+
```
|
|
258
147
|
|
|
259
|
-
|
|
148
|
+
List recent sessions:
|
|
260
149
|
|
|
261
|
-
This app uses Apartment with one database per shard.
|
|
262
|
-
- `User.count` only counts the current shard
|
|
263
|
-
- To count across all shards: `Shard.all.sum { |s| s.switch { User.count } }`
|
|
264
|
-
- Current shard: `Apartment::Tenant.current`
|
|
265
150
|
```
|
|
151
|
+
irb> ai_sessions
|
|
152
|
+
[Sessions — showing 3]
|
|
266
153
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
### Storage
|
|
154
|
+
#42 sf_user_123_calendar find user 123 with calendar issues
|
|
155
|
+
[interactive] 5m ago 2340 tokens
|
|
270
156
|
|
|
271
|
-
|
|
157
|
+
#41 count all active users
|
|
158
|
+
[one_shot] 1h ago 850 tokens
|
|
272
159
|
|
|
273
|
-
|
|
160
|
+
#40 debug_payments explain payment flow
|
|
161
|
+
[interactive] 2h ago 4100 tokens
|
|
274
162
|
|
|
275
|
-
|
|
163
|
+
Use ai_resume(id_or_name) to resume a session.
|
|
164
|
+
```
|
|
276
165
|
|
|
277
|
-
|
|
166
|
+
Resume a session by name or ID — previous output is replayed, then you continue where you left off:
|
|
278
167
|
|
|
279
|
-
```ruby
|
|
280
|
-
ConsoleAgent.configure do |config|
|
|
281
|
-
config.storage_adapter = MyS3Storage.new(bucket: 'my-bucket', prefix: 'console_agent/')
|
|
282
|
-
end
|
|
283
168
|
```
|
|
169
|
+
irb> ai_resume "sf_user_123_calendar"
|
|
170
|
+
--- Replaying previous session output ---
|
|
171
|
+
ai> find user 123 with calendar issues
|
|
172
|
+
...previous output...
|
|
173
|
+
--- End of previous output ---
|
|
284
174
|
|
|
285
|
-
|
|
175
|
+
ConsoleAgent interactive mode (sf_user_123_calendar). Type 'exit' to leave.
|
|
176
|
+
ai> now check their calendar sync status
|
|
177
|
+
...
|
|
178
|
+
```
|
|
286
179
|
|
|
287
|
-
|
|
180
|
+
Name or rename a session after the fact:
|
|
288
181
|
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
config.skills_enabled = false
|
|
293
|
-
end
|
|
182
|
+
```
|
|
183
|
+
irb> ai_name 41, "active_user_count"
|
|
184
|
+
Session #41 named: active_user_count
|
|
294
185
|
```
|
|
295
186
|
|
|
296
|
-
|
|
187
|
+
Filter sessions by search term:
|
|
297
188
|
|
|
298
|
-
|
|
189
|
+
```
|
|
190
|
+
irb> ai_sessions 20, search: "salesforce"
|
|
191
|
+
```
|
|
299
192
|
|
|
300
|
-
|
|
193
|
+
If you have an existing `console_agent_sessions` table, run `ConsoleAgent.migrate!` to add the `name` column.
|
|
301
194
|
|
|
302
|
-
|
|
195
|
+
## Configuration
|
|
303
196
|
|
|
304
|
-
|
|
197
|
+
All settings live in `config/initializers/console_agent.rb` and can be changed at runtime:
|
|
305
198
|
|
|
306
199
|
```ruby
|
|
307
200
|
ConsoleAgent.configure do |config|
|
|
308
|
-
config.provider = :openai
|
|
309
|
-
config.
|
|
201
|
+
config.provider = :anthropic # or :openai
|
|
202
|
+
config.auto_execute = false # true to skip confirmations
|
|
203
|
+
config.max_tokens = 4096 # max tokens per LLM response
|
|
204
|
+
config.max_tool_rounds = 10 # max tool calls per query
|
|
205
|
+
config.session_logging = true # log sessions to DB (run ConsoleAgent.setup!)
|
|
310
206
|
end
|
|
311
207
|
```
|
|
312
208
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
To develop the gem locally against a Rails app, use a path reference in your Gemfile:
|
|
316
|
-
|
|
317
|
-
```ruby
|
|
318
|
-
gem 'console_agent', path: '/path/to/console_agent'
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
Switch back to the published gem when you're done:
|
|
209
|
+
For the admin UI, mount the engine:
|
|
322
210
|
|
|
323
211
|
```ruby
|
|
324
|
-
|
|
212
|
+
mount ConsoleAgent::Engine => '/console_agent'
|
|
325
213
|
```
|
|
326
214
|
|
|
327
215
|
## Requirements
|
|
328
216
|
|
|
329
|
-
- Ruby >= 2.5
|
|
330
|
-
- Rails >= 5.0
|
|
331
|
-
- Faraday >= 1.0
|
|
217
|
+
- Ruby >= 2.5, Rails >= 5.0, Faraday >= 1.0
|
|
332
218
|
|
|
333
219
|
## License
|
|
334
220
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
2
|
+
class ApplicationController < ActionController::Base
|
|
3
|
+
protect_from_forgery with: :exception
|
|
4
|
+
|
|
5
|
+
before_action :authenticate!
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def authenticate!
|
|
10
|
+
username = ConsoleAgent.configuration.admin_username
|
|
11
|
+
password = ConsoleAgent.configuration.admin_password
|
|
12
|
+
|
|
13
|
+
return unless username && password
|
|
14
|
+
|
|
15
|
+
authenticate_or_request_with_http_basic('ConsoleAgent Admin') do |u, p|
|
|
16
|
+
ActiveSupport::SecurityUtils.secure_compare(u, username) &
|
|
17
|
+
ActiveSupport::SecurityUtils.secure_compare(p, password)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
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,42 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
2
|
+
module SessionsHelper
|
|
3
|
+
# Convert ANSI escape codes to HTML spans for terminal-style rendering
|
|
4
|
+
def ansi_to_html(text)
|
|
5
|
+
return '' if text.nil? || text.empty?
|
|
6
|
+
|
|
7
|
+
color_map = {
|
|
8
|
+
'30' => '#000', '31' => '#e74c3c', '32' => '#2ecc71', '33' => '#f39c12',
|
|
9
|
+
'34' => '#3498db', '35' => '#9b59b6', '36' => '#1abc9c', '37' => '#ecf0f1',
|
|
10
|
+
'90' => '#888', '91' => '#ff6b6b', '92' => '#69db7c', '93' => '#ffd43b',
|
|
11
|
+
'94' => '#74c0fc', '95' => '#da77f2', '96' => '#63e6be', '97' => '#fff'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
escaped = h(text).to_str
|
|
15
|
+
|
|
16
|
+
# Process ANSI codes: colors, bold, dim, reset
|
|
17
|
+
escaped.gsub!(/\e\[([0-9;]+)m/) do
|
|
18
|
+
codes = $1.split(';')
|
|
19
|
+
if codes.include?('0') || $1 == '0'
|
|
20
|
+
'</span>'
|
|
21
|
+
else
|
|
22
|
+
styles = []
|
|
23
|
+
codes.each do |code|
|
|
24
|
+
case code
|
|
25
|
+
when '1' then styles << 'font-weight:bold'
|
|
26
|
+
when '2' then styles << 'opacity:0.6'
|
|
27
|
+
when '4' then styles << 'text-decoration:underline'
|
|
28
|
+
else
|
|
29
|
+
styles << "color:#{color_map[code]}" if color_map[code]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
styles.empty? ? '' : "<span style=\"#{styles.join(';')}\">"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Clean up any remaining escape sequences
|
|
37
|
+
escaped.gsub!(/\e\[[0-9;]*[A-Za-z]/, '')
|
|
38
|
+
|
|
39
|
+
escaped.html_safe
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
2
|
+
class Session < ActiveRecord::Base
|
|
3
|
+
self.table_name = 'console_agent_sessions'
|
|
4
|
+
|
|
5
|
+
validates :query, presence: true
|
|
6
|
+
validates :conversation, presence: true
|
|
7
|
+
validates :mode, presence: true, inclusion: { in: %w[one_shot interactive explain] }
|
|
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 = ConsoleAgent.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,57 @@
|
|
|
1
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
|
2
|
+
<h2 style="font-size: 20px;">Sessions</h2>
|
|
3
|
+
<span class="text-muted" style="font-size: 14px;"><%= @total %> total</span>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<% if @sessions.empty? %>
|
|
7
|
+
<div class="meta-card">
|
|
8
|
+
<p class="text-muted">No sessions recorded yet.</p>
|
|
9
|
+
</div>
|
|
10
|
+
<% else %>
|
|
11
|
+
<table>
|
|
12
|
+
<thead>
|
|
13
|
+
<tr>
|
|
14
|
+
<th>Time</th>
|
|
15
|
+
<th>User</th>
|
|
16
|
+
<th>Name</th>
|
|
17
|
+
<th>Query</th>
|
|
18
|
+
<th>Mode</th>
|
|
19
|
+
<th>Tokens</th>
|
|
20
|
+
<th>Executed?</th>
|
|
21
|
+
<th>Duration</th>
|
|
22
|
+
</tr>
|
|
23
|
+
</thead>
|
|
24
|
+
<tbody>
|
|
25
|
+
<% @sessions.each do |session| %>
|
|
26
|
+
<tr>
|
|
27
|
+
<td class="mono"><%= session.created_at.strftime('%Y-%m-%d %H:%M') %></td>
|
|
28
|
+
<td><%= session.user_name %></td>
|
|
29
|
+
<td><%= session.name.present? ? session.name : '-' %></td>
|
|
30
|
+
<td><a href="<%= console_agent.session_path(session) %>"><%= truncate(session.query, length: 80) %></a></td>
|
|
31
|
+
<td><span class="badge badge-<%= session.mode %>"><%= session.mode %></span></td>
|
|
32
|
+
<td class="mono"><%= session.input_tokens + session.output_tokens %></td>
|
|
33
|
+
<td><%= session.executed? ? '<span class="check">Yes</span>'.html_safe : '<span class="cross">No</span>'.html_safe %></td>
|
|
34
|
+
<td class="mono"><%= session.duration_ms ? "#{session.duration_ms}ms" : '-' %></td>
|
|
35
|
+
</tr>
|
|
36
|
+
<% end %>
|
|
37
|
+
</tbody>
|
|
38
|
+
</table>
|
|
39
|
+
|
|
40
|
+
<% if @total_pages > 1 %>
|
|
41
|
+
<div class="pagination">
|
|
42
|
+
<% if @page > 1 %>
|
|
43
|
+
<a href="<%= console_agent.sessions_path(page: @page - 1) %>">← Newer</a>
|
|
44
|
+
<% else %>
|
|
45
|
+
<span class="disabled">← Newer</span>
|
|
46
|
+
<% end %>
|
|
47
|
+
|
|
48
|
+
<span class="page-info">Page <%= @page %> of <%= @total_pages %></span>
|
|
49
|
+
|
|
50
|
+
<% if @page < @total_pages %>
|
|
51
|
+
<a href="<%= console_agent.sessions_path(page: @page + 1) %>">Older →</a>
|
|
52
|
+
<% else %>
|
|
53
|
+
<span class="disabled">Older →</span>
|
|
54
|
+
<% end %>
|
|
55
|
+
</div>
|
|
56
|
+
<% end %>
|
|
57
|
+
<% end %>
|