vybekiit 0.7.4 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/dist/bin.js +10483 -1704
  2. package/dist/global-skills/aws-cdk/SKILL.md +2 -1
  3. package/dist/global-skills/aws-cloudformation/SKILL.md +2 -1
  4. package/dist/global-skills/aws-serverless/SKILL.md +45 -44
  5. package/dist/global-skills/aws-serverless/assets/powertools-handler.py +2 -1
  6. package/dist/global-skills/aws-serverless/references/api-gateway.md +50 -470
  7. package/dist/global-skills/aws-serverless/references/architecture.md +47 -186
  8. package/dist/global-skills/aws-serverless/references/concurrency.md +44 -158
  9. package/dist/global-skills/aws-serverless/references/deployment.md +1 -1
  10. package/dist/global-skills/aws-serverless/references/event-sources.md +72 -391
  11. package/dist/global-skills/aws-serverless/references/lambda.md +69 -428
  12. package/dist/global-skills/aws-serverless/references/orchestration.md +65 -384
  13. package/dist/global-skills/aws-serverless/references/production.md +78 -415
  14. package/dist/global-skills/aws-serverless/references/troubleshooting.md +92 -619
  15. package/dist/global-skills/claimable-postgres/SKILL.md +60 -49
  16. package/dist/global-skills/design-my-data/SKILL.md +2 -0
  17. package/dist/global-skills/eas-app-stores/SKILL.md +8 -0
  18. package/dist/global-skills/eas-app-stores/references/play-store.md +2 -0
  19. package/dist/global-skills/eas-hosting/SKILL.md +8 -0
  20. package/dist/global-skills/eas-observe/SKILL.md +30 -8
  21. package/dist/global-skills/eas-observe/agents/openai.yaml +1 -1
  22. package/dist/global-skills/eas-observe/references/metrics.md +54 -3
  23. package/dist/global-skills/eas-observe/references/queries.md +78 -17
  24. package/dist/global-skills/eas-observe/references/setup.md +164 -5
  25. package/dist/global-skills/eas-observe/references/third-party.md +136 -0
  26. package/dist/global-skills/eas-simulator/SKILL.md +63 -11
  27. package/dist/global-skills/eas-simulator/references/controllers.md +21 -2
  28. package/dist/global-skills/eas-simulator/references/run-your-app.md +16 -11
  29. package/dist/global-skills/eas-simulator/references/troubleshooting.md +8 -5
  30. package/dist/global-skills/eas-update-insights/SKILL.md +8 -0
  31. package/dist/global-skills/eas-workflows/SKILL.md +15 -10
  32. package/dist/global-skills/eas-workflows/scripts/package.json +2 -7
  33. package/dist/global-skills/email-best-practices/.github/workflows/sync-skills.yml +30 -0
  34. package/dist/global-skills/email-best-practices/README.md +63 -0
  35. package/dist/global-skills/email-best-practices/references/accessibility.md +189 -0
  36. package/dist/global-skills/email-best-practices/references/compliance.md +125 -0
  37. package/dist/global-skills/email-best-practices/references/deliverability.md +121 -0
  38. package/dist/global-skills/email-best-practices/references/email-capture.md +129 -0
  39. package/dist/global-skills/email-best-practices/references/email-types.md +173 -0
  40. package/dist/global-skills/email-best-practices/references/list-management.md +157 -0
  41. package/dist/global-skills/email-best-practices/references/marketing-emails.md +115 -0
  42. package/dist/global-skills/email-best-practices/references/sending-reliability.md +155 -0
  43. package/dist/global-skills/email-best-practices/references/transactional-email-catalog.md +418 -0
  44. package/dist/global-skills/email-best-practices/references/transactional-emails.md +92 -0
  45. package/dist/global-skills/email-best-practices/references/webhooks-events.md +167 -0
  46. package/dist/global-skills/email-best-practices/tests/README.md +35 -0
  47. package/dist/global-skills/email-best-practices/tests/scenarios/01-spam-deliverability.md +46 -0
  48. package/dist/global-skills/email-best-practices/tests/scenarios/02-multi-region-compliance.md +48 -0
  49. package/dist/global-skills/email-best-practices/tests/scenarios/03-retry-idempotency.md +36 -0
  50. package/dist/global-skills/email-best-practices/tests/scenarios/04-webhook-bounce-handling.md +52 -0
  51. package/dist/global-skills/email-best-practices/tests/scenarios/05-new-saas-email-plan.md +51 -0
  52. package/dist/global-skills/expo-animation/LICENSE +21 -0
  53. package/dist/global-skills/expo-animation/RECIPES.md +385 -0
  54. package/dist/global-skills/expo-animation/SKILL.md +267 -0
  55. package/dist/global-skills/expo-animation/agents/openai.yaml +4 -0
  56. package/dist/global-skills/expo-app-clip/SKILL.md +8 -0
  57. package/dist/global-skills/expo-brownfield/SKILL.md +8 -0
  58. package/dist/global-skills/expo-brownfield/references/brownfield-isolated.md +53 -4
  59. package/dist/global-skills/expo-data-fetching/SKILL.md +8 -0
  60. package/dist/global-skills/expo-design-system/SKILL.md +356 -0
  61. package/dist/global-skills/expo-design-system/agents/openai.yaml +4 -0
  62. package/dist/global-skills/expo-design-system/references/audit.md +185 -0
  63. package/dist/global-skills/expo-dev-client/SKILL.md +8 -0
  64. package/dist/global-skills/expo-dom/SKILL.md +8 -0
  65. package/dist/global-skills/expo-examples/SKILL.md +8 -0
  66. package/dist/global-skills/expo-migrate-module/SKILL.md +113 -0
  67. package/dist/global-skills/expo-migrate-module/agents/openai.yaml +4 -0
  68. package/dist/global-skills/expo-migrate-module/references/compatibility.md +73 -0
  69. package/dist/global-skills/expo-migrate-module/references/example.md +212 -0
  70. package/dist/global-skills/expo-migrate-module/references/migration-map.md +306 -0
  71. package/dist/global-skills/expo-module/SKILL.md +11 -1
  72. package/dist/global-skills/expo-native-ui/SKILL.md +8 -0
  73. package/dist/global-skills/expo-project-structure/SKILL.md +114 -0
  74. package/dist/global-skills/expo-project-structure/agents/openai.yaml +4 -0
  75. package/dist/global-skills/expo-router/SKILL.md +8 -0
  76. package/dist/global-skills/expo-skill-feedback/SKILL.md +66 -33
  77. package/dist/global-skills/expo-skill-feedback/agents/openai.yaml +2 -2
  78. package/dist/global-skills/expo-tailwind-setup/SKILL.md +8 -0
  79. package/dist/global-skills/expo-ui/SKILL.md +8 -0
  80. package/dist/global-skills/expo-upgrade/SKILL.md +15 -1
  81. package/dist/global-skills/expo-web-to-native/SKILL.md +8 -0
  82. package/dist/global-skills/extension-to-functions-codebase/SKILL.md +129 -0
  83. package/dist/global-skills/extension-to-functions-codebase/references/configuration-migration.md +161 -0
  84. package/dist/global-skills/extension-to-functions-codebase/references/destructuring-shim.md +122 -0
  85. package/dist/global-skills/extension-to-functions-codebase/references/signature-mapping.md +84 -0
  86. package/dist/global-skills/firebase-ai-logic-basics/SKILL.md +61 -19
  87. package/dist/global-skills/firebase-ai-logic-basics/references/flutter_setup.md +6 -3
  88. package/dist/global-skills/firebase-app-hosting-basics/SKILL.md +5 -1
  89. package/dist/global-skills/firebase-auth-basics/SKILL.md +4 -2
  90. package/dist/global-skills/firebase-basics/SKILL.md +5 -5
  91. package/dist/global-skills/firebase-crashlytics/SKILL.md +2 -0
  92. package/dist/global-skills/firebase-data-connect/SKILL.md +2 -0
  93. package/dist/global-skills/firebase-firestore/SKILL.md +3 -6
  94. package/dist/global-skills/firebase-hosting-basics/SKILL.md +4 -1
  95. package/dist/global-skills/firebase-remote-config-basics/SKILL.md +5 -1
  96. package/dist/global-skills/firebase-security-rules-auditor/SKILL.md +4 -1
  97. package/dist/global-skills/harden/SKILL.md +2 -1
  98. package/dist/global-skills/instrument-feature-flags/SKILL.md +1 -0
  99. package/dist/global-skills/instrument-feature-flags/references/COMMANDMENTS.md +5 -0
  100. package/dist/global-skills/instrument-feature-flags/references/adding-feature-flag-code.md +6 -4
  101. package/dist/global-skills/instrument-feature-flags/references/android.md +6 -4
  102. package/dist/global-skills/instrument-feature-flags/references/api.md +6 -4
  103. package/dist/global-skills/instrument-feature-flags/references/best-practices.md +6 -4
  104. package/dist/global-skills/instrument-feature-flags/references/django.md +38 -5
  105. package/dist/global-skills/instrument-feature-flags/references/dotnet.md +10 -4
  106. package/dist/global-skills/instrument-feature-flags/references/elixir.md +6 -4
  107. package/dist/global-skills/instrument-feature-flags/references/flask.md +17 -5
  108. package/dist/global-skills/instrument-feature-flags/references/flutter.md +7 -5
  109. package/dist/global-skills/instrument-feature-flags/references/go.md +6 -4
  110. package/dist/global-skills/instrument-feature-flags/references/ios.md +6 -4
  111. package/dist/global-skills/instrument-feature-flags/references/java.md +6 -4
  112. package/dist/global-skills/instrument-feature-flags/references/laravel.md +6 -4
  113. package/dist/global-skills/instrument-feature-flags/references/next-js.md +74 -8
  114. package/dist/global-skills/instrument-feature-flags/references/nodejs.md +6 -4
  115. package/dist/global-skills/instrument-feature-flags/references/php.md +6 -4
  116. package/dist/global-skills/instrument-feature-flags/references/python.md +6 -4
  117. package/dist/global-skills/instrument-feature-flags/references/react-native.md +6 -4
  118. package/dist/global-skills/instrument-feature-flags/references/react.md +6 -4
  119. package/dist/global-skills/instrument-feature-flags/references/ruby-on-rails.md +6 -4
  120. package/dist/global-skills/instrument-feature-flags/references/ruby.md +6 -4
  121. package/dist/global-skills/instrument-feature-flags/references/rust.md +6 -4
  122. package/dist/global-skills/instrument-feature-flags/references/usage.md +41 -4
  123. package/dist/global-skills/instrument-feature-flags/references/web.md +7 -5
  124. package/dist/global-skills/instrument-product-analytics/SKILL.md +2 -1
  125. package/dist/global-skills/instrument-product-analytics/references/COMMANDMENTS.md +5 -0
  126. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-hybrid.md +12 -14
  127. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-ssr.md +25 -21
  128. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-django.md +75 -55
  129. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-fastapi.md +3 -6
  130. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-flask.md +26 -16
  131. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-nuxt-3-6.md +8 -5
  132. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-nuxt-4.md +4 -4
  133. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-react-react-router-7-framework.md +3 -3
  134. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-ruby-on-rails.md +8 -4
  135. package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-tanstack-start.md +20 -8
  136. package/dist/global-skills/instrument-product-analytics/references/android.md +46 -4
  137. package/dist/global-skills/instrument-product-analytics/references/angular.md +41 -5
  138. package/dist/global-skills/instrument-product-analytics/references/astro.md +41 -5
  139. package/dist/global-skills/instrument-product-analytics/references/configuration.md +7 -4
  140. package/dist/global-skills/instrument-product-analytics/references/django.md +38 -5
  141. package/dist/global-skills/instrument-product-analytics/references/dotnet.md +10 -4
  142. package/dist/global-skills/instrument-product-analytics/references/elixir.md +6 -4
  143. package/dist/global-skills/instrument-product-analytics/references/flask.md +17 -5
  144. package/dist/global-skills/instrument-product-analytics/references/flutter.md +49 -4
  145. package/dist/global-skills/instrument-product-analytics/references/go.md +6 -4
  146. package/dist/global-skills/instrument-product-analytics/references/identify-users.md +38 -7
  147. package/dist/global-skills/instrument-product-analytics/references/ios.md +14 -4
  148. package/dist/global-skills/instrument-product-analytics/references/laravel.md +6 -4
  149. package/dist/global-skills/instrument-product-analytics/references/next-js.md +74 -8
  150. package/dist/global-skills/instrument-product-analytics/references/nuxt-js-3-6.md +39 -7
  151. package/dist/global-skills/instrument-product-analytics/references/nuxt-js.md +40 -4
  152. package/dist/global-skills/instrument-product-analytics/references/php.md +6 -4
  153. package/dist/global-skills/instrument-product-analytics/references/posthog-python.md +21 -17
  154. package/dist/global-skills/instrument-product-analytics/references/python.md +15 -5
  155. package/dist/global-skills/instrument-product-analytics/references/react-native.md +41 -7
  156. package/dist/global-skills/instrument-product-analytics/references/react-router-v6.md +18 -4
  157. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-data-mode.md +18 -4
  158. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md +18 -4
  159. package/dist/global-skills/instrument-product-analytics/references/react-router-v7-framework-mode.md +18 -4
  160. package/dist/global-skills/instrument-product-analytics/references/ruby-on-rails.md +6 -4
  161. package/dist/global-skills/instrument-product-analytics/references/ruby.md +6 -4
  162. package/dist/global-skills/instrument-product-analytics/references/svelte.md +42 -6
  163. package/dist/global-skills/instrument-product-analytics/references/tanstack-start.md +31 -7
  164. package/dist/global-skills/instrument-product-analytics/references/usage.md +41 -4
  165. package/dist/global-skills/instrument-product-analytics/references/vue-js.md +42 -6
  166. package/dist/global-skills/manifest.json +6 -1
  167. package/dist/global-skills/neon/SKILL.md +235 -121
  168. package/dist/global-skills/neon-ai-gateway/SKILL.md +95 -16
  169. package/dist/global-skills/neon-functions/SKILL.md +242 -152
  170. package/dist/global-skills/neon-functions/references/ai-sdk.md +9 -3
  171. package/dist/global-skills/neon-functions/references/mastra-studio.md +1 -1
  172. package/dist/global-skills/neon-functions/references/mcp.md +13 -4
  173. package/dist/global-skills/neon-functions/references/sentry.md +165 -52
  174. package/dist/global-skills/neon-functions/references/sse.md +23 -6
  175. package/dist/global-skills/neon-object-storage/SKILL.md +40 -16
  176. package/dist/global-skills/neon-postgres/SKILL.md +63 -262
  177. package/dist/global-skills/neon-postgres-branches/SKILL.md +61 -50
  178. package/dist/global-skills/neon-postgres-egress-optimizer/SKILL.md +22 -3
  179. package/dist/global-skills/resend/SKILL.md +6 -6
  180. package/dist/global-skills/resend/references/automations.md +2 -2
  181. package/dist/global-skills/resend/references/broadcasts.md +11 -3
  182. package/dist/global-skills/resend/references/domains.md +25 -3
  183. package/dist/global-skills/resend/references/installation.md +7 -7
  184. package/dist/global-skills/setup-payments/SKILL.md +4 -5
  185. package/dist/global-skills/stripe-best-practices/SKILL.md +32 -7
  186. package/dist/global-skills/stripe-best-practices/references/billing.md +11 -1
  187. package/dist/global-skills/stripe-best-practices/references/payments.md +23 -2
  188. package/dist/global-skills/stripe-best-practices/references/security.md +21 -11
  189. package/dist/global-skills/stripe-best-practices/references/tax.md +116 -11
  190. package/dist/global-skills/supabase/CHANGELOG.md +25 -0
  191. package/dist/global-skills/supabase/SKILL.md +5 -1
  192. package/dist/global-skills/supabase-postgres-best-practices/CHANGELOG.md +30 -0
  193. package/dist/global-skills/supabase-postgres-best-practices/SKILL.md +1 -1
  194. package/dist/global-skills/use-kit-mcp/SKILL.md +5 -5
  195. package/dist/global-skills/use-railway/SKILL.md +4 -4
  196. package/dist/global-skills/use-railway/references/iac.md +206 -57
  197. package/dist/global-skills/use-railway/scripts/railway-api.sh +7 -2
  198. package/dist/global-skills/xcode-project-setup/SKILL.md +2 -0
  199. package/package.json +12 -9
  200. package/LICENSE.md +0 -47
  201. package/dist/global-skills/eas-workflows/scripts/validate.js +0 -84
  202. package/dist/global-skills/expo-skill-feedback/scripts/skill-feedback.cjs +0 -118
  203. package/dist/global-skills/neon-functions/references/hono-websockets.md +0 -145
