BundledProductivityVersion 1.1.0

Hermes Agent PDF Skill: Create, Read, Merge, Fill, and OCR PDFs

PDF files: create, read, merge, fill, OCR, edit text.

Written by Neura Market from the official Hermes Agent documentation for Pdf. Commands, paths, and version numbers are reproduced from the source unchanged.

Read the official documentation

The PDF skill gives Hermes Agent a full toolkit for working with PDF files: generating documents from JSON specs, building and filling AcroForm forms, pulling out text, tables, and metadata, rearranging pages, stamping and watermarking, and encrypting or decrypting. It is the skill you reach for when a task involves a PDF as an input, an output, or both, and you want the agent to handle it end to end rather than shelling out to a dozen unrelated utilities.

What it does

At its centre the skill wraps three well-known Python libraries, pypdf, reportlab, and pdfplumber, behind a set of argparse command-line helpers that all live in scripts/. Each helper reads and writes JSON strictly as UTF-8, prints a JSON result to stdout, and exits non-zero on failure, which makes the output easy for the agent to parse and act on.

Two capabilities that used to be separate skills have been absorbed into this one and now live under references/. You read the matching reference file before attempting those tasks:

  • Scanned or image-only PDFs and OCR. There is a fast path built on pymupdf and a higher-quality path built on marker-pdf, driven by scripts/extract_pymupdf.py and scripts/extract_marker.py. The details are in references/ocr-extraction.md.
  • Editing text inside an existing PDF through natural-language prompts, handled by the nano-pdf CLI. The details are in references/nano-pdf-editing.md.

In practice this means a single skill covers document generation, extraction, page surgery, forms, security, and metadata, with the two specialist workflows documented separately so they do not bloat the main path.

Before you start

You need Python 3.10 or newer with pypdf, reportlab, and pdfplumber available. Install them with:

python -m pip install pypdf reportlab pdfplumber

Page rasterization, which covers pdf_page_image.py and overlay rendering, is optional and needs either pypdfium2 or poppler's pdftoppm on your PATH:

python -m pip install pypdfium2

Scripts fall back from pypdfium2 to pdftoppm and report {"rendered": false, "missing": [...]} with exit code 0 when neither is present. That exit code matters: a rasterization request that cannot be satisfied does not look like a crash, so check the JSON body rather than the status alone.

Each helper script checks its imports lazily and prints an install hint if a dependency is missing, so you generally find out what to install at the moment you need it rather than up front.

The skill is bundled with Hermes Agent, so it is installed by default. It is authored by Nous Research, carries the MIT licence, and runs on linux, macos, and windows.

How to run the helpers

Every helper is an argparse CLI in scripts/ and is meant to be run with the terminal tool. Each one supports --help. They read and write JSON strictly as UTF-8, print JSON results to stdout, and exit non-zero on failure.

python scripts/pdf_create.py spec.json -o out.pdf         # build PDF from JSON spec
python scripts/pdf_make_form.py formspec.json -o form.pdf # build fillable AcroForm from JSON spec
python scripts/pdf_form_layout.py formspec.json           # lint form layout BEFORE building
python scripts/pdf_form_layout.py formspec.json --render-overlay boxes.png [--pdf form.pdf]
python scripts/pdf_read.py doc.pdf --text                 # per-page text (JSON)
python scripts/pdf_read.py doc.pdf --tables --csv-dir t/  # tables to JSON + CSV files
python scripts/pdf_read.py doc.pdf --meta                 # metadata, page sizes, encrypted/scanned flags
python scripts/pdf_read.py form.pdf --fields              # form fields: name, type, value
python scripts/pdf_merge.py a.pdf b.pdf -o merged.pdf [--bookmarks]
python scripts/pdf_split.py doc.pdf --pages 1-3,7 -o part.pdf [--rotate 90]
python scripts/pdf_fill_form.py form.pdf --fields-json values.json -o filled.pdf [--flatten]
python scripts/pdf_secure.py doc.pdf --encrypt -o enc.pdf --user-password your-password
python scripts/pdf_secure.py enc.pdf --decrypt -o dec.pdf --password your-password
python scripts/pdf_watermark.py doc.pdf --stamp mark.pdf -o stamped.pdf [--under]
python scripts/pdf_stamp.py doc.pdf -o out.pdf --text "DRAFT" --x 150 --y 400 \
    --font-size 60 --rotation 45 --opacity 0.3 --color "#cc0000" [--pages 1-3]
