-
Notifications
You must be signed in to change notification settings - Fork 0
Dev Guide claude prompt history
Claude 패널에 입력한 프롬프트를 시간순으로 기록하고, transient 미리보기 + 팝업 + 스크롤백 텍스트 검색 점프를 제공하는 플러그인. claude-code 에서 제거된 단일행 스티키 헤더를 대체한다. 이 문서는 플러그인 내부 구조와 확장/테스트 방법을 다루는 개발자용 가이드다.
- 1. 목적과 세 가지 표면
- 2. 등록과 훅 계약
- 3. 서버측 프롬프트 추적 (server_input)
- 4. 히스토리 저장과 영속 (plugin_opts · pane_serialize)
- 5. transient 미리보기 (client_render)
- 6. 스크롤백 텍스트 검색 점프 (코어 무수정)
- 7. 팝업과 행수 설정
- 8. 설계 교훈 — 원격 탭 볼 때 미리보기 가드
- 9. 확장하기
- 10. 테스트
- 관련 문서
Claude CLI 를 띄운 패널에 사용자가 입력하는 프롬프트들을 패널마다 누적·기록하고, 세 가지 방법으로 다시 꺼내볼 수 있게 한다.
- 추적 — Claude 패널 입력을 누적해 Enter 제출 시 한 항목으로 확정·영속. 멀티라인(⇧Enter)도 한 항목으로 보존, 제어 시퀀스·연속 중복 제외. 패널당 상한 200개.
-
transient 미리보기 —
:명령 프롬프트에서prompt-history를 작성하는 동안에만 대상 패널 외곽선 안쪽 위에서부터 직전 프롬프트를 1~3행 오버레이로 표시. 작성이 끝나면 소멸한다. - 팝업·점프 — 팝업에서 ↑↓ 로 이전 프롬프트를 고르고 Enter 로 그 프롬프트가 입력됐던 스크롤백 위치로 점프한다(텍스트 검색 기반, 코어 무수정).
사용자 노출 명령:
| 명령 | 별칭 | 동작 |
|---|---|---|
prompt-history |
prompts, ph
|
히스토리 팝업 열기 |
prompt-history-lines <1-3> |
ph-lines |
미리보기 행수 설정(기본 3, 영속) |
플러그인은 pytmuxlib/plugins/claude-prompt-history/__init__.py 의 _PromptHistoryPlugin 인스턴스(PLUGIN)로 코어 레지스트리에 닿는다. 코어는 이 플러그인을 직접 import 하지 않고, 레지스트리가 선언된 훅만 호출한다 — 디렉토리를 지우면 기능이 조용히 사라지는 delete-to-disable 모델이다.
__init__.py 자체는 가벼움을 위해 textual 을 최상단에서 import 하지 않는다. 화면(screen.py)·렌더(render.py)·서버 로직(server.py)은 실제 호출 시점에 지연 import 한다.
선언 메타데이터:
commands = COMMANDS # (이름, 설명, 카테고리)
noarg = NOARG # 인자 없이 즉시 실행
pane_scoped = PANE_SCOPED # 작성 중 대상 패널에 미리보기
command_options = COMMAND_OPTIONS # ph-lines 행수 picker(←→ 1~3)훅 한눈에:
| 훅 | 측 | 역할 |
|---|---|---|
pane_init / pane_reset
|
서버 | 패널 상태 필드 생성·리셋(_ph_history·_ph_inbuf·_ph_sent) |
pane_serialize / pane_restore
|
서버 | 세션유지 재시작 시 히스토리 직렬화/복원 |
server_opts_init / server_opts_serialize
|
서버 | 미리보기 행수(_ph_max_lines) 옵션 로드/저장 |
server_input / server_paste
|
서버 | 입력·붙여넣기 바이트를 track_input 으로 추적 |
server_status |
서버 | status 메시지에 행수·패널별 히스토리 tail 첨부 |
server_command |
서버 |
set_ph_max_lines·ph_scroll_to 명령 처리 |
attach_client |
클라 |
app.ph_panes·app.ph_max_lines·open_prompt_history 설치 |
handle_command |
클라 |
prompt-history* 명령 디스패치 |
client_status |
클라 | status 의 히스토리·행수 수신·디바운스 병합 |
client_render |
클라 | transient 미리보기 오버레이 합성 |
Claude 패널 판정은 claude-code 가 세우는 pane._claude 를 getattr(pane, "_claude", None) 로 약하게 읽는다. claude-code 플러그인이 없으면 아무 프롬프트도 기록하지 않는다(하드 참조 금지 — 두 플러그인은 독립적으로 켜고 끌 수 있다).
server.py 의 track_input(pane, data: bytes) 가 입력 바이트 스트림에서 (멀티라인) 프롬프트를 누적하고 제출 경계에서 확정한다. textual 비의존.
스캐닝 규칙:
-
\x1b(ESC) — CSI/ESC 제어 시퀀스(화살표 등)를 통째로 건너뜀. 시퀀스가 히스토리를 오염시키지 않는다. -
\n(⇧Enter/Ctrl+J) — 프롬프트 안 줄바꿈으로 버퍼에 누적(멀티라인 = 한 항목). -
\r(Enter) — 제출 경계. 누적분(_ph_inbuf)을strip()후 한 항목으로 확정. - backspace(8·127) — 버퍼 마지막 글자 제거.
- 그 외
ord(ch) >= 32— 일반 문자 누적.
확정 시 연속 중복 제외(직전 항목과 같으면 추가하지 않음)하고, 패널당 상한(_HIST_CAP = 200)을 넘으면 앞에서 잘라낸다. 누적 버퍼는 _INBUF_CAP = 4000 으로 캡한다.
elif ch == "\r": # Enter: 제출 경계 → 한 항목으로 확정
line = buf.strip()
if line and (not hist or hist[-1] != line): # 연속 중복 제외
hist.append(line)
if len(hist) > _HIST_CAP:
del hist[:-_HIST_CAP]
buf = ""서버는 status 브로드캐스트로 클라에 최근 슬라이스만 싣는다(status_fields). 전 200개가 아니라 tail(_TAIL = 30)만 보내고, 변할 때만 또는 full resync 시에만 실어 ssh 트래픽을 아낀다(_ph_sent 디바운스). full 동기화는 _ph_sent 를 건드리지 않아 주기 flush 의 공유 디바운스를 오염시키지 않는다.
상태 소유는 패널 단위다. pane_init 이 세 필드를 만든다.
pane._ph_history = [] # 시간순 제출 프롬프트(멀티라인 보존, 한 항목=한 제출)
pane._ph_inbuf = "" # 입력 누적(claude-code _inbuf 와 별개 — 충돌 방지)
pane._ph_sent = None # status 디바운스(직전 전송 tail 슬라이스)-
respawn(
pane_reset) — 입력 버퍼만 비우고 히스토리는 보존한다(재실행이 과거 프롬프트를 지우지 않음). -
세션유지 재시작(
pane_serialize/pane_restore) — 최근 100개를 직렬화해 재시작 너머로 복원한다. -
미리보기 행수(서버 옵션) —
ph_max_lines(기본 3, 1~3 클램프)를server_opts_init/server_opts_serialize로opts.json의plugin_opts네임스페이스에 영속한다.
_clamp_lines 헬퍼가 1~3 범위로 강제하고 잘못된 값은 기본 3으로 폴백한다.
render.py 의 draw_preview(app, cells, W, H) 가 코어가 세우는 app._cmd_target_pane(=: 작성 중 대상 활성 패널)의 외곽선 안쪽 위에서부터 직전 프롬프트를 오버레이로 그린다. textual 비의존(rich.Style + clientutil 헬퍼).
핵심 특성:
-
transient —
_cmd_target_pane이None이면(명령 작성 종료) 즉시 no-op. 서버가 행을 예약하지 않고 매 프레임 freshcells위에 덮어 그리므로 다음 합성에서 자연히 사라진다. -
멀티라인 — 프롬프트를
\n으로 쪼개 1행부터 최대app.ph_max_lines행까지 표시. 더 있으면 마지막 표시줄 우측 끝에 잘림 마커▾, 첫 줄엔 시작 마커▷. -
와이드 문자 —
_char_cells로 셀폭을 재 CJK/이모지를 2셀로 정확히 배치, 폭 예산(pw - 2, 좌우 1칸 여백)을 넘으면 자른다. - 바 배경색은
theme_color(app, "primary-darken-2").
이 함수는 client_render 훅에서 지연 import 된다(렌더 핫패스에 textual/rich 를 끌어오지 않도록).
점프의 설계 원칙은 코어 model.py 의 앵커/hist_total 에 의존하지 않는다는 것. 대신 제출된 프롬프트 텍스트를 스크롤백에서 검색해 그 줄을 뷰포트 맨 위로 올린다. 코어를 한 줄도 고치지 않고 점프를 구현한다.
server.py 의 scroll_to_prompt(server, sess, index):
- 활성 패널의 tail(
_TAIL) 슬라이스에서index프롬프트를 꺼내 첫 비어있지 않은 줄(first)을 얻는다. - 스크롤백 + 현재 버퍼를 합쳐
full줄 목록을 만들고,_plain_line으로 각 줄을 평문화한다(와이드 문자 연속 빈 셀 건너뜀, 뒤 공백 제거). -
아래(최근)에서 위로 스캔해
first를 포함하는 가장 최근 줄(target)을 찾는다. - 뷰포트 수학으로 스크롤 오프셋을 계산:
scroll = total - lines_n - target,[0, len(hist)]로 클램프.
# 뷰포트 수학(model.render): full = hist+buffer, top 줄 = full[total - scroll - lines].
# full[idx] 를 맨 위로 → scroll = total - lines - idx.
scroll = total - lines_n - target
p.scroll = max(0, min(scroll, len(hist)))
p.dirty = True
return Truedeque 회전이나 재시작으로 그 줄이 스크롤백에서 사라졌으면 False 를 반환(graceful 실패). server_command 는 성공 시 "broadcast"(스크롤은 공유 서버 상태라 전 클라 반영), 실패 시 "handled"(무동작)를 돌려준다.
screen.py 의 PromptHistoryScreen(ModalScreen) + _HistView(render_line 기반 단일 위젯) 패턴. ↑↓ 한 칸 이동은 바뀐 두 줄만 refresh 해 ssh 에서도 빠르다.
- 시간순(오래된→최근)으로 싣고 커서는 최신에 둔다(↑=이전).
- 멀티라인 항목은 끝에
⏎마커(ph.multiline_mark). -
Enter→jump_to(index)가ph_scroll_to명령을 보내고 팝업을 닫는다. -
+/-(또는=) →bump_lines(±1)가 미리보기 행수를 1~3 으로 조정. 낙관적 즉시 반영(app.ph_max_lines갱신) 후set_ph_max_lines로 서버 영속, 다음 status 가 권위를 확정한다. -
Esc→ 닫기.
클라 측 handle_command 가 prompt-history* 별칭을 받아 open_prompt_history(active_pane) 를 호출하거나(팝업), ph-lines N 인자에서 숫자를 뽑아 set_ph_max_lines 를 보낸다.
모든 사용자 문자열은 i18n.register 로 ko/en 등록(ph.popup_title·ph.popup_sub·ph.empty·ph.jump_fail·ph.lines_set …). 서버 코드는 로케일을 모르므로 사용자 표면은 클라에서 i18n.t 로 번역한다.
transient 미리보기는 app._cmd_target_pane(로컬 패널 id)을 기준으로 그린다. 페더레이션으로 원격 탭을 보는 중에는 로컬의 _cmd_target_pane 미리보기가 원격 패널과 id 충돌을 일으켜 원격 패널 위에 잘못 덮일 수 있다.
따라서 client_render 오버레이는 원격 탭을 보는 동안 가드되어야 한다. 일반 규칙으로:
- 미리보기는 로컬 패널을 볼 때만 그린다. 원격 뷰 상태면(
_viewing_remote같은 가드) 합성을 건너뛴다. - 진단 단서: 합성(
_composite)은 매 프레임 fresh cells 라 stale 잔상이 불가능하다. 따라서 원격 패널 위에 뜬 비정상 오버레이는 플러그인client_render가 그린 것이지 코어 select 경로의 누출이 아니다 — 코어 표준 경로는 원격 layout 을 정확히 전달한다.
교훈: id 로 대상 패널을 찾는 클라측 오버레이는, 그 id 공간을 공유하는 다른 뷰(원격/병합 탭) 컨텍스트에서 반드시 명시적으로 무력화해야 한다.
-
새 키/동작 추가 —
_HistView.on_key에 분기 추가,event.stop()으로 App 바인딩 전파를 막는다(화면on_key가 App 바인딩보다 먼저 도달). -
상한·tail 크기 조정 —
server.py의_HIST_CAP(보관)·_TAIL(전송/팝업 슬라이스)·_INBUF_CAP(누적 버퍼). 팝업 인덱스와 status tail 은 같은_TAIL로 정렬되어야 점프 인덱스가 어긋나지 않는다. -
새 status 필드 —
status_fields에 추가하고client_status에서 수신·병합. 트래픽 절감을 위해 변할 때만 싣는_ph_sent패턴을 따른다. -
점프 검색 개선 —
scroll_to_prompt는 첫 줄 부분일치만 쓴다. 더 정밀한 매칭을 넣어도 코어를 건드리지 않는 한 안전하다. 실패는 항상 gracefulFalse를 유지할 것. -
사용자 문자열 — 반드시
i18n.register의 ko/en 양쪽에 키를 추가하고 클라에서i18n.t로 렌더한다. 서버에서t()를 직접 부르지 않는다.
-
커밋 전 필수: 프로젝트 루트에서
python3 tests/run.py를 돌려 요약줄N passed, 0 failed를 확인한다.- 주의:
run.py는 실패해도 종료코드가 0 일 수 있으니 반드시 요약줄을 본다. - 서브셋 실행은 플러그인 믹스인 poison 으로 가짜 실패가 날 수 있으니 권위는 항상 전체 스위트다.
- 주의:
-
추적 단위 테스트 —
track_input에 멀티라인(⇧Enter), 제어 시퀀스(화살표) 무시, 연속 중복 제외, backspace, 상한 회전을 바이트열로 검증한다. textual 비의존이라 헤드리스로 직접 호출 가능. -
점프 테스트 — 가짜 스크롤백을 채운 뒤
scroll_to_prompt가 올바른pane.scroll을 계산하는지, 회전으로 사라진 프롬프트에False를 돌려주는지 확인한다. -
렌더/팝업 — 오버레이·모달의 실 렌더 검증은 Textual
run_test+save_screenshot으로 SVG 를 떠 확인한다(서버 패널 텍스트샷으로는 오버레이/팝업이 안 잡힌다). -
delete-to-disable — 디렉토리를 지운 상태에서 코어/claude-code 가 에러 없이 도는지(약한
getattr참조) 확인한다.
소개 · 사용
- 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
플랫폼 · 성능
품질 · 보안
리뷰·분석 보고서
연혁
기여