bw/
main.rs

1#![doc = include_str!("../README.md")]
2
3use base64::{Engine, engine::general_purpose::STANDARD};
4use bitwarden_cli::install_color_eyre;
5use clap::{CommandFactory, Parser};
6use clap_complete::Shell;
7use color_eyre::eyre::Result;
8use tracing_subscriber::{
9    EnvFilter, prelude::__tracing_subscriber_SubscriberExt as _, util::SubscriberInitExt as _,
10};
11
12use crate::{command::*, render::CommandResult};
13
14mod admin_console;
15mod auth;
16mod command;
17mod platform;
18mod render;
19mod tools;
20mod vault;
21
22#[tokio::main(flavor = "current_thread")]
23async fn main() -> Result<()> {
24    // the log level hierarchy is determined by:
25    //    - if RUST_LOG is detected at runtime
26    //    - if RUST_LOG is provided at compile time
27    //    - default to INFO
28    let filter = EnvFilter::builder()
29        .with_default_directive(
30            option_env!("RUST_LOG")
31                .unwrap_or("info")
32                .parse()
33                .expect("should provide valid log level at compile time."),
34        )
35        // parse directives from the RUST_LOG environment variable,
36        // overriding the default directive for matching targets.
37        .from_env_lossy();
38
39    tracing_subscriber::registry()
40        .with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
41        .with(filter)
42        .init();
43
44    let cli = Cli::parse();
45    install_color_eyre(cli.color)?;
46    let render_config = render::RenderConfig::new(&cli);
47
48    let Some(command) = cli.command else {
49        let mut cmd = Cli::command();
50        cmd.print_help()?;
51        return Ok(());
52    };
53
54    let result = process_commands(command, cli.session).await;
55
56    // Render the result of the command
57    render_config.render_result(result)
58}
59
60async fn process_commands(command: Commands, _session: Option<String>) -> CommandResult {
61    // Try to initialize the client with the session if provided
62    // Ideally we'd have separate clients and this would be an enum, something like:
63    // enum CliClient {
64    //   Unlocked(_),  // If the user already logged in and the provided session is valid
65    //   Locked(_),    // If the user is logged in, but the session hasn't been provided
66    //   LoggedOut(_), // If the user is not logged in
67    // }
68    // If the session was invalid, we'd just return an error immediately
69    // This would allow each command to match on the client type that they need, and we don't need
70    // to do two matches over the whole command tree
71    let client = bitwarden_pm::PasswordManagerClient::new(None);
72
73    match command {
74        // Auth commands
75        Commands::Login(args) => args.run().await,
76        Commands::Logout => todo!(),
77        Commands::Register(register) => register.run().await,
78
79        // KM commands
80        Commands::Unlock(_args) => todo!(),
81
82        // Platform commands
83        Commands::Sync { .. } => todo!(),
84
85        Commands::Encode => {
86            let input = std::io::read_to_string(std::io::stdin())?;
87            let encoded = STANDARD.encode(input);
88            Ok(encoded.into())
89        }
90
91        Commands::Config { command } => command.run().await,
92
93        Commands::Update { .. } => todo!(),
94
95        Commands::Completion { shell } => {
96            let Some(shell) = shell.or_else(Shell::from_env) else {
97                return Ok(
98                    "Couldn't autodetect a valid shell. Run `bw completion --help` for more info."
99                        .into(),
100                );
101            };
102
103            let mut cmd = Cli::command();
104            let name = cmd.get_name().to_string();
105            clap_complete::generate(shell, &mut cmd, name, &mut std::io::stdout());
106            Ok(().into())
107        }
108
109        Commands::Status => todo!(),
110
111        // Vault commands
112        Commands::Item { command: _ } => todo!(),
113        Commands::Template { command } => command.run(),
114
115        Commands::List => todo!(),
116        Commands::Get => todo!(),
117        Commands::Create => todo!(),
118        Commands::Edit => todo!(),
119        Commands::Delete => todo!(),
120        Commands::Restore => todo!(),
121        Commands::Move => todo!(),
122
123        // Admin console commands
124        Commands::Confirm { .. } => todo!(),
125
126        // Tools commands
127        Commands::Generate(arg) => arg.run(&client),
128        Commands::Import => todo!(),
129        Commands::Export => todo!(),
130        Commands::Share => todo!(),
131        Commands::Send => todo!(),
132        Commands::Receive => todo!(),
133    }
134}