@alchemy/cli 0.3.1 → 0.5.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.
@@ -0,0 +1,536 @@
1
+ #!/usr/bin/env node
2
+ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+
4
+ // src/lib/colors.ts
5
+ var forceColor = "FORCE_COLOR" in process.env && process.env.FORCE_COLOR !== "0";
6
+ var noColor = !forceColor && ("NO_COLOR" in process.env || process.env.TERM === "dumb");
7
+ function setNoColor(value) {
8
+ noColor = value;
9
+ }
10
+ var identity = (s) => s;
11
+ var esc = (code) => (s) => noColor ? s : `\x1B[${code}m${s}\x1B[0m`;
12
+ var rgb = (r, g, b) => (s) => noColor ? s : `\x1B[38;2;${r};${g};${b}m${s}\x1B[39m`;
13
+ var bgRgb = (r, g, b) => (s) => noColor ? s : `\x1B[48;2;${r};${g};${b}m${s}\x1B[49m`;
14
+
15
+ // src/lib/errors.ts
16
+ var ErrorCode = {
17
+ AUTH_REQUIRED: "AUTH_REQUIRED",
18
+ INVALID_API_KEY: "INVALID_API_KEY",
19
+ NETWORK_NOT_ENABLED: "NETWORK_NOT_ENABLED",
20
+ INVALID_ACCESS_KEY: "INVALID_ACCESS_KEY",
21
+ ACCESS_KEY_REQUIRED: "ACCESS_KEY_REQUIRED",
22
+ APP_REQUIRED: "APP_REQUIRED",
23
+ ADMIN_API_ERROR: "ADMIN_API_ERROR",
24
+ NETWORK_ERROR: "NETWORK_ERROR",
25
+ RPC_ERROR: "RPC_ERROR",
26
+ INVALID_ARGS: "INVALID_ARGS",
27
+ NOT_FOUND: "NOT_FOUND",
28
+ RATE_LIMITED: "RATE_LIMITED",
29
+ PAYMENT_REQUIRED: "PAYMENT_REQUIRED",
30
+ SETUP_REQUIRED: "SETUP_REQUIRED",
31
+ INTERNAL_ERROR: "INTERNAL_ERROR"
32
+ };
33
+ var RETRYABLE_CODES = /* @__PURE__ */ new Set([
34
+ ErrorCode.RATE_LIMITED,
35
+ ErrorCode.NETWORK_ERROR
36
+ ]);
37
+ var EXIT_CODES = {
38
+ AUTH_REQUIRED: 3,
39
+ INVALID_API_KEY: 3,
40
+ NETWORK_NOT_ENABLED: 3,
41
+ INVALID_ACCESS_KEY: 3,
42
+ ACCESS_KEY_REQUIRED: 3,
43
+ APP_REQUIRED: 3,
44
+ INVALID_ARGS: 2,
45
+ NOT_FOUND: 4,
46
+ RATE_LIMITED: 5,
47
+ NETWORK_ERROR: 6,
48
+ RPC_ERROR: 7,
49
+ ADMIN_API_ERROR: 8,
50
+ PAYMENT_REQUIRED: 9,
51
+ SETUP_REQUIRED: 3,
52
+ INTERNAL_ERROR: 1
53
+ };
54
+ var CLIError = class extends Error {
55
+ code;
56
+ hint;
57
+ details;
58
+ data;
59
+ constructor(code, message, hint, details, data) {
60
+ super(message);
61
+ this.name = "CLIError";
62
+ this.code = code;
63
+ this.hint = hint;
64
+ this.details = details;
65
+ this.data = data;
66
+ }
67
+ toJSON() {
68
+ return {
69
+ error: {
70
+ code: this.code,
71
+ message: this.message,
72
+ ...this.hint && { hint: this.hint },
73
+ ...this.details && { details: this.details },
74
+ ...this.data !== void 0 && { data: this.data },
75
+ retryable: RETRYABLE_CODES.has(this.code)
76
+ }
77
+ };
78
+ }
79
+ format() {
80
+ let out = `${this.code}: ${this.message}`;
81
+ if (this.hint) out += `
82
+ Hint: ${this.hint}`;
83
+ return out;
84
+ }
85
+ };
86
+ function errAuthRequired() {
87
+ return new CLIError(
88
+ ErrorCode.AUTH_REQUIRED,
89
+ "Not authenticated. Run 'alchemy auth' to log in, or set ALCHEMY_API_KEY.",
90
+ "alchemy auth"
91
+ );
92
+ }
93
+ function errAccessKeyRequired() {
94
+ return new CLIError(
95
+ ErrorCode.ACCESS_KEY_REQUIRED,
96
+ "Access key required. Set ALCHEMY_ACCESS_KEY or run 'alchemy config set access-key <key>'.",
97
+ "Get an access key: https://www.alchemy.com/docs/reference/admin-api/overview"
98
+ );
99
+ }
100
+ function errInvalidAPIKey(details) {
101
+ return new CLIError(
102
+ ErrorCode.INVALID_API_KEY,
103
+ "Invalid API key. Check your key and try again.",
104
+ "alchemy config set api-key <your-key>",
105
+ details
106
+ );
107
+ }
108
+ function errNetworkNotEnabled(network, details) {
109
+ const networkLabel = network.toLowerCase().replace(/_/g, "-");
110
+ return new CLIError(
111
+ ErrorCode.NETWORK_NOT_ENABLED,
112
+ `API key is valid, but ${networkLabel} is not enabled for this app.`,
113
+ void 0,
114
+ details
115
+ );
116
+ }
117
+ function errNetwork(detail) {
118
+ return new CLIError(
119
+ ErrorCode.NETWORK_ERROR,
120
+ `Network error: ${detail}`,
121
+ "Check your internet connection and try again."
122
+ );
123
+ }
124
+ var RPC_ERROR_HINTS = {
125
+ [-32700]: "Parse error. The request JSON is malformed.",
126
+ [-32600]: "Invalid request. Check the JSON-RPC request format.",
127
+ [-32601]: "Method not supported. Check the method name and ensure your plan supports it.",
128
+ [-32602]: "Invalid parameters. Check argument types and format.",
129
+ [-32603]: "Internal JSON-RPC error."
130
+ };
131
+ function errRPC(code, message) {
132
+ const hint = RPC_ERROR_HINTS[code];
133
+ return new CLIError(ErrorCode.RPC_ERROR, `RPC error ${code}: ${message}`, hint);
134
+ }
135
+ function errInvalidArgs(detail) {
136
+ return new CLIError(ErrorCode.INVALID_ARGS, detail);
137
+ }
138
+ function errNotFound(resource) {
139
+ return new CLIError(ErrorCode.NOT_FOUND, `Not found: ${resource}`);
140
+ }
141
+ function errRateLimited() {
142
+ return new CLIError(
143
+ ErrorCode.RATE_LIMITED,
144
+ "Rate limited. Please wait and try again.",
145
+ "Consider upgrading your Alchemy plan for higher rate limits."
146
+ );
147
+ }
148
+ function errInvalidAccessKey() {
149
+ return new CLIError(
150
+ ErrorCode.INVALID_ACCESS_KEY,
151
+ "Invalid access key. Check your key and try again.",
152
+ "Get an access key: https://www.alchemy.com/docs/reference/admin-api/overview"
153
+ );
154
+ }
155
+ function errAccessDenied(detail) {
156
+ const message = detail ? `Access denied: ${detail}` : "Access denied. Your access key may not have permission for this operation.";
157
+ return new CLIError(
158
+ ErrorCode.INVALID_ACCESS_KEY,
159
+ message,
160
+ "Check your account tier and feature access at https://dashboard.alchemy.com/"
161
+ );
162
+ }
163
+ function errAppRequired() {
164
+ return new CLIError(
165
+ ErrorCode.APP_REQUIRED,
166
+ "No app selected. Set an app to resolve the API key automatically.",
167
+ "alchemy config set app <app-id>"
168
+ );
169
+ }
170
+ function errWalletKeyRequired() {
171
+ return new CLIError(
172
+ ErrorCode.AUTH_REQUIRED,
173
+ "Wallet key required for x402. Set ALCHEMY_WALLET_KEY, run 'alchemy wallet generate', or use --wallet-key-file.",
174
+ "alchemy wallet generate"
175
+ );
176
+ }
177
+ function errAdminAPI(status, message) {
178
+ return new CLIError(
179
+ ErrorCode.ADMIN_API_ERROR,
180
+ `Admin API error (HTTP ${status}): ${message}`
181
+ );
182
+ }
183
+ function errSetupRequired(data) {
184
+ return new CLIError(
185
+ ErrorCode.SETUP_REQUIRED,
186
+ "Setup required before running in non-interactive mode.",
187
+ "Run 'alchemy' in a TTY for guided onboarding, or run 'alchemy setup status --json' for machine-readable remediation steps.",
188
+ void 0,
189
+ data
190
+ );
191
+ }
192
+ var replMode = false;
193
+ function setReplMode(enabled) {
194
+ replMode = enabled;
195
+ }
196
+ function exitWithError(err) {
197
+ const cliErr = err instanceof CLIError ? err : new CLIError(
198
+ ErrorCode.INTERNAL_ERROR,
199
+ err instanceof Error ? err.message : String(err)
200
+ );
201
+ printError(cliErr);
202
+ if (replMode) {
203
+ throw cliErr;
204
+ }
205
+ process.exit(EXIT_CODES[cliErr.code]);
206
+ }
207
+
208
+ // src/lib/client-utils.ts
209
+ var DEFAULT_BASE_DOMAIN = "alchemy.com";
210
+ function getBaseDomain() {
211
+ if (process.env.ALCHEMY_UNSAFE_OVERRIDES === "1" && process.env.ALCHEMY_BASE_DOMAIN) {
212
+ return process.env.ALCHEMY_BASE_DOMAIN;
213
+ }
214
+ return DEFAULT_BASE_DOMAIN;
215
+ }
216
+ function isLocalhost(hostname) {
217
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
218
+ }
219
+ function parseBaseURLOverride(envVarName) {
220
+ const raw = process.env[envVarName];
221
+ if (!raw) return null;
222
+ let parsed;
223
+ try {
224
+ parsed = new URL(raw);
225
+ } catch {
226
+ throw errInvalidArgs(`Invalid ${envVarName} value.`);
227
+ }
228
+ if (!isLocalhost(parsed.hostname)) {
229
+ throw errInvalidArgs(
230
+ `${envVarName} must target localhost or 127.0.0.1.`
231
+ );
232
+ }
233
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
234
+ throw errInvalidArgs(
235
+ `${envVarName} must use http:// or https://.`
236
+ );
237
+ }
238
+ if (parsed.protocol === "http:" && !isLocalhost(parsed.hostname)) {
239
+ throw errInvalidArgs(
240
+ `${envVarName} can only use non-HTTPS for localhost targets.`
241
+ );
242
+ }
243
+ return parsed;
244
+ }
245
+ var BREADCRUMB_HEADER = "alchemy-cli";
246
+ function escapeRegExp(input) {
247
+ return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
248
+ }
249
+ async function fetchWithTimeout(url, init) {
250
+ try {
251
+ return await fetch(url, {
252
+ ...init,
253
+ headers: {
254
+ ...init.headers,
255
+ "x-alchemy-client-breadcrumb": BREADCRUMB_HEADER
256
+ },
257
+ ...timeout && { signal: AbortSignal.timeout(timeout) }
258
+ });
259
+ } catch (err) {
260
+ if (err instanceof DOMException && err.name === "TimeoutError") {
261
+ throw errNetwork(`Request timed out after ${timeout}ms`);
262
+ }
263
+ const message = err.message ?? String(err);
264
+ const causeMessage = err.cause?.message ?? "";
265
+ const causeCode = err.cause?.code ?? "";
266
+ const fullErrorText = `${message} ${causeMessage} ${causeCode}`;
267
+ if (/ENOTFOUND|EAI_AGAIN|getaddrinfo/i.test(fullErrorText)) {
268
+ try {
269
+ const hostname = new URL(url).hostname;
270
+ const networkSlug = hostname.replace(new RegExp(`\\.g\\.${escapeRegExp(getBaseDomain())}$`), "");
271
+ if (networkSlug !== hostname) {
272
+ throw errInvalidArgs(
273
+ `Unknown network '${networkSlug}'. Run 'alchemy network list' to see available networks.`
274
+ );
275
+ }
276
+ } catch (innerErr) {
277
+ if (innerErr instanceof CLIError) throw innerErr;
278
+ }
279
+ }
280
+ throw errNetwork(message);
281
+ }
282
+ }
283
+
284
+ // src/lib/redact.ts
285
+ var SENSITIVE_ERROR_CODES = /* @__PURE__ */ new Set([
286
+ "AUTH_REQUIRED",
287
+ "INVALID_API_KEY",
288
+ "INVALID_ACCESS_KEY",
289
+ "ACCESS_KEY_REQUIRED"
290
+ ]);
291
+ function getAlchemyKeyPathMarkers() {
292
+ const domain = getBaseDomain();
293
+ return [`${domain}/v2/`, `${domain}/nft/v3/`];
294
+ }
295
+ function isSecretBoundaryChar(char) {
296
+ return char === "/" || char === "?" || char === " " || char === " " || char === "\n" || char === "\r" || char === '"' || char === "'" || char === "`";
297
+ }
298
+ function redactAfterMarker(input, marker) {
299
+ const lower = input.toLowerCase();
300
+ let index = 0;
301
+ let cursor = 0;
302
+ let out = "";
303
+ while (index < input.length) {
304
+ const markerIndex = lower.indexOf(marker, index);
305
+ if (markerIndex === -1) break;
306
+ const secretStart = markerIndex + marker.length;
307
+ let secretEnd = secretStart;
308
+ while (secretEnd < input.length && !isSecretBoundaryChar(input[secretEnd])) {
309
+ secretEnd += 1;
310
+ }
311
+ out += input.slice(cursor, secretStart);
312
+ out += "[REDACTED]";
313
+ cursor = secretEnd;
314
+ index = secretEnd;
315
+ }
316
+ if (!out) return input;
317
+ return out + input.slice(cursor);
318
+ }
319
+ function redactSensitiveText(value) {
320
+ let redacted = value;
321
+ for (const marker of getAlchemyKeyPathMarkers()) {
322
+ redacted = redactAfterMarker(redacted, marker);
323
+ }
324
+ return redacted;
325
+ }
326
+
327
+ // src/lib/error-format.ts
328
+ var ansi = {
329
+ red: (s) => `\x1B[31m${s}\x1B[0m`,
330
+ boldRed: (s) => `\x1B[1;31m${s}\x1B[0m`,
331
+ dim: (s) => `\x1B[2m${s}\x1B[0m`
332
+ };
333
+ function toSafeErrorJSON(err) {
334
+ const payload = err.toJSON();
335
+ const error = payload.error ?? {};
336
+ const code = error.code ?? err.code;
337
+ const safeError = {
338
+ ...code && { code },
339
+ ...typeof error.message === "string" && { message: redactSensitiveText(error.message) },
340
+ ...typeof error.hint === "string" && { hint: redactSensitiveText(error.hint) },
341
+ ...typeof error.retryable === "boolean" && { retryable: error.retryable }
342
+ };
343
+ if (typeof error.details === "string" && !SENSITIVE_ERROR_CODES.has(code)) {
344
+ safeError.details = redactSensitiveText(error.details);
345
+ }
346
+ if (error.data !== void 0) {
347
+ safeError.data = error.data;
348
+ }
349
+ return {
350
+ error: safeError
351
+ };
352
+ }
353
+ function wrapWithPrefix(text, prefix, width) {
354
+ const safeWidth = Math.max(20, width - prefix.length);
355
+ const words = text.trim().split(/\s+/);
356
+ if (words.length === 0 || words.length === 1 && words[0] === "") return [prefix];
357
+ const lines = [];
358
+ let current = "";
359
+ for (const word of words) {
360
+ if (current.length === 0) {
361
+ current = word;
362
+ continue;
363
+ }
364
+ if (current.length + 1 + word.length <= safeWidth) {
365
+ current += ` ${word}`;
366
+ } else {
367
+ lines.push(`${prefix}${current}`);
368
+ current = word;
369
+ }
370
+ }
371
+ if (current.length > 0) {
372
+ lines.push(`${prefix}${current}`);
373
+ }
374
+ return lines;
375
+ }
376
+ function supportsStderrStyling() {
377
+ return (process.stderr.isTTY || forceColor) && !noColor;
378
+ }
379
+ function printError(err) {
380
+ if (isJSONMode()) {
381
+ console.error(JSON.stringify(toSafeErrorJSON(err), null, 2));
382
+ } else {
383
+ const width = process.stderr.columns ?? 100;
384
+ const safeMessage = redactSensitiveText(err.message);
385
+ const safeHint = err.hint ? redactSensitiveText(err.hint) : void 0;
386
+ const safeDetails = err.details && !SENSITIVE_ERROR_CODES.has(err.code) ? redactSensitiveText(err.details) : void 0;
387
+ const detailLines = safeDetails ? safeDetails.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0) : [];
388
+ const lines = [` \u2717 ${err.code}`, ` ${"\u2500".repeat(40)}`];
389
+ lines.push(...wrapWithPrefix(safeMessage, " - ", width));
390
+ if (detailLines.length > 0) {
391
+ lines.push("");
392
+ lines.push(" - Provider:");
393
+ for (const line of detailLines) {
394
+ lines.push(...wrapWithPrefix(line, " - ", width));
395
+ }
396
+ }
397
+ if (safeHint) {
398
+ lines.push("");
399
+ lines.push(...wrapWithPrefix(`Hint: ${safeHint}`, " - ", width));
400
+ }
401
+ if (supportsStderrStyling()) {
402
+ const styled = [
403
+ ` ${ansi.red("\u2717")} ${ansi.boldRed(err.code)}`,
404
+ ` ${ansi.dim("\u2500".repeat(40))}`,
405
+ ...wrapWithPrefix(safeMessage, " - ", width).map((line) => ansi.red(line))
406
+ ];
407
+ if (detailLines.length > 0) {
408
+ styled.push("");
409
+ styled.push(` ${ansi.dim("- Provider:")}`);
410
+ for (const line of detailLines) {
411
+ styled.push(...wrapWithPrefix(line, " - ", width).map((ln) => ansi.dim(ln)));
412
+ }
413
+ }
414
+ if (safeHint) {
415
+ styled.push("");
416
+ styled.push(
417
+ ...wrapWithPrefix(`Hint: ${safeHint}`, " - ", width).map((line) => ansi.dim(line))
418
+ );
419
+ }
420
+ console.error(`
421
+ ${styled.join("\n")}
422
+ `);
423
+ return;
424
+ }
425
+ console.error(`
426
+ ${lines.join("\n")}
427
+ `);
428
+ }
429
+ }
430
+ function formatCommanderError(message) {
431
+ const jsonMode = !process.stdout.isTTY || process.argv.includes("--json");
432
+ const lines = message.trimEnd().split("\n").filter((line) => line.trim() !== "");
433
+ if (lines.length === 0) return message;
434
+ const [first, ...rest] = lines;
435
+ const detail = first.replace(/^error:\s*/i, "").trim();
436
+ if (jsonMode) {
437
+ const err = {
438
+ error: {
439
+ code: "INVALID_ARGS",
440
+ message: detail,
441
+ ...rest.length > 0 && { hint: rest.map((l) => l.trim()).join(" ") }
442
+ }
443
+ };
444
+ return JSON.stringify(err, null, 2) + "\n";
445
+ }
446
+ if (!supportsStderrStyling()) return message;
447
+ const styled = [
448
+ ` ${ansi.red("\u2717")} ${ansi.boldRed("Error")}`,
449
+ ` ${ansi.red(detail)}`,
450
+ ...rest.map((line) => ` ${ansi.dim(line)}`)
451
+ ];
452
+ return `
453
+ ${styled.join("\n")}
454
+ `;
455
+ }
456
+
457
+ // src/lib/output.ts
458
+ var forceJSON = false;
459
+ var quiet = false;
460
+ var verbose = false;
461
+ var debugMode = false;
462
+ var timeout;
463
+ var reveal = false;
464
+ function setFlags(opts) {
465
+ forceJSON = opts.json ?? false;
466
+ quiet = opts.quiet ?? false;
467
+ verbose = opts.verbose ?? false;
468
+ debugMode = opts.debug ?? false;
469
+ reveal = opts.reveal ?? false;
470
+ timeout = opts.timeout;
471
+ }
472
+ function isRevealMode() {
473
+ return reveal;
474
+ }
475
+ function isJSONMode() {
476
+ if (forceJSON) return true;
477
+ return !process.stdout.isTTY;
478
+ }
479
+ function printJSON(value) {
480
+ console.log(JSON.stringify(value, null, 2));
481
+ }
482
+ function printHuman(humanText, jsonValue) {
483
+ if (isJSONMode()) {
484
+ printJSON(jsonValue);
485
+ } else {
486
+ process.stdout.write(humanText);
487
+ }
488
+ }
489
+ function debug(message, ...args) {
490
+ if (debugMode) {
491
+ console.error(`[debug] ${message}`, ...args);
492
+ }
493
+ }
494
+
495
+ export {
496
+ noColor,
497
+ setNoColor,
498
+ identity,
499
+ esc,
500
+ rgb,
501
+ bgRgb,
502
+ quiet,
503
+ verbose,
504
+ setFlags,
505
+ isRevealMode,
506
+ isJSONMode,
507
+ printJSON,
508
+ printHuman,
509
+ debug,
510
+ getBaseDomain,
511
+ isLocalhost,
512
+ parseBaseURLOverride,
513
+ fetchWithTimeout,
514
+ redactSensitiveText,
515
+ formatCommanderError,
516
+ ErrorCode,
517
+ EXIT_CODES,
518
+ CLIError,
519
+ errAuthRequired,
520
+ errAccessKeyRequired,
521
+ errInvalidAPIKey,
522
+ errNetworkNotEnabled,
523
+ errNetwork,
524
+ errRPC,
525
+ errInvalidArgs,
526
+ errNotFound,
527
+ errRateLimited,
528
+ errInvalidAccessKey,
529
+ errAccessDenied,
530
+ errAppRequired,
531
+ errWalletKeyRequired,
532
+ errAdminAPI,
533
+ errSetupRequired,
534
+ setReplMode,
535
+ exitWithError
536
+ };
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
- isInteractiveAllowed
5
- } from "./chunk-6XTLILDF.js";
4
+ isInteractiveAllowed,
5
+ resolveAuthToken
6
+ } from "./chunk-T2XSNZE3.js";
7
+ import {
8
+ getBaseDomain
9
+ } from "./chunk-56ZVYB4G.js";
6
10
 
