serve-sim

작성자: expo

npx serve-sim을 사용하여 실행 중인 iOS, iPad 또는 Apple Watch 시뮬레이터를 제어하고 스트리밍합니다. 시뮬레이터 미리보기, 탭, 제스처, 하드웨어 버튼, 회전 등에 사용합니다.

npx skills add https://github.com/expo/serve-sim --skill serve-sim

serve-sim

Drive an Apple Simulator (iOS, iPad, Apple Watch) from an agent using the serve-sim CLI. serve-sim captures simulator IOSurfaces through its in-process native addon, exposes HTTP or WebRTC video plus WebSocket input, and serves a React preview UI. This skill teaches an agent the exact CLI surface, the gesture JSON shape, the gotchas, and the recommended workflows.

When to use

  • The user wants an agent to tap, swipe, drag, pinch, or send hardware buttons to a running Apple Simulator.
  • The user wants to stream a simulator to a browser (local, LAN, or tunneled) for review or remote control.
  • The user wants to inject a synthetic camera feed (file, webcam, or animated placeholder) into a specific app on the simulator.
  • The user wants to toggle CoreAnimation debug overlays (off-screen rendering, blended layers, slow animations) for performance work.
  • The user wants to simulate a memory warning or rotate the device programmatically.
  • The user wants to read the simulator's accessibility tree to find UI elements without pixel hunting.
  • The user wants to grant, revoke, or reset an app's privacy permissions — camera, photos, location, contacts, or push notifications.

When NOT to use

  • Android emulators → use adb shell tooling.
  • Building or installing an iOS app → use xcodebuild or xcrun simctl install.
  • React Native in-app runtime debugging (Redux state, network inspection, component tree) → use rn-debugger tooling.
  • Real iOS hardware devices → use xcrun devicectl or Xcode.

Prerequisites

Before any other action, verify the host satisfies these. If something is missing, tell the user exactly what to install — do not proceed.

RequirementCheck commandWhy
macOS hostuname -s returns Darwinserve-sim only runs on macOS
Apple siliconuname -m returns arm64bundled native components are arm64-only
Xcode CLI toolsxcrun --version exits 0simctl is the underlying simulator driver
Node.js ≥20node --version ≥20serve-sim supports maintained Node.js LTS releases
macOS 14+ (optional)sw_vers -productVersion ≥14Required ONLY for camera subcommand

A bundled helper script is available: scripts/check-prereqs.sh. Run it; if it exits non-zero, surface the message to the user.

A booted simulator is required for most subcommands. Check with xcrun simctl list devices booted. If none are booted, tell the user to open Xcode → Simulator or to run xcrun simctl boot <UDID>.

Mental model

┌───────────────┐  IOSurface  ┌────────────────────┐  HTTP/WebRTC  ┌─────────┐
│ iOS Simulator │ ──────────► │ serve-sim process  │ ────────────► │ Browser │
└───────────────┘              │ + native N-API     │   WebSocket   └─────────┘
                               └────────────────────┘
                                         ▲
                                  state files in
                                $TMPDIR/serve-sim/

Key invariants the agent must respect:

  • All coordinates are normalized 0..1, with (0, 0) at top-left and (1, 1) at bottom-right of the display. Never pass pixel coordinates.
  • One registered server per device. A server process may capture several simulators, and the capture API itself is non-exclusive.
  • State lives in $TMPDIR/serve-sim/server-{udid}.json. Use serve-sim --list to query it; do not read the JSON directly unless you know what you are doing.
  • The orientation set via rotate is remembered by the helper, and subsequent gestures are rotated client-side. An agent that sends raw coords after a rotation does not need to compensate manually.

Common operations

