Знать, из чего состоит плагин, и собрать работающий плагин — разные умения. На практике первый плагин почти всегда собирается со второго-третьего раза: то компонент оказался не в той папке, то путь в хуке сломался после установки, то скилл после обновления начал называться странным набором цифр.
Все эти проблемы имеют конкретные причины, и все они описаны в документации. Пути внутри плагина подчиняются жёстким правилам, у путей есть специальная переменная, а у скиллов — правило именования, которое проявляется только после публикации.
После этого урока вы соберёте свой плагин с нуля: создадите каталог, напишете манифест, добавите скилл, подагента и хук — и будете понимать, почему каждый файл лежит именно там, где лежит.
Что уже умеем / что добавим
В прошлом уроке мы разобрали идею плагина и общую схему каталога. Теперь спускаемся на уровень файлов: соберём плагин пошагово, разберём поля манифеста, которые управляют путями компонентов, познакомимся с переменной ${CLAUDE_PLUGIN_ROOT} и поймём, почему у скилла плагина стоит явно задавать name во frontmatter.
Что вы узнаете в рамках статьи
- Как создать плагин с нуля командой
claude plugin initили вручную - Какие поля манифеста задают нестандартные пути к компонентам
- Зачем нужна переменная
${CLAUDE_PLUGIN_ROOT}и где её обязательно использовать - Почему у скилла плагина стоит задавать поле
nameво frontmatter - Как добавить в плагин подагента и хук
Ключевые термины
${CLAUDE_PLUGIN_ROOT}— переменная, раскрывающаяся в абсолютный путь к каталогу установки плагина.${CLAUDE_PLUGIN_DATA}— каталог для данных плагина, который переживает обновления, в отличие от корня плагина.claude plugin init— команда, создающая заготовку плагина в каталоге скиллов.- Frontmatter — YAML-блок в начале файла
SKILL.mdили файла подагента с метаданными. - Skills-directory плагин — плагин, живущий прямо в
~/.claude/skills/и загружающийся автоматически, с идентификатором видаmy-tool@skills-dir.
Собираем плагин руками
Начнём с ручной сборки — так виднее структура. Создаём каталог плагина и папку для манифеста:
mkdir -p my-first-plugin/.claude-plugin
mkdir -p my-first-plugin/skills/hello
Пишем манифест в my-first-plugin/.claude-plugin/plugin.json:
{
"name": "my-first-plugin",
"description": "Учебный плагин с приветственным скиллом",
"version": "1.0.0",
"author": {
"name": "Ваше имя"
}
}
Теперь скилл в my-first-plugin/skills/hello/SKILL.md:
---
name: hello
description: Приветствует пользователя и уточняет, чем помочь
---
Поприветствуйте пользователя по имени "$ARGUMENTS" и спросите,
с чем помочь сегодня. Сделайте приветствие личным и коротким.
Всё, плагин готов. Запускаем Claude Code с этим плагином и проверяем:
claude --plugin-dir ./my-first-plugin
Внутри сессии вызываем скилл — с префиксом, как мы разбирали в прошлом уроке:
/my-first-plugin:hello Алекс
Claude поздоровается по имени. Плейсхолдер $ARGUMENTS подставляет текст, который вы написали после имени скилла.
Если вы правите файлы плагина, не нужно перезапускать сессию: команда /reload-plugins перечитает плагины, скиллы, подагентов, хуки, а также MCP- и LSP-серверы плагинов.
Заготовка одной командой
Ручная сборка полезна, чтобы понять структуру, но каждый раз повторять mkdir не обязательно. Есть команда, которая создаёт заготовку:
claude plugin init my-helper
Она создаёт ~/.claude/skills/my-helper/ с манифестом .claude-plugin/plugin.json и стартовым SKILL.md. Такой плагин особенный: он лежит в каталоге скиллов и загружается автоматически со следующей сессии, под идентификатором my-helper@skills-dir. Ни marketplace, ни установка, ни флаг --plugin-dir не нужны — очень удобно для разработки.
Можно сразу попросить заготовки под нужные компоненты:
claude plugin init my-helper --with skills hooks
Флаг --with принимает значения skills, agents, hooks, mcp, lsp, output-style, channel. Отключается такой плагин так же, как любой другой:
claude plugin disable my-helper@skills-dir
Пути внутри плагина: правило, которое ломает сборки
Все пути в манифесте отсчитываются от корня плагина и должны начинаться с ./. Путь, выходящий за корень, например ../shared-utils, вызывает ошибку path escapes plugin directory, и компонент просто выбрасывается из плагина.
Причина в способе установки: плагин из marketplace копируется в кэш ~/.claude/plugins/cache, а соседние каталоги при этом не копируются. Локально ссылка ../ работала бы, после установки — уже нет.
Если стандартные папки вас не устраивают, пути задаются в манифесте:
{
"name": "release-tools",
"version": "1.2.0",
"skills": "./custom/skills/",
"agents": ["./custom/agents/reviewer.md"],
"hooks": "./config/hooks.json"
}
Здесь есть важная асимметрия. Поле skills добавляется к стандартному сканированию папки skills/ — то есть скиллы будут взяты и оттуда, и отсюда. А поля commands, agents и outputStyles заменяют стандартные папки: если вы задали agents, папка agents/ больше не сканируется. Это частый источник исчезнувших подагентов.
Переменная ${CLAUDE_PLUGIN_ROOT}
Хуки и MCP-серверы плагина запускают скрипты. Но где эти скрипты лежат? Абсолютный путь на вашей машине бесполезен: у пользователя плагин окажется в кэше по совсем другому пути.
Для этого есть переменная ${CLAUDE_PLUGIN_ROOT} — она раскрывается в абсолютный путь каталога установки плагина. Вот хук в hooks/hooks.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}\"/scripts/format-code.sh"
}
]
}
]
}
}
Обратите внимание на кавычки вокруг переменной: путь установки может содержать пробелы, и без кавычек команда развалится.
Та же переменная работает в .mcp.json, в полях command, args и env:
{
"mcpServers": {
"plugin-database": {
"command": "${CLAUDE_PLUGIN_ROOT}/servers/db-server",
"args": ["--config", "${CLAUDE_PLUGIN_ROOT}/config.json"]
}
}
}
И одно предупреждение из документации: ${CLAUDE_PLUGIN_ROOT} меняется при каждом обновлении плагина, поэтому никогда не храните там состояние. Для данных, которые должны пережить обновление, есть отдельная переменная ${CLAUDE_PLUGIN_DATA}.
Подагент в плагине и коварство имён
Подагент плагина — обычный markdown-файл с frontmatter в папке agents/:
---
name: security-reviewer
description: Проверяет изменения на типовые уязвимости. Используйте при ревью кода, затрагивающего аутентификацию или работу с вводом.
model: sonnet
disallowedTools: Write, Edit
---
Проверяйте изменения по списку: валидация входных данных,
экранирование при работе с БД, обработка ошибок аутентификации.
Такой подагент будет виден как my-plugin:security-reviewer. Из соображений безопасности во frontmatter подагента плагина не поддерживаются поля hooks, mcpServers и permissionMode — плагин не может незаметно расширить себе права.
Теперь про поле name у скилла, и это тот самый подвох, который проявляется только после публикации. Имя вызова скилла берётся из frontmatter name, а если его нет — из имени каталога. Для плагина, установленного из marketplace, имя каталога — это строка версии, которая меняется при каждом обновлении. То есть без явного name команда вашего скилла будет переименовываться после каждого апдейта. Вывод простой: в скиллах плагина всегда задавайте name явно.
Частые ошибки
- Ссылаться на файлы за пределами плагина через
../. Плагин копируется в кэш отдельно от соседних каталогов, поэтому такой путь даёт ошибкуpath escapes plugin directoryи компонент выбрасывается. Всё нужное держите внутри корня плагина. - Задать
agentsв манифесте и потерять папкуagents/. Поляcommands,agents,outputStylesзаменяют стандартные каталоги, а не дополняют их. Дополняет только полеskills. - Прописать в хуке абсолютный путь со своей машины. У пользователя плагин лежит в кэше по другому пути. Используйте
${CLAUDE_PLUGIN_ROOT}и обязательно берите его в кавычки. - Не задать
nameво frontmatter скилла плагина. Тогда имя берётся из каталога, а у установленного плагина это строка версии — команда будет меняться при каждом обновлении.
Наиболее частые вопросы
- Нужно ли перезапускать Claude Code после правки плагина? — Нет, достаточно
/reload-plugins. Она перечитывает плагины, скиллы, подагентов, хуки, а также MCP- и LSP-серверы плагинов. - Можно ли загрузить сразу несколько плагинов для теста? — Да, повторите флаг:
claude --plugin-dir ./plugin-one --plugin-dir ./plugin-two. - Где хранить состояние плагина между запусками? — В
${CLAUDE_PLUGIN_DATA}. Каталог${CLAUDE_PLUGIN_ROOT}меняется при обновлении, и всё, что там лежало, теряется. - Чем
claude plugin initотличается от ручной сборки? — Он создаёт плагин в~/.claude/skills/, откуда тот загружается автоматически как<name>@skills-dir, без-plugin-dirи без установки. - Может ли плагин ограничить инструменты своего подагента? — Да, полями
toolsиdisallowedToolsво frontmatter. А вотpermissionMode,hooksиmcpServersв подагенте плагина запрещены из соображений безопасности.
Итоги
Поздравляем: вы прошли путь от пустой папки до работающего плагина со скиллом, подагентом и хуком. Это уже не теория — это собранный артефакт, который можно отдать коллеге. По дороге вы разобрали то, на чём спотыкается большинство: правило ./ для путей, разницу между дополняющим полем skills и заменяющими agents и commands, переменную ${CLAUDE_PLUGIN_ROOT} для скриптов и необходимость явного name у скиллов.
Заметьте, как много ошибок здесь тихие: компонент не загрузился, но никто об этом не сообщил. Именно поэтому следующий урок посвящён проверке — мы разберём, как тестировать плагин локально и как команда claude plugin validate находит проблемы до того, как их найдут пользователи.