@alchemy/cli 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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
 
@@ -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-SDUCDSCZ.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;
@@ -1047,6 +1047,109 @@ function isInteractiveAllowed(program) {
1047
1047
  return true;
1048
1048
  }
1049
1049
 
1050
+ // src/lib/update-check.ts
1051
+ import { execFileSync } from "child_process";
1052
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1053
+ import { dirname as dirname2 } from "path";
1054
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
1055
+ var UPDATE_INSTALL_COMMAND = "npm i -g @alchemy/cli";
1056
+ function cachePath() {
1057
+ return configPath().replace(/config\.json$/, ".update-check");
1058
+ }
1059
+ function readCache() {
1060
+ try {
1061
+ return JSON.parse(readFileSync2(cachePath(), "utf-8"));
1062
+ } catch {
1063
+ return null;
1064
+ }
1065
+ }
1066
+ function writeCache(cache) {
1067
+ try {
1068
+ const p = cachePath();
1069
+ mkdirSync2(dirname2(p), { recursive: true });
1070
+ writeFileSync2(p, JSON.stringify(cache), { mode: 384 });
1071
+ } catch {
1072
+ }
1073
+ }
1074
+ function fetchLatestVersion() {
1075
+ try {
1076
+ const result = execFileSync("npm", ["view", "@alchemy/cli", "version"], {
1077
+ encoding: "utf-8",
1078
+ timeout: 5e3,
1079
+ stdio: ["pipe", "pipe", "pipe"]
1080
+ });
1081
+ return result.trim() || null;
1082
+ } catch {
1083
+ return null;
1084
+ }
1085
+ }
1086
+ function semverLT(a, b) {
1087
+ const pa = a.split(".").map(Number);
1088
+ const pb = b.split(".").map(Number);
1089
+ for (let i = 0; i < 3; i++) {
1090
+ if ((pa[i] ?? 0) < (pb[i] ?? 0)) return true;
1091
+ if ((pa[i] ?? 0) > (pb[i] ?? 0)) return false;
1092
+ }
1093
+ return false;
1094
+ }
1095
+ function currentVersion() {
1096
+ return true ? "0.2.1" : "0.0.0";
1097
+ }
1098
+ function toUpdateStatus(latestVersion, checkedAt) {
1099
+ const current = currentVersion();
1100
+ return {
1101
+ currentVersion: current,
1102
+ latestVersion,
1103
+ updateAvailable: latestVersion ? semverLT(current, latestVersion) : false,
1104
+ installCommand: UPDATE_INSTALL_COMMAND,
1105
+ checkedAt
1106
+ };
1107
+ }
1108
+ function getUpdateStatus() {
1109
+ const cache = readCache();
1110
+ if (cache && Date.now() - cache.checkedAt < CACHE_TTL_MS) {
1111
+ return toUpdateStatus(cache.latest, cache.checkedAt);
1112
+ }
1113
+ const latest = fetchLatestVersion();
1114
+ if (latest) {
1115
+ const checkedAt = Date.now();
1116
+ writeCache({ latest, checkedAt });
1117
+ return toUpdateStatus(latest, checkedAt);
1118
+ }
1119
+ if (cache) {
1120
+ return toUpdateStatus(cache.latest, cache.checkedAt);
1121
+ }
1122
+ return toUpdateStatus(null, null);
1123
+ }
1124
+ function getAvailableUpdate() {
1125
+ const current = currentVersion();
1126
+ const cache = readCache();
1127
+ if (cache && Date.now() - cache.checkedAt < CACHE_TTL_MS) {
1128
+ return semverLT(current, cache.latest) ? cache.latest : null;
1129
+ }
1130
+ const latest = fetchLatestVersion();
1131
+ if (latest) {
1132
+ writeCache({ latest, checkedAt: Date.now() });
1133
+ return semverLT(current, latest) ? latest : null;
1134
+ }
1135
+ return null;
1136
+ }
1137
+ function getUpdateNoticeLines(latest) {
1138
+ const yellow2 = esc("33");
1139
+ const bold2 = esc("1");
1140
+ const dim2 = esc("2");
1141
+ return [
1142
+ ` ${yellow2("Update available")} ${dim2(currentVersion())} \u2192 ${bold2(latest)}`,
1143
+ ` Run ${bold2(UPDATE_INSTALL_COMMAND)} to update`
1144
+ ];
1145
+ }
1146
+ function printUpdateNotice(latest) {
1147
+ process.stderr.write(`
1148
+ ${getUpdateNoticeLines(latest).join("\n")}
1149
+
1150
+ `);
1151
+ }
1152
+
1050
1153
  export {
1051
1154
  noColor,
1052
1155
  identity,
@@ -1110,5 +1213,9 @@ export {
1110
1213
  timeAgo,
1111
1214
  etherscanTxURL,
1112
1215
  brandedHelp,
1113
- isInteractiveAllowed
1216
+ isInteractiveAllowed,
1217
+ getUpdateStatus,
1218
+ getAvailableUpdate,
1219
+ getUpdateNoticeLines,
1220
+ printUpdateNotice
1114
1221
  };
@@ -40,7 +40,7 @@ import {
40
40
  toMap,
41
41
  withSpinner,
42
42
  yellow
43
- } from "./chunk-VRBWUQHA.js";
43
+ } from "./chunk-SDUCDSCZ.js";
44
44
 
