SpyBara
Go Premium

plugins/mods/api.md 2026-10-08 22:58 UTC to 2026-10-09 21:01 UTC

This page contains 107 additions and 8 deletions.

2026
Thu 1 23:59 Fri 2 22:59 Thu 8 22:58 Fri 9 22:01

Использование mods API

Вызывайте mods API из мода Claude Code для добавления команд и инструментов, вызова модели, запуска работы по таймеру, отправки сообщений другим сессиям и доступа к файлам и сети.

mods API — это набор методов, которые mod вызывает для выполнения действий: добавления команд и инструментов, вызова модели, запуска работы между событиями и доступа к файловой системе, процессам и сети. Каждый hook получает её в качестве первого аргумента, $, с методами, сгруппированными в пространства имён, такие как $.ui и $.fs. События определяют, когда выполняется hook, а mods API — это то, что hook вызывает после этого.

Создайте свой первый mod перед тем, как начать здесь. Для каждого метода см. методы mods API или прочитайте типы для вашей сборки.

Добавление команды или инструмента

Mod может добавить команду для запуска пользователем и инструмент для вызова Claude. Зарегистрируйте оба в hook session.start. Claude Code ждёт этого hook перед первым запросом, поэтому то, что вы регистрируете, доступно с первого хода.

Добавление команды

Команда предназначена для пользователя. Зарегистрируйте её, затем обработайте command.run для её имени. Этот пример добавляет команду /standup, которая принимает необязательное количество дней:

on('session.start', async ($, e, next) => {
  // Add /standup to the command list, with the description the user sees there
  await $.command.register({ name: 'standup', description: 'Summarize what changed today', argumentHint: '[days]' })
  return next(e)
})

// The matcher limits the hook to /standup, so other commands don't reach it
on('command.run', { command: 'standup' }, async ($, e) => {
  // e.args is the text typed after the command name, or an empty string
  return { text: 'Summary for the last ' + (e.args || '1') + ' day(s): ...' }
})

После запуска сеанса /standup появляется с его описанием в списке, который вы видите при вводе /. argumentHint отображается в подсказке после ввода команды и пробела, как в /standup [days]. Когда вы запускаете /standup 3, второй hook возвращает Summary for the last 3 day(s): ..., и стенограмма показывает этот текст после имени плагина. Hook никогда не вызывает next, потому что команда не имеет поведения, кроме вашего.

text, который вы возвращаете, печатается в стенограмме и Claude его читает. Чтобы ничего не печатать, как команда, которая только открывает pane, верните {}. Чтобы позволить команде выполняться, пока Claude работает, добавьте immediate: true к регистрации.

Выберите имя, которое не использует ни одна встроенная команда. Введите / в сеансе, чтобы увидеть их. $.command.register выбрасывает исключение для занятого имени с сообщением, таким как "/focus" refused: it is the built-in /focus". Hook, который выбрасывает исключение, пропускается, поэтому остальная часть вашего hook session.start тоже не выполняется. Регистрируйте команды в последнюю очередь в этом hook или оберните вызов в try и catch.

Добавление инструмента

Инструмент предназначен для Claude. Зарегистрируйте его с именем, описанием, которое читает Claude, и JSON Schema для его входных данных. Claude видит его под более длинным именем, состоящим из mcp__, имени вашего плагина, двух подчёркиваний и имени, которое вы зарегистрировали. Вы обрабатываете его вызовы в hook tool.call, отфильтрованном по этому полному имени. Этот пример из плагина с именем my-mod регистрирует ticket, поэтому полное имя — mcp__my-mod__ticket. Он даёт Claude инструмент, который ищет билет в трекере проблем:

on('session.start', async ($, e, next) => {
  await $.tool.register({
    name: 'ticket',
    // Claude decides when to call the tool from this description
    description: 'Look up a ticket by its id and return its title and status',
    // The arguments Claude has to send: one required string named id
    inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
  })
  return next(e)
})

