agentchrome 1.51.0

A CLI tool for browser automation via the Chrome DevTools Protocol
Documentation
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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
#![allow(clippy::doc_markdown)]
// Items used by the binary crate may appear unused from the library crate's perspective.
#![allow(dead_code)]

use std::path::PathBuf;

use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
use clap_complete::Shell;

use crate::coords::CoordValue;

#[derive(Parser)]
#[command(
    name = "agentchrome",
    version,
    about = "Browser automation via the Chrome DevTools Protocol",
    long_about = "agentchrome is a command-line tool for browser automation via the Chrome DevTools \
        Protocol (CDP). It provides subcommands for connecting to Chrome/Chromium instances, \
        managing tabs, navigating pages, inspecting the DOM, executing JavaScript, monitoring \
        console output, intercepting network requests, simulating user interactions, filling forms, \
        emulating devices, and collecting performance metrics.\n\n\
        Designed for AI agents and shell scripting, every subcommand produces structured JSON \
        output on stdout and structured JSON errors on stderr. Global flags control connection \
        settings, output format, and target tab selection.",
    after_long_help = "\
QUICK START:
  # Connect to a running Chrome instance
  agentchrome connect

  # Launch a new headless Chrome and connect
  agentchrome connect --launch --headless

  # List open tabs and navigate to a URL
  agentchrome tabs list
  agentchrome navigate https://example.com

  # Take a full-page screenshot
  agentchrome page screenshot --full-page --file shot.png

  # Execute JavaScript and get the result
  agentchrome js exec \"document.title\"

  # Capture the accessibility tree and fill a form field
  agentchrome page snapshot
  agentchrome form fill s5 \"hello@example.com\"

  # Monitor console output in real time
  agentchrome console follow --timeout 5000

EXIT CODES:
  0  Success
  1  General error (invalid arguments, internal failure)
  2  Connection error (Chrome not running, session expired)
  3  Target error (tab not found, no page targets)
  4  Timeout error (navigation or trace timeout)
  5  Protocol error (CDP protocol failure, dialog handling error)

ERROR HANDLING:
  Every non-zero exit emits exactly one JSON object on stderr. The stable
  shape is:
    {\"error\": \"<human-readable message>\", \"code\": <1..5>}
  Some paths enrich the payload with optional fields (stable fields remain
  present) — for example form fill against a non-fillable element:
    {\"error\": \"...\", \"code\": 1, \"kind\": \"not_fillable\",
     \"element_type\": {\"tag\": \"div\", \"role\": null},
     \"suggested_alternatives\": [\"'agentchrome interact click'\"]}
  Exit-code meanings: 0=success, 1=general, 2=connection, 3=target,
  4=timeout, 5=protocol. Success-path stdout remains JSON and stderr stays
  empty; stderr is reserved for the error object only.

ENVIRONMENT VARIABLES:
  AGENTCHROME_PORT     CDP port number (default: 9222)
  AGENTCHROME_HOST     CDP host address (default: 127.0.0.1)
  AGENTCHROME_TIMEOUT  Default command timeout in milliseconds
  AGENTCHROME_CONFIG   Path to configuration file",
    term_width = 100
)]
pub struct Cli {
    #[command(flatten)]
    pub global: GlobalOpts,

    #[command(subcommand)]
    pub command: Command,
}

#[derive(Args)]
pub struct GlobalOpts {
    /// Chrome DevTools Protocol port number [default: 9222]
    #[arg(long, global = true, env = "AGENTCHROME_PORT")]
    pub port: Option<u16>,

    /// Chrome DevTools Protocol host address
    #[arg(
        long,
        default_value = "127.0.0.1",
        global = true,
        env = "AGENTCHROME_HOST"
    )]
    pub host: String,

    /// Direct WebSocket URL (overrides --host and --port)
    #[arg(long, global = true)]
    pub ws_url: Option<String>,

    /// Command timeout in milliseconds
    #[arg(long, global = true, env = "AGENTCHROME_TIMEOUT")]
    pub timeout: Option<u64>,

    /// Target tab ID (defaults to the active tab)
    #[arg(long, global = true)]
    pub tab: Option<String>,

    /// Explicit page target ID (bypasses session state; conflicts with --tab)
    #[arg(long, global = true, conflicts_with = "tab")]
    pub page_id: Option<String>,

    /// Automatically dismiss any dialogs that appear during command execution
    #[arg(long, global = true)]
    pub auto_dismiss_dialogs: bool,

    /// Path to configuration file (overrides default search)
    #[arg(long, global = true, env = "AGENTCHROME_CONFIG")]
    pub config: Option<PathBuf>,

    /// WebSocket keep-alive interval in milliseconds (default: 30000; 0 disables)
    #[arg(
        long = "keepalive-interval",
        value_name = "MS",
        value_parser = clap::value_parser!(u64),
        env = "AGENTCHROME_KEEPALIVE_INTERVAL",
        global = true,
        conflicts_with = "no_keepalive"
    )]
    pub keepalive_interval: Option<u64>,

    /// Disable WebSocket keep-alive pings entirely
    #[arg(long = "no-keepalive", global = true)]
    pub no_keepalive: bool,

    #[command(flatten)]
    pub output: OutputFormat,
}

impl GlobalOpts {
    /// Returns the port if explicitly provided, or the default (9222).
    /// Default CDP port when none is explicitly provided.
    const DEFAULT_PORT: u16 = 9222;

    #[must_use]
    pub fn port_or_default(&self) -> u16 {
        self.port.unwrap_or(Self::DEFAULT_PORT)
    }
}

#[allow(clippy::struct_excessive_bools)]
#[derive(Args)]
pub struct OutputFormat {
    /// Output as compact JSON (mutually exclusive with --pretty, --plain)
    #[arg(long, global = true, conflicts_with_all = ["pretty", "plain"])]
    pub json: bool,

    /// Output as pretty-printed JSON (mutually exclusive with --json, --plain)
    #[arg(long, global = true, conflicts_with_all = ["json", "plain"])]
    pub pretty: bool,

    /// Output as human-readable plain text (mutually exclusive with --json, --pretty)
    #[arg(long, global = true, conflicts_with_all = ["json", "pretty"])]
    pub plain: bool,

    /// Byte threshold for large-response detection (default: 16384)
    #[arg(long, global = true, value_parser = parse_nonzero_usize)]
    pub large_response_threshold: Option<usize>,
}

fn parse_nonzero_usize(s: &str) -> Result<usize, String> {
    let val: usize = s.parse().map_err(|e| format!("{e}"))?;
    if val == 0 {
        return Err("threshold must be greater than 0".to_string());
    }
    Ok(val)
}

/// Session file path on Unix platforms.
pub const SESSION_FILE_PATH_UNIX: &str = "~/.agentchrome/session.json";

/// Session file path on Windows (literal `%USERPROFILE%` placeholder so the
/// help text matches what users see in PowerShell / cmd).
pub const SESSION_FILE_PATH_WINDOWS: &str = "%USERPROFILE%\\.agentchrome\\session.json";

/// Ordered connection-source precedence, highest → lowest. Shared between the
/// `connect --help` long-form text and the `agentchrome capabilities` manifest
/// so the two surfaces cannot drift.
pub const CONNECTION_PRECEDENCE: &[&str] = &[
    "--ws-url",
    "--port",
    "AGENTCHROME_PORT",
    "session.json",
    "default port 9222",
];

#[derive(Subcommand)]
pub enum Command {
    /// Connect to or launch a Chrome instance
    #[command(
        long_about = "Connect to a running Chrome/Chromium instance via the Chrome DevTools \
            Protocol, or launch a new one. Tests the connection and prints browser metadata \
            (browser version, WebSocket URL, user agent). The session is persisted to a \
            local file so subsequent commands reuse the same connection.",
        after_long_help = "\
SESSION FILE:
  Connection state is persisted to a per-user session file so subsequent
  CLI invocations can reuse the same Chrome automatically:

    Unix:    ~/.agentchrome/session.json
    Windows: %USERPROFILE%\\.agentchrome\\session.json

RESOLUTION PRECEDENCE (highest → lowest):
  1. --ws-url
  2. --port
  3. AGENTCHROME_PORT (env var)
  4. session.json
  5. default port 9222

EXAMPLES:
  # Connect to Chrome on the default port (9222)
  agentchrome connect

  # Launch a new headless Chrome instance
  agentchrome connect --launch --headless

  # Cross-invocation auto-discovery (no flags in shell B):
  #   shell A
  agentchrome connect --launch --headless
  #   shell B, later
  agentchrome tabs list

  # Connect to a specific port
  agentchrome connect --port 9333

  # Check connection status (exits 0 whether or not a session exists)
  agentchrome connect --status

  # Disconnect and remove session file
  agentchrome connect --disconnect

  # Run a long command with a custom keep-alive interval
  agentchrome --keepalive-interval 60000 console follow

