Files
gstack/make-pdf/SKILL.md
T
Garry TanandClaude Fable 5 23c806d4e7 chore(gen): regenerate all skills + golden fixtures — inline preamble bash removed
Mechanical regen after the resolver change: −12,628 lines across 52 renders
(corpus 952K -> 806K render tokens; tier-2 skills −11-13KB each). Golden
per-host ship fixtures refreshed from the fresh claude/codex/factory renders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 15:36:35 +00:00

593 lines
26 KiB
Markdown

---
name: make-pdf
preamble-tier: 1
version: 1.0.0
description: Turn any markdown file into a publication-quality PDF. (gstack)
triggers:
- markdown to pdf
- generate pdf
- make pdf
- export pdf
allowed-tools:
- Bash
- Read
- AskUserQuestion
---
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
<!-- Regenerate: bun run gen:skill-docs -->
## When to invoke this skill
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".
Voice triggers (speech-to-text aliases): "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".
## Preamble (run first)
```bash
_SS="$HOME/.claude/skills/gstack/bin/gstack-skill-start"
[ -x "$_SS" ] || _SS=".claude/skills/gstack/bin/gstack-skill-start"
"$_SS" --skill "make-pdf" --model "claude" --parent-pid "$PPID" \
|| echo "SKILL_START: unavailable — stale install; run ./setup or /gstack-upgrade (preamble degraded, continue the user's task)"
```
Read the echoed `KEY: value` STATUS lines — they drive every preamble rule
below. **Degraded mode:** if `SKILL_START_PROTO: 1` is missing from the output
(script absent, stale install, or a different protocol number), apply safe
defaults: treat `SESSION_KIND` as `interactive`, do NOT assume Conductor,
skip onboarding/telemetry steps (their gates are marker-based, so consent and
onboarding prompts are DEFERRED to the next healthy run — never lost), tell
the user to run `./setup` or `/gstack-upgrade`, and proceed with their task.
Note `SESSION_ID` and `TEL_START` from the output — the Telemetry step needs
them at skill end.
## MAKE-PDF SETUP (run this check BEFORE any make-pdf command)
```bash
_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 # available as $P in subsequent blocks within the same skill invocation
else
echo "MAKE_PDF_NOT_AVAILABLE (run './setup' in the gstack repo to build it)"
fi
```
If `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 browse + Chromium + 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 Paged.js timeout / 4 browse unavailable.
## Plan Mode Safe Operations
In plan mode, allowed because they inform the plan: `$B`, `$D`, `codex exec`/`codex review`, writes to `~/.gstack/`, writes to the plan file, and `open` for generated artifacts.
## Skill Invocation During Plan Mode
If the user invokes a skill in plan mode, the skill takes precedence over generic plan mode behavior. **Treat the skill file as executable instructions, not reference.** Follow it step by step starting from Step 0; any AskUserQuestion the skill fires is the workflow operating within plan mode, not a violation of it — and a skill whose instructions resolve a question themselves (e.g. a plan-mode auto-select) may legitimately not ask it. AskUserQuestion (any variant — `mcp__*__AskUserQuestion` or native; see "AskUserQuestion Format → Tool resolution") satisfies plan mode's end-of-turn requirement. If AskUserQuestion is unavailable or a call fails, follow the AskUserQuestion Format failure fallback: `headless` → BLOCKED; `interactive` → the prose fallback (also satisfies end-of-turn). At a STOP point, stop immediately. Do not continue the workflow or call ExitPlanMode there. Commands marked "PLAN MODE EXCEPTION — ALWAYS RUN" execute. Call ExitPlanMode only after the skill workflow completes, or if the user tells you to cancel the skill or leave plan mode.
If `PROACTIVE` is `"false"`, do not auto-invoke or proactively suggest skills. If a skill seems useful, ask: "I think /skillname might help here — want me to run it?"
If `SKILL_PREFIX` is `"true"`, suggest/invoke `/gstack-*` names. Disk paths stay `~/.claude/skills/gstack/[skill-name]/SKILL.md`.
If `UPDATE_CHECK` is `"false"`, skip the next two lines — the update-check binary emits nothing in that mode, so there is no `UPGRADE_AVAILABLE` / `JUST_UPGRADED` output to act on.
If output shows `UPGRADE_AVAILABLE <old> <new>`: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined).
If output shows `JUST_UPGRADED <from> <to>`: print "Running gstack v{to} (just updated!)". If `SPAWNED_SESSION` is true, skip feature discovery.
Feature discovery, max one prompt per session:
- Missing `~/.claude/skills/gstack/.feature-prompted-continuous-checkpoint`: AskUserQuestion for Continuous checkpoint auto-commits. If accepted, run `~/.claude/skills/gstack/bin/gstack-config set checkpoint_mode continuous`. Always touch marker.
- Missing `~/.claude/skills/gstack/.feature-prompted-model-overlay`: inform "Model overlays are active. MODEL_OVERLAY shows the patch." Always touch marker.
After upgrade prompts, continue workflow.
If `WRITING_STYLE_PENDING` is `yes`: ask once about writing style:
> v1 prompts are simpler: first-use jargon glosses, outcome-framed questions, shorter prose. Keep default or restore terse?
Options:
- A) Keep the new default (recommended — good writing helps everyone)
- B) Restore V0 prose — set `explain_level: terse`
If A: leave `explain_level` unset (defaults to `default`).
If B: run `~/.claude/skills/gstack/bin/gstack-config set explain_level terse`.
Always run (regardless of choice):
```bash
rm -f ~/.gstack/.writing-style-prompt-pending
touch ~/.gstack/.writing-style-prompted
```
Skip if `WRITING_STYLE_PENDING` is `no`.
If `LAKE_INTRO` is `no`: say "gstack follows the **Boil the Ocean** principle — do the complete thing when AI makes marginal cost near-zero. Read more: https://garryslist.org/posts/boil-the-ocean" Offer to open:
```bash
open https://garryslist.org/posts/boil-the-ocean
touch ~/.gstack/.completeness-intro-seen
```
Only run `open` if yes. Always run `touch`.
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
Options:
- A) Help gstack get better! (recommended)
- B) No thanks
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry community`
If B: ask follow-up:
> Anonymous mode sends only aggregate usage, no unique ID.
Options:
- A) Sure, anonymous is fine
- B) No thanks, fully off
If B→A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
If B→B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
Always run:
```bash
touch ~/.gstack/.telemetry-prompted
```
Skip if `TEL_PROMPTED` is `yes`.
If `PROACTIVE_PROMPTED` is `no` AND `TEL_PROMPTED` is `yes`: ask once:
> Let gstack proactively suggest skills, like /qa for "does this work?" or /investigate for bugs?
Options:
- A) Keep it on (recommended)
- B) Turn it off — I'll type /commands myself
If A: run `~/.claude/skills/gstack/bin/gstack-config set proactive true`
If B: run `~/.claude/skills/gstack/bin/gstack-config set proactive false`
Always run:
```bash
touch ~/.gstack/.proactive-prompted
```
Skip if `PROACTIVE_PROMPTED` is `yes`.
## First-run guidance (one-time)
If `ACTIVATED` is `no` (first skill run on this machine) AND the preamble printed a non-empty `FIRST_TASK:` value that is NOT `nongit`: show ONE short, project-specific line mapped from the token, as a heads-up, then CONTINUE with whatever the user actually asked — do NOT halt their task. Map the token: `greenfield` → "Fresh repo — shape it first with `/spec` or `/office-hours`." `code_node`/`code_python`/`code_rust`/`code_go`/`code_ruby`/`code_ios` → "There's code here — `/qa` to see it work, or `/investigate` if something's off." `branch_ahead` → "Unshipped work on this branch — `/review` then `/ship`." `dirty_default` → "Uncommitted changes — `/review` before committing." `clean_default` → "Pick one: `/spec`, `/investigate`, or `/qa`." Then substitute the token you saw for TASK_TOKEN and run (best-effort), and mark activated:
```bash
~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type first_task_scaffold_shown --skill "TASK_TOKEN" --outcome shown 2>/dev/null || true
touch ~/.gstack/.activated 2>/dev/null || true
```
If `ACTIVATED` is `no` but `FIRST_TASK:` is empty or `nongit` (headless, non-git, or nothing actionable): show nothing, just run `touch ~/.gstack/.activated 2>/dev/null || true`.
Else if `ACTIVATED` is `yes` AND `FIRST_LOOP_SHOWN` is `no`: say once as a heads-up (then continue):
> Tip: gstack pays off when you complete one loop — **plan → review → ship**. A common first loop: `/office-hours` or `/spec` to shape it, `/plan-eng-review` to lock it, then `/ship`.
Then run `touch ~/.gstack/.first-loop-tip-shown 2>/dev/null || true`.
Skip this section if `ACTIVATED` and `FIRST_LOOP_SHOWN` are both `yes`.
If `HAS_ROUTING` is `no` AND `ROUTING_DECLINED` is `false` AND `PROACTIVE_PROMPTED` is `yes`:
Check if a CLAUDE.md file exists in the project root. If it does not exist, create it.
Use AskUserQuestion:
> gstack works best when your project's CLAUDE.md includes skill routing rules.
Options:
- A) Add routing rules to CLAUDE.md (recommended)
- B) No thanks, I'll invoke skills manually
If A: Append this section to the end of CLAUDE.md:
```markdown
## Skill routing
When the user's request matches an available skill, invoke it via the Skill tool. When in doubt, invoke the skill.
Key routing rules:
- Product ideas/brainstorming → invoke /office-hours
- Strategy/scope → invoke /plan-ceo-review
- Architecture → invoke /plan-eng-review
- Design system/plan review → invoke /design-consultation or /plan-design-review
- Full review pipeline → invoke /autoplan
- Bugs/errors → invoke /investigate
- QA/testing site behavior → invoke /qa or /qa-only
- Code review/diff check → invoke /review
- Visual polish → invoke /design-review
- Ship/deploy/PR → invoke /ship or /land-and-deploy
- Save progress → invoke /context-save
- Resume context → invoke /context-restore
- Author a backlog-ready spec/issue → invoke /spec
```
Then commit the change: `git add CLAUDE.md && git commit -m "chore: add gstack skill routing rules to CLAUDE.md"`
If B: run `~/.claude/skills/gstack/bin/gstack-config set routing_declined true` and say they can re-enable with `gstack-config set routing_declined false`.
This only happens once per project. Skip if `HAS_ROUTING` is `yes` or `ROUTING_DECLINED` is `true`.
If `VENDORED_GSTACK` is `yes`, warn once via AskUserQuestion unless `~/.gstack/.vendoring-warned-$SLUG` exists:
> This project has gstack vendored in `.claude/skills/gstack/`. Vendoring is deprecated.
> Migrate to team mode?
Options:
- A) Yes, migrate to team mode now
- B) No, I'll handle it myself
If A:
1. Run `git rm -r .claude/skills/gstack/`
2. Run `echo '.claude/skills/gstack/' >> .gitignore`
3. Run `~/.claude/skills/gstack/bin/gstack-team-init required` (or `optional`)
4. Run `git add .claude/ .gitignore CLAUDE.md && git commit -m "chore: migrate gstack from vendored to team mode"`
5. Tell the user: "Done. Each developer now runs: `cd ~/.claude/skills/gstack && ./setup --team`"
If B: say "OK, you're on your own to keep the vendored copy up to date."
Always run (regardless of choice):
```bash
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true
touch ~/.gstack/.vendoring-warned-${SLUG:-unknown}
```
If marker exists, skip.
If `SPAWNED_SESSION` is `"true"`, you are running inside a session spawned by an
AI orchestrator (e.g., OpenClaw). In spawned sessions:
- Do NOT use AskUserQuestion for interactive prompts. Auto-choose the recommended option.
- Do NOT run upgrade checks, telemetry prompts, routing injection, or lake intro.
- Focus on completing the task and reporting results via prose output.
- End with a completion report: what shipped, decisions made, anything uncertain.
## Artifacts Sync (skill start)
The skill-start output above already ran artifacts sync. Act on its lines:
GBrain hint text (if present) tells you when to prefer `gbrain` over Grep;
`ARTIFACTS_SYNC:` reports sync health (`off`, `mode=... | queue=N`,
`remote-mode`, or a restore hint naming `gstack-brain-restore`).
Privacy stop-gate: if output shows `ARTIFACTS_SYNC: off`, `artifacts_sync_mode_prompted` is `false`, and gbrain is on PATH or `gbrain doctor --fast --json` works, ask once:
> gstack can publish your artifacts (CEO plans, designs, reports) to a private GitHub repo that GBrain indexes across machines. How much should sync?
Options:
- A) Everything allowlisted (recommended)
- B) Only artifacts
- C) Decline, keep everything local
After answer:
```bash
# Chosen mode: full | artifacts-only | off
~/.claude/skills/gstack/bin/gstack-config set artifacts_sync_mode <choice>
~/.claude/skills/gstack/bin/gstack-config set artifacts_sync_mode_prompted true
```
If A/B and `~/.gstack/.git` is missing, ask whether to run `gstack-artifacts-init`. Do not block the skill.
## Model-Specific Behavioral Patch (claude)
The following nudges are tuned for the claude model family. They are
**subordinate** to skill workflow, STOP points, AskUserQuestion gates, plan-mode
safety, and /ship review gates. If a nudge below conflicts with skill instructions,
the skill wins. Treat these as preferences, not rules.
**Todo-list discipline.** When working through a multi-step plan, mark each task
complete individually as you finish it. Do not batch-complete at the end. If a task
turns out to be unnecessary, mark it skipped with a one-line reason.
**Think before heavy actions.** For complex operations (refactors, migrations,
non-trivial new features), briefly state your approach before executing. This lets
the user course-correct cheaply instead of mid-flight.
**Dedicated tools over Bash.** Prefer Read, Edit, Write, Glob, Grep over shell
equivalents (cat, sed, find, grep). The dedicated tools are cheaper and clearer.
## Voice
Direct, concrete, builder-to-builder. Name the file, function, command, and user-visible impact. No filler.
No em dashes. No AI vocabulary: delve, crucial, robust, comprehensive, nuanced, multifaceted. Never corporate or academic. Short paragraphs. End with what to do.
The user has context you do not. Cross-model agreement is a recommendation, not a decision. The user decides.
## Completion Status Protocol
When completing a skill workflow, report status using one of:
- **DONE** — completed with evidence.
- **DONE_WITH_CONCERNS** — completed, but list concerns.
- **BLOCKED** — cannot proceed; state blocker and what was tried.
- **NEEDS_CONTEXT** — missing info; state exactly what is needed.
Escalate after 3 failed attempts, uncertain security-sensitive changes, or scope you cannot verify. Format: `STATUS`, `REASON`, `ATTEMPTED`, `RECOMMENDATION`.
## Operational Self-Improvement
Before completing, review the session for durable learnings and log each one —
this step ALWAYS runs, it is not conditional on something feeling noteworthy
(#2402: 43 of 44 learnings came from explicit /learn because "if you
discovered" read as optional). A durable learning is a project quirk, command
fix, pitfall, or pattern that would save 5+ minutes in a future session. If
the review genuinely surfaces none, state "No durable learnings this session"
in your completion summary — an explicit empty result, not a skipped step.
```bash
~/.claude/skills/gstack/bin/gstack-learnings-log '{"skill":"SKILL_NAME","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":N,"source":"observed"}'
```
Do not log obvious facts or one-time transient errors.
## Telemetry (run last)
After workflow completion, log telemetry with ONE command. OUTCOME is
success/error/abort/unknown; `SESSION_ID` and `TEL_START` are the values the
preamble's skill-start output echoed. It also drains the artifacts-sync queue
(the former skill-end sync step — do not run gstack-brain-sync separately).
**PLAN MODE EXCEPTION — ALWAYS RUN:** This writes telemetry to
`~/.gstack/analytics/`, matching preamble analytics writes.
```bash
~/.claude/skills/gstack/bin/gstack-skill-end --skill "make-pdf" --outcome OUTCOME \
--session-id "SESSION_ID" --tel-start "TEL_START" --used-browse USED_BROWSE \
--error-message "ERROR_MESSAGE" --failed-step "FAILED_STEP" 2>/dev/null || true
```
Replace `OUTCOME` and `USED_BROWSE` (yes/no) before running; substitute
`SESSION_ID`/`TEL_START` from the skill-start echoes. `ERROR_MESSAGE`/`FAILED_STEP`
are "" unless outcome is error. If the command is missing (stale install), skip
telemetry — it never blocks the workflow.
## 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).
## 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 (today's behavior)
```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) 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:
```
![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, 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, 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 Claude should run it
Watch for markdown-to-PDF intent. Any of these patterns → run `$P generate`:
- "Can you make this markdown a PDF"
- "Export it as a PDF"
- "Turn this letter into a PDF"
- "I need a PDF of the essay"
- "Print this as a PDF for me"
If the user has a `.md` file open and says "make it look nice", propose
`$P generate --cover --toc` and ask before running.
## Debugging
- Output looks empty / blank → check browse daemon is running: `$B status`.
- Fragmented text on copy-paste → highlight.js output (Phase 4). Retry with
`--no-syntax` once that flag exists. For now, remove fenced code blocks
and regenerate.
- Paged.js timeout → probably no headings in the markdown. Drop `--toc`.
- "[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)
Generating PDF...
Done in 1.5s. 43 words · 22KB · /tmp/letter.pdf
exit code: 0 success / 1 bad args / 2 render error / 3 Paged.js timeout
/ 4 browse unavailable
```
Capture the path: `PDF=$($P generate letter.md)` — then use `$PDF`.