All posts

Squoosh CLI, imagemin, sharp, or PiPic: the 2026 image-CLI check-up

PiPic team
Agents
pipic ./img -o ./out --json
{"file":"…/img/broken.jpg","status":"error",…}
✗ exit 1

Ask around for a way to compress images from the terminal and you get the same few answers every time: Squoosh's CLI, imagemin, sharp, or a single-format encoder like pngquant. Most of that advice dates from 2023 or earlier. So we spent an afternoon checking it — 7 August 2026, one ordinary laptop, macOS on Apple Silicon, Node 22.14, the current LTS. We installed everything fresh and ran it all over one small test folder: a PNG, a JPEG, a JPEG that had already been optimised once, and twelve bytes of plain text renamed to broken.jpg. Real image folders always have something weird in them.

Before running anything, the npm registry's own timestamps already tell you a lot:

latest release per package · npm registry · checked 2026-08-07

@squoosh/cli 0.7.3 Jan 2023 "no longer maintained" imagemin 9.0.1 Mar 2025 imagemin-cli 8.0.0 May 2024 imagemin-mozjpeg 10.0.0 Dec 2021 imagemin-webp 8.0.0 Jan 2023 sharp 0.35.3 Jul 2026 sharp-cli 5.2.0 Jun 2025

Squoosh CLI crashed before touching the first image

We started with @squoosh/cli, because it's the one people recommend most. It's the command-line cousin of Squoosh, the web app from Google — the web app is great and still maintained. The CLI is not. Its last release was January 2023, and its readme says so itself: "Project no longer maintained."

The install works. The command starts. Then you ask it to compress something, and it dies on the spot — its WebAssembly loader tries to fetch() a local file path, and today's Node refuses:

npx @squoosh/cli --oxipng '{}' -d out photo.png · Node 22.14

code: 'ERR_INVALID_URL', input: '…/@squoosh/lib/build/imagequant_node-a4aafbae.wasm' — exit 1

We tried the JPEG path too. Same crash, same spot. Install this tool today and it cannot compress a single image. Tutorials still recommend it, and models trained on those tutorials recommend it too. Nobody has corrected the advice, because nobody is home.

imagemin did the job, then lied about the text file

imagemin made a better first impression. The core package had a release in March 2025, and on our real images it simply worked — three files in, "3 images minified", done.

Two things bothered us along the way. First, the install. Four packages, five deprecation warnings, and npm audit found 37 vulnerabilities in the brand-new project — 1 of them critical. The reason isn't hard to find: the actual compression lives in per-format plugins, and the plugins are old. The JPEG one last shipped in December 2021, WebP in January 2023, and AVIF has no first-party plugin at all.

Second, the fake file:

npx imagemin broken.jpg --plugin=mozjpeg · 12 bytes of text
  • Minifying images 1 image minified — exit 0

It copied the twelve bytes of text into the output folder, untouched, and called it a minified image. Exit 0. You'd catch that with your eyes. A script wouldn't. An agent reading "1 image minified, exit 0" reports success and moves on to the next task.

Nothing is wrong with sharp — that's the trap

sharp itself is in great shape. It shipped a release in July 2026, it has carried image work on npm for a decade, and on maintenance it beats everything else here without trying.

But sharp doesn't compress folders. It's a library; the script is on you. So we wrote the script people actually write — a few lines, default settings — and ran it:

node compress.mjs · sharp, default .jpeg() quality

photo.jpg 88556 -> 38879 smaller photo-optimized.jpg 26659 -> 32050 LARGER — exit 0

The fresh JPEG compressed nicely. The already-optimised one came back 20% larger, and our script saved it anyway, because we never told it not to. That's not a bug in sharp. Quality numbers, which files to skip, what to do when the output grows, how to report results — sharp leaves all of it to whoever writes the script, and whoever writes the script is usually you at six in the evening, or your agent. We described that improvised script in Give your coding agent an image compressor; this time we watched it fail in person.

One number goes against us

Worth saying out loud: at default settings, imagemin's mozjpeg produced the smallest JPEG of the day — 26,659 bytes to pipic's 31,774. Default quality levels differ from tool to tool, so byte counts alone settle nothing; you'd need a quality comparison, and we didn't do one. This test is about behaviour, not compression ratios. But we're not going to hide the number that doesn't flatter us.

While we're being fair: the classic single-format encoders — pngquant, oxipng, cjpeg, cwebp, avifenc — are solid, and they're what everything above wraps anyway. If you live in one format, use them happily (for AVIF, the AVIF page covers when it's worth switching). Four formats, though, means four installs, four flag dialects, and a wrapper script that's now yours to maintain.

The same folder through pipic

We gave the pipic CLI the identical directory, corrupt file included:

$ pipic ./img -o ./out --json          # paths shortened
{"file":"…/img/broken.jpg","status":"error","before":12,"after":0,"saved":0,"error":"UPSTREAM_ERROR: Compression service error"}
{"file":"…/img/icon.png","status":"ok","before":13897,"after":7449,"saved":6448}
{"file":"…/img/photo-optimized.jpg","status":"skipped","before":26659,"after":26659,"saved":0,"error":"not smaller — copied as-is"}
{"file":"…/img/photo.jpg","status":"ok","before":88556,"after":31774,"saved":56782}
{"file":"…/img/photo.png","status":"ok","before":302850,"after":104288,"saved":198562}
— exit 1

Check it against the failures above, one by one. The text file comes back as an error with a reason, and the whole run exits 1, so a script knows something went wrong. The already-optimised JPEG is skipped and kept as-is, reason included, instead of quietly growing 20%. And every image gets its own line of JSON with real before/after byte counts, which a script can branch on directly.

The trade-off is real, so here it is plainly. Every other tool in this post runs on your machine. pipic is a service: files upload, get compressed in memory, and are deleted right away — never stored — but they do leave your computer. The CLI needs an account, with 100 images a month free and 5,000 for $9 on Pro. The web compressor stays free, unlimited and account-free regardless. And if your rule is "images never leave this machine", use the local tools — that rule outranks everything else in this post.

What the account buys you is the other side of each finding above: no wasm loader left to rot, no plugin kit to assemble, no quality decisions to improvise, and output your automation can actually rely on.

We also ran a separate benchmark with 12 licensed inputs across PNG, JPEG, WebP, and AVIF. Its method, all 72 raw runs, medians, retained failure, and visual checks are public. That dataset compares PiPic Agent CLI 1.0.6 with sharp 0.35.3. The folder test above examines operational behavior and makes no compression-ratio ranking.

Run the folder test yourself

The whole method fits in an afternoon, and you don't have to trust our numbers. First check whether anyone's home:

npm view @squoosh/cli time.modified

Then make a folder with one already-optimised image and one corrupt file in it, and run your candidate tool over it. Clean images compress fine everywhere; they won't tell you a thing. The weird files are where a tool shows you its judgement — and every folder eventually has weird files.

Try it on your own images
Free, no sign-up, never stored.
Compress images