zig-mobile-runner 0.1.0

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 (225) hide show
  1. package/CHANGELOG.md +484 -0
  2. package/CONTRIBUTING.md +42 -0
  3. package/FEATURES.md +112 -0
  4. package/LICENSE +21 -0
  5. package/README.md +255 -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 +144 -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 +156 -0
  43. package/docs/app-integration.md +316 -0
  44. package/docs/benchmarking.md +275 -0
  45. package/docs/client-installation.md +141 -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/dsl.md +57 -0
  50. package/docs/install.md +233 -0
  51. package/docs/market-positioning.md +70 -0
  52. package/docs/npm.md +359 -0
  53. package/docs/protocol-fixtures/README.md +8 -0
  54. package/docs/protocol-fixtures/core-session.requests.jsonl +8 -0
  55. package/docs/protocol-fixtures/core-session.responses.jsonl +8 -0
  56. package/docs/protocol-versioning.md +65 -0
  57. package/docs/protocol.md +560 -0
  58. package/docs/publication.md +77 -0
  59. package/docs/release-audit.md +99 -0
  60. package/docs/release-candidate.md +111 -0
  61. package/docs/release-evidence.md +188 -0
  62. package/docs/release-notes-template.md +58 -0
  63. package/docs/roadmap.md +334 -0
  64. package/docs/scenario-authoring.md +88 -0
  65. package/docs/shipping.md +170 -0
  66. package/docs/trace-privacy.md +88 -0
  67. package/docs/troubleshooting.md +256 -0
  68. package/examples/android-app-auth-probe.json +89 -0
  69. package/examples/android-app-error-state.json +13 -0
  70. package/examples/android-app-login-smoke.json +192 -0
  71. package/examples/android-app-onboarding.json +12 -0
  72. package/examples/android-app-referral-deep-link.json +12 -0
  73. package/examples/android-shim-smoke.json +19 -0
  74. package/examples/demo-failure.json +12 -0
  75. package/examples/demo-fake.json +14 -0
  76. package/examples/ios-dev-client-open-link.json +26 -0
  77. package/examples/ios-dev-client-route-snapshot.json +24 -0
  78. package/examples/ios-shim-smoke.json +23 -0
  79. package/examples/ios-smoke.json +9 -0
  80. package/go.work +3 -0
  81. package/npm/agents.mjs +183 -0
  82. package/npm/app-config.mjs +95 -0
  83. package/npm/build-zmr.mjs +21 -0
  84. package/npm/commands.mjs +104 -0
  85. package/npm/generated-files.mjs +50 -0
  86. package/npm/index.mjs +75 -0
  87. package/npm/init-app.mjs +80 -0
  88. package/npm/package-scripts.mjs +72 -0
  89. package/npm/postinstall.mjs +21 -0
  90. package/npm/scaffold.mjs +179 -0
  91. package/npm/scenarios.mjs +93 -0
  92. package/npm/setup.mjs +69 -0
  93. package/npm/wizard.mjs +117 -0
  94. package/npm/zmr.mjs +23 -0
  95. package/package.json +114 -0
  96. package/prebuilds/darwin-arm64/zmr +0 -0
  97. package/prebuilds/darwin-x64/zmr +0 -0
  98. package/prebuilds/linux-arm64/zmr +0 -0
  99. package/prebuilds/linux-x64/zmr +0 -0
  100. package/schemas/README.md +26 -0
  101. package/schemas/action-result.schema.json +27 -0
  102. package/schemas/capabilities-output.schema.json +98 -0
  103. package/schemas/devices-output.schema.json +25 -0
  104. package/schemas/doctor-output.schema.json +51 -0
  105. package/schemas/explain-output.schema.json +51 -0
  106. package/schemas/import-output.schema.json +23 -0
  107. package/schemas/init-output.schema.json +71 -0
  108. package/schemas/json-rpc.schema.json +55 -0
  109. package/schemas/release-manifest.schema.json +43 -0
  110. package/schemas/release-readiness-output.schema.json +127 -0
  111. package/schemas/run-output.schema.json +43 -0
  112. package/schemas/scenario.schema.json +128 -0
  113. package/schemas/schemas-output.schema.json +26 -0
  114. package/schemas/semantic-snapshot.schema.json +116 -0
  115. package/schemas/snapshot.schema.json +60 -0
  116. package/schemas/trace-event.schema.json +14 -0
  117. package/schemas/trace-manifest.schema.json +59 -0
  118. package/schemas/validate-output.schema.json +42 -0
  119. package/schemas/version-output.schema.json +23 -0
  120. package/schemas/zmr-config.schema.json +75 -0
  121. package/scripts/android-emulator.sh +126 -0
  122. package/scripts/assert-ios-physical-ready.sh +213 -0
  123. package/scripts/benchmark-command.sh +307 -0
  124. package/scripts/benchmark.sh +359 -0
  125. package/scripts/benchmark_gate.py +117 -0
  126. package/scripts/benchmark_result_row.py +88 -0
  127. package/scripts/compare-benchmarks.py +288 -0
  128. package/scripts/create-android-demo-app.sh +342 -0
  129. package/scripts/create-ios-demo-app.sh +261 -0
  130. package/scripts/demo-android-real.sh +232 -0
  131. package/scripts/demo-ios-real.sh +270 -0
  132. package/scripts/demo.sh +464 -0
  133. package/scripts/device-matrix.sh +338 -0
  134. package/scripts/ensure-ios-shim-target.rb +237 -0
  135. package/scripts/install-android-shim.sh +281 -0
  136. package/scripts/install-ios-shim.sh +589 -0
  137. package/scripts/pilot-gate.sh +560 -0
  138. package/scripts/release-readiness.py +838 -0
  139. package/scripts/release-readiness.sh +91 -0
  140. package/scripts/run-android-pilot.sh +561 -0
  141. package/scripts/run-ios-pilot.sh +509 -0
  142. package/shims/android/README.md +21 -0
  143. package/shims/android/ZMRShimInstrumentedTest.java +152 -0
  144. package/shims/android/protocol.md +18 -0
  145. package/shims/ios/README.md +50 -0
  146. package/shims/ios/ZMRShim.swift +110 -0
  147. package/shims/ios/ZMRShimUITestCase.swift +475 -0
  148. package/shims/ios/protocol.md +74 -0
  149. package/skills/zmr-mobile-testing/SKILL.md +127 -0
  150. package/src/android.zig +344 -0
  151. package/src/android_device_info.zig +99 -0
  152. package/src/android_emulator.zig +154 -0
  153. package/src/android_screen_recording.zig +112 -0
  154. package/src/android_shell.zig +112 -0
  155. package/src/bundle.zig +124 -0
  156. package/src/bundle_redaction.zig +272 -0
  157. package/src/bundle_tar.zig +123 -0
  158. package/src/cli_devices.zig +97 -0
  159. package/src/cli_doctor.zig +114 -0
  160. package/src/cli_import.zig +70 -0
  161. package/src/cli_info.zig +39 -0
  162. package/src/cli_init.zig +72 -0
  163. package/src/cli_output.zig +467 -0
  164. package/src/cli_run.zig +259 -0
  165. package/src/cli_serve.zig +287 -0
  166. package/src/cli_trace.zig +111 -0
  167. package/src/cli_validate.zig +41 -0
  168. package/src/command.zig +211 -0
  169. package/src/config.zig +305 -0
  170. package/src/config_diagnostics.zig +212 -0
  171. package/src/config_paths.zig +49 -0
  172. package/src/device_registry.zig +37 -0
  173. package/src/doctor.zig +412 -0
  174. package/src/doctor_hints.zig +52 -0
  175. package/src/errors.zig +55 -0
  176. package/src/fake_device.zig +163 -0
  177. package/src/health.zig +28 -0
  178. package/src/importer.zig +343 -0
  179. package/src/importer_json.zig +100 -0
  180. package/src/importer_model.zig +103 -0
  181. package/src/ios.zig +399 -0
  182. package/src/ios_devices.zig +219 -0
  183. package/src/ios_lifecycle.zig +72 -0
  184. package/src/ios_shim.zig +242 -0
  185. package/src/ios_snapshot.zig +20 -0
  186. package/src/json_fields.zig +80 -0
  187. package/src/json_rpc.zig +150 -0
  188. package/src/json_rpc_methods.zig +318 -0
  189. package/src/json_rpc_observation.zig +31 -0
  190. package/src/json_rpc_params.zig +52 -0
  191. package/src/json_rpc_protocol.zig +110 -0
  192. package/src/json_rpc_trace.zig +73 -0
  193. package/src/main.zig +135 -0
  194. package/src/mcp.zig +234 -0
  195. package/src/mcp_protocol.zig +64 -0
  196. package/src/mcp_trace.zig +83 -0
  197. package/src/report.zig +346 -0
  198. package/src/report_html.zig +63 -0
  199. package/src/report_values.zig +27 -0
  200. package/src/run_options.zig +152 -0
  201. package/src/runner.zig +280 -0
  202. package/src/runner_actions.zig +109 -0
  203. package/src/runner_config.zig +6 -0
  204. package/src/runner_diagnostics.zig +268 -0
  205. package/src/runner_events.zig +170 -0
  206. package/src/runner_native.zig +88 -0
  207. package/src/runner_waits.zig +300 -0
  208. package/src/scaffold.zig +472 -0
  209. package/src/scenario.zig +346 -0
  210. package/src/scenario_fields.zig +50 -0
  211. package/src/schema_registry.zig +53 -0
  212. package/src/selector.zig +84 -0
  213. package/src/semantic.zig +171 -0
  214. package/src/trace.zig +315 -0
  215. package/src/trace_json.zig +340 -0
  216. package/src/trace_summary.zig +218 -0
  217. package/src/trace_summary_diagnostic.zig +202 -0
  218. package/src/types.zig +120 -0
  219. package/src/uiautomator.zig +164 -0
  220. package/src/validation.zig +187 -0
  221. package/src/version.zig +22 -0
  222. package/viewer/app.js +373 -0
  223. package/viewer/index.html +126 -0
  224. package/viewer/parser.js +233 -0
  225. package/viewer/styles.css +585 -0
