A Claude skill and CLI tool that turns a natural language request into a professional PDF business document. Claude handles the conversation; a deterministic script handles rendering. Same input always produces the same PDF.
# Install the skill — one command
npx skills add julioccorderoc/doc-generatorThen just ask Claude:
"I need a PO for PureSource — 250kg whey protein at $8.40/kg, net 30, FedEx Ground."
Claude collects what it needs, confirms, and drops a clean PDF in your output/ folder. No forms, no SaaS, no copy-paste.
New doc type ideas or contributions welcome — adding one requires five files, nothing else.
Replace placeholders above with screenshots from your output/ folder once generated.
| Slug | Document | Description |
|---|---|---|
purchase_order |
Purchase Order | Buyer-to-vendor authorization for goods or services at agreed prices and terms |
invoice |
Invoice | Issuer-to-client payment request for goods delivered or services rendered |
request_for_quotation |
Request for Quotation (RFQ) | Buyer-to-vendor specification document requesting a price; no monetary values |
test_request_form |
Test Request Form | Customer-to-laboratory sample submission form requesting analyses; no monetary values |
On the roadmap: delivery notes, packing slip, quotes.
- Claude collects data — asks for required fields in a single pass, applies smart defaults, never asks for computed values.
- Claude writes a payload file — JSON at a temp path. No inline JSON, no shell quoting issues.
- Claude invokes the CLI —
uv run python scripts/generate.py --doc_type <type> --payload <path>. - The script renders the PDF — Pydantic validates, Python computes, Jinja2 renders, WeasyPrint writes. No model in the render path.
- Claude reports the result — output path (and, for financial doc types, grand total and balance due), or a plain-language validation error.
Install the skill so Claude can orchestrate the workflow:
npx skills add julioccorderoc/doc-generatorTo update after changes:
npx skills update -gSee SKILL.md for the full skill definition: trigger conditions, data collection protocol, and output presentation format.
The CLI is what actually renders the PDF — it needs to be on your machine.
git clone https://github.com/julioccorderoc/doc-generator.git
cd doc-generator
uv sync
brew install pango # macOS only — required by WeasyPrint
# Ubuntu/Debian: sudo apt-get install libpango-1.0-0 libharfbuzz0b libpangoft2-1.0-0| Library | Role |
|---|---|
| WeasyPrint | HTML/CSS → PDF renderer. No headless browser, no Chromium. |
| Jinja2 | Templating engine. Templates receive pre-formatted strings — no logic inside. |
| Pydantic v2 | Schema validation and computed fields (subtotal, grand_total, etc.). |
| uv | Dependency management and script runner. Reproducible environments with a lockfile. |
For agents or direct use:
uv run python scripts/generate.py --doc_type <type> --payload <path> [--preview] [--output_name <stem>] [--output_dir <dir>] [--save_payload]
| Argument | Required | Description |
|---|---|---|
--doc_type |
Yes | Document type slug (purchase_order, invoice, request_for_quotation, test_request_form). |
--payload |
Yes | Path to a JSON file. File path only — not inline JSON. |
--preview |
No | Opens the PDF with the OS default viewer after generation. Silent no-op in headless environments. |
--output_name |
No | Custom filename stem. Output becomes <PREFIX>_<name>.pdf (e.g. --output_name NS39 → PO_NS39.pdf). Defaults to date + sequential counter. |
--output_dir |
No | Directory to save the generated PDF. Defaults to <project_root>/output/. Pass $(pwd) to save in the caller's working directory. |
--save_payload |
No | Saves the validated payload (with all computed fields) as a .json file alongside the PDF, using the same filename stem. |
Exit codes:
| Outcome | Exit code | stdout |
|---|---|---|
| Success | 0 |
Absolute output path, e.g. /path/to/doc-generator/output/PO_20260316_0001.pdf |
| Validation error | 1 |
Structured error — which fields failed and why |
Unknown doc_type |
1 |
List of registered doc type slugs |
No interactive prompts. No assumed environment variables. Agents capture stdout and check the exit code.
Full platform-agnostic interface contract: CLAUDE.md.
macOS / Linux prerequisites:
uv sync
brew install pango # macOS system dependency — once only
# Ubuntu/Debian: sudo apt-get install libpango-1.0-0 libharfbuzz0b libpangoft2-1.0-0Generate a document:
DYLD_LIBRARY_PATH=/opt/homebrew/lib uv run python scripts/generate.py \
--doc_type purchase_order \
--payload tests/fixtures/sample_po.json \
--previewTest validation error output:
DYLD_LIBRARY_PATH=/opt/homebrew/lib uv run python scripts/generate.py \
--doc_type purchase_order \
--payload tests/fixtures/invalid_po.jsonRun the test suite (no Pango or display needed):
uv run pytestFull field references — all optional fields, validation rules, and example payloads:
- references/purchase_order.md
- references/invoice.md
- references/request_for_quotation.md
- references/test_request_form.md
Adding a new document type requires five files — nothing else changes:
1. references/<doc_type>.md — field definitions, validation rules, computed fields, layout notes
2. schemas/<doc_type>.py — Pydantic v2 model derived from the reference
3. templates/<doc_type>.html — Jinja2 template extending base.html
4. builders/<doc_type>.py — context builder function
5. builders/__init__.py — one DocTypeConfig entry added to REGISTRY
generate.py, base.html, style.css, and the core generation engine are never modified when adding a doc type.
See references/EXTENDING.md for the full developer guide.
| Property | Detail |
|---|---|
| Deterministic | Same JSON input always produces the same PDF. No randomness, no model calls in the render path. |
| Fully local | Runs on your machine. No outbound network calls at generation time. |
| No paid services | WeasyPrint, Jinja2, and Pydantic are all open-source. No API keys required. |
| No credential access | The script reads only the payload file you pass it. It does not touch environment variables, secrets, or system files. |
| Auditable | The render path is a single script: scripts/generate.py. Templates: templates/. Schemas: schemas/. |
Key directories — full detail in CLAUDE.md:
doc-generator/
├── CLAUDE.md ← Agent entry point: CLI contract, conventions, design decisions
├── SKILL.md ← Claude skill definition
├── scripts/ ← CLI entrypoint (generate.py)
├── schemas/ ← Pydantic v2 models per doc type
├── builders/ ← Context builders + REGISTRY
├── templates/ ← Jinja2 templates extending base.html
├── assets/ ← style.css + per-doc-type CSS
├── references/ ← Source-of-truth specs + EXTENDING.md + DESIGN_SYSTEM.md
└── tests/fixtures/ ← Valid and invalid sample payloads
Contributions welcome. The most useful things to add:
- New document types — follow the five-step pattern in references/EXTENDING.md. Each doc type is self-contained.
- Bug fixes — check docs/decisions/ before changing any architectural pattern; a decision record may explain the constraint.
When in doubt, read the reference file for the doc type you are modifying first. The reference is the source of truth — not the code.