iosbridge docs
This is the public part of the iosbridge README: requirements, setup, the command reference and the Claude Code plugin. The full README with troubleshooting, the changelog and the example app are in the private repository you get access to with your purchase.
Requirements
- Linux with Python 3.12 or newer (tested on 3.12, 3.13 and 3.14) and uv or pipx for the command line tool. uv downloads a suitable Python by itself if yours is older.
- xtool set up for your Apple ID (
xtool setup) to build and install apps. - An iPhone on iOS 16 or newer, on the same Wi-Fi as the Linux machine.
- Read access to this private repository on the machine, for
git, uv, pipx, SwiftPM and Claude Code alike. See the next section.
Access to the private repository
Your purchase gave your GitHub account read access to sofatools/iosbridge.
Everything below fetches the repository with plain git, which must be able to
authenticate without asking. Set that up once, either way:
-
HTTPS (recommended): install the GitHub CLI, then
gh auth login # sign in with the account that bought iosbridge gh auth setup-git # lets git (and uv, pipx, SwiftPM) use that login -
SSH: a GitHub SSH key loaded in
ssh-agent(no passphrase prompt) andgithub.comin~/.ssh/known_hosts. Then writegit+ssh://git@github.com/...where this README writesgit+https://github.com/..., andgit@github.com:sofatools/iosbridge.gitinPackage.swift.
Check with git ls-remote https://github.com/sofatools/iosbridge – it should
list the tags without asking for a password.
Setup in 5 steps
-
Install the CLI. This puts
iosbridge(andiosbridge-mcp, used by the Claude Code plugin) on yourPATH:uv tool install git+https://github.com/sofatools/iosbridge # or: pipx install git+https://github.com/sofatools/iosbridgeAppend
@0.1.2to the URL to pin a release. If the shell then saysiosbridge: command not found, runuv tool update-shell(orpipx ensurepath) and open a new terminal. -
Start the relay on the machine your iPhone shares a Wi-Fi with:
export IOSBRIDGE_TOKEN=$(openssl rand -hex 16) echo $IOSBRIDGE_TOKEN # you need it in step 3 and in every other shell iosbridge relay # INFO relay listening on http://192.168.1.20:8765 <- note this addressWith a VPN or several networks it lists more addresses, network cards first:
http://192.168.1.20:8765 (wlan0); also http://10.2.0.2:8765 (wg0). Use the one on the Wi-Fi your iPhone is on. The relay runs in the foreground; leave that terminal open and use another one for the rest. It keeps nothing on disk; Ctrl-C stops it. -
Add
IOSBridgeto your app. In your app’sPackage.swift:dependencies: [ .package(url: "https://github.com/sofatools/iosbridge.git", from: "0.1.2"), ], // and in your app target: // dependencies: [.product(name: "IOSBridge", package: "iosbridge")]Start the client once at launch, debug builds only (see
example/HelloBridge/Sources/HelloBridge/HelloBridgeApp.swiftfor a whole app):import IOSBridge import SwiftUI @main struct MyApp: App { #if DEBUG @State private var bridge = BridgeClient( url: URL(string: "http://192.168.1.20:8765")!, // the relay address from step 2 token: "<your IOSBRIDGE_TOKEN>", guardedLabels: ["delete", "send"] // buttons that need --force ) #endif var body: some Scene { WindowGroup { ContentView() #if DEBUG .task { bridge.start() } #endif } } }In
Info.plistaddNSLocalNetworkUsageDescription(iOS asks the user for Local Network access) and, for plain HTTP to the relay,NSAppTransportSecurity→NSAllowsLocalNetworking= true. Seeexample/HelloBridge/Resources/Info.plist. -
Install on the iPhone with the phone on USB, in your app’s folder:
xtool dev # or: iosbridge installTap the app on the phone (xtool cannot launch apps on iOS 17+ from Linux) and allow Local Network access when asked.
-
Look at it, in a second terminal:
export IOSBRIDGE_TOKEN=... # the same token as the relay iosbridge status # {"ok": true, "listening": true, "last_poll_s_ago": 0.4, "pending": 0} iosbridge screenshot # writes iosbridge-HHMMSS.png in the current folder iosbridge dump # elements with role, label, value, frame iosbridge tap Save
IOSBRIDGE_URL (default http://127.0.0.1:8765) tells the CLI where the relay
is; run the CLI on the same machine as the relay and you never need to set it.
Commands
Every command prints one JSON line on stdout. Exit code 0 on success, 1 when
the app or xtool reported a failure (the JSON says why), 2 on configuration or
connection errors (one line on stderr, starting with iosbridge:), 130 after
Ctrl-C.
| Command | What the app does |
|---|---|
iosbridge relay [--host 0.0.0.0] [--port 8765] |
runs the relay (needs IOSBRIDGE_TOKEN) |
iosbridge status |
listening, last_poll_s_ago, pending |
iosbridge ping |
round trip, answers pong |
iosbridge screenshot [--out PATH] [--scale N] |
PNG of the key window; prints path, width, height, scale |
iosbridge dump |
on-screen accessibility elements: role, label, value, selected, frame |
iosbridge tap LABEL |
activates the best match: exact before prefix before contains, buttons before text, on-screen before off-screen |
iosbridge tap X,Y |
activates the smallest element containing the point (in points, see below) |
iosbridge tap LABEL --force |
also taps labels in guardedLabels |
iosbridge open URL |
opens a URL, e.g. your app’s own scheme |
iosbridge scroll [top|bottom|up|down] |
scrolls the largest scroll view (default down) |
iosbridge type TEXT |
types into the focused, else the first, text field |
iosbridge build [DIR] |
xtool dev build |
iosbridge install [DIR] |
xtool dev (build, sign, install) |
--timeout SECONDS bounds the wait: for the app’s answer (default 30) or for
xtool in build and install (default 1800). It goes before or after the
subcommand, iosbridge --timeout 5 ping and iosbridge ping --timeout 5 are
the same; so does -v for debug logging.
tap, open, scroll and type let the UI settle for 700 ms before they
answer. To change that, build the client from its parts:
BridgeClient(transport: HTTPTransport(url: url, token: token), runner: CommandRunner(driver: UIKitScreenDriver(), guardedLabels: [...], settleMilliseconds: 300)).
Roles: button, header, text, textfield, image, link, adjustable,
other.
Coordinates: everything is in points
Dump frames, tap X,Y and ios_tap(x, y) use the window’s coordinate space in
points (an iPhone 15 is 393 × 852). Screenshots are taken at scale 1 by
default, one pixel per point, so a position you read off a screenshot is the
position to tap. --scale 2 or --scale 3 (up to 3) gives a sharper picture
at the phone’s native resolution; divide pixel positions by that scale before
tapping. The CLI prints width, height and scale with every screenshot,
and the MCP tool adds a line saying the same.
Claude Code plugin
The repository is also a Claude Code plugin marketplace. The plugin adds an MCP
server (ios_status, ios_screenshot, ios_dump, ios_tap, ios_open,
ios_scroll, ios_type, ios_build, ios_install) and a skill ios-debug
that teaches the agent the loop: status, build/install when code changed,
screenshot, dump, tap, screenshot.
Before you install it:
- Install the CLI (step 1). The plugin’s MCP server is the
iosbridge-mcpcommand it puts on yourPATH. - Export
IOSBRIDGE_TOKEN(andIOSBRIDGE_URLif the relay runs elsewhere) in the shell you startclaudefrom; the server inherits that environment. - Claude Code clones the private repository with your git credentials and never
prompts (see Access). If you use HTTPS
and have no GitHub SSH key,
export CLAUDE_CODE_PLUGIN_PREFER_HTTPS=1first.
Then, inside Claude Code:
/plugin marketplace add sofatools/iosbridge
/plugin install iosbridge@sofatools-iosbridge
or from your shell:
claude plugin marketplace add sofatools/iosbridge
claude plugin install iosbridge@sofatools-iosbridge
Restart Claude Code (or run /reload-plugins). /mcp lists the iosbridge
server as connected; the skill is /iosbridge:ios-debug. Ask, for example,
“take a screenshot of the app and tap Increment”.
To try the plugin from a clone without installing it:
claude --plugin-dir /path/to/iosbridge/plugin.
Updating
uv tool install --force git+https://github.com/sofatools/iosbridge # or: pipx upgrade iosbridge
In your app, swift package update (or xtool dev) picks up the new Swift
package within from: "0.1.2". In Claude Code, /plugin marketplace update sofatools-iosbridge, then update the plugin in /plugin. See CHANGELOG.md
for what changed.
How it works
The app polls the relay (GET /commands?wait=20) while it is in the
foreground, runs each command on the main actor through UIKit accessibility and
posts the result (POST /results). The relay is a single Python process with
an in-memory queue and one bearer token; the CLI and the MCP server are thin
HTTP clients of it (POST /enqueue, GET /result/<id>). Nothing leaves your
LAN and nothing is stored.
To read SwiftUI’s accessibility tree the client turns on accessibility
automation through _AXSSetAutomationEnabled in libAccessibility, the same
trick in-app testing frameworks like KIF use. That is private API: keep the
bridge in #if DEBUG and out of App Store builds.
Source: iosbridge README.md, release 0.1.2, commit 4ab5262