pytmux

클로드코드를 여러 세션 돌리려다 윈도우에서 tmux를 띄우는 일이 너무 귀찮아 결국 멀티플렉서를 직접 만들었습니다. 5일 동안 클로드코드의 손을 빌려 만든 pytmux의 개발 과정과 시행착오를 정리해봤습니다.

pytmux

이야기는 클로드 데스크탑 앱에서 다음 탭으로 전환하기를 몇 초 동안 기다리던 어느 저녁으로부터 시작됩니다. 처음 한두 번은 그러려니 했는데 하루에도 수십 번 같은 대기를 반복하다 보니 점점 견디기 어려워졌습니다. 클로드 데스크탑은 일렉트론 기반 앱으로 여느 일렉트론 기반 앱 가령 슬랙처럼 기능에 비해 엄청나게 무거웠습니다. 탭을 여러 개 열고 작업하면 탭 전환 뿐 아니라 기본적인 타이핑조차도 느려지곤 했습니다. 그래서 클로드코드 CLI로 옮겨 왔고 처음에는 한결 가벼워서 만족했습니다. 문제는 작업이 늘면서 한 세션으로는 부족해졌다는 점입니다. 한쪽에서 긴 리팩토링을 돌려 두고 다른 쪽에서 문서를 정리하고 또 다른 쪽에서 테스트를 돌리려면 세션을 여러 개 띄워야 했는데 Warp 터미널의 탭을 좌우로 오가며 그걸 관리하는 일이 썩 편하지 않았습니다. 한 탭은 권한을 물어보며 멈춰 서 있고 다른 탭은 진즉 끝나 idle 상태인데 어느 탭이 어느 작업이었는지 헛갈려 한참을 더듬는 일이 잦았습니다. 분명 같은 화면 안에 다 있는데도 한 번에 하나밖에 못 보니 옆 세션이 무엇을 하고 있는지는 그 탭을 눌러 보기 전에는 알 수 없었습니다.

요구사항은 분명했습니다. tmux처럼 세션을 계속 살려 두고 필요할 때 여러 패널을 한 화면에 나란히 띄워 동시에 모니터링 하는 것이었습니다. 한쪽에서 클로드가 돌아가는 동안 다른 패널에서 로그를 보고 또 다른 패널에서 결과를 확인하는 식입니다. Warp로도 화면 분할이 안 되는 것은 아니지만 여러 세션을 오래 띄워 두기에는 너무 무거웠습니다. 탭을 대여섯 개 열어 두고 반나절을 보내면 어느새 기계가 더워지고 입력이 한 박자씩 밀리기 시작했습니다. 가볍게 셸을 여럿 돌리며 며칠씩 이어 가는 작업에는 맞지 않는 도구였습니다.

또 하나 부담이 있었습니다. 클로드코드를 오토 모드에 놓고 여러 세션을 오가며 일하다 보니 토큰 사용량이 빠르게 늘었고 과사용이 걱정되기 시작했습니다. 이렇게 기계에 일을 맡겨 두고 여러 세션을 동시에 돌리는 작업 방식 자체는 지난 '자동완성'에서 적은 그대로입니다. 과사용을 줄이는 방법이 없는 것은 아니었습니다. 컨텍스트가 차기 전에 미리 정리하고 모델을 가려 쓰고 한 번에 너무 많이 시키지 않는 식의 요령이 여럿 알려져 있습니다. 그런데 이들 대부분은 제 작업 습관을 바꾸라고 요구하는 것들이었습니다. 일하는 흐름을 끊고 사용량을 계속 신경 쓰는 일은 제 관심사가 아니었습니다. 그래서 습관은 가능한 한 그대로 두되 토큰을 과하게 쓰는 상황이 오면 옆에서 조용히 개입해 알아서 처리해주는 장치가 있으면 좋겠다고 생각했습니다. 기계에 어디까지 일을 맡길지를 두고 고민한 '열쇠'의 연장선이기도 합니다.

