* feat: add `doctor` command for install diagnostics and cleanup Adds `agent-browser doctor`, a one-shot diagnostic that checks environment, Chrome install, daemon state, config, encryption key, providers, network reachability, and a live headless launch test. Auto-cleans stale `.sock` / `.pid` / `.version` / `.stream` sidecar files on every run. Destructive repairs (reinstall Chrome, purge old state, close version-mismatched daemons, generate missing encryption key) are gated behind `--fix`. Supports `--offline`, `--quick`, and `--json`. * fixes
157 lines
4.9 KiB
Rust
157 lines
4.9 KiB
Rust
//! Check the Chrome install: binary path, version, cache dirs, user-data
|
|
//! dir, and the optional lightpanda engine.
|
|
|
|
use std::env;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use super::helpers::which_exists;
|
|
use super::{Check, Status};
|
|
|
|
pub(super) fn check(checks: &mut Vec<Check>) {
|
|
let category = "Chrome";
|
|
|
|
let chrome = crate::native::cdp::chrome::find_chrome();
|
|
match chrome {
|
|
Some(path) => {
|
|
let label = path.display().to_string();
|
|
match query_chrome_version(&path) {
|
|
Some(version) => checks.push(Check::new(
|
|
"chrome.installed",
|
|
category,
|
|
Status::Pass,
|
|
format!("{} at {}", version, label),
|
|
)),
|
|
None => checks.push(Check::new(
|
|
"chrome.installed",
|
|
category,
|
|
Status::Pass,
|
|
format!("Chrome at {} (version unknown)", label),
|
|
)),
|
|
}
|
|
}
|
|
None => checks.push(
|
|
Check::new(
|
|
"chrome.installed",
|
|
category,
|
|
Status::Fail,
|
|
"No Chrome binary found",
|
|
)
|
|
.with_fix("agent-browser install"),
|
|
),
|
|
}
|
|
|
|
let cache_dir = crate::install::get_browsers_dir();
|
|
if cache_dir.exists() {
|
|
checks.push(Check::new(
|
|
"chrome.cache_dir",
|
|
category,
|
|
Status::Info,
|
|
format!("Cache dir {}", cache_dir.display()),
|
|
));
|
|
}
|
|
|
|
if let Some(puppeteer_dir) = puppeteer_cache_dir() {
|
|
if puppeteer_dir.exists() {
|
|
checks.push(Check::new(
|
|
"chrome.puppeteer_cache",
|
|
category,
|
|
Status::Info,
|
|
format!(
|
|
"Puppeteer cache also present: {} (will be used as a fallback)",
|
|
puppeteer_dir.display()
|
|
),
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Some(user_data_dir) = crate::native::cdp::chrome::find_chrome_user_data_dir() {
|
|
let profiles = crate::native::cdp::chrome::list_chrome_profiles(&user_data_dir);
|
|
let count = profiles.len();
|
|
let dir_label = user_data_dir.display().to_string();
|
|
if count == 0 {
|
|
checks.push(Check::new(
|
|
"chrome.user_data_dir",
|
|
category,
|
|
Status::Info,
|
|
format!(
|
|
"Chrome user data dir found ({}), no profiles parsed",
|
|
dir_label
|
|
),
|
|
));
|
|
} else {
|
|
checks.push(Check::new(
|
|
"chrome.user_data_dir",
|
|
category,
|
|
Status::Info,
|
|
format!("{} Chrome profile(s) at {}", count, dir_label),
|
|
));
|
|
}
|
|
}
|
|
|
|
if let Ok(engine) = env::var("AGENT_BROWSER_ENGINE") {
|
|
if engine == "lightpanda" {
|
|
// Best-effort PATH lookup; absence is FAIL only when the user
|
|
// explicitly opted into the lightpanda engine.
|
|
if which_exists("lightpanda") {
|
|
checks.push(Check::new(
|
|
"chrome.engine_lightpanda",
|
|
category,
|
|
Status::Pass,
|
|
"Lightpanda binary on PATH",
|
|
));
|
|
} else {
|
|
checks.push(
|
|
Check::new(
|
|
"chrome.engine_lightpanda",
|
|
category,
|
|
Status::Fail,
|
|
"AGENT_BROWSER_ENGINE=lightpanda but no lightpanda binary on PATH",
|
|
)
|
|
.with_fix("install lightpanda or unset AGENT_BROWSER_ENGINE"),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn query_chrome_version(path: &Path) -> Option<String> {
|
|
let output = std::process::Command::new(path)
|
|
.arg("--version")
|
|
.output()
|
|
.ok()?;
|
|
if !output.status.success() {
|
|
return None;
|
|
}
|
|
let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
if s.is_empty() {
|
|
None
|
|
} else {
|
|
Some(s)
|
|
}
|
|
}
|
|
|
|
pub(super) fn puppeteer_cache_dir() -> Option<PathBuf> {
|
|
if let Ok(p) = env::var("PUPPETEER_CACHE_DIR") {
|
|
return Some(PathBuf::from(p));
|
|
}
|
|
dirs::home_dir().map(|h| h.join(".cache").join("puppeteer"))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_puppeteer_cache_dir_returns_sensible_default() {
|
|
// When PUPPETEER_CACHE_DIR is unset, we fall back to
|
|
// ~/.cache/puppeteer. Mutating env vars here would race with other
|
|
// tests, so just verify the fallback path is shaped correctly.
|
|
if env::var("PUPPETEER_CACHE_DIR").is_err() {
|
|
let dir = puppeteer_cache_dir().expect("home dir should resolve in tests");
|
|
let s = dir.to_string_lossy();
|
|
assert!(s.contains(".cache"));
|
|
assert!(s.ends_with("puppeteer"));
|
|
}
|
|
}
|
|
}
|