antigravity-claude-proxy 1.2.16 → 2.0.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.
Files changed (50) hide show
  1. package/README.md +165 -62
  2. package/package.json +16 -2
  3. package/public/app.js +198 -0
  4. package/public/css/src/input.css +491 -0
  5. package/public/css/style.css +1 -0
  6. package/public/favicon.svg +10 -0
  7. package/public/index.html +333 -0
  8. package/public/js/app-init.js +137 -0
  9. package/public/js/components/account-manager.js +211 -0
  10. package/public/js/components/claude-config.js +145 -0
  11. package/public/js/components/dashboard/charts.js +536 -0
  12. package/public/js/components/dashboard/filters.js +362 -0
  13. package/public/js/components/dashboard/stats.js +82 -0
  14. package/public/js/components/dashboard.js +220 -0
  15. package/public/js/components/logs-viewer.js +100 -0
  16. package/public/js/components/model-manager.js +47 -0
  17. package/public/js/components/models.js +36 -0
  18. package/public/js/components/server-config.js +252 -0
  19. package/public/js/config/constants.js +82 -0
  20. package/public/js/data-store.js +238 -0
  21. package/public/js/settings-store.js +58 -0
  22. package/public/js/store.js +549 -0
  23. package/public/js/utils/account-actions.js +199 -0
  24. package/public/js/utils/error-handler.js +145 -0
  25. package/public/js/utils/model-config.js +42 -0
  26. package/public/js/utils/validators.js +168 -0
  27. package/public/js/utils.js +69 -0
  28. package/public/views/accounts.html +269 -0
  29. package/public/views/dashboard.html +457 -0
  30. package/public/views/logs.html +97 -0
  31. package/public/views/models.html +271 -0
  32. package/public/views/settings.html +1023 -0
  33. package/src/account-manager/index.js +12 -0
  34. package/src/account-manager/rate-limits.js +3 -0
  35. package/src/account-manager/selection.js +3 -0
  36. package/src/account-manager/storage.js +10 -2
  37. package/src/auth/oauth.js +3 -2
  38. package/src/cloudcode/index.js +4 -3
  39. package/src/cloudcode/message-handler.js +10 -0
  40. package/src/cloudcode/model-api.js +72 -0
  41. package/src/cloudcode/sse-streamer.js +4 -4
  42. package/src/cloudcode/streaming-handler.js +11 -0
  43. package/src/config.js +86 -0
  44. package/src/constants.js +17 -9
  45. package/src/modules/usage-stats.js +205 -0
  46. package/src/server.js +109 -31
  47. package/src/utils/claude-config.js +111 -0
  48. package/src/utils/logger.js +43 -12
  49. package/src/utils/retry.js +161 -0
  50. package/src/webui/index.js +598 -0
package/README.md CHANGED
@@ -59,75 +59,54 @@ npm start
59
59
 
60
60
  ## Quick Start
61
61
 
62
- ### 1. Add Account(s)
63
-
64
- You have two options:
65
-
66
- **Option A: Use Antigravity (Single Account)**
67
-
68
- If you have Antigravity installed and logged in, the proxy will automatically extract your token. No additional setup needed.
69
-
70
- **Option B: Add Google Accounts via OAuth (Recommended for Multi-Account)**
71
-
72
- Add one or more Google accounts for load balancing.
73
-
74
- #### Desktop/Laptop (with browser)
62
+ ### 1. Start the Proxy Server
75
63
 
76
64
  ```bash
77
65
  # If installed via npm
78
- antigravity-claude-proxy accounts add
66
+ antigravity-claude-proxy start
79
67
 
80
68
  # If using npx
81
- npx antigravity-claude-proxy@latest accounts add
69
+ npx antigravity-claude-proxy@latest start
82
70
 
83
71
  # If cloned locally
84
- npm run accounts:add
72
+ npm start
85
73
  ```
86
74
 
