Skip to content

Dev Guide claude code

Woojin Kim edited this page Jun 23, 2026 · 4 revisions

Dev Guide: claude-code 플러그인

pytmux 안에서 동작하는 Claude Code 세션을 감시·보조하는 종합 플러그인이자 저장소에서 가장 큰 플러그인이다(~8천 줄). 이 페이지는 플러그인을 확장·유지보수하는 개발자를 위한 심화 가이드다. 철학(왜 결정론적 개입인가)은 개요 페이지 Claude-Code-Plugins 가 다루고, 여기서는 코드 구조·등록·상태머신·데이터 모델에 집중한다.

결정론(deterministic) 우선. 이 플러그인의 모든 자동 개입은 명시적 규칙과 임계값으로 정의되며 재현 가능하다 — 같은 화면 입력에 같은 동작이 나온다. 목표는 사용자의 습관을 바꾸지 않으면서 토큰 과소비를 줄이는 것이다. 적응형·휴리스틱 학습 기반(비결정론) 완화는 계획된 미래 방향이며 아직 구현돼 있지 않다 — 현재 화면 파싱은 휴리스틱이지만 그 위의 개입 결정은 전부 고정 규칙이다.


목차

  1. 플러그인 아키텍처 & 파일 맵
  2. 등록: Registry 훅 전수
  3. 서버 믹스인 합성 패턴
  4. Claude 감지 상태머신
  5. 결정론적 토큰 완화 개입과 트리거
  6. 토큰 회계 데이터 모델
  7. 그림자 /usage 프로브
  8. 클라이언트측 표시: 배지·렌더·팝업
  9. 확장·테스트하는 법
  10. 관련 문서

1. 플러그인 아키텍처 & 파일 맵

pytmuxlib/plugins/claude-code/ 디렉터리 하나가 플러그인 전체다. 코어는 이 디렉터리를 직접 import 하지 않고 레지스트리 훅으로만 닿으므로, 디렉터리를 통째로 지우면 모든 Claude 명령·팝업·상태줄 배지·자동개입·토큰 추적이 조용히 사라지고 코어는 무에러로 계속 동작한다(delete-to-disable).

파일 역할
__init__.py 플러그인 진입점. _ClaudeCodePlugin 클래스(모든 레지스트리 훅 메서드)·명령 메타데이터·i18n 카탈로그·token-saver 팝업 행 정의·클라이언트 글루 설치(attach_client).
claude.py 순수 함수 감지·파싱 레이어. 화면 텍스트 → 상태(claude_state)·리밋·하드스톱·API 에러·사용량·모델·계정·권한모드·리셋 시각 파싱. textual·코어 미의존이라 단위 테스트가 쉽다.
servermixin.py ServerClaudeMixin — 런타임에 Server 의 동적 베이스로 합성되는 서버측 로직(_scan_claude 30Hz 스캔 루프·자동개입·토큰 커밋·DB 연결).
panestate.py pane_init/pane_reset/pane_serialize/pane_restore 훅이 위임. 패널별 Claude/토큰 필드(~50개)를 소유한다.
tokens.py 토큰 누계 상태머신(step/reset/parse_running_tokens). 응답별 peak 합산.
usagedb.py SQLite 백엔드 — 스키마·마이그레이션(v1→v5)·insert/query·한도 스냅샷 이력(limits 테이블)·대사(reconcile).
usagelog.py 레코드 생성·버킷 집계(시/일/주/월×계정/세션)·신뢰 계정 판정·뷰 포맷. 클라가 라운드트립 없이 집계하는 순수 규칙.
usageprobe.py 숨은 claude 세션을 띄워 /usage·/status 패널을 스크랩하는 그림자 프로브(PTY/ConPTY).
clientstatus.py 하단 상태줄 Claude 세그먼트(모델 배지·컨텍스트%·5h%·계정·경고·카운트다운)와 클릭존.
clientrender.py 콘텐츠 레이어 위 Claude 프롬프트 헤더·footer 클릭존 스캔(client_render 훅).
screens.py textual 팝업(토큰 로그·사용량 한도·모델 변경·권한모드·시작 규칙 편집·token-saver 설정). 실제로 열 때 지연 import.
db/ SQLite DB 파일이 사는 플러그인 하위 디렉터리(버전관리 제외·호스트 로컬).

