zeno-mobile-runner 0.1.2

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 (213) hide show
  1. package/CHANGELOG.md +497 -0
  2. package/CONTRIBUTING.md +42 -0
  3. package/FEATURES.md +111 -0
  4. package/LICENSE +21 -0
  5. package/README.md +176 -0
  6. package/SECURITY.md +34 -0
  7. package/build.zig +38 -0
  8. package/build.zig.zon +7 -0
  9. package/clients/README.md +149 -0
  10. package/clients/go/README.md +24 -0
  11. package/clients/go/examples/fake-session/main.go +93 -0
  12. package/clients/go/go.mod +3 -0
  13. package/clients/go/zmr/client.go +432 -0
  14. package/clients/kotlin/README.md +35 -0
  15. package/clients/kotlin/build.gradle.kts +35 -0
  16. package/clients/kotlin/settings.gradle.kts +15 -0
  17. package/clients/kotlin/src/main/kotlin/dev/zmr/FakeSession.kt +86 -0
  18. package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +67 -0
  19. package/clients/python/README.md +29 -0
  20. package/clients/python/examples/fake_session.py +48 -0
  21. package/clients/python/pyproject.toml +13 -0
  22. package/clients/python/zmr_client.py +202 -0
  23. package/clients/rust/Cargo.lock +107 -0
  24. package/clients/rust/Cargo.toml +10 -0
  25. package/clients/rust/README.md +19 -0
  26. package/clients/rust/examples/fake_session.rs +70 -0
  27. package/clients/rust/src/lib.rs +461 -0
  28. package/clients/swift/Package.swift +16 -0
  29. package/clients/swift/README.md +36 -0
  30. package/clients/swift/Sources/ZMRClient/ZMRClient.swift +114 -0
  31. package/clients/swift/Sources/ZMRFakeSession/main.swift +86 -0
  32. package/clients/typescript/README.md +34 -0
  33. package/clients/typescript/examples/fake-session.mjs +36 -0
  34. package/clients/typescript/index.d.ts +144 -0
  35. package/clients/typescript/index.mjs +192 -0
  36. package/clients/typescript/package.json +8 -0
  37. package/docs/adr/0001-agent-native-runner-boundary.md +31 -0
  38. package/docs/adr/0002-app-local-zmr-contract.md +39 -0
  39. package/docs/adr/0003-ios-simulator-xctest-shim.md +41 -0
  40. package/docs/adr/0004-benchmark-claims-and-baseline-collection.md +37 -0
  41. package/docs/adr/README.md +12 -0
  42. package/docs/ai-agents.md +154 -0
  43. package/docs/app-integration.md +330 -0
  44. package/docs/benchmarking.md +273 -0
  45. package/docs/client-installation.md +133 -0
  46. package/docs/clients.md +98 -0
  47. package/docs/config.md +175 -0
  48. package/docs/demo.md +259 -0
  49. package/docs/frameworks.md +72 -0
  50. package/docs/install.md +95 -0
  51. package/docs/npm.md +356 -0
  52. package/docs/protocol-fixtures/README.md +8 -0
  53. package/docs/protocol-fixtures/core-session.requests.jsonl +8 -0
  54. package/docs/protocol-fixtures/core-session.responses.jsonl +8 -0
  55. package/docs/protocol-versioning.md +65 -0
  56. package/docs/protocol.md +560 -0
  57. package/docs/scenario-authoring.md +88 -0
  58. package/docs/trace-privacy.md +88 -0
  59. package/docs/troubleshooting.md +256 -0
  60. package/examples/android-app-auth-probe.json +89 -0
  61. package/examples/android-app-error-state.json +13 -0
  62. package/examples/android-app-login-smoke.json +192 -0
  63. package/examples/android-app-onboarding.json +12 -0
  64. package/examples/android-app-referral-deep-link.json +12 -0
  65. package/examples/android-shim-smoke.json +19 -0
  66. package/examples/demo-failure.json +12 -0
  67. package/examples/demo-fake.json +14 -0
  68. package/examples/ios-dev-client-open-link.json +26 -0
  69. package/examples/ios-dev-client-route-snapshot.json +24 -0
  70. package/examples/ios-shim-smoke.json +23 -0
  71. package/examples/ios-smoke.json +9 -0
  72. package/go.work +3 -0
  73. package/npm/agents.mjs +183 -0
  74. package/npm/app-config.mjs +95 -0
  75. package/npm/build-zmr.mjs +21 -0
  76. package/npm/commands.mjs +104 -0
  77. package/npm/generated-files.mjs +50 -0
  78. package/npm/index.mjs +75 -0
  79. package/npm/init-app.mjs +80 -0
  80. package/npm/package-scripts.mjs +72 -0
  81. package/npm/postinstall.mjs +21 -0
  82. package/npm/scaffold.mjs +179 -0
  83. package/npm/scenarios.mjs +93 -0
  84. package/npm/setup.mjs +69 -0
  85. package/npm/wizard.mjs +117 -0
  86. package/npm/zmr.mjs +23 -0
  87. package/package.json +118 -0
  88. package/schemas/README.md +26 -0
  89. package/schemas/action-result.schema.json +27 -0
  90. package/schemas/capabilities-output.schema.json +98 -0
  91. package/schemas/devices-output.schema.json +25 -0
  92. package/schemas/doctor-output.schema.json +51 -0
  93. package/schemas/explain-output.schema.json +51 -0
  94. package/schemas/import-output.schema.json +23 -0
  95. package/schemas/init-output.schema.json +71 -0
  96. package/schemas/json-rpc.schema.json +55 -0
  97. package/schemas/release-manifest.schema.json +43 -0
  98. package/schemas/release-readiness-output.schema.json +127 -0
  99. package/schemas/run-output.schema.json +43 -0
  100. package/schemas/scenario.schema.json +128 -0
  101. package/schemas/schemas-output.schema.json +26 -0
  102. package/schemas/semantic-snapshot.schema.json +116 -0
  103. package/schemas/snapshot.schema.json +60 -0
  104. package/schemas/trace-event.schema.json +14 -0
  105. package/schemas/trace-manifest.schema.json +59 -0
  106. package/schemas/validate-output.schema.json +42 -0
  107. package/schemas/version-output.schema.json +23 -0
  108. package/schemas/zmr-config.schema.json +75 -0
  109. package/scripts/android-emulator.sh +126 -0
  110. package/scripts/assert-ios-physical-ready.sh +213 -0
  111. package/scripts/benchmark-command.sh +307 -0
  112. package/scripts/benchmark.sh +359 -0
  113. package/scripts/benchmark_gate.py +117 -0
  114. package/scripts/benchmark_result_row.py +88 -0
  115. package/scripts/compare-benchmarks.py +288 -0
  116. package/scripts/create-android-demo-app.sh +342 -0
  117. package/scripts/create-ios-demo-app.sh +261 -0
  118. package/scripts/demo-android-real.sh +232 -0
  119. package/scripts/demo-ios-real.sh +270 -0
  120. package/scripts/demo.sh +464 -0
  121. package/scripts/device-matrix.sh +338 -0
  122. package/scripts/ensure-ios-shim-target.rb +237 -0
  123. package/scripts/install-android-shim.sh +281 -0
  124. package/scripts/install-ios-shim.sh +589 -0
  125. package/scripts/pilot-gate.sh +560 -0
  126. package/scripts/release-readiness.py +838 -0
  127. package/scripts/release-readiness.sh +91 -0
  128. package/scripts/run-android-pilot.sh +561 -0
  129. package/scripts/run-ios-pilot.sh +509 -0
  130. package/shims/android/README.md +21 -0
  131. package/shims/android/ZMRShimInstrumentedTest.java +152 -0
  132. package/shims/android/protocol.md +18 -0
  133. package/shims/ios/README.md +50 -0
  134. package/shims/ios/ZMRShim.swift +110 -0
  135. package/shims/ios/ZMRShimUITestCase.swift +518 -0
  136. package/shims/ios/protocol.md +74 -0
  137. package/skills/zmr-mobile-testing/SKILL.md +127 -0
  138. package/src/android.zig +344 -0
  139. package/src/android_device_info.zig +99 -0
  140. package/src/android_emulator.zig +154 -0
  141. package/src/android_screen_recording.zig +112 -0
  142. package/src/android_shell.zig +112 -0
  143. package/src/bundle.zig +124 -0
  144. package/src/bundle_redaction.zig +272 -0
  145. package/src/bundle_tar.zig +123 -0
  146. package/src/cli_devices.zig +97 -0
  147. package/src/cli_doctor.zig +114 -0
  148. package/src/cli_import.zig +70 -0
  149. package/src/cli_info.zig +39 -0
  150. package/src/cli_init.zig +72 -0
  151. package/src/cli_output.zig +467 -0
  152. package/src/cli_run.zig +259 -0
  153. package/src/cli_serve.zig +287 -0
  154. package/src/cli_trace.zig +111 -0
  155. package/src/cli_validate.zig +41 -0
  156. package/src/command.zig +211 -0
  157. package/src/config.zig +305 -0
  158. package/src/config_diagnostics.zig +212 -0
  159. package/src/config_paths.zig +49 -0
  160. package/src/device_registry.zig +37 -0
  161. package/src/doctor.zig +412 -0
  162. package/src/doctor_hints.zig +52 -0
  163. package/src/errors.zig +55 -0
  164. package/src/fake_device.zig +163 -0
  165. package/src/health.zig +28 -0
  166. package/src/importer.zig +343 -0
  167. package/src/importer_json.zig +100 -0
  168. package/src/importer_model.zig +103 -0
  169. package/src/ios.zig +399 -0
  170. package/src/ios_devices.zig +219 -0
  171. package/src/ios_lifecycle.zig +72 -0
  172. package/src/ios_shim.zig +242 -0
  173. package/src/ios_snapshot.zig +20 -0
  174. package/src/json_fields.zig +80 -0
  175. package/src/json_rpc.zig +150 -0
  176. package/src/json_rpc_methods.zig +318 -0
  177. package/src/json_rpc_observation.zig +31 -0
  178. package/src/json_rpc_params.zig +52 -0
  179. package/src/json_rpc_protocol.zig +110 -0
  180. package/src/json_rpc_trace.zig +73 -0
  181. package/src/main.zig +131 -0
  182. package/src/mcp.zig +234 -0
  183. package/src/mcp_protocol.zig +64 -0
  184. package/src/mcp_trace.zig +83 -0
  185. package/src/report.zig +346 -0
  186. package/src/report_html.zig +63 -0
  187. package/src/report_values.zig +27 -0
  188. package/src/run_options.zig +152 -0
  189. package/src/runner.zig +280 -0
  190. package/src/runner_actions.zig +109 -0
  191. package/src/runner_config.zig +6 -0
  192. package/src/runner_diagnostics.zig +268 -0
  193. package/src/runner_events.zig +170 -0
  194. package/src/runner_native.zig +88 -0
  195. package/src/runner_waits.zig +300 -0
  196. package/src/scaffold.zig +472 -0
  197. package/src/scenario.zig +346 -0
  198. package/src/scenario_fields.zig +50 -0
  199. package/src/schema_registry.zig +53 -0
  200. package/src/selector.zig +84 -0
  201. package/src/semantic.zig +171 -0
  202. package/src/trace.zig +315 -0
  203. package/src/trace_json.zig +340 -0
  204. package/src/trace_summary.zig +218 -0
  205. package/src/trace_summary_diagnostic.zig +202 -0
  206. package/src/types.zig +120 -0
  207. package/src/uiautomator.zig +164 -0
  208. package/src/validation.zig +187 -0
  209. package/src/version.zig +22 -0
  210. package/viewer/app.js +373 -0
  211. package/viewer/index.html +126 -0
  212. package/viewer/parser.js +233 -0
  213. package/viewer/styles.css +585 -0
