- 让 Claude 编写它:在 Claude Code 会话中描述你想要的内容
- 自己编写:按照教程学习 mod 代码的工作原理。你不需要 Node.js、打包工具或构建步骤,因为 Claude Code 直接加载
.js和.ts文件。
Mod 需要 Claude Code v2.1.287 或更高版本。在你的 shell 中,运行
claude --version 来检查。要查看 mod 是否可以为你加载,请参阅检查 mod 是否可以加载。让 Claude 为你编写一个 mod
在交互式 Claude Code 会话中描述你想要的 mod,Claude 会编写它。Claude 使用一个名为plugin-authoring 的内置技能,它告诉 Claude 在哪里编写 mod、你的版本有哪些事件和方法,以及 mod 如何被加载。当你请求一个 mod 时,Claude 可以加载该技能,或者你可以通过在 Claude Code 提示符处运行 /plugin-authoring 来自己加载它。
一旦你批准了 mod,它就会运行,除了在某些会话中 Claude 编写的 mod 无法加载的情况。
1
描述 mod
用你自己的话请求 mod,例如
make a mod that shows the current git branch above the prompt。Claude 在会话的 mods 文件夹中的自己的目录中编写 mod,该文件夹是 ~/.claude/dev-mods/ 后跟会话的 ID。mod 的完整路径看起来像 ~/.claude/dev-mods/3f2a9c1e-5b7d-4e8a-9c21-6d0f4b8a7e13/git-branch/。在
default 和 acceptEdits 权限模式中,Claude Code 在 Claude 创建 mod 的每个文件之前都会询问,因为 ~/.claude 是一个受保护的路径。在每个文件出现时批准它。2
批准 mod
当 Claude 保存第一个文件时,Claude Code 会询问是否为会话启用热重新加载。热重新加载运行此会话中 Claude 编写的 mod,并在每个后续更改它们的转折处选择每个更改。选择以下答案之一:
- 为此会话启用:会话的 mods 文件夹中的 mod 在转折结束时加载,并在每个更改它们的转折结束时重新加载。你的答案在整个会话中持续,包括在你恢复它之后。
- 暂时不:现在什么都不加载。文件保留在 Claude 编写的位置,mod 在该会话下次启动时加载。要防止 mod 加载,请删除其目录。
3
检查 mod 是否已加载
在 Claude Code 提示符处运行
/plugin,然后按 Tab 直到选中已安装选项卡。它列出了 mod,你可以在那里关闭它。4
尝试 mod
使用你请求的内容。对于示例提示,当前分支名称出现在提示框上方。如果 mod 没有做你想要的,告诉 Claude 要改变什么。mod 在每个更改其文件的转折结束时重新加载,所以你可以在 Claude 完成后立即尝试更改。
在其他会话中使用 mod
Claude 编写的 mod 仅在创建它的会话中加载,一旦 Claude Code 的会话比cleanupPeriodDays 更旧,它就会删除该会话的 mods 文件夹。要保留 mod,请将其目录从 mods 文件夹复制到你自己的位置,例如 ~/mods/git-branch。然后选择如何加载它:
- 在你启动的会话中:在你的 shell 中,运行
claude --plugin-dir ~/mods/git-branch - 对于其他人:将其添加到市场,以便他们可以安装它
Claude 编写的 mod 无法加载的会话
Claude 编写的 mod 仅在你批准它后加载,在允许 mod 运行的受信任工作区中。在这些会话中它不会加载:- 没有人在那里批准:会话无法向你显示提示,如在
claude -p运行或dontAsk模式中 - 工作区不受信任:你还没有接受目录的信任提示
- Mod 已停止:你使用
--safe-mode或--bare启动,你设置了disableAllHooks,或你的组织的托管设置阻止了它
自己编写一个 mod
在本教程中,你构建一个名为first-mod 的 mod,它计算 Claude 进行的工具调用,在 Claude 工作时在微调器旁边显示计数,并添加一个 /tally 命令来打印它。然后你读取 Claude Code 在你的 mod 旁边写入的类型声明,并运行 claude plugin validate。它们一起向你展示你的版本提供的事件和方法,以及 Claude Code 从你的代码中读取的内容。
这个录制显示了完成的 mod。微调器计算工具调用,/tally 打印计数,对代码的编辑在会话运行时生效:
1
创建插件目录
创建保存文件的两个目录:
- Bash or Zsh
- PowerShell
2
编写清单
Mod 是一个插件,mod 需要一个清单。这个 mod 的清单没有特殊字段。将其保存为
first-mod/.claude-plugin/plugin.json:first-mod/.claude-plugin/plugin.json
3
告诉 Claude Code 你的代码在哪里
当 Claude Code 加载一个插件时,它读取插件的
hooks/hooks.json。该文件中的 modules 键给出你的代码的路径,拥有它是使插件成为 mod 的原因。列出一个路径,相对于 hooks.json。这里它指向 register.js,你在下一步中编写。将其保存为 first-mod/hooks/hooks.json:first-mod/hooks/hooks.json
4
编写代码
这个文件是 mod 的代码,称为 hooks 模块。当 mod 加载时,Claude Code 调用文件导出的 该文件在
register 函数,并传递一个名为 on 的函数。每次调用 on 都会为它命名的事件注册一个事件处理程序,称为 hook。将其保存为 first-mod/hooks/register.js:first-mod/hooks/register.js
calls 中保持计数,并注册四个 hook:session.start在会话启动时运行,在你的第一个提示之前,以及每次 mod 重新加载时。它将/tally命令添加到 Claude Code。tool.call每次 Claude 即将使用工具时运行。它将 1 添加到calls并要求 Claude Code 再次绘制界面。command.run当你键入/tally时运行。它返回要打印的文本。ui.render每次 Claude Code 绘制微调器时运行。它在微调器的单词后添加计数。
5
加载 mod
使用
--plugin-dir 标志启动 Claude Code,它为一个会话加载一个插件目录而不安装它:6
尝试 mod
要求 Claude 做一些需要几个工具调用的事情,例如 如果
list the files here and read the README。当 Claude 工作时,微调器的单词后跟一个上升的计数,如 Thinking · tool calls: 2…。当 Claude 完成时,键入 /tally 并按 Enter。转录显示 first-mod: Claude has made 2 tool calls since this mod loaded,带有你自己的计数。Claude Code 将插件的名称放在命令的文本前面。要在非交互模式下检查命令,请运行它:/tally 不在命令列表中,则模块未加载。请参阅找出为什么 mod 什么都不做。7
在会话运行时更改代码
保持会话打开。在 转录中的一行说
register.js 中,在 ui.render hook 中将 ' · tool calls: ' 更改为 ' · tools used: ' 并保存。突出显示的行是更改的行:first-mod/hooks/register.js
first-mod 重新加载并列出其 hook,下一个微调器使用新文本,如 Thinking · tools used: 1…。示例 mod 如何工作
你传递给on 的每个函数都是一个 hook,它是一个事件处理程序。Claude Code 将相同的三个参数传递给每个 hook:
- Mods API,名为
$:mod 可以调用的每个方法来到达自身之外,在命名空间中,例如$.ui和$.command - 事件,名为
e:事件的输入作为纯数据,例如工具调用的名称和参数 - 下一个处理程序,名为
next:一个函数,将事件传递给其他 mod,然后传递给 Claude Code 自己的行为,并返回结果
first-mod 中的 hook 以 hook 可以处理的三种方式处理它们的事件:
- 观察:
session.starthook 注册命令,tool.callhook 计算调用并要求重新绘制。两者都返回next(e),所以会话启动,工具照常运行。 - 回答:
command.runhook 返回自己的结果,从不调用next。on的第二个参数{ command: 'tally' }是一个过滤器,称为匹配器,所以 hook 仅对/tally运行。 - 重写:
ui.renderhook 使用e的副本调用next,其suffix保持计数,所以 Claude Code 绘制其通常的微调器,你的文本在单词后面
--plugin-dir 加载的目录,并在其中的文件更改时热重新加载 hooks 模块。每次重新加载都会再次运行 register,所以 calls 回到 0,/tally 开始重新计数。要在重新加载中保持值,请参阅保持状态。
继续处理 mod
一旦 mod 加载,你可以让 Claude 更改它,根据你的版本的类型定义检查你的代码,列出 Claude Code 在其中找到的事件和调用,并测试它。使用 Claude 更改 mod
要更改你已有的 mod,使用--plugin-dir 指向 mod 的目录启动会话,以便 Claude 编写的内容在同一会话中加载:
add a /tally-reset command to this mod that sets the tally back to zero。Claude 编辑 hooks 模块,运行 claude plugin validate,并修复它报告的内容。你使用 --plugin-dir 加载的目录是一个受保护的路径,所以在 default 和 acceptEdits 模式中,你被要求批准 Claude 对 mod 的每个编辑。受保护的路径表给出其他权限模式的结果。
Claude 在其转折期间保存的文件在转折结束时重新加载,所以你可以在 Claude 完成后立即尝试 /tally-reset。
获取你的版本的类型定义
每次 Claude Code 从你传递给--plugin-dir 的目录加载或重新加载 mod,或 mod Claude 为你编写时,它会将 TypeScript 声明文件(以 .d.ts 结尾)写入 mod 目录内的 .claude-plugin/types/。它们描述你正在运行的 Claude Code 版本中的确切事件、mods API 方法和元素,所以你的编辑器可以自动完成和类型检查你的 hooks。要在线浏览声明,请阅读 Claude Code 仓库中的 mods/types/claude-code.d.ts,其第一行命名了写入它的版本。该目录包含这些文件:
如果你的 mod 没有自己的
tsconfig.json,Claude Code 会在 mod 的根目录添加一个,扩展生成的那个,所以你的编辑器和 tsc -p ./first-mod 类型检查 mod 而无需更多设置。
事件和方法可以在版本之间更改,所以当它们不同意时,相信这些文件而不是任何页面,包括这个。
claude-code/index.d.ts 是你的构建的最完整的参考,每个 mods API 方法都有注释和示例。要查找某些内容,请在文件中搜索其名称,例如 'tool.call'。
检查 Claude Code 从你的 mod 中读取的内容
要以 Claude Code 看到的方式查看你的 mod,而不运行你的代码或启动会话,请使用claude plugin validate。它检查清单并对 hooks 模块的源运行相同的静态分析,Claude Code 在加载 mod 时运行。在你的 shell 中,在 mod 的目录上运行它:
first-mod,输出包括这些行。
hooks: 行列出你的模块 hook 的事件,每个都带有其在大括号中的过滤器。calls: 行列出它调用的每个 mods API 方法。读取或设置环境变量的模块也会获得 env reads: 和 env writes: 行,使用 $.state 的模块会获得 state reads: 和 state writes:。
如果你打算 hook 的事件在第一行中缺失,Claude Code 也不会调用该 hook。通常的原因是事件名称拼写错误,命令报告为错误,例如 "tool.calls" is not an event。
遵循这些规则,以便静态分析可以找到每个 hook 和调用:
- 完整拼写每个 mods API 调用:
$、命名空间,然后是方法,如$.store.get('notes')。你可以将$传递给在同一文件的顶级声明的函数,对于你的名为loadNotes的函数,calls:行然后读取$.store.get (via loadNotes)。将$传递给方法、在 hook 内定义的函数或从另一个文件导入的函数会导致验证失败。$.state使用的read和update函数是可以接受它的导入。不要将$或其命名空间之一分配给变量、解构它或使用计算名称索引它。const ui = $.ui失败,出现$.ui is used as a value。 - 在每个
on调用中将事件名称写为字符串文字,例如'tool.call'。变量或循环遍历名称列表会失败,出现the event name passed to on() is not a string literal。 - 在
register内,不要声明第二个名为on的变量或参数。验证失败,出现"on" is declared again (shadowed)。 - 仅从插件目录内的文件导入,通过相对路径。唯一允许的裸导入是
claude-code,用于类型和一些帮助程序。 - 在文件顶部使用
import声明,如import { name } from './file.js'。动态import()失败,出现a dynamic import(); a hooks module imports its own files with an import declaration。 - 将每个文件写为 ES 模块,使用
import而不是require。参考列出 Claude Code 加载的文件扩展名。
测试 mod
你可以为 mod 编写自动化测试,并使用claude plugin test 从你的 shell 运行它们,无需会话、登录或网络。测试引发你的 hook 处理的事件,并检查 hook 做了什么。
这个测试引发两个工具调用,运行 /tally,并检查回复计算两者。将其保存为 first-mod/tests/first-mod.test.ts:
first-mod/tests/first-mod.test.ts
first-mod 目录运行测试:
分享你的 mod
Mod 是一个插件,所以你在清单中对其进行版本控制,人们使用/plugin 命令安装和更新它。要将其提供给其他人,将其添加到市场。
在你这样做之前,检查插件的 name:claude plugin validate 失败一个看起来像 Anthropic 自己的名称,例如以 claude- 开头的名称。事件和方法可以在版本之间更改,所以你的 README 是说明你测试的 Claude Code 版本的地方。
继续针对目录使用 --plugin-dir 进行开发,而不是针对已安装的副本。Claude Code 按版本缓存已安装的插件,所以你的编辑在你提高版本并再次安装之前不会到达已安装的副本。
后续步骤
- 在界面中绘制:打开一个窗格,在提示上方绘制,并添加按钮和文本字段
- 对事件做出反应:hook 工具调用、提示和转折
- 使用 mods API:添加命令和工具、调用模型、在计时器上运行工作
- 测试 mod:存根 Claude Code 会回答的内容,以及测试计时器和绘图
- 排查 mod 故障:mod 什么都不做的原因和调试日志
- 阅读内置 mod 的源代码:完整的插件,每个都有其 hooks 模块和测试