rubyn 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +251 -0
- data/Rakefile +12 -0
- data/exe/rubyn +5 -0
- data/lib/generators/rubyn/install_generator.rb +16 -0
- data/lib/rubyn/cli.rb +85 -0
- data/lib/rubyn/client/api_client.rb +172 -0
- data/lib/rubyn/commands/agent.rb +191 -0
- data/lib/rubyn/commands/base.rb +60 -0
- data/lib/rubyn/commands/config.rb +51 -0
- data/lib/rubyn/commands/dashboard.rb +85 -0
- data/lib/rubyn/commands/index.rb +101 -0
- data/lib/rubyn/commands/init.rb +166 -0
- data/lib/rubyn/commands/refactor.rb +175 -0
- data/lib/rubyn/commands/review.rb +61 -0
- data/lib/rubyn/commands/spec.rb +72 -0
- data/lib/rubyn/commands/usage.rb +56 -0
- data/lib/rubyn/config/credentials.rb +39 -0
- data/lib/rubyn/config/project_config.rb +42 -0
- data/lib/rubyn/config/settings.rb +53 -0
- data/lib/rubyn/context/codebase_indexer.rb +195 -0
- data/lib/rubyn/context/context_builder.rb +36 -0
- data/lib/rubyn/context/file_resolver.rb +235 -0
- data/lib/rubyn/context/project_scanner.rb +132 -0
- data/lib/rubyn/engine/app/assets/images/rubyn/RubynLogo.png +0 -0
- data/lib/rubyn/engine/app/assets/javascripts/rubyn/application.js +579 -0
- data/lib/rubyn/engine/app/assets/stylesheets/rubyn/application.css +1073 -0
- data/lib/rubyn/engine/app/controllers/rubyn/agent_controller.rb +26 -0
- data/lib/rubyn/engine/app/controllers/rubyn/application_controller.rb +44 -0
- data/lib/rubyn/engine/app/controllers/rubyn/dashboard_controller.rb +19 -0
- data/lib/rubyn/engine/app/controllers/rubyn/feedback_controller.rb +17 -0
- data/lib/rubyn/engine/app/controllers/rubyn/files_controller.rb +54 -0
- data/lib/rubyn/engine/app/controllers/rubyn/refactor_controller.rb +56 -0
- data/lib/rubyn/engine/app/controllers/rubyn/reviews_controller.rb +32 -0
- data/lib/rubyn/engine/app/controllers/rubyn/settings_controller.rb +17 -0
- data/lib/rubyn/engine/app/controllers/rubyn/specs_controller.rb +33 -0
- data/lib/rubyn/engine/app/views/layouts/rubyn/application.html.erb +63 -0
- data/lib/rubyn/engine/app/views/rubyn/agent/show.html.erb +22 -0
- data/lib/rubyn/engine/app/views/rubyn/dashboard/index.html.erb +120 -0
- data/lib/rubyn/engine/app/views/rubyn/files/index.html.erb +45 -0
- data/lib/rubyn/engine/app/views/rubyn/refactor/show.html.erb +28 -0
- data/lib/rubyn/engine/app/views/rubyn/reviews/show.html.erb +28 -0
- data/lib/rubyn/engine/app/views/rubyn/settings/show.html.erb +42 -0
- data/lib/rubyn/engine/app/views/rubyn/specs/show.html.erb +28 -0
- data/lib/rubyn/engine/config/routes.rb +13 -0
- data/lib/rubyn/engine/engine.rb +18 -0
- data/lib/rubyn/output/diff_renderer.rb +106 -0
- data/lib/rubyn/output/formatter.rb +123 -0
- data/lib/rubyn/output/spinner.rb +26 -0
- data/lib/rubyn/tools/base_tool.rb +74 -0
- data/lib/rubyn/tools/bundle_add.rb +77 -0
- data/lib/rubyn/tools/create_file.rb +32 -0
- data/lib/rubyn/tools/delete_file.rb +29 -0
- data/lib/rubyn/tools/executor.rb +68 -0
- data/lib/rubyn/tools/find_files.rb +33 -0
- data/lib/rubyn/tools/find_references.rb +72 -0
- data/lib/rubyn/tools/git_commit.rb +65 -0
- data/lib/rubyn/tools/git_create_branch.rb +58 -0
- data/lib/rubyn/tools/git_diff.rb +42 -0
- data/lib/rubyn/tools/git_log.rb +43 -0
- data/lib/rubyn/tools/git_status.rb +26 -0
- data/lib/rubyn/tools/list_directory.rb +82 -0
- data/lib/rubyn/tools/move_file.rb +35 -0
- data/lib/rubyn/tools/patch_file.rb +47 -0
- data/lib/rubyn/tools/rails_generate.rb +40 -0
- data/lib/rubyn/tools/rails_migrate.rb +55 -0
- data/lib/rubyn/tools/rails_routes.rb +35 -0
- data/lib/rubyn/tools/read_file.rb +45 -0
- data/lib/rubyn/tools/registry.rb +28 -0
- data/lib/rubyn/tools/run_command.rb +48 -0
- data/lib/rubyn/tools/run_tests.rb +52 -0
- data/lib/rubyn/tools/search_files.rb +82 -0
- data/lib/rubyn/tools/write_file.rb +30 -0
- data/lib/rubyn/version.rb +5 -0
- data/lib/rubyn/version_checker.rb +74 -0
- data/lib/rubyn.rb +95 -0
- data/sig/rubyn.rbs +4 -0
- metadata +379 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8b1ca55e10bd9ae4ec17fbdbfa196577fda468788c083905679a2c1e7e782181
|
|
4
|
+
data.tar.gz: f82e928da5975b754dca641f00488bb6360ffbd4cd45d538909348a4b1ff6b05
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: da671165752719684baaf371f4c1a47d7a9f3e8604af37360b106827d15c08a1508219b7fc47ae859a1c8f54f9e756a7e7a459c66fff4cbb718e70610eb4684a
|
|
7
|
+
data.tar.gz: 7f6f19c30b1ba42e74aeb0f3b0ec936d8f14ac63b6d52949d87c0714fce3b76c041c3cc8bf67a8eb07c36453b6d7602ae35736c46b1771646ce71bcebac748b4
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 matthewsuttles
|
|
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,251 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="lib/rubyn/engine/app/assets/images/rubyn/RubynLogo.png" alt="Rubyn" width="80" height="80" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Rubyn</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>AI Code Assistant for Ruby & Rails</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://rubygems.org/gems/rubyn"><img src="https://img.shields.io/gem/v/rubyn?color=CC342D&label=gem" alt="Gem Version" /></a>
|
|
13
|
+
<a href="https://github.com/MatthewSuttles/rubyn"><img src="https://img.shields.io/badge/specs-412%20passing-brightgreen" alt="Tests" /></a>
|
|
14
|
+
<a href="https://github.com/rubocop/rubocop"><img src="https://img.shields.io/badge/code_style-rubocop-brightgreen.svg" alt="Ruby Style Guide" /></a>
|
|
15
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /></a>
|
|
16
|
+
<a href="https://www.ruby-lang.org"><img src="https://img.shields.io/badge/ruby-%3E%3D%202.7-CC342D.svg" alt="Ruby" /></a>
|
|
17
|
+
<a href="https://github.com/MatthewSuttles/rubyn"><img src="https://img.shields.io/badge/coverage-95%25-brightgreen.svg" alt="Coverage" /></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
Refactor controllers, generate idiomatic RSpec, catch N+1 queries, and review code for anti-patterns — all context-aware with your schema, routes, and specs. Built for Ruby developers who care about conventions.
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
<!-- Screenshots: replace these paths with your actual screenshots -->
|
|
27
|
+
<p align="center">
|
|
28
|
+
<img src="docs/screenshots/dashboard.png" alt="Rubyn Dashboard" width="700" />
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<em>The Rubyn web dashboard mounted inside your Rails app</em>
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<img src="docs/screenshots/console.png" alt="Rubyn Dashboard" width="700" />
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<em>The Rubyn console for those hardcore users</em>
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Why Rubyn?
|
|
46
|
+
|
|
47
|
+
General AI tools write Ruby like they write Python. Rubyn is different:
|
|
48
|
+
|
|
49
|
+
- **Rails-native** — Knows when to extract a service object, how to write idiomatic RSpec, and why your controller is too fat
|
|
50
|
+
- **Context-aware** — Automatically includes your schema, routes, specs, factories, and related models before generating suggestions
|
|
51
|
+
- **Best practices built in** — Every request is enriched with curated Ruby and Rails guidelines matched to what you're working on
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
Add Rubyn to your Gemfile:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
gem "rubyn"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then run:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
bundle install
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or install it directly:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
gem install rubyn
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Requirements
|
|
76
|
+
|
|
77
|
+
- Ruby >= 2.7
|
|
78
|
+
- Rails >= 6.0 (for the web dashboard engine; the CLI works without Rails)
|
|
79
|
+
- A Rubyn API key — sign up at [rubyn.ai](https://rubyn.ai)
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
|
|
85
|
+
### 1. Initialize your project
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
rubyn init
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This will:
|
|
92
|
+
|
|
93
|
+
- Prompt for your API key (or read from `RUBYN_API_KEY` env var)
|
|
94
|
+
- Scan your project (Ruby version, Rails version, test framework, gems)
|
|
95
|
+
- Create `.rubyn/project.yml` in your project root
|
|
96
|
+
|
|
97
|
+
### 2. Refactor a file
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
rubyn refactor app/controllers/orders_controller.rb
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Rubyn pulls in the related model, routes, request spec, and service objects — then suggests idiomatic improvements you can apply with one command.
|
|
104
|
+
|
|
105
|
+
<!-- Screenshot: CLI refactor output -->
|
|
106
|
+
<!-- <img src="docs/screenshots/cli-refactor.png" alt="CLI Refactor" width="600" /> -->
|
|
107
|
+
|
|
108
|
+
### 3. Generate specs
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
rubyn spec app/services/orders/create_service.rb
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Generates idiomatic tests that match your project's framework (RSpec or Minitest), factory setup, and assertion style.
|
|
115
|
+
|
|
116
|
+
### 4. Review code
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
rubyn review app/controllers/
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Catches N+1 queries, SQL injection, missing auth, fat controllers, and other anti-patterns — before your PR.
|
|
123
|
+
|
|
124
|
+
### 5. Start an interactive session
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
rubyn agent
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Ask Rubyn anything about your codebase. Attach files with `@filename`:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
you> How should I refactor @app/models/order.rb to use service objects?
|
|
134
|
+
rubyn> Looking at your Order model, I'd suggest extracting...
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Web Dashboard
|
|
140
|
+
|
|
141
|
+
Rubyn includes a mountable Rails engine that provides a full web UI in development.
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# config/routes.rb
|
|
145
|
+
mount Rubyn::Engine => "/rubyn" if Rails.env.development?
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Then visit [http://localhost:3000/rubyn](http://localhost:3000/rubyn).
|
|
149
|
+
|
|
150
|
+
<!-- Screenshot: File browser with categorized files -->
|
|
151
|
+
<!-- <img src="docs/screenshots/files.png" alt="File Browser" width="600" /> -->
|
|
152
|
+
|
|
153
|
+
Features:
|
|
154
|
+
|
|
155
|
+
- **File Browser** — Browse Ruby files by category (models, controllers, services, etc.) with one-click refactor, spec, and review
|
|
156
|
+
- **Refactor View** — Code blocks with Apply button for each file, Apply All for multi-file changes
|
|
157
|
+
- **Spec Generator** — Generated specs with Write to File button
|
|
158
|
+
- **Code Review** — Findings displayed inline
|
|
159
|
+
- **Agent Chat** — Conversational interface
|
|
160
|
+
- **Settings** — API key status and preferences
|
|
161
|
+
|
|
162
|
+
The engine is fully isolated — it uses its own layout, styles, and JavaScript. It will not interfere with your application.
|
|
163
|
+
|
|
164
|
+
### Non-Rails Projects
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
rubyn dashboard
|
|
168
|
+
# => Dashboard: http://localhost:9292/rubyn
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## All Commands
|
|
174
|
+
|
|
175
|
+
| Command | Description |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `rubyn init` | Initialize Rubyn in your project |
|
|
178
|
+
| `rubyn refactor <file>` | Refactor a file toward best practices |
|
|
179
|
+
| `rubyn spec <file>` | Generate tests for a file |
|
|
180
|
+
| `rubyn review <file_or_dir>` | Review code for anti-patterns |
|
|
181
|
+
| `rubyn agent` | Start an interactive conversation |
|
|
182
|
+
| `rubyn usage` | Show credit balance and recent usage |
|
|
183
|
+
| `rubyn config [key] [value]` | View or set configuration |
|
|
184
|
+
| `rubyn dashboard` | Open the web dashboard |
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## How Context Works
|
|
189
|
+
|
|
190
|
+
When you refactor a controller, Rubyn automatically includes:
|
|
191
|
+
|
|
192
|
+
| You pass | Rubyn also loads |
|
|
193
|
+
|---|---|
|
|
194
|
+
| Controller | Model, routes, request spec, service objects |
|
|
195
|
+
| Model | Schema, controller, model spec, factory |
|
|
196
|
+
| Service object | Referenced models, service spec |
|
|
197
|
+
| Gem lib file | Corresponding spec, sibling classes |
|
|
198
|
+
|
|
199
|
+
Plus relevant best practice documents matched to the file type — no configuration needed.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Plans
|
|
204
|
+
|
|
205
|
+
| Feature | Free | Pro | Lifetime |
|
|
206
|
+
|---|:---:|:---:|:---:|
|
|
207
|
+
| All commands (refactor, spec, review, agent) | Yes | Yes | Yes |
|
|
208
|
+
| Web dashboard | Yes | Yes | Yes |
|
|
209
|
+
| Convention-based file context | Yes | Yes | Yes |
|
|
210
|
+
| Credits | 10/day | 250/month | 250/month |
|
|
211
|
+
| Price | Free | $19/mo | $300 one-time |
|
|
212
|
+
| Overages | - | $0.05/credit * | $0.05/credit * |
|
|
213
|
+
|
|
214
|
+
\* Overages are optional and can be turned off at any time in your account settings.
|
|
215
|
+
|
|
216
|
+
[Compare plans at rubyn.ai/pricing](https://rubyn.ai/pricing)
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Configuration
|
|
221
|
+
|
|
222
|
+
### API Key
|
|
223
|
+
|
|
224
|
+
```sh
|
|
225
|
+
export RUBYN_API_KEY=rk_your_key_here
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Or stored at `~/.rubyn/credentials` with `0600` permissions.
|
|
229
|
+
|
|
230
|
+
### Environment Variables
|
|
231
|
+
|
|
232
|
+
| Variable | Description |
|
|
233
|
+
|---|---|
|
|
234
|
+
| `RUBYN_API_KEY` | API key (overrides credentials file) |
|
|
235
|
+
| `RUBYN_API_URL` | API base URL (default: `https://api.rubyn.ai`) |
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Development
|
|
240
|
+
|
|
241
|
+
```sh
|
|
242
|
+
bin/setup
|
|
243
|
+
bundle exec rspec # 412 specs
|
|
244
|
+
bundle exec rubocop
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE).
|
data/Rakefile
ADDED
data/exe/rubyn
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyn
|
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
|
5
|
+
desc "Install Rubyn engine"
|
|
6
|
+
|
|
7
|
+
def add_route
|
|
8
|
+
route 'mount Rubyn::Engine => "/rubyn" if Rails.env.development?'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def show_instructions
|
|
12
|
+
say "Rubyn engine mounted at /rubyn (development only)"
|
|
13
|
+
say "Run 'rubyn init' to configure your API key and project"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/rubyn/cli.rb
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module Rubyn
|
|
6
|
+
class CLI < Thor
|
|
7
|
+
def self.exit_on_failure?
|
|
8
|
+
true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
no_commands do
|
|
12
|
+
def check_for_updates
|
|
13
|
+
msg = Rubyn::VersionChecker.update_message
|
|
14
|
+
return unless msg
|
|
15
|
+
|
|
16
|
+
puts
|
|
17
|
+
puts Pastel.new.yellow(msg)
|
|
18
|
+
rescue Rubyn::Error, Net::HTTPError, SocketError, Timeout::Error
|
|
19
|
+
# Never let update check break the CLI
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.start(given_args = ARGV, config = {})
|
|
24
|
+
super
|
|
25
|
+
ensure
|
|
26
|
+
# Check for updates after any command (in a non-blocking way)
|
|
27
|
+
new.check_for_updates if given_args.any? && !%w[help -h --help version -v --version].include?(given_args.first)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc "init", "Initialize Rubyn in this project"
|
|
31
|
+
def init
|
|
32
|
+
require_relative "commands/init"
|
|
33
|
+
Commands::Init.new.execute
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc "refactor FILE", "Refactor a file toward best practices"
|
|
37
|
+
def refactor(file)
|
|
38
|
+
require_relative "commands/refactor"
|
|
39
|
+
Commands::Refactor.new.execute(file)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
desc "spec FILE", "Generate RSpec tests for a file"
|
|
43
|
+
def spec(file)
|
|
44
|
+
require_relative "commands/spec"
|
|
45
|
+
Commands::Spec.new.execute(file)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
desc "review FILE_OR_DIR", "Review code for anti-patterns"
|
|
49
|
+
def review(file_or_dir)
|
|
50
|
+
require_relative "commands/review"
|
|
51
|
+
Commands::Review.new.execute(file_or_dir)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
desc "agent", "Start an interactive conversation with Rubyn"
|
|
55
|
+
def agent
|
|
56
|
+
require_relative "commands/agent"
|
|
57
|
+
Commands::Agent.new.execute
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# desc "analyse", "Analyse your codebase for enhanced AI context"
|
|
61
|
+
# option :force, type: :boolean, default: false, desc: "Re-analyse all files, not just changed ones"
|
|
62
|
+
# def analyse
|
|
63
|
+
# require_relative "commands/index"
|
|
64
|
+
# Commands::Index.new.execute(force: options[:force])
|
|
65
|
+
# end
|
|
66
|
+
|
|
67
|
+
desc "usage", "Show credit balance and recent usage"
|
|
68
|
+
def usage
|
|
69
|
+
require_relative "commands/usage"
|
|
70
|
+
Commands::Usage.new.execute
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
desc "config [KEY] [VALUE]", "View or set configuration"
|
|
74
|
+
def config(key = nil, value = nil)
|
|
75
|
+
require_relative "commands/config"
|
|
76
|
+
Commands::Config.new.execute(key, value)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
desc "dashboard", "Open the Rubyn web dashboard"
|
|
80
|
+
def dashboard
|
|
81
|
+
require_relative "commands/dashboard"
|
|
82
|
+
Commands::Dashboard.new.execute
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "faraday/multipart"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module Rubyn
|
|
8
|
+
module Client
|
|
9
|
+
class ApiClient
|
|
10
|
+
attr_reader :base_url
|
|
11
|
+
|
|
12
|
+
def initialize(base_url: nil, api_key: nil)
|
|
13
|
+
@base_url = base_url || Rubyn.configuration.api_url
|
|
14
|
+
@api_key = api_key
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def verify_auth
|
|
18
|
+
post("/api/v1/auth/verify")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def refactor(file_path:, code:, context_files:, project_token:)
|
|
22
|
+
post("/api/v1/ai/refactor", {
|
|
23
|
+
file_path: file_path,
|
|
24
|
+
code: code,
|
|
25
|
+
context_files: context_files,
|
|
26
|
+
project_token: project_token
|
|
27
|
+
})
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def generate_spec(file_path:, code:, context_files:, project_token:)
|
|
31
|
+
post("/api/v1/ai/spec", {
|
|
32
|
+
file_path: file_path,
|
|
33
|
+
code: code,
|
|
34
|
+
context_files: context_files,
|
|
35
|
+
project_token: project_token
|
|
36
|
+
})
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def review(files:, context_files:, project_token:)
|
|
40
|
+
post("/api/v1/ai/review", {
|
|
41
|
+
files: files,
|
|
42
|
+
context_files: context_files,
|
|
43
|
+
project_token: project_token
|
|
44
|
+
})
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def agent_message(conversation_id:, message:, file_context:, project_token:)
|
|
48
|
+
post("/api/v1/ai/agent", {
|
|
49
|
+
conversation_id: conversation_id,
|
|
50
|
+
message: message,
|
|
51
|
+
file_context: file_context,
|
|
52
|
+
project_token: project_token
|
|
53
|
+
})
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def submit_tool_results(conversation_id:, tool_results:, project_token:)
|
|
57
|
+
post("/api/v1/ai/agent/tool_results", {
|
|
58
|
+
conversation_id: conversation_id,
|
|
59
|
+
tool_results: tool_results,
|
|
60
|
+
project_token: project_token
|
|
61
|
+
})
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def list_conversations(project_token:)
|
|
65
|
+
get("/api/v1/conversations", project_token: project_token)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def get_conversation(id:, project_token:)
|
|
69
|
+
get("/api/v1/conversations/#{id}", project_token: project_token)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def sync_project(metadata:, project_token:)
|
|
73
|
+
post("/api/v1/projects/sync", {
|
|
74
|
+
project_token: project_token,
|
|
75
|
+
name: metadata[:project_name],
|
|
76
|
+
project_type: metadata[:project_type],
|
|
77
|
+
ruby_version: metadata[:ruby_version],
|
|
78
|
+
rails_version: metadata[:rails_version],
|
|
79
|
+
test_framework: metadata[:test_framework],
|
|
80
|
+
factory_library: metadata[:factory_library],
|
|
81
|
+
gems_snapshot: metadata[:gems],
|
|
82
|
+
directory_structure: metadata[:directory_structure]
|
|
83
|
+
})
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def index_project(project_token:, files:)
|
|
87
|
+
post("/api/v1/projects/index", project_token: project_token, files: files)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def join_project(project_token:)
|
|
91
|
+
post("/api/v1/projects/join", project_token: project_token)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def get_balance
|
|
95
|
+
get("/api/v1/usage/balance")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def get_history(page: 1)
|
|
99
|
+
get("/api/v1/usage/history", page: page)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def submit_feedback(interaction_id:, rating:, feedback: nil)
|
|
103
|
+
post("/api/v1/feedback", {
|
|
104
|
+
interaction_id: interaction_id,
|
|
105
|
+
rating: rating,
|
|
106
|
+
feedback: feedback
|
|
107
|
+
})
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def api_key
|
|
113
|
+
@api_key || Rubyn::Config::Credentials.api_key
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def connection
|
|
117
|
+
@connection ||= Faraday.new(url: @base_url) do |f|
|
|
118
|
+
f.request :json
|
|
119
|
+
f.response :json, content_type: /\bjson$/
|
|
120
|
+
f.adapter Faraday.default_adapter
|
|
121
|
+
f.options.timeout = 300
|
|
122
|
+
f.options.open_timeout = 10
|
|
123
|
+
f.headers["Authorization"] = "Bearer #{api_key}" if api_key
|
|
124
|
+
f.headers["User-Agent"] = "rubyn-gem/#{Rubyn::VERSION}"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def get(path, params = {})
|
|
129
|
+
response = connection.get(path, params)
|
|
130
|
+
parse_response(response)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def post(path, body = {})
|
|
134
|
+
response = connection.post(path, body)
|
|
135
|
+
parse_response(response)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def parse_response(response)
|
|
139
|
+
return response.body if response.status.between?(200, 299)
|
|
140
|
+
|
|
141
|
+
message = extract_error_message(response)
|
|
142
|
+
|
|
143
|
+
case response.status
|
|
144
|
+
when 400
|
|
145
|
+
raise APIError, message || "Bad request"
|
|
146
|
+
when 401
|
|
147
|
+
raise AuthenticationError, "Invalid API key. Run `rubyn init` to reconfigure."
|
|
148
|
+
when 402
|
|
149
|
+
raise APIError, message || "Insufficient credits. Check your balance with `rubyn usage`."
|
|
150
|
+
when 403
|
|
151
|
+
raise AuthenticationError, message || "Access denied. Check your project membership."
|
|
152
|
+
when 404
|
|
153
|
+
raise APIError, message || "Resource not found."
|
|
154
|
+
when 422
|
|
155
|
+
raise APIError, message || "Unprocessable entity"
|
|
156
|
+
when 429
|
|
157
|
+
raise APIError, "Rate limit exceeded. Please wait and try again."
|
|
158
|
+
else
|
|
159
|
+
raise APIError, "Unexpected error (#{response.status}): #{message}"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def extract_error_message(response)
|
|
164
|
+
body = response.body
|
|
165
|
+
return body["error"] if body.is_a?(Hash) && body["error"]
|
|
166
|
+
return body.to_s unless body.nil? || (body.is_a?(String) && body.empty?)
|
|
167
|
+
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|