Skip to content

Commit 8e8fc8a

Browse files
factorydroidechobt
authored andcommitted
fix(cortex-cli): add color preview to agent show command
Fixes bounty issue #1380 The agent show command now displays colors with a visual preview block using ANSI true color escape codes, along with an approximate color name (e.g., green, blue, red) to make the hex code more understandable.
1 parent 0e4e8e0 commit 8e8fc8a

1 file changed

Lines changed: 119 additions & 1 deletion

File tree

cortex-cli/src/agent_cmd.rs

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,80 @@ async fn run_list(args: ListArgs) -> Result<()> {
794794
Ok(())
795795
}
796796

797+
/// Parse a hex color string (e.g., "#22c55e") into RGB components.
798+
fn parse_hex_color(hex: &str) -> Option<(u8, u8, u8)> {
799+
let hex = hex.trim_start_matches('#');
800+
if hex.len() != 6 {
801+
return None;
802+
}
803+
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
804+
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
805+
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
806+
Some((r, g, b))
807+
}
808+
809+
/// Get a human-readable approximate color name from RGB values.
810+
fn approximate_color_name(r: u8, g: u8, b: u8) -> &'static str {
811+
// Convert to HSL-like heuristics for color naming
812+
let max = r.max(g).max(b);
813+
let min = r.min(g).min(b);
814+
let lightness = (max as u16 + min as u16) / 2;
815+
816+
// Check for grayscale colors
817+
if max - min < 20 {
818+
return if lightness < 50 {
819+
"black"
820+
} else if lightness > 200 {
821+
"white"
822+
} else {
823+
"gray"
824+
};
825+
}
826+
827+
// Determine dominant color
828+
if r >= g && r >= b {
829+
if r > 200 && g < 100 && b < 100 {
830+
"red"
831+
} else if g > 100 {
832+
"orange"
833+
} else if b > 100 {
834+
"pink"
835+
} else {
836+
"red"
837+
}
838+
} else if g >= r && g >= b {
839+
if b > 150 {
840+
"cyan"
841+
} else if r > 150 {
842+
"yellow"
843+
} else {
844+
"green"
845+
}
846+
} else {
847+
// Blue dominant
848+
if r > 150 {
849+
"purple"
850+
} else if g > 150 {
851+
"cyan"
852+
} else {
853+
"blue"
854+
}
855+
}
856+
}
857+
858+
/// Format a hex color with a visual preview and approximate color name.
859+
fn format_color_with_preview(hex: &str) -> String {
860+
if let Some((r, g, b)) = parse_hex_color(hex) {
861+
let color_name = approximate_color_name(r, g, b);
862+
// Use ANSI true color escape codes: \x1b[48;2;R;G;Bm for background
863+
// Display a colored block followed by the hex code and color name
864+
format!("\x1b[48;2;{r};{g};{b}m \x1b[0m {hex} ({color_name})")
865+
} else {
866+
// If parsing fails, just return the hex string
867+
hex.to_string()
868+
}
869+
}
870+
797871
/// Show agent details command.
798872
async fn run_show(args: ShowArgs) -> Result<()> {
799873
let agents = load_all_agents()?;
@@ -846,7 +920,7 @@ async fn run_show(args: ShowArgs) -> Result<()> {
846920
}
847921

848922
if let Some(ref color) = agent.color {
849-
println!("Color: {color}");
923+
println!("Color: {}", format_color_with_preview(color));
850924
}
851925

852926
if !agent.tags.is_empty() {
@@ -1547,4 +1621,48 @@ This is the system prompt.
15471621
assert!(agents.iter().any(|a| a.name == "plan"));
15481622
assert!(agents.iter().any(|a| a.name == "explore"));
15491623
}
1624+
1625+
#[test]
1626+
fn test_parse_hex_color() {
1627+
// Valid hex colors
1628+
assert_eq!(parse_hex_color("#22c55e"), Some((0x22, 0xc5, 0x5e)));
1629+
assert_eq!(parse_hex_color("#3b82f6"), Some((0x3b, 0x82, 0xf6)));
1630+
assert_eq!(parse_hex_color("#ffffff"), Some((255, 255, 255)));
1631+
assert_eq!(parse_hex_color("#000000"), Some((0, 0, 0)));
1632+
assert_eq!(parse_hex_color("ff0000"), Some((255, 0, 0))); // Without #
1633+
1634+
// Invalid hex colors
1635+
assert_eq!(parse_hex_color("#fff"), None); // Too short
1636+
assert_eq!(parse_hex_color("#fffffff"), None); // Too long
1637+
assert_eq!(parse_hex_color(""), None);
1638+
}
1639+
1640+
#[test]
1641+
fn test_approximate_color_name() {
1642+
// Green color (#22c55e -> rgb(34, 197, 94))
1643+
assert_eq!(approximate_color_name(34, 197, 94), "green");
1644+
// Blue color (#3b82f6 -> rgb(59, 130, 246))
1645+
assert_eq!(approximate_color_name(59, 130, 246), "blue");
1646+
// Red color
1647+
assert_eq!(approximate_color_name(255, 50, 50), "red");
1648+
// White
1649+
assert_eq!(approximate_color_name(255, 255, 255), "white");
1650+
// Black
1651+
assert_eq!(approximate_color_name(10, 10, 10), "black");
1652+
// Gray
1653+
assert_eq!(approximate_color_name(128, 128, 128), "gray");
1654+
}
1655+
1656+
#[test]
1657+
fn test_format_color_with_preview() {
1658+
// Test with valid color
1659+
let result = format_color_with_preview("#22c55e");
1660+
assert!(result.contains("#22c55e"));
1661+
assert!(result.contains("green"));
1662+
assert!(result.contains("\x1b[")); // Contains ANSI escape codes
1663+
1664+
// Test with invalid color (should return as-is)
1665+
let result = format_color_with_preview("invalid");
1666+
assert_eq!(result, "invalid");
1667+
}
15501668
}

0 commit comments

Comments
 (0)