AI 활용 노트Last reviewed · 2026-10← 목록

터미널 밖으로 나온 Claude Code

터미널 안에만 살던 대화 기록을 웹으로 꺼냈다. 어느 기기에서든 지난 세션을 검색해 다시 잇고, 폰에서 새 세션을 원격으로 켠다. 집에 켜 둔 컴퓨터 한 대면 된다.

1전체 그림

데이터는 왼쪽에서 오른쪽으로만 흐른다.

원본

기록

Claude Code가 대화 한 턴 한 턴을 기록 파일(JSONL)로 자동 저장. 모든 것의 출발점.

매 1분

변환

순찰 스크립트가 새로 바뀐 기록만 골라 사람이 읽는 웹페이지로 변환.

상시

서빙

작은 파이썬 웹 서버가 변환된 페이지를 내보냄. 죽으면 OS가 자동으로 되살림.

어디서든

열람

사설망(Tailscale)으로 내 모든 기기에서 같은 주소로 접속. 주력은 폰.

↩ 역방향이 하나 있다. 열람 화면의 입력칸에서 보낸 메시지가 서버를 거슬러 올라가 데스크탑에서 새 Claude Code 세션을 켠다(5절). 그 세션이 다시 첫 칸의 기록을 남기고, 1분 뒤 목록에 나타난다. 고리가 닫히는 셈이다.

2원본은 기록 파일 하나, 나머지는 전부 사본

이 시스템의 데이터 규칙은 이 한 줄이 전부다.

원본 — JSONL 기록 파일

Claude Code가 스스로 남기는 표준 기록(~/.claude/projects/ 아래, 세션당 한 파일). 나는 이 파일을 직접 만들지도, 고치지도 않는다. 백업 대상도 이것뿐이다.

사본 — 대시보드의 모든 것

세션별 대화 페이지, 목록, 검색 화면, 분리 저장된 첨부 이미지. 전부 원본에서 기계적으로 다시 만들 수 있는 파생물이라, 통째로 지워도 1분 뒤 되살아난다.

이 규칙이 실무에서 뜻하는 것

· 목록 페이지가 마음에 안 들어도 그 파일을 손으로 고치지 않는다. 어차피 1분 뒤 재생성되며 수정이 사라진다. 바꾸려면 변환기의 템플릿(코드)을 고친다.

· 백업은 원본만 챙긴다. 사본까지 백업하면 용량만 몇 배가 되는데, 어차피 다시 만들 수 있는 것들이다.

· 사고가 나면 원본이 멀쩡한지부터 본다. 멀쩡하면 나머지는 전부 복구 가능한 문제다.

3변환기 — 매 분 도는 순찰 자동

cron(정해진 주기마다 명령을 자동 실행하는 OS 기본 기능)이 1분에 한 번 변환기를 깨운다. 깨어난 변환기가 하는 일은 세 가지다.

바뀐 것만 변환

매번 전체를 다시 만들지 않는다. 이미 변환된 세션은 건너뛰고 새로 바뀐 기록만 처리하기 때문에 1분 주기를 버틴다.

이미지는 밖으로

첨부 사진을 페이지 안에 박지 않고 별도 폴더로 빼서 링크한다. 사진 많은 세션 하나가 4.4MB → 0.4MB로 가벼워졌다.

사라질 뻔한 메시지 구조

답변 생성 중에 내가 보낸 입력(대기열 메시지)은 기록에서 누락되기 쉽다. 변환 단계에서 따로 찾아내 페이지에 살려 둔다.

왜 "대화가 끝나는 순간"이 아니라 "1분마다"인가

처음엔 턴이 끝나는 이벤트(hook)에 변환을 걸었다. 그런데 "끝났다"는 신호가 "기록이 디스크에 다 써졌다"는 보장은 아니었다. 변환기가 반쪽짜리 기록을 읽는 일이 실제로 났다. 이벤트에서 떼어내 1분 주기 순찰로 바꾸자 사라졌다. 즉시성을 1분 내주고 정합성을 산 거래였다.

