use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::HarnessId;
pub const HARNESS_AUTHENTICATION_SCHEMA: &str = "supercode.harness-authentication.v1";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HarnessAuthenticationEnvironment {
LocalBrowser,
Headless,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HarnessAuthenticationMethodId {
Browser,
DeviceCode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum HarnessAuthenticationInteraction {
Browser,
DeviceCode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum HarnessBrowserBehavior {
NativeAuto,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum HarnessAuthenticationState {
Authenticated,
Configured,
Required,
Unavailable,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct HarnessAuthenticationMethod {
pub id: HarnessAuthenticationMethodId,
pub label: &'static str,
pub description: &'static str,
pub interaction: HarnessAuthenticationInteraction,
pub browser_behavior: HarnessBrowserBehavior,
pub headless: bool,
pub recommended: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct HarnessAuthenticationLaunch {
pub cwd: PathBuf,
pub program: String,
pub arguments: Vec<String>,
pub env: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct HarnessAuthenticationPlan {
pub schema: &'static str,
pub harness: HarnessId,
pub method: HarnessAuthenticationMethodId,
pub interaction: HarnessAuthenticationInteraction,
pub browser_behavior: HarnessBrowserBehavior,
pub headless: bool,
pub launch: HarnessAuthenticationLaunch,
pub instructions: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct HarnessAuthenticationReport {
pub schema: &'static str,
pub harness: HarnessId,
pub installed: bool,
pub executable: Option<String>,
pub state: HarnessAuthenticationState,
pub methods: Vec<HarnessAuthenticationMethod>,
pub reason: Option<String>,
}
#[derive(Debug, thiserror::Error)]
pub enum HarnessAuthenticationError {
#[error("unknown harness `{0}`")]
UnknownHarness(String),
#[error("{0} is not installed or its executable is not on PATH")]
NotInstalled(String),
#[error("{0}")]
Unsupported(String),
}
pub fn harness_authentication_methods(
harness: &HarnessId,
) -> Result<Vec<HarnessAuthenticationMethod>, HarnessAuthenticationError> {
let methods = match harness.as_str() {
HarnessId::CLAUDE_CODE => vec![HarnessAuthenticationMethod {
id: HarnessAuthenticationMethodId::Browser,
label: "Sign in with browser",
description: "Claude Code opens its native sign-in page and keeps the terminal available for status and fallback instructions.",
interaction: HarnessAuthenticationInteraction::Browser,
browser_behavior: HarnessBrowserBehavior::NativeAuto,
headless: false,
recommended: true,
}],
HarnessId::CODEX => vec![
HarnessAuthenticationMethod {
id: HarnessAuthenticationMethodId::Browser,
label: "Sign in with browser",
description: "Codex opens its native ChatGPT sign-in flow in the local browser.",
interaction: HarnessAuthenticationInteraction::Browser,
browser_behavior: HarnessBrowserBehavior::NativeAuto,
headless: false,
recommended: true,
},
HarnessAuthenticationMethod {
id: HarnessAuthenticationMethodId::DeviceCode,
label: "Use another device",
description: "Codex prints a short-lived code and verification address for a phone or another browser.",
interaction: HarnessAuthenticationInteraction::DeviceCode,
browser_behavior: HarnessBrowserBehavior::None,
headless: true,
recommended: false,
},
],
value
if crate::harness_support_registry()
.harnesses
.iter()
.any(|descriptor| descriptor.id == *harness) =>
{
return Err(HarnessAuthenticationError::Unsupported(format!(
"Supercode does not yet have a verified native sign-in adapter for `{value}`"
)))
}
value => return Err(HarnessAuthenticationError::UnknownHarness(value.into())),
};
Ok(methods)
}
pub fn harness_authentication_plan(
harness: &HarnessId,
environment: HarnessAuthenticationEnvironment,
requested_method: Option<HarnessAuthenticationMethodId>,
cwd: &Path,
) -> Result<HarnessAuthenticationPlan, HarnessAuthenticationError> {
let methods = harness_authentication_methods(harness)?;
let selected = requested_method
.and_then(|id| methods.iter().find(|method| method.id == id))
.or_else(|| match environment {
HarnessAuthenticationEnvironment::LocalBrowser => {
methods.iter().find(|method| method.recommended)
}
HarnessAuthenticationEnvironment::Headless => {
methods.iter().find(|method| method.headless)
}
})
.ok_or_else(|| {
HarnessAuthenticationError::Unsupported(format!(
"{} does not expose a verified {} sign-in flow in this Supercode version",
harness.as_str(),
match environment {
HarnessAuthenticationEnvironment::LocalBrowser => "local-browser",
HarnessAuthenticationEnvironment::Headless => "headless",
}
))
})?;
if requested_method.is_some_and(|id| !methods.iter().any(|method| method.id == id)) {
return Err(HarnessAuthenticationError::Unsupported(format!(
"{} does not support the requested sign-in method",
harness.as_str()
)));
}
let program = default_auth_program(harness)
.ok_or_else(|| HarnessAuthenticationError::UnknownHarness(harness.as_str().into()))?;
let executable = find_executable(&program)
.ok_or_else(|| HarnessAuthenticationError::NotInstalled(harness.as_str().to_string()))?;
let arguments = match (harness.as_str(), selected.id) {
(HarnessId::CLAUDE_CODE, HarnessAuthenticationMethodId::Browser) => {
vec!["auth".into(), "login".into()]
}
(HarnessId::CODEX, HarnessAuthenticationMethodId::Browser) => vec!["login".into()],
(HarnessId::CODEX, HarnessAuthenticationMethodId::DeviceCode) => {
vec!["login".into(), "--device-auth".into()]
}
_ => {
return Err(HarnessAuthenticationError::Unsupported(format!(
"{} does not support the requested sign-in method",
harness.as_str()
)))
}
};
Ok(HarnessAuthenticationPlan {
schema: HARNESS_AUTHENTICATION_SCHEMA,
harness: harness.clone(),
method: selected.id,
interaction: selected.interaction,
browser_behavior: selected.browser_behavior,
headless: selected.headless,
launch: HarnessAuthenticationLaunch {
cwd: cwd.to_path_buf(),
program: executable.to_string_lossy().into_owned(),
arguments,
env: BTreeMap::new(),
},
instructions: match selected.interaction {
HarnessAuthenticationInteraction::Browser => {
"Keep the native sign-in terminal open until the harness confirms completion. If a browser cannot open, use any fallback instructions printed there."
}
HarnessAuthenticationInteraction::DeviceCode => {
"Keep the native sign-in terminal open, then visit the printed address on any device and enter the short-lived code."
}
},
})
}
pub async fn inspect_harness_authentication(harness: &HarnessId) -> HarnessAuthenticationReport {
let methods = harness_authentication_methods(harness);
let Some(program) = default_auth_program(harness) else {
return HarnessAuthenticationReport {
schema: HARNESS_AUTHENTICATION_SCHEMA,
harness: harness.clone(),
installed: false,
executable: None,
state: HarnessAuthenticationState::Unavailable,
methods: Vec::new(),
reason: Some("No native sign-in executable is registered.".into()),
};
};
let Some(executable) = find_executable(&program) else {
return HarnessAuthenticationReport {
schema: HARNESS_AUTHENTICATION_SCHEMA,
harness: harness.clone(),
installed: false,
executable: None,
state: HarnessAuthenticationState::Unavailable,
methods: methods.unwrap_or_default(),
reason: Some(format!("`{program}` was not found on PATH.")),
};
};
let methods = match methods {
Ok(methods) => methods,
Err(error) => {
return HarnessAuthenticationReport {
schema: HARNESS_AUTHENTICATION_SCHEMA,
harness: harness.clone(),
installed: true,
executable: Some(executable.to_string_lossy().into_owned()),
state: HarnessAuthenticationState::Unavailable,
methods: Vec::new(),
reason: Some(error.to_string()),
}
}
};
let verified = native_auth_status(harness, &executable).await;
let configured = super::harness_service::auth_evidence(harness.as_str());
let (state, reason) = if verified {
(
HarnessAuthenticationState::Authenticated,
Some("The native harness reports an active sign-in.".into()),
)
} else if configured {
(
HarnessAuthenticationState::Configured,
Some("Local credential evidence exists, but the native status command did not confirm an active sign-in.".into()),
)
} else {
(
HarnessAuthenticationState::Required,
Some("The native harness does not report an active sign-in.".into()),
)
};
HarnessAuthenticationReport {
schema: HARNESS_AUTHENTICATION_SCHEMA,
harness: harness.clone(),
installed: true,
executable: Some(executable.to_string_lossy().into_owned()),
state,
methods,
reason,
}
}
async fn native_auth_status(harness: &HarnessId, executable: &Path) -> bool {
let mut command = tokio::process::Command::new(executable);
match harness.as_str() {
HarnessId::CLAUDE_CODE => {
command.args(["auth", "status", "--json"]);
}
HarnessId::CODEX => {
command.args(["login", "status"]);
}
_ => return false,
}
command
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.kill_on_drop(true);
let Ok(Ok(output)) = tokio::time::timeout(Duration::from_secs(3), command.output()).await
else {
return false;
};
if !output.status.success() {
return false;
}
if harness.as_str() == HarnessId::CLAUDE_CODE {
return serde_json::from_slice::<serde_json::Value>(&output.stdout)
.ok()
.and_then(|value| value.get("loggedIn").and_then(serde_json::Value::as_bool))
.unwrap_or(false);
}
true
}
fn default_auth_program(harness: &HarnessId) -> Option<String> {
crate::harness_support_registry()
.harnesses
.into_iter()
.find(|descriptor| descriptor.id == *harness)
.and_then(|descriptor| descriptor.runtime.default_launch)
.map(|launch| launch.program)
}
fn find_executable(program: &str) -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
std::env::split_paths(&path).find_map(|directory| {
let candidate = directory.join(program);
if candidate.is_file() {
return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
}
#[cfg(windows)]
for extension in ["exe", "cmd", "bat"] {
let candidate = directory.join(format!("{program}.{extension}"));
if candidate.is_file() {
return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
}
}
None
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adapter_selection_never_substitutes_a_browser_flow_for_headless_login() {
let codex = HarnessId::new(HarnessId::CODEX);
let claude = HarnessId::new(HarnessId::CLAUDE_CODE);
let methods = harness_authentication_methods(&codex).unwrap();
assert_eq!(
methods
.iter()
.find(|method| method.headless)
.map(|method| method.id),
Some(HarnessAuthenticationMethodId::DeviceCode)
);
assert!(!harness_authentication_methods(&claude)
.unwrap()
.iter()
.any(|method| method.headless));
}
}