블로그
휴대폰에서 Claude Code 원격 실행하기
Tactic Remote가 Mac을 상시 가동 AI 코딩 서버로, iPhone을 일급 제어 면으로 만드는 방법 — 실제 아키텍처 상세, 마케팅 문구 없이.
이 카테고리의 제품 및 엔지니어링 업데이트를 확인하세요.
카테고리 보기Claude Code는 터미널 기반 AI 코딩 어시스턴트입니다. Mac에서 실행되어 코드베이스를 읽고, 코드를 작성하고, 테스트를 실행하고, 변경을 커밋합니다. 문제는: 작업하는 동안 Mac 앞에 앉아 있어야 한다는 것입니다.
Tactic Remote는 이 제약을 깨기 위해 존재합니다. Mac이 실행 엔진으로 남고, iPhone이 제어 면이 됩니다. 이 글은 이것이 정확히 어떻게 작동하는지 — 아키텍처, 프로토콜, 트레이드오프, 설정을 다룹니다. 모호한 설명 없이.
TL;DR: Mac의 Node.js WebSocket 서버가 tmux를 통해 Claude Code를 관리합니다. iPhone이 LAN 또는 Cloudflare Tunnel로 연결하여 명령을 보내고 스트리밍된 터미널 출력을 수신합니다. Claude가 완료하거나 입력이 필요할 때 푸시 알림이 옵니다.
아키텍처
4개 컴포넌트, 3개 네트워크 경로:
Node.js 서버의 런타임 의존성은 정확히 하나: ws npm 패키지뿐입니다. HTTP 서버, 인증, 속도 제한, tmux 관리, 파일 탐색 — 모두 Node.js 내장 기능으로 구현했습니다. 의도적인 설계입니다. 의존성이 적을수록 고장이 적고, 공급망 위험이 적고, 공격 면이 작습니다.
서버가 Claude Code를 제어하는 방법
대부분의 사람이 놀라는 부분: 서버와 Claude Code 사이에 API가 없습니다. 서버가 Claude Code를 제어하는 방식은 여러분이 하는 것과 동일합니다 — 터미널에 타이핑하는 것입니다.
Claude Code는 tmux 세션 안에서 실행됩니다. 서버는 tmux send-keys로 키 입력을 보내고 tmux capture-pane으로 출력을 읽습니다. SDK 통합이 아닌 터미널 자동화입니다.
세션 생성
iPhone에서 새 세션을 생성할 때:
send-keys의 -l 플래그가 중요합니다 — 내용을 문자 그대로 해석하도록 tmux에 지시하여 특수 문자가 tmux 키 이름으로 처리되는 것을 방지합니다.
50,000줄 스크롤백 버퍼는 tmux 기본값의 25배입니다. Claude Code 세션은 방대한 출력을 만듭니다 — 컴파일 로그, 파일 diff, 테스트 스위트. 이 설정 없이는 무거운 작업 몇 분 후 컨텍스트를 잃게 됩니다.
Claude에 메시지 보내기
iPhone에서 프롬프트를 입력하고 전송하면:
시작의 C-u가 입력줄을 지웁니다. 이것 없이는 Claude가 프롬프트에 부분 텍스트를 남겨둔 경우(이전 중단된 입력에서) 메시지가 쓰레기에 추가됩니다.
Claude 중지
Claude Code에는 다른 처리가 필요한 두 가지 상태가 있으므로 중지는 다단계 시퀀스입니다:
Claude가 실행 중이면 Ctrl+C가 중단합니다. Claude가 이미 프롬프트에 있으면 /exit이 Claude Code의 종료 명령입니다. 둘 다 보내면 두 상태를 모두 처리합니다. 200ms 대기는 Claude가 다음 신호 전에 각 신호를 처리할 시간을 줍니다.
터미널 출력 스트리밍
시스템 전체에서 가장 어려운 엔지니어링 문제입니다. Claude Code는 컴파일이나 테스트 실행 중 초당 수백 줄을 출력합니다. 이 출력이 배터리를 소모하거나 네트워크를 압도하지 않으면서 iPhone에 실시간으로 도달해야 합니다.
폴링 루프
서버는 1초 간격으로 터미널 내용을 캡처합니다:
ANSI 이스케이프 코드가 보존된 상태로 마지막 2,000줄의 스크롤백을 가져옵니다.
이벤트 기반 대신 폴링을 사용하는 이유? tmux가 변경 알림을 제공하지 않습니다. tmux 내부 소켓에 파일시스템 워처를 사용하는 것을 평가했지만 macOS 버전마다 불안정했습니다. 1초 폴링은 좋은 균형입니다: 출력이 실시간으로 느껴질 만큼 빠르고, 세션이 유휴일 때 CPU 사용을 최소화할 만큼 느립니다.
안정성 감지
출력 모니터는 원시 캡처를 그대로 스트리밍하지 않습니다. 안정성 감지 알고리즘을 실행합니다:
- 매초 터미널 내용 캡처
- 이전 캡처와 비교
- 다르면: iPhone에 업데이트 전송, 안정성 카운터 리셋
- 5회 연속 캡처가 동일(5초 안정)하면: Claude가 입력을 기다리는지 확인
- Claude가 프롬프트에 있으면:
final: true로 최종 업데이트 전송, 폴링 중단
이것은 Claude의 점진적 렌더링을 처리합니다 — 긴 출력 중에는 터미널이 빠르게 변합니다. Claude가 완료되면 화면이 안정됩니다. 5초의 안정성은 Claude가 완료되었다는 신뢰할 수 있는 신호입니다.
모니터에는 120회 확인(2분)의 하드 캡이 있습니다. 2분 후에도 출력이 변하고 있으면 모니터가 중단되고 클라이언트가 수동 새로고침을 요청할 수 있습니다.
Claude 상태 감지 방법
서버가 Claude Code의 TUI를 화면 스크래핑하여 무엇을 하고 있는지 판단합니다. 이것은 필연적으로 휴리스틱입니다:
터미널 출력의 마지막 몇 줄에서 이 패턴을 확인합니다. Goodbye! 또는 Session ended가 보이면 Claude가 종료된 것입니다. Claude 특유의 표시기 없이 셸 프롬프트(줄 끝의 $)가 보이면 세션이 셸로 돌아간 것입니다.
이것이 취약할까요? 어느 정도는. Claude Code가 TUI 레이아웃을 변경하면 휴리스틱이 깨집니다. 하지만 대안인 — Claude Code에 커스텀 IPC 채널을 구축하는 것 — 은 포크를 유지하고 업스트림 변경을 추적해야 합니다. 기존 TUI를 화면 스크래핑하는 것이 실용적입니다: 수정 없이 모든 Claude Code 버전에서 작동합니다.
ANSI 색상 보존
핵심 설계 결정: 서버가 출력의 ANSI 이스케이프 코드를 보존합니다. 문제가 되는 터미널 시퀀스(커서 이동, OSC 제목 시퀀스, 프라이빗 모드 변경)는 제거하되 색상 코드는 의도적으로 유지합니다:
iOS 앱이 이 코드를 네이티브 AttributedString 스타일링으로 파싱합니다. 이로써 Claude Code의 전체 색상 출력 — 문법 강조, diff 색상, 상태 표시기 — 이 휴대폰 화면에 충실히 렌더링됩니다. 파서는 표준 16색, 256색 모드, 24비트 트루 컬러를 지원합니다.
WebSocket 프로토콜
서버는 같은 WebSocket 연결에서 두 가지 프로토콜을 동시에 사용합니다 — 레거시 플랫 프로토콜과 새로운 요청/응답/이벤트 프로토콜.
새 프로토콜 (권장)
세 가지 프레임 유형:
id 필드가 적절한 비동기 요청 멀티플렉싱을 가능하게 합니다. 클라이언트가 여러 요청을 동시에 보내도 응답을 정확히 매칭할 수 있습니다. 레거시 프로토콜에는 요청 ID가 없어 — 응답이 위치 기반이며, 두 요청을 빠르게 보내면 어떤 응답이 어떤 요청에 속하는지 알 수 없습니다.
전체 메서드 카탈로그
| 메서드 | 용도 |
|---|---|
list_sessions | 이름, 경로, 연결 상태와 함께 모든 tmux 세션 조회 |
create_session | 지정된 경로에 새 tmux 세션 생성 |
attach_session | 기존 세션으로 전환, 현재 출력 가져오기 |
delete_session | tmux 세션 즉시 종료 |
kill_session | Claude를 정상 종료 후 세션 종료 |
start_claude | 현재 세션에서 Claude Code 실행 |
stop_claude | Ctrl+C + /exit 시퀀스 전송 |
send_to_claude | 메시지 입력 후 Enter |
send_key | 단일 키 전송(Enter, Escape, Tab, y, n, Ctrl+C) |
get_output | 현재 터미널 내용과 Claude 상태 가져오기 |
get_full_output | 최대 5,000줄의 스크롤백 가져오기 |
change_path | 작업 디렉토리 변경 |
list_directory | 경로의 파일 탐색 |
create_folder | 새 디렉토리 생성 |
send_key 메서드에는 18개 키의 명시적 허용 목록이 있습니다. 임의의 키 시퀀스를 보낼 수 없습니다 — Claude Code의 인터페이스와 상호작용하는 데 의미 있는 키만 가능합니다. y와 n 키는 특별히 포함되어 있어 폰에서 Claude의 권한 프롬프트에 응답할 수 있습니다.
네트워크: LAN vs. Cloudflare Tunnel
두 가지 연결 모드가 두 가지 다른 문제를 해결합니다.
로컬 네트워크(LAN)
iPhone이 WiFi를 통해 Mac에 직접 연결됩니다:
Mac 컴패니언 앱이 Bonjour 서비스(_ws._tcp.)를 게시하여 iPhone이 네트워크에서 자동으로 발견할 수 있습니다. 설정 불필요 — 앱을 열고, 배너를 탭하면 연결됩니다.
지연 시간: 왕복 10ms 미만. 터미널 스트리밍이 즉각적으로 느껴집니다.
제한: 같은 네트워크에서만 작동합니다. 집 밖으로 나가면 연결이 끊어집니다.
Cloudflare Tunnel
원격 접근을 위해 Mac 컴패니언 앱이 Cloudflare Tunnel을 시작할 수 있습니다:
터널은 Mac에서 아웃바운드 전용입니다. 방화벽에 포트를 열지 않습니다. 라우터 설정 불필요. Cloudflare가 TLS 종단, DDoS 보호, WebSocket 프록시를 처리합니다.
빠른 터널 설정은 랜덤 *.trycloudflare.com 서브도메인과 32자 랜덤 API 키를 생성합니다:
Cloudflare 계정 불필요. 무료. 약 10초 만에 작동합니다.
지연 시간: 일반적으로 30-80ms, 가장 가까운 Cloudflare 엣지까지의 거리에 따라 다릅니다.
트레이드오프: 터널을 재시작할 때마다 URL이 변경됩니다. 영구 URL을 원하면 Cloudflare 계정과 명명된 터널이 필요합니다(여전히 무료).
QR 코드 빠른 연결
수동으로 연결을 설정하려면 휴대폰 키보드로 IP 주소, 포트, API 키를 입력해야 합니다. 이는 오류가 발생하기 쉽고 번거롭습니다.
Mac 컴패니언 앱이 연결 세부 정보를 JSON으로 포함한 QR 코드를 생성합니다:
iPhone 앱에서 Vision 프레임워크의 바코드 감지기로 스캔합니다. 한 번의 스캔으로 모드, 주소, 인증 등 전체 연결이 설정됩니다. 약 3초 소요.
알림 파이프라인
푸시 알림은 Tactic Remote의 "설정 후 잊기" 워크플로의 핵심 기능입니다. 긴 작업을 시작하고 자리를 뜨면, Claude가 완료하거나 입력이 필요할 때 알림을 받습니다.
작동 방식
Claude Code는 라이프사이클 이벤트에 실행되는 셸 스크립트인 hooks를 지원합니다. Mac 컴패니언 앱이 이 hook을 자동으로 구성하여 로컬 서버에 POST합니다:
Claude가 작업을 완료하면 이 hook이 발동합니다. 서버가 POST를 받고, 모든 연결된 클라이언트에 알림 이벤트를 브로드캐스트하며, iOS 앱이 UNNotificationCenter를 통해 로컬 알림을 발송합니다.
이벤트 중복 제거
hook 이벤트는 빠르게 연속으로 발동할 수 있습니다(예: Claude가 여러 하위 작업을 차례로 완료할 때). iOS 앱이 event|project|session|timestamp를 키로 3초 창으로 이벤트를 중복 제거합니다. 같은 논리적 이벤트가 3초 이내에 두 번 도착하면 중복은 조용히 드롭됩니다.
포그라운드 vs. 백그라운드
앱이 포그라운드에 있을 때 알림 이벤트는 터미널 버퍼에 시각적 표시기를 추가합니다:
앱이 백그라운드에 있을 때 시스템 알림이 발동합니다. 탭하면 세션으로 바로 이동합니다.
보안 모델
AI가 생성한 코드를 원격으로 실행하려면 진지한 보안 모델이 필요합니다. 다음이 우리의 모델입니다.
인증
서버는 두 가지 메커니즘을 통한 API 키 인증을 지원합니다:
Authorization: Bearer <key>헤더(표준)Sec-WebSocket-Protocol: <key>헤더(업그레이드 요청에 커스텀 헤더를 설정할 수 없는 iOS WebSocket 라이브러리를 위한 폴백 — 흔한 모바일 제약)
인증은 연결 시 한 번 확인됩니다. 실패하면 WebSocket이 코드 1008(정책 위반)으로 즉시 닫힙니다.
명령 주입 방지
서버가 실행하는 모든 외부 명령은 execSafe()를 거치며, child_process.spawn()을 shell: false로 호출합니다. 인수는 배열로 전달되며 문자열에 보간되지 않습니다. 셸 주입이 구조적으로 불가능합니다.
입력 유효성 검사가 심층 방어를 추가합니다:
| 입력 | 유효성 검사 |
|---|---|
| 세션 이름 | /^[a-zA-Z0-9_-]{1,64}$/ |
| Claude 명령 | /^claude(\s+--?[a-zA-Z0-9-]+)*$/ |
| 키 이름 | 18개 키의 명시적 허용 목록 |
| 파일 경로 | 절대 경로로 변환, startsWith()로 확인 |
경로 순회 보호
파일 탐색은 사용자의 홈 디렉토리 트리로 제한됩니다. 민감한 디렉토리는 다른 설정과 관계없이 항상 차단됩니다:
확인은 세그먼트별 매칭(부분 문자열 매칭이 아님)을 사용하므로 my-.ssh-backup이라는 이름의 디렉토리가 오탐을 발생시키지 않습니다.
속도 제한
실패한 인증 시도는 인메모리 슬라이딩 윈도우를 사용하여 IP당 분당 30회로 제한됩니다. 한도를 초과하면 서버가 retryAfter 힌트와 함께 429 Too Many Requests를 반환합니다.
클라우드 의존성 없음
코드가 Mac을 떠나지 않습니다. Tactic Remote는 제어 플레인이지 데이터 플레인이 아닙니다. WebSocket은 터미널 텍스트 — 명령 입력과 출력 문자열 — 를 운반합니다. 소스 파일, git 히스토리, 빌드 아티팩트는 사용자의 머신에 남습니다.
Cloudflare Tunnel 사용 시 터널이 트래픽을 종단 간 암호화합니다. Cloudflare는 암호화된 WebSocket 프레임을 보지, 코드를 보지 않습니다.
iOS 앱: 터미널 에뮬레이터가 아닙니다
개발 초기에 의식적인 결정을 내렸습니다: iPhone 앱은 멍청한 터미널 에뮬레이터가 아닙니다. 4인치 휴대폰 화면에 풀 80x24 터미널을 우겨넣는 것은 끔찍한 경험입니다.
대신 앱은 다음을 제공합니다:
- 대화형 입력: 터미널 프롬프트가 아닌 텍스트 필드에 Claude에게 메시지를 입력
- 터미널 출력 뷰: ANSI 색상 지원, 자동 스크롤, 핀치 투 줌 글꼴 크기(기본 8pt) 렌더링된 텍스트
- 빠른 액션 버튼: Enter, Escape, Tab, Y, N, Ctrl+C — Claude Code와 상호작용할 때 실제로 필요한 키들
- 세션 피커: tmux 세션 탐색 및 전환
- 경로 브라우저: 작업 디렉토리 설정을 위한 Mac 파일시스템 탐색
- 슬래시 명령:
/help,/clear,/compact,/cost,/model,/status자동완성
재연결 전략
모바일 앱은 적대적 네트워크 환경에서 살아갑니다. 휴대폰이 WiFi와 셀룰러를 오가고, 엘리베이터에 들어가고, 슬립 모드에 들어갑니다. 앱의 재연결 전략이 이 모든 것을 처리합니다:
- 연결 끊김 감지: WebSocket close 이벤트 발동
- 지수 백오프:
min(attempts * 3, 15)초 후 재시도, 최대 5회 - 백그라운드 인식: 앱이 백그라운드되면 재연결 건너뛰기. 포그라운드 복귀 시 재연결 플래그 설정
- 세션 복원: 재연결 시 세션 목록 조회 → 이전 활성 세션 찾기 → 다시 연결 → 0.2초, 0.7초, 1.5초 딜레이로 출력 폴링
재연결 후 단계별 출력 폴링(0.2초, 0.7초, 1.5초)은 tmux가 재연결 후 캡처를 생성하는 데 시간이 필요한 경우를 처리합니다. 즉시 단일 폴링은 종종 빈 내용을 반환합니다.
터미널 버퍼
TerminalBuffer는 500,000자, 10,000줄로 제한된 원형 버퍼입니다. 오버플로 시 앞에서부터 내용이 잘리며, 가장 가까운 줄바꿈 경계에 맞춰 부분적인 첫 줄이 보이지 않도록 합니다.
이 제한은 AttributedString 렌더링이 대규모에서 비용이 크기 때문입니다. 500K 자는 대략 30분의 Claude Code 집중 출력 — 합리적인 세션에 충분하면서 UI 반응성을 유지합니다.
Mac 컴패니언 앱
Mac 컴패니언 앱은 Node.js 서버, Cloudflare 터널, 시스템 설정을 관리하는 메뉴바 앱입니다.
서버 번들링
전체 Node.js 서버가 Mac 앱 안에 번들됩니다. 첫 실행 시 서버 파일을 ~/Library/Application Support/ClaudeCodeRemote/Server/에 복사합니다. 이후 실행에서는 파일을 바이트 단위로 비교하고 변경된 파일만 다시 복사합니다. npm install은 package.json이 변경된 경우에만 다시 실행됩니다.
사용자가 서버를 설치하거나 업데이트하기 위해 터미널을 만질 필요가 없습니다.
의존성 감지
앱이 필요한 도구(Node.js, tmux, Homebrew)를 알려진 경로 먼저 스캔(/opt/homebrew/bin/, /usr/local/bin/)한 후 which로 폴백하여 확인합니다. 의존성이 없으면 설치를 안내하는 설정 화면이 나타납니다.
슬립 방지
서버가 실행 중일 때 앱이 IOPMAssertionCreateWithName을 호출하여 macOS 슬립을 방지합니다. 이는 필수입니다 — 잠자는 Mac은 WebSocket 연결을 서비스할 수 없습니다.
Hook 자동 구성
컴패니언 앱이 ~/.claude/hooks/에 라이프사이클 이벤트를 로컬 서버에 POST하는 셸 스크립트를 작성합니다. 서버 시작 시 자동으로 처리됩니다 — 수동 설정 불필요.
만들지 않은 것
의도적으로 하지 않기로 한 것들:
앱 내 터미널 멀티플렉싱 없음. 서버 측에서는 tmux를 사용하지만 iPhone 앱은 한 번에 하나의 세션만 보여줍니다. 휴대폰 화면의 분할 패인은 읽을 수 없습니다.
파일 편집 없음. 앱이 파일을 탐색할 수 있지만 편집은 불가합니다. Claude Code가 파일 편집을 담당합니다 — 여러분은 리뷰어이지 에디터가 아닙니다. 모바일 코드 에디터를 추가하는 것은 핵심 경험을 저해하는 범위 초과입니다.
오프라인 모드 없음. Mac에 도달할 수 없으면 앱이 마지막 알려진 상태를 보여주지만 유용한 작업을 할 수 없습니다. 그렇지 않은 척하지 않습니다.
원격 데스크톱 폴백 없음. 화면 공유와 VNC가 존재하지만 이 사용 사례에서는 10배 나쁩니다. Tactic Remote는 픽셀이 아닌 구조화된 텍스트를 보냅니다. 이는 더 낮은 대역폭, 더 낮은 지연 시간, 축소된 데스크톱이 아닌 용도에 맞게 설계된 UI를 의미합니다.
빠른 설정
세부 사항을 건너뛰시겠습니까? 실행에 필요한 최소 사항입니다.
Mac에서
- Mac 컴패니언 앱을 설치합니다 tacticremote.com/download에서
- 실행합니다 — 의존성을 자동 설치하고 서버를 시작합니다
- 앱에 표시된 QR 코드를 확인합니다
iPhone에서
- Tactic Remote를 설치합니다 App Store에서
- Mac 앱의 QR 코드를 스캔합니다
- 프로젝트 디렉토리를 가리키는 세션을 생성합니다
- Claude를 시작하고 첫 프롬프트를 전송합니다
원격 접근(홈 네트워크 밖)을 위해서는 Mac 앱 설정에서 Cloudflare Tunnel 토글을 활성화하세요. 앱이 자동으로 공개 URL을 생성합니다.
알림 설정
Mac 앱에서 hook은 서버 시작 시 자동으로 구성됩니다. iPhone에서는 프롬프트가 나올 때 알림 권한을 허용하세요. 끝입니다.
FAQ
iPhone 앱을 닫으면 Claude가 계속 실행되나요?
예. Claude는 Mac의 tmux에서 실행됩니다. iPhone 앱은 그 세션의 창일 뿐입니다. 앱을 닫고 주머니에 넣어도 Claude는 계속 작업합니다. 앱을 다시 열면 부재 중 발생한 모든 것을 볼 수 있습니다.
네트워크가 끊어지면?
iPhone 앱이 지수 백오프로 자동 재연결합니다(약 45초 동안 최대 5회 시도). 재연결 시 이전 세션에 다시 연결하고 축적된 출력을 가져옵니다. 작업이 손실되지 않습니다 — tmux가 Mac 측에서 모든 것을 버퍼링합니다.
배터리 사용량은 얼마나 되나요?
iPhone 앱은 지속적인 백그라운드 연결을 유지하지 않습니다. 백그라운드에서 깔끔하게 연결을 해제합니다. 적극적으로 사용하지 않을 때 배터리 영향은 사실상 제로입니다.
다른 AI 코딩 도구와 사용할 수 있나요?
Tactic Remote는 Claude Code의 터미널 인터페이스를 중심으로 구축되었지만, 기반 아키텍처(tmux 세션 관리, 터미널 스트리밍)는 모든 터미널 기반 도구에서 작동합니다. 터미널에서 실행되면 서버가 관리할 수 있습니다. 상태 감지 휴리스틱은 Claude Code 전용이지만, 기본 send-keys/capture-pane 루프는 범용적입니다.
내 코드는 안전한가요?
코드는 Mac을 절대 떠나지 않습니다. Tactic Remote는 암호화된 WebSocket 연결을 통해 터미널 텍스트를 전송합니다. 서버에는 클라우드 컴포넌트, 텔레메트리, 분석이 없습니다. Cloudflare Tunnel 사용 시 트래픽은 종단 간 암호화됩니다 — Cloudflare는 소스 코드가 아닌 암호화된 프레임을 프록시합니다.
특정 서브시스템을 더 깊이 파고들려면 tmux를 세션 백본으로 선택한 이유, 실시간 터미널 스트리밍 구축, 보안 모델 문서를 참조하세요.