opencode-antigravity-auth 1.0.7 → 1.1.1

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/README.md CHANGED
@@ -6,33 +6,60 @@ Enable Opencode to authenticate against **Antigravity** (Google's IDE) via OAuth
6
6
 
7
7
  ## What you get
8
8
 
9
- - **Google OAuth sign-in** with automatic token refresh
9
+ - **Google OAuth sign-in** (multi-account via `opencode auth login`) with automatic token refresh
10
+ - **Multi-account load balancing** Automatically cycle through multiple Google accounts to maximize rate limits
11
+ - **Automatic endpoint fallback** between Antigravity API endpoints (daily → autopush → prod)
10
12
  - **Antigravity API compatibility** for OpenAI-style requests
11
13
  - **Debug logging** for requests and responses
12
- - **Drop-in setup**—Opencode auto-installs the plugin from config
14
+ - **Drop-in setup** Opencode auto-installs the plugin from config
13
15
 
14
16
  ## Quick start
15
17
 
16
- 1) **Add the plugin to config** (`~/.config/opencode/opencode.json` or project `.opencode.json`):
18
+ ### Step 1: Create your config file
19
+
20
+ If this is your first time using Opencode, create the config directory first:
21
+
22
+ ```bash
23
+ mkdir -p ~/.config/opencode
24
+ ```
25
+
26
+ Then create or edit the config file at `~/.config/opencode/opencode.json`:
17
27
 
18
28
  ```json
19
29
  {
20
- "plugin": ["opencode-antigravity-auth@1.0.7"]
30
+ "plugin": ["opencode-antigravity-auth@1.1.1"]
21
31
  }
22
32
  ```
23
33
 
24
- 2) **Authenticate**
34
+ > **Note:** You can also use a project-local `.opencode.json` file in your project root instead. The global config at `~/.config/opencode/opencode.json` applies to all projects.
35
+
36
+ ### Step 2: Authenticate
37
+
38
+ Run the authentication command:
39
+
40
+ ```bash
41
+ opencode auth login
42
+ ```
43
+
44
+ 1. Select **Google** as the provider
45
+ 2. Select **OAuth with Google (Antigravity)**
46
+ 3. **Project ID prompt:** You'll see this prompt:
47
+ ```
48
+ Project ID (leave blank to use your default project):
49
+ ```
50
+ **Just press Enter to skip this** — it's optional and only needed if you want to use a specific Google Cloud project. Most users can leave it blank.
51
+ 4. Sign in via the browser and return to Opencode. If the browser doesn't open, copy the displayed URL manually.
52
+ 5. After signing in, you can add more Google accounts (up to 10) for load balancing, or press Enter to finish.
25
53
 
26
- - Run `opencode auth login`.
27
- - Choose Google → **OAuth with Google (Antigravity)**.
28
- - Sign in via the browser and return to Opencode. If the browser doesn’t open, use the printed link.
54
+ > **Alternative:** For a quick single-account setup without project ID options, open `opencode` and use the `/connect` command instead.
29
55
 
30
- 3) **Declare the models you want**
56
+ ### Step 3: Add the models you want to use
57
+
58
+ Open the **same config file** you created in Step 1 (`~/.config/opencode/opencode.json`) and add the models under `provider.google.models`:
31
59
 
32
- Add Antigravity models under the `provider.google.models` section of your config:
33
60
  ```json
34
61
  {
35
- "plugin": ["opencode-antigravity-auth"],
62
+ "plugin": ["opencode-antigravity-auth@1.1.1"],
36
63
  "provider": {
37
64
  "google": {
38
65
  "models": {
@@ -84,12 +111,64 @@ Add Antigravity models under the `provider.google.models` section of your config
84
111
  }
85
112
  ```
86
113
 
87
- 4) **Use a model**
114
+ > **Tip:** You only need to add the models you plan to use. The example above includes all available models, but you can remove any you don't need.
115
+
116
+ ### Step 4: Use a model
88
117
 
89
118
  ```bash
90
119
  opencode run "Hello world" --model=google/gemini-3-pro-high
91
120
  ```
92
121
 
