use std::env;
use std::sync::OnceLock;
pub type GateFn = Box<dyn Fn(&str) -> bool + Send + Sync>;
pub type DynamicConfigFn = Box<dyn Fn(&str) -> Option<serde_json::Value> + Send + Sync>;
pub type VersionCheckFn = Box<dyn Fn(&str, &str) -> bool + Send + Sync>;
pub type SubscriberCheckFn = Box<dyn Fn() -> bool + Send + Sync>;
pub type ProfileScopeFn = Box<dyn Fn() -> bool + Send + Sync>;
pub type OauthAccountFn = Box<dyn Fn() -> Option<OAuthAccountInfo> + Send + Sync>;
pub type EnvTruthyFn = Box<dyn Fn(&str) -> bool + Send + Sync>;
static GATE_GETTER: OnceLock<GateFn> = OnceLock::new();
static DYNAMIC_CONFIG_GETTER: OnceLock<DynamicConfigFn> = OnceLock::new();
static VERSION_CHECK_GETTER: OnceLock<VersionCheckFn> = OnceLock::new();
static SUBSCRIBER_CHECK: OnceLock<SubscriberCheckFn> = OnceLock::new();
static PROFILE_SCOPE_CHECK: OnceLock<ProfileScopeFn> = OnceLock::new();
static OAUTH_ACCOUNT_GETTER: OnceLock<OauthAccountFn> = OnceLock::new();
static ENV_TRUTHY_CHECK: OnceLock<EnvTruthyFn> = OnceLock::new();
static BRIDGE_MODE: OnceLock<bool> = OnceLock::new();
static CCR_AUTO_CONNECT: OnceLock<bool> = OnceLock::new();
static CCR_MIRROR: OnceLock<bool> = OnceLock::new();
#[derive(Debug, Clone, Default)]
pub struct OAuthAccountInfo {
pub organization_uuid: Option<String>,
}
pub fn register_gate_check(gate: impl Fn(&str) -> bool + Send + Sync + 'static) {
let _ = GATE_GETTER.set(Box::new(gate));
}
pub fn register_dynamic_config(
getter: impl Fn(&str) -> Option<serde_json::Value> + Send + Sync + 'static,
) {
let _ = DYNAMIC_CONFIG_GETTER.set(Box::new(getter));
}
pub fn register_version_check(checker: impl Fn(&str, &str) -> bool + Send + Sync + 'static) {
let _ = VERSION_CHECK_GETTER.set(Box::new(checker));
}
pub fn register_subscriber_check(check: impl Fn() -> bool + Send + Sync + 'static) {
let _ = SUBSCRIBER_CHECK.set(Box::new(check));
}
pub fn register_profile_scope_check(check: impl Fn() -> bool + Send + Sync + 'static) {
let _ = PROFILE_SCOPE_CHECK.set(Box::new(check));
}
pub fn register_oauth_account_getter(
getter: impl Fn() -> Option<OAuthAccountInfo> + Send + Sync + 'static,
) {
let _ = OAUTH_ACCOUNT_GETTER.set(Box::new(getter));
}
pub fn register_env_truthy_check(check: impl Fn(&str) -> bool + Send + Sync + 'static) {
let _ = ENV_TRUTHY_CHECK.set(Box::new(check));
}
pub fn set_bridge_mode(enabled: bool) {
let _ = BRIDGE_MODE.set(enabled);
}
pub fn set_ccr_auto_connect(enabled: bool) {
let _ = CCR_AUTO_CONNECT.set(enabled);
}
pub fn set_ccr_mirror(enabled: bool) {
let _ = CCR_MIRROR.set(enabled);
}
fn get_gate(gate_name: &str) -> bool {
GATE_GETTER
.get()
.map(|gate| gate(gate_name))
.unwrap_or(false)
}
fn get_dynamic_config(key: &str) -> Option<serde_json::Value> {
DYNAMIC_CONFIG_GETTER.get().and_then(|getter| getter(key))
}
fn check_version(current: &str, min: &str) -> bool {
VERSION_CHECK_GETTER
.get()
.map(|check| check(current, min))
.unwrap_or_else(|| {
current >= min
})
}
fn is_claude_ai_subscriber() -> bool {
SUBSCRIBER_CHECK.get().map(|check| check()).unwrap_or(false)
}
fn has_profile_scope() -> bool {
PROFILE_SCOPE_CHECK
.get()
.map(|check| check())
.unwrap_or(false)
}
fn get_oauth_account_info() -> Option<OAuthAccountInfo> {
OAUTH_ACCOUNT_GETTER.get().and_then(|getter| getter())
}
fn is_env_truthy(key: &str) -> bool {
ENV_TRUTHY_CHECK
.get()
.map(|check| check(key))
.unwrap_or_else(|| {
env::var(key)
.map(|v| v == "1" || v.to_lowercase() == "true")
.unwrap_or(false)
})
}
fn bridge_mode_enabled() -> bool {
BRIDGE_MODE.get().copied().unwrap_or(false)
}
fn ccr_auto_connect_enabled() -> bool {
CCR_AUTO_CONNECT.get().copied().unwrap_or(false)
}
fn ccr_mirror_enabled() -> bool {
CCR_MIRROR.get().copied().unwrap_or(false)
}
pub fn is_bridge_enabled() -> bool {
if !bridge_mode_enabled() {
return false;
}
get_gate("tengu_ccr_bridge")
}
pub fn is_bridge_enabled_blocking() -> bool {
is_bridge_enabled()
}
pub fn get_bridge_disabled_reason() -> Option<String> {
if !bridge_mode_enabled() {
return Some("Remote Control is not available in this build.".to_string());
}
if !is_claude_ai_subscriber() {
return Some(
"Remote Control requires a claude.ai subscription. Run `claude auth login` to sign in \
with your claude.ai account."
.to_string(),
);
}
if !has_profile_scope() {
return Some(
"Remote Control requires a full-scope login token. Long-lived tokens (from `claude \
setup-token` or AI_CODE_OAUTH_TOKEN) are limited to inference-only for security \
reasons. Run `claude auth login` to use Remote Control."
.to_string(),
);
}
let account = get_oauth_account_info();
if account.is_none()
|| account
.as_ref()
.and_then(|a| a.organization_uuid.as_ref())
.is_none()
{
return Some(
"Unable to determine your organization for Remote Control eligibility. Run \
`claude auth login` to refresh your account information."
.to_string(),
);
}
if !get_gate("tengu_ccr_bridge") {
return Some("Remote Control is not yet enabled for your account.".to_string());
}
None
}
pub fn is_env_less_bridge_enabled() -> bool {
if !bridge_mode_enabled() {
return false;
}
get_gate("tengu_bridge_repl_v2")
}
pub fn is_cse_shim_enabled() -> bool {
if !bridge_mode_enabled() {
return true;
}
get_dynamic_config("tengu_bridge_repl_v2_cse_shim_enabled")
.and_then(|v| v.as_bool())
.unwrap_or(true)
}
pub fn check_bridge_min_version(current_version: &str) -> Option<String> {
if !bridge_mode_enabled() {
return None;
}
let config = get_dynamic_config("tengu_bridge_min_version");
let min_version = config
.and_then(|c| c.get("minVersion").cloned())
.and_then(|v| v.as_str().map(|s| s.to_string()))
.unwrap_or_else(|| "0.0.0".to_string());
if !check_version(current_version, &min_version) {
return Some(format!(
"Your version of Claude Code ({}) is too old for Remote Control.\nVersion {} or \
higher is required. Run `claude update` to update.",
current_version, min_version
));
}
None
}
pub fn get_ccr_auto_connect_default() -> bool {
if !ccr_auto_connect_enabled() {
return false;
}
get_gate("tengu_cobalt_harbor")
}
pub fn is_ccr_mirror_enabled() -> bool {
if !ccr_mirror_enabled() {
return false;
}
is_env_truthy("AI_CODE_CCR_MIRROR") || get_gate("tengu_ccr_mirror")
}
pub fn register_cse_shim_gate() {
use crate::bridge::session_id_compat::set_cse_shim_gate;
set_cse_shim_gate(is_cse_shim_enabled);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bridge_disabled_without_build_flag() {
assert!(!is_bridge_enabled());
}
#[test]
fn test_env_less_bridge_default() {
assert!(!is_env_less_bridge_enabled());
}
#[test]
fn test_cse_shim_default() {
assert!(is_cse_shim_enabled());
}
#[test]
fn test_check_bridge_min_version() {
let result = check_bridge_min_version("1.0.0");
assert!(result.is_none());
}
}