pi-antigravity-rotator 1.9.1 → 1.9.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.9.2] - 2026-04-29
4
+
5
+ ### Fixed
6
+ - **Project Discovery Without Shared Fallback**: Login now fails if Google does not return a companion project ID. No more shared `rising-fact-p41fc` fallback.
7
+ - **Activation Hint**: Login/discovery errors now tell you to open the account in Antigravity IDE and send one message first.
8
+ - **Activation Docs**: README now documents the first-use activation rule for new accounts.
9
+
3
10
  ## [1.9.1] - 2026-04-29
4
11
 
5
12
  ### Fixed
package/README.md CHANGED
@@ -9,7 +9,7 @@ Multi-account rotation proxy for Google Antigravity. Distributes API usage acros
9
9
  - **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
10
10
  - **Smart rotation** -- Rotates only the specific model whose quota dropped, leaving other models on their current accounts
11
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
12
+ - **Safer 429 handling** -- On provider `429`, stops the current request and avoids cascade-burning sibling accounts
13
13
  - **Concurrency guardrails** -- Limits each account to one in-flight request by default to avoid bursty pressure
14
14
  - **Operator fresh-window controls** -- You can block new `fresh` window starts globally, then selectively allow specific accounts to override that policy
15
15
  - **Protective pause** -- Pauses all routing for several hours after serious ToS/abuse-style flags so the rest of the pool is not burned
@@ -57,6 +57,7 @@ Run `npm run login` once per Google account:
57
57
  2. Complete the sign-in and grant permissions
58
58
  3. The browser redirects to a `localhost` URL that won't load -- this is expected
59
59
  4. Copy the **full URL** from the browser's address bar and paste it into the terminal
60
+ 5. If project discovery fails, open that same account in Antigravity IDE, send one message, then rerun login
60
61
 
61
62
  The tool automatically:
62
63
 
@@ -66,6 +67,15 @@ The tool automatically:
66
67
 
67
68
  Re-running with the same email updates the existing entry.
68
69
 
70
+ ### Activation rule
71
+
72
+ Some accounts do not expose a discoverable `projectId` until they are used once in the Antigravity IDE.
73
+ If login fails at project discovery:
74
+
75
+ 1. Open that exact Google account in Antigravity IDE.
76
+ 2. Send one message.
77
+ 3. Rerun `npm run login`.
78
+
69
79
  ## Dashboard
70
80
 
71
81
  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`).
@@ -213,7 +223,8 @@ export PI_AI_ANTIGRAVITY_VERSION=1.107.0
213
223
  pi-antigravity-rotator start --config-dir /path/to/config
214
224
  ```
215
225
 
216
- `accounts.json` is created automatically by the login command:
226
+ `accounts.json` is created automatically by the login command.
227
+ Login now fails if Google does not return a project ID. No shared fallback.
217
228
 
