-
Notifications
You must be signed in to change notification settings - Fork 130
Expand file tree
/
Copy pathcommand-schema.ts
More file actions
2057 lines (1955 loc) · 83.3 KB
/
command-schema.ts
File metadata and controls
2057 lines (1955 loc) · 83.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import { SETTINGS_USAGE_OVERRIDE } from '../core/settings-contract.ts';
import { SESSION_SURFACES } from '../core/session-surface.ts';
import type { DaemonInstallSource } from '../contracts.ts';
import type { RemoteConfigMetroOptions } from '../remote-config-schema.ts';
import { CAPTURE_COMMAND_SCHEMAS } from '../commands/capture-definition.ts';
import { INTERACTION_COMMAND_SCHEMAS } from '../commands/interactions/definition.ts';
import { REACT_NATIVE_COMMAND_SCHEMAS } from '../commands/react-native/definition.ts';
import {
SELECTOR_COMMAND_SCHEMAS,
SELECTOR_SNAPSHOT_FLAGS,
} from '../commands/selectors-definition.ts';
import { SESSION_LIFECYCLE_COMMAND_SCHEMAS } from '../commands/session-lifecycle/definition.ts';
import {
SCREENSHOT_SPECIFIC_FLAG_DEFINITIONS,
type ScreenshotRequestFlags,
} from '../commands/capture-screenshot-options.ts';
export type CliFlags = RemoteConfigMetroOptions &
ScreenshotRequestFlags & {
json: boolean;
config?: string;
remoteConfig?: string;
stateDir?: string;
daemonBaseUrl?: string;
daemonAuthToken?: string;
daemonTransport?: 'auto' | 'socket' | 'http';
daemonServerMode?: 'socket' | 'http' | 'dual';
tenant?: string;
sessionIsolation?: 'none' | 'tenant';
runId?: string;
leaseId?: string;
leaseBackend?: 'ios-simulator' | 'ios-instance' | 'android-instance';
force?: boolean;
noLogin?: boolean;
sessionLock?: 'reject' | 'strip';
sessionLocked?: boolean;
sessionLockConflicts?: 'reject' | 'strip';
platform?: 'ios' | 'macos' | 'android' | 'linux' | 'apple';
target?: 'mobile' | 'tv' | 'desktop';
device?: string;
udid?: string;
serial?: string;
iosSimulatorDeviceSet?: string;
androidDeviceAllowlist?: string;
session?: string;
metroHost?: string;
metroPort?: number;
bundleUrl?: string;
launchUrl?: string;
verbose?: boolean;
snapshotInteractiveOnly?: boolean;
snapshotDiff?: boolean;
snapshotCompact?: boolean;
snapshotDepth?: number;
snapshotScope?: string;
snapshotRaw?: boolean;
snapshotForceFull?: boolean;
networkInclude?: 'summary' | 'headers' | 'body' | 'all';
baseline?: string;
threshold?: string;
appsFilter?: 'user-installed' | 'all';
count?: number;
fps?: number;
quality?: number;
hideTouches?: boolean;
intervalMs?: number;
delayMs?: number;
holdMs?: number;
jitterPx?: number;
pixels?: number;
doubleTap?: boolean;
clickButton?: 'primary' | 'secondary' | 'middle';
backMode?: 'in-app' | 'system';
pauseMs?: number;
pattern?: 'one-way' | 'ping-pong';
activity?: string;
launchConsole?: string;
header?: string[];
githubActionsArtifact?: string;
installSource?: DaemonInstallSource;
saveScript?: boolean | string;
shutdown?: boolean;
relaunch?: boolean;
surface?: 'app' | 'frontmost-app' | 'desktop' | 'menubar';
headless?: boolean;
restart?: boolean;
noRecord?: boolean;
retainPaths?: boolean;
retentionMs?: number;
replayUpdate?: boolean;
replayMaestro?: boolean;
replayEnv?: string[];
replayShellEnv?: Record<string, string>;
failFast?: boolean;
timeoutMs?: number;
retries?: number;
artifactsDir?: string;
reportJunit?: string;
steps?: string;
stepsFile?: string;
findFirst?: boolean;
findLast?: boolean;
batchOnError?: 'stop';
batchMaxSteps?: number;
batchSteps?: Array<{
command: string;
positionals?: string[];
flags?: Record<string, unknown>;
}>;
help: boolean;
version: boolean;
};
export type DaemonExcludedCliFlag = 'json' | 'help' | 'version' | 'batchSteps' | 'replayMaestro';
export type FlagKey = keyof CliFlags;
type FlagType = 'boolean' | 'int' | 'enum' | 'string' | 'booleanOrString';
export type FlagDefinition = {
key: FlagKey;
names: readonly string[];
type: FlagType;
multiple?: boolean;
enumValues?: readonly string[];
min?: number;
max?: number;
setValue?: CliFlags[FlagKey];
usageLabel?: string;
usageDescription?: string;
};
export type CommandSchema = {
helpDescription: string;
summary?: string;
positionalArgs: readonly string[];
allowsExtraPositionals?: boolean;
allowedFlags: readonly FlagKey[];
defaults?: Partial<CliFlags>;
skipCapabilityCheck?: boolean;
usageOverride?: string;
listUsageOverride?: string;
};
const AGENT_WORKFLOWS = [
{ label: 'help workflow', description: 'Normal bootstrap, exploration, and validation loop' },
{ label: 'help debugging', description: 'Logs, network, alerts, diagnostics, and traces' },
{
label: 'help react-native',
description: 'React Native app automation hazards, overlays, Metro, and routing',
},
{
label: 'help react-devtools',
description: 'React Native performance, profiling, component tree, and renders',
},
{
label: 'help remote',
description: 'Remote/cloud config, tenants, leases, and local service tunnels',
},
{ label: 'help macos', description: 'Desktop, frontmost-app, and menu bar surfaces' },
{ label: 'help dogfood', description: 'Exploratory QA report workflow' },
] as const;
const AGENT_QUICKSTART_LINES = [
'Default loop: devices/apps -> open -> snapshot -i -> press/fill/get/is/wait/find -> verify -> close.',
'Use selectors or refs as positional targets: id="submit", label="Allow", or @e12 from snapshot -i.',
'Plain snapshot reads state; snapshot -i refreshes current interactive refs only.',
'Default snapshot text is an agent-facing, token-efficient view for planning and targeting actions.',
'Read-only visible/state question: use snapshot/get/is/find; use snapshot -i only when refs are needed.',
'Anti-pattern: snapshot -i followed by snapshot -i | grep ...; prior refs stay valid until app state changes, and --force-full is the explicit full re-read.',
'Truncated text/input preview: expand first with snapshot -s @e12, not get text.',
'React Native apps: read help react-native for Metro, DevTools routing, and RN-specific blockers; use react-native dismiss-overlay for LogBox/RedBox overlays.',
'Android RN/Expo Metro: adb reverse tcp:<port> tcp:<port> is harmless and helps the device reach any local Metro port.',
'Expo Go/dev clients: use the provided URL when given; on iOS prefer open "Expo Go" <url>; Android URL opens infer the foreground package for logs/perf when possible.',
'Install flows: install/install-from-source first, then open the installed id with --relaunch.',
'Text: fill \'id="field-email"\' "qa@example.com" replaces; type appends after press.',
'Clearing text: do not use fill <target> ""; use a visible clear/reset control or report that clearing is unsupported.',
'Android IME capture: if fill says input was captured by the keyboard/IME, inspect keyboard state and switch/disable handwriting before retrying; do not loop fill/type.',
'Run mutating commands serially against one session; parallelize only read-only commands or separate sessions.',
'Before taking over a shared device, run session list and reuse the active session name when one already owns the device.',
'Clipboard limits: iOS Allow Paste cannot be automated through XCUITest; prefill with clipboard write. Android non-ASCII should use fill/type, not raw adb input.',
'After mutation: refs are stale. If the next target is known, use its selector directly; otherwise refresh with snapshot -i, scoped with -s when a stable container is known.',
'Raw coordinates are fallback-only: use snapshot -i -c --json rects when iOS refs no-op or child refs are missing.',
'Batch JSON steps use "command", "positionals", "flags"; never "args" or "step".',
'Navigation: app-owned back uses back; system back uses back --system.',
'Verification commands must name the expected text/selector; bare screenshots/snapshots are not enough.',
'Debug evidence: logs clear --restart/mark/path; trace start ./path; trace stop ./path; network dump --include headers.',
'Use agent-device commands in final plans; raw platform tools, pseudo commands, and helper prose are wrong.',
'Full operating guide: agent-device help workflow. Exploratory QA: agent-device help dogfood.',
] as const;
const CONFIGURATION_LINES = [
'Default config files: ~/.agent-device/config.json, ./agent-device.json',
'Use --config <path> or AGENT_DEVICE_CONFIG to load one explicit config file.',
] as const;
const ENVIRONMENT_LINES = [
{ label: 'AGENT_DEVICE_SESSION', description: 'Default session name' },
{ label: 'AGENT_DEVICE_PLATFORM', description: 'Default platform binding' },
{ label: 'AGENT_DEVICE_SESSION_LOCK', description: 'Bound-session conflict mode' },
{ label: 'AGENT_DEVICE_DAEMON_BASE_URL', description: 'Connect to remote daemon' },
{
label: 'AGENT_DEVICE_DAEMON_AUTH_TOKEN',
description: 'Remote daemon service/API token',
},
{
label: 'AGENT_DEVICE_CLOUD_BASE_URL',
description: 'Bridge/control-plane API origin for cloud auth and /api-keys',
},
] as const;
const EXAMPLE_LINES = [
'agent-device open Settings --platform ios',
'agent-device open TextEdit --platform macos',
'agent-device snapshot -i',
'agent-device react-devtools get tree --depth 3',
'agent-device fill @e3 "test@example.com"',
'agent-device replay ./session.ad',
'agent-device test ./suite --platform android',
] as const;
const HELP_TOPICS = {
workflow: {
summary: 'Normal agent-device bootstrap, exploration, and validation loop',
body: `agent-device help workflow
Version-matched operating guide for normal agent-device work.
Core loop:
devices/apps -> open -> snapshot or snapshot -i -> get/is/find/wait or press/fill/scroll/back -> verify -> close
Command shape:
Plans should use agent-device commands, not raw platform tools, pseudo commands, package-manager aliases, or helper prose.
Put subcommand first, then positionals, then flags:
agent-device open com.example.app --session checkout --platform android --relaunch
agent-device record start ./checkout.mp4 --session checkout
Snapshot refs look like @e12. After snapshot -i, use the exact @eN ref from that output.
If the exact ref is not known yet, first output snapshot -i, then use a concrete example shape like press @e12 in the next command; do not write @<ref>, @ref, @Label_Name, or @eN placeholders.
Close means agent-device close. App-owned back means back; system back means back --system.
Taps are press or click. Gestures use swipe, longpress, or gesture <pan|fling|pinch|rotate|transform>. Android pinch, rotate, and transform use provider-native touch injection when available, then the bundled multi-touch helper.
Bootstrap:
agent-device devices --platform ios
agent-device apps --platform android
agent-device open MyApp --platform ios --device "iPhone 17 Pro"
agent-device open <discovered-app-id> --session checkout --platform android
agent-device install com.example.app ./dist/app.apk --platform android
agent-device reinstall com.example.app ./build/MyApp.app --platform ios
agent-device install-from-source --github-actions-artifact org/repo:app-debug --platform android
agent-device open com.example.app --platform android --relaunch
If app id is unknown, plan devices, apps, then open <discovered-app-id>. Discovery is not enough when the task asks to open/start the app.
Install arguments are app/package id then artifact path. If the task says install, use install; use reinstall only when explicitly requested. Fresh runtime state is open --relaunch after install.
Do not open artifact paths or invent package ids. If apps lookup misses the target and no URL/artifact is provided, ask or stop.
Snapshots and refs:
snapshot reads visible state. snapshot -i gets current interactive refs only; it is the fast path when the next step is an interaction.
Default snapshot text is an agent-facing, token-efficient view for planning and targeting actions; use --raw or --json only when you need the full provider tree.
Snapshot legend:
@e12 [button] label="Add to cart" id="add-cart" enabled hittable -> press @e12 or press 'id="add-cart"'.
@e13 [textinput] label="Notes" preview="Leave at side..." truncated -> snapshot -s @e13 before reading.
@e14 [cell] label="Profiles" focused -> tvOS focus is currently on this row.
[off-screen below] 4 items: "Privacy", "About" -> scroll down, then snapshot -i; those are hints, not refs.
Re-snapshot after navigation, submit, typing/fill, modal/list/reload/dynamic changes when you need new refs.
Anti-pattern: snapshot -i followed by snapshot -i | grep ...
Refs from the first snapshot remain valid until you press, fill, type, scroll, go back, wait for async UI, or otherwise change app state.
After a mutation, prefer a known selector/label directly (for example press 'label="Send"') because interaction commands refresh interactive state internally. If you need to discover the new control, use snapshot -i, or snapshot -i -s "Composer" when a stable container label/id can scope the refresh.
For a targeted query, use find/get/is. If you truly need the full tree again, pass --force-full.
Off-screen summaries are scroll hints; use scroll, not swipe, then snapshot -i.
Missing target in a long list: use a short manual scroll + snapshot loop with a max attempt count. If a named target is summarized as off-screen below/above, use scroll down/up, then snapshot -i; do not use scroll bottom/top because the target may appear before the absolute list edge. Use scroll bottom/top only when the task explicitly asks for the list edge. Edge scrolls verify hidden content with snapshots and stop when no matching hidden content remains.
Truncated text/input previews: do not use get text first; expand with snapshot -s @ref (for example snapshot -s @e7), then read the scoped output.
Rare iOS accessibility gaps: if a row ref is shown disabled/hittable:false and press @ref reports success but no UI change, or a horizontal tab/filter bar is collapsed into one composite/seekbar with no child refs, run agent-device snapshot -i -c --json to read rects, compute the target center, press x y, then diff snapshot -i. Coordinates are fallback-only; document why you used them.
Selectors:
Use selectors as positional targets: id="field-email" or label="Allow".
Do not use CSS selectors, pseudo refs, --selector, --text, or raw x/y when refs/selectors exist.
agent-device fill 'id="catalog-search"' "tart" --delay-ms 80
agent-device press 'id="submit-order"'
agent-device is visible 'label="Online"'
agent-device get text 'id="quantity-value"'
Text entry:
fill replaces; type appends to focused field.
agent-device fill @e5 "qa@example.com"
agent-device fill 'id="field-email"' "qa@example.com"
agent-device press 'id="product-note"'
agent-device type "Handle with care" --delay-ms 80
Empty replacement is not a supported clear-field command: do not plan fill <target> "" or fill <target> ''. Prefer a visible clear/reset control; if the app exposes none, report the tool gap instead of inventing a clear command.
Debounced field with no result selector: agent-device wait 1000. Keyboard read-only: keyboard status/get. Blocked control: try keyboard dismiss when supported.
On iOS, prefer keyboard dismiss before manually pressing visible Done; the runner can use safe native keyboard controls and still reports unsupported layouts explicitly. If it returns UNSUPPORTED_OPERATION, prefer a visible app dismiss control, or use back --system only when system navigation is an acceptable side effect.
Search-as-you-type fields on iOS can drop characters when driven too fast; use --delay-ms on fill/type before trying clipboard paste.
iOS Allow Paste prompt cannot be exercised under XCUITest. To test paste-driven app behavior, prefill first with agent-device clipboard write "some text"; test the system prompt manually.
Android Gboard handwriting/stylus UI can capture text in an IME-owned input instead of the app field. If fill reports that input was captured by the keyboard/IME, use the diagnostic targetInput/actualInput details, inspect keyboard status/get if needed, and switch or disable handwriting outside the command plan before retrying. Do not keep retrying fill/type against the same field while the IME owns focus.
Android text entry is owned by agent-device: provider-native text injection when available, then chunk-safe ASCII shell input. Do not switch to raw adb, clipboard, or paste as an agent fallback. If non-ASCII is unsupported in the current backend, report the tool/device gap.
Session ordering:
Stateful commands against one --session must run serially. Do not run open/press/fill/type/scroll/back/alert/replay/batch/close commands in parallel against the same session.
It is fine to parallelize independent read-only collection or commands that use different sessions/devices.
Read-only and waits:
Read-only visible/state question: use snapshot/get/is/find.
agent-device snapshot
agent-device get text 'id="product-title"'
agent-device get attrs @e4
agent-device is visible 'label="Online"'
agent-device wait text "Refreshing metrics..." 3000
agent-device wait 'label="Ready"' 3000
agent-device find "Increment" press --json
For async/list text presence, prefer wait text over is visible when no interaction is needed.
Use snapshot -i only when refs are needed for an action or targeted query.
Ambiguous find: add --first or --last. If info is not visible/exposed, report that gap instead of typing/searching/navigating to reveal it.
Navigation and gestures:
Use scroll for lists; swipe for coordinate gestures/carousels; gesture pan for deliberate drags; gesture fling for fast directional throws.
For raw coordinate gestures, run snapshot -i first and choose a point near the center of the intended app-owned target. Avoid screen edges, tab bars, navigation bars, and home indicators because those areas can trigger system or app navigation instead of the gesture under test.
If app-owned back is ambiguous or has just misrouted, prefer a visible nav/back button ref, tab-bar ref, or deep link over repeated back/system back.
App-owned action sheets, menus, and camera/scan screens are normal UI. After opening one, run snapshot -i or wait for the option, press by label/ref, handle visible permission sheets through UI or platform-supported native alerts, then wait for a concrete result before returning to chat/form state.
Keep count/pause/pattern on one swipe; flags are --count, --pause-ms, --pattern ping-pong.
longpress accepts coordinates, @refs, or selectors. Prefer @ref/selector from snapshot -i; use coordinates only as a fallback when accessibility refs miss the exact target. Duration and gesture scale/center are positional:
agent-device longpress 300 500 800
agent-device longpress @e12 800
agent-device swipe 320 500 40 500 --count 8 --pause-ms 30 --pattern ping-pong
agent-device gesture pan 200 420 0 -80 500
agent-device gesture fling right 200 420 180
agent-device gesture pinch 0.5 200 400
agent-device gesture rotate 35 200 420
agent-device gesture transform 200 420 80 -40 2 35 700
iOS simulator transform uses XCTest gesture primitives; verify app metrics instead of assuming requested degrees map exactly to recognizer output.
Android transform injects a geometric two-finger path; app recognizers may report non-exact pan/scale/rotation. For Android combined transforms, verify qualitative state such as "pan changed yes" / "pinch changed yes" / "rotate changed yes" unless the app explicitly promises exact centroid metrics.
If Android needs exact app-state values, prefer isolated gesture pan, gesture pinch, or gesture rotate commands over one combined transform.
Validation and evidence:
Nearby mutation diff: agent-device diff snapshot -i.
Expected text/selector verification must include the exact text or selector via wait, is, get, or find; bare screenshots/snapshots are insufficient for named expectations.
Prefer provided testIDs/ids/selectors for verification; use visible text when no durable selector is provided.
If task says snapshot, use snapshot. If it asks visual evidence, use screenshot.
Icon/tappable visual proof: screenshot --overlay-refs. Flag is --overlay-refs.
Startup/frame health/CPU/memory: perf --json or metrics. Replay maintenance: replay -u ./flow.ad.
Recording: record start/stop. By default, stop burns touch overlays into the video; use record start --hide-touches for the fastest raw recording. For gesture-heavy iOS simulator proof videos, prefer --hide-touches because overlay timing depends on a stable runner session while gestures are executing. Tracing: trace start ./trace.log, trace stop ./trace.log. Paths are positional.
Stable known flow: batch ./steps.json, not workflow batch.
Inline batch JSON example:
agent-device batch --steps '[{"command":"open","positionals":["settings"],"flags":{}},{"command":"wait","positionals":["100"],"flags":{}}]'
Batch step keys are command, positionals, flags, and optional runtime. Never use args, step, text, or target as batch step fields.
Android animations: settings animations off/on, not animations disable/restore.
Debug logs: logs clear --restart, logs mark, reproduce, then logs path; do not split clear/restart into separate stop/start commands.
Network headers: network dump --include headers; do not write network log headers.
Remote/cloud: connect to discover a cloud profile, or connect --remote-config ./remote-config.json for a local profile; then open, snapshot, disconnect.
macOS menu bar: open ... --platform macos --surface menubar; snapshot -i --platform macos --surface menubar.
React Native dev loop:
JS-only change with Metro connected:
agent-device metro reload
agent-device find "Home"
Do not use agent-device reload. Use open --relaunch for native startup reset.
React Native apps: use help react-native for Metro/Fast Refresh, DevTools routing, and RN-specific blockers; use react-native dismiss-overlay for LogBox/RedBox overlays.
Android RN/Expo Metro: run adb reverse tcp:<port> tcp:<port> before opening the app or URL; it is harmless even if already configured.
Expo Go is a host shell. Use a provided project URL instead of inventing a bundle id; if no URL is provided but a target/app name is provided, open that target and do not inspect project files to find one. On iOS, prefer host + URL when the host shell is known because direct URL open can report success while leaving the runner/shell focused; verify with snapshot -i after opening:
agent-device open "Expo Go" exp://127.0.0.1:8081 --platform ios
agent-device snapshot -i --platform ios
There is no open-url command; use open with the URL target or host + URL form.
Direct iOS URL open remains valid when no host shell is known, but verify that the app UI loaded:
agent-device open exp://127.0.0.1:8081 --platform ios
Android uses the URL target directly; do not write open <app> <url> there:
agent-device open exp://127.0.0.1:8081 --platform android
Android URL/deep-link opens infer the foreground package after launch when possible, so logs/perf can remain package-bound. If perf still says no package is associated, open the host package/app id first, then open the URL in the same session.
If apps lookup misses the project but shows Expo Go/dev-client and a project URL is available, open the URL/host shell; if no URL is available, ask instead of inventing an app id.
Expo Dev Client/development builds: open the installed dev-client app id/name; if a dev-client URL is provided, open that URL next. For Metro setup use metro prepare --kind expo.
Escalate:
help debugging logs, network, alerts, traces, flaky runtime failures
help react-devtools React Native performance, profiling, props/state/hooks, slow renders, rerenders
help react-native React Native app automation hazards, overlays, Metro, and routing
help remote remote/cloud config, tenant, lease, local service tunnels
help macos desktop, frontmost-app, menu bar surfaces
help dogfood exploratory QA report workflow`,
},
debugging: {
summary: 'Targeted failure evidence without dumping stale context',
body: `agent-device help debugging
Use this when behavior fails, hangs, times out, throws alerts, or needs runtime evidence.
Logs:
Keep log windows small. Prefer clear, mark, reproduce, then path.
agent-device logs clear --restart
agent-device logs mark "before diagnostics retry"
agent-device press 'id="load-diagnostics"'
agent-device logs path
Do not cat a full stale log into agent context. Open or grep only the relevant window when needed.
logs clear --restart is the compact command to clear old logs and start a fresh capture; do not split it into logs stop, logs clear, logs start.
On iOS simulators, logs scope by bundle id and resolved app executable, so use this instead of raw simctl log stream predicates.
For iOS simulator launch-time stdout/stderr, use --launch-console on the direct app launch:
agent-device open MyApp --platform ios --relaunch --launch-console ./artifacts/app.console.log
--launch-console is only for direct iOS simulator app launches, not URL opens.
Network:
Use network dump for recent session HTTP traffic parsed from app logs.
agent-device network dump --include headers
agent-device network dump 20 --include all
Use this instead of logs path when the question is request/response metadata.
network log is a supported alias, but network dump --include headers is the clearest plan form. Do not write network log headers.
Alerts:
Native and platform dialogs:
agent-device alert wait 3000
agent-device alert accept
agent-device alert dismiss
Android support is snapshot-derived for runtime permission prompts and native app dialogs. iOS support is runner-derived for XCTest alerts, app-owned modal popups with native blocking markers, and blocking system dialogs. Use cheap alert get for an immediate check; use alert wait <short-ms> only when a prompt may appear after async work.
If alert says no alert but a sheet is visibly on screen, treat it as app-owned UI:
agent-device snapshot -i
agent-device press 'label="Allow"'
Do not use settings permission to answer a dialog already on screen. Reserve settings permission for setup/resetting permission state before a flow.
Diagnostics and traces:
Use --debug for CLI/daemon diagnostic ids and log paths.
Use trace for low-level session diagnostics around one repro:
agent-device trace start ./traces/diagnostics.trace
agent-device press 'id="load-diagnostics"'
agent-device trace stop ./traces/diagnostics.trace
The trace path is positional. Do not use --path for trace start or trace stop.
Stabilizers:
Android animation-sensitive flows:
agent-device settings animations off
agent-device snapshot
agent-device settings animations on
Re-enable settings you changed before finishing.
React Native internals:
If the question is about React Native performance, profiling, props, state, hooks, render causes, slow components, or rerenders, use help react-devtools instead of inferring from screenshots or logs.`,
},
'react-devtools': {
summary: 'React Native performance, profiling, and component internals',
body: `agent-device help react-devtools
Use this for React Native performance/profiling and internals that the accessibility tree cannot expose: components, props, state, hooks, ownership, slow renders, and rerenders.
Core commands:
agent-device react-devtools start
agent-device react-devtools stop
agent-device react-devtools status
agent-device react-devtools wait --connected
agent-device react-devtools wait --component <ComponentName>
agent-device react-devtools count
agent-device react-devtools get tree --depth 3
agent-device react-devtools find <ComponentName>
agent-device react-devtools find <ComponentName> --exact
agent-device react-devtools get component @c5
agent-device react-devtools errors
agent-device react-devtools profile start
agent-device react-devtools profile stop
agent-device react-devtools profile slow --limit 5
agent-device react-devtools profile rerenders --limit 5
agent-device react-devtools profile report @c5
agent-device react-devtools profile timeline --limit 20
agent-device react-devtools profile export profile.json
agent-device react-devtools profile diff before.json after.json --limit 10
Profiling loop:
1. Verify the app is connected: react-devtools status, then wait --connected if needed.
2. If correlating with logs or network, run logs clear --restart before the first logs mark.
3. Start profiling immediately before the interaction.
4. Drive the interaction with normal agent-device commands and mark before/after the repro when timing matters.
5. Stop profiling.
6. Make one bounded first-pass survey: profile stop for the summary, profile slow --limit 5 once, profile rerenders --limit 5 once, and profile timeline --limit 20 only when commit timing matters.
7. Use profile report @cN for targeted render causes and changed props/state/hooks; use get component @cN for current props/state/hooks.
Rules:
Every React DevTools command is an agent-device subcommand: agent-device react-devtools ...
Do not write agent-devtools, agent-react-devtools, or bare react-devtools commands in final command plans.
Start with get tree --depth 3 or find <name>; use find --exact when fuzzy results are noisy.
@c refs reset after reload/remount. After reload, wait --connected and inspect again.
Keep the profile window narrow; unrelated navigation makes render data noisy.
Do not repeatedly raise broad profile slow limits such as --limit 50, --limit 200, or --limit 500. Drill into a specific @c ref with profile report unless you have a specific target that needs more rows.
For network evidence, use agent-device network dump --include headers; headers is not a positional argument.
For cross-platform validation with explicit device selectors, prefer isolated --state-dir and restart react-devtools between platforms.
Remote Android and iOS bridge runs normally through agent-device react-devtools; the CLI keeps the needed local service tunnel alive until agent-device react-devtools stop or disconnect. Expo support depends on the SDK's bundled React Native runtime.
Remote iOS apps attempt the legacy React DevTools websocket during JavaScript startup. If the app was already open before react-devtools start, run open <bundle-id> --platform ios --relaunch, then wait --connected.
Example:
agent-device react-devtools status
agent-device react-devtools wait --connected
agent-device logs clear --restart
agent-device logs mark "before catalog search"
agent-device react-devtools profile start
agent-device fill 'id="catalog-search"' "tart" --delay-ms 80
agent-device logs mark "after catalog search"
agent-device react-devtools profile stop
agent-device react-devtools profile slow --limit 5
agent-device react-devtools profile rerenders --limit 5
agent-device react-devtools profile timeline --limit 20
agent-device react-devtools profile report @c5
agent-device network dump --include headers
Use snapshot, screenshot, logs, network, and perf for device/app runtime evidence. Use react-devtools only when component internals or React rendering behavior matters.`,
},
'react-native': {
summary: 'React Native app automation hazards and routing',
body: `agent-device help react-native
Use this when the target app is React Native, Expo, or a React Native dev client.
This topic covers React Native-specific automation hazards and routes deeper
questions to the owning help topic.
Choose the next help topic:
Generic navigation, selectors, refs, verification, serial commands: help workflow.
Logs, network, diagnostics, traces, permission dialogs, or runtime failures: help debugging.
Component tree, props/state/hooks, slow renders, rerenders, or render causes: help react-devtools.
Remote/cloud config, leases, and local service tunnels: help remote.
React Native dev loop:
For "start from screen X" flows, prefer open --relaunch before the first snapshot so the app does not reuse a prior in-progress navigation state.
JS-only change with Metro connected:
agent-device metro reload
agent-device find "Home"
Do not use agent-device reload. Use open --relaunch for native startup reset.
Android RN/Expo Metro: run adb reverse tcp:<port> tcp:<port> before opening the app or URL; it is harmless even if already configured.
Expo Go/dev clients are host shells. Use provided project URLs, verify with snapshot -i after opening, and ask instead of inventing app ids or URLs. Help workflow owns the full Expo URL command shapes.
Overlays and busy RN UIs:
If snapshot reports a React Native warning/error overlay, handle it before interacting with the app: run agent-device react-native dismiss-overlay, then agent-device snapshot -i -c. Use refs from the new snapshot.
Do not manually press warning/error text bodies, collapsed banner bodies, full-screen warning parents, or broad LogBox/RedBox refs. The dismiss-overlay command owns the narrow LogBox/RedBox targeting policy.
Report the overlay in the final summary. Use screenshot --overlay-refs before dismissing only if visual evidence is required.
If snapshot times out because the UI never becomes idle, Android accessibility may be blocked by busy or continuously changing app UI. After that timeout, use screenshot as visual truth instead of repeatedly retrying snapshots.
Android runtime permission dialogs and native alerts are handled by alert wait/accept/dismiss. If alert reports no alert, treat the visible surface as app-owned UI and use snapshot -i plus press by label/ref.
React DevTools routing:
Keep the agent-device react-devtools prefix on every React DevTools command.
Use help react-devtools for status/wait, component trees, props/state/hooks, profile windows, slow renders, rerenders, and remote bridge rules.
If React DevTools cannot connect, report status and continue with logs, network, perf, screenshot, and trace evidence instead of blocking the whole flow.
Slow-flow investigation:
Keep one named session, start with session list, open, and snapshot -i.
Use help react-devtools for the narrow React profile window.
Use help debugging for logs clear --restart, logs mark, network dump --include headers, perf --json, traces, and runtime failure evidence.
For 15-20s async work, use wait with the exact expected text or selector instead of repeated snapshots.
Report React render offenders separately from network/backend waits and device frame/CPU/memory findings.`,
},
remote: {
summary: 'Remote config, tenant, lease, and remote host flow',
body: `agent-device help remote
Use remote config or the cloud connection profile when a profile owns daemon URL, auth, tenant, run, lease, device scope, and Metro hints. Do not restate those as individual flags unless overriding intentionally.
Cloud profile flow:
agent-device connect
agent-device open com.example.app
agent-device snapshot
agent-device disconnect
Local profile flow:
agent-device connect --remote-config ./remote-config.json
agent-device open com.example.app
agent-device snapshot
agent-device disconnect
Script flow, per-command config:
agent-device open com.example.app --remote-config ./remote-config.json
agent-device snapshot --remote-config ./remote-config.json
agent-device disconnect --remote-config ./remote-config.json
Rules:
connect and disconnect are top-level commands. Do not write agent-device remote connect or agent-device remote disconnect.
Use connect without --remote-config when the cloud control plane owns the connection profile.
Prefer --remote-config over --daemon-base-url, --tenant, --run-id, and --lease-id when using a local profile.
For self-contained scripts, pass the same --remote-config to every operational command, including disconnect; a preceding connect is optional but not required.
For remote artifact installs, use install-from-source <url> or install-from-source --github-actions-artifact org/repo:artifact; do not download CI artifacts locally first.
After connect, let the active remote connection supply runtime hints.
For remote Android and iOS bridge React DevTools, run agent-device react-devtools normally. The CLI opens the needed local service tunnel for the DevTools daemon and keeps it alive until agent-device react-devtools stop or disconnect.
Use --debug when remote connection or transport errors need diagnostic ids and remote log hints.`,
},
macos: {
summary: 'macOS desktop, frontmost-app, and menu bar surfaces',
body: `agent-device help macos
Use macOS only when the task targets desktop apps, desktop surfaces, or menu bar extras.
Open and inspect:
agent-device open TextEdit --platform macos
agent-device snapshot -i --platform macos
Surfaces:
--surface app normal app session
--surface frontmost-app inspect whichever app is frontmost
--surface desktop desktop-wide surface
--surface menubar menu bar extras and menu bar-only apps
Menu bar app example:
agent-device open "Agent Device Tester Menu" --platform macos --surface menubar
agent-device snapshot -i --platform macos --surface menubar
Context menu example:
agent-device click @e66 --button secondary --platform macos
agent-device snapshot -i --platform macos
Rules:
Use open and snapshot -i for menu bar inspection. Do not output inspect as a command.
Context menus are not ambient UI: secondary-click a visible target, then re-snapshot and use the new menu-item refs.
Do not let iOS simulator-set scoping hide macOS desktop targets.
Prefer refs/selectors over raw coordinates.
macOS snapshot rects are window-space; use current refs or overlay refs instead of guessing coordinates.`,
},
dogfood: {
summary: 'Exploratory QA workflow with reproducible evidence',
body: `agent-device help dogfood
Use this when asked to dogfood, exploratory test, bug hunt, QA, or find issues in an app.
Goal:
Find user-visible issues from runtime behavior. Do not read app source or invent findings from code.
Produce a concise report with severity, repro commands, expected/actual behavior, and evidence paths.
Loop:
1. Identify target app/platform; ask only if missing.
2. Create output dirs and open a named session. If auth or OTP is required, sign in or ask the user for the code.
3. Capture baseline snapshot -i and screenshot.
4. Map top-level navigation, then exercise primary flows and edge states.
5. For each issue, capture evidence and write the finding immediately, then continue.
6. Close the session and reconcile the report summary.
Keep stateful commands serial within the same session. Parallel runs can pollute text fields, focus, alerts, and navigation state.
Coverage:
Navigation, forms, empty/error/loading states, offline or retry behavior, permissions, settings, accessibility labels, orientation/keyboard, and obvious performance stalls.
React Native warning/error overlays can be real findings or test blockers. Capture them, use react-native dismiss-overlay if unrelated, re-snapshot, and report them.
Expo Go/dev-client shells: use the provided exp:// or dev-client URL and record whether the shell, project load, or app UI is being tested. On iOS dogfood, prefer agent-device open "Expo Go" <url> when Expo Go is the known shell, then snapshot -i to confirm the project UI rather than the runner splash.
Android RN/Expo Metro: run adb reverse tcp:<port> tcp:<port> before opening the app or URL; it is harmless even if already configured.
Categories: visual, functional, UX, content, performance, diagnostics, permissions, accessibility.
Severity: critical blocks a core flow/data/crashes; high breaks a major feature; medium has friction or workaround; low is polish.
Evidence commands:
mkdir -p ./dogfood-output/screenshots ./dogfood-output/videos ./dogfood-output/traces
agent-device --session qa open <app> --platform ios
agent-device --session qa snapshot -i
agent-device --session qa screenshot ./dogfood-output/screenshots/initial.png
agent-device --session qa screenshot ./dogfood-output/screenshots/issue-001.png --overlay-refs
agent-device --session qa logs clear --restart
agent-device --session qa logs mark "issue-001 repro"
agent-device --session qa logs path
agent-device --session qa record start ./dogfood-output/videos/issue-001.mp4
agent-device --session qa record start ./dogfood-output/videos/benchmark.mp4 --hide-touches
agent-device --session qa record stop
agent-device --session qa close
Evidence rules:
Interactive/behavioral issues need step screenshots and usually a repro video.
Static/on-load issues can use one screenshot; set repro video to N/A.
Use screenshot --overlay-refs when showing the tappable target or broken state helps repro.
Report shape:
./dogfood-output/report.md
Include date, platform, target app, session, scope, severity counts, and issues.
For each finding: ID, severity, category, title, affected flow/screen, repro commands, expected, actual, evidence files, notes.
Target 5-10 well-evidenced issues when available. If no issues are found, report coverage completed and residual risk instead of claiming the app is bug-free.
Rules:
Findings must come from observed runtime behavior, not source reads.
Re-snapshot after each mutation.
Keep commands in the report reproducible; use selectors or refs from fresh snapshots, not guessed coordinates.
Prefer refs for exploration and selectors for deterministic replay.
Use logs, network, screenshot --overlay-refs, trace, perf, or react-devtools only when they add evidence to a specific issue.
Never delete screenshots, videos, traces, or report artifacts during a session.
Escalate to help debugging or help react-devtools when runtime symptoms require those tools.`,
},
} as const satisfies Record<string, { summary: string; body: string }>;
export type HelpTopicName = keyof typeof HELP_TOPICS;
const FLAG_DEFINITIONS: readonly FlagDefinition[] = [
{
key: 'config',
names: ['--config'],
type: 'string',
usageLabel: '--config <path>',
usageDescription: 'Load CLI defaults from a specific config file',
},
{
key: 'remoteConfig',
names: ['--remote-config'],
type: 'string',
usageLabel: '--remote-config <path>',
usageDescription: 'Load remote host + Metro workflow settings from a specific profile file',
},
{
key: 'stateDir',
names: ['--state-dir'],
type: 'string',
usageLabel: '--state-dir <path>',
usageDescription: 'Daemon state directory (defaults to ~/.agent-device)',
},
{
key: 'daemonBaseUrl',
names: ['--daemon-base-url'],
type: 'string',
usageLabel: '--daemon-base-url <url>',
usageDescription: 'Explicit remote HTTP daemon base URL (skip local daemon discovery/startup)',
},
{
key: 'daemonAuthToken',
names: ['--daemon-auth-token'],
type: 'string',
usageLabel: '--daemon-auth-token <token>',
usageDescription: 'Remote HTTP daemon auth token (sent as request token and bearer header)',
},
{
key: 'daemonTransport',
names: ['--daemon-transport'],
type: 'enum',
enumValues: ['auto', 'socket', 'http'],
usageLabel: '--daemon-transport auto|socket|http',
usageDescription: 'Daemon client transport preference',
},
{
key: 'daemonServerMode',
names: ['--daemon-server-mode'],
type: 'enum',
enumValues: ['socket', 'http', 'dual'],
usageLabel: '--daemon-server-mode socket|http|dual',
usageDescription: 'Daemon server mode used when spawning daemon',
},
{
key: 'tenant',
names: ['--tenant'],
type: 'string',
usageLabel: '--tenant <id>',
usageDescription: 'Tenant scope identifier for isolated daemon sessions',
},
{
key: 'sessionIsolation',
names: ['--session-isolation'],
type: 'enum',
enumValues: ['none', 'tenant'],
usageLabel: '--session-isolation none|tenant',
usageDescription: 'Session isolation strategy (tenant prefixes session namespace)',
},
{
key: 'runId',
names: ['--run-id'],
type: 'string',
usageLabel: '--run-id <id>',
usageDescription: 'Run identifier used for tenant lease admission checks',
},
{
key: 'leaseId',
names: ['--lease-id'],
type: 'string',
usageLabel: '--lease-id <id>',
usageDescription: 'Lease identifier bound to tenant/run admission scope',
},
{
key: 'leaseBackend',
names: ['--lease-backend'],
type: 'enum',
enumValues: ['ios-simulator', 'ios-instance', 'android-instance'],
usageLabel: '--lease-backend ios-simulator|ios-instance|android-instance',
usageDescription: 'Lease backend for remote tenant connection admission',
},
{
key: 'force',
names: ['--force'],
type: 'boolean',
usageLabel: '--force',
usageDescription: 'Force connection state replacement when reconnecting',
},
{
key: 'noLogin',
names: ['--no-login'],
type: 'boolean',
usageLabel: '--no-login',
usageDescription: 'Connect: fail instead of starting implicit cloud login',
},
{
key: 'sessionLock',
names: ['--session-lock'],
type: 'enum',
enumValues: ['reject', 'strip'],
usageLabel: '--session-lock reject|strip',
usageDescription:
'Lock bound-session device routing for this CLI invocation and nested batch steps',
},
{
key: 'sessionLocked',
names: ['--session-locked'],
type: 'boolean',
usageLabel: '--session-locked',
usageDescription: 'Deprecated alias for --session-lock reject',
},
{
key: 'sessionLockConflicts',
names: ['--session-lock-conflicts'],
type: 'enum',
enumValues: ['reject', 'strip'],
usageLabel: '--session-lock-conflicts reject|strip',
usageDescription: 'Deprecated alias for --session-lock',
},
{
key: 'platform',
names: ['--platform'],
type: 'enum',
enumValues: ['ios', 'macos', 'android', 'linux', 'apple'],
usageLabel: '--platform ios|macos|android|linux|apple',
usageDescription: 'Platform to target (`apple` aliases the Apple automation backend)',
},
{
key: 'target',
names: ['--target'],
type: 'enum',
enumValues: ['mobile', 'tv', 'desktop'],
usageLabel: '--target mobile|tv|desktop',
usageDescription: 'Device target class to match',
},
{
key: 'device',
names: ['--device'],
type: 'string',
usageLabel: '--device <name>',
usageDescription: 'Device name to target',
},
{
key: 'udid',
names: ['--udid'],
type: 'string',
usageLabel: '--udid <udid>',
usageDescription: 'iOS device UDID',
},
{
key: 'serial',
names: ['--serial'],
type: 'string',
usageLabel: '--serial <serial>',
usageDescription: 'Android device serial',
},
{
key: 'surface',
names: ['--surface'],
type: 'enum',
enumValues: SESSION_SURFACES,
usageLabel: '--surface app|frontmost-app|desktop|menubar',
usageDescription: 'macOS session surface for open (defaults to app)',
},
{
key: 'headless',
names: ['--headless'],
type: 'boolean',
usageLabel: '--headless',
usageDescription: 'Boot: launch Android emulator without a GUI window',
},
{
key: 'metroHost',
names: ['--metro-host'],
type: 'string',
usageLabel: '--metro-host <host>',
usageDescription: 'Session-scoped Metro/debug host hint',
},
{
key: 'metroPort',
names: ['--metro-port'],
type: 'int',
min: 1,
max: 65535,
usageLabel: '--metro-port <port>',
usageDescription: 'Session-scoped Metro/debug port hint',
},
{
key: 'metroProjectRoot',
names: ['--project-root'],
type: 'string',
usageLabel: '--project-root <path>',
usageDescription: 'metro prepare: React Native project root (default: cwd)',
},
{
key: 'metroKind',
names: ['--kind'],
type: 'enum',
enumValues: ['auto', 'react-native', 'expo'],
usageLabel: '--kind auto|react-native|expo',
usageDescription: 'metro prepare: detect or force the Metro launcher kind',
},
{
key: 'metroPublicBaseUrl',
names: ['--public-base-url'],
type: 'string',
usageLabel: '--public-base-url <url>',
usageDescription: 'metro prepare: public base URL used for direct bundle hints',
},
{
key: 'metroProxyBaseUrl',
names: ['--proxy-base-url'],
type: 'string',
usageLabel: '--proxy-base-url <url>',
usageDescription: 'metro prepare: optional bridge origin for remote Metro access',
},
{
key: 'metroBearerToken',
names: ['--bearer-token'],
type: 'string',
usageLabel: '--bearer-token <token>',
usageDescription:
'metro prepare: host bridge bearer token (or AGENT_DEVICE_METRO_BEARER_TOKEN; falls back to AGENT_DEVICE_DAEMON_AUTH_TOKEN)',
},
{
key: 'metroPreparePort',
names: ['--port'],
type: 'int',
min: 1,
max: 65535,
usageLabel: '--port <port>',
usageDescription: 'metro prepare: local Metro port (default: 8081)',
},
{
key: 'metroListenHost',
names: ['--listen-host'],
type: 'string',
usageLabel: '--listen-host <host>',
usageDescription: 'metro prepare: host Metro listens on (default: 0.0.0.0)',
},
{
key: 'metroStatusHost',
names: ['--status-host'],
type: 'string',
usageLabel: '--status-host <host>',
usageDescription: 'metro prepare: host used for local /status polling (default: 127.0.0.1)',
},
{
key: 'metroStartupTimeoutMs',
names: ['--startup-timeout-ms'],
type: 'int',
min: 1,
usageLabel: '--startup-timeout-ms <ms>',
usageDescription: 'metro prepare: timeout while waiting for Metro to become ready',
},
{
key: 'metroProbeTimeoutMs',
names: ['--probe-timeout-ms'],
type: 'int',
min: 1,
usageLabel: '--probe-timeout-ms <ms>',
usageDescription: 'metro prepare: timeout for /status and proxy bridge calls',
},
{
key: 'metroRuntimeFile',
names: ['--runtime-file'],
type: 'string',
usageLabel: '--runtime-file <path>',
usageDescription: 'metro prepare: optional file path to persist the JSON result',
},
{
key: 'metroNoReuseExisting',
names: ['--no-reuse-existing'],
type: 'boolean',
usageLabel: '--no-reuse-existing',
usageDescription: 'metro prepare: always start a fresh Metro process',
},
{
key: 'metroNoInstallDeps',
names: ['--no-install-deps'],
type: 'boolean',
usageLabel: '--no-install-deps',
usageDescription: 'metro prepare: skip package-manager install when node_modules is missing',
},
{
key: 'bundleUrl',
names: ['--bundle-url'],
type: 'string',
usageLabel: '--bundle-url <url>',
usageDescription: 'Session-scoped bundle URL hint',
},
{
key: 'launchUrl',
names: ['--launch-url'],
type: 'string',
usageLabel: '--launch-url <url>',
usageDescription: 'Session-scoped deep link / launch URL hint',
},
{
key: 'iosSimulatorDeviceSet',
names: ['--ios-simulator-device-set'],
type: 'string',
usageLabel: '--ios-simulator-device-set <path>',
usageDescription: 'Scope iOS simulator discovery/commands to this simulator device set',
},
{
key: 'androidDeviceAllowlist',
names: ['--android-device-allowlist'],
type: 'string',
usageLabel: '--android-device-allowlist <serials>',
usageDescription: 'Comma/space separated Android serial allowlist for discovery/selection',
},
{
key: 'activity',
names: ['--activity'],
type: 'string',
usageLabel: '--activity <component>',
usageDescription: 'Android app launch activity (package/Activity); not for URL opens',
},
{
key: 'launchConsole',
names: ['--launch-console'],
type: 'string',
usageLabel: '--launch-console <path>',
usageDescription: 'open: capture the initial iOS simulator launch console window to a file',
},
{