Skip to main content

agent_first_http/cli/cmd/
cdp.rs

1//! `afhttp cdp` subcommand. Raw CDP method invocation.
2
3use clap::Args as ClapArgs;
4use serde::Serialize;
5
6use crate::cli::output;
7use crate::sdk::Client;
8use crate::shared::error::{Error, ErrorCode};
9use crate::shared::ids::TabId;
10use crate::shared::time::parse_duration;
11
12#[derive(Serialize)]
13struct CdpResult {
14    result: serde_json::Value,
15}
16
17#[derive(ClapArgs, Debug)]
18pub struct Args {
19    /// CDP method name (e.g. Runtime.evaluate).
20    pub method: String,
21    /// CDP endpoint of the running host (e.g. `ws://127.0.0.1:9222`). Falls back to `AFHTTP_ENDPOINT_URL`.
22    #[arg(long = "endpoint-url", env = "AFHTTP_ENDPOINT_URL")]
23    pub endpoint: String,
24    /// Bearer token, if the host was started with `--token-secret`.
25    /// Falls back to `AFHTTP_TOKEN_SECRET`.
26    #[arg(long = "token-secret", env = "AFHTTP_TOKEN_SECRET")]
27    pub token: Option<String>,
28    /// CDP target id (tab) to drive.
29    #[arg(long)]
30    pub tab: String,
31    /// JSON literal, or `@-` to read from stdin.
32    #[arg(long, value_name = "JSON|@-")]
33    pub params: Option<String>,
34    /// "<event>:<timeout>" — wait for a CDP event before exiting.
35    #[arg(long = "wait-event")]
36    pub wait: Option<String>,
37}
38
39pub async fn run(args: Args) -> Result<(), Error> {
40    let mut client = Client::connect(&args.endpoint)?;
41    if let Some(t) = args.token.as_deref() {
42        client = client.with_token(t);
43    }
44    let params = if let Some(raw) = args.params {
45        if raw == "@-" {
46            use std::io::Read;
47            let mut buf = String::new();
48            std::io::stdin()
49                .read_to_string(&mut buf)
50                .map_err(|e| Error::new(ErrorCode::IoError, format!("read stdin: {e}")))?;
51            serde_json::from_str(&buf).map_err(|e| {
52                Error::new(
53                    ErrorCode::InvalidArgument,
54                    format!("--params @-: invalid JSON: {e}"),
55                )
56            })?
57        } else {
58            serde_json::from_str(&raw).map_err(|e| {
59                Error::new(
60                    ErrorCode::InvalidArgument,
61                    format!("--params: invalid JSON: {e}"),
62                )
63            })?
64        }
65    } else {
66        serde_json::Value::Object(Default::default())
67    };
68    let mut req = client
69        .cdp(args.method)
70        .tab(TabId::new(args.tab))
71        .params(params);
72    if let Some(spec) = args.wait {
73        let (ev, timeout) = spec.rsplit_once(':').ok_or_else(|| {
74            Error::new(
75                ErrorCode::InvalidArgument,
76                "--wait-event: expected <event>:<timeout>",
77            )
78        })?;
79        let d = parse_duration(timeout)?;
80        req = req.wait_for(ev, d);
81    }
82    let value = req.send().await?;
83    output::emit("cdp", &CdpResult { result: value })
84}