copy_for_ai 0.1.4

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: e41927310321363d9badd434462197ae4fd62a28d705bbc851bcdd5e4be9f521
4
+ data.tar.gz: 623374e6bb274dd721ac42b6d3ed129ffe17024afc68eb8560d784744b3c23c0
5
+ SHA512:
6
+ metadata.gz: 6a551348845665088b0d2f4ecd758f38b83b74be9eb5e04021ca35ddc84632018a95eb70f3181d82bd1f418a794dd0953cb115acd597724b398609fc71f4566f
7
+ data.tar.gz: 810b4cf6c62442ff27c0d9a959ce41876102de321b7d695a866a3f3bd485098cdc6fe228a85df0f90c2c29e9cef9b4a6ae0a653234859d7f8e80f01d2a5c9ff8
data/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.4] - 2024-12-29
11
+
12
+ ### Added
13
+ - Initial release
14
+ - "Copy for AI" button on Rails development error pages
15
+ - Copies error details as formatted Markdown
16
+ - Includes error type, message, source code, stack traces, and request info
17
+ - Compatible with Rails 7.0+ and 8.0+
18
+ - Styled to match Rails error page aesthetics
19
+ - Clipboard API with fallback for older browsers
20
+
21
+ ### Technical Details
22
+ - Middleware-based injection (inserted before ActionDispatch::DebugExceptions)
23
+ - Automatic detection of Rails debug error pages
24
+ - Multiple fallback strategies for button placement
25
+ - Development-only activation
26
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Andrew Miller
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
13
+ all 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
21
+ THE SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # Copy for AI
2
+
3
+ A Rails gem that adds a **"Copy for AI"** button to development error pages, allowing you to copy error details as formatted Markdown optimized for AI agent consumption.
4
+
5
+ ## Features
6
+
7
+ - Adds a styled button to Rails development error pages
8
+ - Extracts comprehensive error information:
9
+ - Error type and message
10
+ - Source code with file and line number
11
+ - Application stack trace
12
+ - Full stack trace
13
+ - Request details and parameters
14
+ - Formats output as clean Markdown for easy LLM parsing
15
+ - Zero configuration required
16
+ - Only active in development environment
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ ```ruby
23
+ group :development do
24
+ gem 'copy_for_ai'
25
+ end
26
+ ```
27
+
28
+ And then execute:
29
+
30
+ ```bash
31
+ bundle install
32
+ ```
33
+
34
+ That's it! The gem automatically hooks into Rails and adds the button to error pages.
35
+
36
+ ## Usage
37
+
38
+ 1. When your Rails app encounters an error in development, you'll see the standard Rails error page
39
+ 2. Look for the **"Copy for AI"** button in the header area (near the trace toggle buttons)
40
+ 3. Click the button to copy all error details to your clipboard
41
+ 4. Paste the Markdown into your AI assistant of choice
42
+
43
+ ### Example Output
44
+
45
+ When you click "Copy for AI", you'll get formatted Markdown like this:
46
+
47
+ ```markdown
48
+ # Rails Error: NoMethodError
49
+
50
+ ## Error Message
51
+
52
+ undefined method `foo' for nil:NilClass
53
+
54
+ ## Source Code
55
+
56
+ **File:** `app/controllers/users_controller.rb`
57
+ **Line:** 15
58
+
59
+ ​```ruby
60
+ def show
61
+ @user = User.find(params[:id])
62
+ @user.foo # Error occurs here
63
+ end
64
+ ​```
65
+
66
+ ## Application Trace
67
+
68
+ ​```
69
+ app/controllers/users_controller.rb:15:in `show'
70
+ ​```
71
+
72
+ ## Full Trace
73
+
74
+ ​```
75
+ app/controllers/users_controller.rb:15:in `show'
76
+ actionpack (7.0.0) lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
77
+ ...
78
+ ​```
79
+
80
+ ## Request Details
81
+
82
+ - **URL:** /users/1
83
+ - **Method:** GET
84
+ - **Parameters:**
85
+ ​```
86
+ {"id"=>"1"}
87
+ ​```
88
+ ```
89
+
90
+ ## Configuration
91
+
92
+ Currently, the gem works out of the box with no configuration needed. It automatically:
93
+
94
+ - Detects Rails development environment
95
+ - Identifies Rails error pages
96
+ - Injects the copy button with appropriate styling
97
+
98
+ ## Requirements
99
+
100
+ - Ruby >= 2.7.0
101
+ - Rails 7.0+ or Rails 8.0+
102
+
103
+ **Tested with:**
104
+ - Rails 7.0, 7.1, 7.2
105
+ - Rails 8.0
106
+
107
+ ## Development
108
+
109
+ After checking out the repo, run `bundle install` to install dependencies.
110
+
111
+ To install this gem onto your local machine, run:
112
+
113
+ ```bash
114
+ bundle exec rake install
115
+ ```
116
+
117
+ ## Contributing
118
+
119
+ Bug reports and pull requests are welcome on GitHub.
120
+
121
+ ## License
122
+
123
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
124
+
@@ -0,0 +1,366 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CopyForAi
4
+ class ContentInjector
5
+ BUTTON_AND_SCRIPT = <<~HTML
6
+ <style>
7
+ .copy-for-ai-btn {
8
+ background: none;
9
+ color: #CC0000;
10
+ padding: 0;
11
+ cursor: pointer;
12
+ border: none;
13
+ font-family: inherit;
14
+ font-size: inherit;
15
+ text-decoration: underline;
16
+ display: inline;
17
+ transition: color 0.15s ease;
18
+ }
19
+ .copy-for-ai-btn:hover {
20
+ color: #990000;
21
+ }
22
+ .copy-for-ai-btn.copied {
23
+ color: #00AA00;
24
+ }
25
+ .copy-for-ai-floating {
26
+ position: fixed;
27
+ top: 10px;
28
+ right: 10px;
29
+ z-index: 99999;
30
+ background: #CC0000;
31
+ color: #fff;
32
+ padding: 8px 16px;
33
+ font-size: 14px;
34
+ border-radius: 4px;
35
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
36
+ text-decoration: none;
37
+ }
38
+ .copy-for-ai-floating:hover {
39
+ background: #990000;
40
+ }
41
+ .copy-for-ai-floating.copied {
42
+ background: #00AA00;
43
+ color: #fff;
44
+ }
45
+ </style>
46
+ <script>
47
+ (function() {
48
+ function initCopyForAi() {
49
+ // Create the button
50
+ var btn = document.createElement('button');
51
+ btn.className = 'copy-for-ai-btn';
52
+ btn.textContent = 'Copy for AI';
53
+ btn.title = 'Copy error details as Markdown for AI agents';
54
+
55
+ var inserted = false;
56
+
57
+ // Strategy 1: Find the trace links and insert after the last one
58
+ var traceLinks = document.querySelectorAll('a');
59
+ var lastTraceLink = null;
60
+
61
+ for (var i = 0; i < traceLinks.length; i++) {
62
+ var link = traceLinks[i];
63
+ var text = link.textContent.toLowerCase();
64
+ if (text.includes('application trace') || text.includes('framework trace') || text.includes('full trace')) {
65
+ lastTraceLink = link;
66
+ }
67
+ }
68
+
69
+ if (lastTraceLink) {
70
+ // Insert right after the last trace link as a sibling
71
+ var separator = document.createTextNode(' | ');
72
+ lastTraceLink.parentNode.insertBefore(separator, lastTraceLink.nextSibling);
73
+ lastTraceLink.parentNode.insertBefore(btn, separator.nextSibling);
74
+ inserted = true;
75
+ }
76
+
77
+ // Strategy 2: Look for header with Rails.root info
78
+ if (!inserted) {
79
+ var allText = document.body.innerText;
80
+ if (allText.includes('Rails.root:')) {
81
+ // Find the element containing Rails.root
82
+ var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
83
+ while (walker.nextNode()) {
84
+ if (walker.currentNode.textContent.includes('Rails.root:')) {
85
+ var parent = walker.currentNode.parentElement;
86
+ if (parent) {
87
+ parent.parentElement.insertBefore(btn, parent.nextSibling);
88
+ inserted = true;
89
+ break;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ // Strategy 3: Insert after h1 or h2
97
+ if (!inserted) {
98
+ var h2 = document.querySelector('h2');
99
+ if (h2 && h2.parentElement) {
100
+ h2.parentElement.insertBefore(btn, h2.nextSibling);
101
+ inserted = true;
102
+ }
103
+ }
104
+
105
+ // Strategy 4: Floating button as fallback
106
+ if (!inserted) {
107
+ btn.classList.add('copy-for-ai-floating');
108
+ document.body.appendChild(btn);
109
+ }
110
+
111
+ btn.addEventListener('click', function(e) {
112
+ e.preventDefault();
113
+ var markdown = extractErrorAsMarkdown();
114
+ copyToClipboard(markdown, btn);
115
+ });
116
+ }
117
+
118
+ function extractErrorAsMarkdown() {
119
+ var lines = [];
120
+
121
+ // Extract error type from h1
122
+ var h1 = document.querySelector('h1');
123
+ var errorType = h1 ? h1.textContent.trim() : 'Unknown Error';
124
+
125
+ // Extract error message from h2
126
+ var h2 = document.querySelector('h2');
127
+ var errorMessage = h2 ? h2.textContent.trim() : '';
128
+
129
+ lines.push('# Rails Error: ' + errorType);
130
+ lines.push('');
131
+ if (errorMessage) {
132
+ lines.push('## Error Message');
133
+ lines.push('');
134
+ lines.push(errorMessage);
135
+ lines.push('');
136
+ }
137
+
138
+ // Extract Rails.root info
139
+ var bodyText = document.body.innerText;
140
+ var railsRootMatch = bodyText.match(/Rails\\.root:\\s*([^\\n]+)/);
141
+ if (railsRootMatch) {
142
+ lines.push('**Rails.root:** `' + railsRootMatch[1].trim() + '`');
143
+ lines.push('');
144
+ }
145
+
146
+ // Extract source code sections
147
+ var sourceInfo = extractSourceCode();
148
+ if (sourceInfo) {
149
+ lines.push('## Source Code');
150
+ lines.push('');
151
+ if (sourceInfo.file) {
152
+ lines.push('**File:** `' + sourceInfo.file + '`');
153
+ }
154
+ if (sourceInfo.line) {
155
+ lines.push('**Line:** ' + sourceInfo.line);
156
+ }
157
+ lines.push('');
158
+ if (sourceInfo.code) {
159
+ lines.push('```ruby');
160
+ lines.push(sourceInfo.code);
161
+ lines.push('```');
162
+ lines.push('');
163
+ }
164
+ }
165
+
166
+ // Extract traces
167
+ var traces = extractAllTraces();
168
+ if (traces.application) {
169
+ lines.push('## Application Trace');
170
+ lines.push('');
171
+ lines.push('```');
172
+ lines.push(traces.application);
173
+ lines.push('```');
174
+ lines.push('');
175
+ }
176
+ if (traces.framework) {
177
+ lines.push('## Framework Trace');
178
+ lines.push('');
179
+ lines.push('```');
180
+ lines.push(traces.framework);
181
+ lines.push('```');
182
+ lines.push('');
183
+ }
184
+ if (traces.full) {
185
+ lines.push('## Full Trace');
186
+ lines.push('');
187
+ lines.push('```');
188
+ lines.push(traces.full);
189
+ lines.push('```');
190
+ lines.push('');
191
+ }
192
+
193
+ // Extract Routes section if present (for routing errors)
194
+ var routesSection = extractRoutes();
195
+ if (routesSection) {
196
+ lines.push('## Available Routes');
197
+ lines.push('');
198
+ lines.push('```');
199
+ lines.push(routesSection);
200
+ lines.push('```');
201
+ lines.push('');
202
+ }
203
+
204
+ return lines.join('\\n');
205
+ }
206
+
207
+ function extractSourceCode() {
208
+ var result = {};
209
+
210
+ // Look for source extract divs
211
+ var sourceExtracts = document.querySelectorAll('[id*="source"], [class*="source"], .extract');
212
+ for (var i = 0; i < sourceExtracts.length; i++) {
213
+ var section = sourceExtracts[i];
214
+
215
+ // Look for file info
216
+ var headerText = '';
217
+ var headers = section.querySelectorAll('h4, h5, .info, [class*="file"]');
218
+ if (headers.length > 0) {
219
+ headerText = headers[0].textContent.trim();
220
+ var match = headerText.match(/([^:]+\\.rb):?(\\d+)?/);
221
+ if (match) {
222
+ result.file = match[1].trim();
223
+ if (match[2]) result.line = match[2];
224
+ }
225
+ }
226
+
227
+ // Look for code
228
+ var code = section.querySelector('pre, code, .code');
229
+ if (code) {
230
+ result.code = code.textContent.trim();
231
+ break;
232
+ }
233
+ }
234
+
235
+ return (result.code || result.file) ? result : null;
236
+ }
237
+
238
+ function extractAllTraces() {
239
+ var traces = {
240
+ application: null,
241
+ framework: null,
242
+ full: null
243
+ };
244
+
245
+ // Look for trace divs by ID
246
+ var appTrace = document.querySelector('#Application-Trace, #application-trace, [id*="Application"][id*="Trace"]');
247
+ var frameworkTrace = document.querySelector('#Framework-Trace, #framework-trace, [id*="Framework"][id*="Trace"]');
248
+ var fullTrace = document.querySelector('#Full-Trace, #full-trace, [id*="Full"][id*="Trace"]');
249
+
250
+ if (appTrace) {
251
+ var pre = appTrace.querySelector('pre, code');
252
+ if (pre) traces.application = pre.textContent.trim();
253
+ }
254
+ if (frameworkTrace) {
255
+ var pre = frameworkTrace.querySelector('pre, code');
256
+ if (pre) traces.framework = pre.textContent.trim();
257
+ }
258
+ if (fullTrace) {
259
+ var pre = fullTrace.querySelector('pre, code');
260
+ if (pre) traces.full = pre.textContent.trim();
261
+ }
262
+
263
+ // Fallback: look for any trace-related content
264
+ if (!traces.application && !traces.framework && !traces.full) {
265
+ var allPres = document.querySelectorAll('pre');
266
+ for (var i = 0; i < allPres.length; i++) {
267
+ var pre = allPres[i];
268
+ var text = pre.textContent;
269
+ // Check if it looks like a stack trace
270
+ if (text.includes(':in `') || text.includes('.rb:')) {
271
+ if (!traces.full) {
272
+ traces.full = text.trim();
273
+ }
274
+ }
275
+ }
276
+ }
277
+
278
+ return traces;
279
+ }
280
+
281
+ function extractRoutes() {
282
+ // For routing errors, extract available routes
283
+ var routesTable = document.querySelector('table');
284
+ if (routesTable) {
285
+ var rows = routesTable.querySelectorAll('tr');
286
+ if (rows.length > 0) {
287
+ var routeLines = [];
288
+ rows.forEach(function(row) {
289
+ var cells = row.querySelectorAll('th, td');
290
+ if (cells.length >= 3) {
291
+ routeLines.push(
292
+ (cells[0].textContent.trim().padEnd(10) + ' ') +
293
+ (cells[1].textContent.trim().padEnd(40) + ' ') +
294
+ cells[2].textContent.trim()
295
+ );
296
+ }
297
+ });
298
+ if (routeLines.length > 1) { // More than just header
299
+ return routeLines.slice(0, 20).join('\\n') + (rows.length > 20 ? '\\n... and more' : '');
300
+ }
301
+ }
302
+ }
303
+ return null;
304
+ }
305
+
306
+ function copyToClipboard(text, btn) {
307
+ if (navigator.clipboard && navigator.clipboard.writeText) {
308
+ navigator.clipboard.writeText(text).then(function() {
309
+ showCopiedFeedback(btn);
310
+ }).catch(function(err) {
311
+ fallbackCopy(text, btn);
312
+ });
313
+ } else {
314
+ fallbackCopy(text, btn);
315
+ }
316
+ }
317
+
318
+ function fallbackCopy(text, btn) {
319
+ var textarea = document.createElement('textarea');
320
+ textarea.value = text;
321
+ textarea.style.position = 'fixed';
322
+ textarea.style.left = '-9999px';
323
+ document.body.appendChild(textarea);
324
+ textarea.select();
325
+ try {
326
+ document.execCommand('copy');
327
+ showCopiedFeedback(btn);
328
+ } catch (err) {
329
+ alert('Failed to copy. Error details:\\n\\n' + text.substring(0, 1000));
330
+ }
331
+ document.body.removeChild(textarea);
332
+ }
333
+
334
+ function showCopiedFeedback(btn) {
335
+ var originalText = btn.textContent;
336
+ btn.textContent = 'Copied!';
337
+ btn.classList.add('copied');
338
+ setTimeout(function() {
339
+ btn.textContent = originalText;
340
+ btn.classList.remove('copied');
341
+ }, 2000);
342
+ }
343
+
344
+ // Initialize when DOM is ready
345
+ if (document.readyState === 'loading') {
346
+ document.addEventListener('DOMContentLoaded', initCopyForAi);
347
+ } else {
348
+ initCopyForAi();
349
+ }
350
+ })();
351
+ </script>
352
+ HTML
353
+
354
+ class << self
355
+ def inject(html)
356
+ # Inject before closing </body> tag
357
+ if html.include?("</body>")
358
+ html.sub("</body>", "#{BUTTON_AND_SCRIPT}</body>")
359
+ else
360
+ # If no body tag, append to end
361
+ html + BUTTON_AND_SCRIPT
362
+ end
363
+ end
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "content_injector"
4
+
5
+ module CopyForAi
6
+ class Middleware
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ status, headers, response = @app.call(env)
13
+
14
+ # Only modify error responses (4xx and 5xx) with HTML content
15
+ if error_response?(status) && html_response?(headers)
16
+ body = extract_body(response)
17
+
18
+ # Check if this is a Rails debug error page
19
+ if rails_error_page?(body)
20
+ modified_body = ContentInjector.inject(body)
21
+ headers["Content-Length"] = modified_body.bytesize.to_s
22
+ response = [modified_body]
23
+ end
24
+ end
25
+
26
+ [status, headers, response]
27
+ end
28
+
29
+ private
30
+
31
+ def error_response?(status)
32
+ status >= 400 && status < 600
33
+ end
34
+
35
+ def html_response?(headers)
36
+ content_type = headers["Content-Type"] || headers["content-type"] || ""
37
+ content_type.include?("text/html")
38
+ end
39
+
40
+ def extract_body(response)
41
+ body = +""
42
+ response.each { |part| body << part }
43
+ response.close if response.respond_to?(:close)
44
+ body
45
+ end
46
+
47
+ def rails_error_page?(body)
48
+ # Check for Rails debug error page markers - be more flexible
49
+ # Rails 7+ uses different markers than older versions
50
+ has_trace_links = body.include?("Application Trace") ||
51
+ body.include?("Framework Trace") ||
52
+ body.include?("Full Trace") ||
53
+ body.include?("Full trace")
54
+
55
+ has_error_markers = body.include?("Rails.root:") ||
56
+ body.include?("ActionController::RoutingError") ||
57
+ body.include?("id=\"container\"") ||
58
+ body.include?("id=\"traces\"")
59
+
60
+ has_trace_links && has_error_markers
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "middleware"
4
+
5
+ module CopyForAi
6
+ class Railtie < Rails::Railtie
7
+ initializer "copy_for_ai.add_middleware", after: :load_config_initializers do |app|
8
+ # Only add middleware in development environment
9
+ if Rails.env.development?
10
+ # Insert BEFORE DebugExceptions so we can see the error page response
11
+ # that DebugExceptions generates
12
+ begin
13
+ app.middleware.insert_before ActionDispatch::DebugExceptions, CopyForAi::Middleware
14
+ rescue
15
+ # Fallback: insert at the beginning
16
+ app.middleware.insert 0, CopyForAi::Middleware
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CopyForAi
4
+ VERSION = "0.1.4"
5
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "copy_for_ai/version"
4
+ require_relative "copy_for_ai/railtie" if defined?(Rails::Railtie)
5
+
6
+ module CopyForAi
7
+ class Error < StandardError; end
8
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: copy_for_ai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Miller
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-12-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: actionpack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '7.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '7.0'
41
+ description: A Rails gem that adds a 'Copy for AI' button to development error pages,
42
+ allowing you to copy error details as formatted Markdown for easy consumption by
43
+ AI agents.
44
+ email:
45
+ - andrew@example.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - CHANGELOG.md
51
+ - LICENSE.txt
52
+ - README.md
53
+ - lib/copy_for_ai.rb
54
+ - lib/copy_for_ai/content_injector.rb
55
+ - lib/copy_for_ai/middleware.rb
56
+ - lib/copy_for_ai/railtie.rb
57
+ - lib/copy_for_ai/version.rb
58
+ homepage: https://github.com/andrewmiller/copy_for_ai
59
+ licenses:
60
+ - MIT
61
+ metadata:
62
+ homepage_uri: https://github.com/andrewmiller/copy_for_ai
63
+ source_code_uri: https://github.com/andrewmiller/copy_for_ai
64
+ changelog_uri: https://github.com/andrewmiller/copy_for_ai/blob/main/CHANGELOG.md
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 2.7.0
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.5.9
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Copy Rails error messages in AI-friendly format
84
+ test_files: []