Swift CLI to control iPhone via macOS iPhone Mirroring. Built for AI agents (OpenClaw, Claude Code) — the CLI is a thin perceive-and-act bridge, all reasoning lives in the agent.
- Swift 5.9+, macOS 14+ (requires Sequoia 15.0+ at runtime)
- ArgumentParser 1.3.0
- Frameworks: ScreenCaptureKit, Vision, CoreGraphics, AppKit
- Karabiner-Elements required (DriverKit virtual HID for input injection)
swift build # debug
swift build -c release # release
swift test # run tests
sudo cp .build/release/iphonebase /usr/local/bin/ # install
Sources/
IPhoneBaseCore/ # Library
WindowManager.swift # Find & focus iPhone Mirroring window
ScreenCapture.swift # ScreenCaptureKit capture + grid overlay
OCREngine.swift # Apple Vision text recognition
InputInjector.swift # Karabiner DriverKit virtual HID input
HIDKeyMap.swift # USB HID keycodes & character mappings
ActionResult.swift # Shared JSON response envelope
iphonebase/ # CLI executable
IPhoneBase.swift # Entry point, command registration
Commands/ # One file per command (11 commands)
skills/
iphonebase/SKILL.md # OpenClaw skill definition
Tests/
IPhoneBaseCoreTests/ # Unit tests
Perceive → Reason → Act. The CLI provides two things:
- Perception:
perceive --jsonreturns screenshot + OCR + grid metadata - Actions:
tap,swipe,scroll,drag,type,key,home
The agent (LLM) does all reasoning. No OCR-based decision-making in the CLI.
| Category | Commands |
|---|---|
| Perceive | perceive, screenshot, status, doctor |
| Act | tap, swipe, scroll, drag, type, key, home |
- Create
Sources/iphonebase/Commands/XxxCommand.swift - Implement
AsyncParsableCommand(orParsableCommandfor sync-only) - Add
XxxCommand.selfto thesubcommandsarray inIPhoneBase.swift - Include
--jsonflag using the sharedActionResult<T>envelope - Update
skills/iphonebase/SKILL.mdwith the new command docs - Action commands must be "dumb executors" — no embedded OCR or reasoning
- Import order: ArgumentParser, IPhoneBaseCore, Foundation
- All commands support
--jsonvia sharedActionResult<T>envelope (Sources/IPhoneBaseCore/ActionResult.swift) - Results to stdout, debug/verbose to stderr
- InputInjector pattern:
connect()thendefer { disconnect() }; setwindowBoundsfor coordinate validation - Call
wm.bringToFront()before any input injection - Errors: typed enums with
CustomStringConvertible; throwExitCode.failurefor user errors - Commit messages: imperative mood ("Add feature" not "Added feature"), first line under 72 characters
- ScreenCapture captures at 2x retina resolution (image pixels)
- OCR (Vision) returns normalized coords with bottom-left origin —
OCREngineinverts Y perceivescales all coordinates (OCR elements + grid cells) to window-relative screen pointstap x yexpects window-relative screen points — coordinates fromperceiveflow directly- All InputInjector operations use absolute screen coordinates (
window.bounds.origin + offset)
- iPhone Mirroring accepts CGEvent mouse input for taps
- Swipe/drag uses Karabiner HID pointing click-drag (CGEvent scroll wheel is ignored)
- Tap sequence:
CGWarpMouseCursorPosition→ CGEvent mouseDown/mouseUp - Timing delays (
usleep) throughout InputInjector are tuned values, not arbitrary - Karabiner daemon must be running (not just installed)
- Skill at
skills/iphonebase/SKILL.mdfollows AgentSkills spec (YAML frontmatter + markdown) - Requires
bins: ["iphonebase"],os: ["darwin"] - Install to
~/.openclaw/skills/for agent discovery