1#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum MetaCommand {
9 Quit,
11 Help,
13 Reset,
15 Model(Option<String>),
17 Persona(Option<String>),
19 Space(Option<String>),
21 Spaces,
23 Clear,
25}
26
27impl MetaCommand {
28 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 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
73fn 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); assert_eq!(MetaCommand::parse("quit"), None);
146 }
147}