Skip to content

Commit 99c475a

Browse files
committed
fix(chat): Add dedicated handler for ! command execution
Fixed the issue with ! command not parsing correctly by: - Creating a dedicated ExecuteCommand handler - Adding the handler to the command system - Updating Command::to_handler() to use the new handler - Setting requires_confirmation to true for security Also fixed a clippy warning in prompts/list.rs by using first() instead of get(0) 🤖 Assisted by [Amazon Q Developer](https://aws.amazon.com/q/developer)
1 parent c5fbcc0 commit 99c475a

File tree

4 files changed

+108
-10
lines changed

4 files changed

+108
-10
lines changed

crates/chat-cli/src/cli/chat/command.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::cli::chat::commands::clear::CLEAR_HANDLER;
1818
use crate::cli::chat::commands::compact::COMPACT_HANDLER;
1919
use crate::cli::chat::commands::context::CONTEXT_HANDLER;
2020
use crate::cli::chat::commands::editor::EDITOR_HANDLER;
21+
use crate::cli::chat::commands::execute::EXECUTE_HANDLER;
2122
// Import static handlers
2223
use crate::cli::chat::commands::help::HELP_HANDLER;
2324
use crate::cli::chat::commands::issue::ISSUE_HANDLER;
@@ -1067,8 +1068,8 @@ impl Command {
10671068
Command::Quit => &QUIT_HANDLER,
10681069
Command::Clear => &CLEAR_HANDLER,
10691070
Command::Context { subcommand } => subcommand.to_handler(),
1070-
Command::Profile { subcommand } => subcommand.to_handler(), /* Use the to_handler method on
1071-
* ProfileSubcommand */
1071+
Command::Profile { subcommand } => subcommand.to_handler(), // Use the to_handler method on
1072+
// ProfileSubcommand
10721073
Command::Tools { subcommand } => match subcommand {
10731074
Some(sub) => sub.to_handler(), // Use the to_handler method on ToolsSubcommand
10741075
None => &crate::cli::chat::commands::tools::LIST_TOOLS_HANDLER, /* Default to list handler when no
@@ -1079,11 +1080,12 @@ impl Command {
10791080
Command::Usage => &USAGE_HANDLER,
10801081
Command::Issue { .. } => &ISSUE_HANDLER,
10811082
// These commands are not handled through the command system
1082-
Command::Ask { .. } => &HELP_HANDLER, // Fallback to help handler
1083-
Command::Execute { .. } => &HELP_HANDLER, // Fallback to help handler
1083+
Command::Ask { .. } => &HELP_HANDLER, // Fallback to help handler
1084+
Command::Execute { .. } => &EXECUTE_HANDLER, // Use the dedicated execute handler
10841085
Command::Prompts { subcommand } => match subcommand {
10851086
Some(sub) => sub.to_handler(),
1086-
None => &crate::cli::chat::commands::prompts::LIST_PROMPTS_HANDLER, // Default to list handler when no subcommand
1087+
None => &crate::cli::chat::commands::prompts::LIST_PROMPTS_HANDLER, /* Default to list handler when
1088+
* no subcommand */
10871089
},
10881090
}
10891091
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use std::future::Future;
2+
use std::pin::Pin;
3+
use std::process::Command as ProcessCommand;
4+
5+
use crossterm::{
6+
queue,
7+
style,
8+
};
9+
10+
use crate::cli::chat::command::Command;
11+
use crate::cli::chat::commands::context_adapter::CommandContextAdapter;
12+
use crate::cli::chat::commands::handler::CommandHandler;
13+
use crate::cli::chat::{
14+
ChatError,
15+
ChatState,
16+
QueuedTool,
17+
};
18+
19+
/// Static instance of the execute command handler
20+
pub static EXECUTE_HANDLER: ExecuteCommand = ExecuteCommand;
21+
22+
/// Handler for the execute command
23+
pub struct ExecuteCommand;
24+
25+
impl CommandHandler for ExecuteCommand {
26+
fn name(&self) -> &'static str {
27+
"execute"
28+
}
29+
30+
fn description(&self) -> &'static str {
31+
"Execute a shell command"
32+
}
33+
34+
fn usage(&self) -> &'static str {
35+
"!<command>"
36+
}
37+
38+
fn help(&self) -> String {
39+
"Execute a shell command directly from the chat interface.".to_string()
40+
}
41+
42+
fn llm_description(&self) -> String {
43+
r#"
44+
Execute a shell command directly from the chat interface.
45+
46+
Usage:
47+
!<command>
48+
49+
Examples:
50+
- "!ls -la" - List files in the current directory
51+
- "!echo Hello, world!" - Print a message
52+
- "!git status" - Check git status
53+
54+
This command allows you to run any shell command without leaving the chat interface.
55+
"#
56+
.to_string()
57+
}
58+
59+
fn to_command(&self, args: Vec<&str>) -> Result<Command, ChatError> {
60+
let command = args.join(" ");
61+
Ok(Command::Execute { command })
62+
}
63+
64+
fn execute_command<'a>(
65+
&'a self,
66+
command: &'a Command,
67+
ctx: &'a mut CommandContextAdapter<'a>,
68+
tool_uses: Option<Vec<QueuedTool>>,
69+
pending_tool_index: Option<usize>,
70+
) -> Pin<Box<dyn Future<Output = Result<ChatState, ChatError>> + Send + 'a>> {
71+
Box::pin(async move {
72+
if let Command::Execute { command } = command {
73+
queue!(ctx.output, style::Print('\n'))?;
74+
ProcessCommand::new("bash").args(["-c", command]).status().ok();
75+
queue!(ctx.output, style::Print('\n'))?;
76+
77+
Ok(ChatState::PromptUser {
78+
tool_uses,
79+
pending_tool_index,
80+
skip_printing_tools: false,
81+
})
82+
} else {
83+
Err(ChatError::Custom(
84+
"ExecuteCommand can only execute Execute commands".into(),
85+
))
86+
}
87+
})
88+
}
89+
90+
fn requires_confirmation(&self, _args: &[&str]) -> bool {
91+
true // Execute commands require confirmation for security
92+
}
93+
}

