@kamel-ahmed/proxy-claude 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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +622 -0
  3. package/bin/cli.js +124 -0
  4. package/package.json +80 -0
  5. package/public/app.js +228 -0
  6. package/public/css/src/input.css +523 -0
  7. package/public/css/style.css +1 -0
  8. package/public/favicon.svg +10 -0
  9. package/public/index.html +381 -0
  10. package/public/js/components/account-manager.js +245 -0
  11. package/public/js/components/claude-config.js +420 -0
  12. package/public/js/components/dashboard/charts.js +589 -0
  13. package/public/js/components/dashboard/filters.js +362 -0
  14. package/public/js/components/dashboard/stats.js +110 -0
  15. package/public/js/components/dashboard.js +236 -0
  16. package/public/js/components/logs-viewer.js +100 -0
  17. package/public/js/components/models.js +36 -0
  18. package/public/js/components/server-config.js +349 -0
  19. package/public/js/config/constants.js +102 -0
  20. package/public/js/data-store.js +386 -0
  21. package/public/js/settings-store.js +58 -0
  22. package/public/js/store.js +78 -0
  23. package/public/js/translations/en.js +351 -0
  24. package/public/js/translations/id.js +396 -0
  25. package/public/js/translations/pt.js +287 -0
  26. package/public/js/translations/tr.js +342 -0
  27. package/public/js/translations/zh.js +357 -0
  28. package/public/js/utils/account-actions.js +189 -0
  29. package/public/js/utils/error-handler.js +96 -0
  30. package/public/js/utils/model-config.js +42 -0
  31. package/public/js/utils/validators.js +77 -0
  32. package/public/js/utils.js +69 -0
  33. package/public/views/accounts.html +329 -0
  34. package/public/views/dashboard.html +484 -0
  35. package/public/views/logs.html +97 -0
  36. package/public/views/models.html +331 -0
  37. package/public/views/settings.html +1329 -0
  38. package/src/account-manager/credentials.js +243 -0
  39. package/src/account-manager/index.js +380 -0
  40. package/src/account-manager/onboarding.js +117 -0
  41. package/src/account-manager/rate-limits.js +237 -0
  42. package/src/account-manager/storage.js +136 -0
  43. package/src/account-manager/strategies/base-strategy.js +104 -0
  44. package/src/account-manager/strategies/hybrid-strategy.js +195 -0
  45. package/src/account-manager/strategies/index.js +79 -0
  46. package/src/account-manager/strategies/round-robin-strategy.js +76 -0
  47. package/src/account-manager/strategies/sticky-strategy.js +138 -0
  48. package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
  49. package/src/account-manager/strategies/trackers/index.js +8 -0
  50. package/src/account-manager/strategies/trackers/token-bucket-tracker.js +121 -0
  51. package/src/auth/database.js +169 -0
  52. package/src/auth/oauth.js +419 -0
  53. package/src/auth/token-extractor.js +117 -0
  54. package/src/cli/accounts.js +512 -0
  55. package/src/cli/refresh.js +201 -0
  56. package/src/cli/setup.js +338 -0
  57. package/src/cloudcode/index.js +29 -0
  58. package/src/cloudcode/message-handler.js +386 -0
  59. package/src/cloudcode/model-api.js +248 -0
  60. package/src/cloudcode/rate-limit-parser.js +181 -0
  61. package/src/cloudcode/request-builder.js +93 -0
  62. package/src/cloudcode/session-manager.js +47 -0
  63. package/src/cloudcode/sse-parser.js +121 -0
  64. package/src/cloudcode/sse-streamer.js +293 -0
  65. package/src/cloudcode/streaming-handler.js +492 -0
  66. package/src/config.js +107 -0
  67. package/src/constants.js +278 -0
  68. package/src/errors.js +238 -0
  69. package/src/fallback-config.js +29 -0
  70. package/src/format/content-converter.js +193 -0
  71. package/src/format/index.js +20 -0
  72. package/src/format/request-converter.js +248 -0
  73. package/src/format/response-converter.js +120 -0
  74. package/src/format/schema-sanitizer.js +673 -0
  75. package/src/format/signature-cache.js +88 -0
  76. package/src/format/thinking-utils.js +558 -0
  77. package/src/index.js +146 -0
  78. package/src/modules/usage-stats.js +205 -0
  79. package/src/server.js +861 -0
  80. package/src/utils/claude-config.js +245 -0
  81. package/src/utils/helpers.js +51 -0
  82. package/src/utils/logger.js +142 -0
  83. package/src/utils/native-module-helper.js +162 -0
  84. package/src/webui/index.js +707 -0
