zinc-wallet-cli 0.4.0

Agent-first Bitcoin + Ordinals CLI wallet with account-based taproot ordinals + native segwit payment addresses (optional human mode)
use crate::cli::{Cli, SnapshotAction, SnapshotArgs};
use crate::error::AppError;
use crate::output::CommandOutput;
use crate::{confirm, profile_path, read_profile, snapshot_dir, write_bytes_atomic};
use std::fs;

pub async fn run(cli: &Cli, args: &SnapshotArgs) -> Result<CommandOutput, AppError> {
    let profile_path = profile_path(cli)?;
    let snap_dir = snapshot_dir(cli)?;
    fs::create_dir_all(&snap_dir)
        .map_err(|e| AppError::Config(format!("failed to create snapshot dir: {e}")))?;

    match &args.action {
        SnapshotAction::Save { name, overwrite } => {
            let source = read_profile(&profile_path)?;
            let destination = snap_dir.join(format!("{name}.json"));
            if destination.exists() && !(*overwrite || cli.yes) {
                return Err(AppError::Config(
                    "snapshot already exists (use --overwrite or --yes)".to_string(),
                ));
            }
            let bytes = serde_json::to_vec_pretty(&source)
                .map_err(|e| AppError::Internal(format!("snapshot serialize failed: {e}")))?;
            write_bytes_atomic(&destination, &bytes, "snapshot")?;
            Ok(CommandOutput::SnapshotSave {
                snapshot: destination.display().to_string(),
            })
        }
        SnapshotAction::Restore { name } => {
            if !confirm(&format!("Are you sure you want to restore snapshot '{name}'? This will overwrite your current profile."), cli) {
                return Err(AppError::Internal("aborted by user".to_string()));
            }
            let source = snap_dir.join(format!("{name}.json"));
            if !source.exists() {
                return Err(AppError::NotFound(format!(
                    "snapshot does not exist: {}",
                    source.display()
                )));
            }
            let data = fs::read(&source)
                .map_err(|e| AppError::Config(format!("failed to read snapshot: {e}")))?;
            write_bytes_atomic(&profile_path, &data, "profile restore")?;
            Ok(CommandOutput::SnapshotRestore {
                restored: source.display().to_string(),
            })
        }
        SnapshotAction::List => {
            let mut names = Vec::new();
            let entries = fs::read_dir(&snap_dir)
                .map_err(|e| AppError::Config(format!("failed to list snapshots: {e}")))?;
            for entry in entries {
                let entry =
                    entry.map_err(|e| AppError::Config(format!("failed to read entry: {e}")))?;
                if entry
                    .path()
                    .extension()
                    .and_then(|ext| ext.to_str())
                    .is_some_and(|ext| ext == "json")
                {
                    names.push(entry.path().display().to_string());
                }
            }
            names.sort();
            Ok(CommandOutput::SnapshotList { snapshots: names })
        }
    }
}