@houwert/conductor 0.2.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 (60) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/README.md +39 -0
  3. package/dist/commands/assert-not-visible.js +47 -0
  4. package/dist/commands/assert-visible.js +58 -0
  5. package/dist/commands/back.js +25 -0
  6. package/dist/commands/cheat-sheet.js +100 -0
  7. package/dist/commands/daemon.js +61 -0
  8. package/dist/commands/device-pool.js +202 -0
  9. package/dist/commands/erase-text.js +26 -0
  10. package/dist/commands/foreground-app.js +50 -0
  11. package/dist/commands/hide-keyboard.js +27 -0
  12. package/dist/commands/inspect.js +37 -0
  13. package/dist/commands/install.js +64 -0
  14. package/dist/commands/launch-app.js +42 -0
  15. package/dist/commands/list-apps.js +60 -0
  16. package/dist/commands/list-devices.js +61 -0
  17. package/dist/commands/open-link.js +22 -0
  18. package/dist/commands/press-key.js +91 -0
  19. package/dist/commands/run-flow-inline.js +25 -0
  20. package/dist/commands/run-flow.js +29 -0
  21. package/dist/commands/run-parallel.js +143 -0
  22. package/dist/commands/screenshot.js +29 -0
  23. package/dist/commands/scroll-until-visible.js +69 -0
  24. package/dist/commands/scroll.js +36 -0
  25. package/dist/commands/session.js +49 -0
  26. package/dist/commands/set-location.js +18 -0
  27. package/dist/commands/set-orientation.js +23 -0
  28. package/dist/commands/start-device.js +178 -0
  29. package/dist/commands/stop-app.js +32 -0
  30. package/dist/commands/swipe.js +72 -0
  31. package/dist/commands/tap.js +69 -0
  32. package/dist/commands/type.js +22 -0
  33. package/dist/daemon/client.js +112 -0
  34. package/dist/daemon/protocol.js +25 -0
  35. package/dist/daemon/server.js +208 -0
  36. package/dist/drivers/android.js +343 -0
  37. package/dist/drivers/bootstrap.js +371 -0
  38. package/dist/drivers/element-resolver.js +371 -0
  39. package/dist/drivers/flow-runner.js +1309 -0
  40. package/dist/drivers/ios.js +328 -0
  41. package/dist/drivers/js-engine.js +150 -0
  42. package/dist/drivers/wait.js +211 -0
  43. package/dist/index.js +426 -0
  44. package/dist/output.js +36 -0
  45. package/dist/pkg-root.js +28 -0
  46. package/dist/postinstall.js +12 -0
  47. package/dist/runner.js +190 -0
  48. package/dist/session.js +66 -0
  49. package/dist/update-check.js +109 -0
  50. package/dist/utils.js +19 -0
  51. package/dist/verbose.js +17 -0
  52. package/drivers/android/conductor-app.apk +0 -0
  53. package/drivers/android/conductor-server.apk +0 -0
  54. package/drivers/ios/conductor-driver-ios-config.xctestrun +126 -0
  55. package/drivers/ios/conductor-driver-ios.zip +0 -0
  56. package/drivers/ios/conductor-driver-iosUITests-Runner.zip +0 -0
  57. package/package.json +52 -0
  58. package/proto/conductor_android.proto +116 -0
  59. package/skills/conductor/SKILL.md +677 -0
  60. package/skills/conductor/references/flow-syntax.md +179 -0
