@jami-studio/pinpoint 0.1.12

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 (47) hide show
  1. package/.agents/skills/pinpoint/SKILL.md +77 -0
  2. package/README.md +412 -0
  3. package/dist/agent-context-HBCZ4MBN.js +13 -0
  4. package/dist/agent-context-HBCZ4MBN.js.map +1 -0
  5. package/dist/chunk-B7TY4FPP.js +569 -0
  6. package/dist/chunk-B7TY4FPP.js.map +1 -0
  7. package/dist/chunk-CGJZ7BOM.js +88 -0
  8. package/dist/chunk-CGJZ7BOM.js.map +1 -0
  9. package/dist/chunk-DGUM43GV.js +11 -0
  10. package/dist/chunk-DGUM43GV.js.map +1 -0
  11. package/dist/chunk-HAEYE6KB.js +25 -0
  12. package/dist/chunk-HAEYE6KB.js.map +1 -0
  13. package/dist/chunk-J5PXKVTM.js +53 -0
  14. package/dist/chunk-J5PXKVTM.js.map +1 -0
  15. package/dist/chunk-XBX2J7WU.js +94 -0
  16. package/dist/chunk-XBX2J7WU.js.map +1 -0
  17. package/dist/chunk-ZW7IO3EW.js +4927 -0
  18. package/dist/chunk-ZW7IO3EW.js.map +1 -0
  19. package/dist/cli.js +71 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/formatter-CR4COA5Z.js +8 -0
  22. package/dist/formatter-CR4COA5Z.js.map +1 -0
  23. package/dist/index-NvFcZ4SO.d.ts +200 -0
  24. package/dist/index.browser.d.ts +377 -0
  25. package/dist/index.browser.js +620 -0
  26. package/dist/index.browser.js.map +1 -0
  27. package/dist/index.js +936 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/open-file-WR2JHI4P.js +8 -0
  30. package/dist/open-file-WR2JHI4P.js.map +1 -0
  31. package/dist/primitives/index.js +28 -0
  32. package/dist/primitives/index.js.map +1 -0
  33. package/dist/react.d.ts +23 -0
  34. package/dist/react.js +20 -0
  35. package/dist/react.js.map +1 -0
  36. package/dist/server/index.js +457 -0
  37. package/dist/server/index.js.map +1 -0
  38. package/dist/types/index.js +1 -0
  39. package/dist/types/index.js.map +1 -0
  40. package/package.json +88 -0
  41. package/src/scripts/create-pin.ts +43 -0
  42. package/src/scripts/delete-pin.ts +16 -0
  43. package/src/scripts/get-pins.ts +36 -0
  44. package/src/scripts/list-sessions.ts +33 -0
  45. package/src/scripts/resolve-pin.ts +27 -0
  46. package/src/scripts/run.ts +8 -0
  47. package/src/scripts/update-pin.ts +32 -0
