From ee9d0f7ff90d22c6b9380134b55dc89cf7bdb191 Mon Sep 17 00:00:00 2001 From: leetcrypt Date: Mon, 1 Jun 2026 22:31:48 -0700 Subject: [PATCH] feat(client): prompt for a handle on join when none is given Make the connect `user` arg optional. When omitted, the client prompts "choose your handle" as the first thing on join (before the TUI opens) and re-prompts if the server rejects the name (e.g. already taken, 409). Passing a name on the CLI still works unchanged, so the demo script is unaffected. Co-Authored-By: Claude Opus 4.6 --- hh/src/main.rs | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/hh/src/main.rs b/hh/src/main.rs index 90d4e3f..b6ce37c 100644 --- a/hh/src/main.rs +++ b/hh/src/main.rs @@ -33,7 +33,8 @@ enum Cmd { Connect { ip: String, port: u16, - user: String, + /// Display name (handle) to join as. Omit to be prompted on join. + user: Option, #[arg(long)] password: String, #[arg(long, default_value_t = false)] @@ -86,11 +87,28 @@ fn main() -> Result<()> { insecure, theme, } => { - let session = net::authenticate(&ip, port, &user, &password, no_tls, insecure)?; + // Pick a handle and authenticate. If no name was given on the CLI, + // prompt for one as the first thing on join — and re-prompt if the + // server rejects it (e.g. the name is already taken in the room). + let interactive = user.is_none(); + let mut name = match user { + Some(u) => u, + None => prompt_handle()?, + }; + let session = loop { + match net::authenticate(&ip, port, &name, &password, no_tls, insecure) { + Ok(s) => break s, + Err(e) if interactive => { + eprintln!("✖ {e:#}\n that handle didn't work (taken or full?) — pick another."); + name = prompt_handle()?; + } + Err(e) => return Err(e), + } + }; let params = net::ConnParams { ip, port, - user, + user: name, password, no_tls, insecure, @@ -145,6 +163,25 @@ fn main() -> Result<()> { } } +/// Prompt for a display name on stdin before the TUI starts. Loops until a +/// non-empty handle is entered; errors only if stdin closes (EOF). +fn prompt_handle() -> Result { + use std::io::Write; + loop { + print!("⛧ choose your handle: "); + std::io::stdout().flush()?; + let mut s = String::new(); + if std::io::stdin().read_line(&mut s)? == 0 { + anyhow::bail!("no handle entered (stdin closed)"); + } + let name = s.trim(); + if !name.is_empty() { + return Ok(name.to_string()); + } + eprintln!("a handle can't be empty."); + } +} + fn selftest() -> Result<()> { // Re-derive the golden vectors at runtime as a smoke check. let c = crypto::SrpClient::with_a(b"chat", b"labtest", &{