mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-13 16:38:56 +02:00
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>
593 lines
26 KiB
Markdown
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:
|
|
|
|
```
|
|
{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-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`.
|