python scripts/pdf_stamp.py doc.pdf -o out.pdf --image sig.png --x 400 --y 60 --width 120
python scripts/pdf_page_image.py doc.pdf --pages 1-3 --dpi 150 --out-dir imgs/
python scripts/pdf_meta.py doc.pdf --set-meta --title "T" --author "A" -o out.pdf
python scripts/pdf_meta.py doc.pdf --attach data.csv -o out.pdf
python scripts/pdf_meta.py doc.pdf --list-attachments | --extract-attachments dir/

Quick reference

The table below maps each task to the tool underneath and the command or API that drives it.

TaskToolCommand / API
Create doc (headings, tables, images)reportlab platypuspdf_create.py spec.json -o out.pdf
Build fillable formreportlab acroFormpdf_make_form.py formspec.json -o form.pdf
Lint form layout / overlay imagepure python + PILpdf_form_layout.py formspec.json [--render-overlay o.png]
Per-page textpdfplumberpdf_read.py f.pdf --text
Tables → JSON/CSVpdfplumberpdf_read.py f.pdf --tables
Metadata / sizes / encrypted / scannedpypdf + pdfplumberpdf_read.py f.pdf --meta
Merge (+ outline)pypdfpdf_merge.py a.pdf b.pdf -o m.pdf
Split / extract / rotatepypdfpdf_split.py f.pdf --pages 2-5 --rotate 90
List / fill / flatten formpypdfpdf_read.py --fields, pdf_fill_form.py
Encrypt / decrypt (AES-256)pypdfpdf_secure.py --encrypt/--decrypt
Watermark / stamp PDF pagepypdfpdf_watermark.py f.pdf --stamp w.pdf
Stamp text/image at coordinatesreportlab + pypdfpdf_stamp.py f.pdf --text "Sign here" --x 400 --y 60
Pages → PNG (review / OCR hand-off)pypdfium2 or pdftoppmpdf_page_image.py f.pdf --pages 1-3 --out-dir imgs/
Set/clear metadata, attachmentspypdfpdf_meta.py --set-meta / --attach / --extract-attachments
Compress content streamspypdfpdf_split.py f.pdf --pages 1-N --compress

The working procedure

The source lays out nine steps. They are worth following in order, because several of them exist specifically to stop you from reporting a wrong result.

1. Inspect first

Run pdf_read.py file.pdf --meta. Check encrypted, and if it is true, decrypt first with pdf_secure.py --decrypt. Check likely_scanned_pages. If pages are image-only, export them with pdf_page_image.py --pages --dpi 300 --out-dir imgs/ and hand the PNGs to the references/ocr-extraction.md skill. Do not report empty text as "no content".

2. Create

Write a JSON spec with write_file. The elements are heading, paragraph, table, image, and pagebreak, with optional title and author metadata, and page numbers are added automatically. Then run pdf_create.py. If layout matters, verify visually with vision_analyze on a rendered page image.

3. Extract

--text gives a JSON list of per-page strings. --tables gives row arrays per page and can also emit CSV files. Read results with read_file. Never eyeball a binary PDF directly.

4. Manipulate

pdf_merge.py concatenates files and can add one bookmark per source file. pdf_split.py handles page ranges (1-based, for example 1-3,5,9-), rotation in 90° steps, and --compress. To watermark, prepare a single-page stamp PDF, for instance with pdf_create.py, and overlay it with pdf_watermark.py. For one-liner stamps such as "sign here", a diagonal DRAFT, or corner labels, use pdf_stamp.py with text or an image at explicit coordinates.

5. Build forms

Write one form-spec JSON with fields carrying label_box and entry_box in PDF points, as described in references/forms.md. Lint it with pdf_form_layout.py and fix every reported problem. Optionally review the --render-overlay PNG with vision_analyze. Then build with pdf_make_form.py and confirm with pdf_read.py --fields.

6. Fill forms

List fields with --fields to learn the exact names and types. Write a UTF-8 JSON of {"FieldName": "value"} with write_file. Checkboxes accept true or false, and radio or choice values must match the field's export options. Then run pdf_fill_form.py. Re-read with --fields to confirm the values landed.

7. Metadata and attachments

pdf_meta.py --set-meta writes Title, Author, Subject, and Keywords into the DocInfo dictionary. --clear-meta drops them. --attach, --list-attachments, and --extract-attachments round-trip embedded files.

8. Secure

Encrypt with distinct user and owner passwords and AES-256. To remove a password you know, --decrypt writes an unencrypted copy.

9. Verify

Run the checks in the verification section before reporting success.

When not to use it

Two exclusions are explicit. This skill is not for scanned or image-only PDFs, which belong in references/ocr-extraction.md. It is also not for pixel-perfect HTML-to-PDF rendering, where a headless browser is the right tool.

