Skip to main content

browser_automation_cli/
lib.rs

1//! # browser-automation-cli
2//!
3//! One-shot Chrome CDP automation library and CLI for AI agents.
4//!
5//! Lifecycle is always **NASCE → EXECUTA → FINALIZE → MORRE** in a single process.
6//! There is no daemon, no npm runtime, and no remote telemetry.
7//!
8//! ## Overview
9//!
10//! - Parse argv with clap ([`cli`])
11//! - Dispatch one command or a multi-step `run --script` session
12//! - Launch system Chrome/Chromium through chromiumoxide CDP
13//! - Always attempt FINALIZE (Browser.close, wait, kill fallback)
14//!
15//! ## Quick Start
16//!
17//! ```bash
18//! cargo install --path . --locked
19//! browser-automation-cli doctor --offline --quick --json
20//! browser-automation-cli goto https://example.com --json
21//! ```
22//!
23//! Library entry for embedding or tests:
24//!
25//! ```no_run
26//! use std::process::ExitCode;
27//!
28//! fn main() -> ExitCode {
29//!     browser_automation_cli::run()
30//! }
31//! ```
32//!
33//! ## Features
34//!
35//! This crate has no Cargo feature flags.
36//! Rustdoc embeds Mermaid lifecycle diagrams via `aquamarine` on documented entry points.
37//! Optional CLI categories are process flags:
38//!
39//! - `--category-memory` — deep heap tools
40//! - `--category-extensions` — extension tools
41//! - `--category-third-party` — third-party DevTools helpers
42//! - `--category-webmcp` — webmcp tools
43//! - `--experimental-vision` — coordinate click
44//! - `--experimental-screencast` — screencast export (needs ffmpeg)
45//!
46//! ## Targets
47//!
48//! Documented and tested for:
49//!
50//! - `x86_64-unknown-linux-gnu`
51//! - `x86_64-apple-darwin`
52//! - `aarch64-apple-darwin`
53//! - `x86_64-pc-windows-msvc`
54//! - `aarch64-unknown-linux-musl`
55//!
56//! Chrome automation is not supported on `wasm32-unknown-unknown`.
57//!
58//! ## MSRV
59//!
60//! Minimum Supported Rust Version is **1.88.0** (`rust-version` in `Cargo.toml`).
61//!
62//! ## Safety
63//!
64//! - No remote telemetry is emitted by this crate
65//! - Local tracing stays on stderr only
66//! - `docs.rs` builds pass `--cfg docsrs` and may enable `doc_cfg` on nightly
67//! - Unix paths may call `libc` for signal defaults and last-resort process kill
68//!
69//! ## Error handling
70//!
71//! Public errors use [`error::CliError`] with sysexits-style exit codes.
72//! JSON agents should parse the envelope from [`envelope`].
73//!
74//! ## Examples
75//!
76//! ```no_run
77//! use browser_automation_cli::error::{CliError, ErrorKind};
78//! use browser_automation_cli::exit_code_for;
79//!
80//! let err = CliError::new(ErrorKind::Unavailable, "chrome not found");
81//! assert_eq!(exit_code_for(&err), 69);
82//! ```
83//!
84//! ## See also
85//!
86//! - Crate README and `docs/HOW_TO_USE.md`
87//! - `docs/schemas/` for JSON contracts
88//! - `skill/browser-automation-cli-en/SKILL.md` for agent skill surface
89
90#![cfg_attr(docsrs, feature(doc_cfg))]
91#![warn(missing_docs)]
92#![warn(rustdoc::missing_crate_level_docs)]
93#![deny(rustdoc::broken_intra_doc_links)]
94#![warn(rustdoc::private_intra_doc_links)]
95#![deny(rustdoc::invalid_html_tags)]
96#![deny(rustdoc::invalid_rust_codeblocks)]
97#![deny(rustdoc::bare_urls)]
98#![warn(rustdoc::redundant_explicit_links)]
99
100/// Chrome one-shot session: launch, actions, reap.
101pub mod browser;
102/// Clap derive surface and global flags.
103pub mod cli;
104/// ANSI color helpers for human stderr diagnostics.
105pub mod color;
106/// PRD command dispatch (meta paths and browser one-shot).
107pub mod commands_prd;
108/// Shared constants (schema version, product name).
109pub mod constants;
110/// Local install diagnostics (`doctor`).
111pub mod doctor;
112/// JSON success/error envelopes for agents.
113pub mod envelope;
114/// Typed CLI errors and exit codes.
115pub mod error;
116/// Locale messaging helpers.
117pub mod i18n;
118/// Install path helpers for doctor and packaging checks.
119pub mod install;
120/// Cooperative cancel and FINALIZE ledger.
121pub mod lifecycle;
122/// Native CDP stack (browser, network, snapshot, heap).
123pub mod native;
124/// robots.txt policy enforcement.
125pub mod robots;
126/// Input and path validation helpers.
127pub mod validation;
128
129#[cfg(test)]
130#[allow(missing_docs)]
131pub mod test_utils;
132
133use std::process::ExitCode;
134
135use clap::{CommandFactory, Parser};
136
137use crate::cli::Cli;
138use crate::error::CliError;
139use crate::lifecycle::Lifecycle;
140
141/// Parse argv and run the one-shot CLI.
142///
143/// Always attempts FINALIZE before returning, including on clap help/version paths.
144///
145/// # Lifecycle
146///
147/// ```mermaid
148/// flowchart LR
149///   NASCE --> EXECUTA --> FINALIZE --> MORRE
150/// ```
151///
152/// # Returns
153///
154/// Process [`ExitCode`] mapped from sysexits-style CLI codes.
155#[cfg_attr(doc, aquamarine::aquamarine)]
156pub fn run() -> ExitCode {
157    #[cfg(unix)]
158    unsafe {
159        libc::signal(libc::SIGPIPE, libc::SIG_DFL);
160    }
161
162    #[cfg(windows)]
163    {
164        std::env::set_var("MSYS_NO_PATHCONV", "1");
165        std::env::set_var("MSYS2_ARG_CONV_EXCL", "*");
166    }
167
168    let life = Lifecycle::new();
169
170    let cli = match Cli::try_parse() {
171        Ok(c) => c,
172        Err(e) => {
173            // clap: DisplayHelp/DisplayVersion → exit 0; usage errors → 2
174            let code = e.exit_code();
175            let _ = e.print();
176            life.finalize();
177            return ExitCode::from(code as u8);
178        }
179    };
180
181    init_tracing(cli.globals.quiet, cli.globals.verbose, cli.globals.debug);
182
183    let code = commands_prd::dispatch(cli, &life);
184    // finalize is called inside dispatch; call again is idempotent
185    life.finalize();
186    if code <= 0 {
187        ExitCode::SUCCESS
188    } else if code >= 256 {
189        ExitCode::from(255)
190    } else {
191        ExitCode::from(code as u8)
192    }
193}
194
195/// Run clap `debug_assert` on the command tree (tests and diagnostics).
196pub fn command_factory_debug_assert() {
197    Cli::command().debug_assert();
198}
199
200/// Map a [`CliError`] to its process exit code without parsing argv.
201///
202/// Useful for unit tests and library callers that already hold a typed error.
203pub fn exit_code_for(err: &CliError) -> u8 {
204    err.exit_code()
205}
206
207/// Local-only tracing on stderr (zero remote telemetry).
208fn init_tracing(quiet: bool, verbose: bool, debug: bool) {
209    use tracing_subscriber::EnvFilter;
210
211    let filter = if quiet {
212        EnvFilter::new("error")
213    } else if let Ok(from_env) = std::env::var("RUST_LOG") {
214        EnvFilter::new(from_env)
215    } else if debug {
216        EnvFilter::new("debug")
217    } else if verbose {
218        EnvFilter::new("info")
219    } else {
220        EnvFilter::new("error")
221    };
222
223    let _ = tracing_subscriber::fmt()
224        .with_env_filter(filter)
225        .with_writer(std::io::stderr)
226        .with_target(false)
227        .try_init();
228}