11. CLI commands
Ten commands: four that work on PDFs, four on scripts and two for distribution.
| Command | What it does |
|---|---|
run |
Validates a PDF with a script |
compare |
Compares two versions of a PDF |
watch |
Watches a folder and validates what arrives |
fix |
Applies corrections and saves a new PDF |
inspect |
Quick summary of a PDF |
lint |
Analyzes a script without running it |
fmt |
Formats a script |
doc |
Generates documentation from a script |
pack |
Packages profiles and data files |
add |
Installs a package |
Exit codes
Section titled “Exit codes”Every validating command uses the same convention:
| Code | Meaning |
|---|---|
0 |
Everything passed |
1 |
Warnings only |
2 |
Validation errors, or unreadable PDF |
3 |
Syntax error in the script |
In shell scripts:
pdfl run profile.pdfl file.pdf > report.jsoncase $? in 0) echo "approved" ;; 1) echo "approved with warnings" ;; 2) echo "rejected — see report.json" ;; 3) echo "error in the validation script" ;;esacpdfl run
Section titled “pdfl run”Validates a PDF with a script.
pdfl run <script.pdfl> <input.pdf> [options]| Option | Default | What it does |
|---|---|---|
--output json|csv|html|pdf |
json |
Report format |
--output-file <file> |
— | Writes to a file instead of stdout |
--fail-on error|warning |
error |
With warning, warnings also exit 2 |
--verbose |
— | Extra information on stderr |
# JSON report in the terminalpdfl run prepress.pdfl magazine.pdf
# HTML to send back to the clientpdfl run prepress.pdfl magazine.pdf --output html --output-file report.html
# Audit PDF (the pdf format always writes to a file)pdfl run prepress.pdfl magazine.pdf --output pdf --output-file report.pdf
# CSV for a spreadsheetpdfl run prepress.pdfl magazine.pdf --output csv --output-file findings.csv
# Strict: warnings fail toopdfl run prepress.pdfl magazine.pdf --fail-on warningThe JSON report
Section titled “The JSON report”{ "script_name": "prepress.pdfl", "input_file": "magazine.pdf", "profile": "offset-magazine", "status": "FAIL", "total_pages_analyzed": 120, "error_count": 2, "warning_count": 0, "info_count": 0, "diagnostics": [ { "id": "PDFL-001", "severity": "error", "check_name": "Ink coverage", "message": "page 7: 324% ink (limit 300%)", "line": 12 } ]}The same PDF with the same script always produces the same report, byte for byte — so it can be versioned and diffed in CI.
pdfl compare
Section titled “pdfl compare”Compares two versions of a PDF: text, structure and metadata.
pdfl compare <v1.pdf> <v2.pdf> [options]| Option | Default | What it does |
|---|---|---|
--output json|csv|html|pdf |
json |
Format |
--output-file <file> |
— | Writes to a file |
--normalize |
— | Ignores case and spacing |
--ignore-dates |
— | Masks dates before comparing |
--similarity-threshold <0-100> |
100 |
Minimum acceptable similarity |
# Straight comparisonpdfl compare approved_v1.pdf new_v2.pdf
# Tolerating small formatting and date differencespdfl compare approved_v1.pdf new_v2.pdf --normalize --ignore-dates
# Accepts up to 1% difference; below that it becomes an errorpdfl compare v1.pdf v2.pdf --similarity-threshold 99 \ --output html --output-file diff.htmlHow it works
Section titled “How it works”- Pages are aligned by content, not by number: if a page was inserted in the middle, the comparison notices instead of flagging everything after it as different. It scales to documents of over a thousand pages.
- Each aligned page gets a similarity score and a sample of the lines that
changed (
-removed,+added). - Changed metadata becomes a warning; changed text becomes an error when it falls below the threshold and a warning when above it.
- The report carries a
similarityfield with the overall score.
page 4 → 4: similarity 97.8% | -original title | +revised titlepdfl watch
Section titled “pdfl watch”Watches a folder and validates every PDF that arrives or changes.
pdfl watch <folder> --script <script.pdfl> [options]| Option | Default | What it does |
|---|---|---|
--pattern <glob> |
*.pdf |
Which files to process |
--exclude <glob> |
— | Which to skip |
--output-dir <folder> |
next to the PDF | Where to write reports |
--depth <n> |
1 |
Subfolder levels |
--debounce <ms> |
1000 |
Waits for the file to stop being copied |
--report json|csv|html|pdf |
json |
Report format |
--fail-fast |
— | Stops at the first error |
--once |
— | Processes what is already there and exits |
# The print shop's inbox, running continuouslypdfl watch inbox/ --script preflight.pdfl --output-dir reports/ --report html
# Batch mode for CI: process everything and exit with the worst codepdfl watch inbox/ --script preflight.pdfl --onceecho "result: $?"
# Skipping draftspdfl watch inbox/ --script preflight.pdfl \ --pattern "*.pdf" --exclude "*_draft*"Debounce exists because large files arrive in pieces: watch only processes a file once it stops changing, so it never reads half a PDF.
Reports are written as <name>.report.json (or .csv, .html, .pdf).
pdfl fix
Section titled “pdfl fix”Applies fix:: operations and saves a new PDF. Details in
chapter 8.
pdfl fix <input.pdf> <script.pdfl> --output <output.pdf> [options]| Option | What it does |
|---|---|
--output <file> |
Output PDF (required) |
--dry-run |
Lists the operations without saving |
--report json|csv|html|pdf |
Report format |
--report-file <file> |
Writes the report to a file |
# See what would happen, touching nothingpdfl fix original.pdf normalize.pdfl --output out.pdf --dry-run
# Apply for realpdfl fix original.pdf normalize.pdfl --output fixed.pdfpdfl inspect
Section titled “pdfl inspect”Quick summary of a PDF, no script needed.
pdfl inspect <file.pdf>File: magazine.pdfSize: 26 KB (27284713 bytes)SHA-256: af1029842e5bfeae338ead82fb449ef851be742b1d63117c12596e3ea123a616
Pages: 120Page size: 496 x 709 ptBoxes: MediaBox, TrimBox, BleedBox
Metadata: Title: Example Magazine Creator: Adobe InDesign 19.3
Fonts: 26 ABCDEF+Helvetica — embedded Arial — NOT embeddedImages: 81 (minimum DPI 136, spaces: DeviceCMYK, Indexed)Max. estimated TAC: 300% (RGB render approximation)
Warnings: ! there are non-embedded fonts ! 3 image(s) below 300 DPIThis is the first command to run when a new file lands: within seconds you know whether it is worth opening.
pdfl lint
Section titled “pdfl lint”Analyzes a script without running it, reporting quality issues.
pdfl lint <script.pdfl>It detects:
- variables, block parameters and functions that are declared and never used
(prefix with
_to silence:_page) - duplicate or empty checks
- unknown namespaces (
text::,struct::,visual::,prepress::,codes::,fix::,data::) assert/requireoutside any check- use of
fix::(which only runs underpdfl fix)
$ pdfl lint profile.pdflprofile.pdfl: warning: variable 'LIMIT' declared and never usedprofile.pdfl: warning: check "Fonts" declared 2 timesExits with 1 when there are warnings — usable in CI.
pdfl fmt
Section titled “pdfl fmt”Formats the script: two-space indentation, consistent spacing, collapsed blank
lines. Comments and units are preserved (3mm stays 3mm).
pdfl fmt <script.pdfl> # formats in placepdfl fmt <script.pdfl> --check # changes nothing; exits 1 if unformatted# In CI, enforcing a team standardfor f in profiles/*.pdfl; do pdfl fmt "$f" --check || exit 1; donepdfl doc
Section titled “pdfl doc”Generates documentation for a script from the code itself.
pdfl doc <script.pdfl> [--output markdown|html]It produces: the profile, a table of constants, functions, imports and — for each
check — its tags and what it validates (the assert messages become the
description).
# Markdown for the repositorypdfl doc prepress.pdfl > docs/prepress-profile.md
# HTML to hand to people who do not read codepdfl doc prepress.pdfl --output html > profile.htmlThis is the artifact that lets a production manager understand what a profile validates without opening the script.
pdfl pack
Section titled “pdfl pack”Packages scripts and data files into a distributable .pdflpkg.
pdfl pack <folder> [--name <name>] [--version <version>] [--output <file>]It includes .pdfl, .csv, .txt, .json and .xlsx files from the folder
(recursively), plus a manifest.json recording the SHA-256 of each file. The
package is deterministic: the same folder produces identical bytes.
pdfl pack profiles/print-shop --name print-profile --version 1.0.0# creates print-profile.pdflpkgpdfl add
Section titled “pdfl add”Installs a local package, verifying the manifest hashes.
pdfl add <package.pdflpkg> [--dir <folder>]pdfl add print-profile.pdflpkg# installs into ./pdfl_profiles/[email protected]/
If any file’s hash differs from the recorded one, installation is refused — a corrupted or tampered package never lands.
A remote repository and digital signatures are not part of this version:
addinstalls from local files.