WIKIClaude Usage Crab
개인 제품·2026.07 ~ · 마켓플레이스 발행·기획·개발 단독

Claude Usage Crab

서브에이전트가 지금 뭘 하는지 보여주는 확장.

JavaScriptVS Code Extension APINode.jsdiagnostics_channelOAuth PKCE

설치 · VS Code 마켓플레이스 · 소스 · github.com/gnldnd11/claude-usage-monitor

Claude Usage Crab는?

Claude Code의 서브에이전트가 지금 무엇을 하고 있는지를 VS Code 사이드바에서 보여주는 확장이다. .claude/agents/의 에이전트 하나하나가 픽셀 캐릭터가 되고, 실제로 호출되면 방으로 걸어 들어와 책상에 앉아 일한다. 같은 패널에 세션(5시간)·주간(7일)·컨텍스트 사용량이 함께 있다.

VS Code 마켓플레이스에 saegim.claude-usage-crab으로 발행했다. 읽기 전용이고, 훅을 걸지 않고, 설정할 것이 없다.

호출된 에이전트가 방에 들어와 책상에서 일한다. 아래는 전체 로스터.
호출된 에이전트가 방에 들어와 책상에서 일한다. 아래는 전체 로스터.

Anthropic과 무관한 비공식 커뮤니티 확장이다.

왜 만들었나

백그라운드 서브에이전트는 블랙박스다. 던져놓고 나면 지금 일하는 중인지, 끝난 건지, 처박힌 건지 알 수가 없다.

이건 내 문제였다. 이 모노레포 자체가 에이전트 여러 종(계획·구현·리뷰·커밋·검증·사서)으로 굴러간다. 몇 개를 병렬로 던져놓고 "쟤 아직 도는 중인가"를 로그로 뒤지는 게 일상이었다. 그 상태를 한눈에 답해주는 화면이 필요했다.

시작은 사용량 미터였다. 에이전트 탭은 나중에 붙은 축인데, 지금은 이쪽이 제품의 얼굴이 됐다. 만들면서 문제가 더 선명해진 경우다.

전체 구조

핵심 제약은 관측 대상이 협조해주지 않는다는 것이다. Claude Code는 이 확장을 위한 API를 열어주지 않는다. 그래서 이미 디스크에 쓰이고 있는 것(트랜스크립트)과 이미 네트워크로 오가는 것(사용량 응답)만 읽는다. 훅이 없다는 건 곧 에이전트 실행을 방해할 방법이 구조적으로 없다는 뜻이기도 하다.

협조해주지 않는 대상을 관측하는 구조. 두 갈래로 들어와 상태 판정에서 합쳐진다.
협조해주지 않는 대상을 관측하는 구조. 두 갈래로 들어와 상태 판정에서 합쳐진다.
0
추가 API 요청 (정상 사용 시)
0
의존성 · 훅 · 빌드 스텝
6.7K
줄 JavaScript (테스트 별도 5.3K)
169
테스트 (유닛 149 + 웹뷰 20모드)
38
캐릭터 스프라이트
12
방 테마
서브에이전트 실시간 관측 (읽기 전용 · 훅 없음)
에이전트별 토큰·도구호출·소요시간 귀속
처박힌 에이전트 탐지 (자기 중앙값 3배)
세션 · 주간 · 컨텍스트 사용량 미터
증분 트랜스크립트 파싱 (수백 MB 대응)
이름 해시 기반 결정적 캐릭터 배정
쓴 토큰이 크레딧, 그 크레딧으로 게임 4종
의존성 0을 지킨 헤드리스 테스트 스위트

남을 방해하지 않고 남을 관측하기

이 확장에서 제일 마음에 드는 부분이다.

세션·주간 한도는 /api/oauth/usage에서 온다. 그런데 이 엔드포인트는 토큰당 몇 분에 한 번만 통과시킨다. Claude Code가 쓰고 있는 동안 같은 토큰으로 부르면 429가 난다. 폴링을 촘촘하게 할수록 조용한 실패만 쌓인다.

