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.
- package/package.json +1 -1
- package/src/index.js +120 -12
package/package.json
CHANGED
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 (
|
|
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
|
-
--
|
|
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
|
|