@alchemy/cli 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,64 +2,72 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  registerAuth
5
- } from "./chunk-LYUW7O6X.js";
6
- import "./chunk-IGD4NIK7.js";
7
- import {
8
- readStdinArg,
9
- readStdinLines,
10
- registerConfig,
11
- registerWallet,
12
- resolveAddress,
13
- splitCommaList,
14
- validateAddress,
15
- validateTxHash
16
- } from "./chunk-44OGGLN4.js";
5
+ } from "./chunk-UMKDYHMO.js";
6
+ import "./chunk-FFMNT74F.js";
17
7
  import {
18
8
  getRPCNetworks,
19
9
  getSetupStatus,
20
10
  isSetupComplete,
21
11
  nativeTokenSymbol,
22
12
  shouldRunOnboarding
23
- } from "./chunk-5X6YRTPU.js";
13
+ } from "./chunk-T5Z2GJUX.js";
14
+ import {
15
+ isInteractiveAllowed
16
+ } from "./chunk-KDMIWPZH.js";
17
+ import {
18
+ AdminClient,
19
+ adminClientFromFlags,
20
+ clientFromFlags,
21
+ resolveAPIKey,
22
+ resolveAppId,
23
+ resolveNetwork,
24
+ resolveWalletKey,
25
+ resolveX402Client
26
+ } from "./chunk-ATX65U7J.js";
27
+ import "./chunk-JQRGILIS.js";
24
28
  import {
25
29
  getAvailableUpdate,
26
30
  getUpdateStatus,
27
31
  printUpdateNotice
28
- } from "./chunk-Z7J64GJJ.js";
32
+ } from "./chunk-3W4ICF67.js";
29
33
  import {
30
- adminClientFromFlags,
31
34
  bold,
32
35
  brand,
33
36
  brandedHelp,
34
- clientFromFlags,
35
37
  dim,
36
38
  emptyState,
37
39
  etherscanTxURL,
38
40
  failBadge,
39
41
  green,
40
- isInteractiveAllowed,
41
- load,
42
- maskIf,
43
42
  printKeyValueBox,
44
43
  printSyntaxJSON,
45
44
  printTable,
45
+ promptAutocomplete,
46
46
  promptConfirm,
47
+ promptMultiselect,
47
48
  promptSelect,
49
+ promptText,
48
50
  red,
49
- resolveAPIKey,
50
- resolveAppId,
51
- resolveNetwork,
52
- resolveX402Client,
53
- save,
54
51
  successBadge,
55
52
  timeAgo,
56
53
  weiToEth,
57
- withSpinner
58
- } from "./chunk-T2XSNZE3.js";
54
+ withSpinner,
55
+ yellow
56
+ } from "./chunk-NBDWF4ZQ.js";
57
+ import {
58
+ KEY_MAP,
59
+ configDir,
60
+ get,
61
+ load,
62
+ maskIf,
63
+ save,
64
+ toMap
65
+ } from "./chunk-BAAQ7ELR.js";
59
66
  import {
60
67
  EXIT_CODES,
61
68
  ErrorCode,
62
69
  debug,
70
+ errAccessKeyRequired,
63
71
  errAppRequired,
64
72
  errAuthRequired,
65
73
  errInvalidAPIKey,
@@ -68,6 +76,7 @@ import {
68
76
  errNotFound,
69
77
  errRateLimited,
70
78
  errSetupRequired,
79
+ errWalletKeyRequired,
71
80
  esc,
72
81
  exitWithError,
73
82
  fetchWithTimeout,
@@ -87,6 +96,557 @@ import {
87
96
  // src/index.ts
88
97
  import { Command, Help } from "commander";
89
98
 
99
+ // src/lib/ens.ts
100
+ import { keccak_256 } from "@noble/hashes/sha3.js";
101
+ var UNIVERSAL_RESOLVER = "0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe";
102
+ var RESOLVE_SELECTOR = "9061b923";
103
+ var ADDR_SELECTOR = "3b3b57de";
104
+ function bytesToHex(bytes) {
105
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
106
+ }
107
+ function pad32(hex) {
108
+ return hex.padStart(64, "0");
109
+ }
110
+ function namehash(name) {
111
+ let node = new Uint8Array(32);
112
+ if (!name) return node;
113
+ const labels = name.split(".");
114
+ for (let i = labels.length - 1; i >= 0; i--) {
115
+ const labelHash = keccak_256(new TextEncoder().encode(labels[i]));
116
+ const combined = new Uint8Array(64);
117
+ combined.set(node, 0);
118
+ combined.set(labelHash, 32);
119
+ node = keccak_256(combined);
120
+ }
121
+ return node;
122
+ }
123
+ function dnsEncode(name) {
124
+ const labels = name.split(".");
125
+ const parts = [];
126
+ for (const label of labels) {
127
+ const encoded = new TextEncoder().encode(label);
128
+ parts.push(encoded.length);
129
+ parts.push(...encoded);
130
+ }
131
+ parts.push(0);
132
+ return new Uint8Array(parts);
133
+ }
134
+ function buildResolveCalldata(name) {
135
+ const dnsName = dnsEncode(name);
136
+ const node = namehash(name);
137
+ const innerHex = ADDR_SELECTOR + bytesToHex(node);
138
+ const innerLen = 36;
139
+ const dnsHex = bytesToHex(dnsName);
140
+ const nameLen = dnsName.length;
141
+ const namePad = Math.ceil(nameLen / 32) * 32;
142
+ const innerPad = Math.ceil(innerLen / 32) * 32;
143
+ const nameOffset = 64;
144
+ const dataOffset = nameOffset + 32 + namePad;
145
+ let hex = RESOLVE_SELECTOR;
146
+ hex += pad32(nameOffset.toString(16));
147
+ hex += pad32(dataOffset.toString(16));
148
+ hex += pad32(nameLen.toString(16));
149
+ hex += dnsHex.padEnd(namePad * 2, "0");
150
+ hex += pad32(innerLen.toString(16));
151
+ hex += innerHex.padEnd(innerPad * 2, "0");
152
+ return "0x" + hex;
153
+ }
154
+ function isENSName(value) {
155
+ return value.endsWith(".eth") && value.length > 4 && !value.startsWith("0x");
156
+ }
157
+ async function resolveENS(name, client) {
158
+ if (!client.network.startsWith("eth-")) {
159
+ throw errInvalidArgs(
160
+ `ENS resolution is only supported on Ethereum networks. Current network: ${client.network}`
161
+ );
162
+ }
163
+ const calldata = buildResolveCalldata(name.toLowerCase());
164
+ const result = await client.call("eth_call", [
165
+ { to: UNIVERSAL_RESOLVER, data: calldata },
166
+ "latest"
167
+ ]);
168
+ if (!result || result === "0x" || result.length < 130) {
169
+ throw errInvalidArgs(`ENS name "${name}" could not be resolved.`);
170
+ }
171
+ const raw = result.slice(2);
172
+ const dataOffset = parseInt(raw.slice(0, 64), 16) * 2;
173
+ const dataLen = parseInt(raw.slice(dataOffset, dataOffset + 64), 16);
174
+ const dataHex = raw.slice(dataOffset + 64, dataOffset + 64 + dataLen * 2);
175
+ if (dataHex.length < 64) {
176
+ throw errInvalidArgs(`ENS name "${name}" could not be resolved.`);
177
+ }
178
+ const address = "0x" + dataHex.slice(24, 64);
179
+ if (address === "0x0000000000000000000000000000000000000000") {
180
+ throw errInvalidArgs(`ENS name "${name}" is not registered or has no address set.`);
181
+ }
182
+ return address;
183
+ }
184
+
185
+ // src/lib/validators.ts
186
+ function splitCommaList(input) {
187
+ return input.split(",").map((s) => s.trim()).filter(Boolean);
188
+ }
189
+ var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
190
+ var TX_HASH_RE = /^0x[0-9a-fA-F]{64}$/;
191
+ async function readStdinArg(name) {
192
+ if (process.stdin.isTTY) {
193
+ throw errInvalidArgs(`Missing <${name}>. Provide it as an argument or pipe via stdin.`);
194
+ }
195
+ process.stdin.setEncoding("utf-8");
196
+ let input = "";
197
+ for await (const chunk of process.stdin) {
198
+ input += chunk;
199
+ }
200
+ const data = input.trim().split("\n")[0]?.trim() ?? "";
201
+ if (!data) {
202
+ throw errInvalidArgs(`No <${name}> received on stdin.`);
203
+ }
204
+ return data;
205
+ }
206
+ async function readStdinLines(name) {
207
+ if (process.stdin.isTTY) {
208
+ throw errInvalidArgs(`Missing <${name}>. Provide it as an argument or pipe via stdin.`);
209
+ }
210
+ process.stdin.setEncoding("utf-8");
211
+ let input = "";
212
+ for await (const chunk of process.stdin) {
213
+ input += chunk;
214
+ }
215
+ const lines = input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
216
+ if (lines.length === 0) {
217
+ throw errInvalidArgs(`No <${name}> received on stdin.`);
218
+ }
219
+ return lines;
220
+ }
221
+ function validateAddress(address) {
222
+ if (!ADDRESS_RE.test(address)) {
223
+ throw errInvalidArgs(
224
+ `Invalid address "${address}". Expected 0x-prefixed 40-hex-character address.`
225
+ );
226
+ }
227
+ }
228
+ async function resolveAddress(input, client) {
229
+ if (isENSName(input)) {
230
+ return resolveENS(input, client);
231
+ }
232
+ validateAddress(input);
233
+ return input;
234
+ }
235
+ function validateTxHash(hash) {
236
+ if (!TX_HASH_RE.test(hash)) {
237
+ throw errInvalidArgs(
238
+ `Invalid transaction hash "${hash}". Expected 0x-prefixed 64-hex-character hash.`
239
+ );
240
+ }
241
+ }
242
+
243
+ // src/commands/config.ts
244
+ var RESET_KEY_MAP = { ...KEY_MAP, app: "app" };
245
+ var APP_SEARCH_THRESHOLD = 15;
246
+ async function saveAppWithPrompt(app) {
247
+ const cfg = load();
248
+ const updated = {
249
+ ...cfg,
250
+ api_key: app.apiKey,
251
+ app: { id: app.id, name: app.name, apiKey: app.apiKey, webhookApiKey: app.webhookApiKey }
252
+ };
253
+ if (cfg.api_key) {
254
+ const replace = await promptConfirm({
255
+ message: "You already have an API key configured. Use the app's API key instead?",
256
+ initialValue: true,
257
+ cancelMessage: "Cancelled default app update."
258
+ });
259
+ if (replace === null) {
260
+ return false;
261
+ }
262
+ if (!replace) {
263
+ updated.api_key = cfg.api_key;
264
+ }
265
+ }
266
+ save(updated);
267
+ return true;
268
+ }
269
+ async function selectOrCreateApp(admin) {
270
+ let apps;
271
+ try {
272
+ const result = await withSpinner(
273
+ "Fetching apps\u2026",
274
+ "Apps fetched",
275
+ () => admin.listAllApps()
276
+ );
277
+ apps = result.apps;
278
+ } catch {
279
+ console.log(
280
+ ` ${dim("Could not fetch apps. Skipping app selection.")}`
281
+ );
282
+ return;
283
+ }
284
+ if (apps.length > 0) {
285
+ const CREATE_NEW = "__create_new__";
286
+ const options = [
287
+ ...apps.map((a) => ({
288
+ label: `${a.name} (${a.id})`,
289
+ value: a.id
290
+ })),
291
+ { label: "Create a new app", value: CREATE_NEW }
292
+ ];
293
+ const selected = apps.length > APP_SEARCH_THRESHOLD ? await promptAutocomplete({
294
+ message: "Select default app",
295
+ placeholder: "Type app name or id",
296
+ options,
297
+ cancelMessage: "Cancelled app selection.",
298
+ commitLabel: null
299
+ }) : await promptSelect({
300
+ message: "Select default app",
301
+ options,
302
+ cancelMessage: "Cancelled app selection.",
303
+ commitLabel: null
304
+ });
305
+ if (selected === null) {
306
+ return;
307
+ }
308
+ if (selected !== CREATE_NEW) {
309
+ const app = apps.find((a) => a.id === selected);
310
+ const saved = await saveAppWithPrompt(app);
311
+ if (saved) {
312
+ console.log(`
313
+ ${green("\u2713")} Default app set to ${app.name} (${app.id})`);
314
+ } else {
315
+ console.log(` ${dim("Skipped setting default app.")}`);
316
+ }
317
+ return;
318
+ }
319
+ } else {
320
+ console.log(` ${dim("No apps found. Let's create one.")}`);
321
+ }
322
+ const name = await promptText({
323
+ message: "App name",
324
+ cancelMessage: "Cancelled app creation."
325
+ });
326
+ if (name === null) {
327
+ return;
328
+ }
329
+ if (!name.trim()) {
330
+ console.log(` ${dim("Skipped app creation.")}`);
331
+ return;
332
+ }
333
+ let chainChoices = [];
334
+ try {
335
+ const chains = await withSpinner(
336
+ "Fetching chains\u2026",
337
+ "Chains fetched",
338
+ () => admin.listChains()
339
+ );
340
+ chainChoices = chains.filter((c) => c.availability === "public" && !c.isTestnet).map((c) => ({ label: `${c.name} (${c.id})`, value: c.id }));
341
+ } catch {
342
+ }
343
+ let networks;
344
+ if (chainChoices.length > 0) {
345
+ const selectedNetworks = await promptMultiselect({
346
+ message: "Select networks",
347
+ options: chainChoices,
348
+ required: true,
349
+ cancelMessage: "Cancelled network selection."
350
+ });
351
+ if (selectedNetworks === null) {
352
+ return;
353
+ }
354
+ networks = selectedNetworks;
355
+ } else {
356
+ const raw = await promptText({
357
+ message: "Network IDs (comma-separated)",
358
+ cancelMessage: "Cancelled network selection."
359
+ });
360
+ if (raw === null) {
361
+ return;
362
+ }
363
+ networks = splitCommaList(raw);
364
+ }
365
+ if (networks.length === 0) {
366
+ console.log(` ${dim("No networks selected. Skipped app creation.")}`);
367
+ return;
368
+ }
369
+ try {
370
+ const app = await withSpinner(
371
+ "Creating app\u2026",
372
+ "App created",
373
+ () => admin.createApp({ name: name.trim(), networks })
374
+ );
375
+ console.log(` ${green("\u2713")} Created app ${app.name} (${app.id})`);
376
+ const setDefault = await promptConfirm({
377
+ message: "Set as default app?",
378
+ initialValue: true,
379
+ cancelMessage: "Cancelled default app selection."
380
+ });
381
+ if (setDefault === null) {
382
+ return;
383
+ }
384
+ if (setDefault) {
385
+ const saved = await saveAppWithPrompt(app);
386
+ if (saved) {
387
+ console.log(`
388
+ ${green("\u2713")} Default app set to ${app.name} (${app.id})`);
389
+ } else {
390
+ console.log(` ${dim("Skipped setting default app.")}`);
391
+ }
392
+ }
393
+ } catch (err) {
394
+ exitWithError(err);
395
+ }
396
+ }
397
+ function registerConfig(program2) {
398
+ const cmd = program2.command("config").description("Manage CLI configuration");
399
+ const setCmd = cmd.command("set").description("Set a config value");
400
+ setCmd.command("api-key <key>").description("Set the Alchemy API key for RPC requests").action((key) => {
401
+ try {
402
+ const cfg = load();
403
+ save({ ...cfg, api_key: key });
404
+ printHuman(`${green("\u2713")} Set api-key
405
+ `, { key: "api-key", status: "set" });
406
+ if (!isJSONMode() && cfg.app?.apiKey && cfg.app.apiKey !== key) {
407
+ console.log(
408
+ ` ${yellow("\u25C6")} ${dim("Warning: api-key differs from the selected app key. RPC commands use api-key; run 'alchemy config set app <app-id>' to resync.")}`
409
+ );
410
+ }
411
+ } catch (err) {
412
+ exitWithError(err);
413
+ }
414
+ });
415
+ setCmd.command("access-key <key>").description("Set the Alchemy access key for Admin API operations").action(async (key) => {
416
+ try {
417
+ const cfg = load();
418
+ save({ ...cfg, access_key: key });
419
+ printHuman(`${green("\u2713")} Set access-key
420
+ `, { key: "access-key", status: "set" });
421
+ if (isInteractiveAllowed(program2)) {
422
+ await selectOrCreateApp(new AdminClient(key));
423
+ }
424
+ } catch (err) {
425
+ exitWithError(err);
426
+ }
427
+ });
428
+ setCmd.command("webhook-api-key <key>").description("Set the Alchemy webhook API key for Notify operations").action((key) => {
429
+ try {
430
+ const cfg = load();
431
+ save({ ...cfg, webhook_api_key: key });
432
+ printHuman(`${green("\u2713")} Set webhook-api-key
433
+ `, { key: "webhook-api-key", status: "set" });
434
+ } catch (err) {
435
+ exitWithError(err);
436
+ }
437
+ });
438
+ setCmd.command("app [app-id]").description("Select the default app (interactive) or set by ID").action(async (appId) => {
439
+ try {
440
+ const cfg = load();
441
+ const accessKey = program2.opts().accessKey || process.env.ALCHEMY_ACCESS_KEY || cfg.access_key;
442
+ if (!accessKey) throw errAccessKeyRequired();
443
+ if (appId) {
444
+ const admin = new AdminClient(accessKey);
445
+ const app = await withSpinner(
446
+ "Fetching app\u2026",
447
+ "App fetched",
448
+ () => admin.getApp(appId)
449
+ );
450
+ const updated = {
451
+ ...cfg,
452
+ api_key: app.apiKey,
453
+ app: { id: app.id, name: app.name, apiKey: app.apiKey, webhookApiKey: app.webhookApiKey }
454
+ };
455
+ save(updated);
456
+ printHuman(
457
+ `${green("\u2713")} Default app set to ${app.name} (${app.id})
458
+ `,
459
+ { app: { id: app.id, name: app.name }, status: "set" }
460
+ );
461
+ return;
462
+ }
463
+ if (!isInteractiveAllowed(program2)) {
464
+ exitWithError(
465
+ new Error("Interactive app selection requires an interactive terminal. Use 'config set app <app-id>' or 'alchemy apps list' to find app IDs.")
466
+ );
467
+ }
468
+ await selectOrCreateApp(new AdminClient(accessKey));
469
+ } catch (err) {
470
+ exitWithError(err);
471
+ }
472
+ });
473
+ setCmd.command("network <network>").description("Set the default network (e.g. eth-mainnet, polygon-mainnet)").action((network) => {
474
+ try {
475
+ const cfg = load();
476
+ save({ ...cfg, network });
477
+ printHuman(`${green("\u2713")} Set network to ${network}
478
+ `, { key: "network", value: network, status: "set" });
479
+ } catch (err) {
480
+ exitWithError(err);
481
+ }
482
+ });
483
+ setCmd.command("verbose <enabled>").description("Set default verbose output (true|false)").action((enabled) => {
484
+ try {
485
+ const normalized = enabled.trim().toLowerCase();
486
+ if (normalized !== "true" && normalized !== "false") {
487
+ throw errInvalidArgs("verbose must be 'true' or 'false'");
488
+ }
489
+ const verbose2 = normalized === "true";
490
+ const cfg = load();
491
+ save({ ...cfg, verbose: verbose2 });
492
+ printHuman(
493
+ `${green("\u2713")} Set verbose default to ${verbose2}
494
+ `,
495
+ { key: "verbose", value: String(verbose2), status: "set" }
496
+ );
497
+ } catch (err) {
498
+ exitWithError(err);
499
+ }
500
+ });
501
+ setCmd.command("wallet-key-file <path>").description("Set the path to a wallet private key file for x402").action((path) => {
502
+ try {
503
+ const cfg = load();
504
+ save({ ...cfg, wallet_key_file: path });
505
+ printHuman(`${green("\u2713")} Set wallet-key-file
506
+ `, { key: "wallet-key-file", status: "set" });
507
+ } catch (err) {
508
+ exitWithError(err);
509
+ }
510
+ });
511
+ setCmd.command("x402 <enabled>").description("Enable or disable x402 wallet-based auth by default (true|false)").action((enabled) => {
512
+ try {
513
+ const normalized = enabled.trim().toLowerCase();
514
+ if (normalized !== "true" && normalized !== "false") {
515
+ throw errInvalidArgs("x402 must be 'true' or 'false'");
516
+ }
517
+ const x402 = normalized === "true";
518
+ const cfg = load();
519
+ save({ ...cfg, x402 });
520
+ printHuman(
521
+ `${green("\u2713")} Set x402 default to ${x402}
522
+ `,
523
+ { key: "x402", value: String(x402), status: "set" }
524
+ );
525
+ } catch (err) {
526
+ exitWithError(err);
527
+ }
528
+ });
529
+ cmd.command("get <key>").description("Get a config value (api-key, access-key, app, network, verbose, wallet-key-file, x402)").action((key) => {
530
+ const cfg = load();
531
+ let value = get(cfg, key);
532
+ let isDefault = false;
533
+ if (value === void 0) {
534
+ const defaults = {
535
+ network: "eth-mainnet",
536
+ verbose: "false",
537
+ x402: "false"
538
+ };
539
+ const normalizedKey = KEY_MAP[key] ?? key;
540
+ const defaultValue = defaults[normalizedKey] ?? defaults[key];
541
+ if (defaultValue !== void 0) {
542
+ value = defaultValue;
543
+ isDefault = true;
544
+ }
545
+ }
546
+ if (value === void 0) {
547
+ exitWithError(errNotFound(`config key '${key}'`));
548
+ }
549
+ const isSecret = key === "api-key" || key === "api_key" || key === "access-key" || key === "access_key";
550
+ const display = isSecret ? maskIf(value) : value;
551
+ const humanDisplay = isDefault ? `${display} ${dim("(default)")}` : display;
552
+ printHuman(humanDisplay + "\n", { key, value: display, ...isDefault && { default: true } });
553
+ });
554
+ cmd.command("list").description("List all config values").action(async () => {
555
+ const cfg = load();
556
+ const hasApiKeyMismatch = Boolean(
557
+ cfg.api_key && cfg.app?.apiKey && cfg.api_key !== cfg.app.apiKey
558
+ );
559
+ if (isJSONMode()) {
560
+ printJSON(toMap(cfg));
561
+ return;
562
+ }
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");
572
+ const pairs = [
573
+ ["auth", authStatus],
574
+ [
575
+ "api-key",
576
+ cfg.api_key ? `${hasApiKeyMismatch ? `${yellow("\u25C6")} ` : ""}${maskIf(cfg.api_key)}` : dim("(not set)")
577
+ ],
578
+ ["access-key", cfg.access_key ? maskIf(cfg.access_key) : dim("(not set)")],
579
+ ["webhook-api-key", cfg.webhook_api_key ? maskIf(cfg.webhook_api_key) : dim("(not set)")],
580
+ [
581
+ "app",
582
+ cfg.app ? `${cfg.app.name} ${dim(`(${cfg.app.id})`)}` : dim("(not set) \u2014 set automatically via 'alchemy auth' or 'config set app'")
583
+ ],
584
+ ["network", cfg.network || dim("(not set, defaults to eth-mainnet)")],
585
+ [
586
+ "verbose",
587
+ cfg.verbose !== void 0 ? String(cfg.verbose) : dim("(not set, defaults to false)")
588
+ ],
589
+ ["wallet-key-file", cfg.wallet_key_file || dim("(not set)")],
590
+ ["wallet-address", cfg.wallet_address || dim("(not set)")],
591
+ [
592
+ "x402",
593
+ cfg.x402 !== void 0 ? String(cfg.x402) : dim("(not set, defaults to false)")
594
+ ]
595
+ ];
596
+ printKeyValueBox(pairs);
597
+ if (hasApiKeyMismatch) {
598
+ console.log("");
599
+ console.log(
600
+ ` ${yellow("\u25C6")} ${dim("Warning: api-key differs from the selected app key. RPC commands use api-key; run 'alchemy config set app <app-id>' to resync.")}`
601
+ );
602
+ }
603
+ });
604
+ cmd.command("reset [key]").description("Reset config values (all or a specific key)").option("-y, --yes", "Skip confirmation prompt for full reset").action(async (key, options) => {
605
+ try {
606
+ if (key) {
607
+ const mapped = RESET_KEY_MAP[key];
608
+ if (!mapped) {
609
+ throw errInvalidArgs(
610
+ `invalid reset key '${key}' (valid: api-key, access-key, app, network, verbose, wallet-key-file, x402)`
611
+ );
612
+ }
613
+ const cfg = load();
614
+ const updated = { ...cfg };
615
+ delete updated[mapped];
616
+ save(updated);
617
+ printHuman(`${green("\u2713")} Reset ${key}
618
+ `, {
619
+ status: "reset",
620
+ key
621
+ });
622
+ return;
623
+ }
624
+ if (!options.yes && isInteractiveAllowed(program2)) {
625
+ const proceed = await promptConfirm({
626
+ message: "Reset all saved config values?",
627
+ initialValue: false,
628
+ cancelMessage: "Cancelled config reset."
629
+ });
630
+ if (proceed === null) {
631
+ return;
632
+ }
633
+ if (!proceed) {
634
+ console.log(` ${dim("Skipped config reset.")}`);
635
+ return;
636
+ }
637
+ }
638
+ save({});
639
+ printHuman(`${green("\u2713")} Reset all config values
640
+ `, {
641
+ status: "reset",
642
+ scope: "all"
643
+ });
644
+ } catch (err) {
645
+ exitWithError(err);
646
+ }
647
+ });
648
+ }
649
+
90
650
  // src/commands/rpc.ts
91
651
  function registerRPC(program2) {
92
652
  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(
@@ -962,7 +1522,7 @@ function registerApps(program2) {
962
1522
  const cmd = program2.command("apps").description("Manage Alchemy apps");
963
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) => {
964
1524
  try {
965
- const admin = adminClientFromFlags(program2);
1525
+ const admin = await adminClientFromFlags(program2);
966
1526
  const fetchAll = Boolean(opts.all);
967
1527
  const hasSearch = typeof opts.search === "string";
968
1528
  const hasId = typeof opts.id === "string";
@@ -1089,7 +1649,7 @@ function registerApps(program2) {
1089
1649
  });
1090
1650
  cmd.command("get <id>").description("Get app details").action(async (id) => {
1091
1651
  try {
1092
- const admin = adminClientFromFlags(program2);
1652
+ const admin = await adminClientFromFlags(program2);
1093
1653
  const app = await withSpinner(
1094
1654
  "Fetching app\u2026",
1095
1655
  "App fetched",
@@ -1123,7 +1683,7 @@ function registerApps(program2) {
1123
1683
  ...products && { products }
1124
1684
  };
1125
1685
  if (handleDryRun(opts, "create", payload, `Would create app "${opts.name}" on networks: ${networks.join(", ")}`)) return;
1126
- const admin = adminClientFromFlags(program2);
1686
+ const admin = await adminClientFromFlags(program2);
1127
1687
  const app = await withSpinner(
1128
1688
  "Creating app\u2026",
1129
1689
  "App created",
@@ -1162,7 +1722,7 @@ function registerApps(program2) {
1162
1722
  return;
1163
1723
  }
1164
1724
  }
1165
- const admin = adminClientFromFlags(program2);
1725
+ const admin = await adminClientFromFlags(program2);
1166
1726
  await withSpinner(
1167
1727
  "Deleting app\u2026",
1168
1728
  "App deleted",
@@ -1188,7 +1748,7 @@ function registerApps(program2) {
1188
1748
  ...opts.description && { description: opts.description }
1189
1749
  };
1190
1750
  if (handleDryRun(opts, "update", payload, `Would update app ${id}`)) return;
1191
- const admin = adminClientFromFlags(program2);
1751
+ const admin = await adminClientFromFlags(program2);
1192
1752
  const app = await withSpinner(
1193
1753
  "Updating app\u2026",
1194
1754
  "App updated",
@@ -1210,7 +1770,7 @@ function registerApps(program2) {
1210
1770
  try {
1211
1771
  const networks = splitCommaList(opts.networks);
1212
1772
  if (handleDryRun(opts, "networks", { id, networks }, `Would update networks for app ${id}: ${networks.join(", ")}`)) return;
1213
- const admin = adminClientFromFlags(program2);
1773
+ const admin = await adminClientFromFlags(program2);
1214
1774
  const app = await withSpinner(
1215
1775
  "Updating networks\u2026",
1216
1776
  "Networks updated",
@@ -1231,7 +1791,7 @@ function registerApps(program2) {
1231
1791
  try {
1232
1792
  const entries = splitCommaList(opts.addresses).map((s) => ({ value: s }));
1233
1793
  if (handleDryRun(opts, "address-allowlist", { id, addresses: entries }, `Would update address allowlist for app ${id}`)) return;
1234
- const admin = adminClientFromFlags(program2);
1794
+ const admin = await adminClientFromFlags(program2);
1235
1795
  const app = await withSpinner(
1236
1796
  "Updating address allowlist\u2026",
1237
1797
  "Address allowlist updated",
@@ -1252,7 +1812,7 @@ function registerApps(program2) {
1252
1812
  try {
1253
1813
  const entries = splitCommaList(opts.origins).map((s) => ({ value: s }));
1254
1814
  if (handleDryRun(opts, "origin-allowlist", { id, origins: entries }, `Would update origin allowlist for app ${id}`)) return;
1255
- const admin = adminClientFromFlags(program2);
1815
+ const admin = await adminClientFromFlags(program2);
1256
1816
  const app = await withSpinner(
1257
1817
  "Updating origin allowlist\u2026",
1258
1818
  "Origin allowlist updated",
@@ -1273,7 +1833,7 @@ function registerApps(program2) {
1273
1833
  try {
1274
1834
  const entries = splitCommaList(opts.ips).map((s) => ({ value: s }));
1275
1835
  if (handleDryRun(opts, "ip-allowlist", { id, ips: entries }, `Would update IP allowlist for app ${id}`)) return;
1276
- const admin = adminClientFromFlags(program2);
1836
+ const admin = await adminClientFromFlags(program2);
1277
1837
  const app = await withSpinner(
1278
1838
  "Updating IP allowlist\u2026",
1279
1839
  "IP allowlist updated",
@@ -1292,7 +1852,7 @@ function registerApps(program2) {
1292
1852
  });
1293
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) => {
1294
1854
  try {
1295
- const admin = adminClientFromFlags(program2);
1855
+ const admin = await adminClientFromFlags(program2);
1296
1856
  const appId = opts.appId || resolveAppId(program2);
1297
1857
  if (!appId) throw errAppRequired();
1298
1858
  const app = await withSpinner(
@@ -1323,7 +1883,7 @@ function registerApps(program2) {
1323
1883
  });
1324
1884
  cmd.command("select [id]").description("Select an app to use as the default").action(async (id) => {
1325
1885
  try {
1326
- const admin = adminClientFromFlags(program2);
1886
+ const admin = await adminClientFromFlags(program2);
1327
1887
  let selected;
1328
1888
  if (id) {
1329
1889
  selected = await withSpinner(
@@ -1381,7 +1941,7 @@ function registerApps(program2) {
1381
1941
  });
1382
1942
  cmd.command("chains").description("List Admin API chain identifiers for app configuration (e.g. ETH_MAINNET)").action(async () => {
1383
1943
  try {
1384
- const admin = adminClientFromFlags(program2);
1944
+ const admin = await adminClientFromFlags(program2);
1385
1945
  const chains = await withSpinner(
1386
1946
  "Fetching chains\u2026",
1387
1947
  "Chains fetched",
@@ -1420,6 +1980,99 @@ function registerApps(program2) {
1420
1980
  });
1421
1981
  }
1422
1982
 
1983
+ // src/commands/wallet.ts
1984
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
1985
+ import { join, dirname } from "path";
1986
+ import { randomUUID } from "crypto";
1987
+ import { generateWallet, getWalletAddress } from "@alchemy/x402";
1988
+ var WALLET_KEYS_DIR = "wallet-keys";
1989
+ var UUID_SLICE_LEN = 8;
1990
+ var ADDRESS_SLICE_LEN = 12;
1991
+ function walletKeysDirPath() {
1992
+ return join(configDir(), WALLET_KEYS_DIR);
1993
+ }
1994
+ function walletKeyPath(address) {
1995
+ const addr = address.trim().toLowerCase().replace(/^0x/, "").replace(/[^a-z0-9]/g, "").slice(0, ADDRESS_SLICE_LEN);
1996
+ const addressTag = addr || "unknown";
1997
+ const fileName = `wallet-key-${addressTag}-${Date.now()}-${randomUUID().slice(0, UUID_SLICE_LEN)}.txt`;
1998
+ return join(walletKeysDirPath(), fileName);
1999
+ }
2000
+ function persistWalletKey(privateKey, address) {
2001
+ const keyPath = walletKeyPath(address);
2002
+ mkdirSync(dirname(keyPath), { recursive: true, mode: 493 });
2003
+ writeFileSync(keyPath, privateKey + "\n", { mode: 384, flag: "wx" });
2004
+ return keyPath;
2005
+ }
2006
+ function generateAndPersistWallet() {
2007
+ const wallet = generateWallet();
2008
+ const keyPath = persistWalletKey(wallet.privateKey, wallet.address);
2009
+ const cfg = load();
2010
+ save({ ...cfg, wallet_key_file: keyPath, wallet_address: wallet.address });
2011
+ return { address: wallet.address, keyFile: keyPath };
2012
+ }
2013
+ function importAndPersistWallet(path) {
2014
+ let key;
2015
+ try {
2016
+ key = readFileSync(path, "utf-8").trim();
2017
+ } catch {
2018
+ throw errInvalidArgs(`Could not read key file: ${path}`);
2019
+ }
2020
+ const address = getWalletAddress(key);
2021
+ const keyPath = persistWalletKey(key, address);
2022
+ const cfg = load();
2023
+ save({ ...cfg, wallet_key_file: keyPath, wallet_address: address });
2024
+ return { address, keyFile: keyPath };
2025
+ }
2026
+ function registerWallet(program2) {
2027
+ const cmd = program2.command("wallet").description("Manage x402 wallet");
2028
+ cmd.command("generate").description("Generate a new wallet for x402 authentication").action(() => {
2029
+ try {
2030
+ const wallet = generateAndPersistWallet();
2031
+ if (isJSONMode()) {
2032
+ printJSON(wallet);
2033
+ } else {
2034
+ printKeyValueBox([
2035
+ ["Address", green(wallet.address)],
2036
+ ["Key file", wallet.keyFile]
2037
+ ]);
2038
+ console.log(` ${green("\u2713")} Wallet generated and saved to config`);
2039
+ }
2040
+ } catch (err) {
2041
+ exitWithError(err);
2042
+ }
2043
+ });
2044
+ cmd.command("import").argument("<path>", "Path to private key file").description("Import a wallet from a private key file").action((path) => {
2045
+ try {
2046
+ const wallet = importAndPersistWallet(path);
2047
+ if (isJSONMode()) {
2048
+ printJSON(wallet);
2049
+ } else {
2050
+ printKeyValueBox([
2051
+ ["Address", green(wallet.address)],
2052
+ ["Key file", wallet.keyFile]
2053
+ ]);
2054
+ console.log(` ${green("\u2713")} Wallet imported and saved to config`);
2055
+ }
2056
+ } catch (err) {
2057
+ exitWithError(err);
2058
+ }
2059
+ });
2060
+ cmd.command("address").description("Display the address of the configured wallet").action(() => {
2061
+ try {
2062
+ const key = resolveWalletKey(program2);
2063
+ if (!key) throw errWalletKeyRequired();
2064
+ const address = getWalletAddress(key);
2065
+ printHuman(
2066
+ `${address}
2067
+ `,
2068
+ { address }
2069
+ );
2070
+ } catch (err) {
2071
+ exitWithError(err);
2072
+ }
2073
+ });
2074
+ }
2075
+
1423
2076
  // src/commands/setup.ts
1424
2077
  function registerSetup(program2) {
1425
2078
  const cmd = program2.command("setup").description("Setup and onboarding utilities");
@@ -1429,9 +2082,15 @@ function registerSetup(program2) {
1429
2082
  printJSON(status);
1430
2083
  return;
1431
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
+ };
1432
2091
  printKeyValueBox([
1433
2092
  ["Complete", status.complete ? "yes" : "no"],
1434
- ["Satisfied by", status.satisfiedBy ?? dim("(none)")]
2093
+ ["Satisfied by", status.satisfiedBy ? methodLabels[status.satisfiedBy] ?? status.satisfiedBy : dim("(none)")]
1435
2094
  ]);
1436
2095
  if (status.missing.length > 0) {
1437
2096
  console.log("");
@@ -2537,7 +3196,7 @@ function buildAgentPrompt(program2) {
2537
3196
  envVar: "ALCHEMY_ACCESS_KEY",
2538
3197
  flag: "--access-key <key>",
2539
3198
  configKey: "access-key",
2540
- commandFamilies: ["apps", "network list --configured"]
3199
+ commandFamilies: ["apps", "apps configured-networks"]
2541
3200
  },
2542
3201
  {
2543
3202
  method: "Webhook API key",
@@ -2738,7 +3397,7 @@ function resetUpdateNoticeState() {
2738
3397
  }
2739
3398
  program.name("alchemy").description(
2740
3399
  "The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
2741
- ).version("0.5.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(
3400
+ ).version("0.6.0", "-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(
2742
3401
  "-n, --network <network>",
2743
3402
  "Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
2744
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) => {
@@ -2881,7 +3540,7 @@ ${styledLine}`;
2881
3540
  ` ${hDim("\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")}`,
2882
3541
  ` ${hDim("Docs:")} ${hBrand("https://www.alchemy.com/docs")}`
2883
3542
  ].join("\n");
2884
- }).hook("preAction", () => {
3543
+ }).hook("preAction", async (thisCommand, actionCommand) => {
2885
3544
  const opts = program.opts();
2886
3545
  if (opts.color === false) setNoColor(true);
2887
3546
  const cfg = load();
@@ -2893,6 +3552,29 @@ ${styledLine}`;
2893
3552
  reveal: Boolean(opts.reveal),
2894
3553
  timeout: opts.timeout
2895
3554
  });
3555
+ const cmdName = actionCommand.name();
3556
+ const skipAppPrompt = [
3557
+ "auth",
3558
+ "config",
3559
+ "setup",
3560
+ "help",
3561
+ "version",
3562
+ "completions",
3563
+ "agent-prompt",
3564
+ "update-check",
3565
+ "wallet"
3566
+ ];
3567
+ if (!skipAppPrompt.includes(cmdName) && isInteractiveAllowed(program) && !opts.apiKey && !process.env.ALCHEMY_API_KEY) {
3568
+ const { resolveAuthToken } = await import("./resolve-HXKHDOJZ.js");
3569
+ const authToken = await resolveAuthToken(cfg);
3570
+ const hasApiKey = Boolean(cfg.api_key?.trim() || cfg.app?.apiKey);
3571
+ if (authToken && !hasApiKey) {
3572
+ const { selectAppAfterAuth } = await import("./auth-QB3BA7AN.js");
3573
+ console.log("");
3574
+ console.log(` No app selected. Please select an app to continue.`);
3575
+ await selectAppAfterAuth(authToken);
3576
+ }
3577
+ }
2896
3578
  }).hook("postAction", () => {
2897
3579
  if (!isJSONMode() && !quiet) {
2898
3580
  console.log("");
@@ -2918,7 +3600,7 @@ ${styledLine}`;
2918
3600
  if (isInteractiveAllowed(program)) {
2919
3601
  let latestForInteractiveStartup = null;
2920
3602
  if (shouldRunOnboarding(program, cfg)) {
2921
- const { runOnboarding } = await import("./onboarding-CWCVWSUG.js");
3603
+ const { runOnboarding } = await import("./onboarding-S3GAP4OV.js");
2922
3604
  const latest = getAvailableUpdateOnce();
2923
3605
  const completed = await runOnboarding(program, latest);
2924
3606
  updateShownDuringInteractiveStartup = Boolean(latest);
@@ -2930,7 +3612,7 @@ ${styledLine}`;
2930
3612
  latestForInteractiveStartup = getAvailableUpdateOnce();
2931
3613
  updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
2932
3614
  }
2933
- const { startREPL } = await import("./interactive-G4ON47AR.js");
3615
+ const { startREPL } = await import("./interactive-OM476LBG.js");
2934
3616
  program.exitOverride();
2935
3617
  program.configureOutput({
2936
3618
  writeErr: () => {