use std::path::PathBuf;
use clap::{Args, Parser, Subcommand, ValueEnum};
use ferridriver::backend::BackendKind;
use ferridriver::state::ConnectMode;
#[derive(Parser)]
#[command(
name = "ferridriver",
about = "Rust-based browser automation: MCP server, BDD runner, test wrapper",
version,
propagate_version = true
)]
pub struct Cli {
#[arg(short, long, action = clap::ArgAction::Count, global = true)]
pub verbose: u8,
#[arg(short, long, global = true)]
pub config: Option<PathBuf>,
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand)]
pub enum Command {
Mcp(McpArgs),
Bdd(BddArgs),
Test(TestArgs),
Run(RunArgs),
Install(InstallArgs),
Codegen(CodegenArgs),
}
#[derive(Args)]
pub struct McpArgs {
#[command(flatten)]
pub browser: BrowserArgs,
#[command(flatten)]
pub transport: TransportArgs,
}
#[derive(Args)]
pub struct BddArgs {
pub features: Vec<String>,
#[arg(long)]
pub tags: Option<String>,
#[arg(long)]
pub dry_run: bool,
#[arg(long)]
pub fail_fast: bool,
#[arg(long)]
pub strict: bool,
#[arg(long)]
pub step_timeout: Option<u64>,
#[arg(long)]
pub order: Option<String>,
#[arg(long)]
pub language: Option<String>,
#[arg(long)]
pub workers: Option<usize>,
#[arg(long)]
pub shard: Option<String>,
#[arg(long)]
pub reporter: Vec<String>,
#[arg(long)]
pub steps: Vec<String>,
#[arg(long)]
pub world_parameters: Option<String>,
#[command(flatten)]
pub browser: BrowserArgs,
}
#[derive(Args)]
pub struct TestArgs {
pub filter: Option<String>,
#[arg(short = 'p', long = "package")]
pub packages: Vec<String>,
#[arg(long, value_enum)]
pub runner: Option<TestRunner>,
#[arg(long)]
pub profile: Option<String>,
#[arg(last = true)]
pub passthrough: Vec<String>,
}
#[derive(Clone, Copy, ValueEnum)]
pub enum TestRunner {
Nextest,
Cargo,
}
#[derive(Args)]
pub struct RunArgs {
pub script: Option<String>,
#[arg(short = 'e', long = "eval", conflicts_with = "script")]
pub eval: Option<String>,
#[arg(long)]
pub timeout_ms: Option<u64>,
#[arg(last = true)]
pub script_args: Vec<String>,
}
#[derive(Args)]
pub struct InstallArgs {
pub browsers: Vec<String>,
#[arg(long)]
pub with_deps: bool,
}
#[derive(Args)]
pub struct CodegenArgs {
pub url: Option<String>,
#[arg(short, long)]
pub output: Option<PathBuf>,
#[arg(long, default_value = "ts")]
pub language: String,
#[command(flatten)]
pub browser: BrowserArgs,
}
#[derive(Args, Clone)]
pub struct BrowserArgs {
#[arg(long, default_value = "cdp-pipe")]
pub backend: Backend,
#[arg(long)]
pub headless: bool,
#[arg(long)]
pub executable_path: Option<String>,
#[arg(long)]
pub connect: Option<String>,
#[arg(long)]
pub auto_connect: Option<String>,
#[arg(long)]
pub user_data_dir: Option<String>,
}
impl BrowserArgs {
pub fn backend_kind(&self) -> BackendKind {
backend_to_kind(&self.backend)
}
pub fn connect_mode(&self) -> ConnectMode {
resolve_connect_mode(self)
}
}
#[derive(Args, Clone)]
pub struct TransportArgs {
#[arg(long, default_value = "stdio")]
pub transport: Transport,
#[arg(long, default_value = "8080")]
pub port: u16,
}
#[derive(Clone, ValueEnum)]
pub enum Backend {
CdpPipe,
CdpRaw,
#[value(name = "webkit")]
WebKit,
Bidi,
}
#[derive(Clone, ValueEnum)]
pub enum Transport {
Stdio,
Http,
}
pub fn backend_to_kind(b: &Backend) -> BackendKind {
match b {
Backend::CdpPipe => BackendKind::CdpPipe,
Backend::CdpRaw => BackendKind::CdpRaw,
Backend::WebKit => BackendKind::WebKit,
Backend::Bidi => BackendKind::Bidi,
}
}
pub fn resolve_connect_mode(args: &BrowserArgs) -> ConnectMode {
if let Some(ref url) = args.connect {
ConnectMode::ConnectUrl(url.clone())
} else if let Some(ref channel) = args.auto_connect {
ConnectMode::AutoConnect {
channel: channel.clone(),
user_data_dir: args.user_data_dir.clone(),
}
} else {
ConnectMode::Launch
}
}