Storage: - Detects sled lock contention, shows actionable error: "Database locked by another warzone process" with ps command to find the process and rm command to force unlock TUI: - Poll loop no longer calls load_seed() (was re-prompting passphrase) - Seed passed from main.rs to run_tui to poll_loop - Single passphrase prompt per app launch Warnings fixed: - Removed unused `Context` import in tui/app.rs - Added #[allow(dead_code)] on validate_token (used when auth middleware wired) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
109 lines
3.3 KiB
Rust
109 lines
3.3 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,
|
|
},
|
|
/// 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,
|
|
},
|
|
}
|
|
|
|
#[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::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?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|