heroshot 0.14.2 → 0.15.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.
- package/README.md +3 -1
- package/dist/cli/cli.js +195 -148
- package/dist/mcp/index.js +1 -1
- package/dist/{snippet-B6Lg_Ant.js → snippet-Fc-PkcTD.js} +15 -5
- package/editor/dist/editor.js +2226 -1891
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -30,7 +30,9 @@ npx heroshot
|
|
|
30
30
|
|
|
31
31
|
First run opens a browser with a visual editor. Pick elements, adjust padding, style borders, edit text, and add annotations (arrows, rectangles, callouts). Screenshots land in `heroshots/`, config saves to `.heroshot/config.json`. Next run regenerates everything headlessly.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
<p align="center">
|
|
34
|
+
<img src="https://github.com/omachala/heroshot/blob/main/assets/video/demo-v0.14.gif?raw=true" alt="Heroshot demo" width="720">
|
|
35
|
+
</p>
|
|
34
36
|
|
|
35
37
|
## Use in Your Docs
|
|
36
38
|
|
package/dist/cli/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as intro, D as setVerbose, E as outro, O as spinner, S as error, T as note, _ as loadConfig, a as launchBrowser, c as generateSessionKey, d as loadSession, f as saveLocalKey, g as getConfigPath, h as ensureHeroshotDirectory, k as verbose, l as getSessionPath, m as sessionExists, n as snippetAction, o as DEFAULT_VIEWPORT, p as saveSession, r as sync, s as EDITOR_DIR, u as loadLocalKey, v as saveConfig, w as log, x as generateUid, y as VIEWPORT_PRESETS } from "../snippet-
|
|
2
|
+
import { C as intro, D as setVerbose, E as outro, O as spinner, S as error, T as note, _ as loadConfig, a as launchBrowser, c as generateSessionKey, d as loadSession, f as saveLocalKey, g as getConfigPath, h as ensureHeroshotDirectory, k as verbose, l as getSessionPath, m as sessionExists, n as snippetAction, o as DEFAULT_VIEWPORT, p as saveSession, r as sync, s as EDITOR_DIR, u as loadLocalKey, v as saveConfig, w as log, x as generateUid, y as VIEWPORT_PRESETS } from "../snippet-Fc-PkcTD.js";
|
|
3
3
|
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -65,6 +65,151 @@ function dispatchHighlightJob(options) {
|
|
|
65
65
|
} }));
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/browser/toConfigScreenshot.ts
|
|
70
|
+
/**
|
|
71
|
+
* Convert ScreenshotData to Screenshot for config.
|
|
72
|
+
* Filename is derived from name at sync time - not stored in config.
|
|
73
|
+
*/
|
|
74
|
+
function toConfigScreenshot(data) {
|
|
75
|
+
return {
|
|
76
|
+
id: data.id,
|
|
77
|
+
name: data.name,
|
|
78
|
+
url: data.url,
|
|
79
|
+
selector: data.selector,
|
|
80
|
+
...data.padding && { padding: {
|
|
81
|
+
top: Math.round(data.padding.top),
|
|
82
|
+
right: Math.round(data.padding.right),
|
|
83
|
+
bottom: Math.round(data.padding.bottom),
|
|
84
|
+
left: Math.round(data.padding.left)
|
|
85
|
+
} },
|
|
86
|
+
...data.scroll && { scroll: data.scroll },
|
|
87
|
+
...data.paddingFill && { paddingFill: data.paddingFill },
|
|
88
|
+
...data.paddingColor && { paddingColor: data.paddingColor },
|
|
89
|
+
...data.elementFill && { elementFill: data.elementFill },
|
|
90
|
+
...data.elementColor && { elementColor: data.elementColor },
|
|
91
|
+
...data.textOverrides && Object.keys(data.textOverrides).length > 0 && { textOverrides: data.textOverrides },
|
|
92
|
+
...data.annotations && data.annotations.length > 0 && { annotations: data.annotations },
|
|
93
|
+
...data.borderWidth && { borderWidth: data.borderWidth },
|
|
94
|
+
...data.borderColor && { borderColor: data.borderColor },
|
|
95
|
+
...data.borderRadius && { borderRadius: data.borderRadius }
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/browser/saveCurrentConfig.ts
|
|
101
|
+
/**
|
|
102
|
+
* Save current screenshot and browser settings state to config file.
|
|
103
|
+
*/
|
|
104
|
+
function saveCurrentConfig(configPath, allScreenshots, browserSettings, hiddenElements) {
|
|
105
|
+
const config = loadConfig(configPath);
|
|
106
|
+
config.screenshots = allScreenshots.map(toConfigScreenshot);
|
|
107
|
+
if (browserSettings) {
|
|
108
|
+
config.browser = {
|
|
109
|
+
...config.browser,
|
|
110
|
+
viewport: browserSettings.viewport,
|
|
111
|
+
...browserSettings.colorScheme && { colorScheme: browserSettings.colorScheme },
|
|
112
|
+
...browserSettings.deviceScaleFactor && { deviceScaleFactor: browserSettings.deviceScaleFactor }
|
|
113
|
+
};
|
|
114
|
+
if (browserSettings.outputDirectory) config.outputDirectory = browserSettings.outputDirectory;
|
|
115
|
+
if (browserSettings.outputFormat) config.outputFormat = browserSettings.outputFormat;
|
|
116
|
+
else delete config.outputFormat;
|
|
117
|
+
if (browserSettings.jpegQuality && browserSettings.outputFormat === "jpeg") config.jpegQuality = browserSettings.jpegQuality;
|
|
118
|
+
if (browserSettings.workers) config.workers = browserSettings.workers;
|
|
119
|
+
else delete config.workers;
|
|
120
|
+
}
|
|
121
|
+
if (hiddenElements && Object.keys(hiddenElements).length > 0) config.hiddenElements = hiddenElements;
|
|
122
|
+
else delete config.hiddenElements;
|
|
123
|
+
saveConfig(configPath, config);
|
|
124
|
+
verbose("Config saved");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/browser/handleEvent.ts
|
|
129
|
+
function createEventHandler(state, browser, context) {
|
|
130
|
+
const save = () => saveCurrentConfig(state.configPath, state.allScreenshots, state.updatedBrowserSettings, state.hiddenElements);
|
|
131
|
+
return (event) => {
|
|
132
|
+
switch (event.type) {
|
|
133
|
+
case "screenshot-added":
|
|
134
|
+
state.allScreenshots.push(event.data);
|
|
135
|
+
verbose(`Added: ${event.data.name}`);
|
|
136
|
+
save();
|
|
137
|
+
break;
|
|
138
|
+
case "screenshot-updated": {
|
|
139
|
+
const index = state.allScreenshots.findIndex(({ id }) => id === event.data.id);
|
|
140
|
+
if (index !== -1) {
|
|
141
|
+
state.allScreenshots[index] = event.data;
|
|
142
|
+
verbose(`Updated: ${event.data.name}`);
|
|
143
|
+
save();
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case "screenshot-removed": {
|
|
148
|
+
const index = state.allScreenshots.findIndex(({ id }) => id === event.id);
|
|
149
|
+
if (index !== -1) {
|
|
150
|
+
const [removed] = state.allScreenshots.splice(index, 1);
|
|
151
|
+
verbose(`Removed: ${removed?.name ?? event.id}`);
|
|
152
|
+
save();
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case "screenshot-selected": {
|
|
157
|
+
const [currentPage] = context.pages();
|
|
158
|
+
if (!currentPage) break;
|
|
159
|
+
state.selectedId = event.id;
|
|
160
|
+
state.sidebarExpanded = true;
|
|
161
|
+
if (currentPage.url() === event.url) {
|
|
162
|
+
state.pendingJob = {
|
|
163
|
+
type: "highlight",
|
|
164
|
+
selector: event.selector,
|
|
165
|
+
screenshotId: event.id
|
|
166
|
+
};
|
|
167
|
+
currentPage.evaluate(dispatchHighlightJob, {
|
|
168
|
+
selector: event.selector,
|
|
169
|
+
screenshotId: event.id
|
|
170
|
+
}).catch(() => {});
|
|
171
|
+
} else {
|
|
172
|
+
state.pendingJob = {
|
|
173
|
+
type: "navigate-and-highlight",
|
|
174
|
+
url: event.url,
|
|
175
|
+
selector: event.selector,
|
|
176
|
+
screenshotId: event.id
|
|
177
|
+
};
|
|
178
|
+
currentPage.goto(event.url, { waitUntil: "domcontentloaded" }).catch(() => {});
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case "settings-updated":
|
|
183
|
+
state.updatedBrowserSettings = event.data;
|
|
184
|
+
verbose(`Settings updated: ${JSON.stringify(event.data)}`);
|
|
185
|
+
save();
|
|
186
|
+
break;
|
|
187
|
+
case "hidden-elements-updated": {
|
|
188
|
+
const { domain, selectors } = event;
|
|
189
|
+
state.hiddenElements = selectors.length === 0 ? Object.fromEntries(Object.entries(state.hiddenElements).filter(([k]) => k !== domain)) : {
|
|
190
|
+
...state.hiddenElements,
|
|
191
|
+
[domain]: selectors
|
|
192
|
+
};
|
|
193
|
+
verbose(`Hidden elements updated for ${domain}: ${selectors.length} selectors`);
|
|
194
|
+
save();
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case "job-complete":
|
|
198
|
+
state.pendingJob = null;
|
|
199
|
+
break;
|
|
200
|
+
case "done":
|
|
201
|
+
(async () => {
|
|
202
|
+
try {
|
|
203
|
+
saveSession(await context.storageState(), state.sessionKey);
|
|
204
|
+
verbose("Session saved");
|
|
205
|
+
} catch {}
|
|
206
|
+
await browser.close();
|
|
207
|
+
})();
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
68
213
|
//#endregion
|
|
69
214
|
//#region src/browser/schema.ts
|
|
70
215
|
/**
|
|
@@ -118,7 +263,11 @@ const browserSettingsSchema = z.object({
|
|
|
118
263
|
height: z.number()
|
|
119
264
|
}),
|
|
120
265
|
colorScheme: z.enum(["light", "dark"]).optional(),
|
|
121
|
-
deviceScaleFactor: z.number().optional()
|
|
266
|
+
deviceScaleFactor: z.number().optional(),
|
|
267
|
+
outputDirectory: z.string().optional(),
|
|
268
|
+
outputFormat: z.enum(["png", "jpeg"]).optional(),
|
|
269
|
+
jpegQuality: z.number().int().min(1).max(100).optional(),
|
|
270
|
+
workers: z.number().int().min(1).optional()
|
|
122
271
|
});
|
|
123
272
|
/** Schema for toolbar events (discriminated union) */
|
|
124
273
|
const toolbarEventSchema = z.discriminatedUnion("type", [
|
|
@@ -144,6 +293,11 @@ const toolbarEventSchema = z.discriminatedUnion("type", [
|
|
|
144
293
|
type: z.literal("settings-updated"),
|
|
145
294
|
data: browserSettingsSchema
|
|
146
295
|
}),
|
|
296
|
+
z.object({
|
|
297
|
+
type: z.literal("hidden-elements-updated"),
|
|
298
|
+
domain: z.string(),
|
|
299
|
+
selectors: z.array(z.string())
|
|
300
|
+
}),
|
|
147
301
|
z.object({ type: z.literal("job-complete") }),
|
|
148
302
|
z.object({ type: z.literal("done") })
|
|
149
303
|
]);
|
|
@@ -202,7 +356,7 @@ async function injectToolbar(page, options) {
|
|
|
202
356
|
}
|
|
203
357
|
}
|
|
204
358
|
async function doInjectToolbar(page, options) {
|
|
205
|
-
const { screenshots, pendingJob, selectedId, sidebarExpanded, onEvent } = options;
|
|
359
|
+
const { screenshots, settings, pendingJob, selectedId, sidebarExpanded, hiddenElements, onEvent } = options;
|
|
206
360
|
if (!exposedPages.has(page)) {
|
|
207
361
|
await page.exposeFunction("__heroshotEmit", (eventJson) => {
|
|
208
362
|
onEvent(toolbarEventSchema.parse(JSON.parse(eventJson)));
|
|
@@ -214,14 +368,16 @@ async function doInjectToolbar(page, options) {
|
|
|
214
368
|
return;
|
|
215
369
|
}
|
|
216
370
|
const scriptContent = readFileSync(`${EDITOR_DIR}/dist/editor.js`, "utf8");
|
|
217
|
-
await page.evaluate(`(function(screenshots, pendingJob, selectedId, sidebarExpanded, scriptContent) {
|
|
371
|
+
await page.evaluate(`(function(screenshots, settings, pendingJob, selectedId, sidebarExpanded, hiddenElements, scriptContent) {
|
|
218
372
|
// Initialize __heroshot global namespace
|
|
219
373
|
globalThis.__heroshot = {
|
|
220
374
|
initialized: false,
|
|
221
375
|
screenshots: screenshots,
|
|
376
|
+
settings: settings,
|
|
222
377
|
pendingJob: pendingJob,
|
|
223
378
|
selectedId: selectedId,
|
|
224
379
|
sidebarExpanded: sidebarExpanded,
|
|
380
|
+
hiddenElements: hiddenElements,
|
|
225
381
|
// This emit function is why we use string evaluation.
|
|
226
382
|
// As a nested function property, it would get __name() wrapped
|
|
227
383
|
// if we used a typed function approach.
|
|
@@ -235,56 +391,7 @@ async function doInjectToolbar(page, options) {
|
|
|
235
391
|
var script = document.createElement('script');
|
|
236
392
|
script.textContent = scriptContent;
|
|
237
393
|
document.body.appendChild(script);
|
|
238
|
-
})(${JSON.stringify(screenshots)}, ${JSON.stringify(pendingJob)}, ${JSON.stringify(selectedId)}, ${JSON.stringify(sidebarExpanded)}, ${JSON.stringify(scriptContent)})`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
//#endregion
|
|
242
|
-
//#region src/browser/toConfigScreenshot.ts
|
|
243
|
-
/**
|
|
244
|
-
* Convert ScreenshotData to Screenshot for config.
|
|
245
|
-
* Filename is derived from name at sync time - not stored in config.
|
|
246
|
-
*/
|
|
247
|
-
function toConfigScreenshot(data) {
|
|
248
|
-
return {
|
|
249
|
-
id: data.id,
|
|
250
|
-
name: data.name,
|
|
251
|
-
url: data.url,
|
|
252
|
-
selector: data.selector,
|
|
253
|
-
...data.padding && { padding: {
|
|
254
|
-
top: Math.round(data.padding.top),
|
|
255
|
-
right: Math.round(data.padding.right),
|
|
256
|
-
bottom: Math.round(data.padding.bottom),
|
|
257
|
-
left: Math.round(data.padding.left)
|
|
258
|
-
} },
|
|
259
|
-
...data.scroll && { scroll: data.scroll },
|
|
260
|
-
...data.paddingFill && { paddingFill: data.paddingFill },
|
|
261
|
-
...data.paddingColor && { paddingColor: data.paddingColor },
|
|
262
|
-
...data.elementFill && { elementFill: data.elementFill },
|
|
263
|
-
...data.elementColor && { elementColor: data.elementColor },
|
|
264
|
-
...data.textOverrides && Object.keys(data.textOverrides).length > 0 && { textOverrides: data.textOverrides },
|
|
265
|
-
...data.annotations && data.annotations.length > 0 && { annotations: data.annotations },
|
|
266
|
-
...data.borderWidth && { borderWidth: data.borderWidth },
|
|
267
|
-
...data.borderColor && { borderColor: data.borderColor },
|
|
268
|
-
...data.borderRadius && { borderRadius: data.borderRadius }
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
//#endregion
|
|
273
|
-
//#region src/browser/saveCurrentConfig.ts
|
|
274
|
-
/**
|
|
275
|
-
* Save current screenshot and browser settings state to config file.
|
|
276
|
-
*/
|
|
277
|
-
function saveCurrentConfig(configPath, allScreenshots, browserSettings) {
|
|
278
|
-
const config = loadConfig(configPath);
|
|
279
|
-
config.screenshots = allScreenshots.map(toConfigScreenshot);
|
|
280
|
-
if (browserSettings) config.browser = {
|
|
281
|
-
...config.browser,
|
|
282
|
-
viewport: browserSettings.viewport,
|
|
283
|
-
...browserSettings.colorScheme && { colorScheme: browserSettings.colorScheme },
|
|
284
|
-
...browserSettings.deviceScaleFactor && { deviceScaleFactor: browserSettings.deviceScaleFactor }
|
|
285
|
-
};
|
|
286
|
-
saveConfig(configPath, config);
|
|
287
|
-
verbose("Config saved");
|
|
394
|
+
})(${JSON.stringify(screenshots)}, ${JSON.stringify(settings)}, ${JSON.stringify(pendingJob)}, ${JSON.stringify(selectedId)}, ${JSON.stringify(sidebarExpanded)}, ${JSON.stringify(hiddenElements)}, ${JSON.stringify(scriptContent)})`);
|
|
288
395
|
}
|
|
289
396
|
|
|
290
397
|
//#endregion
|
|
@@ -311,12 +418,24 @@ async function setup(options = {}) {
|
|
|
311
418
|
}
|
|
312
419
|
}
|
|
313
420
|
const allScreenshots = configToScreenshotData(config.screenshots);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
421
|
+
const initialSettings = {
|
|
422
|
+
viewport,
|
|
423
|
+
...options.colorScheme && { colorScheme: options.colorScheme },
|
|
424
|
+
...config.browser?.deviceScaleFactor && { deviceScaleFactor: config.browser.deviceScaleFactor },
|
|
425
|
+
outputDirectory: config.outputDirectory,
|
|
426
|
+
outputFormat: config.outputFormat,
|
|
427
|
+
jpegQuality: config.jpegQuality,
|
|
428
|
+
workers: config.workers
|
|
429
|
+
};
|
|
430
|
+
const browserState = {
|
|
431
|
+
allScreenshots,
|
|
432
|
+
pendingJob: null,
|
|
433
|
+
selectedId: null,
|
|
434
|
+
sidebarExpanded: false,
|
|
435
|
+
updatedBrowserSettings: null,
|
|
436
|
+
hiddenElements: config.hiddenElements ?? {},
|
|
437
|
+
configPath,
|
|
438
|
+
sessionKey
|
|
320
439
|
};
|
|
321
440
|
const { browser, context } = await launchBrowser({
|
|
322
441
|
headless: false,
|
|
@@ -325,88 +444,22 @@ async function setup(options = {}) {
|
|
|
325
444
|
colorScheme: options.colorScheme
|
|
326
445
|
});
|
|
327
446
|
setupSpinner.stop("Browser ready");
|
|
328
|
-
const handleEvent = (
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
allScreenshots[index] = event.data;
|
|
339
|
-
verbose(`Updated: ${event.data.name}`);
|
|
340
|
-
save();
|
|
341
|
-
}
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
case "screenshot-removed": {
|
|
345
|
-
const index = allScreenshots.findIndex(({ id }) => id === event.id);
|
|
346
|
-
if (index !== -1) {
|
|
347
|
-
const [removed] = allScreenshots.splice(index, 1);
|
|
348
|
-
verbose(`Removed: ${removed?.name ?? event.id}`);
|
|
349
|
-
save();
|
|
350
|
-
}
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
case "screenshot-selected": {
|
|
354
|
-
const [currentPage] = context.pages();
|
|
355
|
-
if (!currentPage) break;
|
|
356
|
-
selectedId = event.id;
|
|
357
|
-
sidebarExpanded = true;
|
|
358
|
-
if (currentPage.url() === event.url) {
|
|
359
|
-
pendingJob = {
|
|
360
|
-
type: "highlight",
|
|
361
|
-
selector: event.selector,
|
|
362
|
-
screenshotId: event.id
|
|
363
|
-
};
|
|
364
|
-
currentPage.evaluate(dispatchHighlightJob, {
|
|
365
|
-
selector: event.selector,
|
|
366
|
-
screenshotId: event.id
|
|
367
|
-
}).catch(() => {});
|
|
368
|
-
} else {
|
|
369
|
-
pendingJob = {
|
|
370
|
-
type: "navigate-and-highlight",
|
|
371
|
-
url: event.url,
|
|
372
|
-
selector: event.selector,
|
|
373
|
-
screenshotId: event.id
|
|
374
|
-
};
|
|
375
|
-
currentPage.goto(event.url, { waitUntil: "domcontentloaded" }).catch(() => {});
|
|
376
|
-
}
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
case "settings-updated":
|
|
380
|
-
updatedBrowserSettings = event.data;
|
|
381
|
-
verbose(`Settings updated: ${JSON.stringify(event.data)}`);
|
|
382
|
-
save();
|
|
383
|
-
break;
|
|
384
|
-
case "job-complete":
|
|
385
|
-
pendingJob = null;
|
|
386
|
-
break;
|
|
387
|
-
case "done":
|
|
388
|
-
(async () => {
|
|
389
|
-
try {
|
|
390
|
-
saveSession(await context.storageState(), sessionKey);
|
|
391
|
-
verbose("Session saved");
|
|
392
|
-
} catch {}
|
|
393
|
-
await browser.close();
|
|
394
|
-
})();
|
|
395
|
-
break;
|
|
396
|
-
}
|
|
397
|
-
};
|
|
447
|
+
const handleEvent = createEventHandler(browserState, browser, context);
|
|
448
|
+
const injectOptions = () => ({
|
|
449
|
+
screenshots: browserState.allScreenshots,
|
|
450
|
+
settings: initialSettings,
|
|
451
|
+
pendingJob: browserState.pendingJob,
|
|
452
|
+
selectedId: browserState.selectedId,
|
|
453
|
+
sidebarExpanded: browserState.sidebarExpanded,
|
|
454
|
+
hiddenElements: browserState.hiddenElements,
|
|
455
|
+
onEvent: handleEvent
|
|
456
|
+
});
|
|
398
457
|
const setupPage = (page) => {
|
|
399
458
|
page.on("domcontentloaded", async () => {
|
|
400
459
|
const url = page.url();
|
|
401
460
|
if (!url.startsWith("http")) return;
|
|
402
461
|
try {
|
|
403
|
-
await injectToolbar(page,
|
|
404
|
-
screenshots: allScreenshots,
|
|
405
|
-
pendingJob,
|
|
406
|
-
selectedId,
|
|
407
|
-
sidebarExpanded,
|
|
408
|
-
onEvent: handleEvent
|
|
409
|
-
});
|
|
462
|
+
await injectToolbar(page, injectOptions());
|
|
410
463
|
verbose(`Toolbar injected on ${url}`);
|
|
411
464
|
} catch (error) {
|
|
412
465
|
verbose(`Toolbar injection failed on ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -418,22 +471,16 @@ async function setup(options = {}) {
|
|
|
418
471
|
if (context.pages().length === 0) browser.close().catch(() => {});
|
|
419
472
|
});
|
|
420
473
|
};
|
|
421
|
-
|
|
474
|
+
const initPage = (page) => {
|
|
422
475
|
setupPage(page);
|
|
423
476
|
handlePageClose(page);
|
|
424
|
-
}
|
|
477
|
+
};
|
|
478
|
+
context.on("page", initPage);
|
|
425
479
|
const page = context.pages()[0] ?? await context.newPage();
|
|
426
|
-
|
|
427
|
-
handlePageClose(page);
|
|
480
|
+
initPage(page);
|
|
428
481
|
await page.goto("https://heroshot.sh/welcome", { waitUntil: "domcontentloaded" });
|
|
429
482
|
try {
|
|
430
|
-
await injectToolbar(page,
|
|
431
|
-
screenshots: allScreenshots,
|
|
432
|
-
pendingJob,
|
|
433
|
-
selectedId,
|
|
434
|
-
sidebarExpanded,
|
|
435
|
-
onEvent: handleEvent
|
|
436
|
-
});
|
|
483
|
+
await injectToolbar(page, injectOptions());
|
|
437
484
|
verbose("Toolbar injected on welcome page");
|
|
438
485
|
} catch (error) {
|
|
439
486
|
verbose(`Initial toolbar injection failed: ${error instanceof Error ? error.message : String(error)}`);
|
package/dist/mcp/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as loadConfig, b as screenshotSchema, g as getConfigPath, i as filterScreenshots, r as sync, t as generateSnippets, v as saveConfig, x as generateUid } from "../snippet-
|
|
2
|
+
import { _ as loadConfig, b as screenshotSchema, g as getConfigPath, i as filterScreenshots, r as sync, t as generateSnippets, v as saveConfig, x as generateUid } from "../snippet-Fc-PkcTD.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -190,7 +190,7 @@ const resizeActionSchema = z.object({
|
|
|
190
190
|
}).describe("Resize the browser viewport mid-flow.");
|
|
191
191
|
const hideActionSchema = z.object({
|
|
192
192
|
type: z.literal("hide"),
|
|
193
|
-
selectors: z.array(z.string()).describe("Element selectors to hide (
|
|
193
|
+
selectors: z.array(z.string()).describe("Element selectors to hide (visibility: hidden)")
|
|
194
194
|
}).describe("Hide elements from screenshot. Use to remove cookie banners, chat widgets, ads.");
|
|
195
195
|
/** Union of all supported action types. Actions execute sequentially before screenshot. */
|
|
196
196
|
const actionSchema = z.discriminatedUnion("type", [
|
|
@@ -370,7 +370,8 @@ const configSchema = z.object({
|
|
|
370
370
|
jpegQuality: z.number().int().min(1).max(100).default(80).describe("JPEG compression quality (1-100), only used when outputFormat is \"jpeg\""),
|
|
371
371
|
browser: browserSchema.optional().describe("Default browser settings applied to all screenshots"),
|
|
372
372
|
workers: z.number().int().min(1).optional().describe("Number of parallel capture workers (default: 1)"),
|
|
373
|
-
screenshots: z.array(screenshotSchema).default([]).describe("Screenshot definitions")
|
|
373
|
+
screenshots: z.array(screenshotSchema).default([]).describe("Screenshot definitions"),
|
|
374
|
+
hiddenElements: z.record(z.string(), z.array(z.string())).optional().describe("Elements to hide per domain (hostname → CSS selectors)")
|
|
374
375
|
});
|
|
375
376
|
|
|
376
377
|
//#endregion
|
|
@@ -793,7 +794,8 @@ function buildCaptureOptions(config, viewportOnly) {
|
|
|
793
794
|
return {
|
|
794
795
|
format: config.outputFormat ?? "png",
|
|
795
796
|
quality: config.jpegQuality,
|
|
796
|
-
fullPage: !viewportOnly
|
|
797
|
+
fullPage: !viewportOnly,
|
|
798
|
+
hiddenElements: config.hiddenElements
|
|
797
799
|
};
|
|
798
800
|
}
|
|
799
801
|
/**
|
|
@@ -957,11 +959,11 @@ function executeHandleDialog(page, action) {
|
|
|
957
959
|
|
|
958
960
|
//#endregion
|
|
959
961
|
//#region src/sync/actions/hide.ts
|
|
960
|
-
/** Hide elements by setting
|
|
962
|
+
/** Hide elements by setting visibility: hidden (preserves layout) */
|
|
961
963
|
async function executeHide(page, action) {
|
|
962
964
|
if (action.type !== "hide") return;
|
|
963
965
|
for (const selector of action.selectors) await page.locator(selector).evaluateAll((elements) => {
|
|
964
|
-
for (const element of elements) element.style.
|
|
966
|
+
for (const element of elements) element.style.setProperty("visibility", "hidden", "important");
|
|
965
967
|
});
|
|
966
968
|
}
|
|
967
969
|
|
|
@@ -1861,6 +1863,14 @@ async function captureScreenshot(page, screenshot, outputDirectory, captureOptio
|
|
|
1861
1863
|
...navResult,
|
|
1862
1864
|
filename
|
|
1863
1865
|
};
|
|
1866
|
+
if (captureOptions.hiddenElements) {
|
|
1867
|
+
const { hiddenElements: hiddenByDomain } = captureOptions;
|
|
1868
|
+
const { hostname } = new URL(url);
|
|
1869
|
+
if (hiddenByDomain[hostname]?.length) await executeHide(page, {
|
|
1870
|
+
type: "hide",
|
|
1871
|
+
selectors: hiddenByDomain[hostname]
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1864
1874
|
if (screenshot.actions && screenshot.actions.length > 0) try {
|
|
1865
1875
|
await executeActions(page, screenshot.actions);
|
|
1866
1876
|
await page.waitForTimeout(500);
|