返回文章列表

给你的编程 Agent 配一个图片压缩器

PiPic 团队
Agents
pipic ./assets --replace --json
{"file":"assets/hero.png","status":"ok",…}
✓ exit 0

让 Claude Code 或者 Codex "把这个文件夹里的图片压一下",然后看会发生什么。它会提议装 sharp,写一个用完就扔的 Node 脚本,对质量参数连蒙带猜,最后交给你一个文件夹:有些文 件变小了,有一个变大了,而没人说得清是哪一个。

问题不出在 Agent 身上。图片压缩正是那种看起来不值一提、实际上并不简单的能力:格式各异 的编码器、质量启发式,以及一个只有量过才能判断好坏的结果。这是工具该干的活,不是 Agent 每次现场即兴写一遍的脚本。

让它成立的那道分工

有一步 Agent 是真的做不到的:登录。pipic login 会打开浏览器并等你批准。跑在沙箱里的 Agent 有着不同的 HOME、没有浏览器,也没有办法去点那个按钮。

所以别让它做。按"谁真的做得到"来分工:

你,一次就够。 在你自己的终端里:

npm i -g @pipic/cli
pipic login

凭据落在 ~/.config/pipic/config.json。这台机器上以你的身份运行的每一个工具从此都能用 它——包括你的 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.

这段里的每一行都在防一个具体的故障,其中两行防的是很贵的那种。

没有那句 "do not install it or run pipic login yourself",Agent 还是会去试——而 pipic login 会花最多十分钟等一个它给不出的浏览器批准。在 Codex 或 Claude Code 里, 这表现为一个最终被框架杀掉的工具调用,读起来就像这个工具卡死了。

没有那句关于退出码 1 的说明,一个在百来个文件的运行里看到两个失败的 Agent 会做出最顺理 成章的举动:把整个目录重跑一遍。本地并没有"这张压过了"的检查——每个文件都会被重新上传 一次,而每月额度对它接受的每一次请求都计数。免费档一个月一百次,所以一次盲目重试就能把 额度花光。

其余几条防的是便宜些的错误:不带目标 flag 会拿到用法错误;不说明 skipped 不是失败, 它会把没出问题的地方报成出了问题——被跳过的行同样带一个 error 字符串,解释为什么什么 都没变。

Agent 看到的是什么

一段示意性的会话——形状是真的,数字是编的,你那边会不一样:

> compress the images in ./assets

I'll use the pipic CLI.

  $ pipic ./assets --replace --json

  {"file":"assets/hero.png","status":"ok","before":2411233,"after":486201,"saved":1925032}
  {"file":"assets/team.jpg","status":"ok","before":880640,"after":712704,"saved":167936}
  {"file":"assets/icon.svg","status":"skipped","before":0,"after":0,"saved":0,"error":"unsupported file type"}
  {"file":"assets/logo.webp","status":"skipped","before":18944,"after":18944,"saved":0,"error":"not smaller — original kept"}

Done. Two files compressed, saving about 2.0 MB — hero.png did most of
the work, dropping from 2.4 MB to 486 KB. I left logo.webp alone because
compressing it came back larger, and skipped icon.svg since SVG isn't a
raster format.

值得注意的是最后那段话。Agent 不是在猜——它在读一份契约。saved 是字节数,不是比例。 skipped 不等于 errornot smaller — original kept 精确说明了为什么什么都没变。

即兴写的脚本产不出这个。它们打印的是 Agent 当时随手写下的 console.log,而你拿回的 总结,是 Agent 对它自己输出的印象。

为什么"只在更小的时候才写回"在这里格外重要

已经优化过的图片有时候会压得更大。一个粗率的脚本照样会把它覆盖掉,于是你的仓库每跑 一次就悄悄胖一点。

pipic 只在结果确实更小的时候才把文件写回去。--replace 下原文件原封不动;-o 下原 文件会被复制过去,这样输出目录仍然是完整的一套。两种情况下这个文件都报 skipped 而不是 error——所以读输出的 Agent 不会在什么都没出错的时候告诉你有东西失败了。

那个不是可选项的 flag

--replace-o <dir> 互斥,且必须二选一。pipic ./assets 两个都不带,会在发出任何 一个请求之前就以用法错误退出。

这是刻意的,而且在 Agent 开车时更要紧。一个默认就覆盖的工具,迟早会在某次你没细看的运行 里覆盖掉你本想留着的东西。让目标显式化,意味着 Agent 必须声明它的意图,而你能在它给你看 的那条命令里看见这个意图。

当 Agent 看不到你的凭据时

沙箱化的 Agent、容器、CI——任何 ~/.config 不属于你的地方。去账号页 建一个令牌,在那个环境里以 PIPIC_TOKEN 暴露出来。它的优先级高于凭据文件,所以同一条命令 不用 pipic login 也能工作。

注意 pipic token create故意把你送到网页上去建,而不是在本地铸一个出来。一份被 偷走的 CLI 凭据,不该有能力再铸出新的凭据。

退出码就是接口

对任何自动化场景来说,这是最要紧的一部分:

退出码含义
0全部成功
1部分文件失败
2用法错误
3需要登录
4当月配额用尽

3 意味着去跑 pipic login——那是唯一一件必须你亲自做的事。4 意味着你这个月 CLI 与 API 的额度用完了;不会自动扣任何费用,而在 pipic.cc 上压缩无论如何都是免费且不限量的。

一个基于这些码分支的 Agent,不需要你去解读一大段文字就能做对事。一个靠匹配提示语字符串 的 Agent,会在我们第一次改措辞时就崩掉——这正是为什么退出码、status 的取值和字段名才是 稳定契约,而给人看的 error 文本不是。

拿你自己的图片试试
免费,无需注册,绝不留存。
开始压缩