use super::layers;
use crate::context::{Context, Environment};
use crate::host::Os;
use std::path::{Path, PathBuf};
pub(crate) const AUTH_FILE: &str = "auth.json";
pub(crate) fn home(ctx: &Context) -> PathBuf {
match ctx.codex_home() {
Some(dir) if !dir.is_empty() => match ctx.os {
Os::MacOs | Os::Linux => PathBuf::from(dir),
Os::Windows => std::fs::canonicalize(dir)
.map_or_else(|_| PathBuf::from(dir), crate::host::without_verbatim_prefix),
},
_ => ctx.profile.as_deref().unwrap_or(ctx.home()).join(".codex"),
}
}
pub(crate) fn missing_home(ctx: &Context) -> Option<PathBuf> {
let dir = ctx.codex_home().filter(|dir| !dir.is_empty())?;
(ctx.os == Os::Windows && !Path::new(dir).is_dir()).then(|| PathBuf::from(dir))
}
pub(crate) fn auth_file(ctx: &Context) -> PathBuf {
home(ctx).join(AUTH_FILE)
}
pub(crate) fn install_places(env: &Environment) -> Vec<PathBuf> {
let standalone = match env.os() {
Os::MacOs | Os::Linux => Some(env.home().join(".local").join("bin")),
Os::Windows => env
.named_folder("CODEX_INSTALL_DIR")
.filter(|dir| !dir.to_string_lossy().trim().is_empty())
.or_else(|| {
let mut dir = env.named_folder("LOCALAPPDATA")?;
dir.extend(["Programs", "OpenAI", "Codex", "bin"]);
Some(dir)
}),
};
standalone
.into_iter()
.chain(env.os().package_bins(env))
.collect()
}
pub(crate) fn npm_native(script: &Path) -> Option<(PathBuf, crate::provider::Launched)> {
let named = |path: &Path, name: &str| {
path.file_name()
.is_some_and(|its| its.eq_ignore_ascii_case(name))
};
let bin = script.parent().filter(|bin| named(bin, "bin"))?;
let package = bin.parent().filter(|package| named(package, "codex"))?;
package.parent().filter(|scope| named(scope, "@openai"))?;
if !named(script, "codex.js") {
return None;
}
let program = NPM_TARGETS.iter().find_map(|(target, platform)| {
let vendor = bin
.ancestors()
.filter(|dir| !named(dir, "node_modules"))
.map(|dir| dir.join("node_modules").join("@openai").join(platform))
.find(|found| found.join("package.json").is_file())
.unwrap_or_else(|| package.to_path_buf())
.join("vendor");
let program = vendor.join(target).join("bin").join("codex.exe");
crate::host::proc::can_run(&program).then_some(program)
})?;
let root = std::fs::canonicalize(package).map_or_else(
|_| package.to_path_buf(),
crate::host::without_verbatim_prefix,
);
let mut set: crate::provider::Launched = vec![
("CODEX_MANAGED_PACKAGE_ROOT", Some(root.into_os_string())),
("CODEX_MANAGED_BY_NPM", Some("1".into())),
];
set.extend(
[
"CODEX_MANAGED_BY_BUN",
"CODEX_MANAGED_BY_PNPM",
"CODEX_MANAGED_BY_VITE_PLUS",
]
.map(|other| (other, None)),
);
Some((program, set))
}
const NPM_TARGETS: [(&str, &str); 2] = if cfg!(target_arch = "aarch64") {
[
("aarch64-pc-windows-msvc", "codex-win32-arm64"),
("x86_64-pc-windows-msvc", "codex-win32-x64"),
]
} else {
[
("x86_64-pc-windows-msvc", "codex-win32-x64"),
("aarch64-pc-windows-msvc", "codex-win32-arm64"),
]
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Backend {
File,
Keyring,
Either,
Ephemeral,
Secrets,
Unknown,
}
pub(crate) fn config_file(ctx: &Context) -> PathBuf {
home(ctx).join("config.toml")
}
pub(crate) fn store(ctx: &Context) -> layers::Store {
layers::of(ctx, &config_file(ctx))
}
pub(crate) fn sign_in_store(ctx: &Context) -> layers::Store {
layers::of_a_sign_in(ctx, &config_file(ctx))
}
#[cfg(test)]
#[allow(
clippy::disallowed_methods,
reason = "a test sets up its scratch files as a person or a tool would"
)]
mod tests {
use super::*;
fn backend(ctx: &Context) -> Backend {
store(ctx).backend
}
fn keyring_or(alone: Backend) -> Backend {
match crate::host::OS {
Os::MacOs | Os::Linux => alone,
Os::Windows => Backend::Secrets,
}
}
fn at(dir: &Path, config: &str) -> Context {
std::fs::create_dir_all(dir).expect("a scratch codex home");
std::fs::write(dir.join("config.toml"), config).expect("a config");
Context::for_unit_test().with_codex_home(dir.to_string_lossy().into())
}
fn scratch(name: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!(
"pitboard-codex-{name}-{}-{:?}",
std::process::id(),
std::thread::current().id()
));
let _ = std::fs::remove_dir_all(&dir);
dir
}
#[test]
fn codex_finds_its_home_on_windows_as_codex_does() {
use crate::context::Environment;
use crate::host::Os;
let profile = scratch("profile");
std::fs::create_dir_all(profile.join("codex")).expect("a scratch codex home");
std::fs::write(profile.join("file"), "").expect("a file");
let named = |path: PathBuf| path.to_string_lossy().into_owned();
let read = |os: Os, codex: Option<PathBuf>| {
let pairs = [
("USERPROFILE", named(profile.clone())),
("HOME", named(profile.clone())),
]
.into_iter()
.chain(codex.map(|dir| ("CODEX_HOME", named(dir))));
Context::read(&Environment::on(os, pairs), "cli")
};
let handed = Context::new(profile.clone());
assert_eq!(home(&handed), profile.join(".codex"), "a handed home's");
let unset = read(Os::Windows, None);
assert_eq!(
home(&unset),
profile.join(".codex"),
"no profile is looked up in a test"
);
let elsewhere = profile.join("Profile");
assert_eq!(
home(&unset.with_profile(elsewhere.clone())),
elsewhere.join(".codex"),
"not the home, where the profile is elsewhere"
);
let given = read(Os::Windows, Some(profile.join(".").join("codex")));
let canonical = std::fs::canonicalize(profile.join("codex")).expect("there");
assert_eq!(
home(&given),
crate::host::without_verbatim_prefix(canonical)
);
assert_eq!(missing_home(&given), None);
for nothing in [profile.join("never-made"), profile.join("file")] {
assert_eq!(
missing_home(&read(Os::Windows, Some(nothing.clone()))),
Some(nothing)
);
}
for os in [Os::MacOs, Os::Linux] {
let missing = profile.join("never-made");
let ctx = read(os, Some(missing.clone()));
assert_eq!((home(&ctx), missing_home(&ctx)), (missing, None), "{os:?}");
}
let _ = std::fs::remove_dir_all(&profile);
}
#[test]
fn no_setting_means_the_file() {
let dir = scratch("default");
let ctx = at(&dir, "model = \"gpt-5\"\n");
assert_eq!(backend(&ctx), Backend::File);
assert_eq!(auth_file(&ctx), home(&ctx).join("auth.json"));
assert_eq!(
std::fs::canonicalize(home(&ctx)).ok(),
std::fs::canonicalize(&dir).ok()
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn each_store_is_recognised() {
let dir = scratch("stores");
for (written, expected) in [
(
"cli_auth_credentials_store = \"keyring\"",
keyring_or(Backend::Keyring),
),
(
"cli_auth_credentials_store=\"auto\"",
keyring_or(Backend::Either),
),
(
" cli_auth_credentials_store = 'ephemeral'",
Backend::Ephemeral,
),
("cli_auth_credentials_store = \"file\"", Backend::File),
(
"cli_auth_credentials_store = \"nonsense\"",
Backend::Unknown,
),
("cli_auth_credentials_store = keyring", Backend::Unknown),
] {
let ctx = at(&dir, written);
assert_eq!(backend(&ctx), expected, "{written}");
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_comment_after_the_value_is_not_the_value() {
let dir = scratch("comment");
let ctx = at(
&dir,
"cli_auth_credentials_store = \"keyring\" # on this mac\n",
);
assert_eq!(backend(&ctx), keyring_or(Backend::Keyring));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn only_the_top_level_key_is_the_setting() {
let dir = scratch("tables");
for (config, expected, why) in [
(
"[profiles.work]\ncli_auth_credentials_store = \"keyring\"\n",
Backend::File,
"a key inside another table is not the setting",
),
(
"cli_auth_credentials_store = \"keyring\"\n\
[features]\nsecret_auth_storage = true\n",
Backend::Secrets,
"an encrypted file whose key is in the keychain is a store of its own",
),
(
"[features]\nsecret_auth_storage = true\n",
Backend::File,
"the feature only changes a keyring store",
),
] {
assert_eq!(backend(&at(&dir, config)), expected, "{why}");
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn the_system_layers_are_read_as_well_as_the_persons_own() {
let dir = scratch("system");
let host = crate::host::memory::MemoryHost::new();
let ctx = at(&dir, "model = \"gpt-5\"\n").with_memory_stores(host.clone());
let system = |name| layers::system_file(&ctx, name);
host.administers(
system("config.toml"),
"cli_auth_credentials_store = \"keyring\"\n",
);
assert_eq!(backend(&ctx), keyring_or(Backend::Keyring));
std::fs::write(
dir.join("config.toml"),
"cli_auth_credentials_store = \"file\"\n",
)
.expect("a config");
assert_eq!(backend(&ctx), Backend::File);
host.administers(
system("requirements.toml"),
"cli_auth_credentials_store = \"keyring\"\n",
);
assert_eq!(backend(&ctx), keyring_or(Backend::Keyring));
assert_eq!(
sign_in_store(&ctx).backend,
keyring_or(Backend::Keyring),
"a requirement pins a sign-in's store too"
);
let _ = std::fs::remove_dir_all(&dir);
}
}