45
45
  // src/lib/client-utils.ts
46
46
  function isLocalhost(hostname) {
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-XP5KF4W2.js";
18
18
  import {
19
19
  getRPCNetworks,
20
20
  getSetupStatus,
21
21
  isSetupComplete,
22
22
  shouldRunOnboarding
23
- } from "./chunk-MAKSV2EA.js";
23
+ } from "./chunk-HBRTTBCY.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-SDUCDSCZ.js";
66
69
 
67
70
  // src/index.ts
68
71
  import { Command, Help } from "commander";
@@ -1678,7 +1681,8 @@ function buildAgentPrompt(program2) {
1678
1681
  "Always pass --json --no-interactive",
1679
1682
  "Parse stdout as JSON on exit code 0",
1680
1683
  "Parse stderr as JSON on nonzero exit code",
1681
- "Never run bare 'alchemy' without --json --no-interactive"
1684
+ "Never run bare 'alchemy' without --json --no-interactive",
1685
+ "Run alchemy --json --no-interactive update-check when you need to detect available CLI upgrades"
1682
1686
  ],
1683
1687
  preflight: {
1684
1688
  command: "alchemy --json setup status",
@@ -1742,6 +1746,7 @@ function buildAgentPrompt(program2) {
1742
1746
  errors,
1743
1747
  examples: [
1744
1748
  "alchemy --json --no-interactive setup status",
1749
+ "alchemy --json --no-interactive update-check",
1745
1750
  "alchemy --json --no-interactive balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --api-key $ALCHEMY_API_KEY",
1746
1751
  "alchemy --json --no-interactive apps list --access-key $ALCHEMY_ACCESS_KEY",
1747
1752
  "alchemy --json --no-interactive rpc eth_blockNumber --api-key $ALCHEMY_API_KEY",
@@ -1800,6 +1805,27 @@ function registerAgentPrompt(program2) {
1800
1805
  });
1801
1806
  }
1802
1807
 
1808
+ // src/commands/update-check.ts
1809
+ function formatCheckedAt(checkedAt) {
1810
+ return checkedAt ? new Date(checkedAt).toISOString() : dim("(unknown)");
1811
+ }
1812
+ function registerUpdateCheck(program2) {
1813
+ program2.command("update-check").description("Check whether a newer CLI version is available").action(() => {
1814
+ const status = getUpdateStatus();
1815
+ if (isJSONMode()) {
1816
+ printJSON(status);
1817
+ return;
1818
+ }
1819
+ printKeyValueBox([
1820
+ ["Current version", status.currentVersion],
1821
+ ["Latest version", status.latestVersion ?? dim("(unknown)")],
1822
+ ["Update available", status.updateAvailable ? "yes" : "no"],
1823
+ ["Checked at", formatCheckedAt(status.checkedAt)],
1824
+ ["Install", status.installCommand]
1825
+ ]);
1826
+ });
1827
+ }
1828
+
1803
1829
  // src/index.ts
1804
1830
  var hBrand = noColor ? identity : (s) => `\x1B[38;2;54;63;249m${s}\x1B[39m`;
1805
1831
  var hBold = esc("1");
@@ -1837,7 +1863,7 @@ var ROOT_COMMAND_PILLARS = [
1837
1863
  },
1838
1864
  {
1839
1865
  label: "Admin",
1840
- commands: ["apps", "config", "setup", "agent-prompt", "version", "help"]
1866
+ commands: ["apps", "config", "setup", "agent-prompt", "update-check", "version", "help"]
1841
1867
  }
1842
1868
  ];
