@opensea/cli 0.4.1 → 0.4.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 +2 -1
- package/dist/cli.js +248 -55
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +195 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command11 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/client.ts
|
|
7
7
|
var DEFAULT_BASE_URL = "https://api.opensea.io";
|
|
8
8
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
9
|
+
var USER_AGENT = `opensea-cli/${"0.4.2"}`;
|
|
10
|
+
var DEFAULT_MAX_RETRIES = 0;
|
|
11
|
+
var DEFAULT_RETRY_BASE_DELAY_MS = 1e3;
|
|
12
|
+
function isRetryableStatus(status, method) {
|
|
13
|
+
if (status === 429) return true;
|
|
14
|
+
return status >= 500 && method === "GET";
|
|
15
|
+
}
|
|
16
|
+
function parseRetryAfter(header) {
|
|
17
|
+
if (!header) return void 0;
|
|
18
|
+
const seconds = Number(header);
|
|
19
|
+
if (!Number.isNaN(seconds)) return seconds * 1e3;
|
|
20
|
+
const date = Date.parse(header);
|
|
21
|
+
if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
function sleep(ms) {
|
|
25
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
26
|
+
}
|
|
9
27
|
var OpenSeaClient = class {
|
|
10
28
|
apiKey;
|
|
11
29
|
baseUrl;
|
|
12
30
|
defaultChain;
|
|
13
31
|
timeoutMs;
|
|
14
32
|
verbose;
|
|
33
|
+
maxRetries;
|
|
34
|
+
retryBaseDelay;
|
|
15
35
|
constructor(config) {
|
|
16
36
|
this.apiKey = config.apiKey;
|
|
17
37
|
this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
18
38
|
this.defaultChain = config.chain ?? "ethereum";
|
|
19
39
|
this.timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
20
40
|
this.verbose = config.verbose ?? false;
|
|
41
|
+
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
42
|
+
this.retryBaseDelay = config.retryBaseDelay ?? DEFAULT_RETRY_BASE_DELAY_MS;
|
|
43
|
+
}
|
|
44
|
+
get defaultHeaders() {
|
|
45
|
+
return {
|
|
46
|
+
Accept: "application/json",
|
|
47
|
+
"User-Agent": USER_AGENT,
|
|
48
|
+
"x-api-key": this.apiKey
|
|
49
|
+
};
|
|
21
50
|
}
|
|
22
51
|
async get(path, params) {
|
|
23
52
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
@@ -31,21 +60,14 @@ var OpenSeaClient = class {
|
|
|
31
60
|
if (this.verbose) {
|
|
32
61
|
console.error(`[verbose] GET ${url.toString()}`);
|
|
33
62
|
}
|
|
34
|
-
const response = await
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
63
|
+
const response = await this.fetchWithRetry(
|
|
64
|
+
url.toString(),
|
|
65
|
+
{
|
|
66
|
+
method: "GET",
|
|
67
|
+
headers: this.defaultHeaders
|
|
39
68
|
},
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (this.verbose) {
|
|
43
|
-
console.error(`[verbose] ${response.status} ${response.statusText}`);
|
|
44
|
-
}
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
const body = await response.text();
|
|
47
|
-
throw new OpenSeaAPIError(response.status, body, path);
|
|
48
|
-
}
|
|
69
|
+
path
|
|
70
|
+
);
|
|
49
71
|
return response.json();
|
|
50
72
|
}
|
|
51
73
|
async post(path, body, params) {
|
|
@@ -57,34 +79,67 @@ var OpenSeaClient = class {
|
|
|
57
79
|
}
|
|
58
80
|
}
|
|
59
81
|
}
|
|
60
|
-
const headers = {
|
|
61
|
-
Accept: "application/json",
|
|
62
|
-
"x-api-key": this.apiKey
|
|
63
|
-
};
|
|
82
|
+
const headers = { ...this.defaultHeaders };
|
|
64
83
|
if (body) {
|
|
65
84
|
headers["Content-Type"] = "application/json";
|
|
66
85
|
}
|
|
67
86
|
if (this.verbose) {
|
|
68
87
|
console.error(`[verbose] POST ${url.toString()}`);
|
|
69
88
|
}
|
|
70
|
-
const response = await
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!response.ok) {
|
|
80
|
-
const text = await response.text();
|
|
81
|
-
throw new OpenSeaAPIError(response.status, text, path);
|
|
82
|
-
}
|
|
89
|
+
const response = await this.fetchWithRetry(
|
|
90
|
+
url.toString(),
|
|
91
|
+
{
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers,
|
|
94
|
+
body: body ? JSON.stringify(body) : void 0
|
|
95
|
+
},
|
|
96
|
+
path
|
|
97
|
+
);
|
|
83
98
|
return response.json();
|
|
84
99
|
}
|
|
85
100
|
getDefaultChain() {
|
|
86
101
|
return this.defaultChain;
|
|
87
102
|
}
|
|
103
|
+
getApiKeyPrefix() {
|
|
104
|
+
if (this.apiKey.length < 8) return "***";
|
|
105
|
+
return `${this.apiKey.slice(0, 4)}...`;
|
|
106
|
+
}
|
|
107
|
+
async fetchWithRetry(url, init, path) {
|
|
108
|
+
for (let attempt = 0; ; attempt++) {
|
|
109
|
+
const response = await fetch(url, {
|
|
110
|
+
...init,
|
|
111
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
112
|
+
});
|
|
113
|
+
if (this.verbose) {
|
|
114
|
+
console.error(`[verbose] ${response.status} ${response.statusText}`);
|
|
115
|
+
}
|
|
116
|
+
if (response.ok) {
|
|
117
|
+
return response;
|
|
118
|
+
}
|
|
119
|
+
const method = init.method ?? "GET";
|
|
120
|
+
if (attempt < this.maxRetries && isRetryableStatus(response.status, method)) {
|
|
121
|
+
const retryAfterMs = parseRetryAfter(
|
|
122
|
+
response.headers.get("Retry-After")
|
|
123
|
+
);
|
|
124
|
+
const backoffMs = this.retryBaseDelay * 2 ** attempt;
|
|
125
|
+
const jitterMs = Math.random() * this.retryBaseDelay;
|
|
126
|
+
const delayMs = Math.max(retryAfterMs ?? 0, backoffMs) + jitterMs;
|
|
127
|
+
if (this.verbose) {
|
|
128
|
+
console.error(
|
|
129
|
+
`[verbose] Retry ${attempt + 1}/${this.maxRetries} after ${Math.round(delayMs)}ms (status ${response.status})`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
await response.body?.cancel();
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
await sleep(delayMs);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const text = await response.text();
|
|
140
|
+
throw new OpenSeaAPIError(response.status, text, path);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
88
143
|
};
|
|
89
144
|
var OpenSeaAPIError = class extends Error {
|
|
90
145
|
constructor(statusCode, responseBody, path) {
|
|
@@ -370,14 +425,24 @@ function formatToon(data) {
|
|
|
370
425
|
}
|
|
371
426
|
|
|
372
427
|
// src/output.ts
|
|
428
|
+
var _outputOptions = {};
|
|
429
|
+
function setOutputOptions(options) {
|
|
430
|
+
_outputOptions = options;
|
|
431
|
+
}
|
|
373
432
|
function formatOutput(data, format) {
|
|
433
|
+
const processed = _outputOptions.fields ? filterFields(data, _outputOptions.fields) : data;
|
|
434
|
+
let result;
|
|
374
435
|
if (format === "table") {
|
|
375
|
-
|
|
436
|
+
result = formatTable(processed);
|
|
437
|
+
} else if (format === "toon") {
|
|
438
|
+
result = formatToon(processed);
|
|
439
|
+
} else {
|
|
440
|
+
result = JSON.stringify(processed, null, 2);
|
|
376
441
|
}
|
|
377
|
-
if (
|
|
378
|
-
|
|
442
|
+
if (_outputOptions.maxLines != null) {
|
|
443
|
+
result = truncateOutput(result, _outputOptions.maxLines);
|
|
379
444
|
}
|
|
380
|
-
return
|
|
445
|
+
return result;
|
|
381
446
|
}
|
|
382
447
|
function formatTable(data) {
|
|
383
448
|
if (Array.isArray(data)) {
|
|
@@ -413,6 +478,31 @@ function formatTable(data) {
|
|
|
413
478
|
}
|
|
414
479
|
return String(data);
|
|
415
480
|
}
|
|
481
|
+
function pickFields(obj, fields) {
|
|
482
|
+
const result = {};
|
|
483
|
+
for (const field of fields) {
|
|
484
|
+
if (field in obj) {
|
|
485
|
+
result[field] = obj[field];
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return result;
|
|
489
|
+
}
|
|
490
|
+
function filterFields(data, fields) {
|
|
491
|
+
if (Array.isArray(data)) {
|
|
492
|
+
return data.map((item) => filterFields(item, fields));
|
|
493
|
+
}
|
|
494
|
+
if (data && typeof data === "object") {
|
|
495
|
+
return pickFields(data, fields);
|
|
496
|
+
}
|
|
497
|
+
return data;
|
|
498
|
+
}
|
|
499
|
+
function truncateOutput(text, maxLines) {
|
|
500
|
+
const lines = text.split("\n");
|
|
501
|
+
if (lines.length <= maxLines) return text;
|
|
502
|
+
const omitted = lines.length - maxLines;
|
|
503
|
+
return lines.slice(0, maxLines).join("\n") + `
|
|
504
|
+
... (${omitted} more line${omitted === 1 ? "" : "s"})`;
|
|
505
|
+
}
|
|
416
506
|
|
|
417
507
|
// src/commands/accounts.ts
|
|
418
508
|
function accountsCommand(getClient2, getFormat2) {
|
|
@@ -549,10 +639,88 @@ function eventsCommand(getClient2, getFormat2) {
|
|
|
549
639
|
return cmd;
|
|
550
640
|
}
|
|
551
641
|
|
|
552
|
-
// src/commands/
|
|
642
|
+
// src/commands/health.ts
|
|
553
643
|
import { Command as Command4 } from "commander";
|
|
644
|
+
|
|
645
|
+
// src/health.ts
|
|
646
|
+
async function checkHealth(client) {
|
|
647
|
+
const keyPrefix = client.getApiKeyPrefix();
|
|
648
|
+
try {
|
|
649
|
+
await client.get("/api/v2/collections", { limit: 1 });
|
|
650
|
+
} catch (error) {
|
|
651
|
+
let message;
|
|
652
|
+
if (error instanceof OpenSeaAPIError) {
|
|
653
|
+
message = error.statusCode === 429 ? "Rate limited: too many requests" : `API error (${error.statusCode}): ${error.responseBody}`;
|
|
654
|
+
} else {
|
|
655
|
+
message = `Network error: ${error.message}`;
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
status: "error",
|
|
659
|
+
key_prefix: keyPrefix,
|
|
660
|
+
authenticated: false,
|
|
661
|
+
rate_limited: error instanceof OpenSeaAPIError && error.statusCode === 429,
|
|
662
|
+
message
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
await client.get("/api/v2/listings/collection/boredapeyachtclub/all", {
|
|
667
|
+
limit: 1
|
|
668
|
+
});
|
|
669
|
+
return {
|
|
670
|
+
status: "ok",
|
|
671
|
+
key_prefix: keyPrefix,
|
|
672
|
+
authenticated: true,
|
|
673
|
+
rate_limited: false,
|
|
674
|
+
message: "Connectivity and authentication are working"
|
|
675
|
+
};
|
|
676
|
+
} catch (error) {
|
|
677
|
+
if (error instanceof OpenSeaAPIError) {
|
|
678
|
+
if (error.statusCode === 429) {
|
|
679
|
+
return {
|
|
680
|
+
status: "error",
|
|
681
|
+
key_prefix: keyPrefix,
|
|
682
|
+
authenticated: false,
|
|
683
|
+
rate_limited: true,
|
|
684
|
+
message: "Rate limited: too many requests"
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
688
|
+
return {
|
|
689
|
+
status: "error",
|
|
690
|
+
key_prefix: keyPrefix,
|
|
691
|
+
authenticated: false,
|
|
692
|
+
rate_limited: false,
|
|
693
|
+
message: `Authentication failed (${error.statusCode}): invalid API key`
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return {
|
|
698
|
+
status: "ok",
|
|
699
|
+
key_prefix: keyPrefix,
|
|
700
|
+
authenticated: false,
|
|
701
|
+
rate_limited: false,
|
|
702
|
+
message: "Connectivity is working but authentication could not be verified"
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/commands/health.ts
|
|
708
|
+
function healthCommand(getClient2, getFormat2) {
|
|
709
|
+
const cmd = new Command4("health").description("Check API connectivity and authentication").action(async () => {
|
|
710
|
+
const client = getClient2();
|
|
711
|
+
const result = await checkHealth(client);
|
|
712
|
+
console.log(formatOutput(result, getFormat2()));
|
|
713
|
+
if (result.status === "error") {
|
|
714
|
+
process.exit(result.rate_limited ? 3 : 1);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
return cmd;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/commands/listings.ts
|
|
721
|
+
import { Command as Command5 } from "commander";
|
|
554
722
|
function listingsCommand(getClient2, getFormat2) {
|
|
555
|
-
const cmd = new
|
|
723
|
+
const cmd = new Command5("listings").description("Query NFT listings");
|
|
556
724
|
cmd.command("all").description("Get all listings for a collection").argument("<collection>", "Collection slug").option("--limit <limit>", "Number of results", "20").option("--next <cursor>", "Pagination cursor").action(
|
|
557
725
|
async (collection, options) => {
|
|
558
726
|
const client = getClient2();
|
|
@@ -584,9 +752,9 @@ function listingsCommand(getClient2, getFormat2) {
|
|
|
584
752
|
}
|
|
585
753
|
|
|
586
754
|
// src/commands/nfts.ts
|
|
587
|
-
import { Command as
|
|
755
|
+
import { Command as Command6 } from "commander";
|
|
588
756
|
function nftsCommand(getClient2, getFormat2) {
|
|
589
|
-
const cmd = new
|
|
757
|
+
const cmd = new Command6("nfts").description("Query NFTs");
|
|
590
758
|
cmd.command("get").description("Get a single NFT").argument("<chain>", "Chain (e.g. ethereum, base)").argument("<contract>", "Contract address").argument("<token-id>", "Token ID").action(async (chain, contract, tokenId) => {
|
|
591
759
|
const client = getClient2();
|
|
592
760
|
const result = await client.get(
|
|
@@ -654,9 +822,9 @@ function nftsCommand(getClient2, getFormat2) {
|
|
|
654
822
|
}
|
|
655
823
|
|
|
656
824
|
// src/commands/offers.ts
|
|
657
|
-
import { Command as
|
|
825
|
+
import { Command as Command7 } from "commander";
|
|
658
826
|
function offersCommand(getClient2, getFormat2) {
|
|
659
|
-
const cmd = new
|
|
827
|
+
const cmd = new Command7("offers").description("Query NFT offers");
|
|
660
828
|
cmd.command("all").description("Get all offers for a collection").argument("<collection>", "Collection slug").option("--limit <limit>", "Number of results", "20").option("--next <cursor>", "Pagination cursor").action(
|
|
661
829
|
async (collection, options) => {
|
|
662
830
|
const client = getClient2();
|
|
@@ -700,9 +868,9 @@ function offersCommand(getClient2, getFormat2) {
|
|
|
700
868
|
}
|
|
701
869
|
|
|
702
870
|
// src/commands/search.ts
|
|
703
|
-
import { Command as
|
|
871
|
+
import { Command as Command8 } from "commander";
|
|
704
872
|
function searchCommand(getClient2, getFormat2) {
|
|
705
|
-
const cmd = new
|
|
873
|
+
const cmd = new Command8("search").description("Search across collections, tokens, NFTs, and accounts").argument("<query>", "Search query").option(
|
|
706
874
|
"--types <types>",
|
|
707
875
|
"Filter by type (comma-separated: collection,nft,token,account)"
|
|
708
876
|
).option("--chains <chains>", "Filter by chains (comma-separated)").option("--limit <limit>", "Number of results", "20").action(
|
|
@@ -729,9 +897,9 @@ function searchCommand(getClient2, getFormat2) {
|
|
|
729
897
|
}
|
|
730
898
|
|
|
731
899
|
// src/commands/swaps.ts
|
|
732
|
-
import { Command as
|
|
900
|
+
import { Command as Command9 } from "commander";
|
|
733
901
|
function swapsCommand(getClient2, getFormat2) {
|
|
734
|
-
const cmd = new
|
|
902
|
+
const cmd = new Command9("swaps").description(
|
|
735
903
|
"Get swap quotes for token trading"
|
|
736
904
|
);
|
|
737
905
|
cmd.command("quote").description(
|
|
@@ -771,9 +939,9 @@ function swapsCommand(getClient2, getFormat2) {
|
|
|
771
939
|
}
|
|
772
940
|
|
|
773
941
|
// src/commands/tokens.ts
|
|
774
|
-
import { Command as
|
|
942
|
+
import { Command as Command10 } from "commander";
|
|
775
943
|
function tokensCommand(getClient2, getFormat2) {
|
|
776
|
-
const cmd = new
|
|
944
|
+
const cmd = new Command10("tokens").description(
|
|
777
945
|
"Query trending tokens, top tokens, and token details"
|
|
778
946
|
);
|
|
779
947
|
cmd.command("trending").description("Get trending tokens based on OpenSea's trending score").option("--chains <chains>", "Comma-separated list of chains to filter by").option("--limit <limit>", "Number of results (max 100)", "20").option("--next <cursor>", "Pagination cursor").action(
|
|
@@ -817,6 +985,9 @@ function tokensCommand(getClient2, getFormat2) {
|
|
|
817
985
|
}
|
|
818
986
|
|
|
819
987
|
// src/cli.ts
|
|
988
|
+
var EXIT_API_ERROR = 1;
|
|
989
|
+
var EXIT_AUTH_ERROR = 2;
|
|
990
|
+
var EXIT_RATE_LIMITED = 3;
|
|
820
991
|
var BANNER = `
|
|
821
992
|
____ _____
|
|
822
993
|
/ __ \\ / ____|
|
|
@@ -827,8 +998,11 @@ var BANNER = `
|
|
|
827
998
|
| |
|
|
828
999
|
|_|
|
|
829
1000
|
`;
|
|
830
|
-
var program = new
|
|
831
|
-
program.name("opensea").description("OpenSea CLI - Query the OpenSea API from the command line").version(process.env.npm_package_version ?? "0.0.0").addHelpText("before", BANNER).option("--api-key <key>", "OpenSea API key (or set OPENSEA_API_KEY env var)").option("--chain <chain>", "Default chain", "ethereum").option("--format <format>", "Output format (json, table, or toon)", "json").option("--base-url <url>", "API base URL").option("--timeout <ms>", "Request timeout in milliseconds", "30000").option("--verbose", "Log request and response info to stderr")
|
|
1001
|
+
var program = new Command11();
|
|
1002
|
+
program.name("opensea").description("OpenSea CLI - Query the OpenSea API from the command line").version(process.env.npm_package_version ?? "0.0.0").addHelpText("before", BANNER).option("--api-key <key>", "OpenSea API key (or set OPENSEA_API_KEY env var)").option("--chain <chain>", "Default chain", "ethereum").option("--format <format>", "Output format (json, table, or toon)", "json").option("--base-url <url>", "API base URL").option("--timeout <ms>", "Request timeout in milliseconds", "30000").option("--verbose", "Log request and response info to stderr").option(
|
|
1003
|
+
"--fields <fields>",
|
|
1004
|
+
"Comma-separated list of fields to include in output"
|
|
1005
|
+
).option("--max-lines <lines>", "Truncate output after N lines").option("--max-retries <n>", "Max retries on 429/5xx (0 to disable)", "3").option("--no-retry", "Disable request retries");
|
|
832
1006
|
function getClient() {
|
|
833
1007
|
const opts = program.opts();
|
|
834
1008
|
const apiKey = opts.apiKey ?? process.env.OPENSEA_API_KEY;
|
|
@@ -836,14 +1010,16 @@ function getClient() {
|
|
|
836
1010
|
console.error(
|
|
837
1011
|
"Error: API key required. Use --api-key or set OPENSEA_API_KEY environment variable."
|
|
838
1012
|
);
|
|
839
|
-
process.exit(
|
|
1013
|
+
process.exit(EXIT_AUTH_ERROR);
|
|
840
1014
|
}
|
|
1015
|
+
const maxRetries = opts.retry ? parseIntOption(opts.maxRetries, "--max-retries") : 0;
|
|
841
1016
|
return new OpenSeaClient({
|
|
842
1017
|
apiKey,
|
|
843
1018
|
chain: opts.chain,
|
|
844
1019
|
baseUrl: opts.baseUrl,
|
|
845
1020
|
timeout: parseIntOption(opts.timeout, "--timeout"),
|
|
846
|
-
verbose: opts.verbose
|
|
1021
|
+
verbose: opts.verbose,
|
|
1022
|
+
maxRetries
|
|
847
1023
|
});
|
|
848
1024
|
}
|
|
849
1025
|
function getFormat() {
|
|
@@ -852,6 +1028,21 @@ function getFormat() {
|
|
|
852
1028
|
if (opts.format === "toon") return "toon";
|
|
853
1029
|
return "json";
|
|
854
1030
|
}
|
|
1031
|
+
program.hook("preAction", () => {
|
|
1032
|
+
const opts = program.opts();
|
|
1033
|
+
let maxLines;
|
|
1034
|
+
if (opts.maxLines) {
|
|
1035
|
+
maxLines = parseIntOption(opts.maxLines, "--max-lines");
|
|
1036
|
+
if (maxLines < 1) {
|
|
1037
|
+
console.error("Error: --max-lines must be >= 1");
|
|
1038
|
+
process.exit(2);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
setOutputOptions({
|
|
1042
|
+
fields: opts.fields?.split(",").map((f) => f.trim()),
|
|
1043
|
+
maxLines
|
|
1044
|
+
});
|
|
1045
|
+
});
|
|
855
1046
|
program.addCommand(collectionsCommand(getClient, getFormat));
|
|
856
1047
|
program.addCommand(nftsCommand(getClient, getFormat));
|
|
857
1048
|
program.addCommand(listingsCommand(getClient, getFormat));
|
|
@@ -861,15 +1052,17 @@ program.addCommand(accountsCommand(getClient, getFormat));
|
|
|
861
1052
|
program.addCommand(tokensCommand(getClient, getFormat));
|
|
862
1053
|
program.addCommand(searchCommand(getClient, getFormat));
|
|
863
1054
|
program.addCommand(swapsCommand(getClient, getFormat));
|
|
1055
|
+
program.addCommand(healthCommand(getClient, getFormat));
|
|
864
1056
|
async function main() {
|
|
865
1057
|
try {
|
|
866
1058
|
await program.parseAsync(process.argv);
|
|
867
1059
|
} catch (error) {
|
|
868
1060
|
if (error instanceof OpenSeaAPIError) {
|
|
1061
|
+
const isRateLimited = error.statusCode === 429;
|
|
869
1062
|
console.error(
|
|
870
1063
|
JSON.stringify(
|
|
871
1064
|
{
|
|
872
|
-
error: "API Error",
|
|
1065
|
+
error: isRateLimited ? "Rate Limited" : "API Error",
|
|
873
1066
|
status: error.statusCode,
|
|
874
1067
|
path: error.path,
|
|
875
1068
|
message: error.responseBody
|
|
@@ -878,7 +1071,7 @@ async function main() {
|
|
|
878
1071
|
2
|
|
879
1072
|
)
|
|
880
1073
|
);
|
|
881
|
-
process.exit(
|
|
1074
|
+
process.exit(isRateLimited ? EXIT_RATE_LIMITED : EXIT_API_ERROR);
|
|
882
1075
|
}
|
|
883
1076
|
const label = error instanceof TypeError ? "Network Error" : error.name;
|
|
884
1077
|
console.error(
|
|
@@ -891,7 +1084,7 @@ async function main() {
|
|
|
891
1084
|
2
|
|
892
1085
|
)
|
|
893
1086
|
);
|
|
894
|
-
process.exit(
|
|
1087
|
+
process.exit(EXIT_API_ERROR);
|
|
895
1088
|
}
|
|
896
1089
|
}
|
|
897
1090
|
main();
|