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.
- package/dist/cli.js +234 -93
- 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
|
|
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}
|
|
218
|
-
${c.cyan}2.${c.reset}
|
|
219
|
-
${c.cyan}3.${c.reset}
|
|
220
|
-
${c.cyan}4.${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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
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.
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
${c.dim}
|
|
287
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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(
|
|
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
|
|
471
|
+
${c.green}${c.bold}\u2713 ${exchangeCount} EXCHANGE${exchangeCount > 1 ? "S" : ""} CONFIGURED${c.reset}
|
|
336
472
|
|
|
337
|
-
${c.white}${c.bold}STEP
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
}
|