이런 맥락에서 우선 tmux를 쓰려고 했습니다. 그런데 제 환경에는 윈도우 기계가 섞여 있었고 윈도우에서 tmux를 제대로 돌아가게 하는 과정은 생각보다 복잡했습니다. 윈도우에서 tmux를 돌리는 길이 아예 없는 것은 아닙니다. 가장 흔한 방법은 WSL로 리눅스 하나를 통째로 띄우는 것이지만 그게 부담스러우면 WSL 없이 가는 우회로도 있습니다. 바로 Cygwin이나 MSYS2입니다. 둘 다 윈도우 위에 유닉스 비슷한 환경을 통째로 깔아 주는 호환 계층인데 Cygwin은 설치 관리자에서 tmux 패키지를 함께 골라 받고 MSYS2는 pacman이라는 패키지 관리자로 받아 그 환경의 셸 안에서 tmux를 실행합니다[^Terminal Multiplexers for Windows: Installing GNU Screen, tmux, and Top Alternatives Guide]. tmux가 유닉스의 가상 터미널과 프로세스 분기에 기대고 있어 윈도우에 네이티브로 올라오지 못하니 결국 이런 에뮬레이션 환경을 한 겹 깔아야 하는 것입니다. 간단히 tmux를 쓰려고 시작한 일이 리눅스 환경을 통째로 가져오고 있는 상황을 보니 이게 올바른 방향인가 하는 생각이 들어 한참만에 마음을 바꿨습니다. 지난 docker-monitor, whooing-tui, arq-backup-tui를 만들 때처럼 마땅한 게 없으면 그냥 직접 만들기로 한 것입니다. 도구가 손에 맞지 않을 때 직접 깎아 쓰는 버릇은 백업 포맷을 바이트 단위로 해부해 검증기를 짠 '방주' 무렵부터 동일하게 행동하고 있습니다.

직접 만들기로 한 김에 터미널 멀티플렉서가 무엇이고 어떻게 발전해 왔는지 잠깐 살펴봅시다. 한 화면 안에 여러 세션을 띄우고 접속을 끊었다 다시 붙어도 세션이 살아 있게 하는 발상은 꽤 오래된 것입니다. 그 원형이라 할 GNU Screen이 1987년 베를린 공과대학의 올리버 라우만과 카르스텐 보르만의 손에서 나왔으니[^GNU Screen] 거의 40년 전 물건입니다. 지금 표준처럼 쓰이는 tmux는 그보다 한참 뒤인 2007년에 니콜라스 메리엇이 처음 내놓았고 ISC 라이선스로 OpenBSD 기본 시스템에 들어가며 자리를 잡았습니다[^tmux]. tmux의 핵심은 클라이언트와 서버를 나눈 구조입니다. 셸을 들고 있는 서버가 백그라운드에서 따로 돌고 화면을 그리는 클라이언트가 거기 붙었다 떨어졌다 하기 때문에 터미널 창을 닫아도 셸은 계속 살아 있습니다. 더 현대적인 시도가 없었던 것도 아닙니다. 2021년에는 러스트로 짠 Zellij가 단축키를 외우지 않아도 되도록 화면 아래에 가능한 키를 늘 보여 주는 방식으로 등장했습니다[^Zellij Is A New Terminal Multiplexer Written In Rust]. 다만 이들은 거의 다 유닉스 기반으로 개발되어 윈도우에서는 리눅스 호환 계층을 거쳐 우회해 돌리는 것이 보통입니다.

도구를 직접 만들기 시작하고 나서야 한 가지를 알게 됐습니다. 현대적인 터미널 멀티플렉서가 드물고 있어도 기존 도구를 감싼 형태가 많은 이유는 윈도우 포팅이 지독하게 고통스럽기 때문입니다. 유닉스에는 가상 터미널이라는 기반이 오래전부터 있어서 프로그램의 입출력을 가로채 화면에 다시 그리는 일이 자연스럽습니다. 그런데 윈도우에는 그런 기반 자체가 없었습니다. 마이크로소프트가 ConPTY라는 이름의 가상 콘솔 기반을 내놓은 것이 2018년 윈도우 10 1809 버전에 와서의 일이고 그전까지 서드파티 터미널은 보이지 않는 콘솔을 하나 띄워 그 출력을 긁어다 자기 화면에 다시 그리는 식의 불안정한 우회로 버텨야 했습니다[^Windows Command-Line: Introducing the Windows Pseudo Console (ConPTY)]. 게다가 유닉스 멀티플렉서가 당연하게 쓰는 fork와 같은 프로세스 분기나 도메인 소켓 같은 통신 수단도 윈도우에는 그대로 옮겨지지 않습니다. 누구든 현대적인 멀티플렉서를 처음부터 윈도우를 함께 지원하려고 하면 이 부분에서 막힐 겁니다. 이 일은 마치 주방 한구석에 쌓인 채 아무도 손대기 싫어하던 그릇 더미 같았습니다. 한번 닦아 두면 두고두고 편하다는 걸 알면서도 막상 소매를 걷기에는 너무 번거로워 다들 미뤄 두는 그런 것들입니다.