122
+ Or start the interactive TUI and select a model from the model picker:
123
+
124
+ ```bash
125
+ opencode
126
+ ```
127
+
128
+ ## Multi-account load balancing
129
+
130
+ The plugin supports multiple Google accounts to maximize rate limits and provide automatic failover.
131
+
132
+ ### How it works
133
+
134
+ - **Round-robin selection:** Each request uses the next account in the pool
135
+ - **Automatic failover:** On HTTP `429` (rate limit), the plugin automatically switches to the next available account
136
+ - **Smart cooldown:** Rate-limited accounts are temporarily cooled down and automatically become available again after the cooldown expires
137
+ - **Single-account retry:** If you only have one account, the plugin waits for the rate limit to reset and retries automatically
138
+ - **Toast notifications:** The TUI shows which account is being used and when switching occurs
139
+
140
+ ### Adding accounts
141
+
142
+ **CLI flow (`opencode auth login`):**
143
+
144
+ When you run `opencode auth login` and already have accounts saved, you'll be prompted:
145
+
146
+ ```
147
+ 2 account(s) saved:
148
+ 1. user1@gmail.com
149
+ 2. user2@gmail.com
150
+
151
+ (a)dd new account(s) or (f)resh start? [a/f]:
152
+ ```
153
+
154
+ - Press `a` to add more accounts to your existing pool
155
+ - Press `f` to clear all existing accounts and start fresh
156
+
157
+ **TUI flow (`/connect`):**
158
+
159
+ The `/connect` command in the TUI adds accounts non-destructively — it will never clear your existing accounts. To start fresh via TUI, run `opencode auth logout` first, then `/connect`.
160
+
161
+ ### Account storage
162
+
163
+ - Account pool is stored in `~/.config/opencode/antigravity-accounts.json` (or `%APPDATA%\opencode\antigravity-accounts.json` on Windows)
164
+ - This file contains OAuth refresh tokens; **treat it like a password** and don't share or commit it
165
+ - The plugin automatically syncs with OpenCode's auth state — if you log out via OpenCode, stale account storage is cleared automatically
166
+
167
+ ### Automatic account recovery
168
+
169
+ - If Google revokes a refresh token (`invalid_grant`), that account is automatically removed from the pool
170
+ - Rerun `opencode auth login` to re-add the account
171
+
93
172
  ## Debugging
94
173
 
95
174
  Enable verbose logging:
@@ -139,5 +218,15 @@ Use at your own risk. Proceed only if you understand and accept these risks.
139
218
 
140
219
  ## Credits
141
220
 
