falsegreen 0.1.30

FalseGreen client — independent verification for coding agents
//! FalseGreen session exchange and revocation.
//!
//! The shim knows only a one-time enrollment credential and the opaque
//! FalseGreen access token returned by the server. Billing and entitlement
//! interpretation remain entirely server-side.

use std::process::Command;

#[derive(Debug)]
pub struct SessionCredentials {
    pub access_token: String,
    pub session_id: String,
}

/// Exchange an enrollment credential for an individual FalseGreen session.
pub fn login(enrollment_key: &str) -> Result<SessionCredentials, String> {
    let api_url = crate::shim::config::api_url();
    let login_url = api_url.replace("/v1/mcp", "/v1/login");

    let result = Command::new("curl")
        .arg("-s")
        .arg("-X")
        .arg("POST")
        .arg(&login_url)
        .arg("-H")
        .arg(format!("X-API-KEY: {}", enrollment_key))
        .arg("--max-time")
        .arg("30")
        .output();

    match result {
        Ok(output) if output.status.success() => {
            let body = String::from_utf8_lossy(&output.stdout);
            if let Ok(v) = serde_json::from_str::<serde_json::Value>(&body) {
                if let (Some(access_token), Some(session_id)) = (
                    v.get("access_token").and_then(|i| i.as_str()),
                    v.get("session_id").and_then(|i| i.as_str()),
                ) {
                    return Ok(SessionCredentials {
                        access_token: access_token.to_string(),
                        session_id: session_id.to_string(),
                    });
                }
                if let Some(err) = v.get("detail").and_then(|d| d.as_str()) {
                    return Err(err.to_string());
                }
            }
            Err(format!("invalid response: {}", body))
        }
        Ok(output) => {
            let stderr = String::from_utf8_lossy(&output.stderr);
            Err(format!("request failed: {}", stderr))
        }
        Err(e) => Err(format!("failed to send request: {}", e)),
    }
}

/// Revoke an opaque FalseGreen session.
pub fn logout(access_token: &str) -> Result<(), String> {
    let api_url = crate::shim::config::api_url();
    let logout_url = api_url.replace("/v1/mcp", "/v1/logout");

    let result = Command::new("curl")
        .arg("-s")
        .arg("-X")
        .arg("POST")
        .arg(&logout_url)
        .arg("-H")
        .arg(format!("X-API-KEY: {}", access_token))
        .arg("--max-time")
        .arg("30")
        .output();

    match result {
        Ok(output) if output.status.success() => Ok(()),
        Ok(output) => {
            let stderr = String::from_utf8_lossy(&output.stderr);
            let stdout = String::from_utf8_lossy(&output.stdout);
            Err(format!("request failed: {} {}", stderr, stdout))
        }
        Err(e) => Err(format!("failed to send request: {}", e)),
    }
}