1843
1869
  function formatCommandSignature(sub) {
@@ -1872,12 +1898,29 @@ var findCommandByPath = (root, path) => {
1872
1898
  }
1873
1899
  return current;
1874
1900
  };
1901
+ var cachedAvailableUpdate;
1902
+ var updateShownDuringInteractiveStartup = false;
1903
+ function getAvailableUpdateOnce() {
1904
+ if (cachedAvailableUpdate === void 0) {
1905
+ cachedAvailableUpdate = getAvailableUpdate();
1906
+ }
1907
+ return cachedAvailableUpdate;
1908
+ }
1909
+ function resetUpdateNoticeState() {
1910
+ cachedAvailableUpdate = void 0;
1911
+ updateShownDuringInteractiveStartup = false;
1912
+ }
1875
1913
  program.name("alchemy").description(
1876
1914
  "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(
1915
+ ).version("0.2.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(
1878
1916
  "-n, --network <network>",
1879
1917
  "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({
1918
+ ).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).exitOverride((err) => {
1919
+ if (err.code === "commander.help" || err.code === "commander.helpDisplayed" || err.code === "commander.version") {
1920
+ process.exit(0);
1921
+ }
1922
+ process.exit(EXIT_CODES.INVALID_ARGS);
1923
+ }).configureOutput({
1881
1924
  outputError(str, write) {
1882
1925
  write(formatCommanderError(str));
1883
1926
  }
@@ -2018,27 +2061,39 @@ ${styledLine}`;
2018
2061
  }).hook("postAction", () => {
2019
2062
  if (!isJSONMode() && !quiet) {
2020
2063
  console.log("");
2064
+ if (!updateShownDuringInteractiveStartup) {
2065
+ const latest = getAvailableUpdateOnce();
2066
+ if (latest) printUpdateNotice(latest);
2067
+ }
2021
2068
  }
2069
+ resetUpdateNoticeState();
2022
2070
  }).action(async () => {
2023
2071
  const cfg = load();
2024
2072
  if (!isSetupComplete(cfg) && !isInteractiveAllowed(program)) {
2025
2073
  throw errSetupRequired(getSetupStatus(cfg));
2026
2074
  }
2027
2075
  if (isInteractiveAllowed(program)) {
2076
+ let latestForInteractiveStartup = null;
2028
2077
  if (shouldRunOnboarding(program, cfg)) {
2029
- const { runOnboarding } = await import("./onboarding-IKIXUTY7.js");
2030
- const completed = await runOnboarding(program);
2078
+ const { runOnboarding } = await import("./onboarding-YA32OJOT.js");
2079
+ const latest = getAvailableUpdateOnce();
2080
+ const completed = await runOnboarding(program, latest);
2081
+ updateShownDuringInteractiveStartup = Boolean(latest);
2082
+ latestForInteractiveStartup = null;
2031
2083
  if (!completed) {
2032
2084
  return;
2033
2085
  }
2086
+ } else {
2087
+ latestForInteractiveStartup = getAvailableUpdateOnce();
2088
+ updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
2034
2089
  }
2035
- const { startREPL } = await import("./interactive-CP6E23OD.js");
2090
+ const { startREPL } = await import("./interactive-L7N4HI7K.js");
2036
2091
  program.exitOverride();
2037
2092
  program.configureOutput({
2038
2093
  writeErr: () => {
2039
2094
  }
2040
2095
  });
2041
- await startREPL(program);
2096
+ await startREPL(program, latestForInteractiveStartup);
2042
2097
  return;
2043
2098
  }
2044
2099
  program.help();
@@ -2066,6 +2121,7 @@ registerSetup(program);
2066
2121
  registerConfig(program);
2067
2122
  registerSolana(program);
2068
2123
  registerAgentPrompt(program);
2124
+ registerUpdateCheck(program);
2069
2125
  registerVersion(program);
2070
2126
  program.command("help [command...]").description("display help for command").action((commandPath) => {
2071
2127
  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-HBRTTBCY.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-SDUCDSCZ.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-XP5KF4W2.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-SDUCDSCZ.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.1",
4
4
  "description": "Alchemy CLI — interact with blockchain data",
5
5
  "type": "module",
6
6
  "bin": {