7
11
  // src/lib/networks.ts
8
12
  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;
@@ -210,10 +214,11 @@ function toDisplayName(id) {
210
214
  return id.split("-").map((part) => tokenToName(part)).join(" ");
211
215
  }
212
216
  function toHttpsUrlTemplate(id) {
217
+ const domain = getBaseDomain();
213
218
  if (id === "starknet-mainnet" || id === "starknet-sepolia") {
214
- return `https://${id}.g.alchemy.com/starknet/version/rpc/v0_10/{apiKey}`;
219
+ return `https://${id}.g.${domain}/starknet/version/rpc/v0_10/{apiKey}`;
215
220
  }
216
- return `https://${id}.g.alchemy.com/v2/{apiKey}`;
221
+ return `https://${id}.g.${domain}/v2/{apiKey}`;
217
222
  }
218
223
  function getRPCNetworks() {
219
224
  return RPC_NETWORK_IDS.map((id) => ({
@@ -274,10 +279,14 @@ function hasAccessKeyAndApp(cfg) {
274
279
  function hasX402Wallet(cfg) {
275
280
  return cfg.x402 === true && Boolean(cfg.wallet_key_file?.trim());
276
281
  }
282
+ function hasAuthToken(cfg) {
283
+ return resolveAuthToken(cfg) !== void 0;
284
+ }
277
285
  function getSetupMethod(cfg) {
278
286
  if (hasAPIKey(cfg)) return "api_key";
279
287
  if (hasAccessKeyAndApp(cfg)) return "access_key_app";
280
288
  if (hasX402Wallet(cfg)) return "x402_wallet";
289
+ if (hasAuthToken(cfg)) return "auth_token";
281
290
  return null;
282
291
  }
283
292
  function isSetupComplete(cfg) {
@@ -296,8 +305,9 @@ function getSetupStatus(cfg) {
296
305
  return {
297
306
  complete: false,
298
307
  satisfiedBy: null,
299
- missing: ["Provide one auth path: api-key OR access-key+app OR x402+wallet-key-file"],
308
+ missing: ["Provide one auth path: alchemy auth OR api-key OR access-key+app OR SIWx wallet"],
300
309
  nextCommands: [
310
+ "alchemy auth",
301
311
  "alchemy config set api-key <key>",
302
312
  "alchemy config set access-key <key> && alchemy config set app <app-id>",
303
313
  "alchemy wallet generate && alchemy config set x402 true"
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+ import {
4
+ configPath
5
+ } from "./chunk-T2XSNZE3.js";
6
+ import {
7
+ esc
8
+ } from "./chunk-56ZVYB4G.js";
9
+
10
+ // src/lib/update-check.ts
11
+ import { execFileSync } from "child_process";
12
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
13
+ import { dirname } from "path";
14
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
15
+ var UPDATE_INSTALL_COMMAND = "npm i -g @alchemy/cli";
16
+ function cachePath() {
17
+ return configPath().replace(/config\.json$/, ".update-check");
18
+ }
19
+ function readCache() {
20
+ try {
21
+ return JSON.parse(readFileSync(cachePath(), "utf-8"));
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+ function writeCache(cache) {
27
+ try {
28
+ const p = cachePath();
29
+ mkdirSync(dirname(p), { recursive: true });
30
+ writeFileSync(p, JSON.stringify(cache), { mode: 384 });
31
+ } catch {
32
+ }
33
+ }
34
+ function fetchLatestVersion() {
35
+ try {
36
+ const result = execFileSync("npm", ["view", "@alchemy/cli", "version"], {
37
+ encoding: "utf-8",
38
+ timeout: 5e3,
39
+ stdio: ["pipe", "pipe", "pipe"]
40
+ });
41
+ return result.trim() || null;
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ function semverLT(a, b) {
47
+ const pa = a.split(".").map(Number);
48
+ const pb = b.split(".").map(Number);
49
+ for (let i = 0; i < 3; i++) {
50
+ if ((pa[i] ?? 0) < (pb[i] ?? 0)) return true;
51
+ if ((pa[i] ?? 0) > (pb[i] ?? 0)) return false;
52
+ }
53
+ return false;
54
+ }
55
+ function currentVersion() {
56
+ return true ? "0.5.0" : "0.0.0";
57
+ }
58
+ function toUpdateStatus(latestVersion, checkedAt) {
59
+ const current = currentVersion();
60
+ return {
61
+ currentVersion: current,
62
+ latestVersion,
63
+ updateAvailable: latestVersion ? semverLT(current, latestVersion) : false,
64
+ installCommand: UPDATE_INSTALL_COMMAND,
65
+ checkedAt
66
+ };
67
+ }
68
+ function getUpdateStatus() {
69
+ const cache = readCache();
70
+ if (cache && Date.now() - cache.checkedAt < CACHE_TTL_MS) {
71
+ return toUpdateStatus(cache.latest, cache.checkedAt);
72
+ }
73
+ const latest = fetchLatestVersion();
74
+ if (latest) {
75
+ const checkedAt = Date.now();
76
+ writeCache({ latest, checkedAt });
77
+ return toUpdateStatus(latest, checkedAt);
78
+ }
79
+ if (cache) {
80
+ return toUpdateStatus(cache.latest, cache.checkedAt);
81
+ }
82
+ return toUpdateStatus(null, null);
83
+ }
84
+ function getAvailableUpdate() {
85
+ const current = currentVersion();
86
+ const cache = readCache();
87
+ if (cache && Date.now() - cache.checkedAt < CACHE_TTL_MS) {
88
+ return semverLT(current, cache.latest) ? cache.latest : null;
89
+ }
90
+ const latest = fetchLatestVersion();
91
+ if (latest) {
92
+ writeCache({ latest, checkedAt: Date.now() });
93
+ return semverLT(current, latest) ? latest : null;
94
+ }
95
+ return null;
96
+ }
97
+ function getUpdateNoticeLines(latest) {
98
+ const yellow = esc("33");
99
+ const bold = esc("1");
100
+ const dim = esc("2");
101
+ return [
102
+ ` ${yellow("Update available")} ${dim(currentVersion())} \u2192 ${bold(latest)}`,
103
+ ` Run ${bold(UPDATE_INSTALL_COMMAND)} to update`
104
+ ];
105
+ }
106
+ function printUpdateNotice(latest) {
107
+ process.stderr.write(`
108
+ ${getUpdateNoticeLines(latest).join("\n")}
109
+
110
+ `);
111
+ }
112
+
113
+ export {
114
+ getUpdateStatus,
115
+ getAvailableUpdate,
116
+ getUpdateNoticeLines,
117
+ printUpdateNotice
118
+ };