87
- This opens your browser for Google OAuth. Sign in and authorize access. Repeat for multiple accounts.
75
+ The server runs on `http://localhost:8080` by default.
88
76
 
89
- #### Headless Server (Docker, SSH, no desktop)
77
+ ### 2. Link Account(s)
90
78
 
91
- ```bash
92
- # If installed via npm
93
- antigravity-claude-proxy accounts add --no-browser
79
+ Choose one of the following methods to authorize the proxy:
94
80
 
95
- # If using npx
96
- npx antigravity-claude-proxy@latest accounts add -- --no-browser
81
+ #### **Method A: Web Dashboard (Recommended)**
97
82
 
98
- # If cloned locally
99
- npm run accounts:add -- --no-browser
100
- ```
83
+ 1. With the proxy running, open `http://localhost:8080` in your browser.
84
+ 2. Navigate to the **Accounts** tab and click **Add Account**.
85
+ 3. Complete the Google OAuth authorization in the popup window.
101
86
 
102
- This displays an OAuth URL you can open on another device (phone/laptop). After signing in, copy the redirect URL or authorization code and paste it back into the terminal.
87
+ #### **Method B: CLI (Desktop or Headless)**
103
88
 
104
- #### Manage accounts
89
+ If you prefer the terminal or are on a remote server:
105
90
 
106
91
  ```bash
107
- # List all accounts
108
- antigravity-claude-proxy accounts list
109
-
110
- # Verify accounts are working
111
- antigravity-claude-proxy accounts verify
92
+ # Desktop (opens browser)
93
+ antigravity-claude-proxy accounts add
112
94
 
113
- # Interactive account management
114
- antigravity-claude-proxy accounts
95
+ # Headless (Docker/SSH)
96
+ antigravity-claude-proxy accounts add --no-browser
115
97
  ```
116
98
 
117
- ### 2. Start the Proxy Server
99
+ > For full CLI account management options, run `antigravity-claude-proxy accounts --help`.
118
100
 
119
- ```bash
120
- # If installed via npm
121
- antigravity-claude-proxy start
101
+ #### **Method C: Automatic (Antigravity Users)**
122
102
 
123
- # If using npx
124
- npx antigravity-claude-proxy@latest start
103
+ If you have the **Antigravity** app installed and logged in, the proxy will automatically detect your local session. No additional setup is required.
125
104
 
126
- # If cloned locally
127
- npm start
128
- ```
105
+ To use a custom port:
129
106
 
130
- The server runs on `http://localhost:8080` by default.
107
+ ```bash
108
+ PORT=3001 antigravity-claude-proxy start
109
+ ```
131
110
 
132
111
  ### 3. Verify It's Working
133
112
 
@@ -145,6 +124,18 @@ curl "http://localhost:8080/account-limits?format=table"
145
124
 
146
125
  ### Configure Claude Code
147
126
 
127
+ You can configure these settings in two ways:
128
+
129
+ #### **Via Web Console (Recommended)**
130
+
131
+ 1. Open the WebUI at `http://localhost:8080`.
132
+ 2. Go to **Settings** → **Claude CLI**.
133
+ 3. Select your preferred models and click **Apply to Claude CLI**.
134
+
135
+ > [!TIP] > **Configuration Precedence**: System environment variables (set in shell profile like `.zshrc`) take precedence over the `settings.json` file. If you use the Web Console to manage settings, ensure you haven't manually exported conflicting variables in your terminal.
136
+
137
+ #### **Manual Configuration**
138
+
148
139
  Create or edit the Claude Code settings file:
149
140
 
150
141
  **macOS:** `~/.claude/settings.json`
@@ -261,18 +252,18 @@ Then run `claude` for official API or `claude-antigravity` for this proxy.
261
252
 
262
253
  ### Claude Models
263
254
 
264
- | Model ID | Description |
265
- |----------|-------------|
255
+ | Model ID | Description |
256
+ | ---------------------------- | ---------------------------------------- |
266
257
  | `claude-sonnet-4-5-thinking` | Claude Sonnet 4.5 with extended thinking |
