Files
zerogravity/scripts/standalone-ls.sh

278 lines
12 KiB
Bash
Executable File

#!/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"