agentsclimarketplace

Media screenshots

Skill derio-net/blog-craft/skills/media-screenshots

Fill the screenshot placeholders in a blog post by automatically capturing them from a real browser, then replacing the placeholders with rendered shortcodes. The autonomous-screenshot companion to /media. Use when the user wants to grab the screenshots for a post, fill in the screenshot placeholders, capture the dashboards/web-UIs a post references, or says "take the screenshots for this post", "fill the media placeholders with screenshots", or "screenshot everything this post needs". Works in any blog using the shared <!-- MEDIA: --> placeholder contract (frank's blog and blog-craft blogs alike). Handles only screenshot-type placeholders; /media still covers asciinema, photos, and youtube.From its SKILL.md

Install
npx -y skills add derio-net/blog-craft --skill media-screenshots

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

8.1 KB, as published. Nobody here has run it

Media Screenshots

The bridge between a post's screenshot placeholders and a real browser. For each screenshot placeholder in a post, this skill: confirms the URL with you, captures it via the generic browser-screenshot skill, drops the PNG at the placeholder's exact src= filename, optimizes it, then replaces the placeholder with its rendered shortcode.

Announce at start: "Using media-screenshots to capture and fill screenshot placeholders in <post>."

Why this is separate from /media

/media already inserts assets and records asciinema. What it doesn't do is go get a screenshot for you. This skill does exactly that one thing — drive a browser to produce the screenshot asset — and then hands off to the same replacement logic /media uses. Keeping it separate keeps /media lean and lets the screenshot path improve on its own.

It is contract-driven, not blog-specific: all it needs is a post with the shared placeholder pair, so it runs in frank's repo-local blog and in any blog-craft blog without a .blog-craft.yaml gate.

The placeholder contract

Screenshot placeholders are a two-line comment pair:

<!-- MEDIA: screenshot | <description> | <capture instructions, often a URL> -->
<!-- {{</* screenshot src="<filename>.png" caption="..." */>}} -->

The src="<filename>.png" is the pin: produce a file at that exact name in the post's bundle directory and the replacement just works.

Workflow

Step 1 — Locate the post and its screenshot placeholders

If the user named a post, target its bundle directory (the folder containing index.md). Otherwise, scan for screenshot placeholders and let the user pick:

grep -rn "<!-- MEDIA: screenshot" <content-dir> --include="*.md"

(<content-dir> is blog/content in frank, content/docs in a blog-craft blog. Walk up from the post path if you're unsure where the root is.)

For each screenshot placeholder, read the pair and extract:

  • src — the target filename (from the shortcode line)
  • description — what the screenshot should show
  • capture instructions — usually contains a URL and any framing hints

Step 2 — Confirm the URL and framing with the user (one per placeholder)

Do not silently guess URLs. For each screenshot placeholder, show the user the description + instructions and the URL you parsed (if any), and ask them to confirm or supply:

  • the URL to capture
  • the mode: viewport (default, ~1200px), full (whole page), or element (one region — then ask for a CSS selector)
  • dark mode (default yes for dashboards / the house style)
  • whether the target needs login, and if so which env vars hold the credentials (see the auth note below)

This human-in-the-loop step is deliberate: the post's instructions describe intent, but the exact route/panel/state is a judgement call the author should make. Confirm all of them up front so the capture run is unattended.

Same target, different posts → differentiate the framing. When two placeholders (e.g. a building post and its operating companion) resolve to the same URL, do NOT ship the same image twice — vary the mode (full dashboard vs. an element crop of one panel), the route, or the state, and make each caption describe what its framing shows. Identical twins read as a mistake to the reader and will come back as review feedback.

Step 3 — Capture each via the browser-screenshot skill

Use the browser-screenshot skill for the actual capture. Set SHOT_OUT to the placeholder's src inside the bundle so the file lands where the replacement expects it:

SHOT_URL="<confirmed url>" \
SHOT_OUT="<post-bundle>/<src-from-placeholder>" \
SHOT_MODE="viewport" SHOT_WIDTH=1200 SHOT_DARK=1 \
  browser-harness < ~/.agents/skills/browser-screenshot/scripts/capture.py

For element mode add SHOT_MODE=element SHOT_SELECTOR="<css>"; for full-page use SHOT_MODE=full. For auth-walled targets pass the SHOT_LOGIN_* params (read browser-screenshot's SKILL.md). On the Mac, bracket the whole batch with brave-clawdia / brave-clawdia-stop once — not per shot.

If a capture returns AUTH_WALL (exit 3), stop and tell the user which target needs credentials or a richer login recipe; don't fill that placeholder.

Step 4 — Review each capture, then optimize (500KB budget)

Look at every PNG before accepting it (Read the image, don't just file it). A capture can be a valid PNG of plausible size and still be wrong:

  • Blank/spinner captures — async-rendering dashboards (Grafana et al.) produce a dark empty rectangle if shot before panels render. file and wc -c both pass; only eyeballing catches it. Recapture with a SHOT_WAIT_SELECTOR on a data-bearing element (see browser-screenshot's gotchas).
  • Third-party / private names — list-style views (repo explorers, secret folder listings, org pickers) can expose names that must not be published. Check every list view. If a narrower route can't avoid it, redact in-image (e.g. PIL rectangle fill matching the row background) before the file enters the repo — and capture secret-bearing UIs to /tmp first, verify the crop, then copy into the bundle.
  • Masked values — UIs that claim to mask secrets (****) usually do, but verify on the actual capture, not the claim.

Then optimize:

asset="<post-bundle>/<src>"
[ "$(wc -c < "$asset")" -gt 512000 ] && echo "WARN: over 500KB"
if command -v pngquant >/dev/null; then
  pngquant --quality=65-80 --strip --force --output "$asset" "$asset"
elif command -v optipng >/dev/null; then
  optipng -o3 "$asset"
else
  echo "WARN: no pngquant/optipng — skipping optimization"
fi
file "$asset"   # expect: PNG image data

If it's still over budget after optimization, recapture with SHOT_SCALE=1 (if you used 2) or viewport instead of full.

Step 5 — Replace the placeholders with shortcodes

Run the bundled, idempotent helper. It replaces every placeholder pair whose asset is now present and leaves the rest untouched:

python3 <skill-dir>/scripts/media-fill.py "<post-bundle>"

(<skill-dir> is this skill's directory. The bundled media-fill.py mirrors blog-craft's tools/media-fill.py, so this works even in frank's blog where that tool isn't on disk.)

Expected output: filled N placeholder(s) in <bundle>/index.md, with any still-missing assets reported as skipped.

Step 6 — Verify the Hugo build

Find the blog root (the dir with hugo.toml/config.toml/.blog-craft.yaml) and build:

( cd "<blog-root>" && hugo --minify 2>&1 | tail -5 )

Confirm exit 0 and no ERROR lines. Suggest the user preview with their blog's dev-server command if they want to eyeball the rendered figures.

Standards (shared with /media)

  • PNG: max 500KB after optimization; kebab-case filename matching src=
  • Caption: keep the placeholder's caption=; frank requires one, blog-craft recommends it
  • Dark mode: preferred for dashboards and the house screenshot style
  • Only screenshots: leave asciinema/photo/youtube placeholders for /media — this skill ignores them

Reference

  • Generic capture engine: the browser-screenshot skill (modes, auth, gotchas)
  • Replacement logic: scripts/media-fill.py (this skill) — mirror of blog-craft tools/media-fill.py
  • Placeholder format: <!-- MEDIA: <type> | <description> | <instructions> -->

Keep looking

Skills are one crate of 626,474. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.