267
- | `claude-opus-4-5-thinking` | Claude Opus 4.5 with extended thinking |
268
- | `claude-sonnet-4-5` | Claude Sonnet 4.5 without thinking |
258
+ | `claude-opus-4-5-thinking` | Claude Opus 4.5 with extended thinking |
259
+ | `claude-sonnet-4-5` | Claude Sonnet 4.5 without thinking |
269
260
 
270
261
  ### Gemini Models
271
262
 
272
- | Model ID | Description |
273
- |----------|-------------|
274
- | `gemini-3-flash` | Gemini 3 Flash with thinking |
275
- | `gemini-3-pro-low` | Gemini 3 Pro Low with thinking |
263
+ | Model ID | Description |
264
+ | ------------------- | ------------------------------- |
265
+ | `gemini-3-flash` | Gemini 3 Flash with thinking |
266
+ | `gemini-3-pro-low` | Gemini 3 Pro Low with thinking |
276
267
  | `gemini-3-pro-high` | Gemini 3 Pro High with thinking |
277
268
 
278
269
  Gemini models include full thinking support with `thoughtSignature` handling for multi-turn conversations.
@@ -289,23 +280,74 @@ When you add multiple accounts, the proxy automatically:
289
280
  - **Invalid account detection**: Accounts needing re-authentication are marked and skipped
290
281
  - **Prompt caching support**: Stable session IDs enable cache hits across conversation turns
291
282
 
292
- Check account status anytime:
283
+ Check account status, subscription tiers, and quota anytime:
293
284
 
294
285
  ```bash
286
+ # Web UI: http://localhost:8080/ (Accounts tab - shows tier badges and quota progress)
287
+ # CLI Table:
295
288
  curl "http://localhost:8080/account-limits?format=table"
296
289
  ```
297
290
 
291
+ #### CLI Management Reference
292
+
293
+ If you prefer using the terminal for management:
294
+
295
+ ```bash
296
+ # List all accounts
297
+ antigravity-claude-proxy accounts list
298
+
299
+ # Verify account health
300
+ antigravity-claude-proxy accounts verify
301
+
302
+ # Interactive CLI menu
303
+ antigravity-claude-proxy accounts
304
+ ```
305
+
306
+ ---
307
+
308
+ ## Web Management Console
309
+
310
+ The proxy includes a built-in, modern web interface for real-time monitoring and configuration. Access the console at: `http://localhost:8080` (default port).
311
+
312
+ ![Antigravity Console](images/webui-dashboard.png)
313
+
314
+ ### Key Features
315
+
316
+ - **Real-time Dashboard**: Monitor request volume, active accounts, model health, and subscription tier distribution.
317
+ - **Visual Model Quota**: Track per-model usage and next reset times with color-coded progress indicators.
318
+ - **Account Management**: Add/remove Google accounts via OAuth, view subscription tiers (Free/Pro/Ultra) and quota status at a glance.
319
+ - **Claude CLI Configuration**: Edit your `~/.claude/settings.json` directly from the browser.
320
+ - **Live Logs**: Stream server logs with level-based filtering and search.
321
+ - **Advanced Tuning**: Configure retries, timeouts, and debug mode on the fly.
322
+ - **Bilingual Interface**: Full support for English and Chinese (switch via Settings).
323
+
324
+ ---
325
+
326
+ ## Advanced Configuration
327
+
328
+ While most users can use the default settings, you can tune the proxy behavior via the **Settings → Server** tab in the WebUI or by creating a `config.json` file.
329
+
330
+ ### Configurable Options
331
+
332
+ - **WebUI Password**: Secure your dashboard with `WEBUI_PASSWORD` env var or in config.
333
+ - **Custom Port**: Change the default `8080` port.
334
+ - **Retry Logic**: Configure `maxRetries`, `retryBaseMs`, and `retryMaxMs`.
335
+ - **Load Balancing**: Adjust `defaultCooldownMs` and `maxWaitBeforeErrorMs`.
336
+ - **Persistence**: Enable `persistTokenCache` to save OAuth sessions across restarts.
337
+
338
+ Refer to `config.example.json` for a complete list of fields and documentation.
339
+
298
340
  ---
