4 4
5# mods API 사용하기5# mods API 사용하기
6 6
7> Claude Code mod에서 mods API를 호출하여 명령어와 도구를 추가하고, 모델을 호출하고, 타이머에서 작업을 실행하고, 다른 세션에 메시지를 보내고, 파일 및 네트워크에 접근합니다.7> Claude Code mod에서 mods API를 호출하여 명령과 도구를 추가하고, 모델을 호출하고, 타이머로 작업을 실행하고, 다른 세션에 메시지를 보내고, 파일과 네트워크에 접근합니다.
8 8
9mods API는 mod가 작동하기 위해 호출하는 메서드의 집합입니다. 명령어와 도구를 추가하고, 모델을 호출하고, 이벤트 사이에 작업을 실행하고, 파일 시스템, 프로세스 및 네트워크에 접근합니다. 모든 hook은 첫 번째 인수인 `$`로 이를 받으며, 메서드는 `$.ui` 및 `$.fs`와 같은 네임스페이스로 그룹화됩니다. [이벤트](/docs/ko/plugins/mods/events)는 hook이 실행되는 시점을 결정하며, mods API는 hook이 실행되면 호출하는 것입니다.9mods API는 mod가 동작을 수행하기 위해 호출하는 메서드 집합으로, 명령과 도구를 추가하고, 모델을 호출하고, 이벤트 사이에 작업을 실행하고, 파일 시스템, 프로세스, 네트워크에 접근하는 데 사용됩니다. 모든 훅은 이 API를 첫 번째 인수인 `$`로 받으며, 메서드는 `$.ui` 및 `$.fs`와 같은 네임스페이스로 그룹화되어 있습니다. [이벤트](/docs/ko/plugins/mods/events)는 훅이 실행되는 시점을 결정하고, mods API는 훅이 실행된 후 호출하는 대상입니다.
10 10
11여기서 시작하기 전에 [첫 번째 mod를 빌드](/docs/ko/plugins/mods/create)하세요. 모든 메서드에 대해 [mods API 메서드](/docs/ko/plugins/mods/reference#mods-api-methods)를 참조하거나 [빌드용 타입](/docs/ko/plugins/mods/create#get-the-types-for-your-build)을 읽으세요.11여기서 시작하기 전에 [첫 번째 mod](/docs/ko/plugins/mods/create)를 먼저 만들어 보십시오. 모든 메서드에 대해서는 [mods API 메서드](/docs/ko/plugins/mods/reference#mods-api-methods)를 참조하거나 [사용 중인 빌드의 타입](/docs/ko/plugins/mods/create#get-the-types-for-your-build)을 확인하십시오.
12 12
13<h2 id="add-a-command-or-a-tool">13<h2 id="add-a-command-or-a-tool">
14 명령어 또는 도구 추가하기14 명령 또는 도구 추가하기
15</h2>15</h2>
16 16
17mod는 사용자가 실행할 명령어와 Claude가 호출할 도구를 추가할 수 있습니다. 둘 다 [`session.start`](/docs/ko/plugins/mods/reference#session) hook에 등록하세요. Claude Code는 첫 번째 프롬프트 전에 해당 hook을 기다리므로, 등록한 것은 첫 번째 턴부터 사용 가능합니다.17mod는 사용자가 실행할 명령과 Claude가 호출할 도구를 추가할 수 있습니다. 둘 다 [`session.start`](/docs/ko/plugins/mods/reference#session) 훅에서 등록합니다. Claude Code는 첫 번째 프롬프트 전에 이 훅이 완료되기를 기다리므로, 등록한 항목은 첫 번째 턴부터 사용할 수 있습니다.
18 18
19<h3 id="add-a-command">19<h3 id="add-a-command">
20 명령어 추가하기20 명령 추가하기
21</h3>21</h3>
22 22
23명령어는 사용자용입니다. 등록한 후 해당 이름에 대해 [`command.run`](/docs/ko/plugins/mods/reference#commands-and-configuration)을 처리하세요. 이 예제는 선택적 일 수를 받는 `/standup` 명령어를 추가합니다:23명령은 사용자를 위한 것입니다. 명령을 등록한 다음, 해당 이름에 대한 [`command.run`](/docs/ko/plugins/mods/reference#commands-and-configuration)을 처리합니다. 다음 예제는 선택적으로 일수를 받는 `/standup` 명령을 추가합니다.
24 24
25```javascript theme={null}25```javascript theme={null}
26on('session.start', async ($, e, next) => {26on('session.start', async ($, e, next) => {
27 // /standup을 명령어 목록에 추가하고, 사용자가 볼 수 있는 설명을 포함합니다27 // Add /standup to the command list, with the description the user sees there
28 await $.command.register({ name: 'standup', description: 'Summarize what changed today', argumentHint: '[days]' })28 await $.command.register({ name: 'standup', description: 'Summarize what changed today', argumentHint: '[days]' })
29 return next(e)29 return next(e)
30})30})
31 31
32// matcher는 hook을 /standup으로 제한하므로 다른 명령어는 도달하지 않습니다32// The matcher limits the hook to /standup, so other commands don't reach it
33on('command.run', { command: 'standup' }, async ($, e) => {33on('command.run', { command: 'standup' }, async ($, e) => {
34 // e.args는 명령어 이름 뒤에 입력된 텍스트이거나 빈 문자열입니다34 // e.args is the text typed after the command name, or an empty string
35 return { text: 'Summary for the last ' + (e.args || '1') + ' day(s): ...' }35 return { text: 'Summary for the last ' + (e.args || '1') + ' day(s): ...' }
36})36})
37```37```
38 38
39세션이 시작된 후, `/standup`은 설명과 함께 `/`를 입력할 때 보이는 목록에 나타납니다. `argumentHint`는 명령어를 입력한 후 공백을 입력할 때 프롬프트 뒤에 표시되며, `/standup [days]`와 같이 표시됩니다. `/standup 3`을 실행하면, 두 번째 hook은 `Summary for the last 3 day(s): ...`을 반환하고, 트랜스크립트는 플러그인 이름 뒤에 해당 텍스트를 표시합니다. hook은 `next`를 호출하지 않습니다. 명령어는 당신의 동작 외에 다른 동작이 없기 때문입니다.39세션이 시작되면 `/`를 입력했을 때 표시되는 목록에 `/standup`이 설명과 함께 나타납니다. `argumentHint`는 명령과 공백을 입력한 후 프롬프트에 `/standup [days]`처럼 표시됩니다. `/standup 3`을 실행하면 두 번째 훅이 `Summary for the last 3 day(s): ...`를 반환하고, 트랜스크립트에는 플러그인 이름 뒤에 이 텍스트가 표시됩니다. 이 명령에는 직접 정의한 동작 외에 다른 동작이 없으므로 훅은 `next`를 호출하지 않습니다.
40 40
41반환하는 `text`는 트랜스크립트에 인쇄되고 Claude가 읽습니다. 아무것도 인쇄하지 않으려면, [pane](/docs/ko/plugins/mods/interface#pick-where-to-draw)만 여는 명령어의 경우 `{}`를 반환하세요. Claude가 작업 중일 때 명령어를 실행하도록 하려면 등록에 `immediate: true`를 추가하세요.41반환한 `text`는 트랜스크립트에 출력되며 Claude가 이를 읽습니다. [pane](/docs/ko/plugins/mods/interface#pick-where-to-draw)만 여는 명령처럼 아무것도 출력하지 않으려면 `{}`를 반환합니다. Claude가 작업 중일 때도 명령을 실행할 수 있게 하려면 등록 시 `immediate: true`를 추가합니다.
42 42
43내장 명령어가 사용하지 않는 이름을 선택하세요. 세션에서 `/`를 입력하여 확인하세요. `$.command.register`는 사용 중인 이름에 대해 `"/focus" refused: it is the built-in /focus`와 같은 메시지와 함께 throw합니다. throw하는 hook은 건너뛰어지므로 해당 `session.start` hook의 나머지 부분도 실행되지 않습니다. 해당 hook에서 마지막에 명령어를 등록하거나 호출을 `try`와 `catch`로 래핑하세요.43기본 제공 명령이 사용하지 않는 이름을 선택하십시오. 세션에서 `/`를 입력하면 기본 제공 명령을 확인할 수 있습니다. 이미 사용 중인 이름이면 `$.command.register`는 `"/focus" refused: it is the built-in /focus`와 같은 메시지와 함께 예외를 발생시킵니다. 예외를 발생시킨 훅은 건너뛰어지므로 `session.start` 훅의 나머지 부분도 실행되지 않습니다. 명령은 해당 훅의 마지막에 등록하거나, 호출을 `try`와 `catch`로 감싸십시오.
44 44
45<h3 id="add-a-tool">45<h3 id="add-a-tool">
46 도구 추가하기46 도구 추가하기
47</h3>47</h3>
48 48
49도구는 Claude용입니다. 이름, Claude가 읽는 설명, 입력을 위한 JSON Schema로 등록하세요. Claude는 `mcp__`, 플러그인 이름, 두 개의 언더스코어, 등록한 이름으로 구성된 더 긴 이름 아래에서 이를 봅니다. [`tool.call`](/docs/ko/plugins/mods/events#guard-or-change-a-tool-call) hook에서 해당 전체 이름으로 필터링된 호출을 처리합니다. 이 예제는 `my-mod`라는 플러그인에서 `ticket`을 등록하므로 전체 이름은 `mcp__my-mod__ticket`입니다. Claude에게 이슈 추적기에서 티켓을 조회하는 도구를 제공합니다:49도구는 Claude를 위한 것입니다. 이름, Claude가 읽는 설명, 입력에 대한 JSON Schema와 함께 도구를 등록합니다. Claude에게는 `mcp__`, 플러그인 이름, 밑줄 두 개, 등록한 이름으로 구성된 더 긴 이름으로 표시됩니다. 도구 호출은 이 전체 이름으로 필터링한 [`tool.call`](/docs/ko/plugins/mods/events#guard-or-change-a-tool-call) 훅에서 처리합니다. `my-mod`라는 플러그인의 다음 예제는 `ticket`을 등록하므로 전체 이름은 `mcp__my-mod__ticket`이 됩니다. 이 예제는 이슈 트래커에서 티켓을 조회하는 도구를 Claude에게 제공합니다.
50 50
51```javascript theme={null}51```javascript theme={null}
52on('session.start', async ($, e, next) => {52on('session.start', async ($, e, next) => {
53 await $.tool.register({53 await $.tool.register({
54 name: 'ticket',54 name: 'ticket',
55 // Claude는 이 설명에서 도구를 호출할 시점을 결정합니다55 // Claude decides when to call the tool from this description
56 description: 'Look up a ticket by its id and return its title and status',56 description: 'Look up a ticket by its id and return its title and status',
57 // Claude가 보내야 하는 인수: id라는 필수 문자열 하나57 // The arguments Claude has to send: one required string named id
58 inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },58 inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
59 })59 })
60 return next(e)60 return next(e)
61})61})
62 62
63// 전체 도구 이름은 mcp__, 플러그인 이름, 등록한 이름입니다63// The full tool name is mcp__, the plugin's name, and the registered name
64on('tool.call', { tool: 'mcp__my-mod__ticket' }, async ($, e) => {64on('tool.call', { tool: 'mcp__my-mod__ticket' }, async ($, e) => {
65 // 도구의 인수는 e의 필드이므로 id는 e.id입니다65 // The tool's arguments are fields of e, so the id is e.id
66 const response = await $.http.fetch('https://tickets.example.com/api/' + encodeURIComponent(e.id))66 const response = await $.http.fetch('https://tickets.example.com/api/' + encodeURIComponent(e.id))
67 // 어느 쪽이든 결과를 반환하므로 Claude는 조회가 실패했을 때를 알 수 있습니다67 // Return a result either way, so Claude learns when the lookup failed
68 return { result: response.ok ? response.text : 'Lookup failed with status ' + response.status }68 return { result: response.ok ? response.text : 'Lookup failed with status ' + response.status }
69})69})
70```70```
71 71
72티켓에 대해 물으면, Claude는 `mcp__my-mod__ticket`을 해당 id로 호출할 수 있습니다. 두 번째 hook은 티켓을 가져오고 응답 본문을 반환하며, Claude는 이를 도구의 결과로 읽습니다. 서버가 오류 상태로 응답하면, Claude는 `Lookup failed with status`와 숫자를 읽습니다.72티켓에 대해 질문하면 Claude는 티켓 id와 함께 `mcp__my-mod__ticket`을 호출할 수 있습니다. 두 번째 훅은 티켓을 가져와 응답 본문을 반환하며, Claude는 이를 도구의 결과로 읽습니다. 서버가 오류 상태로 응답하면 Claude는 `Lookup failed with status`와 상태 번호를 읽습니다.
73 73
74<h2 id="call-a-model">74<h2 id="call-a-model">
75 모델 호출하기75 모델 호출하기
76</h2>76</h2>
77 77
78mod는 텍스트 정렬 또는 요약과 같은 작은 작업을 위해 대화 외부에서 모델에 질문할 수 있습니다. `$.model.complete`는 세션의 자격 증명으로 모델에 하나의 프롬프트를 보내고 회신으로 해결됩니다. 대화 기록이 없습니다.78mod는 대화와 별개로 모델에 자체적인 질문을 보내 텍스트를 분류하거나 요약하는 등의 작은 작업을 수행할 수 있습니다. `$.model.complete`는 사용자 세션의 자격 증명으로 모델에 프롬프트 하나를 보내고 그 응답으로 resolve됩니다. 대화 기록은 포함되지 않습니다.
79 79
80이 hook은 [`command.run`](#add-a-command)으로 등록된 `/triage` 명령어에 답하여 작은 모델에 그 뒤에 입력된 텍스트에 레이블을 지정하도록 요청합니다:80다음 훅은 [명령으로 등록된](#add-a-command) `/triage` 명령에 응답하며, 작은 모델에 명령 뒤에 입력된 텍스트의 레이블을 지정하도록 요청합니다.
81 81
82```javascript theme={null}82```javascript theme={null}
83on('command.run', { command: 'triage' }, async ($, e) => {83on('command.run', { command: 'triage' }, async ($, e) => {
84 const r = await $.model.complete({84 const r = await $.model.complete({
85 model: 'haiku',85 model: 'haiku',
86 // 시스템 프롬프트는 작업을 설정하고, 프롬프트는 레이블을 지정할 텍스트를 전달합니다86 // The system prompt sets the job, and the prompt carries the text to label
87 system: 'Reply with one word: bug, feature, or question.',87 system: 'Reply with one word: bug, feature, or question.',
88 prompt: e.args,88 prompt: e.args,
89 // 한 단어는 적은 토큰이 필요하며, 호출은 15초 후 포기합니다89 // One word needs few tokens, and the call gives up after 15 seconds
90 maxTokens: 20,90 maxTokens: 20,
91 timeoutMs: 15000,91 timeoutMs: 15000,
92 })92 })
93 // r.text는 모델이 답변했을 때만 존재하므로 먼저 r.isAnswered를 확인하세요93 // r.text exists only when the model answered, so check r.isAnswered first
94 const label = r.isAnswered ? r.text.trim() : 'unknown'94 const label = r.isAnswered ? r.text.trim() : 'unknown'
95 return { text: 'Label: ' + label }95 return { text: 'Label: ' + label }
96})96})
97```97```
98 98
99`/triage the export button does nothing`을 실행하면, mod는 해당 텍스트를 모델로 보내고 `Label: bug`와 같은 답변을 인쇄합니다. Claude의 대화는 요청의 일부가 아닙니다. 모델이 답변하지 않으면 레이블은 `unknown`입니다.99`/triage the export button does nothing`를 실행하면 mod가 해당 텍스트를 모델에 보내고 `Label: bug`와 같은 응답을 출력합니다. Claude의 대화는 요청에 포함되지 않습니다. 모델이 응답하지 않으면 레이블은 `unknown`이 됩니다.
100 100
101Claude API 실패는 호출을 거부하지 않으므로 `r.isAnswered`를 확인하고, `false`일 때 `r.reason`을 읽으세요. 호출은 Claude Code가 보내지 않을 요청(예: 조직이 차단한 모델)에 대해서만 거부합니다. [빌드용 타입](/docs/ko/plugins/mods/create#get-the-types-for-your-build)은 `effort`와 같은 다른 옵션을 나열하고, [제한](/docs/ko/plugins/mods/reference#limits)은 `maxTokens` 기본값을 제공합니다.101Claude API 오류가 발생해도 호출이 reject되지는 않으므로 `r.isAnswered`를 확인하고, 값이 `false`이면 `r.reason`을 읽어야 합니다. 조직에서 차단한 모델처럼 Claude Code가 보내지 않는 요청의 경우에는 호출이 reject됩니다. [사용 중인 빌드의 타입](/docs/ko/plugins/mods/create#get-the-types-for-your-build)에는 `effort`와 같은 다른 옵션이 나열되어 있으며, [제한](/docs/ko/plugins/mods/reference#limits)에서 `maxTokens` 기본값을 확인할 수 있습니다.
102 102
103`$.model.fork({ prompt })`는 대신 현재 대화에 대해 한 가지 질문을 하며, 동일한 모델과 시스템 프롬프트를 사용하므로 Claude API는 대부분을 프롬프트 캐시에서 제공합니다.103`$.model.fork({ prompt })`는 대신 현재 대화를 바탕으로 동일한 모델과 시스템 프롬프트를 사용해 질문 하나를 보내므로, Claude API가 대부분을 프롬프트 캐시에서 처리합니다.
104 104
105이러한 호출은 사용자의 플랜 또는 API 키를 사용합니다.105이러한 호출에는 사용자의 플랜 또는 API 키가 사용됩니다.
106 106
107<h2 id="run-work-in-the-background">107<h2 id="run-work-in-the-background">
108 백그라운드에서 작업 실행하기108 백그라운드에서 작업 실행하기
109</h2>109</h2>
110 110
111한 이벤트를 초과하는 작업(예: 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()`는 밀리초 단위의 시간을 제공합니다.1111분마다 무언가를 확인하는 것처럼 하나의 이벤트보다 오래 지속되는 작업은 `session.start`에서 시작하는 타이머로 실행됩니다. 훅 자체는 하나의 이벤트에 대해 실행되며, 자체 실행 시간에 [시간 제한](/docs/ko/plugins/mods/reference#limits)이 있습니다. `next` 또는 mod API 호출을 기다리는 데 소요된 시간은 `$.clock.sleep`을 제외하고 포함되지 않습니다. `$.clock.every`와 `$.clock.after`는 `setInterval`과 `setTimeout`을 대신하며, 지연 시간(밀리초)을 첫 번째 인수로 받습니다. `$.clock.after(5000, fn)`은 지금부터 5초 후에 `fn`을 한 번 호출합니다. 각각 `cancel()` 메서드가 있는 타이머를 반환하며, `await $.clock.now()`는 현재 시간을 밀리초 단위로 제공합니다.
112 112
113이 hook은 1분마다 pull request의 확인을 조회하고 프롬프트 아래에 결과를 표시합니다. `summarize`는 명령어의 JSON 출력을 몇 단어로 변환하는 자신의 함수입니다:113다음 훅은 1분마다 풀 리퀘스트의 검사 결과를 조회하여 프롬프트 아래에 표시합니다. `summarize`는 명령의 JSON 출력을 몇 단어로 요약하는 사용자 정의 함수입니다.
114 114
115```javascript theme={null}115```javascript theme={null}
116on('session.start', async ($, e, next) => {116on('session.start', async ($, e, next) => {
117 // 60,000밀리초마다 함수를 호출하며, 지금부터 1분 후에 시작합니다117 // Call the function every 60,000 milliseconds, starting one minute from now
118 $.clock.every(60_000, async () => {118 $.clock.every(60_000, async () => {
119 const status = await $.process.run(['gh', 'pr', 'checks', '--json', 'state'])119 const status = await $.process.run(['gh', 'pr', 'checks', '--json', 'state'])
120 // 프롬프트 아래의 줄을 최신 요약으로 바꿉니다120 // Replace the line under the prompt with the latest summary
121 $.ui.status('checks: ' + summarize(status.stdout))121 $.ui.status('checks: ' + summarize(status.stdout))
122 })122 })
123 // 타이머를 기다리지 않고 반환하므로 세션이 바로 시작됩니다123 // Return without waiting for the timer, so the session starts right away
124 return next(e)124 return next(e)
125})125})
126```126```
127 127
128세션은 평소대로 시작됩니다. 1분 후, 프롬프트 아래에 `⚠`, mod의 이름, 그리고 `checks:`와 요약이 있는 줄이 나타납니다. 그 후 1분마다 교체됩니다. 타이머의 콜백은 모든 이벤트 외부에서 실행되므로 턴 사이에 계속 실행되고 턴을 시작하지 않습니다. 콜백이 throw하면, 오류는 [디버그 로그](/docs/ko/plugins/mods/troubleshoot#read-the-debug-log)로 이동하고 타이머는 다음 간격에 다시 실행됩니다.128세션은 평소처럼 시작됩니다. 1분 후 프롬프트 아래에 `⚠`, mod 이름, 그리고 `checks:`와 요약이 담긴 줄이 나타납니다. 이후 이 줄은 1분마다 교체됩니다. 타이머의 콜백은 어떤 이벤트에도 속하지 않고 실행되므로, 턴 사이에도 계속 실행되며 턴을 시작하지 않습니다. 콜백에서 오류가 발생하면 오류는 [디버그 로그](/docs/ko/plugins/mods/troubleshoot#read-the-debug-log)에 기록되고, 타이머는 다음 간격에 다시 실행됩니다.
129 129
130<h3 id="show-something-without-starting-a-turn">130<h3 id="show-something-without-starting-a-turn">
131 턴을 시작하지 않고 무언가 표시하기131 턴을 시작하지 않고 내용 표시하기
132</h3>132</h3>
133 133
134백그라운드 작업은 턴을 시작하지 않고 사용자에게 무언가를 표시할 수 있습니다. 이러한 각 호출은 다른 위치에 텍스트를 배치합니다:134백그라운드 작업은 턴을 시작하지 않고도 사용자에게 내용을 표시할 수 있습니다. 다음 각 호출은 텍스트를 서로 다른 위치에 표시합니다.
135 135
136| 호출 | 사용자가 보는 것 |136| 호출 | 사용자에게 표시되는 내용 |
137| :- | :- |137| :- | :- |
138| `$.ui.status(text)` | 변경할 때까지 프롬프트 아래에 남아있는 한 줄입니다. `⚠`와 mod의 이름으로 시작하며, `⚠ my-mod: checks: 3 passing`과 같습니다. |138| `$.ui.status(text)` | 변경할 때까지 유지되는 프롬프트 아래의 한 줄입니다. `⚠ my-mod: checks: 3 passing`처럼 `⚠`와 mod 이름으로 시작합니다. |
139| `$.ui.toast(text)` | 오른쪽 상단의 작은 상자이며, mod의 이름이 텍스트 위에 있고 몇 초 후 사라집니다 |139| `$.ui.toast(text)` | 오른쪽 상단에 표시되는 토스트 알림으로, 텍스트 위에 mod 이름이 표시되며 몇 초 후 사라집니다 |
140| `$.ui.log(text)` | Claude가 읽지 않는 트랜스크립트의 흐릿한 줄입니다. `●`과 mod의 이름으로 시작하며, `● my-mod: build finished`와 같습니다. |140| `$.ui.log(text)` | Claude가 읽지 않는 트랜스크립트의 흐린 줄입니다. `● my-mod: build finished`처럼 `●`와 mod 이름으로 시작합니다. |
141 141
142<h3 id="start-a-turn-from-a-background-job">142<h3 id="start-a-turn-from-a-background-job">
143 백그라운드 작업에서 턴 시작하기143 백그라운드 작업에서 턴 시작하기
144</h3>144</h3>
145 145
146백그라운드 작업이 Claude의 주의가 필요한 것을 발견하면, `$.prompt.submit({ text })`로 프롬프트를 제출하여 턴을 시작할 수 있습니다. Claude는 발신자로 mod의 이름을 지정하는 문장 뒤의 텍스트를 읽습니다. 해당 문장 없이 사용자 자신의 말로 보내려면 `asUser: true`를 추가하세요. 호출은 세션이 유휴 상태가 될 때까지 기다린 후 새 턴을 시작합니다. 해당 턴이 시작될 때 해결되므로 Claude가 작업 중일 때 실행되는 핸들러에서 `await`하지 마세요.146백그라운드 작업이 Claude의 주의가 필요한 사항을 발견하면 `$.prompt.submit({ text })`로 프롬프트를 제출하여 턴을 시작할 수 있습니다. Claude는 해당 mod를 발신자로 명시하는 문장 뒤에 이어지는 텍스트를 읽습니다. 그 문장 없이 사용자 자신의 말로 보내려면 `asUser: true`를 추가합니다. 이 호출은 세션이 유휴 상태가 될 때까지 기다린 후 새 턴을 시작합니다. 해당 턴이 시작될 때 resolve되므로, Claude가 작업하는 동안 실행되는 핸들러에서는 이를 `await`하지 마십시오.
147 147
148<h3 id="stop-background-work">148<h3 id="stop-background-work">
149 백그라운드 작업 중지하기149 백그라운드 작업 중지하기
150</h3>150</h3>
151 151
152백그라운드 작업은 두 가지 방법으로 중지됩니다. 모듈이 다시 로드되면 타이머가 중지됩니다. hook 내의 장기 실행 작업의 경우, [`next.signal`](/docs/ko/plugins/mods/reference#the-hook-function)은 hook이 처리하는 이벤트가 중단될 때(예: 사용자가 중단할 때) 중단되는 `AbortSignal`이므로 장기 실행 항목에 전달하세요.152타이머는 모듈이 다시 로드될 때 중지됩니다. 훅 내부의 장기 실행 작업의 경우, [`next.signal`](/docs/ko/plugins/mods/reference#the-hook-function)은 사용자가 중단하는 경우처럼 훅이 처리 중인 이벤트가 중단될 때 abort되는 `AbortSignal`이므로, 장기 실행되는 모든 작업에 이를 전달하십시오.
153 153
154<h2 id="send-and-receive-messages-between-sessions">154<h2 id="send-and-receive-messages-between-sessions">
155 세션 간 메시지 보내고 받기155 세션 간 메시지 보내기 및 받기
156</h2>156</h2>
157 157
158mod는 다른 세션 또는 이 세션의 subagent 중 하나에 일반 텍스트 메시지를 보낼 수 있으며, 도착하고 떠나는 메시지를 관찰할 수 있습니다. `$.session.send({ to, text })`는 하나를 보내며, SendMessage 도구가 만드는 것과 동일한 전달입니다. `to`는 세션의 경우 `{ sessionId }`, `$.agent.list()`의 subagent의 경우 `{ agentId }`, 또는 수신한 메시지가 온 문자열 주소입니다. 호출은 메시지가 큐에 들어가면 `{ isDelivered: true }`로 해결됩니다. 아무것도 전달되지 않으면 `{ isDelivered: false, reason }`으로 해결되며, `reason`은 이유를 설명합니다.158mod는 사용자의 다른 세션이나 이 세션의 서브에이전트 중 하나에 일반 텍스트 메시지를 보낼 수 있으며, 도착하고 나가는 메시지를 관찰할 수 있습니다. `$.session.send({ to, text })`는 메시지 하나를 보내며, SendMessage 도구와 동일한 방식으로 전달합니다. `to`는 세션의 경우 `{ sessionId }`, `$.agent.list()`에서 가져온 서브에이전트의 경우 `{ agentId }`, 또는 수신된 메시지의 발신 문자열 주소입니다. 이 호출은 메시지가 대기열에 추가되면 `{ isDelivered: true }`로 resolve됩니다. 아무것도 전달되지 않은 경우 `{ isDelivered: false, reason }`으로 resolve되며, `reason`에 그 이유가 담깁니다.
159 159
160이 hook은 [`command.run`](#add-a-command)으로 등록된 `/ping` 명령어에 답하여 그 뒤에 입력한 id의 세션에 상태를 요청합니다:160다음 훅은 [명령으로 등록된](#add-a-command) `/ping` 명령에 응답하여, 명령 뒤에 입력한 id의 세션에 상태를 요청합니다.
161 161
162```javascript theme={null}162```javascript theme={null}
163on('command.run', { command: 'ping' }, async ($, e) => {163on('command.run', { command: 'ping' }, async ($, e) => {
164 // e.args는 /ping 뒤에 입력된 세션 id입니다164 // e.args is the session id typed after /ping
165 const sent = await $.session.send({ to: { sessionId: e.args }, text: 'Status? One line.' })165 const sent = await $.session.send({ to: { sessionId: e.args }, text: 'Status? One line.' })
166 // 호출은 어느 쪽이든 해결되므로 isDelivered를 확인하여 무슨 일이 일어났는지 알아봅니다166 // The call resolves either way, so check isDelivered to learn what happened
167 if (!sent.isDelivered) $.ui.toast('Not delivered: ' + sent.reason)167 if (!sent.isDelivered) $.ui.toast('Not delivered: ' + sent.reason)
168 // 빈 결과는 이 세션의 트랜스크립트에 아무것도 인쇄하지 않습니다168 // An empty result prints nothing in this session's transcript
169 return {}169 return {}
170})170})
171```171```
172 172
173메시지가 큐에 들어가면 세션에 아무것도 나타나지 않으며, 다른 세션의 Claude는 `Status? One line.`을 읽습니다. 아무것도 전달되지 않으면, 오른쪽 상단의 작은 상자가 이유를 제공하고 몇 초 후 사라집니다.173메시지가 대기열에 추가되면 사용자의 세션에는 아무것도 표시되지 않으며, 다른 세션의 Claude가 `Status? One line.`을 읽습니다. 아무것도 전달되지 않은 경우 토스트 알림으로 이유가 표시됩니다.
174 174
175두 이벤트를 통해 mod는 메시지를 관찰할 수 있습니다. 두 이벤트 모두에서 `next(e)`를 반환하여 각 메시지를 변경되지 않은 상태로 전달합니다:175`session.receive`와 `session.send`를 사용하면 mod가 메시지를 관찰할 수 있습니다. 두 이벤트 모두에서 `next(e)`를 반환하면 각 메시지를 변경 없이 통과시킵니다.
176 176
177| 이벤트 | 발생 시점 | 유용한 필드 |177| 이벤트 | 발생 시점 | 유용한 필드 |
178| :- | :- | :- |178| :- | :- | :- |
179| `session.receive` | 메시지가 이 세션에 도착하며, Claude가 읽기 전입니다 | `e.text`, 및 `e.origin.kind`(예: 다른 세션 또는 agent의 경우 `peer` 또는 `peer-send-message`, `task-notification`, 또는 `scheduled-trigger`). Claude에서 메시지를 유지하려면 `{ consumed: reason }`을 반환합니다. |179| `session.receive` | Claude가 읽기 전에 이 세션에 메시지가 도착할 때 | `e.text`, 그리고 다른 세션이나 에이전트의 경우 `peer` 또는 `peer-send-message`, `task-notification`, `scheduled-trigger` 등의 `e.origin.kind`. Claude에게 전달되지 않도록 하려면 `{ consumed: reason }`을 반환합니다. |
180| `session.send` | 메시지가 SendMessage 도구 또는 mod에서 떠나려고 합니다 | `e.to`, `e.text`, 및 `e.origin.kind`(이는 `model` 또는 `plugin`입니다) |180| `session.send` | SendMessage 도구나 mod에서 메시지가 나가려고 할 때 | `e.to`, `e.text`, 그리고 `model` 또는 `plugin`인 `e.origin.kind` |
181 181
182[인바운드 메시지를 거부](/docs/ko/cross-session-messaging#control-inbound-messages)하도록 설정된 세션은 `session.receive`가 발생하기 전에 메시지를 거부하므로 hook은 이를 보지 않습니다. 승인을 위해 보류 중인 메시지는 먼저 hook에 도달하므로 mod는 아직 승인하지 않은 메시지를 읽을 수 있습니다. hook의 `next(e)`는 메시지가 전달되지 않으면 거부합니다.182[수신 메시지를 거부](/docs/ko/cross-session-messaging#control-inbound-messages)하도록 설정된 세션은 `session.receive`가 발생하기 전에 메시지를 거부하므로 훅에서는 해당 메시지를 볼 수 없습니다. 사용자의 승인을 기다리는 메시지는 먼저 훅에 도달하므로, mod는 아직 승인하지 않은 메시지를 읽을 수 있습니다. 메시지가 전달되지 않으면 훅의 `next(e)`는 reject됩니다.
183 183
184수신한 메시지의 발신자 이름은 발신자가 작성한 것이므로 이를 기반으로 결정하지 마세요.184수신된 메시지의 발신자 이름은 발신자가 작성한 그대로이므로, 이를 근거로 결정을 내리지 마십시오.
185 185
186<h2 id="reach-files-processes-and-the-network">186<h2 id="reach-files-processes-and-the-network">
187 파일, 프로세스 및 네트워크에 접근하기187 파일, 프로세스, 네트워크에 접근하기
188</h2>188</h2>
189 189
190mod는 Claude Code를 실행하는 사용자와 동일한 권한으로 파일 시스템, 프로세스 및 네트워크에 접근합니다. hooks 모듈 자체는 Node.js API, `setTimeout`과 같은 타이머 전역, 자체 네트워크 또는 파일 접근이 없습니다. `URL`, `TextEncoder`, `AbortController`, `crypto.subtle`과 같은 표준 JavaScript 및 웹 API를 사용할 수 있습니다. 아래의 각 네임스페이스는 한 종류의 접근을 다룹니다:190mod는 mods API를 통해 파일 시스템, 프로세스, 네트워크에 접근하며, Claude Code를 실행하는 사용자와 동일한 권한을 가집니다. 훅 모듈 자체에는 Node.js API, `setTimeout` 같은 타이머 전역 객체가 없으며, 자체적인 네트워크 또는 파일 접근 기능도 없습니다. `URL`, `TextEncoder`, `AbortController`, `crypto.subtle` 같은 표준 JavaScript 및 웹 API는 사용할 수 있습니다. 아래의 각 네임스페이스는 한 가지 종류의 접근을 담당합니다.
191 191
192| 네임스페이스 | 수행하는 작업 |192| 네임스페이스 | 기능 |
193| :- | :- |193| :- | :- |
194| `$.fs` | `read(path)`, `write(path, text)`, `exists(path)`, `stat(path)`, 및 `list(path)`는 파일 및 디렉토리에서 작동합니다 |194| `$.fs` | `read(path)`, `write(path, text)`, `exists(path)`, `stat(path)`, `list(path)`로 파일과 디렉터리를 다룹니다 |
195| `$.process` | `run(['git', 'status'])`는 명령어를 시작하고 종료될 때 해결됩니다. `spawn`은 장기 실행 명령어의 출력을 스트리밍합니다. |195| `$.process` | `run(['git', 'status'])`는 명령을 시작하고 명령이 종료되면 resolve됩니다. `spawn`은 장시간 실행되는 명령의 출력을 스트리밍합니다. |
196| `$.http` | `http` 또는 `https`를 통한 `fetch(url, init)`. 본문이 읽혀지면 `{ status, ok, headers, text }`로 해결됩니다. |196| `$.http` | `http` 또는 `https`를 통한 `fetch(url, init)`입니다. 본문을 읽고 나면 `{ status, ok, headers, text }`로 resolve됩니다. |
197| `$.store` | 플러그인 자신의 JSON 키-값 저장소이며, 세션 간에 유지됩니다 |197| `$.store` | 플러그인 전용 JSON 키-값 저장소로, 세션 간에 유지됩니다 |
198| `$.env` | 환경 변수를 `get` 및 `set`합니다. 이름을 리터럴 문자열로 작성하세요. |198| `$.env` | 환경 변수를 `get` 및 `set`합니다. 이름은 문자열 리터럴로 작성합니다. |
199| `$.settings` | 설정 파일 및 관리 정책이 보유한 것을 `read`합니다 |199| `$.settings` | 설정 파일과 관리형 정책에 담긴 내용을 `read`합니다 |
200| `$.session` | `messages()`는 트랜스크립트를 `{ role, text, toolUses }` 목록으로 반환합니다. 또한 작업 디렉토리, 모델 등입니다. [`usage()`](/docs/ko/plugins/mods/reference#mods-api-methods)는 컨텍스트 윈도우 사용 및 플랜 제한을 반환합니다. |200| `$.session` | `messages()`는 트랜스크립트를 `{ role, text, toolUses }`의 목록으로 반환합니다. 작업 디렉터리, 모델 등도 제공합니다. [`usage()`](/docs/ko/plugins/mods/reference#mods-api-methods)는 컨텍스트 윈도우 사용량과 플랜 한도를 반환합니다. |
201| `$.mcp` | 연결된 MCP 서버에서 도구를 `call`합니다 |201| `$.mcp` | 연결된 MCP 서버의 도구를 `call`합니다 |
202 202
203파일 및 프로세스에는 자체 규칙이 몇 가지 있습니다:203파일과 프로세스에는 몇 가지 고유한 규칙이 있습니다.
204 204
205* **경로**: 상대 경로는 세션의 작업 디렉토리 아래에 있습니다205* **경로**: 상대 경로는 세션의 작업 디렉터리를 기준으로 해석됩니다
206* **`$.fs.list`**: 한 디렉토리의 항목을 `{ name, kind, size, isLink }`로 반환하며 하위 디렉토리로 내려가지 않습니다206* **`$.fs.list`**: 한 디렉터리의 항목을 `{ name, kind, size, isLink }` 형태로 반환하며, 재귀적으로 동작하지 않습니다
207* **`$.process.run`**: 인수 목록을 받으며 shell을 사용하지 않습니다. 종료 코드에 관계없이 `{ exitCode, stdout, stderr }`로 해결됩니다. 프로그램을 시작할 수 없거나 기본값인 30초의 타임아웃에서 여전히 실행 중이면 거부하므로 `try`와 `catch`로 래핑하세요.207* **`$.process.run`**: 인수 목록을 받으며 셸을 사용하지 않습니다. 종료 코드와 관계없이 `{ exitCode, stdout, stderr }`로 resolve됩니다. 프로그램을 시작할 수 없거나 타임아웃(기본값 30초) 시점에 여전히 실행 중이면 reject되므로 `try`와 `catch`로 감싸야 합니다.
208 208
209이러한 호출 각각은 그 자체로 이벤트이며, `$.fs.read`의 경우 `fs.read`와 같이 `$.` 없이 네임스페이스 및 메서드로 이름이 지정됩니다. [체인의 앞에 있는](/docs/ko/plugins/mods/events#the-order-mods-run-in) mod는 호출을 관찰, 다시 작성 또는 거부할 수 있으며, 이것이 조직이 mod가 도달하는 것을 제한하는 방법입니다.209이러한 호출 각각은 그 자체로 하나의 이벤트이며, `$.`를 제외한 네임스페이스와 메서드 이름으로 명명됩니다. 예를 들어 `$.fs.read`의 이벤트는 `fs.read`입니다. [체인의 앞쪽](/docs/ko/plugins/mods/events#the-order-mods-run-in)에 있는 mod는 사용자의 호출을 관찰하거나, 재작성하거나, 거부할 수 있으며, 조직은 이 방식으로 mod가 접근할 수 있는 범위를 제한합니다.
210 210
211<h2 id="next-steps">211<h2 id="next-steps">
212 다음 단계212 다음 단계
213</h2>213</h2>
214 214
215* [이벤트에 반응하기](/docs/ko/plugins/mods/events): hook 도구 호출, 프롬프트 및 턴215* [이벤트에 반응하기](/docs/ko/plugins/mods/events): 도구 호출, 프롬프트, 턴에 훅 연결
216* [인터페이스에 그리기](/docs/ko/plugins/mods/interface): mod가 수집한 것을 pane 또는 프롬프트 위에 표시합니다216* [인터페이스에 그리기](/docs/ko/plugins/mods/interface): mod가 수집한 내용을 창이나 프롬프트 위에 표시
217* [mod 테스트하기](/docs/ko/plugins/mods/test): 테스트에서 이러한 호출 중 하나를 stub합니다217* [mod 테스트하기](/docs/ko/plugins/mods/test): 테스트에서 이러한 호출을 스텁으로 대체
218* [Mods 참조](/docs/ko/plugins/mods/reference): 모든 이벤트, 모든 mods API 메서드 및 제한218* [mod 레퍼런스](/docs/ko/plugins/mods/reference): 이벤트, mod API 메서드 및 제한 사항