use clap::{Parser, Subcommand}; mod cli; mod keystore; mod net; mod storage; mod tui; #[derive(Parser)] #[command(name = "warzone", about = "Warzone messenger client")] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Generate a new identity (seed + keypair + pre-keys) Init, /// Recover identity from BIP39 mnemonic Recover { /// 24-word mnemonic #[arg(num_args = 1..)] words: Vec, }, /// Show your fingerprint and public key Info, /// Register your key bundle with a server Register { /// Server URL #[arg(short, long, default_value = "http://localhost:7700")] server: String, }, /// Show Ethereum-compatible address derived from your seed Eth, /// Send an encrypted message Send { /// Recipient fingerprint (e.g. a3f8:c912:...) or @alias recipient: String, /// Message text message: String, /// Server URL #[arg(short, long, default_value = "http://localhost:7700")] server: String, }, /// Poll for and decrypt messages Recv { /// Server URL #[arg(short, long, default_value = "http://localhost:7700")] server: String, }, /// Launch interactive TUI chat Chat { /// Peer fingerprint or @alias (optional) peer: Option, /// Server URL #[arg(short, long, default_value = "http://localhost:7700")] server: String, }, /// Export encrypted backup of local data (sessions, history) Backup { /// Output file path #[arg(default_value = "warzone-backup.wzb")] output: String, }, /// Restore from encrypted backup Restore { /// Backup file path input: String, }, } #[tokio::main] async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { // These don't need an existing identity Commands::Init => return cli::init::run(), Commands::Recover { words } => return cli::recover::run(&words.join(" ")), _ => {} } // All other commands need the seed — unlock once here let seed = keystore::load_seed()?; // Create a copy for the poll thread (Seed doesn't impl Clone due to Zeroize) let poll_seed = warzone_protocol::identity::Seed::from_bytes(seed.0); let identity = seed.derive_identity(); let our_fp = identity.public_identity().fingerprint.to_string(); match cli.command { Commands::Init | Commands::Recover { .. } => unreachable!(), Commands::Info => { let pub_id = identity.public_identity(); println!("Fingerprint: {}", pub_id.fingerprint); println!("Signing key: {}", hex::encode(pub_id.signing.as_bytes())); println!("Encryption key: {}", hex::encode(pub_id.encryption.as_bytes())); } Commands::Eth => { let eth_id = warzone_protocol::ethereum::derive_eth_identity(&seed.0); let pub_id = identity.public_identity(); println!("Warzone fingerprint: {}", pub_id.fingerprint); println!("Ethereum address: {}", eth_id.address.to_checksum()); println!("Same seed, dual identity."); } Commands::Register { server } => { cli::init::register_with_server_identity(&server, &identity).await?; } Commands::Send { recipient, message, server, } => { let _ = cli::init::register_with_server_identity(&server, &identity).await; cli::send::run(&recipient, &message, &server, &identity).await?; } Commands::Recv { server } => { cli::recv::run(&server, &identity).await?; } Commands::Chat { peer, server } => { let _ = cli::init::register_with_server_identity(&server, &identity).await; let db = storage::LocalDb::open()?; tui::run_tui(our_fp, peer, server, identity, poll_seed, db).await?; } Commands::Backup { output } => { // Collect all sled data as JSON let db = storage::LocalDb::open()?; let backup_data = db.export_all()?; let json = serde_json::to_vec(&backup_data)?; let encrypted = warzone_protocol::history::encrypt_history(&seed.0, &json); std::fs::write(&output, &encrypted)?; println!("Backup saved to {} ({} bytes encrypted)", output, encrypted.len()); } Commands::Restore { input } => { let encrypted = std::fs::read(&input)?; let json = warzone_protocol::history::decrypt_history(&seed.0, &encrypted) .map_err(|_| anyhow::anyhow!("Decryption failed — wrong seed?"))?; let backup_data: serde_json::Value = serde_json::from_slice(&json)?; let db = storage::LocalDb::open()?; let count = db.import_all(&backup_data)?; println!("Restored {} entries from {}", count, input); } } Ok(()) }