// The full tool name is mcp__, the plugin's name, and the registered name
on('tool.call', { tool: 'mcp__my-mod__ticket' }, async ($, e) => {
  // The tool's arguments are fields of e, so the id is e.id
  const response = await $.http.fetch('https://tickets.example.com/api/' + encodeURIComponent(e.id))
  // Return a result either way, so Claude learns when the lookup failed
  return { result: response.ok ? response.text : 'Lookup failed with status ' + response.status }
})

Когда вы спрашиваете о билете, Claude может вызвать mcp__my-mod__ticket с его id. Второй hook получает билет и возвращает тело ответа, которое Claude читает как результат инструмента. Когда сервер отвечает с кодом ошибки, Claude читает Lookup failed with status и номер.

Вызов модели

Мод может отправлять модели собственные запросы для небольшой работы, такой как классификация или суммирование текста. $.model.complete отправляет ваш промпт отдельно, а $.model.fork({ prompt }) отправляет текущий диалог с вашим промптом в конце.

В этой таблице сравнивается, что содержит каждый запрос:

В запросе $.model.complete $.model.fork
Модель Переданная вами model Модель сессии
Системный промпт Короткий блок атрибуции, затем ваш system, если вы его передаёте Системный промпт сессии
Сообщения Одно сообщение пользователя — ваш prompt Диалог на текущий момент, затем ваш prompt как сообщение пользователя
CLAUDE.md и другой контекст проекта Не включаются Включаются, как в последнем запросе диалога
Инструменты Нет Инструменты Claude, которые модель не может вызывать

Fork повторяет последний запрос диалога, поэтому Claude API обслуживает большую его часть из кэша промптов, пока диалог ещё находится в кэше.

Оба вызова используют учётные данные сессии, поэтому оплачиваются через план пользователя, API-ключ или облачного провайдера. Типы для вашей сборки документируют каждый метод $.model.

Отправка одного промпта

Передайте model и prompt в $.model.complete. prompt становится сообщением пользователя. Чтобы дать модели инструкции, например роль или формат вывода, передайте также system, который становится системным промптом.

Этот hook отвечает на команду /triage, зарегистрированную как команда, попросив небольшую модель пометить текст, введённый после неё:

on('command.run', { command: 'triage' }, async ($, e) => {
  const r = await $.model.complete({
    model: 'haiku',
    // The system prompt sets the job, and the prompt carries the text to label
    system: 'Reply with one word: bug, feature, or question.',
    prompt: e.args,
    // One word needs few tokens, and the call gives up after 15 seconds
    maxTokens: 20,
    timeoutMs: 15000,
  })
  // r.text exists only when the model answered, so check r.isAnswered first
  const label = r.isAnswered ? r.text.trim() : 'unknown'
  return { text: 'Label: ' + label }
})

Когда вы запускаете /triage the export button does nothing, мод отправляет этот текст модели и печатает её ответ, например Label: bug. Когда модель не отвечает, метка — unknown.

Сбой Claude API не отклоняет вызов, поэтому проверьте r.isAnswered и прочитайте r.reason, когда это false. Вызов отклоняется для запроса, который Claude Code не отправит, например для модели, которую блокирует ваша организация.

Типы для вашей сборки перечисляют другие параметры, такие как effort, а в разделе ограничения указано значение maxTokens по умолчанию.

Использование кэширования промптов

$.model.complete поддерживает кэширование промптов Claude API. API кэширует начало запроса, называемое префиксом, до заданной вами точки останова кэша. Когда каждый вызов начинается с одного и того же длинного статического содержимого, например инструкций или справочных материалов, установите точку останова в конце этого содержимого. Последующие вызовы будут читать его из кэша, а не оплачивать по полной цене входных данных.

Чтобы установить точку останова, передайте prompt как массив блоков { text } вместо строки и добавьте cache: true к последнему блоку статического содержимого. Claude Code отправляет этот блок с полем API cache_control. system принимает ту же форму массива. О том, как выбрать между ними, см. Выбор между prompt и system.

