cellstate/
ui.rs

1//! Shared UI primitives: ASCII art, colors, spinners, and styled output.
2
3use console::{style, Term};
4use indicatif::{ProgressBar, ProgressStyle};
5use std::time::Duration;
6
7// ============================================================================
8// ASCII art header
9// ============================================================================
10
11pub const CELLSTATE_ASCII: &str = r#"
12  ██████╗ █████╗ ██╗     ██╗██████╗ ███████╗██████╗
13 ██╔════╝██╔══██╗██║     ██║██╔══██╗██╔════╝██╔══██╗
14 ██║     ███████║██║     ██║██████╔╝█████╗  ██████╔╝
15 ██║     ██╔══██║██║     ██║██╔══██╗██╔══╝  ██╔══██╗
16 ╚██████╗██║  ██║███████╗██║██████╔╝███████╗██║  ██║
17  ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝╚═════╝ ╚══════╝╚═╝  ╚═╝
18"#;
19
20pub const TAGLINE: &str = "Agent memory that doesn't forget, lie, or black-box you.";
21
22/// Print the full CELLSTATE header with tagline.
23pub fn print_header() {
24    let term = Term::stdout();
25    let _ = term.clear_screen();
26
27    // Print ASCII art in cyan
28    for line in CELLSTATE_ASCII.lines() {
29        println!("{}", style(line).cyan().bold());
30    }
31    println!("  {}", style(TAGLINE).dim());
32    println!();
33}
34
35/// Print the header without clearing the screen (for subcommands).
36pub fn print_banner() {
37    println!("{}", style("◆ CELLSTATE").cyan().bold());
38    println!("  {}", style(TAGLINE).dim());
39    println!();
40}
41
42// ============================================================================
43// Status indicators
44// ============================================================================
45
46pub fn ok(msg: &str) {
47    println!("  {} {}", style("✓").green().bold(), msg);
48}
49
50pub fn warn(msg: &str) {
51    println!("  {} {}", style("⚠").yellow().bold(), msg);
52}
53
54pub fn fail(msg: &str) {
55    println!("  {} {}", style("✗").red().bold(), msg);
56}
57
58pub fn info(msg: &str) {
59    println!("  {} {}", style("·").dim(), msg);
60}
61
62pub fn step(msg: &str) {
63    println!();
64    println!("{}", style(msg).bold());
65}
66
67pub fn section(msg: &str) {
68    println!();
69    println!(
70        "{}",
71        style(format!("── {msg} ──────────────────────────────")).dim()
72    );
73    println!();
74}
75
76pub fn success_box(title: &str, lines: &[&str]) {
77    println!();
78    println!(
79        "{}",
80        style("╭─────────────────────────────────────────────────────────╮").green()
81    );
82    println!(
83        "{} {:<57} {}",
84        style("│").green(),
85        style(title).green().bold(),
86        style("│").green()
87    );
88    for line in lines {
89        println!("{} {:<57} {}", style("│").green(), line, style("│").green());
90    }
91    println!(
92        "{}",
93        style("╰─────────────────────────────────────────────────────────╯").green()
94    );
95    println!();
96}
97
98// ============================================================================
99// Spinners
100// ============================================================================
101
102pub fn spinner(msg: &str) -> ProgressBar {
103    let pb = ProgressBar::new_spinner();
104    pb.enable_steady_tick(Duration::from_millis(80));
105    pb.set_style(
106        ProgressStyle::with_template("  {spinner:.cyan} {msg}")
107            .unwrap()
108            .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
109    );
110    pb.set_message(msg.to_string());
111    pb
112}
113
114pub fn finish_spinner(pb: ProgressBar, msg: &str) {
115    pb.finish_with_message(format!("{} {}", style("✓").green().bold(), msg));
116}
117
118pub fn fail_spinner(pb: ProgressBar, msg: &str) {
119    pb.finish_with_message(format!("{} {}", style("✗").red().bold(), msg));
120}
121
122// ============================================================================
123// Key-value display
124// ============================================================================
125
126pub fn kv(key: &str, value: &str) {
127    println!("  {:<20} {}", style(key).dim(), style(value).cyan());
128}
129
130pub fn kv_ok(key: &str, value: &str) {
131    println!(
132        "  {:<20} {} {}",
133        style(key).dim(),
134        style("✓").green(),
135        value
136    );
137}
138
139pub fn kv_missing(key: &str) {
140    println!(
141        "  {:<20} {}",
142        style(key).dim(),
143        style("not found").dim().italic()
144    );
145}