diff --git a/.agents/skills/building-native-ui/agents/openai.yaml b/.agents/skills/building-native-ui/agents/openai.yaml deleted file mode 100644 index d515050..0000000 --- a/.agents/skills/building-native-ui/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Building Native UI" - short_description: "Build beautiful Expo Router UI with native-feeling navigation, controls, media, animation, and visual effects" - default_prompt: "Use $building-native-ui to build or refactor Expo Router UI, choose native-feeling navigation and controls, structure routes, style screens, and decide when Expo Go is enough before creating native builds." diff --git a/.agents/skills/expo-deployment/SKILL.md b/.agents/skills/eas-app-stores/SKILL.md similarity index 53% rename from .agents/skills/expo-deployment/SKILL.md rename to .agents/skills/eas-app-stores/SKILL.md index 8f96e7d..7916b4f 100644 --- a/.agents/skills/expo-deployment/SKILL.md +++ b/.agents/skills/eas-app-stores/SKILL.md @@ -1,19 +1,21 @@ --- -name: expo-deployment -description: Deploy Expo apps to production with EAS — build and submit to the iOS App Store, Google Play Store, and TestFlight, configure eas.json build and submit profiles, manage app versions and build numbers, publish App Store metadata and ASO, and deploy web bundles and API routes via EAS Hosting. Use whenever the user is preparing a production build, running eas build or eas submit, shipping to TestFlight, releasing or rolling out to the app stores, bumping version or build numbers, or setting up store listing metadata for an Expo app. +name: eas-app-stores +description: EAS service (paid). Deploy Expo apps to the app stores with EAS - build and submit to the iOS App Store, Google Play Store, and TestFlight, configure eas.json build and submit profiles, manage app versions and build numbers, and publish App Store metadata and ASO. Use whenever the user wants to deploy, release, or ship an app to production or the app stores, is preparing a production build, running eas build or eas submit, shipping to TestFlight, bumping version or build numbers, or setting up store listing metadata. For deploying an Expo website or API routes, use the eas-hosting skill. version: 1.0.0 license: MIT --- -# Deployment +# App Store Deployment -This skill covers deploying Expo applications across all platforms using EAS (Expo Application Services). +> **EAS service - costs apply.** This skill uses Expo Application Services (EAS), a paid product with free-tier limits. `eas build` and `eas submit` consume your plan's build minutes, and store submission requires paid Apple Developer and Google Play accounts. Review https://expo.dev/pricing before running cloud commands. + +This skill covers building and releasing Expo apps to the iOS App Store, Google Play Store, and TestFlight using EAS (Expo Application Services). For deploying an Expo website or API routes to EAS Hosting, use the `eas-hosting` skill. ## References Consult these resources as needed: -- ./references/workflows.md -- CI/CD workflows for automated deployments and PR previews +- ./references/workflows.md -- CI/CD workflows for automated store releases and PR previews - ./references/testflight.md -- Submitting iOS builds to TestFlight for beta testing - ./references/app-store-metadata.md -- Managing App Store metadata and ASO optimization - ./references/play-store.md -- Submitting Android builds to Google Play Store @@ -64,20 +66,9 @@ npx eas-cli@latest build -p android --profile production --submit npx testflight ``` -## Web Deployment - -Deploy web apps using EAS Hosting: - -```bash -# Deploy to production -npx expo export -p web -npx eas-cli@latest deploy --prod - -# Deploy PR preview -npx eas-cli@latest deploy -``` +## Web & API Route Hosting -Expo Router API routes deploy together with the web bundle on EAS Hosting — `eas deploy` ships both. To author or configure the API routes themselves, use the `expo-api-routes` skill. +Deploying an Expo website or Expo Router API routes to EAS Hosting (`npx expo export -p web` then `eas deploy`) is covered by the `eas-hosting` skill. This skill focuses on native app store releases. ## EAS Configuration @@ -131,15 +122,9 @@ Standard `eas.json` for production deployments: - Configure tracks: internal → closed → open → production - See ./references/play-store.md for detailed setup -### Web +## Automated Releases -- EAS Hosting provides preview URLs for PRs -- Production deploys to your custom domain -- See ./references/workflows.md for CI/CD automation - -## Automated Deployments - -EAS Workflows automate the build → submit → update → deploy pipeline for CI/CD. See ./references/workflows.md for deployment-oriented examples. To author or validate workflow YAML, use the `expo-cicd-workflows` skill — it works from the live workflow schema. +EAS Workflows automate the build → submit → update pipeline for CI/CD. See ./references/workflows.md for store-release examples. To author or validate workflow YAML, use the `eas-workflows` skill - it works from the live workflow schema. ## Version Management @@ -165,3 +150,11 @@ eas build:view # View submission status eas submit:list ``` + +## Submitting Feedback +If you encounter errors, misleading or outdated information in this skill, report it so Expo can improve: +```bash +npx --yes submit-expo-feedback@latest --category skills --subject "eas-app-stores" "" +``` +Only submit when you have something specific and actionable to report. Include as much relevant context as possible. +If an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above. diff --git a/.agents/skills/eas-app-stores/agents/openai.yaml b/.agents/skills/eas-app-stores/agents/openai.yaml new file mode 100644 index 0000000..79ce425 --- /dev/null +++ b/.agents/skills/eas-app-stores/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "EAS App Stores" + short_description: "Paid EAS service. Build and submit Expo apps to the iOS App Store, Google Play Store, and TestFlight, with eas.json profiles, versioning, and store metadata" + default_prompt: "Use $eas-app-stores when preparing production EAS builds, store submissions, TestFlight distribution, app-store metadata and ASO, or version and build-number management for an Expo app. For web or API route hosting, use eas-hosting instead." diff --git a/.agents/skills/expo-deployment/references/app-store-metadata.md b/.agents/skills/eas-app-stores/references/app-store-metadata.md similarity index 100% rename from .agents/skills/expo-deployment/references/app-store-metadata.md rename to .agents/skills/eas-app-stores/references/app-store-metadata.md diff --git a/.agents/skills/expo-deployment/references/ios-app-store.md b/.agents/skills/eas-app-stores/references/ios-app-store.md similarity index 100% rename from .agents/skills/expo-deployment/references/ios-app-store.md rename to .agents/skills/eas-app-stores/references/ios-app-store.md diff --git a/.agents/skills/expo-deployment/references/play-store.md b/.agents/skills/eas-app-stores/references/play-store.md similarity index 95% rename from .agents/skills/expo-deployment/references/play-store.md rename to .agents/skills/eas-app-stores/references/play-store.md index 88102dd..9d67530 100644 --- a/.agents/skills/expo-deployment/references/play-store.md +++ b/.agents/skills/eas-app-stores/references/play-store.md @@ -6,6 +6,8 @@ 2. **App Created in Console** - Create your app listing before first submission 3. **Service Account** - For automated submissions via EAS +Once these are complete, the default `eas submit` works for a first-time submission and creates the app's first release on the internal testing track. Store listing, content rating, and pricing are only required before promoting a release to production. + ## Service Account Setup ### 1. Create Service Account diff --git a/.agents/skills/expo-deployment/references/testflight.md b/.agents/skills/eas-app-stores/references/testflight.md similarity index 100% rename from .agents/skills/expo-deployment/references/testflight.md rename to .agents/skills/eas-app-stores/references/testflight.md diff --git a/.agents/skills/expo-deployment/references/workflows.md b/.agents/skills/eas-app-stores/references/workflows.md similarity index 73% rename from .agents/skills/expo-deployment/references/workflows.md rename to .agents/skills/eas-app-stores/references/workflows.md index 86053ce..4901f75 100644 --- a/.agents/skills/expo-deployment/references/workflows.md +++ b/.agents/skills/eas-app-stores/references/workflows.md @@ -1,50 +1,10 @@ # EAS Workflows -Automate builds, submissions, and deployments with EAS Workflows. The examples below are deployment-oriented starting points. +Automate builds, submissions, and PR-preview updates with EAS Workflows. The examples below are store-release-oriented starting points. -When you need to write, edit, or validate a workflow YAML file beyond these examples, use the `expo-cicd-workflows` skill. +When you need to write, edit, or validate a workflow YAML file beyond these examples, use the `eas-workflows` skill. For website and API-route deploy workflows (`type: deploy`), see the `eas-hosting` skill. -## Web Deployment - -Deploy web apps on push to main: - -`.eas/workflows/deploy.yml` - -```yaml -name: Deploy - -on: - push: - branches: - - main - -# https://docs.expo.dev/eas/workflows/syntax/#deploy -jobs: - deploy_web: - type: deploy - params: - prod: true -``` - -## PR Previews - -### Web PR Previews - -```yaml -name: Web PR Preview - -on: - pull_request: - types: [opened, synchronize] - -jobs: - preview: - type: deploy - params: - prod: false -``` - -### Native PR Previews with EAS Updates +## PR Previews with EAS Update Deploy OTA updates for pull requests: diff --git a/.agents/skills/eas-observe/SKILL.md b/.agents/skills/eas-observe/SKILL.md new file mode 100644 index 0000000..b2d0e10 --- /dev/null +++ b/.agents/skills/eas-observe/SKILL.md @@ -0,0 +1,54 @@ +--- +name: eas-observe +description: EAS service (paid). Use for anything related to EAS Observe - adding `expo-observe` to an Expo project (AppMetricsRoot/ObserveRoot HOC, markInteractive and ObserveInteractiveMarker, the useObserve hook, the Expo Router / React Navigation integrations for per-route metrics, user-defined events via `Observe.logEvent`, error reporting via ObserveErrorBoundary and `Observe.reportError`, and runtime config such as sampleRate and dispatchInDebug), querying via the EAS CLI (`eas observe:metrics-summary`, `observe:metrics`, `observe:routes`, `observe:events`, `observe:session`, `observe:versions`), interpreting the resulting metrics (cold/warm launch, TTR, TTI, navigation cold/warm TTR, update download, and the TTI frameRate/device/network params for triaging slow startups), or shipping an Observe integration inside a third-party package. +version: 1.1.0 +license: MIT +--- + +# EAS Observe + +> **EAS service - costs apply.** EAS Observe is an Expo Application Services product. The free EAS plan allows up to 10,000 monthly active users, with a limited set of features; higher usage requires a paid subscription. For details, see https://expo.dev/pricing#plan-features. + +EAS Observe tracks startup, navigation, and custom-event performance from production Expo apps. It needs a development or production build — the native library is not in Expo Go. + +> **Source of truth:** https://docs.expo.dev/eas/observe/ — always consult the canonical docs when API details matter, especially get-started, configuration, integrations, and the metrics reference. EAS Observe is evolving; this skill's references are written to stay accurate but may lag the docs. + +## Which reference to read + +The four reference files in `./references/` cover what people typically need this skill for: + +- **Adding EAS Observe to a project** → [`./references/setup.md`](./references/setup.md). Install, wrap the root layout (`AppMetricsRoot` on SDK 55, `ObserveRoot` on SDK 56+), mark the app interactive (global `markInteractive()` on SDK 55, the `useObserve()` hook or `` on SDK 56+), optional per-route navigation metrics through the Expo Router / React Navigation integrations, user-defined events via `Observe.logEvent` (SDK 56+), error reporting, and runtime configuration (sampling, dispatch, environments, custom endpoint). +- **Querying metrics from the terminal** → [`./references/queries.md`](./references/queries.md). The six `eas observe:*` commands — `metrics-summary`, `metrics`, `routes`, `events`, `session`, `versions` — with flags, metric aliases, table layouts, JSON shapes, and common workflows. +- **Reading a dashboard or CLI output** → [`./references/metrics.md`](./references/metrics.md). Target thresholds per metric, what the automatic TTI params mean (`frameRate.*`, `device.*`, `network.*`), and diagnostic patterns for telling slow-but-smooth startup apart from main-thread contention, hard blocks, or throttled devices. +- **Shipping an Observe integration in a library** → [`./references/third-party.md`](./references/third-party.md). For package authors only (SDK 57+): optional peer dependency, config declaration merging, `Observe.registerIntegration()`, and event naming. + +## Quick links to the docs + +- Get started: https://docs.expo.dev/eas/observe/get-started/ +- Dashboard guide: https://docs.expo.dev/eas/observe/dashboard/ +- Querying with EAS CLI: https://docs.expo.dev/eas/observe/eas-cli/ +- Metrics reference: https://docs.expo.dev/eas/observe/reference/metrics/ +- Expo Router integration: https://docs.expo.dev/eas/observe/integrations/expo-router/ +- React Navigation integration: https://docs.expo.dev/eas/observe/integrations/react-navigation/ +- User-defined events: https://docs.expo.dev/eas/observe/events/ +- Configuration: https://docs.expo.dev/eas/observe/configuration/ +- Third-party integrations: https://docs.expo.dev/eas/observe/integrations/third-party/ +- EAS Update download performance: https://docs.expo.dev/eas/observe/eas-update/ +- Troubleshooting: https://docs.expo.dev/eas/observe/reference/troubleshooting/ + +## Known gaps between the docs and the shipped code + +Verified against `eas-cli` 21.8.0 and `expo-observe` 57.0.9. Trust this skill's references over the docs on these points, but re-check with `--help` and the installed package before relying on them: + +- All six CLI commands are on the [Querying with EAS CLI](https://docs.expo.dev/eas/observe/eas-cli/) page. Older doc builds list only four and omit `observe:routes` and `observe:session`. +- Navigation metric aliases are `nav_cold_ttr`, `nav_warm_ttr`, and `nav_tti`. There are no bare `cold_ttr` / `warm_ttr` aliases in the CLI. +- Sorting uses `--sort `. There is no `--order` flag. +- `ObserveErrorBoundary`, `Observe.reportError`, and `configure({ errorHandlingEnabled })` are exported but undocumented. Observe still has no crash reporting; use Sentry or BugSnag for that. + +## Submitting Feedback +If you encounter errors, misleading or outdated information in this skill, report it so Expo can improve: +```bash +npx --yes submit-expo-feedback@latest --category skills --subject "eas-observe" "" +``` +Only submit when you have something specific and actionable to report. Include as much relevant context as possible. +If an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above. diff --git a/.agents/skills/eas-observe/agents/openai.yaml b/.agents/skills/eas-observe/agents/openai.yaml new file mode 100644 index 0000000..444535a --- /dev/null +++ b/.agents/skills/eas-observe/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "EAS Observe" + short_description: "Paid EAS service. Set up expo-observe app metrics and query launch, route, event, and version performance with the EAS CLI" + default_prompt: "Use $eas-observe to add expo-observe instrumentation (AppMetricsRoot/ObserveRoot, useObserve, ObserveInteractiveMarker, the router integrations, error reporting), query metrics with eas observe:* commands, and interpret cold/warm launch, TTR, and TTI results." diff --git a/.agents/skills/eas-observe/references/metrics.md b/.agents/skills/eas-observe/references/metrics.md new file mode 100644 index 0000000..5cf8a94 --- /dev/null +++ b/.agents/skills/eas-observe/references/metrics.md @@ -0,0 +1,98 @@ +# EAS Observe metrics — interpretation cheatsheet + +Quick reference for reading EAS Observe dashboards and CLI output. + +> Source: https://docs.expo.dev/eas/observe/reference/metrics/ — this is the canonical reference for metrics. Consult this page for the latest guidance, full prose definitions, optimization tips, and rationale. + +All durations are in seconds. Metric data is retained for a minimum of 60 days. By default, every installation dispatches all of its events; high-volume apps can sample per installation with `configure({ sampleRate })` — see [Sampling](https://docs.expo.dev/eas/observe/configuration/#sampling). + +## Target thresholds + +| Metric | Full name | Target | Auto-collected? | +|---|---|---|---| +| Cold launch | `expo.app_startup.cold_launch_time` | **< 1.5s** | Yes (native-only — JS code does not affect it) | +| Warm launch | `expo.app_startup.warm_launch_time` | **< 0.5s** | Yes (OS decides when warm vs cold happens) | +| Bundle load | `expo.app_startup.bundle_load_time` | **< 0.3s** | Yes (JS load + evaluation, before `runApplication`) | +| Time to first render (TTR) | `expo.app_startup.ttr` | **< 2s** incl. cold launch | Yes when root is wrapped with `AppMetricsRoot` (SDK 55) / `ObserveRoot` (SDK 56+) | +| Time to interactive (TTI) | `expo.app_startup.tti` | **< 3s** incl. cold launch | **No** — call `markInteractive()` once the screen is genuinely usable | + +Both TTR and TTI are measured *from native launch* through the React render, so the cold-launch portion counts against them. + +## Interpreting TTI events (automatic params) + +Every TTI event carries automatic params in three groups: frame rate, device state, and network state. Read the frame-rate group to classify *what kind* of slowness you're seeing, then read the device and network groups to decide whether the cause is the code or the conditions. + +### Frame rate — what kind of slowness + +| Param | Definition | What it indicates | +|---|---|---| +| `expo.frameRate.slowFrames` | Count of frames ≥ 17ms | Main thread consistently busy during launch (heavy layout, sync bridge calls, too many components rendering) | +| `expo.frameRate.frozenFrames` | Count of frames ≥ 700ms | Hard freezes. Even one during startup is a serious issue (sync I/O, large JSON parsing, blocking network) | +| `expo.frameRate.totalDelay` | Total accumulated time (seconds) frames exceeded their target duration | Best single "smoothness" number — compare to TTI | + +**Diagnostic patterns:** + +- **High TTI + low totalDelay** → slow but smooth. The launch sequence itself is long. Optimize bundle size, data-fetch waterfalls, initialization chains. +- **High TTI + high totalDelay + many slowFrames** → main-thread contention. Offload work, simplify the initial render tree. +- **High TTI + high totalDelay + any frozenFrames** → something is blocking hard. Look for synchronous I/O, large JSON parsing, or blocking network calls. + +### Device state — is the regression environmental? + +| Param | Type | What it indicates | +|---|---|---| +| `expo.device.lowPowerMode` | boolean | OS power saver was active (Low Power Mode on iOS, Battery Saver on Android). It throttles CPU, GPU, and background work. A regression that disappears when you filter this out is environmental, not a code change. | +| `expo.device.thermalState` | `nominal` \| `fair` \| `serious` \| `critical` \| `unknown` | Sustained `serious`/`critical` means the OS is throttling. Startup slows independently of any app change. | +| `expo.device.batteryLevel` | number, 0–1 | Fractional charge at TTI. Rules out throttling on devices that manage performance aggressively at low charge. Omitted when the OS reports no value. | +| `expo.device.batteryCharging` | boolean | Charging raises sustained CPU ceilings on iOS and some Android OEMs. Non-charging samples are the more conservative population. | + +### Network state — is startup network-bound? + +| Param | Type | What it indicates | +|---|---|---| +| `expo.network.connected` | boolean | If TTI degrades only when `true`, startup is network-bound. If it degrades when `false`, the app does too much before showing cached content. | +| `expo.network.type` | `wifi` \| `cellular` \| `ethernet` \| `none` \| `other` \| `unknown` | Compare cellular against Wi-Fi. A large gap points to network-bound startup work. VPN traffic reports the underlying transport. The value set is identical on both platforms, so dashboards need no per-platform branching. | +| `expo.network.isExpensive` | boolean | Both platforms. The OS considers the connection metered (cellular, hotspot). Present only when a network exists. | +| `expo.network.isConstrained` | boolean | **iOS only.** Low Data Mode is on for this path, so the system defers background transfers. | +| `expo.network.dataSaverEnabled` | boolean | **Android only.** Data Saver is on. It is the nearest equivalent of Low Data Mode, but process-wide rather than per-path, hence the separate key. | + +### Network requests — was the network the cause? + +TTI events summarize the HTTP requests made during launch, from the end of the native launch to the `markInteractive()` call. Traffic is observed automatically — `URLSession` on iOS, `OkHttpClient` on Android, which covers `fetch` — and Observe's own uploads are excluded. All of these are omitted when the window held no requests. + +| Param | Unit | What it indicates | +|---|---|---| +| `expo.network.requests.count` | count | Requests that finished in the window. A request still in flight when the app became interactive is not counted anywhere in this table. | +| `expo.network.requests.failed` | count | Errored, returned 4xx/5xx, never got a response, or broke partway through the body. Redirects are not failures. | +| `expo.network.requests.bytesReceived` / `.bytesSent` | bytes | On-the-wire totals for the window. | +| `expo.network.requests.totalDuration` | seconds | Sum of every request duration, failures included. Exceeds wall-clock when requests overlap; one timeout contributes the client's full timeout interval. | +| `expo.network.requests.throughputBytesPerSecond` | bytes/sec | Received bytes over the time bytes were actually moving (union of transfer windows, measured from each first byte). Excludes DNS, connect, server think time, cache hits, and failures. Requests the OS did not clearly identify as network loads are excluded too. Omitted when nothing was received. | +| `expo.network.requests.slowest.duration` | seconds | The single longest **completed** request. Requests that never produced a response are excluded, since a timeout measures the client's own setting. | +| `expo.network.requests.slowest.host` | string | Host of that request. | +| `expo.network.requests.slowest.statusCode` | number | Explains an empty response: `bytesReceived` of 0 is routine on a 304, a problem on a 200. | +| `expo.network.requests.slowest.timeToFirstByte` | seconds | Includes server processing, so treat it as a proxy for network quality, not a measurement of it. | +| `expo.network.requests.slowest.bytesReceived` | bytes | Separates "slow because it moved a lot of data" from "slow while idle". | + +**Diagnostic patterns:** + +- **`slowest.duration` mostly `timeToFirstByte`** → the server was slow to answer. Optimize the endpoint, or stop blocking startup on it. +- **Small `timeToFirstByte` + large `bytesReceived`** → the transfer itself was slow. Shrink the payload or defer it. +- **High `failed` + high `totalDuration`** → the launch burned time on requests that never arrived. Add timeouts and render cached content first. +- **Low `throughputBytesPerSecond` on `wifi`** → suspect the population, not the code; cross-check `isExpensive` and `isConstrained` / `dataSaverEnabled`. + +> The summary is bounded by an in-memory ring buffer of the 200 most recent requests. A launch that makes more undercounts, so read these as a sample of a very busy window. + +### Custom params + +You can attach your own params to the TTI event, and override the route name it is tagged with. See [`./setup.md`](./setup.md) for the call syntax. + +## Dispatch caveats + +- **Debug builds** (native debug OR JS bundle with `__DEV__` = true) do **not** dispatch metrics unless `configure({ dispatchInDebug: true })` is set. +- The `environment` tag (defaults to `process.env.NODE_ENV`) is metadata only — it does not gate dispatch by itself. +- Offline events are buffered on-device and flushed when the app backgrounds or `Observe.dispatchEvents()` is called. + +## Cross-references + +- Full metric definitions and optimization guidance: https://docs.expo.dev/eas/observe/reference/metrics/ +- Setup steps (`AppMetricsRoot` / `ObserveRoot`, `markInteractive`): see [`./setup.md`](./setup.md). +- Querying metrics via the EAS CLI: see [`./queries.md`](./queries.md). diff --git a/.agents/skills/eas-observe/references/queries.md b/.agents/skills/eas-observe/references/queries.md new file mode 100644 index 0000000..5abcb2c --- /dev/null +++ b/.agents/skills/eas-observe/references/queries.md @@ -0,0 +1,403 @@ +# EAS Observe CLI + +EAS Observe collects app performance telemetry and custom events from Expo apps and exposes them through six EAS CLI commands. Pass the `--help` flag to any command for the latest API — the flags below were verified against `eas-cli` 21.8.0. + +> Source: https://docs.expo.dev/eas/observe/eas-cli/ — the canonical CLI page. This reference adds table layouts, JSON output shapes, and pagination details that the docs page does not cover. + +## Commands Overview + +| Command | Purpose | +|---------|---------| +| `eas observe:metrics-summary` | Per-version statistical aggregates for startup and navigation metrics (median, p90, etc.) | +| `eas observe:metrics` | Individual metric samples ordered by value or timestamp (paginated) | +| `eas observe:routes` | Per-route statistical aggregates for navigation metrics (Nav Cold TTR, Nav Warm TTR, Nav TTI) | +| `eas observe:events` | Custom events emitted by the app via `logEvent` — name summary, all events, or filtered by event name (paginated) | +| `eas observe:session` | Full timeline of metric and log events for one session | +| `eas observe:versions` | App version hierarchy with build numbers, OTA update IDs, and event counts | + +> Older published docs list only `metrics-summary`, `metrics`, `events`, and `versions`. All six are on the [Querying with EAS CLI](https://docs.expo.dev/eas/observe/eas-cli/) page; run `--help` to confirm them on your installed version. + +All six commands share these flags: + +- `--start ` and `--end ` — explicit time range +- `--days ` — show data from the last N days (mutually exclusive with `--start`/`--end`, minimum 1) +- `--project-id ` — run against a specific project without needing a project directory. When passed, the command will not try to create a new EAS project where one is unneeded. +- `--json` — machine-readable output (implies `--non-interactive`) +- `--non-interactive` — fail instead of prompting + +`--platform ios` / `--platform android` (default: both) is on every command **except `observe:session`**, which is scoped to one session already. + +Default time range is the last 60 days when none of `--days`, `--start`, `--end` is given. + +**Plan gating.** Observe is a paid feature, and the server rejects queries the account's plan does not include (`EAS_OBSERVE_PLAN_UPGRADE_REQUIRED` or `EAS_OBSERVE_FEATURE_NOT_AVAILABLE_IN_FREE_TIER`). The CLI surfaces the server's upgrade message, which links to the account's billing page. Session timelines in particular are checked before the interactive picker runs. A plan-gate failure is not a bug in the command or its flags. + +## Supported Metrics + +### App-startup metrics + +| Alias | Full name | Display | +|-------|-----------|---------| +| `tti` | `expo.app_startup.tti` | Startup TTI (time to interactive) | +| `ttr` | `expo.app_startup.ttr` | Startup TTR (time to render) | +| `cold_launch` | `expo.app_startup.cold_launch_time` | Cold Launch | +| `warm_launch` | `expo.app_startup.warm_launch_time` | Warm Launch | +| `bundle_load` | `expo.app_startup.bundle_load_time` | Bundle Load | +| `update_download` | `expo.updates.download_time` | Update Download | + +### Navigation metrics + +Emitted only when a navigation integration is enabled (SDK 56+). Measured per route name. + +| Alias | Full name | Display | +|-------|-----------|---------| +| `nav_cold_ttr` | `expo.navigation.cold_ttr` | Nav Cold TTR | +| `nav_warm_ttr` | `expo.navigation.warm_ttr` | Nav Warm TTR | +| `nav_tti` | `expo.navigation.tti` | Nav TTI | + +**Which command takes which alias.** `observe:metrics` (positional argument) and `observe:metrics-summary --metric` accept **all nine** aliases — startup and navigation. `observe:routes --metric` accepts only the three navigation aliases. Use the `nav_` prefix everywhere; there are no bare `cold_ttr` / `warm_ttr` aliases. + +`observe:metrics` also accepts a full metric name in place of an alias, for example `eas observe:metrics expo.app_startup.tti`. `observe:routes` accepts full navigation names the same way. The `--metric` flags on `metrics-summary` and `routes` are strict oclif options, so they take aliases only. + +## `eas observe:metrics-summary` + +Shows per-version statistical aggregates for one or more metrics, with separate tables per platform. + +```bash +# All default metrics, last 60 days, both platforms +eas observe:metrics-summary + +# Single metric +eas observe:metrics-summary --metric tti + +# Multiple metrics — each renders as its own table +eas observe:metrics-summary --metric tti --metric cold_launch + +# Navigation metrics aggregate per version here, per route in observe:routes +eas observe:metrics-summary --metric nav_tti + +# Choose which statistics to display +eas observe:metrics-summary --metric tti --stat median --stat p90 --stat eventCount + +# Narrow time range and platform +eas observe:metrics-summary --metric tti --days 14 --platform ios +``` + +**Stat flags:** exactly `min`, `median`, `max`, `average`, `p80`, `p90`, `p99`, `eventCount`. This command takes **no aliases** — `med`, `avg`, and `count` are rejected here (they work only on `observe:routes`). + +**Default stats:** `median` + `eventCount` in the table; all eight in JSON. + +This command has no `--limit`, `--after`, `--app-version`, or `--update-id`. It always aggregates every version in the time range. + +**Table layout:** +- One table per metric (with merged value + event count cells, e.g. `0.45s (150)`) +- Each table shows iOS and Android in separate sections +- App Version column includes build numbers in parentheses (e.g. `1.2.0 (42)`) +- Footer row per platform shows total events per metric +- **Update IDs are omitted from the table** to keep output readable when a version has many updates; they are included in the JSON output as an array per version + +**JSON output shape:** +```json +{ + "versions": [ + { + "appVersion": "1.2.0", + "platform": "IOS", + "buildNumbers": ["42"], + "updateIds": ["abc-def-...", "..."], + "metrics": { + "expo.app_startup.tti": { "median": 0.45, "p90": 0.9, "...": "..." } + } + } + ], + "totalEventCounts": { + "expo.app_startup.tti": { "IOS": 1234, "ANDROID": 890 } + } +} +``` + +## `eas observe:metrics` + +Shows individual performance metric samples, paginated. The metric is a positional argument, not a flag. If omitted and running interactively, prompts for selection; in non-interactive mode it throws an error. + +```bash +# Interactive: prompts for metric +eas observe:metrics + +# Specify metric as positional arg +eas observe:metrics tti + +# Navigation metrics work here too +eas observe:metrics nav_tti --sort slowest + +# Filter by version or update, sort by slowest +eas observe:metrics tti --app-version 1.2.0 --sort slowest --limit 20 + +# Pagination — pass the endCursor from the previous run +eas observe:metrics tti --after +``` + +**Sample-specific flags:** +- `--sort ` — defaults to `oldest` +- `--limit ` — samples per page (default 10, max 100) +- `--after ` — pagination cursor from the previous run +- `--app-version ` — filter by app version string +- `--update-id ` — filter by EAS update ID + +**Table layout:** +- Summary header shows the metric name, time range, and total sample count across all versions (e.g. `TTI samples for the last 60 days — 1,234 total events`) +- Columns: Value, App Version (with build number), Update (only when any sample has one), Platform, Device, Country, Timestamp +- When `hasNextPage` is true, prints `Next page: --after ` hint below the table +- JSON output also includes `sessionId`, `easClientId`, and a `customParams` object per sample + +## `eas observe:routes` + +Shows per-route statistical aggregates for navigation metrics (Cold TTR, Warm TTR, Nav TTI), grouped by route name with separate sections per platform. + +```bash +# All three navigation metrics, default stats, last 60 days, both platforms +eas observe:routes + +# Single metric, last 7 days, iOS only +eas observe:routes --metric nav_tti --days 7 --platform ios + +# Multiple metrics and stats +eas observe:routes --metric nav_cold_ttr --metric nav_warm_ttr --stat median --stat p90 --stat count + +# Filter to a single build +eas observe:routes --app-version 1.2.0 --build-number 42 + +# Narrow to specific routes (repeat the flag for multiple routes) +eas observe:routes --route-name /new --route-name /settings + +# Pagination — each platform has its own cursor; pass the relevant endCursor +eas observe:routes --after +``` + +**Routes-specific flags:** +- `--metric ` — navigation metric(s) to display, can be repeated. Defaults to all three. +- `--stat ` — statistic(s) per metric. Aliases: `med` → `median`, `event_count` / `eventCount` → `count`. +- `--limit ` — routes per page (default **50**, max **200**, different from `metrics`/`events` which default to 10). +- `--after ` — pagination cursor from the previous run. +- `--app-version ` — filter by app version string. +- `--build-number ` — filter by app build number (routes-only). +- `--route-name ` — filter by route name. Repeatable; only the listed routes are returned across both platforms. Duplicates are de-duplicated; omitting the flag returns all routes. +- `--update-id ` — filter by EAS update ID. + +**Default stats:** `median` + `count` in the table; `median`, `p90`, `count` in JSON. + +**Table layout:** +- Summary header with the chosen stats and time range, e.g. `Med, P90 values (navigation count) for the last 7 days`. +- Separate iOS and Android sections. +- First column is **Route**, followed by one column per metric/stat. With both display stats and `count`, cells are merged like `0.32s (1240)`. +- Each platform has its own pagination hint: `Next page (iOS): --after `. + +**JSON output shape:** +```json +{ + "routes": [ + { + "routeName": "(tabs)/home", + "platform": "IOS", + "metrics": { + "expo.navigation.cold_ttr": { "median": 0.32, "p90": 0.85, "count": 1240 }, + "expo.navigation.tti": { "median": 0.55, "p90": 1.10, "count": 1240 } + } + } + ], + "pageInfoByPlatform": { + "IOS": { "hasNextPage": true, "endCursor": "..." }, + "ANDROID": { "hasNextPage": false, "endCursor": null } + } +} +``` + +## `eas observe:events` + +Shows custom events emitted by the app via the `logEvent` API in `expo-observe`. Behavior depends on what is passed: + +| Invocation | Result | +|---|---| +| `observe:events` | Summary table of available event names with counts | +| `observe:events --all-events` | Full list of events across **all** event names | +| `observe:events ` | Full list of events filtered by that event name | + +```bash +# List the available custom event names and their counts (last 60 days) +eas observe:events + +# All events across all names, last 7 days, iOS only +eas observe:events --all-events --days 7 --platform ios + +# Only events with the given name +eas observe:events login_failed --limit 50 + +# Drill into a single session +eas observe:events --all-events --session-id + +# Pagination +eas observe:events login_failed --after +``` + +**Events-specific flags:** +- `--all-events` — when no event name argument is given, list all events instead of the name summary. Cannot be combined with an event name argument. +- `--session-id ` — filter to events from a single session (events-only). With no event name argument, this lists the session's events instead of the event-name summary. For the full timeline — metrics as well as log events — use `observe:session`. +- `--app-version ` — filter by app version string +- `--update-id ` — filter by EAS update ID +- `--limit ` — events per page (default 10, max 100) +- `--after ` — pagination cursor + +**Table layout (event listings):** +- Summary header: ` events