context_drop 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 +7 -0
- data/MIT-LICENSE +21 -0
- data/README.md +337 -0
- data/Rakefile +20 -0
- data/SPEC.md +532 -0
- data/app/assets/javascripts/context_drop/context_drop.js +3508 -0
- data/app/assets/stylesheets/context_drop/application.css +2 -0
- data/app/controllers/context_drop/application_controller.rb +114 -0
- data/app/controllers/context_drop/attachments_controller.rb +31 -0
- data/app/controllers/context_drop/configs_controller.rb +20 -0
- data/app/controllers/context_drop/dashboard/feedbacks_controller.rb +107 -0
- data/app/controllers/context_drop/feedbacks_controller.rb +208 -0
- data/app/controllers/context_drop/health_controller.rb +9 -0
- data/app/controllers/context_drop/mcp_controller.rb +67 -0
- data/app/helpers/context_drop/widget_helper.rb +62 -0
- data/app/javascript/context_drop/__tests__/widget.test.ts +319 -0
- data/app/javascript/context_drop/api-client.ts +87 -0
- data/app/javascript/context_drop/element-picker.ts +1004 -0
- data/app/javascript/context_drop/icons.ts +35 -0
- data/app/javascript/context_drop/index.ts +11 -0
- data/app/javascript/context_drop/sanitizer.ts +454 -0
- data/app/javascript/context_drop/screenshot.ts +214 -0
- data/app/javascript/context_drop/styles.ts +690 -0
- data/app/javascript/context_drop/types.ts +110 -0
- data/app/javascript/context_drop/widget.ts +1120 -0
- data/app/models/context_drop/application_record.rb +7 -0
- data/app/models/context_drop/attachment.rb +30 -0
- data/app/models/context_drop/element_reference.rb +47 -0
- data/app/models/context_drop/feedback.rb +94 -0
- data/app/services/context_drop/metadata_sanitizer.rb +215 -0
- data/app/services/context_drop/upload_validator.rb +217 -0
- data/app/views/context_drop/dashboard/feedbacks/index.html.erb +86 -0
- data/app/views/context_drop/dashboard/feedbacks/show.html.erb +121 -0
- data/app/views/layouts/context_drop/application.html.erb +215 -0
- data/config/routes.rb +17 -0
- data/db/migrate/20261109000001_create_context_drop_feedbacks.rb +23 -0
- data/db/migrate/20261109000002_create_context_drop_element_references.rb +29 -0
- data/db/migrate/20261109000003_create_context_drop_attachments.rb +24 -0
- data/lib/context_drop/base64_util.rb +15 -0
- data/lib/context_drop/configuration.rb +155 -0
- data/lib/context_drop/engine.rb +32 -0
- data/lib/context_drop/mcp/dispatcher.rb +197 -0
- data/lib/context_drop/mcp/prompts/investigate_feedback.rb +83 -0
- data/lib/context_drop/mcp/server.rb +25 -0
- data/lib/context_drop/mcp/stdio_runner.rb +62 -0
- data/lib/context_drop/mcp/tools/get_feedback.rb +133 -0
- data/lib/context_drop/mcp/tools/get_feedback_attachment.rb +71 -0
- data/lib/context_drop/mcp/tools/list_feedbacks.rb +127 -0
- data/lib/context_drop/mcp/tools/update_feedback_status.rb +72 -0
- data/lib/context_drop/version.rb +5 -0
- data/lib/context_drop.rb +32 -0
- data/lib/generators/context_drop/install/install_generator.rb +52 -0
- data/lib/generators/context_drop/install/templates/initializer.rb.tt +55 -0
- data/lib/tasks/context_drop_tasks.rake +14 -0
- metadata +246 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ContextDrop
|
|
4
|
+
module MCP
|
|
5
|
+
module Tools
|
|
6
|
+
class ListFeedbacks
|
|
7
|
+
MAX_LIMIT = 100
|
|
8
|
+
DEFAULT_LIMIT = 20
|
|
9
|
+
|
|
10
|
+
DEFINITION = {
|
|
11
|
+
name: "list_feedbacks",
|
|
12
|
+
description: "List and search ContextDrop feedback submissions filtered by status, page_url, or free-text query.",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
status: {
|
|
17
|
+
type: "string",
|
|
18
|
+
enum: %w[new reviewed resolved dismissed],
|
|
19
|
+
description: "Filter feedback by workflow status."
|
|
20
|
+
},
|
|
21
|
+
page_url: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Filter feedback by originating page URL substring (e.g. '/checkout')."
|
|
24
|
+
},
|
|
25
|
+
query: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Free-text search matching feedback message or attached DOM element selectors, paths, or text."
|
|
28
|
+
},
|
|
29
|
+
limit: {
|
|
30
|
+
type: "integer",
|
|
31
|
+
description: "Maximum records to return (default 20, max 100)."
|
|
32
|
+
},
|
|
33
|
+
offset: {
|
|
34
|
+
type: "integer",
|
|
35
|
+
description: "Pagination offset (default 0)."
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
def self.call(args = {})
|
|
42
|
+
params = stringify_keys(args)
|
|
43
|
+
scope = ContextDrop::Feedback.includes(:submitter, :element_references, :attachments).recent
|
|
44
|
+
|
|
45
|
+
status = params["status"].to_s.strip
|
|
46
|
+
if status.present?
|
|
47
|
+
unless ContextDrop::Feedback::STATUSES.include?(status)
|
|
48
|
+
return error_result("Invalid status '#{status}'. Must be one of: #{ContextDrop::Feedback::STATUSES.join(', ')}")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
scope = scope.where(status: status)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
page_url = params["page_url"].to_s.strip
|
|
55
|
+
if page_url.present?
|
|
56
|
+
scope = scope.where("context_drop_feedbacks.page_url LIKE ?", "%#{escape_like(page_url)}%")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
query = params["query"].to_s.strip
|
|
60
|
+
if query.present?
|
|
61
|
+
pattern = "%#{escape_like(query)}%"
|
|
62
|
+
scope = scope.left_outer_joins(:element_references).where(
|
|
63
|
+
"context_drop_feedbacks.message LIKE :q OR " \
|
|
64
|
+
"context_drop_element_references.selector LIKE :q OR " \
|
|
65
|
+
"context_drop_element_references.dom_path LIKE :q OR " \
|
|
66
|
+
"context_drop_element_references.text_content LIKE :q",
|
|
67
|
+
q: pattern
|
|
68
|
+
).distinct
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
raw_limit = params["limit"] ? params["limit"].to_i : DEFAULT_LIMIT
|
|
72
|
+
limit = raw_limit.clamp(1, MAX_LIMIT)
|
|
73
|
+
offset = [params["offset"].to_i, 0].max
|
|
74
|
+
|
|
75
|
+
total_count = scope.count
|
|
76
|
+
records = scope.limit(limit).offset(offset).map { |fb| serialize_summary(fb) }
|
|
77
|
+
|
|
78
|
+
{
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: JSON.pretty_generate(
|
|
83
|
+
total: total_count,
|
|
84
|
+
limit: limit,
|
|
85
|
+
offset: offset,
|
|
86
|
+
feedbacks: records
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
isError: false
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.serialize_summary(feedback)
|
|
95
|
+
{
|
|
96
|
+
id: feedback.id,
|
|
97
|
+
status: feedback.status,
|
|
98
|
+
message: feedback.message,
|
|
99
|
+
page_url: feedback.page_url,
|
|
100
|
+
submitter: feedback.submitter_label,
|
|
101
|
+
attachments_count: feedback.attachments.size,
|
|
102
|
+
element_references_count: feedback.element_references.size,
|
|
103
|
+
element_selectors: feedback.element_references.map(&:selector),
|
|
104
|
+
created_at: feedback.created_at&.iso8601
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.error_result(msg)
|
|
109
|
+
{
|
|
110
|
+
content: [{ type: "text", text: JSON.generate(error: msg) }],
|
|
111
|
+
isError: true
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.escape_like(str)
|
|
116
|
+
ActiveRecord::Base.sanitize_sql_like(str.to_s)
|
|
117
|
+
rescue StandardError
|
|
118
|
+
str.to_s.gsub(/[%_\\]/) { |m| "\\#{m}" }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self.stringify_keys(hash)
|
|
122
|
+
hash.is_a?(Hash) ? hash.transform_keys(&:to_s) : {}
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ContextDrop
|
|
4
|
+
module MCP
|
|
5
|
+
module Tools
|
|
6
|
+
class UpdateFeedbackStatus
|
|
7
|
+
DEFINITION = {
|
|
8
|
+
name: "update_feedback_status",
|
|
9
|
+
description: "Transition a ContextDrop feedback record's status ('new', 'reviewed', 'resolved', 'dismissed').",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
required: %w[id status],
|
|
13
|
+
properties: {
|
|
14
|
+
id: {
|
|
15
|
+
type: %w[integer string],
|
|
16
|
+
description: "The ID of the ContextDrop::Feedback record."
|
|
17
|
+
},
|
|
18
|
+
status: {
|
|
19
|
+
type: "string",
|
|
20
|
+
enum: %w[new reviewed resolved dismissed],
|
|
21
|
+
description: "Target workflow status."
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
def self.call(args = {})
|
|
28
|
+
params = args.is_a?(Hash) ? args.transform_keys(&:to_s) : {}
|
|
29
|
+
feedback_id = params["id"] || params["feedback_id"]
|
|
30
|
+
next_status = params["status"].to_s.strip
|
|
31
|
+
|
|
32
|
+
return error_result("Missing required parameter: id") if feedback_id.nil? || feedback_id.to_s.strip.empty?
|
|
33
|
+
|
|
34
|
+
unless ContextDrop::Feedback::STATUSES.include?(next_status)
|
|
35
|
+
return error_result(
|
|
36
|
+
"Invalid status '#{next_status}'. Must be one of: #{ContextDrop::Feedback::STATUSES.join(', ')}"
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
feedback = ContextDrop::Feedback.find_by(id: feedback_id)
|
|
41
|
+
return error_result("Feedback ##{feedback_id} not found") unless feedback
|
|
42
|
+
|
|
43
|
+
previous_status = feedback.status
|
|
44
|
+
feedback.update!(status: next_status)
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: JSON.pretty_generate(
|
|
51
|
+
id: feedback.id,
|
|
52
|
+
previous_status: previous_status,
|
|
53
|
+
status: feedback.status,
|
|
54
|
+
page_url: feedback.page_url,
|
|
55
|
+
updated_at: feedback.updated_at&.iso8601
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
isError: false
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.error_result(msg)
|
|
64
|
+
{
|
|
65
|
+
content: [{ type: "text", text: JSON.generate(error: msg) }],
|
|
66
|
+
isError: true
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/context_drop.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "context_drop/version"
|
|
4
|
+
require_relative "context_drop/base64_util"
|
|
5
|
+
require_relative "context_drop/configuration"
|
|
6
|
+
require_relative "context_drop/mcp/server"
|
|
7
|
+
require_relative "context_drop/engine" if defined?(Rails::Engine)
|
|
8
|
+
|
|
9
|
+
module ContextDrop
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def config
|
|
14
|
+
@config ||= Configuration.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
alias configuration config
|
|
18
|
+
|
|
19
|
+
def configure
|
|
20
|
+
yield(config) if block_given?
|
|
21
|
+
config
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reset_configuration!
|
|
25
|
+
@config = Configuration.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def enabled?
|
|
29
|
+
!!config.enabled
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module ContextDrop
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Installs ContextDrop initializer, mounts Engine routes, copies migrations, and checks Active Storage."
|
|
11
|
+
|
|
12
|
+
def create_initializer_file
|
|
13
|
+
template "initializer.rb.tt", "config/initializers/context_drop.rb"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def mount_engine_routes
|
|
17
|
+
routes_path = File.join(destination_root, "config/routes.rb")
|
|
18
|
+
return unless File.exist?(routes_path)
|
|
19
|
+
|
|
20
|
+
content = File.read(routes_path)
|
|
21
|
+
return if content.include?("ContextDrop::Engine")
|
|
22
|
+
|
|
23
|
+
route 'mount ContextDrop::Engine => "/context_drop"'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def copy_engine_migrations
|
|
27
|
+
rake "context_drop:install:migrations"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def verify_active_storage
|
|
31
|
+
migrations_dir = File.join(destination_root, "db/migrate")
|
|
32
|
+
has_active_storage = Dir.exist?(migrations_dir) &&
|
|
33
|
+
Dir.glob(File.join(migrations_dir, "*create_active_storage_tables*.rb")).any?
|
|
34
|
+
|
|
35
|
+
return if has_active_storage
|
|
36
|
+
|
|
37
|
+
say_status(
|
|
38
|
+
:info,
|
|
39
|
+
"Active Storage migrations not detected. Run `bin/rails active_storage:install` if Active Storage is not yet installed.",
|
|
40
|
+
:yellow
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def print_post_install_instructions
|
|
45
|
+
say "\nContextDrop installed! Next steps:", :green
|
|
46
|
+
say " 1. Run `bin/rails db:migrate` to create context_drop_* tables."
|
|
47
|
+
say " 2. Set `CONTEXT_DROP_ENABLED=true` in your environment."
|
|
48
|
+
say " 3. Add `<%= context_drop_widget %>` before `</body>` in `app/views/layouts/application.html.erb`."
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
ContextDrop.configure do |config|
|
|
4
|
+
# Environment-based master switch (disables widget, dashboard, and ingestion endpoints when false)
|
|
5
|
+
config.enabled = ENV.fetch("CONTEXT_DROP_ENABLED", "false") == "true"
|
|
6
|
+
|
|
7
|
+
# Widget position: :bottom_right, :bottom_left, :top_right, :top_left
|
|
8
|
+
config.position = :bottom_right
|
|
9
|
+
|
|
10
|
+
# Attachment storage backend
|
|
11
|
+
config.storage = :active_storage
|
|
12
|
+
config.active_storage_service = nil # nil uses host app's default Active Storage service
|
|
13
|
+
|
|
14
|
+
# Submission permissions
|
|
15
|
+
config.allow_anonymous = true
|
|
16
|
+
|
|
17
|
+
# Attachment & element limits
|
|
18
|
+
config.max_attachments = 10
|
|
19
|
+
config.max_element_references = 10
|
|
20
|
+
config.max_attachment_size = 10.megabytes
|
|
21
|
+
config.allowed_content_types = %w[image/png image/jpeg image/webp image/gif]
|
|
22
|
+
config.max_image_dimensions = [4096, 4096]
|
|
23
|
+
|
|
24
|
+
# Widget feature flags
|
|
25
|
+
config.element_selection = true
|
|
26
|
+
config.screenshot_capture = true
|
|
27
|
+
config.element_capture_padding = 16
|
|
28
|
+
|
|
29
|
+
# DOM selectors excluded from selection and redacted in screenshots
|
|
30
|
+
config.excluded_selectors = [
|
|
31
|
+
"[data-feedback-ignore]",
|
|
32
|
+
"[data-feedback-sensitive]",
|
|
33
|
+
"input[type='password']",
|
|
34
|
+
"input[type='hidden']",
|
|
35
|
+
"[autocomplete^='cc-']"
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# Host authentication integration (returns any ActiveRecord model for polymorphic `submitter`, or nil)
|
|
39
|
+
config.current_submitter = ->(controller) { controller.try(:current_user) }
|
|
40
|
+
|
|
41
|
+
# Authorization policies
|
|
42
|
+
config.authorize_submission = ->(_controller) { true }
|
|
43
|
+
config.authorize_dashboard = ->(controller) { controller.try(:current_user)&.try(:admin?) || Rails.env.development? }
|
|
44
|
+
|
|
45
|
+
# Rate limiting on submission/upload endpoints
|
|
46
|
+
config.rate_limit = { max_requests: 20, period: 1.hour }
|
|
47
|
+
|
|
48
|
+
# Optional lifecycle callback invoked after feedback creation
|
|
49
|
+
config.on_feedback_created = ->(feedback) {}
|
|
50
|
+
|
|
51
|
+
# Model Context Protocol (MCP) server settings (Phase 2)
|
|
52
|
+
config.mcp_enabled = ENV.fetch("CONTEXT_DROP_MCP_ENABLED", "false") == "true"
|
|
53
|
+
config.mcp_api_key = ENV["CONTEXT_DROP_MCP_API_KEY"]
|
|
54
|
+
config.authorize_mcp = nil
|
|
55
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :context_drop do
|
|
4
|
+
desc "Start the ContextDrop Model Context Protocol (MCP) server over stdio"
|
|
5
|
+
task :mcp do
|
|
6
|
+
# Reserve original STDOUT strictly for MCP JSON-RPC frames while loading the Rails environment
|
|
7
|
+
mcp_stdout = $stdout.dup
|
|
8
|
+
$stdout.reopen($stderr)
|
|
9
|
+
|
|
10
|
+
Rake::Task["environment"].invoke if Rake::Task.task_defined?("environment")
|
|
11
|
+
|
|
12
|
+
ContextDrop::MCP::Server.run_stdio(input: $stdin, output: mcp_stdout, error: $stderr)
|
|
13
|
+
end
|
|
14
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: context_drop
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kemal Elmizan
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-11-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: actionpack
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7.1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activerecord
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '7.1'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '7.1'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: activestorage
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '7.1'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '7.1'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: activesupport
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '7.1'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '7.1'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: marcel
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.0'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: railties
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '7.1'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '7.1'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rake
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '13.0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '13.0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rspec-rails
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '6.0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '6.0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rubocop
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '1.50'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '1.50'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: sqlite3
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '1.6'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '1.6'
|
|
153
|
+
description: |
|
|
154
|
+
ContextDrop is a self-hosted contextual feedback library for Ruby on Rails applications.
|
|
155
|
+
It provides a mountable Rails Engine with engine-owned persistence, a decoupled TypeScript
|
|
156
|
+
Shadow DOM prompt-style feedback composer with interactive DOM element selection and
|
|
157
|
+
privacy-redacted screenshots, a self-hosted triage dashboard, and a built-in Model Context
|
|
158
|
+
Protocol (MCP) server for IDEs and coding agents.
|
|
159
|
+
email:
|
|
160
|
+
- kemal@elmizan.com
|
|
161
|
+
executables: []
|
|
162
|
+
extensions: []
|
|
163
|
+
extra_rdoc_files: []
|
|
164
|
+
files:
|
|
165
|
+
- MIT-LICENSE
|
|
166
|
+
- README.md
|
|
167
|
+
- Rakefile
|
|
168
|
+
- SPEC.md
|
|
169
|
+
- app/assets/javascripts/context_drop/context_drop.js
|
|
170
|
+
- app/assets/stylesheets/context_drop/application.css
|
|
171
|
+
- app/controllers/context_drop/application_controller.rb
|
|
172
|
+
- app/controllers/context_drop/attachments_controller.rb
|
|
173
|
+
- app/controllers/context_drop/configs_controller.rb
|
|
174
|
+
- app/controllers/context_drop/dashboard/feedbacks_controller.rb
|
|
175
|
+
- app/controllers/context_drop/feedbacks_controller.rb
|
|
176
|
+
- app/controllers/context_drop/health_controller.rb
|
|
177
|
+
- app/controllers/context_drop/mcp_controller.rb
|
|
178
|
+
- app/helpers/context_drop/widget_helper.rb
|
|
179
|
+
- app/javascript/context_drop/__tests__/widget.test.ts
|
|
180
|
+
- app/javascript/context_drop/api-client.ts
|
|
181
|
+
- app/javascript/context_drop/element-picker.ts
|
|
182
|
+
- app/javascript/context_drop/icons.ts
|
|
183
|
+
- app/javascript/context_drop/index.ts
|
|
184
|
+
- app/javascript/context_drop/sanitizer.ts
|
|
185
|
+
- app/javascript/context_drop/screenshot.ts
|
|
186
|
+
- app/javascript/context_drop/styles.ts
|
|
187
|
+
- app/javascript/context_drop/types.ts
|
|
188
|
+
- app/javascript/context_drop/widget.ts
|
|
189
|
+
- app/models/context_drop/application_record.rb
|
|
190
|
+
- app/models/context_drop/attachment.rb
|
|
191
|
+
- app/models/context_drop/element_reference.rb
|
|
192
|
+
- app/models/context_drop/feedback.rb
|
|
193
|
+
- app/services/context_drop/metadata_sanitizer.rb
|
|
194
|
+
- app/services/context_drop/upload_validator.rb
|
|
195
|
+
- app/views/context_drop/dashboard/feedbacks/index.html.erb
|
|
196
|
+
- app/views/context_drop/dashboard/feedbacks/show.html.erb
|
|
197
|
+
- app/views/layouts/context_drop/application.html.erb
|
|
198
|
+
- config/routes.rb
|
|
199
|
+
- db/migrate/20261109000001_create_context_drop_feedbacks.rb
|
|
200
|
+
- db/migrate/20261109000002_create_context_drop_element_references.rb
|
|
201
|
+
- db/migrate/20261109000003_create_context_drop_attachments.rb
|
|
202
|
+
- lib/context_drop.rb
|
|
203
|
+
- lib/context_drop/base64_util.rb
|
|
204
|
+
- lib/context_drop/configuration.rb
|
|
205
|
+
- lib/context_drop/engine.rb
|
|
206
|
+
- lib/context_drop/mcp/dispatcher.rb
|
|
207
|
+
- lib/context_drop/mcp/prompts/investigate_feedback.rb
|
|
208
|
+
- lib/context_drop/mcp/server.rb
|
|
209
|
+
- lib/context_drop/mcp/stdio_runner.rb
|
|
210
|
+
- lib/context_drop/mcp/tools/get_feedback.rb
|
|
211
|
+
- lib/context_drop/mcp/tools/get_feedback_attachment.rb
|
|
212
|
+
- lib/context_drop/mcp/tools/list_feedbacks.rb
|
|
213
|
+
- lib/context_drop/mcp/tools/update_feedback_status.rb
|
|
214
|
+
- lib/context_drop/version.rb
|
|
215
|
+
- lib/generators/context_drop/install/install_generator.rb
|
|
216
|
+
- lib/generators/context_drop/install/templates/initializer.rb.tt
|
|
217
|
+
- lib/tasks/context_drop_tasks.rake
|
|
218
|
+
homepage: https://github.com/kemalelmizan/context-drop
|
|
219
|
+
licenses:
|
|
220
|
+
- MIT
|
|
221
|
+
metadata:
|
|
222
|
+
homepage_uri: https://github.com/kemalelmizan/context-drop
|
|
223
|
+
source_code_uri: https://github.com/kemalelmizan/context-drop
|
|
224
|
+
changelog_uri: https://github.com/kemalelmizan/context-drop/blob/main/CHANGELOG.md
|
|
225
|
+
rubygems_mfa_required: 'true'
|
|
226
|
+
post_install_message:
|
|
227
|
+
rdoc_options: []
|
|
228
|
+
require_paths:
|
|
229
|
+
- lib
|
|
230
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
231
|
+
requirements:
|
|
232
|
+
- - ">="
|
|
233
|
+
- !ruby/object:Gem::Version
|
|
234
|
+
version: 3.1.0
|
|
235
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
236
|
+
requirements:
|
|
237
|
+
- - ">="
|
|
238
|
+
- !ruby/object:Gem::Version
|
|
239
|
+
version: '0'
|
|
240
|
+
requirements: []
|
|
241
|
+
rubygems_version: 3.4.19
|
|
242
|
+
signing_key:
|
|
243
|
+
specification_version: 4
|
|
244
|
+
summary: Self-hosted contextual feedback engine, Shadow DOM widget, and MCP server
|
|
245
|
+
for Ruby on Rails.
|
|
246
|
+
test_files: []
|