Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Agents choose the right skill from the task context and each skill's description
| `expo-cicd-workflows` | EAS Workflow YAML files and CI/CD automation. |
| `expo-observe` | EAS Observe setup and launch, route, event, and version metrics. |
| `eas-update-insights` | EAS Update health, crash rates, launch counts, payload size, and rollout gates. |
| `eas-simulator` | Run and drive your app on a remote iOS simulator or Android emulator on EAS cloud — from the CLI or an agent, with a live browser preview (iOS only). |

### Maintenance

Expand Down
6 changes: 1 addition & 5 deletions plugins/expo/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"name": "expo",



"version": "1.4.2",

"version": "1.5.0",
"description": "Official Expo skills for building, deploying, upgrading, and debugging Expo apps",
"author": {
"name": "Expo Team",
Expand Down
6 changes: 1 addition & 5 deletions plugins/expo/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
{
"name": "expo",



"version": "1.4.2",

"version": "1.5.0",
"description": "Official Expo skills for building, deploying, upgrading, and debugging Expo and React Native apps.",
"author": {
"name": "Expo Team",
Expand Down
6 changes: 1 addition & 5 deletions plugins/expo/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
{
"name": "expo",
"displayName": "Expo",



"version": "1.4.2",

"version": "1.5.0",
"description": "Official Expo skills for building, deploying, upgrading, and debugging Expo and React Native apps.",
"author": {
"name": "Expo Team",
Expand Down
1 change: 1 addition & 0 deletions plugins/expo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Official AI agent skills from the Expo team for building, deploying, upgrading,
### Deployment

- **eas-update-insights** — Check EAS Update health, crash rates, adoption, and payload size
- **eas-simulator** — Run and drive your app on a remote iOS/Android simulator on EAS cloud, from the CLI or an AI agent
- **expo-deployment** — Deploy to iOS App Store, Android Play Store, and web hosting
- **expo-cicd-workflows** — EAS workflow YAML files for CI/CD pipelines

Expand Down
154 changes: 154 additions & 0 deletions plugins/expo/skills/eas-simulator/SKILL.md

Large diffs are not rendered by default.

67 changes: 67 additions & 0 deletions plugins/expo/skills/eas-simulator/references/controllers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Controllers: agent-device and argent

`eas-cli` has no device verbs — it manages the *session*. The verbs (open/tap/type/screenshot/inspect) come from a **controller** that `npx --yes eas-cli@latest simulator:exec` runs locally and that talks to the controller daemon on the remote VM. Two controllers are supported by `npx --yes eas-cli@latest simulator:start --type`:

- `agent-device` (Callstack, MIT) — used throughout this skill; runs on demand via `npx agent-device@latest`, nothing installed globally.
- `argent` (Software Mansion) — a capable alternative controller; check its license for your use.
- `serve-sim` — not a controller; a streaming/preview-only type (iOS), no programmatic control.

## agent-device verbs (run via `npx --yes eas-cli@latest simulator:exec npx agent-device@latest <verb>`)

agent-device is a thin **client** talking to a **daemon** (the daemon runs on the VM in a session). `npx --yes eas-cli@latest simulator:exec` sets `AGENT_DEVICE_DAEMON_BASE_URL` + `AGENT_DEVICE_DAEMON_AUTH_TOKEN` from `.env.eas-simulator`, which switches the client into remote mode. Selectors and `@e`-refs come from the latest `snapshot`.

The CLI help is written for agents and is the source of truth — run these for the full verb set and agentic loop guidance:

```bash
npx --yes eas-cli@latest simulator:exec npx agent-device@latest --help
npx --yes eas-cli@latest simulator:exec npx agent-device@latest help workflow
```

EAS-specific notes:

- **`press`, not `tap`.** The tap verb is `press` — `tap` is not a verb.
- **`snapshot -i` is slow on iOS** — tens of seconds is normal; wait for it.
- **`install` uploads** a local binary to the daemon; **`install-from-source`** has the VM download from a URL (use for EAS artifacts — avoids a large upload).
- **Proven in this skill's flows:** `apps`, `install`, `install-from-source`, `open`, `snapshot -i`, `press`, `fill`, `screenshot`. Others (`gesture`/`scroll`/`logs`/`record`/`network`/`perf`/`metro`) are real in the CLI but not exercised here — confirm via `<verb> --help` before relying on them.

## argent (alternative)

`npx --yes eas-cli@latest simulator:start --type argent` provisions an argent remote session. The connection config it returns is different (`ARGENT_TOOLS_URL` / `ARGENT_AUTH_TOKEN`).

**argent sessions cannot install apps today.** `--type argent` provisions only an argent daemon on the VM — there is no agent-device daemon, so agent-device install commands don't apply. Use argent to drive an app that is already on the sim (e.g. start with `--type agent-device` to install, then switch; or use an EAS build with `install-from-source` via an agent-device session first).

**Connecting via MCP (Cursor, Claude Code, Codex, and others).** Install the CLI globally first — the package is `@swmansion/argent`, not `argent`:

```bash
npm install -g @swmansion/argent
```

Then run `argent init --yes` to register the Argent MCP server. Link the session credentials with `argent link` — the recommended path:

```bash
argent link '<ARGENT_TOOLS_URL>' --token '<ARGENT_AUTH_TOKEN>' --yes
```

Reload the agent after linking so its `argent mcp` process picks up the remote session.

**Sandboxed shells** (Claude Code, some CI environments) can't write to `~/.argent/` so `argent link` won't work there. Use env vars in the MCP config file instead — this is argent's highest-precedence resolution and overrides any link:

```json
{
"mcpServers": {
"argent": {
"command": "argent",
"args": ["mcp"],
"env": {
"ARGENT_TOOLS_URL": "<ARGENT_TOOLS_URL from simulator:start>",
"ARGENT_AUTH_TOKEN": "<ARGENT_AUTH_TOKEN from simulator:start>"
}
}
}
}
```

MCP config file location: `.cursor/mcp.json` (Cursor), `.claude/mcp.json` (Claude Code), `mcp.json` in the Codex project root. It carries a session token — **add it to `.gitignore`**.

**Known issues:**
- `argent init --help` launches an interactive wizard regardless of the flag — use `--yes` to skip it, or read the package source for non-interactive flags.
185 changes: 185 additions & 0 deletions plugins/expo/skills/eas-simulator/references/run-your-app.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# Running your app on the remote sim — tested sequences

The remote sim boots blank. You install a **simulator-targeted** build onto the session, then open it. Pick a mode from `SKILL.md`. (Sequences validated against eas-cli 20.3.x + agent-device 0.17.x in mid-2026; the commands are experimental — if one fails, re-check `<cmd> --help`.)

In all modes, the session is started the same way and driven through `npx --yes eas-cli@latest simulator:exec`. Replace `dev.example.app` with the app's iOS `bundleIdentifier` (from `app.json` → `ios.bundleIdentifier`), and run from the project directory.

> These sequences are **iOS**. For **Android**: build via `npx --yes eas-cli@latest build --platform android` (or local Gradle), `install` the `.apk` instead of an `.app`, skip `pod install`, and note there's **no `webPreviewUrl`** (Android is agent-driven / screenshot-only).

## Starting a session (shared by all modes)

```bash
# Reset the dotenv first so the new session id isn't masked by an "Overwriting previous session" warning.
printf '# managed by eas-cli\n' > .env.eas-simulator

# Start (no --json, so it writes .env.eas-simulator). It boots the sim + agent-device daemon.
npx --yes eas-cli@latest simulator:start --platform ios --type agent-device --non-interactive
```

`start`'s own poll is unreliable, so confirm liveness with a bounded loop (boot is ~90s–15min). `get`/`exec`/`stop` default to the session in `.env.eas-simulator`, so you can omit `--id`:

```bash
# Poll up to ~16 min; IN_PROGRESS + remoteConfig = live; a terminal status = failed boot (stop + restart).
for i in $(seq 1 64); do
S=$(npx --yes eas-cli@latest simulator:get --json --non-interactive 2>/dev/null)
echo "$S" | grep -q '"status": *"IN_PROGRESS"' && echo "$S" | grep -q remoteConfig && { echo "live"; break; }
echo "$S" | grep -qE '"status": *"(STOPPED|ERRORED)"' && { echo "boot failed — stop + restart"; break; }
sleep 15
done
```

If you need the id explicitly, it's `EAS_SIMULATOR_SESSION_ID` in `.env.eas-simulator`. `start` also prints a `webPreviewUrl` (iOS-only browser preview — surface it per the SKILL.md "watch it live" rules) and a job-run URL. Once live, the session env is in `.env.eas-simulator`, so `simulator:exec` works.

---

## Mode A — Local release build (embedded JS, no Metro)

A Release build bundles the JS into the binary, so it renders without Metro. Good for a quick "run my current code on a cloud device" when a Mac toolchain is available.

```bash
# 1. Generate native project + build a Release simulator .app
npx expo prebuild --platform ios # set ios.bundleIdentifier in app.json first to avoid prompts
# pod install can fail on Ruby 4 + CocoaPods with a Unicode/ASCII-8BIT error — fix with a UTF-8 locale:
( cd ios && LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 pod install )
LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 xcodebuild \
-workspace ios/<App>.xcworkspace -scheme <App> \
-configuration Release -sdk iphonesimulator -derivedDataPath ios/build build
# → ios/build/Build/Products/Release-iphonesimulator/<App>.app

# 2. Start a session (see "Starting a session" above), then install + open + drive
APP=ios/build/Build/Products/Release-iphonesimulator/<App>.app
npx --yes eas-cli@latest simulator:exec npx agent-device@latest install dev.example.app "$APP" --platform ios
npx --yes eas-cli@latest simulator:exec npx agent-device@latest open dev.example.app --platform ios
npx --yes eas-cli@latest simulator:exec npx agent-device@latest screenshot ./shot.png

# 3. Stop
npx --yes eas-cli@latest simulator:stop # omit --id → stops the dotenv session
```

The `install` here **uploads** the (~90MB) `.app` to the remote daemon over the tunnel, which installs it on the sim with `simctl`.

---

## Mode B — EAS build (the VM downloads it; no credentials)

**Explicit-only** (see the SKILL.md mode picker): a *static* EAS artifact for CI/sharing, or when the user names an existing EAS build. For no-Mac **live** iteration use Mode C with an EAS dev-client build (see Mode C below), not this. **Simulator builds are unsigned, so EAS asks for no credentials.**

⚠️ **Check for an existing build first.** Before triggering a new build, check if a fingerprint-matched one already exists — it saves ~15-20 min:

```bash
npx --yes eas-cli@latest build:list --platform ios --profile <your-sim-profile> --status finished --json | \
head -20 # look for a sim build whose fingerprint matches current source
```

If one matches, skip straight to step 3 with its artifact URL.

⚠️ **Order matters:** build FIRST, `start` the session LAST. The build takes ~15-20 min and a session left idle that long times out (`ERR_NGROK_3200`) — don't `start` until you have the artifact URL.

```bash
# 1. Find or create a simulator build profile in eas.json.
# Read eas.json if it exists and look for a build profile with ios.simulator: true.
# If one exists, note its name and skip to step 2.
# If not, add one named "sim" — use node, python3, jq, or a direct JSON edit, whichever
# is available. Preserve all other profiles. Minimum: { "ios": { "simulator": true } }

# 2. Build (no credentials prompt for a simulator build). Prints an artifact URL when done (~15-20 min).
npx --yes eas-cli@latest build --platform ios --profile sim --non-interactive
# → https://expo.dev/artifacts/eas/<hash>.tar.gz

# 3. Start a session, then install-from-source so the VM downloads the artifact (no local upload)
ART="https://expo.dev/artifacts/eas/<hash>.tar.gz"
npx --yes eas-cli@latest simulator:exec npx agent-device@latest install-from-source "$ART" --platform ios
npx --yes eas-cli@latest simulator:exec npx agent-device@latest open dev.example.app --platform ios
npx --yes eas-cli@latest simulator:exec npx agent-device@latest screenshot ./shot.png

# 4. Stop
npx --yes eas-cli@latest simulator:stop # omit --id → stops the dotenv session
```

**Build freshness:** reuse only a build whose **fingerprint matches current source** (`npx --yes eas-cli@latest build:list --platform ios --json`, or `get-build` by fingerprint per Callstack's public `eas-agent-device` workflow); otherwise **rebuild** or use Mode C. Tell the user which build you used. (Why this matters → SKILL.md "Reusing an existing build" caveat.)

---

## Mode C — Local dev build + tunnel (live edits via Fast Refresh)

This is the agentic edit-and-see loop: a **dev (Debug) build** loads JS from your local **Metro** over **tunnel v2**, so code edits appear on the remote sim via Fast Refresh. It has the most steps — each is necessary.

⚠️ **Don't install a release build as a "quick interim" and screenshot it** — that interim shows stale, build-time code (the "outdated screenshot" trap). Go straight to the dev build + Metro; screenshot only after the dev client is connected to Metro.

**No local Mac toolchain?** (the common cloud/Linux case) Build the dev client on **EAS** instead of step 1 below. ⚠️ Same order-matters rule as Mode B: build first, start the session after you have the artifact URL.

```bash
# ── Non-Mac path: replace step 1 with these ──────────────────────────────────

# Find or create a dev-client simulator build profile in eas.json.
# Read eas.json if it exists and look for a build profile with developmentClient: true + ios.simulator: true.
# If one exists, note its name and skip to the build step.
# If not, add one named "dev-sim" — use node, python3, jq, or a direct JSON edit, whichever
# is available. Preserve all other profiles. Minimum: { "developmentClient": true, "ios": { "simulator": true } }

# Build (~15-20 min). Prints an artifact URL when done.
npx --yes eas-cli@latest build --platform ios --profile dev-sim --non-interactive
# → https://expo.dev/artifacts/eas/<hash>.tar.gz

# Start a session AFTER the build finishes (don't start early — idle sessions time out).
# Then in step 3 below, use install-from-source (VM downloads the artifact) instead of local install:
ART="https://expo.dev/artifacts/eas/<hash>.tar.gz"
npx --yes eas-cli@latest simulator:exec npx agent-device@latest install-from-source "$ART" --platform ios
# Continue from step 3a (open the dev client, enter Metro URL) onward — identical to the Mac path.
```

```bash
# 1. Add expo-dev-client and build a Debug (dev-client) simulator .app
npx expo install expo-dev-client
npx expo prebuild --platform ios --clean # set ios.bundleIdentifier first (as in Mode A) to avoid prompts
( cd ios && LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 pod install )
LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 xcodebuild \
-workspace ios/<App>.xcworkspace -scheme <App> \
-configuration Debug -sdk iphonesimulator -derivedDataPath ios/build-debug build
DEVAPP=ios/build-debug/Build/Products/Debug-iphonesimulator/<App>.app

# 2. Start Metro with tunnel v2 — exactly ONE instance. First check :8081:
# `curl -sf localhost:8081/status` answers AND it's your Metro → reuse it, skip this step.
# Port taken but NOT yours → `PIDS=$(lsof -ti:8081); [ -n "$PIDS" ] && kill $PIDS` BEFORE starting
# (relaunching onto an occupied 8081 is the "port already in use" clash). A bare `&` won't survive across agent
# shell calls — use a long-lived/background run or a separate terminal. tunnel v2 (durable-object,
# not ngrok) works from robot/cloud agents where plain --tunnel is blocked.
EXPO_UNSTABLE_TUNNEL_V2=1 npx expo start --tunnel --port 8081
# → note the deep link exp+<slug>://<host>.on.expo.app; the manifest URL is https://<host>.on.expo.app

# 3. Start a session, install the dev build, then connect it to Metro.
# RELIABLE path = "open the dev client, then Enter URL manually". The deep-link + system "Open in
# '<app>'?" dialog is flaky: the dialog may not appear, and `press 'label="Open"'` can hang ~90s
# against a slow daemon. Don't make the loop depend on it.
# The button labels below ("Enter URL manually"/"Connect"/"Reload"/"Go back", and the system "Open")
# are expo-dev-client / iOS / expo-router UI — the same across ANY Expo app (not app-specific), but
# UI text that can shift across versions. Treat them as illustrative: if a label doesn't match,
# `snapshot -i` and press the current ref. The flow matters, not the exact strings.
npx --yes eas-cli@latest simulator:exec npx agent-device@latest install dev.example.app "$DEVAPP" --platform ios

# a) launch the dev client (it opens its launcher, which only auto-discovers LAN Metro — ours is remote):
npx --yes eas-cli@latest simulator:exec npx agent-device@latest open dev.example.app --platform ios

# b) point it at your remote Metro via "Enter URL manually":
npx --yes eas-cli@latest simulator:exec npx agent-device@latest press 'label="Enter URL manually"'
npx --yes eas-cli@latest simulator:exec npx agent-device@latest snapshot -i # get the text-field ref
npx --yes eas-cli@latest simulator:exec npx agent-device@latest fill @<field> "https://<host>.on.expo.app"
npx --yes eas-cli@latest simulator:exec npx agent-device@latest press 'label="Connect"'

# c) first-run dev menu → Reload to fetch the bundle (first build+transfer over the tunnel ~40-60s):
npx --yes eas-cli@latest simulator:exec npx agent-device@latest press 'label="Reload"'

# d) expo-router may show "Unmatched Route" (the connect URL was parsed as a path) → go to home:
npx --yes eas-cli@latest simulator:exec npx agent-device@latest press 'label="Go back"'

# 4. Edit a source file locally → Fast Refresh pushes it to the remote sim with NO reload. Screenshot to confirm.
npx --yes eas-cli@latest simulator:exec npx agent-device@latest screenshot ./live.png

# 5. Stop the session AND Metro
npx --yes eas-cli@latest simulator:stop # omit --id → stops the dotenv session
# kill the `expo start --tunnel` process
```

Notes:
- The launcher's auto-discovery only scans the LAN, so a remote Metro must be entered via "Enter URL manually" — that's why this is the connect step.
- **This "Enter URL manually" + public tunnel URL flow is the ONLY connect path.** If it fails, don't switch mechanisms or reconnect in a loop — reset to baseline and redo Mode C once (SKILL.md principle 1). (`agent-device`'s `metro prepare --proxy-base-url` bridge exists but is not part of this loop.)
Loading
Loading