← Platforms

Apple SDK — iOS, macOS, watchOS

Remote debugging for native Apple apps: one Swift Package that puts your iOS, macOS and watchOS build under your AI tool's control on a real device or simulator.

One package, three platform lines

The Apple SDK is a single Swift Package (OmniDebugLink) that adapts to the platform it compiles for:

Each build announces its own capability list on connect, so your AI tool automatically sees only the tasks the connected device can actually run. Protocol semantics align with the Flutter and Android clients: coordinates are normalized 0–1 with a top-left origin, and screenshots travel as JPEG in the __odl_file envelope.

Requirements

Install and start

1

Add the package dependency

// Package.swift
.package(url: "https://github.com/omnidebuglink/omnidebuglink_apple.git", from: "0.2.0")

In Xcode this is File → Add Package Dependencies…, or a local package reference if you prefer to develop against a checkout.

2

Start the client at app launch

import OmniDebugLink

// In AppDelegate.application(_:didFinishLaunching:) / App.init():
OmniDebugLink.start("<clientToken>")

The relay URL is built in and the app version is read from the bundle. Connection, heartbeat and reconnect with exponential backoff are handled for you.

3

Forward logs and register custom tasks

OmniDebugLink.recordLog("order placed", level: .info)
OmniDebugLink.recordError(error)

OmniDebugLink.tasks.register("my_task", { req in ["ok": true] }, description: "...")

Handlers run on the main actor, so a custom task can touch UIKit/AppKit directly. Registered tasks re-announce the capability list automatically.

4

Connect your AI tool over MCP

claude mcp add --transport http odl \
  "https://api.omnidebuglink.dev/mcp"

Sign in once in the browser; the AI tool then drives every device under your account, including this one.

Runtime switches

Built-in tasks

Available on every platform, watchOS included:

TaskWhat it does
echo / ping / get_statsConnectivity basics and runtime stats.
read_logs1000-entry ring buffer of forwarded logs and uncaught exceptions (nothing before the SDK started).
prefsNSUserDefaults / UserDefaults get / set / delete / list with valueType coercion.
get_perfFPS and frame-time percentiles, memory, device snapshot.
get_stateApp/version state, screen metrics, keyboard and VoiceOver status (reduced set on watchOS).

The UIKit and AppKit lines share one UI task set — introspection first, then actions:

TaskWhat it does
ui_traverseView tree snapshot as a flat list (3000-node cap); SwiftUI controls are flattened in as addressable pseudo-nodes.
find_objectsSearch by key (accessibilityIdentifier, recommended) / text / view_type substring plus index.
view_componentOne node in depth: Mirror-reflected properties with KVC guards and crashing getters skipped.
wait_forPolls every 200 ms until a match appears; timeout returns found: false, not an error.
screenshotJPEG via drawHierarchy (UIKit) / cacheDisplay (AppKit), with a quality-then-downsample size budget.
ui_clickNearest UIControl gets sendActions(.touchUpInside), otherwise accessibilityActivate(); segments and sliders infer the intended value from the click x.
tap_screenActivates the element at a point on iOS; on macOS a synthesized NSEvent click queued through NSApp.postEvent.
swipeProgrammatic UIScrollView scrolling on iOS; a real NSEvent drag on macOS.
long_pressActivation-style hold on iOS; real NSEvent press-hold-release on macOS.
input_textWrites into the first responder's field via the responder-chain sendAction(to: nil) trick — no private API.
send_keyiOS: UIKeyInput soft dispatch (enter/tab/space/del/escape). macOS: real NSEvent key codes.
set_componentMutates text, segment_index, slider_value, switch state and similar targeted properties.

For more on what these return and how to chain them, see UI & scene introspection and real input injection.

Addressing views

Targets are addressed by key / text / view_type substring with an index for disambiguation, and path as an exact fallback. Find and act happen atomically inside one task, so the tree cannot change between the two steps.

SwiftUI controls do not appear in the view subtree — they live in the host view's accessibilityElements, which the snapshot flattens into addressable pseudo-nodes. In practice .accessibilityIdentifier() does not land on those elements, so set .accessibilityLabel() on SwiftUI controls and locate them by text; ui_click then activates them through accessibilityActivate(), which drives SwiftUI Buttons and Toggles reliably. When the accessibility runtime is not active, results say so and hint at how to enable it.

iOS vs macOS input: an honest difference

iOS has no public touch-synthesis APIUITouch cannot be configured and UIEvent cannot be created, and private API would risk App Store rejection. The UIKit line therefore activates elements instead: ui_click and tap_screen use sendActions plus accessibilityActivate(), swipe performs programmatic scrolling on UIScrollView, and long_press approximates an activation hold. Free-form gesture injection is not possible there with public API, and each task's return value states plainly what was actually done.

macOS injects real events: NSEvents are synthesized in-process and queued through NSApp.postEvent, so clicks, drags, press-holds and key codes are routed exactly like user input — and no accessibility permission prompt is needed. (An earlier CGEvent.postToPid path was measured as ignored by AppKit, which is why the final design uses NSApp delivery.) AppKit's bottom-left coordinate space is converted to the protocol's top-left origin on the way out, so coordinates mean the same thing on both lines.

Platform maturity

LineStatus
iOS / iPadOS (UIKit)Verified end-to-end on Xcode 14 + iOS 16.2 simulator: connection, heartbeat, replacement stop on close code 4000, all tasks, SwiftUI activation, screenshot budget.
macOS (AppKit)Verified on Xcode 14 + macOS 12.5 Intel: connection and heartbeat, coordinate/screenshot consistency, NSEvent injection across all tasks, real SwiftUI control clicks, reflection guards.
tvOS / Mac CatalystRuns the UIKit-line code; not separately verified.
watchOSRead-only subset (basics, logs, prefs, perf, reduced state) — no UI tree, screenshots or input tasks.

Worth knowing