그래서 부르는 대신 듣기로 했다. Claude Code의 확장은 같은 extension host 프로세스에서 돈다. Node의 diagnostics_channel을 구독하면 그쪽이 자기 사용량을 조회하는 요청이 그대로 보인다. 응답 스트림을 통과시키면서 사본만 뜨면, 내 요청은 한 번도 나가지 않는다.

// 응답 스트림을 소비하면 Claude Code가 자기 응답을 못 받는다.
// 리스너를 가로채 원본에 청크를 그대로 넘기고, 사본만 모은다.
res.on = function (event, listener) {
  if (event === 'data') {
    return origOn('data', (chunk) => {
      if (body.length < MAX) body += chunk.toString();
      listener(chunk);            // ← 남의 것은 건드리지 않는다
    });
  }
  ...
};

그래서 패널의 숫자가 Claude Code 내장 다이얼로그와 정확히 일치한다. 계산이 같아서가 아니라 같은 응답이기 때문이다.

세션·주간·Fable·컨텍스트. 이 숫자들은 내가 부른 게 아니라 옆에서 들은 것이다.
세션·주간·Fable·컨텍스트. 이 숫자들은 내가 부른 게 아니라 옆에서 들은 것이다.

여기엔 함정이 둘 있었다. 폴백으로 내가 직접 부른 요청이 내 탭에 다시 잡히면 무한루프가 된다. 자기 요청에는 전용 헤더를 박아 걸러낸다. 그리고 이 코드는 남의 확장이 도는 프로세스 안에서 실행되므로, 탭 핸들러 전 구간을 catch로 감쌌다. 내가 터져도 Claude Code는 멀쩡해야 한다.

끝난 걸 끝났다고 말하기

가장 오래 붙잡은 버그다. 서브에이전트가 끝났는지를 트랜스크립트만 보고 알아내야 한다.

동기 호출은 쉽다. tool_result가 오면 끝난 거다. 문제는 백그라운드 에이전트인데, 이쪽은 tool_result를 주지 않는다. 던지는 즉시 "실행했음" 확인만 돌려주고, 진짜 완료는 한참 뒤에 별도 알림으로 온다.

이걸 모르고 확인 응답을 완료로 셌더니, 백그라운드 에이전트가 던지자마자 끝난 것으로 표시됐다. 이 탭이 존재하는 이유가 정확히 그 케이스인데 말이다. 함정은 세 겹이었다.

  • 즉시 오는 확인 응답은 완료가 아니다. 명시적으로 걸러내야 한다.
  • 완료 알림은 같은 내용이 최대 세 번, 서로 다른 레코드 형태로 쓰인다. 비용 정보를 달고 온 놈을 골라 남긴다.
  • 백그라운드는 확인 응답이 호출 2초 뒤에 떨어지므로 (결과시각 − 호출시각)은 소요시간이 아니다. 하네스가 찍어준 값만 소요시간으로 인정한다.

세 번째가 특히 중요하다. 소요시간을 잘못 계산하면 그 위에 얹은 것이 전부 무너진다. 처박힘 판정도, 에이전트별 비용도.

처박힌 걸 알려주는 기준

에이전트가 오래 도는 건 정상일 수도 있고 아닐 수도 있다. 절대 시간으로는 판정할 수 없다. 그래서 자기 자신의 중앙값 소요시간 3배를 넘으면 호박색으로 바꾼다. 빠른 에이전트가 첫 느린 실행에서 오탐되지 않게 10분 아래로는 절대 처박혔다고 부르지 않고, 한 시간 넘게 결과가 없으면 죽은 세션으로 보고 내린다.

반대 방향의 보정도 필요했다. 백그라운드는 확인 응답이 2초 만에 오기 때문에, 그대로 두면 캐릭터가 방에 들어오기도 전에 사라진다. 사람 눈에 보이라고 만든 화면이 사람 눈에 안 보이는 셈이다. 그래서 최소 6초는 "일하는 중"으로 유지한다.

수백 MB를 10초마다 읽지 않기

트랜스크립트는 append-only고, 무거운 세션은 수백 MB까지 간다. 10초마다 전량 재파싱은 성립하지 않는다.

