mirror of
https://github.com/openai/codex.git
synced 2026-04-28 02:11:08 +03:00
## Summary
- Factor `load_config_as_toml` into `core::config_loader` so config
loading is reusable across callers.
- Layer `~/.codex/config.toml`, optional `~/.codex/managed_config.toml`,
and macOS managed preferences (base64) with recursive table merging and
scoped threads per source.
## Config Flow
```
Managed prefs (macOS profile: com.openai.codex/config_toml_base64)
▲
│
~/.codex/managed_config.toml │ (optional file-based override)
▲
│
~/.codex/config.toml (user-defined settings)
```
- The loader searches under the resolved `CODEX_HOME` directory
(defaults to `~/.codex`).
- Managed configs let administrators ship fleet-wide overrides via
device profiles which is useful for enforcing certain settings like
sandbox or approval defaults.
- For nested hash tables: overlays merge recursively. Child tables are
merged key-by-key, while scalar or array values replace the prior layer
entirely. This lets admins add or tweak individual fields without
clobbering unrelated user settings.
208 lines
6.0 KiB
Rust
208 lines
6.0 KiB
Rust
use codex_app_server_protocol::AuthMode;
|
|
use codex_common::CliConfigOverrides;
|
|
use codex_core::CodexAuth;
|
|
use codex_core::auth::CLIENT_ID;
|
|
use codex_core::auth::login_with_api_key;
|
|
use codex_core::auth::logout;
|
|
use codex_core::config::Config;
|
|
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<()> {
|
|
let opts = ServerOptions::new(codex_home, CLIENT_ID.to_string());
|
|
let server = run_login_server(opts)?;
|
|
|
|
eprintln!(
|
|
"Starting local login server on http://localhost:{}.\nIf your browser did not open, navigate to this URL to authenticate:\n\n{}",
|
|
server.actual_port, server.auth_url,
|
|
);
|
|
|
|
server.block_until_done().await
|
|
}
|
|
|
|
pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
|
|
let config = load_config_or_exit(cli_config_overrides).await;
|
|
|
|
match login_with_chatgpt(config.codex_home).await {
|
|
Ok(_) => {
|
|
eprintln!("Successfully logged in");
|
|
std::process::exit(0);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Error logging in: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn run_login_with_api_key(
|
|
cli_config_overrides: CliConfigOverrides,
|
|
api_key: String,
|
|
) -> ! {
|
|
let config = load_config_or_exit(cli_config_overrides).await;
|
|
|
|
match login_with_api_key(&config.codex_home, &api_key) {
|
|
Ok(_) => {
|
|
eprintln!("Successfully logged in");
|
|
std::process::exit(0);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Error logging in: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
issuer_base_url: Option<String>,
|
|
client_id: Option<String>,
|
|
) -> ! {
|
|
let config = load_config_or_exit(cli_config_overrides).await;
|
|
let mut opts = ServerOptions::new(
|
|
config.codex_home,
|
|
client_id.unwrap_or(CLIENT_ID.to_string()),
|
|
);
|
|
if let Some(iss) = issuer_base_url {
|
|
opts.issuer = iss;
|
|
}
|
|
match run_device_code_login(opts).await {
|
|
Ok(()) => {
|
|
eprintln!("Successfully logged in");
|
|
std::process::exit(0);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Error logging in with device code: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
|
|
let config = load_config_or_exit(cli_config_overrides).await;
|
|
|
|
match CodexAuth::from_codex_home(&config.codex_home) {
|
|
Ok(Some(auth)) => match auth.mode {
|
|
AuthMode::ApiKey => match auth.get_token().await {
|
|
Ok(api_key) => {
|
|
eprintln!("Logged in using an API key - {}", safe_format_key(&api_key));
|
|
std::process::exit(0);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Unexpected error retrieving API key: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
},
|
|
AuthMode::ChatGPT => {
|
|
eprintln!("Logged in using ChatGPT");
|
|
std::process::exit(0);
|
|
}
|
|
},
|
|
Ok(None) => {
|
|
eprintln!("Not logged in");
|
|
std::process::exit(1);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Error checking login status: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn run_logout(cli_config_overrides: CliConfigOverrides) -> ! {
|
|
let config = load_config_or_exit(cli_config_overrides).await;
|
|
|
|
match logout(&config.codex_home) {
|
|
Ok(true) => {
|
|
eprintln!("Successfully logged out");
|
|
std::process::exit(0);
|
|
}
|
|
Ok(false) => {
|
|
eprintln!("Not logged in");
|
|
std::process::exit(0);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Error logging out: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn load_config_or_exit(cli_config_overrides: CliConfigOverrides) -> Config {
|
|
let cli_overrides = match cli_config_overrides.parse_overrides() {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
eprintln!("Error parsing -c overrides: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
let config_overrides = ConfigOverrides::default();
|
|
match Config::load_with_cli_overrides(cli_overrides, config_overrides).await {
|
|
Ok(config) => config,
|
|
Err(e) => {
|
|
eprintln!("Error loading configuration: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn safe_format_key(key: &str) -> String {
|
|
if key.len() <= 13 {
|
|
return "***".to_string();
|
|
}
|
|
let prefix = &key[..8];
|
|
let suffix = &key[key.len() - 5..];
|
|
format!("{prefix}***{suffix}")
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::safe_format_key;
|
|
|
|
#[test]
|
|
fn formats_long_key() {
|
|
let key = "sk-proj-1234567890ABCDE";
|
|
assert_eq!(safe_format_key(key), "sk-proj-***ABCDE");
|
|
}
|
|
|
|
#[test]
|
|
fn short_key_returns_stars() {
|
|
let key = "sk-proj-12345";
|
|
assert_eq!(safe_format_key(key), "***");
|
|
}
|
|
}
|