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) and github.com in ~/.ssh/known_hosts. Then write git+ssh://git@github.com/... where this README writes git+https://github.com/..., and git@github.com:sofatools/iosbridge.git in Package.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

  1. Install the CLI. This puts iosbridge (and iosbridge-mcp, used by the Claude Code plugin) on your PATH:

    uv tool install git+https://github.com/sofatools/iosbridge
    # or: pipx install git+https://github.com/sofatools/iosbridge

    Append @0.1.2 to the URL to pin a release. If the shell then says iosbridge: command not found, run uv tool update-shell (or pipx ensurepath) and open a new terminal.

  2. 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 address

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

  3. Add IOSBridge to your app. In your app’s Package.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.swift for 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.plist add NSLocalNetworkUsageDescription (iOS asks the user for Local Network access) and, for plain HTTP to the relay, NSAppTransportSecurity → NSAllowsLocalNetworking = true. See example/HelloBridge/Resources/Info.plist.

  4. Install on the iPhone with the phone on USB, in your app’s folder:

    xtool dev            # or: iosbridge install

    Tap the app on the phone (xtool cannot launch apps on iOS 17+ from Linux) and allow Local Network access when asked.

  5. 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-mcp command it puts on your PATH.
  • Export IOSBRIDGE_TOKEN (and IOSBRIDGE_URL if the relay runs elsewhere) in the shell you start claude from; 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=1 first.

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