nansen-cli 1.19.0 → 1.21.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 CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.21.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#315](https://github.com/nansen-ai/nansen-cli/pull/315) [`908fa0c`](https://github.com/nansen-ai/nansen-cli/commit/908fa0ccdf18cb79b7efb58b9b31f66e0afedff6) Thanks [@TimNooren](https://github.com/TimNooren)! - Add `nansen agent` command for the Nansen AI research agent with fast/expert modes, SSE streaming, conversation continuation, and JSON output.
8
+
9
+ ### Patch Changes
10
+
11
+ - [#326](https://github.com/nansen-ai/nansen-cli/pull/326) [`1532ba4`](https://github.com/nansen-ai/nansen-cli/commit/1532ba420174ed7635a42641f0c1a2802077fdc0) Thanks [@TimNooren](https://github.com/TimNooren)! - Show API credit cost in research subcommand help text (fetched from OpenAPI spec, cached 24h).
12
+
13
+ - [#330](https://github.com/nansen-ai/nansen-cli/pull/330) [`a6b9b8f`](https://github.com/nansen-ai/nansen-cli/commit/a6b9b8fc7d291ee7375941bb275c224939338161) Thanks [@0xlaveen](https://github.com/0xlaveen)! - Suppress misleading PASSWORD_REQUIRED error when `--provider privy` is specified. Privy wallets don't need a password — only the Privy-specific credentials error is now shown when PRIVY_APP_ID/PRIVY_APP_SECRET are missing.
14
+
15
+ - [#329](https://github.com/nansen-ai/nansen-cli/pull/329) [`f047833`](https://github.com/nansen-ai/nansen-cli/commit/f047833c7e6fb55cb713e0b69f82282fe87d4566) Thanks [@TimNooren](https://github.com/TimNooren)! - Limit deprecation warnings and update notices to help output only, keeping stdout/stderr clean for programmatic usage.
16
+
17
+ - [#332](https://github.com/nansen-ai/nansen-cli/pull/332) [`e9b6de1`](https://github.com/nansen-ai/nansen-cli/commit/e9b6de17fdba3f46733dff026c2495c095bfbf35) Thanks [@0xlaveen](https://github.com/0xlaveen)! - docs: add trading examples and Privy wallet setup to README
18
+
19
+ ## 1.20.0
20
+
21
+ ### Minor Changes
22
+
23
+ - [#302](https://github.com/nansen-ai/nansen-cli/pull/302) [`3f0a5ab`](https://github.com/nansen-ai/nansen-cli/commit/3f0a5abad463c0386122efbe746809913aa823ba) Thanks [@arein](https://github.com/arein)! - Add post-install onboarding that interactively offers to install the Nansen AI coding skill and run a test query after `npm install -g nansen-cli`. Non-interactive environments (CI, piped stdin) receive a one-liner tip and are never blocked.
24
+
25
+ ### Patch Changes
26
+
27
+ - [#313](https://github.com/nansen-ai/nansen-cli/pull/313) [`bb4d9e4`](https://github.com/nansen-ai/nansen-cli/commit/bb4d9e475158147645cae9b8bdd2555568a1e515) Thanks [@0xlaveen](https://github.com/0xlaveen)! - Update API key setup URL from app.nansen.ai/api to app.nansen.ai/auth/agent-setup across CLI help text, error messages, README, and postinstall script.
28
+
3
29
  ## 1.19.0
4
30
 
5
31
  ### Minor Changes
package/README.md CHANGED
@@ -15,11 +15,13 @@ npx skills add nansen-ai/nansen-cli # load agent skill files
15
15
  ## Auth
16
16
 
17
17
  ```bash
18
- nansen login # interactive saves to ~/.nansen/config.json
19
- export NANSEN_API_KEY=... # or env var (highest priority)
18
+ nansen login --api-key <key> # save key to ~/.nansen/config.json
19
+ nansen login --human # interactive prompt
20
+ export NANSEN_API_KEY=... # env var (highest priority)
21
+ nansen logout # remove saved key
20
22
  ```
21
23
 
22
- Get your API key at [app.nansen.ai/api](https://app.nansen.ai/api). AI agents can use the [Agent Setup](https://app.nansen.ai/auth/agent-setup) flow instead.
24
+ Get your API key at [app.nansen.ai/auth/agent-setup](https://app.nansen.ai/auth/agent-setup).
23
25
 
24
26
  ## Commands
25
27
 
@@ -34,10 +36,35 @@ nansen schema [command] [--pretty] # full command reference (no API key neede
34
36
 
35
37
  **Trade:** `quote`, `execute` — DEX swaps on Solana and Base.
36
38
 
37
- **Wallet:** `create`, `list`, `show`, `export`, `default`, `delete`, `send` — local keypairs (EVM + Solana).
39
+ **Wallet:** `create`, `list`, `show`, `export`, `default`, `delete`, `send` — local or Privy server-side wallets (EVM + Solana).
38
40
 
39
41
  Run `nansen schema --pretty` for the full subcommand and field reference.
40
42
 
43
+ ## Trading
44
+
45
+ DEX swaps on `solana` and `base`. Two-step: quote then execute.
46
+
47
+ ```bash
48
+ nansen trade quote --chain solana --from SOL --to USDC --amount 1000000000
49
+ nansen trade execute --quote <quoteId>
50
+ ```
51
+
52
+ Amounts are in base units (lamports, wei). Common symbols (`SOL`, `ETH`, `USDC`, `USDT`) resolve automatically. A wallet is required — set one with `nansen wallet default <name>`.
53
+
54
+ ## Wallet
55
+
56
+ ```bash
57
+ nansen wallet create --name my-wallet # local keypair (EVM + Solana)
58
+ nansen wallet create --name my-wallet --provider privy # server-side via Privy
59
+ nansen wallet list
60
+ nansen wallet default <name>
61
+ nansen wallet send --wallet <name> --to <addr> --amount <n> --chain <chain>
62
+ ```
63
+
64
+ **Local wallets** are password-encrypted. Set `NANSEN_WALLET_PASSWORD` to skip the prompt.
65
+
66
+ **Privy wallets** are server-side — no password, no local key storage. Requires `PRIVY_APP_ID` and `PRIVY_APP_SECRET` env vars. Get credentials at [dashboard.privy.io](https://dashboard.privy.io).
67
+
41
68
  ## Key Options
42
69
 
43
70
  | Option | Description |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nansen-cli",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "description": "Command-line interface for Nansen API - designed for AI agents",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -12,9 +12,11 @@
12
12
  "src/**/*.json",
13
13
  "!src/__tests__/**",
14
14
  "skills/**",
15
+ "scripts/postinstall.js",
15
16
  "CHANGELOG.md"
16
17
  ],
17
18
  "scripts": {
19
+ "postinstall": "node scripts/postinstall.js",
18
20
  "start": "node src/index.js",
19
21
  "pretest": "node scripts/check-changeset.js",
20
22
  "test": "vitest run",
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Post-install onboarding for nansen-cli.
5
+ *
6
+ * Runs after `npm install -g nansen-cli` and offers two optional steps:
7
+ * 1. Install the Nansen AI coding skill (`npx skills add nansen-ai/nansen-cli`)
8
+ * 2. Check account status to verify the API key works (0 credits)
9
+ *
10
+ * Non-interactive environments (CI, piped stdin) get a one-liner tip instead.
11
+ * Always exits 0 — onboarding failures must never break installation.
12
+ */
13
+
14
+ import { createInterface } from "readline";
15
+ import { execFileSync, spawn } from "child_process";
16
+ import { existsSync, readFileSync } from "fs";
17
+ import { join, dirname } from "path";
18
+ import { fileURLToPath } from "url";
19
+
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+
22
+ const BOLD = "\x1b[1m";
23
+ const DIM = "\x1b[2m";
24
+ const GREEN = "\x1b[32m";
25
+ const YELLOW = "\x1b[33m";
26
+ const CYAN = "\x1b[36m";
27
+ const RESET = "\x1b[0m";
28
+
29
+ const SKILL_REPO = "nansen-ai/nansen-cli";
30
+ const TEST_QUERY = ["account"];
31
+ const TEST_QUERY_DISPLAY = "nansen account";
32
+
33
+ // Path to the CLI entry point (works even if `nansen` bin isn't linked yet)
34
+ const CLI_ENTRY = join(__dirname, "..", "src", "index.js");
35
+
36
+ function log(msg = "") {
37
+ process.stderr.write(` ${msg}\n`);
38
+ }
39
+
40
+ function hasTTY() {
41
+ return process.stdin.isTTY && process.stderr.isTTY;
42
+ }
43
+
44
+ function hasNpx() {
45
+ try {
46
+ execFileSync("npx", ["--version"], { stdio: "ignore", shell: process.platform === "win32" });
47
+ return true;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ function isLoggedIn() {
54
+ const home = process.env.HOME || process.env.USERPROFILE || "";
55
+ const configFile = join(home, ".nansen", "config.json");
56
+ if (!existsSync(configFile)) return false;
57
+ try {
58
+ const config = JSON.parse(readFileSync(configFile, "utf8"));
59
+ return !!(config.apiKey || config.api_key);
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ function isSkillInstalled() {
66
+ const home = process.env.HOME || process.env.USERPROFILE || "";
67
+ const locations = [
68
+ join(home, ".claude", "skills", "nansen-cli"),
69
+ join(home, ".claude", "skills", "nansen-ai--nansen-cli"),
70
+ ];
71
+ return locations.some((loc) => existsSync(loc));
72
+ }
73
+
74
+ function prompt(question) {
75
+ return new Promise((resolve) => {
76
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
77
+ let answered = false;
78
+ rl.on("close", () => { if (!answered) resolve(""); });
79
+ rl.question(question, (answer) => {
80
+ answered = true;
81
+ rl.close();
82
+ resolve(answer.trim());
83
+ });
84
+ });
85
+ }
86
+
87
+ function runCommand(cmd, args) {
88
+ return new Promise((resolve) => {
89
+ const child = spawn(cmd, args, { stdio: "inherit", shell: process.platform === "win32" });
90
+ child.on("close", (code) => resolve(code === 0));
91
+ child.on("error", () => resolve(false));
92
+ });
93
+ }
94
+
95
+ async function installSkill() {
96
+ if (isSkillInstalled()) {
97
+ log(`${GREEN}✓${RESET} Nansen skill already installed.`);
98
+ return;
99
+ }
100
+
101
+ if (!hasNpx()) {
102
+ log(`${DIM}Tip: Run 'npx skills add ${SKILL_REPO}' to install the Nansen AI coding skill.${RESET}`);
103
+ return;
104
+ }
105
+
106
+ log(`The Nansen skill lets AI coding agents (Cursor, Claude Code, etc.) query`);
107
+ log(`on-chain data, track smart money, and analyze tokens on your behalf.`);
108
+ const answer = await prompt(` Install Nansen skill for your AI coding agent? [Y/n] `);
109
+
110
+ if (/^n/i.test(answer)) {
111
+ log(`Skipped. You can install it later with: ${CYAN}npx skills add ${SKILL_REPO}${RESET}`);
112
+ return;
113
+ }
114
+
115
+ log(`Installing Nansen skill...`);
116
+ const ok = await runCommand("npx", ["-y", "skills", "add", SKILL_REPO]);
117
+ if (!ok) {
118
+ log(`${YELLOW}Skill installation failed. You can retry with: npx skills add ${SKILL_REPO}${RESET}`);
119
+ }
120
+ }
121
+
122
+ async function testQuery() {
123
+ if (!isLoggedIn()) {
124
+ log();
125
+ log(`Not logged in yet. Run ${CYAN}nansen login --api-key <key>${RESET} to authenticate.`);
126
+ log(`Get your API key at: ${CYAN}https://app.nansen.ai/auth/agent-setup${RESET}`);
127
+ return;
128
+ }
129
+
130
+ log();
131
+ log(`Your API key is configured. Let's verify it works.`);
132
+ const answer = await prompt(` Check account status? (${DIM}${TEST_QUERY_DISPLAY}${RESET}) [Y/n] `);
133
+
134
+ if (/^n/i.test(answer)) {
135
+ log(`Skipped. You're all set! Try: ${CYAN}nansen research smart-money netflow --chain solana${RESET}`);
136
+ return;
137
+ }
138
+
139
+ log(`Running: ${DIM}${TEST_QUERY_DISPLAY}${RESET}`);
140
+ log();
141
+ // Use process.execPath + CLI_ENTRY so it works even if `nansen` bin isn't linked yet
142
+ const ok = await runCommand(process.execPath, [CLI_ENTRY, ...TEST_QUERY, "--pretty"]);
143
+ if (ok) {
144
+ log();
145
+ log(`${GREEN}✓${RESET} All set! Run ${CYAN}nansen help${RESET} to see all available commands.`);
146
+ } else {
147
+ log();
148
+ log(`${YELLOW}Query failed. Check your API key with: nansen login --api-key <key>${RESET}`);
149
+ }
150
+ }
151
+
152
+ async function main() {
153
+ // Only run for global installs; skip local npm install / npm ci
154
+ if (process.env.npm_lifecycle_event === "postinstall" && process.env.npm_config_global !== "true") {
155
+ return;
156
+ }
157
+
158
+ log();
159
+
160
+ if (!hasTTY()) {
161
+ log(`${BOLD}Nansen CLI installed!${RESET}`);
162
+ log();
163
+ log(`Tip: Run '${CYAN}npx skills add ${SKILL_REPO}${RESET}' to install the Nansen AI coding skill.`);
164
+ log(`Tip: Run '${CYAN}nansen login --api-key <key>${RESET}' to authenticate.`);
165
+ return;
166
+ }
167
+
168
+ log(`${BOLD}Nansen CLI installed!${RESET}`);
169
+ log();
170
+
171
+ await installSkill();
172
+ await testQuery();
173
+
174
+ log();
175
+ }
176
+
177
+ main().catch(() => {
178
+ // Never fail installation due to onboarding errors
179
+ });
@@ -58,7 +58,9 @@ nansen trade execute --quote "$quote_id"
58
58
  | ETH | Base | `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` |
59
59
  | USDC | Base | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
60
60
 
61
- ## Amounts are in base units
61
+ ## Amounts are in base units — NEVER USD
62
+
63
+ `--amount` accepts **integer base units only** (lamports, wei, etc). It is never a USD value.
62
64
 
63
65
  | Token | Decimals | 1 token = |
64
66
  |-------|----------|-----------|
@@ -66,6 +68,8 @@ nansen trade execute --quote "$quote_id"
66
68
  | ETH | 18 | `1000000000000000000` |
67
69
  | USDC | 6 | `1000000` |
68
70
 
71
+ If the user says "$20 worth of X", you must convert USD → token amount → base units. For example, to buy $20 of SOL at $150/SOL: $20 ÷ $150 = 0.1333 SOL = 133,300,000 lamports → `--amount 133300000`. Use a price lookup (e.g. `nansen research token info`) to get the current price first.
72
+
69
73
  ## Flags
70
74
 
71
75
  | Flag | Purpose |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: nansen-wallet-manager
3
- description: Wallet management — create, list, show, export, send, delete. Use when creating wallets, checking balances, or sending tokens.
3
+ description: Wallet management — create (local or Privy server-side), list, show, export, send, delete. Use when creating wallets, checking balances, or sending tokens.
4
4
  metadata:
5
5
  openclaw:
6
6
  requires:
@@ -30,7 +30,49 @@ NANSEN_API_KEY=<key> nansen login
30
30
  nansen research profiler labels --address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --chain ethereum
31
31
  ```
32
32
 
33
- ## Wallet Creation (Two-Step Agent Flow)
33
+ ## Wallet Providers
34
+
35
+ The CLI supports two wallet providers:
36
+
37
+ | | **Local** (default) | **Privy** (server-side) |
38
+ |---|---|---|
39
+ | Key storage | Encrypted on disk | Server-side via Privy API |
40
+ | Password required | Yes (min 12 chars) | No |
41
+ | Export private keys | Yes (`wallet export`) | No — keys are managed by Privy |
42
+ | Best for | Human users, manual trading | Agents, automated workflows |
43
+ | Flag | `--provider local` (default) | `--provider privy` |
44
+ | Required env vars | `NANSEN_WALLET_PASSWORD` | `PRIVY_APP_ID` + `PRIVY_APP_SECRET` |
45
+
46
+ ## Privy Wallet Creation
47
+
48
+ Privy wallets are server-side wallets managed by the Privy API. No password is needed — keys never touch the local machine.
49
+
50
+ ### Prerequisites
51
+
52
+ The following environment variables must be set:
53
+
54
+ | Var | Purpose |
55
+ |-----|---------|
56
+ | `PRIVY_APP_ID` | Privy application ID |
57
+ | `PRIVY_APP_SECRET` | Privy application secret |
58
+
59
+ ### Create a Privy wallet
60
+
61
+ ```bash
62
+ nansen wallet create --provider privy
63
+ # Or with a custom name:
64
+ nansen wallet create --name agent-wallet --provider privy
65
+ ```
66
+
67
+ ### Critical rules for agents (Privy)
68
+
69
+ - **No password needed** — Privy manages keys server-side
70
+ - **Cannot export keys** — `wallet export` only works for local wallets
71
+ - All other operations (`list`, `show`, `send`, `delete`, `default`) work identically for both providers
72
+
73
+ ## Local Wallet Creation (Two-Step Agent Flow)
74
+
75
+ > This section covers **local** wallet creation. For Privy server-side wallets, see the [Privy Wallet Creation](#privy-wallet-creation) section above — no password is needed.
34
76
 
35
77
  Wallet creation requires a password from the **human user**. The agent must NOT generate or store the password itself.
36
78
 
@@ -67,6 +109,18 @@ If the `.credentials` file fallback is used, the CLI prints a warning on every o
67
109
 
68
110
  ## Create
69
111
 
112
+ ### Privy (server-side, no password)
113
+
114
+ ```bash
115
+ nansen wallet create --provider privy
116
+ # Or with a custom name:
117
+ nansen wallet create --name trading --provider privy
118
+ ```
119
+
120
+ Requires `PRIVY_APP_ID` + `PRIVY_APP_SECRET` env vars. No password needed.
121
+
122
+ ### Local (encrypted on disk, password required)
123
+
70
124
  ```bash
71
125
  # Ask the user for a password first, then:
72
126
  NANSEN_WALLET_PASSWORD="<password_from_user>" nansen wallet create
@@ -127,6 +181,7 @@ For detailed migration steps (from `~/.nansen/.env`, `.credentials`, or env-var-
127
181
  | `--chain` | `evm` or `solana` |
128
182
  | `--max` | Send entire balance |
129
183
  | `--dry-run` | Preview without broadcasting |
184
+ | `--provider` | Wallet provider: `local` (default, encrypted on disk) or `privy` (server-side via Privy API) |
130
185
  | `--human` | Enable interactive prompts (human terminal use only — agents must NOT use this) |
131
186
  | `--unsafe-no-password` | Skip encryption (keys stored in plaintext — NOT recommended) |
132
187
 
@@ -136,5 +191,8 @@ For detailed migration steps (from `~/.nansen/.env`, `.credentials`, or env-var-
136
191
  |-----|---------|
137
192
  | `NANSEN_WALLET_PASSWORD` | Wallet encryption password — only needed for initial `wallet create`. After that, the OS keychain handles it. |
138
193
  | `NANSEN_API_KEY` | API key (also set via `nansen login --api-key <key>`) |
194
+ | `PRIVY_APP_ID` | Privy application ID (required for `--provider privy`) |
195
+ | `PRIVY_APP_SECRET` | Privy application secret (required for `--provider privy`) |
196
+ | `NANSEN_WALLET_PROVIDER` | Default provider for wallet create — `local` or `privy` |
139
197
  | `NANSEN_EVM_RPC` | Custom EVM RPC endpoint |
140
198
  | `NANSEN_SOLANA_RPC` | Custom Solana RPC endpoint |
package/src/api.js CHANGED
@@ -11,12 +11,12 @@ import { getAnonymousId, TELEMETRY_DISABLED } from './telemetry.js';
11
11
 
12
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
13
 
14
- function telemetryHeaders() {
14
+ export function telemetryHeaders() {
15
15
  if (TELEMETRY_DISABLED) return {};
16
16
  return { 'X-Anonymous-Id': getAnonymousId() };
17
17
  }
18
18
 
19
- const { version: packageVersion } = JSON.parse(
19
+ export const { version: packageVersion } = JSON.parse(
20
20
  fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')
21
21
  );
22
22
 
@@ -85,7 +85,7 @@ export class NansenError extends Error {
85
85
  /**
86
86
  * Map HTTP status codes to error codes
87
87
  */
88
- function statusToErrorCode(status, data = {}) {
88
+ export function statusToErrorCode(status, data = {}) {
89
89
  const message = data?.message || data?.error || '';
90
90
  const messageLower = message.toLowerCase();
91
91
 
@@ -486,7 +486,7 @@ export class NansenAPI {
486
486
  return cached;
487
487
  }
488
488
  }
489
-
489
+
490
490
  let lastError;
491
491
 
492
492
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
@@ -630,7 +630,7 @@ export class NansenAPI {
630
630
  } catch (x402Err) {
631
631
  if (!this.apiKey) {
632
632
  message = 'No API key configured. Two ways to authenticate:\n' +
633
- ' 1. API key: nansen login --api-key <key> (get key at https://app.nansen.ai/api)\n' +
633
+ ' 1. API key: nansen login --api-key <key> (get key at https://app.nansen.ai/auth/agent-setup)\n' +
634
634
  ' 2. x402 micropayment: nansen wallet create + fund with USDC (no API key needed)';
635
635
  } else {
636
636
  message = `x402 auto-payment failed: ${x402Err.message}`;
package/src/cli.js CHANGED
@@ -7,9 +7,11 @@ import { NansenAPI, NansenError, ErrorCode, saveConfig, deleteConfig, getConfigF
7
7
  import { buildWalletCommands } from './wallet.js';
8
8
  import { buildTradingCommands } from './trading.js';
9
9
  import { formatAlertsTable, buildAlertsCommands } from './commands/alerts.js';
10
+ import { buildAgentCommands } from './commands/agent.js';
10
11
  import { resolveAddress, isEnsName } from './ens.js';
11
12
  import fs from 'fs';
12
13
  import { getUpdateNotification, getUpgradeNotice, scheduleUpdateCheck } from './update-check.js';
14
+ import { refreshCostMapIfStale, getCostForEndpoint } from './cost-cache.js';
13
15
  import { trackCommandSucceeded, trackCommandFailed } from './telemetry.js';
14
16
  import { createRequire } from 'module';
15
17
  import * as readline from 'readline';
@@ -165,7 +167,7 @@ export function parseArgs(args) {
165
167
  const key = arg.slice(2);
166
168
  const next = args[i + 1];
167
169
 
168
- if (key === 'pretty' || key === 'help' || key === 'version' || key === 'table' || key === 'no-retry' || key === 'cache' || key === 'no-cache' || key === 'stream' || key === 'enrich' || key === 'full' || key === 'human' || key === 'enabled' || key === 'disabled') {
170
+ if (key === 'pretty' || key === 'help' || key === 'version' || key === 'table' || key === 'no-retry' || key === 'cache' || key === 'no-cache' || key === 'stream' || key === 'enrich' || key === 'full' || key === 'human' || key === 'enabled' || key === 'disabled' || key === 'expert' || key === 'json') {
169
171
  result.flags[key] = true;
170
172
  } else if (next && (!next.startsWith('-') || /^-\d/.test(next))) {
171
173
  // Try to parse as JSON first (for objects/arrays/booleans),
@@ -682,10 +684,11 @@ COMMANDS:
682
684
  research smart-money, profiler, token, search, perp, portfolio, points
683
685
  trade quote, execute
684
686
  wallet create, list, show, export, default, delete, forget-password
687
+ agent Ask the Nansen AI research agent (fast/expert modes)
685
688
  alerts list, create, update, toggle, delete
686
689
  web search, fetch
687
690
  account Show API key status, plan, and remaining credits
688
- login Save API key (--api-key <key> or NANSEN_API_KEY env var)
691
+ login Save API key (--api-key <key>, --human, or NANSEN_API_KEY env var)
689
692
  logout Remove saved API key
690
693
  schema JSON schema for all commands (use "nansen schema <cmd>" for one)
691
694
  cache clear
@@ -701,6 +704,10 @@ EXAMPLES:
701
704
  nansen research profiler balance --address 0x... --chain ethereum
702
705
  nansen trade quote --chain base --from ETH --to USDC --amount 1000000000000000000
703
706
 
707
+ DEPRECATED ALIASES (still work, will be removed in a future version):
708
+ smart-money, profiler, token, search, perp, portfolio, points → use "nansen research <command>"
709
+ quote, execute → use "nansen trade <command>"
710
+
704
711
  Research chains: ethereum, solana, base, bnb, arbitrum, polygon, optimism, avalanche, linea, scroll, mantle, ronin, sei, plasma, sonic, monad, hyperevm, iotaevm
705
712
  Trade chains: solana, base
706
713
  Labels: Fund, Smart Trader, 30D/90D/180D Smart Trader, Smart HL Perps Trader
@@ -859,7 +866,7 @@ export function buildCommands(deps = {}) {
859
866
  log(' --api-key <key> Your Nansen API key');
860
867
  log(' --human Enable interactive prompt');
861
868
  log(' --help Show this help\n');
862
- log('Get your API key at: https://app.nansen.ai/api');
869
+ log('Get your API key at: https://app.nansen.ai/auth/agent-setup');
863
870
  return;
864
871
  }
865
872
 
@@ -879,7 +886,7 @@ export function buildCommands(deps = {}) {
879
886
  return;
880
887
  }
881
888
  log('Nansen CLI Login\n');
882
- log('Get your API key at: https://app.nansen.ai/api\n');
889
+ log('Get your API key at: https://app.nansen.ai/auth/agent-setup\n');
883
890
  apiKey = await promptFn('Enter your API key: ', true);
884
891
  }
885
892
 
@@ -890,7 +897,7 @@ export function buildCommands(deps = {}) {
890
897
  resolution: [
891
898
  'Run: nansen login --api-key <key>',
892
899
  'Or set NANSEN_API_KEY environment variable',
893
- 'Get your API key at: https://app.nansen.ai/api',
900
+ 'Get your API key at: https://app.nansen.ai/auth/agent-setup',
894
901
  ],
895
902
  }));
896
903
  exit(1);
@@ -912,7 +919,7 @@ export function buildCommands(deps = {}) {
912
919
  log(JSON.stringify({
913
920
  error: 'INVALID_API_KEY',
914
921
  message: 'The API key is not valid.',
915
- resolution: ['Check your key at https://app.nansen.ai/api']
922
+ resolution: ['Check your key at https://app.nansen.ai/auth/agent-setup']
916
923
  }));
917
924
  } else {
918
925
  log(JSON.stringify({
@@ -1509,6 +1516,11 @@ export function generateSubcommandHelp(command, subcommand, prefix = null) {
1509
1516
  lines.push(`Params (* required): ${params.join(', ')}`);
1510
1517
  }
1511
1518
 
1519
+ if (subSchema.endpoint) {
1520
+ const cost = getCostForEndpoint(subSchema.endpoint);
1521
+ if (cost) lines.push(`Cost: ${cost.free} credit${cost.free === 1 ? '' : 's'} (Free tier) / ${cost.pro} credit${cost.pro === 1 ? '' : 's'} (Pro tier)`);
1522
+ }
1523
+
1512
1524
  if (subSchema.returns?.length) {
1513
1525
  lines.push(`Returns: ${subSchema.returns.join(', ')}`);
1514
1526
  }
@@ -1548,12 +1560,7 @@ export async function runCLI(rawArgs, deps = {}) {
1548
1560
  const subArgs = positional.slice(1);
1549
1561
  const subcommand = subArgs[0];
1550
1562
 
1551
- // Deprecation warnings for commands that moved under 'research' or 'trade'
1552
- if (DEPRECATED_TO_RESEARCH.has(command)) {
1553
- errorOutput(`Warning: "nansen ${command}" is deprecated. Use "nansen research ${command}" instead.`);
1554
- } else if (DEPRECATED_TO_TRADE.has(command)) {
1555
- errorOutput(`Warning: "nansen ${command}" is deprecated. Use "nansen trade ${command}" instead.`);
1556
- }
1563
+
1557
1564
 
1558
1565
  const pretty = flags.pretty || flags.p;
1559
1566
  const table = flags.table || flags.t;
@@ -1569,7 +1576,14 @@ export async function runCLI(rawArgs, deps = {}) {
1569
1576
  if (updateNotification) errorOutput(updateNotification);
1570
1577
  };
1571
1578
 
1572
- const commands = { ...buildCommands(deps), ...buildWalletCommands(deps), ...buildTradingCommands(deps), ...buildAlertsCommands(deps), ...commandOverrides };
1579
+ // Deprecation note for help output
1580
+ const deprecationNote = (cmd) => {
1581
+ if (DEPRECATED_TO_RESEARCH.has(cmd)) return `Note: "nansen ${cmd}" is deprecated. Use "nansen research ${cmd}" instead.\n\n`;
1582
+ if (DEPRECATED_TO_TRADE.has(cmd)) return `Note: "nansen ${cmd}" is deprecated. Use "nansen trade ${cmd}" instead.\n\n`;
1583
+ return '';
1584
+ };
1585
+
1586
+ const commands = { ...buildCommands(deps), ...buildWalletCommands(deps), ...buildTradingCommands(deps), ...buildAlertsCommands(deps), ...buildAgentCommands(deps), ...commandOverrides };
1573
1587
 
1574
1588
  if (flags.version || flags.v) {
1575
1589
  output(VERSION);
@@ -1577,6 +1591,7 @@ export async function runCLI(rawArgs, deps = {}) {
1577
1591
  }
1578
1592
 
1579
1593
  if (command === 'help' || flags.help || flags.h) {
1594
+ await refreshCostMapIfStale();
1580
1595
  // Check for subcommand-specific help: nansen <command> <subcommand> --help
1581
1596
  if (flags.help || flags.h) {
1582
1597
  // Handle 'research <category> <sub> --help' (3-level)
@@ -1607,17 +1622,17 @@ export async function runCLI(rawArgs, deps = {}) {
1607
1622
  }
1608
1623
  // First try subcommand help
1609
1624
  // Skip for 'trade'/'alerts' — their handlers show their own rich usage
1610
- if (command && subcommand && command !== 'trade' && command !== 'alerts') {
1625
+ if (command && subcommand && command !== 'trade' && command !== 'alerts' && command !== 'agent') {
1611
1626
  const subHelp = generateSubcommandHelp(command, subcommand);
1612
1627
  if (subHelp) {
1613
- output(subHelp);
1628
+ output(deprecationNote(command) + subHelp);
1614
1629
  notify();
1615
1630
  return { type: 'subcommand-help', command, subcommand };
1616
1631
  }
1617
1632
  }
1618
1633
  // Then try command-level help (list subcommands)
1619
1634
  // Skip for 'trade'/'alerts' — let the handler show its own usage
1620
- const cmdSchemaLookup = command !== 'trade' && command !== 'alerts' && (SCHEMA.commands[command] || SCHEMA.commands.research.subcommands[command]);
1635
+ const cmdSchemaLookup = command !== 'trade' && command !== 'alerts' && command !== 'agent' && (SCHEMA.commands[command] || SCHEMA.commands.research.subcommands[command]);
1621
1636
  if (command && cmdSchemaLookup) {
1622
1637
  const cmdSchema = cmdSchemaLookup;
1623
1638
  const lines = [`${command} — ${cmdSchema.description}`];
@@ -1638,7 +1653,7 @@ export async function runCLI(rawArgs, deps = {}) {
1638
1653
  if (cmdSchema.examples?.length) {
1639
1654
  lines.push(`\nExamples:\n ${cmdSchema.examples.join('\n ')}`);
1640
1655
  }
1641
- output(lines.join('\n'));
1656
+ output(deprecationNote(command) + lines.join('\n'));
1642
1657
  notify();
1643
1658
  return { type: 'command-help', command };
1644
1659
  }
@@ -1681,7 +1696,6 @@ export async function runCLI(rawArgs, deps = {}) {
1681
1696
  const formatted = formatOutput(errorData, { pretty, table });
1682
1697
  output(formatted.text);
1683
1698
  trackCommandFailed({ command: fullCommand, duration_ms: Date.now() - startTime, error_code: 'UNKNOWN_COMMAND', flags: usedFlags, chain });
1684
- notify();
1685
1699
  exit(1);
1686
1700
  return { type: 'error', data: errorData };
1687
1701
  }
@@ -1709,7 +1723,6 @@ export async function runCLI(rawArgs, deps = {}) {
1709
1723
  // Commands that handle their own output return undefined
1710
1724
  if (result === undefined) {
1711
1725
  trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, flags: usedFlags, chain });
1712
- notify();
1713
1726
  return { type: 'no-output', command };
1714
1727
  }
1715
1728
 
@@ -1718,7 +1731,6 @@ export async function runCLI(rawArgs, deps = {}) {
1718
1731
  const formatted = formatOutput(result, { pretty, table: false });
1719
1732
  output(formatted.text);
1720
1733
  trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, flags: usedFlags, chain });
1721
- notify();
1722
1734
  return { type: 'schema', data: result };
1723
1735
  }
1724
1736
 
@@ -1731,7 +1743,6 @@ export async function runCLI(rawArgs, deps = {}) {
1731
1743
  // Alerts list with --table uses custom table format
1732
1744
  if (command === 'alerts' && subcommand === 'list' && table) {
1733
1745
  output(formatAlertsTable(result));
1734
- notify();
1735
1746
  return { type: 'success', data: result };
1736
1747
  }
1737
1748
 
@@ -1743,7 +1754,6 @@ export async function runCLI(rawArgs, deps = {}) {
1743
1754
  output(streamOutput);
1744
1755
  }
1745
1756
  trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, from_cache: !!result?.fromCache, flags: usedFlags, chain });
1746
- notify();
1747
1757
  return { type: 'stream', data: result };
1748
1758
  }
1749
1759
 
@@ -1751,7 +1761,6 @@ export async function runCLI(rawArgs, deps = {}) {
1751
1761
  const formatted = formatOutput(successData, { pretty, table, csv });
1752
1762
  output(formatted.text);
1753
1763
  trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, from_cache: !!result?.fromCache, flags: usedFlags, chain });
1754
- notify();
1755
1764
  return { type: csv ? 'csv' : 'success', data: result };
1756
1765
  } catch (error) {
1757
1766
  const errorData = formatError(error);
@@ -1765,7 +1774,6 @@ export async function runCLI(rawArgs, deps = {}) {
1765
1774
  flags: usedFlags,
1766
1775
  chain,
1767
1776
  });
1768
- notify();
1769
1777
  exit(1);
1770
1778
  return { type: 'error', data: errorData };
1771
1779
  }