목록이 열자마자 뜨게 하기

페이지 안에는 "이 목록의 지문"이 한 줄 박혀 있다. 목록을 열면 브라우저는 서버에 지문만 물어보고, 방금 받은 것과 같으면 표를 통째로 다시 내려받지 않는다. 바뀐 게 있을 때만 새로 받는다. 이 한 줄이 없을 때는 열 때마다 같은 표를 한 번 더 받아 처음부터 다시 그렸는데, 첫 화면에 오가는 양이 절반으로 줄었다.

화면도 서버가 완성된 상태로 그려서 보낸다. 버튼도, "몇 분 전"이라는 시각도 빈칸이 아니라 값이 채워진 채로 온다. 브라우저는 시간이 흐른 만큼만 시각을 살짝 고칠 뿐이다. 빈칸을 먼저 그리고 나중에 채우지 않으니, 목록이 뜨는 사이 깜빡이는 구간이 없다.

세션마다 얼마나 썼는지: 금액 대신 소모량

목록 맨 오른쪽 칸은 세션 하나가 쓴 양이다. 처음에는 API 단가로 환산한 달러를 보여 줬다. 그런데 나는 정액 요금제를 쓰고, 요금 규정도 자주 바뀌어서 그 달러가 무엇을 뜻하는지 애매했다. 내가 알고 싶은 것은 청구액이 아니라 어느 세션이 많이 쓰고 어느 세션이 적게 썼는지였다.

그래서 토큰을 한 단위로 맞춰 더한다. 새 입력, 캐시 쓰기, 캐시 읽기, 출력에 각각 가중치를 주고, 모델 계열마다 배수를 곱한다. 가장 많이 쓰는 모델이 1이다. 배수는 어느 하루의 공식 입력 단가 비율을 한 번 베껴 고정했고, 가격이 바뀌어도 고치지 않는다. 고치는 순간 지난 세션과 비교할 수 없게 되기 때문이다. 서브에이전트가 쓴 양은 부모 세션에 합친다. 숫자 옆 점 다섯 개는 전체 세션 가운데 어느 구간인지를 보여 준다. 필터를 바꿔도 점이 흔들리지 않도록, 구간은 늘 전체 세션을 기준으로 매긴다.

목록이 멈추면 멈췄다고 말한다

목록은 몇 초마다 서버에 바뀐 게 있는지 묻는다. 그런데 망이 끊기면 이 확인이 조용히 멈추고, 화면은 옛 목록을 최신인 것처럼 계속 보여 준다. 그래서 확인이 3분 넘게 실패하면 목록 위에 "연결이 끊긴 것 같다, 마지막 확인은 몇 시"라는 한 줄을 띄우고 마지막 대화 시각에 취소선을 긋는다. 15분이 넘으면 이 화면의 상태를 믿지 말라고 적는다. 다시 연결되면 저절로 사라진다.

폰에서는 오경보가 문제였다. 홈 화면 앱은 화면이 꺼져 있는 동안 타이머도 멈추기 때문에, 다시 켠 순간에는 마지막 성공이 한참 전으로 보인다. 그래서 화면이 다시 보이면 먼저 한 번 물어본 뒤에 판정한다. 요청마다 제한 시간도 거는데, 처음에는 응답 머리가 도착하면 시계를 멈춰서 본문이 오다 끊긴 경우를 놓쳤다. 지금은 본문을 다 받을 때까지 시간을 잰다.

한국어에서 굵은 글씨가 별표로 남던 문제

대화 본문은 마크다운으로 그린다. 그런데 **"확인 필요"**로처럼 닫는 별표 앞이 문장부호이고 바로 뒤에 조사가 붙으면, 마크다운 표준 규칙상 굵게 처리되지 않고 별표가 그대로 보인다. 영어는 문장부호 뒤에 빈칸이 오기 때문에 이 규칙에 잘 걸리지 않지만, 한국어는 조사를 붙여 쓰므로 자주 걸린다.

