nansen-cli 1.0.0 → 1.0.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +120 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nansen-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Command-line interface for Nansen API - designed for AI agents",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -23,7 +23,7 @@ function parseArgs(args) {
23
23
  const key = arg.slice(2);
24
24
  const next = args[i + 1];
25
25
 
26
- if (key === 'pretty' || key === 'help') {
26
+ if (key === 'pretty' || key === 'help' || key === 'table') {
27
27
  result.flags[key] = true;
28
28
  } else if (next && !next.startsWith('-')) {
29
29
  // Try to parse as JSON first
@@ -46,9 +46,98 @@ function parseArgs(args) {
46
46
  return result;
47
47
  }
48
48
 
49
+ // Table formatter for human-readable output
50
+ function formatTable(data) {
51
+ // Extract array of records from various response shapes
52
+ let records = [];
53
+ if (Array.isArray(data)) {
54
+ records = data;
55
+ } else if (data?.data && Array.isArray(data.data)) {
56
+ records = data.data;
57
+ } else if (data?.results && Array.isArray(data.results)) {
58
+ records = data.results;
59
+ } else if (data?.data?.results && Array.isArray(data.data.results)) {
60
+ records = data.data.results;
61
+ } else if (typeof data === 'object' && data !== null) {
62
+ // Single object - convert to array
63
+ records = [data];
64
+ }
65
+
66
+ if (records.length === 0) {
67
+ return 'No data';
68
+ }
69
+
70
+ // Get columns from first record, prioritize common useful fields
71
+ const priorityFields = ['token_symbol', 'token_name', 'symbol', 'name', 'address', 'label', 'chain', 'value_usd', 'amount', 'pnl_usd', 'price_usd', 'volume_usd', 'net_flow_usd', 'timestamp', 'block_timestamp'];
72
+ const allKeys = [...new Set(records.flatMap(r => Object.keys(r)))];
73
+
74
+ // Sort: priority fields first, then alphabetically
75
+ const columns = allKeys.sort((a, b) => {
76
+ const aIdx = priorityFields.indexOf(a);
77
+ const bIdx = priorityFields.indexOf(b);
78
+ if (aIdx !== -1 && bIdx !== -1) return aIdx - bIdx;
79
+ if (aIdx !== -1) return -1;
80
+ if (bIdx !== -1) return 1;
81
+ return a.localeCompare(b);
82
+ }).slice(0, 8); // Limit to 8 columns for readability
83
+
84
+ // Calculate column widths
85
+ const widths = columns.map(col => {
86
+ const headerLen = col.length;
87
+ const maxDataLen = Math.max(...records.map(r => {
88
+ const val = formatValue(r[col]);
89
+ return val.length;
90
+ }));
91
+ return Math.min(Math.max(headerLen, maxDataLen), 30); // Cap at 30 chars
92
+ });
93
+
94
+ // Build table
95
+ const separator = '─';
96
+ const lines = [];
97
+
98
+ // Header
99
+ const header = columns.map((col, i) => col.padEnd(widths[i])).join(' │ ');
100
+ lines.push(header);
101
+ lines.push(widths.map(w => separator.repeat(w)).join('─┼─'));
102
+
103
+ // Rows
104
+ for (const record of records.slice(0, 50)) { // Limit to 50 rows
105
+ const row = columns.map((col, i) => {
106
+ const val = formatValue(record[col]);
107
+ return val.slice(0, widths[i]).padEnd(widths[i]);
108
+ }).join(' │ ');
109
+ lines.push(row);
110
+ }
111
+
112
+ if (records.length > 50) {
113
+ lines.push(`... and ${records.length - 50} more rows`);
114
+ }
115
+
116
+ return lines.join('\n');
117
+ }
118
+
119
+ function formatValue(val) {
120
+ if (val === null || val === undefined) return '';
121
+ if (typeof val === 'number') {
122
+ if (Math.abs(val) >= 1000000) return (val / 1000000).toFixed(2) + 'M';
123
+ if (Math.abs(val) >= 1000) return (val / 1000).toFixed(2) + 'K';
124
+ if (Number.isInteger(val)) return val.toString();
125
+ return val.toFixed(2);
126
+ }
127
+ if (typeof val === 'object') return JSON.stringify(val);
128
+ return String(val);
129
+ }
130
+
49
131
  // Output helper
50
- function output(data, pretty = false) {
51
- if (pretty) {
132
+ function output(data, pretty = false, table = false) {
133
+ if (table) {
134
+ if (data.success === false) {
135
+ console.error(`Error: ${data.error}`);
136
+ } else {
137
+ const tableData = data.data || data;
138
+ console.log(formatTable(tableData));
139
+ }
140
+ } else if (pretty) {
52
141
  console.log(JSON.stringify(data, null, 2));
53
142
  } else {
54
143
  console.log(JSON.stringify(data));
@@ -56,17 +145,33 @@ function output(data, pretty = false) {
56
145
  }
57
146
 
58
147
  // Error output
59
- function errorOutput(error, pretty = false) {
148
+ function errorOutput(error, pretty = false, table = false) {
60
149
  const errorData = {
61
150
  success: false,
62
151
  error: error.message,
63
152
  status: error.status,
64
153
  details: error.data
65
154
  };
66
- output(errorData, pretty);
155
+ output(errorData, pretty, table);
67
156
  process.exit(1);
68
157
  }
69
158
 
159
+ // Parse simple sort syntax: "field:direction" or "field" (defaults to DESC)
160
+ function parseSort(sortOption, orderByOption) {
161
+ // If --order-by is provided, use it (full JSON control)
162
+ if (orderByOption) return orderByOption;
163
+
164
+ // If no --sort, return undefined
165
+ if (!sortOption) return undefined;
166
+
167
+ // Parse --sort field:direction or --sort field
168
+ const parts = sortOption.split(':');
169
+ const field = parts[0];
170
+ const direction = (parts[1] || 'desc').toUpperCase();
171
+
172
+ return [{ field, direction }];
173
+ }
174
+
70
175
  // Help text
71
176
  const HELP = `
72
177
  Nansen CLI - Command-line interface for Nansen API
@@ -86,11 +191,13 @@ COMMANDS:
86
191
 
87
192
  GLOBAL OPTIONS:
88
193
  --pretty Format JSON output for readability
194
+ --table Format output as human-readable table
89
195
  --chain Blockchain to query (ethereum, solana, base, etc.)
90
196
  --chains Multiple chains as JSON array
91
197
  --limit Number of results (shorthand for pagination)
92
198
  --filters JSON object with filters
93
- --order-by JSON array with sort order
199
+ --sort Sort by field (e.g., --sort value_usd:desc)
200
+ --order-by JSON array with sort order (advanced)
94
201
  --days Date range in days (default: 30 for most endpoints)
95
202
  --symbol Token symbol (for perp endpoints)
96
203
 
@@ -226,7 +333,7 @@ const commands = {
226
333
  const chain = options.chain || 'solana';
227
334
  const chains = options.chains || [chain];
228
335
  const filters = options.filters || {};
229
- const orderBy = options['order-by'];
336
+ const orderBy = parseSort(options.sort, options['order-by']);
230
337
  const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
231
338
 
232
339
  // Add smart money label filter if specified
@@ -265,7 +372,7 @@ const commands = {
265
372
  const entityName = options.entity || options['entity-name'];
266
373
  const chain = options.chain || 'ethereum';
267
374
  const filters = options.filters || {};
268
- const orderBy = options['order-by'];
375
+ const orderBy = parseSort(options.sort, options['order-by']);
269
376
  const pagination = options.limit ? { page: 1, recordsPerPage: options.limit } : undefined;
270
377
  const days = options.days ? parseInt(options.days) : 30;
271
378
 
@@ -303,7 +410,7 @@ const commands = {
303
410
  const chains = options.chains || [chain];
304
411
  const timeframe = options.timeframe || '24h';
305
412
  const filters = options.filters || {};
306
- const orderBy = options['order-by'];
413
+ const orderBy = parseSort(options.sort, options['order-by']);
307
414
  const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
308
415
  const days = options.days ? parseInt(options.days) : 30;
309
416
 
@@ -370,6 +477,7 @@ async function main() {
370
477
  const command = positional[0] || 'help';
371
478
  const subArgs = positional.slice(1);
372
479
  const pretty = flags.pretty || flags.p;
480
+ const table = flags.table || flags.t;
373
481
 
374
482
  if (command === 'help' || flags.help || flags.h) {
375
483
  console.log(HELP);
@@ -380,7 +488,7 @@ async function main() {
380
488
  output({
381
489
  error: `Unknown command: ${command}`,
382
490
  available: Object.keys(commands)
383
- }, pretty);
491
+ }, pretty, table);
384
492
  process.exit(1);
385
493
  }
386
494
 
@@ -395,9 +503,9 @@ async function main() {
395
503
  try {
396
504
  const api = new NansenAPI();
397
505
  const result = await commands[command](subArgs, api, flags, options);
398
- output({ success: true, data: result }, pretty);
506
+ output({ success: true, data: result }, pretty, table);
399
507
  } catch (error) {
400
- errorOutput(error, pretty);
508
+ errorOutput(error, pretty, table);
401
509
  }
402
510
  }
403
511