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