From 03d80067a88cf3bb60d18869fa6f0c4b0c01c87a Mon Sep 17 00:00:00 2001 From: Helios Agent Date: Thu, 5 Mar 2026 20:22:58 +0100 Subject: [PATCH] feat: structured startup header with privileges, device, session --- crates/client/src/display.rs | 16 +++++----------- crates/client/src/main.rs | 26 +++++++++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/crates/client/src/display.rs b/crates/client/src/display.rs index f7ffe5f..ee79f75 100644 --- a/crates/client/src/display.rs +++ b/crates/client/src/display.rs @@ -100,19 +100,13 @@ pub fn cmd_done(action: &str, name: &str, payload: &str, success: bool, result: crate::logger::write_line(if success { "OK" } else { "ERROR" }, &format!("{name} {payload} → {result}")); } -/// Plain status line (banner / connection info), not in table format. -/// Uses 2 leading spaces + emoji + 2 spaces + message (no tab). -pub fn status(emoji: &str, msg: &str) { - println!(" {} {}", emoji, msg); -} - -/// Same as status() but also logs to file. -pub fn ok(emoji: &str, msg: &str) { - println!(" {} {}", emoji, msg); - crate::logger::write_line("OK", msg); +/// Info line for the startup header (not in table format). +/// Aligns key to a fixed width so values line up. +pub fn info_line(emoji: &str, key: &str, value: &str) { + println!(" {} {:<12} {}", emoji_cell(emoji), key, value); } pub fn err(emoji: &str, msg: &str) { - println!(" {} {}", emoji, msg.red()); + println!(" {} {}", emoji_cell(emoji), msg.red()); crate::logger::write_line("ERROR", msg); } diff --git a/crates/client/src/main.rs b/crates/client/src/main.rs index c7231bd..1606502 100644 --- a/crates/client/src/main.rs +++ b/crates/client/src/main.rs @@ -26,16 +26,24 @@ use display::trunc; fn banner() { println!(); println!(" {} HELIOS REMOTE ({})", "☀".yellow().bold(), env!("GIT_COMMIT")); +} + +fn print_session_info(label: &str, sid: &uuid::Uuid) { #[cfg(windows)] { let admin = is_admin(); - let (icon, admin_str) = if admin { - ("👑", "admin".green().bold().to_string()) + let priv_str = if admin { + "admin".green().bold().to_string() } else { - ("👤", "user (no admin)".yellow().to_string()) + "no admin".yellow().to_string() }; - println!(" {} {}", icon, admin_str); + display::info_line("👤", "privileges:", &priv_str); } + #[cfg(not(windows))] + display::info_line("👤", "privileges:", &"no admin".yellow().to_string()); + + display::info_line("🖥", "device:", &label.bold().to_string()); + display::info_line("#️⃣", "session:", &sid.to_string().dimmed().to_string()); println!(); } @@ -154,14 +162,14 @@ async fn main() { let config = match Config::load() { Some(c) => c, None => { - display::status("ℹ", "No config found — first-time setup"); + display::info_line("ℹ", "setup:", "No config found — first-time setup"); println!(); let c = prompt_config(); println!(); if let Err(e) = c.save() { display::err("❌", &format!("Failed to save config: {e}")); } else { - display::ok("✅", "Config saved"); + display::info_line("✅", "config:", "saved"); } c } @@ -181,6 +189,9 @@ async fn main() { } }; + let label = config.label.clone().unwrap_or_else(|| hostname()); + print_session_info(&label, &sid); + let config = Arc::new(config); let shell = Arc::new(Mutex::new(shell::PersistentShell::new())); @@ -206,10 +217,7 @@ async fn main() { match connect_async_tls_with_config(&config.relay_url, None, false, Some(connector)).await { Ok((ws_stream, _)) => { - let label = config.label.clone().unwrap_or_else(|| hostname()); display::cmd_done("🌐", "connecting", host, true, "connected"); - display::cmd_start("ℹ", "session", &label); - display::cmd_done("ℹ", "session", &label, true, &sid.to_string()); println!(); backoff = Duration::from_secs(1);