  # Disable keep-alive entirely
  agentchrome --no-keepalive page snapshot --json"
    )]
    Connect(ConnectArgs),

    /// Tab management (list, create, close, activate)
    #[command(
        long_about = "Tab management commands: list open tabs, create new tabs, close tabs, and \
            activate (focus) a specific tab. Each operation returns structured JSON with tab IDs \
            and metadata.",
        after_long_help = "\
EXAMPLES:
  # List all open tabs
  agentchrome tabs list

  # Open a new tab and get its ID
  agentchrome tabs create https://example.com

  # Close tabs by ID
  agentchrome tabs close ABC123 DEF456

  # Activate a specific tab
  agentchrome tabs activate ABC123"
    )]
    Tabs(TabsArgs),

    /// URL navigation and history
    #[command(
        long_about = "Navigate to URLs, reload pages, go back/forward in history, and wait for \
            navigation events. Supports waiting for load, DOMContentLoaded, or network idle.",
        after_long_help = "\
EXAMPLES:
  # Navigate to a URL and wait for page load
  agentchrome navigate https://example.com

  # Navigate and wait for network idle
  agentchrome navigate https://example.com --wait-until networkidle

  # Go back in browser history
  agentchrome navigate back

  # Reload the current page, bypassing cache
  agentchrome navigate reload --ignore-cache"
    )]
    Navigate(NavigateArgs),

    /// Page inspection (screenshot, text, accessibility tree, find)
    #[command(
        long_about = "Inspect the current page: capture screenshots (full page or element), \
            extract visible text, dump the accessibility tree, or search for text/elements on \
            the page.",
        after_long_help = "\
EXAMPLES:
  # Extract all visible text from the page
  agentchrome page text

  # Capture the accessibility tree (assigns UIDs to elements)
  agentchrome page snapshot

  # Take a full-page screenshot
  agentchrome page screenshot --full-page --file page.png

  # Find elements by text
  agentchrome page find \"Sign in\"

  # Resize the viewport
  agentchrome page resize 1280x720"
    )]
    Page(PageArgs),

    /// DOM inspection and manipulation
    #[command(
        long_about = "Query and manipulate the DOM: select elements by CSS selector or XPath, \
            get/set attributes and text, read outerHTML, inspect computed styles, navigate the \
            element tree, and remove elements. Target elements by node ID (from 'dom select'), \
            snapshot UID (from 'page snapshot'), or CSS selector (prefixed with 'css:').",
        after_long_help = "\
EXAMPLES:
  # Select elements by CSS selector
  agentchrome dom select \"h1\"

  # Select by XPath
  agentchrome dom select \"//a[@href]\" --xpath

  # Get an element's attribute
  agentchrome dom get-attribute s3 href

  # Read element text
  agentchrome dom get-text css:h1

  # Set an attribute
  agentchrome dom set-attribute s5 class \"highlight\"

  # View the DOM tree
  agentchrome dom tree --depth 3"
    )]
    Dom(DomArgs),

    /// JavaScript execution in page context
    #[command(
        long_about = "Execute JavaScript expressions or scripts in the page context. Returns \
            the result as structured JSON. Supports both synchronous expressions and async \
            functions.",
        after_long_help = "\
EXAMPLES:
  # Get the page title
  agentchrome js exec \"document.title\"

  # Execute a script file
  agentchrome js exec --file script.js

  # Run code on a specific element (by UID from snapshot)
  agentchrome js exec --uid s3 \"(el) => el.textContent\"

  # Read from stdin
  echo 'document.URL' | agentchrome js exec -"
    )]
    Js(JsArgs),

    /// Console message reading and monitoring
    #[command(
        long_about = "Read and monitor browser console messages (log, warn, error, info). \
            Can capture existing messages or stream new messages in real time.",
        after_long_help = "\
EXAMPLES:
  # Read recent console messages
  agentchrome console read

  # Show only error messages
  agentchrome console read --errors-only

  # Stream console messages in real time
  agentchrome console follow

  # Stream errors for 10 seconds
  agentchrome console follow --errors-only --timeout 10000"
    )]
    Console(ConsoleArgs),

    /// Network request monitoring and interception
    #[command(
        long_about = "Monitor and intercept network requests. List recent requests, filter by \
            URL pattern or resource type, capture request/response bodies, and stream requests \
            in real time.",
        after_long_help = "\
EXAMPLES:
  # List recent network requests
  agentchrome network list

  # Filter by resource type
  agentchrome network list --type xhr,fetch

  # Get details of a specific request
  agentchrome network get 42

  # Stream network requests in real time
  agentchrome network follow --url api.example.com"
    )]
    Network(NetworkArgs),

    /// Mouse, keyboard, and scroll interactions
    #[command(
        long_about = "Simulate user interactions: click elements, type text, press key \
            combinations, scroll the page, hover over elements, and perform drag-and-drop \
            operations. Target elements by UID (from 'page snapshot') or CSS selector \
            (prefixed with 'css:').",
        after_long_help = "\
EXAMPLES:
  # Click an element by UID
  agentchrome interact click s5

  # Click by CSS selector
  agentchrome interact click css:#submit-btn

  # Type text into the focused element
  agentchrome interact type \"Hello, world!\"

  # Press a key combination
  agentchrome interact key Control+A

  # Scroll down one viewport height
  agentchrome interact scroll"
    )]
    Interact(InteractArgs),

    /// Form input and submission
    #[command(
        long_about = "Fill in form fields, select dropdown options, toggle checkboxes, and clear \
            fields. Supports targeting fields by UID (from accessibility snapshot) or CSS \
            selector (prefixed with 'css:'). Run 'page snapshot' first to discover field UIDs.",
        after_long_help = "\
EXAMPLES:
  # Fill a field by UID (from page snapshot)
  agentchrome form fill s5 \"hello@example.com\"

  # Fill by CSS selector
  agentchrome form fill css:#email \"user@example.com\"

  # Fill multiple fields at once
  agentchrome form fill-many '[{\"target\":\"s5\",\"value\":\"Alice\"},{\"target\":\"s7\",\"value\":\"alice@example.com\"}]'

  # Clear a field
  agentchrome form clear s5

  # Upload a file
  agentchrome form upload s10 ./photo.jpg"
    )]
    Form(FormArgs),

    /// Device and network emulation
    #[command(
        long_about = "Emulate different devices, screen sizes, and network conditions. Set \
            custom user agents, viewport dimensions, device scale factor, and network throttling \
            profiles.",
        after_long_help = "\
EXAMPLES:
  # Emulate a mobile device
  agentchrome emulate set --viewport 375x667 --device-scale 2 --mobile

  # Simulate slow 3G network
  agentchrome emulate set --network 3g

  # Force dark mode
  agentchrome emulate set --color-scheme dark

  # Check current emulation settings
  agentchrome emulate status

  # Clear all emulation overrides
  agentchrome emulate reset"
    )]
    Emulate(EmulateArgs),

    /// Performance tracing and metrics
    #[command(
        long_about = "Collect performance metrics, capture trace files, measure page load timing, \
            and analyze runtime performance. Outputs metrics as structured JSON for analysis.",
        after_long_help = "\
EXAMPLES:
  # Quick Core Web Vitals measurement
  agentchrome perf vitals

  # Record a trace until Ctrl+C
  agentchrome perf record

  # Record a trace for 5 seconds
  agentchrome perf record --duration 5000

  # Record with page reload
  agentchrome perf record --reload --duration 5000

  # Analyze a trace for render-blocking resources
  agentchrome perf analyze RenderBlocking --trace-file trace.json"
    )]
    Perf(PerfArgs),

    /// Browser cookie management (list, set, delete, clear)
    #[command(
        long_about = "Manage browser cookies via the Chrome DevTools Protocol. List cookies \
            for the current page or all cookies, set new cookies with optional flags, delete \
            specific cookies by name, or clear all cookies. Provides full access to HttpOnly \
            and Secure cookies that are not accessible via document.cookie.",
        after_long_help = "\
EXAMPLES:
  # List cookies for the current page
  agentchrome cookie list

  # List all cookies (not scoped to current URL)
  agentchrome cookie list --all

  # List cookies filtered by domain
  agentchrome cookie list --domain example.com

  # Set a cookie
  agentchrome cookie set session_id abc123 --domain example.com

  # Set a secure, HttpOnly cookie with expiry
  agentchrome cookie set token xyz --domain example.com --secure --http-only --same-site Strict --expires 1735689600

  # Delete a specific cookie
  agentchrome cookie delete session_id --domain example.com

  # Clear all cookies
  agentchrome cookie clear"
    )]
    Cookie(CookieArgs),

    /// Browser dialog handling (alert, confirm, prompt, beforeunload)
    #[command(
        long_about = "Detect and handle browser JavaScript dialogs (alert, confirm, prompt, \
            beforeunload). Query whether a dialog is open, accept or dismiss it, and provide \
            prompt text. Useful for automation scripts that need to respond to dialogs \
            programmatically.",
        after_long_help = "\
EXAMPLES:
  # Check if a dialog is open
  agentchrome dialog info

  # Accept an alert or confirm dialog
  agentchrome dialog handle accept

  # Dismiss a dialog
  agentchrome dialog handle dismiss

  # Accept a prompt with text
  agentchrome dialog handle accept --text \"my input\""
    )]
    Dialog(DialogArgs),

    /// Media element control (list, play, pause, seek)
    #[command(
        long_about = "Discover and control HTML5 audio and video elements on the current page. \
            List all media elements with playback state, play/pause individual elements, seek to \
            a specific time or to the end of the media. Supports targeting by index or CSS \
            selector, bulk operations with --all, and frame-scoped media control with --frame.",
        after_long_help = "\
EXAMPLES:
  # List all media elements on the page
  agentchrome media list

  # Play a media element by index
  agentchrome media play 0

  # Pause a media element
  agentchrome media pause 0

  # Seek to 15.5 seconds
  agentchrome media seek 0 15.5

  # Seek all media elements to end (skip narration gates)
  agentchrome media seek-end --all

  # List media elements inside an iframe
  agentchrome media --frame 0 list

  # Play a media element by CSS selector
  agentchrome media play css:audio.narration"
    )]
    Media(MediaArgs),

    /// Run audits against the current page (requires lighthouse CLI — see 'audit lighthouse --help')
    #[command(
        long_about = "Run external audits against the current browser page. Currently supports \
            Google Lighthouse for measuring performance, accessibility, SEO, best practices, \
            and PWA scores. Connects Lighthouse to the managed Chrome session via the CDP port \
            and returns structured JSON category scores on stdout. Requires the `lighthouse` \
            CLI (see 'audit lighthouse --help' for installation).",
        after_long_help = "\
EXAMPLES:
  # Run a full Lighthouse audit on the current page
  agentchrome audit lighthouse

  # Audit a specific URL
  agentchrome audit lighthouse https://example.com

  # Only measure performance and accessibility
  agentchrome audit lighthouse --only performance,accessibility

  # Save the full Lighthouse report to a file
  agentchrome audit lighthouse --output-file report.json"
    )]
    Audit(AuditArgs),

    /// Pre-automation challenge scan (iframes, overlays, media gates, frameworks, patterns)
    #[command(
        long_about = "Scan a page for automation challenges — iframes, overlay blockers, shadow \
            DOM, canvas/WebGL rendering, media playback gates, and framework-specific interaction \
            quirks — plus named-pattern matches (e.g., Storyline acc-blocker, SCORM player, React \
            portal) with actionable agentchrome command suggestions. Accepts a URL to \
            navigate-then-analyze, or `--current` to analyze the already-loaded page in place.\n\n\
            OUTPUT SCHEMA (JSON on stdout):\n\
              {\n\
                \"url\": string,\n\
                \"scope\": \"diagnosed\" | \"current\",\n\
                \"challenges\": [{category, severity, summary, details, suggestion?}],\n\
                \"patterns\":   [{name, matched, confidence, evidence, suggestion}],\n\
                \"summary\":    {challengeCount, patternMatchCount, hasHighSeverity, straightforward}\n\
              }\n\n\
            EXIT CODES: 0 success; 1 general/arg errors; 2 connection; 3 target; 4 timeout; \
            5 protocol.\n\n\
            Note: <url> and --current are mutually exclusive. Exactly one must be provided.",
        after_long_help = "\
EXAMPLES:
  # Navigate to a URL and diagnose it for automation challenges
  agentchrome diagnose https://example.com/course

  # Diagnose the already-loaded page in the active tab (no navigation)
  agentchrome diagnose --current

  # Extract strategy suggestions using jq
  agentchrome diagnose --current | jq -r '.patterns[].suggestion'

  # Diagnose with network-idle wait strategy
  agentchrome diagnose https://app.example.com --wait-until networkidle"
    )]
    Diagnose(DiagnoseArgs),

    /// Agentic tool skill installation and management
    #[command(
        long_about = "Install, update, uninstall, or list agentchrome skill files for agentic \
            coding tools (Claude Code, Windsurf, Aider, Continue.dev, GitHub Copilot, Cursor, \
            Gemini CLI, Codex). \
            The skill file is a minimal signpost that tells the AI agent what agentchrome is \
            and how to discover its capabilities via the CLI's built-in help system. Auto-detects \
            the active agentic environment, or use --tool to target a specific tool.",
        after_long_help = "\
EXAMPLES:
  # Auto-detect and install
  agentchrome skill install

  # Install for a specific tool
  agentchrome skill install --tool claude-code

  # Install for Codex
  agentchrome skill install --tool codex

  # List supported tools and installation status
  agentchrome skill list

  # Update installed skill to current version
  agentchrome skill update --tool claude-code

  # Remove an installed skill
  agentchrome skill uninstall --tool claude-code"
    )]
    Skill(SkillArgs),

    /// Configuration file management (show, init, path)
    #[command(
        long_about = "Manage the agentchrome configuration file. Show the resolved configuration \
            from all sources, create a default config file, or display the active config file path. \
            Config files use TOML format and are searched in priority order: --config flag, \
            $AGENTCHROME_CONFIG env var, project-local, XDG config dir, home directory.",
        after_long_help = "\
EXAMPLES:
  # Show the resolved configuration
  agentchrome config show

  # Create a default config file
  agentchrome config init

  # Create a config at a custom path
  agentchrome config init --path ./my-config.toml

  # Show the active config file path
  agentchrome config path"
    )]
    Config(ConfigArgs),

    /// Generate shell completion scripts
    #[command(
        long_about = "Generate shell completion scripts for tab-completion of commands, flags, \
            and enum values. Pipe the output to the appropriate file for your shell.",
        after_long_help = "\
EXAMPLES:
  # Bash
  agentchrome completions bash > /etc/bash_completion.d/agentchrome

  # Zsh
  agentchrome completions zsh > ~/.zfunc/_agentchrome

  # Fish
  agentchrome completions fish > ~/.config/fish/completions/agentchrome.fish

  # PowerShell
  agentchrome completions powershell >> $PROFILE

  # Elvish
  agentchrome completions elvish >> ~/.elvish/rc.elv"
    )]
    Completions(CompletionsArgs),

    /// Show usage examples for commands
    #[command(
        long_about = "Show usage examples for agentchrome commands. Without arguments, lists all \
            command groups with a brief description and one example each. With a command name, \
            shows detailed examples for that specific command group. With \"strategies\" as the \
            first positional, shows scenario-based interaction strategy guides — use \
            `agentchrome examples strategies` to list all guides, or \
            `agentchrome examples strategies <name>` to see the full guide for one strategy.",
        after_long_help = "\
EXAMPLES:
  # List all command groups with summary examples
  agentchrome examples

  # Show detailed examples for the navigate command
  agentchrome examples navigate

  # List all interaction strategy guides
  agentchrome examples strategies

  # Show the iframe strategy guide
  agentchrome examples strategies iframes

  # Get all strategies as JSON (for programmatic use)
  agentchrome examples strategies --json

  # Get a single strategy as JSON
  agentchrome examples strategies iframes --json

  # Pretty-printed JSON output
  agentchrome examples --pretty"
    )]
    Examples(ExamplesArgs),

    /// Output a machine-readable manifest of all CLI capabilities
    #[command(
        long_about = "Output a machine-readable manifest of agentchrome CLI capabilities. \
            Without arguments, returns a lightweight listing (name + description per command) \
            for cheap discovery. With a command name, returns the full detail descriptor for \
            that command — subcommands, args, flags, types. The manifest is generated at \
            runtime from the clap command tree, so it is always in sync with the binary.",
        after_long_help = "\
EXAMPLES:
  # Summary listing of all commands (progressive disclosure)
  agentchrome capabilities

  # Summary listing as JSON
  agentchrome capabilities --json

  # Full detail for one command
  agentchrome capabilities page

  # Full detail for one command, as JSON
  agentchrome capabilities page --json

  # Pretty-printed for readability
  agentchrome capabilities --pretty"
    )]
    Capabilities(CapabilitiesArgs),

    /// Display man pages for agentchrome commands
    #[command(
        long_about = "Display man pages for agentchrome commands. Without arguments, displays \
            the main agentchrome man page. With a subcommand name, displays the man page for \
            that specific command. Output is in roff format, suitable for piping to a pager.",
        after_long_help = "\
EXAMPLES:
  # Display the main agentchrome man page
  agentchrome man

  # Display the man page for the connect command
  agentchrome man connect

  # Display the man page for the tabs command
  agentchrome man tabs

  # Pipe to a pager
  agentchrome man navigate | less"
    )]
    Man(ManArgs),

    /// Execute a batch script of agentchrome commands
    #[command(
        long_about = "Execute a JSON batch script composed of agentchrome commands, conditional \
            branches, and loops against the active CDP session. The script runs sequentially \
            and emits a structured JSON result array on stdout. Use 'script run -' to read \
            a script from stdin.\n\n\
            SCRIPT FORMAT (JSON v1):\n\
              { \"commands\": [\n\
                { \"cmd\": [\"navigate\", \"https://example.com\"] },\n\
                { \"cmd\": [\"js\", \"exec\", \"document.title\"], \"bind\": \"title\" },\n\
                { \"if\": \"$vars.title.includes('Example')\",\n\
                  \"then\": [{ \"cmd\": [\"page\", \"snapshot\"] }],\n\
                  \"else\": [] },\n\
                { \"loop\": { \"count\": 3 }, \"body\": [{ \"cmd\": [\"interact\", \"click\", \"s1\"] }] }\n\
              ] }",
        after_long_help = "\
EXAMPLES:
  # Run a script file
  agentchrome script run workflow.json

  # Read a script from stdin
  echo '{\"commands\":[{\"cmd\":[\"navigate\",\"https://example.com\"]}]}' | agentchrome script run -

  # Stop at the first failure
  agentchrome script run --fail-fast workflow.json

  # Validate a script without dispatching any commands
  agentchrome script run --dry-run workflow.json

  # Chain navigate and js exec to extract the page title
  agentchrome script run - <<'EOF'
{\"commands\":[
  {\"cmd\":[\"navigate\",\"https://example.com\"]},
  {\"cmd\":[\"js\",\"exec\",\"document.title\"],\"bind\":\"title\"}
]}
EOF"
    )]
    Script(ScriptArgs),
}

// =============================================================================
// Script subcommand types
// =============================================================================

/// Arguments for the `script` command group.
#[derive(Args)]
pub struct ScriptArgs {
    #[command(subcommand)]
    pub sub: ScriptSubcommand,
}

/// Script subcommands.
#[derive(Subcommand)]
pub enum ScriptSubcommand {
    /// Run a JSON batch script
    #[command(
        long_about = "Run a JSON batch script against the active CDP session. The script file \
            should contain a JSON object with a 'commands' array. Each command is an argv-style \
            array of strings. Use '-' as the file path to read from stdin.\n\n\
            Supports conditional branching (if/then/else), count and while loops, and \
            variable binding (bind: \"name\") with $vars.name substitution in later steps.",
        after_long_help = "\
EXAMPLES:
  # Run a script file
  agentchrome script run workflow.json

  # Read a script from stdin (- means stdin)
  cat workflow.json | agentchrome script run -

  # Stop at the first failing step and exit non-zero
  agentchrome script run --fail-fast workflow.json

  # Validate the script without dispatching commands (offline)
  agentchrome script run --dry-run workflow.json

  # Pretty-print the result JSON
  agentchrome script run --pretty workflow.json"
    )]
    Run(ScriptRunArgs),
}

/// Arguments for `script run`.
#[derive(Args)]
pub struct ScriptRunArgs {
    /// Path to a JSON script file (`-` reads from stdin)
    #[arg(
        value_name = "FILE",
        help = "Path to a JSON script file; use '-' to read from stdin"
    )]
    pub file: String,

    /// Stop at the first failing step and exit with code 1
    #[arg(
        long,
        help = "Stop execution at the first failing step and exit non-zero"
    )]
    pub fail_fast: bool,

    /// Validate the script without dispatching any commands to Chrome
    #[arg(
        long,
        help = "Parse and validate the script without executing any commands"
    )]
    pub dry_run: bool,
}

/// Chrome release channel to use when launching.
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum ChromeChannel {
    Stable,
    Canary,
    Beta,
    Dev,
}

/// Arguments for the `tabs` subcommand group.
#[derive(Args)]
pub struct TabsArgs {
    #[command(subcommand)]
    pub command: TabsCommand,
}

/// Tab management subcommands.
#[derive(Subcommand)]
pub enum TabsCommand {
    /// List open tabs
    #[command(
        long_about = "List all open browser tabs. Returns JSON with each tab's ID, title, URL, \
            and type. By default, only page tabs are shown; use --all to include internal \
            Chrome pages (chrome://, chrome-extension://).",
        after_long_help = "\
EXAMPLES:
  # List page tabs
  agentchrome tabs list

  # Include internal Chrome pages
  agentchrome tabs list --all"
    )]
    List(TabsListArgs),

    /// Create a new tab
    #[command(
        long_about = "Create a new browser tab. Optionally specify a URL to open; defaults to \
            about:blank. Returns JSON with the new tab's ID and URL. Use --background to open \
            the tab without switching focus to it.",
        after_long_help = "\
EXAMPLES:
  # Open a blank tab
  agentchrome tabs create

  # Open a URL
  agentchrome tabs create https://example.com

  # Open in the background
  agentchrome tabs create https://example.com --background"
    )]
    Create(TabsCreateArgs),

    /// Close one or more tabs
    #[command(
        long_about = "Close one or more browser tabs by their IDs. Accepts multiple tab IDs \
            as arguments. Returns JSON confirming which tabs were closed. Cannot close the \
            last remaining tab (Chrome requires at least one open tab).",
        after_long_help = "\
EXAMPLES:
  # Close a single tab
  agentchrome tabs close ABC123

  # Close multiple tabs
  agentchrome tabs close ABC123 DEF456 GHI789"
    )]
    Close(TabsCloseArgs),

    /// Activate (focus) a tab
    #[command(
        long_about = "Activate (bring to front) a specific browser tab by its ID. The tab \
            becomes the active target for subsequent commands. Returns JSON confirming the \
            activated tab.",
        after_long_help = "\
EXAMPLES:
  # Activate a tab by ID
  agentchrome tabs activate ABC123

  # Activate silently
  agentchrome tabs activate ABC123 --quiet"
    )]
    Activate(TabsActivateArgs),
}

