@alchemy/cli 0.5.2 → 0.6.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.
package/README.md CHANGED
@@ -109,7 +109,7 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
109
109
 
110
110
  | Command | What it does | Example |
111
111
  |---|---|---|
112
- | `tokens [address]` | Lists ERC-20 balances for an address | `alchemy tokens 0x...` |
112
+ | `tokens balances [address]` | Lists ERC-20 balances for an address | `alchemy tokens balances 0x...` |
113
113
  | `tokens metadata <contract>` | Gets ERC-20 metadata | `alchemy tokens metadata 0x...` |
114
114
  | `tokens allowance --owner --spender --contract` | Gets ERC-20 allowance | `alchemy tokens allowance --owner 0x... --spender 0x... --contract 0x...` |
115
115
  | `nfts [address]` | Lists NFTs owned by an address | `alchemy nfts 0x...` |
@@ -150,7 +150,7 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
150
150
 
151
151
  | Command | What it does | Example |
152
152
  |---|---|---|
153
- | `network list` | Lists RPC network IDs for use with `--network` (e.g. `eth-mainnet`) | `alchemy network list --configured` |
153
+ | `network list` | Lists RPC network IDs for use with `--network` (e.g. `eth-mainnet`) | `alchemy network list --search ethereum` |
154
154
  | `solana rpc <method> [params...]` | Calls Solana JSON-RPC methods | `alchemy solana rpc getBalance '"<pubkey>"'` |
155
155
  | `solana das <method> [params...]` | Calls Solana DAS methods | `alchemy solana das getAssetsByOwner '{"ownerAddress":"<pubkey>"}'` |
156
156
 
@@ -251,7 +251,7 @@ Additional env vars:
251
251
  | `apps address-allowlist` | `--addresses <addrs>` (required), `--dry-run` |
252
252
  | `apps origin-allowlist` | `--origins <origins>` (required), `--dry-run` |
253
253
  | `apps ip-allowlist` | `--ips <ips>` (required), `--dry-run` |
254
- | `network list` | `--configured`, `--app-id <id>` |
254
+ | `network list` | `--mainnet-only`, `--testnet-only`, `--search <term>` |
255
255
  | `config reset` | `-y, --yes` |
256
256
 
257
257
  ## Authentication Reference
@@ -3,10 +3,11 @@ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  registerAuth,
5
5
  selectAppAfterAuth
6
- } from "./chunk-DBTRDS35.js";
7
- import "./chunk-5ZAK2VSS.js";
6
+ } from "./chunk-UMKDYHMO.js";
7
+ import "./chunk-FFMNT74F.js";
8
8
  import "./chunk-KDMIWPZH.js";
9
- import "./chunk-NM25MEJZ.js";
9
+ import "./chunk-ATX65U7J.js";
10
+ import "./chunk-JQRGILIS.js";
10
11
  import "./chunk-NBDWF4ZQ.js";
11
12
  import "./chunk-BAAQ7ELR.js";
12
13
  import "./chunk-56ZVYB4G.js";
@@ -2,22 +2,24 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  AUTH_PORT,
5
- DEFAULT_EXPIRES_IN_SECONDS,
5
+ OAUTH_CLIENT_ID,
6
6
  exchangeCodeForToken,
7
- getLoginUrl,
7
+ getAuthorizeUrl,
8
8
  openBrowser,
9
9
  performBrowserLogin,
10
+ prepareBrowserLogin,
10
11
  revokeToken,
11
12
  waitForCallback
12
- } from "./chunk-5ZAK2VSS.js";
13
+ } from "./chunk-FFMNT74F.js";
13
14
  import "./chunk-56ZVYB4G.js";
14
15
  export {
15
16
  AUTH_PORT,
16
- DEFAULT_EXPIRES_IN_SECONDS,
17
+ OAUTH_CLIENT_ID,
17
18
  exchangeCodeForToken,
18
- getLoginUrl,
19
+ getAuthorizeUrl,
19
20
  openBrowser,
20
21
  performBrowserLogin,
22
+ prepareBrowserLogin,
21
23
  revokeToken,
22
24
  waitForCallback
23
25
  };
@@ -53,7 +53,7 @@ function semverLT(a, b) {
53
53
  return false;
54
54
  }
55
55
  function currentVersion() {
56
- return true ? "0.5.2" : "0.0.0";
56
+ return true ? "0.6.1" : "0.0.0";
57
57
  }
