app
Codex
Public Made by Adomby adom
Codex in Adom Hydrogen: ecosystem setup, dock dashboard and live engineering demos.
main
John Lauer
Fix Codex Store agreement flags and recover transient launch observations; test on crr
9fee13e
1mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
use crate::process::{powershell, ps_quote, run};
use serde_json::{json, Value};
use std::{
env, fs,
path::{Path, PathBuf},
process::Command,
time::Duration,
};
pub const PACKAGE: &str = "OpenAI.Codex";
pub const STORE_ID: &str = "9PLM9XGG6VKS";
pub fn home() -> PathBuf {
env::var_os("CODEX_HOME")
.map(PathBuf::from)
.unwrap_or_else(|| {
PathBuf::from(
env::var_os("USERPROFILE")
.or_else(|| env::var_os("HOME"))
.unwrap_or_default(),
)
.join(".codex")
})
}
pub fn inventory() -> Result<Value, String> {
if !cfg!(windows) {
return Ok(json!({"installed":false,"platform":env::consts::OS,"supported":false}));
}
let result = powershell(
&format!(
r#"$ErrorActionPreference='Stop'; $p=Get-AppxPackage {PACKAGE};
$processes=@(Get-Process Codex,ChatGPT -ErrorAction SilentlyContinue | Where-Object {{$p -and $_.Path -and $_.Path.StartsWith($p.InstallLocation,[StringComparison]::OrdinalIgnoreCase)}} | ForEach-Object {{@{{pid=$_.Id;hwnd=$_.MainWindowHandle.ToInt64();title=$_.MainWindowTitle;path=$_.Path}}}});
$items=@($processes | Where-Object {{$_.hwnd -ne 0}});
if($p){{ @{{installed=$true;platform='windows';supported=$true;name=$p.Name;version=$p.Version.ToString();path=$p.InstallLocation;family=$p.PackageFamilyName;packageFullName=$p.PackageFullName;windows=$items;processes=$processes}} | ConvertTo-Json -Depth 5 -Compress }}
else {{ @{{installed=$false;platform='windows';supported=$true;windows=$items}} | ConvertTo-Json -Depth 5 -Compress }}"#
),
Duration::from_secs(15),
)?;
if result["success"] != true {
return Err(format!("desktop_inventory_failed: {result}"));
}
serde_json::from_str(
result["stdout"]
.as_str()
.unwrap_or("")
.trim_start_matches('\u{feff}')
.trim(),
)
.map_err(|e| format!("desktop_inventory_invalid_json: {e}; diagnostics={result}"))
}
pub fn executable() -> Result<PathBuf, String> {
if let Some(path) = env::var_os("CODEX_BRIDGE_CLI") {
let path = PathBuf::from(path);
if path.is_file() {
return Ok(path);
}
return Err("configured_cli_missing".into());
}
let inv = inventory()?;
let root = PathBuf::from(inv["path"].as_str().ok_or("desktop_not_installed")?);
if cfg!(windows) {
let result = powershell(
r#"$ErrorActionPreference='Stop'; $p=Get-AppxPackage OpenAI.Codex; $all=@(Get-CimInstance Win32_Process); $parents=@($all | Where-Object {$_.ExecutablePath -and $_.ExecutablePath.StartsWith($p.InstallLocation,[StringComparison]::OrdinalIgnoreCase)} | ForEach-Object {$_.ProcessId}); $cli=$all | Where-Object {$_.Name -eq 'codex.exe' -and $parents -contains $_.ParentProcessId} | Select-Object -First 1; if($cli){@{path=$cli.ExecutablePath}|ConvertTo-Json -Compress}"#,
Duration::from_secs(15),
)?;
if let Ok(value) =
serde_json::from_str::<Value>(result["stdout"].as_str().unwrap_or("").trim())
{
if let Some(path) = value["path"].as_str() {
let path = PathBuf::from(path);
if path.is_file() {
return Ok(path);
}
}
}
return Err("desktop_backend_not_running: open the desktop app first so it activates its bundled CLI".into());
}
for relative in [
"app/resources/codex.exe",
"app/resources/bin/codex.exe",
"resources/codex.exe",
] {
let path = root.join(relative);
if path.is_file() {
return Ok(path);
}
}
Err("desktop_cli_not_found".into())
}
fn install_args(accept: bool) -> Vec<&'static str> {
let mut args = vec![
"install",
"--id",
STORE_ID,
"--exact",
"--source",
"msstore",
"--silent",
"--disable-interactivity",
];
if accept {
args.extend(["--accept-source-agreements", "--accept-package-agreements"]);
}
args
}
pub fn install(accept: bool) -> Result<Value, String> {
if !cfg!(windows) {
return Err("unsupported_platform".into());
}
let before = inventory()?;
if before["installed"] == true {
return Ok(json!({"alreadyInstalled":true,"desktop":before}));
}
let mut cmd = Command::new("winget.exe");
let args = install_args(accept);
cmd.args(&args);
let mut result = match run(cmd, None, Duration::from_secs(900)) {
Ok(result) => result,
Err(error) => json!({"success":false,"error":error}),
};
result["program"] = json!("winget.exe");
result["args"] = json!(args);
Ok(install_result(accept, result, inventory()))
}
fn install_result(accept: bool, result: Value, after: Result<Value, String>) -> Value {
let after = match after {
Ok(after) => after,
Err(error) => {
return json!({"success":false,"errorCode":"install_verification_failed",
"installer":result,"inventoryError":error,
"_hint":"The installer may have completed. Check codex_status before retrying installation."});
}
};
if after["installed"] != true {
let agreement_blocked =
!accept && matches!(result["exitCode"].as_i64(), Some(-1978335162 | -1978335167));
return json!({"success":false,"errorCode":if agreement_blocked {"blocked_on_user"} else {"install_failed"},
"blockedBy":if agreement_blocked {json!("Microsoft Store source and package agreements")} else {Value::Null},
"installer":result,"desktop":after,"_hint":"Inspect installer.args, exitCode, stdout and stderr. Only after the user accepts BOTH Microsoft Store source and package agreements on this target, call codex_desktop_install with acceptAgreements:true."});
}
json!({"desktop":after,"installer":result})
}
pub fn uninstall() -> Result<Value, String> {
let before = inventory()?;
if before["installed"] != true {
return Ok(json!({"alreadyAbsent":true}));
}
if before["processes"]
.as_array()
.is_some_and(|a| !a.is_empty())
{
return Err("desktop_running_close_it_first".into());
}
let result=powershell(&format!("$ErrorActionPreference='Stop'; Get-AppxPackage {PACKAGE} | Remove-AppxPackage -ErrorAction Stop"),Duration::from_secs(180))?;
let after = inventory()?;
Ok(
json!({"success":result["success"]==true && after["installed"]==false,"desktop":after,
"removal":result,"preservedCodexHome":home()}),
)
}
pub fn launch() -> Result<Value, String> {
let inv = inventory()?;
if inv["installed"] != true {
return Err("desktop_not_installed".into());
}
if inv["windows"].as_array().is_some_and(|a| !a.is_empty()) {
return Ok(inv);
}
let family = inv["family"].as_str().ok_or("missing_package_family")?;
let script=format!("$ErrorActionPreference='Stop'; Start-Process explorer.exe -ArgumentList {} -WindowStyle Minimized",ps_quote(&format!("shell:AppsFolder\\{family}!App")));
let result = powershell(&script, Duration::from_secs(15))?;
if result["success"] != true {
return Err(format!("desktop_launch_failed: {result}"));
}
observe_window(
inventory,
|| std::thread::sleep(Duration::from_millis(500)),
30,
)
}
fn observe_window(
mut inspect: impl FnMut() -> Result<Value, String>,
mut pause: impl FnMut(),
attempts: usize,
) -> Result<Value, String> {
let mut failures = 0;
let mut last_error = None;
for attempt in 0..attempts {
match inspect() {
Ok(mut inv) if inv["windows"].as_array().is_some_and(|a| !a.is_empty()) => {
if failures > 0 {
inv["observationWarnings"] =
json!({"failedPolls":failures,"lastError":last_error});
}
return Ok(inv);
}
Ok(_) => {}
// App activation can race a package/process query. Observe again, never relaunch.
Err(error) => {
failures += 1;
last_error = Some(error);
}
}
if attempt + 1 < attempts {
pause();
}
}
Err(format!(
"desktop_window_not_observed: {}",
json!({"failedPolls":failures,"lastInventoryError":last_error,
"_hint":"Activation was sent once. Check codex_status and the app window before retrying open."})
))
}
pub fn auth_import(path: &Path) -> Result<Value, String> {
auth_import_to(path, &home())
}
fn auth_import_to(path: &Path, dir: &Path) -> Result<Value, String> {
use std::io::Write;
if !path.is_absolute() {
return Err("auth_source_must_be_absolute".into());
}
if fs::metadata(path)
.map_err(|_| "auth_source_unreadable")?
.len()
> 128 * 1024
{
return Err("auth_source_too_large".into());
}
let bytes = fs::read(path).map_err(|_| "auth_source_unreadable")?;
if bytes.len() > 128 * 1024 {
return Err("auth_source_too_large".into());
}
let auth: Value = serde_json::from_slice(&bytes).map_err(|_| "invalid_auth_json")?;
if !auth["tokens"]["access_token"].is_string() && !auth["OPENAI_API_KEY"].is_string() {
return Err("unsupported_auth_cache".into());
}
fs::create_dir_all(dir).map_err(|_| "auth_directory_failed")?;
let dest = dir.join("auth.json");
let mut options = fs::OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600);
}
let mut file = options
.open(&dest)
.map_err(|_| "auth_destination_exists_or_unwritable")?;
if cfg!(windows) {
let secured=powershell(&format!("$ErrorActionPreference='Stop'; $acl=Get-Acl {}; $acl.SetAccessRuleProtection($true,$false); $rule=New-Object System.Security.AccessControl.FileSystemAccessRule([System.Security.Principal.WindowsIdentity]::GetCurrent().Name,'FullControl','Allow'); $acl.SetAccessRule($rule); Set-Acl -Path {} -AclObject $acl",ps_quote(&dest.to_string_lossy()),ps_quote(&dest.to_string_lossy())),Duration::from_secs(10))?;
if secured["success"] != true {
drop(file);
let _ = fs::remove_file(&dest);
return Err("auth_permissions_failed".into());
}
}
if file
.write_all(&bytes)
.and_then(|_| file.sync_all())
.is_err()
{
drop(file);
let _ = fs::remove_file(&dest);
return Err("auth_write_failed".into());
}
Ok(
json!({"imported":true,"credentialValuesReturned":false,"destination":dest,
"_hint":"Call codex_account_read to verify authenticated identity. Existing destination credentials are never overwritten."}),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn installer_diagnostics_survive_verification_failure() {
let installer = json!({"exitCode":0,"args":install_args(true),"stdout":"Installed"});
let result = install_result(
true,
installer.clone(),
Err("desktop_inventory_failed: timed out".into()),
);
assert_eq!(result["errorCode"], "install_verification_failed");
assert_eq!(result["installer"], installer);
}
#[test]
fn unrelated_install_errors_are_not_reported_as_missing_consent() {
for code in [-1978335162, -1978335167] {
assert_eq!(
install_result(
false,
json!({"exitCode":code}),
Ok(json!({"installed":false}))
)["errorCode"],
"blocked_on_user"
);
}
for installer in [json!({"exitCode":1}), json!({"error":"winget unavailable"})] {
assert_eq!(
install_result(false, installer, Ok(json!({"installed":false})))["errorCode"],
"install_failed"
);
}
}
#[test]
fn launch_observation_recovers_after_transient_inventory_error() {
let mut results = [
Err("desktop_inventory_failed: transient".into()),
Ok(json!({"windows":[]})),
Ok(json!({"windows":[{"hwnd":42,"title":"Sign in"}]})),
]
.into_iter();
let mut pauses = 0;
let result = observe_window(|| results.next().unwrap(), || pauses += 1, 3).unwrap();
assert_eq!(result["windows"][0]["hwnd"], 42);
assert_eq!(result["observationWarnings"]["failedPolls"], 1);
assert_eq!(pauses, 2);
}
#[test]
fn launch_observation_failure_is_bounded_and_preserves_diagnostics() {
let mut calls = 0;
let error = observe_window(
|| {
calls += 1;
Err("desktop_inventory_failed: test detail".into())
},
|| {},
3,
)
.unwrap_err();
assert_eq!(calls, 3);
assert!(error.starts_with("desktop_window_not_observed:"));
assert!(error.contains("test detail"));
assert!(observe_window(|| Ok(json!({"windows":[]})), || {}, 2).is_err());
}
#[test]
fn install_requires_consent_for_both_agreement_flags() {
let args = install_args(false);
assert!(!args.iter().any(|arg| arg.starts_with("--accept-")));
let accepted = install_args(true);
assert_eq!(&accepted[..args.len()], args);
assert_eq!(
&accepted[args.len()..],
["--accept-source-agreements", "--accept-package-agreements"]
);
assert!(args.contains(&"--disable-interactivity"));
assert!(args.windows(2).any(|pair| pair == ["--id", STORE_ID]));
assert!(args.windows(2).any(|pair| pair == ["--source", "msstore"]));
}
#[test]
fn import_preserves_existing_credentials_and_returns_no_values() {
let root = env::temp_dir().join(format!("codex-auth-test-{}", uuid::Uuid::new_v4()));
fs::create_dir_all(&root).unwrap();
let source = root.join("source.json");
let dest = root.join("home");
fs::write(
&source,
br#"{"tokens":{"access_token":"test-only-placeholder"}}"#,
)
.unwrap();
let result = auth_import_to(&source, &dest).unwrap();
assert!(!result.to_string().contains("test-only-placeholder"));
assert!(auth_import_to(&source, &dest).is_err());
assert_eq!(
fs::read(&source).unwrap(),
fs::read(dest.join("auth.json")).unwrap()
);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
assert_eq!(
fs::metadata(dest.join("auth.json"))
.unwrap()
.permissions()
.mode()
& 0o777,
0o600
);
}
fs::remove_dir_all(root).unwrap();
}
}