fix: drain tty input when displaying inquire prompts to prevent unintentional escapes

This commit is contained in:
2026-08-18 15:10:30 -06:00
parent a9a4ccca88
commit dffaf6b9db
3 changed files with 36 additions and 1 deletions
+29 -1
View File
@@ -35,6 +35,7 @@ use nu_ansi_term::Color;
use serde_json::Value; use serde_json::Value;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::io;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::{LazyLock, Mutex, OnceLock}; use std::sync::{LazyLock, Mutex, OnceLock};
use std::{cmp, env, path::PathBuf, process}; use std::{cmp, env, path::PathBuf, process};
@@ -45,7 +46,7 @@ pub static CODE_BLOCK_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?ms)```\w*(.*)```").unwrap()); LazyLock::new(|| Regex::new(r"(?ms)```\w*(.*)```").unwrap());
pub static THINK_TAG_RE: LazyLock<Regex> = pub static THINK_TAG_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?s)^\s*<think>.*?</think>(\s*|$)").unwrap()); LazyLock::new(|| Regex::new(r"(?s)^\s*<think>.*?</think>(\s*|$)").unwrap());
pub static IS_STDOUT_TERMINAL: LazyLock<bool> = LazyLock::new(|| std::io::stdout().is_terminal()); pub static IS_STDOUT_TERMINAL: LazyLock<bool> = LazyLock::new(|| io::stdout().is_terminal());
pub static HEADLESS: AtomicBool = AtomicBool::new(false); pub static HEADLESS: AtomicBool = AtomicBool::new(false);
pub static ACP_SERVER: AtomicBool = AtomicBool::new(false); pub static ACP_SERVER: AtomicBool = AtomicBool::new(false);
@@ -133,6 +134,33 @@ pub fn parse_bool(value: &str) -> Option<bool> {
} }
} }
pub fn drain_stale_tty_input() {
use crossterm::event::{poll, read};
use std::time::{Duration, Instant};
if !io::stdin().is_terminal() {
return;
}
if crossterm::terminal::enable_raw_mode().is_err() {
return;
}
let deadline = Instant::now() + Duration::from_millis(100);
while Instant::now() < deadline {
match poll(Duration::from_millis(10)) {
Ok(true) => {
if read().is_err() {
break;
}
}
_ => break,
}
}
let _ = crossterm::terminal::disable_raw_mode();
}
pub fn estimate_token_length(text: &str) -> usize { pub fn estimate_token_length(text: &str) -> usize {
let weighted: usize = text.chars().map(|c| if c.is_ascii() { 1 } else { 2 }).sum(); let weighted: usize = text.chars().map(|c| if c.is_ascii() { 1 } else { 2 }).sum();
weighted.div_ceil(4) weighted.div_ceil(4)
+3
View File
@@ -12,6 +12,7 @@ pub use utils::prompt_provider_choice;
use crate::cli::Cli; use crate::cli::Cli;
use crate::config::AppConfig; use crate::config::AppConfig;
use crate::utils::drain_stale_tty_input;
use crate::vault::utils::ensure_password_file_initialized; use crate::vault::utils::ensure_password_file_initialized;
use anyhow::{Context, Result, anyhow, bail}; use anyhow::{Context, Result, anyhow, bail};
use fancy_regex::Regex; use fancy_regex::Regex;
@@ -151,6 +152,7 @@ impl Vault {
"Vault management is disabled in sandbox mode. Use `coyote --add-secret` on your host." "Vault management is disabled in sandbox mode. Use `coyote --add-secret` on your host."
); );
} }
drain_stale_tty_input();
let secret_value = Password::new("Enter the secret value:") let secret_value = Password::new("Enter the secret value:")
.with_validator(required!()) .with_validator(required!())
.with_display_mode(PasswordDisplayMode::Masked) .with_display_mode(PasswordDisplayMode::Masked)
@@ -190,6 +192,7 @@ impl Vault {
"Vault management is disabled in sandbox mode. Use `coyote --add-secret` on your host." "Vault management is disabled in sandbox mode. Use `coyote --add-secret` on your host."
); );
} }
drain_stale_tty_input();
let secret_value = Password::new("Enter the secret value:") let secret_value = Password::new("Enter the secret value:")
.with_validator(required!()) .with_validator(required!())
.with_display_mode(PasswordDisplayMode::Masked) .with_display_mode(PasswordDisplayMode::Masked)
+4
View File
@@ -1,5 +1,6 @@
use crate::config::ensure_parent_exists; use crate::config::ensure_parent_exists;
use crate::sandbox::{SANDBOX_ENV_FLAG, sandbox_secret_env_var}; use crate::sandbox::{SANDBOX_ENV_FLAG, sandbox_secret_env_var};
use crate::utils::drain_stale_tty_input;
use crate::vault::{SECRET_RE, Vault}; use crate::vault::{SECRET_RE, Vault};
use anyhow::Result; use anyhow::Result;
use anyhow::anyhow; use anyhow::anyhow;
@@ -68,6 +69,7 @@ pub fn create_vault_password_file(vault: &mut Vault) -> Result<()> {
} }
} }
drain_stale_tty_input();
let ans = Confirm::new( let ans = Confirm::new(
format!( format!(
"The configured password file '{}' is empty. Create a password?", "The configured password file '{}' is empty. Create a password?",
@@ -107,6 +109,7 @@ pub fn create_vault_password_file(vault: &mut Vault) -> Result<()> {
} }
} }
} else { } else {
drain_stale_tty_input();
let ans = Confirm::new("No password file configured. Do you want to create one now?") let ans = Confirm::new("No password file configured. Do you want to create one now?")
.with_default(true) .with_default(true)
.prompt()?; .prompt()?;
@@ -185,6 +188,7 @@ pub fn create_vault_password_file(vault: &mut Vault) -> Result<()> {
} }
pub fn prompt_provider_choice() -> Result<Option<SupportedProvider>> { pub fn prompt_provider_choice() -> Result<Option<SupportedProvider>> {
drain_stale_tty_input();
let choices = vec![ let choices = vec![
"local - encrypted file on this machine", "local - encrypted file on this machine",
"aws_secrets_manager - AWS Secrets Manager", "aws_secrets_manager - AWS Secrets Manager",