TrendShift 딥다이브 · 2026-08-10 · Daily #5

phone-harness 딥다이브
— LLM 에이전트에게 눈과 손을 달아 맥에 미러링된 진짜 아이폰을 직접 조작하게 만드는 얇은 하네스

phone-harness는 macOS의 아이폰 미러링(iPhone Mirroring) 창 하나를 통째로 통로로 삼아, LLM 에이전트가 실제 아이폰을 조작하게 해 주는 파이썬 도구다. 탈옥도, Xcode도, WebDriverAgent 같은 무거운 자동화 서버도 없이 — 화면을 스크린샷으로 찍어 OCR로 "읽고", 맥의 가상 마우스·키보드 이벤트를 "눌러" 아이폰을 움직인다. 핵심 아이디어는 단순하다. 미러링 창은 아이폰 화면을 그대로 비추는 맥 창이고, 이 창에 마우스·키보드를 넣으면 아이폰이 진짜 터치·타이핑으로 받아들인다. 에이전트에게 필요한 것은 그 창을 보는 눈(캡처+OCR)과 만지는 손(가상 입력)뿐이다. phone-harness는 그 최소한만 ~730줄 파이썬으로 감싼다. (저장소: ShawnPana/phone-harness · Python 3.10+ / macOS Sequoia+ · 의존성은 pyobjc 브리지 4종(Quartz·Vision·AppKit·ApplicationServices) · 코어 5파일 ≈771줄(파이썬 총 793줄) · 라이선스 MIT · 최신 커밋 2026-08-07 · TrendShift Daily #5)
목차
  1. 프로젝트 한 줄 요약
  2. 왜 지금 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한 줄 요약

"에이전트에게 눈(스크린샷+OCR)과 손(가상 입력)을 달아, 맥에 비친 아이폰을 대신 조작하게 하는 도구"

phone-harness는 "let your agent control your phone(에이전트가 당신의 폰을 조작하게 하라)"를 표방하는 작은 파이썬 패키지다. 하는 일을 한 문장으로 줄이면 아이폰 미러링 창을 캡처해 OCR로 화면 속 글자와 그 좌표를 읽고, 그 좌표에 가상 터치·타이핑을 쏘아 넣은 뒤, 다시 캡처해 결과를 확인하는 "보기 → 행동 → 검증" 루프를 반복하는 것이다.

구성 요소를 셋으로 묶으면 이렇다. (1) 눈(See)screencapture로 미러링 창만 딱 찍고, 애플 Vision 프레임워크 OCR로 화면 속 모든 문자열을 "탭 가능한 좌표"와 함께 얻는다. (2) 손(Act) — macOS의 CGEvent를 HID(하드웨어) 수준에서 쏘아 탭·롱프레스·드래그(플릭)·스크롤·타이핑, 그리고 앱 단축키(Cmd+1 홈 / Cmd+2 앱 전환 / Cmd+3 스팟라이트)를 낸다. (3) 검증(Verify) — DOM이 없으니 다시 스크린샷을 찍은 "그 화면 자체가 정답"이다.

한 줄 비유

"맥 화면에 띄운 유리 상자 속 아이폰 + 그 유리를 대신 눌러 주는 로봇 손과 눈"

아이폰 미러링은 아이폰 화면을 맥 위 유리 상자(창)에 실시간으로 비춰 준다. phone-harness의 눈은 이 상자를 사진 찍어 "어디에 무슨 글자가 있는지"를 읽고, 손은 그 위치를 눌러 아이폰을 조작한다. 상자 안은 그냥 영상이라 "무엇이 버튼인지" 알려 주는 구조(DOM)가 전혀 없기 때문에, 매번 사진을 다시 찍어 눈으로 확인하는 것이 유일한 정답 확인법이다.

그래서 동작 하나하나가 탭 → 잠깐 대기(wait_stable) → 다시 OCR로 확인의 짧은 사이클로 굴러간다. README의 예시가 이 리듬을 그대로 보여준다.

● agent: "날씨 앱을 열고 싶다" │ ● ocr() → 화면에서 "Weather" 를 (400, 468) 좌표로 찾음 │ ● tap(400, 468) → wait_stable() → ocr() 로 예보가 떴는지 확인 ✓ done

2왜 지금 주목받는가

"컴퓨터를 조작하는 에이전트" 흐름을 폰으로 확장하는데, 설치 부담이 거의 없다

2026년의 화두는 "컴퓨터를 스스로 조작하는 AI 에이전트(computer-use)"다. phone-harness가 TrendShift Daily 상위에 오른 이유는 그 흐름을 데스크톱에서 스마트폰으로 넘기면서도, 기존 모바일 자동화가 요구하던 무거운 설치·탈옥·특수 서버를 통째로 없애 버렸기 때문이다. macOS Sequoia에 새로 들어온 "아이폰 미러링"을 통로로 재활용한 발상이 핵심이다.

배경 지식
iPhone Mirroring (아이폰 미러링)
macOS Sequoia(15)+ 기능으로, 근처의 아이폰 화면을 맥 창에 그대로 띄우고 맥의 마우스·키보드 입력을 아이폰 터치로 전달한다. 애플은 사람이 쓰라고 만든 기능인데, phone-harness는 이 "입력이 터치로 전달된다"는 성질을 에이전트의 통로로 재활용했다.

주목 포인트 1 — 제로 설치: 탈옥·Xcode·WebDriverAgent 없음

기존 iOS 자동화(Appium 등)는 기기에 WebDriverAgent를 심고 개발자 인증서로 서명해 올려야 했다 — 초보자에겐 벽이다. phone-harness는 그 계층을 통째로 건너뛴다. 이미 애플이 제공하는 미러링 창에 맥의 표준 스크린샷·입력 API를 쏠 뿐이라, 맥과 아이폰만 있으면 시작할 수 있다.

주목 포인트 2 — "화면을 읽는 방식"이 영리하다

미러링 창은 접근성 트리(버튼·라벨 정보)가 전혀 없는 영상 스트림이다. phone-harness는 이 한계를 OCR로 뒤집는다. 화면을 찍어 Vision OCR을 돌리면 "보이는 모든 글자 + 그 좌표"가 나오는데, 저자는 이를 가난한 자의 DOM(the poor man's DOM)이라 부른다. 웹의 DOM 대신 "눈에 보이는 텍스트"를 요소 트리로 삼는 것이다.

주목 포인트 3 — 코드가 "함정과 이유"를 기록한다

소스 주석이 단순 설명이 아니라 직접 부딪혀 배운 macOS의 함정을 담고 있어, 그 자체로 실무 문서다. 예: AppleScript click at이 조용히 무시되는 이유(창이 접근성 트리 없는 영상이라서), 유니코드 타이핑이 안 먹혀 키코드로 쳐야 하는 이유(미러링이 원시 HID 키코드만 전달), 느린 드래그는 iOS 리스트를 거의 못 움직여 휠 스크롤/빠른 플릭을 써야 하는 이유 등.

주목 포인트 4 — 작고 읽기 쉽고, 안전을 설계에 넣었다

보호되는 코어가 ~500줄(실측 771줄)로 하루면 정독 가능한 규모다. 게다가 "연결은 사용자의 몫"이라는 원칙을 코드로 강제한다 — 에이전트는 절대 연결 화면의 Connect를 대신 누르지 않는다. 최신 커밋(2026-08-07)의 주제 자체가 "연결 안 된 상태를 감지해 사용자에게 넘기고, 절대 자동 연결하지 않는다"였다.

경쟁·대체 수단과의 비교

방식설치 부담화면 인식특징
phone-harness매우 낮음(맥+아이폰+pyobjc)스크린샷 + Vision OCR미러링 창 재활용, 코어 ~500줄, 에이전트 친화
Appium + WebDriverAgent높음(WDA 서명·설치)접근성 트리(XCUITest)iOS 표준 자동화. 강력하지만 설정 복잡
Android + ADB/UIAutomator중간(USB 디버깅)뷰 계층(UIAutomator)안드로이드 전용. phone-harness는 iOS 전용이라 상호보완
맥 computer-use 에이전트낮음스크린샷(비전 모델)맥 화면 대상. phone-harness는 이 접근을 "폰"으로 옮긴 셈
RESULT
한마디로

"이미 있는 미러링 창"이라는 통로를 발견해 모바일 자동화의 설치 장벽을 무너뜨렸고, OCR을 요소 트리로 삼는 영리함과 작고 읽기 쉬운 코드 덕에 에이전트·자동화 학습자 모두에게 매력적이었다.

3기술 스택 전체 지도

언어 하나(파이썬), 그 아래로 pyobjc가 macOS 네이티브 프레임워크를 그대로 불러 쓴다

이 프로젝트엔 서버·프론트 구분이 없다. 맥에서 도는 단일 파이썬 CLI일 뿐이다. 대신 진짜 힘은 파이썬이 아니라 파이썬에서 macOS의 C/Objective-C 프레임워크를 직접 호출하게 해 주는 pyobjc 브리지에 있다 — 화면을 찍고, 글자를 읽고, 가상 입력을 쏘는 모든 저수준 일을 애플 프레임워크에 그대로 위임한다.

언어 · 빌드 · 의존성

항목내용
언어Python 3.10+ (pyproject.toml 기준. install.md는 3.12+ 권장)
플랫폼macOS Sequoia(15)+ — 아이폰 미러링이 있어야 함
빌드hatchling 백엔드. pip install -e .phone-harness 명령을 PATH에 설치
의존성pyobjc 4종만 — pyobjc-framework-Quartz / Vision / AppKit / ApplicationServices
실행 형태표준입력(heredoc)으로 파이썬 코드를 받아 헬퍼가 미리 임포트된 네임스페이스에서 exec
배경 지식
pyobjc
파이썬에서 macOS의 Objective-C/C 프레임워크(Cocoa, CoreGraphics, Vision 등)를 마치 파이썬 모듈처럼 부르게 해 주는 브리지. import Quartz, import Vision 한 줄로 애플의 네이티브 API를 그대로 쓸 수 있어, 별도 C 확장 없이 파이썬만으로 저수준 macOS 제어가 가능하다.

프레임워크 지도 (무슨 일을 누가 맡나)

레이어프레임워크 / 도구맡은 일
창 찾기Quartz (CoreGraphics)CGWindowListCopyWindowInfo로 "iPhone Mirroring" 창의 위치·크기·ID를 화면 좌표로 얻음
앱 상태AppKitNSRunningApplication·NSWorkspace로 미러링 앱 실행 여부·최전면 여부 확인, 창 활성화
화면 캡처(눈)screencapture CLI창 ID(-l) 또는 영역(-R)으로 미러링 창만 PNG로 저장
문자 인식(눈)VisionVNRecognizeTextRequest로 캡처 속 모든 글자 + 바운딩 박스 → 화면 좌표 중심점
가상 입력(손)Quartz (CGEvent)CGEventCreateMouseEvent·...KeyboardEvent·...ScrollWheelEventkCGHIDEventTap에 게시
권한 점검ApplicationServicesAXIsProcessTrusted(접근성), CGPreflightScreenCaptureAccess(화면 녹화) 확인
비유로 이해하기

파이썬은 지휘자일 뿐이고, 실제 연주는 애플 프레임워크라는 오케스트라 단원들이 한다. Quartz는 창을 찾고 손가락(가상 입력)을 움직이는 근육, Vision은 글자를 읽는 눈, AppKit은 "지금 누가 무대 앞에 서 있나"를 아는 상황판, screencapture는 셔터를 누르는 카메라다. pyobjc가 이 단원들과 지휘자 사이의 통역을 맡는다.

4아키텍처 심화 분석

"보기 → 행동 → 검증" 루프 하나를, 상태 없는(stateless) 통로 위에서 반복한다

phone-harness의 뼈대는 계층이 분명하다. 맨 아래 mirror.py가 창·캡처·입력이라는 원시 통로를 깔고, ocr.py가 그 캡처를 좌표 달린 글자 목록으로 바꾸며, helpers.py가 이 둘을 조합해 에이전트가 쓸 고수준 동작(tap_text, open_app, scroll_collect…)을 만든다. 중요한 설계 결정은 이 통로가 상태를 갖지 않는다는 것 — 창 위치와 화면은 호출할 때마다 새로 조회하므로, 데몬(백그라운드 프로세스)도 없고 캐시된 좌표도 없다.

전체 구조도

에이전트가 쓴 파이썬 코드 │ (heredoc → stdin) ▼ ┌─────────────────────────────────────────────────────────────┐ │ run.py stdin 코드를 helpers 네임스페이스에서 exec │ │ (--doctor / skill 서브커맨드도 여기서 분기) │ └───────────────┬─────────────────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────┐ │ helpers.py 고수준 동작 (에이전트가 부르는 것) │ │ 보기: ocr() find_text() tap_text() screenshot() │ │ 손 : tap long_press drag press type_text │ │ 흐름: home() open_app() swipe() scroll_collect() │ │ 관문: ensure_mirroring() connection_state() │ │ + agent-workspace/agent_helpers.py (에이전트가 편집) │ └───────┬───────────────────────────────────┬─────────────────┘ ▼ ▼ ┌───────────────────────┐ ┌────────────────────────────┐ │ ocr.py │ │ mirror.py (원시 통로) │ │ Vision OCR │◀────▶│ find_window / capture │ │ 글자+박스→화면 좌표 │ 캡처 │ tap/drag/scroll/press/type │ └───────────────────────┘ │ (CGEvent를 HID에 게시) │ └──────────────┬───────────────┘ ▼ macOS "iPhone Mirroring" 창 ▼ 진짜 아이폰

핵심 패턴 1 — 미러링 창은 "영상"이라 HID 입력만 통한다

가장 근본적인 제약이자 설계의 출발점이다. 미러링 창 안쪽은 macOS 접근성이 아무것도 보지 못하는 비디오 스트림이다. 그래서 버튼을 "요소"로 클릭할 수 없다.

GOTCHA
AppleScript click at은 조용히 무시된다

창이 접근성 트리가 없는 영상이라, 일반적인 UI 자동화(AppleScript click, 접근성 API)는 에러도 없이 그냥 안 먹는다. 유일하게 통하는 것은 HID(하드웨어 입력) 수준에서 합성한 CGEventkCGHIDEventTap에 직접 게시하는 것. 미러링이 이 저수준 마우스·키보드 이벤트를 실제 터치로 아이폰에 전달하기 때문이다.

부수 규칙: 이벤트는 창이 최전면일 때만 전달된다. 그래서 모든 입력 헬퍼는 먼저 activate()로 창을 앞으로 끌어온다.

핵심 패턴 2 — OCR = "가난한 자의 DOM", 그리고 좌표계 뒤집기

DOM이 없으니 "보이는 글자"가 요소 트리를 대신한다. ocr.py는 Vision 결과의 정규화 박스를 화면 좌표 중심점으로 변환해 곧장 tap()에 넘길 수 있게 만든다. 여기서 초보자가 자주 막히는 좌표계 차이를 정확히 처리한다.

핵심 디테일
Vision(좌하단 원점) → 화면(좌상단 원점) 뒤집기
Vision의 바운딩 박스는 왼쪽 아래가 (0,0)인 정규화 좌표다. 반면 화면 좌표는 왼쪽 위가 (0,0)이다. 그래서 y를 py_top = (1 - origin.y - height) × 이미지높이로 뒤집고, 이미지 픽셀을 화면 포인트로 스케일(sx = 창너비 / 이미지너비)한 뒤 창 원점을 더해 최종 좌표를 만든다.

핵심 패턴 3 — 타이핑은 유니코드가 아니라 키코드로

미러링은 CGEventKeyboardSetUnicodeString이 붙인 유니코드 payload를 무시하고 원시 HID 키코드만 iOS로 전달한다. 그래서 type_text는 각 글자를 US 자판 키코드로 매핑하고, 대문자·기호는 Shift 조합으로 만든다. 이모지처럼 키코드가 없는 문자는 아예 타이핑할 수 없어 명시적으로 에러를 낸다 — "왜 안 되는지"를 숨기지 않는 정직한 설계다.

핵심 패턴 4 — 리스트의 끝은 "화면이 움직였나"로 판단한다

긴 목록을 끝까지 스크롤할 때, "내가 새 항목을 못 찾았으니 끝"이라고 판단하면 촘촘한 화면이나 OCR 한 줄 누락에서 오판한다. phone-harness는 대신 화면 픽셀이 실제로 움직였는가로만 끝을 정한다.

DESIGN
Jaccard 겹침 + 정착(settle) 대기

스크롤 전후의 "보이는 텍스트 집합"을 Jaccard 겹침으로 비교한다. 겹침이 0.6 미만이면 진짜 이동, 그 이상이면 안 움직인 것. 이 0.6은 실측상 "실제 전진(<0.45)"과 "끝에서 튕기는 오버스크롤 반동(>0.7)" 사이의 빈 구간에 놓았다. 또 스크롤 후 화면이 정착할 때까지(두 캡처가 같아질 때까지) 기다려, 지연 로딩 콘텐츠가 도착한 뒤 판단한다. 느린 터치 드래그는 iOS 리스트를 거의 못 움직이므로 휠 스크롤을 쓴다.

핵심 패턴 5 — "연결은 사용자의 몫" (안전 관문)

가장 최근 커밋이 강화한 부분이다. 에이전트는 절대 아이폰을 대신 연결하지 않는다. connection_state()가 화면을 OCR해 ready / blocked / no-window / not-running 네 상태로 판별하고, "iPhone in Use" 같은 안내 화면(interstitial)이 보이면 ensure_mirroring()이 즉시 멈추고 사용자에게 직접 연결하라는 메시지를 던진다. Connect 버튼을 눌러 봐야 소용없고, 재연결은 물리적 행동(앱 열기·폰 잠그기)이라 사람만 할 수 있기 때문이다.

5디렉토리 구조 해부

"보호되는 코어" 5파일 + "에이전트가 실행 중 편집하는" 작업공간 1파일

구조는 놀랄 만큼 단순하다. 실제 로직은 src/phone_harness/ 안 5개 파일에 다 들어 있고, 문서(README·SKILL·install)와 얇은 실행 스크립트가 최상위에 있다. 핵심은 코드를 "손대면 안 되는 보호 코어"와 "에이전트가 필요할 때 직접 함수를 추가하는 작업공간"으로 나눈 2계층 설계다 — 저자의 표현대로 "에이전트가 실행 중 빠진 것을 스스로 채워 넣는다".

phone-harness/ ├── phone-harness # 4줄. 개발용 런처(PYTHONPATH 세팅 후 run 실행) ├── pyproject.toml # 의존성(pyobjc 4종) + 콘솔 스크립트 등록 ├── README.md / install.md / SKILL.md # 문서 3종(설명·설치·에이전트용 사용법) ├── src/phone_harness/ # ── 보호되는 코어 (≈771줄) ── │ ├── run.py (40) CLI 진입점: stdin 코드를 exec, --doctor/skill 분기 │ ├── helpers.py (346) 고수준 동작: ocr/tap_text/open_app/scroll_collect… │ ├── mirror.py (259) 원시 통로: 창 찾기·캡처·CGEvent 입력 primitives │ ├── ocr.py (56) Vision OCR → 화면 좌표 박스 변환 │ ├── admin.py (70) --doctor: 권한·앱·세션 사다리 진단 │ └── __init__.py (0) 빈 패키지 표식 └── agent-workspace/ └── agent_helpers.py (22) 에이전트가 편집하는 헬퍼(import 시 코어에 자동 병합)

파일별 규모와 역할

파일줄수핵심 역할
helpers.py346가장 큰 파일. 읽기(ocr/find_text/tap_text), 흐름(home/open_app/swipe), 리스트 스크롤(scroll_screen/scroll_until/scroll_collect), 연결 관문(ensure_mirroring). mirror의 입력 primitives를 re-export
mirror.py259통로 그 자체. 창 탐색(find_window)·활성화·캡처(창ID→영역 폴백), 그리고 tap/long_press/drag/scroll_wheel/press/type_text. 키코드·Shift 매핑 테이블 포함
admin.py70run_doctor() — pyobjc→접근성→화면녹화→앱설치→실행→창→캡처→OCR 순으로 사다리 점검, 첫 FAIL부터 고치게 안내
ocr.py56Vision 텍스트 인식 + 좌표 변환. 가장 짧지만 "화면을 요소 트리로" 만드는 심장
run.py40CLI 배선. heredoc 코드를 헬퍼가 담긴 전역에서 실행, skill 서브커맨드는 SKILL.md를 출력
agent_helpers.py22에이전트 편집 영역. 지금은 tap_icon 하나 — "홈 화면 라벨은 탭 타깃이 아니고 아이콘은 라벨보다 ~35pt 위"라는 학습을 코드로 남김

구조를 읽는 요령: "무엇을 할 수 있나"가 궁금하면 helpers.py를, "어떻게 화면을 만지나"가 궁금하면 mirror.py를, "왜 안 되나"가 궁금하면 admin.py(--doctor)를 보라. 세 파일이면 프로젝트의 90%가 설명된다.

6학습 포인트

이 저장소에서 실제로 배울 수 있는 것 — "비전 모델 없이도 화면을 조작하는" 실무 기술

phone-harness의 교육적 가치는 폰 조작 그 자체가 아니다. "구조 정보(DOM/접근성)가 전혀 없는 화면을, 스크린샷과 OCR과 가상 입력만으로 안정적으로 조작하는" 컴퓨터-유즈(computer-use)의 뼈대가 군더더기 없이 담겨 있다는 점이다. 아래 주제들은 아이폰이 없어도 코드를 읽으며 배울 수 있다.

배울 것 1 — pyobjc

파이썬에서 macOS 네이티브 프레임워크 직접 호출

C 확장 없이 import Quartz / Vision / AppKit만으로 창 목록을 뒤지고(CGWindowListCopyWindowInfo), 이벤트를 합성하고, OCR을 돌린다. "파이썬으로 어디까지 OS를 만질 수 있나"의 좋은 본보기. 실습: 내 맥의 열린 창 목록과 좌표를 pyobjc로 출력해 보기.

배울 것 2 — 화면을 요소 트리로

Vision OCR + 좌표계 변환

ocr.py 56줄이 "이미지 → 좌표 달린 텍스트 목록"의 전 과정을 보여준다. Vision의 좌하단 정규화 좌표를 화면 좌상단 픽셀로 뒤집고 스케일하는 계산은, 스크린샷 기반 자동화라면 어디서나 재사용된다. 실습: 아무 스크린샷에 이 변환을 적용해 각 단어에 사각형을 그려 보기.

배울 것 3 — 가상 입력 합성

CGEvent를 HID 수준에서 게시하기

탭(마우스 이동→다운→업), 드래그(단계별 dragged 이벤트), 휠 스크롤, 키보드(다운/업 + 모디파이어 플래그)까지 CGEvent로 만드는 표준 패턴이 mirror.py에 정리돼 있다. "왜 유니코드가 아니라 키코드인가"라는 함정까지 함께. 실습: 텍스트 편집기를 최전면에 두고 press('cmd+a')type_text를 재현.

배울 것 4 — 견고한 종료 판정

"화면이 움직였나"로 루프를 끝내기

파서 결과가 아니라 픽셀 변화로 끝을 판단하는 scroll_collect는 자동화의 고질병(무한 스크롤·조기 종료)을 정면으로 다룬다. Jaccard 겹침 임계치(0.6), 정착 대기, 지연 로딩 유예 같은 실전 디테일이 그대로 있다. 실습: 임계치를 바꿔 가며 언제 오판이 생기는지 관찰.

배울 것 5 — 에이전트 도구 설계

2계층 헬퍼 · 동의(consent) 관문 · --doctor 사다리

보호 코어와 에이전트 편집 영역을 나눈 구조, 되돌리기 어려운 행동(전송·구매·삭제) 전에 멈추는 SKILL.md의 동의 규칙, 첫 FAIL부터 고치게 하는 --doctor 진단 사다리는 "에이전트가 안전하게 쓰는 도구"를 어떻게 설계하는지의 축소판이다. 실습: 내 CLI 도구에 --doctor식 사전 점검을 붙여 보기.

7하드웨어 / 시스템 요구사항

맥·아이폰·미러링, 그리고 터미널에 줘야 하는 두 가지 권한
구분요구사항
맥 OSmacOS Sequoia(15) 이상 — 아이폰 미러링 기능이 이때 등장
아이폰미러링과 페어링된 아이폰(앱을 한 번 수동으로 열어 페어링해 둬야 함 — 페어링 프롬프트는 실제 폰이 필요)
파이썬3.10+ (설치 문서는 3.12+ 권장) + pyobjc(Quartz·Vision·AppKit)
설치~/.phone-harness에 클론 후 pip install -e . --no-deps로 전역 phone-harness 명령 등록

실행에 필요한 권한(TCC) — 대상은 "터미널 앱"

권한왜 필요한가 / 특이점
손쉬운 사용(Accessibility)탭·키 입력을 위해 필수. 켜면 즉시 적용
화면 기록(Screen Recording)화면을 보기 위해 필수. 터미널을 재시작해야 적용됨(캡처가 검게 나오면 이 때문)
유의
TCC · "더 필요할 수도 있다"
TCC(Transparency, Consent, Control)는 macOS의 개인정보 권한 시스템이다. install.md는 정직하게 경고한다 — 위 두 권한은 "현재까지 알려진" 필수 권한이고 --doctor가 확인하는 전부지만, 깨끗한 맥에서는 첫 실행 때 추가 권한을 물을 수 있다. --doctor가 통과했는데도 탭·캡처가 조용히 안 되면 시스템 설정의 새 권한 프롬프트를 확인하라는 것.
한계
구조적으로 못 하는 것들

한 번에 아이폰 하나·세션 하나만 가능하고, 물리 폰을 잠금 해제하면 미러링이 멈춘다("iPhone in Use"). 멀티터치(핀치·줌)가 없고, 카메라/Face ID 흐름과 DRM 영상(검게 나옴)은 다룰 수 없다. OCR은 글자를 볼 뿐 의미를 모르므로, 라벨 없는 아이콘은 스크린샷 + 비전 모델의 도움이 필요하다.

8직접 해볼 수 있는 실습 과제

난이도별 과제 — ★는 아이폰 없이 코드만으로, ★★☆ 이상은 맥+아이폰이 있으면 더 좋다
난이도 ★☆☆ 입문 (하드웨어 불필요)

1. OCR 좌표 변환을 직접 재현하기ocr.py

아무 스크린샷 한 장에 Vision OCR을 돌리고, ocr.py의 y-뒤집기·스케일 계산을 그대로 적용해 각 단어에 사각형과 중심점을 그려 본다. "정규화 좌하단 → 화면 좌상단" 변환을 손으로 이해하는 게 목표. 아이폰 없이 맥만으로 가능.

난이도 ★☆☆ 입문 (하드웨어 불필요)

2. --doctor 사다리 읽고 흉내 내기admin.py

run_doctor()가 pyobjc→접근성→화면녹화→…→OCR 순으로 점검하며 "첫 FAIL부터 고치라"고 안내하는 구조를 따라 읽고, 내 다른 CLI 도구에 같은 형태의 사전 점검 함수를 붙여 본다. 의존성이 계단식으로 쌓이는 진단의 정석.

난이도 ★★☆ 중급

3. agent_helpers.py에 나만의 헬퍼 추가helpers · agent-workspace

지금 있는 tap_icon(라벨보다 35pt 위를 탭)처럼, 반복 동작을 함수로 캡슐화해 agent-workspace/agent_helpers.py에 넣으면 코어가 자동 병합한다. 예: "특정 앱을 열고 첫 화면이 뜰 때까지 기다리는" open_and_wait(name). 2계층 헬퍼 설계를 체험.

난이도 ★★☆ 중급

4. 리스트 수집기(scroll_collect) 파서 붙이기helpers.py

scroll_collect(extract, key=...)에 특정 화면용 extract를 넘겨, 예컨대 설정 목록·연락처·알림을 끝까지 모아 중복 없이 수집한다. "화면이 움직였나"로 끝을 정하는 로직은 그대로 두고, 파싱만 갈아 끼우는 감각을 익힌다.

난이도 ★★★ 고급

5. 안전 관문 확장 — 새 차단 마커/제스처 추가helpers · mirror

connection_state()_BLOCKED_MARKERS에 기기·지역별 안내 문구를 추가해 오판을 줄이거나, mirror.py에 새 제스처(예: 정밀 롱드래그, 특정 모디파이어 조합 단축키)를 primitive로 추가한다. "연결은 사용자의 몫" 원칙을 깨지 않으면서 통로를 확장하는 연습.

9관련 기술 심화 학습 로드맵

이 저장소를 완전히 소화하기 위한 6주 코스 — 통로 → 눈 → 손 → 견고함 → 안전 순
주차주제무엇을 · 어디서
1주차파이썬 + pyobjc 기초pyobjc가 Objective-C 객체를 파이썬에서 부르는 방식, import Quartz/Vision/AppKit. mirror.pyfind_window로 창 목록·좌표 다루기부터.
2주차화면 캡처 · 창 관리CGWindowListCopyWindowInfo, screencapture -l/-R, 최전면 활성화(NSRunningApplication). "창 ID 캡처 실패 시 영역 캡처로 폴백"하는 capture() 정독.
3주차Vision OCR · 좌표계VNRecognizeTextRequest, 정확도 레벨, 정규화 박스 → 화면 포인트 변환. ocr.py 한 파일을 완전히 이해하고 재작성해 보기.
4주차CGEvent 입력 합성마우스·키보드·스크롤 이벤트 생성과 kCGHIDEventTap 게시, 키코드/모디파이어. mirror.py의 tap/drag/press/type_text.
5주차견고한 자동화 루프정착 대기(wait_stable), 픽셀 이동 판정(Jaccard), 지연 로딩 유예. scroll_screen/scroll_until/scroll_collect로 무한 스크롤 안전하게 끝내기.
6주차에이전트 도구·안전 설계SKILL.md의 동의 규칙, 2계층 헬퍼, connection_state 상태기계, --doctor 사다리. "에이전트가 안전하게 쓰는 도구"를 스스로 하나 설계.
학습 순서의 이유

1~2주는 "화면을 확보한다"(통로·캡처), 3주는 "화면을 읽는다"(눈), 4주는 "화면을 만진다"(손), 5주는 "실수 없이 반복한다"(견고함), 6주는 "안전하게 위임한다"(에이전트 설계) 순이다. 눈·손이 먼저 있어야 루프가 성립하고, 루프가 견고해야 비로소 에이전트에게 맡길 수 있으므로 순서를 건너뛰지 않는 게 좋다.

10핵심 키워드 사전

문서에 나온 용어를 한자리에
용어
iPhone MirroringmacOS Sequoia+의 아이폰 화면 미러링 기능. 맥 입력을 아이폰 터치로 전달 — phone-harness의 통로
pyobjc파이썬에서 macOS Objective-C/C 프레임워크를 부르게 해 주는 브리지
Quartz / CoreGraphics창 목록·이미지·이벤트를 다루는 저수준 그래픽/입력 프레임워크
CGEvent합성한 마우스·키보드·스크롤 이벤트 객체. 이것을 게시해 입력을 흉내 냄
kCGHIDEventTap이벤트를 하드웨어(HID) 수준에 주입하는 지점. 미러링이 이 입력만 터치로 전달
HIDHuman Interface Device. 마우스·키보드 같은 입력 장치 계층
Vision / VNRecognizeTextRequest애플 온디바이스 OCR API. 화면 속 글자와 바운딩 박스를 반환
OCR광학 문자 인식. 여기선 "가난한 자의 DOM" — 보이는 텍스트를 요소 트리로 삼음
screencapturemacOS 기본 스크린샷 CLI. -l(창 ID)·-R(영역)로 미러링 창만 캡처
CGWindowListCopyWindowInfo열린 창들의 소유자·위치·크기·ID를 얻는 함수. 미러링 창 찾기에 사용
TCCTransparency, Consent, Control — macOS 개인정보 권한 시스템(접근성·화면기록)
AXIsProcessTrusted접근성(탭·키 입력) 권한 보유 여부를 확인하는 함수
Jaccard 겹침두 집합의 교집합÷합집합. 스크롤 전후 텍스트 집합을 비교해 "움직였나" 판단(임계 0.6)
settle(정착) 대기화면이 멈출 때까지(연속 캡처가 같아질 때까지) 기다려 지연 로딩을 반영하는 것
stateless 통로데몬·캐시 없이 매 호출마다 창 위치·화면을 새로 조회하는 설계. 창이 움직여도 안전
heredocphone-harness <<'PY' … PY 형태로 여러 줄 파이썬을 표준입력에 넣는 셸 문법
interstitial(안내 화면)"iPhone in Use"·연결 화면 등. 에이전트는 여기서 멈추고 사용자에게 넘김

11참고 링크

원문 · 설치 · 관련 문서
구분링크
저장소github.com/ShawnPana/phone-harness
설치·문제해결저장소의 install.md (권한 부트스트랩 · --doctor 사다리)
에이전트용 사용법저장소의 SKILL.md (동의 규칙 · 스크린 워크플로우)
아이폰 미러링support.apple.com — iPhone Mirroring (macOS Sequoia)
Vision 텍스트 인식developer.apple.com/documentation/vision/recognizing-text-in-images
Quartz Event Services(CGEvent)developer.apple.com/documentation/coregraphics/quartz_event_services
pyobjcpyobjc.readthedocs.io
라이선스MIT License