3-6. 원격으로, 그리고 밖으로 (8/18 ~ 9/14)
한 달 동안 커밋 2,904건, PR 더해졌다. 앞선 다섯 편이 "한 기계 안의 터미널"을 만들었다면, 이 편은 다른 기계로 뻗는 구간이다 — 자체 SSH 클라이언트, 원격 저장소·파일 트리·에이전트 상태·활동 이력을 로컬과 같은 UI 에 태우고, 폰과 Windows 에서 같은 코어를 돌린다. 그리고 그 사이에 터미널 코어를 wasm 으로 떼어 브라우저 라이브러리로 냈다.
SSH 를 직접 짠다 (8/17~8/21)
8월 17일 오후에 계약·패킷·협상, curve25519 키 교환, chacha20-poly1305, 호스트키 검증, 사용자 인증까지(S0~S6b) 깔아 둔 뒤, 18일 하루에 S7 부터 S9 까지 밀어붙였다.
"안드로이드는 된다고 하셨으니 안드로이드는 포그라운드 서비스로 ssh 연결 유지되게 해주세요" — 2026-08-18
다음 날 iOS host 가 같은 펌프로 붙고, 기기에서 키를 만들어 실서버에 붙어 증명하고, 안드로이드 개인키는 Keystore 로 봉인했다(S9c). 비밀번호 인증, none 으로 방법 목록 묻기, 처음 붙는 서버의 지문 승인까지 붙이니 폰이 독립된 SSH 클라이언트가 됐다. 8월 20일에는 같은 연결에 컨트롤 채널을 하나 더 열어(S10) 폰이 세션 목록을 받고 두 host 가 실제로 굴리는 것까지 갔다. maru control --stdio 가 그 중계다.
훅으로 에이전트를 읽는다 (8/20~8/25)
6월에 만든 에이전트 감지는 트랜스크립트(JSONL)를 읽는 방식이었다. 8월 20일에 질문이 하나 나왔다.
"에이전트 읽는것도 그냥 훅 쓰면 되는거 아닌지 아니면 꼭 필요한지 궁금해요" — 2026-08-20
그날 훅을 상태·알림·턴의 단일 소스로 두는 계약을 적고, 훅 이벤트를 나르는 채널의 순수 코어(AH1)와 claude·codex 설정에 훅을 설치하는 경로(AH2a·b)를 만들었다. 게이트는 기본 꺼짐, 끄면 설치한 것을 지운다. 다음 날 훅 모드가 배지·대화·알림을 채우고(AH3~5), 배지가 거짓말하던 열넷을 닫았다. 8월 22일 "선결 조건 셋이 다 닫혔다"며 기본값을 켰다.
훅은 턴의 경계이기도 하다. 훅을 턴 경계 트리거로 삼고(AT1) 턴 제목을 링에 싣고(AT2) 턴이 만진 파일을 직접 캡처해 ✎ 가 내 것과 남의 것을 가르게 했다(AT3). 8월 25일에는 codex 훅이 하나도 안 돌고 있던 것을 찾았다 — 낡은 신뢰 값이었다. 그리고 codex 오류 턴은 "세트를 늘려도 못 닫는다"는 실측을 박고 확정된 한계로 종결했다.
@maru/* — 코어를 밖으로 (8/22~8/24)
8월 22일, 터미널 코어를 wasm 으로 내고 @maru/* 라이브러리를 만들었다. clean-room VT 코어가 앱 밖으로 나간 자리다 — 저장소 안 packages/ 의 @maru/core 0.1.0 이고, 9월 14일 기준 npm 에는 아직 올리지 않았다. 사흘 동안 진짜 PTY 로 TUI 를 검증하며 IME 결함을 고치고, 화면·스크롤 제어·상태 이벤트·검색·OSC·직렬화·키 가로채기·선택 조회까지 API 를 마감했다. 마커와 장식, 앱이 주입하는 링크 규칙, 캔버스가 못 하는 접근성·터치는 DOM 이 맡는 구조도 이때다. 렌더 비용을 재 보니 WebGL 렌더러는 필요 없어 계획에서 지웠고, 장식이 무한 렌더 루프를 만들어 유휴 1638fps 였던 것을 2fps 로 잡았다.
편집기가 편집기가 된다 (8/24~9/6)
8월 초에 "네이티브 텍스트 편집기가 먼저"라고 정한 뒤(3-5), 이 구간에서 실제로 편집이 된다.
- 8/24 — 편집 버퍼를 persistent rope + delta 로, selection 을 같은 연산에서 민다(§3.0·§3.3)
- 8/26 — 커서 이동 일습·랩 시각 행·스크롤 앵커(§3.2)
- 8/27 — 괄호 자동 닫기·감싸기, ⌘/ 주석(없던 언어 판정을 세워서), 한글 조합 표시(N3), ⌘Z — "기능은 서 있었고 키가 없었다"
- 8/28~29 — tree-sitter 구문 트리 1층(N4), grammar 18개, 파싱이 프레임 예산을 지키게 끊고 재개(§2.1a — 워커가 아니다), 접힘이 구문 층으로 승격
- 8/30 — 고른 부분을 에이전트에 보낸다(NS3~NS6), 정의·심볼·검색이
(URI?, range)로 한 경로 - 9/1~9/3 — 대소문자·낱말 찾기, 줄 조작 넷과 Enter 자동 들여쓰기(§3.9a)
- 9/5~9/6 — 열/블록 선택(§3.2a), 다중 커서 —
⌥⌘↑↓(§3.2b),⌥클릭(§3.2c), ⌥더블/트리플클릭(§3.2d) - 9/10~9/11 — 줄별 폭 캐시(L2)로 접힘 토글 30.3ms → 1.29ms, 편집·줄 수·탭 폭이 바뀌어도 캐시가 산다
- 9/13~9/14 — 충돌 마커 인식·선택(S2), 충돌 파일을 세 판 든 편집기로 여는 병합 모드(S3b-1)
병합 편집기는 범위를 먼저 눌렀다 — "병합 편집기 스피릿인 그룹 다 그건 구현 계획 예정에 적어주고 일단 기본 편집기능 부터 하는게 맞는것 같습니다"(9/2).
Windows 가 앱이 된다 (8/18~8/31)
8월 17일 밤 W7 로 첫 터미널 화면(D3D11·DirectWrite·입력)까지 띄운 Windows 는 이 구간에서 앱의 나머지를 얻었다. 파일 트리(W8.2), git 백엔드와 소스 컨트롤(W8.4), 편집기 본문·스크롤·클릭·드래그 선택(W8.3), 번들 폰트를 컬렉션에 담아 macOS 와 같은 폰트로 그리기, 사이드바 띠·프레임리스 창·헤더 아이콘·상태표시줄(W8.9), maru install-cli(W10), 에이전트 도크 검색(W8.15), 가로 스크롤(W8.17c), 카드를 펼치면 대화(W8.22). 그리기 스모크는 27초에서 4초로 줄였고, git 을 부를 캡처 러너는 CreateProcessW 와 익명 파이프로 세워 환경 상속·stdin NUL 같은 함정을 적대적으로 검증했다.
원격이 로컬과 같아진다 (8/29~9/14)
이 편의 가장 큰 축이다. SSH 로 붙은 pane 에서도 로컬과 같은 것이 보이게 하는 작업이 네 갈래로 진행됐다.
원격 에이전트 상태(RA·RAV). 8월 29일 RA1~RA5 로 토대를 놓고 원격 이벤트를 tick 루프에 붙여 배지가 ssh 너머에서 서게 했다. tmux 오배달은 역조회로 되찾는다(RA6). 9월 11일부터는 원격 pane 의 활동 자체가 화면에 뜬다(RAV3·RAV7·RAV8). 원격/SSH 이력은 원래 비목표였는데, 뒤집고 무엇을 선 위로 보낼지 실측으로 정했다.
원격 소스 컨트롤(RS·RW). 원격 pane 이 활성이면 그 저장소를 읽고(RS1·RS2), 비교를 열고(RS3a — 작업트리 쪽은 git 이 못 읽어 파일을 직접 읽는다), 커밋 메시지를 stdin 으로 보내고(RS4b), 워크트리 행을 그 기계에서 읽고 쓴다(RS5). 감시는 정직해야 했다 — macOS 원격은 «감시하는 척» 하고 있었고, 못 하면 못 한다고 말하게 한 뒤(RW6) 폴링으로 되살렸다(RW7). 9월 13일 원격 히스토리를 열 때 벽은 배선이 아니라 «우리가 만든 토큰» 이었다(RS7).
"근데 원격 히스토리도 구현하고 싶은데 지금 변경사항은 나오잖아요?" — 2026-09-13
원격 파일 트리(RF). 9월 5일 설계와 순수 층(RF1)에서 시작해 다음 날 백엔드·실물 sshd 왕복(RF2b), 트리 v1(RF3a), 원격 변경·삭제·생성(RF6)까지 하루에 섰다. 적대적 검증 5회와 실서버 캡처로 눈으로 확인했다.
에이전트 이미지. 발단은 이 요청이다.
"제가 하려는건 우측 도크가 지금 아이콘이 3개인데 하나 더 만들어서 또는 터미널 우측 상단에 하나 만들어서 클로드나 코덱스한테 보내준, 또는 클로드나 코덱스가 읽은 이미지나 영상을 한눈에 볼 수 있는걸 만들고 싶거든요? 일단 그게 가능한가요?" — 2026-08-28
다음 날 갤러리 뼈대·인덱스·격자·크게 보기(IG0~IG4)가 섰고, 제품 스캔 실측 3.6초를 보고 동기 스캔을 기각해 워커로 옮겼다. 스캔은 52.9초에서 9.0초로 줄였다(O(N²) 복사 제거). 9월 14일에는 터미널의 [Image #N] 마커를 Cmd+클릭해 그 자리에서 보고, 원격에서도 픽셀을 당겨와 프리뷰한다(P3).
활동 뷰 (9/6~9/10)
"읽기 뿐만아니라 mcp, cli 등의 호출 기록도 간략하게 보고싶은거거든요? 리스트뷰로 해서 하는게 좋을까요?" — 2026-09-06
다음 날 인덱스가 도구 호출을 담고(AV1-a), 명령이 어떻게 끝났는지 보이고(AV2), 줄을 누르면 전문이 열린다(AV3). 종류를 눌러 고르고(AV4) Enter 가 본문까지 넓힌다(BS1 — 라벨은 세션의 2.9% 다). 9월 9~10일에는 Codex 의 JS 껍데기와 옛 JSON 껍데기, chunk 껍데기를 벗겨 라벨의 74.4% 가 명령이 되고 갇힌 출력 191.5MB 를 되찾았다. 계약은 "구조는 벗기고 글자는 안 알아본다"(§2.3)로 못박았다.
모바일이 앱이 된다 (9/5~9/11)
접근성 서술자와 스크린 리더(M9), OS 글자 크기 추종(M13c), 소리 알림(M9a), 저전력 모드(M14a), 첫 실행 키 생성(M16), 진단 복사와 죽은 자리 기록(M15), 서버·세션 목록 표(M12), 끊김·재접속·keepalive(M3c), 그리고 앱 아이콘 — 앰버 커서 모티프, 세 플랫폼(M11b, 9/9). 배포 준비도 개발 쪽에서 할 수 있는 것부터 시작했다(M11a).
터미널 자체도
8월 21일 수식자 붙은 특수 키를 xterm legacy 형식으로 보내 Ctrl+화살표 단어 이동이 되게 했고(베이스는 terminfo 가 정답이다), 9월 9일 kitty report_events·relative placement·OSC 99 데스크톱 알림을 붙였다. 9월 11일에는 kitty·OSC·키보드 인코더 방어선을 전수로 훑었다 — kitty 여덟 중 여섯이 그물 밖이었고, 키보드 인코더에서 못 잡은 하나는 Caps Lock 이었다.
"정말 미구현 부분이 맞는지 키티 프로토콜 명세서(웹검색)으로 찾고, 실제 코드에서도 적대적 검증 해주세요" — 2026-09-13
session-host: 릴리스를 스스로 증명한다
이 구간 커밋의 3분의 1(920건)이 session-host 다. 절반은 재접속·복구·checkpoint·알림(CR5·CR6·R7·notification journal)이고, 나머지 절반은 8월 27일부터 시작된 릴리스 어댑터다 — GitHub tag chain 검증, 현재 릴리스 인증, manifest 와 evidence signer 결속, predecessor manifest 인증, DMG·attestation 권위, live release workflow, upgrade-B 증거, profile 별 attestation. 앱이 자기 릴리스가 진짜인지 증명하고, 업그레이드가 어느 판정에서 왜 거부됐는지 남긴다.
9월 14일 시점
터미널이 원격을 로컬처럼 다루고, 폰과 Windows 에서 같은 코어가 돌고, 코어는 브라우저 라이브러리로도 나간다. 다음 편은 이 위에 무엇이 얹히는지를 다룰 것이다.