Skip to content

Commit 7411257

Browse files
authored
feat(tty): Add no-input flag and tty checks for interactive commands
1 parent 7eca1fb commit 7411257

5 files changed

Lines changed: 48 additions & 0 deletions

File tree

src/auth.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,9 @@ pub fn login() {
261261
// Check if already authenticated
262262
if is_already_signed_in(&profile_config) {
263263
println!("{}", "You are already signed in.".green());
264+
if !crate::util::is_interactive() {
265+
return;
266+
}
264267
print!("Do you want to log in again? [y/N] ");
265268
use std::io::Write;
266269
std::io::stdout().flush().unwrap();

src/connections_new.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,15 @@ fn walk_auth(schema: &Value) -> Map<String, Value> {
260260
// ── Entry point ───────────────────────────────────────────────────────────────
261261

262262
pub fn run(workspace_id: &str) {
263+
if !crate::util::is_interactive() {
264+
eprintln!(
265+
"error: 'connections new' is interactive and stdin is not a TTY. \
266+
Use 'hotdata connections create list' to discover types and their config schemas, \
267+
then 'hotdata connections create --name <n> --type <t> --config '{{…}}''."
268+
);
269+
std::process::exit(1);
270+
}
271+
263272
let api = ApiClient::new(Some(workspace_id));
264273

265274
// Phase 1: Select connection type

src/main.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ struct Cli {
4646
#[arg(long, global = true, hide = true)]
4747
debug: bool,
4848

49+
/// Disable interactive prompts; commands that need input will error instead
50+
#[arg(long = "no-input", global = true)]
51+
no_input: bool,
52+
4953
#[command(subcommand)]
5054
command: Option<Commands>,
5155
}
@@ -134,6 +138,9 @@ fn main() {
134138
if cli.debug {
135139
util::set_debug(true);
136140
}
141+
if cli.no_input {
142+
util::set_no_input(true);
143+
}
137144

138145
let skip_skill_auto_update =
139146
cli.command.is_none() || matches!(&cli.command, Some(Commands::Skills { .. }));

src/util.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,27 @@ pub fn spinner(msg: &str) -> indicatif::ProgressBar {
1313
pb
1414
}
1515

16+
static NO_INPUT: AtomicBool = AtomicBool::new(false);
17+
18+
pub fn set_no_input(enabled: bool) {
19+
NO_INPUT.store(enabled, Ordering::Relaxed);
20+
}
21+
22+
/// Returns true if interactive prompts are usable. Returns false when:
23+
/// - the global `--no-input` flag was passed,
24+
/// - the `CI` env var is set (most CI runners set this),
25+
/// - stdin is not a TTY (piped, redirected, or invoked by an agent harness).
26+
pub fn is_interactive() -> bool {
27+
if NO_INPUT.load(Ordering::Relaxed) {
28+
return false;
29+
}
30+
if std::env::var_os("CI").is_some() {
31+
return false;
32+
}
33+
use std::io::IsTerminal;
34+
std::io::stdin().is_terminal()
35+
}
36+
1637
static DEBUG: AtomicBool = AtomicBool::new(false);
1738

1839
pub fn set_debug(enabled: bool) {

src/workspace.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ pub fn set(workspace_id: Option<&str>) {
4343
eprintln!("error: no workspaces available.");
4444
std::process::exit(1);
4545
}
46+
if !crate::util::is_interactive() {
47+
eprintln!(
48+
"error: stdin is not a TTY; cannot prompt for selection. \
49+
Run 'hotdata workspaces list' to see available IDs, \
50+
then 'hotdata workspaces set <workspace_id>'."
51+
);
52+
std::process::exit(1);
53+
}
4654
let options: Vec<String> = workspaces
4755
.iter()
4856
.map(|w| format!("{} ({})", w.name, w.public_id))

0 commit comments

Comments
 (0)