npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pi-antigravity-rotator

v1.10.1

Published

Multi-account rotation proxy for Google Antigravity with per-model routing, real-time quota tracking, and infringement detection

Readme

Pi Antigravity Rotator

Multi-account rotation proxy for Google Antigravity. Distributes API usage across multiple Google accounts with per-model routing, real-time quota tracking, automatic token management, and infringement detection.

Features

  • Per-model routing -- Each model (Gemini Pro, Flash, Claude) routes to its own active account independently. Multiple agents using different models won't interfere with each other.
  • Real-time quota monitoring -- Polls Google's quota API every 5 minutes to track remaining usage per model per account
  • Per-model timer tracking -- Timer classification (fresh/7d/5h) is evaluated per model using each model's actual resetTime from the quota API, not a per-account estimate
  • Smart rotation -- Rotates only the specific model whose quota dropped, leaving other models on their current accounts
  • Infringement detection -- On 403 with infringement/abuse/suspension keywords, the account is immediately flagged and excluded from routing
  • Safer 429 handling -- On provider 429, stops the current request and avoids cascade-burning sibling accounts
  • Concurrency guardrails -- Limits each account to one in-flight request by default to avoid bursty pressure
  • Operator fresh-window controls -- You can block new fresh window starts globally, then selectively allow specific accounts to override that policy
  • Protective pause -- Pauses all routing for several hours after serious ToS/abuse-style flags so the rest of the pool is not burned
  • Token auto-refresh -- Tokens are refreshed automatically before expiry; no manual management
  • Endpoint cascade -- Tries daily, autopush, and prod API endpoints for resilience
  • Pro Family Advisor -- Scans your account pool and alerts you if there are major imbalances (like some accounts never getting used because of routing bias), giving you actionable steps to optimize token distribution
  • Advanced Telemetry & Statistics -- Track exactly how much USD you save compared to a paid API plan, predict quota depletion with the Forecast grid, view Latency tracking (p50/p95), and explore 60-day historical usage heatmaps
  • Web dashboard -- Real-time view of model routing table, per-account quota bars with per-model timers, and flagged account alerts
  • Auto-update notifications -- The dashboard checks npm for new releases every 30 minutes and shows a banner with one-click update when a newer version is available
  • Broadcast notifications -- Operator-controlled announcements and alerts delivered directly to the dashboard
  • State persistence -- Survives restarts; routing assignments, per-model request counters, cooldowns, and flags are saved to disk

Quick Start

Option A: Install from npm

npm install -g pi-antigravity-rotator

# Add your first account
pi-antigravity-rotator login

# Start the proxy
pi-antigravity-rotator start

Option B: Clone from source

git clone https://github.com/tuxevil/pi-antigravity-rotator.git
cd pi-antigravity-rotator
npm install

# Add your first account
npm run login

# Start the proxy
npm start

Adding Accounts

Run npm run login once per Google account:

  1. A Google OAuth URL is printed to the terminal -- open it in your browser
  2. Complete the sign-in and grant permissions
  3. The browser redirects to a localhost URL that won't load -- this is expected
  4. Copy the full URL from the browser's address bar and paste it into the terminal
  5. If project discovery fails, open that same account in Antigravity IDE, send one message, then rerun login

The tool automatically:

  • Creates or updates accounts.json with the account credentials
  • Configures ~/.pi/agent/models.json to point google-antigravity at the proxy
  • Configures ~/.pi/agent/auth.json with proxy-managed credentials

Re-running with the same email updates the existing entry.

Activation rule

Some accounts do not expose a discoverable projectId until they are used once in the Antigravity IDE. If login fails at project discovery:

  1. Open that exact Google account in Antigravity IDE.
  2. Send one message.
  3. Rerun npm run login.

Dashboard

After starting the proxy, open http://localhost:51200/dashboard or http://<your-server-ip>:51200/dashboard from any machine on the same network (the proxy binds to 0.0.0.0).

