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
@@ -0,0 +1,420 @@
1
+ /**
2
+ * Claude Config Component
3
+ * Registers itself to window.Components for Alpine.js to consume
4
+ */
5
+ window.Components = window.Components || {};
6
+
7
+ window.Components.claudeConfig = () => ({
8
+ config: { env: {} },
9
+ configPath: '', // Dynamic path from backend
10
+ models: [],
11
+ loading: false,
12
+ restoring: false,
13
+ gemini1mSuffix: false,
14
+
15
+ // Presets state
16
+ presets: [],
17
+ selectedPresetName: '',
18
+ savingPreset: false,
19
+ deletingPreset: false,
20
+ pendingPresetName: '', // For unsaved changes confirmation
21
+ newPresetName: '', // For save preset modal input
22
+
23
+ // Model fields that may contain Gemini model names
24
+ geminiModelFields: [
25
+ 'ANTHROPIC_MODEL',
26
+ 'CLAUDE_CODE_SUBAGENT_MODEL',
27
+ 'ANTHROPIC_DEFAULT_OPUS_MODEL',
28
+ 'ANTHROPIC_DEFAULT_SONNET_MODEL',
29
+ 'ANTHROPIC_DEFAULT_HAIKU_MODEL'
30
+ ],
31
+
32
+ init() {
33
+ // Only fetch config if this is the active sub-tab
34
+ if (this.activeTab === 'claude') {
35
+ this.fetchConfig();
36
+ this.fetchPresets();
37
+ }
38
+
39
+ // Watch local activeTab (from parent settings scope, skip initial trigger)
40
+ this.$watch('activeTab', (tab, oldTab) => {
41
+ if (tab === 'claude' && oldTab !== undefined) {
42
+ this.fetchConfig();
43
+ this.fetchPresets();
44
+ }
45
+ });
46
+
47
+ this.$watch('$store.data.models', (val) => {
48
+ this.models = val || [];
49
+ });
50
+ this.models = Alpine.store('data').models || [];
51
+ },
52
+
53
+ /**
54
+ * Detect if any Gemini model has [1m] suffix
55
+ */
56
+ detectGemini1mSuffix() {
57
+ for (const field of this.geminiModelFields) {
58
+ const val = this.config.env[field];
59
+ if (val && val.toLowerCase().includes('gemini') && val.includes('[1m]')) {
60
+ return true;
61
+ }
62
+ }
63
+ return false;
64
+ },
65
+
66
+ /**
67
+ * Toggle [1m] suffix for all Gemini models
68
+ */
69
+ toggleGemini1mSuffix(enabled) {
70
+ for (const field of this.geminiModelFields) {
71
+ const val = this.config.env[field];
72
+ // Fix: Case-insensitive check for gemini
73
+ if (val && /gemini/i.test(val)) {
74
+ if (enabled && !val.includes('[1m]')) {
75
+ this.config.env[field] = val.trim() + '[1m]';
76
+ } else if (!enabled && val.includes('[1m]')) {
77
+ this.config.env[field] = val.replace(/\s*\[1m\]$/i, '').trim();
78
+ }
79
+ }
80
+ }
81
+ this.gemini1mSuffix = enabled;
82
+ },
83
+
84
+ /**
85
+ * Helper to select a model from the dropdown
86
+ * @param {string} field - The config.env field to update
87
+ * @param {string} modelId - The selected model ID
88
+ */
89
+ selectModel(field, modelId) {
90
+ if (!this.config.env) this.config.env = {};
91
+
92
+ let finalModelId = modelId;
93
+ // If 1M mode is enabled and it's a Gemini model, append the suffix
94
+ if (this.gemini1mSuffix && modelId.toLowerCase().includes('gemini')) {
95
+ if (!finalModelId.includes('[1m]')) {
96
+ finalModelId = finalModelId.trim() + '[1m]';
97
+ }
98
+ }
99
+
100
+ this.config.env[field] = finalModelId;
101
+ },
102
+
103
+ async fetchConfig() {
104
+ const password = Alpine.store('global').webuiPassword;
105
+ try {
106
+ const { response, newPassword } = await window.utils.request('/api/claude/config', {}, password);
107
+ if (newPassword) Alpine.store('global').webuiPassword = newPassword;
108
+
109
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
110
+ const data = await response.json();
111
+ this.config = data.config || {};
112
+ this.configPath = data.path || '~/.claude/settings.json'; // Save dynamic path
113
+ if (!this.config.env) this.config.env = {};
114
+
115
+ // Default MCP CLI to true if not set
116
+ if (this.config.env.ENABLE_EXPERIMENTAL_MCP_CLI === undefined) {
117
+ this.config.env.ENABLE_EXPERIMENTAL_MCP_CLI = 'true';
118
+ }
119
+
120
+ // Detect existing [1m] suffix state, default to true
121
+ const hasExistingSuffix = this.detectGemini1mSuffix();
122
+ const hasGeminiModels = this.geminiModelFields.some(f =>
123
+ this.config.env[f]?.toLowerCase().includes('gemini')
124
+ );
125
+
126
+ // Default to enabled: if no suffix found but Gemini models exist, apply suffix
127
+ if (!hasExistingSuffix && hasGeminiModels) {
128
+ this.toggleGemini1mSuffix(true);
129
+ } else {
130
+ this.gemini1mSuffix = hasExistingSuffix || !hasGeminiModels;
131
+ }
132
+ } catch (e) {
133
+ console.error('Failed to fetch Claude config:', e);
134
+ }
135
+ },
136
+
137
+ async saveClaudeConfig() {
138
+ this.loading = true;
139
+ const password = Alpine.store('global').webuiPassword;
140
+ try {
141
+ const { response, newPassword } = await window.utils.request('/api/claude/config', {
142
+ method: 'POST',
143
+ headers: { 'Content-Type': 'application/json' },
144
+ body: JSON.stringify(this.config)
145
+ }, password);
146
+ if (newPassword) Alpine.store('global').webuiPassword = newPassword;
147
+
148
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
149
+ Alpine.store('global').showToast(Alpine.store('global').t('claudeConfigSaved'), 'success');
150
+ } catch (e) {
151
+ Alpine.store('global').showToast(Alpine.store('global').t('saveConfigFailed') + ': ' + e.message, 'error');
152
+ } finally {
153
+ this.loading = false;
154
+ }
155
+ },
156
+
157
+ restoreDefaultClaudeConfig() {
158
+ document.getElementById('restore_defaults_modal').showModal();
159
+ },
160
+
161
+ async executeRestore() {
162
+ this.restoring = true;
163
+ const password = Alpine.store('global').webuiPassword;
164
+ try {
165
+ const { response, newPassword } = await window.utils.request('/api/claude/config/restore', {
166
+ method: 'POST',
167
+ headers: { 'Content-Type': 'application/json' }
168
+ }, password);
169
+ if (newPassword) Alpine.store('global').webuiPassword = newPassword;
170
+
171
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
172
+ Alpine.store('global').showToast(Alpine.store('global').t('claudeConfigRestored'), 'success');
173
+
174
+ // Close modal
175
+ document.getElementById('restore_defaults_modal').close();
176
+
177
+ // Reload the config to reflect the changes
178
+ await this.fetchConfig();
179
+ } catch (e) {
180
+ Alpine.store('global').showToast(Alpine.store('global').t('restoreConfigFailed') + ': ' + e.message, 'error');
181
+ } finally {
182
+ this.restoring = false;
183
+ }
184
+ },
185
+
186
+ // ==========================================
187
+ // Presets Management
188
+ // ==========================================
189
+
190
+ /**
191
+ * Fetch all saved presets from the server
192
+ */
193
+ async fetchPresets() {
194
+ const password = Alpine.store('global').webuiPassword;
195
+ try {
196
+ const { response, newPassword } = await window.utils.request('/api/claude/presets', {}, password);
197
+ if (newPassword) Alpine.store('global').webuiPassword = newPassword;
198
+
199
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
200
+ const data = await response.json();
201
+ if (data.status === 'ok') {
202
+ this.presets = data.presets || [];
203
+ // Auto-select first preset if none selected
204
+ if (this.presets.length > 0 && !this.selectedPresetName) {
205
+ this.selectedPresetName = this.presets[0].name;
206
+ }
207
+ }
208
+ } catch (e) {
209
+ console.error('Failed to fetch presets:', e);
210
+ }
211
+ },
212
+
213
+ /**
214
+ * Load the selected preset into the form (does not save to Claude CLI)
215
+ */
216
+ loadSelectedPreset() {
217
+ const preset = this.presets.find(p => p.name === this.selectedPresetName);
218
+ if (!preset) {
219
+ return;
220
+ }
221
+
222
+ // Merge preset config into current config.env
223
+ this.config.env = { ...this.config.env, ...preset.config };
224
+
225
+ // Update Gemini 1M toggle based on merged config (not just preset)
226
+ this.gemini1mSuffix = this.detectGemini1mSuffix();
227
+
228
+ Alpine.store('global').showToast(
229
+ Alpine.store('global').t('presetLoaded') || `Preset "${preset.name}" loaded. Click "Apply to Claude CLI" to save.`,
230
+ 'success'
231
+ );
232
+ },
233
+
234
+ /**
235
+ * Check if current config matches any saved preset
236
+ * @returns {boolean} True if current config matches a preset
237
+ */
238
+ currentConfigMatchesPreset() {
239
+ const relevantKeys = [
240
+ 'ANTHROPIC_BASE_URL',
241
+ 'ANTHROPIC_AUTH_TOKEN',
242
+ 'ANTHROPIC_MODEL',
243
+ 'CLAUDE_CODE_SUBAGENT_MODEL',
244
+ 'ANTHROPIC_DEFAULT_OPUS_MODEL',
245
+ 'ANTHROPIC_DEFAULT_SONNET_MODEL',
246
+ 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
247
+ 'ENABLE_EXPERIMENTAL_MCP_CLI'
248
+ ];
249
+
250
+ for (const preset of this.presets) {
251
+ let matches = true;
252
+ for (const key of relevantKeys) {
253
+ const currentVal = this.config.env[key] || '';
254
+ const presetVal = preset.config[key] || '';
255
+ if (currentVal !== presetVal) {
256
+ matches = false;
257
+ break;
258
+ }
259
+ }
260
+ if (matches) return true;
261
+ }
262
+ return false;
263
+ },
264
+
265
+ /**
266
+ * Handle preset selection change - auto-load with unsaved changes warning
267
+ * @param {string} newPresetName - The newly selected preset name
268
+ */
269
+ async onPresetSelect(newPresetName) {
270
+ if (!newPresetName || newPresetName === this.selectedPresetName) return;
271
+
272
+ // Check if current config has unsaved changes (doesn't match any preset)
273
+ const hasUnsavedChanges = !this.currentConfigMatchesPreset();
274
+
275
+ if (hasUnsavedChanges) {
276
+ // Store pending preset and show confirmation modal
277
+ this.pendingPresetName = newPresetName;
278
+ document.getElementById('unsaved_changes_modal').showModal();
279
+ return;
280
+ }
281
+
282
+ this.selectedPresetName = newPresetName;
283
+ this.loadSelectedPreset();
284
+ },
285
+
286
+ /**
287
+ * Confirm loading preset despite unsaved changes
288
+ */
289
+ confirmLoadPreset() {
290
+ document.getElementById('unsaved_changes_modal').close();
291
+ this.selectedPresetName = this.pendingPresetName;
292
+ this.pendingPresetName = '';
293
+ this.loadSelectedPreset();
294
+ },
295
+
296
+ /**
297
+ * Cancel loading preset - revert dropdown selection
298
+ */
299
+ cancelLoadPreset() {
300
+ document.getElementById('unsaved_changes_modal').close();
301
+ // Revert the dropdown to current selection
302
+ const select = document.querySelector('[aria-label="Select preset"]');
303
+ if (select) select.value = this.selectedPresetName;
304
+ this.pendingPresetName = '';
305
+ },
306
+
307
+ /**
308
+ * Save the current config as a new preset
309
+ */
310
+ async saveCurrentAsPreset() {
311
+ // Clear the input and show the save preset modal
312
+ this.newPresetName = '';
313
+ document.getElementById('save_preset_modal').showModal();
314
+ },
315
+
316
+ /**
317
+ * Execute preset save after user enters name
318
+ */
319
+ async executeSavePreset(name) {
320
+ if (!name || !name.trim()) {
321
+ Alpine.store('global').showToast(Alpine.store('global').t('presetNameRequired'), 'error');
322
+ return;
323
+ }
324
+
325
+ this.savingPreset = true;
326
+ const password = Alpine.store('global').webuiPassword;
327
+
328
+ try {
329
+ // Save only relevant env vars
330
+ const relevantKeys = [
331
+ 'ANTHROPIC_BASE_URL',
332
+ 'ANTHROPIC_AUTH_TOKEN',
333
+ 'ANTHROPIC_MODEL',
334
+ 'CLAUDE_CODE_SUBAGENT_MODEL',
335
+ 'ANTHROPIC_DEFAULT_OPUS_MODEL',
336
+ 'ANTHROPIC_DEFAULT_SONNET_MODEL',
337
+ 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
338
+ 'ENABLE_EXPERIMENTAL_MCP_CLI'
339
+ ];
340
+ const presetConfig = {};
341
+ relevantKeys.forEach(k => {
342
+ if (this.config.env[k]) {
343
+ presetConfig[k] = this.config.env[k];
344
+ }
345
+ });
346
+
347
+ const { response, newPassword } = await window.utils.request('/api/claude/presets', {
348
+ method: 'POST',
349
+ headers: { 'Content-Type': 'application/json' },
350
+ body: JSON.stringify({ name: name.trim(), config: presetConfig })
351
+ }, password);
352
+ if (newPassword) Alpine.store('global').webuiPassword = newPassword;
353
+
354
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
355
+ const data = await response.json();
356
+ if (data.status === 'ok') {
357
+ this.presets = data.presets || [];
358
+ this.selectedPresetName = name.trim();
359
+ this.newPresetName = ''; // Clear the input
360
+ Alpine.store('global').showToast(
361
+ Alpine.store('global').t('presetSaved') || `Preset "${name}" saved`,
362
+ 'success'
363
+ );
364
+ document.getElementById('save_preset_modal').close();
365
+ } else {
366
+ throw new Error(data.error || Alpine.store('global').t('saveFailed'));
367
+ }
368
+ } catch (e) {
369
+ Alpine.store('global').showToast(Alpine.store('global').t('failedToSavePreset') + ': ' + e.message, 'error');
370
+ } finally {
371
+ this.savingPreset = false;
372
+ }
373
+ },
374
+
375
+ /**
376
+ * Delete the selected preset
377
+ */
378
+ async deleteSelectedPreset() {
379
+ if (!this.selectedPresetName) {
380
+ Alpine.store('global').showToast(Alpine.store('global').t('noPresetSelected'), 'warning');
381
+ return;
382
+ }
383
+
384
+ // Confirm deletion
385
+ const confirmMsg = Alpine.store('global').t('deletePresetConfirm', { name: this.selectedPresetName });
386
+ if (!confirm(confirmMsg)) {
387
+ return;
388
+ }
389
+
390
+ this.deletingPreset = true;
391
+ const password = Alpine.store('global').webuiPassword;
392
+
393
+ try {
394
+ const { response, newPassword } = await window.utils.request(
395
+ `/api/claude/presets/${encodeURIComponent(this.selectedPresetName)}`,
396
+ { method: 'DELETE' },
397
+ password
398
+ );
399
+ if (newPassword) Alpine.store('global').webuiPassword = newPassword;
400
+
401
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
402
+ const data = await response.json();
403
+ if (data.status === 'ok') {
404
+ this.presets = data.presets || [];
405
+ // Select first available preset or clear selection
406
+ this.selectedPresetName = this.presets.length > 0 ? this.presets[0].name : '';
407
+ Alpine.store('global').showToast(
408
+ Alpine.store('global').t('presetDeleted') || 'Preset deleted',
409
+ 'success'
410
+ );
411
+ } else {
412
+ throw new Error(data.error || Alpine.store('global').t('deleteFailed'));
413
+ }
414
+ } catch (e) {
415
+ Alpine.store('global').showToast(Alpine.store('global').t('failedToDeletePreset') + ': ' + e.message, 'error');
416
+ } finally {
417
+ this.deletingPreset = false;
418
+ }
419
+ }
420
+ });