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,485 @@
|
|
|
1
|
+
//! Chat command handler for grok-cli
|
|
2
|
+
//!
|
|
3
|
+
//! Handles interactive and non-interactive chat sessions with Grok AI
|
|
4
|
+
|
|
5
|
+
use anyhow::{Result, anyhow};
|
|
6
|
+
use colored::*;
|
|
7
|
+
use serde_json::{Value, json};
|
|
8
|
+
use std::env;
|
|
9
|
+
use std::fs;
|
|
10
|
+
use std::io::{self, Write};
|
|
11
|
+
use std::process::Command;
|
|
12
|
+
|
|
13
|
+
use crate::acp::security::SecurityPolicy;
|
|
14
|
+
use crate::acp::tools;
|
|
15
|
+
use crate::{ToolCall, GrokClient};
|
|
16
|
+
use crate::cli::{create_spinner, format_grok_response, print_error, print_info, print_success};
|
|
17
|
+
use crate::config::RateLimitConfig;
|
|
18
|
+
|
|
19
|
+
pub struct ChatOptions<'a> {
|
|
20
|
+
pub message: Vec<String>,
|
|
21
|
+
pub interactive: bool,
|
|
22
|
+
pub system: Option<String>,
|
|
23
|
+
pub temperature: f32,
|
|
24
|
+
pub max_tokens: u32,
|
|
25
|
+
pub api_key: &'a str,
|
|
26
|
+
pub model: &'a str,
|
|
27
|
+
pub timeout_secs: u64,
|
|
28
|
+
pub max_retries: u32,
|
|
29
|
+
pub rate_limit_config: RateLimitConfig,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub async fn handle_chat(options: ChatOptions<'_>) -> Result<()> {
|
|
33
|
+
let client =
|
|
34
|
+
GrokClient::with_settings(options.api_key, options.timeout_secs, options.max_retries)?
|
|
35
|
+
.with_rate_limits(options.rate_limit_config);
|
|
36
|
+
|
|
37
|
+
if options.interactive {
|
|
38
|
+
handle_interactive_chat(
|
|
39
|
+
client,
|
|
40
|
+
options.system,
|
|
41
|
+
options.temperature,
|
|
42
|
+
options.max_tokens,
|
|
43
|
+
options.model,
|
|
44
|
+
)
|
|
45
|
+
.await
|
|
46
|
+
} else {
|
|
47
|
+
let combined_message = options.message.join(" ");
|
|
48
|
+
handle_single_chat(
|
|
49
|
+
client,
|
|
50
|
+
&combined_message,
|
|
51
|
+
options.system,
|
|
52
|
+
options.temperature,
|
|
53
|
+
options.max_tokens,
|
|
54
|
+
options.model,
|
|
55
|
+
)
|
|
56
|
+
.await
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async fn handle_single_chat(
|
|
61
|
+
client: GrokClient,
|
|
62
|
+
message: &str,
|
|
63
|
+
system: Option<String>,
|
|
64
|
+
temperature: f32,
|
|
65
|
+
max_tokens: u32,
|
|
66
|
+
model: &str,
|
|
67
|
+
) -> Result<()> {
|
|
68
|
+
print_info(&format!("Sending message to Grok (model: {})...", model));
|
|
69
|
+
|
|
70
|
+
let spinner = create_spinner("Thinking...");
|
|
71
|
+
|
|
72
|
+
// Prepare messages
|
|
73
|
+
let mut messages = Vec::new();
|
|
74
|
+
if let Some(sys) = system {
|
|
75
|
+
messages.push(json!({
|
|
76
|
+
"role": "system",
|
|
77
|
+
"content": sys
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
messages.push(json!({
|
|
81
|
+
"role": "user",
|
|
82
|
+
"content": message
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
// Add tool definitions
|
|
86
|
+
let tools = tools::get_tool_definitions();
|
|
87
|
+
|
|
88
|
+
let result = client
|
|
89
|
+
.chat_completion_with_history(&messages, temperature, max_tokens, model, Some(tools))
|
|
90
|
+
.await;
|
|
91
|
+
|
|
92
|
+
spinner.finish_and_clear();
|
|
93
|
+
|
|
94
|
+
match result {
|
|
95
|
+
Ok(response) => {
|
|
96
|
+
// Handle tool calls if present
|
|
97
|
+
if let Some(tool_calls) = &response.tool_calls {
|
|
98
|
+
if !tool_calls.is_empty() {
|
|
99
|
+
print_info("Executing requested operations...");
|
|
100
|
+
let mut security = SecurityPolicy::new();
|
|
101
|
+
security.add_trusted_directory(&env::current_dir()?);
|
|
102
|
+
|
|
103
|
+
for tool_call in tool_calls {
|
|
104
|
+
execute_tool_call(tool_call, &security)?;
|
|
105
|
+
}
|
|
106
|
+
print_success("All operations completed!");
|
|
107
|
+
return Ok(());
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Regular text response
|
|
112
|
+
print_success("Response received!");
|
|
113
|
+
println!();
|
|
114
|
+
if let Some(content) = response.content {
|
|
115
|
+
println!("{}", format_grok_response(&content, true));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
Err(e) => {
|
|
119
|
+
print_error(&format!("Failed to get response: {}", e));
|
|
120
|
+
return Err(e);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Ok(())
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn execute_tool_call(tool_call: &ToolCall, security: &SecurityPolicy) -> Result<()> {
|
|
128
|
+
let name = &tool_call.function.name;
|
|
129
|
+
let args: Value = serde_json::from_str(&tool_call.function.arguments)?;
|
|
130
|
+
|
|
131
|
+
match name.as_str() {
|
|
132
|
+
"write_file" => {
|
|
133
|
+
let path = args["path"]
|
|
134
|
+
.as_str()
|
|
135
|
+
.ok_or_else(|| anyhow!("Missing path"))?;
|
|
136
|
+
let content = args["content"]
|
|
137
|
+
.as_str()
|
|
138
|
+
.ok_or_else(|| anyhow!("Missing content"))?;
|
|
139
|
+
let result = tools::write_file(path, content, security)?;
|
|
140
|
+
println!(" {} {}", "✓".green(), result);
|
|
141
|
+
}
|
|
142
|
+
"read_file" => {
|
|
143
|
+
let path = args["path"]
|
|
144
|
+
.as_str()
|
|
145
|
+
.ok_or_else(|| anyhow!("Missing path"))?;
|
|
146
|
+
let content = tools::read_file(path, security)?;
|
|
147
|
+
println!(
|
|
148
|
+
" {} Read {} bytes from {}",
|
|
149
|
+
"✓".green(),
|
|
150
|
+
content.len(),
|
|
151
|
+
path
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
"replace" => {
|
|
155
|
+
let path = args["path"]
|
|
156
|
+
.as_str()
|
|
157
|
+
.ok_or_else(|| anyhow!("Missing path"))?;
|
|
158
|
+
let old = args["old_string"]
|
|
159
|
+
.as_str()
|
|
160
|
+
.ok_or_else(|| anyhow!("Missing old_string"))?;
|
|
161
|
+
let new = args["new_string"]
|
|
162
|
+
.as_str()
|
|
163
|
+
.ok_or_else(|| anyhow!("Missing new_string"))?;
|
|
164
|
+
let expected = args
|
|
165
|
+
.get("expected_replacements")
|
|
166
|
+
.and_then(|v| v.as_u64())
|
|
167
|
+
.map(|v| v as u32);
|
|
168
|
+
let result = tools::replace(path, old, new, expected, security)?;
|
|
169
|
+
println!(" {} {}", "✓".green(), result);
|
|
170
|
+
}
|
|
171
|
+
"list_directory" => {
|
|
172
|
+
let path = args["path"]
|
|
173
|
+
.as_str()
|
|
174
|
+
.ok_or_else(|| anyhow!("Missing path"))?;
|
|
175
|
+
let result = tools::list_directory(path, security)?;
|
|
176
|
+
println!(" {} Directory contents of {}:", "✓".green(), path);
|
|
177
|
+
for line in result.lines() {
|
|
178
|
+
println!(" {}", line);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
"glob_search" => {
|
|
182
|
+
let pattern = args["pattern"]
|
|
183
|
+
.as_str()
|
|
184
|
+
.ok_or_else(|| anyhow!("Missing pattern"))?;
|
|
185
|
+
let result = tools::glob_search(pattern, security)?;
|
|
186
|
+
println!(" {} Files matching '{}':", "✓".green(), pattern);
|
|
187
|
+
for line in result.lines() {
|
|
188
|
+
println!(" {}", line);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
"save_memory" => {
|
|
192
|
+
let fact = args["fact"]
|
|
193
|
+
.as_str()
|
|
194
|
+
.ok_or_else(|| anyhow!("Missing fact"))?;
|
|
195
|
+
let result = tools::save_memory(fact)?;
|
|
196
|
+
println!(" {} {}", "✓".green(), result);
|
|
197
|
+
}
|
|
198
|
+
"run_shell_command" => {
|
|
199
|
+
let command = args["command"]
|
|
200
|
+
.as_str()
|
|
201
|
+
.ok_or_else(|| anyhow!("Missing command"))?;
|
|
202
|
+
println!(" {} Executing: {}", "⚙".cyan(), command);
|
|
203
|
+
let result = tools::run_shell_command(command, security)?;
|
|
204
|
+
println!(" {} Command output:", "✓".green());
|
|
205
|
+
for line in result.lines() {
|
|
206
|
+
println!(" {}", line);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
_ => {
|
|
210
|
+
println!(" {} Unsupported tool: {}", "⚠".yellow(), name);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
Ok(())
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async fn handle_interactive_chat(
|
|
218
|
+
client: GrokClient,
|
|
219
|
+
system: Option<String>,
|
|
220
|
+
temperature: f32,
|
|
221
|
+
max_tokens: u32,
|
|
222
|
+
model: &str,
|
|
223
|
+
) -> Result<()> {
|
|
224
|
+
println!("{}", "🤖 Interactive Grok Chat Session".cyan().bold());
|
|
225
|
+
println!("{}", format!("Model: {}", model).dimmed());
|
|
226
|
+
|
|
227
|
+
if let Some(ref sys) = system {
|
|
228
|
+
println!("{}", format!("System: {}", sys).dimmed());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
println!(
|
|
232
|
+
"{}",
|
|
233
|
+
"Type 'exit', 'quit', or press Ctrl+C to end the session".dimmed()
|
|
234
|
+
);
|
|
235
|
+
println!("{}", "Type 'help' for available commands".dimmed());
|
|
236
|
+
println!();
|
|
237
|
+
|
|
238
|
+
let mut conversation_history = Vec::new();
|
|
239
|
+
|
|
240
|
+
// Add system message if provided
|
|
241
|
+
if let Some(sys) = system {
|
|
242
|
+
conversation_history.push(json!({
|
|
243
|
+
"role": "system",
|
|
244
|
+
"content": sys
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Set up security policy with current directory as trusted
|
|
249
|
+
let mut security = SecurityPolicy::new();
|
|
250
|
+
security.add_trusted_directory(&env::current_dir()?);
|
|
251
|
+
|
|
252
|
+
// Get tool definitions for function calling
|
|
253
|
+
let tools = tools::get_tool_definitions();
|
|
254
|
+
|
|
255
|
+
loop {
|
|
256
|
+
// Prompt for input
|
|
257
|
+
let cwd = env::current_dir().unwrap_or_default();
|
|
258
|
+
print!(
|
|
259
|
+
"{} {} ",
|
|
260
|
+
format!("[{}]", cwd.display()).dimmed(),
|
|
261
|
+
"You:".green().bold()
|
|
262
|
+
);
|
|
263
|
+
io::stdout().flush()?;
|
|
264
|
+
|
|
265
|
+
let mut input = String::new();
|
|
266
|
+
match io::stdin().read_line(&mut input) {
|
|
267
|
+
Ok(0) => {
|
|
268
|
+
// EOF reached (Ctrl+D)
|
|
269
|
+
println!("\n{}", "Goodbye!".cyan());
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
Ok(_) => {
|
|
273
|
+
let input = input.trim();
|
|
274
|
+
let lower_input = input.to_lowercase();
|
|
275
|
+
|
|
276
|
+
// Handle special commands
|
|
277
|
+
if lower_input == "exit" || lower_input == "quit" || lower_input == "q" {
|
|
278
|
+
println!("{}", "Goodbye!".cyan());
|
|
279
|
+
break;
|
|
280
|
+
} else if lower_input == "help" || lower_input == "h" {
|
|
281
|
+
print_help();
|
|
282
|
+
continue;
|
|
283
|
+
} else if lower_input == "clear" || lower_input == "cls" {
|
|
284
|
+
// Clear conversation history but keep system message
|
|
285
|
+
if conversation_history
|
|
286
|
+
.first()
|
|
287
|
+
.and_then(|msg| msg.get("role"))
|
|
288
|
+
.and_then(|role| role.as_str())
|
|
289
|
+
== Some("system")
|
|
290
|
+
{
|
|
291
|
+
let system_msg = conversation_history[0].clone();
|
|
292
|
+
conversation_history.clear();
|
|
293
|
+
conversation_history.push(system_msg);
|
|
294
|
+
} else {
|
|
295
|
+
conversation_history.clear();
|
|
296
|
+
}
|
|
297
|
+
print_success("Conversation history cleared!");
|
|
298
|
+
continue;
|
|
299
|
+
} else if lower_input == "history" {
|
|
300
|
+
print_conversation_history(&conversation_history);
|
|
301
|
+
continue;
|
|
302
|
+
} else if lower_input == "ls" || lower_input == "dir" {
|
|
303
|
+
match fs::read_dir(".") {
|
|
304
|
+
Ok(entries) => {
|
|
305
|
+
println!("{}", "Current Directory Entries:".cyan().bold());
|
|
306
|
+
for entry in entries.flatten() {
|
|
307
|
+
let path = entry.path();
|
|
308
|
+
let name = path.file_name().unwrap_or_default().to_string_lossy();
|
|
309
|
+
if path.is_dir() {
|
|
310
|
+
println!(" {} {}", name.blue().bold(), "(DIR)".dimmed());
|
|
311
|
+
} else {
|
|
312
|
+
println!(" {}", name);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
Err(e) => print_error(&format!("Failed to list directory: {}", e)),
|
|
317
|
+
}
|
|
318
|
+
continue;
|
|
319
|
+
} else if lower_input.starts_with("cd ") {
|
|
320
|
+
let path = input[3..].trim();
|
|
321
|
+
if let Err(e) = env::set_current_dir(path) {
|
|
322
|
+
print_error(&format!("Failed to change directory to '{}': {}", path, e));
|
|
323
|
+
} else {
|
|
324
|
+
print_success(&format!(
|
|
325
|
+
"Changed directory to {}",
|
|
326
|
+
env::current_dir().unwrap_or_default().display()
|
|
327
|
+
));
|
|
328
|
+
}
|
|
329
|
+
continue;
|
|
330
|
+
} else if let Some(stripped) = input.strip_prefix('!') {
|
|
331
|
+
let command_line = stripped.trim();
|
|
332
|
+
if !command_line.is_empty() {
|
|
333
|
+
let result = if cfg!(target_os = "windows") {
|
|
334
|
+
Command::new("cmd").arg("/C").arg(command_line).status()
|
|
335
|
+
} else {
|
|
336
|
+
Command::new("sh").arg("-c").arg(command_line).status()
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
match result {
|
|
340
|
+
Ok(status) => {
|
|
341
|
+
if !status.success() {
|
|
342
|
+
print_error(&format!("Command exited with status: {}", status));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
Err(e) => print_error(&format!("Failed to execute command: {}", e)),
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
continue;
|
|
349
|
+
} else if input.is_empty() {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Add user message to history
|
|
354
|
+
conversation_history.push(json!({
|
|
355
|
+
"role": "user",
|
|
356
|
+
"content": input
|
|
357
|
+
}));
|
|
358
|
+
|
|
359
|
+
// Show spinner while waiting for response
|
|
360
|
+
let spinner = create_spinner("Grok is thinking...");
|
|
361
|
+
|
|
362
|
+
// Get response with timeout and retries (including tool definitions)
|
|
363
|
+
let response_msg = client
|
|
364
|
+
.chat_completion_with_history(
|
|
365
|
+
&conversation_history,
|
|
366
|
+
temperature,
|
|
367
|
+
max_tokens,
|
|
368
|
+
model,
|
|
369
|
+
Some(tools.clone()),
|
|
370
|
+
)
|
|
371
|
+
.await?;
|
|
372
|
+
|
|
373
|
+
spinner.finish_and_clear();
|
|
374
|
+
|
|
375
|
+
// Handle tool calls if present
|
|
376
|
+
if let Some(tool_calls) = &response_msg.tool_calls {
|
|
377
|
+
if !tool_calls.is_empty() {
|
|
378
|
+
println!("{}", "Grok is executing operations...".blue().dimmed());
|
|
379
|
+
|
|
380
|
+
for tool_call in tool_calls {
|
|
381
|
+
if let Err(e) = execute_tool_call(tool_call, &security) {
|
|
382
|
+
print_error(&format!("Tool execution failed: {}", e));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Add assistant's tool call response to history
|
|
387
|
+
conversation_history.push(json!({
|
|
388
|
+
"role": "assistant",
|
|
389
|
+
"content": response_msg.content.clone(),
|
|
390
|
+
"tool_calls": tool_calls
|
|
391
|
+
}));
|
|
392
|
+
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let response = response_msg.content.unwrap_or_default();
|
|
398
|
+
|
|
399
|
+
// Add assistant response to history
|
|
400
|
+
conversation_history.push(json!({
|
|
401
|
+
"role": "assistant",
|
|
402
|
+
"content": response.clone()
|
|
403
|
+
}));
|
|
404
|
+
|
|
405
|
+
// Display response
|
|
406
|
+
println!("{} {}", "Grok:".blue().bold(), response);
|
|
407
|
+
}
|
|
408
|
+
Err(e) => {
|
|
409
|
+
print_error(&format!("Failed to read input: {}", e));
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
Ok(())
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
fn print_help() {
|
|
419
|
+
println!();
|
|
420
|
+
println!("{}", "Available commands:".cyan().bold());
|
|
421
|
+
println!(" {} - Exit the chat session", "exit, quit, q".yellow());
|
|
422
|
+
println!(" {} - Show this help message", "help, h".yellow());
|
|
423
|
+
println!(" {} - Clear conversation history", "clear, cls".yellow());
|
|
424
|
+
println!(" {} - Show conversation history", "history".yellow());
|
|
425
|
+
println!(" {} - List files in current directory", "ls, dir".yellow());
|
|
426
|
+
println!(" {} - Change current directory", "cd <path>".yellow());
|
|
427
|
+
println!(" {} - Execute shell command", "!<command>".yellow());
|
|
428
|
+
println!();
|
|
429
|
+
println!("{}", "Tool Support:".cyan().bold());
|
|
430
|
+
println!(" Grok can now automatically create files and directories!");
|
|
431
|
+
println!(" Just ask naturally, e.g.:");
|
|
432
|
+
println!(" {} 'Create a new Rust project structure'", "•".green());
|
|
433
|
+
println!(
|
|
434
|
+
" {} 'Write a hello world program to main.rs'",
|
|
435
|
+
"•".green()
|
|
436
|
+
);
|
|
437
|
+
println!();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
fn print_conversation_history(history: &[Value]) {
|
|
441
|
+
if history.is_empty() {
|
|
442
|
+
print_info("No conversation history yet.");
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
println!();
|
|
447
|
+
println!("{}", "Conversation History:".cyan().bold());
|
|
448
|
+
println!("{}", "─".repeat(50));
|
|
449
|
+
|
|
450
|
+
for (i, message) in history.iter().enumerate() {
|
|
451
|
+
if let (Some(role), Some(content)) = (
|
|
452
|
+
message.get("role").and_then(|r| r.as_str()),
|
|
453
|
+
message.get("content").and_then(|c| c.as_str()),
|
|
454
|
+
) {
|
|
455
|
+
let role_display = match role {
|
|
456
|
+
"system" => "System".magenta().bold(),
|
|
457
|
+
"user" => "You".green().bold(),
|
|
458
|
+
"assistant" => "Grok".blue().bold(),
|
|
459
|
+
_ => role.white().bold(),
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
println!("{}: {}", role_display, content);
|
|
463
|
+
|
|
464
|
+
if i < history.len() - 1 {
|
|
465
|
+
println!();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
println!("{}", "─".repeat(50));
|
|
471
|
+
println!();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
#[cfg(test)]
|
|
475
|
+
mod tests {
|
|
476
|
+
use super::*;
|
|
477
|
+
use tokio;
|
|
478
|
+
|
|
479
|
+
#[tokio::test]
|
|
480
|
+
async fn test_chat_command_structure() {
|
|
481
|
+
// Test that the chat command structure is properly defined
|
|
482
|
+
// This is a placeholder test - in a real implementation you'd mock the API
|
|
483
|
+
// The test passes as long as the module compiles correctly
|
|
484
|
+
}
|
|
485
|
+
}
|