package/dist/index.js ADDED
@@ -0,0 +1,936 @@
1
+ import {
2
+ ElementInfoSchema,
3
+ FrameworkInfoSchema,
4
+ PinSchema
5
+ } from "./chunk-J5PXKVTM.js";
6
+ import {
7
+ MemoryStore,
8
+ buildElementContext,
9
+ buildSelector,
10
+ detectFramework,
11
+ extractElementInfo,
12
+ freeze,
13
+ getComponentInfo,
14
+ getSourceLocation,
15
+ isFreezeActive,
16
+ openFile,
17
+ registerAdapter,
18
+ unfreeze
19
+ } from "./chunk-B7TY4FPP.js";
20
+ import "./chunk-DGUM43GV.js";
21
+
22
+ // src/storage/rest-client.ts
23
+ var RestClient = class {
24
+ constructor(endpoint) {
25
+ this.endpoint = endpoint;
26
+ this.endpoint = endpoint.replace(/\/+$/, "");
27
+ }
28
+ endpoint;
29
+ async load(pageUrl) {
30
+ const params = new URLSearchParams({ pageUrl });
31
+ const res = await fetch(`${this.endpoint}?${params}`);
32
+ if (!res.ok) return [];
33
+ const data = await res.json();
34
+ return Array.isArray(data) ? data.filter((item) => PinSchema.safeParse(item).success) : [];
35
+ }
36
+ async save(pin) {
37
+ const res = await fetch(this.endpoint, {
38
+ method: "POST",
39
+ headers: { "Content-Type": "application/json" },
40
+ body: JSON.stringify(pin)
41
+ });
42
+ if (!res.ok) throw new Error(`Failed to save pin: ${res.status}`);
43
+ }
44
+ async update(id, patch) {
45
+ const res = await fetch(`${this.endpoint}/${encodeURIComponent(id)}`, {
46
+ method: "PATCH",
47
+ headers: { "Content-Type": "application/json" },
48
+ body: JSON.stringify(patch)
49
+ });
50
+ if (!res.ok) throw new Error(`Failed to update pin: ${res.status}`);
51
+ }
52
+ async delete(id) {
53
+ const res = await fetch(`${this.endpoint}/${encodeURIComponent(id)}`, {
54
+ method: "DELETE"
55
+ });
56
+ if (!res.ok) throw new Error(`Failed to delete pin: ${res.status}`);
57
+ }
58
+ async list(filter) {
59
+ const params = new URLSearchParams();
60
+ if (filter?.pageUrl) params.set("pageUrl", filter.pageUrl);
61
+ if (filter?.status) params.set("status", filter.status);
62
+ const res = await fetch(`${this.endpoint}?${params}`);
63
+ if (!res.ok) return [];
64
+ const data = await res.json();
65
+ return Array.isArray(data) ? data.filter((item) => PinSchema.safeParse(item).success) : [];
66
+ }
67
+ async clear(pageUrl) {
68
+ const params = new URLSearchParams();
69
+ if (pageUrl) params.set("pageUrl", pageUrl);
70
+ const res = await fetch(`${this.endpoint}?${params}`, { method: "DELETE" });
71
+ if (!res.ok) throw new Error(`Failed to clear pins: ${res.status}`);
72
+ }
73
+ };
74
+
75
+ // src/detection/element-picker.ts
76
+ var ElementPicker = class {
77
+ active = false;
78
+ paused = false;
79
+ hoveredElement = null;
80
+ rafId = null;
81
+ stableTimeout = null;
82
+ lastTarget = null;
83
+ options;
84
+ handleMouseMove;
85
+ handleClick;
86
+ handleKeyDown;
87
+ constructor(options = {}) {
88
+ this.options = options;
89
+ this.handleMouseMove = (e) => {
90
+ if (!this.active || this.paused) return;
91
+ if (this.isOwnUI(e)) return;
92
+ if (this.rafId !== null) return;
93
+ this.rafId = requestAnimationFrame(() => {
94
+ this.rafId = null;
95
+ this.processHover(e.clientX, e.clientY);
96
+ });
97
+ };
98
+ this.handleClick = (e) => {
99
+ if (!this.active || this.paused) return;
100
+ if (this.isOwnUI(e)) return;
101
+ e.preventDefault();
102
+ e.stopPropagation();
103
+ e.stopImmediatePropagation();
104
+ const target = this.hoveredElement;
105
+ if (target && !this.shouldIgnore(target)) {
106
+ this.options.onSelect?.(target);
107
+ }
108
+ };
109
+ this.handleKeyDown = (e) => {
110
+ if (!this.active) return;
111
+ if (e.key === "Escape") {
112
+ this.deactivate();
113
+ }
114
+ };
115
+ }
116
+ /**
117
+ * Check if an event originates from Pinpoint's own UI.
118
+ * Uses composedPath() to cross Shadow DOM boundaries.
119
+ */
120
+ isOwnUI(e) {
121
+ const path = e.composedPath();
122
+ for (const node of path) {
123
+ if (node instanceof HTMLElement) {
124
+ if (node.id === "pinpoint-root") return true;
125
+ if (node.hasAttribute?.("data-pinpoint-marker")) return true;
126
+ }
127
+ }
128
+ return false;
129
+ }
130
+ shouldIgnore(element) {
131
+ const root = element.getRootNode();
132
+ if (root instanceof ShadowRoot) {
133
+ const host = root.host;
134
+ if (host.id === "pinpoint-root") return true;
135
+ }
136
+ if (element.hasAttribute("data-pinpoint-marker")) return true;
137
+ if (element.closest?.("[data-pinpoint-marker]")) return true;
138
+ if (!this.options.ignoreSelector) return false;
139
+ return element.closest(this.options.ignoreSelector) !== null || element.matches(this.options.ignoreSelector);
140
+ }
141
+ pierceElementFromPoint(x, y) {
142
+ let element = document.elementFromPoint(x, y);
143
+ if (!element) return null;
144
+ while (element?.shadowRoot) {
145
+ const inner = element.shadowRoot.elementFromPoint(x, y);
146
+ if (!inner || inner === element) break;
147
+ element = inner;
148
+ }
149
+ return element;
150
+ }
151
+ processHover(x, y) {
152
+ const element = this.pierceElementFromPoint(x, y);
153
+ if (!element || this.shouldIgnore(element)) {
154
+ if (this.hoveredElement) {
155
+ this.hoveredElement = null;
156
+ this.lastTarget = null;
157
+ this.clearStableTimeout();
158
+ this.options.onHover?.(null, null);
159
+ }
160
+ return;
161
+ }
162
+ if (element === this.lastTarget) return;
163
+ this.lastTarget = element;
164
+ this.hoveredElement = element;
165
+ const rect = element.getBoundingClientRect();
166
+ this.options.onHover?.(element, rect);
167
+ this.clearStableTimeout();
168
+ this.stableTimeout = setTimeout(() => {
169
+ if (this.hoveredElement === element) {
170
+ this.options.onStableHover?.(element);
171
+ }
172
+ }, 100);
173
+ }
174
+ clearStableTimeout() {
175
+ if (this.stableTimeout !== null) {
176
+ clearTimeout(this.stableTimeout);
177
+ this.stableTimeout = null;
178
+ }
179
+ }
180
+ activate() {
181
+ if (this.active) return;
182
+ this.active = true;
183
+ document.addEventListener("mousemove", this.handleMouseMove, true);
184
+ document.addEventListener("click", this.handleClick, true);
185
+ document.addEventListener("keydown", this.handleKeyDown, true);
186
+ if (this.options.blockInteractions) {
187
+ document.body.style.pointerEvents = "none";
188
+ const overlay = document.getElementById("pinpoint-root");
189
+ if (overlay) overlay.style.pointerEvents = "auto";
190
+ }
191
+ }
192
+ deactivate() {
193
+ if (!this.active) return;
194
+ this.active = false;
195
+ document.removeEventListener("mousemove", this.handleMouseMove, true);
196
+ document.removeEventListener("click", this.handleClick, true);
197
+ document.removeEventListener("keydown", this.handleKeyDown, true);
198
+ if (this.rafId !== null) {
199
+ cancelAnimationFrame(this.rafId);
200
+ this.rafId = null;
201
+ }
202
+ this.clearStableTimeout();
203
+ this.hoveredElement = null;
204
+ this.lastTarget = null;
205
+ if (this.options.blockInteractions) {
206
+ document.body.style.pointerEvents = "";
207
+ }
208
+ this.options.onHover?.(null, null);
209
+ }
210
+ /** Update blockInteractions at runtime (called from settings toggle) */
211
+ setBlockInteractions(value) {
212
+ const wasBlocking = this.options.blockInteractions;
213
+ this.options.blockInteractions = value;
214
+ if (this.active) {
215
+ if (value && !wasBlocking) {
216
+ document.body.style.pointerEvents = "none";
217
+ const overlay = document.getElementById("pinpoint-root");
218
+ if (overlay) overlay.style.pointerEvents = "auto";
219
+ } else if (!value && wasBlocking) {
220
+ document.body.style.pointerEvents = "";
221
+ }
222
+ }
223
+ }
224
+ /** Pause picking without removing listeners (e.g., while popup is open) */
225
+ pause() {
226
+ this.paused = true;
227
+ this.hoveredElement = null;
228
+ this.lastTarget = null;
229
+ this.clearStableTimeout();
230
+ this.options.onHover?.(null, null);
231
+ }
232
+ /** Resume picking after pause */
233
+ resume() {
234
+ this.paused = false;
235
+ }
236
+ isPaused() {
237
+ return this.paused;
238
+ }
239
+ isActive() {
240
+ return this.active;
241
+ }
242
+ /** Get the currently hovered element */
243
+ getHoveredElement() {
244
+ return this.hoveredElement;
245
+ }
246
+ dispose() {
247
+ this.deactivate();
248
+ }
249
+ };
250
+
251
+ // src/detection/drag-select.ts
252
+ var DragSelect = class {
253
+ active = false;
254
+ dragging = false;
255
+ startX = 0;
256
+ startY = 0;
257
+ options;
258
+ handleMouseDown;
259
+ handleMouseMove;
260
+ handleMouseUp;
261
+ constructor(options = {}) {
262
+ this.options = { coverageThreshold: 0.75, ...options };
263
+ this.handleMouseDown = (e) => {
264
+ if (!this.active || e.button !== 0) return;
265
+ if (!e.shiftKey) return;
266
+ e.preventDefault();
267
+ this.dragging = true;
268
+ this.startX = e.clientX;
269
+ this.startY = e.clientY;
270
+ const rect = this.buildRect(e.clientX, e.clientY);
271
+ this.options.onDragStart?.(
272
+ new DOMRect(rect.x, rect.y, rect.width, rect.height)
273
+ );
274
+ };
275
+ this.handleMouseMove = (e) => {
276
+ if (!this.dragging) return;
277
+ e.preventDefault();
278
+ const rect = this.buildRect(e.clientX, e.clientY);
279
+ this.options.onDragMove?.(
280
+ new DOMRect(rect.x, rect.y, rect.width, rect.height)
281
+ );
282
+ };
283
+ this.handleMouseUp = (e) => {
284
+ if (!this.dragging) return;
285
+ this.dragging = false;
286
+ const selectionRect = this.buildRect(e.clientX, e.clientY);
287
+ if (selectionRect.width < 5 && selectionRect.height < 5) return;
288
+ const selected = this.getElementsInRect(selectionRect);
289
+ this.options.onDragEnd?.(selected);
290
+ };
291
+ }
292
+ buildRect(currentX, currentY) {
293
+ return {
294
+ x: Math.min(this.startX, currentX),
295
+ y: Math.min(this.startY, currentY),
296
+ width: Math.abs(currentX - this.startX),
297
+ height: Math.abs(currentY - this.startY)
298
+ };
299
+ }
300
+ getElementsInRect(selectionRect) {
301
+ const threshold = this.options.coverageThreshold;
302
+ const elements = [];
303
+ const walker = document.createTreeWalker(
304
+ document.body,
305
+ NodeFilter.SHOW_ELEMENT,
306
+ {
307
+ acceptNode: (node2) => {
308
+ const el = node2;
309
+ if (this.options.ignoreSelector && el.matches(this.options.ignoreSelector)) {
310
+ return NodeFilter.FILTER_REJECT;
311
+ }
312
+ return NodeFilter.FILTER_ACCEPT;
313
+ }
314
+ }
315
+ );
316
+ let node;
317
+ while (node = walker.nextNode()) {
318
+ const el = node;
319
+ const rect = el.getBoundingClientRect();
320
+ if (rect.width === 0 || rect.height === 0) continue;
321
+ const overlapX = Math.max(
322
+ 0,
323
+ Math.min(rect.right, selectionRect.x + selectionRect.width) - Math.max(rect.left, selectionRect.x)
324
+ );
325
+ const overlapY = Math.max(
326
+ 0,
327
+ Math.min(rect.bottom, selectionRect.y + selectionRect.height) - Math.max(rect.top, selectionRect.y)
328
+ );
329
+ const overlapArea = overlapX * overlapY;
330
+ const elementArea = rect.width * rect.height;
331
+ const coverage = overlapArea / elementArea;
332
+ if (coverage >= threshold) {
333
+ if (el.children.length === 0 || rect.width < 400) {
334
+ elements.push(el);
335
+ }
336
+ }
337
+ }
338
+ return elements;
339
+ }
340
+ activate() {
341
+ if (this.active) return;
342
+ this.active = true;
343
+ document.addEventListener("mousedown", this.handleMouseDown, true);
344
+ document.addEventListener("mousemove", this.handleMouseMove, true);
345
+ document.addEventListener("mouseup", this.handleMouseUp, true);
346
+ }
347
+ deactivate() {
348
+ if (!this.active) return;
349
+ this.active = false;
350
+ this.dragging = false;
351
+ document.removeEventListener("mousedown", this.handleMouseDown, true);
352
+ document.removeEventListener("mousemove", this.handleMouseMove, true);
353
+ document.removeEventListener("mouseup", this.handleMouseUp, true);
354
+ }
355
+ isDragging() {
356
+ return this.dragging;
357
+ }
358
+ dispose() {
359
+ this.deactivate();
360
+ }
361
+ };
362
+
363
+ // src/detection/text-select.ts
364
+ var TextSelect = class {
365
+ active = false;
366
+ options;
367
+ handleSelectionChange;
368
+ debounceTimer = null;
369
+ constructor(options = {}) {
370
+ this.options = { minLength: 3, ...options };
371
+ this.handleSelectionChange = () => {
372
+ if (!this.active) return;
373
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
374
+ this.debounceTimer = setTimeout(() => {
375
+ const selection = this.getTextSelection();
376
+ this.options.onSelect?.(selection);
377
+ }, 150);
378
+ };
379
+ }
380
+ getTextSelection() {
381
+ const selection = window.getSelection();
382
+ if (!selection || selection.isCollapsed || selection.rangeCount === 0) {
383
+ return null;
384
+ }
385
+ const text = selection.toString().trim();
386
+ if (text.length < (this.options.minLength ?? 3)) {
387
+ return null;
388
+ }
389
+ const range = selection.getRangeAt(0);
390
+ const container = range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE ? range.commonAncestorContainer : range.commonAncestorContainer.parentElement;
391
+ if (!container) return null;
392
+ const fullText = container.textContent || "";
393
+ const startOffset = range.startOffset;
394
+ const endOffset = range.endOffset;
395
+ const contextBefore = fullText.slice(
396
+ Math.max(0, startOffset - 50),
397
+ startOffset
398
+ );
399
+ const contextAfter = fullText.slice(endOffset, endOffset + 50);
400
+ const context = `...${contextBefore}[${text}]${contextAfter}...`;
401
+ const rect = range.getBoundingClientRect();
402
+ return {
403
+ text,
404
+ container,
405
+ startOffset,
406
+ endOffset,
407
+ context,
408
+ rect
409
+ };
410
+ }
411
+ activate() {
412
+ if (this.active) return;
413
+ this.active = true;
414
+ document.addEventListener("selectionchange", this.handleSelectionChange);
415
+ }
416
+ deactivate() {
417
+ if (!this.active) return;
418
+ this.active = false;
419
+ document.removeEventListener("selectionchange", this.handleSelectionChange);
420
+ if (this.debounceTimer) {
421
+ clearTimeout(this.debounceTimer);
422
+ this.debounceTimer = null;
423
+ }
424
+ }
425
+ /** Get the current text selection, if any */
426
+ getCurrentSelection() {
427
+ return this.getTextSelection();
428
+ }
429
+ dispose() {
430
+ this.deactivate();
431
+ }
432
+ };
433
+
434
+ // src/frameworks/react-adapter.ts
435
+ var bippy = null;
436
+ var elementSource = null;
437
+ async function loadBippy() {
438
+ if (!bippy) {
439
+ try {
440
+ bippy = await import("bippy");
441
+ } catch {
442
+ bippy = null;
443
+ }
444
+ }
445
+ return bippy;
446
+ }
447
+ async function loadElementSource() {
448
+ if (!elementSource) {
449
+ try {
450
+ elementSource = await import("element-source");
451
+ } catch {
452
+ elementSource = null;
453
+ }
454
+ }
455
+ return elementSource;
456
+ }
457
+ var FRAMEWORK_INTERNALS = /* @__PURE__ */ new Set([
458
+ "Fragment",
459
+ "Suspense",
460
+ "StrictMode",
461
+ "Profiler",
462
+ "Provider",
463
+ "Consumer",
464
+ "ForwardRef",
465
+ "Memo",
466
+ // Next.js internals
467
+ "InnerLayoutRouter",
468
+ "OuterLayoutRouter",
469
+ "RenderFromTemplateContext",
470
+ "ScrollAndFocusHandler",
471
+ "RedirectBoundary",
472
+ "NotFoundBoundary",
473
+ "LoadingBoundary",
474
+ "ErrorBoundary",
475
+ "HotReload",
476
+ "Router",
477
+ "ServerRoot",
478
+ "AppRouter",
479
+ "ServerInsertedHTMLProvider",
480
+ // React Router internals
481
+ "Routes",
482
+ "RenderedRoute",
483
+ "Navigate",
484
+ "Outlet"
485
+ ]);
486
+ function getRDTHook() {
487
+ return typeof window !== "undefined" ? window.__REACT_DEVTOOLS_GLOBAL_HOOK__ : null;
488
+ }
489
+ var reactAdapter = {
490
+ name: "react",
491
+ detect() {
492
+ return !!getRDTHook();
493
+ },
494
+ getComponentInfo(element) {
495
+ const b = bippy;
496
+ if (!b) return null;
497
+ try {
498
+ const fiber = b.getFiberFromHostInstance(element);
499
+ if (!fiber) return null;
500
+ const components = [];
501
+ let current = fiber;
502
+ while (current) {
503
+ const name = b.getDisplayName(current);
504
+ if (name && !FRAMEWORK_INTERNALS.has(name)) {
505
+ components.unshift(name);
506
+ }
507
+ current = current.return ?? null;
508
+ if (components.length > 20) break;
509
+ }
510
+ let componentFiber = fiber;
511
+ while (componentFiber && typeof componentFiber.type === "string") {
512
+ componentFiber = componentFiber.return ?? null;
513
+ }
514
+ const displayName = componentFiber ? b.getDisplayName(componentFiber) : null;
515
+ const sourceInfo = getSourceFromFiber(componentFiber);
516
+ return {
517
+ name: displayName || components[components.length - 1] || "Unknown",
518
+ displayName: displayName || void 0,
519
+ filePath: sourceInfo?.file,
520
+ lineNumber: sourceInfo?.line
521
+ };
522
+ } catch {
523
+ return null;
524
+ }
525
+ },
526
+ getSourceLocation(element) {
527
+ const b = bippy;
528
+ if (!b) return null;
529
+ try {
530
+ const fiber = b.getFiberFromHostInstance(element);
531
+ if (!fiber) return null;
532
+ let componentFiber = fiber;
533
+ while (componentFiber && typeof componentFiber.type === "string") {
534
+ componentFiber = componentFiber.return ?? null;
535
+ }
536
+ return getSourceFromFiber(componentFiber);
537
+ } catch {
538
+ return null;
539
+ }
540
+ },
541
+ freeze() {
542
+ },
543
+ unfreeze() {
544
+ }
545
+ };
546
+ function getSourceFromFiber(fiber) {
547
+ if (!fiber) return null;
548
+ if (fiber._debugSource) {
549
+ return {
550
+ file: fiber._debugSource.fileName,
551
+ line: fiber._debugSource.lineNumber,
552
+ column: fiber._debugSource.columnNumber
553
+ };
554
+ }
555
+ return null;
556
+ }
557
+ if (typeof window !== "undefined") {
558
+ const init = () => {
559
+ loadBippy();
560
+ loadElementSource();
561
+ };
562
+ if (typeof requestIdleCallback !== "undefined") {
563
+ requestIdleCallback(init);
564
+ } else {
565
+ setTimeout(init, 100);
566
+ }
567
+ }
568
+
569
+ // src/frameworks/vue-adapter.ts
570
+ var vueAdapter = {
571
+ name: "vue",
572
+ detect() {
573
+ if (typeof window === "undefined") return false;
574
+ if (window.__VUE__) return true;
575
+ if (document.querySelector("[data-v-]")) return true;
576
+ return !!document.querySelector("[__vue_app__]");
577
+ },
578
+ getComponentInfo(element) {
579
+ const instance = getVueInstance(element);
580
+ if (!instance) return null;
581
+ const name = getComponentName(instance);
582
+ return {
583
+ name: name || "Unknown",
584
+ displayName: name || void 0,
585
+ filePath: instance.$options?.__file || instance.type?.__file,
586
+ lineNumber: void 0
587
+ // Vue doesn't expose line numbers like React
588
+ };
589
+ },
590
+ getSourceLocation(element) {
591
+ const instance = getVueInstance(element);
592
+ if (!instance) return null;
593
+ const file = instance.$options?.__file || instance.type?.__file || instance.type?.__name;
594
+ if (!file) return null;
595
+ return { file };
596
+ }
597
+ };
598
+ function getVueInstance(element) {
599
+ const el = element;
600
+ if (el.__vueParentComponent) {
601
+ return el.__vueParentComponent;
602
+ }
603
+ let current = element;
604
+ while (current) {
605
+ if (current.__vueParentComponent) {
606
+ return current.__vueParentComponent;
607
+ }
608
+ if (current.__vue__) {
609
+ return current.__vue__;
610
+ }
611
+ current = current.parentElement;
612
+ }
613
+ return null;
614
+ }
615
+ function getComponentName(instance) {
616
+ if (!instance) return null;
617
+ if (instance.type?.name) return instance.type.name;
618
+ if (instance.type?.__name) return instance.type.__name;
619
+ if (instance.$options?.name) return instance.$options.name;
620
+ const file = instance.type?.__file || instance.$options?.__file;
621
+ if (file) {
622
+ const match = file.match(/([^/\\]+)\.\w+$/);
623
+ if (match) return match[1];
624
+ }
625
+ return null;
626
+ }
627
+
628
+ // src/frameworks/generic-adapter.ts
629
+ var genericAdapter = {
630
+ name: "generic",
631
+ detect() {
632
+ return true;
633
+ },
634
+ getComponentInfo(_element) {
635
+ return null;
636
+ },
637
+ getSourceLocation(_element) {
638
+ return null;
639
+ }
640
+ };
641
+
642
+ // src/output/formatter.ts
643
+ function formatPins(pins, format = "standard") {
644
+ if (pins.length === 0) return "No annotations.";
645
+ const pageUrl = pins[0].pageUrl;
646
+ switch (format) {
647
+ case "compact":
648
+ return formatCompact(pins, pageUrl);
649
+ case "standard":
650
+ return formatStandard(pins, pageUrl);
651
+ case "detailed":
652
+ return formatDetailed(pins, pageUrl);
653
+ default:
654
+ return formatStandard(pins, pageUrl);
655
+ }
656
+ }
657
+ function formatCompact(pins, pageUrl) {
658
+ const lines = [`## Page Feedback: ${pageUrl} (${pins.length} annotations)`];
659
+ for (let i = 0; i < pins.length; i++) {
660
+ const pin = pins[i];
661
+ lines.push(`${i + 1}. \`${pin.element.selector}\` \u2014 "${pin.comment}"`);
662
+ }
663
+ return lines.join("\n");
664
+ }
665
+ function formatStandard(pins, pageUrl) {
666
+ const lines = [
667
+ `## Page Feedback: ${pageUrl}`,
668
+ `${pins.length} annotation${pins.length === 1 ? "" : "s"} | ${(/* @__PURE__ */ new Date()).toISOString()}`,
669
+ ""
670
+ ];
671
+ for (let i = 0; i < pins.length; i++) {
672
+ const pin = pins[i];
673
+ lines.push(`### ${i + 1}: \`${pin.element.selector}\``);
674
+ lines.push(`**Comment:** ${pin.comment}`);
675
+ lines.push(
676
+ `**Element:** \`<${pin.element.tagName}>\`${pin.element.classNames.length > 0 ? ` with classes \`.${pin.element.classNames.join(" .")}\`` : ""}`
677
+ );
678
+ if (pin.framework) {
679
+ lines.push(
680
+ `**Component:** ${pin.framework.componentPath} (${pin.framework.framework})`
681
+ );
682
+ if (pin.framework.sourceFile) {
683
+ lines.push(`**Source:** \`${pin.framework.sourceFile}\``);
684
+ }
685
+ }
686
+ const rect = pin.element.boundingRect;
687
+ lines.push(
688
+ `**Position:** (${rect.x}, ${rect.y}) ${rect.width}x${rect.height}`
689
+ );
690
+ lines.push(`**Status:** ${pin.status.state}`);
691
+ if (pin.author) {
692
+ lines.push(`**Author:** ${pin.author}`);
693
+ }
694
+ lines.push("");
695
+ }
696
+ return lines.join("\n");
697
+ }
698
+ function formatDetailed(pins, pageUrl) {
699
+ const lines = [formatStandard(pins, pageUrl)];
700
+ for (let i = 0; i < pins.length; i++) {
701
+ const pin = pins[i];
702
+ const detailLines = [];
703
+ if (pin.element.domPath) {
704
+ detailLines.push(`**DOM Path:** ${pin.element.domPath}`);
705
+ }
706
+ if (pin.element.computedStyles && Object.keys(pin.element.computedStyles).length > 0) {
707
+ const styles = Object.entries(pin.element.computedStyles).map(([key, value]) => `${key}: ${value}`).join(", ");
708
+ detailLines.push(`**Computed Styles:** ${styles}`);
709
+ }
710
+ if (pin.element.ariaAttributes && Object.keys(pin.element.ariaAttributes).length > 0) {
711
+ const aria = Object.entries(pin.element.ariaAttributes).map(([key, value]) => `${key}="${value}"`).join(", ");
712
+ detailLines.push(`**Accessibility:** ${aria}`);
713
+ }
714
+ if (pin.element.textContent) {
715
+ detailLines.push(`**Text Content:** "${pin.element.textContent}"`);
716
+ }
717
+ if (pin.element.dataAttributes && Object.keys(pin.element.dataAttributes).length > 0) {
718
+ const data = Object.entries(pin.element.dataAttributes).map(([key, value]) => `${key}="${value}"`).join(", ");
719
+ detailLines.push(`**Data Attributes:** ${data}`);
720
+ }
721
+ if (detailLines.length > 0) {
722
+ lines.push(`#### Details for #${i + 1}:`);
723
+ lines.push(detailLines.join("\n"));
724
+ lines.push("");
725
+ }
726
+ }
727
+ return lines.join("\n");
728
+ }
729
+
730
+ // src/output/agent-context.ts
731
+ function formatRichPinContext(pin) {
732
+ const lines = [];
733
+ const tagName = pin.element.tagName.toLowerCase();
734
+ const classes = pin.element.classNames.length > 0 ? ` class="${pin.element.classNames.join(" ")}"` : "";
735
+ const component = pin.framework?.componentPath ? ` in ${pin.framework.componentPath} component` : "";
736
+ lines.push(`[Annotation on <${tagName}${classes}>${component}]`);
737
+ lines.push(`Comment: "${pin.comment}"`);
738
+ const rect = pin.element.boundingRect;
739
+ const classStr = pin.element.classNames.length > 0 ? `.${pin.element.classNames.join(".")}` : "";
740
+ lines.push(
741
+ `Element: ${tagName}${classStr} at (${Math.round(rect.x)}, ${Math.round(rect.y)})`
742
+ );
743
+ if (pin.framework?.sourceFile) {
744
+ lines.push(`Source: ${pin.framework.sourceFile}`);
745
+ }
746
+ if (pin.element.textContent) {
747
+ const truncated = pin.element.textContent.slice(0, 80);
748
+ lines.push(
749
+ `Text: "${truncated}${pin.element.textContent.length > 80 ? "..." : ""}"`
750
+ );
751
+ }
752
+ return lines.join("\n");
753
+ }
754
+ function formatPinsForAgent(pins, format = "standard") {
755
+ if (pins.length === 0) {
756
+ return { message: "No annotations to send.", context: "" };
757
+ }
758
+ const richAnnotations = pins.map((pin) => formatRichPinContext(pin)).join("\n\n");
759
+ const instruction = `The user has annotated ${pins.length} element${pins.length === 1 ? "" : "s"} on the page with visual feedback. Review each annotation and make the requested changes.
760
+
761
+ `;
762
+ const details = formatPins(pins, format);
763
+ const message = instruction + richAnnotations;
764
+ return { message, context: details };
765
+ }
766
+ function formatQueueForAgent(queue, format = "standard") {
767
+ if (queue.length === 0) {
768
+ return { message: "No queued annotations to send.", context: "" };
769
+ }
770
+ const parts = [];
771
+ const pins = [];
772
+ parts.push(
773
+ `The user has queued ${queue.length} annotation${queue.length === 1 ? "" : "s"} for batch review. Process each one:
774
+ `
775
+ );
776
+ for (let i = 0; i < queue.length; i++) {
777
+ const item = queue[i];
778
+ parts.push(`--- Item ${i + 1} ---`);
779
+ if (item.pin) {
780
+ parts.push(formatRichPinContext(item.pin));
781
+ pins.push(item.pin);
782
+ }
783
+ if (item.drawings && item.drawings.length > 0) {
784
+ parts.push(`[Drawing: ${item.drawings.length} stroke(s) on the page]`);
785
+ for (const stroke of item.drawings) {
786
+ const startPt = stroke.points[0];
787
+ const endPt = stroke.points[stroke.points.length - 1];
788
+ parts.push(
789
+ ` ${stroke.type} from (${Math.round(startPt.x)}, ${Math.round(startPt.y)}) to (${Math.round(endPt.x)}, ${Math.round(endPt.y)}) [${stroke.color}]`
790
+ );
791
+ }
792
+ }
793
+ if (item.textNotes && item.textNotes.length > 0) {
794
+ for (const note of item.textNotes) {
795
+ parts.push(
796
+ `[Text note at (${Math.round(note.x)}, ${Math.round(note.y)}): "${note.text}"]`
797
+ );
798
+ }
799
+ }
800
+ parts.push("");
801
+ }
802
+ const message = parts.join("\n");
803
+ const context = pins.length > 0 ? formatPins(pins, format) : "";
804
+ return { message, context };
805
+ }
806
+
807
+ // src/plugins/registry.ts
808
+ var plugins = /* @__PURE__ */ new Map();
809
+ var hookHandlers = /* @__PURE__ */ new Map();
810
+ function registerPlugin(plugin, api) {
811
+ if (plugins.has(plugin.name)) {
812
+ unregisterPlugin(plugin.name);
813
+ }
814
+ plugins.set(plugin.name, plugin);
815
+ if (plugin.hooks) {
816
+ for (const [hookName, handler] of Object.entries(plugin.hooks)) {
817
+ if (typeof handler === "function") {
818
+ const key = hookName;
819
+ if (!hookHandlers.has(key)) {
820
+ hookHandlers.set(key, /* @__PURE__ */ new Set());
821
+ }
822
+ hookHandlers.get(key).add(handler);
823
+ }
824
+ }
825
+ }
826
+ if (plugin.setup && api) {
827
+ const registry = {
828
+ register(hookName, handler) {
829
+ if (!hookHandlers.has(hookName)) {
830
+ hookHandlers.set(hookName, /* @__PURE__ */ new Set());
831
+ }
832
+ hookHandlers.get(hookName).add(handler);
833
+ },
834
+ unregister(hookName, handler) {
835
+ hookHandlers.get(hookName)?.delete(handler);
836
+ }
837
+ };
838
+ plugin.setup(api, registry);
839
+ }
840
+ }
841
+ function unregisterPlugin(name) {
842
+ const plugin = plugins.get(name);
843
+ if (!plugin) return;
844
+ if (plugin.hooks) {
845
+ for (const [hookName, handler] of Object.entries(plugin.hooks)) {
846
+ if (typeof handler === "function") {
847
+ hookHandlers.get(hookName)?.delete(handler);
848
+ }
849
+ }
850
+ }
851
+ plugins.delete(name);
852
+ }
853
+ function getPlugins() {
854
+ return Array.from(plugins.keys());
855
+ }
856
+ function dispatchHook(name, ...args) {
857
+ const handlers = hookHandlers.get(name);
858
+ if (!handlers) return;
859
+ for (const handler of handlers) {
860
+ try {
861
+ handler(...args);
862
+ } catch (err) {
863
+ console.warn(`[pinpoint] Plugin hook ${name} error:`, err);
864
+ }
865
+ }
866
+ }
867
+
868
+ // src/plugins/agent-native-plugin.ts
869
+ var agentNativePlugin = {
870
+ name: "agent-native"
871
+ };
872
+
873
+ // src/security/input-sanitization.ts
874
+ var HTML_ENTITIES = {
875
+ "&": "&amp;",
876
+ "<": "&lt;",
877
+ ">": "&gt;",
878
+ '"': "&quot;",
879
+ "'": "&#39;"
880
+ };
881
+ function escapeHtml(str) {
882
+ return str.replace(/[&<>"']/g, (char) => HTML_ENTITIES[char] || char);
883
+ }
884
+ function sanitizeString(str, maxLength = 1e3) {
885
+ const cleaned = str.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
886
+ return cleaned.slice(0, maxLength);
887
+ }
888
+
889
+ // src/security/origin-validation.ts
890
+ function isAllowedOrigin(origin, allowedOrigins) {
891
+ if (origin === window.location.origin) return true;
892
+ if (origin.startsWith("http://localhost") || origin.startsWith("http://127.0.0.1") || origin.startsWith("https://localhost")) {
893
+ return true;
894
+ }
895
+ if (allowedOrigins && allowedOrigins.length > 0) {
896
+ return allowedOrigins.includes(origin);
897
+ }
898
+ return false;
899
+ }
900
+ export {
901
+ DragSelect,
902
+ ElementInfoSchema,
903
+ ElementPicker,
904
+ FrameworkInfoSchema,
905
+ MemoryStore,
906
+ PinSchema,
907
+ RestClient,
908
+ TextSelect,
909
+ agentNativePlugin,
910
+ buildElementContext,
911
+ buildSelector,
912
+ detectFramework,
913
+ dispatchHook,
914
+ escapeHtml,
915
+ extractElementInfo,
916
+ formatPins,
917
+ formatPinsForAgent,
918
+ formatQueueForAgent,
919
+ formatRichPinContext,
920
+ freeze,
921
+ genericAdapter,
922
+ getComponentInfo,
923
+ getPlugins,
924
+ getSourceLocation,
925
+ isAllowedOrigin,
926
+ isFreezeActive,
927
+ openFile,
928
+ reactAdapter,
929
+ registerAdapter,
930
+ registerPlugin,
931
+ sanitizeString,
932
+ unfreeze,
933
+ unregisterPlugin,
934
+ vueAdapter
935
+ };
936
+ //# sourceMappingURL=index.js.map