블로그 목록

CLAUDE CODE MODS · 한국어 · 처음 배우는 사람을 위해

Claude Code를, 내 방식으로 확장.

도구 호출을 바꾸고, 정보를 표시하고, 편집을 검토합니다. Claude Code Mods의 실제 기능을 스크롤하며 익힙니다.

01–06 Mods의 동작을 체험07 제작의 입구08–14 구현과 전문 용어
mMODS SCROLL LAB
학습의 시작판단 연습 0 / 14

스크롤하면 예시가 바뀝니다.
중간에 만나는 질문에서는 새로운 상황에 적용해보세요.

첫 번째 원리 살펴보기
내 작업의 요구 → Mod의 기능
실행 전 확인이벤트 훅
현재 사용량 표시컨텍스트
재로딩에도 이력 유지$.state
지난 편집 검토Replay
관찰한다 · 바꾼다 · 직접 답한다
01tool.call → 실행 흐름 제어

Claude의 도구 호출을
그대로, 수정, 직접 처리.

Mods는 Claude Code 안에서 이벤트를 받는 JavaScript·TypeScript 확장입니다. Claude가 Bash를 호출할 때, 내 함수가 실행 전에 입력을 보고 다음 처리를 선택합니다.

같은 이벤트, 세 가지 선택
Bash 명령 · 입력 e
내 훅 관찰
다른 플러그인 · Claude Code
next(e)로 전달 · 결과는 돌아옵니다
const r = await next(e);
// 실행 결과를 관찰
return r;
01 / 03

도구는 실행하고 결과를 관찰합니다.

next(e)로 원래 실행을 이어갑니다. 실행 결과를 받은 뒤 로그나 통계를 남길 수 있습니다. 관찰은 원래 동작을 유지합니다.

02 / 03

도구가 받을 입력을 바꿉니다.

명령을 수정한 새 이벤트를 next에 전달합니다. 수정은 Claude의 도구 호출이 실제로 받을 입력을 바꾸는 기능입니다.

03 / 03

원래 실행 대신 훅이 답합니다.

직접 응답은 next를 호출하지 않고 이벤트에 맞는 결과를 반환합니다. tool.call에 deny를 반환하면 이 호출은 원래 도구 실행으로 넘어가지 않습니다.

판단 연습 01아직 답하지 않음

tool.call 훅에서 이번 Bash 호출을 실행하지 않기로 결정했습니다. 원래 실행으로 넘기지 않으려면?

생각의 단서

행동이 실제 실행으로 넘어가는 지점은 어디일까요?

02Token Weather → 컨텍스트 표시

프롬프트 위에서
컨텍스트 사용량을 읽습니다.

Token Weather는 컨텍스트 사용량을 날씨와 그래프로 보여주는 Mod입니다. Claude가 참고하는 대화·파일·도구 결과의 양을 매 턴 확인할 수 있습니다. 토큰은 모델이 글을 처리하는 작은 조각 단위입니다.

TOKEN WEATHER 설명용 예시 · 200k 창
☀
Clear · 맑음18%
36.1k / 200k컨텍스트 사용량
<25% 맑음25–49% 흐림50–74% 소나기75–89% 폭풍90%+ 압축 신호
최근 측정값 · 최대 12개 / 높이는 이력 내 최댓값 기준
지금 참고하는 자료의 양입니다.
$.session.usage()
01 / 03

작은 작업을 시작합니다.

예시 창은 200k 토큰. 36.1k를 참고하면 반올림해 18%, 맑음입니다.

02 / 03

로그와 코드를 더 읽습니다.

134.4k / 200k = 67.2%. 표시값은 67%, 소나기입니다. 마지막 턴 증가량은 98.3k입니다.

03 / 03

많이 찼다는 신호를 받습니다.

162k / 200k = 81%, 폭풍입니다. 예보는 상태 표시이며, 모드가 자동으로 압축한다는 뜻은 아닙니다.

판단 연습 02아직 답하지 않음

표시가 “Compact soon · 92%”로 바뀌었습니다. 이 표시만으로 알 수 있는 것은?

생각의 단서

계기판의 경고와 실제 조작은 같은 기능일까요?

03turn.complete → UI 갱신

Claude의 턴이 끝나면
최신 값을 화면에 연결합니다.

Token Weather의 동작은 완료 이벤트 → 사용량 읽기 → 상태 저장 → 프롬프트 위 렌더입니다. “턴”은 Claude가 한 번의 작업을 진행하고 마치는 단위입니다. 화면 표시에 실제 세션 데이터를 연결합니다.

한 턴이 끝난 뒤의 순서
1
완료 처리await next(e)
2
사용량 측정 · 상태 저장$.session.usage() → $.state.set
3
구독한 화면이 갱신ui.render → $.state.get
측정값 → 상태 → 화면. 역할을 분리합니다.
on("turn.complete", async ($, e, next) => {
  const r = await next(e);
  if (!e.agentId) await takeReading($);
  return r;
});
01 / 03

작업이 먼저 끝납니다.

turn.complete에서 먼저 await next(e)를 호출합니다. 완료 처리가 끝난 뒤 읽어야 최신 사용량을 관찰합니다.

02 / 03

측정값을 호스트에 보관합니다.

$.session.usage()로 읽고 $.state.set으로 저장합니다. e.agentId가 없는 메인 턴만 측정해 하위 작업이 섞이지 않게 합니다.

03 / 03

값을 읽는 화면이 갱신됩니다.

ui.render 안에서 $.state.get으로 읽으면 그 화면이 상태 변경을 구독합니다. 이 예제에는 별도의 invalidate 호출이 필요 없습니다.

판단 연습 03아직 답하지 않음

메인 대화의 최근 12턴 추세만 보여주려 합니다. 서브에이전트가 끝날 때도 측정하면 무엇을 먼저 고쳐야 할까요?

생각의 단서

값의 정확성뿐 아니라 어떤 사건을 기록했는지도 중요합니다.

04핫 리로딩 → 세션 상태 유지

Mod 코드를 고쳐도
측정 이력은 이어집니다.

실행 중인 Claude Code에서 Mod를 수정하면 코드를 다시 로드해 바로 확인할 수 있습니다. UI 코드는 바뀌어도 같은 세션의 최근 12턴 이력을 유지하려면, 모듈 변수와 호스트 상태를 구분해야 합니다.

HOT RELOAD · 같은 세션
교체되는 모듈

let history

18 · 67 · 81
변수에만 저장
계속 살아 있는 호스트

$.state

18 · 67 · 81
세션 동안 유지
저장 전
코드는 교체되어도, 호스트 상태는 유지됩니다.
let history = [];
01 / 03

모듈 변수에 기록합니다.

let history = []는 지금 로드된 모듈의 기억입니다. 핫 리로딩은 코드를 저장하면 새 모듈로 교체하는 과정입니다.

02 / 03

코드를 저장해 모듈을 바꿉니다.

register가 다시 실행되고 session.start도 발생합니다. 모듈 변수는 초기화되어 이전 기록을 잃습니다.

03 / 03

재로딩에 남길 값은 $.state에 둡니다.

호스트가 같은 세션에서 상태를 보관하므로 새 모듈도 같은 이력을 읽습니다. 세션을 넘는 저장이 필요하면 지속 저장용 API를 따로 검토합니다.

판단 연습 04아직 답하지 않음

