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 **BORN → EXECUTE → FINALIZE → DIE** 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/// Local Firecrawl-parity scrape/crawl/map/search/parse (HTTP + files).
121pub mod scrape_local;
122/// Local MITM capture, CA, and HAR export (one-shot).
123pub mod mitm_local;
124/// Workflow journal DAG (petgraph + SQLite), one-shot run/resume.
125pub mod workflow_local;
126/// XDG Base Directory paths and config file (no `.env` at runtime).
127pub mod xdg;
128/// Cooperative cancel and FINALIZE ledger.
129pub mod lifecycle;
130/// Native CDP stack (browser, network, snapshot, heap).
131pub mod native;
132/// robots.txt policy enforcement.
133pub mod robots;
134/// Input and path validation helpers.
135pub mod validation;
136
137#[cfg(test)]
138#[allow(missing_docs)]
139pub mod test_utils;
140
141use std::process::ExitCode;
142
143use clap::{CommandFactory, Parser};
144
145use crate::cli::Cli;
146use crate::error::CliError;
147use crate::lifecycle::Lifecycle;
148
149/// Parse argv and run the one-shot CLI.
150///
151/// Always attempts FINALIZE before returning, including on clap help/version paths.
152///
153/// # Lifecycle
154///
155/// ```mermaid
156/// flowchart LR
157/// BORN → EXECUTE → FINALIZE → DIE
158/// ```
159///
160/// # Returns
161///
162/// Process [`ExitCode`] mapped from sysexits-style CLI codes.
163#[cfg_attr(doc, aquamarine::aquamarine)]
164pub fn run() -> ExitCode {
165 #[cfg(unix)]
166 unsafe {
167 libc::signal(libc::SIGPIPE, libc::SIG_DFL);
168 }
169
170 #[cfg(windows)]
171 {
172 std::env::set_var("MSYS_NO_PATHCONV", "1");
173 std::env::set_var("MSYS2_ARG_CONV_EXCL", "*");
174 }
175
176 let life = Lifecycle::new();
177
178 let cli = match Cli::try_parse() {
179 Ok(c) => c,
180 Err(e) => {
181 // clap: DisplayHelp/DisplayVersion → exit 0; usage errors → 2
182 let code = e.exit_code();
183 let _ = e.print();
184 life.finalize();
185 return ExitCode::from(code as u8);
186 }
187 };
188
189 init_tracing(cli.globals.quiet, cli.globals.verbose, cli.globals.debug);
190
191 let code = commands_prd::dispatch(cli, &life);
192 // finalize is called inside dispatch; call again is idempotent
193 life.finalize();
194 if code <= 0 {
195 ExitCode::SUCCESS
196 } else if code >= 256 {
197 ExitCode::from(255)
198 } else {
199 ExitCode::from(code as u8)
200 }
201}
202
203/// Run clap `debug_assert` on the command tree (tests and diagnostics).
204pub fn command_factory_debug_assert() {
205 Cli::command().debug_assert();
206}
207
208/// Map a [`CliError`] to its process exit code without parsing argv.
209///
210/// Useful for unit tests and library callers that already hold a typed error.
211pub fn exit_code_for(err: &CliError) -> u8 {
212 err.exit_code()
213}
214
215/// Local-only tracing on stderr (zero remote telemetry).
216fn init_tracing(quiet: bool, verbose: bool, debug: bool) {
217 use tracing_subscriber::EnvFilter;
218
219 let filter = if quiet {
220 EnvFilter::new("error")
221 } else if let Ok(from_env) = std::env::var("RUST_LOG") {
222 EnvFilter::new(from_env)
223 } else if debug {
224 EnvFilter::new("debug")
225 } else if verbose {
226 EnvFilter::new("info")
227 } else {
228 EnvFilter::new("error")
229 };
230
231 let _ = tracing_subscriber::fmt()
232 .with_env_filter(filter)
233 .with_writer(std::io::stderr)
234 .with_target(false)
235 .try_init();
236}