-
Notifications
You must be signed in to change notification settings - Fork 0
Plugin System
pytmux 의 기능 중 상당수(시계 오버레이, 달력, IME 상태 배지, Claude Code 통합 등)는 코어가 아니라 플러그인으로 구현되어 있습니다. 이 페이지는 그 플러그인 시스템의 개념과 아키텍처를 설명합니다. 실제로 플러그인을 하나 작성하는 단계별 가이드는 별도 페이지 Plugin-Authoring-Guide 를 참고하세요.
한 줄 요약:
pytmuxlib/plugins/<name>/하위의 서브패키지를 선택적으로 불러와 명령·자동완성·디스패치·메시지·렌더·서버 요청을 코어에 합칩니다. 디렉토리를 통째로 지우면 그 기능은 명령 검색·자동완성·디스패치 어디에도 나타나지 않고 조용히 비활성화됩니다. 코어는 플러그인을 직접 import 하지 않고 오직 레지스트리를 통해서만 닿기 때문입니다.
- 거대하고 선택적인 기능(예: 디렉토리 트리, Claude Code 통합)을 코어에서 떼어내 한 디렉토리에 응집시키고, 그 디렉토리를 지우는 것만으로 기능을 끌 수 있게 합니다.
- 코어(
client/clientutil/serverio/server등)는 기능 모듈을 하드 참조하지 않습니다. 명령 이름·메시지 타입·서버 액션·렌더 단계를 레지스트리에 위임하므로, 플러그인이 없으면 해당 경로는 에러 없이 no-op 이 됩니다.
이 설계 덕분에 코어는 "일반 멀티플렉서"라는 작고 안정적인 표면을 유지하고, 무거운 도메인 기능은 독립적으로 추가·제거·실험할 수 있습니다.
플러그인 시스템의 핵심 계약은 delete-to-disable(지워서 끄기)입니다.
pytmuxlib/plugins/
├── __init__.py # 로더 + Registry(훅 계약 한곳)
├── clock/ # 디렉토리 = 한 플러그인
│ └── __init__.py # PLUGIN 객체를 노출
├── calendar/
├── claude-code/
└── ...
-
pytmuxlib/plugins/<name>/디렉토리 하나가 곧 플러그인 하나입니다. - 그 디렉토리를 통째로 삭제하면, 해당 기능은 명령 팔레트·자동완성·디스패치·렌더 어디에도 나타나지 않고 조용히 사라집니다. 코어 코드를 한 줄도 고칠 필요가 없습니다.
- 가장 큰 플러그인인
claude-code까지도 이 계약을 만족합니다. 디렉토리를 지우면 서버와 클라이언트 모두 에러 없이 동작하며, Claude 관련 헤더·상태줄 세그먼트·클릭존·토큰 팝업· ESC 동선이 전부 사라지고 평범한 터미널 멀티플렉서로 남습니다.
런타임에 가역적으로 끄고 싶다면(디렉토리를 지우지 않고) 플러그인 관리 팝업에서 토글할 수 있습니다. 이 경우 비활성 플러그인은 모든 훅 디스패치에서 건너뛰어져 무동작이 되며, 설정에 영속됩니다. delete-to-disable 과 달리 되돌릴 수 있습니다.
-
pytmuxlib/plugins/__init__.py의load()가Registry를 만듭니다. -
pkgutil.iter_modules로plugins/하위의 서브패키지(디렉토리 +__init__.py)를 찾아importlib.import_module로 불러오고, 각 모듈의PLUGIN객체를 모읍니다. - import 가 깨진 플러그인은 조용히 건너뜁니다 — 하나가 망가져도 앱 전체가 멈추지 않습니다.
-
클라이언트와 서버 양쪽이
load()를 호출해 각자self.plugins로 보관합니다.
코어는 절대 from pytmuxlib.plugins.clock import ... 같은 직접 import 를 하지 않습니다.
대신 모든 상호작용이 Registry 의 메서드/프로퍼티를 통과합니다. 대표적인 통합 지점:
- 명령 디스패치:
client._run_command가 코어 분기 후plugins.handle_command(...)폴백. - 서버 메시지 처리:
client._dispatch의 마지막 else 에서plugins.handle_message(...). - 알 수 없는 서버 액션:
serverio._handle_cmd의 else 에서plugins.handle_server_request(...). - 명령 목록/자동완성: 코어의
COMMANDS·COMPLETIONS에plugins.commands·plugins.completions를 병합해 팔레트·?목록·자동완성에 노출.
따라서 어떤 플러그인이 없으면 그 경로는 자연스럽게 빈 값/no-op 으로 처리됩니다.
PLUGIN 객체는 자신이 참여하고 싶은 훅만 노출하면 됩니다. 노출하지 않은 훅은 레지스트리가
빈 값으로 취급합니다. 한 플러그인이 명령 한 줄만 더할 수도, 서버 믹스인과 렌더 단계까지
전부 참여할 수도 있습니다.
PLUGIN 객체가 가질 수 있는 식별 속성:
| 속성 | 용도 |
|---|---|
name |
플러그인 이름(토글·영속 키) |
description |
관리 팝업/진단 표시 |
category |
분류 라벨(예: "오버레이") |
default_enabled |
False 면 배포 기본 OFF(예: 레코딩) |
플러그인의 __init__.py 는 textual 을 모듈 최상단에서 import 하지 않습니다. 서버
프로세스도 같은 load() 코드를 읽기 때문입니다. 화면(textual) 등 무거운 의존은 실제로
필요한 시점에 메서드 안에서 지연 import 합니다(예: from .screens import SomeScreen).
claude-code 처럼 하이픈이 든 디렉토리명도 됩니다. importlib.import_module(...) 와
패키지 내부 상대 import(from .screens import X)는 하이픈과 무관하게 동작합니다.
단 외부 절대 import 문(from pytmuxlib.plugins.claude-code...)은 문법 오류이므로,
테스트 등 외부에서 가져올 땐 importlib.import_module(...) 를 씁니다.
훅 계약은 pytmuxlib/plugins/__init__.py 의 Registry 한 곳에 모여 있습니다. 크게
네 갈래로 나눌 수 있습니다.
| 훅 | 의미 |
|---|---|
commands |
코어 COMMANDS 에 합쳐져 ?/팔레트·자동완성에 노출 |
noarg |
인자 없이 즉시 실행해도 되는 명령 |
completions |
자동완성 추가 후보 |
command_options |
팔레트 옵션(선택지) 스키마 |
pane_scoped |
패널 단위로 동작하는 명령 표시 |
menu_items |
컨텍스트/전체 메뉴 항목 기여 |
서버는 단일 스레드 asyncio 루프입니다. 서버측 훅은 스캔·상태·입력·명령 등 서버 동작에 부수효과를 더합니다.
| 훅 | 의미 |
|---|---|
server_mixins |
Server 의 동적 베이스 클래스로 합성될 믹스인 제공 |
server_init / server_opts_init / server_opts_serialize
|
서버 초기화·옵션 로드/저장 참여 |
handle_server_request |
알 수 없는 서버 액션을 받아 회신 메시지(dict) 반환 |
server_scan |
패널 주기 스캔 참여 |
server_filter_rows |
서버가 보내는 행을 표시 필터링 |
server_status / server_pane_overview
|
status·개요 메시지에 필드 주입 |
server_input / server_paste / server_pty_output
|
입력·붙여넣기·PTY 출력 가로채기 |
server_pending / server_usage_refresh
|
보류 상태·주기 갱신 |
server_command |
서버 명령 액션 처리 |
server_shutdown |
종료 정리 |
동적 믹스인 합성: 코어
server.py는 특정 믹스인을 import 하지 않고plugins.Registry.server_mixins()가 돌려준 클래스들을Server의 베이스로 합성합니다 (원래의 MRO 보존). 그래서 일부Server메서드는server.py에 정의가 없고 런타임에 플러그인이 더한 것입니다.
| 훅 | 의미 |
|---|---|
attach_client |
앱 인스턴스마다 1회 — 인스턴스 글루(클로저 메서드) 설치 |
client_unload |
앱 종료 시 정리(예: 보조 프로세스 종료) |
handle_command / handle_message
|
클라 명령·서버 메시지 처리 |
client_tick / client_key
|
주기 틱·키 입력 참여 |
client_overlay / client_overlay_key / client_close_overlay
|
패널을 덮는 오버레이 레이어(시계·달력) |
client_render |
콘텐츠 레이어 그리기(스티키 헤더·클릭존 스캔 등) |
client_status |
status 메시지의 플러그인 필드 흡수 |
client_statusbar / client_statusbar_update / client_statusbar_init / client_statusbar_badges
|
하단 상태줄 세그먼트·배지 렌더 |
client_status_tabs |
통합 상태 팝업에 탭 기여 |
attach_client 는 특히 중요합니다. 클라이언트 앱(PytmuxApp)이 팩토리 안의 중첩 클래스로
정의되어 서버처럼 동적 믹스인 합성을 못 쓰기 때문에, 플러그인은 attach_client 에서
app.some_method = closure 형태로 인스턴스 클로저를 설치해 기능을 붙입니다. 코어는
그런 메서드를 getattr(app, ..., 기본값) 가드로만 읽으므로 플러그인이 없어도 안 깨집니다.
| 훅 | 의미 |
|---|---|
pane_init |
패널 생성 시 플러그인 소유 필드 설치 |
pane_reset |
패널 리셋 시 정리 |
pane_closing |
패널 종료 정리 |
pane_serialize / pane_restore
|
세션 유지 재시작 시 패널 상태 직렬화/복원 |
- 발견은
_discover()가pkgutil.iter_modules로plugins/하위의 서브패키지만 순회하며 이루어집니다(디렉토리 +__init__.py조건). 단일.py모듈은 무시됩니다. - 별도의 등록 명단이 없습니다 — 디렉토리를 추가하면 자동 발견되고, 지우면 사라집니다.
- import 예외가 난 플러그인은 건너뛰므로 다른 플러그인이나 앱 전체에 영향을 주지 않습니다.
- 발견된 플러그인 중
default_enabled = False인 것은 배포 기본 OFF 로 두고, 사용자 설정에 별도 지정이 없으면 비활성 상태로 시작합니다.
플러그인이 영속 설정을 가질 때, 코어 옵션과 충돌하지 않도록 plugin_opts 네임스페이스
밑에 저장합니다.
- 서버 초기화 때
server_opts_init훅으로 플러그인이 자기 옵션을plugin_opts에서 읽습니다(과거 top-level 키 하위호환 포함). - 저장 때는
server_opts_serialize훅이 dict 를 돌려주고, 코어는 그것을 설정 파일의plugin_opts키 밑에 불투명하게(opaque) 저장합니다. 코어는 그 키들의 의미를 알지 못합니다 — 단지 보관할 뿐입니다. - 플러그인이 없으면
plugin_opts는 비어 사라지므로, 설정 파일도 깨끗하게 유지됩니다.
이 불투명 저장 덕분에 코어는 플러그인이 어떤 설정을 갖는지 몰라도 되고, 플러그인은 자유롭게 설정을 추가·제거할 수 있습니다.
저장소에 함께 들어 있는 레퍼런스/실사용 플러그인입니다(작성법·사례는 Plugin-Authoring-Guide 참고).
| 플러그인 | 무엇 | 참여하는 축 |
|---|---|---|
clock |
패널을 큰 블록 시계로 덮는 오버레이 | 클라 오버레이(레퍼런스) |
calendar |
패널을 이번 달 달력으로 덮는 오버레이 | 클라 오버레이(clock 미러) |
ncd |
Norton Change Directory 풍 디렉토리 트리 | 모달 + 서버 왕복 |
ime-indicator |
우상단 IME(한/영) 상태 배지 | 키 관찰 + 렌더 |
claude-prompt-history |
Claude 프롬프트 히스토리(미리보기·팝업·점프) | 서버 입력 훅 + 모달 |
claude-token-usage-view |
사용 한도 막대 + 리셋 카운트다운 | status 흡수 + 모달 |
claude-resume |
Claude 세션 자동 재개 보조 | 서버 훅 |
claude-disable-feedback |
비모달 피드백 오버레이 표시 필터 | 서버 필터 + 렌더 |
rec |
패널 입출력 레코딩(기본 OFF) | 서버 출력 훅 |
claude-code |
Claude Code 통합(스캔·상태·토큰·렌더 전체) | 모든 축 |
대표 사례:
-
clock/calendar— 가장 작은 레퍼런스. 패널 위에 한 레이어를 덮는 오버레이만으로 완결됩니다. 클라 오버레이 훅의 모범 예시입니다. -
ncd— 모달 화면 + 서버 왕복의 예시. 서버 측은handle_server_request(디렉토리 나열·조상 사슬), 클라 측은handle_message(결과 표시)로 처리합니다. -
claude-code— 가장 큰 플러그인. 주기 스캔 루프, 상태줄/헤더 렌더, 토큰 회계, 옵션 영속, 다수 명령과 팝업이 얽혀 있어 거의 모든 훅 카테고리에 참여합니다. 그럼에도 delete-to-disable 계약을 완전히 만족합니다 — 코어에는 이 플러그인의 메서드를 이름으로 직접 부르는 코드가 한 곳도 없으며, 디렉토리를 지우면 일반 멀티플렉서로 깔끔히 남습니다.
claude-code 가 모든 훅을 동원하면서도 코어를 전혀 오염시키지 않는다는 점이, 이 플러그인
시스템이 큰 기능까지 감당할 수 있음을 보여 줍니다.
- Plugin-Authoring-Guide — 플러그인을 직접 작성하는 단계별 가이드
- Project-Overview — pytmux 전체 아키텍처 개요
- User-Manual — 사용자 매뉴얼
소개 · 사용
- Project-Overview
- User-Manual
- Screenshots
- Settings-Popup
- Single-Session-Model
- Tmux-Feature-Comparison
플러그인
Claude Code 플러그인
- Claude-Code-Plugins
- Dev-Guide-claude-code
- Dev-Guide-claude-token-usage-view
- Dev-Guide-claude-prompt-history
- Dev-Guide-claude-resume
- Dev-Guide-claude-disable-feedback
플랫폼 · 성능
품질 · 보안
리뷰·분석 보고서
연혁
기여