코드를 고칠 때마다 최근 사용량 그래프가 사라집니다. 같은 세션에서 이력을 유지할 적절한 방법은?

생각의 단서

교체되는 쪽과 계속 살아 있는 쪽을 구분해보세요.

05Blast Radius → Bash 실행 보류

위험한 Bash 명령을
영향 확인 뒤에 넘깁니다.

Blast Radius는 감지한 위험 명령의 실행을 보류하고, 바뀔 파일을 보고 사용자가 진행·취소를 선택하게 합니다. Claude가 쓰던 Bash 호출 흐름에 확인 UI를 끼워 넣는 Mod입니다.

BLAST RADIUS 실행되지 않는 시뮬레이션
$ rm -rf build
실행 전 영향 보고서9 files1.1 MB
build/index.html
build/assets/app.js
build/assets/style.css
그 외 6개 파일
진행 next(e)
취소 거부 반환
탐지되지 않는 실행 경로가 있을 수 있습니다.
on("tool.call", { tool: "Bash" }, …)
01 / 03

삭제 요청이 들어옵니다.

rm -rf build를 바로 실행하지 않고 tool.call에서 보류합니다. 웹페이지에서는 설명용 시뮬레이션만 보여줍니다.

02 / 03

영향을 먼저 보여줍니다.

원문 터미널 예시에서는 파일 9개, 총 1.1 MB를 표시합니다. 삭제할 대상이 예상과 같은지 사용자가 확인합니다.

03 / 03

판단에 따라 갈림길이 생깁니다.

진행이면 원래 명령을 next(e)에 넘깁니다. 취소면 거부를 반환합니다. 이 보조 장치는 권한 규칙을 대체하지 않습니다.

판단 연습 05아직 답하지 않음

Blast Radius가 위험 명령을 미리 보여줍니다. 이것만으로 모든 삭제 경로를 막았다고 볼 수 있을까요?

생각의 단서

판단을 돕는 장치와 실행 권한을 제한하는 장치의 역할을 나눠보세요.

06Replay Theater → 편집 검토

/replay로 지난 턴의
편집을 순서대로 검토합니다.

Replay Theater는 Claude의 Edit·Write 호출을 기록하고 지난 턴의 변경 전후를 보여주는 Mod입니다. 최종 diff만 보는 대신 편집 한 단계씩 이동하며 검토합니다. 기록 열람은 실제 파일 복원과 별개입니다.

