agentic_server/
agentic_output.rs1const BLUE: &str = "\u{1b}[38;5;75m";
2const GOLD: &str = "\u{1b}[38;5;214m";
3const BRIGHT_WHITE: &str = "\u{1b}[97m";
4const MAGENTA: &str = "\u{1b}[38;5;207m";
5const BOLD: &str = "\u{1b}[1m";
6const RESET: &str = "\u{1b}[0m";
7
8const CODEX_EXAMPLE: &str = "agentic run codex --model Qwen/...";
9const CLAUDE_EXAMPLE: &str = "agentic run claude --upstream http://127.0.0.1:8000";
10
11#[must_use]
12pub fn render_banner(color: bool) -> String {
13 render_box(
14 &["⚡ Agentic API", " Local agent gateway"],
15 None,
16 BLUE,
17 color,
18 |row| match row {
19 "⚡ Agentic API" if color => {
20 format!("{GOLD}⚡{RESET} {BLUE}{BOLD}Agentic{RESET} {GOLD}{BOLD}API{RESET}")
21 }
22 "⚡ Agentic API" => row.to_owned(),
23 _ if color => format!("{BLUE}{row}{RESET}"),
24 _ => row.to_owned(),
25 },
26 )
27}
28
29#[must_use]
30pub fn render_help(help: &str, color: bool) -> String {
31 let mut rendered = String::with_capacity(help.len() + 512);
32 rendered.push_str(&render_banner(color));
33 rendered.push_str("\n\n");
34 rendered.push_str(help.trim());
35 if strip_ansi_codes(help).contains("Usage: agentic <COMMAND>") {
36 rendered.push_str("\n\n");
37 rendered.push_str(&render_examples(color));
38 }
39 rendered
40}
41
42fn strip_ansi_codes(value: &str) -> String {
43 let mut plain = String::with_capacity(value.len());
44 let mut in_escape = false;
45 for character in value.chars() {
46 if in_escape {
47 if character.is_ascii_alphabetic() {
48 in_escape = false;
49 }
50 } else if character == '\u{1b}' {
51 in_escape = true;
52 } else {
53 plain.push(character);
54 }
55 }
56 plain
57}
58
59#[must_use]
60pub fn colorize_help(help: &str, color: bool) -> String {
61 if !color {
62 return help.to_owned();
63 }
64
65 help.lines()
66 .map(|line| {
67 let line = line.replace("Usage:", &format!("{BLUE}{BOLD}Usage:{RESET}"));
68 let line = line.replace("<COMMAND>", &format!("{MAGENTA}{BOLD}<COMMAND>{RESET}"));
69 if line.trim_end().ends_with(':') {
70 format!("{BLUE}{BOLD}{line}{RESET}")
71 } else {
72 line
73 }
74 })
75 .collect::<Vec<_>>()
76 .join("\n")
77}
78
79#[must_use]
80pub fn render_examples(color: bool) -> String {
81 render_box(&[CODEX_EXAMPLE, CLAUDE_EXAMPLE], Some("Examples"), GOLD, color, |row| {
82 if color {
83 format!("{BRIGHT_WHITE}{row}{RESET}")
84 } else {
85 row.to_owned()
86 }
87 })
88}
89
90fn render_box<F>(rows: &[&str], title: Option<&str>, border_color: &str, color: bool, style_row: F) -> String
91where
92 F: Fn(&str) -> String,
93{
94 let content_width = rows.iter().map(|row| display_width(row) + 2).max().unwrap_or(2);
95 let inner_width = title.map_or(content_width, |title| content_width.max(display_width(title) + 3));
96 let border = |text: String| {
97 if color {
98 format!("{border_color}{text}{RESET}")
99 } else {
100 text
101 }
102 };
103 let top = match title {
104 Some(title) => format!(
105 "╭─ {title} {}╮",
106 "─".repeat(inner_width.saturating_sub(display_width(title) + 3))
107 ),
108 None => format!("┌{}┐", "─".repeat(inner_width)),
109 };
110 let mut lines = vec![border(top)];
111 for row in rows {
112 let padding = inner_width.saturating_sub(display_width(row) + 2);
113 let content = style_row(row);
114 lines.push(if color {
115 format!(
116 "{border_color}│{RESET} {content}{}{border_color} │{RESET}",
117 " ".repeat(padding)
118 )
119 } else {
120 format!("│ {content}{} │", " ".repeat(padding))
121 });
122 }
123 lines.push(border(format!("╰{}╯", "─".repeat(inner_width))));
124 lines.join("\n")
125}
126
127#[must_use]
128pub fn redact_url(url: &str) -> String {
129 let Some((scheme, rest)) = url.split_once("://") else {
130 return url.to_owned();
131 };
132 let suffix_start = rest.find(['/', '?', '#']).unwrap_or(rest.len());
133 let (authority, suffix) = rest.split_at(suffix_start);
134 let Some((userinfo, host)) = authority.split_once('@') else {
135 return url.to_owned();
136 };
137 let Some((username, _password)) = userinfo.split_once(':') else {
138 return url.to_owned();
139 };
140 format!("{scheme}://{username}:[REDACTED]@{host}{suffix}")
141}
142
143fn display_width(value: &str) -> usize {
144 value
145 .chars()
146 .map(|character| usize::from(character != '\u{fe0f}'))
147 .sum()
148}
149
150#[cfg(test)]
151mod tests {
152 use super::{colorize_help, redact_url, render_banner, render_examples, render_help};
153
154 #[test]
155 fn banner_rows_have_equal_display_width() {
156 let banner = render_banner(false);
157 let widths: Vec<_> = banner.lines().map(display_width).collect();
158 assert!(widths.windows(2).all(|pair| pair[0] == pair[1]));
159 }
160
161 #[test]
162 fn examples_box_rows_have_equal_display_width() {
163 let examples = render_examples(false);
164 let widths: Vec<_> = examples.lines().map(display_width).collect();
165 assert!(widths.windows(2).all(|pair| pair[0] == pair[1]));
166 }
167
168 #[test]
169 fn help_has_logo_and_examples_box() {
170 let help = render_help("Usage: agentic <COMMAND>\n\nCommands:", false);
171 assert!(help.contains("⚡ Agentic API"));
172 assert!(help.contains("╭─ Examples"));
173 }
174
175 #[test]
176 fn styled_root_usage_keeps_examples_box() {
177 let help = render_help("Usage: agentic \u{1b}[35m<COMMAND>\u{1b}[0m", false);
178
179 assert!(help.contains("╭─ Examples"));
180 }
181
182 #[test]
183 fn help_colors_command_placeholder() {
184 let help = colorize_help("Usage: agentic <COMMAND>", true);
185 assert!(help.contains("\u{1b}[38;5;207m\u{1b}[1m<COMMAND>"));
186 assert!(!colorize_help("Usage: agentic <COMMAND>", false).contains('\u{1b}'));
187 }
188
189 #[test]
190 fn banner_can_be_colored() {
191 let banner = render_banner(true);
192 assert!(banner.contains("\u{1b}[38;5;75m"));
193 assert!(banner.contains("\u{1b}[38;5;214m"));
194 assert!(!render_banner(false).contains('\u{1b}'));
195 }
196
197 #[test]
198 fn examples_box_can_be_colored() {
199 let examples = render_examples(true);
200 assert!(examples.contains("\u{1b}[38;5;214m"));
201 assert!(examples.contains("\u{1b}[97magentic run codex"));
202 assert!(!render_examples(false).contains('\u{1b}'));
203 }
204
205 #[test]
206 fn redact_url_hides_password() {
207 assert_eq!(
208 redact_url("postgresql://alice:secret@db.example/agentic"),
209 "postgresql://alice:[REDACTED]@db.example/agentic"
210 );
211 assert_eq!(
212 redact_url("postgresql://alice:secret@db.example"),
213 "postgresql://alice:[REDACTED]@db.example"
214 );
215 assert_eq!(
216 redact_url("postgresql://alice:secret@db.example?sslmode=require"),
217 "postgresql://alice:[REDACTED]@db.example?sslmode=require"
218 );
219 }
220
221 fn display_width(value: &str) -> usize {
222 value.chars().count()
223 }
224}