지난 'AI 활용 회고 (2025)'에서 AI에 일을 맡기는 마음을 식기세척기 이야기에 비유한 적이 있습니다. 식기세척기는 사람이 손으로 닦는 것보다 느리고 가끔 그릇을 덜 닦기도 하지만 그 시간에 다른 일을 할 수 있게 해 준다는 점에서 쓸모가 있습니다. 닦기 싫은 그릇 더미를 앞에 두고 같은 생각을 했습니다. 이 고통스러운 윈도우 호환 계층 재작성을 제가 한 줄 한 줄 손으로 할 엄두는 나지 않았지만 클로드코드에 맡겨 두고 옆에서 방향만 정해 주는 일이라면 해 볼 만했습니다. 그렇게 만들기 시작한 것이 pytmux입니다. 파이썬과 Textual이라는 터미널 UI 라이브러리로 짠 tmux 유사 멀티플렉서이고 구조는 tmux와 같이 셸을 든 서버와 화면을 그리는 클라이언트를 나눠 두어 앱이나 터미널 창을 닫아도 세션이 계속 살아 있게 했습니다. 5일 동안 클로드코드의 손을 빌려 400여번의 커밋을 만들었습니다. 사람이 한 줄씩 타이핑했다면 엄두가 안 났을 분량입니다. 특히 모바일에서 원격으로 붙어 쓰다 떠오른 자잘한 불편을 적어 두면 그걸 하나씩 구현하고 제출하고 매뉴얼에 반영하는 일을 계속해서 반복했습니다. 메뉴 위치를 옮기고 탭 모양을 다듬고 닫기 확인 팝업을 가운데로 옮기는 식의 요청이 수십 개 쌓이고 또 계속해서 수정되었습니다. 제가 한번에 수 십 개씩 쌓아둔 요청은 다음 날 아침이면 대체로 완료되어 있었습니다.

기능은 처음에 생각한 요구사항에 거의 그대로 도달했습니다. 한 화면을 여러 패널로 나눠 동시에 모니터링 하고 탭으로 작업 공간을 가르고 휠을 올리면 자연스럽게 위쪽으로 스크롤되어 이전 내용을 보여줍니다. tmux는 여러 세션을 지원하지만 저는 눈에 보이는 단일 세션과 그 하위의 탭, 패널 구조만 있으면 충분했기 때문에 의도적으로 단일 세션만 지원하기로 결정했습니다. 명령을 다 외우지 못해도 메뉴와 명령 프롬프트로 거의 모든 동작을 할 수 있고 마우스로 경계선을 끌어 패널 크기를 바꾸거나 클릭으로 포커스를 옮기는 일도 됩니다. tmux를 오래 써 온 사람에게는 사소해 보일 수 있지만 단축키를 외우는 데 소질이 없는 제게는 이 마우스와 메뉴를 완전 지원한다는 점이 가장 큰 차이였습니다. 정작 손이 가장 많이 가는 일은 그다음이었습니다. 윈도우에서 네이티브로 구동시키는 일입니다. 휴일에 맥으로 작업을 마치고 잘 동작할 것이라 예상한 채 평일에 윈도우 기계로 가져왔더니 전혀 실행이 되지 않았습니다. fcntl이나 termios처럼 유닉스에만 있는 것들과 fork, 도메인 소켓이 전부 막힐 때마다 클로드코드가 ConPTY 기반의 pywinpty로 가상 터미널을 다시 만들고 도메인 소켓 대신 루프백 TCP로 통신을 옮기고 콘솔 창이 뜨지 않도록 데몬을 띄우는 식의 대체 경로를 추가했습니다. 막히는 자리는 그뿐이 아니었습니다. 유닉스에서는 가상 터미널의 출력을 이벤트 루프가 바로 모니터링 하면 되는데 윈도우에서는 그 통로를 같은 방식으로 모니터링 할 수 없어 별도의 읽기 스레드가 출력을 받아 루프로 넘겨 주도록 따로 구현해야 했습니다. 운영체제에 따라 갈리는 부분을 세 개의 호환 계층으로 모아 두는 모양으로 정리됐습니다. 이런 자리가 하나씩 쌓이다 보니 세상의 멀티플렉서들이 윈도우를 네이티브로 지원하기보다 리눅스 호환 레이어를 사용하는 쪽을 고르는 이유를 알 수 있었습니다. 그 우회야말로 이 고통스러운 작업을 통째로 피해 가는 합리적인 선택이었습니다. 제가 한 일은 막힌 자리를 같이 짚고 다음에 무엇을 시도할지 정하는 정도였고 지루한 재작성의 대부분은 기계가 했습니다. 결과는 실제 윈도우 11 기계에서 셸을 띄우고 화면을 나누고 한글과 이모지가 깨지지 않는 데까지 확인했고 화면 없이 도는 자동 테스트도 통과했습니다. 엄두가 안 나던 재작성을 기계가 전부 처리해 주었습니다.

