commons-proxy 2.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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +757 -0
  3. package/bin/cli.js +146 -0
  4. package/package.json +97 -0
  5. package/public/Complaint Details.pdf +0 -0
  6. package/public/Cyber Crime Portal.pdf +0 -0
  7. package/public/app.js +229 -0
  8. package/public/css/src/input.css +523 -0
  9. package/public/css/style.css +1 -0
  10. package/public/favicon.png +0 -0
  11. package/public/index.html +549 -0
  12. package/public/js/components/account-manager.js +356 -0
  13. package/public/js/components/add-account-modal.js +414 -0
  14. package/public/js/components/claude-config.js +420 -0
  15. package/public/js/components/dashboard/charts.js +605 -0
  16. package/public/js/components/dashboard/filters.js +362 -0
  17. package/public/js/components/dashboard/stats.js +110 -0
  18. package/public/js/components/dashboard.js +236 -0
  19. package/public/js/components/logs-viewer.js +100 -0
  20. package/public/js/components/models.js +36 -0
  21. package/public/js/components/server-config.js +349 -0
  22. package/public/js/config/constants.js +102 -0
  23. package/public/js/data-store.js +375 -0
  24. package/public/js/settings-store.js +58 -0
  25. package/public/js/store.js +99 -0
  26. package/public/js/translations/en.js +367 -0
  27. package/public/js/translations/id.js +412 -0
  28. package/public/js/translations/pt.js +308 -0
  29. package/public/js/translations/tr.js +358 -0
  30. package/public/js/translations/zh.js +373 -0
  31. package/public/js/utils/account-actions.js +189 -0
  32. package/public/js/utils/error-handler.js +96 -0
  33. package/public/js/utils/model-config.js +42 -0
  34. package/public/js/utils/ui-logger.js +143 -0
  35. package/public/js/utils/validators.js +77 -0
  36. package/public/js/utils.js +69 -0
  37. package/public/proxy-server-64.png +0 -0
  38. package/public/views/accounts.html +361 -0
  39. package/public/views/dashboard.html +484 -0
  40. package/public/views/logs.html +97 -0
  41. package/public/views/models.html +331 -0
  42. package/public/views/settings.html +1327 -0
  43. package/src/account-manager/credentials.js +378 -0
  44. package/src/account-manager/index.js +462 -0
  45. package/src/account-manager/onboarding.js +112 -0
  46. package/src/account-manager/rate-limits.js +369 -0
  47. package/src/account-manager/storage.js +160 -0
  48. package/src/account-manager/strategies/base-strategy.js +109 -0
  49. package/src/account-manager/strategies/hybrid-strategy.js +339 -0
  50. package/src/account-manager/strategies/index.js +79 -0
  51. package/src/account-manager/strategies/round-robin-strategy.js +76 -0
  52. package/src/account-manager/strategies/sticky-strategy.js +138 -0
  53. package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
  54. package/src/account-manager/strategies/trackers/index.js +9 -0
  55. package/src/account-manager/strategies/trackers/quota-tracker.js +120 -0
  56. package/src/account-manager/strategies/trackers/token-bucket-tracker.js +155 -0
  57. package/src/auth/database.js +169 -0
  58. package/src/auth/oauth.js +548 -0
  59. package/src/auth/token-extractor.js +117 -0
  60. package/src/cli/accounts.js +648 -0
  61. package/src/cloudcode/index.js +29 -0
  62. package/src/cloudcode/message-handler.js +510 -0
  63. package/src/cloudcode/model-api.js +248 -0
  64. package/src/cloudcode/rate-limit-parser.js +235 -0
  65. package/src/cloudcode/request-builder.js +93 -0
  66. package/src/cloudcode/session-manager.js +47 -0
  67. package/src/cloudcode/sse-parser.js +121 -0
  68. package/src/cloudcode/sse-streamer.js +293 -0
  69. package/src/cloudcode/streaming-handler.js +615 -0
  70. package/src/config.js +125 -0
  71. package/src/constants.js +407 -0
  72. package/src/errors.js +242 -0
  73. package/src/fallback-config.js +29 -0
  74. package/src/format/content-converter.js +193 -0
  75. package/src/format/index.js +20 -0
  76. package/src/format/request-converter.js +255 -0
  77. package/src/format/response-converter.js +120 -0
  78. package/src/format/schema-sanitizer.js +673 -0
  79. package/src/format/signature-cache.js +88 -0
  80. package/src/format/thinking-utils.js +648 -0
  81. package/src/index.js +148 -0
  82. package/src/modules/usage-stats.js +205 -0
  83. package/src/providers/anthropic-provider.js +258 -0
  84. package/src/providers/base-provider.js +157 -0
  85. package/src/providers/cloudcode.js +94 -0
  86. package/src/providers/copilot.js +399 -0
  87. package/src/providers/github-provider.js +287 -0
  88. package/src/providers/google-provider.js +192 -0
  89. package/src/providers/index.js +211 -0
  90. package/src/providers/openai-compatible.js +265 -0
  91. package/src/providers/openai-provider.js +271 -0
  92. package/src/providers/openrouter-provider.js +325 -0
  93. package/src/providers/setup.js +83 -0
  94. package/src/server.js +870 -0
  95. package/src/utils/claude-config.js +245 -0
  96. package/src/utils/helpers.js +51 -0
  97. package/src/utils/logger.js +142 -0
  98. package/src/utils/native-module-helper.js +162 -0
  99. package/src/webui/index.js +1134 -0