/// Arguments for `tabs list`.
#[derive(Args)]
pub struct TabsListArgs {
    /// Include internal Chrome pages (chrome://, chrome-extension://)
    #[arg(long)]
    pub all: bool,
}

/// Arguments for `tabs create`.
#[derive(Args)]
pub struct TabsCreateArgs {
    /// URL to open (defaults to about:blank)
    pub url: Option<String>,

    /// Open the tab in the background without activating it
    #[arg(long)]
    pub background: bool,
}

/// Arguments for `tabs close`.
#[derive(Args)]
pub struct TabsCloseArgs {
    /// Tab ID(s) or index(es) to close
    pub targets: Vec<String>,

    #[arg(long, hide = true, action = ArgAction::Append, value_name = "ID")]
    pub tab: Vec<String>,
}

/// Arguments for `tabs activate`.
#[derive(Args)]
pub struct TabsActivateArgs {
    /// Tab ID or index to activate
    pub target: String,

    /// Suppress output after activation
    #[arg(long)]
    pub quiet: bool,
}

/// Arguments for the `navigate` subcommand group.
#[derive(Args)]
#[command(args_conflicts_with_subcommands = true)]
pub struct NavigateArgs {
    #[command(subcommand)]
    pub command: Option<NavigateCommand>,

    #[command(flatten)]
    pub url_args: NavigateUrlArgs,
}

/// Navigate subcommands.
#[derive(Subcommand)]
pub enum NavigateCommand {
    /// Go back in browser history
    #[command(
        long_about = "Navigate back one step in the browser's session history, equivalent to \
            clicking the browser's back button. Returns JSON with the new URL after navigation.",
        after_long_help = "\
EXAMPLES:
  # Go back
  agentchrome navigate back"
    )]
    Back,

    /// Go forward in browser history
    #[command(
        long_about = "Navigate forward one step in the browser's session history, equivalent to \
            clicking the browser's forward button. Only works if the user previously navigated \
            back. Returns JSON with the new URL after navigation.",
        after_long_help = "\
EXAMPLES:
  # Go forward
  agentchrome navigate forward"
    )]
    Forward,

    /// Reload the current page
    #[command(
        long_about = "Reload the current page. Use --ignore-cache to bypass the browser cache \
            and force a full reload from the server. Returns JSON with the page URL after reload.",
        after_long_help = "\
EXAMPLES:
  # Reload the page
  agentchrome navigate reload

  # Reload bypassing cache
  agentchrome navigate reload --ignore-cache"
    )]
    Reload(NavigateReloadArgs),
}

/// Arguments for direct URL navigation (`navigate <URL>`).
#[derive(Args)]
pub struct NavigateUrlArgs {
    /// URL to navigate to
    pub url: Option<String>,

    /// Wait strategy after navigation
    #[arg(long, value_enum, default_value_t = WaitUntil::Load)]
    pub wait_until: WaitUntil,

    /// Navigation timeout in milliseconds
    #[arg(long)]
    pub timeout: Option<u64>,

    /// Bypass the browser cache
    #[arg(long)]
    pub ignore_cache: bool,

    /// Wait for a CSS selector to appear after the page loads.
    /// Useful for SPA sites where content renders asynchronously after the load event.
    /// Example: --wait-for-selector "div.email-list"
    #[arg(long)]
    pub wait_for_selector: Option<String>,
}

/// Arguments for `navigate reload`.
#[derive(Args)]
pub struct NavigateReloadArgs {
    /// Bypass the browser cache on reload
    #[arg(long)]
    pub ignore_cache: bool,
}

/// Arguments for the `page` subcommand group.
#[derive(Args)]
pub struct PageArgs {
    /// Target frame by index, path (1/0), or 'auto'
    #[arg(long)]
    pub frame: Option<String>,

    #[command(subcommand)]
    pub command: PageCommand,
}

/// Page inspection subcommands.
#[derive(Subcommand)]
pub enum PageCommand {
    /// Extract visible text from the page
    #[command(
        long_about = "Extract the visible text content from the current page or a specific \
            element. Returns the text as a plain string. Useful for reading page content \
            without HTML markup.",
        after_long_help = "\
EXAMPLES:
  # Get all visible text
  agentchrome page text

  # Get text from a specific element
  agentchrome page text --selector \"#main-content\""
    )]
    Text(PageTextArgs),

    /// Capture the accessibility tree of the page
    #[command(
        long_about = "Capture the accessibility tree (AX tree) of the current page. Each \
            interactive element is assigned a UID (e.g., s1, s2, s3) that can be used with \
            'interact', 'form', and 'js exec --uid' commands. Use --verbose to include \
            additional properties like checked, disabled, and level.",
        after_long_help = "\
EXAMPLES:
  # Capture the accessibility tree
  agentchrome page snapshot

  # Verbose output with extra properties
  agentchrome page snapshot --verbose

  # Save to a file
  agentchrome page snapshot --file snapshot.txt"
    )]
    Snapshot(PageSnapshotArgs),

    /// Find elements by text, CSS selector, or accessibility role
    #[command(
        long_about = "Search for elements on the page by text content, CSS selector, or \
            accessibility role. Returns matching elements with their UIDs, roles, and names. \
            By default, performs a case-insensitive substring match; use --exact for exact \
            matching.",
        after_long_help = "\
EXAMPLES:
  # Find elements by text
  agentchrome page find \"Sign in\"

  # Find by CSS selector
  agentchrome page find --selector \"button.primary\"

  # Find by accessibility role
  agentchrome page find --role button

  # Exact text match with limit
  agentchrome page find \"Submit\" --exact --limit 1"
    )]
    Find(PageFindArgs),

    /// Capture a screenshot of the page, an element, or a region
    #[command(
        long_about = "Capture a screenshot of the current page, a specific element, or a \
            viewport region. Supports PNG (default), JPEG, and WebP formats. Use --full-page \
            to capture the entire scrollable page, --selector or --uid to capture a specific \
            element, or --clip to capture a region. Note: --full-page conflicts with \
            --selector, --uid, and --clip.",
        after_long_help = "\
EXAMPLES:
  # Screenshot the visible viewport
  agentchrome page screenshot --file shot.png

  # Full-page screenshot
  agentchrome page screenshot --full-page --file full.png

  # Screenshot a specific element by UID
  agentchrome page screenshot --uid s3 --file element.png

  # JPEG format with quality
  agentchrome page screenshot --format jpeg --quality 80 --file shot.jpg"
    )]
    Screenshot(PageScreenshotArgs),

    /// Resize the viewport to the given dimensions
    #[command(
        long_about = "Resize the browser viewport to the specified dimensions. The size is \
            given as WIDTHxHEIGHT in pixels (e.g., 1280x720). Useful for testing responsive \
            layouts. See also: 'emulate set --viewport' for device emulation.",
        after_long_help = "\
EXAMPLES:
  # Resize to 1280x720
  agentchrome page resize 1280x720

  # Mobile viewport
  agentchrome page resize 375x667"
    )]
    Resize(PageResizeArgs),

    /// Query a single element's properties by UID or CSS selector
    #[command(
        long_about = "Query a single element's state by accessibility UID (from 'page snapshot') \
            or CSS selector. Returns role, name, tag name, bounding box, accessibility properties, \
            and viewport visibility as JSON.",
        after_long_help = "\
EXAMPLES:
  # Query by UID
  agentchrome page element s10

  # Query by CSS selector
  agentchrome page element \"css:#checkout\"

  # Plain text output
  agentchrome page element s10 --plain"
    )]
    Element(PageElementArgs),

    /// Wait until a condition is met on the current page
    #[command(
        arg_required_else_help = true,
        long_about = "Wait until a specified condition is met on the current page. Supports \
            waiting for a URL to match a glob pattern, text to appear, a CSS selector to match, \
            network activity to settle, or a JavaScript expression to evaluate to truthy. \
            Exactly one condition must be specified. The command blocks until the condition is \
            satisfied or the timeout is reached.",
        after_long_help = "\
EXAMPLES:
  # Wait for URL to match a glob pattern
  agentchrome page wait --url \"*/dashboard*\"

  # Wait for text to appear
  agentchrome page wait --text \"Products\"

  # Wait for a CSS selector to match
  agentchrome page wait --selector \"#results-table\"

  # Wait for at least 5 elements to match a selector
  agentchrome page wait --selector \".item\" --count 5

  # Wait for network to settle
  agentchrome page wait --network-idle

  # Wait for a JavaScript expression to become truthy
  agentchrome page wait --js-expression \"document.querySelector('.btn').disabled === false\"

  # Wait for audio element to finish playing
  agentchrome page wait --js-expression \"document.querySelector('audio').ended\"

  # Custom timeout and poll interval
  agentchrome page wait --text \"loaded\" --timeout 5000 --interval 200"
    )]
    Wait(PageWaitArgs),

    /// List all frames (iframes, framesets) in the page hierarchy
    #[command(
        long_about = "List all frames in the current page, including iframes and frameset frames. \
            Returns a JSON array with each frame's index, ID, URL, name, security origin, \
            dimensions, and nesting depth. Use the index with --frame on other commands to target \
            a specific frame.",
        after_long_help = "\
EXAMPLES:
  # List all frames
  agentchrome page frames

  # Pretty-printed output
  agentchrome page --pretty frames"
    )]
    Frames,

    /// List all workers (service, shared, dedicated) associated with the page
    #[command(
        long_about = "List all workers associated with the current page, including Service Workers, \
            Shared Workers, and dedicated Web Workers. Returns a JSON array with each worker's \
            index, target ID, type, script URL, and status.",
        after_long_help = "\
EXAMPLES:
  # List all workers
  agentchrome page workers"
    )]
    Workers,

    /// Hit test at viewport coordinates to identify click targets and overlays
    #[command(
        name = "hittest",
        long_about = "Hit test at the given viewport coordinates to identify which element \
            receives a click event. Returns the actual hit target, any intercepting overlay \
            elements, and the full z-index stack at those coordinates. Useful for debugging \
            failed click interactions caused by invisible overlays.",
        after_long_help = "\
EXAMPLES:
  # Hit test at viewport coordinates
  agentchrome page hittest 100 200

  # Hit test within a specific iframe
  agentchrome page hittest 50 50 --frame 1"
    )]
    HitTest(PageHitTestArgs),

    /// Analyze page structure: iframes, frameworks, overlays, media, shadow DOM
    #[command(
        long_about = "Analyze the structural composition of the current page. Returns a JSON \
            report covering iframe hierarchy, detected frontend frameworks, interactive element \
            counts, media elements, overlay blockers, and shadow DOM presence. Useful for \
            understanding an unfamiliar page before choosing an automation strategy.",
        after_long_help = "\
EXAMPLES:
  # Analyze current page
  agentchrome page analyze

  # Analyze within a specific iframe
  agentchrome page analyze --frame 1"
    )]
    Analyze,

    /// Resolve a selector to frame-local and page-global coordinates
    #[command(
        long_about = "Resolve a CSS selector or snapshot UID to frame-local and page-global \
            bounding-box and center coordinates. Returns a JSON object with 'frame', \
            'frameLocal', 'page', and 'frameOffset' fields. Useful for verifying what \
            coordinates a subsequent 'interact click-at --relative-to' would dispatch, \
            and for diagnosing coordinate mismatches when working with iframes.\n\n\
            Selector forms:\n  \
              css:#submit     — standard CSS selector (prefix 'css:')\n  \
              s7              — snapshot UID from a prior 'page snapshot' run\n\n\
            Use --frame to resolve in a specific iframe's coordinate space.",
        after_long_help = "\
EXAMPLES:
  # Resolve coordinates for a main-frame element
  agentchrome page coords --selector \"css:#submit\"

  # Resolve coordinates for an element inside frame 1
  agentchrome page coords --frame 1 --selector \"css:#inner\"

  # Resolve by snapshot UID
  agentchrome page coords --selector s7"
    )]
    Coords(PageCoordsArgs),
}

/// Image format for screenshots.
#[derive(Debug, Clone, Copy, ValueEnum, Default)]
pub enum ScreenshotFormat {
    /// PNG (lossless, default)
    #[default]
    Png,
    /// JPEG (lossy)
    Jpeg,
    /// WebP (lossy)
    Webp,
}

/// Arguments for `page screenshot`.
#[derive(Args)]
pub struct PageScreenshotArgs {
    /// Capture the entire scrollable page, not just the visible viewport
    #[arg(long)]
    pub full_page: bool,

    /// Capture a specific element by CSS selector (conflicts with --full-page)
    #[arg(long)]
    pub selector: Option<String>,

    /// Capture a specific element by UID from 'page snapshot' (conflicts with --full-page)
    #[arg(long)]
    pub uid: Option<String>,

    /// CSS selector for the inner scrollable element (requires --full-page)
    #[arg(long)]
    pub scroll_container: Option<String>,

    /// Image format [default: png] [possible values: png, jpeg, webp]
    #[arg(long, value_enum, default_value_t = ScreenshotFormat::Png)]
    pub format: ScreenshotFormat,

    /// JPEG/WebP compression quality, 0-100 (ignored for PNG)
    #[arg(long, value_parser = clap::value_parser!(u8).range(0..=100))]
    pub quality: Option<u8>,

    /// Save screenshot to a file instead of base64-encoded stdout
    #[arg(long)]
    pub file: Option<PathBuf>,

    /// Capture a specific viewport region as X,Y,WIDTH,HEIGHT (e.g. 10,20,200,100)
    #[arg(long)]
    pub clip: Option<String>,
}

/// Arguments for `page text`.
#[derive(Args)]
pub struct PageTextArgs {
    /// CSS selector to extract text from a specific element
    #[arg(long)]
    pub selector: Option<String>,

    /// Extract text from main frame, all iframes, and all open shadow DOM roots (mutually exclusive with --frame)
    #[arg(long)]
    pub deep: bool,
}

/// Arguments for `page snapshot`.
#[allow(clippy::struct_excessive_bools)]
#[derive(Args)]
pub struct PageSnapshotArgs {
    /// Include additional element properties (checked, disabled, level, etc.)
    #[arg(long)]
    pub verbose: bool,

    /// Save snapshot to file instead of stdout
    #[arg(long)]
    pub file: Option<PathBuf>,

    /// Return only interactive and semantically meaningful elements (reduces token usage for AI agents)
    #[arg(long)]
    pub compact: bool,

    /// Include shadow DOM content in the accessibility tree
    #[arg(long)]
    pub pierce_shadow: bool,

    /// Aggregate every iframe's accessibility tree into a single tree (mutually exclusive with --frame)
    #[arg(long)]
    pub include_iframes: bool,
}

/// Arguments for `page find`.
#[derive(Args)]
pub struct PageFindArgs {
    /// Text to search for (searches accessible names, text content, labels)
    pub query: Option<String>,

    /// Find by CSS selector instead of text
    #[arg(long)]
    pub selector: Option<String>,

    /// Filter by accessibility role (button, link, textbox, etc.)
    #[arg(long)]
    pub role: Option<String>,

    /// Require exact text match (default: case-insensitive substring)
    #[arg(long)]
    pub exact: bool,

    /// Maximum results to return
    #[arg(long, default_value_t = 10)]
    pub limit: usize,
}

/// Arguments for the `perf` subcommand group.
#[derive(Args)]
pub struct PerfArgs {
    #[command(subcommand)]
    pub command: PerfCommand,
}

/// Performance tracing subcommands.
#[derive(Subcommand)]
pub enum PerfCommand {
    /// Record a performance trace (long-running, stops on Ctrl+C or --duration)
    #[command(
        long_about = "Record a performance trace in a single long-running session. The trace \
            captures JavaScript execution, layout, paint, network, and other browser activity. \
            Recording continues until you press Ctrl+C or the --duration timeout elapses. \
            Use --reload to reload the page before recording. The trace is saved to a JSON \
            file that can be opened in Chrome DevTools or analyzed with 'perf analyze'.",
        after_long_help = "\
EXAMPLES:
  # Record until Ctrl+C
  agentchrome perf record

