@alchemy/cli 0.2.0 → 0.2.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/README.md CHANGED
@@ -57,6 +57,9 @@ alchemy
57
57
 
58
58
  # Agent/script-friendly command
59
59
  alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --json --no-interactive
60
+
61
+ # Agent checks whether a newer CLI version is available
62
+ alchemy update-check --json --no-interactive
60
63
  ```
61
64
 
62
65
  #### Agent bootstrap
@@ -70,6 +73,8 @@ alchemy --json agent-prompt
70
73
 
71
74
  This returns a single JSON document with execution policy, preflight instructions, auth matrix, the full command tree with all arguments and options, error codes with recovery actions, and example invocations. No external docs required.
72
75
 
76
+ Agents can also call `alchemy --json --no-interactive update-check` to retrieve the current CLI version, latest known version, and install command for upgrades.
77
+
73
78
  ## Command Reference
74
79
 
75
80
  Run commands as `alchemy <command>`.
@@ -151,6 +156,7 @@ Use `alchemy help` or `alchemy help <command>` for generated command help.
151
156
  | `apps origin-allowlist <id>` | Updates app origin allowlist | `alchemy apps origin-allowlist <app-id> --origins https://example.com` |
152
157
  | `apps ip-allowlist <id>` | Updates app IP allowlist | `alchemy apps ip-allowlist <app-id> --ips 1.2.3.4,5.6.7.8` |
153
158
  | `setup status` | Shows setup status + next commands | `alchemy setup status` |
159
+ | `update-check` | Checks whether a newer CLI version is available | `alchemy update-check --json --no-interactive` |
154
160
  | `config set ...` | Sets config values | `alchemy config set api-key <key>` |
155
161
  | `config get <key>` | Gets one config value | `alchemy config get network` |
156
162
  | `config list` | Lists all config values | `alchemy config list` |
@@ -180,7 +186,7 @@ These apply to all commands.
180
186
  |---|---|---|
181
187
  | `--json` | — | Force JSON output |
182
188
  | `-q, --quiet` | — | Suppress non-essential output |
183
- | `-v, --verbose` | — | Enable verbose output |
189
+ | `--verbose` | — | Enable verbose output |
184
190
  | `--no-color` | `NO_COLOR` | Disable color output |
185
191
  | `--reveal` | — | Show secrets in plain text (TTY only) |
186
192
 
@@ -317,6 +323,7 @@ Use `--no-interactive` to disable REPL/prompts in automation.
317
323
 
318
324
  - TTY: formatted human output
319
325
  - Non-TTY: JSON output (script-friendly)
326
+ - `-v`, `--version`: prints the CLI version
320
327
  - `--json`: forces JSON output in any context
321
328
  - `--verbose` or `alchemy config set verbose true`: includes richer payload output on supported commands
322
329
 
@@ -326,8 +326,16 @@ function errNetwork(detail) {
326
326
  "Check your internet connection and try again."
327
327
  );
328
328
  }
329
+ var RPC_ERROR_HINTS = {
330
+ [-32700]: "Parse error. The request JSON is malformed.",
331
+ [-32600]: "Invalid request. Check the JSON-RPC request format.",
332
+ [-32601]: "Method not supported. Check the method name and ensure your plan supports it.",
333
+ [-32602]: "Invalid parameters. Check argument types and format.",
334
+ [-32603]: "Internal JSON-RPC error."
335
+ };
329
336
  function errRPC(code, message) {
330
- return new CLIError(ErrorCode.RPC_ERROR, `RPC error ${code}: ${message}`);
337
+ const hint = RPC_ERROR_HINTS[code];
338
+ return new CLIError(ErrorCode.RPC_ERROR, `RPC error ${code}: ${message}`, hint);
331
339
  }
332
340
  function errInvalidArgs(detail) {
333
341
  return new CLIError(ErrorCode.INVALID_ARGS, detail);
@@ -349,6 +357,14 @@ function errInvalidAccessKey() {
349
357
  "Get an access key: https://www.alchemy.com/docs/reference/admin-api/overview"
350
358
  );
351
359
  }
