wizard 精读:把「只有人能做的事」做成一台脚本
wizard 产出的不是一段说明,而是一台机器:一个交互式 bash 脚本,把开通基础设施、配密钥、走第三方后台、做一次性迁移这类手工流程一步步带人走完——开 URL、说明点什么复制什么、把值收进来、写进 .env 或 gh secret、每段确认、显示还剩几段。它的交互层被冻结在 223 行的 template.sh 里(STAGES 标记以上不许手改),agent 只负责 scoping 和写 stage;它也明确知道自己不该被端到端跑一遍,所以验证是静态的。本文走一遍它的四步,讲清它为什么把边界写进 description(agent 自己能做的就别用它)、为什么让模型碰不到密钥、以及「没有回退按钮」为什么是设计而不是缺陷。
本篇属于系列 读懂最优秀的开发技能是怎么写的(Matt Pocock skills 逐技能精读) · 第 24 篇
(本文读的是 mattpocock/skills 快照 5beb35b,2026-10-19。引用原文都能按文中路径回仓库核对。这是「逐个技能精读」系列的第二十四篇,聚焦 wizard 一个技能。)
它怎么工作:四步,产出一个交互式 bash 脚本
skills/engineering/wizard/SKILL.md 是这套技能里少数几个 model-invoked 的技能之一(没有 disable-model-invocation,Codex 里可以直接 $wizard)。它的定义写得像一段产品说明:
A wizard is a bash script that walks a human, step by step, through a manual procedure that’s tedious to do by hand and tedious to re-explain to an AI every time. It opens each URL, says exactly what to click and copy, captures the values, writes them where they belong (
.env, GitHub secrets), confirms at every stage, and shows how many stages are left.
关键在于 tedious to re-explain to an AI **every time**——它要消灭的不是手工步骤本身,是每次都要跟 AI 重讲一遍流程这件事。
脚本这边它几乎不让你写:仓库里有一份 223 行的 template.sh,逐段进度、确认闸、跨平台开 URL(含 WSL)、隐藏输入(ask_secret)、幂等的 .env 写入(write_env)、gh secret/gh variable 写入、收尾总结,全都已经解决。所以技能把职责切得很窄:
The delightful UX is already solved by template.sh … Your job is only to scope the procedure and author its stages. The library above the
STAGESmarker is identical in every wizard; that consistency is the point: never hand-edit it.
四步:
- Scope the procedure。 先读仓库,别冷启动问人:
.env、.env.example、README、docker-compose*、框架配置,以及.github/workflows/*——那里每一个secrets.*或vars.*引用,都是一个必须由 wizard 产出的值。迁移或切换类的,则要看当前状态、目标状态,以及两者之间那些不可逆的动作。然后把有序的 stage 列表和每个 stage 产出的值给人看,请人确认(可以加、删、换顺序)。完成条件是:每个 stage 有序命名,且对每个要捕获的值都知道三件事——(a) 人从哪里拿到,(b) 写到哪里(.env、GitHub secret、两者、还是不写),(c) 是不是 secret。 - Map each stage’s journey。 给每个 stage 写出精确路径,比如
Dashboard → Developers → API keys → Reveal test key → copy。不知道当前 UI 或确切命令就说明不知道、问人或查文档:never invent steps that may not exist。 - Author the wizard。 复制
template.sh,用stage逐个替换示例,按依赖顺序排;用库里的 helper;设好TOTAL_STAGES。然后守住 template 立下的底线:先开 URL 再要值、任何 secret 都用ask_secret、每个持久化的值都write_env、只给 CI 真正需要的值set_secret、不可逆动作前先confirm;每个stage会清屏,所以一段只做一件聚焦的事,别让人还需要的东西滚出屏幕;marker 以上的库不要动。 - Verify and hand off。
bash -n <script>,有shellcheck就跑;chmod +x;然后是一句很关键的克制——Don't run it end-to-end yourself: it opens browsers and blocks on human input. Trace it statically instead:静态走查每个值都被捕获、都落在第 1 步说的地方,每个set_secret名字都与 CI 里的secrets.*完全一致。最后告诉用户怎么跑;如果这是可复用的搭建路径,就 commit 并挂到 README。
默认它是一次性的:A wizard is ephemeral by default: built for one run, saved to a scratch or scripts/ path, deleted when the job's done. Commit it only when the user wants a repeatable setup path that should live in the repo.
它和「一般技能」的差别:产出物是一台机器,不是一段说明
一、它给的不是文档,是一个可执行的有状态程序。 README 讲流程是从零开始的,也读不出你缺什么;wizard 先读仓库、只问缺的值、记住已经写下的值、每段清屏、每步等确认,收尾列出写成功什么、还剩什么得手工完成。
二、它刻意把人和模型隔开。 agent 只写脚本,不跑脚本;你在自己的终端里跑,密钥经隐藏输入直接进 .env 或 gh secret。文档对这条边界说得很谨慎:这只对运行时捕获的值成立——如果你在 scoping 时把 key 粘进聊天,那它就已经进了上下文。
三、结构是「冻结的库加唯一的舞台」。 marker 以上跨所有 wizard 逐字相同,一致性就是它的卖点;marker 以下才是你要写的东西。这跟 grill-me 只有一行正文、pr 的视图菜单是同一类设计:把每次都要重写的部分压到最小。
四、它知道自己不能自测。 验证是静态的(bash -n、shellcheck 加对照走查),理由直白:它会开浏览器、会阻塞在人的输入上。它甚至用一句「不要端到端跑」把「agent 总想跑一遍」这个反射收住了。
五、边界写在 description 里。 一句 Don't invoke this for steps the agent can perform itself. 直接写在触发描述末尾——它把「什么时候不该用它」当成第一等的内容。文档里还记着它修掉的一个具体失败:以前 agent 撞上凭证墙,会往聊天里甩六个编号步骤让你手敲;现在它写一个 wizard。它也从 in-progress/ 毕业进了 engineering/,行为没变,只是随插件发布。
它有意识摒弃了什么
一、摒弃了「agent 能做的也塞进来」。 description 末尾那句禁令就是这一条:这是给「只有人能做的事」用的。
二、摒弃了自测。 Don't run it end-to-end yourself——不是懒,是因为它本质上无法在无人环境里跑完。
三、摒弃了每个 wizard 各自发明交互。 库冻结、never hand-edit、that consistency is the point。
四、摒弃了猜 UI。 never invent steps that may not exist——不知道就说不知道。
五、摒弃了回退按钮。 有意的:stages 只向前跑,输错了就是 Ctrl-C 重来。之所以敢这么做,是因为它把重来做得便宜——已经写进 .env 的值会作为默认值回填,你对的部分按回车过去,只重打错的那一个。文档里说这是发布周就有人提、至今仍开着的需求,但配合幂等写入,重跑的成本确实低到可以接受。
六、摒弃了「值都往 secret 里塞」。 set_secret 只放 CI 真正需要的值:少一处秘密就少一处要维护、要担心泄露的东西。
七、摒弃了把一次性脚本默认写进仓库。 默认临时路径、默认用完就删。
八、摒弃了「端到端跑一遍」这种伪安全感。 它把力气花在静态的一致性检查上:每个捕获的值都有去处、每个 set_secret 名字都与 secrets.* 逐字一致。
原文证据
职责切分:
The delightful UX is already solved by template.sh … Your job is only to scope the procedure and author its stages. The library above the
STAGESmarker is identical in every wizard; that consistency is the point: never hand-edit it.
先读仓库、只问缺的:
Work out every manual step the human must take and every value that gets captured along the way. Read the repo first, don’t ask cold: …
.github/workflows/*(everysecrets.*/vars.*reference is a value the wizard must produce).
不猜 UI:
Where you don’t actually know the current UI or the exact command, say so and ask the user or check the docs: never invent steps that may not exist.
底线与验证:
Hold the bar the template sets: open the URL before asking for its value, use
ask_secretfor anything secret,write_envevery persisted value,set_secretonly the values CI actually needs, andconfirmbefore any irreversible action.
Don’t run it end-to-end yourself: it opens browsers and blocks on human input. Trace it statically instead …
可迁移结论
- 把「每次都要重讲一遍的流程」当成一类独立的产物。 判断标准很实用:只有人能做的部分有多长、要重复几次、涉及多少个密钥或后台操作。
- 把交互层冻结成库,只让人写舞台。 库跨所有实例逐字相同(
never hand-edit),每次只写那几段stage。一致性不是风格问题,是「能不能让人一眼看懂」的问题。 - 先读仓库再问人。 从
.env.example、docker-compose、CI workflow 里的secrets.*/vars.*反推出「必须产出的值」,只问缺的那些。 - 每个捕获的值都要有三个答案。 从哪拿、写到哪、是不是 secret——把这三个写清楚,脚本就不会缺段。这也是「完成」的定义。
- 给每段设一条底线。 先开 URL 再要值(别让人粘贴他还没去取的东西)、secret 隐藏输入、只给 CI 需要的值。
- 承认 agent 跑不了它,改用静态走查。 别假装端到端验证过;用
bash -n、shellcheck,再加上「每个值都有去处、每个set_secret都与 CI 里的名字逐字一致」。 - 把重来做成便宜的。 不做回退按钮,改用幂等写入加已存值回填默认,Ctrl-C 重跑的代价就低到可以接受。
- 一次性产物就别进仓库。 除非它是下一个人也要走一遍的路径——那时才 commit,并挂到 README 上。
wizard 是这套技能里唯一一个产出可执行程序的技能。它和 setup-matt-pocock-skills 是邻居:一个把仓库配成这套技能能用的状态,一个给「其他所有事情」生成一条搭建路径。它和 implement 也搭:一次构建落到需要凭证或手工切换时,wizard 补齐只有人能走的那几步。
讨论
用 GitHub 账号留言;评论保存在公开仓库chengshu-blog-discussions的 Discussions 里。也可通过 RSS 订阅后续文章。