一句话说清 Mods 是什么
Mod 是一种特殊的 Claude Code 插件:它不是从外部喂脚本进来,而是用 JavaScript / TypeScript 写的事件处理器,直接跑在 Claude Code 进程内部。
Claude Code 在关键节点——工具调用、提示提交、界面绘制——会主动调用这些处理器。处理器可以:
- 观察:记下发生了什么,放行
- 改写:在事件继续之前动点手脚
- 接管:自己处理掉,让默认行为不执行
官方文档里那句概括最准确:a mod 是 “a plugin that changes how Claude Code looks and behaves”——改的是长相和行为,而不只是”给模型多喂一份说明”。
本文信息以 Claude Code 官方文档(code.claude.com/docs/en/plugins/mods)为准,需要终端版 v2.1.287+,桌面 App 自 v2.1.286 起内置。
为什么 Mods 和以前的 hooks 不是一回事
Claude Code 早就有 hooks,但那是外部钩子:在 settings.json 里配一条 shell 命令、一个 HTTP 请求或一段 prompt,事件触发时进程外跑一下。
Mods 是进程内函数。差别的本质是一张能力表:
| Mod | Settings Hook | Skill | MCP Server | |
|---|---|---|---|---|
| 是什么 | 插件里的 JS/TS 函数,进程内被调用 | 生命周期事件上跑的脚本/HTTP/prompt | 一份 SKILL.md 指令 | 外部进程提供工具 |
| 能改什么 | 工具调用、prompt、命令、回合、界面绘制 | 放行/拦截/记录事件、改参数 | 模型知道什么、做什么 | 模型有哪些工具 |
| 能画界面 | 能 | 不能 | 不能 | 不能 |
| 你写什么 | JavaScript / TypeScript | 脚本 + settings 条目 | Markdown | 任意语言的服务器 |
关键分水岭就在画界面这一行。以前所有扩展方式都只能在”文本与工具”层面做文章,Mods 是第一个能让插件往 Claude Code 的屏幕上画东西的机制。
Mods 能干的四件狠事
1. 画你能用的界面
在对话记录旁边开一个 pane,或在输入框上方挂一条 band,带标签页、按钮、输入框。官方示例 token-weather 在输入框上方画”上下文窗口天气预报”,每轮请求后告诉你上下文还剩多少。
2. 重绘 Claude Code 自己画的东西
工具调用那一行、转圈的 spinner、提问对话框——这些原本是 Claude Code 私有的 UI,mod 可以替换或改样式。示例代码里最小的 demo 就是给 spinner 加后缀:
// 计数由两个 hook 共享
let calls = 0
export function register(on) {
// 每次 Claude 准备用工具时 +1
on('tool.call', async ($, e, next) => {
calls += 1
$.ui.invalidate('ui.render')
return next(e)
})
// 每次画 spinner 时,把计数塞进去
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
return next({
...e,
props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' },
})
})
}
跑起来后 spinner 会变成 Thinking · tool calls: 3…。整个 mod 就这一个文件。
3. 步入工具调用或请求
示例 blast-radius 的做法很典型:扣住 rm -rf、force push 这类危险 shell 命令,先展示”这条命令会改掉什么”,配确认/取消按钮,你点了才放行。更狠的玩法是把某次请求改道发给另一个模型,或者不执行工具直接替你回答。
4. 命令与跨 hook 共享状态
/command 可以直接执行你的函数,不经过 Claude 回合,Claude 忙着的时候也能跑。同一 mod 文件里的 hooks 共享变量——一个 hook 计数,另一个 hook 把计数画出来,这是普通 settings hook 做不到的(它们每次都是独立进程)。
一个 Mod 长什么样
目录结构小到可笑:
first-mod/
├── .claude-plugin/
│ └── plugin.json # 清单:名字、版本、描述
└── hooks/
├── hooks.json # 指向代码文件
└── register.js # 你的代码(hooks 模块)
register.js 导出一个 register(on) 函数,用 on(事件名, 处理器) 订阅事件。处理器里除了 next(e) 放行,其他所有越界操作——画界面、加命令、调模型、读文件、发网络请求——必须走 mods API。这条约束是刻意的:因为出口收敛到一个 API,Claude Code 才能在你安装前就用 claude plugin validate 列出这个 mod 会碰哪些事件、会请求哪些能力。
安装分发复用整套插件机制:/plugin install token-chart@your-org,或开发期 --plugin-dir 直接加载本地目录。Anthropic 在 claude-code-playground 仓库放了三个示例 mod(token-weather、blast-radius、replay-theater),每个都是完整插件。
最有说服力的证据:官方功能自己就是 Mod
Mods 不是官方开发、用户旁观的功能,而是官方先吃自己的狗粮。/plugin 的 Installed -> Built-in 里能看到:
| 内置 mod | 干什么 |
|---|---|
cc-plugin-diff | 接管 /diff,画出 diff 面板 |
cc-plugin-agents-md | 把 AGENTS.md 当项目指令加载 |
cc-plugin-telemetry | 上报分析数据,也给其他 mod 提供方法 |
cc-plugin-sec-default | 组织环境下防护用户自装 mod |
cc-plugin-you-should-know | 后台旁观 agent,发现你可能错过的事就在输入框上方提示(默认关闭) |
diff、agents-md、sec-default、telemetry 的源码已开源在 anthropics/claude-code 仓库的 mods/ 目录,每个都是带测试的完整插件。读这些源码是学习 mod 写法最快的路径——没有之一。
换句话说:Claude Code 的一部分产品功能,本身就是用它提供给用户的扩展机制写的。 架构上说到做到,这比任何宣传都硬。
安全边界:不沙箱,装之前先审
官方警告写得非常直白,mod 装上后可以:
- 以你的权限读写任意文件、起进程、发网络请求
- 读你的环境变量和 settings(包括里面的 API Key)
- 看你每一个 prompt 和每一次工具调用
- 改写 prompt / 工具调用,甚至替你批准本该弹窗询问的操作
- 花你的模型额度
而且 mod 不在沙箱里——即便你开了 sandboxing,mod 自己起的进程也在沙箱外。唯一能改的界面是权限弹窗本身(改不了它的内容,但 mod 能提前替你点”允许”)。
防线有三道:
- 安装前
claude plugin validate ./some-mod,读hooks:和calls:输出 - 只装信任的作者与 marketplace(官方 / 社区 / 第三方分层)
- 组织侧用 managed settings 管控;个人侧
--safe-mode或disableAllHooks一键全停
怎么上手
- 确认版本:
claude --version,需要 v2.1.287+(桌面 App v2.1.286+) - 先玩内置的:会话里
/plugin-> Installed -> Built-in,看看cc-plugin-diff这些长什么样 - 装个示例:克隆
anthropics/claude-code-playground,--plugin-dir单会话加载claude-code/mods/token-weather - 让 Claude 替你写:官方内置了
plugin-authoringskill——在会话里直接描述你想要的功能,Claude 把 mod 写出来 - 自己读源码:
github.com/anthropics/claude-code/tree/main/mods
第 4 步是这个功能名字的真正含义:一句话魔改自己。你不需要先学会 mods API,先让 Claude 写一个,再对着生成的代码学——这大概是最符合 Agent 时代的扩展方式了。
总结
- Mod = 进程内的 JS/TS 事件处理器,是 Claude Code 插件体系里最深的一档,能观察/改写/接管工具调用与 UI
- 画界面是分水岭:所有旧扩展方式都做不到,这是第一个能改 Claude Code 长相的机制
- 官方自己在用:
/diff、AGENTS.md 加载都是内置 mod,源码开源,架构说到做到 - 不沙箱,全权限:
claude plugin validate先审再装,组织用 managed settings 兜底
最后:和 DeepSeek Harness 的「万物可插件」对比
Claude Code 不是第一个把「扩展」做成核心卖点的工具。我在《万物可插件:读懂 DeepSeek Harness 的插件化架构》里拆过 dsh:基于 Cordis 运行时,模型适配、工具注册表、会话日志、甚至 Agent 循环本身都是可替换插件,没有必须改源码才能扩展的特权内核——你看到的「产品」只是 Profile + Bundle + Patch 组合出来的默认结果。
一边是 dsh 的「万物皆插件」,一边是 Claude Code 的 Mods,两条路线摆在一起:
| DeepSeek Harness | Claude Code Mods | |
|---|---|---|
| 一句话主张 | Everything is a plugin,核心只是组合器 | 进程内开放扩展面,连官方功能都用它写 |
| 插件替换的对象 | 运行时的骨架:Agent 循环、模型适配、会话存储 | 运行时的行为与表皮:事件、工具调用、UI 绘制 |
| 扩展点 | Cordis 服务 + 类型化事件,apply(ctx) 挂载 | mods API 的 on(事件, 处理器),观察/改写/接管 |
| 代码形态 | TS 插件模块,inject 声明依赖,可回滚副作用 | JS/TS hooks 模块,随插件分发,状态跨 hook 共享 |
| 能否画界面 | 无标准化 UI 扩展点,Web/CLI/SDK 是不同入口 | 能:pane、band、重绘 spinner,官方第一个做到的 |
| 分发形态 | Profile / Bundle / Patch 配置组合 | marketplace + /plugin 安装,user/project/local 三作用域 |
| 谁在用 | 给做 Agent 基础设施的开发者 | 官方内置功能(/diff、AGENTS.md)就是 mod |
| 成熟度 | developer preview,破坏性变更频繁 | 终端版 v2.1.287+ 已默认开启 |
两者的差异可以压缩成一个方向感:
- dsh 是往下挖——把 Agent 的地基做成可换的。你嫌默认循环不合适?换掉。想接别的模型后端?在适配缝上注册。改的是「Agent 是怎么跑的」。
- Mods 是往外开——在一个已经定型的成品工具上,把行为与表皮开出安全的接口。循环不让你碰,但每次工具调用、每帧界面绘制都是你的钩子。改的是「Claude Code 用起来是什么感觉」。
两条路并不互斥,各自也有明显的空缺:
- dsh 缺「画界面」:插件能换循环,但没有一个标准化的方式往 Web UI 上挂自定义面板——扩展点是事件与服务,不是像素
- Claude Code 缺「换骨架」:Mods 玩得再花,Agent 循环、模型调度层都是闭源核心,你永远只能在它给的事件缝里做事
- 分发哲学不同:dsh 的 Patch 层是给「组合产品」的开发者用的,粒度在配置行;Claude Code 的 marketplace 是给「装功能」的终端用户用的,粒度在插件包
值得留意的收敛信号:两边都走向了「官方能力 = 用户扩展机制的实例」——dsh 的产品本体是默认插件组合,Claude Code 的 /diff 是内置 mod。当一家开始用自己的扩展机制写核心功能,另一家也被迫这么做时,「运行时愿意打开多少」就成了 AI 编程工具真正的护城河。
Mods 出现意味着这件事又进了一步:竞争正在从「模型多强」转向「谁的运行时愿意真正打开」——而 dsh 证明了打开到骨架是什么样,Mods 证明了打开到皮肤是什么样。剩下的问题是:谁会第一个把骨架和皮肤一起打开?