REPLAY THEATER 학습용 이름 변경 예시
1 · 정의2 · 호출3 · 테스트
src/greeting.js
− function greet(name) {
+ function welcome(name) {
// 기존 편집의 기록을 읽습니다.
원본 파일을 되돌리지 않고 변경 순서를 보여줍니다.
turn.start → pending = []
01 / 03

턴 시작에 기록을 묶습니다.

turn.start에서 이번 턴의 편집 목록을 준비합니다. Edit / Write 호출의 파일과 변경 전후를 기록합니다.

02 / 03

편집을 그대로 진행시킵니다.

기록한 뒤 next(e)로 전달합니다. Write는 반영 직전 $.fs.read로 기존 내용을 읽습니다. 관찰은 편집을 되돌리는 기능이 아닙니다.

03 / 03

끝난 턴을 단계별로 읽습니다.

turn.complete 뒤 /replay 명령이나 패널로 확인합니다. 넓은 화면은 옆 패널, 좁은 화면은 인라인으로 같은 내용을 보여줍니다.

판단 연습 06아직 답하지 않음

리플레이에서 잘못된 편집을 발견했습니다. 원문 모드의 동작으로 옳은 해석은?

생각의 단서

화면의 시간 이동과 파일 시스템의 시간 이동을 구분해보세요.

07이제, 직접 만들기

원하는 결과를 말하고,
구조를 확인하세요.

API를 모두 외울 필요는 없습니다. Claude에게 원하는 표시를 설명하고, 지금 배운 원리로 구현을 검토하세요.

실전

기능을 고를 때는 “무엇이 달라질까?”부터 봅니다.

정보를 바로 보고 싶다

Token Weather

turn.complete → ui.render

프롬프트 위에서 컨텍스트 추세를 확인합니다. 사용량을 표시하며 자동 압축을 수행하지 않습니다.

실행 전에 결정하고 싶다

Blast Radius

tool.call → 진행 / 취소

감지한 위험 명령을 보류하고 영향 보고서를 보여줍니다. 진행을 선택해야 원래 호출로 넘깁니다.

지난 편집을 이해하고 싶다

Replay Theater

Edit·Write 기록 → /replay

파일 변경을 순서대로 검토합니다. UI에서 이전 단계로 이동해도 실제 파일은 복원되지 않습니다.

세 예제의 영상·전체 Token Weather 구현·나머지 두 예제의 핵심 훅을 아래에서 이어서 읽습니다. 공식 문서가 연결하는 완성 예제 저장소에서는 설치 가능한 플러그인 묶음을 확인할 수 있습니다.

Claude Code에 입력할 요청 · 원문을 바탕으로 재구성
프롬프트 위 표시줄에 컨텍스트 사용량을 날씨처럼 보여주는 token-weather Claude Code 모드를 만들어줘.

한 줄에 표시할 내용:
- 25% 미만 ☀ Clear(노랑), 25–49% ☁ Cloudy(청록), 50–74% ☂ Showers(파랑), 75–89% ☇ Storm(자홍), 90% 이상 ↯ Compact soon(빨강).
- 사용률과 토큰 사용량 / 창 크기. 예: 134.4k / 200k.
- 최근 12턴의 작은 그래프: ▁▂▃▄▅▆▇█.
- 마지막 턴의 증가량. 예: ▲ +98.3k last turn.

세션 시작과 메인 턴 완료 뒤에 갱신해줘. 재로딩에도 같은 세션의 이력이 유지되도록 $.state를 사용하고, 상태 타입 계약도 선언해줘. 검증과 테스트를 실행해줘.
먼저 확인

버전, 로드, 검증.

문서 기준 Claude Code 2.1.287 이상입니다. API가 달라질 수 있으므로 로드 시 생성되는 타입 선언을 확인하세요.

claude --version
claude --plugin-dir ./token-weather
claude plugin validate ./token-weather
claude plugin test ./token-weather

Claude가 작성한 개발 Mod는 핫 리로딩을 승인하면 해당 파일을 바꾼 턴이 끝날 때 로드·재로딩됩니다. /plugin에서 실제 로드 여부를 확인하세요. 세션별 임시 폴더는 정리될 수 있으므로 계속 쓰려면 자신의 폴더로 복사하고 설치합니다.

파일과 역할
.claude-plugin/plugin.json

이름·버전·작성자. 상태를 쓰면 types 경로도 선언합니다.

hooks/hooks.json

modules에 모듈 하나를 연결합니다.

hooks/token-weather.mjs

register에서 이벤트 훅을 등록합니다.

types/index.d.ts

PluginState에 token-weather.readings를 선언합니다.

tests/token-weather.test.ts

사용량을 제어하며 화면이 갱신되는지 확인합니다.

구현할 때 기억할 경계

모드에는 DOM과 Node가 없습니다. 외부 기능은 $ API로 사용합니다. e.props에서 화면 속성을 읽고, 그릴 내용이 없거나 설문이 자리를 쓰면 next(e)로 넘깁니다.

타입

현재 빌드의 .claude-plugin/types/를 기준으로.

화면

너비는 e.props.bodyColumns를 기준으로.

상태

재로딩에도 남길 값은 $.state로.

문제 해결

화면이 안 보이면 claude --debug로.

사용

만들기 전에, 내 화면에서 동작하는지 확인합니다.

Claude Code 사용 환경Mod 훅Mod UI
터미널 · IDE의 통합 터미널 · JetBrains실행표시
Desktop의 Code 탭 (WSL 제외)실행표시 · 터미널 전용 요소 제외
Desktop WSL 세션미지원미지원
VS Code 확장 채팅 패널실행표시 안 됨
claude -p · Agent SDK실행표시 안 됨
Remote Control의 웹·모바일로컬 세션에서 실행로컬 터미널에 표시
클라우드 세션플러그인이 전달된 경우 실행표시 안 됨

터미널은 claude --version으로 2.1.287 이상인지, Desktop은 Code 탭의 /status에서 내장 Claude Code 2.1.286 이상인지 확인합니다. UI Mod를 VS Code 채팅 패널에서 테스트하면 “설치됐는데 보이지 않는다”는 상황이 생깁니다.

  1. 요구를 말합니다. “현재 git 브랜치를 입력 위에 보여주는 Mod를 만들어줘.” 필요하면 /plugin-authoring을 먼저 실행합니다.
  2. 실행을 승인합니다. Claude가 작성한 개발 Mod의 핫 리로딩을 승인하면 해당 턴이 끝날 때 로드됩니다.
  3. 로드를 확인합니다. /plugin의 Installed 탭과 활성 Mod 목록을 봅니다. 코드가 생성된 것과 실행 중인 것은 다릅니다.
  4. 쓰고 수정합니다. 원하는 기능을 확인하고 Claude에게 변경을 요청합니다. 계속 쓰려면 개발 폴더에서 자신의 폴더로 복사해 설치합니다.

설치 Mod 하나를 멈추려면 /plugin에서 비활성화합니다. --safe-mode는 다른 사용자 지정 기능도 함께 멈추므로 개별 Mod를 끄는 것보다 범위가 넓습니다.

근거: 지원 환경 · 공식 제작·재로딩 흐름

판단 연습 07아직 답하지 않음

운영 서버 배포를 요청받았습니다. 실행 전에 대상 서버를 사용자에게 확인받으려면 가장 적절한 설계는?

생각의 단서

이 문제에서 판단이 필요한 시점은 실행 전인가요, 실행 후인가요?

마켓플레이스 최소 구성 보기

다음 파일을 my-mods/.claude-plugin/marketplace.json에 두고, 같은 폴더에 token-weather/를 둡니다.

{
  "name": "my-mods",
  "owner": { "name": "You" },
  "plugins": [{ "name": "token-weather", "source": "./token-weather" }]
}
claude plugin marketplace add ./my-mods
claude plugin install token-weather@my-mods --scope user
PART 02 · 이해에서 구현으로

방금 배운 원리에
전문적인 이름을 붙입니다.

앞에서는 행동을 이해했습니다. 이제 파일을 연결하고, 값을 저장하고, 테스트로 확인하는 과정까지 이어갑니다. 코드는 짧은 덩어리로 나누어 역할을 설명합니다.

08구조와 전문 용어

이벤트를 처리하는 훅.
원래 동작을 잇는 미들웨어.

이름을 외우기보다 앞에서 본 역할에 이름을 붙여보세요. 각 용어는 코드에서 무엇을 찾아야 하는지 알려주는 표지입니다.

01

플러그인 안에 모듈, 모듈 안에 훅.

플러그인 · 배포하는 묶음
모듈 · JavaScript / TypeScript 파일
register(on, options)훅 · 사건에 반응하는 함수들

플러그인은 설치·배포 단위, 모듈은 구현 파일, 훅은 이벤트별 처리 함수입니다. 모드 하나는 hooks.json의 modules에 모듈 하나를 연결합니다.

02

이벤트를 받아 다음 단계에 넘깁니다.

훅의 공통 형태 · JavaScript
on("tool.call", { tool: "Bash" }, async ($, e, next) => {
  // $: Claude Code 기능을 사용하는 API
  // e: 이번 이벤트의 입력 데이터
  // next: 다음 훅, 마지막에는 원래 동작으로 전달
  return next(e);
});
$ · 기능 창구

화면, 세션, 상태, 파일 등 외부 기능에 접근합니다.

e · 이번 사건

이번 도구, 명령, 화면 속성 같은 입력입니다.

next · 다음 단계

입력을 다음 훅으로 전달하고 결과를 받습니다.

03

호출과 결과의 방향이 다릅니다.

호출 내 훅 → 다른 플러그인 → Claude Code

결과 내 훅 ← 다른 플러그인 ← Claude Code

await next(e) 뒤에 기록하면 실행 후 관찰입니다. next 없이 결과를 반환하면 이 훅이 직접 답합니다. 명령 거부뿐 아니라 슬래시 명령어나 도구의 처리도 직접 맡을 수 있습니다.

04

어떤 사건에 개입할지 선택합니다.

사건예시 이름어울리는 목적
도구를 호출함tool.call실행 전 확인, 파일 변경 기록
프롬프트 제출prompt.submit요청에 팀 규칙 덧붙이기
작업 시작·완료turn.start / turn.complete턴 단위 기록, 완료 후 측정
세션 시작·종료session.start 등초기화, 명령 등록, 세션 수명 관리
슬래시 명령 실행command.run/replay 같은 직접 처리
화면을 그림ui.render표시줄·패널의 요소 반환
05

외부 기능은 Mods API로 호출합니다.

모듈은 DOM·Node API를 직접 쓰는 웹앱이 아닙니다. 브라우저의 document나 Node의 fs 대신 Claude Code가 제공하는 API를 호출합니다. 파일은 $.fs, 프로세스는 $.process, HTTP는 $.http처럼 $를 통해 사용합니다.

$.ui$.session$.state$.store$.fs$.process$.clock$.http$.tool$.command$.model

어떤 메서드와 인수를 지원하는지는 현재 빌드가 생성한 타입 선언으로 확인합니다. 이름만 보고 지원 기능을 추측하지 마세요.

06

설정 훅·스킬과 Mods는 목적이 다릅니다.

설정 훅

셸 명령형 훅은 이벤트마다 프로그램을 실행하고 표준 입력·출력으로 JSON을 주고받습니다. 현재 설정 훅에는 HTTP·프롬프트 등 다른 유형도 있습니다.

모드

한 번 로드되어 세션에 남습니다. 상태를 유지하고, UI를 그리며, 패널·프로세스·명령·도구를 다룰 수 있습니다.

설정·권한 규칙·슬래시 명령어·스킬·상태 표시줄은 기존 확장 방법입니다. Mods는 이벤트 처리 코드를 로드해 동작을 수정·대체하고 UI까지 그리는 확장입니다. Claude Code의 AGENTS.md 지원과 /diff 패널 일부도 모드로 구현되어 있습니다. 공개 저장소의 mods/ 소스와 테스트에서 구현 방식을 볼 수 있습니다.

07

같은 확장 요구라도 선택할 기능이 다릅니다.

원하는 결과먼저 볼 기능이유
작업 절차·반복 지시를 Claude에게 알려주기SkillSKILL.md를 Claude가 읽고 적용합니다.
기존 검사 스크립트를 특정 시점에 실행설정 훅PreToolUse 같은 이벤트에 외부 처리를 연결합니다.
외부 서비스에 접근할 새 도구 제공MCP 서버Claude가 사용할 도구를 추가합니다.
입력 위 표시줄·검토 패널·직접 처리 명령Mods이벤트 처리와 Claude Code UI를 코드로 연결합니다.
위 기능들을 함께 설치·배포PluginMods·스킬·훅·MCP를 담는 배포 단위입니다.

설정 훅의 PreToolUse와 Mod의 tool.call은 서로 다른 이벤트 이름입니다. 스크립트형 설정 훅의 표준 입출력 계약을 Mod 함수에 그대로 복사하지 않습니다. Mod에서는 register로 등록하고 $, e, next를 사용합니다.

근거: 설정 훅 · Skills · Plugins · Mods 이벤트/API

판단 연습 08아직 답하지 않음

Claude의 작업 중 도구 호출 수를 입력 위에 계속 표시하고 싶습니다. 가장 적절한 선택은?

생각의 단서

Claude에게 지시를 읽히는 것과 Claude Code의 UI를 직접 확장하는 것은 다릅니다.

원문 실제 시연 · 세 모드 · 터미널
claude.dev 원문에서 영상 보기원문 사이트 밖에서는 영상 재생이 막혀 있습니다
제목 화면 사이로 세 모드를 보여줍니다. Token Weather 18%→81%, Blast Radius는 삭제 대상 9개 확인, Replay Theater는 greet→welcome 편집을 단계별로 검토합니다.
원본 영상 열기
원문 실제 시연 · 세 모드 · 데스크톱
claude.dev 원문에서 영상 보기원문 사이트 밖에서는 영상 재생이 막혀 있습니다
밝은 Code 탭에서도 같은 원리가 동작합니다. 사용률 10%→77%, 삭제 대상 9개, 이름 변경의 단계별 복기를 확인합니다.
원본 영상 열기
09제작 1–3단계

플러그인을 연결하고,
첫 Mod 화면을 띄웁니다.

처음부터 복잡한 기능을 만들지 않습니다. 연결이 맞는지 작은 글자 하나로 확인한 뒤, 실제 측정값을 연결합니다.

01

세 개의 파일이 첫 연결을 만듭니다.

먼저 claude --version으로 문서 기준 2.1.287 이상인지 확인합니다. 모드는 기본 활성화되어 별도 스위치를 켤 필요가 없습니다.

token-weather/.claude-plugin/plugin.json플러그인 이름·버전
token-weather/hooks/hooks.json구현 파일을 가리키는 연결표
token-weather/hooks/token-weather.mjs실제 동작
.claude-plugin/types/모드를 로드할 때 Claude Code가 생성
types/index.d.ts3단계에서 만드는 내 상태 계약
tests/token-weather.test.ts5단계에서 만드는 동작 시험
02

이름표와 연결표를 작성합니다.

.claude-plugin/plugin.json
{
  "name": "token-weather",
  "version": "0.1.0",
  "description": "A live forecast of the context window, drawn above the prompt.",
  "author": {
    "name": "You"
  }
}
hooks/hooks.json
{
  "modules": ["./token-weather.mjs"]
}

modules에는 모듈 하나를 지정합니다. 이 상대 경로는 hooks.json이 있는 위치에서 구현 파일을 찾습니다.

03

일단 “맑음” 한 줄만 그립니다.

2단계 · 첫 UI
// hooks/token-weather.mjs
export function register(on) {
  on("ui.render", { component: "AbovePrompt" }, ($, e, next) => {
    const { Box, Text } = $.ui.resolve(e);
    return Box({
      paddingX: 1,
      children: [Text({ color: "yellow", bold: true, children: "☀ Clear skies" })],
    });
  });
}

AbovePrompt는 프롬프트 바로 위의 빈 표시줄입니다. $.ui.resolve(e)로 현재 화면에 맞는 Box와 Text 생성자를 받고, 요소 트리를 반환합니다. 요소 생성자는 전역이 아닙니다.

로드
claude --plugin-dir ./token-weather

세션을 계속 열어두면 저장할 때마다 모듈이 재로딩됩니다. “Storm 기준을 70%로 바꿔줘”, “끝에 달러 비용을 추가해줘”처럼 같은 세션에서 수정 결과를 확인할 수 있습니다.

04

실제 사용량과 상태 계약을 연결합니다.

값정확히 뜻하는 것
context.tokens마지막 응답을 생성할 때 사용한 입력 토큰 수
context.window모델의 컨텍스트 창 크기
context.percent창 크기에 대한 사용률

$.session.usage()는 상태 표시줄과 같은 수치를 반환합니다. 문서 기준 이 호출에는 비용이 들지 않으며, breakdown을 요청할 때만 토큰 계산 요청을 보냅니다. 200k는 예시 창 크기이며 모든 모델의 고정값이 아닙니다.

types/index.d.ts
export type TokenWeatherReading = {
  tokens: number;
  window: number;
  percent: number;
};

declare module "claude-code" {
  interface PluginState {
    "token-weather": { readings: TokenWeatherReading[] };
  }
}

모듈 구현은 JavaScript로 작성해도 상태 계약은 .d.ts 파일로 선언합니다. readings는 위 구조의 측정값 배열입니다.

매니페스트에 추가
"types": "./types/index.d.ts"

plugin.json 객체 안에 types 필드를 추가합니다. 한 줄만으로 독립된 JSON 파일을 만드는 것이 아닙니다.

05

오류 문장은 수정할 위치를 알려줍니다.

token-weather.readings is not declared: the manifest's types contract must name it in interface PluginState { … }

“상태 계약에 token-weather.readings가 선언되어 있지 않다”는 뜻입니다. 타입 계약과 매니페스트의 types 연결을 확인합니다. 이를 생략하면 claude plugin validate가 중단됩니다.

판단 연습 09아직 답하지 않음

호스트의 API 선언 폴더가 생겼는데, validate가 readings 상태 미선언 오류를 냅니다. 어디를 확인해야 할까요?

생각의 단서

자동 생성된 API 안내와 내 데이터 구조 선언은 다릅니다.

10제작 4단계 · 완성 모듈

숫자 하나가 화면까지
어떻게 도착할까요?

약 80줄의 Token Weather를 여섯 덩어리로 읽습니다. 다음 코드 블록을 순서대로 합치면 전체 모듈이 됩니다. 코드의 기능은 원문과 같습니다.

hooks/token-weather.mjs · 모든 구현 포함
01

01 · 기준표와 상태의 이름

값이 어디까지일 때 어떤 예보를 보여줄지 정합니다. upTo는 상한이며 비교는 “미만”입니다. 25%는 맑음이 아니라 흐림입니다. Infinity는 마지막 구간에 상한을 두지 않는 값입니다.

완성 모듈 · 01 · 기준표와 상태의 이름
// Token Weather: a live forecast of the context window, above the prompt.
const HISTORY = 12;
const BARS = "▁▂▃▄▅▆▇█";
const FORECAST = [
  { upTo: 25, icon: "☀", word: "Clear", color: "yellow" },
  { upTo: 50, icon: "☁", word: "Cloudy", color: "cyan" },
  { upTo: 75, icon: "☂", word: "Showers", color: "blue" },
  { upTo: 90, icon: "☇", word: "Storm", color: "magenta" },
  { upTo: Infinity, icon: "↯", word: "Compact soon", color: "red" },
];

// Held by the host, so the history survives a hot reload of this file.
const readings = { plugin: "token-weather", key: "readings" };
02

02 · 세 가지 사건을 연결

세션 시작과 메인 턴 완료 뒤에 측정합니다. 화면을 그릴 때는 저장한 값을 읽습니다. 설문이 표시줄을 쓰거나 이력이 비었으면 next(e)로 자리를 양보합니다.

완성 모듈 · 02 · 세 가지 사건을 연결
export function register(on) {
  on("session.start", async ($, e, next) => {
    const result = await next(e);
    await takeReading($);
    return result;
  });

  on("turn.complete", async ($, e, next) => {
    const result = await next(e);
    if (!e.agentId) {
      await takeReading($); // main-loop turns only, not subagents
    }
    return result;
  });

  on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
    const { value: history = [] } = await $.state.get(readings);
    if (e.props.hasSurvey || history.length === 0) {
      return next(e);
    }
    const { Box, Text } = $.ui.resolve(e);
    return band(Box, Text, history, e.props.bodyColumns);
  });
}
03