Эта версия хука /triage отправляет длинный набор правил разметки перед текстом для разметки, с точкой останова после правил. RULES — это ваша собственная строка:

on('command.run', { command: 'triage' }, async ($, e) => {
  const r = await $.model.complete({
    model: 'haiku',
    prompt: [
      // Identical on every call, so it forms the cached prefix
      { text: RULES, cache: true },
      // Changes on every call, so it goes after the breakpoint
      { text: e.args },
    ],
  })
  return { text: 'Label: ' + (r.isAnswered ? r.text.trim() : 'unknown') }
})

Для TTL и количества точек останова действуют следующие ограничения:

  • TTL: запись кэша существует пять минут после последнего использования. TTL берётся из настроек Claude Code пользователя, а не из вызова. Для одного часа установите subagentPromptCacheTtl в 1h.
  • Точки останова на запрос: API принимает до четырёх, а ещё одна возвращается как api-error в r.reason

Выбор между `prompt` и `system`

Помещайте статическое содержимое, общее для ваших вызовов, в начало prompt, если только вы не знаете, что ваши запросы идут напрямую в Claude API:

  • Напрямую в Claude API, с API-ключом или подпиской Claude: подходит любое поле
  • Через Amazon Bedrock, Claude Platform on AWS, Google Cloud's Agent Platform, Microsoft Foundry или LLM-шлюз: используйте prompt. Claude Code начинает системный промпт с блока атрибуции, отпечаток которого вычисляется из начала сообщения пользователя. Эндпоинт api.anthropic.com удаляет этот блок перед кэшированием. Другие эндпоинты получают его как часть промпта, поэтому точка останова в system может не дать попадания в кэш, если prompt начинается по-другому.
  • В моде, который запускают другие люди: используйте prompt, потому что вы не выбираете их провайдера

system идёт перед prompt в префиксе, поэтому точка останова в prompt охватывает и system, а вызов с другим system не попадает в кэш.

Проверка попаданий в кэш

Результат $.model.complete содержит объект usage с полями кэша API. usage.cache_creation_input_tokens подсчитывает токены, которые вызов записал в кэш, а usage.cache_read_input_tokens — токены, которые он прочитал из кэша. Ожидайте запись при первом вызове и чтение при последующих вызовах в пределах TTL.

Если каждый вызов записывает и ни один не читает, префикс различается между вызовами или вызовы разделены промежутком больше TTL. Если префикс различается, см. Выбор между prompt и system.

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

Что получает хук `model.complete`

Если вы подключаете хук к событию model.complete, чтобы просматривать или изменять запросы других модов, читайте текст из этих полей:

  • e.prompt: всегда строка. Когда вызывающий передал массив, это текст блоков, объединённый по порядку.
  • e.system: строка, построенная тем же образом, или отсутствует, если вызывающий не передал system
  • e.promptBlocks и e.systemBlocks: массивы вызывающего, каждый присутствует, когда вызывающий передал массив для соответствующего поля

Claude Code отправляет строки, которые ваш хук передаёт в next, и использует переданные вместе с ними массивы для размещения точек останова кэша. Он сохраняет начальные блоки, которые всё ещё совпадают с началом строки, вместе с их точками останова, а остаток строки отправляет без точки останова. Например, next({ ...e, prompt: e.prompt + NOTE }) сохраняет точки останова вызывающего, а хук, который изменяет начало prompt, удаляет их.

Запуск работы в фоне

Работа, которая переживает одно событие, например проверка чего-либо раз в минуту, выполняется на таймере, который вы запускаете из session.start. Сам хук выполняется для одного события и имеет ограничение по времени на собственное время выполнения. Время, потраченное на ожидание next или вызова mods API, не учитывается, кроме $.clock.sleep. $.clock.every и $.clock.after заменяют setInterval и setTimeout, с задержкой в миллисекундах в первую очередь: $.clock.after(5000, fn) вызывает fn один раз через пять секунд. Каждый возвращает таймер с методом cancel(), и await $.clock.now() даёт время в миллисекундах.

