use std::process::Command;
use super::HarnessError;
pub const OAUTH_BETA: &str = "oauth-2025-04-20";
pub const BIN: &str = "ant";
pub const INSTALL_HINT: &str = "install Anthropic's CLI first: `brew install anthropics/tap/ant` \
(macOS) or see https://github.com/anthropics/anthropic-cli/releases";
pub fn installed() -> bool {
Command::new(BIN)
.arg("--version")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
pub fn access_token() -> Result<Option<String>, HarnessError> {
if !installed() {
return Ok(None);
}
let output = Command::new(BIN)
.args(["auth", "print-credentials", "--access-token"])
.stdin(std::process::Stdio::null())
.output()
.map_err(|e| HarnessError::Provider(format!("cannot run `{BIN}`: {e}")))?;
if !output.status.success() {
return Ok(None);
}
let token = String::from_utf8_lossy(&output.stdout).trim().to_string();
if token.is_empty() || token.contains(char::is_whitespace) {
return Ok(None);
}
Ok(Some(token))
}
pub fn status_line() -> Option<String> {
if !installed() {
return None;
}
let output = Command::new(BIN)
.args(["auth", "status"])
.stdin(std::process::Stdio::null())
.output()
.ok()?;
let text = String::from_utf8_lossy(&output.stdout);
let line = text
.lines()
.map(str::trim)
.find(|l| !l.is_empty())?
.to_string();
Some(line)
}
pub fn login(no_browser: bool) -> Result<(), HarnessError> {
if !installed() {
return Err(HarnessError::Config(format!(
"`{BIN}` is not installed — {INSTALL_HINT}"
)));
}
let mut command = Command::new(BIN);
command.args(["auth", "login"]);
if no_browser {
command.arg("--no-browser");
}
let status = command
.status()
.map_err(|e| HarnessError::Provider(format!("cannot run `{BIN} auth login`: {e}")))?;
if !status.success() {
return Err(HarnessError::Provider(
"`ant auth login` did not complete".to_string(),
));
}
Ok(())
}
pub fn logout() -> Result<(), HarnessError> {
if !installed() {
return Ok(());
}
let _ = Command::new(BIN)
.args(["auth", "logout"])
.stdin(std::process::Stdio::null())
.status();
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_cli_is_not_an_error() {
let resolved = access_token().expect("absence must not error");
if !installed() {
assert!(resolved.is_none());
}
}
#[test]
fn beta_header_is_the_documented_one() {
assert_eq!(OAUTH_BETA, "oauth-2025-04-20");
}
}