pi-antigravity-rotator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sebastián Real (tuxevil)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # Pi Antigravity Rotator
2
+
3
+ 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.
4
+
5
+ ## Features
6
+
7
+ - **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.
8
+ - **Real-time quota monitoring** -- Polls Google's quota API every 5 minutes to track remaining usage per model per account
9
+ - **Per-model timer tracking** -- Timer priority (fresh/7d/5h) is evaluated per model using each model's actual `resetTime` from the quota API, not a per-account estimate
10
+ - **Smart rotation** -- Rotates only the specific model whose quota dropped, leaving other models on their current accounts
11
+ - **Infringement detection** -- On 403 with infringement/abuse/suspension keywords, the account is immediately flagged and excluded from routing
12
+ - **Automatic failover** -- On 429 rate limits, instantly switches the affected model to the next available account
13
+ - **Token auto-refresh** -- Tokens are refreshed automatically before expiry; no manual management
14
+ - **Endpoint cascade** -- Tries daily, autopush, and prod API endpoints for resilience
15
+ - **Web dashboard** -- Real-time view of model routing table, per-account quota bars with per-model timers, and flagged account alerts
16
+ - **State persistence** -- Survives restarts; routing assignments, cooldowns, and flags are saved to disk
17
+
18
+ ## Quick Start
19
+
20
+ ### Option A: Install from npm
21
+
22
+ ```bash
23
+ npm install -g pi-antigravity-rotator
24
+
25
+ # Add your first account
26
+ pi-antigravity-rotator login
27
+
28
+ # Start the proxy
29
+ pi-antigravity-rotator start
30
+ ```
31
+
32
+ ### Option B: Clone from source
33
+
34
+ ```bash
35
+ git clone https://github.com/tuxevil/pi-antigravity-rotator.git
36
+ cd pi-antigravity-rotator
37
+ npm install
38
+
39
+ # Add your first account
40
+ npm run login
41
+
42
+ # Start the proxy
43
+ npm start
44
+ ```
45
+
46
+ ## Adding Accounts
47
+
48
+ Run `npm run login` once per Google account:
49
+
50
+ 1. A Google OAuth URL is printed to the terminal -- open it in your browser
51
+ 2. Complete the sign-in and grant permissions
52
+ 3. The browser redirects to a `localhost` URL that won't load -- this is expected
53
+ 4. Copy the **full URL** from the browser's address bar and paste it into the terminal
54
+
55
+ The tool automatically:
56
+
57
+ - Creates or updates `accounts.json` with the account credentials
58
+ - Configures `~/.pi/agent/models.json` to point `google-antigravity` at the proxy
59
+ - Configures `~/.pi/agent/auth.json` with proxy-managed credentials
60
+
61
+ Re-running with the same email updates the existing entry.
62
+
63
+ ## Dashboard
64
+
65
+ 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`).
66
+
67
+ The dashboard shows:
68
+
69
+ - **Model Routing table** -- Which account each model (Gemini Pro, Flash, Claude) is currently routed to
70
+ - **Account cards** with:
71
+ - Status badge: `active`, `ready`, `cooldown`, `flagged`, `disabled`, or `error`
72
+ - Model badges: which models this account is currently serving
73
+ - Per-model quota bars with timer type (`fresh`/`7d`/`5h`) and reset countdown
74
+ - Request counts, last used time, token status
75
+ - Error messages for flagged/errored accounts
76
+ - Re-enable button for flagged or disabled accounts
77
+
78
+ ![Dashboard](dashboard.png)
79
+
80
+ ## How It Works
81
+
82
+ ### Proxying
83
+
84
+ ```
85
+ Pi Agent 1 (Gemini Pro) ---> localhost:51200 ---> Account A
86
+ Pi Agent 2 (Claude) ---> localhost:51200 ---> Account C
87
+ Pi Agent 3 (Flash) ---> localhost:51200 ---> Account A
88
+ (this proxy) (per-model routing)
89
+ ```
90
+
91
+ 1. Pi sends a request to `localhost:51200` with a model name in the body
92
+ 2. The proxy resolves the model to a quota key (e.g., `gemini-3.1-pro`)
93
+ 3. The best available account for that specific model is selected
94
+ 4. The `Authorization` header and `project` field are swapped with real credentials
95
+ 5. The request is forwarded (trying daily, autopush, then prod endpoints)
96
+ 6. The SSE response streams back to pi transparently
97
+
98
+ ### Per-Model Account Selection
99
+
100
+ Each model maintains its own active account. When the proxy needs to rotate a model, it picks the next account using a priority system:
101
+
102
+ | Priority | Badge | Condition | Rationale |
103
+ |----------|-------|-----------|-----------|
104
+ | 1 (first) | `fresh` | No active timer for this model | Start the 7-day clock ASAP so it resets sooner |
105
+ | 2 | `7d` | 7-day timer running for this model | Already ticking, keep using it |
106
+ | 3 (last) | `5h` | 5-hour timer running for this model | Short-lived; wasted if not fully consumed |
107
+
108
+ Within the same priority tier, the account with the most remaining quota for that model wins.
109
+
110
+ ### Rotation Triggers
111
+
112
+ Three mechanisms trigger rotation, scoped to the specific model:
113
+
114
+ 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.
115
+
116
+ 2. **Request-count** (fallback) -- After `requestsPerRotation` requests (default: 5), all models on that account rotate. Safety net for when quota data isn't available yet.
117
+
118
+ 3. **429 failover** (reactive) -- On rate limit or 5xx, the account is marked exhausted with a cooldown and the affected model immediately switches.
119
+
120
+ ### Account Protection
121
+
122
+ The proxy detects blocked/suspended accounts at three levels:
123
+
124
+ 1. **Quota API check** (on startup + every poll) -- If the quota API returns `403 PERMISSION_DENIED` with "violation of Terms of Service", the account is immediately flagged. This catches suspended accounts before any request is wasted.
125
+
126
+ 2. **API endpoint 401** (on request) -- If all 3 endpoints (daily, autopush, prod) reject the token with `401 UNAUTHENTICATED`, the account is flagged. The proxy cascades through all endpoints before giving up.
127
+
128
+ 3. **API endpoint 403** (on request) -- If the response body contains infringement keywords (`infring`, `suspend`, `abus`, `terminat`, `violat`, `banned`, `policy`, `forbidden`), the account is flagged.
129
+
130
+ Flagged accounts are **immediately excluded** from all model routing. The dashboard shows a red `FLAGGED` badge with the error message. Use the Re-enable button or `POST /api/enable/<email>` to clear the flag after resolving the issue with Google.
131
+
132
+ ### Endpoint Cascade
133
+
134
+ The proxy tries three Google API endpoints in order for each request:
135
+
136
+ 1. `daily-cloudcode-pa.sandbox.googleapis.com`
137
+ 2. `autopush-cloudcode-pa.sandbox.googleapis.com`
138
+ 3. `cloudcode-pa.googleapis.com` (prod)
139
+
140
+ On `401`, `403`, or `404`, it cascades to the next endpoint. Only the final endpoint's response is used for flagging decisions.
141
+
142
+ ## Configuration
143
+
144
+ All configuration is in `accounts.json`, created automatically by `npm run login`:
145
+
146
+ ```json
147
+ {
148
+ "proxyPort": 51200,
149
+ "requestsPerRotation": 5,
150
+ "rotateOnQuotaDrop": 20,
151
+ "quotaPollIntervalMs": 300000,
152
+ "accounts": [
153
+ {
154
+ "email": "user@gmail.com",
155
+ "refreshToken": "1//...",
156
+ "projectId": "project-abc123",
157
+ "label": "user"
158
+ }
159
+ ]
160
+ }
161
+ ```
162
+
163
+ ### Options
164
+
165
+ | Field | Default | Description |
166
+ |-------|---------|-------------|
167
+ | `proxyPort` | `51200` | Port the proxy listens on |
168
+ | `requestsPerRotation` | `5` | Max requests before rotating (fallback trigger) |
169
+ | `rotateOnQuotaDrop` | `20` | Rotate when a model's quota drops this many %. Set to `0` to disable |
170
+ | `quotaPollIntervalMs` | `300000` | Quota poll interval in ms (5 minutes) |
171
+
172
+ ### Account Fields
173
+
174
+ | Field | Description |
175
+ |-------|-------------|
176
+ | `email` | Google account email (auto-filled by login) |
177
+ | `refreshToken` | OAuth refresh token (auto-filled by login) |
178
+ | `projectId` | Cloud project ID (auto-discovered during login) |
179
+ | `label` | Display name on the dashboard (auto-filled, defaults to email username) |
180
+
181
+ ## API Endpoints
182
+
183
+ | Method | Path | Description |
184
+ |--------|------|-------------|
185
+ | `GET` | `/dashboard` | Web dashboard |
186
+ | `GET` | `/api/status` | JSON status: accounts, quotas, model routing, flags |
187
+ | `POST` | `/api/enable/<email>` | Clear flagged/disabled state and re-enable an account |
188
+ | `POST` | `/v1internal:streamGenerateContent` | Proxy endpoint (used by pi) |
189
+
190
+ ## Running as a Service
191
+
192
+ ```bash
193
+ # Using nohup
194
+ nohup npm start > rotator.log 2>&1 &
195
+
196
+ # Or with systemd (create /etc/systemd/system/pi-antigravity-rotator.service)
197
+ [Unit]
198
+ Description=Pi Antigravity Rotator
199
+ After=network.target
200
+
201
+ [Service]
202
+ Type=simple
203
+ WorkingDirectory=/path/to/pi-antigravity-rotator
204
+ ExecStart=/usr/bin/npm start
205
+ Restart=always
206
+ RestartSec=5
207
+
208
+ [Install]
209
+ WantedBy=multi-user.target
210
+ ```
211
+
212
+ ## Troubleshooting
213
+
214
+ **Account shows `flagged` status**
215
+ Google detected potential abuse. Review the error message on the dashboard. After resolving with Google, click Re-enable or `POST /api/enable/<email>`.
216
+
217
+ **Account keeps getting disabled after 5 errors**
218
+ Check the error message. Common causes: revoked OAuth consent, expired refresh token (re-run `npm run login`), or Google account suspension.
219
+
220
+ **Quota bars not showing**
221
+ Quota data appears after the first poll cycle (up to 5 minutes). Ensure accounts have valid tokens.
222
+
223
+ **All accounts exhausted**
224
+ The proxy uses the account with the shortest remaining cooldown. Add more accounts or increase `requestsPerRotation`.
225
+
226
+ **Multiple agents on different models**
227
+ 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.
package/bin/cli.mjs ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import("tsx/esm/api").then(({ register }) => {
3
+ register();
4
+ return import("./src/cli.ts");
5
+ });
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import("tsx/esm/api").then(({ register }) => {
3
+ register();
4
+ return import("./src/cli.ts");
5
+ });
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "pi-antigravity-rotator",
3
+ "version": "1.0.0",
4
+ "description": "Multi-account rotation proxy for Google Antigravity with per-model routing, real-time quota tracking, and infringement detection",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "pi-antigravity-rotator": "bin/pi-antigravity-rotator.js"
9
+ },
10
+ "scripts": {
11
+ "start": "tsx src/cli.ts start",
12
+ "login": "tsx src/cli.ts login"
13
+ },
14
+ "files": [
15
+ "bin/",
16
+ "src/",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "keywords": [
21
+ "pi",
22
+ "antigravity",
23
+ "proxy",
24
+ "rotation",
25
+ "google",
26
+ "quota",
27
+ "gemini"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/tuxevil/pi-antigravity-rotator.git"
32
+ },
33
+ "author": "tuxevil",
34
+ "dependencies": {
35
+ "tsx": "^4.19.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.0.0",
39
+ "typescript": "^5.7.0"
40
+ }
41
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+
3
+ // CLI entry point for pi-antigravity-rotator
4
+ // Usage:
5
+ // pi-antigravity-rotator start Start the proxy
6
+ // pi-antigravity-rotator login Add a new account
7
+ // pi-antigravity-rotator status Show account status
8
+
9
+ import { getConfigDir } from "./paths.js";
10
+
11
+ const args = process.argv.slice(2).filter((a) => !a.startsWith("--config-dir") && a !== process.argv[process.argv.indexOf("--config-dir") + 1]);
12
+ const command = args[0] || "start";
13
+
14
+ console.log(`Config dir: ${getConfigDir()}`);
15
+ console.log();
16
+
17
+ switch (command) {
18
+ case "start": {
19
+ // Dynamic import to avoid loading everything for help
20
+ const { main } = await import("./index.js");
21
+ main();
22
+ break;
23
+ }
24
+ case "login": {
25
+ const { runLogin } = await import("./login.js");
26
+ runLogin();
27
+ break;
28
+ }
29
+ case "status": {
30
+ try {
31
+ const port = 51200;
32
+ const res = await fetch(`http://localhost:${port}/api/status`);
33
+ const data = await res.json();
34
+ console.log(JSON.stringify(data, null, 2));
35
+ } catch {
36
+ console.error("Rotator is not running or unreachable on port 51200");
37
+ process.exit(1);
38
+ }
39
+ break;
40
+ }
41
+ default:
42
+ console.log("Pi Antigravity Rotator");
43
+ console.log();
44
+ console.log("Usage:");
45
+ console.log(" pi-antigravity-rotator start Start the proxy (default)");
46
+ console.log(" pi-antigravity-rotator login Add a new Google account");
47
+ console.log(" pi-antigravity-rotator status Show account status (JSON)");
48
+ console.log();
49
+ console.log("Options:");
50
+ console.log(" --config-dir <path> Config directory (default: ~/.pi-antigravity-rotator/)");
51
+ console.log();
52
+ console.log("Environment:");
53
+ console.log(" PI_ROTATOR_DIR Config directory override");
54
+ process.exit(command === "help" || command === "--help" ? 0 : 1);
55
+ }