PDF Toolkit
An Acrobat-class document engine exposed through an agent-friendly CLI. Express work as document intent, not low-level PDF commands.
Runtime contract
The bundled CLI is self-contained (no npm install). It requires Node 20+ and external binaries: qpdf and poppler-utils (pdfinfo, pdftotext, pdftoppm, pdffonts, pdfimages, pdfdetach). Ghostscript (gs) is optional and only improves compression.
Always run commands through Node, from any working directory:
node <skill-dir>/cli/pdf.cjs <command> [options] --json
First time in a session, verify the environment:
node <skill-dir>/cli/pdf.cjs doctor --json
If required binaries are missing, install them (apt-get install qpdf poppler-utils on Debian/Ubuntu, brew install qpdf poppler on macOS) or tell the user what is missing. Do not silently substitute other tools.
Golden workflow (follow for every PDF job)
- Inspect — always inspect before modifying:
pdf inspect <file> --json. Readpages,encrypted,signed,formType,pageTypes.scanned, andwarnings. - Classify — decide the job type (structural / form / template / overlay / security / compression / extraction / render). For ambiguous intent, use
pdf plan <file> --intent "..."to get a structured plan plus blockers. - Check constraints — encryption needs a password; signed documents get invalidated by edits (warn the user); XFA forms are unsupported; scanned pages have no text layer (extraction and search-based operations will silently return nothing — say so).
- Execute — run the operation commands.
- Validate — run
pdf validate <output> --jsonon every modified file. For visually sensitive work (overlays, watermarks, templates), also render withpdf to-imageand look at the result. - Return — hand back the output file, summary, warnings, and validation result.
Reading results
Every command returns a structured result (--json):
{ "ok": true, "operation": "compress", "output": "...", "summary": "...",
"warnings": ["..."], "data": { }, "manifest": { "operations": [], "engine": [] } }
Treat warnings as first-class output: surface them to the user. Exit code is 1 when ok is false.
Command map
| Intent | Command |
|---|---|
| Inspect a document | inspect |
| Plan from natural language | plan --intent "..." |
| Merge / split / page surgery | merge, split, extract-pages, delete-pages, reorder, rotate |
| Work with real form fields | form inspect, form fill, form clear, form flatten |
| Fill a static (non-fillable) layout | template inspect, template fill, template batch |
| Add content at coordinates | add-text, add-image, add-shape |
| Running text and numbering | header, footer, page-number |
| Watermark or stamp | watermark, stamp |
| Protect / unprotect | encrypt, decrypt, permissions, sanitize |
| Reduce file size | compress |
| Get text or assets out | extract, extract-images, extract-attachments |
| Render pages as images | to-image |
| Verify an output | validate |
Full flags and examples: see references/commands.md. Template JSON schema and batch generation: see references/templates.md. A ready-to-try example lives in assets/examples/.
Routing rules that prevent wrong-tool mistakes
- Fillable form vs. static form: if
inspectreportsformType: "acroform", usepdf form fill. IfformType: "none"but the page visually looks like a form, it is a static layout — usepdf template fillwith a coordinate mapping. Never try toform filla static PDF. - Scanned pages: when
pageTypes.scanned > 0, text extraction, text search, and form detection do not apply to those pages. State that OCR is required; the OCR engine is on the product roadmap, not in this build. - Repeatable placements: for one-off insertions use
add-text/add-imageand infer coordinates from ato-imagerender. For anything repeatable, create a template JSON once and reuse it. - Bookmarks: structural operations do not preserve outlines/bookmarks; warn when the source has them.
Security rules (non-negotiable)
- Never attempt to crack passwords or bypass permissions.
decryptrequires a valid password; on failure, report it and stop. - Prefer secrets via environment:
--password-env VARorPDF_PASSWORD. Never echo password values in logs, summaries, or manifests. - Modifying a signed PDF invalidates its signature. Warn before executing and include the warning in the result.
- A coloured rectangle is not redaction. Overlays (watermark, whiteout, shapes) leave underlying text extractable. True redaction is a roadmap capability; if asked to redact, say so instead of faking it with a black box.
- Work on copies. Never modify the user's original file in place — always write to a new
--outputpath.
Coordinates and fonts
- CLI overlay commands (
add-text,add-image,add-shape) use PDF points, bottom-left origin. - Template JSON uses millimetres, top-left origin (designer convention).
- Built-in fonts (helvetica, times, courier, + bold/oblique) cover Latin text plus common punctuation. For CJK or other scripts, pass
--font-filewith a TTF/OTF — otherwise the command fails fast withFONT_REQUIREDrather than emitting garbled text.
Known limitations (roadmap, by design)
Post-MVP capabilities — when a request needs one, name the gap plainly and offer the closest supported alternative:
- OCR — scanned pages cannot be made searchable or text-extracted.
- True content redaction — see security rules; do not fake it with overlays.
- Table / schema-based extraction —
extractreturns text, Markdown, or per-page JSON; it does not reconstruct tables into CSV or extract against a JSON schema. Offer Markdown text extraction and note that table structure is approximate. - Annotations — no highlights, sticky notes, or comments (creation or extraction).
- Images → PDF / other formats → PDF — no
from-imageor document conversion; only PDF inputs. - Page insertion/duplication — no insert-blank-page, insert-PDF-into-PDF, or duplicate-page commands (workaround:
extract-pages+merge). - Image/logo watermarks —
watermarkis text-only; useadd-imageper page for a logo. - Link annotations —
add-shape/add-textdraw content but cannot create clickable links. - PDF comparison — no
comparecommand. - Image downsampling without Ghostscript — compression falls back to lossless.