vios_app 0.1.1

Small JSON vaults: Argon2id + AES-GCM, with optional AAD binding.
Documentation
use std::{
    fs,
    io::{self, Write},
    path::Path,
    thread,
    time::Duration,
};

/// CLI flags you can reuse across all binaries (`#[clap(flatten)]`)
#[derive(clap::Args, Debug, Clone)]
pub struct BootCli {
    /// Hide the banner
    #[arg(long)]
    pub no_banner: bool,

    /// Disable boot animation
    #[arg(long)]
    pub no_anim: bool,

    /// Disable environment probes (agents / vaults / reflex)
    #[arg(long)]
    pub no_probes: bool,
}

#[derive(Debug, Clone, Copy)]
pub struct BootConfig {
    pub show: bool,
    pub anim: bool,
    pub probes: bool,
}

impl From<&BootCli> for BootConfig {
    fn from(c: &BootCli) -> Self {
        Self {
            show: !c.no_banner,
            anim: !c.no_anim,
            probes: !c.no_probes,
        }
    }
}

/// Call this from your bin `main()` right after parsing args.
pub fn print_boot(cfg: BootConfig) {
    if !cfg.show {
        return;
    }

    let name = env!("CARGO_PKG_NAME");
    let ver = env!("CARGO_PKG_VERSION");
    let sha = option_env!("GIT_SHA").unwrap_or("-");
    let built = option_env!("BUILD_TIME_UTC").unwrap_or("-");
    let rustc = option_env!("RUSTC_VERSION").unwrap_or("-");
    let target = option_env!("TARGET_TRIPLE").unwrap_or("-");
    let profile = option_env!("BUILD_PROFILE").unwrap_or("-");

    // Dynamic probes (fast, local-only)
    let mut agents = "-".to_string();
    let mut vaults = "-".to_string();
    let mut reflex = "not found".to_string();

    if cfg.probes {
        agents = count_by_ext("agents", &["intent", "toml"])
            .map(|n| n.to_string())
            .unwrap_or("-".into());
        vaults = count_by_ext("vaults", &["vault"])
            .map(|n| n.to_string())
            .unwrap_or("-".into());
        reflex = detect_reflex("reflex.toml");
    }

    let lines = vec![
        "╔════════════════════════════════════════════════════════════════╗".into(),
        format!("║  VIOS Core OS — FIRST TO CROSS INTACT™                        ║"),
        "╟────────────────────────────────────────────────────────────────╢".into(),
        format!("{name} v{ver}   git:{sha}   built:{built}                 "),
        format!("║  rustc:{rustc}   target:{target}   profile:{profile}      "),
        format!(
            "║  host:{os}-{arch}   pid:{pid:<7}                         ",
            os = std::env::consts::OS,
            arch = std::env::consts::ARCH,
            pid = std::process::id()
        ),
        "╟────────────────────────────────────────────────────────────────╢".into(),
        format!("║  agents:{agents:<6}  vaults:{vaults:<6}  reflex:{reflex:<16}            "),
        "╚════════════════════════════════════════════════════════════════╝".into(),
        "".into(),
    ];

    if cfg.anim {
        type_out(&lines, 6); // ~6ms/char – quick but readable
    } else {
        for l in lines {
            println!("{l}");
        }
    }
}

fn count_by_ext(dir: &str, exts: &[&str]) -> io::Result<usize> {
    let mut n = 0usize;
    for e in fs::read_dir(dir)? {
        let p = e?.path();
        if let Some(ext) = p.extension().and_then(|s| s.to_str()) {
            if exts.iter().any(|x| *x == ext) {
                n += 1;
            }
        }
    }
    Ok(n)
}

fn detect_reflex(path: &str) -> String {
    let p = Path::new(path);
    if !p.exists() {
        return "not found".into();
    }
    let Ok(content) = fs::read_to_string(p) else {
        return "found".into();
    };
    // best-effort parse (structure may vary)
    if let Ok(v) = content.parse::<toml::Value>() {
        if let Some(mode) = v.get("mode").and_then(|m| m.as_str()) {
            return format!("mode:{mode}");
        }
        if let Some(tbl) = v.as_table() {
            if let Some((k, _)) = tbl.iter().next() {
                return format!("toml:{k}");
            }
        }
        "parsed".into()
    } else {
        "found".into()
    }
}

fn type_out(lines: &[String], per_char_ms: u64) {
    let mut out = io::stdout();
    for line in lines {
        for ch in line.chars() {
            let _ = write!(out, "{ch}");
            let _ = out.flush();
            thread::sleep(Duration::from_millis(per_char_ms));
        }
        let _ = writeln!(out);
    }
}