03 · 측정하고 최근 12개만 남기기

창 크기가 없으면 계산하지 않습니다. ??는 값이 null이나 undefined일 때 기본값을 선택합니다. 새 측정값을 붙인 뒤 slice(-HISTORY)로 마지막 12개만 남깁니다.

완성 모듈 · 03 · 측정하고 최근 12개만 남기기
async function takeReading($) {
  const { context } = await $.session.usage();
  if (!context?.window) return;
  const tokens = context.tokens ?? 0;
  const percent = context.percent ?? Math.round((tokens / context.window) * 100);
  const { value: history = [] } = await $.state.get(readings);
  await $.state.set(readings,
    [...history, { tokens, window: context.window, percent }].slice(-HISTORY));
}
04

04 · 화면 너비에 맞게 정보 줄이기

먼저 예보·사용률·토큰을 그립니다. 실제 표시줄 너비가 60열 이상일 때만 최근 턴 그래프와 증감량을 덧붙입니다. 패널이 열리면 표시줄은 전체 터미널보다 좁아질 수 있습니다.

완성 모듈 · 04 · 화면 너비에 맞게 정보 줄이기
function band(Box, Text, history, columns) {
  const now = history[history.length - 1];
  const f = FORECAST.find((b) => now.percent < b.upTo);
  const parts = [
    Text({ color: f.color, bold: true, children: `${f.icon} ${f.word}` }),
    Text({ children: ` ${now.percent}% of context` }),
    Text({ dimColor: true, children: ` ${short(now.tokens)} / ${short(now.window)}` }),
  ];
  if (columns >= 60) {
    parts.push(Text({ dimColor: true, children: " last turns " }));
    parts.push(Text({ color: f.color, children: sparkline(history) }));
    if (history.length > 1) {
      parts.push(Text({ dimColor: true, children: trend(history) }));
    }
  }
  return Box({ flexDirection: "row", paddingX: 1, children: parts });
}
05

