cli/commands/
args.rs

1/*---------------------------------------------------------------------------------------------
2 *  Copyright (c) Microsoft Corporation. All rights reserved.
3 *  Licensed under the MIT License. See License.txt in the project root for license information.
4 *--------------------------------------------------------------------------------------------*/
5
6use std::{fmt, path::PathBuf};
7
8use crate::{constants, log, options, tunnels::code_server::CodeServerArgs};
9use clap::{Args, Parser, Subcommand, ValueEnum};
10use const_format::concatcp;
11
12const CLI_NAME: &str = concatcp!(constants::PRODUCT_NAME_LONG, " CLI");
13const HELP_COMMANDS: &str = concatcp!(
14	"Usage: ",
15	constants::APPLICATION_NAME,
16	" [options][paths...]
17
18To read output from another program, append '-' (e.g. 'echo Hello World | {name} -')"
19);
20
21const STANDALONE_TEMPLATE: &str = concatcp!(
22	CLI_NAME,
23	" Standalone - {version}
24
25",
26	HELP_COMMANDS,
27	"
28Running editor commands requires installing ",
29	constants::QUALITYLESS_PRODUCT_NAME,
30	", and may differ slightly.
31
32{all-args}"
33);
34const INTEGRATED_TEMPLATE: &str = concatcp!(
35	CLI_NAME,
36	" - {version}
37
38",
39	HELP_COMMANDS,
40	"
41
42{all-args}"
43);
44
45const COMMIT_IN_VERSION: &str = match constants::VSCODE_CLI_COMMIT {
46	Some(c) => c,
47	None => "unknown",
48};
49const NUMBER_IN_VERSION: &str = match constants::VSCODE_CLI_VERSION {
50	Some(c) => c,
51	None => "dev",
52};
53const VERSION: &str = concatcp!(NUMBER_IN_VERSION, " (commit ", COMMIT_IN_VERSION, ")");
54
55#[derive(Parser, Debug, Default)]
56#[clap(
57   help_template = INTEGRATED_TEMPLATE,
58   long_about = None,
59	 name = constants::APPLICATION_NAME,
60   version = VERSION,
61 )]
62pub struct IntegratedCli {
63	#[clap(flatten)]
64	pub core: CliCore,
65}
66
67/// Common CLI shared between intergated and standalone interfaces.
68#[derive(Args, Debug, Default, Clone)]
69pub struct CliCore {
70	/// One or more files, folders, or URIs to open.
71	#[clap(name = "paths")]
72	pub open_paths: Vec<String>,
73
74	#[clap(flatten, next_help_heading = Some("EDITOR OPTIONS"))]
75	pub editor_options: EditorOptions,
76
77	#[clap(flatten, next_help_heading = Some("EDITOR TROUBLESHOOTING"))]
78	pub troubleshooting: EditorTroubleshooting,
79
80	#[clap(flatten, next_help_heading = Some("GLOBAL OPTIONS"))]
81	pub global_options: GlobalOptions,
82
83	#[clap(subcommand)]
84	pub subcommand: Option<Commands>,
85}
86
87#[derive(Parser, Debug, Default)]
88#[clap(
89   help_template = STANDALONE_TEMPLATE,
90   long_about = None,
91   version = VERSION,
92	 name = constants::APPLICATION_NAME,
93 )]
94pub struct StandaloneCli {
95	#[clap(flatten)]
96	pub core: CliCore,
97
98	#[clap(subcommand)]
99	pub subcommand: Option<StandaloneCommands>,
100}
101
102pub enum AnyCli {
103	Integrated(IntegratedCli),
104	Standalone(StandaloneCli),
105}
106
107impl AnyCli {
108	pub fn core(&self) -> &CliCore {
109		match self {
110			AnyCli::Integrated(cli) => &cli.core,
111			AnyCli::Standalone(cli) => &cli.core,
112		}
113	}
114}
115
116impl CliCore {
117	pub fn get_base_code_args(&self) -> Vec<String> {
118		let mut args = self.open_paths.clone();
119		self.editor_options.add_code_args(&mut args);
120		self.troubleshooting.add_code_args(&mut args);
121		self.global_options.add_code_args(&mut args);
122		args
123	}
124}
125
126impl<'a> From<&'a CliCore> for CodeServerArgs {
127	fn from(cli: &'a CliCore) -> Self {
128		let mut args = CodeServerArgs {
129			log: cli.global_options.log,
130			accept_server_license_terms: true,
131			..Default::default()
132		};
133
134		args.log = cli.global_options.log;
135		args.accept_server_license_terms = true;
136
137		if cli.global_options.verbose {
138			args.verbose = true;
139		}
140
141		if cli.global_options.disable_telemetry {
142			args.telemetry_level = Some(options::TelemetryLevel::Off);
143		} else if cli.global_options.telemetry_level.is_some() {
144			args.telemetry_level = cli.global_options.telemetry_level;
145		}
146
147		args
148	}
149}
150
151#[derive(Subcommand, Debug, Clone)]
152pub enum StandaloneCommands {
153	/// Updates the CLI.
154	Update(StandaloneUpdateArgs),
155}
156
157#[derive(Args, Debug, Clone)]
158pub struct StandaloneUpdateArgs {
159	/// Only check for updates, without actually updating the CLI.
160	#[clap(long)]
161	pub check: bool,
162}
163
164#[derive(Subcommand, Debug, Clone)]
165
166pub enum Commands {
167	/// Create a tunnel that's accessible on vscode.dev from anywhere.
168	/// Run `code tunnel --help` for more usage info.
169	Tunnel(TunnelArgs),
170
171	/// Manage editor extensions.
172	#[clap(name = "ext")]
173	Extension(ExtensionArgs),
174
175	/// Print process usage and diagnostics information.
176	Status,
177
178	/// Changes the version of the editor you're using.
179	Version(VersionArgs),
180
181	/// Runs a local web version of VS Code.
182	#[clap(about = concatcp!("Runs a local web version of ", constants::PRODUCT_NAME_LONG))]
183	ServeWeb(ServeWebArgs),
184
185	/// Runs the control server on process stdin/stdout
186	#[clap(hide = true)]
187	CommandShell(CommandShellArgs),
188}
189
190#[derive(Args, Debug, Clone)]
191pub struct ServeWebArgs {
192	/// Host to listen on, defaults to 'localhost'
193	#[clap(long)]
194	pub host: Option<String>,
195	// The path to a socket file for the server to listen to.
196	#[clap(long)]
197	pub socket_path: Option<String>,
198	/// Port to listen on. If 0 is passed a random free port is picked.
199	#[clap(long, default_value_t = 8000)]
200	pub port: u16,
201	/// A secret that must be included with all requests.
202	#[clap(long)]
203	pub connection_token: Option<String>,
204	/// A file containing a secret that must be included with all requests.
205	#[clap(long)]
206	pub connection_token_file: Option<String>,
207	/// Run without a connection token. Only use this if the connection is secured by other means.
208	#[clap(long)]
209	pub without_connection_token: bool,
210	/// If set, the user accepts the server license terms and the server will be started without a user prompt.
211	#[clap(long)]
212	pub accept_server_license_terms: bool,
213	/// Specifies the path under which the web UI and the code server is provided.
214	#[clap(long)]
215	pub server_base_path: Option<String>,
216	/// Specifies the directory that server data is kept in.
217	#[clap(long)]
218	pub server_data_dir: Option<String>,
219	/// Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.
220	#[clap(long)]
221	pub user_data_dir: Option<String>,
222	/// Set the root path for extensions.
223	#[clap(long)]
224	pub extensions_dir: Option<String>,
225}
226
227#[derive(Args, Debug, Clone)]
228pub struct CommandShellArgs {
229	/// Listen on a socket instead of stdin/stdout.
230	#[clap(long)]
231	pub on_socket: bool,
232	/// Listen on a port instead of stdin/stdout.
233	#[clap(long, num_args = 0..=1, default_missing_value = "0")]
234	pub on_port: Option<u16>,
235	/// Require the given token string to be given in the handshake.
236	#[clap(long, env = "VSCODE_CLI_REQUIRE_TOKEN")]
237	pub require_token: Option<String>,
238	/// Optional parent process id. If provided, the server will be stopped when the process of the given pid no longer exists
239	#[clap(long, hide = true)]
240	pub parent_process_id: Option<String>,
241}
242
243#[derive(Args, Debug, Clone)]
244pub struct ExtensionArgs {
245	#[clap(subcommand)]
246	pub subcommand: ExtensionSubcommand,
247
248	#[clap(flatten)]
249	pub desktop_code_options: DesktopCodeOptions,
250}
251
252impl ExtensionArgs {
253	pub fn add_code_args(&self, target: &mut Vec<String>) {
254		self.desktop_code_options.add_code_args(target);
255		self.subcommand.add_code_args(target);
256	}
257}
258
259#[derive(Subcommand, Debug, Clone)]
260pub enum ExtensionSubcommand {
261	/// List installed extensions.
262	List(ListExtensionArgs),
263	/// Install an extension.
264	Install(InstallExtensionArgs),
265	/// Uninstall an extension.
266	Uninstall(UninstallExtensionArgs),
267	/// Update the installed extensions.
268	Update,
269}
270
271impl ExtensionSubcommand {
272	pub fn add_code_args(&self, target: &mut Vec<String>) {
273		match self {
274			ExtensionSubcommand::List(args) => {
275				target.push("--list-extensions".to_string());
276				if args.show_versions {
277					target.push("--show-versions".to_string());
278				}
279				if let Some(category) = &args.category {
280					target.push(format!("--category={}", category));
281				}
282			}
283			ExtensionSubcommand::Install(args) => {
284				for id in args.id_or_path.iter() {
285					target.push(format!("--install-extension={}", id));
286				}
287				if args.pre_release {
288					target.push("--pre-release".to_string());
289				}
290				if args.force {
291					target.push("--force".to_string());
292				}
293			}
294			ExtensionSubcommand::Uninstall(args) => {
295				for id in args.id.iter() {
296					target.push(format!("--uninstall-extension={}", id));
297				}
298			}
299			ExtensionSubcommand::Update => {
300				target.push("--update-extensions".to_string());
301			}
302		}
303	}
304}
305
306#[derive(Args, Debug, Clone)]
307pub struct ListExtensionArgs {
308	/// Filters installed extensions by provided category, when using --list-extensions.
309	#[clap(long, value_name = "category")]
310	pub category: Option<String>,
311
312	/// Show versions of installed extensions, when using --list-extensions.
313	#[clap(long)]
314	pub show_versions: bool,
315}
316
317#[derive(Args, Debug, Clone)]
318pub struct InstallExtensionArgs {
319	/// Either an extension id or a path to a VSIX. The identifier of an
320	/// extension is '${publisher}.${name}'. Use '--force' argument to update
321	/// to latest version. To install a specific version provide '@${version}'.
322	/// For example: 'vscode.csharp@1.2.3'.
323	#[clap(name = "ext-id | id")]
324	pub id_or_path: Vec<String>,
325
326	/// Installs the pre-release version of the extension
327	#[clap(long)]
328	pub pre_release: bool,
329
330	/// Update to the latest version of the extension if it's already installed.
331	#[clap(long)]
332	pub force: bool,
333}
334
335#[derive(Args, Debug, Clone)]
336pub struct UninstallExtensionArgs {
337	/// One or more extension identifiers to uninstall. The identifier of an
338	/// extension is '${publisher}.${name}'. Use '--force' argument to update
339	/// to latest version.
340	#[clap(name = "ext-id")]
341	pub id: Vec<String>,
342}
343
344#[derive(Args, Debug, Clone)]
345pub struct VersionArgs {
346	#[clap(subcommand)]
347	pub subcommand: VersionSubcommand,
348}
349
350#[derive(Subcommand, Debug, Clone)]
351pub enum VersionSubcommand {
352	/// Switches the version of the editor in use.
353	Use(UseVersionArgs),
354
355	/// Shows the currently configured editor version.
356	Show,
357}
358
359#[derive(Args, Debug, Clone)]
360pub struct UseVersionArgs {
361	/// The version of the editor you want to use. Can be "stable", "insiders",
362	/// or an absolute path to an existing install.
363	#[clap(value_name = "stable | insiders | x.y.z | path")]
364	pub name: String,
365
366	/// The directory where the version can be found.
367	#[clap(long, value_name = "path")]
368	pub install_dir: Option<String>,
369}
370
371#[derive(Args, Debug, Default, Clone)]
372pub struct EditorOptions {
373	/// Compare two files with each other.
374	#[clap(short, long, value_names = &["file", "file"])]
375	pub diff: Vec<String>,
376
377	/// Add folder(s) to the last active window.
378	#[clap(short, long, value_name = "folder")]
379	pub add: Option<String>,
380
381	/// Open a file at the path on the specified line and character position.
382	#[clap(short, long, value_name = "file:line[:character]")]
383	pub goto: Option<String>,
384
385	/// Force to open a new window.
386	#[clap(short, long)]
387	pub new_window: bool,
388
389	/// Force to open a file or folder in an
390	#[clap(short, long)]
391	pub reuse_window: bool,
392
393	/// Wait for the files to be closed before returning.
394	#[clap(short, long)]
395	pub wait: bool,
396
397	/// The locale to use (e.g. en-US or zh-TW).
398	#[clap(long, value_name = "locale")]
399	pub locale: Option<String>,
400
401	/// Enables proposed API features for extensions. Can receive one or
402	/// more extension IDs to enable individually.
403	#[clap(long, value_name = "ext-id")]
404	pub enable_proposed_api: Vec<String>,
405
406	#[clap(flatten)]
407	pub code_options: DesktopCodeOptions,
408}
409
410impl EditorOptions {
411	pub fn add_code_args(&self, target: &mut Vec<String>) {
412		if !self.diff.is_empty() {
413			target.push("--diff".to_string());
414			for file in self.diff.iter() {
415				target.push(file.clone());
416			}
417		}
418		if let Some(add) = &self.add {
419			target.push("--add".to_string());
420			target.push(add.clone());
421		}
422		if let Some(goto) = &self.goto {
423			target.push("--goto".to_string());
424			target.push(goto.clone());
425		}
426		if self.new_window {
427			target.push("--new-window".to_string());
428		}
429		if self.reuse_window {
430			target.push("--reuse-window".to_string());
431		}
432		if self.wait {
433			target.push("--wait".to_string());
434		}
435		if let Some(locale) = &self.locale {
436			target.push(format!("--locale={}", locale));
437		}
438		if !self.enable_proposed_api.is_empty() {
439			for id in self.enable_proposed_api.iter() {
440				target.push(format!("--enable-proposed-api={}", id));
441			}
442		}
443		self.code_options.add_code_args(target);
444	}
445}
446
447/// Arguments applicable whenever the desktop editor is launched
448#[derive(Args, Debug, Default, Clone)]
449pub struct DesktopCodeOptions {
450	/// Set the root path for extensions.
451	#[clap(long, value_name = "dir")]
452	pub extensions_dir: Option<String>,
453
454	/// Specifies the directory that user data is kept in. Can be used to
455	/// open multiple distinct instances of the editor.
456	#[clap(long, value_name = "dir")]
457	pub user_data_dir: Option<String>,
458
459	/// Sets the editor version to use for this command. The preferred version
460	/// can be persisted with `code version use <version>`. Can be "stable",
461	/// "insiders", a version number, or an absolute path to an existing install.
462	#[clap(long, value_name = "stable | insiders | x.y.z | path")]
463	pub use_version: Option<String>,
464}
465
466/// Argument specifying the output format.
467#[derive(Args, Debug, Clone)]
468pub struct OutputFormatOptions {
469	/// Set the data output formats.
470	#[clap(value_enum, long, value_name = "format", default_value_t = OutputFormat::Text)]
471	pub format: OutputFormat,
472}
473
474impl DesktopCodeOptions {
475	pub fn add_code_args(&self, target: &mut Vec<String>) {
476		if let Some(extensions_dir) = &self.extensions_dir {
477			target.push(format!("--extensions-dir={}", extensions_dir));
478		}
479		if let Some(user_data_dir) = &self.user_data_dir {
480			target.push(format!("--user-data-dir={}", user_data_dir));
481		}
482	}
483}
484
485#[derive(Args, Debug, Default, Clone)]
486pub struct GlobalOptions {
487	/// Directory where CLI metadata should be stored.
488	#[clap(long, env = "VSCODE_CLI_DATA_DIR", global = true)]
489	pub cli_data_dir: Option<String>,
490
491	/// Print verbose output (implies --wait).
492	#[clap(long, global = true)]
493	pub verbose: bool,
494
495	/// Log to a file in addition to stdout. Used when running as a service.
496	#[clap(long, global = true, hide = true)]
497	pub log_to_file: Option<PathBuf>,
498
499	/// Log level to use.
500	#[clap(long, value_enum, value_name = "level", global = true)]
501	pub log: Option<log::Level>,
502
503	/// Disable telemetry for the current command, even if it was previously
504	/// accepted as part of the license prompt or specified in '--telemetry-level'
505	#[clap(long, global = true, hide = true)]
506	pub disable_telemetry: bool,
507
508	/// Sets the initial telemetry level
509	#[clap(value_enum, long, global = true, hide = true)]
510	pub telemetry_level: Option<options::TelemetryLevel>,
511}
512
513impl GlobalOptions {
514	pub fn add_code_args(&self, target: &mut Vec<String>) {
515		if self.verbose {
516			target.push("--verbose".to_string());
517		}
518		if let Some(log) = self.log {
519			target.push(format!("--log={}", log));
520		}
521		if self.disable_telemetry {
522			target.push("--disable-telemetry".to_string());
523		}
524		if let Some(telemetry_level) = &self.telemetry_level {
525			target.push(format!("--telemetry-level={}", telemetry_level));
526		}
527	}
528}
529
530#[derive(Args, Debug, Default, Clone)]
531pub struct EditorTroubleshooting {
532	/// Run CPU profiler during startup.
533	#[clap(long)]
534	pub prof_startup: bool,
535
536	/// Disable all installed extensions.
537	#[clap(long)]
538	pub disable_extensions: bool,
539
540	/// Disable an extension.
541	#[clap(long, value_name = "ext-id")]
542	pub disable_extension: Vec<String>,
543
544	/// Turn sync on or off.
545	#[clap(value_enum, long, value_name = "on | off")]
546	pub sync: Option<SyncState>,
547
548	/// Allow debugging and profiling of extensions. Check the developer tools for the connection URI.
549	#[clap(long, value_name = "port")]
550	pub inspect_extensions: Option<u16>,
551
552	/// Allow debugging and profiling of extensions with the extension host
553	/// being paused after start. Check the developer tools for the connection URI.
554	#[clap(long, value_name = "port")]
555	pub inspect_brk_extensions: Option<u16>,
556
557	/// Disable GPU hardware acceleration.
558	#[clap(long)]
559	pub disable_gpu: bool,
560
561	/// Shows all telemetry events which the editor collects.
562	#[clap(long)]
563	pub telemetry: bool,
564}
565
566impl EditorTroubleshooting {
567	pub fn add_code_args(&self, target: &mut Vec<String>) {
568		if self.prof_startup {
569			target.push("--prof-startup".to_string());
570		}
571		if self.disable_extensions {
572			target.push("--disable-extensions".to_string());
573		}
574		for id in self.disable_extension.iter() {
575			target.push(format!("--disable-extension={}", id));
576		}
577		if let Some(sync) = &self.sync {
578			target.push(format!("--sync={}", sync));
579		}
580		if let Some(port) = &self.inspect_extensions {
581			target.push(format!("--inspect-extensions={}", port));
582		}
583		if let Some(port) = &self.inspect_brk_extensions {
584			target.push(format!("--inspect-brk-extensions={}", port));
585		}
586		if self.disable_gpu {
587			target.push("--disable-gpu".to_string());
588		}
589		if self.telemetry {
590			target.push("--telemetry".to_string());
591		}
592	}
593}
594
595#[derive(ValueEnum, Clone, Copy, Debug)]
596pub enum SyncState {
597	On,
598	Off,
599}
600
601impl fmt::Display for SyncState {
602	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
603		match self {
604			SyncState::Off => write!(f, "off"),
605			SyncState::On => write!(f, "on"),
606		}
607	}
608}
609
610#[derive(ValueEnum, Clone, Copy, Debug)]
611pub enum OutputFormat {
612	Json,
613	Text,
614}
615
616#[derive(Args, Clone, Debug, Default)]
617pub struct ExistingTunnelArgs {
618	/// Name you'd like to assign preexisting tunnel to use to connect the tunnel
619	/// Old option, new code sohuld just use `--name`.
620	#[clap(long, hide = true)]
621	pub tunnel_name: Option<String>,
622
623	/// Token to authenticate and use preexisting tunnel
624	#[clap(long, hide = true)]
625	pub host_token: Option<String>,
626
627	/// ID of preexisting tunnel to use to connect the tunnel
628	#[clap(long, hide = true)]
629	pub tunnel_id: Option<String>,
630
631	/// Cluster of preexisting tunnel to use to connect the tunnel
632	#[clap(long, hide = true)]
633	pub cluster: Option<String>,
634}
635
636#[derive(Args, Debug, Clone, Default)]
637pub struct TunnelServeArgs {
638	/// Optional details to connect to an existing tunnel
639	#[clap(flatten, next_help_heading = Some("ADVANCED OPTIONS"))]
640	pub tunnel: ExistingTunnelArgs,
641
642	/// Randomly name machine for port forwarding service
643	#[clap(long)]
644	pub random_name: bool,
645
646	/// Prevents the machine going to sleep while this command runs.
647	#[clap(long)]
648	pub no_sleep: bool,
649
650	/// Sets the machine name for port forwarding service
651	#[clap(long)]
652	pub name: Option<String>,
653
654	/// Optional parent process id. If provided, the server will be stopped when the process of the given pid no longer exists
655	#[clap(long, hide = true)]
656	pub parent_process_id: Option<String>,
657
658	/// If set, the user accepts the server license terms and the server will be started without a user prompt.
659	#[clap(long)]
660	pub accept_server_license_terms: bool,
661
662	/// Requests that extensions be preloaded and installed on connecting servers.
663	#[clap(long)]
664	pub install_extension: Vec<String>,
665}
666
667impl TunnelServeArgs {
668	pub fn apply_to_server_args(&self, csa: &mut CodeServerArgs) {
669		csa.install_extensions
670			.extend_from_slice(&self.install_extension);
671	}
672}
673
674#[derive(Args, Debug, Clone)]
675pub struct TunnelArgs {
676	#[clap(subcommand)]
677	pub subcommand: Option<TunnelSubcommand>,
678
679	#[clap(flatten)]
680	pub serve_args: TunnelServeArgs,
681}
682
683#[derive(Subcommand, Debug, Clone)]
684pub enum TunnelSubcommand {
685	/// Delete all servers which are currently not running.
686	Prune,
687
688	/// Stops any running tunnel on the system.
689	Kill,
690
691	/// Restarts any running tunnel on the system.
692	Restart,
693
694	/// Gets whether there is a tunnel running on the current machine.
695	Status,
696
697	/// Rename the name of this machine associated with port forwarding service.
698	Rename(TunnelRenameArgs),
699
700	/// Remove this machine's association with the port forwarding service.
701	Unregister,
702
703	#[clap(subcommand)]
704	User(TunnelUserSubCommands),
705
706	/// (Preview) Manages the tunnel when installed as a system service,
707	#[clap(subcommand)]
708	Service(TunnelServiceSubCommands),
709
710	/// (Preview) Forwards local port using the dev tunnel
711	#[clap(hide = true)]
712	ForwardInternal(TunnelForwardArgs),
713}
714
715#[derive(Subcommand, Debug, Clone)]
716pub enum TunnelServiceSubCommands {
717	/// Installs or re-installs the tunnel service on the machine.
718	Install(TunnelServiceInstallArgs),
719
720	/// Uninstalls and stops the tunnel service.
721	Uninstall,
722
723	/// Shows logs for the running service.
724	Log,
725
726	/// Internal command for running the service
727	#[clap(hide = true)]
728	InternalRun,
729}
730
731#[derive(Args, Debug, Clone)]
732pub struct TunnelServiceInstallArgs {
733	/// If set, the user accepts the server license terms and the server will be started without a user prompt.
734	#[clap(long)]
735	pub accept_server_license_terms: bool,
736
737	/// Sets the machine name for port forwarding service
738	#[clap(long)]
739	pub name: Option<String>,
740}
741
742#[derive(Args, Debug, Clone)]
743pub struct TunnelRenameArgs {
744	/// The name you'd like to rename your machine to.
745	pub name: String,
746}
747
748#[derive(Args, Debug, Clone)]
749pub struct TunnelForwardArgs {
750	/// One or more ports to forward.
751	pub ports: Vec<u16>,
752
753	/// Login args -- used for convenience so the forwarding call is a single action.
754	#[clap(flatten)]
755	pub login: LoginArgs,
756}
757
758#[derive(Subcommand, Debug, Clone)]
759pub enum TunnelUserSubCommands {
760	/// Log in to port forwarding service
761	Login(LoginArgs),
762
763	/// Log out of port forwarding service
764	Logout,
765
766	/// Show the account that's logged into port forwarding service
767	Show,
768}
769
770#[derive(Args, Debug, Clone)]
771pub struct LoginArgs {
772	/// An access token to store for authentication. Note: this will not be
773	/// refreshed if it expires!
774	#[clap(long, requires = "provider")]
775	pub access_token: Option<String>,
776
777	/// The auth provider to use. If not provided, a prompt will be shown.
778	#[clap(value_enum, long)]
779	pub provider: Option<AuthProvider>,
780}
781
782#[derive(clap::ValueEnum, Debug, Clone, Copy)]
783pub enum AuthProvider {
784	Microsoft,
785	Github,
786}