58
58
  function toUpdateStatus(latestVersion, checkedAt) {
59
59
  const current = currentVersion();
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+ import {
4
+ getCredentials
5
+ } from "./chunk-JQRGILIS.js";
3
6
  import {
4
7
  load,
5
8
  save
@@ -622,7 +625,17 @@ function resolveAppId(program, cfg) {
622
625
  if (config.app?.id) return config.app.id;
623
626
  return void 0;
624
627
  }
625
- function resolveAuthToken(cfg) {
628
+ async function resolveAuthToken(cfg) {
629
+ const creds = await getCredentials();
630
+ if (creds?.auth_token?.trim()) {
631
+ if (creds.auth_token_expires_at) {
632
+ const expiry = new Date(creds.auth_token_expires_at);
633
+ if (!Number.isNaN(expiry.getTime()) && expiry <= /* @__PURE__ */ new Date()) {
634
+ return void 0;
635
+ }
636
+ }
637
+ return creds.auth_token;
638
+ }
626
639
  const config = cfg ?? load();
627
640
  if (!config.auth_token?.trim()) return void 0;
628
641
  if (config.auth_token_expires_at) {
@@ -633,11 +646,11 @@ function resolveAuthToken(cfg) {
633
646
  }
634
647
  return config.auth_token;
635
648
  }
636
- function adminClientFromFlags(program) {
649
+ async function adminClientFromFlags(program) {
637
650
  const cfg = load();
638
651
  const accessKey = resolveAccessKey(program, cfg);
639
652
  if (accessKey) return new AdminClient(accessKey);
640
- const authToken = resolveAuthToken(cfg);
653
+ const authToken = await resolveAuthToken(cfg);
641
654
  if (authToken) return new AdminClient({ type: "auth_token", token: authToken });
642
655
  throw errAccessKeyRequired();
643
656
  }
@@ -702,7 +715,7 @@ function appNetworkToSlug(rpcUrl) {
702
715
  async function resolveConfiguredNetworkSlugs(program, appIdOverride) {
703
716
  const appId = appIdOverride || resolveAppId(program);
704
717
  if (!appId) throw errAppRequired();
705
- const admin = adminClientFromFlags(program);
718
+ const admin = await adminClientFromFlags(program);
706
719
  const app = await admin.getApp(appId);
707
720
  const slugs = app.chainNetworks.map((network) => appNetworkToSlug(network.rpcUrl)).filter((slug) => Boolean(slug));
708
721
  return Array.from(new Set(slugs)).sort((a, b) => a.localeCompare(b));
@@ -130,7 +130,7 @@ ${SHARED_STYLE}
130
130
  // src/lib/auth.ts
131
131
  var AUTH_PORT = 16424;
132
132
  var AUTH_CALLBACK_PATH = "/callback";
133
- var DEFAULT_EXPIRES_IN_SECONDS = 90 * 24 * 60 * 60;
133
+ var OAUTH_CLIENT_ID = "alchemy-cli";
134
134
  function getAuthBaseUrl() {
135
135
  return process.env.ALCHEMY_AUTH_URL || `https://auth.${getBaseDomain()}`;
136
136
  }
@@ -140,14 +140,29 @@ function generateCodeVerifier() {
140
140
  function deriveCodeChallenge(verifier) {
141
141
  return createHash("sha256").update(verifier).digest("base64url");
142
142
  }
143
- function getLoginUrl(port, codeChallenge) {
143
+ function generateState() {
144
+ return randomBytes(32).toString("base64url");
145
+ }
146
+ function getAuthorizeUrl(port, codeChallenge, state) {
144
147
  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;
148
+ const url = new URL(`${base}/oauth/authorize`);
149
+ url.searchParams.set("response_type", "code");
150
+ url.searchParams.set("client_id", OAUTH_CLIENT_ID);
151
+ url.searchParams.set("redirect_uri", `http://localhost:${port}${AUTH_CALLBACK_PATH}`);
152
+ url.searchParams.set("code_challenge", codeChallenge);
153
+ url.searchParams.set("code_challenge_method", "S256");
154
+ url.searchParams.set("state", state);
155
+ return url.toString();
156
+ }
157
+ function prepareBrowserLogin(port = AUTH_PORT) {
158
+ const codeVerifier = generateCodeVerifier();
159
+ const codeChallenge = deriveCodeChallenge(codeVerifier);
160
+ const state = generateState();
161
+ return {
162
+ authorizeUrl: getAuthorizeUrl(port, codeChallenge, state),
163
+ codeVerifier,
164
+ state
165
+ };
151
166
  }
152
167
  function openBrowser(url) {
153
168
  const cmd = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
@@ -188,6 +203,7 @@ function waitForCallback(port, timeoutMs = 12e4) {
188
203
  clearTimeout(timer);
189
204
  resolve({
190
205
  code,
206
+ state: url.searchParams.get("state"),
191
207
  sendSuccess: () => {
192
208
  res.writeHead(200, { "Content-Type": "text/html" });
193
209
  res.end(AUTH_SUCCESS_HTML);
@@ -220,43 +236,44 @@ function waitForCallback(port, timeoutMs = 12e4) {
220
236
  async function exchangeCodeForToken(code, port, options) {
221
237
  const baseUrl = getAuthBaseUrl();
222
238
  const redirectUri = `http://localhost:${port}${AUTH_CALLBACK_PATH}`;
223
- const body = {
239
+ const body = new URLSearchParams({
240
+ grant_type: "authorization_code",
224
241
  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`, {
242
+ redirect_uri: redirectUri,
243
+ client_id: OAUTH_CLIENT_ID,
244
+ code_verifier: options.codeVerifier
245
+ });
246
+ const response = await fetch(`${baseUrl}/oauth/token`, {
234
247
  method: "POST",
235
- headers: { "Content-Type": "application/json" },
236
- body: JSON.stringify(body)
248
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
249
+ body: body.toString()
237
250
  });
238
251
  if (!response.ok) {
239
252
  const errBody = await response.json().catch(() => ({}));
240
- throw new Error(
241
- errBody.error || `Token exchange failed (HTTP ${response.status})`
242
- );
253
+ const errMsg = errBody.error_description || errBody.error || `Token exchange failed (HTTP ${response.status})`;
254
+ throw new Error(errMsg);
243
255
  }
244
256
  const data = await response.json();
245
- if (!data.authToken) {
246
- throw new Error("Token exchange response missing authToken");
257
+ if (!data.access_token) {
258
+ throw new Error("Token exchange response missing access_token");
247
259
  }
248
- return { token: data.authToken, expiresAt: data.expiresAt };
260
+ const expiresAt = new Date(Date.now() + data.expires_in * 1e3).toISOString();
261
+ return { token: data.access_token, expiresAt };
249
262
  }
250
- async function performBrowserLogin(port = AUTH_PORT, options) {
251
- const codeVerifier = generateCodeVerifier();
252
- const codeChallenge = deriveCodeChallenge(codeVerifier);
253
- const loginUrl = getLoginUrl(port, codeChallenge);
263
+ async function performBrowserLogin(prepared, options) {
264
+ const port = options?.port ?? AUTH_PORT;
265
+ const { authorizeUrl, codeVerifier, state } = prepared ?? prepareBrowserLogin(port);
254
266
  const callbackPromise = waitForCallback(port);
255
- openBrowser(loginUrl);
267
+ if (!options?.skipBrowserOpen) {
268
+ openBrowser(authorizeUrl);
269
+ }
256
270
  const callback = await callbackPromise;
271
+ if (callback.state !== state) {
272
+ callback.sendError("State mismatch \u2014 possible CSRF attack.");
273
+ throw new Error("OAuth state mismatch. Authentication aborted.");
274
+ }
257
275
  try {
258
276
  const result = await exchangeCodeForToken(callback.code, port, {
259
- expiresInSeconds: options?.expiresInSeconds ?? DEFAULT_EXPIRES_IN_SECONDS,
260
277
  codeVerifier
261
278
  });
262
279
  callback.sendSuccess();
@@ -290,8 +307,9 @@ async function revokeToken(token) {
290
307
 
291
308
  export {
292
309
  AUTH_PORT,
293
- DEFAULT_EXPIRES_IN_SECONDS,
294
- getLoginUrl,
310
+ OAUTH_CLIENT_ID,
311
+ getAuthorizeUrl,
312
+ prepareBrowserLogin,
295
313
  openBrowser,
296
314
  waitForCallback,
297
315
  exchangeCodeForToken,
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+
4
+ // src/lib/credential-storage.ts
5
+ import {
6
+ getPassword,
7
+ setPassword,
8
+ deletePassword,
9
+ getKeyring,
10
+ PasswordDeleteError
11
+ } from "cross-keychain";
12
+ var SERVICE = "alchemy-cli";
13
+ var ACCOUNT = "oauth-credentials";
14
+ async function getCredentials() {
15
+ try {
16
+ const raw = await getPassword(SERVICE, ACCOUNT);
17
+ if (!raw) return null;
18
+ const parsed = JSON.parse(raw);
19
+ if (parsed && typeof parsed.auth_token === "string" && typeof parsed.auth_token_expires_at === "string") {
20
+ return parsed;
21
+ }
22
+ return null;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ async function saveCredentials(creds) {
28
+ await setPassword(SERVICE, ACCOUNT, JSON.stringify(creds));
29
+ }
30
+ async function deleteCredentials() {
31
+ try {
32
+ await deletePassword(SERVICE, ACCOUNT);
33
+ } catch (err) {
34
+ if (!(err instanceof PasswordDeleteError)) {
35
+ throw err;
36
+ }
37
+ }
38
+ }
39
+ async function getStorageBackend() {
40
+ try {
41
+ const keyring = await getKeyring();
42
+ return keyring.name;
43
+ } catch {
44
+ return "unknown";
45
+ }
46
+ }
47
+
48
+ export {
49
+ getCredentials,
50
+ saveCredentials,
51
+ deleteCredentials,
52
+ getStorageBackend
53
+ };
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-KDMIWPZH.js";
6
6
  import {
7
7
  resolveAuthToken
8
- } from "./chunk-NM25MEJZ.js";
8
+ } from "./chunk-ATX65U7J.js";
9
9
  import {
10
10
  getBaseDomain
11
11
  } from "./chunk-56ZVYB4G.js";
@@ -281,14 +281,14 @@ function hasAccessKeyAndApp(cfg) {
281
281
  function hasX402Wallet(cfg) {
282
282
  return cfg.x402 === true && Boolean(cfg.wallet_key_file?.trim());
283
283
  }
284
- function hasAuthToken(cfg) {
285
- return resolveAuthToken(cfg) !== void 0;
284
+ function hasAuthTokenAndApp(cfg) {
285
+ return resolveAuthToken(cfg) !== void 0 && Boolean(cfg.app?.apiKey);
286
286
  }
287
287
  function getSetupMethod(cfg) {
288
288
  if (hasAPIKey(cfg)) return "api_key";
289
289
  if (hasAccessKeyAndApp(cfg)) return "access_key_app";
290
290
  if (hasX402Wallet(cfg)) return "x402_wallet";
291
- if (hasAuthToken(cfg)) return "auth_token";
291
+ if (hasAuthTokenAndApp(cfg)) return "auth_token";
292
292
  return null;
293
293
  }
294
294
  function isSetupComplete(cfg) {
@@ -2,17 +2,25 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  AUTH_PORT,
5
- getLoginUrl,
6
- performBrowserLogin,
7
- revokeToken
8
- } from "./chunk-5ZAK2VSS.js";
5
+ exchangeCodeForToken,
6
+ openBrowser,
7
+ prepareBrowserLogin,
8
+ revokeToken,
9
+ waitForCallback
10
+ } from "./chunk-FFMNT74F.js";
9
11
  import {
10
12
  isInteractiveAllowed
11
13
  } from "./chunk-KDMIWPZH.js";
12
14
  import {
13
15
  AdminClient,
14
16
  resolveAuthToken
15
- } from "./chunk-NM25MEJZ.js";
17
+ } from "./chunk-ATX65U7J.js";
18
+ import {
19
+ deleteCredentials,
20
+ getCredentials,
21
+ getStorageBackend,
22
+ saveCredentials
23
+ } from "./chunk-JQRGILIS.js";
16
24
  import {
17
25
  bold,
18
26
  brand,
@@ -23,7 +31,6 @@ import {
23
31
  withSpinner
24
32
  } from "./chunk-NBDWF4ZQ.js";
25
33
  import {
26
- configPath,
27
34
  load,
28
35
  maskIf,
29
36
  save
@@ -44,7 +51,7 @@ function registerAuth(program) {
44
51
  const yes = opts.yes || cmd.opts().yes;
45
52
  try {
46
53
  if (!opts.force) {
47
- const existing = resolveAuthToken();
54
+ const existing = await resolveAuthToken();
48
55
  if (existing) {
49
56
  printHuman(
50
57
  ` ${green("\u2713")} Already authenticated
@@ -57,48 +64,89 @@ function registerAuth(program) {
57
64
  }
58
65
  }
59
66
  if (opts.force) {
60
- const cfg2 = load();
61
- if (cfg2.auth_token) {
62
- await revokeToken(cfg2.auth_token);
63
- save({ ...cfg2, auth_token: void 0, auth_token_expires_at: void 0 });
67
+ const existingCreds = await getCredentials();
68
+ const tokenToRevoke = existingCreds?.auth_token;
69
+ if (!tokenToRevoke) {
70
+ const cfg2 = load();
71
+ if (cfg2.auth_token) {
72
+ await revokeToken(cfg2.auth_token);
73
+ save({ ...cfg2, auth_token: void 0, auth_token_expires_at: void 0 });
74
+ }
75
+ } else {
76
+ await revokeToken(tokenToRevoke);
64
77
  }
78
+ await deleteCredentials();
65
79
  }
80
+ const prepared = prepareBrowserLogin();
81
+ const callbackPromise = waitForCallback(AUTH_PORT);
66
82
  if (!isJSONMode()) {
67
83
  console.log("");
68
84
  console.log(` ${brand("\u25C6")} ${bold("Alchemy Authentication")}`);
69
85
  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")}`);
70
86
  console.log("");
71
- console.log(` ${dim(getLoginUrl(AUTH_PORT))}`);
87
+ console.log(` ${dim(prepared.authorizeUrl)}`);
72
88
  console.log("");
73
89
  }
90
+ let browserOpened = false;
74
91
  if (!yes && !isJSONMode() && isInteractiveAllowed(program)) {
75
- const answer = await promptText({
76
- message: "Press Enter to open browser and link your Alchemy account",
77
- cancelMessage: "Login cancelled."
78
- });
79
- if (answer === null) return;
92
+ const promptResult = await Promise.race([
93
+ promptText({
94
+ message: "Press Enter to open browser, or paste the URL above to log in manually",
95
+ cancelMessage: "Login cancelled."
96
+ }),
97
+ callbackPromise.then(() => "callback_received")
98
+ ]);
99
+ if (promptResult === null) return;
100
+ if (promptResult !== "callback_received") {
101
+ if (!isJSONMode()) {
102
+ console.log(` Opening browser to log in...`);
103
+ console.log(` ${dim("Waiting for authentication...")}`);
104
+ }
105
+ openBrowser(prepared.authorizeUrl);
106
+ browserOpened = true;
107
+ }
80
108
  }
81
- if (!isJSONMode()) {
82
- console.log(` Opening browser to log in...`);
83
- console.log(` ${dim("Waiting for authentication...")}`);
109
+ if (!browserOpened && yes) {
110
+ if (!isJSONMode()) {
111
+ console.log(` Opening browser to log in...`);
112
+ console.log(` ${dim("Waiting for authentication...")}`);
113
+ }
114
+ openBrowser(prepared.authorizeUrl);
84
115
  }
85
- const result = await performBrowserLogin();
86
- const cfg = load();
87
- save({
88
- ...cfg,
116
+ const callback = await callbackPromise;
117
+ if (callback.state !== prepared.state) {
118
+ callback.sendError("State mismatch \u2014 possible CSRF attack.");
119
+ throw new Error("OAuth state mismatch. Authentication aborted.");
120
+ }
121
+ let result;
122
+ try {
123
+ result = await exchangeCodeForToken(callback.code, AUTH_PORT, {
124
+ codeVerifier: prepared.codeVerifier
125
+ });
126
+ callback.sendSuccess();
127
+ } catch (err) {
128
+ callback.sendError("Failed to complete authentication. Please try again.");
129
+ throw err;
130
+ }
131
+ await saveCredentials({
89
132
  auth_token: result.token,
90
133
  auth_token_expires_at: result.expiresAt
91
134
  });
135
+ const cfg = load();
136
+ if (cfg.auth_token) {
137
+ save({ ...cfg, auth_token: void 0, auth_token_expires_at: void 0 });
138
+ }
92
139
  const expiresAt = result.expiresAt;
140
+ const backend = await getStorageBackend();
93
141
  printHuman(
94
142
  ` ${green("\u2713")} Logged in successfully
95
- ${dim("Token saved to")} ${configPath()}
143
+ ${dim("Credentials stored in")} ${backend}
96
144
  ${dim("Expires:")} ${expiresAt}
97
145
  `,
98
146
  {
99
147
  status: "authenticated",
100
148
  expiresAt,
101
- configPath: configPath()
149
+ storageBackend: backend
102
150
  }
103
151
  );
104
152
  if (isInteractiveAllowed(program)) {
@@ -111,11 +159,13 @@ function registerAuth(program) {
111
159
  );
112
160
  }
113
161
  });
114
- cmd.command("status").description("Show current authentication status").action(() => {
162
+ cmd.command("status").description("Show current authentication status").action(async () => {
115
163
  try {
164
+ const creds = await getCredentials();
116
165
  const cfg = load();
117
- const validToken = resolveAuthToken(cfg);
118
- if (!cfg.auth_token) {
166
+ const validToken = await resolveAuthToken(cfg);
167
+ const hasToken = creds?.auth_token || cfg.auth_token;
168
+ if (!hasToken) {
119
169
  printHuman(
120
170
  ` ${dim("Not authenticated. Run")} alchemy auth ${dim("to log in.")}
121
171
  `,
@@ -131,15 +181,20 @@ function registerAuth(program) {
131
181
  );
132
182
  return;
133
183
  }
184
+ const expiresAt = creds?.auth_token_expires_at || cfg.auth_token_expires_at || "unknown";
185
+ const backend = await getStorageBackend();
186
+ const storedIn = creds?.auth_token ? backend : "config file (legacy)";
134
187
  printHuman(
135
188
  ` ${green("\u2713")} Authenticated
136
189
  ${dim("Token:")} ${maskIf(validToken)}
137
- ${dim("Expires:")} ${cfg.auth_token_expires_at || "unknown"}
190
+ ${dim("Storage:")} ${storedIn}
191
+ ${dim("Expires:")} ${expiresAt}
138
192
  `,
139
193
  {
140
194
  authenticated: true,
141
195
  expired: false,
142
- expiresAt: cfg.auth_token_expires_at
196
+ expiresAt,
197
+ storageBackend: storedIn
143
198
  }
144
199
  );
145
200
  } catch (err) {
@@ -148,14 +203,17 @@ function registerAuth(program) {
148
203
  });
149
204
  cmd.command("logout").description("Clear saved authentication token").action(async () => {
150
205
  try {
206
+ const creds = await getCredentials();
151
207
  const cfg = load();
208
+ const activeToken = creds?.auth_token || cfg.auth_token;
152
209
  let revokeResult;
153
- if (cfg.auth_token) {
154
- revokeResult = await revokeToken(cfg.auth_token);
210
+ if (activeToken) {
211
+ revokeResult = await revokeToken(activeToken);
155
212
  }
156
- const { auth_token: _, auth_token_expires_at: __, ...rest } = cfg;
213
+ await deleteCredentials();
214
+ const { auth_token: _, auth_token_expires_at: __, app: ___, ...rest } = cfg;
157
215
  save(rest);
158
- if (!cfg.auth_token) {
216
+ if (!activeToken) {
159
217
  printHuman(
160
218
  ` ${dim("No active session.")}
161
219
  `,
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+ import {
4
+ deleteCredentials,
5
+ getCredentials,
6
+ getStorageBackend,
7
+ saveCredentials
8
+ } from "./chunk-JQRGILIS.js";
9
+ export {
10
+ deleteCredentials,
11
+ getCredentials,
12
+ getStorageBackend,
13
+ saveCredentials
14
+ };
package/dist/index.js CHANGED
@@ -2,15 +2,15 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  registerAuth
5
- } from "./chunk-DBTRDS35.js";
6
- import "./chunk-5ZAK2VSS.js";
5
+ } from "./chunk-UMKDYHMO.js";
6
+ import "./chunk-FFMNT74F.js";
7
7
  import {
8
8
  getRPCNetworks,
9
9
  getSetupStatus,
10
10
  isSetupComplete,
11
11
  nativeTokenSymbol,
12
12
  shouldRunOnboarding
13
- } from "./chunk-FM7GQX6U.js";
13
+ } from "./chunk-T5Z2GJUX.js";
14
14
  import {
15
15
  isInteractiveAllowed
16
16
  } from "./chunk-KDMIWPZH.js";
@@ -23,12 +23,13 @@ import {
23
23
  resolveNetwork,
24
24
  resolveWalletKey,
25
25
  resolveX402Client
26
- } from "./chunk-NM25MEJZ.js";
26
+ } from "./chunk-ATX65U7J.js";
27
+ import "./chunk-JQRGILIS.js";
27
28
  import {
28
29
  getAvailableUpdate,
29
30
  getUpdateStatus,
30
31
  printUpdateNotice
31
- } from "./chunk-NSG4ZKZI.js";
32
+ } from "./chunk-7GD5HACA.js";
32
33
  import {
33
34
  bold,
34
35
  brand,
@@ -559,9 +560,15 @@ function registerConfig(program2) {
559
560
  printJSON(toMap(cfg));
560
561
  return;
561
562
  }
562
- const { resolveAuthToken } = await import("./resolve-CLDYJ27A.js");
563
- const validToken = resolveAuthToken(cfg);
564
- const authStatus = cfg.auth_token ? validToken ? `${green("\u2713")} authenticated${cfg.auth_token_expires_at ? ` ${dim(`(expires ${cfg.auth_token_expires_at})`)}` : ""}` : `${yellow("\u25C6")} expired${cfg.auth_token_expires_at ? ` ${dim(`(${cfg.auth_token_expires_at})`)}` : ""}` : dim("(not set) \u2014 run 'alchemy auth' to log in");
563
+ const { resolveAuthToken } = await import("./resolve-HXKHDOJZ.js");
564
+ const { getCredentials, getStorageBackend } = await import("./credential-storage-T6FFW7DG.js");
565
+ const validToken = await resolveAuthToken(cfg);
566
+ const creds = await getCredentials();
567
+ const hasToken = creds?.auth_token || cfg.auth_token;
568
+ const expiresAt = creds?.auth_token_expires_at || cfg.auth_token_expires_at;
569
+ const backend = await getStorageBackend();
570
+ const storageName = creds?.auth_token ? backend : cfg.auth_token ? "config (legacy)" : "";
571
+ const authStatus = hasToken ? validToken ? `${green("\u2713")} authenticated${expiresAt ? ` ${dim(`(expires ${expiresAt})`)}` : ""}${storageName ? ` ${dim(`[${storageName}]`)}` : ""}` : `${yellow("\u25C6")} expired${expiresAt ? ` ${dim(`(${expiresAt})`)}` : ""}` : dim("(not set) \u2014 run 'alchemy auth' to log in");
565
572
  const pairs = [
566
573
  ["auth", authStatus],
567
574
  [
@@ -642,7 +649,7 @@ function registerConfig(program2) {
642
649
 
643
650
  // src/commands/rpc.ts
644
651
  function registerRPC(program2) {
645
- program2.command("rpc").argument("<method>", "JSON-RPC method name (e.g. eth_blockNumber)").argument("[params...]", "Method parameters as JSON values").description("Make a raw JSON-RPC call").addHelpText(
652
+ program2.command("rpc").argument("<method>", "JSON-RPC method name (e.g. eth_blockNumber)").argument("[params...]", "Method parameters as JSON values").description("Sends a raw JSON-RPC request to an Alchemy node (e.g. eth_call, eth_getCode, eth_blockNumber). Use for low-level RPC calls only. For higher-level operations like balances, transfers, simulation, or token data, use the dedicated subcommands instead.").addHelpText(
646
653
  "after",
647
654
  `
648
655
  Examples:
@@ -722,7 +729,7 @@ function resolveBlockParam(block) {
722
729
  return blockParam;
723
730
  }
724
731
  function registerBalance(program2) {
725
- program2.command("balance").argument("[address]", "Wallet address (0x...) or ENS name, or pipe via stdin").alias("bal").description("Get the native token balance of an address").addHelpText(
732
+ program2.command("balance").argument("[address]", "Wallet address (0x...) or ENS name, or pipe via stdin").alias("bal").description("Returns the native token balance (ETH, MATIC, etc.) for any wallet address or ENS name. Use when the user wants to know how much native currency a wallet holds. Does NOT return ERC-20 tokens \u2014 use `alchemy tokens balances` for that.").addHelpText(
726
733
  "after",
727
734
  `
728
735
  Examples:
@@ -1052,7 +1059,7 @@ function formatNFTRows(nfts) {
1052
1059
  ]);
1053
1060
  }
1054
1061
  function registerNFTs(program2) {
1055
- const cmd = program2.command("nfts").description("NFT API wrappers").argument("[address]", "Wallet address or ENS name (default action: list owned NFTs)").option("--limit <n>", "Maximum number of NFTs to return per page", parseInt).option("--page-key <key>", "Pagination key from a previous response").addHelpText(
1062
+ const cmd = program2.command("nfts").description("Lists all NFTs owned by a wallet address on the current network via the Alchemy NFT API. Use for single-network NFT ownership queries. For all NFTs across multiple networks in a portfolio view, use `alchemy portfolio nfts`.").argument("[address]", "Wallet address or ENS name (default action: list owned NFTs)").option("--limit <n>", "Maximum number of NFTs to return per page", parseInt).option("--page-key <key>", "Pagination key from a previous response").addHelpText(
1056
1063
  "after",
1057
1064
  `
1058
1065
  Examples:
@@ -1515,7 +1522,7 @@ function registerApps(program2) {
1515
1522
  const cmd = program2.command("apps").description("Manage Alchemy apps");
1516
1523
  cmd.command("list").description("List all apps").option("--cursor <cursor>", "Pagination cursor").option("--limit <n>", "Max results per page", parseInt).option("--all", "Fetch all pages").option("--search <query>", "Search apps by name or id (client-side)").option("--id <appId>", "Filter by exact app id (client-side)").action(async (opts) => {
1517
1524
  try {
1518
- const admin = adminClientFromFlags(program2);
1525
+ const admin = await adminClientFromFlags(program2);
1519
1526
  const fetchAll = Boolean(opts.all);
1520
1527
  const hasSearch = typeof opts.search === "string";
1521
1528
  const hasId = typeof opts.id === "string";
@@ -1642,7 +1649,7 @@ function registerApps(program2) {
1642
1649
  });
1643
1650
  cmd.command("get <id>").description("Get app details").action(async (id) => {
1644
1651
  try {
1645
- const admin = adminClientFromFlags(program2);
1652
+ const admin = await adminClientFromFlags(program2);
1646
1653
  const app = await withSpinner(
1647
1654
  "Fetching app\u2026",
1648
1655
  "App fetched",
@@ -1676,7 +1683,7 @@ function registerApps(program2) {
1676
1683
  ...products && { products }
1677
1684
  };
1678
1685
  if (handleDryRun(opts, "create", payload, `Would create app "${opts.name}" on networks: ${networks.join(", ")}`)) return;
1679
- const admin = adminClientFromFlags(program2);
1686
+ const admin = await adminClientFromFlags(program2);
1680
1687
  const app = await withSpinner(
1681
1688
  "Creating app\u2026",
1682
1689
  "App created",
@@ -1715,7 +1722,7 @@ function registerApps(program2) {
1715
1722
  return;
1716
1723
  }
1717
1724
  }
1718
- const admin = adminClientFromFlags(program2);
1725
+ const admin = await adminClientFromFlags(program2);
1719
1726
  await withSpinner(
1720
1727
  "Deleting app\u2026",
1721
1728
  "App deleted",
@@ -1741,7 +1748,7 @@ function registerApps(program2) {
1741
1748
  ...opts.description && { description: opts.description }
1742
1749
  };
1743
1750
  if (handleDryRun(opts, "update", payload, `Would update app ${id}`)) return;
1744
- const admin = adminClientFromFlags(program2);
1751
+ const admin = await adminClientFromFlags(program2);
1745
1752
  const app = await withSpinner(
1746
1753
  "Updating app\u2026",
1747
1754
  "App updated",
@@ -1763,7 +1770,7 @@ function registerApps(program2) {
1763
1770
  try {
1764
1771
  const networks = splitCommaList(opts.networks);
1765
1772
  if (handleDryRun(opts, "networks", { id, networks }, `Would update networks for app ${id}: ${networks.join(", ")}`)) return;
1766
- const admin = adminClientFromFlags(program2);
1773
+ const admin = await adminClientFromFlags(program2);
1767
1774
  const app = await withSpinner(
1768
1775
  "Updating networks\u2026",
1769
1776
  "Networks updated",
@@ -1784,7 +1791,7 @@ function registerApps(program2) {
1784
1791
  try {
1785
1792
  const entries = splitCommaList(opts.addresses).map((s) => ({ value: s }));
1786
1793
  if (handleDryRun(opts, "address-allowlist", { id, addresses: entries }, `Would update address allowlist for app ${id}`)) return;
1787
- const admin = adminClientFromFlags(program2);
1794
+ const admin = await adminClientFromFlags(program2);
1788
1795
  const app = await withSpinner(
1789
1796
  "Updating address allowlist\u2026",
1790
1797
  "Address allowlist updated",
@@ -1805,7 +1812,7 @@ function registerApps(program2) {
1805
1812
  try {
1806
1813
  const entries = splitCommaList(opts.origins).map((s) => ({ value: s }));
1807
1814
  if (handleDryRun(opts, "origin-allowlist", { id, origins: entries }, `Would update origin allowlist for app ${id}`)) return;
1808
- const admin = adminClientFromFlags(program2);
1815
+ const admin = await adminClientFromFlags(program2);
1809
1816
  const app = await withSpinner(
1810
1817
  "Updating origin allowlist\u2026",
1811
1818
  "Origin allowlist updated",
@@ -1826,7 +1833,7 @@ function registerApps(program2) {
1826
1833
  try {
1827
1834
  const entries = splitCommaList(opts.ips).map((s) => ({ value: s }));
1828
1835
  if (handleDryRun(opts, "ip-allowlist", { id, ips: entries }, `Would update IP allowlist for app ${id}`)) return;
1829
- const admin = adminClientFromFlags(program2);
1836
+ const admin = await adminClientFromFlags(program2);
1830
1837
  const app = await withSpinner(
1831
1838
  "Updating IP allowlist\u2026",
1832
1839
  "IP allowlist updated",
@@ -1845,7 +1852,7 @@ function registerApps(program2) {
1845
1852
  });
1846
1853
  cmd.command("configured-networks").description("List RPC network slugs configured for an app").option("--app-id <id>", "App ID (overrides saved app)").action(async (opts) => {
1847
1854
  try {
1848
- const admin = adminClientFromFlags(program2);
1855
+ const admin = await adminClientFromFlags(program2);
1849
1856
  const appId = opts.appId || resolveAppId(program2);
1850
1857
  if (!appId) throw errAppRequired();
1851
1858
  const app = await withSpinner(
@@ -1876,7 +1883,7 @@ function registerApps(program2) {
1876
1883
  });
1877
1884
  cmd.command("select [id]").description("Select an app to use as the default").action(async (id) => {
1878
1885
  try {
1879
- const admin = adminClientFromFlags(program2);
1886
+ const admin = await adminClientFromFlags(program2);
1880
1887
  let selected;
1881
1888
  if (id) {
1882
1889
  selected = await withSpinner(
@@ -1934,7 +1941,7 @@ function registerApps(program2) {
1934
1941
  });
1935
1942
  cmd.command("chains").description("List Admin API chain identifiers for app configuration (e.g. ETH_MAINNET)").action(async () => {
1936
1943
  try {
1937
- const admin = adminClientFromFlags(program2);
1944
+ const admin = await adminClientFromFlags(program2);
1938
1945
  const chains = await withSpinner(
1939
1946
  "Fetching chains\u2026",
1940
1947
  "Chains fetched",
@@ -2050,7 +2057,7 @@ function registerWallet(program2) {
2050
2057
  exitWithError(err);
2051
2058
  }
2052
2059
  });
2053
- cmd.command("address").description("Display the address of the configured wallet").action(() => {
2060
+ cmd.command("address").description("Display the address of the locally configured x402 wallet. This shows the CLI's own signing wallet only \u2014 it does NOT look up arbitrary addresses. To check a wallet's ETH balance, use `alchemy balance`.").action(() => {
2054
2061
  try {
2055
2062
  const key = resolveWalletKey(program2);
2056
2063
  if (!key) throw errWalletKeyRequired();
@@ -2075,9 +2082,15 @@ function registerSetup(program2) {
2075
2082
  printJSON(status);
2076
2083
  return;
2077
2084
  }
2085
+ const methodLabels = {
2086
+ api_key: "API key",
2087
+ access_key_app: "Access key + app",
2088
+ x402_wallet: "SIWx wallet",
2089
+ auth_token: "Browser login + app"
2090
+ };
2078
2091
  printKeyValueBox([
2079
2092
  ["Complete", status.complete ? "yes" : "no"],
2080
- ["Satisfied by", status.satisfiedBy ?? dim("(none)")]
2093
+ ["Satisfied by", status.satisfiedBy ? methodLabels[status.satisfiedBy] ?? status.satisfiedBy : dim("(none)")]
2081
2094
  ]);
2082
2095
  if (status.missing.length > 0) {
2083
2096
  console.log("");
@@ -2189,7 +2202,7 @@ function formatTransferRows(transfers) {
2189
2202
  });
2190
2203
  }
2191
2204
  function registerTransfers(program2) {
2192
- program2.command("transfers").argument("[address]", "Wallet address or ENS name \u2014 queries outgoing transfers (use --to-address for incoming)").description("Get transfer history (alchemy_getAssetTransfers)").option("--from-address <address>", "Filter sender address").option("--to-address <address>", "Filter recipient address").option("--from-block <block>", "Start block (default: 0x0)").option("--to-block <block>", "End block (default: latest)").option("--category <list>", "Comma-separated categories (erc20,erc721,erc1155,external,internal,specialnft)").option("--max-count <hexOrDecimal>", "Max records to return per page").option("--page-key <key>", "Pagination key").addHelpText(
2205
+ program2.command("transfers").argument("[address]", "Wallet address or ENS name \u2014 queries outgoing transfers (use --to-address for incoming)").description("Lists all asset transfer transactions for a wallet address \u2014 ERC-20, ERC-721, ERC-1155, and native (ETH) transfers. Use for transaction history or transfer activity. Does NOT return current balances \u2014 use `alchemy balance` or `alchemy tokens balances` for that.").option("--from-address <address>", "Filter sender address").option("--to-address <address>", "Filter recipient address").option("--from-block <block>", "Start block (default: 0x0)").option("--to-block <block>", "End block (default: latest)").option("--category <list>", "Comma-separated categories (erc20,erc721,erc1155,external,internal,specialnft)").option("--max-count <hexOrDecimal>", "Max records to return per page").option("--page-key <key>", "Pagination key").addHelpText(
2193
2206
  "after",
2194
2207
  `
2195
2208
  Examples:
@@ -2411,7 +2424,7 @@ async function runDataCall(program2, title, path, body) {
2411
2424
  }
2412
2425
  function registerPortfolio(program2) {
2413
2426
  const cmd = program2.command("portfolio").description("Portfolio API wrappers");
2414
- cmd.command("tokens").description("Get token portfolio by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/tokens/by-address").action(async (opts) => {
2427
+ cmd.command("tokens").description("Returns all ERC-20 token holdings and their USD values across one or more networks for a wallet address. Use for a portfolio view of fungible token assets. For a single-network NFT list, use `alchemy nfts`; for native token balance, use `alchemy balance`.").requiredOption("--body <json>", "JSON body for /assets/tokens/by-address").action(async (opts) => {
2415
2428
  try {
2416
2429
  const result = await runDataCall(
2417
2430
  program2,
@@ -2439,7 +2452,7 @@ function registerPortfolio(program2) {
2439
2452
  exitWithError(err);
2440
2453
  }
2441
2454
  });
2442
- cmd.command("nfts").description("Get NFT portfolio by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/nfts/by-address").action(async (opts) => {
2455
+ cmd.command("nfts").description("Returns all NFTs owned by a wallet address across one or more networks. Use to list all NFTs a wallet holds in a portfolio view. For metadata on a specific NFT by contract + token ID, use `alchemy nfts metadata`.").requiredOption("--body <json>", "JSON body for /assets/nfts/by-address").action(async (opts) => {
2443
2456
  try {
2444
2457
  const result = await runDataCall(
2445
2458
  program2,
@@ -2481,7 +2494,7 @@ async function runSimulateCall(program2, options) {
2481
2494
  }
2482
2495
  function registerSimulate(program2) {
2483
2496
  const cmd = program2.command("simulate").description("Simulation API wrappers");
2484
- cmd.command("asset-changes").description("Call alchemy_simulateAssetChanges").requiredOption("--tx <json>", "Transaction object JSON").option("--block-tag <tag>", "Block tag (default latest)", "latest").action(async (opts) => {
2497
+ cmd.command("asset-changes").description("Simulates a transaction and returns a human-readable breakdown of asset changes (token transfers, ETH movements, NFT transfers) before it is broadcast. Use to preview what a transaction will do. For raw JSON-RPC calls, use `alchemy rpc` instead.").requiredOption("--tx <json>", "Transaction object JSON").option("--block-tag <tag>", "Block tag (default latest)", "latest").action(async (opts) => {
2485
2498
  try {
2486
2499
  await runSimulateCall(program2, {
2487
2500
  method: "alchemy_simulateAssetChanges",
@@ -2531,7 +2544,7 @@ function resolveWebhookApiKey(opts) {
2531
2544
  function registerWebhooks(program2) {
2532
2545
  const cmd = program2.command("webhooks").description("Notify API wrappers");
2533
2546
  cmd.option("--webhook-api-key <key>", "Webhook API key").option("--notify-token <token>", "Deprecated alias for webhook API key");
2534
- cmd.command("list").description("List team webhooks").action(async () => {
2547
+ cmd.command("list").description("Lists all existing Alchemy webhooks configured for this team. Use to view or audit registered webhooks. Does NOT create webhooks \u2014 use `alchemy webhooks create` for that.").action(async () => {
2535
2548
  try {
2536
2549
  const token = resolveWebhookApiKey(cmd.opts());
2537
2550
  const result = await withSpinner(
@@ -2545,7 +2558,7 @@ function registerWebhooks(program2) {
2545
2558
  exitWithError(err);
2546
2559
  }
2547
2560
  });
2548
- cmd.command("create").description("Create webhook").requiredOption("--body <json>", "Create webhook JSON payload").option("--dry-run", "Preview without executing").action(async (opts) => {
2561
+ cmd.command("create").description("Creates a new Alchemy webhook subscription for a given event type (address activity, mined transactions, dropped transactions, etc.). Use this to register a new webhook endpoint. To view existing webhooks, use `alchemy webhooks list`.").requiredOption("--body <json>", "Create webhook JSON payload").option("--dry-run", "Preview without executing").action(async (opts) => {
2549
2562
  try {
2550
2563
  const payload = parseRequiredJSON(opts.body, "--body");
2551
2564
  if (opts.dryRun) {
@@ -3183,7 +3196,7 @@ function buildAgentPrompt(program2) {
3183
3196
  envVar: "ALCHEMY_ACCESS_KEY",
3184
3197
  flag: "--access-key <key>",
3185
3198
  configKey: "access-key",
3186
- commandFamilies: ["apps", "network list --configured"]
3199
+ commandFamilies: ["apps", "apps configured-networks"]
3187
3200
  },
3188
3201
  {
3189
3202
  method: "Webhook API key",
@@ -3384,7 +3397,7 @@ function resetUpdateNoticeState() {
3384
3397
  }
3385
3398
  program.name("alchemy").description(
3386
3399
  "The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
3387
- ).version("0.5.2", "-v, --version", "display CLI version").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option("--access-key <key>", "Alchemy access key (env: ALCHEMY_ACCESS_KEY)").option(
3400
+ ).version("0.6.1", "-v, --version", "display CLI version").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option("--access-key <key>", "Alchemy access key (env: ALCHEMY_ACCESS_KEY)").option(
3388
3401
  "-n, --network <network>",
3389
3402
  "Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
3390
3403
  ).option("--x402", "Use x402 wallet-based gateway auth").option("--wallet-key-file <path>", "Path to wallet private key file for x402").option("--json", "Force JSON output (auto-enabled when piped)").option("-q, --quiet", "Suppress non-essential output").option("--verbose", "Enable verbose output").option("--no-color", "Disable color output").option("--reveal", "Show secrets in plain text").option("--timeout <ms>", "Request timeout in milliseconds (default: none)", parseInt).option("--debug", "Enable debug diagnostics").option("--no-interactive", "Disable REPL and prompt-driven interactions").addHelpCommand(false).allowExcessArguments(true).exitOverride((err) => {
@@ -3552,11 +3565,11 @@ ${styledLine}`;
3552
3565
  "wallet"
3553
3566
  ];
3554
3567
  if (!skipAppPrompt.includes(cmdName) && isInteractiveAllowed(program) && !opts.apiKey && !process.env.ALCHEMY_API_KEY) {
3555
- const { resolveAuthToken } = await import("./resolve-CLDYJ27A.js");
3556
- const authToken = resolveAuthToken(cfg);
3568
+ const { resolveAuthToken } = await import("./resolve-HXKHDOJZ.js");
3569
+ const authToken = await resolveAuthToken(cfg);
3557
3570
  const hasApiKey = Boolean(cfg.api_key?.trim() || cfg.app?.apiKey);
3558
3571
  if (authToken && !hasApiKey) {
3559
- const { selectAppAfterAuth } = await import("./auth-PYH5WEC3.js");
3572
+ const { selectAppAfterAuth } = await import("./auth-QB3BA7AN.js");
3560
3573
  console.log("");
3561
3574
  console.log(` No app selected. Please select an app to continue.`);
3562
3575
  await selectAppAfterAuth(authToken);
@@ -3587,7 +3600,7 @@ ${styledLine}`;
3587
3600
  if (isInteractiveAllowed(program)) {
3588
3601
  let latestForInteractiveStartup = null;
3589
3602
  if (shouldRunOnboarding(program, cfg)) {
3590
- const { runOnboarding } = await import("./onboarding-DNEXVUUH.js");
3603
+ const { runOnboarding } = await import("./onboarding-3WIM6PVV.js");
3591
3604
  const latest = getAvailableUpdateOnce();
3592
3605
  const completed = await runOnboarding(program, latest);
3593
3606
  updateShownDuringInteractiveStartup = Boolean(latest);
@@ -3599,7 +3612,7 @@ ${styledLine}`;
3599
3612
  latestForInteractiveStartup = getAvailableUpdateOnce();
3600
3613
  updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
3601
3614
  }
3602
- const { startREPL } = await import("./interactive-CGEVIPC2.js");
3615
+ const { startREPL } = await import("./interactive-QJ4REXWB.js");
3603
3616
  program.exitOverride();
3604
3617
  program.configureOutput({
3605
3618
  writeErr: () => {
@@ -3,12 +3,13 @@ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  getRPCNetworkIds,
5
5
  getSetupMethod
6
- } from "./chunk-FM7GQX6U.js";
6
+ } from "./chunk-T5Z2GJUX.js";
7
7
  import "./chunk-KDMIWPZH.js";
8
- import "./chunk-NM25MEJZ.js";
8
+ import "./chunk-ATX65U7J.js";
9
+ import "./chunk-JQRGILIS.js";
9
10
  import {
10
11
  getUpdateNoticeLines
11
- } from "./chunk-NSG4ZKZI.js";
12
+ } from "./chunk-7GD5HACA.js";
12
13
  import {
13
14
  bold,
14
15
  brand,
@@ -127,7 +128,7 @@ function formatSetupMethodLabel() {
127
128
  if (method === "api_key") return "API key";
128
129
  if (method === "access_key_app") return "Access key + app";
129
130
  if (method === "x402_wallet") return "SIWx wallet";
130
- if (method === "auth_token") return "Auth token";
131
+ if (method === "auth_token") return "Browser login + app";
131
132
  return "Not configured";
132
133
  }
133
134
  function replHistoryPath() {
@@ -2,7 +2,7 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  getUpdateNoticeLines
5
- } from "./chunk-NSG4ZKZI.js";
5
+ } from "./chunk-7GD5HACA.js";
6
6
  import {
7
7
  bold,
8
8
  brand,
@@ -11,10 +11,7 @@ import {
11
11
  green,
12
12
  promptText
13
13
  } from "./chunk-NBDWF4ZQ.js";
14
- import {
15
- load,
16
- save
17
- } from "./chunk-BAAQ7ELR.js";
14
+ import "./chunk-BAAQ7ELR.js";
18
15
  import "./chunk-56ZVYB4G.js";
19
16
 
20
17
  // src/commands/onboarding.ts
@@ -38,20 +35,20 @@ async function runOnboarding(_program, latestUpdate = null) {
38
35
  if (answer === null) {
39
36
  return false;
40
37
  }
41
- const { performBrowserLogin, AUTH_PORT, getLoginUrl } = await import("./auth-76PDHQ3U.js");
38
+ const { performBrowserLogin, prepareBrowserLogin } = await import("./auth-S4DTOWW3.js");
39
+ const prepared = prepareBrowserLogin();
42
40
  console.log(` Opening browser to log in...`);
43
- console.log(` ${dim(getLoginUrl(AUTH_PORT))}`);
41
+ console.log(` ${dim(prepared.authorizeUrl)}`);
44
42
  console.log(` ${dim("Waiting for authentication...")}`);
45
43
  try {
46
- const result = await performBrowserLogin();
47
- const cfg = load();
48
- save({
49
- ...cfg,
44
+ const result = await performBrowserLogin(prepared);
45
+ const { saveCredentials } = await import("./credential-storage-T6FFW7DG.js");
46
+ await saveCredentials({
50
47
  auth_token: result.token,
51
48
  auth_token_expires_at: result.expiresAt
52
49
  });
53
50
  console.log(` ${green("\u2713")} Logged in successfully`);
54
- const { selectAppAfterAuth } = await import("./auth-PYH5WEC3.js");
51
+ const { selectAppAfterAuth } = await import("./auth-QB3BA7AN.js");
55
52
  await selectAppAfterAuth(result.token);
56
53
  return true;
57
54
  } catch (err) {
@@ -12,7 +12,8 @@ import {
12
12
  resolveWalletKey,
13
13
  resolveX402,
14
14
  resolveX402Client
15
- } from "./chunk-NM25MEJZ.js";
15
+ } from "./chunk-ATX65U7J.js";
16
+ import "./chunk-JQRGILIS.js";
16
17
  import "./chunk-BAAQ7ELR.js";
17
18
  import "./chunk-56ZVYB4G.js";
18
19
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alchemy/cli",
3
- "version": "0.5.2",
3
+ "version": "0.6.1",
4
4
  "description": "Alchemy CLI — interact with blockchain data",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,6 +33,7 @@
33
33
  "@noble/hashes": "^2.0.1",
34
34
  "cli-table3": "^0.6.5",
35
35
  "commander": "^14.0.3",
36
+ "cross-keychain": "^1.1.0",
36
37
  "zod": "^4.3.6"
37
38
  },
38
39
  "devDependencies": {