pytmux를 만드는 내내 그 pytmux 안에서 일했습니다. 개밥 먹기인데 서버와 클라이언트를 빠르게 고치다 보니 곧 한 가지 고통에 부딪혔습니다. 클라이언트를 고쳤다면 그냥 재실행하면 되지만 셸을 들고 있는 서버 쪽 코드를 고치면 그걸 반영하려고 서버를 재시작해야 했고 그때마다 열어 둔 패널과 그 안에서 돌던 작업이 전부 사라졌습니다. 한창 여러 세션을 띄워 두고 그 안에서 다음 수정을 시키던 참인데 서버 한 번 새로 띄우자고 그 세션들을 매번 초기화하는 일은 너무나 귀찮았고 작업 맥락을 계속해서 깼습니다.

그래서 세션을 초기화하지 않고도 서버를 재시작하는 길을 찾아봤습니다. 클라이언트를 뗐다 붙이는 것 말고 셸을 살린 채로 서버를 갈아 끼우는 게 가능할 거라고는 기대하지 않았는데 의외로 길이 있었습니다. 핵심은 서버를 죽였다 새로 띄우는 대신 같은 프로세스가 자기 자신을 새 코드로 갈아 끼우는 것이었습니다. 프로세스 번호가 그대로 유지되니 자식 셸들은 여전히 그 서버의 자식으로 남고 셸과 연결된 통로도 닫히지 않습니다. 평소에는 새 패널의 통로가 sibling에게 새지 않도록 닫아 두는데 재시작 직전에는 반대로 그 닫힘 표시를 풀어 새 코드가 그 통로를 그대로 물려받게 해야 했습니다. 화면 내용은 통째로 직렬화해 복원하는 대신 셸에게 창 크기가 바뀐 척 신호를 보내 스스로 다시 그리게 하는 쪽을 골랐습니다. 클로드나 vim처럼 화면을 꽉 채우는 프로그램은 그 신호 한 번이면 깔끔하게 다시 그려졌고 스크롤백은 평문 스냅샷으로 따로 떠 두었습니다.

여기에 혹시 모를 사고를 대비한 안전장치도 하나 달았습니다. 재시작이 정말 안전한지 미리 점검하는 드라이런 명령입니다. 실제로 갈아 끼우기 전에 지금 상태에서 재시작하면 무엇이 보존되고 무엇이 위태로운지를 부작용 없이 검사해 통과인지 실패인지만 알려 줍니다. 이 세션 유지 재시작을 넣고 나서부터가 진짜였습니다. 그때부터는 pytmux 안에서 pytmux를 고치고 서버 코드를 바꾸고도 돌던 세션을 그대로 둔 채 새 코드로 갈아 끼우며 곧바로 이어서 다음 기능을 시키는 흐름이 가능해졌습니다. 빠르게 고치고 빠르게 개선하는 구간은 사실상 여기서부터 시작됐습니다.

