use arete_mcp::entitlements::{Group, Surface};
use clap::Command;
use std::ffi::OsString;
use crate::api_client::ApiClient;
pub fn command_groups(name: &str) -> Option<&'static [Group]> {
Some(match name {
"init" | "doctor" | "self" | "upgrade" | "mcp" | "auth" | "login" | "config"
| "telemetry" | "help" => &[Group::Account],
"stream" | "get" => &[Group::Streams],
"up" | "status" | "stack" | "build" | "program" | "idl" => &[Group::Stacks],
"explore" | "know" | "install" | "update" | "remove" | "sdk" => &[Group::Registry],
"protocol" | "squads" => &[Group::Protocols],
"dev" => &[Group::Dev],
"create" => &[Group::Stacks, Group::Protocols],
_ => return None,
})
}
pub fn visible(cmd: Command, surface: &Surface) -> Command {
if *surface == Surface::Everything {
return cmd;
}
let kept: Vec<Command> = cmd
.get_subcommands()
.filter(|sub| {
command_groups(sub.get_name()).is_none_or(|groups| surface.allows_any(groups))
})
.cloned()
.collect();
let mut root = Command::new("a4").version(env!("CARGO_PKG_VERSION"));
if let Some(about) = cmd.get_about() {
root = root.about(about.clone());
}
if let Some(about) = cmd.get_long_about() {
root = root.long_about(about.clone());
}
root.args(cmd.get_arguments().cloned()).subcommands(kept)
}
pub fn account_surface(api_url: Option<&str>, profile: Option<&str>, config: &str) -> Surface {
let api_url = crate::config::get_api_url(api_url);
let inherited = std::env::var(arete_mcp::credentials::ENV_VAR_PROFILE).ok();
let Ok(profile) = crate::config::resolve_auth_profile(profile, config, inherited.as_deref())
else {
return Surface::Everything;
};
let env_key = profile
.is_none()
.then(|| std::env::var(arete_mcp::credentials::ENV_VAR_API_KEY).ok())
.flatten()
.map(|key| key.trim().to_string())
.filter(|key| !key.is_empty());
let key = match env_key {
Some(key) => Some(key),
None => ApiClient::load_optional_api_key_for_profile(&api_url, profile.as_deref())
.ok()
.flatten(),
};
match key {
Some(key) => ApiClient::account_surface_at(&api_url, &key),
None => Surface::Everything,
}
}
pub fn global_option(args: &[OsString], long: &str, short: Option<&str>) -> Option<String> {
let mut args = args.iter().skip(1).map(|arg| arg.to_string_lossy());
while let Some(arg) = args.next() {
if arg == "--" {
return None;
}
if arg == long || short.is_some_and(|short| arg == short) {
return args.next().map(|value| value.into_owned());
}
if let Some(value) = arg
.strip_prefix(long)
.and_then(|rest| rest.strip_prefix('='))
{
return Some(value.to_string());
}
if let Some(value) = short
.and_then(|short| arg.strip_prefix(short))
.filter(|value| !value.is_empty())
{
return Some(value.strip_prefix('=').unwrap_or(value).to_string());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn args(list: &[&str]) -> Vec<OsString> {
list.iter().map(OsString::from).collect()
}
#[test]
fn global_options_are_read_from_raw_arguments() {
let raw = args(&[
"a4",
"--api-url=http://x",
"help",
"--profile",
"agent",
"-c",
"a.toml",
]);
assert_eq!(
global_option(&raw, "--api-url", None).as_deref(),
Some("http://x")
);
assert_eq!(
global_option(&raw, "--profile", None).as_deref(),
Some("agent")
);
assert_eq!(
global_option(&raw, "--config", Some("-c")).as_deref(),
Some("a.toml")
);
for attached in ["-cproject/arete.toml", "-c=project/arete.toml"] {
let raw = args(&["a4", attached, "--help"]);
assert_eq!(
global_option(&raw, "--config", Some("-c")).as_deref(),
Some("project/arete.toml")
);
}
let after = args(&["a4", "get", "--", "--profile", "x"]);
assert_eq!(global_option(&after, "--profile", None), None);
}
#[test]
fn the_account_endpoint_decides_the_surface() {
use crate::api_client::test_support::MockServer;
assert_eq!(
ApiClient::account_surface_at("http://127.0.0.1:1", "a4_sk_test"),
Surface::Everything
);
for (status, body) in [
(
200,
r#"{"accountKind":"agent","plan":"free","capabilities":[]}"#,
),
(401, r#"{"error":"unauthorized"}"#),
] {
let server = MockServer::json(status, body);
assert_eq!(
ApiClient::account_surface_at(server.base_url(), "a4_sk_test"),
Surface::Everything
);
}
let server = MockServer::json(
200,
r#"{"accountKind":"agent","commandGroups":["account","sandboxes","registry"]}"#,
);
assert_eq!(
ApiClient::account_surface_at(server.base_url(), "a4_sk_test"),
Surface::Groups(
[Group::Account, Group::Sandboxes, Group::Registry]
.into_iter()
.collect()
)
);
let request = server.request();
assert!(request.request_line.starts_with("GET /api/auth/me "));
assert_eq!(request.header("authorization"), Some("Bearer a4_sk_test"));
}
}