Claude Code hooks를 사용하여 모든 AI 코딩 에이전트를 위한 메뉴 바 와처(menu bar watcher)를 구축한 방법
요약
여러 AI 코딩 에이전트를 병렬로 사용할 때 발생하는 상태 확인의 번거로움을 해결하기 위해, Claude Code의 훅(hooks) 시스템을 활용한 메뉴 바 와처(menu bar watcher) 구축 방법을 소개합니다.
핵심 포인트
- Claude Code의 Stop 및 Notification 훅을 사용하여 에이전트 상태 감지
- 기존 워크플로우를 해치지 않는 비침습적(non-intrusive) 도구 설계
- 훅 시스템의 결정론적 특성을 활용한 신뢰할 수 있는 상태 모니터링
- 에이전트의 입력 대기 및 작업 종료 시점을 실시간으로 파악
저는 보통 세네 개의 AI 코딩 에이전트를 동시에 실행합니다. 한 터미널에서는 리팩터링(refactor)을 수행 중인 Claude Code가, 다른 터미널에서는 Codex가, 두 번째 기능 구현에는 Cursor가 열려 있고, 때로는 백그라운드에서 Copilot이 돌아가기도 합니다. 실제 코딩 부분은 훌륭합니다. 저를 지치게 만든 부분은 바로 장부 정리(bookkeeping)였습니다.
에이전트들을 병렬로 실행할 때 발생하는 문제는 이들이 동기화되어 끝나지 않는다는 점입니다. 하나는 마이그레이션(migration)을 실행해도 되는지 묻기 위해 멈춰 서고, 다른 하나는 권한 프롬프트(permission prompt)를 기다리며 조용히 대기합니다. 세 번째는 여전히 작업을 수행 중입니다. 그리고 이 모든 것을 한곳에서 볼 수 있는 단일 장소가 없기 때문에, 결국 누가 나를 필요로 하는지 확인하기 위해 몇 분마다 창을 번갈아 가며 확인하게 됩니다. 한 터미널 작업에 깊이 몰입해 있을 때, 다른 터미널에서 "이 파일을 수정해도 될까요?"라는 질문이 10분 동안 방치되곤 했습니다. 그 시점에서 저는 코딩을 하는 것이 아니라, 돌봄 서비스(babysitting)를 하고 있는 셈이었습니다.
그래서 이를 해결하기 위해 작은 도구를 하나 만들었습니다. 이것이 작동 방식이며, 이 도구를 실제로 유용하게 만든 단 하나의 설계 결정입니다.
기존 도구를 사용하지 않은 이유
여러 에이전트를 통합하는 도구들이 있습니다. 저도 몇 가지 시도해 보았습니다. 그것들은 대부분 에이전트를 래핑(wrapping)하는 방식으로 작동합니다. 즉, 해당 도구의 UI를 통해 모든 것을 실행하고 구동하며, 그 대가로 통합된 뷰(view)를 제공받는 방식입니다.
저에게 그것은 시작조차 할 수 없는 옵션이었습니다. 저는 제가 원하는 방식으로 터미널, 에디터, 멀티플렉서(multiplexer)를 설정하는 데 수년을 보냈습니다. 단지 상태 표시등을 보기 위해 제 전체 워크플로우를 다른 사람의 셸(shell) 안으로 옮기고 싶지 않았습니다. 저는 외부에서 관찰하면서 제가 일하는 방식에는 아무것도 바꾸지 않는 무언가를 원했습니다.
그로 인해 래핑 방식은 제외되었습니다. 그렇다면 질문이 남습니다. 사용자(user)와 에이전트(agent) 사이에 끼어들지 않고 에이전트가 무엇을 하고 있는지 어떻게 알 수 있을까요?
통찰: 에이전트는 이미 자신의 상태를 알리고 있다
무언가를 가로챌(intercept) 필요는 없습니다. 에이전트가 무언가 발생했을 때 이미 알려주기 때문입니다. 여러분은 그저 듣기만 하면 됩니다.
Claude Code에는 훅(hooks) 시스템이 있습니다. 훅(Hooks)은 Claude Code가 수명 주기(lifecycle)의 특정 시점에 자동으로 실행하는 셸 명령(shell commands)이며, JSON 설정 파일에 정의됩니다. 그중 두 가지가 정확히 제가 필요로 했던 것이었습니다:
- Stop은 Claude Code가 응답을 마쳤을 때, 즉 하나의 턴(turn)이 종료되었을 때 실행됩니다.
- Notification은 Claude Code가 입력 대기 중이거나 권한 승인 프롬프트(permission prompt)를 기다리는 등 사용자의 주의가 필요할 때 실행됩니다.
핵심적인 속성은 훅(hooks)이 결정론적(deterministic)이라는 점입니다. 모델이 무언가를 말하기로 결정하는 것에 의존하지 않습니다. 이벤트가 발생하면 명령은 매번 실행됩니다. 이것이 신뢰할 수 있는 상태 표시등과 대부분의 경우에만 맞을 뿐인 표시등의 차이입니다.
훅이 실행될 때, Claude Code는 세션(session) 및 작업 디렉토리(working directory)와 같은 이벤트에 관한 컨텍스트를 포함한 JSON을 표준 입력(stdin)으로 전달합니다. 따라서 훅 핸들러(hook handler)는 이를 읽어 방금 무슨 일이 일어났는지 파악하고 어딘가에 기록할 수 있습니다.
아키텍처: 작은 파일을 작성하고 폴더를 감시하기
이 모든 것을 단순하게 만든 결정은 다음과 같습니다. 앱이 각 에이전트와 직접 통신하게 하는 대신, 각 에이전트의 훅이 아주 작은 상태 파일(status file)을 작성하게 하고, 앱이 해당 파일들이 저장된 폴더를 바라보게 만들었습니다.
Stop 또는 Notification 훅은 해당 세션에 대한 작은 JSON 파일을 작성할 뿐입니다. 대략 다음과 같습니다:
{
"session": "billing-refactor",
"tool": "claude-code",
...
이를 생성하는 훅 설정은 일반적인 Claude Code 설정입니다. 이 아이디어를 단순화하면 다음과 같습니다:
{
"hooks": {
"Notification": [
...
앱은 단 한 가지 일만 수행합니다. 해당 폴더를 감시하며 모든 상태 파일을 하나의 그림으로 통합하는 것입니다. 소켓(sockets)도 없고, 관리해야 할 데몬(daemon)도 없으며, 에이전트가 다른 에이전트에게 말을 거는 구조도 아닙니다. 파일이 나타나거나 변경되면 앱이 폴더를 다시 읽고 아이콘이 업데이트됩니다.
이러한 디커플링(decoupling, 결합 해제) 덕분에 사용하기 매우 쾌적합니다. 에이전트들은 앱이 존재하는지 알지 못합니다. 앱은 파일이 어떻게 그곳에 도달했는지 알지 못합니다. 그리고 나중에 새로운 에이전트를 추가한다는 것은 동일한 폴더에 파일을 떨어뜨리는 훅을 하나 더 작성하는 것만을 의미합니다. 뷰어(viewer)는 전혀 변경될 필요가 없습니다.
이것이 개인정보 보호(privacy)에 관한 이야기가 짧은 이유이기도 합니다. 상태 파일(status files)에는 세션 이름, 도구(tool), 그리고 상태(state) 정보가 담깁니다. 와처(watcher)는 누가 기다리고 있는지를 알려주기 위해 그 이상의 정보가 전혀 필요하지 않기 때문에, 여러분의 코드, 프롬프트(prompts), 또는 모델의 출력값(model's output)은 절대 포함하지 않습니다. 모든 것은 여러분의 기기(machine) 내에 머뭅니다. 계정도 없고 서버도 없습니다.
실제로 여러분이 보게 되는 부분
뷰어(viewer)는 SwiftUI의 MenuBarExtra로 구축된 네이티브 macOS 메뉴 바(menu bar) 앱입니다. 이 앱은 Dock 아이콘 없이 백그라운드 에이전트(background agent)로 실행됩니다. 하루 종일 지켜보는 기능이 Dock 자리를 차지해서는 안 되기 때문입니다.
메뉴 바에는 하나의 눈(eye)이 있으며, 상태에 따라 변화합니다. 세션이 여러분을 기다리고 있을 때는 숫자가 표시된 빨간색으로 열립니다. 모든 것이 작동하는 동안에는 차분하게 지켜봅니다. 모든 것이 조용해지면 닫힙니다. 저는 단순히 색상만 바뀌는 것이 아니라 모양도 함께 바뀌도록 만들었습니다. 그래야 눈여겨보지 않고 곁눈질로 보더라도 색상을 정확히 인지하지 못했을 때 여전히 읽을 수 있기 때문입니다.
두 가지 요소가 이 프로젝트를 "멋진 것"에서 "매일 사용하는 것"으로 바꾸어 놓았습니다. 첫째, 대기 중인 세션을 클릭하면 tmux, iTerm, WezTerm, Cursor, 또는 VS Code 등 해당 터미널이나 에디터 창이 즉시 맨 앞으로 나옵니다. 적절한 창을 찾아 헤맬 필요가 없습니다. 둘째, 알림은 선택 사항(opt-in)이며 계층화되어 있습니다. 소리, 네이티브 알림, 또는 대기 중인 창이 스스로 앞으로 튀어나오게 하는 방식 중 하나를 선택할 수 있습니다. 알림의 강도는 여러분이 직접 결정합니다.
하나의 에이전트에서 여러 에이전트로
파일 감시(file-watching) 패턴이 자리를 잡자, 더 많은 에이전트를 지원하는 일은 거의 지루할 정도로 쉬워졌습니다. 이것이 바로 의도한 바입니다. 각 에이전트는 고유의 생명주기 훅(lifecycle hooks) 또는 이벤트(events)를 가지고 있으며, 각 에이전트에는 동일한 폴더에 동일한 종류의 상태 파일을 쓰는 작은 핸들러(handler)가 할당됩니다. 현재는 Claude Code, Codex CLI, Cursor (CLI 및 에디터 내), Copilot (CLI 및 VS Code 내), 그리고 Google Antigravity를 감시합니다. 이 모든 것들이 하나의 눈으로 통합됩니다.
Mac을 넘어
마지막 공백은 물리적인 부분이었습니다. 이 프로젝트의 핵심은 윈도우(window)를 감시하는 것을 멈추는 것이지만, 메뉴 바(menu bar) 아이콘은 여전히 당신이 화면 앞에 있다는 것을 전제로 합니다. 그래서 저는 Telegram을 추가했습니다. 세션이 대기 상태로 시작되면 휴대폰으로 알림을 보낼 수 있으며, 이는 당신이 자리를 비운 상태에서도 무언가 당신을 필요로 하는 순간을 알 수 있음을 의미합니다.
디자인은 나머지 부분과 마찬가지로 절제된 방식을 따랐습니다. 설정(Settings)에서 약 1분 만에 봇(bot)을 연결할 수 있습니다. 봇 토큰(bot token)은 plist나 설정 파일(config file)이 아닌 macOS 키체인(Keychain)에 저장됩니다. 메시지에는 에이전트(agent)와 프로젝트 이름만 포함됩니다. 그리고 당신이 기능을 켜기 전까지는 아무것도 전송되지 않습니다.
유사한 것을 구축하려는 분들에게 드리는 조언
가장 보편적으로 적용할 수 있는 교훈은 다음과 같습니다: 시스템을 관찰하고 싶을 때, 가로채기(interception)를 시도하기 전에 시스템이 이미 방출하고 있는 이벤트(event)를 먼저 찾으십시오. 훅(Hooks)을 사용했기에 터미널(terminal)을 래핑(wrap), 프록시(proxy) 또는 파싱(parse)할 필요가 없었습니다. 또한 컴포넌트(component)들을 서로 직접 연결하는 대신 폴더에 일반 파일(plain files)을 쓰는 방식을 택함으로써, 각 조각이 서로를 알지 못하도록(ignorant) 유지했습니다. 이는 나중에 더 많은 조각을 추가할 때 정확히 당신이 원하는 방식입니다.
만약 한 번에 하나 이상의 코딩 에이전트를 실행하고 있으며 윈도우 사이클링(window-cycling) 현상을 겪고 있다면, 이 앱은 무료이며 공증(notarized)을 마쳤고 macOS 14 이상에서 실행됩니다. theeye.dev에서 확인하실 수 있습니다.
저는 소프트웨어 아키텍트(software architect)이자 AI 컨설턴트인 Amit Raz입니다. 저는 RZApps에서 이와 같은 작은 도구들을 만들며, 팀들이 자신들의 제품과 워크플로우(workflow)에 AI를 적용할 수 있도록 돕고 있습니다. 제가 만들고 있는 것과 작성 중인 글은 더 많은 내용이 rzailabs.com에 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기