omnitrade-mcp 0.6.3 → 0.7.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 (2) hide show
  1. package/dist/cli.js +244 -100
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -33,20 +33,23 @@ function printBanner() {
33
33
  const line = "-".repeat(W);
34
34
  console.log(`
35
35
  ${c.cyan}+${line}+${c.reset}
36
- ${c.cyan}|${c.reset}${center(`${c.cyan}o${c.gray}----${c.purple}o${c.reset}`, W)}${c.cyan}|${c.reset}
37
- ${c.cyan}|${c.reset}${center(`${c.white}${c.bold}OmniTrade${c.reset}`, W)}${c.cyan}|${c.reset}
38
- ${c.cyan}|${c.reset}${center(`${c.dim}v${VERSION}${c.reset}`, W)}${c.cyan}|${c.reset}
39
36
  ${c.cyan}|${c.reset}${" ".repeat(W)}${c.cyan}|${c.reset}
40
- ${c.cyan}|${c.reset}${center(`${c.white}Talk to your crypto with Claude${c.reset}`, W)}${c.cyan}|${c.reset}
41
- ${c.cyan}|${c.reset}${center(`${c.dim}107 exchanges. Natural language. Local & secure.${c.reset}`, W)}${c.cyan}|${c.reset}
37
+ ${c.cyan}|${c.reset}${center(`${c.cyan}o${c.gray}--${c.purple}o${c.reset}`, W)}${c.cyan}|${c.reset}
38
+ ${c.cyan}|${c.reset}${" ".repeat(W)}${c.cyan}|${c.reset}
39
+ ${c.cyan}|${c.reset}${center(`${c.white}${c.bold}O M N I T R A D E${c.reset}`, W)}${c.cyan}|${c.reset}
40
+ ${c.cyan}|${c.reset}${center(`${c.gray}v${VERSION}${c.reset}`, W)}${c.cyan}|${c.reset}
41
+ ${c.cyan}|${c.reset}${" ".repeat(W)}${c.cyan}|${c.reset}
42
+ ${c.cyan}|${c.reset}${center(`${c.dim}Talk to your crypto with Claude${c.reset}`, W)}${c.cyan}|${c.reset}
43
+ ${c.cyan}|${c.reset}${center(`${c.dim}107 exchanges \u2022 Natural language \u2022 Local & secure${c.reset}`, W)}${c.cyan}|${c.reset}
44
+ ${c.cyan}|${c.reset}${" ".repeat(W)}${c.cyan}|${c.reset}
45
+ ${c.cyan}|${c.reset}${center(`${c.cyan}Connectry${c.reset}`, W)}${c.cyan}|${c.reset}
42
46
  ${c.cyan}|${c.reset}${" ".repeat(W)}${c.cyan}|${c.reset}
43
- ${c.cyan}|${c.reset}${center(`${c.gray}by ${c.cyan}Connectry${c.reset}`, W)}${c.cyan}|${c.reset}
44
47
  ${c.cyan}+${line}+${c.reset}
45
48
  `);
46
49
  }
47
50
  function printCompactLogo() {
48
51
  console.log(`
49
- ${c.cyan}o${c.gray}--${c.purple}o${c.reset} ${c.white}${c.bold}OmniTrade${c.reset} ${c.gray}v${VERSION}${c.reset} ${c.dim}by Connectry${c.reset}
52
+ ${c.cyan}o${c.gray}-${c.purple}o${c.reset} ${c.white}${c.bold}OmniTrade${c.reset} ${c.gray}v${VERSION}${c.reset} ${c.dim}\u2022 Connectry${c.reset}
50
53
  `);
51
54
  }
