omnitrade-mcp 0.6.4 → 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 +234 -93
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -144,6 +144,91 @@ var EXCHANGE_INFO = {
144
144
  "Permissions: \u2713 General \u2713 Trade \u2717 Transfer",
145
145
  "Copy API Key, Secret, and Passphrase"
146
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
+ ]
147
232
  }
148
233
  };
149
234
  function printHelp() {
@@ -208,134 +293,185 @@ async function runSetupWizard() {
208
293
  const question = (q) => new Promise((resolve) => rl.question(q, resolve));
209
294
  console.log(`
210
295
  ${c.white}${c.bold}SETUP WIZARD${c.reset}
211
- ${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}
212
297
 
213
- Let's connect your exchange to Claude in ${c.green}2 minutes${c.reset}.
298
+ Let's connect your exchanges to Claude.
214
299
 
215
300
  ${c.white}${c.bold}HOW IT WORKS${c.reset}
216
301
 
217
- ${c.cyan}1.${c.reset} You create API keys on your exchange ${c.dim}(read/trade only)${c.reset}
218
- ${c.cyan}2.${c.reset} We store them locally on your machine
219
- ${c.cyan}3.${c.reset} Claude Desktop uses them via MCP protocol
220
- ${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}
221
306
 
222
- ${c.orange}\u26A0${c.reset} Your keys ${c.bold}never leave your computer${c.reset}.
223
- OmniTrade runs 100% locally.
307
+ ${c.orange}\u26A0${c.reset} Your keys ${c.bold}never leave your computer${c.reset}. 100% local.
224
308
 
225
309
  `);
226
310
  await question(` ${c.dim}Press Enter to continue...${c.reset}`);
227
311
  console.log(`
228
- ${c.white}${c.bold}STEP 1 \u2014 CHOOSE EXCHANGE${c.reset}
229
- ${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}
230
316
  `);
231
317
  const exchangeKeys = Object.keys(EXCHANGE_INFO);
232
318
  exchangeKeys.forEach((key, i) => {
233
319
  const info = EXCHANGE_INFO[key];
234
- const num = `[${i + 1}]`;
235
- const oauth = info.supportsOAuth ? `${c.green}OAuth${c.reset}` : "";
236
- 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}`);
237
322
  });
238
- 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}`);
239
327
  console.log("");
240
- const exchangeChoice = await question(` ${c.yellow}?${c.reset} Select [1-${exchangeKeys.length + 1}]: `);
241
- const choiceNum = parseInt(exchangeChoice.trim(), 10);
242
- let exchange;
243
- let exchangeInfo;
244
- if (choiceNum >= 1 && choiceNum <= exchangeKeys.length) {
245
- exchange = exchangeKeys[choiceNum - 1];
246
- exchangeInfo = EXCHANGE_INFO[exchange];
247
- } else {
248
- exchange = await question(` ${c.yellow}?${c.reset} Exchange name: `);
249
- 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;
250
378
  }
251
379
  console.log(`
252
- ${c.white}${c.bold}STEP 2 \u2014 CREATE API KEYS${c.reset}
253
- ${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}
254
- `);
255
- if (exchangeInfo) {
256
- console.log(` ${c.white}${c.bold}${exchangeInfo.name}${c.reset} API Setup:
380
+ ${c.green}\u2713${c.reset} Selected: ${selectedExchanges.map((e) => e.info?.name || e.id).join(", ")}
257
381
  `);
258
- exchangeInfo.steps.forEach((step, i) => {
259
- console.log(` ${c.cyan}${i + 1}.${c.reset} ${step}`);
260
- });
261
- console.log("");
262
- console.log(` ${c.blue}API Page:${c.reset} ${exchangeInfo.apiUrl}`);
263
- if (exchangeInfo.testnetUrl) {
264
- console.log(` ${c.blue}Testnet:${c.reset} ${exchangeInfo.testnetUrl}`);
382
+ const config = {
383
+ exchanges: {},
384
+ security: {
385
+ maxOrderSize: 100,
386
+ confirmTrades: true
265
387
  }
266
- console.log(` ${c.blue}Help:${c.reset} ${exchangeInfo.docsUrl}`);
267
- } else {
268
- 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}
269
397
  `);
270
- console.log(` ${c.cyan}1.${c.reset} Log into ${exchange} and find API settings`);
271
- console.log(` ${c.cyan}2.${c.reset} Create a new API key`);
272
- 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}`);
273
- console.log(` ${c.cyan}4.${c.reset} Copy your API Key and Secret`);
274
- }
275
- console.log(`
276
- ${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}
277
- ${c.orange}\u2551${c.reset} ${c.orange}\u26A0 SECURITY:${c.reset} Never enable withdrawal permissions ${c.orange}\u2551${c.reset}
278
- ${c.orange}\u2551${c.reset} OmniTrade only needs read + trade access ${c.orange}\u2551${c.reset}
279
- ${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}
280
400
  `);
281
- await question(` ${c.dim}Press Enter when you have your keys...${c.reset}`);
282
- console.log(`
283
- ${c.white}${c.bold}STEP 3 \u2014 ENTER YOUR KEYS${c.reset}
284
- ${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}
285
-
286
- ${c.dim}Paste your API credentials below.${c.reset}
287
- ${c.dim}Stored at: ~/.omnitrade/config.json (local only)${c.reset}
288
-
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}
289
416
  `);
290
- const apiKey = await question(` ${c.cyan}API Key:${c.reset} `);
291
- const secret = await question(` ${c.cyan}Secret:${c.reset} `);
292
- let password = "";
293
- const needsPassphrase = exchangeInfo?.needsPassphrase || ["coinbase", "kucoin", "okx"].includes(exchange);
294
- if (needsPassphrase) {
295
- password = await question(` ${c.cyan}Passphrase:${c.reset} `);
296
- }
297
- console.log("");
298
- const hasTestnet = exchangeInfo?.testnetUrl || ["binance", "bybit"].includes(exchange);
299
- let testnet = false;
300
- if (hasTestnet) {
301
- console.log(` ${c.dim}Testnet available \u2014 practice with fake money first${c.reset}`);
302
- const testnetAnswer = await question(` ${c.yellow}?${c.reset} Use testnet mode? ${c.dim}(Y/n)${c.reset}: `);
303
- testnet = testnetAnswer.toLowerCase() !== "n";
304
- } else {
305
- console.log(` ${c.dim}Note: ${exchange} doesn't have a public testnet${c.reset}`);
306
- console.log(` ${c.dim}Your API will connect to the live exchange${c.reset}`);
307
- 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!`);
308
441
  }
309
442
  rl.close();
310
- 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,
311
453
  exchanges: {
312
- [exchange]: {
313
- apiKey: apiKey.trim(),
314
- secret: secret.trim(),
315
- ...password.trim() ? { password: password.trim() } : {},
316
- testnet
317
- }
318
- },
319
- security: {
320
- maxOrderSize: 100,
321
- confirmTrades: true
454
+ ...existingConfig.exchanges || {},
455
+ ...config.exchanges || {}
322
456
  }
323
457
  };
324
458
  const configDir = join(homedir(), ".omnitrade");
325
459
  if (!existsSync(configDir)) {
326
460
  mkdirSync(configDir, { recursive: true });
327
461
  }
328
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
462
+ writeFileSync(CONFIG_PATH, JSON.stringify(mergedConfig, null, 2));
329
463
  try {
330
464
  const { chmodSync } = await import("fs");
331
465
  chmodSync(CONFIG_PATH, 384);
332
466
  } catch {
333
467
  }
468
+ const exchangeCount = Object.keys(mergedConfig.exchanges).length;
469
+ const finalStepNum = selectedExchanges.length + 2;
334
470
  console.log(`
335
- ${c.green}${c.bold}\u2713 SAVED${c.reset}
471
+ ${c.green}${c.bold}\u2713 ${exchangeCount} EXCHANGE${exchangeCount > 1 ? "S" : ""} CONFIGURED${c.reset}
336
472
 
337
- ${c.white}${c.bold}STEP 4 \u2014 CONNECT TO CLAUDE${c.reset}
338
- ${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}
339
475
  `);
340
476
  const rl2 = readline.createInterface({
341
477
  input: process.stdin,
@@ -414,20 +550,25 @@ async function runSetupWizard() {
414
550
  `);
415
551
  }
416
552
  rl2.close();
553
+ const configuredExchanges = Object.keys(mergedConfig.exchanges);
554
+ const firstExchange = configuredExchanges[0] || "binance";
417
555
  console.log(`
418
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}
419
557
 
420
558
  ${c.white}${c.bold}TRY IT${c.reset}
421
559
 
422
- 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}
423
563
 
424
564
  ${c.white}${c.bold}USEFUL COMMANDS${c.reset}
425
565
 
426
- ${c.cyan}omnitrade test${c.reset} Test your connection
566
+ ${c.cyan}omnitrade test${c.reset} Test your connections
427
567
  ${c.cyan}omnitrade config${c.reset} View configuration
428
- ${c.cyan}omnitrade setup${c.reset} Add another exchange
568
+ ${c.cyan}omnitrade setup${c.reset} Add more exchanges
429
569
 
430
570
  ${c.green}${c.bold}\u2713 Setup complete!${c.reset}
571
+ ${c.dim}Configured: ${configuredExchanges.join(", ")}${c.reset}
431
572
 
432
573
  `);
433
574
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnitrade-mcp",
3
- "version": "0.6.4",
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",