SpyBara
Go Premium

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

This page contains 75 additions and 75 deletions.

2026
Thu 1 23:59 Fri 2 22:59

mods API 사용하기

Claude Code mod에서 mods API를 호출하여 명령과 도구를 추가하고, 모델을 호출하고, 타이머로 작업을 실행하고, 다른 세션에 메시지를 보내고, 파일과 네트워크에 접근합니다.

mods API는 mod가 동작을 수행하기 위해 호출하는 메서드 집합으로, 명령과 도구를 추가하고, 모델을 호출하고, 이벤트 사이에 작업을 실행하고, 파일 시스템, 프로세스, 네트워크에 접근하는 데 사용됩니다. 모든 훅은 이 API를 첫 번째 인수인 $로 받으며, 메서드는 $.ui 및 $.fs와 같은 네임스페이스로 그룹화되어 있습니다. 이벤트는 훅이 실행되는 시점을 결정하고, mods API는 훅이 실행된 후 호출하는 대상입니다.

여기서 시작하기 전에 첫 번째 mod를 먼저 만들어 보십시오. 모든 메서드에 대해서는 mods API 메서드를 참조하거나 사용 중인 빌드의 타입을 확인하십시오.

명령 또는 도구 추가하기

mod는 사용자가 실행할 명령과 Claude가 호출할 도구를 추가할 수 있습니다. 둘 다 session.start 훅에서 등록합니다. Claude Code는 첫 번째 프롬프트 전에 이 훅이 완료되기를 기다리므로, 등록한 항목은 첫 번째 턴부터 사용할 수 있습니다.

명령 추가하기

명령은 사용자를 위한 것입니다. 명령을 등록한 다음, 해당 이름에 대한 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을 실행하면 두 번째 훅이 Summary for the last 3 day(s): ...를 반환하고, 트랜스크립트에는 플러그인 이름 뒤에 이 텍스트가 표시됩니다. 이 명령에는 직접 정의한 동작 외에 다른 동작이 없으므로 훅은 next를 호출하지 않습니다.

반환한 text는 트랜스크립트에 출력되며 Claude가 이를 읽습니다. pane만 여는 명령처럼 아무것도 출력하지 않으려면 {}를 반환합니다. Claude가 작업 중일 때도 명령을 실행할 수 있게 하려면 등록 시 immediate: true를 추가합니다.

기본 제공 명령이 사용하지 않는 이름을 선택하십시오. 세션에서 /를 입력하면 기본 제공 명령을 확인할 수 있습니다. 이미 사용 중인 이름이면 $.command.register는 "/focus" refused: it is the built-in /focus와 같은 메시지와 함께 예외를 발생시킵니다. 예외를 발생시킨 훅은 건너뛰어지므로 session.start 훅의 나머지 부분도 실행되지 않습니다. 명령은 해당 훅의 마지막에 등록하거나, 호출을 try와 catch로 감싸십시오.

도구 추가하기

도구는 Claude를 위한 것입니다. 이름, Claude가 읽는 설명, 입력에 대한 JSON Schema와 함께 도구를 등록합니다. Claude에게는 mcp__, 플러그인 이름, 밑줄 두 개, 등록한 이름으로 구성된 더 긴 이름으로 표시됩니다. 도구 호출은 이 전체 이름으로 필터링한 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는 티켓 id와 함께 mcp__my-mod__ticket을 호출할 수 있습니다. 두 번째 훅은 티켓을 가져와 응답 본문을 반환하며, Claude는 이를 도구의 결과로 읽습니다. 서버가 오류 상태로 응답하면 Claude는 Lookup failed with status와 상태 번호를 읽습니다.

모델 호출하기

mod는 대화와 별개로 모델에 자체적인 질문을 보내 텍스트를 분류하거나 요약하는 등의 작은 작업을 수행할 수 있습니다. $.model.complete는 사용자 세션의 자격 증명으로 모델에 프롬프트 하나를 보내고 그 응답으로 resolve됩니다. 대화 기록은 포함되지 않습니다.

다음 훅은 명령으로 등록된 /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 오류가 발생해도 호출이 reject되지는 않으므로 r.isAnswered를 확인하고, 값이 false이면 r.reason을 읽어야 합니다. 조직에서 차단한 모델처럼 Claude Code가 보내지 않는 요청의 경우에는 호출이 reject됩니다. 사용 중인 빌드의 타입에는 effort와 같은 다른 옵션이 나열되어 있으며, 제한에서 maxTokens 기본값을 확인할 수 있습니다.

