147 lines
5.0 KiB
Rust
147 lines
5.0 KiB
Rust
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<String>,
|
|
},
|
|
/// 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<String>,
|
|
/// 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(())
|
|
}
|