  # Record for 5 seconds
  agentchrome perf record --duration 5000

  # Record with page reload
  agentchrome perf record --reload --duration 5000

  # Save to a specific file
  agentchrome perf record --file my-trace.json"
    )]
    Record(PerfRecordArgs),

    /// Analyze a specific performance insight from a trace
    #[command(
        long_about = "Analyze a previously saved trace file for a specific performance insight. \
            Available insights: DocumentLatency (document request timing), LCPBreakdown (Largest \
            Contentful Paint phases), RenderBlocking (render-blocking resources), LongTasks \
            (JavaScript tasks > 50ms). Returns structured JSON with the analysis results.",
        after_long_help = "\
EXAMPLES:
  # Analyze LCP breakdown
  agentchrome perf analyze LCPBreakdown --trace-file trace.json

  # Find render-blocking resources
  agentchrome perf analyze RenderBlocking --trace-file trace.json

  # Identify long tasks
  agentchrome perf analyze LongTasks --trace-file trace.json"
    )]
    Analyze(PerfAnalyzeArgs),

    /// Quick Core Web Vitals measurement
    #[command(
        long_about = "Perform a quick Core Web Vitals measurement. Automatically starts a \
            trace, reloads the page, collects vitals (LCP, FID, CLS), and stops the trace. \
            Returns structured JSON with the web vitals metrics.",
        after_long_help = "\
EXAMPLES:
  # Measure web vitals
  agentchrome perf vitals

  # Save the underlying trace file
  agentchrome perf vitals --file vitals-trace.json"
    )]
    Vitals(PerfVitalsArgs),
}

/// Arguments for `perf record`.
#[derive(Args)]
pub struct PerfRecordArgs {
    /// Reload the page before recording
    #[arg(long)]
    pub reload: bool,
    /// Auto-stop after this many milliseconds
    #[arg(long)]
    pub duration: Option<u64>,
    /// Path to save the trace file (default: auto-generated)
    #[arg(long)]
    pub file: Option<PathBuf>,
}

/// Arguments for `perf analyze`.
#[derive(Args)]
pub struct PerfAnalyzeArgs {
    /// Insight to analyze: DocumentLatency, LCPBreakdown, RenderBlocking, LongTasks
    pub insight: String,
    /// Path to a previously saved trace JSON file
    #[arg(long)]
    pub trace_file: PathBuf,
}

/// Arguments for `perf vitals`.
#[derive(Args)]
pub struct PerfVitalsArgs {
    /// Path to save the trace file (default: auto-generated temp)
    #[arg(long)]
    pub file: Option<PathBuf>,
}

/// Arguments for the `js` subcommand group.
#[derive(Args)]
pub struct JsArgs {
    /// Target frame by index, path (1/0), or 'auto'
    #[arg(long)]
    pub frame: Option<String>,

    #[command(subcommand)]
    pub command: JsCommand,
}

/// JavaScript subcommands.
#[derive(Subcommand)]
pub enum JsCommand {
    /// Execute JavaScript in the page context
    #[command(
        long_about = "Execute a JavaScript expression or script in the page context and return \
            the result as JSON. Code can be provided as an inline argument, via --code (recommended \
            for cross-platform quoting), read from a file with --file, or piped via stdin using \
            '--stdin' or '-'. When --uid is specified, the code is wrapped in a function that \
            receives the element as its first argument. By default, promise results are awaited; \
            use --no-await to return immediately.",
        after_long_help = "\
EXAMPLES:
  # Evaluate an expression
  agentchrome js exec \"document.title\"

  # Use --code for cross-platform quoting (recommended on Windows)
  agentchrome js exec --code \"document.querySelector('div')\"

  # Execute a script file
  agentchrome js exec --file script.js

  # Run code on a specific element
  agentchrome js exec --uid s3 \"(el) => el.textContent\"

  # Read from stdin
  echo 'document.URL' | agentchrome js exec --stdin

  # Legacy stdin syntax (also works)
  echo 'document.URL' | agentchrome js exec -

  # Skip awaiting promises
  agentchrome js exec --no-await \"fetch('/api/data')\""
    )]
    Exec(JsExecArgs),
}

/// Arguments for `js exec`.
#[derive(Args)]
pub struct JsExecArgs {
    /// JavaScript code to execute (use '-' to read from stdin)
    #[arg(conflicts_with_all = ["file", "code_flag", "stdin"])]
    pub code: Option<String>,

    /// JavaScript code as a named argument (avoids shell quoting issues on Windows)
    #[arg(long = "code", id = "code_flag", conflicts_with_all = ["code", "file", "stdin"])]
    pub code_flag: Option<String>,

    /// Read JavaScript code from stdin
    #[arg(long, conflicts_with_all = ["code", "code_flag", "file"])]
    pub stdin: bool,

    /// Read JavaScript from a file instead of inline argument
    #[arg(long, conflicts_with_all = ["code", "code_flag", "stdin"])]
    pub file: Option<PathBuf>,

    /// Element UID from 'page snapshot'; code is wrapped in a function receiving the element
    #[arg(long)]
    pub uid: Option<String>,

    /// Return promise objects without awaiting them
    #[arg(long, action = ArgAction::SetTrue)]
    pub no_await: bool,

    /// Execution timeout in milliseconds (overrides global --timeout)
    #[arg(long)]
    pub timeout: Option<u64>,

    /// Truncate result output exceeding this size in bytes
    #[arg(long)]
    pub max_size: Option<usize>,

    /// Worker index from 'page workers' for executing JS in a worker context
    #[arg(long)]
    pub worker: Option<u32>,
}

/// Arguments for the `cookie` subcommand group.
#[derive(Args)]
pub struct CookieArgs {
    #[command(subcommand)]
    pub command: CookieCommand,
}

/// Cookie subcommands.
#[derive(Subcommand)]
pub enum CookieCommand {
    /// List cookies for the current page or all cookies
    #[command(
        long_about = "List cookies associated with the current page. By default, returns cookies \
            scoped to the current page's URLs. Use --all to list all browser cookies regardless \
            of URL. Use --domain to filter by a specific domain.",
        after_long_help = "\
EXAMPLES:
  # List cookies for the current page
  agentchrome cookie list

  # List all cookies
  agentchrome cookie list --all

  # Filter by domain
  agentchrome cookie list --domain example.com"
    )]
    List(CookieListArgs),

    /// Set a browser cookie
    #[command(
        long_about = "Set a browser cookie with the given name and value. The --domain flag is \
            strongly recommended to scope the cookie correctly. Additional flags control path, \
            security attributes, SameSite policy, and expiry time.",
        after_long_help = "\
EXAMPLES:
  # Set a basic cookie
  agentchrome cookie set session_id abc123 --domain example.com

  # Set a secure, HttpOnly cookie
  agentchrome cookie set token xyz --domain example.com --secure --http-only

  # Set a cookie with SameSite and expiry
  agentchrome cookie set prefs dark --domain example.com --same-site Lax --expires 1735689600"
    )]
    Set(CookieSetArgs),

    /// Delete a specific cookie by name
    #[command(
        long_about = "Delete a cookie by name. Use --domain to scope the deletion to a specific \
            domain when multiple cookies share the same name across different domains.",
        after_long_help = "\
EXAMPLES:
  # Delete a cookie by name
  agentchrome cookie delete session_id

  # Delete a cookie scoped to a specific domain
  agentchrome cookie delete session_id --domain example.com"
    )]
    Delete(CookieDeleteArgs),

    /// Clear all cookies
    #[command(
        long_about = "Remove all browser cookies. Returns the number of cookies that were cleared.",
        after_long_help = "\
EXAMPLES:
  # Clear all cookies
  agentchrome cookie clear"
    )]
    Clear,
}

/// Arguments for `cookie list`.
#[derive(Args)]
pub struct CookieListArgs {
    /// Filter cookies by domain
    #[arg(long)]
    pub domain: Option<String>,

    /// List all cookies (not scoped to current URL)
    #[arg(long)]
    pub all: bool,
}

/// Arguments for `cookie set`.
#[derive(Args)]
pub struct CookieSetArgs {
    /// Cookie name
    pub name: String,

    /// Cookie value
    pub value: String,

    /// Cookie domain (strongly recommended)
    #[arg(long)]
    pub domain: Option<String>,

    /// Cookie path
    #[arg(long, default_value = "/")]
    pub path: String,

    /// Set cookie as Secure (HTTPS only)
    #[arg(long)]
    pub secure: bool,

    /// Set cookie as HttpOnly (not accessible via JavaScript)
    #[arg(long)]
    pub http_only: bool,

    /// SameSite attribute: Strict, Lax, or None
    #[arg(long, value_name = "POLICY")]
    pub same_site: Option<String>,

    /// Expiry as Unix timestamp (seconds since epoch)
    #[arg(long)]
    pub expires: Option<f64>,

    #[arg(long, hide = true, conflicts_with = "domain", value_name = "URL")]
    pub url: Option<String>,
}

/// Arguments for `cookie delete`.
#[derive(Args)]
pub struct CookieDeleteArgs {
    /// Cookie name to delete
    pub name: String,

    /// Scope deletion to a specific domain
    #[arg(long)]
    pub domain: Option<String>,
}

/// Arguments for the `dialog` subcommand group.
#[derive(Args)]
pub struct DialogArgs {
    #[command(subcommand)]
    pub command: DialogCommand,
}

/// Dialog subcommands.
#[derive(Subcommand)]
pub enum DialogCommand {
    /// Accept or dismiss the current browser dialog
    #[command(
        long_about = "Accept or dismiss the currently open browser dialog (alert, confirm, \
            prompt, or beforeunload). A dialog must be open before this command can be used. \
            For prompt dialogs, use --text to provide the response text when accepting.",
        after_long_help = "\
EXAMPLES:
  # Accept an alert
  agentchrome dialog handle accept

  # Dismiss a confirm dialog
  agentchrome dialog handle dismiss

  # Accept a prompt with text
  agentchrome dialog handle accept --text \"my response\""
    )]
    Handle(DialogHandleArgs),

    /// Check whether a dialog is currently open
    #[command(
        long_about = "Check whether a JavaScript dialog (alert, confirm, prompt, or \
            beforeunload) is currently open. Returns JSON with the dialog's type, message, \
            and default prompt text if applicable. Returns {\"open\": false} when no dialog \
            is present.",
        after_long_help = "\
EXAMPLES:
  # Check for open dialog
  agentchrome dialog info"
    )]
    Info,
}

/// Arguments for `dialog handle`.
#[derive(Args)]
pub struct DialogHandleArgs {
    /// Action to take: accept or dismiss
    pub action: DialogAction,

    /// Response text for prompt dialogs (only used with 'accept' action)
    #[arg(long)]
    pub text: Option<String>,
}

/// Action to take on a browser dialog.
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum DialogAction {
    /// Accept (OK) the dialog
    Accept,
    /// Dismiss (Cancel) the dialog
    Dismiss,
}

/// Arguments for the `media` subcommand group.
#[derive(Args)]
pub struct MediaArgs {
    /// Target frame by index, path (1/0), or 'auto'
    #[arg(long)]
    pub frame: Option<String>,

    #[command(subcommand)]
    pub command: MediaCommand,
}

/// Media subcommands.
#[derive(Subcommand)]
pub enum MediaCommand {
    /// List all audio and video elements on the page
    #[command(
        long_about = "Enumerate all HTML5 <audio> and <video> elements on the page and return \
            their playback state. Each element includes its index (for targeting), tag type, \
            source URLs, duration, current time, playback state, mute/volume status, and \
            readyState. Returns an empty array if no media elements exist.",
        after_long_help = "\
EXAMPLES:
  # List all media elements
  agentchrome media list

  # List media in a specific iframe
  agentchrome media --frame 0 list"
    )]
    List,

    /// Play a media element
    #[command(
        long_about = "Start playback of a media element identified by index (from 'media list') \
            or CSS selector (prefixed with 'css:'). Returns the updated playback state as JSON. \
            Use --all to play all media elements on the page.",
        after_long_help = "\
EXAMPLES:
  # Play by index
  agentchrome media play 0

  # Play by CSS selector
  agentchrome media play css:audio.narration

  # Play all media elements
  agentchrome media play --all"
    )]
    Play(MediaTargetArgs),

    /// Pause a media element
    #[command(
        long_about = "Pause playback of a media element identified by index or CSS selector. \
            Returns the updated playback state as JSON. Use --all to pause all media elements.",
        after_long_help = "\
EXAMPLES:
  # Pause by index
  agentchrome media pause 0

  # Pause all media elements
  agentchrome media pause --all"
    )]
    Pause(MediaTargetArgs),

    /// Seek a media element to a specific time
    #[command(
        long_about = "Set the current playback position of a media element to a specific time \
            in seconds. The time is clamped to the element's duration by the browser. Returns \
            the updated playback state as JSON. Use --all with --time to seek all elements.",
        after_long_help = "\
EXAMPLES:
  # Seek to 15.5 seconds
  agentchrome media seek 0 15.5

  # Seek all elements to 10 seconds
  agentchrome media seek --all --time 10.0"
    )]
    Seek(MediaSeekArgs),

    /// Seek a media element to its end (duration)
    #[command(
        long_about = "Set the current playback position of a media element to its total duration, \
            effectively ending playback. This is the primary use case for skipping audio narration \
            gates in SCORM courses. Returns the updated playback state as JSON. Use --all to seek \
            all media elements to their end.",
        after_long_help = "\
EXAMPLES:
  # Seek a specific element to end
  agentchrome media seek-end 0

  # Seek all media elements to end
  agentchrome media seek-end --all"
    )]
    SeekEnd(MediaTargetArgs),
}

/// Arguments for media commands that target a single element or all elements.
#[derive(Args)]
pub struct MediaTargetArgs {
    /// Media element index (from 'media list') or CSS selector (prefixed with 'css:')
    pub target: Option<String>,

    /// Apply to all media elements on the page
    #[arg(long, conflicts_with = "target")]
    pub all: bool,
}

/// Arguments for `media seek`.
#[derive(Args)]
pub struct MediaSeekArgs {
    /// Media element index or CSS selector (not required with --all)
    pub target: Option<String>,

    /// Time in seconds to seek to (positional for single target, --time for --all)
    #[arg(conflicts_with = "time")]
    pub time_pos: Option<f64>,

    /// Apply to all media elements on the page
    #[arg(long, conflicts_with = "target")]
    pub all: bool,

    /// Time in seconds to seek to (use with --all)
    #[arg(long, conflicts_with = "time_pos")]
    pub time: Option<f64>,
}

/// Arguments for the `audit` subcommand group.
#[derive(Args)]
pub struct AuditArgs {
    #[command(subcommand)]
    pub command: AuditCommand,
}

/// Audit subcommands.
#[derive(Subcommand)]
pub enum AuditCommand {
    /// Run a Google Lighthouse audit
    #[command(
        long_about = "Run a Google Lighthouse audit against the current page (or a given URL). \
            Connects Lighthouse to the managed Chrome session via the CDP port and returns \
            structured JSON category scores on stdout. Use --only to limit which categories \
            are measured. Use --output-file to save the full Lighthouse JSON report.",
        after_long_help = "\
PREREQUISITES:
  Requires the lighthouse npm package. Install with:
    npm install -g lighthouse
  Or run:
    agentchrome audit lighthouse --install-prereqs

EXAMPLES:
  # Full audit on the current page
  agentchrome audit lighthouse

  # Audit a specific URL
  agentchrome audit lighthouse https://example.com

  # Only performance and accessibility
  agentchrome audit lighthouse --only performance,accessibility

  # Save the full report
  agentchrome audit lighthouse --output-file report.json

  # Install the lighthouse prerequisite (runs: npm install -g lighthouse)
  agentchrome audit lighthouse --install-prereqs"
    )]
    Lighthouse(AuditLighthouseArgs),
}

/// Arguments for `audit lighthouse`.
#[derive(Args)]
pub struct AuditLighthouseArgs {
    /// URL to audit (defaults to the active page URL)
    pub url: Option<String>,