파일마다 누적기를 두고 마지막 틱 이후 늘어난 바이트만 offset으로 읽는다. 잘린 마지막 줄은 다음 틱으로 넘긴다. 전량 재파싱은 파일이 줄었을 때(재작성)와 날짜가 바뀌었을 때(오늘 합계가 이동)만 한다. 정상 틱이 150ms대에서 수 ms로 내려갔다.

여기서 재밌었던 건 burn-rate 경고다. "마지막으로 본 시점보다 새로운 요청 중 최대값"을 알아야 하는데, 그 기준 시점은 파싱 이후에 움직이고 지나간 줄은 두 번 다시 읽지 않는다. 그래서 파일마다 스카이라인(뒤에 더 큰 값이 나오면 앞을 버리는 단조 스택)을 유지한다. 나중에 어떤 임계값으로 물어도 파일을 다시 열지 않고 답한다.

빠르게 읽는 것보다 맞게 세는 게 먼저였다

파싱을 다듬던 중에 더 나쁜 걸 발견했다. 토큰과 요청 수가 2배로 부풀어 있었다.

Claude Code는 한 응답의 content 블록마다 트랜스크립트에 한 줄을 쓴다. 나는 그 줄들을 전부 더하고 있었다. 중복된 줄은 입력과 캐시 값이 같고 출력만 자라는 스트리밍 스냅샷이라, 더할 때마다 같은 응답을 여러 번 센 셈이다. 874개 파일을 세어 보니 usage 줄 93,543개 중 42,527개가 앞선 응답의 반복이었다.

이게 관측 도구에서 제일 아픈 종류의 버그다. 터지지 않는다. 화면은 멀쩡하고 숫자만 틀리다. 쓰는 사람은 그 숫자를 믿을 수밖에 없다.

고치면서 하나 정했다. 반복된 응답은 마지막 값이 아니라 필드별 최댓값으로 센다. 같은 측정에서 51,086개 응답 중 4개가 나중에 전부 0인 usage 줄로 다시 쓰여 있었는데, 마지막 값을 쓰면 그 4개(각각 100만 토큰쯤)가 사라진다. 최댓값은 덤으로 성질 하나를 더 준다. 줄 순서가 어떻든, 증분 파싱이 파일을 어디서 끊든 같은 답이 나온다.

세는 규칙은 파일 하나로 뺐다. 패널의 오늘 합계와 크레딧 원장이 같은 규칙으로 세야 하는데, 규칙이 두 군데 있으면 둘이 갈라진다.

컨텍스트 게이지에도 비슷한 판단이 하나 있다. 컨텍스트 창이 200K인지 1M인지는 메시지가 아니라 세션의 속성이다. 마지막 메시지만 보면, 1M 세션이 압축으로 200K 아래로 내려간 순간 "200K 창이 거의 찼다"로 오독된다. 그래서 그 세션의 최대 컨텍스트로 티어를 판정한다.

호출을 몸으로 보여주기

로그 한 줄로 "agent started"라고 쓰면 읽어야 알고, 읽으려면 찾아가야 한다. 그래서 방을 만들었다.

워커는 에이전트 이름이 아니라 호출 단위로 만든다. 같은 에이전트를 병렬로 세 번 부르면 캐릭터 세 명이 나란히 선다. 이름이 같다고 하나로 접으면 병렬로 던졌다는 사실 자체가 화면에서 사라진다.

캐릭터 배정은 이름 해시로 결정적이다. 같은 에이전트는 영원히 같은 얼굴이라 한 번 익히면 계속 통한다. 다만 해시를 그대로 쓰면 둘이 같은 캐릭터를 뽑는 일이 생겨서, 로스터 전체를 놓고 충돌 없이 배정한다. 사용자가 직접 고른 외형이 먼저 자리를 잡고, 나머지가 빈 자리를 채운다.

캐릭터에 커서를 올리면 크루 카드. 레벨·XP·누적 작업량이 전부 실측이다.
캐릭터에 커서를 올리면 크루 카드. 레벨·XP·누적 작업량이 전부 실측이다.

XP는 완료한 일만큼 쌓인다. 여기서 하나 정한 게 있다. 활동 시간은 프로필에 보여주되 XP에는 넣지 않는다. 시간은 비용이지 업적이 아니라서다. 오래 돈 에이전트가 레벨이 높아지면 레벨이 거짓말을 하게 된다.

