use std::{fmt::Write as _, io::IsTerminal};
pub(crate) fn use_human_output(json_output: bool, stdout_is_terminal: bool) -> bool {
!json_output && stdout_is_terminal
}
pub(crate) fn stdout_is_terminal() -> bool {
std::io::stdout().is_terminal()
}
pub(crate) fn unix_now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |duration| duration.as_secs())
}
fn unsafe_display(character: char) -> bool {
character.is_control()
|| matches!(
character,
'\u{061c}' | '\u{200b}'..='\u{200f}' | '\u{202a}'..='\u{202e}' | '\u{2060}'..='\u{2064}' | '\u{2066}'..='\u{2069}' | '\u{feff}' )
}
pub(crate) fn safe_text(text: &str) -> String {
text.chars()
.map(|character| {
if unsafe_display(character) {
' '
} else {
character
}
})
.collect()
}
pub(crate) fn cell(text: &str, max_chars: usize) -> String {
let text = safe_text(text);
let mut characters = text.chars();
let cut: String = characters.by_ref().take(max_chars).collect();
if characters.next().is_some() {
format!("{cut}…")
} else {
cut
}
}
pub(crate) fn elapsed_text(created_at: Option<u64>, now: u64) -> String {
let Some(started) = created_at else {
return "-".to_owned();
};
let elapsed = now.saturating_sub(started);
if elapsed < 60 {
format!("{elapsed}s")
} else if elapsed < 3_600 {
format!("{}m", elapsed / 60)
} else {
format!("{}h", elapsed / 3_600)
}
}
fn short_id(task_id: &str) -> String {
task_id.chars().take(8).collect()
}
pub(crate) struct StatusRow {
pub task_id: String,
pub state: String,
pub harness: String,
pub created_at: Option<u64>,
pub objective: String,
}
pub(crate) struct TaskDetail {
pub task_id: String,
pub state: String,
pub harness: String,
pub model: Option<String>,
pub workspace: String,
pub created_at: Option<u64>,
pub outcome: Option<String>,
pub questions: Option<u64>,
}
pub(crate) struct RunReceipt {
pub state: String,
pub outcome: Option<String>,
pub harness: String,
pub workspace: String,
pub task_id: String,
}
pub(crate) struct DoctorProblem {
pub name: String,
pub reason: String,
pub command: Option<String>,
}
const CELL_MAX: usize = 60;
fn column_widths(widths: &[usize], cells: &[&str]) -> Vec<usize> {
let mut widths = widths.to_vec();
for (index, cell) in cells.iter().enumerate() {
widths[index] = widths[index].max(cell.chars().count());
}
widths
}
fn pad(cell: &str, width: usize) -> String {
let mut padded = cell.to_owned();
for _ in cell.chars().count()..width {
padded.push(' ');
}
padded
}
pub(crate) fn status_table(rows: &[StatusRow], now: u64) -> String {
if rows.is_empty() {
return "no tasks\n".to_owned();
}
let headers = ["ID", "STATE", "HARNESS", "ELAPSED", "OBJECTIVE"];
let mut widths = column_widths(&[0; 5], &headers);
let cells: Vec<[String; 5]> = rows
.iter()
.map(|row| {
[
short_id(&row.task_id),
cell(&row.state, CELL_MAX),
cell(&row.harness, CELL_MAX),
elapsed_text(row.created_at, now),
cell(&row.objective, CELL_MAX),
]
})
.collect();
for row in &cells {
widths = column_widths(&widths, &row.each_ref().map(String::as_str));
}
let mut output = String::new();
for (index, header) in headers.iter().enumerate() {
if index > 0 {
output.push_str(" ");
}
output.push_str(&pad(header, widths[index]));
}
output.push('\n');
for row in &cells {
for (index, cell) in row.iter().enumerate() {
if index > 0 {
output.push_str(" ");
}
if index + 1 == row.len() {
output.push_str(cell);
} else {
output.push_str(&pad(cell, widths[index]));
}
}
output.push('\n');
}
output
}
pub(crate) fn task_detail(detail: &TaskDetail, now: u64) -> String {
let mut output = String::new();
let mut line = |key: &str, value: &str| {
let _ = writeln!(output, "{key}: {value}");
};
line("task", &detail.task_id);
line("state", &safe_text(&detail.state));
line("harness", &safe_text(&detail.harness));
line(
"model",
&detail
.model
.as_deref()
.map_or_else(|| "-".to_owned(), safe_text),
);
line("workspace", &safe_text(&detail.workspace));
line("elapsed", &elapsed_text(detail.created_at, now));
if let Some(outcome) = &detail.outcome {
line("result", &safe_text(outcome));
}
if let Some(questions) = detail.questions {
line("questions", &questions.to_string());
}
let _ = writeln!(output, "full detail: brgr status {} --json", detail.task_id);
output
}
pub(crate) fn print_run_receipt(
receipt: &serde_json::Value,
human: &RunReceipt,
json_output: bool,
) {
if use_human_output(json_output, stdout_is_terminal()) {
print!("{}", run_receipt(human));
} else {
crate::print_value(receipt, json_output);
}
}
pub(crate) fn print_doctor(
value: &serde_json::Value,
problems: &[DoctorProblem],
json_output: bool,
) {
if use_human_output(json_output, stdout_is_terminal()) {
print!("{}", doctor_summary(problems));
}
crate::print_value(value, json_output);
}
pub(crate) fn doctor_summary(problems: &[DoctorProblem]) -> String {
if problems.is_empty() {
return "ok\n".to_owned();
}
let mut output = String::new();
for (index, problem) in problems.iter().enumerate() {
if index == 0 {
output.push_str("needs_attention: ");
}
let name = safe_text(&problem.name);
let reason = safe_text(&problem.reason);
match problem.command.as_deref() {
Some(command) => {
let command = safe_text(command);
let _ = writeln!(output, "{name} ({reason}: {command})");
}
None => {
let _ = writeln!(output, "{name} ({reason})");
}
}
}
output
}
pub(crate) fn run_receipt(receipt: &RunReceipt) -> String {
let mut output = String::new();
let _ = writeln!(output, "state: {}", safe_text(&receipt.state));
if let Some(outcome) = &receipt.outcome {
let _ = writeln!(output, "outcome: {}", safe_text(outcome));
}
let _ = writeln!(output, "harness: {}", safe_text(&receipt.harness));
let _ = writeln!(output, "workspace: {}", safe_text(&receipt.workspace));
let _ = writeln!(output, "task: {}", receipt.task_id);
output
}
#[cfg(test)]
mod tests {
use super::*;
fn row(
task_id: &str,
state: &str,
harness: &str,
created_at: Option<u64>,
objective: &str,
) -> StatusRow {
StatusRow {
task_id: task_id.to_owned(),
state: state.to_owned(),
harness: harness.to_owned(),
created_at,
objective: objective.to_owned(),
}
}
#[test]
fn human_output_needs_a_terminal_and_no_json_flag() {
assert!(use_human_output(false, true));
assert!(!use_human_output(true, true));
assert!(!use_human_output(false, false));
assert!(!use_human_output(true, false));
}
#[test]
fn an_empty_status_list_says_no_tasks() {
assert_eq!(status_table(&[], 1_000), "no tasks\n");
}
#[test]
fn the_status_table_is_a_header_plus_one_line_per_task() {
let rows = vec![
row(
"2b2c279a-b8e1-43a8-846c-cb8344a301d7",
"terminal",
"local.gjc",
Some(940),
"Review this change",
),
row(
"aaaaaaaa-1111-4111-8111-111111111111",
"running",
"local.opencode",
Some(1_000),
"Second task",
),
];
let text = status_table(&rows, 1_000);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(lines.len(), 3, "{text}");
assert!(lines[0].contains("ID"), "{text}");
assert!(lines[0].contains("STATE"), "{text}");
assert!(lines[0].contains("HARNESS"), "{text}");
assert!(lines[0].contains("ELAPSED"), "{text}");
assert!(lines[0].contains("OBJECTIVE"), "{text}");
assert_eq!(&lines[1][..8], "2b2c279a");
assert!(lines[1].contains("terminal"));
assert!(lines[1].contains("local.gjc"));
assert!(lines[1].contains("1m"));
assert!(lines[1].contains("Review this change"));
assert_eq!(&lines[2][..8], "aaaaaaaa");
assert!(lines[2].contains("0s"), "{text}");
assert!(!text.contains("2b2c279a-b8e1-43a8-846c-cb8344a301d7"));
}
#[test]
fn elapsed_is_dash_without_a_clock_then_seconds_minutes_hours() {
assert_eq!(elapsed_text(None, 1_000), "-");
assert_eq!(elapsed_text(Some(988), 1_000), "12s");
assert_eq!(elapsed_text(Some(0), 12), "12s");
assert_eq!(elapsed_text(Some(760), 1_000), "4m");
assert_eq!(elapsed_text(Some(0), 7_200), "2h");
assert_eq!(elapsed_text(Some(0), 86_400), "24h");
assert_eq!(elapsed_text(Some(2_000), 1_000), "0s");
}
#[test]
fn the_objective_cell_cuts_at_sixty_chars_with_an_ellipsis() {
let exact = "a".repeat(60);
assert_eq!(cell(&exact, 60), exact);
let over = "b".repeat(61);
let shown = cell(&over, 60);
assert_eq!(shown.chars().count(), 61, "{shown}");
assert!(shown.ends_with('…'));
assert_eq!(&shown[..60], &over[..60]);
}
#[test]
fn the_objective_cell_does_not_split_a_multibyte_character() {
let mut objective = "ê°€".repeat(61);
let shown = cell(&objective, 60);
assert_eq!(shown.chars().count(), 61);
assert_eq!(&shown[..180], &objective[..180]);
objective.insert(0, 'é');
let shown = cell(&objective, 60);
assert_eq!(shown.chars().count(), 61);
assert!(shown.starts_with('é'));
assert!(shown.ends_with('…'));
}
#[test]
fn the_arabic_letter_mark_cannot_reorder_a_line() {
let text = safe_text("owner\u{061c}name");
assert!(!text.contains('\u{061c}'), "{text:?}");
assert_eq!(text, "owner name");
}
#[test]
fn untrusted_text_loses_every_terminal_control_character() {
assert_eq!(safe_text("plain"), "plain");
assert_eq!(safe_text("a\nb\tc"), "a b c");
assert_eq!(safe_text("ok\u{1b}[31mred"), "ok [31mred");
assert_eq!(safe_text("\u{1b}[2Jhidden"), " [2Jhidden");
assert_eq!(safe_text("a\u{202e}b"), "a b");
assert_eq!(safe_text("a\u{200b}b"), "a b");
assert_eq!(safe_text("a\u{2066}b"), "a b");
assert_eq!(safe_text("a\u{feff}b"), "a b");
let hostile: String = (0_u32..0xa0)
.filter_map(char::from_u32)
.filter(|c| c.is_control())
.collect();
assert!(safe_text(&hostile).chars().all(|c| c == ' '));
}
#[test]
fn a_cell_is_sanitized_before_it_is_cut() {
let hostile = "\u{1b}[2J".repeat(20);
let shown = cell(&hostile, 60);
assert!(!shown.contains('\u{1b}'));
assert!(shown.contains("[2J"), "{shown}");
assert_eq!(shown.chars().count(), 61, "{shown}");
}
#[test]
fn task_detail_lists_the_summary_then_points_at_json() {
let detail = TaskDetail {
task_id: "2b2c279a-b8e1-43a8-846c-cb8344a301d7".to_owned(),
state: "terminal".to_owned(),
harness: "local.gjc".to_owned(),
model: Some("swe-1.6".to_owned()),
workspace: "/tmp/work tree".to_owned(),
created_at: Some(940),
outcome: Some("candidate".to_owned()),
questions: Some(2),
};
let text = task_detail(&detail, 1_000);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(lines[0], "task: 2b2c279a-b8e1-43a8-846c-cb8344a301d7");
assert_eq!(lines[1], "state: terminal");
assert_eq!(lines[2], "harness: local.gjc");
assert_eq!(lines[3], "model: swe-1.6");
assert_eq!(lines[4], "workspace: /tmp/work tree");
assert_eq!(lines[5], "elapsed: 1m");
assert_eq!(lines[6], "result: candidate");
assert_eq!(lines[7], "questions: 2");
assert_eq!(
lines[8],
"full detail: brgr status 2b2c279a-b8e1-43a8-846c-cb8344a301d7 --json"
);
assert_eq!(lines.len(), 9, "{text}");
}
#[test]
fn task_detail_omits_missing_model_result_and_questions() {
let detail = TaskDetail {
task_id: "2b2c279a-b8e1-43a8-846c-cb8344a301d7".to_owned(),
state: "running".to_owned(),
harness: "local.gjc".to_owned(),
model: None,
workspace: "/tmp/work".to_owned(),
created_at: None,
outcome: None,
questions: None,
};
let text = task_detail(&detail, 1_000);
assert!(text.contains("model: -"));
assert!(text.contains("elapsed: -"));
assert!(!text.contains("result:"));
assert!(!text.contains("questions:"));
assert!(
text.ends_with(
"full detail: brgr status 2b2c279a-b8e1-43a8-846c-cb8344a301d7 --json\n"
)
);
}
#[test]
fn task_detail_sanitizes_untrusted_fields() {
let detail = TaskDetail {
task_id: "2b2c279a-b8e1-43a8-846c-cb8344a301d7".to_owned(),
state: "running".to_owned(),
harness: "local.gjc\u{1b}[31m".to_owned(),
model: None,
workspace: "/tmp/work\n/etc".to_owned(),
created_at: None,
outcome: None,
questions: None,
};
let text = task_detail(&detail, 1_000);
assert!(!text.contains('\u{1b}'), "{text:?}");
assert!(text.contains("harness: local.gjc [31m"));
assert!(text.contains("workspace: /tmp/work /etc"));
}
#[test]
fn run_receipt_ends_with_the_full_task_id() {
let receipt = RunReceipt {
state: "starting".to_owned(),
outcome: None,
harness: "local.gjc".to_owned(),
workspace: "/tmp/work".to_owned(),
task_id: "2b2c279a-b8e1-43a8-846c-cb8344a301d7".to_owned(),
};
let text = run_receipt(&receipt);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(lines[0], "state: starting");
assert_eq!(lines[1], "harness: local.gjc");
assert_eq!(lines[2], "workspace: /tmp/work");
assert_eq!(
lines.last(),
Some(&"task: 2b2c279a-b8e1-43a8-846c-cb8344a301d7")
);
}
#[test]
fn run_receipt_shows_a_finished_runs_outcome() {
let receipt = RunReceipt {
state: "terminal".to_owned(),
outcome: Some("candidate".to_owned()),
harness: "local.gjc".to_owned(),
workspace: "/tmp/work".to_owned(),
task_id: "2b2c279a-b8e1-43a8-846c-cb8344a301d7".to_owned(),
};
let text = run_receipt(&receipt);
assert!(text.contains("outcome: candidate"), "{text}");
assert!(text.ends_with("task: 2b2c279a-b8e1-43a8-846c-cb8344a301d7\n"));
}
#[test]
fn run_receipt_sanitizes_workspace_and_harness() {
let receipt = RunReceipt {
state: "starting".to_owned(),
outcome: None,
harness: "bad\u{1b}[31m".to_owned(),
workspace: "/tmp/a\u{202e}b".to_owned(),
task_id: "2b2c279a-b8e1-43a8-846c-cb8344a301d7".to_owned(),
};
let text = run_receipt(&receipt);
assert!(!text.contains('\u{1b}'), "{text:?}");
assert!(!text.contains('\u{202e}'), "{text:?}");
assert!(text.contains("harness: bad [31m"), "{text}");
assert!(text.contains("workspace: /tmp/a b"), "{text}");
assert!(text.ends_with("task: 2b2c279a-b8e1-43a8-846c-cb8344a301d7\n"));
}
#[test]
fn doctor_summary_for_one_problem_names_the_item_and_the_fix_command() {
let text = doctor_summary(&[DoctorProblem {
name: "local.cline".to_owned(),
reason: "recertify".to_owned(),
command: Some("brgr harness recertify local.cline".to_owned()),
}]);
let first = text.lines().next().expect("summary has a first line");
assert_eq!(
first,
"needs_attention: local.cline (recertify: brgr harness recertify local.cline)"
);
assert_eq!(text.lines().count(), 1, "{text}");
}
#[test]
fn doctor_summary_when_there_is_no_problem_is_ok() {
assert_eq!(doctor_summary(&[]), "ok\n");
}
#[test]
fn doctor_summary_lists_each_problem_on_its_own_line() {
let text = doctor_summary(&[
DoctorProblem {
name: "local.cline".to_owned(),
reason: "recertify".to_owned(),
command: Some("brgr harness recertify local.cline".to_owned()),
},
DoctorProblem {
name: "codex_trace".to_owned(),
reason: "not verified: codex-cli 0.162.1, verified baseline 0.160".to_owned(),
command: None,
},
]);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(
lines[0],
"needs_attention: local.cline (recertify: brgr harness recertify local.cline)"
);
assert_eq!(
lines[1],
"codex_trace (not verified: codex-cli 0.162.1, verified baseline 0.160)"
);
assert_eq!(lines.len(), 2, "{text}");
}
#[test]
fn doctor_summary_sanitizes_harness_and_manifest_text() {
let text = doctor_summary(&[DoctorProblem {
name: "local.bad\u{1b}[31m".to_owned(),
reason: "recertify\nnow".to_owned(),
command: Some("brgr harness recertify local.bad\u{202e}".to_owned()),
}]);
assert!(!text.contains('\u{1b}'), "{text:?}");
assert!(!text.contains('\u{202e}'), "{text:?}");
assert_eq!(text.lines().count(), 1, "{text}");
assert!(text.contains("local.bad [31m"), "{text}");
assert!(text.contains("recertify now"), "{text}");
}
}