1use room_protocol::plugin::{CommandInfo, ParamSchema, ParamType, Plugin};
2
3use super::{queue, stats, taskboard};
4
5pub fn builtin_command_infos() -> Vec<CommandInfo> {
11 vec![
12 CommandInfo {
13 name: "dm".to_owned(),
14 description: "Send a private message".to_owned(),
15 usage: "/dm <user> <message>".to_owned(),
16 params: vec![
17 ParamSchema {
18 name: "user".to_owned(),
19 param_type: ParamType::Username,
20 required: true,
21 description: "Recipient username".to_owned(),
22 },
23 ParamSchema {
24 name: "message".to_owned(),
25 param_type: ParamType::Text,
26 required: true,
27 description: "Message content".to_owned(),
28 },
29 ],
30 subcommands: vec![],
31 },
32 CommandInfo {
33 name: "reply".to_owned(),
34 description: "Reply to a message".to_owned(),
35 usage: "/reply <id> <message>".to_owned(),
36 params: vec![
37 ParamSchema {
38 name: "id".to_owned(),
39 param_type: ParamType::Text,
40 required: true,
41 description: "Message ID to reply to".to_owned(),
42 },
43 ParamSchema {
44 name: "message".to_owned(),
45 param_type: ParamType::Text,
46 required: true,
47 description: "Reply content".to_owned(),
48 },
49 ],
50 subcommands: vec![],
51 },
52 CommandInfo {
53 name: "who".to_owned(),
54 description: "List users in the room".to_owned(),
55 usage: "/who [--verbose|-v]".to_owned(),
56 params: vec![ParamSchema {
57 name: "flag".to_owned(),
58 param_type: ParamType::Choice(vec!["--verbose".to_owned(), "-v".to_owned()]),
59 required: false,
60 description: "Show detailed status duration and last message time".to_owned(),
61 }],
62 subcommands: vec![],
63 },
64 CommandInfo {
65 name: "who_all".to_owned(),
66 description: "List all daemon users (cross-room)".to_owned(),
67 usage: "/who_all".to_owned(),
68 params: vec![],
69 subcommands: vec![],
70 },
71 CommandInfo {
72 name: "kick".to_owned(),
73 description: "Kick a user from the room".to_owned(),
74 usage: "/kick <user>".to_owned(),
75 params: vec![ParamSchema {
76 name: "user".to_owned(),
77 param_type: ParamType::Username,
78 required: true,
79 description: "User to kick (host only)".to_owned(),
80 }],
81 subcommands: vec![],
82 },
83 CommandInfo {
84 name: "reauth".to_owned(),
85 description: "Invalidate a user's token".to_owned(),
86 usage: "/reauth <user>".to_owned(),
87 params: vec![ParamSchema {
88 name: "user".to_owned(),
89 param_type: ParamType::Username,
90 required: true,
91 description: "User to reauth (host only)".to_owned(),
92 }],
93 subcommands: vec![],
94 },
95 CommandInfo {
96 name: "clear-tokens".to_owned(),
97 description: "Revoke all session tokens".to_owned(),
98 usage: "/clear-tokens".to_owned(),
99 params: vec![],
100 subcommands: vec![],
101 },
102 CommandInfo {
103 name: "exit".to_owned(),
104 description: "Shut down the broker".to_owned(),
105 usage: "/exit".to_owned(),
106 params: vec![],
107 subcommands: vec![],
108 },
109 CommandInfo {
110 name: "clear".to_owned(),
111 description: "Clear the room history".to_owned(),
112 usage: "/clear".to_owned(),
113 params: vec![],
114 subcommands: vec![],
115 },
116 CommandInfo {
117 name: "info".to_owned(),
118 description: "Show room metadata or user info".to_owned(),
119 usage: "/info [username]".to_owned(),
120 params: vec![ParamSchema {
121 name: "username".to_owned(),
122 param_type: ParamType::Username,
123 required: false,
124 description: "User to inspect (omit for room info)".to_owned(),
125 }],
126 subcommands: vec![],
127 },
128 CommandInfo {
129 name: "room-info".to_owned(),
130 description: "Alias for /info — show room visibility, config, and member count"
131 .to_owned(),
132 usage: "/room-info".to_owned(),
133 params: vec![],
134 subcommands: vec![],
135 },
136 CommandInfo {
137 name: "subscribe".to_owned(),
138 description: "Subscribe to this room".to_owned(),
139 usage: "/subscribe [tier]".to_owned(),
140 params: vec![ParamSchema {
141 name: "tier".to_owned(),
142 param_type: ParamType::Choice(vec!["full".to_owned(), "mentions_only".to_owned()]),
143 required: false,
144 description: "Subscription tier (default: full)".to_owned(),
145 }],
146 subcommands: vec![],
147 },
148 CommandInfo {
149 name: "set_subscription".to_owned(),
150 description: "Alias for /subscribe — set subscription tier for this room".to_owned(),
151 usage: "/set_subscription [tier]".to_owned(),
152 params: vec![ParamSchema {
153 name: "tier".to_owned(),
154 param_type: ParamType::Choice(vec!["full".to_owned(), "mentions_only".to_owned()]),
155 required: false,
156 description: "Subscription tier (default: full)".to_owned(),
157 }],
158 subcommands: vec![],
159 },
160 CommandInfo {
161 name: "unsubscribe".to_owned(),
162 description: "Unsubscribe from this room".to_owned(),
163 usage: "/unsubscribe".to_owned(),
164 params: vec![],
165 subcommands: vec![],
166 },
167 CommandInfo {
168 name: "subscribe_events".to_owned(),
169 description: "Set event type filter for this room".to_owned(),
170 usage: "/subscribe_events [filter]".to_owned(),
171 params: vec![ParamSchema {
172 name: "filter".to_owned(),
173 param_type: ParamType::Text,
174 required: false,
175 description: "all, none, or comma-separated event types (default: all)".to_owned(),
176 }],
177 subcommands: vec![],
178 },
179 CommandInfo {
180 name: "set_event_filter".to_owned(),
181 description: "Alias for /subscribe_events — set event type filter".to_owned(),
182 usage: "/set_event_filter [filter]".to_owned(),
183 params: vec![ParamSchema {
184 name: "filter".to_owned(),
185 param_type: ParamType::Text,
186 required: false,
187 description: "all, none, or comma-separated event types (default: all)".to_owned(),
188 }],
189 subcommands: vec![],
190 },
191 CommandInfo {
192 name: "set_status".to_owned(),
193 description: "Set your presence status".to_owned(),
194 usage: "/set_status <status>".to_owned(),
195 params: vec![ParamSchema {
196 name: "status".to_owned(),
197 param_type: ParamType::Text,
198 required: false,
199 description: "Status text (omit to clear)".to_owned(),
200 }],
201 subcommands: vec![],
202 },
203 CommandInfo {
204 name: "subscriptions".to_owned(),
205 description: "List subscription tiers and event filters for this room".to_owned(),
206 usage: "/subscriptions".to_owned(),
207 params: vec![],
208 subcommands: vec![],
209 },
210 CommandInfo {
211 name: "team".to_owned(),
212 description: "Manage teams — join, leave, list, show".to_owned(),
213 usage: "/team <action> [args...]".to_owned(),
214 params: vec![
215 ParamSchema {
216 name: "action".to_owned(),
217 param_type: ParamType::Choice(vec![
218 "join".to_owned(),
219 "leave".to_owned(),
220 "list".to_owned(),
221 "show".to_owned(),
222 ]),
223 required: true,
224 description: "Subcommand".to_owned(),
225 },
226 ParamSchema {
227 name: "args".to_owned(),
228 param_type: ParamType::Text,
229 required: false,
230 description: "Team name and optional username".to_owned(),
231 },
232 ],
233 subcommands: vec![],
234 },
235 CommandInfo {
236 name: "help".to_owned(),
237 description: "List available commands or get help for a specific command".to_owned(),
238 usage: "/help [command]".to_owned(),
239 params: vec![ParamSchema {
240 name: "command".to_owned(),
241 param_type: ParamType::Text,
242 required: false,
243 description: "Command name to get help for".to_owned(),
244 }],
245 subcommands: vec![],
246 },
247 ]
248}
249
250pub fn all_known_commands() -> Vec<CommandInfo> {
255 let mut cmds = builtin_command_infos();
256 cmds.extend(queue::QueuePlugin::default_commands());
257 cmds.extend(stats::StatsPlugin.commands());
258 cmds.extend(taskboard::TaskboardPlugin::default_commands());
259 cmds
262}
263
264#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
273 fn builtin_command_infos_covers_all_expected_commands() {
274 let cmds = builtin_command_infos();
275 let names: Vec<&str> = cmds.iter().map(|c| c.name.as_str()).collect();
276 for expected in &[
277 "dm",
278 "reply",
279 "who",
280 "who_all",
281 "help",
282 "info",
283 "kick",
284 "reauth",
285 "clear-tokens",
286 "exit",
287 "clear",
288 "room-info",
289 "set_status",
290 "subscribe",
291 "set_subscription",
292 "unsubscribe",
293 "subscribe_events",
294 "set_event_filter",
295 "subscriptions",
296 "team",
297 ] {
298 assert!(
299 names.contains(expected),
300 "missing built-in command: {expected}"
301 );
302 }
303 }
304
305 #[test]
306 fn builtin_command_infos_dm_has_username_param() {
307 let cmds = builtin_command_infos();
308 let dm = cmds.iter().find(|c| c.name == "dm").unwrap();
309 assert_eq!(dm.params.len(), 2);
310 assert_eq!(dm.params[0].param_type, ParamType::Username);
311 assert!(dm.params[0].required);
312 assert_eq!(dm.params[1].param_type, ParamType::Text);
313 }
314
315 #[test]
316 fn builtin_command_infos_kick_has_username_param() {
317 let cmds = builtin_command_infos();
318 let kick = cmds.iter().find(|c| c.name == "kick").unwrap();
319 assert_eq!(kick.params.len(), 1);
320 assert_eq!(kick.params[0].param_type, ParamType::Username);
321 assert!(kick.params[0].required);
322 }
323
324 #[test]
325 fn builtin_command_infos_who_has_verbose_flag() {
326 let cmds = builtin_command_infos();
327 let who = cmds.iter().find(|c| c.name == "who").unwrap();
328 assert_eq!(who.params.len(), 1);
329 assert_eq!(who.params[0].name, "flag");
330 assert!(!who.params[0].required);
331 }
332
333 #[test]
336 fn all_known_commands_includes_builtins_and_plugins() {
337 let cmds = all_known_commands();
338 let names: Vec<&str> = cmds.iter().map(|c| c.name.as_str()).collect();
339 assert!(names.contains(&"dm"));
341 assert!(names.contains(&"who"));
342 assert!(names.contains(&"kick"));
343 assert!(names.contains(&"help"));
344 assert!(names.contains(&"stats"));
346 assert!(names.contains(&"taskboard"));
347 assert!(names.contains(&"queue"));
348 }
349
350 #[test]
351 fn all_known_commands_no_duplicates() {
352 let cmds = all_known_commands();
353 let mut names: Vec<&str> = cmds.iter().map(|c| c.name.as_str()).collect();
354 let before = names.len();
355 names.sort();
356 names.dedup();
357 assert_eq!(before, names.len(), "duplicate command names found");
358 }
359}