supercode_harness/
startup_prompts.rs1use std::path::Path;
11
12use serde::Deserialize;
13
14pub(crate) fn codex_program() -> std::result::Result<String, String> {
15 if let Some(program) = std::env::var_os(crate::sessions_control::CODEX_BIN_ENV) {
16 if !program.is_empty() {
17 let path = std::path::PathBuf::from(program);
18 if !path.is_absolute() {
19 return Err("SUPERCODE_CODEX_BIN must name an absolute executable path".into());
20 }
21 return Ok(path.to_string_lossy().into_owned());
22 }
23 }
24 let home = supercode_interchange::user_home()
25 .ok_or_else(|| "the executing user has no home for its consolidated Codex".to_string())?;
26 Ok(home
27 .join(".local/bin")
28 .join(if cfg!(windows) { "codex.cmd" } else { "codex" })
29 .to_string_lossy()
30 .into_owned())
31}
32
33pub(crate) fn codex_arguments(given: &[String]) -> Vec<String> {
34 let mut normalized = vec!["-c".into(), "check_for_update_on_startup=false".into()];
35 let mut index = 0;
36 while index < given.len() {
37 let argument = &given[index];
38 if argument == "--" {
39 normalized.extend_from_slice(&given[index..]);
40 break;
41 }
42 if argument == "-c" || argument == "--config" {
43 if given.get(index + 1).is_some_and(|value| {
44 value.split('=').next().map(str::trim) == Some("check_for_update_on_startup")
45 }) {
46 index += 2;
47 continue;
48 }
49 } else if argument
50 .strip_prefix("--config=")
51 .or_else(|| {
52 argument
53 .strip_prefix("-c")
54 .map(|value| value.trim_start_matches('='))
55 })
56 .is_some_and(|value| {
57 value.split('=').next().map(str::trim) == Some("check_for_update_on_startup")
58 })
59 {
60 index += 1;
61 continue;
62 }
63 normalized.push(argument.clone());
64 index += 1;
65 }
66 normalized
67}
68
69#[derive(Deserialize)]
70struct Prompts {
71 startup: Vec<Vec<String>>,
72 unattended: Vec<String>,
73 unattended_chosen_by: Vec<String>,
74}
75
76fn table() -> std::collections::BTreeMap<String, Prompts> {
77 serde_json::from_str(include_str!("startup_prompts.json"))
78 .expect("startup_prompts.json is the harness crate's own table")
79}
80
81pub fn startup_arguments(
85 harness: &str,
86 cwd: Option<&Path>,
87 given: &[String],
88 unattended: bool,
89) -> Vec<String> {
90 let table = table();
91 let Some(prompts) = table.get(harness) else {
92 return Vec::new();
93 };
94 let cwd = cwd.map(|cwd| {
95 serde_json::to_string(cwd.to_string_lossy().as_ref())
96 .expect("a filesystem path always serializes as JSON text")
97 });
98 let mut arguments = Vec::new();
99 for pair in &prompts.startup {
100 let needs_cwd = pair.iter().any(|flag| flag.contains("{cwd}"));
101 if needs_cwd && cwd.is_none() {
102 continue;
103 }
104 let key = pair
105 .last()
106 .and_then(|flag| flag.split('=').next())
107 .unwrap_or_default();
108 if !key.is_empty() && given.iter().any(|argument| argument.contains(key)) {
109 continue;
110 }
111 arguments.extend(pair.iter().map(|flag| match &cwd {
112 Some(cwd) => flag.replace("{cwd}", cwd),
113 None => flag.clone(),
114 }));
115 }
116 let chosen = given.iter().any(|argument| {
117 prompts
118 .unattended_chosen_by
119 .iter()
120 .any(|flag| argument == flag || argument.starts_with(&format!("{flag}=")))
121 });
122 if unattended && !chosen {
123 arguments.extend(prompts.unattended.iter().cloned());
124 }
125 arguments
126}