    /// Comma-separated list of categories to measure (e.g. performance,accessibility)
    #[arg(long)]
    pub only: Option<String>,

    /// Save the full Lighthouse JSON report to this file
    #[arg(long)]
    pub output_file: Option<PathBuf>,

    /// Install the `lighthouse` prerequisite via `npm install -g lighthouse` (no prompt — the flag is the consent)
    #[arg(long)]
    pub install_prereqs: bool,
}

/// Arguments for the `diagnose` subcommand.
#[derive(Args)]
#[command(group(clap::ArgGroup::new("target").required(true).args(["url", "current"])))]
pub struct DiagnoseArgs {
    /// URL to navigate to and diagnose (mutually exclusive with --current)
    #[arg(conflicts_with = "current")]
    pub url: Option<String>,

    /// Diagnose the already-loaded page in the active tab without navigating
    /// (mutually exclusive with <url>)
    #[arg(long, conflicts_with = "url")]
    pub current: bool,

    /// Wait strategy after navigation (URL mode only; ignored with --current)
    #[arg(long, value_enum, default_value_t = WaitUntil::Load)]
    pub wait_until: WaitUntil,

    /// Navigation timeout in milliseconds (URL mode only; ignored with --current)
    #[arg(long)]
    pub timeout: Option<u64>,
}

/// Arguments for the `skill` subcommand group.
#[derive(Args)]
pub struct SkillArgs {
    #[command(subcommand)]
    pub command: SkillCommand,
}

/// Skill subcommands.
#[derive(Subcommand)]
pub enum SkillCommand {
    /// Install the agentchrome skill for an agentic coding tool
    #[command(
        long_about = "Install a concise agentchrome skill/instruction file for the detected (or \
            specified) agentic coding tool. The skill tells the AI agent what agentchrome is \
            and how to discover its capabilities. Re-running install overwrites the existing \
            skill file (idempotent).",
        after_long_help = "\
EXAMPLES:
  # Auto-detect tool and install
  agentchrome skill install

  # Install for a specific tool
  agentchrome skill install --tool claude-code

  # Install for Codex
  agentchrome skill install --tool codex"
    )]
    Install(SkillInstallArgs),

    /// Remove a previously installed agentchrome skill
    #[command(
        long_about = "Remove a previously installed agentchrome skill file for the detected (or \
            specified) agentic coding tool. For tools with shared rule files, only the \
            agentchrome section is removed.",
        after_long_help = "\
EXAMPLES:
  # Auto-detect and uninstall
  agentchrome skill uninstall

  # Uninstall for a specific tool
  agentchrome skill uninstall --tool cursor"
    )]
    Uninstall(SkillToolArgs),

    /// Update an installed skill to the current version
    #[command(
        long_about = "Replace an installed agentchrome skill file with the current version's \
            content. Errors if no skill is currently installed for the tool.",
        after_long_help = "\
EXAMPLES:
  # Update for auto-detected tool
  agentchrome skill update

  # Update for a specific tool
  agentchrome skill update --tool claude-code"
    )]
    Update(SkillToolArgs),

    /// List supported agentic tools and installation status
    #[command(
        long_about = "List all supported agentic coding tools with their detection method, \
            install path, and whether a skill is currently installed.",
        after_long_help = "\
EXAMPLES:
  # List all tools
  agentchrome skill list"
    )]
    List,
}

/// Arguments for `skill install`.
#[derive(Args)]
pub struct SkillInstallArgs {
    /// Target tool (auto-detected if omitted)
    #[arg(long, value_enum)]
    pub tool: Option<ToolName>,
}

/// Arguments for `skill uninstall` and `skill update`.
#[derive(Args)]
pub struct SkillToolArgs {
    /// Target tool (auto-detected if omitted)
    #[arg(long, value_enum)]
    pub tool: Option<ToolName>,
}

/// Supported agentic coding tools.
#[derive(Debug, Clone, ValueEnum)]
pub enum ToolName {
    ClaudeCode,
    Windsurf,
    Aider,
    Continue,
    CopilotJb,
    Cursor,
    Gemini,
    Codex,
}

/// Mouse button for decomposed mouse events.
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum MouseButton {
    /// Left mouse button (default)
    Left,
    /// Middle mouse button (scroll wheel)
    Middle,
    /// Right mouse button (context menu)
    Right,
}

/// Wait strategy for navigation commands.
#[derive(Debug, Clone, Copy, ValueEnum, Default, PartialEq, Eq)]
pub enum WaitUntil {
    /// Wait for the load event
    #[default]
    Load,
    /// Wait for DOMContentLoaded event
    Domcontentloaded,
    /// Wait until network is idle (no requests for 500ms)
    Networkidle,
    /// Return immediately after initiating navigation
    None,
}

/// Arguments for the `connect` subcommand.
#[allow(clippy::struct_excessive_bools)]
#[derive(Args)]
pub struct ConnectArgs {
    /// Launch a new Chrome instance instead of connecting to an existing one
    #[arg(long)]
    pub launch: bool,

    /// Show current connection status (conflicts with --launch, --disconnect)
    #[arg(long, conflicts_with_all = ["launch", "disconnect"])]
    pub status: bool,

    /// Disconnect and remove session file (conflicts with --launch, --status)
    #[arg(long, conflicts_with_all = ["launch", "status"])]
    pub disconnect: bool,

    /// Launch Chrome in headless mode
    #[arg(long, requires = "launch")]
    pub headless: bool,

    /// Chrome release channel to launch [default: stable] [possible values: stable, canary, beta, dev]
    #[arg(long, requires = "launch", default_value = "stable")]
    pub channel: ChromeChannel,

    /// Path to a Chrome/Chromium executable (overrides channel-based discovery)
    #[arg(long, requires = "launch")]
    pub chrome_path: Option<PathBuf>,

    /// Additional arguments to pass to Chrome (can be repeated)
    #[arg(long, requires = "launch")]
    pub chrome_arg: Vec<String>,
}

/// Arguments for the `interact` subcommand group.
#[derive(Args)]
pub struct InteractArgs {
    /// Target frame by index, path (1/0), or 'auto'
    #[arg(long)]
    pub frame: Option<String>,

    #[command(subcommand)]
    pub command: InteractCommand,
}

/// Interact subcommands.
#[derive(Subcommand)]
pub enum InteractCommand {
    /// Click an element by UID or CSS selector
    #[command(
        long_about = "Click an element identified by UID (from 'page snapshot', e.g., 's5') or \
            CSS selector (prefixed with 'css:', e.g., 'css:#submit'). By default, performs a \
            left single-click at the element's center. Use --double for double-click or --right \
            for right-click (context menu). These flags are mutually exclusive.",
        after_long_help = "\
EXAMPLES:
  # Click by UID
  agentchrome interact click s5

  # Click by CSS selector
  agentchrome interact click css:#submit-btn

  # Double-click
  agentchrome interact click s5 --double

  # Right-click (context menu)
  agentchrome interact click s5 --right"
    )]
    Click(ClickArgs),

    /// Click at viewport coordinates
    #[command(
        long_about = "Click at specific viewport coordinates (X, Y in pixels). Useful when \
            targeting elements that are not in the accessibility tree or for precise coordinate-\
            based interactions. Use --double for double-click or --right for right-click.",
        after_long_help = "\
EXAMPLES:
  # Click at coordinates
  agentchrome interact click-at 100 200

  # Double-click at coordinates
  agentchrome interact click-at 100 200 --double"
    )]
    ClickAt(ClickAtArgs),

    /// Hover over an element
    #[command(
        long_about = "Move the mouse over an element identified by UID or CSS selector. \
            Triggers hover effects, tooltips, and mouseover events. Does not click.",
        after_long_help = "\
EXAMPLES:
  # Hover by UID
  agentchrome interact hover s3

  # Hover by CSS selector
  agentchrome interact hover css:.tooltip-trigger"
    )]
    Hover(HoverArgs),

    /// Drag from one element to another
    #[command(
        long_about = "Drag from one element to another. Both source and target are identified \
            by UID or CSS selector. Simulates mouse down on the source, move to the target, \
            and mouse up on the target.",
        after_long_help = "\
EXAMPLES:
  # Drag between elements by UID
  agentchrome interact drag s3 s7

  # Drag using CSS selectors
  agentchrome interact drag css:#item css:#dropzone"
    )]
    Drag(DragArgs),

    /// Drag from coordinates to coordinates
    #[command(
        long_about = "Drag from one set of viewport coordinates to another. Simulates mouse down \
            at the source coordinates, move to the target coordinates, and mouse up at the target. \
            Use --steps to interpolate intermediate mousemove events for applications that track \
            drag movement (e.g., canvas-based interfaces).",
        after_long_help = "\
EXAMPLES:
  # Drag from (100,200) to (300,400)
  agentchrome interact drag-at 100 200 300 400

  # Drag with interpolated steps
  agentchrome interact drag-at 0 0 500 500 --steps 10

  # Drag inside an iframe
  agentchrome interact --frame 1 drag-at 50 60 200 300"
    )]
    DragAt(DragAtArgs),

    /// Press mouse button at coordinates (no release)
    #[command(
        name = "mousedown-at",
        long_about = "Dispatch only a mousePressed event at specific viewport coordinates. \
            No mouseReleased event is sent, allowing decomposed mouse interactions such as \
            long-press, drag sequences across multiple invocations, or custom interaction \
            patterns. Use --button to specify left, middle, or right mouse button.",
        after_long_help = "\
EXAMPLES:
  # Mousedown at coordinates
  agentchrome interact mousedown-at 100 200

  # Right-button mousedown
  agentchrome interact mousedown-at 100 200 --button right

  # Mousedown inside an iframe
  agentchrome interact --frame 1 mousedown-at 50 60"
    )]
    MouseDownAt(MouseDownAtArgs),

    /// Release mouse button at coordinates
    #[command(
        name = "mouseup-at",
        long_about = "Dispatch only a mouseReleased event at specific viewport coordinates. \
            No mousePressed event is sent, allowing decomposed mouse interactions such as \
            completing a drag started by a prior mousedown-at invocation. \
            Use --button to specify left, middle, or right mouse button.",
        after_long_help = "\
EXAMPLES:
  # Mouseup at coordinates
  agentchrome interact mouseup-at 300 400

  # Right-button mouseup
  agentchrome interact mouseup-at 300 400 --button right

  # Mouseup inside an iframe
  agentchrome interact --frame 1 mouseup-at 50 60"
    )]
    MouseUpAt(MouseUpAtArgs),

    /// Type text character-by-character into the focused element
    #[command(
        long_about = "Type text character-by-character into the currently focused element. \
            Simulates individual key press and release events for each character. Use --delay \
            to add a pause between keystrokes. To focus an element first, use 'interact click'.",
        after_long_help = "\
EXAMPLES:
  # Type text
  agentchrome interact type \"Hello, world!\"

  # Type with delay between keystrokes
  agentchrome interact type \"slow typing\" --delay 50"
    )]
    Type(TypeArgs),

    /// Press a key or key combination (e.g. Enter, Control+A)
    #[command(
        long_about = "Press a key or key combination. Supports modifier keys (Control, Shift, \
            Alt, Meta) combined with regular keys using '+' separator. Use --repeat to press \
            the key multiple times. Common keys: Enter, Tab, Escape, Backspace, ArrowUp, \
            ArrowDown, ArrowLeft, ArrowRight, Home, End, PageUp, PageDown, Delete.",
        after_long_help = "\
EXAMPLES:
  # Press Enter
  agentchrome interact key Enter

  # Select all (Ctrl+A)
  agentchrome interact key Control+A

  # Press Tab 3 times
  agentchrome interact key Tab --repeat 3

  # Multi-modifier combo
  agentchrome interact key Control+Shift+ArrowRight"
    )]
    Key(KeyArgs),

    /// Scroll the page or a container element
    #[command(
        long_about = "Scroll the page or a specific container element. By default, scrolls \
            down by one viewport height. Use --direction to scroll in other directions, \
            --amount to set a custom distance in pixels, or the shortcut flags --to-top, \
            --to-bottom, --to-element to scroll to specific positions. Use --selector or \
            --uid to scroll within a specific scrollable container by CSS selector or \
            accessibility UID. Use --container for the legacy combined-target syntax. \
            Use --smooth for animated scrolling.",
        after_long_help = "\
EXAMPLES:
  # Scroll down one viewport height
  agentchrome interact scroll

  # Scroll up 200 pixels
  agentchrome interact scroll --direction up --amount 200

  # Scroll to bottom of page
  agentchrome interact scroll --to-bottom

  # Scroll until an element is visible
  agentchrome interact scroll --to-element s15

  # Scroll a container by CSS selector
  agentchrome interact scroll --selector \".stage\" --direction down

  # Scroll a container by UID (requires prior snapshot)
  agentchrome interact scroll --uid s42 --direction down --amount 300

  # Smooth scroll within a container
  agentchrome interact scroll --container css:.scrollable --smooth"
    )]
    Scroll(ScrollArgs),
}

/// Arguments for `interact click`.
#[allow(clippy::struct_excessive_bools)]
#[derive(Args)]
pub struct ClickArgs {
    /// Target element (UID like 's1' or CSS selector like 'css:#button')
    pub target: String,

    /// Perform a double-click instead of single click (conflicts with --right)
    #[arg(long, conflicts_with = "right")]
    pub double: bool,

    /// Perform a right-click (context menu) instead of left click (conflicts with --double)
    #[arg(long, conflicts_with = "double")]
    pub right: bool,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,

    /// Wait strategy after click (e.g., for SPA navigation).
    /// If omitted, click returns immediately with a brief navigation check.
    #[arg(long, value_enum)]
    pub wait_until: Option<WaitUntil>,
}

/// Arguments for `interact click-at`.
#[allow(clippy::struct_excessive_bools)]
#[derive(Args)]
pub struct ClickAtArgs {
    /// X coordinate: absolute pixels (e.g., 100) or percentage (e.g., 50%) when --relative-to is set
    #[arg(value_parser = clap::value_parser!(CoordValue), allow_hyphen_values = true)]
    pub x: CoordValue,

    /// Y coordinate: absolute pixels (e.g., 200) or percentage (e.g., 50%) when --relative-to is set
    #[arg(value_parser = clap::value_parser!(CoordValue), allow_hyphen_values = true)]
    pub y: CoordValue,

    /// Resolve X/Y as offsets or percentages relative to this element's top-left corner.
    /// Accepts a UID (e.g., s7) or CSS selector (e.g., css:#submit).
    #[arg(long = "relative-to")]
    pub relative_to: Option<String>,

    /// Perform a double-click instead of single click (conflicts with --right)
    #[arg(long, conflicts_with = "right")]
    pub double: bool,

    /// Perform a right-click (context menu) instead of left click (conflicts with --double)
    #[arg(long, conflicts_with = "double")]
    pub right: bool,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,

    /// Wait strategy after click (e.g., for SPA navigation).
    /// If omitted, click returns immediately after dispatching.
    #[arg(long, value_enum)]
    pub wait_until: Option<WaitUntil>,
}

/// Arguments for `interact hover`.
#[derive(Args)]
pub struct HoverArgs {
    /// Target element (UID like 's1' or CSS selector like 'css:#button')
    pub target: String,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for `interact drag`.
#[derive(Args)]
pub struct DragArgs {
    /// Source element to drag from (UID or CSS selector)
    pub from: String,

    /// Target element to drag to (UID or CSS selector)
    pub to: String,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for `interact drag-at`.
#[derive(Args)]
pub struct DragAtArgs {
    /// Source X coordinate: absolute pixels or percentage (e.g., 50%) when --relative-to is set
    #[arg(value_parser = clap::value_parser!(CoordValue), allow_hyphen_values = true)]
    pub from_x: CoordValue,

    /// Source Y coordinate: absolute pixels or percentage (e.g., 50%) when --relative-to is set
    #[arg(value_parser = clap::value_parser!(CoordValue), allow_hyphen_values = true)]
    pub from_y: CoordValue,

