pinmark 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/CHANGELOG.md +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +170 -0
- data/app/controllers/concerns/pinmark/session.rb +32 -0
- data/app/controllers/pinmark/annotations_controller.rb +102 -0
- data/app/controllers/pinmark/application_controller.rb +19 -0
- data/app/javascript/pinmark/pinmark_controller.js +857 -0
- data/app/views/pinmark/_activator.html.erb +14 -0
- data/app/views/pinmark/_overlay.html.erb +24 -0
- data/config/importmap.rb +6 -0
- data/config/routes.rb +11 -0
- data/lib/generators/pinmark/install/install_generator.rb +82 -0
- data/lib/pinmark/engine.rb +38 -0
- data/lib/pinmark/hooks/erb_partial.rb +39 -0
- data/lib/pinmark/hooks/view_component.rb +21 -0
- data/lib/pinmark/mcp/queue.rb +90 -0
- data/lib/pinmark/mcp/rack_app.rb +36 -0
- data/lib/pinmark/mcp/server.rb +29 -0
- data/lib/pinmark/mcp/tools/clear_addressed.rb +28 -0
- data/lib/pinmark/mcp/tools/list_pending.rb +44 -0
- data/lib/pinmark/mcp/tools/list_resolved.rb +41 -0
- data/lib/pinmark/mcp/tools/mark_addressed.rb +41 -0
- data/lib/pinmark/phlex.rb +44 -0
- data/lib/pinmark/source_locator.rb +17 -0
- data/lib/pinmark/stylesheets.rb +83 -0
- data/lib/pinmark/tracker.rb +41 -0
- data/lib/pinmark/version.rb +5 -0
- data/lib/pinmark/wrapper.rb +40 -0
- data/lib/pinmark.rb +36 -0
- data/package.json +14 -0
- metadata +147 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pinmark
|
|
4
|
+
# Inline CSS used by the engine's UI partials. Kept in Ruby so the partials
|
|
5
|
+
# stay free of large heredoc blocks and the styles can be reused across
|
|
6
|
+
# render contexts without depending on the asset pipeline.
|
|
7
|
+
module Stylesheets
|
|
8
|
+
ACTIVATOR = <<~CSS
|
|
9
|
+
.pinmark-activator {
|
|
10
|
+
position: fixed;
|
|
11
|
+
bottom: 12px;
|
|
12
|
+
left: 12px;
|
|
13
|
+
z-index: 99999;
|
|
14
|
+
padding: 6px 10px;
|
|
15
|
+
background: #1f2937;
|
|
16
|
+
color: #fff;
|
|
17
|
+
border: 1px solid #374151;
|
|
18
|
+
border-radius: 6px;
|
|
19
|
+
font: 12px sans-serif;
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
|
|
22
|
+
}
|
|
23
|
+
.pinmark-activator:hover { background: #374151; }
|
|
24
|
+
.pinmark-activator.is-on { background: #f97316; color: #111; border-color: #f97316; }
|
|
25
|
+
.pinmark-activator.is-on:hover { background: #ea580c; }
|
|
26
|
+
CSS
|
|
27
|
+
|
|
28
|
+
ACTIVATOR_SCRIPT = <<~JS
|
|
29
|
+
(function() {
|
|
30
|
+
var btn = document.getElementById('pinmark-activator');
|
|
31
|
+
if (!btn) return;
|
|
32
|
+
btn.addEventListener('click', function() {
|
|
33
|
+
var on = document.cookie.split('; ').some(function(c) { return c.indexOf('pinmark=1') === 0; });
|
|
34
|
+
if (on) {
|
|
35
|
+
document.cookie = 'pinmark=; path=/; max-age=0';
|
|
36
|
+
} else {
|
|
37
|
+
document.cookie = 'pinmark=1; path=/; max-age=2592000';
|
|
38
|
+
}
|
|
39
|
+
window.location.reload();
|
|
40
|
+
});
|
|
41
|
+
})();
|
|
42
|
+
JS
|
|
43
|
+
|
|
44
|
+
OVERLAY = <<~CSS
|
|
45
|
+
.pinmark-toggle { position: fixed; bottom: 12px; right: 12px; z-index: 99999; padding: 6px 10px; background: #111; color: #fff; border: 0; border-radius: 6px; font: 12px sans-serif; cursor: pointer; }
|
|
46
|
+
.pinmark-toggle.is-active { background: #f97316; color: #111; }
|
|
47
|
+
.pinmark-mode { position: fixed; bottom: 12px; right: 124px; z-index: 99999; padding: 6px 10px; background: #1f2937; color: #fff; border: 0; border-radius: 6px; font: 12px sans-serif; cursor: pointer; }
|
|
48
|
+
.pinmark-mode.is-element { background: #38bdf8; color: #0c1f2e; }
|
|
49
|
+
.pinmark-panel { position: fixed; top: 12px; right: 12px; max-width: 320px; max-height: 70vh; overflow: auto; background: rgba(17,17,17,.92); color: #fff; padding: 12px; border-radius: 8px; font: 12px/1.4 sans-serif; z-index: 99999; display: none; }
|
|
50
|
+
.pinmark-panel.is-open { display: block; }
|
|
51
|
+
.pinmark-highlight { outline: 2px solid #f97316 !important; outline-offset: -2px; cursor: crosshair; }
|
|
52
|
+
.pinmark-highlight-tag { outline: 2px dashed #38bdf8 !important; }
|
|
53
|
+
.pinmark-highlight-context { outline: 2px dotted rgba(249,115,22,.55) !important; outline-offset: -2px; }
|
|
54
|
+
.pinmark-popover { position: fixed; z-index: 99999; background: #111; color: #fff; padding: 8px; border-radius: 6px; min-width: 220px; }
|
|
55
|
+
.pinmark-popover textarea { width: 100%; min-height: 60px; box-sizing: border-box; background: #1f2937; color: #fff; border: 1px solid #374151; border-radius: 4px; padding: 6px; font: 12px/1.4 sans-serif; resize: vertical; }
|
|
56
|
+
.pinmark-popover textarea::placeholder { color: #9ca3af; }
|
|
57
|
+
.pinmark-popover textarea:focus { outline: none; border-color: #38bdf8; }
|
|
58
|
+
.pinmark-popover-actions { display: flex; gap: 6px; margin-top: 6px; }
|
|
59
|
+
.pinmark-popover-actions button { padding: 4px 10px; background: #1f2937; color: #fff; border: 1px solid #374151; border-radius: 4px; font: 12px sans-serif; cursor: pointer; }
|
|
60
|
+
.pinmark-popover-actions button:hover { background: #374151; }
|
|
61
|
+
.pinmark-popover-actions button:first-child { background: #f97316; border-color: #f97316; color: #111; }
|
|
62
|
+
.pinmark-popover-actions button:first-child:hover { background: #ea580c; }
|
|
63
|
+
.pinmark-popover-header { font: 600 12px sans-serif; color: #fff; margin-bottom: 6px; line-height: 1.3; word-break: break-word; }
|
|
64
|
+
.pinmark-popover-header .mcp-source { display: block; font-weight: 400; opacity: .75; font-size: 11px; }
|
|
65
|
+
.pinmark-popover-header .mcp-selector { display: block; font-weight: 400; color: #38bdf8; font-size: 11px; word-break: break-all; }
|
|
66
|
+
.pinmark-popover-debug { margin-top: 6px; color: #ddd; font: 11px sans-serif; }
|
|
67
|
+
.pinmark-popover-debug summary { cursor: pointer; user-select: none; opacity: .7; }
|
|
68
|
+
.pinmark-popover-debug summary:hover { opacity: 1; }
|
|
69
|
+
.pinmark-popover-debug pre { margin: 6px 0 0; padding: 6px; background: rgba(255,255,255,.05); border-radius: 4px; white-space: pre-wrap; word-break: break-word; max-height: 160px; overflow: auto; font: 10px/1.4 ui-monospace, monospace; }
|
|
70
|
+
.pinmark-label { position: fixed; z-index: 99999; padding: 2px 6px; background: #111; color: #fff; font: 11px sans-serif; border-radius: 4px; pointer-events: none; max-width: 360px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
71
|
+
.pinmark-label.is-tag { background: #38bdf8; color: #0c1f2e; }
|
|
72
|
+
.pinmark-markers { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 99998; }
|
|
73
|
+
.pinmark-marker { position: absolute; pointer-events: auto; transform: translate(-50%, -50%); width: 22px; height: 22px; border-radius: 50%; background: #f97316; color: #111; border: 2px solid #fff; box-shadow: 0 1px 4px rgba(0,0,0,.4); font: 600 11px sans-serif; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; padding: 0; }
|
|
74
|
+
.pinmark-marker:hover { transform: translate(-50%, -50%) scale(1.15); }
|
|
75
|
+
.pinmark-marker.is-resolved { background: #34d399; opacity: .75; }
|
|
76
|
+
.pinmark-marquee { position: absolute; z-index: 99998; border: 1.5px dashed #f97316; background: rgba(249,115,22,.12); pointer-events: none; }
|
|
77
|
+
.pinmark-panel-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
|
|
78
|
+
.pinmark-panel-collapse { background: transparent; color: #fff; border: 0; cursor: pointer; padding: 0 6px; font: 14px sans-serif; opacity: .6; }
|
|
79
|
+
.pinmark-panel-collapse:hover { opacity: 1; }
|
|
80
|
+
.pinmark-panel.is-collapsed { max-height: none; padding: 8px 12px; overflow: hidden; }
|
|
81
|
+
CSS
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pinmark
|
|
4
|
+
class Tracker
|
|
5
|
+
class StackUnderflowError < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
attr_reader :nodes
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@nodes = []
|
|
12
|
+
@stack = []
|
|
13
|
+
@counter = 0
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def push(component:, source:)
|
|
17
|
+
@counter += 1
|
|
18
|
+
id = "pm-#{@counter}"
|
|
19
|
+
parent_id = @stack.last
|
|
20
|
+
@nodes << { id:, component:, source:, parent_id: }
|
|
21
|
+
@stack.push(id)
|
|
22
|
+
id
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def pop
|
|
26
|
+
raise StackUnderflowError, "pinmark stack underflow" if @stack.empty?
|
|
27
|
+
|
|
28
|
+
@stack.pop
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def tree
|
|
32
|
+
by_parent = @nodes.group_by { |node| node[:parent_id] }
|
|
33
|
+
build = lambda { |parent_id|
|
|
34
|
+
(by_parent[parent_id] || []).map do |node|
|
|
35
|
+
node.merge(children: build.call(node[:id]))
|
|
36
|
+
end
|
|
37
|
+
}
|
|
38
|
+
build.call(nil)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module Pinmark
|
|
6
|
+
module Wrapper
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Wrap a block's HTML output in pinmark begin/end markers, pushing
|
|
10
|
+
# and popping the per-request tracker. When annotations are inactive (non-dev,
|
|
11
|
+
# or no tracker) the block is invoked verbatim and its return value passes
|
|
12
|
+
# through unchanged.
|
|
13
|
+
#
|
|
14
|
+
# The block must return a String (or something coercible via #to_s).
|
|
15
|
+
def wrap(component:, source:)
|
|
16
|
+
tracker = Pinmark.tracker if Rails.env.development?
|
|
17
|
+
return yield unless tracker
|
|
18
|
+
|
|
19
|
+
id = tracker.push(component:, source:)
|
|
20
|
+
parent_id = tracker.nodes.last[:parent_id]
|
|
21
|
+
|
|
22
|
+
id_attr = escape(id)
|
|
23
|
+
class_attr = escape(component)
|
|
24
|
+
src_attr = escape(source)
|
|
25
|
+
parent_attr = escape(parent_id)
|
|
26
|
+
|
|
27
|
+
begin_marker = %(<!-- pinmark:begin id="#{id_attr}" class="#{class_attr}" src="#{src_attr}" parent="#{parent_attr}" -->)
|
|
28
|
+
end_marker = %(<!-- pinmark:end id="#{id_attr}" -->)
|
|
29
|
+
|
|
30
|
+
content = yield.to_s
|
|
31
|
+
"#{begin_marker}#{content}#{end_marker}".html_safe
|
|
32
|
+
ensure
|
|
33
|
+
tracker&.pop if id
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def escape(value)
|
|
37
|
+
CGI.escapeHTML(value.to_s).gsub("--", "--")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/pinmark.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pinmark/version"
|
|
4
|
+
require "pinmark/engine"
|
|
5
|
+
require "pinmark/stylesheets"
|
|
6
|
+
require "pinmark/tracker"
|
|
7
|
+
require "pinmark/source_locator"
|
|
8
|
+
require "pinmark/wrapper"
|
|
9
|
+
require "pinmark/phlex"
|
|
10
|
+
require "pinmark/hooks/erb_partial"
|
|
11
|
+
require "pinmark/hooks/view_component"
|
|
12
|
+
require "pinmark/mcp/queue"
|
|
13
|
+
require "pinmark/mcp/tools/list_pending"
|
|
14
|
+
require "pinmark/mcp/tools/list_resolved"
|
|
15
|
+
require "pinmark/mcp/tools/mark_addressed"
|
|
16
|
+
require "pinmark/mcp/tools/clear_addressed"
|
|
17
|
+
require "pinmark/mcp/server"
|
|
18
|
+
require "pinmark/mcp/rack_app"
|
|
19
|
+
|
|
20
|
+
module Pinmark
|
|
21
|
+
# Whether the pinmark hooks should be active for the current request.
|
|
22
|
+
# The host app sets Pinmark.tracker (typically per-request via the
|
|
23
|
+
# Session concern) when the dev cookie / param toggles annotations on.
|
|
24
|
+
#
|
|
25
|
+
# We intentionally keep the predicate cheap so the wrapper / hooks can call it
|
|
26
|
+
# on every render in development without measurable overhead.
|
|
27
|
+
def self.active?
|
|
28
|
+
Rails.env.development? && tracker.present?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.tracker
|
|
32
|
+
return nil unless defined?(::Current) && ::Current.respond_to?(:pinmark)
|
|
33
|
+
|
|
34
|
+
::Current.pinmark
|
|
35
|
+
end
|
|
36
|
+
end
|
data/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pinmark",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Stimulus controller for the pinmark Rails engine. Consumed by webpack-based host apps via a `file:` dependency.",
|
|
6
|
+
"main": "app/javascript/pinmark/pinmark_controller.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"app/javascript/pinmark/pinmark_controller.js"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./app/javascript/pinmark/pinmark_controller.js",
|
|
12
|
+
"./pinmark_controller": "./app/javascript/pinmark/pinmark_controller.js"
|
|
13
|
+
}
|
|
14
|
+
}
|
metadata
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: pinmark
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Przemyslaw Lusar
|
|
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: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: mcp
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.14'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.14'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rspec-rails
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: phlex-rails
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: view_component
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
description: Pinmark adds a dev-only floating overlay to any Rails app. Click a component
|
|
83
|
+
or any element on the page, leave a comment, and Claude Code consumes the queue
|
|
84
|
+
via MCP — closing the loop between visual feedback and source edits. Works with
|
|
85
|
+
Phlex, ViewComponent, and ERB partials.
|
|
86
|
+
email:
|
|
87
|
+
- lluzak@gmail.com
|
|
88
|
+
executables: []
|
|
89
|
+
extensions: []
|
|
90
|
+
extra_rdoc_files: []
|
|
91
|
+
files:
|
|
92
|
+
- CHANGELOG.md
|
|
93
|
+
- LICENSE.txt
|
|
94
|
+
- README.md
|
|
95
|
+
- app/controllers/concerns/pinmark/session.rb
|
|
96
|
+
- app/controllers/pinmark/annotations_controller.rb
|
|
97
|
+
- app/controllers/pinmark/application_controller.rb
|
|
98
|
+
- app/javascript/pinmark/pinmark_controller.js
|
|
99
|
+
- app/views/pinmark/_activator.html.erb
|
|
100
|
+
- app/views/pinmark/_overlay.html.erb
|
|
101
|
+
- config/importmap.rb
|
|
102
|
+
- config/routes.rb
|
|
103
|
+
- lib/generators/pinmark/install/install_generator.rb
|
|
104
|
+
- lib/pinmark.rb
|
|
105
|
+
- lib/pinmark/engine.rb
|
|
106
|
+
- lib/pinmark/hooks/erb_partial.rb
|
|
107
|
+
- lib/pinmark/hooks/view_component.rb
|
|
108
|
+
- lib/pinmark/mcp/queue.rb
|
|
109
|
+
- lib/pinmark/mcp/rack_app.rb
|
|
110
|
+
- lib/pinmark/mcp/server.rb
|
|
111
|
+
- lib/pinmark/mcp/tools/clear_addressed.rb
|
|
112
|
+
- lib/pinmark/mcp/tools/list_pending.rb
|
|
113
|
+
- lib/pinmark/mcp/tools/list_resolved.rb
|
|
114
|
+
- lib/pinmark/mcp/tools/mark_addressed.rb
|
|
115
|
+
- lib/pinmark/phlex.rb
|
|
116
|
+
- lib/pinmark/source_locator.rb
|
|
117
|
+
- lib/pinmark/stylesheets.rb
|
|
118
|
+
- lib/pinmark/tracker.rb
|
|
119
|
+
- lib/pinmark/version.rb
|
|
120
|
+
- lib/pinmark/wrapper.rb
|
|
121
|
+
- package.json
|
|
122
|
+
homepage: https://github.com/lluzak/pinmark
|
|
123
|
+
licenses:
|
|
124
|
+
- MIT
|
|
125
|
+
metadata:
|
|
126
|
+
homepage_uri: https://github.com/lluzak/pinmark
|
|
127
|
+
source_code_uri: https://github.com/lluzak/pinmark
|
|
128
|
+
changelog_uri: https://github.com/lluzak/pinmark/blob/master/CHANGELOG.md
|
|
129
|
+
rubygems_mfa_required: 'true'
|
|
130
|
+
rdoc_options: []
|
|
131
|
+
require_paths:
|
|
132
|
+
- lib
|
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '3.2'
|
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
|
+
requirements:
|
|
140
|
+
- - ">="
|
|
141
|
+
- !ruby/object:Gem::Version
|
|
142
|
+
version: '0'
|
|
143
|
+
requirements: []
|
|
144
|
+
rubygems_version: 3.7.2
|
|
145
|
+
specification_version: 4
|
|
146
|
+
summary: Pin-style UI annotations that flow into Claude Code via MCP.
|
|
147
|
+
test_files: []
|