给你的编程 Agent 配一个图片压缩器
让 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 不等于 error。not 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 文本不是。