Skip to main content

teams_cli/
cli.rs

1use clap::{Args, Parser, Subcommand};
2use clap_complete::Shell;
3
4use crate::output::OutputFormat;
5
6#[derive(Debug, Parser)]
7#[command(
8    name = "teams",
9    version,
10    about = "Microsoft Teams from your terminal, for humans and agents",
11    after_help = "Get started:\n  teams init                         Guided setup\n  teams tui --demo                   Explore without an account\n  teams doctor                       Diagnose configuration and access\n  teams schema --command 'messages send'\n                                     Inspect the automation contract"
12)]
13pub struct Cli {
14    /// Config profile to use
15    #[arg(long, global = true, env = "TEAMS_PROFILE")]
16    pub profile: Option<String>,
17    /// Output format; auto is text on a terminal and JSON when piped
18    #[arg(short = 'o', long, global = true, value_enum, default_value = "auto")]
19    pub output: OutputFormat,
20    /// Suppress progress and informational messages
21    #[arg(long, global = true)]
22    pub quiet: bool,
23    /// Alias for --output json
24    #[arg(long, global = true, hide = true)]
25    pub json: bool,
26    #[command(subcommand)]
27    pub command: Option<Command>,
28}
29
30#[derive(Debug, Subcommand)]
31pub enum Command {
32    /// Guided Microsoft Entra app and account setup
33    Init(InitArgs),
34    /// Manage delegated Microsoft authentication
35    Auth {
36        #[command(subcommand)]
37        command: AuthCommand,
38    },
39    /// Inspect resolved, secret-free configuration
40    Config {
41        #[command(subcommand)]
42        command: ConfigCommand,
43    },
44    /// Show the signed-in Microsoft 365 identity
45    Whoami,
46    /// List teams joined by the signed-in user
47    List(OffsetPageArgs),
48    /// Work with team channels
49    Channels {
50        #[command(subcommand)]
51        command: ChannelsCommand,
52    },
53    /// Work with one-to-one and group chats
54    Chats {
55        #[command(subcommand)]
56        command: ChatsCommand,
57    },
58    /// Read and send messages
59    Messages {
60        #[command(subcommand)]
61        command: MessagesCommand,
62    },
63    /// Open the keyboard-first terminal interface
64    Tui(TuiArgs),
65    /// Check configuration, credential storage, and Graph access
66    Doctor,
67    /// Describe supported and deliberately unsupported capabilities
68    Capabilities,
69    /// Emit the offline CLI Spec v0.3 contract
70    Schema {
71        #[arg(long)]
72        command: Option<String>,
73    },
74    /// Generate shell completions
75    Completions { shell: Shell },
76}
77
78#[derive(Debug, Args)]
79pub struct InitArgs {
80    /// Override the maintained teams-cli public-client application ID
81    #[arg(long, env = "TEAMS_CLIENT_ID")]
82    pub client_id: Option<String>,
83    /// Tenant ID/domain, or `organizations`
84    #[arg(long, default_value = "organizations", env = "TEAMS_TENANT")]
85    pub tenant: String,
86    /// Save configuration without starting sign-in
87    #[arg(long)]
88    pub no_login: bool,
89    /// Use device-code sign-in instead of browser PKCE
90    #[arg(long)]
91    pub device_code: bool,
92    /// Request admin-consented access to read channel history
93    #[arg(long)]
94    pub channel_history: bool,
95    /// Block remote write operations for this profile
96    #[arg(long, env = "TEAMS_READ_ONLY")]
97    pub read_only: bool,
98}
99
100#[derive(Debug, Subcommand)]
101pub enum ConfigCommand {
102    /// Print the resolved profile without credentials
103    Show,
104    /// Print the absolute configuration file path
105    Path,
106}
107
108#[derive(Debug, Subcommand)]
109pub enum AuthCommand {
110    /// Sign in with delegated OAuth
111    Login {
112        /// Use device-code sign-in for headless and remote environments
113        #[arg(long)]
114        device_code: bool,
115        /// Request admin-consented access to read channel history
116        #[arg(long)]
117        channel_history: bool,
118    },
119    /// Remove locally stored credentials
120    Logout,
121    /// Show local authentication status without network access
122    Status,
123}
124
125#[derive(Debug, Subcommand)]
126pub enum ChannelsCommand {
127    /// List channels in a team
128    List {
129        team: String,
130        #[command(flatten)]
131        page: OffsetPageArgs,
132    },
133}
134
135#[derive(Debug, Subcommand)]
136pub enum ChatsCommand {
137    List(PageArgs),
138}
139
140#[derive(Debug, Args, Clone)]
141pub struct PageArgs {
142    /// Maximum records in this page
143    #[arg(long, default_value_t = 50, value_parser = clap::value_parser!(u16).range(1..=50))]
144    pub limit: u16,
145    /// Opaque continuation URL from a previous response
146    #[arg(long)]
147    pub cursor: Option<String>,
148    /// Comma-separated output fields
149    #[arg(long)]
150    pub fields: Option<String>,
151}
152
153#[derive(Debug, Args, Clone)]
154pub struct OffsetPageArgs {
155    /// Maximum records in this page
156    #[arg(long, default_value_t = 50, value_parser = clap::value_parser!(u16).range(1..=100))]
157    pub limit: u16,
158    /// Number of records to skip
159    #[arg(long, default_value_t = 0)]
160    pub offset: usize,
161    /// Comma-separated output fields
162    #[arg(long)]
163    pub fields: Option<String>,
164}
165
166#[derive(Debug, Subcommand)]
167pub enum MessagesCommand {
168    /// List messages from a chat or channel
169    List {
170        #[arg(long, conflicts_with = "team", required_unless_present = "team")]
171        chat: Option<String>,
172        #[arg(long, requires = "channel", required_unless_present = "chat")]
173        team: Option<String>,
174        #[arg(long, requires = "team")]
175        channel: Option<String>,
176        #[command(flatten)]
177        page: PageArgs,
178    },
179    /// Send a message as the signed-in user
180    Send {
181        #[arg(long, conflicts_with = "team", required_unless_present = "team")]
182        chat: Option<String>,
183        #[arg(long, requires = "channel", required_unless_present = "chat")]
184        team: Option<String>,
185        #[arg(long, requires = "team")]
186        channel: Option<String>,
187        /// Plain-text message body; use '-' to read stdin
188        #[arg(long)]
189        body: String,
190    },
191}
192
193#[derive(Debug, Args)]
194pub struct TuiArgs {
195    /// Use deterministic sample data; no account or network required
196    #[arg(long)]
197    pub demo: bool,
198    /// Print one ANSI-free frame instead of taking over the terminal
199    #[arg(long, requires = "demo")]
200    pub snapshot: bool,
201    #[arg(long, default_value_t = 100, hide = true)]
202    pub width: u16,
203    #[arg(long, default_value_t = 30, hide = true)]
204    pub height: u16,
205}