Skip to main content

slack_rs/cli/
introspection.rs

1//! CLI introspection - self-describing CLI capabilities
2//!
3//! Provides machine-readable information about commands, flags, and output schemas.
4//! Implements Agentic CLI Design principle 7 (Introspectable).
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9/// CLI command definition
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct CommandDef {
12    pub name: String,
13    pub description: String,
14    pub usage: String,
15    pub flags: Vec<FlagDef>,
16    pub examples: Vec<ExampleDef>,
17    pub exit_codes: Vec<ExitCodeDef>,
18}
19
20/// Flag/option definition
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct FlagDef {
23    pub name: String,
24    #[serde(rename = "type")]
25    pub flag_type: String,
26    pub required: bool,
27    pub description: String,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub default: Option<String>,
30}
31
32/// Command example
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ExampleDef {
35    pub description: String,
36    pub command: String,
37}
38
39/// Exit code definition
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ExitCodeDef {
42    pub code: i32,
43    pub description: String,
44}
45
46/// Commands list response
47#[derive(Debug, Serialize, Deserialize)]
48pub struct CommandsListResponse {
49    #[serde(rename = "schemaVersion")]
50    pub schema_version: u32,
51    #[serde(rename = "type")]
52    pub response_type: String,
53    pub ok: bool,
54    pub commands: Vec<CommandDef>,
55}
56
57/// Structured help response
58#[derive(Debug, Serialize, Deserialize)]
59pub struct HelpResponse {
60    #[serde(rename = "schemaVersion")]
61    pub schema_version: u32,
62    #[serde(rename = "type")]
63    pub response_type: String,
64    pub ok: bool,
65    pub command: String,
66    pub usage: String,
67    pub flags: Vec<FlagDef>,
68    pub examples: Vec<ExampleDef>,
69    #[serde(rename = "exitCodes")]
70    pub exit_codes: Vec<ExitCodeDef>,
71}
72
73/// Schema response
74#[derive(Debug, Serialize, Deserialize)]
75pub struct SchemaResponse {
76    #[serde(rename = "schemaVersion")]
77    pub schema_version: u32,
78    #[serde(rename = "type")]
79    pub response_type: String,
80    pub ok: bool,
81    pub command: String,
82    pub schema: Value,
83}
84
85/// Get all command definitions
86pub fn get_command_definitions() -> Vec<CommandDef> {
87    vec![
88        // api call
89        CommandDef {
90            name: "api call".to_string(),
91            description: "Call a Slack API method".to_string(),
92            usage: "slack-rs api call <method> [key=value]... [flags]".to_string(),
93            flags: vec![
94                FlagDef {
95                    name: "--json".to_string(),
96                    flag_type: "boolean".to_string(),
97                    required: false,
98                    description: "Send as JSON body (default: form-urlencoded)".to_string(),
99                    default: None,
100                },
101                FlagDef {
102                    name: "--get".to_string(),
103                    flag_type: "boolean".to_string(),
104                    required: false,
105                    description: "Use GET method (default: POST)".to_string(),
106                    default: None,
107                },
108                FlagDef {
109                    name: "--raw".to_string(),
110                    flag_type: "boolean".to_string(),
111                    required: false,
112                    description: "Output raw Slack API response (without envelope)".to_string(),
113                    default: None,
114                },
115                FlagDef {
116                    name: "--profile".to_string(),
117                    flag_type: "string".to_string(),
118                    required: false,
119                    description: "Profile name".to_string(),
120                    default: Some("default".to_string()),
121                },
122            ],
123            examples: vec![
124                ExampleDef {
125                    description: "Get user info".to_string(),
126                    command: "slack-rs api call users.info user=U123456 --get".to_string(),
127                },
128                ExampleDef {
129                    description: "Post message".to_string(),
130                    command: "slack-rs api call chat.postMessage channel=C123 text=Hello"
131                        .to_string(),
132                },
133            ],
134            exit_codes: vec![
135                ExitCodeDef {
136                    code: 0,
137                    description: "Success".to_string(),
138                },
139                ExitCodeDef {
140                    code: 1,
141                    description: "API call failed".to_string(),
142                },
143            ],
144        },
145        // auth login
146        CommandDef {
147            name: "auth login".to_string(),
148            description: "Authenticate with Slack via OAuth".to_string(),
149            usage: "slack-rs auth login [profile_name] [flags]".to_string(),
150            flags: vec![
151                FlagDef {
152                    name: "--client-id".to_string(),
153                    flag_type: "string".to_string(),
154                    required: false,
155                    description: "OAuth client ID".to_string(),
156                    default: None,
157                },
158                FlagDef {
159                    name: "--bot-scopes".to_string(),
160                    flag_type: "string".to_string(),
161                    required: false,
162                    description: "Bot scopes (comma-separated or 'all')".to_string(),
163                    default: None,
164                },
165                FlagDef {
166                    name: "--user-scopes".to_string(),
167                    flag_type: "string".to_string(),
168                    required: false,
169                    description: "User scopes (comma-separated or 'all')".to_string(),
170                    default: None,
171                },
172            ],
173            examples: vec![ExampleDef {
174                description: "Login with default profile".to_string(),
175                command: "slack-rs auth login".to_string(),
176            }],
177            exit_codes: vec![
178                ExitCodeDef {
179                    code: 0,
180                    description: "Success".to_string(),
181                },
182                ExitCodeDef {
183                    code: 1,
184                    description: "Login failed".to_string(),
185                },
186            ],
187        },
188        // auth status
189        CommandDef {
190            name: "auth status".to_string(),
191            description: "Show authentication status".to_string(),
192            usage: "slack-rs auth status [profile_name]".to_string(),
193            flags: vec![],
194            examples: vec![ExampleDef {
195                description: "Check status".to_string(),
196                command: "slack-rs auth status".to_string(),
197            }],
198            exit_codes: vec![
199                ExitCodeDef {
200                    code: 0,
201                    description: "Success".to_string(),
202                },
203                ExitCodeDef {
204                    code: 1,
205                    description: "Command failed".to_string(),
206                },
207            ],
208        },
209        // auth list
210        CommandDef {
211            name: "auth list".to_string(),
212            description: "List all profiles".to_string(),
213            usage: "slack-rs auth list".to_string(),
214            flags: vec![],
215            examples: vec![ExampleDef {
216                description: "List profiles".to_string(),
217                command: "slack-rs auth list".to_string(),
218            }],
219            exit_codes: vec![
220                ExitCodeDef {
221                    code: 0,
222                    description: "Success".to_string(),
223                },
224                ExitCodeDef {
225                    code: 1,
226                    description: "Command failed".to_string(),
227                },
228            ],
229        },
230        // auth logout
231        CommandDef {
232            name: "auth logout".to_string(),
233            description: "Remove authentication for a profile".to_string(),
234            usage: "slack-rs auth logout [profile_name]".to_string(),
235            flags: vec![],
236            examples: vec![ExampleDef {
237                description: "Logout".to_string(),
238                command: "slack-rs auth logout".to_string(),
239            }],
240            exit_codes: vec![
241                ExitCodeDef {
242                    code: 0,
243                    description: "Success".to_string(),
244                },
245                ExitCodeDef {
246                    code: 1,
247                    description: "Command failed".to_string(),
248                },
249            ],
250        },
251        // conv list
252        CommandDef {
253            name: "conv list".to_string(),
254            description: "List conversations".to_string(),
255            usage: "slack-rs conv list [flags]".to_string(),
256            flags: vec![
257                FlagDef {
258                    name: "--types".to_string(),
259                    flag_type: "string".to_string(),
260                    required: false,
261                    description: "Conversation types (comma-separated)".to_string(),
262                    default: None,
263                },
264                FlagDef {
265                    name: "--limit".to_string(),
266                    flag_type: "integer".to_string(),
267                    required: false,
268                    description: "Maximum number of conversations".to_string(),
269                    default: None,
270                },
271                FlagDef {
272                    name: "--filter".to_string(),
273                    flag_type: "string".to_string(),
274                    required: false,
275                    description: "Filter (key:value format, can be repeated)".to_string(),
276                    default: None,
277                },
278                FlagDef {
279                    name: "--format".to_string(),
280                    flag_type: "string".to_string(),
281                    required: false,
282                    description: "Output format (json, jsonl, table, tsv)".to_string(),
283                    default: Some("json".to_string()),
284                },
285                FlagDef {
286                    name: "--raw".to_string(),
287                    flag_type: "boolean".to_string(),
288                    required: false,
289                    description: "Output raw response (without envelope)".to_string(),
290                    default: None,
291                },
292                FlagDef {
293                    name: "--profile".to_string(),
294                    flag_type: "string".to_string(),
295                    required: false,
296                    description: "Profile name".to_string(),
297                    default: Some("default".to_string()),
298                },
299            ],
300            examples: vec![
301                ExampleDef {
302                    description: "List all conversations".to_string(),
303                    command: "slack-rs conv list".to_string(),
304                },
305                ExampleDef {
306                    description: "List with filter".to_string(),
307                    command: "slack-rs conv list --filter is_member:true".to_string(),
308                },
309            ],
310            exit_codes: vec![
311                ExitCodeDef {
312                    code: 0,
313                    description: "Success".to_string(),
314                },
315                ExitCodeDef {
316                    code: 1,
317                    description: "API call failed".to_string(),
318                },
319            ],
320        },
321        // conv search
322        CommandDef {
323            name: "conv search".to_string(),
324            description: "Search conversations by name".to_string(),
325            usage: "slack-rs conv search <pattern> [flags]".to_string(),
326            flags: vec![FlagDef {
327                name: "--profile".to_string(),
328                flag_type: "string".to_string(),
329                required: false,
330                description: "Profile name".to_string(),
331                default: Some("default".to_string()),
332            }],
333            examples: vec![ExampleDef {
334                description: "Search conversations".to_string(),
335                command: "slack-rs conv search general".to_string(),
336            }],
337            exit_codes: vec![
338                ExitCodeDef {
339                    code: 0,
340                    description: "Success".to_string(),
341                },
342                ExitCodeDef {
343                    code: 1,
344                    description: "Command failed".to_string(),
345                },
346            ],
347        },
348        // conv history
349        CommandDef {
350            name: "conv history".to_string(),
351            description: "Get conversation history".to_string(),
352            usage: "slack-rs conv history <channel> [flags]".to_string(),
353            flags: vec![
354                FlagDef {
355                    name: "--limit".to_string(),
356                    flag_type: "integer".to_string(),
357                    required: false,
358                    description: "Maximum number of messages".to_string(),
359                    default: None,
360                },
361                FlagDef {
362                    name: "--profile".to_string(),
363                    flag_type: "string".to_string(),
364                    required: false,
365                    description: "Profile name".to_string(),
366                    default: Some("default".to_string()),
367                },
368            ],
369            examples: vec![ExampleDef {
370                description: "Get history".to_string(),
371                command: "slack-rs conv history C123456".to_string(),
372            }],
373            exit_codes: vec![
374                ExitCodeDef {
375                    code: 0,
376                    description: "Success".to_string(),
377                },
378                ExitCodeDef {
379                    code: 1,
380                    description: "Command failed".to_string(),
381                },
382            ],
383        },
384        // thread get
385        CommandDef {
386            name: "thread get".to_string(),
387            description: "Get thread messages (conversation replies)".to_string(),
388            usage: "slack-rs thread get <channel> <thread_ts> [flags]".to_string(),
389            flags: vec![
390                FlagDef {
391                    name: "--limit".to_string(),
392                    flag_type: "integer".to_string(),
393                    required: false,
394                    description: "Number of messages per page".to_string(),
395                    default: Some("100".to_string()),
396                },
397                FlagDef {
398                    name: "--inclusive".to_string(),
399                    flag_type: "boolean".to_string(),
400                    required: false,
401                    description: "Include parent message in results".to_string(),
402                    default: None,
403                },
404                FlagDef {
405                    name: "--raw".to_string(),
406                    flag_type: "boolean".to_string(),
407                    required: false,
408                    description: "Output raw Slack API response".to_string(),
409                    default: None,
410                },
411                FlagDef {
412                    name: "--profile".to_string(),
413                    flag_type: "string".to_string(),
414                    required: false,
415                    description: "Profile name".to_string(),
416                    default: Some("default".to_string()),
417                },
418                FlagDef {
419                    name: "--token-type".to_string(),
420                    flag_type: "string".to_string(),
421                    required: false,
422                    description: "Token type (bot or user)".to_string(),
423                    default: None,
424                },
425            ],
426            examples: vec![
427                ExampleDef {
428                    description: "Get thread messages".to_string(),
429                    command: "slack-rs thread get C123456 1234567890.123456".to_string(),
430                },
431                ExampleDef {
432                    description: "Get thread with parent message".to_string(),
433                    command: "slack-rs thread get C123456 1234567890.123456 --inclusive".to_string(),
434                },
435            ],
436            exit_codes: vec![
437                ExitCodeDef {
438                    code: 0,
439                    description: "Success".to_string(),
440                },
441                ExitCodeDef {
442                    code: 1,
443                    description: "Command failed".to_string(),
444                },
445            ],
446        },
447        // msg post
448        CommandDef {
449            name: "msg post".to_string(),
450            description: "Post a message to a channel".to_string(),
451            usage: "slack-rs msg post <channel> <text> [flags]".to_string(),
452            flags: vec![
453                FlagDef {
454                    name: "--thread-ts".to_string(),
455                    flag_type: "string".to_string(),
456                    required: false,
457                    description: "Thread timestamp for reply".to_string(),
458                    default: None,
459                },
460                FlagDef {
461                    name: "--reply-broadcast".to_string(),
462                    flag_type: "boolean".to_string(),
463                    required: false,
464                    description: "Broadcast reply to channel".to_string(),
465                    default: None,
466                },
467                FlagDef {
468                    name: "--profile".to_string(),
469                    flag_type: "string".to_string(),
470                    required: false,
471                    description: "Profile name".to_string(),
472                    default: Some("default".to_string()),
473                },
474                FlagDef {
475                    name: "--idempotency-key".to_string(),
476                    flag_type: "string".to_string(),
477                    required: false,
478                    description: "Idempotency key for preventing duplicate operations".to_string(),
479                    default: None,
480                },
481            ],
482            examples: vec![ExampleDef {
483                description: "Post message".to_string(),
484                command: "slack-rs msg post C123 'Hello world'".to_string(),
485            }],
486            exit_codes: vec![
487                ExitCodeDef {
488                    code: 0,
489                    description: "Success".to_string(),
490                },
491                ExitCodeDef {
492                    code: 1,
493                    description: "Post failed".to_string(),
494                },
495            ],
496        },
497        // msg update
498        CommandDef {
499            name: "msg update".to_string(),
500            description: "Update a message".to_string(),
501            usage: "slack-rs msg update <channel> <ts> <text> [flags]".to_string(),
502            flags: vec![
503                FlagDef {
504                    name: "--profile".to_string(),
505                    flag_type: "string".to_string(),
506                    required: false,
507                    description: "Profile name".to_string(),
508                    default: Some("default".to_string()),
509                },
510                FlagDef {
511                    name: "--idempotency-key".to_string(),
512                    flag_type: "string".to_string(),
513                    required: false,
514                    description: "Idempotency key for preventing duplicate operations".to_string(),
515                    default: None,
516                },
517            ],
518            examples: vec![ExampleDef {
519                description: "Update message".to_string(),
520                command: "slack-rs msg update C123 1234567890.123456 'Updated text'".to_string(),
521            }],
522            exit_codes: vec![
523                ExitCodeDef {
524                    code: 0,
525                    description: "Success".to_string(),
526                },
527                ExitCodeDef {
528                    code: 1,
529                    description: "Update failed".to_string(),
530                },
531            ],
532        },
533        // msg delete
534        CommandDef {
535            name: "msg delete".to_string(),
536            description: "Delete a message".to_string(),
537            usage: "slack-rs msg delete <channel> <ts> [flags]".to_string(),
538            flags: vec![
539                FlagDef {
540                    name: "--profile".to_string(),
541                    flag_type: "string".to_string(),
542                    required: false,
543                    description: "Profile name".to_string(),
544                    default: Some("default".to_string()),
545                },
546                FlagDef {
547                    name: "--idempotency-key".to_string(),
548                    flag_type: "string".to_string(),
549                    required: false,
550                    description: "Idempotency key for preventing duplicate operations".to_string(),
551                    default: None,
552                },
553            ],
554            examples: vec![ExampleDef {
555                description: "Delete message".to_string(),
556                command: "slack-rs msg delete C123 1234567890.123456".to_string(),
557            }],
558            exit_codes: vec![
559                ExitCodeDef {
560                    code: 0,
561                    description: "Success".to_string(),
562                },
563                ExitCodeDef {
564                    code: 1,
565                    description: "Delete failed".to_string(),
566                },
567            ],
568        },
569        // users info
570        CommandDef {
571            name: "users info".to_string(),
572            description: "Get user information".to_string(),
573            usage: "slack-rs users info <user_id> [flags]".to_string(),
574            flags: vec![FlagDef {
575                name: "--profile".to_string(),
576                flag_type: "string".to_string(),
577                required: false,
578                description: "Profile name".to_string(),
579                default: Some("default".to_string()),
580            }],
581            examples: vec![ExampleDef {
582                description: "Get user info".to_string(),
583                command: "slack-rs users info U123456".to_string(),
584            }],
585            exit_codes: vec![
586                ExitCodeDef {
587                    code: 0,
588                    description: "Success".to_string(),
589                },
590                ExitCodeDef {
591                    code: 1,
592                    description: "Command failed".to_string(),
593                },
594            ],
595        },
596        // react add
597        CommandDef {
598            name: "react add".to_string(),
599            description: "Add a reaction to a message".to_string(),
600            usage: "slack-rs react add <channel> <ts> <emoji> [flags]".to_string(),
601            flags: vec![
602                FlagDef {
603                    name: "--profile".to_string(),
604                    flag_type: "string".to_string(),
605                    required: false,
606                    description: "Profile name".to_string(),
607                    default: Some("default".to_string()),
608                },
609                FlagDef {
610                    name: "--idempotency-key".to_string(),
611                    flag_type: "string".to_string(),
612                    required: false,
613                    description: "Idempotency key for preventing duplicate operations".to_string(),
614                    default: None,
615                },
616            ],
617            examples: vec![ExampleDef {
618                description: "Add reaction".to_string(),
619                command: "slack-rs react add C123 1234567890.123456 thumbsup".to_string(),
620            }],
621            exit_codes: vec![
622                ExitCodeDef {
623                    code: 0,
624                    description: "Success".to_string(),
625                },
626                ExitCodeDef {
627                    code: 1,
628                    description: "Command failed".to_string(),
629                },
630            ],
631        },
632        // react remove
633        CommandDef {
634            name: "react remove".to_string(),
635            description: "Remove a reaction from a message".to_string(),
636            usage: "slack-rs react remove <channel> <ts> <emoji> [flags]".to_string(),
637            flags: vec![
638                FlagDef {
639                    name: "--profile".to_string(),
640                    flag_type: "string".to_string(),
641                    required: false,
642                    description: "Profile name".to_string(),
643                    default: Some("default".to_string()),
644                },
645                FlagDef {
646                    name: "--idempotency-key".to_string(),
647                    flag_type: "string".to_string(),
648                    required: false,
649                    description: "Idempotency key for preventing duplicate operations".to_string(),
650                    default: None,
651                },
652            ],
653            examples: vec![ExampleDef {
654                description: "Remove reaction".to_string(),
655                command: "slack-rs react remove C123 1234567890.123456 thumbsup".to_string(),
656            }],
657            exit_codes: vec![
658                ExitCodeDef {
659                    code: 0,
660                    description: "Success".to_string(),
661                },
662                ExitCodeDef {
663                    code: 1,
664                    description: "Command failed".to_string(),
665                },
666            ],
667        },
668        // file upload
669        CommandDef {
670            name: "file upload".to_string(),
671            description: "Upload a file".to_string(),
672            usage: "slack-rs file upload <path> [flags]".to_string(),
673            flags: vec![
674                FlagDef {
675                    name: "--profile".to_string(),
676                    flag_type: "string".to_string(),
677                    required: false,
678                    description: "Profile name".to_string(),
679                    default: Some("default".to_string()),
680                },
681                FlagDef {
682                    name: "--idempotency-key".to_string(),
683                    flag_type: "string".to_string(),
684                    required: false,
685                    description: "Idempotency key for preventing duplicate operations".to_string(),
686                    default: None,
687                },
688            ],
689            examples: vec![ExampleDef {
690                description: "Upload file".to_string(),
691                command: "slack-rs file upload document.pdf".to_string(),
692            }],
693            exit_codes: vec![
694                ExitCodeDef {
695                    code: 0,
696                    description: "Success".to_string(),
697                },
698                ExitCodeDef {
699                    code: 1,
700                    description: "Upload failed".to_string(),
701                },
702            ],
703        },
704        // file download
705        CommandDef {
706            name: "file download".to_string(),
707            description: "Download a file from Slack".to_string(),
708            usage: "slack-rs file download [<file_id>] [flags]".to_string(),
709            flags: vec![
710                FlagDef {
711                    name: "--url".to_string(),
712                    flag_type: "string".to_string(),
713                    required: false,
714                    description: "Direct download URL (alternative to file_id)".to_string(),
715                    default: None,
716                },
717                FlagDef {
718                    name: "--out".to_string(),
719                    flag_type: "string".to_string(),
720                    required: false,
721                    description: "Output path (omit for current directory, '-' for stdout, directory for auto-naming)".to_string(),
722                    default: None,
723                },
724                FlagDef {
725                    name: "--profile".to_string(),
726                    flag_type: "string".to_string(),
727                    required: false,
728                    description: "Profile name".to_string(),
729                    default: Some("default".to_string()),
730                },
731                FlagDef {
732                    name: "--token-type".to_string(),
733                    flag_type: "string".to_string(),
734                    required: false,
735                    description: "Token type (bot or user)".to_string(),
736                    default: None,
737                },
738            ],
739            examples: vec![
740                ExampleDef {
741                    description: "Download by file ID".to_string(),
742                    command: "slack-rs file download F123456".to_string(),
743                },
744                ExampleDef {
745                    description: "Download to stdout".to_string(),
746                    command: "slack-rs file download F123456 --out -".to_string(),
747                },
748                ExampleDef {
749                    description: "Download by URL".to_string(),
750                    command: "slack-rs file download --url https://files.slack.com/...".to_string(),
751                },
752            ],
753            exit_codes: vec![
754                ExitCodeDef {
755                    code: 0,
756                    description: "Success".to_string(),
757                },
758                ExitCodeDef {
759                    code: 1,
760                    description: "Download failed".to_string(),
761                },
762            ],
763        },
764        // search
765        CommandDef {
766            name: "search".to_string(),
767            description: "Search messages".to_string(),
768            usage: "slack-rs search <query> [flags]".to_string(),
769            flags: vec![
770                FlagDef {
771                    name: "--count".to_string(),
772                    flag_type: "integer".to_string(),
773                    required: false,
774                    description: "Number of results".to_string(),
775                    default: None,
776                },
777                FlagDef {
778                    name: "--page".to_string(),
779                    flag_type: "integer".to_string(),
780                    required: false,
781                    description: "Page number".to_string(),
782                    default: None,
783                },
784                FlagDef {
785                    name: "--profile".to_string(),
786                    flag_type: "string".to_string(),
787                    required: false,
788                    description: "Profile name".to_string(),
789                    default: Some("default".to_string()),
790                },
791            ],
792            examples: vec![ExampleDef {
793                description: "Search messages".to_string(),
794                command: "slack-rs search 'important announcement'".to_string(),
795            }],
796            exit_codes: vec![
797                ExitCodeDef {
798                    code: 0,
799                    description: "Success".to_string(),
800                },
801                ExitCodeDef {
802                    code: 1,
803                    description: "Search failed".to_string(),
804                },
805            ],
806        },
807        // auth rename
808        CommandDef {
809            name: "auth rename".to_string(),
810            description: "Rename a profile".to_string(),
811            usage: "slack-rs auth rename <old_name> <new_name>".to_string(),
812            flags: vec![],
813            examples: vec![ExampleDef {
814                description: "Rename profile".to_string(),
815                command: "slack-rs auth rename work personal".to_string(),
816            }],
817            exit_codes: vec![
818                ExitCodeDef {
819                    code: 0,
820                    description: "Success".to_string(),
821                },
822                ExitCodeDef {
823                    code: 1,
824                    description: "Rename failed".to_string(),
825                },
826            ],
827        },
828        // auth export
829        CommandDef {
830            name: "auth export".to_string(),
831            description: "Export profiles to encrypted file".to_string(),
832            usage: "slack-rs auth export [flags]".to_string(),
833            flags: vec![
834                FlagDef {
835                    name: "--profile".to_string(),
836                    flag_type: "string".to_string(),
837                    required: false,
838                    description: "Export specific profile".to_string(),
839                    default: Some("default".to_string()),
840                },
841                FlagDef {
842                    name: "--all".to_string(),
843                    flag_type: "boolean".to_string(),
844                    required: false,
845                    description: "Export all profiles".to_string(),
846                    default: None,
847                },
848                FlagDef {
849                    name: "--out".to_string(),
850                    flag_type: "string".to_string(),
851                    required: true,
852                    description: "Output file path".to_string(),
853                    default: None,
854                },
855                FlagDef {
856                    name: "--passphrase-env".to_string(),
857                    flag_type: "string".to_string(),
858                    required: false,
859                    description: "Environment variable containing passphrase".to_string(),
860                    default: None,
861                },
862                FlagDef {
863                    name: "--passphrase-prompt".to_string(),
864                    flag_type: "boolean".to_string(),
865                    required: false,
866                    description: "Prompt for passphrase".to_string(),
867                    default: None,
868                },
869                FlagDef {
870                    name: "--yes".to_string(),
871                    flag_type: "boolean".to_string(),
872                    required: false,
873                    description: "Confirm dangerous operation".to_string(),
874                    default: None,
875                },
876            ],
877            examples: vec![ExampleDef {
878                description: "Export all profiles".to_string(),
879                command: "slack-rs auth export --all --out profiles.enc --yes".to_string(),
880            }],
881            exit_codes: vec![
882                ExitCodeDef {
883                    code: 0,
884                    description: "Success".to_string(),
885                },
886                ExitCodeDef {
887                    code: 1,
888                    description: "Export failed".to_string(),
889                },
890            ],
891        },
892        // auth import
893        CommandDef {
894            name: "auth import".to_string(),
895            description: "Import profiles from encrypted file".to_string(),
896            usage: "slack-rs auth import [flags]".to_string(),
897            flags: vec![
898                FlagDef {
899                    name: "--in".to_string(),
900                    flag_type: "string".to_string(),
901                    required: true,
902                    description: "Input file path".to_string(),
903                    default: None,
904                },
905                FlagDef {
906                    name: "--passphrase-env".to_string(),
907                    flag_type: "string".to_string(),
908                    required: false,
909                    description: "Environment variable containing passphrase".to_string(),
910                    default: None,
911                },
912                FlagDef {
913                    name: "--passphrase-prompt".to_string(),
914                    flag_type: "boolean".to_string(),
915                    required: false,
916                    description: "Prompt for passphrase".to_string(),
917                    default: None,
918                },
919                FlagDef {
920                    name: "--yes".to_string(),
921                    flag_type: "boolean".to_string(),
922                    required: false,
923                    description: "Automatically accept conflicts".to_string(),
924                    default: None,
925                },
926                FlagDef {
927                    name: "--force".to_string(),
928                    flag_type: "boolean".to_string(),
929                    required: false,
930                    description: "Overwrite existing profiles".to_string(),
931                    default: None,
932                },
933                FlagDef {
934                    name: "--dry-run".to_string(),
935                    flag_type: "boolean".to_string(),
936                    required: false,
937                    description: "Show what would be imported without making changes".to_string(),
938                    default: None,
939                },
940                FlagDef {
941                    name: "--json".to_string(),
942                    flag_type: "boolean".to_string(),
943                    required: false,
944                    description: "Output results in JSON format".to_string(),
945                    default: None,
946                },
947            ],
948            examples: vec![
949                ExampleDef {
950                    description: "Import profiles".to_string(),
951                    command: "slack-rs auth import --in profiles.enc".to_string(),
952                },
953                ExampleDef {
954                    description: "Preview import without making changes".to_string(),
955                    command: "slack-rs auth import --in profiles.enc --dry-run".to_string(),
956                },
957                ExampleDef {
958                    description: "Preview import with JSON output".to_string(),
959                    command: "slack-rs auth import --in profiles.enc --dry-run --json".to_string(),
960                },
961            ],
962            exit_codes: vec![
963                ExitCodeDef {
964                    code: 0,
965                    description: "Success".to_string(),
966                },
967                ExitCodeDef {
968                    code: 1,
969                    description: "Import failed".to_string(),
970                },
971            ],
972        },
973        // config oauth set
974        CommandDef {
975            name: "config oauth set".to_string(),
976            description: "Set OAuth configuration for a profile".to_string(),
977            usage: "slack-rs config oauth set <profile> --client-id <id> --redirect-uri <uri> --scopes <scopes> [flags]".to_string(),
978            flags: vec![
979                FlagDef {
980                    name: "--client-id".to_string(),
981                    flag_type: "string".to_string(),
982                    required: true,
983                    description: "OAuth client ID".to_string(),
984                    default: None,
985                },
986                FlagDef {
987                    name: "--redirect-uri".to_string(),
988                    flag_type: "string".to_string(),
989                    required: true,
990                    description: "OAuth redirect URI".to_string(),
991                    default: None,
992                },
993                FlagDef {
994                    name: "--scopes".to_string(),
995                    flag_type: "string".to_string(),
996                    required: true,
997                    description: "Comma-separated list of scopes or 'all'".to_string(),
998                    default: None,
999                },
1000                FlagDef {
1001                    name: "--client-secret-env".to_string(),
1002                    flag_type: "string".to_string(),
1003                    required: false,
1004                    description: "Read secret from environment variable".to_string(),
1005                    default: None,
1006                },
1007                FlagDef {
1008                    name: "--client-secret-file".to_string(),
1009                    flag_type: "string".to_string(),
1010                    required: false,
1011                    description: "Read secret from file".to_string(),
1012                    default: None,
1013                },
1014                FlagDef {
1015                    name: "--client-secret".to_string(),
1016                    flag_type: "string".to_string(),
1017                    required: false,
1018                    description: "Direct secret value (requires --yes, unsafe)".to_string(),
1019                    default: None,
1020                },
1021                FlagDef {
1022                    name: "--yes".to_string(),
1023                    flag_type: "boolean".to_string(),
1024                    required: false,
1025                    description: "Confirm dangerous operation".to_string(),
1026                    default: None,
1027                },
1028            ],
1029            examples: vec![ExampleDef {
1030                description: "Set OAuth config".to_string(),
1031                command: "slack-rs config oauth set work --client-id 123.456 --redirect-uri http://127.0.0.1:8765/callback --scopes all".to_string(),
1032            }],
1033            exit_codes: vec![
1034                ExitCodeDef {
1035                    code: 0,
1036                    description: "Success".to_string(),
1037                },
1038                ExitCodeDef {
1039                    code: 1,
1040                    description: "Config set failed".to_string(),
1041                },
1042            ],
1043        },
1044        // config oauth show
1045        CommandDef {
1046            name: "config oauth show".to_string(),
1047            description: "Show OAuth configuration for a profile".to_string(),
1048            usage: "slack-rs config oauth show <profile>".to_string(),
1049            flags: vec![],
1050            examples: vec![ExampleDef {
1051                description: "Show OAuth config".to_string(),
1052                command: "slack-rs config oauth show work".to_string(),
1053            }],
1054            exit_codes: vec![
1055                ExitCodeDef {
1056                    code: 0,
1057                    description: "Success".to_string(),
1058                },
1059                ExitCodeDef {
1060                    code: 1,
1061                    description: "Config show failed".to_string(),
1062                },
1063            ],
1064        },
1065        // config oauth delete
1066        CommandDef {
1067            name: "config oauth delete".to_string(),
1068            description: "Delete OAuth configuration for a profile".to_string(),
1069            usage: "slack-rs config oauth delete <profile>".to_string(),
1070            flags: vec![],
1071            examples: vec![ExampleDef {
1072                description: "Delete OAuth config".to_string(),
1073                command: "slack-rs config oauth delete work".to_string(),
1074            }],
1075            exit_codes: vec![
1076                ExitCodeDef {
1077                    code: 0,
1078                    description: "Success".to_string(),
1079                },
1080                ExitCodeDef {
1081                    code: 1,
1082                    description: "Config delete failed".to_string(),
1083                },
1084            ],
1085        },
1086        // config set
1087        CommandDef {
1088            name: "config set".to_string(),
1089            description: "Set default token type for a profile".to_string(),
1090            usage: "slack-rs config set <profile> --token-type <type>".to_string(),
1091            flags: vec![FlagDef {
1092                name: "--token-type".to_string(),
1093                flag_type: "string".to_string(),
1094                required: true,
1095                description: "Default token type (bot or user)".to_string(),
1096                default: None,
1097            }],
1098            examples: vec![ExampleDef {
1099                description: "Set token type".to_string(),
1100                command: "slack-rs config set work --token-type bot".to_string(),
1101            }],
1102            exit_codes: vec![
1103                ExitCodeDef {
1104                    code: 0,
1105                    description: "Success".to_string(),
1106                },
1107                ExitCodeDef {
1108                    code: 1,
1109                    description: "Config set failed".to_string(),
1110                },
1111            ],
1112        },
1113        // conv select
1114        CommandDef {
1115            name: "conv select".to_string(),
1116            description: "Interactively select a conversation".to_string(),
1117            usage: "slack-rs conv select [flags]".to_string(),
1118            flags: vec![FlagDef {
1119                name: "--profile".to_string(),
1120                flag_type: "string".to_string(),
1121                required: false,
1122                description: "Profile name".to_string(),
1123                default: Some("default".to_string()),
1124            }],
1125            examples: vec![ExampleDef {
1126                description: "Select conversation".to_string(),
1127                command: "slack-rs conv select".to_string(),
1128            }],
1129            exit_codes: vec![
1130                ExitCodeDef {
1131                    code: 0,
1132                    description: "Success".to_string(),
1133                },
1134                ExitCodeDef {
1135                    code: 1,
1136                    description: "Selection failed".to_string(),
1137                },
1138            ],
1139        },
1140        // users cache-update
1141        CommandDef {
1142            name: "users cache-update".to_string(),
1143            description: "Update user cache for mention resolution".to_string(),
1144            usage: "slack-rs users cache-update [flags]".to_string(),
1145            flags: vec![
1146                FlagDef {
1147                    name: "--profile".to_string(),
1148                    flag_type: "string".to_string(),
1149                    required: false,
1150                    description: "Profile name".to_string(),
1151                    default: Some("default".to_string()),
1152                },
1153                FlagDef {
1154                    name: "--force".to_string(),
1155                    flag_type: "boolean".to_string(),
1156                    required: false,
1157                    description: "Force cache update".to_string(),
1158                    default: None,
1159                },
1160            ],
1161            examples: vec![ExampleDef {
1162                description: "Update user cache".to_string(),
1163                command: "slack-rs users cache-update".to_string(),
1164            }],
1165            exit_codes: vec![
1166                ExitCodeDef {
1167                    code: 0,
1168                    description: "Success".to_string(),
1169                },
1170                ExitCodeDef {
1171                    code: 1,
1172                    description: "Cache update failed".to_string(),
1173                },
1174            ],
1175        },
1176        // users resolve-mentions
1177        CommandDef {
1178            name: "users resolve-mentions".to_string(),
1179            description: "Resolve user mentions in text".to_string(),
1180            usage: "slack-rs users resolve-mentions <text> [flags]".to_string(),
1181            flags: vec![
1182                FlagDef {
1183                    name: "--profile".to_string(),
1184                    flag_type: "string".to_string(),
1185                    required: false,
1186                    description: "Profile name".to_string(),
1187                    default: Some("default".to_string()),
1188                },
1189                FlagDef {
1190                    name: "--format".to_string(),
1191                    flag_type: "string".to_string(),
1192                    required: false,
1193                    description: "Output format".to_string(),
1194                    default: None,
1195                },
1196            ],
1197            examples: vec![ExampleDef {
1198                description: "Resolve mentions".to_string(),
1199                command: "slack-rs users resolve-mentions '@john said hello'".to_string(),
1200            }],
1201            exit_codes: vec![
1202                ExitCodeDef {
1203                    code: 0,
1204                    description: "Success".to_string(),
1205                },
1206                ExitCodeDef {
1207                    code: 1,
1208                    description: "Resolution failed".to_string(),
1209                },
1210            ],
1211        },
1212        // commands
1213        CommandDef {
1214            name: "commands".to_string(),
1215            description: "List all available commands in machine-readable format".to_string(),
1216            usage: "slack-rs commands --json".to_string(),
1217            flags: vec![FlagDef {
1218                name: "--json".to_string(),
1219                flag_type: "boolean".to_string(),
1220                required: true,
1221                description: "Output in JSON format".to_string(),
1222                default: None,
1223            }],
1224            examples: vec![ExampleDef {
1225                description: "List commands".to_string(),
1226                command: "slack-rs commands --json".to_string(),
1227            }],
1228            exit_codes: vec![
1229                ExitCodeDef {
1230                    code: 0,
1231                    description: "Success".to_string(),
1232                },
1233                ExitCodeDef {
1234                    code: 1,
1235                    description: "Command failed".to_string(),
1236                },
1237            ],
1238        },
1239        // schema
1240        CommandDef {
1241            name: "schema".to_string(),
1242            description: "Show output schema for a command".to_string(),
1243            usage: "slack-rs schema --command <cmd> --output json-schema".to_string(),
1244            flags: vec![
1245                FlagDef {
1246                    name: "--command".to_string(),
1247                    flag_type: "string".to_string(),
1248                    required: true,
1249                    description: "Command name".to_string(),
1250                    default: None,
1251                },
1252                FlagDef {
1253                    name: "--output".to_string(),
1254                    flag_type: "string".to_string(),
1255                    required: true,
1256                    description: "Output format (json-schema)".to_string(),
1257                    default: None,
1258                },
1259            ],
1260            examples: vec![ExampleDef {
1261                description: "Show schema".to_string(),
1262                command: "slack-rs schema --command conv.list --output json-schema".to_string(),
1263            }],
1264            exit_codes: vec![
1265                ExitCodeDef {
1266                    code: 0,
1267                    description: "Success".to_string(),
1268                },
1269                ExitCodeDef {
1270                    code: 1,
1271                    description: "Schema generation failed".to_string(),
1272                },
1273            ],
1274        },
1275        // install-skills
1276        CommandDef {
1277            name: "install-skills".to_string(),
1278            description: "Install agent skill from embedded or local source".to_string(),
1279            usage: "slack-rs install-skills [source] [--global]".to_string(),
1280            flags: vec![
1281                FlagDef {
1282                    name: "source".to_string(),
1283                    flag_type: "string".to_string(),
1284                    required: false,
1285                    description: "Source to install from: 'self' (embedded) or 'local:<path>'".to_string(),
1286                    default: Some("self".to_string()),
1287                },
1288                FlagDef {
1289                    name: "--global".to_string(),
1290                    flag_type: "boolean".to_string(),
1291                    required: false,
1292                    description: "Install to ~/.agents instead of ./.agents".to_string(),
1293                    default: Some("false".to_string()),
1294                },
1295            ],
1296            examples: vec![
1297                ExampleDef {
1298                    description: "Install embedded skill (default)".to_string(),
1299                    command: "slack-rs install-skills".to_string(),
1300                },
1301                ExampleDef {
1302                    description: "Install from local path".to_string(),
1303                    command: "slack-rs install-skills local:/path/to/skill".to_string(),
1304                },
1305                ExampleDef {
1306                    description: "Install globally to ~/.agents".to_string(),
1307                    command: "slack-rs install-skills --global".to_string(),
1308                },
1309            ],
1310            exit_codes: vec![
1311                ExitCodeDef {
1312                    code: 0,
1313                    description: "Success - skill installed".to_string(),
1314                },
1315                ExitCodeDef {
1316                    code: 1,
1317                    description: "Failure - installation error".to_string(),
1318                },
1319            ],
1320        },
1321        // demo
1322        CommandDef {
1323            name: "demo".to_string(),
1324            description: "Run demonstration".to_string(),
1325            usage: "slack-rs demo".to_string(),
1326            flags: vec![],
1327            examples: vec![ExampleDef {
1328                description: "Run demo".to_string(),
1329                command: "slack-rs demo".to_string(),
1330            }],
1331            exit_codes: vec![
1332                ExitCodeDef {
1333                    code: 0,
1334                    description: "Success".to_string(),
1335                },
1336            ],
1337        },
1338    ]
1339}
1340
1341/// Normalize command name (converts "conv.list" to "conv list", etc.)
1342fn normalize_command_name(name: &str) -> String {
1343    // Replace dots with spaces for consistent lookup
1344    name.replace('.', " ")
1345}
1346
1347/// Get command definition by name
1348/// Supports both space-separated ("conv list") and dot-separated ("conv.list") formats
1349pub fn get_command_definition(command_name: &str) -> Option<CommandDef> {
1350    let normalized = normalize_command_name(command_name);
1351    get_command_definitions()
1352        .into_iter()
1353        .find(|cmd| cmd.name == normalized)
1354}
1355
1356/// Generate commands list response
1357pub fn generate_commands_list() -> CommandsListResponse {
1358    CommandsListResponse {
1359        schema_version: 1,
1360        response_type: "commands.list".to_string(),
1361        ok: true,
1362        commands: get_command_definitions(),
1363    }
1364}
1365
1366/// Generate structured help for a command
1367pub fn generate_help(command_name: &str) -> Result<HelpResponse, String> {
1368    let cmd = get_command_definition(command_name)
1369        .ok_or_else(|| format!("Command '{}' not found", command_name))?;
1370
1371    Ok(HelpResponse {
1372        schema_version: 1,
1373        response_type: "help".to_string(),
1374        ok: true,
1375        command: cmd.name.clone(),
1376        usage: cmd.usage.clone(),
1377        flags: cmd.flags.clone(),
1378        examples: cmd.examples.clone(),
1379        exit_codes: cmd.exit_codes.clone(),
1380    })
1381}
1382
1383/// Generate JSON schema for a command's output
1384pub fn generate_schema(command_name: &str) -> Result<SchemaResponse, String> {
1385    // Verify command exists
1386    let _cmd = get_command_definition(command_name)
1387        .ok_or_else(|| format!("Command '{}' not found", command_name))?;
1388
1389    // Special case for install-skills
1390    let schema = if command_name == "install-skills" {
1391        serde_json::json!({
1392            "$schema": "http://json-schema.org/draft-07/schema#",
1393            "type": "object",
1394            "properties": {
1395                "schemaVersion": {
1396                    "type": "string",
1397                    "description": "Schema version number"
1398                },
1399                "type": {
1400                    "type": "string",
1401                    "description": "Response type identifier",
1402                    "const": "skill-installation"
1403                },
1404                "ok": {
1405                    "type": "boolean",
1406                    "description": "Indicates if the operation was successful"
1407                },
1408                "skills": {
1409                    "type": "array",
1410                    "description": "List of installed skills",
1411                    "items": {
1412                        "type": "object",
1413                        "properties": {
1414                            "name": {
1415                                "type": "string",
1416                                "description": "Skill name"
1417                            },
1418                            "path": {
1419                                "type": "string",
1420                                "description": "Installation path"
1421                            },
1422                            "source_type": {
1423                                "type": "string",
1424                                "description": "Source type (self or local)"
1425                            }
1426                        },
1427                        "required": ["name", "path", "source_type"]
1428                    }
1429                }
1430            },
1431            "required": ["schemaVersion", "type", "ok", "skills"]
1432        })
1433    } else {
1434        // Generate basic envelope schema for other commands
1435        serde_json::json!({
1436            "$schema": "http://json-schema.org/draft-07/schema#",
1437            "type": "object",
1438            "properties": {
1439                "schemaVersion": {
1440                    "type": "integer",
1441                    "description": "Schema version number"
1442                },
1443                "type": {
1444                    "type": "string",
1445                    "description": "Response type identifier"
1446                },
1447                "ok": {
1448                    "type": "boolean",
1449                    "description": "Indicates if the operation was successful"
1450                },
1451                "response": {
1452                    "type": "object",
1453                    "description": "Slack API response data"
1454                },
1455                "meta": {
1456                    "type": "object",
1457                    "description": "Metadata about the request and profile",
1458                    "properties": {
1459                        "profile": {"type": "string"},
1460                        "team_id": {"type": "string"},
1461                        "user_id": {"type": "string"},
1462                        "method": {"type": "string"},
1463                        "command": {"type": "string"}
1464                    }
1465                }
1466            },
1467            "required": ["schemaVersion", "type", "ok"]
1468        })
1469    };
1470
1471    Ok(SchemaResponse {
1472        schema_version: 1,
1473        response_type: "schema".to_string(),
1474        ok: true,
1475        command: command_name.to_string(),
1476        schema,
1477    })
1478}
1479
1480#[cfg(test)]
1481mod tests {
1482    use super::*;
1483
1484    #[test]
1485    fn test_get_command_definitions() {
1486        let commands = get_command_definitions();
1487        assert!(!commands.is_empty());
1488        assert!(commands.iter().any(|c| c.name == "api call"));
1489        assert!(commands.iter().any(|c| c.name == "conv list"));
1490    }
1491
1492    #[test]
1493    fn test_get_command_definition() {
1494        let cmd = get_command_definition("conv list");
1495        assert!(cmd.is_some());
1496        let cmd = cmd.unwrap();
1497        assert_eq!(cmd.name, "conv list");
1498        assert!(!cmd.flags.is_empty());
1499    }
1500
1501    #[test]
1502    fn test_generate_commands_list() {
1503        let response = generate_commands_list();
1504        assert_eq!(response.schema_version, 1);
1505        assert_eq!(response.response_type, "commands.list");
1506        assert!(response.ok);
1507        assert!(!response.commands.is_empty());
1508    }
1509
1510    #[test]
1511    fn test_generate_help() {
1512        let help = generate_help("conv list");
1513        assert!(help.is_ok());
1514        let help = help.unwrap();
1515        assert_eq!(help.schema_version, 1);
1516        assert_eq!(help.response_type, "help");
1517        assert!(help.ok);
1518        assert_eq!(help.command, "conv list");
1519    }
1520
1521    #[test]
1522    fn test_generate_help_unknown_command() {
1523        let help = generate_help("unknown command");
1524        assert!(help.is_err());
1525    }
1526
1527    #[test]
1528    fn test_generate_schema() {
1529        let schema = generate_schema("conv list");
1530        assert!(schema.is_ok());
1531        let schema = schema.unwrap();
1532        assert_eq!(schema.schema_version, 1);
1533        assert_eq!(schema.response_type, "schema");
1534        assert!(schema.ok);
1535        assert_eq!(schema.command, "conv list");
1536    }
1537
1538    #[test]
1539    fn test_commands_list_json_serialization() {
1540        let response = generate_commands_list();
1541        let json = serde_json::to_string(&response);
1542        assert!(json.is_ok());
1543
1544        // Verify it can be parsed back
1545        let parsed: Result<CommandsListResponse, _> = serde_json::from_str(&json.unwrap());
1546        assert!(parsed.is_ok());
1547    }
1548
1549    #[test]
1550    fn test_help_json_serialization() {
1551        let help = generate_help("conv list").unwrap();
1552        let json = serde_json::to_string(&help);
1553        assert!(json.is_ok());
1554
1555        // Verify it can be parsed back
1556        let parsed: Result<HelpResponse, _> = serde_json::from_str(&json.unwrap());
1557        assert!(parsed.is_ok());
1558    }
1559
1560    #[test]
1561    fn test_schema_json_serialization() {
1562        let schema = generate_schema("conv list").unwrap();
1563        let json = serde_json::to_string(&schema);
1564        assert!(json.is_ok());
1565
1566        // Verify it can be parsed back
1567        let parsed: Result<SchemaResponse, _> = serde_json::from_str(&json.unwrap());
1568        assert!(parsed.is_ok());
1569    }
1570}