142
- - Inspired by and different from [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) by [jenslys](https://github.com/jenslys). Thanks for the groundwork! 🚀
143
- - Thanks to [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) for the inspiration.
221
+ Built with help and inspiration from:
222
+
223
+ - [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) — Gemini OAuth groundwork by [@jenslys](https://github.com/jenslys)
224
+ - [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) — Helpful reference for Antigravity API
225
+
226
+ ## Support
227
+
228
+ If this plugin helps you, consider supporting its continued maintenance:
229
+
230
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S81QBOIR)
231
+
232
+
@@ -0,0 +1,37 @@
1
+ import { type AccountStorage } from "./storage";
2
+ import type { OAuthAuthDetails, RefreshParts } from "./types";
3
+ export interface ManagedAccount {
4
+ index: number;
5
+ email?: string;
6
+ addedAt: number;
7
+ lastUsed: number;
8
+ parts: RefreshParts;
9
+ access?: string;
10
+ expires?: number;
11
+ isRateLimited: boolean;
12
+ rateLimitResetTime: number;
13
+ }
14
+ /**
15
+ * In-memory multi-account manager for round-robin routing.
16
+ *
17
+ * Source of truth for the pool is `antigravity-accounts.json`.
18
+ */
19
+ export declare class AccountManager {
20
+ private accounts;
21
+ private cursor;
22
+ static loadFromDisk(authFallback?: OAuthAuthDetails): Promise<AccountManager>;
23
+ constructor(authFallback?: OAuthAuthDetails, stored?: AccountStorage | null);
24
+ getAccountCount(): number;
25
+ getAccountsSnapshot(): ManagedAccount[];
26
+ /**
27
+ * Picks the next available account (round-robin), skipping accounts in cooldown.
28
+ */
29
+ pickNext(): ManagedAccount | null;
30
+ markRateLimited(account: ManagedAccount, retryAfterMs: number): void;
31
+ removeAccount(account: ManagedAccount): boolean;
32
+ updateFromAuth(account: ManagedAccount, auth: OAuthAuthDetails): void;
33
+ toAuthDetails(account: ManagedAccount): OAuthAuthDetails;
34
+ getMinWaitTimeMs(): number;
35
+ saveToDisk(): Promise<void>;
36
+ }
37
+ //# sourceMappingURL=accounts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../../../src/plugin/accounts.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE9D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAaD;;;;GAIG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,MAAM,CAAK;WAEN,YAAY,CAAC,YAAY,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,cAAc,CAAC;gBAKvE,YAAY,CAAC,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI;IAuE3E,eAAe,IAAI,MAAM;IAIzB,mBAAmB,IAAI,cAAc,EAAE;IAIvC;;OAEG;IACH,QAAQ,IAAI,cAAc,GAAG,IAAI;IAiCjC,eAAe,CAAC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAMpE,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO;IAwB/C,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAOrE,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,gBAAgB;IASxD,gBAAgB,IAAI,MAAM;IAuBpB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAkBlC"}
@@ -0,0 +1,197 @@
1
+ import { formatRefreshParts, parseRefreshParts } from "./auth";
2
+ import { loadAccounts, saveAccounts } from "./storage";
3
+ function nowMs() {
4
+ return Date.now();
5
+ }
6
+ function clampNonNegativeInt(value, fallback) {
7
+ if (typeof value !== "number" || !Number.isFinite(value)) {
8
+ return fallback;
9
+ }
10
+ return value < 0 ? 0 : Math.floor(value);
11
+ }
12
+ /**
13
+ * In-memory multi-account manager for round-robin routing.
14
+ *
15
+ * Source of truth for the pool is `antigravity-accounts.json`.
16
+ */
17
+ export class AccountManager {
18
+ accounts = [];
19
+ cursor = 0;
20
+ static async loadFromDisk(authFallback) {
21
+ const stored = await loadAccounts();
22
+ return new AccountManager(authFallback, stored);
23
+ }
24
+ constructor(authFallback, stored) {
25
+ const authParts = authFallback ? parseRefreshParts(authFallback.refresh) : null;
26
+ if (stored && stored.accounts.length === 0) {
27
+ this.accounts = [];
28
+ this.cursor = 0;
29
+ return;
30
+ }
31
+ if (stored && stored.accounts.length > 0) {
32
+ const baseNow = nowMs();
33
+ this.accounts = stored.accounts
34
+ .map((acc, index) => {
35
+ if (!acc.refreshToken || typeof acc.refreshToken !== "string") {
36
+ return null;
37
+ }
38
+ const matchesFallback = !!(authFallback &&
39
+ authParts &&
40
+ authParts.refreshToken &&
41
+ acc.refreshToken === authParts.refreshToken);
42
+ return {
43
+ index,
44
+ email: acc.email,
45
+ addedAt: clampNonNegativeInt(acc.addedAt, baseNow),
46
+ lastUsed: clampNonNegativeInt(acc.lastUsed, 0),
47
+ parts: {
48
+ refreshToken: acc.refreshToken,
49
+ projectId: acc.projectId,
50
+ managedProjectId: acc.managedProjectId,
51
+ },
52
+ access: matchesFallback ? authFallback?.access : undefined,
53
+ expires: matchesFallback ? authFallback?.expires : undefined,
54
+ isRateLimited: !!acc.isRateLimited,
55
+ rateLimitResetTime: clampNonNegativeInt(acc.rateLimitResetTime, 0),
56
+ };
57
+ })
58
+ .filter((a) => a !== null);
59
+ this.cursor = clampNonNegativeInt(stored.activeIndex, 0);
60
+ if (this.accounts.length > 0) {
61
+ this.cursor = this.cursor % this.accounts.length;
62
+ }
63
+ return;
64
+ }
65
+ if (authFallback) {
66
+ const parts = parseRefreshParts(authFallback.refresh);
67
+ if (parts.refreshToken) {
68
+ const now = nowMs();
69
+ this.accounts = [
70
+ {
71
+ index: 0,
72
+ email: undefined,
73
+ addedAt: now,
74
+ lastUsed: 0,
75
+ parts,
76
+ access: authFallback.access,
77
+ expires: authFallback.expires,
78
+ isRateLimited: false,
79
+ rateLimitResetTime: 0,
80
+ },
81
+ ];
82
+ this.cursor = 0;
83
+ }
84
+ }
85
+ }
86
+ getAccountCount() {
87
+ return this.accounts.length;
88
+ }
89
+ getAccountsSnapshot() {
90
+ return this.accounts.map((a) => ({ ...a, parts: { ...a.parts } }));
91
+ }
92
+ /**
93
+ * Picks the next available account (round-robin), skipping accounts in cooldown.
94
+ */
95
+ pickNext() {
96
+ const total = this.accounts.length;
97
+ if (total === 0) {
98
+ return null;
99
+ }
100
+ const now = nowMs();
101
+ // Clear expired cooldowns.
102
+ for (const acc of this.accounts) {
103
+ if (acc.isRateLimited && acc.rateLimitResetTime > 0 && now > acc.rateLimitResetTime) {
104
+ acc.isRateLimited = false;
105
+ acc.rateLimitResetTime = 0;
106
+ }
107
+ }
108
+ for (let i = 0; i < total; i++) {
109
+ const idx = (this.cursor + i) % total;
110
+ const candidate = this.accounts[idx];
111
+ if (!candidate) {
112
+ continue;
113
+ }
114
+ if (candidate.isRateLimited) {
115
+ continue;
116
+ }
117
+ this.cursor = (idx + 1) % total;
118
+ candidate.lastUsed = now;
119
+ return candidate;
120
+ }
121
+ return null;
122
+ }
123
+ markRateLimited(account, retryAfterMs) {
124
+ const duration = clampNonNegativeInt(retryAfterMs, 0);
125
+ account.isRateLimited = true;
126
+ account.rateLimitResetTime = nowMs() + duration;
127
+ }
128
+ removeAccount(account) {
129
+ const idx = this.accounts.indexOf(account);
130
+ if (idx < 0) {
131
+ return false;
132
+ }
133
+ this.accounts.splice(idx, 1);
134
+ this.accounts.forEach((acc, index) => {
135
+ acc.index = index;
136
+ });
137
+ if (this.accounts.length === 0) {
138
+ this.cursor = 0;
139
+ return true;
140
+ }
141
+ if (this.cursor > idx) {
142
+ this.cursor -= 1;
143
+ }
144
+ this.cursor = this.cursor % this.accounts.length;
145
+ return true;
146
+ }
147
+ updateFromAuth(account, auth) {
148
+ const parts = parseRefreshParts(auth.refresh);
149
+ account.parts = parts;
150
+ account.access = auth.access;
151
+ account.expires = auth.expires;
152
+ }
153
+ toAuthDetails(account) {
154
+ return {
155
+ type: "oauth",
156
+ refresh: formatRefreshParts(account.parts),
157
+ access: account.access,
158
+ expires: account.expires,
159
+ };
160
+ }
161
+ getMinWaitTimeMs() {
162
+ const now = nowMs();
163
+ // Clear expired cooldowns first (same logic as pickNext)
164
+ for (const acc of this.accounts) {
165
+ if (acc.isRateLimited && acc.rateLimitResetTime > 0 && now > acc.rateLimitResetTime) {
166
+ acc.isRateLimited = false;
167
+ acc.rateLimitResetTime = 0;
168
+ }
169
+ }
170
+ const available = this.accounts.some((a) => !a.isRateLimited);
171
+ if (available) {
172
+ return 0;
173
+ }
174
+ const waits = this.accounts
175
+ .filter((a) => a.isRateLimited && a.rateLimitResetTime > 0)
176
+ .map((a) => Math.max(0, a.rateLimitResetTime - now));
177
+ return waits.length > 0 ? Math.min(...waits) : 0;
178
+ }
179
+ async saveToDisk() {
180
+ const storage = {
181
+ version: 1,
182
+ accounts: this.accounts.map((a) => ({
183
+ email: a.email,
184
+ refreshToken: a.parts.refreshToken,
185
+ projectId: a.parts.projectId,
186
+ managedProjectId: a.parts.managedProjectId,
187
+ addedAt: a.addedAt,
188
+ lastUsed: a.lastUsed,
189
+ isRateLimited: a.isRateLimited,
190
+ rateLimitResetTime: a.rateLimitResetTime,
191
+ })),
192
+ activeIndex: this.cursor,
193
+ };
194
+ await saveAccounts(storage);
195
+ }
196
+ }
197
+ //# sourceMappingURL=accounts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounts.js","sourceRoot":"","sources":["../../../src/plugin/accounts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=accounts.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounts.test.d.ts","sourceRoot":"","sources":["../../../src/plugin/accounts.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,139 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { AccountManager } from "./accounts";
3
+ describe("AccountManager", () => {
4
+ beforeEach(() => {
5
+ vi.useRealTimers();
6
+ });
7
+ it("treats on-disk storage as source of truth, even when empty", () => {
8
+ const fallback = {
9
+ type: "oauth",
10
+ refresh: "r1|p1",
11
+ access: "access",
12
+ expires: 123,
13
+ };
14
+ const stored = {
15
+ version: 1,
16
+ accounts: [],
17
+ activeIndex: 0,
18
+ };
19
+ const manager = new AccountManager(fallback, stored);
20
+ expect(manager.getAccountCount()).toBe(0);
21
+ });
22
+ it("rotates accounts round-robin", () => {
23
+ const stored = {
24
+ version: 1,
25
+ accounts: [
26
+ { refreshToken: "r1", projectId: "p1", addedAt: 1, lastUsed: 0 },
27
+ { refreshToken: "r2", projectId: "p2", addedAt: 1, lastUsed: 0 },
28
+ ],
29
+ activeIndex: 0,
30
+ };
31
+ const manager = new AccountManager(undefined, stored);
32
+ expect(manager.pickNext()?.parts.refreshToken).toBe("r1");
33
+ expect(manager.pickNext()?.parts.refreshToken).toBe("r2");
34
+ expect(manager.pickNext()?.parts.refreshToken).toBe("r1");
35
+ });
36
+ it("attaches fallback access tokens only to the matching stored account", () => {
37
+ const fallback = {
38
+ type: "oauth",
39
+ refresh: "r2|p2",
40
+ access: "access-2",
41
+ expires: 123,
42
+ };
43
+ const stored = {
44
+ version: 1,
45
+ accounts: [
46
+ { refreshToken: "r1", projectId: "p1", addedAt: 1, lastUsed: 0 },
47
+ { refreshToken: "r2", projectId: "p2", addedAt: 1, lastUsed: 0 },
48
+ ],
49
+ activeIndex: 0,
50
+ };
51
+ const manager = new AccountManager(fallback, stored);
52
+ const snapshot = manager.getAccountsSnapshot();
53
+ expect(snapshot[0]?.access).toBeUndefined();
54
+ expect(snapshot[0]?.expires).toBeUndefined();
55
+ expect(snapshot[1]?.access).toBe("access-2");
56
+ expect(snapshot[1]?.expires).toBe(123);
57
+ });
58
+ it("does not attach fallback access tokens to an unrelated account", () => {
59
+ const fallback = {
60
+ type: "oauth",
61
+ refresh: "r3|p3",
62
+ access: "access-3",
63
+ expires: 456,
64
+ };
65
+ const stored = {
66
+ version: 1,
67
+ accounts: [
68
+ { refreshToken: "r1", projectId: "p1", addedAt: 1, lastUsed: 0 },
69
+ { refreshToken: "r2", projectId: "p2", addedAt: 1, lastUsed: 0 },
70
+ ],
71
+ activeIndex: 0,
72
+ };
73
+ const manager = new AccountManager(fallback, stored);
74
+ const snapshot = manager.getAccountsSnapshot();
75
+ expect(snapshot.some((account) => !!account.access)).toBe(false);
76
+ expect(snapshot.some((account) => typeof account.expires === "number")).toBe(false);
77
+ });
78
+ it("skips rate-limited accounts", () => {
79
+ vi.useFakeTimers();
80
+ vi.setSystemTime(new Date(0));
81
+ const stored = {
82
+ version: 1,
83
+ accounts: [
84
+ { refreshToken: "r1", projectId: "p1", addedAt: 1, lastUsed: 0 },
85
+ { refreshToken: "r2", projectId: "p2", addedAt: 1, lastUsed: 0 },
86
+ ],
87
+ activeIndex: 0,
88
+ };
89
+ const manager = new AccountManager(undefined, stored);
90
+ const first = manager.pickNext();
91
+ expect(first?.parts.refreshToken).toBe("r1");
92
+ manager.markRateLimited(first, 60_000);
93
+ const next = manager.pickNext();
94
+ expect(next?.parts.refreshToken).toBe("r2");
95
+ });
96
+ it("returns minimum wait time and re-enables after cooldown", () => {
97
+ vi.useFakeTimers();
98
+ vi.setSystemTime(new Date(0));
99
+ const stored = {
100
+ version: 1,
101
+ accounts: [
102
+ { refreshToken: "r1", projectId: "p1", addedAt: 1, lastUsed: 0 },
103
+ { refreshToken: "r2", projectId: "p2", addedAt: 1, lastUsed: 0 },
104
+ ],
105
+ activeIndex: 0,
106
+ };
107
+ const manager = new AccountManager(undefined, stored);
108
+ const acc1 = manager.pickNext();
109
+ const acc2 = manager.pickNext();
110
+ manager.markRateLimited(acc1, 10_000);
111
+ manager.markRateLimited(acc2, 5_000);
112
+ expect(manager.pickNext()).toBeNull();
113
+ expect(manager.getMinWaitTimeMs()).toBe(5_000);
114
+ vi.setSystemTime(new Date(6_000));
115
+ const available = manager.pickNext();
116
+ expect(available?.parts.refreshToken).toBe("r2");
117
+ });
118
+ it("removes an account and keeps cursor consistent", () => {
119
+ vi.useFakeTimers();
120
+ vi.setSystemTime(new Date(0));
121
+ const stored = {
122
+ version: 1,
123
+ accounts: [
124
+ { refreshToken: "r1", projectId: "p1", addedAt: 1, lastUsed: 0 },
125
+ { refreshToken: "r2", projectId: "p2", addedAt: 1, lastUsed: 0 },
126
+ { refreshToken: "r3", projectId: "p3", addedAt: 1, lastUsed: 0 },
127
+ ],
128
+ activeIndex: 1,
129
+ };
130
+ const manager = new AccountManager(undefined, stored);
131
+ const picked = manager.pickNext();
132
+ expect(picked?.parts.refreshToken).toBe("r2");
133
+ manager.removeAccount(picked);
134
+ expect(manager.getAccountCount()).toBe(2);
135
+ const next = manager.pickNext();
136
+ expect(next?.parts.refreshToken).toBe("r3");
137
+ });
138
+ });
139
+ //# sourceMappingURL=accounts.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounts.test.js","sourceRoot":"","sources":["../../../src/plugin/accounts.test.ts"],"names":[],"mappings":"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"}
@@ -2,4 +2,18 @@
2
2
  * Prompts the user for a project ID via stdin/stdout.
3
3
  */
4
4
  export declare function promptProjectId(): Promise<string>;
5
+ /**
6
+ * Prompts user whether they want to add another OAuth account.
7
+ */
8
+ export declare function promptAddAnotherAccount(currentCount: number): Promise<boolean>;
9
+ export type LoginMode = "add" | "fresh";
10
+ export interface ExistingAccountInfo {
11
+ email?: string;
12
+ index: number;
13
+ }
14
+ /**
15
+ * Prompts user to choose login mode when accounts already exist.
16
+ * Returns "add" to append new accounts, "fresh" to clear and start over.
17
+ */
18
+ export declare function promptLoginMode(existingAccounts: ExistingAccountInfo[]): Promise<LoginMode>;
5
19
  //# sourceMappingURL=cli.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/plugin/cli.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAQvD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/plugin/cli.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAQvD;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASpF;AAED,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC;AAExC,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CA0BjG"}
@@ -13,4 +13,47 @@ export async function promptProjectId() {
13
13
  rl.close();
14
14
  }
15
15
  }
