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