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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +62 -43
- data/lib/copy_for_ai/content_injector.rb +306 -62
- data/lib/copy_for_ai/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 609491e473023a0d9d124026197d92c931c6b69c53d9ee66eecdb1364bd42157
|
|
4
|
+
data.tar.gz: 344352c8e5cfd6c80859ddacf088e173793067665f7f40bddb135718c20675b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
-
|
|
14
|
-
-
|
|
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
|
|
40
|
-
3.
|
|
41
|
-
4.
|
|
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
|
-
|
|
45
|
+
## Output Formats
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
### TOON (Default)
|
|
46
48
|
|
|
47
|
-
|
|
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
|
-
|
|
51
|
+
```
|
|
52
|
+
error_type: Routing Error
|
|
53
|
+
error_message: "No route matches [GET] \\"/asdf\\""
|
|
54
|
+
rails_root: /Users/dev/myapp
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
application_trace:
|
|
57
|
+
app/controllers/users_controller.rb:15:in `show'
|
|
53
58
|
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
**Line:** 15
|
|
66
|
+
Rich formatting with headers and code blocks:
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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: #
|
|
31
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
163
|
+
h2.parentElement.insertBefore(container, h2.nextSibling);
|
|
101
164
|
inserted = true;
|
|
102
165
|
}
|
|
103
166
|
}
|
|
104
167
|
|
|
105
|
-
// Strategy 4: Floating
|
|
168
|
+
// Strategy 4: Floating container as fallback
|
|
106
169
|
if (!inserted) {
|
|
107
|
-
|
|
108
|
-
document.body.appendChild(
|
|
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
|
|
114
|
-
|
|
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
|
|
119
|
-
var
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
147
|
-
var sourceInfo = extractSourceCode();
|
|
148
|
-
if (sourceInfo) {
|
|
336
|
+
if (data.source) {
|
|
149
337
|
lines.push('## Source Code');
|
|
150
338
|
lines.push('');
|
|
151
|
-
if (
|
|
152
|
-
lines.push('**File:** `' +
|
|
339
|
+
if (data.source.file) {
|
|
340
|
+
lines.push('**File:** `' + data.source.file + '`');
|
|
153
341
|
}
|
|
154
|
-
if (
|
|
155
|
-
lines.push('**Line:** ' +
|
|
342
|
+
if (data.source.line) {
|
|
343
|
+
lines.push('**Line:** ' + data.source.line);
|
|
156
344
|
}
|
|
157
345
|
lines.push('');
|
|
158
|
-
if (
|
|
346
|
+
if (data.source.code) {
|
|
159
347
|
lines.push('```ruby');
|
|
160
|
-
lines.push(
|
|
348
|
+
lines.push(data.source.code);
|
|
161
349
|
lines.push('```');
|
|
162
350
|
lines.push('');
|
|
163
351
|
}
|
|
164
352
|
}
|
|
165
353
|
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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) {
|
|
542
|
+
if (routeLines.length > 1) {
|
|
299
543
|
return routeLines.slice(0, 20).join('\\n') + (rows.length > 20 ? '\\n... and more' : '');
|
|
300
544
|
}
|
|
301
545
|
}
|
data/lib/copy_for_ai/version.rb
CHANGED