PDF/A is out of scope as well. pypdf and reportlab cannot produce or validate conformant PDF/A. If archival conformance is required, run Ghostscript through the terminal tool, for example gs -dPDFA=2 -dPDFACompatibilityPolicy=1 -sColorConversionStrategy=UseDeviceIndependentColor -sDEVICE=pdfwrite -o out.pdf in.pdf with a suitable ICC profile, and validate with veraPDF. Both are external installs, and the result still needs validation rather than assumption.

Limits and gotchas

The pitfalls below are the ones that most often produce a confidently wrong answer.

  • Scanned PDFs. An empty extract_text() combined with page images means there is no text layer. Route to references/ocr-extraction.md and do not fabricate text.
  • Flattening limits. pdf_fill_form.py --flatten uses pypdf's flatten support, which converts widget appearances into page content. It is reliable for plain text fields and checkboxes but can drop or misrender exotic widgets such as rich text, custom appearance streams, and some radio groups. Verify the flattened output visually with vision_analyze. For bulletproof flattening, use an external renderer such as Ghostscript or pdftoppm plus reassembly as a fallback.
  • NeedAppearances. After filling, viewers only render values if appearance streams exist. The fill script sets the AcroForm NeedAppearances flag so conforming viewers regenerate them, but some minimal viewers ignore it. Flatten if display fidelity matters.
  • Non-Latin form values. Values are stored correctly as UTF-16, but the field's default font may lack the glyphs, so a viewer can show blanks even though the data round-trips. Verify with --fields, not just visually.
  • Compression expectations. --compress only deflates content streams. Typical savings are 0 to 20 percent, and it does nothing for PDFs dominated by images or already-compressed streams. It is not a substitute for image downsampling, which is Ghostscript territory.
  • Permission flags do not enforce. Owner-password permission bits such as no-print and no-copy are polite requests that viewers may honour. Any library, including pypdf, can read and strip them. Only the user password actually gates content through encryption. Never present permission flags as security.
  • Table extraction is heuristic. pdfplumber detects tables from ruling lines and word alignment, so borderless or merged-cell tables may need table_settings tuning or manual cleanup.
  • Page indexing. The helper CLIs take 1-based pages, while pypdf APIs are 0-based. The scripts convert, so do not double-convert.
  • Rotated stamp text extraction. pdfplumber's line grouping scrambles rotated glyphs, so a 45° "DRAFT" extracts as stray letters. Verify rotated stamps with pypdf's extract_text() or a rendered image instead.
  • Radio groups. reportlab needs at least two radio() widgets per group, fills need the slashed export value such as "/red", and flatten fidelity is worst for radios. See references/forms.md.
  • Metadata scope. pdf_meta.py writes the classic DocInfo dictionary only. Embedded XMP metadata, if any, is left untouched and may show different values in some viewers.
  • Rotation and encryption ordering. Rotation must be a multiple of 90, and encrypted inputs must be decrypted before any other operation.

Verification

The source gives a specific check for each kind of operation. Run the matching one before you claim success.

  • After create, merge, or split: pdf_read.py out.pdf --meta, confirming page_count and per-page rotation when you rotated.
  • After extraction: check the JSON is non-empty and spot-check a known string or cell.
  • Form design loop: pdf_form_layout.py spec.json must exit 0. Then run --render-overlay boxes.png --pdf form.pdf and review the PNG with vision_analyze, asking about overlaps, misalignment, and labels detached from their fields. Red marks entry boxes with field names, blue marks label boxes. Iterate spec, lint, overlay until clean.
  • After building a form: pdf_read.py form.pdf --fields lists every spec field with the right type and options.
  • After form fill: pdf_read.py filled.pdf --fields and compare values for exact match, including non-ASCII.
  • After stamping: re-extract text, using pypdf for rotated stamps, or render the page with pdf_page_image.py and inspect with vision_analyze.
  • After metadata or attachment edits: pdf_read.py --meta or pdf_meta.py --list-attachments, and re-extract an attachment to byte-compare.
  • After encrypt: --meta shows "encrypted": true and opening without a password fails. After decrypt, text extraction matches the original.
  • For anything visual, including watermarks and flattened forms, render and inspect with vision_analyze.

What pairs with it

The skill sits alongside the other document-format skills in the same productivity family: docx, xlsx, and powerpoint. If a workflow starts in a Word document, a spreadsheet, or a slide deck and ends as a PDF, those skills handle the earlier stages and this one takes over at the point where the output needs to be paginated, secured, or archived. The OCR reference is the natural companion when a PDF arrives as a scan rather than as text, and the nano-pdf editing reference covers the case where the change you need is inside existing prose rather than in the structure around it.

Skills the docs pair this with

More Productivity skills