paginated-report-pdf
Skill4 starsChanged 14 days agoMITView on GitHub
Generates a client-facing, document-style paginated PDF report (cover page, numbered sections, one page per topic/site/item, tables, callout boxes, numbered step lists) from an HTML source, using headless Chrome — no external PDF library needed. Use this whenever the user asks for a "PDF report", "informe en PDF", "documento para el cliente", "PDF con nuestra marca/logo", or wants an existing HTML dashboard/artifact turned into a proper paginated document instead of a browser screenshot or a raw print of the dashboard itself. Also use when asked to add a persistent logo/header to every page of a PDF, or to regenerate/update a PDF that was built this way before. Works for ANY brand/client — always run the brand intake step first, never assume a specific client's colors or logo.
In this repository37 other files it gives its agents.
- blast-radiusSkill
- elementflow-kbSkill
- gitlab-changelog-releaseSkill
- gitlab-ci-quality-gatesSkill
- gitlab-project-bootstrapSkill
- gitlab-security-setupSkill
- gitlab-wiki-bootstrapSkill
- lando-checkSkill
- lando-img-placeholderSkill
- lando-mail-smtpSkill
- lando-setupSkill
- mallard-releaseSkill
- mr-flowSkill
- no-commentsSkill
- panda-kbSkill
- ploi-prestashop-deploySkill
- prestashop-kbSkill
- ps-consoleSkill
- ps-css-buildSkill
- ps-demo-userSkill
- ps-email-previewSkill
- ps-image-regenSkill
- ps-invoice-previewSkill
- ps-new-projectSkill
- ps-one-page-checkoutSkill
- ps-project-docSkill
- ps-security-auditSkill
- ps-storefront-polishSkill
- ps-theme-cacheSkill
- ps-translateSkill
- ps-view-transitionsSkill
- ps-watchSkill
- reflectSkill
- show-me-your-workSkill
- skill-authorSkill
- task-contextSkill
- verify-ps-projectSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
15 rules. About 0 are common, 6 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 31
Use this: when the deliverable is a real *document*: cover page, sections, tables, a page per item, meant to be read start-to-finish or …
- Distinctive: line 39
Never reach for a heavier toolchain (wkhtmltopdf, Puppeteer, a PDF library) unless headless Chrome is confirmed unavailable — it almost …
- Distinctive: line 47
Before writing any HTML, establish the brand's visual identity.
- Very specific: line 57
Ask: If neither of the above resolves it, ask (a single AskUserQuestion covering all of the below, or a plain question if that tool isn't …
- Distinctive: line 62
Default to a neutral dark gray/near-black if the user has no preference and there's truly no brand to match.
- Very specific: line 70
Never hardcode a specific client's brand (colors, logo, name) as a default — every value in the HTML below is a placeholder resolved from …
- Very specific: line 80
If missing, look for chromium/google-chrome on PATH, or ask the user.
- Distinctive: line 81
Don't install a new dependency for this.
- Very specific: line 133
Always include both -webkit-print-color-adjust: exact.
- Very specific: line 145
If the user said "no logo", skip this step entirely and drop the <img> from the header template in Step 3 — don't fabricate a placeholder …
- Very specific: line 184
Add this <div class="page-header"> as the first child of every <section class="page"> except the cover (which usually has its own full-size …
- Very specific: line 212
Read a few pages of the generated PDF back (the Read tool renders PDF pages as images) — check: logo present per page (if one was …
- Very specific: line 223
When inserting a new page in the middle of a numbered sequence (e.g.
- Very specific: line 228
When batch-inserting the same snippet (like the per-page header in Step 3) into many <section class="page"> blocks at once, do it with a …
- Distinctive: line 257
If this PDF is a paginated version of an existing interactive HTML dashboard, the two will drift if only one gets edited.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Paginated report PDF (HTML → Chrome headless → PDF)
Produces a real, linear document (cover, numbered sections, per-item pages) rendered by headless Chrome from a self-contained HTML file — not a screenshot, not a raw print of an interactive dashboard. Brand-agnostic: the skill asks for the brand's visual identity before writing a single line of HTML, so the same instructions produce a correctly-branded PDF for whichever client/project you're in.
When to use this vs. other options
- Use this when the deliverable is a real document: cover page, sections, tables, a page per item, meant to be read start-to-finish or archived/emailed as a PDF.
- Don't use this to just "export" an existing interactive dashboard (tabs, sidebar nav) as-is — that produces a broken, unreadable PDF (nav elements repeated, tab content hidden). Write a separate, purpose-built paginated HTML file instead. It's fine — even expected — for this file to reuse content/wording from the dashboard, restructured into linear document flow.
- Never reach for a heavier toolchain (wkhtmltopdf, Puppeteer, a PDF library) unless headless Chrome is confirmed unavailable — it almost always already is (see prerequisite check below). If the task is PDF manipulation (merge/split/extract/forms/OCR) rather than authoring a new document, that's a different job — don't force this skill onto it.
Step 0 — brand intake (always do this first)
Before writing any HTML, establish the brand's visual identity. Resolve each of these, in order of preference:
- Already evident from the current project — an existing design system,
theme.css/tokens file, a previous PDF built with this skill in the same repo, or a logo file already in the project (assets/,public/,static/). If found, reuse it and say so — don't re-ask for what's already on disk. - Given directly by the user in this conversation — a color, a font name, a path/URL to a logo. Use it as stated.
- Ask. If neither of the above resolves it, ask (a single
AskUserQuestioncovering all of the below, or a plain question if that tool isn't available):- Logo: file path, URL, or "no logo for this one".
- Primary color (hex) — used for callout borders, pills, accent text. Default to a neutral dark gray/near-black if the user has no preference and there's truly no brand to match.
- Font family — a real font name if the brand has one; otherwise the system-font stack in Step 1 is a fine default, say so explicitly instead of silently picking something.
- Document title / client name — goes in the cover page and the per-page header.
Never hardcode a specific client's brand (colors, logo, name) as a default —
every value in the HTML below is a placeholder resolved from this step:
{brand_name}, {primary_color}, {font_family}, {logo_data_uri}.
Prerequisite check
ls "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"If missing, look for chromium/google-chrome on PATH, or ask the user.
Don't install a new dependency for this.
Step 1 — write the paginated HTML source
One .html file, self-contained (no external CSS/font/image URLs —
everything inline or base64, since the file may be printed with no network).
Structure:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>{brand_name} — {document_title}</title>
<style>
:root { --brand-primary: {primary_color}; }
@page { size: A4; margin: 22mm 18mm 20mm 18mm; }
* { box-sizing: border-box; }
body {
font-family: {font_family}, -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.page { page-break-after: always; }
.page:last-child { page-break-after: auto; }
</style>
</head>
<body>
<section class="page"> ... cover ... </section>
<section class="page"> ... section 2 ... </section>
<!-- one <section class="page"> per logical page; content that overflows a page
just flows onto the next physical page automatically, no extra markup needed -->
</body>
</html>Key CSS pieces worth building (reference tokens like var(--brand-primary),
never a hardcoded hex, so the same structure repaints for any brand):
.callout/.callout.strong— colored left-border box for a key finding or warning, border color =var(--brand-primary)..stat-grid/.stat .num— big-number summary stats row..pill.good/.pill.warn— small colored status badges inside tables..steps li+.step-circle— numbered/checkmarked circular steps (a plan or a checklist), circle background =var(--brand-primary)..kv-table— a label/value table (first column bold+uppercase+narrow) for a spec sheet..item-badge,.item-head,.meta-pills— a per-item detail-page header pattern (name, URL/ref, status badge, spec chips) — reuse this whenever the report has "one page per X" (per site, per store, per module, per incident).
Always include both -webkit-print-color-adjust: exact; and
print-color-adjust: exact; on body — without them Chrome's print engine
can wash out background colors on callout boxes.
Step 2 — embed the logo (or any image) as base64, once
curl -sL -o /tmp/logo.png "<logo-path-or-url-from-step-0>"
base64 -i /tmp/logo.png | tr -d '\n' > /tmp/logo.b64Then inline it: <img src="data:image/png;base64,$(cat /tmp/logo.b64)" alt="{brand_name}">.
If the user said "no logo", skip this step entirely and drop the <img> from
the header template in Step 3 — don't fabricate a placeholder logo.
Once embedded in one file, extract it back out for reuse elsewhere with:
grep -o 'data:image/png;base64,[A-Za-z0-9+/=]*' source.html | head -1— no need to re-fetch/re-encode the image for every file that needs it, or for a later PDF for the same brand.
Step 3 — logo/header on every single page (not just the cover)
Chrome's CLI (--headless --print-to-pdf) does not support a custom
per-page header/footer template (that's a Puppeteer-only API,
page.pdf({headerTemplate})) — --print-header-footer only gives Chrome's
own plain URL/date/title footer, not a custom image. The reliable fix: put
the logo in the HTML content of every page section, not in a
browser-managed header:
.page-header {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 18px; padding-bottom: 10px; border-bottom: 1px solid #eeeef1;
}
.page-header img { height: 14px; width: auto; }<section class="page">
<div class="page-header">
<img src="data:image/png;base64,..." alt="{brand_name}">
<span class="doc-title">{document_title}</span>
</div>
... rest of page content ...
</section>Add this <div class="page-header"> as the first child of every
<section class="page"> except the cover (which usually has its own
full-size logo treatment already). If there's no logo (Step 0/2 said so),
keep just the <span class="doc-title">. If there are many pages, script the
insertion rather than hand-editing each one — see the one-liner pattern
below.
Step 4 — generate the PDF
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--headless --disable-gpu --no-pdf-header-footer \
--print-to-pdf="/absolute/path/to/output.pdf" \
"file:///absolute/path/to/source.html"--no-pdf-header-footersuppresses Chrome's own plain-text header/footer (URL, date, page number) — always pass it, since the HTML already carries its own header per Step 3.- The
file://URL must be an absolute path. - Chrome prints a lot of unrelated
updater/crashpadlog noise to stderr — harmless, ignore it; check the actual output file size/existence instead of parsing stderr for success. - This runs Chrome fully headless, no window, no user interaction — safe to run repeatedly while iterating.
Step 5 — verify before calling it done
Read a few pages of the generated PDF back (the Read tool renders PDF pages
as images) — check: logo present per page (if one was requested), tables
didn't get cut mid-row across a page break in an ugly way, colored boxes
rendered with the brand color (not washed out or defaulted to a generic
color), no leftover placeholder text ({brand_name} etc. never survives into
the actual output — if you see it, Step 0 was skipped or a substitution was
missed). Iterate on the HTML and regenerate — this loop is fast (a few
seconds per Chrome invocation).
Iterating on structure (adding/removing pages, renumbering sections)
When inserting a new page in the middle of a numbered sequence (e.g. a
"2 · Section name" eyebrow scheme), grep for all section-eyebrow occurrences
first and fix the numbering in one pass — it's easy to insert a new page and
forget every later page still says the old number.
When batch-inserting the same snippet (like the per-page header in Step 3)
into many <section class="page"> blocks at once, do it with a small Python
script that splits on <section class="page and inserts right after each
opening tag's > — faster and less error-prone than editing each section by
hand:
import re
with open(path, encoding='utf-8') as f:
c = f.read()
m = re.search(r'data:image/png;base64,[A-Za-z0-9+/=]+', c)
data_uri = m.group(0) if m else None # None if this brand has no logo
header_html = (
f'\n <div class="page-header"><img src="{data_uri}" alt="{{brand_name}}" /><span class="doc-title">{{document_title}}</span></div>'
if data_uri else
f'\n <div class="page-header"><span class="doc-title">{{document_title}}</span></div>'
)
parts = c.split('<section class="page')
new_parts = [parts[0], parts[1]] # keep head + cover page untouched
for seg in parts[2:]:
idx = seg.index('>') + 1
new_parts.append(seg[:idx] + header_html + seg[idx:])
c = '<section class="page'.join(new_parts)
with open(path, 'w', encoding='utf-8') as f:
f.write(c)Keeping the PDF in sync with other artifacts
If this PDF is a paginated version of an existing interactive HTML dashboard, the two will drift if only one gets edited. When a fact changes: update the dashboard's per-item section AND the matching PDF-source page AND regenerate the PDF in the same pass — don't defer the PDF regen "for later", it's cheap (Step 4 is one command) and skipping it is exactly how documents go stale.
Prior art
This skill's technique (HTML → Chrome headless → PDF, per-page HTML header since Chrome CLI has no custom header template) was worked out end-to-end on a real client deliverable — a security-audit report with a cover page, per-site incident pages, callout boxes, and a persistent logo header. That file is a legitimate reference for the CSS patterns in Step 1 if you want to see them fully worked, but treat every brand value in it as project-specific — never copy its actual colors/logo into a different client's report.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

