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.

$ pipic ./assets --replace
✓ 38 images compressed — 24.1 MB → 5.8 MB (−76%)
3 already optimized, left unchanged

Source: GitHub · npm

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-time

pipic 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.

$ npm i -g @pipic/cli
$ pipic login
Opening your browser…
✓ Signed in as you@example.com

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.

$ pipic login --no-browser
Open https://pipic.cc/cli/authorize
Enter this code: PXBQ-GTZM
Expires in 10 minutes · waiting…

Quick Start (AI Agent)

Every time

Already 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:

pipic ./assets --json --replace
{"file":"/p/hero.png","status":"ok","before":2411233,"after":486201,"saved":1925032}
{"file":"/p/notes.txt","status":"skipped","before":0,"after":0,"saved":0,"error":"unsupported file type"}
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 succeeded
1Some files failed
2Usage error
3Sign-in required
4Quota exhausted

This 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 token

Request

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.
curl
$ curl -X POST https://pipic.cc/compress/image \
  -H "Authorization: Bearer $PIPIC_TOKEN" \
  -H "Content-Type: image/png" \
  --data-binary @photo.png \
  -o photo-compressed.png

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:

{"success":false,"code":"MONTHLY_QUOTA_EXCEEDED","message":"Monthly compression quota reached, try again next month"}
400INVALID_TYPE — missing or non-image Content-Type; NO_FILE — empty body
401UNAUTHORIZED, TOKEN_EXPIRED or TOKEN_REVOKED — sign in again or create a new token
413FILE_TOO_LARGE — over 8 MB
415UNSUPPORTED_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 up
502UPSTREAM_ERROR — our compression backend rejected this image; retrying the same bytes fails the same way. The attempt is refunded to your quota
503UPSTREAM_ERROR or ALL_KEYS_EXHAUSTED — temporarily out of capacity; safe to retry with backoff. The attempt is refunded to your quota

Good 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