diff --git a/crates/arc-workflows/src/cli/mod.rs b/crates/arc-workflows/src/cli/mod.rs index fe9b37341..10a4d4c28 100644 --- a/crates/arc-workflows/src/cli/mod.rs +++ b/crates/arc-workflows/src/cli/mod.rs @@ -584,6 +584,16 @@ pub fn format_tokens_human(tokens: i64) -> String { } } +/// Shorten an absolute path by replacing the home directory prefix with `~`. +pub fn tilde_path(path: &Path) -> String { + if let Some(home) = dirs::home_dir() { + if let Ok(suffix) = path.strip_prefix(&home) { + return format!("~/{}", suffix.display()); + } + } + path.display().to_string() +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/arc-workflows/src/cli/progress.rs b/crates/arc-workflows/src/cli/progress.rs index 5c7f0b857..7f47e9690 100644 --- a/crates/arc-workflows/src/cli/progress.rs +++ b/crates/arc-workflows/src/cli/progress.rs @@ -59,7 +59,7 @@ fn red_cross() -> &'static str { // ── Duration formatting ───────────────────────────────────────────────── -fn format_duration_short(d: Duration) -> String { +pub(crate) fn format_duration_short(d: Duration) -> String { let secs = d.as_secs(); if secs >= 60 { format!("{}m{:02}s", secs / 60, secs % 60) @@ -311,7 +311,7 @@ impl ProgressUI { // ── Logs dir (called externally) ──────────────────────────────────── pub fn show_logs_dir(&mut self, logs_dir: &Path) { - let path_str = logs_dir.display().to_string(); + let path_str = super::tilde_path(logs_dir); match &self.renderer { ProgressRenderer::Tty(tty) => { let bar = tty.multi.add(ProgressBar::new_spinner()); diff --git a/crates/arc-workflows/src/cli/run.rs b/crates/arc-workflows/src/cli/run.rs index 3212f98c6..80f9dd642 100644 --- a/crates/arc-workflows/src/cli/run.rs +++ b/crates/arc-workflows/src/cli/run.rs @@ -219,7 +219,7 @@ pub async fn run_command( let goal = graph.goal(); if !goal.is_empty() { - eprintln!("{} {goal}", styles.bold.apply_to("Goal:")); + eprintln!("{} {goal}\n", styles.bold.apply_to("Goal:")); } print_diagnostics(&diagnostics, styles); @@ -277,7 +277,7 @@ pub async fn run_command( eprintln!( "{} {}", styles.dim.apply_to("Logs:"), - styles.underline.apply_to(logs_dir.display()), + styles.underline.apply_to(super::tilde_path(&logs_dir)), ); } else { progress_ui @@ -708,12 +708,15 @@ pub async fn run_command( if total_tokens > 0 { if acc.has_pricing { eprintln!( - "Cost: {} ({} tokens)", - format_cost(acc.total_cost), - format_tokens_human(total_tokens) + "{}", + styles.dim.apply_to(format!( + "Cost: {} ({} tokens)", + format_cost(acc.total_cost), + format_tokens_human(total_tokens) + )) ); } else { - eprintln!("Tokens: {}", format_tokens_human(total_tokens)); + eprintln!("{}", styles.dim.apply_to(format!("Tokens: {}", format_tokens_human(total_tokens)))); } if acc.total_cache_read_tokens > 0 { eprintln!( @@ -1217,6 +1220,8 @@ async fn generate_retro( } // Run retro agent session + eprintln!("{}", styles.dim.apply_to("Running retro...")); + let retro_start = std::time::Instant::now(); let narrative_result = if dry_run_mode { Ok(crate::retro_agent::dry_run_narrative()) } else if let Some(client) = llm_client { @@ -1224,18 +1229,29 @@ async fn generate_retro( } else { Err(anyhow::anyhow!("No LLM client available")) }; + let retro_dur = progress::format_duration_short(retro_start.elapsed()); match narrative_result { Ok(narrative) => { retro.apply_narrative(narrative); match retro.save(logs_dir) { Ok(()) => { + let retro_path = format!( + "{}/retro.json", + super::tilde_path(logs_dir) + ); + let msg = format!("Retro saved to {retro_path}"); + let term_width = console::Term::stderr() + .size() + .1 as usize; + let dur_len = retro_dur.len(); + let pad = term_width.saturating_sub(msg.len() + dur_len); eprintln!( - "{} {}", + "{} {}{:pad$}{}", styles.dim.apply_to("Retro saved to"), - styles - .underline - .apply_to(format!("{}/retro.json", logs_dir.display())), + styles.underline.apply_to(&retro_path), + "", + styles.dim.apply_to(&retro_dur), ); } Err(e) => {