@zhafron/opencode-iflow-auth 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.
- package/README.md +231 -0
- package/dist/constants.d.ts +19 -0
- package/dist/constants.js +61 -0
- package/dist/iflow/apikey.d.ts +6 -0
- package/dist/iflow/apikey.js +17 -0
- package/dist/iflow/oauth.d.ts +20 -0
- package/dist/iflow/oauth.js +113 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/plugin/accounts.d.ts +24 -0
- package/dist/plugin/accounts.js +147 -0
- package/dist/plugin/auth-page.d.ts +3 -0
- package/dist/plugin/auth-page.js +573 -0
- package/dist/plugin/cli.d.ts +11 -0
- package/dist/plugin/cli.js +77 -0
- package/dist/plugin/config/index.d.ts +2 -0
- package/dist/plugin/config/index.js +2 -0
- package/dist/plugin/config/loader.d.ts +3 -0
- package/dist/plugin/config/loader.js +110 -0
- package/dist/plugin/config/schema.d.ts +35 -0
- package/dist/plugin/config/schema.js +22 -0
- package/dist/plugin/errors.d.ts +14 -0
- package/dist/plugin/errors.js +25 -0
- package/dist/plugin/logger.d.ts +8 -0
- package/dist/plugin/logger.js +63 -0
- package/dist/plugin/server.d.ts +7 -0
- package/dist/plugin/server.js +98 -0
- package/dist/plugin/storage.d.ts +4 -0
- package/dist/plugin/storage.js +91 -0
- package/dist/plugin/token.d.ts +3 -0
- package/dist/plugin/token.js +26 -0
- package/dist/plugin/types.d.ts +55 -0
- package/dist/plugin/types.js +0 -0
- package/dist/plugin.d.ts +32 -0
- package/dist/plugin.js +504 -0
- package/package.json +61 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
function escapeHtml(text) {
|
|
2
|
+
return text
|
|
3
|
+
.replace(/&/g, '&')
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"')
|
|
7
|
+
.replace(/'/g, ''');
|
|
8
|
+
}
|
|
9
|
+
export function getIDCAuthHtml(verificationUrl, userCode, statusUrl) {
|
|
10
|
+
const escapedUrl = escapeHtml(verificationUrl);
|
|
11
|
+
const escapedCode = escapeHtml(userCode);
|
|
12
|
+
const escapedStatusUrl = escapeHtml(statusUrl);
|
|
13
|
+
return `<!DOCTYPE html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="UTF-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
18
|
+
<title>AWS Builder ID Authentication</title>
|
|
19
|
+
<style>
|
|
20
|
+
* {
|
|
21
|
+
margin: 0;
|
|
22
|
+
padding: 0;
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
}
|
|
25
|
+
body {
|
|
26
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
27
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
28
|
+
min-height: 100vh;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
padding: 20px;
|
|
33
|
+
}
|
|
34
|
+
.container {
|
|
35
|
+
background: white;
|
|
36
|
+
border-radius: 16px;
|
|
37
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
38
|
+
max-width: 500px;
|
|
39
|
+
width: 100%;
|
|
40
|
+
padding: 48px 40px;
|
|
41
|
+
text-align: center;
|
|
42
|
+
animation: slideIn 0.4s ease-out;
|
|
43
|
+
}
|
|
44
|
+
@keyframes slideIn {
|
|
45
|
+
from {
|
|
46
|
+
opacity: 0;
|
|
47
|
+
transform: translateY(-20px);
|
|
48
|
+
}
|
|
49
|
+
to {
|
|
50
|
+
opacity: 1;
|
|
51
|
+
transform: translateY(0);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
h1 {
|
|
55
|
+
color: #1a202c;
|
|
56
|
+
font-size: 28px;
|
|
57
|
+
font-weight: 700;
|
|
58
|
+
margin-bottom: 12px;
|
|
59
|
+
}
|
|
60
|
+
.subtitle {
|
|
61
|
+
color: #718096;
|
|
62
|
+
font-size: 16px;
|
|
63
|
+
margin-bottom: 32px;
|
|
64
|
+
line-height: 1.5;
|
|
65
|
+
}
|
|
66
|
+
.code-container {
|
|
67
|
+
background: #f7fafc;
|
|
68
|
+
border: 2px solid #e2e8f0;
|
|
69
|
+
border-radius: 12px;
|
|
70
|
+
padding: 24px;
|
|
71
|
+
margin-bottom: 24px;
|
|
72
|
+
position: relative;
|
|
73
|
+
}
|
|
74
|
+
.code-label {
|
|
75
|
+
color: #4a5568;
|
|
76
|
+
font-size: 14px;
|
|
77
|
+
font-weight: 600;
|
|
78
|
+
text-transform: uppercase;
|
|
79
|
+
letter-spacing: 0.5px;
|
|
80
|
+
margin-bottom: 12px;
|
|
81
|
+
}
|
|
82
|
+
.code {
|
|
83
|
+
font-family: 'Courier New', monospace;
|
|
84
|
+
font-size: 32px;
|
|
85
|
+
font-weight: 700;
|
|
86
|
+
color: #2d3748;
|
|
87
|
+
letter-spacing: 4px;
|
|
88
|
+
user-select: all;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
padding: 8px;
|
|
91
|
+
border-radius: 6px;
|
|
92
|
+
transition: background 0.2s;
|
|
93
|
+
}
|
|
94
|
+
.code:hover {
|
|
95
|
+
background: #edf2f7;
|
|
96
|
+
}
|
|
97
|
+
.copy-hint {
|
|
98
|
+
color: #a0aec0;
|
|
99
|
+
font-size: 12px;
|
|
100
|
+
margin-top: 8px;
|
|
101
|
+
}
|
|
102
|
+
.url-container {
|
|
103
|
+
margin-bottom: 32px;
|
|
104
|
+
}
|
|
105
|
+
.url-label {
|
|
106
|
+
color: #4a5568;
|
|
107
|
+
font-size: 14px;
|
|
108
|
+
font-weight: 600;
|
|
109
|
+
margin-bottom: 12px;
|
|
110
|
+
}
|
|
111
|
+
.url-link {
|
|
112
|
+
display: inline-block;
|
|
113
|
+
color: #4299e1;
|
|
114
|
+
text-decoration: none;
|
|
115
|
+
font-size: 16px;
|
|
116
|
+
padding: 12px 24px;
|
|
117
|
+
border: 2px solid #4299e1;
|
|
118
|
+
border-radius: 8px;
|
|
119
|
+
transition: all 0.2s;
|
|
120
|
+
font-weight: 600;
|
|
121
|
+
}
|
|
122
|
+
.url-link:hover {
|
|
123
|
+
background: #4299e1;
|
|
124
|
+
color: white;
|
|
125
|
+
transform: translateY(-2px);
|
|
126
|
+
box-shadow: 0 4px 12px rgba(66, 153, 225, 0.4);
|
|
127
|
+
}
|
|
128
|
+
.status {
|
|
129
|
+
display: flex;
|
|
130
|
+
align-items: center;
|
|
131
|
+
justify-content: center;
|
|
132
|
+
gap: 12px;
|
|
133
|
+
color: #718096;
|
|
134
|
+
font-size: 14px;
|
|
135
|
+
margin-top: 32px;
|
|
136
|
+
padding-top: 24px;
|
|
137
|
+
border-top: 1px solid #e2e8f0;
|
|
138
|
+
}
|
|
139
|
+
.spinner {
|
|
140
|
+
width: 20px;
|
|
141
|
+
height: 20px;
|
|
142
|
+
border: 3px solid #e2e8f0;
|
|
143
|
+
border-top-color: #4299e1;
|
|
144
|
+
border-radius: 50%;
|
|
145
|
+
animation: spin 0.8s linear infinite;
|
|
146
|
+
}
|
|
147
|
+
@keyframes spin {
|
|
148
|
+
to { transform: rotate(360deg); }
|
|
149
|
+
}
|
|
150
|
+
.instructions {
|
|
151
|
+
background: #edf2f7;
|
|
152
|
+
border-radius: 8px;
|
|
153
|
+
padding: 16px;
|
|
154
|
+
margin-bottom: 24px;
|
|
155
|
+
text-align: left;
|
|
156
|
+
}
|
|
157
|
+
.instructions ol {
|
|
158
|
+
margin-left: 20px;
|
|
159
|
+
color: #4a5568;
|
|
160
|
+
font-size: 14px;
|
|
161
|
+
line-height: 1.8;
|
|
162
|
+
}
|
|
163
|
+
.instructions li {
|
|
164
|
+
margin-bottom: 8px;
|
|
165
|
+
}
|
|
166
|
+
@media (max-width: 600px) {
|
|
167
|
+
.container {
|
|
168
|
+
padding: 32px 24px;
|
|
169
|
+
}
|
|
170
|
+
h1 {
|
|
171
|
+
font-size: 24px;
|
|
172
|
+
}
|
|
173
|
+
.code {
|
|
174
|
+
font-size: 24px;
|
|
175
|
+
letter-spacing: 2px;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
</style>
|
|
179
|
+
</head>
|
|
180
|
+
<body>
|
|
181
|
+
<div class="container">
|
|
182
|
+
<h1>AWS Builder ID Authentication</h1>
|
|
183
|
+
<p class="subtitle">Complete the authentication in your browser</p>
|
|
184
|
+
|
|
185
|
+
<div class="instructions">
|
|
186
|
+
<ol>
|
|
187
|
+
<li>A browser window will open automatically</li>
|
|
188
|
+
<li>Enter the code shown below</li>
|
|
189
|
+
<li>Complete the authentication process</li>
|
|
190
|
+
</ol>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div class="code-container">
|
|
194
|
+
<div class="code-label">Your Code</div>
|
|
195
|
+
<div class="code" onclick="copyCode()">${escapedCode}</div>
|
|
196
|
+
<div class="copy-hint">Click to copy</div>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div class="url-container">
|
|
200
|
+
<div class="url-label">Verification URL</div>
|
|
201
|
+
<a href="${escapedUrl}" target="_blank" class="url-link">Open Browser</a>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div class="status">
|
|
205
|
+
<div class="spinner"></div>
|
|
206
|
+
<span>Waiting for authentication...</span>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<script>
|
|
211
|
+
const statusUrl = '${escapedStatusUrl}';
|
|
212
|
+
const verificationUrl = '${escapedUrl}';
|
|
213
|
+
|
|
214
|
+
function copyCode() {
|
|
215
|
+
const code = '${escapedCode}';
|
|
216
|
+
navigator.clipboard.writeText(code).then(() => {
|
|
217
|
+
const codeEl = document.querySelector('.code');
|
|
218
|
+
const originalBg = codeEl.style.background;
|
|
219
|
+
codeEl.style.background = '#48bb78';
|
|
220
|
+
codeEl.style.color = 'white';
|
|
221
|
+
setTimeout(() => {
|
|
222
|
+
codeEl.style.background = originalBg;
|
|
223
|
+
codeEl.style.color = '#2d3748';
|
|
224
|
+
}, 300);
|
|
225
|
+
}).catch(() => {});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
window.addEventListener('load', () => {
|
|
229
|
+
setTimeout(() => {
|
|
230
|
+
window.open(verificationUrl, '_blank');
|
|
231
|
+
}, 500);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
async function checkStatus() {
|
|
235
|
+
try {
|
|
236
|
+
const response = await fetch(statusUrl);
|
|
237
|
+
const data = await response.json();
|
|
238
|
+
|
|
239
|
+
if (data.status === 'success') {
|
|
240
|
+
window.location.href = '/success';
|
|
241
|
+
} else if (data.status === 'failed' || data.status === 'timeout') {
|
|
242
|
+
window.location.href = '/error?message=' + encodeURIComponent(data.message || 'Authentication failed');
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error('Status check failed:', error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
setInterval(checkStatus, 2000);
|
|
250
|
+
checkStatus();
|
|
251
|
+
</script>
|
|
252
|
+
</body>
|
|
253
|
+
</html>`;
|
|
254
|
+
}
|
|
255
|
+
export function getSuccessHtml() {
|
|
256
|
+
return `<!DOCTYPE html>
|
|
257
|
+
<html lang="en">
|
|
258
|
+
<head>
|
|
259
|
+
<meta charset="UTF-8">
|
|
260
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
261
|
+
<title>Authentication Successful</title>
|
|
262
|
+
<style>
|
|
263
|
+
* {
|
|
264
|
+
margin: 0;
|
|
265
|
+
padding: 0;
|
|
266
|
+
box-sizing: border-box;
|
|
267
|
+
}
|
|
268
|
+
body {
|
|
269
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
270
|
+
background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
|
|
271
|
+
min-height: 100vh;
|
|
272
|
+
display: flex;
|
|
273
|
+
align-items: center;
|
|
274
|
+
justify-content: center;
|
|
275
|
+
padding: 20px;
|
|
276
|
+
}
|
|
277
|
+
.container {
|
|
278
|
+
background: white;
|
|
279
|
+
border-radius: 16px;
|
|
280
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
281
|
+
max-width: 450px;
|
|
282
|
+
width: 100%;
|
|
283
|
+
padding: 48px 40px;
|
|
284
|
+
text-align: center;
|
|
285
|
+
animation: slideIn 0.4s ease-out;
|
|
286
|
+
}
|
|
287
|
+
@keyframes slideIn {
|
|
288
|
+
from {
|
|
289
|
+
opacity: 0;
|
|
290
|
+
transform: scale(0.9);
|
|
291
|
+
}
|
|
292
|
+
to {
|
|
293
|
+
opacity: 1;
|
|
294
|
+
transform: scale(1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
.checkmark {
|
|
298
|
+
width: 80px;
|
|
299
|
+
height: 80px;
|
|
300
|
+
margin: 0 auto 24px;
|
|
301
|
+
position: relative;
|
|
302
|
+
}
|
|
303
|
+
.checkmarkcle {
|
|
304
|
+
width: 80px;
|
|
305
|
+
height: 80px;
|
|
306
|
+
border-radius: 50%;
|
|
307
|
+
background: #48bb78;
|
|
308
|
+
animation: scaleIn 0.5s ease-out;
|
|
309
|
+
}
|
|
310
|
+
@keyframes scaleIn {
|
|
311
|
+
from {
|
|
312
|
+
transform: scale(0);
|
|
313
|
+
}
|
|
314
|
+
to {
|
|
315
|
+
transform: scale(1);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
.checkmark-check {
|
|
319
|
+
position: absolute;
|
|
320
|
+
top: 50%;
|
|
321
|
+
left: 50%;
|
|
322
|
+
transform: translate(-50%, -50%);
|
|
323
|
+
width: 30px;
|
|
324
|
+
height: 50px;
|
|
325
|
+
border: solid white;
|
|
326
|
+
border-width: 0 6px 6px 0;
|
|
327
|
+
transform: translate(-50%, -60%) rotate(45deg);
|
|
328
|
+
animation: checkmark 0.5s 0.3s ease-out forwards;
|
|
329
|
+
opacity: 0;
|
|
330
|
+
}
|
|
331
|
+
@keyframes checkmark {
|
|
332
|
+
to {
|
|
333
|
+
opacity: 1;
|
|
334
|
+
transform: translate(-50%, -60%) rotate(45deg) scale(1);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
h1 {
|
|
338
|
+
color: #1a202c;
|
|
339
|
+
font-size: 28px;
|
|
340
|
+
font-weight: 700;
|
|
341
|
+
margin-bottom: 12px;
|
|
342
|
+
}
|
|
343
|
+
.message {
|
|
344
|
+
color: #718096;
|
|
345
|
+
font-size: 16px;
|
|
346
|
+
line-height: 1.6;
|
|
347
|
+
margin-bottom: 24px;
|
|
348
|
+
}
|
|
349
|
+
.auto-close {
|
|
350
|
+
color: #a0aec0;
|
|
351
|
+
font-size: 14px;
|
|
352
|
+
padding-top: 24px;
|
|
353
|
+
border-top: 1px solid #e2e8f0;
|
|
354
|
+
}
|
|
355
|
+
@media (max-width: 600px) {
|
|
356
|
+
.container {
|
|
357
|
+
padding: 32px 24px;
|
|
358
|
+
}
|
|
359
|
+
h1 {
|
|
360
|
+
font-size: 24px;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
</style>
|
|
364
|
+
</head>
|
|
365
|
+
<body>
|
|
366
|
+
<div class="container">
|
|
367
|
+
<div class="checkmark">
|
|
368
|
+
<div class="checkmark-circle"></div>
|
|
369
|
+
<div class="checkmark-check"></div>
|
|
370
|
+
</div>
|
|
371
|
+
<h1>Authentication Successful!</h1>
|
|
372
|
+
<p class="message">You have been successfully authenticated with AWS Builder ID. You can now close this window and return to your terminal.</p>
|
|
373
|
+
<div class="auto-close">This window will close automatically in <span id="countdown">3</span> seconds</div>
|
|
374
|
+
</div>
|
|
375
|
+
|
|
376
|
+
<script>
|
|
377
|
+
let seconds = 3;
|
|
378
|
+
const countdownEl = document.getElementById('countdown');
|
|
379
|
+
|
|
380
|
+
const interval = setInterval(() => {
|
|
381
|
+
seconds--;
|
|
382
|
+
if (countdownEl) {
|
|
383
|
+
countdownEl.textContent = seconds.toString();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (seconds <= 0) {
|
|
387
|
+
clearInterval(interval);
|
|
388
|
+
try {
|
|
389
|
+
window.close();
|
|
390
|
+
} catch (e) {
|
|
391
|
+
console.log('Couluto-close window');
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}, 1000);
|
|
395
|
+
</script>
|
|
396
|
+
</body>
|
|
397
|
+
</html>`;
|
|
398
|
+
}
|
|
399
|
+
export function getErrorHtml(message) {
|
|
400
|
+
const escapedMessage = escapeHtml(message);
|
|
401
|
+
return `<!DOCTYPE html>
|
|
402
|
+
<html lang="en">
|
|
403
|
+
<head>
|
|
404
|
+
<meta charset="UTF-8">
|
|
405
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
406
|
+
<title>Authentication Failed</title>
|
|
407
|
+
<style>
|
|
408
|
+
* {
|
|
409
|
+
margin: 0;
|
|
410
|
+
padding: 0;
|
|
411
|
+
box-sizing: border-box;
|
|
412
|
+
}
|
|
413
|
+
body {
|
|
414
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
415
|
+
background: linear-gradient(135deg, #fc8181 0%, #f56565 100%);
|
|
416
|
+
min-height: 100vh;
|
|
417
|
+
display: flex;
|
|
418
|
+
align-items: center;
|
|
419
|
+
justify-content: center;
|
|
420
|
+
padding: 20px;
|
|
421
|
+
}
|
|
422
|
+
.container {
|
|
423
|
+
background: white;
|
|
424
|
+
border-radius: 16px;
|
|
425
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
426
|
+
max-width: 450px;
|
|
427
|
+
width: 100%;
|
|
428
|
+
padding: 48px 40px;
|
|
429
|
+
text-align: center;
|
|
430
|
+
animation: slideIn 0.4s ease-out;
|
|
431
|
+
}
|
|
432
|
+
@keyframes slideIn {
|
|
433
|
+
from {
|
|
434
|
+
opacity: 0;
|
|
435
|
+
transform: scale(0.9);
|
|
436
|
+
}
|
|
437
|
+
to {
|
|
438
|
+
opacity: 1;
|
|
439
|
+
transform: scale(1);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
.error-icon {
|
|
443
|
+
width: 80px;
|
|
444
|
+
height: 80px;
|
|
445
|
+
margin: 0 auto 24px;
|
|
446
|
+
position: relative;
|
|
447
|
+
}
|
|
448
|
+
.error-circle {
|
|
449
|
+
width: 80px;
|
|
450
|
+
height: 80px;
|
|
451
|
+
border-radius: 50%;
|
|
452
|
+
background: #fc8181;
|
|
453
|
+
animation: scaleIn 0.5s ease-out;
|
|
454
|
+
}
|
|
455
|
+
@keyframes scaleIn {
|
|
456
|
+
from {
|
|
457
|
+
transform: scale(0);
|
|
458
|
+
}
|
|
459
|
+
to {
|
|
460
|
+
transform: scale(1);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
.error-x {
|
|
464
|
+
position: absolute;
|
|
465
|
+
top: 50%;
|
|
466
|
+
left: 50%;
|
|
467
|
+
transform: translate(-50%, -50%);
|
|
468
|
+
width: 40px;
|
|
469
|
+
height: 40px;
|
|
470
|
+
}
|
|
471
|
+
.error-x::before,
|
|
472
|
+
.error-x::after {
|
|
473
|
+
content: '';
|
|
474
|
+
position: absolute;
|
|
475
|
+
top: 50%;
|
|
476
|
+
left: 50%;
|
|
477
|
+
width: 40px;
|
|
478
|
+
height: 6px;
|
|
479
|
+
background: white;
|
|
480
|
+
border-radius: 3px;
|
|
481
|
+
animation: xmark 0.5s 0.3s ease-out forwards;
|
|
482
|
+
opacity: 0;
|
|
483
|
+
}
|
|
484
|
+
.error-x::before {
|
|
485
|
+
transform: translate(-50%, -50%) rotate(45deg);
|
|
486
|
+
}
|
|
487
|
+
.error-x::after {
|
|
488
|
+
transform: translate(-50%, -50%) rotate(-45deg);
|
|
489
|
+
}
|
|
490
|
+
@keyframes xmark {
|
|
491
|
+
to {
|
|
492
|
+
opacity: 1;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
h1 {
|
|
496
|
+
color: #1a202c;
|
|
497
|
+
font-size: 28px;
|
|
498
|
+
font-weight: 700;
|
|
499
|
+
margin-bottom: 12px;
|
|
500
|
+
}
|
|
501
|
+
.message {
|
|
502
|
+
color: #718096;
|
|
503
|
+
font-size: 16px;
|
|
504
|
+
line-height: 1.6;
|
|
505
|
+
margin-bottom: 16px;
|
|
506
|
+
}
|
|
507
|
+
.error-details {
|
|
508
|
+
background: #fff5f5;
|
|
509
|
+
border: 1px solid #feb2b2;
|
|
510
|
+
border-radius: 8px;
|
|
511
|
+
padding: 16px;
|
|
512
|
+
margin-bottom: 24px;
|
|
513
|
+
color: #c53030;
|
|
514
|
+
font-size: 14px;
|
|
515
|
+
word-break: break-word;
|
|
516
|
+
}
|
|
517
|
+
.instruction {
|
|
518
|
+
color: #4a5568;
|
|
519
|
+
font-size: 15px;
|
|
520
|
+
margin-bottom: 24px;
|
|
521
|
+
}
|
|
522
|
+
.auto-close {
|
|
523
|
+
color: #a0aec0;
|
|
524
|
+
font-size: 14px;
|
|
525
|
+
padding-top: 24px;
|
|
526
|
+
border-top: 1px solid #e2e8f0;
|
|
527
|
+
}
|
|
528
|
+
@media (max-width: 600px) {
|
|
529
|
+
.container {
|
|
530
|
+
padding: 32px 24px;
|
|
531
|
+
}
|
|
532
|
+
h1 {
|
|
533
|
+
font-size: 24px;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
</style>
|
|
537
|
+
</head>
|
|
538
|
+
<body>
|
|
539
|
+
<div class="container">
|
|
540
|
+
<div class="error-icon">
|
|
541
|
+
<div class="error-circle"></div>
|
|
542
|
+
<div class="error-x"></div>
|
|
543
|
+
</div>
|
|
544
|
+
<h1>Authentication Failed</h1>
|
|
545
|
+
<p class="message">We were unable to complete the authentication process.</p>
|
|
546
|
+
<div class="error-details">${escapedMessage}</div>
|
|
547
|
+
<p class="instruction">You can close this window and try again from your terminal.</p>
|
|
548
|
+
<div class="auto-close">This window will close automatically in <span id="countdown">5</span> seconds</div>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<script>
|
|
552
|
+
let seconds = 5;
|
|
553
|
+
const countdownEl = document.getElementById('countdown');
|
|
554
|
+
|
|
555
|
+
const interval = setInterval(() => {
|
|
556
|
+
seconds--;
|
|
557
|
+
if (countdownEl) {
|
|
558
|
+
countdownEl.textContent = seconds.toString();
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (seconds <= 0) {
|
|
562
|
+
clearInterval(interval);
|
|
563
|
+
try {
|
|
564
|
+
window.close();
|
|
565
|
+
} catch (e) {
|
|
566
|
+
console.log('Could not auto-close window');
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}, 1000);
|
|
570
|
+
</script>
|
|
571
|
+
</body>
|
|
572
|
+
</html>`;
|
|
573
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function promptAddAnotherAccount(currentCount: number): Promise<boolean>;
|
|
2
|
+
export type LoginMode = 'add' | 'fresh';
|
|
3
|
+
export interface ExistingAccountInfo {
|
|
4
|
+
email?: string;
|
|
5
|
+
index: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function promptLoginMode(existingAccounts: ExistingAccountInfo[]): Promise<LoginMode>;
|
|
8
|
+
export type AuthMethod = 'oauth' | 'apikey';
|
|
9
|
+
export declare function promptAuthMethod(): Promise<AuthMethod>;
|
|
10
|
+
export declare function promptApiKey(): Promise<string>;
|
|
11
|
+
export declare function promptEmail(): Promise<string>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
3
|
+
export async function promptAddAnotherAccount(currentCount) {
|
|
4
|
+
const rl = createInterface({ input, output });
|
|
5
|
+
try {
|
|
6
|
+
const answer = await rl.question(`Add another account? (${currentCount} added) (y/n): `);
|
|
7
|
+
const normalized = answer.trim().toLowerCase();
|
|
8
|
+
return normalized === 'y' || normalized === 'yes';
|
|
9
|
+
}
|
|
10
|
+
finally {
|
|
11
|
+
rl.close();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function promptLoginMode(existingAccounts) {
|
|
15
|
+
const rl = createInterface({ input, output });
|
|
16
|
+
try {
|
|
17
|
+
console.log(`\n${existingAccounts.length} account(s) saved:`);
|
|
18
|
+
for (const acc of existingAccounts) {
|
|
19
|
+
const label = acc.email || `Account ${acc.index + 1}`;
|
|
20
|
+
console.log(` ${acc.index + 1}. ${label}`);
|
|
21
|
+
}
|
|
22
|
+
console.log('');
|
|
23
|
+
while (true) {
|
|
24
|
+
const answer = await rl.question('(a)dd new account(s) or (f)resh start? [a/f]: ');
|
|
25
|
+
const normalized = answer.trim().toLowerCase();
|
|
26
|
+
if (normalized === 'a' || normalized === 'add') {
|
|
27
|
+
return 'add';
|
|
28
|
+
}
|
|
29
|
+
if (normalized === 'f' || normalized === 'fresh') {
|
|
30
|
+
return 'fresh';
|
|
31
|
+
}
|
|
32
|
+
console.log("Please enter 'a' to add accounts or 'f' to start fresh.");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
rl.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function promptAuthMethod() {
|
|
40
|
+
const rl = createInterface({ input, output });
|
|
41
|
+
try {
|
|
42
|
+
while (true) {
|
|
43
|
+
const answer = await rl.question('Choose auth method: (o)auth or (a)pi key? [o/a]: ');
|
|
44
|
+
const normalized = answer.trim().toLowerCase();
|
|
45
|
+
if (normalized === 'o' || normalized === 'oauth') {
|
|
46
|
+
return 'oauth';
|
|
47
|
+
}
|
|
48
|
+
if (normalized === 'a' || normalized === 'apikey' || normalized === 'api') {
|
|
49
|
+
return 'apikey';
|
|
50
|
+
}
|
|
51
|
+
console.log("Please enter 'o' for OAuth or 'a' for API Key.");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
rl.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function promptApiKey() {
|
|
59
|
+
const rl = createInterface({ input, output });
|
|
60
|
+
try {
|
|
61
|
+
const answer = await rl.question('Enter your iFlow API Key (sk-...): ');
|
|
62
|
+
return answer.trim();
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
rl.close();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export async function promptEmail() {
|
|
69
|
+
const rl = createInterface({ input, output });
|
|
70
|
+
try {
|
|
71
|
+
const answer = await rl.question('Enter email (optional, for display): ');
|
|
72
|
+
return answer.trim() || 'api-key-user';
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
rl.close();
|
|
76
|
+
}
|
|
77
|
+
}
|