nansen-cli 1.43.0 → 1.44.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/CHANGELOG.md +80 -0
- package/README.md +34 -0
- package/package.json +3 -3
- package/src/api.js +184 -23
- package/src/bridge.js +103 -5
- package/src/cli.js +47 -53
- package/src/commands/completion.js +652 -0
- package/src/commands/mcp.js +19 -3
- package/src/commands/research.js +199 -25
- package/src/hl-client.js +20 -8
- package/src/limit-order.js +31 -20
- package/src/mcp-verify.js +66 -1
- package/src/perp.js +72 -24
- package/src/query-options.js +32 -0
- package/src/schema.json +631 -131
- package/src/semver.js +26 -0
- package/src/telemetry.js +118 -19
- package/src/trading.js +196 -22
- package/src/transfer.js +4 -1
- package/src/update-check.js +13 -6
- package/src/wallet.js +10 -6
- package/src/x402.js +8 -1
package/src/cli.js
CHANGED
|
@@ -12,8 +12,12 @@ import { buildLimitOrderCommands } from './limit-order.js';
|
|
|
12
12
|
import { formatAlertsTable, buildAlertsCommands } from './commands/alerts.js';
|
|
13
13
|
import { buildAgentCommands } from './commands/agent.js';
|
|
14
14
|
import { buildMcpCommands } from './commands/mcp.js';
|
|
15
|
-
import {
|
|
15
|
+
import { buildCompletionCommands } from './commands/completion.js';
|
|
16
|
+
import { buildResearchCommands, RESEARCH_HISTORICAL_SUBCOMMANDS, RESEARCH_SUBCOMMANDS } from './commands/research.js';
|
|
17
|
+
import { buildPagination, parseSort } from './query-options.js';
|
|
18
|
+
export { buildPagination, parseSort };
|
|
16
19
|
import { resolveAddress, isEnsName } from './ens.js';
|
|
20
|
+
import { compareSemver } from './semver.js';
|
|
17
21
|
import fs from 'fs';
|
|
18
22
|
import { getUpdateNotification, getUpgradeNotice, scheduleUpdateCheck } from './update-check.js';
|
|
19
23
|
import { getAuthStatus, runDoctorChecks, runConnectivityChecks, formatDoctorReport } from './doctor.js';
|
|
@@ -54,14 +58,6 @@ export function resolveBooleanOption(options, flags, key) {
|
|
|
54
58
|
return undefined;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
export function buildPagination(options) {
|
|
58
|
-
if (!options.limit && !options.page) return undefined;
|
|
59
|
-
return {
|
|
60
|
-
page: Math.max(1, parseInt(options.page, 10) || 1),
|
|
61
|
-
per_page: options.limit,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
61
|
// ============= Field Filtering =============
|
|
66
62
|
|
|
67
63
|
/**
|
|
@@ -168,18 +164,15 @@ export function compactSchema(schema) {
|
|
|
168
164
|
};
|
|
169
165
|
}
|
|
170
166
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (ap !== bp) return ap > bp ? 1 : -1;
|
|
181
|
-
return 0;
|
|
182
|
-
}
|
|
167
|
+
// Long options that never consume the next argument. The shell-completion
|
|
168
|
+
// generator needs the same list to tell an option's value apart from a
|
|
169
|
+
// subcommand, so it lives here rather than inline in parseArgs.
|
|
170
|
+
export const VALUELESS_FLAGS = new Set([
|
|
171
|
+
'pretty', 'help', 'version', 'table', 'no-retry', 'cache', 'no-cache', 'stream',
|
|
172
|
+
'enrich', 'full', 'human', 'enabled', 'disabled', 'expert', 'json', 'offline',
|
|
173
|
+
'no-simulate', 'no-verify-outcome', 'no-revoke-excessive-allowance', 'dry-run',
|
|
174
|
+
'send-api-key',
|
|
175
|
+
]);
|
|
183
176
|
|
|
184
177
|
export function parseArgs(args) {
|
|
185
178
|
const result = { _: [], flags: {}, options: {} };
|
|
@@ -191,7 +184,7 @@ export function parseArgs(args) {
|
|
|
191
184
|
const key = arg.slice(2);
|
|
192
185
|
const next = args[i + 1];
|
|
193
186
|
|
|
194
|
-
if (key
|
|
187
|
+
if (VALUELESS_FLAGS.has(key)) {
|
|
195
188
|
result.flags[key] = true;
|
|
196
189
|
} else if (next && (!next.startsWith('-') || /^-\d/.test(next))) {
|
|
197
190
|
// Try to parse as JSON first (for objects/arrays/booleans),
|
|
@@ -232,7 +225,11 @@ export function formatValue(val) {
|
|
|
232
225
|
if (val === null || val === undefined) return '';
|
|
233
226
|
if (typeof val === 'number') {
|
|
234
227
|
if (Math.abs(val) >= 1000000) return (val / 1000000).toFixed(2) + 'M';
|
|
235
|
-
if (Math.abs(val) >= 1000)
|
|
228
|
+
if (Math.abs(val) >= 1000) {
|
|
229
|
+
const formatted = (val / 1000).toFixed(2);
|
|
230
|
+
if (Math.abs(parseFloat(formatted)) >= 1000) return (val / 1000000).toFixed(2) + 'M';
|
|
231
|
+
return formatted + 'K';
|
|
232
|
+
}
|
|
236
233
|
if (Number.isInteger(val)) return val.toString();
|
|
237
234
|
return val.toFixed(2);
|
|
238
235
|
}
|
|
@@ -456,22 +453,6 @@ export function parseDateOption(dateOption, days = 30) {
|
|
|
456
453
|
return { from, to };
|
|
457
454
|
}
|
|
458
455
|
|
|
459
|
-
// Parse simple sort syntax: "field:direction" or "field" (defaults to DESC)
|
|
460
|
-
export function parseSort(sortOption, orderByOption) {
|
|
461
|
-
// If --order-by is provided, use it (full JSON control)
|
|
462
|
-
if (orderByOption) return orderByOption;
|
|
463
|
-
|
|
464
|
-
// If no --sort, return undefined
|
|
465
|
-
if (!sortOption) return undefined;
|
|
466
|
-
|
|
467
|
-
// Parse --sort field:direction or --sort field
|
|
468
|
-
const parts = sortOption.split(':');
|
|
469
|
-
const field = parts[0];
|
|
470
|
-
const direction = (parts[1] || 'desc').toUpperCase();
|
|
471
|
-
|
|
472
|
-
return [{ field, direction }];
|
|
473
|
-
}
|
|
474
|
-
|
|
475
456
|
// Enrich transfers with Nansen labels for from/to addresses
|
|
476
457
|
async function enrichTransfers(result, apiInstance, chain) {
|
|
477
458
|
const transfers = result?.data?.results || result?.transfers || result?.data || [];
|
|
@@ -742,6 +723,7 @@ COMMANDS:
|
|
|
742
723
|
logout Remove saved API key
|
|
743
724
|
doctor Diagnostics: auth, wallets, caches, connectivity (--offline --json)
|
|
744
725
|
schema JSON schema for all commands (use "nansen schema <cmd>" for one)
|
|
726
|
+
completion Shell completions: bash, zsh, fish
|
|
745
727
|
cache clear
|
|
746
728
|
changelog --since <version> to filter
|
|
747
729
|
|
|
@@ -781,7 +763,7 @@ Labels: Fund, Smart Trader, 30D/90D/180D Smart Trader, Smart HL Perps Trader
|
|
|
781
763
|
Docs: https://docs.nansen.ai
|
|
782
764
|
Skills: npx skills add nansen-ai/nansen-cli (agent-optimised docs per command group)
|
|
783
765
|
|
|
784
|
-
Telemetry: anonymous usage stats (commands, timing, errors). Perp order/close additionally send
|
|
766
|
+
Telemetry: anonymous usage stats (commands, timing, errors). Perp order/close additionally send each leg's side, outcome, order id, shared submission id, and a SHA-256 wallet identifier. Raw wallet, price, size, and exchange error text are not sent. Disable: DO_NOT_TRACK=1
|
|
785
767
|
`;
|
|
786
768
|
|
|
787
769
|
// Usage text for the `trade` command group. Shared by the trade handler and the
|
|
@@ -1128,6 +1110,15 @@ export function buildCommands(deps = {}) {
|
|
|
1128
1110
|
}
|
|
1129
1111
|
const since = _options.since;
|
|
1130
1112
|
if (since) {
|
|
1113
|
+
// compareSemver treats a missing trailing component as 0, so accept
|
|
1114
|
+
// "1", "1.43", and "1.43.0" alike here — but anything that isn't
|
|
1115
|
+
// digits-and-dots (e.g. "abc") needs a clear error instead of
|
|
1116
|
+
// silently comparing as if it were version 0.0.0, which would show
|
|
1117
|
+
// every entry rather than flag the typo.
|
|
1118
|
+
if (!/^v?\d+(\.\d+){0,2}$/.test(String(since))) {
|
|
1119
|
+
log(`Invalid --since value "${since}": expected a version like 1.43 or 1.43.0.`);
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1131
1122
|
// Show only entries from the given version onwards
|
|
1132
1123
|
const lines = content.split('\n');
|
|
1133
1124
|
const filtered = [];
|
|
@@ -1526,7 +1517,7 @@ export function buildCommands(deps = {}) {
|
|
|
1526
1517
|
};
|
|
1527
1518
|
|
|
1528
1519
|
if (!handlers[subcommand]) {
|
|
1529
|
-
|
|
1520
|
+
throw new NansenError(`Unknown perp analytics subcommand: ${subcommand}. Available: screener, leaderboard`, ErrorCode.UNKNOWN);
|
|
1530
1521
|
}
|
|
1531
1522
|
|
|
1532
1523
|
return handlers[subcommand]();
|
|
@@ -1583,7 +1574,7 @@ export function buildCommands(deps = {}) {
|
|
|
1583
1574
|
const maxUniqueTraders24h = options['max-unique-traders-24h'] != null ? Number(options['max-unique-traders-24h']) : undefined;
|
|
1584
1575
|
const minVolume24hr = options['min-volume-24hr'] != null ? Number(options['min-volume-24hr']) : undefined;
|
|
1585
1576
|
const maxVolume24hr = options['max-volume-24hr'] != null ? Number(options['max-volume-24hr']) : undefined;
|
|
1586
|
-
const negRisk = options
|
|
1577
|
+
const negRisk = resolveBooleanOption(options, flags, 'neg-risk');
|
|
1587
1578
|
const minOpenInterest = options['min-open-interest'] != null ? Number(options['min-open-interest']) : undefined;
|
|
1588
1579
|
const maxOpenInterest = options['max-open-interest'] != null ? Number(options['max-open-interest']) : undefined;
|
|
1589
1580
|
const endDateBefore = options['end-date-before'];
|
|
@@ -1627,32 +1618,33 @@ export function buildCommands(deps = {}) {
|
|
|
1627
1618
|
// it, so it has to be taken exactly once, here.
|
|
1628
1619
|
const perpAnalytics = cmds['perp'];
|
|
1629
1620
|
|
|
1630
|
-
const
|
|
1621
|
+
const researchSub = buildResearchCommands(deps).research;
|
|
1631
1622
|
|
|
1632
1623
|
cmds['research'] = async (args, apiInstance, flags, options) => {
|
|
1633
1624
|
const rawCategory = args[0];
|
|
1634
1625
|
if (!rawCategory || rawCategory === 'help') {
|
|
1635
1626
|
return {
|
|
1636
1627
|
categories: [...RESEARCH_CATEGORIES],
|
|
1628
|
+
subcommands: [...RESEARCH_SUBCOMMANDS],
|
|
1637
1629
|
historical: [...RESEARCH_HISTORICAL_SUBCOMMANDS],
|
|
1638
1630
|
aliases: RESEARCH_CATEGORY_ALIASES,
|
|
1639
1631
|
description: 'Research and analytics commands',
|
|
1640
1632
|
example: 'nansen research smart-money netflow --chain solana'
|
|
1641
1633
|
};
|
|
1642
1634
|
}
|
|
1643
|
-
if (
|
|
1644
|
-
return
|
|
1635
|
+
if (RESEARCH_SUBCOMMANDS.has(rawCategory)) {
|
|
1636
|
+
return researchSub(args, apiInstance, flags, options);
|
|
1645
1637
|
}
|
|
1646
1638
|
const category = RESEARCH_CATEGORY_ALIASES[rawCategory] || rawCategory;
|
|
1647
1639
|
if (!RESEARCH_CATEGORIES.has(category)) {
|
|
1648
|
-
throw new NansenError(`Unknown research category: ${rawCategory}. Available: ${[...RESEARCH_CATEGORIES, ...
|
|
1640
|
+
throw new NansenError(`Unknown research category: ${rawCategory}. Available: ${[...RESEARCH_CATEGORIES, ...RESEARCH_SUBCOMMANDS].join(', ')}`, ErrorCode.UNKNOWN);
|
|
1649
1641
|
}
|
|
1650
1642
|
// `research perp` reaches only the analytics half (screener/leaderboard) —
|
|
1651
|
-
// the trading subcommands live at the top level.
|
|
1652
|
-
// cmds['perp']
|
|
1653
|
-
//
|
|
1654
|
-
if (category === 'perp'
|
|
1655
|
-
return perpAnalytics(
|
|
1643
|
+
// the trading subcommands live at the top level. Use the captured handler
|
|
1644
|
+
// for every subcommand because cmds['perp'] is replaced below by the
|
|
1645
|
+
// combined top-level trading dispatcher.
|
|
1646
|
+
if (category === 'perp') {
|
|
1647
|
+
return perpAnalytics(args.slice(1), apiInstance, flags, options);
|
|
1656
1648
|
}
|
|
1657
1649
|
return cmds[category](args.slice(1), apiInstance, flags, options);
|
|
1658
1650
|
};
|
|
@@ -1873,7 +1865,9 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1873
1865
|
// contract covers the background update-check fetch and telemetry too,
|
|
1874
1866
|
// not just the command's own requests.
|
|
1875
1867
|
const isMcpUsage = command === 'mcp' && (subcommand !== 'verify' || flags.help || flags.h);
|
|
1876
|
-
|
|
1868
|
+
// `completion` renders from the checked-in schema — no network, and its
|
|
1869
|
+
// stdout is piped straight into a shell, so keep the update check out of it.
|
|
1870
|
+
const isOfflineCommand = command === 'auth' || (command === 'doctor' && flags.offline) || isMcpUsage || command === 'completion';
|
|
1877
1871
|
const trackSucceeded = isOfflineCommand ? async () => {} : trackCommandSucceeded;
|
|
1878
1872
|
const trackFailed = isOfflineCommand ? async () => {} : trackCommandFailed;
|
|
1879
1873
|
|
|
@@ -1895,7 +1889,7 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1895
1889
|
|
|
1896
1890
|
// mcp prints its own output via `log`; runCLI callers inject their stdout
|
|
1897
1891
|
// sink as `output`, so map it across (an explicit `log` dep still wins).
|
|
1898
|
-
const commands = { ...buildCommands(deps), ...buildWalletCommands(deps), ...buildTradingCommands(deps), ...buildAlertsCommands(deps), ...buildAgentCommands(deps), ...buildMcpCommands({ ...deps, log: deps.log ?? output }), ...commandOverrides };
|
|
1892
|
+
const commands = { ...buildCommands(deps), ...buildWalletCommands(deps), ...buildTradingCommands(deps), ...buildAlertsCommands(deps), ...buildAgentCommands(deps), ...buildMcpCommands({ ...deps, log: deps.log ?? output }), ...buildCompletionCommands({ ...deps, log: deps.log ?? output }), ...commandOverrides };
|
|
1899
1893
|
|
|
1900
1894
|
if (flags.version || flags.v) {
|
|
1901
1895
|
output(VERSION);
|