Skip to content

Dev Guide claude prompt history

Woojin Kim edited this page Jun 20, 2026 · 1 revision

개발자 가이드 — claude-prompt-history 플러그인

Claude 패널에 입력한 프롬프트를 시간순으로 기록하고, transient 미리보기 + 팝업 + 스크롤백 텍스트 검색 점프를 제공하는 플러그인. claude-code 에서 제거된 단일행 스티키 헤더를 대체한다. 이 문서는 플러그인 내부 구조와 확장/테스트 방법을 다루는 개발자용 가이드다.

프롬프트 히스토리 미리보기

목차


1. 목적과 세 가지 표면

Claude CLI 를 띄운 패널에 사용자가 입력하는 프롬프트들을 패널마다 누적·기록하고, 세 가지 방법으로 다시 꺼내볼 수 있게 한다.

  • 추적 — Claude 패널 입력을 누적해 Enter 제출 시 한 항목으로 확정·영속. 멀티라인(⇧Enter)도 한 항목으로 보존, 제어 시퀀스·연속 중복 제외. 패널당 상한 200개.
  • transient 미리보기: 명령 프롬프트에서 prompt-history작성하는 동안에만 대상 패널 외곽선 안쪽 위에서부터 직전 프롬프트를 1~3행 오버레이로 표시. 작성이 끝나면 소멸한다.
  • 팝업·점프 — 팝업에서 ↑↓ 로 이전 프롬프트를 고르고 Enter 로 그 프롬프트가 입력됐던 스크롤백 위치로 점프한다(텍스트 검색 기반, 코어 무수정).

사용자 노출 명령:

명령 별칭 동작
prompt-history prompts, ph 히스토리 팝업 열기
prompt-history-lines <1-3> ph-lines 미리보기 행수 설정(기본 3, 영속)

2. 등록과 훅 계약

플러그인은 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._claudegetattr(pane, "_claude", None)약하게 읽는다. claude-code 플러그인이 없으면 아무 프롬프트도 기록하지 않는다(하드 참조 금지 — 두 플러그인은 독립적으로 켜고 끌 수 있다).


3. 서버측 프롬프트 추적 (server_input)

server.pytrack_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 의 공유 디바운스를 오염시키지 않는다.


4. 히스토리 저장과 영속 (plugin_opts · pane_serialize)

상태 소유는 패널 단위다. 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_serializeopts.jsonplugin_opts 네임스페이스에 영속한다.

_clamp_lines 헬퍼가 1~3 범위로 강제하고 잘못된 값은 기본 3으로 폴백한다.


5. transient 미리보기 (client_render)

render.pydraw_preview(app, cells, W, H) 가 코어가 세우는 app._cmd_target_pane(=: 작성 중 대상 활성 패널)의 외곽선 안쪽 위에서부터 직전 프롬프트를 오버레이로 그린다. textual 비의존(rich.Style + clientutil 헬퍼).

핵심 특성:

  • transient_cmd_target_paneNone 이면(명령 작성 종료) 즉시 no-op. 서버가 행을 예약하지 않고 매 프레임 fresh cells 위에 덮어 그리므로 다음 합성에서 자연히 사라진다.
  • 멀티라인 — 프롬프트를 \n 으로 쪼개 1행부터 최대 app.ph_max_lines 행까지 표시. 더 있으면 마지막 표시줄 우측 끝에 잘림 마커 , 첫 줄엔 시작 마커 .
  • 와이드 문자_char_cells 로 셀폭을 재 CJK/이모지를 2셀로 정확히 배치, 폭 예산(pw - 2, 좌우 1칸 여백)을 넘으면 자른다.
  • 바 배경색은 theme_color(app, "primary-darken-2").

이 함수는 client_render 훅에서 지연 import 된다(렌더 핫패스에 textual/rich 를 끌어오지 않도록).


6. 스크롤백 텍스트 검색 점프 (코어 무수정)