설치 첫날 모두가 Lv.1로 시작하는 것도 같은 종류의 거짓말이라, 최초 1회 과거 트랜스크립트를 훑어 레벨을 복원한다. 이건 무거운 작업이라 JSON 파싱 없이 정규식만 쓰고, 40ms 일하고 150ms 쉬는 식으로 잘라서 돌린다. 확장 호스트를 막으면 에디터 전체가 버벅인다.

방 하나 그리는 데 규칙이 필요했다

방 테마는 지금 12종이다. 처음엔 정사각 디오라마를 통째로 넣었는데 세 가지가 동시에 터졌다. 라이트 테마에서 검은 사각형이 스티커처럼 박히고, 800px 그림이 280px 사이드바에서 뭉개지고, 세로를 40% 잡아먹었다.

원인은 하나였다. 디오라마를 하나의 물체로 보여준 것. 방 안쪽으로 파고들어 자르면 프레임이 바닥과 벽으로 꽉 차서 배경 자체가 사라지고, 셋이 한 번에 해결된다. 지금은 1.8:1 실내 비네트로 잘라 쓰고, 절차를 문서로 못 박아뒀다.

이전 · 정사각 디오라마를 통째로
이전 · 정사각 디오라마를 통째로
이후 · 방 안쪽으로 파고들어 자른 비네트
이후 · 방 안쪽으로 파고들어 자른 비네트

같은 방(Backrooms)을 같은 그림으로 찍은 전후다. 그림을 새로 그린 게 아니라 어디를 자르느냐만 바꿨다.

Decision #01배경 투명화는 하지 않는다

투명 처리로 라이트 테마 문제를 풀려고 floodfill, connected-components, 모폴로지까지 전부 시도했고 전부 실패했다. 이 그림들은 어두운 외곽선이 배경과 톤이 이어져 있어서, 색 기반 제거가 그림 내부까지 타고 들어가 캐릭터와 가구에 구멍을 낸다. 크롭이 정답이다.

Decision #02정지 픽셀아트 위에 블러 레이어를 얹지 않는다

모니터 화면에 글로우를 주려고 radial-gradient + blur + screen 블렌드를 얹었더니 안개 낀 것처럼 됐다. 픽셀아트는 경계가 또렷한 게 매력인데 블러가 그걸 깎는다. 접근 자체가 안 맞았다.

방이 12종이 되고 나서야 다른 문제가 보였다. 고르는 자리가 드롭다운이라 한 번에 이름 하나만 보였고, 그래서 대부분은 방이 여러 개라는 것 자체를 모르고 썼다. 열두 개를 만들어놓고 하나만 보여준 셈이다. 지금은 눌렀을 때 썸네일 그리드가 열리고, 지금 쓰는 방이 강조돼 있다.

여기에도 함정이 하나 있었다. 고르라고 원본 방 그림 열두 장을 한꺼번에 띄우면 7MB를 디코딩하느라 웹뷰가 멈춘다. 그래서 방마다 200px에 128색으로 줄인 썸네일을 따로 굽는다. 열두 장 합쳐 148KB다.

아무도 안 돌 때 방이 허전한 문제는 아직 미해결이다. 유휴 시 마스코트를 책상에 앉히려 했지만 뒷모습 스프라이트가 없어서 접었다. 다시 손댄다면 블러나 반투명이 아니라 또렷한 스프라이트를 하나 더 놓는 방향이어야 한다.

윈도우에서는 그 방이 비어 있었다

실기 보고가 하나 왔다. "레벨은 오르는데 아무도 방에 안 걸어 들어온다."

증상이 정확히 한 곳을 가리켰다. XP는 모든 트랜스크립트에서 적립하고, 방은 지금 이 창의 워크스페이스 것만 들인다. 그 범위 판정만 통째로 실패하면 딱 이 모양이 된다.