두 번째 부담이던 토큰 과사용도 같은 도구로 해결하기 시작했습니다. pytmux는 패널 화면을 읽어 클로드코드가 지금 무엇을 하는 중인지 살펴봅니다. 처리 중인지 입력을 기다리는지 사용량 한도에 막혀 멈춰 있는지를 화면에 그려지는 문구로 판별하고 컨텍스트 잔량과 토큰 사용량도 같은 방식으로 읽어 상태줄에 보여 줍니다. 이걸 발판 삼아 몇 가지 자동 개입을 추가했습니다. 한도에 막혀 멈추면 화면에 적힌 해제 시각을 읽어 그때가 됐을 때 알아서 작업을 재개하고 컨텍스트가 가득 차기 전에 미리 정리하고 입력을 기다리는 상태가 이어지면 권한 모드를 알아서 설정하고 리모트 컨트롤도 켭니다. 제 습관을 거의 바꾸지 않고도 옆에서 기계가 사용량을 모니터링 하는 형태입니다. 다만 이런 개입은 엉뚱하게 나서면 멀쩡히 돌던 작업을 망칠 수 있어서 전부 기본적으로는 꺼 두었고 켜더라도 되돌릴 수 없는 동작일수록 더 강한 안전장치를 두었습니다. 발동 직전에 화면이 정말 그 상태인지 한 번 더 확인하고 사람이 입력하면 취소되며 곧 일어날 자동 동작은 상태줄에 남은 시간을 세는 카운트다운으로 미리 보여 주어 원치 않으면 그 사이에 아무 키나 눌러 멈출 수 있게 했습니다. 같은 계정으로 여러 세션을 돌릴 때 한쪽만 보고 판단하면 전체 사용량을 놓치기 쉬워서 계정 단위로 합산해 보여주는 인터페이스를 준비했습니다. 화면을 읽어 판별하는 방식이어서 클로드코드 CLI 앱이 변경되면 깨질 수 있습니다.

만드는 과정이 내내 순조롭지는 않았습니다. 오히려 환경에 따라서만 나타나는 버그를 쫓는 데 시간을 가장 많이 썼습니다. 한번은 클로드코드가 떠 있는 패널에서 새 탭을 열면 새 패널에 옆 패널의 화면이 섞여 들어와 마치 복사된 것처럼 보이는 일이 있었습니다. 화면 없이 도는 테스트로는 아무리 돌려도 새 탭이 깨끗해서 한참을 헛다리만 짚었습니다. 결국 패널마다 원시 출력을 그대로 기록하는 도구를 처음부터 켜 두도록 설정하고 나서야 원인을 알아낼 수 있었습니다. 새 셸이 이전 패널의 터미널 통로를 그대로 물려받아 출력이 섞이던 것이라 통로를 만들자마자 자식에게 물려주지 않도록 막아 해결했습니다. 데몬으로 떠 있는 서버에 여러 패널을 빠르게 만들고 지우는 상황에서만 나타나는 동작이라 화면 없이 도는 테스트로는 영영 못 잡았을 버그였습니다. 또 한번은 로컬에서만 클로드코드의 인터페이스 전체에 밑줄이 그어지는데 원격에서는 멀쩡한 일이 있었습니다. 이건 터미널 에뮬레이션 라이브러리가 현대식 색상 표기의 한 형식을 읽지 못해 한번 켜진 밑줄을 끄지 못하던 것이었는데 라이브러리에 문제의 바이트를 직접 먹여 보는 것으로 화면 없이 결정적으로 재현해 냈습니다. 이런 환경 의존 문제를 만날 때 행동할 규칙을 만들었습니다. 재현도 검증도 안 되는 버그는 추측하지 말고 먼저 무슨 일이 벌어지는지 기록부터 하는 것입니다.

