ricecoder_commands/
types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// A custom command definition
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct CommandDefinition {
7    /// Unique identifier for the command
8    pub id: String,
9
10    /// Human-readable name
11    pub name: String,
12
13    /// Description of what the command does
14    pub description: String,
15
16    /// The shell command to execute (supports templates)
17    pub command: String,
18
19    /// Command arguments with templates
20    pub arguments: Vec<CommandArgument>,
21
22    /// Whether the command is enabled
23    pub enabled: bool,
24
25    /// Whether to inject output into chat
26    pub inject_output: bool,
27
28    /// Timeout in seconds (0 = no timeout)
29    pub timeout_seconds: u64,
30
31    /// Tags for categorization
32    pub tags: Vec<String>,
33
34    /// Custom metadata
35    pub metadata: HashMap<String, String>,
36}
37
38/// A command argument definition
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct CommandArgument {
41    /// Argument name
42    pub name: String,
43
44    /// Argument description
45    pub description: String,
46
47    /// Whether the argument is required
48    pub required: bool,
49
50    /// Default value if not provided
51    pub default: Option<String>,
52
53    /// Validation pattern (regex)
54    pub validation_pattern: Option<String>,
55
56    /// Argument type (string, number, boolean, etc.)
57    pub arg_type: ArgumentType,
58}
59
60/// Argument type enumeration
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
62#[serde(rename_all = "lowercase")]
63pub enum ArgumentType {
64    String,
65    Number,
66    Boolean,
67    Path,
68    Choice(Vec<String>),
69}
70
71/// Command execution context
72#[derive(Debug, Clone)]
73pub struct CommandContext {
74    /// Current working directory
75    pub cwd: String,
76
77    /// Environment variables
78    pub env: HashMap<String, String>,
79
80    /// User-provided arguments
81    pub arguments: HashMap<String, String>,
82}
83
84/// Result of command execution
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct CommandExecutionResult {
87    /// Command ID that was executed
88    pub command_id: String,
89
90    /// Exit code (0 = success)
91    pub exit_code: i32,
92
93    /// Standard output
94    pub stdout: String,
95
96    /// Standard error
97    pub stderr: String,
98
99    /// Whether execution was successful
100    pub success: bool,
101
102    /// Execution duration in milliseconds
103    pub duration_ms: u64,
104}
105
106impl CommandDefinition {
107    /// Create a new command definition
108    pub fn new(id: impl Into<String>, name: impl Into<String>, command: impl Into<String>) -> Self {
109        Self {
110            id: id.into(),
111            name: name.into(),
112            description: String::new(),
113            command: command.into(),
114            arguments: Vec::new(),
115            enabled: true,
116            inject_output: false,
117            timeout_seconds: 0,
118            tags: Vec::new(),
119            metadata: HashMap::new(),
120        }
121    }
122
123    /// Set the description
124    pub fn with_description(mut self, description: impl Into<String>) -> Self {
125        self.description = description.into();
126        self
127    }
128
129    /// Add an argument
130    pub fn with_argument(mut self, argument: CommandArgument) -> Self {
131        self.arguments.push(argument);
132        self
133    }
134
135    /// Set whether to inject output
136    pub fn with_inject_output(mut self, inject: bool) -> Self {
137        self.inject_output = inject;
138        self
139    }
140
141    /// Set timeout
142    pub fn with_timeout(mut self, seconds: u64) -> Self {
143        self.timeout_seconds = seconds;
144        self
145    }
146
147    /// Add a tag
148    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
149        self.tags.push(tag.into());
150        self
151    }
152
153    /// Set enabled state
154    pub fn with_enabled(mut self, enabled: bool) -> Self {
155        self.enabled = enabled;
156        self
157    }
158}
159
160impl CommandArgument {
161    /// Create a new command argument
162    pub fn new(name: impl Into<String>, arg_type: ArgumentType) -> Self {
163        Self {
164            name: name.into(),
165            description: String::new(),
166            required: false,
167            default: None,
168            validation_pattern: None,
169            arg_type,
170        }
171    }
172
173    /// Set the description
174    pub fn with_description(mut self, description: impl Into<String>) -> Self {
175        self.description = description.into();
176        self
177    }
178
179    /// Set whether the argument is required
180    pub fn with_required(mut self, required: bool) -> Self {
181        self.required = required;
182        self
183    }
184
185    /// Set the default value
186    pub fn with_default(mut self, default: impl Into<String>) -> Self {
187        self.default = Some(default.into());
188        self
189    }
190
191    /// Set the validation pattern
192    pub fn with_validation_pattern(mut self, pattern: impl Into<String>) -> Self {
193        self.validation_pattern = Some(pattern.into());
194        self
195    }
196}
197
198impl CommandExecutionResult {
199    /// Create a new execution result
200    pub fn new(command_id: impl Into<String>, exit_code: i32) -> Self {
201        Self {
202            command_id: command_id.into(),
203            exit_code,
204            stdout: String::new(),
205            stderr: String::new(),
206            success: exit_code == 0,
207            duration_ms: 0,
208        }
209    }
210
211    /// Set stdout
212    pub fn with_stdout(mut self, stdout: impl Into<String>) -> Self {
213        self.stdout = stdout.into();
214        self
215    }
216
217    /// Set stderr
218    pub fn with_stderr(mut self, stderr: impl Into<String>) -> Self {
219        self.stderr = stderr.into();
220        self
221    }
222
223    /// Set duration
224    pub fn with_duration(mut self, duration_ms: u64) -> Self {
225        self.duration_ms = duration_ms;
226        self
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    #[test]
235    fn test_command_definition_creation() {
236        let cmd = CommandDefinition::new("test-cmd", "Test Command", "echo hello");
237        assert_eq!(cmd.id, "test-cmd");
238        assert_eq!(cmd.name, "Test Command");
239        assert_eq!(cmd.command, "echo hello");
240        assert!(cmd.enabled);
241        assert!(!cmd.inject_output);
242    }
243
244    #[test]
245    fn test_command_definition_builder() {
246        let cmd = CommandDefinition::new("test-cmd", "Test Command", "echo hello")
247            .with_description("A test command")
248            .with_inject_output(true)
249            .with_timeout(30)
250            .with_tag("test")
251            .with_enabled(false);
252
253        assert_eq!(cmd.description, "A test command");
254        assert!(cmd.inject_output);
255        assert_eq!(cmd.timeout_seconds, 30);
256        assert_eq!(cmd.tags, vec!["test"]);
257        assert!(!cmd.enabled);
258    }
259
260    #[test]
261    fn test_command_argument_creation() {
262        let arg = CommandArgument::new("name", ArgumentType::String)
263            .with_description("User name")
264            .with_required(true);
265
266        assert_eq!(arg.name, "name");
267        assert_eq!(arg.description, "User name");
268        assert!(arg.required);
269        assert_eq!(arg.arg_type, ArgumentType::String);
270    }
271
272    #[test]
273    fn test_execution_result_creation() {
274        let result = CommandExecutionResult::new("test-cmd", 0)
275            .with_stdout("output")
276            .with_duration(100);
277
278        assert_eq!(result.command_id, "test-cmd");
279        assert_eq!(result.exit_code, 0);
280        assert!(result.success);
281        assert_eq!(result.stdout, "output");
282        assert_eq!(result.duration_ms, 100);
283    }
284}