Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
38 changes: 32 additions & 6 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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![])
Expand All @@ -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)
Expand Down
34 changes: 34 additions & 0 deletions cli/src/pty_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;<title>\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
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand Down
Loading