@alchemy/cli 0.5.1 → 0.5.2

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,71 @@
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-DBTRDS35.js";
6
+ import "./chunk-5ZAK2VSS.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-FM7GQX6U.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-NM25MEJZ.js";
24
27
  import {
25
28
  getAvailableUpdate,
26
29
  getUpdateStatus,
27
30
  printUpdateNotice
28
- } from "./chunk-Z7J64GJJ.js";
31
+ } from "./chunk-NSG4ZKZI.js";
29
32
  import {
30
- adminClientFromFlags,
31
33
  bold,
32
34
  brand,
33
35
  brandedHelp,
34
- clientFromFlags,
35
36
  dim,
36
37
  emptyState,
37
38
  etherscanTxURL,
38
39
  failBadge,
39
40
  green,
40
- isInteractiveAllowed,
41
- load,
42
- maskIf,
43
41
  printKeyValueBox,
44
42
  printSyntaxJSON,
45
43
  printTable,
44
+ promptAutocomplete,
46
45
  promptConfirm,
46
+ promptMultiselect,
47
47
  promptSelect,
48
+ promptText,
48
49
  red,
49
- resolveAPIKey,
50
- resolveAppId,
51
- resolveNetwork,
52
- resolveX402Client,
53
- save,
54
50
  successBadge,
55
51
  timeAgo,
56
52
  weiToEth,
57
- withSpinner
58
- } from "./chunk-T2XSNZE3.js";
53
+ withSpinner,
54
+ yellow
55
+ } from "./chunk-NBDWF4ZQ.js";
56
+ import {
57
+ KEY_MAP,
58
+ configDir,
59
+ get,
60
+ load,
61
+ maskIf,
62
+ save,
63
+ toMap
64
+ } from "./chunk-BAAQ7ELR.js";
59
65
  import {
60
66
  EXIT_CODES,
61
67
  ErrorCode,
62
68
  debug,
69
+ errAccessKeyRequired,
63
70
  errAppRequired,
64
71
  errAuthRequired,
65
72
  errInvalidAPIKey,
@@ -68,6 +75,7 @@ import {
68
75
  errNotFound,
69
76
  errRateLimited,
70
77
  errSetupRequired,
78
+ errWalletKeyRequired,
71
79
  esc,
72
80
  exitWithError,
73
81
  fetchWithTimeout,
@@ -87,6 +95,551 @@ import {
87
95
  // src/index.ts
88
96
  import { Command, Help } from "commander";
89
97
 
98
+ // src/lib/ens.ts
99
+ import { keccak_256 } from "@noble/hashes/sha3.js";
100
+ var UNIVERSAL_RESOLVER = "0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe";
101
+ var RESOLVE_SELECTOR = "9061b923";
102
+ var ADDR_SELECTOR = "3b3b57de";
103
+ function bytesToHex(bytes) {
104
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
105
+ }
106
+ function pad32(hex) {
107
+ return hex.padStart(64, "0");
108
+ }
109
+ function namehash(name) {
110
+ let node = new Uint8Array(32);
111
+ if (!name) return node;
112
+ const labels = name.split(".");
113
+ for (let i = labels.length - 1; i >= 0; i--) {
114
+ const labelHash = keccak_256(new TextEncoder().encode(labels[i]));
115
+ const combined = new Uint8Array(64);
116
+ combined.set(node, 0);
117
+ combined.set(labelHash, 32);
118
+ node = keccak_256(combined);
119
+ }
120
+ return node;
121
+ }
122
+ function dnsEncode(name) {
123
+ const labels = name.split(".");
124
+ const parts = [];
125
+ for (const label of labels) {
126
+ const encoded = new TextEncoder().encode(label);
127
+ parts.push(encoded.length);
128
+ parts.push(...encoded);
129
+ }
130
+ parts.push(0);
131
+ return new Uint8Array(parts);
132
+ }
133
+ function buildResolveCalldata(name) {
134
+ const dnsName = dnsEncode(name);
135
+ const node = namehash(name);
136
+ const innerHex = ADDR_SELECTOR + bytesToHex(node);
137
+ const innerLen = 36;
138
+ const dnsHex = bytesToHex(dnsName);
139
+ const nameLen = dnsName.length;
140
+ const namePad = Math.ceil(nameLen / 32) * 32;
141
+ const innerPad = Math.ceil(innerLen / 32) * 32;
142
+ const nameOffset = 64;
143
+ const dataOffset = nameOffset + 32 + namePad;
144
+ let hex = RESOLVE_SELECTOR;
145
+ hex += pad32(nameOffset.toString(16));
146
+ hex += pad32(dataOffset.toString(16));
147
+ hex += pad32(nameLen.toString(16));
148
+ hex += dnsHex.padEnd(namePad * 2, "0");
149
+ hex += pad32(innerLen.toString(16));
150
+ hex += innerHex.padEnd(innerPad * 2, "0");
151
+ return "0x" + hex;
152
+ }
153
+ function isENSName(value) {
154
+ return value.endsWith(".eth") && value.length > 4 && !value.startsWith("0x");
155
+ }
156
+ async function resolveENS(name, client) {
157
+ if (!client.network.startsWith("eth-")) {
158
+ throw errInvalidArgs(
159
+ `ENS resolution is only supported on Ethereum networks. Current network: ${client.network}`
160
+ );
161
+ }
162
+ const calldata = buildResolveCalldata(name.toLowerCase());
163
+ const result = await client.call("eth_call", [
164
+ { to: UNIVERSAL_RESOLVER, data: calldata },
165
+ "latest"
166
+ ]);
167
+ if (!result || result === "0x" || result.length < 130) {
168
+ throw errInvalidArgs(`ENS name "${name}" could not be resolved.`);
169
+ }
170
+ const raw = result.slice(2);
171
+ const dataOffset = parseInt(raw.slice(0, 64), 16) * 2;
172
+ const dataLen = parseInt(raw.slice(dataOffset, dataOffset + 64), 16);
173
+ const dataHex = raw.slice(dataOffset + 64, dataOffset + 64 + dataLen * 2);
174
+ if (dataHex.length < 64) {
175
+ throw errInvalidArgs(`ENS name "${name}" could not be resolved.`);
176
+ }
177
+ const address = "0x" + dataHex.slice(24, 64);
178
+ if (address === "0x0000000000000000000000000000000000000000") {
179
+ throw errInvalidArgs(`ENS name "${name}" is not registered or has no address set.`);
180
+ }
181
+ return address;
182
+ }
183
+
184
+ // src/lib/validators.ts
185
+ function splitCommaList(input) {
186
+ return input.split(",").map((s) => s.trim()).filter(Boolean);
187
+ }
188
+ var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
189
+ var TX_HASH_RE = /^0x[0-9a-fA-F]{64}$/;
190
+ async function readStdinArg(name) {
191
+ if (process.stdin.isTTY) {
192
+ throw errInvalidArgs(`Missing <${name}>. Provide it as an argument or pipe via stdin.`);
193
+ }
194
+ process.stdin.setEncoding("utf-8");
195
+ let input = "";
196
+ for await (const chunk of process.stdin) {
197
+ input += chunk;
198
+ }
199
+ const data = input.trim().split("\n")[0]?.trim() ?? "";
200
+ if (!data) {
201
+ throw errInvalidArgs(`No <${name}> received on stdin.`);
202
+ }
203
+ return data;
204
+ }
205
+ async function readStdinLines(name) {
206
+ if (process.stdin.isTTY) {
207
+ throw errInvalidArgs(`Missing <${name}>. Provide it as an argument or pipe via stdin.`);
208
+ }
209
+ process.stdin.setEncoding("utf-8");
210
+ let input = "";
211
+ for await (const chunk of process.stdin) {
212
+ input += chunk;
213
+ }
214
+ const lines = input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
215
+ if (lines.length === 0) {
216
+ throw errInvalidArgs(`No <${name}> received on stdin.`);
217
+ }
218
+ return lines;
219
+ }
220
+ function validateAddress(address) {
221
+ if (!ADDRESS_RE.test(address)) {
222
+ throw errInvalidArgs(
223
+ `Invalid address "${address}". Expected 0x-prefixed 40-hex-character address.`
224
+ );
225
+ }
226
+ }
227
+ async function resolveAddress(input, client) {
228
+ if (isENSName(input)) {
229
+ return resolveENS(input, client);
230
+ }
231
+ validateAddress(input);
232
+ return input;
233
+ }
234
+ function validateTxHash(hash) {
235
+ if (!TX_HASH_RE.test(hash)) {
236
+ throw errInvalidArgs(
237
+ `Invalid transaction hash "${hash}". Expected 0x-prefixed 64-hex-character hash.`
238
+ );
239
+ }
240
+ }
241
+
242
+ // src/commands/config.ts
243
+ var RESET_KEY_MAP = { ...KEY_MAP, app: "app" };
244
+ var APP_SEARCH_THRESHOLD = 15;
245
+ async function saveAppWithPrompt(app) {
246
+ const cfg = load();
247
+ const updated = {
248
+ ...cfg,
249
+ api_key: app.apiKey,
250
+ app: { id: app.id, name: app.name, apiKey: app.apiKey, webhookApiKey: app.webhookApiKey }
251
+ };
252
+ if (cfg.api_key) {
253
+ const replace = await promptConfirm({
254
+ message: "You already have an API key configured. Use the app's API key instead?",
255
+ initialValue: true,
256
+ cancelMessage: "Cancelled default app update."
257
+ });
258
+ if (replace === null) {
259
+ return false;
260
+ }
261
+ if (!replace) {
262
+ updated.api_key = cfg.api_key;
263
+ }
264
+ }
265
+ save(updated);
266
+ return true;
267
+ }
268
+ async function selectOrCreateApp(admin) {
269
+ let apps;
270
+ try {
271
+ const result = await withSpinner(
272
+ "Fetching apps\u2026",
273
+ "Apps fetched",
274
+ () => admin.listAllApps()
275
+ );
276
+ apps = result.apps;
277
+ } catch {
278
+ console.log(
279
+ ` ${dim("Could not fetch apps. Skipping app selection.")}`
280
+ );
281
+ return;
282
+ }
283
+ if (apps.length > 0) {
284
+ const CREATE_NEW = "__create_new__";
285
+ const options = [
286
+ ...apps.map((a) => ({
287
+ label: `${a.name} (${a.id})`,
288
+ value: a.id
289
+ })),
290
+ { label: "Create a new app", value: CREATE_NEW }
291
+ ];
292
+ const selected = apps.length > APP_SEARCH_THRESHOLD ? await promptAutocomplete({
293
+ message: "Select default app",
294
+ placeholder: "Type app name or id",
295
+ options,
296
+ cancelMessage: "Cancelled app selection.",
297
+ commitLabel: null
298
+ }) : await promptSelect({
299
+ message: "Select default app",
300
+ options,
301
+ cancelMessage: "Cancelled app selection.",
302
+ commitLabel: null
303
+ });
304
+ if (selected === null) {
305
+ return;
306
+ }
307
+ if (selected !== CREATE_NEW) {
308
+ const app = apps.find((a) => a.id === selected);
309
+ const saved = await saveAppWithPrompt(app);
310
+ if (saved) {
311
+ console.log(`
312
+ ${green("\u2713")} Default app set to ${app.name} (${app.id})`);
313
+ } else {
314
+ console.log(` ${dim("Skipped setting default app.")}`);
315
+ }
316
+ return;
317
+ }
318
+ } else {
319
+ console.log(` ${dim("No apps found. Let's create one.")}`);
320
+ }
321
+ const name = await promptText({
322
+ message: "App name",
323
+ cancelMessage: "Cancelled app creation."
324
+ });
325
+ if (name === null) {
326
+ return;
327
+ }
328
+ if (!name.trim()) {
329
+ console.log(` ${dim("Skipped app creation.")}`);
330
+ return;
331
+ }
332
+ let chainChoices = [];
333
+ try {
334
+ const chains = await withSpinner(
335
+ "Fetching chains\u2026",
336
+ "Chains fetched",
337
+ () => admin.listChains()
338
+ );
339
+ chainChoices = chains.filter((c) => c.availability === "public" && !c.isTestnet).map((c) => ({ label: `${c.name} (${c.id})`, value: c.id }));
340
+ } catch {
341
+ }
342
+ let networks;
343
+ if (chainChoices.length > 0) {
344
+ const selectedNetworks = await promptMultiselect({
345
+ message: "Select networks",
346
+ options: chainChoices,
347
+ required: true,
348
+ cancelMessage: "Cancelled network selection."
349
+ });
350
+ if (selectedNetworks === null) {
351
+ return;
352
+ }
353
+ networks = selectedNetworks;
354
+ } else {
355
+ const raw = await promptText({
356
+ message: "Network IDs (comma-separated)",
357
+ cancelMessage: "Cancelled network selection."
358
+ });
359
+ if (raw === null) {
360
+ return;
361
+ }
362
+ networks = splitCommaList(raw);
363
+ }
364
+ if (networks.length === 0) {
365
+ console.log(` ${dim("No networks selected. Skipped app creation.")}`);
366
+ return;
367
+ }
368
+ try {
369
+ const app = await withSpinner(
370
+ "Creating app\u2026",
371
+ "App created",
372
+ () => admin.createApp({ name: name.trim(), networks })
373
+ );
374
+ console.log(` ${green("\u2713")} Created app ${app.name} (${app.id})`);
375
+ const setDefault = await promptConfirm({
376
+ message: "Set as default app?",
377
+ initialValue: true,
378
+ cancelMessage: "Cancelled default app selection."
379
+ });
380
+ if (setDefault === null) {
381
+ return;
382
+ }
383
+ if (setDefault) {
384
+ const saved = await saveAppWithPrompt(app);
385
+ if (saved) {
386
+ console.log(`
387
+ ${green("\u2713")} Default app set to ${app.name} (${app.id})`);
388
+ } else {
389
+ console.log(` ${dim("Skipped setting default app.")}`);
390
+ }
391
+ }
392
+ } catch (err) {
393
+ exitWithError(err);
394
+ }
395
+ }
396
+ function registerConfig(program2) {
397
+ const cmd = program2.command("config").description("Manage CLI configuration");
398
+ const setCmd = cmd.command("set").description("Set a config value");
399
+ setCmd.command("api-key <key>").description("Set the Alchemy API key for RPC requests").action((key) => {
400
+ try {
401
+ const cfg = load();
402
+ save({ ...cfg, api_key: key });
403
+ printHuman(`${green("\u2713")} Set api-key
404
+ `, { key: "api-key", status: "set" });
405
+ if (!isJSONMode() && cfg.app?.apiKey && cfg.app.apiKey !== key) {
406
+ console.log(
407
+ ` ${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.")}`
408
+ );
409
+ }
410
+ } catch (err) {
411
+ exitWithError(err);
412
+ }
413
+ });
414
+ setCmd.command("access-key <key>").description("Set the Alchemy access key for Admin API operations").action(async (key) => {
415
+ try {
416
+ const cfg = load();
417
+ save({ ...cfg, access_key: key });
418
+ printHuman(`${green("\u2713")} Set access-key
419
+ `, { key: "access-key", status: "set" });
420
+ if (isInteractiveAllowed(program2)) {
421
+ await selectOrCreateApp(new AdminClient(key));
422
+ }
423
+ } catch (err) {
424
+ exitWithError(err);
425
+ }
426
+ });
427
+ setCmd.command("webhook-api-key <key>").description("Set the Alchemy webhook API key for Notify operations").action((key) => {
428
+ try {
429
+ const cfg = load();
430
+ save({ ...cfg, webhook_api_key: key });
431
+ printHuman(`${green("\u2713")} Set webhook-api-key
432
+ `, { key: "webhook-api-key", status: "set" });
433
+ } catch (err) {
434
+ exitWithError(err);
435
+ }
436
+ });
437
+ setCmd.command("app [app-id]").description("Select the default app (interactive) or set by ID").action(async (appId) => {
438
+ try {
439
+ const cfg = load();
440
+ const accessKey = program2.opts().accessKey || process.env.ALCHEMY_ACCESS_KEY || cfg.access_key;
441
+ if (!accessKey) throw errAccessKeyRequired();
442
+ if (appId) {
443
+ const admin = new AdminClient(accessKey);
444
+ const app = await withSpinner(
445
+ "Fetching app\u2026",
446
+ "App fetched",
447
+ () => admin.getApp(appId)
448
+ );
449
+ const updated = {
450
+ ...cfg,
451
+ api_key: app.apiKey,
452
+ app: { id: app.id, name: app.name, apiKey: app.apiKey, webhookApiKey: app.webhookApiKey }
453
+ };
454
+ save(updated);
455
+ printHuman(
456
+ `${green("\u2713")} Default app set to ${app.name} (${app.id})
457
+ `,
458
+ { app: { id: app.id, name: app.name }, status: "set" }
459
+ );
460
+ return;
461
+ }
462
+ if (!isInteractiveAllowed(program2)) {
463
+ exitWithError(
464
+ new Error("Interactive app selection requires an interactive terminal. Use 'config set app <app-id>' or 'alchemy apps list' to find app IDs.")
465
+ );
466
+ }
467
+ await selectOrCreateApp(new AdminClient(accessKey));
468
+ } catch (err) {
469
+ exitWithError(err);
470
+ }
471
+ });
472
+ setCmd.command("network <network>").description("Set the default network (e.g. eth-mainnet, polygon-mainnet)").action((network) => {
473
+ try {
474
+ const cfg = load();
475
+ save({ ...cfg, network });
476
+ printHuman(`${green("\u2713")} Set network to ${network}
477
+ `, { key: "network", value: network, status: "set" });
478
+ } catch (err) {
479
+ exitWithError(err);
480
+ }
481
+ });
482
+ setCmd.command("verbose <enabled>").description("Set default verbose output (true|false)").action((enabled) => {
483
+ try {
484
+ const normalized = enabled.trim().toLowerCase();
485
+ if (normalized !== "true" && normalized !== "false") {
486
+ throw errInvalidArgs("verbose must be 'true' or 'false'");
487
+ }
488
+ const verbose2 = normalized === "true";
489
+ const cfg = load();
490
+ save({ ...cfg, verbose: verbose2 });
491
+ printHuman(
492
+ `${green("\u2713")} Set verbose default to ${verbose2}
493
+ `,
494
+ { key: "verbose", value: String(verbose2), status: "set" }
495
+ );
496
+ } catch (err) {
497
+ exitWithError(err);
498
+ }
499
+ });
500
+ setCmd.command("wallet-key-file <path>").description("Set the path to a wallet private key file for x402").action((path) => {
501
+ try {
502
+ const cfg = load();
503
+ save({ ...cfg, wallet_key_file: path });
504
+ printHuman(`${green("\u2713")} Set wallet-key-file
505
+ `, { key: "wallet-key-file", status: "set" });
506
+ } catch (err) {
507
+ exitWithError(err);
508
+ }
509
+ });
510
+ setCmd.command("x402 <enabled>").description("Enable or disable x402 wallet-based auth by default (true|false)").action((enabled) => {
511
+ try {
512
+ const normalized = enabled.trim().toLowerCase();
513
+ if (normalized !== "true" && normalized !== "false") {
514
+ throw errInvalidArgs("x402 must be 'true' or 'false'");
515
+ }
516
+ const x402 = normalized === "true";
517
+ const cfg = load();
518
+ save({ ...cfg, x402 });
519
+ printHuman(
520
+ `${green("\u2713")} Set x402 default to ${x402}
521
+ `,
522
+ { key: "x402", value: String(x402), status: "set" }
523
+ );
524
+ } catch (err) {
525
+ exitWithError(err);
526
+ }
527
+ });
528
+ cmd.command("get <key>").description("Get a config value (api-key, access-key, app, network, verbose, wallet-key-file, x402)").action((key) => {
529
+ const cfg = load();
530
+ let value = get(cfg, key);
531
+ let isDefault = false;
532
+ if (value === void 0) {
533
+ const defaults = {
534
+ network: "eth-mainnet",
535
+ verbose: "false",
536
+ x402: "false"
537
+ };
538
+ const normalizedKey = KEY_MAP[key] ?? key;
539
+ const defaultValue = defaults[normalizedKey] ?? defaults[key];
540
+ if (defaultValue !== void 0) {
541
+ value = defaultValue;
542
+ isDefault = true;
543
+ }
544
+ }
545
+ if (value === void 0) {
546
+ exitWithError(errNotFound(`config key '${key}'`));
547
+ }
548
+ const isSecret = key === "api-key" || key === "api_key" || key === "access-key" || key === "access_key";
549
+ const display = isSecret ? maskIf(value) : value;
550
+ const humanDisplay = isDefault ? `${display} ${dim("(default)")}` : display;
551
+ printHuman(humanDisplay + "\n", { key, value: display, ...isDefault && { default: true } });
552
+ });
553
+ cmd.command("list").description("List all config values").action(async () => {
554
+ const cfg = load();
555
+ const hasApiKeyMismatch = Boolean(
556
+ cfg.api_key && cfg.app?.apiKey && cfg.api_key !== cfg.app.apiKey
557
+ );
558
+ if (isJSONMode()) {
559
+ printJSON(toMap(cfg));
560
+ return;
561
+ }
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");
565
+ const pairs = [
566
+ ["auth", authStatus],
567
+ [
568
+ "api-key",
569
+ cfg.api_key ? `${hasApiKeyMismatch ? `${yellow("\u25C6")} ` : ""}${maskIf(cfg.api_key)}` : dim("(not set)")
570
+ ],
571
+ ["access-key", cfg.access_key ? maskIf(cfg.access_key) : dim("(not set)")],
572
+ ["webhook-api-key", cfg.webhook_api_key ? maskIf(cfg.webhook_api_key) : dim("(not set)")],
573
+ [
574
+ "app",
575
+ cfg.app ? `${cfg.app.name} ${dim(`(${cfg.app.id})`)}` : dim("(not set) \u2014 set automatically via 'alchemy auth' or 'config set app'")
576
+ ],
577
+ ["network", cfg.network || dim("(not set, defaults to eth-mainnet)")],
578
+ [
579
+ "verbose",
580
+ cfg.verbose !== void 0 ? String(cfg.verbose) : dim("(not set, defaults to false)")
581
+ ],
582
+ ["wallet-key-file", cfg.wallet_key_file || dim("(not set)")],
583
+ ["wallet-address", cfg.wallet_address || dim("(not set)")],
584
+ [
585
+ "x402",
586
+ cfg.x402 !== void 0 ? String(cfg.x402) : dim("(not set, defaults to false)")
587
+ ]
588
+ ];
589
+ printKeyValueBox(pairs);
590
+ if (hasApiKeyMismatch) {
591
+ console.log("");
592
+ console.log(
593
+ ` ${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.")}`
594
+ );
595
+ }
596
+ });
597
+ 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) => {
598
+ try {
599
+ if (key) {
600
+ const mapped = RESET_KEY_MAP[key];
601
+ if (!mapped) {
602
+ throw errInvalidArgs(
603
+ `invalid reset key '${key}' (valid: api-key, access-key, app, network, verbose, wallet-key-file, x402)`
604
+ );
605
+ }
606
+ const cfg = load();
607
+ const updated = { ...cfg };
608
+ delete updated[mapped];
609
+ save(updated);
610
+ printHuman(`${green("\u2713")} Reset ${key}
611
+ `, {
612
+ status: "reset",
613
+ key
614
+ });
615
+ return;
616
+ }
617
+ if (!options.yes && isInteractiveAllowed(program2)) {
618
+ const proceed = await promptConfirm({
619
+ message: "Reset all saved config values?",
620
+ initialValue: false,
621
+ cancelMessage: "Cancelled config reset."
622
+ });
623
+ if (proceed === null) {
624
+ return;
625
+ }
626
+ if (!proceed) {
627
+ console.log(` ${dim("Skipped config reset.")}`);
628
+ return;
629
+ }
630
+ }
631
+ save({});
632
+ printHuman(`${green("\u2713")} Reset all config values
633
+ `, {
634
+ status: "reset",
635
+ scope: "all"
636
+ });
637
+ } catch (err) {
638
+ exitWithError(err);
639
+ }
640
+ });
641
+ }
642
+
90
643
  // src/commands/rpc.ts
91
644
  function registerRPC(program2) {
92
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(
@@ -1420,6 +1973,99 @@ function registerApps(program2) {
1420
1973
  });
1421
1974
  }
1422
1975
 
1976
+ // src/commands/wallet.ts
1977
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
1978
+ import { join, dirname } from "path";
1979
+ import { randomUUID } from "crypto";
1980
+ import { generateWallet, getWalletAddress } from "@alchemy/x402";
1981
+ var WALLET_KEYS_DIR = "wallet-keys";
1982
+ var UUID_SLICE_LEN = 8;
1983
+ var ADDRESS_SLICE_LEN = 12;
1984
+ function walletKeysDirPath() {
1985
+ return join(configDir(), WALLET_KEYS_DIR);
1986
+ }
1987
+ function walletKeyPath(address) {
1988
+ const addr = address.trim().toLowerCase().replace(/^0x/, "").replace(/[^a-z0-9]/g, "").slice(0, ADDRESS_SLICE_LEN);
1989
+ const addressTag = addr || "unknown";
1990
+ const fileName = `wallet-key-${addressTag}-${Date.now()}-${randomUUID().slice(0, UUID_SLICE_LEN)}.txt`;
1991
+ return join(walletKeysDirPath(), fileName);
1992
+ }
1993
+ function persistWalletKey(privateKey, address) {
1994
+ const keyPath = walletKeyPath(address);
1995
+ mkdirSync(dirname(keyPath), { recursive: true, mode: 493 });
1996
+ writeFileSync(keyPath, privateKey + "\n", { mode: 384, flag: "wx" });
1997
+ return keyPath;
1998
+ }
1999
+ function generateAndPersistWallet() {
2000
+ const wallet = generateWallet();
2001
+ const keyPath = persistWalletKey(wallet.privateKey, wallet.address);
2002
+ const cfg = load();
2003
+ save({ ...cfg, wallet_key_file: keyPath, wallet_address: wallet.address });
2004
+ return { address: wallet.address, keyFile: keyPath };
2005
+ }
2006
+ function importAndPersistWallet(path) {
2007
+ let key;
2008
+ try {
2009
+ key = readFileSync(path, "utf-8").trim();
2010
+ } catch {
2011
+ throw errInvalidArgs(`Could not read key file: ${path}`);
2012
+ }
2013
+ const address = getWalletAddress(key);
2014
+ const keyPath = persistWalletKey(key, address);
2015
+ const cfg = load();
2016
+ save({ ...cfg, wallet_key_file: keyPath, wallet_address: address });
2017
+ return { address, keyFile: keyPath };
2018
+ }
2019
+ function registerWallet(program2) {
2020
+ const cmd = program2.command("wallet").description("Manage x402 wallet");
2021
+ cmd.command("generate").description("Generate a new wallet for x402 authentication").action(() => {
2022
+ try {
2023
+ const wallet = generateAndPersistWallet();
2024
+ if (isJSONMode()) {
2025
+ printJSON(wallet);
2026
+ } else {
2027
+ printKeyValueBox([
2028
+ ["Address", green(wallet.address)],
2029
+ ["Key file", wallet.keyFile]
2030
+ ]);
2031
+ console.log(` ${green("\u2713")} Wallet generated and saved to config`);
2032
+ }
2033
+ } catch (err) {
2034
+ exitWithError(err);
2035
+ }
2036
+ });
2037
+ cmd.command("import").argument("<path>", "Path to private key file").description("Import a wallet from a private key file").action((path) => {
2038
+ try {
2039
+ const wallet = importAndPersistWallet(path);
2040
+ if (isJSONMode()) {
2041
+ printJSON(wallet);
2042
+ } else {
2043
+ printKeyValueBox([
2044
+ ["Address", green(wallet.address)],
2045
+ ["Key file", wallet.keyFile]
2046
+ ]);
2047
+ console.log(` ${green("\u2713")} Wallet imported and saved to config`);
2048
+ }
2049
+ } catch (err) {
2050
+ exitWithError(err);
2051
+ }
2052
+ });
2053
+ cmd.command("address").description("Display the address of the configured wallet").action(() => {
2054
+ try {
2055
+ const key = resolveWalletKey(program2);
2056
+ if (!key) throw errWalletKeyRequired();
2057
+ const address = getWalletAddress(key);
2058
+ printHuman(
2059
+ `${address}
2060
+ `,
2061
+ { address }
2062
+ );
2063
+ } catch (err) {
2064
+ exitWithError(err);
2065
+ }
2066
+ });
2067
+ }
2068
+
1423
2069
  // src/commands/setup.ts
1424
2070
  function registerSetup(program2) {
1425
2071
  const cmd = program2.command("setup").description("Setup and onboarding utilities");
@@ -2738,7 +3384,7 @@ function resetUpdateNoticeState() {
2738
3384
  }
2739
3385
  program.name("alchemy").description(
2740
3386
  "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(
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(
2742
3388
  "-n, --network <network>",
2743
3389
  "Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
2744
3390
  ).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 +3527,7 @@ ${styledLine}`;
2881
3527
  ` ${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
3528
  ` ${hDim("Docs:")} ${hBrand("https://www.alchemy.com/docs")}`
2883
3529
  ].join("\n");
2884
- }).hook("preAction", () => {
3530
+ }).hook("preAction", async (thisCommand, actionCommand) => {
2885
3531
  const opts = program.opts();
2886
3532
  if (opts.color === false) setNoColor(true);
2887
3533
  const cfg = load();
@@ -2893,6 +3539,29 @@ ${styledLine}`;
2893
3539
  reveal: Boolean(opts.reveal),
2894
3540
  timeout: opts.timeout
2895
3541
  });
3542
+ const cmdName = actionCommand.name();
3543
+ const skipAppPrompt = [
3544
+ "auth",
3545
+ "config",
3546
+ "setup",
3547
+ "help",
3548
+ "version",
3549
+ "completions",
3550
+ "agent-prompt",
3551
+ "update-check",
3552
+ "wallet"
3553
+ ];
3554
+ 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);
3557
+ const hasApiKey = Boolean(cfg.api_key?.trim() || cfg.app?.apiKey);
3558
+ if (authToken && !hasApiKey) {
3559
+ const { selectAppAfterAuth } = await import("./auth-PYH5WEC3.js");
3560
+ console.log("");
3561
+ console.log(` No app selected. Please select an app to continue.`);
3562
+ await selectAppAfterAuth(authToken);
3563
+ }
3564
+ }
2896
3565
  }).hook("postAction", () => {
2897
3566
  if (!isJSONMode() && !quiet) {
2898
3567
  console.log("");
@@ -2918,7 +3587,7 @@ ${styledLine}`;
2918
3587
  if (isInteractiveAllowed(program)) {
2919
3588
  let latestForInteractiveStartup = null;
2920
3589
  if (shouldRunOnboarding(program, cfg)) {
2921
- const { runOnboarding } = await import("./onboarding-CWCVWSUG.js");
3590
+ const { runOnboarding } = await import("./onboarding-DNEXVUUH.js");
2922
3591
  const latest = getAvailableUpdateOnce();
2923
3592
  const completed = await runOnboarding(program, latest);
2924
3593
  updateShownDuringInteractiveStartup = Boolean(latest);
@@ -2930,7 +3599,7 @@ ${styledLine}`;
2930
3599
  latestForInteractiveStartup = getAvailableUpdateOnce();
2931
3600
  updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
2932
3601
  }
2933
- const { startREPL } = await import("./interactive-G4ON47AR.js");
3602
+ const { startREPL } = await import("./interactive-CGEVIPC2.js");
2934
3603
  program.exitOverride();
2935
3604
  program.configureOutput({
2936
3605
  writeErr: () => {