Skip to main content

browser_automation_cli/
cli.rs

1//! Clap derive surface for browser-automation-cli (PRD Layer L).
2//!
3//! Help text on flags is the primary documentation for this module.
4#![allow(missing_docs)]
5
6use clap::{Parser, Subcommand, ValueEnum};
7
8/// One-shot browser automation CLI for AI agents.
9#[derive(Debug, Parser)]
10#[command(
11    name = "browser-automation-cli",
12    version,
13    about = "One-shot browser automation CLI (Chrome CDP). BORN, EXECUTE, FINALIZE, DIE.",
14    long_about = None,
15    propagate_version = true
16)]
17pub struct Cli {
18    #[command(flatten)]
19    pub globals: GlobalOpts,
20
21    #[command(subcommand)]
22    pub command: Commands,
23}
24
25#[derive(Debug, Clone, Parser)]
26pub struct GlobalOpts {
27    #[arg(long, global = true)]
28    pub json: bool,
29
30    /// Suppress non-error human logs on stderr
31    #[arg(
32        short = 'q',
33        long = "quiet",
34        global = true,
35    )]
36    pub quiet: bool,
37
38    /// Increase stderr verbosity (`--verbose` once = info; use RUST_LOG for finer control)
39    #[arg(
40        long = "verbose",
41        global = true,
42    )]
43    pub verbose: bool,
44
45    /// Maximum tracing detail on stderr (debug/trace)
46    #[arg(long = "debug", global = true)]
47    pub debug: bool,
48
49    #[arg(
50        long,
51        global = true,
52        default_value_t = 0
53    )]
54    pub timeout: u64,
55
56    /// Per-step timeout in seconds for `run` scripts (0 = inherit global timeout)
57    #[arg(
58        long,
59        global = true,
60        default_value_t = 0
61    )]
62    pub step_timeout: u64,
63
64    /// Launch Chrome with a visible window (debug; default headless=new)
65    #[arg(long, global = true)]
66    pub headed: bool,
67
68    #[arg(long, global = true)]
69    pub artifacts_dir: Option<std::path::PathBuf>,
70
71    #[arg(long, global = true)]
72    pub lang: Option<String>,
73
74    #[arg(long, global = true)]
75    pub capture_console: bool,
76
77    #[arg(long, global = true)]
78    pub capture_network: bool,
79
80    #[arg(long, global = true)]
81    pub ignore_robots: bool,
82
83    #[arg(
84        long,
85        global = true,
86    )]
87    pub i_accept_robots_risk: bool,
88
89    /// Enable deep heap analysis tools (PRD category-memory)
90    #[arg(long, global = true)]
91    pub category_memory: bool,
92
93    /// Enable extension management tools
94    #[arg(
95        long,
96        global = true,
97    )]
98    pub category_extensions: bool,
99
100    /// Enable third-party developer tool surface
101    #[arg(
102        long,
103        global = true,
104    )]
105    pub category_third_party: bool,
106
107    /// Enable WebMCP-compatible tool surface
108    #[arg(long, global = true)]
109    pub category_webmcp: bool,
110
111    /// Enable experimental screencast (may require ffmpeg for file export)
112    #[arg(
113        long,
114        global = true,
115    )]
116    pub experimental_screencast: bool,
117
118    /// Enable coordinate click-at (vision) tools
119    #[arg(
120        long,
121        global = true,
122    )]
123    pub experimental_vision: bool,
124}
125
126#[derive(Debug, Subcommand)]
127pub enum Commands {
128    /// Diagnose Chrome install and one-shot readiness
129    Doctor {
130        #[arg(long)]
131        offline: bool,
132        #[arg(long)]
133        quick: bool,
134        #[arg(long)]
135        fix: bool,
136        #[arg(long)]
137        json: bool,
138    },
139    /// List available commands
140    Commands {
141        #[arg(long)]
142        json: bool,
143    },
144    /// JSON Schema fragment for a command (agent discovery)
145    Schema {
146        #[arg(long = "cmd", value_name = "CMD")]
147        cmd: String,
148    },
149    /// Print CLI version
150    Version,
151    /// Navigate to a URL (one-shot)
152    Goto {
153        url: String,
154        /// JS to evaluate before navigation (tool-ref initScript)
155        #[arg(long)]
156        init_script: Option<String>,
157        /// Accept beforeunload dialogs automatically
158        #[arg(long)]
159        handle_before_unload: bool,
160        /// Navigation timeout override in milliseconds
161        #[arg(long)]
162        navigation_timeout_ms: Option<u64>,
163    },
164    /// Accessibility snapshot with @eN refs
165    View {
166        #[arg(long)]
167        verbose: bool,
168        #[arg(long)]
169        path: Option<std::path::PathBuf>,
170    },
171    /// Click an element (selector or @eN)
172    Press {
173        target: String,
174        #[arg(long)]
175        dblclick: bool,
176        /// Attach slim a11y snapshot in the same process after the action
177        #[arg(long)]
178        include_snapshot: bool,
179    },
180    /// Click at page CSS coordinates (requires --experimental-vision)
181    ClickAt {
182        #[arg(long)]
183        x: f64,
184        #[arg(long)]
185        y: f64,
186        #[arg(long)]
187        dblclick: bool,
188        /// Attach slim a11y snapshot after the click
189        #[arg(long)]
190        include_snapshot: bool,
191    },
192    /// Fill an input value (select/checkbox/radio/text smart fill)
193    Write {
194        target: String,
195        value: String,
196        /// Attach slim a11y snapshot after fill
197        #[arg(long)]
198        include_snapshot: bool,
199    },
200    /// Press a keyboard key
201    Keys {
202        key: String,
203        /// Attach slim a11y snapshot after the key press
204        #[arg(long)]
205        include_snapshot: bool,
206    },
207    /// Type text (tool-ref type_text). Use --target or --focus-only.
208    Type {
209        /// Text to type (required positional)
210        text: String,
211        /// CSS selector or @eN (optional; use --focus-only for focused element)
212        #[arg(long)]
213        target: Option<String>,
214        #[arg(long)]
215        clear: bool,
216        /// Optional key to press after typing (e.g. Enter)
217        #[arg(long)]
218        submit: Option<String>,
219        /// Type into currently focused element without resolving a target
220        #[arg(long)]
221        focus_only: bool,
222    },
223    /// Wait for ms and/or text and/or selector and/or load state
224    Wait {
225        #[arg(long, default_value_t = 0)]
226        ms: u64,
227        /// Text to wait for (repeatable; resolves when any value appears — tool-ref OR)
228        #[arg(long = "text", action = clap::ArgAction::Append)]
229        text: Vec<String>,
230        #[arg(long)]
231        selector: Option<String>,
232        /// Page lifecycle: load | domcontentloaded | networkidle | none
233        #[arg(long)]
234        state: Option<String>,
235        /// Max wait time in milliseconds for text/selector/state (0 = default)
236        #[arg(long)]
237        wait_timeout_ms: Option<u64>,
238        /// Attach slim a11y snapshot after the wait succeeds
239        #[arg(long)]
240        include_snapshot: bool,
241    },
242    /// Hover an element
243    Hover {
244        target: String,
245        /// Attach slim a11y snapshot after hover
246        #[arg(long)]
247        include_snapshot: bool,
248    },
249    /// Drag from one target to another
250    Drag {
251        #[arg(long)]
252        from: String,
253        #[arg(long)]
254        to: String,
255        /// Attach slim a11y snapshot after drag
256        #[arg(long)]
257        include_snapshot: bool,
258    },
259    /// Fill multiple form fields from JSON `[{target|uid,value},...]`
260    FillForm {
261        #[arg(long)]
262        json: String,
263        /// Attach slim a11y snapshot after fill-form
264        #[arg(long)]
265        include_snapshot: bool,
266    },
267    /// Upload a file to a file input
268    Upload {
269        target: String,
270        path: std::path::PathBuf,
271        /// Attach slim a11y snapshot after upload
272        #[arg(long)]
273        include_snapshot: bool,
274    },
275    /// History back
276    Back,
277    /// History forward
278    Forward,
279    /// Reload current page
280    Reload {
281        #[arg(long)]
282        ignore_cache: bool,
283        /// JS to run before navigation/reload (tool-ref initScript)
284        #[arg(long)]
285        init_script: Option<String>,
286        /// Accept beforeunload dialogs automatically
287        #[arg(long)]
288        handle_before_unload: bool,
289    },
290    /// Evaluate JavaScript (expression or function declaration)
291    Eval {
292        /// JS expression or function declaration `() => ...`
293        expression: String,
294        /// Snapshot uids (@eN) passed as function args (JSON array of strings)
295        #[arg(long)]
296        args: Option<String>,
297        /// accept | dismiss | prompt response text (default accept)
298        #[arg(long)]
299        dialog_action: Option<String>,
300        /// Write evaluate result JSON to this path
301        #[arg(long)]
302        file_path: Option<std::path::PathBuf>,
303    },
304    /// Capture a screenshot
305    Grab {
306        #[arg(long)]
307        path: Option<std::path::PathBuf>,
308        #[arg(long, value_enum, default_value_t = GrabFormat::Png)]
309        format: GrabFormat,
310        #[arg(long)]
311        full_page: bool,
312        #[arg(long)]
313        quality: Option<i32>,
314        /// CSS selector or @eN element to capture
315        #[arg(long)]
316        element: Option<String>,
317    },
318    /// Run multi-step NDJSON script in one process
319    Run {
320        #[arg(long)]
321        script: std::path::PathBuf,
322    },
323    /// Limited inline subcommand (goto)
324    Exec {
325        // Do NOT set allow_hyphen_values: global flags like --json after `exec`
326        // must stay on GlobalOpts, not be swallowed into trailing args.
327        #[arg(trailing_var_arg = true)]
328        args: Vec<String>,
329    },
330    /// Extract text or attribute from a target
331    Extract {
332        target: String,
333        #[arg(long)]
334        attr: Option<String>,
335    },
336    /// Extract visible text from a target (PRD §7 `text`)
337    Text { target: String },
338    /// Scroll page or element by delta pixels (PRD §7 `scroll`)
339    Scroll {
340        /// CSS selector or @eN (optional; omit for window scroll)
341        #[arg(long)]
342        target: Option<String>,
343        #[arg(long, default_value_t = 0.0)]
344        delta_x: f64,
345        #[arg(long, default_value_t = 0.0)]
346        delta_y: f64,
347    },
348    /// Cookie jar helpers for the active page (Network domain)
349    Cookie {
350        #[command(subcommand)]
351        action: CookieAction,
352    },
353    /// Read one attribute from a target
354    Attr { target: String, name: String },
355    /// Assertions (url / text / console)
356    Assert {
357        #[command(subcommand)]
358        kind: AssertKind,
359    },
360    /// Captured console messages (--capture-console)
361    Console {
362        #[command(subcommand)]
363        action: ConsoleAction,
364    },
365    /// Captured network requests (--capture-network)
366    Net {
367        #[command(subcommand)]
368        action: NetAction,
369    },
370    /// Page info or multi-tab management
371    Page {
372        #[command(subcommand)]
373        action: Option<PageAction>,
374    },
375    /// Accept or dismiss dialogs
376    Dialog {
377        #[command(subcommand)]
378        action: DialogAction,
379    },
380    /// Navigate and return body text / formats (local Firecrawl-parity)
381    Scrape {
382        url: String,
383        /// text | markdown | html | links | metadata
384        #[arg(long, default_value = "text")]
385        format: String,
386        /// http (reqwest+scraper) or browser (CDP)
387        #[arg(long, default_value = "browser")]
388        engine: String,
389        /// Prefer main/article content heuristics
390        #[arg(long)]
391        only_main_content: bool,
392    },
393    /// Scrape many URLs from a file (HTTP engine, one-shot)
394    BatchScrape {
395        #[arg(long)]
396        urls_file: std::path::PathBuf,
397        #[arg(long, default_value = "text")]
398        format: String,
399        #[arg(long, default_value_t = 2)]
400        concurrency: usize,
401    },
402    /// Crawl from a seed URL (HTTP BFS, one-shot)
403    Crawl {
404        url: String,
405        #[arg(long, default_value_t = 20)]
406        limit: usize,
407        #[arg(long, default_value_t = 2)]
408        max_depth: usize,
409        #[arg(long, default_value = "text")]
410        format: String,
411        /// Stay on seed host
412        #[arg(long, default_value_t = true)]
413        same_host: bool,
414    },
415    /// Map site URLs from a seed (HTTP)
416    Map {
417        url: String,
418        #[arg(long, default_value_t = 50)]
419        limit: usize,
420        #[arg(long, default_value_t = 2)]
421        max_depth: usize,
422    },
423    /// Local search (HTTP SERP links or URL map)
424    Search {
425        query: String,
426        #[arg(long, default_value_t = 10)]
427        limit: usize,
428    },
429    /// Parse a local file (html/md/txt/pdf text extract)
430    Parse {
431        path: std::path::PathBuf,
432    },
433    /// MITM capture / CA / HAR (one-shot local)
434    Mitm {
435        #[command(subcommand)]
436        action: MitmAction,
437    },
438    /// Workflow journal DAG (petgraph + SQLite)
439    Workflow {
440        #[command(subcommand)]
441        action: WorkflowAction,
442    },
443    /// XDG config and path management (no .env at runtime)
444    Config {
445        #[command(subcommand)]
446        action: ConfigAction,
447    },
448    /// Emulate device / network / UA / geo / CPU
449    Emulate {
450        #[arg(long)]
451        user_agent: Option<String>,
452        #[arg(long)]
453        locale: Option<String>,
454        #[arg(long)]
455        timezone: Option<String>,
456        #[arg(long)]
457        offline: bool,
458        #[arg(long)]
459        latitude: Option<f64>,
460        #[arg(long)]
461        longitude: Option<f64>,
462        #[arg(long)]
463        media: Option<String>,
464        /// Network preset: Offline, No throttling, Slow 3G, Fast 3G, Slow 4G, Fast 4G
465        #[arg(long)]
466        network_conditions: Option<String>,
467        /// CPU slowdown factor 1..=20 (1 disables)
468        #[arg(long)]
469        cpu_throttling_rate: Option<f64>,
470        /// prefers-color-scheme: dark | light | auto
471        #[arg(long)]
472        color_scheme: Option<String>,
473        /// Extra HTTP headers as JSON object string
474        #[arg(long)]
475        extra_headers: Option<String>,
476        /// Viewport `WxHxDPR` with optional `,mobile`, `,touch`, `,landscape` flags
477        #[arg(long)]
478        viewport: Option<String>,
479    },
480    /// Resize page viewport
481    Resize {
482        #[arg(long)]
483        width: i32,
484        #[arg(long)]
485        height: i32,
486        #[arg(long, default_value_t = 1.0)]
487        scale: f64,
488        #[arg(long)]
489        mobile: bool,
490    },
491    /// Performance trace / metrics
492    Perf {
493        #[command(subcommand)]
494        action: PerfAction,
495    },
496    /// Run Lighthouse audit (external binary)
497    Lighthouse {
498        url: String,
499        #[arg(long)]
500        out_dir: Option<std::path::PathBuf>,
501        #[arg(long, default_value = "desktop")]
502        device: String,
503        /// navigation (default) or snapshot (maps to navigation in one-shot CLI)
504        #[arg(long, default_value = "navigation")]
505        mode: String,
506        #[arg(long)]
507        lighthouse_path: Option<std::path::PathBuf>,
508    },
509    /// Screencast start/stop (experimental)
510    Screencast {
511        #[command(subcommand)]
512        action: ScreencastAction,
513    },
514    /// Heap snapshot tools (requires --category-memory for deep analysis)
515    Heap {
516        #[command(subcommand)]
517        action: HeapAction,
518    },
519    /// Chrome extension tools (requires --category-extensions)
520    Extension {
521        #[command(subcommand)]
522        action: ExtensionAction,
523    },
524    /// Third-party developer tools surface (requires --category-third-party)
525    Devtools3p {
526        #[command(subcommand)]
527        action: Devtools3pAction,
528    },
529    /// Web surface tools (requires --category-webmcp)
530    Webmcp {
531        #[command(subcommand)]
532        action: WebmcpAction,
533    },
534    /// Generate shell completions (path leve, no Chrome)
535    Completions {
536        #[arg(value_enum)]
537        shell: CompletionShell,
538    },
539}
540
541#[derive(Debug, Clone, Subcommand)]
542pub enum PerfAction {
543    Start {
544        #[arg(long)]
545        path: Option<std::path::PathBuf>,
546        #[arg(long)]
547        reload: bool,
548        /// Auto-stop after page load/reload (tool-ref autoStop)
549        #[arg(long)]
550        auto_stop: bool,
551    },
552    Stop {
553        #[arg(long)]
554        path: Option<std::path::PathBuf>,
555    },
556    Insight {
557        /// Insight name (e.g. DocumentLatency, LCPBreakdown)
558        #[arg(long)]
559        name: Option<String>,
560        /// Insight set id from perf stop "available_insight_sets"
561        #[arg(long)]
562        insight_set_id: Option<String>,
563        /// Alias for --name (tool-ref insightName)
564        #[arg(long)]
565        insight_name: Option<String>,
566    },
567}
568
569#[derive(Debug, Clone, Subcommand)]
570pub enum ScreencastAction {
571    Start {
572        #[arg(long)]
573        path: Option<std::path::PathBuf>,
574    },
575    Stop {
576        /// Output path (.webm/.mp4 encodes via ffmpeg; otherwise PNG frames dir)
577        #[arg(long)]
578        path: Option<std::path::PathBuf>,
579    },
580}
581
582#[derive(Debug, Clone, Subcommand)]
583pub enum HeapAction {
584    Take {
585        #[arg(long)]
586        path: std::path::PathBuf,
587    },
588    Close {
589        #[arg(long)]
590        path: std::path::PathBuf,
591    },
592    Compare {
593        #[arg(long)]
594        base: std::path::PathBuf,
595        #[arg(long)]
596        current: std::path::PathBuf,
597        /// Optional class index filter (tool-ref classIndex)
598        #[arg(long)]
599        class_index: Option<u64>,
600    },
601    Summary {
602        #[arg(long)]
603        path: std::path::PathBuf,
604    },
605    Details {
606        #[arg(long)]
607        path: std::path::PathBuf,
608        #[arg(long)]
609        filter_name: Option<String>,
610        #[arg(long)]
611        page_idx: Option<usize>,
612        #[arg(long)]
613        page_size: Option<usize>,
614    },
615    ClassNodes {
616        #[arg(long)]
617        path: std::path::PathBuf,
618        #[arg(long)]
619        id: u64,
620        #[arg(long)]
621        filter_name: Option<String>,
622        #[arg(long)]
623        page_idx: Option<usize>,
624        #[arg(long)]
625        page_size: Option<usize>,
626    },
627    Dominators {
628        #[arg(long)]
629        path: std::path::PathBuf,
630        #[arg(long)]
631        node: u64,
632    },
633    DupStrings {
634        #[arg(long)]
635        path: std::path::PathBuf,
636        #[arg(long)]
637        page_idx: Option<usize>,
638        #[arg(long)]
639        page_size: Option<usize>,
640    },
641    Edges {
642        #[arg(long)]
643        path: std::path::PathBuf,
644        #[arg(long)]
645        node: u64,
646        #[arg(long)]
647        page_idx: Option<usize>,
648        #[arg(long)]
649        page_size: Option<usize>,
650    },
651    Retainers {
652        #[arg(long)]
653        path: std::path::PathBuf,
654        #[arg(long)]
655        node: u64,
656        #[arg(long)]
657        page_idx: Option<usize>,
658        #[arg(long)]
659        page_size: Option<usize>,
660    },
661    Paths {
662        #[arg(long)]
663        path: std::path::PathBuf,
664        #[arg(long)]
665        node: u64,
666        #[arg(long, default_value_t = 8)]
667        max_depth: u64,
668        #[arg(long)]
669        max_nodes: Option<u64>,
670        #[arg(long)]
671        max_siblings: Option<u64>,
672    },
673    /// Detailed info for one heap object (size, distance, retained size, detachedness)
674    ObjectDetails {
675        #[arg(long)]
676        path: std::path::PathBuf,
677        #[arg(long)]
678        node: u64,
679    },
680}
681
682#[derive(Debug, Clone, Subcommand)]
683pub enum ExtensionAction {
684    List,
685    Install {
686        path: std::path::PathBuf,
687    },
688    Reload {
689        id: String,
690        /// Unpacked extension dir so one-shot can --load-extension before reload
691        #[arg(long)]
692        path: Option<std::path::PathBuf>,
693    },
694    Trigger {
695        id: String,
696        /// Unpacked extension dir so one-shot can --load-extension before trigger
697        #[arg(long)]
698        path: Option<std::path::PathBuf>,
699    },
700    Uninstall {
701        id: String,
702    },
703}
704
705#[derive(Debug, Clone, Subcommand)]
706pub enum Devtools3pAction {
707    List {
708        /// Optional page URL to open before discovery
709        #[arg(long)]
710        url: Option<String>,
711    },
712    Exec {
713        name: String,
714        #[arg(long)]
715        params: Option<String>,
716        #[arg(long)]
717        url: Option<String>,
718    },
719}
720
721#[derive(Debug, Clone, Subcommand)]
722pub enum WebmcpAction {
723    List {
724        #[arg(long)]
725        url: Option<String>,
726    },
727    Exec {
728        name: String,
729        #[arg(long)]
730        input: Option<String>,
731        #[arg(long)]
732        url: Option<String>,
733    },
734}
735
736#[derive(Debug, Clone, Copy, ValueEnum)]
737pub enum CompletionShell {
738    Bash,
739    Zsh,
740    Fish,
741    Elvish,
742    Powershell,
743}
744
745#[derive(Debug, Clone, Subcommand)]
746pub enum PageAction {
747    /// Current page url and title (default when bare `page`)
748    Info,
749    /// List tabs in this one-shot process
750    List,
751    /// Open a new tab
752    New {
753        #[arg(long)]
754        url: Option<String>,
755        /// Open without focusing (tool-ref background)
756        #[arg(long)]
757        background: bool,
758        /// Create isolated browser context when supported
759        #[arg(long)]
760        isolated_context: bool,
761    },
762    /// Select tab by zero-based index (alias: --page-id)
763    Select {
764        #[arg(value_name = "INDEX")]
765        index: Option<usize>,
766        /// Tool-ref pageId alias for index
767        #[arg(long = "page-id")]
768        page_id: Option<usize>,
769        /// Bring selected tab to front (tool-ref bringToFront)
770        #[arg(long, default_value_t = true)]
771        bring_to_front: bool,
772    },
773    /// Close a tab (default: active)
774    Close {
775        #[arg(long)]
776        index: Option<usize>,
777        /// Tool-ref pageId alias for index
778        #[arg(long = "page-id")]
779        page_id: Option<usize>,
780    },
781}
782
783#[derive(Debug, Clone, Subcommand)]
784pub enum CookieAction {
785    /// List cookies (optional URL filter)
786    List {
787        #[arg(long)]
788        url: Option<String>,
789    },
790    /// Set cookies from a JSON array of cookie objects
791    Set {
792        /// JSON array: [{"name":"a","value":"b","url":"https://..."}]
793        #[arg(long)]
794        json: String,
795    },
796    /// Clear all browser cookies in this one-shot process
797    Clear,
798}
799
800#[derive(Debug, Clone, Copy, ValueEnum, Default)]
801pub enum GrabFormat {
802    #[default]
803    Png,
804    Jpeg,
805    Webp,
806}
807
808#[derive(Debug, Clone, Subcommand)]
809pub enum AssertKind {
810    Url {
811        value: String,
812        #[arg(long)]
813        contains: bool,
814    },
815    Text {
816        value: String,
817        #[arg(long)]
818        target: Option<String>,
819    },
820    Console {
821        #[arg(long, default_value = "error")]
822        level: String,
823        #[arg(long, default_value_t = 0)]
824        max: u64,
825    },
826}
827
828#[derive(Debug, Clone, Subcommand)]
829pub enum ConsoleAction {
830    List {
831        /// 0-based page index for pagination
832        #[arg(long)]
833        page_idx: Option<usize>,
834        /// Max messages per page
835        #[arg(long)]
836        page_size: Option<usize>,
837        /// Filter by types (comma-separated: log,warning,error,info,debug)
838        #[arg(long)]
839        types: Option<String>,
840        /// Include messages preserved across navigations in this process
841        #[arg(long)]
842        include_preserved: bool,
843        /// Optional service worker id filter
844        #[arg(long)]
845        service_worker_id: Option<String>,
846    },
847    Get {
848        id: usize,
849    },
850    Clear,
851    Dump {
852        #[arg(long)]
853        path: std::path::PathBuf,
854    },
855}
856
857#[derive(Debug, Clone, Subcommand)]
858pub enum NetAction {
859    List {
860        /// 0-based page index for pagination
861        #[arg(long)]
862        page_idx: Option<usize>,
863        /// Max requests per page
864        #[arg(long)]
865        page_size: Option<usize>,
866        /// Filter resource types (comma-separated: Document,Script,XHR,Fetch,...)
867        #[arg(long)]
868        resource_types: Option<String>,
869        /// Include requests preserved over recent navigations in this process
870        #[arg(long)]
871        include_preserved: bool,
872    },
873    Get {
874        /// 0-based index in net list, or CDP requestId string
875        id: String,
876        #[arg(long)]
877        request_path: Option<std::path::PathBuf>,
878        #[arg(long)]
879        response_path: Option<std::path::PathBuf>,
880    },
881}
882
883#[derive(Debug, Clone, Subcommand)]
884pub enum DialogAction {
885    Accept {
886        #[arg(long)]
887        text: Option<String>,
888    },
889    Dismiss,
890}
891
892#[derive(Debug, Clone, Subcommand)]
893pub enum MitmAction {
894    /// CA paths, capture count, bind policy
895    Status,
896    /// List captured exchanges
897    List {
898        #[arg(long)]
899        host: Option<String>,
900        #[arg(long, default_value_t = 100)]
901        limit: usize,
902    },
903    /// Get one exchange by id
904    Get { id: u64 },
905    /// Export HAR 1.2 JSON
906    Har {
907        #[arg(long)]
908        out: std::path::PathBuf,
909    },
910    /// Export capture as JSON/NDJSON
911    Export {
912        #[arg(long, default_value = "json")]
913        format: String,
914        #[arg(long)]
915        out: std::path::PathBuf,
916    },
917    /// Unique hosts seen
918    Domains,
919    /// REST/GraphQL endpoint discovery
920    Apis {
921        #[arg(long)]
922        kind: Option<String>,
923    },
924    /// Ensure local CA under XDG data
925    InitCa,
926    /// Start one-shot MITM proxy on 127.0.0.1 (ephemeral port); captures until timeout
927    Start {
928        /// Seconds to keep the proxy alive (one-shot; default 30)
929        #[arg(long, default_value_t = 30)]
930        seconds: u64,
931    },
932}
933
934#[derive(Debug, Clone, Subcommand)]
935pub enum WorkflowAction {
936    /// Validate DAG and execute offline steps; journal under XDG state
937    Run {
938        #[arg(long)]
939        manifest: std::path::PathBuf,
940        #[arg(long)]
941        journal: Option<std::path::PathBuf>,
942    },
943    /// Resume / re-run from journal + manifest
944    Resume {
945        #[arg(long)]
946        manifest: std::path::PathBuf,
947        #[arg(long)]
948        journal: Option<std::path::PathBuf>,
949    },
950    /// Show journal step statuses
951    Status {
952        #[arg(long)]
953        journal: Option<std::path::PathBuf>,
954        #[arg(long)]
955        name: Option<String>,
956    },
957}
958
959#[derive(Debug, Clone, Subcommand)]
960pub enum ConfigAction {
961    /// Print resolved XDG paths
962    Path,
963    /// Create XDG layout + default config.toml
964    Init,
965    /// Show config values
966    Show,
967    /// Set a config key (lang|timeout|artifacts_dir|ignore_robots|namespace|encryption_key|color)
968    Set {
969        key: String,
970        value: String,
971    },
972    /// Get one config key
973    Get {
974        key: Option<String>,
975    },
976}