05 · 작은 그래프는 상대적인 높이

최근 이력에서 가장 큰 토큰 수를 기준으로 높이를 나눕니다. 가장 높은 막대가 항상 컨텍스트 100%를 뜻하지는 않습니다. 절대 사용률은 옆의 %에서 읽습니다.

완성 모듈 · 05 · 작은 그래프는 상대적인 높이
function sparkline(history) {
  const top = Math.max(...history.map((r) => r.tokens), 1);
  return history.map((r) =>
    BARS[Math.floor((r.tokens / top) * (BARS.length - 1))]).join("");
}
06

06 · 증가, 감소, 그대로를 구분

마지막 두 턴의 차이를 표시합니다. 변화가 없으면 steady, 증가면 ▲, 감소면 ▼입니다. short는 큰 수를 k 또는 M 단위로 줄이고 소수 한 자리까지 표시합니다.

완성 모듈 · 06 · 증가, 감소, 그대로를 구분
function trend(history) {
  const delta = history[history.length - 1].tokens - history[history.length - 2].tokens;
  if (delta === 0) return " steady";
  return delta > 0 ? ` ▲ +${short(delta)} last turn` : ` ▼ ${short(-delta)} last turn`;
}

function short(n) {
  if (n >= 1_000_000) return `${+(n / 1_000_000).toFixed(1)}M`;
  if (n >= 1_000) return `${+(n / 1_000).toFixed(1)}k`;
  return String(n);
}
이벤트 최상위e.component · e.surface · e.requestId · e.viewport
컴포넌트가 받은 속성e.props.hasSurvey · e.props.bodyColumns

hasSurvey는 설문이 표시줄을 쓰려 한다는 신호입니다. bodyColumns는 현재 표시줄의 실제 너비입니다. 그릴 내용이 없을 때도 next(e)로 다음 구현에 넘깁니다. ☀ ☁ ☂ ☇ ↯ 같은 한 칸 기호를 써 터미널 정렬을 맞춥니다.

판단 연습 10아직 답하지 않음

스파크라인의 가장 높은 막대가 표시되고, 사용률은 18%입니다. 두 표시가 모순일까요?

생각의 단서

그래프와 백분율이 각각 무엇을 분모로 삼는지 확인해보세요.

원문 실제 시연 · 표시줄만 살펴보기
claude.dev 원문에서 영상 보기원문 사이트 밖에서는 영상 재생이 막혀 있습니다
Clear 18%, Showers 67%, Storm 81%. 예보 색·토큰 수·최근 턴의 작은 그래프가 한 줄에 어떻게 결합되는지 보세요.
원본 영상 열기
원문 실제 시연 · Token Weather · 터미널
claude.dev 원문에서 영상 보기원문 사이트 밖에서는 영상 재생이 막혀 있습니다
200k 창에서 Python 파일을 더 읽는 세 턴. 18%→67%→81%로 표시가 변합니다.
원본 영상 열기
원문 실제 시연 · Token Weather · 데스크톱
claude.dev 원문에서 영상 보기원문 사이트 밖에서는 영상 재생이 막혀 있습니다
기상 관측 코드, 로그·측정값, 테스트를 차례로 읽습니다. 10%→54%→77%로 표시가 변합니다.
원본 영상 열기
11제작 5단계 · 검증과 테스트

검증은 설계도를 보고,
테스트는 작동을 봅니다.

선이 연결되었다고 전등이 켜지는 것은 아닙니다. 파일과 선언이 맞는지 검사하는 일과, 입력을 바꾸었을 때 화면이 바뀌는지 시험하는 일은 다릅니다.

01

validate는 무엇이 연결됐는지 읽습니다.

실행
claude plugin validate ./token-weather
원문 검증 출력 · 이해를 돕기 위한 예시
> types ./types/index.d.ts declares state: token-weather.readings
> ./token-weather.mjs hooks: session.start, turn.complete,
  ui.render{component=AbovePrompt}
> ./token-weather.mjs calls: $.session.usage (via takeReading),
  $.state.get, $.state.set (via takeReading), $.ui.resolve
> ./token-weather.mjs state writes: token-weather.readings
> ./token-weather.mjs state reads: token-weather.readings
√ Validation passed

매니페스트와 모듈 소스를 읽어 훅, API 호출, 상태 읽기·쓰기를 보여줍니다. 여기의 통과 메시지는 원문 예시이며, 이 웹페이지가 실제 플러그인을 실행한 결과가 아닙니다.

