//! Shared constants — auto-detected from the installed Antigravity binary at startup. //! //! On first access, we locate the Antigravity installation (via the running //! language server PID or well-known paths), parse `product.json` for version //! strings, and extract Chrome/Electron versions from the binary. If detection //! fails, we fall back to hardcoded values. use std::fs; use std::process::Command; use std::sync::LazyLock; /// Auto-detected version info from the installed Antigravity app. struct DetectedVersions { antigravity: String, chrome: String, electron: String, client: String, } /// Locate the Antigravity install directory by tracing the language server PID /// back to its binary, then walking up to the app root. Falls back to /// well-known install paths. fn find_install_dir() -> Option { let p = crate::platform::Platform::detect(); // 1. Check if platform-detected app_root exists let app_root_parent = std::path::Path::new(&p.app_root) .parent() .and_then(|p| p.parent()) .map(|p| p.to_string_lossy().to_string()); if let Some(ref dir) = app_root_parent { if fs::metadata(format!("{dir}/resources/app/product.json")).is_ok() { return Some(dir.clone()); } } // 2. Try tracing the running language server via /proc (Linux only) #[cfg(target_os = "linux")] { if let Ok(output) = Command::new("sh") .args(["-c", "pgrep -f language_server | head -1"]) .output() { let pid = String::from_utf8_lossy(&output.stdout).trim().to_string(); if !pid.is_empty() { if let Ok(exe) = fs::read_link(format!("/proc/{pid}/exe")) { let exe_str = exe.to_string_lossy().to_string(); if let Some(idx) = exe_str.find("/resources/") { return Some(exe_str[..idx].to_string()); } } } } } // 3. Fall back to well-known install paths #[cfg(target_os = "linux")] let candidates = ["/usr/share/antigravity", "/opt/Antigravity"]; #[cfg(target_os = "macos")] let candidates = [ "/Applications/Antigravity.app/Contents", &format!( "{}/Applications/Antigravity.app/Contents", std::env::var("HOME").unwrap_or_default() ), ]; #[cfg(target_os = "windows")] let candidates = [&format!( "{}\\Programs\\Antigravity", std::env::var("LOCALAPPDATA").unwrap_or_default() )]; #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] let candidates: [&str; 0] = []; for path in &candidates { if fs::metadata(format!("{path}/resources/app/product.json")).is_ok() { return Some(path.to_string()); } } None } /// Read `product.json` from the install dir and extract version fields. fn read_product_json(install_dir: &str) -> (Option, Option) { let path = format!("{install_dir}/resources/app/product.json"); let Ok(contents) = fs::read_to_string(&path) else { return (None, None); }; let Ok(json) = serde_json::from_str::(&contents) else { return (None, None); }; let version = json["version"].as_str().map(|s| s.to_string()); let ide_version = json["ideVersion"].as_str().map(|s| s.to_string()); (version, ide_version) } /// Extract Chrome and Electron versions from the main binary via `strings`. /// Pattern: "Chrome/142.0.7444.175", "Electron/39.2.3". fn extract_binary_versions(install_dir: &str) -> (Option, Option) { let binary = format!("{install_dir}/antigravity"); if fs::metadata(&binary).is_err() { return (None, None); } // Use grep -oE on the binary to avoid loading the whole thing into memory let chrome = Command::new("sh") .args([ "-c", &format!( "strings '{}' | grep -oE 'Chrome/[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+' | head -1", binary ), ]) .output() .ok() .and_then(|o| { let s = String::from_utf8_lossy(&o.stdout).trim().to_string(); s.strip_prefix("Chrome/").map(|v| v.to_string()) }); let electron = Command::new("sh") .args([ "-c", &format!( "strings '{}' | grep -oE 'Electron/[0-9]+\\.[0-9]+\\.[0-9]+' | head -1", binary ), ]) .output() .ok() .and_then(|o| { let s = String::from_utf8_lossy(&o.stdout).trim().to_string(); s.strip_prefix("Electron/").map(|v| v.to_string()) }); (chrome, electron) } /// Detect all versions from the installed Antigravity app. fn detect_versions() -> DetectedVersions { // Hardcoded fallbacks — last known good values const FALLBACK_ANTIGRAVITY: &str = "1.107.0"; const FALLBACK_CHROME: &str = "142.0.7444.175"; const FALLBACK_ELECTRON: &str = "39.2.3"; const FALLBACK_CLIENT: &str = "1.16.5"; let Some(install_dir) = find_install_dir() else { tracing::warn!("Could not find Antigravity install — using fallback versions"); return DetectedVersions { antigravity: FALLBACK_ANTIGRAVITY.to_string(), chrome: FALLBACK_CHROME.to_string(), electron: FALLBACK_ELECTRON.to_string(), client: FALLBACK_CLIENT.to_string(), }; }; // product.json → antigravity version + client/IDE version let (ag_ver, client_ver) = read_product_json(&install_dir); // Binary → Chrome + Electron versions let (chrome_ver, electron_ver) = extract_binary_versions(&install_dir); let versions = DetectedVersions { antigravity: ag_ver.unwrap_or_else(|| FALLBACK_ANTIGRAVITY.to_string()), chrome: chrome_ver.unwrap_or_else(|| FALLBACK_CHROME.to_string()), electron: electron_ver.unwrap_or_else(|| FALLBACK_ELECTRON.to_string()), client: client_ver.unwrap_or_else(|| FALLBACK_CLIENT.to_string()), }; tracing::info!( antigravity = %versions.antigravity, chrome = %versions.chrome, electron = %versions.electron, client = %versions.client, "Detected app versions" ); versions } // ─── Public API ────────────────────────────────────────────────────────────── /// All detected versions — computed once on first access. static VERSIONS: LazyLock = LazyLock::new(detect_versions); /// Antigravity app version (e.g. "1.107.0"). pub fn antigravity_version() -> &'static str { &VERSIONS.antigravity } /// Chrome version bundled with Electron (e.g. "142.0.7444.175"). pub fn chrome_version() -> &'static str { &VERSIONS.chrome } /// Electron version (e.g. "39.2.3"). pub fn electron_version() -> &'static str { &VERSIONS.electron } /// Client/IDE version from product.json (e.g. "1.16.5"). pub fn client_version() -> &'static str { &VERSIONS.client } pub const CLIENT_NAME: &str = "antigravity"; pub const LS_SERVICE: &str = "exa.language_server_pb.LanguageServerService"; /// Log base directory for Antigravity. pub fn log_base() -> String { let p = crate::platform::Platform::detect(); // Antigravity logs live next to its state DB let state_parent = std::path::Path::new(&p.state_db_path) .parent() .and_then(|p| p.parent()) .and_then(|p| p.parent()) .map(|p| p.to_string_lossy().to_string()) .unwrap_or_else(|| { let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string()); format!("{home}/.config/Antigravity") }); format!("{state_parent}/logs") } /// Token file path. pub fn token_file_path() -> String { crate::platform::Platform::detect() .token_path .to_string_lossy() .to_string() } /// User-Agent string matching the Electron webview — computed once. pub static USER_AGENT: LazyLock = LazyLock::new(|| { let os_part = user_agent_os_part(); format!( "Mozilla/5.0 ({os_part}) AppleWebKit/537.36 \ (KHTML, like Gecko) Antigravity/{} \ Chrome/{} Electron/{} Safari/537.36", antigravity_version(), chrome_version(), electron_version() ) }); /// Returns the OS portion of the User-Agent string matching real Electron/Chrome. fn user_agent_os_part() -> &'static str { #[cfg(target_os = "macos")] { "Macintosh; Intel Mac OS X 10_15_7" } #[cfg(target_os = "windows")] { "Windows NT 10.0; Win64; x64" } #[cfg(not(any(target_os = "macos", target_os = "windows")))] { "X11; Linux x86_64" } } /// Chrome major version for sec-ch-ua header — computed once. pub static CHROME_MAJOR: LazyLock = LazyLock::new(|| { chrome_version() .split('.') .next() .unwrap_or("142") .to_string() }); /// Safely truncate a string to at most `max` characters (not bytes). pub fn safe_truncate(s: &str, max: usize) -> String { match s.char_indices().nth(max) { None => s.to_string(), Some((idx, _)) => format!("{}...", &s[..idx]), } }