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.
- package/dist/cli.js +214 -101
- package/dist/index.js +54 -9
- 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.
|
|
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
|
|
24
|
+
function printBanner() {
|
|
25
25
|
console.log(`
|
|
26
|
-
${c.cyan}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
|
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}
|
|
57
|
-
${c.cyan}start${c.reset}
|
|
58
|
-
${c.cyan}test${c.reset}
|
|
59
|
-
${c.cyan}config${c.reset}
|
|
60
|
-
${c.cyan}exchanges${c.reset}
|
|
61
|
-
${c.cyan}help${c.reset}
|
|
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}
|
|
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
|
|
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.
|
|
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}
|
|
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
|
|
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}
|
|
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
|
-
|
|
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}
|
|
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
|
|
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
|
|
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}
|
|
190
|
+
${c.white}${c.bold}HOW IT WORKS${c.reset}
|
|
90
191
|
|
|
91
|
-
${c.cyan}1.${c.reset}
|
|
92
|
-
${c.cyan}2.${c.reset}
|
|
93
|
-
${c.cyan}3.${c.reset} Claude Desktop
|
|
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
|
|
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
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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(`
|
|
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
|
|
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
|
|
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}
|
|
160
|
-
${c.dim}
|
|
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.
|
|
164
|
-
const secret = await question(` ${c.
|
|
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
|
-
|
|
167
|
-
|
|
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
|
|
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
|
|
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
|
|
296
|
-
{
|
|
297
|
-
|
|
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
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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 {
|