@okx_ai/okx-trade-cli 1.2.9-beta.1 → 1.2.9-beta.2
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 +151 -204
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/postinstall.js +1 -8
package/dist/index.js
CHANGED
|
@@ -4,30 +4,25 @@
|
|
|
4
4
|
import { createRequire as createRequire3 } from "module";
|
|
5
5
|
|
|
6
6
|
// ../core/dist/index.js
|
|
7
|
-
import {
|
|
8
|
-
import { unlink } from "fs/promises";
|
|
9
|
-
import { execFile } from "child_process";
|
|
10
|
-
import { access, constants } from "fs/promises";
|
|
11
|
-
import { homedir } from "os";
|
|
12
|
-
import { join } from "path";
|
|
7
|
+
import { ProxyAgent } from "undici";
|
|
13
8
|
import { createHmac } from "crypto";
|
|
14
9
|
import fs from "fs";
|
|
15
10
|
import path from "path";
|
|
16
11
|
import os from "os";
|
|
17
12
|
import { writeFileSync, renameSync, unlinkSync, mkdirSync } from "fs";
|
|
18
|
-
import { join
|
|
13
|
+
import { join, resolve, basename, sep } from "path";
|
|
19
14
|
import { randomUUID } from "crypto";
|
|
20
15
|
import yauzl from "yauzl";
|
|
21
16
|
import { createWriteStream, mkdirSync as mkdirSync2 } from "fs";
|
|
22
17
|
import { resolve as resolve2, dirname } from "path";
|
|
23
18
|
import { readFileSync, existsSync } from "fs";
|
|
24
|
-
import { join as
|
|
19
|
+
import { join as join2 } from "path";
|
|
25
20
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
|
|
26
|
-
import { join as
|
|
27
|
-
import { homedir
|
|
21
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
22
|
+
import { homedir } from "os";
|
|
28
23
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync3 } from "fs";
|
|
29
|
-
import { join as
|
|
30
|
-
import { homedir as
|
|
24
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
25
|
+
import { homedir as homedir2 } from "os";
|
|
31
26
|
|
|
32
27
|
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
|
|
33
28
|
function getLineColFromPtr(string, ptr) {
|
|
@@ -857,66 +852,12 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
|
|
|
857
852
|
|
|
858
853
|
// ../core/dist/index.js
|
|
859
854
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync4 } from "fs";
|
|
860
|
-
import { join as
|
|
861
|
-
import { homedir as
|
|
855
|
+
import { join as join5 } from "path";
|
|
856
|
+
import { homedir as homedir3 } from "os";
|
|
862
857
|
import * as fs3 from "fs";
|
|
863
858
|
import * as path3 from "path";
|
|
864
859
|
import * as os3 from "os";
|
|
865
860
|
import { execFileSync } from "child_process";
|
|
866
|
-
var EXEC_TIMEOUT_MS = 15e3;
|
|
867
|
-
var DOH_BIN_DIR = join(homedir(), ".okx", "bin");
|
|
868
|
-
var DOH_CACHE_FILE = join(homedir(), ".okx", ".doh-cache.json");
|
|
869
|
-
function getDohBinaryPath() {
|
|
870
|
-
if (process.env.OKX_DOH_BINARY_PATH) {
|
|
871
|
-
return process.env.OKX_DOH_BINARY_PATH;
|
|
872
|
-
}
|
|
873
|
-
const ext = process.platform === "win32" ? ".exe" : "";
|
|
874
|
-
return join(DOH_BIN_DIR, `okx-doh-resolver${ext}`);
|
|
875
|
-
}
|
|
876
|
-
function getDohCachePath() {
|
|
877
|
-
return DOH_CACHE_FILE;
|
|
878
|
-
}
|
|
879
|
-
async function dohBinaryExists() {
|
|
880
|
-
try {
|
|
881
|
-
const flag = process.platform === "win32" ? constants.F_OK : constants.X_OK;
|
|
882
|
-
await access(getDohBinaryPath(), flag);
|
|
883
|
-
return true;
|
|
884
|
-
} catch {
|
|
885
|
-
return false;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
function execDohBinary(domain) {
|
|
889
|
-
const binPath = getDohBinaryPath();
|
|
890
|
-
return new Promise((resolve3) => {
|
|
891
|
-
execFile(
|
|
892
|
-
binPath,
|
|
893
|
-
["--domain", domain],
|
|
894
|
-
{ timeout: EXEC_TIMEOUT_MS, encoding: "utf-8" },
|
|
895
|
-
(error, stdout) => {
|
|
896
|
-
if (error) {
|
|
897
|
-
resolve3(null);
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
try {
|
|
901
|
-
const result = JSON.parse(stdout);
|
|
902
|
-
if (result.code === 0 && result.data) {
|
|
903
|
-
resolve3(result.data);
|
|
904
|
-
} else {
|
|
905
|
-
resolve3(null);
|
|
906
|
-
}
|
|
907
|
-
} catch {
|
|
908
|
-
resolve3(null);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
);
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
async function resolveDoh(domain) {
|
|
915
|
-
if (!await dohBinaryExists()) {
|
|
916
|
-
return null;
|
|
917
|
-
}
|
|
918
|
-
return execDohBinary(domain);
|
|
919
|
-
}
|
|
920
861
|
function getNow() {
|
|
921
862
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
922
863
|
}
|
|
@@ -1135,80 +1076,12 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1135
1076
|
config;
|
|
1136
1077
|
rateLimiter;
|
|
1137
1078
|
dispatcher;
|
|
1138
|
-
|
|
1139
|
-
dohResolverFn;
|
|
1140
|
-
dohResolved = false;
|
|
1141
|
-
dohNode = null;
|
|
1142
|
-
dohAgent = null;
|
|
1143
|
-
dohBaseUrl = null;
|
|
1144
|
-
/**
|
|
1145
|
-
* @param config - OKX API client configuration
|
|
1146
|
-
* @param options - Optional overrides (e.g. custom DoH resolver for testing).
|
|
1147
|
-
* Pass `{ resolveDoh: null }` to disable DoH entirely.
|
|
1148
|
-
*/
|
|
1149
|
-
constructor(config, options) {
|
|
1079
|
+
constructor(config) {
|
|
1150
1080
|
this.config = config;
|
|
1151
1081
|
this.rateLimiter = new RateLimiter(3e4, config.verbose);
|
|
1152
1082
|
if (config.proxyUrl) {
|
|
1153
1083
|
this.dispatcher = new ProxyAgent(config.proxyUrl);
|
|
1154
1084
|
}
|
|
1155
|
-
this.dohResolverFn = options?.resolveDoh !== void 0 ? options.resolveDoh : resolveDoh;
|
|
1156
|
-
}
|
|
1157
|
-
/**
|
|
1158
|
-
* Lazily resolve the DoH proxy node on the first request.
|
|
1159
|
-
* Skipped entirely when the user has configured proxy_url or DoH is disabled.
|
|
1160
|
-
* On failure, silently falls back to direct connection.
|
|
1161
|
-
*/
|
|
1162
|
-
async ensureDoh() {
|
|
1163
|
-
if (this.dohResolved || this.dispatcher || !this.dohResolverFn) return;
|
|
1164
|
-
this.dohResolved = true;
|
|
1165
|
-
try {
|
|
1166
|
-
const { hostname, protocol } = new URL(this.config.baseUrl);
|
|
1167
|
-
const node = await this.dohResolverFn(hostname);
|
|
1168
|
-
if (!node) return;
|
|
1169
|
-
if (node.ip === hostname) {
|
|
1170
|
-
if (this.config.verbose) {
|
|
1171
|
-
vlog(`DoH: resolved ip matches hostname (${hostname}), using direct connection`);
|
|
1172
|
-
}
|
|
1173
|
-
return;
|
|
1174
|
-
}
|
|
1175
|
-
this.dohNode = node;
|
|
1176
|
-
this.dohBaseUrl = `${protocol}//${node.host}`;
|
|
1177
|
-
this.dohAgent = new Agent({
|
|
1178
|
-
connect: {
|
|
1179
|
-
lookup: (_hostname, options, callback) => {
|
|
1180
|
-
if (options?.all) {
|
|
1181
|
-
callback(null, [{ address: node.ip, family: 4 }]);
|
|
1182
|
-
} else {
|
|
1183
|
-
callback(null, node.ip, 4);
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
});
|
|
1188
|
-
if (this.config.verbose) {
|
|
1189
|
-
vlog(`DoH proxy active: ${hostname} \u2192 ${node.host} (${node.ip}), ttl=${node.ttl}s`);
|
|
1190
|
-
}
|
|
1191
|
-
} catch (err) {
|
|
1192
|
-
if (this.config.verbose) {
|
|
1193
|
-
const cause = err instanceof Error ? err.message : String(err);
|
|
1194
|
-
vlog(`DoH resolution failed, falling back to direct: ${cause}`);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
/**
|
|
1199
|
-
* Invalidate DoH state and delete the binary's cache file so the next
|
|
1200
|
-
* resolution performs a fresh lookup. Called on network-level failures
|
|
1201
|
-
* when a DoH proxy node was in use.
|
|
1202
|
-
*/
|
|
1203
|
-
async invalidateDoh() {
|
|
1204
|
-
this.dohNode = null;
|
|
1205
|
-
this.dohAgent = null;
|
|
1206
|
-
this.dohBaseUrl = null;
|
|
1207
|
-
this.dohResolved = false;
|
|
1208
|
-
try {
|
|
1209
|
-
await unlink(getDohCachePath());
|
|
1210
|
-
} catch {
|
|
1211
|
-
}
|
|
1212
1085
|
}
|
|
1213
1086
|
logRequest(method, url, auth) {
|
|
1214
1087
|
if (!this.config.verbose) return;
|
|
@@ -1379,18 +1252,13 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1379
1252
|
* Security: validates Content-Type and enforces maxBytes limit.
|
|
1380
1253
|
*/
|
|
1381
1254
|
async privatePostBinary(path42, body, opts) {
|
|
1382
|
-
await this.ensureDoh();
|
|
1383
1255
|
const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
|
|
1384
1256
|
const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
|
|
1385
1257
|
const bodyJson = body ? JSON.stringify(body) : "";
|
|
1386
1258
|
const endpoint = `POST ${path42}`;
|
|
1387
|
-
|
|
1388
|
-
this.logRequest("POST", `${baseUrl}${path42}`, "private");
|
|
1259
|
+
this.logRequest("POST", `${this.config.baseUrl}${path42}`, "private");
|
|
1389
1260
|
const reqConfig = { method: "POST", path: path42, auth: "private" };
|
|
1390
1261
|
const headers = this.buildHeaders(reqConfig, path42, bodyJson, getNow());
|
|
1391
|
-
if (this.dohNode) {
|
|
1392
|
-
headers.set("User-Agent", "OKX/2.7.2");
|
|
1393
|
-
}
|
|
1394
1262
|
const t0 = Date.now();
|
|
1395
1263
|
const response = await this.fetchBinary(path42, endpoint, headers, bodyJson, t0);
|
|
1396
1264
|
const elapsed = Date.now() - t0;
|
|
@@ -1420,19 +1288,14 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1420
1288
|
/** Execute fetch for binary endpoint, wrapping network errors. */
|
|
1421
1289
|
async fetchBinary(path42, endpoint, headers, bodyJson, t0) {
|
|
1422
1290
|
try {
|
|
1423
|
-
const baseUrl = this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
|
|
1424
1291
|
const fetchOptions = {
|
|
1425
1292
|
method: "POST",
|
|
1426
1293
|
headers,
|
|
1427
1294
|
body: bodyJson || void 0,
|
|
1428
1295
|
signal: AbortSignal.timeout(this.config.timeoutMs)
|
|
1429
1296
|
};
|
|
1430
|
-
if (this.dispatcher)
|
|
1431
|
-
|
|
1432
|
-
} else if (this.dohAgent) {
|
|
1433
|
-
fetchOptions.dispatcher = this.dohAgent;
|
|
1434
|
-
}
|
|
1435
|
-
return await fetch(`${baseUrl}${path42}`, fetchOptions);
|
|
1297
|
+
if (this.dispatcher) fetchOptions.dispatcher = this.dispatcher;
|
|
1298
|
+
return await fetch(`${this.config.baseUrl}${path42}`, fetchOptions);
|
|
1436
1299
|
} catch (error) {
|
|
1437
1300
|
if (this.config.verbose) {
|
|
1438
1301
|
vlog(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1463,11 +1326,9 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1463
1326
|
// JSON request
|
|
1464
1327
|
// ---------------------------------------------------------------------------
|
|
1465
1328
|
async request(reqConfig) {
|
|
1466
|
-
await this.ensureDoh();
|
|
1467
1329
|
const queryString = buildQueryString(reqConfig.query);
|
|
1468
1330
|
const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
|
|
1469
|
-
const
|
|
1470
|
-
const url = `${baseUrl}${requestPath}`;
|
|
1331
|
+
const url = `${this.config.baseUrl}${requestPath}`;
|
|
1471
1332
|
const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
|
|
1472
1333
|
const timestamp = getNow();
|
|
1473
1334
|
this.logRequest(reqConfig.method, url, reqConfig.auth);
|
|
@@ -1475,9 +1336,6 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1475
1336
|
await this.rateLimiter.consume(reqConfig.rateLimit);
|
|
1476
1337
|
}
|
|
1477
1338
|
const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
|
|
1478
|
-
if (this.dohNode) {
|
|
1479
|
-
headers.set("User-Agent", "OKX/2.7.2");
|
|
1480
|
-
}
|
|
1481
1339
|
const t0 = Date.now();
|
|
1482
1340
|
let response;
|
|
1483
1341
|
try {
|
|
@@ -1489,18 +1347,9 @@ var OkxRestClient = class _OkxRestClient {
|
|
|
1489
1347
|
};
|
|
1490
1348
|
if (this.dispatcher) {
|
|
1491
1349
|
fetchOptions.dispatcher = this.dispatcher;
|
|
1492
|
-
} else if (this.dohAgent) {
|
|
1493
|
-
fetchOptions.dispatcher = this.dohAgent;
|
|
1494
1350
|
}
|
|
1495
1351
|
response = await fetch(url, fetchOptions);
|
|
1496
1352
|
} catch (error) {
|
|
1497
|
-
if (this.dohNode) {
|
|
1498
|
-
if (this.config.verbose) {
|
|
1499
|
-
vlog(`DoH request failed, invalidating cache and retrying: ${error instanceof Error ? error.message : String(error)}`);
|
|
1500
|
-
}
|
|
1501
|
-
await this.invalidateDoh();
|
|
1502
|
-
return this.request(reqConfig);
|
|
1503
|
-
}
|
|
1504
1353
|
if (this.config.verbose) {
|
|
1505
1354
|
const elapsed2 = Date.now() - t0;
|
|
1506
1355
|
const cause = error instanceof Error ? error.message : String(error);
|
|
@@ -2342,6 +2191,59 @@ function registerAccountTools() {
|
|
|
2342
2191
|
}
|
|
2343
2192
|
];
|
|
2344
2193
|
}
|
|
2194
|
+
async function resolveQuoteCcySz(instId, sz, tgtCcy, instType, client) {
|
|
2195
|
+
if (tgtCcy !== "quote_ccy") {
|
|
2196
|
+
return { sz, tgtCcy, conversionNote: void 0 };
|
|
2197
|
+
}
|
|
2198
|
+
const [instrumentsRes, tickerRes] = await Promise.all([
|
|
2199
|
+
client.publicGet("/api/v5/public/instruments", {
|
|
2200
|
+
instType,
|
|
2201
|
+
instId
|
|
2202
|
+
}),
|
|
2203
|
+
client.publicGet("/api/v5/market/ticker", { instId })
|
|
2204
|
+
]);
|
|
2205
|
+
const instruments = Array.isArray(instrumentsRes.data) ? instrumentsRes.data : [];
|
|
2206
|
+
if (instruments.length === 0) {
|
|
2207
|
+
throw new Error(
|
|
2208
|
+
`Failed to fetch instrument info for ${instId}: empty instrument list. Cannot determine ctVal for quote_ccy conversion.`
|
|
2209
|
+
);
|
|
2210
|
+
}
|
|
2211
|
+
const ctValStr = String(instruments[0].ctVal ?? "");
|
|
2212
|
+
const ctVal = parseFloat(ctValStr);
|
|
2213
|
+
if (!isFinite(ctVal) || ctVal <= 0) {
|
|
2214
|
+
throw new Error(
|
|
2215
|
+
`Invalid ctVal "${ctValStr}" for ${instId}. ctVal must be a positive number for quote_ccy conversion.`
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2218
|
+
const tickers = Array.isArray(tickerRes.data) ? tickerRes.data : [];
|
|
2219
|
+
if (tickers.length === 0) {
|
|
2220
|
+
throw new Error(
|
|
2221
|
+
`Failed to fetch ticker price for ${instId}: empty ticker response. Cannot determine last price for quote_ccy conversion.`
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
const lastStr = String(tickers[0].last ?? "");
|
|
2225
|
+
const lastPx = parseFloat(lastStr);
|
|
2226
|
+
if (!isFinite(lastPx) || lastPx <= 0) {
|
|
2227
|
+
throw new Error(
|
|
2228
|
+
`Invalid last price "${lastStr}" for ${instId}. Last price must be a positive number for quote_ccy conversion.`
|
|
2229
|
+
);
|
|
2230
|
+
}
|
|
2231
|
+
const usdtAmount = parseFloat(sz);
|
|
2232
|
+
const contractValue = ctVal * lastPx;
|
|
2233
|
+
const contracts = Math.floor(usdtAmount / contractValue);
|
|
2234
|
+
if (contracts <= 0) {
|
|
2235
|
+
const minUsdt = contractValue.toFixed(2);
|
|
2236
|
+
throw new Error(
|
|
2237
|
+
`sz=${sz} USDT is too small to buy even 1 contract of ${instId}. Minimum amount required is at least ${minUsdt} USDT (ctVal=${ctValStr}, lastPx=${lastStr}, 1 contract = ${minUsdt} USDT).`
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
const conversionNote = `Converting ${sz} USDT \u2192 ${contracts} contracts (ctVal=${ctValStr}, lastPx=${lastStr}, formula: floor(${sz} / (${ctValStr} \xD7 ${lastStr})) = ${contracts})`;
|
|
2241
|
+
return {
|
|
2242
|
+
sz: String(contracts),
|
|
2243
|
+
tgtCcy: void 0,
|
|
2244
|
+
conversionNote
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2345
2247
|
function registerAlgoTradeTools() {
|
|
2346
2248
|
return [
|
|
2347
2249
|
{
|
|
@@ -2437,6 +2339,13 @@ function registerAlgoTradeTools() {
|
|
|
2437
2339
|
handler: async (rawArgs, context) => {
|
|
2438
2340
|
const args = asRecord(rawArgs);
|
|
2439
2341
|
const reduceOnly = args.reduceOnly;
|
|
2342
|
+
const resolved = await resolveQuoteCcySz(
|
|
2343
|
+
requireString(args, "instId"),
|
|
2344
|
+
requireString(args, "sz"),
|
|
2345
|
+
readString(args, "tgtCcy"),
|
|
2346
|
+
"SWAP",
|
|
2347
|
+
context.client
|
|
2348
|
+
);
|
|
2440
2349
|
const response = await context.client.privatePost(
|
|
2441
2350
|
"/api/v5/trade/order-algo",
|
|
2442
2351
|
compactObject({
|
|
@@ -2445,8 +2354,8 @@ function registerAlgoTradeTools() {
|
|
|
2445
2354
|
side: requireString(args, "side"),
|
|
2446
2355
|
posSide: readString(args, "posSide"),
|
|
2447
2356
|
ordType: requireString(args, "ordType"),
|
|
2448
|
-
sz:
|
|
2449
|
-
tgtCcy:
|
|
2357
|
+
sz: resolved.sz,
|
|
2358
|
+
tgtCcy: resolved.tgtCcy,
|
|
2450
2359
|
tpTriggerPx: readString(args, "tpTriggerPx"),
|
|
2451
2360
|
tpOrdPx: readString(args, "tpOrdPx"),
|
|
2452
2361
|
tpTriggerPxType: readString(args, "tpTriggerPxType"),
|
|
@@ -2462,7 +2371,11 @@ function registerAlgoTradeTools() {
|
|
|
2462
2371
|
}),
|
|
2463
2372
|
privateRateLimit("swap_place_algo_order", 20)
|
|
2464
2373
|
);
|
|
2465
|
-
|
|
2374
|
+
const result = normalizeResponse(response);
|
|
2375
|
+
if (resolved.conversionNote) {
|
|
2376
|
+
result._conversion = resolved.conversionNote;
|
|
2377
|
+
}
|
|
2378
|
+
return result;
|
|
2466
2379
|
}
|
|
2467
2380
|
},
|
|
2468
2381
|
{
|
|
@@ -2770,6 +2683,13 @@ function registerFuturesAlgoTools() {
|
|
|
2770
2683
|
handler: async (rawArgs, context) => {
|
|
2771
2684
|
const args = asRecord(rawArgs);
|
|
2772
2685
|
const reduceOnly = args.reduceOnly;
|
|
2686
|
+
const resolved = await resolveQuoteCcySz(
|
|
2687
|
+
requireString(args, "instId"),
|
|
2688
|
+
requireString(args, "sz"),
|
|
2689
|
+
readString(args, "tgtCcy"),
|
|
2690
|
+
"FUTURES",
|
|
2691
|
+
context.client
|
|
2692
|
+
);
|
|
2773
2693
|
const response = await context.client.privatePost(
|
|
2774
2694
|
"/api/v5/trade/order-algo",
|
|
2775
2695
|
compactObject({
|
|
@@ -2778,8 +2698,8 @@ function registerFuturesAlgoTools() {
|
|
|
2778
2698
|
side: requireString(args, "side"),
|
|
2779
2699
|
posSide: readString(args, "posSide"),
|
|
2780
2700
|
ordType: requireString(args, "ordType"),
|
|
2781
|
-
sz:
|
|
2782
|
-
tgtCcy:
|
|
2701
|
+
sz: resolved.sz,
|
|
2702
|
+
tgtCcy: resolved.tgtCcy,
|
|
2783
2703
|
tpTriggerPx: readString(args, "tpTriggerPx"),
|
|
2784
2704
|
tpOrdPx: readString(args, "tpOrdPx"),
|
|
2785
2705
|
tpTriggerPxType: readString(args, "tpTriggerPxType"),
|
|
@@ -2795,7 +2715,11 @@ function registerFuturesAlgoTools() {
|
|
|
2795
2715
|
}),
|
|
2796
2716
|
privateRateLimit("futures_place_algo_order", 20)
|
|
2797
2717
|
);
|
|
2798
|
-
|
|
2718
|
+
const result = normalizeResponse(response);
|
|
2719
|
+
if (resolved.conversionNote) {
|
|
2720
|
+
result._conversion = resolved.conversionNote;
|
|
2721
|
+
}
|
|
2722
|
+
return result;
|
|
2799
2723
|
}
|
|
2800
2724
|
},
|
|
2801
2725
|
{
|
|
@@ -3126,7 +3050,7 @@ function safeWriteFile(targetDir, fileName, data) {
|
|
|
3126
3050
|
throw new Error(`Invalid file name: "${fileName}"`);
|
|
3127
3051
|
}
|
|
3128
3052
|
const resolvedDir = resolve(targetDir);
|
|
3129
|
-
const filePath =
|
|
3053
|
+
const filePath = join(resolvedDir, safeName);
|
|
3130
3054
|
const resolvedPath = resolve(filePath);
|
|
3131
3055
|
if (!resolvedPath.startsWith(resolvedDir + sep)) {
|
|
3132
3056
|
throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
|
|
@@ -3242,7 +3166,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
|
|
|
3242
3166
|
});
|
|
3243
3167
|
}
|
|
3244
3168
|
function readMetaJson(contentDir) {
|
|
3245
|
-
const metaPath =
|
|
3169
|
+
const metaPath = join2(contentDir, "_meta.json");
|
|
3246
3170
|
if (!existsSync(metaPath)) {
|
|
3247
3171
|
throw new Error(`_meta.json not found in ${contentDir}. Invalid skill package.`);
|
|
3248
3172
|
}
|
|
@@ -3268,12 +3192,12 @@ function readMetaJson(contentDir) {
|
|
|
3268
3192
|
};
|
|
3269
3193
|
}
|
|
3270
3194
|
function validateSkillMdExists(contentDir) {
|
|
3271
|
-
const skillMdPath =
|
|
3195
|
+
const skillMdPath = join2(contentDir, "SKILL.md");
|
|
3272
3196
|
if (!existsSync(skillMdPath)) {
|
|
3273
3197
|
throw new Error(`SKILL.md not found in ${contentDir}. Invalid skill package.`);
|
|
3274
3198
|
}
|
|
3275
3199
|
}
|
|
3276
|
-
var DEFAULT_REGISTRY_PATH =
|
|
3200
|
+
var DEFAULT_REGISTRY_PATH = join3(homedir(), ".okx", "skills", "registry.json");
|
|
3277
3201
|
function readRegistry(registryPath = DEFAULT_REGISTRY_PATH) {
|
|
3278
3202
|
if (!existsSync2(registryPath)) {
|
|
3279
3203
|
return { version: 1, skills: {} };
|
|
@@ -3360,7 +3284,7 @@ function registerSkillsTools() {
|
|
|
3360
3284
|
{
|
|
3361
3285
|
name: "skills_download",
|
|
3362
3286
|
module: "skills",
|
|
3363
|
-
description: "Download a skill zip file from OKX Skills Marketplace to a local directory. Always call skills_search first to confirm the skill name exists. Downloads the latest approved version. NOTE:
|
|
3287
|
+
description: "Download a skill zip file from OKX Skills Marketplace to a local directory. Always call skills_search first to confirm the skill name exists. Downloads the latest approved version. NOTE: Downloads third-party developer content as a zip \u2014 does NOT install to agents. For full installation use CLI: okx skill add <name>. Use when the user wants to inspect or manually install a skill package.",
|
|
3364
3288
|
inputSchema: {
|
|
3365
3289
|
type: "object",
|
|
3366
3290
|
properties: {
|
|
@@ -4881,6 +4805,13 @@ function buildContractTradeTools(cfg) {
|
|
|
4881
4805
|
const args = asRecord(rawArgs);
|
|
4882
4806
|
const reduceOnly = args.reduceOnly;
|
|
4883
4807
|
const attachAlgoOrds = buildAttachAlgoOrds(args);
|
|
4808
|
+
const resolved = await resolveQuoteCcySz(
|
|
4809
|
+
requireString(args, "instId"),
|
|
4810
|
+
requireString(args, "sz"),
|
|
4811
|
+
readString(args, "tgtCcy"),
|
|
4812
|
+
defaultType,
|
|
4813
|
+
context.client
|
|
4814
|
+
);
|
|
4884
4815
|
const response = await context.client.privatePost(
|
|
4885
4816
|
"/api/v5/trade/order",
|
|
4886
4817
|
compactObject({
|
|
@@ -4889,8 +4820,8 @@ function buildContractTradeTools(cfg) {
|
|
|
4889
4820
|
side: requireString(args, "side"),
|
|
4890
4821
|
posSide: readString(args, "posSide"),
|
|
4891
4822
|
ordType: requireString(args, "ordType"),
|
|
4892
|
-
sz:
|
|
4893
|
-
tgtCcy:
|
|
4823
|
+
sz: resolved.sz,
|
|
4824
|
+
tgtCcy: resolved.tgtCcy,
|
|
4894
4825
|
px: readString(args, "px"),
|
|
4895
4826
|
reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
|
|
4896
4827
|
clOrdId: readString(args, "clOrdId"),
|
|
@@ -4899,7 +4830,11 @@ function buildContractTradeTools(cfg) {
|
|
|
4899
4830
|
}),
|
|
4900
4831
|
privateRateLimit(n("place_order"), 60)
|
|
4901
4832
|
);
|
|
4902
|
-
|
|
4833
|
+
const result = normalizeResponse(response);
|
|
4834
|
+
if (resolved.conversionNote) {
|
|
4835
|
+
result._conversion = resolved.conversionNote;
|
|
4836
|
+
}
|
|
4837
|
+
return result;
|
|
4903
4838
|
}
|
|
4904
4839
|
},
|
|
4905
4840
|
// ── cancel_order ─────────────────────────────────────────────────────────
|
|
@@ -5974,7 +5909,7 @@ function registerOptionAlgoTools() {
|
|
|
5974
5909
|
tgtCcy: {
|
|
5975
5910
|
type: "string",
|
|
5976
5911
|
enum: ["base_ccy", "quote_ccy"],
|
|
5977
|
-
description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT (
|
|
5912
|
+
description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT (auto-converted to contracts)"
|
|
5978
5913
|
},
|
|
5979
5914
|
reduceOnly: {
|
|
5980
5915
|
type: "boolean",
|
|
@@ -5990,6 +5925,13 @@ function registerOptionAlgoTools() {
|
|
|
5990
5925
|
handler: async (rawArgs, context) => {
|
|
5991
5926
|
const args = asRecord(rawArgs);
|
|
5992
5927
|
const reduceOnly = readBoolean(args, "reduceOnly");
|
|
5928
|
+
const resolved = await resolveQuoteCcySz(
|
|
5929
|
+
requireString(args, "instId"),
|
|
5930
|
+
requireString(args, "sz"),
|
|
5931
|
+
readString(args, "tgtCcy"),
|
|
5932
|
+
"OPTION",
|
|
5933
|
+
context.client
|
|
5934
|
+
);
|
|
5993
5935
|
const response = await context.client.privatePost(
|
|
5994
5936
|
"/api/v5/trade/order-algo",
|
|
5995
5937
|
compactObject({
|
|
@@ -5997,8 +5939,8 @@ function registerOptionAlgoTools() {
|
|
|
5997
5939
|
tdMode: requireString(args, "tdMode"),
|
|
5998
5940
|
side: requireString(args, "side"),
|
|
5999
5941
|
ordType: requireString(args, "ordType"),
|
|
6000
|
-
sz:
|
|
6001
|
-
tgtCcy:
|
|
5942
|
+
sz: resolved.sz,
|
|
5943
|
+
tgtCcy: resolved.tgtCcy,
|
|
6002
5944
|
tpTriggerPx: readString(args, "tpTriggerPx"),
|
|
6003
5945
|
tpOrdPx: readString(args, "tpOrdPx"),
|
|
6004
5946
|
tpTriggerPxType: readString(args, "tpTriggerPxType"),
|
|
@@ -7405,7 +7347,7 @@ function createToolRunner(client, config) {
|
|
|
7405
7347
|
};
|
|
7406
7348
|
}
|
|
7407
7349
|
function configFilePath() {
|
|
7408
|
-
return
|
|
7350
|
+
return join4(homedir2(), ".okx", "config.toml");
|
|
7409
7351
|
}
|
|
7410
7352
|
function readFullConfig() {
|
|
7411
7353
|
const path42 = configFilePath();
|
|
@@ -7556,7 +7498,7 @@ function loadConfig(cli) {
|
|
|
7556
7498
|
verbose: cli.verbose ?? false
|
|
7557
7499
|
};
|
|
7558
7500
|
}
|
|
7559
|
-
var CACHE_FILE =
|
|
7501
|
+
var CACHE_FILE = join5(homedir3(), ".okx", "update-check.json");
|
|
7560
7502
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
7561
7503
|
function readCache() {
|
|
7562
7504
|
try {
|
|
@@ -7569,7 +7511,7 @@ function readCache() {
|
|
|
7569
7511
|
}
|
|
7570
7512
|
function writeCache(cache) {
|
|
7571
7513
|
try {
|
|
7572
|
-
mkdirSync5(
|
|
7514
|
+
mkdirSync5(join5(homedir3(), ".okx"), { recursive: true });
|
|
7573
7515
|
writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
7574
7516
|
} catch {
|
|
7575
7517
|
}
|
|
@@ -8399,7 +8341,7 @@ async function cmdDiagnoseMcp(options = {}) {
|
|
|
8399
8341
|
|
|
8400
8342
|
// src/commands/diagnose.ts
|
|
8401
8343
|
var CLI_VERSION = readCliVersion();
|
|
8402
|
-
var GIT_HASH = true ? "
|
|
8344
|
+
var GIT_HASH = true ? "5f833a2" : "dev";
|
|
8403
8345
|
function maskKey2(key) {
|
|
8404
8346
|
if (!key) return "(not set)";
|
|
8405
8347
|
if (key.length <= 8) return "****";
|
|
@@ -8697,12 +8639,12 @@ async function runCliChecks(config, profile, outputPath) {
|
|
|
8697
8639
|
// src/commands/upgrade.ts
|
|
8698
8640
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
8699
8641
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
|
|
8700
|
-
import { dirname as dirname5, join as
|
|
8701
|
-
import { homedir as
|
|
8642
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
8643
|
+
import { homedir as homedir5 } from "os";
|
|
8702
8644
|
var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
|
|
8703
|
-
var CACHE_FILE2 =
|
|
8645
|
+
var CACHE_FILE2 = join7(homedir5(), ".okx", "last_check");
|
|
8704
8646
|
var THROTTLE_MS = 12 * 60 * 60 * 1e3;
|
|
8705
|
-
var NPM_BIN =
|
|
8647
|
+
var NPM_BIN = join7(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
|
|
8706
8648
|
function readLastCheck() {
|
|
8707
8649
|
try {
|
|
8708
8650
|
return parseInt(readFileSync6(CACHE_FILE2, "utf-8").trim(), 10) || 0;
|
|
@@ -8712,7 +8654,7 @@ function readLastCheck() {
|
|
|
8712
8654
|
}
|
|
8713
8655
|
function writeLastCheck() {
|
|
8714
8656
|
try {
|
|
8715
|
-
mkdirSync7(
|
|
8657
|
+
mkdirSync7(join7(homedir5(), ".okx"), { recursive: true });
|
|
8716
8658
|
writeFileSync6(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
|
|
8717
8659
|
} catch {
|
|
8718
8660
|
}
|
|
@@ -12411,16 +12353,17 @@ async function cmdDcdQuoteAndBuy(run, opts) {
|
|
|
12411
12353
|
}
|
|
12412
12354
|
|
|
12413
12355
|
// src/commands/skill.ts
|
|
12414
|
-
import { tmpdir, homedir as
|
|
12415
|
-
import { join as
|
|
12356
|
+
import { tmpdir, homedir as homedir7 } from "os";
|
|
12357
|
+
import { join as join9, dirname as dirname6 } from "path";
|
|
12416
12358
|
import { mkdirSync as mkdirSync8, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
|
|
12417
12359
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
12418
12360
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
12419
12361
|
function resolveNpx() {
|
|
12420
|
-
const sibling =
|
|
12362
|
+
const sibling = join9(dirname6(process.execPath), "npx");
|
|
12421
12363
|
if (existsSync7(sibling)) return sibling;
|
|
12422
12364
|
return "npx";
|
|
12423
12365
|
}
|
|
12366
|
+
var THIRD_PARTY_INSTALL_NOTICE = "Note: This skill was created by a third-party developer, not by OKX. Review SKILL.md before use.";
|
|
12424
12367
|
async function cmdSkillSearch(run, opts) {
|
|
12425
12368
|
const args = {};
|
|
12426
12369
|
if (opts.keyword) args.keyword = opts.keyword;
|
|
@@ -12470,13 +12413,13 @@ async function cmdSkillCategories(run, json) {
|
|
|
12470
12413
|
outputLine("");
|
|
12471
12414
|
}
|
|
12472
12415
|
async function cmdSkillAdd(name, config, json) {
|
|
12473
|
-
const tmpBase =
|
|
12416
|
+
const tmpBase = join9(tmpdir(), `okx-skill-${randomUUID2()}`);
|
|
12474
12417
|
mkdirSync8(tmpBase, { recursive: true });
|
|
12475
12418
|
try {
|
|
12476
12419
|
outputLine(`Downloading ${name}...`);
|
|
12477
12420
|
const client = new OkxRestClient(config);
|
|
12478
12421
|
const zipPath = await downloadSkillZip(client, name, tmpBase);
|
|
12479
|
-
const contentDir = await extractSkillZip(zipPath,
|
|
12422
|
+
const contentDir = await extractSkillZip(zipPath, join9(tmpBase, "content"));
|
|
12480
12423
|
const meta = readMetaJson(contentDir);
|
|
12481
12424
|
validateSkillMdExists(contentDir);
|
|
12482
12425
|
outputLine("Installing to detected agents...");
|
|
@@ -12486,7 +12429,7 @@ async function cmdSkillAdd(name, config, json) {
|
|
|
12486
12429
|
timeout: 6e4
|
|
12487
12430
|
});
|
|
12488
12431
|
} catch (e) {
|
|
12489
|
-
const savedZip =
|
|
12432
|
+
const savedZip = join9(process.cwd(), `${name}.zip`);
|
|
12490
12433
|
try {
|
|
12491
12434
|
copyFileSync2(zipPath, savedZip);
|
|
12492
12435
|
} catch {
|
|
@@ -12496,11 +12439,7 @@ async function cmdSkillAdd(name, config, json) {
|
|
|
12496
12439
|
throw e;
|
|
12497
12440
|
}
|
|
12498
12441
|
upsertSkillRecord(meta);
|
|
12499
|
-
|
|
12500
|
-
outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
|
|
12501
|
-
} else {
|
|
12502
|
-
outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
|
|
12503
|
-
}
|
|
12442
|
+
printSkillInstallResult(meta, json);
|
|
12504
12443
|
} finally {
|
|
12505
12444
|
rmSync(tmpBase, { recursive: true, force: true });
|
|
12506
12445
|
}
|
|
@@ -12529,7 +12468,7 @@ function cmdSkillRemove(name, json) {
|
|
|
12529
12468
|
timeout: 6e4
|
|
12530
12469
|
});
|
|
12531
12470
|
} catch {
|
|
12532
|
-
const agentsPath =
|
|
12471
|
+
const agentsPath = join9(homedir7(), ".agents", "skills", name);
|
|
12533
12472
|
try {
|
|
12534
12473
|
rmSync(agentsPath, { recursive: true, force: true });
|
|
12535
12474
|
} catch {
|
|
@@ -12593,11 +12532,19 @@ function cmdSkillList(json) {
|
|
|
12593
12532
|
outputLine("");
|
|
12594
12533
|
outputLine(`${skills.length} skills installed.`);
|
|
12595
12534
|
}
|
|
12535
|
+
function printSkillInstallResult(meta, json) {
|
|
12536
|
+
if (json) {
|
|
12537
|
+
outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
|
|
12538
|
+
} else {
|
|
12539
|
+
outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
|
|
12540
|
+
outputLine(` ${THIRD_PARTY_INSTALL_NOTICE}`);
|
|
12541
|
+
}
|
|
12542
|
+
}
|
|
12596
12543
|
|
|
12597
12544
|
// src/index.ts
|
|
12598
12545
|
var _require3 = createRequire3(import.meta.url);
|
|
12599
12546
|
var CLI_VERSION2 = _require3("../package.json").version;
|
|
12600
|
-
var GIT_HASH2 = true ? "
|
|
12547
|
+
var GIT_HASH2 = true ? "5f833a2" : "dev";
|
|
12601
12548
|
function handleConfigCommand(action, rest, json, lang, force) {
|
|
12602
12549
|
if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
|
|
12603
12550
|
if (action === "show") return cmdConfigShow(json);
|