void-cli 0.0.4

CLI for void — anonymous encrypted source control
//! Read the decompressed body of an encrypted shard.
//!
//! Loads a shard by CID from the object store, decrypts it, decompresses
//! the body, and writes the raw decompressed bytes to stdout.
//! Use with TreeManifest entries for offset/length-based file extraction.

use std::io::Write;
use std::path::Path;

use serde::Serialize;
use void_core::cid;
use void_core::store::{FsStore, ObjectStoreExt};

use crate::context::{open_repo, void_err_to_cli};
use crate::output::{run_command, CliError, CliOptions};

pub struct ReadShardFileArgs {
    pub shard_cid: String,
    pub path: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ReadShardFileOutput {
    pub shard_cid: String,
    pub path: String,
    pub size: usize,
}

pub fn run(cwd: &Path, args: ReadShardFileArgs, opts: &CliOptions) -> Result<(), CliError> {
    run_command("debug read-shard-file", opts, |ctx| {
        let repo = open_repo(cwd)?;
        let vault = repo.vault();

        let objects_dir = repo.void_dir().join("objects");
        let store = FsStore::new(objects_dir)
            .map_err(|e| CliError::internal(format!("failed to open store: {}", e)))?;

        ctx.progress(format!("Loading shard {}...", &args.shard_cid));

        // Parse CID and load encrypted shard
        let shard_cid = cid::parse(&args.shard_cid)
            .map_err(|e| CliError::invalid_args(format!("invalid CID: {}", e)))?;
        let shard_blob: void_core::crypto::EncryptedShard = store.get_blob(&shard_cid).map_err(void_err_to_cli)?;
        let decrypted = vault.unseal_shard(&shard_blob)
            .map_err(|e| CliError::internal(format!("failed to decrypt shard: {}", e)))?;

        let size = decrypted.len();

        if !ctx.use_json() {
            ctx.info(format!(
                "Shard body: {} bytes (compressed, opaque block)",
                size
            ));
            ctx.info("File index is in TreeManifest, not shard. Use `void debug read-shard` for size info.".to_string());
            std::io::stdout()
                .flush()
                .map_err(|e| CliError::internal(format!("failed to flush output: {}", e)))?;
        }

        Ok(ReadShardFileOutput {
            shard_cid: args.shard_cid,
            path: args.path,
            size,
        })
    })
}