    /// Target X coordinate: absolute pixels or percentage (e.g., 100%) when --relative-to is set
    #[arg(value_parser = clap::value_parser!(CoordValue), allow_hyphen_values = true)]
    pub to_x: CoordValue,

    /// Target Y coordinate: absolute pixels or percentage (e.g., 100%) when --relative-to is set
    #[arg(value_parser = clap::value_parser!(CoordValue), allow_hyphen_values = true)]
    pub to_y: CoordValue,

    /// Resolve from/to coordinates as offsets or percentages relative to this element.
    /// Accepts a UID (e.g., s7) or CSS selector (e.g., css:#submit).
    #[arg(long = "relative-to")]
    pub relative_to: Option<String>,

    /// Number of intermediate mousemove steps for interpolated drag movement
    #[arg(long)]
    pub steps: Option<u32>,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for `interact mousedown-at`.
#[derive(Args)]
pub struct MouseDownAtArgs {
    /// X coordinate: absolute pixels or percentage (e.g., 50%) when --relative-to is set
    #[arg(value_parser = clap::value_parser!(CoordValue), allow_hyphen_values = true)]
    pub x: CoordValue,

    /// Y coordinate: absolute pixels or percentage (e.g., 50%) when --relative-to is set
    #[arg(value_parser = clap::value_parser!(CoordValue), allow_hyphen_values = true)]
    pub y: CoordValue,

    /// Resolve X/Y as offsets or percentages relative to this element's top-left corner.
    /// Accepts a UID (e.g., s7) or CSS selector (e.g., css:#submit).
    #[arg(long = "relative-to")]
    pub relative_to: Option<String>,

    /// Mouse button to press
    #[arg(long, value_enum)]
    pub button: Option<MouseButton>,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for `interact mouseup-at`.
#[derive(Args)]
pub struct MouseUpAtArgs {
    /// X coordinate: absolute pixels or percentage (e.g., 50%) when --relative-to is set
    #[arg(value_parser = clap::value_parser!(CoordValue), allow_hyphen_values = true)]
    pub x: CoordValue,

    /// Y coordinate: absolute pixels or percentage (e.g., 50%) when --relative-to is set
    #[arg(value_parser = clap::value_parser!(CoordValue), allow_hyphen_values = true)]
    pub y: CoordValue,

    /// Resolve X/Y as offsets or percentages relative to this element's top-left corner.
    /// Accepts a UID (e.g., s7) or CSS selector (e.g., css:#submit).
    #[arg(long = "relative-to")]
    pub relative_to: Option<String>,

    /// Mouse button to release
    #[arg(long, value_enum)]
    pub button: Option<MouseButton>,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for `interact type`.
#[derive(Args)]
pub struct TypeArgs {
    /// Text to type character-by-character
    #[arg(required = true)]
    pub text: String,

    /// Delay between keystrokes in milliseconds (default: 0 for instant)
    #[arg(long, default_value_t = 0)]
    pub delay: u64,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for `interact key`.
#[derive(Args)]
pub struct KeyArgs {
    /// Key or key combination to press (e.g. Enter, Control+A, Shift+ArrowDown)
    #[arg(required = true)]
    pub keys: String,

    /// Number of times to press the key
    #[arg(long, default_value_t = 1)]
    pub repeat: u32,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Scroll direction for `interact scroll`.
#[derive(Debug, Clone, Copy, ValueEnum, Default)]
pub enum ScrollDirection {
    /// Scroll down (default)
    #[default]
    Down,
    /// Scroll up
    Up,
    /// Scroll left
    Left,
    /// Scroll right
    Right,
}

/// Arguments for `interact scroll`.
#[allow(clippy::struct_excessive_bools)]
#[derive(Args)]
pub struct ScrollArgs {
    /// Scroll direction
    #[arg(long, value_enum, default_value_t = ScrollDirection::Down,
           conflicts_with_all = ["to_element", "to_top", "to_bottom"])]
    pub direction: ScrollDirection,

    /// Scroll distance in pixels (default: viewport height for vertical, viewport width for horizontal)
    #[arg(long, conflicts_with_all = ["to_element", "to_top", "to_bottom"])]
    pub amount: Option<u32>,

    /// Scroll until a specific element is in view (UID like 's5' or CSS selector like 'css:#footer')
    #[arg(long, conflicts_with_all = ["direction", "amount", "to_top", "to_bottom", "container"])]
    pub to_element: Option<String>,

    /// Scroll to the top of the page
    #[arg(long, conflicts_with_all = ["direction", "amount", "to_element", "to_bottom", "container"])]
    pub to_top: bool,

    /// Scroll to the bottom of the page
    #[arg(long, conflicts_with_all = ["direction", "amount", "to_element", "to_top", "container"])]
    pub to_bottom: bool,

    /// Use smooth scrolling behavior
    #[arg(long)]
    pub smooth: bool,

    /// CSS selector to target a scrollable container (e.g., '.stage', '#panel')
    #[arg(long, conflicts_with_all = ["uid", "to_element", "to_top", "to_bottom", "container"])]
    pub selector: Option<String>,

    /// Accessibility UID to target a scrollable container (e.g., 's42', requires prior snapshot)
    #[arg(long, conflicts_with_all = ["selector", "to_element", "to_top", "to_bottom", "container"])]
    pub uid: Option<String>,

    /// Scroll within a container element (UID like 's3' or CSS selector like 'css:.scrollable')
    #[arg(long, conflicts_with_all = ["to_element", "to_top", "to_bottom", "selector", "uid"])]
    pub container: Option<String>,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for the `form` subcommand group.
#[derive(Args)]
pub struct FormArgs {
    /// Target frame by index, path (1/0), or 'auto'
    #[arg(long)]
    pub frame: Option<String>,

    #[command(subcommand)]
    pub command: FormCommand,
}

/// Form subcommands.
#[derive(Subcommand)]
pub enum FormCommand {
    /// Fill a form field by UID or CSS selector
    #[command(
        long_about = "Set the value of a form field identified by UID (from 'page snapshot', \
            e.g., 's5') or CSS selector (prefixed with 'css:', e.g., 'css:#email'). Works \
            with text inputs, textareas, select dropdowns, checkboxes, and ARIA combobox \
            elements (role=\"combobox\"). Combobox elements are automatically detected and \
            filled using a click-type-confirm sequence. Dispatches change and input events \
            to trigger form validation.",
        after_long_help = "\
EXAMPLES:
  # Fill by UID
  agentchrome form fill s5 \"hello@example.com\"

  # Fill by CSS selector
  agentchrome form fill css:#email \"user@example.com\"

  # Select a dropdown option
  agentchrome form fill s8 \"Option B\"

  # Fill an ARIA combobox
  agentchrome form fill s5 \"Acme Corp\"

  # Custom confirmation key for combobox
  agentchrome form fill --confirm-key Tab s5 \"Acme Corp\""
    )]
    Fill(FormFillArgs),

    /// Fill multiple form fields at once from JSON
    #[command(
        long_about = "Fill multiple form fields in a single command. Accepts a JSON array of \
            {target, value} objects either as an inline argument or from a file with --file. Each \
            target is a UID (like 's5') or a CSS selector (prefixed with 'css:'), matching the \
            vocabulary of `form fill`. Each field is filled in order. Useful for completing \
            entire forms in one step. The legacy `uid` key is still accepted as an alias for \
            `target` so existing scripts keep working.",
        after_long_help = "\
EXAMPLES:
  # Fill multiple fields inline
  agentchrome form fill-many '[{\"target\":\"s5\",\"value\":\"Alice\"},{\"target\":\"s7\",\"value\":\"alice@example.com\"}]'

  # Fill from a JSON file
  agentchrome form fill-many --file form-data.json"
    )]
    FillMany(FormFillManyArgs),

    /// Clear a form field's value
    #[command(
        long_about = "Clear the value of a form field identified by UID or CSS selector. \
            Sets the field to an empty string and dispatches change and input events.",
        after_long_help = "\
EXAMPLES:
  # Clear a field by UID
  agentchrome form clear s5

  # Clear by CSS selector
  agentchrome form clear css:#search-input"
    )]
    Clear(FormClearArgs),

    /// Upload files to a file input element
    #[command(
        long_about = "Upload one or more files to a file input element identified by UID or \
            CSS selector. The element must be an <input type=\"file\">. Multiple file paths \
            can be specified for multi-file upload inputs.",
        after_long_help = "\
EXAMPLES:
  # Upload a single file
  agentchrome form upload s10 ./photo.jpg

  # Upload multiple files
  agentchrome form upload css:#file-input ./doc1.pdf ./doc2.pdf"
    )]
    Upload(FormUploadArgs),

    /// Submit a form programmatically
    #[command(
        long_about = "Submit a form identified by UID (from 'page snapshot', e.g., 's3') or \
            CSS selector (prefixed with 'css:', e.g., 'css:#login-form'). The target can be \
            the form element itself or any element inside the form — the parent form is \
            resolved automatically. Uses requestSubmit() to respect browser validation.",
        after_long_help = "\
EXAMPLES:
  # Submit by form UID
  agentchrome form submit s3

  # Submit by CSS selector
  agentchrome form submit css:#login-form

  # Submit targeting an input inside the form
  agentchrome form submit s5

  # Include updated snapshot after submit
  agentchrome form submit s3 --include-snapshot"
    )]
    Submit(FormSubmitArgs),
}

/// Arguments for `form fill`.
#[derive(Args)]
pub struct FormFillArgs {
    /// Target element (UID like 's1' or CSS selector like 'css:#email')
    pub target: String,

    /// Value to set on the form field
    pub value: String,

    /// Key to confirm combobox selection (default: Enter)
    #[arg(long)]
    pub confirm_key: Option<String>,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for `form fill-many`.
#[derive(Args)]
pub struct FormFillManyArgs {
    /// Inline JSON array of {target, value} objects
    #[arg(value_name = "JSON")]
    pub input: Option<String>,

    /// Read JSON from a file instead of inline argument
    #[arg(long)]
    pub file: Option<PathBuf>,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for `form clear`.
#[derive(Args)]
pub struct FormClearArgs {
    /// Target element (UID like 's1' or CSS selector like 'css:#email')
    pub target: String,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for `form upload`.
#[derive(Args)]
pub struct FormUploadArgs {
    /// Target file input element (UID like 's5' or CSS selector like 'css:#file-input')
    pub target: String,

    /// File paths to upload
    #[arg(required = true)]
    pub files: Vec<PathBuf>,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for `form submit`.
#[derive(Args)]
pub struct FormSubmitArgs {
    /// Target element (UID like 's3' or CSS selector like 'css:#login-form')
    #[arg(value_name = "TARGET")]
    pub target: String,

    /// Include updated accessibility snapshot in output
    #[arg(long)]
    pub include_snapshot: bool,

    /// Use compact mode for the included snapshot (only interactive and landmark elements)
    #[arg(long)]
    pub compact: bool,
}

/// Arguments for the `console` subcommand group.
#[derive(Args)]
pub struct ConsoleArgs {
    #[command(subcommand)]
    pub command: ConsoleCommand,
}

/// Console subcommands.
#[derive(Subcommand)]
pub enum ConsoleCommand {
    /// List console messages or get details of a specific message
    #[command(
        long_about = "Read captured console messages from the current page. Without arguments, \
            lists recent messages with their IDs, types, and text. Pass a message ID to get \
            full details including stack trace and arguments. Filter by type or use --errors-only \
            for error and assert messages only.",
        after_long_help = "\
EXAMPLES:
  # List recent console messages
  agentchrome console read

  # Get details of a specific message
  agentchrome console read 42

  # Show only errors
  agentchrome console read --errors-only

  # Filter by type
  agentchrome console read --type warn,error --limit 20"
    )]
    Read(ConsoleReadArgs),

    /// Stream console messages in real-time (tail -f style)
    #[command(
        long_about = "Stream new console messages in real time as they are logged, similar to \
            'tail -f'. Each message is printed as a JSON line. Use --timeout to auto-exit \
            after a specified duration. Filter by type or use --errors-only to stream only \
            error and assert messages.\n\n\
            By default, the command monitors output and exits 0 when the timeout elapses \
            or Ctrl+C is pressed, regardless of log levels observed. Pass --fail-on-error \
            to turn the stream into a CI assertion: if any error-level message is observed \
            during the window, the command exits 1 with a JSON error on stderr \
            ({\"error\":\"Error-level console messages were seen\",\"code\":1}).",
        after_long_help = "\
EXAMPLES:
  # Stream all console output (exit 0 on timeout / Ctrl+C)
  agentchrome console follow

  # Stream errors only for 10 seconds (monitoring — exit 0 even if errors are seen)
  agentchrome console follow --errors-only --timeout 10000

  # Stream specific message types
  agentchrome console follow --type log,warn

  # CI assertion — exit 1 if any console.error is observed during the window
  agentchrome console follow --timeout 10000 --fail-on-error"
    )]
    Follow(ConsoleFollowArgs),
}

/// Arguments for `console read`.
#[derive(Args)]
pub struct ConsoleReadArgs {
    /// Message ID to get detailed information about a specific message
    pub msg_id: Option<u64>,

    /// Filter by message type (comma-separated: log,error,warn,info,debug,dir,table,trace,assert,count,timeEnd)
    #[arg(long, value_name = "TYPES", conflicts_with = "errors_only")]
    pub r#type: Option<String>,

    /// Show only error and assert messages (shorthand for --type error,assert)
    #[arg(long, conflicts_with = "type")]
    pub errors_only: bool,

    /// Maximum number of messages to return
    #[arg(long, default_value_t = 50)]
    pub limit: usize,

    /// Pagination page number (0-based)
    #[arg(long, default_value_t = 0)]
    pub page: usize,

    /// Include messages from previous navigations
    #[arg(long)]
    pub include_preserved: bool,
}

/// Arguments for `console follow`.
#[derive(Args)]
pub struct ConsoleFollowArgs {
    /// Filter by message type (comma-separated: log,error,warn,info,debug,dir,table,trace,assert,count,timeEnd)
    #[arg(long, value_name = "TYPES", conflicts_with = "errors_only")]
    pub r#type: Option<String>,

    /// Show only error and assert messages (shorthand for --type error,assert)
    #[arg(long, conflicts_with = "type")]
    pub errors_only: bool,

    /// Auto-exit after the specified number of milliseconds
    #[arg(long)]
    pub timeout: Option<u64>,

    /// Exit with code 1 if any error-level console message is observed during the window
    #[arg(long)]
    pub fail_on_error: bool,
}

/// Arguments for the `network` subcommand group.
#[derive(Args)]
pub struct NetworkArgs {
    #[command(subcommand)]
    pub command: NetworkCommand,
}

/// Network subcommands.
#[derive(Subcommand)]
pub enum NetworkCommand {
    /// List network requests or get details of a specific request
    #[command(
        long_about = "List captured network requests from the current page. Returns JSON with \
            each request's ID, method, URL, status, resource type, and timing. Filter by \
            resource type, URL pattern, HTTP status code, or HTTP method. Use --limit and \
            --page for pagination.",
        after_long_help = "\
EXAMPLES:
  # List recent requests
  agentchrome network list

  # Filter by resource type
  agentchrome network list --type xhr,fetch

  # Filter by URL pattern
  agentchrome network list --url api.example.com

  # Filter by status code
  agentchrome network list --status 4xx"
    )]
    List(NetworkListArgs),

    /// Get detailed information about a specific network request
    #[command(
        long_about = "Get detailed information about a specific network request by its numeric \
            ID. Returns JSON with full request and response headers, timing breakdown, and \
            body size. Use --save-request or --save-response to save the request or response \
            body to a file.",
        after_long_help = "\
EXAMPLES:
  # Get request details
  agentchrome network get 42

  # Save the response body to a file
  agentchrome network get 42 --save-response body.json

  # Save both request and response bodies
  agentchrome network get 42 --save-request req.json --save-response resp.json"
    )]
    Get(NetworkGetArgs),

    /// Stream network requests in real-time (tail -f style)
    #[command(
        long_about = "Stream network requests in real time as they are made, similar to \
            'tail -f'. Each request is printed as a JSON line. Filter by resource type, \
            URL pattern, or HTTP method. Use --timeout to auto-exit after a specified \
            duration. Use --verbose to include request and response headers.",
        after_long_help = "\
EXAMPLES:
  # Stream all network requests
  agentchrome network follow

  # Stream API requests only
  agentchrome network follow --type xhr,fetch --url /api/

  # Stream with headers for 30 seconds
  agentchrome network follow --verbose --timeout 30000"
    )]
    Follow(NetworkFollowArgs),
}

