toobit-trade-cli 1.0.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/index.d.ts +2 -0
- package/dist/index.js +491 -0
- package/package.json +30 -0
- package/src/commands/account.ts +51 -0
- package/src/commands/config.ts +64 -0
- package/src/commands/futures.ts +79 -0
- package/src/commands/market.ts +71 -0
- package/src/commands/spot.ts +52 -0
- package/src/formatter.ts +22 -0
- package/src/index.ts +93 -0
- package/src/parser.ts +79 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +9 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import {
|
|
5
|
+
loadConfig,
|
|
6
|
+
ToobitRestClient,
|
|
7
|
+
createToolRunner,
|
|
8
|
+
toToolErrorPayload,
|
|
9
|
+
configFilePath as configFilePath2
|
|
10
|
+
} from "@toobit_agent/agent-toobitkit-core";
|
|
11
|
+
|
|
12
|
+
// src/parser.ts
|
|
13
|
+
import { parseArgs } from "util";
|
|
14
|
+
function parseCli(argv = process.argv.slice(2)) {
|
|
15
|
+
const STRING_OPTIONS = /* @__PURE__ */ new Set([
|
|
16
|
+
"profile",
|
|
17
|
+
"symbol",
|
|
18
|
+
"interval",
|
|
19
|
+
"limit",
|
|
20
|
+
"side",
|
|
21
|
+
"type",
|
|
22
|
+
"quantity",
|
|
23
|
+
"price",
|
|
24
|
+
"orderId",
|
|
25
|
+
"clientOrderId",
|
|
26
|
+
"leverage",
|
|
27
|
+
"orderType",
|
|
28
|
+
"tokenId",
|
|
29
|
+
"instId",
|
|
30
|
+
"period",
|
|
31
|
+
"bar",
|
|
32
|
+
"startTime",
|
|
33
|
+
"endTime",
|
|
34
|
+
"marginType"
|
|
35
|
+
]);
|
|
36
|
+
let command = "help";
|
|
37
|
+
let subcommand = "";
|
|
38
|
+
const flagArgs = [];
|
|
39
|
+
let positionalCount = 0;
|
|
40
|
+
for (let i = 0; i < argv.length; i++) {
|
|
41
|
+
const arg = argv[i];
|
|
42
|
+
if (arg.startsWith("-")) {
|
|
43
|
+
flagArgs.push(arg);
|
|
44
|
+
const optName = arg.replace(/^--?/, "");
|
|
45
|
+
if (STRING_OPTIONS.has(optName) && i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
|
|
46
|
+
flagArgs.push(argv[++i]);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
if (positionalCount === 0) command = arg;
|
|
50
|
+
else if (positionalCount === 1) subcommand = arg;
|
|
51
|
+
positionalCount++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const parsed = parseArgs({
|
|
55
|
+
args: flagArgs,
|
|
56
|
+
options: {
|
|
57
|
+
profile: { type: "string" },
|
|
58
|
+
json: { type: "boolean", default: false },
|
|
59
|
+
"read-only": { type: "boolean", default: false },
|
|
60
|
+
symbol: { type: "string" },
|
|
61
|
+
interval: { type: "string" },
|
|
62
|
+
limit: { type: "string" },
|
|
63
|
+
side: { type: "string" },
|
|
64
|
+
type: { type: "string" },
|
|
65
|
+
quantity: { type: "string" },
|
|
66
|
+
price: { type: "string" },
|
|
67
|
+
orderId: { type: "string" },
|
|
68
|
+
clientOrderId: { type: "string" },
|
|
69
|
+
leverage: { type: "string" },
|
|
70
|
+
orderType: { type: "string" },
|
|
71
|
+
tokenId: { type: "string" },
|
|
72
|
+
instId: { type: "string" },
|
|
73
|
+
period: { type: "string" },
|
|
74
|
+
bar: { type: "string" },
|
|
75
|
+
startTime: { type: "string" },
|
|
76
|
+
endTime: { type: "string" },
|
|
77
|
+
marginType: { type: "string" }
|
|
78
|
+
},
|
|
79
|
+
strict: false,
|
|
80
|
+
allowPositionals: true
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
command,
|
|
84
|
+
subcommand,
|
|
85
|
+
profile: parsed.values.profile,
|
|
86
|
+
json: parsed.values.json ?? false,
|
|
87
|
+
readOnly: parsed.values["read-only"] ?? false,
|
|
88
|
+
flags: parsed.values,
|
|
89
|
+
positionals: parsed.positionals
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/formatter.ts
|
|
94
|
+
function formatJson(data, json) {
|
|
95
|
+
if (json) return JSON.stringify(data, null, 2);
|
|
96
|
+
if (data === null || data === void 0) return "No data.";
|
|
97
|
+
if (typeof data !== "object") return String(data);
|
|
98
|
+
return JSON.stringify(data, null, 2);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/commands/market.ts
|
|
102
|
+
async function handleMarketCommand(cli, run) {
|
|
103
|
+
const f = cli.flags;
|
|
104
|
+
let result;
|
|
105
|
+
switch (cli.subcommand) {
|
|
106
|
+
case "time":
|
|
107
|
+
result = await run("market_get_server_time", {});
|
|
108
|
+
break;
|
|
109
|
+
case "info":
|
|
110
|
+
result = await run("market_get_exchange_info", {});
|
|
111
|
+
break;
|
|
112
|
+
case "ticker":
|
|
113
|
+
result = await run("market_get_ticker_price", { symbol: f.symbol });
|
|
114
|
+
break;
|
|
115
|
+
case "ticker-24hr":
|
|
116
|
+
result = await run("market_get_ticker_24hr", { symbol: f.symbol });
|
|
117
|
+
break;
|
|
118
|
+
case "depth":
|
|
119
|
+
result = await run("market_get_depth", { symbol: f.symbol, limit: f.limit ? Number(f.limit) : void 0 });
|
|
120
|
+
break;
|
|
121
|
+
case "trades":
|
|
122
|
+
result = await run("market_get_trades", { symbol: f.symbol, limit: f.limit ? Number(f.limit) : void 0 });
|
|
123
|
+
break;
|
|
124
|
+
case "klines":
|
|
125
|
+
case "candles":
|
|
126
|
+
result = await run("market_get_klines", {
|
|
127
|
+
symbol: f.symbol,
|
|
128
|
+
interval: f.interval ?? f.bar ?? "1h",
|
|
129
|
+
limit: f.limit ? Number(f.limit) : void 0,
|
|
130
|
+
startTime: f.startTime ? Number(f.startTime) : void 0,
|
|
131
|
+
endTime: f.endTime ? Number(f.endTime) : void 0
|
|
132
|
+
});
|
|
133
|
+
break;
|
|
134
|
+
case "book-ticker":
|
|
135
|
+
result = await run("market_get_book_ticker", { symbol: f.symbol });
|
|
136
|
+
break;
|
|
137
|
+
case "mark-price":
|
|
138
|
+
result = await run("market_get_mark_price", { symbol: f.symbol });
|
|
139
|
+
break;
|
|
140
|
+
case "funding-rate":
|
|
141
|
+
result = await run("market_get_funding_rate", { symbol: f.symbol });
|
|
142
|
+
break;
|
|
143
|
+
case "funding-rate-history":
|
|
144
|
+
result = await run("market_get_funding_rate_history", {
|
|
145
|
+
symbol: f.symbol,
|
|
146
|
+
limit: f.limit ? Number(f.limit) : void 0
|
|
147
|
+
});
|
|
148
|
+
break;
|
|
149
|
+
case "open-interest":
|
|
150
|
+
result = await run("market_get_open_interest", { symbol: f.symbol });
|
|
151
|
+
break;
|
|
152
|
+
case "index":
|
|
153
|
+
result = await run("market_get_index_price", { symbol: f.symbol });
|
|
154
|
+
break;
|
|
155
|
+
case "contract-ticker":
|
|
156
|
+
result = await run("market_get_contract_ticker_24hr", { symbol: f.symbol });
|
|
157
|
+
break;
|
|
158
|
+
case "long-short-ratio":
|
|
159
|
+
result = await run("market_get_long_short_ratio", { symbol: f.symbol, period: f.period ?? "1h" });
|
|
160
|
+
break;
|
|
161
|
+
default:
|
|
162
|
+
process.stdout.write(`Unknown market subcommand: ${cli.subcommand}
|
|
163
|
+
Available: time, info, ticker, ticker-24hr, depth, trades, klines, candles, book-ticker, mark-price, funding-rate, funding-rate-history, open-interest, index, contract-ticker, long-short-ratio
|
|
164
|
+
`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
process.stdout.write(formatJson(result, cli.json) + "\n");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/commands/spot.ts
|
|
171
|
+
async function handleSpotCommand(cli, run) {
|
|
172
|
+
const f = cli.flags;
|
|
173
|
+
let result;
|
|
174
|
+
switch (cli.subcommand) {
|
|
175
|
+
case "place":
|
|
176
|
+
result = await run("spot_place_order", {
|
|
177
|
+
symbol: f.symbol,
|
|
178
|
+
side: f.side,
|
|
179
|
+
type: f.type ?? "MARKET",
|
|
180
|
+
quantity: f.quantity,
|
|
181
|
+
price: f.price
|
|
182
|
+
});
|
|
183
|
+
break;
|
|
184
|
+
case "cancel":
|
|
185
|
+
result = await run("spot_cancel_order", { orderId: f.orderId, clientOrderId: f.clientOrderId });
|
|
186
|
+
break;
|
|
187
|
+
case "cancel-all":
|
|
188
|
+
result = await run("spot_cancel_open_orders", { symbol: f.symbol });
|
|
189
|
+
break;
|
|
190
|
+
case "get":
|
|
191
|
+
result = await run("spot_get_order", { orderId: f.orderId, clientOrderId: f.clientOrderId });
|
|
192
|
+
break;
|
|
193
|
+
case "open-orders":
|
|
194
|
+
case "orders":
|
|
195
|
+
result = await run("spot_get_open_orders", { symbol: f.symbol, limit: f.limit ? Number(f.limit) : void 0 });
|
|
196
|
+
break;
|
|
197
|
+
case "history":
|
|
198
|
+
result = await run("spot_get_trade_orders", {
|
|
199
|
+
symbol: f.symbol,
|
|
200
|
+
limit: f.limit ? Number(f.limit) : void 0,
|
|
201
|
+
startTime: f.startTime ? Number(f.startTime) : void 0,
|
|
202
|
+
endTime: f.endTime ? Number(f.endTime) : void 0
|
|
203
|
+
});
|
|
204
|
+
break;
|
|
205
|
+
case "fills":
|
|
206
|
+
result = await run("spot_get_fills", {
|
|
207
|
+
symbol: f.symbol,
|
|
208
|
+
limit: f.limit ? Number(f.limit) : void 0
|
|
209
|
+
});
|
|
210
|
+
break;
|
|
211
|
+
default:
|
|
212
|
+
process.stdout.write(`Unknown spot subcommand: ${cli.subcommand}
|
|
213
|
+
Available: place, cancel, cancel-all, get, orders, open-orders, history, fills
|
|
214
|
+
`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
process.stdout.write(formatJson(result, cli.json) + "\n");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/commands/futures.ts
|
|
221
|
+
async function handleFuturesCommand(cli, run) {
|
|
222
|
+
const f = cli.flags;
|
|
223
|
+
let result;
|
|
224
|
+
switch (cli.subcommand) {
|
|
225
|
+
case "place":
|
|
226
|
+
result = await run("futures_place_order", {
|
|
227
|
+
symbol: f.symbol,
|
|
228
|
+
side: f.side,
|
|
229
|
+
orderType: f.orderType ?? f.type ?? "MARKET",
|
|
230
|
+
quantity: f.quantity,
|
|
231
|
+
price: f.price,
|
|
232
|
+
leverage: f.leverage
|
|
233
|
+
});
|
|
234
|
+
break;
|
|
235
|
+
case "cancel":
|
|
236
|
+
result = await run("futures_cancel_order", { orderId: f.orderId, clientOrderId: f.clientOrderId });
|
|
237
|
+
break;
|
|
238
|
+
case "cancel-all":
|
|
239
|
+
result = await run("futures_cancel_all_orders", { symbol: f.symbol });
|
|
240
|
+
break;
|
|
241
|
+
case "amend":
|
|
242
|
+
result = await run("futures_amend_order", { orderId: f.orderId, quantity: f.quantity, price: f.price });
|
|
243
|
+
break;
|
|
244
|
+
case "get":
|
|
245
|
+
result = await run("futures_get_order", { orderId: f.orderId, clientOrderId: f.clientOrderId });
|
|
246
|
+
break;
|
|
247
|
+
case "orders":
|
|
248
|
+
case "open-orders":
|
|
249
|
+
result = await run("futures_get_open_orders", { symbol: f.symbol });
|
|
250
|
+
break;
|
|
251
|
+
case "history":
|
|
252
|
+
result = await run("futures_get_history_orders", {
|
|
253
|
+
symbol: f.symbol,
|
|
254
|
+
limit: f.limit ? Number(f.limit) : void 0
|
|
255
|
+
});
|
|
256
|
+
break;
|
|
257
|
+
case "positions":
|
|
258
|
+
result = await run("futures_get_positions", { symbol: f.symbol });
|
|
259
|
+
break;
|
|
260
|
+
case "history-positions":
|
|
261
|
+
result = await run("futures_get_history_positions", { symbol: f.symbol });
|
|
262
|
+
break;
|
|
263
|
+
case "leverage":
|
|
264
|
+
if (f.leverage) {
|
|
265
|
+
result = await run("futures_set_leverage", { symbol: f.symbol, leverage: Number(f.leverage) });
|
|
266
|
+
} else {
|
|
267
|
+
result = await run("futures_get_leverage", { symbol: f.symbol });
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
case "margin-type":
|
|
271
|
+
result = await run("futures_set_margin_type", { symbol: f.symbol, marginType: f.marginType });
|
|
272
|
+
break;
|
|
273
|
+
case "flash-close":
|
|
274
|
+
result = await run("futures_flash_close", { symbol: f.symbol, side: f.side });
|
|
275
|
+
break;
|
|
276
|
+
case "balance":
|
|
277
|
+
result = await run("futures_get_balance", {});
|
|
278
|
+
break;
|
|
279
|
+
case "fills":
|
|
280
|
+
result = await run("futures_get_fills", { symbol: f.symbol, limit: f.limit ? Number(f.limit) : void 0 });
|
|
281
|
+
break;
|
|
282
|
+
case "pnl":
|
|
283
|
+
result = await run("futures_get_today_pnl", {});
|
|
284
|
+
break;
|
|
285
|
+
case "commission":
|
|
286
|
+
result = await run("futures_get_commission_rate", { symbol: f.symbol });
|
|
287
|
+
break;
|
|
288
|
+
default:
|
|
289
|
+
process.stdout.write(`Unknown futures subcommand: ${cli.subcommand}
|
|
290
|
+
Available: place, cancel, cancel-all, amend, get, orders, history, positions, history-positions, leverage, margin-type, flash-close, balance, fills, pnl, commission
|
|
291
|
+
`);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
process.stdout.write(formatJson(result, cli.json) + "\n");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/commands/account.ts
|
|
298
|
+
async function handleAccountCommand(cli, run) {
|
|
299
|
+
const f = cli.flags;
|
|
300
|
+
let result;
|
|
301
|
+
switch (cli.subcommand) {
|
|
302
|
+
case "balance":
|
|
303
|
+
case "info":
|
|
304
|
+
case "":
|
|
305
|
+
result = await run("account_get_info", {});
|
|
306
|
+
break;
|
|
307
|
+
case "balance-flow":
|
|
308
|
+
result = await run("account_get_balance_flow", {
|
|
309
|
+
tokenId: f.tokenId,
|
|
310
|
+
limit: f.limit ? Number(f.limit) : void 0
|
|
311
|
+
});
|
|
312
|
+
break;
|
|
313
|
+
case "sub-accounts":
|
|
314
|
+
result = await run("account_get_sub_accounts", {});
|
|
315
|
+
break;
|
|
316
|
+
case "check-api-key":
|
|
317
|
+
result = await run("account_check_api_key", {});
|
|
318
|
+
break;
|
|
319
|
+
case "deposit-address":
|
|
320
|
+
result = await run("account_get_deposit_address", { tokenId: f.tokenId });
|
|
321
|
+
break;
|
|
322
|
+
case "deposits":
|
|
323
|
+
result = await run("account_get_deposit_orders", {
|
|
324
|
+
tokenId: f.tokenId,
|
|
325
|
+
limit: f.limit ? Number(f.limit) : void 0
|
|
326
|
+
});
|
|
327
|
+
break;
|
|
328
|
+
case "withdrawals":
|
|
329
|
+
result = await run("account_get_withdraw_orders", {
|
|
330
|
+
tokenId: f.tokenId,
|
|
331
|
+
limit: f.limit ? Number(f.limit) : void 0
|
|
332
|
+
});
|
|
333
|
+
break;
|
|
334
|
+
case "audit":
|
|
335
|
+
result = await run("trade_get_history", { limit: f.limit ? Number(f.limit) : void 0 });
|
|
336
|
+
break;
|
|
337
|
+
default:
|
|
338
|
+
process.stdout.write(`Unknown account subcommand: ${cli.subcommand}
|
|
339
|
+
Available: balance, info, balance-flow, sub-accounts, check-api-key, deposit-address, deposits, withdrawals, audit
|
|
340
|
+
`);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
process.stdout.write(formatJson(result, cli.json) + "\n");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/commands/config.ts
|
|
347
|
+
import * as readline from "readline";
|
|
348
|
+
import {
|
|
349
|
+
configFilePath,
|
|
350
|
+
readFullConfig,
|
|
351
|
+
writeFullConfig
|
|
352
|
+
} from "@toobit_agent/agent-toobitkit-core";
|
|
353
|
+
function prompt(question) {
|
|
354
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
355
|
+
return new Promise((resolve) => {
|
|
356
|
+
rl.question(question, (answer) => {
|
|
357
|
+
rl.close();
|
|
358
|
+
resolve(answer.trim());
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
async function handleConfigCommand(cli) {
|
|
363
|
+
switch (cli.subcommand) {
|
|
364
|
+
case "init": {
|
|
365
|
+
process.stdout.write("Toobit API Configuration Wizard\n\n");
|
|
366
|
+
const profileName = await prompt("Profile name (default: live): ") || "live";
|
|
367
|
+
const apiKey = await prompt("API Key: ");
|
|
368
|
+
const secretKey = await prompt("Secret Key: ");
|
|
369
|
+
if (!apiKey || !secretKey) {
|
|
370
|
+
process.stdout.write("API Key and Secret Key are required.\n");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const config = readFullConfig();
|
|
374
|
+
config.default_profile = config.default_profile ?? profileName;
|
|
375
|
+
if (!config.profiles) config.profiles = {};
|
|
376
|
+
config.profiles[profileName] = { api_key: apiKey, secret_key: secretKey };
|
|
377
|
+
writeFullConfig(config);
|
|
378
|
+
process.stdout.write(`
|
|
379
|
+
\u2713 Saved to ${configFilePath()}
|
|
380
|
+
Profile: ${profileName}
|
|
381
|
+
`);
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case "show": {
|
|
385
|
+
const config = readFullConfig();
|
|
386
|
+
process.stdout.write(`Config file: ${configFilePath()}
|
|
387
|
+
|
|
388
|
+
`);
|
|
389
|
+
process.stdout.write(`Default profile: ${config.default_profile ?? "(none)"}
|
|
390
|
+
`);
|
|
391
|
+
process.stdout.write(`Profiles: ${Object.keys(config.profiles ?? {}).join(", ") || "(none)"}
|
|
392
|
+
`);
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
case "list-profiles":
|
|
396
|
+
case "list": {
|
|
397
|
+
const config = readFullConfig();
|
|
398
|
+
const profiles = Object.keys(config.profiles ?? {});
|
|
399
|
+
if (profiles.length === 0) {
|
|
400
|
+
process.stdout.write("No profiles configured.\n");
|
|
401
|
+
} else {
|
|
402
|
+
for (const name of profiles) {
|
|
403
|
+
const marker = name === config.default_profile ? " (default)" : "";
|
|
404
|
+
process.stdout.write(` ${name}${marker}
|
|
405
|
+
`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
default:
|
|
411
|
+
process.stdout.write(`Unknown config subcommand: ${cli.subcommand}
|
|
412
|
+
Available: init, show, list
|
|
413
|
+
`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/index.ts
|
|
418
|
+
function printHelp() {
|
|
419
|
+
const help = `
|
|
420
|
+
Toobit Trade CLI \u2014 Trade from your terminal
|
|
421
|
+
|
|
422
|
+
Usage: toobit <command> <subcommand> [options]
|
|
423
|
+
|
|
424
|
+
Commands:
|
|
425
|
+
market Market data (ticker, depth, klines, funding-rate, etc.)
|
|
426
|
+
spot Spot trading (place, cancel, orders, fills)
|
|
427
|
+
futures USDT-M futures (place, cancel, positions, leverage, etc.)
|
|
428
|
+
account Account info (balance, deposits, withdrawals, audit)
|
|
429
|
+
config Configuration management (init, show, list)
|
|
430
|
+
|
|
431
|
+
Global Options:
|
|
432
|
+
--profile <name> Profile from ${configFilePath2()}
|
|
433
|
+
--json Output raw JSON
|
|
434
|
+
--read-only Disable write operations
|
|
435
|
+
--help Show this help
|
|
436
|
+
|
|
437
|
+
Examples:
|
|
438
|
+
toobit market ticker --symbol BTCUSDT
|
|
439
|
+
toobit market candles --symbol BTCUSDT --interval 1h --limit 10
|
|
440
|
+
toobit market funding-rate --symbol BTCUSDT
|
|
441
|
+
toobit spot place --symbol BTCUSDT --side BUY --type MARKET --quantity 0.001
|
|
442
|
+
toobit futures positions --symbol BTCUSDT
|
|
443
|
+
toobit account balance
|
|
444
|
+
toobit config init
|
|
445
|
+
`;
|
|
446
|
+
process.stdout.write(help);
|
|
447
|
+
}
|
|
448
|
+
async function main() {
|
|
449
|
+
const cli = parseCli();
|
|
450
|
+
if (cli.command === "help" || cli.flags.help) {
|
|
451
|
+
printHelp();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (cli.command === "config") {
|
|
455
|
+
await handleConfigCommand(cli);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const config = loadConfig({
|
|
459
|
+
modules: "all",
|
|
460
|
+
profile: cli.profile,
|
|
461
|
+
readOnly: cli.readOnly,
|
|
462
|
+
userAgent: "toobit-trade-cli/1.0.0",
|
|
463
|
+
sourceTag: "CLI"
|
|
464
|
+
});
|
|
465
|
+
const client = new ToobitRestClient(config);
|
|
466
|
+
const run = createToolRunner(client, config);
|
|
467
|
+
switch (cli.command) {
|
|
468
|
+
case "market":
|
|
469
|
+
await handleMarketCommand(cli, run);
|
|
470
|
+
break;
|
|
471
|
+
case "spot":
|
|
472
|
+
await handleSpotCommand(cli, run);
|
|
473
|
+
break;
|
|
474
|
+
case "futures":
|
|
475
|
+
await handleFuturesCommand(cli, run);
|
|
476
|
+
break;
|
|
477
|
+
case "account":
|
|
478
|
+
await handleAccountCommand(cli, run);
|
|
479
|
+
break;
|
|
480
|
+
default:
|
|
481
|
+
process.stdout.write(`Unknown command: ${cli.command}
|
|
482
|
+
`);
|
|
483
|
+
printHelp();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
main().catch((error) => {
|
|
487
|
+
const payload = toToolErrorPayload(error);
|
|
488
|
+
process.stderr.write(`${JSON.stringify(payload, null, 2)}
|
|
489
|
+
`);
|
|
490
|
+
process.exitCode = 1;
|
|
491
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "toobit-trade-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"toobit": "dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"types": "dist/index.d.ts",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"clean": "rm -rf dist"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@toobit_agent/agent-toobitkit-core": "workspace:*",
|
|
23
|
+
"smol-toml": "^1.3.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.0.0",
|
|
27
|
+
"tsup": "^8.0.0",
|
|
28
|
+
"typescript": "^5.5.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ToolRunner } from "@toobit_agent/agent-toobitkit-core";
|
|
2
|
+
import type { CliParsed } from "../parser.js";
|
|
3
|
+
import { formatJson } from "../formatter.js";
|
|
4
|
+
|
|
5
|
+
export async function handleAccountCommand(cli: CliParsed, run: ToolRunner): Promise<void> {
|
|
6
|
+
const f = cli.flags;
|
|
7
|
+
let result;
|
|
8
|
+
|
|
9
|
+
switch (cli.subcommand) {
|
|
10
|
+
case "balance":
|
|
11
|
+
case "info":
|
|
12
|
+
case "":
|
|
13
|
+
result = await run("account_get_info", {});
|
|
14
|
+
break;
|
|
15
|
+
case "balance-flow":
|
|
16
|
+
result = await run("account_get_balance_flow", {
|
|
17
|
+
tokenId: f.tokenId,
|
|
18
|
+
limit: f.limit ? Number(f.limit) : undefined,
|
|
19
|
+
});
|
|
20
|
+
break;
|
|
21
|
+
case "sub-accounts":
|
|
22
|
+
result = await run("account_get_sub_accounts", {});
|
|
23
|
+
break;
|
|
24
|
+
case "check-api-key":
|
|
25
|
+
result = await run("account_check_api_key", {});
|
|
26
|
+
break;
|
|
27
|
+
case "deposit-address":
|
|
28
|
+
result = await run("account_get_deposit_address", { tokenId: f.tokenId });
|
|
29
|
+
break;
|
|
30
|
+
case "deposits":
|
|
31
|
+
result = await run("account_get_deposit_orders", {
|
|
32
|
+
tokenId: f.tokenId,
|
|
33
|
+
limit: f.limit ? Number(f.limit) : undefined,
|
|
34
|
+
});
|
|
35
|
+
break;
|
|
36
|
+
case "withdrawals":
|
|
37
|
+
result = await run("account_get_withdraw_orders", {
|
|
38
|
+
tokenId: f.tokenId,
|
|
39
|
+
limit: f.limit ? Number(f.limit) : undefined,
|
|
40
|
+
});
|
|
41
|
+
break;
|
|
42
|
+
case "audit":
|
|
43
|
+
result = await run("trade_get_history", { limit: f.limit ? Number(f.limit) : undefined });
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
process.stdout.write(`Unknown account subcommand: ${cli.subcommand}\nAvailable: balance, info, balance-flow, sub-accounts, check-api-key, deposit-address, deposits, withdrawals, audit\n`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
process.stdout.write(formatJson(result, cli.json) + "\n");
|
|
51
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as readline from "node:readline";
|
|
2
|
+
import {
|
|
3
|
+
configFilePath,
|
|
4
|
+
readFullConfig,
|
|
5
|
+
writeFullConfig,
|
|
6
|
+
} from "@toobit_agent/agent-toobitkit-core";
|
|
7
|
+
import type { CliParsed } from "../parser.js";
|
|
8
|
+
|
|
9
|
+
function prompt(question: string): Promise<string> {
|
|
10
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
rl.question(question, (answer) => {
|
|
13
|
+
rl.close();
|
|
14
|
+
resolve(answer.trim());
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function handleConfigCommand(cli: CliParsed): Promise<void> {
|
|
20
|
+
switch (cli.subcommand) {
|
|
21
|
+
case "init": {
|
|
22
|
+
process.stdout.write("Toobit API Configuration Wizard\n\n");
|
|
23
|
+
const profileName = (await prompt("Profile name (default: live): ")) || "live";
|
|
24
|
+
const apiKey = await prompt("API Key: ");
|
|
25
|
+
const secretKey = await prompt("Secret Key: ");
|
|
26
|
+
|
|
27
|
+
if (!apiKey || !secretKey) {
|
|
28
|
+
process.stdout.write("API Key and Secret Key are required.\n");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const config = readFullConfig();
|
|
33
|
+
config.default_profile = config.default_profile ?? profileName;
|
|
34
|
+
if (!config.profiles) config.profiles = {};
|
|
35
|
+
config.profiles[profileName] = { api_key: apiKey, secret_key: secretKey };
|
|
36
|
+
writeFullConfig(config);
|
|
37
|
+
process.stdout.write(`\n✓ Saved to ${configFilePath()}\n Profile: ${profileName}\n`);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case "show": {
|
|
41
|
+
const config = readFullConfig();
|
|
42
|
+
process.stdout.write(`Config file: ${configFilePath()}\n\n`);
|
|
43
|
+
process.stdout.write(`Default profile: ${config.default_profile ?? "(none)"}\n`);
|
|
44
|
+
process.stdout.write(`Profiles: ${Object.keys(config.profiles ?? {}).join(", ") || "(none)"}\n`);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case "list-profiles":
|
|
48
|
+
case "list": {
|
|
49
|
+
const config = readFullConfig();
|
|
50
|
+
const profiles = Object.keys(config.profiles ?? {});
|
|
51
|
+
if (profiles.length === 0) {
|
|
52
|
+
process.stdout.write("No profiles configured.\n");
|
|
53
|
+
} else {
|
|
54
|
+
for (const name of profiles) {
|
|
55
|
+
const marker = name === config.default_profile ? " (default)" : "";
|
|
56
|
+
process.stdout.write(` ${name}${marker}\n`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
default:
|
|
62
|
+
process.stdout.write(`Unknown config subcommand: ${cli.subcommand}\nAvailable: init, show, list\n`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { ToolRunner } from "@toobit_agent/agent-toobitkit-core";
|
|
2
|
+
import type { CliParsed } from "../parser.js";
|
|
3
|
+
import { formatJson } from "../formatter.js";
|
|
4
|
+
|
|
5
|
+
export async function handleFuturesCommand(cli: CliParsed, run: ToolRunner): Promise<void> {
|
|
6
|
+
const f = cli.flags;
|
|
7
|
+
let result;
|
|
8
|
+
|
|
9
|
+
switch (cli.subcommand) {
|
|
10
|
+
case "place":
|
|
11
|
+
result = await run("futures_place_order", {
|
|
12
|
+
symbol: f.symbol,
|
|
13
|
+
side: f.side,
|
|
14
|
+
orderType: f.orderType ?? f.type ?? "MARKET",
|
|
15
|
+
quantity: f.quantity,
|
|
16
|
+
price: f.price,
|
|
17
|
+
leverage: f.leverage,
|
|
18
|
+
});
|
|
19
|
+
break;
|
|
20
|
+
case "cancel":
|
|
21
|
+
result = await run("futures_cancel_order", { orderId: f.orderId, clientOrderId: f.clientOrderId });
|
|
22
|
+
break;
|
|
23
|
+
case "cancel-all":
|
|
24
|
+
result = await run("futures_cancel_all_orders", { symbol: f.symbol });
|
|
25
|
+
break;
|
|
26
|
+
case "amend":
|
|
27
|
+
result = await run("futures_amend_order", { orderId: f.orderId, quantity: f.quantity, price: f.price });
|
|
28
|
+
break;
|
|
29
|
+
case "get":
|
|
30
|
+
result = await run("futures_get_order", { orderId: f.orderId, clientOrderId: f.clientOrderId });
|
|
31
|
+
break;
|
|
32
|
+
case "orders":
|
|
33
|
+
case "open-orders":
|
|
34
|
+
result = await run("futures_get_open_orders", { symbol: f.symbol });
|
|
35
|
+
break;
|
|
36
|
+
case "history":
|
|
37
|
+
result = await run("futures_get_history_orders", {
|
|
38
|
+
symbol: f.symbol,
|
|
39
|
+
limit: f.limit ? Number(f.limit) : undefined,
|
|
40
|
+
});
|
|
41
|
+
break;
|
|
42
|
+
case "positions":
|
|
43
|
+
result = await run("futures_get_positions", { symbol: f.symbol });
|
|
44
|
+
break;
|
|
45
|
+
case "history-positions":
|
|
46
|
+
result = await run("futures_get_history_positions", { symbol: f.symbol });
|
|
47
|
+
break;
|
|
48
|
+
case "leverage":
|
|
49
|
+
if (f.leverage) {
|
|
50
|
+
result = await run("futures_set_leverage", { symbol: f.symbol, leverage: Number(f.leverage) });
|
|
51
|
+
} else {
|
|
52
|
+
result = await run("futures_get_leverage", { symbol: f.symbol });
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
case "margin-type":
|
|
56
|
+
result = await run("futures_set_margin_type", { symbol: f.symbol, marginType: f.marginType });
|
|
57
|
+
break;
|
|
58
|
+
case "flash-close":
|
|
59
|
+
result = await run("futures_flash_close", { symbol: f.symbol, side: f.side });
|
|
60
|
+
break;
|
|
61
|
+
case "balance":
|
|
62
|
+
result = await run("futures_get_balance", {});
|
|
63
|
+
break;
|
|
64
|
+
case "fills":
|
|
65
|
+
result = await run("futures_get_fills", { symbol: f.symbol, limit: f.limit ? Number(f.limit) : undefined });
|
|
66
|
+
break;
|
|
67
|
+
case "pnl":
|
|
68
|
+
result = await run("futures_get_today_pnl", {});
|
|
69
|
+
break;
|
|
70
|
+
case "commission":
|
|
71
|
+
result = await run("futures_get_commission_rate", { symbol: f.symbol });
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
process.stdout.write(`Unknown futures subcommand: ${cli.subcommand}\nAvailable: place, cancel, cancel-all, amend, get, orders, history, positions, history-positions, leverage, margin-type, flash-close, balance, fills, pnl, commission\n`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
process.stdout.write(formatJson(result, cli.json) + "\n");
|
|
79
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ToolRunner } from "@toobit_agent/agent-toobitkit-core";
|
|
2
|
+
import type { CliParsed } from "../parser.js";
|
|
3
|
+
import { formatJson } from "../formatter.js";
|
|
4
|
+
|
|
5
|
+
export async function handleMarketCommand(cli: CliParsed, run: ToolRunner): Promise<void> {
|
|
6
|
+
const f = cli.flags;
|
|
7
|
+
let result;
|
|
8
|
+
|
|
9
|
+
switch (cli.subcommand) {
|
|
10
|
+
case "time":
|
|
11
|
+
result = await run("market_get_server_time", {});
|
|
12
|
+
break;
|
|
13
|
+
case "info":
|
|
14
|
+
result = await run("market_get_exchange_info", {});
|
|
15
|
+
break;
|
|
16
|
+
case "ticker":
|
|
17
|
+
result = await run("market_get_ticker_price", { symbol: f.symbol });
|
|
18
|
+
break;
|
|
19
|
+
case "ticker-24hr":
|
|
20
|
+
result = await run("market_get_ticker_24hr", { symbol: f.symbol });
|
|
21
|
+
break;
|
|
22
|
+
case "depth":
|
|
23
|
+
result = await run("market_get_depth", { symbol: f.symbol, limit: f.limit ? Number(f.limit) : undefined });
|
|
24
|
+
break;
|
|
25
|
+
case "trades":
|
|
26
|
+
result = await run("market_get_trades", { symbol: f.symbol, limit: f.limit ? Number(f.limit) : undefined });
|
|
27
|
+
break;
|
|
28
|
+
case "klines":
|
|
29
|
+
case "candles":
|
|
30
|
+
result = await run("market_get_klines", {
|
|
31
|
+
symbol: f.symbol,
|
|
32
|
+
interval: f.interval ?? f.bar ?? "1h",
|
|
33
|
+
limit: f.limit ? Number(f.limit) : undefined,
|
|
34
|
+
startTime: f.startTime ? Number(f.startTime) : undefined,
|
|
35
|
+
endTime: f.endTime ? Number(f.endTime) : undefined,
|
|
36
|
+
});
|
|
37
|
+
break;
|
|
38
|
+
case "book-ticker":
|
|
39
|
+
result = await run("market_get_book_ticker", { symbol: f.symbol });
|
|
40
|
+
break;
|
|
41
|
+
case "mark-price":
|
|
42
|
+
result = await run("market_get_mark_price", { symbol: f.symbol });
|
|
43
|
+
break;
|
|
44
|
+
case "funding-rate":
|
|
45
|
+
result = await run("market_get_funding_rate", { symbol: f.symbol });
|
|
46
|
+
break;
|
|
47
|
+
case "funding-rate-history":
|
|
48
|
+
result = await run("market_get_funding_rate_history", {
|
|
49
|
+
symbol: f.symbol,
|
|
50
|
+
limit: f.limit ? Number(f.limit) : undefined,
|
|
51
|
+
});
|
|
52
|
+
break;
|
|
53
|
+
case "open-interest":
|
|
54
|
+
result = await run("market_get_open_interest", { symbol: f.symbol });
|
|
55
|
+
break;
|
|
56
|
+
case "index":
|
|
57
|
+
result = await run("market_get_index_price", { symbol: f.symbol });
|
|
58
|
+
break;
|
|
59
|
+
case "contract-ticker":
|
|
60
|
+
result = await run("market_get_contract_ticker_24hr", { symbol: f.symbol });
|
|
61
|
+
break;
|
|
62
|
+
case "long-short-ratio":
|
|
63
|
+
result = await run("market_get_long_short_ratio", { symbol: f.symbol, period: f.period ?? "1h" });
|
|
64
|
+
break;
|
|
65
|
+
default:
|
|
66
|
+
process.stdout.write(`Unknown market subcommand: ${cli.subcommand}\nAvailable: time, info, ticker, ticker-24hr, depth, trades, klines, candles, book-ticker, mark-price, funding-rate, funding-rate-history, open-interest, index, contract-ticker, long-short-ratio\n`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
process.stdout.write(formatJson(result, cli.json) + "\n");
|
|
71
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ToolRunner } from "@toobit_agent/agent-toobitkit-core";
|
|
2
|
+
import type { CliParsed } from "../parser.js";
|
|
3
|
+
import { formatJson } from "../formatter.js";
|
|
4
|
+
|
|
5
|
+
export async function handleSpotCommand(cli: CliParsed, run: ToolRunner): Promise<void> {
|
|
6
|
+
const f = cli.flags;
|
|
7
|
+
let result;
|
|
8
|
+
|
|
9
|
+
switch (cli.subcommand) {
|
|
10
|
+
case "place":
|
|
11
|
+
result = await run("spot_place_order", {
|
|
12
|
+
symbol: f.symbol,
|
|
13
|
+
side: f.side,
|
|
14
|
+
type: f.type ?? "MARKET",
|
|
15
|
+
quantity: f.quantity,
|
|
16
|
+
price: f.price,
|
|
17
|
+
});
|
|
18
|
+
break;
|
|
19
|
+
case "cancel":
|
|
20
|
+
result = await run("spot_cancel_order", { orderId: f.orderId, clientOrderId: f.clientOrderId });
|
|
21
|
+
break;
|
|
22
|
+
case "cancel-all":
|
|
23
|
+
result = await run("spot_cancel_open_orders", { symbol: f.symbol });
|
|
24
|
+
break;
|
|
25
|
+
case "get":
|
|
26
|
+
result = await run("spot_get_order", { orderId: f.orderId, clientOrderId: f.clientOrderId });
|
|
27
|
+
break;
|
|
28
|
+
case "open-orders":
|
|
29
|
+
case "orders":
|
|
30
|
+
result = await run("spot_get_open_orders", { symbol: f.symbol, limit: f.limit ? Number(f.limit) : undefined });
|
|
31
|
+
break;
|
|
32
|
+
case "history":
|
|
33
|
+
result = await run("spot_get_trade_orders", {
|
|
34
|
+
symbol: f.symbol,
|
|
35
|
+
limit: f.limit ? Number(f.limit) : undefined,
|
|
36
|
+
startTime: f.startTime ? Number(f.startTime) : undefined,
|
|
37
|
+
endTime: f.endTime ? Number(f.endTime) : undefined,
|
|
38
|
+
});
|
|
39
|
+
break;
|
|
40
|
+
case "fills":
|
|
41
|
+
result = await run("spot_get_fills", {
|
|
42
|
+
symbol: f.symbol,
|
|
43
|
+
limit: f.limit ? Number(f.limit) : undefined,
|
|
44
|
+
});
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
process.stdout.write(`Unknown spot subcommand: ${cli.subcommand}\nAvailable: place, cancel, cancel-all, get, orders, open-orders, history, fills\n`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
process.stdout.write(formatJson(result, cli.json) + "\n");
|
|
52
|
+
}
|
package/src/formatter.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function formatJson(data: unknown, json: boolean): string {
|
|
2
|
+
if (json) return JSON.stringify(data, null, 2);
|
|
3
|
+
if (data === null || data === undefined) return "No data.";
|
|
4
|
+
if (typeof data !== "object") return String(data);
|
|
5
|
+
return JSON.stringify(data, null, 2);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function formatTable(rows: Record<string, unknown>[], columns?: string[]): string {
|
|
9
|
+
if (rows.length === 0) return "No data.";
|
|
10
|
+
const keys = columns ?? Object.keys(rows[0]);
|
|
11
|
+
const widths = keys.map((k) =>
|
|
12
|
+
Math.max(k.length, ...rows.map((r) => String(r[k] ?? "").length)),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const header = keys.map((k, i) => k.padEnd(widths[i])).join(" ");
|
|
16
|
+
const separator = widths.map((w) => "-".repeat(w)).join(" ");
|
|
17
|
+
const body = rows.map((row) =>
|
|
18
|
+
keys.map((k, i) => String(row[k] ?? "").padEnd(widths[i])).join(" "),
|
|
19
|
+
).join("\n");
|
|
20
|
+
|
|
21
|
+
return `${header}\n${separator}\n${body}`;
|
|
22
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadConfig,
|
|
3
|
+
ToobitRestClient,
|
|
4
|
+
createToolRunner,
|
|
5
|
+
toToolErrorPayload,
|
|
6
|
+
configFilePath,
|
|
7
|
+
} from "@toobit_agent/agent-toobitkit-core";
|
|
8
|
+
import { parseCli } from "./parser.js";
|
|
9
|
+
import { handleMarketCommand } from "./commands/market.js";
|
|
10
|
+
import { handleSpotCommand } from "./commands/spot.js";
|
|
11
|
+
import { handleFuturesCommand } from "./commands/futures.js";
|
|
12
|
+
import { handleAccountCommand } from "./commands/account.js";
|
|
13
|
+
import { handleConfigCommand } from "./commands/config.js";
|
|
14
|
+
|
|
15
|
+
function printHelp(): void {
|
|
16
|
+
const help = `
|
|
17
|
+
Toobit Trade CLI — Trade from your terminal
|
|
18
|
+
|
|
19
|
+
Usage: toobit <command> <subcommand> [options]
|
|
20
|
+
|
|
21
|
+
Commands:
|
|
22
|
+
market Market data (ticker, depth, klines, funding-rate, etc.)
|
|
23
|
+
spot Spot trading (place, cancel, orders, fills)
|
|
24
|
+
futures USDT-M futures (place, cancel, positions, leverage, etc.)
|
|
25
|
+
account Account info (balance, deposits, withdrawals, audit)
|
|
26
|
+
config Configuration management (init, show, list)
|
|
27
|
+
|
|
28
|
+
Global Options:
|
|
29
|
+
--profile <name> Profile from ${configFilePath()}
|
|
30
|
+
--json Output raw JSON
|
|
31
|
+
--read-only Disable write operations
|
|
32
|
+
--help Show this help
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
toobit market ticker --symbol BTCUSDT
|
|
36
|
+
toobit market candles --symbol BTCUSDT --interval 1h --limit 10
|
|
37
|
+
toobit market funding-rate --symbol BTCUSDT
|
|
38
|
+
toobit spot place --symbol BTCUSDT --side BUY --type MARKET --quantity 0.001
|
|
39
|
+
toobit futures positions --symbol BTCUSDT
|
|
40
|
+
toobit account balance
|
|
41
|
+
toobit config init
|
|
42
|
+
`;
|
|
43
|
+
process.stdout.write(help);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function main(): Promise<void> {
|
|
47
|
+
const cli = parseCli();
|
|
48
|
+
|
|
49
|
+
if (cli.command === "help" || cli.flags.help) {
|
|
50
|
+
printHelp();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (cli.command === "config") {
|
|
55
|
+
await handleConfigCommand(cli);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const config = loadConfig({
|
|
60
|
+
modules: "all",
|
|
61
|
+
profile: cli.profile,
|
|
62
|
+
readOnly: cli.readOnly,
|
|
63
|
+
userAgent: "toobit-trade-cli/1.0.0",
|
|
64
|
+
sourceTag: "CLI",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const client = new ToobitRestClient(config);
|
|
68
|
+
const run = createToolRunner(client, config);
|
|
69
|
+
|
|
70
|
+
switch (cli.command) {
|
|
71
|
+
case "market":
|
|
72
|
+
await handleMarketCommand(cli, run);
|
|
73
|
+
break;
|
|
74
|
+
case "spot":
|
|
75
|
+
await handleSpotCommand(cli, run);
|
|
76
|
+
break;
|
|
77
|
+
case "futures":
|
|
78
|
+
await handleFuturesCommand(cli, run);
|
|
79
|
+
break;
|
|
80
|
+
case "account":
|
|
81
|
+
await handleAccountCommand(cli, run);
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
process.stdout.write(`Unknown command: ${cli.command}\n`);
|
|
85
|
+
printHelp();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
main().catch((error: unknown) => {
|
|
90
|
+
const payload = toToolErrorPayload(error);
|
|
91
|
+
process.stderr.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
});
|
package/src/parser.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
|
|
3
|
+
export interface CliParsed {
|
|
4
|
+
command: string;
|
|
5
|
+
subcommand: string;
|
|
6
|
+
profile?: string;
|
|
7
|
+
json: boolean;
|
|
8
|
+
readOnly: boolean;
|
|
9
|
+
flags: Record<string, string | boolean | undefined>;
|
|
10
|
+
positionals: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parseCli(argv = process.argv.slice(2)): CliParsed {
|
|
14
|
+
const STRING_OPTIONS = new Set([
|
|
15
|
+
"profile", "symbol", "interval", "limit", "side", "type",
|
|
16
|
+
"quantity", "price", "orderId", "clientOrderId", "leverage",
|
|
17
|
+
"orderType", "tokenId", "instId", "period", "bar",
|
|
18
|
+
"startTime", "endTime", "marginType",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
let command = "help";
|
|
22
|
+
let subcommand = "";
|
|
23
|
+
const flagArgs: string[] = [];
|
|
24
|
+
let positionalCount = 0;
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < argv.length; i++) {
|
|
27
|
+
const arg = argv[i];
|
|
28
|
+
if (arg.startsWith("-")) {
|
|
29
|
+
flagArgs.push(arg);
|
|
30
|
+
const optName = arg.replace(/^--?/, "");
|
|
31
|
+
if (STRING_OPTIONS.has(optName) && i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
|
|
32
|
+
flagArgs.push(argv[++i]);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
if (positionalCount === 0) command = arg;
|
|
36
|
+
else if (positionalCount === 1) subcommand = arg;
|
|
37
|
+
positionalCount++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const parsed = parseArgs({
|
|
42
|
+
args: flagArgs,
|
|
43
|
+
options: {
|
|
44
|
+
profile: { type: "string" },
|
|
45
|
+
json: { type: "boolean", default: false },
|
|
46
|
+
"read-only": { type: "boolean", default: false },
|
|
47
|
+
symbol: { type: "string" },
|
|
48
|
+
interval: { type: "string" },
|
|
49
|
+
limit: { type: "string" },
|
|
50
|
+
side: { type: "string" },
|
|
51
|
+
type: { type: "string" },
|
|
52
|
+
quantity: { type: "string" },
|
|
53
|
+
price: { type: "string" },
|
|
54
|
+
orderId: { type: "string" },
|
|
55
|
+
clientOrderId: { type: "string" },
|
|
56
|
+
leverage: { type: "string" },
|
|
57
|
+
orderType: { type: "string" },
|
|
58
|
+
tokenId: { type: "string" },
|
|
59
|
+
instId: { type: "string" },
|
|
60
|
+
period: { type: "string" },
|
|
61
|
+
bar: { type: "string" },
|
|
62
|
+
startTime: { type: "string" },
|
|
63
|
+
endTime: { type: "string" },
|
|
64
|
+
marginType: { type: "string" },
|
|
65
|
+
},
|
|
66
|
+
strict: false,
|
|
67
|
+
allowPositionals: true,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
command,
|
|
72
|
+
subcommand,
|
|
73
|
+
profile: parsed.values.profile as string | undefined,
|
|
74
|
+
json: (parsed.values.json as boolean) ?? false,
|
|
75
|
+
readOnly: (parsed.values["read-only"] as boolean) ?? false,
|
|
76
|
+
flags: parsed.values as Record<string, string | boolean | undefined>,
|
|
77
|
+
positionals: parsed.positionals as string[],
|
|
78
|
+
};
|
|
79
|
+
}
|
package/tsconfig.json
ADDED