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.
- package/dist/cli.js +244 -100
- 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.
|
|
41
|
-
${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}
|
|
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
|
|
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}
|
|
215
|
-
${c.cyan}2.${c.reset}
|
|
216
|
-
${c.cyan}3.${c.reset}
|
|
217
|
-
${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}
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
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.
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
268
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
${c.dim}
|
|
284
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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(
|
|
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
|
|
471
|
+
${c.green}${c.bold}\u2713 ${exchangeCount} EXCHANGE${exchangeCount > 1 ? "S" : ""} CONFIGURED${c.reset}
|
|
333
472
|
|
|
334
|
-
${c.white}${c.bold}STEP
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
}
|