맞았다. 워크스페이스 경로를 Claude Code의 폴더 이름으로 바꿀 때 슬래시만 대시로 치환하고 있었다. 맥에서는 그걸로 충분하다. 윈도우 경로는 c:\dev\workspace라서 백슬래시와 콜론이 그대로 남고, 어떤 폴더와도 안 맞는다. 실제 폴더 이름은 c--dev-workspace다. 드라이브 문자 대소문자도 믿을 수 없었다. 같은 머신에서 c--dev-workspace와 C--Users-...가 나란히 관측됐다.

고치면서 마음에 들었던 건 치환을 전부 길이 1:1로 유지한 것이다. 그래서 인코딩된 이름의 길이가 원본 경로를 그대로 가리킨다. 상위 폴더에서 세션을 연 경우(모노레포에서 흔하다. 창은 repo/projects/foo, Claude Code는 repo/에서 돌린다)에 진짜 상위 폴더와 이름만 닮은 형제 폴더를 추측 없이 가를 수 있다. 그 자리의 원본 문자가 구분자면 상위, 대시면 형제다.

Decision #03같은 이름의 형제 폴더는 일부러 안 걸러낸다

/a/b/saegim에서 돌린 세션이 /a/b/saegim-studio 창의 방에 섞여 들어올 수 있다. 인코딩이 잃어버린 것이 바로 실행 경로라서 원본 경로로도 못 가른다. 그냥 뒀다. 이쪽 비용은 방에 남이 한 명 섞이는 것이고, 더 엄하게 판정하려면 어딘가에서 추측을 해야 하는데 그 추측이 틀리면 방이 빈다. 방이 비는 건 이 모든 코드가 존재하는 이유인 바로 그 버그다. 보이는 쪽으로 틀리는 편이 낫다.

그런데 이걸 고친 뒤에도 윈도우 증상이 그대로였다. 여기서 또 추측하면 두 번째 헛짚기라, 판정이 실제로 무엇을 봤는지 한 줄로 찍는 진단 로그를 넣었다. 플랫폼과 구분자, 원본 경로와 인코딩 결과, 실제 폴더 이름, 호출 수와 그중 범위에 든 수. 마지막 둘이 어디서 끊기는지를 가른다. 내가 닿을 수 없는 기계라서, 지금은 그 로그를 읽어야 다음으로 갈 수 있다. 윈도우는 아직 해결됐다고 말할 수 없다.

쓴 토큰이 크레딧이 된다

여기부터는 관측과 상관없는 축이다. 쓴 토큰이 크레딧이 되고, 그 크레딧으로 게가 운영하는 가게에서 게임을 한다. 블랙잭, 하이로우, 드로우 포커, 검 대장간 넷이다. 설정 기어에서 통째로 숨길 수 있다.

100K 가중 토큰이 1 크레딧이다. 가중치는 API 가격을 베꼈다. 출력이 입력의 5배, 캐시 쓰기 1.25배, 캐시 읽기 0.1배. 모델별로 나누지 않고 한 세트로 간다. 청구서가 아니라 게임 화폐라서 그게 맞다.

크레딧은 살 수도 환전할 수도 없다. Claude Code를 쓴 것 외에는 버는 길이 없다.

곁다리로 붙인 기능인데, 정작 공들인 곳은 전부 돈이 걸린 상태를 다루는 쪽이었다.

  • 부동소수를 돈 경로에 들이지 않는다. 0.1은 부동소수로 정확히 표현되지 않아서, 가중치를 정수 분자와 분모로 들고 있다. 포커 배당도 전부 정수 배수라 반올림이 한 군데도 없다.
  • 날짜별로 크레딧이 아니라 원본 토큰을 저장한다. 가중치나 환율을 바꾸면 과거도 같이 다시 가격이 매겨진다.
  • 원장은 "본 것의 기록"이지 매번 다시 계산하는 값이 아니다. 설정에 따라 트랜스크립트가 30일 뒤 지워진다. 매번 재계산하면 쓴 과거가 사라진다.
Decision #04같은 상태를 여러 창이 공유할 때, 병합 규칙을 필드 성질별로 나눴다

VS Code 창을 여러 개 띄우면 같은 원장을 여럿이 쓴다. 날짜별 토큰은 올라가기만 하니 필드별 최댓값으로 병합하면 된다. 그런데 게임 수지는 내려가기도 한다. 최댓값으로는 표현이 안 된다. 그래서 창마다 증가 전용 카운터를 따로 들고 그것끼리 최댓값 병합한다.

