rails-markup 1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +304 -0
- data/app/assets/javascripts/rails_markup/toolbar.js +2225 -0
- data/app/controllers/rails_markup/annotations_controller.rb +244 -0
- data/app/controllers/rails_markup/application_controller.rb +7 -0
- data/app/controllers/rails_markup/dashboard_controller.rb +230 -0
- data/app/controllers/rails_markup/external/annotations_controller.rb +68 -0
- data/app/models/rails_markup/annotation.rb +176 -0
- data/app/views/layouts/rails_markup/application.html.erb +158 -0
- data/app/views/rails_markup/dashboard/_annotation_card.html.erb +25 -0
- data/app/views/rails_markup/dashboard/_annotation_list.html.erb +57 -0
- data/app/views/rails_markup/dashboard/_annotation_page.html.erb +13 -0
- data/app/views/rails_markup/dashboard/_filters.html.erb +75 -0
- data/app/views/rails_markup/dashboard/_stats.html.erb +22 -0
- data/app/views/rails_markup/dashboard/_styles.html.erb +115 -0
- data/app/views/rails_markup/dashboard/board.html.erb +135 -0
- data/app/views/rails_markup/dashboard/index.html.erb +38 -0
- data/app/views/rails_markup/dashboard/show.html.erb +129 -0
- data/app/views/rails_markup/shared/_toolbar.html.erb +28 -0
- data/bin/rails-markup +5 -0
- data/config/routes.rb +45 -0
- data/db/migrate/20260720000000_add_client_uuid_to_rails_markup_annotations.rb +13 -0
- data/db/migrate/20260721000000_backfill_rails_markup_client_uuids.rb +17 -0
- data/lib/generators/rails_markup/install/templates/auth_controller.rb.erb +17 -0
- data/lib/generators/rails_markup/install/templates/bin_markup.erb +5 -0
- data/lib/generators/rails_markup/install/templates/create_rails_markup_annotations.rb.erb +27 -0
- data/lib/generators/rails_markup/install/templates/initializer.rb.erb +54 -0
- data/lib/generators/rails_markup/install_generator.rb +167 -0
- data/lib/generators/rails_markup/uninstall_generator.rb +110 -0
- data/lib/rails_markup/cli/base.rb +8 -0
- data/lib/rails_markup/cli/initializer_writer.rb +54 -0
- data/lib/rails_markup/cli/setup_wizard.rb +229 -0
- data/lib/rails_markup/cli.rb +831 -0
- data/lib/rails_markup/client_uuid_maintenance.rb +174 -0
- data/lib/rails_markup/configuration.rb +160 -0
- data/lib/rails_markup/engine.rb +18 -0
- data/lib/rails_markup/http_server.rb +226 -0
- data/lib/rails_markup/http_store_proxy.rb +122 -0
- data/lib/rails_markup/mcp_config.rb +258 -0
- data/lib/rails_markup/mcp_server.rb +639 -0
- data/lib/rails_markup/server.rb +73 -0
- data/lib/rails_markup/store.rb +219 -0
- data/lib/rails_markup/version.rb +5 -0
- data/lib/rails_markup.rb +11 -0
- data/lib/tasks/rails_markup_client_uuids.rake +19 -0
- metadata +198 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
|
|
5
|
+
module RailsMarkup
|
|
6
|
+
# Main entry point — starts HTTP server and optionally MCP server.
|
|
7
|
+
# HTTP and MCP share the same Store instance so annotations flow both ways.
|
|
8
|
+
# If the HTTP port is already taken, MCP proxies to the existing server.
|
|
9
|
+
class Server
|
|
10
|
+
attr_reader :store
|
|
11
|
+
|
|
12
|
+
def initialize(port: 4747, mcp_only: false)
|
|
13
|
+
@port = port
|
|
14
|
+
@mcp_only = mcp_only
|
|
15
|
+
@store = Store.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def start
|
|
19
|
+
if @mcp_only
|
|
20
|
+
start_mcp_only
|
|
21
|
+
else
|
|
22
|
+
start_http_and_mcp
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def start_mcp(store)
|
|
29
|
+
mcp = McpServer.new(store: store)
|
|
30
|
+
mcp.start
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# MCP-only mode: always proxy to the HTTP server.
|
|
34
|
+
# The user runs `rails-markup server` separately for HTTP.
|
|
35
|
+
def start_mcp_only
|
|
36
|
+
$stderr.puts "[rails-markup] MCP server listening on stdio (proxying to HTTP on port #{@port})"
|
|
37
|
+
proxy = HttpStoreProxy.new(base_url: "http://localhost:#{@port}")
|
|
38
|
+
start_mcp(proxy)
|
|
39
|
+
rescue IOError
|
|
40
|
+
# stdin closed — MCP client disconnected
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def start_http_and_mcp
|
|
44
|
+
if port_available?(@port)
|
|
45
|
+
# We own the port — start HTTP + MCP with shared in-memory store
|
|
46
|
+
http_thread = Thread.new do
|
|
47
|
+
http = HttpServer.new(store: @store, port: @port)
|
|
48
|
+
$stderr.puts "[rails-markup] HTTP server listening on port #{@port}"
|
|
49
|
+
http.start
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
$stderr.puts "[rails-markup] MCP server listening on stdio"
|
|
53
|
+
start_mcp(@store)
|
|
54
|
+
else
|
|
55
|
+
# Port is taken — proxy MCP reads/writes to the existing HTTP server
|
|
56
|
+
$stderr.puts "[rails-markup] Port #{@port} in use — MCP proxying to existing server"
|
|
57
|
+
$stderr.puts "[rails-markup] MCP server listening on stdio"
|
|
58
|
+
proxy = HttpStoreProxy.new(base_url: "http://localhost:#{@port}")
|
|
59
|
+
start_mcp(proxy)
|
|
60
|
+
end
|
|
61
|
+
rescue IOError
|
|
62
|
+
# stdin closed — MCP client disconnected
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def port_available?(port)
|
|
66
|
+
server = TCPServer.new("0.0.0.0", port)
|
|
67
|
+
server.close
|
|
68
|
+
true
|
|
69
|
+
rescue Errno::EADDRINUSE
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module RailsMarkup
|
|
7
|
+
# In-memory store for sessions and annotations.
|
|
8
|
+
# Ephemeral by design — data lives for one coding session.
|
|
9
|
+
class Store
|
|
10
|
+
Session = Struct.new(:id, :url, :metadata, :created_at, :annotations, keyword_init: true)
|
|
11
|
+
Annotation = Struct.new(:id, :session_id, :target, :content, :intent, :severity, :status,
|
|
12
|
+
:selected_text, :metadata, :created_at, :thread, keyword_init: true)
|
|
13
|
+
|
|
14
|
+
MAX_SESSIONS = 100
|
|
15
|
+
SESSION_TTL = 4 * 3600 # 4 hours
|
|
16
|
+
|
|
17
|
+
attr_reader :sessions
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@sessions = {}
|
|
21
|
+
@annotations_index = {} # id -> annotation (O(1) lookup)
|
|
22
|
+
@subscribers = [] # SSE callbacks: [session_id, callback]
|
|
23
|
+
@mutex = Mutex.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# --- Sessions ---
|
|
27
|
+
|
|
28
|
+
def create_session(url:, metadata: {})
|
|
29
|
+
id = SecureRandom.hex(8)
|
|
30
|
+
session = Session.new(
|
|
31
|
+
id: id,
|
|
32
|
+
url: url,
|
|
33
|
+
metadata: metadata || {},
|
|
34
|
+
created_at: Time.now.iso8601,
|
|
35
|
+
annotations: []
|
|
36
|
+
)
|
|
37
|
+
@mutex.synchronize do
|
|
38
|
+
evict_stale_sessions
|
|
39
|
+
@sessions[id] = session
|
|
40
|
+
end
|
|
41
|
+
session
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get_session(id)
|
|
45
|
+
@mutex.synchronize { @sessions[id] }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def list_sessions
|
|
49
|
+
@mutex.synchronize { @sessions.values }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# --- Annotations ---
|
|
53
|
+
|
|
54
|
+
def create_annotation(session_id:, target:, content:, intent: "change", severity: "suggestion",
|
|
55
|
+
selected_text: nil, metadata: {})
|
|
56
|
+
id = SecureRandom.hex(8)
|
|
57
|
+
annotation = Annotation.new(
|
|
58
|
+
id: id,
|
|
59
|
+
session_id: session_id,
|
|
60
|
+
target: target,
|
|
61
|
+
content: content,
|
|
62
|
+
intent: intent,
|
|
63
|
+
severity: severity,
|
|
64
|
+
status: "pending",
|
|
65
|
+
selected_text: selected_text,
|
|
66
|
+
metadata: metadata || {},
|
|
67
|
+
created_at: Time.now.iso8601,
|
|
68
|
+
thread: []
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Single mutex block — no TOCTOU gap
|
|
72
|
+
@mutex.synchronize do
|
|
73
|
+
session = @sessions[session_id]
|
|
74
|
+
return nil unless session
|
|
75
|
+
|
|
76
|
+
session.annotations << annotation
|
|
77
|
+
@annotations_index[id] = annotation
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
notify(session_id, type: "annotation_created", annotation: serialize_annotation(annotation))
|
|
81
|
+
annotation
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def get_annotation(annotation_id)
|
|
85
|
+
@mutex.synchronize { @annotations_index[annotation_id] }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def pending_for_session(session_id)
|
|
89
|
+
session = get_session(session_id)
|
|
90
|
+
return [] unless session
|
|
91
|
+
|
|
92
|
+
@mutex.synchronize { session.annotations.select { |a| a.status == "pending" } }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def all_pending
|
|
96
|
+
@mutex.synchronize do
|
|
97
|
+
@sessions.values.flat_map { |s| s.annotations.select { |a| a.status == "pending" } }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# --- Status transitions ---
|
|
102
|
+
|
|
103
|
+
def acknowledge(annotation_id)
|
|
104
|
+
update_status(annotation_id, "acknowledged")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def resolve(annotation_id, summary: nil)
|
|
108
|
+
ann = update_status(annotation_id, "resolved")
|
|
109
|
+
return nil unless ann
|
|
110
|
+
|
|
111
|
+
ann.thread << { role: "agent", message: summary, timestamp: Time.now.iso8601 } if summary
|
|
112
|
+
notify(ann.session_id, type: "annotation_update", annotation: serialize_annotation(ann),
|
|
113
|
+
status: "resolved", summary: summary)
|
|
114
|
+
ann
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def dismiss(annotation_id, reason: nil)
|
|
118
|
+
ann = update_status(annotation_id, "dismissed")
|
|
119
|
+
return nil unless ann
|
|
120
|
+
|
|
121
|
+
ann.thread << { role: "agent", message: reason, timestamp: Time.now.iso8601 } if reason
|
|
122
|
+
notify(ann.session_id, type: "annotation_update", annotation: serialize_annotation(ann),
|
|
123
|
+
status: "dismissed", reason: reason)
|
|
124
|
+
ann
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def reply(annotation_id, message:)
|
|
128
|
+
ann = get_annotation(annotation_id)
|
|
129
|
+
return nil unless ann
|
|
130
|
+
|
|
131
|
+
@mutex.synchronize do
|
|
132
|
+
ann.thread << { role: "agent", message: message, timestamp: Time.now.iso8601 }
|
|
133
|
+
end
|
|
134
|
+
notify(ann.session_id, type: "annotation_update", annotation: serialize_annotation(ann),
|
|
135
|
+
status: ann.status, message: message)
|
|
136
|
+
ann
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# --- SSE subscriptions ---
|
|
140
|
+
|
|
141
|
+
def subscribe(session_id, &callback)
|
|
142
|
+
sub = [session_id, callback]
|
|
143
|
+
@mutex.synchronize { @subscribers << sub }
|
|
144
|
+
sub
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def unsubscribe(sub)
|
|
148
|
+
@mutex.synchronize { @subscribers.delete(sub) }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# --- Serialization ---
|
|
152
|
+
|
|
153
|
+
def serialize_session(session)
|
|
154
|
+
{
|
|
155
|
+
id: session.id,
|
|
156
|
+
url: session.url,
|
|
157
|
+
metadata: session.metadata,
|
|
158
|
+
createdAt: session.created_at,
|
|
159
|
+
annotations: session.annotations.map { |a| serialize_annotation(a) }
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def serialize_annotation(ann)
|
|
164
|
+
{
|
|
165
|
+
id: ann.id,
|
|
166
|
+
sessionId: ann.session_id,
|
|
167
|
+
target: ann.target,
|
|
168
|
+
content: ann.content,
|
|
169
|
+
intent: ann.intent,
|
|
170
|
+
severity: ann.severity,
|
|
171
|
+
status: ann.status,
|
|
172
|
+
selectedText: ann.selected_text,
|
|
173
|
+
authorName: ann.metadata&.dig("author"),
|
|
174
|
+
metadata: ann.metadata,
|
|
175
|
+
createdAt: ann.created_at,
|
|
176
|
+
thread: ann.thread
|
|
177
|
+
}
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
def update_status(annotation_id, new_status)
|
|
183
|
+
ann = get_annotation(annotation_id)
|
|
184
|
+
return nil unless ann
|
|
185
|
+
|
|
186
|
+
@mutex.synchronize { ann.status = new_status }
|
|
187
|
+
ann
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def notify(session_id, data)
|
|
191
|
+
dead = []
|
|
192
|
+
@mutex.synchronize do
|
|
193
|
+
@subscribers.each do |sub|
|
|
194
|
+
sid, callback = sub
|
|
195
|
+
next unless sid.nil? || sid == session_id
|
|
196
|
+
|
|
197
|
+
callback.call(data)
|
|
198
|
+
rescue StandardError
|
|
199
|
+
dead << sub
|
|
200
|
+
end
|
|
201
|
+
dead.each { |s| @subscribers.delete(s) }
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def evict_stale_sessions
|
|
206
|
+
return if @sessions.size < MAX_SESSIONS
|
|
207
|
+
|
|
208
|
+
cutoff = (Time.now - SESSION_TTL).iso8601
|
|
209
|
+
@sessions.delete_if do |_id, session|
|
|
210
|
+
if session.created_at < cutoff
|
|
211
|
+
session.annotations.each { |a| @annotations_index.delete(a.id) }
|
|
212
|
+
true
|
|
213
|
+
else
|
|
214
|
+
false
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
data/lib/rails_markup.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "rails_markup/version"
|
|
4
|
+
require_relative "rails_markup/configuration"
|
|
5
|
+
require_relative "rails_markup/store"
|
|
6
|
+
require_relative "rails_markup/http_store_proxy"
|
|
7
|
+
require_relative "rails_markup/http_server"
|
|
8
|
+
require_relative "rails_markup/mcp_server"
|
|
9
|
+
require_relative "rails_markup/server"
|
|
10
|
+
|
|
11
|
+
require_relative "rails_markup/engine" if defined?(Rails::Engine)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_markup/client_uuid_maintenance"
|
|
4
|
+
|
|
5
|
+
namespace :rails_markup do
|
|
6
|
+
namespace :client_uuids do
|
|
7
|
+
desc "Repair blank or noncanonical Rails Markup client UUIDs (safe to repeat)"
|
|
8
|
+
task repair: :environment do
|
|
9
|
+
repaired = RailsMarkup::ClientUuidMaintenance.repair!
|
|
10
|
+
puts "Repaired #{repaired} Rails Markup client UUID(s)."
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
desc "Fail unless every Rails Markup annotation has a canonical client UUID"
|
|
14
|
+
task verify: :environment do
|
|
15
|
+
RailsMarkup::ClientUuidMaintenance.verify!
|
|
16
|
+
puts "All Rails Markup client UUIDs are canonical."
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails-markup
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- InventList
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: webrick
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.8'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.8'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: railties
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '7.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: activerecord
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '7.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '7.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: thor
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.3'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.3'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: bubbletea
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0.1'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0.1'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: lipgloss
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0.2'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0.2'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: csv
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '3.3'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '3.3'
|
|
110
|
+
description: An MCP server that lets you annotate Rails views in the browser and have
|
|
111
|
+
AI agents read and act on your feedback.
|
|
112
|
+
email:
|
|
113
|
+
- hello@inventlist.com
|
|
114
|
+
executables:
|
|
115
|
+
- rails-markup
|
|
116
|
+
extensions: []
|
|
117
|
+
extra_rdoc_files: []
|
|
118
|
+
files:
|
|
119
|
+
- LICENSE
|
|
120
|
+
- README.md
|
|
121
|
+
- app/assets/javascripts/rails_markup/toolbar.js
|
|
122
|
+
- app/controllers/rails_markup/annotations_controller.rb
|
|
123
|
+
- app/controllers/rails_markup/application_controller.rb
|
|
124
|
+
- app/controllers/rails_markup/dashboard_controller.rb
|
|
125
|
+
- app/controllers/rails_markup/external/annotations_controller.rb
|
|
126
|
+
- app/models/rails_markup/annotation.rb
|
|
127
|
+
- app/views/layouts/rails_markup/application.html.erb
|
|
128
|
+
- app/views/rails_markup/dashboard/_annotation_card.html.erb
|
|
129
|
+
- app/views/rails_markup/dashboard/_annotation_list.html.erb
|
|
130
|
+
- app/views/rails_markup/dashboard/_annotation_page.html.erb
|
|
131
|
+
- app/views/rails_markup/dashboard/_filters.html.erb
|
|
132
|
+
- app/views/rails_markup/dashboard/_stats.html.erb
|
|
133
|
+
- app/views/rails_markup/dashboard/_styles.html.erb
|
|
134
|
+
- app/views/rails_markup/dashboard/board.html.erb
|
|
135
|
+
- app/views/rails_markup/dashboard/index.html.erb
|
|
136
|
+
- app/views/rails_markup/dashboard/show.html.erb
|
|
137
|
+
- app/views/rails_markup/shared/_toolbar.html.erb
|
|
138
|
+
- bin/rails-markup
|
|
139
|
+
- config/routes.rb
|
|
140
|
+
- db/migrate/20260720000000_add_client_uuid_to_rails_markup_annotations.rb
|
|
141
|
+
- db/migrate/20260721000000_backfill_rails_markup_client_uuids.rb
|
|
142
|
+
- lib/generators/rails_markup/install/templates/auth_controller.rb.erb
|
|
143
|
+
- lib/generators/rails_markup/install/templates/bin_markup.erb
|
|
144
|
+
- lib/generators/rails_markup/install/templates/create_rails_markup_annotations.rb.erb
|
|
145
|
+
- lib/generators/rails_markup/install/templates/initializer.rb.erb
|
|
146
|
+
- lib/generators/rails_markup/install_generator.rb
|
|
147
|
+
- lib/generators/rails_markup/uninstall_generator.rb
|
|
148
|
+
- lib/rails_markup.rb
|
|
149
|
+
- lib/rails_markup/cli.rb
|
|
150
|
+
- lib/rails_markup/cli/base.rb
|
|
151
|
+
- lib/rails_markup/cli/initializer_writer.rb
|
|
152
|
+
- lib/rails_markup/cli/setup_wizard.rb
|
|
153
|
+
- lib/rails_markup/client_uuid_maintenance.rb
|
|
154
|
+
- lib/rails_markup/configuration.rb
|
|
155
|
+
- lib/rails_markup/engine.rb
|
|
156
|
+
- lib/rails_markup/http_server.rb
|
|
157
|
+
- lib/rails_markup/http_store_proxy.rb
|
|
158
|
+
- lib/rails_markup/mcp_config.rb
|
|
159
|
+
- lib/rails_markup/mcp_server.rb
|
|
160
|
+
- lib/rails_markup/server.rb
|
|
161
|
+
- lib/rails_markup/store.rb
|
|
162
|
+
- lib/rails_markup/version.rb
|
|
163
|
+
- lib/tasks/rails_markup_client_uuids.rake
|
|
164
|
+
homepage: https://github.com/inventlist/rails-markup
|
|
165
|
+
licenses:
|
|
166
|
+
- MIT
|
|
167
|
+
metadata:
|
|
168
|
+
homepage_uri: https://github.com/inventlist/rails-markup
|
|
169
|
+
source_code_uri: https://github.com/inventlist/rails-markup
|
|
170
|
+
changelog_uri: https://github.com/inventlist/rails-markup/blob/main/CHANGELOG.md
|
|
171
|
+
post_install_message: |
|
|
172
|
+
Rails Markup installed! Run the generator to set up:
|
|
173
|
+
|
|
174
|
+
rails generate rails_markup:install
|
|
175
|
+
rails db:migrate
|
|
176
|
+
|
|
177
|
+
Options:
|
|
178
|
+
--mount-path=/admin/annotations (engine route)
|
|
179
|
+
--base-controller=AdminController (auth parent class)
|
|
180
|
+
--layout=application (toolbar injection)
|
|
181
|
+
rdoc_options: []
|
|
182
|
+
require_paths:
|
|
183
|
+
- lib
|
|
184
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
185
|
+
requirements:
|
|
186
|
+
- - ">="
|
|
187
|
+
- !ruby/object:Gem::Version
|
|
188
|
+
version: '3.2'
|
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
190
|
+
requirements:
|
|
191
|
+
- - ">="
|
|
192
|
+
- !ruby/object:Gem::Version
|
|
193
|
+
version: '0'
|
|
194
|
+
requirements: []
|
|
195
|
+
rubygems_version: 3.6.9
|
|
196
|
+
specification_version: 4
|
|
197
|
+
summary: Point-and-click annotation tool for AI agents
|
|
198
|
+
test_files: []
|