okx-trade-cli 1.0.1 → 1.0.3
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.js +363 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { parseArgs } from "util";
|
|
5
|
+
import { createRequire } from "module";
|
|
5
6
|
|
|
6
|
-
//
|
|
7
|
+
// ../core/dist/index.js
|
|
7
8
|
import { createHmac } from "crypto";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import os from "os";
|
|
8
11
|
import { readFileSync, existsSync } from "fs";
|
|
9
12
|
import { join } from "path";
|
|
10
13
|
import { homedir } from "os";
|
|
11
14
|
import { parse } from "smol-toml";
|
|
15
|
+
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
|
|
16
|
+
import { join as join2 } from "path";
|
|
17
|
+
import { homedir as homedir2 } from "os";
|
|
12
18
|
function getNow() {
|
|
13
19
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
14
20
|
}
|
|
@@ -20,6 +26,7 @@ var OkxMcpError = class extends Error {
|
|
|
20
26
|
code;
|
|
21
27
|
suggestion;
|
|
22
28
|
endpoint;
|
|
29
|
+
traceId;
|
|
23
30
|
constructor(type, message, options) {
|
|
24
31
|
super(message, options?.cause ? { cause: options.cause } : void 0);
|
|
25
32
|
this.name = type;
|
|
@@ -27,6 +34,7 @@ var OkxMcpError = class extends Error {
|
|
|
27
34
|
this.code = options?.code;
|
|
28
35
|
this.suggestion = options?.suggestion;
|
|
29
36
|
this.endpoint = options?.endpoint;
|
|
37
|
+
this.traceId = options?.traceId;
|
|
30
38
|
}
|
|
31
39
|
};
|
|
32
40
|
var ConfigError = class extends OkxMcpError {
|
|
@@ -35,13 +43,13 @@ var ConfigError = class extends OkxMcpError {
|
|
|
35
43
|
}
|
|
36
44
|
};
|
|
37
45
|
var RateLimitError = class extends OkxMcpError {
|
|
38
|
-
constructor(message, suggestion, endpoint) {
|
|
39
|
-
super("RateLimitError", message, { suggestion, endpoint });
|
|
46
|
+
constructor(message, suggestion, endpoint, traceId) {
|
|
47
|
+
super("RateLimitError", message, { suggestion, endpoint, traceId });
|
|
40
48
|
}
|
|
41
49
|
};
|
|
42
50
|
var AuthenticationError = class extends OkxMcpError {
|
|
43
|
-
constructor(message, suggestion, endpoint) {
|
|
44
|
-
super("AuthenticationError", message, { suggestion, endpoint });
|
|
51
|
+
constructor(message, suggestion, endpoint, traceId) {
|
|
52
|
+
super("AuthenticationError", message, { suggestion, endpoint, traceId });
|
|
45
53
|
}
|
|
46
54
|
};
|
|
47
55
|
var OkxApiError = class extends OkxMcpError {
|
|
@@ -67,6 +75,7 @@ function toToolErrorPayload(error, fallbackEndpoint) {
|
|
|
67
75
|
message: error.message,
|
|
68
76
|
suggestion: error.suggestion,
|
|
69
77
|
endpoint: error.endpoint ?? fallbackEndpoint,
|
|
78
|
+
traceId: error.traceId,
|
|
70
79
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
71
80
|
};
|
|
72
81
|
}
|
|
@@ -150,6 +159,9 @@ var RateLimiter = class {
|
|
|
150
159
|
function isDefined(value) {
|
|
151
160
|
return value !== void 0 && value !== null;
|
|
152
161
|
}
|
|
162
|
+
function extractTraceId(headers) {
|
|
163
|
+
return headers.get("x-trace-id") ?? headers.get("x-request-id") ?? headers.get("traceid") ?? void 0;
|
|
164
|
+
}
|
|
153
165
|
function stringifyQueryValue(value) {
|
|
154
166
|
if (Array.isArray(value)) {
|
|
155
167
|
return value.map((item) => String(item)).join(",");
|
|
@@ -176,28 +188,28 @@ var OkxRestClient = class {
|
|
|
176
188
|
constructor(config) {
|
|
177
189
|
this.config = config;
|
|
178
190
|
}
|
|
179
|
-
async publicGet(
|
|
191
|
+
async publicGet(path3, query, rateLimit) {
|
|
180
192
|
return this.request({
|
|
181
193
|
method: "GET",
|
|
182
|
-
path,
|
|
194
|
+
path: path3,
|
|
183
195
|
auth: "public",
|
|
184
196
|
query,
|
|
185
197
|
rateLimit
|
|
186
198
|
});
|
|
187
199
|
}
|
|
188
|
-
async privateGet(
|
|
200
|
+
async privateGet(path3, query, rateLimit) {
|
|
189
201
|
return this.request({
|
|
190
202
|
method: "GET",
|
|
191
|
-
path,
|
|
203
|
+
path: path3,
|
|
192
204
|
auth: "private",
|
|
193
205
|
query,
|
|
194
206
|
rateLimit
|
|
195
207
|
});
|
|
196
208
|
}
|
|
197
|
-
async privatePost(
|
|
209
|
+
async privatePost(path3, body, rateLimit) {
|
|
198
210
|
return this.request({
|
|
199
211
|
method: "POST",
|
|
200
|
-
path,
|
|
212
|
+
path: path3,
|
|
201
213
|
auth: "private",
|
|
202
214
|
body,
|
|
203
215
|
rateLimit
|
|
@@ -216,6 +228,9 @@ var OkxRestClient = class {
|
|
|
216
228
|
"Content-Type": "application/json",
|
|
217
229
|
Accept: "application/json"
|
|
218
230
|
});
|
|
231
|
+
if (this.config.userAgent) {
|
|
232
|
+
headers.set("User-Agent", this.config.userAgent);
|
|
233
|
+
}
|
|
219
234
|
if (config.auth === "private") {
|
|
220
235
|
if (!this.config.hasAuth) {
|
|
221
236
|
throw new ConfigError(
|
|
@@ -255,6 +270,7 @@ var OkxRestClient = class {
|
|
|
255
270
|
);
|
|
256
271
|
}
|
|
257
272
|
const rawText = await response.text();
|
|
273
|
+
const traceId = extractTraceId(response.headers);
|
|
258
274
|
let parsed;
|
|
259
275
|
try {
|
|
260
276
|
parsed = rawText ? JSON.parse(rawText) : {};
|
|
@@ -266,7 +282,8 @@ var OkxRestClient = class {
|
|
|
266
282
|
{
|
|
267
283
|
code: String(response.status),
|
|
268
284
|
endpoint: `${config.method} ${config.path}`,
|
|
269
|
-
suggestion: "Verify endpoint path and request parameters."
|
|
285
|
+
suggestion: "Verify endpoint path and request parameters.",
|
|
286
|
+
traceId
|
|
270
287
|
}
|
|
271
288
|
);
|
|
272
289
|
}
|
|
@@ -282,7 +299,8 @@ var OkxRestClient = class {
|
|
|
282
299
|
{
|
|
283
300
|
code: String(response.status),
|
|
284
301
|
endpoint: `${config.method} ${config.path}`,
|
|
285
|
-
suggestion: "Retry later or verify endpoint parameters."
|
|
302
|
+
suggestion: "Retry later or verify endpoint parameters.",
|
|
303
|
+
traceId
|
|
286
304
|
}
|
|
287
305
|
);
|
|
288
306
|
}
|
|
@@ -293,12 +311,14 @@ var OkxRestClient = class {
|
|
|
293
311
|
throw new AuthenticationError(
|
|
294
312
|
message,
|
|
295
313
|
"Check API key, secret, passphrase and permissions.",
|
|
296
|
-
`${config.method} ${config.path}
|
|
314
|
+
`${config.method} ${config.path}`,
|
|
315
|
+
traceId
|
|
297
316
|
);
|
|
298
317
|
}
|
|
299
318
|
throw new OkxApiError(message, {
|
|
300
319
|
code: responseCode,
|
|
301
|
-
endpoint: `${config.method} ${config.path}
|
|
320
|
+
endpoint: `${config.method} ${config.path}`,
|
|
321
|
+
traceId
|
|
302
322
|
});
|
|
303
323
|
}
|
|
304
324
|
return {
|
|
@@ -309,21 +329,24 @@ var OkxRestClient = class {
|
|
|
309
329
|
};
|
|
310
330
|
}
|
|
311
331
|
};
|
|
332
|
+
var DEFAULT_LOG_DIR = path.join(os.homedir(), ".okx", "logs");
|
|
312
333
|
var OKX_API_BASE_URL = "https://www.okx.com";
|
|
313
334
|
var MODULES = [
|
|
314
335
|
"market",
|
|
315
336
|
"spot",
|
|
316
337
|
"swap",
|
|
317
|
-
"
|
|
338
|
+
"futures",
|
|
339
|
+
"account",
|
|
340
|
+
"bot"
|
|
318
341
|
];
|
|
319
342
|
var DEFAULT_MODULES = ["spot", "swap", "account"];
|
|
320
343
|
function configFilePath() {
|
|
321
344
|
return join(homedir(), ".okx", "config.toml");
|
|
322
345
|
}
|
|
323
346
|
function readTomlProfile(profileName) {
|
|
324
|
-
const
|
|
325
|
-
if (!existsSync(
|
|
326
|
-
const raw = readFileSync(
|
|
347
|
+
const path3 = configFilePath();
|
|
348
|
+
if (!existsSync(path3)) return {};
|
|
349
|
+
const raw = readFileSync(path3, "utf-8");
|
|
327
350
|
const config = parse(raw);
|
|
328
351
|
const name = profileName ?? config.default_profile ?? "default";
|
|
329
352
|
return config.profiles?.[name] ?? {};
|
|
@@ -390,9 +413,77 @@ function loadConfig(cli) {
|
|
|
390
413
|
timeoutMs: Math.floor(rawTimeout),
|
|
391
414
|
modules: parseModuleList(cli.modules),
|
|
392
415
|
readOnly: cli.readOnly,
|
|
393
|
-
demo
|
|
416
|
+
demo,
|
|
417
|
+
userAgent: cli.userAgent
|
|
394
418
|
};
|
|
395
419
|
}
|
|
420
|
+
var CACHE_FILE = join2(homedir2(), ".okx", "update-check.json");
|
|
421
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
422
|
+
function readCache() {
|
|
423
|
+
try {
|
|
424
|
+
if (existsSync2(CACHE_FILE)) {
|
|
425
|
+
return JSON.parse(readFileSync2(CACHE_FILE, "utf-8"));
|
|
426
|
+
}
|
|
427
|
+
} catch {
|
|
428
|
+
}
|
|
429
|
+
return {};
|
|
430
|
+
}
|
|
431
|
+
function writeCache(cache) {
|
|
432
|
+
try {
|
|
433
|
+
mkdirSync(join2(homedir2(), ".okx"), { recursive: true });
|
|
434
|
+
writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function isNewerVersion(current, latest) {
|
|
439
|
+
const parse22 = (v) => v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10));
|
|
440
|
+
const [cMaj, cMin, cPat] = parse22(current);
|
|
441
|
+
const [lMaj, lMin, lPat] = parse22(latest);
|
|
442
|
+
if (lMaj !== cMaj) return lMaj > cMaj;
|
|
443
|
+
if (lMin !== cMin) return lMin > cMin;
|
|
444
|
+
return lPat > cPat;
|
|
445
|
+
}
|
|
446
|
+
async function fetchLatestVersion(packageName) {
|
|
447
|
+
try {
|
|
448
|
+
const controller = new AbortController();
|
|
449
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
450
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`, {
|
|
451
|
+
signal: controller.signal,
|
|
452
|
+
headers: { accept: "application/json" }
|
|
453
|
+
});
|
|
454
|
+
clearTimeout(timeout);
|
|
455
|
+
if (!res.ok) return null;
|
|
456
|
+
const data = await res.json();
|
|
457
|
+
return data.version ?? null;
|
|
458
|
+
} catch {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function refreshCacheInBackground(packageName) {
|
|
463
|
+
fetchLatestVersion(packageName).then((latest) => {
|
|
464
|
+
if (!latest) return;
|
|
465
|
+
const cache = readCache();
|
|
466
|
+
cache[packageName] = { latestVersion: latest, checkedAt: Date.now() };
|
|
467
|
+
writeCache(cache);
|
|
468
|
+
}).catch(() => {
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
function checkForUpdates(packageName, currentVersion) {
|
|
472
|
+
const cache = readCache();
|
|
473
|
+
const entry = cache[packageName];
|
|
474
|
+
if (entry && isNewerVersion(currentVersion, entry.latestVersion)) {
|
|
475
|
+
process.stderr.write(
|
|
476
|
+
`
|
|
477
|
+
Update available for ${packageName}: ${currentVersion} \u2192 ${entry.latestVersion}
|
|
478
|
+
Run: npm install -g ${packageName}
|
|
479
|
+
|
|
480
|
+
`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
if (!entry || Date.now() - entry.checkedAt > CHECK_INTERVAL_MS) {
|
|
484
|
+
refreshCacheInBackground(packageName);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
396
487
|
|
|
397
488
|
// src/config/loader.ts
|
|
398
489
|
function loadProfileConfig(opts) {
|
|
@@ -400,7 +491,8 @@ function loadProfileConfig(opts) {
|
|
|
400
491
|
profile: opts.profile,
|
|
401
492
|
modules: opts.modules,
|
|
402
493
|
readOnly: opts.readOnly ?? false,
|
|
403
|
-
demo: opts.demo ?? false
|
|
494
|
+
demo: opts.demo ?? false,
|
|
495
|
+
userAgent: opts.userAgent
|
|
404
496
|
});
|
|
405
497
|
}
|
|
406
498
|
|
|
@@ -784,6 +876,27 @@ async function cmdSwapAlgoAmend(client, opts) {
|
|
|
784
876
|
`
|
|
785
877
|
);
|
|
786
878
|
}
|
|
879
|
+
async function cmdSwapAlgoTrailPlace(client, opts) {
|
|
880
|
+
const body = {
|
|
881
|
+
instId: opts.instId,
|
|
882
|
+
tdMode: opts.tdMode,
|
|
883
|
+
side: opts.side,
|
|
884
|
+
ordType: "move_order_stop",
|
|
885
|
+
sz: opts.sz
|
|
886
|
+
};
|
|
887
|
+
if (opts.posSide) body["posSide"] = opts.posSide;
|
|
888
|
+
if (opts.callbackRatio) body["callbackRatio"] = opts.callbackRatio;
|
|
889
|
+
if (opts.callbackSpread) body["callbackSpread"] = opts.callbackSpread;
|
|
890
|
+
if (opts.activePx) body["activePx"] = opts.activePx;
|
|
891
|
+
if (opts.reduceOnly !== void 0) body["reduceOnly"] = String(opts.reduceOnly);
|
|
892
|
+
const res = await client.privatePost("/api/v5/trade/order-algo", body);
|
|
893
|
+
if (opts.json) return printJson(res.data);
|
|
894
|
+
const order = res.data[0];
|
|
895
|
+
process.stdout.write(
|
|
896
|
+
`Trailing stop placed: ${order?.["algoId"]} (${order?.["sCode"] === "0" ? "OK" : order?.["sMsg"]})
|
|
897
|
+
`
|
|
898
|
+
);
|
|
899
|
+
}
|
|
787
900
|
async function cmdSwapAlgoCancel(client, instId, algoId, json) {
|
|
788
901
|
const res = await client.privatePost("/api/v5/trade/cancel-algos", [
|
|
789
902
|
{ algoId, instId }
|
|
@@ -804,13 +917,15 @@ async function cmdSwapAlgoOrders(client, opts) {
|
|
|
804
917
|
const res = await client.privateGet(endpoint, { ...baseParams, ordType: opts.ordType });
|
|
805
918
|
orders = res.data ?? [];
|
|
806
919
|
} else {
|
|
807
|
-
const [r1, r2] = await Promise.all([
|
|
920
|
+
const [r1, r2, r3] = await Promise.all([
|
|
808
921
|
client.privateGet(endpoint, { ...baseParams, ordType: "conditional" }),
|
|
809
|
-
client.privateGet(endpoint, { ...baseParams, ordType: "oco" })
|
|
922
|
+
client.privateGet(endpoint, { ...baseParams, ordType: "oco" }),
|
|
923
|
+
client.privateGet(endpoint, { ...baseParams, ordType: "move_order_stop" })
|
|
810
924
|
]);
|
|
811
925
|
orders = [
|
|
812
926
|
...r1.data ?? [],
|
|
813
|
-
...r2.data ?? []
|
|
927
|
+
...r2.data ?? [],
|
|
928
|
+
...r3.data ?? []
|
|
814
929
|
];
|
|
815
930
|
}
|
|
816
931
|
if (opts.json) return printJson(orders);
|
|
@@ -846,28 +961,33 @@ async function cmdSwapSetLeverage(client, opts) {
|
|
|
846
961
|
}
|
|
847
962
|
|
|
848
963
|
// src/config/toml.ts
|
|
849
|
-
import { writeFileSync, mkdirSync, existsSync as
|
|
964
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
|
|
850
965
|
import { stringify } from "smol-toml";
|
|
851
966
|
function configDir() {
|
|
852
967
|
return configFilePath().replace(/\/config\.toml$/, "");
|
|
853
968
|
}
|
|
854
969
|
function writeCliConfig(config) {
|
|
855
970
|
const dir = configDir();
|
|
856
|
-
if (!
|
|
857
|
-
|
|
971
|
+
if (!existsSync3(dir)) {
|
|
972
|
+
mkdirSync2(dir, { recursive: true });
|
|
858
973
|
}
|
|
859
|
-
|
|
974
|
+
writeFileSync2(configFilePath(), stringify(config), "utf-8");
|
|
860
975
|
}
|
|
861
976
|
|
|
862
977
|
// src/commands/config.ts
|
|
863
|
-
import { existsSync as
|
|
864
|
-
import { parse as parse2 } from "smol-toml";
|
|
978
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
979
|
+
import { parse as parse2, stringify as stringify2 } from "smol-toml";
|
|
980
|
+
import { createInterface } from "readline";
|
|
981
|
+
import { spawnSync } from "child_process";
|
|
865
982
|
function readFullConfig() {
|
|
866
|
-
const
|
|
867
|
-
if (!
|
|
868
|
-
const raw =
|
|
983
|
+
const path3 = configFilePath();
|
|
984
|
+
if (!existsSync4(path3)) return { profiles: {} };
|
|
985
|
+
const raw = readFileSync3(path3, "utf-8");
|
|
869
986
|
return parse2(raw);
|
|
870
987
|
}
|
|
988
|
+
function prompt(rl, question) {
|
|
989
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
990
|
+
}
|
|
871
991
|
function cmdConfigShow(json) {
|
|
872
992
|
const config = readFullConfig();
|
|
873
993
|
if (json) return printJson(config);
|
|
@@ -901,8 +1021,188 @@ function cmdConfigSet(key, value) {
|
|
|
901
1021
|
process.exitCode = 1;
|
|
902
1022
|
}
|
|
903
1023
|
}
|
|
1024
|
+
async function cmdConfigInit() {
|
|
1025
|
+
const apiUrl = "https://www.okx.com/account/my-api";
|
|
1026
|
+
process.stdout.write("OKX Trade CLI \u2014 \u914D\u7F6E\u5411\u5BFC\n\n");
|
|
1027
|
+
process.stdout.write(`\u8BF7\u524D\u5F80 ${apiUrl} \u521B\u5EFA API Key\uFF08\u9700\u8981 trade \u6743\u9650\uFF09
|
|
1028
|
+
|
|
1029
|
+
`);
|
|
1030
|
+
try {
|
|
1031
|
+
const opener = process.platform === "darwin" ? "open" : "xdg-open";
|
|
1032
|
+
spawnSync(opener, [apiUrl], { stdio: "ignore" });
|
|
1033
|
+
} catch {
|
|
1034
|
+
}
|
|
1035
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1036
|
+
try {
|
|
1037
|
+
const profileNameRaw = await prompt(rl, "Profile \u540D\u79F0 (\u9ED8\u8BA4: default): ");
|
|
1038
|
+
const profileName = profileNameRaw.trim() || "default";
|
|
1039
|
+
const apiKey = (await prompt(rl, "API Key: ")).trim();
|
|
1040
|
+
if (!apiKey) {
|
|
1041
|
+
process.stderr.write("\u9519\u8BEF: API Key \u4E0D\u80FD\u4E3A\u7A7A\n");
|
|
1042
|
+
process.exitCode = 1;
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const secretKey = (await prompt(rl, "Secret Key: ")).trim();
|
|
1046
|
+
if (!secretKey) {
|
|
1047
|
+
process.stderr.write("\u9519\u8BEF: Secret Key \u4E0D\u80FD\u4E3A\u7A7A\n");
|
|
1048
|
+
process.exitCode = 1;
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const passphrase = (await prompt(rl, "Passphrase: ")).trim();
|
|
1052
|
+
if (!passphrase) {
|
|
1053
|
+
process.stderr.write("\u9519\u8BEF: Passphrase \u4E0D\u80FD\u4E3A\u7A7A\n");
|
|
1054
|
+
process.exitCode = 1;
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
const demoRaw = (await prompt(rl, "\u4F7F\u7528\u6A21\u62DF\u76D8\uFF1F(Y/n) ")).trim().toLowerCase();
|
|
1058
|
+
const demo = demoRaw !== "n";
|
|
1059
|
+
if (demo) {
|
|
1060
|
+
process.stdout.write("\u5DF2\u9009\u62E9\u6A21\u62DF\u76D8\u6A21\u5F0F\uFF0C\u53EF\u968F\u65F6\u901A\u8FC7 okx config set \u5207\u6362\u4E3A\u5B9E\u76D8\u3002\n");
|
|
1061
|
+
}
|
|
1062
|
+
const config = readFullConfig();
|
|
1063
|
+
config.profiles[profileName] = { api_key: apiKey, secret_key: secretKey, passphrase, demo };
|
|
1064
|
+
const configPath = configFilePath();
|
|
1065
|
+
try {
|
|
1066
|
+
writeCliConfig(config);
|
|
1067
|
+
process.stdout.write(`
|
|
1068
|
+
\u914D\u7F6E\u5DF2\u4FDD\u5B58\u5230 ${configPath}
|
|
1069
|
+
`);
|
|
1070
|
+
process.stdout.write(`\u4F7F\u7528\u65B9\u5F0F: okx --profile ${profileName} account balance
|
|
1071
|
+
`);
|
|
1072
|
+
if (!config.default_profile) {
|
|
1073
|
+
process.stdout.write(`\u63D0\u793A: \u8FD0\u884C okx config set default_profile ${profileName} \u53EF\u5C06\u5176\u8BBE\u4E3A\u9ED8\u8BA4
|
|
1074
|
+
`);
|
|
1075
|
+
}
|
|
1076
|
+
} catch (err) {
|
|
1077
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1078
|
+
const isPermission = err instanceof Error && "code" in err && (err.code === "EACCES" || err.code === "EPERM");
|
|
1079
|
+
process.stderr.write(`\u5199\u5165\u914D\u7F6E\u6587\u4EF6\u5931\u8D25: ${message}
|
|
1080
|
+
`);
|
|
1081
|
+
if (isPermission) {
|
|
1082
|
+
process.stderr.write(`\u6743\u9650\u4E0D\u8DB3\uFF0C\u8BF7\u68C0\u67E5 ${configPath} \u53CA\u5176\u7236\u76EE\u5F55\u7684\u8BFB\u5199\u6743\u9650\u3002
|
|
1083
|
+
`);
|
|
1084
|
+
}
|
|
1085
|
+
process.stderr.write("\u8BF7\u624B\u52A8\u5C06\u4EE5\u4E0B\u5185\u5BB9\u5199\u5165 " + configPath + ":\n\n");
|
|
1086
|
+
process.stdout.write(stringify2(config) + "\n");
|
|
1087
|
+
process.exitCode = 1;
|
|
1088
|
+
}
|
|
1089
|
+
} finally {
|
|
1090
|
+
rl.close();
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/commands/client-setup.ts
|
|
1095
|
+
import * as fs from "fs";
|
|
1096
|
+
import * as path2 from "path";
|
|
1097
|
+
import * as os2 from "os";
|
|
1098
|
+
import * as readline from "readline";
|
|
1099
|
+
var CLIENTS = [
|
|
1100
|
+
{
|
|
1101
|
+
name: "Claude Desktop",
|
|
1102
|
+
configPath: path2.join(os2.homedir(), "Library/Application Support/Claude/claude_desktop_config.json"),
|
|
1103
|
+
mcpKey: "mcpServers"
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
name: "Cursor",
|
|
1107
|
+
configPath: path2.join(os2.homedir(), ".cursor/mcp.json"),
|
|
1108
|
+
mcpKey: "mcpServers"
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
name: "Windsurf",
|
|
1112
|
+
configPath: path2.join(os2.homedir(), ".codeium/windsurf/mcp_config.json"),
|
|
1113
|
+
mcpKey: "mcpServers"
|
|
1114
|
+
}
|
|
1115
|
+
];
|
|
1116
|
+
var MCP_ENTRY = {
|
|
1117
|
+
command: "okx-trade-mcp",
|
|
1118
|
+
args: ["--modules", "all"]
|
|
1119
|
+
};
|
|
1120
|
+
var MCP_SERVER_NAME = "okx-trade-mcp";
|
|
1121
|
+
function prompt2(rl, question) {
|
|
1122
|
+
return new Promise((resolve) => {
|
|
1123
|
+
rl.question(question, (answer) => {
|
|
1124
|
+
resolve(answer);
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
async function cmdSetupClients() {
|
|
1129
|
+
const detected = CLIENTS.filter((c) => fs.existsSync(c.configPath));
|
|
1130
|
+
if (detected.length === 0) {
|
|
1131
|
+
process.stdout.write(
|
|
1132
|
+
"No supported IDE/client installations detected.\nChecked:\n" + CLIENTS.map((c) => ` - ${c.name}: ${c.configPath}`).join("\n") + "\n"
|
|
1133
|
+
);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
process.stdout.write(`Detected ${detected.length} client(s):
|
|
1137
|
+
`);
|
|
1138
|
+
for (const c of detected) {
|
|
1139
|
+
process.stdout.write(` - ${c.name}
|
|
1140
|
+
`);
|
|
1141
|
+
}
|
|
1142
|
+
process.stdout.write("\n");
|
|
1143
|
+
const rl = readline.createInterface({
|
|
1144
|
+
input: process.stdin,
|
|
1145
|
+
output: process.stdout
|
|
1146
|
+
});
|
|
1147
|
+
try {
|
|
1148
|
+
for (const client of detected) {
|
|
1149
|
+
const answer = await prompt2(rl, `Configure ${client.name}? (y/N) `);
|
|
1150
|
+
if (answer.trim().toLowerCase() !== "y") {
|
|
1151
|
+
process.stdout.write(` Skipped ${client.name}.
|
|
1152
|
+
`);
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
let data = { [client.mcpKey]: {} };
|
|
1156
|
+
if (fs.existsSync(client.configPath)) {
|
|
1157
|
+
const raw = fs.readFileSync(client.configPath, "utf-8");
|
|
1158
|
+
try {
|
|
1159
|
+
data = JSON.parse(raw);
|
|
1160
|
+
} catch {
|
|
1161
|
+
process.stderr.write(
|
|
1162
|
+
` Error: Failed to parse JSON for ${client.name} at ${client.configPath}. Skipping.
|
|
1163
|
+
`
|
|
1164
|
+
);
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
if (typeof data[client.mcpKey] !== "object" || data[client.mcpKey] === null) {
|
|
1169
|
+
data[client.mcpKey] = {};
|
|
1170
|
+
}
|
|
1171
|
+
const servers = data[client.mcpKey];
|
|
1172
|
+
if (Object.prototype.hasOwnProperty.call(servers, MCP_SERVER_NAME)) {
|
|
1173
|
+
process.stdout.write(` Already configured in ${client.name}. Skipping.
|
|
1174
|
+
`);
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
servers[MCP_SERVER_NAME] = MCP_ENTRY;
|
|
1178
|
+
const jsonOutput = JSON.stringify(data, null, 2);
|
|
1179
|
+
try {
|
|
1180
|
+
fs.writeFileSync(client.configPath, jsonOutput, "utf-8");
|
|
1181
|
+
process.stdout.write(` Configured ${client.name} successfully.
|
|
1182
|
+
`);
|
|
1183
|
+
} catch (err) {
|
|
1184
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1185
|
+
process.stderr.write(
|
|
1186
|
+
` Error: Failed to write config for ${client.name}: ${reason}
|
|
1187
|
+
Add the following to "${client.configPath}" manually:
|
|
1188
|
+
|
|
1189
|
+
"${MCP_SERVER_NAME}": ${JSON.stringify(MCP_ENTRY, null, 2).split("\n").join("\n ")}
|
|
1190
|
+
|
|
1191
|
+
`
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
} finally {
|
|
1196
|
+
rl.close();
|
|
1197
|
+
}
|
|
1198
|
+
process.stdout.write(
|
|
1199
|
+
"\nDone. Please restart any configured IDE/client for the changes to take effect.\n"
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
904
1202
|
|
|
905
1203
|
// src/index.ts
|
|
1204
|
+
var _require = createRequire(import.meta.url);
|
|
1205
|
+
var CLI_VERSION = _require("../package.json").version;
|
|
906
1206
|
function printHelp() {
|
|
907
1207
|
process.stdout.write(`
|
|
908
1208
|
Usage: okx [--profile <name>] [--json] <command> [args]
|
|
@@ -939,6 +1239,8 @@ Commands:
|
|
|
939
1239
|
swap cancel <instId> --ordId <id>
|
|
940
1240
|
swap leverage --instId <id> --lever <n> --mgnMode <cross|isolated> [--posSide <side>]
|
|
941
1241
|
swap algo orders [--instId <id>] [--history] [--ordType <conditional|oco>]
|
|
1242
|
+
swap algo trail --instId <id> --side <buy|sell> --sz <n> --callbackRatio <ratio>
|
|
1243
|
+
[--activePx <price>] [--posSide <net|long|short>] [--tdMode <cross|isolated>] [--reduceOnly]
|
|
942
1244
|
swap algo place --instId <id> --side <buy|sell> --sz <n> [--ordType <conditional|oco>]
|
|
943
1245
|
[--tpTriggerPx <price>] [--tpOrdPx <price|-1>]
|
|
944
1246
|
[--slTriggerPx <price>] [--slOrdPx <price|-1>]
|
|
@@ -948,11 +1250,14 @@ Commands:
|
|
|
948
1250
|
[--newSlTriggerPx <price>] [--newSlOrdPx <price|-1>]
|
|
949
1251
|
swap algo cancel --instId <id> --algoId <id>
|
|
950
1252
|
|
|
1253
|
+
config init
|
|
951
1254
|
config show
|
|
952
1255
|
config set <key> <value>
|
|
1256
|
+
config setup-clients
|
|
953
1257
|
`);
|
|
954
1258
|
}
|
|
955
1259
|
async function main() {
|
|
1260
|
+
checkForUpdates("okx-trade-cli", CLI_VERSION);
|
|
956
1261
|
const { values, positionals } = parseArgs({
|
|
957
1262
|
args: process.argv.slice(2),
|
|
958
1263
|
options: {
|
|
@@ -988,7 +1293,11 @@ async function main() {
|
|
|
988
1293
|
newTpTriggerPx: { type: "string" },
|
|
989
1294
|
newTpOrdPx: { type: "string" },
|
|
990
1295
|
newSlTriggerPx: { type: "string" },
|
|
991
|
-
newSlOrdPx: { type: "string" }
|
|
1296
|
+
newSlOrdPx: { type: "string" },
|
|
1297
|
+
// trailing stop
|
|
1298
|
+
callbackRatio: { type: "string" },
|
|
1299
|
+
callbackSpread: { type: "string" },
|
|
1300
|
+
activePx: { type: "string" }
|
|
992
1301
|
},
|
|
993
1302
|
allowPositionals: true
|
|
994
1303
|
});
|
|
@@ -999,14 +1308,16 @@ async function main() {
|
|
|
999
1308
|
const [module, action, ...rest] = positionals;
|
|
1000
1309
|
const json = values.json ?? false;
|
|
1001
1310
|
if (module === "config") {
|
|
1311
|
+
if (action === "init") return cmdConfigInit();
|
|
1002
1312
|
if (action === "show") return cmdConfigShow(json);
|
|
1003
1313
|
if (action === "set") return cmdConfigSet(rest[0], rest[1]);
|
|
1314
|
+
if (action === "setup-clients") return cmdSetupClients();
|
|
1004
1315
|
process.stderr.write(`Unknown config command: ${action}
|
|
1005
1316
|
`);
|
|
1006
1317
|
process.exitCode = 1;
|
|
1007
1318
|
return;
|
|
1008
1319
|
}
|
|
1009
|
-
const config = loadProfileConfig({ profile: values.profile });
|
|
1320
|
+
const config = loadProfileConfig({ profile: values.profile, userAgent: `okx-trade-cli/${CLI_VERSION}` });
|
|
1010
1321
|
const client = new OkxRestClient(config);
|
|
1011
1322
|
if (module === "market") {
|
|
1012
1323
|
if (action === "ticker") return cmdMarketTicker(client, rest[0], json);
|
|
@@ -1111,6 +1422,19 @@ async function main() {
|
|
|
1111
1422
|
});
|
|
1112
1423
|
if (action === "algo") {
|
|
1113
1424
|
const subAction = rest[0];
|
|
1425
|
+
if (subAction === "trail")
|
|
1426
|
+
return cmdSwapAlgoTrailPlace(client, {
|
|
1427
|
+
instId: values.instId,
|
|
1428
|
+
side: values.side,
|
|
1429
|
+
sz: values.sz,
|
|
1430
|
+
callbackRatio: values.callbackRatio,
|
|
1431
|
+
callbackSpread: values.callbackSpread,
|
|
1432
|
+
activePx: values.activePx,
|
|
1433
|
+
posSide: values.posSide,
|
|
1434
|
+
tdMode: values.tdMode ?? "cross",
|
|
1435
|
+
reduceOnly: values.reduceOnly,
|
|
1436
|
+
json
|
|
1437
|
+
});
|
|
1114
1438
|
if (subAction === "place")
|
|
1115
1439
|
return cmdSwapAlgoPlace(client, {
|
|
1116
1440
|
instId: values.instId,
|
|
@@ -1155,8 +1479,12 @@ async function main() {
|
|
|
1155
1479
|
main().catch((error) => {
|
|
1156
1480
|
const payload = toToolErrorPayload(error);
|
|
1157
1481
|
process.stderr.write(`Error: ${payload.message}
|
|
1482
|
+
`);
|
|
1483
|
+
if (payload.traceId) process.stderr.write(`TraceId: ${payload.traceId}
|
|
1158
1484
|
`);
|
|
1159
1485
|
if (payload.suggestion) process.stderr.write(`Hint: ${payload.suggestion}
|
|
1486
|
+
`);
|
|
1487
|
+
process.stderr.write(`Version: okx-trade-cli@${CLI_VERSION}
|
|
1160
1488
|
`);
|
|
1161
1489
|
process.exitCode = 1;
|
|
1162
1490
|
});
|