$ sudo teach IT

Знать, из чего состоит плагин, и собрать работающий плагин — разные умения. На практике первый плагин почти всегда собирается со второго-третьего раза: то компонент оказался не в той папке, то путь в хуке сломался после установки, то скилл после обновления начал называться странным набором цифр.

Все эти проблемы имеют конкретные причины, и все они описаны в документации. Пути внутри плагина подчиняются жёстким правилам, у путей есть специальная переменная, а у скиллов — правило именования, которое проявляется только после публикации.

После этого урока вы соберёте свой плагин с нуля: создадите каталог, напишете манифест, добавите скилл, подагента и хук — и будете понимать, почему каждый файл лежит именно там, где лежит.

Что уже умеем / что добавим

В прошлом уроке мы разобрали идею плагина и общую схему каталога. Теперь спускаемся на уровень файлов: соберём плагин пошагово, разберём поля манифеста, которые управляют путями компонентов, познакомимся с переменной ${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 находит проблемы до того, как их найдут пользователи.

На следующий урок