nansen-cli 1.3.3 → 1.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/CLAUDE.md +30 -6
- package/package.json +1 -1
- package/src/api.js +61 -3
- package/src/cli.js +268 -9
package/CLAUDE.md
CHANGED
|
@@ -178,14 +178,38 @@ Structured error codes for programmatic handling:
|
|
|
178
178
|
- **Beta endpoints** (`/api/beta/...`) may have different pagination
|
|
179
179
|
- **EVM vs Solana addresses** — validation auto-detects based on chain param
|
|
180
180
|
|
|
181
|
-
##
|
|
181
|
+
## Publishing (npm)
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
**⚠️ DO NOT manually run `npm version` or `npm publish`. CI handles everything.**
|
|
184
|
+
|
|
185
|
+
### How it works:
|
|
186
|
+
|
|
187
|
+
1. **Add a changeset** for user-facing changes:
|
|
188
|
+
```bash
|
|
189
|
+
npx changeset
|
|
190
|
+
# Or manually create .changeset/<name>.md
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
2. **Push to main** — CI runs tests
|
|
194
|
+
|
|
195
|
+
3. **CI creates a "Version Packages" PR** — This bumps version + updates CHANGELOG
|
|
196
|
+
|
|
197
|
+
4. **Merge the Version PR** — CI auto-publishes to npm
|
|
198
|
+
|
|
199
|
+
### Changeset format:
|
|
200
|
+
```markdown
|
|
201
|
+
---
|
|
202
|
+
"nansen-cli": minor
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
Description of changes (appears in CHANGELOG)
|
|
186
206
|
```
|
|
187
|
-
|
|
188
|
-
|
|
207
|
+
|
|
208
|
+
Choose: `patch` (bug fixes), `minor` (new features), `major` (breaking changes)
|
|
209
|
+
|
|
210
|
+
### If you mess up:
|
|
211
|
+
- Accidentally bumped version manually? `git revert` and add a changeset instead
|
|
212
|
+
- CI publish failed? Check GitHub Actions logs, likely needs `NPM_TOKEN` secret refresh
|
|
189
213
|
|
|
190
214
|
## PR Checklist
|
|
191
215
|
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -634,14 +634,23 @@ export class NansenAPI {
|
|
|
634
634
|
}
|
|
635
635
|
|
|
636
636
|
async addressPnl(params = {}) {
|
|
637
|
-
const { address, chain = 'ethereum' } = params;
|
|
637
|
+
const { address, chain = 'ethereum', date, days = 30, pagination } = params;
|
|
638
638
|
if (address) {
|
|
639
639
|
const validation = validateAddress(address, chain);
|
|
640
640
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
641
641
|
}
|
|
642
|
-
|
|
642
|
+
// Build date range
|
|
643
|
+
let dateRange = date;
|
|
644
|
+
if (!dateRange) {
|
|
645
|
+
const to = new Date().toISOString().split('T')[0];
|
|
646
|
+
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
647
|
+
dateRange = { from, to };
|
|
648
|
+
}
|
|
649
|
+
return this.request('/api/v1/profiler/address/pnl', {
|
|
643
650
|
address,
|
|
644
|
-
chain
|
|
651
|
+
chain,
|
|
652
|
+
date: dateRange,
|
|
653
|
+
pagination
|
|
645
654
|
});
|
|
646
655
|
}
|
|
647
656
|
|
|
@@ -938,6 +947,55 @@ export class NansenAPI {
|
|
|
938
947
|
});
|
|
939
948
|
}
|
|
940
949
|
|
|
950
|
+
async tokenInformation(params = {}) {
|
|
951
|
+
const { tokenAddress, chain = 'solana', timeframe = '24h' } = params;
|
|
952
|
+
if (tokenAddress) {
|
|
953
|
+
const validation = validateTokenAddress(tokenAddress, chain);
|
|
954
|
+
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
955
|
+
}
|
|
956
|
+
return this.request('/api/v1/tgm/token-information', {
|
|
957
|
+
token_address: tokenAddress,
|
|
958
|
+
chain,
|
|
959
|
+
timeframe
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// ============= Perp Endpoints =============
|
|
964
|
+
|
|
965
|
+
async perpScreener(params = {}) {
|
|
966
|
+
const { filters = {}, orderBy, pagination, days = 30 } = params;
|
|
967
|
+
const to = new Date().toISOString().split('T')[0];
|
|
968
|
+
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
969
|
+
return this.request('/api/v1/perp-screener', {
|
|
970
|
+
date: { from, to },
|
|
971
|
+
filters,
|
|
972
|
+
order_by: orderBy,
|
|
973
|
+
pagination
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
async perpLeaderboard(params = {}) {
|
|
978
|
+
const { filters = {}, orderBy, pagination, days = 30 } = params;
|
|
979
|
+
const to = new Date().toISOString().split('T')[0];
|
|
980
|
+
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
981
|
+
return this.request('/api/v1/perp-leaderboard', {
|
|
982
|
+
date: { from, to },
|
|
983
|
+
filters,
|
|
984
|
+
order_by: orderBy,
|
|
985
|
+
pagination
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// ============= Points Endpoints =============
|
|
990
|
+
|
|
991
|
+
async pointsLeaderboard(params = {}) {
|
|
992
|
+
const { tier, pagination } = params;
|
|
993
|
+
return this.request('/api/v1/points/leaderboard', {
|
|
994
|
+
tier,
|
|
995
|
+
pagination
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
|
|
941
999
|
// ============= Portfolio Endpoints =============
|
|
942
1000
|
|
|
943
1001
|
async portfolioDefiHoldings(params = {}) {
|
package/src/cli.js
CHANGED
|
@@ -90,7 +90,7 @@ export const SCHEMA = {
|
|
|
90
90
|
},
|
|
91
91
|
'pnl': {
|
|
92
92
|
description: 'PnL and trade performance',
|
|
93
|
-
options: { address: { type: 'string', required: true }, chain: { type: 'string', default: 'ethereum' } },
|
|
93
|
+
options: { address: { type: 'string', required: true }, chain: { type: 'string', default: 'ethereum' }, date: { type: 'string', description: 'Date or date range (YYYY-MM-DD or {"from":"YYYY-MM-DD","to":"YYYY-MM-DD"})' }, days: { type: 'number', default: 30 }, limit: { type: 'number' } },
|
|
94
94
|
returns: ['token_address', 'token_symbol', 'realized_pnl_usd', 'unrealized_pnl_usd', 'total_pnl_usd']
|
|
95
95
|
},
|
|
96
96
|
'search': {
|
|
@@ -165,6 +165,15 @@ export const SCHEMA = {
|
|
|
165
165
|
'token': {
|
|
166
166
|
description: 'Token God Mode - deep analytics for any token',
|
|
167
167
|
subcommands: {
|
|
168
|
+
'info': {
|
|
169
|
+
description: 'Get detailed information for a specific token',
|
|
170
|
+
options: {
|
|
171
|
+
token: { type: 'string', required: true, description: 'Token address' },
|
|
172
|
+
chain: { type: 'string', default: 'solana' },
|
|
173
|
+
timeframe: { type: 'string', default: '24h', enum: ['5m', '10m', '1h', '6h', '24h', '7d', '30d'] }
|
|
174
|
+
},
|
|
175
|
+
returns: ['token_address', 'token_symbol', 'token_name', 'chain', 'price_usd', 'volume_usd', 'market_cap', 'holder_count', 'liquidity_usd']
|
|
176
|
+
},
|
|
168
177
|
'screener': {
|
|
169
178
|
description: 'Discover and filter tokens',
|
|
170
179
|
options: {
|
|
@@ -172,6 +181,7 @@ export const SCHEMA = {
|
|
|
172
181
|
chains: { type: 'array' },
|
|
173
182
|
timeframe: { type: 'string', default: '24h', enum: ['5m', '10m', '1h', '6h', '24h', '7d', '30d'] },
|
|
174
183
|
'smart-money': { type: 'boolean', description: 'Filter for Smart Money only' },
|
|
184
|
+
search: { type: 'string', description: 'Filter results by token symbol or name (client-side)' },
|
|
175
185
|
limit: { type: 'number' },
|
|
176
186
|
sort: { type: 'string' }
|
|
177
187
|
},
|
|
@@ -243,6 +253,44 @@ export const SCHEMA = {
|
|
|
243
253
|
returns: ['protocol', 'chain', 'position_type', 'token_symbol', 'balance', 'balance_usd']
|
|
244
254
|
}
|
|
245
255
|
}
|
|
256
|
+
},
|
|
257
|
+
'perp': {
|
|
258
|
+
description: 'Perpetual futures analytics',
|
|
259
|
+
subcommands: {
|
|
260
|
+
'screener': {
|
|
261
|
+
description: 'Screen perpetual futures contracts',
|
|
262
|
+
options: {
|
|
263
|
+
days: { type: 'number', default: 30 },
|
|
264
|
+
limit: { type: 'number' },
|
|
265
|
+
sort: { type: 'string' },
|
|
266
|
+
filters: { type: 'object' }
|
|
267
|
+
},
|
|
268
|
+
returns: ['token_symbol', 'volume_usd', 'open_interest', 'funding_rate', 'price_change_24h']
|
|
269
|
+
},
|
|
270
|
+
'leaderboard': {
|
|
271
|
+
description: 'Perpetual futures PnL leaderboard',
|
|
272
|
+
options: {
|
|
273
|
+
days: { type: 'number', default: 30 },
|
|
274
|
+
limit: { type: 'number' },
|
|
275
|
+
sort: { type: 'string' },
|
|
276
|
+
filters: { type: 'object' }
|
|
277
|
+
},
|
|
278
|
+
returns: ['address', 'address_label', 'realized_pnl', 'unrealized_pnl', 'total_pnl', 'trade_count', 'win_rate']
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
'points': {
|
|
283
|
+
description: 'Nansen Points analytics',
|
|
284
|
+
subcommands: {
|
|
285
|
+
'leaderboard': {
|
|
286
|
+
description: 'Points leaderboard',
|
|
287
|
+
options: {
|
|
288
|
+
tier: { type: 'string', description: 'Filter by tier' },
|
|
289
|
+
limit: { type: 'number' }
|
|
290
|
+
},
|
|
291
|
+
returns: ['rank', 'address', 'address_label', 'points', 'tier']
|
|
292
|
+
}
|
|
293
|
+
}
|
|
246
294
|
}
|
|
247
295
|
},
|
|
248
296
|
globalOptions: {
|
|
@@ -773,10 +821,16 @@ COMMANDS:
|
|
|
773
821
|
logout Remove saved API key
|
|
774
822
|
schema Output JSON schema for all commands (for agent introspection)
|
|
775
823
|
cache Cache management (clear)
|
|
776
|
-
smart-money Smart Money analytics (netflow, dex-trades, holdings, dcas, historical-holdings)
|
|
777
|
-
profiler Wallet profiling (balance, labels,
|
|
778
|
-
|
|
779
|
-
|
|
824
|
+
smart-money Smart Money analytics (netflow, dex-trades, perp-trades, holdings, dcas, historical-holdings)
|
|
825
|
+
profiler Wallet profiling (balance, labels, transactions, pnl, pnl-summary, search,
|
|
826
|
+
historical-balances, related-wallets, counterparties, perp-positions, perp-trades,
|
|
827
|
+
batch, trace, compare)
|
|
828
|
+
token Token God Mode (info, screener, holders, flows, dex-trades, pnl, who-bought-sold,
|
|
829
|
+
flow-intelligence, transfers, jup-dca, perp-trades, perp-positions,
|
|
830
|
+
perp-pnl-leaderboard)
|
|
831
|
+
portfolio Portfolio analytics (defi)
|
|
832
|
+
perp Perpetual futures analytics (screener, leaderboard)
|
|
833
|
+
points Nansen Points analytics (leaderboard)
|
|
780
834
|
help Show this help message
|
|
781
835
|
|
|
782
836
|
GLOBAL OPTIONS:
|
|
@@ -1043,7 +1097,10 @@ export function buildCommands(deps = {}) {
|
|
|
1043
1097
|
const date = parseDateOption(options.date, days);
|
|
1044
1098
|
return apiInstance.addressTransactions({ address, chain, filters, orderBy, pagination, days, date });
|
|
1045
1099
|
},
|
|
1046
|
-
'pnl': () =>
|
|
1100
|
+
'pnl': () => {
|
|
1101
|
+
const date = parseDateOption(options.date, days);
|
|
1102
|
+
return apiInstance.addressPnl({ address, chain, date, days, pagination });
|
|
1103
|
+
},
|
|
1047
1104
|
'search': () => apiInstance.entitySearch({ query: options.query }),
|
|
1048
1105
|
'historical-balances': () => apiInstance.addressHistoricalBalances({ address, chain, filters, orderBy, pagination, days }),
|
|
1049
1106
|
'related-wallets': () => apiInstance.addressRelatedWallets({ address, chain, orderBy, pagination }),
|
|
@@ -1122,7 +1179,21 @@ export function buildCommands(deps = {}) {
|
|
|
1122
1179
|
}
|
|
1123
1180
|
|
|
1124
1181
|
const handlers = {
|
|
1125
|
-
'
|
|
1182
|
+
'info': () => apiInstance.tokenInformation({ tokenAddress, chain, timeframe }),
|
|
1183
|
+
'screener': async () => {
|
|
1184
|
+
const result = await apiInstance.tokenScreener({ chains, timeframe, filters, orderBy, pagination });
|
|
1185
|
+
// Client-side search filter (API doesn't support server-side search)
|
|
1186
|
+
const search = options.search;
|
|
1187
|
+
if (search && result?.data) {
|
|
1188
|
+
const q = search.toLowerCase();
|
|
1189
|
+
const filtered = result.data.filter(t =>
|
|
1190
|
+
(t.token_symbol && t.token_symbol.toLowerCase().includes(q)) ||
|
|
1191
|
+
(t.token_name && t.token_name.toLowerCase().includes(q))
|
|
1192
|
+
);
|
|
1193
|
+
return { ...result, data: filtered };
|
|
1194
|
+
}
|
|
1195
|
+
return result;
|
|
1196
|
+
},
|
|
1126
1197
|
'holders': () => apiInstance.tokenHolders({ tokenAddress, chain, labelType: onlySmartMoney ? 'smart_money' : 'all_holders', filters, orderBy, pagination }),
|
|
1127
1198
|
'flows': () => {
|
|
1128
1199
|
const date = parseDateOption(options.date, days);
|
|
@@ -1146,7 +1217,7 @@ export function buildCommands(deps = {}) {
|
|
|
1146
1217
|
'perp-positions': () => apiInstance.tokenPerpPositions({ tokenSymbol, filters, orderBy, pagination }),
|
|
1147
1218
|
'perp-pnl-leaderboard': () => apiInstance.tokenPerpPnlLeaderboard({ tokenSymbol, filters, orderBy, pagination, days }),
|
|
1148
1219
|
'help': () => ({
|
|
1149
|
-
commands: ['screener', 'holders', 'flows', 'dex-trades', 'pnl', 'who-bought-sold', 'flow-intelligence', 'transfers', 'jup-dca', 'perp-trades', 'perp-positions', 'perp-pnl-leaderboard'],
|
|
1220
|
+
commands: ['info', 'screener', 'holders', 'flows', 'dex-trades', 'pnl', 'who-bought-sold', 'flow-intelligence', 'transfers', 'jup-dca', 'perp-trades', 'perp-positions', 'perp-pnl-leaderboard'],
|
|
1150
1221
|
description: 'Token God Mode endpoints',
|
|
1151
1222
|
example: 'nansen token screener --chain solana --timeframe 24h --smart-money'
|
|
1152
1223
|
})
|
|
@@ -1184,6 +1255,51 @@ export function buildCommands(deps = {}) {
|
|
|
1184
1255
|
return { error: `Unknown subcommand: ${subcommand}`, available: Object.keys(handlers) };
|
|
1185
1256
|
}
|
|
1186
1257
|
|
|
1258
|
+
return handlers[subcommand]();
|
|
1259
|
+
},
|
|
1260
|
+
|
|
1261
|
+
'perp': async (args, apiInstance, flags, options) => {
|
|
1262
|
+
const subcommand = args[0] || 'help';
|
|
1263
|
+
const filters = options.filters || {};
|
|
1264
|
+
const orderBy = parseSort(options.sort, options['order-by']);
|
|
1265
|
+
const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
|
|
1266
|
+
const days = options.days ? parseInt(options.days) : 30;
|
|
1267
|
+
|
|
1268
|
+
const handlers = {
|
|
1269
|
+
'screener': () => apiInstance.perpScreener({ filters, orderBy, pagination, days }),
|
|
1270
|
+
'leaderboard': () => apiInstance.perpLeaderboard({ filters, orderBy, pagination, days }),
|
|
1271
|
+
'help': () => ({
|
|
1272
|
+
commands: ['screener', 'leaderboard'],
|
|
1273
|
+
description: 'Perpetual futures analytics endpoints',
|
|
1274
|
+
example: 'nansen perp screener --days 7 --limit 20'
|
|
1275
|
+
})
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
if (!handlers[subcommand]) {
|
|
1279
|
+
return { error: `Unknown subcommand: ${subcommand}`, available: Object.keys(handlers) };
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
return handlers[subcommand]();
|
|
1283
|
+
},
|
|
1284
|
+
|
|
1285
|
+
'points': async (args, apiInstance, flags, options) => {
|
|
1286
|
+
const subcommand = args[0] || 'help';
|
|
1287
|
+
const tier = options.tier;
|
|
1288
|
+
const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
|
|
1289
|
+
|
|
1290
|
+
const handlers = {
|
|
1291
|
+
'leaderboard': () => apiInstance.pointsLeaderboard({ tier, pagination }),
|
|
1292
|
+
'help': () => ({
|
|
1293
|
+
commands: ['leaderboard'],
|
|
1294
|
+
description: 'Nansen Points analytics endpoints',
|
|
1295
|
+
example: 'nansen points leaderboard --limit 100'
|
|
1296
|
+
})
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1299
|
+
if (!handlers[subcommand]) {
|
|
1300
|
+
return { error: `Unknown subcommand: ${subcommand}`, available: Object.keys(handlers) };
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1187
1303
|
return handlers[subcommand]();
|
|
1188
1304
|
}
|
|
1189
1305
|
};
|
|
@@ -1192,6 +1308,120 @@ export function buildCommands(deps = {}) {
|
|
|
1192
1308
|
// Commands that don't require API authentication
|
|
1193
1309
|
export const NO_AUTH_COMMANDS = ['login', 'logout', 'help', 'schema', 'cache'];
|
|
1194
1310
|
|
|
1311
|
+
// Command aliases for convenience
|
|
1312
|
+
export const COMMAND_ALIASES = {
|
|
1313
|
+
'tgm': 'token', // Token God Mode
|
|
1314
|
+
'sm': 'smart-money', // Smart Money
|
|
1315
|
+
'prof': 'profiler', // Profiler
|
|
1316
|
+
'port': 'portfolio' // Portfolio
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
// Generate help text for a specific subcommand using SCHEMA
|
|
1320
|
+
export function generateSubcommandHelp(command, subcommand) {
|
|
1321
|
+
const cmdSchema = SCHEMA.commands[command];
|
|
1322
|
+
if (!cmdSchema) return null;
|
|
1323
|
+
|
|
1324
|
+
const subSchema = cmdSchema.subcommands?.[subcommand];
|
|
1325
|
+
if (!subSchema) return null;
|
|
1326
|
+
|
|
1327
|
+
const lines = [];
|
|
1328
|
+
lines.push(`\n${command} ${subcommand} - ${subSchema.description || 'No description'}\n`);
|
|
1329
|
+
|
|
1330
|
+
// Usage
|
|
1331
|
+
const requiredOpts = [];
|
|
1332
|
+
const optionalOpts = [];
|
|
1333
|
+
|
|
1334
|
+
if (subSchema.options) {
|
|
1335
|
+
for (const [name, opt] of Object.entries(subSchema.options)) {
|
|
1336
|
+
if (opt.required) {
|
|
1337
|
+
requiredOpts.push(name);
|
|
1338
|
+
} else {
|
|
1339
|
+
optionalOpts.push(name);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
let usage = `USAGE:\n nansen ${command} ${subcommand}`;
|
|
1345
|
+
if (requiredOpts.length) {
|
|
1346
|
+
usage += ' ' + requiredOpts.map(o => `--${o} <value>`).join(' ');
|
|
1347
|
+
}
|
|
1348
|
+
if (optionalOpts.length) {
|
|
1349
|
+
usage += ' [options]';
|
|
1350
|
+
}
|
|
1351
|
+
lines.push(usage);
|
|
1352
|
+
|
|
1353
|
+
// Required options
|
|
1354
|
+
if (requiredOpts.length) {
|
|
1355
|
+
lines.push('\nREQUIRED:');
|
|
1356
|
+
for (const name of requiredOpts) {
|
|
1357
|
+
const opt = subSchema.options[name];
|
|
1358
|
+
const desc = opt.description || `${opt.type}`;
|
|
1359
|
+
lines.push(` --${name.padEnd(16)} ${desc}`);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// Optional options
|
|
1364
|
+
if (optionalOpts.length) {
|
|
1365
|
+
lines.push('\nOPTIONS:');
|
|
1366
|
+
for (const name of optionalOpts) {
|
|
1367
|
+
const opt = subSchema.options[name];
|
|
1368
|
+
const defaultStr = opt.default !== undefined ? ` (default: ${opt.default})` : '';
|
|
1369
|
+
const desc = (opt.description || opt.type) + defaultStr;
|
|
1370
|
+
lines.push(` --${name.padEnd(16)} ${desc}`);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// Return fields
|
|
1375
|
+
if (subSchema.returns && subSchema.returns.length) {
|
|
1376
|
+
lines.push('\nRETURNS:');
|
|
1377
|
+
lines.push(` ${subSchema.returns.join(', ')}`);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// Examples
|
|
1381
|
+
lines.push('\nEXAMPLES:');
|
|
1382
|
+
const chain = subSchema.options?.chain?.default || 'solana';
|
|
1383
|
+
|
|
1384
|
+
// Example values for common required options
|
|
1385
|
+
const exampleValues = {
|
|
1386
|
+
address: '0x123...',
|
|
1387
|
+
token: '0x123...',
|
|
1388
|
+
query: '"search term"',
|
|
1389
|
+
symbol: 'BTC',
|
|
1390
|
+
date: '2024-01-01'
|
|
1391
|
+
};
|
|
1392
|
+
|
|
1393
|
+
// Build example based on required options
|
|
1394
|
+
let example = ` nansen ${command} ${subcommand}`;
|
|
1395
|
+
for (const name of requiredOpts) {
|
|
1396
|
+
const value = exampleValues[name] || '<value>';
|
|
1397
|
+
example += ` --${name} ${value}`;
|
|
1398
|
+
}
|
|
1399
|
+
if (subSchema.options?.chain && !requiredOpts.includes('chain')) {
|
|
1400
|
+
example += ` --chain ${chain}`;
|
|
1401
|
+
}
|
|
1402
|
+
example += ' --pretty';
|
|
1403
|
+
lines.push(example);
|
|
1404
|
+
|
|
1405
|
+
// Add a filtered example if filters are supported
|
|
1406
|
+
if (subSchema.options?.filters || subSchema.options?.labels) {
|
|
1407
|
+
let filterExample = ` nansen ${command} ${subcommand}`;
|
|
1408
|
+
for (const name of requiredOpts) {
|
|
1409
|
+
const value = exampleValues[name] || '<value>';
|
|
1410
|
+
filterExample += ` --${name} ${value}`;
|
|
1411
|
+
}
|
|
1412
|
+
if (subSchema.options?.chain && !requiredOpts.includes('chain')) {
|
|
1413
|
+
filterExample += ` --chain ${chain}`;
|
|
1414
|
+
}
|
|
1415
|
+
if (subSchema.options?.labels) {
|
|
1416
|
+
filterExample += ' --labels "Smart Trader"';
|
|
1417
|
+
}
|
|
1418
|
+
filterExample += ' --limit 10 --table';
|
|
1419
|
+
lines.push(filterExample);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
return lines.join('\n');
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1195
1425
|
// Run CLI with given args (returns result, allows custom output/exit handlers)
|
|
1196
1426
|
export async function runCLI(rawArgs, deps = {}) {
|
|
1197
1427
|
const {
|
|
@@ -1204,8 +1434,11 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1204
1434
|
|
|
1205
1435
|
const { _: positional, flags, options } = parseArgs(rawArgs);
|
|
1206
1436
|
|
|
1207
|
-
|
|
1437
|
+
// Resolve command aliases
|
|
1438
|
+
const rawCommand = positional[0] || 'help';
|
|
1439
|
+
const command = COMMAND_ALIASES[rawCommand] || rawCommand;
|
|
1208
1440
|
const subArgs = positional.slice(1);
|
|
1441
|
+
const subcommand = subArgs[0];
|
|
1209
1442
|
const pretty = flags.pretty || flags.p;
|
|
1210
1443
|
const table = flags.table || flags.t;
|
|
1211
1444
|
const stream = flags.stream || flags.s;
|
|
@@ -1224,6 +1457,32 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1224
1457
|
}
|
|
1225
1458
|
|
|
1226
1459
|
if (command === 'help' || flags.help || flags.h) {
|
|
1460
|
+
// Check for subcommand-specific help: nansen <command> <subcommand> --help
|
|
1461
|
+
if (flags.help || flags.h) {
|
|
1462
|
+
// First try subcommand help
|
|
1463
|
+
if (command && subcommand) {
|
|
1464
|
+
const subHelp = generateSubcommandHelp(command, subcommand);
|
|
1465
|
+
if (subHelp) {
|
|
1466
|
+
output(subHelp);
|
|
1467
|
+
notify();
|
|
1468
|
+
return { type: 'subcommand-help', command, subcommand };
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
// Then try command-level help (list subcommands)
|
|
1472
|
+
if (command && SCHEMA.commands[command]) {
|
|
1473
|
+
const cmdSchema = SCHEMA.commands[command];
|
|
1474
|
+
const lines = [`\n${command} - ${cmdSchema.description}\n`];
|
|
1475
|
+
lines.push('SUBCOMMANDS:');
|
|
1476
|
+
for (const [sub, subSchema] of Object.entries(cmdSchema.subcommands || {})) {
|
|
1477
|
+
lines.push(` ${sub.padEnd(20)} ${subSchema.description || ''}`);
|
|
1478
|
+
}
|
|
1479
|
+
lines.push(`\nFor detailed help: nansen ${command} <subcommand> --help`);
|
|
1480
|
+
output(lines.join('\n'));
|
|
1481
|
+
notify();
|
|
1482
|
+
return { type: 'command-help', command };
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
// Fallback to main help
|
|
1227
1486
|
output(BANNER + HELP);
|
|
1228
1487
|
notify();
|
|
1229
1488
|
return { type: 'help' };
|