$.model.fork({ prompt })는 대신 현재 대화를 바탕으로 동일한 모델과 시스템 프롬프트를 사용해 질문 하나를 보내므로, Claude API가 대부분을 프롬프트 캐시에서 처리합니다.

이러한 호출에는 사용자의 플랜 또는 API 키가 사용됩니다.

백그라운드에서 작업 실행하기

1분마다 무언가를 확인하는 것처럼 하나의 이벤트보다 오래 지속되는 작업은 session.start에서 시작하는 타이머로 실행됩니다. 훅 자체는 하나의 이벤트에 대해 실행되며, 자체 실행 시간에 시간 제한이 있습니다. next 또는 mod API 호출을 기다리는 데 소요된 시간은 $.clock.sleep을 제외하고 포함되지 않습니다. $.clock.every와 $.clock.after는 setInterval과 setTimeout을 대신하며, 지연 시간(밀리초)을 첫 번째 인수로 받습니다. $.clock.after(5000, fn)은 지금부터 5초 후에 fn을 한 번 호출합니다. 각각 cancel() 메서드가 있는 타이머를 반환하며, await $.clock.now()는 현재 시간을 밀리초 단위로 제공합니다.

다음 훅은 1분마다 풀 리퀘스트의 검사 결과를 조회하여 프롬프트 아래에 표시합니다. 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)
})

세션은 평소처럼 시작됩니다. 1분 후 프롬프트 아래에 ⚠, mod 이름, 그리고 checks:와 요약이 담긴 줄이 나타납니다. 이후 이 줄은 1분마다 교체됩니다. 타이머의 콜백은 어떤 이벤트에도 속하지 않고 실행되므로, 턴 사이에도 계속 실행되며 턴을 시작하지 않습니다. 콜백에서 오류가 발생하면 오류는 디버그 로그에 기록되고, 타이머는 다음 간격에 다시 실행됩니다.

턴을 시작하지 않고 내용 표시하기

백그라운드 작업은 턴을 시작하지 않고도 사용자에게 내용을 표시할 수 있습니다. 다음 각 호출은 텍스트를 서로 다른 위치에 표시합니다.

호출 사용자에게 표시되는 내용
$.ui.status(text) 변경할 때까지 유지되는 프롬프트 아래의 한 줄입니다. ⚠ my-mod: checks: 3 passing처럼 ⚠와 mod 이름으로 시작합니다.
$.ui.toast(text) 오른쪽 상단에 표시되는 토스트 알림으로, 텍스트 위에 mod 이름이 표시되며 몇 초 후 사라집니다
$.ui.log(text) Claude가 읽지 않는 트랜스크립트의 흐린 줄입니다. ● my-mod: build finished처럼 ●와 mod 이름으로 시작합니다.

백그라운드 작업에서 턴 시작하기

백그라운드 작업이 Claude의 주의가 필요한 사항을 발견하면 $.prompt.submit({ text })로 프롬프트를 제출하여 턴을 시작할 수 있습니다. Claude는 해당 mod를 발신자로 명시하는 문장 뒤에 이어지는 텍스트를 읽습니다. 그 문장 없이 사용자 자신의 말로 보내려면 asUser: true를 추가합니다. 이 호출은 세션이 유휴 상태가 될 때까지 기다린 후 새 턴을 시작합니다. 해당 턴이 시작될 때 resolve되므로, Claude가 작업하는 동안 실행되는 핸들러에서는 이를 await하지 마십시오.

백그라운드 작업 중지하기

타이머는 모듈이 다시 로드될 때 중지됩니다. 훅 내부의 장기 실행 작업의 경우, next.signal은 사용자가 중단하는 경우처럼 훅이 처리 중인 이벤트가 중단될 때 abort되는 AbortSignal이므로, 장기 실행되는 모든 작업에 이를 전달하십시오.

세션 간 메시지 보내기 및 받기

