SimDeck is a developer tool built for streamlining mobile app development using agents. Drive iOS Simulators and Android emulators from browser & CLI.
npx simdeckOpen the URL in your IDE of choice, for example in-app browser in Codex.
Install the CLI globally for agentic-use:
npm i -g simdeck@latestAfter installing the CLI, install the Codex skill so agents know the stable SimDeck workflow:
npx skills add NativeScript/SimDeck --skill simdeck -gFor VS Code, install the nativescript.simdeck-vscode extension to open the simulator
view inside the editor.
- Supports streaming both iOS simulators and Android emulators
- Full simulator control & inspection using private iOS accessibility APIs and Android UIAutomator - available using
simdeckCLI - Real-time screen
describecommand using accessibility view tree - available in token-efficient format for agents - Profiling built-in: CPU, memory, disk writes, network throughput, hang signals, and stack sampling
- CoreSimulator chrome asset rendering for device bezels
- NativeScript, React Native, Flutter, UIKit and SwiftUI runtime inspector plugins to debug app's view hierarchy live
simdeck/testfor fast JS-based app tests that can query accessibility state and drive simulator controls
Full documentation lives at simdeck.nativescript.org, with guides, the CLI reference, the REST API, the video pipeline, and the inspector protocols.
For hosted pull request simulator sessions, use the GitHub Actions integration documented in the GitHub Actions guide.
simdeckTo focus a specific simulator by name or UDID, pass it as the only argument:
simdeck "iPhone 17 Pro Max"simdeck -d for detached start, simdeck -k to kill the background daemon, and simdeck -r to restart it.
The served loopback browser UI receives the generated API access token automatically. LAN clients should pair with the printed code before receiving the API cookie.
For pairing with SimDeck iOS app:
simdeck pairThis starts or refreshes the global LaunchAgent-backed SimDeck service, prints
local, LAN, and Tailscale URLs when available, and shows a QR code with a
simdeck://pair link. The QR contains the pairing code plus all detected
non-loopback addresses, so pairing once can save both the LAN and Tailscale
routes with the same service token.
Normal service restarts preserve that token so paired clients stay connected.
Use simdeck service reset only when you want to rotate the service token and
restart the LaunchAgent.
The LaunchAgent service uses port 4310. Project daemons start at port 4311 and
probe upward when that port is busy. When the service is active, simdeck and
simdeck ui print the existing service endpoints instead of starting a project
daemon; use the daemon subcommand when you explicitly want a workspace daemon.
CLI commands automatically use the same warm daemon:
simdeck list
simdeck use <udid>
simdeck tap 0.5 0.5 --normalized
simdeck tap "Continue"
simdeck describe --format agent --max-depth 2 --interactive
simdeck press @e3
simdeck snapshot --format agent --max-depth 2 -i
simdeck --device <other-udid> describe --format agent --max-depth 2simdeck list
simdeck use <udid>
simdeck boot <udid>
simdeck shutdown
simdeck erase
simdeck install /path/to/App.app
simdeck install /path/to/App.ipa
simdeck install android:<avd-name> /path/to/app.apk
simdeck uninstall com.example.App
simdeck open-url https://example.com
simdeck launch com.apple.Preferences
simdeck toggle-appearance
simdeck pasteboard set "hello"
simdeck pasteboard get
simdeck screenshot --output screen.png
simdeck screenshot --with-bezel --output screen-bezel.png
simdeck record --seconds 5 --output screen-recording.mp4
simdeck stream --frames 120 > stream.h264
simdeck describe
simdeck describe --format agent --max-depth 4
simdeck describe --format agent --max-depth 4 --interactive
simdeck snapshot --format agent --max-depth 4 -i
simdeck describe --point 120,240
simdeck wait-for --label "Welcome" --timeout-ms 5000
simdeck wait --label "Welcome" --timeout-ms 5000
simdeck assert --id login.button --source auto --max-depth 8
simdeck tap 120 240
simdeck tap --label "Continue" --wait-timeout-ms 5000
simdeck tap --id com.apple.settings.screenTime --expect-id BackButton
simdeck tap "Continue"
simdeck press @e3
simdeck back
simdeck swipe 200 700 200 200
simdeck gesture scroll-down
simdeck pinch --start-distance 160 --end-distance 80
simdeck rotate-gesture --radius 100 --degrees 90
simdeck touch 0.5 0.5 --phase began --normalized
simdeck touch 120 240 --down --up --delay-ms 800
simdeck key enter
simdeck key-sequence --keycodes h,e,l,l,o
simdeck key-combo --modifiers cmd --key a
simdeck type "hello"
simdeck type --file message.txt
simdeck button lock --duration-ms 1000
simdeck button volume-up
simdeck button action --duration-ms 1000
simdeck button digital-crown
simdeck crown --delta 50
simdeck button left-side-button
simdeck batch --step "tap --label Continue --expect-label Done" --step "type 'hello'" --step "back"
simdeck dismiss-keyboard
simdeck button software-keyboard
simdeck home
simdeck app-switcher
simdeck rotate-left
simdeck rotate-right
simdeck chrome-profile
simdeck logs --seconds 30 --limit 200
simdeck processes
simdeck stats --watch
simdeck sample --seconds 3simdeck list defaults to compact JSON for agent-friendly device selection.
Use simdeck list --format json for the full inventory with paths and display
metadata.
simdeck use <udid> stores a default simulator for the current project
directory. Most device commands accept [<udid>]; when it is omitted, SimDeck
uses --device, SIMDECK_DEVICE, SIMDECK_UDID, the saved project default,
or the only booted simulator, in that order.
import { connect } from "simdeck/test";
const sim = await connect({ udid: "<udid>" });
try {
await sim.tap(0.5, 0.5);
await sim.waitFor({ label: "Continue" });
await sim.screenshot();
await sim.screenshot({ withBezel: true });
await sim.record({ seconds: 5 });
} finally {
sim.close();
}connect() starts the project daemon when needed, reuses it when it is already
healthy, and only stops daemons it started itself. Pass udid to connect()
to make it the default for session methods; each method still accepts an
explicit UDID as the first argument when needed. Query helpers such as
tree(), query(), waitFor(), assert(), and selector tapElement()
default to source: "native-ax" for fast agent control; pass
source: "auto" when a test intentionally wants richer framework inspector
trees first.
NativeScript apps can connect directly to the running server from JS and expose their NativeScript logical hierarchy plus raw UIKit backing views without linking the Swift inspector framework:
import { startSimDeckInspector } from "@nativescript/simdeck-inspector";
if (__DEV__) {
startSimDeckInspector({ port: 4310 });
}The runtime connects to GET /api/inspector/connect as a WebSocket. The Rust
server prefers connected NativeScript inspectors for hierarchy requests and
falls back to the Swift TCP inspector or the built-in native accessibility
bridge when no matching app inspector is available.
React Native apps can expose their component tree and Metro dev-mode source locations with the React Native inspector package:
import "react-native-simdeck/auto";
import "expo-router/entry";Import it before expo-router/entry or AppRegistry.registerComponent(...)
so the package can capture React Fiber commits. The auto entrypoint no-ops
outside development, reads EXPO_PUBLIC_SIMDECK_PORT when present, and
otherwise scans common SimDeck daemon ports.
Flutter apps can expose their widget tree, render frames, semantics metadata, and debug widget creation locations with the Flutter inspector package:
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:simdeck_flutter_inspector/simdeck_flutter_inspector.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
if (kDebugMode) {
startSimDeckFlutterInspector(port: 4310);
}
runApp(const App());
}Contributors should read CONTRIBUTING.md for local build instructions, the dev workflow, and architecture notes.
Copyright OpenJS Foundation and NativeScript contributors. All rights reserved. The OpenJS Foundation has registered trademarks and uses trademarks. For a list of trademarks of the OpenJS Foundation, please see our Trademark Policy and Trademark List. Trademarks and logos not indicated on the list of OpenJS Foundation trademarks are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.
The OpenJS Foundation | Terms of Use | Privacy Policy | OpenJS Foundation Bylaws | Trademark Policy | Trademark List | Cookie Policy

