//! doctor command: render the environment report, optionally with --fix. use super::*; use crate::doctor::{self, Status}; use crate::events::{Event, EventKind}; use crate::output::{boxed_table, print_json, status_style}; use crate::runner::SystemRunner; use anyhow::Result; pub fn run(app: &App, fix: bool, watch: Option) -> Result { if let Some(interval) = watch { return watch_loop(app, interval.max(1)); } if fix { let repaired = doctor::fix(app)?; for r in &repaired { app.log.success(r); } let runner = SystemRunner::new(false, app.cli.verbose, &app.log); for r in doctor::install_missing_tools(app, &runner)? { app.log.success(r); } app.log.info("run 'am doctor' again to verify"); return Ok(0); } // The doctor report probes real tools, even under --dry-run. let runner = SystemRunner::new(false, false, &app.log); let report = doctor::run(app, &runner); if !app.dry_run() { app.emit( &Event::now(EventKind::Doctor) .with_exit_code(if report.ok() { 0 } else { 1 }), ); } if app.json() { print_json(&report.checks); return Ok(if report.ok() { 0 } else { 1 }); } let color = app.color(); let theme = app.theme(); let mut table_rows = Vec::new(); for c in &report.checks { let status = match c.status { Status::Ok => status_style("ok", theme, color), Status::Warn => status_style("warn", theme, color), Status::Error => status_style("error", theme, color), }; table_rows.push(vec![c.name.clone(), status, c.detail.clone()]); } print!( "{}", boxed_table( vec!["CHECK", "STATUS", "DETAIL"], table_rows, theme, color, ) ); if report.problems() > 0 { app.log .warn(&format!("{} problem(s) found — 'am doctor --fix' repairs what it can", report.problems())); } else { app.log.success("environment looks good"); } Ok(if report.ok() { 0 } else { 1 }) } /// Issue #59: re-run the doctor checks every N seconds; alert (desktop /// notification + event) on the first failure, recover silently. Clean /// Ctrl-C via ctrlc. fn watch_loop(app: &App, interval: u64) -> Result { app.log .info(&format!("watching every {interval}s (Ctrl-C to stop)")); let mut was_ok = true; loop { let runner = SystemRunner::new(false, false, &app.log); let report = doctor::run(app, &runner); let ok = report.ok(); if !ok && was_ok { // First failure: alert. app.emit( &Event::now(EventKind::Doctor) .with_exit_code(1) .with_reason(format!("{} problem(s) found", report.problems())), ); app.log.error(&format!( "{} problem(s) found — 'am doctor --fix' repairs what it can", report.problems() )); let _ = crate::process::desktop_notify( "agent-manager", &format!("doctor: {} problem(s)", report.problems()), ); was_ok = false; } else if ok && !was_ok { app.log.success("environment recovered"); was_ok = true; } else if !ok { app.log .warn(&format!("still {} problem(s)", report.problems())); } std::thread::sleep(std::time::Duration::from_secs(interval)); } }