Simulateur EAS
Le Simulateur EAS exécute un simulateur iOS distant ou un émulateur Android sur l'infrastructure EAS que tu pilotes depuis ta machine — via la CLI, via un agent IA (agent-device), et via une prévisualisation navigateur. C'est la solution pour les environnements qui ne peuvent pas exécuter un simulateur localement (machines Linux, agents cloud/background comme Cursor Cloud), et pour permettre à un agent de vérifier une modification sur un vrai simulateur au lieu de seulement raisonner sur le code.
Les commandes simulator:* sont expérimentales et cachées, et nécessitent une eas-cli récente (≥ 20.3.0 au moment de la rédaction) — c'est pourquoi ce skill exécute tout via npx --yes eas-cli@latest. Les flags et les verbes peuvent changer ; si une commande échoue, <cmd> --help fait autorité.
Quand l'utiliser
La description du frontmatter porte les phrases déclencheur. En résumé : utilise ceci pour mettre l'app d'un utilisateur sur un simulateur cloud et interagir avec — surtout depuis un agent sans Mac ou cloud/sandbox. Pas pour les sims locaux (expo run:ios, Xcode, Android Studio), les builds/signatures de store (c'est EAS Build), ou les appareils physiques. Pour le cas macOS, voir Cloud vs local ci-dessous.
Cloud vs local : décide d'abord
- Non-macOS (Linux / CI / sandbox cloud comme Cursor Cloud, détecter via
uname -s≠Darwin) : le seul moyen d'avoir un sim — procède. - macOS : les sims locaux existent et une session cloud coûte de l'argent + latence, donc demande d'abord ("un sim cloud distant — pour partager une prévisualisation en direct, déléguer, ou tester une version iOS que tu n'as pas — ou juste exécuter localement ?") sauf si l'utilisateur a explicitement dit cloud/remote/shareable.
- Honore toujours un choix explicite ; pour « l'exécuter localement » délègue à
expo run:ios/ Xcode.
# Détection programmatique — exécute ceci pour décider avant toute chose :
if [ "$(uname -s)" != "Darwin" ] || ! xcrun --find simctl &>/dev/null 2>&1; then
echo "no local sim — proceed with EAS Simulator"
else
echo "local sim available — ask the user (cloud or local?)"
fi
Prérequis
- Exécute chaque commande
easvianpx --yes eas-cli@latest …— garantit une CLI assez récente pour avoirsimulator:*(uneasglobal est souvent trop vieux), et--yesignore l'invite de npx. (Un simpleeasconvient sieas --versionest actuel.) - Authentifié. Machine interactive →
npx --yes eas-cli@latest login. Sandbox cloud / CI / agent headless n'a pas de connexion navigateur — définisEXPO_TOKEN(expo.dev → Account → Access Tokens) dans l'env à la place. Vérifie de chaque côté avecnpx --yes eas-cli@latest whoami. - Exécute depuis un répertoire de projet Expo. Une app fraîche a besoin d'une configuration unique :
npx --yes eas-cli@latest initpour créer/lier le projet (quand il n'y a pas deprojectId), et définisios.bundleIdentifierdans la config app si elle manque — une freshcreate-expo-appn'en a souvent aucun, etprebuild/eas buildl'exigent (ils demandent ou échouent sans ; par ex.dev.<owner>.<slug>). Lis la config actuelle avecnpx expo config --json(elle peut vivre dansapp.config.js). La première exécution Mode-C est lente (build native) ; les exécutions ultérieures la réutilisent. - Un contrôleur pour piloter l'appareil. Ce skill utilise agent-device (open source, MIT), exécuté à la demande via
npx agent-device@latest— rien d'installé globalement. argent est une alternative (--type argentdanssimulator:start) ; voir references/controllers.md. .env.eas-simulatorest écrit/géré par eas-cli (pas ce skill) : il contient l'id session (EAS_SIMULATOR_SESSION_ID) + l'URL daemon/token, doncget/stop/execciblent cette session par défaut (généralement omets--id; passe--id <id>pour cibler une autre). Il porte un token → garde-le gitignored (eas-cli le marque « do not commit » mais peut ne pas ajouter la règle ignore, et le.gitignored'une app fraîche ne le couvira pas — ajoute.env.eas-simulatorsi absent).--max-duration-minutesest forfait payant seulement ; sinon une limite par défaut s'applique.
La boucle principale (toujours la même)
Une session est : start → (installe ton app) → drive → stop. eas-cli possède la session ; les verbes d'appareil (open/press/screenshot) viennent du contrôleur, que npx --yes eas-cli@latest simulator:exec exécute pour toi avec l'env de connexion de la session chargé.
# 1. Démarre une session (lance le sim distant + daemon agent-device ; écrit .env.eas-simulator).
printf '# managed by eas-cli\n' > .env.eas-simulator # efface d'abord toute session périmée
npx --yes eas-cli@latest simulator:start --platform ios --type agent-device --non-interactive
# Puis confirme que c'est vivant : simulator:get --json → status IN_PROGRESS (bounded poll dans run-your-app.md).
# 2. Pilote-le via `exec` (charge l'env session, puis exécute la commande que tu lui donnes).
# agent-device exécute à la demande via npx — rien d'installé globalement.
npx --yes eas-cli@latest simulator:exec npx agent-device@latest open <app-or-url> --platform ios
npx --yes eas-cli@latest simulator:exec npx agent-device@latest snapshot -i # arbre d'accessibilité interactif → refs @e1, @e2
npx --yes eas-cli@latest simulator:exec npx agent-device@latest press @e2 # appuie sur une ref (NOTE : 'press', pas 'tap')
npx --yes eas-cli@latest simulator:exec npx agent-device@latest screenshot ./shot.png
# 3. Arrête (termine la facturation ; démantèle la VM) et réinitialise le dotenv. Omets --id pour cibler la session dotenv.
npx --yes eas-cli@latest simulator:stop
printf '# managed by eas-cli\n' > .env.eas-simulator
Pour la regarder en direct, donne à l'utilisateur l'URL webPreviewUrl que start imprime (une session iOS --type agent-device exécute serve-sim aux côtés du daemon, donc elle en émet une — contrôle agent et prévisualisation navigateur en une session ; Android n'a pas de prévisualisation, et --type serve-sim est prévisualisation seulement). Cette URL est pour le navigateur de l'utilisateur — tu ne peux pas l'ouvrir pour lui, et elle ne doit jamais toucher le sim :
- « Ouvre-la ici » (Cursor/VS Code) → imprime l'URL seule sur sa ligne et dis à l'utilisateur d'ouvrir Simple Browser (
Cmd/Ctrl+Shift+P→ « Simple Browser: Show ») et de la coller. Puis arrête : ne fais pas de shell vers un navigateur système ou un gestionnaire URL Cursor/VS Code, et ne demande pas « une onglet est-elle apparue ? » — tu ne peux pas le confirmer, la passation est faite. - Ne fais jamais
opende l'URLwebPreviewUrlsur le sim. C'est une prévisualisation navigateur, pas un deep link et pas un argumentagent-device open; la router vers l'appareil rend un navigateur-dans-un-navigateur (un vrai échec passé). - Agent headless (pas d'affichage) → retourne juste l'URL comme livrable.
- La garder vivante pour l'utilisateur pour qu'il la pilote → délimite-la : démarre avec
--max-duration-minutes Npour qu'elle s'arrête auto ; dis-lui qu'elle facture jusqu'à l'arrêt et quand elle s'arrête auto ; propose de la rouvrir/étendre quand elle se termine. (C'est le seul cas où « arrêt immédiat » ne s'applique pas ; les exécutionsscreenshot/getuniques s'arrêtent toujours immédiatement.)
start imprime aussi une URL d'exécution job.
Commandes en un coup d'œil
| Commande | Objectif |
|---|---|
npx --yes eas-cli@latest simulator:start --platform ios\|android [--type agent-device\|argent\|serve-sim] [--package-version X] [--max-duration-minutes N] [--non-interactive] [--json] |
Crée une session ; lance le sim + contrôleur ; écrit .env.eas-simulator ; imprime webPreviewUrl + URL d'exécution job |
npx --yes eas-cli@latest simulator:exec <cmd> [args…] |
Charge .env.eas-simulator, puis exécute <cmd> avec cet env. Le pont vers le contrôleur. |
npx --yes eas-cli@latest simulator:get [--id] [--json] |
Statut session + détails de connexion. Utilise ceci pour confirmer la disponibilité (voir Principes opérationnels). |
npx --yes eas-cli@latest simulator:list [--status …] [--type …] [--platform …] |
Liste les sessions d'une app |
npx --yes eas-cli@latest simulator:stop [--id] |
Arrête une session (idempotent) |
Exécuter l'app de l'utilisateur — choisis un mode
Le sim distant lance vierge — pas de Expo Go, pas d'apps. Installe une build, puis pilote-la — mais associe d'abord le type de build à l'objectif (la boîte ci-dessous) ; c'est là où les exécutions de session en direct déraillent. Suites complètes : references/run-your-app.md — lis avant d'exécuter un mode.
Associe la build à l'objectif avant d'installer quoi que ce soit — c'est là où les exécutions de session en direct déraillent. Deux pièges, même racine (saisir une build qui ne convient pas à la demande) :
- Mauvais type. Les édits en direct (Mode C) nécessitent une dev build. Une build statique — une Release locale (A), la build sim EAS par défaut (B), ou toute build laissée sur le sim d'une exécution screenshot antérieure — gèle son JS au moment de la build et ne peut jamais hot-reload. Pour une demande en direct, ignore les builds existantes entièrement et installe une build dev (Debug locale, ou une build EAS avec
developmentClient: true). Ne reconnecte jamais Metro à une build statique en espérant qu'elle rechargera — elle ne rechargera pas.- Périmée. Un regard statique doit correspondre au source actuel — réutilise uniquement une build matching empreinte, sinon build fraîche ; la réutilisation est explicite seulement.
Donc une build EAS/release rémanente n'est pas un raccourci pour « itérer en direct » — c'est le mauvais binaire. Le fait qu'une build existe ne la rend jamais la bonne.
| Mode | Ce que c'est | Choisis quand | Édits en direct ? |
|---|---|---|---|
| A — Build release locale | Build une Release .app localement, agent-device install la (l'upload) |
L'utilisateur a une chaîne d'outils Mac et veut une rapide « exécute mon code actuel sur un appareil cloud » | Non (rebuild pour voir les changements) |
| B — Build EAS (rare, explicite seulement) | eas build une build simulator, agent-device install-from-source <url> (la VM la télécharge) |
Seulement si explicitement demandé — l'utilisateur nomme une build existante/EAS, ou veut un artefact EAS statique pour CI/partage. Pas pour « montre-moi »/« itère » (utilise C). Les builds sim ne nécessitent pas de credentials. | Non |
| C — Build dev locale + tunnel | Build dev (Debug) + EXPO_UNSTABLE_TUNNEL_V2=1 expo start --tunnel + connecte le dev client à Metro |
La boucle édition-et-voir agentic — change le code et vois-le en direct (Fast Refresh) | Oui |
Décision rapide — défaut à C ; A et B sont explicites seulement :
- C (presque tout) : itère, interagis, fouille l'app, édits en direct — et la plupart des « montre-moi mon app » (le code actuel a besoin d'une build de toute façon, donc direct+actuel gagne). Mac → builds dev client localement ; pas Mac → build sur EAS (
developmentClient: true). Incertain → C. - A : seulement un screenshot statique explicite unique sur un Mac.
- B : seulement quand l'utilisateur nomme une build existante/EAS ou veut un artefact EAS statique (CI/partage) — voir la boîte ci-dessus pour pourquoi une build statique est le mauvais outil pour « itérer ».
Piloter l'appareil (agent-device)
agent-device est le contrôleur. Les verbes courants (exécute chacun comme npx --yes eas-cli@latest simulator:exec npx agent-device@latest <verb>) :
| Verbe | Fait |
|---|---|
apps --platform ios |
Liste les apps installées (le sim vierge n'en montre aucune) |
install <appId> <path> --platform ios |
Installe un .app local (l'upload) |
install-from-source <url> --platform ios |
Installe depuis une URL — la VM la télécharge (utilise pour les artefacts EAS) |
open <appId\|deep-link> --platform ios |
Lance une app (bundle id) ou suit un deep link d'app (exp+slug://…). Pas pour l'URL webPreviewUrl — c'est une prévisualisation navigateur pour l'utilisateur, jamais l'appareil. |
snapshot -i |
Arbre d'accessibilité interactif → refs style @e1 |
press <ref\|selector> |
Appuie (par ex. press @e2 ou press 'label="Open"') — le verbe appuyer est press, pas tap |
fill <ref> "text" |
Saisit du texte dans un champ |
screenshot <path> |
Capture l'écran vers un PNG local (téléchargé du daemon) — nécessite qu'une app soit ouverte (open d'abord) |
metro prepare / metro reload |
Pointe un dev client vers Metro / recharge (Mode C) |
Pour l'ensemble complet des verbes et l'alternative du contrôleur argent, voir references/controllers.md.
Principes opérationnels
Le modèle mental non-évident qui vaut la peine d'être intériorisé. Les lookups spécifiques erreur→fix (verbes figés, tap→press, --platform, --json, locale pod install, sessions orphelines, variabilité boot) vivent dans references/troubleshooting.md.
-
Établis la vérité de base, puis réinitialise — ne fais pas de boucle de rustine. Ne suppose jamais qu'une session existante ou Metro t'appartient ou est sain. Avant de piloter, confirme :
- cwd — tu es dans le répertoire de projet Expo prévu (un
start/execmal dirigé session l'app erronée + abandonne un.env.eas-simulatorégaré ;pwd/ vérifierapp.json). - session vivante —
IN_PROGRESSviasimulator:get --json(une session arrêtée garde son id +remoteConfig, donc le dotenv seul n'est pas preuve). - un Metro sur
:8081— réutilise si c'est le tien, sinon libère le port avant de démarrer (run-your-app.md). - la build correspond à l'intention — une build release ne peut pas live-reload ; si les édits en direct sont voulus et qu'une build release est installée, installe la dev build, ne reconnecte pas.
Si le code actuel ne s'affiche pas après ta première connexion, arrête de piquer l'état en direct : réinitialise à la baseline (arrête session → efface dotenv → tue Metro) et refais le mode une fois ; un second échec → arrête et signale. Ne redémarre jamais Metro sur place, ne reconnecte pas plus d'une fois, ne rebuild pas le client natif pour corriger un problème JS/connexion, et ne surface pas une URL de prévisualisation alors que l'état est inconnu. (Une baisse daemon —
ERR_NGROK_3200/Remote daemon is unavailable— c'est pareil : réinitialise, ne réessaie pas.) - cwd — tu es dans le répertoire de projet Expo prévu (un
-
execest un wrapper, pas un pilote.simulator:execcharge.env.eas-simulatoret engendre la commande que tu passes ; les verbes d'appareil viennent du contrôleur (npx agent-device@latest). Il n'y a pas desimulator:tap. -
Agis immédiatement ; ne gare pas une session idle. Les sessions sont éphémères — installe et pilote juste après
start. En laisser une idle abandonne le tunnel/daemon (→ réinitialise, par #1). -
Arrête à chaque chemin de sortie (facturation) et réinitialise le dotenv.
--non-interactiven'arrête pas auto, et une session oubliée facture jusqu'à l'arrêt. Ne fais passtartde nouveau pour « réessayer » un boot lent — cela orpheline une seconde session facturée. -
Screenshot seulement la build correcte, fraîche. Mode C seulement après que le dev client se connecte à Metro ; A/B seulement depuis une build correspondant au source actuel — réutiliser une build pré-existante est la cause #1 « mes édits ne s'affichent pas » (voir la mise en garde build ci-dessus). (
9:41dans la barre statut est la sim par défaut, pas péremption.)
Arrête et nettoie
Arrête la session (termine la facturation) et réinitialise le dotenv pour qu'une exécution ultérieure ne tente pas de réutiliser la session morte :
npx --yes eas-cli@latest simulator:stop # omets --id → arrête la session dotenv (ou passe --id <id>)
printf '# managed by eas-cli\n' > .env.eas-simulator # efface l'id session périmé pour qu'il ne soit pas réutilisé
# si tu as démarré Metro pour Mode C, l'arrête aussi (Ctrl+C dans son terminal, ou tue le processus expo)
Références
- references/run-your-app.md — suites de commandes testées complètes pour les modes A, B et C (lis avant d'exécuter un mode).
- references/controllers.md — référence verbe agent-device et alternative
argent. - references/troubleshooting.md — erreurs concrètes et corrections.
Source de vérité : docs Expo et les CLIs eas / agent-device (npx --yes eas-cli@latest simulator:* --help, agent-device --help). Ce skill enseigne comment les appliquer ; il ne les remplace pas.