Skip to main content

xcom_rs/introspection/
help.rs

1//! Detailed help generation for all commands.
2//!
3//! [`CommandHelp::for_command`] returns human-readable and machine-readable
4//! help for a given command, including usage, exit codes, error vocabulary,
5//! and examples.
6//!
7//! The `description` field is derived from [`super::registry::CommandsList`] so
8//! that it stays in sync with the single source of truth for command metadata.
9
10use serde::{Deserialize, Serialize};
11
12use super::registry::CommandsList;
13
14/// A single exit-code descriptor.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ExitCodeInfo {
17    pub code: i32,
18    pub description: String,
19}
20
21/// A single error-code descriptor.
22#[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/// A single usage example.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ExampleInfo {
33    pub description: String,
34    pub command: String,
35}
36
37/// Full help record for a command.
38#[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    /// Standard exit codes shared by every command.
52    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    /// Standard error vocabulary shared by every command.
75    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    /// Look up the description for `command` from the registry, falling back to
132    /// a generic string if the command is not found.
133    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    /// Build the [`CommandHelp`] for the given command name.
143    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}