The dashboard shows:

  • Top Status & Controls -- Real-time routing state, uptime, requests, and PII masking toggle.
  • Pro Family Advisor & Dual-Window Tracking -- Advanced logic that tracks and compares both Pro and Free quota windows simultaneously. The Advisor analyzes cumulative quota to suggest mathematical upgrades/downgrades.
  • Token Usage & Savings -- Interactive chart (1h, 2h, 4h, 8h, 12h, 1d, 7d, 1m) showing token consumption by model, with estimated USD savings and CSV/JSON export options.
  • Activity Heatmap -- 60-day responsive GitHub-style contribution grid showing request intensity hour by hour.
  • Latency (p50/p95) -- Real-time median and 95th percentile tracking for Time-to-First-Byte (TTFB) and Total Duration per model.
  • Quota Forecast -- Predictive modeling showing when each model's quota will run out based on the current requests/hour burn rate.
  • Searchable Request Log -- Live feed of the last 200 requests with exact timestamps, models, masked accounts, status codes, and latency.
  • Account Cards -- Sorted by total quota. Shows status (active, ready, cooldown, flagged, disabled), dual-window trackers, quota bars with timers, and precise error messages.
  • Operator Panels -- "Attention Needed" summaries for quarantined accounts and a real-time event feed of rotator actions.

Dashboard

How It Works

Proxying

Pi Agent 1 (Gemini Pro)  --->  localhost:51200  --->  Account A
Pi Agent 2 (Claude)      --->  localhost:51200  --->  Account C
Pi Agent 3 (Flash)       --->  localhost:51200  --->  Account A
                               (this proxy)          (per-model routing)
  1. Pi sends a request to localhost:51200 with a model name in the body
  2. The proxy resolves the model to a quota key (e.g., gemini-3.1-pro)
  3. The best available account for that specific model is selected
  4. The Authorization header and project field are swapped with real credentials
  5. The request is forwarded (trying daily, autopush, then prod endpoints)
  6. The SSE response streams back to pi transparently

Per-Model Account Selection

Each model maintains its own active account. When the proxy needs to rotate a model, it picks the next account using a priority system:

| Priority | Badge | Condition | Rationale | |----------|-------|-----------|-----------| | 1 (first) | 5h | Short reset window is already active for this model | Drain short-window quota before it recharges | | 2 | 7d | Long reset window is already active for this model | Already ticking, so it is still worth using | | 3 (last) | fresh | No active reset window is known for this model yet | Save untouched quota for later if other timed pools exist |

Within the same priority tier, the account with the most remaining quota for that model wins. If multiple accounts tie on priority and quota, rotation advances circularly from the current account so equal candidates share traffic instead of always favoring the first configured match.

Timer meanings:

  • fresh -- no future resetTime is currently reported for that model on that account. In practice, this means no active reset window is visible in quota polling yet. The dashboard labels this as idle to avoid implying that it is automatically safe to start.
  • 5h -- resetTime is less than 6 hours away.
  • 7d -- resetTime is 6 hours or more away.

Rotation Triggers

Three mechanisms trigger rotation, scoped to the specific model:

  1. Quota-based (primary) -- Polls the Google quota API every 5 minutes. When a model's remaining quota drops by rotateOnQuotaDrop percentage points (default: 20%), that model rotates to the next account. Other models stay on their current accounts.

  2. Request-count (fallback) -- Before forwarding a request, the rotator checks how many requests the current account has already served for that specific model and rotates once it reaches requestsPerRotation (default: 5). Per-model counters are persisted so restarts do not reset the threshold. By default this fallback is only used when quota data for that model is still unknown; set useRequestCountRotationWhenQuotaUnknownOnly to false to keep request-count rotation active even when quota telemetry exists. If the threshold is reached but every replacement account is cooling down, flagged, disabled, busy, blocked by fresh-window policy, or out of quota for that model, the rotator stays on the current healthy account instead of returning 503.

  3. 429 containment (reactive) -- On provider rate limit, the account is marked exhausted with a parsed retry cooldown and the current request stops. Repeated unique-account 429s trip project/model and model-wide circuit breakers so retries cannot burn through the pool.

Fresh Windows

The quota polling API only exposes one visible quotaInfo block per model. If a model has no visible resetTime, the rotator classifies it as fresh internally and the dashboard shows it as idle.

Operationally, idle means:

  • no timer window is currently visible for that model in quota polling
  • starting that account may open a new quota window
  • because the provider does not expose all parallel buckets explicitly, the rotator cannot guarantee ahead of time whether that new visible window will behave like a short 5h opportunity or a longer 7d runway

For that reason, the rotator has two operator controls:

  • a global fresh-window toggle that blocks opening new idle windows by default
  • a per-account override that allows specific accounts to ignore the global block when you intentionally want them available