02

test는 실제 런타임에서 값을 바꿉니다.

시작36.1k → Clear200k 창, 120열 표시줄
입력 변경134.4ksession.usage 응답을 제어
확인67% · Showers▲ +98.3k도 확인

claude plugin test는 실제 Claude Code 런타임에서 *.test.ts를 실행합니다. 테스트의 on 훅은 모드 뒤에 등록되어 호스트 응답을 대신합니다. 외부 값에 운을 맡기지 않고 원하는 입력으로 동작을 시험합니다.

03

시험은 “갱신”까지 확인합니다.

원문 전체 테스트 코드 · 줄바꿈만 정리
// tests/token-weather.test.ts
import { describe, expect, test } from "claude-code/testing";

describe("token-weather", () => {
  test("the band follows the context window", async ($, on) => {
    // Hooks registered here run after the mod and stub what Claude Code would answer.
    let tokens = 36_100;
    on("session.start", ($, e) => ({ cwd: e.cwd }));
    on("session.usage", () => ({
      value: {
        startedAt: 0,
        rateLimits: [],
        context: { tokens, window: 200_000, percent: Math.round(tokens / 2_000) },
      },
    }));
    on("turn.complete", () => ({ text: "" }));

    await $.session.start({ surface: "terminal", isInteractive: true, cwd: "/work" } as any);
    const ui = await $.ui.mount({
      plugin: "token-weather",
      surface: "terminal",
      component: "AbovePrompt",
      props: { hasSurvey: false, isWorking: false, maxRows: 10, bodyColumns: 120 },
    } as any);
    expect(await ui.find({ type: "Text", text: /Clear/ })).toBeDefined();

    tokens = 134_400;
    await $.turn.complete({ reason: "answer", answer: "ok", durationMs: 1 } as any);
    expect(await ui.find({ type: "Text", text: /Showers/ })).toBeDefined();
    expect(await ui.find({ type: "Text", text: /67% of context/ })).toBeDefined();
    expect(await ui.find({ type: "Text", text: /▲ \+98\.3k last turn/ })).toBeDefined();
    await ui.unmount();
  });
});

시작 화면, 변경된 예보, 사용률, 증감량을 각각 확인합니다. 마지막에는 ui.unmount()로 정리합니다.

실행과 원문 결과 예시
$ claude plugin test ./token-weather
(pass) token-weather > the band follows the context window
 1 pass
 0 fail

별도 다시 그리기 요청이 없어도 turn.complete 뒤 화면이 바뀌는지 확인합니다. 이 테스트는 3단계에서 설명한 상태 구독과 자동 갱신도 검증합니다.

04

화면이 안 보이면 원인을 좁힙니다.

현재 타입 선언

모드를 로드할 때 생성되는 .claude-plugin/types/를 기준으로 이벤트·API·props를 확인합니다. 편집기와 tsc -p에서도 사용할 수 있습니다.

속성 위치

hasSurvey와 bodyColumns는 e 자체가 아니라 e.props 안에 있습니다.

재로딩

register와 session.start가 다시 실행됩니다. 이력 초기화와 중복 등록 문제를 살펴봅니다.

로그

claude --debug로 반환한 요소 트리가 검증에 실패했는지 읽습니다.

판단 연습 11아직 답하지 않음

validate는 통과했지만 턴 완료 뒤 화면이 바뀌지 않습니다. 무엇을 추가 확인해야 할까요?

생각의 단서

연결 확인과 입력 변화에 대한 결과 확인은 다릅니다.

12Blast Radius · 구현 원리

기다림도, 취소도,
실행 흐름의 일부입니다.

앞에서 본 실행 전 확인을 코드로 읽습니다. 위험을 분류하고, 영향을 측정하고, 결정을 기다린 다음에 원래 실행을 넘깁니다.

01

위험 명령의 범위를 정합니다.

rm -rfgit reset --hardgit clean강제 push데이터베이스 마이그레이션

Blast Radius는 Bash의 tool.call, Pane과 AbovePrompt의 ui.render라는 세 훅을 사용합니다. 분류 결과가 없으면 바로 next(e), 위험 후보이면 확인을 거칩니다. 진행을 선택하면 원래 명령을 그대로 넘깁니다. 취소하면 Claude에게 거부 사유를 전달합니다.

02

보류 훅의 전체 흐름을 읽습니다.

원문 핵심 훅 · 발췌 코드
on("tool.call", { tool: "Bash" }, async ($, e, next) => {
  const risk = classify(String(e.command ?? ""));
  if (risk === null) return next(e); // everything else runs as normal

  const report = await measure($, risk, await $.session.cwd());
  held = { command: e.command, risk, report, decision: null };
  const opened = await $.ui.open({
    id: "blast-radius", title: "Blast Radius", focus: true,
  });
  if (!opened.isPlaced) held.where = "band";

  while (held.decision === null && !next.signal.aborted) {
    await $.process.run(["sleep", "0.25"]);
  }
  if (held.decision === "proceed") return next(e);
  return {
    deny: `Blast Radius held this command: the user pressed Cancel. It would have: ${report.summary}.`,
  };
});

classify, measure, held와 렌더 훅은 모드의 다른 부분에 정의됩니다. 이 코드는 핵심 흐름을 설명하는 원문 발췌이며 독립 실행 가능한 전체 모드가 아닙니다.

classify 명령 텍스트를 읽고 위험을 분류합니다.

measure 작업 위치에서 영향을 측정합니다.

held 보류한 명령과 사용자 결정을 묶습니다.

$.ui.open 확인 패널을 엽니다.

while 결정이나 중단 신호까지 기다립니다.

return 진행은 next(e), 나머지는 거부로 응답합니다.

03

측정은 도구의 사전 확인 기능을 씁니다.

보고서는 git status --porcelain, git clean -n, git log HEAD..origin/main, du, showmigrations 같은 도구의 결과에서 얻습니다. 명령마다 제공하는 확인 기능을 선택합니다.

인수는 배열로 분리 · 설명용 예시
await $.process.run(["git", "clean", "-n"]);

원문 핵심 원리는 argv 배열입니다. 셸 문자열을 조립해 경로 내용을 실행 코드로 만드는 방식과 구분합니다.

04

사용자가 생각하는 동안 훅을 살려둡니다.

문서 기준 훅은 이벤트 처리마다 자체 실행 시간 10초가 주어집니다. $ API 호출 안에서 기다리는 시간은 여기에 포함되지 않습니다. 그래서 짧은 sleep 프로세스로 기다리고, 버튼의 onPress가 결정을 바꿉니다. Esc로 next.signal이 중단되면 대기를 끝냅니다.

버튼 구성 · 원문의 API 예시
Button({ label: "Proceed", hotkey: "1", onPress })

Proceed는 1, Cancel은 2로 선택합니다. 클릭뿐 아니라 Tab과 Enter, 숫자 단축키로도 조작할 수 있습니다.

05

같은 보고서를 들어갈 자리에 놓습니다.

넓은 화면
대화확인 패널

대화 옆 패널에 표시.

공간 부족
프롬프트 위 보고서프롬프트

isPlaced: false면 표시줄 사용.

120열 터미널의 원문 화면에서는 git reset --hard 영향 보고서가 프롬프트 위에 표시됩니다. 특정 전체 화면 너비만으로 배치를 단정하지 않고 실제 배치 결과를 확인합니다.

