Skip to content

CLI shape: make physical iOS devices a first-class target (listing, device-kind field, top-level + daemon routing) #115

Description

@onevcat

Problem inventory before we design the CLI shape. This issue only catalogues the current state and the inconsistencies; the actual design is deferred to follow-up discussion.

The theme: sim-use ios-device is currently an appendage, not a first-class target. If we commit to physical iOS support, it should behave like every other target (with some capabilities legitimately restricted), rather than living off to the side.

Current state

1. sim-use devices listing coverage

Target Listed by sim-use devices? Source
iOS Simulator ✅ simctl list devices
iOS physical device ❌ blind spot would need FBDeviceControl (what ios-device devices already uses)
Android emulator ✅ adb devices
Android physical device ✅ adb devices

simctl only knows simulators, so sim-use devices never shows a plugged-in iPhone — even though sim-use ios-device devices lists it fine through a different code path. Android, by contrast, shows both emulators and physical devices. So the listing is asymmetric: Android physical is visible, iOS physical is not.

2. No device-kind field

Device (the sim-use devices row / --json schema) carries platform ∈ {ios, android}, plus free-form state, runtime, name, deviceId. There is no field distinguishing simulator / emulator / physical:

  • Android emulator vs physical device is only inferable from the serial shape (emulator-5554 prefix), not a structured field.
  • iOS simulator vs physical has no field either (and physical isn't listed at all today).

The PLATFORM column answers "ios or android" but not "simulator, emulator, or real device", which is exactly the distinction that determines available capabilities.

3. Physical iOS is not routed like other targets

Surface iOS Simulator Android iOS physical
Top-level verbs (ui, tap, screenshot, …) ✅ routed by UDID shape (PlatformRouter) ✅ ❌ rejected — must use the ios-device namespace
Per-UDID daemon (auto-spawn, amortised init) ✅ ✅ ❌ — every ios-device call opens its own DTX session
Namespace sim-use ios <verb> (iOS-only extras) sim-use android <verb> sim-use ios-device <verb> (the whole surface)
  • PlatformRouter has only .iOSSim / .android cases; a physical iOS UDID resolves to neither and top-level verbs reject it (pointing at ios-device).
  • The ios-device subcommands are plain AsyncParsableCommands that call DeviceSession.withClient directly — they do not implement SimUseExecutableCommand, so they bypass the daemon entirely.

This contradicts sim-use's stated design: "one command surface; the UDID shape decides the backend." A physical iOS device is the only target you address through a wholly separate command tree.

Problems to solve (design deferred)

  1. Listing parity — sim-use devices should surface physical iOS devices (~~probably opt-in given FBDeviceControl discovery cost; see the ~5 s attachment-quiescence wait~~), so discovery is uniform across every target. (Edit 2026-08-27: the opt-in rationale is retracted — measured discovery is ~0.4 s with a device attached, and fix: bail fast from ios-device discovery when no device is attached #117 cut the no-device path to ~1 s, so default-on is plausible; see the measurement comments below.)
  2. Device-kind field — add a structured field distinguishing simulator / emulator / physical (name TBD), orthogonal to platform. Lets the table and --json say "ios physical" vs "ios simulator" vs "android emulator" explicitly, and lets callers reason about which capabilities apply.
  3. First-class routing — decide whether physical iOS joins PlatformRouter + top-level verb forwarding + the daemon, so sim-use ui / sim-use tap work against a plugged-in iPhone (with the restricted capability set), instead of requiring ios-device. This is the biggest question and where "first-class citizen vs restricted appendage" is actually decided.
  4. Capability model — with physical iOS routed through the top level, unsupported verbs (coordinate tap, swipe, gesture, screenshot, --json, …) need a consistent "capability not available on this target" contract rather than today's hard namespace split. (Edit 2026-08-27: screenshot moved to the supported column — shipped over CoreDevice in feat: add ios-device screenshot backed by devicectl #118. The contract also needs to express "supported by the tool, refused by this device" — CoreDevice screen recording is capability-gated per device.)

Constraints / facts that bound the design

Non-goals for this issue

No implementation, no chosen design — this is the problem catalogue we align on first. Follow-ups will design (a) the device-kind field + listing parity, and (b) the routing/daemon integration, likely as separate changes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions