copy_for_ai 0.1.4 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e41927310321363d9badd434462197ae4fd62a28d705bbc851bcdd5e4be9f521
4
- data.tar.gz: 623374e6bb274dd721ac42b6d3ed129ffe17024afc68eb8560d784744b3c23c0
3
+ metadata.gz: 609491e473023a0d9d124026197d92c931c6b69c53d9ee66eecdb1364bd42157
4
+ data.tar.gz: 344352c8e5cfd6c80859ddacf088e173793067665f7f40bddb135718c20675b8
5
5
  SHA512:
6
- metadata.gz: 6a551348845665088b0d2f4ecd758f38b83b74be9eb5e04021ca35ddc84632018a95eb70f3181d82bd1f418a794dd0953cb115acd597724b398609fc71f4566f
7
- data.tar.gz: 810b4cf6c62442ff27c0d9a959ce41876102de321b7d695a866a3f3bd485098cdc6fe228a85df0f90c2c29e9cef9b4a6ae0a653234859d7f8e80f01d2a5c9ff8
6
+ metadata.gz: b147e890cd27790effdf054f3f30821dd221c6e830b66d8313270d15a1262bc0e514bd130a77e8c09bd5e91695163e2f717bd4d9cad06b9b6d46698f5f06efd0
7
+ data.tar.gz: 733e82ba8e9bbdc43226544a68b3f2dc566c6bb74f8626cb7d6caf902f7fd6949282e3c0f189ba53a27e65d8b8096848e86e3f7f73f1423a7cf90c2f29af000d
data/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2024-12-29
11
+
12
+ ### Added
13
+ - **Format selector**: Choose between TOON, Markdown, or Plaintext output formats
14
+ - **TOON format** (default): Token-efficient format optimized for LLM consumption
15
+ - Based on [TOON specification](https://github.com/toon-format/toon)
16
+ - Reduces token usage compared to Markdown
17
+ - **Markdown format**: Rich formatting with headers and code blocks
18
+ - **Plaintext format**: Simple text output for maximum compatibility
19
+ - Format preference persisted in localStorage
20
+
21
+ ### Changed
22
+ - Default output format changed from Markdown to TOON
23
+ - Refactored JavaScript to use structured error data extraction
24
+
10
25
  ## [0.1.4] - 2024-12-29
11
26
 
12
27
  ### Added
data/README.md CHANGED
@@ -1,17 +1,18 @@
1
1
  # Copy for AI
2
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.
3
+ A Rails gem that adds a **"Copy for AI"** button to development error pages, allowing you to copy error details in formats optimized for AI agent consumption.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - Adds a styled button to Rails development error pages
8
+ - **Multiple output formats**: TOON (default), Markdown, or Plaintext
8
9
  - Extracts comprehensive error information:
9
10
  - Error type and message
10
11
  - Source code with file and line number
11
12
  - Application stack trace
12
13
  - Full stack trace
13
- - Request details and parameters
14
- - Formats output as clean Markdown for easy LLM parsing
14
+ - Available routes (for routing errors)
15
+ - Format preference saved in localStorage
15
16
  - Zero configuration required
16
17
  - Only active in development environment
17
18
 
@@ -36,64 +37,77 @@ That's it! The gem automatically hooks into Rails and adds the button to error p
36
37
  ## Usage
37
38
 
38
39
  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
40
+ 2. Look for the **"Copy for AI"** button next to the trace toggle links
41
+ 3. Select your preferred format from the dropdown (TOON, MD, or TXT)
42
+ 4. Click the button to copy all error details to your clipboard
43
+ 5. Paste into your AI assistant of choice
42
44
 
43
- ### Example Output
45
+ ## Output Formats
44
46
 
45
- When you click "Copy for AI", you'll get formatted Markdown like this:
47
+ ### TOON (Default)
46
48
 
47
- ```markdown
48
- # Rails Error: NoMethodError
49
+ [TOON](https://github.com/toon-format/toon) (Token-Oriented Object Notation) is a compact, human-readable format designed for LLM prompts. It reduces token usage while remaining easy to parse.
49
50
 
50
- ## Error Message
51
+ ```
52
+ error_type: Routing Error
53
+ error_message: "No route matches [GET] \\"/asdf\\""
54
+ rails_root: /Users/dev/myapp
51
55
 
52
- undefined method `foo' for nil:NilClass
56
+ application_trace:
57
+ app/controllers/users_controller.rb:15:in `show'
53
58
 
54
- ## Source Code
59
+ full_trace:
60
+ app/controllers/users_controller.rb:15:in `show'
61
+ actionpack (7.0.0) lib/action_controller/metal/basic_implicit_render.rb:6
62
+ ```
63
+
64
+ ### Markdown
55
65
 
56
- **File:** `app/controllers/users_controller.rb`
57
- **Line:** 15
66
+ Rich formatting with headers and code blocks:
58
67
 
59
- ​```ruby
60
- def show
61
- @user = User.find(params[:id])
62
- @user.foo # Error occurs here
63
- end
64
- ​```
68
+ ```markdown
69
+ # Rails Error: Routing Error
70
+
71
+ ## Error Message
72
+
73
+ No route matches [GET] "/asdf"
74
+
75
+ **Rails.root:** `/Users/dev/myapp`
65
76
 
66
77
  ## Application Trace
67
78
 
68
- ​```
79
+ \`\`\`
69
80
  app/controllers/users_controller.rb:15:in `show'
70
- ​```
81
+ \`\`\`
82
+ ```
71
83
 
72
- ## Full Trace
84
+ ### Plaintext
73
85
 
74
- ​```
86
+ Simple text format for maximum compatibility:
87
+
88
+ ```
89
+ RAILS ERROR: Routing Error
90
+ ==================================================
91
+
92
+ ERROR MESSAGE:
93
+ No route matches [GET] "/asdf"
94
+
95
+ RAILS ROOT: /Users/dev/myapp
96
+
97
+ APPLICATION TRACE:
98
+ ------------------------------
75
99
  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
100
  ```
89
101
 
90
- ## Configuration
102
+ ## Why TOON?
103
+
104
+ TOON format offers significant token savings compared to Markdown while remaining human-readable and easy for LLMs to parse. This means:
91
105
 
92
- Currently, the gem works out of the box with no configuration needed. It automatically:
106
+ - **Lower API costs** when using paid LLM APIs
107
+ - **More context** can fit in the same token window
108
+ - **Faster responses** due to reduced input size
93
109
 
94
- - Detects Rails development environment
95
- - Identifies Rails error pages
96
- - Injects the copy button with appropriate styling
110
+ Learn more at [toonformat.dev](https://toonformat.dev).
97
111
 
98
112
  ## Requirements
99
113
 
@@ -108,6 +122,12 @@ Currently, the gem works out of the box with no configuration needed. It automat
108
122
 
109
123
  After checking out the repo, run `bundle install` to install dependencies.
110
124
 
125
+ Run the test suite:
126
+
127
+ ```bash
128
+ bundle exec rspec
129
+ ```
130
+
111
131
  To install this gem onto your local machine, run:
112
132
 
113
133
  ```bash
@@ -121,4 +141,3 @@ Bug reports and pull requests are welcome on GitHub.
121
141
  ## License
122
142
 
123
143
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
124
-
@@ -4,6 +4,9 @@ module CopyForAi
4
4
  class ContentInjector
5
5
  BUTTON_AND_SCRIPT = <<~HTML
6
6
  <style>
7
+ .copy-for-ai-container {
8
+ display: inline;
9
+ }
7
10
  .copy-for-ai-btn {
8
11
  background: none;
9
12
  color: #CC0000;
@@ -22,35 +25,97 @@ module CopyForAi
22
25
  .copy-for-ai-btn.copied {
23
26
  color: #00AA00;
24
27
  }
28
+ .copy-for-ai-format-select {
29
+ background: #333;
30
+ color: #CC0000;
31
+ border: 1px solid #555;
32
+ border-radius: 3px;
33
+ padding: 1px 4px;
34
+ font-size: 11px;
35
+ font-family: inherit;
36
+ cursor: pointer;
37
+ margin-left: 4px;
38
+ vertical-align: baseline;
39
+ }
40
+ .copy-for-ai-format-select:hover {
41
+ border-color: #CC0000;
42
+ }
43
+ .copy-for-ai-format-select:focus {
44
+ outline: none;
45
+ border-color: #CC0000;
46
+ }
25
47
  .copy-for-ai-floating {
26
48
  position: fixed;
27
49
  top: 10px;
28
50
  right: 10px;
29
51
  z-index: 99999;
30
- background: #CC0000;
31
- color: #fff;
32
- padding: 8px 16px;
33
- font-size: 14px;
52
+ background: #222;
53
+ padding: 8px 12px;
34
54
  border-radius: 4px;
35
55
  box-shadow: 0 2px 8px rgba(0,0,0,0.3);
56
+ }
57
+ .copy-for-ai-floating .copy-for-ai-btn {
58
+ background: #CC0000;
59
+ color: #fff;
60
+ padding: 4px 12px;
61
+ border-radius: 3px;
36
62
  text-decoration: none;
37
63
  }
38
- .copy-for-ai-floating:hover {
64
+ .copy-for-ai-floating .copy-for-ai-btn:hover {
39
65
  background: #990000;
66
+ color: #fff;
40
67
  }
41
- .copy-for-ai-floating.copied {
68
+ .copy-for-ai-floating .copy-for-ai-btn.copied {
42
69
  background: #00AA00;
43
- color: #fff;
70
+ }
71
+ .copy-for-ai-floating .copy-for-ai-format-select {
72
+ background: #444;
73
+ margin-left: 8px;
44
74
  }
45
75
  </style>
46
76
  <script>
47
77
  (function() {
78
+ var currentFormat = localStorage.getItem('copyForAiFormat') || 'toon';
79
+
48
80
  function initCopyForAi() {
81
+ // Create container
82
+ var container = document.createElement('span');
83
+ container.className = 'copy-for-ai-container';
84
+
49
85
  // Create the button
50
86
  var btn = document.createElement('button');
51
87
  btn.className = 'copy-for-ai-btn';
52
88
  btn.textContent = 'Copy for AI';
53
- btn.title = 'Copy error details as Markdown for AI agents';
89
+ btn.title = 'Copy error details for AI agents';
90
+
91
+ // Create format selector
92
+ var select = document.createElement('select');
93
+ select.className = 'copy-for-ai-format-select';
94
+ select.title = 'Select output format';
95
+
96
+ var formats = [
97
+ { value: 'toon', label: 'TOON' },
98
+ { value: 'markdown', label: 'MD' },
99
+ { value: 'plaintext', label: 'TXT' }
100
+ ];
101
+
102
+ formats.forEach(function(fmt) {
103
+ var option = document.createElement('option');
104
+ option.value = fmt.value;
105
+ option.textContent = fmt.label;
106
+ if (fmt.value === currentFormat) {
107
+ option.selected = true;
108
+ }
109
+ select.appendChild(option);
110
+ });
111
+
112
+ select.addEventListener('change', function() {
113
+ currentFormat = select.value;
114
+ localStorage.setItem('copyForAiFormat', currentFormat);
115
+ });
116
+
117
+ container.appendChild(btn);
118
+ container.appendChild(select);
54
119
 
55
120
  var inserted = false;
56
121
 
@@ -67,10 +132,9 @@ module CopyForAi
67
132
  }
68
133
 
69
134
  if (lastTraceLink) {
70
- // Insert right after the last trace link as a sibling
71
135
  var separator = document.createTextNode(' | ');
72
136
  lastTraceLink.parentNode.insertBefore(separator, lastTraceLink.nextSibling);
73
- lastTraceLink.parentNode.insertBefore(btn, separator.nextSibling);
137
+ lastTraceLink.parentNode.insertBefore(container, separator.nextSibling);
74
138
  inserted = true;
75
139
  }
76
140
 
@@ -78,13 +142,12 @@ module CopyForAi
78
142
  if (!inserted) {
79
143
  var allText = document.body.innerText;
80
144
  if (allText.includes('Rails.root:')) {
81
- // Find the element containing Rails.root
82
145
  var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
83
146
  while (walker.nextNode()) {
84
147
  if (walker.currentNode.textContent.includes('Rails.root:')) {
85
148
  var parent = walker.currentNode.parentElement;
86
149
  if (parent) {
87
- parent.parentElement.insertBefore(btn, parent.nextSibling);
150
+ parent.parentElement.insertBefore(container, parent.nextSibling);
88
151
  inserted = true;
89
152
  break;
90
153
  }
@@ -93,110 +156,233 @@ module CopyForAi
93
156
  }
94
157
  }
95
158
 
96
- // Strategy 3: Insert after h1 or h2
159
+ // Strategy 3: Insert after h2
97
160
  if (!inserted) {
98
161
  var h2 = document.querySelector('h2');
99
162
  if (h2 && h2.parentElement) {
100
- h2.parentElement.insertBefore(btn, h2.nextSibling);
163
+ h2.parentElement.insertBefore(container, h2.nextSibling);
101
164
  inserted = true;
102
165
  }
103
166
  }
104
167
 
105
- // Strategy 4: Floating button as fallback
168
+ // Strategy 4: Floating container as fallback
106
169
  if (!inserted) {
107
- btn.classList.add('copy-for-ai-floating');
108
- document.body.appendChild(btn);
170
+ container.classList.add('copy-for-ai-floating');
171
+ document.body.appendChild(container);
109
172
  }
110
173
 
111
174
  btn.addEventListener('click', function(e) {
112
175
  e.preventDefault();
113
- var markdown = extractErrorAsMarkdown();
114
- copyToClipboard(markdown, btn);
176
+ var errorData = extractErrorData();
177
+ var output;
178
+
179
+ switch (currentFormat) {
180
+ case 'toon':
181
+ output = formatAsToon(errorData);
182
+ break;
183
+ case 'markdown':
184
+ output = formatAsMarkdown(errorData);
185
+ break;
186
+ case 'plaintext':
187
+ output = formatAsPlaintext(errorData);
188
+ break;
189
+ default:
190
+ output = formatAsToon(errorData);
191
+ }
192
+
193
+ copyToClipboard(output, btn);
115
194
  });
116
195
  }
117
196
 
118
- function extractErrorAsMarkdown() {
119
- var lines = [];
197
+ function extractErrorData() {
198
+ var data = {
199
+ errorType: '',
200
+ errorMessage: '',
201
+ railsRoot: '',
202
+ source: null,
203
+ traces: { application: null, framework: null, full: null },
204
+ routes: null
205
+ };
120
206
 
121
207
  // Extract error type from h1
122
208
  var h1 = document.querySelector('h1');
123
- var errorType = h1 ? h1.textContent.trim() : 'Unknown Error';
209
+ data.errorType = h1 ? h1.textContent.trim() : 'Unknown Error';
124
210
 
125
211
  // Extract error message from h2
126
212
  var h2 = document.querySelector('h2');
127
- var errorMessage = h2 ? h2.textContent.trim() : '';
213
+ data.errorMessage = h2 ? h2.textContent.trim() : '';
214
+
215
+ // Extract Rails.root info
216
+ var bodyText = document.body.innerText;
217
+ var railsRootMatch = bodyText.match(/Rails\\.root:\\s*([^\\n]+)/);
218
+ if (railsRootMatch) {
219
+ data.railsRoot = railsRootMatch[1].trim();
220
+ }
221
+
222
+ // Extract source code
223
+ data.source = extractSourceCode();
224
+
225
+ // Extract traces
226
+ data.traces = extractAllTraces();
227
+
228
+ // Extract routes
229
+ data.routes = extractRoutes();
230
+
231
+ return data;
232
+ }
233
+
234
+ // ===== TOON FORMAT =====
235
+ function formatAsToon(data) {
236
+ var lines = [];
237
+
238
+ // Error info as simple key-value pairs
239
+ lines.push('error_type: ' + data.errorType);
240
+ if (data.errorMessage) {
241
+ lines.push('error_message: ' + escapeForToon(data.errorMessage));
242
+ }
243
+ if (data.railsRoot) {
244
+ lines.push('rails_root: ' + data.railsRoot);
245
+ }
246
+
247
+ // Source code
248
+ if (data.source) {
249
+ lines.push('');
250
+ lines.push('source:');
251
+ if (data.source.file) {
252
+ lines.push(' file: ' + data.source.file);
253
+ }
254
+ if (data.source.line) {
255
+ lines.push(' line: ' + data.source.line);
256
+ }
257
+ if (data.source.code) {
258
+ lines.push(' code: |');
259
+ data.source.code.split('\\n').forEach(function(codeLine) {
260
+ lines.push(' ' + codeLine);
261
+ });
262
+ }
263
+ }
264
+
265
+ // Traces - use compact array notation
266
+ if (data.traces.application) {
267
+ lines.push('');
268
+ lines.push('application_trace:');
269
+ formatTraceForToon(data.traces.application).forEach(function(t) {
270
+ lines.push(' ' + t);
271
+ });
272
+ }
273
+ if (data.traces.framework) {
274
+ lines.push('');
275
+ lines.push('framework_trace:');
276
+ formatTraceForToon(data.traces.framework).forEach(function(t) {
277
+ lines.push(' ' + t);
278
+ });
279
+ }
280
+ if (data.traces.full) {
281
+ lines.push('');
282
+ lines.push('full_trace:');
283
+ formatTraceForToon(data.traces.full).forEach(function(t) {
284
+ lines.push(' ' + t);
285
+ });
286
+ }
287
+
288
+ // Routes
289
+ if (data.routes) {
290
+ lines.push('');
291
+ lines.push('available_routes:');
292
+ data.routes.split('\\n').slice(0, 15).forEach(function(route) {
293
+ lines.push(' ' + route.trim());
294
+ });
295
+ }
128
296
 
129
- lines.push('# Rails Error: ' + errorType);
297
+ return lines.join('\\n');
298
+ }
299
+
300
+ function formatTraceForToon(trace) {
301
+ if (!trace) return [];
302
+ return trace.split('\\n').slice(0, 25).map(function(line) {
303
+ return line.trim();
304
+ }).filter(function(line) {
305
+ return line.length > 0;
306
+ });
307
+ }
308
+
309
+ function escapeForToon(str) {
310
+ // If string contains special chars, wrap in quotes
311
+ if (str.includes('\\n') || str.includes(':') || str.includes('"')) {
312
+ return '"' + str.replace(/"/g, '\\\\"').replace(/\\n/g, ' ') + '"';
313
+ }
314
+ return str;
315
+ }
316
+
317
+ // ===== MARKDOWN FORMAT =====
318
+ function formatAsMarkdown(data) {
319
+ var lines = [];
320
+
321
+ lines.push('# Rails Error: ' + data.errorType);
130
322
  lines.push('');
131
- if (errorMessage) {
323
+
324
+ if (data.errorMessage) {
132
325
  lines.push('## Error Message');
133
326
  lines.push('');
134
- lines.push(errorMessage);
327
+ lines.push(data.errorMessage);
135
328
  lines.push('');
136
329
  }
137
330
 
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() + '`');
331
+ if (data.railsRoot) {
332
+ lines.push('**Rails.root:** `' + data.railsRoot + '`');
143
333
  lines.push('');
144
334
  }
145
335
 
146
- // Extract source code sections
147
- var sourceInfo = extractSourceCode();
148
- if (sourceInfo) {
336
+ if (data.source) {
149
337
  lines.push('## Source Code');
150
338
  lines.push('');
151
- if (sourceInfo.file) {
152
- lines.push('**File:** `' + sourceInfo.file + '`');
339
+ if (data.source.file) {
340
+ lines.push('**File:** `' + data.source.file + '`');
153
341
  }
154
- if (sourceInfo.line) {
155
- lines.push('**Line:** ' + sourceInfo.line);
342
+ if (data.source.line) {
343
+ lines.push('**Line:** ' + data.source.line);
156
344
  }
157
345
  lines.push('');
158
- if (sourceInfo.code) {
346
+ if (data.source.code) {
159
347
  lines.push('```ruby');
160
- lines.push(sourceInfo.code);
348
+ lines.push(data.source.code);
161
349
  lines.push('```');
162
350
  lines.push('');
163
351
  }
164
352
  }
165
353
 
166
- // Extract traces
167
- var traces = extractAllTraces();
168
- if (traces.application) {
354
+ if (data.traces.application) {
169
355
  lines.push('## Application Trace');
170
356
  lines.push('');
171
357
  lines.push('```');
172
- lines.push(traces.application);
358
+ lines.push(data.traces.application);
173
359
  lines.push('```');
174
360
  lines.push('');
175
361
  }
176
- if (traces.framework) {
362
+
363
+ if (data.traces.framework) {
177
364
  lines.push('## Framework Trace');
178
365
  lines.push('');
179
366
  lines.push('```');
180
- lines.push(traces.framework);
367
+ lines.push(data.traces.framework);
181
368
  lines.push('```');
182
369
  lines.push('');
183
370
  }
184
- if (traces.full) {
371
+
372
+ if (data.traces.full) {
185
373
  lines.push('## Full Trace');
186
374
  lines.push('');
187
375
  lines.push('```');
188
- lines.push(traces.full);
376
+ lines.push(data.traces.full);
189
377
  lines.push('```');
190
378
  lines.push('');
191
379
  }
192
380
 
193
- // Extract Routes section if present (for routing errors)
194
- var routesSection = extractRoutes();
195
- if (routesSection) {
381
+ if (data.routes) {
196
382
  lines.push('## Available Routes');
197
383
  lines.push('');
198
384
  lines.push('```');
199
- lines.push(routesSection);
385
+ lines.push(data.routes);
200
386
  lines.push('```');
201
387
  lines.push('');
202
388
  }
@@ -204,19 +390,82 @@ module CopyForAi
204
390
  return lines.join('\\n');
205
391
  }
206
392
 
393
+ // ===== PLAINTEXT FORMAT =====
394
+ function formatAsPlaintext(data) {
395
+ var lines = [];
396
+
397
+ lines.push('RAILS ERROR: ' + data.errorType);
398
+ lines.push('='.repeat(50));
399
+ lines.push('');
400
+
401
+ if (data.errorMessage) {
402
+ lines.push('ERROR MESSAGE:');
403
+ lines.push(data.errorMessage);
404
+ lines.push('');
405
+ }
406
+
407
+ if (data.railsRoot) {
408
+ lines.push('RAILS ROOT: ' + data.railsRoot);
409
+ lines.push('');
410
+ }
411
+
412
+ if (data.source) {
413
+ lines.push('SOURCE CODE:');
414
+ if (data.source.file) {
415
+ lines.push('File: ' + data.source.file);
416
+ }
417
+ if (data.source.line) {
418
+ lines.push('Line: ' + data.source.line);
419
+ }
420
+ if (data.source.code) {
421
+ lines.push('');
422
+ lines.push(data.source.code);
423
+ }
424
+ lines.push('');
425
+ }
426
+
427
+ if (data.traces.application) {
428
+ lines.push('APPLICATION TRACE:');
429
+ lines.push('-'.repeat(30));
430
+ lines.push(data.traces.application);
431
+ lines.push('');
432
+ }
433
+
434
+ if (data.traces.framework) {
435
+ lines.push('FRAMEWORK TRACE:');
436
+ lines.push('-'.repeat(30));
437
+ lines.push(data.traces.framework);
438
+ lines.push('');
439
+ }
440
+
441
+ if (data.traces.full) {
442
+ lines.push('FULL TRACE:');
443
+ lines.push('-'.repeat(30));
444
+ lines.push(data.traces.full);
445
+ lines.push('');
446
+ }
447
+
448
+ if (data.routes) {
449
+ lines.push('AVAILABLE ROUTES:');
450
+ lines.push('-'.repeat(30));
451
+ lines.push(data.routes);
452
+ lines.push('');
453
+ }
454
+
455
+ return lines.join('\\n');
456
+ }
457
+
458
+ // ===== HELPER FUNCTIONS =====
207
459
  function extractSourceCode() {
208
460
  var result = {};
209
461
 
210
- // Look for source extract divs
211
462
  var sourceExtracts = document.querySelectorAll('[id*="source"], [class*="source"], .extract');
212
463
  for (var i = 0; i < sourceExtracts.length; i++) {
213
464
  var section = sourceExtracts[i];
214
465
 
215
- // Look for file info
216
- var headerText = '';
217
466
  var headers = section.querySelectorAll('h4, h5, .info, [class*="file"]');
218
467
  if (headers.length > 0) {
219
- headerText = headers[0].textContent.trim();
468
+ var headerText = headers[0].textContent.trim();
220
469
  var match = headerText.match(/([^:]+\\.rb):?(\\d+)?/);
221
470
  if (match) {
222
471
  result.file = match[1].trim();
@@ -224,7 +473,6 @@ module CopyForAi
224
473
  }
225
474
  }
226
475
 
227
- // Look for code
228
476
  var code = section.querySelector('pre, code, .code');
229
477
  if (code) {
230
478
  result.code = code.textContent.trim();
@@ -242,7 +490,6 @@ module CopyForAi
242
490
  full: null
243
491
  };
244
492
 
245
- // Look for trace divs by ID
246
493
  var appTrace = document.querySelector('#Application-Trace, #application-trace, [id*="Application"][id*="Trace"]');
247
494
  var frameworkTrace = document.querySelector('#Framework-Trace, #framework-trace, [id*="Framework"][id*="Trace"]');
248
495
  var fullTrace = document.querySelector('#Full-Trace, #full-trace, [id*="Full"][id*="Trace"]');
@@ -260,13 +507,11 @@ module CopyForAi
260
507
  if (pre) traces.full = pre.textContent.trim();
261
508
  }
262
509
 
263
- // Fallback: look for any trace-related content
264
510
  if (!traces.application && !traces.framework && !traces.full) {
265
511
  var allPres = document.querySelectorAll('pre');
266
512
  for (var i = 0; i < allPres.length; i++) {
267
513
  var pre = allPres[i];
268
514
  var text = pre.textContent;
269
- // Check if it looks like a stack trace
270
515
  if (text.includes(':in `') || text.includes('.rb:')) {
271
516
  if (!traces.full) {
272
517
  traces.full = text.trim();
@@ -279,7 +524,6 @@ module CopyForAi
279
524
  }
280
525
 
281
526
  function extractRoutes() {
282
- // For routing errors, extract available routes
283
527
  var routesTable = document.querySelector('table');
284
528
  if (routesTable) {
285
529
  var rows = routesTable.querySelectorAll('tr');
@@ -295,7 +539,7 @@ module CopyForAi
295
539
  );
296
540
  }
297
541
  });
298
- if (routeLines.length > 1) { // More than just header
542
+ if (routeLines.length > 1) {
299
543
  return routeLines.slice(0, 20).join('\\n') + (rows.length > 20 ? '\\n... and more' : '');
300
544
  }
301
545
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CopyForAi
4
- VERSION = "0.1.4"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: copy_for_ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Miller