How device_utils.py Constructs and Executes IDB Commands for Simulator Interaction

The build_idb_command function in device_utils.py constructs IDB commands as argument lists by splitting operation strings, appending positional arguments, and optionally injecting --udid flags, then returns the list for subprocess.run execution.

The conorluddy/ios-simulator-skill repository provides Python utilities for automating iOS simulators via Facebook's IDB (iOS Device Bridge). At the heart of this automation lies device_utils.py, which centralizes how device_utils.py constructs and executes IDB commands through a single helper function that eliminates shell-parsing ambiguity.

The build_idb_command Helper Function

All IDB interactions in the repository flow through build_idb_command defined in ios-simulator-skill/scripts/common/device_utils.py. This function transforms high-level operation descriptions into executable argument lists suitable for Python's subprocess module.

Constructing the Base Command

The function begins by splitting the operation string and prefixing it with the idb binary:

cmd = ["idb"] + operation.split()

This approach converts a string like "ui tap" into the list ["idb", "ui", "tap"]. According to the source code at lines 71-73, this split ensures that multi-word IDB subcommands are properly separated into distinct arguments without requiring the caller to pre-split strings.

Appending Positional Arguments

Next, the function extends the command with any additional positional arguments passed via *args:

cmd.extend(str(arg) for arg in args)

As shown at lines 74-76, this generator expression converts every argument to a string before appending, allowing callers to pass integers (like screen coordinates) or other types safely. For example, coordinates 200 and 400 become string elements in the final command list.

Injecting the Device Identifier

The function conditionally appends the --udid flag only when a specific device identifier is provided:

if udid:
    cmd.extend(["--udid", udid])

Lines 78-80 implement this logic, allowing the same helper to target either a specific simulator (when udid is provided) or the default booted device (when udid is None). This eliminates the need for callers to handle flag presence logic repeatedly.

Executing Commands with subprocess

The build_idb_command function returns a complete argument list (lines 81-82), but does not execute the command itself. Execution happens in the calling functions using subprocess.run with strict error handling.

For example, the get_device_screen_size function (lines 18-22) demonstrates this pattern:

cmd = build_idb_command("ui describe-all", udid, "--json")
result = subprocess.run(
    cmd, capture_output=True, text=True, check=True
)

This separation of concerns ensures that device_utils.py remains a pure command-builder while individual modules handle execution context, output parsing, and error recovery. Using check=True ensures that IDB failures raise CalledProcessError immediately, preventing silent failures during simulator interactions.

Practical Usage Examples

The following patterns demonstrate how build_idb_command handles various simulator automation scenarios across the codebase.

Tapping on the Booted Simulator

When no UDID is specified, IDB defaults to the currently booted simulator:

from ios_simulator_skill.scripts.common.device_utils import build_idb_command
import subprocess

cmd = build_idb_command("ui tap", None, "200", "400")

# Result: ["idb", "ui", "tap", "200", "400"]

subprocess.run(cmd, check=True)

Targeting a Specific Device by UDID

For multi-device scenarios, pass a specific UDID to isolate the target:

udid = "A1B2C3D4-5678-90AB-CDEF-1234567890AB"
cmd = build_idb_command("ui tap", udid, "250", "300")

# Result: ["idb", "ui", "tap", "250", "300", "--udid", "A1B2C3D4-5678-90AB-CDEF-1234567890AB"]

subprocess.run(cmd, check=True)

Retrieving the Accessibility Tree

Complex queries like dumping the UI hierarchy use additional flags:

import json

def fetch_accessibility_tree(udid: str) -> dict:
    cmd = build_idb_command("ui describe-all", udid, "--json")
    result = subprocess.run(
        cmd, capture_output=True, text=True, check=True
    )
    return json.loads(result.stdout)

Integration Across the Codebase

The build_idb_command helper serves as the single source of truth for IDB interactions throughout the repository. Rather than constructing raw shell strings, specialized modules delegate to this utility:

This architecture eliminates shell-injection risks and ensures that device-targeting logic (the --udid flag) behaves identically whether performing gestures, typing text, or querying accessibility hierarchies.

Summary

  • build_idb_command in device_utils.py serves as the central factory for IDB argument lists, converting string operations into properly split command components.
  • The function handles operation splitting, positional argument conversion, and optional UDID injection without shell string concatenation.
  • Execution occurs via subprocess.run in calling modules, using the returned list with check=True for strict error handling.
  • This pattern appears consistently across gesture.py, keyboard.py, and navigator.py, providing a single source of truth for simulator automation.

Frequently Asked Questions

What is IDB and why does device_utils.py use it?

IDB (iOS Device Bridge) is a command-line tool developed by Meta for automating iOS simulators and devices. The device_utils.py module uses IDB because it provides robust access to UI automation features like tapping, swiping, and accessibility tree inspection that are not available through Apple's simctl alone.

How does build_idb_command handle special characters in arguments?

The function converts all positional arguments to strings using str(arg) and extends the command list directly without shell interpolation. Because it returns a list rather than a string, special characters like spaces or quotes are treated as literal arguments by subprocess.run, eliminating injection vulnerabilities and parsing errors common with shell strings.

Can I use device_utils.py with physical iOS devices?

Yes, provided the physical device is provisioned for development and accessible via IDB. The build_idb_command function accepts a udid parameter that works for both simulator UUIDs and physical device identifiers. When a UDID is passed, the function appends --udid to the command, allowing IDB to target the specific hardware device rather than the booted simulator.

Where does the subprocess execution happen if not in device_utils.py?

The actual subprocess.run calls occur in specialized modules that import the builder, such as ios-simulator-skill/scripts/gesture.py, keyboard.py, and navigator.py. Additionally, higher-level utilities within device_utils.py itself—like get_device_screen_size (lines 18-22)—execute the commands immediately after building them, demonstrating that execution can occur both within the utility module and in dependent scripts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →