ai_error_clip 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: 95724784f3d9f42e22d4b99a3d8f9859c7ed2c0f63f6cf944d5e59b84a7f9c1a
4
+ data.tar.gz: cb608730af7c7deaaf719e4d252da25f298826f51b25f14ea374662a815e8eef
5
+ SHA512:
6
+ metadata.gz: 6714d9ff1ea31fcd00ace552c8140ce54408554da504b958e8f369412727b86ba65304bebde1212bd14abd20f0268475234458482c39d110921fa0f8298fb3d6
7
+ data.tar.gz: f617f5416c954f6e1a8225d7ce8e94055e309bce68f0647690404def5a522812c11894d857572ea7e11aa408565d788afc081d18d427508427d4e2d188048f69
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 utarage
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # ai_error_clip
2
+
3
+ `ai_error_clip` is a small Rails plugin that replaces the default development
4
+ error page with an AI-friendly layout. The page gathers the request, parameters,
5
+ context, and the first ten lines of the backtrace into one screen with a single
6
+ button that copies the information for pasting into an AI assistant.
7
+
8
+ ## Installation
9
+
10
+ Add the gem to your Rails application's `Gemfile`. When developing locally you
11
+ can use a `path:` reference:
12
+
13
+ ```ruby
14
+ group :development do
15
+ gem "ai_error_clip", path: "vendor/ai_error_clip"
16
+ end
17
+ ```
18
+
19
+ Then install the dependencies inside your application (for example via Bundler):
20
+
21
+ ```sh
22
+ bundle install
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Once the gem is loaded, it automatically attaches to `ActionController::Base`
28
+ in development mode. You do not need to change controller code manually.
29
+
30
+ When an exception occurs, Rails renders the gem's `Error Details` page instead
31
+ of the default stack trace. Use the **Copy details for AI** button to copy
32
+ everything required for a support conversation.
33
+
34
+ ## Development
35
+
36
+ To modify the gem locally:
37
+
38
+ 1. Adjust files under `lib/ai_error_clip` or `app/views/ai_error_clip`.
39
+ 2. Run tests or manual checks in your Rails application.
40
+ 3. Build the gem for distribution with:
41
+
42
+ ```sh
43
+ gem build ai_error_clip.gemspec
44
+ ```
45
+
46
+ ## License
47
+
48
+ This project is licensed under the MIT License. See [LICENSE](LICENSE) for
49
+ details.
@@ -0,0 +1,160 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Error</title>
5
+ <style>
6
+ body {
7
+ font-family: monospace;
8
+ background: #f5f5f5;
9
+ padding: 12px;
10
+ margin: 0;
11
+ }
12
+ .error-container {
13
+ background: white;
14
+ border: 1px solid #ddd;
15
+ padding: 16px;
16
+ max-width: 960px;
17
+ margin: 0 auto;
18
+ }
19
+ .error-header {
20
+ display: flex;
21
+ justify-content: space-between;
22
+ align-items: flex-start;
23
+ gap: 12px;
24
+ margin-bottom: 12px;
25
+ }
26
+ .error-actions {
27
+ display: flex;
28
+ flex-direction: column;
29
+ align-items: flex-end;
30
+ gap: 6px;
31
+ flex-shrink: 0;
32
+ }
33
+ .copy-status {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 12px;
37
+ }
38
+ h1 {
39
+ color: #d00;
40
+ font-size: 24px;
41
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
42
+ text-transform: uppercase;
43
+ margin: 0 0 12px 0;
44
+ }
45
+ .error-grid {
46
+ display: grid;
47
+ grid-template-columns: 150px 1fr;
48
+ gap: 8px 12px;
49
+ }
50
+ .error-label {
51
+ font-weight: bold;
52
+ color: #666;
53
+ font-size: 12px;
54
+ padding-top: 2px;
55
+ }
56
+ .error-content {
57
+ background: #f9f9f9;
58
+ border: 1px solid #e0e0e0;
59
+ padding: 8px;
60
+ white-space: pre-wrap;
61
+ word-wrap: break-word;
62
+ font-size: 13px;
63
+ overflow: hidden;
64
+ }
65
+ .error-content--scroll {
66
+ max-height: 200px;
67
+ overflow: auto;
68
+ }
69
+ .copy-button {
70
+ background: #0066cc;
71
+ color: white;
72
+ border: none;
73
+ padding: 8px 16px;
74
+ font-size: 13px;
75
+ cursor: pointer;
76
+ border-radius: 4px;
77
+ white-space: nowrap;
78
+ }
79
+ .copy-button:hover {
80
+ background: #0052a3;
81
+ }
82
+ .copy-button:active {
83
+ background: #004080;
84
+ }
85
+ .copied-message {
86
+ display: none;
87
+ color: #080;
88
+ font-size: 12px;
89
+ }
90
+ .copy-hint {
91
+ color: #666;
92
+ font-size: 11px;
93
+ margin-top: 6px;
94
+ }
95
+ </style>
96
+ <script>
97
+ function copyErrorContent() {
98
+ const errorPath = document.getElementById('error-path').innerText;
99
+ const errorClass = document.getElementById('error-class').innerText;
100
+ const errorMessage = document.getElementById('error-message').innerText;
101
+ const errorBacktrace = document.getElementById('error-backtrace').innerText;
102
+ const errorBodyElement = document.getElementById('error-body');
103
+ const errorBody = errorBodyElement ? errorBodyElement.innerText : null;
104
+ const errorContextElement = document.getElementById('error-context');
105
+ const errorContext = errorContextElement ? errorContextElement.innerText : null;
106
+ const contextSection = errorContext ? `\n\nContext:\n${errorContext}` : '';
107
+ const bodySection = errorBody ? `\n\nPayload / Parameters:\n${errorBody}` : '';
108
+ const fullContent = `Request Path:\n${errorPath}\n\nError Class:\n${errorClass}\n\nError Message:\n${errorMessage}${contextSection}${bodySection}\n\nBacktrace:\n${errorBacktrace}`;
109
+
110
+ navigator.clipboard.writeText(fullContent).then(function() {
111
+ const message = document.getElementById('copied-message');
112
+ message.style.display = 'inline';
113
+ setTimeout(function() {
114
+ message.style.display = 'none';
115
+ }, 2000);
116
+ });
117
+ }
118
+ </script>
119
+ </head>
120
+ <body>
121
+ <div class="error-container">
122
+ <div class="error-header">
123
+ <h1>Error Details</h1>
124
+ <div class="error-actions">
125
+ <div class="copy-status">
126
+ <span class="copied-message" id="copied-message">✓ Copied</span>
127
+ <button class="copy-button" onclick="copyErrorContent()">Copy details for AI</button>
128
+ </div>
129
+ </div>
130
+ </div>
131
+
132
+ <div class="error-grid">
133
+ <div class="error-label">Request Path</div>
134
+ <div class="error-content" id="error-path"><%= @ai_error_clip_error[:method] %> <%= @ai_error_clip_error[:path] %></div>
135
+
136
+ <div class="error-label">Error Class</div>
137
+ <div class="error-content" id="error-class"><%= @ai_error_clip_error[:class] %></div>
138
+
139
+ <div class="error-label">Error Message</div>
140
+ <div class="error-content" id="error-message"><%= @ai_error_clip_error[:message] %></div>
141
+
142
+ <% if @ai_error_clip_error[:context].present? %>
143
+ <div class="error-label">Context</div>
144
+ <div class="error-content error-content--scroll" id="error-context"><% @ai_error_clip_error[:context].each do |key, value| %><%= "#{key}: #{value}" %>
145
+ <% end %></div>
146
+ <% end %>
147
+
148
+ <% if @ai_error_clip_error[:body].present? %>
149
+ <div class="error-label">Payload / Parameters</div>
150
+ <div class="error-content error-content--scroll" id="error-body"><%= h @ai_error_clip_error[:body] %></div>
151
+ <% end %>
152
+
153
+ <div class="error-label">Backtrace</div>
154
+ <div class="error-content error-content--scroll" id="error-backtrace"><% @ai_error_clip_error[:backtrace].each do |line| %><%= line %>
155
+ <% end %></div>
156
+ </div>
157
+
158
+ </div>
159
+ </body>
160
+ </html>
@@ -0,0 +1,101 @@
1
+ require "active_support/concern"
2
+ require "json"
3
+
4
+ module AiErrorClip
5
+ module ControllerExtension
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ if Rails.env.development?
10
+ rescue_from StandardError, with: :ai_error_clip_handle_standard_error
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ # シンプルエラー画面を表示するか通常のエラーに任せるか判断する
17
+ def ai_error_clip_handle_standard_error(exception)
18
+ if Rails.env.development?
19
+ ai_error_clip_render_simple_error(exception)
20
+ else
21
+ raise exception
22
+ end
23
+ end
24
+
25
+ # エラー内容を整形してAI向けのテンプレートを描画する
26
+ def ai_error_clip_render_simple_error(exception)
27
+ # GETクエリも含めた送信データをまとめる
28
+ request_payload = ai_error_clip_formatted_request_body
29
+
30
+ @ai_error_clip_error = {
31
+ class: exception.class.name,
32
+ message: exception.message,
33
+ backtrace: Array(exception.backtrace).first(10),
34
+ path: request.path,
35
+ method: request.method,
36
+ body: request_payload,
37
+ context: ai_error_clip_request_context
38
+ }
39
+
40
+ render template: "ai_error_clip/simple_error", status: :internal_server_error, layout: false
41
+ end
42
+
43
+ # リクエストに関する追加情報をまとめる
44
+ def ai_error_clip_request_context
45
+ {
46
+ environment: Rails.env,
47
+ rails_version: Rails.version,
48
+ ruby_version: RUBY_VERSION,
49
+ request_id: request.request_id,
50
+ controller: controller_name,
51
+ action: action_name,
52
+ referer: request.referer,
53
+ user_agent: request.user_agent
54
+ }.compact
55
+ end
56
+
57
+ # リクエストパラメーターをJSON風に整形する(GETも含む)
58
+ def ai_error_clip_formatted_request_body
59
+ raw_params = ai_error_clip_capture_raw_params
60
+ return request.raw_post.presence unless raw_params.present?
61
+
62
+ begin
63
+ JSON.pretty_generate(raw_params)
64
+ rescue StandardError
65
+ raw_params.inspect
66
+ end
67
+ end
68
+
69
+ # Strong Parametersを安全なハッシュに変換する
70
+ def ai_error_clip_capture_raw_params
71
+ params_hash = params.to_unsafe_h.deep_dup
72
+ %w[controller action utf8 authenticity_token commit].each do |key|
73
+ params_hash.delete(key)
74
+ params_hash.delete(key.to_sym)
75
+ end
76
+ ai_error_clip_sanitize_param_values(params_hash)
77
+ rescue StandardError
78
+ request.filtered_parameters.except(:controller, :action, "controller", "action")
79
+ end
80
+
81
+ # ファイルなどの値を文字列情報にそろえる
82
+ def ai_error_clip_sanitize_param_values(value)
83
+ case value
84
+ when ActionDispatch::Http::UploadedFile
85
+ {
86
+ name: value.original_filename,
87
+ type: value.content_type,
88
+ size: value.size
89
+ }
90
+ when Hash
91
+ value.transform_values { |v| ai_error_clip_sanitize_param_values(v) }
92
+ when ActionController::Parameters
93
+ ai_error_clip_sanitize_param_values(value.to_unsafe_h)
94
+ when Array
95
+ value.map { |v| ai_error_clip_sanitize_param_values(v) }
96
+ else
97
+ value
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,16 @@
1
+ require "rails/railtie"
2
+ require_relative "controller_extension"
3
+
4
+ module AiErrorClip
5
+ class Railtie < ::Rails::Railtie
6
+ initializer "ai_error_clip.configure_controller" do
7
+ ActiveSupport.on_load(:action_controller_base) do
8
+ # Railsコントローラーでgemの機能を使えるようにする
9
+ include AiErrorClip::ControllerExtension
10
+
11
+ # gem内のビューを描画対象に追加する
12
+ append_view_path File.expand_path("../../app/views", __dir__)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module AiErrorClip
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ require "ai_error_clip/version"
2
+
3
+ require "ai_error_clip/railtie" if defined?(Rails)
4
+
5
+ module AiErrorClip
6
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ai_error_clip
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - utarage
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: ai_error_clip replaces the Rails development exception page with an AI-focused
13
+ layout that gathers request details, parameters, context, and a short backtrace
14
+ in one place.
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE
20
+ - README.md
21
+ - app/views/ai_error_clip/simple_error.html.erb
22
+ - lib/ai_error_clip.rb
23
+ - lib/ai_error_clip/controller_extension.rb
24
+ - lib/ai_error_clip/railtie.rb
25
+ - lib/ai_error_clip/version.rb
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubygems_version: 3.6.7
44
+ specification_version: 4
45
+ summary: AI-friendly Rails development error page with one-click copy
46
+ test_files: []