1use serde::{Deserialize, Serialize};
11
12use super::registry::CommandsList;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ExitCodeInfo {
17 pub code: i32,
18 pub description: String,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ErrorCodeInfo {
24 pub code: String,
25 pub description: String,
26 #[serde(rename = "isRetryable")]
27 pub is_retryable: bool,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ExampleInfo {
33 pub description: String,
34 pub command: String,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct CommandHelp {
40 pub command: String,
41 pub description: String,
42 pub usage: String,
43 #[serde(rename = "exitCodes")]
44 pub exit_codes: Vec<ExitCodeInfo>,
45 #[serde(rename = "errorVocabulary")]
46 pub error_vocabulary: Vec<ErrorCodeInfo>,
47 pub examples: Vec<ExampleInfo>,
48}
49
50impl CommandHelp {
51 fn standard_exit_codes() -> Vec<ExitCodeInfo> {
53 vec![
54 ExitCodeInfo {
55 code: 0,
56 description: "Success".to_string(),
57 },
58 ExitCodeInfo {
59 code: 2,
60 description: "Invalid argument or missing required argument".to_string(),
61 },
62 ExitCodeInfo {
63 code: 3,
64 description: "Authentication or authorization failed".to_string(),
65 },
66 ExitCodeInfo {
67 code: 4,
68 description: "Operation failed (network, rate limit, service unavailable, etc.)"
69 .to_string(),
70 },
71 ]
72 }
73
74 fn standard_error_vocabulary() -> Vec<ErrorCodeInfo> {
76 vec![
77 ErrorCodeInfo {
78 code: "INVALID_ARGUMENT".to_string(),
79 description: "Invalid argument provided".to_string(),
80 is_retryable: false,
81 },
82 ErrorCodeInfo {
83 code: "MISSING_ARGUMENT".to_string(),
84 description: "Required argument missing".to_string(),
85 is_retryable: false,
86 },
87 ErrorCodeInfo {
88 code: "UNKNOWN_COMMAND".to_string(),
89 description: "Command not recognized".to_string(),
90 is_retryable: false,
91 },
92 ErrorCodeInfo {
93 code: "AUTHENTICATION_FAILED".to_string(),
94 description: "Authentication credentials invalid or expired".to_string(),
95 is_retryable: false,
96 },
97 ErrorCodeInfo {
98 code: "AUTHORIZATION_FAILED".to_string(),
99 description: "Insufficient permissions".to_string(),
100 is_retryable: false,
101 },
102 ErrorCodeInfo {
103 code: "RATE_LIMIT_EXCEEDED".to_string(),
104 description: "Rate limit exceeded, retry after delay".to_string(),
105 is_retryable: true,
106 },
107 ErrorCodeInfo {
108 code: "NETWORK_ERROR".to_string(),
109 description: "Network connection failed".to_string(),
110 is_retryable: true,
111 },
112 ErrorCodeInfo {
113 code: "SERVICE_UNAVAILABLE".to_string(),
114 description: "Service temporarily unavailable".to_string(),
115 is_retryable: true,
116 },
117 ErrorCodeInfo {
118 code: "INTERNAL_ERROR".to_string(),
119 description: "Internal error occurred".to_string(),
120 is_retryable: false,
121 },
122 ErrorCodeInfo {
123 code: "INTERACTION_REQUIRED".to_string(),
124 description: "User interaction required but --non-interactive mode is enabled"
125 .to_string(),
126 is_retryable: false,
127 },
128 ]
129 }
130
131 fn description_from_registry(command: &str) -> String {
134 CommandsList::new()
135 .commands
136 .into_iter()
137 .find(|c| c.name == command)
138 .map(|c| c.description)
139 .unwrap_or_else(|| format!("Help for {command}"))
140 }
141
142 pub fn for_command(command: &str) -> Self {
144 let exit_codes = Self::standard_exit_codes();
145 let error_vocabulary = Self::standard_error_vocabulary();
146 let description = Self::description_from_registry(command);
147
148 match command {
149 "commands" => Self {
150 command: command.to_string(),
151 description,
152 usage: "xcom-rs commands [--output json|yaml|text]".to_string(),
153 exit_codes,
154 error_vocabulary,
155 examples: vec![
156 ExampleInfo {
157 description: "List commands in JSON format".to_string(),
158 command: "xcom-rs commands --output json".to_string(),
159 },
160 ExampleInfo {
161 description: "List commands in text format".to_string(),
162 command: "xcom-rs commands --output text".to_string(),
163 },
164 ],
165 },
166 "schema" => Self {
167 command: command.to_string(),
168 description,
169 usage: "xcom-rs schema --command <name> [--output json|yaml|text]".to_string(),
170 exit_codes,
171 error_vocabulary,
172 examples: vec![
173 ExampleInfo {
174 description: "Get schema for commands command".to_string(),
175 command: "xcom-rs schema --command commands --output json".to_string(),
176 },
177 ExampleInfo {
178 description: "Get schema for help command".to_string(),
179 command: "xcom-rs schema --command help --output json".to_string(),
180 },
181 ],
182 },
183 "help" => Self {
184 command: command.to_string(),
185 description,
186 usage: "xcom-rs help <command> [--output json|yaml|text]".to_string(),
187 exit_codes,
188 error_vocabulary,
189 examples: vec![
190 ExampleInfo {
191 description: "Get help for commands command".to_string(),
192 command: "xcom-rs help commands --output json".to_string(),
193 },
194 ExampleInfo {
195 description: "Get help for schema command".to_string(),
196 command: "xcom-rs help schema --output json".to_string(),
197 },
198 ],
199 },
200 "demo-interactive" => Self {
201 command: command.to_string(),
202 description,
203 usage: "xcom-rs demo-interactive [--non-interactive] [--output json|yaml|text]"
204 .to_string(),
205 exit_codes,
206 error_vocabulary,
207 examples: vec![
208 ExampleInfo {
209 description: "Run in interactive mode".to_string(),
210 command: "xcom-rs demo-interactive".to_string(),
211 },
212 ExampleInfo {
213 description:
214 "Run in non-interactive mode (will fail with INTERACTION_REQUIRED)"
215 .to_string(),
216 command: "xcom-rs demo-interactive --non-interactive --output json"
217 .to_string(),
218 },
219 ],
220 },
221 "install-skills" => Self {
222 command: command.to_string(),
223 description,
224 usage: "xcom-rs install-skills [--skill <name>] [--agent <agent>] [--global] \
225 [--yes] [--output json|yaml|text]"
226 .to_string(),
227 exit_codes,
228 error_vocabulary,
229 examples: vec![
230 ExampleInfo {
231 description: "Install all skills to project scope with JSON output"
232 .to_string(),
233 command: "xcom-rs install-skills --yes --non-interactive --output json"
234 .to_string(),
235 },
236 ExampleInfo {
237 description: "Install specific skill to global scope for Claude"
238 .to_string(),
239 command: "xcom-rs install-skills --skill example-skill --agent claude \
240 --global --yes --output json"
241 .to_string(),
242 },
243 ExampleInfo {
244 description: "Install all skills to OpenCode project scope".to_string(),
245 command: "xcom-rs install-skills --agent opencode --yes --output json"
246 .to_string(),
247 },
248 ],
249 },
250 "search recent" => Self {
251 command: command.to_string(),
252 description,
253 usage: "xcom-rs search recent \"<query>\" [--limit N] [--cursor <token>] \
254 [--output json|ndjson|yaml|text]"
255 .to_string(),
256 exit_codes,
257 error_vocabulary,
258 examples: vec![
259 ExampleInfo {
260 description: "Search for recent tweets about Rust".to_string(),
261 command: "xcom-rs search recent \"rust programming\" --output json"
262 .to_string(),
263 },
264 ExampleInfo {
265 description: "Search with pagination limit".to_string(),
266 command: "xcom-rs search recent \"AI\" --limit 20 --output json"
267 .to_string(),
268 },
269 ExampleInfo {
270 description: "Paginate through search results".to_string(),
271 command: "xcom-rs search recent \"AI\" --cursor cursor_20 --output ndjson"
272 .to_string(),
273 },
274 ],
275 },
276 "search users" => Self {
277 command: command.to_string(),
278 description,
279 usage: "xcom-rs search users \"<query>\" [--limit N] [--cursor <token>] \
280 [--output json|ndjson|yaml|text]"
281 .to_string(),
282 exit_codes,
283 error_vocabulary,
284 examples: vec![
285 ExampleInfo {
286 description: "Search for users named Alice".to_string(),
287 command: "xcom-rs search users \"alice\" --output json".to_string(),
288 },
289 ExampleInfo {
290 description: "Search users with limit".to_string(),
291 command: "xcom-rs search users \"developer\" --limit 5 --output json"
292 .to_string(),
293 },
294 ExampleInfo {
295 description: "Get users as NDJSON stream".to_string(),
296 command: "xcom-rs search users \"rust\" --output ndjson".to_string(),
297 },
298 ],
299 },
300 "tweets reply" => Self {
301 command: command.to_string(),
302 description,
303 usage: "xcom-rs tweets reply <tweet_id> \"<text>\" [--client-request-id <id>] \
304 [--if-exists return|error] [--output json|yaml|text]"
305 .to_string(),
306 exit_codes,
307 error_vocabulary,
308 examples: vec![
309 ExampleInfo {
310 description: "Reply to a tweet".to_string(),
311 command: "xcom-rs tweets reply 1234567890 \"Great post!\" --output json"
312 .to_string(),
313 },
314 ExampleInfo {
315 description: "Reply with idempotency key".to_string(),
316 command: "xcom-rs tweets reply 1234567890 \"Reply\" \
317 --client-request-id my-reply-001 --output json"
318 .to_string(),
319 },
320 ],
321 },
322 "tweets thread" => Self {
323 command: command.to_string(),
324 description,
325 usage: "xcom-rs tweets thread \"<t1>\" \"<t2>\" ... \
326 [--client-request-id-prefix <prefix>] [--if-exists return|error] \
327 [--output json|yaml|text]"
328 .to_string(),
329 exit_codes,
330 error_vocabulary,
331 examples: vec![
332 ExampleInfo {
333 description: "Post a two-tweet thread".to_string(),
334 command:
335 "xcom-rs tweets thread \"First tweet\" \"Second tweet\" --output json"
336 .to_string(),
337 },
338 ExampleInfo {
339 description: "Post thread with idempotency prefix".to_string(),
340 command: "xcom-rs tweets thread \"A\" \"B\" \"C\" \
341 --client-request-id-prefix thread-001 --output json"
342 .to_string(),
343 },
344 ],
345 },
346 "tweets show" => Self {
347 command: command.to_string(),
348 description,
349 usage: "xcom-rs tweets show <tweet_id> [--output json|yaml|text]".to_string(),
350 exit_codes,
351 error_vocabulary,
352 examples: vec![ExampleInfo {
353 description: "Show a tweet by ID".to_string(),
354 command: "xcom-rs tweets show 1234567890 --output json".to_string(),
355 }],
356 },
357 "tweets conversation" => Self {
358 command: command.to_string(),
359 description,
360 usage: "xcom-rs tweets conversation <tweet_id> [--output json|yaml|text]"
361 .to_string(),
362 exit_codes,
363 error_vocabulary,
364 examples: vec![ExampleInfo {
365 description: "Fetch conversation tree for a tweet".to_string(),
366 command: "xcom-rs tweets conversation 1234567890 --output json".to_string(),
367 }],
368 },
369 "timeline.home" => Self {
370 command: command.to_string(),
371 description,
372 usage: "xcom-rs timeline home [--limit <n>] [--cursor <token>] \
373 [--output json|ndjson|yaml|text]"
374 .to_string(),
375 exit_codes,
376 error_vocabulary,
377 examples: vec![
378 ExampleInfo {
379 description: "Get home timeline in JSON format".to_string(),
380 command: "xcom-rs timeline home --output json".to_string(),
381 },
382 ExampleInfo {
383 description: "Get 20 tweets with NDJSON output".to_string(),
384 command: "xcom-rs timeline home --limit 20 --output ndjson".to_string(),
385 },
386 ExampleInfo {
387 description: "Get next page using cursor".to_string(),
388 command: "xcom-rs timeline home --cursor next_token_10 --output json"
389 .to_string(),
390 },
391 ],
392 },
393 "timeline.mentions" => Self {
394 command: command.to_string(),
395 description,
396 usage: "xcom-rs timeline mentions [--limit <n>] [--cursor <token>] \
397 [--output json|ndjson|yaml|text]"
398 .to_string(),
399 exit_codes,
400 error_vocabulary,
401 examples: vec![
402 ExampleInfo {
403 description: "Get mentions in JSON format".to_string(),
404 command: "xcom-rs timeline mentions --output json".to_string(),
405 },
406 ExampleInfo {
407 description: "Get 5 mentions with NDJSON output".to_string(),
408 command: "xcom-rs timeline mentions --limit 5 --output ndjson".to_string(),
409 },
410 ],
411 },
412 "timeline.user" => Self {
413 command: command.to_string(),
414 description,
415 usage: "xcom-rs timeline user <handle> [--limit <n>] [--cursor <token>] \
416 [--output json|ndjson|yaml|text]"
417 .to_string(),
418 exit_codes,
419 error_vocabulary,
420 examples: vec![
421 ExampleInfo {
422 description: "Get tweets from user in JSON format".to_string(),
423 command: "xcom-rs timeline user johndoe --output json".to_string(),
424 },
425 ExampleInfo {
426 description: "Get 5 tweets from user with NDJSON output".to_string(),
427 command: "xcom-rs timeline user johndoe --limit 5 --output ndjson"
428 .to_string(),
429 },
430 ],
431 },
432 "media.upload" => Self {
433 command: command.to_string(),
434 description,
435 usage: "xcom-rs media upload <path> [--output json|yaml|text]".to_string(),
436 exit_codes,
437 error_vocabulary,
438 examples: vec![
439 ExampleInfo {
440 description: "Upload an image and get media_id in JSON format".to_string(),
441 command: "xcom-rs media upload /path/to/image.jpg --output json"
442 .to_string(),
443 },
444 ExampleInfo {
445 description: "Upload a video file".to_string(),
446 command: "xcom-rs media upload /path/to/video.mp4 --output json"
447 .to_string(),
448 },
449 ],
450 },
451 "tweets like" => Self {
452 command: command.to_string(),
453 description,
454 usage: "xcom-rs tweets like <tweet_id> [--output json|yaml|text]".to_string(),
455 exit_codes,
456 error_vocabulary,
457 examples: vec![ExampleInfo {
458 description: "Like a specific tweet".to_string(),
459 command: "xcom-rs tweets like 1234567890 --output json".to_string(),
460 }],
461 },
462 "tweets unlike" => Self {
463 command: command.to_string(),
464 description,
465 usage: "xcom-rs tweets unlike <tweet_id> [--output json|yaml|text]".to_string(),
466 exit_codes,
467 error_vocabulary,
468 examples: vec![ExampleInfo {
469 description: "Unlike a specific tweet".to_string(),
470 command: "xcom-rs tweets unlike 1234567890 --output json".to_string(),
471 }],
472 },
473 "tweets retweet" => Self {
474 command: command.to_string(),
475 description,
476 usage: "xcom-rs tweets retweet <tweet_id> [--output json|yaml|text]".to_string(),
477 exit_codes,
478 error_vocabulary,
479 examples: vec![ExampleInfo {
480 description: "Retweet a specific tweet".to_string(),
481 command: "xcom-rs tweets retweet 1234567890 --output json".to_string(),
482 }],
483 },
484 "tweets unretweet" => Self {
485 command: command.to_string(),
486 description,
487 usage: "xcom-rs tweets unretweet <tweet_id> [--output json|yaml|text]".to_string(),
488 exit_codes,
489 error_vocabulary,
490 examples: vec![ExampleInfo {
491 description: "Unretweet a specific tweet".to_string(),
492 command: "xcom-rs tweets unretweet 1234567890 --output json".to_string(),
493 }],
494 },
495 "bookmarks add" => Self {
496 command: command.to_string(),
497 description,
498 usage: "xcom-rs bookmarks add <tweet_id> [--output json|yaml|text]".to_string(),
499 exit_codes,
500 error_vocabulary,
501 examples: vec![ExampleInfo {
502 description: "Bookmark a specific tweet".to_string(),
503 command: "xcom-rs bookmarks add 1234567890 --output json".to_string(),
504 }],
505 },
506 "bookmarks remove" => Self {
507 command: command.to_string(),
508 description,
509 usage: "xcom-rs bookmarks remove <tweet_id> [--output json|yaml|text]".to_string(),
510 exit_codes,
511 error_vocabulary,
512 examples: vec![ExampleInfo {
513 description: "Remove a specific tweet from bookmarks".to_string(),
514 command: "xcom-rs bookmarks remove 1234567890 --output json".to_string(),
515 }],
516 },
517 "bookmarks list" => Self {
518 command: command.to_string(),
519 description,
520 usage: "xcom-rs bookmarks list [--limit N] [--cursor <token>] \
521 [--output json|ndjson|yaml|text]"
522 .to_string(),
523 exit_codes,
524 error_vocabulary,
525 examples: vec![
526 ExampleInfo {
527 description: "List bookmarks with JSON output".to_string(),
528 command: "xcom-rs bookmarks list --output json".to_string(),
529 },
530 ExampleInfo {
531 description: "List bookmarks with limit and cursor".to_string(),
532 command:
533 "xcom-rs bookmarks list --limit 20 --cursor <next_token> --output json"
534 .to_string(),
535 },
536 ExampleInfo {
537 description: "List bookmarks as NDJSON stream".to_string(),
538 command: "xcom-rs bookmarks list --output ndjson".to_string(),
539 },
540 ],
541 },
542 _ => Self {
543 command: command.to_string(),
544 description,
545 usage: format!("xcom-rs {} [options]", command),
546 exit_codes,
547 error_vocabulary,
548 examples: vec![],
549 },
550 }
551 }
552}