52
55
  var EXCHANGE_INFO = {
@@ -141,6 +144,91 @@ var EXCHANGE_INFO = {
141
144
  "Permissions: \u2713 General \u2713 Trade \u2717 Transfer",
142
145
  "Copy API Key, Secret, and Passphrase"
143
146
  ]
147
+ },
148
+ gate: {
149
+ name: "Gate.io",
150
+ description: "Wide selection of altcoins",
151
+ apiUrl: "https://www.gate.io/myaccount/api_key_manage",
152
+ docsUrl: "https://www.gate.io/help/api/general/17337",
153
+ supportsOAuth: false,
154
+ needsPassphrase: false,
155
+ steps: [
156
+ "Go to My Account \u2192 API Management",
157
+ 'Click "Create API Key"',
158
+ "Permissions: \u2713 Spot Trade \u2713 Read \u2717 Withdraw",
159
+ "Copy API Key and Secret"
160
+ ]
161
+ },
162
+ bitget: {
163
+ name: "Bitget",
164
+ description: "Copy trading platform",
165
+ apiUrl: "https://www.bitget.com/account/api",
166
+ docsUrl: "https://www.bitget.com/academy/api-trading-guide",
167
+ supportsOAuth: false,
168
+ needsPassphrase: true,
169
+ steps: [
170
+ "Go to Account \u2192 API Management",
171
+ 'Click "Create API"',
172
+ "Set a passphrase",
173
+ "Permissions: \u2713 Read \u2713 Trade \u2717 Transfer",
174
+ "Copy API Key, Secret, and Passphrase"
175
+ ]
176
+ },
177
+ htx: {
178
+ name: "HTX (Huobi)",
179
+ description: "Major Asian exchange",
180
+ apiUrl: "https://www.htx.com/en-us/apikey/",
181
+ docsUrl: "https://www.htx.com/support/en-us/detail/900000203206",
182
+ supportsOAuth: false,
183
+ needsPassphrase: false,
184
+ steps: [
185
+ "Go to API Management",
186
+ 'Click "Create API Key"',
187
+ "Permissions: \u2713 Read \u2713 Trade \u2717 Withdraw",
188
+ "Copy API Key and Secret"
189
+ ]
190
+ },
191
+ mexc: {
192
+ name: "MEXC",
193
+ description: "High liquidity, many pairs",
194
+ apiUrl: "https://www.mexc.com/user/openapi",
195
+ docsUrl: "https://www.mexc.com/support/articles/360030903032",
196
+ supportsOAuth: false,
197
+ needsPassphrase: false,
198
+ steps: [
199
+ "Go to API Management",
200
+ 'Click "Create API"',
201
+ "Permissions: \u2713 Read \u2713 Trade \u2717 Withdraw",
202
+ "Copy API Key and Secret"
203
+ ]
204
+ },
205
+ cryptocom: {
206
+ name: "Crypto.com",
207
+ description: "Popular mobile-first exchange",
208
+ apiUrl: "https://crypto.com/exchange/user/settings/api-management",
209
+ docsUrl: "https://help.crypto.com/en/articles/3511424",
210
+ supportsOAuth: false,
211
+ needsPassphrase: false,
212
+ steps: [
213
+ "Go to Settings \u2192 API Management",
214
+ 'Click "Create New API Key"',
215
+ "Permissions: \u2713 Read \u2713 Trade \u2717 Withdraw",
216
+ "Copy API Key and Secret"
217
+ ]
218
+ },
219
+ gemini: {
220
+ name: "Gemini",
221
+ description: "US regulated, institutional",
222
+ apiUrl: "https://exchange.gemini.com/settings/api",
223
+ docsUrl: "https://support.gemini.com/hc/en-us/articles/360031080191",
224
+ supportsOAuth: false,
225
+ needsPassphrase: false,
226
+ steps: [
227
+ "Go to Settings \u2192 API",
228
+ 'Click "Create a New API Key"',
229
+ "Scope: \u2713 Trading \u2717 Fund Management",
230
+ "Copy API Key and Secret"
231
+ ]
144
232
  }
145
233
  };