이 모양 하나만 굵게 바꾸는 작은 확장을 렌더러에 붙였다. 별표가 코드나 링크 안에 있는지는 렌더러의 분석에 맡겼다. 처음에는 그 판정을 확장 안에서 따로 흉내 냈는데, 검토에서 백틱 개수와 링크 경계를 잘못 읽는 경우가 연달아 나왔기 때문이다. 고친 뒤에는 실제 대화 메시지 11만여 개를 옛 렌더와 새 렌더로 그려 비교했고, 달라진 2천여 곳은 거의 모두 굵게 바뀐 자리였다.

4서버와 접속 — 작은 서버 하나, 사설망 하나

변환된 페이지를 내보내는 건 상시 켜 둔 데스크탑에서 24시간 떠 있는 한 파일짜리 파이썬 웹 서버다. 꺼지면 OS의 서비스 관리자가 알아서 되살린다(KeepAlive).

서버가 받는 요청들

페이지 서빙목록, 검색 화면, 세션별 대화 페이지, 분리된 이미지
검색검색어가 오면 원본 기록을 직접 훑어 관련도순으로 응답 (AND·OR·NOT·"구문" 지원)
활성 표시지금 켜져 있는 세션을 몇 초 간격 폴링으로 목록에 표시
새 세션 시작역방향 흐름의 입구. 5절에서 펼친다
제목 편집세션 제목을 화면에서 바로 바꿔 저장
상태 변경 보호새 세션, 닫기, 되살리기처럼 상태를 바꾸는 요청은 전용 헤더가 있어야 받는다. 헤더 없이 온 옛 탭에는 새로고침하라는 안내를 돌려준다
요청 로그모든 요청을 파일로 남긴다. 사고가 나면 가장 먼저 여는 곳

접속 — 같은 주소, 내 모든 기기

서버는 한 대뿐이고, 다른 기기는 Tailscale(내 기기들끼리만 직접 연결되는 개인 VPN)이 부여한 사설 IP로 직접 붙는다. 프록시도, 공개 인터넷 배포도 없다.

데스크탑
서버 본체 · 상시 켜 둠
노트북
외출용 브라우저
폰 (주력)
홈 화면에 PWA로 설치
태블릿
브라우저
왜 HTTPS가 아닌가 (그리고 왜 주소를 안 적나)

접속 주소가 사설망 안에만 존재해 공개 인터넷에서는 닿지 않고, 구간 암호화는 Tailscale(WireGuard)이 이미 해 준다. 그래서 인증서·도메인·로그인 관리를 통째로 생략하고 평문 HTTP로 두었다. 개인 도구에서 이만한 유지비 절감이 드물다. 대신 실제 IP나 포트 같은 배포 좌표는 이 문서에 적지 않는다.

5역방향 — 폰에서 새 세션을 켠다

보기만 하는 건 아니다. 화면 아래 입력칸에 글이나 이미지를 넣으면 집의 데스크탑에서 새 Claude Code 세션이 켜진다. 밖에서 제일 많이 쓰는 기능이다.

1
브라우저에서 메시지(+이미지) 입력 → 서버로 전송. 이미지도 요청 본문에 실어 보내므로(base64) 어느 기기에서든 동작한다.
2
서버가 새 세션 ID를 미리 만들고 요청을 접수. 이미지는 본인만 읽을 수 있는 권한의 임시 파일로 저장. 같은 요청이 짧은 간격에 두 번 오면 한 번만 처리한다. 모바일 네트워크의 재전송 때문에 세션이 두 개 생긴 적이 있어서다.
3
서버가 터미널을 열어 그 세션 ID로 Claude Code를 실행. 요청 자체엔 즉시 "접수됨"을 돌려준다. 처음엔 작업이 끝날 때까지 수십 초를 붙들고 있었는데, 폰에서는 멈춘 것처럼 보였다. 접수와 실행을 분리하니 해결됐다.
4
새 세션이 평소처럼 기록 파일을 남긴다 → 1분 뒤 변환 → 목록에 등장. 그 사이엔 "세션 시작 중…" 임시 행이 맨 위에서 자리를 지킨다.

