Html Export
[Document Processing] Use when a workflow step or the user asks for HTML page, deck, mockup or an…
by duc01226 · MIT
The skill
[Document Processing] Use when a workflow step or the user asks for HTML page, deck, mockup or animation export to PNG, vector PDF, MP4 or GIF. --to={png|pdf|mp4|gif}.
Codex compatibility note:
- Invoke
$skill-name: load the registered.agents/skills/<name>/SKILL.mdand execute it with Codex tools; no separateSkilltool or Claude mode switch is needed. Author.claude/**; mirrors are generated.- Track steps before execution and keep progress/evidence current. Explicit skill requests execute their protocol. Use the native question tool for required questions.
- Map foreign tool names to available host capabilities. A name mismatch needs no approval; an actually unavailable required capability stops with its step/evidence and a user question before adaptation.
- Subagent authorization: when a skill is user-invoked or AI-detected and its protocol requires subagents, that skill activation authorizes use of the required
spawn_agentsubagent(s) for that task.- Preserve protocol order unless the user approves a deviation. Workflows use
$start-workflow: gates stay fixed; other steps may flex with logged reasons and evidence.
Quick Summary
Goal: Turn a local HTML file into PNG screenshots with a page-error and blank-capture check, a vector PDF, an MP4 or a GIF, through one entry point that reports the result as an exit code.
Workflow:
- Pick a target —
--to=png(render evidence),--to=pdf(print or slides),--to=mp4/--to=gif(deterministic animation recording) - Check setup — dependencies are opt-in and skill-local; a missing one exits
3with the setup commands - Export —
node .claude/skills/html-export/scripts/export.cjs --to=<target> <input...> [options] - Read the exit code first — then
report.json/frames.json, then the output; a written file is never proof on its own. A reused--outkeeps earlier outputs: only the files this run's manifest names belong to it —report.jsonfiles[](png),report.jsonoutput(pdf),frames.jsonoutput(mp4/gif)
Key Rules:
--tois required; there is no default target.- Nothing is ever installed on the user's behalf. Exit
3meansNOT VERIFIABLEplus a setup pointer. - Render only HTML the project or the user produced; anything fetched or untrusted only with
--offline(see Trust). - The HTML stays canonical. Fix the page or pick the right selector; never restructure HTML for the exporter.
Be skeptical. Apply critical thinking, sequential thinking. Every claim needs traced proof, confidence percentages (Idea should be more than 80%).
Contents
- Quick Summary
- html-export
- Setup (opt-in only)
- Trust
- Usage
- Producer selectors
- Exit codes
- Calling from other skills
- What exit 0 proves
- Resources
- Closing Reminders
html-export
Agents and users may invoke this skill directly, and other skills may run the script by path. Either way the exporter only renders; it never installs, commits or publishes anything.
Setup (opt-in only)
Needs Node.js 20 or newer and, in the skill's own node_modules, Playwright 1.55.1 or newer (never a parent project's copy). Run these from the project root, only when the user asks; the exporter prints them on exit 3 and never runs them.
# Playwright and pdf-lib go into the skill folder; Chromium goes into Playwright's per-user browser cache
macOS/Linux shell: (cd .claude/skills/html-export && npm install && npx playwright install chromium)
Linux system libs: (cd .claude/skills/html-export && npx playwright install --with-deps chromium)
Windows PowerShell: Push-Location .claude/skills/html-export; npm install; npx playwright install chromium; Pop-Location
Windows cmd: pushd .claude\skills\html-export && npm install && npx playwright install chromium && popd
# ffmpeg, only for mp4/gif (the build must include the libx264 encoder)
Windows: winget install Gyan.FFmpeg
macOS: brew install ffmpeg
Linux (Debian/Ubuntu): sudo apt-get install ffmpeg
Install from inside the skill folder, never with npm install --prefix: run from a project with its own package.json, that form makes npm add the host project as a dependency of the skill (it rewrites the skill's package.json and links the project root into its node_modules).
ffmpeg and ffprobe are looked up only in the absolute directories on PATH, never in the current directory.
| Environment variable | Effect |
|---|---|
HTML_EXPORT_FFMPEG |
Pins the ffmpeg build instead of the PATH lookup. Must be an absolute path; on Windows it must also name a .exe or .com file (never a .bat / .cmd wrapper). Otherwise exit 3 |
HTML_EXPORT_FFPROBE |
The same for ffprobe, with the same rules |
HTML_EXPORT_DEBUG=1 |
Prints the stack trace of an unexpected error to stderr. Set it when an exit 1 names an unexpected error, and quote the stack |
Removing the skill. Deleting the skill folder removes Playwright, pdf-lib and every other npm dependency. Chromium stays in Playwright's per-user browser cache (or under PLAYWRIGHT_BROWSERS_PATH when that is set); to remove it too, first run npx playwright uninstall from inside the skill folder — it removes only the browsers no other Playwright installation still uses — then delete the folder. Regenerate the mirrors afterwards.
Trust
Render only HTML that the project or the user produced: its own decks, mockups, reports and drafts. For anything fetched from the web or otherwise untrusted, always pass --offline. Chromium runs without its OS sandbox, and a page can embed any local file the user can read (for example through an iframe) into the PNG, PDF or video. --offline blocks the page's network traffic; it is not a sandbox, and an embedded local file still lands in the output.
A page the project produced stays trusted when it uses fetched files only as verified images through <img src> (an SVG loaded as an image runs no script and loads nothing) and loads no remote script, frame or media; remote web fonts through CSS are fine. Inlining fetched SVG markup, or embedding it through <object>, <iframe> or <embed>, makes the page untrusted: pass --offline.
Usage
node .claude/skills/html-export/scripts/export.cjs --to=png page.html
node .claude/skills/html-export/scripts/export.cjs --to=pdf deck.html --page=1920x1080
node .claude/skills/html-export/scripts/export.cjs --to=mp4 intro.html --duration=4
node .claude/skills/html-export/scripts/export.cjs --help
A value flag takes =value or the next argument; --slides takes only the attached form, so --slides <css> reads the selector as an input file. Quote a selector with brackets or spaces in every shell: single quotes in bash, zsh and PowerShell (--slides='[data-state]'; unquoted, zsh treats the brackets as a glob), double quotes in cmd.exe.
The dispatcher checks every flag and input before any dependency or target runs, so a bad command exits 2 even when nothing is installed: an input, or a pdf --order entry, that is not an .html or .htm file (any letter case; a positional pdf input may also be a folder), an unknown flag or one the target rejects, --timeout or --scale not above 0, --timeout above 2147483647 (about 24.8 days), a malformed WxH, a size listed twice in --viewport, or an empty --slides=. Three checks need the page, so they run after rendering starts and stop the run at once: a --slides selector the browser cannot parse (png, pdf), a pdf selector that matches nothing, and a video with no duration. The video --keep-frames folder check runs after the dependency checks, before rendering.
Generic options
| Option | Meaning | Applies to |
|---|---|---|
--out=<dir> |
Output directory. Default: <project>/tmp/html-export/<YYMMDD-HHmm>-<first input name>/ |
all |
--viewport=<WxH[,WxH]> |
Viewport size; a comma list only for png | all |
--slides[=<css>] |
Navigate slides or screens; bare form uses the default selector section.slide[data-slide-id], [data-export-slide] |
png, pdf |
--page=<WxH> |
PDF page size in CSS pixels, print mode only | |
--scale=<n> |
Device scale factor, greater than 0 | all |
--timeout=<ms> |
Timeout in milliseconds (default 30000, at most 2147483647) for load, fonts and each step, and for every call into the page: the window.__ready wait, evidence collection, slide navigation, pdf slide isolation, and the video duration, ready, __seek and per-frame calls. A page that hangs one of them becomes a logged page fault. It never bounds the browser's own start or stop (see below) |
all |
--offline |
Take the page offline: besides the input itself, only local file: URLs load (empty or localhost host). Every other request, including file://<host>/, and every WebSocket is refused and listed in blocked[]; WebTransport, speculation-rules prefetch and service workers are also blocked, silently. The failure message Chromium logs for a blocked request or WebSocket is not a page error; code that then throws still is one |
all |
--allow-errors |
Accept page errors; never a blank capture or a failed step | all |
--self-check |
Render the known-good fixture first; png only | png |
--help |
Print usage and exit 0 |
all |
Browser start and stop. Launching Chromium, creating its context and page, setting up the --offline route, and closing the browser each get max(--timeout, 30000) ms, the same budget as the dependency check, so a short --timeout never fails a slow launch. A start call that runs over fails that input as a tool fault (pageFault: false, message <call> did not finish within N ms; the browser is not responding (a busy or broken machine, not the page)). A close that runs over prints Warning: the browser did not close within N ms; ..., the run continues with its exit code unchanged, and the exporter exits on its own instead of hanging; that browser is stopped when it exits.
--to=png — render evidence
Target flag: --full-page (opt-in). Inputs: one or more .html / .htm files; --self-check may run with no input to check only the verifier.
- Default viewports
1440x900,390x844. Each capture is viewport-only;--full-pageadds<name>@<WxH>-full.pngwith the whole scrollable document and its own blank check.--full-pagewith--slidesexits2. - Writes
<name>@<WxH>.png, or with--slidesone<name>@<WxH>-s<NN>.pngper item, plusreport.json. - Log and continue: a failed open, navigation, capture or timeout on one input, viewport or slide goes to
report.jsonfailures[](each entry withpageFault) and stderr, and the rest of the batch still runs. A page that fails to open still lists the errors it raised before that inerrors[]. - Exit
4on a page-caused failure, a blank capture, or a page error (unless--allow-errors), even when nothing was captured; a--slidesselector that matches nothing counts as blank. Exit1only for a tool fault with none of those (browser start, a write error) or when--self-checkreportsverifier broken. A--slidesselector the browser cannot parse exits2(report.jsonusageError).--pageis a usage error; use--viewport.
--to=pdf — vector PDF
Target flag: --order=<file> — one .html / .htm path per line (blank and # lines skipped, relative paths resolved against the file's folder). Inputs: HTML files or directories (natural order), or --order, never both.
- Print mode (default): the page's print CSS. Page size:
--page=WxHwins over a CSS@page { size }rule; without--pagethe CSS size applies, else Chromium's default. - Slides mode (
--slides[=<css>]): running animations are finished, then each navigated item prints as one viewport-sized page with screen media.--pagewith--slidesis a usage error; size pages with one--viewport=WxH. - Several inputs, or slides mode, merge into
<out>/<name>.pdfwith pdf-lib. stdout carries only the PDF path;report.json(errors,failures[], navigation,blocked[]) is written beside it. - Log and continue: a failed input or slide is recorded (with
pageFault) and skipped, and the PDF holds every page that printed. Exit4when the page caused a failure or reported errors (unless--allow-errors), even if nothing printed (then no PDF is written). Exit1when only the tool failed, or nothing printed without a page fault; the pages that printed are still written. A merge or PDF-write failure exits1withreport.jsonfatal. A slides selector that cannot be parsed or matches nothing exits2with no PDF (report.jsonusageError).--self-checkis rejected.
--to=mp4 / --to=gif — deterministic recording
Target flags: --fps=<1-60> (default 30 for mp4, 15 for gif), --duration=<seconds>, --audio=<local file> (mp4 only, AAC, trimmed to the shorter stream), --gif-width=<px> (gif only, default 960), --keep-frames. Input: exactly one HTML file.
- Units differ:
--durationis in seconds; the page hookwindow.__durationis in milliseconds.--durationwins; with neither, the run exits2and writes nothing. - Default viewport
1920x1080, one viewport only.--slides,--pageand--self-checkare usage errors. MP4 is libx264 at-crf 18; a build without libx264 exits3. - The recorder sets
window.__recordingbefore page scripts run and hides elements markeddata-export-hide. Contract:references/animation-recording.md. - Writes the video plus
frames.json(statusok/page-errors/failed,exitCode,failurewithpageFault,pageErrors,noMotion, a sha256 per frame). Page errors, including a timer or animation-frame callback that throws while the recorder advances the clock, are recorded and recording continues: exit4with the files written (0with--allow-errors). A page that never loads or becomes ready, a__seekthat throws, a page that breaks the recorder's setup or animation pinning, or any in-page call or frame step that exceeds--timeoutexits4with no video. A tool fault (browser start, a write error, ffmpeg) exits1, or4when the page also reported errors that--allow-errorsdoes not accept. Identical frames print a warning and setnoMotion(exit stays0). --keep-framesalso keeps every frame in<out>/frames/. An existingframes/is reused only when it is a real directory holding nothing but the recorder's own frame files; otherwise the run exits2and touches nothing. Frames are captured into a work folder and replace the earlier frame files only after the video encoded, so a failed recording or encode leaves an earlierframes/as it was. A failed run never writes the video path, and an earlier video stays in place;frames.jsonis rewritten by every run except a usage error.
Producer selectors
| Producer | png | |
|---|---|---|
| presentation-builder | --slides (default selector) |
--page=1920x1080 (print mode) |
| feature-presentation | --slides=section.deck__slide, or --slides (default) |
--page=1920x1080 (print mode), or --slides=section.deck__slide |
| work-item (mockup mode) | --slides='[data-state]' (every screen) |
— |
Any other page can mark its items with data-export-slide. feature-presentation slides carry both slide and deck__slide with a data-slide-id, so its existing section.deck__slide selector and the default selector find the same slides.
Exit codes
| Exit | Meaning |
|---|---|
0 |
Export succeeded and the target's checks passed |
1 |
Tool fault, and the page did nothing wrong: the browser did not launch or start in time — including a dependency-check launch that timed out (the message says timed out: a busy or broken machine, not a missing browser) — a write error, a pdf merge failure, an unexpected exception (set HTML_EXPORT_DEBUG=1 for its stack), png verifier broken, an ffmpeg failure (a failed GIF pass keeps the MP4 when that name is free). A page fault, blank png capture or unaccepted page error in the same run makes it 4 |
2 |
Usage error: missing or unknown --to, unknown or rejected flag, bad input or value, an input or pdf --order entry that is not an .html / .htm file (a positional pdf input may also be a folder), --timeout above 2147483647; a png or pdf --slides selector the browser cannot parse; pdf slides selector matching nothing; video with no duration; --keep-frames on a foreign frames/ |
3 |
Dependency missing: Node older than 20, Playwright older than 1.55.1 or absent, Chromium not installed or failing to launch for any reason other than a timeout (for example a missing system library), pdf-lib for a merge, ffmpeg with libx264, or an HTML_EXPORT_FFMPEG / HTML_EXPORT_FFPROBE that is relative or, on Windows, not a .exe / .com file |
4 |
The page is wrong, even when nothing was produced: page errors (unless --allow-errors), a blank png capture, or a page-caused failure (pageFault: true) — never loads, never ready, fonts time out, navigation error, an in-page hang, a __seek that throws or times out, a page that breaks the video recorder's setup or animation pinning |
| other | Interrupted, for example 130 after Ctrl+C: no verdict and possibly no report.json. A video run can leave <out>/.video-work-* folders; the recorder removes them on a best-effort basis, and any that remain are safe to delete |
Calling from other skills
Invoke the skill, or run node .claude/skills/html-export/scripts/export.cjs ... by path. Every caller follows the same rule:
- Exit
0→ evidence, only within the scope below. - Exit
4→ fix the page and re-run (fix loop);report.jsonorframes.jsonnames each failure. - Exit
3→ mark the checkNOT VERIFIABLEwith a one-line pointer to$html-exportsetup. Never run install commands on the user's behalf. - Exit
1/2→ tool failure: quote stderr, markNOT VERIFIABLE. Never count it as a design defect or a pass. - Any other exit (such as
130after an interrupt) → handle it like1/2. - Evidence files → only those the run's manifest names (
report.jsonfiles[]for png,outputfor pdf,frames.jsonoutputfor video); a reused--outstill holds earlier outputs.
What exit 0 proves
Without --allow-errors:
- png: each input loaded at each requested viewport; no console error or uncaught exception from load through the last capture; no step failed; every capture held visible text, an image, an SVG or a canvas; with
--slides, every matched item was shown and captured. - pdf: every input and slide printed into the PDF and no page error occurred.
- mp4/gif: the video was encoded from frames captured on the recorder's controlled clock, with no page error.
It does not prove the page looks right, that interaction works, anything below the first viewport (unless --full-page), other browsers, or that remote assets load. Open the output and judge it; interactive checks stay NOT VERIFIABLE unless exercised another way.
Resources
references/verification.md— png evidence,report.jsonfields, failure signatures, offline rule, self-check, known limitsreferences/deck-export.md— pdf print vs slides mode, page size, per-producer commands, ordering, merge, pdfreport.jsonreferences/animation-recording.md— the recording contract: time rule,window.__ready/__seek/__duration/__recording,data-export-hide,frames.json- Tests:
node .claude/skills/html-export/tests/run-tests.cjs(browser cases reportENVIRONMENT-BLOCKEDwhen Chromium or ffmpeg is absent)
Closing Reminders
IMPORTANT MUST ATTENTION Goal: export HTML to PNG, PDF, MP4 or GIF through scripts/export.cjs --to={png|pdf|mp4|gif} and report the result by exit code.
IMPORTANT MUST ATTENTION read the exit code before the output — a written file proves nothing; exit 4 still writes files; only the run's manifest (report.json / frames.json) names this run's outputs
IMPORTANT MUST ATTENTION never install dependencies on the user's behalf — exit 3 is NOT VERIFIABLE plus a setup pointer
IMPORTANT MUST ATTENTION exit 1/2 is a tool failure, never a design defect or a pass
IMPORTANT MUST ATTENTION --duration is seconds, window.__duration is milliseconds
IMPORTANT MUST ATTENTION the HTML stays canonical — never restructure it for the exporter
Use this skill
Connect your agent to the Skill Store MCP server and ask for it by ref: duc01226/html-export-54533859702.
Details
- Publisher
- duc01226
- Category
- Creative & Media › Image & design
- License
- MIT
- Updated
- 2026-10-18
References
More from duc01226
- Template Skill [Category] Use when defining the standard skill description pattern.
- Ai Context Refresh [Documentation] Use when generating, smart-merging or refactoring the root AI context (CLAUDE.md,…
- Ai Engineering Review [Code Quality] Use when a workflow step or the user asks for AI code or plan review: LLM calls, p…
- Architecture [Architecture] Use when a workflow step or the user asks for architecture design, compliance revi…