1use std::io::{self, IsTerminal, Write};
2
3const LOGO: [&str; 6] = [
4 r" ██╗ ███████╗ █████╗ ███╗ ██╗ ██████╗████████╗██╗ ██╗",
5 r" ██║ ██╔════╝██╔══██╗████╗ ██║ ██╔════╝╚══██╔══╝╚██╗██╔╝",
6 r" ██║ █████╗ ███████║██╔██╗ ██║ ██║ ██║ ╚███╔╝ ",
7 r" ██║ ██╔══╝ ██╔══██║██║╚██╗██║ ██║ ██║ ██╔██╗ ",
8 r" ███████╗███████╗██║ ██║██║ ╚████║ ╚██████╗ ██║ ██╔╝ ██╗",
9 r" ╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝",
10];
11
12const TAGLINE: &str = "Context Runtime for AI Agents";
13
14pub fn print_logo_animated() {
15 let cfg = crate::core::config::Config::load();
16 let t = crate::core::theme::load_theme(&cfg.theme);
17 print_logo_animated_themed(&t);
18}
19
20pub fn print_logo_animated_themed(t: &crate::core::theme::Theme) {
21 if crate::core::theme::no_color() {
22 print_logo_plain();
23 return;
24 }
25 if !io::stdout().is_terminal() {
26 print_logo_themed_static(t);
27 return;
28 }
29
30 let mut stdout = io::stdout();
31 let frames = 28;
32 let frame_ms = 45;
33 let top_padding = 2;
34
35 let _ = writeln!(stdout);
36 let _ = writeln!(stdout);
37
38 for frame in 0..frames {
39 if frame > 0 {
40 print!("\x1b[{}A", LOGO.len() + 2 + top_padding);
41 for _ in 0..top_padding {
42 let _ = writeln!(stdout);
43 }
44 }
45
46 let wave_offset = frame as f64 / frames as f64;
47
48 for (i, line) in LOGO.iter().enumerate() {
49 let chars: Vec<char> = line.chars().collect();
50 let max_j = chars.len().max(1) as f64;
51 let mut buf = String::with_capacity(chars.len() * 20);
52
53 for (j, ch) in chars.iter().enumerate() {
54 if *ch == ' ' {
55 buf.push(' ');
56 continue;
57 }
58 let pos = j as f64 / max_j + i as f64 * 0.15;
59 let blend = ((pos + wave_offset * 2.0) * std::f64::consts::PI)
60 .sin()
61 .mul_add(0.5, 0.5);
62 let c = t.primary.lerp(&t.secondary, blend);
63 buf.push_str(&c.fg());
64 buf.push(*ch);
65 }
66 buf.push_str("\x1b[0m");
67 let _ = writeln!(stdout, "{buf}");
68 }
69
70 let tag_blend = ((wave_offset * 2.0 + 1.0) * std::f64::consts::PI)
71 .sin()
72 .mul_add(0.5, 0.5);
73 let tag_color = t.muted.lerp(&t.accent, tag_blend * 0.5);
74 let _ = writeln!(stdout, "{} {TAGLINE}\x1b[0m", tag_color.fg());
75 let _ = writeln!(stdout);
76
77 let _ = stdout.flush();
78 std::thread::sleep(std::time::Duration::from_millis(frame_ms));
79 }
80
81 print!("\x1b[{}A", LOGO.len() + 2 + top_padding);
82 print_logo_themed_static(t);
83}
84
85pub fn print_logo_static() {
86 let cfg = crate::core::config::Config::load();
87 let t = crate::core::theme::load_theme(&cfg.theme);
88 print_logo_themed_static(&t);
89}
90
91fn print_logo_themed_static(t: &crate::core::theme::Theme) {
92 if crate::core::theme::no_color() {
93 print_logo_plain();
94 return;
95 }
96 let mut stdout = io::stdout();
97
98 let _ = writeln!(stdout);
99 let _ = writeln!(stdout);
100
101 for (i, line) in LOGO.iter().enumerate() {
102 let chars: Vec<char> = line.chars().collect();
103 let mut buf = String::with_capacity(chars.len() * 20);
104
105 for (j, ch) in chars.iter().enumerate() {
106 if *ch == ' ' {
107 buf.push(' ');
108 continue;
109 }
110 let progress = if chars.len() > 1 {
111 j as f64 / (chars.len() - 1) as f64
112 } else {
113 0.5
114 };
115 let row_t = i as f64 / (LOGO.len() - 1).max(1) as f64;
116 let blend = (progress + row_t * 0.3).min(1.0);
117 let c = t.primary.lerp(&t.secondary, blend);
118 buf.push_str(&c.fg());
119 buf.push(*ch);
120 }
121 buf.push_str("\x1b[0m");
122 let _ = writeln!(stdout, "{buf}");
123 }
124
125 let _ = writeln!(stdout, "{} {TAGLINE}\x1b[0m", t.muted.fg());
126 let _ = writeln!(stdout);
127 let _ = stdout.flush();
128}
129
130fn print_logo_plain() {
131 println!();
132 println!();
133 for line in &LOGO {
134 println!("{line}");
135 }
136 println!(" {TAGLINE}");
137 println!();
138}
139
140pub fn print_command_box() {
141 use crate::core::theme;
142 let cfg = crate::core::config::Config::load();
143 let t = theme::load_theme(&cfg.theme);
144 let d = theme::dim();
145 let b = theme::bold();
146 let r = theme::rst();
147 let cmd = t.accent.fg();
148 let ok = t.success.fg();
149 let m = t.muted.fg();
150
151 println!(" {d}┌─────────────────────────────────────────────────────────┐{r}");
152 println!(
153 " {d}│{r} {cmd}{b}lean-ctx gain{r} {m}Token savings dashboard{r} {d}│{r}"
154 );
155 println!(
156 " {d}│{r} {cmd}{b}lean-ctx dashboard{r} {m}Web analytics (browser){r} {d}│{r}"
157 );
158 println!(
159 " {d}│{r} {cmd}{b}lean-ctx heatmap{r} {m}Project context heat map{r} {d}│{r}"
160 );
161 println!(
162 " {d}│{r} {cmd}{b}lean-ctx benchmark{r} {m}Test compression quality{r} {d}│{r}"
163 );
164 println!(
165 " {d}│{r} {cmd}{b}lean-ctx config{r} {m}Edit settings{r} {d}│{r}"
166 );
167 println!(
168 " {d}│{r} {cmd}{b}lean-ctx doctor{r} {m}Verify installation{r} {d}│{r}"
169 );
170 println!(
171 " {d}│{r} {cmd}{b}lean-ctx update{r} {m}Self-update to latest{r} {d}│{r}"
172 );
173 println!(" {d}│{r} {cmd}{b}lean-ctx off{r} / {cmd}{b}on{r} {m}Toggle compression{r} {d}│{r}");
174 println!(
175 " {d}│{r} {cmd}{b}lean-ctx report-issue{r} {m}Report a bug (auto-diagnostics){r} {d}│{r}"
176 );
177 println!(" {d}│{r} {cmd}{b}lean-ctx contribute{r} {m}Share anonymized compression stats{r}{d}│{r}");
178 println!(
179 " {d}│{r} {cmd}{b}lean-ctx uninstall{r} {m}Clean removal{r} {d}│{r}"
180 );
181 println!(" {d}└─────────────────────────────────────────────────────────┘{r}");
182 println!(" {ok}Ready!{r} Your next AI command will be automatically optimized.");
183 println!(" {d}Docs: https://leanctx.com/docs{r}");
184 println!();
185}
186
187pub fn print_step_header(step: u8, total: u8, title: &str) {
188 let dim = "\x1b[2m";
189 let bold = "\x1b[1m";
190 let cyan = "\x1b[36m";
191 let rst = "\x1b[0m";
192 println!();
193 println!(" {cyan}{bold}[{step}/{total}]{rst} {bold}{title}{rst}");
194 println!(" {dim}─────────────────────────────────────────────────────{rst}");
195}
196
197pub fn print_status_ok(msg: &str) {
198 println!(" \x1b[32m✓\x1b[0m {msg}");
199}
200
201pub fn print_status_skip(msg: &str) {
202 println!(" \x1b[2m○\x1b[0m \x1b[2m{msg}\x1b[0m");
203}
204
205pub fn print_status_new(msg: &str) {
206 println!(" \x1b[1;32m✓\x1b[0m \x1b[1m{msg}\x1b[0m");
207}
208
209pub fn print_status_warn(msg: &str) {
210 println!(" \x1b[33m⚠\x1b[0m {msg}");
211}
212
213pub fn spinner_tick(msg: &str, frame: usize) {
214 let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
215 let ch = frames[frame % frames.len()];
216 print!("\r \x1b[36m{ch}\x1b[0m {msg}");
217 let _ = io::stdout().flush();
218}
219
220pub fn spinner_done(msg: &str) {
221 print!("\r \x1b[32m✓\x1b[0m {msg}\x1b[K\n");
222 let _ = io::stdout().flush();
223}
224
225pub fn print_setup_header() {
226 let dim = "\x1b[2m";
227 let bold = "\x1b[1m";
228 let green = "\x1b[32m";
229 let rst = "\x1b[0m";
230 println!();
231 println!(" {dim}╭──────────────────────────────────────────╮{rst}");
232 println!(
233 " {dim}│{rst} {green}{bold}◆ lean-ctx setup{rst} {dim}│{rst}"
234 );
235 println!(" {dim}│{rst} {dim}Configuring your development environment{rst} {dim}│{rst}");
236 println!(" {dim}╰──────────────────────────────────────────╯{rst}");
237 println!();
238}