Skip to main content

oxios_cli/
commands.rs

1//! Meta-command parsing for the CLI channel.
2//!
3//! Recognises dot-commands that control the interactive session:
4//! `.quit`, `.help`, `.reset`, `.model`, `.persona`, `.clear`.
5
6/// A parsed meta-command.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum MetaCommand {
9    /// Quit the session.
10    Quit,
11    /// Show help text.
12    Help,
13    /// Reset the current session / conversation.
14    Reset,
15    /// Switch the active model. Carries the model name, if provided.
16    Model(Option<String>),
17    /// Switch the active persona. Carries the persona name, if provided.
18    Persona(Option<String>),
19    /// Switch the active Space. Carries the Space name/ID, if provided.
20    Space(Option<String>),
21    /// List all Spaces.
22    Spaces,
23    /// Clear the terminal screen.
24    Clear,
25}
26
27impl MetaCommand {
28    /// Attempt to parse a line as a meta-command.
29    ///
30    /// Returns `Some(MetaCommand)` if the line starts with `.`,
31    /// or `None` if it is a regular user message.
32    pub fn parse(line: &str) -> Option<Self> {
33        let trimmed = line.trim();
34        if !trimmed.starts_with('.') {
35            return None;
36        }
37
38        let parts: Vec<&str> = trimmed.splitn(2, whitespace_or_end).collect();
39        let cmd = parts[0];
40        let arg = parts
41            .get(1)
42            .map(|s| s.trim().to_string())
43            .filter(|s| !s.is_empty());
44
45        match cmd {
46            ".quit" | ".exit" | ".q" => Some(Self::Quit),
47            ".help" | ".h" | ".?" => Some(Self::Help),
48            ".reset" | ".r" => Some(Self::Reset),
49            ".model" | ".m" => Some(Self::Model(arg)),
50            ".persona" | ".p" => Some(Self::Persona(arg)),
51            ".space" | ".sp" => Some(Self::Space(arg)),
52            ".spaces" => Some(Self::Spaces),
53            ".clear" | ".cls" => Some(Self::Clear),
54            _ => None,
55        }
56    }
57
58    /// Returns the help text shown by `.help`.
59    pub fn help_text() -> &'static str {
60        r#"Oxios CLI — Meta-commands:
61  .quit, .exit, .q      Exit the session
62  .help, .h, .?         Show this help
63  .reset, .r             Reset the current session
64  .model, .m [NAME]      Show or switch the active model
65  .persona, .p [NAME]    Show or switch the active persona
66  .space, .sp [ID|NAME]  Show or switch the active Space
67  .spaces                List all Spaces
68  .clear, .cls           Clear the terminal screen
69"#
70    }
71}
72
73/// Helper: find the first whitespace or end-of-string.
74fn whitespace_or_end(c: char) -> bool {
75    c.is_whitespace()
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn parse_quit() {
84        assert_eq!(MetaCommand::parse(".quit"), Some(MetaCommand::Quit));
85        assert_eq!(MetaCommand::parse(".exit"), Some(MetaCommand::Quit));
86        assert_eq!(MetaCommand::parse(".q"), Some(MetaCommand::Quit));
87    }
88
89    #[test]
90    fn parse_help() {
91        assert_eq!(MetaCommand::parse(".help"), Some(MetaCommand::Help));
92        assert_eq!(MetaCommand::parse(".h"), Some(MetaCommand::Help));
93    }
94
95    #[test]
96    fn parse_model_with_arg() {
97        assert_eq!(
98            MetaCommand::parse(".model gpt-4o"),
99            Some(MetaCommand::Model(Some("gpt-4o".into())))
100        );
101    }
102
103    #[test]
104    fn parse_model_no_arg() {
105        assert_eq!(MetaCommand::parse(".model"), Some(MetaCommand::Model(None)));
106    }
107
108    #[test]
109    fn parse_persona_with_arg() {
110        assert_eq!(
111            MetaCommand::parse(".persona coder"),
112            Some(MetaCommand::Persona(Some("coder".into())))
113        );
114    }
115
116    #[test]
117    fn parse_space_no_arg() {
118        assert_eq!(MetaCommand::parse(".space"), Some(MetaCommand::Space(None)));
119        assert_eq!(MetaCommand::parse(".sp"), Some(MetaCommand::Space(None)));
120    }
121
122    #[test]
123    fn parse_space_with_arg() {
124        assert_eq!(
125            MetaCommand::parse(".space my-space"),
126            Some(MetaCommand::Space(Some("my-space".into())))
127        );
128        assert_eq!(
129            MetaCommand::parse(".sp 550e8400-e29b-41d4-a716-446655440000"),
130            Some(MetaCommand::Space(Some(
131                "550e8400-e29b-41d4-a716-446655440000".into()
132            )))
133        );
134    }
135
136    #[test]
137    fn parse_spaces() {
138        assert_eq!(MetaCommand::parse(".spaces"), Some(MetaCommand::Spaces));
139    }
140
141    #[test]
142    fn not_a_command() {
143        assert_eq!(MetaCommand::parse("hello world"), None);
144        assert_eq!(MetaCommand::parse("exit"), None); // no dot prefix
145        assert_eq!(MetaCommand::parse("quit"), None);
146    }
147}