use std::path::Path;
use serde::Deserialize;
pub(crate) fn codex_program() -> std::result::Result<String, String> {
if let Some(program) = std::env::var_os(crate::sessions_control::CODEX_BIN_ENV) {
if !program.is_empty() {
let path = std::path::PathBuf::from(program);
if !path.is_absolute() {
return Err("SUPERCODE_CODEX_BIN must name an absolute executable path".into());
}
return Ok(path.to_string_lossy().into_owned());
}
}
let home = supercode_interchange::user_home()
.ok_or_else(|| "the executing user has no home for its consolidated Codex".to_string())?;
Ok(home
.join(".local/bin")
.join(if cfg!(windows) { "codex.cmd" } else { "codex" })
.to_string_lossy()
.into_owned())
}
pub(crate) fn codex_arguments(given: &[String]) -> Vec<String> {
let mut normalized = vec!["-c".into(), "check_for_update_on_startup=false".into()];
let mut index = 0;
while index < given.len() {
let argument = &given[index];
if argument == "--" {
normalized.extend_from_slice(&given[index..]);
break;
}
if argument == "-c" || argument == "--config" {
if given.get(index + 1).is_some_and(|value| {
value.split('=').next().map(str::trim) == Some("check_for_update_on_startup")
}) {
index += 2;
continue;
}
} else if argument
.strip_prefix("--config=")
.or_else(|| {
argument
.strip_prefix("-c")
.map(|value| value.trim_start_matches('='))
})
.is_some_and(|value| {
value.split('=').next().map(str::trim) == Some("check_for_update_on_startup")
})
{
index += 1;
continue;
}
normalized.push(argument.clone());
index += 1;
}
normalized
}
#[derive(Deserialize)]
struct Prompts {
startup: Vec<Vec<String>>,
unattended: Vec<String>,
unattended_chosen_by: Vec<String>,
}
fn table() -> std::collections::BTreeMap<String, Prompts> {
serde_json::from_str(include_str!("startup_prompts.json"))
.expect("startup_prompts.json is the harness crate's own table")
}
pub fn startup_arguments(
harness: &str,
cwd: Option<&Path>,
given: &[String],
unattended: bool,
) -> Vec<String> {
let table = table();
let Some(prompts) = table.get(harness) else {
return Vec::new();
};
let cwd = cwd.map(|cwd| {
serde_json::to_string(cwd.to_string_lossy().as_ref())
.expect("a filesystem path always serializes as JSON text")
});
let mut arguments = Vec::new();
for pair in &prompts.startup {
let needs_cwd = pair.iter().any(|flag| flag.contains("{cwd}"));
if needs_cwd && cwd.is_none() {
continue;
}
let key = pair
.last()
.and_then(|flag| flag.split('=').next())
.unwrap_or_default();
if !key.is_empty() && given.iter().any(|argument| argument.contains(key)) {
continue;
}
arguments.extend(pair.iter().map(|flag| match &cwd {
Some(cwd) => flag.replace("{cwd}", cwd),
None => flag.clone(),
}));
}
let chosen = given.iter().any(|argument| {
prompts
.unattended_chosen_by
.iter()
.any(|flag| argument == flag || argument.starts_with(&format!("{flag}=")))
});
if unattended && !chosen {
arguments.extend(prompts.unattended.iter().cloned());
}
arguments
}