package/bin/cli.js ADDED
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+ import { readFileSync } from 'fs';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ // Read package.json for version
11
+ const packageJson = JSON.parse(
12
+ readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
13
+ );
14
+
15
+ const args = process.argv.slice(2);
16
+ const command = args[0];
17
+
18
+ function showHelp() {
19
+ console.log(`
20
+ antigravity-claude-proxy v${packageJson.version}
21
+
22
+ Proxy server for using Antigravity's Claude models with Claude Code CLI.
23
+
24
+ USAGE:
25
+ antigravity-claude-proxy <command> [options]
26
+
27
+ COMMANDS:
28
+ start Start the proxy server (default port: 8080)
29
+ accounts Manage Google accounts (interactive)
30
+ accounts add Add a new Google account via OAuth
31
+ accounts list List all configured accounts
32
+ accounts remove Remove accounts interactively
33
+ accounts verify Verify account tokens are valid
34
+ accounts clear Remove all accounts
35
+ refresh Check and refresh account tokens
36
+ setup Install Claude Code CLI and create global 'proxy-claude' command
37
+
38
+ OPTIONS:
39
+ --help, -h Show this help message
40
+ --version, -v Show version number
41
+
42
+ ENVIRONMENT:
43
+ PORT Server port (default: 8080)
44
+
45
+ EXAMPLES:
46
+ antigravity-claude-proxy start
47
+ PORT=3000 antigravity-claude-proxy start
48
+ antigravity-claude-proxy accounts add
49
+ antigravity-claude-proxy accounts list
50
+
51
+ CONFIGURATION:
52
+ Claude Code CLI (~/.claude/settings.json):
53
+ {
54
+ "env": {
55
+ "ANTHROPIC_BASE_URL": "http://localhost:8080"
56
+ }
57
+ }
58
+ `);
59
+ }
60
+
61
+ function showVersion() {
62
+ console.log(packageJson.version);
63
+ }
64
+
65
+ async function main() {
66
+ // Handle flags
67
+ if (args.includes('--help') || args.includes('-h')) {
68
+ showHelp();
69
+ process.exit(0);
70
+ }
71
+
72
+ if (args.includes('--version') || args.includes('-v')) {
73
+ showVersion();
74
+ process.exit(0);
75
+ }
76
+
77
+ // Handle commands
78
+ switch (command) {
79
+ case 'setup':
80
+ await import('../src/cli/setup.js').then(module => module.runSetup());
81
+ break;
82
+
83
+ case 'start':
84
+ case undefined:
85
+ // Default to starting the server
86
+ await import('../src/index.js');
87
+ break;
88
+
89
+ case 'accounts': {
90
+ // Pass remaining args to accounts CLI
91
+ const subCommand = args[1] || 'add';
92
+ process.argv = ['node', 'accounts-cli.js', subCommand, ...args.slice(2)];
93
+ await import('../src/cli/accounts.js');
94
+ break;
95
+ }
96
+
97
+ case 'refresh': {
98
+ // Token refresh
99
+ const force = args.includes('--force') || args.includes('-f');
100
+ const quiet = args.includes('--quiet') || args.includes('-q');
101
+ const { runRefresh } = await import('../src/cli/refresh.js');
102
+ await runRefresh({ force, quiet });
103
+ break;
104
+ }
105
+
106
+ case 'help':
107
+ showHelp();
108
+ break;
109
+
110
+ case 'version':
111
+ showVersion();
112
+ break;
113
+
114
+ default:
115
+ console.error(`Unknown command: ${command}`);
116
+ console.error('Run "antigravity-proxy --help" for usage information.');
117
+ process.exit(1);
118
+ }
119
+ }
120
+
121
+ main().catch((err) => {
122
+ console.error('Error:', err.message);
123
+ process.exit(1);
124
+ });
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@kamel-ahmed/proxy-claude",
3
+ "version": "1.0.0",
4
+ "description": "Proxy server to use Antigravity's Claude models with Claude Code CLI - run 'proxy-claude' to start",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "proxy-claude": "bin/cli.js"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "bin",
13
+ "public"
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",
19
+ "start": "node src/index.js",
20
+ "dev": "node --watch src/index.js",
21
+ "dev:full": "concurrently \"npm run watch:css\" \"npm run dev\"",
22
+ "accounts": "node src/cli/accounts.js",
23
+ "accounts:add": "node src/cli/accounts.js add",
24
+ "accounts:list": "node src/cli/accounts.js list",
25
+ "accounts:remove": "node src/cli/accounts.js remove",
26
+ "accounts:verify": "node src/cli/accounts.js verify",
27
+ "refresh": "node src/cli/refresh.js",
28
+ "refresh:force": "node src/cli/refresh.js --force",
29
+ "setup": "node bin/cli.js setup",
30
+ "test": "node tests/run-all.cjs",
31
+ "test:signatures": "node tests/test-thinking-signatures.cjs",
32
+ "test:multiturn": "node tests/test-multiturn-thinking-tools.cjs",
33
+ "test:streaming": "node tests/test-multiturn-thinking-tools-streaming.cjs",
34
+ "test:interleaved": "node tests/test-interleaved-thinking.cjs",
35
+ "test:images": "node tests/test-images.cjs",
36
+ "test:caching": "node tests/test-caching-streaming.cjs",
37
+ "test:crossmodel": "node tests/test-cross-model-thinking.cjs",
38
+ "test:oauth": "node tests/test-oauth-no-browser.cjs",
39
+ "test:emptyretry": "node tests/test-empty-response-retry.cjs",
40
+ "test:sanitizer": "node tests/test-schema-sanitizer.cjs",
41
+ "test:strategies": "node tests/test-strategies.cjs"
42
+ },
43
+ "keywords": [
44
+ "claude",
45
+ "anthropic",
46
+ "antigravity",
47
+ "proxy",
48
+ "vertex-ai"
49
+ ],
50
+ "author": "Kamel Ahmed",
51
+ "license": "MIT",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/kamel-ahmed/proxy-claude.git"
55
+ },
56
+ "homepage": "https://github.com/kamel-ahmed/proxy-claude#readme",
57
+ "bugs": {
58
+ "url": "https://github.com/kamel-ahmed/proxy-claude/issues"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ },
63
+ "engines": {
64
+ "node": ">=18.0.0"
65
+ },
66
+ "dependencies": {
67
+ "async-mutex": "^0.5.0",
68
+ "better-sqlite3": "^12.5.0",
69
+ "cors": "^2.8.5",
70
+ "express": "^4.18.2"
71
+ },
72
+ "devDependencies": {
73
+ "@tailwindcss/forms": "^0.5.7",
74
+ "autoprefixer": "^10.4.16",
75
+ "concurrently": "^8.2.2",
76
+ "daisyui": "^4.12.14",
77
+ "postcss": "^8.4.32",
78
+ "tailwindcss": "^3.4.0"
79
+ }
80
+ }
package/public/app.js ADDED
@@ -0,0 +1,228 @@
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
+ sidebarOpen: window.innerWidth >= 1024,
61
+ toggleSidebar() {
62
+ this.sidebarOpen = !this.sidebarOpen;
63
+ },
64
+
65
+ init() {
66
+ console.log('App controller initialized');
67
+
68
+ // Handle responsive sidebar transitions
69
+ let lastWidth = window.innerWidth;
70
+ let resizeTimeout = null;
71
+
72
+ window.addEventListener('resize', () => {
73
+ if (resizeTimeout) clearTimeout(resizeTimeout);
74
+
75
+ resizeTimeout = setTimeout(() => {
76
+ const currentWidth = window.innerWidth;
77
+ const lgBreakpoint = 1024;
78
+
79
+ // Desktop -> Mobile: Auto-close sidebar to prevent overlay blocking screen
80
+ if (lastWidth >= lgBreakpoint && currentWidth < lgBreakpoint) {
81
+ this.sidebarOpen = false;
82
+ }
83
+
84
+ // Mobile -> Desktop: Auto-open sidebar (restore standard desktop layout)
85
+ if (lastWidth < lgBreakpoint && currentWidth >= lgBreakpoint) {
86
+ this.sidebarOpen = true;
87
+ }
88
+
89
+ lastWidth = currentWidth;
90
+ }, 150);
91
+ });
92
+
93
+ // Theme setup
94
+ document.documentElement.setAttribute('data-theme', 'black');
95
+ document.documentElement.classList.add('dark');
96
+
97
+ // Chart Defaults
98
+ if (typeof Chart !== 'undefined') {
99
+ Chart.defaults.color = window.utils.getThemeColor('--color-text-dim');
100
+ Chart.defaults.borderColor = window.utils.getThemeColor('--color-space-border');
101
+ Chart.defaults.font.family = '"JetBrains Mono", monospace';
102
+ }
103
+
104
+ // Start Data Polling
105
+ this.startAutoRefresh();
106
+ document.addEventListener('refresh-interval-changed', () => this.startAutoRefresh());
107
+
108
+ // Initial Fetch
109
+ Alpine.store('data').fetchData();
110
+ },
111
+
112
+ refreshTimer: null,
113
+
114
+ fetchData() {
115
+ Alpine.store('data').fetchData();
116
+ },
117
+
118
+ startAutoRefresh() {
119
+ if (this.refreshTimer) clearInterval(this.refreshTimer);
120
+ const interval = parseInt(Alpine.store('settings')?.refreshInterval || 60);
121
+ if (interval > 0) {
122
+ this.refreshTimer = setInterval(() => Alpine.store('data').fetchData(), interval * 1000);
123
+ }
124
+ },
125
+
126
+ t(key) {
127
+ return Alpine.store('global')?.t(key) || key;
128
+ },
129
+
130
+ async addAccountWeb(reAuthEmail = null) {
131
+ const password = Alpine.store('global').webuiPassword;
132
+ try {
133
+ const urlPath = reAuthEmail
134
+ ? `/api/auth/url?email=${encodeURIComponent(reAuthEmail)}`
135
+ : '/api/auth/url';
136
+
137
+ const { response, newPassword } = await window.utils.request(urlPath, {}, password);
138
+ if (newPassword) Alpine.store('global').webuiPassword = newPassword;
139
+
140
+ const data = await response.json();
141
+
142
+ if (data.status === 'ok') {
143
+ // Show info toast that OAuth is in progress
144
+ Alpine.store('global').showToast(Alpine.store('global').t('oauthInProgress'), 'info');
145
+
146
+ // Open OAuth window
147
+ const oauthWindow = window.open(data.url, 'google_oauth', 'width=600,height=700,scrollbars=yes');
148
+
149
+ // Poll for account changes instead of relying on postMessage
150
+ // (since OAuth callback is now on port 51121, not this server)
151
+ const initialAccountCount = Alpine.store('data').accounts.length;
152
+ let pollCount = 0;
153
+ const maxPolls = 60; // 2 minutes (2 second intervals)
154
+ let cancelled = false;
155
+
156
+ // Show progress modal
157
+ Alpine.store('global').oauthProgress = {
158
+ active: true,
159
+ current: 0,
160
+ max: maxPolls,
161
+ cancel: () => {
162
+ cancelled = true;
163
+ clearInterval(pollInterval);
164
+ Alpine.store('global').oauthProgress.active = false;
165
+ Alpine.store('global').showToast(Alpine.store('global').t('oauthCancelled'), 'info');
166
+ if (oauthWindow && !oauthWindow.closed) {
167
+ oauthWindow.close();
168
+ }
169
+ }
170
+ };
171
+
172
+ const pollInterval = setInterval(async () => {
173
+ if (cancelled) {
174
+ clearInterval(pollInterval);
175
+ return;
176
+ }
177
+
178
+ pollCount++;
179
+ Alpine.store('global').oauthProgress.current = pollCount;
180
+
181
+ // Check if OAuth window was closed manually
182
+ if (oauthWindow && oauthWindow.closed && !cancelled) {
183
+ clearInterval(pollInterval);
184
+ Alpine.store('global').oauthProgress.active = false;
185
+ Alpine.store('global').showToast(Alpine.store('global').t('oauthWindowClosed'), 'warning');
186
+ return;
187
+ }
188
+
189
+ // Refresh account list
190
+ await Alpine.store('data').fetchData();
191
+
192
+ // Check if new account was added
193
+ const currentAccountCount = Alpine.store('data').accounts.length;
194
+ if (currentAccountCount > initialAccountCount) {
195
+ clearInterval(pollInterval);
196
+ Alpine.store('global').oauthProgress.active = false;
197
+
198
+ const actionKey = reAuthEmail ? 'accountReauthSuccess' : 'accountAddedSuccess';
199
+ Alpine.store('global').showToast(
200
+ Alpine.store('global').t(actionKey),
201
+ 'success'
202
+ );
203
+ document.getElementById('add_account_modal')?.close();
204
+
205
+ if (oauthWindow && !oauthWindow.closed) {
206
+ oauthWindow.close();
207
+ }
208
+ }
209
+
210
+ // Stop polling after max attempts
211
+ if (pollCount >= maxPolls) {
212
+ clearInterval(pollInterval);
213
+ Alpine.store('global').oauthProgress.active = false;
214
+ Alpine.store('global').showToast(
215
+ Alpine.store('global').t('oauthTimeout'),
216
+ 'warning'
217
+ );
218
+ }
219
+ }, 2000); // Poll every 2 seconds
220
+ } else {
221
+ Alpine.store('global').showToast(data.error || Alpine.store('global').t('failedToGetAuthUrl'), 'error');
222
+ }
223
+ } catch (e) {
224
+ Alpine.store('global').showToast(Alpine.store('global').t('failedToStartOAuth') + ': ' + e.message, 'error');
225
+ }
226
+ }
227
+ }));
228
+ });