When fresh-window starts are blocked:

  • visible 5h timers still have highest priority
  • visible 7d timers are still used normally
  • idle accounts are held back unless you explicitly enable their per-account override

Account Protection

The proxy detects blocked/suspended accounts at three levels:

  1. Quota API check (initial poll + every poll) -- If the quota API returns 401 or 403, the account is immediately flagged.

  2. API 401 (on request) -- If the prod endpoint rejects the token with 401 UNAUTHENTICATED, the account is flagged.

  3. API 403 (on request) -- If the response body contains enforcement keywords such as infring, suspend, abus, terminat, violat, banned, policy, forbidden, or verif, the account is flagged.

Flagged accounts are immediately excluded from all model routing. If the reason looks serious enough (for example ToS, abuse, infringement, suspension, or ban language), the rotator also enables a global protective pause that stops all routing for protectivePauseMs (default: 6 hours). The dashboard shows a red FLAGGED badge with the error message and quarantine guidance. Flagged accounts are intentionally kept out of rotation until the provider explicitly restores access.

Cooldown Management

  • Cooldowns are capped at 30 minutes max
  • Stale cooldowns from previous sessions are capped on startup
  • When every non-flagged account is cooling down, the routing state becomes cooldown_wait
  • The dashboard shows why routing is waiting, how long until the next retry window, and which accounts are cooling down
  • Quota-based rotation only triggers if a healthy account is available; the proxy won't rotate away from a working account if there's no better alternative

Error Handling

  • 429 (rate limit) -- account is marked exhausted with cooldown; request stops and returns 429/Retry-After to force client backoff
  • 401 -- account is flagged and excluded from routing
  • 403 with enforcement keywords -- account is flagged and may trigger protective pause
  • 503 (no capacity) -- returned directly to the agent when all healthy accounts are cooling down, busy, flagged, or disabled
  • 5xx (other server errors) -- account error counter incremented, rotates to next

Dashboard Visibility

The dashboard is intended to replace day-to-day journalctl digging for normal operations. The top status panel shows:

  • The current routing state (healthy, cooldown_wait, busy, paused, stopped)
  • The exact stop or wait reason
  • The next retry window when cooldowns are active
  • Protective pause remaining time and the provider signal that triggered it
  • The global fresh-window policy and a button to block or allow new idle window starts
  • Pool counts for available, ready, active, cooldown, busy, flagged, disabled, and error accounts
  • An Attention Needed section summarizing flagged, cooling, disabled, and error accounts
  • A recent event feed with the latest rotator/proxy incidents that led to the current state

Configuration

Config files (accounts.json, state.json) are stored in ~/.pi-antigravity-rotator/ by default. Override with:

# Environment variables
export PI_ROTATOR_DIR=/path/to/config
export PI_ROTATOR_QUOTA_USER_AGENT="antigravity/1.107.0 darwin/arm64"
# Optional: require this token for dashboard/API access. If unset, legacy open access is preserved.
export PI_ROTATOR_ADMIN_TOKEN="change-me"
# Optional: max accepted proxy request body size in bytes. Default: 26214400 (25 MiB).
export PI_ROTATOR_MAX_BODY_BYTES=26214400
# Optional: log verbosity. One of debug, info, warn, error, silent. Default: info.
export PI_ROTATOR_LOG_LEVEL=info
# Optional override for Antigravity UA version used by quota fetches
export PI_AI_ANTIGRAVITY_VERSION=1.107.0

# Or CLI flag
pi-antigravity-rotator start --config-dir /path/to/config

accounts.json is created automatically by the login command. Login now fails if Google does not return a project ID. No shared fallback.

{
  "proxyPort": 51200,
  "requestsPerRotation": 5,
  "rotateOnQuotaDrop": 20,
  "quotaPollIntervalMs": 300000,
  "maxConcurrentRequestsPerAccount": 1,
  "maxConcurrentRequestsPerProjectModel": 1,
  "projectCircuitBreaker429Threshold": 3,
  "projectCircuitBreakerWindowMs": 600000,
  "projectCircuitBreakerCooldownMs": 3600000,
  "modelCircuitBreaker429Threshold": 3,
  "modelCircuitBreakerCooldownMs": 21600000,
  "dailyAccountSlowRequests": 250,
  "dailyAccountStopRequests": 350,
  "dailyProjectSlowRequests": 900,
  "dailyProjectStopRequests": 1200,
  "slowModeJitterMinMs": 8000,
  "slowModeJitterMaxMs": 25000,
  "protectivePauseMs": 21600000,
  "useRequestCountRotationWhenQuotaUnknownOnly": true,
  "accounts": [
    {
      "email": "[email protected]",
      "refreshToken": "1//...",
      "projectId": "project-abc123",
      "label": "user"
    }
  ]
}

