opencode-gemini-cli-oauth 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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +256 -0
  3. package/dist/auth/account-manager.d.ts +50 -0
  4. package/dist/auth/account-manager.d.ts.map +1 -0
  5. package/dist/auth/account-manager.js +142 -0
  6. package/dist/auth/account-manager.js.map +1 -0
  7. package/dist/auth/oauth.d.ts +40 -0
  8. package/dist/auth/oauth.d.ts.map +1 -0
  9. package/dist/auth/oauth.js +126 -0
  10. package/dist/auth/oauth.js.map +1 -0
  11. package/dist/auth/request-interceptor.d.ts +18 -0
  12. package/dist/auth/request-interceptor.d.ts.map +1 -0
  13. package/dist/auth/request-interceptor.js +94 -0
  14. package/dist/auth/request-interceptor.js.map +1 -0
  15. package/dist/constants.d.ts +55 -0
  16. package/dist/constants.d.ts.map +1 -0
  17. package/dist/constants.js +61 -0
  18. package/dist/constants.js.map +1 -0
  19. package/dist/index-minimal.d.ts +2 -0
  20. package/dist/index-minimal.d.ts.map +1 -0
  21. package/dist/index-minimal.js +2 -0
  22. package/dist/index-minimal.js.map +1 -0
  23. package/dist/index-simple.d.ts +5 -0
  24. package/dist/index-simple.d.ts.map +1 -0
  25. package/dist/index-simple.js +5 -0
  26. package/dist/index-simple.js.map +1 -0
  27. package/dist/index.d.ts +16 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +17 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/plugin-minimal.d.ts +21 -0
  32. package/dist/plugin-minimal.d.ts.map +1 -0
  33. package/dist/plugin-minimal.js +27 -0
  34. package/dist/plugin-minimal.js.map +1 -0
  35. package/dist/plugin-simple.d.ts +28 -0
  36. package/dist/plugin-simple.d.ts.map +1 -0
  37. package/dist/plugin-simple.js +117 -0
  38. package/dist/plugin-simple.js.map +1 -0
  39. package/dist/plugin.d.ts +46 -0
  40. package/dist/plugin.d.ts.map +1 -0
  41. package/dist/plugin.js +66 -0
  42. package/dist/plugin.js.map +1 -0
  43. package/dist/storage/storage.d.ts +56 -0
  44. package/dist/storage/storage.d.ts.map +1 -0
  45. package/dist/storage/storage.js +138 -0
  46. package/dist/storage/storage.js.map +1 -0
  47. package/dist/types.d.ts +73 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +7 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +42 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yusuf
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,256 @@
1
+ # OpenCode Gemini CLI OAuth Plugin
2
+
3
+ Use Gemini models in OpenCode with your **Google account quota** - no API keys needed!
4
+
5
+ This plugin integrates Gemini CLI's OAuth authentication with OpenCode, allowing you to use Google's free Gemini quota without generating API keys.
6
+
7
+ ## ✨ Features
8
+
9
+ - 🔐 **OAuth Authentication** - Reuses Gemini CLI credentials from `~/.gemini/oauth_creds.json`
10
+ - 👥 **Multi-Account Support** - Automatic rotation across multiple Google accounts
11
+ - 🔄 **Auto Token Refresh** - Handles expired tokens seamlessly
12
+ - 🚦 **Rate Limit Handling** - Auto-switches to next account when rate limited
13
+ - 🤝 **Antigravity Compatible** - Works alongside `opencode-antigravity-auth` plugin
14
+ - 🆓 **Free Quota** - Use Google's free tier quota (1,500 requests/day per account)
15
+
16
+ ## 📦 Installation
17
+
18
+ ### Prerequisites
19
+
20
+ 1. **Gemini CLI** must be installed and authenticated:
21
+ ```bash
22
+ npm install -g @google/gemini-cli
23
+ gemini auth login # Authenticate with your Google account
24
+ ```
25
+
26
+ 2. **OpenCode** v1.0.0 or higher
27
+
28
+ ### Install Plugin
29
+
30
+ ```bash
31
+ # Clone repository
32
+ git clone https://github.com/yourusername/opencode-gemini-cli-oauth.git
33
+ cd opencode-gemini-cli-oauth
34
+
35
+ # Install dependencies
36
+ npm install
37
+
38
+ # Build
39
+ npm run build
40
+
41
+ # Install globally
42
+ npm link
43
+ ```
44
+
45
+ ### Configure OpenCode
46
+
47
+ Add to `~/.config/opencode/opencode.json`:
48
+
49
+ ```json
50
+ {
51
+ "plugin": [
52
+ "opencode-antigravity-auth@beta",
53
+ "opencode-gemini-cli-oauth"
54
+ ],
55
+ "provider": {
56
+ "gemini-cli-oauth": {
57
+ "npm": "@ai-sdk/google",
58
+ "models": {
59
+ "gemini-2.5-flash-exp": {
60
+ "name": "Gemini 2.5 Flash (Google Account)",
61
+ "limit": { "context": 1048576, "output": 65536 },
62
+ "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ ## 🚀 Usage
71
+
72
+ ### 1. Authenticate with Gemini CLI
73
+
74
+ ```bash
75
+ # Login with your Google account
76
+ gemini auth login
77
+
78
+ # Verify authentication
79
+ gemini "hello"
80
+ ```
81
+
82
+ ### 2. Launch OpenCode
83
+
84
+ ```bash
85
+ opencode
86
+ ```
87
+
88
+ ### 3. Select Model
89
+
90
+ In OpenCode:
91
+ - Press `Ctrl+P` → "Switch Model"
92
+ - Choose "Gemini 2.5 Flash (Google Account)" or any model with "(Google Account)"
93
+ - Start chatting!
94
+
95
+ ## 🔀 3-Way Authentication Options
96
+
97
+ After installing this plugin, you have **3 authentication methods** for Google models:
98
+
99
+ | Method | Provider | Models | Source | Quota |
100
+ |--------|----------|--------|--------|-------|
101
+ | **1. Google Account (OAuth)** | `gemini-cli-oauth` | Gemini 2.5 Flash/Pro, Gemini 2.0 Flash | This plugin | Google account free tier |
102
+ | **2. API Key** | `google-api-key` | Gemini 1.5/2.5 models | Generated from [AI Studio](https://aistudio.google.com/apikey) | API key quota |
103
+ | **3. Antigravity** | `google` | Gemini 3 Pro/Flash, Claude Sonnet/Opus | [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) | Antigravity quota |
104
+
105
+ All three can coexist without conflicts!
106
+
107
+ ## 👥 Multi-Account Setup
108
+
109
+ To rotate across multiple Google accounts:
110
+
111
+ 1. **Authenticate each account** with Gemini CLI:
112
+ ```bash
113
+ gemini auth login # Login with account 1
114
+ # Complete OAuth flow
115
+
116
+ # Repeat for more accounts (Gemini CLI will store them)
117
+ ```
118
+
119
+ 2. **Plugin automatically rotates** between accounts when:
120
+ - An account hits rate limit (429 error)
121
+ - Distributing requests evenly
122
+
123
+ 3. **Check account pool**:
124
+ ```bash
125
+ cat ~/.gemini/opencode-plugin/account_pool.json
126
+ ```
127
+
128
+ ## 🔧 How It Works
129
+
130
+ 1. **OAuth Token Injection**
131
+ - Plugin reads credentials from `~/.gemini/oauth_creds.json` (created by Gemini CLI)
132
+ - Injects `Authorization: Bearer <access_token>` header to Gemini API requests
133
+ - Removes `?key=` parameter from URLs (OAuth doesn't need it)
134
+
135
+ 2. **Request Interception**
136
+ - Only intercepts requests to `generativelanguage.googleapis.com`
137
+ - Other providers (Antigravity, standard Google) are unaffected
138
+
139
+ 3. **Token Refresh**
140
+ - Monitors token expiry (`expiry_date` field)
141
+ - Auto-refreshes 5 minutes before expiration
142
+ - Uses `refresh_token` to get new `access_token`
143
+
144
+ 4. **Rate Limit Handling**
145
+ - Detects 429 responses
146
+ - Marks account as rate-limited with `retry-after` timestamp
147
+ - Switches to next available account
148
+ - Retries request automatically
149
+
150
+ ## 📊 Quota Management
151
+
152
+ **Free Tier Limits (per Google account):**
153
+ - **Requests:** 1,500 requests/day
154
+ - **Tokens:** 1,000,000 tokens/day
155
+ - **RPM:** 15 requests/minute (Gemini 2.5 Flash)
156
+
157
+ **With 5 accounts, you get:**
158
+ - 7,500 requests/day
159
+ - 5,000,000 tokens/day
160
+ - Automatic rotation on rate limits
161
+
162
+ ## 🐛 Troubleshooting
163
+
164
+ ### "No OAuth credentials found"
165
+
166
+ ```bash
167
+ # Re-authenticate with Gemini CLI
168
+ gemini auth login
169
+ ```
170
+
171
+ ### "Failed to refresh access token"
172
+
173
+ ```bash
174
+ # Clear cached credentials and re-auth
175
+ rm ~/.gemini/oauth_creds.json
176
+ gemini auth login
177
+ ```
178
+
179
+ ### Plugin not loading
180
+
181
+ ```bash
182
+ # Check plugin is installed
183
+ npm list -g opencode-gemini-cli-oauth
184
+
185
+ # Re-link if needed
186
+ cd ~/projects/opencode-gemini-cli-oauth
187
+ npm link
188
+ ```
189
+
190
+ ### Rate limited on all accounts
191
+
192
+ ```bash
193
+ # Check account pool status
194
+ cat ~/.gemini/opencode-plugin/account_pool.json
195
+
196
+ # Clear rate limit timestamps
197
+ echo '{"accounts":[],"currentIndex":0}' > ~/.gemini/opencode-plugin/account_pool.json
198
+
199
+ # Or wait for retry-after period to expire
200
+ ```
201
+
202
+ ## 🔒 Security Notes
203
+
204
+ 1. **OAuth credentials are stored locally** in `~/.gemini/oauth_creds.json`
205
+ 2. **Client ID/Secret are public** (safe for "installed applications" per Google OAuth guidelines)
206
+ 3. **Tokens auto-refresh** - no manual intervention needed
207
+ 4. **No credentials sent to third parties** - direct Google API communication only
208
+
209
+ ## 📚 API Reference
210
+
211
+ ### Exported Classes
212
+
213
+ ```typescript
214
+ import {
215
+ OAuthManager, // OAuth token management
216
+ AccountManager, // Multi-account rotation
217
+ StorageManager, // Credentials storage
218
+ } from 'opencode-gemini-cli-oauth';
219
+
220
+ // Example: Get current access token
221
+ const oauthManager = new OAuthManager();
222
+ const token = await oauthManager.getAccessToken();
223
+
224
+ // Example: Check account pool stats
225
+ const accountManager = new AccountManager();
226
+ const stats = await accountManager.getPoolStats();
227
+ console.log(stats); // { totalAccounts: 5, availableAccounts: 4, rateLimitedAccounts: 1 }
228
+ ```
229
+
230
+ ## 🤝 Contributing
231
+
232
+ Contributions welcome! Please:
233
+
234
+ 1. Fork the repository
235
+ 2. Create a feature branch
236
+ 3. Submit a pull request
237
+
238
+ ## 📄 License
239
+
240
+ MIT License - see LICENSE file for details
241
+
242
+ ## 🙏 Credits
243
+
244
+ - Built by [@yusuf](https://github.com/yourusername)
245
+ - Inspired by [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth)
246
+ - Uses [Google Auth Library](https://github.com/googleapis/google-auth-library-nodejs)
247
+
248
+ ## 🔗 Related Projects
249
+
250
+ - [Gemini CLI](https://github.com/google/generative-ai-cli) - Official Google Gemini CLI
251
+ - [OpenCode](https://opencode.ai) - AI-powered code editor
252
+ - [OpenCode Antigravity Auth](https://github.com/NoeFabris/opencode-antigravity-auth) - Antigravity integration
253
+
254
+ ---
255
+
256
+ **Made with ❤️ for the OpenCode community**
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Yusuf
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import type { AccountPoolEntry } from '../types.js';
7
+ /**
8
+ * Account manager for multi-account rotation and quota management
9
+ */
10
+ export declare class AccountManager {
11
+ private oauthManager;
12
+ constructor();
13
+ /**
14
+ * Initialize account pool from Gemini CLI credentials
15
+ */
16
+ initialize(): Promise<void>;
17
+ /**
18
+ * Get current account from pool
19
+ */
20
+ getCurrentAccount(): Promise<AccountPoolEntry | null>;
21
+ /**
22
+ * Get access token for current account
23
+ */
24
+ getCurrentAccessToken(): Promise<string>;
25
+ /**
26
+ * Rotate to next account in pool
27
+ */
28
+ rotateAccount(): Promise<AccountPoolEntry | null>;
29
+ /**
30
+ * Mark account as rate limited
31
+ */
32
+ markAccountRateLimited(email: string, retryAfterSeconds: number): Promise<void>;
33
+ /**
34
+ * Get next available account (skips rate-limited accounts)
35
+ */
36
+ getNextAvailableAccount(): Promise<AccountPoolEntry | null>;
37
+ /**
38
+ * Increment request count for current account
39
+ */
40
+ incrementRequestCount(): Promise<void>;
41
+ /**
42
+ * Get account pool statistics
43
+ */
44
+ getPoolStats(): Promise<{
45
+ totalAccounts: number;
46
+ availableAccounts: number;
47
+ rateLimitedAccounts: number;
48
+ }>;
49
+ }
50
+ //# sourceMappingURL=account-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-manager.d.ts","sourceRoot":"","sources":["../../src/auth/account-manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAoB,MAAM,aAAa,CAAC;AAEtE;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAe;;IAMnC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiCjC;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAI3D;;OAEG;IACG,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC;IAqC9C;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAIvD;;OAEG;IACG,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrF;;OAEG;IACG,uBAAuB,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAoBjE;;OAEG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAU5C;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC;QAC5B,aAAa,EAAE,MAAM,CAAC;QACtB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;CAcH"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Yusuf
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { StorageManager } from '../storage/storage.js';
7
+ import { OAuthManager } from './oauth.js';
8
+ /**
9
+ * Account manager for multi-account rotation and quota management
10
+ */
11
+ export class AccountManager {
12
+ oauthManager;
13
+ constructor() {
14
+ this.oauthManager = new OAuthManager();
15
+ }
16
+ /**
17
+ * Initialize account pool from Gemini CLI credentials
18
+ */
19
+ async initialize() {
20
+ const credentials = await StorageManager.readOAuthCredentials();
21
+ if (!credentials) {
22
+ console.warn('⚠️ No OAuth credentials found. Run: gemini auth login');
23
+ return;
24
+ }
25
+ // Get user info
26
+ const userInfo = await this.oauthManager.getUserInfo();
27
+ // Check if account already exists in pool
28
+ const pool = await StorageManager.readAccountPool();
29
+ const existingAccount = pool.accounts.find((acc) => acc.email === userInfo.email);
30
+ if (existingAccount) {
31
+ // Update existing account
32
+ await StorageManager.updateAccountInPool(userInfo.email, {
33
+ credentials,
34
+ lastUsed: Date.now(),
35
+ });
36
+ }
37
+ else {
38
+ // Add new account
39
+ const newEntry = {
40
+ email: userInfo.email,
41
+ credentials,
42
+ lastUsed: Date.now(),
43
+ requestCount: 0,
44
+ };
45
+ await StorageManager.upsertAccountInPool(newEntry);
46
+ }
47
+ }
48
+ /**
49
+ * Get current account from pool
50
+ */
51
+ async getCurrentAccount() {
52
+ return await StorageManager.getCurrentAccount();
53
+ }
54
+ /**
55
+ * Get access token for current account
56
+ */
57
+ async getCurrentAccessToken() {
58
+ const account = await this.getCurrentAccount();
59
+ if (!account) {
60
+ // Fallback to default OAuth credentials
61
+ return await this.oauthManager.getAccessToken();
62
+ }
63
+ // Check if token needs refresh
64
+ const now = Date.now();
65
+ const expiryTime = account.credentials.expiry_date || 0;
66
+ const needsRefresh = expiryTime - now < 5 * 60 * 1000; // 5 minutes buffer
67
+ if (needsRefresh) {
68
+ const refreshResult = await this.oauthManager.refreshAccessToken(account.credentials.refresh_token);
69
+ if (refreshResult.success && refreshResult.accessToken) {
70
+ // Update account credentials in pool
71
+ const updatedCredentials = {
72
+ ...account.credentials,
73
+ access_token: refreshResult.accessToken,
74
+ expiry_date: refreshResult.expiryDate || Date.now() + 3600 * 1000,
75
+ };
76
+ await StorageManager.updateAccountInPool(account.email, {
77
+ credentials: updatedCredentials,
78
+ });
79
+ return refreshResult.accessToken;
80
+ }
81
+ }
82
+ return account.credentials.access_token;
83
+ }
84
+ /**
85
+ * Rotate to next account in pool
86
+ */
87
+ async rotateAccount() {
88
+ return await StorageManager.rotateToNextAccount();
89
+ }
90
+ /**
91
+ * Mark account as rate limited
92
+ */
93
+ async markAccountRateLimited(email, retryAfterSeconds) {
94
+ const rateLimitUntil = Date.now() + retryAfterSeconds * 1000;
95
+ await StorageManager.updateAccountInPool(email, { rateLimitUntil });
96
+ }
97
+ /**
98
+ * Get next available account (skips rate-limited accounts)
99
+ */
100
+ async getNextAvailableAccount() {
101
+ const pool = await StorageManager.readAccountPool();
102
+ const now = Date.now();
103
+ // Find first account that is not rate limited
104
+ for (let i = 0; i < pool.accounts.length; i++) {
105
+ const account = pool.accounts[(pool.currentIndex + i) % pool.accounts.length];
106
+ if (!account.rateLimitUntil || account.rateLimitUntil < now) {
107
+ // Update current index
108
+ pool.currentIndex = (pool.currentIndex + i) % pool.accounts.length;
109
+ await StorageManager.writeAccountPool(pool);
110
+ return account;
111
+ }
112
+ }
113
+ // All accounts are rate limited
114
+ return null;
115
+ }
116
+ /**
117
+ * Increment request count for current account
118
+ */
119
+ async incrementRequestCount() {
120
+ const account = await this.getCurrentAccount();
121
+ if (account) {
122
+ await StorageManager.updateAccountInPool(account.email, {
123
+ requestCount: account.requestCount + 1,
124
+ lastUsed: Date.now(),
125
+ });
126
+ }
127
+ }
128
+ /**
129
+ * Get account pool statistics
130
+ */
131
+ async getPoolStats() {
132
+ const pool = await StorageManager.readAccountPool();
133
+ const now = Date.now();
134
+ const rateLimitedCount = pool.accounts.filter((acc) => acc.rateLimitUntil && acc.rateLimitUntil > now).length;
135
+ return {
136
+ totalAccounts: pool.accounts.length,
137
+ availableAccounts: pool.accounts.length - rateLimitedCount,
138
+ rateLimitedAccounts: rateLimitedCount,
139
+ };
140
+ }
141
+ }
142
+ //# sourceMappingURL=account-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-manager.js","sourceRoot":"","sources":["../../src/auth/account-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Yusuf
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { OAuth2Client } from 'google-auth-library';
7
+ import type { TokenRefreshResult } from '../types.js';
8
+ /**
9
+ * OAuth authentication manager
10
+ */
11
+ export declare class OAuthManager {
12
+ private client;
13
+ constructor();
14
+ /**
15
+ * Get access token from stored credentials
16
+ * Automatically refreshes if expired or near expiry
17
+ */
18
+ getAccessToken(): Promise<string>;
19
+ /**
20
+ * Refresh access token using refresh token
21
+ */
22
+ refreshAccessToken(refreshToken: string, retryCount?: number): Promise<TokenRefreshResult>;
23
+ /**
24
+ * Verify if credentials are valid
25
+ */
26
+ verifyCredentials(): Promise<boolean>;
27
+ /**
28
+ * Get user info from token
29
+ */
30
+ getUserInfo(): Promise<{
31
+ email: string;
32
+ name?: string;
33
+ picture?: string;
34
+ }>;
35
+ /**
36
+ * Create OAuth2Client with credentials
37
+ */
38
+ createAuthenticatedClient(): Promise<OAuth2Client>;
39
+ }
40
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AASnD,OAAO,KAAK,EAAoB,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAExE;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAe;;IAS7B;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IA6BvC;;OAEG;IACG,kBAAkB,CACtB,YAAY,EAAE,MAAM,EACpB,UAAU,SAAI,GACb,OAAO,CAAC,kBAAkB,CAAC;IA0C9B;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;IAU3C;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAWhF;;OAEG;IACG,yBAAyB,IAAI,OAAO,CAAC,YAAY,CAAC;CAqBzD"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Yusuf
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { OAuth2Client } from 'google-auth-library';
7
+ import { OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, TOKEN_EXPIRY_BUFFER_MS, MAX_REFRESH_RETRIES, } from '../constants.js';
8
+ import { StorageManager } from '../storage/storage.js';
9
+ /**
10
+ * OAuth authentication manager
11
+ */
12
+ export class OAuthManager {
13
+ client;
14
+ constructor() {
15
+ this.client = new OAuth2Client({
16
+ clientId: OAUTH_CLIENT_ID,
17
+ clientSecret: OAUTH_CLIENT_SECRET,
18
+ });
19
+ }
20
+ /**
21
+ * Get access token from stored credentials
22
+ * Automatically refreshes if expired or near expiry
23
+ */
24
+ async getAccessToken() {
25
+ const credentials = await StorageManager.readOAuthCredentials();
26
+ if (!credentials) {
27
+ throw new Error('No OAuth credentials found. Please authenticate using Gemini CLI: gemini auth login');
28
+ }
29
+ // Check if token needs refresh
30
+ const now = Date.now();
31
+ const expiryTime = credentials.expiry_date || 0;
32
+ const needsRefresh = expiryTime - now < TOKEN_EXPIRY_BUFFER_MS;
33
+ if (needsRefresh) {
34
+ const refreshResult = await this.refreshAccessToken(credentials.refresh_token);
35
+ if (!refreshResult.success) {
36
+ throw new Error(`Failed to refresh access token: ${refreshResult.error}. Please re-authenticate using: gemini auth login`);
37
+ }
38
+ return refreshResult.accessToken;
39
+ }
40
+ return credentials.access_token;
41
+ }
42
+ /**
43
+ * Refresh access token using refresh token
44
+ */
45
+ async refreshAccessToken(refreshToken, retryCount = 0) {
46
+ try {
47
+ this.client.setCredentials({ refresh_token: refreshToken });
48
+ const { credentials } = await this.client.refreshAccessToken();
49
+ if (!credentials.access_token) {
50
+ throw new Error('No access token returned from refresh');
51
+ }
52
+ // Update stored credentials
53
+ const existingCreds = await StorageManager.readOAuthCredentials();
54
+ const updatedCreds = {
55
+ ...existingCreds,
56
+ access_token: credentials.access_token,
57
+ expiry_date: credentials.expiry_date || Date.now() + 3600 * 1000,
58
+ token_type: credentials.token_type || 'Bearer',
59
+ };
60
+ await StorageManager.writeOAuthCredentials(updatedCreds);
61
+ return {
62
+ success: true,
63
+ accessToken: credentials.access_token,
64
+ expiryDate: credentials.expiry_date || undefined,
65
+ };
66
+ }
67
+ catch (error) {
68
+ const errorMessage = error instanceof Error ? error.message : String(error);
69
+ // Retry on transient errors
70
+ if (retryCount < MAX_REFRESH_RETRIES) {
71
+ await new Promise((resolve) => setTimeout(resolve, 1000 * (retryCount + 1)));
72
+ return this.refreshAccessToken(refreshToken, retryCount + 1);
73
+ }
74
+ return {
75
+ success: false,
76
+ error: errorMessage,
77
+ };
78
+ }
79
+ }
80
+ /**
81
+ * Verify if credentials are valid
82
+ */
83
+ async verifyCredentials() {
84
+ try {
85
+ const token = await this.getAccessToken();
86
+ const tokenInfo = await this.client.getTokenInfo(token);
87
+ return !!tokenInfo.email;
88
+ }
89
+ catch (error) {
90
+ return false;
91
+ }
92
+ }
93
+ /**
94
+ * Get user info from token
95
+ */
96
+ async getUserInfo() {
97
+ const token = await this.getAccessToken();
98
+ const tokenInfo = await this.client.getTokenInfo(token);
99
+ return {
100
+ email: tokenInfo.email || 'unknown',
101
+ name: undefined,
102
+ picture: undefined,
103
+ };
104
+ }
105
+ /**
106
+ * Create OAuth2Client with credentials
107
+ */
108
+ async createAuthenticatedClient() {
109
+ const credentials = await StorageManager.readOAuthCredentials();
110
+ if (!credentials) {
111
+ throw new Error('No OAuth credentials found');
112
+ }
113
+ const client = new OAuth2Client({
114
+ clientId: OAUTH_CLIENT_ID,
115
+ clientSecret: OAUTH_CLIENT_SECRET,
116
+ });
117
+ client.setCredentials({
118
+ access_token: credentials.access_token,
119
+ refresh_token: credentials.refresh_token,
120
+ expiry_date: credentials.expiry_date,
121
+ token_type: credentials.token_type,
122
+ });
123
+ return client;
124
+ }
125
+ }
126
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Yusuf
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { AccountManager } from '../auth/account-manager.js';
7
+ /**
8
+ * Create authenticated fetch function with OAuth token injection
9
+ */
10
+ export declare function createAuthenticatedFetch(accountManager: AccountManager): typeof fetch;
11
+ /**
12
+ * Create request interceptor for OpenCode plugin
13
+ */
14
+ export declare function createRequestInterceptor(): Promise<{
15
+ fetch: typeof fetch;
16
+ accountManager: AccountManager;
17
+ }>;
18
+ //# sourceMappingURL=request-interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-interceptor.d.ts","sourceRoot":"","sources":["../../src/auth/request-interceptor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AA0B5D;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,cAAc,GAAG,OAAO,KAAK,CA6DrF;AAED;;GAEG;AACH,wBAAsB,wBAAwB;;;GAkB7C"}