Skip to main content

atman_runtime/
meta_commands.rs

1pub struct MetaCommand {
2    pub name: &'static str,
3    pub desc: &'static str,
4    pub usage: &'static str,
5    pub aliases: &'static [&'static str],
6}
7
8pub const META_COMMANDS: &[MetaCommand] = &[
9    MetaCommand {
10        name: "help",
11        desc: "show this help",
12        usage: ":help",
13        aliases: &[],
14    },
15    MetaCommand {
16        name: "exit",
17        desc: "leave repl",
18        usage: ":exit | :quit",
19        aliases: &["quit"],
20    },
21    MetaCommand {
22        name: "session",
23        desc: "print current session id",
24        usage: ":session",
25        aliases: &[],
26    },
27    MetaCommand {
28        name: "cost",
29        desc: "cost summary hint",
30        usage: ":cost",
31        aliases: &[],
32    },
33    MetaCommand {
34        name: "mode",
35        desc: "switch trust mode (calm/steady/eager/reckless)",
36        usage: ":mode",
37        aliases: &[],
38    },
39    MetaCommand {
40        name: "mode-theme",
41        desc: "switch display theme (default/wuxia/animal/weather/drink)",
42        usage: ":mode-theme",
43        aliases: &[],
44    },
45    MetaCommand {
46        name: "model",
47        desc: "switch active model",
48        usage: ":model",
49        aliases: &[],
50    },
51    MetaCommand {
52        name: "attach",
53        desc: "attach file / list / clear",
54        usage: ":attach <path> | :attach clear | :attach list",
55        aliases: &[],
56    },
57    MetaCommand {
58        name: "suggest",
59        desc: "meta-LLM flow suggestion",
60        usage: ":suggest",
61        aliases: &[],
62    },
63    MetaCommand {
64        name: "goal",
65        desc: "get / set / clear session goal",
66        usage: ":goal | :goal <text> | :goal clear",
67        aliases: &[],
68    },
69    MetaCommand {
70        name: "sessions",
71        desc: "list recent sessions",
72        usage: ":sessions",
73        aliases: &[],
74    },
75    MetaCommand {
76        name: "projects",
77        desc: "open all projects",
78        usage: ":projects",
79        aliases: &[],
80    },
81    MetaCommand {
82        name: "storage",
83        desc: "set current project storage scope",
84        usage: ":storage [global|local]",
85        aliases: &[],
86    },
87    MetaCommand {
88        name: "sidebar",
89        desc: "sidebar on / off / auto",
90        usage: ":sidebar on | off | auto",
91        aliases: &[],
92    },
93    MetaCommand {
94        name: "todo",
95        desc: "list / done <id> / cancel <id> / clear",
96        usage: ":todo list | :todo done <id> | :todo cancel <id> | :todo clear",
97        aliases: &[],
98    },
99    MetaCommand {
100        name: "rename",
101        desc: "set / clear session title",
102        usage: ":rename <text> | :rename clear",
103        aliases: &[],
104    },
105    MetaCommand {
106        name: "copy",
107        desc: "push to clipboard (OSC 52)",
108        usage: ":copy last-message | :copy last-tool",
109        aliases: &[],
110    },
111    MetaCommand {
112        name: "compact",
113        desc: "compact transcript now",
114        usage: ":compact",
115        aliases: &[],
116    },
117];
118
119pub fn builtin_list() -> Vec<(&'static str, &'static str)> {
120    META_COMMANDS.iter().map(|c| (c.name, c.desc)).collect()
121}
122
123pub fn help_lines() -> Vec<&'static str> {
124    let mut lines: Vec<&'static str> = META_COMMANDS.iter().map(|c| c.usage).collect();
125    lines.push("");
126    lines.push("resume a prior session: exit, then run `atman --continue <session_id>`");
127    lines.push("@./path or @/abs     — inline attach in bare input");
128    lines
129}
130
131pub fn match_command(input: &str) -> Option<&'static MetaCommand> {
132    let name = input.split_whitespace().next().unwrap_or(input);
133    META_COMMANDS
134        .iter()
135        .find(|c| c.name == name || c.aliases.contains(&name))
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn all_commands_have_nonempty_fields() {
144        for c in META_COMMANDS {
145            assert!(!c.name.is_empty());
146            assert!(!c.desc.is_empty());
147            assert!(!c.usage.is_empty());
148        }
149    }
150
151    #[test]
152    fn match_by_name() {
153        assert_eq!(match_command("help").unwrap().name, "help");
154        assert_eq!(match_command("mode").unwrap().name, "mode");
155        assert_eq!(match_command("mode eager").unwrap().name, "mode");
156    }
157
158    #[test]
159    fn match_by_alias() {
160        assert_eq!(match_command("quit").unwrap().name, "exit");
161    }
162
163    #[test]
164    fn unknown_returns_none() {
165        assert!(match_command("nonexistent").is_none());
166    }
167
168    #[test]
169    fn builtin_list_matches_commands() {
170        let list = builtin_list();
171        assert_eq!(list.len(), META_COMMANDS.len());
172        assert!(list.iter().any(|(n, _)| *n == "mode"));
173        assert!(list.iter().any(|(n, _)| *n == "projects"));
174        assert!(list.iter().any(|(n, _)| *n == "storage"));
175    }
176
177    #[test]
178    fn help_lines_not_empty() {
179        let lines = help_lines();
180        assert!(!lines.is_empty());
181        assert!(lines.iter().any(|l| l.contains("resume")));
182    }
183}