경량 규약: __init__.py 는 textual 을 import 하지 않는다 — 서버 프로세스도 plugins.load() 로 이 모듈을 읽기 때문이다. textual 화면(screens.py)과 서버 로직 (servermixin.py)은 실제로 필요할 때만 함수 안에서 지연 import 한다.

token-saver 설정 팝업


2. 등록: Registry 훅 전수

코어의 pytmuxlib/plugins/__init__.py Registry 가 모든 훅 계약을 한곳에 모은다. _ClaudeCodePlugin 은 그 훅 메서드들을 구현하고, 모듈 끝에서 PLUGIN = _ClaudeCodePlugin() 싱글턴을 노출한다. 각 훅은 코어가 레지스트리로만 호출하므로, 디렉터리를 지우면 훅이 no-op 가 된다.

메타데이터(정적 속성)

  • name / description / category — 플러그인 매니저 표시용.
  • commands (COMMANDS) — (이름, 설명, 카테고리) 튜플 목록. 코어 COMMANDS/ COMPLETIONS 에 병합.
  • noarg (NOARG) — 인자 없이 즉시 실행하는 명령 집합.
  • command_options (COMMAND_OPTIONS) — 팔레트에서 on/off/토글을 키보드로 고르는 선택지 스키마.

서버 합성·생애주기 훅

  • server_mixin()ServerClaudeMixin 반환(§3). 이 한 줄이 서버측 Claude 로직 전체를 Server 에 붙인다.
  • server_init(server)server._init_token_state() — 토큰 DB 런타임 상태 설치.
  • server_opts_init(server, opts) / server_opts_serialize(server)opts.jsonplugin_opts 네임스페이스를 읽고/쓴다(코어는 키 의미를 모르고 불투명하게 저장). 마이그레이션 shim 으로 구 top-level 키를 폴백 읽기 한다.

서버 런타임 훅(코어 serverio/server 가 매 프레임/이벤트로 호출)

  • server_scan(server, sess, win)_scan_claude()30Hz flush 스캔의 핵심 진입점.
  • server_status(server, sess, win, msg, full) — status 메시지에 Claude 필드 in-place 채움.
  • server_pane_overview(server, pane, info) — 트리/개요에 Claude 상태·토큰 덧붙임.
  • server_input(server, pane, data) / server_paste(server, pane, data) — 사용자 입력의 부수효과(프롬프트 추적 + 무장된 자동 액션 해제 — 사용자가 직접 이어받음).
  • server_pending(server, pane) — 무장된 자동 액션 카운트다운 {kind, eta}.
  • server_usage_refresh(server) (async) — 그림자 /usage 자동 갱신 1회.
  • server_command(server, client, sess, action, msg)set_* 명령 액션 디스패치 (명시적 if/elif 라우팅). 반환값 'handled'/'send_full'/'broadcast' 가 코어 후속을 지시.
  • handle_server_request(server, sess, action, msg)request_token_log 요청을 SQLite 에서 읽어 회신(코어 serverio 가 더는 usagedb 를 import 하지 않게 함).
  • pane_closing(server, pane) — 닫히는 Claude 패널 토큰을 같은 계정 생존 패널로 이관 (_carry_tokens_on_close) + 무장 타이머 해제.

Pane 상태 소유 훅

  • pane_init / pane_reset / pane_serialize / pane_restorepanestate 모듈 위임. 코어 Pane 에는 없는 Claude/토큰 필드를 플러그인이 소유한다.