Options

| Field | Default | Description | |-------|---------|-------------| | proxyPort | 51200 | Port the proxy listens on | | requestsPerRotation | 5 | Max per-model requests before attempting request-count rotation | | rotateOnQuotaDrop | 20 | Rotate when a model's quota drops this many %. Set to 0 to disable | | quotaPollIntervalMs | 300000 | Quota poll interval in ms (5 minutes) | | maxConcurrentRequestsPerAccount | 1 | Max simultaneous requests allowed per account | | maxConcurrentRequestsPerProjectModel | 1 | Max simultaneous requests allowed across accounts sharing the same projectId for the same quota model | | projectCircuitBreaker429Threshold | 3 | Unique accounts from the same projectId that must hit provider 429 before pausing that project/model | | projectCircuitBreakerWindowMs | 600000 | Rolling window for the project/model 429 circuit breaker | | projectCircuitBreakerCooldownMs | 3600000 | Minimum project/model pause after the circuit breaker trips | | modelCircuitBreaker429Threshold | 3 | Unique accounts across all projects that must hit provider 429 for the same quota model before pausing that model globally | | modelCircuitBreakerCooldownMs | 21600000 | Minimum model-wide pause after the global model circuit breaker trips | | dailyAccountSlowRequests | 250 | Daily upstream attempts per account before slow-mode jitter starts | | dailyAccountStopRequests | 350 | Daily upstream attempts per account before routing stops for that account until the next UTC day | | dailyProjectSlowRequests | 900 | Daily upstream attempts per projectId before slow-mode jitter starts | | dailyProjectStopRequests | 1200 | Daily upstream attempts per projectId before routing stops for that project until the next UTC day | | slowModeJitterMinMs | 8000 | Minimum slow-mode delay before upstream request | | slowModeJitterMaxMs | 25000 | Maximum slow-mode delay before upstream request | | protectivePauseMs | 21600000 | Global routing pause after a serious provider enforcement signal | | useRequestCountRotationWhenQuotaUnknownOnly | true | Use request-count rotation only until quota telemetry exists for the request's model. Set to false to keep rotating by request count even with known quotas |

Account Fields

| Field | Description | |-------|-------------| | email | Google account email (auto-filled by login) | | refreshToken | OAuth refresh token (auto-filled by login) | | projectId | Cloud project ID discovered from Google during login | | projectSource | Optional metadata: google when discovered from Google, manual if edited by hand | | label | Display name on the dashboard (auto-filled, defaults to email username) |

API Endpoints

| Method | Path | Description | |--------|------|-------------| | GET | /dashboard | Web dashboard | | GET | /login | Hosted account-link landing page | | GET | /api/status | JSON status: accounts, quotas, model routing, flags | | POST | /api/enable/<email> | Re-enable a disabled account after its underlying issue is fixed | | POST | /api/settings/fresh-window-starts/on | Allow opening new idle/fresh windows globally | | POST | /api/settings/fresh-window-starts/off | Block opening new idle/fresh windows globally | | POST | /api/account-fresh-window-starts/<email>/on | Allow one account to override the global fresh-window block | | POST | /api/account-fresh-window-starts/<email>/off | Return one account to the global fresh-window policy | | POST | /api/self-update | Trigger npm self-update to latest version (admin-only) | | POST | /v1internal:streamGenerateContent | Native Antigravity proxy endpoint (used by pi) | | GET | /v1/models | OpenAI-compatible model list | | POST | /v1/chat/completions | OpenAI-compatible non-streaming chat adapter | | POST | /v1/messages | Anthropic-compatible non-streaming messages adapter |

If PI_ROTATOR_ADMIN_TOKEN is set, dashboard/API requests must include either Authorization: Bearer <token>, X-Rotator-Admin-Token: <token>, or ?token=<token> for browser dashboard access. The native pi proxy endpoint and compatibility adapters remain unauthenticated so existing clients keep working. Put this service behind a trusted local boundary if exposing beyond localhost/LAN.

Compatibility Adapters

The compatibility adapters are additive. They do not change the native /v1internal:streamGenerateContent route used by pi.