@@ -0,0 +1,677 @@
1
+ # conductor
2
+
3
+ A token-efficient CLI for Conductor mobile UI testing, designed for AI agents.
4
+
5
+ ## Usage
6
+
7
+ ```
8
+ conductor <command> [args] [options]
9
+ ```
10
+
11
+ Global options:
12
+ - `--device <id>` — target device ID; also keys the session and daemon (auto-detected if omitted)
13
+ - `--help` — show help
14
+ - `--json` — machine-readable JSON output (avoid unless you need structured parsing)
15
+ - `--verbose / -v` — log daemon calls, driver fallbacks, and raw output
16
+
17
+ ---
18
+
19
+ ## Commands
20
+
21
+ ### `list-devices`
22
+
23
+ List connected Android emulators and iOS simulators.
24
+
25
+ ```bash
26
+ conductor list-devices
27
+ ```
28
+
29
+ Output:
30
+ ```
31
+ android device emulator-5554 Pixel_6_API_33
32
+ ios booted ABC123-DEF456 iPhone 15
33
+ ```
34
+
35
+ ---
36
+
37
+ ### `foreground-app`
38
+
39
+ Print the bundle ID (iOS) or package name (Android) of the currently open app.
40
+
41
+ ```bash
42
+ conductor foreground-app
43
+ conductor foreground-app --device C59D3241-FB6A-4E3B-AE7B-A82D3C933889
44
+ ```
45
+
46
+ ---
47
+
48
+ ### `list-apps`
49
+
50
+ List all installed app IDs (bundle IDs on iOS, package names on Android), sorted alphabetically. Does not require the driver to be running.
51
+
52
+ ```bash
53
+ conductor list-apps
54
+ conductor list-apps --device emulator-5554
55
+ ```
56
+
57
+ ---
58
+
59
+ ### `launch-app <appId>`
60
+
61
+ Launch an app by bundle ID and save it to the session.
62
+
63
+ ```bash
64
+ conductor launch-app com.example.myapp
65
+ conductor launch-app com.example.myapp --device emulator-5554
66
+ conductor launch-app com.example.myapp --clear-state # wipe app data first
67
+ conductor launch-app com.example.myapp --clear-keychain # wipe keychain first
68
+ conductor launch-app com.example.myapp --no-stop-app # resume instead of restart
69
+ conductor launch-app com.example.myapp --argument env=staging --argument debug=true
70
+ ```
71
+
72
+ Flags:
73
+ - `--clear-state` — clear app data/state before launching
74
+ - `--clear-keychain` — clear keychain before launching (iOS: full keychain; Android: account credentials)
75
+ - `--no-stop-app` — do not stop the app before launching; brings it to the foreground instead of restarting it (default: app is stopped first)
76
+ - `--argument key=value` — set a launch argument; repeatable for multiple arguments
77
+
78
+ The `appId` and `deviceId` are persisted to `~/.conductor/sessions/<deviceId>.json` and reused by subsequent commands.
79
+
80
+ ---
81
+
82
+ ### `stop-app [<appId>]`
83
+
84
+ Stop the running app. Uses session `appId` if not specified.
85
+
86
+ ```bash
87
+ conductor stop-app
88
+ conductor stop-app com.example.myapp
89
+ ```
90
+
91
+ ---
92
+
93
+ ### `tap <element>`
94
+
95
+ Tap a UI element by its text label or accessibility ID. **Try the most obvious text label or ID first** — e.g. `"Sign In"`, `"Submit"`, `"btn_login"`. Only run `inspect` to look up the exact identifier if your first attempt fails. **For icon-only buttons (no visible text label), always run `inspect` first** to find the accessibility ID before tapping.
96
+
97
+ ```bash
98
+ conductor tap "Sign In"
99
+ conductor tap --id "btn_login" # match by accessibility ID instead of text
100
+ conductor tap --text "Edit" # match by text only (not id)
101
+ conductor tap "Next" --index 1 # pick the 2nd match (0-based)
102
+ conductor tap "Add to cart" --long-press
103
+ conductor tap "Like" --double-tap
104
+ conductor tap "Delete" --optional # do not fail if not found
105
+ conductor tap "Edit" --below "Username" # tap "Edit" that is below the "Username" element
106
+ conductor tap "Submit" --above "Footer"
107
+ conductor tap ">" --right-of "Email"
108
+ ```
109
+
110
+ Flags:
111
+ - `--id <id>` — match by accessibility ID / resourceId instead of text
112
+ - `--text <text>` — match by text only (not id); bare positional arg matches text OR id
113
+ - `--index <n>` — pick the nth match when multiple elements share the same text/id (0-based)
114
+ - `--long-press` — hold instead of tap
115
+ - `--double-tap` — double-tap the element
116
+ - `--optional` — do not fail if element is not found
117
+ - `--focused` — match only focused elements
118
+ - `--enabled` / `--no-enabled` — match by enabled state
119
+ - `--checked` / `--no-checked` — match by checked state
120
+ - `--selected` / `--no-selected` — match by selected state
121
+ - `--below <text>` — match element below the given reference element
122
+ - `--above <text>` — match element above the given reference element
123
+ - `--left-of <text>` — match element left of the given reference element
124
+ - `--right-of <text>` — match element right of the given reference element
125
+
126
+ ---
127
+
128
+ ### `type <text>`
129
+
130
+ Type text into the currently focused input field.
131
+
132
+ ```bash
133
+ conductor type "hello@example.com"
134
+ conductor type "my password"
135
+ ```
136
+
137
+ ---
138
+
139
+ ### `erase-text [n]`
140
+
141
+ Erase characters from the currently focused input field. Defaults to 50 characters.
142
+
143
+ ```bash
144
+ conductor erase-text # erase 50 characters
145
+ conductor erase-text 10 # erase 10 characters
146
+ ```
147
+
148
+ ---
149
+
150
+ ### `back`
151
+
152
+ Press the Android back button. **Android only** — iOS has no back button. On iOS, always run `inspect` first to find the exact label or accessibility ID of the in-app back button, then use `tap` to press it.
153
+
154
+ ```bash
155
+ conductor back
156
+ ```
157
+
158
+ ---
159
+
160
+ ### `press-key <key>`
161
+
162
+ Press a hardware or keyboard key. Valid keys: `Enter`, `Backspace`, `Home`, `End`, `Tab`, `Delete`, `Escape`, `VolumeUp`, `VolumeDown`, `Power`, `Lock`, `Back`, `Camera`, `Search`.
163
+
164
+ Not all keys are supported on all platforms — unsupported keys are silently ignored (e.g. `Back` on iOS).
165
+
166
+ ```bash
167
+ conductor press-key Enter
168
+ conductor press-key Backspace
169
+ conductor press-key VolumeUp
170
+ ```
171
+
172
+ ---
173
+
174
+ ### `hide-keyboard`
175
+
176
+ Dismiss the on-screen keyboard. On iOS, sends the return key; on Android, sends KEYCODE_ESCAPE.
177
+
178
+ ```bash
179
+ conductor hide-keyboard
180
+ ```
181
+
182
+ ---
183
+
184
+ ### `scroll`
185
+
186
+ Scroll the screen. Default direction is `down`.
187
+
188
+ ```bash
189
+ conductor scroll
190
+ conductor scroll --direction up
191
+ conductor scroll --direction left
192
+ conductor scroll --direction right
193
+ ```
194
+
195
+ Directions: `down` | `up` | `left` | `right`
196
+
197
+ ---
198
+
199
+ ### `swipe`
200
+
201
+ Perform a swipe gesture.
202
+
203
+ ```bash
204
+ conductor swipe --direction UP
205
+ conductor swipe --direction DOWN
206
+ conductor swipe --direction LEFT
207
+ conductor swipe --direction RIGHT
208
+ conductor swipe --start 0.5,0.8 --end 0.5,0.2 # normalised coords (0–1)
209
+ conductor swipe --start 540,1600 --end 540,400 # absolute px
210
+ conductor swipe --direction UP --duration 1000 # slower swipe
211
+ ```
212
+
213
+ Flags:
214
+ - `--direction <UP|DOWN|LEFT|RIGHT>` — directional swipe (case-insensitive)
215
+ - `--start <x,y>` — start coordinate; values ≤1 are treated as normalised (0–1), larger as absolute px
216
+ - `--end <x,y>` — end coordinate (same normalisation rule)
217
+ - `--duration <ms>` — swipe duration in milliseconds (default: 500); use `--start`/`--end` or `--direction`
218
+
219
+ ---
220
+
221
+ ### `scroll-until-visible <element>`
222
+
223
+ Scroll in a direction until an element is visible. Useful for long lists.
224
+
225
+ ```bash
226
+ conductor scroll-until-visible "Checkout"
227
+ conductor scroll-until-visible --id "btn_submit"
228
+ conductor scroll-until-visible "Terms" --direction up
229
+ conductor scroll-until-visible "Privacy" --timeout 60000
230
+ ```
231
+
232
+ Flags:
233
+ - `--id <id>` — match by accessibility ID / resourceId instead of text
234
+ - `--text <text>` — match by text only (not id)
235
+ - `--direction <down|up|left|right>` — scroll direction (default: `down`)
236
+ - `--timeout <ms>` — max time in milliseconds (default: 30 000)
237
+
238
+ ---
239
+
240
+ ### `assert-visible <element>`
241
+
242
+ Assert that a UI element is visible on screen. Exits with code 1 if not found. Try the expected text or ID directly — only use `inspect` if it fails and you need to find the exact identifier.
243
+
244
+ ```bash
245
+ conductor assert-visible "Welcome"
246
+ conductor assert-visible --id "dashboard_title"
247
+ conductor assert-visible --text "Submit" # text-only match
248
+ conductor assert-visible "Loading..." --optional # do not fail if absent
249
+ conductor assert-visible "Dashboard" --timeout 30000 # wait up to 30 s
250
+ conductor assert-visible "Edit" --below "Username" # relative position
251
+ conductor assert-visible "Item" --index 2 # third match (0-based)
252
+ ```
253
+
254
+ Flags:
255
+ - `--id <id>` — match by accessibility ID / resourceId instead of text
256
+ - `--text <text>` — match by text only (not id); bare positional arg matches text OR id
257
+ - `--index <n>` — pick the nth match (0-based)
258
+ - `--timeout <ms>` — max wait time in milliseconds (default: 17 000)
259
+ - `--optional` — succeed even if the element is not found (useful for conditional checks)
260
+ - `--focused` — match only focused elements
261
+ - `--enabled` / `--no-enabled` — match by enabled state
262
+ - `--checked` / `--no-checked` — match by checked state
263
+ - `--selected` / `--no-selected` — match by selected state
264
+ - `--below <text>` — match element below the given reference element
265
+ - `--above <text>` — match element above the given reference element
266
+ - `--left-of <text>` — match element left of the given reference element
267
+ - `--right-of <text>` — match element right of the given reference element
268
+
269
+ ---
270
+
271
+ ### `assert-not-visible <element>`
272
+
273
+ Assert that a UI element is **not** visible on screen. Fails (exit code 1) if the element is found. Use for verifying elements have been dismissed or hidden.
274
+
275
+ ```bash
276
+ conductor assert-not-visible "Loading..."
277
+ conductor assert-not-visible --id "error_banner"
278
+ conductor assert-not-visible "Error" --timeout 5000 # wait up to 5 s for it to disappear
279
+ ```
280
+
281
+ Flags:
282
+ - `--id <id>` — match by accessibility ID / resourceId instead of text
283
+ - `--text <text>` — match by text only (not id)
284
+ - `--index <n>` — pick the nth match (0-based)
285
+ - `--timeout <ms>` — max time to wait for element to disappear (default: 1 000)
286
+
287
+ ---
288
+
289
+ ### `open-link <url>`
290
+
291
+ Open a URL or deep link on the device. Works for both HTTP URLs and custom scheme deep links.
292
+
293
+ ```bash
294
+ conductor open-link "https://example.com/reset-password"
295
+ conductor open-link "myapp://onboarding"
296
+ ```
297
+
298
+ ---
299
+
300
+ ### `set-location --lat <n> --lng <n>`
301
+
302
+ Set the device's simulated GPS location.
303
+
304
+ ```bash
305
+ conductor set-location --lat 52.3676 --lng 4.9041
306
+ conductor set-location --lat 37.7749 --lng -122.4194
307
+ ```
308
+
309
+ ---
310
+
311
+ ### `set-orientation <portrait|landscape>`
312
+
313
+ Set the device orientation.
314
+
315
+ ```bash
316
+ conductor set-orientation portrait
317
+ conductor set-orientation landscape
318
+ ```
319
+
320
+ ---
321
+
322
+ ### `screenshot`
323
+
324
+ Take a screenshot of the current screen. **Prefer `inspect` over `screenshot`** when you need to understand what is on screen — the view hierarchy tells you element text, IDs, and structure without consuming a vision token. Use `screenshot` only for visual evidence, debugging rendering issues, or when a human needs to see the screen.
325
+
326
+ ```bash
327
+ conductor screenshot
328
+ conductor screenshot --output /tmp/screen.png
329
+ conductor screenshot --output ./screenshots/login.png
330
+ ```
331
+
332
+ Default output: `./screenshot-<timestamp>.png`
333
+
334
+ **Screenshot path guidance:** Always write to `/tmp/<agent-name>/` (e.g. `/tmp/tester-ios-iphone-16/screen.png`). This keeps files off the working directory, is unique per agent, and is the same folder used for YAML flows. Create it with `mkdir -p` before first use.
335
+
336
+ ---
337
+
338
+ ### `inspect`
339
+
340
+ Print the UI element hierarchy of the current screen. Use this when a `tap` or `assert-visible` fails and you need to discover the exact element text or accessibility ID.
341
+
342
+ The hierarchy shows each element's type, text, and accessibility ID (resourceId / accessibilityIdentifier). Use the `text` value or `id` value as the argument to `tap` / `assert-visible`.
343
+
344
+ ```bash
345
+ conductor inspect
346
+ ```
347
+
348
+ ---
349
+
350
+ ### `run-flow <file>`
351
+
352
+ Execute a Maestro YAML flow file.
353
+
354
+ ```bash
355
+ conductor run-flow ./flows/login.yaml
356
+ conductor run-flow ./flows/checkout.yaml --device emulator-5554
357
+ ```
358
+
359
+ ---
360
+
361
+ ### `run-flow-inline <yaml>`
362
+
363
+ Execute inline Maestro YAML commands directly.
364
+
365
+ ```bash
366
+ conductor run-flow-inline "- tapOn: \"Submit\"\n- assertVisible: \"Success\""
367
+ ```
368
+
369
+ ---
370
+
371
+ ### `session`
372
+
373
+ Show, clear, or list device sessions.
374
+
375
+ ```bash
376
+ conductor session # show session for auto-detected device
377
+ conductor session --device <id> # show session for a specific device
378
+ conductor session --clear --device <id> # clear a device's session
379
+ conductor session --list # list all device sessions
380
+ ```
381
+
382
+ Sessions are stored in `~/.conductor/sessions/<deviceId>.json`.
383
+
384
+ ---
385
+
386
+ ### `cheat-sheet`
387
+
388
+ Print this command reference inline.
389
+
390
+ ```bash
391
+ conductor cheat-sheet
392
+ ```
393
+
394
+ ---
395
+
396
+ ### `install`
397
+
398
+ Copy the conductor skill files into the current project's `.claude/skills/conductor/` directory, so AI agents in that project can use the `cheat-sheet` command locally.
399
+
400
+ ```bash
401
+ conductor install --skills
402
+ ```
403
+
404
+ ---
405
+
406
+ ### `daemon-start`
407
+
408
+ Start the background daemon for the current device session. The daemon keeps the driver process alive between commands, reducing startup overhead. Commands auto-start the driver directly if no daemon is running — this is optional.
409
+
410
+ ```bash
411
+ conductor daemon-start
412
+ conductor daemon-start --device emulator-5554
413
+ ```
414
+
415
+ ---
416
+
417
+ ### `daemon-stop`
418
+
419
+ Stop the daemon for a device session.
420
+
421
+ ```bash
422
+ conductor daemon-stop
423
+ conductor daemon-stop --device emulator-5554
424
+ conductor daemon-stop --all # stop all running daemons
425
+ ```
426
+
427
+ ---
428
+
429
+ ### `daemon-status`
430
+
431
+ Show whether the daemon is running for a device session.
432
+
433
+ ```bash
434
+ conductor daemon-status
435
+ conductor daemon-status --device emulator-5554
436
+ ```
437
+
438
+ ---
439
+
440
+ ### `device-pool`
441
+
442
+ Manage a shared pool of devices for concurrent multi-agent use. Pool state is stored in `~/.conductor/device-pool.json` with file-based locking so multiple agents can safely acquire/release devices without colliding.
443
+
444
+ ```bash
445
+ conductor device-pool --list # list all devices and their pool status
446
+ conductor device-pool --acquire # claim a free device; prints its ID
447
+ conductor device-pool --release <id> # release a device back to the pool
448
+ ```
449
+
450
+ Typical multi-agent workflow:
451
+ ```bash
452
+ # Each agent acquires its own device before starting
453
+ DEVICE=$(conductor device-pool --acquire)
454
+ conductor launch-app com.example.myapp --device "$DEVICE"
455
+ # ... run tests ...
456
+ conductor device-pool --release "$DEVICE"
457
+ ```
458
+
459
+ Stale acquisitions (whose process is no longer running) are automatically pruned on the next `--acquire`.
460
+
461
+ ---
462
+
463
+ ### `run-parallel`
464
+
465
+ Distribute a directory of Maestro YAML flow files across all booted devices, running them in parallel. Flows are assigned round-robin; results are aggregated and printed at the end.
466
+
467
+ ```bash
468
+ conductor run-parallel --flows-dir ./tests
469
+ ```
470
+
471
+ Exits with code 0 if all flows pass, 1 if any fail.
472
+
473
+ ---
474
+
475
+ ## Typical Agent Workflow
476
+
477
+ **Prefer individual CLI commands over `run-flow` / `run-flow-inline`.** Use `tap`, `type`, `scroll`, `swipe`, `assert-visible`, etc. directly — they are faster, give immediate feedback per step, and make failures easier to diagnose. Only reach for `run-flow` or `run-flow-inline` when you need Maestro-specific YAML features (conditional logic, `runScript`, retryTapIfNoChange`, etc.) that have no CLI equivalent.
478
+
479
+ **Always `launch-app` before interacting.** Never attempt to `tap`, `type`, `scroll`, or navigate before the app is launched and the session is set. `launch-app` both opens the app and saves the `appId`/`deviceId` to the session so all subsequent commands know which device and app to target.
480
+
481
+ **To understand what is on screen, run `inspect` first** — it gives you element text, IDs, and structure. Only take a `screenshot` when you need visual evidence or are debugging a rendering issue.
482
+
483
+ **Try obvious text labels and IDs first. Only run `inspect` when a `tap` or `assert-visible` fails and you need to discover the exact identifier.**
484
+
485
+ Single-agent (default session, no flag needed):
486
+ ```bash
487
+ # 1. Check what devices are available
488
+ conductor list-devices
489
+
490
+ # 2. Launch the app — ALWAYS do this first (sets session, opens app)
491
+ conductor launch-app com.example.myapp --device emulator-5554
492
+
493
+ # 3. Interact using the most likely text labels or IDs
494
+ conductor tap "Sign In" # try the obvious label first
495
+ conductor tap "username_field" # or a guessed test ID
496
+ conductor type "user@example.com"
497
+ conductor tap "password_field"
498
+ conductor type "secret123"
499
+ conductor tap "Login"
500
+
501
+ # → If a tap fails, run inspect to find the real identifier:
502
+ conductor inspect
503
+ # → hierarchy shows text="Log in", id="btn_login" — retry with correct value
504
+ conductor tap "Log in"
505
+
506
+ # 4. Assert — try expected text directly
507
+ conductor assert-visible "Dashboard"
508
+
509
+ # 5. Inspect to understand what is on screen (prefer over screenshot)
510
+ conductor inspect
511
+
512
+ # 6. Screenshot only for visual evidence or rendering checks
513
+ conductor screenshot --output /tmp/tester-ios-iphone-16/post-login.png
514
+
515
+ # 7. Run a full flow only when CLI commands aren't sufficient
516
+ # Write the YAML to /tmp/<agent-name>/ first — never pass YAML inline, it doesn't work
517
+ mkdir -p /tmp/tester-ios-iphone-16
518
+ cat > /tmp/tester-ios-iphone-16/checkout.yaml << 'EOF'
519
+ appId: com.example.myapp
520
+ ---
521
+ - tapOn: "Checkout"
522
+ - assertVisible: "Order confirmed"
523
+ EOF
524
+ conductor run-flow /tmp/tester-ios-iphone-16/checkout.yaml
525
+ ```
526
+
527
+ ---
528
+
529
+ ## Session State
530
+
531
+ Each session is stored in `~/.conductor/sessions/<name>.json`:
532
+
533
+ ```json
534
+ {
535
+ "appId": "com.example.myapp",
536
+ "deviceId": "emulator-5554"
537
+ }
538
+ ```
539
+
540
+ - Set automatically by `launch-app`
541
+ - Cleared by `session --clear`
542
+ - Used by all interaction commands when `--device` is not specified
543
+
544
+ ---
545
+
546
+ ## Multi-Agent Parallel Testing
547
+
548
+ Pass `--device <id>` on every command. The device ID is the natural key — each device gets its own session file and its own daemon process (with a persistent direct driver connection). Two agents targeting different devices never share state or a daemon.
549
+
550
+ ### Device assignment — do this first, before spawning agents
551
+
552
+ **CRITICAL: each device must be assigned to exactly one agent. Never give two agents the same device.**
553
+
554
+ Before spawning any agents, check how many devices are already booted with `list-devices`. If there are not enough for the number of agents you need, boot more with `start-device` — one call per additional device needed. Only then assign and spawn agents.
555
+
556
+ ```bash
557
+ # 1. Check what's already booted
558
+ conductor list-devices
559
+ # ios booted C59D3241-... iPhone 16
560
+ # (only 1 device, but we need 2)
561
+
562
+ # 2. Boot another if needed
563
+ conductor start-device --platform ios
564
+
565
+ # 3. Re-check to get the new device's ID
566
+ conductor list-devices
567
+ # ios booted C59D3241-... iPhone 16
568
+ # ios booted 86B1BC33-... iPhone 15
569
+ ```
570
+
571
+ Assign explicitly — one device per agent, no sharing.
572
+
573
+ ```bash
574
+ conductor list-devices
575
+ # ios booted C59D3241-FB6A-4E3B-AE7B-A82D3C933889 iPhone 16
576
+ # ios booted 86B1BC33-7D83-47FF-AAFE-1BD70FC53038 iPhone 15
577
+ # android device emulator-5554 Pixel_6_API_33
578
+ ```
579
+
580
+ Assign explicitly — one device per agent, no sharing:
581
+ ```
582
+ Agent tester-ios-iphone-16 → --device C59D3241-FB6A-4E3B-AE7B-A82D3C933889
583
+ Agent tester-ios-iphone-15 → --device 86B1BC33-7D83-47FF-AAFE-1BD70FC53038
584
+ Agent tester-android-pixel-6 → --device emulator-5554
585
+ ```
586
+
587
+ ### Agent naming
588
+
589
+ Name each agent after the platform under test and the device it controls: `tester-${platform}-${device_name}` (lowercased, spaces replaced with hyphens). The platform prefix reflects what is being tested — it is not always `ios`. Examples:
590
+ - `tester-ios-iphone-16` — testing an iOS app on an iPhone 16 simulator
591
+ - `tester-android-pixel-6` — testing an Android app on a Pixel 6 emulator
592
+ - `tester-web-iphone-15` — testing a mobile web app on an iPhone 15 simulator
593
+ - `tester-payments-pixel-7` — testing a payments flow on a Pixel 7 emulator
594
+
595
+ This makes it immediately obvious which agent controls which device and prevents accidental reassignment.
596
+
597
+ ### Setup — one `launch-app` per device
598
+
599
+ ```bash
600
+ # Agent tester-ios-iphone-16
601
+ conductor launch-app com.example.myapp --device C59D3241-FB6A-4E3B-AE7B-A82D3C933889
602
+
603
+ # Agent tester-android-pixel-6
604
+ conductor launch-app com.example.myapp --device emulator-5554
605
+ ```
606
+
607
+ **Every subsequent command carries `--device`:**
608
+ ```bash
609
+ # Agent tester-ios-iphone-16
610
+ conductor tap "Sign In" --device C59D3241-FB6A-4E3B-AE7B-A82D3C933889
611
+ conductor type "user@a.com" --device C59D3241-FB6A-4E3B-AE7B-A82D3C933889
612
+ conductor inspect --device C59D3241-FB6A-4E3B-AE7B-A82D3C933889
613
+
614
+ # Agent tester-android-pixel-6 (runs fully in parallel)
615
+ conductor tap "Sign In" --device emulator-5554
616
+ conductor type "user@b.com" --device emulator-5554
617
+ conductor inspect --device emulator-5554
618
+ ```
619
+
620
+ **Inspect all device sessions:**
621
+ ```bash
622
+ conductor session --list
623
+ # Sessions:
624
+ # C59D3241-... appId=com.example.myapp deviceId=C59D3241-...
625
+ # emulator-5554 appId=com.example.myapp deviceId=emulator-5554
626
+ ```
627
+
628
+ **Rules for multi-agent use:**
629
+ - Run `list-devices` first and assign devices before spawning agents — never let agents pick their own
630
+ - Each agent owns exactly one device; no two agents share a device
631
+ - Name agents `tester-ios-${device_name}` / `tester-android-${device_name}` so ownership is obvious
632
+ - Always pass `--device` on every command — this is how agents stay isolated
633
+ - Each device gets its own daemon (`~/.conductor/daemons/<deviceId>/`), keeping the driver process alive for the lifetime of the daemon
634
+ - Screenshot paths must be unique per device; use the device name in the path
635
+
636
+ ---
637
+
638
+ ## Output Modes
639
+
640
+ Output is human-readable by default:
641
+ ```
642
+ ✓ tap "Submit" — done
643
+ ✗ assert-visible "Welcome" — element not found
644
+ ```
645
+
646
+ Use `--json` only when you need structured output for parsing:
647
+ ```json
648
+ {"status": "ok", "message": "tap \"Submit\" — done"}
649
+ {"status": "error", "message": "assert-visible \"Welcome\" — element not found"}
650
+ ```
651
+
652
+ ---
653
+
654
+ ### `start-device`
655
+
656
+ Boot an iOS simulator or Android emulator. Creates the Simulator window and waits until the device is fully ready.
657
+
658
+ ```bash
659
+ conductor start-device --platform ios
660
+ conductor start-device --platform android
661
+ conductor start-device --platform ios --os-version 18
662
+ conductor start-device --platform android --avd Pixel_6_API_33
663
+ ```
664
+
665
+ Flags:
666
+ - `--platform <ios|android>` — required
667
+ - `--os-version <n>` — filter by OS version (iOS: e.g. `18`; Android: API level e.g. `33`)
668
+ - `--avd <name>` — Android AVD name to launch (default: first available AVD)
669
+
670
+ iOS picks the first available iPhone simulator matching the OS version filter (or any if unfiltered). Android launches the named AVD, or the first AVD found.
671
+
672
+ ---
673
+
674
+ ## Related Documentation
675
+
676
+ - [Conductor Flow Syntax](./references/flow-syntax.md)
677
+