새 세션을 켜는 길이 하나 더 있다. 대화가 길어지면 지금까지 한 일을 인계문 한 장으로 정리해 두는데, 그 인계문 위에 '세션 잇기' 버튼이 붙는다. 누르면 그 인계문을 첫 메시지로 새 대화가 열리고, 새 대화가 열린 것을 확인한 뒤 지금 대화는 멈춘다(ChatGPT 대화의 경우는 6절). 긴 작업을 세션 여러 개로 나눠 이어 갈 때 쓴다.

이어진 대화는 첫머리에 부모 세션을 가리키는 표식을 달고 시작한다. 그래서 나중에 이 대화가 어디서 갈라져 나왔는지 찾을 수 있다. 대시보드도 같은 표식을 보고 어느 인계문에 버튼을 붙일지 판단한다. 반대 방향도 있다. 부모 세션의 상세 화면 맨 위에는 "다음 세션으로 이어졌습니다"라는 한 줄이 붙어서, 옛 대화를 열었다가 바로 이어진 대화로 넘어갈 수 있다.

6두 AI를 한 목록에

지금은 ChatGPT 대화도 같은 목록에 있다. 어느 AI로 시작할지는 입력칸에서 고른다.

기록은 각자, 목록은 하나

ChatGPT(OpenAI Codex)도 대화마다 기록 파일을 남긴다. 변환기가 그 파일도 읽어 같은 목록에 섞고, 맨 왼쪽 칸의 회사 아이콘으로 어느 쪽 대화인지 구분한다. 2절의 규칙은 그대로다. 원본은 각 도구가 남긴 기록이고, 목록과 상세 화면은 다시 만들 수 있는 사본이다.

첫 답까지만 쥐고 놓는다

입력칸에서 ChatGPT를 고르면 데스크탑 터미널이 첫 답까지만 만들고 스스로 물러난다. 한 대화는 한 번에 한 실행기만 쓸 수 있어서, 터미널이 계속 쥐고 있으면 폰의 ChatGPT 앱이 그 대화를 이어 쓰지 못한다. 첫 답 뒤로는 폰 앱에서 그대로 이어 간다. 데스크탑의 ChatGPT 앱이 꺼져 있으면 목록 위에 켜기 버튼이 뜬다.

세션 잇기는 도구를 가리지 않는다

Claude에서 ChatGPT로, ChatGPT에서 Claude로, 같은 쪽끼리도 인계문 한 장으로 넘어간다. 두 AI가 같은 형식으로 인계문을 쓰도록 ChatGPT 쪽 규칙 파일(AGENTS.md)을 Claude 쪽 규칙 원문에서 만들어 낸다. 같은 인계문으로 두 번 눌러도 새 대화는 하나만 생긴다. 이전 대화는 새 대화가 뜬 것을 확인한 뒤에 닫힌다. 어느 AI 쪽 대화든 같다.

닫기와 되살리기

조작 칸에는 두 AI 모두 [닫기]와 [되살리기]가 있다. Claude 대화를 닫으면 실행 중인 세션이 끝나고, ChatGPT 대화를 닫으면 ChatGPT 쪽에서도 보관 처리되어 폰 앱의 대화 목록에서 빠진다. 되살리면 다시 돌아온다. 다만 보관은 데스크탑 앱이 그 대화를 놓고 있을 때만 성공해서, 앱이 쥐고 있으면 서버가 기다렸다가 앱이 놓으면 처리한다. 데스크탑 앱의 사이드바는 이 보관을 따라오지 않는다.

