adb-ready 0.5.1 → 0.7.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.
@@ -0,0 +1,151 @@
1
+ # Target pools and fan-out
2
+
3
+ Use a target pool when the same bounded verification must run against an
4
+ explicit set of Android targets. Pool mode is opt-in. A normal `adb-ready run`
5
+ still selects exactly one target.
6
+
7
+ Every acquired member receives its own single-target session, target lease,
8
+ verifier result, cleanup, and evidence bundle. The pool result aggregates those
9
+ unchanged member envelopes in declaration order.
10
+
11
+ ## Declare a local pool
12
+
13
+ ```json
14
+ {
15
+ "$schema": "./node_modules/adb-ready/schema/config-v1.schema.json",
16
+ "version": 1,
17
+ "targets": {
18
+ "pools": {
19
+ "smoke": {
20
+ "maxConcurrency": 2,
21
+ "failFast": false,
22
+ "leaseWaitMs": 30000,
23
+ "members": [
24
+ { "id": "desk", "kind": "adb", "serial": "R3CT..." },
25
+ { "id": "api36", "kind": "avd", "name": "Pixel_9_API_36" },
26
+ {
27
+ "id": "lab",
28
+ "kind": "remote-adb",
29
+ "host": "android-lab.internal",
30
+ "port": 5037,
31
+ "serial": "10.20.0.15:5555"
32
+ }
33
+ ]
34
+ }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ Run the same verifier on every member:
41
+
42
+ ```bash
43
+ adb-ready run --pool smoke -- npm run test:e2e
44
+ ```
45
+
46
+ Preview every member without inspecting ADB, starting an AVD, or running the
47
+ verifier:
48
+
49
+ ```bash
50
+ adb-ready run --pool smoke --dry-run --json -- npm run test:e2e
51
+ ```
52
+
53
+ The pool owns concurrency policy; it never infers an unbounded worker count.
54
+ Override it for one invocation only when the CI runner has known capacity:
55
+
56
+ ```bash
57
+ adb-ready run --pool smoke --max-concurrency 1 --fail-fast \
58
+ --lease-wait 45s -- npm run test:e2e
59
+ ```
60
+
61
+ ## Member kinds
62
+
63
+ | Kind | Required fields | Ownership |
64
+ | --- | --- | --- |
65
+ | `adb` | `id`, exact `serial` | Uses an already ADB-visible target. |
66
+ | `avd` | `id`, exact existing AVD `name` | Reuses or starts that AVD; stops only an emulator process started by this run. |
67
+ | `remote-adb` | `id`, `host`, exact `serial`; optional `port` | Uses the explicit ADB server without changing the global local server. |
68
+ | `firebase` | `id`, `model`, `version`; optional `locale`, `orientation` | Firebase owns the remote target and lifecycle. Use only with `test firebase`. |
69
+
70
+ Member IDs and resource identities must be unique inside a pool. Members are
71
+ required by default. Set `"required": false` only when that dimension is truly
72
+ informational; an optional failure remains visible but does not fail the
73
+ aggregate.
74
+
75
+ ## Firebase dimensions
76
+
77
+ A Firebase-only pool submits one independently observable matrix per declared
78
+ dimension. This gives every dimension a stable member result and lets ADB Ready
79
+ bound submission concurrency.
80
+
81
+ ```json
82
+ {
83
+ "version": 1,
84
+ "targets": {
85
+ "pools": {
86
+ "firebase-smoke": {
87
+ "maxConcurrency": 2,
88
+ "failFast": true,
89
+ "members": [
90
+ { "id": "pixel-api35", "kind": "firebase", "model": "akita", "version": "35" },
91
+ { "id": "tablet-api34", "kind": "firebase", "model": "tangorpro", "version": "34" }
92
+ ]
93
+ }
94
+ }
95
+ }
96
+ }
97
+ ```
98
+
99
+ ```bash
100
+ adb-ready test firebase instrumentation \
101
+ --pool firebase-smoke \
102
+ --project my-project \
103
+ --app app-debug.apk \
104
+ --test-apk app-debug-androidTest.apk
105
+ ```
106
+
107
+ Dimensions are checked against the live Firebase catalog before upload. With
108
+ `failFast`, ADB Ready stops submitting queued dimensions after a required
109
+ failure. It does not cancel matrices already running remotely. Use
110
+ `adb-ready test firebase cancel` for an intentional provider cancellation.
111
+
112
+ ## Queueing and leases
113
+
114
+ ADB Ready serializes contending work with an owner-recorded, heartbeating lease.
115
+ `leaseWaitMs` bounds how long a pool member waits; cancellation exits the queue
116
+ without taking ownership. Expired leases and leases owned by a dead process on
117
+ the same host are recovered conservatively.
118
+
119
+ The built-in coordination backend is a per-user filesystem on one host. It is
120
+ not a distributed lock. CI jobs on different machines need runner-level
121
+ routing or another explicit external coordination system. ADB Ready never
122
+ infers cross-host ownership.
123
+
124
+ An explicit remote ADB server is also a trust boundary: its socket is not
125
+ encrypted by ADB Ready. Protect it with a trusted private network, VPN, SSH
126
+ tunnel, or equivalent transport; never expose port 5037 to an untrusted
127
+ network.
128
+
129
+ ## Aggregate result
130
+
131
+ Human output shows the status of every member. JSON and NDJSON preserve every
132
+ nested result envelope, including its command ID, structured problems,
133
+ timestamps, and evidence path. The aggregate cannot report success when any
134
+ required member failed, disappeared, was cancelled, or never started.
135
+
136
+ Useful automation fields:
137
+
138
+ ```text
139
+ data.pool
140
+ data.maxConcurrency
141
+ data.failFast
142
+ data.coordination
143
+ data.members[].id
144
+ data.members[].status
145
+ data.members[].exitCode
146
+ data.members[].result
147
+ data.summary
148
+ ```
149
+
150
+ Pool configuration is declarative data. It cannot add executable hooks or
151
+ shell fragments, and every verifier continues to use direct argv execution.
@@ -96,6 +96,21 @@ This is useful for WSL, containers, VMs, SSH-forwarded workstations, and device
96
96
  labs. ADB Ready does not start, kill, or reconfigure that shared server
97
97
  implicitly.
98
98
 
99
+ Before every command sent through an explicit `--adb-host` or `--adb-port`, ADB
100
+ Ready opens a bounded read-only connection and requests only `host:version`.
101
+ The real `adb` process starts only when the server protocol matches the local
102
+ client. This matters because the upstream ADB client automatically sends
103
+ `host:kill` when it encounters a mismatched server version (see the
104
+ [AOSP ADB client implementation](https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/client/adb_client.cpp)).
105
+ ADB Ready instead returns `ADB_REMOTE_SERVER_PROTOCOL_MISMATCH` and leaves the
106
+ shared server untouched.
107
+
108
+ The preflight also distinguishes an unreachable route, timeout, cancellation,
109
+ and a non-ADB response. It is repeated before each command so replacement of a
110
+ server during a long session fails closed. This is a narrow safety handshake,
111
+ not a replacement ADB transport; all device operations still use the selected
112
+ `adb` executable.
113
+
99
114
  ## Network limitations
100
115
 
101
116
  Wireless discovery and connection can be blocked by guest Wi-Fi, client
@@ -110,6 +125,12 @@ adb-ready doctor --verbose
110
125
  adb-ready devices
111
126
  ```
