- 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
280 lines
9.2 KiB
Rust
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]),
|
|
}
|
|
}
|