tempo-cli 0.4.0

Automatic project time tracking CLI tool with beautiful terminal interface
Documentation
pub struct CliFormatter;

impl CliFormatter {
    pub fn print_section_header(title: &str) {
        println!("\n{}", ansi_color("clean_blue", title, true));
        println!("{}", "".repeat(title.len()).dimmed());
    }

    pub fn print_field(label: &str, value: &str, color: Option<&str>) {
        let colored_value = match color {
            Some(c) => ansi_color(c, value, false),
            None => value.to_string(),
        };
        println!(
            "{} {:<20} {}",
            "".dimmed(),
            format!("{}:", label).dimmed(),
            colored_value
        );
    }

    pub fn print_field_bold(label: &str, value: &str, color: Option<&str>) {
        let colored_value = match color {
            Some(c) => ansi_color(c, value, true),
            None => bold(value),
        };
        println!(
            "{} {:<20} {}",
            "".dimmed(),
            format!("{}:", label).dimmed(),
            colored_value
        );
    }

    pub fn print_status(status: &str, is_active: bool) {
        let (symbol, color) = if is_active {
            ("", "accent")
        } else {
            ("", "red")
        };
        println!(
            "{} {:<20} {} {}",
            "".dimmed(),
            "Status:".dimmed(),
            ansi_color(color, symbol, false),
            ansi_color(color, status, true)
        );
    }

    pub fn print_summary(title: &str, total: &str) {
        println!("\n{}", ansi_color("white", title, true));
        println!("  {}", ansi_color("green", total, true));
    }

    pub fn print_project_entry(name: &str, duration: &str) {
        println!(
            "  {:<25} {}",
            ansi_color("clean_blue", &truncate_string(name, 25), true),
            ansi_color("accent", duration, true)
        );
    }

    pub fn print_context_entry(context: &str, duration: &str) {
        let color = get_context_color(context);
        println!(
            "    {:<20} {}",
            ansi_color(color, &format!("├─ {}", context), false),
            ansi_color("accent", duration, false)
        );
    }

    pub fn print_session_entry(
        _session_id: Option<i64>,
        project: &str,
        duration: &str,
        status: &str,
        timestamp: &str,
    ) {
        let status_symbol = if status == "active" { "" } else { "" };
        let status_color = if status == "active" { "accent" } else { "gray" };

        println!(
            "  {} {:<20} {:<15} {}",
            ansi_color(status_color, status_symbol, false),
            ansi_color("clean_blue", &truncate_string(project, 20), false),
            ansi_color("accent", duration, false),
            timestamp.dimmed()
        );
    }

    pub fn print_empty_state(message: &str) {
        println!("\n  {}", message.dimmed());
    }

    pub fn print_error(message: &str) {
        println!(
            "  {} {}",
            ansi_color("red", "", true),
            ansi_color("red", message, false)
        );
    }

    pub fn print_success(message: &str) {
        println!(
            "  {} {}",
            ansi_color("accent", "", true),
            ansi_color("accent", message, false)
        );
    }

    pub fn print_warning(message: &str) {
        println!(
            "  {} {}",
            ansi_color("yellow", "", true),
            ansi_color("yellow", message, false)
        );
    }

    pub fn print_info(message: &str) {
        println!("  {} {}", ansi_color("clean_blue", "", true), message);
    }

    pub fn print_block_line(label: &str, value: &str) {
        println!(
            "{} {:<20} {}",
            "".dimmed(),
            format!("{}:", label).dimmed(),
            value
        );
    }

    pub fn print_daemon_start(version: &str) {
        println!(
            "{}",
            format!("Starting Tempo Daemon (v{})...", version).dimmed()
        );
    }

    pub fn print_daemon_success(pid: u32, info: &str) {
        let msg = format!("Daemon active. PID: {} [{}]", pid, info);
        println!(
            "{} {}",
            ansi_color("accent", "", true),
            ansi_color("accent", &msg, false)
        );
    }
}

// Helper functions
pub fn ansi_color(color: &str, text: &str, bold: bool) -> String {
    let color_code = match color {
        "red" => "38;2;243;139;168",        // #F38BA8 (ERROR)
        "green" => "38;2;166;227;161",      // #A6E3A1 (SUCCESS) 
        "yellow" => "38;2;249;226;175",     // #F9E2AF (WARNING)
        "blue" => "38;2;137;180;250",       // #89B4FA (PRIMARY_DASHBOARD)
        "magenta" => "38;2;203;166;247",    // #CBA6F7 (Purple accent)
        "cyan" => "38;2;148;226;213",       // #94E2D5 (Teal accent)
        "white" => "38;2;217;224;238",      // #D9E0EE (TEXT_MAIN)
        "gray" => "38;2;108;112;134",       // #6C7086 (TEXT_SECONDARY)
        "accent" => "38;2;51;255;153",      // #33FF99 (PRIMARY_FOCUS/Clean green)
        "neon_cyan" => "38;2;0;255;255",    // #00FFFF (NEON_CYAN)
        "neon_green" => "38;2;57;255;20",   // #39FF14 (NEON_GREEN)
        "clean_blue" => "38;2;100;150;255", // #6496FF (CLEAN_BLUE)
        _ => "38;2;217;224;238",            // default to TEXT_MAIN
    };

    if bold {
        format!("\x1b[1;{}m{}\x1b[0m", color_code, text)
    } else {
        format!("\x1b[{}m{}\x1b[0m", color_code, text)
    }
}

fn bold(text: &str) -> String {
    format!("\x1b[1m{}\x1b[0m", text)
}

pub trait StringFormat {
    fn dimmed(&self) -> String;
}

impl StringFormat for str {
    fn dimmed(&self) -> String {
        format!("\x1b[2m{}\x1b[0m", self)
    }
}

fn get_context_color(context: &str) -> &str {
    match context {
        "terminal" => "neon_cyan",
        "ide" => "magenta",
        "linked" => "yellow",
        "manual" => "clean_blue",
        _ => "white",
    }
}

pub fn truncate_string(s: &str, max_len: usize) -> String {
    if s.len() <= max_len {
        s.to_string()
    } else {
        format!("{}", &s[..max_len.saturating_sub(1)])
    }
}

pub fn format_duration_clean(seconds: i64) -> String {
    let hours = seconds / 3600;
    let minutes = (seconds % 3600) / 60;
    let secs = seconds % 60;

    if hours > 0 {
        format!("{}h {}m", hours, minutes)
    } else if minutes > 0 {
        format!("{}m {}s", minutes, secs)
    } else {
        format!("{}s", secs)
    }
}