@@ -23,6 +23,7 @@ Before installing, confirm all of the following:
23
23
  1. **An Expo account.** Sign up at [expo.dev/signup](https://expo.dev/signup) if needed.
24
24
  2. **Expo SDK 55 or later.** Run `npx expo-doctor` to check, and `npx expo install --fix` to update dependencies. SDK 56+ unlocks the newer `ObserveRoot` / `useObserve` API.
25
25
  3. **An EAS project.** The app must have `extra.eas.projectId` set in its app config. If not, run `eas init` to create one.
26
+ 4. **A development build or a production build.** `expo-observe` is a native library, so it does **not** work in Expo Go. Testing the integration requires a [development build](https://docs.expo.dev/develop/development-builds/introduction/).
26
27
 
27
28
  ## Step 1 — Install the library
28
29
 
@@ -162,6 +163,40 @@ export default ObserveRoot.wrap(RootLayout);
162
163
 
163
164
  `markInteractive()` is safe to call repeatedly — only the **first** call per session is recorded. If the app has more than one entry screen (onboarding, login, deep-link targets), call `markInteractive()` on **each one**. Otherwise TTI will be missing for sessions that open via a deep link to a screen without the call.
164
165
 
166
+ ### Custom params and route name
167
+
168
+ `markInteractive()` accepts an options object. Use `params` to slice TTI by an app-specific dimension (cohort, tenant, feature-flag variant, cache hit). Use `routeName` to override the route the event is tagged with — useful when the logical screen name differs from the router path, when the route is dynamic, or when the app does not use Expo Router. Param values may be strings, numbers, booleans, or any JSON-serializable value.
169
+
170
+ ```tsx
171
+ // SDK 56+: const { markInteractive } = useObserve();
172
+ // SDK 55: AppMetrics.markInteractive({ ... })
173
+ markInteractive({
174
+ routeName: '/feed',
175
+ params: { tenant: 'acme', cohort: 'beta', cacheHit: true },
176
+ });
177
+ ```
178
+
179
+ ### Declarative alternative: `<ObserveInteractiveMarker />` (SDK 56+)
180
+
181
+ Instead of calling `markInteractive()` from an effect, render the marker component at the point the screen becomes interactive. It renders nothing and calls `markInteractive()` once on mount.
182
+
183
+ ```tsx
184
+ import { ObserveInteractiveMarker } from 'expo-observe';
185
+
186
+ function Feed({ items }) {
187
+ if (!items) return <Spinner />;
188
+
189
+ return (
190
+ <>
191
+ <FeedList items={items} />
192
+ <ObserveInteractiveMarker params={{ cacheHit: true }} />
193
+ </>
194
+ );
195
+ }
196
+ ```
197
+
198
+ The marker fires **once**, on mount, so its `params` are read from the first render. Changing them later has no effect and logs a development warning. When the params are only known later, call `useObserve().markInteractive(...)` imperatively instead.
199
+
165
200
  ## Step 4 — Build the app
166
201
 
167
202
  Metrics are collected from real builds, not from `expo start`:
@@ -180,7 +215,7 @@ To query metrics from the terminal with the EAS CLI, see [`./queries.md`](./quer
180
215
 
181
216
  ## Optional — per-route navigation metrics (SDK 56+)
182
217
 
183
- By default `expo-observe` records app-wide startup metrics only. To additionally get **per-route / per-screen** navigation metrics (`cold_ttr`, `warm_ttr`, and a per-navigation `tti`, each tagged with the route/screen), enable one of the navigation integrations. These require **SDK 56 or later**; on earlier SDKs they are silent no-ops. Query the resulting data with `eas observe:routes` (see [`./queries.md`](./queries.md)).
218
+ By default `expo-observe` records app-wide startup metrics only. To additionally get **per-route / per-screen** navigation metrics (`cold_ttr`, `warm_ttr`, and a per-navigation `tti`, each tagged with the route/screen), enable one of the navigation integrations. These require **SDK 56 or later**; on earlier SDKs they are silent no-ops. Query the resulting data with `eas observe:routes`, or with `observe:metrics` / `observe:metrics-summary` under the CLI aliases `nav_cold_ttr`, `nav_warm_ttr`, and `nav_tti` (see [`./queries.md`](./queries.md)).
184
219
 
185
220
  Pick the integration that matches the app's router:
186
221
 
@@ -216,6 +251,8 @@ Docs: https://docs.expo.dev/eas/observe/integrations/expo-router/
216
251
 
217
252
  Events are tagged with the route **pattern** (e.g. `/(tabs)/sessions/[sessionId]`) so the dashboard buckets distinct param values together; the resolved `url` and `routeParams` are also included. Requires `expo-router` installed at runtime, or the integration no-ops.
218
253
 
254
+ `router.prefetch()` does not count as a user navigation and never seeds a `cold_ttr` or `warm_ttr` measurement. The next user-driven navigation to a prefetched route emits `warm_ttr`, because the screen already rendered.
255
+
219
256
  ### React Navigation
220
257
 
221
258
  Docs: https://docs.expo.dev/eas/observe/integrations/react-navigation/
@@ -233,7 +270,9 @@ Requires `@react-navigation/native` 7.0.0 or later. Same `useObserve()` screen u
233
270
  });
234
271
  ```
235
272
 
236
- 2. Replace the top-level `<NavigationContainer>` with `<ObserveNavigationContainer>` — a drop-in replacement that accepts the same props and forwards the same ref. If you pass a `linking` config it is used to resolve a human-readable screen path; otherwise the metric falls back to `route.name`.
273
+ 2. Connect the integration to the navigation tree. Which component you use depends on whether the app uses React Navigation's dynamic or static configuration. Both record the same per-screen metrics.
274
+
275
+ **Dynamic configuration** — replace the top-level `<NavigationContainer>` with `<ObserveNavigationContainer>`. It is a drop-in replacement that accepts the same props and forwards the same ref. If you pass a `linking` config it is used to resolve a human-readable screen path; otherwise the metric falls back to `route.name`.
237
276
 
238
277
  ```tsx
239
278
  import { ObserveNavigationContainer } from 'expo-observe/integrations/react-navigation';
@@ -243,8 +282,65 @@ Requires `@react-navigation/native` 7.0.0 or later. Same `useObserve()` screen u
243
282
  }
