From d7616cdeef4e2c5af459a869c50ec476a684c87a Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 24 May 2026 09:46:28 +0000 Subject: [PATCH] fix(cua-driver-rs): TTY hint instead of silent stdin hang on bare invocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bare \`cua-driver\` (no subcommand) defaults to MCP mode and reads JSON-RPC from stdin forever. When invoked from an interactive terminal that looks like a hang — users see no output and assume the binary is broken. Add an \`IsTerminal\` check (stable since Rust 1.70): if stdin is a TTY, print a hint listing the common subcommands and exit cleanly. Piped / redirected stdin — the normal MCP client case (Claude Code, Claude Desktop, etc.) — falls through to MCP mode unchanged. Explicit \`cua-driver mcp\` bypasses the check for power users who want to pipe JSON-RPC by hand. Hint goes to stderr to avoid any risk of contaminating an MCP wire, exit code 0 since we're successfully diagnosing the situation. --- .../crates/cua-driver/src/cli.rs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/libs/cua-driver-rs/crates/cua-driver/src/cli.rs b/libs/cua-driver-rs/crates/cua-driver/src/cli.rs index 265a88c818..699bcb5e18 100644 --- a/libs/cua-driver-rs/crates/cua-driver/src/cli.rs +++ b/libs/cua-driver-rs/crates/cua-driver/src/cli.rs @@ -172,7 +172,34 @@ pub fn parse_command() -> Command { let mut pos = positionals.into_iter(); match pos.next() { - None | Some("mcp") => Command::Mcp { + None => { + // Bare `cua-driver` defaults to MCP, which reads JSON-RPC from + // stdin forever. From a terminal that looks like a hang. If + // stdin is a TTY (i.e. interactive shell, no client piping + // stdio), surface a hint and exit. Piped / redirected stdin — + // the normal MCP client case — falls through to MCP mode. + // Explicit `cua-driver mcp` bypasses the check entirely. + use std::io::IsTerminal as _; + if std::io::stdin().is_terminal() { + eprintln!("cua-driver: bare invocation defaults to the MCP server, which reads"); + eprintln!("JSON-RPC from stdin. From a terminal that looks like a hang."); + eprintln!(); + eprintln!("You probably meant one of:"); + eprintln!(" cua-driver list-tools # available tools"); + eprintln!(" cua-driver status # check the daemon"); + eprintln!(" cua-driver mcp-config --client claude-code # wire into a client"); + eprintln!(" cua-driver --help # everything else"); + eprintln!(); + eprintln!("To run the MCP server explicitly (and pipe JSON-RPC by hand):"); + eprintln!(" cua-driver mcp"); + std::process::exit(0); + } + Command::Mcp { + no_daemon_relaunch, + socket: socket.clone(), + } + } + Some("mcp") => Command::Mcp { no_daemon_relaunch, socket: socket.clone(), },