Chengshu@skadai · 2026.10.20
RSS
1,745 字 · 590 词 · 约 7 分钟

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 STAGES marker is identical in every wizard; that consistency is the point: never hand-edit it.

四步:

  1. Scope the procedure。 先读仓库,别冷启动问人:.env、.env.example、README、docker-compose*、框架配置,以及 .github/workflows/*——那里每一个 secrets.* 或 vars.* 引用,都是一个必须由 wizard 产出的值。迁移或切换类的,则要看当前状态、目标状态,以及两者之间那些不可逆的动作。然后把有序的 stage 列表和每个 stage 产出的值给人看,请人确认(可以加、删、换顺序)。完成条件是:每个 stage 有序命名,且对每个要捕获的值都知道三件事——(a) 人从哪里拿到,(b) 写到哪里(.env、GitHub secret、两者、还是不写),(c) 是不是 secret。
  2. Map each stage’s journey。 给每个 stage 写出精确路径,比如 Dashboard → Developers → API keys → Reveal test key → copy。不知道当前 UI 或确切命令就说明不知道、问人或查文档:never invent steps that may not exist。
  3. Author the wizard。 复制 template.sh,用 stage 逐个替换示例,按依赖顺序排;用库里的 helper;设好 TOTAL_STAGES。然后守住 template 立下的底线:先开 URL 再要值、任何 secret 都用 ask_secret、每个持久化的值都 write_env、只给 CI 真正需要的值 set_secret、不可逆动作前先 confirm;每个 stage 会清屏,所以一段只做一件聚焦的事,别让人还需要的东西滚出屏幕;marker 以上的库不要动。
  4. 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 STAGES marker 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/* (every secrets.* / 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_secret for anything secret, write_env every persisted value, set_secret only the values CI actually needs, and confirm before any irreversible action.

Don’t run it end-to-end yourself: it opens browsers and blocks on human input. Trace it statically instead …

可迁移结论

  1. 把「每次都要重讲一遍的流程」当成一类独立的产物。 判断标准很实用:只有人能做的部分有多长、要重复几次、涉及多少个密钥或后台操作。
  2. 把交互层冻结成库,只让人写舞台。 库跨所有实例逐字相同(never hand-edit),每次只写那几段 stage。一致性不是风格问题,是「能不能让人一眼看懂」的问题。
  3. 先读仓库再问人。 从 .env.example、docker-compose、CI workflow 里的 secrets.*/vars.* 反推出「必须产出的值」,只问缺的那些。
  4. 每个捕获的值都要有三个答案。 从哪拿、写到哪、是不是 secret——把这三个写清楚,脚本就不会缺段。这也是「完成」的定义。
  5. 给每段设一条底线。 先开 URL 再要值(别让人粘贴他还没去取的东西)、secret 隐藏输入、只给 CI 需要的值。
  6. 承认 agent 跑不了它,改用静态走查。 别假装端到端验证过;用 bash -n、shellcheck,再加上「每个值都有去处、每个 set_secret 都与 CI 里的名字逐字一致」。
  7. 把重来做成便宜的。 不做回退按钮,改用幂等写入加已存值回填默认,Ctrl-C 重跑的代价就低到可以接受。
  8. 一次性产物就别进仓库。 除非它是下一个人也要走一遍的路径——那时才 commit,并挂到 README 上。

wizard 是这套技能里唯一一个产出可执行程序的技能。它和 setup-matt-pocock-skills 是邻居:一个把仓库配成这套技能能用的状态,一个给「其他所有事情」生成一条搭建路径。它和 implement 也搭:一次构建落到需要凭证或手工切换时,wizard 补齐只有人能走的那几步。

讨论

用 GitHub 账号留言;评论保存在公开仓库chengshu-blog-discussions的 Discussions 里。也可通过 RSS 订阅后续文章。