feat: for codex exec, if PROMPT is not specified, read from stdin if not a TTY (#1178)
This attempts to make `codex exec` more flexible in how the prompt can be passed: * as before, it can be passed as a single string argument * if `-` is passed as the value, the prompt is read from stdin * if no argument is passed _and stdin is a tty_, prints a warning to stderr that no prompt was specified an exits non-zero. * if no argument is passed _and stdin is NOT a tty_, prints `Reading prompt from stdin...` to stderr to let the user know that Codex will wait until it reads EOF from stdin to proceed. (You can repro this case by doing `yes | just exec` since stdin is not a TTY in that case but it also never reaches EOF).
This commit is contained in:
@@ -45,8 +45,10 @@ pub struct Cli {
|
|||||||
#[arg(long = "output-last-message")]
|
#[arg(long = "output-last-message")]
|
||||||
pub last_message_file: Option<PathBuf>,
|
pub last_message_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// Initial instructions for the agent.
|
/// Initial instructions for the agent. If not provided as an argument (or
|
||||||
pub prompt: String,
|
/// if `-` is used), instructions are read from stdin.
|
||||||
|
#[arg(value_name = "PROMPT")]
|
||||||
|
pub prompt: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ mod cli;
|
|||||||
mod event_processor;
|
mod event_processor;
|
||||||
|
|
||||||
use std::io::IsTerminal;
|
use std::io::IsTerminal;
|
||||||
|
use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -40,6 +41,41 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
|
|||||||
config_overrides,
|
config_overrides,
|
||||||
} = cli;
|
} = cli;
|
||||||
|
|
||||||
|
// Determine the prompt based on CLI arg and/or stdin.
|
||||||
|
let prompt = match prompt {
|
||||||
|
Some(p) if p != "-" => p,
|
||||||
|
// Either `-` was passed or no positional arg.
|
||||||
|
maybe_dash => {
|
||||||
|
// When no arg (None) **and** stdin is a TTY, bail out early – unless the
|
||||||
|
// user explicitly forced reading via `-`.
|
||||||
|
let force_stdin = matches!(maybe_dash.as_deref(), Some("-"));
|
||||||
|
|
||||||
|
if std::io::stdin().is_terminal() && !force_stdin {
|
||||||
|
eprintln!(
|
||||||
|
"No prompt provided. Either specify one as an argument or pipe the prompt into stdin."
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the user knows we are waiting on stdin, as they may
|
||||||
|
// have gotten into this state by mistake. If so, and they are not
|
||||||
|
// writing to stdin, Codex will hang indefinitely, so this should
|
||||||
|
// help them debug in that case.
|
||||||
|
if !force_stdin {
|
||||||
|
eprintln!("Reading prompt from stdin...");
|
||||||
|
}
|
||||||
|
let mut buffer = String::new();
|
||||||
|
if let Err(e) = std::io::stdin().read_to_string(&mut buffer) {
|
||||||
|
eprintln!("Failed to read prompt from stdin: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
} else if buffer.trim().is_empty() {
|
||||||
|
eprintln!("No prompt provided via stdin.");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let (stdout_with_ansi, stderr_with_ansi) = match color {
|
let (stdout_with_ansi, stderr_with_ansi) = match color {
|
||||||
cli::Color::Always => (true, true),
|
cli::Color::Always => (true, true),
|
||||||
cli::Color::Never => (false, false),
|
cli::Color::Never => (false, false),
|
||||||
|
|||||||
Reference in New Issue
Block a user