agent_core/tui/
commands.rs

1// Slash command system for the TUI
2//
3// Provides default commands that work with any agent, plus support for
4// agent-specific custom commands.
5
6use super::widgets::SlashCommandTrait;
7
8/// A slash command with its name and description
9#[derive(Debug, Clone)]
10pub struct SlashCommand {
11    /// Command name without the slash (e.g., "help")
12    pub name: &'static str,
13    /// Brief description of what the command does
14    pub description: &'static str,
15}
16
17impl SlashCommandTrait for SlashCommand {
18    fn name(&self) -> &str {
19        self.name
20    }
21
22    fn description(&self) -> &str {
23        self.description
24    }
25}
26
27impl SlashCommandTrait for &SlashCommand {
28    fn name(&self) -> &str {
29        self.name
30    }
31
32    fn description(&self) -> &str {
33        self.description
34    }
35}
36
37/// Default commands available in all agents
38pub static DEFAULT_COMMANDS: &[SlashCommand] = &[
39    SlashCommand {
40        name: "help",
41        description: "Show available commands and usage",
42    },
43    SlashCommand {
44        name: "clear",
45        description: "Clear the conversation history",
46    },
47    SlashCommand {
48        name: "status",
49        description: "Show current session status",
50    },
51    SlashCommand {
52        name: "new-session",
53        description: "Create a new LLM session",
54    },
55    SlashCommand {
56        name: "quit",
57        description: "Exit the application",
58    },
59    SlashCommand {
60        name: "version",
61        description: "Show application version",
62    },
63    SlashCommand {
64        name: "themes",
65        description: "Open theme picker to change colors",
66    },
67    SlashCommand {
68        name: "compact",
69        description: "Compact the conversation history",
70    },
71    SlashCommand {
72        name: "sessions",
73        description: "View and switch between sessions",
74    },
75];
76
77/// Returns all default slash commands
78pub fn get_default_commands() -> &'static [SlashCommand] {
79    DEFAULT_COMMANDS
80}
81
82/// Returns commands that match the given input prefix.
83/// Input should include the leading slash (e.g., "/he" to match "/help")
84pub fn filter_commands<'a>(
85    commands: &'a [SlashCommand],
86    input: &str,
87) -> Vec<&'a SlashCommand> {
88    let search_term = input.trim_start_matches('/');
89
90    commands
91        .iter()
92        .filter(|cmd| cmd.name.starts_with(search_term))
93        .collect()
94}
95
96/// Returns a command by its name, or None if not found
97pub fn get_command_by_name<'a>(
98    commands: &'a [SlashCommand],
99    name: &str,
100) -> Option<&'a SlashCommand> {
101    let name = name.trim_start_matches('/');
102    commands.iter().find(|cmd| cmd.name == name)
103}
104
105/// Check if input is a slash command
106pub fn is_slash_command(input: &str) -> bool {
107    input.starts_with('/')
108}
109
110/// Parse slash command from input, returning (command_name, args)
111pub fn parse_command(input: &str) -> Option<(&str, &str)> {
112    if !is_slash_command(input) {
113        return None;
114    }
115
116    let trimmed = input.trim_start_matches('/');
117    let mut parts = trimmed.splitn(2, ' ');
118    let name = parts.next()?;
119    let args = parts.next().unwrap_or("");
120
121    Some((name, args))
122}
123
124/// Generate help message with all available commands
125pub fn generate_help_message(commands: &[SlashCommand]) -> String {
126    let mut help = String::from("Available commands:\n\n");
127
128    for cmd in commands {
129        help.push_str(&format!("/{} - {}\n", cmd.name, cmd.description));
130    }
131
132    help
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_filter_commands() {
141        let matches = filter_commands(DEFAULT_COMMANDS, "/he");
142        assert_eq!(matches.len(), 1);
143        assert_eq!(matches[0].name, "help");
144
145        let matches = filter_commands(DEFAULT_COMMANDS, "/");
146        assert_eq!(matches.len(), DEFAULT_COMMANDS.len());
147    }
148
149    #[test]
150    fn test_get_command_by_name() {
151        assert!(get_command_by_name(DEFAULT_COMMANDS, "help").is_some());
152        assert!(get_command_by_name(DEFAULT_COMMANDS, "/help").is_some());
153        assert!(get_command_by_name(DEFAULT_COMMANDS, "unknown").is_none());
154    }
155
156    #[test]
157    fn test_parse_command() {
158        assert_eq!(parse_command("/help"), Some(("help", "")));
159        assert_eq!(
160            parse_command("/new-session arg"),
161            Some(("new-session", "arg"))
162        );
163        assert_eq!(parse_command("not a command"), None);
164    }
165
166    #[test]
167    fn test_is_slash_command() {
168        assert!(is_slash_command("/help"));
169        assert!(!is_slash_command("hello"));
170    }
171}