299
341
 
300
342
  ## API Endpoints
301
343
 
302
- | Endpoint | Method | Description |
303
- |----------|--------|-------------|
304
- | `/health` | GET | Health check |
305
- | `/account-limits` | GET | Account status and quota limits (add `?format=table` for ASCII table) |
306
- | `/v1/messages` | POST | Anthropic Messages API |
307
- | `/v1/models` | GET | List available models |
308
- | `/refresh-token` | POST | Force token refresh |
344
+ | Endpoint | Method | Description |
345
+ | ----------------- | ------ | --------------------------------------------------------------------- |
346
+ | `/health` | GET | Health check |
347
+ | `/account-limits` | GET | Account status and quota limits (add `?format=table` for ASCII table) |
348
+ | `/v1/messages` | POST | Anthropic Messages API |
349
+ | `/v1/models` | GET | List available models |
350
+ | `/refresh-token` | POST | Force token refresh |
309
351
 
310
352
  ---
311
353
 
@@ -339,6 +381,7 @@ npm run test:caching # Prompt caching
339
381
  ### "Could not extract token from Antigravity"
340
382
 
341
383
  If using single-account mode with Antigravity:
384
+
342
385
  1. Make sure Antigravity app is installed and running
343
386
  2. Ensure you're logged in to Antigravity
344
387
 
@@ -347,11 +390,13 @@ Or add accounts via OAuth instead: `antigravity-claude-proxy accounts add`
347
390
  ### 401 Authentication Errors
348
391
 
349
392
  The token might have expired. Try:
393
+
350
394
  ```bash
351
395
  curl -X POST http://localhost:8080/refresh-token
352
396
  ```
353
397
 
354
398
  Or re-authenticate the account:
399
+
355
400
  ```bash
356
401
  antigravity-claude-proxy accounts
357
402
  ```
@@ -363,6 +408,7 @@ With multiple accounts, the proxy automatically switches to the next available a
363
408
  ### Account Shows as "Invalid"
364
409
 
365
410
  Re-authenticate the account:
411
+
366
412
  ```bash
367
413
  antigravity-claude-proxy accounts
368
414
  # Choose "Re-authenticate" for the invalid account
@@ -412,6 +458,63 @@ By using this software, you acknowledge and accept the following:
412
458
 
413
459
  ---
414
460
 
461
+ ## Development
462
+
463
+ ### For Developers & Contributors
464
+
465
+ This project uses a local Tailwind CSS build system. CSS is pre-compiled and included in the repository, so you can run the project immediately after cloning.
466
+
467
+ #### Quick Start
468
+
469
+ ```bash
470
+ git clone https://github.com/badri-s2001/antigravity-claude-proxy.git
471
+ cd antigravity-claude-proxy
472
+ npm install # Automatically builds CSS via prepare hook
473
+ npm start # Start server (no rebuild needed)
474
+ ```
475
+
476
+ #### Frontend Development
477
+
478
+ If you need to modify styles in `public/css/src/input.css`:
479
+
480
+ ```bash
481
+ # Option 1: Build once
482
+ npm run build:css
483
+
484
+ # Option 2: Watch for changes (auto-rebuild)
485
+ npm run watch:css
486
+
487
+ # Option 3: Watch both CSS and server (recommended)
488
+ npm run dev:full
489
+ ```
490
+
491
+ **File Structure:**
492
+ - `public/css/src/input.css` - Source CSS with Tailwind `@apply` directives (edit this)
493
+ - `public/css/style.css` - Compiled & minified CSS (auto-generated, don't edit)
494
+ - `tailwind.config.js` - Tailwind configuration
495
+ - `postcss.config.js` - PostCSS configuration
496
+
497
+ #### Backend-Only Development
498
+
499
+ If you're only working on backend code and don't need frontend dev tools:
500
+
501
+ ```bash
502
+ npm install --production # Skip devDependencies (saves ~20MB)
503
+ npm start
504
+ ```
505
+
506
+ **Note:** Pre-compiled CSS is committed to the repository, so you don't need to rebuild unless modifying styles.
507
+
508
+ #### Project Structure
509
+
510
+ See [CLAUDE.md](./CLAUDE.md) for detailed architecture documentation, including:
511
+ - Request flow and module organization
512
+ - Frontend architecture (Alpine.js + Tailwind)
513
+ - Service layer patterns (`ErrorHandler.withLoading`, `AccountActions`)
514
+ - Dashboard module documentation
515
+
516
+ ---
517
+
415
518
  ## Credits
416
519
 
417
520
  This project is based on insights and code from:
@@ -429,4 +532,4 @@ MIT
429
532
 
430
533
  ## Star History
431
534
 
432
- [![Star History Chart](https://api.star-history.com/svg?repos=badrisnarayanan/antigravity-claude-proxy&type=date&legend=top-left&cache-control=no-cache)](https://www.star-history.com/#badrisnarayanan/antigravity-claude-proxy&type=date&legend=top-left)
535
+ [![Star History Chart](https://api.star-history.com/svg?repos=badrisnarayanan/antigravity-claude-proxy&type=date&legend=top-left&cache-control=no-cache)](https://www.star-history.com/#badrisnarayanan/antigravity-claude-proxy&type=date&legend=top-left)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antigravity-claude-proxy",
3
- "version": "1.2.16",
3
+ "version": "2.0.1",
4
4
  "description": "Proxy server to use Antigravity's Claude models with Claude Code CLI",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -9,11 +9,16 @@
9
9
  },
10
10
  "files": [
11
11
  "src",
12
- "bin"
12
+ "bin",
13
+ "public"
13
14
  ],
14
15
  "scripts": {
16
+ "build:css": "tailwindcss -i ./public/css/src/input.css -o ./public/css/style.css --minify",
17
+ "watch:css": "tailwindcss -i ./public/css/src/input.css -o ./public/css/style.css --watch",
18
+ "prepare": "npm run build:css",
15
19
  "start": "node src/index.js",
16
20
  "dev": "node --watch src/index.js",
21
+ "dev:full": "concurrently \"npm run watch:css\" \"npm run dev\"",
17
22
  "accounts": "node src/cli/accounts.js",
18
23
  "accounts:add": "node src/cli/accounts.js add",
19
24
  "accounts:list": "node src/cli/accounts.js list",
@@ -52,8 +57,17 @@
52
57
  "node": ">=18.0.0"
53
58
  },
54
59
  "dependencies": {
60
+ "async-mutex": "^0.5.0",
55
61
  "better-sqlite3": "^12.5.0",
56
62
  "cors": "^2.8.5",
57
63
  "express": "^4.18.2"
64
+ },
65
+ "devDependencies": {
66
+ "@tailwindcss/forms": "^0.5.7",
67
+ "autoprefixer": "^10.4.16",
68
+ "concurrently": "^8.2.2",
69
+ "daisyui": "^4.12.14",
70
+ "postcss": "^8.4.32",
71
+ "tailwindcss": "^3.4.0"
58
72
  }
59
73
  }
package/public/app.js ADDED
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Antigravity Console - Main Entry
3
+ *
4
+ * This file orchestrates Alpine.js initialization.
5
+ * Components are loaded via separate script files that register themselves
6
+ * to window.Components before this script runs.
7
+ */
8
+
9
+ document.addEventListener('alpine:init', () => {
10
+ // Register Components (loaded from separate files via window.Components)
11
+ Alpine.data('dashboard', window.Components.dashboard);
12
+ Alpine.data('models', window.Components.models);
13
+ Alpine.data('accountManager', window.Components.accountManager);
14
+ Alpine.data('claudeConfig', window.Components.claudeConfig);
15
+ Alpine.data('logsViewer', window.Components.logsViewer);
16
+
17
+ // View Loader Directive
18
+ Alpine.directive('load-view', (el, { expression }, { evaluate }) => {
19
+ if (!window.viewCache) window.viewCache = new Map();
20
+
21
+ // Evaluate the expression to get the actual view name (removes quotes)
22
+ const viewName = evaluate(expression);
23
+
24
+ if (window.viewCache.has(viewName)) {
25
+ el.innerHTML = window.viewCache.get(viewName);
26
+ Alpine.initTree(el);
27
+ return;
28
+ }
29
+
30
+ fetch(`views/${viewName}.html?t=${Date.now()}`)
31
+ .then(response => {
32
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
33
+ return response.text();
34
+ })
35
+ .then(html => {
36
+ // Update cache (optional, or remove if we want always-fresh)
37
+ // keeping cache for session performance, but initial load will now bypass browser cache
38
+ window.viewCache.set(viewName, html);
39
+ el.innerHTML = html;
40
+ Alpine.initTree(el);
41
+ })
42
+ .catch(err => {
43
+ console.error('Failed to load view:', viewName, err);
44
+ el.innerHTML = `<div class="p-4 border border-red-500/50 bg-red-500/10 rounded-lg text-red-400 font-mono text-sm">
45
+ Error loading view: ${viewName}<br>
46
+ <span class="text-xs opacity-75">${err.message}</span>
47
+ </div>`;
48
+ });
49
+ });
50
+
51
+ // Main App Controller
52
+ Alpine.data('app', () => ({
53
+ get connectionStatus() {
54
+ return Alpine.store('data')?.connectionStatus || 'connecting';
55
+ },
56
+ get loading() {
57
+ return Alpine.store('data')?.loading || false;
58
+ },
59
+
60
+ init() {
61
+ console.log('App controller initialized');
62
+
63
+ // Theme setup
64
+ document.documentElement.setAttribute('data-theme', 'black');
65
+ document.documentElement.classList.add('dark');
66
+
67
+ // Chart Defaults
68
+ if (typeof Chart !== 'undefined') {
69
+ Chart.defaults.color = window.utils.getThemeColor('--color-text-dim');
70
+ Chart.defaults.borderColor = window.utils.getThemeColor('--color-space-border');
71
+ Chart.defaults.font.family = '"JetBrains Mono", monospace';
72
+ }
73
+
74
+ // Start Data Polling
75
+ this.startAutoRefresh();
76
+ document.addEventListener('refresh-interval-changed', () => this.startAutoRefresh());
77
+
78
+ // Initial Fetch
79
+ Alpine.store('data').fetchData();
80
+ },
81
+
82
+ refreshTimer: null,
83
+
84
+ fetchData() {
85
+ Alpine.store('data').fetchData();
86
+ },
87
+
88
+ startAutoRefresh() {
89
+ if (this.refreshTimer) clearInterval(this.refreshTimer);
90
+ const interval = parseInt(Alpine.store('settings')?.refreshInterval || 60);
91
+ if (interval > 0) {
92
+ this.refreshTimer = setInterval(() => Alpine.store('data').fetchData(), interval * 1000);
93
+ }
94
+ },
95
+
96
+ t(key) {
97
+ return Alpine.store('global')?.t(key) || key;
98
+ },
99
+
100
+ async addAccountWeb(reAuthEmail = null) {
101
+ const password = Alpine.store('global').webuiPassword;
102
+ try {
103
+ const urlPath = reAuthEmail
104
+ ? `/api/auth/url?email=${encodeURIComponent(reAuthEmail)}`
105
+ : '/api/auth/url';
106
+
107
+ const { response, newPassword } = await window.utils.request(urlPath, {}, password);
108
+ if (newPassword) Alpine.store('global').webuiPassword = newPassword;
109
+
110
+ const data = await response.json();
111
+
112
+ if (data.status === 'ok') {
113
+ // Show info toast that OAuth is in progress
114
+ Alpine.store('global').showToast(Alpine.store('global').t('oauthInProgress'), 'info');
115
+
116
+ // Open OAuth window
117
+ const oauthWindow = window.open(data.url, 'google_oauth', 'width=600,height=700,scrollbars=yes');
118
+
119
+ // Poll for account changes instead of relying on postMessage
120
+ // (since OAuth callback is now on port 51121, not this server)
121
+ const initialAccountCount = Alpine.store('data').accounts.length;
122
+ let pollCount = 0;
123
+ const maxPolls = 60; // 2 minutes (2 second intervals)
124
+ let cancelled = false;
125
+
126
+ // Show progress modal
127
+ Alpine.store('global').oauthProgress = {
128
+ active: true,
129
+ current: 0,
130
+ max: maxPolls,
131
+ cancel: () => {
132
+ cancelled = true;
133
+ clearInterval(pollInterval);
134
+ Alpine.store('global').oauthProgress.active = false;
135
+ Alpine.store('global').showToast(Alpine.store('global').t('oauthCancelled'), 'info');
136
+ if (oauthWindow && !oauthWindow.closed) {
137
+ oauthWindow.close();
138
+ }
139
+ }
140
+ };
141
+
142
+ const pollInterval = setInterval(async () => {
143
+ if (cancelled) {
144
+ clearInterval(pollInterval);
145
+ return;
146
+ }
147
+
148
+ pollCount++;
149
+ Alpine.store('global').oauthProgress.current = pollCount;
150
+
151
+ // Check if OAuth window was closed manually
152
+ if (oauthWindow && oauthWindow.closed && !cancelled) {
153
+ clearInterval(pollInterval);
154
+ Alpine.store('global').oauthProgress.active = false;
155
+ Alpine.store('global').showToast(Alpine.store('global').t('oauthWindowClosed'), 'warning');
156
+ return;
157
+ }
158
+
159
+ // Refresh account list
160
+ await Alpine.store('data').fetchData();
161
+
162
+ // Check if new account was added
163
+ const currentAccountCount = Alpine.store('data').accounts.length;
164
+ if (currentAccountCount > initialAccountCount) {
165
+ clearInterval(pollInterval);
166
+ Alpine.store('global').oauthProgress.active = false;
167
+
168
+ const actionKey = reAuthEmail ? 'accountReauthSuccess' : 'accountAddedSuccess';
169
+ Alpine.store('global').showToast(
170
+ Alpine.store('global').t(actionKey),
171
+ 'success'
172
+ );
173
+ document.getElementById('add_account_modal')?.close();
174
+
175
+ if (oauthWindow && !oauthWindow.closed) {
176
+ oauthWindow.close();
177
+ }
178
+ }
179
+
180
+ // Stop polling after max attempts
181
+ if (pollCount >= maxPolls) {
182
+ clearInterval(pollInterval);
183
+ Alpine.store('global').oauthProgress.active = false;
184
+ Alpine.store('global').showToast(
185
+ Alpine.store('global').t('oauthTimeout'),
186
+ 'warning'
187
+ );
188
+ }
189
+ }, 2000); // Poll every 2 seconds
190
+ } else {
191
+ Alpine.store('global').showToast(data.error || Alpine.store('global').t('failedToGetAuthUrl'), 'error');
192
+ }
193
+ } catch (e) {
194
+ Alpine.store('global').showToast(Alpine.store('global').t('failedToStartOAuth') + ': ' + e.message, 'error');
195
+ }
196
+ }
197
+ }));
198
+ });