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,62 @@
|
|
|
1
|
+
//! Display module for Grok CLI
|
|
2
|
+
//!
|
|
3
|
+
//! Handles ASCII art, banners, tips, and other visual elements
|
|
4
|
+
|
|
5
|
+
pub mod ascii_art;
|
|
6
|
+
pub mod banner;
|
|
7
|
+
pub mod components;
|
|
8
|
+
pub mod interactive;
|
|
9
|
+
pub mod terminal;
|
|
10
|
+
pub mod tips;
|
|
11
|
+
|
|
12
|
+
pub use ascii_art::print_grok_logo;
|
|
13
|
+
pub use banner::{
|
|
14
|
+
clear_current_line, print_directory_recommendation, print_welcome_banner,
|
|
15
|
+
BannerConfig,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
use colored::*;
|
|
19
|
+
use std::io::{self, Write};
|
|
20
|
+
use terminal_size::{terminal_size, Height, Width};
|
|
21
|
+
|
|
22
|
+
/// Get terminal dimensions
|
|
23
|
+
pub fn get_terminal_size() -> (u16, u16) {
|
|
24
|
+
if let Some((Width(w), Height(h))) = terminal_size() {
|
|
25
|
+
(w, h)
|
|
26
|
+
} else {
|
|
27
|
+
(80, 24) // Default fallback
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Clear the terminal screen
|
|
32
|
+
pub fn clear_screen() {
|
|
33
|
+
print!("\x1B[2J\x1B[1;1H");
|
|
34
|
+
io::stdout().flush().unwrap_or(());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Print a separator line
|
|
38
|
+
pub fn print_separator(width: u16, color: Option<Color>) {
|
|
39
|
+
let line = "─".repeat(width as usize);
|
|
40
|
+
if let Some(c) = color {
|
|
41
|
+
println!("{}", line.color(c));
|
|
42
|
+
} else {
|
|
43
|
+
println!("{}", line.dimmed());
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Print centered text
|
|
48
|
+
pub fn print_centered(text: &str, width: u16, color: Option<Color>) {
|
|
49
|
+
let text_len = text.len();
|
|
50
|
+
let padding = if width as usize > text_len {
|
|
51
|
+
(width as usize - text_len) / 2
|
|
52
|
+
} else {
|
|
53
|
+
0
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
let centered = format!("{}{}", " ".repeat(padding), text);
|
|
57
|
+
if let Some(c) = color {
|
|
58
|
+
println!("{}", centered.color(c));
|
|
59
|
+
} else {
|
|
60
|
+
println!("{}", centered);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use crossterm::{
|
|
3
|
+
execute,
|
|
4
|
+
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
|
5
|
+
};
|
|
6
|
+
use ratatui::backend::CrosstermBackend;
|
|
7
|
+
use ratatui::Terminal;
|
|
8
|
+
use std::io::{self, Stdout};
|
|
9
|
+
|
|
10
|
+
pub type Tui = Terminal<CrosstermBackend<Stdout>>;
|
|
11
|
+
|
|
12
|
+
pub fn init_tui() -> Result<Tui> {
|
|
13
|
+
enable_raw_mode()?;
|
|
14
|
+
let mut stdout = io::stdout();
|
|
15
|
+
execute!(stdout, EnterAlternateScreen)?;
|
|
16
|
+
let backend = CrosstermBackend::new(stdout);
|
|
17
|
+
let terminal = Terminal::new(backend)?;
|
|
18
|
+
Ok(terminal)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pub fn restore_tui() -> Result<()> {
|
|
22
|
+
disable_raw_mode()?;
|
|
23
|
+
execute!(io::stdout(), LeaveAlternateScreen)?;
|
|
24
|
+
Ok(())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// A lighter version of init_tui that doesn't enter alternate screen
|
|
28
|
+
/// Useful for the chat interface where we want to keep history visible
|
|
29
|
+
pub fn init_inline_tui() -> Result<Tui> {
|
|
30
|
+
enable_raw_mode()?;
|
|
31
|
+
let stdout = io::stdout();
|
|
32
|
+
// We don't enter alternate screen here
|
|
33
|
+
let backend = CrosstermBackend::new(stdout);
|
|
34
|
+
let terminal = Terminal::new(backend)?;
|
|
35
|
+
Ok(terminal)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub fn restore_inline_tui() -> Result<()> {
|
|
39
|
+
disable_raw_mode()?;
|
|
40
|
+
// We don't leave alternate screen here
|
|
41
|
+
Ok(())
|
|
42
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
//! Tips display module for Grok CLI
|
|
2
|
+
//!
|
|
3
|
+
//! Handles display of helpful tips and getting started information
|
|
4
|
+
|
|
5
|
+
use colored::*;
|
|
6
|
+
use rand::prelude::IndexedMutRandom;
|
|
7
|
+
use rand::seq::SliceRandom;
|
|
8
|
+
|
|
9
|
+
/// Collection of helpful tips for users
|
|
10
|
+
pub static GETTING_STARTED_TIPS: &[&str] = &[
|
|
11
|
+
"Ask questions, edit files, or run commands.",
|
|
12
|
+
"Be specific for the best results.",
|
|
13
|
+
"/help for more information.",
|
|
14
|
+
"Use 'grok code explain <file>' to understand code.",
|
|
15
|
+
"Use 'grok code review <file>' for code improvements.",
|
|
16
|
+
"Use 'grok chat --interactive' for ongoing conversations.",
|
|
17
|
+
"Set GROK_API_KEY environment variable to avoid typing --api-key.",
|
|
18
|
+
"Use 'grok config show' to view your current configuration.",
|
|
19
|
+
"Create a config.toml file to customize default settings.",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/// Advanced usage tips
|
|
23
|
+
pub static ADVANCED_TIPS: &[&str] = &[
|
|
24
|
+
"Use system prompts with 'grok chat --system \"You are a...\"' for specialized assistance.",
|
|
25
|
+
"Adjust temperature with --temperature for more creative or focused responses.",
|
|
26
|
+
"Use 'grok acp server' to integrate with Zed editor.",
|
|
27
|
+
"Pipe input to Grok CLI: 'cat file.py | grok chat \"Explain this code\"'.",
|
|
28
|
+
"Use 'grok health --all' to diagnose connectivity issues.",
|
|
29
|
+
"Set custom models with --model flag (e.g., grok-3, grok-2-latest).",
|
|
30
|
+
"Use 'grok code generate' to create new code from descriptions.",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/// Productivity tips
|
|
34
|
+
pub static PRODUCTIVITY_TIPS: &[&str] = &[
|
|
35
|
+
"Create shell aliases: alias gc='grok chat' for faster access.",
|
|
36
|
+
"Use tab completion in your shell for Grok CLI commands.",
|
|
37
|
+
"Combine with other CLI tools: 'git diff | grok chat \"Review these changes\"'.",
|
|
38
|
+
"Set up your preferred editor integration for seamless workflow.",
|
|
39
|
+
"Use 'grok config set' to save frequently used settings.",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/// Troubleshooting tips
|
|
43
|
+
pub static TROUBLESHOOTING_TIPS: &[&str] = &[
|
|
44
|
+
"Having connection issues? Check 'grok health --api' for diagnostics.",
|
|
45
|
+
"API key not working? Verify it's set correctly with 'grok config show'.",
|
|
46
|
+
"Slow responses? Try reducing --max-tokens or adjusting --timeout.",
|
|
47
|
+
"Use --verbose flag to see detailed debug information.",
|
|
48
|
+
"Check your internet connection - Starlink users may experience drops.",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/// Configuration for tip display
|
|
52
|
+
#[derive(Debug, Clone)]
|
|
53
|
+
pub struct TipConfig {
|
|
54
|
+
pub show_tips: bool,
|
|
55
|
+
pub randomize: bool,
|
|
56
|
+
pub max_tips: usize,
|
|
57
|
+
pub width: Option<u16>,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl Default for TipConfig {
|
|
61
|
+
fn default() -> Self {
|
|
62
|
+
Self {
|
|
63
|
+
show_tips: true,
|
|
64
|
+
randomize: true,
|
|
65
|
+
max_tips: 3,
|
|
66
|
+
width: None,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Print getting started tips
|
|
72
|
+
pub fn print_getting_started_tips(config: &TipConfig) {
|
|
73
|
+
if !config.show_tips {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
println!("{}", "Tips for getting started:".bright_cyan());
|
|
78
|
+
|
|
79
|
+
let tips = if config.randomize {
|
|
80
|
+
get_random_tips(GETTING_STARTED_TIPS, config.max_tips)
|
|
81
|
+
} else {
|
|
82
|
+
GETTING_STARTED_TIPS
|
|
83
|
+
.iter()
|
|
84
|
+
.take(config.max_tips)
|
|
85
|
+
.cloned()
|
|
86
|
+
.collect()
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
for (i, tip) in tips.iter().enumerate() {
|
|
90
|
+
println!("{}. {}", (i + 1).to_string().bright_white(), tip);
|
|
91
|
+
}
|
|
92
|
+
println!();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Print advanced tips for experienced users
|
|
96
|
+
pub fn print_advanced_tips(config: &TipConfig) {
|
|
97
|
+
if !config.show_tips {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
println!("{}", "Advanced tips:".bright_magenta());
|
|
102
|
+
|
|
103
|
+
let tips = if config.randomize {
|
|
104
|
+
get_random_tips(ADVANCED_TIPS, config.max_tips)
|
|
105
|
+
} else {
|
|
106
|
+
ADVANCED_TIPS
|
|
107
|
+
.iter()
|
|
108
|
+
.take(config.max_tips)
|
|
109
|
+
.cloned()
|
|
110
|
+
.collect()
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
for tip in tips {
|
|
114
|
+
println!("• {}", tip);
|
|
115
|
+
}
|
|
116
|
+
println!();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Print productivity tips
|
|
120
|
+
pub fn print_productivity_tips(config: &TipConfig) {
|
|
121
|
+
if !config.show_tips {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
println!("{}", "Productivity tips:".bright_green());
|
|
126
|
+
|
|
127
|
+
let tips = if config.randomize {
|
|
128
|
+
get_random_tips(PRODUCTIVITY_TIPS, config.max_tips)
|
|
129
|
+
} else {
|
|
130
|
+
PRODUCTIVITY_TIPS
|
|
131
|
+
.iter()
|
|
132
|
+
.take(config.max_tips)
|
|
133
|
+
.cloned()
|
|
134
|
+
.collect()
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
for tip in tips {
|
|
138
|
+
println!("💡 {}", tip);
|
|
139
|
+
}
|
|
140
|
+
println!();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Print troubleshooting tips
|
|
144
|
+
pub fn print_troubleshooting_tips(config: &TipConfig) {
|
|
145
|
+
if !config.show_tips {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
println!("{}", "Troubleshooting tips:".bright_yellow());
|
|
150
|
+
|
|
151
|
+
let tips = if config.randomize {
|
|
152
|
+
get_random_tips(TROUBLESHOOTING_TIPS, config.max_tips)
|
|
153
|
+
} else {
|
|
154
|
+
TROUBLESHOOTING_TIPS
|
|
155
|
+
.iter()
|
|
156
|
+
.take(config.max_tips)
|
|
157
|
+
.cloned()
|
|
158
|
+
.collect()
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
for tip in tips {
|
|
162
|
+
println!("🔧 {}", tip);
|
|
163
|
+
}
|
|
164
|
+
println!();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// Get random tips from a collection
|
|
168
|
+
fn get_random_tips<'a>(tips: &'a [&'a str], count: usize) -> Vec<&'a str> {
|
|
169
|
+
let mut rng = rand::rng();
|
|
170
|
+
let mut selected_tips: Vec<&str> = tips.to_vec();
|
|
171
|
+
selected_tips.shuffle(&mut rng);
|
|
172
|
+
selected_tips.into_iter().take(count).collect()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Print a tip of the day
|
|
176
|
+
pub fn print_tip_of_the_day() {
|
|
177
|
+
let mut all_tips: Vec<&str> = GETTING_STARTED_TIPS
|
|
178
|
+
.iter()
|
|
179
|
+
.chain(ADVANCED_TIPS.iter())
|
|
180
|
+
.chain(PRODUCTIVITY_TIPS.iter())
|
|
181
|
+
.chain(TROUBLESHOOTING_TIPS.iter())
|
|
182
|
+
.cloned()
|
|
183
|
+
.collect();
|
|
184
|
+
|
|
185
|
+
if let Some(tip) = all_tips.choose_mut(&mut rand::rng()) {
|
|
186
|
+
println!("{} {}", "💡 Tip of the day:".bright_yellow(), tip);
|
|
187
|
+
println!();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Print contextual tips based on current operation
|
|
192
|
+
pub fn print_contextual_tips(context: &str, config: &TipConfig) {
|
|
193
|
+
if !config.show_tips {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
match context {
|
|
198
|
+
"first-run" => print_getting_started_tips(config),
|
|
199
|
+
"error" => print_troubleshooting_tips(config),
|
|
200
|
+
"config" => print_config_tips(config),
|
|
201
|
+
"code" => print_code_tips(config),
|
|
202
|
+
"chat" => print_chat_tips(config),
|
|
203
|
+
_ => print_getting_started_tips(config),
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Print configuration-specific tips
|
|
208
|
+
fn print_config_tips(config: &TipConfig) {
|
|
209
|
+
println!("{}", "Configuration tips:".bright_blue());
|
|
210
|
+
let tips = vec![
|
|
211
|
+
"Use 'grok config init' to create a default configuration file",
|
|
212
|
+
"Set your preferred model with 'grok config set default_model grok-3'",
|
|
213
|
+
"Configure API settings with 'grok config set api_key <your-key>'",
|
|
214
|
+
"Use 'grok config validate' to check your configuration",
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
let display_tips = if config.randomize {
|
|
218
|
+
get_random_tips(&tips, config.max_tips)
|
|
219
|
+
} else {
|
|
220
|
+
tips.iter().take(config.max_tips).copied().collect()
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
for tip in display_tips {
|
|
224
|
+
println!("⚙️ {}", tip);
|
|
225
|
+
}
|
|
226
|
+
println!();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// Print code-specific tips
|
|
230
|
+
fn print_code_tips(config: &TipConfig) {
|
|
231
|
+
println!("{}", "Code assistance tips:".bright_purple());
|
|
232
|
+
let tips = vec![
|
|
233
|
+
"Use 'grok code explain' to understand complex code",
|
|
234
|
+
"Get code reviews with 'grok code review --focus security'",
|
|
235
|
+
"Generate code with 'grok code generate --language rust'",
|
|
236
|
+
"Fix issues with 'grok code fix <file> \"description of problem\"'",
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
let display_tips = if config.randomize {
|
|
240
|
+
get_random_tips(&tips, config.max_tips)
|
|
241
|
+
} else {
|
|
242
|
+
tips.iter().take(config.max_tips).copied().collect()
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
for tip in display_tips {
|
|
246
|
+
println!("💻 {}", tip);
|
|
247
|
+
}
|
|
248
|
+
println!();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// Print chat-specific tips
|
|
252
|
+
fn print_chat_tips(config: &TipConfig) {
|
|
253
|
+
println!("{}", "Chat tips:".bright_cyan());
|
|
254
|
+
let tips = vec![
|
|
255
|
+
"Use --interactive for ongoing conversations",
|
|
256
|
+
"Set system prompts with --system for specialized help",
|
|
257
|
+
"Adjust creativity with --temperature (0.1 = focused, 1.5 = creative)",
|
|
258
|
+
"Use --max-tokens to control response length",
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
let display_tips = if config.randomize {
|
|
262
|
+
get_random_tips(&tips, config.max_tips)
|
|
263
|
+
} else {
|
|
264
|
+
tips.iter().take(config.max_tips).copied().collect()
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
for tip in display_tips {
|
|
268
|
+
println!("💬 {}", tip);
|
|
269
|
+
}
|
|
270
|
+
println!();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// Print a formatted help section
|
|
274
|
+
pub fn print_help_section(title: &str, items: &[(&str, &str)]) {
|
|
275
|
+
println!("{}", title.bright_cyan().bold());
|
|
276
|
+
println!();
|
|
277
|
+
|
|
278
|
+
for (command, description) in items {
|
|
279
|
+
println!(" {:<25} {}", command.bright_white(), description);
|
|
280
|
+
}
|
|
281
|
+
println!();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#[cfg(test)]
|
|
285
|
+
mod tests {
|
|
286
|
+
use super::*;
|
|
287
|
+
|
|
288
|
+
#[test]
|
|
289
|
+
fn test_get_random_tips() {
|
|
290
|
+
let tips = ["tip1", "tip2", "tip3", "tip4", "tip5"];
|
|
291
|
+
let random_tips = get_random_tips(&tips, 3);
|
|
292
|
+
assert_eq!(random_tips.len(), 3);
|
|
293
|
+
|
|
294
|
+
// All returned tips should be from the original set
|
|
295
|
+
for tip in random_tips {
|
|
296
|
+
assert!(tips.contains(&tip));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#[test]
|
|
301
|
+
fn test_tip_config_default() {
|
|
302
|
+
let config = TipConfig::default();
|
|
303
|
+
assert!(config.show_tips);
|
|
304
|
+
assert!(config.randomize);
|
|
305
|
+
assert_eq!(config.max_tips, 3);
|
|
306
|
+
assert!(config.width.is_none());
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
#[test]
|
|
310
|
+
fn test_tips_not_empty() {
|
|
311
|
+
assert!(!GETTING_STARTED_TIPS.is_empty());
|
|
312
|
+
assert!(!ADVANCED_TIPS.is_empty());
|
|
313
|
+
assert!(!PRODUCTIVITY_TIPS.is_empty());
|
|
314
|
+
assert!(!TROUBLESHOOTING_TIPS.is_empty());
|
|
315
|
+
}
|
|
316
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
//! Grok Client Extensions
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides compatibility extensions for the grok_api::GrokClient
|
|
4
|
+
//! to maintain API compatibility with the previous local implementation.
|
|
5
|
+
|
|
6
|
+
use anyhow::Result;
|
|
7
|
+
use grok_api::{ChatMessage, ChatResponse as GrokApiChatResponse, Message};
|
|
8
|
+
use serde_json::Value;
|
|
9
|
+
|
|
10
|
+
use crate::config::RateLimitConfig;
|
|
11
|
+
|
|
12
|
+
/// Extended Grok client that wraps grok_api::GrokClient with additional methods
|
|
13
|
+
#[derive(Clone, Debug)]
|
|
14
|
+
pub struct GrokClient {
|
|
15
|
+
inner: grok_api::GrokClient,
|
|
16
|
+
rate_limit_config: Option<RateLimitConfig>,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl GrokClient {
|
|
20
|
+
/// Create a new GrokClient with default settings
|
|
21
|
+
pub fn new(api_key: &str) -> Result<Self> {
|
|
22
|
+
let inner = grok_api::GrokClient::new(api_key)?;
|
|
23
|
+
Ok(Self {
|
|
24
|
+
inner,
|
|
25
|
+
rate_limit_config: None,
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Create a new GrokClient with custom timeout and retry settings
|
|
30
|
+
pub fn with_settings(api_key: &str, timeout_secs: u64, max_retries: u32) -> Result<Self> {
|
|
31
|
+
let inner = grok_api::GrokClient::builder()
|
|
32
|
+
.api_key(api_key)
|
|
33
|
+
.timeout_secs(timeout_secs)
|
|
34
|
+
.max_retries(max_retries)
|
|
35
|
+
.build()?;
|
|
36
|
+
|
|
37
|
+
Ok(Self {
|
|
38
|
+
inner,
|
|
39
|
+
rate_limit_config: None,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Set rate limit configuration (for compatibility - currently a no-op)
|
|
44
|
+
pub fn with_rate_limits(mut self, config: RateLimitConfig) -> Self {
|
|
45
|
+
self.rate_limit_config = Some(config);
|
|
46
|
+
self
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Send a single chat completion request to Grok
|
|
50
|
+
pub async fn chat_completion(
|
|
51
|
+
&self,
|
|
52
|
+
message: &str,
|
|
53
|
+
system_prompt: Option<&str>,
|
|
54
|
+
temperature: f32,
|
|
55
|
+
max_tokens: u32,
|
|
56
|
+
model: &str,
|
|
57
|
+
) -> Result<String> {
|
|
58
|
+
let mut messages = Vec::new();
|
|
59
|
+
|
|
60
|
+
// Add system message if provided
|
|
61
|
+
if let Some(system) = system_prompt {
|
|
62
|
+
messages.push(ChatMessage::system(system));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Add user message
|
|
66
|
+
messages.push(ChatMessage::user(message));
|
|
67
|
+
|
|
68
|
+
let response = self
|
|
69
|
+
.inner
|
|
70
|
+
.chat_with_history(&messages)
|
|
71
|
+
.temperature(temperature)
|
|
72
|
+
.max_tokens(max_tokens)
|
|
73
|
+
.model(model)
|
|
74
|
+
.send()
|
|
75
|
+
.await?;
|
|
76
|
+
|
|
77
|
+
Ok(response.content().unwrap_or("").to_string())
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Send chat completion with conversation history and optional tools
|
|
81
|
+
pub async fn chat_completion_with_history(
|
|
82
|
+
&self,
|
|
83
|
+
messages: &[Value],
|
|
84
|
+
temperature: f32,
|
|
85
|
+
max_tokens: u32,
|
|
86
|
+
model: &str,
|
|
87
|
+
tools: Option<Vec<Value>>,
|
|
88
|
+
) -> Result<Message> {
|
|
89
|
+
// Convert JSON messages to ChatMessage format
|
|
90
|
+
let chat_messages: Vec<ChatMessage> = messages
|
|
91
|
+
.iter()
|
|
92
|
+
.filter_map(|msg| {
|
|
93
|
+
let role = msg.get("role")?.as_str()?;
|
|
94
|
+
let content = msg.get("content")?.as_str()?;
|
|
95
|
+
|
|
96
|
+
Some(match role {
|
|
97
|
+
"system" => ChatMessage::system(content),
|
|
98
|
+
"user" => ChatMessage::user(content),
|
|
99
|
+
"assistant" => ChatMessage::assistant(content),
|
|
100
|
+
_ => return None,
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
.collect();
|
|
104
|
+
|
|
105
|
+
let mut request = self
|
|
106
|
+
.inner
|
|
107
|
+
.chat_with_history(&chat_messages)
|
|
108
|
+
.temperature(temperature)
|
|
109
|
+
.max_tokens(max_tokens)
|
|
110
|
+
.model(model);
|
|
111
|
+
|
|
112
|
+
// Add tools if provided
|
|
113
|
+
if let Some(tool_defs) = tools {
|
|
114
|
+
// Convert tools to the format expected by grok_api
|
|
115
|
+
// Note: This is a simplified conversion - you may need to adjust
|
|
116
|
+
// based on the exact tool format expected by grok_api
|
|
117
|
+
request = request.tools(tool_defs);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let response = request.send().await?;
|
|
121
|
+
|
|
122
|
+
// Convert the response to the Message format
|
|
123
|
+
convert_response_to_message(response)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Test the connection to the Grok API
|
|
127
|
+
pub async fn test_connection(&self) -> Result<()> {
|
|
128
|
+
self.inner.test_connection().await.map_err(|e| e.into())
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// List available models
|
|
132
|
+
pub async fn list_models(&self) -> Result<Vec<String>> {
|
|
133
|
+
self.inner.list_models().await.map_err(|e| e.into())
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Get the underlying grok_api client
|
|
137
|
+
pub fn inner(&self) -> &grok_api::GrokClient {
|
|
138
|
+
&self.inner
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Convert ChatResponse to Message format for compatibility
|
|
143
|
+
fn convert_response_to_message(response: GrokApiChatResponse) -> Result<Message> {
|
|
144
|
+
// Get the message from the first choice
|
|
145
|
+
if let Some(message) = response.message() {
|
|
146
|
+
Ok(message.clone())
|
|
147
|
+
} else {
|
|
148
|
+
Ok(Message {
|
|
149
|
+
role: "assistant".to_string(),
|
|
150
|
+
content: response.content().map(|s| s.to_string()),
|
|
151
|
+
tool_calls: None,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#[cfg(test)]
|
|
157
|
+
mod tests {
|
|
158
|
+
use super::*;
|
|
159
|
+
|
|
160
|
+
#[tokio::test]
|
|
161
|
+
async fn test_grok_client_creation() {
|
|
162
|
+
let client = GrokClient::with_settings("test-key", 30, 3);
|
|
163
|
+
assert!(client.is_ok());
|
|
164
|
+
|
|
165
|
+
let empty_key_client = GrokClient::with_settings("", 30, 3);
|
|
166
|
+
assert!(empty_key_client.is_err());
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn test_with_rate_limits() {
|
|
171
|
+
let client = GrokClient::new("test-key").unwrap();
|
|
172
|
+
let rate_config = RateLimitConfig::default();
|
|
173
|
+
let client_with_limits = client.with_rate_limits(rate_config);
|
|
174
|
+
|
|
175
|
+
assert!(client_with_limits.rate_limit_config.is_some());
|
|
176
|
+
}
|
|
177
|
+
}
|