Создание мода
Попросите Claude написать мод Claude Code из описания или напишите его сами, чтобы подсчитывать вызовы инструментов и добавить команду. Изучите цикл перезагрузки и валидации.
Мод — это плагин Claude Code plugin с файлом входа, называемым модулем hooks: файл JavaScript или TypeScript, функции которого Claude Code вызывает при возникновении событий. Есть два способа создать мод:
- Попросите Claude написать его: опишите, что вы хотите в сеансе Claude Code
- Напишите его сами: следуйте руководству, чтобы узнать, как работает код мода. Вам не нужны Node.js, бандлер или этап сборки, потому что Claude Code загружает файлы
.jsи.tsнапрямую.
Если вы еще не решили, является ли мод правильным инструментом, сначала прочитайте сравнение на странице обзора.
Моды требуют Claude Code v2.1.287 или позже. В вашей оболочке выполните claude --version, чтобы проверить. Чтобы узнать, могут ли моды загружаться для вас, см. Проверка возможности загрузки модов.
Попросите Claude написать мод
Опишите нужный вам мод в интерактивном сеансе Claude Code, и Claude напишет его. Claude работает из встроенного skill с именем plugin-authoring, который сообщает ему, где написать мод, какие события и методы есть в вашей версии, и как загружается мод. Claude может загрузить skill, когда вы попросите мод, или вы можете загрузить его сами, выполнив /plugin-authoring в приглашении Claude Code.
Мод запускается после того, как вы его одобрите, кроме сеансов, где мод, написанный Claude, не может загрузиться.
Опишите мод
Попросите мод своими словами, например make a mod that shows the current git branch above the prompt. Claude пишет мод в отдельном каталоге в папке модов сеанса, которая находится в ~/.claude/dev-mods/, за которой следует ID сеанса. Полный путь мода выглядит как ~/.claude/dev-mods/3f2a9c1e-5b7d-4e8a-9c21-6d0f4b8a7e13/git-branch/.
В режимах разрешений default и acceptEdits permission modes Claude Code спрашивает перед тем, как Claude создаст каждый из файлов мода, потому что ~/.claude — это защищённый путь. Одобрите каждый файл по мере его появления.
Одобрите мод
Когда Claude сохраняет первый файл, Claude Code спрашивает, следует ли включить горячую перезагрузку для сеанса. Горячая перезагрузка запускает моды, написанные Claude в этом сеансе, и подхватывает каждое последующее изменение.
Выберите один из этих ответов:
- Enable for this session: моды в папке модов сеанса загружаются при завершении хода и перезагружаются в конце каждого хода, который их изменяет. Ваш ответ действует для сеанса, включая после его возобновления.
- Not now: ничего не загружается пока. Файлы остаются там, где их написал Claude, и моды загружаются при следующем запуске этого сеанса. Чтобы мод никогда не загружался, удалите его каталог.
Проверьте, что мод загрузился
Выполните /plugin в приглашении Claude Code и нажимайте Tab, пока не будет выбрана вкладка Installed. Она перечисляет мод, и вы можете отключить его там.
Попробуйте мод
Используйте то, что вы просили. Для примера приглашения текущее имя ветки появляется над полем приглашения. Если мод не делает то, что вы хотели, скажите Claude, что изменить. Мод перезагружается в конце каждого хода, который изменяет его файлы, поэтому вы можете попробовать изменение сразу после завершения Claude.
Используйте мод в других сеансах
Мод, написанный Claude, загружается только в сеансе, который его создал, и Claude Code удаляет папку модов этого сеанса после того, как она старше cleanupPeriodDays. Чтобы сохранить мод, скопируйте его каталог из папки модов в место по вашему выбору, например ~/mods/git-branch. Затем выберите, как его загружать:
- В сеансе, который вы запускаете: в вашей оболочке выполните
claude --plugin-dir ~/mods/git-branch - Для других людей: добавьте его на маркетплейс, чтобы они могли его установить
Сеансы, где мод, написанный Claude, не может загрузиться
Мод, написанный Claude, загружается только после того, как вы его одобрите, в доверенной рабочей области, где разрешено запускать моды. В этих сеансах он не загружается:
- Никого нет, чтобы одобрить: сеанс не может показать вам приглашение, как в запуске
claude -pили в режимеdontAsk - Рабочая область не доверена: вы не приняли приглашение доверия для каталога
- Моды остановлены: вы запустили с
--safe-modeили--bare, вы установилиdisableAllHooks, или управляемые параметры вашей организации это блокируют
Напишите мод сами
В этом руководстве вы создаёте мод с именем first-mod, который подсчитывает вызовы инструментов, которые делает Claude, показывает счётчик рядом со спиннером во время работы Claude и добавляет команду /tally, которая его выводит. Затем вы читаете объявления типов, которые Claude Code пишет рядом с вашим модом, и выполняете claude plugin validate. Вместе они показывают вам события и методы, которые предлагает ваша версия, и что Claude Code читает из вашего кода.
Эта запись показывает готовый мод. Спиннер подсчитывает вызовы инструментов, /tally выводит счётчик, и редактирование кода вступает в силу во время работы сеанса:
Вы пишете три файла:
first-mod/
├── .claude-plugin/
│ └── plugin.json
└── hooks/
├── hooks.json
└── register.js
plugin.json: манифест плагинаhooks.json: указывает на ваш файл кодаregister.js: ваш код, называемый модулем hooks
Создайте каталог плагина
Создайте два каталога, которые содержат файлы:
mkdir -p first-mod/.claude-plugin first-mod/hooks
New-Item -ItemType Directory -Force first-mod\.claude-plugin, first-mod\hooks
Напишите манифест
Мод — это плагин, и моду нужен манифест. Манифест этого мода не имеет специальных полей. Сохраните это как first-mod/.claude-plugin/plugin.json:
{
"name": "first-mod",
"version": "0.1.0",
"description": "Counts Claude's tool calls, shows the count beside the spinner, and adds a /tally command",
"author": { "name": "Your Name" }
}
Скажите Claude Code, где находится ваш код
Когда Claude Code загружает плагин, он читает hooks/hooks.json плагина. Ключ modules в этом файле указывает путь к вашему коду, и его наличие делает плагин модом. Перечислите один путь, относительный к hooks.json. Здесь он указывает на register.js, который вы напишете на следующем шаге.
Сохраните это как first-mod/hooks/hooks.json:
{
"description": "The first-mod hooks module",
"modules": ["./register.js"]
}
Напишите код
Этот файл — это код мода, называемый модулем hooks. Когда мод загружается, Claude Code вызывает функцию register, которую экспортирует файл, и передаёт ей функцию с именем on. Каждый вызов on регистрирует обработчик события, называемый hook, для события, которое он называет.
Сохраните это как first-mod/hooks/register.js:
// The count, shared by the hooks below
let calls = 0
// Claude Code calls this once when the mod loads
export function register(on) {
// Runs when the session starts, before your first prompt
on('session.start', async ($, e, next) => {
// Add the /tally command
await $.command.register({
name: 'tally',
description: 'Show how many tool calls Claude has made',
})
// Let the session start as usual
return next(e)
})
// Runs each time Claude is about to use a tool
on('tool.call', async ($, e, next) => {
calls += 1
// Ask Claude Code to draw the interface again, so the new count shows
$.ui.invalidate('ui.render')
// Let the tool run as usual
return next(e)
})
// Runs when you type /tally, and only then, because of the matcher
on('command.run', { command: 'tally' }, async () => {
// The text to print in the transcript
return { text: 'Claude has made ' + calls + ' tool calls since this mod loaded' }
})
// Runs each time Claude Code draws the spinner
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
// Keep Claude Code's spinner, with the count added after its word
return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
})
}
Файл хранит счётчик в calls и регистрирует четыре hook:
session.startзапускается при запуске сеанса, перед вашим первым приглашением, и снова каждый раз, когда мод перезагружается. Он добавляет команду/tallyв Claude Code.tool.callзапускается каждый раз, когда Claude собирается использовать инструмент. Он добавляет один кcallsи просит Claude Code нарисовать интерфейс снова.command.runзапускается, когда вы вводите/tally. Он возвращает текст для вывода.ui.renderзапускается каждый раз, когда Claude Code рисует спиннер. Он добавляет счётчик после слова спиннера.
Как работает пример мода объясняет три аргумента, которые принимает каждый hook, и что каждый из них возвращает.
Загрузите мод
Запустите Claude Code с флагом --plugin-dir, который загружает каталог плагина для одного сеанса без его установки:
claude --plugin-dir ./first-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 ставит имя плагина перед текстом команды.
Чтобы проверить команду без интерактивного сеанса, выполните её в неинтерактивном режиме:
claude -p "/tally" --plugin-dir ./first-mod
first-mod: Claude has made 0 tool calls since this mod loaded
Если /tally не в списке команд, модуль не загрузился. См. Узнайте, почему мод ничего не делает.
Измените код во время работы сеанса
Оставьте сеанс открытым. В register.js измените ' · tool calls: ' на ' · tools used: ' в hook ui.render и сохраните. Выделенная строка — это та, которая изменяется:
// Runs each time Claude Code draws the spinner
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
// Keep Claude Code's spinner, with the count added after its word
return next({ ...e, props: { ...e.props, suffix: ' · tools used: ' + calls + '…' } })
})
Строка в транскрипте говорит, что first-mod перезагрузился и перечисляет его hooks, и следующий спиннер использует новый текст, как в Thinking · tools used: 1….
Как работает пример мода
Каждая функция, которую вы передаёте on, — это hook, который является обработчиком события. Claude Code передаёт каждому hook одни и те же три аргумента:
- API модов, названный
$: каждый метод, который мод может вызвать, чтобы выйти за пределы себя, в пространствах имён таких как$.uiи$.command - Событие, названное
e: входные данные события как простые данные, такие как имя и аргументы вызова инструмента - Следующий обработчик, названный
next: функция, которая передаёт событие другим модам, а затем собственному поведению Claude Code, и возвращает результат
Hooks в first-mod обрабатывают свои события тремя способами, которыми может обрабатывать hook:
- Observe: hook
session.startрегистрирует команду, и hooktool.callподсчитывает вызов и просит перерисовку. Оба возвращаютnext(e), поэтому сеанс запускается и инструмент работает как обычно. - Answer: hook
command.runвозвращает свой собственный результат и никогда не вызываетnext. Второй аргументon,{ command: 'tally' }, — это фильтр, называемый matcher, поэтому hook запускается только для/tally. - Rewrite: hook
ui.renderвызываетnextс копиейe, чейsuffixсодержит счётчик, поэтому Claude Code рисует свой обычный спиннер с вашим текстом после слова
Claude Code следит за каталогом, загруженным с --plugin-dir, и горячо перезагружает модуль hooks при изменении файла в нём. Каждая перезагрузка запускает register снова, поэтому calls возвращается к 0 и /tally начинает считать снова. Чтобы сохранить значение между перезагрузками, см. Сохранение состояния.
Продолжайте работать над модом
После загрузки мода вы можете попросить Claude изменить его, проверить ваш код против определений типов для вашей версии, перечислить события и вызовы, которые Claude Code находит в нём, и протестировать его.
Измените мод с помощью Claude
Чтобы изменить мод, который у вас уже есть, запустите сеанс с --plugin-dir, указывающим на каталог мода, чтобы то, что пишет Claude, загружалось в том же сеансе:
claude --plugin-dir ./first-mod
Затем попросите изменение, например 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 мода. Таблица защищённых путей даёт результат для других режимов разрешений.
Файлы, которые Claude сохраняет во время его хода, перезагружаются при завершении хода, поэтому вы можете попробовать /tally-reset сразу после завершения Claude.
Получите определения типов для вашей версии
Каждый раз, когда Claude Code загружает или перезагружает мод из каталога, который вы передаёте --plugin-dir, или мод Claude написал для вас, он пишет файлы объявлений TypeScript, заканчивающиеся на .d.ts, в .claude-plugin/types/ внутри каталога мода. Они описывают точные события, методы API модов и элементы в версии Claude Code, которую вы запускаете, поэтому ваш редактор может автодополнять и проверять типы ваших hooks. Чтобы просмотреть объявления в Интернете, прочитайте mods/types/claude-code.d.ts в репозитории Claude Code, первая строка которого называет версию, которая его написала. Каталог содержит эти файлы:
| Путь | Что он объявляет |
|---|---|
claude-code/index.d.ts |
Каждое событие и его входные данные и результат, каждое пространство имён API модов и метод, и элементы, которые каждая поверхность может рисовать |
claude-code-tools/index.d.ts |
Входные данные встроенных инструментов и результаты, поэтому проверка e.tool === 'Bash' сужает e |
claude-code-mcp/index.d.ts |
Входные данные инструментов MCP, которые были подключены в последний раз, когда вы сохранили файл в модо |
index.d.ts в каталоге, названном в честь плагина |
Что этот плагин добавляет в API модов. Есть один каталог для каждого плагина, который ваш plugin.json перечисляет в dependencies. |
tsconfig.json |
Параметры компилятора, которые подходят для модуля hooks |
Если ваш мод не имеет собственного tsconfig.json, Claude Code добавляет один в корень мода, который расширяет сгенерированный, поэтому ваш редактор и tsc -p ./first-mod проверяют тип мода без дополнительной настройки.
События и методы могут изменяться между выпусками, поэтому доверяйте этим файлам больше, чем любой странице, включая эту, когда они не согласны.
claude-code/index.d.ts — это самый полный справочник для вашей сборки, с комментарием и примером для каждого метода API модов. Чтобы что-то найти, поищите в файле его имя, например 'tool.call'.
Проверьте, что Claude Code читает из вашего мода
Чтобы увидеть ваш мод так, как Claude Code его видит, без запуска вашего кода или запуска сеанса, используйте claude plugin validate. Он проверяет манифест и выполняет тот же статический анализ на исходном коде модуля hooks, который Claude Code выполняет при загрузке мода. В вашей оболочке выполните его на каталоге мода:
claude plugin validate ./first-mod
Для first-mod вывод включает эти строки.
❯ ./register.js hooks: session.start, tool.call, command.run{command=tally}, ui.render{component=Spinner}
❯ ./register.js calls: $.command.register, $.ui.invalidate
✔ Validation passed
Строка hooks: перечисляет события, которые ваш модуль hook, каждое с его фильтром в скобках. Строка calls: перечисляет каждый метод API модов, который он вызывает. Модуль, который читает или устанавливает переменные окружения, также получает строки env reads: и env writes:, и тот, который использует $.state, получает state reads: и state writes:.
Если событие, которое вы хотели hook, отсутствует в первой строке, Claude Code не будет вызывать этот hook либо. Обычная причина — неправильное написание имени события, которое команда сообщает как ошибку, такую как "tool.calls" is not an event.
Следуйте этим правилам, чтобы статический анализ мог найти каждый hook и вызов:
- Напишите каждый вызов API модов полностью:
$, пространство имён, затем метод, как в$.store.get('notes'). Вы можете передать$функции, объявленной на верхнем уровне того же файла, и для функции вашей с именемloadNotesстрокаcalls:затем читает$.store.get (via loadNotes). Передача$методу, функции, определённой внутри hook, или функции, которую вы импортируете из другого из ваших файлов, не проходит валидацию. Функцииreadиupdate, которые использует$.state, — это импорты, которые могут её принять. Не присваивайте$или одно из его пространств имён переменной, не деструктурируйте его и не индексируйте его с вычисленным именем.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.
Протестируйте мод
Вы можете написать автоматизированные тесты для мода и запустить их из вашей оболочки с claude plugin test, без сеанса, входа или сети. Тест вызывает события, которые обрабатывают ваши hooks, и проверяет, что сделали hooks.
Этот тест вызывает два вызова инструментов, запускает /tally и проверяет, что ответ считает оба. Сохраните это как first-mod/tests/first-mod.test.ts:
import { expect, test } from 'claude-code/testing'
test('/tally reports the tool calls the mod has seen', async ($, on) => {
// Answer each tool call in Claude Code's place, so no tool runs
on('tool.call', () => ({ result: 'ok' }))
// Raise two tool calls, which the mod's tool.call hook counts
await $.tool.call({ tool: 'Bash', command: 'ls' })
await $.tool.call({ tool: 'Read', file_path: 'README.md' })
// Run /tally and check the text its hook returns
const answer = await $.command.run({ command: 'tally', args: '' })
expect(answer.text).toBe('Claude has made 2 tool calls since this mod loaded')
})
В вашей оболочке выполните тесты из каталога first-mod:
claude plugin test
Вывод называет каждый тест и прошёл ли он, с временем, которое варьируется от запуска к запуску:
tests/first-mod.test.ts:
(pass) /tally reports the tool calls the mod has seen [22.87ms]
1 pass
0 fail
Ran 1 test across 1 file. [0.19s]
Протестируйте мод охватывает заглушку вызова модели или хранилища, и тестирование таймеров и рисунков.
Поделитесь своим модом
Мод — это плагин, поэтому вы версионируете его в манифесте, и люди устанавливают и обновляют его с помощью команд /plugin. Чтобы дать его другим людям, добавьте его на маркетплейс.
Перед этим проверьте name плагина: claude plugin validate не проходит имя, которое выглядит как одно из собственных Anthropic, например то, которое начинается с claude-. События и методы могут изменяться между выпусками, поэтому ваш README — это место, где нужно сказать, какую версию Claude Code вы тестировали.
Продолжайте разработку против каталога с --plugin-dir, а не против установленной копии. Claude Code кэширует установленный плагин по версии, поэтому ваши редактирования не достигают установленной копии, пока вы не повысите версию и не установите снова.
Следующие шаги
- Рисуйте в интерфейсе: откройте панель, рисуйте над приглашением и добавляйте кнопки и текстовые поля
- Реагируйте на события: hook вызовы инструментов, приглашения и ходы
- Используйте API модов: добавляйте команды и инструменты, вызывайте модель и запускайте работу по таймеру
- Протестируйте мод: заглушка того, что Claude Code ответит, и тестирование таймеров и рисунков
- Устраняйте неполадки мода: причины, по которым мод ничего не делает, и журнал отладки
- Прочитайте исходный код встроенных модов: полные плагины, каждый со своим модулем hooks и тестами