@alchemy/cli 0.4.0 → 0.5.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.
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env node
2
+ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+ import {
4
+ getBaseDomain
5
+ } from "./chunk-56ZVYB4G.js";
6
+
7
+ // src/lib/auth.ts
8
+ import { createHash, randomBytes } from "crypto";
9
+ import { createServer } from "http";
10
+ import { execFile } from "child_process";
11
+ import { platform } from "os";
12
+ import { URL } from "url";
13
+
14
+ // src/lib/auth-html.ts
15
+ var SHARED_STYLE = `
16
+ * { margin: 0; padding: 0; box-sizing: border-box; }
17
+ body {
18
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
19
+ display: flex; justify-content: center; align-items: center;
20
+ min-height: 100vh;
21
+ background: #000;
22
+ color: #fff;
23
+ overflow: hidden;
24
+ }
25
+ .card {
26
+ text-align: center;
27
+ padding: 3rem;
28
+ position: relative;
29
+ z-index: 1;
30
+ }
31
+ h1 {
32
+ font-size: 1.25rem;
33
+ font-weight: 600;
34
+ margin-bottom: 0.5rem;
35
+ letter-spacing: -0.01em;
36
+ }
37
+ p {
38
+ color: #6b6b6b;
39
+ font-size: 0.875rem;
40
+ }
41
+ `;
42
+ var ALCHEMY_LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="160" height="35" viewBox="0 0 160 35" fill="none">
43
+ <path d="M50.1086 18.1241V17.7249C50.1086 16.7216 49.7656 15.9539 49.0796 15.4215C48.4143 14.8687 47.5411 14.5923 46.4601 14.5923C45.6285 14.5923 44.9736 14.7357 44.4955 15.0223C44.0381 15.2885 43.6015 15.6979 43.1857 16.2507C43.061 16.4145 42.9258 16.5271 42.7803 16.5886C42.6348 16.65 42.4373 16.6807 42.1878 16.6807H41.2523C41.0236 16.6807 40.8157 16.5988 40.6286 16.435C40.4623 16.2712 40.3895 16.0767 40.4103 15.8515C40.4727 15.2373 40.7741 14.6333 41.3146 14.0396C41.876 13.4253 42.614 12.9237 43.5287 12.5347C44.4435 12.1457 45.4206 11.9512 46.4601 11.9512C48.3935 11.9512 49.9527 12.4835 51.1377 13.5482C52.3435 14.6128 52.9464 16.1074 52.9464 18.032V27.3988C52.9464 27.6241 52.8633 27.8186 52.6969 27.9824C52.5306 28.1461 52.3331 28.228 52.1044 28.228H50.9506C50.7219 28.228 50.5244 28.1461 50.3581 27.9824C50.1918 27.8186 50.1086 27.6241 50.1086 27.3988V26.1397C49.776 26.8768 49.0692 27.4603 47.9881 27.8902C46.907 28.3202 45.826 28.5352 44.7449 28.5352C43.6847 28.5352 42.7387 28.3509 41.9072 27.9824C41.0756 27.5933 40.4311 27.0713 39.9737 26.4161C39.5371 25.7609 39.3188 25.0239 39.3188 24.2049C39.3188 22.6693 39.901 21.4716 41.0652 20.6117C42.2294 19.7313 43.7782 19.1376 45.7116 18.8305L50.1086 18.1241ZM50.1086 20.581L46.4289 21.1645C45.0776 21.3692 44.0277 21.7071 43.2793 22.178C42.5308 22.6284 42.1566 23.2017 42.1566 23.8978C42.1566 24.4096 42.3957 24.8703 42.8739 25.2798C43.352 25.6893 44.0797 25.894 45.0568 25.894C46.5536 25.894 47.7698 25.4743 48.7053 24.6349C49.6409 23.7954 50.1086 22.7001 50.1086 21.3488V20.581Z" fill="white"/>
44
+ <path d="M60.334 27.3988C60.334 27.6241 60.2508 27.8186 60.0845 27.9824C59.9182 28.1461 59.7207 28.228 59.492 28.228H58.3382C58.1095 28.228 57.912 28.1461 57.7456 27.9824C57.5793 27.8186 57.4962 27.6241 57.4962 27.3988V7.25242C57.4962 7.0272 57.5793 6.8327 57.7456 6.66891C57.912 6.50511 58.1095 6.42322 58.3382 6.42322H59.492C59.7207 6.42322 59.9182 6.50511 60.0845 6.66891C60.2508 6.8327 60.334 7.0272 60.334 7.25242V27.3988Z" fill="white"/>
45
+ <path d="M71.5669 25.894C73.6459 25.894 75.0492 25.0955 75.7768 23.4985C75.9223 23.1914 76.0679 22.9765 76.2134 22.8536C76.3589 22.7308 76.5564 22.6693 76.8059 22.6693H77.7414C77.9701 22.6693 78.1676 22.7512 78.3339 22.915C78.5002 23.0584 78.5834 23.2324 78.5834 23.4371C78.5834 24.1332 78.3027 24.8805 77.7414 25.679C77.1801 26.4775 76.3693 27.1532 75.309 27.706C74.2488 28.2588 73.0014 28.5352 71.5669 28.5352C70.0908 28.5352 68.8123 28.228 67.7312 27.6138C66.6502 26.9996 65.8186 26.1602 65.2365 25.0955C64.6544 24.0104 64.3321 22.8024 64.2698 21.4716C64.249 21.2259 64.2386 20.755 64.2386 20.0589C64.2386 19.5675 64.249 19.2195 64.2698 19.0147C64.4569 16.9264 65.1741 15.227 66.4215 13.9167C67.6689 12.6064 69.384 11.9512 71.5669 11.9512C73.0014 11.9512 74.2384 12.2276 75.2779 12.7804C76.3381 13.3127 77.1385 13.9781 77.679 14.7766C78.2404 15.5546 78.5418 16.2917 78.5834 16.9878C78.6042 17.213 78.521 17.4075 78.3339 17.5713C78.1676 17.7351 77.9701 17.817 77.7414 17.817H76.8059C76.5564 17.817 76.3589 17.7556 76.2134 17.6327C76.0679 17.5099 75.9223 17.2949 75.7768 16.9878C75.0492 15.3908 73.6459 14.5923 71.5669 14.5923C70.4235 14.5923 69.4256 14.9711 68.5732 15.7287C67.7208 16.4862 67.2323 17.6327 67.1075 19.1683C67.0868 19.3935 67.0764 19.762 67.0764 20.2739C67.0764 20.7448 67.0868 21.0929 67.1075 21.3181C67.2531 22.8536 67.7416 24.0002 68.5732 24.7577C69.4256 25.5152 70.4235 25.894 71.5669 25.894Z" fill="white"/>
46
+ <path d="M96.723 27.3988C96.723 27.6241 96.6398 27.8186 96.4735 27.9824C96.3072 28.1461 96.1097 28.228 95.881 28.228H94.7272C94.4985 28.228 94.301 28.1461 94.1346 27.9824C93.9683 27.8186 93.8852 27.6241 93.8852 27.3988V19.2911C93.8852 17.7556 93.5006 16.5886 92.7313 15.7901C91.9621 14.9916 90.8915 14.5923 89.5194 14.5923C88.168 14.5923 87.0974 14.9916 86.3074 15.7901C85.5382 16.5886 85.1536 17.7556 85.1536 19.2911V27.3988C85.1536 27.6241 85.0704 27.8186 84.9041 27.9824C84.7378 28.1461 84.5403 28.228 84.3116 28.228H83.1578C82.9291 28.228 82.7316 28.1461 82.5653 27.9824C82.3989 27.8186 82.3158 27.6241 82.3158 27.3988V7.25242C82.3158 7.0272 82.3989 6.8327 82.5653 6.66891C82.7316 6.50511 82.9291 6.42322 83.1578 6.42322H84.3116C84.5403 6.42322 84.7378 6.50511 84.9041 6.66891C85.0704 6.8327 85.1536 7.0272 85.1536 7.25242V14.0396C85.6109 13.4458 86.2346 12.9544 87.0246 12.5654C87.8354 12.1559 88.8229 11.9512 89.9871 11.9512C91.2969 11.9512 92.4611 12.2276 93.4798 12.7804C94.4985 13.3332 95.2885 14.1317 95.8498 15.1759C96.4319 16.1996 96.723 17.4178 96.723 18.8305V27.3988Z" fill="white"/>
47
+ <path d="M107.651 11.9512C109.875 11.9512 111.632 12.6473 112.921 14.0396C114.21 15.4318 114.854 17.3256 114.854 19.7211V20.5503C114.854 20.7755 114.771 20.97 114.605 21.1338C114.438 21.2976 114.241 21.3795 114.012 21.3795H103.285V21.5638C103.326 22.915 103.742 23.9797 104.532 24.7577C105.343 25.5152 106.382 25.894 107.651 25.894C108.69 25.894 109.491 25.7609 110.052 25.4948C110.634 25.2286 111.154 24.8498 111.611 24.3585C111.777 24.1947 111.923 24.0821 112.048 24.0206C112.193 23.9592 112.38 23.9285 112.609 23.9285H113.544C113.794 23.9285 114.002 24.0104 114.168 24.1742C114.334 24.338 114.407 24.5325 114.386 24.7577C114.303 25.3105 113.991 25.8838 113.451 26.4775C112.931 27.0508 112.172 27.5422 111.174 27.9516C110.197 28.3407 109.023 28.5352 107.651 28.5352C106.32 28.5352 105.135 28.2383 104.096 27.6445C103.056 27.0303 102.225 26.1909 101.601 25.1262C100.998 24.0616 100.634 22.8639 100.509 21.533C100.468 20.9188 100.447 20.4479 100.447 20.1203C100.447 19.7928 100.468 19.3218 100.509 18.7076C100.634 17.4382 100.998 16.2917 101.601 15.268C102.225 14.2443 103.046 13.4356 104.064 12.8418C105.104 12.2481 106.299 11.9512 107.651 11.9512ZM112.048 18.7998V18.7076C112.048 17.4587 111.642 16.4657 110.831 15.7287C110.041 14.9711 108.981 14.5923 107.651 14.5923C106.445 14.5923 105.416 14.9711 104.563 15.7287C103.732 16.4862 103.306 17.4792 103.285 18.7076V18.7998H112.048Z" fill="white"/>
48
+ <path d="M121.454 14.0396C121.953 13.4049 122.514 12.9032 123.138 12.5347C123.762 12.1457 124.593 11.9512 125.633 11.9512C128.065 11.9512 129.77 12.8418 130.747 14.6231C131.35 13.7632 132.036 13.108 132.805 12.6576C133.574 12.1867 134.583 11.9512 135.83 11.9512C137.867 11.9512 139.375 12.5552 140.352 13.7632C141.35 14.9711 141.849 16.6807 141.849 18.8919V27.3988C141.849 27.6241 141.766 27.8186 141.599 27.9824C141.433 28.1461 141.235 28.228 141.007 28.228H139.853C139.624 28.228 139.427 28.1461 139.26 27.9824C139.094 27.8186 139.011 27.6241 139.011 27.3988V19.199C139.011 16.1279 137.784 14.5923 135.331 14.5923C134.209 14.5923 133.315 14.9609 132.649 15.6979C131.984 16.435 131.651 17.5304 131.651 18.984V27.3988C131.651 27.6241 131.568 27.8186 131.402 27.9824C131.236 28.1461 131.038 28.228 130.809 28.228H129.656C129.427 28.228 129.229 28.1461 129.063 27.9824C128.897 27.8186 128.814 27.6241 128.814 27.3988V19.199C128.814 16.1279 127.587 14.5923 125.134 14.5923C124.011 14.5923 123.117 14.9609 122.452 15.6979C121.787 16.435 121.454 17.5304 121.454 18.984V27.3988C121.454 27.6241 121.371 27.8186 121.205 27.9824C121.038 28.1461 120.841 28.228 120.612 28.228H119.458C119.23 28.228 119.032 28.1461 118.866 27.9824C118.699 27.8186 118.616 27.6241 118.616 27.3988V13.0875C118.616 12.8623 118.699 12.6678 118.866 12.504C119.032 12.3402 119.23 12.2583 119.458 12.2583H120.612C120.841 12.2583 121.038 12.3402 121.205 12.504C121.371 12.6678 121.454 12.8623 121.454 13.0875V14.0396Z" fill="white"/>
49
+ <path d="M150.583 33.3261C150.395 33.8175 150.094 34.0631 149.678 34.0631H148.4C148.192 34.0631 148.015 33.9915 147.869 33.8482C147.724 33.7048 147.651 33.5308 147.651 33.3261C147.651 33.2442 147.662 33.1725 147.682 33.1111L150.676 26.6925L144.502 13.2104C144.481 13.1489 144.47 13.0773 144.47 12.9954C144.47 12.7906 144.543 12.6166 144.689 12.4733C144.834 12.33 145.011 12.2583 145.219 12.2583H146.497C146.913 12.2583 147.215 12.504 147.402 12.9954L152.204 23.4371L157.069 12.9954C157.256 12.504 157.557 12.2583 157.973 12.2583H159.252C159.46 12.2583 159.636 12.33 159.782 12.4733C159.927 12.6166 160 12.7906 160 12.9954C160 13.0773 159.99 13.1489 159.969 13.2104L150.583 33.3261Z" fill="white"/>
50
+ <path d="M27.5283 18.0684L17.0572 0.290967C17.0065 0.203185 16.9331 0.130062 16.8443 0.0789785C16.7556 0.0278948 16.6547 0.000656259 16.5518 1.1711e-05C16.4489 -0.000632837 16.3476 0.0253391 16.2582 0.0753071C16.1688 0.125275 16.0945 0.197472 16.0427 0.284612L12.9067 5.6115C12.804 5.78587 12.7499 5.98367 12.7499 6.18502C12.7499 6.38636 12.804 6.58416 12.9067 6.75853L19.7345 18.356C19.8373 18.5305 19.9852 18.6754 20.1633 18.7761C20.3414 18.8768 20.5434 18.9297 20.749 18.9295H27.0211C27.1237 18.9292 27.2245 18.9025 27.3134 18.8521C27.4022 18.8016 27.476 18.7292 27.5274 18.642C27.5787 18.5549 27.6058 18.456 27.606 18.3554C27.6061 18.2547 27.5794 18.1558 27.5283 18.0684Z" fill="white"/>
51
+ <path d="M0.0807432 27.3596L10.5519 9.58214C10.6032 9.49505 10.6771 9.42275 10.766 9.37248C10.8549 9.32221 10.9557 9.29575 11.0583 9.29575C11.1609 9.29575 11.2618 9.32221 11.3507 9.37248C11.4396 9.42275 11.5134 9.49505 11.5648 9.58214L14.7024 14.9043C14.8051 15.0789 14.8591 15.277 14.8591 15.4786C14.8591 15.6802 14.8051 15.8782 14.7024 16.0529L7.87452 27.6503C7.77211 27.8249 7.62454 27.9698 7.44668 28.0705C7.26883 28.1712 7.06699 28.2241 6.8616 28.2239H0.588012C0.484665 28.2244 0.38303 28.198 0.293436 28.1475C0.203841 28.097 0.129495 28.0242 0.0779537 27.9364C0.0264127 27.8486 -0.00048522 27.7489 6.62616e-06 27.6476C0.000498472 27.5463 0.0283519 27.4469 0.0807432 27.3596Z" fill="white"/>
52
+ <path d="M11.5633 28.2201H32.5055C32.6082 28.22 32.7092 28.1933 32.7981 28.1429C32.887 28.0925 32.9608 28.0199 33.012 27.9327C33.0633 27.8454 33.0902 27.7464 33.09 27.6457C33.0899 27.545 33.0627 27.4461 33.0112 27.359L29.8784 22.0337C29.7756 21.8591 29.6277 21.7142 29.4496 21.6136C29.2715 21.5129 29.0695 21.46 28.8639 21.4602H15.2082C15.0026 21.46 14.8006 21.5129 14.6224 21.6136C14.4443 21.7142 14.2964 21.8591 14.1936 22.0337L11.0577 27.359C11.0061 27.4461 10.9789 27.545 10.9788 27.6457C10.9786 27.7464 11.0055 27.8454 11.0568 27.9327C11.108 28.0199 11.1819 28.0925 11.2708 28.1429C11.3597 28.1933 11.4605 28.22 11.5633 28.2201Z" fill="white"/>
53
+ </svg>`;
54
+ function escapeHtml(text) {
55
+ return text.replace(
56
+ /[&<>"']/g,
57
+ (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]
58
+ );
59
+ }
60
+ var AUTH_SUCCESS_HTML = `<!DOCTYPE html>
61
+ <html>
62
+ <head><title>Alchemy CLI</title>
63
+ <style>
64
+ ${SHARED_STYLE}
65
+ .logo {
66
+ margin-bottom: 2rem;
67
+ }
68
+ .logo svg {
69
+ width: 140px;
70
+ height: auto;
71
+ opacity: 0;
72
+ animation: fadeIn 0.6s ease forwards;
73
+ }
74
+ .check-circle {
75
+ width: 64px; height: 64px;
76
+ border-radius: 50%;
77
+ background: linear-gradient(135deg, #363FF9, #5B63FF);
78
+ display: flex; align-items: center; justify-content: center;
79
+ margin: 0 auto 1.5rem;
80
+ opacity: 0;
81
+ transform: scale(0.5);
82
+ animation: popIn 0.5s ease forwards 0.3s;
83
+ }
84
+ .check-circle svg {
85
+ width: 28px; height: 28px;
86
+ }
87
+ h1 { animation: fadeUp 0.5s ease forwards 0.5s; }
88
+ p { animation: fadeUp 0.5s ease forwards 0.65s; }
89
+ @keyframes fadeIn { to { opacity: 1; } }
90
+ @keyframes popIn { to { opacity: 1; transform: scale(1); } }
91
+ @keyframes fadeUp {
92
+ from { opacity: 0; transform: translateY(8px); }
93
+ to { opacity: 1; transform: translateY(0); }
94
+ }
95
+ </style>
96
+ </head>
97
+ <body>
98
+ <div class="card">
99
+ <div class="logo">${ALCHEMY_LOGO_SVG}</div>
100
+ <div class="check-circle">
101
+ <svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
102
+ <polyline points="20 6 9 17 4 12"/>
103
+ </svg>
104
+ </div>
105
+ <h1>Authenticated</h1>
106
+ <p>You can close this tab and return to your terminal.</p>
107
+ </div>
108
+ </body>
109
+ </html>`;
110
+ function authErrorHtml(message) {
111
+ const escaped = escapeHtml(message);
112
+ return `<!DOCTYPE html>
113
+ <html>
114
+ <head><title>Alchemy CLI</title>
115
+ <style>
116
+ ${SHARED_STYLE}
117
+ .x { font-size: 3rem; margin-bottom: 1rem; color: #ef4444; }
118
+ </style>
119
+ </head>
120
+ <body>
121
+ <div class="card">
122
+ <div class="x">&#x2717;</div>
123
+ <h1>Authentication Failed</h1>
124
+ <p>${escaped}</p>
125
+ </div>
126
+ </body>
127
+ </html>`;
128
+ }
129
+
130
+ // src/lib/auth.ts
131
+ var AUTH_PORT = 16424;
132
+ var AUTH_CALLBACK_PATH = "/callback";
133
+ var DEFAULT_EXPIRES_IN_SECONDS = 90 * 24 * 60 * 60;
134
+ function getAuthBaseUrl() {
135
+ return process.env.ALCHEMY_AUTH_URL || `https://auth.${getBaseDomain()}`;
136
+ }
137
+ function generateCodeVerifier() {
138
+ return randomBytes(32).toString("base64url");
139
+ }
140
+ function deriveCodeChallenge(verifier) {
141
+ return createHash("sha256").update(verifier).digest("base64url");
142
+ }
143
+ function getLoginUrl(port, codeChallenge) {
144
+ const base = getAuthBaseUrl();
145
+ const redirect = encodeURIComponent(`http://localhost:${port}${AUTH_CALLBACK_PATH}`);
146
+ let url = `${base}/login?redirectUrl=${redirect}&_t=${Date.now()}`;
147
+ if (codeChallenge) {
148
+ url += `&code_challenge=${encodeURIComponent(codeChallenge)}`;
149
+ }
150
+ return url;
151
+ }
152
+ function openBrowser(url) {
153
+ const cmd = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
154
+ execFile(cmd, [url]);
155
+ }
156
+ function waitForCallback(port, timeoutMs = 12e4) {
157
+ return new Promise((resolve, reject) => {
158
+ const timer = setTimeout(() => {
159
+ server.close();
160
+ reject(new Error("Authentication timed out. Please try again."));
161
+ }, timeoutMs);
162
+ const server = createServer((req, res) => {
163
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
164
+ if (url.pathname !== AUTH_CALLBACK_PATH) {
165
+ res.writeHead(404);
166
+ res.end("Not found");
167
+ return;
168
+ }
169
+ const error = url.searchParams.get("error");
170
+ if (error) {
171
+ const description = url.searchParams.get("error_description") || error;
172
+ clearTimeout(timer);
173
+ res.writeHead(200, { "Content-Type": "text/html" });
174
+ res.end(authErrorHtml(description));
175
+ server.close();
176
+ reject(new Error(`Authentication failed: ${description}`));
177
+ return;
178
+ }
179
+ const code = url.searchParams.get("code");
180
+ if (!code) {
181
+ clearTimeout(timer);
182
+ res.writeHead(400);
183
+ res.end("Missing auth code");
184
+ server.close();
185
+ reject(new Error("Authentication callback missing auth code."));
186
+ return;
187
+ }
188
+ clearTimeout(timer);
189
+ resolve({
190
+ code,
191
+ sendSuccess: () => {
192
+ res.writeHead(200, { "Content-Type": "text/html" });
193
+ res.end(AUTH_SUCCESS_HTML);
194
+ server.close();
195
+ },
196
+ sendError: (message) => {
197
+ res.writeHead(500, { "Content-Type": "text/html" });
198
+ res.end(authErrorHtml(message));
199
+ server.close();
200
+ }
201
+ });
202
+ });
203
+ server.on("error", (err) => {
204
+ clearTimeout(timer);
205
+ server.close();
206
+ if (err.code === "EADDRINUSE") {
207
+ reject(
208
+ new Error(
209
+ `Port ${port} is already in use. Another 'alchemy auth' may be running.`
210
+ )
211
+ );
212
+ } else {
213
+ reject(err);
214
+ }
215
+ });
216
+ server.listen(port, "127.0.0.1");
217
+ server.unref();
218
+ });
219
+ }
220
+ async function exchangeCodeForToken(code, port, options) {
221
+ const baseUrl = getAuthBaseUrl();
222
+ const redirectUri = `http://localhost:${port}${AUTH_CALLBACK_PATH}`;
223
+ const body = {
224
+ code,
225
+ redirect_uri: redirectUri
226
+ };
227
+ if (options?.expiresInSeconds) {
228
+ body.expires_in_seconds = options.expiresInSeconds;
229
+ }
230
+ if (options?.codeVerifier) {
231
+ body.code_verifier = options.codeVerifier;
232
+ }
233
+ const response = await fetch(`${baseUrl}/api/cli/token`, {
234
+ method: "POST",
235
+ headers: { "Content-Type": "application/json" },
236
+ body: JSON.stringify(body)
237
+ });
238
+ if (!response.ok) {
239
+ const errBody = await response.json().catch(() => ({}));
240
+ throw new Error(
241
+ errBody.error || `Token exchange failed (HTTP ${response.status})`
242
+ );
243
+ }
244
+ const data = await response.json();
245
+ if (!data.authToken) {
246
+ throw new Error("Token exchange response missing authToken");
247
+ }
248
+ return { token: data.authToken, expiresAt: data.expiresAt };
249
+ }
250
+ async function performBrowserLogin(port = AUTH_PORT, options) {
251
+ const codeVerifier = generateCodeVerifier();
252
+ const codeChallenge = deriveCodeChallenge(codeVerifier);
253
+ const loginUrl = getLoginUrl(port, codeChallenge);
254
+ const callbackPromise = waitForCallback(port);
255
+ openBrowser(loginUrl);
256
+ const callback = await callbackPromise;
257
+ try {
258
+ const result = await exchangeCodeForToken(callback.code, port, {
259
+ expiresInSeconds: options?.expiresInSeconds ?? DEFAULT_EXPIRES_IN_SECONDS,
260
+ codeVerifier
261
+ });
262
+ callback.sendSuccess();
263
+ return result;
264
+ } catch (err) {
265
+ callback.sendError("Failed to complete authentication. Please try again.");
266
+ throw err;
267
+ }
268
+ }
269
+ async function revokeToken(token) {
270
+ const baseUrl = getAuthBaseUrl();
271
+ try {
272
+ const response = await fetch(`${baseUrl}/api/cli/logout`, {
273
+ method: "POST",
274
+ headers: {
275
+ Authorization: `Bearer ${token}`,
276
+ "Content-Type": "application/json"
277
+ }
278
+ });
279
+ if (response.ok) {
280
+ return "revoked";
281
+ }
282
+ if (response.status === 401) {
283
+ return "already_invalid";
284
+ }
285
+ return "server_error";
286
+ } catch {
287
+ return "network_error";
288
+ }
289
+ }
290
+
291
+ export {
292
+ AUTH_PORT,
293
+ DEFAULT_EXPIRES_IN_SECONDS,
294
+ getLoginUrl,
295
+ openBrowser,
296
+ waitForCallback,
297
+ exchangeCodeForToken,
298
+ performBrowserLogin,
299
+ revokeToken
300
+ };
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+ import {
4
+ AUTH_PORT,
5
+ getLoginUrl,
6
+ performBrowserLogin,
7
+ revokeToken
8
+ } from "./chunk-IGD4NIK7.js";
9
+ import {
10
+ AdminClient,
11
+ bold,
12
+ brand,
13
+ configPath,
14
+ dim,
15
+ green,
16
+ isInteractiveAllowed,
17
+ load,
18
+ maskIf,
19
+ promptSelect,
20
+ resolveAuthToken,
21
+ save,
22
+ withSpinner
23
+ } from "./chunk-T2XSNZE3.js";
24
+ import {
25
+ CLIError,
26
+ ErrorCode,
27
+ debug,
28
+ exitWithError,
29
+ isJSONMode,
30
+ printHuman
31
+ } from "./chunk-56ZVYB4G.js";
32
+
33
+ // src/commands/auth.ts
34
+ function registerAuth(program) {
35
+ const cmd = program.command("auth").description("Authenticate with your Alchemy account");
36
+ cmd.command("login", { isDefault: true }).description("Log in via browser").option("--force", "Force re-authentication even if a valid token exists").action(async (opts) => {
37
+ try {
38
+ if (!opts.force) {
39
+ const existing = resolveAuthToken();
40
+ if (existing) {
41
+ printHuman(
42
+ ` ${green("\u2713")} Already authenticated
43
+ ${dim("Token:")} ${maskIf(existing)}
44
+ ${dim("Run")} alchemy auth login --force ${dim("to re-authenticate.")}
45
+ `,
46
+ { status: "already_authenticated" }
47
+ );
48
+ return;
49
+ }
50
+ }
51
+ if (opts.force) {
52
+ const cfg2 = load();
53
+ if (cfg2.auth_token) {
54
+ await revokeToken(cfg2.auth_token);
55
+ save({ ...cfg2, auth_token: void 0, auth_token_expires_at: void 0 });
56
+ }
57
+ }
58
+ if (!isJSONMode()) {
59
+ console.log("");
60
+ console.log(` ${brand("\u25C6")} ${bold("Alchemy Authentication")}`);
61
+ console.log(` ${dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`);
62
+ console.log("");
63
+ console.log(` Opening browser to log in...`);
64
+ console.log(` ${dim(getLoginUrl(AUTH_PORT))}`);
65
+ console.log("");
66
+ console.log(` ${dim("Waiting for authentication...")}`);
67
+ }
68
+ const result = await performBrowserLogin();
69
+ const cfg = load();
70
+ save({
71
+ ...cfg,
72
+ auth_token: result.token,
73
+ auth_token_expires_at: result.expiresAt
74
+ });
75
+ const expiresAt = result.expiresAt;
76
+ printHuman(
77
+ ` ${green("\u2713")} Logged in successfully
78
+ ${dim("Token saved to")} ${configPath()}
79
+ ${dim("Expires:")} ${expiresAt}
80
+ `,
81
+ {
82
+ status: "authenticated",
83
+ expiresAt,
84
+ configPath: configPath()
85
+ }
86
+ );
87
+ if (isInteractiveAllowed(program)) {
88
+ await selectAppAfterAuth(result.token);
89
+ }
90
+ process.exit(0);
91
+ } catch (err) {
92
+ exitWithError(
93
+ err instanceof CLIError ? err : new CLIError(ErrorCode.AUTH_REQUIRED, String(err.message))
94
+ );
95
+ }
96
+ });
97
+ cmd.command("status").description("Show current authentication status").action(() => {
98
+ try {
99
+ const cfg = load();
100
+ const validToken = resolveAuthToken(cfg);
101
+ if (!cfg.auth_token) {
102
+ printHuman(
103
+ ` ${dim("Not authenticated. Run")} alchemy auth ${dim("to log in.")}
104
+ `,
105
+ { authenticated: false }
106
+ );
107
+ return;
108
+ }
109
+ if (!validToken) {
110
+ printHuman(
111
+ ` ${dim("Session expired. Run")} alchemy auth ${dim("to log in again.")}
112
+ `,
113
+ { authenticated: false, expired: true }
114
+ );
115
+ return;
116
+ }
117
+ printHuman(
118
+ ` ${green("\u2713")} Authenticated
119
+ ${dim("Token:")} ${maskIf(validToken)}
120
+ ${dim("Expires:")} ${cfg.auth_token_expires_at || "unknown"}
121
+ `,
122
+ {
123
+ authenticated: true,
124
+ expired: false,
125
+ expiresAt: cfg.auth_token_expires_at
126
+ }
127
+ );
128
+ } catch (err) {
129
+ exitWithError(err);
130
+ }
131
+ });
132
+ cmd.command("logout").description("Clear saved authentication token").action(async () => {
133
+ try {
134
+ const cfg = load();
135
+ let revokeResult;
136
+ if (cfg.auth_token) {
137
+ revokeResult = await revokeToken(cfg.auth_token);
138
+ }
139
+ const { auth_token: _, auth_token_expires_at: __, ...rest } = cfg;
140
+ save(rest);
141
+ if (!cfg.auth_token) {
142
+ printHuman(
143
+ ` ${dim("No active session.")}
144
+ `,
145
+ { status: "no_session" }
146
+ );
147
+ } else if (revokeResult === "already_invalid") {
148
+ printHuman(
149
+ ` ${green("\u2713")} Logged out ${dim("(token was already invalidated)")}
150
+ `,
151
+ { status: "logged_out", tokenAlreadyInvalid: true }
152
+ );
153
+ } else if (revokeResult === "server_error" || revokeResult === "network_error") {
154
+ printHuman(
155
+ ` ${green("\u2713")} Logged out locally ${dim("(could not reach server to revoke token)")}
156
+ `,
157
+ { status: "logged_out", serverRevoked: false }
158
+ );
159
+ } else {
160
+ printHuman(
161
+ ` ${green("\u2713")} Logged out
162
+ `,
163
+ { status: "logged_out" }
164
+ );
165
+ }
166
+ } catch (err) {
167
+ exitWithError(err);
168
+ }
169
+ });
170
+ }
171
+ async function selectAppAfterAuth(authToken) {
172
+ let apps;
173
+ try {
174
+ const admin = new AdminClient({ type: "auth_token", token: authToken });
175
+ const result = await withSpinner(
176
+ "Fetching apps\u2026",
177
+ "Apps fetched",
178
+ () => admin.listAllApps()
179
+ );
180
+ apps = result.apps;
181
+ } catch (err) {
182
+ const msg = err instanceof Error ? err.message : String(err);
183
+ debug(`Failed to fetch apps: ${msg}`);
184
+ if (!isJSONMode()) {
185
+ console.error(` ${dim(`Could not fetch apps: ${msg}`)}`);
186
+ }
187
+ return;
188
+ }
189
+ if (apps.length === 0) {
190
+ console.log(` ${dim("No apps found. Create one at dashboard.alchemy.com")}`);
191
+ return;
192
+ }
193
+ let selectedApp;
194
+ if (apps.length === 1) {
195
+ selectedApp = apps[0];
196
+ console.log(` ${green("\u2713")} Auto-selected app: ${bold(selectedApp.name)}`);
197
+ } else {
198
+ console.log("");
199
+ const appId = await promptSelect({
200
+ message: "Select an app",
201
+ options: apps.map((app) => ({
202
+ value: app.id,
203
+ label: app.name,
204
+ hint: `${app.chainNetworks.length} networks`
205
+ })),
206
+ cancelMessage: "Skipped app selection."
207
+ });
208
+ if (!appId) return;
209
+ const found = apps.find((a) => a.id === appId);
210
+ if (!found) return;
211
+ selectedApp = found;
212
+ }
213
+ const cfg = load();
214
+ save({
215
+ ...cfg,
216
+ app: {
217
+ id: selectedApp.id,
218
+ name: selectedApp.name,
219
+ apiKey: selectedApp.apiKey,
220
+ webhookApiKey: selectedApp.webhookApiKey
221
+ }
222
+ });
223
+ console.log(
224
+ ` ${dim("App")} ${selectedApp.name} ${dim("saved to config")}`
225
+ );
226
+ }
227
+
228
+ export {
229
+ registerAuth,
230
+ selectAppAfterAuth
231
+ };