@opensea/cli 0.2.1 → 0.4.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/dist/cli.js CHANGED
@@ -6,16 +6,21 @@ import { Command as Command10 } from "commander";
6
6
  // src/client.ts
7
7
  var DEFAULT_BASE_URL = "https://api.opensea.io";
8
8
  var DEFAULT_GRAPHQL_URL = "https://gql.opensea.io/graphql";
9
+ var DEFAULT_TIMEOUT_MS = 3e4;
9
10
  var OpenSeaClient = class {
10
11
  apiKey;
11
12
  baseUrl;
12
13
  graphqlUrl;
13
14
  defaultChain;
15
+ timeoutMs;
16
+ verbose;
14
17
  constructor(config) {
15
18
  this.apiKey = config.apiKey;
16
19
  this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
17
20
  this.graphqlUrl = config.graphqlUrl ?? DEFAULT_GRAPHQL_URL;
18
21
  this.defaultChain = config.chain ?? "ethereum";
22
+ this.timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
23
+ this.verbose = config.verbose ?? false;
19
24
  }
20
25
  async get(path, params) {
21
26
  const url = new URL(`${this.baseUrl}${path}`);
@@ -26,35 +31,64 @@ var OpenSeaClient = class {
26
31
  }
27
32
  }
28
33
  }
34
+ if (this.verbose) {
35
+ console.error(`[verbose] GET ${url.toString()}`);
36
+ }
29
37
  const response = await fetch(url.toString(), {
30
38
  method: "GET",
31
39
  headers: {
32
40
  Accept: "application/json",
33
41
  "x-api-key": this.apiKey
34
- }
42
+ },
43
+ signal: AbortSignal.timeout(this.timeoutMs)
35
44
  });
45
+ if (this.verbose) {
46
+ console.error(`[verbose] ${response.status} ${response.statusText}`);
47
+ }
36
48
  if (!response.ok) {
37
49
  const body = await response.text();
38
50
  throw new OpenSeaAPIError(response.status, body, path);
39
51
  }
40
52
  return response.json();
41
53
  }
