Files
gstack/make-pdf/SKILL.md.tmpl
T
Garry Tanandcapy-ai[bot] 0dbc60708e fix(make-pdf): read local images only from the document's directory, and only image files (GST-1747) (#4113)
* fix(make-pdf): read local images only from the document's directory, and only image files (GST-1747)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(make-pdf): inline a local file only when its bytes match its image format

A file with an image extension now needs that format's signature (or a recognized size header) before it is inlined; anything else gets the blocked placeholder, and --strict throws. The docx ICNS test now sees the local file blocked by the prepass and the inline data URI dropped by the docx guard.

Review-Finding: p1/specialist-batched#1 reader=security
Review-Finding: p1/specialist-batched#2 reader=testing
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(make-pdf): compare image paths with path.relative so a root-level document keeps its images

A markdown file at the filesystem root made the containment prefix '//' (or 'C:\\'), which no path matched.

Review-Finding: v1/adversarial-native#5 reader=adversarial-native
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-07 06:53:27 +00:00

256 lines
10 KiB
Cheetah

---
name: make-pdf
preamble-tier: 1
version: 1.0.0
description: |
Turn any markdown file into a publication-quality PDF. Proper 1in margins,
intelligent page breaks, page numbers, cover pages, running headers, curly
quotes and em dashes, clickable TOC, diagonal DRAFT watermark. Not a draft
artifact — a finished artifact. Use when asked to "make a PDF", "export to
PDF", "turn this markdown into a PDF", or "generate a document". (gstack)
voice-triggers:
- "make this a pdf"
- "make it a pdf"
- "export to pdf"
- "turn this into a pdf"
- "turn this markdown into a pdf"
- "generate a pdf"
- "make a pdf from"
- "pdf this markdown"
triggers:
- markdown to pdf
- generate pdf
- make pdf
- export pdf
allowed-tools:
- Bash
- Read
- AskUserQuestion
---
{{PREAMBLE}}
# 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
### 80% case — memo/letter
One command, no flags. Gets a clean PDF with running header + page numbers
+ CONFIDENTIAL footer by default.
```bash
"$P" generate letter.md # writes /tmp/letter.pdf
"$P" generate letter.md letter.pdf # explicit output path
```
### Publication mode — cover + TOC + chapter breaks
```bash
"$P" generate --cover --toc --author "Garry Tan" --title "On Horizons" \
essay.md essay.pdf
```
Each 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
```bash
"$P" generate --watermark DRAFT memo.md draft.pdf
```
Diagonal 10% opacity DRAFT across every page. When the draft is final, drop
the flag and regenerate.
### Fast iteration via preview
```bash
"$P" preview essay.md
```
Renders 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.
### Brand-free (no CONFIDENTIAL footer)
```bash
"$P" generate --no-confidential memo.md memo.pdf
```
### Diagrams — 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:
```
```mermaid title="Auth flow" ← caption + aria-label
```mermaid render=false ← keep it as a code block
```mermaid page=landscape ← force this diagram onto a landscape page
```mermaid page=portrait ← veto auto-landscape for this diagram
```
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) is never
read: it renders as a visible placeholder with a warning, and `--strict`
makes it fatal. Only image files (png, jpeg, gif, webp, svg) inline; other
files, files over 64MB and non-regular files (fifos, devices) degrade to a
placeholder instead of hanging the run.
Per-image directives, written immediately after the image:
```
![chart](data.png){width=full} ← stretch to content-box width
![chart](data.png){width=50%} ← percentage or 3in/8cm/200px
![wide](arch.png){page=landscape} ← give it its own landscape page
![wide](shot.png){page=portrait} ← veto auto-landscape
```
Wide, 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
```bash
"$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
```bash
"$P" generate docs.md --strict # missing, remote, out-of-tree, non-image,
# oversized and non-regular-file images exit non-zero
# instead of warn + placeholder
```
## Common 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, non-image,
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)
```
## When 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
`./setup` in the gstack repo to build the fallback, re-run. `"$P" setup` checks
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 a4` or `--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`.