Generate a branded social media image announcing a new feature or update. Auto-detects brand from your codebase and captures via Playwright.
Drop the folder into your agent's skills directory — e.g. ~/.claude/skills/feature-image/ for Claude Code. Works with any agent that reads SKILL.md.
---
name: feature-image
argument-hint: [feature description]
description: "Generate a branded social media image announcing a new feature or update. Analyzes git history, auto-detects brand from codebase (Tailwind, CSS vars, design tokens), replicates UI elements, and captures via Playwright. Use when the user wants to create an announcement image, says 'feature image,' 'announcement graphic,' 'social image for feature,' or wants to visually announce a code change."
metadata:
author: Shpigford
version: 2.1.0
---
Generate a branded social media image for announcing a feature or update. The image is built as an HTML page styled to match the project's brand, then screenshotted with Playwright.
## Requirements
This file is the whole skill and is written to work in any coding agent, not just one. It assumes only that you can:
1. **Run shell commands** -- a POSIX shell (macOS, Linux, WSL, or Git Bash on Windows). The setup uses heredocs, which do not work in PowerShell or `cmd.exe`.
2. **Read and write files.**
3. **Show an image to the user** -- either by displaying it inline, or by telling them the path so they can open it. Both are handled below.
On the machine: Node and npm, plus network access on first run. Nothing else. If your harness has a structured question/choice tool, use it wherever this file says "ask the user"; otherwise print a numbered list and wait for a reply.
The YAML frontmatter at the top is metadata for harnesses that read it. Harnesses that don't will treat it as inert text -- it changes nothing about the instructions below.
## Phase 1: Ensure Playwright is Available
This skill builds its own runtime on first use. Run the block below verbatim every time -- it is idempotent and costs about 50ms once the runtime exists.
The runtime lives in the OS cache directory, not next to this file, so the skill itself stays a single portable document. The capture script must sit next to its own `node_modules`, because Node resolves the bare `playwright` import from the **script's** directory, not your cwd. This is why the script cannot live in `/tmp` -- the import will fail there.
```bash
RT="${XDG_CACHE_HOME:-$HOME/.cache}/feature-image-runtime"
mkdir -p "$RT"
cat > "$RT/package.json" <<'JSON'
{
"name": "feature-image-runtime",
"version": "1.0.0",
"private": true,
"type": "module",
"dependencies": { "playwright": "^1.62.1" }
}
JSON
cat > "$RT/capture.mjs" <<'MJS'
// Screenshots a local HTML file at exact dimensions, 2x retina.
// Usage: node capture.mjs <htmlPath> <outPath> <width> <height>
import { chromium } from 'playwright';
const [htmlPath, outPath, width, height] = process.argv.slice(2);
if (!htmlPath || !outPath || !width || !height) {
console.error('Usage: node capture.mjs <htmlPath> <outPath> <width> <height>');
process.exit(1);
}
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: Number(width), height: Number(height) },
deviceScaleFactor: 2,
});
const page = await context.newPage();
// goto (not setContent) so local assets -- logos, embedded screenshots --
// resolve relative to the HTML file.
await page.goto(`file://${htmlPath}`, { waitUntil: 'networkidle' });
await page.evaluate(() => document.fonts.ready);
const overflow = await page.evaluate(() => ({
x: document.documentElement.scrollWidth - window.innerWidth,
y: document.documentElement.scrollHeight - window.innerHeight,
}));
await page.screenshot({ path: outPath, type: 'png' });
await browser.close();
if (overflow.x > 1 || overflow.y > 1) {
console.log(`WARNING: content overflows canvas by ${overflow.x}x${overflow.y}px -- text is likely clipped`);
}
// Report dimensions and size here so verification needs no extra tooling
// (`sips` is macOS-only, `identify` needs ImageMagick).
const bytes = (await import('fs')).statSync(outPath).size;
console.log(`Saved: ${outPath}`);
console.log(`Dimensions: ${width * 2}x${height * 2}px (${width}x${height} @2x)`);
console.log(`Size: ${(bytes / 1024).toFixed(0)}KB`);
MJS
# Install the package only if it isn't already resolvable
node -e "require.resolve('playwright',{paths:['$RT']})" 2>/dev/null \
|| (cd "$RT" && npm install --no-audit --no-fund)
# Install the browser only if absent (separate from the package, shared OS-wide cache)
node -e "
const {chromium} = require('$RT/node_modules/playwright');
require('fs').accessSync(chromium.executablePath());
" 2>/dev/null || (cd "$RT" && npx playwright install chromium)
echo "runtime ready: $RT"
```
Rewriting `package.json` and `capture.mjs` every run is deliberate -- it keeps the runtime in sync with this file, so editing the script here propagates on the next run. The `npm install` is skipped once playwright resolves, so the rewrite costs nothing.
Reuse `$RT` in Phase 8. If any step fails, report the actual error -- do not fall back to a `/tmp` script, it cannot work.
## Phase 2: Understand What Changed (Git-Aware)
Analyze the recent git history to understand what feature/update to announce:
1. **Check recent commits:**
```bash
git log --oneline -20
```
2. **Check current diff (staged + unstaged):**
```bash
git diff HEAD --stat
git diff HEAD -- '*.tsx' '*.jsx' '*.vue' '*.svelte' '*.html' '*.css' '*.scss' '*.rb' '*.erb'
```
3. **Check recent branch name** (often describes the feature):
```bash
git branch --show-current
```
4. **Synthesize** what the feature/update is from this context.
5. **Confirm with the user.** If they already described the feature when invoking this skill, skip the question entirely and use their description. Otherwise ask:
> Based on recent changes, it looks like you're working on **[X]**. Should the announcement be about that, or something else?
Offer two choices: the auto-detected description, or "something else" so they can describe it themselves.
## Phase 3: Auto-Generate Announcement Text
Generate text elements for the image:
- **Headline**: A punchy, short headline (3-8 words) about the feature
- **Tagline**: A one-sentence supporting description
- **Badge/Label**: Optional category label (e.g., "New Feature", "Update", "Improvement")
**Do not ask the user to approve the copy.** Write your best version and keep going -- Phase 9 already offers "Adjust text" once they can see it in context, which is the only point where the judgment is actually useful. Approving a headline in the abstract, before seeing it set in type at the real size, wastes a round-trip.
State the copy you chose in one line as you proceed, so they can interrupt if it's badly off.
## Phase 4-5: Choose Platform & Visual Style
Ask these two together in **one** exchange -- they are independent, and splitting them costs an extra round-trip for nothing. If your harness has a multi-question tool, use one call; otherwise print both lists at once and take a reply like "1, B".
**Platform** (determines canvas size):
1. Twitter/X -- 1200x675, standard card
2. LinkedIn -- 1200x627, share image
3. Instagram -- 1080x1080, square
4. Open Graph -- 1200x630, universal social preview
**Visual style:**
- **A. Stylized mockup (recommended)** -- simplified, polished recreation of UI elements using the app's actual components and CSS. Recognizable but not pixel-perfect.
- **B. Screenshot + overlay** -- real screenshot of the running app with branded text overlays, gradients, and annotations.
- **C. Abstract/illustrative** -- brand colors and typography in a geometric or gradient design that suggests the feature without replicating specific UI.
Store the chosen width and height for the Playwright viewport.
### Style: Stylized Mockup
This is the most involved style. The goal is to create a representation of the UI that *feels* like the app without being a literal screenshot.
1. **Find relevant UI components** related to the feature:
- Search for component files that match the feature (e.g., if the feature is "dark mode", find theme toggle components)
- Read the component markup to understand the UI structure
- Note key visual elements: buttons, cards, inputs, tables, navigation items
2. **Extract visual patterns from components:**
- Border radius values
- Shadow styles
- Specific layout patterns (sidebar + main, card grids, etc.)
- Icon usage
- Interactive element styles (buttons, toggles, inputs)Members read the full skill.
Join the Founding Club — every skill, field note, and drop while you're a member.