GoalCommandNotes
Start preview servernpx @expo/serve-sim [device]Default preview at http://localhost:3200; helper routes are same-origin. Foreground process.
Start WebRTC previewnpx @expo/serve-sim --transport webrtc [device]Uses WebRTC media and the ordered helper WebSocket for input.
Start headless / daemonnpx @expo/serve-sim --detach [device]Returns JSON with pid, port, url. Use for agent loops.
Show stream in host's previewnpx @expo/serve-sim --detach -q → hand off url to host preview toolSee "Showing the stream in your agent's preview" section.
List running streamsnpx @expo/serve-sim --listAdd -q for JSON-only output.
Stop all helpersnpx @expo/serve-sim --killPass [device] to stop a specific one.
Single tapnpx @expo/serve-sim tap <x> <y><x> <y> in 0..1. Use this, not gesture, for plain taps. See "Critical gotcha" below.
Multi-step gesturenpx @expo/serve-sim gesture '<json>'See references/gestures.md.
Hardware buttonnpx @expo/serve-sim button <name>Names: home, swipe_home, app_switcher, lock, siri, side_button. See references/buttons-rotation.md.
Rotate devicenpx @expo/serve-sim rotate <orientation>portrait, portrait_upside_down, landscape_left, landscape_right.
Simulate memory warningnpx @expo/serve-sim memory-warningEquivalent to Debug → Simulate Memory Warning.
CoreAnimation debugnpx @expo/serve-sim ca-debug <option> <on|off>Options: blended, copies, misaligned, offscreen, slow-animations. See references/ca-debug.md.
Inject camera feednpx @expo/serve-sim camera <bundle-id> [--file <path>|--webcam [name]](Re)launches the app with the camera dylib attached. macOS 14+ only. See references/camera.md.
Hot-swap camera sourcenpx @expo/serve-sim camera switch <placeholder|webcam|file> [arg]No app relaunch.
Manage app permissionsnpx @expo/serve-sim permissions <grant|revoke|reset|list> <permission> <bundle-id>Camera, photos, location, push notifications, contacts, etc. See references/permissions.md.
Read accessibility treeDerive /ax from the helper base in streamUrlReturns axe-style JSON. See references/endpoints.md.

Most subcommands accept -d <udid|name> to target a specific device when several are booted.

Critical gotcha: prefer tap over gesture for taps

Each serve-sim gesture call opens its own WebSocket. If you issue two back-to-back gesture calls — one with {"type":"begin",...} and one with {"type":"end",...} — the simulator receives them with enough latency between them that the touch is interpreted as a long-press, not a tap. This is a deliberate constraint of the protocol, not a bug to work around.

Rule: for any single-shot tap, use serve-sim tap <x> <y>. Only use gesture for drags, swipes, or multi-step interactions where you must thread the same socket across begin → move × N → end.

Targeting a specific device

When multiple simulators are booted, every subcommand accepts -d <udid|name>. The name match is case-insensitive against the device name returned by xcrun simctl list devices booted. Examples:

npx @expo/serve-sim tap 0.5 0.5 -d "iPhone 16 Pro"
npx @expo/serve-sim button home -d ABC12345-...
npx @expo/serve-sim --list                                # show all running streams

If the user has only one booted simulator, omit -d entirely. The skill should prefer auto-detection over hard-coding device names.

Output modes

By default, serve-sim prints human-readable status to stdout. For agent loops, prefer JSON output:

npx @expo/serve-sim --list -q          # JSON object; one stream is top-level, many are under .streams
npx @expo/serve-sim --detach -q        # JSON with pid/port/url after spawn
npx @expo/serve-sim camera status -q   # JSON with {alive, source, mirror, ...}

Parse -q output programmatically. Never parse the non--q human output — it can change between versions.

Showing the stream in your agent's preview

When the user asks to "see the simulator here", "view it in preview", "open it in this tool", or similar, the goal is to stream the simulator into the same surface the user is chatting with. serve-sim returns a regular HTTP URL — the agent's job is to surface that URL and, if the host exposes a preview tool, hand it off.