112
127
 
128
+ For WSL, a container, VM, or remote runner, first verify that its network
129
+ namespace can reach the exact protected ADB host and port. A successful DNS
130
+ lookup or mDNS discovery does not prove that the TCP route is usable. ADB Ready
131
+ reports this as `ADB_REMOTE_SERVER_UNREACHABLE` rather than an empty target
132
+ list.
133
+
113
134
  If discovery is unavailable but the device shows a reachable connection
114
135
  endpoint, pass it explicitly. Do not assume port `5555`; modern Wireless
115
136
  debugging ports are dynamic.
@@ -39,6 +39,12 @@ selection fails. An MCP connection binds one target and refuses a silent
39
39
  switch. UI references include the current hierarchy digest and are revalidated
40
40
  immediately before mutation.
41
41
 
42
+ MCP profiles narrow discovery for a workflow and reduce accidental tool
43
+ selection, but they are not an authorization boundary. Every exposed operation
44
+ still applies its own target, path, input, ownership, and destructive-action
45
+ checks. Tool annotations and namespaced sensitivity metadata help clients choose
46
+ approval policy; they are hints, not security enforcement.
47
+
42
48
  ### Unsafe file mutation
43
49
 
44
50
  Capture and setup paths are constrained to the project, checked for unsafe
@@ -52,6 +58,20 @@ redacted. Pairing codes use protected input or stdin and never enter process
52
58
  arguments. Screenshots and UI hierarchies require explicit calls and never join
