-
Notifications
You must be signed in to change notification settings - Fork 0
Dev Guide claude resume
claude-resume 는 이 머신에 저장된 과거 Claude Code 세션을 시각·프로젝트·AI 제목 으로
나열하고, 하나를 골라 Enter 하면 새 탭에서 그 세션을 리줌(claude --resume <id>)하는
피커 플러그인이다. 이 문서는 플러그인 내부 구조와 확장·테스트 방법을 다루는 개발자용 가이드다.
플러그인 일반 작성 규약은 Plugin-Authoring-Guide 를, Claude Code 통합 플러그인 군 전체는 Claude-Code-Plugins 를 참조하라.
- 1. 목적과 전체 흐름
- 2. 파일 구성
- 3. 등록 — 명령·훅·메시지
- 4. sessions.py — 세션 기록 열거
- 5. 피커 화면(3열 UI)
- 6. 선택→리줌 경로
- 7. 보안: 세션 id 위생
- 8. 확장하기
- 9. 테스트
- 관련 문서
Claude Code 는 대화 세션을 JSONL 기록 파일로 디스크에 남긴다. 이 플러그인은 그 기록을 훑어 사람이 알아볼 수 있는 한 줄짜리 라벨(시각·프로젝트·제목)로 정리해 피커로 보여 주고, 사용자가 고른 세션을 새 탭의 셸에서 다시 이어 가게 한다.
핵심 설계 결정은 열거·리줌을 서버에서 수행한다는 것이다. 세션 파일이 서버 측 머신에 있으므로 서버가 직접 읽어야 정확하고(remote-attach 상황에서도 올바른 머신을 본다), 새 탭의 패널 id race 도 피한다. 클라이언트는 목록을 요청하고 받은 결과로 모달을 띄울 뿐이다.
클라 명령 claude-resume
│ app.send_cmd("claude_list_sessions")
▼
서버 handle_server_request("claude_list_sessions")
│ sessions.list_sessions(limit=300)
▼
클라 handle_message(t == "claude_sessions") → ClaudeResumeScreen 푸시
│ 행 선택 + Enter
▼
클라 send_cmd("claude_resume_session", session_id, cwd)
▼
서버 handle_server_request("claude_resume_session")
│ new_window(path=cwd) → 새 패널 pty 에 "claude --resume <id>\r" write
▼
서버 _broadcast_session(sess) → 전 클라에 새 탭 동기화
delete-to-disable: 이 플러그인 디렉토리를 통째로 지우면 claude-resume 명령과 서버 회신이
조용히 사라지고 코어는 그대로 동작한다(코어는 이 플러그인을 직접 import 하지 않는다).
| 파일 | 역할 | textual 의존 |
|---|---|---|
__init__.py |
코어와의 계약 — 명령 메타·디스패치·메시지/요청 핸들러. 가벼움. | 없음 |
sessions.py |
세션 열거(순수 로직). 서버에서 import 된다. | 없음 |
screen.py |
리줌 피커 모달. 클라에서 실제로 열 때 지연 import. | 있음 |
sessions.py 와 __init__.py 는 textual 을 최상단에서 import 하지 않는다 — 서버 프로세스도
이들을 읽기 때문이다. textual 위젯이 필요한 screen.py 는 handle_message 안에서 지연
import 한다.
플러그인은 _ClaudeResumePlugin 인스턴스(PLUGIN)로 레지스트리에 노출된다. 모든 후크 이름은
다른 플러그인과 동일한 계약을 따른다.
COMMANDS = [
("claude-resume", "이 머신의 Claude Code 세션 목록 — …", "Claude"),
]
NOARG = {"claude-resume", "claude-sessions", "cr"}
_ALIASES = ("claude-resume", "claude-sessions", "cr")-
COMMANDS는 코어가COMMANDS / COMPLETIONS / COMMAND_NOARG에 합쳐 쓴다. 세 번째 요소"Claude"는 명령 카테고리다. -
claude-resume외에 별칭claude-sessions,cr을 받는다. 셋 다 인자가 없으므로NOARG에 등록한다.
-
attach_client(app)—app.request_claude_sessions진입점을 설치한다. 이 함수는app._want_claude_sessions = True플래그를 세우고app.send_cmd("claude_list_sessions")로 서버에 목록을 요청한다. -
handle_command(app, c, args)—c가 별칭 중 하나면request_claude_sessions()를 호출하고True(소비) 반환. -
handle_message(app, msg)— 서버 응답t == "claude_sessions"를 받으면, 요청 플래그가 서 있을 때만(_want_claude_sessions) 화면을 연다. 플래그가 없으면 방어적으로 무시한다(요청하지 않은 응답 차단). 여기서screen.ClaudeResumeScreen을 지연 import 해app.push_screen(...)한다.
-
handle_server_request(server, sess, action, msg)— 두 액션을 처리한다.-
"claude_list_sessions"→sessions.list_sessions(limit=_LIST_LIMIT)결과를{"t": "claude_sessions", "sessions": [...]}로 회신. -
"claude_resume_session"→ 6장 참조.
-
명령 메타가 카테고리 "Claude" 로 등록되므로 명령 팔레트/컨텍스트 메뉴의 Claude 그룹에서
선택할 수 있고, :claude-resume(또는 :cr)로 직접 호출할 수도 있다.
i18n.register(...) 로 ko/en 카탈로그를 싣는다. ko 는 COMMANDS 에서 cmd.<name> 키가
자동 시드되고, 피커 화면 문자열(cresume.title / .none / .hint / .opening)을 ko·en 양쪽에
보강한다. 화면 코드는 항상 i18n.t("cresume.…") 로 가져온다.
sessions.py 는 textual 무관 순수 로직이라 서버에서 바로 쓴다.
Claude Code 는 대화 세션을 사용자 홈의 Claude 세션 디렉토리 아래에 프로젝트별로 나누어 저장한다. 개략적인 레이아웃은 다음과 같다.
~/.claude/projects/<cwd-슬러그>/<session-uuid>.jsonl
-
<cwd-슬러그>= 그 세션의 작업 디렉토리 경로에서 구분자를-로 치환한 이름. - 각
.jsonl은 줄당 1개 JSON 이벤트. 슬러그 역변환은 손실이라 그대로 라벨에 쓰지 않는다.
진입점은 projects_dir() 로, os.path.expanduser("~") 아래의 세션 루트 경로를 돌려준다.
하드코딩된 홈 경로는 없다.
type |
필드 | 용도 |
|---|---|---|
ai-title |
aiTitle |
AI 생성 세션 제목(공식 /resume 피커가 보여주는 제목) |
last-prompt |
lastPrompt |
마지막 사용자 프롬프트 |
user |
message.content |
사용자 메시지(첫 메시지 폴백 · 빈 세션 판별) |
| (아무 줄) | cwd |
세션 작업 디렉토리(리줌 시 그리로 cd) |
-
parse_session(path)— JSONL 한 파일을 한 줄씩 읽어 메타 dict 를 만든다. 깨진 줄 (ValueError/TypeError)·비-dict 는 건너뛴다.cwd는 처음 본 값을 채택. 제목 우선순위는ai-title>last-prompt> 첫user메시지 > session id.user이벤트가 0개인 빈 세션은None으로 제외한다(리줌 대상 아님). 결과 dict:{"id", "cwd", "title", "mtime", "ai_title", "last_prompt"}_user_text(o)는content가 str 이거나[{type:"text", text}]리스트인 두 형태를 모두 처리한다._clean(s, n)은 개행·연속 공백을 한 칸으로 줄이고_TITLE_MAX(200) 로 자른다. -
_project_label(cwd, slug)— 표시용 프로젝트 라벨.cwd가 있으면 마지막 두 경로 요소(예:dir/subdir)를, 한 요소뿐이면 그 요소를,cwd가 없으면 슬러그를 쓴다. -
list_sessions(root=None, limit=None)— 루트 아래 모든 슬러그 디렉토리의*.jsonl을parse_session으로 훑어, 각 항목에project라벨을 더하고mtime내림차순(최신순) 으로 정렬한다.limit이 있으면 상위 N 만 반환. 디렉토리/파일 접근 실패(OSError)는 조용히 건너뛰어 부분 결과라도 보여 준다.root미지정 시projects_dir()를 쓴다 — 이 인자 덕에 테스트에서 임시 디렉토리를 주입할 수 있다.
__init__.py 의 _LIST_LIMIT = 300 이 기본 상한이라, 아주 오래된 세션까지 무한정 나열하지
않고 최신 300개만 보낸다.
ClaudeResumeScreen(ModalScreen)이 서버가 보낸 세션 목록을 한 줄짜리 행으로 보여 준다.
각 행은 세 열로 구성된다.
수정시각 프로젝트 제목
───────────── ──────────────────── ─────────────────────────
MM-DD HH:MM dir/subdir AI 생성 제목 또는 폴백…
-
시각 —
_when(mtime)가"%m-%d %H:%M"로 포맷(로컬 시간). 실패 시 빈 문자열. -
프로젝트 —
_project_label결과를_cellpad(s, _PROJ_CELLS)로 표시 셀폭 22 에 맞춰 우측 패딩·정렬한다._cellpad는clientutil._char_cells로 글자 폭을 재서 CJK 2칸 을 고려하고, 넘치면…로 자른다(폭 계산이 폰트 무관해야 정렬이 맞는다). -
제목 —
parse_session이 고른 제목.
행 빌드는 _row(s) 가 담당하고, 모든 텍스트 라벨은 markup=False 로 만들어 세션 제목 안의
대괄호 등이 textual 마크업으로 오해되지 않게 한다. 세션이 없으면 리스트 대신
i18n.t("cresume.none") 안내를 보인다. 하단 힌트는 i18n.t("cresume.hint")
(↑↓ 이동 · Enter 새 탭에서 리줌 · Esc 닫기).
-
on_mount— 세션이 있으면ListView에 포커스를 준다. -
on_list_view_selected— Enter/클릭으로 행 선택. 아이템 idcr_{i}에서 인덱스를 뽑아_resume(idx)호출(event.stop()으로 전파 차단). -
on_key—escape면 닫는다. -
on_click—[x]버튼(crclose) 클릭이나 모달 바깥(백드롭) 클릭이면 닫는다 (crbox안쪽인지 부모 체인을 거슬러 판정). 다른 모달과 동일한 닫기 동선.
행을 고르면 _resume(idx) 가:
-
self.app.send_cmd("claude_resume_session", session_id=s["id"], cwd=s["cwd"])로 서버에 리줌을 요청하고, -
i18n.t("cresume.opening", title=…)안내를 표시한 뒤, - 모달을 닫는다(
dismiss(None)).
서버 handle_server_request("claude_resume_session", …) 가:
-
resume_command(msg["session_id"])로 주입할 명령 문자열을 만든다. 위생 실패면None을 받아 아무것도 하지 않는다(7장). -
server.new_window(sess, path=msg["cwd"])로 세션의 원래 cwd 에서 새 탭을 연다(설계 결정: cd 후 리줌). - 새 탭의 활성 패널 pty 에
cmd(=claude --resume <id>\r)를 UTF-8 로 write 한다. pty 가 없거나OSError면 조용히 무시. -
server._broadcast_session(sess)로 세션 전 클라에 전체 동기화를 방송해 새 탭이 모두에게 보이게 한다.
resume_command(session_id, enter="\r") 는 셸과 무관하게 claude --resume <id> + Enter 를
만든다. enter 를 분리해 둔 덕에 테스트에서 개행을 비워 순수 명령 문자열만 검증할 수 있다.
세션 id 는 새 탭 셸에 그대로 주입되므로 셸 인젝션을 막아야 한다. resume_command 는
정규식 게이트를 통과한 id 만 받는다.
_ID_OK = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")- 영숫자로 시작하고 영숫자·
.·_·-만 허용(uuid 형식). 공백·;·$·따옴표 등 셸 메타문자가 들어가면 매칭 실패. - 위생 실패 시
resume_command가None을 돌려주고, 서버는 새 탭조차 열지 않는다.
목록은 디스크의 파일명에서 id 를 얻지만, 리줌 시점에 서버가 다시 한 번 위생 검사하므로
클라가 임의 session_id 를 보내도 안전하다.
-
새 열·정렬 키 추가 —
parse_session이 돌려주는 dict 에 필드를 더하고(list_sessions는 그대로 통과시킴),screen._row에서 새 열을 그린다. 정렬을 바꾸려면list_sessions의sort(key=…)를 수정. -
다른 제목 소스 — JSONL 의 새 줄 타입을 라벨에 쓰려면
parse_session의 우선순위 사슬 (ai_title or last_prompt or first_user or session_id)에 끼워 넣는다. -
상한·페이지네이션 —
_LIST_LIMIT을 조정하거나,list_sessions(limit=…)호출부에서 넘긴다. 매우 큰 환경이면 mtime 필터를list_sessions에 추가하는 편이 파싱 비용을 줄인다. -
리줌 동작 변경 — 새 탭 대신 현재 패널에서 리줌하는 등 동작을 바꾸려면
handle_server_request의claude_resume_session분기를 고친다. 주입 문자열은resume_command한 곳에만 있으니 거기서 형태를 바꾼다. -
새 별칭 —
COMMANDS/NOARG/_ALIASES세 곳에 추가한다(handle_command는_ALIASES로 판정).
플러그인 계약·레지스트리 훅 전반은 Plugin-Authoring-Guide 를 보라.
-
순수 로직(
sessions.py) — textual 의존이 없어 가장 쉽게 테스트한다. 임시 디렉토리에 슬러그 폴더와*.jsonl픽스처를 만들고list_sessions(root=<tmp>)를 호출해 검증한다. 확인 포인트:- 제목 우선순위(
ai-title>last-prompt> 첫user> id), - 빈 세션(
user0개) 제외, -
mtime내림차순 정렬과limit절단, - 깨진 JSON 줄·접근 실패를 건너뛰고 부분 결과를 돌려주는지(
OSError내성), -
_project_label의 마지막 두 경로 요소 규칙.root인자 덕에 사용자 홈을 건드리지 않고 결정론적으로 돌릴 수 있다.
- 제목 우선순위(
-
위생 —
resume_command에 정상 uuid·셸 메타문자·빈 문자열을 넣어 정상은 명령 문자열, 불량은None임을 확인한다.enter=""로 개행을 빼면 비교가 깔끔하다. -
화면(
screen.py) — textual 모달이라 driver 로 직접 검증이 제한적이다. 단위 수준에서는_cellpad의 셀폭 패딩/말줄임(CJK 2칸 포함)과_when포맷·예외 처리를 검증한다. 실제 렌더는 스크린샷 하네스로 SVG 를 떠 시각 확인한다(플러그인 SVG 스크린샷 절차 참조). -
전체 스위트 —
python3 tests/run.py로 돌리고 요약줄(N passed, 0 failed) 을 확인한다. 서브셋 실행은 플러그인 믹스인 poison 으로 가짜 실패가 날 수 있어, 권위는 항상 전체 스위트다.
소개 · 사용
- 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
플랫폼 · 성능
품질 · 보안
리뷰·분석 보고서
연혁
기여