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    /// Show the signed-in Microsoft 365 identity
40    Whoami,
41    /// List teams joined by the signed-in user
42    List(OffsetPageArgs),
43    /// Work with team channels
44    Channels {
45        #[command(subcommand)]
46        command: ChannelsCommand,
47    },
48    /// Work with one-to-one and group chats
49    Chats {
50        #[command(subcommand)]
51        command: ChatsCommand,
52    },
53    /// Read and send messages
54    Messages {
55        #[command(subcommand)]
56        command: MessagesCommand,
57    },
58    /// Open the keyboard-first terminal interface
59    Tui(TuiArgs),
60    /// Check configuration, credential storage, and Graph access
61    Doctor,
62    /// Describe supported and deliberately unsupported capabilities
63    Capabilities,
64    /// Emit the offline CLI Spec v0.3 contract
65    Schema {
66        #[arg(long)]
67        command: Option<String>,
68    },
69    /// Generate shell completions
70    Completions { shell: Shell },
71}
72
73#[derive(Debug, Args)]
74pub struct InitArgs {
75    /// Override the maintained teams-cli public-client application ID
76    #[arg(long, env = "TEAMS_CLIENT_ID")]
77    pub client_id: Option<String>,
78    /// Tenant ID/domain, or `organizations`
79    #[arg(long, default_value = "organizations", env = "TEAMS_TENANT")]
80    pub tenant: String,
81    /// Save configuration without starting sign-in
82    #[arg(long)]
83    pub no_login: bool,
84    /// Use device-code sign-in instead of browser PKCE
85    #[arg(long)]
86    pub device_code: bool,
87    /// Request admin-consented access to read channel history
88    #[arg(long)]
89    pub channel_history: bool,
90}
91
92#[derive(Debug, Subcommand)]
93pub enum AuthCommand {
94    /// Sign in with delegated OAuth
95    Login {
96        /// Use device-code sign-in for headless and remote environments
97        #[arg(long)]
98        device_code: bool,
99        /// Request admin-consented access to read channel history
100        #[arg(long)]
101        channel_history: bool,
102    },
103    /// Remove locally stored credentials
104    Logout,
105    /// Show local authentication status without network access
106    Status,
107}
108
109#[derive(Debug, Subcommand)]
110pub enum ChannelsCommand {
111    /// List channels in a team
112    List {
113        team: String,
114        #[command(flatten)]
115        page: OffsetPageArgs,
116    },
117}
118
119#[derive(Debug, Subcommand)]
120pub enum ChatsCommand {
121    List(PageArgs),
122}
123
124#[derive(Debug, Args, Clone)]
125pub struct PageArgs {
126    /// Maximum records in this page
127    #[arg(long, default_value_t = 50, value_parser = clap::value_parser!(u16).range(1..=50))]
128    pub limit: u16,
129    /// Opaque continuation URL from a previous response
130    #[arg(long)]
131    pub cursor: Option<String>,
132    /// Comma-separated output fields
133    #[arg(long)]
134    pub fields: Option<String>,
135}
136
137#[derive(Debug, Args, Clone)]
138pub struct OffsetPageArgs {
139    /// Maximum records in this page
140    #[arg(long, default_value_t = 50, value_parser = clap::value_parser!(u16).range(1..=100))]
141    pub limit: u16,
142    /// Number of records to skip
143    #[arg(long, default_value_t = 0)]
144    pub offset: usize,
145    /// Comma-separated output fields
146    #[arg(long)]
147    pub fields: Option<String>,
148}
149
150#[derive(Debug, Subcommand)]
151pub enum MessagesCommand {
152    /// List messages from a chat or channel
153    List {
154        #[arg(long, conflicts_with = "team", required_unless_present = "team")]
155        chat: Option<String>,
156        #[arg(long, requires = "channel", required_unless_present = "chat")]
157        team: Option<String>,
158        #[arg(long, requires = "team")]
159        channel: Option<String>,
160        #[command(flatten)]
161        page: PageArgs,
162    },
163    /// Send a message as the signed-in user
164    Send {
165        #[arg(long, conflicts_with = "team", required_unless_present = "team")]
166        chat: Option<String>,
167        #[arg(long, requires = "channel", required_unless_present = "chat")]
168        team: Option<String>,
169        #[arg(long, requires = "team")]
170        channel: Option<String>,
171        /// Plain-text message body; use '-' to read stdin
172        #[arg(long)]
173        body: String,
174    },
175}
176
177#[derive(Debug, Args)]
178pub struct TuiArgs {
179    /// Use deterministic sample data; no account or network required
180    #[arg(long)]
181    pub demo: bool,
182    /// Print one ANSI-free frame instead of taking over the terminal
183    #[arg(long, requires = "demo")]
184    pub snapshot: bool,
185    #[arg(long, default_value_t = 100, hide = true)]
186    pub width: u16,
187    #[arg(long, default_value_t = 30, hide = true)]
188    pub height: u16,
189}