muthr 0.1.52

Zero-trust orchestrator for MLX inference, container-based sandboxes, and MCP services on Apple Silicon
// Copyright 2026 tappunk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::io::{self, IsTerminal, Write};
use std::sync::atomic::{AtomicU8, Ordering};

const VERBOSITY_QUIET: u8 = 0;
const VERBOSITY_NORMAL: u8 = 1;
const VERBOSITY_VERBOSE: u8 = 2;

static GLOBAL_VERBOSITY: AtomicU8 = AtomicU8::new(VERBOSITY_NORMAL);

pub fn set_quiet() {
    GLOBAL_VERBOSITY.store(VERBOSITY_QUIET, Ordering::Relaxed);
}

pub fn set_verbose() {
    GLOBAL_VERBOSITY.store(VERBOSITY_VERBOSE, Ordering::Relaxed);
}

pub fn is_quiet() -> bool {
    GLOBAL_VERBOSITY.load(Ordering::Relaxed) == VERBOSITY_QUIET
}

pub fn is_verbose() -> bool {
    GLOBAL_VERBOSITY.load(Ordering::Relaxed) == VERBOSITY_VERBOSE
}

pub fn log_info(message: &str) {
    if GLOBAL_VERBOSITY.load(Ordering::Relaxed) == VERBOSITY_VERBOSE {
        eprintln!("info: {}", message);
    }
}

pub fn log_warn(message: &str) {
    if GLOBAL_VERBOSITY.load(Ordering::Relaxed) >= VERBOSITY_NORMAL {
        eprintln!("warning: {}", message);
    }
}

pub fn select_list(items: &[&str]) -> Option<usize> {
    if items.is_empty() {
        return None;
    }

    let stderr = io::stderr();
    let mut handle = stderr.lock();

    writeln!(handle).ok();
    for (i, item) in items.iter().enumerate() {
        writeln!(handle, "  {}) {}", i + 1, item).ok();
    }
    write!(handle, "Select preset (1-{}) or q to quit: ", items.len()).ok();
    handle.flush().ok();

    let mut input = String::new();
    io::stdin().read_line(&mut input).ok();

    let trimmed = input.trim();
    if trimmed == "q" || trimmed.is_empty() {
        return None;
    }

    match trimmed.parse::<usize>() {
        Ok(n) if n > 0 && n <= items.len() => Some(n - 1),
        _ => select_list(items),
    }
}

pub fn is_human_output(output: crate::OutputFormat) -> bool {
    if output != crate::OutputFormat::Text {
        return false;
    }
    if std::env::var("NO_COLOR").is_ok() {
        return false;
    }
    if let Ok(v) = std::env::var("CLICOLOR")
        && v == "0"
    {
        return false;
    }
    if let Ok(v) = std::env::var("CLICOLOR_FORCE")
        && v == "1"
    {
        return true;
    }
    io::stderr().is_terminal()
}