@ondeinference / cli / commits / 62b1298

fix: exit 0 when stdout is not a TTY (winget headless validator)

The winget submission validator runs the binary with no arguments and no attached terminal. The bare `onde` code path immediately calls `enable_raw_mode()` / `EnterAlternateScreen`, which fail without a TTY and exit non-zero — triggering Validation-Executable-Error. Add a no-TTY guard (via `IsTerminal`) before `redirect_output()` so onde prints a usage summary and exits 0 when stdout is not a terminal. Also add explicit `--version`/`-V` and `--help`/`-h` handlers that work the same way, since the validator also exercises those flags. Closes #11 Co-Authored-By: siGit Code v1.6.7-acp <noreply@sigit.si>

Seto Elkahfi committed Oct 11, 2026 at 22:26 UTC 62b1298b0d608a8b861fc2d8466006d3e57fc1ce
1 file changed +37 -1
src/main.rs
+37 -1
@@ -13,7 +13,22 @@ mod project;
13 mod token;
14 mod ui;
15
16 -use std::io::Write;
16 +use std::io::{IsTerminal, Write};
17 +
18 +/// One-line usage, printed for `--help` and whenever `onde` is launched
19 +/// without an attached terminal.
20 +fn print_usage() {
21 + println!(
22 + "onde {}\n{}\n\n\
23 + Usage:\n \
24 + onde Launch the interactive terminal UI (requires a TTY)\n \
25 + onde --mcp Run the MCP JSON-RPC server over stdio\n \
26 + onde --version Print the version and exit\n \
27 + onde --help Print this help and exit",
28 + env!("CARGO_PKG_VERSION"),
29 + env!("CARGO_PKG_DESCRIPTION"),
30 + );
31 +}
32
33 /// Redirect **both** stdout and stderr to `~/.cache/onde/debug.log` before
34 /// ratatui takes over the alternate screen.
@@ -81,6 +96,27 @@ async fn main() -> anyhow::Result<()> {
96 return mcp::serve().await;
97 }
98
99 + // Bare `--version`/`--help` must work without a terminal.
100 + let args: Vec<String> = std::env::args().skip(1).collect();
101 + if args.iter().any(|arg| arg == "--version" || arg == "-V") {
102 + println!("onde {}", env!("CARGO_PKG_VERSION"));
103 + return Ok(());
104 + }
105 + if args.iter().any(|arg| arg == "--help" || arg == "-h") {
106 + print_usage();
107 + return Ok(());
108 + }
109 +
110 + // The TUI needs a real terminal. When stdout isn't a TTY — e.g. the winget
111 + // submission validator runs the executable headlessly with no arguments —
112 + // enabling raw mode / the alternate screen below fails and the process
113 + // exits non-zero (Validation-Executable-Error). Print usage and exit 0
114 + // instead of launching ratatui into a pipe.
115 + if !std::io::stdout().is_terminal() {
116 + print_usage();
117 + return Ok(());
118 + }
119 +
120 let tty = redirect_output();
121
122 // Build a writer that targets the real terminal. When