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,549 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="commons" class="dark">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>CommonsProxy Console</title>
8
+ <link rel="icon" type="image/svg+xml" href="favicon.svg">
9
+
10
+ <!-- Libraries -->
11
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
12
+ <!-- Alpine.js must be deferred so stores register their listeners first -->
13
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
14
+
15
+ <!-- Compiled Tailwind CSS (includes DaisyUI) -->
16
+ <link rel="stylesheet" href="css/style.css">
17
+ </head>
18
+
19
+ <body
20
+ class="bg-space-950 text-gray-300 font-sans antialiased min-h-screen overflow-hidden selection:bg-neon-purple selection:text-white"
21
+ x-cloak x-data="app" x-init="if(window.UILogger) window.UILogger.debug('App initialized')">
22
+
23
+ <!-- Toast Notification -->
24
+ <div class="fixed top-4 right-4 z-[100] flex flex-col gap-2 pointer-events-none">
25
+ <template x-if="$store.global.toast">
26
+ <div x-transition:enter="transition ease-out duration-300"
27
+ x-transition:enter-start="opacity-0 translate-x-8 scale-95"
28
+ x-transition:enter-end="opacity-100 translate-x-0 scale-100"
29
+ x-transition:leave="transition ease-in duration-200"
30
+ x-transition:leave-start="opacity-100 translate-x-0 scale-100"
31
+ x-transition:leave-end="opacity-0 translate-x-4 scale-95"
32
+ class="alert shadow-lg border backdrop-blur-md pointer-events-auto min-w-[300px]" :class="{
33
+ 'alert-info border-neon-cyan/20 bg-space-900/90 text-neon-cyan': $store.global.toast.type === 'info',
34
+ 'alert-success border-neon-green/20 bg-space-900/90 text-neon-green': $store.global.toast.type === 'success',
35
+ 'alert-error border-red-500/20 bg-space-900/90 text-red-400': $store.global.toast.type === 'error'
36
+ }">
37
+ <div class="flex items-center gap-3">
38
+ <!-- Icons based on type -->
39
+ <template x-if="$store.global.toast.type === 'info'">
40
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
41
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
42
+ d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
43
+ </svg>
44
+ </template>
45
+ <template x-if="$store.global.toast.type === 'success'">
46
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
47
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
48
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
49
+ </svg>
50
+ </template>
51
+ <template x-if="$store.global.toast.type === 'error'">
52
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
53
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
54
+ d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
55
+ </svg>
56
+ </template>
57
+ <span x-text="$store.global.toast.message" class="font-mono text-sm"></span>
58
+ </div>
59
+ </div>
60
+ </template>
61
+ </div>
62
+
63
+ <!-- Navbar -->
64
+ <div
65
+ class="h-14 border-b border-space-border flex items-center px-4 lg:px-6 justify-between bg-space-900/50 backdrop-blur-md z-50 relative">
66
+ <div class="flex items-center gap-3">
67
+ <!-- Mobile Menu Button -->
68
+ <button @click="toggleSidebar()" class="text-gray-400 hover:text-white focus:outline-none p-1 transition-colors">
69
+ <svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
70
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
71
+ </svg>
72
+ </button>
73
+
74
+ <div
75
+ class="w-8 h-8 rounded bg-gradient-to-br from-neon-purple to-blue-600 flex items-center justify-center text-white font-bold shadow-[0_0_15px_rgba(168,85,247,0.4)]">
76
+ CP</div>
77
+ <div class="flex flex-col">
78
+ <span class="text-sm font-bold tracking-wide text-white"
79
+ x-text="$store.global.t('systemName')">COMMONSPROXY</span>
80
+ <span class="text-[10px] text-gray-500 font-mono tracking-wider"
81
+ x-text="$store.global.t('systemDesc')">UNIVERSAL PROXY SYSTEM</span>
82
+ </div>
83
+ </div>
84
+
85
+ <div class="flex items-center gap-4">
86
+ <!-- Connection Pill -->
87
+ <div class="flex items-center gap-2 px-3 py-1 rounded-full text-xs font-mono border transition-all duration-300"
88
+ :class="connectionStatus === 'connected'
89
+ ? 'bg-neon-green/10 border-neon-green/20 text-neon-green'
90
+ : (connectionStatus === 'connecting' ? 'bg-yellow-500/10 border-yellow-500/20 text-yellow-500' : 'bg-red-500/10 border-red-500/20 text-red-500')">
91
+ <div class="w-1.5 h-1.5 rounded-full"
92
+ :class="connectionStatus === 'connected' ? 'bg-neon-green shadow-[0_0_8px_rgba(34,197,94,0.6)]' : (connectionStatus === 'connecting' ? 'bg-yellow-500 animate-pulse' : 'bg-red-500')">
93
+ </div>
94
+ <span
95
+ x-text="connectionStatus === 'connected' ? $store.global.t('online') : (connectionStatus === 'disconnected' ? $store.global.t('offline') : $store.global.t('connecting'))"></span>
96
+ </div>
97
+
98
+ <div class="h-4 w-px bg-space-border"></div>
99
+
100
+ <!-- Refresh Button -->
101
+ <button type="button" class="btn btn-ghost btn-xs btn-square text-gray-400 hover:text-white hover:bg-white/5"
102
+ @click="fetchData" :disabled="loading" :title="$store.global.t('refreshData')" aria-label="Refresh data">
103
+ <svg class="w-4 h-4" :class="{'animate-spin': loading}" fill="none" stroke="currentColor"
104
+ viewBox="0 0 24 24">
105
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
106
+ d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
107
+ </svg>
108
+ </button>
109
+ </div>
110
+ </div>
111
+
112
+ <!-- Layout -->
113
+ <div class="flex h-[calc(100vh-56px)] relative">
114
+
115
+ <!-- Mobile Sidebar Overlay -->
116
+ <div x-show="sidebarOpen"
117
+ x-transition:enter="transition-opacity ease-linear duration-300"
118
+ x-transition:enter-start="opacity-0"
119
+ x-transition:enter-end="opacity-100"
120
+ x-transition:leave="transition-opacity ease-linear duration-300"
121
+ x-transition:leave-start="opacity-100"
122
+ x-transition:leave-end="opacity-0"
123
+ @click="sidebarOpen = false"
124
+ class="fixed inset-0 bg-black/50 z-40 lg:hidden"
125
+ style="display: none;"></div>
126
+
127
+ <!-- Sidebar -->
128
+ <div class="fixed top-14 bottom-0 left-0 z-40 bg-space-900 border-r border-space-border transition-all duration-300 shadow-2xl overflow-hidden lg:static lg:h-auto lg:shadow-none lg:flex-shrink-0"
129
+ :class="{
130
+ 'translate-x-0': sidebarOpen,
131
+ '-translate-x-full': !sidebarOpen,
132
+ 'w-64': sidebarOpen,
133
+ 'lg:translate-x-0': sidebarOpen,
134
+ 'lg:w-64': sidebarOpen,
135
+ 'sidebar-collapsed': !sidebarOpen
136
+ }">
137
+
138
+ <!-- Inner Sidebar Content (Fixed Width to prevent squashing) -->
139
+ <div class="w-64 flex flex-col h-full pt-6 pb-4 flex-shrink-0">
140
+ <!-- Mobile Menu Header -->
141
+ <div class="flex items-center justify-between px-4 mb-6 lg:hidden">
142
+ <span class="text-sm font-bold text-white" x-text="$store.global.t('menu')">Menu</span>
143
+ <button @click="sidebarOpen = false" class="text-gray-400 hover:text-white">
144
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
145
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
146
+ </svg>
147
+ </button>
148
+ </div>
149
+
150
+ <!-- Desktop Header (Main) -->
151
+ <div class="px-4 mb-2 text-xs font-bold text-gray-600 uppercase tracking-widest hidden lg:block"
152
+ x-text="$store.global.t('main')">Main</div>
153
+
154
+ <nav class="flex flex-col gap-1">
155
+ <button
156
+ class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
157
+ :class="{'active': $store.global.activeTab === 'dashboard'}"
158
+ @click="$store.global.activeTab = 'dashboard'">
159
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
160
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
161
+ d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
162
+ </svg>
163
+ <span x-text="$store.global.t('dashboard')">Dashboard</span>
164
+ </button>
165
+ <button
166
+ class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
167
+ :class="{'active': $store.global.activeTab === 'models'}"
168
+ @click="$store.global.activeTab = 'models'">
169
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
170
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
171
+ d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
172
+ </svg>
173
+ <span x-text="$store.global.t('models')">Models</span>
174
+ </button>
175
+ <button
176
+ class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
177
+ :class="{'active': $store.global.activeTab === 'accounts'}"
178
+ @click="$store.global.activeTab = 'accounts'">
179
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
180
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
181
+ d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
182
+ </svg>
183
+ <span x-text="$store.global.t('accounts')">Accounts</span>
184
+ </button>
185
+ </nav>
186
+
187
+ <div class="px-4 mt-8 mb-2 text-xs font-bold text-gray-600 uppercase tracking-widest"
188
+ x-text="$store.global.t('system')">System</div>
189
+ <nav class="flex flex-col gap-1">
190
+ <button
191
+ class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
192
+ :class="{'active': $store.global.activeTab === 'logs'}" @click="$store.global.activeTab = 'logs'">
193
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
194
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
195
+ d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
196
+ </svg>
197
+ <span x-text="$store.global.t('logs')">Logs</span>
198
+ </button>
199
+ <button
200
+ class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
201
+ :class="{'active': $store.global.activeTab === 'settings'}"
202
+ @click="$store.global.activeTab = 'settings'">
203
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
204
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
205
+ d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
206
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
207
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
208
+ </svg>
209
+ <span x-text="$store.global.t('settings')">Settings</span>
210
+ </button>
211
+ </nav>
212
+
213
+ <!-- Footer Info -->
214
+ <div class="mt-auto px-6 text-[10px] text-gray-700 font-mono">
215
+ <div class="flex justify-between">
216
+ <span x-text="'V ' + $store.global.version">V 1.0.0</span>
217
+ <a href="https://github.com/AryanVBW/CommonsProxy" target="_blank" rel="noopener noreferrer"
218
+ class="hover:text-neon-purple transition-colors" x-text="$store.global.t('github')">GitHub</a>
219
+ </div>
220
+ </div>
221
+ </div>
222
+ </div>
223
+
224
+ <!-- Main Content -->
225
+ <div class="flex-1 overflow-auto bg-space-950 relative custom-scrollbar">
226
+
227
+ <!-- Views Container -->
228
+ <!-- Dashboard -->
229
+ <div x-show="$store.global.activeTab === 'dashboard'" x-load-view="'dashboard'"
230
+ x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
231
+ class="w-full"></div>
232
+
233
+ <!-- Models -->
234
+ <div x-show="$store.global.activeTab === 'models'" x-load-view="'models'"
235
+ x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
236
+ class="w-full"></div>
237
+
238
+ <!-- Logs -->
239
+ <div x-show="$store.global.activeTab === 'logs'" x-load-view="'logs'" x-transition:enter="fade-enter-active"
240
+ x-transition:enter-start="fade-enter-from" class="w-full h-full"></div>
241
+
242
+ <!-- Accounts -->
243
+ <div x-show="$store.global.activeTab === 'accounts'" x-load-view="'accounts'"
244
+ x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
245
+ class="w-full"></div>
246
+
247
+ <!-- Settings -->
248
+ <div x-show="$store.global.activeTab === 'settings'" x-load-view="'settings'"
249
+ x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
250
+ class="w-full"></div>
251
+ </div>
252
+ </div>
253
+
254
+ <!-- Add Account Modal -->
255
+ <dialog id="add_account_modal" class="modal backdrop-blur-sm" x-data="addAccountModal">
256
+ <div class="modal-box max-w-lg w-full bg-space-900 border border-space-border text-gray-300 shadow-[0_0_50px_rgba(0,0,0,0.5)] p-6">
257
+ <h3 class="font-bold text-lg text-white mb-4" x-text="$store.global.t('addAccount')">Add New Account</h3>
258
+
259
+ <div class="flex flex-col gap-4">
260
+ <!-- Provider Selection -->
261
+ <div class="form-control">
262
+ <label class="label">
263
+ <span class="label-text text-gray-400">Select Provider</span>
264
+ </label>
265
+ <select x-model="selectedProvider" @change="onProviderChange()"
266
+ class="select select-bordered w-full bg-space-800 text-gray-300">
267
+ <template x-for="provider in providers" :key="provider.id">
268
+ <option :value="provider.id" x-text="provider.name"></option>
269
+ </template>
270
+ </select>
271
+ </div>
272
+
273
+ <!-- Google OAuth Flow -->
274
+ <template x-if="isOAuthProvider">
275
+ <div class="flex flex-col gap-4">
276
+ <p class="text-sm text-gray-400 leading-relaxed" x-text="$store.global.t('connectGoogleDesc')">
277
+ Connect a Google Workspace account to increase your API quota limit.
278
+ The account will be used to proxy Claude requests via CommonsProxy.
279
+ </p>
280
+
281
+ <button class="btn btn-primary flex items-center justify-center gap-3 h-11" @click="addAccountWeb">
282
+ <svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
283
+ <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"></path>
284
+ <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"></path>
285
+ <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"></path>
286
+ <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"></path>
287
+ </svg>
288
+ <span x-text="$store.global.t('connectGoogle')">Connect Google Account</span>
289
+ </button>
290
+
291
+ <div class="text-center mt-2">
292
+ <p class="text-xs text-gray-500 mb-2" x-text="$store.global.t('or')">OR</p>
293
+
294
+ <!-- Manual OAuth Mode -->
295
+ <details class="group mb-2" @toggle="initManualAuth($event)">
296
+ <summary class="text-xs text-gray-400 hover:text-neon-cyan cursor-pointer transition-colors inline-flex items-center gap-1">
297
+ <svg class="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
298
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
299
+ </svg>
300
+ <span x-text="$store.global.t('manualMode')">Manual Mode</span>
301
+ </summary>
302
+ <div class="mt-3 p-3 bg-black/50 rounded border border-space-border/30 text-xs">
303
+ <template x-if="authUrl">
304
+ <div>
305
+ <div class="flex items-center gap-2 mb-2">
306
+ <input type="text" readonly :value="authUrl"
307
+ class="input input-xs input-bordered flex-1 bg-space-800 text-gray-300 font-mono">
308
+ <button class="btn btn-xs btn-ghost text-neon-cyan" @click="copyLink" :title="$store.global.t('linkCopied')">
309
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
310
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
311
+ </svg>
312
+ </button>
313
+ </div>
314
+ <div class="flex items-center gap-2">
315
+ <input type="text" x-model="callbackInput"
316
+ :placeholder="$store.global.t('pasteCallbackPlaceholder')"
317
+ class="input input-xs input-bordered flex-1 bg-space-800 text-gray-300 font-mono">
318
+ <button class="btn btn-xs btn-success"
319
+ @click="completeManualAuth"
320
+ :disabled="!callbackInput || submitting"
321
+ :class="{ 'loading': submitting }">
322
+ <span x-text="$store.global.t('completeAuth')">OK</span>
323
+ </button>
324
+ </div>
325
+ </div>
326
+ </template>
327
+ <template x-if="!authUrl">
328
+ <div class="text-gray-500">Loading...</div>
329
+ </template>
330
+ </div>
331
+ </details>
332
+
333
+ <!-- CLI Command -->
334
+ <details class="group">
335
+ <summary class="text-xs text-gray-400 hover:text-neon-cyan cursor-pointer transition-colors inline-flex items-center gap-1">
336
+ <svg class="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
337
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
338
+ </svg>
339
+ <span x-text="$store.global.t('useCliCommand')">Use CLI Command</span>
340
+ </summary>
341
+ <div class="mt-3 p-3 bg-black/50 rounded border border-space-border/30 font-mono text-xs text-gray-300">
342
+ <div class="flex items-center gap-2">
343
+ <span class="text-gray-600">$</span>
344
+ <code>npm run accounts:add</code>
345
+ </div>
346
+ </div>
347
+ </details>
348
+ </div>
349
+ </div>
350
+ </template>
351
+
352
+ <!-- GitHub Copilot Device Auth Flow -->
353
+ <template x-if="isDeviceAuthProvider">
354
+ <div class="flex flex-col gap-4">
355
+ <p class="text-sm text-gray-400 leading-relaxed">
356
+ Connect your GitHub Copilot account using GitHub's device authorization flow.
357
+ You'll need an active GitHub Copilot subscription.
358
+ </p>
359
+
360
+ <!-- Before auth started -->
361
+ <template x-if="!copilotFlowId">
362
+ <button class="btn btn-primary flex items-center justify-center gap-3 h-11"
363
+ @click="startCopilotDeviceAuth()"
364
+ :disabled="submitting"
365
+ :class="{ 'loading': submitting }">
366
+ <svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
367
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
368
+ </svg>
369
+ <span>Connect GitHub Copilot</span>
370
+ </button>
371
+ </template>
372
+
373
+ <!-- After auth started - show code -->
374
+ <template x-if="copilotFlowId">
375
+ <div class="flex flex-col gap-4">
376
+ <div class="bg-space-800/80 border border-space-border rounded-lg p-4 text-center">
377
+ <p class="text-xs text-gray-500 uppercase tracking-wider mb-2">Your Device Code</p>
378
+ <div class="flex items-center justify-center gap-3">
379
+ <code class="text-3xl font-bold text-white tracking-[0.3em] select-all" x-text="copilotUserCode"></code>
380
+ <button class="btn btn-xs btn-ghost text-neon-cyan" @click="copyCopilotCode()" title="Copy code">
381
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
382
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
383
+ </svg>
384
+ </button>
385
+ </div>
386
+ </div>
387
+
388
+ <div class="flex items-center gap-2 text-sm text-gray-400">
389
+ <template x-if="copilotPolling">
390
+ <div class="flex items-center gap-2 w-full">
391
+ <svg class="w-4 h-4 animate-spin text-neon-purple flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
392
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
393
+ </svg>
394
+ <span>Waiting for authorization on GitHub...</span>
395
+ </div>
396
+ </template>
397
+ </div>
398
+
399
+ <p class="text-xs text-gray-500">
400
+ Enter the code above at
401
+ <a :href="copilotVerificationUri" target="_blank" class="text-neon-cyan hover:underline" x-text="copilotVerificationUri"></a>
402
+ to authorize CommonsProxy.
403
+ </p>
404
+
405
+ <button class="btn btn-sm btn-ghost text-gray-500 mt-2" @click="stopCopilotPolling(); copilotFlowId = null;">
406
+ Cancel
407
+ </button>
408
+ </div>
409
+ </template>
410
+ </div>
411
+ </template>
412
+
413
+ <!-- API Key / PAT Flow (Anthropic, OpenAI, GitHub) -->
414
+ <template x-if="requiresApiKey">
415
+ <div class="flex flex-col gap-3">
416
+ <p class="text-sm text-gray-400 leading-relaxed">
417
+ Enter your <span x-text="currentProvider?.name"></span> credentials below.
418
+ </p>
419
+
420
+ <!-- Email -->
421
+ <div class="form-control">
422
+ <label class="label">
423
+ <span class="label-text text-gray-400">Email</span>
424
+ </label>
425
+ <input type="email" x-model="email" placeholder="your@email.com"
426
+ class="input input-bordered w-full bg-space-800 text-gray-300" />
427
+ </div>
428
+
429
+ <!-- API Key / PAT -->
430
+ <div class="form-control">
431
+ <label class="label">
432
+ <span class="label-text text-gray-400" x-text="apiKeyLabel">API Key</span>
433
+ </label>
434
+ <input type="password" x-model="apiKey" :placeholder="apiKeyPlaceholder"
435
+ class="input input-bordered w-full bg-space-800 text-gray-300 font-mono text-sm" />
436
+ </div>
437
+
438
+ <!-- Custom Endpoint (Optional) -->
439
+ <details class="group">
440
+ <summary class="text-xs text-gray-400 hover:text-neon-cyan cursor-pointer transition-colors inline-flex items-center gap-1">
441
+ <svg class="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
442
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
443
+ </svg>
444
+ <span>Custom Endpoint (Optional)</span>
445
+ </summary>
446
+ <div class="form-control mt-2">
447
+ <input type="text" x-model="customEndpoint" placeholder="https://api.example.com"
448
+ class="input input-sm input-bordered w-full bg-space-800 text-gray-300 font-mono text-xs" />
449
+ <label class="label">
450
+ <span class="label-text-alt text-gray-500">Leave blank to use default endpoint</span>
451
+ </label>
452
+ </div>
453
+ </details>
454
+
455
+ <!-- Add Button -->
456
+ <button class="btn btn-primary w-full mt-2"
457
+ @click="addAccountWithProvider()"
458
+ :disabled="submitting || !email || !apiKey"
459
+ :class="{ 'loading': submitting }">
460
+ <span x-show="!submitting">Add Account</span>
461
+ <span x-show="submitting">Adding...</span>
462
+ </button>
463
+ </div>
464
+ </template>
465
+ </div>
466
+
467
+ <div class="modal-action mt-6">
468
+ <form method="dialog">
469
+ <button type="submit" class="btn btn-ghost hover:bg-white/10" @click="resetState()" x-text="$store.global.t('close')">Close</button>
470
+ </form>
471
+ </div>
472
+ </div>
473
+ <form method="dialog" class="modal-backdrop">
474
+ <button type="button" @click="resetState()" x-text="$store.global.t('close')">close</button>
475
+ </form>
476
+ </dialog>
477
+
478
+ <!-- OAuth Progress Modal -->
479
+ <dialog id="oauth_progress_modal" class="modal" :class="{ 'modal-open': $store.global.oauthProgress.active }">
480
+ <div class="modal-box bg-space-900 border border-neon-purple/50">
481
+ <h3 class="font-bold text-lg text-white flex items-center gap-2">
482
+ <svg class="w-6 h-6 text-neon-purple animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
483
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
484
+ </svg>
485
+ <span x-text="$store.global.t('oauthWaiting')">Waiting for OAuth...</span>
486
+ </h3>
487
+ <p class="py-4 text-gray-400 text-sm" x-text="$store.global.t('oauthWaitingDesc')">
488
+ Please complete authentication in the popup window.
489
+ </p>
490
+
491
+ <!-- Progress Bar -->
492
+ <div class="w-full bg-space-800 rounded-full h-2 mb-4 overflow-hidden">
493
+ <div class="bg-neon-purple h-2 rounded-full transition-all duration-500"
494
+ :style="`width: ${($store.global.oauthProgress.current / $store.global.oauthProgress.max) * 100}%`">
495
+ </div>
496
+ </div>
497
+
498
+ <!-- Progress Text -->
499
+ <div class="flex justify-between text-xs text-gray-600 mb-4">
500
+ <span x-text="`${$store.global.oauthProgress.current} / ${$store.global.oauthProgress.max}s`"></span>
501
+ <span x-text="`${Math.round(($store.global.oauthProgress.current / $store.global.oauthProgress.max) * 100)}%`"></span>
502
+ </div>
503
+
504
+ <div class="modal-action">
505
+ <button class="btn btn-sm btn-ghost text-gray-400"
506
+ @click="$store.global.oauthProgress.cancel && $store.global.oauthProgress.cancel()"
507
+ x-text="$store.global.t('cancelOAuth')">
508
+ Cancel
509
+ </button>
510
+ </div>
511
+ </div>
512
+ </dialog>
513
+
514
+ <!-- Scripts - Loading Order Matters! -->
515
+ <!-- 1. Config & Utils (global helpers) -->
516
+ <script src="js/config/constants.js"></script>
517
+ <script src="js/utils/ui-logger.js"></script><!-- Issue #183: Conditional logging utility -->
518
+ <script src="js/utils.js"></script>
519
+ <script src="js/utils/error-handler.js"></script>
520
+ <script src="js/utils/account-actions.js"></script>
521
+ <script src="js/utils/validators.js"></script>
522
+ <script src="js/utils/model-config.js"></script>
523
+ <!-- Translation files (must load before store.js) -->
524
+ <script src="js/translations/en.js"></script>
525
+ <script src="js/translations/zh.js"></script>
526
+ <script src="js/translations/tr.js"></script>
527
+ <script src="js/translations/id.js"></script>
528
+ <script src="js/translations/pt.js"></script>
529
+ <!-- 2. Alpine Stores (register alpine:init listeners) -->
530
+ <script src="js/store.js"></script>
531
+ <script src="js/data-store.js"></script>
532
+ <script src="js/settings-store.js"></script>
533
+ <!-- 3. Components (register to window.Components) -->
534
+ <!-- Dashboard modules (load before main dashboard) -->
535
+ <script src="js/components/dashboard/stats.js"></script>
536
+ <script src="js/components/dashboard/charts.js"></script>
537
+ <script src="js/components/dashboard/filters.js"></script>
538
+ <script src="js/components/dashboard.js"></script>
539
+ <script src="js/components/models.js"></script>
540
+ <script src="js/components/account-manager.js"></script>
541
+ <script src="js/components/claude-config.js"></script>
542
+ <script src="js/components/logs-viewer.js"></script>
543
+ <script src="js/components/server-config.js"></script>
544
+ <script src="js/components/add-account-modal.js"></script>
545
+ <!-- 4. App (registers Alpine components from window.Components) -->
546
+ <script src="app.js"></script>
547
+ </body>
548
+
549
+ </html>