클라이언트 훅

  • attach_client(app) — 앱 인스턴스에 클라이언트 글루(클로저 메서드)를 설치 (open_model_config·open_token_log·open_perm_mode·_saver_* 등). PytmuxApp 이 팩토리 내부 지역 클래스라 동적 믹스인을 못 써 클로저 설치 패턴을 쓴다.
  • client_render(app, cells, W, H) — 콘텐츠 레이어 위 Claude 헤더·클릭존(clientrender).
  • client_status(app, msg) — status 의 Claude 필드 흡수 + 절감 에스컬레이션 훅 발화.
  • client_statusbar_init / client_statusbar_update / client_statusbar — 하단 상태줄 Claude 세그먼트 초기화·흡수·렌더(clientstatus).
  • handle_message(app, msg) — 서버 token_log 회신 → TokenLogScreen 팝업.
  • handle_command(app, c, args)claude-rules·token-saver·token-log 등 명령 핸들러.

훅 추가 패턴: 새 기능이 코어의 어떤 지점에 붙어야 하면, 먼저 코어가 그 지점에서 registry.<hook>(...) 를 부르는지 확인하고(없으면 코어에 훅 호출 추가), 그 다음 이 플러그인 클래스에 같은 이름의 메서드를 구현한다. 코어는 훅 이름만 알고 구현은 모른다.


3. 서버 믹스인 합성 패턴

Server 클래스(server.py)에는 _scan_claude·set_autoresume·refresh_usage 같은 메서드가 소스에 없다. 이들은 런타임에 합성된다:

flowchart LR
    A["server_mixin()"] --> B["ServerClaudeMixin"]
    B --> C["Server 의 동적 베이스 클래스로 합성<br/>(self.plugins 에 claude 가 있으면)"]
Loading
  • server_mixin()ServerClaudeMixin 을 반환하고, 코어가 server_mixins() 결과를 Server 의 베이스에 끼워 합성 클래스를 만든다.
  • 그래서 jump-to-def 가 Server._scan_claude 에 안 닿으면 servermixin.py 를 grep 해야 한다(이 함정은 server.pyclass Server 위 주석에도 적혀 있다).
  • 불변식: self.plugins 에 claude 가 있으면 ServerServerClaudeMixin 도 합성돼 있다. server_init 등 형제 런타임 훅은 이 불변식에 기댄다.

ServerClaudeMixin 의 메서드는 screen_text(p.screen)(파일 상단 헬퍼)로 패널 화면을 한 문자열로 떠서 claude.py 의 순수 파서에 먹이고, pane.pty.write(...) 로 개입을 주입한다. 모든 PTY 쓰기는 if pane.pty is None: return 가드를 거친다.

런타임 비활성과 합성의 차이: 플러그인 매니저(:plugins)로 런타임에 끄면 훅 (server_scan 등)이 안 불려 동작이 멈추지만, ServerClaudeMixin 메서드 자체는 import 시 이미 합성돼 있다. 메서드까지 완전히 빼려면 서버 재시작이 필요하다.


4. Claude 감지 상태머신

플러그인은 비신뢰 패널 출력(Claude Code CLI 가 그린 TUI 화면)을 스크랩해 상태를 추정한다. Claude 가 고정 위치에 구조화된 신호를 주지 않으므로 전부 휴리스틱 파서(claude.py)다. 핵심 원칙: 지어내지 않는다 — 신뢰 신호가 없으면 None 을 내고 호출부는 발화를 보류한다.

4.1 1차 상태: claude_state(text)

