iOS Simulator Skill for Agents
Production-ready skill for building, testing, and automating iOS apps. 30 scripts optimized for both human developers and AI agents.
(If you'd prefer an MCP, XC-MCP -> NPM )
Xcode Build + Simulator Automation
This skill covers both sides of iOS development:
- Xcode builds via
xcodebuild — compile, test, and parse results with progressive error disclosure
- Simulator interaction via
xcrun simctl and idb — semantic UI navigation, accessibility testing, device lifecycle
If you only need Xcode build tooling without the simulator scripts, see the plugin version: xclaude-plugin
Installation
Claude Code — plugin marketplace (recommended)
/plugin marketplace add conorluddy/ios-simulator-skill
/plugin install ios-simulator-skill@conorluddy
Codex — plugin marketplace (recommended)
codex plugin marketplace add conorluddy/ios-simulator-skill
codex plugin add ios-simulator-skill@conorluddy
Or run /plugins inside Codex and install it from the conorluddy marketplace. Start a new
Codex session afterwards. Update later with codex plugin marketplace upgrade conorluddy.
Manual install
A skill is loaded from SKILL.md at the root of its directory:
| Agent | User-level skills | Project-level skills |
|---|
| Claude Code | ~/.claude/skills/<name>/SKILL.md | .claude/skills/<name>/SKILL.md |
| Codex | ~/.agents/skills/<name>/SKILL.md | .agents/skills/<name>/SKILL.md |
The commands below use the Claude Code path; swap in ~/.agents/skills for Codex.
This repository is a plugin, so the skill itself lives at
ios-simulator-skill/skills/ios-simulator-skill/; cloning the whole repo into your skills
directory puts SKILL.md three levels too deep and the skill will not load.
From a release (simplest):
curl -L https://github.com/conorluddy/ios-simulator-skill/releases/latest/download/ios-simulator-skill.zip -o skill.zip
unzip skill.zip -d ~/.claude/skills/ios-simulator-skill
From a clone (to track main):
git clone https://github.com/conorluddy/ios-simulator-skill.git ~/src/ios-simulator-skill
cp -R ~/src/ios-simulator-skill/ios-simulator-skill/skills/ios-simulator-skill \
~/.claude/skills/ios-simulator-skill
For a project-local install, use the project-level path from the table instead.
Restart Claude Code afterwards (Codex picks up new skills automatically; restart it if the skill
doesn't appear). Verify SKILL.md sits at the root of the destination:
ls ~/.claude/skills/ios-simulator-skill/SKILL.md # Claude Code
ls ~/.agents/skills/ios-simulator-skill/SKILL.md # Codex
Prerequisites
| Requirement | Version | Needed for |
|---|
| macOS | 15 (Sequoia)+ | everything |
| Xcode (full app, not just Command Line Tools) | 26+ | building, simctl, idb |
| Python | 3.12+ | every script |
idb (CLI + companion) | 1.5.1+ (1.6.3+ recommended) | tapping, swiping, typing |
| Pillow | any | visual diffs only (pip3 install pillow) |
Install idb — required for every interactive script (navigator.py, gesture.py, keyboard.py):
brew install facebook/fb/idb # installs idb-cli and idb-companion
The Homebrew build is prebuilt: it needs an Apple silicon Mac, macOS 15+, and a full Xcode 26+
selected with xcode-select (Command Line Tools alone are not enough). Intel Macs are not
supported (facebook/idb#954).
brew install idb-companion no longer works: idb-companion was removed from Homebrew
core and now lives in Meta's own facebook/fb tap. If you followed older instructions,
see Updating idb.
To verify everything at once:
bash scripts/sim_health_check.sh # add --json for machine-readable output
Updating idb
On Xcode 27, idb-companion must be 1.5.1 or newer. Older builds look for
SimulatorKit.framework where Xcode 26 kept it. Under Xcode 27 the companion still starts and
the accessibility tree still reads correctly, but every tap, swipe and keystroke is silently
dropped — idb reports success and nothing happens on screen.
brew upgrade facebook/fb/idb-cli facebook/fb/idb-companion
brew list --versions idb-cli idb-companion # expect matching versions, >= 1.5.1
Upgrade the CLI and companion together. Newer client features are served by the companion, so
a 1.7 CLI talking to a 1.5 companion fails or falls back to older output. Companions that are
already running keep the old binary until restarted, so run idb kill after upgrading.
Pinning or bisecting idb. The facebook/fb tap carries only the newest release, so Homebrew
can't install an older one. Upstream's homebrew.py (in the root of
facebook/idb) installs CI builds instead, into a separate local
tap. It needs a GitHub token (GITHUB_TOKEN, GH_TOKEN or a logged-in gh):
curl -fsSLO https://raw.githubusercontent.com/facebook/idb/main/homebrew.py
python3 homebrew.py builds # releases and recent CI builds of main
python3 homebrew.py install <commit-sha> # or pr:<n>, run:<id>; a tag works only if it's the newest
python3 homebrew.py status # what is installed, from which build
python3 homebrew.py uninstall --restore # back to facebook/fb/idb
Coming from the old pip package? An old fb-idb (1.1.x) left in a pyenv or virtualenv shadows
the Homebrew CLI if it comes first on your PATH, so check with which -a idb. Remove it with
pip uninstall fb-idb (then pyenv rehash under pyenv). sim_health_check.sh detects this.
Xcode 27 notes
- There is no
Simulator.app. Xcode 27 replaced it with DeviceHub.app, in
Xcode.app/Contents/Applications/. Scripts drive simulators headlessly via simctl and idb,
so you rarely need either — but open -a Simulator will fail.
- Quitting DeviceHub shuts down the simulator it is hosting.
- If
idb calls start failing with Connection refused after a crash or a manual pkill, a dead
companion is still registered. Clear it with idb disconnect <udid>; sim_health_check.sh
detects this.
Features
Xcode Build with Progressive Disclosure
The build_and_test.py script wraps xcodebuild with token-efficient output. A build returns a single summary line with an xcresult ID:
Build: SUCCESS (0 errors, 3 warnings) [xcresult-20251018-143052]
Then drill into details on demand:
python scripts/build_and_test.py --get-errors xcresult-20251018-143052
python scripts/build_and_test.py --get-warnings xcresult-20251018-143052
python scripts/build_and_test.py --get-log xcresult-20251018-143052
This keeps agent conversations focused — no walls of build output unless you ask for them.
Simulator Navigation via Accessibility
Instead of fragile pixel-coordinate tapping, all navigation uses iOS accessibility APIs to find elements by meaning:
# Fragile — breaks if UI changes
idb ui tap 320 400
# Robust — finds by meaning
python scripts/navigator.py --find-text "Login" --tap
The accessibility tree gives structured data (element types, labels, frames, tap targets) at ~10 tokens default output vs 1,600-6,300 tokens for a screenshot. See AI-Accessible Apps for more on why accessibility-first navigation matters for AI agents.
Screenshot Token Optimization
When screenshots are needed (visual verification, bug reports, diffs), the skill automatically resizes and compresses them to minimize token cost. Default output across all 30 scripts is 3-5 lines — 96% reduction vs raw tool output.
| Task | Raw Tools | This Skill | Savings |
|---|
| Screen analysis | 200+ lines | 5 lines | 97.5% |
| Find & tap button | 100+ lines | 1 line | 99% |
| Login flow | 400+ lines | 15 lines | 96% |
All 30 Scripts
Every script supports --help and --json. See SKILL.md for the complete reference.
Build & Development
| Script | What it does | Key flags |
|---|
build_and_test.py | Build Xcode projects, run tests, parse xcresult bundles | --project, --scheme, --test, --get-errors, --get-warnings |
log_monitor.py | Real-time log monitoring with severity filtering | --app, --severity, --follow, --duration |
Device State
| Script | What it does | Key flags |
|---|
appearance.py | Switch dark mode, dynamic type, increase contrast, locale, region | --theme, --text-size, --contrast, --locale, --region, --reset |
location.py | Simulate GPS coordinates and run built-in scenarios | --lat, --lng, --city, --gpx, --list-scenarios, --clear |
media.py | Seed or empty the photo library (--clear needs idb 1.7+ and --yes) | --add, --clear, --yes |
Navigation & Interaction
| Script | What it does | Key flags |
|---|
screen_mapper.py | Analyze current screen, list interactive elements | --verbose, --hints, --deep |
navigator.py | Find and interact with elements semantically | --find-text, --find-type, --find-id, --tap, --expect, --enter-text, --set-value |
gesture.py | Swipes (finger direction), scrolls (content direction), pinch, long press, pull to refresh, double-tap, drag, rotate, shake | --swipe, --scroll, --pinch, --scale, --long-press, --refresh, --double-tap, --drag, --rotate, --shake |
keyboard.py | Text input, keys, key chords, key sequences and hardware buttons | --type, --key (+ --shift/--control/--option/--command), --key-sequence, --button, --clear, --dismiss |
app_launcher.py | Launch, terminate, install, deep link apps | --launch, --terminate, --install, --open-url, --list |
Testing & Analysis
| Script | What it does | Key flags |
|---|
accessibility_audit.py | WCAG compliance checking on current screen | --verbose, --output |
visual_diff.py | Compare two screenshots for visual changes | --threshold, --output, --details |
test_recorder.py | Automated test documentation with screenshots | --test-name, --output |
app_state_capture.py | Debugging snapshots (screenshot, hierarchy, logs) | --app-bundle-id, --output, --log-lines |
sim_health_check.sh | Verify environment (Xcode, simctl, IDB, Python) | — |
model_inspector.py | Inspect Core Data / SwiftData models from project files | --project-path, --raw, --show-versions |
container.py | Inspect app sandbox: list, cat, UserDefaults, Core Data, export | --ls, --cat, --userdefaults, --core-data-path, --export |
hang_watcher.py (HangBuster) | Record + summarise os_log hang events with progressive disclosure (session mode + raw NDJSON + legacy stream); auto-restart on stream death, automatic disk-cap cleanup | --start [--raw-capture --max-size-mb N --no-gzip], --stop, --get-details, --list-sessions, --diff, --budget-tokens, --auto-sample (legacy: --watch, --since) |
localization_audit.py | Audit .xcstrings catalogs for missing keys, unused keys, placeholder mismatches | --catalog, --source, --strict |
Permissions & Environment
| Script | What it does | Key flags |
|---|
clipboard.py | Copy text to simulator clipboard for paste testing | --copy, --test-name |
status_bar.py | Override status bar (time, battery, network) | --preset, --time, --battery-level, --clear |
push_notification.py | Send, list and clear simulated push notifications | --bundle-id, --title, --body, --payload, --list, --clear |