1use std::collections::HashMap;
2use std::process::Command;
3
4use anyhow::{Context, Result};
5
6use crate::config::CommandConfig;
7
8pub struct SlashCommand {
9 pub name: String,
10 pub description: String,
11 command: String,
12 _timeout: u64,
13}
14
15impl SlashCommand {
16 pub fn from_config(name: &str, cfg: &CommandConfig) -> Self {
17 SlashCommand {
18 name: name.to_string(),
19 description: cfg.description.clone(),
20 command: cfg.command.clone(),
21 _timeout: cfg.timeout,
22 }
23 }
24
25 pub fn execute(&self, args: &str, cwd: &str) -> Result<String> {
26 let mut cmd = Command::new("/bin/sh");
27 cmd.arg("-c").arg(&self.command);
28 cmd.env("DOT_COMMAND", &self.name);
29 cmd.env("DOT_ARGS", args);
30 cmd.env("DOT_CWD", cwd);
31 cmd.current_dir(cwd);
32 let output = cmd
33 .output()
34 .with_context(|| format!("command '{}' failed to execute", self.name))?;
35 let stdout = String::from_utf8_lossy(&output.stdout);
36 let stderr = String::from_utf8_lossy(&output.stderr);
37 if !output.status.success() {
38 anyhow::bail!(
39 "command '{}' exited with {}: {}",
40 self.name,
41 output.status,
42 stderr
43 );
44 }
45 Ok(stdout.to_string())
46 }
47}
48
49pub struct CommandRegistry {
50 commands: HashMap<String, SlashCommand>,
51}
52
53impl CommandRegistry {
54 pub fn new() -> Self {
55 CommandRegistry {
56 commands: HashMap::new(),
57 }
58 }
59
60 pub fn register(&mut self, cmd: SlashCommand) {
61 tracing::info!("Registered command: /{}", cmd.name);
62 self.commands.insert(cmd.name.clone(), cmd);
63 }
64
65 pub fn execute(&self, name: &str, args: &str, cwd: &str) -> Result<String> {
66 match self.commands.get(name) {
67 Some(cmd) => cmd.execute(args, cwd),
68 None => anyhow::bail!("unknown command: /{}", name),
69 }
70 }
71
72 pub fn list(&self) -> Vec<(&str, &str)> {
73 self.commands
74 .values()
75 .map(|c| (c.name.as_str(), c.description.as_str()))
76 .collect()
77 }
78
79 pub fn has(&self, name: &str) -> bool {
80 self.commands.contains_key(name)
81 }
82
83 pub fn is_empty(&self) -> bool {
84 self.commands.is_empty()
85 }
86}
87
88impl Default for CommandRegistry {
89 fn default() -> Self {
90 Self::new()
91 }
92}