Files
zerogravity/src/constants.rs
Nikketryhard 134126358f fix: cross-platform support + auto token from state.vscdb
- User-Agent now matches actual OS (macOS/Windows/Linux)
- grep -oP replaced with grep -oE for macOS BSD compat
- Port-killer gated with cfg(unix)/cfg(windows)
- zg binary: macOS uses launchctl, Windows uses schtasks
- Data dir mismatch fixed in mitm-redirect.sh
- Windows setup-windows.ps1 ProjectDir fixed
- README: token path, prerequisites updated
- setup-linux.sh: pre-flight dependency checks
- OAuth token auto-read from Antigravity state.vscdb
- Version bump to 1.0.1
2026-02-18 04:09:41 -06:00

280 lines
9.2 KiB
Rust

//! 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<String> {
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<String>, Option<String>) {
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::<serde_json::Value>(&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<String>, Option<String>) {
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<DetectedVersions> = 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<String> = 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<String> = 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]),
}
}