반환 "limit" / "busy" / "idle" / None. 판정 순서가 중요하다(모드 footer 는 busy 중에도 보이므로 busy 를 먼저):

  1. claude_limit(text) → 사용량 리밋 배너(차단)면 "limit".
  2. 작업 스피너(_BUSY_SPINNER_RE) 또는 esc to interrupt"busy".
  3. 권한 모드 footer(shift+tab to·mode on (shift)·단축키 힌트 → "idle".
  4. 어느 것도 아니면 None(Claude Code 아님).

4.2 신호 분리(오탐 방지)

서로 다른 멈춤 상태를 별도 파서로 구분한다 — 각각 다른 개입이 필요하기 때문이다:

파서 의미 올바른 대응
claude_limit 사용량/rate 5h 리밋(시간 지나면 풀림) 리셋 시각까지 대기 후 자동재개
claude_context_hardstop 대화 컨텍스트가 꽉 참(시간이 지나도 안 풀림) 즉시 /compact
claude_api_error 전송 에러(API Error·rate_limit_error·overloaded) 백오프 후 "계속" 재시도

오탐 가드의 실제 사례:

  • _claude_body(text) 가 사용자 입력 줄(>)·소스/diff 줄·슬래시 메뉴 행을 떼어내 사용자가 친 "rate limit"·소스 리터럴·/usage-credits 도움말의 'hit a limit' 오탐을 막는다.
  • claude_context_hardstop 은 화면에 실제 /compact 또는 /clear 리터럴이 떠 있을 때만 True(하드스톱 안내는 항상 "· /compact or /clear to continue" 동반).
  • claude_api_error 는 맨단어 "rate limit" 을 떼고 배너 앵커(API Error:/()와 산문에 못 나오는 JSON 에러타입(rate_limit_error/overloaded_error)만 본다 — Claude 가 자기 설명 문자열을 화면에 띄운 idle 프레임의 오탐을 막은 결과다.

4.3 보조 파서

  • claude_usage / claude_context_pct — 컨텍스트 잔량%·토큰 수. 둘은 의미가 반대: claude_usage 는 표시용 사용량%(100-잔량)를, claude_context_pct 는 자동화 트리거용 잔량%(작을수록 꽉 참)를 낸다.
  • claude_model — 모델 배지(Opus 4.8opus-4.8). 패밀리 화이트리스트는 환경변수 PYTMUX_CLAUDE_MODEL_FAMILIES 로 코드 수정 없이 확장 가능.
  • claude_account / claude_account_full — 계정 식별. 이메일 기반 신뢰 신호만 본다: ① <email>'s Organization ② 계정 라벨(Login:/Email:) 바로 뒤 이메일. 산문·코드·git URL 의 임의 이메일과 RFC 예약 도메인(example.com 등)은 배제. 못 잡으면 None → 서버가 "unknown" 으로 묶는다.
  • claude_welcome — 버전 splash 배너(/clear·세션 시작 = 컨텍스트 비워짐 신호).
  • claude_remote_active / claude_remote_menu / claude_remote_blocked — 원격 제어 (/rc) 상태·관리 메뉴·정책 차단.
  • claude_feedback_prompt — 세션 종료 피드백 배너.
  • parse_usage / parse_inline_limit / parse_reset_delay / parse_reset_ts/usage 패널·인라인 한도·리셋 시각 파싱.

ReDoS 주의: 여러 정규식이 길이 상한({1,9}·{1,64} 등)으로 묶여 있다 — 무제한 +/* 는 거대 빈화면·숫자열·문자열에서 O(n²) 백트래킹으로 폭주한다(실측 ~14–22초). 새 파서를 추가할 때 같은 상한 규약을 따른다.

4.4 30Hz 스캔 루프: _scan_claude(sess, win)

모든 탭의 모든 패널을 매 flush 마다 훑는다(활성 윈도우만이 아니라 — 백그라운드 탭 완료 알림 #22 때문). 한 패널당 흐름:

  1. dirty 게이팅: 마지막 스캔 이후 출력이 없으면(_feed_seq 불변) 스캔을 통째로 건너뛴다. 단 프레임 카운터로 도는 디바운스(완료 안정·헤더 미스·/rc 메뉴 디바운스·busy 이탈 확정)가 진행 중인 pending 패널은 화면이 정적이어도 계속 스캔.
  2. txt = screen_text(p.screen)new_cl = claude_state(txt).
  3. 포맷 미인식 가시화(§3.7): 파서가 None 인데 포그라운드 프로세스가 실제 claude 면(_fg_is_claude, ps 검사라 throttle) _update_fmt_unknown 이 ⚠ 경고를 세워 추적 중단을 가시화한다(화면 포맷과 무관한 ground-truth).
  4. 원격 제어 메뉴 자동 Dismiss·실측 한도 캡처(패널/인라인).
  5. 세션 경계 처리:
    • None→Claude: 토큰 누계 리셋·세션 id 부여·계정 재감지·시작 규칙 주입 예약· auto-launch(/rc+권한 auto) 예약·컨텍스트 디바운스 해제.
    • 수동 /clear(환영 배너 새로 뜸): _reset_token_session 으로 누계 끊기.
    • →None: 예약 해제·권한모드 비움·auto_token_on_exit 발화(진짜 셸 복귀 교차검증).
  6. 토큰 step: tokens.parse_running_tokens(txt) + tokens.step(...) 으로 응답 peak 를 누계에 확정(committed>0_log_tokens 영속).
  7. busy 이탈 히스테리시스(§3.4): busy→idle 첫 프레임은 리페인트 깜빡임일 수 있어 연속 2프레임 idle 일 때만 응답 경계로 확정.
  8. 완료 경계 개입 분기(busy→idle): 프롬프트 단위 클리어·자동 doc/clear·컨텍스트 잔량 정리·반복 루프 카운트·모델 힌트 평가(§5).

상태 변화가 있으면 True 반환 → 코어가 broadcast 한다.


5. 보조 자동 개입과 트리거

모든 개입은 고정 규칙·임계값이고 토글로 켜고 끈다(대부분 기본 OFF 또는 안전 기본값). token-saver 팝업에서 토글하며 opts.json plugin_opts 에 영속한다.

이전의 토큰 과사용 자동 완화(실측 한도 게이트·컨텍스트 잔량 자동 정리·하드스톱 자동 /compact·예산 계획 유도·모델 과선택 힌트)는 제거됐다. 토큰 추적·표시는 유지되고, 자동 개입은 아래의 옵트인 동작만 남는다.

token-saver 설정

5.1 발화직전 재확인(공통 안전 패턴)

시간 지연이 있는 모든 자동 주입(_fire_resume·_fire_retry)은 발화 직전 화면을 재확인한다 — 예약과 발화 사이에 사용자가 직접 대응했거나 화면이 바뀌었으면 주입을 취소해 작업 중인 Claude 에 끼어들지 않는다. 또한 server_input 이 사용자 입력 시 무장된 모든 타이머를 거둔다(사용자가 작업을 이어받음).

5.2 토큰 리밋 자동재개 (autoresume)

  • 트리거: claude_state == "limit" + parse_reset_delay 로 리셋 시각 파싱.
  • _maybe_schedule_resumedelay + 5초_fire_resume 예약.
  • _fire_resume: 화면이 여전히 limit 일 때만 resume_msg("continue") 주입.

자동재개

5.3 전송 에러 자동 재시도 (claude_auto_retry, 기본 ON)

  • 트리거: _hdr_claude(디바운스된 Claude 신호) + claude_api_error(txt) + autoresume 미무장.
  • 백오프 _RETRY_DELAYS = (60, 120, 300) — 1·2차 빠른 재시도 후 5분 케이던스로 무기한.
  • _fire_retry: 여전히 에러이고 busy 아닐 때만 "계속" 주입.

5.4 권한모드 자동 전환 (claude_auto_mode)

  • idle 시 권한모드를 shift+tab 폐루프로 순환 주입. bypass(명시적 위험)는 불간섭.

권한모드

5.5 프롬프트 단위 정리 (prompt_clear)

  • 응답 완료 경계마다 진행상황 문서화 + /clear 1회. 옵트인이며 완료 경계에서만 동작.

5.6 알림 전용(개입 없음)

  • 장기 턴 경고(long_turn반복 루프 경고(repeat_alert, track_repeat).

5.7 세션 종료 시 토큰 화면 (auto_token_on_exit, 기본 ON)

  • _hdr_claude True→False(30프레임 디바운스 = 진짜 종료) + _claude_really_exited (포그라운드 셸 복귀 교차검증) 시 한도 요약을 패널 스크롤백에 주입.

6. 토큰 회계 데이터 모델

6.1 토큰 누계 상태머신 (tokens.py)

Claude 의 busy footer 는 ↑/↓ N tokens 처럼 현재 응답 한 건의 running 토큰을 보인다(세션 누계도, 프레임 델타도 아님). 정의: 세션 누계 = 각 응답의 최종(peak) 합.

  • parse_running_tokens↑/↓ 가 앞에 붙은 토큰만 합산(누계 언급과 구분).
  • step(state, running, busy) — 응답 종료(busy 끝 또는 running 급감=새 응답 시작)에 peak 를 total 에 확정하고 그 양을 반환(committed>0 → 영속 로깅 이벤트).
  • 잔상 가드(idle_mark): 응답 종료 후에도 ↑/↓ N tokens 텍스트가 화면에 남으면 비-busy 프레임마다 같은 값이 재확정되던 버그가 있었다(라이브 하루치의 83%, 한 응답 최대 117회 중복). 비-busy 확정 시 그 값을 idle_mark 로 기억하고 busy 복귀까지 mark 이하 running 을 잔상으로 무시한다.
  • 표시 누계 = total + peak(진행 중 응답 포함, 경계에서 연속·이중계산 없음).

6.2 SQLite 스키마 (usagedb.py)

DB 파일은 플러그인 디렉터리 하위 db/ 에 산다(delete-to-disable 가 데이터까지 포함). PYTMUX_HOME 설정 시 <home>/db/ 로, PYTMUX_TOKENS_DB 로 강제 지정 가능 (테스트 격리). 첫 연결 시 레거시 위치에서 1회 마이그레이션(WAL 사이드카 동반).

PRAGMA user_version = 8. WAL 모드 + synchronous=NORMAL(응답마다 fsync 안 함).

TABLE usage      (ts, tab, pane, session, account, tokens, tzoff, model)
TABLE limits     (ts, account, session_pct, session_reset,
                  week_all_pct, week_all_reset,
                  week_sonnet_pct, week_sonnet_reset, source)
TABLE usage_xc   (xkey PK, ts, session_uuid, tab, pane, pytmux_session,
                  model, account, input, output, cache_create, cache_read,
                  is_sidechain)              -- 트랜스크립트 권위(cache 포함)
TABLE usage_xc_cursor (path PK, offset, mtime)   -- 증분 테일 오프셋
버전 내용
v1 usage 테이블(레코드 스키마 = usagelog 와 동일).
v2 limits 테이블 추가(실측 /usage 스냅샷 이력). CREATE IF NOT EXISTS 라 v1 DB 자동 업그레이드.
v3 데이터 정리: 잔상 가드 이전에 쌓인 중복 커밋(60초 내 같은 pane·session·tokens 연쇄)을 첫 건만 남기고 usage_dup_archive 로 격리(하드 삭제 아님).
v4 데이터 정정: @ 없는 가짜 계정(화면 스크랩 약신호가 적재한 산문 구절)을 unknown 으로 일괄 정정. 원값은 usage_acct_fixlog 에 보존.
v5 usage.tzoff(쓰기 시점 로컬 UTC 오프셋) 컬럼 추가 — DST/여행으로 시스템 tz 가 바뀌어도 hour 버킷이 재분류되지 않게(레거시 NULL 은 시스템 로컬 폴백).
v6 usage.model(적재 시점 활성 모델 배지) 컬럼 추가 — 티어별 지출·과티어 정량 측정. 레거시 NULL=미상.
v7 §10-D 트랜스크립트 회계: usage_xc·usage_xc_cursor 추가. 스크랩 usage(footer ↑/↓)는 cache 를 못 봐 실제의 ~0.4%(≈230× 과소)만 잡으므로, ~/.claude/projects/*.jsonlmessage.usage 4항목(input·output·cache_creation·cache_read)을 PK=xkey(멱등 INSERT OR IGNORE)로 별도 적재한다. cache 포함 정확 누계는 이 테이블이 담당.
v8 usage_xc.account 컬럼 추가 — 트랜스크립트엔 계정 라벨이 없어 ingest 시 pane._claude_account 로 채운다(백필된 레거시 행은 NULL→unknown). 이로써 cache 포함 누계를 계정별로도 집계(팝업 계정 뷰·상태줄 계정 Σ).

마이그레이션은 단계별로 실패 시 user_version 을 보류해 다음 connect 에서 재시도한다.

6.3 세션 버킷팅

  • 패널마다 _claude_session_id 를 가지며 None→Claude 전이·수동/auto /clear 마다 새 id 를 부여(_next_claude_session_id).
  • _seed_session_seq: 서버 부팅마다 카운터가 0 으로 초기화돼 재시작 후 같은 id 가 옛 세션과 병합되던 문제를, 첫 부여 직전 DB 의 max(session) 으로 시드해 막는다.

6.4 집계 (usagelog.py)

서버는 records 만 돌려주고(request_token_log 회신), 클라가 aggregate/agg_view 로 시/일/주/월×계정/세션 버킷을 라운드트립 없이 만든다. 신뢰 계정 판정(is_trusted)· unknown 폴딩(fold_unknown)·tzoff 기반 버킷 키(bucket_key)가 순수 규칙으로 공유된다. 서버는 추가로 total_all(스크랩 활동 Σ)·daily(일자별 합성 레코드)·reconcile(실측 Δ vs 스크랩 Σ 대사)·daily_pct/hourly_pct/hourly_week_pct(권위 /usage 한도 %)를 함께 보낸다.

§10-D 표시층 캐시포함: usage_xc(트랜스크립트, cache 포함)가 차 있으면 request_token_log 회신의 records·dailycache 포함(xc_query_records· xc_daily_breakdown)으로 보낸다 — 스크랩 usage 행과 동일한 행 구조(tokens 만 input+output+cache_create+cache_read full, session=pytmux_session)라 클라 집계 코드는 무변경이다. 따라서 팝업 기간/세션/계정/모델 상세표가 그대로 cache 포함 수치가 된다. 스크랩 usage(footer ↑/↓, ~0.4%)는 total_all 로만 남아 팝업 요약줄에 activity~ 보조신호로 표기된다. 상태줄 계정 Σ(claude_tokens)도 _account_token_total_xc(usage_xc full)로 보내며, 단일 계정이면 전체 full(unknown 포함)을, 다중 계정이면 계정별 full 을 쓴다(usage_xc 미보유/빈 테이블이면 스크랩 폴백). 요약 Σ 는 xc_totals(full/footer/cache_read/cache_create/ratio)를 1차값으로 보인다.

토큰 로그

6.5 한도 스냅샷 이력 (limits 테이블)

실측 /usage 값이 바뀐 순간만 insert_limits 가 적는다(직전과 동일값이면 skip). 출처(source)는 "panel"(/usage 전체)·"inline"(footer 인라인)·"probe"(그림자). reconcile 뷰가 실측 스냅샷 Δpct 와 스크랩 Σ 를 대조해 진단을 돕는다.


7. 그림자 /usage 프로브

usageprobe.query_usage숨은 claude 세션을 PTY(_PosixSession)/ConPTY (_WinSession)로 띄워 /usage·/status 패널을 스크랩한다 — 사용자 화면 무간섭.

  1. 부팅 대기(입력 박스 신호: shortcuts·shift+tab·for agents 중 하나).
  2. 부팅 화면에서 계정 캡처(별칭 acct = 디스크 영속용, 전체 acct_full = 휘발성 표시용).
  3. /usage\r 주입 → % used 대기 → parse_usage 로 세션 5h·주 전체·주 Sonnet % 파싱.
  4. 계정 미식별 시에만 Esc+/status\r 1회 추가 스크랩(계정 라벨은 Status 탭에만 존재).
  5. 즉시 kill/close.

서버측 refresh_usage(async)가 executor 스레드에서 이를 돌려(타임아웃 35초) self._usage 에 저장하고 broadcast 한다. 자동 갱신 주기(usage_refresh_sec, 기본 600초)에 더해 이벤트 트리거(응답 종료 디바운스·임계 부근 주기 단축)로 보강한다. spawn 이 비싸므로 (부팅 ~12초) 중복 방지(_usage_busy)·디바운스(_schedule_usage_refresh)가 핵심이다.

사용량 패널

그림자 세션이 폰/데스크탑 앱과 다른 계정이면 한도 %가 실제로 다르므로, 사용자가 눈으로 대조할 수 있게 account 를 결과에 함께 싣는다.


8. 클라이언트측 표시: 배지·렌더·팝업

  • 상태줄(clientstatus.py): init_defaults(필드 설치)·absorb(status 흡수)· render_segs(좌하단 세그먼트 — 모델 배지·컨텍스트%·토큰Σ·예산·카운트다운·경고 + 클릭존). 임계 80%/100% 에 노랑/빨강 ⚠, 무장된 자동 액션은 ⏳+남은 초.
  • 콘텐츠 레이어(clientrender.py): render 가 Claude 프롬프트 헤더를 그리고 _scan_all_footer_zones 가 권한모드·원격제어·busy interrupt 클릭존을 채운다.
  • 팝업(screens.py, 지연 import): TokenLogScreen·InfoScreen(usage 한도)· ModelCtxScreen·PermModeScreen·RulesEditScreen·ClaudeSaverScreen.

서버발 사용자 표면(원격 notice 등)은 서버가 t() 를 못 부르므로(로케일 미지) key+kw 만 싣고 한국어 text 폴백을 동반, 클라가 _notice_text 로 번역한다. 플러그인 자체 토스트 문자열은 __init__.pyccmsg.* i18n 카탈로그(ko/en 대칭)에 모여 있다.


9. 확장·테스트하는 법

거대 파일 부분 읽기

servermixin.py(~2천 줄)·screens.py(~1900 줄)·__init__.py(~1천 줄)는 한 컨텍스트에 안 들어온다. grep -n '^class \|^ def \|^def ' 로 위치를 잡고 Read offset/limit 으로 관심 영역만 본다. 명령 라우팅은 명시적 if/elif 라 grep '"set_autoresume"' 처럼 문자열로 핸들러를 바로 찾을 수 있다.

새 감지 신호를 추가하려면

  1. claude.py순수 함수로 파서를 추가(텍스트 in, 판정 out). ReDoS 길이 상한 준수.
  2. tests/fixtures/claude/ 에 실 캡처 골든 픽스처를 두고 회귀 고정 — 현행 Claude UI 포맷에 강결합돼 있어 포맷이 바뀌면 조용히 멈춘다(§3.7 _fmt_unknown 이 그걸 가시화).
  3. _scan_claude 에서 그 파서를 호출하고, 개입이면 발화직전 재확인을 거친다.

새 토글 옵션을 추가하려면

  1. _OPTS_KEYS(key, default, cast) 추가 → opts.json plugin_opts 영속 자동.
  2. set_<key> 메서드(servermixinserver_command 디스패치 분기·SAVER_ROWS/ saver_display/saver_action(__init__.py) 추가.
  3. server_statusfull 블록에 정적 옵션이면 실어 클라에 전달.

테스트

커밋 전 필수: python3 tests/run.py 로 헤드리스 전체 스위트를 돌려 요약줄 N passed, 0 failed 를 확인한다(run.py 는 실패해도 종료코드 0 일 수 있으니 요약줄을 본다). 서브셋 실행은 플러그인 믹스인 poison 으로 가짜 실패가 날 수 있어 권위는 항상 전체 스위트다. 관련 테스트: test_claude(파서 단위)·test_server(스캔/개입/토큰)· test_client(상태줄/팝업). 실 PTY·실 ConPTY·실 Claude 패널은 driver 검증 불가다.


관련 문서

Clone this wiki locally