一条命令压缩图片 —— 在终端、构建脚本,或你的 Agent 里。

PiPic CLI 把网页同款的压缩能力带进脚本、CI 流水线和 AI 编程 Agent。需要 Node 20 及以上版本,不做任何遥测,图片压缩完成的那一刻就会被删除。

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

源码: GitHub · npm

为什么用 pipic CLI?

  • 为 Agent 而生:NDJSON 输出与稳定的退出码,AI Agent 可以直接解析、按分支处理,不用去猜一段说明文字。
  • 一条命令压缩整个文件夹 —— 就地覆写或写到新目录都行,本地批量处理和构建产物都适用。
  • 登录一次,之后无需人工干预:脚本、CI(通过 PIPIC_TOKEN)与 Agent 都能直接使用,不用再批准一次。
  • 绝不做遥测。图片压缩完成的那一刻就会从服务器删除。

环境要求

需要 Node 20 及以上版本,命令名是 pipic。

快速开始(人工)

一次性

pipic login 会打开你的浏览器去批准这次请求。批准之后,凭证会保存在这台机器上 —— 之后任何以你身份运行的工具,包括 AI 编程 Agent,都能直接使用 pipic,无需再次登录。

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

没有浏览器?SSH、容器、CI

pipic login --no-browser —— 或者 CLI 检测到自己处在无图形界面的环境(SSH、CI、容器)时 —— 会退回设备码登录,你可以在任意一台有浏览器的设备上批准。

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

快速开始(AI Agent)

每次

这台机器上已经装好并登录过了?把下面这段原样粘贴给你的编程 Agent:

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 跑在容器或沙箱里、读不到你的 ~/.config?去账号页建一个 token,把它设成那个环境里的 PIPIC_TOKEN 即可。

命令

pipic <路径…>压缩文件或目录。这是默认命令。
pipic login [--no-browser]通过浏览器或设备码授权这台机器。
pipic logout登出并撤销这台机器的凭证。
pipic whoami显示当前登录的账号与档位。
pipic quota显示本月还剩多少配额。
pipic token list列出你的 token,附前缀与最近使用时间。
pipic token create打印一个网页链接去创建 —— 出于安全考虑,创建 token 只能在网页上完成,CLI 本身不会创建。
pipic token revoke <id>撤销一枚你自己名下的 token,通常一分钟内生效。

login、logout、whoami、quota、token 会被优先当作子命令匹配,而不是路径。如果你恰好有一个同名文件夹想压缩,写成 ./login 或 login/ —— 只要带路径分隔符,就会走压缩命令而不是子命令。

压缩选项

--replace就地覆写原图 —— 原子替换,保留权限,解引用软链接。
-o, --out <目录>把结果写到这个目录,而不是就地覆写。
--jsonNDJSON 输出,一行一个文件 —— 供脚本与 Agent 解析。
--concurrency <n>并发上传数,默认 4。

--replace 与 -o 必须二选一 —— 两者互斥,且都不是默认行为;不给任何一个,pipic 会以用法错误(退出码 2)退出。

你可能想知道

  • 只有压缩结果确实变小才会写回 —— 已经优化过的图片会被跳过,并给出原因。-o 模式下这张图仍会被原样复制到输出目录,所以输出目录永远是一份完整集合。
  • --replace 会先写临时文件再原子改名,Ctrl-C 中断也不会留下半个文件。
  • 超过 8 MB 的文件会被跳过并给出明确原因,不会被静默丢弃。
  • 支持 JPG、PNG、WebP 和 AVIF,不会发送任何遥测数据。
  • 在 -o 模式下,两个来自不同子目录、文件名相同的文件会撞车:pipic 会把两张都报错,而不是替你猜该保留哪一张。

JSON 输出契约

--json 会按文件逐行打印一个 JSON 对象,固定六个字段:

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"}
file你传入的路径,原样返回。
status"ok"、"skipped" 或 "error" —— 不会有其他取值。
before原始体积,单位字节。
after压缩后体积,单位字节 —— skipped/error 行为 0。
saved省下的字节数(before 减 after),不是百分比。
error只在 skipped 与 error 行出现 —— 一句人能看懂的原因,不是错误码。

