diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 9b4a4e32d4..056e05e1a9 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -648,6 +648,7 @@ version = "0.0.0" dependencies = [ "clap", "codex-core", + "dotenvy", "serde", "toml 0.9.1", ] @@ -794,6 +795,7 @@ version = "0.0.0" dependencies = [ "anyhow", "assert_cmd", + "codex-common", "codex-core", "codex-linux-sandbox", "mcp-types", @@ -1272,6 +1274,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "dupe" version = "0.9.1" diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 7e23782d75..f60bc90a49 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -9,6 +9,7 @@ use codex_cli::SeatbeltCommand; use codex_cli::login::run_login_with_chatgpt; use codex_cli::proto; use codex_common::CliConfigOverrides; +use codex_common::load_dotenv; use codex_exec::Cli as ExecCli; use codex_tui::Cli as TuiCli; use std::path::PathBuf; @@ -99,6 +100,8 @@ fn main() -> anyhow::Result<()> { } async fn cli_main(codex_linux_sandbox_exe: Option) -> anyhow::Result<()> { + load_dotenv(); + let cli = MultitoolCli::parse(); match cli.subcommand { diff --git a/codex-rs/common/Cargo.toml b/codex-rs/common/Cargo.toml index 3b843181cf..c7a59e4881 100644 --- a/codex-rs/common/Cargo.toml +++ b/codex-rs/common/Cargo.toml @@ -9,11 +9,12 @@ workspace = true [dependencies] clap = { version = "4", features = ["derive", "wrap_help"], optional = true } codex-core = { path = "../core" } +dotenvy = { version = "0.15.7", optional = true } toml = { version = "0.9", optional = true } serde = { version = "1", optional = true } [features] # Separate feature so that `clap` is not a mandatory dependency. -cli = ["clap", "toml", "serde"] +cli = ["clap", "dotenvy", "toml", "serde"] elapsed = [] sandbox_summary = [] diff --git a/codex-rs/common/src/dotenv.rs b/codex-rs/common/src/dotenv.rs new file mode 100644 index 0000000000..7a5a4ca276 --- /dev/null +++ b/codex-rs/common/src/dotenv.rs @@ -0,0 +1,7 @@ +/// Load env vars from ~/.codex/.env and `$(pwd)/.env`. +pub fn load_dotenv() { + if let Ok(codex_home) = codex_core::config::find_codex_home() { + dotenvy::from_path(codex_home.join(".env")).ok(); + } + dotenvy::dotenv().ok(); +} diff --git a/codex-rs/common/src/lib.rs b/codex-rs/common/src/lib.rs index 3d498a8e2c..f9559060e5 100644 --- a/codex-rs/common/src/lib.rs +++ b/codex-rs/common/src/lib.rs @@ -1,6 +1,12 @@ #[cfg(feature = "cli")] mod approval_mode_cli_arg; +#[cfg(feature = "cli")] +mod dotenv; + +#[cfg(feature = "cli")] +pub use dotenv::load_dotenv; + #[cfg(feature = "elapsed")] pub mod elapsed; diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs index 8ed06c45af..2dfd3e55fe 100644 --- a/codex-rs/core/src/config.rs +++ b/codex-rs/core/src/config.rs @@ -561,7 +561,7 @@ fn default_model() -> String { /// function will Err if the path does not exist. /// - If `CODEX_HOME` is not set, this function does not verify that the /// directory exists. -fn find_codex_home() -> std::io::Result { +pub fn find_codex_home() -> std::io::Result { // Honor the `CODEX_HOME` environment variable when it is set to allow users // (and tests) to override the default location. if let Ok(val) = std::env::var("CODEX_HOME") { diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 620ab82327..9e6a36628c 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -10,6 +10,7 @@ use std::path::PathBuf; use std::sync::Arc; pub use cli::Cli; +use codex_common::load_dotenv; use codex_core::codex_wrapper; use codex_core::config::Config; use codex_core::config::ConfigOverrides; @@ -31,6 +32,8 @@ use tracing_subscriber::EnvFilter; use crate::event_processor::EventProcessor; pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> anyhow::Result<()> { + load_dotenv(); + let Cli { images, model, diff --git a/codex-rs/mcp-server/Cargo.toml b/codex-rs/mcp-server/Cargo.toml index e524576a88..14c64f2672 100644 --- a/codex-rs/mcp-server/Cargo.toml +++ b/codex-rs/mcp-server/Cargo.toml @@ -16,6 +16,7 @@ workspace = true [dependencies] anyhow = "1" +codex-common = { path = "../common", features = ["cli"] } codex-core = { path = "../core" } codex-linux-sandbox = { path = "../linux-sandbox" } mcp-types = { path = "../mcp-types" } diff --git a/codex-rs/mcp-server/src/lib.rs b/codex-rs/mcp-server/src/lib.rs index 300d1b5fe5..6cead4e46d 100644 --- a/codex-rs/mcp-server/src/lib.rs +++ b/codex-rs/mcp-server/src/lib.rs @@ -4,6 +4,7 @@ use std::io::Result as IoResult; use std::path::PathBuf; +use codex_common::load_dotenv; use mcp_types::JSONRPCMessage; use tokio::io::AsyncBufReadExt; use tokio::io::AsyncWriteExt; @@ -38,6 +39,8 @@ pub use crate::patch_approval::PatchApprovalResponse; const CHANNEL_CAPACITY: usize = 128; pub async fn run_main(codex_linux_sandbox_exe: Option) -> IoResult<()> { + load_dotenv(); + // Install a simple subscriber so `tracing` output is visible. Users can // control the log level with `RUST_LOG`. tracing_subscriber::fmt()