* 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
141 lines
3.9 KiB
Rust
141 lines
3.9 KiB
Rust
//! Check the local environment: CLI version, platform, state/socket dirs,
|
|
//! and free disk space.
|
|
|
|
use std::path::Path;
|
|
|
|
use super::helpers::{disk_free_bytes, human_size, is_writable_dir};
|
|
use super::{Check, Status};
|
|
use crate::connection::get_socket_dir;
|
|
use crate::native::state::get_state_dir;
|
|
|
|
pub(super) fn check(checks: &mut Vec<Check>) {
|
|
let category = "Environment";
|
|
|
|
let version = env!("CARGO_PKG_VERSION");
|
|
let platform = format!("{} {}", std::env::consts::OS, std::env::consts::ARCH);
|
|
checks.push(Check::new(
|
|
"env.version",
|
|
category,
|
|
Status::Pass,
|
|
format!("CLI version {} ({})", version, platform),
|
|
));
|
|
|
|
match dirs::home_dir() {
|
|
Some(home) => checks.push(Check::new(
|
|
"env.home",
|
|
category,
|
|
Status::Pass,
|
|
format!("Home directory {}", home.display()),
|
|
)),
|
|
None => checks.push(Check::new(
|
|
"env.home",
|
|
category,
|
|
Status::Fail,
|
|
"Could not determine home directory",
|
|
)),
|
|
}
|
|
|
|
let state_dir = get_state_dir();
|
|
let socket_dir = get_socket_dir();
|
|
|
|
// Under the default setup, state and socket dirs are the same
|
|
// (~/.agent-browser). Collapse to a single line when they match;
|
|
// split when XDG_RUNTIME_DIR or AGENT_BROWSER_SOCKET_DIR diverts
|
|
// sockets elsewhere.
|
|
if state_dir == socket_dir {
|
|
push_dir_check(
|
|
checks,
|
|
"env.state_dir",
|
|
category,
|
|
"State and socket directory",
|
|
&state_dir,
|
|
);
|
|
} else {
|
|
push_dir_check(
|
|
checks,
|
|
"env.state_dir",
|
|
category,
|
|
"State directory",
|
|
&state_dir,
|
|
);
|
|
push_dir_check(
|
|
checks,
|
|
"env.socket_dir",
|
|
category,
|
|
"Socket directory",
|
|
&socket_dir,
|
|
);
|
|
}
|
|
|
|
match disk_free_bytes(&state_dir) {
|
|
Some(bytes) => {
|
|
let mb = bytes / (1024 * 1024);
|
|
let human = human_size(bytes);
|
|
if mb < 500 {
|
|
checks.push(
|
|
Check::new(
|
|
"env.disk_free",
|
|
category,
|
|
Status::Warn,
|
|
format!("Low disk space at state dir: {} free", human),
|
|
)
|
|
.with_fix("free up disk space; Chrome installs require ~500 MB"),
|
|
);
|
|
} else {
|
|
checks.push(Check::new(
|
|
"env.disk_free",
|
|
category,
|
|
Status::Pass,
|
|
format!("{} free at state dir", human),
|
|
));
|
|
}
|
|
}
|
|
None => checks.push(Check::new(
|
|
"env.disk_free",
|
|
category,
|
|
Status::Info,
|
|
"Disk free check unavailable on this platform",
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn push_dir_check(
|
|
checks: &mut Vec<Check>,
|
|
id: &'static str,
|
|
category: &'static str,
|
|
label: &str,
|
|
dir: &Path,
|
|
) {
|
|
if dir.exists() {
|
|
if is_writable_dir(dir) {
|
|
checks.push(Check::new(
|
|
id,
|
|
category,
|
|
Status::Pass,
|
|
format!("{} {}", label, dir.display()),
|
|
));
|
|
} else {
|
|
checks.push(
|
|
Check::new(
|
|
id,
|
|
category,
|
|
Status::Fail,
|
|
format!("{} not writable: {}", label, dir.display()),
|
|
)
|
|
.with_fix(format!("chmod u+rwx {}", dir.display())),
|
|
);
|
|
}
|
|
} else {
|
|
checks.push(Check::new(
|
|
id,
|
|
category,
|
|
Status::Info,
|
|
format!(
|
|
"{} does not exist yet (will be created on first use): {}",
|
|
label,
|
|
dir.display()
|
|
),
|
|
));
|
|
}
|
|
}
|