16
+ /**
17
+ * Prompts user whether they want to add another OAuth account.
18
+ */
19
+ export async function promptAddAnotherAccount(currentCount) {
20
+ const rl = createInterface({ input, output });
21
+ try {
22
+ const answer = await rl.question(`Add another account? (${currentCount} added) (y/n): `);
23
+ const normalized = answer.trim().toLowerCase();
24
+ return normalized === "y" || normalized === "yes";
25
+ }
26
+ finally {
27
+ rl.close();
28
+ }
29
+ }
30
+ /**
31
+ * Prompts user to choose login mode when accounts already exist.
32
+ * Returns "add" to append new accounts, "fresh" to clear and start over.
33
+ */
34
+ export async function promptLoginMode(existingAccounts) {
35
+ const rl = createInterface({ input, output });
36
+ try {
37
+ console.log(`\n${existingAccounts.length} account(s) saved:`);
38
+ for (const acc of existingAccounts) {
39
+ const label = acc.email || `Account ${acc.index + 1}`;
40
+ console.log(` ${acc.index + 1}. ${label}`);
41
+ }
42
+ console.log("");
43
+ while (true) {
44
+ const answer = await rl.question("(a)dd new account(s) or (f)resh start? [a/f]: ");
45
+ const normalized = answer.trim().toLowerCase();
46
+ if (normalized === "a" || normalized === "add") {
47
+ return "add";
48
+ }
49
+ if (normalized === "f" || normalized === "fresh") {
50
+ return "fresh";
51
+ }
52
+ console.log("Please enter 'a' to add accounts or 'f' to start fresh.");
53
+ }
54
+ }
55
+ finally {
56
+ rl.close();
57
+ }
58
+ }
16
59
  //# sourceMappingURL=cli.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../src/plugin/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,cAAc,CAAC;AAEhE;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,wDAAwD,CAAC,CAAC;QAC3F,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../src/plugin/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,cAAc,CAAC;AAEhE;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,wDAAwD,CAAC,CAAC;QAC3F,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,YAAoB;IAChE,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,YAAY,iBAAiB,CAAC,CAAC;QACzF,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC/C,OAAO,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,KAAK,CAAC;IACpD,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AASD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,gBAAuC;IAC3E,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,KAAK,gBAAgB,CAAC,MAAM,oBAAoB,CAAC,CAAC;QAC9D,KAAK,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,WAAW,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;YACtD,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,KAAK,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC,CAAC;YACnF,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAE/C,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;gBAC/C,OAAO,KAAK,CAAC;YACf,CAAC;YACD,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;gBACjD,OAAO,OAAO,CAAC;YACjB,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { OAuthAuthDetails, PluginClient, ProjectContextResult } from "./types";
1
+ import type { OAuthAuthDetails, ProjectContextResult } from "./types";
2
2
  interface AntigravityUserTier {
3
3
  id?: string;
4
4
  isDefault?: boolean;
@@ -28,6 +28,6 @@ export declare function onboardManagedProject(accessToken: string, tierId: strin
28
28
  /**
29
29
  * Resolves an effective project ID for the current auth state, caching results per refresh token.
30
30
  */
31
- export declare function ensureProjectContext(auth: OAuthAuthDetails, client: PluginClient, providerId: string): Promise<ProjectContextResult>;
31
+ export declare function ensureProjectContext(auth: OAuthAuthDetails): Promise<ProjectContextResult>;
32
32
  export {};
33
33
  //# sourceMappingURL=project.d.ts.map