@alchemy/cli 0.15.0 → 0.16.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/README.md CHANGED
@@ -182,6 +182,16 @@ Per-command override: pass `--signer <session\|local>` to `evm send`, `evm appro
182
182
  | `agent-prompt` | Emits complete agent/automation usage instructions | `alchemy --json --no-interactive agent-prompt` |
183
183
  | `version` | Prints CLI version | `alchemy version` |
184
184
 
185
+ ### Usage API (alpha)
186
+
187
+ The `usage` commands are in **alpha** — the response shape may change, and
188
+ access is limited to teams enrolled in the Usage API alpha.
189
+
190
+ ```sh
191
+ alchemy --json usage summary
192
+ alchemy usage timeseries --start-date 2026-06-01 --granularity day
193
+ ```
194
+
185
195
  ## Flags
186
196
 
187
197
  ### Global flags
@@ -221,6 +231,7 @@ Additional env vars:
221
231
  | Env var | Description |
222
232
  |---|---|
223
233
  | `ALCHEMY_CONFIG` | Custom path to config file |
234
+ | `ALCHEMY_AUTH_TOKEN` | Admin API auth token override |
224
235
  | `ALCHEMY_WALLET_KEY` | EVM wallet private key for x402 auth and local signing |
225
236
  | `ALCHEMY_SOLANA_WALLET_KEY` | Solana wallet private key |
226
237
  | `ALCHEMY_ACTIVE_SIGNER` | Active EVM signer override (`session` or `local`) |
@@ -3,11 +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-WDGPT4OT.js";
6
+ } from "./chunk-LRCCQA3I.js";
7
7
  import "./chunk-CZWKHYTE.js";
8
8
  import "./chunk-EJB4WDTU.js";
9
- import "./chunk-CXR7CCJ7.js";
10
- import "./chunk-OIRERSZT.js";
9
+ import "./chunk-FFQ7XKW7.js";
10
+ import "./chunk-GJL52AFY.js";
11
11
  import "./chunk-PISUI34T.js";
12
12
  import "./chunk-CFIDLPKB.js";
13
13
  export {
@@ -953,6 +953,7 @@ var AdminClient = class _AdminClient {
953
953
  }
954
954
  // Test/debug only: used by mock E2E to route admin requests locally.
955
955
  static ADMIN_API_BASE_URL_ENV = "ALCHEMY_ADMIN_API_BASE_URL";
956
+ static USAGE_DEBUG_TEAM_ID_HEADER = "x-alchemy-cli-debug-team-id";
956
957
  credential;
957
958
  constructor(credential) {
958
959
  if (!credential.token.trim()) {
@@ -993,7 +994,7 @@ var AdminClient = class _AdminClient {
993
994
  throw errInvalidArgs("Refusing to send credentials over non-HTTPS connection.");
994
995
  }
995
996
  }
996
- async request(method, path, body) {
997
+ async request(method, path, body, options) {
997
998
  const url = `${this.baseURL()}${path}`;
998
999
  debug(`${method} ${url}`);
999
1000
  this.assertSafeRequestTarget(url);
@@ -1003,7 +1004,8 @@ var AdminClient = class _AdminClient {
1003
1004
  headers: {
1004
1005
  Authorization: `Bearer ${this.credential.token}`,
1005
1006
  "Content-Type": "application/json",
1006
- Accept: "application/json"
1007
+ Accept: "application/json",
1008
+ ...options?.headers
1007
1009
  },
1008
1010
  ...body !== void 0 && { body: JSON.stringify(body) }
1009
1011
  });
@@ -1070,6 +1072,31 @@ var AdminClient = class _AdminClient {
1070
1072
  } while (cursor);
1071
1073
  return { apps, pages };
1072
1074
  }
1075
+ usageDebugHeaders(options) {
1076
+ if (options?.teamId === void 0) return void 0;
1077
+ const baseUrl = new URL(this.baseURL());
1078
+ if (!isLocalhost(baseUrl.hostname)) {
1079
+ throw errInvalidArgs(
1080
+ "--team-id is a temporary debug option and only works with ALCHEMY_ADMIN_API_BASE_URL pointing at localhost."
1081
+ );
1082
+ }
1083
+ return {
1084
+ [_AdminClient.USAGE_DEBUG_TEAM_ID_HEADER]: String(options.teamId)
1085
+ };
1086
+ }
1087
+ async getUsageSummary(options) {
1088
+ return await this.request("GET", "/v1/usage/summary", void 0, {
1089
+ headers: this.usageDebugHeaders(options)
1090
+ });
1091
+ }
1092
+ async getUsageTimeseries(body, options) {
1093
+ return await this.request(
1094
+ "POST",
1095
+ "/v1/usage/time-series",
1096
+ body,
1097
+ { headers: this.usageDebugHeaders(options) }
1098
+ );
1099
+ }
1073
1100
  async getApp(id) {
1074
1101
  const resp = await this.request("GET", `/v1/apps/${id}`);
1075
1102
  return resp.data;
@@ -1621,6 +1648,9 @@ function resolveAppId(program, cfg) {
1621
1648
  return void 0;
1622
1649
  }
1623
1650
  function resolveAuthToken(cfg) {
1651
+ if (process.env.ALCHEMY_AUTH_TOKEN?.trim()) {
1652
+ return process.env.ALCHEMY_AUTH_TOKEN.trim();
1653
+ }
1624
1654
  const config = cfg ?? load();
1625
1655
  if (!config.auth_token?.trim()) return void 0;
1626
1656
  if (config.auth_token_expires_at) {
@@ -606,6 +606,7 @@ export {
606
606
  green,
607
607
  red,
608
608
  dim,
609
+ cyan,
609
610
  bold,
610
611
  yellow,
611
612
  brand,
@@ -3,7 +3,7 @@ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  gasManagerClientFromFlags,
5
5
  toAdminNetworkId
6
- } from "./chunk-CXR7CCJ7.js";
6
+ } from "./chunk-FFQ7XKW7.js";
7
7
  import {
8
8
  dim,
9
9
  green,
@@ -11,7 +11,7 @@ import {
11
11
  promptConfirm,
12
12
  promptText,
13
13
  withSpinner
14
- } from "./chunk-OIRERSZT.js";
14
+ } from "./chunk-GJL52AFY.js";
15
15
  import {
16
16
  load,
17
17
  save
@@ -11,7 +11,7 @@ import {
11
11
  import {
12
12
  AdminClient,
13
13
  resolveAuthToken
14
- } from "./chunk-CXR7CCJ7.js";
14
+ } from "./chunk-FFQ7XKW7.js";
15
15
  import {
16
16
  bold,
17
17
  brand,
@@ -20,7 +20,7 @@ import {
20
20
  promptAutocomplete,
21
21
  promptText,
22
22
  withSpinner
23
- } from "./chunk-OIRERSZT.js";
23
+ } from "./chunk-GJL52AFY.js";
24
24
  import {
25
25
  configPath,
26
26
  load,
@@ -53,7 +53,7 @@ function semverLT(a, b) {
53
53
  return false;
54
54
  }
55
55
  function currentVersion() {
56
- return true ? "0.15.0" : "0.0.0";
56
+ return true ? "0.16.0" : "0.0.0";
57
57
  }
58
58
  function toUpdateStatus(latestVersion, checkedAt) {
59
59
  const current = currentVersion();
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  resolveAuthToken,
8
8
  resolveWalletSession
9
- } from "./chunk-CXR7CCJ7.js";
9
+ } from "./chunk-FFQ7XKW7.js";
10
10
 
11
11
  // src/lib/onboarding.ts
12
12
  var SETUP_CAPABILITY_ORDER = [
package/dist/index.js CHANGED
@@ -5,10 +5,10 @@ import {
5
5
  errNotLoggedInForPolicyLookup,
6
6
  errSponsorshipNeedsPolicy,
7
7
  selectOrCreatePolicy
8
- } from "./chunk-M7HFRKW6.js";
8
+ } from "./chunk-HPRJEGSP.js";
9
9
  import {
10
10
  registerAuth
11
- } from "./chunk-WDGPT4OT.js";
11
+ } from "./chunk-LRCCQA3I.js";
12
12
  import {
13
13
  openBrowser
14
14
  } from "./chunk-CZWKHYTE.js";
@@ -18,7 +18,7 @@ import {
18
18
  getSetupStatus,
19
19
  isSetupComplete,
20
20
  shouldRunOnboarding
21
- } from "./chunk-RQDWIB62.js";
21
+ } from "./chunk-Q2VRERYE.js";
22
22
  import {
23
23
  isInteractiveAllowed
24
24
  } from "./chunk-EJB4WDTU.js";
@@ -63,16 +63,17 @@ import {
63
63
  updateSession,
64
64
  validateNetwork,
65
65
  walletNetworkToChain
66
- } from "./chunk-CXR7CCJ7.js";
66
+ } from "./chunk-FFQ7XKW7.js";
67
67
  import {
68
68
  getAvailableUpdate,
69
69
  getUpdateStatus,
70
70
  printUpdateNotice
71
- } from "./chunk-UL5ZSWTE.js";
71
+ } from "./chunk-N422J6U3.js";
72
72
  import {
73
73
  bold,
74
74
  brand,
75
75
  brandedHelp,
76
+ cyan,
76
77
  dim,
77
78
  emptyState,
78
79
  etherscanTxURL,
@@ -92,7 +93,7 @@ import {
92
93
  weiToEth,
93
94
  withSpinner,
94
95
  yellow
95
- } from "./chunk-OIRERSZT.js";
96
+ } from "./chunk-GJL52AFY.js";
96
97
  import {
97
98
  KEY_MAP,
98
99
  configDir,
@@ -148,7 +149,7 @@ import {
148
149
  } from "./chunk-CFIDLPKB.js";
149
150
 
150
151
  // src/index.ts
151
- import { Command, Help } from "commander";
152
+ import { Command as Command2, Help } from "commander";
152
153
 
153
154
  // src/lib/ens.ts
154
155
  import { keccak_256 } from "@noble/hashes/sha3.js";
@@ -582,8 +583,8 @@ function registerConfig(program2) {
582
583
  "Interactive policy selection requires an interactive terminal. Pass an ID: `alchemy config set evm-gas-policy-id <id>`."
583
584
  );
584
585
  }
585
- const { selectOrCreatePolicy: selectOrCreatePolicy2 } = await import("./policy-prompt-UNNIF63S.js");
586
- const { resolveNetwork: resolveNetwork2 } = await import("./resolve-IRTGQL4A.js");
586
+ const { selectOrCreatePolicy: selectOrCreatePolicy2 } = await import("./policy-prompt-DRO7Q5VI.js");
587
+ const { resolveNetwork: resolveNetwork2 } = await import("./resolve-B64J4LR6.js");
587
588
  const network = resolveNetwork2(program2);
588
589
  await selectOrCreatePolicy2({
589
590
  flavor: "sponsorship",
@@ -637,8 +638,8 @@ function registerConfig(program2) {
637
638
  "Interactive policy selection requires an interactive terminal. Pass an ID: `alchemy config set solana-fee-policy-id <id>`."
638
639
  );
639
640
  }
640
- const { selectOrCreatePolicy: selectOrCreatePolicy2 } = await import("./policy-prompt-UNNIF63S.js");
641
- const { resolveSolanaNetwork: resolveSolanaNetwork2 } = await import("./resolve-IRTGQL4A.js");
641
+ const { selectOrCreatePolicy: selectOrCreatePolicy2 } = await import("./policy-prompt-DRO7Q5VI.js");
642
+ const { resolveSolanaNetwork: resolveSolanaNetwork2 } = await import("./resolve-B64J4LR6.js");
642
643
  const network = resolveSolanaNetwork2(program2);
643
644
  await selectOrCreatePolicy2({
644
645
  flavor: "solana",
@@ -695,7 +696,7 @@ function registerConfig(program2) {
695
696
  printJSON(toMap(cfg));
696
697
  return;
697
698
  }
698
- const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-IRTGQL4A.js");
699
+ const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-B64J4LR6.js");
699
700
  const validToken = resolveAuthToken2(cfg);
700
701
  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");
701
702
  const pairs = [
@@ -1370,6 +1371,455 @@ function registerApps(program2) {
1370
1371
  });
1371
1372
  }
1372
1373
 
1374
+ // src/commands/usage.ts
1375
+ import { Option } from "commander";
1376
+
1377
+ // src/lib/usage-format.ts
1378
+ var BLOCKS = [" ", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589", "\u2588"];
1379
+ var SPARKS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
1380
+ var MAX_ROWS = 31;
1381
+ function asRecord(value) {
1382
+ if (value == null || typeof value !== "object" || Array.isArray(value)) {
1383
+ return void 0;
1384
+ }
1385
+ return value;
1386
+ }
1387
+ function asStringArray(value) {
1388
+ if (!Array.isArray(value)) return void 0;
1389
+ const strings = value.filter((item) => typeof item === "string");
1390
+ return strings.length > 0 ? strings : void 0;
1391
+ }
1392
+ function parsePoint(value) {
1393
+ const record = asRecord(value);
1394
+ if (!record) return void 0;
1395
+ const dimensions = asRecord(record.dimensions);
1396
+ return {
1397
+ startTime: typeof record.startTime === "string" ? record.startTime : void 0,
1398
+ endTime: typeof record.endTime === "string" ? record.endTime : void 0,
1399
+ isPartial: typeof record.isPartial === "boolean" ? record.isPartial : void 0,
1400
+ dimensions: dimensions ? Object.fromEntries(
1401
+ Object.entries(dimensions).filter((entry) => typeof entry[1] === "string")
1402
+ ) : void 0,
1403
+ amount: typeof record.amount === "string" ? record.amount : void 0,
1404
+ unit: typeof record.unit === "string" ? record.unit : void 0,
1405
+ usd: typeof record.usd === "string" ? record.usd : void 0
1406
+ };
1407
+ }
1408
+ function getUsageTimeSeriesData(result) {
1409
+ const root = asRecord(result);
1410
+ const envelope = asRecord(root?.data);
1411
+ const points = Array.isArray(envelope?.data) ? envelope.data.map(parsePoint).filter((point) => Boolean(point)) : void 0;
1412
+ if (!envelope || !points) return void 0;
1413
+ const query = asRecord(envelope.query);
1414
+ const freshness = asRecord(envelope.freshness);
1415
+ return {
1416
+ query: query ? {
1417
+ startTime: typeof query.startTime === "string" ? query.startTime : void 0,
1418
+ endTime: typeof query.endTime === "string" ? query.endTime : void 0,
1419
+ metrics: asStringArray(query.metrics),
1420
+ groupBy: asStringArray(query.groupBy)
1421
+ } : void 0,
1422
+ freshness: freshness ? {
1423
+ dataThrough: typeof freshness.dataThrough === "string" ? freshness.dataThrough : void 0,
1424
+ containsPartialToday: typeof freshness.containsPartialToday === "boolean" ? freshness.containsPartialToday : void 0
1425
+ } : void 0,
1426
+ data: points
1427
+ };
1428
+ }
1429
+ function finiteNumber(value) {
1430
+ if (!value) return void 0;
1431
+ const parsed = Number(value);
1432
+ return Number.isFinite(parsed) ? parsed : void 0;
1433
+ }
1434
+ function chooseValueField(data) {
1435
+ const metrics = data.query?.metrics ?? [];
1436
+ const hasAmount = data.data.some((point) => finiteNumber(point.amount) !== void 0);
1437
+ const hasUsd = data.data.some((point) => finiteNumber(point.usd) !== void 0);
1438
+ if (metrics.includes("usd") && !metrics.includes("amount") && hasUsd) return "usd";
1439
+ if (hasAmount) return "amount";
1440
+ if (hasUsd) return "usd";
1441
+ return void 0;
1442
+ }
1443
+ function dateLabel(value) {
1444
+ if (!value) return "unknown";
1445
+ return value.slice(0, 10);
1446
+ }
1447
+ function aggregateByDay(points, field) {
1448
+ const buckets = /* @__PURE__ */ new Map();
1449
+ for (const point of points) {
1450
+ const value = finiteNumber(point[field]);
1451
+ if (value === void 0) continue;
1452
+ const date = dateLabel(point.startTime);
1453
+ const existing = buckets.get(date);
1454
+ if (existing) {
1455
+ existing.value += value;
1456
+ existing.isPartial = existing.isPartial || point.isPartial === true;
1457
+ } else {
1458
+ buckets.set(date, {
1459
+ date,
1460
+ value,
1461
+ isPartial: point.isPartial === true
1462
+ });
1463
+ }
1464
+ }
1465
+ return [...buckets.values()].sort((a, b) => a.date.localeCompare(b.date));
1466
+ }
1467
+ function formatNumber(value, field) {
1468
+ return new Intl.NumberFormat("en-US", {
1469
+ maximumFractionDigits: field === "usd" ? 2 : 3
1470
+ }).format(value);
1471
+ }
1472
+ function metricLabel(args) {
1473
+ if (args.field === "usd") return "USD";
1474
+ return args.unit ?? "usage";
1475
+ }
1476
+ function bar(args) {
1477
+ if (args.max <= 0 || args.value <= 0) return dim(" ".repeat(args.width));
1478
+ const filled = args.value / args.max * args.width;
1479
+ const full = Math.floor(filled);
1480
+ const partial = Math.round((filled - full) * 8);
1481
+ const partialBlock = partial > 0 ? BLOCKS[partial] : "";
1482
+ const barText = "\u2588".repeat(full) + partialBlock;
1483
+ const padding = " ".repeat(Math.max(0, args.width - full - (partial > 0 ? 1 : 0)));
1484
+ return cyan(barText) + dim(padding);
1485
+ }
1486
+ function sparkline(values) {
1487
+ const max = Math.max(...values, 1);
1488
+ const line = values.map((value) => SPARKS[Math.round(value / max * 7)] ?? SPARKS[0]).join("");
1489
+ return green(line);
1490
+ }
1491
+ function labelWidth(labels) {
1492
+ return Math.min(Math.max(...labels.map((label) => label.length), 4), 18);
1493
+ }
1494
+ function formatRange(data, buckets) {
1495
+ const start = data.query?.startTime ? dateLabel(data.query.startTime) : buckets[0]?.date;
1496
+ const end = data.query?.endTime ? dateLabel(data.query.endTime) : buckets[buckets.length - 1]?.date;
1497
+ return start && end ? `${start} -> ${end}` : "unknown range";
1498
+ }
1499
+ function dimensionLabel(point) {
1500
+ const entries = Object.entries(point.dimensions ?? {}).filter(([, value]) => value).map(([key, value]) => `${key}=${value}`);
1501
+ return entries.length > 0 ? entries.join(", ") : void 0;
1502
+ }
1503
+ function aggregateDimensions(points, field) {
1504
+ const totals = /* @__PURE__ */ new Map();
1505
+ for (const point of points) {
1506
+ const label = dimensionLabel(point);
1507
+ const value = finiteNumber(point[field]);
1508
+ if (!label || value === void 0) continue;
1509
+ totals.set(label, (totals.get(label) ?? 0) + value);
1510
+ }
1511
+ return [...totals.entries()].map(([label, value]) => ({ label, value })).sort((a, b) => b.value - a.value).slice(0, 6);
1512
+ }
1513
+ function formatUsageTimeSeriesResult(result) {
1514
+ const data = getUsageTimeSeriesData(result);
1515
+ if (!data || data.data.length === 0) return null;
1516
+ const field = chooseValueField(data);
1517
+ if (!field) return null;
1518
+ const buckets = aggregateByDay(data.data, field);
1519
+ if (buckets.length === 0) return null;
1520
+ const unit = metricLabel({
1521
+ field,
1522
+ unit: data.data.find((point) => point.unit)?.unit
1523
+ });
1524
+ const values = buckets.map((bucket) => bucket.value);
1525
+ const total = values.reduce((sum, value) => sum + value, 0);
1526
+ const peak = buckets.reduce(
1527
+ (best, bucket) => bucket.value > best.value ? bucket : best
1528
+ );
1529
+ const max = Math.max(...values, 1);
1530
+ const shownBuckets = buckets.slice(0, MAX_ROWS);
1531
+ const width = labelWidth(shownBuckets.map((bucket) => bucket.date));
1532
+ const lines = [];
1533
+ lines.push("");
1534
+ lines.push(` ${brand(bold("Usage time series"))}`);
1535
+ lines.push(` ${dim(formatRange(data, buckets))} ${dim("metric")} ${field}`);
1536
+ lines.push("");
1537
+ lines.push(
1538
+ ` ${dim("total")} ${green(formatNumber(total, field))} ${dim(unit)} ${dim("peak")} ${yellow(`${peak.date} ${formatNumber(peak.value, field)}`)}`
1539
+ );
1540
+ if (data.freshness?.dataThrough) {
1541
+ const partial = data.freshness.containsPartialToday ? " partial today" : "";
1542
+ lines.push(` ${dim("fresh through")} ${dateLabel(data.freshness.dataThrough)}${dim(partial)}`);
1543
+ }
1544
+ lines.push("");
1545
+ lines.push(` ${sparkline(values)}`);
1546
+ lines.push("");
1547
+ for (const bucket of shownBuckets) {
1548
+ const date = bucket.date.padEnd(width);
1549
+ const partial = bucket.isPartial ? " *" : " ";
1550
+ const value = formatNumber(bucket.value, field).padStart(12);
1551
+ lines.push(
1552
+ ` ${dim(date)}${partial} ${value} ${dim(unit)} ${bar({
1553
+ value: bucket.value,
1554
+ max,
1555
+ width: 28
1556
+ })}`
1557
+ );
1558
+ }
1559
+ if (buckets.length > shownBuckets.length) {
1560
+ lines.push(
1561
+ ` ${dim(`showing ${shownBuckets.length} of ${buckets.length} days; use --json for full data`)}`
1562
+ );
1563
+ }
1564
+ if (buckets.some((bucket) => bucket.isPartial)) {
1565
+ lines.push(` ${dim("* partial bucket")}`);
1566
+ }
1567
+ const dimensions = aggregateDimensions(data.data, field);
1568
+ if (dimensions.length > 0) {
1569
+ const dimensionMax = Math.max(...dimensions.map((item) => item.value), 1);
1570
+ const dimensionWidth = labelWidth(dimensions.map((item) => item.label));
1571
+ lines.push("");
1572
+ lines.push(` ${brand(bold("Top dimensions"))}`);
1573
+ for (const item of dimensions) {
1574
+ const label = item.label.length > dimensionWidth ? `${item.label.slice(0, dimensionWidth - 1)}\u2026` : item.label.padEnd(dimensionWidth);
1575
+ lines.push(
1576
+ ` ${dim(label)} ${formatNumber(item.value, field).padStart(12)} ${dim(unit)} ${bar({
1577
+ value: item.value,
1578
+ max: dimensionMax,
1579
+ width: 20
1580
+ })}`
1581
+ );
1582
+ }
1583
+ }
1584
+ return `${lines.join("\n")}
1585
+ `;
1586
+ }
1587
+
1588
+ // src/commands/usage.ts
1589
+ var DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
1590
+ var DATETIME_WITH_TIMEZONE_RE = /^\d{4}-\d{2}-\d{2}T.+(Z|[+-]\d{2}:\d{2})$/;
1591
+ var TEAM_ID_RE = /^\d+$/;
1592
+ var VALID_METRICS = /* @__PURE__ */ new Set(["amount", "usd"]);
1593
+ var VALID_GROUP_BY = /* @__PURE__ */ new Set([
1594
+ "requestType",
1595
+ "app",
1596
+ "network",
1597
+ "method"
1598
+ ]);
1599
+ var VALID_REQUEST_TYPES = /* @__PURE__ */ new Set([
1600
+ "http",
1601
+ "websocket",
1602
+ "webhook",
1603
+ "grpc"
1604
+ ]);
1605
+ function parseBodyJSON(value) {
1606
+ let parsed;
1607
+ try {
1608
+ parsed = JSON.parse(value);
1609
+ } catch {
1610
+ throw errInvalidArgs("--body must be valid JSON.");
1611
+ }
1612
+ if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
1613
+ throw errInvalidArgs("--body must be a JSON object.");
1614
+ }
1615
+ return parsed;
1616
+ }
1617
+ function splitCommaList2(value) {
1618
+ if (!value) return void 0;
1619
+ const items = value.split(",").map((item) => item.trim()).filter(Boolean);
1620
+ return items.length > 0 ? items : void 0;
1621
+ }
1622
+ function assertAllowedValues(values, allowed, flagName) {
1623
+ for (const value of values ?? []) {
1624
+ if (!allowed.has(value)) {
1625
+ throw errInvalidArgs(
1626
+ `${flagName} contains unsupported value '${value}'.`
1627
+ );
1628
+ }
1629
+ }
1630
+ }
1631
+ function debugTeamIdOption() {
1632
+ return new Option(
1633
+ "--team-id <id>",
1634
+ "Temporary local usage API team override"
1635
+ ).hideHelp();
1636
+ }
1637
+ function parseTeamId(value) {
1638
+ if (value === void 0) return void 0;
1639
+ if (!TEAM_ID_RE.test(value)) {
1640
+ throw errInvalidArgs("--team-id must be a positive integer.");
1641
+ }
1642
+ const teamId = Number(value);
1643
+ if (!Number.isSafeInteger(teamId) || teamId <= 0) {
1644
+ throw errInvalidArgs("--team-id must be a positive integer.");
1645
+ }
1646
+ return teamId;
1647
+ }
1648
+ function buildUsageRequestOptions(opts) {
1649
+ const teamId = parseTeamId(opts.teamId);
1650
+ return teamId === void 0 ? void 0 : { teamId };
1651
+ }
1652
+ function normalizeDateTime(value, flagName) {
1653
+ if (!value) return void 0;
1654
+ if (DATE_ONLY_RE.test(value)) {
1655
+ const date2 = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
1656
+ if (Number.isNaN(date2.getTime()) || date2.toISOString().slice(0, 10) !== value) {
1657
+ throw errInvalidArgs(`${flagName} must be a valid date or datetime.`);
1658
+ }
1659
+ return `${value}T00:00:00.000Z`;
1660
+ }
1661
+ if (!DATETIME_WITH_TIMEZONE_RE.test(value)) {
1662
+ throw errInvalidArgs(
1663
+ `${flagName} must be YYYY-MM-DD or an ISO datetime with a timezone.`
1664
+ );
1665
+ }
1666
+ const date = new Date(value);
1667
+ if (Number.isNaN(date.getTime())) {
1668
+ throw errInvalidArgs(`${flagName} must be a valid date or datetime.`);
1669
+ }
1670
+ return date.toISOString();
1671
+ }
1672
+ function hasBuilderOptions(opts) {
1673
+ return Object.entries(opts).some(
1674
+ ([key, value]) => key !== "body" && key !== "teamId" && value !== void 0
1675
+ );
1676
+ }
1677
+ function buildTimeseriesRequest(opts) {
1678
+ if (opts.body) {
1679
+ if (hasBuilderOptions(opts)) {
1680
+ throw errInvalidArgs("--body cannot be combined with other time series flags.");
1681
+ }
1682
+ return parseBodyJSON(opts.body);
1683
+ }
1684
+ if (opts.startDate && opts.startTime) {
1685
+ throw errInvalidArgs("Pass either --start-date or --start-time, not both.");
1686
+ }
1687
+ if (opts.endDate && opts.endTime) {
1688
+ throw errInvalidArgs("Pass either --end-date or --end-time, not both.");
1689
+ }
1690
+ const startTime = normalizeDateTime(
1691
+ opts.startTime ?? opts.startDate,
1692
+ opts.startTime ? "--start-time" : "--start-date"
1693
+ );
1694
+ const endTime = normalizeDateTime(
1695
+ opts.endTime ?? opts.endDate,
1696
+ opts.endTime ? "--end-time" : "--end-date"
1697
+ ) ?? (/* @__PURE__ */ new Date()).toISOString();
1698
+ if (!startTime) {
1699
+ throw errInvalidArgs(
1700
+ "Usage time series requires --start-date or --start-time, or --body."
1701
+ );
1702
+ }
1703
+ const products = splitCommaList2(opts.products);
1704
+ const metrics = splitCommaList2(opts.metrics);
1705
+ const appIds = splitCommaList2(opts.appIds);
1706
+ const networks = splitCommaList2(opts.networks);
1707
+ const methods = splitCommaList2(opts.methods);
1708
+ const requestTypes = splitCommaList2(opts.requestTypes);
1709
+ const groupBy = splitCommaList2(opts.groupBy);
1710
+ assertAllowedValues(metrics, VALID_METRICS, "--metrics");
1711
+ assertAllowedValues(requestTypes, VALID_REQUEST_TYPES, "--request-types");
1712
+ assertAllowedValues(groupBy, VALID_GROUP_BY, "--group-by");
1713
+ if (groupBy && groupBy.length > 1) {
1714
+ throw errInvalidArgs("--group-by accepts at most one dimension.");
1715
+ }
1716
+ const granularity = opts.granularity ?? "day";
1717
+ if (granularity !== "day" && granularity !== "hour") {
1718
+ throw errInvalidArgs("--granularity must be 'hour' or 'day'.");
1719
+ }
1720
+ const filters = {
1721
+ ...appIds && { appIds },
1722
+ ...networks && { networks },
1723
+ ...methods && { methods },
1724
+ ...requestTypes && { requestTypes }
1725
+ };
1726
+ return {
1727
+ startTime,
1728
+ endTime,
1729
+ granularity,
1730
+ ...products && { products },
1731
+ ...metrics && { metrics },
1732
+ ...Object.keys(filters).length > 0 && { filters },
1733
+ ...groupBy && { groupBy }
1734
+ };
1735
+ }
1736
+ function printUsageResult(result) {
1737
+ if (isJSONMode()) {
1738
+ printJSON(result);
1739
+ return;
1740
+ }
1741
+ printSyntaxJSON(result);
1742
+ }
1743
+ function printUsageTimeseriesResult(result) {
1744
+ if (isJSONMode()) {
1745
+ printJSON(result);
1746
+ return;
1747
+ }
1748
+ const formatted = formatUsageTimeSeriesResult(result);
1749
+ if (formatted) {
1750
+ printHuman(formatted, result);
1751
+ return;
1752
+ }
1753
+ printSyntaxJSON(result);
1754
+ }
1755
+ function registerUsage(program2) {
1756
+ const cmd = program2.command("usage").description("View Alchemy usage data (alpha)").addHelpText(
1757
+ "after",
1758
+ () => (
1759
+ // Skip in JSON help mode so structured output stays valid JSON.
1760
+ isJSONMode() ? "" : [
1761
+ "",
1762
+ "Alpha: the Usage API is in early access. The response shape may",
1763
+ "change, and access is limited to teams enrolled in the alpha."
1764
+ ].join("\n")
1765
+ )
1766
+ );
1767
+ cmd.command("summary").description("Get account usage summary").addOption(debugTeamIdOption()).action(async (opts) => {
1768
+ try {
1769
+ const admin = adminClientFromFlags(program2);
1770
+ const usageOptions = buildUsageRequestOptions(opts);
1771
+ const result = await withSpinner(
1772
+ "Fetching usage summary...",
1773
+ "Usage summary fetched",
1774
+ () => usageOptions ? admin.getUsageSummary(usageOptions) : admin.getUsageSummary()
1775
+ );
1776
+ printUsageResult(result);
1777
+ } catch (err) {
1778
+ exitWithError(err);
1779
+ }
1780
+ });
1781
+ cmd.command("timeseries").alias("time-series").description("Get account usage time series data").option("--start-date <date>", "Start date (YYYY-MM-DD)").option("--end-date <date>", "End date (YYYY-MM-DD)").option("--start-time <datetime>", "Start time as an ISO datetime").option("--end-time <datetime>", "End time as an ISO datetime").option("--products <products>", "Comma-separated BillingProduct enum names").option("--metrics <metrics>", "Comma-separated metrics: amount,usd").option("--app-ids <ids>", "Filter by app IDs (comma-separated)").option(
1782
+ "--networks <networks>",
1783
+ "Filter by networks (comma-separated), e.g. eth-mainnet,base-mainnet"
1784
+ ).option("--methods <methods>", "Filter by methods (comma-separated), e.g. eth_getLogs").option(
1785
+ "--request-types <types>",
1786
+ "Filter by request types (comma-separated): http, websocket, webhook, grpc"
1787
+ ).option(
1788
+ "--group-by <dimension>",
1789
+ "Group by one dimension: requestType, app, network, method"
1790
+ ).option("--granularity <granularity>", "Granularity: hour or day (default day)").option("--body <json>", "Raw time series request JSON body").addOption(debugTeamIdOption()).addHelpText(
1791
+ "after",
1792
+ () => (
1793
+ // Skip in JSON help mode so the structured output stays valid JSON.
1794
+ isJSONMode() ? "" : [
1795
+ "",
1796
+ "Filters (optional, comma-separated, combined with AND):",
1797
+ " --app-ids one or more app IDs",
1798
+ " --networks network slugs, e.g. eth-mainnet,base-mainnet",
1799
+ " --methods RPC/REST methods, e.g. eth_getLogs",
1800
+ " --request-types http, websocket, webhook, grpc",
1801
+ "",
1802
+ "Group by (choose one dimension):",
1803
+ " requestType | app | network | method"
1804
+ ].join("\n")
1805
+ )
1806
+ ).action(async (opts) => {
1807
+ try {
1808
+ const admin = adminClientFromFlags(program2);
1809
+ const body = buildTimeseriesRequest(opts);
1810
+ const usageOptions = buildUsageRequestOptions(opts);
1811
+ const result = await withSpinner(
1812
+ "Fetching usage time series...",
1813
+ "Usage time series fetched",
1814
+ () => usageOptions ? admin.getUsageTimeseries(body, usageOptions) : admin.getUsageTimeseries(body)
1815
+ );
1816
+ printUsageTimeseriesResult(result);
1817
+ } catch (err) {
1818
+ exitWithError(err);
1819
+ }
1820
+ });
1821
+ }
1822
+
1373
1823
  // src/commands/wallet.ts
1374
1824
  import { readFileSync, writeFileSync, mkdirSync, rmSync } from "fs";
1375
1825
  import { join, dirname } from "path";
@@ -3371,7 +3821,7 @@ function registerWebhooks(program2) {
3371
3821
  });
3372
3822
  }
3373
3823
 
3374
- // src/lib/solana-rpc.ts
3824
+ // ../../packages/sdk/dist/solana/index.js
3375
3825
  var SOLANA_DAS_METHODS = /* @__PURE__ */ new Set([
3376
3826
  "getAsset",
3377
3827
  "getAssetsByOwner",
@@ -3385,7 +3835,37 @@ var SOLANA_DAS_METHODS = /* @__PURE__ */ new Set([
3385
3835
  "getAssetSignatures",
3386
3836
  "getNftEditions"
3387
3837
  ]);
3838
+ var SOLANA_DAS_PAYWALL_PATTERN = /\b(free tier|payg|paid tier|paid plan|upgrade|plan does not|current plan|not available on your plan)\b/i;
3839
+ function isSolanaDasMethod(method) {
3840
+ return SOLANA_DAS_METHODS.has(method);
3841
+ }
3842
+ function solanaRpcFamilyForMethod(method) {
3843
+ return isSolanaDasMethod(method) ? "das" : "rpc";
3844
+ }
3845
+ function classifySolanaDasPaywallError(err) {
3846
+ const details = errorText(err);
3847
+ if (!SOLANA_DAS_PAYWALL_PATTERN.test(details)) {
3848
+ return void 0;
3849
+ }
3850
+ return {
3851
+ message: "Solana DAS request requires a paid Alchemy plan.",
3852
+ hint: "Upgrade your app to PAYG or use a plan with Solana DAS access at https://dashboard.alchemy.com/.",
3853
+ ...details && { details }
3854
+ };
3855
+ }
3388
3856
  function errorText(err) {
3857
+ if (err instanceof Error) {
3858
+ const details = stringValue(err.details);
3859
+ return [err.message, details].filter(Boolean).join(" ");
3860
+ }
3861
+ return String(err);
3862
+ }
3863
+ function stringValue(value) {
3864
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3865
+ }
3866
+
3867
+ // src/lib/solana-rpc.ts
3868
+ function errorText2(err) {
3389
3869
  if (err instanceof CLIError) {
3390
3870
  return [err.message, err.details].filter(Boolean).join(" ");
3391
3871
  }
@@ -3394,22 +3874,19 @@ function errorText(err) {
3394
3874
  }
3395
3875
  return String(err);
3396
3876
  }
3397
- function solanaRpcFamilyForMethod(method) {
3398
- return SOLANA_DAS_METHODS.has(method) ? "das" : "rpc";
3399
- }
3400
3877
  function normalizeSolanaDasPaywallError(err) {
3401
3878
  if (err instanceof CLIError && err.code === ErrorCode.PAYMENT_REQUIRED) {
3402
3879
  return err;
3403
3880
  }
3404
- const detail = errorText(err);
3405
- if (!/\b(free tier|payg|paid tier|paid plan|upgrade|plan does not|current plan|not available on your plan)\b/i.test(detail)) {
3881
+ const paywall = classifySolanaDasPaywallError(err);
3882
+ if (!paywall) {
3406
3883
  return null;
3407
3884
  }
3408
3885
  return new CLIError(
3409
3886
  ErrorCode.PAYMENT_REQUIRED,
3410
- "Solana DAS request requires a paid Alchemy plan.",
3411
- "Upgrade your app to PAYG or use a plan with Solana DAS access at https://dashboard.alchemy.com/.",
3412
- detail || void 0
3887
+ paywall.message,
3888
+ paywall.hint,
3889
+ paywall.details
3413
3890
  );
3414
3891
  }
3415
3892
  async function callSolana(client, family, method, params) {
@@ -3420,7 +3897,7 @@ async function callSolana(client, family, method, params) {
3420
3897
  debug(`solana ${family} ${method} ok ${Date.now() - startedAt}ms`);
3421
3898
  return result;
3422
3899
  } catch (err) {
3423
- debug(`solana ${family} ${method} failed ${Date.now() - startedAt}ms`, errorText(err));
3900
+ debug(`solana ${family} ${method} failed ${Date.now() - startedAt}ms`, errorText2(err));
3424
3901
  throw err;
3425
3902
  }
3426
3903
  }
@@ -5832,6 +6309,49 @@ var PortfolioApiClient = class {
5832
6309
  return dataPost(portfolioBaseUrl(this.options), this.apiKey, path, body, this.options);
5833
6310
  }
5834
6311
  };
6312
+ var PORTFOLIO_BALANCE_FIELD_NAMES = /* @__PURE__ */ new Set([
6313
+ "balance",
6314
+ "tokenbalance",
6315
+ "rawbalance",
6316
+ "rawtokenbalance",
6317
+ "amount"
6318
+ ]);
6319
+ function limitPortfolioPayload(value, limit) {
6320
+ if (limit === void 0)
6321
+ return value;
6322
+ if (Array.isArray(value)) {
6323
+ return value.slice(0, limit).map((item) => limitPortfolioPayload(item, limit));
6324
+ }
6325
+ if (isRecord(value)) {
6326
+ return Object.fromEntries(Object.entries(value).map(([key, nested]) => [
6327
+ key,
6328
+ limitPortfolioPayload(nested, limit)
6329
+ ]));
6330
+ }
6331
+ return value;
6332
+ }
6333
+ function normalizePortfolioOutput(value) {
6334
+ if (Array.isArray(value)) {
6335
+ return value.map(normalizePortfolioOutput);
6336
+ }
6337
+ if (!isRecord(value)) {
6338
+ return value;
6339
+ }
6340
+ const decimals = decimalsForRecord(value);
6341
+ const output = {};
6342
+ for (const [key, nested] of Object.entries(value)) {
6343
+ output[key] = normalizePortfolioOutput(nested);
6344
+ if (typeof nested !== "string" || !shouldNormalizeBalanceField(key) || !isHexQuantity(nested)) {
6345
+ continue;
6346
+ }
6347
+ const raw = BigInt(nested);
6348
+ output[`${key}Decimal`] = raw.toString();
6349
+ if (decimals !== void 0) {
6350
+ output[`${key}Formatted`] = formatRawAmount(raw, decimals);
6351
+ }
6352
+ }
6353
+ return output;
6354
+ }
5835
6355
  async function dataFetch(url, method, body, options) {
5836
6356
  const fetchFn = options.fetchFn ?? fetch;
5837
6357
  const resp = await fetchFn(url, {
@@ -5850,6 +6370,48 @@ async function dataFetch(url, method, body, options) {
5850
6370
  }
5851
6371
  return resp;
5852
6372
  }
6373
+ function isRecord(value) {
6374
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6375
+ }
6376
+ function isHexQuantity(value) {
6377
+ return /^0x[0-9a-f]+$/i.test(value);
6378
+ }
6379
+ function parseDecimals2(value) {
6380
+ const decimals = typeof value === "number" ? value : typeof value === "string" && /^\d+$/.test(value) ? Number(value) : void 0;
6381
+ if (decimals === void 0 || !Number.isInteger(decimals) || decimals < 0 || decimals > 255) {
6382
+ return void 0;
6383
+ }
6384
+ return decimals;
6385
+ }
6386
+ function decimalsForRecord(value) {
6387
+ const direct = parseDecimals2(value.decimals);
6388
+ if (direct !== void 0)
6389
+ return direct;
6390
+ for (const key of ["tokenMetadata", "metadata", "token"]) {
6391
+ const nested = value[key];
6392
+ if (isRecord(nested)) {
6393
+ const decimals = parseDecimals2(nested.decimals);
6394
+ if (decimals !== void 0)
6395
+ return decimals;
6396
+ }
6397
+ }
6398
+ return void 0;
6399
+ }
6400
+ function formatRawAmount(raw, decimals) {
6401
+ if (decimals === 0)
6402
+ return raw.toString();
6403
+ const divisor = 10n ** BigInt(decimals);
6404
+ const whole = raw / divisor;
6405
+ const fraction = raw % divisor;
6406
+ if (fraction === 0n)
6407
+ return whole.toString();
6408
+ const fractionText = fraction.toString().padStart(decimals, "0").replace(/0+$/, "");
6409
+ return `${whole}.${fractionText}`;
6410
+ }
6411
+ function shouldNormalizeBalanceField(key) {
6412
+ const normalized = key.toLowerCase();
6413
+ return PORTFOLIO_BALANCE_FIELD_NAMES.has(normalized) || normalized.endsWith("balance");
6414
+ }
5853
6415
 
5854
6416
  // ../../packages/sdk/dist/surfaces/index.js
5855
6417
  var RPC_API_SURFACE = [
@@ -6378,7 +6940,7 @@ var AGENT_PROMPT_SCOPES = [
6378
6940
  "xchain"
6379
6941
  ];
6380
6942
  var SCOPE_COMMAND_PATHS = {
6381
- app: ["app", "auth", "config"],
6943
+ app: ["app", "usage", "auth", "config"],
6382
6944
  data: ["evm data", "evm logs", "evm block", "evm tx", "evm receipt"],
6383
6945
  evm: ["evm"],
6384
6946
  solana: ["solana"],
@@ -6395,7 +6957,7 @@ var SCOPE_COMMAND_PATHS = {
6395
6957
  xchain: ["xchain"]
6396
6958
  };
6397
6959
  var SCOPE_EXAMPLE_MATCHERS = {
6398
- app: [" app ", " auth ", " config "],
6960
+ app: [" app ", " usage ", " auth ", " config "],
6399
6961
  data: [" evm data ", " evm logs ", " evm block ", " evm tx ", " evm receipt "],
6400
6962
  evm: [" evm "],
6401
6963
  solana: [" solana "],
@@ -6441,7 +7003,7 @@ function buildCommandSchema(cmd) {
6441
7003
  required: a.required
6442
7004
  }));
6443
7005
  }
6444
- const opts = cmd.options;
7006
+ const opts = visibleOptions(cmd);
6445
7007
  if (opts.length > 0) {
6446
7008
  schema.options = opts.map((o) => ({
6447
7009
  flags: o.flags,
@@ -6456,6 +7018,11 @@ function buildCommandSchema(cmd) {
6456
7018
  }
6457
7019
  return schema;
6458
7020
  }
7021
+ function visibleOptions(cmd) {
7022
+ return cmd.options.filter((option) => {
7023
+ return !option.hidden;
7024
+ });
7025
+ }
6459
7026
  function commandMatchesPath(cmd, path) {
6460
7027
  if (path[0] !== cmd.name) return null;
6461
7028
  if (path.length === 1) return cmd;
@@ -6507,6 +7074,7 @@ function applyScope(payload, scope) {
6507
7074
  );
6508
7075
  }
6509
7076
  function buildAgentPrompt(program2) {
7077
+ const hasUsageCommand = program2.commands.some((cmd) => cmd.name() === "usage");
6510
7078
  const errors = {};
6511
7079
  for (const [code, exitCode] of Object.entries(EXIT_CODES)) {
6512
7080
  errors[code] = {
@@ -6516,6 +7084,24 @@ function buildAgentPrompt(program2) {
6516
7084
  };
6517
7085
  }
6518
7086
  const commands = program2.commands.filter((cmd) => cmd.name() !== "agent-prompt").map(buildCommandSchema);
7087
+ const examples = [
7088
+ "alchemy --json --no-interactive config status",
7089
+ "alchemy --json --no-interactive update-check",
7090
+ "alchemy --json --no-interactive evm data balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --api-key $ALCHEMY_API_KEY -n eth-mainnet",
7091
+ "alchemy --json --no-interactive app list",
7092
+ ...hasUsageCommand ? [
7093
+ "alchemy --json --no-interactive usage summary",
7094
+ "alchemy --json --no-interactive usage timeseries --start-date 2026-06-01 --end-date 2026-06-08"
7095
+ ] : [],
7096
+ "alchemy --json --no-interactive evm rpc eth_blockNumber --api-key $ALCHEMY_API_KEY -n eth-mainnet",
7097
+ "alchemy --json --no-interactive evm network list",
7098
+ "alchemy --json --no-interactive evm send 0xRecipient 0.001 --dry-run -n eth-sepolia",
7099
+ `alchemy --json --no-interactive evm contract read 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 "balanceOf(address)(uint256)" --args '["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"]' -n eth-mainnet`,
7100
+ "alchemy --json --no-interactive evm swap quote --from 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE --to 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --amount 1.0 --from-address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -n eth-mainnet",
7101
+ "alchemy --json --no-interactive evm logs --from-block latest --limit 25 -n eth-mainnet",
7102
+ "alchemy --json --no-interactive evm block latest --summary -n eth-mainnet",
7103
+ "alchemy --json --no-interactive evm status 0xCallId -n eth-mainnet"
7104
+ ];
6519
7105
  return {
6520
7106
  executionPolicy: [
6521
7107
  "Always pass --json --no-interactive",
@@ -6585,9 +7171,15 @@ function buildAgentPrompt(program2) {
6585
7171
  },
6586
7172
  {
6587
7173
  method: "Alchemy login",
7174
+ envVar: "ALCHEMY_AUTH_TOKEN",
6588
7175
  setup: "alchemy auth login",
6589
- commandFamilies: ["app", "evm network", "gas-manager"],
6590
- notes: "Admin surfaces use the browser login session stored by `alchemy auth login`."
7176
+ commandFamilies: [
7177
+ "app",
7178
+ ...hasUsageCommand ? ["usage"] : [],
7179
+ "evm network",
7180
+ "gas-manager"
7181
+ ],
7182
+ notes: "Admin surfaces use the browser login session stored by `alchemy auth login`, or `ALCHEMY_AUTH_TOKEN` when set."
6591
7183
  },
6592
7184
  {
6593
7185
  method: "Webhook API key",
@@ -6641,20 +7233,7 @@ function buildAgentPrompt(program2) {
6641
7233
  ],
6642
7234
  commands,
6643
7235
  errors,
6644
- examples: [
6645
- "alchemy --json --no-interactive config status",
6646
- "alchemy --json --no-interactive update-check",
6647
- "alchemy --json --no-interactive evm data balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --api-key $ALCHEMY_API_KEY -n eth-mainnet",
6648
- "alchemy --json --no-interactive app list",
6649
- "alchemy --json --no-interactive evm rpc eth_blockNumber --api-key $ALCHEMY_API_KEY -n eth-mainnet",
6650
- "alchemy --json --no-interactive evm network list",
6651
- "alchemy --json --no-interactive evm send 0xRecipient 0.001 --dry-run -n eth-sepolia",
6652
- `alchemy --json --no-interactive evm contract read 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 "balanceOf(address)(uint256)" --args '["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"]' -n eth-mainnet`,
6653
- "alchemy --json --no-interactive evm swap quote --from 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE --to 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --amount 1.0 --from-address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -n eth-mainnet",
6654
- "alchemy --json --no-interactive evm logs --from-block latest --limit 25 -n eth-mainnet",
6655
- "alchemy --json --no-interactive evm block latest --summary -n eth-mainnet",
6656
- "alchemy --json --no-interactive evm status 0xCallId -n eth-mainnet"
6657
- ],
7236
+ examples,
6658
7237
  docs: "https://www.alchemy.com/docs"
6659
7238
  };
6660
7239
  }
@@ -6877,7 +7456,7 @@ function registrySymbolSuggestions(network) {
6877
7456
  }
6878
7457
 
6879
7458
  // src/lib/preflight-errors.ts
6880
- function errorText2(err) {
7459
+ function errorText3(err) {
6881
7460
  if (err instanceof CLIError) {
6882
7461
  return [err.message, err.details].filter(Boolean).join(" ");
6883
7462
  }
@@ -6912,7 +7491,7 @@ function normalizePreflightRevertError(err) {
6912
7491
  if (err instanceof CLIError && err.code === ErrorCode.PREFLIGHT_REVERT) {
6913
7492
  return err;
6914
7493
  }
6915
- const detail = errorText2(err);
7494
+ const detail = errorText3(err);
6916
7495
  const reason = extractRevertReason(detail);
6917
7496
  if (!reason) return null;
6918
7497
  return errPreflightRevert(reason, detail || void 0);
@@ -8250,13 +8829,6 @@ Examples:
8250
8829
  }
8251
8830
 
8252
8831
  // src/commands/portfolio.ts
8253
- var BALANCE_FIELD_NAMES = /* @__PURE__ */ new Set([
8254
- "balance",
8255
- "tokenbalance",
8256
- "rawbalance",
8257
- "rawtokenbalance",
8258
- "amount"
8259
- ]);
8260
8832
  async function runDataCall(program2, title, path, body) {
8261
8833
  const x402 = resolveX402Client(program2);
8262
8834
  return withSpinner(
@@ -8265,85 +8837,10 @@ async function runDataCall(program2, title, path, body) {
8265
8837
  () => x402 ? x402.callRest(`data/v1${path}`, { method: "POST", body }) : callApiData(resolveAPIKey(program2), path, { method: "POST", body })
8266
8838
  );
8267
8839
  }
8268
- function limitPayload(value, limit) {
8269
- if (limit === void 0) return value;
8270
- if (Array.isArray(value)) {
8271
- return value.slice(0, limit).map((item) => limitPayload(item, limit));
8272
- }
8273
- if (isRecord(value)) {
8274
- return Object.fromEntries(
8275
- Object.entries(value).map(([key, nested]) => [
8276
- key,
8277
- limitPayload(nested, limit)
8278
- ])
8279
- );
8280
- }
8281
- return value;
8282
- }
8283
- function isRecord(value) {
8284
- return typeof value === "object" && value !== null && !Array.isArray(value);
8285
- }
8286
- function isHexQuantity(value) {
8287
- return /^0x[0-9a-f]+$/i.test(value);
8288
- }
8289
- function parseDecimals2(value) {
8290
- const decimals = typeof value === "number" ? value : typeof value === "string" && /^\d+$/.test(value) ? Number(value) : void 0;
8291
- if (decimals === void 0 || !Number.isInteger(decimals) || decimals < 0 || decimals > 255) {
8292
- return void 0;
8293
- }
8294
- return decimals;
8295
- }
8296
- function decimalsForRecord(value) {
8297
- const direct = parseDecimals2(value.decimals);
8298
- if (direct !== void 0) return direct;
8299
- for (const key of ["tokenMetadata", "metadata", "token"]) {
8300
- const nested = value[key];
8301
- if (isRecord(nested)) {
8302
- const decimals = parseDecimals2(nested.decimals);
8303
- if (decimals !== void 0) return decimals;
8304
- }
8305
- }
8306
- return void 0;
8307
- }
8308
- function formatRawAmount(raw, decimals) {
8309
- if (decimals === 0) return raw.toString();
8310
- const divisor = 10n ** BigInt(decimals);
8311
- const whole = raw / divisor;
8312
- const fraction = raw % divisor;
8313
- if (fraction === 0n) return whole.toString();
8314
- const fractionText = fraction.toString().padStart(decimals, "0").replace(/0+$/, "");
8315
- return `${whole}.${fractionText}`;
8316
- }
8317
- function shouldNormalizeBalanceField(key) {
8318
- const normalized = key.toLowerCase();
8319
- return BALANCE_FIELD_NAMES.has(normalized) || normalized.endsWith("balance");
8320
- }
8321
- function normalizePortfolioOutput(value) {
8322
- if (Array.isArray(value)) {
8323
- return value.map(normalizePortfolioOutput);
8324
- }
8325
- if (!isRecord(value)) {
8326
- return value;
8327
- }
8328
- const decimals = decimalsForRecord(value);
8329
- const output = {};
8330
- for (const [key, nested] of Object.entries(value)) {
8331
- output[key] = normalizePortfolioOutput(nested);
8332
- if (typeof nested !== "string" || !shouldNormalizeBalanceField(key) || !isHexQuantity(nested)) {
8333
- continue;
8334
- }
8335
- const raw = BigInt(nested);
8336
- output[`${key}Decimal`] = raw.toString();
8337
- if (decimals !== void 0) {
8338
- output[`${key}Formatted`] = formatRawAmount(raw, decimals);
8339
- }
8340
- }
8341
- return output;
8342
- }
8343
8840
  async function runPortfolioCommand(program2, title, path, opts) {
8344
8841
  const limit = parseOptionalInt(opts.limit, "--limit");
8345
8842
  const result = await runDataCall(program2, title, path, JSON.parse(opts.body));
8346
- const output = normalizePortfolioOutput(limitPayload(result, limit));
8843
+ const output = normalizePortfolioOutput(limitPortfolioPayload(result, limit));
8347
8844
  if (isJSONMode()) printJSON(output);
8348
8845
  else printSyntaxJSON(output);
8349
8846
  }
@@ -9001,7 +9498,7 @@ function buildWalletQuoteClient(program2, address3) {
9001
9498
  }
9002
9499
 
9003
9500
  // src/lib/quote-errors.ts
9004
- function errorText3(err) {
9501
+ function errorText4(err) {
9005
9502
  if (err instanceof CLIError) {
9006
9503
  return [err.message, err.details].filter(Boolean).join(" ");
9007
9504
  }
@@ -9059,7 +9556,7 @@ function normalizeQuoteError(err, flow) {
9059
9556
  if (err instanceof CLIError && err.code !== ErrorCode.RPC_ERROR && err.code !== ErrorCode.INTERNAL_ERROR) {
9060
9557
  return err;
9061
9558
  }
9062
- const detail = errorText3(err);
9559
+ const detail = errorText4(err);
9063
9560
  const cause = classifyQuoteFailure(detail);
9064
9561
  return new CLIError(
9065
9562
  ErrorCode.QUOTE_FAILED,
@@ -10648,7 +11145,7 @@ var ROOT_COMMAND_PILLARS = [
10648
11145
  },
10649
11146
  {
10650
11147
  label: "Account & Platform",
10651
- commands: ["auth", "wallet", "app", "webhook", "config"]
11148
+ commands: ["auth", "wallet", "app", "usage", "webhook", "config"]
10652
11149
  },
10653
11150
  {
10654
11151
  label: "Utilities",
@@ -10682,7 +11179,12 @@ function rootOptionGroupLabel(flags) {
10682
11179
  }
10683
11180
  return "General";
10684
11181
  }
10685
- var program = new Command();
11182
+ function visibleOptions2(cmd) {
11183
+ return cmd.options.filter((option) => {
11184
+ return !option.hidden;
11185
+ });
11186
+ }
11187
+ var program = new Command2();
10686
11188
  var argvTokens = process.argv.slice(2);
10687
11189
  var isHelpInvocation = argvTokens.some(
10688
11190
  (token) => token === "help" || token === "--help" || token === "-h"
@@ -10698,6 +11200,16 @@ var findCommandByPath = (root, path) => {
10698
11200
  }
10699
11201
  return current;
10700
11202
  };
11203
+ var commandPathIncludes = (command, names) => {
11204
+ let current = command;
11205
+ while (current) {
11206
+ if (names.has(current.name())) {
11207
+ return true;
11208
+ }
11209
+ current = current.parent ?? null;
11210
+ }
11211
+ return false;
11212
+ };
10701
11213
  var cachedAvailableUpdate;
10702
11214
  var updateShownDuringInteractiveStartup = false;
10703
11215
  function getAvailableUpdateOnce() {
@@ -10718,7 +11230,7 @@ async function flushProcessOutput() {
10718
11230
  }
10719
11231
  program.name("alchemy").description(
10720
11232
  "The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
10721
- ).version("0.15.0", "-v, --version", "display CLI version").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option(
11233
+ ).version("0.16.0", "-v, --version", "display CLI version").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option(
10722
11234
  "-n, --network <network>",
10723
11235
  "Target network for networked commands"
10724
11236
  ).option("--x402", "Use x402 wallet-based gateway auth").option(
@@ -10756,7 +11268,7 @@ program.name("alchemy").description(
10756
11268
  required: a.required
10757
11269
  }));
10758
11270
  }
10759
- const opts = cmd.options;
11271
+ const opts = visibleOptions2(cmd);
10760
11272
  if (opts.length > 0) {
10761
11273
  schema.options = opts.map((o) => ({
10762
11274
  flags: o.flags,
@@ -10891,11 +11403,11 @@ ${styledLine}`;
10891
11403
  reveal: Boolean(opts.reveal),
