@terminus-ai/cli 0.0.1

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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1055 -0
  3. package/bin/agent-discovery.mjs +71 -0
  4. package/bin/agent-icon.mjs +77 -0
  5. package/bin/agent-models.mjs +77 -0
  6. package/bin/agent-type.mjs +51 -0
  7. package/bin/agentdev.mjs +657 -0
  8. package/bin/app-route-script.mjs +59 -0
  9. package/bin/app-runtime-contract.mjs +2 -0
  10. package/bin/appdev-remote.mjs +346 -0
  11. package/bin/appdev.mjs +4446 -0
  12. package/bin/apps.mjs +5512 -0
  13. package/bin/capability-calls.mjs +437 -0
  14. package/bin/capsule-data.mjs +260 -0
  15. package/bin/client.mjs +189 -0
  16. package/bin/commands.mjs +1194 -0
  17. package/bin/dev-capsules.mjs +1599 -0
  18. package/bin/dev-contract.mjs +262 -0
  19. package/bin/dev-data.mjs +287 -0
  20. package/bin/dev-members.mjs +18 -0
  21. package/bin/dev-net.mjs +316 -0
  22. package/bin/dev-notification-popup.mjs +628 -0
  23. package/bin/dev-ports.mjs +567 -0
  24. package/bin/dev-server-binding.mjs +35 -0
  25. package/bin/dev-server-ops.mjs +1086 -0
  26. package/bin/dev-ui/IoskeleyMono-400.woff2 +0 -0
  27. package/bin/dev-ui/IoskeleyMono-600.woff2 +0 -0
  28. package/bin/dev-ui/OFL.txt +92 -0
  29. package/bin/dev-ui/agent-robot.webp +0 -0
  30. package/bin/dev-ui/app.js +5217 -0
  31. package/bin/dev-ui/highlight.js +195 -0
  32. package/bin/dev-ui/index.html +34 -0
  33. package/bin/dev-ui/style.css +3640 -0
  34. package/bin/devlint.mjs +112 -0
  35. package/bin/devserver.mjs +2127 -0
  36. package/bin/devtriggers.mjs +367 -0
  37. package/bin/endpoints.mjs +156 -0
  38. package/bin/errors.mjs +61 -0
  39. package/bin/files.mjs +169 -0
  40. package/bin/horizontal-capabilities/v1/contract.json +280 -0
  41. package/bin/http.mjs +500 -0
  42. package/bin/lint-manifests/justbash-commands.json +88 -0
  43. package/bin/lint-manifests/python-stdlib.json +295 -0
  44. package/bin/login-page.mjs +488 -0
  45. package/bin/schedules.mjs +664 -0
  46. package/bin/server-sandbox.mjs +204 -0
  47. package/bin/servicedev.mjs +425 -0
  48. package/bin/sync.mjs +357 -0
  49. package/bin/terminus.js +3666 -0
  50. package/bin/toolchain.mjs +125 -0
  51. package/bin/vendor/app-runtime-v1/app-host.json +124 -0
  52. package/bin/vendor/app-runtime-v1/capability-calls.json +412 -0
  53. package/bin/vendor/app-runtime-v1/doors.json +2867 -0
  54. package/bin/vendor/appd/node-harness.mjs +209 -0
  55. package/bin/vendor/appd/python-harness.py +12 -0
  56. package/bin/vendor/appd/server-protocol.json +84 -0
  57. package/bin/vendor/where.mjs +541 -0
  58. package/bin/versioning.mjs +72 -0
  59. package/bin/write-rules.mjs +398 -0
  60. package/package.json +41 -0
