nansen-cli 1.43.1 → 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 +66 -0
- package/README.md +34 -0
- package/package.json +1 -1
- package/src/api.js +184 -23
- package/src/bridge.js +103 -5
- package/src/cli.js +42 -52
- 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/update-check.js +13 -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),
|
|
@@ -460,22 +453,6 @@ export function parseDateOption(dateOption, days = 30) {
|
|
|
460
453
|
return { from, to };
|
|
461
454
|
}
|
|
462
455
|
|
|
463
|
-
// Parse simple sort syntax: "field:direction" or "field" (defaults to DESC)
|
|
464
|
-
export function parseSort(sortOption, orderByOption) {
|
|
465
|
-
// If --order-by is provided, use it (full JSON control)
|
|
466
|
-
if (orderByOption) return orderByOption;
|
|
467
|
-
|
|
468
|
-
// If no --sort, return undefined
|
|
469
|
-
if (!sortOption) return undefined;
|
|
470
|
-
|
|
471
|
-
// Parse --sort field:direction or --sort field
|
|
472
|
-
const parts = sortOption.split(':');
|
|
473
|
-
const field = parts[0];
|
|
474
|
-
const direction = (parts[1] || 'desc').toUpperCase();
|
|
475
|
-
|
|
476
|
-
return [{ field, direction }];
|
|
477
|
-
}
|
|
478
|
-
|
|
479
456
|
// Enrich transfers with Nansen labels for from/to addresses
|
|
480
457
|
async function enrichTransfers(result, apiInstance, chain) {
|
|
481
458
|
const transfers = result?.data?.results || result?.transfers || result?.data || [];
|
|
@@ -746,6 +723,7 @@ COMMANDS:
|
|
|
746
723
|
logout Remove saved API key
|
|
747
724
|
doctor Diagnostics: auth, wallets, caches, connectivity (--offline --json)
|
|
748
725
|
schema JSON schema for all commands (use "nansen schema <cmd>" for one)
|
|
726
|
+
completion Shell completions: bash, zsh, fish
|
|
749
727
|
cache clear
|
|
750
728
|
changelog --since <version> to filter
|
|
751
729
|
|
|
@@ -785,7 +763,7 @@ Labels: Fund, Smart Trader, 30D/90D/180D Smart Trader, Smart HL Perps Trader
|
|
|
785
763
|
Docs: https://docs.nansen.ai
|
|
786
764
|
Skills: npx skills add nansen-ai/nansen-cli (agent-optimised docs per command group)
|
|
787
765
|
|
|
788
|
-
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
|
|
789
767
|
`;
|
|
790
768
|
|
|
791
769
|
// Usage text for the `trade` command group. Shared by the trade handler and the
|
|
@@ -1132,6 +1110,15 @@ export function buildCommands(deps = {}) {
|
|
|
1132
1110
|
}
|
|
1133
1111
|
const since = _options.since;
|
|
1134
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
|
+
}
|
|
1135
1122
|
// Show only entries from the given version onwards
|
|
1136
1123
|
const lines = content.split('\n');
|
|
1137
1124
|
const filtered = [];
|
|
@@ -1530,7 +1517,7 @@ export function buildCommands(deps = {}) {
|
|
|
1530
1517
|
};
|
|
1531
1518
|
|
|
1532
1519
|
if (!handlers[subcommand]) {
|
|
1533
|
-
|
|
1520
|
+
throw new NansenError(`Unknown perp analytics subcommand: ${subcommand}. Available: screener, leaderboard`, ErrorCode.UNKNOWN);
|
|
1534
1521
|
}
|
|
1535
1522
|
|
|
1536
1523
|
return handlers[subcommand]();
|
|
@@ -1587,7 +1574,7 @@ export function buildCommands(deps = {}) {
|
|
|
1587
1574
|
const maxUniqueTraders24h = options['max-unique-traders-24h'] != null ? Number(options['max-unique-traders-24h']) : undefined;
|
|
1588
1575
|
const minVolume24hr = options['min-volume-24hr'] != null ? Number(options['min-volume-24hr']) : undefined;
|
|
1589
1576
|
const maxVolume24hr = options['max-volume-24hr'] != null ? Number(options['max-volume-24hr']) : undefined;
|
|
1590
|
-
const negRisk = options
|
|
1577
|
+
const negRisk = resolveBooleanOption(options, flags, 'neg-risk');
|
|
1591
1578
|
const minOpenInterest = options['min-open-interest'] != null ? Number(options['min-open-interest']) : undefined;
|
|
1592
1579
|
const maxOpenInterest = options['max-open-interest'] != null ? Number(options['max-open-interest']) : undefined;
|
|
1593
1580
|
const endDateBefore = options['end-date-before'];
|
|
@@ -1631,32 +1618,33 @@ export function buildCommands(deps = {}) {
|
|
|
1631
1618
|
// it, so it has to be taken exactly once, here.
|
|
1632
1619
|
const perpAnalytics = cmds['perp'];
|
|
1633
1620
|
|
|
1634
|
-
const
|
|
1621
|
+
const researchSub = buildResearchCommands(deps).research;
|
|
1635
1622
|
|
|
1636
1623
|
cmds['research'] = async (args, apiInstance, flags, options) => {
|
|
1637
1624
|
const rawCategory = args[0];
|
|
1638
1625
|
if (!rawCategory || rawCategory === 'help') {
|
|
1639
1626
|
return {
|
|
1640
1627
|
categories: [...RESEARCH_CATEGORIES],
|
|
1628
|
+
subcommands: [...RESEARCH_SUBCOMMANDS],
|
|
1641
1629
|
historical: [...RESEARCH_HISTORICAL_SUBCOMMANDS],
|
|
1642
1630
|
aliases: RESEARCH_CATEGORY_ALIASES,
|
|
1643
1631
|
description: 'Research and analytics commands',
|
|
1644
1632
|
example: 'nansen research smart-money netflow --chain solana'
|
|
1645
1633
|
};
|
|
1646
1634
|
}
|
|
1647
|
-
if (
|
|
1648
|
-
return
|
|
1635
|
+
if (RESEARCH_SUBCOMMANDS.has(rawCategory)) {
|
|
1636
|
+
return researchSub(args, apiInstance, flags, options);
|
|
1649
1637
|
}
|
|
1650
1638
|
const category = RESEARCH_CATEGORY_ALIASES[rawCategory] || rawCategory;
|
|
1651
1639
|
if (!RESEARCH_CATEGORIES.has(category)) {
|
|
1652
|
-
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);
|
|
1653
1641
|
}
|
|
1654
1642
|
// `research perp` reaches only the analytics half (screener/leaderboard) —
|
|
1655
|
-
// the trading subcommands live at the top level.
|
|
1656
|
-
// cmds['perp']
|
|
1657
|
-
//
|
|
1658
|
-
if (category === 'perp'
|
|
1659
|
-
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);
|
|
1660
1648
|
}
|
|
1661
1649
|
return cmds[category](args.slice(1), apiInstance, flags, options);
|
|
1662
1650
|
};
|
|
@@ -1877,7 +1865,9 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1877
1865
|
// contract covers the background update-check fetch and telemetry too,
|
|
1878
1866
|
// not just the command's own requests.
|
|
1879
1867
|
const isMcpUsage = command === 'mcp' && (subcommand !== 'verify' || flags.help || flags.h);
|
|
1880
|
-
|
|
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';
|
|
1881
1871
|
const trackSucceeded = isOfflineCommand ? async () => {} : trackCommandSucceeded;
|
|
1882
1872
|
const trackFailed = isOfflineCommand ? async () => {} : trackCommandFailed;
|
|
1883
1873
|
|
|
@@ -1899,7 +1889,7 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1899
1889
|
|
|
1900
1890
|
// mcp prints its own output via `log`; runCLI callers inject their stdout
|
|
1901
1891
|
// sink as `output`, so map it across (an explicit `log` dep still wins).
|
|
1902
|
-
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 };
|
|
1903
1893
|
|
|
1904
1894
|
if (flags.version || flags.v) {
|
|
1905
1895
|
output(VERSION);
|