console_agent 0.0.1 → 0.2.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/LICENSE +21 -0
- data/README.md +335 -0
- data/lib/console_agent/configuration.rb +61 -0
- data/lib/console_agent/console_methods.rb +131 -0
- data/lib/console_agent/context_builder.rb +229 -0
- data/lib/console_agent/executor.rb +193 -0
- data/lib/console_agent/providers/anthropic.rb +112 -0
- data/lib/console_agent/providers/base.rb +106 -0
- data/lib/console_agent/providers/openai.rb +114 -0
- data/lib/console_agent/railtie.rb +30 -0
- data/lib/console_agent/repl.rb +342 -0
- data/lib/console_agent/storage/base.rb +27 -0
- data/lib/console_agent/storage/file_storage.rb +63 -0
- data/lib/console_agent/tools/code_tools.rb +114 -0
- data/lib/console_agent/tools/memory_tools.rb +136 -0
- data/lib/console_agent/tools/model_tools.rb +95 -0
- data/lib/console_agent/tools/registry.rb +253 -0
- data/lib/console_agent/tools/schema_tools.rb +60 -0
- data/lib/console_agent/version.rb +3 -0
- data/lib/console_agent.rb +76 -1
- data/lib/generators/console_agent/install_generator.rb +26 -0
- data/lib/generators/console_agent/templates/initializer.rb +33 -0
- metadata +95 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 53925e32cd4919e550bfdc081ede866613ce3ea18a585aca6cb81d1687ce3901
|
|
4
|
+
data.tar.gz: ae601be1b81ff2fcb89addda1c008c0646118f2af21c9009a1792556ffd9b4ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 54ec67e116e4a05de7a5d915f93dc7babf2d64d2b008bc507718dce92d58ad032beeaa1d7710d8190943454f937f91780ff5614e44f5ef173ab5feff6dddb339
|
|
7
|
+
data.tar.gz: f12af224368e44b149d740f8d0b2dee4a38ce86564ee9337c5a8ed569a83bb387d3edcf2ae76142d4b510f507c5e6d64d82aab70c16eb94f66d83e3811eb0cd6
|
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,335 @@
|
|
|
1
|
+
# ConsoleAgent
|
|
2
|
+
|
|
3
|
+
An AI-powered assistant for your Rails console. Ask questions in plain English, get executable Ruby code.
|
|
4
|
+
|
|
5
|
+
It's like Claude Code embedded in your Rails Console.
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
Add to your Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'console_agent', group: :development
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Run the install generator:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bundle install
|
|
21
|
+
rails generate console_agent:install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Set your API key (pick one):
|
|
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:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
rails console
|
|
38
|
+
ai "show me all users who signed up this week"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You can also set or change your API key at runtime in the console:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
ConsoleAgent.configure { |c| c.api_key = 'sk-ant-...' }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Console Commands
|
|
48
|
+
|
|
49
|
+
| Command | Description |
|
|
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
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
irb> ai "find the 5 most recent orders over $100"
|
|
61
|
+
Thinking...
|
|
62
|
+
-> list_tables
|
|
63
|
+
12 tables: users, orders, line_items, products...
|
|
64
|
+
-> describe_table("orders")
|
|
65
|
+
8 columns
|
|
66
|
+
-> describe_model("Order")
|
|
67
|
+
4 associations, 2 validations
|
|
68
|
+
|
|
69
|
+
You can query recent high-value orders like this:
|
|
70
|
+
|
|
71
|
+
Order.where("total > ?", 100).order(created_at: :desc).limit(5)
|
|
72
|
+
|
|
73
|
+
[tokens in: 1,240 | out: 85 | total: 1,325]
|
|
74
|
+
Execute? [y/N/edit] y
|
|
75
|
+
=> [#<Order id: 4821, ...>, ...]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Interactive Mode
|
|
79
|
+
|
|
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
|
+
```
|
|
94
|
+
|
|
95
|
+
### Configuration Status
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
irb> ai_status
|
|
99
|
+
[ConsoleAgent v0.1.0]
|
|
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
|
|
146
|
+
|
|
147
|
+
# Debug mode: prints full API requests/responses and tool calls
|
|
148
|
+
# config.debug = true
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
All settings can be changed at runtime in the console:
|
|
153
|
+
|
|
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
|
+
```
|
|
159
|
+
|
|
160
|
+
## Context Modes
|
|
161
|
+
|
|
162
|
+
### Smart Mode (default)
|
|
163
|
+
|
|
164
|
+
The LLM gets a minimal system prompt and uses tools to look up what it needs:
|
|
165
|
+
|
|
166
|
+
- **list_tables** / **describe_table** — database schema on demand
|
|
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
|
|
173
|
+
|
|
174
|
+
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.
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
ConsoleAgent.configure { |c| c.context_mode = :full }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Tools Available in Smart Mode
|
|
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.
|
|
197
|
+
|
|
198
|
+
## Memories & Skills
|
|
199
|
+
|
|
200
|
+
ConsoleAgent can remember what it learns about your codebase across sessions and load reusable skill instructions on demand.
|
|
201
|
+
|
|
202
|
+
### Memories
|
|
203
|
+
|
|
204
|
+
When the AI investigates something complex (like how sharding works in your app), it can save what it learned for next time. Memories are stored in `.console_agent/memories.yml` in your Rails app.
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
ai> how many users are on this shard?
|
|
208
|
+
Thinking...
|
|
209
|
+
-> search_code("shard")
|
|
210
|
+
Found 50 matches
|
|
211
|
+
-> read_file("config/initializers/sharding.rb")
|
|
212
|
+
202 lines
|
|
213
|
+
-> save_memory("Sharding architecture")
|
|
214
|
+
Memory saved: "Sharding architecture"
|
|
215
|
+
|
|
216
|
+
Each shard is a separate database. User.count returns the count for the current shard only.
|
|
217
|
+
|
|
218
|
+
User.count
|
|
219
|
+
|
|
220
|
+
[tokens in: 2,340 | out: 120 | total: 2,460]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Next session, the AI already knows:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
ai> count users on this shard
|
|
227
|
+
Thinking...
|
|
228
|
+
|
|
229
|
+
User.count
|
|
230
|
+
|
|
231
|
+
[tokens in: 580 | out: 45 | total: 625]
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The memory file is human-editable YAML:
|
|
235
|
+
|
|
236
|
+
```yaml
|
|
237
|
+
# .console_agent/memories.yml
|
|
238
|
+
memories:
|
|
239
|
+
- id: mem_1709123456_42
|
|
240
|
+
name: Sharding architecture
|
|
241
|
+
description: "App uses database-per-shard. Each shard is a separate DB. User.count only counts the current shard."
|
|
242
|
+
tags: [database, sharding]
|
|
243
|
+
created_at: "2026-02-27T10:30:00Z"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
You can commit this file to your repo so the whole team benefits, or gitignore it for personal use.
|
|
247
|
+
|
|
248
|
+
### Skills
|
|
249
|
+
|
|
250
|
+
Skills are reusable instruction files that teach the AI how to handle specific tasks. Store them in `.console_agent/skills/` as markdown files with YAML frontmatter:
|
|
251
|
+
|
|
252
|
+
```markdown
|
|
253
|
+
# .console_agent/skills/sharding.md
|
|
254
|
+
---
|
|
255
|
+
name: Sharded database queries
|
|
256
|
+
description: How to query across database shards. Use when user asks about counting records across shards or shard-specific operations.
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Instructions
|
|
260
|
+
|
|
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
|
+
```
|
|
266
|
+
|
|
267
|
+
Only the skill name and description are sent to the AI on every request (~100 tokens per skill). The full instructions are loaded on demand via the `load_skill` tool only when relevant.
|
|
268
|
+
|
|
269
|
+
### Storage
|
|
270
|
+
|
|
271
|
+
By default, memories and skills are stored on the filesystem at `Rails.root/.console_agent/`. This works for development and for production environments with a persistent filesystem.
|
|
272
|
+
|
|
273
|
+
For containers or other environments where the filesystem is ephemeral, you have two options:
|
|
274
|
+
|
|
275
|
+
**Option A: Commit to your repo.** Create `.console_agent/memories.yml` and `.console_agent/skills/*.md` in your codebase. They'll be baked into your Docker image.
|
|
276
|
+
|
|
277
|
+
**Option B: Custom storage adapter.** Implement the storage interface and plug it in:
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
ConsoleAgent.configure do |config|
|
|
281
|
+
config.storage_adapter = MyS3Storage.new(bucket: 'my-bucket', prefix: 'console_agent/')
|
|
282
|
+
end
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
A storage adapter just needs four methods: `read(key)`, `write(key, content)`, `list(pattern)`, and `exists?(key)`.
|
|
286
|
+
|
|
287
|
+
To disable memories or skills:
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
ConsoleAgent.configure do |config|
|
|
291
|
+
config.memories_enabled = false
|
|
292
|
+
config.skills_enabled = false
|
|
293
|
+
end
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Providers
|
|
297
|
+
|
|
298
|
+
### Anthropic (default)
|
|
299
|
+
|
|
300
|
+
Uses the Claude Messages API. Set `ANTHROPIC_API_KEY` or `config.api_key`.
|
|
301
|
+
|
|
302
|
+
### OpenAI
|
|
303
|
+
|
|
304
|
+
Uses the Chat Completions API. Set `OPENAI_API_KEY` or `config.api_key`.
|
|
305
|
+
|
|
306
|
+
```ruby
|
|
307
|
+
ConsoleAgent.configure do |config|
|
|
308
|
+
config.provider = :openai
|
|
309
|
+
config.model = 'gpt-5.3-codex' # optional, this is the default
|
|
310
|
+
end
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Local Development
|
|
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:
|
|
322
|
+
|
|
323
|
+
```ruby
|
|
324
|
+
gem 'console_agent'
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Requirements
|
|
328
|
+
|
|
329
|
+
- Ruby >= 2.5
|
|
330
|
+
- Rails >= 5.0
|
|
331
|
+
- Faraday >= 1.0
|
|
332
|
+
|
|
333
|
+
## License
|
|
334
|
+
|
|
335
|
+
MIT
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
:storage_adapter, :memories_enabled
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@provider = :anthropic
|
|
13
|
+
@api_key = nil
|
|
14
|
+
@model = nil
|
|
15
|
+
@max_tokens = 4096
|
|
16
|
+
@auto_execute = false
|
|
17
|
+
@context_mode = :smart
|
|
18
|
+
@temperature = 0.2
|
|
19
|
+
@timeout = 30
|
|
20
|
+
@debug = false
|
|
21
|
+
@max_tool_rounds = 100
|
|
22
|
+
@storage_adapter = nil
|
|
23
|
+
@memories_enabled = true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def resolved_api_key
|
|
27
|
+
return @api_key if @api_key && !@api_key.empty?
|
|
28
|
+
|
|
29
|
+
case @provider
|
|
30
|
+
when :anthropic
|
|
31
|
+
ENV['ANTHROPIC_API_KEY']
|
|
32
|
+
when :openai
|
|
33
|
+
ENV['OPENAI_API_KEY']
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def resolved_model
|
|
38
|
+
return @model if @model && !@model.empty?
|
|
39
|
+
|
|
40
|
+
case @provider
|
|
41
|
+
when :anthropic
|
|
42
|
+
'claude-opus-4-6'
|
|
43
|
+
when :openai
|
|
44
|
+
'gpt-5.3-codex'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def validate!
|
|
49
|
+
unless PROVIDERS.include?(@provider)
|
|
50
|
+
raise ConfigurationError, "Unknown provider: #{@provider}. Valid: #{PROVIDERS.join(', ')}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
unless resolved_api_key
|
|
54
|
+
env_var = @provider == :anthropic ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'
|
|
55
|
+
raise ConfigurationError, "No API key. Set config.api_key or #{env_var} env var."
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class ConfigurationError < StandardError; end
|
|
61
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
module ConsoleAgent
|
|
2
|
+
module ConsoleMethods
|
|
3
|
+
def ai_status
|
|
4
|
+
ConsoleAgent.status
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def ai_memories(n = nil)
|
|
8
|
+
require 'yaml'
|
|
9
|
+
require 'console_agent/tools/memory_tools'
|
|
10
|
+
storage = ConsoleAgent.storage
|
|
11
|
+
keys = storage.list('memories/*.md').sort
|
|
12
|
+
|
|
13
|
+
if keys.empty?
|
|
14
|
+
$stdout.puts "\e[2mNo memories stored yet.\e[0m"
|
|
15
|
+
return nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
memories = keys.filter_map do |key|
|
|
19
|
+
content = storage.read(key)
|
|
20
|
+
next if content.nil? || content.strip.empty?
|
|
21
|
+
next unless content =~ /\A---\s*\n(.*?\n)---\s*\n(.*)/m
|
|
22
|
+
fm = YAML.safe_load($1, permitted_classes: [Time, Date]) || {}
|
|
23
|
+
fm.merge('description' => $2.strip, 'file' => key)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if memories.empty?
|
|
27
|
+
$stdout.puts "\e[2mNo memories stored yet.\e[0m"
|
|
28
|
+
return nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
shown = n ? memories.last(n) : memories.last(5)
|
|
32
|
+
total = memories.length
|
|
33
|
+
|
|
34
|
+
$stdout.puts "\e[36m[Memories — showing last #{shown.length} of #{total}]\e[0m"
|
|
35
|
+
shown.each do |m|
|
|
36
|
+
$stdout.puts "\e[33m #{m['name']}\e[0m"
|
|
37
|
+
$stdout.puts "\e[2m #{m['description']}\e[0m"
|
|
38
|
+
tags = Array(m['tags'])
|
|
39
|
+
$stdout.puts "\e[2m tags: #{tags.join(', ')}\e[0m" unless tags.empty?
|
|
40
|
+
$stdout.puts
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
path = storage.respond_to?(:root_path) ? File.join(storage.root_path, 'memories') : 'memories/'
|
|
44
|
+
$stdout.puts "\e[2mStored in: #{path}/\e[0m"
|
|
45
|
+
$stdout.puts "\e[2mUse ai_memories(n) to show last n.\e[0m"
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def ai(query = nil)
|
|
50
|
+
if query.nil?
|
|
51
|
+
$stderr.puts "\e[33mUsage: ai \"your question here\"\e[0m"
|
|
52
|
+
$stderr.puts "\e[33m ai \"query\" - ask + confirm execution\e[0m"
|
|
53
|
+
$stderr.puts "\e[33m ai! \"query\" - enter interactive mode (or ai! with no args)\e[0m"
|
|
54
|
+
$stderr.puts "\e[33m ai? \"query\" - explain only, no execution\e[0m"
|
|
55
|
+
$stderr.puts "\e[33m ai_status - show current configuration\e[0m"
|
|
56
|
+
$stderr.puts "\e[33m ai_memories - show recent memories (ai_memories(n) for last n)\e[0m"
|
|
57
|
+
return nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
require 'console_agent/context_builder'
|
|
61
|
+
require 'console_agent/providers/base'
|
|
62
|
+
require 'console_agent/executor'
|
|
63
|
+
require 'console_agent/repl'
|
|
64
|
+
|
|
65
|
+
repl = Repl.new(__console_agent_binding)
|
|
66
|
+
repl.one_shot(query.to_s)
|
|
67
|
+
rescue => e
|
|
68
|
+
$stderr.puts "\e[31mConsoleAgent error: #{e.message}\e[0m"
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def ai!(query = nil)
|
|
73
|
+
require 'console_agent/context_builder'
|
|
74
|
+
require 'console_agent/providers/base'
|
|
75
|
+
require 'console_agent/executor'
|
|
76
|
+
require 'console_agent/repl'
|
|
77
|
+
|
|
78
|
+
repl = Repl.new(__console_agent_binding)
|
|
79
|
+
|
|
80
|
+
if query
|
|
81
|
+
repl.one_shot(query.to_s)
|
|
82
|
+
else
|
|
83
|
+
repl.interactive
|
|
84
|
+
end
|
|
85
|
+
rescue => e
|
|
86
|
+
$stderr.puts "\e[31mConsoleAgent error: #{e.message}\e[0m"
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def ai?(query = nil)
|
|
91
|
+
unless query
|
|
92
|
+
$stderr.puts "\e[33mUsage: ai? \"your question here\" - explain without executing\e[0m"
|
|
93
|
+
return nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
require 'console_agent/context_builder'
|
|
97
|
+
require 'console_agent/providers/base'
|
|
98
|
+
require 'console_agent/executor'
|
|
99
|
+
require 'console_agent/repl'
|
|
100
|
+
|
|
101
|
+
repl = Repl.new(__console_agent_binding)
|
|
102
|
+
repl.explain(query.to_s)
|
|
103
|
+
rescue => e
|
|
104
|
+
$stderr.puts "\e[31mConsoleAgent error: #{e.message}\e[0m"
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def __console_agent_binding
|
|
111
|
+
# Try IRB workspace binding
|
|
112
|
+
if defined?(IRB) && IRB.respond_to?(:CurrentContext)
|
|
113
|
+
ctx = IRB.CurrentContext rescue nil
|
|
114
|
+
if ctx && ctx.respond_to?(:workspace) && ctx.workspace.respond_to?(:binding)
|
|
115
|
+
return ctx.workspace.binding
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Try Pry binding
|
|
120
|
+
if defined?(Pry) && respond_to?(:pry_instance, true)
|
|
121
|
+
pry_inst = pry_instance rescue nil
|
|
122
|
+
if pry_inst && pry_inst.respond_to?(:current_binding)
|
|
123
|
+
return pry_inst.current_binding
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Fallback
|
|
128
|
+
TOPLEVEL_BINDING
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|