@hybridlabor-api/aos 4.0.2 → 4.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.
Files changed (79) hide show
  1. package/.claude/agents/database-reviewer.md +109 -0
  2. package/.claude/agents/go-build-resolver.md +112 -0
  3. package/.claude/agents/opensource-forker.md +216 -0
  4. package/.claude/agents/opensource-sanitizer.md +206 -0
  5. package/.claude/agents/security-reviewer.md +126 -0
  6. package/.claude/agents/silent-failure-hunter.md +68 -0
  7. package/CLAUDE.md +15 -0
  8. package/README.md +0 -13
  9. package/THIRD_PARTY_NOTICES.md +133 -0
  10. package/package.json +4 -2
  11. package/skills/basic/bdbmediastorm/SKILL.md +7 -1
  12. package/skills/basic/startcycle/SKILL.md +2 -0
  13. package/skills/bdbrainstorm/SKILL.md +1 -0
  14. package/skills/global_config/plan-canvas/SKILL.md +233 -0
  15. package/skills/global_config/plan-canvas/scripts/lib/loopback-guard.js +59 -0
  16. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/markdown.js +301 -0
  17. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/sdk.js +239 -0
  18. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +636 -0
  19. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/sessions.js +271 -0
  20. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +630 -0
  21. package/skills/global_config/plan-canvas/scripts/plan-canvas.js +419 -0
  22. package/.agents/skills/firecrawl/SKILL.md +0 -149
  23. package/.agents/skills/firecrawl/rules/install.md +0 -82
  24. package/.agents/skills/firecrawl/rules/security.md +0 -26
  25. package/.agents/skills/firecrawl-agent/SKILL.md +0 -58
  26. package/.agents/skills/firecrawl-build/SKILL.md +0 -39
  27. package/.agents/skills/firecrawl-build-interact/SKILL.md +0 -68
  28. package/.agents/skills/firecrawl-build-onboarding/SKILL.md +0 -103
  29. package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +0 -39
  30. package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +0 -20
  31. package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +0 -17
  32. package/.agents/skills/firecrawl-build-scrape/SKILL.md +0 -69
  33. package/.agents/skills/firecrawl-build-search/SKILL.md +0 -69
  34. package/.agents/skills/firecrawl-crawl/SKILL.md +0 -59
  35. package/.agents/skills/firecrawl-download/SKILL.md +0 -70
  36. package/.agents/skills/firecrawl-interact/SKILL.md +0 -84
  37. package/.agents/skills/firecrawl-map/SKILL.md +0 -51
  38. package/.agents/skills/firecrawl-scrape/SKILL.md +0 -69
  39. package/.agents/skills/firecrawl-search/SKILL.md +0 -60
  40. package/docs/sessions/AUDIT-HANDOVER-2026-08-28.md +0 -169
  41. package/docs/sessions/BDB_REMOTEOS_MCP_HANDOVER.md +0 -130
  42. package/docs/sessions/SESSION-HANDOVER-v3.13.md +0 -249
  43. package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +0 -10
  44. package/mcps/after-effects-mcp/build/index.js +0 -840
  45. package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +0 -153
  46. package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +0 -218
  47. package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +0 -71
  48. package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +0 -147
  49. package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +0 -114
  50. package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +0 -115
  51. package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +0 -192
  52. package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +0 -90
  53. package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +0 -50
  54. package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +0 -1773
  55. package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +0 -160
  56. package/mcps/bdb-remoteos-mcp/queue.db +0 -0
  57. package/mcps/bdb-remoteos-mcp/src/bdb_remoteos_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
  58. package/mcps/bdb-remoteos-mcp/src/bdb_remoteos_mcp/__pycache__/incus_client.cpython-312.pyc +0 -0
  59. package/mcps/bdb-remoteos-mcp/src/bdb_remoteos_mcp/__pycache__/main.cpython-312.pyc +0 -0
  60. package/mcps/bdb-remoteos-mcp/src/bdb_remoteos_mcp/__pycache__/queue.cpython-312.pyc +0 -0
  61. package/mcps/bdb-remoteos-mcp/src/bdb_remoteos_mcp/__pycache__/schemas.cpython-312.pyc +0 -0
  62. package/mcps/bdb-remoteos-mcp/src/bdb_remoteos_mcp/__pycache__/server.cpython-312.pyc +0 -0
  63. package/mcps/bdb-remoteos-mcp/src/bdb_remoteos_mcp/__pycache__/webhook.cpython-312.pyc +0 -0
  64. package/mcps/bdb-remoteos-mcp/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  65. package/mcps/bdb-remoteos-mcp/tests/__pycache__/mock_incus.cpython-312.pyc +0 -0
  66. package/mcps/bdb-remoteos-mcp/tests/__pycache__/test_mcp_server.cpython-312-pytest-9.1.1.pyc +0 -0
  67. package/mcps/bdb-remoteos-mcp/tests/__pycache__/test_security_redteam.cpython-312-pytest-9.1.1.pyc +0 -0
  68. package/mcps/bdb-remoteos-mcp/tests/__pycache__/test_webhook.cpython-312-pytest-9.1.1.pyc +0 -0
  69. package/mcps/computer-use-mcp/dist/client.d.ts +0 -150
  70. package/mcps/computer-use-mcp/dist/client.js +0 -136
  71. package/mcps/computer-use-mcp/dist/entrypoint.d.ts +0 -16
  72. package/mcps/computer-use-mcp/dist/entrypoint.js +0 -26
  73. package/mcps/computer-use-mcp/dist/native.d.ts +0 -212
  74. package/mcps/computer-use-mcp/dist/native.js +0 -50
  75. package/mcps/computer-use-mcp/dist/server.d.ts +0 -32
  76. package/mcps/computer-use-mcp/dist/server.js +0 -342
  77. package/mcps/computer-use-mcp/dist/session.d.ts +0 -101
  78. package/mcps/computer-use-mcp/dist/session.js +0 -2372
  79. package/skills/bdbsaastraining/scripts/__pycache__/build_profile.cpython-314.pyc +0 -0
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Host/Origin gating for ECC's loopback HTTP servers (control pane, plan
5
+ * canvas). DNS rebinding can point an attacker-controlled hostname at
6
+ * 127.0.0.1, so every request must present a Host header from this
7
+ * allowlist before the server does any work.
8
+ *
9
+ * Source: affaan-m/ECC — MIT, see THIRD_PARTY_NOTICES.md
10
+ */
11
+
12
+ const LOOPBACK_HOSTNAMES = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']);
13
+
14
+ // Extract the hostname portion of an HTTP Host header value, stripping any
15
+ // port. Returns null when the header is missing or malformed.
16
+ function parseHostHeader(value) {
17
+ if (!value || typeof value !== 'string') return null;
18
+ const trimmed = value.trim();
19
+ if (!trimmed) return null;
20
+ const match = trimmed.match(/^(\[[^\]]+\]|[^:]+)(?::(\d+))?$/);
21
+ if (!match) return null;
22
+ if (match[2] !== undefined) {
23
+ const port = Number(match[2]);
24
+ if (!Number.isInteger(port) || port > 65535) return null;
25
+ }
26
+ return match[1].toLowerCase();
27
+ }
28
+
29
+ function buildAllowedHostnames(configuredHost) {
30
+ const set = new Set(LOOPBACK_HOSTNAMES);
31
+ if (configuredHost) set.add(String(configuredHost).toLowerCase());
32
+ return set;
33
+ }
34
+
35
+ function isAllowedHostHeader(hostHeader, allowedHostnames) {
36
+ const hostname = parseHostHeader(hostHeader);
37
+ if (!hostname) return false;
38
+ return allowedHostnames.has(hostname);
39
+ }
40
+
41
+ // Origin is absent on same-origin navigations and CLI clients; when present
42
+ // it must resolve to an allowed hostname.
43
+ function isAllowedOrigin(originHeader, allowedHostnames) {
44
+ if (!originHeader || typeof originHeader !== 'string') return true;
45
+ try {
46
+ const url = new URL(originHeader);
47
+ return allowedHostnames.has(url.hostname.toLowerCase());
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ module.exports = {
54
+ LOOPBACK_HOSTNAMES,
55
+ buildAllowedHostnames,
56
+ isAllowedHostHeader,
57
+ isAllowedOrigin,
58
+ parseHostHeader
59
+ };
@@ -0,0 +1,301 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Minimal GitHub-flavored-markdown subset renderer for Plan Canvas.
5
+ * Renders .claude/plans/*.plan.md artifacts to HTML body content.
6
+ *
7
+ * Security model: the entire source line is HTML-escaped before any inline
8
+ * rule runs, so raw HTML in the markdown always displays as text. Link and
9
+ * image URLs are validated against an allowlist of protocols.
10
+ *
11
+ * Source: affaan-m/ECC — MIT, see THIRD_PARTY_NOTICES.md
12
+ */
13
+
14
+ // Placeholders live in the Unicode private-use area so escaped output can
15
+ // never collide with them. Pre-existing occurrences are stripped from input.
16
+ const TOKEN_OPEN = '\uE000';
17
+ const TOKEN_CLOSE = '\uE001';
18
+ const TOKEN_RE = new RegExp(TOKEN_OPEN + '(\\d+)' + TOKEN_CLOSE, 'g');
19
+ const STRIP_RE = new RegExp('[' + TOKEN_OPEN + TOKEN_CLOSE + ']', 'g');
20
+
21
+ const LIST_ITEM_RE = /^(\s*)([-*]|\d+\.)\s+(.*)$/;
22
+ const HR_RE = /^ {0,3}(-{3,}|\*{3,})\s*$/;
23
+
24
+ function escapeHtml(value) {
25
+ return String(value ?? '')
26
+ .replace(/&/g, '&')
27
+ .replace(/</g, '&lt;')
28
+ .replace(/>/g, '&gt;')
29
+ .replace(/"/g, '&quot;')
30
+ .replace(/'/g, '&#39;');
31
+ }
32
+
33
+ function slugify(text) {
34
+ return String(text ?? '')
35
+ .toLowerCase()
36
+ .replace(/[^a-z0-9\s-]/g, '')
37
+ .trim()
38
+ .replace(/[\s-]+/g, '-')
39
+ .replace(/^-+|-+$/g, '');
40
+ }
41
+
42
+ // Strip whitespace/control characters so "Ja vaScript:" style tricks cannot
43
+ // hide a scheme, then classify against the allowlist.
44
+ function classifyUrl(rawUrl) {
45
+ const compact = String(rawUrl)
46
+ .split('')
47
+ .filter((ch) => ch.charCodeAt(0) > 32)
48
+ .join('')
49
+ .toLowerCase();
50
+ if (compact.startsWith('#')) return 'anchor';
51
+ if (compact.startsWith('//')) return 'blocked';
52
+ const scheme = compact.match(/^[a-z][a-z0-9+.-]*:/);
53
+ if (!scheme) return 'relative';
54
+ if (scheme[0] === 'http:' || scheme[0] === 'https:') return 'http';
55
+ if (scheme[0] === 'mailto:') return 'mailto';
56
+ return 'blocked';
57
+ }
58
+
59
+ function applyEmphasis(s) {
60
+ return s
61
+ .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
62
+ .replace(/~~([^~]+)~~/g, '<del>$1</del>')
63
+ .replace(/\*([^*]+)\*/g, '<em>$1</em>')
64
+ .replace(/(^|[^\w])_([^_]+)_(?!\w)/g, '$1<em>$2</em>');
65
+ }
66
+
67
+ function renderInline(rawText) {
68
+ const tokens = [];
69
+ const stash = (html) => {
70
+ tokens.push(html);
71
+ return TOKEN_OPEN + (tokens.length - 1) + TOKEN_CLOSE;
72
+ };
73
+
74
+ let s = escapeHtml(rawText);
75
+
76
+ // Code spans first: contents stay escaped and opt out of all other rules.
77
+ s = s.replace(/`([^`]+)`/g, (_m, code) => stash('<code>' + code + '</code>'));
78
+
79
+ s = s.replace(/!\[([^\]]*)\]\(([^)]*)\)/g, (_m, alt, src) => {
80
+ const kind = classifyUrl(src);
81
+ if (kind !== 'http' && kind !== 'relative') return alt;
82
+ return stash('<img src="' + src.trim() + '" alt="' + alt + '">');
83
+ });
84
+
85
+ s = s.replace(/\[([^\]]+)\]\(([^)]*)\)/g, (_m, label, url) => {
86
+ const kind = classifyUrl(url);
87
+ const text = applyEmphasis(label);
88
+ if (kind === 'blocked') return text;
89
+ const extra = kind === 'http' ? ' target="_blank" rel="noopener"' : '';
90
+ return stash('<a href="' + url.trim() + '"' + extra + '>' + text + '</a>');
91
+ });
92
+
93
+ s = applyEmphasis(s);
94
+
95
+ // Stashed anchors may hold code-span tokens, so resolve until none remain.
96
+ while (s.includes(TOKEN_OPEN)) {
97
+ s = s.replace(TOKEN_RE, (_m, idx) => tokens[Number(idx)]);
98
+ }
99
+ return s;
100
+ }
101
+
102
+ function splitTableRow(line) {
103
+ let s = line.trim();
104
+ if (s.startsWith('|')) s = s.slice(1);
105
+ if (s.endsWith('|') && !s.endsWith('\\|')) s = s.slice(0, -1);
106
+ return s
107
+ .replace(/\\\|/g, TOKEN_OPEN)
108
+ .split('|')
109
+ .map((cell) => cell.split(TOKEN_OPEN).join('|').trim());
110
+ }
111
+
112
+ function isAlignmentRow(line) {
113
+ if (!line || !line.includes('|')) return false;
114
+ const cells = splitTableRow(line);
115
+ return cells.length > 0 && cells.every((cell) => /^:?-+:?$/.test(cell));
116
+ }
117
+
118
+ function cellAlign(spec) {
119
+ const left = spec.startsWith(':');
120
+ const right = spec.endsWith(':');
121
+ if (left && right) return 'center';
122
+ if (right) return 'right';
123
+ if (left) return 'left';
124
+ return '';
125
+ }
126
+
127
+ function renderListItem(text) {
128
+ const task = text.match(/^\[([ xX])\]\s+(.*)$/);
129
+ if (task) {
130
+ const checked = task[1].trim() ? ' checked' : '';
131
+ return '<li class="task"><input type="checkbox" disabled' + checked + '> ' +
132
+ renderInline(task[2]) + '</li>';
133
+ }
134
+ return '<li>' + renderInline(text) + '</li>';
135
+ }
136
+
137
+ function listTag(marker) {
138
+ return /^\d/.test(marker) ? 'ol' : 'ul';
139
+ }
140
+
141
+ function buildList(items, start, indent) {
142
+ const tag = listTag(items[start].marker);
143
+ const parts = [];
144
+ let i = start;
145
+ while (i < items.length && items[i].indent >= indent) {
146
+ if (items[i].indent > indent) {
147
+ // Deeper item: nest a sublist inside the previous <li>
148
+ const nested = buildList(items, i, items[i].indent);
149
+ if (parts.length > 0) {
150
+ const last = parts.pop();
151
+ parts.push(last.replace(/<\/li>$/, '\n' + nested.html + '\n</li>'));
152
+ } else {
153
+ parts.push('<li>\n' + nested.html + '\n</li>');
154
+ }
155
+ i = nested.end;
156
+ } else {
157
+ // A marker-type change at the same indent starts a new list
158
+ // (CommonMark); stop here so the caller renders the next run with
159
+ // its own tag instead of absorbing it into this one.
160
+ if (listTag(items[i].marker) !== tag) break;
161
+ parts.push(renderListItem(items[i].text));
162
+ i += 1;
163
+ }
164
+ }
165
+ return { html: '<' + tag + '>\n' + parts.join('\n') + '\n</' + tag + '>', end: i };
166
+ }
167
+
168
+ function buildListBlock(items) {
169
+ let lists = [];
170
+ let i = 0;
171
+ while (i < items.length) {
172
+ const list = buildList(items, i, items[i].indent);
173
+ lists = [...lists, list.html];
174
+ i = list.end;
175
+ }
176
+ return lists.join('\n');
177
+ }
178
+
179
+ function startsBlock(line, nextLine) {
180
+ return /^```/.test(line) ||
181
+ /^#{1,6}\s/.test(line) ||
182
+ HR_RE.test(line) ||
183
+ /^ {0,3}>/.test(line) ||
184
+ LIST_ITEM_RE.test(line) ||
185
+ (line.includes('|') && isAlignmentRow(nextLine || ''));
186
+ }
187
+
188
+ function renderMarkdown(text) {
189
+ if (!text) return '';
190
+ const lines = String(text)
191
+ .replace(STRIP_RE, '')
192
+ .replace(/\r\n?/g, '\n')
193
+ .split('\n');
194
+ const out = [];
195
+ let i = 0;
196
+
197
+ while (i < lines.length) {
198
+ const line = lines[i];
199
+
200
+ if (!line.trim()) {
201
+ i += 1;
202
+ continue;
203
+ }
204
+
205
+ const fence = line.match(/^```(.*)$/);
206
+ if (fence) {
207
+ const lang = fence[1].trim().split(/\s+/)[0].toLowerCase().replace(/[^a-z0-9-]/g, '');
208
+ const body = [];
209
+ i += 1;
210
+ while (i < lines.length && !/^```\s*$/.test(lines[i])) {
211
+ body.push(lines[i]);
212
+ i += 1;
213
+ }
214
+ i += 1; // skip closing fence (or run off EOF)
215
+ if (lang === 'mermaid') {
216
+ // Mermaid reads the element's textContent, and the browser decodes
217
+ // character references there — so escaping keeps `-->`/`<` intact for
218
+ // the renderer while preventing HTML injection or a </pre> breakout.
219
+ out.push('<pre class="mermaid">' + escapeHtml(body.join('\n')) + '</pre>');
220
+ continue;
221
+ }
222
+ const cls = lang ? ' class="language-' + lang + '"' : '';
223
+ out.push('<pre><code' + cls + '>' + escapeHtml(body.join('\n')) + '</code></pre>');
224
+ continue;
225
+ }
226
+
227
+ const heading = line.match(/^(#{1,6})\s+(.+?)\s*$/);
228
+ if (heading) {
229
+ const level = heading[1].length;
230
+ out.push('<h' + level + ' id="' + slugify(heading[2]) + '">' +
231
+ renderInline(heading[2]) + '</h' + level + '>');
232
+ i += 1;
233
+ continue;
234
+ }
235
+
236
+ // Horizontal rule (alignment rows never reach here: tables consume them)
237
+ if (HR_RE.test(line)) {
238
+ out.push('<hr>');
239
+ i += 1;
240
+ continue;
241
+ }
242
+
243
+ // Blockquote: strip one `>` level and recurse, which handles nesting
244
+ if (/^ {0,3}>/.test(line)) {
245
+ const inner = [];
246
+ while (i < lines.length && /^ {0,3}>/.test(lines[i])) {
247
+ inner.push(lines[i].replace(/^ {0,3}> ?/, ''));
248
+ i += 1;
249
+ }
250
+ out.push('<blockquote>\n' + renderMarkdown(inner.join('\n')) + '\n</blockquote>');
251
+ continue;
252
+ }
253
+
254
+ // Table: header row followed by an alignment row
255
+ if (line.includes('|') && isAlignmentRow(lines[i + 1] || '')) {
256
+ const aligns = splitTableRow(lines[i + 1]).map(cellAlign);
257
+ const row = (tag, cells) => '<tr>' + cells.map((cell, idx) => {
258
+ const style = aligns[idx] ? ' style="text-align:' + aligns[idx] + '"' : '';
259
+ return '<' + tag + style + '>' + renderInline(cell) + '</' + tag + '>';
260
+ }).join('') + '</tr>';
261
+ const head = row('th', splitTableRow(line));
262
+ const body = [];
263
+ i += 2;
264
+ while (i < lines.length && lines[i].trim() && lines[i].includes('|')) {
265
+ body.push(row('td', splitTableRow(lines[i])));
266
+ i += 1;
267
+ }
268
+ out.push('<table>\n<thead>\n' + head + '\n</thead>\n<tbody>\n' +
269
+ body.join('\n') + '\n</tbody>\n</table>');
270
+ continue;
271
+ }
272
+
273
+ if (LIST_ITEM_RE.test(line)) {
274
+ const items = [];
275
+ while (i < lines.length) {
276
+ const m = lines[i].match(LIST_ITEM_RE);
277
+ if (!m) break;
278
+ items.push({ indent: m[1].length, marker: m[2], text: m[3] });
279
+ i += 1;
280
+ }
281
+ // An outdent below the first item's indentation ends that list. Render
282
+ // the remaining run as a sibling list so malformed indentation cannot
283
+ // silently drop content or create an empty parent item.
284
+ out.push(buildListBlock(items));
285
+ continue;
286
+ }
287
+
288
+ // Paragraph: run of plain lines up to a blank line or block start
289
+ const para = [line.trim()];
290
+ i += 1;
291
+ while (i < lines.length && lines[i].trim() && !startsBlock(lines[i], lines[i + 1])) {
292
+ para.push(lines[i].trim());
293
+ i += 1;
294
+ }
295
+ out.push('<p>' + renderInline(para.join('\n')) + '</p>');
296
+ }
297
+
298
+ return out.join('\n');
299
+ }
300
+
301
+ module.exports = { renderMarkdown, escapeHtml, slugify };
@@ -0,0 +1,239 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Plan Canvas artifact SDK — the script injected into the reviewed artifact.
5
+ *
6
+ * The artifact runs in a sandboxed iframe without allow-same-origin, so this
7
+ * script can only talk to the chrome via postMessage. It renders all of its
8
+ * own UI inside a shadow root so it never annotates itself and never leaks
9
+ * styles into the artifact.
10
+ *
11
+ * Source: affaan-m/ECC — MIT, see THIRD_PARTY_NOTICES.md
12
+ */
13
+
14
+ function artifactSdkJs() {
15
+ return `'use strict';
16
+ (() => {
17
+ if (window.parent === window) return; // only meaningful inside the canvas
18
+ if (window.__eccPlanCanvasSdk) return;
19
+ window.__eccPlanCanvasSdk = true;
20
+
21
+ let annotate = true;
22
+ let card = null;
23
+
24
+ const post = msg => window.parent.postMessage(msg, '*');
25
+
26
+ // --- shadow-root UI host --------------------------------------------
27
+ const host = document.createElement('div');
28
+ host.setAttribute('data-ecc-plan-canvas', 'ui');
29
+ host.style.cssText = 'position:absolute;top:0;left:0;width:0;height:0;z-index:2147483647';
30
+ const root = host.attachShadow({ mode: 'open' });
31
+ root.innerHTML = \`
32
+ <style>
33
+ :host{all:initial}
34
+ .hl{position:fixed;pointer-events:none;border:1.5px solid #6885e8;background:rgba(104,133,232,0.12);border-radius:4px;display:none;z-index:2147483646;transition:all .06s ease-out}
35
+ .selhint{position:absolute;display:none;z-index:2147483647;background:#101218;color:#dfe2e9;border:1px solid #272c3e;border-radius:6px;padding:4px 10px;font:600 11.5px -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;cursor:pointer;box-shadow:0 8px 32px rgba(0,0,0,0.6)}
36
+ .selhint:hover{border-color:#6885e8}
37
+ .card{position:absolute;display:none;z-index:2147483647;width:300px;background:#101218;border:1px solid #272c3e;border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,0.6);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;color:#dfe2e9}
38
+ .card h4{margin:0;padding:10px 12px 0;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:#80859a}
39
+ .card .snippet{padding:4px 12px 0;font:10.5px 'SF Mono','Fira Code',monospace;color:#4acbbe;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
40
+ .card textarea{display:block;width:calc(100% - 24px);margin:8px 12px;min-height:56px;resize:vertical;background:#13161e;border:1px solid #1d2130;border-radius:6px;color:#dfe2e9;font:12.5px -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;padding:7px 9px;outline:none;box-sizing:border-box}
41
+ .card textarea:focus{border-color:#6885e8}
42
+ .card .row{display:flex;justify-content:flex-end;gap:8px;padding:0 12px 12px}
43
+ .card button{border-radius:6px;font:600 11.5px -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;padding:5px 12px;cursor:pointer}
44
+ .card .cancel{background:none;border:1px solid #1d2130;color:#80859a}
45
+ .card .cancel:hover{color:#dfe2e9;border-color:#272c3e}
46
+ .card .queue{background:#6885e8;border:1px solid #6885e8;color:#fff}
47
+ .card .queue:hover{background:#3d5ab8}
48
+ .card .keys{padding:0 12px 10px;font-size:9.5px;color:#4c5168}
49
+ </style>
50
+ <div class="hl"></div>
51
+ <button class="selhint" type="button">Annotate selection</button>
52
+ <div class="card">
53
+ <h4></h4>
54
+ <div class="snippet"></div>
55
+ <textarea placeholder="What should change here?"></textarea>
56
+ <div class="row">
57
+ <button class="cancel" type="button">Cancel</button>
58
+ <button class="queue" type="button">Queue</button>
59
+ </div>
60
+ <div class="keys">Enter to queue &middot; Cmd/Ctrl+Enter to queue &amp; send</div>
61
+ </div>\`;
62
+ const attach = () => document.body ? document.body.appendChild(host) : null;
63
+ if (document.body) attach();
64
+ else document.addEventListener('DOMContentLoaded', attach);
65
+
66
+ const hl = root.querySelector('.hl');
67
+ const selhint = root.querySelector('.selhint');
68
+ const cardEl = root.querySelector('.card');
69
+ const cardTitle = cardEl.querySelector('h4');
70
+ const cardSnippet = cardEl.querySelector('.snippet');
71
+ const cardText = cardEl.querySelector('textarea');
72
+
73
+ // --- selectors & context ---------------------------------------------
74
+ const esc = v => (window.CSS && CSS.escape) ? CSS.escape(v) : v.replace(/[^a-zA-Z0-9_-]/g, '\\\\$&');
75
+ function selectorFor(el) {
76
+ const parts = [];
77
+ let node = el;
78
+ for (let depth = 0; node && node.nodeType === 1 && depth < 6; depth++) {
79
+ if (node.id) { parts.unshift('#' + esc(node.id)); return parts.join(' > '); }
80
+ const tag = node.tagName.toLowerCase();
81
+ if (tag === 'body' || tag === 'html') { parts.unshift(tag); break; }
82
+ let nth = 1;
83
+ let sib = node;
84
+ while ((sib = sib.previousElementSibling)) if (sib.tagName === node.tagName) nth++;
85
+ parts.unshift(tag + ':nth-of-type(' + nth + ')');
86
+ node = node.parentElement;
87
+ }
88
+ return parts.join(' > ');
89
+ }
90
+ function snippetFor(el) {
91
+ return (el.innerText || el.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, 200);
92
+ }
93
+ const INTERACTIVE = new Set(['button', 'input', 'select', 'textarea', 'option', 'label', 'summary', 'a']);
94
+ function isInteractive(el) {
95
+ let node = el;
96
+ while (node && node.nodeType === 1) {
97
+ if (INTERACTIVE.has(node.tagName.toLowerCase()) || node.isContentEditable) return true;
98
+ node = node.parentElement;
99
+ }
100
+ return false;
101
+ }
102
+ const isOurs = el => el === host || host.contains(el);
103
+
104
+ // --- annotation card ---------------------------------------------------
105
+ function openCard(target) {
106
+ card = target;
107
+ cardTitle.textContent = target.kindLabel;
108
+ cardSnippet.textContent = target.anchor.snippet || target.anchor.selector;
109
+ cardText.value = '';
110
+ cardEl.style.display = 'block';
111
+ const x = Math.min(target.x, window.innerWidth - 320) + window.scrollX;
112
+ const y = target.y + 12 + window.scrollY;
113
+ cardEl.style.left = Math.max(8, x) + 'px';
114
+ cardEl.style.top = y + 'px';
115
+ cardText.focus();
116
+ }
117
+ function closeCard() {
118
+ card = null;
119
+ cardEl.style.display = 'none';
120
+ }
121
+ function queueCard(sendNow) {
122
+ if (!card) return;
123
+ const text = cardText.value.trim();
124
+ if (!text) { cardText.focus(); return; }
125
+ post({
126
+ type: sendNow ? 'pc:queue-and-send' : 'pc:queue',
127
+ item: { kind: 'annotation', text, anchor: card.anchor }
128
+ });
129
+ closeCard();
130
+ }
131
+ cardEl.querySelector('.cancel').addEventListener('click', closeCard);
132
+ cardEl.querySelector('.queue').addEventListener('click', () => queueCard(false));
133
+ cardText.addEventListener('keydown', e => {
134
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); queueCard(true); }
135
+ else if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); queueCard(false); }
136
+ else if (e.key === 'Escape') closeCard();
137
+ });
138
+
139
+ // --- element hover / click ---------------------------------------------
140
+ document.addEventListener('mousemove', e => {
141
+ if (!annotate || card) { hl.style.display = 'none'; return; }
142
+ const el = e.target;
143
+ if (!el || isOurs(el) || el === document.body || el === document.documentElement || isInteractive(el)) {
144
+ hl.style.display = 'none';
145
+ return;
146
+ }
147
+ const rect = el.getBoundingClientRect();
148
+ hl.style.display = 'block';
149
+ hl.style.left = rect.left - 2 + 'px';
150
+ hl.style.top = rect.top - 2 + 'px';
151
+ hl.style.width = rect.width + 'px';
152
+ hl.style.height = rect.height + 'px';
153
+ }, true);
154
+
155
+ document.addEventListener('click', e => {
156
+ if (!annotate) return;
157
+ const el = e.target;
158
+ if (isOurs(el)) return;
159
+ if (card) { if (!cardEl.contains(e.composedPath()[0])) closeCard(); return; }
160
+ if (isInteractive(el)) return; // let controls behave natively
161
+ const selection = window.getSelection();
162
+ if (selection && !selection.isCollapsed) return; // handled by selection flow
163
+ if (el === document.body || el === document.documentElement) return;
164
+ e.preventDefault();
165
+ e.stopPropagation();
166
+ hl.style.display = 'none';
167
+ openCard({
168
+ kindLabel: 'Annotate <' + el.tagName.toLowerCase() + '>',
169
+ anchor: { selector: selectorFor(el), tag: el.tagName.toLowerCase(), snippet: snippetFor(el) },
170
+ x: e.clientX,
171
+ y: e.clientY
172
+ });
173
+ }, true);
174
+
175
+ // --- text selection -------------------------------------------------------
176
+ document.addEventListener('mouseup', e => {
177
+ if (!annotate || card || isOurs(e.target)) return;
178
+ setTimeout(() => {
179
+ const selection = window.getSelection();
180
+ const text = selection ? String(selection).replace(/\\s+/g, ' ').trim() : '';
181
+ if (!text || !selection.rangeCount) { selhint.style.display = 'none'; return; }
182
+ const rect = selection.getRangeAt(0).getBoundingClientRect();
183
+ selhint.style.display = 'block';
184
+ selhint.style.left = rect.left + window.scrollX + 'px';
185
+ selhint.style.top = rect.bottom + 6 + window.scrollY + 'px';
186
+ selhint.onclick = () => {
187
+ selhint.style.display = 'none';
188
+ const anchorNode = selection.anchorNode;
189
+ const el = anchorNode && anchorNode.nodeType === 1 ? anchorNode : anchorNode && anchorNode.parentElement;
190
+ openCard({
191
+ kindLabel: 'Annotate selection',
192
+ anchor: {
193
+ selector: el ? selectorFor(el) : 'body',
194
+ tag: 'text',
195
+ snippet: text.slice(0, 200),
196
+ textRange: { text: text.slice(0, 1000) }
197
+ },
198
+ x: rect.left,
199
+ y: rect.bottom
200
+ });
201
+ };
202
+ }, 0);
203
+ }, true);
204
+ document.addEventListener('selectionchange', () => {
205
+ const selection = window.getSelection();
206
+ if (!selection || selection.isCollapsed) selhint.style.display = 'none';
207
+ });
208
+
209
+ // --- chrome bridge ---------------------------------------------------------
210
+ window.addEventListener('message', e => {
211
+ const msg = e.data || {};
212
+ if (msg.type === 'pc:set-mode') {
213
+ annotate = Boolean(msg.annotate);
214
+ if (!annotate) { hl.style.display = 'none'; selhint.style.display = 'none'; closeCard(); }
215
+ } else if (msg.type === 'pc:restore-scroll') {
216
+ window.scrollTo(msg.x || 0, msg.y || 0);
217
+ }
218
+ });
219
+ document.addEventListener('keydown', e => {
220
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'i') {
221
+ e.preventDefault();
222
+ post({ type: 'pc:toggle-mode' });
223
+ } else if (e.key === 'Escape' && card) closeCard();
224
+ }, true);
225
+
226
+ let scrollTimer = null;
227
+ window.addEventListener('scroll', () => {
228
+ if (scrollTimer) return;
229
+ scrollTimer = setTimeout(() => {
230
+ scrollTimer = null;
231
+ post({ type: 'pc:scroll', x: window.scrollX, y: window.scrollY });
232
+ }, 150);
233
+ }, { passive: true });
234
+
235
+ post({ type: 'pc:ready' });
236
+ })();`;
237
+ }
238
+
239
+ module.exports = { artifactSdkJs };