42
- async post(path) {
54
+ async post(path, body, params) {
43
55
  const url = new URL(`${this.baseUrl}${path}`);
56
+ if (params) {
57
+ for (const [key, value] of Object.entries(params)) {
58
+ if (value !== void 0 && value !== null) {
59
+ url.searchParams.set(key, String(value));
60
+ }
61
+ }
62
+ }
63
+ const headers = {
64
+ Accept: "application/json",
65
+ "x-api-key": this.apiKey
66
+ };
67
+ if (body) {
68
+ headers["Content-Type"] = "application/json";
69
+ }
70
+ if (this.verbose) {
71
+ console.error(`[verbose] POST ${url.toString()}`);
72
+ }
44
73
  const response = await fetch(url.toString(), {
45
74
  method: "POST",
46
- headers: {
47
- Accept: "application/json",
48
- "x-api-key": this.apiKey
49
- }
75
+ headers,
76
+ body: body ? JSON.stringify(body) : void 0,
77
+ signal: AbortSignal.timeout(this.timeoutMs)
50
78
  });
79
+ if (this.verbose) {
80
+ console.error(`[verbose] ${response.status} ${response.statusText}`);
81
+ }
51
82
  if (!response.ok) {
52
- const body = await response.text();
53
- throw new OpenSeaAPIError(response.status, body, path);
83
+ const text = await response.text();
84
+ throw new OpenSeaAPIError(response.status, text, path);
54
85
  }
55
86
  return response.json();
56
87
  }
57
88
  async graphql(query, variables) {
89
+ if (this.verbose) {
90
+ console.error(`[verbose] POST ${this.graphqlUrl}`);
91
+ }
58
92
  const response = await fetch(this.graphqlUrl, {
59
93
  method: "POST",
60
94
  headers: {
@@ -62,8 +96,12 @@ var OpenSeaClient = class {
62
96
  Accept: "application/json",
63
97
  "x-api-key": this.apiKey
64
98
  },
65
- body: JSON.stringify({ query, variables })
99
+ body: JSON.stringify({ query, variables }),
100
+ signal: AbortSignal.timeout(this.timeoutMs)
66
101
  });
102
+ if (this.verbose) {
103
+ console.error(`[verbose] ${response.status} ${response.statusText}`);
104
+ }
67
105
  if (!response.ok) {
68
106
  const body = await response.text();
69
107
  throw new OpenSeaAPIError(response.status, body, "graphql");
@@ -98,11 +136,284 @@ var OpenSeaAPIError = class extends Error {
98
136
  // src/commands/accounts.ts
99
137
  import { Command } from "commander";
100
138
 
139
+ // src/toon.ts
140
+ var INDENT = " ";
141
+ var NUMERIC_RE = /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i;
142
+ var LEADING_ZERO_RE = /^0\d+$/;
143
+ var UNQUOTED_KEY_RE = /^[A-Za-z_][A-Za-z0-9_.]*$/;
144
+ function escapeString(s) {
145
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
146
+ }
147
+ function needsQuoting(value, delimiter) {
148
+ if (value === "") return true;
149
+ if (value !== value.trim()) return true;
150
+ if (value === "true" || value === "false" || value === "null") return true;
151
+ if (NUMERIC_RE.test(value) || LEADING_ZERO_RE.test(value)) return true;
152
+ if (/[:"\\[\]{}]/.test(value)) return true;
153
+ if (/[\n\r\t]/.test(value)) return true;
154
+ if (value.includes(delimiter)) return true;
155
+ if (value.startsWith("-")) return true;
156
+ return false;
157
+ }
158
+ function encodeKey(key) {
159
+ if (UNQUOTED_KEY_RE.test(key)) return key;
160
+ return `"${escapeString(key)}"`;
161
+ }
162
+ function encodePrimitive(value, delimiter) {
163
+ if (value === null) return "null";
164
+ if (value === void 0) return "null";
165
+ if (typeof value === "boolean") return String(value);
166
+ if (typeof value === "number") return String(value);
167
+ if (typeof value === "string") {
168
+ if (needsQuoting(value, delimiter)) {
169
+ return `"${escapeString(value)}"`;
170
+ }
171
+ return value;
172
+ }
173
+ return `"${escapeString(String(value))}"`;
174
+ }
175
+ function isPrimitive(value) {
176
+ return value === null || value === void 0 || typeof value === "boolean" || typeof value === "number" || typeof value === "string";
177
+ }
178
+ function isTabular(arr) {
179
+ if (arr.length === 0) return false;
180
+ const first = arr[0];
181
+ if (first === null || typeof first !== "object" || Array.isArray(first))
182
+ return false;
183
+ const keys = Object.keys(first).sort();
184
+ for (const item of arr) {
185
+ if (item === null || typeof item !== "object" || Array.isArray(item))
186
+ return false;
187
+ const itemKeys = Object.keys(item).sort();
188
+ if (itemKeys.length !== keys.length) return false;
189
+ for (let i = 0; i < keys.length; i++) {
190
+ if (itemKeys[i] !== keys[i]) return false;
191
+ }
192
+ for (const k of keys) {
193
+ if (!isPrimitive(item[k])) return false;
194
+ }
195
+ }
196
+ return true;
197
+ }
198
+ function isPrimitiveArray(arr) {
199
+ return arr.every(isPrimitive);
200
+ }
201
+ function encodeValue(value, depth, delimiter) {
202
+ if (isPrimitive(value)) {
203
+ return encodePrimitive(value, delimiter);
204
+ }
205
+ if (Array.isArray(value)) {
206
+ return encodeArray(value, depth, delimiter);
207
+ }
208
+ if (typeof value === "object" && value !== null) {
209
+ return encodeObject(value, depth, delimiter);
210
+ }
211
+ return encodePrimitive(value, delimiter);
212
+ }
213
+ function encodeObject(obj, depth, delimiter) {
214
+ const entries = Object.entries(obj);
215
+ if (entries.length === 0) return "";
216
+ const lines = [];
217
+ const prefix = INDENT.repeat(depth);
218
+ for (const [key, value] of entries) {
219
+ const encodedKey = encodeKey(key);
220
+ if (isPrimitive(value)) {
221
+ lines.push(`${prefix}${encodedKey}: ${encodePrimitive(value, delimiter)}`);
222
+ } else if (Array.isArray(value)) {
223
+ lines.push(encodeArrayField(encodedKey, value, depth, delimiter));
224
+ } else if (typeof value === "object" && value !== null) {
225
+ const nested = value;
226
+ if (Object.keys(nested).length === 0) {
227
+ lines.push(`${prefix}${encodedKey}:`);
228
+ } else {
229
+ lines.push(`${prefix}${encodedKey}:`);
230
+ lines.push(encodeObject(nested, depth + 1, delimiter));
231
+ }
232
+ }
233
+ }
234
+ return lines.join("\n");
235
+ }
236
+ function encodeArrayField(encodedKey, arr, depth, delimiter) {
237
+ const prefix = INDENT.repeat(depth);
238
+ if (arr.length === 0) {
239
+ return `${prefix}${encodedKey}[0]:`;
240
+ }
241
+ if (isPrimitiveArray(arr)) {
242
+ const values = arr.map((v) => encodePrimitive(v, delimiter)).join(delimiter);
243
+ return `${prefix}${encodedKey}[${arr.length}]: ${values}`;
244
+ }
245
+ if (isTabular(arr)) {
246
+ const firstObj = arr[0];
247
+ const fields = Object.keys(firstObj);
248
+ const fieldHeader = fields.map(encodeKey).join(delimiter);
249
+ const lines = [];
250
+ lines.push(`${prefix}${encodedKey}[${arr.length}]{${fieldHeader}}:`);
251
+ const rowPrefix = INDENT.repeat(depth + 1);
252
+ for (const item of arr) {
253
+ const obj = item;
254
+ const row = fields.map((f) => encodePrimitive(obj[f], delimiter)).join(delimiter);
255
+ lines.push(`${rowPrefix}${row}`);
256
+ }
257
+ return lines.join("\n");
258
+ }
259
+ return encodeExpandedList(encodedKey, arr, depth, delimiter);
260
+ }
261
+ function encodeExpandedList(encodedKey, arr, depth, delimiter) {
262
+ const prefix = INDENT.repeat(depth);
263
+ const itemPrefix = INDENT.repeat(depth + 1);
264
+ const lines = [];
265
+ lines.push(`${prefix}${encodedKey}[${arr.length}]:`);
266
+ for (const item of arr) {
267
+ if (isPrimitive(item)) {
268
+ lines.push(`${itemPrefix}- ${encodePrimitive(item, delimiter)}`);
269
+ } else if (Array.isArray(item)) {
270
+ if (isPrimitiveArray(item)) {
271
+ const values = item.map((v) => encodePrimitive(v, delimiter)).join(delimiter);
272
+ lines.push(`${itemPrefix}- [${item.length}]: ${values}`);
273
+ } else {
274
+ lines.push(`${itemPrefix}- [${item.length}]:`);
275
+ for (const inner of item) {
276
+ lines.push(encodeValue(inner, depth + 2, delimiter));
277
+ }
278
+ }
279
+ } else if (typeof item === "object" && item !== null) {
280
+ const obj = item;
281
+ const entries = Object.entries(obj);
282
+ if (entries.length === 0) {
283
+ lines.push(`${itemPrefix}-`);
284
+ } else {
285
+ const [firstKey, firstValue] = entries[0];
286
+ const ek = encodeKey(firstKey);
287
+ if (Array.isArray(firstValue)) {
288
+ const arrayLine = encodeArrayField(ek, firstValue, 0, delimiter);
289
+ lines.push(`${itemPrefix}- ${arrayLine.trimStart()}`);
290
+ } else if (isPrimitive(firstValue)) {
291
+ lines.push(
292
+ `${itemPrefix}- ${ek}: ${encodePrimitive(firstValue, delimiter)}`
293
+ );
294
+ } else {
295
+ lines.push(`${itemPrefix}- ${ek}:`);
296
+ lines.push(
297
+ encodeObject(
298
+ firstValue,
299
+ depth + 2,
300
+ delimiter
301
+ )
302
+ );
303
+ }
304
+ for (let i = 1; i < entries.length; i++) {
305
+ const [k, v] = entries[i];
306
+ const encodedK = encodeKey(k);
307
+ if (isPrimitive(v)) {
308
+ lines.push(
309
+ `${INDENT.repeat(depth + 2)}${encodedK}: ${encodePrimitive(v, delimiter)}`
310
+ );
311
+ } else if (Array.isArray(v)) {
312
+ lines.push(encodeArrayField(encodedK, v, depth + 2, delimiter));
313
+ } else if (typeof v === "object" && v !== null) {
314
+ lines.push(`${INDENT.repeat(depth + 2)}${encodedK}:`);
315
+ lines.push(
316
+ encodeObject(v, depth + 3, delimiter)
317
+ );
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+ return lines.join("\n");
324
+ }
325
+ function encodeArray(arr, depth, delimiter) {
326
+ const prefix = INDENT.repeat(depth);
327
+ if (arr.length === 0) {
328
+ return `${prefix}[0]:`;
329
+ }
330
+ if (isPrimitiveArray(arr)) {
331
+ const values = arr.map((v) => encodePrimitive(v, delimiter)).join(delimiter);
332
+ return `${prefix}[${arr.length}]: ${values}`;
333
+ }
334
+ if (isTabular(arr)) {
335
+ const firstObj = arr[0];
336
+ const fields = Object.keys(firstObj);
337
+ const fieldHeader = fields.map(encodeKey).join(delimiter);
338
+ const lines2 = [];
339
+ lines2.push(`${prefix}[${arr.length}]{${fieldHeader}}:`);
340
+ const rowPrefix = INDENT.repeat(depth + 1);
341
+ for (const item of arr) {
342
+ const obj = item;
343
+ const row = fields.map((f) => encodePrimitive(obj[f], delimiter)).join(delimiter);
344
+ lines2.push(`${rowPrefix}${row}`);
345
+ }
346
+ return lines2.join("\n");
347
+ }
348
+ const lines = [];
349
+ lines.push(`${prefix}[${arr.length}]:`);
350
+ const itemPrefix = INDENT.repeat(depth + 1);
351
+ for (const item of arr) {
352
+ if (isPrimitive(item)) {
353
+ lines.push(`${itemPrefix}- ${encodePrimitive(item, delimiter)}`);
354
+ } else if (Array.isArray(item)) {
355
+ if (isPrimitiveArray(item)) {
356
+ const values = item.map((v) => encodePrimitive(v, delimiter)).join(delimiter);
357
+ lines.push(`${itemPrefix}- [${item.length}]: ${values}`);
358
+ } else {
359
+ lines.push(`${itemPrefix}- [${item.length}]:`);
360
+ }
361
+ } else if (typeof item === "object" && item !== null) {
362
+ const obj = item;
363
+ const entries = Object.entries(obj);
364
+ if (entries.length > 0) {
365
+ const [firstKey, firstValue] = entries[0];
366
+ const ek = encodeKey(firstKey);
367
+ if (isPrimitive(firstValue)) {
368
+ lines.push(
369
+ `${itemPrefix}- ${ek}: ${encodePrimitive(firstValue, delimiter)}`
370
+ );
371
+ } else {
372
+ lines.push(`${itemPrefix}- ${ek}:`);
373
+ lines.push(encodeValue(firstValue, depth + 2, delimiter));
374
+ }
375
+ for (let i = 1; i < entries.length; i++) {
376
+ const [k, v] = entries[i];
377
+ const encodedK = encodeKey(k);
378
+ if (isPrimitive(v)) {
379
+ lines.push(
380
+ `${INDENT.repeat(depth + 2)}${encodedK}: ${encodePrimitive(v, delimiter)}`
381
+ );
382
+ } else if (Array.isArray(v)) {
383
+ lines.push(encodeArrayField(encodedK, v, depth + 2, delimiter));
384
+ } else if (typeof v === "object" && v !== null) {
385
+ lines.push(`${INDENT.repeat(depth + 2)}${encodedK}:`);
386
+ lines.push(
387
+ encodeObject(v, depth + 3, delimiter)
388
+ );
389
+ }
390
+ }
391
+ }
392
+ }
393
+ }
394
+ return lines.join("\n");
395
+ }
396
+ function formatToon(data) {
397
+ if (isPrimitive(data)) {
398
+ return encodePrimitive(data, ",");
399
+ }
400
+ if (Array.isArray(data)) {
401
+ return encodeArray(data, 0, ",");
402
+ }
403
+ if (typeof data === "object" && data !== null) {
404
+ return encodeObject(data, 0, ",");
405
+ }
406
+ return String(data);
407
+ }
408
+
101
409
  // src/output.ts
102
410
  function formatOutput(data, format) {
103
411
  if (format === "table") {
104
412
  return formatTable(data);
105
413
  }
414
+ if (format === "toon") {
415
+ return formatToon(data);
416
+ }
106
417
  return JSON.stringify(data, null, 2);
107
418
  }
108
419
  function formatTable(data) {
@@ -130,6 +441,7 @@ function formatTable(data) {
130
441
  }
131
442
  if (data && typeof data === "object") {
132
443
  const entries = Object.entries(data);
444
+ if (entries.length === 0) return "(empty)";
133
445
  const maxKeyLength = Math.max(...entries.map(([k]) => k.length));
134
446
  return entries.map(([key, value]) => {
135
447
  const displayValue = typeof value === "object" && value !== null ? JSON.stringify(value) : String(value ?? "");
@@ -152,6 +464,24 @@ function accountsCommand(getClient2, getFormat2) {
152
464
 
153
465
  // src/commands/collections.ts
154
466
  import { Command as Command2 } from "commander";
467
+
468
+ // src/parse.ts
469
+ function parseIntOption(value, name) {
470
+ const parsed = Number.parseInt(value, 10);
471
+ if (Number.isNaN(parsed)) {
472
+ throw new Error(`Invalid value for ${name}: "${value}" is not an integer`);
473
+ }
474
+ return parsed;
475
+ }
476
+ function parseFloatOption(value, name) {
477
+ const parsed = Number.parseFloat(value);
478
+ if (Number.isNaN(parsed)) {
479
+ throw new Error(`Invalid value for ${name}: "${value}" is not a number`);
480
+ }
481
+ return parsed;
482
+ }
483
+
484
+ // src/commands/collections.ts
155
485
  function collectionsCommand(getClient2, getFormat2) {
156
486
  const cmd = new Command2("collections").description(
157
487
  "Manage and query NFT collections"
@@ -172,7 +502,7 @@ function collectionsCommand(getClient2, getFormat2) {
172
502
  order_by: options.orderBy,
173
503
  creator_username: options.creator,
174
504
  include_hidden: options.includeHidden,
175
- limit: Number.parseInt(options.limit, 10),
505
+ limit: parseIntOption(options.limit, "--limit"),
176
506
  next: options.next
177
507
  });
178
508
  console.log(formatOutput(result, getFormat2()));
@@ -207,10 +537,10 @@ function eventsCommand(getClient2, getFormat2) {
207
537
  const client = getClient2();
208
538
  const result = await client.get("/api/v2/events", {
209
539
  event_type: options.eventType,
210
- after: options.after ? Number.parseInt(options.after, 10) : void 0,
211
- before: options.before ? Number.parseInt(options.before, 10) : void 0,
540
+ after: options.after ? parseIntOption(options.after, "--after") : void 0,
541
+ before: options.before ? parseIntOption(options.before, "--before") : void 0,
212
542
  chain: options.chain,
213
- limit: Number.parseInt(options.limit, 10),
543
+ limit: parseIntOption(options.limit, "--limit"),
214
544
  next: options.next
215
545
  });
216
546
  console.log(formatOutput(result, getFormat2()));
@@ -222,7 +552,7 @@ function eventsCommand(getClient2, getFormat2) {
222
552
  const result = await client.get(`/api/v2/events/accounts/${address}`, {
223
553
  event_type: options.eventType,
224
554
  chain: options.chain,
225
- limit: Number.parseInt(options.limit, 10),
555
+ limit: parseIntOption(options.limit, "--limit"),
226
556
  next: options.next
227
557
  });
228
558
  console.log(formatOutput(result, getFormat2()));
@@ -233,7 +563,7 @@ function eventsCommand(getClient2, getFormat2) {
233
563
  const client = getClient2();
234
564
  const result = await client.get(`/api/v2/events/collection/${slug}`, {
235
565
  event_type: options.eventType,
236
- limit: Number.parseInt(options.limit, 10),
566
+ limit: parseIntOption(options.limit, "--limit"),
237
567
  next: options.next
238
568
  });
239
569
  console.log(formatOutput(result, getFormat2()));
@@ -246,7 +576,7 @@ function eventsCommand(getClient2, getFormat2) {
246
576
  `/api/v2/events/chain/${chain}/contract/${contract}/nfts/${tokenId}`,
247
577
  {
248
578
  event_type: options.eventType,
249
- limit: Number.parseInt(options.limit, 10),
579
+ limit: parseIntOption(options.limit, "--limit"),
250
580
  next: options.next
251
581
  }
252
582
  );
@@ -264,7 +594,7 @@ function listingsCommand(getClient2, getFormat2) {
264
594
  async (collection, options) => {
265
595
  const client = getClient2();
266
596
  const result = await client.get(`/api/v2/listings/collection/${collection}/all`, {
267
- limit: Number.parseInt(options.limit, 10),
597
+ limit: parseIntOption(options.limit, "--limit"),
268
598
  next: options.next
269
599
  });
270
600
  console.log(formatOutput(result, getFormat2()));
@@ -274,7 +604,7 @@ function listingsCommand(getClient2, getFormat2) {
274
604
  async (collection, options) => {
275
605
  const client = getClient2();
276
606
  const result = await client.get(`/api/v2/listings/collection/${collection}/best`, {
277
- limit: Number.parseInt(options.limit, 10),
607
+ limit: parseIntOption(options.limit, "--limit"),
278
608
  next: options.next
279
609
  });
280
610
  console.log(formatOutput(result, getFormat2()));
@@ -306,7 +636,7 @@ function nftsCommand(getClient2, getFormat2) {
306
636
  const result = await client.get(
307
637
  `/api/v2/collection/${slug}/nfts`,
308
638
  {
309
- limit: Number.parseInt(options.limit, 10),
639
+ limit: parseIntOption(options.limit, "--limit"),
310
640
  next: options.next
311
641
  }
312
642
  );
@@ -318,7 +648,7 @@ function nftsCommand(getClient2, getFormat2) {
318
648
  const result = await client.get(
319
649
  `/api/v2/chain/${chain}/contract/${contract}/nfts`,
320
650
  {
321
- limit: Number.parseInt(options.limit, 10),
651
+ limit: parseIntOption(options.limit, "--limit"),
322
652
  next: options.next
323
653
  }
324
654
  );
@@ -331,7 +661,7 @@ function nftsCommand(getClient2, getFormat2) {
331
661
  const result = await client.get(
332
662
  `/api/v2/chain/${chain}/account/${address}/nfts`,
333
663
  {
334
- limit: Number.parseInt(options.limit, 10),
664
+ limit: parseIntOption(options.limit, "--limit"),
335
665
  next: options.next
336
666
  }
337
667
  );
@@ -368,7 +698,7 @@ function offersCommand(getClient2, getFormat2) {
368
698
  async (collection, options) => {
369
699
  const client = getClient2();
370
700
  const result = await client.get(`/api/v2/offers/collection/${collection}/all`, {
371
- limit: Number.parseInt(options.limit, 10),
701
+ limit: parseIntOption(options.limit, "--limit"),
372
702
  next: options.next
373
703
  });
374
704
  console.log(formatOutput(result, getFormat2()));
@@ -378,7 +708,7 @@ function offersCommand(getClient2, getFormat2) {
378
708
  async (collection, options) => {
379
709
  const client = getClient2();
380
710
  const result = await client.get(`/api/v2/offers/collection/${collection}`, {
381
- limit: Number.parseInt(options.limit, 10),
711
+ limit: parseIntOption(options.limit, "--limit"),
382
712
  next: options.next
383
713
  });
384
714
  console.log(formatOutput(result, getFormat2()));
@@ -397,7 +727,7 @@ function offersCommand(getClient2, getFormat2) {
397
727
  const result = await client.get(`/api/v2/offers/collection/${collection}/traits`, {
398
728
  type: options.type,
399
729
  value: options.value,
400
- limit: Number.parseInt(options.limit, 10),
730
+ limit: parseIntOption(options.limit, "--limit"),
401
731
  next: options.next
402
732
  });
403
733
  console.log(formatOutput(result, getFormat2()));
@@ -512,7 +842,7 @@ function searchCommand(getClient2, getFormat2) {
512
842
  const client = getClient2();
513
843
  const result = await client.graphql(SEARCH_COLLECTIONS_QUERY, {
514
844
  query,
515
- limit: Number.parseInt(options.limit, 10),
845
+ limit: parseIntOption(options.limit, "--limit"),
516
846
  chains: options.chains?.split(",")
517
847
  });
518
848
  console.log(formatOutput(result.collectionsByQuery, getFormat2()));
@@ -524,7 +854,7 @@ function searchCommand(getClient2, getFormat2) {
524
854
  const result = await client.graphql(SEARCH_NFTS_QUERY, {
525
855
  query,
526
856
  collectionSlug: options.collection,
527
- limit: Number.parseInt(options.limit, 10),
857
+ limit: parseIntOption(options.limit, "--limit"),
528
858
  chains: options.chains?.split(",")
529
859
  });
530
860
  console.log(formatOutput(result.itemsByQuery, getFormat2()));
@@ -535,7 +865,7 @@ function searchCommand(getClient2, getFormat2) {
535
865
  const client = getClient2();
536
866
  const result = await client.graphql(SEARCH_TOKENS_QUERY, {
537
867
  query,
538
- limit: Number.parseInt(options.limit, 10),
868
+ limit: parseIntOption(options.limit, "--limit"),
539
869
  chain: options.chain
540
870
  });
541
871
  console.log(formatOutput(result.currenciesByQuery, getFormat2()));
@@ -545,7 +875,7 @@ function searchCommand(getClient2, getFormat2) {
545
875
  const client = getClient2();
546
876
  const result = await client.graphql(SEARCH_ACCOUNTS_QUERY, {
547
877
  query,
548
- limit: Number.parseInt(options.limit, 10)
878
+ limit: parseIntOption(options.limit, "--limit")
549
879
  });
550
880
  console.log(formatOutput(result.accountsByQuery, getFormat2()));
551
881
  });
@@ -584,7 +914,7 @@ function swapsCommand(getClient2, getFormat2) {
584
914
  to_address: options.toAddress,
585
915
  quantity: options.quantity,
586
916
  address: options.address,
587
- slippage: options.slippage ? Number.parseFloat(options.slippage) : void 0,
917
+ slippage: options.slippage ? parseFloatOption(options.slippage, "--slippage") : void 0,
588
918
  recipient: options.recipient
589
919
  }
590
920
  );
@@ -600,29 +930,31 @@ function tokensCommand(getClient2, getFormat2) {
600
930
  const cmd = new Command9("tokens").description(
601
931
  "Query trending tokens, top tokens, and token details"
602
932
  );
603
- 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("--cursor <cursor>", "Pagination cursor").action(
933
+ 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(
604
934
  async (options) => {
605
935
  const client = getClient2();
606
936
  const result = await client.get(
607
937
  "/api/v2/tokens/trending",
608
938
  {
609
939
  chains: options.chains,
610
- limit: Number.parseInt(options.limit, 10),
611
- cursor: options.cursor
940
+ limit: parseIntOption(options.limit, "--limit"),
941
+ // Tokens API uses "cursor" instead of "next" as the query param
942
+ cursor: options.next
612
943
  }
613
944
  );
614
945
  console.log(formatOutput(result, getFormat2()));
615
946
  }
616
947
  );
617
- cmd.command("top").description("Get top tokens ranked by 24-hour trading volume").option("--chains <chains>", "Comma-separated list of chains to filter by").option("--limit <limit>", "Number of results (max 100)", "20").option("--cursor <cursor>", "Pagination cursor").action(
948
+ cmd.command("top").description("Get top tokens ranked by 24-hour trading volume").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(
618
949
  async (options) => {
619
950
  const client = getClient2();
620
951
  const result = await client.get(
621
952
  "/api/v2/tokens/top",
622
953
  {
623
954
  chains: options.chains,
624
- limit: Number.parseInt(options.limit, 10),
625
- cursor: options.cursor
955
+ limit: parseIntOption(options.limit, "--limit"),
956
+ // Tokens API uses "cursor" instead of "next" as the query param
957
+ cursor: options.next
626
958
  }
627
959
  );
628
960
  console.log(formatOutput(result, getFormat2()));
@@ -650,7 +982,7 @@ var BANNER = `
650
982
  |_|
651
983
  `;
652
984
  var program = new Command10();
653
- 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 or table)", "json").option("--base-url <url>", "API base URL");
985
+ 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");
654
986
  function getClient() {
655
987
  const opts = program.opts();
656
988
  const apiKey = opts.apiKey ?? process.env.OPENSEA_API_KEY;
@@ -663,12 +995,16 @@ function getClient() {
663
995
  return new OpenSeaClient({
664
996
  apiKey,
665
997
  chain: opts.chain,
666
- baseUrl: opts.baseUrl
998
+ baseUrl: opts.baseUrl,
999
+ timeout: parseIntOption(opts.timeout, "--timeout"),
1000
+ verbose: opts.verbose
667
1001
  });
668
1002
  }
669
1003
  function getFormat() {
670
1004
  const opts = program.opts();
671
- return opts.format === "table" ? "table" : "json";
1005
+ if (opts.format === "table") return "table";
1006
+ if (opts.format === "toon") return "toon";
1007
+ return "json";
672
1008
  }
673
1009
  program.addCommand(collectionsCommand(getClient, getFormat));
674
1010
  program.addCommand(nftsCommand(getClient, getFormat));
@@ -698,7 +1034,18 @@ async function main() {
698
1034
  );
699
1035
  process.exit(1);
700
1036
  }
701
- throw error;
1037
+ const label = error instanceof TypeError ? "Network Error" : error.name;
1038
+ console.error(
1039
+ JSON.stringify(
1040
+ {
1041
+ error: label,
1042
+ message: error.message
1043
+ },
1044
+ null,
1045
+ 2
1046
+ )
1047
+ );
1048
+ process.exit(1);
702
1049
  }
703
1050
  }
704
1051
  main();