360
+ function errAccessDenied(detail) {
361
+ const message = detail ? `Access denied: ${detail}` : "Access denied. Your access key may not have permission for this operation.";
362
+ return new CLIError(
363
+ ErrorCode.INVALID_ACCESS_KEY,
364
+ message,
365
+ "Check your account tier and feature access at https://dashboard.alchemy.com/"
366
+ );
367
+ }
352
368
  function errAppRequired() {
353
369
  return new CLIError(
354
370
  ErrorCode.APP_REQUIRED,
@@ -1047,6 +1063,109 @@ function isInteractiveAllowed(program) {
1047
1063
  return true;
1048
1064
  }
1049
1065
 
1066
+ // src/lib/update-check.ts
1067
+ import { execFileSync } from "child_process";
1068
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1069
+ import { dirname as dirname2 } from "path";
1070
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
1071
+ var UPDATE_INSTALL_COMMAND = "npm i -g @alchemy/cli";
1072
+ function cachePath() {
1073
+ return configPath().replace(/config\.json$/, ".update-check");
1074
+ }
1075
+ function readCache() {
1076
+ try {
1077
+ return JSON.parse(readFileSync2(cachePath(), "utf-8"));
1078
+ } catch {
1079
+ return null;
1080
+ }
1081
+ }
1082
+ function writeCache(cache) {
1083
+ try {
1084
+ const p = cachePath();
1085
+ mkdirSync2(dirname2(p), { recursive: true });
1086
+ writeFileSync2(p, JSON.stringify(cache), { mode: 384 });
1087
+ } catch {
1088
+ }
1089
+ }
1090
+ function fetchLatestVersion() {
1091
+ try {
1092
+ const result = execFileSync("npm", ["view", "@alchemy/cli", "version"], {
1093
+ encoding: "utf-8",
1094
+ timeout: 5e3,
1095
+ stdio: ["pipe", "pipe", "pipe"]
1096
+ });
1097
+ return result.trim() || null;
1098
+ } catch {
1099
+ return null;
1100
+ }
1101
+ }
1102
+ function semverLT(a, b) {
1103
+ const pa = a.split(".").map(Number);
1104
+ const pb = b.split(".").map(Number);
1105
+ for (let i = 0; i < 3; i++) {
1106
+ if ((pa[i] ?? 0) < (pb[i] ?? 0)) return true;
1107
+ if ((pa[i] ?? 0) > (pb[i] ?? 0)) return false;
1108
+ }
1109
+ return false;
1110
+ }
1111
+ function currentVersion() {
1112
+ return true ? "0.2.2" : "0.0.0";
1113
+ }
1114
+ function toUpdateStatus(latestVersion, checkedAt) {
1115
+ const current = currentVersion();
1116
+ return {
1117
+ currentVersion: current,
1118
+ latestVersion,
1119
+ updateAvailable: latestVersion ? semverLT(current, latestVersion) : false,
1120
+ installCommand: UPDATE_INSTALL_COMMAND,
1121
+ checkedAt
1122
+ };
1123
+ }
1124
+ function getUpdateStatus() {
1125
+ const cache = readCache();
1126
+ if (cache && Date.now() - cache.checkedAt < CACHE_TTL_MS) {
1127
+ return toUpdateStatus(cache.latest, cache.checkedAt);
1128
+ }
1129
+ const latest = fetchLatestVersion();
1130
+ if (latest) {
1131
+ const checkedAt = Date.now();
1132
+ writeCache({ latest, checkedAt });
1133
+ return toUpdateStatus(latest, checkedAt);
1134
+ }
1135
+ if (cache) {
1136
+ return toUpdateStatus(cache.latest, cache.checkedAt);
1137
+ }
1138
+ return toUpdateStatus(null, null);
1139
+ }
1140
+ function getAvailableUpdate() {
1141
+ const current = currentVersion();
1142
+ const cache = readCache();
1143
+ if (cache && Date.now() - cache.checkedAt < CACHE_TTL_MS) {
1144
+ return semverLT(current, cache.latest) ? cache.latest : null;
1145
+ }
1146
+ const latest = fetchLatestVersion();
1147
+ if (latest) {
1148
+ writeCache({ latest, checkedAt: Date.now() });
1149
+ return semverLT(current, latest) ? latest : null;
1150
+ }
1151
+ return null;
1152
+ }
1153
+ function getUpdateNoticeLines(latest) {
1154
+ const yellow2 = esc("33");
1155
+ const bold2 = esc("1");
1156
+ const dim2 = esc("2");
1157
+ return [
1158
+ ` ${yellow2("Update available")} ${dim2(currentVersion())} \u2192 ${bold2(latest)}`,
1159
+ ` Run ${bold2(UPDATE_INSTALL_COMMAND)} to update`
1160
+ ];
1161
+ }
1162
+ function printUpdateNotice(latest) {
1163
+ process.stderr.write(`
1164
+ ${getUpdateNoticeLines(latest).join("\n")}
1165
+
1166
+ `);
1167
+ }
1168
+
1050
1169
  export {
1051
1170
  noColor,
1052
1171
  identity,
@@ -1075,6 +1194,7 @@ export {
1075
1194
  errNotFound,
1076
1195
  errRateLimited,
1077
1196
  errInvalidAccessKey,
1197
+ errAccessDenied,
1078
1198
  errAppRequired,
1079
1199
  errWalletKeyRequired,
1080
1200
  errAdminAPI,
@@ -1110,5 +1230,9 @@ export {
1110
1230
  timeAgo,
1111
1231
  etherscanTxURL,
1112
1232
  brandedHelp,
1113
- isInteractiveAllowed
1233
+ isInteractiveAllowed,
1234
+ getUpdateStatus,
1235
+ getAvailableUpdate,
1236
+ getUpdateNoticeLines,
1237
+ printUpdateNotice
1114
1238
  };
@@ -7,6 +7,7 @@ import {
7
7
  configDir,
8
8
  debug,
9
9
  dim,
10
+ errAccessDenied,
10
11
  errAccessKeyRequired,
11
12
  errAdminAPI,
12
13
  errAppRequired,
@@ -40,7 +41,7 @@ import {
40
41
  toMap,
41
42
  withSpinner,
42
43
  yellow
43
- } from "./chunk-VRBWUQHA.js";
44
+ } from "./chunk-2WI4JODY.js";
44
45
 
45
46
  // src/lib/client-utils.ts
46
47
  function isLocalhost(hostname) {
@@ -82,7 +83,24 @@ async function fetchWithTimeout(url, init) {
82
83
  if (err instanceof DOMException && err.name === "TimeoutError") {
83
84
  throw errNetwork(`Request timed out after ${timeout}ms`);
84
85
  }
85
- throw errNetwork(err.message);
86
+ const message = err.message ?? String(err);
87
+ const causeMessage = err.cause?.message ?? "";
88
+ const causeCode = err.cause?.code ?? "";
89
+ const fullErrorText = `${message} ${causeMessage} ${causeCode}`;
90
+ if (/ENOTFOUND|EAI_AGAIN|getaddrinfo/i.test(fullErrorText)) {
91
+ try {
92
+ const hostname = new URL(url).hostname;
93
+ const networkSlug = hostname.replace(/\.g\.alchemy\.com$/, "");
94
+ if (networkSlug !== hostname) {
95
+ throw errInvalidArgs(
96
+ `Unknown network '${networkSlug}'. Run 'alchemy network list' to see available networks.`
97
+ );
98
+ }
99
+ } catch (innerErr) {
100
+ if (innerErr instanceof CLIError) throw innerErr;
101
+ }
102
+ }
103
+ throw errNetwork(message);
86
104
  }
87
105
  }
88
106
 
@@ -145,7 +163,18 @@ var AdminClient = class _AdminClient {
145
163
  },
146
164
  ...body !== void 0 && { body: JSON.stringify(body) }
147
165
  });
148
- if (resp.status === 401 || resp.status === 403) throw errInvalidAccessKey();
166
+ if (resp.status === 401) throw errInvalidAccessKey();
167
+ if (resp.status === 403) {
168
+ const detail = await resp.text().catch(() => "");
169
+ let reason;
170
+ try {
171
+ const parsed = JSON.parse(detail);
172
+ reason = parsed?.message || parsed?.error?.message || parsed?.error || void 0;
173
+ } catch {
174
+ reason = detail || void 0;
175
+ }
176
+ throw errAccessDenied(typeof reason === "string" ? reason : void 0);
177
+ }
149
178
  if (resp.status === 404) {
150
179
  const text = await resp.text().catch(() => "");
151
180
  throw errNotFound(text || path);
@@ -686,10 +715,14 @@ var Client = class _Client {
686
715
  try {
687
716
  parsed = new URL(`https://${hostname}`);
688
717
  } catch {
689
- throw errInvalidArgs(`Invalid network: ${network}`);
718
+ throw errInvalidArgs(
719
+ `Unknown network '${network}'. Run 'alchemy network list' to see available networks.`
720
+ );
690
721
  }
691
722
  if (!parsed.hostname.endsWith(".g.alchemy.com")) {
692
- throw errInvalidArgs(`Invalid network: ${network} \u2014 hostname must end with .g.alchemy.com`);
723
+ throw errInvalidArgs(
724
+ `Unknown network '${network}'. Run 'alchemy network list' to see available networks.`
725
+ );
693
726
  }
694
727
  }
695
728
  rpcBaseURLOverride() {
@@ -718,6 +751,16 @@ var Client = class _Client {
718
751
  if (networkNotEnabled) return networkNotEnabled;
719
752
  return errInvalidAPIKey(detail || void 0);
720
753
  }
754
+ tryParseRPCError(text) {
755
+ try {
756
+ const parsed = JSON.parse(text);
757
+ if (parsed?.error?.code !== void 0 && parsed?.error?.message !== void 0) {
758
+ return errRPC(parsed.error.code, parsed.error.message);
759
+ }
760
+ } catch {
761
+ }
762
+ return null;
763
+ }
721
764
  async doFetch(url, init) {
722
765
  return fetchWithTimeout(url, init);
723
766
  }
@@ -743,6 +786,8 @@ var Client = class _Client {
743
786
  }
744
787
  if (!resp.ok) {
745
788
  const text = await resp.text().catch(() => "");
789
+ const rpcError = this.tryParseRPCError(text);
790
+ if (rpcError) throw rpcError;
746
791
  throw errNetwork(`HTTP ${resp.status}: ${text}`);
747
792
  }
748
793
  const rpcResp = await resp.json();
@@ -766,6 +811,8 @@ var Client = class _Client {
766
811
  }
767
812
  if (!resp.ok) {
768
813
  const text = await resp.text().catch(() => "");
814
+ const rpcError = this.tryParseRPCError(text);
815
+ if (rpcError) throw rpcError;
769
816
  throw errNetwork(`HTTP ${resp.status}: ${text}`);
770
817
  }
771
818
  return resp.json();
@@ -2,7 +2,7 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  isInteractiveAllowed
5
- } from "./chunk-VRBWUQHA.js";
5
+ } from "./chunk-2WI4JODY.js";
6
6
 
7
7
  // src/lib/networks.ts
8
8
  var TESTNET_TOKEN_RE = /(testnet|sepolia|holesky|hoodi|devnet|minato|amoy|fuji|saigon|cardona|aeneid|curtis|chiado|cassiopeia|blaze|ropsten|signet|mocha|fam|bepolia)$/i;
package/dist/index.js CHANGED
@@ -14,13 +14,13 @@ import {
14
14
  splitCommaList,
15
15
  validateAddress,
16
16
  validateTxHash
17
- } from "./chunk-VD5WQHLQ.js";
17
+ } from "./chunk-6KOJKH6N.js";
18
18
  import {
19
19
  getRPCNetworks,
20
20
  getSetupStatus,
21
21
  isSetupComplete,
22
22
  shouldRunOnboarding
23
- } from "./chunk-MAKSV2EA.js";
23
+ } from "./chunk-NA7MQB7X.js";
24
24
  import {
25
25
  EXIT_CODES,
26
26
  ErrorCode,
@@ -42,6 +42,8 @@ import {
42
42
  exitWithError,
43
43
  failBadge,
44
44
  formatCommanderError,
45
+ getAvailableUpdate,
46
+ getUpdateStatus,
45
47
  green,
46
48
  identity,
47
49
  isInteractiveAllowed,
@@ -54,6 +56,7 @@ import {
54
56
  printKeyValueBox,
55
57
  printSyntaxJSON,
56
58
  printTable,
59
+ printUpdateNotice,
57
60
  promptSelect,
58
61
  quiet,
59
62
  setFlags,
@@ -62,7 +65,7 @@ import {
62
65
  verbose,
63
66
  weiToEth,
64
67
  withSpinner
65
- } from "./chunk-VRBWUQHA.js";
68
+ } from "./chunk-2WI4JODY.js";
66
69
 
67
70
  // src/index.ts
68
71
  import { Command, Help } from "commander";
@@ -274,6 +277,9 @@ Examples:
274
277
  "block must be a number, hex, or 'latest'"
275
278
  );
276
279
  }
