phone-harness: Xcode나 WebDriverAgent 없이 AI 에이전트가 실제 아이폰을 조작하는 도구

phone-harness 소개

아이폰에서만 되는 일이 아직 많습니다. iOS 전용 앱, 사용자의 전화번호에 묶인 2단계 인증, 실제 기기에서만 확인되는 화면 배치 같은 것들입니다. 그런데 AI 에이전트에게 이 일을 맡기려면 준비물이 갑자기 무거워집니다. 실기기 iOS 자동화는 보통 Xcode 프로젝트를 만들어 서명하고 WebDriverAgent 같은 자동화 서버를 기기에 올리는 과정을 거치며, 그마저도 인증서가 만료되면 다시 손봐야 합니다. 에이전트에게 시키고 싶은 일은 "메모 앱을 열고 한 줄 적어 줘" 정도인데, 그 앞에 놓인 설정이 본 작업보다 큽니다.

phone-harness는 이 준비물을 통째로 건너뛰는 방향을 택한 도구입니다. macOS Sequoia 이상에서 제공되는 iPhone 미러링 창 하나를 통로로 삼아, 맥에 연결된 실제 아이폰을 AI 에이전트가 직접 조작하게 합니다. 미러링 창은 아이폰 화면을 맥의 창으로 띄우고 맥의 마우스·키보드 입력을 실제 터치로 전달하는데, phone-harness는 여기에 눈과 손을 더합니다. 눈은 창을 캡쳐해 Vision 프레임워크로 글자를 읽는 OCR(광학 문자 인식, Optical Character Recognition)이고, 손은 HID(Human Interface Device) 계층에 직접 이벤트를 넣는 CGEvent입니다. 저장소는 이 구조를 두고 탈옥도, Xcode도, WebDriverAgent도 필요 없다고 밝히고 있습니다.

phone-harness는 Python으로 작성되어 있고, 저자는 핵심부를 src/phone_harness/ 아래 약 500줄 규모로 유지한다고 설명합니다. 미러링 통로 자체가 상태를 갖지 않아서 창 위치와 화면 캡쳐를 호출할 때마다 다시 조회하며, 그래서 백그라운드에 상주하는 데몬이 없고 매 실행이 독립적으로 끝납니다. 부족한 기능은 에이전트가 실행 도중 agent-workspace/agent_helpers.py에 직접 써 넣도록 열어 두었고, 이 파일은 모든 스크립트의 이름 공간에 자동으로 불러들여집니다.

phone-harness가 아이폰을 조작하는 3단계

phone-harness의 동작은 보기, 조작, 확인 세 단계가 반복되는 형태입니다:

보기 단계에서는 미러링 창만 잘라 캡쳐한 뒤 Vision 프레임워크로 글자를 인식합니다. ocr()는 화면에 보이는 문자열마다 {text, confidence, x, y, w, h} 형태로 신뢰도와 좌표를 함께 돌려주므로, 에이전트는 이미지를 눈으로 훑는 대신 눌러야 할 지점을 좌표로 바로 얻습니다. 저장소는 이 방식을 웹 브라우저의 DOM을 대신하는 "가난한 자의 DOM(The poor man's DOM)" 이라고 표현합니다.

조작 단계는 HID 계층에 CGEvent를 보내는 방식입니다. 탭, 길게 누르기, 드래그와 플릭, 스크롤 제스처, 유니코드 입력이 여기에 해당하고, iPhone 미러링 앱 자체의 단축키(홈은 Cmd+1, 앱 전환기는 Cmd+2, Spotlight는 Cmd+3)도 함께 쓸 수 있습니다. 라벨이 붙은 요소는 tap_text("Weather")처럼 글자로 누를 수 있으며, 실패하면 지금 화면에 무엇이 보이는지를 예외 메시지에 담아 알려 줍니다.

