pinpoints 0.1.4

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 (83) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +187 -0
  3. package/out/browser/browserSessionManager.d.ts +19 -0
  4. package/out/browser/browserSessionManager.d.ts.map +1 -0
  5. package/out/browser/browserSessionManager.js +169 -0
  6. package/out/browser/browserSessionManager.js.map +1 -0
  7. package/out/cli/index.d.ts +3 -0
  8. package/out/cli/index.d.ts.map +1 -0
  9. package/out/cli/index.js +564 -0
  10. package/out/cli/index.js.map +1 -0
  11. package/out/core/pickerToolbar.d.ts +9 -0
  12. package/out/core/pickerToolbar.d.ts.map +1 -0
  13. package/out/core/pickerToolbar.js +475 -0
  14. package/out/core/pickerToolbar.js.map +1 -0
  15. package/out/export/contextFormatter.d.ts +9 -0
  16. package/out/export/contextFormatter.d.ts.map +1 -0
  17. package/out/export/contextFormatter.js +151 -0
  18. package/out/export/contextFormatter.js.map +1 -0
  19. package/out/extension.d.ts +4 -0
  20. package/out/extension.d.ts.map +1 -0
  21. package/out/extension.js +95 -0
  22. package/out/extension.js.map +1 -0
  23. package/out/extraction/domExtractor.d.ts +10 -0
  24. package/out/extraction/domExtractor.d.ts.map +1 -0
  25. package/out/extraction/domExtractor.js +72 -0
  26. package/out/extraction/domExtractor.js.map +1 -0
  27. package/out/extraction/layoutExtractor.d.ts +11 -0
  28. package/out/extraction/layoutExtractor.d.ts.map +1 -0
  29. package/out/extraction/layoutExtractor.js +107 -0
  30. package/out/extraction/layoutExtractor.js.map +1 -0
  31. package/out/extraction/redactor.d.ts +9 -0
  32. package/out/extraction/redactor.d.ts.map +1 -0
  33. package/out/extraction/redactor.js +78 -0
  34. package/out/extraction/redactor.js.map +1 -0
  35. package/out/extraction/screenshotExtractor.d.ts +7 -0
  36. package/out/extraction/screenshotExtractor.d.ts.map +1 -0
  37. package/out/extraction/screenshotExtractor.js +104 -0
  38. package/out/extraction/screenshotExtractor.js.map +1 -0
  39. package/out/extraction/selectorExtractor.d.ts +16 -0
  40. package/out/extraction/selectorExtractor.d.ts.map +1 -0
  41. package/out/extraction/selectorExtractor.js +172 -0
  42. package/out/extraction/selectorExtractor.js.map +1 -0
  43. package/out/extraction/styleExtractor.d.ts +10 -0
  44. package/out/extraction/styleExtractor.d.ts.map +1 -0
  45. package/out/extraction/styleExtractor.js +96 -0
  46. package/out/extraction/styleExtractor.js.map +1 -0
  47. package/out/picker/pickerController.d.ts +33 -0
  48. package/out/picker/pickerController.d.ts.map +1 -0
  49. package/out/picker/pickerController.js +979 -0
  50. package/out/picker/pickerController.js.map +1 -0
  51. package/out/schemas/index.d.ts +418 -0
  52. package/out/schemas/index.d.ts.map +1 -0
  53. package/out/schemas/index.js +128 -0
  54. package/out/schemas/index.js.map +1 -0
  55. package/out/source/sourceLocator.d.ts +7 -0
  56. package/out/source/sourceLocator.d.ts.map +1 -0
  57. package/out/source/sourceLocator.js +37 -0
  58. package/out/source/sourceLocator.js.map +1 -0
  59. package/out/source/sourceMapResolver.d.ts +17 -0
  60. package/out/source/sourceMapResolver.d.ts.map +1 -0
  61. package/out/source/sourceMapResolver.js +204 -0
  62. package/out/source/sourceMapResolver.js.map +1 -0
  63. package/out/source/workspaceGrep.d.ts +30 -0
  64. package/out/source/workspaceGrep.d.ts.map +1 -0
  65. package/out/source/workspaceGrep.js +237 -0
  66. package/out/source/workspaceGrep.js.map +1 -0
  67. package/out/testPaste.d.ts +1 -0
  68. package/out/testPaste.d.ts.map +1 -0
  69. package/out/testPaste.js +3 -0
  70. package/out/testPaste.js.map +1 -0
  71. package/out/ui/statusBarManager.d.ts +14 -0
  72. package/out/ui/statusBarManager.d.ts.map +1 -0
  73. package/out/ui/statusBarManager.js +89 -0
  74. package/out/ui/statusBarManager.js.map +1 -0
  75. package/package.json +132 -0
  76. package/resources/fonts/icons.css +19 -0
  77. package/resources/fonts/icons.html +69 -0
  78. package/resources/fonts/icons.json +3 -0
  79. package/resources/fonts/icons.ts +13 -0
  80. package/resources/fonts/icons.woff +0 -0
  81. package/resources/icon.png +0 -0
  82. package/resources/icons/pinpoint-logo.svg +4 -0
  83. package/resources/logo.svg +97 -0
@@ -0,0 +1,979 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.PickerController = void 0;
37
+ const vscode = __importStar(require("vscode"));
38
+ const path = __importStar(require("path"));
39
+ const fs = __importStar(require("fs"));
40
+ const net = __importStar(require("net"));
41
+ const browserSessionManager_1 = require("../browser/browserSessionManager");
42
+ const selectorExtractor_1 = require("../extraction/selectorExtractor");
43
+ const domExtractor_1 = require("../extraction/domExtractor");
44
+ const styleExtractor_1 = require("../extraction/styleExtractor");
45
+ const layoutExtractor_1 = require("../extraction/layoutExtractor");
46
+ const screenshotExtractor_1 = require("../extraction/screenshotExtractor");
47
+ const redactor_1 = require("../extraction/redactor");
48
+ const contextFormatter_1 = require("../export/contextFormatter");
49
+ const sourceLocator_1 = require("../source/sourceLocator");
50
+ const pickerToolbar_1 = require("../core/pickerToolbar");
51
+ class PickerController {
52
+ constructor(context) {
53
+ this.context = context;
54
+ this.browserSession = null;
55
+ this.currentMode = 'pick';
56
+ this.screenshotEnabled = false;
57
+ this.contextRadius = 1;
58
+ this.injectionTarget = 'claude-code';
59
+ this.capturedElements = [];
60
+ this.tempDir = null;
61
+ this.isPickerActive = false;
62
+ this.workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
63
+ this.loadSettings();
64
+ }
65
+ loadSettings() {
66
+ const config = vscode.workspace.getConfiguration('pinpoint');
67
+ this.currentMode = (config.get('defaultMode') || 'pick');
68
+ this.screenshotEnabled = config.get('screenshotEnabled', false);
69
+ this.contextRadius = config.get('contextRadius', 1);
70
+ this.injectionTarget = config.get('injectionTarget', 'claude-code');
71
+ }
72
+ async startPicker() {
73
+ try {
74
+ // Initialize temp directory
75
+ if (!this.workspaceRoot) {
76
+ throw new Error('No workspace folder open. Please open a folder first.');
77
+ }
78
+ this.tempDir = path.join(this.workspaceRoot, '.pinpoint', 'temp');
79
+ if (!fs.existsSync(this.tempDir)) {
80
+ fs.mkdirSync(this.tempDir, { recursive: true });
81
+ }
82
+ // Ensure .gitignore includes .pinpoint/
83
+ this.ensureGitignore();
84
+ // Detect local servers and let user pick or enter custom URL
85
+ const url = await this.promptForUrl();
86
+ if (!url) {
87
+ return;
88
+ }
89
+ // Launch browser and navigate directly to the URL
90
+ this.browserSession = new browserSessionManager_1.BrowserSessionManager();
91
+ try {
92
+ const page = await vscode.window.withProgress({
93
+ location: vscode.ProgressLocation.Notification,
94
+ title: `PinPoint: Loading ${url}...`,
95
+ }, async () => {
96
+ const p = await this.browserSession.launch({ url });
97
+ return p;
98
+ });
99
+ vscode.window.showInformationMessage('Page loaded. Injecting picker...');
100
+ // Enable inspect mode
101
+ await this.enableInspectMode(page);
102
+ // Load logo SVG from file (single source of truth)
103
+ const logoSvgPath = path.join(this.context.extensionPath, 'resources', 'logo.svg');
104
+ const logoSvgContent = fs.readFileSync(logoSvgPath, 'utf-8');
105
+ // Inject picker UI and handlers
106
+ await this.injectPickerUI(page, logoSvgContent, this.currentMode, this.injectionTarget);
107
+ // Re-inject on navigation/refresh to make persistent
108
+ const attemptReinject = async () => {
109
+ if (this.isPickerActive) {
110
+ try {
111
+ // Wait for body to be available just in case
112
+ await page.waitForSelector('body', { timeout: 2000 }).catch(() => { });
113
+ await this.injectPickerUI(page, logoSvgContent, this.currentMode, this.injectionTarget);
114
+ }
115
+ catch (e) {
116
+ console.error('Failed to reinject picker UI:', e);
117
+ }
118
+ }
119
+ };
120
+ page.on('domcontentloaded', attemptReinject);
121
+ page.on('load', attemptReinject);
122
+ page.on('framenavigated', (frame) => {
123
+ if (frame === page.mainFrame()) {
124
+ attemptReinject();
125
+ }
126
+ });
127
+ this.isPickerActive = true;
128
+ vscode.window.showInformationMessage('Picker active: Hover and click elements. Toolbar visible at bottom of page.');
129
+ }
130
+ catch (innerError) {
131
+ vscode.window.showErrorMessage(`Error during picker setup: ${innerError}`);
132
+ throw innerError;
133
+ }
134
+ }
135
+ catch (error) {
136
+ vscode.window.showErrorMessage(`Failed to start picker: ${error}`);
137
+ await this.stopPicker();
138
+ }
139
+ }
140
+ async injectPickerUI(page, logoSvg, initialMode, initialTarget) {
141
+ await (0, pickerToolbar_1.injectPickerToolbar)(page, { logoSvg, initialMode, initialTarget });
142
+ return;
143
+ await page.evaluate((args) => {
144
+ const { logoSvg, initialMode, initialTarget } = args;
145
+ if (document.getElementById('pinpoint-module'))
146
+ return;
147
+ const btnBase = `
148
+ height: 36px;
149
+ padding: 0;
150
+ background: transparent;
151
+ border: none;
152
+ border-radius: 9999px;
153
+ color: rgba(255, 255, 255, 0.6);
154
+ cursor: pointer;
155
+ transition: color 0.2s ease;
156
+ display: flex;
157
+ align-items: center;
158
+ justify-content: center;
159
+ position: relative;
160
+ z-index: 1;
161
+ width: 36px;
162
+ flex-shrink: 0;
163
+ `;
164
+ // Create floating toolbar that doesn't affect page layout
165
+ const moduleContainer = document.createElement('div');
166
+ moduleContainer.id = 'pinpoint-module';
167
+ moduleContainer.style.cssText = 'position: absolute; top: 0; left: 0; width: 0; height: 0; pointer-events: none; overflow: visible; z-index: 2147483647;';
168
+ moduleContainer.innerHTML = `
169
+ <div id="pinpoint-tooltip" style="
170
+ position: fixed;
171
+ z-index: 9999999999;
172
+ background: rgba(0, 0, 0, 0.85);
173
+ color: #fff;
174
+ font-size: 11px;
175
+ font-weight: 500;
176
+ padding: 4px 10px;
177
+ border-radius: 6px;
178
+ pointer-events: none;
179
+ white-space: nowrap;
180
+ opacity: 0;
181
+ transition: opacity 0.15s ease;
182
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
183
+ "></div>
184
+ <div id="pinpoint-toolbar" style="
185
+ pointer-events: auto;
186
+ position: fixed;
187
+ bottom: 24px;
188
+ left: 50%;
189
+ transform: translateX(-50%);
190
+ z-index: 999999999;
191
+ display: flex;
192
+ flex-direction: row;
193
+ align-items: center;
194
+ gap: 2px;
195
+ padding: 6px 10px;
196
+ background: rgba(30, 30, 30, 0.92);
197
+ backdrop-filter: blur(16px);
198
+ border: 1px solid rgba(255, 255, 255, 0.08);
199
+ border-radius: 9999px;
200
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35), 0 2px 8px rgba(0, 0, 0, 0.2);
201
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
202
+ user-select: none;
203
+ transition: padding 0.3s cubic-bezier(0.4, 0, 0.2, 1), gap 0.3s cubic-bezier(0.4, 0, 0.2, 1);
204
+ ">
205
+ <!-- Toggle button (always visible) -->
206
+ <button id="pinpoint-toggle" title="Capture mode active (click or Esc to interact)" style="
207
+ height: 36px;
208
+ width: 36px;
209
+ padding: 0;
210
+ background: rgba(14, 165, 233, 0.2);
211
+ border: none;
212
+ border-radius: 9999px;
213
+ color: #0ea5e9;
214
+ cursor: pointer;
215
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
216
+ display: flex;
217
+ align-items: center;
218
+ justify-content: center;
219
+ flex-shrink: 0;
220
+ ">
221
+ <span id="pinpoint-logo-slot" style="display:flex;align-items:center;justify-content:center;pointer-events:none;"></span>
222
+ </button>
223
+
224
+ <!-- Collapsible content -->
225
+ <div id="pinpoint-toolbar-content" style="
226
+ display: flex;
227
+ flex-direction: row;
228
+ align-items: center;
229
+ gap: 2px;
230
+ overflow: hidden;
231
+ transition: max-width 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
232
+ max-width: 600px;
233
+ opacity: 1;
234
+ ">
235
+
236
+ <!-- Drag handle -->
237
+ <div id="pinpoint-drag" title="Drag to reposition" style="
238
+ width: 28px;
239
+ height: 36px;
240
+ display: flex;
241
+ align-items: center;
242
+ justify-content: center;
243
+ cursor: grab;
244
+ color: rgba(255, 255, 255, 0.3);
245
+ flex-shrink: 0;
246
+ ">
247
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
248
+ <circle cx="9" cy="6" r="1.5"/><circle cx="15" cy="6" r="1.5"/>
249
+ <circle cx="9" cy="12" r="1.5"/><circle cx="15" cy="12" r="1.5"/>
250
+ <circle cx="9" cy="18" r="1.5"/><circle cx="15" cy="18" r="1.5"/>
251
+ </svg>
252
+ </div>
253
+
254
+ <!-- Mode buttons -->
255
+ <div id="pinpoint-modes" style="
256
+ display: flex;
257
+ flex-direction: row;
258
+ align-items: center;
259
+ gap: 2px;
260
+ position: relative;
261
+ ">
262
+ <div id="pinpoint-mode-slider" style="
263
+ position: absolute;
264
+ top: 0;
265
+ left: 0;
266
+ height: 100%;
267
+ background: rgba(255, 255, 255, 0.15);
268
+ border-radius: 9999px;
269
+ z-index: 0;
270
+ pointer-events: none;
271
+ width: 36px;
272
+ transition: left 0.25s cubic-bezier(0.4, 0, 0.2, 1);
273
+ "></div>
274
+ <button data-mode="pick" title="Quick Fix" style="${btnBase}">
275
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;">
276
+ <circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/><circle cx="5" cy="12" r="1"/><circle cx="19" cy="12" r="1"/>
277
+ </svg>
278
+ </button>
279
+ <button data-mode="full" title="Full" style="${btnBase}">
280
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;">
281
+ <rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="12" cy="12" r="3"/>
282
+ </svg>
283
+ </button>
284
+ </div>
285
+
286
+ <!-- Divider -->
287
+ <div style="width: 1px; height: 20px; background: rgba(255, 255, 255, 0.15); margin: 0 6px; flex-shrink: 0;"></div>
288
+
289
+ <!-- Target buttons -->
290
+ <div id="pinpoint-targets" style="
291
+ display: flex;
292
+ flex-direction: row;
293
+ align-items: center;
294
+ gap: 2px;
295
+ position: relative;
296
+ ">
297
+ <div id="pinpoint-target-slider" style="
298
+ position: absolute;
299
+ top: 0;
300
+ left: 0;
301
+ height: 100%;
302
+ background: rgba(255, 255, 255, 0.15);
303
+ border-radius: 9999px;
304
+ z-index: 0;
305
+ pointer-events: none;
306
+ width: 36px;
307
+ transition: left 0.25s cubic-bezier(0.4, 0, 0.2, 1);
308
+ "></div>
309
+ <button data-target="claude-code" title="Claude Code" style="${btnBase}">
310
+ <svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor" style="flex-shrink:0;">
311
+ <path d="m3.127 10.604 3.135-1.76.053-.153-.053-.085H6.11l-.525-.032-1.791-.048-1.554-.065-1.505-.08-.38-.081L0 7.832l.036-.234.32-.214.455.04 1.009.069 1.513.105 1.097.064 1.626.17h.259l.036-.105-.089-.065-.068-.064-1.566-1.062-1.695-1.121-.887-.646-.48-.327-.243-.306-.104-.67.435-.48.585.04.15.04.593.456 1.267.981 1.654 1.218.242.202.097-.068.012-.049-.109-.181-.9-1.626-.96-1.655-.428-.686-.113-.411a2 2 0 0 1-.068-.484l.496-.674L4.446 0l.662.089.279.242.411.94.666 1.48 1.033 2.014.302.597.162.553.06.17h.105v-.097l.085-1.134.157-1.392.154-1.792.052-.504.25-.605.497-.327.387.186.319.456-.045.294-.19 1.23-.37 1.93-.243 1.29h.142l.161-.16.654-.868 1.097-1.372.484-.545.565-.601.363-.287h.686l.505.751-.226.775-.707.895-.585.759-.839 1.13-.524.904.048.072.125-.012 1.897-.403 1.024-.186 1.223-.21.553.258.06.263-.218.536-1.307.323-1.533.307-2.284.54-.028.02.032.04 1.029.098.44.024h1.077l2.005.15.525.346.315.424-.053.323-.807.411-3.631-.863-.872-.218h-.12v.073l.726.71 1.331 1.202 1.667 1.55.084.383-.214.302-.226-.032-1.464-1.101-.565-.497-1.28-1.077h-.084v.113l.295.432 1.557 2.34.08.718-.112.234-.404.141-.444-.08-.911-1.28-.94-1.44-.759-1.291-.093.053-.448 4.821-.21.246-.484.186-.403-.307-.214-.496.214-.98.258-1.28.21-1.016.19-1.263.112-.42-.008-.028-.092.012-.953 1.307-1.448 1.957-1.146 1.227-.274.109-.477-.247.045-.44.266-.39 1.586-2.018.956-1.25.617-.723-.004-.105h-.036l-4.212 2.736-.75.096-.324-.302.04-.496.154-.162 1.267-.871z"/>
312
+ </svg>
313
+ </button>
314
+ <button data-target="copilot-chat" title="Copilot Chat" style="${btnBase}">
315
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;">
316
+ <path d="M4 18v-5.5c0 -.667 .167 -1.333 .5 -2"/>
317
+ <path d="M12 7.5c0 -1 -.01 -4.07 -4 -3.5c-3.5 .5 -4 2.5 -4 3.5c0 1.5 0 4 3 4c4 0 5 -2.5 5 -4"/>
318
+ <path d="M4 12c-1.333 .667 -2 1.333 -2 2c0 1 0 3 1.5 4c3 2 6.5 3 8.5 3s5.499 -1 8.5 -3c1.5 -1 1.5 -3 1.5 -4c0 -.667 -.667 -1.333 -2 -2"/>
319
+ <path d="M20 18v-5.5c0 -.667 -.167 -1.333 -.5 -2"/>
320
+ <path d="M12 7.5l0 -.297l.01 -.269l.027 -.298l.013 -.105l.033 -.215c.014 -.073 .029 -.146 .046 -.22l.06 -.223c.336 -1.118 1.262 -2.237 3.808 -1.873c2.838 .405 3.703 1.797 3.93 2.842l.036 .204c0 .033 .01 .066 .013 .098l.016 .185l0 .171l0 .49l-.015 .394l-.02 .271c-.122 1.366 -.655 2.845 -2.962 2.845c-3.256 0 -4.524 -1.656 -4.883 -3.081l-.053 -.242a3.865 3.865 0 0 1 -.036 -.235l-.021 -.227a3.518 3.518 0 0 1 -.007 -.215l.005 0"/>
321
+ <path d="M10 15v2"/><path d="M14 15v2"/>
322
+ </svg>
323
+ </button>
324
+ <button data-target="clipboard" title="Clipboard" style="${btnBase}">
325
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;">
326
+ <rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2"/>
327
+ </svg>
328
+ </button>
329
+ </div>
330
+ </div>
331
+ </div>
332
+ `;
333
+ document.body.appendChild(moduleContainer);
334
+ // Inject logo SVG from file and size it to fit the button
335
+ const logoSlot = document.getElementById('pinpoint-logo-slot');
336
+ logoSlot.innerHTML = logoSvg;
337
+ const svgEl = logoSlot.querySelector('svg');
338
+ if (svgEl) {
339
+ svgEl.setAttribute('width', '22');
340
+ svgEl.setAttribute('height', '22');
341
+ svgEl.style.flexShrink = '0';
342
+ svgEl.style.pointerEvents = 'none';
343
+ const paths = svgEl.querySelectorAll('path');
344
+ paths.forEach((p, i) => {
345
+ if (i === 0) {
346
+ // Outer pin silhouette — fills with currentColor (mode-driven)
347
+ p.setAttribute('fill', 'currentColor');
348
+ p.removeAttribute('stroke');
349
+ p.removeAttribute('stroke-width');
350
+ p.removeAttribute('stroke-linejoin');
351
+ }
352
+ else {
353
+ // Inner details — dark cutout so they read against the colored fill
354
+ const cutout = 'rgba(20,20,20,0.92)';
355
+ if (p.getAttribute('fill') === 'none') {
356
+ p.setAttribute('stroke', cutout);
357
+ }
358
+ else {
359
+ p.setAttribute('fill', cutout);
360
+ p.removeAttribute('stroke');
361
+ }
362
+ }
363
+ });
364
+ }
365
+ const toolbar = document.getElementById('pinpoint-toolbar');
366
+ const modes = document.getElementById('pinpoint-modes');
367
+ const targets = document.getElementById('pinpoint-targets');
368
+ const modeSlider = document.getElementById('pinpoint-mode-slider');
369
+ const targetSlider = document.getElementById('pinpoint-target-slider');
370
+ const tooltip = document.getElementById('pinpoint-tooltip');
371
+ // Slider update helper — sets position; CSS transition handles the animation
372
+ function updateSlider(slider, activeBtn) {
373
+ slider.style.left = activeBtn.offsetLeft + 'px';
374
+ slider.style.width = activeBtn.offsetWidth + 'px';
375
+ }
376
+ // Tooltip helper
377
+ function showTooltip(btn, text) {
378
+ tooltip.textContent = text;
379
+ tooltip.style.opacity = '1';
380
+ const btnRect = btn.getBoundingClientRect();
381
+ const tipWidth = tooltip.offsetWidth;
382
+ tooltip.style.left = (btnRect.left + btnRect.width / 2 - tipWidth / 2) + 'px';
383
+ tooltip.style.top = (btnRect.top - 32) + 'px';
384
+ }
385
+ function hideTooltip() {
386
+ tooltip.style.opacity = '0';
387
+ }
388
+ // Shared state
389
+ let lastEl = null;
390
+ // Interact/Capture toggle
391
+ let isInteractMode = false;
392
+ const toggleBtn = document.getElementById('pinpoint-toggle');
393
+ const toolbarContent = document.getElementById('pinpoint-toolbar-content');
394
+ const shortcutLabel = 'Esc';
395
+ function setInteractMode(interact) {
396
+ isInteractMode = interact;
397
+ if (interact) {
398
+ // Collapsed: hide content, compact circle
399
+ toolbarContent.style.maxWidth = '0';
400
+ toolbarContent.style.opacity = '0';
401
+ toolbar.style.padding = '6px';
402
+ toolbar.style.gap = '0';
403
+ // Collapsed: main logo color
404
+ toggleBtn.style.background = '#ABFF06';
405
+ // Always black icon
406
+ toggleBtn.style.color = '#000000';
407
+ // Clear any hover highlight
408
+ if (lastEl) {
409
+ lastEl.style.outline = '';
410
+ lastEl = null;
411
+ }
412
+ }
413
+ else {
414
+ // Expanded: show content, restore padding
415
+ toolbarContent.style.maxWidth = '600px';
416
+ toolbarContent.style.opacity = '1';
417
+ toolbar.style.padding = '6px 10px';
418
+ toolbar.style.gap = '2px';
419
+ // Expanded: use main logo color
420
+ toggleBtn.style.background = '#ABFF06';
421
+ // Always black icon
422
+ toggleBtn.style.color = '#000000';
423
+ }
424
+ }
425
+ // Apply initial visual state
426
+ setInteractMode(isInteractMode);
427
+ toggleBtn.addEventListener('click', (e) => {
428
+ e.stopPropagation();
429
+ setInteractMode(!isInteractMode);
430
+ });
431
+ toggleBtn.addEventListener('mouseenter', () => {
432
+ const label = isInteractMode
433
+ ? `Switch to Capture (${shortcutLabel})`
434
+ : `Switch to Interact (${shortcutLabel})`;
435
+ showTooltip(toggleBtn, label);
436
+ });
437
+ toggleBtn.addEventListener('mouseleave', () => {
438
+ hideTooltip();
439
+ });
440
+ document.addEventListener('keydown', (e) => {
441
+ const ke = e;
442
+ if (ke.key === 'Escape') {
443
+ ke.preventDefault();
444
+ ke.stopPropagation();
445
+ setInteractMode(!isInteractMode);
446
+ }
447
+ });
448
+ // Drag logic
449
+ const dragHandle = document.getElementById('pinpoint-drag');
450
+ let isDragging = false;
451
+ let dragOffsetX = 0;
452
+ let dragOffsetY = 0;
453
+ dragHandle.addEventListener('mousedown', (e) => {
454
+ const me = e;
455
+ isDragging = true;
456
+ dragHandle.style.cursor = 'grabbing';
457
+ const rect = toolbar.getBoundingClientRect();
458
+ dragOffsetX = me.clientX - rect.left;
459
+ dragOffsetY = me.clientY - rect.top;
460
+ toolbar.style.left = rect.left + 'px';
461
+ toolbar.style.bottom = 'auto';
462
+ toolbar.style.top = rect.top + 'px';
463
+ toolbar.style.transform = 'none';
464
+ me.preventDefault();
465
+ me.stopPropagation();
466
+ });
467
+ document.addEventListener('mousemove', (e) => {
468
+ if (!isDragging)
469
+ return;
470
+ const me = e;
471
+ let newX = me.clientX - dragOffsetX;
472
+ let newY = me.clientY - dragOffsetY;
473
+ const rect = toolbar.getBoundingClientRect();
474
+ newX = Math.max(0, Math.min(window.innerWidth - rect.width, newX));
475
+ newY = Math.max(0, Math.min(window.innerHeight - rect.height, newY));
476
+ toolbar.style.left = newX + 'px';
477
+ toolbar.style.top = newY + 'px';
478
+ me.preventDefault();
479
+ me.stopPropagation();
480
+ });
481
+ document.addEventListener('mouseup', (e) => {
482
+ if (isDragging) {
483
+ isDragging = false;
484
+ dragHandle.style.cursor = 'grab';
485
+ e.preventDefault();
486
+ e.stopPropagation();
487
+ }
488
+ });
489
+ // Mode buttons
490
+ window.pinPointMode = initialMode || 'pick';
491
+ const modeButtons = modes.querySelectorAll('button');
492
+ let activeModeBtn = Array.from(modeButtons).find(btn => btn.getAttribute('data-mode') === window.pinPointMode) || modeButtons[0];
493
+ // Initialize active mode button
494
+ activeModeBtn.style.color = '#ffffff';
495
+ updateSlider(modeSlider, activeModeBtn);
496
+ modeButtons.forEach((btn) => {
497
+ btn.addEventListener('click', (e) => {
498
+ e.stopPropagation();
499
+ const mode = btn.getAttribute('data-mode');
500
+ window.pinPointMode = mode;
501
+ console.log('PINPOINT_MODE_CHANGED:', mode);
502
+ if (activeModeBtn !== btn) {
503
+ activeModeBtn.style.color = 'rgba(255, 255, 255, 0.6)';
504
+ activeModeBtn = btn;
505
+ activeModeBtn.style.color = '#ffffff';
506
+ updateSlider(modeSlider, activeModeBtn);
507
+ }
508
+ });
509
+ btn.addEventListener('mouseenter', () => {
510
+ if (btn !== activeModeBtn) {
511
+ btn.style.color = 'rgba(255, 255, 255, 0.9)';
512
+ showTooltip(btn, btn.getAttribute('title') || '');
513
+ }
514
+ });
515
+ btn.addEventListener('mouseleave', () => {
516
+ if (btn !== activeModeBtn) {
517
+ btn.style.color = 'rgba(255, 255, 255, 0.6)';
518
+ }
519
+ hideTooltip();
520
+ });
521
+ });
522
+ // Target buttons
523
+ const targetButtons = targets.querySelectorAll('button');
524
+ let activeTargetBtn = Array.from(targetButtons).find(btn => btn.getAttribute('data-target') === initialTarget) || targetButtons[0];
525
+ // Initialize active target button
526
+ activeTargetBtn.style.color = '#ffffff';
527
+ updateSlider(targetSlider, activeTargetBtn);
528
+ targetButtons.forEach((btn) => {
529
+ btn.addEventListener('click', (e) => {
530
+ e.stopPropagation();
531
+ const target = btn.getAttribute('data-target');
532
+ console.log('PINPOINT_TARGET_CHANGED:', target);
533
+ if (activeTargetBtn !== btn) {
534
+ activeTargetBtn.style.color = 'rgba(255, 255, 255, 0.6)';
535
+ activeTargetBtn = btn;
536
+ activeTargetBtn.style.color = '#ffffff';
537
+ updateSlider(targetSlider, activeTargetBtn);
538
+ }
539
+ });
540
+ btn.addEventListener('mouseenter', () => {
541
+ if (btn !== activeTargetBtn) {
542
+ btn.style.color = 'rgba(255, 255, 255, 0.9)';
543
+ showTooltip(btn, btn.getAttribute('title') || '');
544
+ }
545
+ });
546
+ btn.addEventListener('mouseleave', () => {
547
+ if (btn !== activeTargetBtn) {
548
+ btn.style.color = 'rgba(255, 255, 255, 0.6)';
549
+ }
550
+ hideTooltip();
551
+ });
552
+ });
553
+ // Hover highlight + click handler
554
+ document.addEventListener('mousemove', (e) => {
555
+ if (isDragging)
556
+ return;
557
+ if (isInteractMode)
558
+ return;
559
+ // Use elementFromPoint instead of e.target for more reliable hit testing,
560
+ // especially with complex layouts or overlays
561
+ const target = document.elementFromPoint(e.clientX, e.clientY);
562
+ if (!target)
563
+ return;
564
+ const el = target;
565
+ // Skip highlighting the module itself
566
+ if (el.closest('#pinpoint-module'))
567
+ return;
568
+ if (lastEl && lastEl !== el && lastEl !== window.__pinpoint_clicked) {
569
+ lastEl.style.outline = '';
570
+ }
571
+ el.style.outline = '3px solid #0ea5e9';
572
+ el.style.outlineOffset = '2px';
573
+ lastEl = el;
574
+ }, true);
575
+ document.addEventListener('click', (e) => {
576
+ if (isDragging)
577
+ return;
578
+ if (isInteractMode)
579
+ return;
580
+ const target = document.elementFromPoint(e.clientX, e.clientY);
581
+ if (!target)
582
+ return;
583
+ const el = target;
584
+ // Don't capture if clicking the module
585
+ if (el.closest('#pinpoint-module'))
586
+ return;
587
+ e.preventDefault();
588
+ e.stopPropagation();
589
+ window.__pinpoint_clicked = el;
590
+ console.log('PINPOINT_SELECTED:', JSON.stringify({
591
+ tag: el.tagName,
592
+ class: el.className,
593
+ id: el.id,
594
+ }));
595
+ }, true);
596
+ }, { logoSvg, initialMode, initialTarget });
597
+ }
598
+ async promptForUrl() {
599
+ const CUSTOM_URL_LABEL = '$(edit) Enter custom URL...';
600
+ const HISTORY_KEY = 'pinpoint.urlHistory';
601
+ const MAX_HISTORY = 10;
602
+ const history = this.context.globalState.get(HISTORY_KEY, []);
603
+ const detectedServers = await this.detectLocalServers();
604
+ const items = [];
605
+ if (detectedServers.length > 0) {
606
+ items.push({ label: 'Detected Servers', kind: vscode.QuickPickItemKind.Separator });
607
+ for (const server of detectedServers) {
608
+ items.push({
609
+ label: `$(radio-tower) ${server.url}`,
610
+ description: server.label,
611
+ detail: server.url,
612
+ });
613
+ }
614
+ }
615
+ if (history.length > 0) {
616
+ items.push({ label: 'Recent', kind: vscode.QuickPickItemKind.Separator });
617
+ for (const historyUrl of history) {
618
+ if (detectedServers.some(s => s.url === historyUrl))
619
+ continue;
620
+ items.push({
621
+ label: `$(history) ${historyUrl}`,
622
+ detail: historyUrl,
623
+ });
624
+ }
625
+ }
626
+ items.push({ label: '', kind: vscode.QuickPickItemKind.Separator });
627
+ items.push({
628
+ label: CUSTOM_URL_LABEL,
629
+ description: 'Type any URL',
630
+ alwaysShow: true,
631
+ });
632
+ const picked = await vscode.window.showQuickPick(items, {
633
+ placeHolder: 'Select a local server or enter a custom URL',
634
+ matchOnDescription: true,
635
+ matchOnDetail: true,
636
+ });
637
+ if (!picked)
638
+ return undefined;
639
+ let url;
640
+ if (picked.label === CUSTOM_URL_LABEL) {
641
+ url = await vscode.window.showInputBox({
642
+ prompt: 'Enter the URL to inspect',
643
+ placeHolder: 'e.g., http://localhost:3000',
644
+ value: history[0] || 'http://localhost:3000',
645
+ });
646
+ }
647
+ else {
648
+ url = picked.detail;
649
+ }
650
+ if (!url)
651
+ return undefined;
652
+ const updatedHistory = [url, ...history.filter(h => h !== url)].slice(0, MAX_HISTORY);
653
+ await this.context.globalState.update(HISTORY_KEY, updatedHistory);
654
+ return url;
655
+ }
656
+ async detectLocalServers() {
657
+ const commonPorts = [
658
+ { port: 3000, label: 'React / Express' },
659
+ { port: 3001, label: 'React (alt)' },
660
+ { port: 4200, label: 'Angular' },
661
+ { port: 4321, label: 'Astro' },
662
+ { port: 5000, label: 'Flask / .NET' },
663
+ { port: 5173, label: 'Vite' },
664
+ { port: 5174, label: 'Vite (alt)' },
665
+ { port: 5500, label: 'Live Server' },
666
+ { port: 8000, label: 'Django / Python' },
667
+ { port: 8080, label: 'General dev server' },
668
+ { port: 8081, label: 'General dev server' },
669
+ { port: 8888, label: 'Jupyter / dev server' },
670
+ { port: 3333, label: 'Dev server' },
671
+ { port: 4000, label: 'Phoenix / Gatsby' },
672
+ { port: 1234, label: 'Parcel' },
673
+ { port: 9000, label: 'Webpack / PHP' },
674
+ ];
675
+ const results = [];
676
+ const checks = commonPorts.map(({ port, label }) => this.isPortOpen(port, 150).then(open => {
677
+ if (open) {
678
+ results.push({ url: `http://localhost:${port}`, label: `Port ${port} · ${label}` });
679
+ }
680
+ }));
681
+ await Promise.all(checks);
682
+ results.sort((a, b) => {
683
+ const portA = parseInt(a.url.split(':').pop());
684
+ const portB = parseInt(b.url.split(':').pop());
685
+ return portA - portB;
686
+ });
687
+ return results;
688
+ }
689
+ isPortOpen(port, timeout) {
690
+ return new Promise(resolve => {
691
+ const socket = new net.Socket();
692
+ socket.setTimeout(timeout);
693
+ socket.once('connect', () => { socket.destroy(); resolve(true); });
694
+ socket.once('timeout', () => { socket.destroy(); resolve(false); });
695
+ socket.once('error', () => { socket.destroy(); resolve(false); });
696
+ socket.connect(port, '127.0.0.1');
697
+ });
698
+ }
699
+ async stopPicker() {
700
+ this.isPickerActive = false;
701
+ if (this.browserSession) {
702
+ await this.browserSession.close();
703
+ this.browserSession = null;
704
+ }
705
+ this.capturedElements = [];
706
+ }
707
+ toggleScreenshot() {
708
+ this.screenshotEnabled = !this.screenshotEnabled;
709
+ }
710
+ isScreenshotEnabled() {
711
+ return this.screenshotEnabled;
712
+ }
713
+ setMode(mode) {
714
+ this.currentMode = mode;
715
+ }
716
+ clearSelection() {
717
+ this.capturedElements = [];
718
+ }
719
+ async enableInspectMode(page) {
720
+ page.on('console', (msg) => {
721
+ const text = msg.text();
722
+ if (text.startsWith('PINPOINT_SELECTED:')) {
723
+ const dataStr = text.replace('PINPOINT_SELECTED:', '').trim();
724
+ try {
725
+ const data = JSON.parse(dataStr);
726
+ this.captureClickedElement(page);
727
+ }
728
+ catch (e) {
729
+ // Fail silently
730
+ }
731
+ }
732
+ else if (text.startsWith('PINPOINT_MODE_CHANGED:')) {
733
+ const mode = text.replace('PINPOINT_MODE_CHANGED:', '').trim();
734
+ if (['pick', 'full'].includes(mode)) {
735
+ this.currentMode = mode;
736
+ vscode.workspace.getConfiguration('pinpoint').update('defaultMode', mode, true);
737
+ }
738
+ }
739
+ else if (text.startsWith('PINPOINT_TARGET_CHANGED:')) {
740
+ const target = text.replace('PINPOINT_TARGET_CHANGED:', '').trim();
741
+ if (['claude-code', 'copilot-chat', 'clipboard'].includes(target)) {
742
+ this.injectionTarget = target;
743
+ vscode.workspace.getConfiguration('pinpoint').update('injectionTarget', target, true);
744
+ }
745
+ }
746
+ });
747
+ }
748
+ async captureClickedElement(page) {
749
+ try {
750
+ // Get the selected mode from the page
751
+ const mode = await page.evaluate(() => {
752
+ return window.pinPointMode || 'pick';
753
+ });
754
+ // Update the current mode
755
+ if (mode && ['pick', 'full'].includes(mode)) {
756
+ this.currentMode = mode;
757
+ }
758
+ // Get the element that was clicked (stored reference, with fallbacks)
759
+ const elementHandle = await page.evaluateHandle(() => {
760
+ const el = window.__pinpoint_clicked;
761
+ if (el) {
762
+ delete window.__pinpoint_clicked;
763
+ return el;
764
+ }
765
+ return document.querySelector('[style*="0ea5e9"]') || document.activeElement;
766
+ });
767
+ if (elementHandle) {
768
+ const tagName = await elementHandle.evaluate((el) => el.tagName.toLowerCase());
769
+ if (tagName === 'body' || tagName === 'html') {
770
+ vscode.window.showWarningMessage('PinPoint: Could not identify the clicked element. Please try again.');
771
+ return;
772
+ }
773
+ await this.captureElement(elementHandle);
774
+ }
775
+ }
776
+ catch (error) {
777
+ console.error('Failed to capture clicked element:', error);
778
+ }
779
+ }
780
+ ensureGitignore() {
781
+ const gitignorePath = path.join(this.workspaceRoot, '.gitignore');
782
+ const tmpPattern = '.pinpoint/';
783
+ try {
784
+ let gitignoreContent = '';
785
+ if (fs.existsSync(gitignorePath)) {
786
+ gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
787
+ }
788
+ if (!gitignoreContent.includes(tmpPattern)) {
789
+ gitignoreContent += (gitignoreContent ? '\n' : '') + tmpPattern;
790
+ fs.writeFileSync(gitignorePath, gitignoreContent);
791
+ }
792
+ }
793
+ catch {
794
+ // Silently fail if we can't update gitignore
795
+ }
796
+ }
797
+ async captureElement(elementHandle) {
798
+ try {
799
+ if (!this.browserSession) {
800
+ throw new Error('Browser session not active');
801
+ }
802
+ const page = this.browserSession.getPage();
803
+ if (!page) {
804
+ throw new Error('Page not available');
805
+ }
806
+ // Extract information
807
+ const selectorExtractor = new selectorExtractor_1.SelectorExtractor();
808
+ const domExtractor = new domExtractor_1.DomExtractor();
809
+ const styleExtractor = new styleExtractor_1.StyleExtractor();
810
+ const layoutExtractor = new layoutExtractor_1.LayoutExtractor();
811
+ const screenshotExtractor = new screenshotExtractor_1.ScreenshotExtractor();
812
+ const redactor = new redactor_1.Redactor();
813
+ // Get identity first
814
+ const identity = await elementHandle.evaluate((el) => {
815
+ const dataAttributes = {};
816
+ for (const attr of Array.from(el.attributes)) {
817
+ if (attr.name.startsWith('data-')) {
818
+ dataAttributes[attr.name] = attr.value;
819
+ }
820
+ }
821
+ return {
822
+ tag: el.tagName.toLowerCase(),
823
+ id: el.id || undefined,
824
+ classes: Array.from(el.classList),
825
+ role: el.getAttribute('role') || undefined,
826
+ ariaLabel: el.getAttribute('aria-label') || undefined,
827
+ dataAttributes: Object.keys(dataAttributes).length > 0 ? dataAttributes : undefined,
828
+ text: (el.textContent?.trim().substring(0, 200) || undefined),
829
+ accessibleName: el.ariaLabel || undefined,
830
+ };
831
+ });
832
+ // Extract all data
833
+ const selectors = await selectorExtractor.extract(elementHandle);
834
+ const dom = await domExtractor.extract(elementHandle, this.contextRadius);
835
+ const styles = await styleExtractor.extract(elementHandle, this.currentMode);
836
+ const layout = await layoutExtractor.extract(elementHandle, page);
837
+ let visual;
838
+ const shouldScreenshot = this.currentMode === 'full' || this.screenshotEnabled;
839
+ if (shouldScreenshot && this.tempDir) {
840
+ try {
841
+ visual = await screenshotExtractor.extract(elementHandle, this.tempDir);
842
+ }
843
+ catch (error) {
844
+ console.warn('Screenshot capture failed:', error);
845
+ vscode.window.showWarningMessage('PinPoint: Screenshot capture failed. Other data was captured successfully.');
846
+ }
847
+ }
848
+ // Detect source file
849
+ let sourceLocation;
850
+ try {
851
+ const sourceLocator = new sourceLocator_1.SourceLocator();
852
+ sourceLocation = await sourceLocator.locate(page, identity, dom, this.workspaceRoot);
853
+ }
854
+ catch (error) {
855
+ console.warn('Source detection failed:', error);
856
+ }
857
+ // Detect React component name via fiber tree
858
+ let reactComponent;
859
+ try {
860
+ reactComponent = await elementHandle.evaluate((el) => {
861
+ const fiberKey = Object.keys(el).find(k => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));
862
+ if (!fiberKey)
863
+ return undefined;
864
+ let fiber = el[fiberKey];
865
+ while (fiber) {
866
+ if (fiber.type && typeof fiber.type === 'function') {
867
+ const name = fiber.type.displayName || fiber.type.name;
868
+ if (name && name !== 'Anonymous')
869
+ return name;
870
+ }
871
+ fiber = fiber.return;
872
+ }
873
+ return undefined;
874
+ }) || undefined;
875
+ }
876
+ catch (error) {
877
+ console.warn('React component detection failed:', error);
878
+ }
879
+ // Build MaxContext
880
+ let context = {
881
+ meta: {
882
+ url: page.url(),
883
+ timestamp: new Date().toISOString(),
884
+ viewport: layout.viewport || { width: 1920, height: 1080 },
885
+ dpr: layout.devicePixelRatio,
886
+ },
887
+ identity,
888
+ selectors,
889
+ dom,
890
+ layout,
891
+ styles,
892
+ visual,
893
+ sourceLocation,
894
+ reactComponent,
895
+ };
896
+ // Redact sensitive data
897
+ context = redactor.redact(context);
898
+ // Store captured element
899
+ this.capturedElements.push(context);
900
+ // Format and inject to chat
901
+ const formatter = new contextFormatter_1.ContextFormatter();
902
+ const injectedText = formatter.formatForChat(this.capturedElements, this.currentMode, this.workspaceRoot);
903
+ // Inject to focused location (terminal, chat, or clipboard)
904
+ await this.injectToFocusedLocation(injectedText, this.capturedElements.length);
905
+ }
906
+ catch (error) {
907
+ vscode.window.showErrorMessage(`Failed to capture element: ${error}`);
908
+ }
909
+ }
910
+ async injectToFocusedLocation(injectedText, elementNumber) {
911
+ const target = this.injectionTarget;
912
+ if (target === 'claude-code') {
913
+ try {
914
+ await vscode.env.clipboard.writeText(injectedText + '\n\n');
915
+ await vscode.commands.executeCommand('claude-vscode.focus');
916
+ // Wait for the chat to open and focus (increased delay to handle cold start)
917
+ await new Promise(r => setTimeout(r, 600));
918
+ await vscode.commands.executeCommand('editor.action.clipboardPasteAction');
919
+ // Auxiliary view updates - don't fail the whole block if these error
920
+ try {
921
+ await new Promise(r => setTimeout(r, 100));
922
+ await vscode.commands.executeCommand('list.scrollToBottom');
923
+ await vscode.commands.executeCommand('cursorBottom');
924
+ }
925
+ catch (e) { /* ignore scroll errors */ }
926
+ vscode.window.showInformationMessage(`Captured element #${elementNumber} to Claude Code`);
927
+ return;
928
+ }
929
+ catch (err) {
930
+ console.warn('Claude Code injection failed, falling back to clipboard', err);
931
+ }
932
+ }
933
+ if (target === 'copilot-chat') {
934
+ try {
935
+ await vscode.env.clipboard.writeText(injectedText + '\n\n');
936
+ await vscode.commands.executeCommand('workbench.action.chat.open');
937
+ // Wait enough time for the chat view to focus
938
+ await new Promise(r => setTimeout(r, 600));
939
+ await vscode.commands.executeCommand('editor.action.clipboardPasteAction');
940
+ // Auxiliary view updates - don't fail the whole block if these error
941
+ try {
942
+ await new Promise(r => setTimeout(r, 100));
943
+ await vscode.commands.executeCommand('workbench.action.chat.scrollToBottom');
944
+ }
945
+ catch (e) { /* might not exist in all versions */ }
946
+ try {
947
+ await vscode.commands.executeCommand('list.scrollToBottom'); // Fallback
948
+ await vscode.commands.executeCommand('cursorBottom');
949
+ }
950
+ catch (e) { /* ignore scroll errors */ }
951
+ vscode.window.showInformationMessage(`Captured element #${elementNumber} to Copilot Chat`);
952
+ return;
953
+ }
954
+ catch (err) {
955
+ console.warn('Copilot Chat injection failed, falling back to clipboard', err);
956
+ }
957
+ }
958
+ // Clipboard fallback (or explicit clipboard target)
959
+ await vscode.env.clipboard.writeText(injectedText);
960
+ vscode.window.showInformationMessage(`Context copied to clipboard. Paste where needed.`);
961
+ }
962
+ cleanup() {
963
+ if (this.browserSession) {
964
+ this.browserSession.dispose();
965
+ this.browserSession = null;
966
+ }
967
+ // Clean up temp directory
968
+ if (this.tempDir && fs.existsSync(this.tempDir)) {
969
+ try {
970
+ fs.rmSync(this.tempDir, { recursive: true, force: true });
971
+ }
972
+ catch {
973
+ // Silently fail
974
+ }
975
+ }
976
+ }
977
+ }
978
+ exports.PickerController = PickerController;
979
+ //# sourceMappingURL=pickerController.js.map