@okx_ai/okx-trade-cli 1.2.8-beta.4 → 1.2.8-beta.6
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 +782 -67
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -9,9 +9,20 @@ import { createHmac } from "crypto";
|
|
|
9
9
|
import fs from "fs";
|
|
10
10
|
import path from "path";
|
|
11
11
|
import os from "os";
|
|
12
|
-
import {
|
|
13
|
-
import { join,
|
|
12
|
+
import { writeFileSync, renameSync, unlinkSync, mkdirSync } from "fs";
|
|
13
|
+
import { join, resolve, basename, sep } from "path";
|
|
14
|
+
import { randomUUID } from "crypto";
|
|
15
|
+
import yauzl from "yauzl";
|
|
16
|
+
import { createWriteStream, mkdirSync as mkdirSync2 } from "fs";
|
|
17
|
+
import { resolve as resolve2, dirname } from "path";
|
|
18
|
+
import { readFileSync, existsSync } from "fs";
|
|
19
|
+
import { join as join2 } from "path";
|
|
20
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
|
|
21
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
14
22
|
import { homedir } from "os";
|
|
23
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync3 } from "fs";
|
|
24
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
25
|
+
import { homedir as homedir2 } from "os";
|
|
15
26
|
|
|
16
27
|
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
|
|
17
28
|
function getLineColFromPtr(string, ptr) {
|
|
@@ -88,7 +99,7 @@ function skipVoid(str, ptr, banNewLines, banComments) {
|
|
|
88
99
|
ptr++;
|
|
89
100
|
return banComments || c !== "#" ? ptr : skipVoid(str, skipComment(str, ptr), banNewLines);
|
|
90
101
|
}
|
|
91
|
-
function skipUntil(str, ptr,
|
|
102
|
+
function skipUntil(str, ptr, sep2, end, banNewLines = false) {
|
|
92
103
|
if (!end) {
|
|
93
104
|
ptr = indexOfNewline(str, ptr);
|
|
94
105
|
return ptr < 0 ? str.length : ptr;
|
|
@@ -97,7 +108,7 @@ function skipUntil(str, ptr, sep, end, banNewLines = false) {
|
|
|
97
108
|
let c = str[i];
|
|
98
109
|
if (c === "#") {
|
|
99
110
|
i = indexOfNewline(str, i);
|
|
100
|
-
} else if (c ===
|
|
111
|
+
} else if (c === sep2) {
|
|
101
112
|
return i + 1;
|
|
102
113
|
} else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
|
|
103
114
|
return i;
|
|
@@ -840,9 +851,9 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
|
|
|
840
851
|
}
|
|
841
852
|
|
|
842
853
|
// ../core/dist/index.js
|
|
843
|
-
import { readFileSync as
|
|
844
|
-
import { join as
|
|
845
|
-
import { homedir as
|
|
854
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync4 } from "fs";
|
|
855
|
+
import { join as join5 } from "path";
|
|
856
|
+
import { homedir as homedir3 } from "os";
|
|
846
857
|
import * as fs3 from "fs";
|
|
847
858
|
import * as path3 from "path";
|
|
848
859
|
import * as os3 from "os";
|
|
@@ -927,8 +938,8 @@ function toToolErrorPayload(error, fallbackEndpoint) {
|
|
|
927
938
|
};
|
|
928
939
|
}
|
|
929
940
|
function sleep(ms) {
|
|
930
|
-
return new Promise((
|
|
931
|
-
setTimeout(
|
|
941
|
+
return new Promise((resolve3) => {
|
|
942
|
+
setTimeout(resolve3, ms);
|
|
932
943
|
});
|
|
933
944
|
}
|
|
934
945
|
var RateLimiter = class {
|
|
@@ -1061,7 +1072,7 @@ function vlog(message) {
|
|
|
1061
1072
|
process.stderr.write(`[verbose] ${message}
|
|
1062
1073
|
`);
|
|
1063
1074
|
}
|
|
1064
|
-
var OkxRestClient = class {
|
|
1075
|
+
var OkxRestClient = class _OkxRestClient {
|
|
1065
1076
|
config;
|
|
1066
1077
|
rateLimiter;
|
|
1067
1078
|
dispatcher;
|
|
@@ -1214,6 +1225,88 @@ var OkxRestClient = class {
|
|
|
1214
1225
|
raw: parsed
|
|
1215
1226
|
};
|
|
1216
1227
|
}
|
|
1228
|
+
// ---------------------------------------------------------------------------
|
|
1229
|
+
// Binary (non-JSON) download — reuses auth, proxy, rate-limit, verbose
|
|
1230
|
+
// ---------------------------------------------------------------------------
|
|
1231
|
+
static DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
|
|
1232
|
+
// 50 MB
|
|
1233
|
+
/**
|
|
1234
|
+
* Try to parse a text body as OKX JSON error and throw the appropriate error.
|
|
1235
|
+
* Re-throws OkxApiError / AuthenticationError / RateLimitError if matched.
|
|
1236
|
+
* Returns the parsed message string (or fallback) if no specific error was thrown.
|
|
1237
|
+
*/
|
|
1238
|
+
tryThrowJsonError(text, path42, traceId) {
|
|
1239
|
+
try {
|
|
1240
|
+
const parsed = JSON.parse(text);
|
|
1241
|
+
if (parsed.code && parsed.code !== "0") {
|
|
1242
|
+
this.throwOkxError(parsed.code, parsed.msg, { method: "POST", path: path42, auth: "private" }, traceId);
|
|
1243
|
+
}
|
|
1244
|
+
return parsed.msg ?? "";
|
|
1245
|
+
} catch (e) {
|
|
1246
|
+
if (e instanceof OkxApiError || e instanceof AuthenticationError || e instanceof RateLimitError) throw e;
|
|
1247
|
+
return "";
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Send a signed POST request and return the raw binary response.
|
|
1252
|
+
* Inherits all client capabilities: auth, proxy, rate-limit, verbose, user-agent.
|
|
1253
|
+
* Security: validates Content-Type and enforces maxBytes limit.
|
|
1254
|
+
*/
|
|
1255
|
+
async privatePostBinary(path42, body, opts) {
|
|
1256
|
+
const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
|
|
1257
|
+
const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
|
|
1258
|
+
const bodyJson = body ? JSON.stringify(body) : "";
|
|
1259
|
+
const endpoint = `POST ${path42}`;
|
|
1260
|
+
this.logRequest("POST", `${this.config.baseUrl}${path42}`, "private");
|
|
1261
|
+
const reqConfig = { method: "POST", path: path42, auth: "private" };
|
|
1262
|
+
const headers = this.buildHeaders(reqConfig, path42, bodyJson, getNow());
|
|
1263
|
+
const t0 = Date.now();
|
|
1264
|
+
const response = await this.fetchBinary(path42, endpoint, headers, bodyJson, t0);
|
|
1265
|
+
const elapsed = Date.now() - t0;
|
|
1266
|
+
const traceId = extractTraceId(response.headers);
|
|
1267
|
+
if (!response.ok) {
|
|
1268
|
+
const text = await response.text();
|
|
1269
|
+
this.logResponse(response.status, text.length, elapsed, traceId, String(response.status));
|
|
1270
|
+
const msg = this.tryThrowJsonError(text, path42, traceId) || `HTTP ${response.status}`;
|
|
1271
|
+
throw new OkxApiError(msg, { code: String(response.status), endpoint, traceId });
|
|
1272
|
+
}
|
|
1273
|
+
const ct = response.headers.get("content-type") ?? "";
|
|
1274
|
+
if (!ct.includes(expectedCT)) {
|
|
1275
|
+
const text = await response.text();
|
|
1276
|
+
this.logResponse(response.status, text.length, elapsed, traceId, "unexpected-ct");
|
|
1277
|
+
this.tryThrowJsonError(text, path42, traceId);
|
|
1278
|
+
throw new OkxApiError(`Expected binary response (${expectedCT}) but got: ${ct}`, { code: "UNEXPECTED_CONTENT_TYPE", endpoint, traceId });
|
|
1279
|
+
}
|
|
1280
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1281
|
+
if (buffer.length > maxBytes) {
|
|
1282
|
+
throw new OkxApiError(`Response size ${buffer.length} bytes exceeds limit of ${maxBytes} bytes.`, { code: "RESPONSE_TOO_LARGE", endpoint, traceId });
|
|
1283
|
+
}
|
|
1284
|
+
if (this.config.verbose) {
|
|
1285
|
+
vlog(`\u2190 ${response.status} | binary ${buffer.length}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
|
|
1286
|
+
}
|
|
1287
|
+
return { endpoint, requestTime: (/* @__PURE__ */ new Date()).toISOString(), data: buffer, contentType: ct, contentLength: buffer.length, traceId };
|
|
1288
|
+
}
|
|
1289
|
+
/** Execute fetch for binary endpoint, wrapping network errors. */
|
|
1290
|
+
async fetchBinary(path42, endpoint, headers, bodyJson, t0) {
|
|
1291
|
+
try {
|
|
1292
|
+
const fetchOptions = {
|
|
1293
|
+
method: "POST",
|
|
1294
|
+
headers,
|
|
1295
|
+
body: bodyJson || void 0,
|
|
1296
|
+
signal: AbortSignal.timeout(this.config.timeoutMs)
|
|
1297
|
+
};
|
|
1298
|
+
if (this.dispatcher) fetchOptions.dispatcher = this.dispatcher;
|
|
1299
|
+
return await fetch(`${this.config.baseUrl}${path42}`, fetchOptions);
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
if (this.config.verbose) {
|
|
1302
|
+
vlog(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
|
|
1303
|
+
}
|
|
1304
|
+
throw new NetworkError(`Failed to call OKX endpoint ${endpoint}.`, endpoint, error);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
// ---------------------------------------------------------------------------
|
|
1308
|
+
// Header building
|
|
1309
|
+
// ---------------------------------------------------------------------------
|
|
1217
1310
|
buildHeaders(reqConfig, requestPath, bodyJson, timestamp) {
|
|
1218
1311
|
const headers = new Headers({
|
|
1219
1312
|
"Content-Type": "application/json",
|
|
@@ -1235,6 +1328,9 @@ var OkxRestClient = class {
|
|
|
1235
1328
|
}
|
|
1236
1329
|
return headers;
|
|
1237
1330
|
}
|
|
1331
|
+
// ---------------------------------------------------------------------------
|
|
1332
|
+
// JSON request
|
|
1333
|
+
// ---------------------------------------------------------------------------
|
|
1238
1334
|
async request(reqConfig) {
|
|
1239
1335
|
const queryString = buildQueryString(reqConfig.query);
|
|
1240
1336
|
const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
|
|
@@ -1546,9 +1642,10 @@ var MODULES = [
|
|
|
1546
1642
|
"account",
|
|
1547
1643
|
"news",
|
|
1548
1644
|
...EARN_SUB_MODULE_IDS,
|
|
1549
|
-
...BOT_SUB_MODULE_IDS
|
|
1645
|
+
...BOT_SUB_MODULE_IDS,
|
|
1646
|
+
"skills"
|
|
1550
1647
|
];
|
|
1551
|
-
var DEFAULT_MODULES = ["spot", "swap", "option", "account", ...BOT_DEFAULT_SUB_MODULES];
|
|
1648
|
+
var DEFAULT_MODULES = ["spot", "swap", "option", "account", ...BOT_DEFAULT_SUB_MODULES, "skills"];
|
|
1552
1649
|
function registerAccountTools() {
|
|
1553
1650
|
return [
|
|
1554
1651
|
{
|
|
@@ -1889,7 +1986,7 @@ function registerAccountTools() {
|
|
|
1889
1986
|
{
|
|
1890
1987
|
name: "account_get_config",
|
|
1891
1988
|
module: "account",
|
|
1892
|
-
description: "Get account configuration: position mode (net vs hedge), account level, auto-loan settings, etc.",
|
|
1989
|
+
description: "Get account configuration: position mode (net vs hedge), account level, auto-loan settings, etc. Note: `settleCcy` is the current settlement currency for USDS-margined contracts. `settleCcyList` is the list of available settlement currencies to choose from. These fields only apply to USDS-margined contracts and can be ignored for standard USDT/coin-margined trading.",
|
|
1893
1990
|
isWrite: false,
|
|
1894
1991
|
inputSchema: {
|
|
1895
1992
|
type: "object",
|
|
@@ -2879,6 +2976,299 @@ function registerAuditTools() {
|
|
|
2879
2976
|
}
|
|
2880
2977
|
];
|
|
2881
2978
|
}
|
|
2979
|
+
function safeWriteFile(targetDir, fileName, data) {
|
|
2980
|
+
const safeName = basename(fileName);
|
|
2981
|
+
if (!safeName || safeName === "." || safeName === "..") {
|
|
2982
|
+
throw new Error(`Invalid file name: "${fileName}"`);
|
|
2983
|
+
}
|
|
2984
|
+
const resolvedDir = resolve(targetDir);
|
|
2985
|
+
const filePath = join(resolvedDir, safeName);
|
|
2986
|
+
const resolvedPath = resolve(filePath);
|
|
2987
|
+
if (!resolvedPath.startsWith(resolvedDir + sep)) {
|
|
2988
|
+
throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
|
|
2989
|
+
}
|
|
2990
|
+
mkdirSync(resolvedDir, { recursive: true });
|
|
2991
|
+
const tmpPath = `${resolvedPath}.${randomUUID()}.tmp`;
|
|
2992
|
+
try {
|
|
2993
|
+
writeFileSync(tmpPath, data);
|
|
2994
|
+
renameSync(tmpPath, resolvedPath);
|
|
2995
|
+
} catch (err) {
|
|
2996
|
+
try {
|
|
2997
|
+
unlinkSync(tmpPath);
|
|
2998
|
+
} catch {
|
|
2999
|
+
}
|
|
3000
|
+
throw err;
|
|
3001
|
+
}
|
|
3002
|
+
return resolvedPath;
|
|
3003
|
+
}
|
|
3004
|
+
function validateZipEntryPath(targetDir, entryName) {
|
|
3005
|
+
const resolvedDir = resolve(targetDir);
|
|
3006
|
+
const resolvedEntry = resolve(resolvedDir, entryName);
|
|
3007
|
+
if (!resolvedEntry.startsWith(resolvedDir + sep) && resolvedEntry !== resolvedDir) {
|
|
3008
|
+
throw new Error(`Zip path traversal detected: "${entryName}" resolves outside extraction directory`);
|
|
3009
|
+
}
|
|
3010
|
+
return resolvedEntry;
|
|
3011
|
+
}
|
|
3012
|
+
var MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024;
|
|
3013
|
+
async function downloadSkillZip(client, name, targetDir) {
|
|
3014
|
+
const result = await client.privatePostBinary(
|
|
3015
|
+
"/api/v5/skill/download",
|
|
3016
|
+
{ name },
|
|
3017
|
+
{ maxBytes: MAX_DOWNLOAD_BYTES }
|
|
3018
|
+
);
|
|
3019
|
+
const fileName = `${name}.zip`;
|
|
3020
|
+
const filePath = safeWriteFile(targetDir, fileName, result.data);
|
|
3021
|
+
return filePath;
|
|
3022
|
+
}
|
|
3023
|
+
var DEFAULT_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
|
|
3024
|
+
var DEFAULT_MAX_FILES = 1e3;
|
|
3025
|
+
var DEFAULT_MAX_COMPRESSION_RATIO = 100;
|
|
3026
|
+
function validateEntry(entry, targetDir, state, limits) {
|
|
3027
|
+
state.fileCount++;
|
|
3028
|
+
if (state.fileCount > limits.maxFiles) {
|
|
3029
|
+
throw new Error(
|
|
3030
|
+
`Zip contains more than ${limits.maxFiles} entries, exceeding limit of ${limits.maxFiles}. Possible zip bomb.`
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
3033
|
+
const resolvedPath = validateZipEntryPath(targetDir, entry.fileName);
|
|
3034
|
+
const externalAttrs = entry.externalFileAttributes;
|
|
3035
|
+
if (externalAttrs) {
|
|
3036
|
+
const unixMode = externalAttrs >>> 16 & 65535;
|
|
3037
|
+
if ((unixMode & 40960) === 40960) {
|
|
3038
|
+
throw new Error(`Zip entry "${entry.fileName}" is a symlink. Symlinks are not allowed for security.`);
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
if (entry.compressedSize > 0) {
|
|
3042
|
+
const ratio = entry.uncompressedSize / entry.compressedSize;
|
|
3043
|
+
if (ratio > limits.maxCompressionRatio) {
|
|
3044
|
+
throw new Error(
|
|
3045
|
+
`Zip entry "${entry.fileName}" has compression ratio ${ratio.toFixed(1)}, exceeding limit of ${limits.maxCompressionRatio}. Possible zip bomb.`
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
state.totalBytes += entry.uncompressedSize;
|
|
3050
|
+
if (state.totalBytes > limits.maxTotalBytes) {
|
|
3051
|
+
throw new Error(
|
|
3052
|
+
`Extracted size ${state.totalBytes} bytes exceeds limit of ${limits.maxTotalBytes} bytes. Possible zip bomb.`
|
|
3053
|
+
);
|
|
3054
|
+
}
|
|
3055
|
+
return resolvedPath;
|
|
3056
|
+
}
|
|
3057
|
+
async function extractSkillZip(zipPath, targetDir, limits) {
|
|
3058
|
+
const maxTotalBytes = limits?.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES;
|
|
3059
|
+
const maxFiles = limits?.maxFiles ?? DEFAULT_MAX_FILES;
|
|
3060
|
+
const maxCompressionRatio = limits?.maxCompressionRatio ?? DEFAULT_MAX_COMPRESSION_RATIO;
|
|
3061
|
+
const resolvedTarget = resolve2(targetDir);
|
|
3062
|
+
mkdirSync2(resolvedTarget, { recursive: true });
|
|
3063
|
+
return new Promise((resolvePromise, reject) => {
|
|
3064
|
+
yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
|
|
3065
|
+
if (err) return reject(err);
|
|
3066
|
+
const state = { fileCount: 0, totalBytes: 0 };
|
|
3067
|
+
zipfile.readEntry();
|
|
3068
|
+
zipfile.on("entry", (entry) => {
|
|
3069
|
+
if (entry.fileName.endsWith("/")) {
|
|
3070
|
+
zipfile.readEntry();
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
let resolvedPath;
|
|
3074
|
+
try {
|
|
3075
|
+
resolvedPath = validateEntry(entry, resolvedTarget, state, { maxFiles, maxTotalBytes, maxCompressionRatio });
|
|
3076
|
+
} catch (e) {
|
|
3077
|
+
zipfile.close();
|
|
3078
|
+
return reject(e);
|
|
3079
|
+
}
|
|
3080
|
+
zipfile.openReadStream(entry, (streamErr, readStream) => {
|
|
3081
|
+
if (streamErr) {
|
|
3082
|
+
zipfile.close();
|
|
3083
|
+
return reject(streamErr);
|
|
3084
|
+
}
|
|
3085
|
+
mkdirSync2(dirname(resolvedPath), { recursive: true });
|
|
3086
|
+
const writeStream = createWriteStream(resolvedPath);
|
|
3087
|
+
readStream.pipe(writeStream);
|
|
3088
|
+
writeStream.on("close", () => zipfile.readEntry());
|
|
3089
|
+
writeStream.on("error", (writeErr) => {
|
|
3090
|
+
zipfile.close();
|
|
3091
|
+
reject(writeErr);
|
|
3092
|
+
});
|
|
3093
|
+
});
|
|
3094
|
+
});
|
|
3095
|
+
zipfile.on("end", () => resolvePromise(resolvedTarget));
|
|
3096
|
+
zipfile.on("error", reject);
|
|
3097
|
+
});
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
function readMetaJson(contentDir) {
|
|
3101
|
+
const metaPath = join2(contentDir, "_meta.json");
|
|
3102
|
+
if (!existsSync(metaPath)) {
|
|
3103
|
+
throw new Error(`_meta.json not found in ${contentDir}. Invalid skill package.`);
|
|
3104
|
+
}
|
|
3105
|
+
const raw = readFileSync(metaPath, "utf-8");
|
|
3106
|
+
let parsed;
|
|
3107
|
+
try {
|
|
3108
|
+
parsed = JSON.parse(raw);
|
|
3109
|
+
} catch {
|
|
3110
|
+
throw new Error(`Failed to parse _meta.json: invalid JSON`);
|
|
3111
|
+
}
|
|
3112
|
+
const meta = parsed;
|
|
3113
|
+
if (typeof meta.name !== "string" || !meta.name) {
|
|
3114
|
+
throw new Error(`_meta.json: "name" field is required`);
|
|
3115
|
+
}
|
|
3116
|
+
if (typeof meta.version !== "string" || !meta.version) {
|
|
3117
|
+
throw new Error(`_meta.json: "version" field is required`);
|
|
3118
|
+
}
|
|
3119
|
+
return {
|
|
3120
|
+
name: String(meta.name),
|
|
3121
|
+
version: String(meta.version),
|
|
3122
|
+
title: typeof meta.title === "string" ? meta.title : "",
|
|
3123
|
+
description: typeof meta.description === "string" ? meta.description : ""
|
|
3124
|
+
};
|
|
3125
|
+
}
|
|
3126
|
+
function validateSkillMdExists(contentDir) {
|
|
3127
|
+
const skillMdPath = join2(contentDir, "SKILL.md");
|
|
3128
|
+
if (!existsSync(skillMdPath)) {
|
|
3129
|
+
throw new Error(`SKILL.md not found in ${contentDir}. Invalid skill package.`);
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
var DEFAULT_REGISTRY_PATH = join3(homedir(), ".okx", "skills", "registry.json");
|
|
3133
|
+
function readRegistry(registryPath = DEFAULT_REGISTRY_PATH) {
|
|
3134
|
+
if (!existsSync2(registryPath)) {
|
|
3135
|
+
return { version: 1, skills: {} };
|
|
3136
|
+
}
|
|
3137
|
+
try {
|
|
3138
|
+
const raw = readFileSync2(registryPath, "utf-8");
|
|
3139
|
+
return JSON.parse(raw);
|
|
3140
|
+
} catch {
|
|
3141
|
+
return { version: 1, skills: {} };
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
function writeRegistry(registry, registryPath = DEFAULT_REGISTRY_PATH) {
|
|
3145
|
+
mkdirSync3(dirname2(registryPath), { recursive: true });
|
|
3146
|
+
writeFileSync2(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
|
|
3147
|
+
}
|
|
3148
|
+
function upsertSkillRecord(meta, registryPath = DEFAULT_REGISTRY_PATH) {
|
|
3149
|
+
const registry = readRegistry(registryPath);
|
|
3150
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3151
|
+
const existing = registry.skills[meta.name];
|
|
3152
|
+
registry.skills[meta.name] = {
|
|
3153
|
+
name: meta.name,
|
|
3154
|
+
version: meta.version,
|
|
3155
|
+
description: meta.description,
|
|
3156
|
+
installedAt: existing?.installedAt ?? now,
|
|
3157
|
+
updatedAt: now,
|
|
3158
|
+
source: "marketplace"
|
|
3159
|
+
};
|
|
3160
|
+
writeRegistry(registry, registryPath);
|
|
3161
|
+
}
|
|
3162
|
+
function removeSkillRecord(name, registryPath = DEFAULT_REGISTRY_PATH) {
|
|
3163
|
+
const registry = readRegistry(registryPath);
|
|
3164
|
+
if (!(name in registry.skills)) return false;
|
|
3165
|
+
delete registry.skills[name];
|
|
3166
|
+
writeRegistry(registry, registryPath);
|
|
3167
|
+
return true;
|
|
3168
|
+
}
|
|
3169
|
+
function getSkillRecord(name, registryPath = DEFAULT_REGISTRY_PATH) {
|
|
3170
|
+
const registry = readRegistry(registryPath);
|
|
3171
|
+
return registry.skills[name];
|
|
3172
|
+
}
|
|
3173
|
+
function registerSkillsTools() {
|
|
3174
|
+
return [
|
|
3175
|
+
{
|
|
3176
|
+
name: "skills_get_categories",
|
|
3177
|
+
module: "skills",
|
|
3178
|
+
description: "List all available skill categories in OKX Skills Marketplace. Use the returned categoryId as input to skills_search for category filtering. Do NOT use for searching or downloading skills \u2014 use skills_search or skills_download.",
|
|
3179
|
+
inputSchema: {
|
|
3180
|
+
type: "object",
|
|
3181
|
+
properties: {},
|
|
3182
|
+
additionalProperties: false
|
|
3183
|
+
},
|
|
3184
|
+
isWrite: false,
|
|
3185
|
+
handler: handleGetCategories
|
|
3186
|
+
},
|
|
3187
|
+
{
|
|
3188
|
+
name: "skills_search",
|
|
3189
|
+
module: "skills",
|
|
3190
|
+
description: "Search for skills in OKX Skills Marketplace by keyword or category. To get valid category IDs, call skills_get_categories first. Returns skill names for use with skills_download. Do NOT use for downloading \u2014 use skills_download.",
|
|
3191
|
+
inputSchema: {
|
|
3192
|
+
type: "object",
|
|
3193
|
+
properties: {
|
|
3194
|
+
keyword: {
|
|
3195
|
+
type: "string",
|
|
3196
|
+
description: "Search keyword (matches name, description, tags)"
|
|
3197
|
+
},
|
|
3198
|
+
categories: {
|
|
3199
|
+
type: "string",
|
|
3200
|
+
description: "Filter by category ID"
|
|
3201
|
+
},
|
|
3202
|
+
page: {
|
|
3203
|
+
type: "string",
|
|
3204
|
+
description: "Page number, starting from 1. Default: 1"
|
|
3205
|
+
},
|
|
3206
|
+
limit: {
|
|
3207
|
+
type: "string",
|
|
3208
|
+
description: "Results per page. Default: 20, max: 100"
|
|
3209
|
+
}
|
|
3210
|
+
},
|
|
3211
|
+
additionalProperties: false
|
|
3212
|
+
},
|
|
3213
|
+
isWrite: false,
|
|
3214
|
+
handler: handleSearch
|
|
3215
|
+
},
|
|
3216
|
+
{
|
|
3217
|
+
name: "skills_download",
|
|
3218
|
+
module: "skills",
|
|
3219
|
+
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: This only downloads the zip \u2014 it 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.",
|
|
3220
|
+
inputSchema: {
|
|
3221
|
+
type: "object",
|
|
3222
|
+
properties: {
|
|
3223
|
+
name: {
|
|
3224
|
+
type: "string",
|
|
3225
|
+
description: "Skill name (unique identifier)"
|
|
3226
|
+
},
|
|
3227
|
+
targetDir: {
|
|
3228
|
+
type: "string",
|
|
3229
|
+
description: "Directory path where the zip file will be saved"
|
|
3230
|
+
}
|
|
3231
|
+
},
|
|
3232
|
+
required: ["name", "targetDir"],
|
|
3233
|
+
additionalProperties: false
|
|
3234
|
+
},
|
|
3235
|
+
isWrite: true,
|
|
3236
|
+
handler: handleDownload
|
|
3237
|
+
}
|
|
3238
|
+
];
|
|
3239
|
+
}
|
|
3240
|
+
async function handleGetCategories(_args, ctx) {
|
|
3241
|
+
const result = await ctx.client.privateGet(
|
|
3242
|
+
"/api/v5/skill/categories"
|
|
3243
|
+
);
|
|
3244
|
+
return result;
|
|
3245
|
+
}
|
|
3246
|
+
async function handleSearch(args, ctx) {
|
|
3247
|
+
const query = {};
|
|
3248
|
+
if (args.keyword) query.keyword = String(args.keyword);
|
|
3249
|
+
if (args.categories) query.categories = String(args.categories);
|
|
3250
|
+
if (args.page) query.page = String(args.page);
|
|
3251
|
+
if (args.limit) query.limit = String(args.limit);
|
|
3252
|
+
const result = await ctx.client.privateGet(
|
|
3253
|
+
"/api/v5/skill/search",
|
|
3254
|
+
query
|
|
3255
|
+
);
|
|
3256
|
+
const totalPage = result.raw?.totalPage;
|
|
3257
|
+
return { ...result, totalPage };
|
|
3258
|
+
}
|
|
3259
|
+
async function handleDownload(args, ctx) {
|
|
3260
|
+
const name = String(args.name);
|
|
3261
|
+
const targetDir = String(args.targetDir);
|
|
3262
|
+
const filePath = await downloadSkillZip(ctx.client, name, targetDir);
|
|
3263
|
+
return {
|
|
3264
|
+
endpoint: "POST /api/v5/skill/download",
|
|
3265
|
+
requestTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3266
|
+
data: {
|
|
3267
|
+
name,
|
|
3268
|
+
filePath
|
|
3269
|
+
}
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
2882
3272
|
function normalizeWrite(response) {
|
|
2883
3273
|
const data = response.data;
|
|
2884
3274
|
if (Array.isArray(data) && data.length > 0) {
|
|
@@ -3477,7 +3867,6 @@ function registerEarnTools() {
|
|
|
3477
3867
|
required: ["ccy", "amt"]
|
|
3478
3868
|
},
|
|
3479
3869
|
handler: async (rawArgs, context) => {
|
|
3480
|
-
assertNotDemo(context.config, "earn_savings_purchase");
|
|
3481
3870
|
const args = asRecord(rawArgs);
|
|
3482
3871
|
const response = await context.client.privatePost(
|
|
3483
3872
|
"/api/v5/finance/savings/purchase-redempt",
|
|
@@ -3512,7 +3901,6 @@ function registerEarnTools() {
|
|
|
3512
3901
|
required: ["ccy", "amt"]
|
|
3513
3902
|
},
|
|
3514
3903
|
handler: async (rawArgs, context) => {
|
|
3515
|
-
assertNotDemo(context.config, "earn_savings_redeem");
|
|
3516
3904
|
const args = asRecord(rawArgs);
|
|
3517
3905
|
const response = await context.client.privatePost(
|
|
3518
3906
|
"/api/v5/finance/savings/purchase-redempt",
|
|
@@ -3546,7 +3934,6 @@ function registerEarnTools() {
|
|
|
3546
3934
|
required: ["ccy", "rate"]
|
|
3547
3935
|
},
|
|
3548
3936
|
handler: async (rawArgs, context) => {
|
|
3549
|
-
assertNotDemo(context.config, "earn_set_lending_rate");
|
|
3550
3937
|
const args = asRecord(rawArgs);
|
|
3551
3938
|
const response = await context.client.privatePost(
|
|
3552
3939
|
"/api/v5/finance/savings/set-lending-rate",
|
|
@@ -3690,7 +4077,7 @@ function registerOnchainEarnTools() {
|
|
|
3690
4077
|
{
|
|
3691
4078
|
name: "onchain_earn_purchase",
|
|
3692
4079
|
module: "earn.onchain",
|
|
3693
|
-
description: "Invest in a staking/DeFi product. [CAUTION] Moves real funds.
|
|
4080
|
+
description: "Invest in a staking/DeFi product. [CAUTION] Moves real funds.",
|
|
3694
4081
|
isWrite: true,
|
|
3695
4082
|
inputSchema: {
|
|
3696
4083
|
type: "object",
|
|
@@ -3723,7 +4110,6 @@ function registerOnchainEarnTools() {
|
|
|
3723
4110
|
required: ["productId", "investData"]
|
|
3724
4111
|
},
|
|
3725
4112
|
handler: async (rawArgs, context) => {
|
|
3726
|
-
assertNotDemo(context.config, "onchain_earn_purchase");
|
|
3727
4113
|
const args = asRecord(rawArgs);
|
|
3728
4114
|
const response = await context.client.privatePost(
|
|
3729
4115
|
"/api/v5/finance/staking-defi/purchase",
|
|
@@ -3744,7 +4130,7 @@ function registerOnchainEarnTools() {
|
|
|
3744
4130
|
{
|
|
3745
4131
|
name: "onchain_earn_redeem",
|
|
3746
4132
|
module: "earn.onchain",
|
|
3747
|
-
description: "Redeem a staking/DeFi investment. [CAUTION] Some products have lock periods, early redemption may incur penalties.
|
|
4133
|
+
description: "Redeem a staking/DeFi investment. [CAUTION] Some products have lock periods, early redemption may incur penalties.",
|
|
3748
4134
|
isWrite: true,
|
|
3749
4135
|
inputSchema: {
|
|
3750
4136
|
type: "object",
|
|
@@ -3765,7 +4151,6 @@ function registerOnchainEarnTools() {
|
|
|
3765
4151
|
required: ["ordId", "protocolType"]
|
|
3766
4152
|
},
|
|
3767
4153
|
handler: async (rawArgs, context) => {
|
|
3768
|
-
assertNotDemo(context.config, "onchain_earn_redeem");
|
|
3769
4154
|
const args = asRecord(rawArgs);
|
|
3770
4155
|
const response = await context.client.privatePost(
|
|
3771
4156
|
"/api/v5/finance/staking-defi/redeem",
|
|
@@ -3785,7 +4170,7 @@ function registerOnchainEarnTools() {
|
|
|
3785
4170
|
{
|
|
3786
4171
|
name: "onchain_earn_cancel",
|
|
3787
4172
|
module: "earn.onchain",
|
|
3788
|
-
description: "Cancel a pending staking/DeFi purchase order. [CAUTION]
|
|
4173
|
+
description: "Cancel a pending staking/DeFi purchase order. [CAUTION]",
|
|
3789
4174
|
isWrite: true,
|
|
3790
4175
|
inputSchema: {
|
|
3791
4176
|
type: "object",
|
|
@@ -3802,7 +4187,6 @@ function registerOnchainEarnTools() {
|
|
|
3802
4187
|
required: ["ordId", "protocolType"]
|
|
3803
4188
|
},
|
|
3804
4189
|
handler: async (rawArgs, context) => {
|
|
3805
|
-
assertNotDemo(context.config, "onchain_earn_cancel");
|
|
3806
4190
|
const args = asRecord(rawArgs);
|
|
3807
4191
|
const response = await context.client.privatePost(
|
|
3808
4192
|
"/api/v5/finance/staking-defi/cancel",
|
|
@@ -4095,7 +4479,6 @@ function registerDcdTools() {
|
|
|
4095
4479
|
required: ["productId", "notionalSz", "notionalCcy"]
|
|
4096
4480
|
},
|
|
4097
4481
|
handler: async (rawArgs, context) => {
|
|
4098
|
-
assertNotDemo(context.config, "dcd_subscribe");
|
|
4099
4482
|
const args = asRecord(rawArgs);
|
|
4100
4483
|
const productId = requireString(args, "productId");
|
|
4101
4484
|
const notionalSz = requireString(args, "notionalSz");
|
|
@@ -4267,13 +4650,30 @@ function registerAutoEarnTools() {
|
|
|
4267
4650
|
}
|
|
4268
4651
|
];
|
|
4269
4652
|
}
|
|
4653
|
+
var EARN_DEMO_MESSAGE = "Earn features (savings, DCD, on-chain staking, auto-earn) are not available in simulated trading mode.";
|
|
4654
|
+
var EARN_DEMO_SUGGESTION = "Switch to a live account to use Earn features.";
|
|
4655
|
+
var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem"]);
|
|
4656
|
+
function withDemoGuard(tool) {
|
|
4657
|
+
if (!tool.isWrite || DEMO_GUARD_SKIP.has(tool.name)) return tool;
|
|
4658
|
+
const originalHandler = tool.handler;
|
|
4659
|
+
return {
|
|
4660
|
+
...tool,
|
|
4661
|
+
handler: async (args, context) => {
|
|
4662
|
+
if (context.config.demo) {
|
|
4663
|
+
throw new ConfigError(EARN_DEMO_MESSAGE, EARN_DEMO_SUGGESTION);
|
|
4664
|
+
}
|
|
4665
|
+
return originalHandler(args, context);
|
|
4666
|
+
}
|
|
4667
|
+
};
|
|
4668
|
+
}
|
|
4270
4669
|
function registerAllEarnTools() {
|
|
4271
|
-
|
|
4670
|
+
const tools = [
|
|
4272
4671
|
...registerEarnTools(),
|
|
4273
4672
|
...registerOnchainEarnTools(),
|
|
4274
4673
|
...registerDcdTools(),
|
|
4275
4674
|
...registerAutoEarnTools()
|
|
4276
4675
|
];
|
|
4676
|
+
return tools.map(withDemoGuard);
|
|
4277
4677
|
}
|
|
4278
4678
|
function buildContractTradeTools(cfg) {
|
|
4279
4679
|
const { prefix, module, label, instTypes, instIdExample } = cfg;
|
|
@@ -4285,7 +4685,7 @@ function buildContractTradeTools(cfg) {
|
|
|
4285
4685
|
{
|
|
4286
4686
|
name: n("place_order"),
|
|
4287
4687
|
module,
|
|
4288
|
-
description: `Place ${label} order. Attach TP/SL via tpTriggerPx/slTriggerPx. [CAUTION] Executes real trades.`,
|
|
4688
|
+
description: `Place ${label} order. Attach TP/SL via tpTriggerPx/slTriggerPx. Before placing, use market_get_instruments to get ctVal (contract face value) \u2014 do NOT assume contract sizes. [CAUTION] Executes real trades.`,
|
|
4289
4689
|
isWrite: true,
|
|
4290
4690
|
inputSchema: {
|
|
4291
4691
|
type: "object",
|
|
@@ -4313,7 +4713,7 @@ function buildContractTradeTools(cfg) {
|
|
|
4313
4713
|
},
|
|
4314
4714
|
sz: {
|
|
4315
4715
|
type: "string",
|
|
4316
|
-
description: "
|
|
4716
|
+
description: "Number of contracts. Each contract = ctVal units (e.g. 0.1 ETH for ETH-USDT-SWAP). Query market_get_instruments for exact ctVal. Set tgtCcy=quote_ccy to specify sz in USDT instead."
|
|
4317
4717
|
},
|
|
4318
4718
|
tgtCcy: {
|
|
4319
4719
|
type: "string",
|
|
@@ -5297,7 +5697,7 @@ function registerMarketTools() {
|
|
|
5297
5697
|
{
|
|
5298
5698
|
name: "market_get_stock_tokens",
|
|
5299
5699
|
module: "market",
|
|
5300
|
-
description: "Get all stock token instruments (instCategory=3). Stock tokens track real-world stock prices on OKX (e.g. AAPL-USDT-SWAP).
|
|
5700
|
+
description: '[Deprecated: use market_get_instruments_by_category with instCategory="3" instead] Get all stock token instruments (instCategory=3). Stock tokens track real-world stock prices on OKX (e.g. AAPL-USDT-SWAP).',
|
|
5301
5701
|
isWrite: false,
|
|
5302
5702
|
inputSchema: {
|
|
5303
5703
|
type: "object",
|
|
@@ -5327,6 +5727,46 @@ function registerMarketTools() {
|
|
|
5327
5727
|
const filtered = Array.isArray(data) ? data.filter((item) => item.instCategory === "3") : data;
|
|
5328
5728
|
return normalizeResponse({ ...response, data: filtered });
|
|
5329
5729
|
}
|
|
5730
|
+
},
|
|
5731
|
+
{
|
|
5732
|
+
name: "market_get_instruments_by_category",
|
|
5733
|
+
module: "market",
|
|
5734
|
+
description: "Discover tradeable instruments by asset category. Stock tokens (instCategory=3, e.g. AAPL-USDT-SWAP, TSLA-USDT-SWAP), Metals (4, e.g. XAUUSDT-USDT-SWAP for gold), Commodities (5, e.g. OIL-USDT-SWAP for crude oil), Forex (6, e.g. EURUSDT-USDT-SWAP for EUR/USD), Bonds (7, e.g. US30Y-USDT-SWAP). Use this to find instIds before querying prices or placing orders. Filters client-side by instCategory.",
|
|
5735
|
+
isWrite: false,
|
|
5736
|
+
inputSchema: {
|
|
5737
|
+
type: "object",
|
|
5738
|
+
properties: {
|
|
5739
|
+
instCategory: {
|
|
5740
|
+
type: "string",
|
|
5741
|
+
enum: ["3", "4", "5", "6", "7"],
|
|
5742
|
+
description: "Asset category: 3=Stock tokens, 4=Metals, 5=Commodities, 6=Forex, 7=Bonds"
|
|
5743
|
+
},
|
|
5744
|
+
instType: {
|
|
5745
|
+
type: "string",
|
|
5746
|
+
enum: ["SPOT", "SWAP"],
|
|
5747
|
+
description: "Instrument type. Default: SWAP"
|
|
5748
|
+
},
|
|
5749
|
+
instId: {
|
|
5750
|
+
type: "string",
|
|
5751
|
+
description: "Optional: filter by specific instrument ID"
|
|
5752
|
+
}
|
|
5753
|
+
},
|
|
5754
|
+
required: ["instCategory"]
|
|
5755
|
+
},
|
|
5756
|
+
handler: async (rawArgs, context) => {
|
|
5757
|
+
const args = asRecord(rawArgs);
|
|
5758
|
+
const instCategory = requireString(args, "instCategory");
|
|
5759
|
+
const instType = readString(args, "instType") ?? "SWAP";
|
|
5760
|
+
const instId = readString(args, "instId");
|
|
5761
|
+
const response = await context.client.publicGet(
|
|
5762
|
+
"/api/v5/public/instruments",
|
|
5763
|
+
compactObject({ instType, instId }),
|
|
5764
|
+
publicRateLimit("market_get_instruments_by_category", 20)
|
|
5765
|
+
);
|
|
5766
|
+
const data = response.data;
|
|
5767
|
+
const filtered = Array.isArray(data) ? data.filter((item) => item.instCategory === instCategory) : data;
|
|
5768
|
+
return normalizeResponse({ ...response, data: filtered });
|
|
5769
|
+
}
|
|
5330
5770
|
}
|
|
5331
5771
|
];
|
|
5332
5772
|
}
|
|
@@ -5894,7 +6334,7 @@ function registerOptionTools() {
|
|
|
5894
6334
|
{
|
|
5895
6335
|
name: "option_place_order",
|
|
5896
6336
|
module: "option",
|
|
5897
|
-
description: "Place OPTION order. instId: {uly}-{expiry}-{strike}-C/P, e.g. BTC-USD-241227-50000-C. [CAUTION] Executes real trades.",
|
|
6337
|
+
description: "Place OPTION order. instId: {uly}-{expiry}-{strike}-C/P, e.g. BTC-USD-241227-50000-C. Before placing, use market_get_instruments to get ctVal (contract face value) \u2014 do NOT assume contract sizes. [CAUTION] Executes real trades.",
|
|
5898
6338
|
isWrite: true,
|
|
5899
6339
|
inputSchema: {
|
|
5900
6340
|
type: "object",
|
|
@@ -7104,7 +7544,8 @@ function allToolSpecs() {
|
|
|
7104
7544
|
...registerNewsTools(),
|
|
7105
7545
|
...registerBotTools(),
|
|
7106
7546
|
...registerAllEarnTools(),
|
|
7107
|
-
...registerAuditTools()
|
|
7547
|
+
...registerAuditTools(),
|
|
7548
|
+
...registerSkillsTools()
|
|
7108
7549
|
];
|
|
7109
7550
|
}
|
|
7110
7551
|
function createToolRunner(client, config) {
|
|
@@ -7119,12 +7560,12 @@ function createToolRunner(client, config) {
|
|
|
7119
7560
|
};
|
|
7120
7561
|
}
|
|
7121
7562
|
function configFilePath() {
|
|
7122
|
-
return
|
|
7563
|
+
return join4(homedir2(), ".okx", "config.toml");
|
|
7123
7564
|
}
|
|
7124
7565
|
function readFullConfig() {
|
|
7125
7566
|
const path42 = configFilePath();
|
|
7126
|
-
if (!
|
|
7127
|
-
const raw =
|
|
7567
|
+
if (!existsSync3(path42)) return { profiles: {} };
|
|
7568
|
+
const raw = readFileSync3(path42, "utf-8");
|
|
7128
7569
|
try {
|
|
7129
7570
|
return parse(raw);
|
|
7130
7571
|
} catch (err) {
|
|
@@ -7152,11 +7593,11 @@ var CONFIG_HEADER = `# OKX Trade Kit Configuration
|
|
|
7152
7593
|
`;
|
|
7153
7594
|
function writeFullConfig(config) {
|
|
7154
7595
|
const path42 = configFilePath();
|
|
7155
|
-
const dir =
|
|
7156
|
-
if (!
|
|
7157
|
-
|
|
7596
|
+
const dir = dirname3(path42);
|
|
7597
|
+
if (!existsSync3(dir)) {
|
|
7598
|
+
mkdirSync4(dir, { recursive: true });
|
|
7158
7599
|
}
|
|
7159
|
-
|
|
7600
|
+
writeFileSync3(path42, CONFIG_HEADER + stringify(config), "utf-8");
|
|
7160
7601
|
}
|
|
7161
7602
|
function expandShorthand(moduleId) {
|
|
7162
7603
|
if (moduleId === "all") return [...MODULES];
|
|
@@ -7270,12 +7711,12 @@ function loadConfig(cli) {
|
|
|
7270
7711
|
verbose: cli.verbose ?? false
|
|
7271
7712
|
};
|
|
7272
7713
|
}
|
|
7273
|
-
var CACHE_FILE =
|
|
7714
|
+
var CACHE_FILE = join5(homedir3(), ".okx", "update-check.json");
|
|
7274
7715
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
7275
7716
|
function readCache() {
|
|
7276
7717
|
try {
|
|
7277
|
-
if (
|
|
7278
|
-
return JSON.parse(
|
|
7718
|
+
if (existsSync4(CACHE_FILE)) {
|
|
7719
|
+
return JSON.parse(readFileSync4(CACHE_FILE, "utf-8"));
|
|
7279
7720
|
}
|
|
7280
7721
|
} catch {
|
|
7281
7722
|
}
|
|
@@ -7283,8 +7724,8 @@ function readCache() {
|
|
|
7283
7724
|
}
|
|
7284
7725
|
function writeCache(cache) {
|
|
7285
7726
|
try {
|
|
7286
|
-
|
|
7287
|
-
|
|
7727
|
+
mkdirSync5(join5(homedir3(), ".okx"), { recursive: true });
|
|
7728
|
+
writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
7288
7729
|
} catch {
|
|
7289
7730
|
}
|
|
7290
7731
|
}
|
|
@@ -7591,26 +8032,26 @@ var Report = class {
|
|
|
7591
8032
|
this.lines.push({ key, value });
|
|
7592
8033
|
}
|
|
7593
8034
|
print() {
|
|
7594
|
-
const
|
|
8035
|
+
const sep2 = "\u2500".repeat(52);
|
|
7595
8036
|
outputLine("");
|
|
7596
|
-
outputLine(` \u2500\u2500 Diagnostic Report (copy & share) ${
|
|
8037
|
+
outputLine(` \u2500\u2500 Diagnostic Report (copy & share) ${sep2.slice(35)}`);
|
|
7597
8038
|
for (const { key, value } of this.lines) {
|
|
7598
8039
|
outputLine(` ${key.padEnd(14)} ${value}`);
|
|
7599
8040
|
}
|
|
7600
|
-
outputLine(` ${
|
|
8041
|
+
outputLine(` ${sep2}`);
|
|
7601
8042
|
outputLine("");
|
|
7602
8043
|
}
|
|
7603
8044
|
/** Write report to a file path, returns true on success. */
|
|
7604
8045
|
writeToFile(filePath) {
|
|
7605
8046
|
try {
|
|
7606
|
-
const
|
|
8047
|
+
const sep2 = "-".repeat(52);
|
|
7607
8048
|
const lines = [
|
|
7608
|
-
`-- Diagnostic Report (copy & share) ${
|
|
8049
|
+
`-- Diagnostic Report (copy & share) ${sep2.slice(35)}`
|
|
7609
8050
|
];
|
|
7610
8051
|
for (const { key, value } of this.lines) {
|
|
7611
8052
|
lines.push(`${key.padEnd(14)} ${value}`);
|
|
7612
8053
|
}
|
|
7613
|
-
lines.push(
|
|
8054
|
+
lines.push(sep2, "");
|
|
7614
8055
|
fs2.writeFileSync(filePath, lines.join("\n"), "utf8");
|
|
7615
8056
|
return true;
|
|
7616
8057
|
} catch (_e) {
|
|
@@ -7986,13 +8427,13 @@ async function checkStdioHandshake(entryPath, report) {
|
|
|
7986
8427
|
clientInfo: { name: "okx-diagnose", version: "1.0" }
|
|
7987
8428
|
}
|
|
7988
8429
|
});
|
|
7989
|
-
return new Promise((
|
|
8430
|
+
return new Promise((resolve3) => {
|
|
7990
8431
|
let settled = false;
|
|
7991
8432
|
const settle = (passed) => {
|
|
7992
8433
|
if (settled) return;
|
|
7993
8434
|
settled = true;
|
|
7994
8435
|
clearTimeout(timer);
|
|
7995
|
-
|
|
8436
|
+
resolve3(passed);
|
|
7996
8437
|
};
|
|
7997
8438
|
const child = spawn(process.execPath, [entryPath], {
|
|
7998
8439
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -8113,7 +8554,7 @@ async function cmdDiagnoseMcp(options = {}) {
|
|
|
8113
8554
|
|
|
8114
8555
|
// src/commands/diagnose.ts
|
|
8115
8556
|
var CLI_VERSION = readCliVersion();
|
|
8116
|
-
var GIT_HASH = true ? "
|
|
8557
|
+
var GIT_HASH = true ? "cb0891d" : "dev";
|
|
8117
8558
|
function maskKey2(key) {
|
|
8118
8559
|
if (!key) return "(not set)";
|
|
8119
8560
|
if (key.length <= 8) return "****";
|
|
@@ -8130,7 +8571,7 @@ async function checkDns(hostname) {
|
|
|
8130
8571
|
}
|
|
8131
8572
|
async function checkSocket(createFn, successEvent, timeoutMs) {
|
|
8132
8573
|
const t0 = Date.now();
|
|
8133
|
-
return new Promise((
|
|
8574
|
+
return new Promise((resolve3) => {
|
|
8134
8575
|
const socket = createFn();
|
|
8135
8576
|
const cleanup = () => {
|
|
8136
8577
|
socket.removeAllListeners();
|
|
@@ -8138,15 +8579,15 @@ async function checkSocket(createFn, successEvent, timeoutMs) {
|
|
|
8138
8579
|
};
|
|
8139
8580
|
socket.once(successEvent, () => {
|
|
8140
8581
|
cleanup();
|
|
8141
|
-
|
|
8582
|
+
resolve3({ ok: true, ms: Date.now() - t0 });
|
|
8142
8583
|
});
|
|
8143
8584
|
socket.once("timeout", () => {
|
|
8144
8585
|
cleanup();
|
|
8145
|
-
|
|
8586
|
+
resolve3({ ok: false, ms: Date.now() - t0, error: `timed out after ${timeoutMs}ms` });
|
|
8146
8587
|
});
|
|
8147
8588
|
socket.once("error", (err) => {
|
|
8148
8589
|
cleanup();
|
|
8149
|
-
|
|
8590
|
+
resolve3({ ok: false, ms: Date.now() - t0, error: err.message });
|
|
8150
8591
|
});
|
|
8151
8592
|
});
|
|
8152
8593
|
}
|
|
@@ -8410,24 +8851,24 @@ async function runCliChecks(config, profile, outputPath) {
|
|
|
8410
8851
|
|
|
8411
8852
|
// src/commands/upgrade.ts
|
|
8412
8853
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
8413
|
-
import { readFileSync as
|
|
8414
|
-
import { dirname as
|
|
8415
|
-
import { homedir as
|
|
8854
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
|
|
8855
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
8856
|
+
import { homedir as homedir5 } from "os";
|
|
8416
8857
|
var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
|
|
8417
|
-
var CACHE_FILE2 =
|
|
8858
|
+
var CACHE_FILE2 = join7(homedir5(), ".okx", "last_check");
|
|
8418
8859
|
var THROTTLE_MS = 12 * 60 * 60 * 1e3;
|
|
8419
|
-
var NPM_BIN =
|
|
8860
|
+
var NPM_BIN = join7(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
|
|
8420
8861
|
function readLastCheck() {
|
|
8421
8862
|
try {
|
|
8422
|
-
return parseInt(
|
|
8863
|
+
return parseInt(readFileSync6(CACHE_FILE2, "utf-8").trim(), 10) || 0;
|
|
8423
8864
|
} catch {
|
|
8424
8865
|
return 0;
|
|
8425
8866
|
}
|
|
8426
8867
|
}
|
|
8427
8868
|
function writeLastCheck() {
|
|
8428
8869
|
try {
|
|
8429
|
-
|
|
8430
|
-
|
|
8870
|
+
mkdirSync7(join7(homedir5(), ".okx"), { recursive: true });
|
|
8871
|
+
writeFileSync6(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
|
|
8431
8872
|
} catch {
|
|
8432
8873
|
}
|
|
8433
8874
|
}
|
|
@@ -8632,7 +9073,12 @@ async function cmdNewsDetail(run, id, opts) {
|
|
|
8632
9073
|
return;
|
|
8633
9074
|
}
|
|
8634
9075
|
const rawContent = article["content"] ? String(article["content"]) : void 0;
|
|
8635
|
-
|
|
9076
|
+
let content;
|
|
9077
|
+
if (rawContent && rawContent.length > 500) {
|
|
9078
|
+
content = rawContent.slice(0, 500) + "... (use --json for full text)";
|
|
9079
|
+
} else {
|
|
9080
|
+
content = rawContent;
|
|
9081
|
+
}
|
|
8636
9082
|
printKv({
|
|
8637
9083
|
id: article["id"],
|
|
8638
9084
|
title: article["title"],
|
|
@@ -8820,7 +9266,11 @@ var HELP_TREE = {
|
|
|
8820
9266
|
},
|
|
8821
9267
|
"stock-tokens": {
|
|
8822
9268
|
usage: "okx market stock-tokens [--instType <SPOT|SWAP>] [--instId <id>]",
|
|
8823
|
-
description: "List all stock token instruments (instCategory=3, e.g. AAPL-USDT-SWAP)"
|
|
9269
|
+
description: "[Deprecated: use instruments-by-category --instCategory 3] List all stock token instruments (instCategory=3, e.g. AAPL-USDT-SWAP)"
|
|
9270
|
+
},
|
|
9271
|
+
"instruments-by-category": {
|
|
9272
|
+
usage: "okx market instruments-by-category --instCategory <4|5|6|7> [--instType <SPOT|SWAP>] [--instId <id>]",
|
|
9273
|
+
description: "List instruments by asset category: 4=Metals (gold/silver), 5=Commodities (oil/gas), 6=Forex (EUR/USD), 7=Bonds"
|
|
8824
9274
|
}
|
|
8825
9275
|
}
|
|
8826
9276
|
},
|
|
@@ -9577,6 +10027,7 @@ var CLI_OPTIONS = {
|
|
|
9577
10027
|
live: { type: "boolean", default: false },
|
|
9578
10028
|
// market extras
|
|
9579
10029
|
instType: { type: "string" },
|
|
10030
|
+
instCategory: { type: "string" },
|
|
9580
10031
|
quoteCcy: { type: "string" },
|
|
9581
10032
|
// account extras
|
|
9582
10033
|
archive: { type: "boolean", default: false },
|
|
@@ -9666,6 +10117,10 @@ var CLI_OPTIONS = {
|
|
|
9666
10117
|
period: { type: "string" },
|
|
9667
10118
|
points: { type: "string" },
|
|
9668
10119
|
"sort-by": { type: "string" },
|
|
10120
|
+
// skill marketplace
|
|
10121
|
+
categories: { type: "string" },
|
|
10122
|
+
dir: { type: "string" },
|
|
10123
|
+
page: { type: "string" },
|
|
9669
10124
|
// diagnostics — cli/mcp/all/output are diagnose-specific; verbose is shared
|
|
9670
10125
|
verbose: { type: "boolean", default: false },
|
|
9671
10126
|
mcp: { type: "boolean", default: false },
|
|
@@ -9949,6 +10404,37 @@ async function cmdMarketIndicator(run, indicator, instId, opts) {
|
|
|
9949
10404
|
}
|
|
9950
10405
|
}
|
|
9951
10406
|
}
|
|
10407
|
+
async function cmdMarketInstrumentsByCategory(run, opts) {
|
|
10408
|
+
const result = await run("market_get_instruments_by_category", {
|
|
10409
|
+
instCategory: opts.instCategory,
|
|
10410
|
+
instType: opts.instType,
|
|
10411
|
+
instId: opts.instId
|
|
10412
|
+
});
|
|
10413
|
+
const items = getData2(result);
|
|
10414
|
+
if (opts.json) return printJson(items);
|
|
10415
|
+
const CATEGORY_LABELS = {
|
|
10416
|
+
"3": "Stock tokens",
|
|
10417
|
+
"4": "Metals",
|
|
10418
|
+
"5": "Commodities",
|
|
10419
|
+
"6": "Forex",
|
|
10420
|
+
"7": "Bonds"
|
|
10421
|
+
};
|
|
10422
|
+
const label = CATEGORY_LABELS[opts.instCategory] ?? opts.instCategory;
|
|
10423
|
+
process.stdout.write(`instCategory=${opts.instCategory} (${label}) \u2014 ${items?.length ?? 0} instruments
|
|
10424
|
+
|
|
10425
|
+
`);
|
|
10426
|
+
printTable(
|
|
10427
|
+
(items ?? []).slice(0, 50).map((t) => ({
|
|
10428
|
+
instId: t["instId"],
|
|
10429
|
+
instCategory: t["instCategory"],
|
|
10430
|
+
ctVal: t["ctVal"],
|
|
10431
|
+
lotSz: t["lotSz"],
|
|
10432
|
+
minSz: t["minSz"],
|
|
10433
|
+
tickSz: t["tickSz"],
|
|
10434
|
+
state: t["state"]
|
|
10435
|
+
}))
|
|
10436
|
+
);
|
|
10437
|
+
}
|
|
9952
10438
|
async function cmdMarketStockTokens(run, opts) {
|
|
9953
10439
|
const result = await run("market_get_stock_tokens", { instType: opts.instType, instId: opts.instId });
|
|
9954
10440
|
const items = getData2(result);
|
|
@@ -11384,7 +11870,7 @@ Config saved to ${p}
|
|
|
11384
11870
|
}
|
|
11385
11871
|
};
|
|
11386
11872
|
function prompt(rl, question) {
|
|
11387
|
-
return new Promise((
|
|
11873
|
+
return new Promise((resolve3) => rl.question(question, resolve3));
|
|
11388
11874
|
}
|
|
11389
11875
|
function cmdConfigShow(json) {
|
|
11390
11876
|
const config = readFullConfig();
|
|
@@ -12349,10 +12835,194 @@ async function cmdDcdQuoteAndBuy(run, opts) {
|
|
|
12349
12835
|
}
|
|
12350
12836
|
}
|
|
12351
12837
|
|
|
12838
|
+
// src/commands/skill.ts
|
|
12839
|
+
import { tmpdir, homedir as homedir7 } from "os";
|
|
12840
|
+
import { join as join9, dirname as dirname6 } from "path";
|
|
12841
|
+
import { mkdirSync as mkdirSync8, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
|
|
12842
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
12843
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
12844
|
+
function resolveNpx() {
|
|
12845
|
+
const sibling = join9(dirname6(process.execPath), "npx");
|
|
12846
|
+
if (existsSync7(sibling)) return sibling;
|
|
12847
|
+
return "npx";
|
|
12848
|
+
}
|
|
12849
|
+
async function cmdSkillSearch(run, opts) {
|
|
12850
|
+
const args = {};
|
|
12851
|
+
if (opts.keyword) args.keyword = opts.keyword;
|
|
12852
|
+
if (opts.categories) args.categories = opts.categories;
|
|
12853
|
+
if (opts.page) args.page = opts.page;
|
|
12854
|
+
if (opts.limit) args.limit = opts.limit;
|
|
12855
|
+
const result = await run("skills_search", args);
|
|
12856
|
+
const data = result.data;
|
|
12857
|
+
const totalPage = result.totalPage;
|
|
12858
|
+
if (opts.json) {
|
|
12859
|
+
outputLine(JSON.stringify(result, null, 2));
|
|
12860
|
+
return;
|
|
12861
|
+
}
|
|
12862
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
12863
|
+
outputLine("No skills found.");
|
|
12864
|
+
return;
|
|
12865
|
+
}
|
|
12866
|
+
outputLine("");
|
|
12867
|
+
outputLine(" NAME VERSION DESCRIPTION");
|
|
12868
|
+
for (const item of data) {
|
|
12869
|
+
const name = (item.name ?? "").padEnd(20);
|
|
12870
|
+
const ver = (item.latestVersion ?? "").padEnd(10);
|
|
12871
|
+
const desc = (item.description ?? "").slice(0, 50);
|
|
12872
|
+
outputLine(` ${name}${ver}${desc}`);
|
|
12873
|
+
}
|
|
12874
|
+
outputLine("");
|
|
12875
|
+
const page = opts.page ?? "1";
|
|
12876
|
+
const pageInfo = totalPage ? ` (page ${page}/${totalPage})` : "";
|
|
12877
|
+
outputLine(`${data.length} skills found${pageInfo}. Use \`okx skill add <name>\` to install.`);
|
|
12878
|
+
}
|
|
12879
|
+
async function cmdSkillCategories(run, json) {
|
|
12880
|
+
const result = await run("skills_get_categories", {});
|
|
12881
|
+
const data = result.data;
|
|
12882
|
+
if (json) {
|
|
12883
|
+
outputLine(JSON.stringify(result, null, 2));
|
|
12884
|
+
return;
|
|
12885
|
+
}
|
|
12886
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
12887
|
+
outputLine("No categories found.");
|
|
12888
|
+
return;
|
|
12889
|
+
}
|
|
12890
|
+
outputLine("");
|
|
12891
|
+
outputLine(" ID NAME");
|
|
12892
|
+
for (const cat of data) {
|
|
12893
|
+
outputLine(` ${(cat.categoryId ?? "").padEnd(20)}${cat.name ?? ""}`);
|
|
12894
|
+
}
|
|
12895
|
+
outputLine("");
|
|
12896
|
+
}
|
|
12897
|
+
async function cmdSkillAdd(name, config, json) {
|
|
12898
|
+
const tmpBase = join9(tmpdir(), `okx-skill-${randomUUID2()}`);
|
|
12899
|
+
mkdirSync8(tmpBase, { recursive: true });
|
|
12900
|
+
try {
|
|
12901
|
+
outputLine(`Downloading ${name}...`);
|
|
12902
|
+
const client = new OkxRestClient(config);
|
|
12903
|
+
const zipPath = await downloadSkillZip(client, name, tmpBase);
|
|
12904
|
+
const contentDir = await extractSkillZip(zipPath, join9(tmpBase, "content"));
|
|
12905
|
+
const meta = readMetaJson(contentDir);
|
|
12906
|
+
validateSkillMdExists(contentDir);
|
|
12907
|
+
outputLine("Installing to detected agents...");
|
|
12908
|
+
try {
|
|
12909
|
+
execFileSync2(resolveNpx(), ["skills", "add", contentDir, "-y", "-g"], {
|
|
12910
|
+
stdio: "inherit",
|
|
12911
|
+
timeout: 6e4
|
|
12912
|
+
});
|
|
12913
|
+
} catch (e) {
|
|
12914
|
+
const savedZip = join9(process.cwd(), `${name}.zip`);
|
|
12915
|
+
try {
|
|
12916
|
+
copyFileSync2(zipPath, savedZip);
|
|
12917
|
+
} catch {
|
|
12918
|
+
}
|
|
12919
|
+
errorLine(`npx skills add failed. The zip has been downloaded but not installed.`);
|
|
12920
|
+
errorLine(`You can manually install from: ${savedZip}`);
|
|
12921
|
+
throw e;
|
|
12922
|
+
}
|
|
12923
|
+
upsertSkillRecord(meta);
|
|
12924
|
+
if (json) {
|
|
12925
|
+
outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
|
|
12926
|
+
} else {
|
|
12927
|
+
outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
|
|
12928
|
+
}
|
|
12929
|
+
} finally {
|
|
12930
|
+
rmSync(tmpBase, { recursive: true, force: true });
|
|
12931
|
+
}
|
|
12932
|
+
}
|
|
12933
|
+
async function cmdSkillDownload(name, targetDir, config, json) {
|
|
12934
|
+
outputLine(`Downloading ${name}...`);
|
|
12935
|
+
const client = new OkxRestClient(config);
|
|
12936
|
+
const filePath = await downloadSkillZip(client, name, targetDir);
|
|
12937
|
+
if (json) {
|
|
12938
|
+
outputLine(JSON.stringify({ name, filePath }, null, 2));
|
|
12939
|
+
} else {
|
|
12940
|
+
outputLine(`\u2713 Downloaded ${name}.zip`);
|
|
12941
|
+
outputLine(` Path: ${filePath}`);
|
|
12942
|
+
}
|
|
12943
|
+
}
|
|
12944
|
+
function cmdSkillRemove(name, json) {
|
|
12945
|
+
const removed = removeSkillRecord(name);
|
|
12946
|
+
if (!removed) {
|
|
12947
|
+
errorLine(`Skill "${name}" is not installed.`);
|
|
12948
|
+
process.exitCode = 1;
|
|
12949
|
+
return;
|
|
12950
|
+
}
|
|
12951
|
+
try {
|
|
12952
|
+
execFileSync2(resolveNpx(), ["skills", "remove", name, "-y", "-g"], {
|
|
12953
|
+
stdio: "inherit",
|
|
12954
|
+
timeout: 6e4
|
|
12955
|
+
});
|
|
12956
|
+
} catch {
|
|
12957
|
+
const agentsPath = join9(homedir7(), ".agents", "skills", name);
|
|
12958
|
+
try {
|
|
12959
|
+
rmSync(agentsPath, { recursive: true, force: true });
|
|
12960
|
+
} catch {
|
|
12961
|
+
}
|
|
12962
|
+
}
|
|
12963
|
+
if (json) {
|
|
12964
|
+
outputLine(JSON.stringify({ name, status: "removed" }, null, 2));
|
|
12965
|
+
} else {
|
|
12966
|
+
outputLine(`\u2713 Skill "${name}" removed`);
|
|
12967
|
+
}
|
|
12968
|
+
}
|
|
12969
|
+
async function cmdSkillCheck(run, name, json) {
|
|
12970
|
+
const local = getSkillRecord(name);
|
|
12971
|
+
if (!local) {
|
|
12972
|
+
errorLine(`Skill "${name}" is not installed.`);
|
|
12973
|
+
process.exitCode = 1;
|
|
12974
|
+
return;
|
|
12975
|
+
}
|
|
12976
|
+
const result = await run("skills_search", { keyword: name });
|
|
12977
|
+
const data = result.data;
|
|
12978
|
+
const remote = data?.find((s) => s.name === name);
|
|
12979
|
+
if (!remote) {
|
|
12980
|
+
errorLine(`Skill "${name}" not found in marketplace.`);
|
|
12981
|
+
process.exitCode = 1;
|
|
12982
|
+
return;
|
|
12983
|
+
}
|
|
12984
|
+
const upToDate = local.version === remote.latestVersion;
|
|
12985
|
+
if (json) {
|
|
12986
|
+
outputLine(JSON.stringify({
|
|
12987
|
+
name,
|
|
12988
|
+
installedVersion: local.version,
|
|
12989
|
+
latestVersion: remote.latestVersion,
|
|
12990
|
+
upToDate
|
|
12991
|
+
}, null, 2));
|
|
12992
|
+
} else if (upToDate) {
|
|
12993
|
+
outputLine(`${name}: installed v${local.version} \u2192 latest v${remote.latestVersion} (up to date)`);
|
|
12994
|
+
} else {
|
|
12995
|
+
outputLine(`${name}: installed v${local.version} \u2192 latest v${remote.latestVersion} (update available)`);
|
|
12996
|
+
outputLine(` Use \`okx skill add ${name}\` to update.`);
|
|
12997
|
+
}
|
|
12998
|
+
}
|
|
12999
|
+
function cmdSkillList(json) {
|
|
13000
|
+
const registry = readRegistry();
|
|
13001
|
+
const skills = Object.values(registry.skills);
|
|
13002
|
+
if (json) {
|
|
13003
|
+
outputLine(JSON.stringify(registry, null, 2));
|
|
13004
|
+
return;
|
|
13005
|
+
}
|
|
13006
|
+
if (skills.length === 0) {
|
|
13007
|
+
outputLine("No skills installed.");
|
|
13008
|
+
return;
|
|
13009
|
+
}
|
|
13010
|
+
outputLine("");
|
|
13011
|
+
outputLine(" NAME VERSION INSTALLED AT");
|
|
13012
|
+
for (const s of skills) {
|
|
13013
|
+
const name = s.name.padEnd(20);
|
|
13014
|
+
const ver = s.version.padEnd(10);
|
|
13015
|
+
const date = s.installedAt.slice(0, 19).replace("T", " ");
|
|
13016
|
+
outputLine(` ${name}${ver}${date}`);
|
|
13017
|
+
}
|
|
13018
|
+
outputLine("");
|
|
13019
|
+
outputLine(`${skills.length} skills installed.`);
|
|
13020
|
+
}
|
|
13021
|
+
|
|
12352
13022
|
// src/index.ts
|
|
12353
13023
|
var _require3 = createRequire3(import.meta.url);
|
|
12354
13024
|
var CLI_VERSION2 = _require3("../package.json").version;
|
|
12355
|
-
var GIT_HASH2 = true ? "
|
|
13025
|
+
var GIT_HASH2 = true ? "cb0891d" : "dev";
|
|
12356
13026
|
function handleConfigCommand(action, rest, json, lang, force) {
|
|
12357
13027
|
if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
|
|
12358
13028
|
if (action === "show") return cmdConfigShow(json);
|
|
@@ -12395,6 +13065,13 @@ function handleMarketPublicCommand(run, action, rest, v, json) {
|
|
|
12395
13065
|
return cmdMarketOpenInterest(run, { instType: v.instType, instId: v.instId, json });
|
|
12396
13066
|
if (action === "stock-tokens")
|
|
12397
13067
|
return cmdMarketStockTokens(run, { instType: v.instType, instId: v.instId, json });
|
|
13068
|
+
if (action === "instruments-by-category")
|
|
13069
|
+
return cmdMarketInstrumentsByCategory(run, {
|
|
13070
|
+
instCategory: v.instCategory,
|
|
13071
|
+
instType: v.instType,
|
|
13072
|
+
instId: v.instId,
|
|
13073
|
+
json
|
|
13074
|
+
});
|
|
12398
13075
|
if (action === "indicator") {
|
|
12399
13076
|
const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
|
|
12400
13077
|
const backtestTime = v["backtest-time"] !== void 0 ? Number(v["backtest-time"]) : void 0;
|
|
@@ -13141,6 +13818,42 @@ function handleNewsCommand(run, action, rest, v, json) {
|
|
|
13141
13818
|
`);
|
|
13142
13819
|
process.exitCode = 1;
|
|
13143
13820
|
}
|
|
13821
|
+
function requireSkillName(rest, usage) {
|
|
13822
|
+
const name = rest[0];
|
|
13823
|
+
if (!name) {
|
|
13824
|
+
errorLine(usage);
|
|
13825
|
+
process.exitCode = 1;
|
|
13826
|
+
}
|
|
13827
|
+
return name;
|
|
13828
|
+
}
|
|
13829
|
+
function handleSkillAdd(rest, config, json) {
|
|
13830
|
+
const n = requireSkillName(rest, "Usage: okx skill add <name>");
|
|
13831
|
+
if (n) return cmdSkillAdd(n, config, json);
|
|
13832
|
+
}
|
|
13833
|
+
function handleSkillDownload(rest, v, config, json) {
|
|
13834
|
+
const n = requireSkillName(rest, "Usage: okx skill download <name> [--dir <path>]");
|
|
13835
|
+
if (n) return cmdSkillDownload(n, v.dir ?? process.cwd(), config, json);
|
|
13836
|
+
}
|
|
13837
|
+
function handleSkillRemove(rest, json) {
|
|
13838
|
+
const n = requireSkillName(rest, "Usage: okx skill remove <name>");
|
|
13839
|
+
if (n) cmdSkillRemove(n, json);
|
|
13840
|
+
}
|
|
13841
|
+
function handleSkillCheck(run, rest, json) {
|
|
13842
|
+
const n = requireSkillName(rest, "Usage: okx skill check <name>");
|
|
13843
|
+
if (n) return cmdSkillCheck(run, n, json);
|
|
13844
|
+
}
|
|
13845
|
+
function handleSkillCommand(run, action, rest, v, json, config) {
|
|
13846
|
+
if (action === "search") return cmdSkillSearch(run, { keyword: rest[0] ?? v.keyword, categories: v.categories, page: v.page, limit: v.limit, json });
|
|
13847
|
+
if (action === "categories") return cmdSkillCategories(run, json);
|
|
13848
|
+
if (action === "list") return cmdSkillList(json);
|
|
13849
|
+
if (action === "add") return handleSkillAdd(rest, config, json);
|
|
13850
|
+
if (action === "download") return handleSkillDownload(rest, v, config, json);
|
|
13851
|
+
if (action === "remove") return handleSkillRemove(rest, json);
|
|
13852
|
+
if (action === "check") return handleSkillCheck(run, rest, json);
|
|
13853
|
+
errorLine(`Unknown skill command: ${action}`);
|
|
13854
|
+
errorLine("Valid: search, categories, add, download, remove, check, list");
|
|
13855
|
+
process.exitCode = 1;
|
|
13856
|
+
}
|
|
13144
13857
|
function outputResult(result, json) {
|
|
13145
13858
|
if (json) {
|
|
13146
13859
|
outputLine(JSON.stringify(result, null, 2));
|
|
@@ -13203,7 +13916,8 @@ async function main() {
|
|
|
13203
13916
|
option: () => handleOptionCommand(run, action, rest, v, json),
|
|
13204
13917
|
news: () => handleNewsCommand(run, action, rest, v, json),
|
|
13205
13918
|
bot: () => handleBotCommand(run, action, rest, v, json),
|
|
13206
|
-
earn: () => handleEarnCommand(run, action, rest, v, json)
|
|
13919
|
+
earn: () => handleEarnCommand(run, action, rest, v, json),
|
|
13920
|
+
skill: () => handleSkillCommand(run, action, rest, v, json, config)
|
|
13207
13921
|
};
|
|
13208
13922
|
const handler = moduleHandlers[module];
|
|
13209
13923
|
if (handler) return handler();
|
|
@@ -13234,6 +13948,7 @@ export {
|
|
|
13234
13948
|
handleOptionAlgoCommand,
|
|
13235
13949
|
handleOptionCommand,
|
|
13236
13950
|
handleSetupCommand,
|
|
13951
|
+
handleSkillCommand,
|
|
13237
13952
|
handleSpotAlgoCommand,
|
|
13238
13953
|
handleSpotCommand,
|
|
13239
13954
|
handleSwapAlgoCommand,
|