+6
-0
@@ -8,2 +8,8 @@ # Changelog | ||
| ## [2.2.3] - 2026-06-28 | ||
| ### Fixed | ||
| - GIF frame auto-padding (centered, transparent) instead of throwing on dimension mismatch | ||
| - Auto-append file extension in outPath() and PDF capture | ||
| ## [2.2.0] - 2026-06-27 | ||
@@ -10,0 +16,0 @@ |
+31
-13
@@ -27,17 +27,35 @@ /** | ||
| fs.mkdirSync(path.dirname(outputPath), { recursive: true }); | ||
| // Read first frame to auto-detect dimensions | ||
| const first = readPng(frames[0].filePath); | ||
| const width = options?.width ?? first.width; | ||
| const height = options?.height ?? first.height; | ||
| const gif = new GIFEncoder(width, height, { loop }); | ||
| // Read all frames to auto-detect max canvas dimensions | ||
| const framesData = frames.map(f => ({ frame: f, png: readPng(f.filePath) })); | ||
| let canvasWidth = options?.width ?? Math.max(...framesData.map(f => f.png.width)); | ||
| let canvasHeight = options?.height ?? Math.max(...framesData.map(f => f.png.height)); | ||
| const gif = new GIFEncoder(canvasWidth, canvasHeight, { loop }); | ||
| try { | ||
| for (const frame of frames) { | ||
| for (const { frame, png } of framesData) { | ||
| const delay = (frame.delay ?? 80) * 10; // centiseconds → ms | ||
| const png = readPng(frame.filePath); | ||
| if (png.width !== width || png.height !== height) { | ||
| throw new GifError(`Frame dimensions (${png.width}x${png.height}) must match ` + | ||
| `GIF canvas (${width}x${height}): ${frame.filePath}`); | ||
| let data = png.data; | ||
| let w = png.width; | ||
| let h = png.height; | ||
| // Auto-pad smaller frames to match canvas dimensions | ||
| if (w !== canvasWidth || h !== canvasHeight) { | ||
| const padded = new Uint8Array(canvasWidth * canvasHeight * 4); | ||
| padded.fill(0); // transparent black | ||
| const ox = Math.floor((canvasWidth - w) / 2); | ||
| const oy = Math.floor((canvasHeight - h) / 2); | ||
| for (let y = 0; y < h; y++) { | ||
| for (let x = 0; x < w; x++) { | ||
| const si = (y * w + x) * 4; | ||
| const di = ((y + oy) * canvasWidth + (x + ox)) * 4; | ||
| padded[di] = data[si]; | ||
| padded[di + 1] = data[si + 1]; | ||
| padded[di + 2] = data[si + 2]; | ||
| padded[di + 3] = data[si + 3]; | ||
| } | ||
| } | ||
| data = padded; | ||
| w = canvasWidth; | ||
| h = canvasHeight; | ||
| } | ||
| const palette = quantize(png.data, 256); | ||
| const idxData = applyPalette(png.data, palette); | ||
| const palette = quantize(data, 256); | ||
| const idxData = applyPalette(data, palette); | ||
| const flatPalette = new Uint8Array(palette.length * 3); | ||
@@ -49,3 +67,3 @@ for (let i = 0; i < palette.length; i++) { | ||
| } | ||
| gif.writeFrame(idxData, width, height, { delay, palette: flatPalette, transparent: false }); | ||
| gif.writeFrame(idxData, w, h, { delay, palette: flatPalette, transparent: false }); | ||
| } | ||
@@ -52,0 +70,0 @@ gif.finish(); |
+7
-2
@@ -164,3 +164,6 @@ #!/usr/bin/env node | ||
| if (name) { | ||
| return resolveSafePath(OUTPUT_DIR, name); | ||
| // Append default extension if name lacks one (Playwright needs ext for MIME) | ||
| const hasExt = /\.(png|jpe?g|gif|pdf)$/i.test(name); | ||
| const finalName = hasExt ? name : `${name}.${ext()}`; | ||
| return resolveSafePath(OUTPUT_DIR, finalName); | ||
| } | ||
@@ -303,3 +306,5 @@ return path.join(OUTPUT_DIR, `${prefix}-${Date.now()}.${ext()}`); | ||
| const filename = output || `pdf-${Date.now()}.pdf`; | ||
| const p = resolveSafePath(OUTPUT_DIR, filename); | ||
| const hasExt = /\.(pdf)$/i.test(filename); | ||
| const finalName = hasExt ? filename : `${filename}.pdf`; | ||
| const p = resolveSafePath(OUTPUT_DIR, finalName); | ||
| await capturePdf(url, p, fullPage, width, height, config); | ||
@@ -306,0 +311,0 @@ logger.audit({ event: AuditEventType.CapturePdf, severity: "info", detail: `URL: ${url}`, source: "capture_pdf" }); |
+1
-1
| { | ||
| "name": "snapmcp", | ||
| "version": "2.2.2", | ||
| "version": "2.2.3", | ||
| "description": "All-in-one MCP server for visual captures: terminal, code, browser, markdown, diffs, HTML, and PDF — via Playwright", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
187743
0.76%4016
0.58%