omnitrade-mcp 0.4.4 → 0.6.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.
Files changed (3) hide show
  1. package/dist/cli.js +214 -101
  2. package/dist/index.js +54 -9
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
5
5
  import { homedir } from "os";
6
6
  import { join } from "path";
7
7
  import * as readline from "readline";
8
- var VERSION = "0.4.1";
8
+ var VERSION = "0.5.0";
9
9
  var CONFIG_PATH = join(homedir(), ".omnitrade", "config.json");
10
10
  var c = {
11
11
  reset: "\x1B[0m",
@@ -21,153 +21,266 @@ var c = {
21
21
  orange: "\x1B[38;5;208m",
22
22
  red: "\x1B[38;5;196m"
23
23
  };
24
- function printLogo() {
24
+ function printBanner() {
25
25
  console.log(`
26
- ${c.cyan}+---------------------------------------------------------------+
27
- | |
28
- | ${c.white}${c.bold} ___ __ __ _ _ ${c.purple}_${c.white} _____ ____ _ ____ _____ ${c.reset} ${c.cyan}|
29
- | ${c.white}${c.bold} / _ \\| \\/ | \\ | |${c.purple}| |${c.white}_ _| _ \\ / \\ | _ \\| ____| ${c.reset} ${c.cyan}|
30
- | ${c.white}${c.bold}| | | | |\\/| | \\| |${c.purple}| |${c.white} | | | |_) | / _ \\ | | | | _| ${c.reset} ${c.cyan}|
31
- | ${c.white}${c.bold}| |_| | | | | |\\ |${c.purple}| |${c.white} | | | _ < / ___ \\| |_| | |___ ${c.reset} ${c.cyan}|
32
- | ${c.white}${c.bold} \\___/|_| |_|_| \\_|${c.purple}|_|${c.white} |_| |_| \\_\\/_/ \\_\\____/|_____| ${c.reset} ${c.cyan}|
33
- | |
34
- | ${c.gray}-----------------------------------------------------${c.reset} ${c.cyan}|
35
- | |
36
- | ${c.white}One AI.${c.reset} ${c.cyan}107 Exchanges.${c.reset} ${c.purple}Natural Language Trading.${c.reset} ${c.cyan}|
37
- | |
38
- | ${c.gray}v${VERSION}${c.reset} ${c.gray}Connectry Labs${c.reset} ${c.cyan}|
39
- | |
40
- +---------------------------------------------------------------+${c.reset}
26
+ ${c.cyan}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${c.reset}
27
+ ${c.cyan}\u2551${c.reset} ${c.cyan}\u2551${c.reset}
28
+ ${c.cyan}\u2551${c.reset} ${c.white}${c.bold}O M N I${c.purple}T R A D E${c.reset} ${c.cyan}\u2551${c.reset}
29
+ ${c.cyan}\u2551${c.reset} ${c.dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset} ${c.cyan}\u2551${c.reset}
30
+ ${c.cyan}\u2551${c.reset} ${c.cyan}\u2551${c.reset}
31
+ ${c.cyan}\u2551${c.reset} ${c.white}Talk to your crypto.${c.reset} ${c.cyan}\u2551${c.reset}
32
+ ${c.cyan}\u2551${c.reset} ${c.dim}Connect any exchange to Claude with natural language.${c.reset} ${c.cyan}\u2551${c.reset}
33
+ ${c.cyan}\u2551${c.reset} ${c.cyan}\u2551${c.reset}
34
+ ${c.cyan}\u2551${c.reset} ${c.cyan}\u2022${c.reset} Check balances ${c.cyan}\u2022${c.reset} Track prices ${c.cyan}\u2022${c.reset} Execute trades ${c.cyan}\u2551${c.reset}
35
+ ${c.cyan}\u2551${c.reset} ${c.cyan}\u2022${c.reset} Portfolio value ${c.cyan}\u2022${c.reset} Find arbitrage ${c.cyan}\u2022${c.reset} 107 exchanges ${c.cyan}\u2551${c.reset}
36
+ ${c.cyan}\u2551${c.reset} ${c.cyan}\u2551${c.reset}
37
+ ${c.cyan}\u2551${c.reset} ${c.gray}v${VERSION}${c.reset} ${c.dim}Connectry Labs${c.reset} ${c.cyan}\u2551${c.reset}
38
+ ${c.cyan}\u2551${c.reset} ${c.cyan}\u2551${c.reset}
39
+ ${c.cyan}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${c.reset}
41
40
  `);
42
41
  }
43
42
  function printCompactLogo() {
44
43
  console.log(`
45
- ${c.cyan}+---------------------------------------------------------------+
46
- | ${c.white}${c.bold}OMNI${c.purple}TRADE${c.reset} ${c.gray}MCP${c.reset} - ${c.white}One AI.${c.reset} ${c.cyan}107 Exchanges.${c.reset} ${c.cyan}|
47
- +---------------------------------------------------------------+${c.reset}
44
+ ${c.cyan}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
45
+ ${c.white}${c.bold}OMNI${c.purple}TRADE${c.reset} ${c.gray}MCP${c.reset} ${c.dim}\u2022${c.reset} ${c.white}One AI.${c.reset} ${c.cyan}107 Exchanges.${c.reset}
46
+ ${c.cyan}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
48
47
  `);
49
48
  }
49
+ var EXCHANGE_INFO = {
50
+ binance: {
51
+ name: "Binance",
52
+ description: "Largest global exchange by volume",
53
+ apiUrl: "https://www.binance.com/en/my/settings/api-management",
54
+ testnetUrl: "https://testnet.binance.vision",
55
+ docsUrl: "https://www.binance.com/en/support/faq/how-to-create-api-keys-360002502072",
56
+ supportsOAuth: false,
57
+ needsPassphrase: false,
58
+ steps: [
59
+ "Log into Binance and go to API Management",
60
+ 'Click "Create API" and choose "System generated"',
61
+ "Complete 2FA verification",
62
+ "Enable permissions: \u2713 Read \u2713 Spot Trading \u2717 Withdrawals",
63
+ "Copy your API Key and Secret Key"
64
+ ]
65
+ },
66
+ coinbase: {
67
+ name: "Coinbase",
68
+ description: "US-based, beginner friendly",
69
+ apiUrl: "https://www.coinbase.com/settings/api",
70
+ docsUrl: "https://docs.cdp.coinbase.com/exchange/docs/authorization-and-authentication",
71
+ supportsOAuth: true,
72
+ needsPassphrase: true,
73
+ steps: [
74
+ "Go to Coinbase Settings \u2192 API",
75
+ 'Click "New API Key"',
76
+ "Select permissions: \u2713 View \u2713 Trade \u2717 Transfer",
77
+ "Complete 2FA verification",
78
+ "Copy API Key, Secret, and Passphrase"
79
+ ]
80
+ },
81
+ kraken: {
82
+ name: "Kraken",
83
+ description: "Security-focused, great for privacy",
84
+ apiUrl: "https://www.kraken.com/u/security/api",
85
+ docsUrl: "https://support.kraken.com/hc/en-us/articles/360000919966",
86
+ supportsOAuth: false,
87
+ needsPassphrase: false,
88
+ steps: [
89
+ "Go to Security \u2192 API",
90
+ 'Click "Add key"',
91
+ 'Name your key (e.g., "OmniTrade")',
92
+ "Enable: \u2713 Query Funds \u2713 Query Orders \u2713 Create Orders",
93
+ "Generate and copy your keys"
94
+ ]
95
+ },
96
+ bybit: {
97
+ name: "Bybit",
98
+ description: "Derivatives and spot trading",
99
+ apiUrl: "https://www.bybit.com/app/user/api-management",
100
+ testnetUrl: "https://testnet.bybit.com",
101
+ docsUrl: "https://www.bybit.com/en-US/help-center/article/How-to-create-your-API-key",
102
+ supportsOAuth: false,
103
+ needsPassphrase: false,
104
+ steps: [
105
+ "Go to Account \u2192 API Management",
106
+ 'Click "Create New Key"',
107
+ 'Choose "System-generated API Keys"',
108
+ "Set permissions: \u2713 Read \u2713 Trade \u2717 Withdraw",
109
+ "Complete verification and copy keys"
110
+ ]
111
+ },
112
+ okx: {
113
+ name: "OKX",
114
+ description: "Full-featured trading platform",
115
+ apiUrl: "https://www.okx.com/account/my-api",
116
+ docsUrl: "https://www.okx.com/help-center/how-to-create-your-api-key",
117
+ supportsOAuth: false,
118
+ needsPassphrase: true,
119
+ steps: [
120
+ "Go to Profile \u2192 API",
121
+ 'Click "Create API key"',
122
+ "Set a passphrase (you'll need this later)",
123
+ "Permissions: \u2713 Read \u2713 Trade \u2717 Withdraw",
124
+ "Copy API Key, Secret, and Passphrase"
125
+ ]
126
+ },
127
+ kucoin: {
128
+ name: "KuCoin",
129
+ description: "Great altcoin variety",
130
+ apiUrl: "https://www.kucoin.com/account/api",
131
+ docsUrl: "https://www.kucoin.com/support/360015102174",
132
+ supportsOAuth: false,
133
+ needsPassphrase: true,
134
+ steps: [
135
+ "Go to Account Security \u2192 API Management",
136
+ 'Click "Create API"',
137
+ "Set a passphrase (required for API calls)",
138
+ "Permissions: \u2713 General \u2713 Trade \u2717 Transfer",
139
+ "Copy API Key, Secret, and Passphrase"
140
+ ]
141
+ }
142
+ };
50
143
  function printHelp() {
51
- printLogo();
144
+ printBanner();
52
145
  console.log(`
146
+ ${c.white}${c.bold}QUICK START${c.reset}
147
+ ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
148
+
149
+ ${c.yellow}$${c.reset} ${c.green}${c.bold}omnitrade setup${c.reset} ${c.dim}\u2190 Start here! 2-minute wizard${c.reset}
150
+
53
151
  ${c.white}${c.bold}COMMANDS${c.reset}
54
- ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
152
+ ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
55
153
 
56
- ${c.green}${c.bold}setup${c.reset} Guided setup wizard ${c.dim}(start here!)${c.reset}
57
- ${c.cyan}start${c.reset} Start MCP server for Claude Desktop
58
- ${c.cyan}test${c.reset} Test your exchange connections
59
- ${c.cyan}config${c.reset} View current configuration
60
- ${c.cyan}exchanges${c.reset} List all 107 supported exchanges
61
- ${c.cyan}help${c.reset} Show this help
154
+ ${c.green}setup${c.reset} Interactive setup wizard
155
+ ${c.cyan}start${c.reset} Launch MCP server for Claude
156
+ ${c.cyan}test${c.reset} Verify exchange connections
157
+ ${c.cyan}config${c.reset} View saved configuration
158
+ ${c.cyan}exchanges${c.reset} Browse all 107 supported exchanges
159
+ ${c.cyan}help${c.reset} Show this help
62
160
 
63
- ${c.white}${c.bold}GET STARTED${c.reset}
64
- ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
161
+ ${c.white}${c.bold}EXAMPLE PROMPTS${c.reset} ${c.dim}(after setup)${c.reset}
162
+ ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
65
163
 
66
- ${c.yellow}$${c.reset} ${c.green}omnitrade setup${c.reset}
164
+ ${c.dim}"What's my portfolio worth?"${c.reset}
165
+ ${c.dim}"Show me the BTC price on Binance"${c.reset}
166
+ ${c.dim}"Buy 0.01 ETH at market price"${c.reset}
167
+ ${c.dim}"Compare ETH prices across exchanges"${c.reset}
67
168
 
68
- ${c.white}${c.bold}DOCUMENTATION${c.reset}
69
- ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
169
+ ${c.white}${c.bold}LINKS${c.reset}
170
+ ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
70
171
 
71
- ${c.blue}https://github.com/Connectry-io/omnitrade-mcp${c.reset}
172
+ ${c.blue}Docs:${c.reset} github.com/Connectry-io/omnitrade-mcp
173
+ ${c.blue}Claude:${c.reset} claude.ai/download
72
174
 
73
175
  `);
74
176
  }
75
177
  async function runSetupWizard() {
76
- printLogo();
178
+ printBanner();
77
179
  const rl = readline.createInterface({
78
180
  input: process.stdin,
79
181
  output: process.stdout
80
182
  });
81
183
  const question = (q) => new Promise((resolve) => rl.question(q, resolve));
82
184
  console.log(`
83
- ${c.white}${c.bold}WELCOME TO OMNITRADE${c.reset}
84
- ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
185
+ ${c.white}${c.bold}SETUP WIZARD${c.reset}
186
+ ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
85
187
 
86
- Let's connect your first crypto exchange to Claude.
87
- This takes about ${c.green}2 minutes${c.reset}.
188
+ Let's connect your exchange to Claude in ${c.green}2 minutes${c.reset}.
88
189
 
89
- ${c.white}${c.bold}WHAT YOU NEED${c.reset}
190
+ ${c.white}${c.bold}HOW IT WORKS${c.reset}
90
191
 
91
- ${c.cyan}1.${c.reset} A crypto exchange account ${c.dim}(Binance, Coinbase, etc.)${c.reset}
92
- ${c.cyan}2.${c.reset} API keys from that exchange
93
- ${c.cyan}3.${c.reset} Claude Desktop installed
192
+ ${c.cyan}1.${c.reset} You create API keys on your exchange ${c.dim}(read/trade only)${c.reset}
193
+ ${c.cyan}2.${c.reset} We store them locally on your machine
194
+ ${c.cyan}3.${c.reset} Claude Desktop uses them via MCP protocol
195
+ ${c.cyan}4.${c.reset} You chat naturally: ${c.dim}"What's my BTC balance?"${c.reset}
196
+
197
+ ${c.orange}\u26A0${c.reset} Your keys ${c.bold}never leave your computer${c.reset}.
198
+ OmniTrade runs 100% locally.
94
199
 
95
200
  `);
96
201
  await question(` ${c.dim}Press Enter to continue...${c.reset}`);
97
202
  console.log(`
98
- ${c.white}${c.bold}STEP 1/4 \u2014 CHOOSE EXCHANGE${c.reset}
99
- ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
100
-
101
- ${c.cyan}[1]${c.reset} Binance ${c.dim}Largest global exchange${c.reset}
102
- ${c.cyan}[2]${c.reset} Coinbase ${c.dim}US-based, beginner friendly${c.reset}
103
- ${c.cyan}[3]${c.reset} Kraken ${c.dim}Security focused${c.reset}
104
- ${c.cyan}[4]${c.reset} Bybit ${c.dim}Derivatives trading${c.reset}
105
- ${c.cyan}[5]${c.reset} OKX ${c.dim}Full-featured${c.reset}
106
- ${c.cyan}[6]${c.reset} KuCoin ${c.dim}Altcoin variety${c.reset}
107
- ${c.cyan}[7]${c.reset} Other ${c.dim}Enter name manually${c.reset}
108
-
203
+ ${c.white}${c.bold}STEP 1 \u2014 CHOOSE EXCHANGE${c.reset}
204
+ ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
109
205
  `);
110
- const exchangeChoice = await question(` ${c.yellow}?${c.reset} Select [1-7]: `);
111
- const exchangeMap = {
112
- "1": "binance",
113
- "2": "coinbase",
114
- "3": "kraken",
115
- "4": "bybit",
116
- "5": "okx",
117
- "6": "kucoin"
118
- };
119
- let exchange = exchangeMap[exchangeChoice.trim()];
120
- if (!exchange) {
206
+ const exchangeKeys = Object.keys(EXCHANGE_INFO);
207
+ exchangeKeys.forEach((key, i) => {
208
+ const info = EXCHANGE_INFO[key];
209
+ const num = `[${i + 1}]`;
210
+ const oauth = info.supportsOAuth ? `${c.green}OAuth${c.reset}` : "";
211
+ console.log(` ${c.cyan}${num.padEnd(4)}${c.reset} ${info.name.padEnd(12)} ${c.dim}${info.description}${c.reset} ${oauth}`);
212
+ });
213
+ console.log(` ${c.cyan}[${exchangeKeys.length + 1}]${c.reset} Other ${c.dim}Enter exchange name manually${c.reset}`);
214
+ console.log("");
215
+ const exchangeChoice = await question(` ${c.yellow}?${c.reset} Select [1-${exchangeKeys.length + 1}]: `);
216
+ const choiceNum = parseInt(exchangeChoice.trim(), 10);
217
+ let exchange;
218
+ let exchangeInfo;
219
+ if (choiceNum >= 1 && choiceNum <= exchangeKeys.length) {
220
+ exchange = exchangeKeys[choiceNum - 1];
221
+ exchangeInfo = EXCHANGE_INFO[exchange];
222
+ } else {
121
223
  exchange = await question(` ${c.yellow}?${c.reset} Exchange name: `);
224
+ exchange = exchange.toLowerCase().trim();
122
225
  }
123
- exchange = exchange.toLowerCase().trim();
124
226
  console.log(`
125
- ${c.white}${c.bold}STEP 2/4 \u2014 GET API KEYS${c.reset}
126
- ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
127
-
128
- Create API keys on ${c.white}${c.bold}${exchange.toUpperCase()}${c.reset}:
129
- `);
130
- if (exchange === "binance") {
131
- console.log(`
132
- ${c.cyan}1.${c.reset} Go to ${c.blue}https://testnet.binance.vision${c.reset} ${c.dim}(testnet)${c.reset}
133
- ${c.cyan}2.${c.reset} Click ${c.white}"Generate HMAC_SHA256 Key"${c.reset}
134
- ${c.cyan}3.${c.reset} Permissions: ${c.green}\u2713 Read${c.reset} ${c.green}\u2713 Trade${c.reset} ${c.red}\u2717 Withdraw${c.reset}
135
- ${c.cyan}4.${c.reset} Copy ${c.white}API Key${c.reset} and ${c.white}Secret Key${c.reset}
227
+ ${c.white}${c.bold}STEP 2 \u2014 CREATE API KEYS${c.reset}
228
+ ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
136
229
  `);
137
- } else if (exchange === "coinbase") {
138
- console.log(`
139
- ${c.cyan}1.${c.reset} Go to ${c.blue}https://portal.cdp.coinbase.com${c.reset}
140
- ${c.cyan}2.${c.reset} Create a new project
141
- ${c.cyan}3.${c.reset} Generate API credentials
142
- ${c.cyan}4.${c.reset} Copy ${c.white}API Key${c.reset}, ${c.white}Secret${c.reset}, and ${c.white}Passphrase${c.reset}
230
+ if (exchangeInfo) {
231
+ console.log(` ${c.white}${c.bold}${exchangeInfo.name}${c.reset} API Setup:
143
232
  `);
233
+ exchangeInfo.steps.forEach((step, i) => {
234
+ console.log(` ${c.cyan}${i + 1}.${c.reset} ${step}`);
235
+ });
236
+ console.log("");
237
+ console.log(` ${c.blue}API Page:${c.reset} ${exchangeInfo.apiUrl}`);
238
+ if (exchangeInfo.testnetUrl) {
239
+ console.log(` ${c.blue}Testnet:${c.reset} ${exchangeInfo.testnetUrl}`);
240
+ }
241
+ console.log(` ${c.blue}Help:${c.reset} ${exchangeInfo.docsUrl}`);
144
242
  } else {
145
- console.log(`
146
- ${c.cyan}1.${c.reset} Log into ${c.white}${exchange}${c.reset}
147
- ${c.cyan}2.${c.reset} Go to API settings
148
- ${c.cyan}3.${c.reset} Create new API key
149
- ${c.cyan}4.${c.reset} Enable: ${c.green}\u2713 Read${c.reset} ${c.green}\u2713 Trade${c.reset} ${c.red}\u2717 Withdraw${c.reset}
243
+ console.log(` ${c.white}${c.bold}${exchange.toUpperCase()}${c.reset} API Setup:
150
244
  `);
245
+ console.log(` ${c.cyan}1.${c.reset} Log into ${exchange} and find API settings`);
246
+ console.log(` ${c.cyan}2.${c.reset} Create a new API key`);
247
+ console.log(` ${c.cyan}3.${c.reset} Enable: ${c.green}\u2713 Read${c.reset} ${c.green}\u2713 Trade${c.reset} ${c.red}\u2717 Withdraw${c.reset}`);
248
+ console.log(` ${c.cyan}4.${c.reset} Copy your API Key and Secret`);
151
249
  }
152
- console.log(` ${c.orange}\u26A0 Never enable withdrawal permissions!${c.reset}
250
+ console.log(`
251
+ ${c.orange}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${c.reset}
252
+ ${c.orange}\u2551${c.reset} ${c.orange}\u26A0 SECURITY:${c.reset} Never enable withdrawal permissions ${c.orange}\u2551${c.reset}
253
+ ${c.orange}\u2551${c.reset} OmniTrade only needs read + trade access ${c.orange}\u2551${c.reset}
254
+ ${c.orange}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${c.reset}
153
255
  `);
154
256
  await question(` ${c.dim}Press Enter when you have your keys...${c.reset}`);
155
257
  console.log(`
156
- ${c.white}${c.bold}STEP 3/4 \u2014 ENTER API KEYS${c.reset}
157
- ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
258
+ ${c.white}${c.bold}STEP 3 \u2014 ENTER YOUR KEYS${c.reset}
259
+ ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
158
260
 
159
- ${c.dim}Keys are stored locally at ~/.omnitrade/config.json${c.reset}
160
- ${c.dim}They never leave your machine.${c.reset}
261
+ ${c.dim}Paste your API credentials below.${c.reset}
262
+ ${c.dim}Stored at: ~/.omnitrade/config.json (local only)${c.reset}
161
263
 
162
264
  `);
163
- const apiKey = await question(` ${c.yellow}?${c.reset} API Key: `);
164
- const secret = await question(` ${c.yellow}?${c.reset} Secret: `);
265
+ const apiKey = await question(` ${c.cyan}API Key:${c.reset} `);
266
+ const secret = await question(` ${c.cyan}Secret:${c.reset} `);
165
267
  let password = "";
166
- if (["coinbase", "kucoin", "okx"].includes(exchange)) {
167
- password = await question(` ${c.yellow}?${c.reset} Passphrase: `);
268
+ const needsPassphrase = exchangeInfo?.needsPassphrase || ["coinbase", "kucoin", "okx"].includes(exchange);
269
+ if (needsPassphrase) {
270
+ password = await question(` ${c.cyan}Passphrase:${c.reset} `);
271
+ }
272
+ console.log("");
273
+ const hasTestnet = exchangeInfo?.testnetUrl || ["binance", "bybit"].includes(exchange);
274
+ let testnet = false;
275
+ if (hasTestnet) {
276
+ console.log(` ${c.dim}Testnet available \u2014 practice with fake money first${c.reset}`);
277
+ const testnetAnswer = await question(` ${c.yellow}?${c.reset} Use testnet mode? ${c.dim}(Y/n)${c.reset}: `);
278
+ testnet = testnetAnswer.toLowerCase() !== "n";
279
+ } else {
280
+ console.log(` ${c.dim}Note: ${exchange} doesn't have a public testnet${c.reset}`);
281
+ console.log(` ${c.dim}Your API will connect to the live exchange${c.reset}`);
282
+ await question(` ${c.dim}Press Enter to continue...${c.reset}`);
168
283
  }
169
- const testnetAnswer = await question(` ${c.yellow}?${c.reset} Use testnet? ${c.dim}(Y/n)${c.reset}: `);
170
- const testnet = testnetAnswer.toLowerCase() !== "n";
171
284
  rl.close();
172
285
  const config = {
173
286
  exchanges: {
@@ -196,8 +309,8 @@ async function runSetupWizard() {
196
309
  console.log(`
197
310
  ${c.green}${c.bold}\u2713 SAVED${c.reset}
198
311
 
199
- ${c.white}${c.bold}STEP 4/4 \u2014 CONNECT TO CLAUDE${c.reset}
200
- ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
312
+ ${c.white}${c.bold}STEP 4 \u2014 CONNECT TO CLAUDE${c.reset}
313
+ ${c.gray}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}
201
314
  `);
202
315
  const rl2 = readline.createInterface({
203
316
  input: process.stdin,
package/dist/index.js CHANGED
@@ -292,9 +292,12 @@ function registerBalanceTools(server, exchangeManager) {
292
292
  );
293
293
  server.tool(
294
294
  "get_portfolio",
295
- "Get a unified portfolio summary across all exchanges with total values.",
296
- {},
297
- async () => {
295
+ "Get a unified portfolio summary with USD values. Shows total portfolio worth and individual holdings.",
296
+ {
297
+ minValue: z2.number().default(1).describe("Minimum USD value to display (default: $1). Set to 0 to show all."),
298
+ showAll: z2.boolean().default(false).describe("Show all assets including dust (overrides minValue)")
299
+ },
300
+ async ({ minValue, showAll }) => {
298
301
  const assetTotals = {};
299
302
  const errors = [];
300
303
  for (const [name, ex] of exchangeManager.getAll()) {
@@ -313,17 +316,59 @@ function registerBalanceTools(server, exchangeManager) {
313
316
  errors.push(`${name}: ${error.message}`);
314
317
  }
315
318
  }
316
- const assets = Object.entries(assetTotals).map(([asset, data]) => ({
317
- asset,
318
- total: data.total,
319
- distribution: data.byExchange
320
- })).sort((a, b) => b.total - a.total);
319
+ const priceCache = {};
320
+ const stablecoins = ["USDT", "USDC", "BUSD", "DAI", "USD", "TUSD", "USDP"];
321
+ const priceExchange = exchangeManager.getAll().values().next().value;
322
+ if (priceExchange) {
323
+ for (const symbol of Object.keys(assetTotals)) {
324
+ if (stablecoins.includes(symbol.toUpperCase())) {
325
+ priceCache[symbol] = 1;
326
+ continue;
327
+ }
328
+ try {
329
+ const ticker = await priceExchange.fetchTicker(`${symbol}/USDT`);
330
+ if (ticker.last) {
331
+ priceCache[symbol] = ticker.last;
332
+ }
333
+ } catch {
334
+ try {
335
+ const ticker = await priceExchange.fetchTicker(`${symbol}/USD`);
336
+ if (ticker.last) {
337
+ priceCache[symbol] = ticker.last;
338
+ }
339
+ } catch {
340
+ priceCache[symbol] = 0;
341
+ }
342
+ }
343
+ }
344
+ }
345
+ const assets = Object.entries(assetTotals).map(([asset, data]) => {
346
+ const price = priceCache[asset] ?? 0;
347
+ const usdValue = data.total * price;
348
+ return {
349
+ asset,
350
+ quantity: data.total,
351
+ price,
352
+ usdValue: parseFloat(usdValue.toFixed(2)),
353
+ distribution: data.byExchange
354
+ };
355
+ }).filter((a) => showAll || a.usdValue >= minValue).sort((a, b) => b.usdValue - a.usdValue);
356
+ const totalUsdValue = assets.reduce((sum, a) => sum + a.usdValue, 0);
357
+ const hiddenCount = Object.keys(assetTotals).length - assets.length;
321
358
  const portfolio = {
322
359
  summary: {
360
+ totalValue: `$${totalUsdValue.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`,
323
361
  totalAssets: assets.length,
362
+ hiddenAssets: hiddenCount > 0 ? `${hiddenCount} assets below $${minValue}` : void 0,
324
363
  exchanges: exchangeManager.getNames()
325
364
  },
326
- assets,
365
+ holdings: assets.map((a) => ({
366
+ asset: a.asset,
367
+ quantity: a.quantity,
368
+ price: a.price > 0 ? `$${a.price.toLocaleString("en-US")}` : "N/A",
369
+ value: `$${a.usdValue.toLocaleString("en-US", { minimumFractionDigits: 2 })}`,
370
+ percent: totalUsdValue > 0 ? `${(a.usdValue / totalUsdValue * 100).toFixed(1)}%` : "0%"
371
+ })),
327
372
  errors: errors.length > 0 ? errors : void 0
328
373
  };
329
374
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnitrade-mcp",
3
- "version": "0.4.4",
3
+ "version": "0.6.0",
4
4
  "description": "Multi-exchange AI trading via MCP. 107 exchanges. One AI.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",