Этот hook ищет проверки pull request один раз в минуту и показывает результат под запросом. summarize — это функция вашего собственного, которая превращает вывод JSON команды в несколько слов:

on('session.start', async ($, e, next) => {
  // Call the function every 60,000 milliseconds, starting one minute from now
  $.clock.every(60_000, async () => {
    const status = await $.process.run(['gh', 'pr', 'checks', '--json', 'state'])
    // Replace the line under the prompt with the latest summary
    $.ui.status('checks: ' + summarize(status.stdout))
  })
  // Return without waiting for the timer, so the session starts right away
  return next(e)
})

Сеанс начинается как обычно. Через минуту под запросом появляется строка с ⚠, именем mod и затем checks: и вашей сводкой. Она заменяется один раз в минуту после этого. Обратный вызов таймера выполняется вне любого события, поэтому он продолжает работать между ходами и не запускает один. Если обратный вызов выбрасывает исключение, ошибка переходит в журнал отладки и таймер снова выполняется в следующем интервале.

Показать что-то без запуска хода

Фоновая работа может показать пользователю что-то без запуска хода. Каждый из этих вызовов помещает текст в другое место:

Вызов Что видит пользователь
$.ui.status(text) Одна строка под запросом, которая остаётся, пока вы её не измените. Она начинается с ⚠ и имени mod, как в ⚠ my-mod: checks: 3 passing.
$.ui.toast(text) Всплывающее уведомление с именем мода, которое исчезает через несколько секунд. В полноэкранном режиме отрисовки это блок в правом верхнем углу, а в классическом рендерере — одна строка справа под промптом.
$.ui.log(text) Тусклая строка в стенограмме, которую Claude не читает. Она начинается с ● и имени mod, как в ● my-mod: build finished.

Запуск хода из фоновой работы

Когда фоновая работа находит что-то, что требует внимания Claude, она может запустить ход, отправив запрос с $.prompt.submit({ text }). Claude читает текст после предложения, которое называет ваш mod как отправителя. Чтобы отправить его как собственные слова пользователя, без этого предложения, добавьте asUser: true. Вызов ждёт, пока сеанс будет неактивным, а затем запускает новый ход. Он разрешается при запуске этого хода, поэтому не await его в обработчике, который выполняется, пока Claude работает.

Остановка фоновой работы

Таймеры останавливаются при перезагрузке модуля. Для долгоживущей работы внутри хука next.signal — это AbortSignal, который прерывается, когда событие, которое обрабатывает ваш хук, отменяется, например когда пользователь прерывает, поэтому передавайте его всему, что работает долго.

Отправка и получение сообщений между сеансами

Мод может отправить простое текстовое сообщение другой вашей сессии, одному из субагентов этой сессии или участнику её команды агентов. Он также может наблюдать за сообщениями, которые приходят и уходят.

Чтобы отправить сообщение, вызовите $.session.send({ to, text }): он выполняет такую же доставку, как инструмент SendMessage. Задайте to в зависимости от получателя сообщения:

  • Другая ваша сессия: { sessionId }
  • Субагент или участник команды: { agentId } с id из $.agent.list()
  • Отправитель полученного вами сообщения: строковый адрес, с которого пришло это сообщение

Вызов разрешается после того, как сообщение поставлено в очередь, с { isDelivered: true }. Если ничего не было доставлено, он разрешается с { isDelivered: false, reason }, и reason сообщает причину.

Этот hook отвечает на команду /ping, зарегистрированную как команда, попросив сеанс, чей id вы вводите после неё, получить статус:

on('command.run', { command: 'ping' }, async ($, e) => {
  // e.args is the session id typed after /ping
  const sent = await $.session.send({ to: { sessionId: e.args }, text: 'Status? One line.' })
  // The call resolves either way, so check isDelivered to learn what happened
  if (!sent.isDelivered) $.ui.toast('Not delivered: ' + sent.reason)
  // An empty result prints nothing in this session's transcript
  return {}
})