53
59
  AI context automatically.
54
60
 
61
+ Screenshot capture rejects malformed PNG structure, invalid checksums,
62
+ unsupported encodings, and excessive source dimensions before publishing a
63
+ file. MCP image results are cropped or proportionally bounded by default and
64
+ must fit an encoded payload budget; full-resolution delivery is an explicit
65
+ client choice. These controls bound transport and context size, not the visual
66
+ sensitivity of the selected pixels.
67
+
68
+ Failure inspection accepts only a verified installed package, binds every
69
+ probe to the same target, uses package-scoped exit history and log filtering,
70
+ and retains DropBox blocks only when an exact package or package-process line
71
+ matches. Unavailable or permission-limited sources are reported rather than
72
+ replaced by target-wide evidence. Source bytes, time windows, record counts,
73
+ and returned excerpts are bounded and redacted.
74
+
55
75
  ### Unbounded or misleading automation
56
76
 
57
77
  Process output, recordings, UI trees, session storage, context, retries, and
@@ -59,6 +79,12 @@ waits are bounded. Results distinguish process acceptance (`ok`) from observed
59
79
  postconditions (`verified`). An unchanged UI is reported as a verification gap,
60
80
  not silently upgraded to verified success.
61
81
 
82
+ Keyboard dismissal requires two independent Android visibility signals to
83
+ agree before Back is sent and after it completes. Runtime-permission responses
84
+ require one recognized PermissionController dialog and one exact resource ID;
85
+ localized labels, generic system dialogs, biometric prompts, notification
86
+ setup, and ambiguous OEM states are never accepted speculatively.
87
+
62
88
  ### Network exposure
63
89
 
64
90
  The 0.2 MCP server uses stdio and opens no listener. ADB itself may connect to a
@@ -73,6 +99,13 @@ ADB Ready's isolation boundary.
73
99
  - autonomous destructive recovery;
74
100
  - automatic screenshot or UI-text upload.
75
101
 
102
+ Protected UI text is accepted from CLI stdin or by environment-variable name
103
+ through MCP. It is not placed in host argv, result envelopes, plans, journals,
104
+ screenshots created by the action, or AI context. It still exists transiently
105
+ in ADB Ready memory and the child-process pipe, and the selected Android target,
106
+ input method, app, or OEM auditing may observe it. ADB Ready does not claim to
107
+ turn a normal visible field or an untrusted target into a secure channel.
108
+
76
109
  ## Residual risk
77
110
 
78
111
  A trusted ADB server can control connected Android targets, and an approved AI
@@ -21,6 +21,9 @@ adb-ready context --since 5m --only problems,recovery,logs
21
21
  | `ADB_NOT_FOUND` | No usable ADB executable was resolved | Install Platform-Tools or pass `--adb PATH` |
22
22
  | `ADB_SERVER_UNAVAILABLE` | The selected local or remote server could not be queried | Check the configured host/port and run `adb start-server` explicitly if appropriate |
23
23
  | `ADB_VERSION_MISMATCH` | Client and server report incompatible versions | Align Platform-Tools versions; review before restarting the shared server |
24
+ | `ADB_REMOTE_SERVER_UNREACHABLE` | The explicit server socket has no usable route | Check WSL/container/VM/VPN/firewall/tunnel routing; do not expose port 5037 publicly |
25
+ | `ADB_REMOTE_SERVER_PROTOCOL_MISMATCH` | Remote and local ADB protocols differ | Align Platform-Tools deliberately; ADB Ready will not let upstream ADB kill the shared server |
26
+ | `ADB_REMOTE_SERVER_INVALID_RESPONSE` | The endpoint or local client could not complete the read-only protocol preflight | Verify the host, port, tunnel destination, and local Platform-Tools installation |
24
27
  | `TARGET_UNAUTHORIZED` | Android has not authorized this host | Unlock the device and accept its RSA debugging prompt |
25
28
  | `TARGET_NO_PERMISSIONS` | The host cannot access the USB device | Fix host USB permissions or rules, then reconnect |
26
29
  | `TARGET_OFFLINE` | ADB knows the transport but it is not ready | Check cable/network state and retry `devices` |
@@ -34,6 +37,11 @@ adb-ready context --since 5m --only problems,recovery,logs
34
37
  | `UI_HIERARCHY_BUSY` | Another process held the selected target's single UI Automation service through the UI timeout | Let that capture finish or increase the UI timeout, then retry |
35
38
  | `UI_HIERARCHY_LOCK_UNAVAILABLE` | The per-user coordination directory is not writable | Restore write access to the ADB Ready state directory, then retry |
36
39
  | `UI_NOT_IDLE` | Android UI Automator could not observe a quiet accessibility window | Pause continuous UI changes or navigate to a stable screen, then retry |
40
+ | `UI_UNICODE_INPUT_UNAVAILABLE` | Unicode/multiline input was requested but ADBKeyBoard is not installed and enabled | Install a compatible official ADBKeyBoard release, enable it in Android settings, and retry; ADB Ready will not enable an IME silently |
41
+ | `UI_INPUT_METHOD_UNKNOWN` | Android did not expose the current IME, so it cannot be restored safely | Select a keyboard on the target before retrying Unicode input |
42
+ | `UI_KEYBOARD_STATE_AMBIGUOUS` | InputMethodManager and WindowInsets disagree about keyboard visibility | Wait for the transition to settle, then inspect again; ADB Ready will not press Back while state is uncertain |
43
+ | `UI_KEYBOARD_STATE_UNSUPPORTED` | The Android build omitted one of the two required keyboard signals | Dismiss the keyboard in the app or use a target that exposes both dumpsys states |
44
+ | `UI_PERMISSION_DIALOG_UNSUPPORTED` | The current PermissionController or OEM dialog is not one exact supported runtime-permission prompt | Handle the dialog manually; notification, biometric, Settings, and unknown OEM dialogs are not auto-accepted |
37
45
  | `SESSION_RECOVERY_FAILED` | The bounded target/port recovery budget was exhausted | Inspect `problems`, network state, and saved recovery events |
38
46
  | `SESSION_PERSISTENCE_FAILED` | The private session record could not be written | Check user-state directory permissions and capacity |
39
47
  | `FRAMEWORK_LAUNCHER_NOT_FOUND` | The detected Flutter or Gradle project has no runnable launcher | Install Flutter and expose `flutter` on PATH, or restore the project's checked-in Gradle Wrapper |
@@ -135,6 +143,37 @@ Resolve the user-action problem, then start a new session. Increase the recovery
135
143
  budget only when the environment is known to need more time; do not use an
136
144
  unbounded timeout.
137
145
 