@@ -0,0 +1,275 @@
1
+ # Benchmarking
2
+
3
+ ZMR benchmark output is intentionally simple: each run appends one JSON object to `results.jsonl`, and `zmr report` turns that directory into a local HTML report.
4
+
5
+ ## Single Tool Benchmark
6
+
7
+ ```bash
8
+ scripts/benchmark.sh \
9
+ --zmr examples/android-app-login-smoke.json \
10
+ --device emulator-5554 \
11
+ --runs 10 \
12
+ --trace-root traces/zmr-login \
13
+ --results traces/bench-comparison/results.jsonl \
14
+ --replace \
15
+ --min-pass-rate 100 \
16
+ --max-failures 0 \
17
+ --max-p95-ms 30000
18
+ ```
19
+
20
+ The command writes trace artifacts under `--trace-root` and appends normalized
21
+ rows to `--results`. Omit `--results` to use `<trace-root>/results.jsonl`.
22
+ Omitting `--trace-root` writes under `traces/` in the current app directory,
23
+ not inside the installed ZMR package.
24
+ Use `--replace` when starting a fresh shared comparison file.
25
+ When any gate option is present, `scripts/benchmark_gate.py` reads
26
+ `results.jsonl` and exits non-zero if pass rate, failure count, mean duration,
27
+ or p95 duration misses the configured threshold.
28
+
29
+ Generate a report:
30
+
31
+ ```bash
32
+ zmr report traces/bench-<timestamp> --out traces/bench-<timestamp>/report.html
33
+ ```
34
+
35
+ ## Pilot Wrapper
36
+
37
+ The configurable Android pilot script can run both sample scenarios repeatedly:
38
+
39
+ ```bash
40
+ ./scripts/run-android-pilot.sh \
41
+ --app-root /path/to/mobile-app \
42
+ --device emulator-5554 \
43
+ --runs 20 \
44
+ --min-pass-rate 100 \
45
+ --max-failures 0 \
46
+ --max-p95-ms 30000
47
+ ```
48
+
49
+ For lower variance, restore a clean emulator snapshot at the start of the pilot.
50
+ Use `--screen-record` when investigating visual flakes:
51
+
52
+ ```bash
53
+ ./scripts/run-android-pilot.sh \
54
+ --app-root /path/to/mobile-app \
55
+ --device emulator-5554 \
56
+ --avd Small_Phone \
57
+ --reset-emulator \
58
+ --restore-snapshot zmr-clean \
59
+ --screen-record \
60
+ --runs 20 \
61
+ --min-pass-rate 100 \
62
+ --max-failures 0
63
+ ```
64
+
65
+ For `--runs 1`, the script exports normal and redacted `.zmrtrace` bundles. For `--runs > 1`, it writes benchmark directories and HTML reports.
66
+
67
+ The iOS pilot wrapper supports the same repeated-run gates:
68
+
69
+ ```bash
70
+ ./scripts/run-ios-pilot.sh \
71
+ --app-root /path/to/mobile-app \
72
+ --app-path /path/to/mobile-app/build/Debug-iphonesimulator/Sample.app \
73
+ --device booted \
74
+ --ios-shim /path/to/mobile-app/.zmr/ios-shim \
75
+ --runs 20 \
76
+ --min-pass-rate 100 \
77
+ --max-failures 0 \
78
+ --max-p95-ms 45000
79
+ ```
80
+
81
+ For a paired physical iOS device, pass the target type, a concrete device
82
+ identifier from `zmr devices`, and a signed device artifact:
83
+
84
+ ```bash
85
+ ./scripts/run-ios-pilot.sh \
86
+ --app-root /path/to/mobile-app \
87
+ --app-path /path/to/mobile-app/build/Release-iphoneos/Sample.ipa \
88
+ --ios-device-type physical \
89
+ --device <physical-device-id> \
90
+ --ios-shim /path/to/mobile-app/.zmr/ios-shim \
91
+ --runs 20 \
92
+ --min-pass-rate 100 \
93
+ --max-failures 0 \
94
+ --max-p95-ms 45000
95
+ ```
96
+
97
+ When `--ios-shim` is set, the iOS pilot prewarms the app-local XCTest shim with
98
+ an `appState` command before timing scenarios. That moves cold
99
+ `xcodebuild build-for-testing` work out of the measured run and fails early when
100
+ the UI test target is miswired. Use `--skip-shim-prewarm` only when measuring
101
+ first-command cold-start behavior.
102
+
103
+ For release validation on a machine that has both platform builds and targets
104
+ ready, `zmr-pilot-gate` runs the Android and iOS pilot wrappers with one
105
+ external gate command:
106
+
107
+ ```bash
108
+ zmr-pilot-gate \
109
+ --android --ios \
110
+ --android-app-root /path/to/mobile-app \
111
+ --ios-app-root /path/to/mobile-app --ios-app-path /path/to/mobile-app/build/Debug-iphonesimulator/Sample.app \
112
+ --ios-device-type simulator \
113
+ --ios-shim /path/to/mobile-app/.zmr/ios-shim \
114
+ --runs 20 \
115
+ --min-pass-rate 100 \
116
+ --max-failures 0
117
+ ```
118
+
119
+ ## Reading Results
120
+
121
+ Benchmark reports include:
122
+
123
+ - pass rate
124
+ - failure count
125
+ - mean duration
126
+ - p95 duration
127
+ - per-run status
128
+ - terminal trace status
129
+ - failed step index and error when available
130
+ - links to each run's `events.jsonl`
131
+
132
+ Before making public performance claims, run the same scenario repeatedly on a clean emulator image and include the raw `results.jsonl` plus the redacted trace bundle for any failure.
133
+
134
+ ## Compare Against A Baseline
135
+
136
+ Use `zmr-compare-benchmarks` when a private app repo has benchmark rows from
137
+ ZMR and another local runner. The public ZMR repo keeps this generic: rows are
138
+ grouped by the `tool` field and no external runner is hardcoded.
139
+
140
+ Collect ZMR rows into the shared comparison file first:
141
+
142
+ ```bash
143
+ zmr-benchmark \
144
+ --zmr .zmr/android-smoke.json \
145
+ --platform android \
146
+ --device emulator-5554 \
147
+ --app-id com.example.mobiletest \
148
+ --app-build <build-id-or-artifact> \
149
+ --runs 20 \
150
+ --trace-root traces/zmr-login \
151
+ --results traces/bench-comparison/results.jsonl \
152
+ --replace \
153
+ --min-pass-rate 100 \
154
+ --max-failures 0
155
+ ```
156
+
157
+ Then collect rows from an existing command-line runner by wrapping it with
158
+ `zmr-benchmark-command`. This keeps benchmark collection tool-agnostic while
159
+ still capturing per-run stdout/stderr logs and appending to the same results
160
+ file:
161
+
162
+ ```bash
163
+ zmr-benchmark-command \
164
+ --tool baseline \
165
+ --platform android \
166
+ --device emulator-5554 \
167
+ --app-id com.example.mobiletest \
168
+ --scenario .zmr/android-smoke.json \
169
+ --app-build <build-id-or-artifact> \
170
+ --runs 20 \
171
+ --trace-root traces/baseline-login \
172
+ --results traces/bench-comparison/results.jsonl \
173
+ -- baseline-runner test .baseline/login.yaml
174
+ ```
175
+
176
+ For another runner or command, only change `--tool` and the command after
177
+ `--`:
178
+
179
+ ```bash
180
+ zmr-benchmark-command \
181
+ --tool runner-b \
182
+ --platform ios \
183
+ --device booted \
184
+ --app-id com.example.mobiletest \
185
+ --scenario .zmr/ios-smoke.json \
186
+ --app-build <build-id-or-artifact> \
187
+ --runs 20 \
188
+ --trace-root traces/runner-b-login \
189
+ --results traces/bench-comparison/results.jsonl \
190
+ -- npm run e2e:ios
191
+ ```
192
+
193
+ ```bash
194
+ zmr-compare-benchmarks \
195
+ --results traces/bench-comparison/results.jsonl \
196
+ --candidate zmr \
197
+ --baseline baseline \
198
+ --min-candidate-pass-rate 100 \
199
+ --max-candidate-failures 0 \
200
+ --min-mean-speedup 1.25 \
201
+ --min-p95-speedup 1.25 \
202
+ --format markdown \
203
+ --out traces/bench-comparison/comparison.md \
204
+ --evidence-out traces/bench-comparison/evidence.jsonl
205
+ ```
206
+
207
+ The report includes pass rate, failure count, mean duration, p95 duration, mean
208
+ speedup, p95 speedup, candidate/baseline run counts, and whether the rows have
209
+ the same benchmark context. The optional gates make CI fail when ZMR is not
210
+ reliable enough or not faster than the baseline by the required margin. Only
211
+ compare runs collected on the same host, device state, app build, and scenario.
212
+ `--evidence-out` requires `--min-candidate-pass-rate`,
213
+ `--max-candidate-failures`, `--min-mean-speedup`, and `--min-p95-speedup`, so
214
+ market-claim evidence records explicit reliability and speedup thresholds. When
215
+ `--evidence-out` is set, a successful comparison also requires at least 20 candidate rows,
216
+ at least 20 baseline rows, and matching `platform`, `device`,
217
+ `appId`, `scenario`, and `appBuild` metadata across candidate and baseline
218
+ rows, then appends a `competitive benchmark comparison` row that
219
+ `zmr-release-readiness --target market-claim` can consume directly.
220
+
221
+ ## Device Matrix
222
+
223
+ Use `zmr-device-matrix` when CI needs to run one or more scenarios across
224
+ multiple local emulators, simulators, or attached devices:
225
+
226
+ ```bash
227
+ zmr-device-matrix \
228
+ --matrix .zmr/device-matrix.json \
229
+ --trace-root traces/zmr-matrix \
230
+ --min-pass-rate 100 \
231
+ --max-failures 0
232
+ ```
233
+
234
+ Example matrix:
235
+
236
+ ```json
237
+ {
238
+ "runs": 2,
239
+ "appId": "com.example.mobiletest",
240
+ "devices": [
241
+ {
242
+ "name": "android-api-35",
243
+ "platform": "android",
244
+ "serial": "emulator-5554",
245
+ "scenario": ".zmr/android-smoke.json",
246
+ "adb": "adb",
247
+ "androidShim": ".zmr/android-shim"
248
+ },
249
+ {
250
+ "name": "ios-18",
251
+ "platform": "ios",
252
+ "iosDeviceType": "simulator",
253
+ "serial": "booted",
254
+ "scenario": ".zmr/ios-smoke.json",
255
+ "xcrun": "xcrun",
256
+ "iosShim": ".zmr/ios-shim"
257
+ },
258
+ {
259
+ "name": "ios-physical",
260
+ "platform": "ios",
261
+ "iosDeviceType": "physical",
262
+ "serial": "<physical-device-id>",
263
+ "scenario": ".zmr/ios-smoke.json",
264
+ "xcrun": "xcrun",
265
+ "iosShim": ".zmr/ios-shim"
266
+ }
267
+ ]
268
+ }
269
+ ```
270
+
271
+ The command writes `matrix.jsonl` and `summary.json` under the trace root.
272
+ Each device/run pair has a normal trace directory, so failures can be inspected
273
+ with `zmr explain`, `zmr report`, or the trace viewer.
274
+ For iOS rows, omit `iosDeviceType` for the default simulator path or set it to
275
+ `physical` to pass `--ios-device-type physical` through to `zmr run`.
@@ -0,0 +1,141 @@
1
+ # Client Installation
2
+
3
+ ZMR has two layers:
4
+
5
+ 1. The `zmr` binary controls devices, runs scenarios, serves JSON-RPC, and writes traces.
6
+ 2. Language clients are optional wrappers around `zmr serve --transport stdio`.
7
+
8
+ For fastest adoption, install the binary once with npm, a release tarball, or
9
+ Homebrew. Then use a language client only when you want tests or agents written
10
+ in that language.
11
+
12
+ ## Binary First
13
+
14
+ Today, install the GitHub release tarball:
15
+
16
+ ```bash
17
+ npm install --save-dev https://github.com/johnmikel/zig-mobile-runner/releases/download/v0.1.0/zig-mobile-runner-0.1.0.tgz
18
+ npx zmr version
19
+ ```
20
+
21
+ After the npm registry package is published:
22
+
23
+ ```bash
24
+ npm install --save-dev zig-mobile-runner
25
+ npx zmr-wizard --app-id com.example.mobiletest --package-json
26
+ ```
27
+
28
+ Homebrew is the best install path for non-JavaScript teams because it gives any
29
+ language the same `zmr` executable:
30
+
31
+ ```bash
32
+ # Today, after downloading or building a release archive:
33
+ brew install --build-from-source ./dist/homebrew/zmr.rb
34
+
35
+ # Intended tap install after the tap is published:
36
+ brew tap johnmikel/zmr
37
+ brew install zmr
38
+ ```
39
+
40
+ ## TypeScript
41
+
42
+ ```bash
43
+ npm install --save-dev zig-mobile-runner
44
+ ```
45
+
46
+ ```js
47
+ import { createZmrClient } from "zig-mobile-runner/clients/typescript/index.mjs";
48
+
49
+ const zmr = createZmrClient({
50
+ command: "zmr",
51
+ args: ["serve", "--transport", "stdio", "--config", ".zmr/config.json"],
52
+ });
53
+ ```
54
+
55
+ ## Python
56
+
57
+ ```bash
58
+ python3 -m pip install "git+https://github.com/johnmikel/zig-mobile-runner.git#subdirectory=clients/python"
59
+ ```
60
+
61
+ ```python
62
+ from zmr_client import ZmrClient
63
+
64
+ with ZmrClient("zmr", ["serve", "--transport", "stdio", "--config", ".zmr/config.json"]) as zmr:
65
+ zmr.create_session()
66
+ snapshot = zmr.snapshot()
67
+ ```
68
+
69
+ ## Go
70
+
71
+ ```bash
72
+ go get github.com/johnmikel/zig-mobile-runner/clients/go@main
73
+ ```
74
+
75
+ ```go
76
+ client, err := zmr.Start(ctx, "zmr", "serve", "--transport", "stdio", "--config", ".zmr/config.json")
77
+ ```
78
+
79
+ ## Rust
80
+
81
+ Until the Rust client is published as its own crate, add the repository as a
82
+ vendor checkout or submodule and depend on the client package by path:
83
+
84
+ ```bash
85
+ git submodule add https://github.com/johnmikel/zig-mobile-runner.git vendor/zig-mobile-runner
86
+ ```
87
+
88
+ ```toml
89
+ [dependencies]
90
+ zmr-client = { path = "vendor/zig-mobile-runner/clients/rust" }
91
+ ```
92
+
93
+ ```rust
94
+ let mut client = zmr_client::Client::start("zmr", ["serve", "--transport", "stdio"])?;
95
+ ```
96
+
97
+ ## Swift
98
+
99
+ Until the Swift client is split into a standalone SwiftPM repository or package
100
+ registry entry, add the repository as a vendor checkout or submodule and use a
101
+ local SwiftPM package path:
102
+
103
+ ```bash
104
+ git submodule add https://github.com/johnmikel/zig-mobile-runner.git vendor/zig-mobile-runner
105
+ ```
106
+
107
+ ```swift
108
+ .package(path: "vendor/zig-mobile-runner/clients/swift")
109
+ ```
110
+
111
+ The Swift client is for macOS agent/test tools. It is not embedded in the iOS
112
+ app under test.
113
+
114
+ ## Kotlin
115
+
116
+ Use the Kotlin client as source or build a local jar:
117
+
118
+ ```bash
119
+ git submodule add https://github.com/johnmikel/zig-mobile-runner.git vendor/zig-mobile-runner
120
+ gradle -p vendor/zig-mobile-runner/clients/kotlin build
121
+ ```
122
+
123
+ ```kotlin
124
+ val zmr = ZmrClient(listOf("zmr", "serve", "--transport", "stdio", "--config", ".zmr/config.json"))
125
+ ```
126
+
127
+ The Kotlin client is useful for Android teams that prefer Kotlin for host-side
128
+ test orchestration. It does not replace the Android app shim or run inside the
129
+ app process.
130
+
131
+ ## Which Client Should You Use?
132
+
133
+ - Use only the CLI for committed `.zmr/*.json` scenarios and CI.
134
+ - Use TypeScript or Python for most AI agents because they are easy to generate
135
+ and inspect.
136
+ - Use Go or Rust for long-running infrastructure services.
137
+ - Use Swift or Kotlin when native mobile teams want host-side tooling in their
138
+ everyday language.
139
+
140
+ All clients call the same public JSON-RPC protocol, so feature parity should
141
+ come from the runner, not from language-specific behavior.
@@ -0,0 +1,98 @@
1
+ # Client Guide
2
+
3
+ ZMR clients are reference implementations for the JSON-RPC protocol used by
4
+ `zmr serve`. They are intentionally small and dependency-light.
5
+
6
+ ## What Clients Mean
7
+
8
+ The runner is still the Zig binary. A client starts or connects to:
9
+
10
+ ```bash
11
+ zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent
12
+ ```
13
+
14
+ Then it sends JSON-RPC methods such as:
15
+
16
+ - `runner.capabilities`
17
+ - `session.create`
18
+ - `observe.snapshot`
19
+ - `observe.semanticSnapshot`
20
+ - `ui.tap`
21
+ - `wait.until`
22
+ - `assert.visible`
23
+ - `assert.healthy`
24
+ - `trace.events`
25
+ - `trace.export`
26
+
27
+ Use clients when an AI agent, service, or test harness wants to drive ZMR
28
+ programmatically instead of shelling out for each scenario. For package-manager
29
+ install commands, see [client-installation.md](client-installation.md).
30
+ Prefer the semantic snapshot helper for agent planning; it normalizes native
31
+ Android/iOS classes into roles, selectors, bounds, and recommended actions.
32
+ Use `assert.healthy` after launches, deep links, and high-risk transitions so
33
+ agent-written tests fail on crash overlays and development-client load errors
34
+ even when normal page text is also present.
35
+
36
+ ## Language Layouts
37
+
38
+ | Language | Files | Why it looks this way |
39
+ | --- | --- | --- |
40
+ | TypeScript | `clients/typescript/index.mjs`, `index.d.ts` | ESM runtime plus type declarations, no build step required |
41
+ | Python | `clients/python/zmr_client.py`, `pyproject.toml` | Standard-library importable module that can be vendored or pip-installed from source |
42
+ | Go | `clients/go/zmr/client.go` | Normal Go package inside a module |
43
+ | Rust | `clients/rust/src/lib.rs` | Cargo library crate convention |
44
+ | Swift | `clients/swift/Sources/ZMRClient/ZMRClient.swift` | SwiftPM package for macOS host-side tools |
45
+ | Kotlin | `clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt` | Gradle/Kotlin source package for JVM host-side tools |
46
+
47
+ Rust has `src/lib.rs` because Cargo expects a library crate there. The other
48
+ clients do have equivalent entry points; they are just idiomatic for their
49
+ languages rather than named `lib.rs`. Swift and Kotlin are useful for native
50
+ mobile teams, but they still run on the development machine and drive the
51
+ external `zmr` binary. They are not app-runtime SDKs.
52
+
53
+ ## Quick Starts
54
+
55
+ TypeScript:
56
+
57
+ ```bash
58
+ node clients/typescript/examples/fake-session.mjs
59
+ ```
60
+
61
+ Python:
62
+
63
+ ```bash
64
+ python3 clients/python/examples/fake_session.py
65
+ ```
66
+
67
+ Go:
68
+
69
+ ```bash
70
+ go run ./clients/go/examples/fake-session \
71
+ --zmr ./zig-out/bin/zmr \
72
+ --adb ./tests/fake-adb.sh \
73
+ --trace-dir traces/demo-go-client
74
+ ```
75
+
76
+ Rust:
77
+
78
+ ```bash
79
+ cargo run --manifest-path clients/rust/Cargo.toml --example fake_session -- \
80
+ --zmr ./zig-out/bin/zmr \
81
+ --adb ./tests/fake-adb.sh \
82
+ --trace-dir traces/demo-rust-client
83
+ ```
84
+
85
+ Swift:
86
+
87
+ ```bash
88
+ swift build --package-path clients/swift
89
+ ```
90
+
91
+ Kotlin:
92
+
93
+ ```bash
94
+ gradle -p clients/kotlin build
95
+ ```
96
+
97
+ For real app usage, replace the fake server with `zmr serve --transport stdio`
98
+ and pass `.zmr/config.json`.
package/docs/config.md ADDED
@@ -0,0 +1,175 @@
1
+ # App-Local Config
2
+
3
+ ZMR uses `.zmr/config.json` as the app-local source of truth for default app ids,
4
+ devices, scenario paths, and trace directories.
5
+
6
+ The schema is published at `schemas/zmr-config.schema.json`.
7
+ Runtime parsing follows the schema for primitive field types. For example,
8
+ boolean fields such as `artifacts.screenRecording`, `artifacts.screenshots`,
9
+ `android.resetBeforeRun`, and `android.waitReady` must be JSON booleans, not
10
+ strings. Path, id, redaction-list, and script command string fields must be
11
+ non-empty. `zmr doctor --json --config .zmr/config.json` reports those type/value mistakes as
12
+ structured `config` warnings. Unknown fields are rejected too, so typos in
13
+ app-local config do not silently fall back to defaults.
14
+
15
+ Example:
16
+
17
+ ```json
18
+ {
19
+ "schemaVersion": 1,
20
+ "appId": "com.example.mobiletest",
21
+ "android": {
22
+ "enabled": true,
23
+ "defaultDevice": "emulator-5554",
24
+ "smokeScenario": ".zmr/android-smoke.json",
25
+ "traceDir": "traces/zmr-android",
26
+ "avdName": "Small_Phone",
27
+ "restoreSnapshot": "zmr-clean",
28
+ "createAvdIfMissing": false,
29
+ "avdSystemImage": "system-images;android-35;google_apis;arm64-v8a",
30
+ "avdDeviceProfile": "pixel_6",
31
+ "resetBeforeRun": false,
32
+ "waitReady": true
33
+ },
34
+ "ios": {
35
+ "enabled": true,
36
+ "defaultDevice": "booted",
37
+ "smokeScenario": ".zmr/ios-smoke.json",
38
+ "traceDir": "traces/zmr-ios"
39
+ },
40
+ "artifacts": {
41
+ "screenshots": true,
42
+ "hierarchy": true,
43
+ "logs": true,
44
+ "screenRecording": false
45
+ },
46
+ "redaction": {
47
+ "denylistText": ["customer dob", "internal token"],
48
+ "allowlistText": ["public token label"],
49
+ "denylistResourceIds": ["password-field", "ssn"],
50
+ "allowlistResourceIds": ["public-token-label"]
51
+ },
52
+ "tools": {
53
+ "androidShimPath": "./.zmr/android-shim",
54
+ "iosShimPath": "./.zmr/ios-shim"
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Precedence
60
+
61
+ ZMR auto-discovers `.zmr/config.json` from the current working directory.
62
+ Pass `--config <path>` to load a different file.
63
+
64
+ Relative scenario, trace, and shim paths from config resolve against the app
65
+ root. For the standard `.zmr/config.json` location, the app root is the parent
66
+ directory of `.zmr/`, even when `--config` is an absolute path and ZMR is
67
+ invoked from another checkout. Relative optional tool commands such as
68
+ `tools.adbPath` are resolved the same way when they look like paths; bare
69
+ commands such as `adb`, `xcrun`, or `zig` stay as PATH lookups.
70
+
71
+ Explicit CLI flags always win:
72
+
73
+ - `--app-id` overrides `appId`
74
+ - `--device` overrides platform `defaultDevice`
75
+ - `--trace-dir` overrides platform `traceDir`
76
+ - `--android-avd`, `--create-avd-if-missing`, `--avd-system-image`, `--avd-device`, `--restore-snapshot`, `--reset-emulator`, and `--wait-emulator` override Android emulator lifecycle defaults
77
+ - `--screen-record` and `--no-screen-record` override `artifacts.screenRecording`
78
+ - a positional scenario path overrides platform `smokeScenario`
79
+ - `--adb`, `--emulator`, `--avdmanager`, `--android-shim`, `--xcrun`, `--ios-shim`, and `--zig` override optional tool paths
80
+
81
+ ## Android Emulator Lifecycle
82
+
83
+ The Android platform config can boot and wait for an emulator before a traced
84
+ `zmr run` starts:
85
+
86
+ - `avdName`: AVD name passed to the Android emulator.
87
+ - `createAvdIfMissing`: check `emulator -list-avds` and create the AVD when absent.
88
+ - `avdSystemImage`: installed Android system image package for `avdmanager create avd`.
89
+ - `avdDeviceProfile`: optional device profile for the created AVD.
90
+ - `restoreSnapshot`: optional emulator snapshot name to load at boot.
91
+ - `resetBeforeRun`: best-effort `adb emu kill` before booting the configured AVD.
92
+ - `waitReady`: wait for `adb wait-for-device` and `sys.boot_completed=1`.
93
+
94
+ The equivalent CLI flags are `--android-avd <name>`,
95
+ `--create-avd-if-missing`, `--avd-system-image <package>`,
96
+ `--avd-device <profile>`, `--restore-snapshot <name>`, `--reset-emulator`,
97
+ and `--wait-emulator`. AVD creation, snapshot restore, and reset require an AVD
98
+ name. AVD creation also requires an installed system image package.
99
+
100
+ ## Android Shim
101
+
102
+ Set `tools.androidShimPath` when an app repo has built or installed an Android
103
+ instrumentation shim command. ZMR sends one JSON command to the shim over stdin
104
+ and reads one JSON response from stdout. Existing ADB/UI Automator behavior
105
+ remains the fallback when no shim is configured.
106
+
107
+ CLI `--android-shim <path>` takes precedence over the config value for
108
+ `zmr run`, `zmr serve`, and `zmr doctor`.
109
+
110
+ ## iOS Shim
111
+
112
+ Set `tools.iosShimPath` when an app repo has built or installed an
113
+ XCTest/XCUIAutomation shim command. ZMR sends one JSON command to the shim over
114
+ stdin and reads one JSON response from stdout. The public scenario and JSON-RPC
115
+ interfaces stay unchanged. The generated shim command may cache the XCTest
116
+ `build-for-testing` output and use `test-without-building` internally; ZMR still
117
+ treats it as a simple command transport.
118
+
119
+ With the shim configured, iOS waits and assertions use native XCTest selector
120
+ queries for single-field selectors before falling back to portable snapshot
121
+ matching. `observe.snapshot` uses a bounded XCTest snapshot so large native or
122
+ React Native trees remain fast enough for interactive agent loops.
123
+
124
+ CLI `--ios-shim <path>` takes precedence over the config value for `zmr run`,
125
+ `zmr serve`, and `zmr doctor`.
126
+
127
+ ## Artifact Capture
128
+
129
+ The `artifacts` object controls what raw trace artifacts are persisted during
130
+ `zmr run`.
131
+
132
+ - `screenshots`: write PNG screenshot artifacts.
133
+ - `hierarchy`: write raw Android UI hierarchy XML artifacts.
134
+ - `logs`: include recent device log windows in snapshots.
135
+ - `screenRecording`: capture an Android MP4 for the whole traced `zmr run`.
136
+
137
+ Screenshots, hierarchy, and logs default to `true`; screen recording defaults to
138
+ `false` because it can be large and privacy-sensitive. Set raw artifacts to
139
+ `false` in app repos when traces may contain private visual state,
140
+ accessibility text, or log output. Selector matching can still use the live
141
+ hierarchy even when raw hierarchy persistence is disabled.
142
+
143
+ ## Trace Redaction
144
+
145
+ The `redaction` object adds app-specific rules on top of ZMR's built-in email,
146
+ token, and sensitive-key scrubbing for persisted trace JSON.
147
+
148
+ - `denylistText`: redact trace strings containing any listed text.
149
+ - `allowlistText`: skip app-specific text denylist matches for strings
150
+ containing any listed text. Built-in email/token scrubbing still applies.
151
+ - `denylistResourceIds`: redact matching node `resourceId` values and force the
152
+ node's `text` and `contentDesc` fields to secret placeholders.
153
+ - `allowlistResourceIds`: skip resource-id based redaction for matching nodes.
154
+ Built-in value-level email/token scrubbing still applies.
155
+
156
+ All matches are case-insensitive substrings. These rules apply to local
157
+ persisted snapshot JSON and trace events for both `zmr run` and
158
+ `zmr serve --trace-dir`. They do not edit raw screenshot pixels or raw hierarchy
159
+ XML; disable those artifacts or share `zmr export --redact` bundles when traces
160
+ leave a trusted machine.
161
+
162
+ ## Commands
163
+
164
+ ```bash
165
+ zmr init --app --json --dir . --app-id com.example.mobiletest
166
+ zmr run --config .zmr/config.json
167
+ zmr run .zmr/android-smoke.json --device emulator-5554
168
+ zmr serve --transport stdio --config .zmr/config.json
169
+ zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent
170
+ zmr doctor --strict --json --config .zmr/config.json
171
+ ```
172
+
173
+ For `zmr serve`, the platform `traceDir` from `.zmr/config.json` is used as the
174
+ live JSON-RPC trace directory unless `--trace-dir` overrides it. Keep generated
175
+ trace output under `traces/` and ignore that directory in app repositories.