Когда сообщение поставлено в очередь, в вашей сессии ничего не появляется, и Claude другой сессии читает Status? One line. Когда ничего не было доставлено, всплывающее уведомление сообщает причину.

session.receive и session.send позволяют mod наблюдать сообщения. Верните next(e) из обоих, чтобы пропустить каждое сообщение без изменений:

Событие Срабатывает когда Полезные поля
session.receive Сообщение поступает для этого сеанса, перед тем как Claude его прочитает e.text и e.origin.kind, такие как peer или peer-send-message для другого сеанса или агента, task-notification или scheduled-trigger. Верните { consumed: reason }, чтобы помешать Claude.
session.send Сообщение вот-вот уйдёт, из инструмента SendMessage или mod e.to, e.text и e.origin.kind, который является model или plugin

Сеанс, установленный на отказ входящих сообщений, отказывает сообщение перед срабатыванием session.receive, поэтому hook никогда его не видит. Сообщение, которое удерживается для вашего одобрения, сначала достигает hook, поэтому mod может прочитать сообщение, которое вы ещё не одобрили. next(e) hook отклоняет, когда сообщение не доставляется.

Имя отправителя в полученном сообщении — это то, что написал отправитель, поэтому не основывайте решение на нём.

Доступ к файлам, процессам и сети

Мод получает доступ к файловой системе, процессам и сети через API модов с теми же разрешениями, что и пользователь, запускающий Claude Code. Сам модуль хуков не имеет API Node.js, нет глобальных таймеров, таких как setTimeout, и нет собственного доступа к сети или файлам. Доступны стандартный JavaScript и веб-API, такие как URL, TextEncoder, AbortController и crypto.subtle. Каждое пространство имён ниже охватывает один вид доступа:

Пространство имён Что оно делает
$.fs read(path), write(path, text), exists(path), stat(path) и list(path) работают с файлами и каталогами
$.process run(['git', 'status']) запускает команду и разрешается при выходе. spawn потоком выводит долгоживущую команду.
$.http fetch(url, init) по http или https. Он разрешается в { status, ok, headers, text } после прочтения тела.
$.store Хранилище JSON ключ-значение вашего собственного плагина, сохраняемое между сессиями
$.env get и set переменные окружения. Напишите имя как буквальную строку.
$.settings read то, что содержат файлы настроек и управляемая политика
$.session messages() возвращает транскрипт как список { role, text, toolUses }. Также рабочий каталог, модель и многое другое. usage() возвращает использование контекстного окна и ограничения плана.
$.mcp call инструмент на подключённом MCP-сервере

Файлы и процессы имеют несколько собственных правил:

  • Пути: относительный путь разрешается относительно рабочего каталога сессии
  • $.fs.list: возвращает записи одного каталога как { name, kind, size, isLink } и не спускается в подкаталоги
  • $.process.run: принимает список аргументов и не использует оболочку. Он разрешается в { exitCode, stdout, stderr } независимо от кода выхода. Он отклоняется, если программа не может запуститься или всё ещё выполняется при истечении времени ожидания, которое по умолчанию составляет 30 секунд, поэтому оберните его в try и catch.

Каждый из этих вызовов сам по себе является событием, названным по его пространству имён и методу без $., например fs.read для $.fs.read. Мод ранее в цепи может наблюдать, переписывать или отклонять ваш вызов — именно так организация ограничивает то, к чему моды получают доступ.

Мод может отклонить ваш вызов $.process.spawn уже после того, как команда выдала вывод или завершилась, и ничего из сделанного командой не отменяется. В этом случае вызов отклоняется с сообщением, которое заканчивается одной из этих строк и причиной, указанной отклонившим модом:

  • $.process.spawn started, and a plugin withheld its result:: отклонивший мод не дочитал вывод команды до конца. Claude Code останавливает команду, если она всё ещё выполняется.
  • $.process.spawn ran, and a plugin withheld its result:: отклонивший мод дочитал вывод команды до конца, поэтому команда уже завершилась

Следующие шаги