146
+ ## Screenshot capture is rejected
147
+
148
+ `SCREENSHOT_BOUNDS_INVALID` means the requested `--crop X,Y,WIDTH,HEIGHT` lies
149
+ outside the decoded source image. Capture once without a crop and read the
150
+ reported `image.source` dimensions, then retry with coordinates inside them.
151
+
152
+ `SCREENSHOT_PAYLOAD_TOO_LARGE` protects an MCP client from an unexpectedly
153
+ large image. Crop the relevant region or lower `maxWidth`/`maxHeight`; use
154
+ `fullResolution: true` only when the client can deliberately accept it.
155
+
156
+ `SCREENSHOT_INVALID` or `SCREENSHOT_ENCODING_UNSUPPORTED` means ADB did not
157
+ produce a complete standard non-interlaced 8-bit PNG. Confirm that the selected
158
+ display is available and not blocked by a secure surface, then retry without
159
+ assuming that a PNG signature alone proves usable evidence.
160
+
161
+ ## Failure inspection has unavailable sources
162
+
163
+ `adb-ready inspect failures APP_ID` succeeds when the app and target are
164
+ verified even if an optional Android evidence source is absent. Read the
165
+ `sources` list: each entry distinguishes available, truncated,
166
+ permission-limited, and unsupported evidence.
167
+ The top-level `truncated` flag means the requested incident limit bounded the
168
+ combined result; a source-level flag means Android output or log retention was
169
+ bounded before correlation.
170
+
171
+ Application exit history requires Android 11/API 30 or a compatible OEM
172
+ backport. DropBox visibility varies by Android build and shell permissions.
173
+ ADB Ready does not substitute unrelated target-wide crashes. Use a recent
174
+ `--since` window, reproduce the failure, and retry; use focused `adb-ready logs`
175
+ when the app is still running and live output is required.
176
+
138
177
  ## Report a reproducible issue
139
178
 
140
179
  For ordinary bugs, open a GitHub issue with the ADB Ready version, host/runtime,
@@ -30,6 +30,33 @@ parallel, and selector processing or input does not hold the capture lock. If
30
30
  another process does not finish within the configured UI timeout, the command
31
31
  returns `UI_HIERARCHY_BUSY` instead of misreporting an inaccessible screen.
32
32
 
33
+ Choose an acquisition profile when the default one-shot observation is not the
34
+ right tradeoff:
35
+
36
+ ```bash
37
+ adb-ready inspect ui --acquisition balanced
38
+ adb-ready inspect ui --acquisition fast --interactive-only
39
+ adb-ready inspect ui --acquisition strict
40
+ ```
41
+
42
+ - `balanced` takes one fresh platform-idle snapshot within the normal bounded
43
+ UI timeout and is the default.
44
+ - `fast` keeps the same safe target-scoped platform backend but limits the
45
+ attempt to three seconds. It reports a timeout or non-idle screen instead of
46
+ pretending that an empty result is a valid screen.
47
+ - `strict` requires two consecutive matching hierarchy digests within at most
48
+ three captures. A continuously changing screen returns
49
+ `UI_HIERARCHY_UNSTABLE`.
50
+
51
+ Every successful structured snapshot reports its observation time, duration,
52
+ attempt count, stability status, source and idle strategy, requested filters,
53
+ node limits, truncation, observed display bounds and rotation, and detected
54
+ semantic limitations. An empty hierarchy is an explicit `UI_HIERARCHY_EMPTY`
55
+ failure. WebView, Compose, and Flutter
56
+ markers are reported only when their corresponding platform view is actually
57
+ observed; they describe framework accessibility boundaries rather than guessing
58
+ why an otherwise unreadable window failed.
59
+
33
60
  ## Audit one screen for people and agents
34
61
 
