bhcli

A TUI for chatting on LE PHP Chats
git clone https://git.dasho.dev/bhcli.git
Log | Files | Refs | README

command_router.rs (7495B)


      1 use crate::ai_service::AIService;
      2 use crate::chatops::{ChatOpResult, CommandContext, CommandRegistry, UserRole};
      3 use std::sync::Arc;
      4 use tokio::runtime::Runtime;
      5 
      6 /// Main router for handling ChatOps commands
      7 pub struct ChatOpsRouter {
      8    registry: CommandRegistry,
      9 }
     10 
     11 impl ChatOpsRouter {
     12    pub fn new() -> Self {
     13        Self {
     14            registry: crate::chatops::init_chatops(),
     15        }
     16    }
     17 
     18    pub fn new_with_ai(ai_service: Arc<AIService>, runtime: Arc<Runtime>) -> Self {
     19        Self {
     20            registry: crate::chatops::init_chatops_with_ai(ai_service, runtime),
     21        }
     22    }
     23 
     24    /// Process a slash command input
     25    pub fn process_command(
     26        &self,
     27        input: &str,
     28        username: &str,
     29        role: UserRole,
     30    ) -> Option<ChatOpResult> {
     31        // Skip if not a chatops command (let existing system handle it)
     32        if !self.is_chatops_command(input) {
     33            return None;
     34        }
     35 
     36        let parts = self.parse_command(input);
     37        if parts.is_empty() {
     38            return Some(ChatOpResult::Error("Empty command".to_string()));
     39        }
     40 
     41        let command_name = &parts[0];
     42        let args = parts[1..].to_vec();
     43 
     44        // Handle special built-in commands
     45        match command_name.as_str() {
     46            "chatops" => return Some(self.handle_help_command(vec![], &role)),
     47            "commands" | "list" => return Some(self.handle_list_commands(&role)),
     48            "help" => {
     49                // Only handle specific chatops help, let general help fall through
     50                if !args.is_empty() && self.registry.get_help(&args[0]).is_some() {
     51                    return Some(self.handle_help_command(args, &role));
     52                }
     53                // Let general /help fall through to main help system
     54                return None;
     55            }
     56            _ => {}
     57        }
     58 
     59        let context = CommandContext {
     60            username: username.to_string(),
     61            room: None, // TODO: Extract from app state if needed
     62            role,
     63        };
     64 
     65        match self.registry.execute_command(command_name, args, &context) {
     66            Ok(result) => {
     67                // Truncate long results to prevent chat spam but allow more context
     68                let truncated = if result.should_truncate(40) {
     69                    result.truncate(40)
     70                } else {
     71                    result
     72                };
     73                Some(truncated)
     74            }
     75            Err(error) => Some(ChatOpResult::Error(error.to_string())),
     76        }
     77    }
     78 
     79    /// Check if this is a chatops command (vs existing system command)
     80    fn is_chatops_command(&self, input: &str) -> bool {
     81        if !input.starts_with('/') {
     82            return false;
     83        }
     84 
     85        let parts = self.parse_command(input);
     86        if parts.is_empty() {
     87            return false;
     88        }
     89 
     90        let command_name = &parts[0];
     91 
     92        // Built-in chatops commands
     93        if matches!(command_name.as_str(), "help" | "commands" | "list") {
     94            return true;
     95        }
     96 
     97        // Check if it's a registered chatops command
     98        self.registry.get_command(command_name).is_some()
     99    }
    100 
    101    /// Parse command input into parts
    102    fn parse_command(&self, input: &str) -> Vec<String> {
    103        let input = input.trim_start_matches('/');
    104 
    105        // Simple parsing - split on whitespace but respect quotes
    106        let mut parts = Vec::new();
    107        let mut current = String::new();
    108        let mut in_quotes = false;
    109        let mut chars = input.chars().peekable();
    110 
    111        while let Some(ch) = chars.next() {
    112            match ch {
    113                '"' if !in_quotes => {
    114                    in_quotes = true;
    115                }
    116                '"' if in_quotes => {
    117                    in_quotes = false;
    118                    if !current.is_empty() {
    119                        parts.push(current.clone());
    120                        current.clear();
    121                    }
    122                }
    123                ' ' | '\t' if !in_quotes => {
    124                    if !current.is_empty() {
    125                        parts.push(current.clone());
    126                        current.clear();
    127                    }
    128                }
    129                _ => {
    130                    current.push(ch);
    131                }
    132            }
    133        }
    134 
    135        if !current.is_empty() {
    136            parts.push(current);
    137        }
    138 
    139        parts
    140    }
    141 
    142    /// Handle help command
    143    fn handle_help_command(&self, args: Vec<String>, _role: &UserRole) -> ChatOpResult {
    144        if args.is_empty() {
    145            // General ChatOps help - return as single message for better full-screen display
    146            let help_text = vec![
    147                "🤖 **ChatOps Developer Commands Available:**",
    148                "",
    149                "**Documentation & Lookup:**",
    150                "/man <command> - Manual pages",
    151                "/doc <lang> <term> - Language docs",
    152                "/explain <topic> - Explain concepts",
    153                "/cheat <term> - Cheat sheets",
    154                "/so <query> - StackOverflow search",
    155                "",
    156                "**Tools & Utilities:**",
    157                "/hash <algo> <text> - Hash functions",
    158                "/uuid - Generate UUID",
    159                "/base64 <encode|decode> <text>",
    160                "/regex <pattern> <text> - Test regex",
    161                "/time - Current time",
    162                "",
    163                "**Network Tools:**",
    164                "/whois <domain> - Domain lookup",
    165                "/dig <domain> - DNS lookup",
    166                "/ping <host> - Ping host",
    167                "/headers <url> - HTTP headers",
    168                "",
    169                "**GitHub Integration:**",
    170                "/github <user>/<repo> - Repo info",
    171                "/crates <crate> - Rust crate info",
    172                "/npm <package> - NPM package info",
    173                "",
    174                "Use `/help <command>` for help on specific ChatOps commands.",
    175                "Use `/commands` to list available commands for your role.",
    176                "Use `/help` for general chat commands and shortcuts.",
    177            ]
    178            .join("\n");
    179 
    180            ChatOpResult::Message(help_text)
    181        } else {
    182            // Specific command help
    183            let command_name = &args[0];
    184            match self.registry.get_help(command_name) {
    185                Some(help) => ChatOpResult::Message(help),
    186                None => {
    187                    ChatOpResult::Error(format!("No help available for command: {}", command_name))
    188                }
    189            }
    190        }
    191    }
    192 
    193    /// Handle list commands
    194    fn handle_list_commands(&self, role: &UserRole) -> ChatOpResult {
    195        let commands = self.registry.list_commands(role);
    196        if commands.is_empty() {
    197            return ChatOpResult::Message("No commands available for your role.".to_string());
    198        }
    199 
    200        let mut output = vec![
    201            format!("📋 **Available Commands ({} total):**", commands.len()),
    202            "".to_string(),
    203        ];
    204 
    205        for (name, description) in commands {
    206            output.push(format!("**{}** - {}", name, description));
    207        }
    208 
    209        ChatOpResult::Block(output)
    210    }
    211 
    212    /// Register a user alias
    213    #[allow(dead_code)]
    214    pub fn register_user_alias(&mut self, alias: String, target: String) {
    215        self.registry.register_alias(alias, target);
    216    }
    217 
    218    /// Remove a user alias
    219    #[allow(dead_code)]
    220    pub fn remove_user_alias(&mut self, alias: &str) {
    221        self.registry.remove_alias(alias);
    222    }
    223 }