The command line
pagecraft in a shell script or a CI job, with the same engine and no upload.
pagecraft is the engine with a different front door. Same operations, same
options, same guarantee: it opens no network connection, and there is nowhere
for a file to go.
npx pagecraft merge cover.pdf body.pdf --output report.pdf
Or install it, if you are going to use it more than once:
Not on npm yet. Until it is, build it from source:
git clone https://github.com/amanawasthi2025/pagecraft
cd pagecraft
pnpm install && pnpm build
node packages/cli/dist/bin.js --help
The shape of a command
pagecraft <operation> [files…] [options]
pagecraft recipe <recipe.json> [files…] [options]
pagecraft <operation> --help
Every subcommand comes from the registry, and every flag comes from that
operation’s own options schema — so the help below is written by the operations
themselves rather than maintained beside them. What pagecraft --help prints on
this build is what this build can do.
Files in
Name them, match them with a pattern, or point at a directory:
pagecraft rotate report.pdf --angle 90
pagecraft rotate 'scans/*.pdf' --angle 90
pagecraft rotate scans/ --angle 90
Quote the pattern. Otherwise your shell expands it first, which usually works and occasionally produces a command line longer than the kernel will take.
Given no files at all, the file is read from standard input — but tell it what it is, because a pipe carries no name and the name is how the format is known:
cat report.pdf | pagecraft compress --name report.pdf --output -
An operation that takes one file at a time is applied to each in turn, so a pattern matching forty invoices is forty runs and forty files.
Files out
By default, into the working directory, under a name derived from the input:
report.pdf compressed is report-compressed.pdf, so an operation does not
normally land on top of the file it read.
pagecraft compress report.pdf # ./report-compressed.pdf
pagecraft compress report.pdf --output out/ # out/report-compressed.pdf
pagecraft compress report.pdf --output small.pdf # ./small.pdf
pagecraft compress report.pdf --output - # standard output
--output decides which of those by looking at what is already there: a path
that exists is whatever it is, and a path that does not is a filename if it has
an extension and a directory otherwise. - is standard output.
An operation that produces several files needs a directory. Pointed at one
filename or at standard output, it stops and says so — That produced 4 files, and standard output takes one. — rather than picking one or inventing an
archive. (pagecraft recipe is the exception, and writes a zip: see
Recipes.)
A path you name is written to as you named it. There is no prompt and no
.bak: pagecraft compress report.pdf --output report.pdf replaces the file it
just read. That is the behaviour a script wants, and it is the one to remember
before you run something over a directory you care about.
Options are the schema, dashed
A field an operation calls imageQuality is --image-quality. A boolean that
is off is turned on by naming it; a boolean that is already on is turned off
with --no-. A field that takes several values is given them separated by
commas. A list of objects — areas on a page, bookmarks — is JSON, or @ and a file
holding it, because nobody types an area at a shell.
pagecraft compress big.pdf --mode quality --image-quality 60
pagecraft extract-text report.pdf --no-dehyphenate
pagecraft protect payroll.pdf --user-password 'correct horse battery staple' --allow print,accessibility
pagecraft bookmarks report.pdf --entries @outline.json
--help on an operation lists all of it, with defaults, with what each option
is for, and with an honest list of what the operation cannot do:
pagecraft redact --help
The flags the command line owns
| Flag | What it does |
|---|---|
-o, --output <path> |
A directory, a filename, or - for standard output |
--name <filename> |
What to call the file read from standard input |
--open-password <password> |
The password that opens the input files |
-q, --quiet |
No progress and no summary |
-h, --help |
What this command takes |
-v, --version |
Which Pagecraft this is |
An operation’s own names win. unlock declares a password option, and no
global flag may take that spelling from it — a global that quietly shadowed a
field would make an operation unusable from a terminal with nothing the user
could type to get it back.
Passwords
Two different things, and it matters which you mean:
pagecraft merge locked.pdf other.pdf --open-password hunter2 # to read the input
pagecraft unlock locked.pdf --password hunter2 # to take it off
Either way, the password is an argument, and on a shared machine an argument is
visible in the process list for as long as the command runs. There is no
environment variable and no prompt: that is a real limitation rather than an
oversight, and the honest advice is not to type a password on a machine where
somebody else can read ps.
Exit codes, for a script that has to branch
| Code | What it means |
|---|---|
0 |
It worked |
1 |
Something failed while running: a file would not open, a step could not finish |
2 |
The command was wrong: an unknown operation, a bad option, a file that is not there |
130 |
Interrupted |
Progress and the summary of what was written go to standard error, so standard output is only ever the file you asked for. That is what makes this safe:
pagecraft compress report.pdf --output - > small.pdf
In CI
There is nothing to configure, no key to set and no service to reach. A job that has Node has everything:
- run: npx pagecraft recipe .github/tidy.json 'docs/*.pdf' --output out/
Because it makes no network calls, it also works on a runner with egress blocked — which is the point, if the documents are the reason egress is blocked.
What it cannot do
- OCR needs a recognizer. The command line will tell you so rather than quietly producing a document with no text in it.
- Rasterizing needs a canvas.
@napi-rs/canvasis an optional dependency and is installed for you on the common platforms; on one where it is not, the operations that draw pages say what is missing. - A 200MB document needs the memory a 200MB document needs. The engine streams what it can, and there is a size past which the honest answer is a bigger machine.
Where to go next
- Recipes — several operations in one pass, over many files.
- Getting started — the same engine from code.
- The reference — every operation, with the command line for its example.