cobro-mcp 0.4.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.
@@ -0,0 +1,769 @@
1
+ "use strict";
2
+ (() => {
3
+ // src/overlay/ui.ts
4
+ var CSS2 = `
5
+ :host{position:fixed;inset:0;margin:0;padding:0;border:0;background:transparent;width:100vw;height:100vh;overflow:visible;pointer-events:none;font:12px/1.5 ui-monospace,Menlo,Consolas,monospace;color:#e8ecf5}
6
+ :host::backdrop{display:none}
7
+ *{box-sizing:border-box}
8
+ .glass{position:fixed;inset:0;pointer-events:auto;cursor:crosshair;display:none}
9
+ .hover-box{position:fixed;display:none;border:2px solid #e35d5d;background:rgba(227,93,93,.08);border-radius:2px;pointer-events:none}
10
+ .hover-badge{position:fixed;display:none;background:#1c2333;border:1px solid #e35d5d;border-radius:4px;padding:4px 8px;white-space:pre;color:#fff;pointer-events:none;max-width:480px}
11
+ .band{position:fixed;display:none;border:1.5px dashed #e35d5d;background:rgba(227,93,93,.06);pointer-events:none}
12
+ .toolbar{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);display:flex;gap:6px;align-items:center;background:#0e111a;border:1px solid #2a3350;border-radius:999px;padding:6px 10px;box-shadow:0 4px 16px rgba(0,0,0,.5);pointer-events:auto}
13
+ .toolbar button,.panel button{font:inherit;color:#cdd8f0;background:#232c42;border:1px solid #3a4a72;border-radius:999px;padding:4px 10px;cursor:pointer}
14
+ .toolbar button:hover,.panel button:hover{background:#2e3a58;border-color:#4c5f92;color:#fff}
15
+ .toolbar button:focus-visible,.panel button:focus-visible{outline:2px solid #9db8ef;outline-offset:1px}
16
+ .toolbar button.on{color:#fff;background:#e35d5d;border-color:#e35d5d}
17
+ .els button{background:transparent;border-color:transparent;color:#8291b0;padding:0 6px}
18
+ .els button:hover{background:#2e3a58;color:#fff}
19
+ .dot{font-size:9px;line-height:1;color:#8291b0}
20
+ .dot.waiting{color:#4fd18b}
21
+ .dot.sent{color:#f0b429}
22
+ .dot.working{color:#9db8ef}
23
+ .dot.done{color:#4fd18b}
24
+ .status{max-width:440px;overflow:hidden;color:#aab6d0}
25
+ .status.off{color:#f0b429}
26
+ .status-in{display:inline-block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;transition:transform .2s ease-out}
27
+ .status-in.scroll{max-width:none;overflow:visible;text-overflow:clip}
28
+ .status-in.enter{animation:cobroin .15s ease-out}
29
+ @keyframes cobroin{from{opacity:.4;transform:translateX(-6px)}to{opacity:1;transform:translateX(0)}}
30
+ @media (prefers-reduced-motion: reduce){.status-in{transition:none}.status-in.enter{animation:none}}
31
+ .panel{position:fixed;right:14px;bottom:60px;width:320px;background:#171b28;border:1px solid #e35d5d;border-radius:8px;padding:10px 12px;box-shadow:0 4px 16px rgba(0,0,0,.5);pointer-events:auto;display:none}
32
+ .panel.show{display:block}
33
+ .panel h4{margin:0 0 6px;color:#e8ecf5;font-size:12px;font-weight:400}
34
+ .panel h4 .mark{color:#e35d5d;margin-right:4px}
35
+ .els{max-height:110px;overflow:auto;margin-bottom:8px;color:#aab6d0;font-size:11px}
36
+ .els div{display:flex;justify-content:space-between;gap:6px;word-break:break-all}
37
+ .els .missing{color:#f0b429}
38
+ textarea{width:100%;height:54px;resize:none;font:inherit;color:#e8ecf5;background:#0e111a;border:1px solid #3a4a72;border-radius:4px;padding:4px 6px;margin-bottom:8px}
39
+ .row{display:flex;justify-content:flex-end;gap:6px;align-items:center}
40
+ .row .send{color:#fff;background:#e35d5d;border-color:#e35d5d;font-weight:700}
41
+ .row .send:disabled{opacity:.4;cursor:not-allowed}
42
+ .flash{position:fixed;border:2px solid #4fd18b;border-radius:2px;pointer-events:none;animation:cobroflash 1.6s ease-out forwards}
43
+ @keyframes cobroflash{0%{opacity:1}100%{opacity:0}}
44
+ /* shadow root\uC758 \uC790\uC2DD\uC740 \uBAA8\uB450 position:fixed \uD615\uC81C \u2014 picker\uAC00 glass\uB97C toolbar/panel \uB4A4\uC5D0 append\uD558\uBBC0\uB85C \uC313\uC784 \uC21C\uC11C\uB97C \uBA85\uC2DC\uD55C\uB2E4 */
45
+ .glass{z-index:0}
46
+ .hover-box,.hover-badge,.band,.flash{z-index:1}
47
+ .toolbar,.panel{z-index:2}
48
+ `;
49
+ var LANG = navigator.language.toLowerCase().startsWith("ko") ? "ko" : "en";
50
+ var T = {
51
+ ko: {
52
+ agentIdle: "\uC5D0\uC774\uC804\uD2B8 \uBBF8\uC5F0\uACB0",
53
+ agentWaiting: "\uD53C\uB4DC\uBC31 \uB300\uAE30 \uC911",
54
+ agentSent: "\uC804\uC1A1\uB428 \u2014 \uC5D0\uC774\uC804\uD2B8 \uC751\uB2F5 \uB300\uAE30",
55
+ agentWorking: "\uC218\uC815 \uC911",
56
+ agentDone: "\uC644\uB8CC",
57
+ disconnected: "\uC5F0\uACB0 \uB04A\uAE40 \u2014 \uC7AC\uC5F0\uACB0 \uC911",
58
+ hintSend: "Send\uB85C \uC804\uC1A1\uD558\uC138\uC694",
59
+ hintClick: "\uD398\uC774\uC9C0\uC5D0\uC11C \uC694\uC18C\uB97C \uD074\uB9AD\uD558\uC138\uC694 \xB7 Esc\uB85C \uD574\uC81C",
60
+ hintMore: (n) => `\uC694\uC18C ${n}\uAC1C \uC120\uD0DD \xB7 \uB354 \uACE0\uB974\uAC70\uB098 \uBA54\uBAA8\uB97C \uC801\uC73C\uC138\uC694`,
61
+ hintNote: "\uBA54\uBAA8\uB97C \uC801\uACE0 Send\uB97C \uB204\uB974\uC138\uC694",
62
+ hintPick: "Ctrl+Shift+F \uB610\uB294 Select\uB85C \uC694\uC18C\uB97C \uACE0\uB974\uC138\uC694",
63
+ refresh: "\uAC31\uC2E0",
64
+ selCount: (n) => `\uC694\uC18C ${n}\uAC1C \uC120\uD0DD\uB428`,
65
+ selNone: "\uC120\uD0DD\uB41C \uC694\uC18C \uC5C6\uC74C \xB7 \uBA54\uBAA8\uB9CC \uBCF4\uB0B4\uB3C4 \uB429\uB2C8\uB2E4",
66
+ elMissing: "\uC694\uC18C \uC5C6\uC74C",
67
+ notePlaceholder: "\uC218\uC815 \uC694\uCCAD \uBA54\uBAA8\u2026",
68
+ tipSelect: "\uC694\uC18C \uC120\uD0DD \uBAA8\uB4DC (Ctrl+Shift+F)",
69
+ tipCollapse: "\uD328\uB110 \uC811\uAE30 / \uD3BC\uCE58\uAE30",
70
+ tipSend: "\uC120\uD0DD\uD55C \uC694\uC18C\uC640 \uBA54\uBAA8\uB97C \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC804\uC1A1",
71
+ tipSendLocked: "\uC5D0\uC774\uC804\uD2B8\uAC00 \uC791\uC5C5 \uC911 \u2014 done \uB4A4\uC5D0 \uBCF4\uB0BC \uC218 \uC788\uC2B5\uB2C8\uB2E4",
72
+ tipRemove: "\uC774 \uC694\uC18C \uBE7C\uAE30"
73
+ },
74
+ en: {
75
+ agentIdle: "Agent not connected",
76
+ agentWaiting: "Waiting for your feedback",
77
+ agentSent: "Sent \u2014 waiting for the agent",
78
+ agentWorking: "Working",
79
+ agentDone: "Done",
80
+ disconnected: "Disconnected \u2014 reconnecting",
81
+ hintSend: "Press Send to deliver",
82
+ hintClick: "Click an element on the page \xB7 Esc to exit",
83
+ hintMore: (n) => `${n} selected \xB7 pick more or write a note`,
84
+ hintNote: "Write a note, then press Send",
85
+ hintPick: "Press Ctrl+Shift+F or Select to pick an element",
86
+ refresh: "refresh",
87
+ selCount: (n) => `${n} element(s) selected`,
88
+ selNone: "No element selected \xB7 a note alone is fine",
89
+ elMissing: "missing",
90
+ notePlaceholder: "Describe the change\u2026",
91
+ tipSelect: "Pick mode (Ctrl+Shift+F)",
92
+ tipCollapse: "Collapse / expand the panel",
93
+ tipSend: "Send the selected elements and note to the agent",
94
+ tipSendLocked: "Agent is working \u2014 you can send after done",
95
+ tipRemove: "Remove this element"
96
+ }
97
+ }[LANG];
98
+ var AGENT_TEXT = {
99
+ idle: () => T.agentIdle,
100
+ waiting: () => T.agentWaiting,
101
+ sent: () => T.agentSent,
102
+ working: (t) => T.agentWorking + (t ? ": " + t : ""),
103
+ done: (t) => T.agentDone + (t ? ": " + t : "")
104
+ };
105
+ var DOT_TITLE = { idle: T.agentIdle, waiting: T.agentWaiting, sent: T.agentSent, working: T.agentWorking, done: T.agentDone };
106
+ function createUI(h) {
107
+ const host = document.createElement("div");
108
+ host.setAttribute("data-cobro-host", "");
109
+ host.setAttribute("popover", "manual");
110
+ const root = host.attachShadow({ mode: "open" });
111
+ const style = document.createElement("style");
112
+ style.textContent = CSS2;
113
+ const toolbar = document.createElement("div");
114
+ toolbar.className = "toolbar";
115
+ const selectBtn = document.createElement("button");
116
+ selectBtn.textContent = "Select";
117
+ selectBtn.title = T.tipSelect;
118
+ selectBtn.onclick = () => h.onToggleSelect();
119
+ const dot = document.createElement("span");
120
+ dot.className = "dot";
121
+ dot.textContent = "\u25CF";
122
+ const status = document.createElement("span");
123
+ status.className = "status";
124
+ const statusIn = document.createElement("span");
125
+ statusIn.className = "status-in";
126
+ status.append(statusIn);
127
+ const collapseBtn = document.createElement("button");
128
+ collapseBtn.textContent = "Collapse";
129
+ collapseBtn.title = T.tipCollapse;
130
+ toolbar.append(selectBtn, dot, status, collapseBtn);
131
+ const panel = document.createElement("div");
132
+ panel.className = "panel";
133
+ root.append(style, toolbar, panel);
134
+ let collapsed = false;
135
+ let lastVm = null;
136
+ let lastHint = null;
137
+ collapseBtn.onclick = () => {
138
+ collapsed = !collapsed;
139
+ collapseBtn.textContent = collapsed ? "Expand" : "Collapse";
140
+ if (lastVm) render(lastVm);
141
+ };
142
+ const textareas = /* @__PURE__ */ new Map();
143
+ let leaveTimer;
144
+ let hovering = false;
145
+ const applyHoverScroll = () => {
146
+ const over = statusIn.scrollWidth - status.clientWidth;
147
+ if (over <= 0) {
148
+ statusIn.classList.remove("scroll");
149
+ statusIn.style.transform = "";
150
+ statusIn.style.transitionDuration = "";
151
+ return;
152
+ }
153
+ statusIn.classList.add("scroll");
154
+ statusIn.style.transitionDuration = Math.max(0.6, over / 60) + "s";
155
+ statusIn.style.transform = `translateX(-${over}px)`;
156
+ };
157
+ status.addEventListener("mouseenter", () => {
158
+ clearTimeout(leaveTimer);
159
+ hovering = true;
160
+ applyHoverScroll();
161
+ });
162
+ status.addEventListener("mouseleave", () => {
163
+ hovering = false;
164
+ statusIn.style.transitionDuration = ".2s";
165
+ statusIn.style.transform = "";
166
+ clearTimeout(leaveTimer);
167
+ leaveTimer = setTimeout(() => statusIn.classList.remove("scroll"), 200);
168
+ });
169
+ statusIn.addEventListener("animationend", () => statusIn.classList.remove("enter"));
170
+ const mount = () => {
171
+ if (!host.isConnected) document.documentElement.append(host);
172
+ try {
173
+ if (!host.matches(":popover-open")) host.showPopover();
174
+ } catch {
175
+ host.style.zIndex = "2147483647";
176
+ }
177
+ };
178
+ const bump = () => {
179
+ try {
180
+ if (host.matches(":popover-open")) {
181
+ host.hidePopover();
182
+ host.showPopover();
183
+ }
184
+ } catch {
185
+ }
186
+ };
187
+ document.addEventListener("toggle", (e) => {
188
+ if (e.target !== host) bump();
189
+ }, true);
190
+ new MutationObserver((muts) => {
191
+ if (!host.isConnected) mount();
192
+ if (muts.some((m) => m.type === "attributes" && m.target.tagName === "DIALOG")) bump();
193
+ }).observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ["open"] });
194
+ mount();
195
+ const el = (tag, cls, text) => {
196
+ const d = document.createElement(tag);
197
+ if (cls) d.className = cls;
198
+ if (text != null) d.textContent = text;
199
+ return d;
200
+ };
201
+ function render(vm) {
202
+ lastVm = vm;
203
+ const active = root.activeElement;
204
+ const wasTa = active instanceof HTMLTextAreaElement ? active : null;
205
+ const sel = wasTa ? [wasTa.selectionStart, wasTa.selectionEnd] : null;
206
+ selectBtn.classList.toggle("on", vm.selecting);
207
+ const cur = vm.drafts[vm.drafts.length - 1];
208
+ const hasElements = !!cur && cur.elements.length > 0;
209
+ const suffix = vm.strategy ? ` \xB7 ${T.refresh}: ${vm.strategy}` : "";
210
+ let hint;
211
+ if (!vm.connected) hint = T.disconnected;
212
+ else if (vm.agent.status === "sent" || vm.agent.status === "working" || vm.agent.status === "done") hint = AGENT_TEXT[vm.agent.status](vm.agent.text) + suffix;
213
+ else if (hasElements && cur.note.trim() !== "") hint = T.hintSend + suffix;
214
+ else if (vm.selecting && !hasElements) hint = T.hintClick + suffix;
215
+ else if (vm.selecting) hint = T.hintMore(cur.elements.length) + suffix;
216
+ else if (hasElements) hint = T.hintNote + suffix;
217
+ else hint = T.hintPick + suffix;
218
+ if (hint !== lastHint) {
219
+ lastHint = hint;
220
+ if (!statusIn.classList.contains("scroll")) {
221
+ statusIn.classList.remove("enter");
222
+ void statusIn.offsetWidth;
223
+ statusIn.classList.add("enter");
224
+ }
225
+ }
226
+ statusIn.textContent = hint;
227
+ statusIn.title = hint;
228
+ if (hovering) {
229
+ applyHoverScroll();
230
+ if (statusIn.classList.contains("scroll")) statusIn.classList.remove("enter");
231
+ }
232
+ status.classList.toggle("off", !vm.connected);
233
+ dot.className = vm.connected ? "dot " + vm.agent.status : "dot";
234
+ dot.title = vm.connected ? DOT_TITLE[vm.agent.status] : T.disconnected;
235
+ const show = !collapsed && vm.drafts.length > 0;
236
+ panel.classList.toggle("show", show);
237
+ if (vm.drafts.length === 0) {
238
+ panel.textContent = "";
239
+ textareas.clear();
240
+ return;
241
+ }
242
+ if (!show) return;
243
+ panel.textContent = "";
244
+ if (cur) {
245
+ const h4 = el("h4");
246
+ h4.append(el("span", "mark", "\u25AE"), document.createTextNode(cur.elements.length > 0 ? T.selCount(cur.elements.length) : T.selNone));
247
+ panel.append(h4);
248
+ const list = el("div", "els");
249
+ cur.elements.forEach((e, i) => {
250
+ const row2 = el("div", e.missing ? "missing" : "");
251
+ row2.append(el("span", "", `${i + 1}. ${e.selector.split(" > ").pop()}${e.react ? " \xB7 " + e.react.component : ""}${e.missing ? " \xB7 " + T.elMissing : ""}`));
252
+ const x = el("button", "", "\u2715");
253
+ x.title = T.tipRemove;
254
+ x.onclick = () => h.onRemoveElement(cur.id, i);
255
+ row2.append(x);
256
+ list.append(row2);
257
+ });
258
+ panel.append(list);
259
+ let ta = textareas.get(cur.id);
260
+ if (!ta) {
261
+ ta = document.createElement("textarea");
262
+ ta.placeholder = T.notePlaceholder;
263
+ const id = cur.id;
264
+ ta.addEventListener("input", () => h.onNoteInput(id, ta.value));
265
+ textareas.set(id, ta);
266
+ }
267
+ if (ta.value !== cur.note) ta.value = cur.note;
268
+ panel.append(ta);
269
+ }
270
+ const row = el("div", "row");
271
+ const send = el("button", "send", "Send");
272
+ send.disabled = vm.locked;
273
+ send.title = vm.locked ? T.tipSendLocked : T.tipSend;
274
+ send.onclick = () => h.onSend();
275
+ row.append(send);
276
+ panel.append(row);
277
+ for (const id of [...textareas.keys()]) if (!vm.drafts.some((b) => b.id === id)) textareas.delete(id);
278
+ if (wasTa && sel && wasTa.isConnected) {
279
+ wasTa.focus();
280
+ wasTa.setSelectionRange(sel[0], sel[1]);
281
+ }
282
+ }
283
+ function flash(selectors) {
284
+ for (const s of selectors) {
285
+ let target = null;
286
+ try {
287
+ target = document.querySelector(s);
288
+ } catch {
289
+ }
290
+ if (!target) continue;
291
+ const r = target.getBoundingClientRect();
292
+ const f = el("div", "flash");
293
+ Object.assign(f.style, { left: r.left - 2 + "px", top: r.top - 2 + "px", width: r.width + 4 + "px", height: r.height + 4 + "px" });
294
+ root.append(f);
295
+ setTimeout(() => f.remove(), 1700);
296
+ }
297
+ }
298
+ function focusNote() {
299
+ const ta = panel.querySelector("textarea");
300
+ ta?.focus();
301
+ }
302
+ return { host, root, render, flash, focusNote };
303
+ }
304
+
305
+ // src/overlay/picker.ts
306
+ function createPicker(opts) {
307
+ const { root, host } = opts;
308
+ const glass = document.createElement("div");
309
+ glass.className = "glass";
310
+ const box = document.createElement("div");
311
+ box.className = "hover-box";
312
+ const badge = document.createElement("div");
313
+ badge.className = "hover-badge";
314
+ const band = document.createElement("div");
315
+ band.className = "band";
316
+ root.append(glass, box, badge, band);
317
+ let active = false;
318
+ let dragStart = null;
319
+ let dragging = false;
320
+ let suppressClick = false;
321
+ let target = null;
322
+ const notOurs = (el) => el !== host && !host.contains(el) && el !== document.documentElement && el !== document.body;
323
+ const pick = (x, y) => document.elementsFromPoint(x, y).find(notOurs) ?? null;
324
+ const rectOf = (e) => ({ left: Math.min(dragStart.x, e.clientX), top: Math.min(dragStart.y, e.clientY), right: Math.max(dragStart.x, e.clientX), bottom: Math.max(dragStart.y, e.clientY) });
325
+ const hide = () => {
326
+ box.style.display = badge.style.display = "none";
327
+ target = null;
328
+ };
329
+ const show = (el, x, y, label) => {
330
+ const r = el.getBoundingClientRect();
331
+ Object.assign(box.style, { display: "block", left: r.left + "px", top: r.top + "px", width: r.width + "px", height: r.height + "px" });
332
+ badge.textContent = `${label}
333
+ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
334
+ badge.style.display = "block";
335
+ const bw = badge.offsetWidth, bh = badge.offsetHeight;
336
+ badge.style.left = Math.min(x + 14, innerWidth - bw - 8) + "px";
337
+ badge.style.top = (y + 18 + bh > innerHeight ? y - bh - 8 : y + 18) + "px";
338
+ };
339
+ const labelOf = (el) => el.id ? "#" + el.id : el.tagName.toLowerCase() + [...el.classList].slice(0, 2).map((c) => "." + c).join("");
340
+ const inBand = (b) => {
341
+ const within = /* @__PURE__ */ new Set();
342
+ for (const el of document.body.querySelectorAll("*")) {
343
+ if (!notOurs(el)) continue;
344
+ const r = el.getBoundingClientRect();
345
+ if (r.width && r.height && r.left >= b.left && r.right <= b.right && r.top >= b.top && r.bottom <= b.bottom) within.add(el);
346
+ }
347
+ return [...within].filter((el) => !(el.parentElement && within.has(el.parentElement)));
348
+ };
349
+ glass.addEventListener("mousedown", (e) => {
350
+ if (e.button === 0) {
351
+ dragStart = { x: e.clientX, y: e.clientY };
352
+ dragging = false;
353
+ }
354
+ });
355
+ glass.addEventListener("mousemove", (e) => {
356
+ if (dragStart && (dragging || Math.hypot(e.clientX - dragStart.x, e.clientY - dragStart.y) > 6)) {
357
+ dragging = true;
358
+ hide();
359
+ const b = rectOf(e);
360
+ Object.assign(band.style, { display: "block", left: b.left + "px", top: b.top + "px", width: b.right - b.left + "px", height: b.bottom - b.top + "px" });
361
+ e.preventDefault();
362
+ return;
363
+ }
364
+ const el = pick(e.clientX, e.clientY);
365
+ if (!el) return hide();
366
+ target = el;
367
+ show(el, e.clientX, e.clientY, labelOf(el));
368
+ });
369
+ glass.addEventListener("mouseup", (e) => {
370
+ if (!dragging) {
371
+ dragStart = null;
372
+ return;
373
+ }
374
+ e.preventDefault();
375
+ opts.onBandPick(inBand(rectOf(e)));
376
+ band.style.display = "none";
377
+ dragStart = null;
378
+ dragging = false;
379
+ suppressClick = true;
380
+ });
381
+ glass.addEventListener("click", (e) => {
382
+ e.preventDefault();
383
+ if (suppressClick) {
384
+ suppressClick = false;
385
+ return;
386
+ }
387
+ if (target) opts.onPick(target);
388
+ });
389
+ glass.addEventListener("mouseleave", hide);
390
+ glass.addEventListener("wheel", (e) => {
391
+ e.preventDefault();
392
+ for (let cur = pick(e.clientX, e.clientY); cur && cur !== document.body; cur = cur.parentElement) {
393
+ const cs = getComputedStyle(cur);
394
+ if (/(auto|scroll)/.test(cs.overflowY) && cur.scrollHeight > cur.clientHeight) {
395
+ cur.scrollBy(e.deltaX, e.deltaY);
396
+ return;
397
+ }
398
+ }
399
+ window.scrollBy(e.deltaX, e.deltaY);
400
+ }, { passive: false });
401
+ const setActive = (on) => {
402
+ active = on;
403
+ suppressClick = false;
404
+ glass.style.display = on ? "block" : "none";
405
+ if (!on) {
406
+ hide();
407
+ band.style.display = "none";
408
+ dragStart = null;
409
+ dragging = false;
410
+ }
411
+ };
412
+ setActive(false);
413
+ return { setActive, isActive: () => active, destroy: () => {
414
+ glass.remove();
415
+ box.remove();
416
+ badge.remove();
417
+ band.remove();
418
+ } };
419
+ }
420
+
421
+ // src/overlay/guard.ts
422
+ var BUBBLE_TYPES = [
423
+ "pointerdown",
424
+ "pointerup",
425
+ "pointermove",
426
+ "mousedown",
427
+ "mouseup",
428
+ "mousemove",
429
+ "click",
430
+ "dblclick",
431
+ "contextmenu",
432
+ "wheel",
433
+ "touchstart",
434
+ "touchend",
435
+ "touchmove",
436
+ "keydown",
437
+ "keyup",
438
+ "keypress",
439
+ "focusin",
440
+ "focusout",
441
+ "input",
442
+ "change"
443
+ ];
444
+ var FOCUS_TYPES = ["focus", "blur", "focusin", "focusout"];
445
+ function installGuards(host) {
446
+ const isOurs = (e) => e.composedPath().includes(host);
447
+ const bubbleStop = (e) => {
448
+ e.stopPropagation();
449
+ };
450
+ const captureStop = (e) => {
451
+ if (isOurs(e) || e instanceof FocusEvent && e.relatedTarget instanceof Node && host.contains(e.relatedTarget)) e.stopImmediatePropagation();
452
+ };
453
+ for (const t of BUBBLE_TYPES) host.addEventListener(t, bubbleStop);
454
+ for (const t of FOCUS_TYPES) window.addEventListener(t, captureStop, true);
455
+ return () => {
456
+ for (const t of BUBBLE_TYPES) host.removeEventListener(t, bubbleStop);
457
+ for (const t of FOCUS_TYPES) window.removeEventListener(t, captureStop, true);
458
+ };
459
+ }
460
+
461
+ // src/overlay/channel.ts
462
+ var NativeWS = window.WebSocket;
463
+ var nativeSend = NativeWS.prototype.send;
464
+ function connectChannel(opts) {
465
+ let ws = null;
466
+ let delay = 500;
467
+ let closed = false;
468
+ const queue = [];
469
+ const raw = (m) => {
470
+ if (ws && ws.readyState === NativeWS.OPEN) nativeSend.call(ws, JSON.stringify(m));
471
+ };
472
+ const connect = () => {
473
+ if (closed) return;
474
+ const sock = new NativeWS(`ws://127.0.0.1:${opts.port}`);
475
+ ws = sock;
476
+ sock.onopen = () => {
477
+ delay = 500;
478
+ raw({ type: "hello", token: opts.token });
479
+ while (queue.length) raw(queue.shift());
480
+ opts.onOpen();
481
+ };
482
+ sock.onmessage = (ev) => {
483
+ try {
484
+ opts.onMessage(JSON.parse(String(ev.data)));
485
+ } catch {
486
+ }
487
+ };
488
+ sock.onclose = (ev) => {
489
+ ws = null;
490
+ opts.onClose();
491
+ if (closed || ev.code === 4001) return;
492
+ setTimeout(connect, delay);
493
+ delay = Math.min(delay * 2, 8e3);
494
+ };
495
+ sock.onerror = () => {
496
+ };
497
+ };
498
+ connect();
499
+ return {
500
+ send(m) {
501
+ if (ws && ws.readyState === NativeWS.OPEN) raw(m);
502
+ else if (m.type !== "hello") queue.push(m);
503
+ },
504
+ close() {
505
+ closed = true;
506
+ ws?.close();
507
+ }
508
+ };
509
+ }
510
+
511
+ // src/overlay/selector.ts
512
+ var esc = (s) => typeof CSS !== "undefined" && CSS.escape ? CSS.escape(s) : s.replace(/([^\w-])/g, "\\$1");
513
+ function seg(el) {
514
+ if (el.id) return "#" + esc(el.id);
515
+ const dt = el.getAttribute("data-testid");
516
+ if (dt) return `[data-testid="${dt.replace(/"/g, '\\"')}"]`;
517
+ let s = el.tagName.toLowerCase();
518
+ s += [...el.classList].slice(0, 2).map((c) => "." + esc(c)).join("");
519
+ const p = el.parentElement;
520
+ if (p) {
521
+ const sib = [...p.children].filter((c) => c.tagName === el.tagName);
522
+ if (sib.length > 1) s += `:nth-of-type(${sib.indexOf(el) + 1})`;
523
+ }
524
+ return s;
525
+ }
526
+ function uniqueSelector(el) {
527
+ const parts = [];
528
+ for (let cur = el; cur && cur !== document.body && cur !== document.documentElement && parts.length < 8; cur = cur.parentElement) {
529
+ const s = seg(cur);
530
+ parts.unshift(s);
531
+ if (s.startsWith("#") || s.startsWith("[data-testid")) break;
532
+ }
533
+ for (let i = parts.length - 1; i >= 0; i--) {
534
+ const cand = parts.slice(i).join(" > ");
535
+ try {
536
+ if (document.querySelectorAll(cand).length === 1) return cand;
537
+ } catch {
538
+ }
539
+ }
540
+ return parts.join(" > ");
541
+ }
542
+
543
+ // src/overlay/inspect.ts
544
+ var STYLE_KEYS = [
545
+ "display",
546
+ "position",
547
+ "width",
548
+ "height",
549
+ "padding",
550
+ "margin",
551
+ "gap",
552
+ "color",
553
+ "background-color",
554
+ "font-size",
555
+ "font-weight",
556
+ "border-radius"
557
+ ];
558
+ function reactInfo(el) {
559
+ const key = Object.keys(el).find((k) => k.startsWith("__reactFiber$"));
560
+ if (!key) return void 0;
561
+ let f = el[key] ?? null;
562
+ for (let i = 0; f && i < 30; i++, f = f.return ?? null) {
563
+ const t = f.type;
564
+ if (t && typeof t !== "string") {
565
+ const name = t.displayName || t.name;
566
+ if (name) {
567
+ const src = f._debugSource;
568
+ return src?.fileName ? { component: name, source: `${src.fileName}${src.lineNumber ? ":" + src.lineNumber : ""}` } : { component: name };
569
+ }
570
+ }
571
+ }
572
+ return void 0;
573
+ }
574
+ function inspectElement(el) {
575
+ const r = el.getBoundingClientRect();
576
+ const cs = getComputedStyle(el);
577
+ const styles = {};
578
+ for (const k of STYLE_KEYS) {
579
+ const v = cs.getPropertyValue(k);
580
+ if (v && (k === "display" || v !== "none" && v !== "normal")) styles[k] = v;
581
+ }
582
+ const info = {
583
+ selector: uniqueSelector(el),
584
+ tag: el.tagName.toLowerCase(),
585
+ classes: [...el.classList],
586
+ text: (el.textContent || "").trim().replace(/\s+/g, " ").slice(0, 40),
587
+ rect: { x: Math.round(r.left + window.scrollX), y: Math.round(r.top + window.scrollY), w: Math.round(r.width), h: Math.round(r.height) },
588
+ styles
589
+ };
590
+ const react = reactInfo(el);
591
+ if (react) info.react = react;
592
+ return info;
593
+ }
594
+
595
+ // src/overlay/refresh.ts
596
+ var DONE_EVENT = "cobro:done";
597
+ function detectStrategy() {
598
+ if (document.querySelector('script[src*="/@vite/client"]')) return "none";
599
+ if (Object.keys(window).some((k) => k.startsWith("webpackHotUpdate"))) return "none";
600
+ const next = window.__NEXT_DATA__;
601
+ if (next?.buildId === "development") return "none";
602
+ return "reload";
603
+ }
604
+ function applyDone(info, strategy, reload = () => location.reload()) {
605
+ window.dispatchEvent(new CustomEvent(DONE_EVENT, { detail: info }));
606
+ if (strategy === "reload") reload();
607
+ }
608
+
609
+ // src/overlay/index.ts
610
+ (() => {
611
+ if (window.top !== window) return;
612
+ const PORT = __COBRO_PORT__;
613
+ const TOKEN = __COBRO_TOKEN__;
614
+ const boot = () => {
615
+ if (document.documentElement.hasAttribute("data-cobro")) return;
616
+ document.documentElement.setAttribute("data-cobro", "1");
617
+ let session = null;
618
+ let drafts = null;
619
+ let current = null;
620
+ let connected = false;
621
+ let draftTimer = null;
622
+ const pageInfo = () => ({ url: location.href, title: document.title, viewport: { w: innerWidth, h: innerHeight } });
623
+ const newBatch = () => ({ id: crypto.randomUUID(), note: "", elements: [], status: "draft", createdAt: (/* @__PURE__ */ new Date()).toISOString() });
624
+ const ensureCurrent = () => {
625
+ drafts ??= [];
626
+ let b = drafts.find((d) => d.id === current);
627
+ if (!b) {
628
+ b = newBatch();
629
+ drafts.push(b);
630
+ current = b.id;
631
+ }
632
+ return b;
633
+ };
634
+ const flushDraft = () => {
635
+ if (draftTimer) {
636
+ clearTimeout(draftTimer);
637
+ draftTimer = null;
638
+ }
639
+ chan.send({ type: "draft", batches: drafts ?? [] });
640
+ };
641
+ const pushDraft = () => {
642
+ if (draftTimer) clearTimeout(draftTimer);
643
+ draftTimer = setTimeout(flushDraft, 300);
644
+ };
645
+ const vm = () => ({
646
+ selecting: picker.isActive(),
647
+ connected,
648
+ agent: session?.agent ?? { status: "idle", text: "" },
649
+ strategy: session ? session.strategy ?? session.detected : null,
650
+ drafts: drafts ?? [],
651
+ locked: session?.agent.status === "sent" || session?.agent.status === "working"
652
+ });
653
+ const render = () => ui.render(vm());
654
+ const addEl = (b, el, toggle) => {
655
+ const info = inspectElement(el);
656
+ const i = b.elements.findIndex((e) => e.selector === info.selector);
657
+ if (i >= 0) {
658
+ if (toggle) b.elements.splice(i, 1);
659
+ } else b.elements.push(info);
660
+ };
661
+ const resolveDraft = (b) => ({ ...b, status: "draft", elements: b.elements.map((e, i) => {
662
+ let found = null;
663
+ try {
664
+ found = document.querySelector(e.selector);
665
+ } catch {
666
+ }
667
+ const missing = !found;
668
+ if (missing !== !!e.missing) chan.send({ type: "resolved", batchId: b.id, index: i, missing });
669
+ return { ...e, missing };
670
+ }) });
671
+ const ui = createUI({
672
+ onToggleSelect: () => {
673
+ picker.setActive(!picker.isActive());
674
+ render();
675
+ },
676
+ onNoteInput: (id, note) => {
677
+ const b = drafts?.find((d) => d.id === id);
678
+ if (b) {
679
+ b.note = note;
680
+ pushDraft();
681
+ }
682
+ },
683
+ onRemoveElement: (id, i) => {
684
+ const b = drafts?.find((d) => d.id === id);
685
+ if (b) {
686
+ b.elements.splice(i, 1);
687
+ pushDraft();
688
+ render();
689
+ }
690
+ },
691
+ onSend: () => {
692
+ const ready = (drafts ?? []).filter((b) => b.elements.length && b.note.trim());
693
+ if (!ready.length) {
694
+ ui.focusNote();
695
+ return;
696
+ }
697
+ flushDraft();
698
+ chan.send({ type: "send", batchIds: ready.map((b) => b.id), page: pageInfo() });
699
+ drafts = (drafts ?? []).filter((b) => !ready.includes(b));
700
+ current = null;
701
+ picker.setActive(false);
702
+ render();
703
+ }
704
+ });
705
+ const picker = createPicker({
706
+ root: ui.root,
707
+ host: ui.host,
708
+ onPick: (el) => {
709
+ addEl(ensureCurrent(), el, true);
710
+ pushDraft();
711
+ render();
712
+ ui.focusNote();
713
+ },
714
+ onBandPick: (els) => {
715
+ const b = ensureCurrent();
716
+ for (const el of els) addEl(b, el, false);
717
+ pushDraft();
718
+ render();
719
+ }
720
+ });
721
+ window.addEventListener("keydown", (e) => {
722
+ if (e.ctrlKey && e.shiftKey && e.code === "KeyF") {
723
+ e.preventDefault();
724
+ picker.setActive(!picker.isActive());
725
+ render();
726
+ } else if (e.key === "Escape" && picker.isActive()) {
727
+ picker.setActive(false);
728
+ render();
729
+ }
730
+ }, true);
731
+ installGuards(ui.host);
732
+ const onMessage = (m) => {
733
+ if (m.type === "state") {
734
+ session = m.session;
735
+ const nonDraft = new Set(m.session.batches.filter((b) => b.status !== "draft").map((b) => b.id));
736
+ const serverDrafts = m.session.batches.filter((b) => b.status === "draft");
737
+ if (drafts === null) {
738
+ drafts = serverDrafts.map(resolveDraft);
739
+ current = drafts[drafts.length - 1]?.id ?? null;
740
+ } else {
741
+ drafts = drafts.filter((d) => !nonDraft.has(d.id));
742
+ }
743
+ render();
744
+ } else if (m.type === "done") {
745
+ ui.flash(m.info.selectors);
746
+ flushDraft();
747
+ applyDone(m.info, m.strategy);
748
+ }
749
+ };
750
+ const chan = connectChannel({
751
+ port: PORT,
752
+ token: TOKEN,
753
+ onMessage,
754
+ onOpen: () => {
755
+ connected = true;
756
+ chan.send({ type: "page", page: pageInfo(), detected: detectStrategy() });
757
+ render();
758
+ },
759
+ onClose: () => {
760
+ connected = false;
761
+ render();
762
+ }
763
+ });
764
+ render();
765
+ };
766
+ if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot);
767
+ else boot();
768
+ })();
769
+ })();