1use 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, OutputFormat};
8
9#[derive(Debug, Parser)]
10#[command(
11 name = "sharepoint",
12 about = "Agent-friendly SharePoint Online CLI",
13 after_help = "Run `sharepoint schema` for a machine-readable description of all commands.",
14 version,
15 propagate_version = true,
16 disable_help_subcommand = true
17)]
18pub struct Cli {
19 #[arg(
21 long,
22 short = 'o',
23 global = true,
24 default_value = "auto",
25 value_name = "FORMAT"
26 )]
27 pub output: OutputFormat,
28
29 #[arg(long, global = true)]
31 pub quiet: bool,
32
33 #[arg(long, global = true, env = ENV_PROFILE)]
35 pub profile: Option<String>,
36
37 #[arg(long, global = true, env = ENV_TENANT)]
39 pub tenant: Option<String>,
40
41 #[arg(long, global = true, env = ENV_CLIENT_ID)]
43 pub client_id: Option<String>,
44
45 #[command(subcommand)]
46 pub command: Command,
47}
48
49#[derive(Debug, Subcommand)]
50pub enum Command {
51 Init,
53 #[command(subcommand)]
55 Auth(AuthCmd),
56 #[command(subcommand)]
58 Config(ConfigCmd),
59 #[command(subcommand)]
61 Sites(SitesCmd),
62 #[command(subcommand)]
64 Drives(DrivesCmd),
65 #[command(subcommand)]
67 Files(FilesCmd),
68 Schema,
70}
71
72#[derive(Debug, Subcommand)]
73pub enum AuthCmd {
74 Login,
76 Logout,
78 Status {
80 #[arg(long, default_value_t = 50)]
82 limit: usize,
83 #[arg(long)]
85 page: Option<String>,
86 #[arg(long, value_delimiter = ',')]
88 fields: Vec<String>,
89 },
90}
91
92#[derive(Debug, Subcommand)]
93pub enum ConfigCmd {
94 Show,
96 Path,
98}
99
100#[derive(Debug, Subcommand)]
101pub enum SitesCmd {
102 List {
104 #[arg(long)]
105 query: Option<String>,
106 #[arg(long, default_value_t = 50)]
107 limit: usize,
108 #[arg(long)]
109 all: bool,
110 #[arg(long)]
111 page: Option<String>,
112 #[arg(long, value_delimiter = ',')]
114 fields: Vec<String>,
115 },
116 Use {
118 site: String,
120 },
121}
122
123#[derive(Debug, Subcommand)]
124pub enum DrivesCmd {
125 List {
127 site: String,
128 #[arg(long, default_value_t = 50)]
129 limit: usize,
130 #[arg(long)]
131 all: bool,
132 #[arg(long, value_delimiter = ',')]
134 fields: Vec<String>,
135 },
136}
137
138#[derive(Debug, Subcommand)]
139pub enum FilesCmd {
140 Ls {
142 #[arg(value_name = "REF")]
143 reference: String,
144 #[arg(short = 'r', long)]
145 recursive: bool,
146 #[arg(long)]
147 limit: Option<usize>,
148 #[arg(long)]
149 all: bool,
150 #[arg(long)]
151 page: Option<String>,
152 #[arg(long, value_delimiter = ',')]
154 fields: Vec<String>,
155 },
156 Stat {
158 #[arg(value_name = "REF")]
159 reference: String,
160 },
161 Download {
163 #[arg(value_name = "REF")]
164 reference: String,
165 #[arg(long, short = 'p')]
167 path: Option<String>,
168 #[arg(long)]
169 overwrite: bool,
170 },
171 Find {
173 #[arg(value_name = "REF")]
174 reference: String,
175 #[arg(long)]
176 query: Option<String>,
177 #[arg(long)]
178 name: Option<String>,
179 #[arg(long, default_value_t = 200)]
180 limit: usize,
181 #[arg(long)]
182 all: bool,
183 #[arg(long)]
184 page: Option<String>,
185 #[arg(long, value_delimiter = ',')]
187 fields: Vec<String>,
188 },
189}
190
191pub struct Runtime {
192 pub out: OutputConfig,
193 pub cfg: ResolvedConfig,
194 pub config_file: ConfigFile,
195 pub config_path: std::path::PathBuf,
196 pub cache_path: std::path::PathBuf,
197}
198
199impl Runtime {
200 pub fn build(cli: &Cli) -> Result<Self> {
201 let config_path = config::config_path()?;
202 let config_file = config::load_file(&config_path)?;
203 let env_lookup =
204 |k: &str| -> Option<String> { std::env::var(k).ok().filter(|s| !s.is_empty()) };
205 let mut cfg = config::resolve(&config_file, cli.profile.as_deref(), &env_lookup)?;
206 if let Some(t) = &cli.tenant {
207 cfg.tenant_id = Some(t.clone());
208 }
209 if let Some(c) = &cli.client_id {
210 cfg.client_id = Some(c.clone());
211 }
212 let cache_path = config::token_cache_path()?;
213 Ok(Self {
214 out: OutputConfig::new(cli.output, cli.quiet),
215 cfg,
216 config_file,
217 config_path,
218 cache_path,
219 })
220 }
221}
222
223pub async fn run(cli: Cli) -> Result<()> {
224 if matches!(cli.command, Command::Schema) {
226 return crate::commands::schema::run();
227 }
228 let rt = Runtime::build(&cli)?;
229 match cli.command {
230 Command::Schema => unreachable!(),
231 Command::Init => crate::commands::init::run(&rt).await,
232 Command::Auth(sub) => crate::commands::auth::run(&rt, sub).await,
233 Command::Config(sub) => crate::commands::config::run(&rt, sub).await,
234 Command::Sites(sub) => crate::commands::sites::run(&rt, sub).await,
235 Command::Drives(sub) => crate::commands::drives::run(&rt, sub).await,
236 Command::Files(sub) => crate::commands::files::run(&rt, sub).await,
237 }
238}