ADE iOS Simulator and Preview Lab
SkillMediaThis skill lets your AI run an iOS or SwiftUI app on a simulator so you can see a change actually working instead of just reading about it. Your AI can launch the app, interact with it, and capture or show what appears on screen.
Available today. Use it from your connected AI after setup.
No other account needed.
After adding it, ask your AI to launch your app on an iOS simulator and show you the change you want to check in action.
Then ask your AI: use the ADE iOS Simulator and Preview Lab skill
What your AI can do with it
- Launch an iOS app on a simulator
- Tap, drag, and type in the running app
- Take screenshots of what is on screen
- Stream the simulator screen while the app runs
- Inspect the elements shown on screen
- Render SwiftUI previews through Preview Lab
What this skill tells your AI
The instructions your AI receives, as published by arul28/ade in apps/desktop/resources/agent-skills/ade-ios-simulator/SKILL.md and read by ahel’s review.
Quick verify
Check support, get a device, act, prove. Use --socket so CLI actions and the desktop drawer share one session.
ade --socket ios-sim status --text
ade --socket ios-sim apps --text
ade --socket ios-sim launch --target <id> --text
ade --socket ios-sim proof-bundle --caption "Settings row renders" --text
statusis the gate. Ifsupportedis false the runtime is not a Mac — stop and say so. Do not probe further: the commands that touch a real simulator fail with the macOS-only error, and the rest answer with inert results that look like success.launchbuilds your lane worktree, installs it, and starts it.open-deviceboots a simulator with no app and builds nothing — use it to drive an app that is already installed.launchruns in the background. Add--foregroundwhen the user asked to watch it.launch --followwaits out a cold build on a real budget (17 min) and prints the launch summary. It does not stream per-step progress.screenshot --out <path>returns afilePathyou can Read. Without--outthe PNG goes to<buildRoot>/.ade/cache/ios-simulator/screenshots/and only the newest 20 survive.proof-bundlewrites the screenshot plus the machine, device, build root, on-screen elements, and recent log rows. Use it for reviewer-facing evidence.proofstill files a lone screenshot in the proof drawer.- Release when done:
ade --socket ios-sim shutdown --textfor an app session,close-devicefor a device session.
Read the current state
One command answers "what is going on with the simulator drawer".
ade --socket ios-sim status --text
It reports, in one payload:
supportedandtools— whether this machine can run a simulator at all, and which ofxcrun,xcodebuild,idbandidb_companionare present.activeDevice— the device every other command defaults to.activeSession— the app session: bundle id, app name, build root, lane, and the chat that owns it. Null means no app is running.deviceSession— a booted simulator with no app, and the chat that owns it. Null means nobody opened one.stream— the live view:running,backend,fps,bitrateKbps, andlastError. It never carries the stream address or its token.
Read the ownership fields before you act. When chatSessionId names another chat, that chat owns the session: claim takes it deliberately, and every other command drives whatever is running.
Add ade --socket ios-sim log --text when you need what the app and ADE have been doing, and ade --socket ios-sim snapshot --text when you need what is on screen.
Show the user the drawer
Agent commands do not open the drawer. The user gets a "Simulator running" pill with an Open action instead, so an agent working in the background never steals the screen.
Open it deliberately when the user asked to watch:
ade --socket ios-sim launch --target <id> --open-drawer --text
Launch a named app
apps lists every launchable target with its id, name, bundle id, and whether it is buildable or only installed.
ade --socket ios-sim apps --text
ade --socket ios-sim launch --target <id> --text # by target id
ade --socket ios-sim launch --bundle-id com.acme.app --text # an installed app
ade --socket ios-sim launch --scheme MyApp --no-build --text # skip the build
Prefer --target. A bundle id alone finds only an app that is already installed, so it starts whatever binary is on the device rather than your lane's code.
Drive the app
Name elements, not pixels. A coordinate tap is a guess that the layout did not move, and the guess fails silently: the tap lands on whatever moved into that rectangle.
ade --socket ios-sim snapshot --text
ade --socket ios-sim tap-element --label "Sign in" --text
ade --socket ios-sim fill-element --identifier email-field --value ada@example.com --text
ade --socket ios-sim wait-for-element --label Welcome --timeout-ms 8000 --text
ade --socket ios-sim assert-visible --label Welcome --text
- Run
snapshotfirst. It returns every element with aref, a label, a role, and a source file. - Query with
--ref,--identifier,--label,--text,--role, and--index. Every result reportsmatchCount, so an ambiguous query is visible instead of silent. - A
refnames its own tier.id:andcomponent:survive a re-render.label:changes when the copy changes.pos:survives nothing — read it as "this element carries no identity" and ask for an accessibility identifier. wait-for-elementreplaces a sleep. Add--goneto wait for a disappearance.assert-visibleis the last step, so the result states what was proven.- Fall back to a coordinate tap only when no query matches:
tap --x --y,drag --start-x --start-y --end-x --end-y,type --value,select --x --y. A drag takes 180ms unless you pass--duration-ms; raise it for a slow scroll.
launch returns capabilities (canTap / canType / canDrag / canInspect). Tap, type, drag, and every element action need both idb and idb_companion. Snapshots still work with xcrun alone.
Device tools
ade --socket ios-sim appearance dark --text
ade --socket ios-sim content-size accessibility-extra-large --text
ade --socket ios-sim accessibility reduce-motion on --text
ade --socket ios-sim location 37.7749 -122.4194 --text
ade --socket ios-sim permission grant photos --bundle-id <id> --text
ade --socket ios-sim push --bundle-id <id> --title Hi --body "You have mail" --text
ade --socket ios-sim open-url myapp://settings --text
ade --socket ios-sim status-bar --time 9:41 --wifi-bars 3 --battery-level 100 --text
ade --socket ios-sim relaunch --bundle-id <id> --text
ade --socket ios-sim terminate --bundle-id <id> --text
ade --socket ios-sim uninstall --bundle-id <id> --text
ade --socket ios-sim app-state --bundle-id <id> --text
ade --socket ios-sim settings --text
- Set the status bar to 9:41 with full bars before a screenshot, so the shot stays stable between runs.
relaunchrestarts the installed binary. It does not build. Uselaunchwhen the point is to see a code change.uninstallremoves the container, which is how you prove a first-run flow. It is guarded, like the event log: pass--chat-session <id>to name yourself, or--forceto take it from another chat. The other device tools are not guarded, in line withtapandtype.location --clearandstatus-bar --cleardrop an override.settingsreads appearance, content size, and accessibility back from the device. It cannot read a location or a status bar back — those report what ADE last set, and reset when the runtime restarts.
Read the device log around an action:
ade --socket ios-sim log-start --bundle-id <id> --text
ade --socket ios-sim log --since <cursor> --limit 100 --text
ade --socket ios-sim log-stop --text
One list holds the app's own os_log rows and ADE's own actions in order. Pass the returned cursor back as --since to read only new rows.
--bundle-id is required. log stream reads the whole device, so a run with no app scope returns every other app's rows and the system's. There is one log process per host as well, so log-start and log-stop refuse a chat that owns neither the device session nor the app session, and they also refuse a chat that did not start the log that is running — two chats can each hold a stake, and only one of them started it. Pass --chat-session <id> to name yourself, or --force to take a running log.
Live view
Only when the user should watch the app run:
ade --socket ios-sim live-start --fps 60 --text
ade --socket ios-sim stream-start --backend idb-h264 --scale-factor 0.5 --text
ade --socket ios-sim stream-status --text
ade --socket ios-sim stream-stop --text
simulator-window-capture is the default and the cheapest path. It captures the real Simulator.app window on this Mac, so it needs the Screen Recording grant and a visible window. idb-h264 encodes on the machine that owns the simulator and needs neither. Use idb-h264 when the runtime is a remote Mac. Agent launches do not open the drawer; the user gets a "Simulator running" pill with an Open action. Use stream-status to explain a blank live view. It reports the backend, the codec, fps and bitrate, but never the stream URL or its token: only stream-start returns those, and the token stops working when the stream ends.
Preview Lab
ade --socket ios-sim preview-status --text
ade --socket ios-sim previews --source <swift-file> --text
ade --socket ios-sim preview-match --source <swift-file> --line <n> --text
ade --socket ios-sim preview-ensure --source <swift-file> --line <n> --text
ade --socket ios-sim preview-current --text
ade --socket ios-sim preview-render --source <swift-file> --index <n> --text
To bridge the current screen into Preview Lab, select a source-backed element (or pass --source / --line), then run preview-current. That one command resolves the best nearby preview, opens or waits for Xcode, and renders through Xcode MCP.
Use preview-match when you only need the target decision without rendering. Preview fixtures must not require live sync, keychain, network, push, sockets, or production databases. Add a preview only when no useful nearby one exists.
Ownership and recovery
One chat owns a simulator session at a time. A second launch fails with IOS_SIMULATOR_OWNED_BY_OTHER_SESSION, naming the owning chat, the lane, and how long ago it claimed.
- Ownership releases automatically only when the owning chat is deleted or archived. Closing it does not free the simulator.
- The guard is cooperative, not a lock.
shutdown --force,shutdown --ignore-ownership,launch --force,claim --ignore-ownership, and passing the owner's own chat session id all get through. So the restraint is yours to keep: ask before you evict another chat. - Waiting only pays off if the owner is actively finishing. An idle chat holds the session indefinitely, so do not sit in a retry loop.
claim --lane <lane-id>attaches an already-running session to a lane. It is not a step in a normal launch, and it is a takeover when another chat owns the session.close-devicereleases a device session. ADE never shuts down a device it did not boot; pass--shutdownto shut one down anyway.
Gotchas
IOS_SIMULATOR_TARGET_ROOT_MISMATCHmeans the target id came from a different build root. Re-runade --socket ios-sim apps --textand use a fresh id.IOS_SIMULATOR_NO_BUILDABLE_TARGETmeans nothing buildable resolved under the root and you named no target, so the only candidates would run stale code. Pass--target-id/--bundle-idonly if you deliberately want the installed app.IOS_SIMULATOR_LAUNCH_IN_PROGRESSmeans a launch is already running. Wait for it.shutdown --forceis the escape hatch if it is genuinely wedged.IOS_SIMULATOR_LANE_NOT_RESOLVEDmeans the lane has no worktree on this machine. It is a hard failure on purpose: the alternative is building the primary checkout and reporting someone else's code as verified. Pass--project-root.screenshot --outandproof-bundle --outresolve relative paths against the build root, and the path must stay inside it. The returned path is absolute; Read that rather than rebuilding it.appsdrives project and scheme detection. If it does not find your app, re-run it and report the selected project, scheme, and build output. Do not work around it with symlink projects, fake schemes, or repo-layout shims.preview-current/preview-matchreturningno-contextmeans nothing on screen is source-backed. Runsnapshot,selecta source-backed element, or pass--source/--line.--textreads two ways. A bare--textis ADE's output mode.--text <value>is the element query's substring match; write--text-match <value>when you want no ambiguity.
Signals
- GitHub stars
- 105
- Forks
- 12
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
ade-ios-simulator- Source
- github.com/arul28/ade