확인 단계는 다시 캡쳐하는 것입니다. 검사할 DOM이 없으므로 캡쳐한 화면 자체가 유일한 근거이고, 저장소는 모든 동작 뒤에 wait_stable()로 화면이 멎기를 기다린 다음 ocr()이나 screenshot()으로 결과를 확인하라고 안내합니다. 목록을 끝까지 훑는 scroll_collect()와 scroll_until()은 종료 조건을 파서가 새 항목을 찾았는지가 아니라 화면이 실제로 움직였는지로 판정하는데, 빽빽한 화면이나 놓친 OCR 한 줄 때문에 스크롤이 일찍 끝나는 것을 막기 위해서입니다.

phone-harness가 흔한 방법 대신 선택한 것들

저장소의 "Why this works" 섹션에는 시행착오 끝에 버린 방법들이 함께 적혀 있습니다. 미러링 창이 사실상 영상 스트림이라는 점이 이 선택들을 갈랐습니다:

흔히 쓰이는 방법 phone-harness의 선택
탈옥, Xcode 프로젝트 서명, WebDriverAgent 설치 맥의 iPhone 미러링 창 하나
접근성 트리(accessibility tree) 조회 창 캡쳐 + Vision 프레임워크 OCR
AppleScript의 click at HID 계층의 CGEvent 주입
유니코드 키 페이로드 전달 원시 HID 키코드로 입력
느린 터치 드래그 목록은 휠 스크롤, 페이지는 빠른 플릭

접근성 트리와 AppleScript가 빠진 이유는 같습니다. 미러링 창 내부는 macOS 접근성 API가 들여다볼 수 없는 영상이라, AppleScript의 click at은 오류도 없이 조용히 무시됩니다. 창이 화면 맨 앞에 있지 않을 때 들어간 입력도 마찬가지로 삼켜지므로, phone-harness의 입력 함수들은 이벤트를 보내기 전에 창을 먼저 활성화합니다.

같은 문제를 다른 각도에서 푼 프로젝트들도 PyTorchKR에 소개된 적이 있습니다. iOS 시뮬레이터를 대상으로 삼는 serve-sim, 자연어 명령으로 Android와 iOS 단말기를 조작하는 mobile-use, 데스크톱에서 OS 접근성 트리를 활용하는 agent-desktop이 그런 예입니다. phone-harness는 이 중 실기기를 대상으로 하면서 접근성 트리 대신 화면 인식에 기대는 쪽에 서 있습니다.

phone-harness 설치 및 사용법

설치에는 macOS Sequoia 이상과 한 번 짝지어 둔 iPhone 미러링, 그리고 Python 3.12 이상이 필요합니다. 짝짓기는 물리적인 기기에서 승인해야 하므로 사용자가 직접 해야 합니다:

git clone https://github.com/ShawnPana/phone-harness ~/.phone-harness
cd ~/.phone-harness
pip install pyobjc-framework-Quartz pyobjc-framework-Vision pyobjc-framework-AppKit
pip install -e . --no-deps

phone-harness --doctor

~/.phone-harness를 표준 위치로 삼는 것은 ~/.oh-my-zsh나 ~/.nvm과 같은 관례를 따른 것으로, 코드와 SKILL.md, 사용자가 만든 agent-workspace/가 어느 기계에서나 에이전트가 아는 경로에 있도록 하기 위한 선택입니다. pip install -e .로 편집 가능한 상태를 유지하면서 phone-harness 명령을 PATH에 올리므로, 폴더를 옮기면 설치를 다시 해야 합니다.

여기에 더해 터미널 앱에 시스템 설정의 손쉬운 사용(탭과 키 입력)과 화면 기록(화면 보기) 권한을 줘야 합니다. 손쉬운 사용은 즉시 적용되지만 화면 기록은 터미널을 다시 시작해야 반영됩니다. 저장소는 이 두 가지가 "현재까지 필요하다고 확인된" 권한이라고 적으면서, 개발이 이미 권한이 열려 있던 기계에서 이뤄졌기 때문에 새 맥에서는 다른 승인 창이 더 뜰 수 있다고 미리 밝혀 두었습니다. --doctor는 통과하는데 탭이나 캡쳐가 조용히 아무 일도 하지 않는다면 권한 창을 확인해 보라는 안내가 함께 있습니다.

실행은 표준 입력으로 파이썬 코드를 넘기는 형태이고, 도우미 함수들은 미리 불러들여져 있습니다:

./phone-harness <<'PY'
open_app("Notes")
tap_text("New Note")
type_text("hello from the harness")
print([o["text"] for o in ocr()][:10])
PY

일상적인 사용법은 SKILL.md에 정리되어 있는데, 이 파일은 install.md가 Claude Code와 Codex에 에이전트 스킬로 등록해 주는 대상이기도 합니다. 등록해 두면 에이전트가 아이폰이 필요한 작업에서 이 도구를 스스로 꺼내 씁니다.

phone-harness의 한계와 안전 장치

저장소는 못 하는 일을 따로 모아 두었습니다. 한 번에 한 대의 아이폰만 다룰 수 있고, 물리적인 아이폰의 잠금을 풀면 미러링 세션이 멈춥니다. 멀티터치가 없어 핀치 제스처를 쓸 수 없고, 카메라와 Face ID 흐름도 다룰 수 없으며, DRM이 걸린 영상은 검게 나옵니다. OCR은 글자를 읽을 뿐 의미를 알지 못하므로, 라벨 없는 아이콘을 누르려면 화면을 캡쳐해 시각을 다루는 모델에게 넘겨야 합니다.

연결 자체를 도구가 처리하지 않는다는 점도 분명히 해 두었습니다. 미러링을 연결하거나 재개하는 일은 사람이 앱을 열고 승인해야 하는 물리적인 동작이라, ensure_mirroring()은 연결이 안 된 상태에서 작업을 시작하지 않고 그 사실을 알리며 멈춥니다. SKILL.md는 에이전트에게 Connect 버튼을 대신 누르거나 연결을 기다리며 반복 조회하지 말고, 사용자에게 알린 뒤 확인을 받고 나서 한 번만 다시 시도하라고 지시합니다.

동의에 관한 지침도 스킬 문서에 들어 있습니다. 대상이 사용자의 실제 전화기이므로, 메시지 전송이나 게시, 결제, 삭제, 설정 변경처럼 외부로 나가거나 되돌리기 어려운 동작 앞에서는 멈추고 물어보라고 못 박고 있습니다. 읽고 이동하는 것은 괜찮지만 메시지·사진·메일 같은 개인적인 내용에 필요 이상으로 머물지 말라는 문장도 함께 있습니다. 같은 문서는 맥이나 웹에서 할 수 있는 일이면 그쪽에서 하고 전화기는 건드리지 말라고도 적고 있어서, 도구의 적용 범위를 저자 스스로 좁혀 둔 편입니다.

phone-harness는 누구에게 유용한가

맥과 아이폰을 함께 쓰면서 iOS 전용 앱이나 전화번호에 묶인 절차를 에이전트에게 맡기고 싶은 사람에게 맞습니다. Xcode 프로젝트와 인증서를 준비하는 대신 미러링 짝짓기와 권한 두 개로 시작할 수 있어서, 자동화 자체가 목적이 아니라 도중에 잠깐 필요한 경우에 특히 부담이 적습니다.

반대로 CI에서 여러 대를 병렬로 실행하는 테스트 자동화라면 맞지 않습니다. 한 번에 한 대만 다룰 수 있고 macOS와 물리적인 아이폰이 함께 있어야 하며, 사람이 기기를 만지는 순간 세션이 멈추기 때문입니다. 시뮬레이터만으로 충분한 작업이라면 실기기를 붙일 이유가 없고, Android가 대상이라면 애초에 다른 도구를 봐야 합니다. 핀치 제스처나 Face ID가 필요한 흐름도 이 도구의 범위 밖입니다.

phone-harness의 라이선스

phone-harness는 MIT 라이선스로 공개되어 있어 개인 및 상업적 목적으로 자유롭게 사용할 수 있습니다.

:house: phone-harness 공식 홈페이지

:github: phone-harness 프로젝트 GitHub 저장소

더 읽어보기




이 글은 GPT 모델로 정리한 초안을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 댓글로 알려주시기를 부탁드립니다. :hugs:

이 도구를 직접 설치해 사용해보셨다면, :pytorch:파이토치 한국 사용자 모임:south_korea: 회원들을 위해 경험이나 팁을 댓글로 남겨주세요! :folded_hands: