nansen-cli 1.19.0 → 1.20.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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#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.
8
+
9
+ ### Patch Changes
10
+
11
+ - [#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.
12
+
3
13
  ## 1.19.0
4
14
 
5
15
  ### 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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nansen-cli",
3
- "version": "1.19.0",
3
+ "version": "1.20.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
+ });
@@ -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
 
@@ -127,6 +169,7 @@ For detailed migration steps (from `~/.nansen/.env`, `.credentials`, or env-var-
127
169
  | `--chain` | `evm` or `solana` |
128
170
  | `--max` | Send entire balance |
129
171
  | `--dry-run` | Preview without broadcasting |
172
+ | `--provider` | Wallet provider: `local` (default, encrypted on disk) or `privy` (server-side via Privy API) |
130
173
  | `--human` | Enable interactive prompts (human terminal use only — agents must NOT use this) |
131
174
  | `--unsafe-no-password` | Skip encryption (keys stored in plaintext — NOT recommended) |
132
175
 
@@ -136,5 +179,8 @@ For detailed migration steps (from `~/.nansen/.env`, `.credentials`, or env-var-
136
179
  |-----|---------|
137
180
  | `NANSEN_WALLET_PASSWORD` | Wallet encryption password — only needed for initial `wallet create`. After that, the OS keychain handles it. |
138
181
  | `NANSEN_API_KEY` | API key (also set via `nansen login --api-key <key>`) |
182
+ | `PRIVY_APP_ID` | Privy application ID (required for `--provider privy`) |
183
+ | `PRIVY_APP_SECRET` | Privy application secret (required for `--provider privy`) |
184
+ | `NANSEN_WALLET_PROVIDER` | Default provider for wallet create — `local` or `privy` |
139
185
  | `NANSEN_EVM_RPC` | Custom EVM RPC endpoint |
140
186
  | `NANSEN_SOLANA_RPC` | Custom Solana RPC endpoint |
package/src/api.js CHANGED
@@ -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
@@ -685,7 +685,7 @@ COMMANDS:
685
685
  alerts list, create, update, toggle, delete
686
686
  web search, fetch
687
687
  account Show API key status, plan, and remaining credits
688
- login Save API key (--api-key <key> or NANSEN_API_KEY env var)
688
+ login Save API key (--api-key <key>, --human, or NANSEN_API_KEY env var)
689
689
  logout Remove saved API key
690
690
  schema JSON schema for all commands (use "nansen schema <cmd>" for one)
691
691
  cache clear
@@ -859,7 +859,7 @@ export function buildCommands(deps = {}) {
859
859
  log(' --api-key <key> Your Nansen API key');
860
860
  log(' --human Enable interactive prompt');
861
861
  log(' --help Show this help\n');
862
- log('Get your API key at: https://app.nansen.ai/api');
862
+ log('Get your API key at: https://app.nansen.ai/auth/agent-setup');
863
863
  return;
864
864
  }
865
865
 
@@ -879,7 +879,7 @@ export function buildCommands(deps = {}) {
879
879
  return;
880
880
  }
881
881
  log('Nansen CLI Login\n');
882
- log('Get your API key at: https://app.nansen.ai/api\n');
882
+ log('Get your API key at: https://app.nansen.ai/auth/agent-setup\n');
883
883
  apiKey = await promptFn('Enter your API key: ', true);
884
884
  }
885
885
 
@@ -890,7 +890,7 @@ export function buildCommands(deps = {}) {
890
890
  resolution: [
891
891
  'Run: nansen login --api-key <key>',
892
892
  'Or set NANSEN_API_KEY environment variable',
893
- 'Get your API key at: https://app.nansen.ai/api',
893
+ 'Get your API key at: https://app.nansen.ai/auth/agent-setup',
894
894
  ],
895
895
  }));
896
896
  exit(1);
@@ -912,7 +912,7 @@ export function buildCommands(deps = {}) {
912
912
  log(JSON.stringify({
913
913
  error: 'INVALID_API_KEY',
914
914
  message: 'The API key is not valid.',
915
- resolution: ['Check your key at https://app.nansen.ai/api']
915
+ resolution: ['Check your key at https://app.nansen.ai/auth/agent-setup']
916
916
  }));
917
917
  } else {
918
918
  log(JSON.stringify({