2026-10-13 Claude Code 모드와 pinboard 한글판 만들기
Claude Code에 모드(mod)라는 확장 방식이 생겼다. 처음에는 개념만 살펴보고 "아직 끌리지 않는다"며 미뤄 두었는데, 커뮤니티 모드를 둘러보다가 pinboard 하나를 설치해 써 보게 됐다. 써 보니 화면이 전부 영어인 게 불편해서 결국 한글판으로 고쳐 쓰고 있다. 오늘은 모드가 무엇인지, pinboard가 어떻게 동작하는지, 그리고 어디를 어떻게 고쳤는지 정리한다.
확인 환경: Claude Code 2.1.295(Windows에서 pinboard 설치·수정, 모드 API 설명은 같은 빌드의 내장 모드 제작 문서로 대조). 모드 API는 얼리 액세스라 버전이 바뀌면 이름이나 동작이 달라질 수 있다.
1. Claude Code 모드란
모드는 Claude Code 안에 TypeScript 코드를 끼워 넣어 화면과 동작을 바꾸는 플러그인이다. 만들 수 있는 것은 대략 이렇다.
- 화면: 사이드 패널(Pane), 프롬프트 위 띠(AbovePrompt), 상태 줄, 토스트
- 명령: 슬래시 명령, 모델이 부를 수 있는 도구
- 동작: 도구 호출 막기·고치기, 프롬프트·시스템 프롬프트 수정
훅 하나의 모양
모드의 핵심은 on(event, matcher?, hook) 한 줄이다. 어떤 이벤트에 훅을 걸고, 훅은 항상 ($, e, next) 세 개를 받는다.
$: 엔진 인터페이스. 패널 열기($.ui.open), 명령 등록($.command.register), 도구 등록($.tool.register)처럼 바깥 세계에 닿는 일은 전부 여기를 거친다.e: 이벤트 입력. 얼려 있는(frozen) 평범한 값이다.next(e): 아래에 있는 다른 플러그인과 엔진 기본 동작을 실행하고 그 결과를 돌려준다.
on('tool.call', { tool: 'Bash' }, async ($, e, next) => { // 1) next를 부르지 않고 직접 답하면 막을 수 있다 // return { deny: '이 명령은 허용하지 않습니다' } // 2) 값을 바꿔서 넘기면 뒤에서 보는 입력이 바뀐다 // return next({ ...e, command: e.command.trim() }) // 3) 그대로 넘기고 결과를 보고 반응할 수도 있다 const result = await next(e) return result})백엔드 개발자 입장에서는 서블릿 필터나 Spring의 인터셉터 체인과 비슷하게 읽힌다. next를 부르면 체인을 계속 타고, 안 부르면 거기서 응답을 끝낸다.
파일 구성
모드 하나는 파일 세 개로 시작한다.
<mod>/├── .claude-plugin/plugin.json # 이름, 버전, 설명├── hooks/hooks.json # { "modules": ["./register.tsx"] }└── hooks/register.tsx # export const register: Register = (on) => { ... }세션 동안 값을 들고 있으려면($.state) 네 번째 파일로 types/index.d.ts 타입 계약을 두고, plugin.json에서 그 파일을 가리킨다. 모드 안에는 DOM도 Node도 없고, 화면은 JSX로 그리되 Box, Text, Button 같은 요소를 $.ui.resolve(e)에서 받아 쓴다. 모드 UI는 터미널과 Claude Desktop 앱의 Code 탭에서 표시된다. VS Code 확장의 채팅 패널에서는 훅은 실행되지만 모드 UI는 표시되지 않는다. VS Code 통합 터미널에서 Claude Code CLI를 실행하는 경우에는 UI가 표시된다. 공식 지원 범위를 참고하면 이 차이를 확인할 수 있다.
설치 전에 볼 것
모드는 파일 읽기·쓰기, 프로세스 실행, 네트워크에 닿을 수 있다. 그래서 남이 만든 모드는 설치 전에 소스와 접근 범위를 먼저 보는 게 좋다. 비공식 카탈로그인 awesome-claude-code-mods(웹: mods.aidojo.si)는 모드마다 접근 범위와 claude plugin validate 결과를 보여 줘서 이 확인에 쓸 만했다. 다만 소스만 정적으로 검사한 결과라 통과했다고 안전이 보장되지는 않는다.
2. pinboard: 대화 옆에 고정하는 보드
pinboard(Jerad Bitner, MIT)는 대화가 스크롤되어도 사라지지 않게 열린 결정, 할 일, 만든 링크를 사이드 패널에 고정하는 모드다. 여러 모드 중 이걸 고른 이유는 단순했다. 써 보고 싶은 기능이었고, validate 결과 파일·프로세스·네트워크 접근이 없어 부담이 작았다. 여러 개를 한꺼번에 깔지 않고 이것 하나만 user 범위로 설치해 시험했다.
register.tsx 한 파일(241줄)을 읽어 보면 모드 API가 거의 다 들어 있어서 교재로도 좋았다.
| 하는 일 | 쓰는 훅 |
|---|---|
/pinboard 명령과 update 도구 등록 | session.start |
| 명령을 받으면 패널 열기 | command.run → $.ui.open |
| 요청마다 현재 보드를 시스템 프롬프트 끝에 붙이기 | prompt.compose |
모델이 부른 update 도구로 보드 갱신 | tool.call |
| 질문으로 끝났는데 열린 결정이 없으면 모델을 한 번 되돌리기 | classic.Stop |
gh pr create, git push 결과에서 링크 잡기 | tool.call (모든 도구) |
| 패널 그리기 | ui.render (Pane) |
재미있었던 부분은 두 가지다.
- 질문을 놓치지 않게 하는 Stop 훅: 답이 사용자에게 묻는 질문으로 끝났는데 보드에 열린 결정이 없으면, 턴을 끝내지 못하게 막고 "그 질문을 보드에 고정하라"는 문구를 돌려준다. 질문이 대화 위로 밀려 사라지는 걸 막는 장치다.
- 할 일은 강제하지 않는다: 할 일은 훅으로 강제하지 않고, 도구 설명에 적힌 규칙(3단계 이상일 때 쓰기, 한 번에 하나만 진행, 확인 뒤 완료)을 보고 모델이 스스로 판단해 붙인다.
3. 한글판 pinboard-ko로 고치기
왜 고쳤나
써 보니 두 가지가 걸렸다.
- 패널과 대화 줄이 전부 영어라 읽기 불편했다. 한국어로 일하는데 보드만 영어인 게 어색했다.
- 할 일을 끝내면 패널에
✓ 3 done한 줄만 남아서, 방금 무엇을 끝냈는지가 사라졌다.
pinboard에는 설정 메뉴가 없어서 고치려면 hooks/register.tsx를 직접 수정해야 한다. MIT 라이선스라 고쳐 써도 된다.
캐시가 아니라 복사본을 고친다
설치된 플러그인은 ~/.claude/plugins/cache/... 아래에 있는데, 여기를 고치면 업데이트 때 덮어써진다. 그래서 소스를 학습용 폴더로 복사해 고쳤다.
복사본이 원본과 같은 이름을 쓰면 둘이 충돌하므로 이름이 들어가는 곳을 전부 새 이름으로 바꿨다.
-const PANE = 'pinboard'-const TITLE = 'Pinboard'-const TOOL = 'mcp__pinboard__update'+const PANE = 'pinboard-ko'+const TITLE = '핀보드'+const TOOL = 'mcp__pinboard-ko__update'-const decisions = atom({ plugin: 'pinboard', key: 'decisions' } as const, [] as Decision[])+const decisions = atom({ plugin: 'pinboard-ko', key: 'decisions' } as const, [] as Decision[])빠뜨리기 쉬운 곳은 이렇다.
- 플러그인 이름(
plugin.json)과 패널 id - 도구 이름: 등록은
update로 하지만 실제 이름은mcp__<플러그인>__update라서 플러그인 이름을 바꾸면 따라 바뀐다. 코드에 하드코딩된TOOL상수와 Stop 훅 문구 안의 도구 이름도 같이 바꿔야 한다. atom의plugin키와 타입 계약의PluginState키- 슬래시 명령(
/pinboard→/pinboard-ko)과 시스템 프롬프트 섹션 id
로컬 폴더를 claude plugin marketplace add <폴더>로 마켓플레이스에 추가해 설치하면 설치본이 그 폴더를 직접 읽는다. 그래서 수정한 뒤에는 /reload-plugins만 하면 바로 반영된다. 원본 pinboard는 claude plugin disable로 꺼 두었다.
화면은 한글로, 모델용 요약은 영어로
화면에 보이는 문구는 전부 한국어로 바꿨다. 패널 제목(결정 대기, 할 일, 링크), 빈 상태 문구, 비우기 버튼, 대화에 찍히는 한 줄 요약(핀보드: 할 일 +2, 1개 완료), GitHub 링크 라벨(issue → 이슈)이다.
그런데 패널 문구만 바꾸면 항목 내용은 여전히 영어로 들어온다. 항목 글은 모델이 쓰기 때문이다. 그래서 모델이 읽는 도구 설명 끝에 규칙을 한 줄 더했다.
// 한글판: 패널에 그대로 보이는 항목 글을 한국어로 쓰게 한다'Write every todo, decision and decide answer in Korean (keep code, commands, file paths and proper nouns as they are). Keep each one short, one line.',Stop 훅이 모델을 되돌릴 때 쓰는 문구도 한국어로 바꿨다. 반대로 시스템 프롬프트에 붙는 보드 요약(Pinboard now: ...)은 영어로 남겼다. 사람이 아니라 모델만 읽는 글이라 바꿀 이유가 없었다.
끝난 할 일을 남기기
화면 문구 다음으로 고친 게 이 부분이다. 원본은 끝난 할 일을 개수만 남기고 접었다.
- {doneCount > 0 && <Text dimColor>{` ✓ ${doneCount} done`}</Text>}+ {/* 한글판: 끝난 할 일도 글을 남긴다. 남은 일 아래에 흐리게, 배열 순서상 마지막 DONE_SHOWN개까지만 */}+ {doneCount > DONE_SHOWN && <Text dimColor>{` ✓ 이전 ${doneCount - DONE_SHOWN}개 완료`}</Text>}+ {allTodos.filter(t => t.isDone).slice(-DONE_SHOWN).map(t => item('✓', t.text, true))}이제 끝난 할 일이 남은 일 아래에 흐린 ✓ 내용 줄로 남는다. 표시할 줄 수를 제한하려고 DONE_SHOWN = 8을 두어 완료 항목 중 배열 순서상 마지막 8개까지만 글로 보여 주고, 나머지는 ✓ 이전 N개 완료 한 줄로 접는다. 이 코드는 완료 시각순으로 정렬하지 않는다. 원본은 할 일을 추가한 순서를 유지하므로, 여기서 마지막 8개는 최근에 완료한 8개와 다를 수 있다.
검증
claude plugin validate --strict: 통과claude plugin test: 원래 테스트에 한글판 테스트 1개를 더해 10개 통과. 화면 문구를 바꾸면 기존 테스트의 기대값도 같이 바꿔야 한다.
그 뒤 실제로 써 보면서 한국어 패널과 끝난 할 일 목록이 잘 나오는 것을 확인했다.
4. 알게 된 것: 보드는 언제 사라지나
보드 데이터는 $.state에 들어 있다. 이 값은 모드 코드의 변수가 아니라 호스트가 세션 동안 들고 있는 값이라 수명이 이렇게 갈린다.
| 상황 | $.state |
|---|---|
코드 수정 후 핫 리로드, /reload-plugins | 남는다 |
/clear, /resume, 새 세션 | 사라진다 |
리로드되면 모듈은 새로 실행되어 모듈 안의 일반 변수는 처음부터 시작하지만, $.state는 호스트 쪽 값이라 남는다. 세션을 넘어 남기고 싶으면 $.store를 써야 한다. 또 상태 공간이 플러그인 이름별로 나뉘어 있어서, 이름을 pinboard-ko로 바꾸면 원본 pinboard의 보드는 넘어오지 않는다.
$.store에 저장해서 다음 세션에도 보드를 이어 갈까 생각했지만 그대로 두기로 했다. 세션을 넘겨 남기면 지난 할 일이 새 세션마다 따라와서 비우는 규칙을 따로 만들어야 하고, 오래 남길 내용은 이미 진행 기록이나 노트에 따로 적고 있기 때문이다. 핀보드는 지금 세션의 작업대로만 쓰는 게 맞다고 봤다.
정리
- 모드는
on(event, matcher?, hook)과($, e, next)로 Claude Code의 화면과 동작에 끼어드는 TypeScript 플러그인이다.next를 부르면 체인을 계속 타고, 안 부르면 거기서 끝낸다. - 남의 모드를 고칠 때는 설치 캐시가 아니라 복사본을 고치고, 이름이 들어가는 곳을 전부 바꾼다.
- 화면 문구만 바꾸면 반쪽이다. 모델이 쓰는 글은 도구 설명의 규칙으로 바꿔야 한다.
$.state는 세션 범위다. 리로드에는 남고/clear나 새 세션에는 사라진다.
다음에는 다른 모드도 하나씩 시험해 보거나, 처음에 떠올렸던 아이디어(진행 중인 일을 보여 주는 띠 같은 것)를 직접 만들어 볼 생각이다.
오류 제보·의견 보내기
글 제목과 주소가 포함된 메일 초안을 엽니다. 내용과 받는 사람을 확인한 뒤 보내주세요.
받는 사람: [email protected]
메일 앱에서 작성메일 앱이 열리지 않으면 아래 내용을 복사해 평소 쓰는 이메일에 붙여 넣으세요.
관련 글
오늘의 공부 2026-10-06 개발자를 위한 Claude Code 추천 명령어
Claude Code를 많이 쓰는 사람들이 공통으로 추천하는 습관과, 그걸 실천하는 명령어·단축키를 기본부터 최근 추가된 기능까지 공식 문서로 확인해 정리했다.
오늘의 공부 2026-10-07 코틀린 입문: 기본 타입, 함수, Unit, 파일 구조, if와 when
코틀린 입문으로 기본 타입, 함수 작성법, Unit, 파일 구조, if와 when을 공부하고 예제를 직접 컴파일해 결과를 확인하며 정리했다.