Skip to main content

sharepoint_cli/
cli.rs

1//! CLI entry point: clap derive structs and the `run` dispatcher.
2
3use clap::{Parser, Subcommand};
4
5use crate::config::{self, ConfigFile, ENV_CLIENT_ID, ENV_PROFILE, ENV_TENANT, ResolvedConfig};
6use crate::error::Result;
7use crate::output::OutputConfig;
8
9#[derive(Debug, Parser)]
10#[command(
11    name = "sharepoint",
12    about = "Agent-friendly SharePoint Online CLI",
13    version,
14    propagate_version = true,
15    disable_help_subcommand = true
16)]
17pub struct Cli {
18    /// Output JSON to stdout (auto when stdout is not a TTY).
19    #[arg(long, global = true)]
20    pub json: bool,
21
22    /// Suppress informational messages on stderr.
23    #[arg(long, global = true)]
24    pub quiet: bool,
25
26    /// Active config profile (default: "default"). Env: SHAREPOINT_PROFILE.
27    #[arg(long, global = true, env = ENV_PROFILE)]
28    pub profile: Option<String>,
29
30    /// Tenant override. Env: SHAREPOINT_TENANT_ID.
31    #[arg(long, global = true, env = ENV_TENANT)]
32    pub tenant: Option<String>,
33
34    /// Client ID override. Env: SHAREPOINT_CLIENT_ID.
35    #[arg(long, global = true, env = ENV_CLIENT_ID)]
36    pub client_id: Option<String>,
37
38    #[command(subcommand)]
39    pub command: Command,
40}
41
42#[derive(Debug, Subcommand)]
43pub enum Command {
44    /// Interactive setup + first device-code login.
45    Init,
46    /// Sub-commands: login, logout, status.
47    #[command(subcommand)]
48    Auth(AuthCmd),
49    /// Sub-commands: show, path.
50    #[command(subcommand)]
51    Config(ConfigCmd),
52    /// Sub-commands: list, use.
53    #[command(subcommand)]
54    Sites(SitesCmd),
55    /// Sub-commands: list.
56    #[command(subcommand)]
57    Drives(DrivesCmd),
58    /// Sub-commands: ls, stat, download, find.
59    #[command(subcommand)]
60    Files(FilesCmd),
61}
62
63#[derive(Debug, Subcommand)]
64pub enum AuthCmd {
65    /// Run the device-code flow and cache the resulting tokens.
66    Login,
67    /// Delete cached tokens for the active profile's tenant/client.
68    Logout,
69    /// Show cached account info, expiry, scopes.
70    Status,
71}
72
73#[derive(Debug, Subcommand)]
74pub enum ConfigCmd {
75    /// Print the resolved config (token & secrets masked).
76    Show,
77    /// Print the absolute path to the config file.
78    Path,
79}
80
81#[derive(Debug, Subcommand)]
82pub enum SitesCmd {
83    /// List sites. Without --query: followed sites; with --query: search.
84    List {
85        #[arg(long)]
86        query: Option<String>,
87        #[arg(long, default_value_t = 50)]
88        limit: usize,
89        #[arg(long)]
90        all: bool,
91        #[arg(long)]
92        page: Option<String>,
93    },
94    /// Set `default_site` in the active profile.
95    Use {
96        /// Site name or URL.
97        site: String,
98    },
99}
100
101#[derive(Debug, Subcommand)]
102pub enum DrivesCmd {
103    /// List drives (libraries) for a site reference.
104    List {
105        site: String,
106        #[arg(long, default_value_t = 50)]
107        limit: usize,
108        #[arg(long)]
109        all: bool,
110    },
111}
112
113#[derive(Debug, Subcommand)]
114pub enum FilesCmd {
115    /// List items at a reference (folder).
116    Ls {
117        #[arg(value_name = "REF")]
118        reference: String,
119        #[arg(short = 'r', long)]
120        recursive: bool,
121        #[arg(long, default_value_t = 200)]
122        limit: usize,
123        #[arg(long)]
124        all: bool,
125        #[arg(long)]
126        page: Option<String>,
127    },
128    /// Show metadata for a single item.
129    Stat {
130        #[arg(value_name = "REF")]
131        reference: String,
132    },
133    /// Download a file. PATH or `-` for stdout.
134    Download {
135        #[arg(value_name = "REF")]
136        reference: String,
137        #[arg(long, short = 'o')]
138        output: Option<String>,
139        #[arg(long)]
140        overwrite: bool,
141    },
142    /// Search inside a drive (by query and/or shell glob).
143    Find {
144        #[arg(value_name = "REF")]
145        reference: String,
146        #[arg(long)]
147        query: Option<String>,
148        #[arg(long)]
149        name: Option<String>,
150        #[arg(long, default_value_t = 200)]
151        limit: usize,
152        #[arg(long)]
153        all: bool,
154        #[arg(long)]
155        page: Option<String>,
156    },
157}
158
159pub struct Runtime {
160    pub out: OutputConfig,
161    pub cfg: ResolvedConfig,
162    pub config_file: ConfigFile,
163    pub config_path: std::path::PathBuf,
164    pub cache_path: std::path::PathBuf,
165}
166
167impl Runtime {
168    pub fn build(cli: &Cli) -> Result<Self> {
169        let config_path = config::config_path()?;
170        let config_file = config::load_file(&config_path)?;
171        let env_lookup =
172            |k: &str| -> Option<String> { std::env::var(k).ok().filter(|s| !s.is_empty()) };
173        let mut cfg = config::resolve(&config_file, cli.profile.as_deref(), &env_lookup)?;
174        if let Some(t) = &cli.tenant {
175            cfg.tenant_id = Some(t.clone());
176        }
177        if let Some(c) = &cli.client_id {
178            cfg.client_id = Some(c.clone());
179        }
180        let cache_path = config::token_cache_path()?;
181        Ok(Self {
182            out: OutputConfig::new(cli.json, cli.quiet),
183            cfg,
184            config_file,
185            config_path,
186            cache_path,
187        })
188    }
189}
190
191pub async fn run(cli: Cli) -> Result<()> {
192    let rt = Runtime::build(&cli)?;
193    match cli.command {
194        Command::Init => crate::commands::init::run(&rt).await,
195        Command::Auth(sub) => crate::commands::auth::run(&rt, sub).await,
196        Command::Config(sub) => crate::commands::config::run(&rt, sub).await,
197        Command::Sites(sub) => crate::commands::sites::run(&rt, sub).await,
198        Command::Drives(sub) => crate::commands::drives::run(&rt, sub).await,
199        Command::Files(sub) => crate::commands::files::run(&rt, sub).await,
200    }
201}