가장 오래 머리를 싸맨 것은 한 패널의 클로드코드와 pytmux 클라이언트가 거의 동시에 죽어 버리는 일이었습니다. 처음에는 셸을 든 서버가 죽으면서 클라이언트도 함께 터졌다고 생각했습니다. 그런데 기록을 살펴보니 서버는 23시간 넘게 멀쩡히 살아 있었고 부모 셸도 그대로였습니다. 메모리가 모자라 강제 종료된 흔적도 비정상 종료 보고서도 없었습니다. 혹시 제가 추가한 자동 개입이 작업을 끝내 버린 게 아닌가 싶어 코드를 다시 살펴봤지만 pytmux가 패널에 넣는 것은 재개나 정리 같은 글자뿐이고 프로세스를 끊는 신호를 보내는 길은 아예 없었습니다. 가설을 하나씩 지워 가다 남은 결론은 두 죽음이 서로 원인과 결과가 아니라 그저 같은 시각에 따로 일어난 사건이라는 것이었습니다. 마침 그 시간대에 클로드코드가 자동 업데이트 되었다는 점이 외부 요인이었을 가능성이 있지만 상관관계일 뿐 인과로 단정하기는 어렵습니다. 다만 이 조사에서 가장 쓸모 있던 발견은 따로 있었습니다. 클라이언트가 혼자 죽었을 때 아무 기록도 남기지 않고 스스로 되살아나지도 못한다는 점이었습니다. 그래서 클라이언트가 죽으면 그 사실을 남기고 다시 띄우되 짧은 시간에 거듭 죽으면 멈추도록 했습니다.

직접 만든 도구에는 손이 가는 일이 하나 더 있습니다. 매뉴얼입니다. 오랫동안 일하면서 소프트웨어의 사용 설명서를 만들고 버전이 바뀔 때마다 그것을 최신 상태로 맞춰 두는 일은 늘 어렵고 소모적이었습니다. 특히 화면을 담은 스크린샷이 골칫거리였습니다. 게임 웹사이트의 매뉴얼 페이지를 보면 안내 이미지가 실제 최신 빌드와 한참 어긋나 있는 경우가 흔한데 이는 담당자가 게을러서가 아니라 인터페이스가 조금만 바뀌어도 관련 스크린샷을 하나하나 다시 찍어 같은 구도로 끼워 넣는 일이 대단히 번거롭기 때문입니다. 버튼 위치나 색이 조금 바뀔 때마다 수십 장을 다시 캡처하는 일을 사람이 매번 제때 하기는 어렵고 스크린샷은 만든 순간부터 빠르게 낡게 됩니다.

이번에는 인터페이스를 TUI로 만든 덕에 이 일을 자동화할 수 있었습니다. 핵심은 손으로 캡처하지 않고 실제 앱을 직접 실행해 원하는 상태를 만든 다음 그 화면을 기록하는 것입니다. 처음에는 매뉴얼의 화면을 코드 블록 안 ASCII 그림으로 그려 두었는데 이건 버전 관리에 친화적이고 폰트에 무관하다는 장점이 있는 대신 활성 패널의 파란 테두리나 상태줄 색 같은 실제 모습을 보여 주지 못했습니다. 그래서 실제 스크린샷을 찍기로 하고 방법을 검토했습니다. 터미널 출력을 이미지로 만들어내는 방법은 박스 그리기 글리프나 글자 폭이 변환기 폰트에 따라 깨질 위험이 있었고 실제 터미널에서 사람이 직접 찍는 방식은 재현이 안 되고 자동으로 다시 생성할 수도 없어 일찌감치 제외했습니다. 결국 고른 것은 클라이언트 UI 프레임워크가 자체로 제공하는 화면 내보내기였습니다. pytmux 클라이언트를 화면 없이 띄워 분할이나 새 탭 같은 상태로 만든 뒤 그 화면을 SVG로 떠내면 제가 실제로 보는 그 화면이 그대로 백터 이미지가 됩니다. SVG라 가볍고 깃헙 문서에서 바로 렌더링되는 장점이 있습니다.

막상 자동화해 보니 잔손이 많이 갔습니다. 상태줄의 시계와 달력은 띄울 때마다 다른 시각을 그려서 같은 장면을 다시 만들어도 이미지가 달라지는데 큰 문제는 아니라고 판단해 그대로 두었습니다. 진짜 클로드를 패널에서 돌려야만 나오는 장면은 따로 떼어 실제로 클로드 CLI를 한 세션 띄워 처리중 아이콘이나 권한모드 팝업을 캡처하되 계정 이메일이나 환영 배너에 적힌 이름 같은 민감정보는 저장 직전에 자동으로 가린 공개본을 만들게 했습니다. 생성기가 빈 이미지를 뱉거나 도중에 죽지 않는지 또 각 장면에 기대한 표시가 들어 있는지는 픽셀을 비교하는 대신 SVG 안의 문자열을 확인하는 회귀 테스트를 만들었습니다. 그 결과 매뉴얼에는 스무 장 남짓의 실제 화면이 실렸고 이제 인터페이스가 바뀌어도 명령 한 줄로 스크린샷을 전부 다시 만들어 끼워 넣을 수 있습니다. 손으로 찍어 붙였다면 진즉 낡았을 설명서가 코드 변경에 따라 스스로 업데이트됩니다.