끝난 창은 서버가 치운다

터미널에서 연 ChatGPT 대화가 끝나도 터미널 창은 "프로세스 종료" 상태로 남아 있었다. 창을 연 쪽이 "이 대화는 이 창"이라는 기록을 도중에 지워 버려서, 대화가 끝난 뒤 어느 창을 닫을지 몰랐던 것이다. 지금은 그 기록을 끝까지 들고 있고, 10초마다 도는 청소가 대화 종료를 보면 창을 닫는다. 사람이 탭이나 화면 분할을 더한 창은 건드리지 않는다.

기대는 지점은 매일 확인한다

남의 도구에 기대는 기능이다. 그래서 기대는 지점(기록 형식, 대화 목록의 칸, 잠금 방식)을 목록으로 적어 두고 매일 아침 대조한다. 공식 릴리스 노트도 매일 훑어서, 이 지점과 관련된 낱말이 들어간 새 정식판이 나오면 알림이 온다.

7스스로 돌아가게 하는 장치들

매일 들여다보지 않아도 되도록 감시, 백업, 배포에 안전장치를 하나씩 달았다.

죽음 감지는 바깥에서

변환 순찰이 돌 때마다 외부 하트비트 서비스(healthchecks.io)에 신호를 보낸다. 신호가 끊기면 바깥에서 알림이 온다. 죽은 시스템은 자기 부고를 못 보내니까. 저장 폴더가 지나치게 커져도 경고가 온다.

백업은 원본만, 매주

원본 기록 전체를 압축해 클라우드 드라이브에 저장하고, 최근 몇 주 치만 남기고 자동 회전한다. 사본(페이지·이미지)은 뺐다. 2절의 규칙대로 언제든 다시 만들 수 있어서다.

배포의 함정 하나

변환기 코드를 고치면 전체 재생성을 한 번 돌리고, 서버 프로세스를 확실히 종료한 뒤 재시작한다. 재시작했다고 믿었는데 옛 프로세스가 계속 살아 있던 함정을 한 번 밟았다.

8실제 화면

브라우저로 보는 대시보드를 그대로 본뜬 정지 화면이다. 세션 제목과 시간, 숫자는 전부 가상이고 버튼은 눌리지 않는다.

🔒 http://100.x.x.x:····/ — Tailscale 사설망 (실제 주소·포트는 비공개)
Claude Code 대시보드
총 325개 세션
AI대화ID조작마지막 대화 ▼시작지속소모량
세션 시작 중… ······· 방금———
Claude 신규 가입 폼 유효성 검사 버그 수정 a3f1c0d9 방금06-09 14:248분412K●●●●●
ChatGPT 주간 매출 리포트 자동 집계 스크립트 작성 7b29e8a4 12분 전06-09 14:0516분1.3M●●●●●
Claude 고객 문의 자동 분류 프롬프트 튜닝 c5d70f12 1시간 전06-09 13:0238분3.8M●●●●●
Claude 이미지 업로드 용량 제한 버그 추적 e1846b3a 06-08 22:1406-08 21:101시간 4분2.1M●●●●●
ChatGPT 위키 전체 검색 기능 추가 90ab2fd7 06-07 12:0306-07 10:481시간 15분9.6M●●●●●
Claude 배포 파이프라인 캐시 정리 + 회의록 요약 템플릿 2f6c9e05 06-06 09:4606-06 09:1531분186K●●●●●
① 픽셀 마스코트 + 총 세션 수 — 걸어다니는 로봇. 서버가 살아 있다는 표시이자, 솔직히는 그냥 애착.
② 검색창 — 모든 세션의 대화 내용을 관련도순으로 검색한다(AND·OR·NOT·"구문").
③ 활성 세션 — 지금 켜진 대화만 진한 색으로 뜨고 닫기 버튼이 보인다. 나머지는 회색.
④ 되살리기 버튼 — 흐려진 옛 세션을 그 자리에서 다시 이어 간다(resume).
⑤ "세션 시작 중…" — 원격으로 켠 직후, 변환되기 전까지 맨 위를 지키는 임시 행.
⑥ 하단 입력칸 — 글이나 이미지를 보내면 데스크탑에서 새 대화가 시작된다(5절의 역방향).
⑦ AI 칸 — 어느 쪽 대화인지 표시한다(6절). 실제 화면에서는 회사 아이콘이다.
⑧ 소모량 — 세션이 쓴 양과 전체 중 위치(점 5단계). 이 정지 화면은 PC 폭이고, 폰에서는 목록이 두 줄로 접히며 숫자만 남는다.