120열 터미널에서 git reset --hard가 커밋하지 않은 두 파일의 변경을 없앤다는 보고서를 프롬프트 위에 표시한 실제 화면
PDF 19쪽 · 원문 실제 화면. 120열에서도 옆 패널 대신 표시줄을 사용합니다. NOTES.md와 src/math.js, 두 파일의 커밋하지 않은 변경이 사라진다는 영향을 먼저 보여줍니다. 1은 진행, 2는 취소입니다. 화면의 “복구할 수 없음” 경고는 이 명령으로 사라질 변경의 중요성을 알립니다.
06

감지와 강제 차단은 역할이 다릅니다.

한계도 설계의 일부

명령 텍스트를 보기 때문에 $(…), 별칭, 내부에서 rm을 호출하는 스크립트는 감지를 피할 수 있습니다. 이 모드는 보조 안전장치입니다. 원문은 필수 차단을 권한 규칙과 구분해 설계하라고 설명합니다. 현재 공식 문서에서는 Bash 패턴 규칙에도 다른 호출 형태를 놓치는 한계가 있으며, Mod가 권한 판단에 개입할 수 있다고 명시합니다. 규칙 하나나 이 미리보기만으로 모든 실행 경로의 차단을 보장하지 않습니다.

판단 연습 12아직 답하지 않음

확인 패널을 열었지만 isPlaced가 false입니다. 원문 설계에서 어떻게 대응하나요?

생각의 단서

판단 내용은 유지하고 표현할 위치를 바꿀 수 있습니다.

원문 실제 시연 · Blast Radius · 터미널
claude.dev 원문에서 영상 보기원문 사이트 밖에서는 영상 재생이 막혀 있습니다
rm -rf build로 파일 9개, 총 1.1 MB가 삭제될 예정입니다. 먼저 Cancel로 거부하고, 다음 시도에서 Proceed로 진행합니다.
원본 영상 열기
원문 실제 시연 · Blast Radius · 데스크톱
claude.dev 원문에서 영상 보기원문 사이트 밖에서는 영상 재생이 막혀 있습니다
밝은 화면의 카드에서 파일 9개, 총 498 KB를 확인합니다. 취소와 진행이 원래 호출의 흐름을 바꿉니다.
원본 영상 열기
13Replay Theater · 구현 원리

여러 편집을 묶으면
한 턴의 이야기가 됩니다.

한 번의 작업에서 파일 여러 개를 바꾸더라도 기록은 한 턴 단위로 묶습니다. 기록 수집, 검토 준비, 명령 실행의 역할을 나눠봅니다.

01

바꾸기 전에 이전 값을 읽습니다.

Replay Theater는 Edit와 Write 호출의 파일 경로, 변경 전후 텍스트를 기록합니다. Write는 덮어쓰기가 반영되기 직전 $.fs.read로 기존 파일 내용을 읽어야 실제 이전 값과 새 값을 비교할 수 있습니다.

02

수집·확정·표시를 분리합니다.

원문 핵심 훅 · 발췌 코드
on("tool.call", async ($, e, next) => {
  if (EDIT_TOOLS.has(e.tool)) {
    state.pending.push(...(await stepsFor($, e))); // old/new text → diff
  }
  return next(e); // the edit runs untouched
});

on("turn.start", ($, e, next) => {
  if (!e.agentId) state.pending = [];
  return next(e);
});

on("turn.complete", async ($, e, next) => {
  const r = await next(e);
  if (!e.agentId && state.pending.length) state.replay = state.pending;
  return r;
});

on("session.start", async ($, e, next) => {
  const r = await next(e);
  await $.command.register({
    name: "replay", description: "Step through the last turn's file edits",
  });
  return r;
});
on("command.run", { command: "replay" }, async ($, e) => ({
  text: (await openReplay($)) ? "Replaying" : "No edits",
}));

EDIT_TOOLS, stepsFor, state, openReplay는 모드의 다른 부분에서 정의됩니다. 발췌문의 state 객체를 재로딩에 안전한 저장소 선언과 혼동하지 마세요. 이 코드만으로 완성된 플러그인이 되는 것은 아닙니다.

turn.start 메인 턴의 임시 편집 목록을 비웁니다.

tool.call 변경을 기록하고 원래 편집을 넘깁니다.

turn.complete 편집이 있으면 이번 턴을 리플레이로 확정합니다.

session.start /replay 명령을 등록합니다.

command.run 명령을 직접 처리해 패널을 엽니다.

03

등록과 실행은 다른 사건입니다.

$.command.register

“replay라는 명령이 있다”는 메뉴를 준비합니다.

command.run

사용자가 그 명령을 실행했을 때 응답합니다.

턴이 끝나면 프롬프트 위 안내가 나타납니다. r을 누르거나 /replay를 입력하면 번호가 붙은 단계 표시줄과 Prev, Next, Close가 있는 패널을 엽니다. 편집이 없으면 No edits로 응답합니다.

04

화면이 달라도 같은 요소를 그립니다.

전체 화면
대화diff 단계

패널이 오른쪽에 붙습니다.

80열 터미널
인라인 diff 단계프롬프트

프롬프트 위에 인라인으로 열립니다.

모드는 같은 요소 트리를 그리고, 화면이 배치를 담당합니다. e.agentId로 서브에이전트 턴이 메인 기록과 섞이지 않게 합니다. 이 모드는 편집을 관찰하며 막거나 바꾸지 않습니다.

80열 터미널에서 5개 편집 중 2단계의 greet에서 welcome으로 변경한 diff와 Prev Next Close 버튼을 프롬프트 위에 표시한 실제 화면
PDF 21쪽 · 원문 실제 화면. 80열에서는 인라인 패널로 표시됩니다. 지금은 5개 편집 중 2단계이며, greet.js의 한 줄에서 greet(n)이 welcome(n)으로 바뀐 기록을 보여줍니다. Prev·Next는 기록 탐색이고 실제 파일 복원 버튼이 아닙니다.
판단 연습 13아직 답하지 않음

파일을 덮어쓰는 Write 호출에서 정확한 변경 전후 diff를 만들려면 언제 이전 내용을 읽어야 할까요?

생각의 단서

비교할 옛 값이 사라지는 시점을 생각해보세요.

원문 실제 시연 · Replay Theater · 터미널
claude.dev 원문에서 영상 보기원문 사이트 밖에서는 영상 재생이 막혀 있습니다
파일 3개에서 greet→welcome 이름을 바꾼 편집 5개를 1~5단계로 검토합니다. 자홍 안내, 패널 테두리, Prev·Next·Close를 확인합니다.
원본 영상 열기
원문 실제 시연 · Replay Theater · 데스크톱
claude.dev 원문에서 영상 보기원문 사이트 밖에서는 영상 재생이 막혀 있습니다
파일 4개의 편집 6개를 1~6단계로 검토합니다. Replay 버튼에서 패널로 연결되고 같은 변경 전후를 보여줍니다.
원본 영상 열기
14제작 6단계 · 배포와 응용

나의 질문을 기능으로,
기능을 다른 사람에게.

앞에서는 무엇이 가능한지 배웠습니다. 마지막에는 유지·설치·신뢰를 확인하고, 새로운 문제에 이벤트와 API를 연결합니다.

01

임시 제작과 계속 사용을 구분합니다.