280
+ if (num < 0) {
281
+ throw errInvalidArgs("Block number must be non-negative.");
282
+ }
277
283
  blockParam = `0x${num.toString(16)}`;
278
284
  }
279
285
  const client = clientFromFlags(program2);
@@ -1678,7 +1684,8 @@ function buildAgentPrompt(program2) {
1678
1684
  "Always pass --json --no-interactive",
1679
1685
  "Parse stdout as JSON on exit code 0",
1680
1686
  "Parse stderr as JSON on nonzero exit code",
1681
- "Never run bare 'alchemy' without --json --no-interactive"
1687
+ "Never run bare 'alchemy' without --json --no-interactive",
1688
+ "Run alchemy --json --no-interactive update-check when you need to detect available CLI upgrades"
1682
1689
  ],
1683
1690
  preflight: {
1684
1691
  command: "alchemy --json setup status",
@@ -1742,6 +1749,7 @@ function buildAgentPrompt(program2) {
1742
1749
  errors,
1743
1750
  examples: [
1744
1751
  "alchemy --json --no-interactive setup status",
1752
+ "alchemy --json --no-interactive update-check",
1745
1753
  "alchemy --json --no-interactive balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --api-key $ALCHEMY_API_KEY",
1746
1754
  "alchemy --json --no-interactive apps list --access-key $ALCHEMY_ACCESS_KEY",
1747
1755
  "alchemy --json --no-interactive rpc eth_blockNumber --api-key $ALCHEMY_API_KEY",
@@ -1800,6 +1808,27 @@ function registerAgentPrompt(program2) {
1800
1808
  });
1801
1809
  }
1802
1810
 
1811
+ // src/commands/update-check.ts
1812
+ function formatCheckedAt(checkedAt) {
1813
+ return checkedAt ? new Date(checkedAt).toISOString() : dim("(unknown)");
1814
+ }
1815
+ function registerUpdateCheck(program2) {
1816
+ program2.command("update-check").description("Check whether a newer CLI version is available").action(() => {
1817
+ const status = getUpdateStatus();
1818
+ if (isJSONMode()) {
1819
+ printJSON(status);
1820
+ return;
1821
+ }
1822
+ printKeyValueBox([
1823
+ ["Current version", status.currentVersion],
1824
+ ["Latest version", status.latestVersion ?? dim("(unknown)")],
1825
+ ["Update available", status.updateAvailable ? "yes" : "no"],
1826
+ ["Checked at", formatCheckedAt(status.checkedAt)],
1827
+ ["Install", status.installCommand]
1828
+ ]);
1829
+ });
1830
+ }
1831
+
1803
1832
  // src/index.ts
1804
1833
  var hBrand = noColor ? identity : (s) => `\x1B[38;2;54;63;249m${s}\x1B[39m`;
1805
1834
  var hBold = esc("1");
@@ -1837,7 +1866,7 @@ var ROOT_COMMAND_PILLARS = [
1837
1866
  },
1838
1867
  {
1839
1868
  label: "Admin",
1840
- commands: ["apps", "config", "setup", "agent-prompt", "version", "help"]
1869
+ commands: ["apps", "config", "setup", "agent-prompt", "update-check", "version", "help"]
1841
1870
  }
1842
1871
  ];