검은 또 다르다. 강화 레벨은 오르고 내리고, 플레이어의 검은 하나다. 창마다 하나씩 두면 검이 네 자루가 된다. 그래서 검은 레지스터처럼 병합한다. 받아들인 수가 많은 쪽이 통째로 이기고, 평생 수치만 양쪽 최댓값을 가져간다.

여기서 버그를 하나 만들었다. 판매금을 평생 수치처럼 최댓값으로 접었더니, 두 창이 한 자루를 팔아 각각 돈을 받았다. 되돌아온 검을 팔면 검과 값을 둘 다 쥐는 경우도 생겼다. 고친 방법은 판매금을 검 기록 안으로 옮긴 것이다. 기록과 함께 살고 기록과 함께 죽으니, 한 자루는 한 번만 팔린다. 반면 강화 수수료는 안 돌려준다. 밀린 창도 시도를 하려고 실제로 지불했다.

네 게임은 지갑 하나를 쓴다. 카드 게임 셋은 동시에 살아 있을 수 있는데, 스테이크는 판이 정산될 때까지 잔액에서 빠지지 않는다. 그대로 두면 세 판이 같은 크레딧을 두고 겨뤄서 초과 인출이 난다. 그래서 각 게임의 베팅 한도는 잔액 − 다른 게임이 걸고 있는 것이다.

회수율은 네 게임을 같은 띠에 맞췄다. 하이로우는 슈에 실제로 남은 카드로 양쪽 배당을 매기고 거기서 3%를 뗀다. 그래서 "어느 카드도 어느 쪽도 더 좋은 베팅이 아니다"가 장식이 아니라 사실이 된다. 안전하거나 거친 선택만 있다. 포커는 흔히 인용되는 9/6 대신 8/5 테이블이다. 9/6은 99.5%로 너무 후하다.

대장간은 한 줄로 맞출 수 없었다. 파괴가 등반을 처음부터 되돌리니까, +T까지 밀고 판다는 전략 전체의 회수율이 띠 안에 들어야 한다. 테스트가 레벨마다 기대값을 풀고, 어디서 팔지 고르는 512가지 전부에 같은 계산을 돌린다. 최선도 98% 아래, 최악도 95% 위다. 어느 한 레벨만 파먹을 수 없다는 뜻이다.

의존성 0을 테스트에서도 지키려면

게임이 붙으면서 테스트가 제품 코드의 80% 분량까지 올라왔다. npm test 하나로 유닛·호스트 149건과 웹뷰 20가지 모드가 돈다.

의존성 0은 여기서도 규칙이라, jsdom 없이 DOM을 직접 짰다. HTML 파서와 선택자, 직렬화기, 버블링 이벤트, 그리고 최소한의 포커스 모델까지 244줄이다. 포커스된 버튼에서 Enter가 클릭으로 가는 Chromium 동작도 들어 있다. 웹뷰 코드를 고칠 때마다 손으로 눌러보고 싶지 않아서다.

호스트 쪽은 가짜 vscode를 깔고 진짜 activate()를 돌린다. 대신 HOME을 임시 디렉터리로 바꿔서, 테스트가 내 실제 트랜스크립트나 크레덴셜을 건드리지 않는다. 웹뷰는 모드마다 별 프로세스에서 한 번씩 돌리고, 멈춘 모드는 60초에 죽여서 실패로 센다. 테스트가 영원히 안 끝나는 상태는 통과도 실패도 아니다.

라이브로 보려면, 열려 있지 않은 문을 지나야 한다

세션 %를 진짜 라이브로 보여주려면 자기 토큰이 필요하다. 이 확장에서 유일하게 깔끔하지 않은 부분이고, 지금도 답을 기다리는 중이라 여기 그대로 적는다.

/api/oauth/usage의 rate limit은 계정이 아니라 토큰별이다. Claude Code의 토큰을 빌려 쓰면 그 창을 Claude Code와 나눠 갖게 되고, 활성 사용 중에는 거의 항상 429가 난다. 폴 간격과 재시도를 열두 번 손봐도 못 이겼다. 갱신이 리로드 시점에만 일어나면 그건 라이브가 아니다.

