| name | pdf-tools |
| description | Firm-owned PDF reading + actions over the in-app PDF viewer. Use whenever the user wants to work with a PDF: read it (extract its text to quote, summarize, or compare), annotate it (sticky notes, highlight boxes), list or fill its form fields (AcroForm), or stamp a signature (typed name + date, or a PNG signature image). Editing always writes a NEW copy (.annotated.pdf / .filled.pdf / .signed.pdf) — the original PDF is never modified. Self-contained; runs on the firm's own infrastructure; pairs with the in-app PDF viewer. |
PDF reading + actions (text / annotate / fill-form / sign)
This skill is how this firm reads, annotates, fills, and signs PDFs with AI. It is
self-contained: assets/pdf-agent.mjs imports vendored engines (pdf-lib for
actions, pdf.js for text — pure JS, no native deps), so it runs in any workspace
with no install. Do not hand-edit PDF bytes or reach for python or pdftotext
(they may not exist on the machine) — use this tool. Every editing command writes
a new file next to the source; the original PDF is never touched.
The tool
node .opencode/skills/pdf-tools/assets/pdf-agent.mjs inspect "<file.pdf>"
node .opencode/skills/pdf-tools/assets/pdf-agent.mjs text "<file.pdf>" --pages 1-4
echo '<plan-json>' | node .opencode/skills/pdf-tools/assets/pdf-agent.mjs annotate "<file.pdf>" --plan -
echo '{"Field Name":"value","Agree":true}' | node .opencode/skills/pdf-tools/assets/pdf-agent.mjs fill "<file.pdf>" --data -
node .opencode/skills/pdf-tools/assets/pdf-agent.mjs sign "<file.pdf>" --name "Jane Doe" --date 2026-07-01
node .opencode/skills/pdf-tools/assets/pdf-agent.mjs sign "<file.pdf>" --name "Jane Doe" --image signature.png --page 3 --x 320 --y 90
Coordinates: pages are 1-based; x/y are PDF points (1/72 inch, US
Letter is 612×792) with the origin at the BOTTOM-LEFT of the page; (x, y) is
the bottom-left corner of the note/highlight/signature. Get each page's size from
inspect before placing anything.
Annotate — the plan
{
"annotations": [
{ "type": "note", "page": 2, "x": 400, "y": 500, "text": "Cap is low for this deal — propose EUR 75k." },
{ "type": "highlight", "page": 2, "x": 70, "y": 512, "width": 300, "height": 14 }
]
}
note draws a yellow sticky note (text wraps automatically); text is required.
highlight draws a translucent box over existing text; width is required,
height defaults to 14 (one text line).
- Optional
color is "#RRGGBB". A bad page/coordinate fails with a clear error
instead of writing a broken file.
Fill-form
inspect first — fill by the exact name it reports. Values by field type:
text takes any string, checkbox takes true/false, dropdown / option-list /
radio-group take one of the listed options (anything else is skipped with the
valid options in the reason). The result lists filled and skipped (each skip has a
reason) — report skips and retry with corrected names/options rather than dropping them.
Sign
--name is required; --date defaults to today. Default placement is the
bottom-right of the LAST page — pass --page/--x/--y to place it elsewhere (e.g.
on the signature line inspect showed you). With --image sig.png the PNG is drawn
above the signature rule instead of the typed name (--image-width scales it,
default 140pt). This is a visible signature stamp, not a cryptographic signature.
Read (text)
To answer questions about a PDF, quote it, summarize it, or compare it against
another document, run text and work from the returned per-page JSON — cite the
page numbers it gives you. A scanned/image-only PDF has no text layer: pages come
back empty. Say so plainly (offer annotation instead); never fabricate content.
Workflow
- Resolve the target PDF (attached,
@path, named, or in a folder). If the user
points at a scan, remember highlights cover images fine, but form-fill needs real
AcroForm fields — inspect reports fields: [] for flat PDFs; say so and offer
annotations instead.
inspect and read the JSON: page sizes for placement, field names/options for
filling. To read the document itself, run text.
- Run the action (
annotate / fill / sign). The tool refuses to overwrite
the source; output goes to <base>.annotated.pdf / .filled.pdf / .signed.pdf
next to it (--out to change).
- Hand back: state the output file by the
name the tool returns (a plain,
space-free filename — this is what makes it appear as a clickable PDF artifact in
the panel). Tell the user to open it in the in-app PDF viewer, summarize what you
did, and flag any skipped entries with their reasons.
Guardrails
- Non-destructive. Never modify the source PDF in place; there is deliberately no
--in-place.
- Never fabricate form values, names, or dates — ask when a value is missing.
- Encrypted/broken PDFs fail with a clear message; relay it, don't work around it.
- Distribution. Bundled-core: seeded into every workspace via
core-skills.ts
(scripts/gen-core-skills.mjs), alongside the in-app PDF viewer it feeds.