eas-simulator

Par expo · skills

Exécute et contrôle l'application d'un utilisateur sur un simulateur iOS/Android distant hébergé dans le cloud EAS. Toujours lire avant d'exécuter des commandes `eas simulator:*` — ce fichier contient la syntaxe actuelle de cette API expérimentale. À utiliser dès que l'utilisateur a besoin d'un simulateur qu'il ne peut pas faire tourner localement — « lance mon app sur un simulateur cloud », « utilise eas simulator pour lancer/installer/capturer mon app », « je suis sur Linux/Cursor et j'ai besoin d'un appareil iOS », « pas de sim sur cette machine / CI headless », « laisse un agent naviguer dans mon app et faire des captures d'écran », « teste mon dev build sur un sim distant avec le rechargement en direct », « stream l'écran d'un sim dans mon navigateur » — même quand ils ne disent pas « EAS Simulator » ou « cloud ». Sur un hôte SANS simulateur local (Linux, CI, sandbox cloud) c'est l'option par défaut — utilise-la directement ; sur macOS, ne pas déclencher automatiquement pour un simple « lance sur le simulateur » — l'utiliser uniquement pour un sim cloud/distant/partageable, une version iOS manquante, ou une session pilotée par un agent. PAS pour les sims locaux (expo run:ios, Xcode, Android Studio), EAS Build/Update, la prévisualisation web, ni les appareils physiques.

npx skills add https://github.com/expo/skills --skill eas-simulator

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 -sDarwin) : 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 eas via npx --yes eas-cli@latest … — garantit une CLI assez récente pour avoir simulator:* (un eas global est souvent trop vieux), et --yes ignore l'invite de npx. (Un simple eas convient si eas --version est actuel.)
  • Authentifié. Machine interactive → npx --yes eas-cli@latest login. Sandbox cloud / CI / agent headless n'a pas de connexion navigateur — définis EXPO_TOKEN (expo.dev → Account → Access Tokens) dans l'env à la place. Vérifie de chaque côté avec npx --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 init pour créer/lier le projet (quand il n'y a pas de projectId), et définis ios.bundleIdentifier dans la config app si elle manque — une fresh create-expo-app n'en a souvent aucun, et prebuild/eas build l'exigent (ils demandent ou échouent sans ; par ex. dev.<owner>.<slug>). Lis la config actuelle avec npx expo config --json (elle peut vivre dans app.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 argent dans simulator:start) ; voir references/controllers.md.
  • .env.eas-simulator est écrit/géré par eas-cli (pas ce skill) : il contient l'id session (EAS_SIMULATOR_SESSION_ID) + l'URL daemon/token, donc get/stop/exec ciblent 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 .gitignore d'une app fraîche ne le couvira pas — ajoute .env.eas-simulator si absent).
  • --max-duration-minutes est 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 open de l'URL webPreviewUrl sur le sim. C'est une prévisualisation navigateur, pas un deep link et pas un argument agent-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 N pour 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écutions screenshot/get uniques 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) :

  1. 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.
  2. 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, tappress, --platform, --json, locale pod install, sessions orphelines, variabilité boot) vivent dans references/troubleshooting.md.

  1. É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/exec mal dirigé session l'app erronée + abandonne un .env.eas-simulator égaré ; pwd / vérifier app.json).
    • session vivanteIN_PROGRESS via simulator: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.)

  2. exec est un wrapper, pas un pilote. simulator:exec charge .env.eas-simulator et engendre la commande que tu passes ; les verbes d'appareil viennent du contrôleur (npx agent-device@latest). Il n'y a pas de simulator:tap.

  3. 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).

  4. Arrête à chaque chemin de sortie (facturation) et réinitialise le dotenv. --non-interactive n'arrête pas auto, et une session oubliée facture jusqu'à l'arrêt. Ne fais pas start de nouveau pour « réessayer » un boot lent — cela orpheline une seconde session facturée.

  5. 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:41 dans 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

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.

Skills similaires