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}