Skip to main content
Mod 是一个 Claude Code 插件,具有一个入口文件,称为 hooks 模块:一个 JavaScript 或 TypeScript 文件,其函数在事件发生时由 Claude Code 调用。有两种方式来创建一个:
  • 让 Claude 编写它:在 Claude Code 会话中描述你想要的内容
  • 自己编写:按照教程学习 mod 代码的工作原理。你不需要 Node.js、打包工具或构建步骤,因为 Claude Code 直接加载 .js 和 .ts 文件。
如果你还没有决定 mod 是否是合适的工具,请先阅读概述中的比较。
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

创建插件目录

创建保存文件的两个目录:
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 绘制微调器时运行。它在微调器的单词后添加计数。
示例 mod 如何工作解释了每个 hook 采用的三个参数以及每个参数返回的内容。
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.start hook 注册命令,tool.call hook 计算调用并要求重新绘制。两者都返回 next(e),所以会话启动,工具照常运行。
  • 回答:command.run hook 返回自己的结果,从不调用 next。on 的第二个参数 { command: 'tally' } 是一个过滤器,称为匹配器,所以 hook 仅对 /tally 运行。
  • 重写:ui.render hook 使用 e 的副本调用 next,其 suffix 保持计数,所以 Claude Code 绘制其通常的微调器,你的文本在单词后面
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
在你的 shell 中,从 first-mod 目录运行测试:
输出命名每个测试及其是否通过,时间从运行到运行变化:
测试 mod涵盖存根模型调用或存储,以及测试计时器和绘图。

分享你的 mod

Mod 是一个插件,所以你在清单中对其进行版本控制,人们使用 /plugin 命令安装和更新它。要将其提供给其他人,将其添加到市场。 在你这样做之前,检查插件的 name:claude plugin validate 失败一个看起来像 Anthropic 自己的名称,例如以 claude- 开头的名称。事件和方法可以在版本之间更改,所以你的 README 是说明你测试的 Claude Code 版本的地方。 继续针对目录使用 --plugin-dir 进行开发,而不是针对已安装的副本。Claude Code 按版本缓存已安装的插件,所以你的编辑在你提高版本并再次安装之前不会到达已安装的副本。

后续步骤