10892
11404
  timeout: opts.timeout
10893
11405
  });
10894
- const cmdName = actionCommand.name();
10895
- const skipAppPrompt = [
11406
+ const skipAppPrompt = /* @__PURE__ */ new Set([
10896
11407
  "auth",
10897
11408
  "config",
10898
11409
  "help",
11410
+ "usage",
10899
11411
  "version",
10900
11412
  "completions",
10901
11413
  "agent-prompt",
@@ -10903,13 +11415,13 @@ ${styledLine}`;
10903
11415
  "install",
10904
11416
  "update-check",
10905
11417
  "wallet"
10906
- ];
10907
- if (!skipAppPrompt.includes(cmdName) && isInteractiveAllowed(program) && !opts.apiKey && !process.env.ALCHEMY_API_KEY) {
10908
- const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-IRTGQL4A.js");
11418
+ ]);
11419
+ if (!commandPathIncludes(actionCommand, skipAppPrompt) && isInteractiveAllowed(program) && !opts.apiKey && !process.env.ALCHEMY_API_KEY) {
11420
+ const { resolveAuthToken: resolveAuthToken2 } = await import("./resolve-B64J4LR6.js");
10909
11421
  const authToken = resolveAuthToken2(cfg);
10910
11422
  const hasApiKey = Boolean(cfg.api_key?.trim() || cfg.app?.apiKey);
10911
11423
  if (authToken && !hasApiKey) {
10912
- const { selectAppAfterAuth } = await import("./auth-ZBEPAFEP.js");
11424
+ const { selectAppAfterAuth } = await import("./auth-JHVDOG26.js");
10913
11425
  console.log("");
10914
11426
  console.log(` No app selected. Please select an app to continue.`);
10915
11427
  await selectAppAfterAuth(authToken);
@@ -10944,7 +11456,7 @@ ${styledLine}`;
10944
11456
  if (isInteractiveAllowed(program)) {
10945
11457
  let latestForInteractiveStartup = null;
10946
11458
  if (shouldRunOnboarding(program, cfg)) {
10947
- const { runOnboarding } = await import("./onboarding-L2FTZRYN.js");
11459
+ const { runOnboarding } = await import("./onboarding-47TK4HNM.js");
10948
11460
  const latest = getAvailableUpdateOnce();
10949
11461
  const completed = await runOnboarding(program, latest);
10950
11462
  updateShownDuringInteractiveStartup = Boolean(latest);
@@ -10958,7 +11470,7 @@ ${styledLine}`;
10958
11470
  latestForInteractiveStartup
10959
11471
  );
10960
11472
  }
10961
- const { startREPL } = await import("./interactive-3ACOLHPG.js");
11473
+ const { startREPL } = await import("./interactive-PQGRT27R.js");
10962
11474
  program.exitOverride();
10963
11475
  program.configureOutput({
10964
11476
  writeErr: () => {
@@ -10975,6 +11487,7 @@ registerXchain(program);
10975
11487
  registerWallets(program);
10976
11488
  registerGasManager(program);
10977
11489
  registerApps(program);
11490
+ registerUsage(program);
10978
11491
  registerWebhooks(program);
10979
11492
  registerAuth(program);
10980
11493
  registerConfig(program);
@@ -2,14 +2,14 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  getSetupMethod
5
- } from "./chunk-RQDWIB62.js";
5
+ } from "./chunk-Q2VRERYE.js";
6
6
  import "./chunk-EJB4WDTU.js";
7
7
  import {
8
8
  getRPCNetworkIds
9
- } from "./chunk-CXR7CCJ7.js";
9
+ } from "./chunk-FFQ7XKW7.js";
10
10
  import {
11
11
  getUpdateNoticeLines
12
- } from "./chunk-UL5ZSWTE.js";
12
+ } from "./chunk-N422J6U3.js";
13
13
  import {
14
14
  bold,
15
15
  brand,
@@ -17,7 +17,7 @@ import {
17
17
  dim,
18
18
  green,
19
19
  setBrandedHelpSuppressed
20
- } from "./chunk-OIRERSZT.js";
20
+ } from "./chunk-GJL52AFY.js";
21
21
  import {
22
22
  configDir,
23
23
  load
@@ -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-UL5ZSWTE.js";
5
+ } from "./chunk-N422J6U3.js";
6
6
  import {
7
7
  bold,
8
8
  brand,
@@ -10,7 +10,7 @@ import {
10
10
  dim,
11
11
  green,
12
12
  promptText
13
- } from "./chunk-OIRERSZT.js";
13
+ } from "./chunk-GJL52AFY.js";
14
14
  import {
15
15
  load,
16
16
  save
@@ -51,7 +51,7 @@ async function runOnboarding(_program, latestUpdate = null) {
51
51
  auth_token_expires_at: result.expiresAt
52
52
  });
53
53
  console.log(` ${green("\u2713")} Logged in successfully`);
54
- const { selectAppAfterAuth } = await import("./auth-ZBEPAFEP.js");
54
+ const { selectAppAfterAuth } = await import("./auth-JHVDOG26.js");
55
55
  await selectAppAfterAuth(result.token);
56
56
  return true;
57
57
  } catch (err) {
@@ -5,9 +5,9 @@ import {
5
5
  errNotLoggedInForPolicyLookup,
6
6
  errSponsorshipNeedsPolicy,
7
7
  selectOrCreatePolicy
8
- } from "./chunk-M7HFRKW6.js";
9
- import "./chunk-CXR7CCJ7.js";
10
- import "./chunk-OIRERSZT.js";
8
+ } from "./chunk-HPRJEGSP.js";
9
+ import "./chunk-FFQ7XKW7.js";
10
+ import "./chunk-GJL52AFY.js";
11
11
  import "./chunk-PISUI34T.js";
12
12
  import "./chunk-CFIDLPKB.js";
13
13
  export {
@@ -26,7 +26,7 @@ import {
26
26
  resolveWalletSession,
27
27
  resolveX402,
28
28
  resolveX402Client
29
- } from "./chunk-CXR7CCJ7.js";
29
+ } from "./chunk-FFQ7XKW7.js";
30
30
  import "./chunk-PISUI34T.js";
31
31
  import "./chunk-CFIDLPKB.js";
32
32
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alchemy/cli",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Alchemy CLI — interact with blockchain data",
5
5
  "type": "module",
6
6
  "bin": {