From 1ec11649ac3e04085df50dc12e857a8feeb00ec3 Mon Sep 17 00:00:00 2001 From: arqueon <66568719+arqueon@users.noreply.github.com> Date: Fri, 26 Jun 2026 20:50:16 -0600 Subject: [PATCH 1/5] Add Antigravity quota backend --- .github/workflows/ci.yml | 36 ++- providers/get-antigravity-usage | 5 + providers/get-provider-health | 4 + providers/get-provider-usage | 209 ++++++++++++++++++ .../fixtures/antigravity-schema-changed.json | 3 + tests/fixtures/antigravity-valid.json | 28 +++ 6 files changed, 283 insertions(+), 2 deletions(-) create mode 100755 providers/get-antigravity-usage create mode 100644 tests/fixtures/antigravity-schema-changed.json create mode 100644 tests/fixtures/antigravity-valid.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c950e88..ba91fd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -220,10 +220,10 @@ jobs: with: persist-credentials: false - - name: Install dependencies (jq, curl) + - name: Install dependencies (jq, curl, sqlite3) run: | sudo apt-get update -qq - sudo apt-get install -y --no-install-recommends jq curl + sudo apt-get install -y --no-install-recommends jq curl sqlite3 - name: Smoke test — get-claude-usage (no credentials) run: | @@ -263,6 +263,38 @@ jobs: } echo "get-provider-usage smoke: OK — provider=${PROVIDER}" + - name: Integration test — Antigravity quota fixture + run: | + OUT="$(ANTIGRAVITY_RESPONSE_FILE=tests/fixtures/antigravity-valid.json \ + ANTIGRAVITY_ACCOUNT_EMAIL=test@example.invalid \ + providers/get-provider-usage "antigravity" "")" + echo "$OUT" | jq -e ' + length == 1 + and .[0].provider == "antigravity" + and .[0].usage.primary.resetDescription == "Claude" + and .[0].usage.primary.usedPercent == 75 + and .[0].usage.secondary.resetDescription == "Gemini 3 Flash" + and .[0].usage.secondary.usedPercent == 50 + and .[0].usage.tertiary.resetDescription == "Gemini 3 Pro" + and .[0].usage.tertiary.usedPercent == 20 + ' >/dev/null + + AUTH_ERROR="$(ANTIGRAVITY_RESPONSE_FILE=tests/fixtures/antigravity-valid.json \ + ANTIGRAVITY_HTTP_STATUS=401 providers/get-provider-usage "antigravity" "")" + echo "$AUTH_ERROR" | jq -e '.[0].error.code == 401' >/dev/null + + RATE_ERROR="$(ANTIGRAVITY_RESPONSE_FILE=tests/fixtures/antigravity-valid.json \ + ANTIGRAVITY_HTTP_STATUS=429 providers/get-provider-usage "antigravity" "")" + echo "$RATE_ERROR" | jq -e '.[0].error.code == 429' >/dev/null + + MISSING_SESSION="$(ANTIGRAVITY_STATE_DB=/does/not/exist \ + providers/get-provider-usage "antigravity" "")" + echo "$MISSING_SESSION" | jq -e '.[0].error.code == 2' >/dev/null + + SCHEMA_ERROR="$(ANTIGRAVITY_RESPONSE_FILE=tests/fixtures/antigravity-schema-changed.json \ + providers/get-provider-usage "antigravity" "")" + echo "$SCHEMA_ERROR" | jq -e '.[0].error.message | contains("schema changed")' >/dev/null + - name: Smoke test — all stubs delegate correctly (structure check) run: | FAIL=0 diff --git a/providers/get-antigravity-usage b/providers/get-antigravity-usage new file mode 100755 index 0000000..a98a351 --- /dev/null +++ b/providers/get-antigravity-usage @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$SCRIPT_DIR/get-provider-wrapper" antigravity diff --git a/providers/get-provider-health b/providers/get-provider-health index 1cccaad..7bc48b3 100755 --- a/providers/get-provider-health +++ b/providers/get-provider-health @@ -19,6 +19,10 @@ status_for() { claude) command -v claude >/dev/null 2>&1 || [ -d "$HOME/.claude" ] || { status="missing"; detail="claude CLI or ~/.claude"; } ;; copilot) command -v gh >/dev/null 2>&1 || has_any_env COPILOT_GITHUB_TOKEN GH_TOKEN GITHUB_TOKEN || { status="missing"; detail="gh CLI or GitHub token"; } ;; gemini) command -v gemini >/dev/null 2>&1 || has_any_env GEMINI_API_KEY GOOGLE_API_KEY GOOGLE_GENERATIVE_AI_API_KEY || { status="missing"; detail="gemini CLI or API key"; } ;; + antigravity) + command -v secret-tool >/dev/null 2>&1 || command -v sqlite3 >/dev/null 2>&1 || { status="missing"; detail="secret-tool or sqlite3"; } + [ "$status" != "ready" ] || command -v secret-tool >/dev/null 2>&1 || [ -r "${XDG_CONFIG_HOME:-$HOME/.config}/Antigravity/User/globalStorage/state.vscdb" ] || { status="missing"; detail="CLI keyring or IDE state"; } + ;; 9router) [ -f "$HOME/.9router/db/data.sqlite" ] || [ -f "$HOME/.9router/usage.json" ] || { status="missing"; detail="9Router usage database"; } ;; openrouter) has_any_env OPENROUTER_API_KEY || [ -f "$HOME/.9router/db/data.sqlite" ] || { status="missing"; detail="OPENROUTER_API_KEY or 9Router data"; } ;; deepseek) has_any_env DEEPSEEK_API_KEY || { status="missing"; detail="DEEPSEEK_API_KEY"; } ;; diff --git a/providers/get-provider-usage b/providers/get-provider-usage index 5b8da62..8fe7927 100755 --- a/providers/get-provider-usage +++ b/providers/get-provider-usage @@ -205,6 +205,212 @@ fetch_gemini_native() { json_error gemini gemini-local 2 provider "Gemini is not authenticated locally. Run gemini once, or set GEMINI_API_KEY/GOOGLE_API_KEY." } +extract_antigravity_keyring_token() { + jq -r ' + def token_string: + if type == "object" then + .access_token // .accessToken // .token // .Token // empty + elif type == "string" and test("^ya29\\.") then + . + else + empty + end; + if type == "object" then + (.token | token_string) + // token_string + // empty + else + token_string + end + ' 2>/dev/null || true +} + +extract_antigravity_keyring_account() { + jq -r ' + if type == "object" then + .email // .account_email // (.token.email // .token.account_email // empty) // "Antigravity CLI" + else + "Antigravity CLI" + end + ' 2>/dev/null || printf 'Antigravity CLI' +} + +fetch_antigravity_native() { + local state_db="${ANTIGRAVITY_STATE_DB:-${XDG_CONFIG_HOME:-$HOME/.config}/Antigravity/User/globalStorage/state.vscdb}" + local project_file="${ANTIGRAVITY_PROJECT_FILE:-$HOME/.gemini/antigravity-cli/cache/default_project_id.txt}" + # Antigravity CLI 1.0.x uses the non-sandbox daily endpoint for model/quota + # discovery. Keep an override because this internal surface is not stable. + local endpoint="${ANTIGRAVITY_API_BASE_URL:-https://daily-cloudcode-pa.googleapis.com}" + local response_file="${ANTIGRAVITY_RESPONSE_FILE:-}" + local account="Antigravity" + local login_method="local OAuth session" + local tmp_body http_status auth_json token project request_body message keyring_payload service + + tmp_body="$(mktemp)" + + # A response fixture keeps parsing tests deterministic and avoids requiring + # a live Google session in CI. It is never selected during normal runtime. + if [ -n "$response_file" ]; then + if [ ! -r "$response_file" ]; then + rm -f "$tmp_body" + json_error antigravity antigravity-internal 2 runtime "Antigravity response fixture is not readable." + return 0 + fi + cp "$response_file" "$tmp_body" + http_status="${ANTIGRAVITY_HTTP_STATUS:-200}" + account="${ANTIGRAVITY_ACCOUNT_EMAIL:-Antigravity}" + else + token="" + + # Antigravity CLI keeps its current OAuth profile in the desktop keyring. + # Lookup is deliberately limited to known service names; the secret is + # captured in memory and never emitted. Standard oauth2.Token JSON and a + # plain access-token value are both accepted for forward compatibility. + if command -v secret-tool >/dev/null 2>&1; then + keyring_payload="$(timeout 3 secret-tool lookup service gemini username antigravity 2>/dev/null || true)" + if [ -n "$keyring_payload" ]; then + token="$(printf '%s' "$keyring_payload" | extract_antigravity_keyring_token)" + account="$(printf '%s' "$keyring_payload" | extract_antigravity_keyring_account)" + if [ -z "$token" ] && [[ "$keyring_payload" == ya29.* ]]; then + token="$keyring_payload" + account="Antigravity CLI" + fi + [ -z "$token" ] || login_method="CLI keyring OAuth" + fi + + for service in "${ANTIGRAVITY_KEYRING_SERVICE:-Antigravity CLI}" "antigravity-cli" "Antigravity"; do + [ -z "$token" ] || break + keyring_payload="$(timeout 3 secret-tool lookup service "$service" 2>/dev/null || true)" + [ -n "$keyring_payload" ] || continue + token="$(printf '%s' "$keyring_payload" | extract_antigravity_keyring_token)" + account="$(printf '%s' "$keyring_payload" | extract_antigravity_keyring_account)" + if [ -z "$token" ] && [[ "$keyring_payload" == ya29.* ]]; then + token="$keyring_payload" + account="Antigravity CLI" + fi + if [ -n "$token" ]; then + login_method="CLI keyring OAuth" + break + fi + done + fi + + # Older IDE builds also leave an access token in state.vscdb. Keep it as a + # fallback, but prefer the keyring because the database copy can be stale. + if [ -z "$token" ] && command -v sqlite3 >/dev/null 2>&1 && [ -r "$state_db" ]; then + auth_json="$(sqlite3 "$state_db" "select value from ItemTable where key='antigravityAuthStatus' limit 1;" 2>/dev/null || true)" + token="$(printf '%s' "$auth_json" | jq -r '.apiKey // empty' 2>/dev/null || true)" + account="$(printf '%s' "$auth_json" | jq -r '.email // "Antigravity IDE"' 2>/dev/null || printf 'Antigravity IDE')" + login_method="IDE state OAuth" + fi + + if [ -z "$token" ]; then + rm -f "$tmp_body" + json_error antigravity antigravity-local 2 provider "No usable Antigravity session was found in the CLI keyring or IDE state. Run agy or open Antigravity and sign in." + return 0 + fi + + project="" + [ ! -r "$project_file" ] || project="$(sed -n '1p' "$project_file")" + request_body="$(jq -cn --arg project "$project" 'if $project == "" then {} else {project:$project} end')" + + # Feed the bearer header through a pipe-backed config descriptor and the + # request body through stdin. Neither token nor project appears in the + # process argument list, stdout, stderr, or a temporary file. + http_status="$( + curl -sS --max-time 10 \ + --config <(printf 'header = "Authorization: Bearer %s"\n' "$token") \ + -o "$tmp_body" -w '%{http_code}' \ + -X POST \ + -H "Content-Type: application/json" \ + -H "User-Agent: antigravity/linux/amd64" \ + --data-binary @- \ + "$endpoint/v1internal:fetchAvailableModels" 2>/dev/null \ + <<< "$request_body" \ + || true + )" + fi + + if [ "$http_status" != "200" ]; then + message="$(jq -r '.error.message // empty' "$tmp_body" 2>/dev/null || true)" + rm -f "$tmp_body" + case "$http_status" in + 401|403) + json_error antigravity antigravity-internal "$http_status" provider "Antigravity session expired. Open Antigravity and sign in again." + ;; + 429) + [ -n "$message" ] || message="Antigravity quota endpoint is rate-limited. Retry after its reset time." + json_error antigravity antigravity-internal 429 provider "$message" + ;; + *) + [ -n "$message" ] || message="Antigravity quota request returned HTTP ${http_status:-0}." + json_error antigravity antigravity-internal "${http_status:-1}" provider "$message" + ;; + esac + return 0 + fi + + if ! jq -e '.models | type == "object"' "$tmp_body" >/dev/null 2>&1; then + rm -f "$tmp_body" + json_error antigravity antigravity-internal 1 provider "Antigravity quota response schema changed: models object is missing." + return 0 + fi + + jq -c --arg account "$account" --arg login_method "$login_method" ' + def clamp: + if type != "number" then null + elif . < 0 then 0 + elif . > 1 then 1 + else . end; + def family($key; $entry): + (($key + " " + ($entry.displayName // "") + " " + ($entry.modelName // "")) | ascii_downcase) as $name + | if ($name | contains("claude")) then "Claude" + elif (($name | test("gemini[- ]?3")) and ($name | contains("flash"))) then "Gemini 3 Flash" + elif ($name | test("gemini[- ]?3")) then "Gemini 3 Pro" + else null end; + def window($group): { + usedPercent: (((1 - $group.remaining) * 10000 | round) / 100), + windowMinutes: null, + resetsAt: $group.resetsAt, + resetDescription: $group.name, + displayValue: (((1 - $group.remaining) * 100 | round | tostring) + "% used") + }; + [ + .models | to_entries[] + | . as $model + | (family(.key; .value)) as $family + | (.value.quotaInfo.remainingFraction | clamp) as $remaining + | select($family != null and $remaining != null) + | {name:$family, remaining:$remaining, resetsAt:(.value.quotaInfo.resetTime // null)} + ] + | group_by(.name) + | map({ + name: .[0].name, + remaining: (map(.remaining) | min), + resetsAt: (map(.resetsAt | select(. != null)) | sort | first // null) + }) + | sort_by(.remaining) + | map(window(.)) as $windows + | if ($windows | length) == 0 then + error("no supported quota entries") + else { + provider:"antigravity", + source:"antigravity-internal", + usage:{ + identity:{providerID:"antigravity",accountEmail:$account,loginMethod:$login_method}, + accountEmail:$account, + loginMethod:$login_method, + primary:$windows[0], + secondary:($windows[1] // null), + tertiary:($windows[2] // null), + updatedAt:(now|todateiso8601) + }, + credits:{remaining:"per-model quota"} + } end + ' "$tmp_body" 2>/dev/null || json_error antigravity antigravity-internal 1 provider "Antigravity quota response contains no supported model quota entries." + rm -f "$tmp_body" +} + fetch_openrouter_native() { local key="${OPENROUTER_API_KEY:-}" if [ -z "$key" ]; then @@ -1303,6 +1509,9 @@ fetch_provider() { gemini) fetch_gemini_native ;; + antigravity) + fetch_antigravity_native + ;; 9router) fetch_9router_native 9router 9router ;; diff --git a/tests/fixtures/antigravity-schema-changed.json b/tests/fixtures/antigravity-schema-changed.json new file mode 100644 index 0000000..1206964 --- /dev/null +++ b/tests/fixtures/antigravity-schema-changed.json @@ -0,0 +1,3 @@ +{ + "availableModels": [] +} diff --git a/tests/fixtures/antigravity-valid.json b/tests/fixtures/antigravity-valid.json new file mode 100644 index 0000000..8d426ee --- /dev/null +++ b/tests/fixtures/antigravity-valid.json @@ -0,0 +1,28 @@ +{ + "models": { + "claude-sonnet-4-6": { + "displayName": "Claude Sonnet 4.6", + "quotaInfo": { + "remainingFraction": 0.25, + "resetTime": "2026-06-27T03:00:00Z" + } + }, + "gemini-3-pro-high": { + "displayName": "Gemini 3 Pro High", + "quotaInfo": { + "remainingFraction": 0.8, + "resetTime": "2026-06-27T04:00:00Z" + } + }, + "gemini-3-flash": { + "displayName": "Gemini 3 Flash", + "quotaInfo": { + "remainingFraction": 0.5, + "resetTime": "2026-06-27T05:00:00Z" + } + }, + "unrelated-model": { + "displayName": "Unrelated model" + } + } +} From 346d1f55f602d102c147596c85b35ccc9ecbf296 Mon Sep 17 00:00:00 2001 From: arqueon <66568719+arqueon@users.noreply.github.com> Date: Fri, 26 Jun 2026 20:50:24 -0600 Subject: [PATCH 2/5] Expose Antigravity in the widget --- AiOverviewControlSettings.qml | 1 + AiOverviewControlWidget.qml | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/AiOverviewControlSettings.qml b/AiOverviewControlSettings.qml index a3e11dd..256c3b2 100644 --- a/AiOverviewControlSettings.qml +++ b/AiOverviewControlSettings.qml @@ -60,6 +60,7 @@ PluginSettings { { id:"codex", name:"Codex", icon:"terminal", mode:"telemetry", requirement:"codex CLI", envVar:"", note:"Official app-server rate limits" }, { id:"claude", name:"Claude", icon:"psychology", mode:"telemetry", requirement:"claude CLI or ~/.claude", envVar:"", note:"Local analytics and authenticated usage" }, { id:"copilot", name:"Copilot", icon:"code", mode:"telemetry", requirement:"gh CLI or GitHub token", envVar:"COPILOT_GITHUB_TOKEN", note:"Authenticated Copilot quota from the GitHub session" }, + { id:"antigravity", name:"Antigravity", icon:"rocket_launch", mode:"telemetry", requirement:"Antigravity CLI keyring or IDE session", envVar:"", note:"Per-model quota and reset times from the internal read-only endpoint" }, { id:"gemini", name:"Gemini", icon:"star", mode:"telemetry", requirement:"gemini CLI or API key", envVar:"GEMINI_API_KEY", note:"Authentication status; quota remains in AI Studio" }, { id:"9router", name:"9Router", icon:"share", mode:"telemetry", requirement:"local 9Router database", envVar:"", note:"Local requests, tokens and cost" }, { id:"openrouter", name:"OpenRouter", icon:"route", mode:"telemetry", requirement:"API key or 9Router data", envVar:"OPENROUTER_API_KEY", note:"Official key usage and limits" }, diff --git a/AiOverviewControlWidget.qml b/AiOverviewControlWidget.qml index 6019615..9fcde5f 100644 --- a/AiOverviewControlWidget.qml +++ b/AiOverviewControlWidget.qml @@ -134,6 +134,7 @@ PluginComponent { "codex", "claude", "copilot", + "antigravity", "gemini", "9router", "openrouter", @@ -526,6 +527,7 @@ PluginComponent { codex: "Codex", claude: "Claude", copilot: "Copilot", + antigravity: "Antigravity", cursor: "Cursor", gemini: "Gemini", openrouter: "OpenRouter", @@ -722,6 +724,7 @@ PluginComponent { if (providerId === "codex") return "data_object"; if (providerId === "claude") return "psychology"; if (providerId === "copilot") return "hub"; + if (providerId === "antigravity") return "rocket_launch"; if (providerId === "gemini") return "auto_awesome"; if (providerId === "openrouter") return "route"; if (providerId === "9router") return "share"; @@ -758,6 +761,7 @@ PluginComponent { if (providerId === "claude") return Theme.warning; if (providerId === "codex") return Theme.success; if (providerId === "copilot") return Theme.primary; + if (providerId === "antigravity") return Theme.primary; if (providerId === "gemini") return Theme.secondary; if (providerId === "openrouter") return Theme.primary; if (providerId === "9router") return Theme.secondary; @@ -816,6 +820,10 @@ PluginComponent { const windowData = primaryUsageWindow(provider); if (windowData && windowData.displayValue && String(windowData.displayValue).length > 0) { const label = windowData.resetDescription || t("status.usage", "usage"); + const reset = provider.provider === "antigravity" ? formatTimeUntil(windowData.resetsAt) : ""; + if (reset && reset !== "—") { + return `${source} · ${label} · ${windowData.displayValue} · ${t("status.reset", "reset")} ${reset}`; + } return `${source} · ${label} · ${windowData.displayValue}`; } const reset = providerReset(provider); @@ -973,6 +981,7 @@ PluginComponent { claude: "https://claude.ai/settings/usage", codex: "https://chatgpt.com/codex/settings/usage", copilot: "https://github.com/settings/copilot/features", + antigravity: "", gemini: "https://aistudio.google.com/usage", openrouter: "https://openrouter.ai/activity", deepseek: "https://platform.deepseek.com/usage", From 84e94166315d11dece326057cd4923a1f46bb63a Mon Sep 17 00:00:00 2001 From: arqueon <66568719+arqueon@users.noreply.github.com> Date: Fri, 26 Jun 2026 20:50:29 -0600 Subject: [PATCH 3/5] Document Antigravity quota support --- README.md | 9 +++++---- docs/README.pt-BR.md | 5 +++-- docs/architecture.md | 5 +++++ docs/configuration.md | 2 ++ docs/providers.md | 13 ++++++++++++- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 535ccfd..825bf2d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ billing, authentication, and local usage telemetry — right in your DankBar. [![CI](https://github.com/bernardopg/AiOverviewControl/actions/workflows/ci.yml/badge.svg)](https://github.com/bernardopg/AiOverviewControl/actions/workflows/ci.yml) [![Release](https://img.shields.io/github/v/release/bernardopg/AiOverviewControl)](https://github.com/bernardopg/AiOverviewControl/releases/latest) [![License](https://img.shields.io/github/license/bernardopg/AiOverviewControl)](./LICENSE) -[![Providers](https://img.shields.io/badge/providers-33-7C4DFF)](./docs/providers.md) +[![Providers](https://img.shields.io/badge/providers-34-7C4DFF)](./docs/providers.md) [![Languages](https://img.shields.io/badge/UI%20languages-5-00BFA5)](./docs/i18n-crowdin.md) [Install](#installation) · [Screenshots](#screenshots) · [Providers](./docs/providers.md) · @@ -48,7 +48,7 @@ it does not. No dashboard scraping. No fabricated percentages. Ever. | | | | --- | --- | -| 📊 **Unified dashboard** | 33 AI providers and developer tools in one place. | +| 📊 **Unified dashboard** | 34 AI providers and developer tools in one place. | | 🛰️ **Fleet overview** | Cross-provider rollup in the hero — quota-only average load, hottest provider, how many are near their cap, and the soonest reset. | | ⏱️ **Official Codex windows** | Rate-limit windows straight from `codex app-server`. | | 🤖 **Deep Claude analytics** | Quota plus local token, session, model, project, and cost analytics. | @@ -84,7 +84,7 @@ Provider cards use one of these honest coverage levels: | Coverage | Meaning | | --- | --- | -| **Quota** | Returns real rate-limit/spend windows and used percentage (Codex, Copilot, OpenRouter, Z.ai, GLM). | +| **Quota** | Returns real rate-limit/spend windows and used percentage (Codex, Copilot, Antigravity, OpenRouter, Z.ai, GLM). | | **Balance** | Returns remaining prepaid balance or credits in real currency (Kimi, DeepSeek, Together). | | **Analytics** | Reads consumption counters or provider-owned local data (Cloudflare GraphQL, 9Router, Claude, Ollama). | | **Authentication** | Verifies credentials via a read-only endpoint without stable quota data (Gemini, Mistral, NVIDIA, MiniMax, Qwen, xAI, and more). | @@ -97,6 +97,7 @@ Notable integrations: | Codex | Official `codex app-server` account and rate-limit methods. | | Claude Code | OAuth quota plus local `~/.claude/projects` analytics. | | GitHub Copilot | Authenticated GitHub/Copilot quota snapshot. | +| Antigravity | Per-model quota and reset times from the local IDE session and internal read-only endpoint. | | 9Router | Local SQLite or JSON usage data, including routed-model telemetry. | | OpenRouter | Key limits, spend, balance, and 30-day model activity. | | Kimi (Moonshot) | `GET /v1/users/me/balance` — available, voucher, and cash balance (USD/CNY). | @@ -114,7 +115,7 @@ The full matrix, credentials, and upstream references are documented in - DankMaterialShell running on Quickshell. - `bash`, `jq`, and `curl`. -- Provider-specific CLIs or credentials only for providers you enable. +- Provider-specific CLIs or credentials only for providers you enable; Antigravity additionally requires `sqlite3`. Recommended baseline for the default provider set: diff --git a/docs/README.pt-BR.md b/docs/README.pt-BR.md index f71cf05..7ca3e84 100644 --- a/docs/README.pt-BR.md +++ b/docs/README.pt-BR.md @@ -12,7 +12,7 @@ billing, autenticação e telemetria local de uso de IA — direto na sua DankBa [![CI](https://github.com/bernardopg/AiOverviewControl/actions/workflows/ci.yml/badge.svg)](https://github.com/bernardopg/AiOverviewControl/actions/workflows/ci.yml) [![Release](https://img.shields.io/github/v/release/bernardopg/AiOverviewControl)](https://github.com/bernardopg/AiOverviewControl/releases/latest) [![Licença](https://img.shields.io/github/license/bernardopg/AiOverviewControl)](../LICENSE) -[![Provedores](https://img.shields.io/badge/provedores-33-7C4DFF)](./providers.md) +[![Provedores](https://img.shields.io/badge/provedores-34-7C4DFF)](./providers.md) [![Idiomas](https://img.shields.io/badge/idiomas%20de%20UI-5-00BFA5)](./i18n-crowdin.md) [Instalação](#instalação) · [Screenshots](#screenshots) · [Provedores](./providers.md) · @@ -97,6 +97,7 @@ Integrações medidas notáveis: | Codex | Métodos oficiais de conta e rate-limit do `codex app-server`. | | Claude Code | Cota OAuth mais analytics local de `~/.claude/projects`. | | GitHub Copilot | Snapshot autenticado de cota GitHub/Copilot. | +| Antigravity | Cotas por modelo e horários de reset da sessão local do IDE via endpoint interno somente leitura. | | 9Router | Dados locais de uso em SQLite ou JSON, incluindo telemetria por modelo roteado. | | OpenRouter | Limites de chave, gasto, saldo e atividade de modelos em 30 dias. | | DeepSeek, Kimi, Together | APIs de saldo ou crédito do provedor. | @@ -112,7 +113,7 @@ A matriz completa, credenciais e referências upstream estão documentadas em - DankMaterialShell rodando sobre Quickshell. - `bash`, `jq` e `curl`. -- CLIs ou credenciais específicas apenas para os provedores habilitados. +- CLIs ou credenciais específicas apenas para os provedores habilitados; Antigravity também requer `sqlite3`. Linha de base recomendada para o conjunto padrão de provedores: diff --git a/docs/architecture.md b/docs/architecture.md index dfe3de4..8a428a4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -11,6 +11,7 @@ providers/get-provider-health Prerequisite checks for settings providers/get-codex-usage Codex app-server protocol bridge providers/get-claude-usage Claude local analytics and quota bridge providers/get-copilot-usage Authenticated GitHub Copilot quota bridge +providers/get-antigravity-usage Local Antigravity session quota bridge providers/get-provider-wrapper Single-provider wrapper providers/get-*-usage Canonical provider entrypoints ``` @@ -62,6 +63,10 @@ Errors return: `get-codex-usage` starts `codex app-server`, sends `initialize`, `account/read`, and `account/rateLimits/read`, then maps the official response to the common schema. The bridge uses a bounded process lifetime and never reads browser state. +## Antigravity protocol + +`get-antigravity-usage` reads the current Antigravity CLI OAuth profile from the desktop keyring, falling back to the IDE SQLite state used by older builds, and calls the read-only internal `v1internal:fetchAvailableModels` endpoint. It groups model quotas into Claude, Gemini 3 Pro, and Gemini 3 Flash windows, ordered by highest consumption. The bearer token is supplied to curl over stdin and is never printed or placed in process arguments. This is an internal Google endpoint and may change without notice. + ## Settings keys | Key | Default | Purpose | diff --git a/docs/configuration.md b/docs/configuration.md index 115de9d..c4ff2c3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -29,6 +29,8 @@ codex,claude,copilot Add API providers only after their required environment variables are available to the DMS process. +Antigravity does not use an environment variable. It prefers the signed-in CLI profile from Linux Secret Service via `secret-tool`, then falls back to `${XDG_CONFIG_HOME:-~/.config}/Antigravity/User/globalStorage/state.vscdb` via `sqlite3` for older IDE builds. + ## Environment variables | Provider | Variables | diff --git a/docs/providers.md b/docs/providers.md index c8b1f78..aead00c 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -21,7 +21,7 @@ Every provider maps to exactly one coverage level. The level dictates what the w | Level | Meaning | Example providers | | --- | --- | --- | -| **Quota** | Real `usedPercent` + reset window from an official protocol/API. | `codex`, `copilot`, `openrouter`, `zai`, `glm` | +| **Quota** | Real `usedPercent` + reset window from a protocol/API. | `codex`, `copilot`, `antigravity`, `openrouter`, `zai`, `glm` | | **Balance** | Remaining prepaid balance / credits in real currency. | `kimi`, `deepseek`, `together` | | **Analytics** | Consumption counters (requests/tokens/neurons/cost) with no remaining-quota value. | `cloudflare` (GraphQL), `9router`, `claude` (local) | | **Auth** | Read-only key validation only — no usage numbers. | `gemini`, `mistral`, `nvidia`, `qwen`, `byteplus`, `groq`, `cohere`, `replicate`, `fireworks`, `minimax`, `xai`, `kilo` | @@ -83,6 +83,17 @@ The matrix below summarises the **authentication/billing surface** for every sup +antigravity +Quota (internal API) +✅ local Antigravity OAuth session +⚠️ per-model remaining fraction + reset time +✅ Antigravity plan +— +~/.config/Antigravity +Antigravity IDE +undocumented v1internal:fetchAvailableModels + + gemini Auth ✅ GET /v1beta/models From 79f1f1ac1a04724e5419731c17d5084c303684c3 Mon Sep 17 00:00:00 2001 From: Bernardo Pinto Gomes Date: Sun, 28 Jun 2026 19:39:45 -0300 Subject: [PATCH 4/5] fix(antigravity): send bearer token, update model families, use stable host - Authorization header was not interpolating $token (printf had no %s), so the live endpoint always failed auth while fixture tests passed. Now interpolates correctly via the pipe-backed curl config. - Update model family buckets to current catalog: Claude Opus 4.6, Claude Sonnet 4.6, Gemini 3.5 Flash, Gemini 3.1 Pro, GPT-OSS 120B. - Switch default endpoint to cloudcode-pa.googleapis.com (production host used by gemini-cli) instead of the daily preview host. - Add X-Goog-Api-Client header to match official clients. - Update fixture and CI assertions to the new model families. Validated end-to-end against the real endpoint with a live IDE session. --- .github/workflows/ci.yml | 8 ++++---- providers/get-provider-usage | 16 ++++++++++------ tests/fixtures/antigravity-valid.json | 19 +++++++++++++------ 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba91fd6..ae8cc73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -271,12 +271,12 @@ jobs: echo "$OUT" | jq -e ' length == 1 and .[0].provider == "antigravity" - and .[0].usage.primary.resetDescription == "Claude" + and .[0].usage.primary.resetDescription == "Claude Opus 4.6" and .[0].usage.primary.usedPercent == 75 - and .[0].usage.secondary.resetDescription == "Gemini 3 Flash" + and .[0].usage.secondary.resetDescription == "Gemini 3.5 Flash" and .[0].usage.secondary.usedPercent == 50 - and .[0].usage.tertiary.resetDescription == "Gemini 3 Pro" - and .[0].usage.tertiary.usedPercent == 20 + and .[0].usage.tertiary.resetDescription == "GPT-OSS 120B" + and .[0].usage.tertiary.usedPercent == 40 ' >/dev/null AUTH_ERROR="$(ANTIGRAVITY_RESPONSE_FILE=tests/fixtures/antigravity-valid.json \ diff --git a/providers/get-provider-usage b/providers/get-provider-usage index 8fe7927..d84fb9c 100755 --- a/providers/get-provider-usage +++ b/providers/get-provider-usage @@ -238,9 +238,9 @@ extract_antigravity_keyring_account() { fetch_antigravity_native() { local state_db="${ANTIGRAVITY_STATE_DB:-${XDG_CONFIG_HOME:-$HOME/.config}/Antigravity/User/globalStorage/state.vscdb}" local project_file="${ANTIGRAVITY_PROJECT_FILE:-$HOME/.gemini/antigravity-cli/cache/default_project_id.txt}" - # Antigravity CLI 1.0.x uses the non-sandbox daily endpoint for model/quota - # discovery. Keep an override because this internal surface is not stable. - local endpoint="${ANTIGRAVITY_API_BASE_URL:-https://daily-cloudcode-pa.googleapis.com}" + # The production host used by both Antigravity IDE and the official gemini-cli. + # Keep an override for testing or future endpoint migrations. + local endpoint="${ANTIGRAVITY_API_BASE_URL:-https://cloudcode-pa.googleapis.com}" local response_file="${ANTIGRAVITY_RESPONSE_FILE:-}" local account="Antigravity" local login_method="local OAuth session" @@ -324,6 +324,7 @@ fetch_antigravity_native() { -X POST \ -H "Content-Type: application/json" \ -H "User-Agent: antigravity/linux/amd64" \ + -H "X-Goog-Api-Client: google-cloud-sdk vscode_cloudshelleditor/0.1" \ --data-binary @- \ "$endpoint/v1internal:fetchAvailableModels" 2>/dev/null \ <<< "$request_body" \ @@ -364,9 +365,12 @@ fetch_antigravity_native() { else . end; def family($key; $entry): (($key + " " + ($entry.displayName // "") + " " + ($entry.modelName // "")) | ascii_downcase) as $name - | if ($name | contains("claude")) then "Claude" - elif (($name | test("gemini[- ]?3")) and ($name | contains("flash"))) then "Gemini 3 Flash" - elif ($name | test("gemini[- ]?3")) then "Gemini 3 Pro" + | if ($name | contains("opus")) then "Claude Opus 4.6" + elif ($name | test("sonnet\\b")) then "Claude Sonnet 4.6" + elif (($name | test("gemini[- .]?3[- .]?5")) and ($name | contains("flash"))) then "Gemini 3.5 Flash" + elif (($name | test("gemini[- .]?3[- .]?1")) and ($name | contains("pro"))) then "Gemini 3.1 Pro" + elif (($name | test("gemini[- .]?3")) and ($name | contains("flash"))) then "Gemini 3 Flash" + elif ($name | test("gpt[- ]?oss")) then "GPT-OSS 120B" else null end; def window($group): { usedPercent: (((1 - $group.remaining) * 10000 | round) / 100), diff --git a/tests/fixtures/antigravity-valid.json b/tests/fixtures/antigravity-valid.json index 8d426ee..6c458f9 100644 --- a/tests/fixtures/antigravity-valid.json +++ b/tests/fixtures/antigravity-valid.json @@ -1,26 +1,33 @@ { "models": { - "claude-sonnet-4-6": { - "displayName": "Claude Sonnet 4.6", + "claude-opus-4-6-thinking": { + "displayName": "Claude Opus 4.6 (Thinking)", "quotaInfo": { "remainingFraction": 0.25, "resetTime": "2026-06-27T03:00:00Z" } }, - "gemini-3-pro-high": { - "displayName": "Gemini 3 Pro High", + "gemini-3.1-pro-high": { + "displayName": "Gemini 3.1 Pro (High)", "quotaInfo": { "remainingFraction": 0.8, "resetTime": "2026-06-27T04:00:00Z" } }, - "gemini-3-flash": { - "displayName": "Gemini 3 Flash", + "gemini-3.5-flash-low": { + "displayName": "Gemini 3.5 Flash (Medium)", "quotaInfo": { "remainingFraction": 0.5, "resetTime": "2026-06-27T05:00:00Z" } }, + "gpt-oss-120b-medium": { + "displayName": "GPT-OSS 120B (Medium)", + "quotaInfo": { + "remainingFraction": 0.6, + "resetTime": "2026-06-27T06:00:00Z" + } + }, "unrelated-model": { "displayName": "Unrelated model" } From 62b023ce03b91e8fef2e69309177c858ed3a47aa Mon Sep 17 00:00:00 2001 From: Bernardo Pinto Gomes Date: Sun, 28 Jun 2026 19:48:37 -0300 Subject: [PATCH 5/5] docs(antigravity): align docs with corrected endpoint/models; bump 1.5.0 - architecture.md: document stable cloudcode-pa host and current model families (Opus/Sonnet 4.6, 3.5 Flash, 3.1 Pro, GPT-OSS) instead of stale 'Claude / Gemini 3 Pro / Flash' buckets. - providers.md: endpoint is the documented Cloud Code Assist surface, not 'undocumented'; coverage is 'Quota (Cloud Code Assist)'. - README.md / README.pt-BR.md: integration line now references the Cloud Code Assist endpoint used by Antigravity IDE and gemini-cli. - CHANGELOG: add 1.5.0 entry covering the Antigravity provider, the auth-header fix, dual credential discovery, and the sqlite3 note. - plugin.json: 1.4.12 -> 1.5.0 (new provider, minor bump). --- CHANGELOG.md | 14 ++++++++++++++ README.md | 2 +- docs/README.pt-BR.md | 2 +- docs/architecture.md | 2 +- docs/providers.md | 4 ++-- plugin.json | 2 +- 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 260ae57..5c59bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ ## [Unreleased] +## 1.5.0 - 2026-06-28 + +### Providers +- **Antigravity provider added (Quota coverage).** Surfaces real per-model quota and reset windows from the Google Cloud Code Assist endpoint (`v1internal:fetchAvailableModels` on `cloudcode-pa.googleapis.com`) — the same protocol used by Antigravity IDE and the official `gemini-cli`. Quotas are grouped into current model families: Claude Opus 4.6, Claude Sonnet 4.6, Gemini 3.5 Flash, Gemini 3.1 Pro, and GPT-OSS 120B. +- **Dual credential discovery.** Authentication prefers the signed-in `agy` CLI profile via Linux Secret Service (`secret-tool`), then falls back to the IDE's `globalStorage/state.vscdb` (`antigravityAuthStatus`) for desktop builds. The OAuth bearer token is supplied to `curl` via stdin and never appears in process arguments, stdout, or logs. +- **Health probe.** `get-provider-health` now recognizes `antigravity` and reports ready/missing based on the keyring or SQLite state availability. + +### Fixed +- **Antigravity Authorization header.** The bearer token was not interpolated into the `curl` config line (missing `%s`), so the live path always failed authentication while fixture tests passed. Token interpolation now works and the live path returns real quota data. + +### Notes +- Provider count: 33 → 34. +- Antigravity additionally requires `sqlite3` on the system for the IDE-state fallback path. + ## 1.4.12 - 2026-06-26 ### Providers diff --git a/README.md b/README.md index 825bf2d..82cb655 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ Notable integrations: | Codex | Official `codex app-server` account and rate-limit methods. | | Claude Code | OAuth quota plus local `~/.claude/projects` analytics. | | GitHub Copilot | Authenticated GitHub/Copilot quota snapshot. | -| Antigravity | Per-model quota and reset times from the local IDE session and internal read-only endpoint. | +| Antigravity | Per-model quota and reset times from the Cloud Code Assist endpoint used by Antigravity IDE and gemini-cli. | | 9Router | Local SQLite or JSON usage data, including routed-model telemetry. | | OpenRouter | Key limits, spend, balance, and 30-day model activity. | | Kimi (Moonshot) | `GET /v1/users/me/balance` — available, voucher, and cash balance (USD/CNY). | diff --git a/docs/README.pt-BR.md b/docs/README.pt-BR.md index 7ca3e84..e713eac 100644 --- a/docs/README.pt-BR.md +++ b/docs/README.pt-BR.md @@ -97,7 +97,7 @@ Integrações medidas notáveis: | Codex | Métodos oficiais de conta e rate-limit do `codex app-server`. | | Claude Code | Cota OAuth mais analytics local de `~/.claude/projects`. | | GitHub Copilot | Snapshot autenticado de cota GitHub/Copilot. | -| Antigravity | Cotas por modelo e horários de reset da sessão local do IDE via endpoint interno somente leitura. | +| Antigravity | Cotas por modelo e horários de reset via endpoint Cloud Code Assist, usado pela IDE Antigravity e pelo gemini-cli. | | 9Router | Dados locais de uso em SQLite ou JSON, incluindo telemetria por modelo roteado. | | OpenRouter | Limites de chave, gasto, saldo e atividade de modelos em 30 dias. | | DeepSeek, Kimi, Together | APIs de saldo ou crédito do provedor. | diff --git a/docs/architecture.md b/docs/architecture.md index 8a428a4..b75500c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -65,7 +65,7 @@ Errors return: ## Antigravity protocol -`get-antigravity-usage` reads the current Antigravity CLI OAuth profile from the desktop keyring, falling back to the IDE SQLite state used by older builds, and calls the read-only internal `v1internal:fetchAvailableModels` endpoint. It groups model quotas into Claude, Gemini 3 Pro, and Gemini 3 Flash windows, ordered by highest consumption. The bearer token is supplied to curl over stdin and is never printed or placed in process arguments. This is an internal Google endpoint and may change without notice. +`get-antigravity-usage` reads the current Antigravity CLI OAuth profile from the desktop keyring, falling back to the IDE SQLite state used by older builds, and calls the read-only `v1internal:fetchAvailableModels` endpoint on `cloudcode-pa.googleapis.com` — the same host used by Antigravity IDE and the official gemini-cli. It groups model quotas into current families (Claude Opus 4.6, Claude Sonnet 4.6, Gemini 3.5 Flash, Gemini 3.1 Pro, GPT-OSS 120B), ordered by highest consumption. The bearer token is supplied to curl over stdin and is never printed or placed in process arguments. ## Settings keys diff --git a/docs/providers.md b/docs/providers.md index aead00c..751af66 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -84,14 +84,14 @@ The matrix below summarises the **authentication/billing surface** for every sup antigravity -Quota (internal API) +Quota (Cloud Code Assist) ✅ local Antigravity OAuth session ⚠️ per-model remaining fraction + reset time ✅ Antigravity plan — ~/.config/Antigravity Antigravity IDE -undocumented v1internal:fetchAvailableModels +v1internal:fetchAvailableModels on cloudcode-pa.googleapis.com gemini diff --git a/plugin.json b/plugin.json index 1597d1d..28988a9 100644 --- a/plugin.json +++ b/plugin.json @@ -2,7 +2,7 @@ "id": "aiOverviewControl", "name": "AiOverviewControl", "description": "Plugin-managed AI quota, billing, authentication, and local usage telemetry for DankMaterialShell", - "version": "1.4.12", + "version": "1.5.0", "author": "bernardopg", "icon": "monitoring", "type": "widget",