feat: initial commit — antigravity proxy with MITM, standalone LS, and snapshot tooling
This commit is contained in:
277
scripts/standalone-ls.sh
Executable file
277
scripts/standalone-ls.sh
Executable file
@@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env bash
|
||||
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
||||
# ║ Standalone Language Server Launcher ║
|
||||
# ║ ║
|
||||
# ║ Launches an isolated LS instance that: ║
|
||||
# ║ - Shares OAuth via the main app's extension server ║
|
||||
# ║ - Has its own HTTPS port, data dir, and cascades ║
|
||||
# ║ - Optionally routes traffic through our MITM proxy ║
|
||||
# ║ - Can capture a clean traffic snapshot ║
|
||||
# ║ ║
|
||||
# ║ Usage: ║
|
||||
# ║ ./standalone-ls.sh # Launch, test, exit ║
|
||||
# ║ ./standalone-ls.sh --fg # Foreground (stay alive) ║
|
||||
# ║ ./standalone-ls.sh --mitm # Route through MITM proxy ║
|
||||
# ║ ./standalone-ls.sh --snapshot # Capture clean traffic dump ║
|
||||
# ║ ./standalone-ls.sh --snapshot --prompt "Say hello" ║
|
||||
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# ── Defaults ──────────────────────────────────────────────────────────────────
|
||||
LS_BIN="/usr/share/antigravity/resources/app/extensions/antigravity/bin/language_server_linux_x64"
|
||||
HTTPS_PORT="42200"
|
||||
DATA_DIR="/tmp/antigravity-standalone"
|
||||
FOREGROUND=false
|
||||
USE_MITM=false
|
||||
SNAPSHOT=false
|
||||
TIMEOUT=15
|
||||
PROMPT=""
|
||||
MODEL="MODEL_PLACEHOLDER_M3"
|
||||
|
||||
# ── Parse args ────────────────────────────────────────────────────────────────
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--port) HTTPS_PORT="$2"; shift 2 ;;
|
||||
--mitm) USE_MITM=true; shift ;;
|
||||
--fg) FOREGROUND=true; shift ;;
|
||||
--timeout) TIMEOUT="$2"; shift 2 ;;
|
||||
--snapshot) SNAPSHOT=true; TIMEOUT=30; shift ;;
|
||||
--prompt) PROMPT="$2"; shift 2 ;;
|
||||
--model) MODEL="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --port PORT HTTPS port for standalone LS (default: 42200)"
|
||||
echo " --mitm Route traffic through MITM proxy"
|
||||
echo " --fg Run in foreground (stay alive)"
|
||||
echo " --timeout SECS Background mode timeout (default: 15)"
|
||||
echo " --snapshot Capture clean traffic snapshot"
|
||||
echo " --prompt TEXT Prompt to send (snapshot mode)"
|
||||
echo " --model MODEL Model alias (default: MODEL_PLACEHOLDER_M3)"
|
||||
exit 0 ;;
|
||||
*) echo "Unknown option: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Discover main LS config ──────────────────────────────────────────────────
|
||||
MAIN_PID=$(pgrep -f 'language_server_linux_x64' | head -1 || true)
|
||||
if [[ -z "$MAIN_PID" ]]; then
|
||||
echo "[-] No main LS process found. Main Antigravity must be running."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MAIN_CSRF=$(tr '\0' '\n' < /proc/"$MAIN_PID"/cmdline | grep -A1 'csrf_token' | tail -1)
|
||||
EXT_PORT=$(tr '\0' '\n' < /proc/"$MAIN_PID"/cmdline | grep -A1 'extension_server_port' | tail -1)
|
||||
|
||||
echo "[*] Main LS PID: $MAIN_PID"
|
||||
echo "[*] CSRF: $MAIN_CSRF"
|
||||
echo "[*] Extension server: $EXT_PORT"
|
||||
|
||||
# ── Build protobuf metadata for stdin ─────────────────────────────────────────
|
||||
TS=$(date +%s)
|
||||
METADATA=$(python3 -c "
|
||||
import sys
|
||||
def v(n):
|
||||
r = bytearray()
|
||||
while n > 0x7f:
|
||||
r.append((n & 0x7f) | 0x80)
|
||||
n >>= 7
|
||||
r.append(n & 0x7f)
|
||||
return bytes(r)
|
||||
def s(f, val):
|
||||
t = v((f << 3) | 2)
|
||||
d = val.encode()
|
||||
return t + v(len(d)) + d
|
||||
buf = bytearray()
|
||||
buf += s(1, 'standalone-api-key-$TS')
|
||||
buf += s(3, 'antigravity')
|
||||
buf += s(4, '1.15.8')
|
||||
buf += s(5, '1.16.39')
|
||||
buf += s(6, 'en_US')
|
||||
buf += s(10, 'standalone-session-$TS')
|
||||
buf += s(11, 'antigravity')
|
||||
sys.stdout.buffer.write(bytes(buf))
|
||||
" | base64)
|
||||
|
||||
# ── Setup data directory ──────────────────────────────────────────────────────
|
||||
mkdir -p "$DATA_DIR/.gemini"
|
||||
|
||||
# ── MITM environment ─────────────────────────────────────────────────────────
|
||||
MITM_ENV=()
|
||||
if $USE_MITM; then
|
||||
REAL_HOME="${SUDO_USER:+$(getent passwd "$SUDO_USER" | cut -d: -f6)}"
|
||||
REAL_HOME="${REAL_HOME:-$HOME}"
|
||||
MITM_PORT_FILE="${REAL_HOME}/.config/antigravity-proxy/mitm-port"
|
||||
CA_PATH="${REAL_HOME}/.config/antigravity-proxy/mitm-ca.pem"
|
||||
|
||||
if [[ -f "$MITM_PORT_FILE" ]]; then
|
||||
MITM_PORT=$(cat "$MITM_PORT_FILE")
|
||||
else
|
||||
MITM_PORT="8742"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$CA_PATH" ]]; then
|
||||
echo "[-] MITM CA cert not found at $CA_PATH"
|
||||
echo " Start the proxy first to generate it."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
COMBINED_CA="/tmp/antigravity-mitm-combined-ca.pem"
|
||||
SYS_CA=""
|
||||
for candidate in /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt /etc/ssl/cert.pem; do
|
||||
if [[ -f "$candidate" ]]; then SYS_CA="$candidate"; break; fi
|
||||
done
|
||||
if [[ -n "$SYS_CA" ]]; then
|
||||
cat "$SYS_CA" "$CA_PATH" > "$COMBINED_CA"
|
||||
else
|
||||
echo "[-] No system CA bundle found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MITM_ENV=(
|
||||
"HTTPS_PROXY=http://127.0.0.1:${MITM_PORT}"
|
||||
"SSL_CERT_FILE=${COMBINED_CA}"
|
||||
"GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=${COMBINED_CA}"
|
||||
)
|
||||
echo "[*] MITM: enabled (port $MITM_PORT)"
|
||||
else
|
||||
echo "[*] MITM: disabled (use --mitm to enable)"
|
||||
fi
|
||||
|
||||
# ── LS args ───────────────────────────────────────────────────────────────────
|
||||
LS_ARGS=(
|
||||
-enable_lsp
|
||||
-extension_server_port "$EXT_PORT"
|
||||
-csrf_token "$MAIN_CSRF"
|
||||
-server_port "$HTTPS_PORT"
|
||||
-workspace_id "standalone_$TS"
|
||||
-cloud_code_endpoint "https://daily-cloudcode-pa.googleapis.com"
|
||||
-app_data_dir "antigravity-standalone"
|
||||
-gemini_dir "$DATA_DIR/.gemini"
|
||||
)
|
||||
|
||||
# ── Extra env for snapshot mode ───────────────────────────────────────────────
|
||||
EXTRA_ENV=()
|
||||
if $SNAPSHOT; then
|
||||
EXTRA_ENV=("GODEBUG=http2debug=2")
|
||||
echo "[*] Snapshot: enabled (HTTP/2 debug tracing)"
|
||||
fi
|
||||
|
||||
# ── Banner ────────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " Standalone LS"
|
||||
echo " Port: $HTTPS_PORT (HTTPS)"
|
||||
echo " Data: $DATA_DIR"
|
||||
echo " Mode: $($FOREGROUND && echo "foreground" || echo "background ($TIMEOUT s)")"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# ── Foreground mode ───────────────────────────────────────────────────────────
|
||||
if $FOREGROUND; then
|
||||
echo "$METADATA" | base64 -d | \
|
||||
env "${MITM_ENV[@]+"${MITM_ENV[@]}"}" \
|
||||
"${EXTRA_ENV[@]+"${EXTRA_ENV[@]}"}" \
|
||||
ANTIGRAVITY_EDITOR_APP_ROOT="/usr/share/antigravity/resources/app" \
|
||||
exec "$LS_BIN" "${LS_ARGS[@]}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Background mode ──────────────────────────────────────────────────────────
|
||||
LOG="/tmp/standalone-ls.log"
|
||||
rm -f "$LOG"
|
||||
|
||||
echo "$METADATA" | base64 -d | \
|
||||
env "${MITM_ENV[@]+"${MITM_ENV[@]}"}" \
|
||||
"${EXTRA_ENV[@]+"${EXTRA_ENV[@]}"}" \
|
||||
ANTIGRAVITY_EDITOR_APP_ROOT="/usr/share/antigravity/resources/app" \
|
||||
timeout "$TIMEOUT" "$LS_BIN" "${LS_ARGS[@]}" \
|
||||
> "$LOG" 2>&1 &
|
||||
|
||||
LS_PID=$!
|
||||
echo "[*] PID: $LS_PID"
|
||||
|
||||
# Wait for init
|
||||
for i in $(seq 1 5); do
|
||||
sleep 1
|
||||
if ! kill -0 "$LS_PID" 2>/dev/null; then
|
||||
echo "[-] LS died after ${i}s"
|
||||
echo "=== LOGS ==="
|
||||
cat "$LOG"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "[+] LS alive and initialized"
|
||||
|
||||
# ── Snapshot mode: send a prompt and capture traffic ──────────────────────────
|
||||
if $SNAPSHOT; then
|
||||
if [[ -z "$PROMPT" ]]; then
|
||||
PROMPT="Say exactly: Hello standalone world"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[*] Sending cascade: \"$PROMPT\""
|
||||
CASCADE_ID=$(curl -sk --max-time 10 \
|
||||
"https://127.0.0.1:${HTTPS_PORT}/exa.language_server_pb.LanguageServerService/StartCascade" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-codeium-csrf-token: $MAIN_CSRF" \
|
||||
-H "Origin: vscode-file://vscode-app" \
|
||||
-d "{
|
||||
\"prompt\": \"$PROMPT\",
|
||||
\"modelOrAlias\": {\"model\": \"$MODEL\"},
|
||||
\"workspaceRootPaths\": [\"$DATA_DIR\"]
|
||||
}" 2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin).get('cascadeId',''))" 2>/dev/null || true)
|
||||
|
||||
echo "[*] Cascade: $CASCADE_ID"
|
||||
echo "[*] Waiting 15s for upstream API calls..."
|
||||
sleep 15
|
||||
|
||||
# Kill LS to flush logs
|
||||
kill "$LS_PID" 2>/dev/null
|
||||
wait "$LS_PID" 2>/dev/null || true
|
||||
|
||||
# Parse and display
|
||||
echo ""
|
||||
python3 "$SCRIPT_DIR/parse-snapshot.py" "$LOG"
|
||||
|
||||
# Also save raw log
|
||||
SNAPSHOT_FILE="/tmp/standalone-snapshot-$(date +%Y%m%d-%H%M%S).log"
|
||||
cp "$LOG" "$SNAPSHOT_FILE"
|
||||
echo ""
|
||||
echo "[*] Raw log saved to: $SNAPSHOT_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Normal mode: test and report ──────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "=== GetUserStatus ==="
|
||||
curl -sk "https://127.0.0.1:${HTTPS_PORT}/exa.language_server_pb.LanguageServerService/GetUserStatus" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-codeium-csrf-token: $MAIN_CSRF" \
|
||||
-H "Origin: vscode-file://vscode-app" \
|
||||
-d '{}' 2>/dev/null | python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
d = json.load(sys.stdin)
|
||||
us = d.get('userStatus', {})
|
||||
ps = us.get('planStatus', {})
|
||||
pi = ps.get('planInfo', {})
|
||||
print(f'Plan: {pi.get(\"planName\",\"?\")}, Prompt: {ps.get(\"availablePromptCredits\",\"?\")}, Flow: {ps.get(\"availableFlowCredits\",\"?\")}')
|
||||
ut = us.get('userTier', {})
|
||||
print(f'Tier: {ut.get(\"name\",\"?\")}')
|
||||
models = us.get('cascadeModelConfigData', {}).get('clientModelConfigs', [])
|
||||
print(f'Models: {len(models)}')
|
||||
for m in models[:5]:
|
||||
qi = m.get('quotaInfo', {})
|
||||
print(f' - {m.get(\"label\")}: remaining={qi.get(\"remainingFraction\",\"?\")}')
|
||||
except Exception as e:
|
||||
print(f'Error: {e}')
|
||||
" 2>/dev/null
|
||||
|
||||
echo ""
|
||||
kill "$LS_PID" 2>/dev/null || true
|
||||
wait "$LS_PID" 2>/dev/null || true
|
||||
echo "[*] Done"
|
||||
Reference in New Issue
Block a user