1843
1872
  function formatCommandSignature(sub) {
@@ -1872,12 +1901,29 @@ var findCommandByPath = (root, path) => {
1872
1901
  }
1873
1902
  return current;
1874
1903
  };
1904
+ var cachedAvailableUpdate;
1905
+ var updateShownDuringInteractiveStartup = false;
1906
+ function getAvailableUpdateOnce() {
1907
+ if (cachedAvailableUpdate === void 0) {
1908
+ cachedAvailableUpdate = getAvailableUpdate();
1909
+ }
1910
+ return cachedAvailableUpdate;
1911
+ }
1912
+ function resetUpdateNoticeState() {
1913
+ cachedAvailableUpdate = void 0;
1914
+ updateShownDuringInteractiveStartup = false;
1915
+ }
1875
1916
  program.name("alchemy").description(
1876
1917
  "The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
1877
- ).version("0.2.0").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option("--access-key <key>", "Alchemy access key (env: ALCHEMY_ACCESS_KEY)").option(
1918
+ ).version("0.2.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(
1878
1919
  "-n, --network <network>",
1879
1920
  "Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
1880
- ).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").option("-q, --quiet", "Suppress non-essential output").option("-v, --verbose", "Enable verbose output").option("--no-color", "Disable color output").option("--reveal", "Show secrets in plain text (TTY only)").option("--timeout <ms>", "Request timeout in milliseconds", parseInt).option("--debug", "Enable debug diagnostics").option("--no-interactive", "Disable REPL and prompt-driven interactions").addHelpCommand(false).configureOutput({
1921
+ ).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").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 (TTY only)").option("--timeout <ms>", "Request timeout in milliseconds", parseInt).option("--debug", "Enable debug diagnostics").option("--no-interactive", "Disable REPL and prompt-driven interactions").addHelpCommand(false).allowExcessArguments(true).exitOverride((err) => {
1922
+ if (err.code === "commander.help" || err.code === "commander.helpDisplayed" || err.code === "commander.version") {
1923
+ process.exit(0);
1924
+ }
1925
+ process.exit(EXIT_CODES.INVALID_ARGS);
1926
+ }).configureOutput({
1881
1927
  outputError(str, write) {
1882
1928
  write(formatCommanderError(str));
1883
1929
  }
@@ -2018,27 +2064,47 @@ ${styledLine}`;
2018
2064
  }).hook("postAction", () => {
2019
2065
  if (!isJSONMode() && !quiet) {
2020
2066
  console.log("");
2067
+ if (!updateShownDuringInteractiveStartup) {
2068
+ const latest = getAvailableUpdateOnce();
2069
+ if (latest) printUpdateNotice(latest);
2070
+ }
2071
+ }
2072
+ resetUpdateNoticeState();
2073
+ }).action(async (_opts, cmd) => {
2074
+ const excessArgs = cmd.args;
2075
+ if (excessArgs.length > 0) {
2076
+ exitWithError(
2077
+ errInvalidArgs(
2078
+ `Unknown command '${excessArgs[0]}'. Run 'alchemy help' for available commands.`
2079
+ )
2080
+ );
2021
2081
  }
2022
- }).action(async () => {
2023
2082
  const cfg = load();
2024
2083
  if (!isSetupComplete(cfg) && !isInteractiveAllowed(program)) {
2025
2084
  throw errSetupRequired(getSetupStatus(cfg));
2026
2085
  }
2027
2086
  if (isInteractiveAllowed(program)) {
2087
+ let latestForInteractiveStartup = null;
2028
2088
  if (shouldRunOnboarding(program, cfg)) {
2029
- const { runOnboarding } = await import("./onboarding-IKIXUTY7.js");
2030
- const completed = await runOnboarding(program);
2089
+ const { runOnboarding } = await import("./onboarding-SJ2BRK5I.js");
2090
+ const latest = getAvailableUpdateOnce();
2091
+ const completed = await runOnboarding(program, latest);
2092
+ updateShownDuringInteractiveStartup = Boolean(latest);
2093
+ latestForInteractiveStartup = null;
2031
2094
  if (!completed) {
2032
2095
  return;
2033
2096
  }
2097
+ } else {
2098
+ latestForInteractiveStartup = getAvailableUpdateOnce();
2099
+ updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
2034
2100
  }
2035
- const { startREPL } = await import("./interactive-CP6E23OD.js");
2101
+ const { startREPL } = await import("./interactive-EAUEVR63.js");
2036
2102
  program.exitOverride();
2037
2103
  program.configureOutput({
2038
2104
  writeErr: () => {
2039
2105
  }
2040
2106
  });
2041
- await startREPL(program);
2107
+ await startREPL(program, latestForInteractiveStartup);
2042
2108
  return;
2043
2109
  }
2044
2110
  program.help();
@@ -2066,6 +2132,7 @@ registerSetup(program);
2066
2132
  registerConfig(program);
2067
2133
  registerSolana(program);
2068
2134
  registerAgentPrompt(program);
2135
+ registerUpdateCheck(program);
2069
2136
  registerVersion(program);
2070
2137
  program.command("help [command...]").description("display help for command").action((commandPath) => {
2071
2138
  if (!commandPath || commandPath.length === 0) {
@@ -3,7 +3,7 @@ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  getRPCNetworkIds,
5
5
  getSetupMethod
6
- } from "./chunk-MAKSV2EA.js";
6
+ } from "./chunk-NA7MQB7X.js";
7
7
  import {
8
8
  bgRgb,
9
9
  bold,
@@ -11,6 +11,7 @@ import {
11
11
  brandedHelp,
12
12
  configDir,
13
13
  dim,
14
+ getUpdateNoticeLines,
14
15
  green,
15
16
  isJSONMode,
16
17
  load,
@@ -18,7 +19,7 @@ import {
18
19
  rgb,
19
20
  setBrandedHelpSuppressed,
20
21
  setReplMode
21
- } from "./chunk-VRBWUQHA.js";
22
+ } from "./chunk-2WI4JODY.js";
22
23
 
23
24
  // src/commands/interactive.ts
24
25
  import * as readline from "readline";
@@ -88,6 +89,7 @@ var COMMAND_NAMES = [
88
89
  "config get",
89
90
  "config list",
90
91
  "help",
92
+ "update-check",
91
93
  "network",
92
94
  "network list",
93
95
  "nfts",
@@ -136,7 +138,7 @@ function saveReplHistory(lines) {
136
138
  mkdirSync(dirname(historyFilePath), { recursive: true, mode: 493 });
137
139
  writeFileSync(historyFilePath, normalized.join("\n") + "\n", { mode: 384 });
138
140
  }
139
- async function startREPL(program) {
141
+ async function startREPL(program, latestUpdate = null) {
140
142
  if (!stdin.isTTY) return;
141
143
  setReplMode(true);
142
144
  setBrandedHelpSuppressed(true);
@@ -217,6 +219,12 @@ async function startREPL(program) {
217
219
  console.log(` ${green("\u2713")} ${dim(`Configured auth: ${formatSetupMethodLabel()}`)}`);
218
220
  console.log(` ${dim("Run commands directly (no 'alchemy' prefix).")}`);
219
221
  console.log("");
222
+ if (latestUpdate) {
223
+ for (const line of getUpdateNoticeLines(latestUpdate)) {
224
+ console.log(line);
225
+ }
226
+ console.log("");
227
+ }
220
228
  console.log(` ${brand("\u25C6")} ${bold("Quick commands")}`);
221
229
  console.log(` ${dim("- rpc eth_chainId")}`);
222
230
  console.log(` ${dim("- config list")}`);
@@ -5,12 +5,13 @@ import {
5
5
  generateAndPersistWallet,
6
6
  importAndPersistWallet,
7
7
  selectOrCreateApp
8
- } from "./chunk-VD5WQHLQ.js";
8
+ } from "./chunk-6KOJKH6N.js";
9
9
  import {
10
10
  bold,
11
11
  brand,
12
12
  brandedHelp,
13
13
  dim,
14
+ getUpdateNoticeLines,
14
15
  green,
15
16
  load,
16
17
  maskIf,
@@ -18,7 +19,7 @@ import {
18
19
  promptSelect,
19
20
  promptText,
20
21
  save
21
- } from "./chunk-VRBWUQHA.js";
22
+ } from "./chunk-2WI4JODY.js";
22
23
 
23
24
  // src/commands/onboarding.ts
24
25
  function printNextSteps(method) {
@@ -106,7 +107,7 @@ async function runX402Onboarding() {
106
107
  save({ ...cfg, x402: true });
107
108
  console.log(` ${green("\u2713")} x402 enabled with wallet ${wallet.address}`);
108
109
  }
109
- async function runOnboarding(_program) {
110
+ async function runOnboarding(_program, latestUpdate = null) {
110
111
  process.stdout.write(brandedHelp({ force: true }));
111
112
  console.log("");
112
113
  console.log(` ${brand("\u25C6")} ${bold("Welcome to Alchemy CLI")}`);
@@ -115,6 +116,12 @@ async function runOnboarding(_program) {
115
116
  console.log(` ${dim(" Choose one auth path to continue.")}`);
116
117
  console.log(` ${dim(" Tip: select 'exit' to skip setup for now.")}`);
117
118
  console.log("");
119
+ if (latestUpdate) {
120
+ for (const line of getUpdateNoticeLines(latestUpdate)) {
121
+ console.log(line);
122
+ }
123
+ console.log("");
124
+ }
118
125
  const method = await promptSelect({
119
126
  message: "Choose an auth setup path",
120
127
  options: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alchemy/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Alchemy CLI — interact with blockchain data",
5
5
  "type": "module",
6
6
  "bin": {