SpyBara
Go Premium

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

This page contains 218 additions and 0 deletions.

2026
Thu 1 21:02

mods API 사용하기

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

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

여기서 시작하기 전에 첫 번째 mod를 빌드하세요. 모든 메서드에 대해 mods API 메서드를 참조하거나 빌드용 타입을 읽으세요.

명령어 또는 도구 추가하기

mod는 사용자가 실행할 명령어와 Claude가 호출할 도구를 추가할 수 있습니다. 둘 다 session.start hook에 등록하세요. Claude Code는 첫 번째 프롬프트 전에 해당 hook을 기다리므로, 등록한 것은 첫 번째 턴부터 사용 가능합니다.

명령어 추가하기

명령어는 사용자용입니다. 등록한 후 해당 이름에 대해 command.run을 처리하세요. 이 예제는 선택적 일 수를 받는 /standup 명령어를 추가합니다:

on('session.start', async ($, e, next) => {
  // /standup을 명령어 목록에 추가하고, 사용자가 볼 수 있는 설명을 포함합니다
  await $.command.register({ name: 'standup', description: 'Summarize what changed today', argumentHint: '[days]' })
  return next(e)
})

// matcher는 hook을 /standup으로 제한하므로 다른 명령어는 도달하지 않습니다
on('command.run', { command: 'standup' }, async ($, e) => {
  // e.args는 명령어 이름 뒤에 입력된 텍스트이거나 빈 문자열입니다
  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와 같은 메시지와 함께 throw합니다. throw하는 hook은 건너뛰어지므로 해당 session.start hook의 나머지 부분도 실행되지 않습니다. 해당 hook에서 마지막에 명령어를 등록하거나 호출을 try와 catch로 래핑하세요.

도구 추가하기

도구는 Claude용입니다. 이름, Claude가 읽는 설명, 입력을 위한 JSON Schema로 등록하세요. Claude는 mcp__, 플러그인 이름, 두 개의 언더스코어, 등록한 이름으로 구성된 더 긴 이름 아래에서 이를 봅니다. tool.call hook에서 해당 전체 이름으로 필터링된 호출을 처리합니다. 이 예제는 my-mod라는 플러그인에서 ticket을 등록하므로 전체 이름은 mcp__my-mod__ticket입니다. Claude에게 이슈 추적기에서 티켓을 조회하는 도구를 제공합니다:

on('session.start', async ($, e, next) => {
  await $.tool.register({
    name: 'ticket',
    // Claude는 이 설명에서 도구를 호출할 시점을 결정합니다
    description: 'Look up a ticket by its id and return its title and status',
    // Claude가 보내야 하는 인수: id라는 필수 문자열 하나
    inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
  })
  return next(e)
})

// 전체 도구 이름은 mcp__, 플러그인 이름, 등록한 이름입니다
on('tool.call', { tool: 'mcp__my-mod__ticket' }, async ($, e) => {
  // 도구의 인수는 e의 필드이므로 id는 e.id입니다
  const response = await $.http.fetch('https://tickets.example.com/api/' + encodeURIComponent(e.id))
  // 어느 쪽이든 결과를 반환하므로 Claude는 조회가 실패했을 때를 알 수 있습니다
  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은 command.run으로 등록된 /triage 명령어에 답하여 작은 모델에 그 뒤에 입력된 텍스트에 레이블을 지정하도록 요청합니다:

on('command.run', { command: 'triage' }, async ($, e) => {
  const r = await $.model.complete({
    model: 'haiku',
    // 시스템 프롬프트는 작업을 설정하고, 프롬프트는 레이블을 지정할 텍스트를 전달합니다
    system: 'Reply with one word: bug, feature, or question.',
    prompt: e.args,
    // 한 단어는 적은 토큰이 필요하며, 호출은 15초 후 포기합니다
    maxTokens: 20,
    timeoutMs: 15000,
  })
  // r.text는 모델이 답변했을 때만 존재하므로 먼저 r.isAnswered를 확인하세요
  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를 확인하고, false일 때 r.reason을 읽으세요. 호출은 Claude Code가 보내지 않을 요청(예: 조직이 차단한 모델)에 대해서만 거부합니다. 빌드용 타입은 effort와 같은 다른 옵션을 나열하고, 제한은 maxTokens 기본값을 제공합니다.

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

이러한 호출은 사용자의 플랜 또는 API 키를 사용합니다.

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

한 이벤트를 초과하는 작업(예: 1분마다 무언가를 확인)은 session.start에서 시작하는 타이머에서 실행됩니다. hook 자체는 하나의 이벤트에 대해 실행되며 자체 실행 시간 제한은 10초입니다. next 또는 mods API 호출에 소비된 시간은 계산되지 않습니다. $.clock.sleep 제외. $.clock.every 및 $.clock.after는 setInterval 및 setTimeout을 대신하며, 지연은 밀리초 단위입니다: $.clock.after(5000, fn)은 지금부터 5초 후에 fn을 한 번 호출합니다. 각각은 cancel() 메서드가 있는 타이머를 반환하고, await $.clock.now()는 밀리초 단위의 시간을 제공합니다.

이 hook은 1분마다 pull request의 확인을 조회하고 프롬프트 아래에 결과를 표시합니다. summarize는 명령어의 JSON 출력을 몇 단어로 변환하는 자신의 함수입니다:

on('session.start', async ($, e, next) => {
  // 60,000밀리초마다 함수를 호출하며, 지금부터 1분 후에 시작합니다
  $.clock.every(60_000, async () => {
    const status = await $.process.run(['gh', 'pr', 'checks', '--json', 'state'])
    // 프롬프트 아래의 줄을 최신 요약으로 바꿉니다
    $.ui.status('checks: ' + summarize(status.stdout))
  })
  // 타이머를 기다리지 않고 반환하므로 세션이 바로 시작됩니다
  return next(e)
})

세션은 평소대로 시작됩니다. 1분 후, 프롬프트 아래에 ⚠, mod의 이름, 그리고 checks:와 요약이 있는 줄이 나타납니다. 그 후 1분마다 교체됩니다. 타이머의 콜백은 모든 이벤트 외부에서 실행되므로 턴 사이에 계속 실행되고 턴을 시작하지 않습니다. 콜백이 throw하면, 오류는 디버그 로그로 이동하고 타이머는 다음 간격에 다시 실행됩니다.

턴을 시작하지 않고 무언가 표시하기

백그라운드 작업은 턴을 시작하지 않고 사용자에게 무언가를 표시할 수 있습니다. 이러한 각 호출은 다른 위치에 텍스트를 배치합니다:

호출 사용자가 보는 것
$.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를 추가하세요. 호출은 세션이 유휴 상태가 될 때까지 기다린 후 새 턴을 시작합니다. 해당 턴이 시작될 때 해결되므로 Claude가 작업 중일 때 실행되는 핸들러에서 await하지 마세요.

백그라운드 작업 중지하기

백그라운드 작업은 두 가지 방법으로 중지됩니다. 모듈이 다시 로드되면 타이머가 중지됩니다. hook 내의 장기 실행 작업의 경우, next.signal은 hook이 처리하는 이벤트가 중단될 때(예: 사용자가 중단할 때) 중단되는 AbortSignal이므로 장기 실행 항목에 전달하세요.

세션 간 메시지 보내고 받기

mod는 다른 세션 또는 이 세션의 subagent 중 하나에 일반 텍스트 메시지를 보낼 수 있으며, 도착하고 떠나는 메시지를 관찰할 수 있습니다. $.session.send({ to, text })는 하나를 보내며, SendMessage 도구가 만드는 것과 동일한 전달입니다. to는 세션의 경우 { sessionId }, $.agent.list()의 subagent의 경우 { agentId }, 또는 수신한 메시지가 온 문자열 주소입니다. 호출은 메시지가 큐에 들어가면 { isDelivered: true }로 해결됩니다. 아무것도 전달되지 않으면 { isDelivered: false, reason }으로 해결되며, reason은 이유를 설명합니다.

이 hook은 command.run으로 등록된 /ping 명령어에 답하여 그 뒤에 입력한 id의 세션에 상태를 요청합니다:

on('command.run', { command: 'ping' }, async ($, e) => {
  // e.args는 /ping 뒤에 입력된 세션 id입니다
  const sent = await $.session.send({ to: { sessionId: e.args }, text: 'Status? One line.' })
  // 호출은 어느 쪽이든 해결되므로 isDelivered를 확인하여 무슨 일이 일어났는지 알아봅니다
  if (!sent.isDelivered) $.ui.toast('Not delivered: ' + sent.reason)
  // 빈 결과는 이 세션의 트랜스크립트에 아무것도 인쇄하지 않습니다
  return {}
})

메시지가 큐에 들어가면 세션에 아무것도 나타나지 않으며, 다른 세션의 Claude는 Status? One line.을 읽습니다. 아무것도 전달되지 않으면, 오른쪽 상단의 작은 상자가 이유를 제공하고 몇 초 후 사라집니다.

두 이벤트를 통해 mod는 메시지를 관찰할 수 있습니다. 두 이벤트 모두에서 next(e)를 반환하여 각 메시지를 변경되지 않은 상태로 전달합니다:

이벤트 발생 시점 유용한 필드
session.receive 메시지가 이 세션에 도착하며, Claude가 읽기 전입니다 e.text, 및 e.origin.kind(예: 다른 세션 또는 agent의 경우 peer 또는 peer-send-message, task-notification, 또는 scheduled-trigger). Claude에서 메시지를 유지하려면 { consumed: reason }을 반환합니다.
session.send 메시지가 SendMessage 도구 또는 mod에서 떠나려고 합니다 e.to, e.text, 및 e.origin.kind(이는 model 또는 plugin입니다)

인바운드 메시지를 거부하도록 설정된 세션은 session.receive가 발생하기 전에 메시지를 거부하므로 hook은 이를 보지 않습니다. 승인을 위해 보류 중인 메시지는 먼저 hook에 도달하므로 mod는 아직 승인하지 않은 메시지를 읽을 수 있습니다. hook의 next(e)는 메시지가 전달되지 않으면 거부합니다.

수신한 메시지의 발신자 이름은 발신자가 작성한 것이므로 이를 기반으로 결정하지 마세요.

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

mod는 Claude Code를 실행하는 사용자와 동일한 권한으로 파일 시스템, 프로세스 및 네트워크에 접근합니다. hooks 모듈 자체는 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'])는 명령어를 시작하고 종료될 때 해결됩니다. spawn은 장기 실행 명령어의 출력을 스트리밍합니다.
$.http http 또는 https를 통한 fetch(url, init). 본문이 읽혀지면 { status, ok, headers, text }로 해결됩니다.
$.store 플러그인 자신의 JSON 키-값 저장소이며, 세션 간에 유지됩니다
$.env 환경 변수를 get 및 set합니다. 이름을 리터럴 문자열로 작성하세요.
$.settings 설정 파일 및 관리 정책이 보유한 것을 read합니다
$.session messages()는 트랜스크립트를 { role, text, toolUses } 목록으로 반환합니다. 또한 작업 디렉토리, 모델 등입니다. usage()는 컨텍스트 윈도우 사용 및 플랜 제한을 반환합니다.
$.mcp 연결된 MCP 서버에서 도구를 call합니다

파일 및 프로세스에는 자체 규칙이 몇 가지 있습니다:

  • 경로: 상대 경로는 세션의 작업 디렉토리 아래에 있습니다
  • $.fs.list: 한 디렉토리의 항목을 { name, kind, size, isLink }로 반환하며 하위 디렉토리로 내려가지 않습니다
  • $.process.run: 인수 목록을 받으며 shell을 사용하지 않습니다. 종료 코드에 관계없이 { exitCode, stdout, stderr }로 해결됩니다. 프로그램을 시작할 수 없거나 기본값인 30초의 타임아웃에서 여전히 실행 중이면 거부하므로 try와 catch로 래핑하세요.

이러한 호출 각각은 그 자체로 이벤트이며, $.fs.read의 경우 fs.read와 같이 $. 없이 네임스페이스 및 메서드로 이름이 지정됩니다. 체인의 앞에 있는 mod는 호출을 관찰, 다시 작성 또는 거부할 수 있으며, 이것이 조직이 mod가 도달하는 것을 제한하는 방법입니다.

다음 단계