diff --git a/codex-rs/cli/src/login.rs b/codex-rs/cli/src/login.rs index 2b497c06..cd20e2ba 100644 --- a/codex-rs/cli/src/login.rs +++ b/codex-rs/cli/src/login.rs @@ -9,6 +9,8 @@ use codex_core::config::ConfigOverrides; use codex_login::ServerOptions; use codex_login::run_device_code_login; use codex_login::run_login_server; +use std::io::IsTerminal; +use std::io::Read; use std::path::PathBuf; pub async fn login_with_chatgpt(codex_home: PathBuf) -> std::io::Result<()> { @@ -56,6 +58,33 @@ pub async fn run_login_with_api_key( } } +pub fn read_api_key_from_stdin() -> String { + let mut stdin = std::io::stdin(); + + if stdin.is_terminal() { + eprintln!( + "--with-api-key expects the API key on stdin. Try piping it, e.g. `printenv OPENAI_API_KEY | codex login --with-api-key`." + ); + std::process::exit(1); + } + + eprintln!("Reading API key from stdin..."); + + let mut buffer = String::new(); + if let Err(err) = stdin.read_to_string(&mut buffer) { + eprintln!("Failed to read API key from stdin: {err}"); + std::process::exit(1); + } + + let api_key = buffer.trim().to_string(); + if api_key.is_empty() { + eprintln!("No API key provided via stdin."); + std::process::exit(1); + } + + api_key +} + /// Login using the OAuth device code flow. pub async fn run_login_with_device_code( cli_config_overrides: CliConfigOverrides, diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 10e26215..51c13742 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -7,6 +7,7 @@ use codex_chatgpt::apply_command::ApplyCommand; use codex_chatgpt::apply_command::run_apply_command; use codex_cli::LandlockCommand; use codex_cli::SeatbeltCommand; +use codex_cli::login::read_api_key_from_stdin; use codex_cli::login::run_login_status; use codex_cli::login::run_login_with_api_key; use codex_cli::login::run_login_with_chatgpt; @@ -139,7 +140,18 @@ struct LoginCommand { #[clap(skip)] config_overrides: CliConfigOverrides, - #[arg(long = "api-key", value_name = "API_KEY")] + #[arg( + long = "with-api-key", + help = "Read the API key from stdin (e.g. `printenv OPENAI_API_KEY | codex login --with-api-key`)" + )] + with_api_key: bool, + + #[arg( + long = "api-key", + value_name = "API_KEY", + help = "(deprecated) Previously accepted the API key directly; now exits with guidance to use --with-api-key", + hide = true + )] api_key: Option, /// EXPERIMENTAL: Use device code flow (not yet supported) @@ -298,7 +310,13 @@ async fn cli_main(codex_linux_sandbox_exe: Option) -> anyhow::Result<() login_cli.client_id, ) .await; - } else if let Some(api_key) = login_cli.api_key { + } else if login_cli.api_key.is_some() { + eprintln!( + "The --api-key flag is no longer supported. Pipe the key instead, e.g. `printenv OPENAI_API_KEY | codex login --with-api-key`." + ); + std::process::exit(1); + } else if login_cli.with_api_key { + let api_key = read_api_key_from_stdin(); run_login_with_api_key(login_cli.config_overrides, api_key).await; } else { run_login_with_chatgpt(login_cli.config_overrides).await; diff --git a/docs/authentication.md b/docs/authentication.md index 0db35489..d2d129f8 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -5,9 +5,17 @@ If you prefer to pay-as-you-go, you can still authenticate with your OpenAI API key: ```shell -codex login --api-key "your-api-key-here" +printenv OPENAI_API_KEY | codex login --with-api-key ``` +Alternatively, read from a file: + +```shell +codex login --with-api-key < my_key.txt +``` + +The legacy `--api-key` flag now exits with an error instructing you to use `--with-api-key` so that the key never appears in shell history or process listings. + This key must, at minimum, have write access to the Responses API. ## Migrating to ChatGPT login from API key