Skip to main content

supercode_harness/
startup_prompts.rs

1//! The flags that take each harness past its routine startup prompts, in one table
2//! (`startup_prompts.json`, which the TypeScript SDK reads as its own copy) for every launch path:
3//! `supercode open --new`, a resume, a native start. `startup` pairs answer the prompts every launch
4//! meets (Codex's new-version check, its trust-this-folder question) and keep a pane scrollable (Codex
5//! drawn inline, not on the alternate screen, where a pane's wheel turns into arrow keys); `unattended` flags answer the
6//! approval prompts of a launch nobody watches, unless the caller chose its own
7//! (`unattended_chosen_by`). `{cwd}` in a flag is the launch's directory as a JSON string, the form a
8//! Codex `-c projects={…}` table takes it in; a pair that needs it is left out without one.
9
10use std::path::Path;
11
12use serde::Deserialize;
13
14#[derive(Deserialize)]
15struct Prompts {
16    startup: Vec<Vec<String>>,
17    unattended: Vec<String>,
18    unattended_chosen_by: Vec<String>,
19}
20
21fn table() -> std::collections::BTreeMap<String, Prompts> {
22    serde_json::from_str(include_str!("startup_prompts.json"))
23        .expect("startup_prompts.json is the harness crate's own table")
24}
25
26/// The arguments `harness` is launched with to open past its routine prompts, ahead of the
27/// caller's own `given` arguments. A startup flag the caller already sets (its key, the text before
28/// `=`, appears in `given`) is left to the caller.
29pub fn startup_arguments(
30    harness: &str,
31    cwd: Option<&Path>,
32    given: &[String],
33    unattended: bool,
34) -> Vec<String> {
35    let table = table();
36    let Some(prompts) = table.get(harness) else {
37        return Vec::new();
38    };
39    let cwd = cwd.map(|cwd| {
40        serde_json::to_string(cwd.to_string_lossy().as_ref())
41            .expect("a filesystem path always serializes as JSON text")
42    });
43    let mut arguments = Vec::new();
44    for pair in &prompts.startup {
45        let needs_cwd = pair.iter().any(|flag| flag.contains("{cwd}"));
46        if needs_cwd && cwd.is_none() {
47            continue;
48        }
49        let key = pair
50            .last()
51            .and_then(|flag| flag.split('=').next())
52            .unwrap_or_default();
53        if !key.is_empty() && given.iter().any(|argument| argument.contains(key)) {
54            continue;
55        }
56        arguments.extend(pair.iter().map(|flag| match &cwd {
57            Some(cwd) => flag.replace("{cwd}", cwd),
58            None => flag.clone(),
59        }));
60    }
61    let chosen = given.iter().any(|argument| {
62        prompts
63            .unattended_chosen_by
64            .iter()
65            .any(|flag| argument == flag || argument.starts_with(&format!("{flag}=")))
66    });
67    if unattended && !chosen {
68        arguments.extend(prompts.unattended.iter().cloned());
69    }
70    arguments
71}