teams-cli 0.1.7

A Microsoft Teams CLI and TUI designed equally for humans and agents
Documentation
use std::io::IsTerminal;

use clap::ValueEnum;
use serde::Serialize;

use crate::error::AppError;

#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum OutputFormat {
    Auto,
    Text,
    Json,
}

#[derive(Debug, Clone, Copy)]
pub struct Output {
    pub format: OutputFormat,
    pub quiet: bool,
}

impl Output {
    pub fn json(self) -> bool {
        matches!(self.format, OutputFormat::Json)
            || (matches!(self.format, OutputFormat::Auto) && !std::io::stdout().is_terminal())
    }

    pub fn value<T: Serialize>(
        &self,
        value: &T,
        text: impl FnOnce() -> String,
    ) -> Result<(), AppError> {
        if self.json() {
            println!(
                "{}",
                serde_json::to_string_pretty(value)
                    .map_err(|e| AppError::Unexpected(e.to_string()))?
            );
        } else {
            println!("{}", text());
        }
        Ok(())
    }

    pub fn note(&self, message: impl AsRef<str>) {
        if !self.quiet {
            eprintln!("{}", message.as_ref());
        }
    }
}

pub fn print_error(error: &AppError, structured: bool) {
    if !structured {
        eprintln!("Error: {error}");
        eprintln!("Hint: run `teams doctor` for a guided diagnosis.");
    }
    let contract = error.contract();
    if structured {
        let envelope = serde_json::json!({
            "error": {
                "kind": contract.kind,
                "message": error.to_string(),
                "retryable": contract.retryable,
            }
        });
        eprintln!("{}", serde_json::to_string(&envelope).unwrap_or_else(|_| r#"{"error":{"kind":"unexpected_error","message":"error serialization failed","retryable":false}}"#.into()));
    }
}

pub fn structured_from_args() -> bool {
    let args: Vec<String> = std::env::args().collect();
    let explicit_text = args.windows(2).any(|w| w == ["--output", "text"])
        || args.iter().any(|a| a == "--output=text" || a == "-otext");
    let explicit_json = args.windows(2).any(|w| w == ["--output", "json"])
        || args
            .iter()
            .any(|a| a == "--output=json" || a == "-ojson" || a == "--json");
    !explicit_text && (explicit_json || !std::io::stdout().is_terminal())
}