--- 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 --- ## 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 [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 ` — 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 `: 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 `: 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 ~/.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 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 Diagonal watermark ("DRAFT", "CONFIDENTIAL") --header-template Custom running header --footer-template 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`.