diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 72e11d6..1c240bc 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -1219,7 +1219,7 @@ dependencies = [ [[package]] name = "mobilecli" -version = "0.2.0" +version = "0.2.1" dependencies = [ "base64", "chrono", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 7b2004b..3fa62ef 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mobilecli" -version = "0.2.0" +version = "0.2.1" description = "Stream your terminal CLIs to your phone - shell hook edition" authors = ["MobileCLI"] edition = "2021" diff --git a/cli/src/main.rs b/cli/src/main.rs index 9fbaa88..0a50a86 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -130,12 +130,19 @@ async fn main() -> ExitCode { colored::control::set_virtual_terminal(true).ok(); } - // Initialize tracing + // Initialize tracing. + // + // Default to warn so the foreground UX (bare `mobilecli`, `mobilecli claude`, + // etc.) does not flash a wall of info-level logs at session start that then + // get wiped by tmux's alternate screen. Operators can opt back into verbose + // logs with `RUST_LOG=mobilecli=info` or `RUST_LOG=mobilecli=debug`. The + // background daemon process inherits this default but its own stderr is + // redirected to ~/.mobilecli/daemon.log, so its log volume is unchanged from + // the user's perspective. + let env_filter = tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("mobilecli=warn")); tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::from_default_env() - .add_directive("mobilecli=info".parse().unwrap()), - ) + .with_env_filter(env_filter) .with_target(false) .init(); @@ -253,7 +260,8 @@ async fn main() -> ExitCode { } // Determine what command to run - let (command, args) = if run_args.args.is_empty() { + let bare_invocation = run_args.args.is_empty(); + let (command, args) = if bare_invocation { // Use cross-platform shell detection let shell = platform::default_shell(); (shell, vec![]) @@ -263,6 +271,24 @@ async fn main() -> ExitCode { (command, args) }; + // For bare `mobilecli` invocations from an interactive terminal, surface a + // one-line discoverability hint pointing at `mobilecli help`. The hint is + // printed before the session starts and is intentionally short so it does + // not crowd the "Connected!" banner that follows from pty_wrapper. + if bare_invocation && !run_args.quiet { + use std::io::IsTerminal; + if std::io::stdout().is_terminal() { + println!( + "{} Tip: run {} to see commands like {}, {}, {}.", + "β†’".dimmed(), + "`mobilecli help`".cyan(), + "claude".cyan(), + "codex".cyan(), + "pair".cyan() + ); + } + } + // Generate session name let session_name = run_args.session_name.unwrap_or_else(|| { std::path::Path::new(&command) diff --git a/cli/src/pty_wrapper.rs b/cli/src/pty_wrapper.rs index af75151..b025329 100644 --- a/cli/src/pty_wrapper.rs +++ b/cli/src/pty_wrapper.rs @@ -184,6 +184,27 @@ fn request_terminal_resize(cols: u16, rows: u16) { set_stdout_winsize(cols, rows); } +/// Set the host terminal window title via OSC 0. Pass an empty string to clear. +/// +/// Skipped when stdout is not a TTY to avoid leaking escape sequences into +/// pipes/logs. OSC 0 (`\x1b]0;\x07`) sets both the icon name and the +/// window title and is supported by every mainstream terminal emulator we +/// target. Title characters that could themselves terminate the sequence (BEL, +/// ESC, control bytes) are stripped to avoid corrupting downstream output. +fn set_host_terminal_title(title: &str) { + if !std::io::stdout().is_terminal() { + return; + } + let sanitized: String = title + .chars() + .filter(|c| !c.is_control()) + .take(120) + .collect(); + let mut stdout = std::io::stdout(); + let _ = write!(stdout, "\x1b]0;{}\x07", sanitized); + let _ = stdout.flush(); +} + fn tmux_base_command(socket_name: &str) -> Command { let mut cmd = Command::new("tmux"); // Use platform-appropriate null device @@ -655,6 +676,14 @@ pub async fn run_wrapped(config: WrapConfig) -> Result<i32, WrapError> { ); } + // Set the host terminal window title to advertise the linked state. + // OSC 0 sets both icon name and window title; survives tmux alt-screen + // because tmux's `set-titles` defaults to off (and we leave it off), so + // the host emulator retains whatever title was last set on its stdout. + // The next shell prompt after session end (or our explicit reset below) + // restores a normal title. + set_host_terminal_title(&format!("πŸ“± MobileCLI Β· {}", config.session_name)); + // Create PTY let pty_system = native_pty_system(); let (cols, rows) = get_terminal_size(); @@ -1177,6 +1206,11 @@ pub async fn run_wrapped(config: WrapConfig) -> Result<i32, WrapError> { print!("\x1bc"); let _ = std::io::Write::flush(&mut std::io::stdout()); + // Clear the MobileCLI window title set at session start. We emit an empty + // OSC 0 string; the next shell prompt (PROMPT_COMMAND/precmd) will set a + // normal title on its own. + set_host_terminal_title(""); + // Print exit message println!(); if exit_code == 0 { </div><div class="naked_ctrl"> <form action="/index.cgi/contrast" method="get" name="gate"> <p><a href="http://altstyle.alfasado.net">AltStyle</a> γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ <a href="https://patch-diff.githubusercontent.com/raw/MobileCLI/mobilecli/pull/35.diff">(->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«)</a> / <label>をドレス: <input type="text" name="naked_post_url" value="https://patch-diff.githubusercontent.com/raw/MobileCLI/mobilecli/pull/35.diff" size="22" /></label> <label>ヒード: <select name="naked_post_mode"> <option value="default">γƒ‡γƒ•γ‚©γƒ«γƒˆ</option> <option value="speech">ιŸ³ε£°γƒ–γƒ©γ‚¦γ‚Ά</option> <option value="ruby">γƒ«γƒ“δ»˜γ</option> <option value="contrast" selected="selected">配色反軒</option> <option value="larger-text">ζ–‡ε­—ζ‹‘ε€§</option> <option value="mobile">ヒバむル</option> </select> <input type="submit" value="葨瀺" /> </p> </form> </div>