Skip to content

Commit f93ff7a

Browse files
authored
feat: add command summaries to execute_bash tool for better UX (#1415)
1 parent a7d09a7 commit f93ff7a

File tree

2 files changed

+79
-3
lines changed

2 files changed

+79
-3
lines changed

crates/q_chat/src/tools/execute_bash.rs

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ const READONLY_COMMANDS: &[&str] = &["ls", "cat", "echo", "pwd", "which", "head"
3333
#[derive(Debug, Clone, Deserialize)]
3434
pub struct ExecuteBash {
3535
pub command: String,
36+
/// Optional summary explaining what the command does
37+
#[serde(default)]
38+
pub summary: Option<String>,
3639
}
3740

3841
impl ExecuteBash {
@@ -114,13 +117,29 @@ impl ExecuteBash {
114117
queue!(updates, style::Print("\n"),)?;
115118
}
116119

117-
Ok(queue!(
120+
queue!(
118121
updates,
119122
style::SetForegroundColor(Color::Green),
120123
style::Print(&self.command),
121-
style::Print("\n\n"),
124+
style::Print("\n"),
122125
style::ResetColor
123-
)?)
126+
)?;
127+
128+
// Add the summary if available
129+
if let Some(summary) = &self.summary {
130+
queue!(
131+
updates,
132+
style::SetForegroundColor(Color::Blue),
133+
style::Print("\nPurpose: "),
134+
style::ResetColor,
135+
style::Print(summary),
136+
style::Print("\n"),
137+
)?;
138+
}
139+
140+
queue!(updates, style::Print("\n"))?;
141+
142+
Ok(())
124143
}
125144

126145
pub async fn validate(&mut self, _ctx: &Context) -> Result<()> {
@@ -317,6 +336,59 @@ mod tests {
317336
}
318337
}
319338

339+
#[test]
340+
fn test_deserialize_with_summary() {
341+
let json = r#"{"command": "ls -la", "summary": "List all files with details"}"#;
342+
let tool = serde_json::from_str::<ExecuteBash>(json).unwrap();
343+
assert_eq!(tool.command, "ls -la");
344+
assert_eq!(tool.summary, Some("List all files with details".to_string()));
345+
}
346+
347+
#[test]
348+
fn test_deserialize_without_summary() {
349+
let json = r#"{"command": "ls -la"}"#;
350+
let tool = serde_json::from_str::<ExecuteBash>(json).unwrap();
351+
assert_eq!(tool.command, "ls -la");
352+
assert_eq!(tool.summary, None);
353+
}
354+
355+
#[test]
356+
fn test_queue_description_with_summary() {
357+
let mut buffer = Vec::new();
358+
359+
let tool = ExecuteBash {
360+
command: "ls -la".to_string(),
361+
summary: Some("List all files in the current directory with details".to_string()),
362+
};
363+
364+
tool.queue_description(&mut buffer).unwrap();
365+
366+
// Convert to string and print for debugging
367+
let output = String::from_utf8_lossy(&buffer).to_string();
368+
println!("Debug output: {:?}", output);
369+
370+
// Check for command and summary text, ignoring ANSI color codes
371+
assert!(output.contains("ls -la"));
372+
assert!(output.contains("Purpose:"));
373+
assert!(output.contains("List all files in the current directory with details"));
374+
}
375+
376+
#[test]
377+
fn test_queue_description_without_summary() {
378+
let mut buffer = Vec::new();
379+
380+
let tool = ExecuteBash {
381+
command: "ls -la".to_string(),
382+
summary: None,
383+
};
384+
385+
tool.queue_description(&mut buffer).unwrap();
386+
387+
let output = String::from_utf8(buffer).unwrap();
388+
assert!(output.contains("ls -la"));
389+
assert!(!output.contains("Purpose:"));
390+
}
391+
320392
#[test]
321393
fn test_requires_acceptance_for_readonly_commands() {
322394
let cmds = &[

crates/q_chat/src/tools/tool_index.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
"command": {
99
"type": "string",
1010
"description": "Bash command to execute"
11+
},
12+
"summary": {
13+
"type": "string",
14+
"description": "A brief explanation of what the command does"
1115
}
1216
},
1317
"required": [

0 commit comments

Comments
 (0)