Codex Handbook
cli/src/doctor/output.rs 1705 lines
//! Renders doctor reports for terminal users.//!//! The renderer is intentionally separate from check construction so the JSON//! report can stay stable while the human view optimizes for scanability. It//! groups checks by concern, colors only status/actionable tokens, and redacts//! sensitive detail lines before showing them in detailed output.mod detail;use std::fmt::Write as _;use detail::HumanDetail;use detail::detail_lines;use owo_colors::OwoColorize;use owo_colors::XtermColors;use super::CheckStatus;use super::DoctorCheck;use super::DoctorReport;const NAME_WIDTH: usize = 12;const DETAIL_LABEL_WIDTH: usize = 24;const SEPARATOR_WIDTH: usize = 61;const GROUPS: &[OutputGroup] = &[    OutputGroup {        title: "Environment",        keys: &[            "system", "runtime", "install", "search", "git", "terminal", "title", "state",            "threads",        ],    },    OutputGroup {        title: "Configuration",        keys: &["config", "auth", "mcp", "sandbox"],    },    OutputGroup {        title: "Updates",        keys: &["updates"],    },    OutputGroup {        title: "Connectivity",        keys: &["network", "websocket", "reachability"],    },    OutputGroup {        title: "Background Server",        keys: &["app-server"],    },];struct OutputGroup {    title: &'static str,    keys: &'static [&'static str],}/// Rendering controls for human doctor output.////// These options affect presentation only. They must not change which checks/// run or which fields are present in the underlying JSON report.#[derive(Clone, Copy, Debug)]pub(super) struct HumanOutputOptions {    pub(super) show_details: bool,    pub(super) show_all: bool,    pub(super) ascii: bool,    pub(super) color_enabled: bool,}/// Formats a doctor report into the grouped terminal layout.////// The renderer expects checks to carry stable categories, but it owns their/// display order. Adding a new category without adding it to GROUPS keeps JSON/// output intact but hides that row from the human view.pub(super) fn render_human_report(report: &DoctorReport, options: HumanOutputOptions) -> String {    let mut out = String::new();    let _ = writeln!(        out,        "{} {}",        bold("Codex Doctor", options),        dim(&header_suffix(report), options)    );    out.push('\n');    let notes = notes_for_report(report);    if !notes.is_empty() {        let _ = writeln!(out, "{}", bold("Notes", options));        for note in &notes {            write_note_row(&mut out, note, options);        }        let _ = writeln!(out, "{}", dim(&separator(options), options));        out.push('\n');    }    let mut wrote_group = false;    for group in GROUPS {        let group_checks = checks_for_group(report, group);        if group_checks.is_empty() {            continue;        }        if wrote_group {            out.push('\n');        }        wrote_group = true;        let _ = writeln!(out, "{}", bold(group.title, options));        for check in group_checks {            write_check_row(&mut out, check, options);        }    }    out.push('\n');    let _ = writeln!(out, "{}", dim(&separator(options), options));    let _ = writeln!(out, "{}", summary_line(report, options));    out.push('\n');    write_footer(&mut out, options);    out}fn checks_for_group<'a>(report: &'a DoctorReport, group: &OutputGroup) -> Vec<&'a DoctorCheck> {    group        .keys        .iter()        .flat_map(|key| {            report                .checks                .iter()                .filter(move |check| check.category == *key)        })        .collect()}fn write_check_row(out: &mut String, check: &DoctorCheck, options: HumanOutputOptions) {    let description = row_description(check, options);    let status = display_status(check);    let _ = writeln!(        out,        "  {}{} {}",        status_marker_slot(status, options),        format_args!("{:<NAME_WIDTH$}", check.category),        style_description(&description, status, options)    );    if options.show_details {        for detail in detail_lines(check, options) {            write_detail_line(out, detail, options);        }    }}fn write_note_row(out: &mut String, note: &DoctorNote, options: HumanOutputOptions) {    let _ = writeln!(        out,        "   {}{} {}",        status_marker_slot(note.status, options),        format_args!("{:<NAME_WIDTH$}", note.name),        style_note_summary(note, options)    );}fn write_detail_line(out: &mut String, detail: HumanDetail, options: HumanOutputOptions) {    match detail {        HumanDetail::Row {            label,            value,            expected,        } => {            let is_issue = expected.is_some();            let label = format!("{label:<DETAIL_LABEL_WIDTH$}");            let value = if let Some(expected) = expected {                format!(                    "{} {}",                    detail_value(&value, options),                    dim(&format!("(expected {expected})"), options)                )            } else {                detail_value(&value, options)            };            let _ = writeln!(                out,                "    {} {} {}",                detail_marker(is_issue, options),                detail_label(&label, options),                value            );        }        HumanDetail::Continuation(value) => {            let spacer = " ".repeat(DETAIL_LABEL_WIDTH);            let _ = writeln!(                out,                "      {} {}",                detail_label(&spacer, options),                detail_value(&value, options)            );        }        HumanDetail::Bullet(value) => {            let _ = writeln!(                out,                "    {} {}",                very_dim(if options.ascii { "-" } else { "·" }, options),                dim(&highlight_actions(&value, options), options)            );        }        HumanDetail::Remedy(value) => {            let marker = if options.ascii { "->" } else { "→" };            let _ = writeln!(                out,                "    {} {}",                orange(marker, options),                highlight_actions(&value, options)            );        }    }}fn row_description(check: &DoctorCheck, options: HumanOutputOptions) -> String {    if matches!(check.status, CheckStatus::Warning | CheckStatus::Fail) && !check.issues.is_empty()    {        return issue_summary(check);    }    if matches!(check.status, CheckStatus::Warning | CheckStatus::Fail)        && let Some(remediation) = &check.remediation    {        let dash = if options.ascii { " - " } else { " — " };        let summary = &check.summary;        return format!("{summary}{dash}{remediation}");    }    display_summary(check, options)}fn issue_summary(check: &DoctorCheck) -> String {    match check.issues.as_slice() {        [] => check.summary.clone(),        [issue] => issue.cause.clone(),        issues => format!(            "{} issues - {}",            issues.len(),            issues                .iter()                .take(2)                .map(|issue| issue.cause.as_str())                .collect::<Vec<_>>()                .join("; ")        ),    }}#[derive(Clone, Copy, Debug, Eq, PartialEq)]enum DisplayStatus {    Ok,    Update,    Note,    Warning,    Fail,    Idle,}struct DoctorNote {    status: DisplayStatus,    name: String,    summary: String,}fn display_status(check: &DoctorCheck) -> DisplayStatus {    if check.category == "app-server"        && check.status == CheckStatus::Ok        && check            .details            .iter()            .any(|detail| detail == "status: not running")    {        return DisplayStatus::Idle;    }    match check.status {        CheckStatus::Ok => DisplayStatus::Ok,        CheckStatus::Warning => DisplayStatus::Warning,        CheckStatus::Fail => DisplayStatus::Fail,    }}fn status_marker(status: DisplayStatus, options: HumanOutputOptions) -> String {    let marker = if options.ascii {        match status {            DisplayStatus::Ok => "[ok]",            DisplayStatus::Update => "[up]",            DisplayStatus::Note | DisplayStatus::Warning => "[!!]",            DisplayStatus::Fail => "[XX]",            DisplayStatus::Idle => "[--]",        }    } else {        match status {            DisplayStatus::Ok => "✓",            DisplayStatus::Update => "↑",            DisplayStatus::Note | DisplayStatus::Warning => "⚠",            DisplayStatus::Fail => "✗",            DisplayStatus::Idle => "○",        }    };    match status {        DisplayStatus::Ok => green(marker, options),        DisplayStatus::Update => amber(marker, options),        DisplayStatus::Note | DisplayStatus::Warning => orange(marker, options),        DisplayStatus::Fail => red(marker, options),        DisplayStatus::Idle => dim(marker, options),    }}fn status_marker_slot(status: DisplayStatus, options: HumanOutputOptions) -> String {    let marker = status_marker(status, options);    format!("{marker} ")}fn style_description(    description: &str,    status: DisplayStatus,    options: HumanOutputOptions,) -> String {    let highlighted = highlight_actions(description, options);    match status {        DisplayStatus::Ok | DisplayStatus::Idle => dim(&highlighted, options),        DisplayStatus::Update => amber(&highlighted, options),        DisplayStatus::Note | DisplayStatus::Warning | DisplayStatus::Fail => highlighted,    }}fn detail_marker(is_issue: bool, options: HumanOutputOptions) -> String {    if !is_issue {        return " ".to_string();    }    orange(if options.ascii { ">" } else { "▸" }, options)}fn style_note_summary(note: &DoctorNote, options: HumanOutputOptions) -> String {    if note.status == DisplayStatus::Update {        return style_update_note_summary(&note.summary, options);    }    style_description(&note.summary, note.status, options)}fn style_update_note_summary(summary: &str, options: HumanOutputOptions) -> String {    if !options.color_enabled {        return summary.to_string();    }    let Some((version, rest)) = summary.split_once(" available") else {        return amber(summary, options);    };    let Some((action, parenthetical)) = rest.split_once(" (") else {        return format!(            "{}{}",            amber(&format!("{version} available"), options),            amber(rest, options)        );    };    format!(        "{}{} {}",        amber(&format!("{version} available"), options),        amber(action, options),        dim(&format!("({parenthetical}"), options)    )}fn summary_line(report: &DoctorReport, options: HumanOutputOptions) -> String {    let notes = notes_for_report(report);    let counts = StatusCounts::from_report(report, notes.len());    let separator = dim(if options.ascii { " | " } else { " · " }, options);    let status = overall_status_label(report.overall_status);    let mut parts = vec![count_label(counts.ok, "ok", DisplayStatus::Ok, options)];    if counts.idle > 0 {        parts.push(count_label(            counts.idle,            "idle",            DisplayStatus::Idle,            options,        ));    }    if counts.notes > 0 {        parts.push(count_label(            counts.notes,            "notes",            DisplayStatus::Note,            options,        ));    }    parts.push(count_label(        counts.warning,        "warn",        DisplayStatus::Warning,        options,    ));    parts.push(count_label(        counts.fail,        "fail",        DisplayStatus::Fail,        options,    ));    format!(        "{} {}",        parts.join(&separator),        styled_overall_status(status, report.overall_status, options)    )}fn count_label(    count: usize,    label: &str,    status: DisplayStatus,    options: HumanOutputOptions,) -> String {    let count = dim(&count.to_string(), options);    let label = match status {        DisplayStatus::Ok => green(label, options),        DisplayStatus::Update => amber(label, options),        DisplayStatus::Note | DisplayStatus::Warning => orange(label, options),        DisplayStatus::Fail => red(label, options),        DisplayStatus::Idle => dim(label, options),    };    format!("{count} {label}")}fn overall_status_label(status: CheckStatus) -> &'static str {    match status {        CheckStatus::Ok => "ok",        CheckStatus::Warning => "degraded",        CheckStatus::Fail => "failed",    }}fn styled_overall_status(label: &str, status: CheckStatus, options: HumanOutputOptions) -> String {    if !options.color_enabled {        return label.to_string();    }    match status {        CheckStatus::Ok => label.green().bold().to_string(),        CheckStatus::Warning => label.yellow().bold().to_string(),        CheckStatus::Fail => label.red().bold().to_string(),    }}fn write_footer(out: &mut String, options: HumanOutputOptions) {    if options.show_details {        let _ = writeln!(            out,            "{} {:<24} {} {}",            cyan("--summary", options),            dim("compact output", options),            cyan("--all", options),            dim("expand truncated lists", options)        );    } else {        let _ = writeln!(            out,            "{}",            dim(                "Run codex doctor without --summary for detailed diagnostics.",                options            )        );        let _ = writeln!(            out,            "{} {:<28} {} {}",            cyan("--all", options),            dim("expand truncated lists", options),            cyan("--json", options),            dim("redacted report", options)        );        return;    }    let _ = writeln!(        out,        "{} {}",        cyan("--json", options),        dim("redacted report", options)    );}fn header_suffix(report: &DoctorReport) -> String {    let version = format!("v{}", report.codex_version);    report        .checks        .iter()        .find(|check| check.category == "runtime")        .and_then(|check| detail::detail_value(check, "platform"))        .map_or(version.clone(), |platform| {            format!("{version} · {platform}")        })}fn notes_for_report(report: &DoctorReport) -> Vec<DoctorNote> {    let mut notes = Vec::new();    if let Some(check) = find_check(report, "updates") {        update_note(check, report)            .into_iter()            .for_each(|note| notes.push(note));    }    if let Some(check) = find_check(report, "state") {        rollout_note(check)            .into_iter()            .for_each(|note| notes.push(note));    }    if let Some(check) = find_check(report, "sandbox") {        sandbox_note(check)            .into_iter()            .for_each(|note| notes.push(note));    }    non_ok_notes(report)        .into_iter()        .for_each(|note| notes.push(note));    auth_reachability_note(report)        .into_iter()        .for_each(|note| notes.push(note));    notes}fn find_check<'a>(report: &'a DoctorReport, category: &str) -> Option<&'a DoctorCheck> {    report        .checks        .iter()        .find(|check| check.category == category)}fn update_note(check: &DoctorCheck, report: &DoctorReport) -> Option<DoctorNote> {    let status = detail::detail_value(check, "latest version status")?;    if !status.contains("newer version is available") {        return None;    }    let latest = detail::detail_value(check, "latest version")        .or_else(|| detail::detail_value(check, "cached latest version"))        .unwrap_or_else(|| "newer version".to_string());    let dismissed = detail::detail_value(check, "dismissed version");    let mut parenthetical = format!("current {}", report.codex_version);    if let Some(dismissed) = dismissed        && !detail::is_falsy(&dismissed)    {        parenthetical.push_str(&format!(", dismissed {dismissed}"));    }    Some(DoctorNote {        status: DisplayStatus::Update,        name: "updates".to_string(),        summary: format!("{latest} available ({parenthetical})"),    })}fn rollout_note(check: &DoctorCheck) -> Option<DoctorNote> {    let active = detail::detail_value(check, "active rollout files")?;    let (files, bytes) = detail::rollout_files_and_bytes(&active)?;    if files < 1000 && bytes < 1024 * 1024 * 1024 {        return None;    }    Some(DoctorNote {        status: DisplayStatus::Warning,        name: "rollouts".to_string(),        summary: format!(            "{} active files · {} on disk",            detail::format_count(files),            detail::format_bytes(bytes)        ),    })}fn sandbox_note(check: &DoctorCheck) -> Option<DoctorNote> {    let filesystem = detail::detail_value(check, "filesystem sandbox")?;    let network = detail::detail_value(check, "network sandbox")?;    if filesystem == "restricted" && network == "restricted" {        return None;    }    Some(DoctorNote {        status: DisplayStatus::Warning,        name: "sandbox".to_string(),        summary: format!("filesystem {filesystem} · network {network}"),    })}fn non_ok_notes(report: &DoctorReport) -> Vec<DoctorNote> {    report        .checks        .iter()        .filter(|check| matches!(check.status, CheckStatus::Warning | CheckStatus::Fail))        .map(|check| DoctorNote {            status: display_status(check),            name: check.category.clone(),            summary: actionable_note_summary(check),        })        .collect()}fn actionable_note_summary(check: &DoctorCheck) -> String {    if !check.issues.is_empty() {        return issue_summary(check);    }    if let Some(remediation) = &check.remediation {        return format!("{} - {remediation}", check.summary);    }    check.summary.clone()}fn auth_reachability_note(report: &DoctorReport) -> Option<DoctorNote> {    let websocket = find_check(report, "websocket")?;    let reachability = find_check(report, "reachability")?;    let auth_mode = detail::detail_value(websocket, "auth mode")?;    let reachability_mode = detail::detail_value(reachability, "reachability mode")?;    let auth_mode_lower = auth_mode.to_ascii_lowercase();    let reachability_mode_lower = reachability_mode.to_ascii_lowercase();    if auth_mode_lower.contains("chatgpt") && reachability_mode_lower.contains("api key") {        return Some(DoctorNote {            status: DisplayStatus::Warning,            name: "auth".to_string(),            summary: "mixed auth signals: ChatGPT login plus API key env var; HTTP reachability uses API-key mode".to_string(),        });    }    None}fn display_summary(check: &DoctorCheck, options: HumanOutputOptions) -> String {    match check.category.as_str() {        "system" => system_summary(check),        "runtime" => runtime_summary(check),        "install" if check.status == CheckStatus::Ok => "consistent".to_string(),        "search" => search_summary(check),        "git" => git_summary(check),        "terminal" => terminal_summary(check),        "title" => title_summary(check, options),        "state" => state_summary(check),        "config" if check.status == CheckStatus::Ok => "loaded".to_string(),        "mcp" => mcp_summary(check),        "sandbox" => sandbox_summary(check),        "network" => network_summary(check),        "websocket" => websocket_summary(check),        "app-server" => app_server_summary(check),        _ => check.summary.clone(),    }}fn system_summary(check: &DoctorCheck) -> String {    detail::detail_value(check, "os language").unwrap_or_else(|| check.summary.clone())}fn runtime_summary(check: &DoctorCheck) -> String {    if detail::detail_value(check, "current executable")        .is_some_and(|path| path.contains("/target/debug/"))    {        return "local debug build".to_string();    }    detail::detail_value(check, "install method").unwrap_or_else(|| check.summary.clone())}fn search_summary(check: &DoctorCheck) -> String {    let provider = detail::detail_value(check, "search provider");    let command = detail::detail_value(check, "search command");    let readiness = detail::detail_value(check, "search command readiness");    match (readiness, provider, command) {        (Some(readiness), Some(provider), Some(command)) if check.status == CheckStatus::Ok => {            format!("{readiness} ({provider}, `{command}`)")        }        _ => check.summary.clone(),    }}fn git_summary(check: &DoctorCheck) -> String {    detail::detail_value(check, "git version")        .or_else(|| detail::detail_value(check, "selected git"))        .unwrap_or_else(|| check.summary.clone())}fn terminal_summary(check: &DoctorCheck) -> String {    let mut parts = Vec::new();    if let Some(terminal) = detail::detail_value(check, "terminal") {        let version = detail::detail_value(check, "terminal version");        parts.push(version.map_or(terminal.clone(), |version| format!("{terminal} {version}")));    }    if let Some(multiplexer) = detail::detail_value(check, "multiplexer") {        parts.push(multiplexer);    }    if let Some(term) = detail::detail_value(check, "TERM") {        parts.push(format!("TERM={term}"));    }    if parts.is_empty() {        check.summary.clone()    } else {        parts.join(" · ")    }}fn title_summary(check: &DoctorCheck, options: HumanOutputOptions) -> String {    let source = detail::detail_value(check, "terminal title source");    let project = detail::detail_value(check, "terminal title project value");    match (source, project) {        (Some(source), Some(project)) => {            let separator = if options.ascii { " | " } else { " · " };            format!("{source}{separator}project {project}")        }        (Some(source), None) => source,        _ => check.summary.clone(),    }}fn state_summary(check: &DoctorCheck) -> String {    let databases_ok = [        "state DB integrity",        "log DB integrity",        "goals DB integrity",        "memories DB integrity",    ]    .into_iter()    .all(|label| detail::detail_value(check, label).is_some_and(|value| value == "ok"));    if check.status == CheckStatus::Ok && databases_ok {        "databases healthy".to_string()    } else {        check.summary.clone()    }}fn mcp_summary(check: &DoctorCheck) -> String {    let Some(count) = detail::detail_value(check, "configured servers") else {        return check.summary.clone();    };    let disabled =        detail::detail_value(check, "disabled servers").unwrap_or_else(|| "0".to_string());    let transports = check        .details        .iter()        .filter_map(|detail| detail.split_once(" servers: "))        .filter(|(transport, _)| *transport != "configured" && *transport != "disabled")        .map(|(transport, count)| format!("{count} {transport}"))        .collect::<Vec<_>>();    if transports.is_empty() {        format!("{count} servers · {disabled} disabled")    } else {        format!(            "{} server ({}) · {} disabled",            count,            transports.join(", "),            disabled        )    }}fn sandbox_summary(check: &DoctorCheck) -> String {    let approval = detail::detail_value(check, "approval policy");    let filesystem = detail::detail_value(check, "filesystem sandbox");    let network = detail::detail_value(check, "network sandbox");    match (approval, filesystem, network) {        (Some(approval), Some(filesystem), Some(network)) => {            format!("{filesystem} fs + {network} network · approval {approval}")        }        _ => check.summary.clone(),    }}fn network_summary(check: &DoctorCheck) -> String {    detail::detail_value(check, "proxy env vars")        .map(|value| {            if value == "none" {                "no proxy env vars".to_string()            } else {                "proxy env vars present".to_string()            }        })        .unwrap_or_else(|| check.summary.clone())}fn websocket_summary(check: &DoctorCheck) -> String {    let status = detail::detail_value(check, "handshake result")        .or_else(|| detail::detail_value(check, "handshake status"));    let timeout = detail::detail_value(check, "connect timeout")        .map(|value| value.replace("000 ms", "s").replace(" ms", "ms"));    match (status, timeout) {        (Some(status), Some(timeout)) => format!("connected ({status}) · {timeout} timeout"),        _ => check.summary.clone(),    }}fn app_server_summary(check: &DoctorCheck) -> String {    let status = detail::detail_value(check, "status");    let mode = detail::detail_value(check, "mode");    match (status, mode) {        (Some(status), Some(mode)) => format!("{status} ({mode} mode)"),        _ => check.summary.clone(),    }}fn separator(options: HumanOutputOptions) -> String {    if options.ascii {        "-".repeat(SEPARATOR_WIDTH)    } else {        "─".repeat(SEPARATOR_WIDTH)    }}fn highlight_actions(text: &str, options: HumanOutputOptions) -> String {    if !options.color_enabled {        return text.to_string();    }    let mut out = String::new();    let mut parts = text.split('`');    if let Some(first) = parts.next() {        out.push_str(&highlight_flags(first, options));    }    let mut in_code = true;    for part in parts {        if in_code {            out.push_str(&cyan(part, options));        } else {            out.push_str(&highlight_flags(part, options));        }        in_code = !in_code;    }    out}fn highlight_flags(text: &str, options: HumanOutputOptions) -> String {    text.split_inclusive(char::is_whitespace)        .map(|token| {            let trimmed = token.trim_end();            let suffix = &token[trimmed.len()..];            let bare = trimmed.trim_end_matches([',', '.', ':', ';', ')']);            let punctuation = &trimmed[bare.len()..];            if bare.starts_with("--") {                let highlighted = cyan(bare, options);                format!("{highlighted}{punctuation}{suffix}")            } else {                token.to_string()            }        })        .collect()}pub(super) fn redact_detail(detail: &str) -> String {    let lower = detail.to_ascii_lowercase();    let label = lower.split(':').next().unwrap_or_default();    if label.contains("env var") {        return redact_urls(detail);    }    if detail        .split_once(": ")        .is_some_and(|(_, value)| is_safe_presence_value(value))    {        return redact_urls(detail);    }    let secret_keys = [        "openai_api_key",        "codex_api_key",        "codex_access_token",        "authorization",        "bearer_token",        "token",        "secret",    ];    if secret_keys.iter().any(|key| lower.contains(key)) {        let name = detail.split(':').next().unwrap_or(detail);        format!("{name}: <redacted>")    } else {        redact_urls(detail)    }}fn is_safe_presence_value(value: &str) -> bool {    matches!(        value.trim().to_ascii_lowercase().as_str(),        "true" | "false" | "yes" | "no" | "present" | "absent" | "missing" | "not set"    )}fn redact_urls(detail: &str) -> String {    detail        .split_inclusive(char::is_whitespace)        .map(redact_url_token)        .collect()}fn redact_url_token(token: &str) -> String {    let Some(scheme_end) = token.find("://") else {        return token.to_string();    };    let mut suffix_start = token.len();    while suffix_start > scheme_end + 3        && matches!(            token.as_bytes()[suffix_start - 1],            b' ' | b'\t' | b'\n' | b'\r' | b'.' | b',' | b';' | b':' | b')' | b']'        )    {        suffix_start -= 1;    }    let (body, suffix) = token.split_at(suffix_start);    let scheme_prefix_end = scheme_end + 3;    let rest = &body[scheme_prefix_end..];    let authority_end = rest        .find(['/', '?', '#'])        .map(|index| scheme_prefix_end + index)        .unwrap_or(body.len());    let authority = &body[scheme_prefix_end..authority_end];    let authority = authority        .rsplit_once('@')        .map_or(authority, |(_, host)| host);    let path = &body[authority_end..];    let path = path        .find(['?', '#'])        .map(|index| &path[..index])        .unwrap_or(path);    let path = redact_url_path(path);    format!(        "{}{}{}{}",        &body[..scheme_prefix_end],        authority,        path,        suffix    )}fn redact_url_path(path: &str) -> String {    let mut segments = path.split('/').filter(|segment| !segment.is_empty());    let Some(first_segment) = segments.next() else {        return path.to_string();    };    if segments.next().is_some() {        format!("/{first_segment}/<redacted>")    } else {        path.to_string()    }}#[derive(Default)]struct StatusCounts {    ok: usize,    idle: usize,    notes: usize,    warning: usize,    fail: usize,}impl StatusCounts {    fn from_report(report: &DoctorReport, notes: usize) -> Self {        let mut counts = Self {            notes,            ..Self::default()        };        for check in &report.checks {            match display_status(check) {                DisplayStatus::Ok => counts.ok += 1,                DisplayStatus::Idle => counts.idle += 1,                DisplayStatus::Warning => counts.warning += 1,                DisplayStatus::Fail => counts.fail += 1,                DisplayStatus::Update | DisplayStatus::Note => {}            }        }        counts    }}fn bold(text: &str, options: HumanOutputOptions) -> String {    if options.color_enabled {        text.bold().to_string()    } else {        text.to_string()    }}fn dim(text: &str, options: HumanOutputOptions) -> String {    if options.color_enabled {        text.dimmed().to_string()    } else {        text.to_string()    }}fn very_dim(text: &str, options: HumanOutputOptions) -> String {    color256(text, /*code*/ 238, options)}fn detail_label(text: &str, options: HumanOutputOptions) -> String {    color256(text, /*code*/ 240, options)}fn detail_value(text: &str, options: HumanOutputOptions) -> String {    if !options.color_enabled {        return text.to_string();    }    style_detail_text(text, options)}fn style_detail_text(text: &str, options: HumanOutputOptions) -> String {    let mut out = String::new();    let mut parts = text.split('`');    if let Some(first) = parts.next() {        out.push_str(&style_detail_plain_text(first, options));    }    let mut in_code = true;    for part in parts {        if in_code {            out.push_str(&cyan(part, options));        } else {            out.push_str(&style_detail_plain_text(part, options));        }        in_code = !in_code;    }    out}fn style_detail_plain_text(text: &str, options: HumanOutputOptions) -> String {    text.split_inclusive(char::is_whitespace)        .map(|token| style_detail_token(token, options))        .collect()}fn style_detail_token(token: &str, options: HumanOutputOptions) -> String {    let trimmed = token.trim_end();    let suffix = &token[trimmed.len()..];    let bare = trimmed.trim_end_matches([',', '.', ':', ';', ')']);    let punctuation = &trimmed[bare.len()..];    let styled = style_detail_bare_token(bare, options);    format!("{styled}{punctuation}{suffix}")}fn style_detail_bare_token(bare: &str, options: HumanOutputOptions) -> String {    if bare.is_empty() {        return String::new();    }    if bare == "<redacted>" {        return color256(&bare.italic().to_string(), /*code*/ 244, options);    }    if bare.contains("(missing)") || detail::is_falsy(bare) {        return color256(bare, /*code*/ 240, options);    }    if let Some((label, value)) = bare.split_once(':')        && detail::is_falsy(value)    {        return format!("{label}:{}", color256(value, /*code*/ 240, options));    }    if bare == "ok" {        return green(bare, options);    }    if bare.starts_with("--") || looks_copyable(bare) {        return cyan(bare, options);    }    if matches!(bare, "B" | "KB" | "MB" | "GB" | "TB" | "files" | "file") {        return dim(bare, options);    }    bare.to_string()}fn green(text: &str, options: HumanOutputOptions) -> String {    color256(text, /*code*/ 10, options)}fn amber(text: &str, options: HumanOutputOptions) -> String {    color256(text, /*code*/ 220, options)}fn orange(text: &str, options: HumanOutputOptions) -> String {    color256(text, /*code*/ 214, options)}fn red(text: &str, options: HumanOutputOptions) -> String {    color256(text, /*code*/ 196, options)}fn cyan(text: &str, options: HumanOutputOptions) -> String {    color256(text, /*code*/ 117, options)}fn color256(text: &str, code: u8, options: HumanOutputOptions) -> String {    if options.color_enabled {        text.color(XtermColors::from(code)).to_string()    } else {        text.to_string()    }}fn looks_copyable(text: &str) -> bool {    text.starts_with("http://")        || text.starts_with("https://")        || text.starts_with("wss://")        || text.starts_with("~/")        || text.starts_with('/')        || text.starts_with("./")        || text.starts_with("../")}#[cfg(test)]mod tests {    use pretty_assertions::assert_eq;    use super::*;    fn detailed_no_color_unicode_options() -> HumanOutputOptions {        HumanOutputOptions {            show_details: true,            show_all: false,            ascii: false,            color_enabled: false,        }    }    fn summary_no_color_unicode_options() -> HumanOutputOptions {        HumanOutputOptions {            show_details: false,            show_all: false,            ascii: false,            color_enabled: false,        }    }    fn detailed_all_no_color_unicode_options() -> HumanOutputOptions {        HumanOutputOptions {            show_details: true,            show_all: true,            ascii: false,            color_enabled: false,        }    }    fn detailed_color_unicode_options() -> HumanOutputOptions {        HumanOutputOptions {            show_details: true,            show_all: false,            ascii: false,            color_enabled: true,        }    }    fn sample_report() -> DoctorReport {        let checks = vec![            DoctorCheck::new(                "system.environment",                "system",                CheckStatus::Ok,                "OS language en-US",            )            .detail("os: macOS 15.0")            .detail("os language: en-US")            .detail("VISUAL: code --wait")            .detail("EDITOR: vim")            .detail("PAGER: less -R")            .detail("GIT_PAGER: delta")            .detail("GH_PAGER: less")            .detail("LESS: -FRX"),            DoctorCheck::new(                "runtime.provenance",                "runtime",                CheckStatus::Ok,                "running local build on darwin-arm64",            ),            DoctorCheck::new(                "installation",                "install",                CheckStatus::Ok,                "installation looks consistent",            ),            DoctorCheck::new(                "runtime.search",                "search",                CheckStatus::Ok,                "search is OK (bundled)",            ),            DoctorCheck::new(                "git.environment",                "git",                CheckStatus::Ok,                "git version 2.54.0",            )            .detail("selected git: /usr/bin/git")            .detail("git version: git version 2.54.0")            .detail("repo detected: true"),            DoctorCheck::new(                "terminal.env",                "terminal",                CheckStatus::Warning,                "narrow terminal",            ),            DoctorCheck::new(                "terminal.title",                "title",                CheckStatus::Ok,                "terminal title default",            )            .detail("terminal title source: default")            .detail("terminal title items: activity, project-name")            .detail("terminal title project value: codex"),            DoctorCheck::new(                "state.paths",                "state",                CheckStatus::Ok,                "state paths inspectable",            ),            DoctorCheck::new(                "auth.credentials",                "auth",                CheckStatus::Fail,                "token expired",            )            .detail("OPENAI_API_KEY: present")            .remediation("Run `codex login`."),            DoctorCheck::new(                "updates.status",                "updates",                CheckStatus::Ok,                "update configuration is locally consistent",            ),            DoctorCheck::new(                "network.env",                "network",                CheckStatus::Ok,                "network environment readable",            ),            DoctorCheck::new(                "network.websocket_reachability",                "websocket",                CheckStatus::Ok,                "Responses WebSocket handshake succeeded",            ),            DoctorCheck::new(                "app_server.status",                "app-server",                CheckStatus::Ok,                "background server is not running",            ),            DoctorCheck::new(                "network.provider_reachability",                "reachability",                CheckStatus::Ok,                "active provider endpoints are reachable over HTTP",            ),        ];        DoctorReport {            schema_version: 1,            generated_at: "0s since unix epoch".to_string(),            overall_status: CheckStatus::Fail,            codex_version: "0.0.0".to_string(),            checks,        }    }    #[test]    fn render_human_report_includes_details_by_default_without_color() {        let rendered = render_human_report(&sample_report(), detailed_no_color_unicode_options());        let expected = format!(            "\Codex Doctor v0.0.0Notes   ⚠ terminal     narrow terminal   ✗ auth         token expired - Run `codex login`.─────────────────────────────────────────────────────────────Environment  ✓ system       en-US      os                       macOS 15.0      OS language              en-US      VISUAL                   code --wait      EDITOR                   vim      PAGER                    less -R      GIT_PAGER                delta      GH_PAGER                 less      LESS                     -FRX  ✓ runtime      running local build on darwin-arm64  ✓ install      consistent      managed by               npm: no · bun: no · package root —  ✓ search       search is OK (bundled)  ✓ git          git version 2.54.0      selected git             /usr/bin/git      version                  git version 2.54.0      repo detected            true  ⚠ terminal     narrow terminal  ✓ title        default · project codex      title source             default      title items              activity, project-name      project value            codex  ✓ state        state paths inspectableConfiguration  ✗ auth         token expired — Run `codex login`.      OPENAI_API_KEY           presentUpdates  ✓ updates      update configuration is locally consistentConnectivity  ✓ network      network environment readable  ✓ websocket    Responses WebSocket handshake succeeded  ✓ reachability active provider endpoints are reachable over HTTPBackground Server  ✓ app-server   background server is not running{}12 ok · 2 notes · 1 warn · 1 fail failed--summary compact output           --all expand truncated lists--json redacted report",            "─".repeat(SEPARATOR_WIDTH)        );        assert_eq!(rendered, expected);    }    #[test]    fn render_human_report_snapshot_covers_environment_rows() {        insta::assert_snapshot!(            "doctor_human_report_environment_rows",            render_human_report(&sample_report(), detailed_no_color_unicode_options())        );    }    #[test]    fn render_human_report_supports_summary_output_without_color() {        let rendered = render_human_report(&sample_report(), summary_no_color_unicode_options());        let expected = format!(            "\Codex Doctor v0.0.0Notes   ⚠ terminal     narrow terminal   ✗ auth         token expired - Run `codex login`.─────────────────────────────────────────────────────────────Environment  ✓ system       en-US  ✓ runtime      running local build on darwin-arm64  ✓ install      consistent  ✓ search       search is OK (bundled)  ✓ git          git version 2.54.0  ⚠ terminal     narrow terminal  ✓ title        default · project codex  ✓ state        state paths inspectableConfiguration  ✗ auth         token expired — Run `codex login`.Updates  ✓ updates      update configuration is locally consistentConnectivity  ✓ network      network environment readable  ✓ websocket    Responses WebSocket handshake succeeded  ✓ reachability active provider endpoints are reachable over HTTPBackground Server  ✓ app-server   background server is not running{}12 ok · 2 notes · 1 warn · 1 fail failedRun codex doctor without --summary for detailed diagnostics.--all expand truncated lists       --json redacted report",            "─".repeat(SEPARATOR_WIDTH)        );        assert_eq!(rendered, expected);    }    #[test]    fn render_human_report_includes_threads_row_in_environment() {        let mut report = sample_report();        report.checks.push(DoctorCheck::new(            "state.rollout_db_parity",            "threads",            CheckStatus::Warning,            "rollout files and state DB thread inventory differ",        ));        let rendered = render_human_report(&report, summary_no_color_unicode_options());        let threads_line = rendered            .lines()            .find(|line| line.contains("threads"))            .expect("threads row should be rendered");        assert!(            threads_line.contains("rollout files and state DB thread inventory differ"),            "{threads_line}"        );    }    #[test]    fn render_human_report_includes_memories_db_in_state_health_summary() {        let report = DoctorReport {            schema_version: 1,            generated_at: "0s since unix epoch".to_string(),            overall_status: CheckStatus::Ok,            codex_version: "0.0.0".to_string(),            checks: vec![                DoctorCheck::new(                    "state.paths",                    "state",                    CheckStatus::Ok,                    "state paths inspectable",                )                .detail("state DB: /tmp/state.sqlite")                .detail("state DB integrity: ok")                .detail("log DB: /tmp/logs.sqlite")                .detail("log DB integrity: ok")                .detail("goals DB: /tmp/goals.sqlite")                .detail("goals DB integrity: ok")                .detail("memories DB: /tmp/memories.sqlite")                .detail("memories DB integrity: ok"),            ],        };        let rendered = render_human_report(&report, detailed_no_color_unicode_options());        assert!(rendered.contains("✓ state        databases healthy"));        assert!(rendered.contains("memories DB              /tmp/memories.sqlite · integrity ok"));    }    #[test]    fn render_human_report_supports_ascii_output() {        let rendered = render_human_report(            &sample_report(),            HumanOutputOptions {                show_details: false,                show_all: false,                ascii: true,                color_enabled: false,            },        );        let expected = format!(            "\Codex Doctor v0.0.0Notes   [!!] terminal     narrow terminal   [XX] auth         token expired - Run `codex login`.-------------------------------------------------------------Environment  [ok] system       en-US  [ok] runtime      running local build on darwin-arm64  [ok] install      consistent  [ok] search       search is OK (bundled)  [ok] git          git version 2.54.0  [!!] terminal     narrow terminal  [ok] title        default | project codex  [ok] state        state paths inspectableConfiguration  [XX] auth         token expired - Run `codex login`.Updates  [ok] updates      update configuration is locally consistentConnectivity  [ok] network      network environment readable  [ok] websocket    Responses WebSocket handshake succeeded  [ok] reachability active provider endpoints are reachable over HTTPBackground Server  [ok] app-server   background server is not running{}12 ok | 2 notes | 1 warn | 1 fail failedRun codex doctor without --summary for detailed diagnostics.--all expand truncated lists       --json redacted report",            "-".repeat(SEPARATOR_WIDTH)        );        assert_eq!(rendered, expected);    }    #[test]    fn render_human_report_includes_redacted_details() {        let rendered = render_human_report(            &sample_report(),            HumanOutputOptions {                show_details: true,                show_all: false,                ascii: false,                color_enabled: false,            },        );        assert!(rendered.contains("      OPENAI_API_KEY           present"));    }    #[test]    fn render_human_report_explains_terminal_warning_issue() {        let report = DoctorReport {            schema_version: 1,            generated_at: "0s since unix epoch".to_string(),            overall_status: CheckStatus::Warning,            codex_version: "0.0.0".to_string(),            checks: vec![                DoctorCheck::new(                    "terminal.env",                    "terminal",                    CheckStatus::Warning,                    "width 79 cols - output may wrap (recommended >=80)",                )                .detail("terminal: Ghostty")                .detail("terminal version: 1.3.1")                .detail("terminal size: 79x26")                .issue(                    super::super::DoctorIssue::new(                        CheckStatus::Warning,                        "width 79 cols - output may wrap (recommended >=80)",                    )                    .expected(">= 80 columns")                    .remedy("resize the window to at least 80 columns")                    .field("terminal size"),                ),            ],        };        let rendered = render_human_report(&report, detailed_no_color_unicode_options());        assert!(            rendered.contains("⚠ terminal     width 79 cols - output may wrap (recommended >=80)")        );        assert!(rendered.contains("▸ terminal size            79x26 (expected >= 80 columns)"));        assert!(rendered.contains("→ resize the window to at least 80 columns"));        assert!(!rendered.contains("⚠ terminal     Ghostty 1.3.1"));    }    #[test]    fn render_human_report_promotes_notes_without_changing_statuses() {        let report = DoctorReport {            schema_version: 1,            generated_at: "0s since unix epoch".to_string(),            overall_status: CheckStatus::Warning,            codex_version: "0.0.0".to_string(),            checks: vec![                DoctorCheck::new(                    "updates.status",                    "updates",                    CheckStatus::Ok,                    "update configuration is locally consistent",                )                .detail("latest version status: newer version is available")                .detail("latest version: 0.130.0")                .detail("dismissed version: 0.128.0"),                DoctorCheck::new(                    "state.paths",                    "state",                    CheckStatus::Ok,                    "state paths inspectable",                )                .detail("active rollout files: 1515 files, 2702146365 total bytes, 1783594 average bytes"),                DoctorCheck::new(                    "sandbox.helpers",                    "sandbox",                    CheckStatus::Ok,                    "sandbox configuration is readable",                )                .detail("filesystem sandbox: danger-full-access")                .detail("network sandbox: restricted")                .detail("approval policy: Never"),                DoctorCheck::new(                    "mcp.config",                    "mcp",                    CheckStatus::Warning,                    "MCP configuration has optional issues",                ),                DoctorCheck::new(                    "network.websocket_reachability",                    "websocket",                    CheckStatus::Ok,                    "Responses WebSocket handshake succeeded",                )                .detail("auth mode: chatgpt"),                DoctorCheck::new(                    "network.provider_reachability",                    "reachability",                    CheckStatus::Ok,                    "active provider endpoints are reachable over HTTP",                )                .detail("reachability mode: API key auth"),                DoctorCheck::new(                    "app_server.status",                    "app-server",                    CheckStatus::Ok,                    "background server is not running",                )                .detail("status: not running")                .detail("mode: ephemeral"),            ],        };        let rendered = render_human_report(&report, summary_no_color_unicode_options());        assert!(rendered.contains("Notes\n   ↑ updates"));        assert!(rendered.contains("0.130.0 available (current 0.0.0, dismissed 0.128.0)"));        assert!(rendered.contains("⚠ rollouts"));        assert!(rendered.contains("⚠ sandbox"));        assert!(rendered.contains("⚠ mcp"));        assert!(rendered.contains(            "⚠ auth         mixed auth signals: ChatGPT login plus API key env var; HTTP reachability uses API-key mode"        ));        assert!(rendered.contains("○ app-server   not running (ephemeral mode)"));        assert!(rendered.contains("5 ok · 1 idle · 5 notes · 1 warn · 0 fail degraded"));    }    #[test]    fn render_human_report_expands_feature_flags_with_all() {        let report = DoctorReport {            schema_version: 1,            generated_at: "0s since unix epoch".to_string(),            overall_status: CheckStatus::Ok,            codex_version: "0.0.0".to_string(),            checks: vec![                DoctorCheck::new("config.load", "config", CheckStatus::Ok, "config loaded")                    .detail("model: gpt-5.5")                    .detail("model provider: openai")                    .detail("feature flags enabled: 3")                    .detail("enabled feature flags: shell_tool, memories, goals")                    .detail("feature flag overrides: memories=true"),            ],        };        let compact = render_human_report(&report, detailed_no_color_unicode_options());        let expanded = render_human_report(&report, detailed_all_no_color_unicode_options());        assert!(!compact.contains("enabled flags"));        assert!(            compact.contains(                "feature flags            3 enabled · 1 overridden (full list with --all)"            )        );        assert!(expanded.contains("enabled flags            shell_tool, memories, goals"));    }    #[test]    fn detail_value_colors_inline_statuses_and_low_signal_values() {        let rendered = detail_value(            "npm: no · commit unknown · integrity ok · ~/code/codex/target/debug/codex · <redacted>",            detailed_color_unicode_options(),        );        assert!(rendered.contains("npm: \u{1b}[38;5;240mno"));        assert!(rendered.contains("\u{1b}[38;5;240munknown"));        assert!(rendered.contains("\u{1b}[38;5;10mok"));        assert!(rendered.contains("\u{1b}[38;5;117m~/code/codex/target/debug/codex"));        assert!(rendered.contains("\u{1b}[38;5;244m"));    }    #[test]    fn update_note_emphasizes_available_version_and_dims_context() {        let rendered = style_update_note_summary(            "0.130.0 available (current 0.0.0, dismissed 0.128.0)",            detailed_color_unicode_options(),        );        assert!(rendered.contains("\u{1b}[38;5;220m0.130.0 available"));        assert!(rendered.contains("\u{1b}[2m(current 0.0.0, dismissed 0.128.0)"));    }    #[test]    fn redact_detail_sanitizes_urls() {        let redacted = redact_detail(            "reachability failed: https://user:pass@example.com/mcp?x=abc#frag (connect failed)",        );        assert_eq!(            redacted,            "reachability failed: https://example.com/mcp (connect failed)"        );    }    #[test]    fn redact_detail_sanitizes_secret_url_path_segments() {        let redacted = redact_detail("reachability failed: https://example.com/mcp/abc123xyz");        assert_eq!(            redacted,            "reachability failed: https://example.com/mcp/<redacted>"        );    }    #[test]    fn redact_detail_preserves_env_var_names() {        assert_eq!(            redact_detail("auth env vars present: OPENAI_API_KEY, CODEX_API_KEY"),            "auth env vars present: OPENAI_API_KEY, CODEX_API_KEY"        );    }    #[test]    fn redact_detail_preserves_secret_presence_booleans() {        assert_eq!(            redact_detail("stored ChatGPT tokens: true"),            "stored ChatGPT tokens: true"        );        assert_eq!(            redact_detail("stored ChatGPT tokens: false"),            "stored ChatGPT tokens: false"        );    }    #[test]    fn render_human_report_can_emit_color() {        let rendered = render_human_report(            &sample_report(),            HumanOutputOptions {                show_details: false,                show_all: false,                ascii: false,                color_enabled: true,            },        );        assert!(rendered.contains("\u{1b}["));    }}