rubber_duck 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 171bf433b6ed6a157adb397cb9e365b12531ef1631b06901282b870a2c9fafda
4
+ data.tar.gz: b2ad788c01a589196d6b502e87653935d15b04d569a78c750881b56189ff146f
5
+ SHA512:
6
+ metadata.gz: b092243fc9b50c76878a8a6b396888e39e856d0e9a814be6f258dcd9b4de04f773695f4ddca00bbce89d9e304b3d686c30cb62485ac183a939a7c474f3b1b952
7
+ data.tar.gz: b13dca3ab57855361634f8ccf5741386f260e778dfaccccd4a8ce7f2db44d0cf08d64599b701762f1869f782a821aae19be78a7f64ddf6fa737e9a720e2d7ef4
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Emmanuel Vernet
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # RubberDuck 🦆
2
+
3
+ RubberDuck is a Rails engine that provides an AI-powered debugging assistant directly on your native Rails development error pages.
4
+ When an error occurs, a rubber duck button is injected into the page.
5
+ Clicking it sends error details including log lines and http error to an AI service and displays a contextualized explanation in a modal.
6
+
7
+ Here's an example of the view it can generate currently
8
+ ![Alt text](app/assets/images/rubber_duck/v-0-example.png)
9
+
10
+
11
+ ## Usage
12
+ - this gem runs automatically when a standard rails error page shows. No rails commands needed!
13
+
14
+ > ⚠️ **Warning**
15
+ >
16
+ > Be aware that this gem uses your code and server logs to be sent to the AI model. Do not hardcode sensitive values such as tokens!!
17
+ However, the gem will not run in production, only in development
18
+
19
+ ## Installation
20
+ Add this line to your application's Gemfile:
21
+
22
+ > ⚠️ **Warning**
23
+ >
24
+ > Add this to your development gems. It will not run in production environments!
25
+
26
+ ```ruby
27
+ gem "rubber_duck"
28
+ ```
29
+
30
+ And then execute:
31
+ ```ruby
32
+ bundle install
33
+ ```
34
+
35
+ Or install it yourself as:
36
+ ```ruby
37
+ gem install rubber_duck
38
+ ```
39
+
40
+ Run the install generator to create the initializer file:
41
+ ```ruby
42
+ rails generate rubber_duck:install
43
+ ```
44
+
45
+ ## Configuration
46
+ Before using the gem, you need to configure your AI service API key in the initializer file located at `config/initializers/rubber_duck.rb`.
47
+ ```ruby
48
+ config/initializers/rubber_duck.rb
49
+ RubberDuck.configure do |config|
50
+ # Set your OpenAI API key
51
+ config.openai_api_key = ENV['OPENAI_API_KEY']
52
+
53
+ # (Optional) Specify a different AI model to use.
54
+ config.model = 'gpt-4'
55
+ end
56
+ ```
57
+ Ensure you have set the `OPENAI_API_KEY` environment variable in your development environment.
58
+
59
+ ## Contributing
60
+ To be determined...
61
+
62
+ ## License
63
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1,83 @@
1
+ // force align button in DOM
2
+ document.addEventListener("DOMContentLoaded", () => {
3
+ const h1 = document.querySelector("h1");
4
+ const duck = document.getElementById("rubber-duck-container");
5
+
6
+ if (!h1 || !duck) return;
7
+
8
+ h1.style.display = "flex";
9
+ h1.style.alignItems = "center";
10
+
11
+ h1.appendChild(duck);
12
+ });
13
+
14
+ // create modal & API call
15
+ (function () {
16
+ // 1. Load Marked (Markdown) and Prism (Syntax Highlighting) libs
17
+ const libs = [
18
+ "https://cdn.jsdelivr.net/npm/marked/marked.min.js",
19
+ "https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js",
20
+ "https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-ruby.min.js", // Support for Ruby
21
+ ];
22
+
23
+ libs.forEach((src) => {
24
+ const script = document.createElement("script");
25
+ script.src = src;
26
+ document.head.appendChild(script);
27
+ });
28
+
29
+ // 2. Load Prism CSS for the theme
30
+ const link = document.createElement("link");
31
+ link.rel = "stylesheet";
32
+ link.href =
33
+ "https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css";
34
+ document.head.appendChild(link);
35
+
36
+ const button = document.getElementById("rubber-duck-button");
37
+ const modal = document.getElementById("rubber-duck-modal");
38
+ const overlay = document.getElementById("rubber-duck-overlay");
39
+ const closeBtn = document.getElementById("rubber-duck-close");
40
+ const content = document.getElementById("rubber-duck-content");
41
+
42
+ button.addEventListener("click", async () => {
43
+ modal.style.display = "block";
44
+ overlay.style.display = "block";
45
+ content.innerHTML = "Analyzing error... This may take a few seconds.";
46
+
47
+ try {
48
+ const response = await fetch("/rubber_duck/analyze_error", {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/json" },
51
+ body: JSON.stringify(errorData),
52
+ });
53
+ const result = await response.json();
54
+
55
+ if (result.success) {
56
+ content.innerHTML = window.marked.parse(result.response);
57
+ setTimeout(() => {
58
+ if (window.Prism) {
59
+ window.Prism.highlightAllUnder(content);
60
+ }
61
+ }, 100);
62
+ } else {
63
+ content.innerHTML =
64
+ '<span style="color: #DC2626;">Error: ' +
65
+ (result.error || "Unknown error") +
66
+ "</span>";
67
+ }
68
+ } catch (error) {
69
+ content.innerHTML =
70
+ '<span style="color: #DC2626;">Failed to connect: ' +
71
+ error.message +
72
+ "</span>";
73
+ }
74
+ });
75
+
76
+ function closeModal() {
77
+ modal.style.display = "none";
78
+ overlay.style.display = "none";
79
+ }
80
+
81
+ closeBtn.addEventListener("click", closeModal);
82
+ overlay.addEventListener("click", closeModal);
83
+ })();
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module RubberDuck
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,18 @@
1
+ module RubberDuck
2
+ class ErrorsController < ApplicationController
3
+ skip_before_action :verify_authenticity_token, only: [:analyze]
4
+
5
+ def analyze
6
+ unless Rails.env.development?
7
+ return render json: { error: "Only available in development" }, status: :forbidden
8
+ end
9
+
10
+ result = AiService.analyze_error(
11
+ exception_message: params[:exception],
12
+ backtrace: params[:backtrace],
13
+ logs: params[:logs]
14
+ )
15
+ render json: result
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,4 @@
1
+ module RubberDuck
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module RubberDuck
2
+ module ErrorsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module RubberDuck
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module RubberDuck
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module RubberDuck
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,72 @@
1
+ require "faraday"
2
+ require "json"
3
+
4
+ module RubberDuck
5
+ class AiService
6
+ class << self
7
+ def analyze_error(exception_message:, backtrace:, logs:)
8
+ return { error: "No API key configured" } unless RubberDuck.configuration.openai_api_key
9
+ prompt = build_prompt(exception_message, backtrace, logs)
10
+
11
+ begin
12
+ response = call_openai(prompt)
13
+ { success: true, response: response }
14
+ rescue => e
15
+ { success: false, error: e.message }
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def build_prompt(exception, backtrace, logs)
22
+ <<~PROMPT
23
+ INSTRUCTIONS:
24
+ - Keep verbosity at the minimum.
25
+ - Keep your response concise and actionable.
26
+ - Always return every single code snippet inside triple backticks with a language tag.
27
+ - Do not add <ul> or <li> tags.
28
+ You are a helpful Ruby on Rails debugging assistant. A developer encountered this error:
29
+ ERROR: #{exception}
30
+ BACKTRACE:
31
+ #{backtrace&.first(10)&.join("\n") || "No backtrace available"}
32
+ RECENT LOGS:
33
+ #{logs || "No logs available"}
34
+ OBJECTIVE:
35
+ 1. Explain what this error means in simple terms
36
+ 2. Explain the HTTP error in simple terms if any available
37
+ 3. Identify the likely causes
38
+ 4. Suggest specific fixes
39
+ PROMPT
40
+ end
41
+
42
+ def call_openai(prompt)
43
+ conn = Faraday.new(url: "https://api.openai.com") do |f|
44
+ f.request :json
45
+ f.response :json
46
+ f.adapter Faraday.default_adapter
47
+ end
48
+ response = conn.post("/v1/responses") do |req|
49
+ req.headers["Authorization"] = "Bearer #{RubberDuck.configuration.openai_api_key}"
50
+ req.headers["Content-Type"] = "application/json"
51
+ req.body = {
52
+ model: RubberDuck.configuration.model || "gpt-5-nano",
53
+ input: prompt,
54
+ text: {
55
+ format: {
56
+ type: "text"
57
+ },
58
+ verbosity: "low"
59
+ },
60
+ reasoning: {
61
+ effort: "low"
62
+ }
63
+ }
64
+ end
65
+ ## debug output
66
+ # puts "======> OpenAI Response Body: #{response.body.inspect}"
67
+ ##
68
+ response.body.dig("output", 1, "content", 0, "text")
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Rubber duck</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= yield :head %>
9
+
10
+ <%= stylesheet_link_tag "rubber_duck/application", media: "all" %>
11
+ </head>
12
+ <body>
13
+
14
+ <%= yield %>
15
+
16
+ </body>
17
+ </html>
@@ -0,0 +1,11 @@
1
+ <div id="rubber-duck-container" style="display: flex; margin-left: 20px;">
2
+ <button id="rubber-duck-button" style="background: #8f9cc9; color: white; border: none; padding: 8px 16px; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
3
+ 🦆 RubberDuck
4
+ </button>
5
+ <div id="rubber-duck-modal" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 24px; border-radius: 12px; box-shadow: 0 20px 25px rgba(0,0,0,0.2); max-width: 600px; max-height: 80vh; overflow-y: auto; z-index: 10001;">
6
+ <h3 style="margin: 0 0 16px 0; color: #1F2937;"><%= @model_name.capitalize %> Analysis</h3>
7
+ <div id="rubber-duck-content" style="color: #4B5563; line-height: 1.6;">Analyzing...</div>
8
+ <button id="rubber-duck-close" style="margin-top: 16px; background: #E5E7EB; color: #374151; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer;">Close</button>
9
+ </div>
10
+ <div id="rubber-duck-overlay" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 10000;"></div>
11
+ </div>
@@ -0,0 +1,13 @@
1
+ RubberDuck.configure do |config|
2
+ # Get your API key from https://platform.openai.com
3
+ config.openai_api_key = ENV["OPENAI_API_KEY"]
4
+
5
+ # Model to use
6
+ config.model = "gpt-4o-mini"
7
+
8
+ # Enable/disable the helper
9
+ config.enabled = true
10
+
11
+ # Number of log lines to send to AI for context
12
+ config.log_lines = 50
13
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ RubberDuck::Engine.routes.draw do
2
+ post "/analyze_error", to: "errors#analyze"
3
+ end
@@ -0,0 +1,40 @@
1
+ require "rails/generators"
2
+
3
+ module RubberDuck
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ def create_initializer_file
9
+ create_file "config/initializers/rubber_duck.rb", <<~CONTENT
10
+ RubberDuck.configure do |config|
11
+ # Get your API key from https://platform.openai.com
12
+ config.openai_api_key = ENV["OPENAI_API_KEY"]
13
+
14
+ # Model to use
15
+ config.model = "gpt-5-nano"
16
+
17
+ # Enable/disable the helper
18
+ config.enabled = true
19
+
20
+ # Number of log lines to send to AI for context
21
+ config.log_lines = 50
22
+ end
23
+ CONTENT
24
+ end
25
+
26
+ def add_route
27
+ say "Adding engine mount point to routes.rb", :green
28
+ route "mount RubberDuck::Engine => '/rubber_duck'"
29
+ end
30
+
31
+ def show_instructions
32
+ say "\n✅ RubberDuck installed!", :green
33
+ say "\nNext steps:"
34
+ say " 1. Add OPENAI_API_KEY to your .env file"
35
+ say " 2. Restart your Rails server"
36
+ say " 3. Trigger an error and look for the 'Ask AI' button\n"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ module RubberDuck
2
+ class Configuration
3
+ attr_accessor :openai_api_key, :model, :enabled, :log_lines
4
+
5
+ def initialize
6
+ @openai_api_key = nil
7
+ @model = "gpt-5-nano"
8
+ @enabled = true
9
+ @log_lines = 50
10
+ end
11
+ end
12
+ class << self
13
+
14
+ def configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ def configure
19
+ yield(configuration)
20
+ end
21
+
22
+ def reset_configuration!
23
+ @configuration = Configuration.new
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ require "rubber_duck/middleware"
2
+ module RubberDuck
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace RubberDuck
5
+ # config.app_middleware.use RubberDuck::Middleware
6
+ config.app_middleware.insert_before ActionDispatch::ShowExceptions, RubberDuck::Middleware
7
+
8
+ # Middleware injects UI on error pages
9
+ # initializer "rubber_duck.middleware" do |app|
10
+ # app.middleware.use RubberDuck::Middleware if Rails.env.development?
11
+ # end
12
+ end
13
+ end
@@ -0,0 +1,140 @@
1
+ require "rack/utils"
2
+ require "erb"
3
+
4
+ module RubberDuck
5
+ class Middleware
6
+ def initialize(app)
7
+ @app = app
8
+ @model_name = RubberDuck.configuration.model
9
+ end
10
+
11
+ def call(env)
12
+ status, headers, response = @app.call(env)
13
+
14
+ # Debug logging
15
+ # Rails.logger.info "RubberDuck: status=#{status}, content_type=#{headers['Content-Type']}, dev=#{Rails.env.development?}, enabled=#{RubberDuck.configuration.enabled}"
16
+
17
+ # Only intercept in development mode
18
+ if should_inject?(env, status, headers)
19
+ # Rails.logger.info "RubberDuck: Replacing response with custom error page"
20
+ return inject_button_response(env, status, headers, response)
21
+ end
22
+
23
+ [ status, headers, response ]
24
+ end
25
+
26
+ private
27
+
28
+ def should_inject?(env, status, headers)
29
+ # Check if this is an error response
30
+ return false unless Rails.env.development?
31
+ return false unless RubberDuck.configuration.enabled
32
+ return false unless status >= 400
33
+
34
+ # ONLY inject if request for HTML
35
+ # accept_header = env["HTTP_ACCEPT"].to_s
36
+ # return false unless accept_header.include?("text/html")
37
+
38
+ # Ignore specific background noise
39
+ # path = env["PATH_INFO"].to_s
40
+ # return false if path.match?(/\.(ico|json|map|png|jpg|js|css)$/)
41
+
42
+ true
43
+ end
44
+
45
+ def inject_button_response(env, status, headers, response)
46
+ # Read original response body
47
+ original_body = ""
48
+ response.each { |part| original_body << part }
49
+ response.close if response.respond_to?(:close)
50
+
51
+ # Rails.logger.info "RubberDuck: Original body size: #{original_body.bytesize}"
52
+ # Rails.logger.info "RubberDuck: Has </body>?: #{original_body.include?('</body>')}"
53
+
54
+ # Don't inject if not HTML
55
+ unless original_body.include?('<html') || original_body.include?('</body>')
56
+ # Rails.logger.info "RubberDuck: Not HTML, skipping injection"
57
+ return [ status, headers, [ original_body ] ]
58
+ end
59
+
60
+ exception = env["action_dispatch.exception"]
61
+ modified_body = inject_button_into_html(original_body, exception, env, status)
62
+
63
+ # Rails.logger.info "RubberDuck: Modified body size: #{modified_body.bytesize}"
64
+ # Rails.logger.info "RubberDuck: Button injected?: #{modified_body.include?('rubber-duck-button')}"
65
+
66
+ headers["Content-Type"] = "text/html; charset=utf-8"
67
+ headers["Content-Length"] = modified_body.bytesize.to_s
68
+ headers["X-RubberDuck-Handled"] = "true"
69
+
70
+ [ status, headers, [ modified_body ] ]
71
+ end
72
+
73
+ def inject_button_into_html(html, exception, env, status)
74
+ gem_path = Gem.loaded_specs['rubber_duck'].full_gem_path
75
+ # button_html = ApplicationController.render(
76
+ # partial: "rubber_duck/button",
77
+ # locals: {
78
+ # model_name: @model_name
79
+ # }
80
+ # )
81
+ partial_path = File.join(gem_path, 'app/views/rubber_duck/_button.html.erb')
82
+ partial_content = File.read(partial_path)
83
+ button_html = ERB.new(partial_content).result(binding)
84
+
85
+ logs = capture_logs
86
+ error_data_script = build_error_data_script(exception, env, status, logs)
87
+
88
+ # Load JS from file
89
+ modal_js = File.read(File.join(gem_path, 'app/assets/javascripts/rubber_duck/modal.js'))
90
+
91
+ injection = <<~HTML
92
+ <div>
93
+ #{button_html}
94
+ </div>
95
+ <script>
96
+ #{error_data_script}
97
+ #{modal_js}
98
+ </script>
99
+ HTML
100
+
101
+ # Inject before </body> if exists, otherwise append
102
+ if html =~ /<\/header>/i
103
+ html.sub(/<\/header>/i, "#{injection}</header>")
104
+ else
105
+ html + injection
106
+ end
107
+ end
108
+
109
+ def build_error_data_script(exception, env, status, logs)
110
+ # Prepare the data object in Ruby
111
+ data = if exception
112
+ {
113
+ exception: exception.message,
114
+ backtrace: exception.backtrace&.first(10) || [],
115
+ logs: logs
116
+ }
117
+ else
118
+ {
119
+ status: status,
120
+ path: env["PATH_INFO"],
121
+ logs: logs
122
+ }
123
+ end
124
+
125
+ # Inject it as a single, globally accessible JSON object
126
+ <<~JS
127
+ window.errorData = #{data.to_json};
128
+ JS
129
+ end
130
+
131
+ def capture_logs
132
+ log_file = Rails.root.join("log", "development.log")
133
+ return "Logs not available" unless File.exist?(log_file)
134
+ lines = File.readlines(log_file).last(RubberDuck.configuration.log_lines)
135
+ lines.join
136
+ rescue => e
137
+ "Error reading logs: #{e.message}"
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,3 @@
1
+ module RubberDuck
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,8 @@
1
+ require "rubber_duck/version"
2
+ require "rubber_duck/configuration"
3
+ require "rubber_duck/middleware"
4
+ require "rubber_duck/engine"
5
+
6
+ module RubberDuck
7
+ # Your code goes here...
8
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rubber_duck do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubber_duck
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Emmanuel Vernet
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 8.0.2.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '8.0'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 8.0.2.1
32
+ - !ruby/object:Gem::Dependency
33
+ name: faraday
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '2.0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '2.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: dotenv-rails
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ type: :development
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ description: This gem allows the Rails developer to avoid switching context from Rails
61
+ error pages during development. When getting an error, you can send the error and
62
+ logs to an AI model of your choice and get a response to help you understand or
63
+ pin point the issue while avoiding copy pasting code or logs into an external AI
64
+ window. Perfect for those who prefer to code with minimal AI presence in their editor
65
+ of choice!
66
+ email:
67
+ - vernet.emmanuel@gmail.com
68
+ executables: []
69
+ extensions: []
70
+ extra_rdoc_files: []
71
+ files:
72
+ - MIT-LICENSE
73
+ - README.md
74
+ - Rakefile
75
+ - app/assets/images/rubber_duck/v-0-example.png
76
+ - app/assets/javascripts/rubber_duck/modal.js
77
+ - app/assets/stylesheets/rubber_duck/application.css
78
+ - app/controllers/rubber_duck/application_controller.rb
79
+ - app/controllers/rubber_duck/errors_controller.rb
80
+ - app/helpers/rubber_duck/application_helper.rb
81
+ - app/helpers/rubber_duck/errors_helper.rb
82
+ - app/jobs/rubber_duck/application_job.rb
83
+ - app/mailers/rubber_duck/application_mailer.rb
84
+ - app/models/rubber_duck/application_record.rb
85
+ - app/services/rubber_duck/ai_service.rb
86
+ - app/views/layouts/rubber_duck/application.html.erb
87
+ - app/views/rubber_duck/_button.html.erb
88
+ - config/initializers/rubber_duck.rb
89
+ - config/routes.rb
90
+ - lib/generators/rubber_duck/install_generator.rb
91
+ - lib/rubber_duck.rb
92
+ - lib/rubber_duck/configuration.rb
93
+ - lib/rubber_duck/engine.rb
94
+ - lib/rubber_duck/middleware.rb
95
+ - lib/rubber_duck/version.rb
96
+ - lib/tasks/rubber_duck_tasks.rake
97
+ homepage: https://github.com/EmmanuelVernet/rubber_duck
98
+ licenses:
99
+ - MIT
100
+ metadata:
101
+ allowed_push_host: https://rubygems.org
102
+ homepage_uri: https://github.com/EmmanuelVernet/rubber_duck
103
+ source_code_uri: https://github.com/EmmanuelVernet/rubber_duck
104
+ changelog_uri: https://github.com/EmmanuelVernet/rubber_duck/releases
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '3.2'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubygems_version: 3.6.9
120
+ specification_version: 4
121
+ summary: RubberDuck is a Developer error helper gem to help you analyze errors with
122
+ AI in Rails
123
+ test_files: []