use crate::tracking::SessionTracker;
use console::{style, Term};
pub fn print_banner(version: &str, model: &str, project: &str, auth_status: &str) {
let term = Term::stdout();
let width = term.size().1 as usize;
let width = width.clamp(60, 100);
let line = style("─".repeat(width)).dim().to_string();
println!();
println!(
" {} {} {} {}",
style("✦").cyan().bold(),
style("xcodeai").cyan().bold(),
style(format!("v{}", version)).dim(),
style("autonomous AI coding agent").dim(),
);
println!(
" {} {} {} {}",
style("model:").dim(),
style(model).green(),
style("project:").dim(),
style(project).yellow(),
);
println!(" {} {}", style("auth:").dim(), auth_status,);
println!("{}", line);
println!(
" {}",
style("Type a task and press Enter. /plan to discuss first. /help for commands. Ctrl-D to quit.").dim()
);
println!("{}", line);
println!();
}
pub fn print_separator(label: &str) {
let label_str = if label.is_empty() {
style("─".repeat(44)).dim().to_string()
} else {
format!(
"{} {} {}",
style("─".repeat(2)).dim(),
style(label).dim(),
style("─".repeat(40 - label.len().min(38))).dim()
)
};
println!("{}", label_str);
}
pub fn ok(msg: &str) {
println!(" {} {}", style("✓").green().bold(), msg);
}
pub fn warn(msg: &str) {
println!(" {} {}", style("!").yellow().bold(), style(msg).yellow());
}
pub fn err(msg: &str) {
eprintln!(" {} {}", style("✗").red().bold(), style(msg).red());
}
pub fn info(msg: &str) {
println!(" {}", style(msg).dim());
}
pub fn print_status_bar(
tracker: &SessionTracker,
mcp_names: &[String],
lsp_active: bool,
lsp_name: &str,
) {
let token_segment: Option<String> = {
let total = tracker.total_tokens();
if total == 0 {
None
} else {
let prompt_fmt = format_number_ui(tracker.total_prompt_tokens());
let completion_fmt = format_number_ui(tracker.total_completion_tokens());
let cost_str = match tracker.estimated_cost_usd() {
Some(cost) => format!(" ~${:.3}", cost),
None => String::new(),
};
Some(format!("tokens {}↑ {}↓{}", prompt_fmt, completion_fmt, cost_str))
}
};
let mcp_segment: Option<String> = if mcp_names.is_empty() {
None
} else {
let names = mcp_names.join(" · ");
Some(format!("MCP: {}", names))
};
let lsp_segment: Option<String> = if lsp_active {
let label = if lsp_name.is_empty() {
"LSP: ● active".to_string()
} else {
format!("LSP: ● {}", lsp_name)
};
Some(label)
} else if !lsp_name.is_empty() {
Some(format!("LSP: ○ {}", lsp_name))
} else {
None
};
let segments: Vec<String> = [token_segment, mcp_segment, lsp_segment]
.into_iter()
.flatten() .collect();
if segments.is_empty() {
return;
}
let bar_content = segments.join(&format!(" {} ", style("│").dim()));
println!(" {}", style(bar_content).dim());
}
fn format_number_ui(n: u32) -> String {
let s = n.to_string();
let mut result = String::with_capacity(s.len() + s.len() / 3);
for (i, ch) in s.chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
result.push(',');
}
result.push(ch);
}
result.chars().rev().collect()
}