Blog
By Daniel Bernal
·
April 01, 2026
·
8 min read
Apple ships xcrun simctl with Xcode. It works. It's also a museum piece.
The tool is what you use when you need to control an iOS simulator from the terminal: boot, install, launch, take screenshots, manage state. Every tutorial from the last decade shows the same pattern: list simulators, copy a UDID, paste it into the next command. You can get away with names when they're unique, but the UDID copy-paste is what the docs, Stack Overflow answers, and most CI scripts still show.
Four commands. Four UDID copies. Zero structured output. This post shows what a modern version of this workflow looks like.
What's wrong with simctl
simctl isn't broken. It's just aged. The design hasn't changed since it was introduced.
UDIDs are the default mental model. simctl accepts device names when they're unambiguous (xcrun simctl boot "iPhone 16 Pro" works) and supports a booted shortcut, but the moment you have duplicate names, two booted simulators, or a CI script targeting specific iOS versions, you're back to listing and copying UDIDs. The convenience only works until it doesn't.
Unstructured list output. xcrun simctl list returns formatted text grouped by runtime. Parsing it in a script means grep and awk. The --json flag exists but returns a deeply nested structure that's awkward to filter.
No validation. Pass a bad UDID and some commands fail silently. Others print cryptic errors. The tool assumes you know what you're doing.
Split across tools. simctl handles simulators. devicectl handles physical devices. They have completely different syntax for similar operations. Install to a simulator versus install to a device requires different flags, different arguments, different mental models.
The FlowDeck way
One command set. Simulators and devices use the same patterns. Names instead of UDIDs. JSON on every command.
List simulators
Structured output, filterable by platform, JSON-first for automation.
Boot, shutdown, erase
simctl accepts names too when they're unambiguous. The difference is what happens when they aren't. FlowDeck resolves ambiguity predictably (platform, runtime, and availability all factor in) so scripts keep working when you add a second "iPhone 16 Pro" for a different iOS version.
Create and delete simulators
That last flag is the one that saves hours. --unavailable deletes every simulator referencing a runtime you've since uninstalled, which is the main reason your ~/Library/Developer/CoreSimulator folder is 40GB.
Run apps
flowdeck run handles build, install, and launch in one command. No separate install step. No figuring out the .app path in derived data. Launching returns an app ID that you use to stream logs or terminate the app.
Screenshots and UI
Screenshot and full accessibility tree in one call, returned as structured data. Not just an image file, actual UI element data you can query.
Manage runtimes
simctl added runtime management in Xcode 15 with xcrun simctl runtime add. It works. The syntax is harder to remember, the arguments take image paths or identifiers, and listing what's actually installable requires a separate list subcommand with different flags. FlowDeck's interface is simpler: list what you have, list what you can get, install by version.
Side-by-side
Task | simctl | FlowDeck |
|---|---|---|
List simulators |
|
|
Boot by name | Works when name is unique; ambiguous names need UDID |
|
Install runtime |
|
|
Install + launch app |
|
|
JSON output | Nested structure on | Every command |
Device + simulator parity | Different syntax | Same commands |
The interactive mode shortcut
For interactive simulator work, flowdeck -i gives you the full terminal UI for picking simulators, booting them, running apps, and watching logs. No commands to memorize. Keyboard shortcuts for every operation.
Here's a quick look at interactive mode in action:
What this is and isn't
FlowDeck calls simctl and devicectl under the hood. It doesn't replace Apple's tools, it gives you a better interface to them. If you need a simctl feature FlowDeck doesn't expose, you can still call simctl directly. The two coexist.
Native Swift. Runs locally. No telemetry.
How do I boot an iOS simulator from the terminal?
Does xcrun simctl support JSON output?
Can I install a simulator runtime from the command line?
What's the difference between simctl and devicectl?
What replaced idb for iOS simulator automation?
#ai-agents
#claude-code
#codex
#comparison
#ios-development