9이 구조에서 가져갈 만한 것

다른 시스템에 옮겨 심는다면 스크립트가 아니라 이쪽이다.

원본은 하나, 나머지는 전부 파생물로.

손으로 고치는 건 원본과 그걸 만드는 코드뿐. 파생물을 지워도 되는 것으로 유지하면 백업과 복구, 실험이 전부 싸진다. 백업 대상도 원본 하나로 줄어든다.

"끝났다"는 이벤트는 "저장됐다"는 보장이 아니다.

쓰기 완료를 보장하지 않는 이벤트에 후속 처리를 걸면 경합이 난다. 짧은 주기 순찰(폴링)이 더 단순하고 안전했다. 즉시성을 조금 내주는 대신.

감시는 시스템 바깥에 둔다.

자기 상태를 자기에게 보고시키면 죽는 순간 보고도 같이 죽는다. 신호가 멈추면 알림이 오도록 방향을 뒤집어 외부에서 지켜보게 했다.

개인 도구의 멀티 디바이스는 사설망이 가장 싸다.

공개 배포 없이 내 모든 기기에서 접속이 된다. 인증·도메인·인증서 관리가 통째로 사라지고, 외부에 노출되는 면적은 0이다.

원격 트리거는 접수와 실행을 분리한다.

모바일은 긴 대기를 못 견딘다. 요청에는 즉시 "접수됨"을 돌려주고 실행은 뒤에서 한다. 재전송으로 생기는 중복 요청도 서버 쪽에서 걸러 줘야 한다.

긴 작업은 한 세션에 다 담지 않는다.

맥락이 무거워지면 지금까지를 인계문으로 접고 새 대화로 넘긴다. 그 절차가 버튼 하나로 줄면 세션을 나누는 부담이 준다.

한 대화에는 한 실행기만.

원격으로 켠 ChatGPT 대화는 터미널이 첫 답까지만 쥐고 놓는다. 쥐고 있는 동안에는 다른 기기가 그 대화를 이어 쓰지 못한다.

비교하려는 숫자는 가격과 떼어 둔다.

가격표는 바뀐다. 세션끼리 많고 적음을 보려는 숫자라면 배수를 한 번 정해 고정하고, 바뀐 가격을 따라가지 않는다. 따라가는 순간 지난달 숫자와 이번 달 숫자가 다른 자로 잰 값이 된다.

갱신이 멈춘 화면은 스스로 말해야 한다.

자동으로 새로 고치는 화면은 멈춰도 멀쩡해 보인다. 마지막으로 성공한 시각을 재고, 오래되면 화면이 먼저 "이 상태는 오래됐다"고 알려야 한다.

남의 도구에 기대는 지점은 목록으로 적고 매일 확인한다.

기록 형식이 조용히 바뀌면 화면도 조용히 틀린다. 기대는 지점을 바깥에서 매일 대조하고, 릴리스 노트도 그 지점을 기준으로 읽는다.

⚠️ 이 문서는 구조 설명용이다. 실제 접속 주소·포트·기기 구성·대화 내용은 포함하지 않으며, 화면 미리보기 속 데이터는 전부 가상이다. 구현 코드는 공개 repo github.com/sidoyu/claude-session-dashboard에 있다.