enhance-readme — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited enhance-readme (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
### Which enhance skill? (surface router)
>
| Your surface | Use | |:-------------|:----| | Web product page / dashboard — composition, hierarchy, spacing, motion |enhance-web-ui| | Web product page — UX heuristics, flows, data wiring |enhance-web-ux| | Web landing / marketing / portfolio (greenfield, anti-slop) |enhance-web-landing| | Web existing site upgrade (audit-first, preserve behavior) |enhance-web-redesign| | Web 3D / WebGL / cinematic scroll on an existing site (audit-first) |enhance-web-web3d| | React Native screen (Expo / bare) |mobile-rn-screen| | Capacitor / hybrid shell (one web app shipped to iOS + Android) |enhance-capacitor-ui(axis architecture first) → then the web or rn skill | | Repo README showcase |enhance-readme|
>
You are here: `enhance-readme`. Native iOS/Android (SwiftUI / Compose, no web layer) is out of scope for all of these — use Apple HIG / Material directly.
Add a theme-aware hero image and a tour grid to a project's README so the repo advertises itself visually instead of being a wall of text.
Use the live production URL when one exists. It shows real data, no dev banners, and matches what visitors will see if they click through.
Always capture both dark AND light mode for any image that goes in the hero. GitHub renders the README in both themes; broken contrast on one of them looks worse than no image at all.
Save to `docs/screenshots/` at the repo root. Never commit.playwright-mcp/artifacts (yml snapshots, console logs) — add it to.gitignore.
Hard limit: 10 MB per image, 25 MB per file in markdown. Soft target: under 5 MB per image. PNGs at 1600x1000 viewport land around 200–500 KB each.
Use `<picture>` with `prefers-color-scheme` media queries for theme-aware swap. Verified working on github.com README rendering as of 2024+.
Copy and track:
- [ ] Step 1: Detect demo URL, login flow, theme toggle mechanism
- [ ] Step 2: Capture screenshots (Playwright MCP, 1600x1000 viewport)
- [ ] Step 3: Generate hero + tour markdown (run scripts/generate-readme-blocks.mjs)
- [ ] Step 4: Refine captions and weave into README
- [ ] Step 5: Update tech badges + Tech Stack table to current versions
- [ ] Step 6: Add .playwright-mcp/ to .gitignore, commit, push
- [ ] Step 7 (optional): Record guided-tour GIF (run scripts/record-readme-tour.mjs)Read the existing README for a "Demo" or "Live demo" link. Fall back to package.json homepage field, then vercel.json / .github/workflows/deploy.yml for hosting hints.
If no live URL exists, start the local dev server (npm run dev / npm start / equivalent) and use that. Note the port and base path.
Look for theme switching:
tailwind.config.* and CSS files for .dark class usage → toggle with document.documentElement.classList.toggle('dark')[data-theme="dark"] → toggle with document.documentElement.dataset.theme = 'light'localStorage keys: theme, color-mode, prefers-color-schemeConfirm credentials if the app has auth. Most demos use admin/demo, test/test, or there's a hint on the login page.
Use the Playwright MCP. Always read the `protocol-browser-anti-stall` skill first if the user has it — never block the browser for more than 3 seconds at a time.
browser_resize { width: 1600, height: 1000 }browser_navigate { url: <demo-url> }
browser_wait_for { time: 2 }
browser_snapshot # find login refs
browser_fill_form # username + password
browser_click # log in
browser_wait_for { time: 2 }
browser_take_screenshot { filename: "<page>-dark.png" }Repeat for each feature page you want in the tour (analytics, settings, list views, etc.). Aim for 1 hero page + 3 tour pages = 4 cells.
browser_evaluate {
function: "() => { document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light');
localStorage.setItem('theme', 'light');
return 'ok'; }"
}
browser_wait_for { time: 2 }
browser_take_screenshot { filename: "<page>-light.png" }Adjust the JS if the app uses data-theme or a different localStorage key (detected in Step 1).
docs/screenshots/Playwright saves to the working directory. Move them:
mkdir -p docs/screenshots
mv *-dark.png *-light.png docs/screenshots/Use kebab-case with a -dark / -light suffix:
docs/screenshots/
dashboard-dark.png # hero (auto-detected by name)
dashboard-light.png
analytics-dark.png
analytics-light.png # optional: only needed if used in hero
projects-dark.png
ai-palette-dark.png # tour-only cells need just the dark variantThe generator script picks the hero by looking for these keywords (in order): hero, dashboard, home, landing, overview, main. Override with --hero=<basename> if needed.
Run from the repo root:
node ~/.cursor/skills/enhance-readme/scripts/generate-readme-blocks.mjs --demo-url=https://your-live-demo.example.com/Optional flags:
--dir=docs/screenshots (default)--hero=dashboard (force a specific basename as hero, no -dark suffix)The script:
docs/screenshots/*-dark with its *-light siblingIf any file exceeds 10 MB the script exits non-zero — compress with oxipng -o4 docs/screenshots/*.png (lossless) or re-capture at lower quality.
Paste the HERO block directly under the badges, replacing any existing tagline:
<div align="center">
# ProjectName
![badges...]
**A one-line tagline that nails what this is.**
A second-line elaboration with the most distinctive features (3–5 keywords).
[HERO BLOCK FROM SCRIPT GOES HERE]
</div>
---Paste the TOUR block right after the hero, before the existing Demo / Features / Getting Started sections:
[TOUR BLOCK FROM SCRIPT GOES HERE]
---
## Demo
...The script generates <b>Page Name</b> · TODO short caption. Replace each TODO with a one-line technical description that mentions a specific library or pattern visible in the screenshot. Keep the tone natural and slightly playful — no marketing fluff.
Good caption pattern:
<b>Analytics</b> · Recharts v3 with an 8-color OKLCH palette, split-scale YoY
(revenue vs counts), donut hover with center valueBad caption pattern:
<b>Analytics</b> · insights to drive your business forward!While editing the README, sync the badges and Tech Stack table to actual package.json versions. Common drift:
Use shields.io URLs:


Add to .gitignore (create if missing):
# Playwright MCP browser session artifacts
.playwright-mcp/Commit with a conventional-commits message:
docs(readme): hero showcase + tour grid with theme-aware screenshots
- Add `<picture>` hero auto-swapping between dark/light, linked to live demo
- Add N-cell Tour grid with one-line captions
- Save M screenshots to docs/screenshots/ (~X MB total)
- Update tech badges to current versions
- Ignore .playwright-mcp/ session artifactsThe README change does not require a redeploy of the app (it's repo-only). Nothing to verify beyond the GitHub render.
A short autoplaying GIF placed above the static screenshots gives a clearer feel for the app than any single frame. GitHub renders inline GIFs up to 10 MB on every README, no <video> workarounds needed.
The companion script handles everything: launches a headless Chromium, dismisses onboarding overlays, optionally logs in, runs an editorial 4-stop scroll tour, and converts the recording to a palette-dithered GIF.
node ~/.cursor/skills/enhance-readme/scripts/record-readme-tour.mjs \
--url=https://your-live-demo.example.com/ \
--out=docs/screenshots/tour.gifOptional flags:
--width=1280 (default) — final GIF width in pixels--height=800 (default) — recording viewport height--duration=9000 (default) — milliseconds of usable tour after dismiss/login is trimmed--fps=15 (default) — frame rate; lower = smaller file--user=admin --pass=demo — fills any visible email/text + password input and submits--routes=.,reports,fixes,judge — walk through a sequence of pages instead of scrolling one. Comma-separated paths, resolved against --url. The script clicks an in-page link if one matches the target path (smooth SPA transition, no white flash) and falls back to page.goto() only when no link exists. Time is split equally across stops.--storage='{"app:tour-completed":"true","app:mode":"advanced"}' — JSON object of localStorage entries seeded BEFORE first paint via Playwright's addInitScript. Use to skip first-run tours, force a specific theme/density/admin-mode, or hide "what's new" popovers — anything the app gates on a localStorage key. Way more reliable than trying to click-dismiss a coach-mark mid-recording.--keep-webm — also writes tour.webm next to tour.gif (useful if you also want a <video>-quality copy to attach to a PR)Authenticated, multi-page tours: combine--user/--passwith--routesand--storagefor a "logged in, walking through the app" tour. Pre-seedlocalStoragewith whatever flags the app uses to skip onboarding (tour-completed,welcomed, etc.) so the GIF doesn't open with a coach-mark covering everything. Use relative paths in--routes(reportsnot/reports) when the app has a base path (e.g. GitHub Pages projects served at/repo-name/) — absolute paths bypass the base and 404.
| Output target | Width | FPS | Typical size for 9s | Use when |
|---|---|---|---|---|
| README inline (default) | 800 | 15 | 4–7 MB | Most projects — fits under GitHub's 10 MB inline cap with headroom |
| Hero showcase | 1280 | 15 | 7–10 MB | Visually rich apps where detail matters more than file size |
| Mobile / docs site embed | 600 | 12 | 2–4 MB | When the GIF will be embedded in a docs site that loads it on every page |
A 9 s 1280×800 raw .webm is ~150 KB; the same as an unoptimised GIF would be ~30 MB. The two-pass palettegen + paletteuse=dither=bayer:bayer_scale=5 recipe in the script is what makes the GIF land in the single-digit MB range without obvious banding. If the script fails the 10 MB hard cap, lower --width first (it has the strongest effect), then --fps.
Paste the GIF block directly under the static hero `<picture>` block so the page reads:
See the "Tour GIF" template in the Output Templates section below.
GIFs are large binary blobs — commit them in their own commit so reviewers don't have to load 5 MB to look at a one-line code change later:
docs(readme): animated guided-tour GIF (~5 MB, 9s @ 800px)
- Add docs/screenshots/tour.gif: 4-stop scroll tour of the live demo
- Embed under the static hero so first-paint stays fast on slow connections
- Recorded via ~/.cursor/skills/enhance-readme/scripts/record-readme-tour.mjsWhat works inside markdown on github.com:
| Element | Status | Notes |
|---|---|---|
<picture> + <source media="(prefers-color-scheme: dark)"> | Yes | Auto-swaps with viewer's GitHub theme |
<table>, <tr>, <td width="50%" align="center"> | Yes | Use for grid layouts |
<sub>, <sup>, <details>, <summary> | Yes | For captions and collapsibles |
<a href> wrapping <img> | Yes | Click image → open URL |
<div align="center"> | Yes | The only reliable centering method |
<style>, style="" attributes | No | Stripped by GitHub's sanitizer |
class="" for custom CSS | No | Stripped |
| GIF up to 10 MB | Yes | Autoplays on loop, no pause control. Best for guided tours. |
<img src="docs/screenshots/tour.gif"> | Yes | Same as <img> for any image — relative path resolves fine |
<video src="..."> from a relative repo path | No | GitHub's sanitizer strips <video> from rendered markdown |
| MP4/WebM uploaded to issues/PRs | Yes (linked) | The upload returns a user-images.githubusercontent.com URL that DOES render — paste that URL inside <video> or <img>. Workaround for >10 MB tours. |
Relative image paths (docs/screenshots/...) | Yes | Resolved against the README's location |
GitHub content width is ~870 px for desktop. Images at width="100%" look good from ~1024 px source up to 1600 px.
/dashboard returns 404 on direct navigation, go to root first, then click the in-app navigation. CloudFront serves the SPA's index.html only for the configured paths.browser_evaluate with document.documentElement.classList.toggle('dark') is one tool call versus four.docs/screenshots/ with the final name.<img src="docs/screenshots/foo.png"> not docs\screenshots\foo.png. The generator script handles this.vite preview / npx serve and visit via http://localhost:....browser_navigate to the login page, the session was preserved from a prior run. Use it; no need to re-log-in.camo.githubusercontent.com (the proxy that serves every README image) keys its cache off the source URL. Overwriting an existing file with new bytes will NOT update the rendered image on github.com — Camo will keep serving the cached pre-overwrite version for hours, sometimes days. If you re-record tour.gif and the github.com README still shows the old one, rename the file (e.g. tour.gif → tour-v2.gif or something self-documenting like tour-pdca-loop.gif) and update the <img src> — the new URL hashes fresh and Camo refetches immediately. This applies to ALL images in the README, not just GIFs. The raw.githubusercontent.com endpoint is NOT cached this way, so always verify file contents there before assuming the file is wrong.If the script is unavailable, here are the raw templates.
<div align="center">
# ProjectName
![badges...]
**Tagline.**
Subtitle line.
<a href="LIVE_URL" title="Open the live demo">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/HERO-dark.png">
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/HERO-light.png">
<img alt="Project hero — what it shows" src="docs/screenshots/HERO-dark.png" width="100%">
</picture>
</a>
<sub>↑ click to open the live demo · the image swaps with your system theme</sub>
</div><div align="center">
<a href="LIVE_URL" title="Open the live demo">
<img alt="Animated guided tour of the app" src="docs/screenshots/tour.gif" width="100%">
</a>
<sub>↑ a 9-second guided tour · click to open the live demo</sub>
</div>## Tour
A quick look at the rooms inside. Click any panel to land on it in the live demo.
<table>
<tr>
<td width="50%" align="center">
<a href="LIVE_URL/page-1">
<img alt="Page 1 alt" src="docs/screenshots/page-1-dark.png" width="100%">
</a>
<br>
<sub><b>Page 1</b> · concrete technical detail with library name</sub>
</td>
<td width="50%" align="center">
<a href="LIVE_URL/page-2">
<img alt="Page 2 alt" src="docs/screenshots/page-2-dark.png" width="100%">
</a>
<br>
<sub><b>Page 2</b> · concrete technical detail with library name</sub>
</td>
</tr>
<tr>
<td width="50%" align="center">
<a href="LIVE_URL/page-3">
<img alt="Page 3 alt" src="docs/screenshots/page-3-dark.png" width="100%">
</a>
<br>
<sub><b>Page 3</b> · concrete technical detail with library name</sub>
</td>
<td width="50%" align="center">
<a href="LIVE_URL">
<img alt="Light mode showcase" src="docs/screenshots/HERO-light.png" width="100%">
</a>
<br>
<sub><b>Light mode</b> · how the app looks in daytime</sub>
</td>
</tr>
</table>The skill is done when:
docs/screenshots/, total < 10 MB.playwright-mcp/ is in .gitignorepackage.json versionstour.gif lives at docs/screenshots/tour.gif, autoplays under the static hero, and weighs less than 8 MB~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.