simgrid-cli
v0.5.0
Published
One grid for all your simulators — run multiple Expo projects on multiple devices, in parallel, without the alt-tab dance.
Maintainers
Readme
simgrid
One grid for all your simulators — run multiple Expo projects on multiple devices, in parallel, without the alt-tab dance.
📖 Website & docs: https://matthysdev.github.io/simgrid/ · 📦 npm: simgrid-cli
The problem
Working on several Expo / React Native projects at the same time is painful. Launch one project and it grabs whichever simulator it feels like — often the wrong one, or one already claimed by another project. You end up alt-tabbing between windows, killing and restarting Metro, resolving port conflicts by hand, and losing track of which app is running where.
simgrid solves this at the device layer: it knows which simulators and emulators exist, which ones already have your dev build installed, which ones are busy with another project, and it routes each project to the right device automatically.
For Expo projects. simgrid is built for Expo apps (managed or bare-with-Expo): it reads
app.json/app.config.ts, usesexpo-dev-clientdeep links, and drivesexpo start/expo run. Dev-build freshness is computed with Expo Fingerprint. A plain (non-Expo) React Native project won't be detected correctly.
Quick start
# Install (published on npm as "simgrid-cli"; the command is "simgrid")
npm i -g simgrid-cli
# Wire simgrid into one project (rewrites package.json "start" to "simgrid";
# the previous start script is kept as "start:orig")
npx simgrid-cli init
# Then start the project the usual way
npm start
# or
bun start
# Or run without init — works directly
npx simgrid-cliOn first run simgrid shows an interactive picker. Pick one or more devices, and it handles everything: booting, Metro, deep-linking the dev client.
How it works
1. Read project identity (app.json / app.config.ts → name, scheme, bundle ID)
2. Load shared registry (~/.simgrid/state.json) and reconcile against reality
(sessions whose Metro process is no longer alive are dropped)
3. Discover all simulators / emulators / physical devices
4. Annotate each device: dev build installed? busy with another project?
5. Show the interactive picker (pre-checked = your last choice for this project)
6. For each selected device:
a. Boot / clone if needed
b. Allocate a free Metro port (one port per project, reused across devices)
c. Start Metro (expo start --port N) — or reuse the already-running one
d. Deep-link the dev client: storefront://expo-development-client/?url=http%3A%2F%2Flocalhost%3A8081
(or run expo run:<platform> --port N --device <id> if no build is installed yet)
7. Register the session; deregister cleanly on Ctrl-C or Metro exitPicker
simgrid Storefront
iOS Simulators
iPhone / iPad
› [x] iPhone 15 ✅ build installed
[ ] iPhone 15 Pro ✅ build installed · 🔴 busy: Dashboard :8082
[ ] iPhone SE (3rd gen) ⚙️ will build
Android Emulators
[ ] Pixel 7 (emulator) ✅ build installed
Android Devices
[ ] Matthys's iPhone ✅ build installed
↑↓ navigate · space select · ⏎ launch (pre-checked = your last pick)Multi-select is supported — launch the same project on several devices at once. The 🔴 busy tag comes from the registry (PID-checked at startup). Devices with a build already installed are listed first for instant launch.
Shared registry
~/.simgrid/state.json tracks which project runs on which device, the Metro port, and the process PID. Every simgrid run reconciles the registry by checking live PIDs before showing the picker, so sessions whose Metro process has exited are dropped automatically. Device state (booted / offline) is not checked during reconciliation — it is annotated live when the device list is built. No daemon required.
Commands
| Command | Description |
|---|---|
| simgrid / simgrid start | Interactive picker, then launch |
| simgrid --profile <name> | Launch a saved device set in one shot — saved automatically the first time you use a new name |
| simgrid init | Wire "start": "simgrid" into this project's package.json |
| simgrid status | Show which project is running on which device |
| simgrid logs [device] | Stream a running device's system logs (filtered to the app on iOS sims). Omit device to pick when several are running |
| simgrid profiles | List the saved device profiles for this project |
| simgrid doctor | Check that xcrun / adb / emulator are installed, with install hints for whatever is missing |
| simgrid stop | Stop this project's sessions and deregister them |
Profiles
Pass --profile <name> to skip the picker and relaunch a remembered set of devices:
simgrid --profile demo # first run: pick devices → saved as "demo"
simgrid --profile demo # next runs: launches that exact set, no picker
simgrid profiles # list saved profiles for this projectIf a profile's devices aren't available right now, simgrid falls back to the picker.
Requirements
- Node ≥ 18
- iOS simulators: macOS + Xcode (provides
simctl,devicectl) - Android emulators / devices: Android SDK with
adbandemulatorinPATH - Your project must use an Expo dev build (expo-dev-client), not Expo Go — see Expo development builds
Known limitations (v1)
- iOS physical devices — no build detection.
hasBuildis always false for physical iPhones/iPads because there is no cheap equivalent ofsimctl get_app_containerover USB. Launch always goes throughexpo run:ios --device, which installs if needed. - Paired-but-disconnected iPhones are listed. simgrid hides only devices where
tunnelState: unavailable; a paired iPhone that is simply unplugged still appears and will connect on demand when launched. - Offline AVDs show "⚙️ will build". Build detection (
pm list packages) requires a booted device, so a shutdown AVD always shows as needing a build even if it already has one. It will fast-launch after the first boot. - Busy iOS simulator → clone; cleanup is manual. When you pick a simulator that another project is already using, simgrid offers to clone it (
simctl create "iPhone 15 — simgrid"). Clones persist after the session; delete them manually in Simulator.app or withxcrun simctl delete <udid>. Automated cleanup is planned for v2. expo runport reuse. simgrid passes--porttoexpo run:<platform>so the freshly built app connects to the already-running Metro instance on that port. The--no-bundlerflag is not used because it is broken on recent Expo SDK versions.simgrid logs— no physical iOS device support. Logs stream from iOS simulators (simctl spawn … log stream) and Android emulators/devices (adb logcat). Streaming the system log from a physical iPhone/iPad over USB isn't wired up yet.
simgrid vs baguette
baguette and simgrid operate at two completely different levels and are complementary.
baguette is a low-level simulator control tool: it boots a simulator headlessly, drives it with tap/swipe/screenshot, streams video at 60fps, inspects the accessibility tree, and exposes a web UI. It targets iOS only (Apple Silicon / Xcode private frameworks) and runs a resident daemon (baguette serve). Think of it as a successor to idb/AXe, oriented toward automation, CI, and agents that need to drive a UI.
simgrid operates one layer above: it asks "which project goes to which device?". It reads your app.json, detects installed dev builds across iOS and Android, manages Metro ports across multiple projects, and deep-links the Expo dev client. It is daemonless by design (better for open-source adoption) and works across iOS simulators, Android emulators, and physical devices.
Use baguette to control what happens inside a simulator; use simgrid to decide which simulator runs what.
Contributing
Contributions are welcome. The codebase is vanilla TypeScript (ESM, Node ≥ 18), with no framework. Each device adapter (src/devices/ios-sim.ts, android.ts, ios-device.ts) is isolated behind a common interface, making it straightforward to add a new platform or mock an existing one for testing. Run npm test / bun run test for the test suite.
License
MIT — see LICENSE.
