Skip to main content

agent_first_http/cli/cmd/
argenums.rs

1//! Shared `clap::ValueEnum` wrappers for the closed-enum CLI flags.
2//!
3//! The SDK/host layer enums (`RenderMode`, `BrowserChoice`, `DisplayMode`, …)
4//! must not depend on `clap` — the `sdk`-only build has no clap. So the CLI
5//! layer keeps thin `ValueEnum` mirrors here and converts into the domain types
6//! at dispatch. Variants render in kebab-case by default, which gives each flag
7//! a validated value list, shell completion, and a uniform "invalid value …
8//! [possible values: …]" error for free.
9
10use clap::ValueEnum;
11
12use crate::host::bootstrap::{
13    BrowserChoice, DisplayMode, HealthPublic, Takeover, TakeoverProviderKind,
14};
15use crate::sdk::fetch::RenderMode;
16
17/// `--render` strategy.
18#[derive(ValueEnum, Clone, Copy, Debug, Default, PartialEq, Eq)]
19pub enum RenderArg {
20    /// HTTP fast path, no browser.
21    None,
22    /// HTTP first, escalate to the browser on failure.
23    #[default]
24    Auto,
25    /// Browser only.
26    Always,
27}
28
29impl From<RenderArg> for RenderMode {
30    fn from(v: RenderArg) -> Self {
31        match v {
32            RenderArg::None => RenderMode::None,
33            RenderArg::Auto => RenderMode::Auto,
34            RenderArg::Always => RenderMode::Always,
35        }
36    }
37}
38
39/// `--browser` backend. Kebab-case canonical names; the legacy underscore
40/// spellings stay accepted as hidden aliases.
41#[derive(ValueEnum, Clone, Copy, Debug, Default, PartialEq, Eq)]
42pub enum BrowserArg {
43    #[default]
44    Auto,
45    Chromium,
46    Chrome,
47    #[value(name = "chrome-headless-shell", alias = "chrome_shell")]
48    ChromeHeadlessShell,
49    #[value(name = "fingerprint-chromium", alias = "fingerprint_chromium")]
50    FingerprintChromium,
51    Edge,
52    Brave,
53    Lightpanda,
54    Camoufox,
55}
56
57impl From<BrowserArg> for BrowserChoice {
58    fn from(v: BrowserArg) -> Self {
59        match v {
60            BrowserArg::Auto => BrowserChoice::Auto,
61            BrowserArg::Chromium => BrowserChoice::Chromium,
62            BrowserArg::Chrome => BrowserChoice::Chrome,
63            BrowserArg::ChromeHeadlessShell => BrowserChoice::ChromeShell,
64            BrowserArg::FingerprintChromium => BrowserChoice::FingerprintChromium,
65            BrowserArg::Edge => BrowserChoice::Edge,
66            BrowserArg::Brave => BrowserChoice::Brave,
67            BrowserArg::Lightpanda => BrowserChoice::Lightpanda,
68            BrowserArg::Camoufox => BrowserChoice::Camoufox,
69        }
70    }
71}
72
73/// `--display` mode for the host.
74#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
75pub enum DisplayArg {
76    Headless,
77    Headful,
78}
79
80impl From<DisplayArg> for DisplayMode {
81    fn from(v: DisplayArg) -> Self {
82        match v {
83            DisplayArg::Headless => DisplayMode::Headless,
84            DisplayArg::Headful => DisplayMode::Headful,
85        }
86    }
87}
88
89/// `--health-public` mode.
90#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
91pub enum HealthPublicArg {
92    Off,
93    Minimal,
94}
95
96impl From<HealthPublicArg> for HealthPublic {
97    fn from(v: HealthPublicArg) -> Self {
98        match v {
99            HealthPublicArg::Off => HealthPublic::Off,
100            HealthPublicArg::Minimal => HealthPublic::Minimal,
101        }
102    }
103}
104
105/// `--takeover-provider` selector: `off` (or `none`) disables the takeover
106/// surface, a provider name selects it.
107#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
108pub enum TakeoverProviderArg {
109    #[value(alias = "none")]
110    Off,
111    Kasmvnc,
112}
113
114impl TakeoverProviderArg {
115    /// The provider name to serve, or `None` when takeover is off.
116    pub fn provider_name(self) -> Option<&'static str> {
117        match self {
118            TakeoverProviderArg::Off => None,
119            TakeoverProviderArg::Kasmvnc => Some("kasmvnc"),
120        }
121    }
122}
123
124impl From<TakeoverProviderArg> for Takeover {
125    fn from(v: TakeoverProviderArg) -> Self {
126        match v {
127            TakeoverProviderArg::Off => Takeover::Off,
128            TakeoverProviderArg::Kasmvnc => Takeover::On {
129                provider: TakeoverProviderKind::KasmVnc,
130            },
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    /// Every wrapper variant parses from its canonical kebab-case name (plus the
140    /// underscore aliases for the browser backends).
141    #[test]
142    fn value_enum_names_parse() {
143        for name in ["none", "auto", "always"] {
144            assert!(RenderArg::from_str(name, true).is_ok(), "{name}");
145        }
146        for name in [
147            "auto",
148            "chromium",
149            "chrome",
150            "chrome-headless-shell",
151            "chrome_shell",
152            "fingerprint-chromium",
153            "fingerprint_chromium",
154            "edge",
155            "brave",
156            "lightpanda",
157            "camoufox",
158        ] {
159            assert!(BrowserArg::from_str(name, true).is_ok(), "{name}");
160        }
161        assert!(TakeoverProviderArg::from_str("off", true).is_ok());
162        assert!(TakeoverProviderArg::from_str("none", true).is_ok());
163        assert!(TakeoverProviderArg::from_str("kasmvnc", true).is_ok());
164        assert!(BrowserArg::from_str("rocket", true).is_err());
165    }
166
167    #[test]
168    fn takeover_provider_maps_to_domain() {
169        assert_eq!(Takeover::from(TakeoverProviderArg::Off), Takeover::Off);
170        assert_eq!(TakeoverProviderArg::Off.provider_name(), None);
171        assert_eq!(
172            TakeoverProviderArg::Kasmvnc.provider_name(),
173            Some("kasmvnc")
174        );
175        assert_eq!(
176            Takeover::from(TakeoverProviderArg::Kasmvnc),
177            Takeover::On {
178                provider: TakeoverProviderKind::KasmVnc
179            }
180        );
181    }
182
183    #[test]
184    fn browser_maps_canonical_and_alias() {
185        assert_eq!(
186            BrowserChoice::from(BrowserArg::ChromeHeadlessShell),
187            BrowserChoice::ChromeShell
188        );
189        assert_eq!(
190            BrowserChoice::from(BrowserArg::from_str("chrome_shell", true).unwrap()),
191            BrowserChoice::ChromeShell
192        );
193    }
194
195    #[test]
196    fn toggles_map_to_bool_and_modes() {
197        assert_eq!(RenderMode::from(RenderArg::None), RenderMode::None);
198        assert_eq!(DisplayMode::from(DisplayArg::Headful), DisplayMode::Headful);
199        assert_eq!(
200            HealthPublic::from(HealthPublicArg::Minimal),
201            HealthPublic::Minimal
202        );
203    }
204}