218
229
  ```json
219
230
  {
@@ -273,7 +284,8 @@ pi-antigravity-rotator start --config-dir /path/to/config
273
284
  |-------|-------------|
274
285
  | `email` | Google account email (auto-filled by login) |
275
286
  | `refreshToken` | OAuth refresh token (auto-filled by login) |
276
- | `projectId` | Cloud project ID (auto-discovered during login) |
287
+ | `projectId` | Cloud project ID discovered from Google during login |
288
+ | `projectSource` | Optional metadata: `google` when discovered from Google, `manual` if edited by hand |
277
289
  | `label` | Display name on the dashboard (auto-filled, defaults to email username) |
278
290
 
279
291
  ## API Endpoints
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-antigravity-rotator",
3
- "version": "1.9.1",
3
+ "version": "1.9.2",
4
4
  "description": "Multi-account rotation proxy for Google Antigravity with per-model routing, real-time quota tracking, and infringement detection",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/login.ts CHANGED
@@ -79,19 +79,21 @@ export async function runLogin(): Promise<void> {
79
79
  const email = await getUserEmail(tokenData.accessToken);
80
80
 
81
81
  console.log("Discovering project...");
82
- const projectId = await discoverProject(tokenData.accessToken);
82
+ const project = await discoverProject(tokenData.accessToken);
83
83
 
84
84
  const label = email ? email.split("@")[0] : "Account";
85
85
  const entry: AccountConfig = {
86
86
  email: email || "unknown@gmail.com",
87
87
  refreshToken: tokenData.refreshToken,
88
- projectId,
88
+ projectId: project.projectId,
89
+ projectSource: project.source,
89
90
  label,
90
91
  };
91
92
 
92
93
  console.log();
93
94
  const { isNew } = addAccountToConfig(entry);
94
95
  console.log(` ${isNew ? "Added" : "Updated"} ${entry.email} in ${ACCOUNTS_FILE}`);
96
+ console.log(` projectId=${project.projectId} (source=${project.source})`);
95
97
 
96
98
  ensurePiModelsConfig();
97
99
  ensurePiAuthConfig();
package/src/oauth.ts CHANGED
@@ -11,7 +11,6 @@ export const SCOPES = [
11
11
  "https://www.googleapis.com/auth/experimentsandconfigs",
12
12
  ];
13
13
  export const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
14
- export const DEFAULT_PROJECT_ID = "rising-fact-p41fc";
15
14
 
16
15
  export interface OAuthClientConfig {
17
16
  clientId: string;
@@ -107,7 +106,13 @@ export async function exchangeAuthorizationCode(code: string, verifier: string):
107
106
  };
108
107
  }
109
108
 
110
- export async function discoverProject(accessToken: string): Promise<string> {
109
+ export interface ProjectDiscoveryResult {
110
+ projectId: string;
111
+ source: "google";
112
+ endpoint: string;
113
+ }
114
+
115
+ export async function discoverProject(accessToken: string): Promise<ProjectDiscoveryResult> {
111
116
  const headers: Record<string, string> = {
112
117
  Authorization: `Bearer ${accessToken}`,
113
118
  "Content-Type": "application/json",
@@ -138,14 +143,14 @@ export async function discoverProject(accessToken: string): Promise<string> {
138
143
  cloudaicompanionProject?: string | { id?: string };
139
144
  };
140
145
  if (typeof data.cloudaicompanionProject === "string" && data.cloudaicompanionProject) {
141
- return data.cloudaicompanionProject;
146
+ return { projectId: data.cloudaicompanionProject, source: "google", endpoint };
142
147
  }
143
148
  if (
144
149
  data.cloudaicompanionProject &&
145
150
  typeof data.cloudaicompanionProject === "object" &&
146
151
  data.cloudaicompanionProject.id
147
152
  ) {
148
- return data.cloudaicompanionProject.id;
153
+ return { projectId: data.cloudaicompanionProject.id, source: "google", endpoint };
149
154
  }
150
155
  }
151
156
  } catch {
@@ -153,7 +158,7 @@ export async function discoverProject(accessToken: string): Promise<string> {
153
158
  }
154
159
  }
155
160
 
156
- return DEFAULT_PROJECT_ID;
161
+ throw new Error("Could not discover Cloud Code companion project ID from Google. If this account is new, open it in Antigravity IDE and send one message first, then retry login. Login failed instead of falling back to a shared projectId.");
157
162
  }
158
163
 
159
164
  export async function getUserEmail(accessToken: string): Promise<string | undefined> {
package/src/onboarding.ts CHANGED
@@ -203,12 +203,13 @@ export async function handleHostedCallback(
203
203
  try {
204
204
  const tokenData = await exchangeAuthorizationCode(code, session.verifier);
205
205
  const email = await getUserEmail(tokenData.accessToken);
206
- const projectId = await discoverProject(tokenData.accessToken);
206
+ const project = await discoverProject(tokenData.accessToken);
207
207
  const label = email ? email.split("@")[0] : "Account";
208
208
  const entry = {
209
209
  email: email || "unknown@gmail.com",
210
210
  refreshToken: tokenData.refreshToken,
211
- projectId,
211
+ projectId: project.projectId,
212
+ projectSource: project.source,
212
213
  label,
213
214
  };
214
215
 
@@ -221,6 +222,7 @@ export async function handleHostedCallback(
221
222
  "Account Connected",
222
223
  `<h1>Account Connected</h1>
223
224
  <p><strong>${entry.email}</strong> was ${isNew ? "added" : "updated"} successfully.</p>
225
+ <p>Project: <span class="mono">${project.projectId}</span> via ${project.source}.</p>
224
226
  <p>The rotator can start using this account immediately.</p>
225
227
  <div class="note">If you ever want to stop sharing access, revoke this app's access from the Google account security settings.</div>`,
226
228
  ),
package/src/types.ts CHANGED
@@ -6,6 +6,8 @@ export interface AccountConfig {
6
6
  email: string;
7
7
  refreshToken: string;
8
8
  projectId: string;
9
+ // How the projectId was obtained.
10
+ projectSource?: "google" | "manual";
9
11
  label?: string;
10
12
  // Optional - pro/free is detected dynamically from quota API reset times
11
13
  type?: AccountType;