Skip to main content

Phone Harness 📱

phone-harness · let your agent control your phone.

Connect an AI agent — Claude Code, Codex, or any LLM — directly to your real iPhone with a thin, editable harness. No jailbreak, no Xcode, no WebDriverAgent.

The Mac's iPhone Mirroring window is the whole transport: screencapture + Vision-framework OCR for eyes, HID-level CGEvents for hands. Nothing between the agent and the phone. The agent writes what's missing during execution in agent-workspace/agent_helpers.py.

  ● agent: wants to open Weather
  │
  ● ocr() → "Weather" at (400, 468)
  │
  ● tap(400, 468) → wait_stable() → ocr() confirms the forecast
  ✓ done

Your phone, driven by an agent.

Setup prompt

Paste into Claude Code or Codex:

Set up phone-harness for me. Clone https://github.com/ShawnPana/phone-harness
into ~/.phone-harness (its canonical home) and read `install.md` first to install
it and connect it to my real iPhone
through the macOS iPhone Mirroring app — install it so `phone-harness` is a
command on my PATH, and register it as an agent skill named phone-harness using
`phone-harness skill` as the body, so you reach for it automatically. Then read
`SKILL.md` for normal usage, and always read `src/phone_harness/helpers.py`
because that is where the functions are. Whenever you capture or verify the
screen, activate the iPhone Mirroring window so I can see what you're doing on
the phone.

Setup needs two things only I can do: pairing iPhone Mirroring with my phone
once, and granting the terminal Accessibility + Screen Recording in System
Settings — walk me through those and wait for me. Verify with
`./phone-harness --doctor`.

After it's installed, as a quick demo that interaction works, go to my Home
Screen and — if the phone is connected and unlocked — ask me whether you should
open the Weather app as a harmless test; only open it if I say yes. If the
session is paused or the phone is locked, just tell me the doctor status instead.

The agent will walk you through the two things only you can do: pairing iPhone Mirroring with your phone once (the pairing prompts need the physical phone), and granting the terminal Accessibility (taps & keystrokes) and Screen Recording (seeing the phone) in System Settings → Privacy & Security. Screen Recording takes effect after the terminal restarts; Accessibility is immediate. Then ./phone-harness --doctor verifies the whole chain.

These are the permissions currently known to be required. A fresh machine may prompt for more the first time an action runs — if --doctor passes but taps or capture silently do nothing, watch for a macOS permission prompt. See install.md for details.

Why this works

iPhone Mirroring (macOS Sequoia+) renders the phone as a Mac window and forwards real mouse and keyboard input as touches. That gives an agent everything it needs for real-device iOS automation:

  • See — capture just the mirroring window, OCR it with Apple's Vision framework: every visible string with a tap-ready coordinate. The poor man's DOM.
  • Act — CGEvents posted at the HID tap: taps, long-presses, drags/flicks, scroll gestures, unicode typing, and the app's own shortcuts (Cmd+1 Home, Cmd+2 App Switcher, Cmd+3 Spotlight).
  • Verify — screenshot again. No DOM means the capture is the ground truth.

Things that do NOT work, learned the hard way: AppleScript click at (silently ignored — the window is a video stream with no accessibility tree), unicode key payloads (mirroring forwards raw HID keycodes, so typing must use keycodes), a slow touch-drag (barely moves an iOS list — use wheel scroll for lists, a fast flick for pages), and input while the window isn't frontmost (swallowed).

Usage

./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

Day-to-day workflow lives in SKILL.md, which install.md registers as an agent skill (phone-harness skill prints the body) so the agent reaches for it on its own.

Architecture

  • SKILL.md — day-to-day usage (the agent-facing product surface)
  • install.md — permissions bootstrap and troubleshooting
  • src/phone_harness/ — protected core (~500 lines):
    • mirror.py — window discovery, focus, capture, CGEvent input
    • ocr.py — Vision-framework text recognition → screen-point boxes
    • helpers.py — the primitives pre-imported into scripts
    • admin.py--doctor
    • run.py — the CLI (exec stdin with helpers in scope)
  • agent-workspace/agent_helpers.py — helper code the agent edits; auto-loaded into every script's namespace

The mirror transport is stateless (window bounds and captures are re-queried per call), so there is no daemon — every invocation is self-contained.

Development

From a checkout, use ./phone-harness to run the working tree directly:

./phone-harness <<'PY'
print(screen_info())
PY

Limits

  • One phone, one session; unlocking the physical phone pauses mirroring.
  • No multi-touch (no pinch), no camera/Face ID flows, DRM video renders black.
  • OCR sees text, not semantics — unlabeled icons need a screenshot + a vision-capable model.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

phone_harness-0.1.1.tar.gz (21.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

phone_harness-0.1.1-py3-none-any.whl (24.1 kB view details)

Uploaded Python 3

File details

Details for the file phone_harness-0.1.1.tar.gz.

File metadata

  • Download URL: phone_harness-0.1.1.tar.gz
  • Upload date:
  • Size: 21.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for phone_harness-0.1.1.tar.gz
Algorithm Hash digest
SHA256 dded44d198a0457715fe1e338b437ce541f9e73d0fa356808fa23caa13d9cb00
MD5 c33e8c53a41d6b0ee58493d55aa1070b
BLAKE2b-256 a521be4b8a259878370b7b7ed7a98e063477b2305b29e467b0697d5f1c69ba76

See more details on using hashes here.

Provenance

The following attestation bundles were made for phone_harness-0.1.1.tar.gz:

Publisher: publish.yml on ShawnPana/phone-harness

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file phone_harness-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: phone_harness-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 24.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for phone_harness-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1724d7a4e9f9e21c69bfc509add1f00276fa776a8ae0e270f96311d0f29b6629
MD5 92f98445eda69f0e0b0e2072e0d4723d
BLAKE2b-256 072af367c1693e28e69e2b7ead8d6458a58afc9bc096505772efc4956f61f716

See more details on using hashes here.

Provenance

The following attestation bundles were made for phone_harness-0.1.1-py3-none-any.whl:

Publisher: publish.yml on ShawnPana/phone-harness

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.2.0

2 files

This release

0.1.1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page