agent_first_http/cli/
args.rs1use clap::{Parser, Subcommand};
5
6use crate::shared::error::{Error, ErrorCode};
7
8pub const DISPLAY_NAME: &str = env!("DISPLAY_NAME");
9
10#[derive(Parser, Debug)]
11#[command(
12 name = env!("DISPLAY_NAME"),
13 bin_name = "afhttp",
14 version,
15 about = env!("CARGO_PKG_DESCRIPTION"),
16 long_about = concat!(env!("DISPLAY_NAME"), " - ", env!("CARGO_PKG_DESCRIPTION")),
17 disable_help_subcommand = true,
18)]
19pub struct Cli {
20 #[arg(
25 long = "output-to",
26 global = true,
27 default_value = "split",
28 value_parser = ["split", "stdout", "stderr"]
29 )]
30 pub output_to: String,
31
32 #[arg(long = "stdout-file", value_name = "PATH", global = true)]
34 pub stdout_file: Option<String>,
35
36 #[arg(long = "stderr-file", value_name = "PATH", global = true)]
38 pub stderr_file: Option<String>,
39
40 #[command(subcommand)]
41 pub command: Command,
42}
43
44#[derive(Subcommand, Debug)]
45#[command(disable_help_subcommand = true)]
46pub enum Command {
47 #[command(
49 long_about = "Use `--takeover` to keep a persistent tab open when a captcha, login, or \
50 2FA wall needs a human. The result returns the short-lived takeover URL in \
51 `next_action.takeover_url_secret` together with a command that re-fetches \
52 the same tab."
53 )]
54 Fetch(Box<crate::cli::cmd::fetch::Args>),
55 Host(crate::cli::cmd::host::Args),
57 Upload(crate::cli::cmd::upload::Args),
59 Cdp(crate::cli::cmd::cdp::Args),
61 Panel(crate::cli::cmd::panel::Args),
63 Health(crate::cli::cmd::health::Args),
65 Capabilities(crate::cli::cmd::capabilities::Args),
67 Profile(crate::cli::cmd::profile::Args),
69 Tabs(crate::cli::cmd::tabs::Args),
71 Skill(crate::cli::cmd::skill::Args),
73 Container(crate::cli::cmd::container::Args),
75}
76
77pub struct Parsed {
78 pub command: Command,
79}
80
81pub fn parse() -> Result<Parsed, Error> {
82 let cli = Cli::try_parse().map_err(|e| {
83 use clap::error::ErrorKind;
84 if matches!(e.kind(), ErrorKind::DisplayHelp | ErrorKind::DisplayVersion) {
87 let _ = e.print();
88 std::process::exit(0);
89 }
90 if matches!(
91 e.kind(),
92 ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand | ErrorKind::MissingSubcommand
93 ) {
94 return Error::new(
95 ErrorCode::InvalidArgument,
96 "a command is required; try: afhttp --help",
97 );
98 }
99 Error::new(ErrorCode::InvalidArgument, e.to_string())
102 })?;
103 let _stream_redirect_args = (&cli.stdout_file, &cli.stderr_file, &cli.output_to);
104 Ok(Parsed {
105 command: cli.command,
106 })
107}
108
109#[cfg(test)]
110mod tests {
111 use clap::CommandFactory;
112
113 use super::*;
114
115 #[test]
116 fn clap_command_flag_snapshot_matches() {
117 let mut snapshot = String::new();
118 write_command_snapshot(&Cli::command(), 0, &mut snapshot);
119 assert_eq!(
120 snapshot,
121 include_str!("../../tests/golden/cli-command-flags.txt")
122 );
123 }
124
125 #[test]
126 fn cli_contract_has_no_legacy_aliases() {
127 let command = Cli::command();
128 assert_eq!(command.get_subcommands().count(), 11);
129 let mut snapshot = String::new();
130 write_command_snapshot(&command, 0, &mut snapshot);
131 for forbidden in [
132 " command download\n",
133 " command takeover\n",
134 " command hard-site\n",
135 "--profile-name",
136 concat!("profile", "_name"),
137 concat!("?", "profile="),
138 "arg timeout --timeout\n",
139 "arg health --health\n",
140 "arg network_redact --network-redact\n",
141 "arg takeover_quality --takeover-quality\n",
142 "legacy",
143 ] {
144 assert!(
145 !snapshot.contains(forbidden),
146 "CLI contract retained forbidden legacy surface {forbidden:?}: {snapshot}"
147 );
148 }
149 }
150
151 fn write_command_snapshot(cmd: &clap::Command, depth: usize, out: &mut String) {
152 let indent = " ".repeat(depth);
153 out.push_str(&format!("{indent}command {}\n", cmd.get_name()));
154 for arg in cmd.get_arguments() {
155 let long = arg
156 .get_long()
157 .map(|v| format!(" --{v}"))
158 .unwrap_or_default();
159 out.push_str(&format!("{indent} arg {}{long}\n", arg.get_id()));
160 }
161 for sub in cmd.get_subcommands() {
162 write_command_snapshot(sub, depth + 1, out);
163 }
164 }
165}