@@ -0,0 +1,154 @@
1
+ # AI Agent Guide
2
+
3
+ ZMR is built for external agents. The runner provides device state, typed
4
+ actions, waits, assertions, and trace export; the agent decides the next step.
5
+
6
+ ## Agent Setup Loop
7
+
8
+ Start inside the app checkout:
9
+
10
+ ```bash
11
+ zmr doctor --json --config .zmr/config.json
12
+ zmr validate --json .zmr/android-smoke.json
13
+ zmr validate --json .zmr/ios-smoke.json
14
+ zmr schemas --json
15
+ ```
16
+
17
+ Use `zmr doctor --strict --json` in CI or setup flows that should fail on any
18
+ warning. Prefer JSON output for automation because it includes stable error
19
+ codes, field paths, and remediation hints.
20
+
21
+ ## Live JSON-RPC Session
22
+
23
+ Agents should prefer `zmr serve` for interactive work:
24
+
25
+ ```bash
26
+ zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent
27
+ ```
28
+
29
+ Recommended flow:
30
+
31
+ 1. Call `runner.capabilities` and check protocol/platform support.
32
+ 2. Call `session.create`.
33
+ 3. Call `observe.semanticSnapshot` when choosing the next action, or
34
+ `observe.snapshot` when raw adapter details are needed.
35
+ 4. Choose one typed action or assertion.
36
+ 5. Let ZMR settle, then observe again.
37
+ 6. Poll `trace.events` during long runs.
38
+ 7. Call `trace.export` with `redact: true` before sharing artifacts.
39
+ 8. Call `session.close`.
40
+
41
+ Do not parse screenshots or terminal text when the same fact is available from
42
+ snapshot nodes, action results, CLI JSON, or trace events.
43
+
44
+ If `zmr run --json` returns `status: "partial"`, inspect `partialFailure`.
45
+ For iOS visual captures, `artifactStatus: "captured"` with
46
+ `semanticStatus: "failed"` means screenshot proof exists but accessibility or
47
+ XCTest hierarchy extraction failed. Use `zmr explain --json <trace-dir>` for
48
+ the same diagnostic shape after the run.
49
+
50
+ ## MCP Session
51
+
52
+ Agents that support the Model Context Protocol can use ZMR directly as a local
53
+ stdio MCP server:
54
+
55
+ ```bash
56
+ zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent
57
+ ```
58
+
59
+ The MCP server exposes mobile-specific tools:
60
+
61
+ - `snapshot`: raw ZMR observation JSON
62
+ - `semantic_snapshot`: normalized roles, names, selectors, bounds, and
63
+ recommended actions
64
+ - `tap`, `type`, `press_back`, and `open_link`
65
+ - `wait_visible`
66
+ - `trace_events` and `trace_export`
67
+
68
+ Prefer `semantic_snapshot` for action planning. It avoids forcing an agent to
69
+ infer intent from platform-specific Android/UI Automator or XCTest class names.
70
+
71
+ ## Scenario File Workflow
72
+
73
+ For repeatable tests, generate or edit `.zmr/*.json` scenarios:
74
+
75
+ ```bash
76
+ zmr validate --json .zmr/login-smoke.json
77
+ zmr run .zmr/login-smoke.json --json --trace-dir traces/zmr-login-smoke
78
+ zmr explain --json traces/zmr-login-smoke
79
+ zmr export traces/zmr-login-smoke --out traces/zmr-login-smoke-redacted.zmrtrace --redact
80
+ ```
81
+
82
+ Use stable selectors in this order when available:
83
+
84
+ - app accessibility identifiers or resource ids
85
+ - content descriptions or accessibility labels
86
+ - exact visible text for stable product copy
87
+ - `textContains` only when the visible text legitimately varies
88
+ - coordinate actions only as a last resort
89
+
90
+ Use `waitAny` for screens with legitimate branches, and `whenVisible` for
91
+ optional platform or dev-client screens. Keep credentials and app-private data
92
+ in the app repository or environment, not in public scenarios.
93
+
94
+ ## Failure Triage
95
+
96
+ When a run fails, inspect:
97
+
98
+ - `zmr run --json` terminal summary
99
+ - `zmr explain --json <trace-dir>`
100
+ - `trace.json`
101
+ - `events.jsonl`
102
+ - the last snapshot JSON
103
+ - the trace viewer report from `zmr report`
104
+
105
+ Selector failures include active app context, visible text, disabled/hidden or
106
+ offscreen exact candidates, and nearest text matches when available. Treat
107
+ those diagnostics as the source of truth before changing a selector.
108
+
109
+ ## Benchmarking
110
+
111
+ Use ZMR repeated runs first:
112
+
113
+ ```bash
114
+ zmr-benchmark --zmr .zmr/android-smoke.json --platform android --device emulator-5554 --app-id com.example.mobiletest --app-build <build-id-or-artifact> --runs 20 --trace-root traces/zmr-android-reliability --results traces/bench-comparison/results.jsonl --replace --min-pass-rate 100 --max-failures 0
115
+ ```
116
+
117
+ For a fair comparison with an app-local baseline command, collect normalized
118
+ rows and compare them:
119
+
120
+ ```bash
121
+ zmr-benchmark-command --tool baseline --platform android --device emulator-5554 --app-id com.example.mobiletest --scenario .zmr/android-smoke.json --app-build <build-id-or-artifact> --runs 20 --trace-root traces/baseline --results traces/bench-comparison/results.jsonl -- <baseline command>
122
+ zmr-compare-benchmarks --results traces/bench-comparison/results.jsonl --candidate zmr --baseline baseline --min-candidate-pass-rate 100 --max-candidate-failures 0 --min-mean-speedup 1.25 --min-p95-speedup 1.25 --out traces/bench-comparison/comparison.md --evidence-out traces/bench-comparison/evidence.jsonl
123
+ ```
124
+
125
+ Only share benchmark summaries when the candidate and baseline exercise
126
+ equivalent app paths under the same device state. Useful benchmark context
127
+ includes `platform`, `device`, `appId`, `scenario`, and `appBuild`, plus enough
128
+ candidate and baseline rows for your team to trust the result.
129
+
130
+ ## Evidence Summaries
131
+
132
+ Teams that collect repeated app/device pilot rows can evaluate them with:
133
+
134
+ ```bash
135
+ zmr-release-readiness --json \
136
+ --evidence traces/zmr-pilots/evidence.jsonl \
137
+ --target production
138
+ ```
139
+
140
+ Use `satisfied` for proven requirements and `blocked`, `missing`,
141
+ `insufficient`, `failed`, and `planned` for remaining work. Use
142
+ `recommendedWording` for the human-facing status and keep
143
+ `claimLimitations` intact. When blocked, run `nextSteps[].commands` in order
144
+ and use `nextSteps[].covers` to map each command back to the blocked
145
+ requirements it resolves.
146
+
147
+ ## Safety Rules
148
+
149
+ - Run `tests/public-safety-test.sh` before publishing docs, examples, or traces.
150
+ - Do not commit app-private traces, screenshots, credentials, tokens, bundle
151
+ identifiers, or private app names.
152
+ - Prefer `zmr export --redact`; add `--omit-screenshots` for public bundles
153
+ when visual artifacts may contain sensitive data.
154
+ - Keep app-local state under `.zmr/` and generated run output under `traces/`.
@@ -0,0 +1,330 @@
1
+ # App Integration
2
+
3
+ ZMR is intentionally a separate runner. A mobile app repo does not need to vendor ZMR, but it should expose a small, stable test surface so agents can drive the app deterministically.
4
+
5
+ Most app teams should install ZMR as a dev dependency:
6
+
7
+ ```bash
8
+ npm install --save-dev zeno-mobile-runner
9
+ npx zmr-wizard --app-id com.example.mobiletest --package-json
10
+ ```
11
+
12
+ That keeps scenarios and app scripts in the app repo while the runner remains versioned through npm.
13
+ For Expo development builds, add `--expo-dev-client-scheme <scheme>` to scaffold
14
+ Android and iOS open-link smoke scenarios that load Metro before selector
15
+ assertions run.
16
+
17
+ ## React Native, Expo, And Flutter
18
+
19
+ ZMR works best when the app exposes stable, user-meaningful selectors:
20
+
21
+ - React Native apps should use `testID`, `accessibilityLabel`, stable visible
22
+ text, and deep links for direct navigation.
23
+ - Expo development builds can use `--expo-dev-client-scheme <scheme>` so ZMR
24
+ opens the dev client before running selector assertions.
25
+ - Flutter apps should expose important controls through `Semantics` labels,
26
+ stable text, and deep links. ZMR drives Flutter apps at the Android/iOS app
27
+ level; it does not inspect Flutter widget trees.
28
+
29
+ See [frameworks.md](frameworks.md) for framework-specific examples.
30
+
31
+ ## What The App Provides
32
+
33
+ For Android:
34
+
35
+ - A debug/test APK.
36
+ - A stable application id, for example `com.example.mobiletest`.
37
+ - Optional deep links for direct navigation into test states.
38
+ - Accessibility labels, text, or resource ids for important controls.
39
+ - A test server or local dev server when the app requires one.
40
+ - Optional Android instrumentation shim command for faster hierarchy and
41
+ selector-grade actions.
42
+
43
+ Create the app-local Android shim command from the ZMR package or checkout:
44
+
45
+ ```bash
46
+ npx zmr-install-android-shim \
47
+ --app-root . \
48
+ --test-package com.example.mobiletest.test \
49
+ --runner androidx.test.runner.AndroidJUnitRunner \
50
+ --android-module android/app \
51
+ --gradle-file android/app/build.gradle
52
+ ```
53
+
54
+ With `--android-module`, the installer copies the shim into the app module's
55
+ standard `src/androidTest/java/dev/zmr/shim/` tree. With `--gradle-file`, it
56
+ appends guarded Gradle blocks once for `testInstrumentationRunner` and AndroidX
57
+ Test/UI Automator dependencies. If the Gradle file already declares a custom
58
+ `testInstrumentationRunner` and `--runner` is omitted, the generated shim command
59
+ uses the existing runner. Omit those flags when you prefer to wire source and
60
+ dependencies yourself from the generated `.zmr/ZMRShimInstrumentedTest.java`.
61
+ The generated
62
+ `.zmr/android-shim` executable is the value to pass to `--android-shim` or
63
+ `tools.androidShimPath`.
64
+
65
+ For iOS:
66
+
67
+ - A simulator `.app` build.
68
+ - A stable bundle id, for example `com.example.mobiletest`.
69
+ - Optional deep links for direct navigation into test states.
70
+ - Accessibility labels for important controls.
71
+ - Optional simulator XCTest/XCUIAutomation shim command for hierarchy and
72
+ selector-grade actions.
73
+
74
+ Create the app-local shim command from the ZMR package or checkout:
75
+
76
+ ```bash
77
+ npx zmr-install-ios-shim \
78
+ --app-root . \
79
+ --scheme SampleUITests \
80
+ --test-target SampleUITests \
81
+ --workspace ios/Sample.xcworkspace \
82
+ --app-target SampleApp \
83
+ --derived-data-path ios/build/ZMRDerivedData \
84
+ --bundle-id com.example.mobiletest \
85
+ --patch-xcodeproj
86
+ ```
87
+
88
+ Run `.zmr/ensure-ios-shim-target.sh` to create/update the UI test target, add
89
+ the generated `.zmr/ZMRShimUITestCase.swift` and
90
+ `.zmr/shims/ios/ZMRShim.swift` files, configure
91
+ `.zmr/ZMRShimUITests-Info.plist`, and write a shared scheme. The helper uses the
92
+ Ruby `xcodeproj` gem. With `--workspace`, it resolves the referenced
93
+ `.xcodeproj` automatically when there is one project, or when exactly one
94
+ project contains `--app-target`, or when `--bundle-id` disambiguates matching
95
+ app targets. Pass `--project ios/Sample.xcodeproj` explicitly for
96
+ still-ambiguous multi-project workspaces or project-only apps.
97
+
98
+ The generated `.zmr/ios-shim` executable is written into
99
+ `tools.iosShimPath` in `.zmr/config.json`, and can still be passed explicitly
100
+ with `--ios-shim`. It caches `build-for-testing` output and uses
101
+ `test-without-building` for selector commands through `.zmr/ios-shim-state/`.
102
+ Set `ZMR_IOS_SHIM_FORCE_REBUILD=1` after app-side target changes, or
103
+ `ZMR_IOS_SHIM_ONESHOT=1` when you need to debug the slower cold-start path.
104
+
105
+ ## Recommended App Repo Layout
106
+
107
+ The exact layout is app-specific, but this shape works well:
108
+
109
+ ```text
110
+ mobile-app/
111
+ android/app/build/outputs/apk/debug/app-debug.apk
112
+ build/Debug-iphonesimulator/Sample.app
113
+ .zmr/
114
+ config.json
115
+ android-auth-probe.json
116
+ android-login-smoke.json
117
+ ios-smoke.json
118
+ ```
119
+
120
+ Keep app-owned scenarios and ZMR defaults in `.zmr/` when they are app-specific. Keep generic examples in the ZMR repo. ZMR auto-discovers `.zmr/config.json` from the app repo; explicit CLI flags still override config defaults.
121
+
122
+ ## Android App Pilot Command
123
+
124
+ ```bash
125
+ /path/to/zeno-mobile-runner/scripts/run-android-pilot.sh \
126
+ --app-root /path/to/mobile-app \
127
+ --app-id com.example.mobiletest \
128
+ --device emulator-5554
129
+ ```
130
+
131
+ Use a saved emulator snapshot for repeatability:
132
+
133
+ ```bash
134
+ /path/to/zeno-mobile-runner/scripts/run-android-pilot.sh \
135
+ --app-root /path/to/mobile-app \
136
+ --app-id com.example.mobiletest \
137
+ --device emulator-5554 \
138
+ --avd Small_Phone \
139
+ --reset-emulator \
140
+ --restore-snapshot zmr-clean \
141
+ --screen-record
142
+ ```
143
+
144
+ `--screen-record` writes `screenrecord.mp4` under the pilot trace root. For
145
+ direct traced runs, use `zmr run --android-avd Small_Phone
146
+ --create-avd-if-missing --avd-system-image
147
+ 'system-images;android-35;google_apis;arm64-v8a' --avd-device pixel_6
148
+ --restore-snapshot zmr-clean --wait-emulator --screen-record`, or set the
149
+ equivalent `android.avdName`, `android.createAvdIfMissing`,
150
+ `android.avdSystemImage`, `android.avdDeviceProfile`,
151
+ `android.restoreSnapshot`, `android.waitReady`, and
152
+ `artifacts.screenRecording` values in `.zmr/config.json`. Treat recordings like
153
+ screenshots: keep them local or share only when the app state is safe.
154
+
155
+ The Android wrapper expects the default APK path under the app root. Override it when needed:
156
+
157
+ ```bash
158
+ /path/to/zeno-mobile-runner/scripts/run-android-pilot.sh \
159
+ --app-root /path/to/mobile-app \
160
+ --apk /path/to/app-debug.apk \
161
+ --device emulator-5554
162
+ ```
163
+
164
+ ## Public Android Demo Command
165
+
166
+ For a generic public Android app:
167
+
168
+ ```bash
169
+ npx zmr-demo-android --out /tmp/zmr-android-demo --device emulator-5554 --avd <avd-name>
170
+ ```
171
+
172
+ This writes a signed debug APK plus `.zmr/android-smoke.json` without Gradle or
173
+ network access, then installs and runs it on the requested Android target. For
174
+ manual inspection or customization:
175
+
176
+ ```bash
177
+ npx zmr-create-android-demo-app --out /tmp/zmr-android-demo
178
+ adb install -r /tmp/zmr-android-demo/build/app-debug.apk
179
+ /path/to/zeno-mobile-runner/zig-out/bin/zmr run /tmp/zmr-android-demo/.zmr/android-smoke.json \
180
+ --device emulator-5554 \
181
+ --app-id com.example.mobiletest \
182
+ --trace-dir /tmp/zmr-android-demo/traces/android-demo
183
+ ```
184
+
185
+ Use this path to prove local Android install, launch, selector action, typing,
186
+ snapshot, and trace capture before wiring ZMR into a private app.
187
+
188
+ ## iOS Demo Command
189
+
190
+ For a generic public demo app with the shim already installed:
191
+
192
+ ```bash
193
+ npx zmr-demo-ios --out /tmp/zmr-ios-demo --device booted
194
+ ```
195
+
196
+ That command creates the demo app, boots an available simulator when needed,
197
+ builds it, runs the iOS pilot, and writes redacted trace bundles. To inspect or
198
+ customize the generated app before running the pilot manually:
199
+
200
+ ```bash
201
+ npx zmr-create-ios-demo-app --out /tmp/zmr-ios-demo
202
+ cd /tmp/zmr-ios-demo
203
+ xcodebuild -project ios/ZMRDemo.xcodeproj -scheme ZMRDemo -destination 'generic/platform=iOS Simulator' -derivedDataPath DerivedData build
204
+ ```
205
+
206
+ Then boot a simulator and run:
207
+
208
+ ```bash
209
+ /path/to/zeno-mobile-runner/scripts/run-ios-pilot.sh \
210
+ --app-root /tmp/zmr-ios-demo \
211
+ --app-path /tmp/zmr-ios-demo/DerivedData/Build/Products/Debug-iphonesimulator/ZMRDemo.app \
212
+ --app-id com.example.mobiletest \
213
+ --device booted \
214
+ --ios-shim /tmp/zmr-ios-demo/.zmr/ios-shim
215
+ ```
216
+
217
+ Build the app for an iOS simulator, boot a simulator, then run:
218
+
219
+ ```bash
220
+ /path/to/zeno-mobile-runner/scripts/run-ios-pilot.sh \
221
+ --app-root /path/to/mobile-app \
222
+ --app-path /path/to/mobile-app/build/Debug-iphonesimulator/Sample.app \
223
+ --app-id com.example.mobiletest \
224
+ --device booted \
225
+ --ios-shim /path/to/mobile-app/.zmr/ios-shim
226
+ ```
227
+
228
+ Without `--ios-shim`, the iOS path is a smoke demo: install, launch/open-link,
229
+ screenshot, logs, trace, report, and redacted export. With `--ios-shim`, ZMR
230
+ also runs `examples/ios-shim-smoke.json`, producing a second report and
231
+ redacted bundle for selector-grade native waits, tap, type, and bounded
232
+ snapshot actions. If a selector wait times out, ZMR records a final XCTest
233
+ snapshot when possible so reports and agents can see the active app context,
234
+ visible labels, hidden/disabled/offscreen candidates, and nearest text matches.
235
+ When the app is already running, ZMR uses the shim `appState` response as an
236
+ idempotent launch confirmation if `simctl launch` itself returns an error.
237
+
238
+ On iOS simulators, `clearState` means best-effort app uninstall by bundle id.
239
+ For physical iOS devices, lifecycle commands go through `devicectl` and
240
+ selector commands go through the same app-local XCTest shim, subject to signing,
241
+ provisioning, Developer Mode, and local Xcode availability. Screenshot
242
+ artifacts use the XCTest shim; log artifact capture is simulator-first in this
243
+ release.
244
+ Use a simulator-built `iphonesimulator` `.app` for simulator runs. A signed
245
+ device `.ipa` must be run with `--ios-device-type physical`; the pilot wrapper
246
+ rejects device IPAs on simulator runs before installing anything.
247
+ Use `--ios-device-type physical` with a concrete device identifier from
248
+ `zmr devices` for physical pilot runs:
249
+
250
+ ```bash
251
+ /path/to/zeno-mobile-runner/scripts/run-ios-pilot.sh \
252
+ --app-root /path/to/mobile-app \
253
+ --app-path /path/to/mobile-app/build/Release-iphoneos/Sample.ipa \
254
+ --ios-device-type physical \
255
+ --device <physical-device-id> \
256
+ --ios-shim /path/to/mobile-app/.zmr/ios-shim \
257
+ --runs 20 \
258
+ --min-pass-rate 100 \
259
+ --max-failures 0
260
+ ```
261
+
262
+ If the app is already missing, ZMR treats the simulator as clean and continues.
263
+ Install the simulator `.app` again before launch/open-link steps that need it.
264
+
265
+ ## Direct CLI Use
266
+
267
+ Android:
268
+
269
+ ```bash
270
+ zmr run .zmr/android-auth-probe.json \
271
+ --device emulator-5554 \
272
+ --app-id com.example.mobiletest \
273
+ --android-shim ./.zmr/android-shim \
274
+ --trace-dir traces/android-auth
275
+ ```
276
+
277
+ Or use app-local defaults:
278
+
279
+ ```bash
280
+ zmr run --config .zmr/config.json
281
+ ```
282
+
283
+ iOS:
284
+
285
+ ```bash
286
+ xcrun simctl install booted /path/to/Sample.app
287
+ zmr run .zmr/ios-shim-smoke.json \
288
+ --platform ios \
289
+ --device booted \
290
+ --app-id com.example.mobiletest \
291
+ --ios-shim ./.zmr/ios-shim \
292
+ --trace-dir traces/ios-smoke
293
+ ```
294
+
295
+ ## Agent JSON-RPC Use
296
+
297
+ Start a local server next to the device. App repos scaffolded by
298
+ `zmr-wizard --package-json` can use the generated scripts:
299
+
300
+ ```bash
301
+ npm run zmr:serve
302
+ npm run zmr:mcp
303
+ ```
304
+
305
+ External agents can call:
306
+
307
+ - `runner.capabilities`
308
+ - `session.create`
309
+ - `app.launch`
310
+ - `app.openLink`
311
+ - `observe.snapshot`
312
+ - `observe.semanticSnapshot`
313
+ - `ui.tap`
314
+ - `wait.until`
315
+ - `assert.visible`
316
+ - `trace.export`
317
+
318
+ Use `observe.semanticSnapshot` before choosing actions, and use
319
+ `observe.snapshot` when raw adapter details are needed. Every action should
320
+ settle and observe again. Scenario runs call the adapter-level settle hook after
321
+ mutating actions; native shims can wait for platform idle while shell-only paths
322
+ keep a bounded sleep fallback. Start `serve` with `--trace-dir` so
323
+ `trace.export` can produce a redacted `.zmrtrace` bundle for the whole agent
324
+ session.
325
+
326
+ ## Public Artifact Rules
327
+
328
+ - Share `*-redacted.zmrtrace` bundles.
329
+ - Do not publish raw Metro logs, simulator logs, or unredacted screenshot bundles from private apps.
330
+ - Run `bash tests/public-safety-test.sh` before publishing this repo.