use std::ffi::OsString;
use clap::{Args, Subcommand};
use crate::clients::ClientKind;
#[must_use]
pub fn protect_client_arguments(mut arguments: Vec<OsString>, nested: bool) -> Vec<OsString> {
let start = if nested {
arguments
.iter()
.position(|argument| argument == "with")
.map_or(arguments.len(), |position| position + 1)
} else {
1
};
let value_options = [
"--server",
"--token",
"--model",
"--run-ttl-hours",
"--run-max-requests",
];
let clients = [
"codex",
"claude-code",
"claude",
"cursor",
"gemini-cli",
"gemini",
"grok-cli",
"grok",
"opencode",
"qwen-code",
"qwen",
"agent",
];
let mut position = start;
while position < arguments.len() {
let value = arguments[position].to_string_lossy();
if value_options.contains(&value.as_ref()) {
position += 2;
continue;
}
if value_options
.iter()
.any(|option| value.starts_with(&format!("{option}=")))
{
position += 1;
continue;
}
if clients.contains(&value.as_ref()) {
let boundary = position + 1;
if arguments
.get(boundary)
.is_none_or(|argument| argument != "--")
{
arguments.insert(boundary, "--".into());
}
break;
}
position += 1;
}
arguments
}
#[derive(Clone, Debug, Args)]
#[command(trailing_var_arg = true)]
pub struct WithArgs {
#[arg(long)]
pub global: bool,
#[arg(long, requires = "global")]
pub undo: bool,
#[arg(long, conflicts_with = "interactive")]
pub non_interactive: bool,
#[arg(long, conflicts_with = "non_interactive")]
pub interactive: bool,
#[arg(long)]
pub server: Option<String>,
#[arg(long, hide_env_values = true, conflicts_with = "token_stdin")]
pub token: Option<String>,
#[arg(long, conflicts_with = "token")]
pub token_stdin: bool,
#[arg(long)]
pub model: Option<String>,
#[arg(long, default_value_t = 1)]
pub run_ttl_hours: i64,
#[arg(long)]
pub run_max_requests: Option<u64>,
#[arg(value_enum)]
pub client: ClientKind,
#[arg(value_name = "CLIENT_ARGS", allow_hyphen_values = true)]
pub client_args: Vec<OsString>,
}
#[derive(Debug, Subcommand)]
pub enum ServerOp {
Use {
server: Option<String>,
#[arg(long, hide_env_values = true, conflicts_with = "token_stdin")]
token: Option<String>,
#[arg(long, conflicts_with = "token")]
token_stdin: bool,
#[arg(long)]
clear: bool,
#[arg(long)]
run_max_requests: Option<u64>,
},
Status,
Start,
Claim,
Stop,
Remove {
#[arg(long)]
yes: bool,
},
#[command(hide = true)]
Reap { pid: u32 },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wrapper_flags_after_client_are_protected() {
let arguments = ["router", "with", "--global", "codex", "--global", "prompt"]
.into_iter()
.map(OsString::from)
.collect();
assert_eq!(
protect_client_arguments(arguments, true),
[
"router", "with", "--global", "codex", "--", "--global", "prompt"
]
.map(OsString::from)
);
}
#[test]
fn option_values_that_match_clients_are_not_boundaries() {
let arguments = ["with-router", "--model", "codex", "qwen", "hello"]
.into_iter()
.map(OsString::from)
.collect();
assert_eq!(
protect_client_arguments(arguments, false),
["with-router", "--model", "codex", "qwen", "--", "hello"].map(OsString::from)
);
}
}