1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Source: ~/claudecode/openclaudecode/src/utils/renderOptions.ts
use std::sync::OnceLock;
/// Cached stdin override - computed once per process.
static STDIN_OVERRIDE: OnceLock<Option<std::fs::File>> = OnceLock::new();
/// Gets a File for /dev/tty when stdin is piped.
/// This allows interactive rendering even when stdin is a pipe.
/// Result is cached for the lifetime of the process.
fn get_stdin_override() -> Option<&'static std::fs::File> {
STDIN_OVERRIDE
.get_or_init(|| {
// No override needed if stdin is already a TTY
if atty::is(atty::Stream::Stdin) {
return None;
}
// Skip in CI environments (localized: CI)
if is_env_truthy(std::env::var("CI").ok()) {
return None;
}
// Skip if running MCP (input hijacking breaks MCP)
let args: Vec<String> = std::env::args().collect();
if args.iter().any(|a| a == "mcp") {
return None;
}
// No /dev/tty on Windows
if cfg!(target_os = "windows") {
return None;
}
// Try to open /dev/tty as an alternative input source
std::fs::File::open("/dev/tty").ok()
})
.as_ref()
}
/// Check if an environment variable is truthy.
fn is_env_truthy(value: Option<String>) -> bool {
match value {
Some(v) => {
let v = v.to_lowercase();
v == "1" || v == "true" || v == "yes" || v == "on"
}
None => false,
}
}
/// Render options for the TUI.
#[derive(Debug, Clone, Default)]
pub struct RenderOptions {
/// Whether to exit on Ctrl+C.
pub exit_on_ctrl_c: bool,
}
/// Returns base render options, including stdin override when needed.
/// Use this for all render calls to ensure piped input works correctly.
pub fn get_base_render_options(exit_on_ctrl_c: bool) -> RenderOptions {
let _stdin = get_stdin_override();
RenderOptions { exit_on_ctrl_c }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_env_truthy() {
assert!(is_env_truthy(Some("1".to_string())));
assert!(is_env_truthy(Some("true".to_string())));
assert!(!is_env_truthy(None));
assert!(!is_env_truthy(Some("0".to_string())));
}
#[test]
fn test_get_base_render_options() {
let opts = get_base_render_options(false);
assert!(!opts.exit_on_ctrl_c);
let opts = get_base_render_options(true);
assert!(opts.exit_on_ctrl_c);
}
}