SpyBara
Go Premium

plugins/mods/api.md 2026-09-30 23:00 UTC to 2026-10-01 23:02 UTC

This page contains 218 additions and 0 deletions.

2026
Thu 1 23:02

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

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

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 и номер.

Вызов модели

Mod может задать модели вопрос самостоятельно, вне разговора, для небольшой работы, такой как сортировка или суммирование текста. $.model.complete отправляет один запрос модели с учётными данными вашего сеанса и разрешается в ответ. У неё нет истории разговора.

Этот 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, mod отправляет этот текст модели и печатает её ответ, например Label: bug. Разговор Claude не является частью запроса. Когда модель не отвечает, метка — unknown.

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

$.model.fork({ prompt }) вместо этого задаёт один вопрос по текущему разговору с той же моделью и системным запросом, поэтому Claude API обслуживает большую часть из кэша запросов.

Эти вызовы используют план пользователя или ключ API.

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

Работа, которая переживает одно событие, например проверка чего-либо раз в минуту, выполняется на таймере, который вы запускаете из session.start. Сам hook выполняется для одного события и имеет ограничение по времени в 10 секунд собственного времени выполнения. Время, потраченное на ожидание 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) Небольшой прямоугольник в верхнем правом углу с именем mod над текстом, который исчезает через несколько секунд
$.ui.log(text) Тусклая строка в стенограмме, которую Claude не читает. Она начинается с ● и имени mod, как в ● my-mod: build finished.

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

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

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

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

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

Mod может отправить простое текстовое сообщение другому вашему сеансу или одному из подагентов этого сеанса и наблюдать сообщения, которые приходят и уходят. $.session.send({ to, text }) отправляет один, такую же доставку, которую делает инструмент SendMessage. to — это { sessionId } для сеанса, { agentId } для подагента из $.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. Когда ничего не было доставлено, небольшой прямоугольник в верхнем правом углу даёт причину и исчезает через несколько секунд.

Два события позволяют 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 отклоняет, когда сообщение не доставляется.

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

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

Mod получает доступ к файловой системе, процессам и сети через mods API с теми же разрешениями, что и пользователь, запускающий Claude Code. Сам модуль hooks не имеет 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. Mod ранее в цепи может наблюдать, переписывать или отказывать ваш вызов, что является тем, как организация ограничивает то, что mods достигают.

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