🇰🇷 한국어 · 🇺🇸 English
Tailscale 네트워크 너머에서 cmux 터미널을 iPhone으로 조작하는 비공식 원격 클라이언트.
cmux Remote는 Mac에서 돌아가는 cmux의 작업공간과 터미널을 iPhone에서 읽고 조작할 수 있게 해주는 SwiftUI 앱 + Swift 데몬 묶음입니다. 모든 트래픽은 사용자의 Tailscale tailnet 안에서만 흐르며, 공용 인터넷으로 노출되는 포트는 없습니다.
이 프로젝트는 Manaflow가 만들거나 공식 지원하는 결과물이 아닙니다. cmux와 문서화된 JSON-RPC 프로토콜로만 통신하는 독립 네트워크 클라이언트.
v1.0.5 이후 새로 추가되거나 바뀐 점:
- 🔔 네이티브 푸시 알림 (APNs) — cmux 이벤트와 Claude/Codex 계열
needs input프롬프트를 relay가 APNs로 직접 보냅니다. relay에apns블록을 설정하면 앱이 백그라운드·종료 상태여도 배너가 도착하고, 설정하지 않으면 기존 로컬 알림으로 자동 폴백합니다. - ⌨️ Ctrl-C 단축키 — 실행 중인 명령을 끊을 수 있도록 터미널 키보드 바에 전용 Ctrl-C 키를 추가했습니다.
- 🖼️ App Store 스크린샷 5장 전체 교체 — 최신 워크스페이스 · 터미널 · 키보드 · Inbox · 설정 화면을 반영했습니다.
![]() 작업공간 / surface 칩바 |
![]() 터미널 실시간 미러링 |
![]() 키 액세서리 바 · Ctrl-C |
![]() 알림 Inbox · 푸시 |
![]() 설정 · 페어링 가이드 |
1.0.5 이하 변경 내역은 아래 변경 이력 참고.
얼리 프리뷰 (v1.0.6). 다음이 됩니다:
- cmux 작업공간 / surface 목록 보기, 생성, 이름 변경, 닫기
- 임의의 터미널 surface를 실시간 미러링 (15Hz diff 폴링, 120줄 bounded history)
- 키 입력 / 키 조합 / 텍스트 / 커맨드 라인 / LIVE 즉시 입력 전송 · Ctrl-C 단축키
- iPhone 클립보드 붙여넣기 + 사진 첨부 경로 삽입
- 연결된 MacBook 배터리 상태 표시
- cmux 알림과 Claude/Codex 계열
needs input이벤트를 iOS Inbox + 로컬 알림 또는 APNs 푸시로 표시 - 입력 패널을 화면 하단에 붙이고, 가려진 터미널 줄을 끌어올릴 수 있도록 하단 스크롤 여유 추가
- 마우스 모드 TUI 탭 입력 (Textual / Bubble Tea / fzf / omx 등)
- pane 포커스 자동 고정 + 이전 pane 토글
macOS 14 + iOS 17 실기기 + 시뮬레이터에서 같은 Wi-Fi와 Tailnet 환경에서 스모크 테스트했습니다 (Tailscale 1.84+).
알림 전달 — 1.0.6부터 relay에
apns블록을 설정하면 앱이 종료/장시간 백그라운드 상태여도 APNs 푸시로 배너가 도착합니다. APNs를 설정하지 않으면 기존처럼 앱이 foreground이거나 백그라운드에서 WebSocket이 살아있는 동안만 뜨는 로컬 알림으로 동작합니다. (아래 설정의apns블록 참고.)
최근 변경을 최신순으로 요약합니다. 형식:
날짜 · 버전/범위 · 요약. 범위 —app(iOS 앱) ·relay(Mac 데몬) ·setup(설치/문서). 버전별 App Store 상세 노트는docs/launch-assets/release-notes/에 있습니다.
- 2026-06-23 · v1.0.6 (app + relay) — 네이티브 APNs 푸시 알림(relay
apns블록 설정 시 앱 종료 상태에도 배너 도착, 미설정 시 로컬 알림 폴백), 터미널 키보드 바 Ctrl-C 단축키, App Store 6.9" 스크린샷 5장 전체 교체. - 2026-06-06 · relay — cmux 1.0.5가 Unix 소켓을
~/Library/Application Support/cmux에서~/.local/state/cmux로 이전한 것에 대응.cmuxSocketPath()가 마커를 최신순(/tmp/cmux-last-socket-path→~/.local/state/cmux/last-socket-path→ 레거시 Application Support)으로 추적하고, 없으면~/.local/state/cmux/cmux.sock로 폴백. iOS 앱 무변경 → App Store 재제출 불필요. - 2026-06-05 · v1.0.5 (app) — LIVE 즉시 입력 모드(문자 단위 즉시 전송), 한글 IME 보호(조합 중 자모 분리 방지), 입력 패널 하단 flush, 터미널 하단 스크롤 여유 5줄,
needs inputInbox 커버리지 개선. - 2026-06-05 · setup — relay 설치 스크립트 foolproof화 + 연결 가이드(
docs/connection-guide.md) 추가. - 2026-05-29 · v1.0.4 (app) — 파싱 row/style run 캐싱으로 터미널 렌더링 가속, 120줄 bounded history, 256색/트루컬러 ANSI, checksum 정합 개선.
- 2026-05-28 · v1.0.3 (relay) — cmux가 소켓을 rotation할 때 stale 소켓을 붙잡아 "Connection refused"가 반복되던 문제 수정, 재설치 시 기본 동적 소켓 탐색, 소켓 경로 회귀 테스트 추가.
- 2026-05-24 · v1.0.2 (app) — 모바일 키보드 동작 개선, 작업공간 생성/이름변경/닫기, 이미지 첨부, 연결 컴퓨터 배터리 상태, Inbox 개선.
cmux는 AI 코딩 에이전트를 굴리기에 훌륭한 Mac 네이티브 터미널이지만, 책상을 떠나는 순간 모든 진행이 화면 너머로 사라집니다. cmux Remote는 같은 작업공간에 얇은 유리창을 하나 더 붙여서, 소파에서, 지하철에서, 카페에서도 Mac이 하고 있는 일을 확인하고 키 입력으로 끼어들 수 있게 합니다. 일은 여전히 Mac이 다 하고, iPhone은 그저 원격 조종기.
iPhone (iOS 17+) Tailscale Mac
┌─────────────────────┐ ┌────────────────────────────────┐
│ cmux Remote (앱) │── HTTP + WS ─────────▶│ cmux-relay (Swift, launchd) │
│ · 작업공간 목록 │ (Tailscale가 암호화)│ · HTTP/1.1 라우트 │
│ · 터미널 미러 │ │ · /v1/stream WebSocket │
│ · 액세서리 키바 │◀── events.stream ─────│ · DiffEngine (15Hz 폴링) │
│ · 로컬 알림 │ │ · Tailscale whois 인증 │
└─────────────────────┘ │ · 디바이스 토큰 + Rate Limit │
└─────────────┬──────────────────┘
│ Unix socket
│ JSON-RPC
▼
┌────────────────────────────────┐
│ cmux.app │
│ ~/.local/state/cmux/cmux.sock │
└────────────────────────────────┘
설치는 두 파트:
cmux-relay— cmux와 같은 Mac에서 도는 Swift 데몬. cmux의 로컬 Unix 소켓에 JSON-RPC로 붙고, tailnet 인터페이스에 HTTP+WebSocket을 띄웁니다. TLS는 Tailscale의 WireGuard 전송이 담당.- cmux Remote (iOS) — SwiftUI 앱. 사용자 본인의 relay에만 붙고, 외부 네트워크로 나가는 호출은 없습니다.
cmux 소스 코드는 이 저장소에 포함되지 않습니다. 문서화된 JSON-RPC 스키마로만 cmux와 통신합니다.
- 작업공간 / 터미널 surface 목록
- 워크스페이스 생성 시 입력한 제목을 cmux
workspace.create의title로 반영 - 워크스페이스 이름 변경 (
workspace.rename) / 닫기 (workspace.close) - 칩바에서 surface 생성 / 닫기 (확정 다이얼로그 포함)
- 작업공간 전환 / surface 전환 시 자동 재구독 + 하단 자동 스크롤
- 첫 RPC 게이트 (
CMUXClient.awaitReady) — inbound bridge 설치 race 방지
- 15Hz diff 폴링 + 풀텍스트 fallback + checksum reconcile
- 120줄 bounded history로 surface 전환/새로고침 시 갑작스러운 24줄 축소 완화
- 입력 액세서리에 가려진 줄을 완전히 볼 수 있도록 터미널 하단 5행 스크롤 여유 추가
- 렌더 row/style-run 캐시 + Canvas run 단위 그리기로 대형 화면에서도 빠른 스크롤/줌 유지
- Tokyo Night Storm ANSI 팔레트, 256색/truecolor ANSI 렌더링 준비 + CRT 스캔라인 셰이더
- 동아시아 와이드 글리프 폭 계산
- iOS 컬러 이모지 자동 승격 차단 (●, ⏺, ✔, ▶ 등에 VS-15 적용)
- 액세서리 바:
escOK/$tab← ↑ ↓ →/newspace - LIVE 입력 모드 — 제출 버튼 없이 글자 단위로 즉시 터미널 전송
- 키보드 닫기 / 백스페이스 / iPhone 클립보드 붙여넣기 / 사진 첨부 버튼
- 커맨드 컴포저 — 텍스트 입력 + 엔터 묶음 전송, 전송 후 키보드 자동 닫힘
- 한글 IME 입력은 LIVE 즉시 전송에서 제외해 자모가 분리되지 않도록 로컬 조합 유지
- 사진 첨부는 iPhone 이미지를 Mac의
~/Downloads/cmux-remote/로 저장하고 입력창에 저장 경로를 삽입 surface.send_key는NSEventsynth — 화살표/Ctrl 조합 등 멀티바이트 시퀀스가 atomic하게 전달. Ink 기반 TUI (Claude Code 등)의 ESC 파서 타임아웃 문제 해결.- 포커스 게이트 — 구독 / 재구독 / 매 sendKey 직전
surface.focus자동 호출. 책상에서 cmux 포커스를 옮긴 뒤에도 iPhone 키가 의도한 surface로 도착.
- cmux events.stream의 notification을 iOS 로컬 알림으로 표시
(
UNUserNotificationCenter, threadIdentifier로 작업공간별 그룹핑) - 권한은 lazy 요청, 부팅 시 한 번 prewarm
- 중복 ID 가드 — 재연결로 같은 알림이 두 번 와도 한 번만 배너
- Inbox 화면 — 최근 200건 보관, 가장 최근 먼저
- Claude/Codex 계열
needs input,needs attention, 승인 요청 이벤트를 일반 cmux 알림과 같은 Inbox 항목으로 승격 - relay에
apns블록을 설정하면 cmux 이벤트와needs input을 APNs 푸시로 전달 — 앱이 종료된 상태에도 도달, 미설정 시 로컬 알림 폴백 - 딥링크
cmux://surface/<id>처리 (푸시 페이로드 딥링크 자동 오픈은 v1.1 예정) - Settings의
SEND TEST NOTIFICATION버튼 — 로컬 inject 즉시 확인 + relay→cmux→events.stream 라운드트립 별도 상태 라인
- HTTP/1.1 + WebSocket upgrade (
SwiftNIO) - JSON-RPC 2.0 dispatch
- DiffEngine — actor 기반, per-device FPS 예산, row 단위 diff
- 인증: Tailscale UDS
whois(foreground) / GUI fallback - 디바이스 토큰: hashed bearer, 메뉴바에서 개별 revoke
- per-device rate limiter + boot_id 기반 reset 브로드캐스트
- launchd 유저 에이전트로 자동 시작, PATH 주입으로
tailscaleCLI 발견 - MacBook 배터리 상태 조회 (
host.battery) 및 iPhone 헤더 배지 표시 - iPhone 사진 업로드를 Mac의
~/Downloads/cmux-remote/안에만 저장 (file.upload) - events.stream 전용 cmux UDS 채널 분리 (구독 채널은 push-only lock)
- APNs 푸시 fanout (
APNsProvider) — 디바이스 토큰별 알림 전송, relay에apns미설정 시 비활성
- Relay는 0.0.0.0에 바인딩하되 비-Tailscale 소스 주소를 애플리케이션
레이어에서 거부 (
EndpointPolicy) - 디바이스별 토큰 + 메뉴바 revoke
- 알림 페이로드에는 터미널 내용 미포함 (작업공간/surface id + 짧은 제목만)
- 텔레메트리 없음, 분석 없음, 서드파티 네트워크 호출 없음
- macOS 13 Ventura 이상
- cmux 설치 + 소켓 노출
(기본
~/.local/state/cmux/cmux.sock) - Swift 5.10 툴체인 (Xcode 15.3+) — 소스에서 빌드용
- Tailscale 로그인 상태
- 비어있는 TCP 포트 (기본
4399)
- iOS 17 이상
- Mac과 같은 Tailnet (Tailscale 앱 로그인)
- 사이드로딩용 Apple Developer 계정 (개인 무료 7일 인증서로도 가능)
- Tailscale 1.84+ 양쪽
- Funnel 불필요, 외부 hostname 불필요
연결이 안 되나요? 처음 설치하거나 "연결할 수 없음"이 뜨면 누구나 따라 할 수 있는 단계별 설치 + 문제 해결 안내를 보세요 → 연결 가이드 (docs/connection-guide.md)
relay는 cmux가 도는 그 Mac에서 함께 돌아야 합니다. 아래 3가지를 먼저 확인하세요:
cmux --version # cmux 설치 + 실행 중이어야 함
tailscale status # Tailscale 로그인 + 온라인이어야 함
swift --version # Swift 5.10+ (Xcode 15.3+) 빌드용- cmux가 실행 중이어야 relay가 소켓에 붙습니다. (꺼져 있으면
socketMissing) - iPhone과 Mac이 같은 Tailnet에 로그인돼 있어야 합니다.
git clone https://github.com/NewTurn2017/cmux-remote.git
cd cmux-remote
# launchd 유저 에이전트로 빌드 + 설치 (로그인 시 자동 시작)
# 스크립트가 swift build -c release를 알아서 실행합니다.
./scripts/install-launchd.sh설치 스크립트는 릴리스 바이너리를 빌드해 ~/.cmuxremote/bin/로 복사하고,
~/.cmuxremote/relay.json 기본 설정이 없으면 자동 생성하며,
~/Library/LaunchAgents/com.genie.cmuxremote.plist를 렌더링한 뒤
서비스를 부트스트랩합니다. 로그는 ~/.cmuxremote/log/로 떨어집니다.
# 헬스 체크 — Mac에서 자기 Tailscale IP로 두드리기
curl -s http://$(tailscale ip -4):4399/v1/health
# {"ok":true,"version":"0.1.0"} ← 이게 나오면 relay 정상
# cmux 소켓에도 붙었는지 점검
./scripts/cmux-probe.sh
# {"id":"probe-1","result":{...}}응답이 없거나 비정상이면 로그부터 봅니다:
tail -n 40 ~/.cmuxremote/log/stderr.logstarting cmux-relay on 0.0.0.0:4399 → listening … →
cmux event stream attached 3줄이 보이면 정상입니다.
안 보이면 아래 연결이 안 될 때 섹션으로.
먼저 Mac의 주소를 확인하세요:
tailscale ip -4 # 예: 100.x.y.z ← 이 IP를 앱에 입력
tailscale status # MagicDNS 이름(예: my-mac)을 쓰고 싶을 때iPhone에서 cmux Remote 열기:
- Add Mac 탭
- 위에서 확인한 Tailscale IP 또는 MagicDNS 이름 입력, 포트는
4399 - Add — relay가 Tailscale 신원을 확인하고 페어링합니다
relay는 자기 Mac의 tailnet 로그인을 자동으로 허용하므로, iPhone이 같은
Tailscale 계정이면 보통 추가 설정 없이 바로 붙습니다. 다른 계정이거나
relay가 태그 노드로 도는 경우에만 아래 설정의 allow_login에 본인
로그인을 직접 추가하세요(그 외 로그인은 403 Forbidden). 페어링 시
디바이스별 토큰이 발급되며
~/.cmuxremote/bin/cmux-relay devices revoke <id>로 언제든 해지할 수 있습니다.
- Workspaces — 작업공간 목록. 탭하면 surface 칩바가 펼쳐짐. 여기서 작업공간 생성, 이름 변경, 닫기도 처리.
- Terminal — 탭한 surface가 미러링. 하단 액세서리 바로 키 입력. 키보드 줄, esc / 화살표 / tab / 마우스 모드 / pane 토글 다 거기.
- Notifications — cmux 알림 Inbox. 앱이 살아있을 때 도착한 알림이 시간순으로 쌓입니다. iOS 배너도 같이 떠요 (포그라운드/짧은 백그라운드).
- Settings — 호스트/포트, 재연결, 테스트 알림 발사.
Relay는 ~/.cmuxremote/relay.json을 읽습니다. 파일이 없으면
install-launchd.sh가 아래 기본값으로 자동 생성합니다 (기존 파일은
건드리지 않음):
{
"listen": "0.0.0.0:4399",
"default_fps": 15,
"idle_fps": 5
}listen은 0.0.0.0이지만 비-Tailscale 소스 주소는 애플리케이션
레이어에서 차단됩니다. 개발 중 localhost를 허용하려면
CMUX_DEV_ALLOW_LOCALHOST=1 환경 변수로 install 스크립트를 돌리세요.
생략한 키는 위 기본값으로 채워지므로 위 3줄짜리 설정만으로도 relay는
정상 부팅합니다. 페어링은 allow_login에 등록된 tailnet 로그인의 기기만
허용하지만(나머지는 403 Forbidden), relay가 자기 Mac의 로그인을 자동으로
추가하므로 같은 Tailscale 계정의 iPhone은 보통 비워둬도 붙습니다. 자동
허용을 끄려면 CMUX_NO_SELF_LOGIN=1로 install 스크립트를 돌리세요.
다른 계정의 기기를 붙이거나 relay가 태그 노드로 도는 경우에만 로그인을 직접
추가합니다. 본인 로그인 값은 Tailscale 관리 콘솔, 또는
tailscale status --json의 Self.UserID가 가리키는 User[…].LoginName
에서 확인할 수 있습니다 (예: [email protected]). 이 값을 넣고 relay를
재시작하세요:
{
"listen": "0.0.0.0:4399",
"allow_login": ["[email protected]"],
"default_fps": 15,
"idle_fps": 5
}launchctl kickstart -k "gui/$(id -u)/com.genie.cmuxremote"cmux Unix socket은 기본적으로 cmux가 쓰는 last-socket-path 마커를
최신 규칙부터 따라갑니다: 고정 경로 /tmp/cmux-last-socket-path →
~/.local/state/cmux/last-socket-path → 레거시
~/Library/Application Support/cmux/last-socket-path. 어느 것도 없으면
~/.local/state/cmux/cmux.sock로 폴백합니다. cmux 재시작으로
cmux-501.sock처럼 socket 이름이 바뀌거나, cmux 업데이트가 소켓을
~/Library/Application Support/cmux에서 ~/.local/state/cmux로 옮겨도
relay가 stale socket에 묶이지 않도록 하기 위함입니다. 고정 경로가 꼭
필요한 운영 환경에서만 CMUX_SOCKET_PATH=/path/to/socket으로
install 스크립트를 실행하세요.
APNs 푸시 (
apns블록). relay.json에apns블록을 넣으면 cmux 알림이 APNs 푸시로 전달돼 앱이 종료된 상태에서도 도착합니다. 블록이 없으면 알림은 로컬 알림으로만 표시됩니다.{ "apns": { "key_path": "~/.cmuxremote/AuthKey_XXXXXXXXXX.p8", "key_id": "XXXXXXXXXX", "team_id": "XXXXXXXXXX", "topic": "com.genie.CmuxRemote", "env": "prod" } }
env는 개발/사이드로드 빌드면"sandbox", App Store/배포 빌드면"prod".
relay는 launchd 유저 에이전트(com.genie.cmuxremote)로 돌아갑니다.
RunAtLoad + KeepAlive라 로그인 시 자동 시작되고 죽으면 다시 떠요.
SERVICE="gui/$(id -u)/com.genie.cmuxremote"
# 재시작 (재빌드 없이 — 가장 자주 씀)
launchctl kickstart -k "$SERVICE"
# 상태 (state / pid / last exit code)
launchctl print "$SERVICE" | grep -E "state|pid|last exit"
# 실시간 로그
tail -f ~/.cmuxremote/log/stderr.log
# 일시 중지 (KeepAlive 때문에 bootout 사용)
launchctl bootout "$SERVICE"소스를 바꿔 새 바이너리를 반영하려면 빌드 → 복사 → plist 렌더 → bootstrap + kickstart를 한 번에 처리하는 설치 스크립트를 다시 돌립니다:
./scripts/install-launchd.sh # swift build -c release 포함
./scripts/uninstall-launchd.sh # bootout + plist 제거정상 기동 시 stderr.log에 starting cmux-relay on 0.0.0.0:4399 →
listening … → cmux event stream attached 순으로 찍힙니다.
cmux event stream unavailable: socketMissing가 보이면 cmux 앱부터
켜고 relay를 kickstart 하세요. Connection refused가 반복되면 relay가
stale socket에 묶인 것 — install 스크립트를 다시 실행해 최신 바이너리로
재설치하면 새 마커 경로(/tmp/cmux-last-socket-path →
~/.local/state/cmux)를 자동으로 따라갑니다. 급하면
cat /tmp/cmux-last-socket-path로 라이브 소켓을 확인해 CMUX_SOCKET_PATH로
핀하세요.
iPhone 앱에서 연결이 안 되면, Mac에서 아래 순서로 한 줄씩 확인하세요. 대부분 1~2번에서 해결됩니다. 자세한 단계별 안내와 사용자에게 그대로 전달할 수 있는 안내문은 연결 가이드 참고.
SERVICE="gui/$(id -u)/com.genie.cmuxremote"| 확인 | 명령 | 안 되면 |
|---|---|---|
| ① cmux 실행 중? | cmux --version |
cmux 앱을 켜고 → launchctl kickstart -k "$SERVICE" |
| ② relay 떠 있나? | curl -s http://$(tailscale ip -4):4399/v1/health |
launchctl kickstart -k "$SERVICE", 그래도 안 되면 ./scripts/install-launchd.sh 재실행 |
| ③ 로그 정상? | tail -n 40 ~/.cmuxremote/log/stderr.log |
아래 로그별 대응 참고 |
| ④ Tailscale 양쪽 온라인? | tailscale status |
Mac·iPhone 둘 다 같은 Tailnet 로그인 확인 |
| ⑤ 앱 주소 맞나? | tailscale ip -4 |
앱에 이 IP + 포트 4399 입력했는지 확인 |
로그별 대응:
cmux event stream unavailable: socketMissing— cmux가 꺼져 있음. cmux 앱을 켜고launchctl kickstart -k "$SERVICE".Connection refused반복 — 소켓 경로가 바뀜(cmux 재시작으로 이름이 바뀌었거나, 업데이트가 소켓을~/.local/state/cmux로 옮김). 최신 relay는 마커를 자동 추적하니./scripts/install-launchd.sh로 재설치하면 해결. 급하면cat /tmp/cmux-last-socket-path의 경로를CMUX_SOCKET_PATH로 핀.- 헬스 체크는 OK인데 앱만 못 붙음 — 네트워크/주소 문제. iPhone과
Mac이 같은 Tailnet인지, 앱 주소·포트(
4399)가 맞는지, 디바이스 토큰이 revoke되지 않았는지(.build/release/cmux-relay devices list) 확인.
cmux를 자주 재시작한다면, 소켓 회전 후 relay를 다시 붙이는 가장 빠른 방법은
launchctl kickstart -k "$SERVICE"입니다.
- v1.0 — 작업공간 목록, surface 생성/닫기, 터미널 미러, 키 입력, 마우스 모드, pane 토글, 로컬 알림, Tokyo Night Storm UI
- v1.0.2 — 키보드 레이아웃 안정화, 사진 첨부, MacBook 배터리 배지,
needs inputInbox, 워크스페이스 생성/이름변경/닫기 - v1.0.3 — 실기기 relay socket 회전/재연결 안정화
- v1.0.4 — 터미널 렌더링 성능 최적화, 120줄 history, ANSI 256색/truecolor 렌더링 기반, 실기기 live relay 스모크 검증
- v1.0.5 — LIVE 입력 모드, 한글 IME 보호, 하단 고정 입력 패널, 터미널 5행 하단 스크롤 여유, Claude/Codex Inbox 회귀 테스트
- v1.0.6 — 네이티브 APNs 푸시 알림(앱 종료 상태 도달), Ctrl-C 단축키, App Store 스크린샷 5장 교체
- v1.1 — 푸시 후속 — 푸시 페이로드 → 딥링크로 surface 자동 오픈, 전달 신뢰성/재시도 강화
- v1.2 — iPad 레이아웃, 외장 키보드 폴리시
- v1.3 — cmux "open in pane" 인텐트용 파일 프리뷰
- v2.0 — 고빈도 TUI(vim, htop, k9s) 대상 바이트스트림 RPC
- 혹시 — Android 클라이언트 (PR 환영,
docs/specs/참고)
명시적 비목표: 공용 인터넷 노출(Tailscale Funnel), 멀티유저 공유, 라이브 세션 외부의 서버측 영속 저장.
cmux-remote/
├─ README.md / README.en.md
├─ LICENSE
├─ docs/
│ ├─ screenshots/ # README용 스크린샷
│ └─ specs/ # 설계 문서, 결정 RFC
├─ Package.swift # SharedKit / CMUXClient / RelayCore / cmux-relay
├─ Sources/
│ ├─ SharedKit/ # Codable 모델, JSON-RPC 봉투, 키 테이블, 스크린 해셔
│ ├─ CMUXClient/ # cmux UDS JSON-RPC 클라이언트 (Mac 전용)
│ ├─ RelayCore/ # Auth, Session, DiffEngine, RowState, DeviceStore
│ └─ RelayServer/ # @main, NIO HTTP+WS, launchd 엔트리
├─ Tests/ # 유닛 + 통합 테스트
├─ ios/
│ ├─ CmuxRemote.xcodeproj
│ └─ CmuxRemote/
│ ├─ CmuxRemoteApp.swift / ContentView.swift
│ ├─ Network/ # RPCClient, WSClient, AuthClient, EndpointPolicy
│ ├─ Notifications/ # LocalNotificationPresenter, NotificationCenterView
│ ├─ Stores/ # WorkspaceStore, SurfaceStore, NotificationStore, HostStatusStore
│ ├─ Terminal/ # CellGrid, ANSIParser, TerminalView, 셀폭 계산
│ ├─ Workspace/ # WorkspaceListView, WorkspaceDrawer, WorkspaceView
│ ├─ Settings/ # SettingsView
│ ├─ Keyboard/ # CommandComposer
│ ├─ UI/ # Tokyo Night 테마, 스플래시, Metal 셰이더
│ ├─ Security/ # HardeningCheck
│ └─ Storage/ # Keychain
└─ scripts/
├─ install-launchd.sh # cmux-relay launchd 설치
├─ uninstall-launchd.sh
├─ relay.plist.tmpl
├─ cmux-probe.sh # cmux 소켓 핑
├─ smoke-relay.sh # tailnet end-to-end 스모크
└─ evaluate-terminal-keyboard.sh
내부 식별자는 camelCase
CmuxRemote(Xcode 타깃, Swift 모듈, 번들 IDcom.genie.CmuxRemote). 홈스크린 표시 이름은 공백을 둔 cmux Remote. 양쪽 다 정상.
# Swift 테스트 전체 (relay + shared kits)
swift test
# iOS 앱 Xcode 프로젝트 생성
cd ios && xcodegen generate
# 시뮬레이터에서 iOS 테스트 (Fake RPC 디스패치)
xcodebuild test -project CmuxRemote.xcodeproj \
-scheme CmuxRemote -destination 'platform=iOS Simulator,name=iPhone 15'
# 실제 cmux + Tailscale 풀스택 스모크 (느림, 에페메럴 노드 사용)
SMOKE_EPHEMERAL=1 ./scripts/smoke-relay.sh스모크 스크립트는 임시 Tailscale 노드 + 격리된 config 디렉토리를
띄우고, 가짜 디바이스를 등록한 뒤 문서화된 모든 relay 엔드포인트
(/v1/health, /v1/devices/me/register, /v1/state,
/v1/devices/me/apns, WebSocket hello, workspace.list,
surface.list, surface.subscribe, screen.diff,
screen.checksum)를 차례로 두드립니다. relay 와이어 포맷을
건드릴 때 유용.
iOS 앱은 FAKE_RPC=1 (DEBUG 빌드 기본값) 또는 시뮬레이터에서
FakeRPCDispatch를 사용해 relay 없이도 빌드 + UI 테스트가
돌아갑니다.
이슈와 PR 환영합니다. 몇 가지 규칙:
- PR 하나에 기능 하나. diff는 작게.
- 테스트 추가/갱신. relay는 단위 커버리지가 있고, iOS는 fake-relay 디스패치로 UI 테스트가 돕니다.
- cmux 소스를 이 저장소에 붙여넣지 마세요. 라이선스 분리 유지가 중요 (아래 참고).
- 버그 리포트에는 relay 로그 + cmux 버전 (
cmux --version)을 같이.
더 큰 아이디어(새 transport, 새 auth 모델, 바이트스트림 RPC 등)는
discussion을 열거나 docs/specs/에 디자인 문서를 먼저 올려주세요.
- Relay는 tailnet 인터페이스만 받아들입니다 — 비-Tailscale 소스 주소는
애플리케이션 레이어에서 거부 (개발용 localhost 허용은
CMUX_DEV_ALLOW_LOCALHOST=1로만). - iPhone마다 페어링 시 발급된 토큰을 가집니다. 메뉴바에서 개별 revoke.
- 알림 페이로드에는 터미널 내용이 포함되지 않습니다 — workspace/surface id + 짧은 title만.
- 텔레메트리 / 분석 / 서드파티 네트워크 호출 없음.
보안 이슈는 이슈 트래커에 공개로 올리지 말고 SECURITY.md의 메인테이너
이메일로 알려주세요.
cmux Remote는 MIT 라이선스 — LICENSE 참조.
cmux는 © Manaflow, Inc., GPL-3.0-or-later 또는 상용 라이선스로 듀얼 라이선스됩니다. cmux Remote는 독립 네트워크 클라이언트입니다. cmux 소스 코드를 포함하거나, 링크하거나, 수정하지 않습니다. 통신은 전적으로 문서화된 JSON-RPC 프로토콜을 통해서만 이루어집니다. Free Software Foundation은 GPL 프로그램과 문서화된 네트워크 프로토콜을 통해서만 상호작용하는 프로그램은 그 프로그램의 파생 저작물이 아니라는 일반적 입장을 가지고 있으며, cmux Remote는 이에 근거해 배포됩니다.
"cmux"는 Manaflow, Inc.가 자사 터미널 제품을 식별하기 위해 사용하는 이름입니다. cmux Remote는 이 클라이언트가 상호운용되도록 설계된 소프트웨어를 식별하기 위한 기술적 묘사 용도로만 이 이름을 사용합니다. cmux Remote는 Manaflow, Inc.와 제휴, 후원, 추천 관계가 아닙니다. Manaflow 측에서 이름 변경을 요청하시면 이슈를 열어주세요 — 군말 없이 이름을 바꾸겠습니다.