자기 토큰이면 자기 예산이라 60초 폴링이 매번 통과한다. 그래서 선택형 사인인(PKCE OAuth)을 넣었고, 그게 세션 %가 살아 움직이는 유일한 길이었다.

문제는 이게 서드파티에 열려 있는 길이 아니라는 것이다. 확장 전용 클라이언트를 등록할 창구가 없어서 Claude Code의 클라이언트로 붙고 있고, 정책은 이 방식의 OAuth를 자사 제품(Claude Code·Claude.ai) 전용으로 제한한다. 읽어보고 회색지대라고 판단하지 않았다. 제한에 걸린다고 봤다. 차단이 진행 중이라는 공개 이슈도 있으니 언제 막혀도 이상하지 않은 상태다.

여기서 선택지는 셋이었다. 모른 척 쓰거나, 조용히 내리거나, 물어보거나.

Decision #05숨기는 대신 직접 물었다 (2026-07-24 문의, 답변 대기)

Anthropic 지원팀에 세 가지를 계단식으로 물었다. ① 서드파티가 개인 플랜 사용량을 조회할 공식 방법이 있는가 ② 없다면 429 없이 갱신할 권장 방법이 있는가 ③ 둘 다 없고 이 방식이 안 되는 거라면 즉시 내리겠다.

1차 답변으로 확인된 건 공개 API로는 서드파티가 개인 플랜 사용량을 조회할 방법이 없다는 것이다(공개된 Usage/Cost API는 조직의 API 소비량을 보는 것이지 플랜 한도가 아니다). 정책 적합성 판단은 담당자에게 넘어갔고, 지금은 그 답을 기다리는 중이다.

그동안 손 놓고 기다리지는 않았다. 제품의 무게중심을 로그인 없이 도는 것으로 옮겼다. 에이전트 관측·토큰·요청·컨텍스트는 전부 로컬 트랜스크립트에서 나오고, 사인인은 세션 % 하나를 라이브로 만드는 부가 기능이다. 답이 "안 된다"로 오면 그 기능만 걷어내면 되고, 확장은 그대로 선다.

되는 걸 찾아내는 것과, 되니까 그냥 쓰는 건 다르다고 생각한다.

프라이버시와 경계

모든 데이터는 로컬 ~/.claude에서 읽는다. 텔레메트리도, 중계 서버도 없다. 토큰은 api.anthropic.com 외에는 어디로도 나가지 않는다. 자체 로그인은 선택 사항이고(자기 토큰이면 자기 예산이라 세션 %가 더 자주 갱신된다), PKCE OAuth로 받아 VS Code secrets에 넣는다.

빌드 스텝이 없다는 게 여기서 보안 논거가 된다. 트랜스파일도 번들러도 없는 순수 JavaScript라 .vsix에 들어간 게 레포에 있는 그것이다. 감사하려는 사람이 대조할 수 있다.

에이전트 숨기기는 표시 전용이다. 숨겨도 그 에이전트는 평소대로 돈다. 관측 도구가 관측 대상을 바꾸기 시작하면 그때부터는 다른 물건이다.

기술 스택

  • 런타임: VS Code Extension API (^1.75) · Node.js · 순수 JavaScript (의존성 0 · 빌드 없음)
  • 관측: diagnostics_channel · 증분 파일 읽기 (fs.readSync offset) · fs.watch
  • 인증: PKCE OAuth (선택) · VS Code SecretStorage
  • 화면: 웹뷰 (스프라이트 시트 크롭 렌더) · 상태바 아이템
  • 테스트: node assert 만으로 유닛·호스트 149건 + 웹뷰 20모드. DOM은 자체 구현
  • 배포: VS Code 마켓플레이스 (saegim.claude-usage-crab) · 공개 미러 레포

