Содержание
Мод в Claude Code это маленький плагин на JavaScript или TypeScript, который работает прямо внутри Claude Code: рисует свою панель или строку над полем ввода, добавляет команды и может остановить опасную команду до запуска. Быстрее всего сделать мод так: откройте Claude Code и напишите «сделай мод, который…». Claude сам напишет и проверит код, Claude Code спросит, включить ли горячую перезагрузку, и после вашего «да» мод заработает в конце ответа.
Моды появились в начале октября 2026 года, в Claude Code 2.1.287. Я собрал четыре мода: строку с веткой git, счётчик инструментов у спиннера, охранника от rm -rf и панель заметок. Ниже весь путь по шагам, с кодом и настоящими скриншотами из терминала.
До модов Claude Code настраивали снаружи: хуки в settings.json запускают скрипт на событие, скиллы дают Claude инструкции, MCP-серверы подключают новые инструменты. Мод работает внутри самого Claude Code. Claude Code вызывает функции мода, когда что-то происходит: Claude собирается запустить инструмент, вы отправили сообщение, на экране перерисовывается часть интерфейса. Функция может просто посмотреть на событие, изменить его или обработать сама.
Что умеет мод:
- Рисовать свой интерфейс. Панель сбоку или над полем ввода, полосу прямо над строкой ввода, всплывающие уведомления. В панели работают кнопки, вкладки и поля ввода.
- Перерисовывать интерфейс Claude Code. Например, дописать свой текст к спиннеру или заменить строку вызова инструмента.
- Вмешиваться в вызов инструмента. Остановить команду, переписать её, спросить вас перед запуском или ответить вместо инструмента.
- Добавлять свои команды с косой чертой. Такая команда сразу выполняет ваш код, без хода Claude, даже пока Claude работает.
- Работать с контекстом и моделью. Дописывать к вашим сообщениям текст для Claude, считать токены каждого запроса, вызывать модель и запускать работу по таймеру.
Часть самого Claude Code уже сделана модами. Например, команда /diff с панелью изменений и скилл plugin-authoring, по которому Claude пишет моды.
Чем мод отличается от хуков, скиллов и MCP
- Хук из настроек: скрипт или HTTP-запрос на событие. Может разрешить или запретить вызов инструмента, но рисовать не умеет. Подходит, если у вас уже есть готовый скрипт.
- Скилл: файл
SKILL.mdс инструкциями. Меняет то, что Claude знает и делает, а не сам Claude Code. - MCP-сервер: отдельная программа, которая даёт Claude новые инструменты, например доступ к базе данных или трекеру задач.
- Мод: код внутри Claude Code. Единственный из четырёх умеет рисовать в интерфейсе и перехватывать любое событие.
Всё это можно положить в один плагин: мод, скилл и MCP-сервер прекрасно уживаются вместе. Если вы ещё не пользовались Claude Code, начните со статьи как установить Claude Code, а про агентов и инструменты простыми словами есть разбор ИИ-агентов.
- Claude Code 2.1.287 или новее в терминале. В приложении Claude на вкладке Code моды работают с версии 2.1.286.
- Подписка Claude Pro или Max либо ключ API: без них Claude Code не запустится. Как оформить подписку из России, я разобрал в статье как оплатить Claude.
- Больше ничего. Node.js, npm и сборщик не нужны: Claude Code сам загружает файлы
.jsи.ts.
Проверьте версию в терминале:
claude --versionЕсли версия старше, обновитесь командой claude update. Моды включены по умолчанию, отдельно ничего включать не нужно.
Самый простой путь, код писать не придётся. В Claude Code встроен скилл plugin-authoring: в нём сказано, куда класть файлы мода, какие события и методы есть именно в вашей версии и как мод загружается. Claude подключает скилл сам, когда вы просите мод. Можно и вручную: наберите /plugin-authoring.
Шаг 1. Опишите мод своими словами
Откройте Claude Code в папке проекта и напишите, что хотите получить. Я попросил:
сделай мод, который показывает текущую ветку git над строкой вводаClaude загрузил скилл, разобрался с API модов и начал писать файлы в служебную папку сессии ~/.claude/dev-mods/<номер сессии>/git-branch-band/. Папка ~/.claude защищена, поэтому в обычном режиме разрешений Claude Code спросит про каждый файл. Это нормально, подтверждайте.
Шаг 2. Разрешите горячую перезагрузку
Как только Claude сохраняет первый файл мода, Claude Code спрашивает, включить ли горячую перезагрузку для этой сессии.
- Enable for this session: моды из папки сессии загрузятся, когда закончится ход, и будут перезагружаться после каждой правки. Ответ действует до конца сессии, в том числе после её возобновления.
- Not now: файлы останутся на месте, а мод загрузится при следующем запуске этой сессии. Чтобы мод не загрузился никогда, удалите его папку.
Шаг 3. Проверьте результат
У меня Claude работал минуту с небольшим: написал мод на TypeScript, прогнал проверку claude plugin validate, проверку типов и тесты. Когда ход закончился, в ленте появилась строка «git-branch-band: loaded», а над полем ввода зелёным загорелась ветка main.
Мод сразу живой. Я попросил Claude создать ветку feature/landing и переключиться на неё, и строка над полем ввода сменилась.
Проверить, что мод загрузился, можно командой /plugin: на вкладке Installed серой строкой написано, сколько модов активно и как они называются. Там же мод можно выключить.
Шаг 4. Доработайте мод словами
Дальше мод меняется обычными сообщениями. Claude Code сам подсказал следующую идею в поле ввода, и я попросил: «добавь ещё индикатор незакоммиченных изменений: сколько файлов изменено». Claude поправил код, дописал тесты, и мод перезагрузился сам в конце ответа. Потом я попросил сделать заготовку лендинга, и рядом с веткой появился жёлтый счётчик изменённых файлов.
Весь мод получился на 87 строк. Если интересно заглянуть внутрь, вот главная часть, которая рисует полосу. Хук слушает событие ui.render для места AbovePrompt, то есть для полосы над полем ввода, и возвращает дерево элементов:
on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
const current = await read($, branch)
// Нет ветки или над полем ввода опрос: ничего не рисуем
if (e.props.hasSurvey || current === null) return next(e)
const { Box, Text } = $.ui.resolve(e)
return (
<Box>
<Text dimColor>⎇ </Text>
<Text color={current.isDetached ? 'yellow' : 'green'}>{current.name}</Text>
{current.changed > 0
? <Text color="yellow">{' '}±{current.changed} files changed</Text>
: <Text dimColor> ✓ clean</Text>}
</Box>
)
})Как сохранить мод, чтобы он не пропал
Мод, который написал Claude, загружается только в той сессии, где его сделали. А папки старых сессий Claude Code со временем удаляет (срок задаёт настройка cleanupPeriodDays). Поэтому готовый мод скопируйте в свою папку:
cp -R ~/.claude/dev-mods/<номер сессии>/git-branch-band ~/mods/И запускайте Claude Code с флагом --plugin-dir:
claude --plugin-dir ~/mods/git-branch-bandЧтобы мод загружался всегда и без флага, его ставят как плагин из маркетплейса. Как это сделать, расскажу ниже.
Если хочется понимать, как мод устроен, соберите его сами. Это пример из официальной документации, я только перевёл тексты на русский: мод считает, сколько раз Claude вызвал инструменты, показывает счёт рядом со спиннером и добавляет команду /tally. Понадобятся три файла:
first-mod/
├── .claude-plugin/
│ └── plugin.json
└── hooks/
├── hooks.json
└── register.jsplugin.json это паспорт плагина, hooks.json говорит, где лежит код, а register.js и есть сам мод. Создайте папки:
mkdir -p first-mod/.claude-plugin first-mod/hooksНа Windows в PowerShell: New-Item -ItemType Directory -Force first-mod\.claude-plugin, first-mod\hooks.
Файл plugin.json
{
"name": "first-mod",
"version": "0.1.0",
"description": "Считает вызовы инструментов, показывает счёт у спиннера и добавляет команду /tally",
"author": { "name": "Артём Исламов" }
}Файл hooks.json
Именно ключ modules превращает обычный плагин в мод. Путь указывается относительно hooks.json.
{
"modules": ["./register.js"]
}Файл register.js
// Счётчик, общий для всех хуков ниже
let calls = 0
// Claude Code вызывает эту функцию один раз, когда загружает мод
export function register(on) {
// Сессия стартовала: добавляем команду /tally
on('session.start', async ($, e, next) => {
await $.command.register({
name: 'tally',
description: 'Сколько инструментов вызвал Claude',
})
return next(e)
})
// Claude собирается вызвать инструмент: считаем и просим перерисовать экран
on('tool.call', async ($, e, next) => {
calls += 1
$.ui.invalidate('ui.render')
return next(e)
})
// Вы набрали /tally: печатаем счёт в ленту
on('command.run', { command: 'tally' }, async () => {
return { text: 'Вызовов инструментов с загрузки мода: ' + calls }
})
// Claude Code рисует спиннер: дописываем счёт после слова
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
return next({ ...e, props: { ...e.props, suffix: ' · инструментов: ' + calls + '…' } })
})
}Когда мод загружается, Claude Code вызывает функцию register и передаёт ей функцию on. Каждый вызов on подписывает обработчик (в документации его называют хуком) на событие. Здесь их четыре:
session.startсрабатывает при старте сессии и после каждой перезагрузки мода. Регистрирует команду/tally.tool.callсрабатывает, когда Claude собирается вызвать инструмент. Прибавляет единицу и просит перерисовать экран.command.runс фильтром{ command: 'tally' }срабатывает только на/tallyи возвращает текст для ленты.ui.renderс фильтром{ component: 'Spinner' }срабатывает, когда Claude Code рисует спиннер, и дописывает к нему счёт.
Запустите мод
Запустите Claude Code из папки, где лежит first-mod, с флагом --plugin-dir. Флаг подключает папку как плагин на одну сессию, ничего не устанавливая:
claude --plugin-dir ./first-modПопросите Claude сделать что-нибудь в несколько шагов, например показать файлы в папке и прочитать README. Пока он работает, рядом со словом спиннера растёт счётчик.
Наберите /tally, и мод напечатает счёт. Имя плагина перед текстом Claude Code ставит сам. Проверить команду можно и без интерактивной сессии: claude -p "/tally" --plugin-dir ./first-mod.
Горячая перезагрузка
Папку, подключённую через --plugin-dir, Claude Code отслеживает. Не закрывая сессию, я переписал в register.js неуклюжую фразу ответа /tally и сохранил файл. В ленте сразу появилась строка «first-mod: reloaded» со списком хуков, а /tally ответил уже по-новому.
Обратите внимание на ноль. Каждая перезагрузка заново вызывает register, и обычные переменные сбрасываются. Чтобы значение пережило перезагрузку, храните его в $.state: там оно живёт до конца сессии, а интерфейс сам перерисовывается при изменении. Для того, что нужно помнить между сессиями, есть $.store: это хранилище «ключ-значение» в файле плагина.
Как устроен хук
Каждый хук получает три аргумента:
$: API модов. Через него мод рисует, добавляет команды, запускает процессы, читает файлы и ходит в сеть. Другого пути наружу у мода нет, поэтому Claude Code может заранее показать, что мод делает.e: само событие, например имя инструмента и его аргументы. Менять его нельзя, только передавать изменённую копию.next: передаёт событие дальше, другим модам и в итоге самому Claude Code.
От того, что хук делает с next, зависит его роль:
- Наблюдать: сделать своё и вернуть
next(e). Так считает вызовы хукtool.call. - Переписать: передать в
nextизменённую копию события. Так хук спиннера дописывает текст. - Ответить самому: вернуть результат и не вызывать
next. Тогда стандартное поведение не выполнится, как у команды/tallyили у охранника из следующего раздела.
Самый практичный мод из моих. Claude иногда решает почистить папку командой rm -rf, а в авто-режиме такая команда может пройти без вопросов. Этот мод ловит опасные команды и спрашивает вас: rm -rf, git reset --hard и git push --force. Файлы plugin.json и hooks.json такие же, как в прошлом примере, только с именем rm-guard. Код:
// Команды, перед которыми мод спросит разрешения
const RISKY = /\brm\s+-rf?\b|\bgit\s+reset\s+--hard\b|\bgit\s+push\b.*--force/
export function register(on) {
on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
// Обычные команды пропускаем без вопросов
if (!RISKY.test(e.command)) return next(e)
// По умолчанию отказ: если никто не ответит, команда не запустится
let answer = 'Отменить'
try {
answer = await $.ui.ask('Опасная команда: ' + e.command + '. Запускать?', ['Запустить', 'Отменить'])
} catch {}
if (answer !== 'Запустить') {
return { deny: 'Пользователь отменил команду. Спроси его, прежде чем пробовать другой способ.' }
}
return next(e)
}).catch(async () => {
// Если хук упал, команду не запускаем
return { deny: 'Проверка опасных команд дала сбой, команда не запущена.' }
})
}Фильтр { tool: 'Bash' } запускает хук только для команд терминала. Метод $.ui.ask показывает вопрос и ждёт ответа, а команда всё это время стоит на паузе. Если вернуть объект с полем deny и не вызывать next, команда не запустится, а Claude прочитает текст отказа как результат. А .catch в конце нужен на случай, если хук упадёт: без него Claude Code пропустил бы сломанный хук и выполнил команду.
Я запустил Claude Code с этим модом и попросил удалить папку build:
Я выбрал «Отменить». Команда не запустилась, а Claude прочитал отказ и не стал искать обходной путь.
Это напоминание, а не защита
Мод сравнивает текст команды с шаблоном, поэтому git push -f или удаление через скрипт на Python он пропустит. От настоящих потерь защищают резервные копии, права доступа и защищённые ветки на стороне Git-хостинга.
Моды умеют рисовать полноценный интерфейс. Этот мод добавляет команду /notes: она открывает панель с полем ввода и списком заметок. Заметки сохраняются в $.store, поэтому переживают перезапуск Claude Code.
const PANE = 'notes'
// Список заметок, который рисует панель
let notes = []
export function register(on) {
on('session.start', async ($, e, next) => {
await $.command.register({ name: 'notes', description: 'Открыть панель заметок' })
// Заметки прошлых сессий лежат в $.store
const saved = await $.store.get('notes')
if (Array.isArray(saved)) notes = saved
return next(e)
})
// /notes открывает панель
on('command.run', { command: 'notes' }, async ($) => {
await $.ui.open({ id: PANE, title: 'Заметки', focus: true, closeOnEscape: true })
return {}
})
// Что нарисовать в панели
on('ui.render', { component: 'Pane' }, async ($, e, next) => {
if (e.requestId !== PANE) return next(e)
const { Box, Text, Button, Input } = $.ui.resolve(e)
const redraw = () => $.ui.invalidate('ui.render')
return Box({
flexDirection: 'column',
children: [
Input({
key: 'new-note',
label: 'Заметка',
placeholder: 'Напишите и нажмите Enter',
value: '',
submitLabel: 'добавить',
autoFocus: true,
onSubmit: async (value) => {
if (!value.trim()) return
notes = [...notes, value.trim()]
redraw()
await $.store.set('notes', notes)
},
}),
...notes.map((note, i) =>
Box({
flexDirection: 'row',
columnGap: 1,
children: [
Button({
key: 'delete-' + i,
label: 'x',
plain: true,
onPress: async () => {
notes = notes.filter((_, j) => j !== i)
redraw()
await $.store.set('notes', notes)
},
}),
Text({ children: [note] }),
],
}),
),
],
})
})
}Панель появляется после вызова $.ui.open, но сама ничего не рисует: Claude Code каждый раз спрашивает у хука ui.render, что в ней показать. Хук собирает ответ из элементов Box, Text, Button и Input, похоже на вёрстку. Нажали кнопку, мод поменял список и попросил перерисовку через $.ui.invalidate.
В узком окне терминала панель открывается над полем ввода:
Если окно широкое, та же панель встаёт сбоку от ленты:
Управление с клавиатуры: Tab переходит между кнопками, Enter нажимает кнопку, Ctrl+X и стрелка меняют размер панели, Esc возвращает фокус в поле ввода. Важная деталь: панель, которую мод открывает сам, без вашей команды, появится только в окне шириной от 144 колонок, чтобы не занять маленький экран.
Перед тем как пользоваться модом или делиться им, прогоните две команды:
claude plugin validate ./папка-модапроверяет манифест и читает код, не запуская его. В строкеhooksвидно, на какие события подписан мод, в строкеcallsвидно, что он просит у Claude Code.claude plugin test(из папки мода) запускает тесты мода без сессии, входа и интернета.
Тест лежит в папке tests и вызывает события вместо Claude Code. Пример для счётчика из второго способа:
import { expect, test } from 'claude-code/testing'
test('/tally считает вызовы инструментов', async ($, on) => {
// Отвечаем на вызовы инструментов сами, чтобы ничего не запускалось
on('tool.call', () => ({ result: 'ok' }))
await $.tool.call({ tool: 'Bash', command: 'ls' })
await $.tool.call({ tool: 'Read', file_path: 'README.md' })
const answer = await $.command.run({ command: 'tally', args: '' })
expect(answer.text).toBe('Вызовов инструментов с загрузки мода: 2')
})Ещё полезная мелочь: при каждой загрузке Claude Code кладёт в папку мода .claude-plugin/types/ с описанием всех событий и методов вашей версии. Редактор по ним подсказывает код. Если документация и эти файлы расходятся, верьте файлам: API модов пока меняется от версии к версии.
Мод это обычный плагин Claude Code, поэтому и делятся им как плагином:
- Паре человек: отправьте папку или zip-архив, пусть запускают с
--plugin-dir. - Команде: заведите свой маркетплейс. Это репозиторий с файлом
.claude-plugin/marketplace.jsonи папкой на каждый плагин. Его можно прописать в настройках репозитория, чтобы он подключался у всех, кто там работает. - Всем: сделайте репозиторий-маркетплейс публичным или отправьте плагин в каталог Anthropic.
Чужой мод из маркетплейса ставится двумя командами в Claude Code:
/plugin marketplace add владелец/репозиторий
/plugin install имя-мода@имя-маркетплейсаЕсли ставили из терминала, пока сессия открыта, выполните в ней /reload-plugins. Перед публикацией проверьте имя: validate не пропустит имя, похожее на официальное, например начинающееся с claude-. И напишите в README, на какой версии Claude Code мод проверен.
Готовые примеры от Anthropic лежат в репозитории claude-code-playground: token-weather рисует над полем ввода «прогноз погоды» для контекстного окна, blast-radius задерживает рискованные команды и показывает, что они изменят, replay-theater по команде /replay пошагово проигрывает правки Claude за последний ход. Исходники встроенных модов, включая /diff, открыты в папке mods репозитория Claude Code.
Мод не изолирован и работает с вашими правами. Он может читать и менять любые ваши файлы, запускать программы, ходить в интернет, читать переменные окружения вместе с ключами, видеть все ваши сообщения, одобрять вызовы инструментов без вопроса и тратить ваши лимиты на вызовы модели. Песочница Claude Code защищает только команды, которые запускает Claude, а процессы мода работают вне её.
Ставьте моды только от тех, кому доверяете
Перед установкой скачайте папку мода и выполните claude plugin validate ./папка-мода: строки hooks и calls покажут, на какие события он подписан и что делает, без запуска кода. Окно разрешений мод изменить не может, это единственное место интерфейса, которое ему недоступно.
Как выключить моды:
- Один мод: в
/pluginна вкладке Installed. - Все установленные моды на одну сессию: запустите Claude Code с флагом
--safe-mode. - Все и навсегда: добавьте
"disableAllHooks": trueв~/.claude/settings.json. Остановятся и обычные хуки, и своя строка состояния.
Встроенные моды самого Claude Code эти настройки не трогают.
- Терминал, включая встроенный терминал редактора и плагин JetBrains: работает всё.
- Приложение Claude, вкладка Code: работает всё, кроме нескольких элементов только для терминала. В сессиях WSL плагинов нет совсем.
- Расширение VS Code: хуки срабатывают, но панели и полосы не рисуются.
claude -pи Agent SDK: хуки срабатывают, рисовать негде.- Облачные сессии и Remote Control: хуки срабатывают, а интерфейс мода виден только в терминале на вашем компьютере.
- Проверьте версию:
claude --version, нужна 2.1.287 или новее. - Откройте
/plugin: если мода нет в строке «mods active», он не загрузился. - Прогоните
claude plugin validate: опечатка в имени события даст ошибку вроде"tool.calls" is not an event. - Мод, написанный Claude, не загрузится в папке без доверия (вы не ответили на вопрос о доверии при первом запуске), в режиме
claude -p, с флагами--safe-modeи--bareи с настройкойdisableAllHooks. - Панель пустая: ищите в ленте строку
ui.render (Pane) refused, в ней сказано, какое свойство элемента не подошло. - Пишите код так, чтобы его понимала проверка: имя события строкой прямо в вызове
on, методы API целиком, например$.store.get('notes'), без сохранения$.uiв переменную. Иначе проверка не увидит хук или вызов.
Моды только появились, и API ещё будет меняться. Но уже сейчас за вечер можно собрать себе Claude Code под свой проект: с нужными кнопками, подсказками и страховкой от неприятных команд. Начните с просьбы к Claude, а в код заглядывайте, когда захочется большего.
Частые вопросы
Что такое мод в Claude Code?
Это плагин с кодом на JavaScript или TypeScript, который работает внутри Claude Code. Мод может рисовать свои панели и строки, добавлять команды, менять или останавливать вызовы инструментов. Моды появились в Claude Code 2.1.287 в октябре 2026 года.
Нужно ли уметь программировать, чтобы сделать мод?
Нет. Достаточно написать в Claude Code, какой мод нужен, например «сделай мод, который показывает ветку git над строкой ввода». Claude напишет код, проверит его и тесты, а мод загрузится после вашего согласия на горячую перезагрузку.
Чем мод отличается от хука в settings.json?
Хук из настроек запускает внешний скрипт или HTTP-запрос и умеет разрешать или запрещать вызовы инструментов. Мод работает внутри Claude Code, хранит состояние между событиями и умеет рисовать интерфейс: панели, полосу над полем ввода, свой текст у спиннера.
Куда Claude сохраняет мод и почему он пропал?
В папку ~/.claude/dev-mods/<номер сессии>/. Такой мод работает только в своей сессии, а старые папки Claude Code удаляет. Скопируйте мод в свою папку и запускайте claude --plugin-dir путь-к-моду или установите его из маркетплейса.
Работают ли моды в VS Code и в приложении Claude?
В приложении Claude на вкладке Code моды работают полностью, кроме нескольких элементов только для терминала. В расширении VS Code хуки срабатывают, но панели и полосы не рисуются.
Безопасно ли ставить чужие моды?
Мод работает с вашими правами: читает файлы, запускает программы, видит ваши сообщения и ключи в переменных окружения. Ставьте моды только от тех, кому доверяете, и перед установкой смотрите claude plugin validate: он покажет, что мод делает, не запуская код.
Сколько стоят моды?
Отдельно ничего. Моды входят в Claude Code, а он работает по подписке Claude Pro от $20 в месяц, Max или с ключом API.
