Compress images with one command — in your terminal, your build, or your agent.
The PiPic CLI brings the same compression as the website into scripts, CI pipelines and AI coding agents. Node 20 or newer. No telemetry. Images are deleted the moment they’re compressed.
Why pipic CLI?
- Agent-native: NDJSON output and stable exit codes an AI agent can parse and branch on, not prose it has to guess at.
- One command compresses a whole folder — in place or into a fresh directory — for local batches and build output alike.
- Sign in once and it runs unattended after that: scripts, CI (via PIPIC_TOKEN) and agents all use it without approving anything again.
- No telemetry, ever. Images are deleted from the server the instant compression finishes.
Requirements
Node 20 or newer. The command is called pipic.
Quick Start (Human)
One-timepipic login opens your browser to approve the request. Once approved, credentials are saved on this machine — any tool that runs as you, including an AI coding agent, can use pipic without signing in again.
No browser? SSH, containers, CI
pipic login --no-browser — or an environment the CLI detects as headless (SSH, CI, a container) — falls back to a device code you approve from any other device with a browser.
Quick Start (AI Agent)
Every timeAlready installed and signed in on this machine? Paste this to your coding agent as-is:
Use the `pipic` CLI to compress the images in ./assets.
It's already installed and signed in — do not install it or run
`pipic login` yourself.
pipic ./assets --replace --json
It prints one JSON object per file:
{"file":"a.png","status":"ok","before":102400,"after":41000,"saved":61400}
status is "ok", "skipped" or "error". "skipped" is not a failure — it
carries an `error` string explaining why (e.g. already small enough).
Exit 0 = all good, 1 = some files failed, 3 = not signed in,
4 = monthly quota exhausted.
Don't retry on exit 2 (usage error), 3 or 4 — on 3, stop and tell the
user to sign in. On exit 1, re-run only the paths whose rows had status
"error": a full re-run re-uploads every file and spends quota again.Agent running in a container or sandbox that can’t see your ~/.config? Create a token on your account page and set PIPIC_TOKEN in that environment instead.
Commands
pipic <paths…>Compress files or a directory. This is the default command.pipic login [--no-browser]Authorize this machine through the browser, or with a device code.pipic logoutSign out and revoke this machine’s token.pipic whoamiShow the signed-in account and its plan.pipic quotaShow how much of this month’s quota is left.pipic token listList your tokens with their prefix and last use.pipic token createPrints a link to create one on the website — for security, creating a token always happens there, never in the CLI.pipic token revoke <id>Revoke a token you own, usually effective within a minute.login, logout, whoami, quota and token are matched as subcommands before pipic treats them as paths. To compress a folder that happens to share one of those names, write ./login or login/ — anything with a path separator goes to the compress command instead.
Compress options
--replaceOverwrite the originals in place — atomic replace, permissions kept, symlinks resolved.-o, --out <dir>Write results to this directory instead.--jsonNDJSON output, one line per file — for scripts and agents.--concurrency <n>Parallel uploads. Default 4.You must give exactly one of --replace or -o — they’re mutually exclusive, and neither is a default. Without one, pipic exits with a usage error (code 2).
Good to know
- Files are only written back when the result is actually smaller — already-optimized images are skipped, with the reason reported. Under -o the original is copied across anyway, so the output directory is always a complete set.
- --replace writes to a temp file and renames it, so a Ctrl-C never leaves a half-written file behind.
- Files over 8 MB are skipped with a clear reason, never silently dropped.
- JPG, PNG, WebP and AVIF are supported. No telemetry is ever sent.
- With -o, two files that share a name — from different subfolders, say — collide: pipic reports an error for both rather than guessing which one to keep.
JSON Output Contract
--json prints one JSON object per line, one per file, with six fixed fields:
fileThe path you passed in, unchanged.status"ok", "skipped", or "error" — no other values.beforeOriginal size in bytes.afterCompressed size in bytes — 0 on skipped/error rows.savedBytes saved (before minus after) — never a percentage.errorPresent on skipped and error rows only — a human-readable reason, not a code.Exit codes
0All files succeeded1Some files failed2Usage error3Sign-in required4Quota exhaustedThis is a stable contract: branch scripts and agents on status and the exit code — never on the text of error.
HTTP API
No Node? Call the same compression engine directly over HTTP — one image per request, no SDK, no multipart forms.
POST https://pipic.cc/compress/image
Get a token
The API uses the same Personal Access Tokens as the CLI. Sign in with GitHub or Google, then create one from your account page — the plain token is shown once, so save it somewhere safe.
Create a tokenRequest
AuthorizationBearer <token> — required on every request.Content-Typeimage/png, image/jpeg, image/webp or image/avif — required, and must match the bytes you send.X-Original-NameOptional. URL-encode the filename first — only encodeURIComponent-safe characters are echoed back.BodyRaw image bytes, not multipart/form-data. One image per request, up to 8 MB.Response
200 OK returns the compressed image as the response body — same format in, same format out. Content-Type reflects the compressed format; Content-Length and X-Original-Name come back too when available.
Errors
Anything other than 200 responds with a JSON envelope. Branch your code on the code field, not on the message text — here’s a real one:
400INVALID_TYPE — missing or non-image Content-Type; NO_FILE — empty body401UNAUTHORIZED, TOKEN_EXPIRED or TOKEN_REVOKED — sign in again or create a new token413FILE_TOO_LARGE — over 8 MB415UNSUPPORTED_TYPE — an image/* type we don’t compress (only PNG, JPEG, WebP, AVIF)429RATE_LIMITED (see the Retry-After header) or MONTHLY_QUOTA_EXCEEDED — this month’s allowance is used up502UPSTREAM_ERROR — our compression backend rejected this image; retrying the same bytes fails the same way. The attempt is refunded to your quota503UPSTREAM_ERROR or ALL_KEYS_EXHAUSTED — temporarily out of capacity; safe to retry with backoff. The attempt is refunded to your quotaGood to know
- A request counts against your quota the moment we accept it for compression — including ones that go on to fail with 400, 413 or 415. Requests rejected before that point never count: any 401, a 429 RATE_LIMITED, and a file rejected purely on its declared Content-Length header. If we fail on our side (502/503), the attempt is refunded automatically.
Every header and error code, in one machine-readable file: /openapi.json
FAQ
Will an agent touch files I didn’t tell it to?
No — pipic requires an explicit --replace or -o <dir>. Without one it refuses to run, and it never sends a single request before reporting that error.
I signed in once — can an agent keep using pipic forever?
Yes. The credential lives on this machine at ~/.config/pipic/config.json, and any tool running as you can read it — no extra approval needed per agent.
An agent says it’s not signed in — what do I do?
It’s most likely running in a container or sandbox that can’t see your ~/.config. Create a token on your account page and set it as PIPIC_TOKEN in that environment.
What happens when the free quota runs out?
pipic exits with code 4 and nothing is ever charged automatically. Free includes 100 images a month; Pro raises that to 5,000.
Are my images kept anywhere?
No — they’re deleted from the server the moment compression finishes, and nothing is stored long-term.
Privacy
No telemetry, ever. Images are deleted from the server the moment compression finishes — nothing is stored long-term.
Read the full privacy policy