146
234
  function printHelp() {
@@ -205,134 +293,185 @@ async function runSetupWizard() {
205
293
  const question = (q) => new Promise((resolve) => rl.question(q, resolve));
206
294
  console.log(`
207
295
  ${c.white}${c.bold}SETUP WIZARD${c.reset}
208
- ${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}
296
+ ${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${c.reset}
209
297
 
210
- Let's connect your exchange to Claude in ${c.green}2 minutes${c.reset}.
298
+ Let's connect your exchanges to Claude.
211
299
 
212
300
  ${c.white}${c.bold}HOW IT WORKS${c.reset}
213
301
 
214
- ${c.cyan}1.${c.reset} You create API keys on your exchange ${c.dim}(read/trade only)${c.reset}
215
- ${c.cyan}2.${c.reset} We store them locally on your machine
216
- ${c.cyan}3.${c.reset} Claude Desktop uses them via MCP protocol
217
- ${c.cyan}4.${c.reset} You chat naturally: ${c.dim}"What's my BTC balance?"${c.reset}
302
+ ${c.cyan}1.${c.reset} Select which exchanges you use
303
+ ${c.cyan}2.${c.reset} Create API keys on each ${c.dim}(read/trade only, never withdraw)${c.reset}
304
+ ${c.cyan}3.${c.reset} Enter your keys \u2014 stored locally on your machine
305
+ ${c.cyan}4.${c.reset} Chat naturally: ${c.dim}"What's my total portfolio worth?"${c.reset}
218
306
 
219
- ${c.orange}\u26A0${c.reset} Your keys ${c.bold}never leave your computer${c.reset}.
220
- OmniTrade runs 100% locally.
307
+ ${c.orange}\u26A0${c.reset} Your keys ${c.bold}never leave your computer${c.reset}. 100% local.
221
308
 
222
309
  `);
223
310
  await question(` ${c.dim}Press Enter to continue...${c.reset}`);
224
311
  console.log(`
225
- ${c.white}${c.bold}STEP 1 \u2014 CHOOSE EXCHANGE${c.reset}
226
- ${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}
312
+ ${c.white}${c.bold}STEP 1 \u2014 SELECT EXCHANGES${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\u2500\u2500\u2500\u2500${c.reset}
314
+
315
+ ${c.dim}Enter numbers separated by commas (e.g., 1,3,5) or just one:${c.reset}
227
316
  `);
228
317
  const exchangeKeys = Object.keys(EXCHANGE_INFO);
229
318
  exchangeKeys.forEach((key, i) => {
230
319
  const info = EXCHANGE_INFO[key];
231
- const num = `[${i + 1}]`;
232
- const oauth = info.supportsOAuth ? `${c.green}OAuth${c.reset}` : "";
233
- console.log(` ${c.cyan}${num.padEnd(4)}${c.reset} ${info.name.padEnd(12)} ${c.dim}${info.description}${c.reset} ${oauth}`);
320
+ const num = `[${i + 1}]`.padEnd(5);
321
+ console.log(` ${c.cyan}${num}${c.reset}${info.name.padEnd(14)} ${c.dim}${info.description}${c.reset}`);
234
322
  });
235
- console.log(` ${c.cyan}[${exchangeKeys.length + 1}]${c.reset} Other ${c.dim}Enter exchange name manually${c.reset}`);
323
+ const otherNum = exchangeKeys.length + 1;
324
+ const listNum = exchangeKeys.length + 2;
325
+ console.log(` ${c.cyan}[${otherNum}]${c.reset} Other ${c.dim}Enter name manually (any of 107 exchanges)${c.reset}`);
326
+ console.log(` ${c.cyan}[${listNum}]${c.reset} List all ${c.dim}See all 107 supported exchanges${c.reset}`);
236
327
  console.log("");
237
- const exchangeChoice = await question(` ${c.yellow}?${c.reset} Select [1-${exchangeKeys.length + 1}]: `);
238
- const choiceNum = parseInt(exchangeChoice.trim(), 10);
239
- let exchange;
240
- let exchangeInfo;
241
- if (choiceNum >= 1 && choiceNum <= exchangeKeys.length) {
242
- exchange = exchangeKeys[choiceNum - 1];
243
- exchangeInfo = EXCHANGE_INFO[exchange];
244
- } else {
245
- exchange = await question(` ${c.yellow}?${c.reset} Exchange name: `);
246
- exchange = exchange.toLowerCase().trim();
328
+ const exchangeChoice = await question(` ${c.yellow}?${c.reset} Select: `);
329
+ if (exchangeChoice.trim() === String(listNum)) {
330
+ console.log(`
331
+ ${c.white}${c.bold}ALL 107 SUPPORTED EXCHANGES${c.reset}
332
+ ${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${c.reset}
333
+
334
+ OmniTrade uses ${c.cyan}CCXT${c.reset} (CryptoCurrency eXchange Trading Library),
335
+ which supports ${c.white}107 exchanges${c.reset} including:
336
+
337
+ ${c.cyan}Major:${c.reset} binance, coinbase, kraken, bybit, okx, kucoin,
338
+ gate, bitget, htx, mexc, cryptocom, gemini
339
+
340
+ ${c.cyan}Popular:${c.reset} bitstamp, bitfinex, poloniex, bittrex, upbit,
341
+ bithumb, lbank, phemex, woo, ascendex
342
+
343
+ ${c.cyan}Futures:${c.reset} binanceusdm, binancecoinm, bybit, okx, deribit,
344
+ bitmex, bitget, phemex, woo
345
+
346
+ ${c.cyan}DEX:${c.reset} uniswap, sushiswap ${c.dim}(limited support)${c.reset}
347
+
348
+ ${c.dim}Full list: Run ${c.cyan}omnitrade exchanges${c.reset}${c.dim} or visit:${c.reset}
349
+ ${c.blue}github.com/ccxt/ccxt/wiki/Exchange-Markets${c.reset}
350
+
351
+ ${c.white}${c.bold}TO SET UP ANY EXCHANGE:${c.reset}
352
+
353
+ 1. Select ${c.cyan}[${otherNum}] Other${c.reset} and type the exchange name
354
+ 2. Go to that exchange's API settings
355
+ 3. Create API key with ${c.green}Read + Trade${c.reset} permissions
356
+ 4. Enter your API key and secret
357
+
358
+ `);
359
+ await question(` ${c.dim}Press Enter to go back to selection...${c.reset}`);
360
+ rl.close();
361
+ return runSetupWizard();
362
+ }
363
+ const selections = exchangeChoice.split(",").map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n));
364
+ const selectedExchanges = [];
365
+ for (const num of selections) {
366
+ if (num >= 1 && num <= exchangeKeys.length) {
367
+ const id = exchangeKeys[num - 1];
368
+ selectedExchanges.push({ id, info: EXCHANGE_INFO[id] });
369
+ } else if (num === otherNum) {
370
+ const name = await question(` ${c.yellow}?${c.reset} Exchange name: `);
371
+ selectedExchanges.push({ id: name.toLowerCase().trim() });
372
+ }
373
+ }
374
+ if (selectedExchanges.length === 0) {
375
+ console.log(` ${c.red}No valid exchanges selected. Please try again.${c.reset}`);
376
+ rl.close();
377
+ return;
247
378
  }
248
379
  console.log(`
249
- ${c.white}${c.bold}STEP 2 \u2014 CREATE API KEYS${c.reset}
250
- ${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}
380
+ ${c.green}\u2713${c.reset} Selected: ${selectedExchanges.map((e) => e.info?.name || e.id).join(", ")}
251
381
  `);
252
- if (exchangeInfo) {
253
- console.log(` ${c.white}${c.bold}${exchangeInfo.name}${c.reset} API Setup:
254
- `);
255
- exchangeInfo.steps.forEach((step, i) => {
256
- console.log(` ${c.cyan}${i + 1}.${c.reset} ${step}`);
257
- });
258
- console.log("");
259
- console.log(` ${c.blue}API Page:${c.reset} ${exchangeInfo.apiUrl}`);
260
- if (exchangeInfo.testnetUrl) {
261
- console.log(` ${c.blue}Testnet:${c.reset} ${exchangeInfo.testnetUrl}`);
382
+ const config = {
383
+ exchanges: {},
384
+ security: {
385
+ maxOrderSize: 100,
386
+ confirmTrades: true
262
387
  }
263
- console.log(` ${c.blue}Help:${c.reset} ${exchangeInfo.docsUrl}`);
264
- } else {
265
- console.log(` ${c.white}${c.bold}${exchange.toUpperCase()}${c.reset} API Setup:
388
+ };
389
+ for (let i = 0; i < selectedExchanges.length; i++) {
390
+ const { id: exchange, info: exchangeInfo } = selectedExchanges[i];
391
+ const displayName = exchangeInfo?.name || exchange.toUpperCase();
392
+ const stepNum = i + 2;
393
+ const totalSteps = selectedExchanges.length + 2;
394
+ console.log(`
395
+ ${c.white}${c.bold}STEP ${stepNum}/${totalSteps} \u2014 ${displayName} API KEYS${c.reset}
396
+ ${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${c.reset}
266
397
  `);
267
- console.log(` ${c.cyan}1.${c.reset} Log into ${exchange} and find API settings`);
268
- console.log(` ${c.cyan}2.${c.reset} Create a new API key`);
269
- 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}`);
270
- console.log(` ${c.cyan}4.${c.reset} Copy your API Key and Secret`);
271
- }
272
- console.log(`
273
- ${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}
274
- ${c.orange}\u2551${c.reset} ${c.orange}\u26A0 SECURITY:${c.reset} Never enable withdrawal permissions ${c.orange}\u2551${c.reset}
275
- ${c.orange}\u2551${c.reset} OmniTrade only needs read + trade access ${c.orange}\u2551${c.reset}
276
- ${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}
398
+ if (exchangeInfo) {
399
+ console.log(` ${c.dim}Create API keys at:${c.reset} ${c.blue}${exchangeInfo.apiUrl}${c.reset}
277
400
  `);
278
- await question(` ${c.dim}Press Enter when you have your keys...${c.reset}`);
279
- console.log(`
280
- ${c.white}${c.bold}STEP 3 \u2014 ENTER YOUR KEYS${c.reset}
281
- ${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}
282
-
283
- ${c.dim}Paste your API credentials below.${c.reset}
284
- ${c.dim}Stored at: ~/.omnitrade/config.json (local only)${c.reset}
285
-
401
+ exchangeInfo.steps.forEach((step, j) => {
402
+ console.log(` ${c.cyan}${j + 1}.${c.reset} ${step}`);
403
+ });
404
+ console.log("");
405
+ if (exchangeInfo.docsUrl) {
406
+ console.log(` ${c.dim}Need help?${c.reset} ${c.blue}${exchangeInfo.docsUrl}${c.reset}`);
407
+ }
408
+ } else {
409
+ console.log(` ${c.dim}Go to ${exchange}'s API settings and create a new API key.${c.reset}`);
410
+ console.log(` ${c.dim}Enable: ${c.green}\u2713 Read${c.reset}${c.dim} ${c.green}\u2713 Trade${c.reset}${c.dim} ${c.red}\u2717 Withdraw${c.reset}`);
411
+ }
412
+ console.log(`
413
+ ${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\u2550\u2550\u2557${c.reset}
414
+ ${c.orange}\u2551${c.reset} ${c.orange}\u26A0 SECURITY:${c.reset} Never enable withdrawal permissions ${c.orange}\u2551${c.reset}
415
+ ${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\u2550\u2550\u255D${c.reset}
286
416
  `);
287
- const apiKey = await question(` ${c.cyan}API Key:${c.reset} `);
288
- const secret = await question(` ${c.cyan}Secret:${c.reset} `);
289
- let password = "";
290
- const needsPassphrase = exchangeInfo?.needsPassphrase || ["coinbase", "kucoin", "okx"].includes(exchange);
291
- if (needsPassphrase) {
292
- password = await question(` ${c.cyan}Passphrase:${c.reset} `);
293
- }
294
- console.log("");
295
- const hasTestnet = exchangeInfo?.testnetUrl || ["binance", "bybit"].includes(exchange);
296
- let testnet = false;
297
- if (hasTestnet) {
298
- console.log(` ${c.dim}Testnet available \u2014 practice with fake money first${c.reset}`);
299
- const testnetAnswer = await question(` ${c.yellow}?${c.reset} Use testnet mode? ${c.dim}(Y/n)${c.reset}: `);
300
- testnet = testnetAnswer.toLowerCase() !== "n";
301
- } else {
302
- console.log(` ${c.dim}Note: ${exchange} doesn't have a public testnet${c.reset}`);
303
- console.log(` ${c.dim}Your API will connect to the live exchange${c.reset}`);
304
- await question(` ${c.dim}Press Enter to continue...${c.reset}`);
417
+ await question(` ${c.dim}Press Enter when you have your keys...${c.reset}`);
418
+ console.log(`
419
+ ${c.dim}Paste your ${displayName} credentials:${c.reset}
420
+ `);
421
+ const apiKey = await question(` ${c.cyan}API Key:${c.reset} `);
422
+ const secret = await question(` ${c.cyan}Secret:${c.reset} `);
423
+ let password = "";
424
+ const needsPassphrase = exchangeInfo?.needsPassphrase || ["coinbase", "kucoin", "okx", "bitget"].includes(exchange);
425
+ if (needsPassphrase) {
426
+ password = await question(` ${c.cyan}Passphrase:${c.reset} `);
427
+ }
428
+ let testnet = false;
429
+ const hasTestnet = exchangeInfo?.testnetUrl || ["binance", "bybit"].includes(exchange);
430
+ if (hasTestnet) {
431
+ const testnetAnswer = await question(` ${c.yellow}?${c.reset} Use testnet? ${c.dim}(y/N)${c.reset}: `);
432
+ testnet = testnetAnswer.toLowerCase() === "y";
433
+ }
434
+ config.exchanges[exchange] = {
435
+ apiKey: apiKey.trim(),
436
+ secret: secret.trim(),
437
+ ...password.trim() ? { password: password.trim() } : {},
438
+ testnet
439
+ };
440
+ console.log(` ${c.green}\u2713${c.reset} ${displayName} configured!`);
305
441
  }
306
442
  rl.close();
307
- const config = {
443
+ let existingConfig = {};
444
+ if (existsSync(CONFIG_PATH)) {
445
+ try {
446
+ existingConfig = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
447
+ } catch {
448
+ }
449
+ }
450
+ const mergedConfig = {
451
+ ...existingConfig,
452
+ ...config,
308
453
  exchanges: {
309
- [exchange]: {
310
- apiKey: apiKey.trim(),
311
- secret: secret.trim(),
312
- ...password.trim() ? { password: password.trim() } : {},
313
- testnet
314
- }
315
- },
316
- security: {
317
- maxOrderSize: 100,
318
- confirmTrades: true
454
+ ...existingConfig.exchanges || {},
455
+ ...config.exchanges || {}
319
456
  }
320
457
  };
321
458
  const configDir = join(homedir(), ".omnitrade");
322
459
  if (!existsSync(configDir)) {
323
460
  mkdirSync(configDir, { recursive: true });
324
461
  }
325
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
462
+ writeFileSync(CONFIG_PATH, JSON.stringify(mergedConfig, null, 2));
326
463
  try {
327
464
  const { chmodSync } = await import("fs");
328
465
  chmodSync(CONFIG_PATH, 384);
329
466
  } catch {
330
467
  }
468
+ const exchangeCount = Object.keys(mergedConfig.exchanges).length;
469
+ const finalStepNum = selectedExchanges.length + 2;
331
470
  console.log(`
332
- ${c.green}${c.bold}\u2713 SAVED${c.reset}
471
+ ${c.green}${c.bold}\u2713 ${exchangeCount} EXCHANGE${exchangeCount > 1 ? "S" : ""} CONFIGURED${c.reset}
333
472
 
334
- ${c.white}${c.bold}STEP 4 \u2014 CONNECT TO CLAUDE${c.reset}
335
- ${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}
473
+ ${c.white}${c.bold}STEP ${finalStepNum}/${finalStepNum} \u2014 CONNECT TO CLAUDE${c.reset}
474
+ ${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${c.reset}
336
475
  `);
337
476
  const rl2 = readline.createInterface({
338
477
  input: process.stdin,
@@ -411,20 +550,25 @@ async function runSetupWizard() {
411
550
  `);
412
551
  }
413
552
  rl2.close();
553
+ const configuredExchanges = Object.keys(mergedConfig.exchanges);
554
+ const firstExchange = configuredExchanges[0] || "binance";
414
555
  console.log(`
415
556
  ${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}
416
557
 
417
558
  ${c.white}${c.bold}TRY IT${c.reset}
418
559
 
419
- Ask Claude: ${c.dim}"What's my balance on ${exchange}?"${c.reset}
560
+ ${c.dim}"What's my portfolio worth?"${c.reset}
561
+ ${c.dim}"Show my ${firstExchange} balance"${c.reset}
562
+ ${c.dim}"Compare BTC prices across my exchanges"${c.reset}
420
563
 
421
564
  ${c.white}${c.bold}USEFUL COMMANDS${c.reset}
422
565
 
423
- ${c.cyan}omnitrade test${c.reset} Test your connection
566
+ ${c.cyan}omnitrade test${c.reset} Test your connections
424
567
  ${c.cyan}omnitrade config${c.reset} View configuration
425
- ${c.cyan}omnitrade setup${c.reset} Add another exchange
568
+ ${c.cyan}omnitrade setup${c.reset} Add more exchanges
426
569
 
427
570
  ${c.green}${c.bold}\u2713 Setup complete!${c.reset}
571
+ ${c.dim}Configured: ${configuredExchanges.join(", ")}${c.reset}
428
572
 
429
573
  `);
430
574
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnitrade-mcp",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "Multi-exchange AI trading via MCP. 107 exchanges. One AI.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",