| name | pdf-cli |
| description | Read, fill, and comment on PDF files. Use to extract a PDF as Markdown or text, read a dense table or scanned page, fill out a PDF form (AcroForm/XFA-hybrid), export or inspect form field values, add or reply to annotations and comments, pull embedded images or figures out, or rasterize pages to PNG. Not for Word .docx, Google Docs, Excel, PowerPoint, or creating a PDF from scratch. |
pdf-cli
pdf is a command-line tool for reading, filling, and commenting on PDF files.
It saves incrementally (the original bytes stay a byte-identical prefix, so
signatures survive), addresses everything with stable locators, and signals
success through an exit code plus a one-line confirmation — so even small,
cheap models can drive it reliably.
macOS only. It refuses to run elsewhere rather than half-working.
0. Make sure the binary is on PATH
Run pdf --version. If you get "command not found", install it. Prefer the npm
registry — no shell piping, and the package runs no install scripts:
bun add -g bun-pdf
Already installed as a standalone binary? pdf upgrade replaces it in place
with the latest release, verifying the SHA-256 first. An npm install is owned by
the package manager, and upgrade says so rather than fighting it.
No Bun? From this skill folder run sh scripts/bootstrap.sh. It downloads no
scripts and runs no remote code — it resolves the latest release tag from the
GitHub API, downloads that release's prebuilt binary pinned to the tag, and
verifies its SHA-256 against the release's published SHA256SUMS before
installing. (By hand: download pdf-darwin-arm64 or pdf-darwin-x64 plus
SHA256SUMS from https://github.com/kklimuk/pdf-cli/releases/latest, verify,
chmod +x, put it on PATH.)
1. The contract is --help / pdf info — start there
The help text is authoritative and versioned with the binary. This skill is thin
on purpose and defers to it. Before doing anything, run (none of these need a FILE):
pdf --help
pdf info locators
Then, for any real file, start with pdf info FILE. It prints page count,
token estimate, form type, which pages are scanned, and — most usefully — the
exact next command to run.
2. Locators are the backbone
Every command that points at part of a document takes a locator. pN is the
page and is 1-based, always agreeing with --pages. Everything minted below
page level is 0-based. Char spans are end-exclusive.
| form | means |
|---|
p3 | page 3 |
p3.b2 | block 2 on page 3 |
p3.b2:10-24 | chars 10–24 of that block |
p3.t0 | table 0 on page 3 |
p3.t0:r4c1 | row 4, column 1 of that table |
p3.img0 | image 0 on page 3 |
fld7 | form field 7 |
Never hand-count offsets. Run pdf find FILE "phrase" and paste the minted
locator straight into --at.
3. Reading without drowning
pdf read FILE renders pages as annotated markdown: tables as <table>, form
fields as <input name="alias">, images as . HTML comments carry
addresses; pdf:TYPE comments are deviation-only metadata.
Reads are token-budgeted (~25k). On a big document, narrow rather than dump:
pdf wc FILE
pdf read FILE --pages 6
pdf find FILE "31,650"
pdf read FILE --at p6.t3
4. Batch instead of looping
Asking several questions of one document? One call, not N:
pdf find FILE --batch queries.jsonl
pdf read FILE --at p2.t0 --at p6.t3
5. Filling a form
pdf info FILE
pdf form fields FILE
echo '{"f1_01":"Dana"}' | pdf form fill FILE --data -
pdf form export FILE
Aliases from pdf form fields are what fill expects. A choose-one field
lists its exact legal values; send one of those verbatim.
6. The output contract
- Exit code is the signal:
0 ok · 1 error · 2 usage/bad locator · 3 not found.
- Errors are one JSON line:
{"code": "...", "error": "...", "hint": "..."}. Read the hint.
- A mutation that changes nothing is an ERROR (
NO_CHANGE, MATCH_NOT_FOUND,
ANCHOR_NOT_FOUND) — never a silent success. Do not treat a zero count as done.
--dry-run validates everything and writes nothing. A passing dry-run means
the real run will succeed.
- Changes must read back. After any mutation, verify with
pdf form export,
pdf comments list, or pdf read. The tool checks this itself and fails loudly
when it cannot confirm — trust that over your own expectation.
7. Scanned pages
pdf info FILE reports which pages are scanned. Those have no text layer, so
read has nothing to give you. Rasterize and look at them instead:
pdf render FILE --pages 2 --dpi 300 --hires -o out/
8. Things that will bite you
- Don't rebuild a PDF to change it.
pdf form fill / pdf comments add save
incrementally by default; --rewrite exists but breaks signatures and is
refused on signed documents without --force.
- Don't guess a field alias.
pdf form fields prints them.
- Don't page through a document to find something.
pdf find is one call.
- Dynamic XFA forms cannot be filled —
pdf info says so explicitly when it sees one.