복잡한 이야기만 했는데 정작 만들면서 가장 즐거웠던 건 쓸데없는 기능이었습니다. tmux에는 시계 모드라는 게 있어서 화면을 큰 시계로 채워 줍니다. 그걸 가져오되 조금 다르게 만들었습니다. 시계를 켜면 지금 패널의 셸 화면을 어둡게 깔고 그 위에 시각을 큰 숫자로 그리는데 뒤의 셸은 사라지지 않고 흐릿한 채로 계속 출력을 갱신합니다. 빌드 로그가 어둑한 배경에서 흐르고 그 앞에 시계가 떠 있는 화면을 보고 있으면 터미널에서 이 정도 사치는 부려도 괜찮지 않나 하는 생각이 들었습니다. 여기에 달력 모드를 하나 더 붙였습니다. 하단 상태줄의 날짜를 누르면 이번 달 달력이 같은 방식으로 패널을 덮는데 화면이 좁으면 평범한 숫자 달력이 뜨고 넓으면 시계와 같은 큰 글꼴로 날짜를 큼직하게 보여 줍니다. 터미널인데도 숫자가 큼직하게 떠 있는 모양이 마음에 들었습니다.

이 사치품에도 어려움은 있었습니다. 큰 시계를 처음 띄웠을 때 한글이 섞인 화면에서 숫자가 어긋나 깨지는 문제가 있어 글자 폭 계산을 손봐야 했고 큰 달력에서는 한 날짜의 두 자리 숫자 사이가 너무 벌어져 16이 1과 6으로 따로 갈라져서 자리 사이 간격을 한 칸 줄여 두 숫자가 한 덩어리가 되게 맞췄습니다. 시계와 달력을 켜고 끄는 동작도 같은 명령을 다시 누르면 닫히도록 다듬었고 닫기 버튼 위치를 패널 닫기 버튼과 같은 위치로 맞췄습니다. 별것 아닌 기능에 이렇게까지 손이 가나 싶다가도 지루한 재작성은 어차피 기계가 맡고 있으니 이런 데서 즐거우면 그만이라는 생각이 들었습니다. pytmux를 만들며 이 부분이 가장 재미있었습니다.

지금 pytmux는 그럭저럭 쓸만한 상태가 되었습니다. macOS와 리눅스는 물론 윈도우에서도 별도 환경 없이 네이티브로 돌아가고 헤드리스 테스트 286개를 통과합니다. 설치 스크립트 하나면 어디서든 명령 한 줄로 띄울 수 있고 코드는 깃헙에도 올려 두었습니다. 만들다 보니 처음에는 생각하지 않은 기능도 생겼습니다. 클라이언트와 서버 사이의 응답이 느려지면 패널 외곽선을 붉게 보여 주고 고착되면 돌아가던 셸을 죽이지 않고 연결만 다시 세워 반응을 회복합니다. 적어도 제가 매일 여러 세션을 돌리는 작업에는 이제 Warp 대신 Windows Terminal에 pytmux만 띄워놓고 지냅니다.

재미있는 점은 클로드코드를 더 편하게 여럿 돌리려고 만든 도구인데 정작 그 도구를 만든 것도 클로드코드였고 도구가 하는 일의 한 축도 그 클로드코드를 옆에서 모니터링 하며 사용량을 다스리는 것입니다. 기계가 자신을 관리할 도구를 스스로 작성한 셈입니다. 이 기계는 여전히 느리고 가끔 멀쩡한 것을 망가뜨리며 맡긴 일을 한 번 더 살펴봐야 안심이 됩니다. 그래도 아무도 손대기 싫어하던 윈도우 포팅을 대신 떠맡아 준 것만으로도 충분히 값을 했습니다. 없으면 직접 만든다는 원칙은 그대로지만 만드는 데 드는 품이 예전과는 많이 달라졌다는 것을 이번에 다시 알았습니다. 당분간 이 기계에 일을 맡기며 살아 볼 작정입니다.