crates/chat-cli/src/cli/chat/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod compact;
33
pub mod context;
44
pub mod context_adapter;
55
pub mod editor;
6+
pub mod execute;
67
pub mod handler;
78
pub mod help;
89
pub mod issue;

crates/chat-cli/src/cli/chat/commands/prompts/list.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ impl CommandHandler for ListPromptsCommand {
4444
}
4545

4646
fn to_command(&self, args: Vec<&str>) -> Result<Command, ChatError> {
47-
let search_word = args.get(0).map(|s| (*s).to_string());
48-
47+
let search_word = args.first().map(|s| (*s).to_string());
48+
4949
Ok(Command::Prompts {
5050
subcommand: Some(PromptsSubcommand::List { search_word }),
5151
})
@@ -75,17 +75,19 @@ impl CommandHandler for ListPromptsCommand {
7575
style::SetForegroundColor(Color::Yellow),
7676
style::Print("No MCP servers with prompts are currently available.\n\n"),
7777
style::ResetColor,
78-
style::Print("To use prompts, you need to install and configure MCP servers that provide prompt templates.\n\n")
78+
style::Print(
79+
"To use prompts, you need to install and configure MCP servers that provide prompt templates.\n\n"
80+
)
7981
)?;
80-
82+
8183
if let Some(word) = search_word {
8284
queue!(
8385
ctx.output,
8486
style::Print(format!("Search term: \"{}\"\n", word)),
8587
style::Print("No matching prompts found.\n\n")
8688
)?;
8789
}
88-
90+
8991
ctx.output.flush()?;
9092

9193
Ok(ChatState::PromptUser {

0 commit comments

Comments
 (0)