void-cli 0.0.4

CLI for void — anonymous encrypted source control
//! Encrypt data using a provided key.
//!
//! Takes a hex-encoded key and plaintext, encrypts with AES-256-GCM.

use serde::Serialize;
use void_core::crypto;

use crate::output::{run_command, CliError, CliOptions};

/// Command-line arguments for encrypt.
#[derive(Debug)]
pub struct EncryptArgs {
    /// 32-byte key as hex (64 chars).
    pub key: String,
    /// Plaintext data to encrypt.
    pub plaintext: String,
}

/// JSON output for the encrypt command.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EncryptOutput {
    /// Encrypted data (hex-encoded).
    pub ciphertext: String,
}

/// Run the encrypt command.
///
/// # Arguments
///
/// * `args` - Encrypt arguments
/// * `opts` - CLI options
pub fn run(args: EncryptArgs, opts: &CliOptions) -> Result<(), CliError> {
    run_command("encrypt", opts, |_ctx| {
        // Parse key from hex (64 hex chars = 32 bytes)
        let key_bytes = hex::decode(&args.key)
            .map_err(|e| CliError::invalid_args(format!("invalid key hex: {}", e)))?;

        if key_bytes.len() != 32 {
            return Err(CliError::invalid_args(format!(
                "key must be 32 bytes (64 hex chars), got {} bytes",
                key_bytes.len()
            )));
        }

        let key: [u8; 32] = key_bytes.try_into().unwrap();

        // Encrypt with empty AAD (matching TypeScript API)
        let encrypted = crypto::encrypt(&key, args.plaintext.as_bytes(), b"")
            .map_err(|e| CliError::encryption_error(format!("encryption failed: {}", e)))?;

        Ok(EncryptOutput {
            ciphertext: hex::encode(&encrypted),
        })
    })
}