grok-cli-acp 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +42 -0
- package/.github/workflows/ci.yml +30 -0
- package/.github/workflows/rust.yml +22 -0
- package/.grok/.env.example +85 -0
- package/.grok/COMPLETE_FIX_SUMMARY.md +466 -0
- package/.grok/ENV_CONFIG_GUIDE.md +173 -0
- package/.grok/QUICK_REFERENCE.md +180 -0
- package/.grok/README.md +104 -0
- package/.grok/TESTING_GUIDE.md +393 -0
- package/CHANGELOG.md +465 -0
- package/CODE_REVIEW_SUMMARY.md +414 -0
- package/COMPLETE_FIX_SUMMARY.md +415 -0
- package/CONFIGURATION.md +489 -0
- package/CONTEXT_FILES_GUIDE.md +419 -0
- package/CONTRIBUTING.md +55 -0
- package/CURSOR_POSITION_FIX.md +206 -0
- package/Cargo.toml +88 -0
- package/ERROR_HANDLING_REPORT.md +361 -0
- package/FINAL_FIX_SUMMARY.md +462 -0
- package/FIXES.md +37 -0
- package/FIXES_SUMMARY.md +87 -0
- package/GROK_API_MIGRATION_SUMMARY.md +111 -0
- package/LICENSE +22 -0
- package/MIGRATION_TO_GROK_API.md +223 -0
- package/README.md +504 -0
- package/REVIEW_COMPLETE.md +416 -0
- package/REVIEW_QUICK_REFERENCE.md +173 -0
- package/SECURITY.md +463 -0
- package/SECURITY_AUDIT.md +661 -0
- package/SETUP.md +287 -0
- package/TESTING_TOOLS.md +88 -0
- package/TESTING_TOOL_EXECUTION.md +239 -0
- package/TOOL_EXECUTION_FIX.md +491 -0
- package/VERIFICATION_CHECKLIST.md +419 -0
- package/docs/API.md +74 -0
- package/docs/CHAT_LOGGING.md +39 -0
- package/docs/CURSOR_FIX_DEMO.md +306 -0
- package/docs/ERROR_HANDLING_GUIDE.md +547 -0
- package/docs/FILE_OPERATIONS.md +449 -0
- package/docs/INTERACTIVE.md +401 -0
- package/docs/PROJECT_CREATION_GUIDE.md +570 -0
- package/docs/QUICKSTART.md +378 -0
- package/docs/QUICK_REFERENCE.md +691 -0
- package/docs/RELEASE_NOTES_0.1.2.md +240 -0
- package/docs/TOOLS.md +459 -0
- package/docs/TOOLS_QUICK_REFERENCE.md +210 -0
- package/docs/ZED_INTEGRATION.md +371 -0
- package/docs/extensions.md +464 -0
- package/docs/settings.md +293 -0
- package/examples/extensions/logging-hook/README.md +91 -0
- package/examples/extensions/logging-hook/extension.json +22 -0
- package/package.json +30 -0
- package/scripts/test_acp.py +252 -0
- package/scripts/test_acp.sh +143 -0
- package/scripts/test_acp_simple.sh +72 -0
- package/src/acp/mod.rs +741 -0
- package/src/acp/protocol.rs +323 -0
- package/src/acp/security.rs +298 -0
- package/src/acp/tools.rs +697 -0
- package/src/bin/banner_demo.rs +216 -0
- package/src/bin/docgen.rs +18 -0
- package/src/bin/installer.rs +217 -0
- package/src/cli/app.rs +310 -0
- package/src/cli/commands/acp.rs +721 -0
- package/src/cli/commands/chat.rs +485 -0
- package/src/cli/commands/code.rs +513 -0
- package/src/cli/commands/config.rs +394 -0
- package/src/cli/commands/health.rs +442 -0
- package/src/cli/commands/history.rs +421 -0
- package/src/cli/commands/mod.rs +14 -0
- package/src/cli/commands/settings.rs +1384 -0
- package/src/cli/mod.rs +166 -0
- package/src/config/mod.rs +2212 -0
- package/src/display/ascii_art.rs +139 -0
- package/src/display/banner.rs +289 -0
- package/src/display/components/input.rs +323 -0
- package/src/display/components/mod.rs +2 -0
- package/src/display/components/settings_list.rs +306 -0
- package/src/display/interactive.rs +1255 -0
- package/src/display/mod.rs +62 -0
- package/src/display/terminal.rs +42 -0
- package/src/display/tips.rs +316 -0
- package/src/grok_client_ext.rs +177 -0
- package/src/hooks/loader.rs +407 -0
- package/src/hooks/mod.rs +158 -0
- package/src/lib.rs +174 -0
- package/src/main.rs +65 -0
- package/src/mcp/client.rs +195 -0
- package/src/mcp/config.rs +20 -0
- package/src/mcp/mod.rs +6 -0
- package/src/mcp/protocol.rs +67 -0
- package/src/utils/auth.rs +41 -0
- package/src/utils/chat_logger.rs +568 -0
- package/src/utils/context.rs +390 -0
- package/src/utils/mod.rs +16 -0
- package/src/utils/network.rs +320 -0
- package/src/utils/rate_limiter.rs +166 -0
- package/src/utils/session.rs +73 -0
- package/src/utils/shell_permissions.rs +389 -0
- package/src/utils/telemetry.rs +41 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
//! ASCII art definitions for Grok CLI
|
|
2
|
+
//!
|
|
3
|
+
//! Contains various sizes of the Grok logo and related ASCII art
|
|
4
|
+
|
|
5
|
+
use colored::*;
|
|
6
|
+
|
|
7
|
+
/// Full Grok logo ASCII art (large version)
|
|
8
|
+
pub const GROK_LOGO_LARGE: &str = r#"
|
|
9
|
+
░██████╗░██████╗░░█████╗░██╗░░██╗
|
|
10
|
+
██╔════╝░██╔══██╗██╔══██╗██║░██╔╝
|
|
11
|
+
██║░░██╗░██████╔╝██║░░██║█████═╝░
|
|
12
|
+
██║░░╚██╗██╔══██╗██║░░██║██╔═██╗░
|
|
13
|
+
╚██████╔╝██║░░██║╚█████╔╝██║░╚██╗
|
|
14
|
+
░╚═════╝░╚═╝░░╚═╝░╚════╝░╚═╝░░╚═╝
|
|
15
|
+
"#;
|
|
16
|
+
|
|
17
|
+
/// Medium Grok logo ASCII art
|
|
18
|
+
pub const GROK_LOGO_MEDIUM: &str = r#"
|
|
19
|
+
██████╗ ██████╗ ██████╗ ██╗ ██╗
|
|
20
|
+
██╔════╝ ██╔══██╗██╔═══██╗██║ ██╔╝
|
|
21
|
+
██║ ███╗██████╔╝██║ ██║█████╔╝
|
|
22
|
+
██║ ██║██╔══██╗██║ ██║██╔═██╗
|
|
23
|
+
╚██████╔╝██║ ██║╚██████╔╝██║ ██╗
|
|
24
|
+
╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
|
|
25
|
+
"#;
|
|
26
|
+
|
|
27
|
+
/// Small Grok logo ASCII art
|
|
28
|
+
pub const GROK_LOGO_SMALL: &str = r#"
|
|
29
|
+
▄████ ██▀███ ▒█████ ██ ▄█▀
|
|
30
|
+
██▒ ▀█▒▓██ ▒ ██▒▒██▒ ██▒ ██▄█▒
|
|
31
|
+
▒██░▄▄▄░▓██ ░▄█ ▒▒██░ ██▒▓███▄░
|
|
32
|
+
░▓█ ██▓▒██▀▀█▄ ▒██ ██░▓██ █▄
|
|
33
|
+
░▒▓███▀▒░██▓ ▒██▒░ ████▓▒░▒██▒ █▄
|
|
34
|
+
░▒ ▒ ░ ▒▓ ░▒▓░░ ▒░▒░▒░ ▒ ▒▒ ▓▒
|
|
35
|
+
"#;
|
|
36
|
+
|
|
37
|
+
/// Tiny Grok logo ASCII art (single line)
|
|
38
|
+
pub const GROK_LOGO_TINY: &str = r#"GROK"#;
|
|
39
|
+
|
|
40
|
+
/// X.ai branding ASCII art
|
|
41
|
+
pub const X_AI_BRANDING: &str = r#"
|
|
42
|
+
▄▀█ █ █▄▄ █▄█
|
|
43
|
+
█▄█ ▄ █ ▄ █▄█ █
|
|
44
|
+
"#;
|
|
45
|
+
|
|
46
|
+
/// Get the appropriate logo based on terminal width
|
|
47
|
+
pub fn get_logo_for_width(width: u16) -> &'static str {
|
|
48
|
+
if width >= 60 {
|
|
49
|
+
GROK_LOGO_LARGE
|
|
50
|
+
} else if width >= 45 {
|
|
51
|
+
GROK_LOGO_MEDIUM
|
|
52
|
+
} else if width >= 30 {
|
|
53
|
+
GROK_LOGO_SMALL
|
|
54
|
+
} else {
|
|
55
|
+
GROK_LOGO_TINY
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Get the width of the ASCII art
|
|
60
|
+
pub fn get_logo_width(logo: &str) -> usize {
|
|
61
|
+
logo.lines()
|
|
62
|
+
.map(|line| line.trim_end().len())
|
|
63
|
+
.max()
|
|
64
|
+
.unwrap_or(0)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Print the Grok logo with gradient colors
|
|
68
|
+
pub fn print_grok_logo(width: u16) {
|
|
69
|
+
let logo = get_logo_for_width(width);
|
|
70
|
+
let lines: Vec<&str> = logo.lines().collect();
|
|
71
|
+
|
|
72
|
+
for (i, line) in lines.iter().enumerate() {
|
|
73
|
+
if line.trim().is_empty() {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Apply gradient from blue to purple to pink
|
|
78
|
+
let colored_line = match i % 3 {
|
|
79
|
+
0 => line.bright_blue(),
|
|
80
|
+
1 => line.bright_magenta(),
|
|
81
|
+
_ => line.bright_cyan(),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
println!("{}", colored_line);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Print X.ai branding
|
|
89
|
+
pub fn print_x_ai_branding() {
|
|
90
|
+
for line in X_AI_BRANDING.lines() {
|
|
91
|
+
if !line.trim().is_empty() {
|
|
92
|
+
println!("{}", line.bright_black());
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Animated logo display (for fun startup effect)
|
|
98
|
+
pub fn print_animated_logo(width: u16) {
|
|
99
|
+
use std::{thread, time::Duration};
|
|
100
|
+
|
|
101
|
+
let logo = get_logo_for_width(width);
|
|
102
|
+
let lines: Vec<&str> = logo.lines().collect();
|
|
103
|
+
|
|
104
|
+
for (i, line) in lines.iter().enumerate() {
|
|
105
|
+
if line.trim().is_empty() {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Apply gradient and print with small delay
|
|
110
|
+
let colored_line = match i % 3 {
|
|
111
|
+
0 => line.bright_blue(),
|
|
112
|
+
1 => line.bright_magenta(),
|
|
113
|
+
_ => line.bright_cyan(),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
println!("{}", colored_line);
|
|
117
|
+
thread::sleep(Duration::from_millis(100));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[cfg(test)]
|
|
122
|
+
mod tests {
|
|
123
|
+
use super::*;
|
|
124
|
+
|
|
125
|
+
#[test]
|
|
126
|
+
fn test_get_logo_for_width() {
|
|
127
|
+
assert_eq!(get_logo_for_width(80), GROK_LOGO_LARGE);
|
|
128
|
+
assert_eq!(get_logo_for_width(50), GROK_LOGO_MEDIUM);
|
|
129
|
+
assert_eq!(get_logo_for_width(35), GROK_LOGO_SMALL);
|
|
130
|
+
assert_eq!(get_logo_for_width(20), GROK_LOGO_TINY);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#[test]
|
|
134
|
+
fn test_get_logo_width() {
|
|
135
|
+
assert!(get_logo_width(GROK_LOGO_LARGE) > 0);
|
|
136
|
+
assert!(get_logo_width(GROK_LOGO_MEDIUM) > 0);
|
|
137
|
+
assert!(get_logo_width(GROK_LOGO_SMALL) > 0);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
//! Banner display module for Grok CLI
|
|
2
|
+
//!
|
|
3
|
+
//! Handles startup banners, update notifications, and warning messages
|
|
4
|
+
|
|
5
|
+
use colored::*;
|
|
6
|
+
use std::io::{self, Write};
|
|
7
|
+
|
|
8
|
+
/// Banner types for different contexts
|
|
9
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
10
|
+
pub enum BannerType {
|
|
11
|
+
/// Welcome/startup banner
|
|
12
|
+
Welcome,
|
|
13
|
+
/// Update notification
|
|
14
|
+
Update,
|
|
15
|
+
/// Warning message
|
|
16
|
+
Warning,
|
|
17
|
+
/// Error message
|
|
18
|
+
Error,
|
|
19
|
+
/// Information message
|
|
20
|
+
Info,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Banner configuration
|
|
24
|
+
#[derive(Debug, Clone)]
|
|
25
|
+
pub struct BannerConfig {
|
|
26
|
+
pub show_banner: bool,
|
|
27
|
+
pub show_tips: bool,
|
|
28
|
+
pub show_updates: bool,
|
|
29
|
+
pub width: Option<u16>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl Default for BannerConfig {
|
|
33
|
+
fn default() -> Self {
|
|
34
|
+
Self {
|
|
35
|
+
show_banner: true,
|
|
36
|
+
show_tips: true,
|
|
37
|
+
show_updates: true,
|
|
38
|
+
width: None,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Print a bordered banner with content
|
|
44
|
+
pub fn print_banner(title: &str, content: &[&str], banner_type: BannerType, width: Option<u16>) {
|
|
45
|
+
let term_width = width.unwrap_or(crate::display::get_terminal_size().0);
|
|
46
|
+
let banner_width = std::cmp::min(term_width - 4, 80); // Leave margin and cap at 80
|
|
47
|
+
|
|
48
|
+
let (border_color, title_color, content_color) = match banner_type {
|
|
49
|
+
BannerType::Welcome => (Color::Cyan, Color::BrightCyan, Color::White),
|
|
50
|
+
BannerType::Update => (Color::Green, Color::BrightGreen, Color::White),
|
|
51
|
+
BannerType::Warning => (Color::Yellow, Color::BrightYellow, Color::White),
|
|
52
|
+
BannerType::Error => (Color::Red, Color::BrightRed, Color::White),
|
|
53
|
+
BannerType::Info => (Color::Blue, Color::BrightBlue, Color::White),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Top border
|
|
57
|
+
println!(
|
|
58
|
+
"{}",
|
|
59
|
+
format!("┌{}┐", "─".repeat(banner_width as usize - 2)).color(border_color)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Title
|
|
63
|
+
if !title.is_empty() {
|
|
64
|
+
let title_line = format_banner_line(title, banner_width, true);
|
|
65
|
+
println!(
|
|
66
|
+
"{}{}{}",
|
|
67
|
+
"│".color(border_color),
|
|
68
|
+
title_line.color(title_color).bold(),
|
|
69
|
+
"│".color(border_color)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Separator after title
|
|
73
|
+
println!(
|
|
74
|
+
"{}",
|
|
75
|
+
format!("├{}┤", "─".repeat(banner_width as usize - 2)).color(border_color)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Content lines
|
|
80
|
+
for line in content {
|
|
81
|
+
if line.trim().is_empty() {
|
|
82
|
+
// Empty line
|
|
83
|
+
println!(
|
|
84
|
+
"{}{}{}",
|
|
85
|
+
"│".color(border_color),
|
|
86
|
+
" ".repeat(banner_width as usize - 2),
|
|
87
|
+
"│".color(border_color)
|
|
88
|
+
);
|
|
89
|
+
} else {
|
|
90
|
+
let content_line = format_banner_line(line, banner_width, false);
|
|
91
|
+
println!(
|
|
92
|
+
"{}{}{}",
|
|
93
|
+
"│".color(border_color),
|
|
94
|
+
content_line.color(content_color),
|
|
95
|
+
"│".color(border_color)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Bottom border
|
|
101
|
+
println!(
|
|
102
|
+
"{}",
|
|
103
|
+
format!("└{}┘", "─".repeat(banner_width as usize - 2)).color(border_color)
|
|
104
|
+
);
|
|
105
|
+
println!(); // Extra line after banner
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Format a line to fit within banner width with proper padding
|
|
109
|
+
fn format_banner_line(text: &str, width: u16, center: bool) -> String {
|
|
110
|
+
let content_width = width as usize - 4; // Account for borders and padding
|
|
111
|
+
let text = text.trim();
|
|
112
|
+
|
|
113
|
+
if text.len() > content_width {
|
|
114
|
+
// Truncate if too long
|
|
115
|
+
format!(
|
|
116
|
+
" {:<width$} ",
|
|
117
|
+
&text[..content_width],
|
|
118
|
+
width = content_width
|
|
119
|
+
)
|
|
120
|
+
} else if center {
|
|
121
|
+
// Center the text
|
|
122
|
+
let padding = (content_width - text.len()) / 2;
|
|
123
|
+
let right_padding = content_width - text.len() - padding;
|
|
124
|
+
format!(
|
|
125
|
+
" {}{}{} ",
|
|
126
|
+
" ".repeat(padding),
|
|
127
|
+
text,
|
|
128
|
+
" ".repeat(right_padding)
|
|
129
|
+
)
|
|
130
|
+
} else {
|
|
131
|
+
// Left align with padding
|
|
132
|
+
format!(" {:<width$} ", text, width = content_width)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Print welcome banner with logo and tips
|
|
137
|
+
pub fn print_welcome_banner(config: &BannerConfig) {
|
|
138
|
+
if !config.show_banner {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let (width, _) = crate::display::get_terminal_size();
|
|
143
|
+
|
|
144
|
+
// Print logo first
|
|
145
|
+
crate::display::print_grok_logo(width);
|
|
146
|
+
|
|
147
|
+
// Print version
|
|
148
|
+
let version = env!("CARGO_PKG_VERSION");
|
|
149
|
+
let version_text = format!("v{}", version);
|
|
150
|
+
let padding = (width as usize - version_text.len()) / 2;
|
|
151
|
+
println!("{}{}", " ".repeat(padding), version_text.dimmed());
|
|
152
|
+
println!();
|
|
153
|
+
|
|
154
|
+
// Welcome message
|
|
155
|
+
if config.show_tips {
|
|
156
|
+
let content = vec![
|
|
157
|
+
"Tips for getting started:",
|
|
158
|
+
"1. Ask questions, edit files, or run commands.",
|
|
159
|
+
"2. Be specific for the best results.",
|
|
160
|
+
"3. /help for more information.",
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
print_banner("", &content, BannerType::Welcome, config.width);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// Print update notification banner
|
|
168
|
+
pub fn print_update_banner(current_version: &str, latest_version: &str, config: &BannerConfig) {
|
|
169
|
+
if !config.show_updates {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let update_message = format!(
|
|
174
|
+
"Grok CLI update available! {} → {}",
|
|
175
|
+
current_version, latest_version
|
|
176
|
+
);
|
|
177
|
+
let content = vec![
|
|
178
|
+
&update_message,
|
|
179
|
+
"Please run: cargo install --git https://github.com/microtech/grok-cli",
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
print_banner(
|
|
183
|
+
"Update Available",
|
|
184
|
+
&content,
|
|
185
|
+
BannerType::Update,
|
|
186
|
+
config.width,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// Print warning banner
|
|
191
|
+
pub fn print_warning_banner(title: &str, message: &str, config: &BannerConfig) {
|
|
192
|
+
let content = vec![message];
|
|
193
|
+
print_banner(title, &content, BannerType::Warning, config.width);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// Print directory recommendation banner
|
|
197
|
+
pub fn print_directory_recommendation(current_dir: &str, config: &BannerConfig) {
|
|
198
|
+
let home_message = "You are running Grok CLI in your home directory.".to_string();
|
|
199
|
+
let current_dir_message = format!("Current directory: {}", current_dir);
|
|
200
|
+
let content = vec![
|
|
201
|
+
&home_message,
|
|
202
|
+
"It is recommended to run in a project-specific directory.",
|
|
203
|
+
"",
|
|
204
|
+
¤t_dir_message,
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
print_banner(
|
|
208
|
+
"Directory Recommendation",
|
|
209
|
+
&content,
|
|
210
|
+
BannerType::Info,
|
|
211
|
+
config.width,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// Print error banner
|
|
216
|
+
pub fn print_error_banner(title: &str, error: &str) {
|
|
217
|
+
let content = vec![error];
|
|
218
|
+
print_banner(title, &content, BannerType::Error, None);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// Print a simple status line (like the bottom status bar in Gemini CLI)
|
|
222
|
+
pub fn print_status_line(
|
|
223
|
+
mode: &str,
|
|
224
|
+
model: &str,
|
|
225
|
+
context_used: Option<&str>,
|
|
226
|
+
directory: Option<&str>,
|
|
227
|
+
) {
|
|
228
|
+
let (width, _) = crate::display::get_terminal_size();
|
|
229
|
+
|
|
230
|
+
let mut status_parts = vec![mode.to_string()];
|
|
231
|
+
|
|
232
|
+
if let Some(dir) = directory {
|
|
233
|
+
status_parts.push(format!("📁 {}", dir));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
status_parts.push(format!("🤖 {}", model));
|
|
237
|
+
|
|
238
|
+
if let Some(context) = context_used {
|
|
239
|
+
status_parts.push(format!("📊 {}", context));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let status_line = status_parts.join(" | ");
|
|
243
|
+
let padding = if width as usize > status_line.len() {
|
|
244
|
+
width as usize - status_line.len()
|
|
245
|
+
} else {
|
|
246
|
+
0
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
print!("{}", " ".repeat(padding));
|
|
250
|
+
println!("{}", status_line.dimmed());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/// Clear the current line and move cursor to beginning
|
|
254
|
+
pub fn clear_current_line() {
|
|
255
|
+
print!("\r\x1b[K");
|
|
256
|
+
io::stdout().flush().unwrap_or(());
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// Print a simple progress indicator
|
|
260
|
+
pub fn print_progress_dots(count: usize) {
|
|
261
|
+
clear_current_line();
|
|
262
|
+
print!("Thinking{}", ".".repeat((count % 4) + 1));
|
|
263
|
+
io::stdout().flush().unwrap_or(());
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#[cfg(test)]
|
|
267
|
+
mod tests {
|
|
268
|
+
use super::*;
|
|
269
|
+
|
|
270
|
+
#[test]
|
|
271
|
+
fn test_format_banner_line() {
|
|
272
|
+
let result = format_banner_line("Hello", 20, false);
|
|
273
|
+
assert!(result.starts_with(" Hello"));
|
|
274
|
+
assert_eq!(result.len(), 18); // 20 - 2 for padding chars
|
|
275
|
+
|
|
276
|
+
let result = format_banner_line("Hello", 20, true);
|
|
277
|
+
assert!(result.contains("Hello"));
|
|
278
|
+
assert_eq!(result.len(), 18);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#[test]
|
|
282
|
+
fn test_banner_config_default() {
|
|
283
|
+
let config = BannerConfig::default();
|
|
284
|
+
assert!(config.show_banner);
|
|
285
|
+
assert!(config.show_tips);
|
|
286
|
+
assert!(config.show_updates);
|
|
287
|
+
assert!(config.width.is_none());
|
|
288
|
+
}
|
|
289
|
+
}
|