vios_app 0.1.1

Small JSON vaults: Argon2id + AES-GCM, with optional AAD binding.
Documentation
use clap::{Arg, Command};
use std::{fs, io::Write, path::PathBuf, process::Command as Pcmd, time::SystemTime};
use sha2::{Digest, Sha256};

fn sha256_file(path: &PathBuf) -> anyhow::Result<String> {
    let bytes = fs::read(path)?;
    let mut h = Sha256::new();
    h.update(&bytes);
    Ok(format!("{:x}", h.finalize()))
}

fn timestamp() -> String {
    let dt = SystemTime::now();
    let chrono_dt: chrono::DateTime<chrono::Utc> = dt.into();
    chrono_dt.format("%Y-%m-%dT%H%M%S").to_string()
}

fn main() -> anyhow::Result<()> {
    let m = Command::new("whisper_input (demo)")
        .about("Records a short audio clip or uses --file, stores it in ./vaults, computes SHA256, and can auto-anchor via vault_license.")
        .arg(Arg::new("file").long("file").value_name("WAV").help("Use an existing WAV file"))
        .arg(Arg::new("seconds").long("seconds").value_name("N").default_value("5").help("Record N seconds via ffmpeg if --file not given"))
        .arg(Arg::new("auto_mint").long("auto-mint").action(clap::ArgAction::SetTrue).help("Automatically call ./target/release/vault_license --hash <sha>"))
        .get_matches();

    fs::create_dir_all("vaults")?;

    // Decide the input WAV
    let wav_path: PathBuf = if let Some(f) = m.get_one::<String>("file") {
        PathBuf::from(f)
    } else {
        // record via ffmpeg to ./vaults/voice_<ts>.wav
        let secs = m.get_one::<String>("seconds").unwrap().parse::<u64>().unwrap_or(5);
        let out = PathBuf::from(format!("vaults/voice_{}.wav", timestamp()));
        eprintln!("Recording {secs}s from default mic to {:?}", out);
        let status = Pcmd::new("ffmpeg")
            .args([
                "-y",
                "-f", "avfoundation", "-i", ":0",
                "-t", &secs.to_string(),
                out.to_str().unwrap(),
            ])
            .status();

        match status {
            Ok(s) if s.success() => out,
            _ => {
                // macOS hint: if avfoundation index differs, try :1 or show help
                eprintln!("ffmpeg recording failed. Try selecting a different input index (e.g. :1).");
                std::process::exit(2);
            }
        }
    };

    // Copy WAV into vaults (if it isn’t already there)
    let dst_path = if wav_path.starts_with("vaults/") {
        wav_path.clone()
    } else {
        let dst = PathBuf::from(format!("vaults/voice_{}.wav", timestamp()));
        fs::copy(&wav_path, &dst)?;
        dst
    };

    // Compute SHA256
    let sha = sha256_file(&dst_path)?;
    println!("Saved:  {}", dst_path.display());
    println!("SHA256: {}", sha);

    // Write a tiny sidecar manifest for demo viewing
    let manifest = serde_json::json!({
        "type": "voice_log_demo",
        "file": dst_path.to_string_lossy(),
        "sha256": sha,
        "created_at": chrono::Utc::now().to_rfc3339(),
    });
    let manifest_path = PathBuf::from(format!("vaults/{}.json", dst_path.file_stem().unwrap().to_string_lossy()));
    let mut f = fs::File::create(&manifest_path)?;
    f.write_all(serde_json::to_string_pretty(&manifest)?.as_bytes())?;
    println!("Manifest: {}", manifest_path.display());

    // Optional: auto-anchor via vault_license (if user requested)
    let auto = m.get_flag("auto_mint");
    if auto {
        println!("\n-- auto-minting via vault_license --");
        let status = Pcmd::new("./target/release/vault_license")
            .args(["--hash", &sha])
            .status();
        match status {
            Ok(s) if s.success() => {
                println!("vault_license completed.");
            }
            _ => {
                eprintln!("vault_license failed to run. You can run it yourself:\n  ./target/release/vault_license --hash {}", sha);
            }
        }
    } else {
        println!("\nNext step (manual mint):\n  ./target/release/vault_license --hash {}", sha);
    }

    Ok(())
}