@@ -0,0 +1,488 @@
1
+ // The page the browser lands on when a `terminus login` round trip comes back
2
+ // to the loopback server. It is the last frame of the auth flow, so it is a
3
+ // flat print of the panel the user just left: `AuthActionShell` — the water
4
+ // background, the large logo lockup, and one `.ink-glass-pane` card holding an
5
+ // `AuthStatusMark` + `AuthActionHeader`. Same relationship the transactional
6
+ // email has to the same panel (`terminus-backend/src/email.rs`), and the same
7
+ // obligation: tokens are copied from `terminus-frontend/app/globals.css`, so
8
+ // re-check this file when they move.
9
+ //
10
+ // Everything is inlined. This is served by a one-shot HTTP server on
11
+ // 127.0.0.1 with no asset routes, and it is on screen for a few seconds, so
12
+ // there is nothing to cache and nothing to split out.
13
+ //
14
+ // Two things are fetched from the network, and both degrade to a page that
15
+ // still looks finished if the machine is offline:
16
+ // - the two webfonts, from the same Google Fonts source `lib/fonts.ts`
17
+ // builds them from, with the fallback tails `globals.css` declares;
18
+ // - the crystal mark, from the web origin the login was started against,
19
+ // exactly as the email does. It removes itself if it does not load.
20
+ //
21
+ // Deliberately no monospace anywhere, so no shell command is named in the
22
+ // copy: IoskeleyMono is vendored inside the frontend bundle and cannot be
23
+ // served from here, and a code voice that settles on the platform's mono is
24
+ // the one font outcome the type system rules out.
25
+
26
+ const FONT_TITLE = "'EB Garamond',Georgia,'Times New Roman',serif";
27
+ const FONT_BODY = "Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif";
28
+ const FONT_HREF =
29
+ "https://fonts.googleapis.com/css2?family=EB+Garamond:wght@600&family=Inter:wght@400;500;600&display=swap";
30
+
31
+ // Only the fractal-noise pass from `WaterBackground`; every other layer is a
32
+ // gradient, so this is the whole of the component's asset payload.
33
+ const GRAIN_DATA_URI =
34
+ "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='200'%20height='200'%3E%3Cfilter%20id='n'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.9'%20numOctaves='2'%20stitchTiles='stitch'/%3E%3C/filter%3E%3Crect%20width='200'%20height='200'%20filter='url(%23n)'%20opacity='0.55'/%3E%3C/svg%3E";
35
+
36
+ const STYLES = `
37
+ :root { color-scheme: light; }
38
+
39
+ * { box-sizing: border-box; }
40
+
41
+ body {
42
+ position: relative;
43
+ margin: 0;
44
+ min-height: 100dvh;
45
+ overflow-x: hidden;
46
+ background-color: #fbfdff;
47
+ color: #1c1917;
48
+ font-family: ${FONT_BODY};
49
+ -webkit-font-smoothing: antialiased;
50
+ text-rendering: optimizeLegibility;
51
+ }
52
+
53
+ /* The running water the product sits on. Fixed rather than absolute: this
54
+ page has no scroll of its own, and fixed keeps the basin behind the card
55
+ when a narrow window forces one. */
56
+ .water {
57
+ position: fixed;
58
+ inset: 0;
59
+ z-index: -1;
60
+ overflow: hidden;
61
+ pointer-events: none;
62
+ }
63
+
64
+ .water-basin {
65
+ position: absolute;
66
+ inset: 0;
67
+ background: linear-gradient(180deg, #fbfdff 0%, #edf8ff 38%, #e8f4fb 66%, #fbfdff 100%);
68
+ }
69
+
70
+ /* Tailwind composes this from a translate and a blur on separate elements;
71
+ one element with negative margins is the same picture without stacking a
72
+ filter on top of a transform. */
73
+ .water-glow {
74
+ position: absolute;
75
+ left: 50%;
76
+ top: 52%;
77
+ width: 84rem;
78
+ height: 46rem;
79
+ margin-left: -42rem;
80
+ margin-top: -23rem;
81
+ border-radius: 50%;
82
+ background: radial-gradient(closest-side, rgba(147, 210, 248, 0.5), rgba(186, 230, 253, 0.3) 52%, transparent 76%);
83
+ filter: blur(40px);
84
+ }
85
+
86
+ .water-current {
87
+ position: absolute;
88
+ top: -24%;
89
+ bottom: -24%;
90
+ left: -50%;
91
+ width: 300%;
92
+ background-repeat: repeat-x;
93
+ background-size: 25% 100%;
94
+ filter: blur(42px);
95
+ -webkit-mask-image: linear-gradient(180deg, transparent 3%, black 24%, black 78%, transparent 97%);
96
+ mask-image: linear-gradient(180deg, transparent 3%, black 24%, black 78%, transparent 97%);
97
+ transform-origin: center;
98
+ will-change: transform;
99
+ }
100
+
101
+ .water-current-a {
102
+ background-image:
103
+ radial-gradient(ellipse 38% 8% at 8% 35%, rgb(224 242 254 / 0.82), transparent 74%),
104
+ radial-gradient(ellipse 44% 10% at 43% 54%, rgb(125 211 252 / 0.52), transparent 76%),
105
+ radial-gradient(ellipse 31% 7% at 72% 31%, rgb(255 255 255 / 0.68), transparent 74%),
106
+ radial-gradient(ellipse 48% 11% at 96% 68%, rgb(96 165 250 / 0.3), transparent 78%);
107
+ animation: water-current-a 21s linear -14s infinite;
108
+ }
109
+
110
+ .water-current-b {
111
+ background-image:
112
+ radial-gradient(ellipse 42% 9% at 16% 70%, rgb(186 230 253 / 0.7), transparent 76%),
113
+ radial-gradient(ellipse 29% 7% at 45% 38%, rgb(147 197 253 / 0.42), transparent 74%),
114
+ radial-gradient(ellipse 46% 9% at 77% 61%, rgb(224 242 254 / 0.84), transparent 78%),
115
+ radial-gradient(ellipse 33% 8% at 98% 43%, rgb(125 211 252 / 0.34), transparent 75%);
116
+ animation: water-current-b 31s linear -23s infinite;
117
+ }
118
+
119
+ .water-ribbon {
120
+ position: absolute;
121
+ left: -50%;
122
+ width: 300%;
123
+ background-repeat: repeat-x;
124
+ background-size: 25% 100%;
125
+ border-radius: 50%;
126
+ filter: blur(20px);
127
+ opacity: 0.5;
128
+ will-change: transform;
129
+ }
130
+
131
+ .water-ribbon-a {
132
+ top: 25%;
133
+ height: 10rem;
134
+ background-image: linear-gradient(
135
+ 90deg,
136
+ transparent 0%,
137
+ rgb(255 255 255 / 0.04) 8%,
138
+ rgb(255 255 255 / 0.68) 27%,
139
+ rgb(125 211 252 / 0.12) 49%,
140
+ rgb(255 255 255 / 0.56) 73%,
141
+ transparent 100%
142
+ );
143
+ animation: water-ribbon-a 27s linear -18s infinite;
144
+ }
145
+
146
+ .water-ribbon-b {
147
+ top: 64%;
148
+ height: 8rem;
149
+ background-image: linear-gradient(
150
+ 90deg,
151
+ transparent 0%,
152
+ rgb(125 211 252 / 0.06) 12%,
153
+ rgb(255 255 255 / 0.58) 35%,
154
+ rgb(96 165 250 / 0.11) 58%,
155
+ rgb(255 255 255 / 0.5) 79%,
156
+ transparent 100%
157
+ );
158
+ animation: water-ribbon-b 36s linear -25s infinite;
159
+ }
160
+
161
+ .water-grain {
162
+ position: absolute;
163
+ inset: 0 -200px 0 0;
164
+ opacity: 0.1;
165
+ mix-blend-mode: soft-light;
166
+ background-image: url("${GRAIN_DATA_URI}");
167
+ will-change: transform;
168
+ animation: water-grain 34s linear -12s infinite;
169
+ }
170
+
171
+ @keyframes water-current-a {
172
+ from { transform: rotate(-2deg) translate3d(0, 1.5%, 0); }
173
+ to { transform: rotate(-2deg) translate3d(-25%, 1.5%, 0); }
174
+ }
175
+
176
+ @keyframes water-current-b {
177
+ from { transform: rotate(2deg) translate3d(0, -1.5%, 0); }
178
+ to { transform: rotate(2deg) translate3d(-25%, -1.5%, 0); }
179
+ }
180
+
181
+ @keyframes water-ribbon-a {
182
+ from { transform: rotate(-4deg) translate3d(0, 1rem, 0); }
183
+ to { transform: rotate(-4deg) translate3d(-25%, 1rem, 0); }
184
+ }
185
+
186
+ @keyframes water-ribbon-b {
187
+ from { transform: rotate(3deg) translate3d(0, -0.75rem, 0); }
188
+ to { transform: rotate(3deg) translate3d(-25%, -0.75rem, 0); }
189
+ }
190
+
191
+ @keyframes water-grain {
192
+ from { transform: translate3d(0, 0, 0); }
193
+ to { transform: translate3d(-200px, 0, 0); }
194
+ }
195
+
196
+ /* The logo lockup at the shell's size="large" step: 40px mark, 20px
197
+ wordmark, held back from the mark so the mark leads. */
198
+ .masthead {
199
+ position: absolute;
200
+ inset: 0 0 auto;
201
+ z-index: 10;
202
+ display: flex;
203
+ align-items: center;
204
+ height: 5rem;
205
+ padding: 0 1rem;
206
+ }
207
+
208
+ .lockup {
209
+ display: flex;
210
+ align-items: center;
211
+ gap: 12px;
212
+ }
213
+
214
+ .lockup img {
215
+ display: block;
216
+ width: 40px;
217
+ height: 40px;
218
+ }
219
+
220
+ .lockup span {
221
+ font-family: ${FONT_TITLE};
222
+ font-size: 20px;
223
+ font-weight: 600;
224
+ letter-spacing: -0.025em;
225
+ white-space: nowrap;
226
+ }
227
+
228
+ /* The card sits where the login card sat — dead centre of the viewport, the
229
+ masthead floating over it — so the last frame lands in the same place the
230
+ form the user just submitted was standing. */
231
+ .stage {
232
+ position: relative;
233
+ z-index: 1;
234
+ display: flex;
235
+ min-height: 100dvh;
236
+ align-items: center;
237
+ justify-content: center;
238
+ padding: 6rem 1rem;
239
+ }
240
+
241
+ /* .ink-glass-pane at authCardClass's measurements, verbatim. */
242
+ .card {
243
+ position: relative;
244
+ width: 100%;
245
+ max-width: 500px;
246
+ max-height: calc(100dvh - 3rem);
247
+ overflow-y: auto;
248
+ border-radius: 24px;
249
+ padding: 24px;
250
+ text-align: center;
251
+ background-color: rgb(255 255 255 / 0.52);
252
+ background-image: linear-gradient(180deg, rgb(29 78 168 / 0.02) 0%, rgb(29 78 168 / 0.08) 100%);
253
+ -webkit-backdrop-filter: blur(26px) saturate(185%);
254
+ backdrop-filter: blur(26px) saturate(185%);
255
+ box-shadow:
256
+ inset 0 0 0 0.5px rgb(8 28 74 / 0.13),
257
+ inset 0 1px 0 rgb(255 255 255 / 0.85),
258
+ inset 0 0 40px rgb(255 255 255 / 0.35),
259
+ 0 2px 8px -2px rgb(8 28 74 / 0.07),
260
+ 0 26px 60px -28px rgb(8 28 74 / 0.35);
261
+ animation: card-in 0.18s cubic-bezier(0.23, 1, 0.32, 1) both;
262
+ }
263
+
264
+ /* The specular bar. z-index: -1 stays inside the card because the
265
+ backdrop-filter above already opened a stacking context. */
266
+ .card::before {
267
+ content: "";
268
+ position: absolute;
269
+ z-index: -1;
270
+ inset: 1px 1px auto;
271
+ height: 46%;
272
+ border-radius: 1.5rem 1.5rem 0 0;
273
+ background: linear-gradient(180deg, rgb(255 255 255 / 0.5), rgb(255 255 255 / 0));
274
+ pointer-events: none;
275
+ }
276
+
277
+ /* AuthStatusMark, standing exactly where AuthMark's crystal stood on the
278
+ login card: same 44px, same mx-auto mb-4. The crystal is already in the
279
+ masthead, so this slot is free to carry the verdict instead of the brand. */
280
+ .status {
281
+ display: grid;
282
+ place-items: center;
283
+ width: 44px;
284
+ height: 44px;
285
+ margin: 0 auto 16px;
286
+ border-radius: 999px;
287
+ animation: status-in 0.3s cubic-bezier(0.23, 1, 0.32, 1) 0.06s both;
288
+ }
289
+
290
+ .status-ok { background: #ecfdf5; color: #047857; }
291
+ .status-danger { background: #fef2f2; color: #b91c1c; }
292
+
293
+ h1 {
294
+ margin: 0;
295
+ font-family: ${FONT_TITLE};
296
+ font-size: 34px;
297
+ font-weight: 600;
298
+ line-height: 1.25;
299
+ letter-spacing: -0.02em;
300
+ }
301
+
302
+ .lead {
303
+ margin: 6px 0 0;
304
+ font-size: 14px;
305
+ line-height: 24px;
306
+ color: #57534e;
307
+ }
308
+
309
+ /* Reads as one of the auth form's own fields — same rim, same translucent
310
+ fill — so the account it names looks like something the flow filled in. */
311
+ /* The address, set as type rather than boxed. It first wore the auth form's
312
+ own field — same rim, same translucent fill — which put a second card
313
+ inside the card and read as an input the reader was meant to do something
314
+ with. Nothing here is editable, so it is just the line under the sentence:
315
+ centred with the rest, and given the ink and the weight the muted lead
316
+ above it does not have. */
317
+ .account {
318
+ display: flex;
319
+ align-items: center;
320
+ justify-content: center;
321
+ gap: 8px;
322
+ margin-top: 12px;
323
+ font-size: 14px;
324
+ line-height: 20px;
325
+ font-weight: 500;
326
+ }
327
+
328
+ .account svg { flex: none; color: #78716c; }
329
+
330
+ .account span {
331
+ min-width: 0;
332
+ overflow: hidden;
333
+ text-overflow: ellipsis;
334
+ white-space: nowrap;
335
+ }
336
+
337
+ /* authTermsClass — the quiet line the login card closes on. */
338
+ .note {
339
+ margin: 24px 0 0;
340
+ font-size: 12px;
341
+ line-height: 20px;
342
+ color: #78716c;
343
+ }
344
+
345
+ @keyframes card-in {
346
+ from { opacity: 0; transform: translateY(4px); }
347
+ to { opacity: 1; transform: translateY(0); }
348
+ }
349
+
350
+ @keyframes status-in {
351
+ from { opacity: 0; transform: scale(0.85); }
352
+ to { opacity: 1; transform: scale(1); }
353
+ }
354
+
355
+ @media (min-width: 640px) {
356
+ .masthead { padding: 0 1.5rem; }
357
+ .stage { padding: 6rem 1.5rem; }
358
+ .card { padding: 32px; }
359
+ }
360
+
361
+ @media (min-width: 1024px) {
362
+ .masthead { padding: 0 2rem; }
363
+ }
364
+
365
+ /* Continuous blurred layers are needlessly expensive on phone GPUs; the
366
+ frozen composition keeps the same identity. */
367
+ @media (max-width: 640px) {
368
+ .water-current,
369
+ .water-ribbon,
370
+ .water-grain {
371
+ animation: none;
372
+ will-change: auto;
373
+ }
374
+
375
+ .water-current { filter: blur(44px); }
376
+ .water-current-a { transform: translate3d(-8%, 1.5%, 0) rotate(-2deg); }
377
+ .water-current-b { transform: translate3d(-16%, -1.5%, 0) rotate(2deg); }
378
+
379
+ h1 { font-size: 30px; }
380
+ }
381
+
382
+ @media (prefers-reduced-motion: reduce) {
383
+ .water-current,
384
+ .water-ribbon,
385
+ .water-grain,
386
+ .card,
387
+ .status {
388
+ animation: none;
389
+ }
390
+ }
391
+ `;
392
+
393
+ // AuthActionShell's CheckIcon and AlertIcon, at `size-5`.
394
+ const CHECK_ICON =
395
+ '<svg aria-hidden="true" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"'
396
+ + ' stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m4.5 12.5 5 5 10-11"/></svg>';
397
+
398
+ const ALERT_ICON =
399
+ '<svg aria-hidden="true" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"'
400
+ + ' stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8.5v5M12 16.8v.2M10.3 4'
401
+ + ' 2.9 17a2 2 0 0 0 1.7 3h14.8a2 2 0 0 0 1.7-3L13.7 4a2 2 0 0 0-3.4 0Z"/></svg>';
402
+
403
+ const MAIL_ICON =
404
+ '<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"'
405
+ + ' stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="4.5" width="19"'
406
+ + ' height="15" rx="2.5"/><path d="m3.5 6.5 8.5 6 8.5-6"/></svg>';
407
+
408
+ function escapeHtml(value) {
409
+ return String(value)
410
+ .replaceAll("&", "&amp;")
411
+ .replaceAll("<", "&lt;")
412
+ .replaceAll(">", "&gt;")
413
+ .replaceAll('"', "&quot;")
414
+ .replaceAll("'", "&#39;");
415
+ }
416
+
417
+ function page({ tone, title, lead, note, account, brandOrigin }) {
418
+ const mark = tone === "ok" ? CHECK_ICON : ALERT_ICON;
419
+ const accountRow = account
420
+ ? `<div class="account">${MAIL_ICON}<span>${escapeHtml(account)}</span></div>`
421
+ : "";
422
+ const logo = brandOrigin
423
+ ? `<img alt="" src="${escapeHtml(`${brandOrigin}/terminus-icon.png`)}" onerror="this.remove()">`
424
+ : "";
425
+
426
+ return `<!doctype html>
427
+ <html lang="en">
428
+ <head>
429
+ <meta charset="utf-8">
430
+ <meta name="viewport" content="width=device-width, initial-scale=1">
431
+ <meta name="color-scheme" content="light">
432
+ <title>${escapeHtml(title)} · Terminus</title>
433
+ <link rel="preconnect" href="https://fonts.googleapis.com">
434
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
435
+ <link rel="stylesheet" href="${FONT_HREF}">
436
+ <style>${STYLES}</style>
437
+ </head>
438
+ <body>
439
+ <div class="water" aria-hidden="true">
440
+ <div class="water-basin"></div>
441
+ <div class="water-glow"></div>
442
+ <div class="water-current water-current-a"></div>
443
+ <div class="water-current water-current-b"></div>
444
+ <div class="water-ribbon water-ribbon-a"></div>
445
+ <div class="water-ribbon water-ribbon-b"></div>
446
+ <div class="water-grain"></div>
447
+ </div>
448
+ <header class="masthead"><span class="lockup">${logo}<span>Terminus</span></span></header>
449
+ <main class="stage">
450
+ <div class="card">
451
+ <div class="status status-${tone}" aria-hidden="true">${mark}</div>
452
+ <h1>${escapeHtml(title)}</h1>
453
+ <p class="lead">${escapeHtml(lead)}</p>
454
+ ${accountRow}
455
+ <p class="note">${escapeHtml(note)}</p>
456
+ </div>
457
+ </main>
458
+ </body>
459
+ </html>
460
+ `;
461
+ }
462
+
463
+ /** The successful hand-back: the code is in hand and the terminal takes over. */
464
+ export function loginCompletePage({ email, brandOrigin } = {}) {
465
+ return page({
466
+ tone: "ok",
467
+ title: "You're signed in",
468
+ lead: "Terminus CLI is connected on this device.",
469
+ note: "You can close this tab and return to your terminal.",
470
+ account: email,
471
+ brandOrigin,
472
+ });
473
+ }
474
+
475
+ /**
476
+ * Every way the callback can arrive without a usable code. The reason is
477
+ * already on stderr in the terminal; the browser's job is to say plainly that
478
+ * this window is finished, and where the flow continues.
479
+ */
480
+ export function loginFailedPage({ reason, brandOrigin } = {}) {
481
+ return page({
482
+ tone: "danger",
483
+ title: "Sign-in didn't finish",
484
+ lead: reason || "The browser didn't hand a valid login back to the CLI.",
485
+ note: "Close this tab and start the login again from your terminal.",
486
+ brandOrigin,
487
+ });
488
+ }