244
283
  ```
245
284
 
285
+ **Static configuration** — `createStaticNavigation()` renders the container for you, so there is nothing to replace. Create the navigation ref yourself, pass it to both `<Navigation>` and `<ObserveNavigationProvider>`, and the provider records the same timings by listening through that ref.
286
+
287
+ ```tsx
288
+ import { createStaticNavigation, useNavigationContainerRef } from '@react-navigation/native';
289
+ import { ObserveNavigationProvider } from 'expo-observe/integrations/react-navigation';
290
+
291
+ const Navigation = createStaticNavigation(RootStack);
292
+
293
+ export default function App() {
294
+ const navigationRef = useNavigationContainerRef();
295
+
296
+ return (
297
+ <ObserveNavigationProvider navigationRef={navigationRef}>
298
+ <Navigation ref={navigationRef} />
299
+ </ObserveNavigationProvider>
300
+ );
301
+ }
302
+ ```
303
+
304
+ `ObserveNavigationProvider` renders no container of its own, but it must be an ancestor of every screen so `useObserve()` works inside them. Pass the **same** ref to both elements; a ref that is not connected to a container emits no metrics.
305
+
306
+ With React Navigation v7's default `lazy: true`, unfocused tabs stay unmounted, so their first focus records as `cold_ttr` rather than `warm_ttr`.
307
+
246
308
  In both integrations, `useObserve()` is safe to leave in place even when the integration is disabled or the router package is absent — it falls back to the global `markInteractive`.
247
309
 
310
+ ### Per-route event params
311
+
312
+ Navigation events carry these params in addition to any custom ones passed to `markInteractive`:
313
+
314
+ | Param | Type | Notes |
315
+ |---|---|---|
316
+ | `routeName` | string | Route pattern (`/(tabs)/sessions/[sessionId]`) with Expo Router, route-name path (`/Tabs/Sessions`) with React Navigation. Never contains param values. |
317
+ | `url` | string | Resolved pathname. Expo Router only. |
318
+ | `routeParams` | object | Resolved route params, e.g. `{ sessionId: 'abc' }`. |
319
+ | `isAppLaunch` | boolean | `cold_ttr` only. `true` when measured from process start rather than from a navigation action. |
320
+ | `urlHidden` | boolean | Present as `true` when a filtered param caused `url` to be omitted. See below. |
321
+
322
+ ### Filter sensitive params (SDK 57+)
323
+
324
+ By default the integrations export the resolved URL and every serializable route and query param. If any of those carry sensitive values, list their keys in `filteredParams`:
325
+
326
+ ```tsx
327
+ Observe.configure({
328
+ integrations: {
329
+ 'expo-router': { filteredParams: ['userId', 'token'] },
330
+ // or: 'react-navigation': { filteredParams: ['userId', 'token'] },
331
+ },
332
+ });
333
+ ```
334
+
335
+ Filtered keys are removed from `routeParams`, and the event drops `url` in favor of `urlHidden: true`. `routeName` is unaffected, because it is a pattern and never contains param values.
336
+
337
+ ### Common integration mistakes
338
+
339
+ - Calling `Observe.configure()` after mount, or toggling an integration flag mid-session, **throws**. Enable it at module scope.
340
+ - `Calling markInteractive on unmounted screen` or `No metadata available for the current screen` means the call ran outside a screen component or after unmount. Move it into a `useEffect` inside the screen component.
341
+ - Call `useObserve()` inside the screen component, not in a wrapper above it. A screen identity that changes between renders logs a warning.
342
+ - With React Navigation, `markInteractive()` records only once the screen is focused. A call on an unfocused screen updates internal state but emits no `tti` event until focus.
343
+
248
344
  ## Optional — user-defined events (SDK 56+)
249
345
 
250
346
  Beyond the automatic startup and navigation metrics, you can record your own named events from anywhere in the app to track product moments — a completed onboarding, an exported report, a selected item. Use `Observe.logEvent(name, options?)`.
@@ -266,9 +362,10 @@ function handleOnboardingComplete() {
266
362
  | Parameter | Type | Required | Notes |
267
363
  |---|---|---|---|
268
364
  | `name` | `string` | yes | Stable, dot-separated identifier, e.g. `'report.exported'`. |
269
- | `options.attributes` | `Record<string, string \| number \| boolean \| array \| nested object>` | no | Structured context attached to the event. |
365
+ | `options.attributes` | `Record<string, string \| number \| boolean \| array \| nested object>` | no | Structured context attached to the event. Other JS values (`Date`, `undefined`, functions) are dropped. |
270
366
  | `options.body` | `string` | no | Free-form message complementing the structured attributes. |
271
367
  | `options.severity` | `'trace' \| 'debug' \| 'info' \| 'warn' \| 'error' \| 'fatal'` | no | Defaults to `'info'`. |
368
+ | `options.displayName` | `string` | no | Human-friendly label, e.g. `'Onboarding completed'`. Used **only** in the dashboard's session timeline view; grouping still keys off `name`. |
272
369
 
273
370
  **Attributes** are the primary way to make an event queryable — attach the identifiers and measurements you'll want to filter or break down by:
274
371
 
@@ -306,12 +403,74 @@ User-defined events are persisted on-device, batched, and dispatched on the next
306
403
 
307
404
  User-defined events appear under the **Events** tab in the Observe dashboard, and are queryable from the terminal with `eas observe:events` — see [`./queries.md`](./queries.md).
308
405
 
406
+ ## Optional — error reporting
407
+
408
+ `expo-observe` records errors as non-fatal `exception` log events alongside the performance metrics. They arrive by three paths, and they are **not** a replacement for a crash reporter such as Sentry — EAS Observe has no crash reporting yet.
409
+
410
+ > These APIs ship in `expo-observe` but are not covered by the published docs yet. Verify the exports against the installed package version before relying on them.
411
+
412
+ **1. Unhandled JS errors — automatic.** A global `ErrorUtils` handler is installed when the package is first imported, which is earlier than any `configure()` call, so errors thrown before configuration are still recorded. React Native's own behavior is unchanged: the red box in development and fatal termination in production still happen. Turn the recording off with `configure({ errorHandlingEnabled: false })`.
413
+
414
+ **2. Render-phase errors — `<ObserveErrorBoundary>`.** Render errors never reach `ErrorUtils`, so a boundary is the only way to capture them with the React component stack. The boundary records the error and then renders `fallback` in place of the subtree.
415
+
416
+ ```tsx
417
+ import { ObserveErrorBoundary } from 'expo-observe';
418
+
419
+ <ObserveErrorBoundary
420
+ fallback={({ error, resetError }) => <ErrorScreen error={error} onRetry={resetError} />}>
421
+ <Feed />
422
+ </ObserveErrorBoundary>;
423
+ ```
424
+
425
+ `fallback` is required and accepts a React element, `null`, or a function receiving `{ error, resetError }`. `resetError()` clears the caught error and re-mounts the children, so they restart from a clean state. There is no capture-and-rethrow mode — a boundary always renders one of the three fallback forms. Errors that no boundary catches still reach the global handler.
426
+
427
+ **3. Handled errors — `Observe.reportError(error)`.** Report failures your code caught and recovered from, which reach neither the global handler nor a boundary.
428
+
429
+ ```tsx
430
+ try {
431
+ await syncCart();
432
+ } catch (error) {
433
+ Observe.reportError(error);
434
+ }
435
+ ```
436
+
437
+ The value is normalized before it is sent: an `Error` contributes `name`, `message`, and `stack`; any other thrown value (a string, a plain object, a number) is stringified into the message with no stack. `reportError` never throws — it is called from a `catch` block, so a failure inside it must not turn a handled error into an unhandled one.
438
+
439
+ ## Optional — runtime configuration
440
+
441
+ `Observe.configure()` controls collection and dispatch. Call it at module scope, before mount. Integration flags in particular cannot be toggled at runtime.
442
+
443
+ | Option | Type | Default | What it does |
444
+ |---|---|---|---|
445
+ | `environment` | string | `process.env.NODE_ENV`, falling back to `'production'` | Metadata tag used to group metrics; filterable in the dashboard. Does not gate dispatch. |
446
+ | `dispatchingEnabled` | boolean | `true` | Master switch. While `false`, pending metrics are dropped, not queued. |
447
+ | `dispatchInDebug` | boolean | `false` | Dispatch metrics collected from debug builds. No effect on release builds. |
448
+ | `sampleRate` | number | `undefined` (all installations) | Fraction of installations that dispatch, in `[0, 1]`. |
449
+ | `errorHandlingEnabled` | boolean | `true` | Record unhandled JS errors as `exception` events. |
450
+ | `integrations` | object | `undefined` | Opt in to `expo-router`, `react-navigation`, or a third-party integration. |
451
+
452
+ **Network request monitoring is automatic and has no off switch.** `expo-observe` observes `URLSession` traffic on iOS and `OkHttpClient` traffic on Android from launch, and attaches a rollup of the launch window to the TTI event — including the **host** of the slowest request (see [`./metrics.md`](./metrics.md)). Observe's own uploads are excluded. No `configure()` option disables it as of `expo-observe` 57.0.9, so treat request hosts as data that leaves the device.
453
+
454
+ **Sampling** is deterministic per installation: an installation is permanently in-sample or out-of-sample for a given rate, so the slice is stable across launches rather than a random subset of sessions. Out-of-sample installations drop pending metrics instead of accumulating them, so lowering the rate later does not retroactively send earlier sessions. Values outside `[0, 1]` are clamped. Sampling depends on `dispatchingEnabled`.
455
+
456
+ **Manual flush.** Events dispatch automatically when the app backgrounds — on Android through a background worker once connectivity returns, on iOS when the app resigns active or is about to terminate. Call `await Observe.dispatchEvents()` to flush early, which is mainly useful while testing.
457
+
458
+ **Custom endpoint.** EAS Observe sends OTLP over HTTP with a JSON payload, posting to `<endpointUrl>/<project-id>/v1/metrics` and `<endpointUrl>/<project-id>/v1/logs`. Point it at any OpenTelemetry-compatible backend or collector through the **app config**, not `configure()`:
459
+
460
+ ```json
461
+ { "expo": { "extra": { "eas": { "observe": { "endpointUrl": "https://your-endpoint.com" } } } } }
462
+ ```
463
+
464
+ The URL is baked into the native layer at build time, so run `npx expo prebuild` and rebuild after changing it. If the backend expects standard OTLP paths without the project-ID prefix, route through a collector.
465
+
309
466
  ## Quick checklist
310
467
 
311
- - [ ] SDK ≥ 55, EAS project linked.
468
+ - [ ] SDK ≥ 55, EAS project linked, development or production build (not Expo Go).
312
469
  - [ ] `expo-observe` installed via `npx expo install`.
313
470
  - [ ] Root component exported through `AppMetricsRoot.wrap(...)` (SDK 55) or `ObserveRoot.wrap(...)` (SDK 56+).
314
471
  - [ ] `markInteractive()` called from every entry screen once it is genuinely interactive — global `AppMetrics.markInteractive()` on SDK 55, or `useObserve()` hook on SDK 56+.
315
- - [ ] (Optional, SDK 56+) Per-route metrics enabled via `Observe.configure({ integrations: { ... } })`, plus `<ObserveNavigationContainer>` for React Navigation.
472
+ - [ ] (Optional, SDK 56+) Per-route metrics enabled via `Observe.configure({ integrations: { ... } })`, plus `<ObserveNavigationContainer>` (dynamic) or `<ObserveNavigationProvider>` (static) for React Navigation.
473
+ - [ ] (Optional, SDK 57+) Sensitive route/query params listed in `filteredParams`.
316
474
  - [ ] (Optional, SDK 56+) User-defined events emitted via `Observe.logEvent(name, { attributes })` with stable, lowercase, dot-separated names and no PII.
475
+ - [ ] (Optional) `<ObserveErrorBoundary>` around risky subtrees and `Observe.reportError` in recovery paths.
317
476
  - [ ] New build produced with `eas build` and metrics visible in the Observe dashboard.
@@ -0,0 +1,136 @@
1
+ # Add an EAS Observe integration to a third-party package
2
+
3
+ This reference is for **package authors**, not app developers. It describes how a library ships an optional EAS Observe integration so that apps using the library get events about problems that application code cannot detect on its own.
4
+
5
+ > Source: https://docs.expo.dev/eas/observe/integrations/third-party/ — consult this page for the latest guidance.
6
+
7
+ App-side setup lives in [`./setup.md`](./setup.md). Querying the resulting events is in [`./queries.md`](./queries.md).
8
+
9
+ ## When to use this
10
+
11
+ Report actionable issues the developer can fix. Good candidates:
12
+
13
+ - An image decoded at far higher resolution than the device screen needs.
14
+ - A background task that exceeds its expected duration.
15
+ - A native resource that loads slowly.
16
+
17
+ Do not use it for general product analytics or for anything the app author could log themselves.
18
+
19
+ ## Requirements
20
+
21
+ - **Expo SDK 57 or later.** `Observe.registerIntegration()` is not available earlier.
22
+ - The consuming app must already have `expo-observe` installed and a build produced.
23
+
24
+ ## Step 1 — Depend on `expo-observe` optionally
25
+
26
+ The package must keep working when `expo-observe` is absent. Declare it as an **optional peer dependency** plus a dev dependency for types and tests. Never make it a required runtime dependency.
27
+
28
+ ```json
29
+ {
30
+ "peerDependencies": { "expo-observe": ">=57.0.0" },
31
+ "peerDependenciesMeta": { "expo-observe": { "optional": true } },
32
+ "devDependencies": { "expo-observe": "^57.0.0" }
33
+ }
34
+ ```
35
+
36
+ Load it with `require()` inside `try/catch`, and type it with `typeof import()` so the types survive:
37
+
38
+ ```ts
39
+ // observe.ts
40
+ let observeModule: typeof import('expo-observe') | undefined;
41
+
42
+ try {
43
+ observeModule = require('expo-observe') as typeof import('expo-observe');
44
+ } catch {
45
+ // The integration stays disabled when expo-observe is not installed.
46
+ }
47
+ ```
48
+
49
+ ## Step 2 — Declare the integration config
50
+
51
+ Use declaration merging to add your integration key to `ObserveIntegrationsConfig`:
52
+
53
+ ```ts
54
+ // observe.types.ts
55
+ export type YourPackageIntegrationConfig = {
56
+ thresholdMs?: number;
57
+ };
58
+
59
+ declare module 'expo-observe' {
60
+ interface ObserveIntegrationsConfig {
61
+ 'your-package'?: boolean | YourPackageIntegrationConfig;
62
+ }
63
+ }
64
+ ```
65
+
66
+ Export this declaration from the package entry point so TypeScript loads it when the app imports your package. App developers then enable it the same way they enable the first-party integrations:
67
+
68
+ ```tsx
69
+ Observe.configure({
70
+ integrations: {
71
+ 'your-package': true,
72
+ // or, with options:
73
+ // 'your-package': { thresholdMs: 1500 },
74
+ },
75
+ });
76
+ ```
77
+
78
+ ## Step 3 — Register the integration
79
+
80
+ `Observe.registerIntegration(name, callback)` invokes the callback once, when the named integration config becomes available. The callback does not run when the key is omitted or set to `false`.
81
+
82
+ ```ts
83
+ // observe.ts
84
+ export function initObserveIntegration() {
85
+ // The `typeof window` check skips initialization during server-side rendering on web.
86
+ if (typeof window !== 'undefined' && observeModule) {
87
+ const { Observe } = observeModule;
88
+
89
+ Observe.registerIntegration('your-package', config => {
90
+ if (config) {
91
+ enableObserveIntegration(config === true ? {} : config);
92
+ }
93
+ });
94
+ }
95
+ }
96
+ ```
97
+
98
+ Call the initializer from the package entry point:
99
+
100
+ ```ts
101
+ // index.ts
102
+ import { initObserveIntegration } from './observe';
103
+
104
+ export type { YourPackageIntegrationConfig } from './observe.types';
105
+
106
+ initObserveIntegration();
107
+ ```
108
+
109
+ Note the `config === true` normalization: the key accepts either a boolean or an options object, so collapse `true` to `{}` before using it.
110
+
111
+ ## Step 4 — Log events
112
+
113
+ Emit through `Observe.logEvent()` when the package detects an actionable issue. Guard on both module presence and enablement, so a disabled integration costs nothing.
114
+
115
+ ```ts
116
+ export function logExpensiveOperation(durationMs: number, thresholdMs: number) {
117
+ if (!observeModule || !enabled) {
118
+ return;
119
+ }
120
+
121
+ const { Observe } = observeModule;
122
+
123
+ Observe.logEvent('your-package.expensive-operation', {
124
+ severity: 'warn',
125
+ body: 'Reduce the work performed by this operation or increase the configured threshold.',
126
+ attributes: { durationMs, thresholdMs },
127
+ });
128
+ }
129
+ ```
130
+
131
+ Naming rules, matching the app-side event conventions in [`./setup.md`](./setup.md):
132
+
133
+ - Lowercase, dot-separated, with **your package name as the first segment**: `your-package.expensive-operation`.
134
+ - Keep names stable. The dashboard groups by exact name.
135
+ - No PII in names, attribute keys, or attribute values.
136
+ - Use `severity` to separate warnings from errors, and `body` for the remediation hint. The attributes carry the measurements.
@@ -3,7 +3,7 @@ name: eas-simulator
3
3
  description: "EAS service (paid). Run and control a user's app on a remote iOS/Android simulator hosted on EAS cloud. Read before running any `eas simulator:*` commands - it has the current syntax for this experimental API. Use whenever the user needs a simulator they can't run locally - 'run my app on a cloud simulator', 'use eas simulator to run/install/screenshot my app', 'I'm on Linux/Cursor and need an iOS device', 'no sim on this box / headless CI', 'let an agent click through my app and screenshot it', 'test my dev build on a remote sim with live reload', 'stream a sim to my browser' - even when they don't say 'EAS Simulator' or 'cloud'. On a host WITHOUT a local simulator (Linux, CI, cloud sandbox) it's the default; on macOS, do NOT auto-trigger for a plain 'run on the simulator' - use it only for a cloud/remote/shareable sim, an iOS version they lack, or an agent-driven session. NOT for local sims (expo run:ios, Xcode, Android Studio), EAS Build/Update, web preview, or physical devices."
4
4
  version: 1.0.0
5
5
  license: MIT
6
- allowed-tools: "Bash(npx *eas-cli@*), Bash(npx *agent-device@*), Bash(npx expo *), Bash(eas *), Bash(expo *), Bash(xcodebuild*), Bash(pod*)"
6
+ allowed-tools: "Bash(npx *eas-cli@*), Bash(npx *agent-device@*), Bash(npx expo *), Bash(eas *), Bash(expo *), Bash(xcodebuild*), Bash(pod*), Bash(argent *), Bash(ffmpeg*)"
7
7
  ---
8
8
 
9
9
  # EAS Simulator
@@ -20,7 +20,7 @@ The frontmatter `description` carries the trigger phrases. In short: use this to
20
20
 
21
21
  ## Cloud vs local: decide this first
22
22
 
23
- - **Non-macOS** (Linux / CI / cloud sandbox like Cursor Cloud, detect via `uname -s` ≠ `Darwin`): the only way to get a sim — **just proceed.**
23
+ - **Non-macOS** (Linux / CI / cloud sandbox like Cursor Cloud, detect via `uname -s` ≠ `Darwin`): the only way to get a sim — **proceed, once you've confirmed access** (see *Check availability first* below).
24
24
  - **macOS:** local sims exist and a cloud session costs money + latency, so **ask first** ("a remote cloud sim — to share a live preview, offload, or test an iOS version you lack — or just run locally?") unless the user explicitly said cloud/remote/shareable.
25
25
  - Always honor an explicit choice; for "run it locally" hand off to `expo run:ios` / Xcode.
26
26
 
@@ -41,6 +41,23 @@ fi
41
41
  - A controller to drive the device. This skill uses **agent-device** (open source, MIT), run on demand via `npx agent-device@latest` — nothing globally installed. **argent** is an alternative (`--type argent` in `simulator:start`); see [references/controllers.md](./references/controllers.md).
42
42
  - **`.env.eas-simulator`** is written/managed by eas-cli (not this skill): it holds the session id (`EAS_SIMULATOR_SESSION_ID`) + the daemon URL/**token**, so `get`/`stop`/`exec` default to that session (usually **omit `--id`**; pass `--id <id>` to target another). It carries a **token → keep it gitignored** (eas-cli marks it "do not commit" but may not add the ignore rule, and a fresh app's `.gitignore` won't cover it — add `.env.eas-simulator` if missing).
43
43
  - `--max-duration-minutes` is paid-plan only; otherwise a default applies.
44
+ - **The command blocks assume a POSIX shell** (bash/zsh) — `printf`, `lsof`, `$(seq …)` loops won't run in cmd/PowerShell. On Windows, run them in WSL or Git Bash, or translate as you go (the `eas-cli`/`agent-device` invocations themselves are cross-platform).
45
+
46
+ ## Check availability first
47
+
48
+ EAS Simulator is a **limited-access** EAS feature that is still rolling out, so it isn't enabled on every account. Confirm access **before** starting a session — this is a read-only check: no session, no billing.
49
+
50
+ ```bash
51
+ npx --yes eas-cli@latest simulator:availability --json
52
+ # → {"available": true, ...} enabled → continue to the core loop
53
+ # → {"available": false, ...} not enabled → do NOT start a session
54
+ ```
55
+
56
+ If it's **not** available, don't call `simulator:start` (it will fail). Instead, hand off gracefully so you keep making progress without this skill:
57
+ - Tell the user EAS Simulator isn't available on their account yet — it's coming soon.
58
+ - Fall back to their normal local path for the actual goal — `expo run:ios` / Xcode / Android Studio for a local sim/emulator, an EAS Build, or whatever else fits. Don't dead-end on the cloud sim; the request was almost never "use EAS Simulator specifically."
59
+
60
+ (If `simulator:availability` isn't recognized, the CLI is too old — upgrade, or treat a `not enabled for this account` error from `simulator:start` the same way: stop and fall back.)
44
61
 
45
62
  ## The core loop (always the same)
46
63
 
@@ -49,7 +66,8 @@ A session is: **start → (install your app) → drive → stop.** `eas-cli` own
49
66
  ```bash
