openlatch-client 0.6.0

OpenLatch runtime enforcement node — the capture-and-enforce adapter that evaluates every covered action against a coding agent's Autonomy Zone before it runs
//! The rail's colours — the installer playground's palette, as ANSI.
//!
//! Terminals map the 16 ANSI colours to their own scheme; these are picked so
//! Windows Terminal's default (Campbell) draws exactly the playground's hex
//! values, and every other scheme draws the same roles.
//!
//! | Role | Playground | ANSI |
//! | ---- | ---------- | ---- |
//! | [`dim`] — gutter, secondary text | `#767676` | 90 bright black |
//! | [`white`] — stage labels, titles, values | `#f2f2f2` | 97 bright white |
//! | [`accent`] — spinner, active glyph, commands, links | `#3a96dd` | 36 cyan |
//! | [`highlight`] — the highlighted choice of a select | `#61d6d6` | 96 bright cyan |
//! | [`green`] — done, live | `#16c60c` | 92 bright green |
//! | [`amber`] — needs attention | `#e5c100` | 33 yellow |
//! | [`red`] — failed, not live | `#e74856` | 91 bright red |
//!
//! Every function returns `text` untouched when `on` is false.

fn sgr(code: &str, text: &str, on: bool) -> String {
    if on && !text.is_empty() {
        format!("\x1b[{code}m{text}\x1b[0m")
    } else {
        text.to_string()
    }
}

pub fn dim(text: &str, on: bool) -> String {
    sgr("90", text, on)
}

pub fn white(text: &str, on: bool) -> String {
    sgr("97", text, on)
}

pub fn accent(text: &str, on: bool) -> String {
    sgr("36", text, on)
}

pub fn highlight(text: &str, on: bool) -> String {
    sgr("96", text, on)
}

/// A URL: accent, underlined.
pub fn link(text: &str, on: bool) -> String {
    sgr("4;36", text, on)
}

pub fn green(text: &str, on: bool) -> String {
    sgr("92", text, on)
}

pub fn amber(text: &str, on: bool) -> String {
    sgr("33", text, on)
}

pub fn red(text: &str, on: bool) -> String {
    sgr("91", text, on)
}

/// The severity a result carries: [`green`], [`amber`] or [`red`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tone {
    Success,
    Warn,
    Fail,
}

impl Tone {
    /// `text` in this tone's colour.
    pub fn paint(self, text: &str, on: bool) -> String {
        match self {
            Self::Success => green(text, on),
            Self::Warn => amber(text, on),
            Self::Fail => red(text, on),
        }
    }
}

/// Bold, over whatever colour `text` already carries.
pub fn bold(text: &str, on: bool) -> String {
    if on && !text.is_empty() {
        format!("\x1b[1m{text}\x1b[22m")
    } else {
        text.to_string()
    }
}

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

    #[test]
    fn test_palette_codes() {
        assert_eq!(dim("x", true), "\x1b[90mx\x1b[0m");
        assert_eq!(white("x", true), "\x1b[97mx\x1b[0m");
        assert_eq!(accent("x", true), "\x1b[36mx\x1b[0m");
        assert_eq!(highlight("x", true), "\x1b[96mx\x1b[0m");
        assert_eq!(link("x", true), "\x1b[4;36mx\x1b[0m");
        assert_eq!(green("x", true), "\x1b[92mx\x1b[0m");
        assert_eq!(amber("x", true), "\x1b[33mx\x1b[0m");
        assert_eq!(red("x", true), "\x1b[91mx\x1b[0m");
        assert_eq!(
            bold(&red("x", true), true),
            "\x1b[1m\x1b[91mx\x1b[0m\x1b[22m"
        );
        assert_eq!(Tone::Success.paint("x", true), green("x", true));
        assert_eq!(Tone::Warn.paint("x", true), amber("x", true));
        assert_eq!(Tone::Fail.paint("x", true), red("x", true));
        assert_eq!(dim("x", false), "x");
        assert_eq!(dim("", true), "");
    }
}