왜 이 스택

  • 빌드 없는 순수 JS: 확장이 남의 토큰을 다루니까, 배포물과 소스가 1:1로 대조되는 게 기능보다 중요했다.
  • diagnostics_channel: 유일하게 "부르지 않고 아는" 길이었다. 폴링은 429로 조용히 실패하고, 훅은 사용자 설정을 요구한다.
  • 웹뷰 + 스프라이트 시트: 구매한 시트마다 셀 크기(32/48/64px)와 레이아웃이 달라서, 시트별 메타와 실측 bbox로 크롭해 어떤 캐릭터든 화면에서 같은 키로 보이게 정규화했다.
  • 테스트도 의존성 0: 라이브러리를 하나 들이면 배포물과 소스를 1:1로 대조한다는 성질이 그만큼 흐려진다. 테스트에만 쓰는 의존성이라도 레포에는 남는다. 그래서 DOM을 직접 짰다.

정직한 한계

읽고 있는 것 두 가지가 모두 문서화되지 않은 것이다. 사용량 엔드포인트도, 트랜스크립트 포맷도. 바뀌면 해당 숫자가 멈춘다. 이건 확장 소개에도 그대로 적어뒀다. 감출 성질의 것이 아니라 쓰는 사람이 알고 써야 하는 사실이다.

백그라운드 서브에이전트는 알 수 있는 진행률이 없다. 그래서 퍼센트를 지어내는 대신 불확정 바와 경과시간만 보여준다. 화면이 덜 그럴듯해지는 대신 거짓말을 하지 않는다.

자체 사인인은 정책 제한에 걸린다고 보고 직접 물어둔 상태다. 답에 따라 기능이 빠질 수 있고, 그 전제로 제품 구조를 짜뒀다.

윈도우에서 방이 비는 증상은 아직 해결을 확인하지 못했다. 경로 판정을 전면으로 고쳤는데도 증상이 남았고, 진단 로그를 넣은 자리에서 멈춰 있다. 지금 가장 큰 미해결 항목이다.

에이전트 탭의 Beta 표기는 1.18.0에서 뗐다. 뱃지를 뗀 것이고, 그게 어떤 검증을 통과했다는 뜻은 아니다.

번들된 픽셀아트는 내가 만든 게 아니다. 코드만 MIT이고 아트는 이 확장 안에서만 쓰도록 라이선스된 서드파티 자산이다. 캐릭터 스프라이트가 그쪽이라서, 스프라이트를 직접 만들었다고는 쓸 수 없다.

크레딧은 게임 화폐다. 살 수도 환전할 수도 없고, 버는 길은 Claude Code를 쓰는 것뿐이다.

회고

이 프로젝트의 값은 사용량 미터를 하나 더 만든 데 있지 않다.

협조하지 않는 대상을 관측하는 방법을 찾아낸 것이 하나다. API도 훅도 없이, 남이 디스크에 쓰는 것과 남이 네트워크로 주고받는 것만으로. 그러면서 그 남을 한 번도 방해하지 않는다. 스트림은 통과시키고, 내 요청은 배제하고, 전 구간을 catch로 감쌌다.

내가 매일 겪는 문제를 내 도구로 해결하고 발행까지 한 것이 다른 하나다. 에이전트로 일하다가 "얘가 지금 도는 중인가"에 막혔고, 그걸 화면으로 만들었고, 마켓플레이스에 올렸다. 쓰는 사람이 곧 만든 사람이라 매일 회귀 테스트가 돌아간다.

그 아래를 받치는 건 결국 평범한 것들이다. 수백 MB를 감당하는 증분 파싱, 세 겹 함정을 통과한 완료 판정, 지어낸 퍼센트를 거부한 판단.

뒤에 붙은 크레딧과 게임은 성격이 다른 연습이 됐다. 관측은 틀려도 숫자가 틀리는 것으로 끝나는데, 잔액은 틀리면 없던 돈이 생긴다. 창 여럿이 같은 원장을 쓸 때 어떤 필드를 최댓값으로 접고 어떤 필드를 못 접는지, 돈 경로에서 부동소수를 어디까지 밀어낼 수 있는지를 여기서 처음 끝까지 따져봤다. 한 자루를 두 번 팔아버린 버그도 그 과정에서 나왔다. 규칙을 측정 가능한 성질로 적어두면 테스트가 대신 지켜준다는 것도 이쪽에서 배웠다.