mod는 사용자의 다른 세션이나 이 세션의 서브에이전트 중 하나에 일반 텍스트 메시지를 보낼 수 있으며, 도착하고 나가는 메시지를 관찰할 수 있습니다. $.session.send({ to, text })는 메시지 하나를 보내며, SendMessage 도구와 동일한 방식으로 전달합니다. to는 세션의 경우 { sessionId }, $.agent.list()에서 가져온 서브에이전트의 경우 { agentId }, 또는 수신된 메시지의 발신 문자열 주소입니다. 이 호출은 메시지가 대기열에 추가되면 { isDelivered: true }로 resolve됩니다. 아무것도 전달되지 않은 경우 { isDelivered: false, reason }으로 resolve되며, reason에 그 이유가 담깁니다.

다음 훅은 명령으로 등록된 /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, 그리고 다른 세션이나 에이전트의 경우 peer 또는 peer-send-message, task-notification, scheduled-trigger 등의 e.origin.kind. Claude에게 전달되지 않도록 하려면 { consumed: reason }을 반환합니다.
session.send SendMessage 도구나 mod에서 메시지가 나가려고 할 때 e.to, e.text, 그리고 model 또는 plugin인 e.origin.kind

수신 메시지를 거부하도록 설정된 세션은 session.receive가 발생하기 전에 메시지를 거부하므로 훅에서는 해당 메시지를 볼 수 없습니다. 사용자의 승인을 기다리는 메시지는 먼저 훅에 도달하므로, mod는 아직 승인하지 않은 메시지를 읽을 수 있습니다. 메시지가 전달되지 않으면 훅의 next(e)는 reject됩니다.

수신된 메시지의 발신자 이름은 발신자가 작성한 그대로이므로, 이를 근거로 결정을 내리지 마십시오.

파일, 프로세스, 네트워크에 접근하기

mod는 mods API를 통해 파일 시스템, 프로세스, 네트워크에 접근하며, Claude Code를 실행하는 사용자와 동일한 권한을 가집니다. 훅 모듈 자체에는 Node.js API, setTimeout 같은 타이머 전역 객체가 없으며, 자체적인 네트워크 또는 파일 접근 기능도 없습니다. URL, TextEncoder, AbortController, crypto.subtle 같은 표준 JavaScript 및 웹 API는 사용할 수 있습니다. 아래의 각 네임스페이스는 한 가지 종류의 접근을 담당합니다.

네임스페이스 기능
$.fs read(path), write(path, text), exists(path), stat(path), list(path)로 파일과 디렉터리를 다룹니다
$.process run(['git', 'status'])는 명령을 시작하고 명령이 종료되면 resolve됩니다. spawn은 장시간 실행되는 명령의 출력을 스트리밍합니다.
$.http http 또는 https를 통한 fetch(url, init)입니다. 본문을 읽고 나면 { status, ok, headers, text }로 resolve됩니다.
$.store 플러그인 전용 JSON 키-값 저장소로, 세션 간에 유지됩니다
$.env 환경 변수를 get 및 set합니다. 이름은 문자열 리터럴로 작성합니다.
$.settings 설정 파일과 관리형 정책에 담긴 내용을 read합니다
$.session messages()는 트랜스크립트를 { role, text, toolUses }의 목록으로 반환합니다. 작업 디렉터리, 모델 등도 제공합니다. usage()는 컨텍스트 윈도우 사용량과 플랜 한도를 반환합니다.
$.mcp 연결된 MCP 서버의 도구를 call합니다

파일과 프로세스에는 몇 가지 고유한 규칙이 있습니다.

  • 경로: 상대 경로는 세션의 작업 디렉터리를 기준으로 해석됩니다
  • $.fs.list: 한 디렉터리의 항목을 { name, kind, size, isLink } 형태로 반환하며, 재귀적으로 동작하지 않습니다
  • $.process.run: 인수 목록을 받으며 셸을 사용하지 않습니다. 종료 코드와 관계없이 { exitCode, stdout, stderr }로 resolve됩니다. 프로그램을 시작할 수 없거나 타임아웃(기본값 30초) 시점에 여전히 실행 중이면 reject되므로 try와 catch로 감싸야 합니다.

이러한 호출 각각은 그 자체로 하나의 이벤트이며, $.를 제외한 네임스페이스와 메서드 이름으로 명명됩니다. 예를 들어 $.fs.read의 이벤트는 fs.read입니다. 체인의 앞쪽에 있는 mod는 사용자의 호출을 관찰하거나, 재작성하거나, 거부할 수 있으며, 조직은 이 방식으로 mod가 접근할 수 있는 범위를 제한합니다.

다음 단계