@lpm-registry/cli 0.58.0 → 0.59.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 (62) hide show
  1. package/README.md +25 -44
  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 +42 -82
  6. package/scripts/install-binary.js +219 -550
  7. package/CHANGELOG.md +0 -197
  8. package/LICENSE +0 -15
  9. package/index.d.ts +0 -131
  10. package/index.js +0 -31
  11. package/lib/api.js +0 -398
  12. package/lib/commands/add.js +0 -1217
  13. package/lib/commands/audit.js +0 -283
  14. package/lib/commands/cache.js +0 -209
  15. package/lib/commands/check-name.js +0 -112
  16. package/lib/commands/config.js +0 -186
  17. package/lib/commands/doctor.js +0 -140
  18. package/lib/commands/info.js +0 -215
  19. package/lib/commands/init.js +0 -146
  20. package/lib/commands/install.js +0 -264
  21. package/lib/commands/login.js +0 -595
  22. package/lib/commands/logout.js +0 -94
  23. package/lib/commands/marketplace-compare.js +0 -164
  24. package/lib/commands/marketplace-earnings.js +0 -89
  25. package/lib/commands/mcp-setup.js +0 -363
  26. package/lib/commands/npmrc.js +0 -178
  27. package/lib/commands/open.js +0 -82
  28. package/lib/commands/outdated.js +0 -291
  29. package/lib/commands/pool-stats.js +0 -100
  30. package/lib/commands/publish.js +0 -949
  31. package/lib/commands/quality.js +0 -211
  32. package/lib/commands/remove.js +0 -82
  33. package/lib/commands/run.js +0 -14
  34. package/lib/commands/search.js +0 -143
  35. package/lib/commands/setup.js +0 -221
  36. package/lib/commands/skills.js +0 -898
  37. package/lib/commands/token-rotate.js +0 -25
  38. package/lib/commands/uninstall.js +0 -107
  39. package/lib/commands/whoami.js +0 -129
  40. package/lib/config.js +0 -328
  41. package/lib/constants.js +0 -190
  42. package/lib/ecosystem.js +0 -501
  43. package/lib/editor-skills.js +0 -321
  44. package/lib/editors.js +0 -215
  45. package/lib/import-rewriter.js +0 -364
  46. package/lib/install-targets/mcp-server.js +0 -245
  47. package/lib/install-targets/vscode-extension.js +0 -178
  48. package/lib/install-targets.js +0 -82
  49. package/lib/integrity.js +0 -179
  50. package/lib/lpm-config-prompts.js +0 -102
  51. package/lib/lpm-config.js +0 -408
  52. package/lib/oidc.js +0 -166
  53. package/lib/project-utils.js +0 -152
  54. package/lib/quality/checks.js +0 -687
  55. package/lib/quality/display.js +0 -155
  56. package/lib/quality/score.js +0 -118
  57. package/lib/quality/swift-checks.js +0 -447
  58. package/lib/safe-path.js +0 -180
  59. package/lib/secure-store.js +0 -321
  60. package/lib/swift-project.js +0 -637
  61. package/lib/ui.js +0 -40
  62. package/scripts/__tests__/install-binary.test.js +0 -346
@@ -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