claude-code-status-line 1.3.1

A configurable status line for Claude Code with powerline arrows, context tracking, and quota monitoring
Documentation
use crate::colors::SectionColors;
use crate::render::get_details_and_fg_codes;
use chrono::{DateTime, Duration, Utc};

fn format_pct(pct: Option<f64>) -> String {
    match pct {
        // Floor so a nearly exhausted window never shows 100% early.
        Some(pct) => format!("{:.0}%", pct.floor()),
        None => "-".to_string(),
    }
}

pub fn format_quota_display(
    label: &str,
    pct: Option<f64>,
    reset: &str,
    colors: &SectionColors,
) -> String {
    let pct_str = format_pct(pct);

    if reset.is_empty() {
        format!("{}: {}", label, pct_str)
    } else {
        let (details, fg) = get_details_and_fg_codes(colors);
        format!("{}: {} {}({}){}", label, pct_str, details, reset, fg)
    }
}

pub fn format_quota_compact(label: &str, pct: Option<f64>) -> String {
    format!("{}: {}", label, format_pct(pct))
}

pub fn format_time_remaining(resets_at: &str, now: DateTime<Utc>) -> String {
    let reset_time = match DateTime::parse_from_rfc3339(resets_at) {
        Ok(time) => time.with_timezone(&Utc),
        Err(_) => return String::new(),
    };

    let duration = reset_time.signed_duration_since(now);

    if duration <= Duration::zero() {
        return "now".to_string();
    }

    crate::utils::format_duration_secs(duration.num_seconds() as u64, false)
}

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

    #[test]
    fn test_format_quota_compact_floors_percentage() {
        assert_eq!(format_quota_compact("5h", Some(23.456)), "5h: 23%");
        assert_eq!(format_quota_compact("7d", Some(41.7)), "7d: 41%");
        assert_eq!(format_quota_compact("5h", Some(99.6)), "5h: 99%");
        assert_eq!(format_quota_compact("5h", None), "5h: -");
    }

    #[test]
    fn test_format_time_remaining_now() {
        let now = Utc::now();
        let past = now - Duration::minutes(5);
        assert_eq!(format_time_remaining(&past.to_rfc3339(), now), "now");
    }

    #[test]
    fn test_format_time_remaining_minutes() {
        let now = Utc::now();
        let future = now + Duration::minutes(45);
        let result = format_time_remaining(&future.to_rfc3339(), now);
        assert!(result.ends_with('m'));
        assert!(!result.contains('h'));
        assert!(!result.contains('d'));
    }

    #[test]
    fn test_format_time_remaining_hours() {
        let now = Utc::now();
        let future = now + Duration::hours(5) + Duration::minutes(30);
        let result = format_time_remaining(&future.to_rfc3339(), now);
        assert!(result.contains('h'));
        assert!(result.contains('m'));
        assert!(!result.contains('d'));
    }

    #[test]
    fn test_format_time_remaining_days() {
        let now = Utc::now();
        let future = now + Duration::days(3) + Duration::hours(12);
        let result = format_time_remaining(&future.to_rfc3339(), now);
        assert!(result.contains('d'));
        assert!(result.contains('h'));
    }
}