/// Arguments for `network list`.
#[derive(Args)]
pub struct NetworkListArgs {
    /// Filter by resource type (comma-separated: document,stylesheet,image,media,font,script,xhr,fetch,websocket,manifest,other)
    #[arg(long, value_name = "TYPES")]
    pub r#type: Option<String>,

    /// Filter by URL pattern (substring match)
    #[arg(long)]
    pub url: Option<String>,

    /// Filter by HTTP status code (exact like 404 or wildcard like 4xx)
    #[arg(long)]
    pub status: Option<String>,

    /// Filter by HTTP method (GET, POST, etc.)
    #[arg(long)]
    pub method: Option<String>,

    /// Maximum number of requests to return
    #[arg(long, default_value_t = 50)]
    pub limit: usize,

    /// Pagination page number (0-based)
    #[arg(long, default_value_t = 0)]
    pub page: usize,

    /// Include requests from previous navigations
    #[arg(long)]
    pub include_preserved: bool,

    /// Filter network requests by originating frame index
    #[arg(long)]
    pub frame: Option<String>,
}

/// Arguments for `network get`.
#[derive(Args)]
pub struct NetworkGetArgs {
    /// Numeric request ID to inspect
    pub req_id: u64,

    /// Save request body to a file
    #[arg(long)]
    pub save_request: Option<PathBuf>,

    /// Save response body to a file
    #[arg(long)]
    pub save_response: Option<PathBuf>,
}

/// Arguments for `network follow`.
#[derive(Args)]
pub struct NetworkFollowArgs {
    /// Filter by resource type (comma-separated: document,stylesheet,image,media,font,script,xhr,fetch,websocket,manifest,other)
    #[arg(long, value_name = "TYPES")]
    pub r#type: Option<String>,

    /// Filter by URL pattern (substring match)
    #[arg(long)]
    pub url: Option<String>,

    /// Filter by HTTP method (GET, POST, etc.)
    #[arg(long)]
    pub method: Option<String>,

    /// Auto-exit after the specified number of milliseconds
    #[arg(long)]
    pub timeout: Option<u64>,

    /// Include request and response headers in stream output
    #[arg(long)]
    pub verbose: bool,
}

/// Arguments for `page resize`.
#[derive(Args)]
pub struct PageResizeArgs {
    /// Viewport size as WIDTHxHEIGHT (e.g. 1280x720)
    pub size: String,
}

/// Arguments for `page element`.
#[derive(Args)]
pub struct PageElementArgs {
    /// Element target: UID (s1, s2, ...) or CSS selector (css:#id, css:.class)
    pub target: String,
}

/// Arguments for `page hittest`.
#[derive(Args)]
pub struct PageHitTestArgs {
    /// X viewport coordinate
    pub x: u32,

    /// Y viewport coordinate
    pub y: u32,
}

/// Arguments for `page coords`.
#[derive(Args)]
pub struct PageCoordsArgs {
    /// Element target: UID from 'page snapshot' (e.g., s7) or CSS selector (e.g., css:#submit)
    #[arg(long)]
    pub selector: String,
}

/// Arguments for `page wait`.
#[derive(Args)]
pub struct PageWaitArgs {
    /// Wait for the page URL to match a glob pattern
    #[arg(long, group = "condition")]
    pub url: Option<String>,

    /// Wait for text to appear in the page content
    #[arg(long, group = "condition")]
    pub text: Option<String>,

    /// Wait for a CSS selector to match an element in the DOM
    #[arg(long, group = "condition")]
    pub selector: Option<String>,

    /// Wait for network activity to settle (no requests for 500ms)
    #[arg(long, group = "condition")]
    pub network_idle: bool,

    /// Wait for a JavaScript expression to evaluate to a truthy value
    #[arg(long, group = "condition")]
    pub js_expression: Option<String>,

    /// Minimum number of elements that must match the selector (requires --selector)
    #[arg(long, requires = "selector", default_value = "1")]
    pub count: u64,

    /// Poll interval in milliseconds (for --url, --text, --selector, --js-expression)
    #[arg(long, default_value = "100")]
    pub interval: u64,
}

/// Arguments for the `dom` subcommand group.
#[derive(Args)]
pub struct DomArgs {
    /// Target frame by index, path (1/0), or 'auto'
    #[arg(long)]
    pub frame: Option<String>,

    /// Pierce open shadow DOM boundaries for element queries
    #[arg(long)]
    pub pierce_shadow: bool,

    #[command(subcommand)]
    pub command: DomCommand,
}

/// DOM inspection and manipulation subcommands.
#[derive(Subcommand)]
pub enum DomCommand {
    /// Select elements by CSS selector or XPath
    #[command(
        alias = "query",
        long_about = "Query elements in the DOM by CSS selector (default) or XPath expression \
            (with --xpath). Returns a JSON array of matching elements with their node IDs, \
            tag names, attributes, and text content. Node IDs can be used with other dom \
            subcommands.",
        after_long_help = "\
EXAMPLES:
  # Select by CSS selector
  agentchrome dom select \"h1\"

  # Select by XPath
  agentchrome dom select \"//a[@href]\" --xpath

  # Select with a complex CSS selector
  agentchrome dom select \"div.content > p:first-child\""
    )]
    Select(DomSelectArgs),

    /// Get a single attribute value from an element
    #[command(
        name = "get-attribute",
        long_about = "Read a single attribute value from a DOM element. The element can be \
            targeted by node ID (from 'dom select'), snapshot UID (from 'page snapshot'), \
            or CSS selector (prefixed with 'css:'). Returns the attribute name and value.",
        after_long_help = "\
EXAMPLES:
  # Get href by UID
  agentchrome dom get-attribute s3 href

  # Get class by CSS selector
  agentchrome dom get-attribute css:h1 class"
    )]
    GetAttribute(DomGetAttributeArgs),

    /// Get the text content of an element
    #[command(
        name = "get-text",
        long_about = "Read the textContent of a DOM element. Returns the combined text of \
            the element and all its descendants.",
        after_long_help = "\
EXAMPLES:
  # Get text by UID
  agentchrome dom get-text s3

  # Get text by CSS selector
  agentchrome dom get-text css:h1"
    )]
    GetText(DomNodeIdArgs),

    /// Get the outer HTML of an element
    #[command(
        name = "get-html",
        long_about = "Read the outerHTML of a DOM element, including the element itself and \
            all its children as an HTML string.",
        after_long_help = "\
EXAMPLES:
  # Get HTML by UID
  agentchrome dom get-html s3

  # Get HTML by CSS selector
  agentchrome dom get-html css:div.content"
    )]
    GetHtml(DomNodeIdArgs),

    /// Set an attribute on an element
    #[command(
        name = "set-attribute",
        long_about = "Set or update an attribute on a DOM element. Creates the attribute if \
            it doesn't exist, or updates its value if it does.",
        after_long_help = "\
EXAMPLES:
  # Set class attribute
  agentchrome dom set-attribute s5 class \"highlight\"

  # Set data attribute by CSS selector
  agentchrome dom set-attribute css:#main data-active true"
    )]
    SetAttribute(DomSetAttributeArgs),

    /// Set the text content of an element
    #[command(
        name = "set-text",
        long_about = "Replace the textContent of a DOM element, removing all child nodes \
            and setting the element's content to the given text.",
        after_long_help = "\
EXAMPLES:
  # Set text by UID
  agentchrome dom set-text s3 \"New heading\"

  # Set text by CSS selector
  agentchrome dom set-text css:h1 \"Updated Title\""
    )]
    SetText(DomSetTextArgs),

    /// Remove an element from the DOM
    #[command(
        long_about = "Remove a DOM element and all its children from the document. This is \
            irreversible within the current page session.",
        after_long_help = "\
EXAMPLES:
  # Remove by UID
  agentchrome dom remove s3

  # Remove by CSS selector
  agentchrome dom remove css:div.ad-banner"
    )]
    Remove(DomNodeIdArgs),

    /// Get computed CSS styles of an element
    #[command(
        name = "get-style",
        long_about = "Read the computed CSS styles of a DOM element. Without a property name, \
            returns all computed styles. With a property name, returns just that property's \
            value.",
        after_long_help = "\
EXAMPLES:
  # Get all computed styles
  agentchrome dom get-style s3

  # Get a specific property
  agentchrome dom get-style s3 display

  # Get style by CSS selector
  agentchrome dom get-style css:h1 color"
    )]
    GetStyle(DomGetStyleArgs),

    /// Set the inline style of an element
    #[command(
        name = "set-style",
        long_about = "Set the inline style attribute of a DOM element. The style string replaces \
            the entire inline style. Use CSS property syntax.",
        after_long_help = "\
EXAMPLES:
  # Set inline style
  agentchrome dom set-style s3 \"color: red; font-size: 24px\"

  # Set style by CSS selector
  agentchrome dom set-style css:h1 \"display: none\""
    )]
    SetStyle(DomSetStyleArgs),

    /// Get the parent element
    #[command(
        long_about = "Navigate to the parent element of a DOM node. Returns the parent's \
            node ID, tag name, attributes, and text content.",
        after_long_help = "\
EXAMPLES:
  # Get parent of a UID
  agentchrome dom parent s3

  # Get parent by CSS selector
  agentchrome dom parent css:span.label"
    )]
    Parent(DomNodeIdArgs),

    /// List direct child elements
    #[command(
        long_about = "List the direct child elements (element nodes only, nodeType 1) of a \
            DOM node. Returns a JSON array of child elements.",
        after_long_help = "\
EXAMPLES:
  # List children by UID
  agentchrome dom children s3

  # List children by CSS selector
  agentchrome dom children css:div.container"
    )]
    Children(DomNodeIdArgs),

    /// List sibling elements
    #[command(
        long_about = "List the sibling elements of a DOM node (other children of the same \
            parent, excluding the target element itself).",
        after_long_help = "\
EXAMPLES:
  # List siblings by UID
  agentchrome dom siblings s3

  # List siblings by CSS selector
  agentchrome dom siblings css:li.active"
    )]
    Siblings(DomNodeIdArgs),

    /// Pretty-print the DOM tree
    #[command(
        long_about = "Display the DOM tree as indented plain text. By default shows the full \
            document tree. Use --depth to limit traversal depth and --root to start from a \
            specific element.",
        after_long_help = "\
EXAMPLES:
  # Show the full DOM tree
  agentchrome dom tree

  # Limit depth to 3 levels
  agentchrome dom tree --depth 3

  # Show tree from a specific element (positional form)
  agentchrome dom tree css:div.content

  # Show tree from a specific element (flag form)
  agentchrome dom tree --root css:div.content"
    )]
    Tree(DomTreeArgs),

    /// List event listeners attached to an element
    #[command(
        long_about = "List all event listeners attached to a DOM element. Shows listeners \
            registered via addEventListener and inline handlers (e.g., onclick). Output \
            includes event type, capture/bubble phase, once/passive flags, and handler \
            source location.",
        after_long_help = "\
EXAMPLES:
  # List listeners by UID
  agentchrome dom events s3

  # List listeners by CSS selector
  agentchrome dom events css:button

  # List listeners in a frame
  agentchrome dom --frame 0 events css:button"
    )]
    Events(DomNodeIdArgs),
}

/// Arguments for `dom select`.
#[derive(Args)]
pub struct DomSelectArgs {
    /// CSS selector or XPath expression to query
    pub selector: String,

    /// Interpret the selector as an XPath expression instead of CSS
    #[arg(long)]
    pub xpath: bool,
}

/// Arguments for `dom get-attribute`.
#[derive(Args)]
pub struct DomGetAttributeArgs {
    /// Target element (node ID, UID like 's1', or CSS selector like 'css:#el')
    pub node_id: String,

    /// Attribute name to read
    pub attribute: String,
}

/// Arguments for `dom set-attribute`.
#[derive(Args)]
pub struct DomSetAttributeArgs {
    /// Target element (node ID, UID like 's1', or CSS selector like 'css:#el')
    pub node_id: String,

    /// Attribute name to set
    pub attribute: String,

    /// Attribute value
    pub value: String,
}

/// Arguments for `dom get-style`.
#[derive(Args)]
pub struct DomGetStyleArgs {
    /// Target element (node ID, UID like 's1', or CSS selector like 'css:#el')
    pub node_id: String,

    /// CSS property name (omit for all computed styles)
    pub property: Option<String>,
}

/// Arguments for `dom set-style`.
#[derive(Args)]
pub struct DomSetStyleArgs {
    /// Target element (node ID, UID like 's1', or CSS selector like 'css:#el')
    pub node_id: String,

    /// CSS style text (e.g. "color: red; font-size: 24px")
    pub style: String,
}

/// Arguments for `dom set-text`.
#[derive(Args)]
pub struct DomSetTextArgs {
    /// Target element (node ID, UID like 's1', or CSS selector like 'css:#el')
    pub node_id: String,

    /// Text content to set
    pub text: String,
}

/// Shared arguments for dom subcommands that take only a node ID.
///
/// Used by get-text, get-html, remove, parent, children, siblings.
#[derive(Args)]
pub struct DomNodeIdArgs {
    /// Target element (node ID, UID like 's1', or CSS selector like 'css:#el')
    pub node_id: String,
}

/// Arguments for `dom tree`.
#[derive(Args)]
pub struct DomTreeArgs {
    /// Maximum tree depth to display
    #[arg(long)]
    pub depth: Option<u32>,

    /// Start the tree from a specific element (node ID, UID, or CSS selector)
    #[arg(long)]
    pub root: Option<String>,

    /// Optional target element (node ID, UID, or CSS selector). Positional form of `--root`.
    #[arg(value_name = "ROOT", conflicts_with = "root")]
    pub root_positional: Option<String>,
}

/// Arguments for the `emulate` subcommand group.
#[derive(Args)]
pub struct EmulateArgs {
    #[command(subcommand)]
    pub command: EmulateCommand,
}

/// Emulate subcommands.
#[derive(Subcommand)]
pub enum EmulateCommand {
    /// Apply one or more emulation overrides
    #[command(
        long_about = "Apply one or more device or network emulation overrides. Multiple \
            overrides can be combined in a single command (e.g., viewport + network + user \
            agent). Overrides persist until 'emulate reset' is called or the browser is closed. \
            Note: --geolocation and --no-geolocation are mutually exclusive, as are \
            --user-agent and --no-user-agent.",
        after_long_help = "\
EXAMPLES:
  # Emulate a mobile device
  agentchrome emulate set --viewport 375x667 --device-scale 2 --mobile

  # Simulate slow network
  agentchrome emulate set --network 3g

  # Set geolocation (San Francisco)
  agentchrome emulate set --geolocation 37.7749,-122.4194

  # Force dark mode with custom user agent
  agentchrome emulate set --color-scheme dark --user-agent \"CustomBot/1.0\"

  # Throttle CPU (4x slowdown)
  agentchrome emulate set --cpu 4"
    )]
    Set(EmulateSetArgs),

    /// Clear all emulation overrides
    #[command(
        long_about = "Clear all device and network emulation overrides, restoring the browser \
            to its default settings. This removes viewport, user agent, geolocation, network \
            throttling, CPU throttling, and color scheme overrides.",
        after_long_help = "\
EXAMPLES:
  # Reset all overrides
  agentchrome emulate reset"
    )]
    Reset,

    /// Show current emulation settings
    #[command(
        long_about = "Display the current emulation state including viewport dimensions, \
            user agent, device scale factor, network conditions, CPU throttling, and color \
            scheme. Returns JSON with the active emulation configuration.",
        after_long_help = "\
EXAMPLES:
  # Check emulation status
  agentchrome emulate status"
    )]
    Status,
}