claude로 시작해 원하는 모드를 설명하면 API를 미리 알지 않아도 제작을 요청할 수 있습니다. “무엇을 보여줄지”를 설명하세요. 내장 작성 가이드가 이벤트 선택, 재로딩 상태 보관, validate 사용법을 안내합니다.

핫 리로딩을 허용하면 턴이 끝난 뒤 표시줄이 나타나고, 이후 수정이 바로 반영됩니다. 이 빠른 방법은 현재 세션에만 로드되며 폴더가 나중에 정리될 수 있습니다. 계속 쓰려면 폴더를 다른 곳에 복사해 일반 플러그인처럼 설치합니다.

앞의 제작 요청에서 표시 요구사항을 바꾸면 내 모드가 됩니다. 예보 임계값이나 비용 표시 같은 작은 수정부터 시작하세요.

02

마켓플레이스는 배포 목록입니다.

my-mods/.claude-plugin/marketplace.json설치 목록
my-mods/token-weather/완성 플러그인 폴더
.claude-plugin/marketplace.json
{
  "name": "my-mods",
  "owner": { "name": "You" },
  "plugins": [{ "name": "token-weather", "source": "./token-weather" }]
}
로컬 목록 추가 → 사용자 설치
claude plugin marketplace add ./my-mods
claude plugin install token-weather@my-mods --scope user

@ 앞은 플러그인 이름, 뒤는 마켓플레이스 이름입니다. 목록을 추가하는 단계와 실제 설치를 구분합니다.

03

저장소를 공유하면 같은 방법으로 설치합니다.

마켓플레이스 파일과 플러그인을 GitHub 저장소에 올리면 저장소가 마켓플레이스가 됩니다. 일반 push로 업데이트할 수 있습니다. 설치한 사용자는 다음 세 명령으로 추가·설치·재로딩합니다.

Claude Code 안에서 실행
/plugin marketplace add your-org/my-mods
/plugin install token-weather@my-mods
/reload-plugins

your-org/my-mods는 자신의 조직·저장소 이름으로 바꿉니다. 재로딩하면 모드가 시작됩니다. 나타나지 않으면 Claude Code를 재시작합니다.

04

설치 전 코드와 배포자를 확인합니다.

모드는 내 컴퓨터의 Claude Code 안에서 실행되는 코드이고 Claude Code와 같은 접근 권한을 갖습니다. 작성자는 Anthropic이 아니라 해당 배포자입니다. 저장소를 읽고 신뢰할 수 있는 사람이 만든 모드를 설치하세요. 설치 명령을 실행하기 전에는 아무것도 설치되지 않습니다.

Claude 디렉터리 제출에서 모드가 포함된 플러그인을 등록할 수도 있습니다. 매일 쓰는 모드가 되었다면 X나 LinkedIn에 GIF·스크린샷과 마켓플레이스 링크를 공유해 다른 사람이 가능성을 살펴보고 설치할 수 있게 합니다.

05

새 문제를 익숙한 세 가지 판단으로 바꿉니다.

지금 비용과 한도는 어느 정도일까?

비용·한도 계기판

$.session.usage() → $.ui.status

표시줄에 비용·사용 한도를 보여줍니다. 컨텍스트 비율과 계정 한도를 구분하세요.

매번 설명하는 규칙을 줄일 수 있을까?

팀 규칙을 붙이는 요청

prompt.submit

입력을 수정해 다음 처리에 넘깁니다.

Claude는 지금까지 무엇을 읽었을까?

읽은 파일 목록

도구 호출 관찰 → 패널

세션에서 읽은 파일을 목록으로 모아 표시합니다.

긴 작업이 끝났음을 놓치지 않으려면?

완료 알림 타이머

turn.complete → $.ui.toast

작업 완료 시 알림을 표시합니다.

어느 서버에 적용하는 작업일까?

운영 환경 보호 장치

tool.call

kubectl 컨텍스트나 terraform apply 같은 스택별 확인 장치를 만듭니다.

“언제 개입할까, 무엇을 넘길까, 어디에 남길까”를 먼저 정합니다. 그 다음에 이벤트·상태·UI를 선택하면 API 이름에 끌려 기능을 만드는 일을 줄일 수 있습니다.

사례

세 예제 밖에서도 같은 기능이 쓰입니다.

기본 제공 Mod

/diff 검토 화면

세션의 커밋하지 않은 변경을 패널에서 검토합니다. 직접 명령 처리와 UI 확장이 한 기능으로 묶인 사례입니다.

기본 제공 Mod

AGENTS.md 읽기

프로젝트 CLAUDE.md가 없을 때 AGENTS.md의 프로젝트 지시를 읽습니다. Mods는 시각적 패널뿐 아니라 Claude에게 전달할 정보에도 개입합니다.

커뮤니티 Mod

Next steps

턴이 끝나면 다음 요청을 최대 3개 제안합니다. 1·2·3은 해당 요청을 입력 초안에 넣고, 0은 닫습니다. 제안 선택은 요청 자동 제출과 다릅니다.

공식 Mods 카탈로그의 시연·기능 설명 · 카탈로그가 연결하는 Next steps 소스

카탈로그는 실제 사용 모습을, API 문서는 구현 계약을 확인하는 데 씁니다. 예제 저장소 링크는 공식 문서에서 확인했으며, 이 페이지의 코드 검토는 원문에 실린 구현을 기준으로 합니다.

판단 연습 14아직 답하지 않음

$.state에 이력을 저장하는 비용 계기판을 만들어 동료가 계속 쓰게 하려 합니다. 가장 적절한 마무리는?

생각의 단서

기능을 보여주는 것과 설치 가능한 형태로 제공하는 것은 다릅니다.

실전에서 확인할 수 있는 이해

설명하고, 만들고, 검증할 수 있나요?

  1. 새 요구사항을 관찰·수정·직접 응답 중 하나로 설명한다.
  2. 이벤트와 개입 시점을 고른다.
  3. 재로딩에도 남겨야 할 상태와 타입 계약을 정한다.
  4. 예시 입력을 바꾸어 UI가 갱신되는 테스트를 읽는다.
  5. 모드가 놓칠 수 있는 실행 경로와 권한 규칙의 역할을 구분한다.
  6. 설치 가능한 플러그인으로 묶고 배포자를 확인한다.

객관식 정답은 원리를 확인하는 출발점입니다. 자신의 요구사항으로 모드를 만들고 검증하면 이해를 실제 능력으로 옮길 수 있습니다.

더 읽을 공식 레퍼런스 · 2026.10.08 확인

동작은 시연으로,
구현은 내 버전의 API로.

튜토리얼의 코드와 최신 문서는 버전이 다를 수 있습니다. 현재 레퍼런스는 v2.1.290 기준이며, 내 Claude Code가 생성한 .claude-plugin/types/ 선언을 우선합니다. 원문 내용은 유지하되 최신 설명과 달라진 부분은 본문에서 구분했습니다.

이제 가져갈 세 가지 질문

언제 개입할까?
무엇을 넘길까?
어디에 남길까?

시점실행 전 판단인가,
실행 후 관찰인가.

전달그대로 넘기는가,
바꾸는가, 여기서 답하는가.

보관재로딩되어도
남아야 하는 값인가.

판단 연습 14개가 이 원리를 새로운 상황에 적용하도록 돕습니다.
첫 원리 다시 살펴보기