@lpm-registry/cli 0.57.0 → 0.59.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 (62) hide show
  1. package/README.md +21 -45
  2. package/bin/lpm.js +2 -354
  3. package/bin/lpx.js +4 -0
  4. package/bin/native.js +154 -0
  5. package/package.json +40 -82
  6. package/CHANGELOG.md +0 -197
  7. package/LICENSE +0 -15
  8. package/index.d.ts +0 -131
  9. package/index.js +0 -31
  10. package/lib/api.js +0 -398
  11. package/lib/commands/add.js +0 -1217
  12. package/lib/commands/audit.js +0 -283
  13. package/lib/commands/cache.js +0 -209
  14. package/lib/commands/check-name.js +0 -112
  15. package/lib/commands/config.js +0 -186
  16. package/lib/commands/doctor.js +0 -140
  17. package/lib/commands/info.js +0 -215
  18. package/lib/commands/init.js +0 -146
  19. package/lib/commands/install.js +0 -264
  20. package/lib/commands/login.js +0 -595
  21. package/lib/commands/logout.js +0 -94
  22. package/lib/commands/marketplace-compare.js +0 -164
  23. package/lib/commands/marketplace-earnings.js +0 -89
  24. package/lib/commands/mcp-setup.js +0 -363
  25. package/lib/commands/npmrc.js +0 -178
  26. package/lib/commands/open.js +0 -82
  27. package/lib/commands/outdated.js +0 -291
  28. package/lib/commands/pool-stats.js +0 -100
  29. package/lib/commands/publish.js +0 -949
  30. package/lib/commands/quality.js +0 -211
  31. package/lib/commands/remove.js +0 -82
  32. package/lib/commands/run.js +0 -14
  33. package/lib/commands/search.js +0 -143
  34. package/lib/commands/setup.js +0 -221
  35. package/lib/commands/skills.js +0 -898
  36. package/lib/commands/token-rotate.js +0 -25
  37. package/lib/commands/uninstall.js +0 -107
  38. package/lib/commands/whoami.js +0 -129
  39. package/lib/config.js +0 -328
  40. package/lib/constants.js +0 -190
  41. package/lib/ecosystem.js +0 -501
  42. package/lib/editor-skills.js +0 -321
  43. package/lib/editors.js +0 -215
  44. package/lib/import-rewriter.js +0 -364
  45. package/lib/install-targets/mcp-server.js +0 -245
  46. package/lib/install-targets/vscode-extension.js +0 -178
  47. package/lib/install-targets.js +0 -82
  48. package/lib/integrity.js +0 -179
  49. package/lib/lpm-config-prompts.js +0 -102
  50. package/lib/lpm-config.js +0 -408
  51. package/lib/oidc.js +0 -166
  52. package/lib/project-utils.js +0 -152
  53. package/lib/quality/checks.js +0 -687
  54. package/lib/quality/display.js +0 -155
  55. package/lib/quality/score.js +0 -118
  56. package/lib/quality/swift-checks.js +0 -447
  57. package/lib/safe-path.js +0 -180
  58. package/lib/secure-store.js +0 -321
  59. package/lib/swift-project.js +0 -637
  60. package/lib/ui.js +0 -40
  61. package/scripts/__tests__/install-binary.test.js +0 -346
  62. package/scripts/install-binary.js +0 -571