점프의 설계 원칙은 코어 model.py 의 앵커/hist_total 에 의존하지 않는다는 것. 대신 제출된 프롬프트 텍스트를 스크롤백에서 검색해 그 줄을 뷰포트 맨 위로 올린다. 코어를 한 줄도 고치지 않고 점프를 구현한다.

server.pyscroll_to_prompt(server, sess, index):

  1. 활성 패널의 tail(_TAIL) 슬라이스에서 index 프롬프트를 꺼내 첫 비어있지 않은 줄(first)을 얻는다.
  2. 스크롤백 + 현재 버퍼를 합쳐 full 줄 목록을 만들고, _plain_line 으로 각 줄을 평문화한다(와이드 문자 연속 빈 셀 건너뜀, 뒤 공백 제거).
  3. 아래(최근)에서 위로 스캔해 first 를 포함하는 가장 최근 줄(target)을 찾는다.
  4. 뷰포트 수학으로 스크롤 오프셋을 계산: 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 True

deque 회전이나 재시작으로 그 줄이 스크롤백에서 사라졌으면 False 를 반환(graceful 실패). server_command 는 성공 시 "broadcast"(스크롤은 공유 서버 상태라 전 클라 반영), 실패 시 "handled"(무동작)를 돌려준다.


7. 팝업과 행수 설정

프롬프트 히스토리 팝업

screen.pyPromptHistoryScreen(ModalScreen) + _HistView(render_line 기반 단일 위젯) 패턴. ↑↓ 한 칸 이동은 바뀐 두 줄만 refresh 해 ssh 에서도 빠르다.

  • 시간순(오래된→최근)으로 싣고 커서는 최신에 둔다(↑=이전).
  • 멀티라인 항목은 끝에 마커(ph.multiline_mark).
  • Enterjump_to(index)ph_scroll_to 명령을 보내고 팝업을 닫는다.
  • +/-(또는 =) → bump_lines(±1) 가 미리보기 행수를 1~3 으로 조정. 낙관적 즉시 반영(app.ph_max_lines 갱신) 후 set_ph_max_lines 로 서버 영속, 다음 status 가 권위를 확정한다.
  • Esc → 닫기.

클라 측 handle_commandprompt-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 로 번역한다.


8. 설계 교훈 — 원격 탭 볼 때 미리보기 가드

transient 미리보기는 app._cmd_target_pane(로컬 패널 id)을 기준으로 그린다. 페더레이션으로 원격 탭을 보는 중에는 로컬의 _cmd_target_pane 미리보기가 원격 패널과 id 충돌을 일으켜 원격 패널 위에 잘못 덮일 수 있다.

따라서 client_render 오버레이는 원격 탭을 보는 동안 가드되어야 한다. 일반 규칙으로:

  • 미리보기는 로컬 패널을 볼 때만 그린다. 원격 뷰 상태면(_viewing_remote 같은 가드) 합성을 건너뛴다.
  • 진단 단서: 합성(_composite)은 매 프레임 fresh cells 라 stale 잔상이 불가능하다. 따라서 원격 패널 위에 뜬 비정상 오버레이는 플러그인 client_render 가 그린 것이지 코어 select 경로의 누출이 아니다 — 코어 표준 경로는 원격 layout 을 정확히 전달한다.

교훈: id 로 대상 패널을 찾는 클라측 오버레이는, 그 id 공간을 공유하는 다른 뷰(원격/병합 탭) 컨텍스트에서 반드시 명시적으로 무력화해야 한다.


9. 확장하기

  • 새 키/동작 추가_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 는 첫 줄 부분일치만 쓴다. 더 정밀한 매칭을 넣어도 코어를 건드리지 않는 한 안전하다. 실패는 항상 graceful False 를 유지할 것.
  • 사용자 문자열 — 반드시 i18n.register 의 ko/en 양쪽에 키를 추가하고 클라에서 i18n.t 로 렌더한다. 서버에서 t() 를 직접 부르지 않는다.

10. 테스트

  • 커밋 전 필수: 프로젝트 루트에서 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 참조) 확인한다.

관련 문서

Clone this wiki locally