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 <noreply@anthropic.com>
This commit is contained in:
leetcrypt 2026-06-01 22:31:48 -07:00
parent b4c5f9a9fa
commit ee9d0f7ff9

View File

@ -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<String>,
#[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<String> {
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", &{