use crossterm::{
execute,
style::{Color as CtColor, Print, ResetColor, SetForegroundColor},
};
use std::io::{self, Write as _};
pub const NEWT_ORANGE_CT: CtColor = CtColor::Rgb {
r: 220,
g: 60,
b: 20,
};
pub(crate) const FADE_CT: CtColor = CtColor::Rgb {
r: 90,
g: 90,
b: 90,
};
pub(crate) fn term_cols() -> usize {
crossterm::terminal::size()
.map(|(c, _)| c as usize)
.unwrap_or(80)
.max(8)
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct FittedLine {
pub head: String,
pub fade: String,
pub ellipsis: &'static str,
}
pub(crate) fn fit_line(s: &str, max_cols: usize) -> FittedLine {
let chars: Vec<char> = s.chars().collect();
if chars.len() <= max_cols {
return FittedLine {
head: s.to_string(),
fade: String::new(),
ellipsis: "",
};
}
let budget = max_cols.saturating_sub(1).max(1);
let kept = &chars[..budget];
let fade_n = 2.min(kept.len());
let split = kept.len() - fade_n;
FittedLine {
head: kept[..split].iter().collect(),
fade: kept[split..].iter().collect(),
ellipsis: "…",
}
}
pub fn print_newt(msg: &str, color: bool, verbose: bool) {
let prefix = if color {
if verbose {
"newt ▸ "
} else {
"▸ "
}
} else if verbose {
"newt > "
} else {
"> "
};
println!("{prefix}{msg}");
}
pub fn print_list_item(label: &str, active: bool, color: bool) {
if !active {
println!(" {label}");
return;
}
if color {
execute!(
io::stdout(),
SetForegroundColor(CtColor::Red),
Print("▸ "),
ResetColor,
Print(label),
Print(" "),
SetForegroundColor(CtColor::Red),
Print("◀ "),
SetForegroundColor(CtColor::Green),
Print("active"),
ResetColor,
Print("\n"),
)
.ok();
} else {
println!("> {label} <- active");
}
}
pub fn print_harness_notice(msg: &str, color: bool) {
if color {
execute!(
io::stdout(),
SetForegroundColor(CtColor::DarkYellow),
Print(format!("⚠ newt: {msg}\n")),
ResetColor,
)
.ok();
} else {
println!("⚠ newt: {msg}");
}
io::stdout().flush().ok();
}
pub(crate) fn print_debug(msg: &str, color: bool) {
if color {
execute!(
io::stdout(),
SetForegroundColor(CtColor::DarkGrey),
Print(format!("[debug] {msg}\n")),
ResetColor,
)
.ok();
} else {
println!("[debug] {msg}");
}
io::stdout().flush().ok();
}
pub(crate) fn print_trace(msg: &str, color: bool) {
if color {
execute!(
io::stdout(),
SetForegroundColor(CtColor::DarkGrey),
Print(format!("[trace] {msg}\n")),
ResetColor,
)
.ok();
} else {
println!("[trace] {msg}");
}
io::stdout().flush().ok();
}
pub(crate) fn fmt_tokens(n: u32) -> String {
let s = n.to_string();
let mut out = String::with_capacity(s.len() + s.len() / 3);
for (i, c) in s.chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
out.push(',');
}
out.push(c);
}
out.chars().rev().collect()
}
pub(crate) fn fmt_tokens_k(n: u32) -> String {
format!("{}k", (n + 500) / 1000)
}
pub fn fmt_tokens_compact(n: u32) -> String {
let k = (n + 500) / 1000;
if k >= 1024 {
let m = k as f64 / 1024.0;
if (m - m.round()).abs() < 0.05 {
format!("{}M", m.round() as u64)
} else {
format!("{m:.1}M")
}
} else {
format!("{k}k")
}
}
pub fn fmt_token_gauge(used: u32, budget: u32) -> String {
format!("{}/{}", fmt_tokens_k(used), fmt_tokens_k(budget))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GaugeLevel {
Ok,
Warn,
Critical,
}
pub fn gauge_level(used: u32, budget: u32) -> GaugeLevel {
let pct = if budget == 0 {
0
} else {
(used as u64 * 100 / budget as u64) as u32
};
if pct >= 90 {
GaugeLevel::Critical
} else if pct >= 75 {
GaugeLevel::Warn
} else {
GaugeLevel::Ok
}
}
pub(crate) fn emit_overflow_notice(
color: bool,
usage: Option<&crate::TokenUsage>,
safe_context: Option<u32>,
model: &str,
attempt: u32,
) {
let token_str = usage
.map(|u| format!("{} tokens", fmt_tokens(u.input_tokens)))
.unwrap_or_else(|| "unknown tokens".to_string());
let safe_str = safe_context
.map(|s| format!(" > {} safe window for {model}", fmt_tokens(s)))
.unwrap_or_default();
let msg = format!(
"⚠ context overflow likely ({token_str}{safe_str})\n⟳ trimming context and retrying (attempt {attempt}/2)…"
);
if color {
execute!(
io::stdout(),
SetForegroundColor(CtColor::DarkYellow),
Print(format!("{msg}\n")),
ResetColor,
)
.ok();
} else {
println!("{msg}");
}
io::stdout().flush().ok();
}
pub(crate) fn compression_notice_text(
action: super::compress::CompressAction,
before: usize,
after: usize,
suffix: &str,
) -> (String, bool) {
use super::compress::CompressAction;
let b = fmt_tokens(before.min(u32::MAX as usize) as u32);
let a = fmt_tokens(after.min(u32::MAX as usize) as u32);
match action {
CompressAction::StaticFallback => (
format!(
"⛔ summary unavailable — context compacted to a marker \
(~{b} → ~{a} est. tokens{suffix}). Re-read files if needed."
),
true,
),
CompressAction::Summarized => (
format!("✓ context summarized: ~{b} → ~{a} est. tokens{suffix}"),
false,
),
other => (
format!(
"⧉ context compressed: ~{b} → ~{a} est. tokens ({}{suffix})",
other.describe()
),
false,
),
}
}
pub(crate) fn emit_compression_notice(
color: bool,
before: usize,
after: usize,
action: super::compress::CompressAction,
suffix: &str,
) {
let (msg, loud) = compression_notice_text(action, before, after, suffix);
let hue = if loud {
CtColor::Red
} else {
CtColor::DarkYellow
};
if color {
execute!(
io::stdout(),
SetForegroundColor(hue),
Print(format!("{msg}\n")),
ResetColor,
)
.ok();
} else {
println!("{msg}");
}
io::stdout().flush().ok();
}
pub(crate) fn print_retry_indicator(attempt: u32, delay: std::time::Duration, color: bool) {
let delay_s = delay.as_secs_f32();
let msg = format!(" ↻ connection lost — retrying in {delay_s:.1}s (attempt {attempt})…\n");
if color {
execute!(
io::stdout(),
SetForegroundColor(CtColor::Rgb {
r: 200,
g: 140,
b: 0
}),
Print(&msg),
ResetColor,
)
.ok();
} else {
print!("{msg}");
}
io::stdout().flush().ok();
}
pub(crate) fn print_tool_call(name: &str, detail: &str, color: bool) {
let cols = term_cols();
let prefix_w = 3 + name.chars().count() + 2; let fitted = fit_line(detail, cols.saturating_sub(prefix_w));
if color {
execute!(
io::stdout(),
SetForegroundColor(NEWT_ORANGE_CT),
Print(format!("⚙ {name}")),
ResetColor,
SetForegroundColor(CtColor::DarkGrey),
Print(format!(": {}", fitted.head)),
SetForegroundColor(FADE_CT),
Print(&fitted.fade),
Print(fitted.ellipsis),
ResetColor,
Print("\n"),
)
.ok();
} else {
println!(
"⚙ {name}: {}{}{}",
fitted.head, fitted.fade, fitted.ellipsis
);
}
io::stdout().flush().ok();
}
pub(crate) fn print_tool_output(output: &str, max_lines: usize, color: bool) {
if output.is_empty() {
return;
}
let max = max_lines;
let lines: Vec<&str> = output.lines().collect();
let shown = if max == 0 {
lines.len()
} else {
lines.len().min(max)
};
let hidden = lines.len().saturating_sub(shown);
let display = lines[..shown].join("\n");
if color {
execute!(
io::stdout(),
SetForegroundColor(CtColor::DarkGrey),
Print(format!("{display}\n")),
ResetColor,
)
.ok();
} else {
println!("{display}");
}
if hidden > 0 {
if color {
execute!(
io::stdout(),
SetForegroundColor(CtColor::DarkGrey),
Print(format!(" … ({hidden} more lines hidden)\n")),
ResetColor,
)
.ok();
} else {
println!(" … ({hidden} more lines hidden)");
}
}
io::stdout().flush().ok();
}
pub(crate) fn print_denied(axis: &str, target: &str, color: bool) {
if color {
execute!(
io::stdout(),
SetForegroundColor(CtColor::DarkGrey),
Print(format!(
"⊘ capability denied: {axis} does not permit '{target}'\n"
)),
ResetColor,
)
.ok();
} else {
println!("⊘ capability denied: {axis} does not permit '{target}'");
}
io::stdout().flush().ok();
}
#[cfg(test)]
mod tests {
use super::{fit_line, fmt_tokens, print_harness_notice, print_list_item, print_newt};
#[test]
fn fit_line_passes_through_when_it_fits() {
let f = fit_line("hello", 10);
assert_eq!(f.head, "hello");
assert_eq!(f.fade, "");
assert_eq!(f.ellipsis, "");
let exact = fit_line("hello", 5);
assert_eq!(exact.ellipsis, "");
assert_eq!(exact.head, "hello");
}
#[test]
fn fit_line_truncates_with_faded_tail_and_ellipsis() {
let f = fit_line("abcdefghijk", 6);
assert_eq!(f.ellipsis, "…");
assert_eq!(f.head, "abc");
assert_eq!(f.fade, "de");
assert!(f.head.chars().count() + f.fade.chars().count() < 6);
}
#[test]
fn fit_line_handles_tiny_budgets() {
let f = fit_line("abcdef", 1);
assert_eq!(f.ellipsis, "…");
assert_eq!(f.head, "");
assert_eq!(f.fade, "a");
}
#[test]
fn fmt_tokens_inserts_thousands_separators() {
assert_eq!(fmt_tokens(0), "0");
assert_eq!(fmt_tokens(999), "999");
assert_eq!(fmt_tokens(1_000), "1,000");
assert_eq!(fmt_tokens(1_234_567), "1,234,567");
}
#[test]
fn gauge_formatting_k_compact_fraction_and_level() {
use super::{fmt_token_gauge, fmt_tokens_compact, fmt_tokens_k, gauge_level, GaugeLevel};
assert_eq!(fmt_tokens_k(899_000), "899k");
assert_eq!(fmt_tokens_k(1_024_000), "1024k");
assert_eq!(fmt_tokens_k(512_400), "512k");
assert_eq!(fmt_tokens_compact(899_000), "899k");
assert_eq!(fmt_tokens_compact(1_024_000), "1M");
assert_eq!(fmt_tokens_compact(2_048_000), "2M");
assert_eq!(fmt_tokens_compact(1_536_000), "1.5M");
assert_eq!(fmt_token_gauge(899_000, 1_024_000), "899k/1024k");
assert_eq!(gauge_level(100, 1000), GaugeLevel::Ok);
assert_eq!(gauge_level(740, 1000), GaugeLevel::Ok);
assert_eq!(gauge_level(750, 1000), GaugeLevel::Warn);
assert_eq!(gauge_level(890, 1000), GaugeLevel::Warn);
assert_eq!(gauge_level(900, 1000), GaugeLevel::Critical);
assert_eq!(gauge_level(0, 0), GaugeLevel::Ok); }
#[test]
fn compression_notice_text_registers_per_outcome() {
use super::compression_notice_text;
use crate::agentic::compress::CompressAction;
let (msg, loud) =
compression_notice_text(CompressAction::StaticFallback, 10_000, 6_000, "");
assert!(loud, "static marker is the loud last resort");
assert!(msg.starts_with("⛔"), "{msg}");
assert!(msg.contains("summary unavailable"), "{msg}");
assert!(msg.contains("Re-read files"), "{msg}");
let (msg, loud) = compression_notice_text(CompressAction::Summarized, 10_000, 6_000, "");
assert!(!loud);
assert!(msg.starts_with("✓") && msg.contains("summarized"), "{msg}");
let (msg, loud) = compression_notice_text(CompressAction::Pruned, 10_000, 6_000, "");
assert!(!loud);
assert!(
msg.starts_with("⧉") && msg.contains("structural prune"),
"{msg}"
);
let (msg, _) = compression_notice_text(
CompressAction::Summarized,
10_000,
6_000,
", still over budget",
);
assert!(msg.contains(", still over budget"), "{msg}");
}
#[test]
#[ignore = "visual preview; run with --ignored --nocapture"]
fn compression_notice_visual_preview() {
use super::compression_notice_text;
use crate::agentic::compress::CompressAction;
let paint = |loud: bool, s: &str| {
if loud {
format!("\x1b[31m{s}\x1b[0m") } else {
format!("\x1b[33m{s}\x1b[0m") }
};
println!("\n compression-notice registers (24.7):");
for action in [
CompressAction::Pruned,
CompressAction::Summarized,
CompressAction::StaticFallback,
] {
let (msg, loud) = compression_notice_text(action, 1_024_000, 600_000, "");
println!(" {}", paint(loud, &msg));
}
println!();
}
#[test]
#[ignore = "visual preview; run with --ignored --nocapture"]
fn gauge_visual_preview() {
use super::{fmt_token_gauge, fmt_tokens_compact, gauge_level, GaugeLevel};
use crossterm::style::Color;
let color = |lvl: GaugeLevel| match lvl {
GaugeLevel::Ok => Color::Green,
GaugeLevel::Warn => Color::DarkYellow,
GaugeLevel::Critical => Color::Red,
};
let paint = |c: Color, s: &str| match c {
Color::Green => format!("\x1b[32m{s}\x1b[0m"),
Color::DarkYellow => format!("\x1b[33m{s}\x1b[0m"),
Color::Red => format!("\x1b[31m{s}\x1b[0m"),
_ => s.to_string(),
};
let budget = 1_024_000;
println!("\n context-budget gauge — fraction form (live header):");
for used in [102_000u32, 512_000, 800_000, 972_000, 1_010_000] {
let lvl = gauge_level(used, budget);
let g = fmt_token_gauge(used, budget);
println!(" {:<14} {:?}", paint(color(lvl), &g), lvl);
}
println!("\n compact budget form (1M = 1024k):");
for n in [899_000u32, 1_024_000, 1_536_000, 2_048_000] {
println!(" {n:>9} → {}", fmt_tokens_compact(n));
}
println!(
"\n mock header:\n [2026-06-22 14:32:01] vi --INSERT-- nemotron @ REDACTED-HOST {}\n",
paint(color(gauge_level(972_000, budget)), &fmt_token_gauge(972_000, budget)),
);
}
#[test]
fn printers_cover_every_branch_without_panicking() {
for color in [true, false] {
for verbose in [true, false] {
print_newt("narrator line", color, verbose);
}
print_list_item("name · ollama · model @ url", true, color);
print_list_item("name · ollama · model @ url", false, color);
print_harness_notice(
"over budget — dispatching and letting the backend decide",
color,
);
}
}
}