feat: rebrand to ZeroGravity, replace proxyctl with zg Rust binary

Phase 1 - Rename:
- Crate: antigravity-proxy -> zerogravity
- Env: ANTIGRAVITY_OAUTH_TOKEN -> ZEROGRAVITY_TOKEN
- Paths: ~/.config/antigravity-proxy -> ~/.config/zerogravity
- Paths: /tmp/antigravity-* -> /tmp/zerogravity-*
- User: antigravity-ls -> zerogravity-ls
- Service: antigravity-proxy -> zerogravity

Phase 2 - zg daemon manager:
- New Rust binary src/bin/zg.rs replaces scripts/proxyctl bash
- Commands: start, stop, restart, rebuild, status, logs, test, health
- Auto-resolves project dir from binary location
- All commands exit immediately (safe for agent fast-bash)
This commit is contained in:
Nikketryhard
2026-02-18 01:54:54 -06:00
parent 409ee97405
commit 00587fcce8
18 changed files with 403 additions and 122 deletions

74
Cargo.lock generated
View File

@@ -103,43 +103,6 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "antigravity-proxy"
version = "3.0.0"
dependencies = [
"async-stream",
"axum",
"base64",
"brotli 7.0.0",
"bytes",
"chrono",
"clap",
"flate2",
"http",
"http-body-util",
"httparse",
"hyper",
"hyper-util",
"rand",
"rcgen",
"regex",
"rustls",
"rustls-native-certs",
"rustls-pemfile",
"serde",
"serde_json",
"time",
"tokio",
"tokio-rustls",
"tokio-stream",
"tower-http",
"tracing",
"tracing-subscriber",
"uuid",
"wreq",
"wreq-util",
]
[[package]] [[package]]
name = "async-stream" name = "async-stream"
version = "0.3.6" version = "0.3.6"
@@ -2396,6 +2359,43 @@ dependencies = [
"synstructure", "synstructure",
] ]
[[package]]
name = "zerogravity"
version = "3.0.0"
dependencies = [
"async-stream",
"axum",
"base64",
"brotli 7.0.0",
"bytes",
"chrono",
"clap",
"flate2",
"http",
"http-body-util",
"httparse",
"hyper",
"hyper-util",
"rand",
"rcgen",
"regex",
"rustls",
"rustls-native-certs",
"rustls-pemfile",
"serde",
"serde_json",
"time",
"tokio",
"tokio-rustls",
"tokio-stream",
"tower-http",
"tracing",
"tracing-subscriber",
"uuid",
"wreq",
"wreq-util",
]
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.8.2" version = "1.8.2"

View File

