Skip to content

About

Deterministic skill for professional PDF generation.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

doc-generator

Python License Stars

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-generator

Then 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.


What it looks like

Replace placeholders above with screenshots from your output/ folder once generated.


Supported Document Types

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.


How It Works

  1. Claude collects data — asks for required fields in a single pass, applies smart defaults, never asks for computed values.
  2. Claude writes a payload file — JSON at a temp path. No inline JSON, no shell quoting issues.
  3. Claude invokes the CLI — uv run python scripts/generate.py --doc_type <type> --payload <path>.
  4. The script renders the PDF — Pydantic validates, Python computes, Jinja2 renders, WeasyPrint writes. No model in the render path.
  5. Claude reports the result — output path (and, for financial doc types, grand total and balance due), or a plain-language validation error.

Claude Code Skill

Install the skill so Claude can orchestrate the workflow:

npx skills add julioccorderoc/doc-generator

To update after changes:

npx skills update -g

See SKILL.md for the full skill definition: trigger conditions, data collection protocol, and output presentation format.

Manual Setup

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

Stack

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.

CLI Reference

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.


Running Locally

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-0

Generate a document:

DYLD_LIBRARY_PATH=/opt/homebrew/lib uv run python scripts/generate.py \
  --doc_type purchase_order \
  --payload tests/fixtures/sample_po.json \
  --preview

Test validation error output:

DYLD_LIBRARY_PATH=/opt/homebrew/lib uv run python scripts/generate.py \
  --doc_type purchase_order \
  --payload tests/fixtures/invalid_po.json

Run the test suite (no Pango or display needed):

uv run pytest

Full field references — all optional fields, validation rules, and example payloads:


Extending

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.


Why This Skill Is Safe to Install

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/.

Project Structure

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

Contributing

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.

About

Deterministic skill for professional PDF generation.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages