aristo-cli 0.2.3

Aristo CLI binary (the `aristo` command).
Documentation
//! Interactive post-command notices: "a newer aristo is available" (from
//! crates.io) and "your installed skills are stale" (local).
//!
//! Both are gated the same way — they run only when stderr is a terminal,
//! `CI` is unset, and the user hasn't opted out via
//! `ARISTO_NO_UPDATE_NOTIFIER`. Notices print to stderr *after* the
//! command's real output, so machine-readable stdout is never touched and
//! pipelines stay deterministic.

use std::io::IsTerminal;

/// Env var that disables the post-command notices entirely (any value).
const OPT_OUT_ENV: &str = "ARISTO_NO_UPDATE_NOTIFIER";

/// Print any due notices to stderr. Best-effort and silent on every
/// failure — never affects the command's exit code.
pub fn maybe_notify() {
    if !enabled() {
        return;
    }
    // 1. A newer aristo release on crates.io (throttled, networked).
    if let Some(msg) = aristo_core::update_check::check(env!("CARGO_PKG_VERSION")) {
        eprintln!("{msg}");
    }
    // 2. Installed skills generated by an older aristo (local, no network).
    if let Some(msg) = crate::commands::install_skills::stale_skills_notice() {
        eprintln!("{msg}");
    }
}

/// Read the real environment and stderr, then defer to [`enabled_with`].
fn enabled() -> bool {
    enabled_with(
        std::env::var_os(OPT_OUT_ENV).is_some(),
        std::env::var_os("CI").is_some(),
        std::io::stderr().is_terminal(),
    )
}

/// Pure gate decision: notify only on an interactive terminal, with no
/// opt-out and no `CI` marker. Keeping this separate lets the matrix be
/// tested without mutating process env (the workspace forbids
/// `unsafe_code`, which `set_var` requires).
fn enabled_with(opt_out: bool, ci: bool, stderr_is_terminal: bool) -> bool {
    !opt_out && !ci && stderr_is_terminal
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn notifies_on_interactive_terminal_without_optout_or_ci() {
        assert!(enabled_with(false, false, true));
    }

    #[test]
    fn suppressed_when_stderr_not_a_terminal() {
        assert!(!enabled_with(false, false, false));
    }

    #[test]
    fn suppressed_in_ci_even_on_a_terminal() {
        assert!(!enabled_with(false, true, true));
    }

    #[test]
    fn suppressed_when_opted_out() {
        assert!(!enabled_with(true, false, true));
    }
}