@@ -1,8 +1,16 @@
[package] [package]
name = "antigravity-proxy" name = "zerogravity"
version = "3.0.0" version = "3.0.0"
edition = "2021" edition = "2021"
[[bin]]
name = "zerogravity"
path = "src/main.rs"
[[bin]]
name = "zg"
path = "src/bin/zg.rs"
[dependencies] [dependencies]
axum = { version = "0.8", features = ["json"] } axum = { version = "0.8", features = ["json"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }

View File

@@ -27,41 +27,41 @@ This "LS as dumb relay" pattern keeps the LS interactions minimal and predictabl
## Agent Quick Reference ## Agent Quick Reference
### `proxyctl` — Daemon Manager ### `zg` — Daemon Manager
`proxyctl` commands exit immediately (not foreground) — safe for agent use via fast-bash MCP. `zg` commands exit immediately (not foreground) — safe for agent use via fast-bash MCP.
```bash ```bash
# Rebuild and restart after code changes # Rebuild and restart after code changes
proxyctl restart zg restart
# Quick test # Quick test
proxyctl test "say hi in 3 words" zg test "say hi in 3 words"
# Check status # Check status
proxyctl status zg status
# Check health # Check health
proxyctl health zg health
``` ```
| Command | Description | | Command | Description |
| --------------------- | ----------------------------------- | | --------------------- | ----------------------------------- |
| `proxyctl start` | Start the proxy daemon | | `zg start` | Start the proxy daemon |
| `proxyctl stop` | Stop the proxy daemon | | `zg stop` | Stop the proxy daemon |
| `proxyctl restart` | Rebuild + restart | | `zg restart` | Rebuild + restart |
| `proxyctl rebuild` | Build release binary only | | `zg rebuild` | Build release binary only |
| `proxyctl status` | Service status + quota + usage | | `zg status` | Service status + quota + usage |
| `proxyctl logs [N]` | Tail last N lines + follow | | `zg logs [N]` | Tail last N lines + follow |
| `proxyctl logs-all` | Full log dump (no follow) | | `zg logs-all` | Full log dump (no follow) |
| `proxyctl test [msg]` | Quick test request (gemini-3-flash) | | `zg test [msg]` | Quick test request (gemini-3-flash) |
| `proxyctl health` | Health check | | `zg health` | Health check |
### Testing After Changes ### Testing After Changes
```bash ```bash
# 1. Rebuild + restart # 1. Rebuild + restart
proxyctl restart zg restart
# 2. Test an endpoint # 2. Test an endpoint
curl -s http://localhost:8741/v1/chat/completions \ curl -s http://localhost:8741/v1/chat/completions \
@@ -69,7 +69,7 @@ curl -s http://localhost:8741/v1/chat/completions \
-d '{"model": "gemini-3-flash", "messages": [{"role": "user", "content": "Say hi"}]}' | jq . -d '{"model": "gemini-3-flash", "messages": [{"role": "user", "content": "Say hi"}]}' | jq .
# 3. Inspect latest trace # 3. Inspect latest trace
TRACE_DIR=~/.config/antigravity-proxy/traces/$(date +%Y-%m-%d) TRACE_DIR=~/.config/zerogravity/traces/$(date +%Y-%m-%d)
cat "$TRACE_DIR/$(ls -t "$TRACE_DIR" | head -1)/summary.md" cat "$TRACE_DIR/$(ls -t "$TRACE_DIR" | head -1)/summary.md"
``` ```
@@ -99,8 +99,8 @@ cat "$TRACE_DIR/$(ls -t "$TRACE_DIR" | head -1)/summary.md"
The proxy needs an OAuth token: The proxy needs an OAuth token:
1. **Env var**: `ANTIGRAVITY_OAUTH_TOKEN=ya29.xxx` 1. **Env var**: `ZEROGRAVITY_TOKEN=ya29.xxx`
2. **Token file**: `~/.config/antigravity-proxy-token` 2. **Token file**: `~/.config/zerogravity-token`
3. **Runtime**: `curl -X POST http://localhost:8741/v1/token -d '{"token":"ya29.xxx"}'` 3. **Runtime**: `curl -X POST http://localhost:8741/v1/token -d '{"token":"ya29.xxx"}'`
## CLI Flags ## CLI Flags

View File

@@ -23,10 +23,10 @@ graph LR
```bash ```bash
# Headless mode (no running Antigravity app needed) # Headless mode (no running Antigravity app needed)
RUST_LOG=info ./target/release/antigravity-proxy --headless RUST_LOG=info ./target/release/zerogravity --headless
# Or use the daemon manager # Or use the daemon manager
proxyctl start zg start
``` ```
## Endpoints ## Endpoints
@@ -50,22 +50,22 @@ proxyctl start
The proxy needs an OAuth token: The proxy needs an OAuth token:
1. **Env var**: `ANTIGRAVITY_OAUTH_TOKEN=ya29.xxx` 1. **Env var**: `ZEROGRAVITY_TOKEN=ya29.xxx`
2. **Token file**: `~/.config/antigravity-proxy-token` 2. **Token file**: `~/.config/zerogravity-token`
3. **Runtime**: `curl -X POST http://localhost:8741/v1/token -d '{"token":"ya29.xxx"}'` 3. **Runtime**: `curl -X POST http://localhost:8741/v1/token -d '{"token":"ya29.xxx"}'`
## `proxyctl` Commands ## `zg` Commands
| Command | Description | | Command | Description |
| --------------------- | ------------------------------ | | --------------------- | ------------------------------ |
| `proxyctl start` | Start the proxy daemon | | `zg start` | Start the proxy daemon |
| `proxyctl stop` | Stop the proxy daemon | | `zg stop` | Stop the proxy daemon |
| `proxyctl restart` | Rebuild + restart | | `zg restart` | Rebuild + restart |
| `proxyctl rebuild` | Build release binary only | | `zg rebuild` | Build release binary only |
| `proxyctl status` | Service status + quota + usage | | `zg status` | Service status + quota + usage |
| `proxyctl logs [N]` | Tail last N lines + follow | | `zg logs [N]` | Tail last N lines + follow |
| `proxyctl test [msg]` | Quick test request | | `zg test [msg]` | Quick test request |
| `proxyctl health` | Health check | | `zg health` | Health check |
## Documentation ## Documentation

View File

@@ -132,11 +132,11 @@ Events dispatched through `tokio::sync::mpsc` channels from MITM → API handler
### UID-Scoped iptables (Classic Mode) ### UID-Scoped iptables (Classic Mode)
```bash ```bash
# One-time setup — creates antigravity-ls user + iptables rule # One-time setup — creates zerogravity-ls user + iptables rule
sudo ./scripts/mitm-redirect.sh install sudo ./scripts/mitm-redirect.sh install
# Run proxy (standalone LS + MITM both enabled by default) # Run proxy (standalone LS + MITM both enabled by default)
RUST_LOG=info ./target/release/antigravity-proxy RUST_LOG=info ./target/release/zerogravity
# Check intercepted usage # Check intercepted usage
curl -s http://localhost:8741/v1/usage | jq . curl -s http://localhost:8741/v1/usage | jq .
@@ -150,7 +150,7 @@ sudo ./scripts/mitm-redirect.sh uninstall
No iptables or sudo needed. The LS connects through `HTTPS_PROXY` instead: No iptables or sudo needed. The LS connects through `HTTPS_PROXY` instead:
```bash ```bash
RUST_LOG=info ./target/release/antigravity-proxy --headless RUST_LOG=info ./target/release/zerogravity --headless
``` ```
--- ---

View File

@@ -5,7 +5,7 @@ Per-call debug traces for inspecting request/response flow. Every API call write
## Location ## Location
``` ```
~/.config/antigravity-proxy/traces/{YYYY-MM-DD}/{HH-MM-SS.sss}_{cascade_short}/ ~/.config/zerogravity/traces/{YYYY-MM-DD}/{HH-MM-SS.sss}_{cascade_short}/
``` ```
Disable with `--no-trace`. Disable with `--no-trace`.
@@ -108,11 +108,11 @@ Traces are designed for LLM consumption. To inspect the last trace:
```bash ```bash
# Find latest trace # Find latest trace
ls -t ~/.config/antigravity-proxy/traces/$(date +%Y-%m-%d)/ | head -1 ls -t ~/.config/zerogravity/traces/$(date +%Y-%m-%d)/ | head -1
# Read the summary # Read the summary
cat ~/.config/antigravity-proxy/traces/$(date +%Y-%m-%d)/$(ls -t ~/.config/antigravity-proxy/traces/$(date +%Y-%m-%d)/ | head -1)/summary.md cat ~/.config/zerogravity/traces/$(date +%Y-%m-%d)/$(ls -t ~/.config/zerogravity/traces/$(date +%Y-%m-%d)/ | head -1)/summary.md
# Grep for failures # Grep for failures
grep 'outcome=.*error\|outcome=.*timeout' ~/.config/antigravity-proxy/traces/$(date +%Y-%m-%d)/*/meta.txt grep 'outcome=.*error\|outcome=.*timeout' ~/.config/zerogravity/traces/$(date +%Y-%m-%d)/*/meta.txt
``` ```

View File

@@ -6,7 +6,7 @@
# modification, no system-wide changes. # modification, no system-wide changes.
# #
# Flow: # Flow:
# 1. Standalone LS runs as 'antigravity-ls' user (via sudo -u) # 1. Standalone LS runs as 'zerogravity-ls' user (via sudo -u)
# 2. iptables catches :443 traffic from that UID only → REDIRECT to MITM port # 2. iptables catches :443 traffic from that UID only → REDIRECT to MITM port
# 3. MITM terminates TLS (Go client trusts our CA via SSL_CERT_FILE) # 3. MITM terminates TLS (Go client trusts our CA via SSL_CERT_FILE)
# 4. MITM forwards upstream, captures usage # 4. MITM forwards upstream, captures usage
@@ -24,10 +24,10 @@
set -euo pipefail set -euo pipefail
MITM_PORT="${2:-8742}" MITM_PORT="${2:-8742}"
LS_USER="antigravity-ls" LS_USER="zerogravity-ls"
DATA_DIR="/tmp/antigravity-standalone" DATA_DIR="/tmp/antigravity-standalone"
LS_BINARY="/usr/share/antigravity/resources/app/extensions/antigravity/bin/language_server_linux_x64" LS_BINARY="/usr/share/antigravity/resources/app/extensions/antigravity/bin/language_server_linux_x64"
SUDOERS_FILE="/etc/sudoers.d/antigravity-ls" SUDOERS_FILE="/etc/sudoers.d/zerogravity-ls"
install() { install() {
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
@@ -54,7 +54,7 @@ install() {
echo " + data dir: $DATA_DIR (mode 1777, writable by all)" echo " + data dir: $DATA_DIR (mode 1777, writable by all)"
# ── 3. Sudoers entry ──────────────────────────────────────────────── # ── 3. Sudoers entry ────────────────────────────────────────────────
# Allow the invoking user (SUDO_USER) to run ANY command as antigravity-ls. # Allow the invoking user (SUDO_USER) to run ANY command as zerogravity-ls.
# This is needed for the proxy to spawn the LS binary. # This is needed for the proxy to spawn the LS binary.
local REAL_USER="${SUDO_USER:-$(logname 2>/dev/null || whoami)}" local REAL_USER="${SUDO_USER:-$(logname 2>/dev/null || whoami)}"
cat > "$SUDOERS_FILE" <<EOF cat > "$SUDOERS_FILE" <<EOF
@@ -78,7 +78,7 @@ EOF
echo echo
echo "[mitm-redirect] ✓ Installed (only affects uid=$LS_UID)" echo "[mitm-redirect] ✓ Installed (only affects uid=$LS_UID)"
echo " Restart the proxy to take effect:" echo " Restart the proxy to take effect:"
echo " RUST_LOG=info ./target/release/antigravity-proxy --standalone" echo " RUST_LOG=info ./target/release/zerogravity --standalone"
} }
uninstall() { uninstall() {

View File

@@ -390,7 +390,7 @@ pub(crate) async fn handle_completions(
if token.is_empty() { if token.is_empty() {
return err_response( return err_response(
StatusCode::UNAUTHORIZED, StatusCode::UNAUTHORIZED,
"No OAuth token. POST to /v1/token or set ANTIGRAVITY_OAUTH_TOKEN env var.".into(), "No OAuth token. POST to /v1/token or set ZEROGRAVITY_TOKEN env var.".into(),
"authentication_error", "authentication_error",
); );
} }

View File

@@ -195,7 +195,7 @@ async fn handle_gemini_inner(
if token.is_empty() { if token.is_empty() {
return err_response( return err_response(
StatusCode::UNAUTHORIZED, StatusCode::UNAUTHORIZED,
"No OAuth token. POST to /v1/token or set ANTIGRAVITY_OAUTH_TOKEN env var.".into(), "No OAuth token. POST to /v1/token or set ZEROGRAVITY_TOKEN env var.".into(),
"authentication_error", "authentication_error",
); );
} }

View File

@@ -226,7 +226,7 @@ pub(crate) async fn handle_responses(
if token.is_empty() { if token.is_empty() {
return err_response( return err_response(
StatusCode::UNAUTHORIZED, StatusCode::UNAUTHORIZED,
"No OAuth token. POST to /v1/token or set ANTIGRAVITY_OAUTH_TOKEN env var.".into(), "No OAuth token. POST to /v1/token or set ZEROGRAVITY_TOKEN env var.".into(),
"authentication_error", "authentication_error",
); );
} }

View File

@@ -154,7 +154,7 @@ impl Backend {
} }
// Then env var // Then env var
if let Ok(env_token) = std::env::var("ANTIGRAVITY_OAUTH_TOKEN") { if let Ok(env_token) = std::env::var("ZEROGRAVITY_TOKEN") {
if !env_token.is_empty() { if !env_token.is_empty() {
let mut guard = self.inner.write().await; let mut guard = self.inner.write().await;
if guard.oauth_token != env_token { if guard.oauth_token != env_token {
@@ -607,12 +607,12 @@ fn discover() -> Result<BackendInner, String> {
https_port = "3100".to_string(); https_port = "3100".to_string();
} }
let oauth_token = std::env::var("ANTIGRAVITY_OAUTH_TOKEN") let oauth_token = std::env::var("ZEROGRAVITY_TOKEN")
.ok() .ok()
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
.or_else(|| { .or_else(|| {
let home = std::env::var("HOME").unwrap_or_default(); let home = std::env::var("HOME").unwrap_or_default();
let path = format!("{home}/.config/antigravity-proxy-token"); let path = format!("{home}/.config/zerogravity/token");
fs::read_to_string(&path) fs::read_to_string(&path)
.ok() .ok()
.map(|s| s.trim().to_string()) .map(|s| s.trim().to_string())

View File

@@ -1,3 +1,3 @@
fn main() { fn main() {
antigravity_proxy::snapshot::run_cli(); zerogravity::snapshot::run_cli();
} }

273
src/bin/zg.rs Normal file
View File

@@ -0,0 +1,273 @@
//! `zg` — ZeroGravity daemon manager.
//!
//! All commands exit immediately (safe for agent use via fast-bash MCP).
use std::process::{Command, Stdio};
const SERVICE: &str = "zerogravity";
const PORT: u16 = 8741;
// ANSI colors
const RED: &str = "\x1b[0;31m";
const GREEN: &str = "\x1b[0;32m";
const YELLOW: &str = "\x1b[1;33m";
const CYAN: &str = "\x1b[0;36m";
const BOLD: &str = "\x1b[1m";
const DIM: &str = "\x1b[2m";
const NC: &str = "\x1b[0m";
fn main() {
let args: Vec<String> = std::env::args().collect();
let cmd = args.get(1).map(|s| s.as_str()).unwrap_or("");
let arg = args.get(2).map(|s| s.as_str());
match cmd {
"start" => do_start(),
"stop" => do_stop(),
"restart" => do_restart(),
"rebuild" => do_build(),
"status" => do_status(),
"logs" => do_logs(arg.unwrap_or("30"), false),
"logs-follow" => do_logs(arg.unwrap_or("30"), true),
"logs-all" => do_logs_all(),
"test" => do_test(arg.unwrap_or("Say hello in exactly 3 words")),
"health" => do_health(),
_ => usage(),
}
}
fn usage() {
println!("{BOLD}zg{NC} — ZeroGravity daemon manager\n");
println!(" {CYAN}start{NC} Start the proxy daemon");
println!(" {CYAN}stop{NC} Stop the proxy daemon");
println!(" {CYAN}restart{NC} Rebuild + restart");
println!(" {CYAN}rebuild{NC} Build release binary only");
println!(" {CYAN}status{NC} Service status + quota + usage");
println!(" {CYAN}logs{NC} [N] Show last N lines (default 30)");
println!(" {CYAN}logs-follow{NC} [N] Tail last N lines + follow");
println!(" {CYAN}logs-all{NC} Full log dump");
println!(" {CYAN}test{NC} [msg] Quick test request (gemini-3-flash)");
println!(" {CYAN}health{NC} Health check");
}
fn project_dir() -> String {
// Resolve project dir from binary location:
// binary is at <project>/target/release/zg or <project>/target/debug/zg
let exe = std::env::current_exe().expect("cannot resolve exe path");
let exe_dir = exe.parent().expect("no parent dir");
// Walk up from target/release/ or target/debug/
if let Some(target_dir) = exe_dir.parent() {
if let Some(project) = target_dir.parent() {
return project.to_string_lossy().to_string();
}
}
// Fallback: use current dir
std::env::current_dir()
.expect("no cwd")
.to_string_lossy()
.to_string()
}
fn base_url() -> String {
let port = std::env::var("PROXY_PORT")
.ok()
.and_then(|p| p.parse::<u16>().ok())
.unwrap_or(PORT);
format!("http://localhost:{port}")
}
fn systemctl(args: &[&str]) -> bool {
Command::new("systemctl")
.arg("--user")
.args(args)
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn curl_get(path: &str) -> Option<String> {
let url = format!("{}{}", base_url(), path);
Command::new("curl")
.args(["-sf", &url])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
}
fn curl_post(path: &str, body: &str) -> Option<String> {
let url = format!("{}{}", base_url(), path);
Command::new("curl")
.args(["-sf", &url, "-H", "Content-Type: application/json", "-d", body])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
}
fn health_ok() -> bool {
curl_get("/health").is_some()
}
fn jq_print(json: &str) {
let mut child = Command::new("jq")
.arg(".")
.stdin(Stdio::piped())
.spawn()
.expect("jq not found");
if let Some(stdin) = child.stdin.as_mut() {
use std::io::Write;
let _ = stdin.write_all(json.as_bytes());
}
let _ = child.wait();
}
// ── Commands ──
fn do_build() {
let dir = project_dir();
println!("{YELLOW}Building release binary...{NC}");
let status = Command::new("cargo")
.args(["build", "--release"])
.current_dir(&dir)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.expect("failed to run cargo");
if status.success() {
println!("{GREEN}Build complete.{NC}");
} else {
eprintln!("{RED}Build failed.{NC}");
std::process::exit(1);
}
}
fn do_start() {
systemctl(&["daemon-reload"]);
systemctl(&["start", SERVICE]);
println!("{GREEN}Started.{NC} Waiting for ready...");
for _ in 0..20 {
if health_ok() {
println!("{GREEN}ZeroGravity is up on port {PORT}.{NC}");
return;
}
std::thread::sleep(std::time::Duration::from_millis(500));
}
eprintln!("{RED}Proxy didn't become healthy in 10s. Check logs:{NC}");
let _ = Command::new("journalctl")
.args(["--user", "-u", SERVICE, "--no-pager", "-n", "20"])
.status();
std::process::exit(1);
}
fn do_stop() {
let _ = systemctl(&["stop", SERVICE]);
println!("{YELLOW}Stopped.{NC}");
}
fn do_restart() {
println!("{YELLOW}Stopping...{NC}");
do_stop();
do_build();
do_start();
}
fn do_status() {
println!("{BOLD}── Service ──{NC}");
let output = Command::new("systemctl")
.args(["--user", "status", SERVICE, "--no-pager"])
.output();
match output {
Ok(o) => {
let text = String::from_utf8_lossy(&o.stdout);
// Print first 6 lines
for (i, line) in text.lines().enumerate() {
if i >= 6 { break; }
println!("{line}");
}
}
Err(_) => println!("{RED}Not running{NC}"),
}
println!();
if !health_ok() {
println!("{RED}Proxy is not responding on port {PORT}.{NC}");
std::process::exit(1);
}
println!("{BOLD}── Quota ──{NC}");
match curl_get("/v1/quota") {
Some(json) => jq_print(&json),
None => println!("{DIM}(no quota data){NC}"),
}
println!();
println!("{BOLD}── Usage ──{NC}");
match curl_get("/v1/usage") {
Some(json) => jq_print(&json),
None => println!("{DIM}(no usage data){NC}"),
}
println!();
println!("{BOLD}── Sessions ──{NC}");
match curl_get("/v1/sessions") {
Some(json) => jq_print(&json),
None => println!("{DIM}(no sessions){NC}"),
}
}
fn do_logs(n: &str, follow: bool) {
let mut args = vec!["--user", "-u", SERVICE, "--no-pager", "-n", n];
if follow {
args.push("-f");
}
let _ = Command::new("journalctl")
.args(&args)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status();
}
fn do_logs_all() {
let _ = Command::new("journalctl")
.args(["--user", "-u", SERVICE, "--no-pager"])
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status();
}
fn do_test(msg: &str) {
println!("{CYAN}Testing:{NC} {msg}");
let body = format!(
r#"{{"model":"gemini-3-flash","input":"{}","stream":false,"timeout":30}}"#,
msg.replace('"', r#"\""#)
);
match curl_post("/v1/responses", &body) {
Some(json) => jq_print(&json),
None => {
eprintln!("{RED}Request failed. Is the proxy running?{NC}");
std::process::exit(1);
}
}
}
fn do_health() {
match curl_get("/health") {
Some(json) => {
jq_print(&json);
println!("{GREEN}Healthy{NC}");
}
None => {
println!("{RED}Not responding{NC}");
std::process::exit(1);
}
}
}

View File

@@ -185,7 +185,7 @@ pub fn log_base() -> String {
/// Token file path. /// Token file path.
pub fn token_file_path() -> String { pub fn token_file_path() -> String {
let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string()); let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
format!("{home}/.config/antigravity-proxy-token") format!("{home}/.config/zerogravity/token")
} }
/// User-Agent string matching the Electron webview — computed once. /// User-Agent string matching the Electron webview — computed once.

View File

@@ -1,4 +1,4 @@
//! Antigravity OpenAI Proxy — Rust edition v3 (stealth hardened). //! ZeroGravity — Rust edition v3 (stealth hardened).
//! //!
//! Single-binary replacement for server.py. BoringSSL TLS impersonation, //! Single-binary replacement for server.py. BoringSSL TLS impersonation,
//! byte-exact protobuf encoding, Chrome header fingerprinting, cascade //! byte-exact protobuf encoding, Chrome header fingerprinting, cascade
@@ -26,8 +26,8 @@ use mitm::store::MitmStore;
#[derive(Parser)] #[derive(Parser)]
#[command( #[command(
name = "antigravity-proxy", name = "zerogravity",
about = "Antigravity OpenAI Proxy (stealth)" about = "ZeroGravity — stealth LLM proxy"
)] )]
struct Cli { struct Cli {
/// Port to listen on /// Port to listen on
@@ -64,7 +64,7 @@ struct Cli {
#[arg(long, conflicts_with = "headless")] #[arg(long, conflicts_with = "headless")]
classic: bool, classic: bool,
/// Disable per-call debug traces (on by default, writes JSON to ~/.config/antigravity-proxy/traces/) /// Disable per-call debug traces (on by default, writes JSON to ~/.config/zerogravity/traces/)
#[arg(long)] #[arg(long)]
no_trace: bool, no_trace: bool,
} }
@@ -236,12 +236,12 @@ async fn main() {
let backend = Arc::new(if let Some((_, port, ref csrf)) = standalone_ls { let backend = Arc::new(if let Some((_, port, ref csrf)) = standalone_ls {
// Build backend pointing at standalone LS // Build backend pointing at standalone LS
let oauth = std::env::var("ANTIGRAVITY_OAUTH_TOKEN") let oauth = std::env::var("ZEROGRAVITY_TOKEN")
.ok() .ok()
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
.or_else(|| { .or_else(|| {
let home = std::env::var("HOME").unwrap_or_default(); let home = std::env::var("HOME").unwrap_or_default();
let path = format!("{home}/.config/antigravity-proxy-token"); let path = format!("{home}/.config/zerogravity/token");
std::fs::read_to_string(&path) std::fs::read_to_string(&path)
.ok() .ok()
.map(|s| s.trim().to_string()) .map(|s| s.trim().to_string())
@@ -282,7 +282,7 @@ async fn main() {
let trace_collector = trace::TraceCollector::new(trace_enabled); let trace_collector = trace::TraceCollector::new(trace_enabled);
if trace_enabled { if trace_enabled {
trace_collector.cleanup_old_traces(7); trace_collector.cleanup_old_traces(7);
info!("Debug tracing enabled → ~/.config/antigravity-proxy/traces/"); info!("Debug tracing enabled → ~/.config/zerogravity/traces/");
} }
let state = Arc::new(AppState { let state = Arc::new(AppState {
@@ -391,7 +391,7 @@ fn print_banner(
let ver = crate::constants::antigravity_version(); let ver = crate::constants::antigravity_version();
println!(); println!();
println!(" \x1b[1;35m>> antigravity-proxy\x1b[0m \x1b[2mv{ver}\x1b[0m"); println!(" \x1b[1;35m>> zerogravity\x1b[0m \x1b[2mv{ver}\x1b[0m");
println!(" \x1b[2m────────────────────────────────────────────────\x1b[0m"); println!(" \x1b[2m────────────────────────────────────────────────\x1b[0m");
println!(); println!();
println!(" \x1b[1mcore\x1b[0m"); println!(" \x1b[1mcore\x1b[0m");
@@ -462,11 +462,11 @@ fn print_banner(
if token == "NOT SET" { if token == "NOT SET" {
println!(" \x1b[1;33m[!]\x1b[0m no oauth token"); println!(" \x1b[1;33m[!]\x1b[0m no oauth token");
println!(" export ANTIGRAVITY_OAUTH_TOKEN=ya29.xxx"); println!(" export ZEROGRAVITY_TOKEN=ya29.xxx");
println!( println!(
" curl -X POST http://127.0.0.1:{port}/v1/token -d '{{\"token\":\"ya29.xxx\"}}'" " curl -X POST http://127.0.0.1:{port}/v1/token -d '{{\"token\":\"ya29.xxx\"}}'"
); );
println!(" echo 'ya29.xxx' > ~/.config/antigravity-proxy-token"); println!(" echo 'ya29.xxx' > ~/.config/zerogravity/token");
println!(); println!();
} }
} }
@@ -522,5 +522,5 @@ fn dirs_data_dir() -> std::path::PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
std::path::PathBuf::from(home) std::path::PathBuf::from(home)
.join(".config") .join(".config")
.join("antigravity-proxy") .join("zerogravity")
} }

View File

@@ -24,13 +24,13 @@ const LS_BINARY_PATH: &str =
const APP_ROOT: &str = "/usr/share/antigravity/resources/app"; const APP_ROOT: &str = "/usr/share/antigravity/resources/app";
/// Data directory for the standalone LS. /// Data directory for the standalone LS.
const DATA_DIR: &str = "/tmp/antigravity-standalone"; const DATA_DIR: &str = "/tmp/zerogravity-standalone";
/// System user for UID-scoped iptables isolation. /// System user for UID-scoped iptables isolation.
const LS_USER: &str = "antigravity-ls"; const LS_USER: &str = "zerogravity-ls";
/// Path for the compiled dns_redirect.so preload library. /// Path for the compiled dns_redirect.so preload library.
const DNS_REDIRECT_SO_PATH: &str = "/tmp/antigravity-dns-redirect.so"; const DNS_REDIRECT_SO_PATH: &str = "/tmp/zerogravity-dns-redirect.so";
/// Source file for the DNS redirect preload library (relative to binary). /// Source file for the DNS redirect preload library (relative to binary).
const DNS_REDIRECT_C_SOURCE: &str = include_str!("../mitm/dns_redirect.c"); const DNS_REDIRECT_C_SOURCE: &str = include_str!("../mitm/dns_redirect.c");

View File

@@ -57,9 +57,9 @@ impl StandaloneLS {
1, // DETECT_AND_USE_PROXY_ENABLED 1, // DETECT_AND_USE_PROXY_ENABLED
); );
// Setup data dir (mode 1777 so both current user and antigravity-ls can write) // Setup data dir (mode 1777 so both current user and zerogravity-ls can write)
let gemini_dir = format!("{DATA_DIR}/.gemini"); let gemini_dir = format!("{DATA_DIR}/.gemini");
let app_data_dir = format!("{DATA_DIR}/.gemini/antigravity-standalone"); let app_data_dir = format!("{DATA_DIR}/.gemini/zerogravity-standalone");
let annotations_dir = format!("{app_data_dir}/annotations"); let annotations_dir = format!("{app_data_dir}/annotations");
let brain_dir = format!("{app_data_dir}/brain"); let brain_dir = format!("{app_data_dir}/brain");
for dir in [ for dir in [
@@ -77,7 +77,7 @@ impl StandaloneLS {
} }
} }
// Check if data dir is writable by writing a test file. // Check if data dir is writable by writing a test file.
// Old runs as `antigravity-ls` user leave dirs owned by that user. // Old runs as `zerogravity-ls` user leave dirs owned by that user.
let test_path = format!("{app_data_dir}/.write_test"); let test_path = format!("{app_data_dir}/.write_test");
if std::fs::write(&test_path, b"ok").is_err() { if std::fs::write(&test_path, b"ok").is_err() {
eprintln!( eprintln!(
@@ -138,12 +138,12 @@ impl StandaloneLS {
}) })
.unwrap_or_else(|| { .unwrap_or_else(|| {
// Fall back to env var / token file // Fall back to env var / token file
let token = std::env::var("ANTIGRAVITY_OAUTH_TOKEN") let token = std::env::var("ZEROGRAVITY_TOKEN")
.ok() .ok()
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
.or_else(|| { .or_else(|| {
let home = std::env::var("HOME").unwrap_or_default(); let home = std::env::var("HOME").unwrap_or_default();
let path = format!("{home}/.config/antigravity-proxy-token"); let path = format!("{home}/.config/zerogravity/token");
std::fs::read_to_string(&path) std::fs::read_to_string(&path)
.ok() .ok()
.map(|s| s.trim().to_string()) .map(|s| s.trim().to_string())
@@ -153,7 +153,7 @@ impl StandaloneLS {
if !token.is_empty() { if !token.is_empty() {
info!("Loaded OAuth token from env/file (no refresh token — manual refresh needed)"); info!("Loaded OAuth token from env/file (no refresh token — manual refresh needed)");
} else { } else {
eprintln!("[headless] ⚠ No OAuth token found. Login to Antigravity first, or set ANTIGRAVITY_OAUTH_TOKEN"); eprintln!("[headless] ⚠ No OAuth token found. Login to Antigravity first, or set ZEROGRAVITY_TOKEN");
} }
(token, None) (token, None)
}); });
@@ -224,7 +224,7 @@ impl StandaloneLS {
"https://daily-cloudcode-pa.googleapis.com".to_string() "https://daily-cloudcode-pa.googleapis.com".to_string()
}, },
"-app_data_dir".to_string(), "-app_data_dir".to_string(),
"antigravity-standalone".to_string(), "zerogravity-standalone".to_string(),
"-gemini_dir".to_string(), "-gemini_dir".to_string(),
gemini_dir, gemini_dir,
]; ];
@@ -240,16 +240,16 @@ impl StandaloneLS {
if let Some(mitm) = mitm_config { if let Some(mitm) = mitm_config {
// Go's SSL_CERT_FILE replaces the entire system cert pool, so we // Go's SSL_CERT_FILE replaces the entire system cert pool, so we
// need a combined bundle: system CAs + our MITM CA // need a combined bundle: system CAs + our MITM CA
// Write to /tmp — accessible by antigravity-ls user // Write to /tmp — accessible by zerogravity-ls user
// (user's ~/.config/ is not traversable by other UIDs) // (user's ~/.config/ is not traversable by other UIDs)
let combined_ca_path = "/tmp/antigravity-mitm-combined-ca.pem".to_string(); let combined_ca_path = "/tmp/zerogravity-mitm-ca.pem".to_string();
let system_ca = let system_ca =
std::fs::read_to_string("/etc/ssl/certs/ca-certificates.crt").unwrap_or_default(); std::fs::read_to_string("/etc/ssl/certs/ca-certificates.crt").unwrap_or_default();
let mitm_ca = std::fs::read_to_string(&mitm.ca_cert_path) let mitm_ca = std::fs::read_to_string(&mitm.ca_cert_path)
.map_err(|e| format!("Failed to read MITM CA cert: {e}"))?; .map_err(|e| format!("Failed to read MITM CA cert: {e}"))?;
std::fs::write(&combined_ca_path, format!("{system_ca}\n{mitm_ca}")) std::fs::write(&combined_ca_path, format!("{system_ca}\n{mitm_ca}"))
.map_err(|e| format!("Failed to write combined CA bundle: {e}"))?; .map_err(|e| format!("Failed to write combined CA bundle: {e}"))?;
// Make readable by antigravity-ls user // Make readable by zerogravity-ls user
#[cfg(unix)] #[cfg(unix)]
{ {
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
@@ -286,18 +286,18 @@ impl StandaloneLS {
env_vars.push(("LD_PRELOAD".into(), so)); env_vars.push(("LD_PRELOAD".into(), so));
env_vars.push(( env_vars.push((
"DNS_REDIRECT_LOG".into(), "DNS_REDIRECT_LOG".into(),
"/tmp/antigravity-dns-redirect.log".into(), "/tmp/zerogravity-dns-redirect.log".into(),
)); ));
} }
} }
} }
// In headless mode, never use sudo — run as current user // In headless mode, never use sudo — run as current user
// In normal mode, use sudo if 'antigravity-ls' user exists // In normal mode, use sudo if 'zerogravity-ls' user exists
let use_sudo = !headless && has_ls_user(); let use_sudo = !headless && has_ls_user();
let mut cmd = if use_sudo { let mut cmd = if use_sudo {
info!("Using UID isolation: spawning LS as 'antigravity-ls' user"); info!("Using UID isolation: spawning LS as 'zerogravity-ls' user");
let mut c = Command::new("sudo"); let mut c = Command::new("sudo");
c.args(["-n", "-u", LS_USER, "--", "/usr/bin/env"]); c.args(["-n", "-u", LS_USER, "--", "/usr/bin/env"]);
for (k, v) in &env_vars { for (k, v) in &env_vars {
@@ -317,7 +317,7 @@ impl StandaloneLS {
}; };
// Capture stderr for debugging — logs to /tmp so we can diagnose LS failures // Capture stderr for debugging — logs to /tmp so we can diagnose LS failures
let stderr_file = std::fs::File::create("/tmp/antigravity-ls-debug.log") let stderr_file = std::fs::File::create("/tmp/zerogravity-ls-debug.log")
.map_err(|e| format!("Failed to create LS debug log: {e}"))?; .map_err(|e| format!("Failed to create LS debug log: {e}"))?;
cmd.stdin(Stdio::piped()) cmd.stdin(Stdio::piped())
.stdout(Stdio::null()) .stdout(Stdio::null())
@@ -342,7 +342,7 @@ impl StandaloneLS {
let ls_pid = if use_sudo { let ls_pid = if use_sudo {
// Give sudo a moment to spawn the real process // Give sudo a moment to spawn the real process
std::thread::sleep(std::time::Duration::from_millis(500)); std::thread::sleep(std::time::Duration::from_millis(500));
// Find the LS process owned by antigravity-ls user // Find the LS process owned by zerogravity-ls user
find_ls_pid_for_user(LS_USER).ok() find_ls_pid_for_user(LS_USER).ok()
} else { } else {
Some(child.id()) Some(child.id())
@@ -424,7 +424,7 @@ impl StandaloneLS {
// The child is sudo which already exited. Kill the actual LS. // The child is sudo which already exited. Kill the actual LS.
if let Some(pid) = self.ls_pid { if let Some(pid) = self.ls_pid {
info!(pid, "Killing LS process via sudo -u {}", LS_USER); info!(pid, "Killing LS process via sudo -u {}", LS_USER);
// Run kill AS the antigravity-ls user (same UID can signal) // Run kill AS the zerogravity-ls user (same UID can signal)
let ok = std::process::Command::new("sudo") let ok = std::process::Command::new("sudo")
.args(["-n", "-u", LS_USER, "kill", "-TERM", &pid.to_string()]) .args(["-n", "-u", LS_USER, "kill", "-TERM", &pid.to_string()])
.stdout(Stdio::null()) .stdout(Stdio::null())

View File

@@ -1,7 +1,7 @@
//! Per-call debug trace system. //! Per-call debug trace system.
//! //!
//! Every API call gets a structured JSON trace file written to //! Every API call gets a structured JSON trace file written to
//! `~/.config/antigravity-proxy/traces/{YYYY-MM-DD}/{HH-MM-SS}_{cascade_short}.json`. //! `~/.config/zerogravity/traces/{YYYY-MM-DD}/{HH-MM-SS}_{cascade_short}.json`.
//! //!
//! Designed for LLM consumption: compact, structured, no raw bodies. //! Designed for LLM consumption: compact, structured, no raw bodies.
@@ -23,7 +23,7 @@ impl TraceCollector {
let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
let traces_dir = PathBuf::from(home) let traces_dir = PathBuf::from(home)
.join(".config") .join(".config")
.join("antigravity-proxy") .join("zerogravity")
.join("traces"); .join("traces");
Self { Self {
enabled, enabled,