35
62
  ```bash
@@ -92,6 +119,7 @@ field happens to be focused:
92
119
  adb-ready ui get 'id=com.example:id/email' --json
93
120
  adb-ready ui fill 'id=com.example:id/email' 'person@example.com'
94
121
  adb-ready ui fill 'id=com.example:id/search' 'pixel' --submit
122
+ adb-ready ui fill 'id=com.example:id/name' 'Příliš žluťoučký 🦊' --input-mode unicode
95
123
  adb-ready ui clear 'id=com.example:id/search'
96
124
  ```
97
125
 
@@ -128,9 +156,84 @@ node whose `scrollable` property is true; without a selector it uses the screen.
128
156
  Supported keys are `back`, `home`, `enter`, `menu`, `volume-up`, and
129
157
  `volume-down`.
130
158
 
131
- Android's text-input command passes through a device shell. ADB Ready therefore
132
- accepts only 1–256 ASCII letters, numbers, spaces, and `._@+,:/-`. Unsupported
133
- characters are rejected instead of being reinterpreted by a shell.
159
+ Text input is bounded to 1–256 Unicode code points and 4096 UTF-8 bytes. The
160
+ default `--input-mode auto` uses Android's built-in `input text` only for the
161
+ conservative ASCII set of letters, numbers, spaces, and `._@+,:/-`. Use
162
+ `--input-mode ascii` to require that path explicitly.
163
+
164
+ Unicode, emoji, RTL, CJK, and multiline input use the open-source
165
+ [ADBKeyBoard](https://github.com/senzhk/ADBKeyBoard) broadcast contract because
166
+ Android's built-in input command does not reliably represent those values. The
167
+ IME must already be installed and enabled on the selected target. ADB Ready
168
+ checks that contract before touching the screen, temporarily selects the IME,
169
+ and restores the exact previous IME in a `finally` path. It never downloads,
170
+ installs, enables, or leaves a keyboard selected silently. If the helper or a
171
+ restorable current IME is unavailable, the action fails before typing.
172
+
173
+ Some apps drop text delivered too quickly. Pace either backend by Unicode code
174
+ point with a whole-millisecond delay from 0 to 2000:
175
+
176
+ ```bash
177
+ adb-ready ui type 'مرحبا بالعالم' --input-mode unicode --typing-delay 40
178
+ ```
179
+
180
+ ### Protected input
181
+
182
+ Do not put passwords or tokens in a command argument. Pipe one value to stdin:
183
+
184
+ ```bash
185
+ printf '%s' "$TEST_PASSWORD" |
186
+ adb-ready ui fill 'id=com.example:id/password' --secret-stdin --submit
187
+ ```
188
+
189
+ `--secret-stdin` reads at most 4096 bytes to EOF, removes one final line ending,
190
+ and preserves embedded newlines. Plaintext is excluded from host process
191
+ arguments, operation plans, terminal output, structured results, event
192
+ journals, screenshots created by this action, and AI context. The value exists
193
+ only in process memory and the pipe used to deliver it to the selected target.
194
+ For Unicode, the on-device shell receives base64 rather than plaintext.
195
+
196
+ This is a bounded host-side guarantee, not a secure-input claim about Android
197
+ or the app: the selected IME and target receive the value, a normal visible
198
+ field may display it, and device/OEM auditing can observe shell activity. Use a
199
+ password field and assert a non-secret postcondition. A protected field cannot
200
+ expose its value for exact verification, so ADB Ready returns
201
+ `text-not-observable` rather than inventing success.
202
+
203
+ ## Keyboard and runtime-permission dialogs
204
+
205
+ Inspect or dismiss the software keyboard without sending a blind Back action:
206
+
207
+ ```bash
208
+ adb-ready ui keyboard status --json
209
+ adb-ready ui keyboard dismiss
210
+ ```
211
+
212
+ ADB Ready requires Android's InputMethodManager `mInputShown` state and its
213
+ WindowInsets `type=ime` visibility to both exist and agree. `dismiss` sends Back
214
+ only after both report visible, then reads both services again and succeeds only
215
+ after both report hidden. An already hidden keyboard is a verified no-op.
216
+ Missing or conflicting signals return `UI_KEYBOARD_STATE_UNSUPPORTED` or
217
+ `UI_KEYBOARD_STATE_AMBIGUOUS`; ADB Ready does not guess from inset height or
218
+ press Back against an unknown screen.
219
+
220
+ Inspect one standard Android runtime-permission prompt before choosing an exact
221
+ decision:
222
+
223
+ ```bash
224
+ adb-ready ui permission inspect --json
225
+ adb-ready ui permission respond allow-while-using
226
+ adb-ready ui permission respond allow-once --dry-run --json
227
+ adb-ready ui permission respond deny
228
+ ```
229
+
230
+ Inspection returns only the decisions actually present on the current dialog.
231
+ Responses match PermissionController package and resource IDs, never translated
232
+ button labels or coordinates copied from another device. A fresh hierarchy must
233
+ show that the specific prompt changed or disappeared after the tap. Multiple,
234
+ unknown, or OEM-specific states fail closed. Notification permission setup,
235
+ biometric prompts, Settings mutation, and generic system-dialog acceptance are
236
+ intentionally outside this workflow.
134
237
 
135
238
  ## Find, assert, compare, and wait
136
239
 
@@ -143,8 +246,9 @@ adb-ready ui assert 'text=Loading' --state gone
143
246
  adb-ready ui compare 7c4a31b8d2ef0000000000000000000000000000000000000000000000000000
144
247
  ```
145
248
 
146
- `compare` consumes the complete digest returned by inspection or another UI
147
- action and reports whether the current hierarchy changed.
249
+ The CLI `compare` command consumes the complete digest returned by inspection
250
+ or another UI action and reports whether the current hierarchy changed. It
251
+ does not persist the earlier sensitive hierarchy.
148
252
 
149
253
  Waits use exact, explicit selectors:
150
254
 
@@ -186,11 +290,31 @@ the next state is known.
186
290
  ## AI agents
187
291
 
188
292
  The MCP server exposes the same intent-level workflow through `audit_ui`, `get_ui`,
189
- `find_ui`, `fill_ui`, `clear_ui`, `scroll_ui`, `assert_ui`, and `compare_ui`.
293
+ `find_ui`, `fill_ui`, `clear_ui`, `scroll_ui`, `assert_ui`, `compare_ui`,
294
+ `inspect_keyboard`, `dismiss_keyboard`, `inspect_permission_dialog`, and
295
+ `respond_to_permission_dialog`.
190
296
  Its structured selectors can match exact values, prefixes, or substrings and
191
297
  qualify enabled/actionable state. An optional one-based occurrence is accepted
192
298
  only when repeated nodes are intentional. Arguments are schema-validated, each
193
299
  MCP connection stays bound to one target, and no raw ADB or shell tool is
194
300
  exposed.
195
301
 
302
+ `type_text_ui` and `fill_ui` accept `inputMode` and `typingDelayMs`. For a
303
+ secret, set `secretEnv` to the *name* of an environment variable already passed
304
+ to the local MCP server and omit `text`. ADB Ready reads the value locally; the
305
+ MCP request, tool result, and retained agent conversation contain only the
306
+ variable name. Supplying both fields, neither field, or a missing/empty variable
307
+ fails before device mutation.
308
+
309
+ `inspect_ui` retains at most eight sensitive snapshots in memory for that MCP
310
+ connection only. `compare_ui` accepts one of those complete digests and returns
311
+ a bounded semantic diff: added, removed, updated, and moved nodes with current
312
+ selector context. `changed` describes the requested filtered view, while
313
+ `digestChanged` also reveals changes outside that view. Unchanged screens return
314
+ empty change lists. Bases expire
315
+ after five minutes and are never written to disk; missing or expired digests
316
+ require a fresh `inspect_ui`. A diff is rejected rather than guessed when its
317
+ target, display bounds or rotation, hierarchy filters, acquisition contract, or
318
+ completeness differs from the base.
319
+
196
320
  [Connect an agent →](./agent-integration.md)
@@ -15,8 +15,14 @@ adb-ready dev --dry-run
15
15
  - [`flutter/adb-ready.config.json`](./flutter/adb-ready.config.json)
16
16
  - [`capacitor/adb-ready.config.json`](./capacitor/adb-ready.config.json)
17
17
  - [`custom/adb-ready.config.json`](./custom/adb-ready.config.json)
18
+ - [`ci-emulator/`](./ci-emulator/) — maintained GitHub-hosted emulator
19
+ acceptance with a finite run and retained failure evidence
20
+ - [`ci-physical/`](./ci-physical/) — manual self-hosted physical-device job
21
+ with explicit serial selection, host-local leasing, and bounded evidence
18
22
  - [`automation/README.md`](./automation/README.md) — one finite AVD, deployment,
19
23
  verifier, evidence, and cleanup job
24
+ - [`target-pools/`](./target-pools/) — bounded fan-out across explicit local,
25
+ AVD, remote-ADB, or Firebase members
20
26
 
21
27
  Prefer `adb-ready init` when starting from an existing detected project. Add
22
28
  only the ports, hooks, and retention rules that the project genuinely needs.
@@ -0,0 +1,32 @@
1
+ # GitHub-hosted Android emulator
2
+
3
+ This fixture powers ADB Ready's maintained emulator acceptance workflow. It
4
+ demonstrates the ownership boundary used in ordinary CI:
5
+
6
+ 1. the runner provisions and owns one Android emulator;
7
+ 2. ADB Ready binds a finite run to its exact serial;
8
+ 3. the fixture verifies Android boot and identity, opens Settings, and captures
9
+ native evidence; and
10
+ 4. ADB Ready retains normalized NDJSON, JUnit, screenshot, window, and manifest
11
+ evidence before the runner tears its emulator down.
12
+
13
+ The workflow is [`android-emulator.yml`](../../.github/workflows/android-emulator.yml).
14
+ Every external Action is pinned to a full commit. The job uses no repository
15
+ secret and uploads evidence with `if: always()`.
16
+
17
+ Do not copy the fixture's custom development process into an application. Keep
18
+ your real project command and readiness assertions, then use the same bounded
19
+ shape:
20
+
21
+ ```bash
22
+ npx adb-ready run \
23
+ --device emulator-5554 \
24
+ --run-timeout 10m \
25
+ --json \
26
+ --non-interactive \
27
+ -- npm run test:e2e
28
+ ```
29
+
30
+ ADB Ready does not implicitly create SDKs or AVDs. If another workflow layer
31
+ created the emulator, ADB Ready attaches without claiming ownership and leaves
32
+ emulator teardown to that layer.
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "../../schema/config-v1.schema.json",
3
+ "version": 1,
4
+ "dev": {
5
+ "preset": "custom",
6
+ "command": {
7
+ "executable": "node",
8
+ "args": ["examples/ci-emulator/dev-service.mjs"]
9
+ },
10
+ "reversePorts": [],
11
+ "logs": false,
12
+ "cleanupPorts": true,
13
+ "watch": true,
14
+ "ready": {
15
+ "timeoutMs": 60000,
16
+ "pollIntervalMs": 1000,
17
+ "all": [{ "kind": "boot" }, { "kind": "unlocked" }]
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,14 @@
1
+ let stopping = false;
2
+
3
+ const keepAlive = setInterval(() => undefined, 60_000);
4
+
5
+ function stop() {
6
+ if (stopping) return;
7
+ stopping = true;
8
+ process.stderr.write("CI development service stopped.\n");
9
+ clearInterval(keepAlive);
10
+ }
11
+
12
+ process.once("SIGINT", stop);
13
+ process.once("SIGTERM", stop);
14
+ process.stderr.write("CI development service ready.\n");