envoy-cli 0.1.3-release

A Git-like CLI for managing encrypted environment files
use clap::{Parser, Subcommand};
use dialoguer::Password;

use crate::commands::{auth::logout_command, status::status};

pub mod commands;
pub mod utils;

#[derive(Parser)]
#[command(name = "envy")]
#[command(about = "Secure .env storage with client-side encryption")]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum RemoteCommand {
    Add { name: String, url: String },
}

#[derive(Subcommand)]
enum Commands {
    Encrypt {
        #[arg(short, long, default_value = ".env")]
        input: String,
    },
    Decrypt {},
    Init {
        name: Option<String>,
    },
    Login {},
    Logout {},
    Update {},
    Push {
        remote: Option<String>,
    },
    Pull {
        remote: Option<String>,
    },
    Status {},
    Remote {
        #[command(subcommand)]
        command: RemoteCommand,
    },
}

fn main() -> anyhow::Result<()> {
    dotenvy::dotenv().ok();
    let cli = Cli::parse();

    match cli.command {
        Commands::Update {} => {
            let result = tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .build()
                .unwrap()
                .block_on(async { commands::update::update().await });

            if let Err(e) = result {
                eprintln!(
                    "{} {}",
                    console::style("").red().bold(),
                    console::style(format!("Update failed: {}", e)).red()
                );
                std::process::exit(1);
            }
        }
        Commands::Init { name } => {
            let result = tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .build()
                .unwrap()
                .block_on(async { commands::init::init_project(name).await });

            if let Err(e) = result {
                eprintln!(
                    "{} {}",
                    console::style("").red().bold(),
                    console::style(format!("Initialization failed: {}", e)).red()
                );
                std::process::exit(1);
            }
        }
        Commands::Login {} => {
            let result = tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .build()
                .unwrap()
                .block_on(async { commands::auth::login().await });

            if let Err(e) = result {
                eprintln!(
                    "{} {}",
                    console::style("").red().bold(),
                    console::style(format!("Login failed: {}", e)).red()
                );
                std::process::exit(1);
            }
        }
        Commands::Logout {} => {
            logout_command()?;
        }
        Commands::Remote { command } => match command {
            RemoteCommand::Add { name, url } => {
                commands::remote::add_remote(&name, &url)?;
            }
        },
        Commands::Encrypt { input } => {
            utils::initialized::check_initialized()?;
            let passphrase = Password::new()
                .with_prompt("Enter passphrase")
                .with_confirmation("Confirm passphrase", "Passphrases do not match")
                .interact()?;

            commands::crypto::encrypt_file(&input, &passphrase)?;
            println!(
                "{} {}",
                console::style("").green().bold(),
                console::style("File encrypted successfully").green()
            );
        }

        Commands::Decrypt {} => {
            utils::initialized::check_initialized()?;
            let passphrase = Password::new().with_prompt("Enter passphrase").interact()?;

            commands::crypto::decrypt_files(&passphrase)?;
            println!(
                "{} {}",
                console::style("").green().bold(),
                console::style("File decrypted successfully").green()
            );
        }

        Commands::Push { remote } => {
            utils::initialized::check_initialized()?;
            let passphrase = Password::new().with_prompt("Enter passphrase").interact()?;

            let result = tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .build()
                .unwrap()
                .block_on(async { commands::push::push(&passphrase, remote.as_deref()).await });

            if let Err(e) = result {
                eprintln!(
                    "{} {}",
                    console::style("").red().bold(),
                    console::style(format!("Push failed: {}", e)).red()
                );
                std::process::exit(1);
            }
        }

        Commands::Pull { remote } => {
            utils::initialized::check_initialized()?;
            let passphrase = Password::new().with_prompt("Enter passphrase").interact()?;

            let result = tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .build()
                .unwrap()
                .block_on(async { commands::pull::pull(&passphrase, remote.as_deref()).await });

            if let Err(e) = result {
                eprintln!(
                    "{} {}",
                    console::style("").red().bold(),
                    console::style(format!("Pull failed: {}", e)).red()
                );
                std::process::exit(1);
            }
        }
        Commands::Status {} => {
            utils::initialized::check_initialized()?;
            let passphrase = Password::new().with_prompt("Enter passphrase").interact()?;
            status(&passphrase)?;
        }
    }

    Ok(())
}