OpenAI-compatible example:

curl http://localhost:51200/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gemini-3-flash",
    "messages": [{"role": "user", "content": "Say pong"}],
    "stream": false
  }'

Anthropic-compatible example:

curl http://localhost:51200/v1/messages \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "claude-sonnet-4-6",
    "system": "Be terse.",
    "messages": [{"role": "user", "content": "Say pong"}],
    "max_tokens": 128,
    "stream": false
  }'

Current adapter scope:

  • Text chat/messages.
  • Streaming mode is supported as compatibility SSE. The adapter buffers the upstream Antigravity stream, then emits one OpenAI/Anthropic-compatible final delta. Native token-by-token pass-through is not implemented yet.
  • Image input is supported when sent as base64 data URL (OpenAI image_url.url = data:image/...;base64,...) or Anthropic base64 source (type=image, source.type=base64).
  • Tool/function calling is explicitly rejected with 400 until a safe contract mapper is implemented.

Development Checks

npm run typecheck
npm test
npm run check

The test suite covers model resolution contracts and dashboard render/syntax smoke checks.

Running as a Service

# Using nohup
nohup npm start > rotator.log 2>&1 &

# Or with systemd (create /etc/systemd/system/pi-antigravity-rotator.service)
[Unit]
Description=Pi Antigravity Rotator
After=network.target

[Service]
Type=simple
WorkingDirectory=/path/to/pi-antigravity-rotator
ExecStart=/usr/bin/npm start
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Troubleshooting

Account shows flagged status Google detected potential abuse or enforcement. Review the error message on the dashboard and resolve the provider-side block first. Flagged accounts are quarantined and are not re-enabled through /api/enable/<email> until the underlying provider issue is cleared by replacing or restoring the account.

Account keeps getting disabled after 5 errors Check the error message. Common causes: revoked OAuth consent, expired refresh token (re-run npm run login), or Google account suspension.

Quota bars not showing Quota data appears after the first poll cycle (up to 5 minutes). Ensure accounts have valid tokens.

All accounts exhausted If the outage is temporary (cooldown, model breaker, or protective pause), the proxy returns 429 with Retry-After and retryAfterMs; clients must back off. The proxy returns terminal 503 only when there is no known retry time, for example all accounts are disabled/flagged.

Multiple agents on different models This is fully supported. Each model routes independently. Agent 1 using Gemini Pro and Agent 2 using Claude will each have their own active account and won't interfere with each other's rotation.

Telemetry

pi-antigravity-rotator collects anonymous usage telemetry to help understand how the tool is used and — most importantly — to improve the anti-flag algorithm that protects your accounts.

What is collected

Heartbeat (at boot, every 1h, at shutdown):

  • Random install ID (UUID — not tied to any account or person)
  • Rotator version, Node.js version, OS, architecture
  • Account count, models in use, total request count, uptime
  • Routing health state (healthy/paused/stopped)
  • Flagged/disabled/pro/free account counts
  • Per-model token usage (input/output tokens + request count per model)
  • Feature usage flags (dashboard opened, login used, etc. — booleans only)

Flag events (sent immediately when Google flags an account):

  • HTTP status that triggered the flag (401 or 403)
  • Which known patterns matched (e.g. infring, abus, suspend — from a fixed allowlist)
  • Model being requested, quota timer type, quota percentage
  • Account request velocity (requests/hour), concurrent requests, lifetime requests
  • Pool state: size, healthy count, whether protective pause triggered
  • Time since previous flag

The dashboard shows both raw Flag Events and deduped Unique Flag Incidents so repeated identical incidents don't inflate the signal.

Flag data is the most valuable signal. It lets us study what behavior patterns lead to flags and improve the rotation algorithm to avoid them — benefiting everyone.

What is NEVER collected

  • ❌ Email addresses
  • ❌ OAuth tokens or API keys
  • ❌ IP addresses
  • ❌ Google project IDs
  • ❌ Request/response bodies
  • ❌ Error message text (only which known keywords matched)

Endpoint

Telemetry posts to:

http://telemetry.dragont.ec:3800/v1/events

Opt out

Set the environment variable before starting:

export PI_ROTATOR_TELEMETRY=off

Or use any of: PI_ROTATOR_TELEMETRY=false, PI_ROTATOR_TELEMETRY=0.

Support Me

If this tool has helped you optimize your API usage and save costs, consider supporting its development!