50
67
  # 1. Start a session (boots the remote sim + agent-device daemon; writes .env.eas-simulator).
51
68
  printf '# managed by eas-cli\n' > .env.eas-simulator # clear any stale session first
52
- npx --yes eas-cli@latest simulator:start --platform ios --type agent-device --non-interactive
69
+ npx --yes eas-cli@latest simulator:start --platform ios --type agent-device --non-interactive \
70
+ --name "Checkout flow screenshots" # always name it — see 'Always name the session'
53
71
  # Then confirm it's live: simulator:get --json → status IN_PROGRESS (bounded poll in run-your-app.md).
54
72
 
55
73
  # 2. Drive it through `exec` (loads the session env, then runs the command you give it).
@@ -72,14 +90,37 @@ To **watch** it live, hand the user the `webPreviewUrl` that `start` prints (an
72
90
 
73
91
  `start` also prints a job-run URL.
74
92
 
93
+ ## Always name the session
94
+
95
+ Pass `--name "<description>"` on every `simulator:start`. The name appears in `simulator:list`, `simulator:get`, and on the **Simulator sessions** page on expo.dev, where it replaces the generic title on each row. Unnamed, every row reads "Simulator session" over a random id — a wall of identical entries nobody can navigate. Write the name for a **human scanning that list days later**, not for yourself during this run.
96
+
97
+ Write what the session is *for*, in a few plain words:
98
+
99
+ ```bash
100
+ --name "Checkout flow screenshots" # what you did
101
+ --name "Dev build — dark mode fix" # what you were testing
102
+ --name "Login repro for issue 412" # why it exists
103
+ ```
104
+
105
+ Rules:
106
+ - Derive it from the user's request, not from the mode or the tooling. `Mode C session`, `agent-device ios`, and `test` say nothing.
107
+ - **Length: aim for 3–6 words, ~40 characters, and treat 50 as the practical limit.** It renders as a single-line title in a narrow table column, so a long name clips. The API accepts up to **255 characters** and rejects an empty/whitespace-only name, but 255 is a ceiling you never approach, not a target. One noun phrase, no sentences.
108
+ - Be specific within that budget. Include a ticket or PR number when there is one.
109
+ - **Sentence case:** capitalize the first word only, and leave identifiers in their real casing (`Dev build for expo-router v4`, `Repro for EXPO-1234`). It's a row title, so no Title Case, no all-lowercase, and no trailing period.
110
+ - **Don't repeat what the table already shows.** Every row already displays the session id, platform, start time, duration, and who created it — so no ids, no `iOS`, no dates, no your-own-name. Spend the whole budget on what those columns can't say: the purpose.
111
+ - If the user names it, use their name as-is.
112
+ - Sessions are per-run, so name each new one for that run. Don't reuse an old name for different work.
113
+
114
+ `--name` is newer than `simulator:start` itself, so an older installed `eas-cli` can reject it. If that happens, run via `npx --yes eas-cli@latest` or upgrade; as a last resort, retry once without `--name` (the session starts unnamed). See [references/troubleshooting.md](./references/troubleshooting.md).
115
+
75
116
  ## Commands at a glance
76
117
 
77
118
  | Command | Purpose |
78
119
  |---|---|
79
- | `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]` | Create a session; boot the sim + controller; write `.env.eas-simulator`; print `webPreviewUrl` + job-run URL |
120
+ | `npx --yes eas-cli@latest simulator:start --platform ios\|android --name "<description>" [--type agent-device\|argent\|serve-sim] [--package-version X] [--max-duration-minutes N] [--non-interactive] [--json]` | Create a session; boot the sim + controller; write `.env.eas-simulator`; print `webPreviewUrl` + job-run URL. **Always pass `--name`** (see *Always name the session*). **`--json` suppresses the `.env.eas-simulator` write** — omit it for the `exec` flow, or set the env yourself from `remoteConfig`. |
80
121
  | `npx --yes eas-cli@latest simulator:exec <cmd> [args…]` | Load `.env.eas-simulator`, then run `<cmd>` with that env. The bridge to the controller. |
81
- | `npx --yes eas-cli@latest simulator:get [--id] [--json]` | Session status + connection details. **Use this to confirm readiness** (see *Operating principles*). |
82
- | `npx --yes eas-cli@latest simulator:list [--status …] [--type …] [--platform …]` | List an app's sessions |
122
+ | `npx --yes eas-cli@latest simulator:get [--id] [--json]` | Session status + connection details, including the session `--name`. **Use this to confirm readiness** (see *Operating principles*). |
123
+ | `npx --yes eas-cli@latest simulator:list [--status …] [--type …] [--platform …]` | List an app's sessions by name — this is what the `--name` you pass to `start` is for |
83
124
  | `npx --yes eas-cli@latest simulator:stop [--id]` | Stop a session (idempotent) |
84
125
 
85
126
  ## Running the user's app — pick a mode
@@ -109,16 +150,19 @@ Quick decision — **default to C; A and B are explicit-only:**
109
150
 
110
151
  | Verb | Does |
111
152
  |---|---|
112
- | `apps --platform ios` | List installed apps (the blank sim shows none) |
153
+ | `apps --platform ios` | List user-installed apps (the blank sim shows none); add `--all` to include system apps |
113
154
  | `install <appId> <path> --platform ios` | Install a local `.app` (uploads it) |
114
155
  | `install-from-source <url> --platform ios` | Install from a URL — the VM downloads it (use for EAS artifacts) |
115
- | `open <appId\|deep-link> --platform ios` | Launch an app (bundle id) or follow an app **deep link** (`exp+slug://…`). **Not** for the `webPreviewUrl` — that's a browser preview for the user, never the device. |
156
+ | `open <appId\|deep-link> --platform ios` | Launch an app (bundle id) or follow an app **deep link** (`exp+slug://…`). A first-time deep link raises a system **"Open in '<app>'?"** dialog — expect it (don't burn a snapshot discovering it) and `press 'label="Open"'` to hand off; it can be slow, so bound it with agent-device's own `--timeout` (e.g. `press 'label="Open"' --timeout 120000`) — **not** a shell `timeout` wrapper (macOS has no `timeout` binary). (Mode C sidesteps this dialog for the Metro-connect link via "Enter URL manually" — see run-your-app.md.) **Not** for the `webPreviewUrl` — that's a browser preview for the user, never the device. |
116
157
  | `snapshot -i` | Interactive accessibility tree → `@e1`-style refs |
117
158
  | `press <ref\|selector>` | Tap (e.g. `press @e2` or `press 'label="Open"'`) — **the tap verb is `press`, not `tap`** |
118
159
  | `fill <ref> "text"` | Type into a field |
119
160
  | `screenshot <path>` | Capture the screen to a local PNG (downloaded from the daemon) — requires an app to be open (`open` first) |
161
+ | `record start` / `record stop <path>` | Record the screen to a video — use this for **motion** (animations, gestures, transitions, timing), which a single screenshot can't capture |
120
162
  | `metro prepare` / `metro reload` | Point a dev client at Metro / reload (Mode C) |
121
163
 
164
+ **Screenshots vs. video.** Default to `screenshot` for static state, but for anything that *moves* — an animation, a transition, a gesture, a timing/jank question — **record a video and inspect the frames** instead; a still can't prove motion. Both controllers record (agent-device `record start`/`stop`, argent `screen-recording-start`/`stop`). Recordings sample at ~30fps — enough to see visible jank, not to prove sub-frame 60/120Hz hitches. For **timing** specifically, argent drops static frames by default (turn `trimStatic` off) — that plus other per-controller gotchas are in [references/controllers.md](./references/controllers.md).
165
+
122
166
  For the full verb set and the `argent` controller alternative, see [references/controllers.md](./references/controllers.md).
123
167
 
124
168
  ## Operating principles
@@ -128,10 +172,10 @@ The non-obvious mental model worth internalizing. Specific error→fix lookups (
128
172
  1. **Establish ground truth, then reset — don't patch-loop.** Never assume an existing session or Metro is yours or healthy. Before driving, confirm:
129
173
  - **cwd** — you're in the intended Expo project dir (a misdirected `start`/`exec` sessions the *wrong app* + drops a stray `.env.eas-simulator`; `pwd` / check `app.json`).
130
174
  - **session live** — `IN_PROGRESS` via `simulator:get --json` (a stopped session keeps its id + `remoteConfig`, so the dotenv alone isn't proof).
131
- - **one Metro on `:8081`** — reuse if it's yours, else free the port before starting (run-your-app.md).
175
+ - **Metro on its own port** — reuse only if you started it this session; else start one on a free port (`--port <N>`, e.g. 8082), don't kill another server to reclaim `:8081` (run-your-app.md).
132
176
  - **build fits intent** — a **release build can't live-reload**; if live edits are wanted and a release build is installed, **install the dev build, don't reconnect**.
133
177
 
134
- If current code isn't rendering after your **first** connect, stop poking live state: **reset to baseline** (stop session → clear dotenv → kill Metro) and redo the mode **once**; a second failure → stop and report. Never restart Metro in place, reconnect more than once, rebuild the native client to fix a JS/connection problem, or surface a preview URL while state is unknown. (A daemon drop — `ERR_NGROK_3200` / `Remote daemon is unavailable` — is the same: reset, don't retry.)
178
+ If current code isn't rendering after your **first** connect, stop poking live state: **reset to baseline** (stop session → clear dotenv → kill your Metro) and redo the mode **once**; a second failure → stop and report. Never restart Metro in place, reconnect more than once, rebuild the native client to fix a JS/connection problem, or surface a preview URL while state is unknown. (A daemon drop — `ERR_NGROK_3200` / `Remote daemon is unavailable` — is the same: reset, don't retry.)
135
179
  2. **`exec` is a wrapper, not a driver.** `simulator:exec` loads `.env.eas-simulator` and spawns the command you pass; the device verbs come from the controller (`npx agent-device@latest`). There is no `simulator:tap`.
136
180
  3. **Act immediately; don't park an idle session.** Sessions are short-lived — install and drive right after `start`. Leaving one idle drops the tunnel/daemon (→ reset, per #1).
137
181
  4. **Stop on every exit path (billing) and reset the dotenv.** `--non-interactive` doesn't auto-stop, and a forgotten session bills until stopped. Don't `start` again to "retry" a slow boot — that orphans a second billed session.
@@ -149,8 +193,16 @@ printf '# managed by eas-cli\n' > .env.eas-simulator # clear the stale session
149
193
 
150
194
  ## References
151
195
 
152
- - [references/run-your-app.md](./references/run-your-app.md) — full tested command sequences for modes A, B, and C (read before running a mode).
196
+ - [references/run-your-app.md](./references/run-your-app.md) — full command sequences for modes A, B, and C (read before running a mode).
153
197
  - [references/controllers.md](./references/controllers.md) — agent-device verb reference and the `argent` alternative.
154
198
  - [references/troubleshooting.md](./references/troubleshooting.md) — concrete errors and fixes.
155
199
 
156
200
  Source of truth: Expo docs and the `eas` / `agent-device` CLIs (`npx --yes eas-cli@latest simulator:* --help`, `agent-device --help`). This skill teaches how to apply them; it doesn't replace them.
201
+
202
+ ## Submitting Feedback
203
+ If you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:
204
+ ```bash
205
+ npx --yes submit-expo-feedback@latest --category skills --subject "eas-simulator" "<actionable feedback>"
206
+ ```
207
+ Only submit when you have something specific and actionable to report. Include as much relevant context as possible.
208
+ 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.
@@ -22,13 +22,31 @@ EAS-specific notes:
22
22
  - **`press`, not `tap`.** The tap verb is `press` — `tap` is not a verb.
23
23
  - **`snapshot -i` is slow on iOS** — tens of seconds is normal; wait for it.
24
24
  - **`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).
25
- - **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.
25
+ - **Exercised against a live session:** `apps`, `install`, `install-from-source`, `open`, `snapshot -i`, `press`, `fill`, `screenshot`, `scroll`, `gesture` (needs a preset, e.g. `gesture swipe left`), `logs`, `record` (`start`/`stop <path>`), `network`, `perf`. `metro` (`prepare`/`reload`) is the Mode C dev-client bridge. Pass `--platform ios`; run `<verb>` with no args to see its required subcommand/args.
26
26
 
27
27
  ## argent (alternative)
28
28
 
29
29
  `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`).
30
30
 
31
- **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).
31
+ **Invoking argent — run its tools directly.** Drive argent by running its tools directly — `argent run <tool> --udid <udid> …` (with `argent link` or the env-var config below) — where flags work (the examples here use this path); or via its MCP server, which passes structured params. Heads-up (flagged elsewhere, not reproduced in our own runs): routing an `argent run` call through `npx --yes eas-cli@latest simulator:exec` can **strip the `--flag` arguments**, so the tool runs with no options and fails confusingly. If you must go through `simulator:exec`, wrap it in `sh -c` and pass one `--args` JSON blob instead of flags: `npx --yes eas-cli@latest simulator:exec sh -c 'argent run <tool> --args "{\"udid\":\"<udid>\", …}"'`. argent's gesture tools take **normalized 0.0–1.0** coordinates, not pixels — check its help for the exact input shape.
32
+
33
+ **Installing apps in an argent session.** `--type argent` provisions only an argent daemon on the VM — there is no agent-device daemon, so agent-device install verbs don't apply. Install a local build with argent's own `reinstall-app` (tar-upload):
34
+
35
+ ```bash
36
+ argent run reinstall-app --udid <udid> --bundleId <bundle-id> --appPath ./MyApp.app
37
+ ```
38
+
39
+ Whenever the tools client is routed to a remote tool-server, it tars the local bundle and streams it up automatically — no extra flag. "Remote" covers both `argent link` and the env-var MCP config (`ARGENT_TOOLS_URL`), so this works in sandboxed shells too. It's a registry tool, so the MCP server exposes it identically — same call by CLI or MCP. Works for iOS `.app` (a directory), Android `.apk`, and Vega `.vpkg`; the client prints an upload line on stderr.
40
+
41
+ Needs argent ≥ 0.16.0 (the release that adds tar-upload) — verify with `argent --version`. On older versions `reinstall-app` resolves `--appPath` on the VM only, so a local path fails; drive an app already on the sim instead.
42
+
43
+ **System dialogs on argent (e.g. the first-time deep-link "Open in '<app>'?").** argent's UI queries (`describe` / `await-ui-element`) may not see system dialogs / native modals — a screenshot shows the dialog, but element lookups time out. When that happens, argent surfaces a hint with the fix (today that's a `boot-device --force` to switch its AX backend); follow the hint, then locate and tap "Open". There's no single press-with-timeout — you wait for the element, then tap it. Use argent's own command help for the exact tools and flags.
44
+
45
+ **Recording video on argent (`screen-recording-start`/`stop`).** The gotcha to know: argent **trims static stretches by default**, which drops the very frames you're measuring — turn that off when you care about cadence or timing (see argent's help for the flag). Recordings also carry a burned-in "Argent" watermark that can't be disabled on a hosted session — fine for diagnosis, mind it before sharing publicly. The stop call returns a video already downloaded locally; extract frames with `ffmpeg` (may need installing) to inspect motion frame by frame. The capture samples at ~30fps, so it shows visible jank but can't prove or disprove sub-frame hitches on 60/120Hz content.
46
+
47
+ **Screenshot resolution and token cost.** Screenshots cost context tokens once the agent reads them, so resolution is a real tradeoff. **argent's `screenshot` has two independent levers.** `scale` sets the image resolution and defaults **low** (too coarse to judge layout), so pass a larger scale when you need to **read** the UI. `includeImageInContext:false` keeps an image **out of the agent's context entirely** (zero token cost) — use that for a baseline you'll only **diff** later, and keep *that* one at full resolution so the pixel diff stays accurate. So: scale down images you actually read; drop unread ones with `includeImageInContext`, don't just shrink them. Exact flags and the current default: argent's help.
48
+
49
+ **agent-device** screenshots default to full resolution — a crisp PNG you read from disk, token-heavier for its size, so match the capture to the question. From **v0.20.6** it gains the same lever argent has and drops the old one: `screenshot --scale <0.01–1>` proportionally resizes both dimensions (`1` = full resolution), with a token-conscious default via `AGENT_DEVICE_SCREENSHOT_SCALE` (or `screenshotScale` in config) that an explicit `--scale` overrides — keep it unset or `1` for pixel-diff baselines; the former `--max-size` is removed (older calls refused with migration guidance). Verify the version with `agent-device --version`. One caveat for remote sessions: the resize runs on the daemon, so a newer client against an older EAS session daemon can have `--scale` silently ignored and get full-res back.
32
50
 
33
51
  **Connecting via MCP (Cursor, Claude Code, Codex, and others).** Install the CLI globally first — the package is `@swmansion/argent`, not `argent`:
34
52
 
@@ -65,3 +83,4 @@ MCP config file location: `.cursor/mcp.json` (Cursor), `.claude/mcp.json` (Claud
65
83
 
66
84
  **Known issues:**
67
85
  - `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.
86
+ - Re-running `argent link` against an already-linked URL **without `--yes`** reports "Already linked. No changes." and keeps the old token — every call then fails with `401 Unauthorized`. Always pass `--yes` (as above) so a rotated token is actually written.
@@ -13,7 +13,10 @@ In all modes, the session is started the same way and driven through `npx --yes
13
13
  printf '# managed by eas-cli\n' > .env.eas-simulator
14
14
 
15
15
  # Start (no --json, so it writes .env.eas-simulator). It boots the sim + agent-device daemon.
16
- npx --yes eas-cli@latest simulator:start --platform ios --type agent-device --non-interactive
16
+ # --name is required practice: it labels the session in simulator:list/get and on expo.dev.
17
+ # Describe what the run is for, in the user's terms — see "Always name the session" in SKILL.md.
18
+ npx --yes eas-cli@latest simulator:start --platform ios --type agent-device --non-interactive \
19
+ --name "Checkout flow screenshots"
17
20
  ```
18
21
 
19
22
  `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`:
@@ -68,7 +71,7 @@ The `install` here **uploads** the (~90MB) `.app` to the remote daemon over the
68
71
 
69
72
  ```bash
70
73
  npx --yes eas-cli@latest build:list --platform ios --profile <your-sim-profile> --status finished --json | \
71
- head -20 # look for a sim build whose fingerprint matches current source
74
+ head -20 # <your-sim-profile> = the profile you find/create in step 1; look for one whose fingerprint matches current source
72
75
  ```
73
76
 
74
77
  If one matches, skip straight to step 3 with its artifact URL.
@@ -138,14 +141,16 @@ LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 xcodebuild \
138
141
  -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build-debug build
139
142
  DEVAPP=ios/build-debug/Build/Products/Debug-iphonesimulator/<App>.app
140
143
 
141
- # 2. Start Metro with tunnel v2 — exactly ONE instance. First check :8081:
142
- # `curl -sf localhost:8081/status` answers AND it's your Metro → reuse it, skip this step.
143
- # Port taken but NOT yours → `PIDS=$(lsof -ti:8081); [ -n "$PIDS" ] && kill $PIDS` BEFORE starting
144
- # (relaunching onto an occupied 8081 is the "port already in use" clash). A bare `&` won't survive across agent
145
- # shell calls — use a long-lived/background run or a separate terminal. tunnel v2 (durable-object,
146
- # not ngrok) works from robot/cloud agents where plain --tunnel is blocked.
147
- EXPO_UNSTABLE_TUNNEL_V2=1 npx expo start --tunnel --port 8081
148
- # → note the deep link exp+<slug>://<host>.on.expo.app; the manifest URL is https://<host>.on.expo.app
144
+ # 2. Start Metro with tunnel v2 on its OWN port — don't force :8081 or kill anything to reclaim it.
145
+ # Each `expo start --tunnel` gets its own unique tunnel URL, so a second Metro never has to fight
146
+ # for the first one's port. Pick a free high port with `--port <N>` (e.g. 8082). Only reuse a
147
+ # running Metro if YOU started it this session — there's no command to prove ownership, so when
148
+ # unsure just start a new one on another port; never kill someone else's server. A bare `&` won't
149
+ # survive across agent shell calls — use a long-lived/background run or a separate terminal. tunnel
150
+ # v2 (durable-object, not ngrok) works from robot/cloud agents where plain --tunnel is blocked.
151
+ EXPO_UNSTABLE_TUNNEL_V2=1 npx expo start --tunnel --port <N>
152
+ # → capture the tunnel/manifest URL + deep link it prints (format like https://<host>.on.expo.app).
153
+ # The port is NOT in the URL, so read it from stdout — `--port` only identifies the local process.
149
154
 
150
155
  # 3. Start a session, install the dev build, then connect it to Metro.
151
156
  # RELIABLE path = "open the dev client, then Enter URL manually". The deep-link + system "Open in
@@ -163,7 +168,7 @@ npx --yes eas-cli@latest simulator:exec npx agent-device@latest open dev.example
163
168
  # b) point it at your remote Metro via "Enter URL manually":
164
169
  npx --yes eas-cli@latest simulator:exec npx agent-device@latest press 'label="Enter URL manually"'
165
170
  npx --yes eas-cli@latest simulator:exec npx agent-device@latest snapshot -i # get the text-field ref
166
- npx --yes eas-cli@latest simulator:exec npx agent-device@latest fill @<field> "https://<host>.on.expo.app"
171
+ npx --yes eas-cli@latest simulator:exec npx agent-device@latest fill @<field> "<manifest URL Metro printed in step 2>" # e.g. https://<host>.on.expo.app
167
172
  npx --yes eas-cli@latest simulator:exec npx agent-device@latest press 'label="Connect"'
168
173
 
169
174
  # c) first-run dev menu → Reload to fetch the bundle (first build+transfer over the tunnel ~40-60s):