make-pdf
The facts
Its description
Turn any markdown file into a publication-quality PDF. (gstack) Triggers: markdown to pdf; generate pdf; make pdf; export pdf
The description the agent reads to decide when to use it. Phrases the author says should start it.
When it starts
A person asks to turn a markdown file into a PDF — exporting, generating, or making a PDF from a `.md` file, often wanting it to look polished. The description and triggers are all about markdown-to-PDF conversion, so any request to produce a PDF from markdown text picks it.
Things a person might say (the model's guesses)
- “Turn this README into a PDF.”
- “Can you export essay.md as a PDF with a cover page and a table of contents?”
- “Make a PDF out of my markdown notes.”
A model's summary of when it is used.
Tools it may use, or may not
allowed-tools: Bash, Read, AskUserQuestion
Tools it may use while it runs without asking you first.
Runs scripts
bin/gstack-skill-start, bin/gstack-learnings-log, bin/gstack-skill-end
Scripts the text names. Found by matching words, so treat them as pointers.
Stops for a person
3 lines stop to ask a person
Lines that stop and wait for a person before going on. Found by matching words, so treat them as pointers.
What leads to it
- gstack hands off to this PROACTIVE all, or user names it gstack, line 228
- browse is an alternative to this for rendering local HTML into a PNG or PDF browse, line 451
Other skills that hand off to this one, call it, require it or offer it instead, as a model read them.
Licence
MIT, from LICENSE
The terms its author shares it under, which let it be shown here.
Other files in this skill (17) Close
The text
Most marks come from matching words in the file, so treat them as pointers.
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly --> <!-- Regenerate: bun run gen:skill-docs -->
Raw lines 1 to 18Boilerplate, lines 19 to 43. The same text appears in 8 other files here. Close
MAKE-PDF SETUP (run this check BEFORE any make-pdf command)
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
P=""
[ -n "$MAKE_PDF_BIN" ] && [ -x "$MAKE_PDF_BIN" ] && P="$MAKE_PDF_BIN"
[ -z "$P" ] && [ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/make-pdf/dist/pdf" ] && P="$_ROOT/.claude/skills/gstack/make-pdf/dist/pdf"
[ -z "$P" ] && P="$HOME/.claude/skills/gstack/make-pdf/dist/pdf"
if [ -x "$P" ]; then
echo "MAKE_PDF_READY: $P"
alias _p_="$P" # shellcheck alias helper (not exported)
export P # env-var hosts re-derive $P in every later block (runtime prelude)
else
echo "MAKE_PDF_NOT_AVAILABLE (run './setup' in the gstack repo to build it)"
fiIf MAKE_PDF_NOT_AVAILABLE is printed: tell the user the binary is not built. Have them run ./setup from the gstack repo, then retry.
If MAKE_PDF_READY is printed: $P is the binary path for the rest of the skill. Use $P (not an explicit path) so the skill body stays portable.
Core commands:
"$P" generate <input.md> [output.pdf]— render markdown to PDF (80% use case)"$P" generate --cover --toc essay.md out.pdf— full publication layout"$P" generate --watermark DRAFT memo.md draft.pdf— diagonal DRAFT watermark"$P" preview <input.md>— render HTML and open in browser (fast iteration)"$P" setup— verify the browser (Aside, or gstack's own headless fallback) + pdftotext and run a smoke test"$P" --help— full flag reference
Output contract:
stdout: ONLY the output path on success. One line.stderr: progress (Rendering HTML... Generating PDF...) unless--quiet.- Exit 0 success / 1 bad args / 2 render error / 3 TOC page numbers failed / 4 no browser available (open the Aside app, or run
./setupto build gstack's own browser).
PDFs print through Aside when it is running and through gstack's own headless browser otherwise; the stderr progress line says which (Rendering PDF through Aside / through gstack's browser).
Boilerplate, lines 82 to 180. The same text appears in 26 other files here. Close
Plan Status Footer
Skills that run plan reviews (/plan-*-review, /codex review) include the EXIT PLAN MODE GATE blocking checklist at the end of the skill, which verifies the plan file ends with ## GSTACK REVIEW REPORT before ExitPlanMode is called. Skills that don't run plan reviews (operational skills like /ship, /qa, /review) typically don't operate in plan mode and have no review report to verify; this footer is a no-op for them. Writing the plan file is the one edit allowed in plan mode.
make-pdf: publication-quality PDFs from markdown
Turn .md files into PDFs that look like Faber & Faber essays: 1in margins, left-aligned body, Helvetica throughout, curly quotes and em dashes, optional cover page and clickable TOC, diagonal DRAFT watermark when you need it. Copy-paste from the PDF produces clean words, never "S a i l i n g".
On Linux, install fonts-liberation for correct rendering — Helvetica and Arial aren't present by default, and Liberation Sans is the standard metric-compatible fallback. CI and Docker builds install it automatically via Dockerfile.ci.
Emoji need a color-emoji font. macOS (Apple Color Emoji) and Windows (Segoe UI Emoji) ship one; most Linux distros and containers ship none, so emoji render as empty boxes (▯). ./setup auto-installs fonts-noto-color-emoji on Linux (apt/dnf/pacman/apk, best-effort) and the print CSS falls back through Apple / Segoe / Noto emoji families. Set GSTACK_SKIP_FONTS=1 to skip the install (CI without sudo, managed or offline machines). These matter on the gstack-browser fallback; Aside renders with the fonts already on the Mac.
PDF output prints through the Aside browser (macOS 15+, aside.com) when it is running, and falls back to gstack's own headless browser (built by ./setup; GSTACK_BROWSE_BIN / BROWSE_BIN point at a different build) everywhere else — Linux, Windows, or a closed Aside app. Exit 4 means neither browser is available. --to html and --to docx need no browser at all (diagrams in DOCX need one to rasterize; without it they embed as source text).
Core patterns
Raw lines 211 to 21280% case — memo/letter
One command, no flags. Gets a clean PDF with running header + page numbers
- CONFIDENTIAL footer by default.
"$P" generate letter.md # writes /tmp/letter.pdf
"$P" generate letter.md letter.pdf # explicit output pathRaw lines 213 to 222Publication mode — cover + TOC + chapter breaks
"$P" generate --cover --toc --author "Garry Tan" --title "On Horizons" \
essay.md essay.pdfEach top-level H1 in the markdown starts a new page. Disable with --no-chapter-breaks for memos that happen to have multiple H1s.
Draft-stage watermark
"$P" generate --watermark DRAFT memo.md draft.pdfDiagonal 10% opacity DRAFT across every page. When the draft is final, drop the flag and regenerate.
Raw lines 233 to 241Fast iteration via preview
"$P" preview essay.mdRenders HTML with the same print CSS and opens it in your browser. Refresh as you edit the markdown. Skip the PDF round trip until you're ready.
Raw lines 242 to 250Brand-free (no CONFIDENTIAL footer)
"$P" generate --no-confidential memo.md memo.pdfRaw lines 251 to 256Diagrams — mermaid and excalidraw fences render as pictures
A column-0 `mermaid or `excalidraw fence in the markdown renders as a crisp vector diagram, fully offline (vendored bundle, no CDN). Indented fences (inside lists) stay plain code blocks by design. A broken fence produces a visible red diagnostic block with the parse error — never silent raw code.
Fence info-string options:
A `excalidraw fence contains a full .excalidraw scene file (what excalidraw.com saves). Authoring NEW diagrams from English is /diagram's job — it emits an editable triplet (source, .excalidraw, SVG/PNG) and pairs with this skill: embed the .mmd source in your markdown, not the PNG.
Images — scaled right, never truncated
Local images inline automatically (relative paths resolve against the markdown file). Every image caps at the content box — zero truncation, ever. Oversized photos downscale to print resolution (300dpi) so payloads stay small with no visible quality loss.
Remote (http/https) images are blocked with a visible placeholder by default — offline posture; pass --allow-network to fetch them. An image that resolves outside the markdown's directory (even via symlink) still inlines, but warns loudly; --strict makes it fatal. Files over 64MB or non-regular files (fifos, devices) degrade to a placeholder instead of hanging the run.
Per-image directives, written immediately after the image:
{width=full} ← stretch to content-box width
{width=50%} ← percentage or 3in/8cm/200px
{page=landscape} ← give it its own landscape page
{page=portrait} ← veto auto-landscapeWide, small-text diagram images auto-promote to their own landscape page (conservative: aspect ≥ 1.8, width over ~2.5x the content box, AND a diagram-ish alt word — diagram/architecture/flowchart/chart/graph). The promoted page is vertically centered. When the heuristic guesses wrong, {page=portrait} vetoes it; false negatives just need {page=landscape}.
Other formats — single-file HTML and Word
"$P" generate readme.md out.html --to html # ONE self-contained file: inline
# SVG diagrams, data-URI images,
# zero network refs, screen-readable
"$P" generate readme.md out.docx --to docx # Word: content fidelity (headings,
# tables, code, diagrams as PNG) —
# layout is Word's, not ours--to is the output format. --format is something else entirely (a --page-size alias) — don't confuse them.
CI mode — fail loud on missing assets
"$P" generate docs.md --strict # missing, remote, out-of-tree, oversized,
# and non-regular-file images exit non-zero
# instead of warn + placeholderRaw lines 322 to 329Common flags
Page layout:
--margins <dim> 1in (default) | 72pt | 2.54cm | 25mm
--page-size letter|a4|legal
Structure:
--cover Cover page (title, author, date, hairline rule)
--toc Clickable TOC with page numbers
--no-chapter-breaks Don't start a new page at every H1
Branding:
--watermark <text> Diagonal watermark ("DRAFT", "CONFIDENTIAL")
--header-template <html> Custom running header
--footer-template <html> Custom footer (mutex with --page-numbers)
--no-confidential Suppress the CONFIDENTIAL right-footer
Output:
--to pdf|html|docx Output format (default: pdf). html = single
self-contained file; docx = content fidelity.
--strict Missing, remote, out-of-tree, oversized, or
non-regular-file images fail the run (CI mode).
--page-numbers "N of M" footer (default on)
--tagged Accessible PDF (default on)
--outline PDF bookmarks from headings (default on)
--quiet Suppress progress on stderr
--verbose Per-stage timings
Network:
--allow-network Fetch external images. Off by default: remote
images render as a visible blocked placeholder
(no tracking pixels fetch at print time).
Metadata:
--title "..." Document title (defaults to first H1)
--author "..." Author for cover + PDF metadata
--date "..." Date for cover (defaults to today)Raw lines 330 to 369When to run it
Run "$P" generate when the user wants markdown as a PDF. If the user has a .md file open and says "make it look nice", propose "$P" generate --cover --toc and ask before running.
Debugging
- Exit 4 / "no browser available" → neither the Aside browser (macOS 15+, aside.com) nor gstack's own headless browser is usable. Open Aside, or run
./setupin the gstack repo to build the fallback, re-run."$P" setupchecks the whole chain and says which browser it found. - Diagram shows a red "failed to render" block → the parse error is printed in the block. If EVERY diagram fails with "diagram renderer:", the browser went away mid-run (Aside closed, or the fallback daemon died).
- Fragmented text on copy-paste → remove fenced code blocks and regenerate (no flag turns code styling off).
- Exit 3 (
$P: --toc: …) → TOC page numbers could not be verified against the printed PDF; the message says why. Drop--toc, or shorten very long TOC headings if it says the numbers did not settle. - "[remote image blocked]" placeholder in the output → add
--allow-network(understand you're giving the markdown file permission to fetch from its image URLs). - Generated PDF too tall/wide →
--page-size a4or--margins 0.75in.
Output contract
stdout: /tmp/letter.pdf ← just the path, one line
stderr: Rendering HTML... ← progress spinner (unless --quiet)
Rendering PDF through Aside... ← or "through gstack's browser"
Done in 11.2s. 43 words · 22KB · /tmp/letter.pdf
exit code: 0 success / 1 bad args / 2 render error / 3 TOC page numbers failed
/ 4 no browser available (Aside not open, fallback not built)Capture the path: PDF=$("$P" generate letter.md) — then use $PDF.
From garrytan/gstack at 3f79d81, 2026-11-01. Skills read by DeepSeek V4.1 Flash on 2026-11-04. Summary and techniques by Claude Opus 5.5 on 2026-11-06.