package/bin/cli.js ADDED
@@ -0,0 +1,146 @@
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
+ CommonsProxy v${packageJson.version}
21
+
22
+ Universal proxy server for Claude Code CLI - supports multiple AI providers.
23
+
24
+ USAGE:
25
+ commons-proxy <command> [options]
26
+
27
+ COMMANDS:
28
+ start Start the proxy server (default port: 8080)
29
+ accounts Manage accounts (interactive)
30
+ accounts add Add a new account (prompts for provider)
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
+
36
+ OPTIONS:
37
+ --help, -h Show this help message
38
+ --version, -v Show version number
39
+ --strategy=<type> Account selection strategy (sticky|round-robin|hybrid)
40
+ --fallback Enable model fallback when quota exhausted
41
+ --debug Enable debug logging
42
+ --no-browser Use manual code input for OAuth (headless servers)
43
+
44
+ ENVIRONMENT:
45
+ PORT Server port (default: 8080)
46
+ WEBUI_PASSWORD Password protect WebUI (optional)
47
+ STRATEGY Account selection strategy (default: hybrid)
48
+ FALLBACK Enable model fallback (true|false)
49
+ DEBUG Enable debug logging (true|false)
50
+
51
+ PROVIDERS:
52
+ 🔵 Google Cloud Code OAuth 2.0 flow for Claude & Gemini models
53
+ 🟠 Anthropic Direct API access via API key
54
+ 🟢 OpenAI GPT models via API key (supports Azure)
55
+ 🟣 GitHub Models GitHub marketplace access via Personal Access Token
56
+ 🟧 GitHub Copilot Device authorization flow for Copilot models
57
+ 🟪 OpenRouter Unified API for 100+ models via API key
58
+
59
+ EXAMPLES:
60
+ # Start server with default settings
61
+ commons-proxy start
62
+
63
+ # Start with custom port and strategy
64
+ PORT=3000 commons-proxy start --strategy=round-robin
65
+
66
+ # Start with model fallback enabled
67
+ commons-proxy start --fallback --debug
68
+
69
+ # Add accounts from different providers
70
+ commons-proxy accounts add # Interactive (prompts for provider)
71
+ commons-proxy accounts add --no-browser # Manual OAuth code input
72
+
73
+ # List and verify accounts
74
+ commons-proxy accounts list
75
+ commons-proxy accounts verify
76
+
77
+ CONFIGURATION:
78
+ Claude Code CLI (~/.claude/settings.json):
79
+ {
80
+ "env": {
81
+ "ANTHROPIC_BASE_URL": "http://localhost:8080"
82
+ }
83
+ }
84
+
85
+ Web Interface:
86
+ http://localhost:8080 - Manage accounts, view stats, configure settings
87
+
88
+ DOCUMENTATION:
89
+ Getting Started: docs/PROVIDERS.md - Setup guides for all providers
90
+ Contributing: CONTRIBUTING.md - Developer onboarding guide
91
+ GitHub: https://github.com/AryanVBW/CommonsProxy
92
+ npm: https://www.npmjs.com/package/commons-proxy
93
+ `);
94
+ }
95
+
96
+ function showVersion() {
97
+ console.log(packageJson.version);
98
+ }
99
+
100
+ async function main() {
101
+ // Handle flags
102
+ if (args.includes('--help') || args.includes('-h')) {
103
+ showHelp();
104
+ process.exit(0);
105
+ }
106
+
107
+ if (args.includes('--version') || args.includes('-v')) {
108
+ showVersion();
109
+ process.exit(0);
110
+ }
111
+
112
+ // Handle commands
113
+ switch (command) {
114
+ case 'start':
115
+ case undefined:
116
+ // Default to starting the server
117
+ await import('../src/index.js');
118
+ break;
119
+
120
+ case 'accounts': {
121
+ // Pass remaining args to accounts CLI
122
+ const subCommand = args[1] || 'add';
123
+ process.argv = ['node', 'accounts-cli.js', subCommand, ...args.slice(2)];
124
+ await import('../src/cli/accounts.js');
125
+ break;
126
+ }
127
+
128
+ case 'help':
129
+ showHelp();
130
+ break;
131
+
132
+ case 'version':
133
+ showVersion();
134
+ break;
135
+
136
+ default:
137
+ console.error(`Unknown command: ${command}`);
138
+ console.error('Run "commons-proxy --help" for usage information.');
139
+ process.exit(1);
140
+ }
141
+ }
142
+
143
+ main().catch((err) => {
144
+ console.error('Error:', err.message);
145
+ process.exit(1);
146
+ });
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "commons-proxy",
3
+ "version": "2.0.0",
4
+ "description": "Universal AI proxy server with multi-provider support (Google Cloud Code, Anthropic, OpenAI, GitHub Models, GitHub Copilot, OpenRouter) - Anthropic-compatible API for Claude Code CLI",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "commons-proxy": "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
+ "prepare-release": "npm run build:css && npm test",
20
+ "start": "node src/index.js",
21
+ "dev": "node --watch src/index.js",
22
+ "dev:full": "concurrently \"npm run watch:css\" \"npm run dev\"",
23
+ "accounts": "node src/cli/accounts.js",
24
+ "accounts:add": "node src/cli/accounts.js add",
25
+ "accounts:list": "node src/cli/accounts.js list",
26
+ "accounts:remove": "node src/cli/accounts.js remove",
27
+ "accounts:verify": "node src/cli/accounts.js verify",
28
+ "test": "node tests/run-all.cjs",
29
+ "test:signatures": "node tests/test-thinking-signatures.cjs",
30
+ "test:multiturn": "node tests/test-multiturn-thinking-tools.cjs",
31
+ "test:streaming": "node tests/test-multiturn-thinking-tools-streaming.cjs",
32
+ "test:interleaved": "node tests/test-interleaved-thinking.cjs",
33
+ "test:images": "node tests/test-images.cjs",
34
+ "test:caching": "node tests/test-caching-streaming.cjs",
35
+ "test:crossmodel": "node tests/test-cross-model-thinking.cjs",
36
+ "test:oauth": "node tests/test-oauth-no-browser.cjs",
37
+ "test:emptyretry": "node tests/test-empty-response-retry.cjs",
38
+ "test:sanitizer": "node tests/test-schema-sanitizer.cjs",
39
+ "test:strategies": "node tests/test-strategies.cjs",
40
+ "test:cache-control": "node tests/test-cache-control.cjs"
41
+ },
42
+ "keywords": [
43
+ "claude",
44
+ "anthropic",
45
+ "openai",
46
+ "github-models",
47
+ "github-copilot",
48
+ "openrouter",
49
+ "commons",
50
+ "ai-proxy",
51
+ "ai-gateway",
52
+ "multi-provider",
53
+ "llm-proxy",
54
+ "llm",
55
+ "copilot",
56
+ "proxy",
57
+ "vertex-ai",
58
+ "gemini",
59
+ "gpt",
60
+ "load-balancer",
61
+ "api-gateway"
62
+ ],
63
+ "author": "Badri Narayanan S <badrinarayanans@gmail.com>",
64
+ "contributors": [
65
+ "AryanVBW"
66
+ ],
67
+ "license": "MIT",
68
+ "repository": {
69
+ "type": "git",
70
+ "url": "git+https://github.com/AryanVBW/CommonsProxy.git"
71
+ },
72
+ "homepage": "https://github.com/AryanVBW/CommonsProxy#readme",
73
+ "bugs": {
74
+ "url": "https://github.com/AryanVBW/CommonsProxy/issues"
75
+ },
76
+ "engines": {
77
+ "node": ">=18.0.0"
78
+ },
79
+ "publishConfig": {
80
+ "access": "public",
81
+ "registry": "https://registry.npmjs.org/"
82
+ },
83
+ "dependencies": {
84
+ "async-mutex": "^0.5.0",
85
+ "better-sqlite3": "^12.5.0",
86
+ "cors": "^2.8.5",
87
+ "express": "^4.18.2"
88
+ },
89
+ "devDependencies": {
90
+ "@tailwindcss/forms": "^0.5.7",
91
+ "autoprefixer": "^10.4.16",
92
+ "concurrently": "^8.2.2",
93
+ "daisyui": "^4.12.14",
94
+ "postcss": "^8.4.32",
95
+ "tailwindcss": "^3.4.0"
96
+ }
97
+ }
Binary file
Binary file
package/public/app.js ADDED
@@ -0,0 +1,229 @@
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
+ Alpine.data('addAccountModal', window.Components.addAccountModal);
17
+
18
+ // View Loader Directive
19
+ Alpine.directive('load-view', (el, { expression }, { evaluate }) => {
20
+ if (!window.viewCache) window.viewCache = new Map();
21
+
22
+ // Evaluate the expression to get the actual view name (removes quotes)
23
+ const viewName = evaluate(expression);
24
+
25
+ if (window.viewCache.has(viewName)) {
26
+ el.innerHTML = window.viewCache.get(viewName);
27
+ Alpine.initTree(el);
28
+ return;
29
+ }
30
+
31
+ fetch(`views/${viewName}.html?t=${Date.now()}`)
32
+ .then(response => {
33
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
34
+ return response.text();
35
+ })
36
+ .then(html => {
37
+ // Update cache (optional, or remove if we want always-fresh)
38
+ // keeping cache for session performance, but initial load will now bypass browser cache
39
+ window.viewCache.set(viewName, html);
40
+ el.innerHTML = html;
41
+ Alpine.initTree(el);
42
+ })
43
+ .catch(err => {
44
+ console.error('Failed to load view:', viewName, err);
45
+ el.innerHTML = `<div class="p-4 border border-red-500/50 bg-red-500/10 rounded-lg text-red-400 font-mono text-sm">
46
+ Error loading view: ${viewName}<br>
47
+ <span class="text-xs opacity-75">${err.message}</span>
48
+ </div>`;
49
+ });
50
+ });
51
+
52
+ // Main App Controller
53
+ Alpine.data('app', () => ({
54
+ get connectionStatus() {
55
+ return Alpine.store('data')?.connectionStatus || 'connecting';
56
+ },
57
+ get loading() {
58
+ return Alpine.store('data')?.loading || false;
59
+ },
60
+
61
+ sidebarOpen: window.innerWidth >= 1024,
62
+ toggleSidebar() {
63
+ this.sidebarOpen = !this.sidebarOpen;
64
+ },
65
+
66
+ init() {
67
+ console.log('App controller initialized');
68
+
69
+ // Handle responsive sidebar transitions
70
+ let lastWidth = window.innerWidth;
71
+ let resizeTimeout = null;
72
+
73
+ window.addEventListener('resize', () => {
74
+ if (resizeTimeout) clearTimeout(resizeTimeout);
75
+
76
+ resizeTimeout = setTimeout(() => {
77
+ const currentWidth = window.innerWidth;
78
+ const lgBreakpoint = 1024;
79
+
80
+ // Desktop -> Mobile: Auto-close sidebar to prevent overlay blocking screen
81
+ if (lastWidth >= lgBreakpoint && currentWidth < lgBreakpoint) {
82
+ this.sidebarOpen = false;
83
+ }
84
+
85
+ // Mobile -> Desktop: Auto-open sidebar (restore standard desktop layout)
86
+ if (lastWidth < lgBreakpoint && currentWidth >= lgBreakpoint) {
87
+ this.sidebarOpen = true;
88
+ }
89
+
90
+ lastWidth = currentWidth;
91
+ }, 150);
92
+ });
93
+
94
+ // Theme setup
95
+ document.documentElement.setAttribute('data-theme', 'black');
96
+ document.documentElement.classList.add('dark');
97
+
98
+ // Chart Defaults
99
+ if (typeof Chart !== 'undefined') {
100
+ Chart.defaults.color = window.utils.getThemeColor('--color-text-dim');
101
+ Chart.defaults.borderColor = window.utils.getThemeColor('--color-space-border');
102
+ Chart.defaults.font.family = '"JetBrains Mono", monospace';
103
+ }
104
+
105
+ // Start Data Polling
106
+ this.startAutoRefresh();
107
+ document.addEventListener('refresh-interval-changed', () => this.startAutoRefresh());
108
+
109
+ // Initial Fetch
110
+ Alpine.store('data').fetchData();
111
+ },
112
+
113
+ refreshTimer: null,
114
+
115
+ fetchData() {
116
+ Alpine.store('data').fetchData();
117
+ },
118
+
119
+ startAutoRefresh() {
120
+ if (this.refreshTimer) clearInterval(this.refreshTimer);
121
+ const interval = parseInt(Alpine.store('settings')?.refreshInterval || 60);
122
+ if (interval > 0) {
123
+ this.refreshTimer = setInterval(() => Alpine.store('data').fetchData(), interval * 1000);
124
+ }
125
+ },
126
+
127
+ t(key) {
128
+ return Alpine.store('global')?.t(key) || key;
129
+ },
130
+
131
+ async addAccountWeb(reAuthEmail = null) {
132
+ const password = Alpine.store('global').webuiPassword;
133
+ try {
134
+ const urlPath = reAuthEmail
135
+ ? `/api/auth/url?email=${encodeURIComponent(reAuthEmail)}`
136
+ : '/api/auth/url';
137
+
138
+ const { response, newPassword } = await window.utils.request(urlPath, {}, password);
139
+ if (newPassword) Alpine.store('global').webuiPassword = newPassword;
140
+
141
+ const data = await response.json();
142
+
143
+ if (data.status === 'ok') {
144
+ // Show info toast that OAuth is in progress
145
+ Alpine.store('global').showToast(Alpine.store('global').t('oauthInProgress'), 'info');
146
+
147
+ // Open OAuth window
148
+ const oauthWindow = window.open(data.url, 'google_oauth', 'width=600,height=700,scrollbars=yes');
149
+
150
+ // Poll for account changes instead of relying on postMessage
151
+ // (since OAuth callback is now on port 51121, not this server)
152
+ const initialAccountCount = Alpine.store('data').accounts.length;
153
+ let pollCount = 0;
154
+ const maxPolls = 60; // 2 minutes (2 second intervals)
155
+ let cancelled = false;
156
+
157
+ // Show progress modal
158
+ Alpine.store('global').oauthProgress = {
159
+ active: true,
160
+ current: 0,
161
+ max: maxPolls,
162
+ cancel: () => {
163
+ cancelled = true;
164
+ clearInterval(pollInterval);
165
+ Alpine.store('global').oauthProgress.active = false;
166
+ Alpine.store('global').showToast(Alpine.store('global').t('oauthCancelled'), 'info');
167
+ if (oauthWindow && !oauthWindow.closed) {
168
+ oauthWindow.close();
169
+ }
170
+ }
171
+ };
172
+
173
+ const pollInterval = setInterval(async () => {
174
+ if (cancelled) {
175
+ clearInterval(pollInterval);
176
+ return;
177
+ }
178
+
179
+ pollCount++;
180
+ Alpine.store('global').oauthProgress.current = pollCount;
181
+
182
+ // Check if OAuth window was closed manually
183
+ if (oauthWindow && oauthWindow.closed && !cancelled) {
184
+ clearInterval(pollInterval);
185
+ Alpine.store('global').oauthProgress.active = false;
186
+ Alpine.store('global').showToast(Alpine.store('global').t('oauthWindowClosed'), 'warning');
187
+ return;
188
+ }
189
+
190
+ // Refresh account list
191
+ await Alpine.store('data').fetchData();
192
+
193
+ // Check if new account was added
194
+ const currentAccountCount = Alpine.store('data').accounts.length;
195
+ if (currentAccountCount > initialAccountCount) {
196
+ clearInterval(pollInterval);
197
+ Alpine.store('global').oauthProgress.active = false;
198
+
199
+ const actionKey = reAuthEmail ? 'accountReauthSuccess' : 'accountAddedSuccess';
200
+ Alpine.store('global').showToast(
201
+ Alpine.store('global').t(actionKey),
202
+ 'success'
203
+ );
204
+ document.getElementById('add_account_modal')?.close();
205
+
206
+ if (oauthWindow && !oauthWindow.closed) {
207
+ oauthWindow.close();
208
+ }
209
+ }
210
+
211
+ // Stop polling after max attempts
212
+ if (pollCount >= maxPolls) {
213
+ clearInterval(pollInterval);
214
+ Alpine.store('global').oauthProgress.active = false;
215
+ Alpine.store('global').showToast(
216
+ Alpine.store('global').t('oauthTimeout'),
217
+ 'warning'
218
+ );
219
+ }
220
+ }, 2000); // Poll every 2 seconds
221
+ } else {
222
+ Alpine.store('global').showToast(data.error || Alpine.store('global').t('failedToGetAuthUrl'), 'error');
223
+ }
224
+ } catch (e) {
225
+ Alpine.store('global').showToast(Alpine.store('global').t('failedToStartOAuth') + ': ' + e.message, 'error');
226
+ }
227
+ }
228
+ }));
229
+ });