@@ -1,595 +0,0 @@
1
- import { createHash } from "node:crypto"
2
- import http from "node:http"
3
- import { hostname, userInfo } from "node:os"
4
- import open from "open"
5
- import { request } from "../api.js"
6
- import { getRegistryUrl, setRefreshToken, setToken } from "../config.js"
7
- import { WARNING_MESSAGES } from "../constants.js"
8
- import { createSpinner, log, printHeader } from "../ui.js"
9
-
10
- function getErrorHtml(title, message) {
11
- return `
12
- <!DOCTYPE html>
13
- <html>
14
- <head>
15
- <title>LPM - ${title}</title>
16
- <link rel="preconnect" href="https://fonts.googleapis.com">
17
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
18
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@400;500;600&display=swap" rel="stylesheet">
19
- <style>
20
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
21
-
22
- :root {
23
- --bg-primary: #0a0a0b;
24
- --bg-card: #111113;
25
- --bg-subtle: #18181b;
26
- --border: #27272a;
27
- --text-primary: #fafafa;
28
- --text-secondary: #a1a1aa;
29
- --text-muted: #71717a;
30
- --error: #ef4444;
31
- --error-glow: rgba(239, 68, 68, 0.15);
32
- }
33
-
34
- body {
35
- font-family: 'Outfit', system-ui, sans-serif;
36
- min-height: 100vh;
37
- display: flex;
38
- justify-content: center;
39
- align-items: center;
40
- background: var(--bg-primary);
41
- background-image:
42
- radial-gradient(ellipse 80% 50% at 50% -20%, var(--error-glow), transparent),
43
- radial-gradient(circle at 50% 50%, var(--bg-primary), var(--bg-primary));
44
- padding: 1.5rem;
45
- }
46
-
47
- .container {
48
- width: 100%;
49
- max-width: 420px;
50
- animation: fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1);
51
- }
52
-
53
- @keyframes fadeInUp {
54
- from { opacity: 0; transform: translateY(20px); }
55
- to { opacity: 1; transform: translateY(0); }
56
- }
57
-
58
- .card {
59
- background: var(--bg-card);
60
- border: 1px solid var(--border);
61
- border-radius: 16px;
62
- padding: 2.5rem 2rem;
63
- text-align: center;
64
- position: relative;
65
- overflow: hidden;
66
- }
67
-
68
- .card::before {
69
- content: '';
70
- position: absolute;
71
- top: 0;
72
- left: 50%;
73
- transform: translateX(-50%);
74
- width: 200px;
75
- height: 1px;
76
- background: linear-gradient(90deg, transparent, var(--error), transparent);
77
- opacity: 0.6;
78
- }
79
-
80
- .icon-wrapper {
81
- width: 80px;
82
- height: 80px;
83
- margin: 0 auto 1.5rem;
84
- position: relative;
85
- display: flex;
86
- align-items: center;
87
- justify-content: center;
88
- }
89
-
90
- .icon-circle {
91
- width: 64px;
92
- height: 64px;
93
- background: var(--error);
94
- border-radius: 50%;
95
- display: flex;
96
- align-items: center;
97
- justify-content: center;
98
- box-shadow: 0 0 40px var(--error-glow), 0 0 80px var(--error-glow);
99
- animation: scaleIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.1s both;
100
- }
101
-
102
- @keyframes scaleIn {
103
- from { transform: scale(0); }
104
- to { transform: scale(1); }
105
- }
106
-
107
- .x-icon {
108
- width: 28px;
109
- height: 28px;
110
- stroke: var(--bg-primary);
111
- stroke-width: 3;
112
- stroke-linecap: round;
113
- fill: none;
114
- }
115
-
116
- .title {
117
- font-size: 1.5rem;
118
- font-weight: 600;
119
- color: var(--text-primary);
120
- margin-bottom: 0.75rem;
121
- letter-spacing: -0.02em;
122
- animation: fadeIn 0.5s ease-out 0.3s both;
123
- }
124
-
125
- @keyframes fadeIn {
126
- from { opacity: 0; }
127
- to { opacity: 1; }
128
- }
129
-
130
- .message {
131
- font-size: 0.9375rem;
132
- color: var(--text-secondary);
133
- line-height: 1.6;
134
- margin-bottom: 1.5rem;
135
- animation: fadeIn 0.5s ease-out 0.4s both;
136
- }
137
-
138
- .terminal-hint {
139
- padding: 0.75rem 1rem;
140
- background: var(--bg-subtle);
141
- border-radius: 8px;
142
- font-family: 'JetBrains Mono', monospace;
143
- font-size: 0.75rem;
144
- color: var(--text-muted);
145
- animation: fadeIn 0.5s ease-out 0.5s both;
146
- }
147
-
148
- .terminal-hint code {
149
- color: var(--error);
150
- }
151
- </style>
152
- </head>
153
- <body>
154
- <div class="container">
155
- <div class="card">
156
- <div class="icon-wrapper">
157
- <div class="icon-circle">
158
- <svg class="x-icon" viewBox="0 0 24 24">
159
- <path d="M18 6L6 18M6 6l12 12"/>
160
- </svg>
161
- </div>
162
- </div>
163
-
164
- <h1 class="title">${title}</h1>
165
- <p class="message">${message}</p>
166
-
167
- <div class="terminal-hint">
168
- Run <code>lpm login</code> in your terminal to try again
169
- </div>
170
- </div>
171
- </div>
172
- </body>
173
- </html>`
174
- }
175
-
176
- export async function login() {
177
- printHeader()
178
- const registryUrl = getRegistryUrl()
179
- const spinner = createSpinner(`Logging in to ${registryUrl}...`).start()
180
-
181
- function closeAndExit(res) {
182
- res.on("finish", () => {
183
- server.close(() => process.exit(0))
184
- })
185
- }
186
-
187
- const server = http.createServer(async (req, res) => {
188
- const url = new URL(req.url, `http://${req.headers.host}`)
189
-
190
- res.setHeader("Access-Control-Allow-Origin", "*")
191
-
192
- if (url.pathname === "/callback") {
193
- // Exchange code flow (Feature 42) — code is exchanged for token server-side
194
- const exchangeCode = url.searchParams.get("code")
195
- // Legacy fallback: direct token in URL (for older server versions)
196
- const directToken = url.searchParams.get("token")
197
-
198
- let token = directToken
199
- let refreshToken = null
200
- if (exchangeCode && !directToken) {
201
- try {
202
- const exchangeRes = await fetch(`${registryUrl}/api/cli/exchange`, {
203
- method: "POST",
204
- headers: { "Content-Type": "application/json" },
205
- body: JSON.stringify({ code: exchangeCode }),
206
- })
207
- if (exchangeRes.ok) {
208
- const data = await exchangeRes.json()
209
- token = data.token
210
- // Feature 44: store refresh token if server provides one
211
- if (data.refreshToken) {
212
- refreshToken = data.refreshToken
213
- }
214
- } else {
215
- const err = await exchangeRes.json().catch(() => ({}))
216
- throw new Error(err.error || "Exchange failed")
217
- }
218
- } catch (err) {
219
- spinner.fail(`Token exchange failed: ${err.message}`)
220
- res.writeHead(200, { "Content-Type": "text/html" })
221
- res.end(
222
- getErrorHtml(
223
- "Exchange Failed",
224
- "Failed to exchange the authorization code. Please try again.",
225
- ),
226
- )
227
- closeAndExit(res)
228
- return
229
- }
230
- }
231
-
232
- if (token) {
233
- // Store access token
234
- await setToken(token)
235
- // Store refresh token for session auth (Feature 44 Part B)
236
- if (refreshToken) {
237
- await setRefreshToken(refreshToken)
238
- }
239
-
240
- try {
241
- const response = await request("/-/whoami")
242
- if (response.ok) {
243
- const data = await response.json()
244
- spinner.succeed(`Successfully logged in as: ${data.username}`)
245
-
246
- // Show warning if personal username is not set
247
- if (!data.profile_username) {
248
- console.log("")
249
- log.warn(WARNING_MESSAGES.usernameNotSet)
250
- log.warn(WARNING_MESSAGES.usernameNotSetHint(registryUrl))
251
- }
252
-
253
- const html = `
254
- <!DOCTYPE html>
255
- <html>
256
- <head>
257
- <title>LPM - Access Granted</title>
258
- <link rel="preconnect" href="https://fonts.googleapis.com">
259
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
260
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@400;500;600&display=swap" rel="stylesheet">
261
- <style>
262
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
263
-
264
- :root {
265
- --bg-primary: #0a0a0b;
266
- --bg-card: #111113;
267
- --bg-subtle: #18181b;
268
- --border: #27272a;
269
- --text-primary: #fafafa;
270
- --text-secondary: #a1a1aa;
271
- --text-muted: #71717a;
272
- --accent: #22c55e;
273
- --accent-glow: rgba(34, 197, 94, 0.15);
274
- }
275
-
276
- body {
277
- font-family: 'Outfit', system-ui, sans-serif;
278
- min-height: 100vh;
279
- display: flex;
280
- justify-content: center;
281
- align-items: center;
282
- background: var(--bg-primary);
283
- background-image:
284
- radial-gradient(ellipse 80% 50% at 50% -20%, var(--accent-glow), transparent),
285
- radial-gradient(circle at 50% 50%, var(--bg-primary), var(--bg-primary));
286
- padding: 1.5rem;
287
- }
288
-
289
- .container {
290
- width: 100%;
291
- max-width: 420px;
292
- animation: fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1);
293
- }
294
-
295
- @keyframes fadeInUp {
296
- from { opacity: 0; transform: translateY(20px); }
297
- to { opacity: 1; transform: translateY(0); }
298
- }
299
-
300
- .card {
301
- background: var(--bg-card);
302
- border: 1px solid var(--border);
303
- border-radius: 16px;
304
- padding: 2.5rem 2rem;
305
- text-align: center;
306
- position: relative;
307
- overflow: hidden;
308
- }
309
-
310
- .card::before {
311
- content: '';
312
- position: absolute;
313
- top: 0;
314
- left: 50%;
315
- transform: translateX(-50%);
316
- width: 200px;
317
- height: 1px;
318
- background: linear-gradient(90deg, transparent, var(--accent), transparent);
319
- opacity: 0.6;
320
- }
321
-
322
- .icon-wrapper {
323
- width: 80px;
324
- height: 80px;
325
- margin: 0 auto 1.5rem;
326
- position: relative;
327
- display: flex;
328
- align-items: center;
329
- justify-content: center;
330
- }
331
-
332
- .icon-ring {
333
- position: absolute;
334
- inset: 0;
335
- border-radius: 50%;
336
- border: 2px solid var(--accent);
337
- opacity: 0;
338
- animation: ringPulse 2s ease-out 0.3s infinite;
339
- }
340
-
341
- .icon-ring:nth-child(2) { animation-delay: 0.6s; }
342
-
343
- @keyframes ringPulse {
344
- 0% { transform: scale(1); opacity: 0.6; }
345
- 100% { transform: scale(1.8); opacity: 0; }
346
- }
347
-
348
- .icon-circle {
349
- width: 64px;
350
- height: 64px;
351
- background: var(--accent);
352
- border-radius: 50%;
353
- display: flex;
354
- align-items: center;
355
- justify-content: center;
356
- box-shadow: 0 0 40px var(--accent-glow), 0 0 80px var(--accent-glow);
357
- animation: scaleIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.1s both;
358
- }
359
-
360
- @keyframes scaleIn {
361
- from { transform: scale(0); }
362
- to { transform: scale(1); }
363
- }
364
-
365
- .checkmark {
366
- width: 32px;
367
- height: 32px;
368
- stroke: var(--bg-primary);
369
- stroke-width: 3;
370
- stroke-linecap: round;
371
- stroke-linejoin: round;
372
- fill: none;
373
- }
374
-
375
- .checkmark-path {
376
- stroke-dasharray: 50;
377
- stroke-dashoffset: 50;
378
- animation: drawCheck 0.4s ease-out 0.5s forwards;
379
- }
380
-
381
- @keyframes drawCheck {
382
- to { stroke-dashoffset: 0; }
383
- }
384
-
385
- .title {
386
- font-size: 1.5rem;
387
- font-weight: 600;
388
- color: var(--text-primary);
389
- margin-bottom: 0.5rem;
390
- letter-spacing: -0.02em;
391
- animation: fadeIn 0.5s ease-out 0.3s both;
392
- }
393
-
394
- @keyframes fadeIn {
395
- from { opacity: 0; }
396
- to { opacity: 1; }
397
- }
398
-
399
- .subtitle {
400
- font-size: 0.9375rem;
401
- color: var(--text-secondary);
402
- margin-bottom: 1.5rem;
403
- animation: fadeIn 0.5s ease-out 0.4s both;
404
- }
405
-
406
- .user-badge {
407
- display: inline-flex;
408
- align-items: center;
409
- gap: 0.5rem;
410
- background: var(--bg-subtle);
411
- border: 1px solid var(--border);
412
- border-radius: 8px;
413
- padding: 0.625rem 1rem;
414
- margin-bottom: 1.5rem;
415
- animation: fadeIn 0.5s ease-out 0.5s both;
416
- }
417
-
418
- .user-icon {
419
- width: 18px;
420
- height: 18px;
421
- stroke: var(--text-muted);
422
- stroke-width: 2;
423
- fill: none;
424
- }
425
-
426
- .username {
427
- font-family: 'JetBrains Mono', monospace;
428
- font-size: 0.875rem;
429
- font-weight: 500;
430
- color: var(--text-primary);
431
- }
432
-
433
- .divider {
434
- height: 1px;
435
- background: var(--border);
436
- margin: 1.25rem 0;
437
- animation: fadeIn 0.5s ease-out 0.6s both;
438
- }
439
-
440
- .footer {
441
- display: flex;
442
- align-items: center;
443
- justify-content: center;
444
- gap: 0.5rem;
445
- animation: fadeIn 0.5s ease-out 0.7s both;
446
- }
447
-
448
- .footer-text {
449
- font-size: 0.8125rem;
450
- color: var(--text-muted);
451
- }
452
-
453
- .countdown {
454
- font-family: 'JetBrains Mono', monospace;
455
- font-size: 0.75rem;
456
- font-weight: 500;
457
- color: var(--accent);
458
- background: var(--accent-glow);
459
- padding: 0.25rem 0.5rem;
460
- border-radius: 4px;
461
- }
462
-
463
- .terminal-hint {
464
- margin-top: 1.5rem;
465
- padding: 0.75rem 1rem;
466
- background: var(--bg-subtle);
467
- border-radius: 8px;
468
- font-family: 'JetBrains Mono', monospace;
469
- font-size: 0.75rem;
470
- color: var(--text-muted);
471
- animation: fadeIn 0.5s ease-out 0.8s both;
472
- }
473
-
474
- .terminal-hint code {
475
- color: var(--accent);
476
- }
477
- </style>
478
- </head>
479
- <body>
480
- <div class="container">
481
- <div class="card">
482
- <div class="icon-wrapper">
483
- <div class="icon-ring"></div>
484
- <div class="icon-ring"></div>
485
- <div class="icon-circle">
486
- <svg class="checkmark" viewBox="0 0 24 24">
487
- <path class="checkmark-path" d="M5 12l5 5L19 7"/>
488
- </svg>
489
- </div>
490
- </div>
491
-
492
- <h1 class="title">Access Granted</h1>
493
- <p class="subtitle">CLI authentication successful</p>
494
-
495
- <div class="user-badge">
496
- <svg class="user-icon" viewBox="0 0 24 24">
497
- <circle cx="12" cy="8" r="4"/>
498
- <path d="M4 21v-2a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v2"/>
499
- </svg>
500
- <span class="username">${data.username}</span>
501
- </div>
502
-
503
- <div class="divider"></div>
504
-
505
- <div class="footer" id="footer">
506
- <span class="footer-text">Closing in</span>
507
- <span class="countdown" id="countdown">5s</span>
508
- </div>
509
-
510
- <div class="terminal-hint" id="hint">
511
- Return to your terminal to continue using <code>lpm</code>
512
- </div>
513
- </div>
514
- </div>
515
-
516
- <script>
517
- let seconds = 5;
518
- const countdown = document.getElementById('countdown');
519
- const footer = document.getElementById('footer');
520
- const hint = document.getElementById('hint');
521
-
522
- const interval = setInterval(() => {
523
- seconds--;
524
- countdown.textContent = seconds + 's';
525
- if (seconds <= 0) {
526
- clearInterval(interval);
527
- window.close();
528
- // If window didn't close (browser security), show message
529
- setTimeout(() => {
530
- footer.innerHTML = '<span class="footer-text" style="color: var(--accent);">You can close this tab now</span>';
531
- hint.innerHTML = 'Return to your terminal to continue using <code>lpm</code>';
532
- }, 100);
533
- }
534
- }, 1000);
535
- </script>
536
- </body>
537
- </html>`
538
- res.setHeader("Content-Type", "text/html")
539
- closeAndExit(res)
540
- res.end(html)
541
- } else {
542
- console.error("\nToken verification failed.")
543
- spinner.fail("Token verification failed")
544
- res.setHeader("Content-Type", "text/html")
545
- closeAndExit(res)
546
- res.end(
547
- getErrorHtml(
548
- "Invalid Token",
549
- "The authentication token could not be verified. Please try logging in again.",
550
- ),
551
- )
552
- }
553
- } catch (err) {
554
- console.error("\nError verifying token:", err.message)
555
- spinner.fail("Error verifying token")
556
- res.setHeader("Content-Type", "text/html")
557
- closeAndExit(res)
558
- res.end(
559
- getErrorHtml(
560
- "Verification Error",
561
- "An error occurred while verifying your token. Please try again.",
562
- ),
563
- )
564
- }
565
- } else {
566
- spinner.fail("No token received")
567
- res.setHeader("Content-Type", "text/html")
568
- closeAndExit(res)
569
- res.end(
570
- getErrorHtml(
571
- "No Token",
572
- "No authentication token was received. Please try logging in again.",
573
- ),
574
- )
575
- }
576
- } else {
577
- res.end("LPM CLI Login Server")
578
- }
579
- })
580
-
581
- // Compute device fingerprint for session auth (Feature 44 Part B)
582
- const fp = createHash("sha256")
583
- .update(`${hostname()}:${userInfo().username}:lpm-cli`)
584
- .digest("hex")
585
- const dn = encodeURIComponent(hostname())
586
-
587
- server.listen(0, async () => {
588
- const port = server.address().port
589
- const loginUrl = `${registryUrl}/cli/login?port=${port}&fp=${fp}&dn=${dn}`
590
-
591
- console.log(`Opening browser to: ${loginUrl}`)
592
- await open(loginUrl)
593
- console.log("Waiting for authentication...")
594
- })
595
- }
@@ -1,94 +0,0 @@
1
- /**
2
- * Logout Command
3
- *
4
- * Clears stored authentication token and optionally revokes it on the server.
5
- *
6
- * Usage:
7
- * lpm logout [options]
8
- *
9
- * Options:
10
- * --revoke Also revoke the token on the server
11
- * --clear-cache Clear local package cache
12
- *
13
- * @module cli/lib/commands/logout
14
- */
15
-
16
- import chalk from "chalk"
17
- import ora from "ora"
18
- import { request } from "../api.js"
19
- import { clearToken, getToken } from "../config.js"
20
-
21
- /**
22
- * Execute the logout command.
23
- *
24
- * @param {Object} options - Command options
25
- * @param {boolean} [options.revoke] - Whether to revoke the token on the server
26
- * @param {boolean} [options.clearCache] - Whether to clear the local cache
27
- */
28
- export async function logout(options = {}) {
29
- const spinner = ora("Logging out...").start()
30
-
31
- try {
32
- const token = await getToken()
33
-
34
- if (!token) {
35
- spinner.info("Not currently logged in.")
36
- return
37
- }
38
-
39
- // Optionally revoke the token on the server
40
- if (options.revoke) {
41
- spinner.text = "Revoking token on server..."
42
-
43
- try {
44
- const response = await request("/tokens/revoke", {
45
- method: "POST",
46
- headers: {
47
- "Content-Type": "application/json",
48
- },
49
- body: JSON.stringify({ token }),
50
- skipRetry: true,
51
- })
52
-
53
- if (response.ok) {
54
- spinner.text = "Token revoked on server."
55
- } else {
56
- // Don't fail logout if revoke fails - just warn
57
- spinner.text =
58
- "Could not revoke token on server (continuing with local logout)."
59
- }
60
- } catch {
61
- // Don't fail logout if revoke fails
62
- spinner.text =
63
- "Could not reach server to revoke token (continuing with local logout)."
64
- }
65
- }
66
-
67
- // Clear local token
68
- await clearToken()
69
-
70
- // Optionally clear cache
71
- if (options.clearCache) {
72
- spinner.text = "Clearing local cache..."
73
- // Import dynamically to avoid circular dependency
74
- const { clearCache } = await import("./cache.js")
75
- await clearCache()
76
- }
77
-
78
- spinner.succeed(chalk.green("Successfully logged out."))
79
-
80
- if (options.revoke) {
81
- console.log(chalk.dim(" Token has been revoked on the server."))
82
- }
83
-
84
- if (options.clearCache) {
85
- console.log(chalk.dim(" Local cache has been cleared."))
86
- }
87
- } catch (error) {
88
- spinner.fail(chalk.red("Logout failed."))
89
- console.error(chalk.red(` ${error.message}`))
90
- process.exit(1)
91
- }
92
- }
93
-
94
- export default logout