/// Arguments for `emulate set`.
#[allow(clippy::struct_excessive_bools)]
#[derive(Args)]
pub struct EmulateSetArgs {
    /// Network condition profile: offline, slow-4g, 4g, 3g, none
    #[arg(long, value_enum)]
    pub network: Option<NetworkProfile>,

    /// CPU throttling rate (1 = no throttling, 2-20 = slowdown factor)
    #[arg(long, value_parser = clap::value_parser!(u32).range(1..=20))]
    pub cpu: Option<u32>,

    /// Set geolocation override as LAT,LONG (e.g. 37.7749,-122.4194; conflicts with --no-geolocation)
    #[arg(long, conflicts_with = "no_geolocation")]
    pub geolocation: Option<String>,

    /// Clear geolocation override (conflicts with --geolocation)
    #[arg(long, conflicts_with = "geolocation")]
    pub no_geolocation: bool,

    /// Set custom user agent string (conflicts with --no-user-agent)
    #[arg(long, conflicts_with = "no_user_agent")]
    pub user_agent: Option<String>,

    /// Reset user agent to browser default (conflicts with --user-agent)
    #[arg(long, conflicts_with = "user_agent")]
    pub no_user_agent: bool,

    /// Force color scheme: dark, light, auto
    #[arg(long, value_enum)]
    pub color_scheme: Option<ColorScheme>,

    /// Set viewport dimensions as WIDTHxHEIGHT (e.g. 375x667)
    #[arg(long)]
    pub viewport: Option<String>,

    /// Set device pixel ratio (e.g. 2.0)
    #[arg(long)]
    pub device_scale: Option<f64>,

    /// Emulate mobile device (touch events, mobile viewport)
    #[arg(long)]
    pub mobile: bool,
}

/// Network condition profiles for emulation.
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum NetworkProfile {
    /// Fully offline (no network)
    Offline,
    /// Slow 4G (150ms latency, 1.6 Mbps down, 750 Kbps up)
    #[value(name = "slow-4g")]
    Slow4g,
    /// 4G (20ms latency, 4 Mbps down, 3 Mbps up)
    #[value(name = "4g")]
    FourG,
    /// 3G (100ms latency, 750 Kbps down, 250 Kbps up)
    #[value(name = "3g")]
    ThreeG,
    /// No throttling (disable network emulation)
    None,
}

/// Color scheme for emulation.
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum ColorScheme {
    /// Force dark mode
    Dark,
    /// Force light mode
    Light,
    /// Reset to browser default
    Auto,
}

/// Arguments for the `config` subcommand group.
#[derive(Args)]
pub struct ConfigArgs {
    #[command(subcommand)]
    pub command: ConfigCommand,
}

/// Config management subcommands.
#[derive(Subcommand)]
pub enum ConfigCommand {
    /// Display the resolved configuration from all sources
    #[command(
        long_about = "Display the fully resolved configuration by merging all sources in \
            priority order: CLI flags > environment variables > config file > defaults. \
            Returns JSON showing every setting and its effective value. Useful for debugging \
            which settings are active.",
        after_long_help = "\
EXAMPLES:
  # Show resolved config
  agentchrome config show

  # Show config from a specific file
  agentchrome --config ./my-config.toml config show"
    )]
    Show,

    /// Create a default config file with commented example values
    #[command(
        long_about = "Create a new configuration file with all available settings documented \
            as comments. By default, the file is created at the XDG config directory \
            (~/.config/agentchrome/config.toml on Linux, ~/Library/Application Support/\
            agentchrome/config.toml on macOS). Use --path to specify a custom location. \
            Will not overwrite an existing file.",
        after_long_help = "\
EXAMPLES:
  # Create default config file
  agentchrome config init

  # Create at a custom path
  agentchrome config init --path ./my-config.toml"
    )]
    Init(ConfigInitArgs),

    /// Show the active config file path (or null if none)
    #[command(
        long_about = "Show the path of the active configuration file. Searches in priority \
            order: --config flag, $AGENTCHROME_CONFIG env var, project-local \
            (.agentchrome.toml), XDG config dir, home directory (~/.agentchrome.toml). \
            Returns JSON with {\"path\": \"...\"} or {\"path\": null} if no config file is found.",
        after_long_help = "\
EXAMPLES:
  # Show active config path
  agentchrome config path"
    )]
    Path,
}

/// Arguments for `config init`.
#[derive(Args)]
pub struct ConfigInitArgs {
    /// Create config file at a custom path instead of the default XDG location
    #[arg(long)]
    pub path: Option<PathBuf>,
}

/// Arguments for the `completions` subcommand.
#[derive(Args)]
pub struct CompletionsArgs {
    /// Shell to generate completions for (bash, zsh, fish, powershell, elvish)
    pub shell: Shell,
}

/// Arguments for the `man` subcommand.
#[derive(Args)]
pub struct ManArgs {
    /// Subcommand to display man page for (omit for top-level)
    pub command: Option<String>,
}

/// Arguments for the `examples` subcommand.
#[derive(Args)]
pub struct ExamplesArgs {
    /// Command group to show examples for (e.g., navigate, tabs, page),
    /// or the literal "strategies" to access scenario-based interaction guides.
    pub command: Option<String>,

    /// When `command` is "strategies", the strategy name to show in detail.
    /// Run `agentchrome examples strategies` to see valid strategy names
    /// (e.g., iframes, overlays, scorm, drag-and-drop, shadow-dom,
    /// spa-navigation-waits, react-controlled-inputs,
    /// debugging-failed-interactions, authentication-cookie-reuse,
    /// multi-tab-workflows).
    pub name: Option<String>,
}

/// Arguments for the `capabilities` subcommand.
#[derive(Args)]
pub struct CapabilitiesArgs {
    /// Command name to show the full descriptor for (omit for the summary listing).
    /// Run `agentchrome capabilities` to list valid names.
    pub command: Option<String>,

    /// Minimal output: command names and descriptions only
    #[arg(long)]
    pub compact: bool,
}

#[cfg(test)]
mod tests {
    use super::Cli;
    use clap::Parser;

    // Helper: parse a space-separated command string as if run from the CLI.
    fn try_parse(cmd: &str) -> Result<Cli, clap::Error> {
        let parts: Vec<&str> = std::iter::once("agentchrome")
            .chain(cmd.split_whitespace())
            .collect();
        Cli::try_parse_from(parts)
    }

    // AC8: No arguments → clap error (required arg group not satisfied)
    #[test]
    fn diagnose_no_args_is_error() {
        assert!(
            try_parse("diagnose").is_err(),
            "Expected a clap error when neither URL nor --current is supplied"
        );
    }

    // AC9: Both URL and --current → clap error (conflict)
    #[test]
    fn diagnose_url_and_current_is_error() {
        assert!(
            try_parse("diagnose https://example.com --current").is_err(),
            "Expected a clap error when both URL and --current are supplied"
        );
    }

    // Positive: URL only → parses successfully
    #[test]
    fn diagnose_url_only_parses() {
        let cli = try_parse("diagnose https://example.com").expect("URL-only form should parse");
        let super::Command::Diagnose(args) = cli.command else {
            panic!("Expected Diagnose command variant");
        };
        assert_eq!(args.url.as_deref(), Some("https://example.com"));
        assert!(!args.current);
    }

    // Positive: --current only → parses successfully
    #[test]
    fn diagnose_current_only_parses() {
        let cli = try_parse("diagnose --current").expect("--current-only form should parse");
        let super::Command::Diagnose(args) = cli.command else {
            panic!("Expected Diagnose command variant");
        };
        assert!(args.current);
        assert!(args.url.is_none());
    }

    // --current before a positional URL value still conflicts (order-independent)
    #[test]
    fn diagnose_current_before_url_is_error() {
        assert!(
            try_parse("diagnose --current https://example.com").is_err(),
            "Expected a clap error when --current appears before the URL"
        );
    }

    // T003: ExamplesArgs parse tests
    #[test]
    fn examples_strategies_no_name_parses() {
        let cli = try_parse("examples strategies").expect("examples strategies should parse");
        let super::Command::Examples(args) = cli.command else {
            panic!("Expected Examples command variant");
        };
        assert_eq!(args.command.as_deref(), Some("strategies"));
        assert!(args.name.is_none());
    }

    #[test]
    fn examples_strategies_with_name_parses() {
        let cli = try_parse("examples strategies iframes")
            .expect("examples strategies iframes should parse");
        let super::Command::Examples(args) = cli.command else {
            panic!("Expected Examples command variant");
        };
        assert_eq!(args.command.as_deref(), Some("strategies"));
        assert_eq!(args.name.as_deref(), Some("iframes"));
    }

    #[test]
    fn examples_navigate_still_parses() {
        let cli = try_parse("examples navigate").expect("examples navigate should still parse");
        let super::Command::Examples(args) = cli.command else {
            panic!("Expected Examples command variant");
        };
        assert_eq!(args.command.as_deref(), Some("navigate"));
        assert!(args.name.is_none());
    }

    // T015: Clap help metadata steering compliance test
    #[test]
    fn examples_subcommand_carries_clap_help_metadata() {
        use clap::CommandFactory;
        let cmd = Cli::command();
        let examples_subcmd = cmd
            .get_subcommands()
            .find(|s| s.get_name() == "examples")
            .expect("examples subcommand must exist");

        // long_about must be non-empty and mention "strategies"
        let long_about = examples_subcmd
            .get_long_about()
            .expect("examples must have long_about");
        let long_about_str = long_about.to_string();
        assert!(
            !long_about_str.is_empty(),
            "examples long_about must not be empty"
        );
        assert!(
            long_about_str.contains("strategies"),
            "examples long_about must mention 'strategies'\ngot: {long_about_str}"
        );

        // after_long_help must mention "examples strategies" and "--json"
        let after_long_help = examples_subcmd
            .get_after_long_help()
            .expect("examples must have after_long_help");
        let after_str = after_long_help.to_string();
        assert!(
            after_str.contains("examples strategies"),
            "examples after_long_help must contain 'examples strategies'\ngot: {after_str}"
        );
        assert!(
            after_str.contains("--json"),
            "examples after_long_help must contain at least one --json example\ngot: {after_str}"
        );
    }

    #[test]
    fn capabilities_with_positional_parses() {
        let cli = try_parse("capabilities page").expect("capabilities page should parse");
        let super::Command::Capabilities(args) = cli.command else {
            panic!("Expected Capabilities command variant");
        };
        assert_eq!(args.command.as_deref(), Some("page"));
        assert!(!args.compact);
    }

    #[test]
    fn capabilities_no_args_parses() {
        let cli = try_parse("capabilities").expect("capabilities should parse");
        let super::Command::Capabilities(args) = cli.command else {
            panic!("Expected Capabilities command variant");
        };
        assert!(args.command.is_none());
    }

    #[test]
    fn capabilities_subcommand_carries_clap_help_metadata() {
        use clap::CommandFactory;
        let cmd = Cli::command();
        let caps = cmd
            .get_subcommands()
            .find(|s| s.get_name() == "capabilities")
            .expect("capabilities subcommand must exist");

        let long_about = caps
            .get_long_about()
            .expect("capabilities must have long_about");
        let long_about_str = long_about.to_string();
        assert!(
            long_about_str.contains("detail") || long_about_str.contains("listing"),
            "capabilities long_about must mention 'detail' or 'listing'\ngot: {long_about_str}"
        );

        let after = caps
            .get_after_long_help()
            .expect("capabilities must have after_long_help");
        let after_str = after.to_string();
        assert!(
            after_str.contains("capabilities"),
            "capabilities after_long_help must contain 'capabilities'\ngot: {after_str}"
        );
        assert!(
            after_str.contains("--json"),
            "capabilities after_long_help must contain a --json example\ngot: {after_str}"
        );
    }

    // Issue #230: hidden flag-shape aliases

    #[test]
    fn cookie_set_accepts_url_alias() {
        let cli = try_parse("cookie set n v --url https://example.com/path")
            .expect("cookie set --url should parse");
        let super::Command::Cookie(args) = cli.command else {
            panic!("expected Cookie command");
        };
        let super::CookieCommand::Set(set) = args.command else {
            panic!("expected CookieCommand::Set");
        };
        assert_eq!(set.url.as_deref(), Some("https://example.com/path"));
        assert!(set.domain.is_none());
    }

    #[test]
    fn cookie_set_rejects_url_and_domain_together() {
        assert!(
            Cli::try_parse_from([
                "agentchrome",
                "cookie",
                "set",
                "n",
                "v",
                "--url",
                "https://example.com/",
                "--domain",
                "other.com",
            ])
            .is_err(),
            "clap conflicts_with should reject --url + --domain"
        );
    }

    #[test]
    fn tabs_close_accepts_repeated_tab_flags() {
        let cli = try_parse("tabs close --tab A --tab B").expect("--tab repeats should parse");
        let super::Command::Tabs(args) = cli.command else {
            panic!("expected Tabs command");
        };
        let super::TabsCommand::Close(close) = args.command else {
            panic!("expected TabsCommand::Close");
        };
        assert!(close.targets.is_empty());
        assert_eq!(close.tab, vec!["A".to_string(), "B".to_string()]);
    }

    #[test]
    fn tabs_close_no_args_still_parses() {
        let cli = try_parse("tabs close").expect("tabs close with no args should parse");
        let super::Command::Tabs(args) = cli.command else {
            panic!("expected Tabs command");
        };
        let super::TabsCommand::Close(close) = args.command else {
            panic!("expected TabsCommand::Close");
        };
        assert!(close.targets.is_empty());
        assert!(close.tab.is_empty());
    }

    #[test]
    fn dom_query_is_alias_for_select() {
        let cli = try_parse("dom query h1").expect("dom query should parse as dom select");
        let super::Command::Dom(args) = cli.command else {
            panic!("expected Dom command");
        };
        let super::DomCommand::Select(sel) = args.command else {
            panic!("expected DomCommand::Select via alias");
        };
        assert_eq!(sel.selector, "h1");
        assert!(!sel.xpath);
    }

    #[test]
    fn dom_query_supports_xpath_flag() {
        let cli =
            try_parse("dom query //a[@href] --xpath").expect("dom query should support --xpath");
        let super::Command::Dom(args) = cli.command else {
            panic!("expected Dom command");
        };
        let super::DomCommand::Select(sel) = args.command else {
            panic!("expected DomCommand::Select via alias");
        };
        assert_eq!(sel.selector, "//a[@href]");
        assert!(sel.xpath);
    }

    #[test]
    fn cookie_set_help_does_not_mention_url_alias() {
        use clap::CommandFactory;
        let cmd = Cli::command();
        let cookie = cmd
            .get_subcommands()
            .find(|s| s.get_name() == "cookie")
            .expect("cookie subcommand exists");
        let set = cookie
            .get_subcommands()
            .find(|s| s.get_name() == "set")
            .expect("cookie set subcommand exists");
        let url_arg = set
            .get_arguments()
            .find(|a| a.get_id() == "url")
            .expect("url arg exists on cookie set");
        assert!(url_arg.is_hide_set(), "--url must be hidden from help");
    }

    #[test]
    fn dom_query_alias_is_hidden() {
        use clap::CommandFactory;
        let cmd = Cli::command();
        let dom = cmd
            .get_subcommands()
            .find(|s| s.get_name() == "dom")
            .expect("dom subcommand exists");
        let select = dom
            .get_subcommands()
            .find(|s| s.get_name() == "select")
            .expect("dom select subcommand exists");
        let aliases: Vec<&str> = select.get_all_aliases().collect();
        assert!(
            aliases.contains(&"query"),
            "dom select must alias 'query', got {aliases:?}"
        );
        let visible: Vec<&str> = select.get_visible_aliases().collect();
        assert!(
            !visible.contains(&"query"),
            "dom select 'query' alias must be hidden, not visible"
        );
    }
}