278 lines
12 KiB
Bash
Executable File
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"
|