退出码

0全部成功
1部分失败
2用法错误
3需要登录
4配额用尽

这是一份稳定的契约:脚本与 Agent 应该按 status 与退出码分支处理 —— 绝不要匹配 error 的文本内容。

HTTP API

不能跑 Node?直接用 HTTP 调用同一个压缩引擎——每次请求一张图,不需要 SDK,也不用 multipart 表单。

POST https://pipic.cc/compress/image

获取 token

API 与 CLI 共用同一套 Personal Access Token。用 GitHub 或 Google 登录后,在账号页创建一枚——明文 token 只显示这一次,请妥善保存。

创建 token

请求

AuthorizationBearer <token> —— 每次请求都必须带上。
Content-Typeimage/png、image/jpeg、image/webp 或 image/avif —— 必填,且必须与实际发送的字节匹配。
X-Original-Name可选。先做 URL 编码——响应里只会原样带回 encodeURIComponent 允许的字符集。
Body裸图片二进制,不是 multipart/form-data。一次请求一张图,最大 8MB。
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

响应

200 OK 的响应体就是压缩后的图片——同格式进,同格式出。Content-Type 反映压缩后的格式;能拿到时也会带上 Content-Length 与 X-Original-Name。

错误

非 200 的响应都会带这样一个 JSON 错误信封。请按 code 字段分支处理,不要匹配 message 文本——下面是一个真实的例子:

{"success":false,"code":"MONTHLY_QUOTA_EXCEEDED","message":"Monthly compression quota reached, try again next month"}
400INVALID_TYPE —— Content-Type 缺失或不是图片类型;NO_FILE —— 请求体为空
401UNAUTHORIZED、TOKEN_EXPIRED 或 TOKEN_REVOKED —— 重新登录,或新建一枚 token
413FILE_TOO_LARGE —— 超过 8MB
415UNSUPPORTED_TYPE —— 我们不压缩的 image/* 类型(只支持 PNG、JPEG、WebP、AVIF)
429RATE_LIMITED(看 Retry-After 头)或 MONTHLY_QUOTA_EXCEEDED —— 本月额度已用完
502UPSTREAM_ERROR —— 压缩后端拒绝了这张图,原样重试只会得到同样的结果。这次尝试会退回配额
503UPSTREAM_ERROR 或 ALL_KEYS_EXHAUSTED —— 后端暂时没有容量,可退避后重试。这次尝试会退回配额

你可能想知道

  • 一次请求在被我们接受进入压缩流程的那一刻就会计入配额——即使它随后以 400、413 或 415 失败也一样。在那之前就被拒绝的请求不计数:任意 401、429 RATE_LIMITED,以及仅凭声明的 Content-Length 头就被拒的文件。如果失败原因在我们这边(502/503),这次尝试会被自动退回。

想要每个请求头、每个错误码的完整机读清单: /openapi.json

常见问题

Agent 会不会动我没让它动的文件?

不会 —— pipic 要求显式给出 --replace 或 -o <目录>,不给就直接拒绝执行,而且在报这个错误之前不会发起任何请求。

我登录一次,Agent 就能一直用下去吗?

是的。凭证保存在这台机器的 ~/.config/pipic/config.json,任何以你身份运行的工具都能读到它 —— 每个 Agent 都不需要单独再批准一次。

Agent 说它没有登录,该怎么办?

多半是跑在读不到你 ~/.config 的容器或沙箱里。去账号页建一个 token,把它设成那个环境里的 PIPIC_TOKEN 即可。

免费额度用完会怎样?

pipic 会以退出码 4 退出,绝不会自动扣费。Free 每月 100 张,Pro 提升到每月 5,000 张。

我的图片会被保存吗?

不会 —— 压缩完成的那一刻就会从服务器删除,不做任何长期留存。

隐私

绝不做遥测。图片压缩完成的那一刻就会从服务器删除 —— 不做任何长期留存。

阅读完整隐私政策