Steps:

  1. Start serve-sim and capture the URL:

    npx @expo/serve-sim --detach -q
    

    This returns JSON like {"pid":..., "port":3100, "url":"http://localhost:3100", "streamUrl":"http://localhost:3100/helper/<udid>/stream.mjpeg", ...}. The url field is the human-facing preview UI; streamUrl is the raw MJPEG endpoint.

  2. Always surface the URL plainly in your response so the user can fallback to opening it manually in any browser.

  3. Probe your host's preview tool and hand off the URL if one exists. Examples of tool names you may see in your toolset:

    • preview_start (Claude Code) — call it with { url: "<url>" }.
    • mcp__Claude_Preview__preview_start (some MCP setups).
    • A browser_open, open_url, or similar URL-opening tool — pass the URL.
    • Cursor / Codex CLI / others may not expose a preview tool to the agent. In that case, just print the URL and tell the user how to open it (their browser, their IDE's built-in browser pane, etc.).
  4. Do not assume any specific preview tool exists. Inspect the tools available to you in the current session. If one matches the description above, use it. If none does, fall back to step 2 (print the URL prominently).

The stream stays alive until npx @expo/serve-sim --kill. Multiple clients (the host's preview + the user's browser + a tunnel) can read the same URL simultaneously.

See references/workflows.md workflow "Show the simulator stream in the host's preview" for the full recipe.

Workflows

For complete end-to-end recipes (UI automation, camera testing, accessibility-driven taps, deep-link flows, preview handoff), see references/workflows.md. The reference covers the patterns documented in serve-sim's own AGENTS.md.

Cleanup

Always stop helpers when finished, unless the user explicitly wants them to keep running:

npx @expo/serve-sim --kill            # stop all
npx @expo/serve-sim --kill "iPhone 16 Pro"  # stop one

Orphan servers occupy their recorded ports and prevent fresh starts.

Anti-patterns

  • Do not pass pixel coordinates. All coords are normalized 0..1. If the user gives pixel values, divide by the screen dimensions reported by GET /config.
  • Do not use gesture for plain taps. Use tap. See "Critical gotcha" above.
  • Do not assume npx @expo/serve-sim is already running. Verify with --list or by checking $TMPDIR/serve-sim/server-{udid}.json. If absent, start it explicitly.
  • Do not skip the prerequisites check on the first invocation in a session. Wrong macOS version, missing Xcode CLI tools, or Node <20 produce confusing errors downstream.
  • Do not invent button names. Only these six are valid: home, swipe_home, app_switcher, lock, siri, side_button. See references/buttons-rotation.md for the source-of-truth list.
  • Do not parse the non-quiet human output. Use -q for JSON.
  • Do not leave camera helpers running across unrelated tasks. Stop them with npx @expo/serve-sim camera --stop-webcam when done.
  • Do not guess coordinates when an accessibility lookup returns no match. If you fetched the AX tree (e.g. GET /ax) to find a target element and the query returned no result, fail loudly — tapping a guessed spot is almost always worse than reporting "target not found" back to the user. See references/workflows.md workflow 1 for the guard pattern.

Reference index

expo의 다른 스킬

expo-observe
expo
Use for anything related to EAS Observe — adding `expo-observe` to an Expo project (AppMetricsRoot/ObserveRoot HOC, markInteractive, the useObserve hook, the…
upgrading-expo
expo
Expo SDK 버전 업그레이드 및 의존성 충돌 해결을 위한 구조화된 가이드입니다. 진단, 캐시 정리, 네이티브 변경 사항을 위한 프리빌드 워크플로우를 포함한 단계별 업그레이드 프로세스를 제공합니다. SDK 53–55의 주요 변경 사항(React 19 마이그레이션, New Architecture 기본값, React Compiler 설정, 네이티브 모듈 업데이트(탭, 오디오, 비디오))을 다룹니다. expo-av, expo-permissions, AsyncStorage와 같은 패키지에 대한 폐기 맵과 대체 권장 사항을 포함합니다...
expo-upgrade
expo
프레임워크(OSS). Expo SDK 버전 업그레이드 및 의존성 문제 해결을 위한 가이드라인
apidevelopmenttesting
expo-data-fetching
expo
Framework (OSS). Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, React Query, SWR, error handling, caching, offline support, and Expo Router data loaders (`useLoaderData`).
expo-ui-swift-ui
expo
`@expo/ui/swift-ui` package lets you use SwiftUI Views and modifiers in your app.
serve-sim-placeholder-assets
expo
serve-sim의 Device Hub 스타일 시뮬레이터 플레이스홀더 에셋을 로컬 Xcode/CoreTypes 리소스에서 감사하고 업데이트합니다. 새로운 Xcode 또는 macOS를 설치한 후에 사용하십시오…
add-app-clip
expo
iOS App Clip 타겟을 Expo 앱에 추가합니다. 사용자가 App Clip, AASA, apple-app-site-association, appclips, 스마트 앱 배너를 언급하거나 출시를 원할 때 사용하세요.
building-native-ui
expo
네이티브 Expo 앱 구축을 위한 완벽 가이드로, 라우팅, 스타일링, 컴포넌트 및 플랫폼 규칙을 다룹니다. Expo Router 기초, 네이티브 탭, 스택 내비게이션, 모달, 폼 시트와 상세한 경로 구조 규칙을 포함합니다. Apple 휴먼 인터페이스 가이드라인에 맞춘 스타일링 규칙(플렉스박스 레이아웃, 세이프 에어리어 처리, 애니메이션, CSS boxShadow를 통한 그림자, 반응형 디자인 패턴)을 다루며, 라이브러리 선호도(expo-image의 SF Symbols, expo-audio, expo-video 등)를 문서화합니다.