@okx_ai/okx-trade-cli 1.3.0-beta.3 → 1.3.0-beta.5

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 CHANGED
@@ -4,37 +4,25 @@
4
4
  import { createRequire as createRequire3 } from "module";
5
5
 
6
6
  // ../core/dist/index.js
7
- import { Agent, ProxyAgent } from "undici";
8
- import { execFile } from "child_process";
9
- import { homedir } from "os";
10
- import { join } from "path";
11
- import {
12
- readFileSync,
13
- writeFileSync,
14
- mkdirSync,
15
- unlinkSync,
16
- renameSync
17
- } from "fs";
18
- import { homedir as homedir2 } from "os";
19
- import { join as join2, dirname } from "path";
7
+ import { ProxyAgent } from "undici";
20
8
  import { createHmac } from "crypto";
21
9
  import fs from "fs";
22
10
  import path from "path";
23
11
  import os from "os";
24
- import { writeFileSync as writeFileSync2, renameSync as renameSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
25
- import { join as join3, resolve, basename, sep } from "path";
12
+ import { writeFileSync, renameSync, unlinkSync, mkdirSync } from "fs";
13
+ import { join, resolve, basename, sep } from "path";
26
14
  import { randomUUID } from "crypto";
27
15
  import yauzl from "yauzl";
28
- import { createWriteStream, mkdirSync as mkdirSync3 } from "fs";
29
- import { resolve as resolve2, dirname as dirname2 } from "path";
30
- import { readFileSync as readFileSync2, existsSync } from "fs";
31
- import { join as join4 } from "path";
32
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync2 } from "fs";
33
- import { join as join5, dirname as dirname3 } from "path";
34
- import { homedir as homedir3 } from "os";
35
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync3 } from "fs";
36
- import { join as join6, dirname as dirname4 } from "path";
37
- import { homedir as homedir4 } from "os";
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";
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";
38
26
 
39
27
  // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
40
28
  function getLineColFromPtr(string, ptr) {
@@ -863,137 +851,16 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
863
851
  }
864
852
 
865
853
  // ../core/dist/index.js
866
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync4 } from "fs";
867
- import { join as join7 } from "path";
868
- import { homedir as homedir5 } from "os";
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";
857
+ import fs2 from "fs";
858
+ import path2 from "path";
859
+ import os2 from "os";
869
860
  import * as fs3 from "fs";
870
861
  import * as path3 from "path";
871
862
  import * as os3 from "os";
872
863
  import { execFileSync } from "child_process";
873
- var EXEC_TIMEOUT_MS = 3e4;
874
- var DOH_BIN_DIR = join(homedir(), ".okx", "bin");
875
- function getDohBinaryPath() {
876
- if (process.env.OKX_DOH_BINARY_PATH) {
877
- return process.env.OKX_DOH_BINARY_PATH;
878
- }
879
- const ext = process.platform === "win32" ? ".exe" : "";
880
- return join(DOH_BIN_DIR, `okx-doh-resolver${ext}`);
881
- }
882
- function execDohBinary(domain, exclude = [], userAgent) {
883
- const binPath = getDohBinaryPath();
884
- const args = ["--domain", domain];
885
- if (exclude.length > 0) {
886
- args.push("--exclude", exclude.join(","));
887
- }
888
- if (userAgent) {
889
- args.push("--user-agent", userAgent);
890
- }
891
- return new Promise((resolve3) => {
892
- execFile(
893
- binPath,
894
- args,
895
- { timeout: EXEC_TIMEOUT_MS, encoding: "utf-8" },
896
- (error, stdout) => {
897
- if (error) {
898
- resolve3(null);
899
- return;
900
- }
901
- try {
902
- const result = JSON.parse(stdout);
903
- if (result.code === 0 && result.data) {
904
- resolve3(result.data);
905
- } else {
906
- resolve3(null);
907
- }
908
- } catch {
909
- resolve3(null);
910
- }
911
- }
912
- );
913
- });
914
- }
915
- var DOH_CACHE_PATH = join2(homedir2(), ".okx", "doh-node-cache.json");
916
- function readCache(hostname, cachePath = DOH_CACHE_PATH) {
917
- try {
918
- const raw = readFileSync(cachePath, "utf-8");
919
- const file = JSON.parse(raw);
920
- return file[hostname] ?? null;
921
- } catch {
922
- return null;
923
- }
924
- }
925
- function writeCache(hostname, entry, cachePath = DOH_CACHE_PATH) {
926
- try {
927
- const dir = dirname(cachePath);
928
- mkdirSync(dir, { recursive: true });
929
- let file = {};
930
- try {
931
- file = JSON.parse(readFileSync(cachePath, "utf-8"));
932
- } catch {
933
- }
934
- file[hostname] = entry;
935
- const tmpPath = `${cachePath}.tmp`;
936
- writeFileSync(tmpPath, JSON.stringify(file));
937
- renameSync(tmpPath, cachePath);
938
- } catch {
939
- }
940
- }
941
- var FAILED_NODE_TTL_MS = 60 * 60 * 1e3;
942
- function classifyAndCache(node, hostname, failedNodes) {
943
- if (!node) {
944
- if (failedNodes.length > 0) {
945
- writeCache(hostname, {
946
- mode: "direct",
947
- node: null,
948
- failedNodes,
949
- updatedAt: Date.now()
950
- });
951
- }
952
- return { mode: null, node: null };
953
- }
954
- if (node.ip === hostname || node.host === hostname) {
955
- writeCache(hostname, {
956
- mode: "direct",
957
- node: null,
958
- failedNodes,
959
- updatedAt: Date.now()
960
- });
961
- return { mode: "direct", node: null };
962
- }
963
- writeCache(hostname, {
964
- mode: "proxy",
965
- node,
966
- failedNodes,
967
- updatedAt: Date.now()
968
- });
969
- return { mode: "proxy", node };
970
- }
971
- function getActiveFailedNodes(nodes) {
972
- if (!nodes || nodes.length === 0) return [];
973
- const now = Date.now();
974
- return nodes.filter((n) => now - n.failedAt < FAILED_NODE_TTL_MS);
975
- }
976
- async function resolveDoh(hostname) {
977
- const entry = readCache(hostname);
978
- if (entry) {
979
- if (entry.mode === "direct") {
980
- return { mode: "direct", node: null };
981
- }
982
- if (entry.mode === "proxy" && entry.node) {
983
- return { mode: "proxy", node: entry.node };
984
- }
985
- }
986
- return { mode: null, node: null };
987
- }
988
- async function reResolveDoh(hostname, failedIp, userAgent) {
989
- const entry = readCache(hostname);
990
- const active = getActiveFailedNodes(entry?.failedNodes);
991
- const now = Date.now();
992
- const failedNodes = failedIp ? active.some((n) => n.ip === failedIp) ? active : [...active, { ip: failedIp, failedAt: now }] : active;
993
- const excludeIps = failedNodes.map((n) => n.ip);
994
- const node = await execDohBinary(hostname, excludeIps, userAgent);
995
- return classifyAndCache(node, hostname, failedNodes);
996
- }
997
864
  function getNow() {
998
865
  return (/* @__PURE__ */ new Date()).toISOString();
999
866
  }
@@ -1212,14 +1079,6 @@ var OkxRestClient = class _OkxRestClient {
1212
1079
  config;
1213
1080
  rateLimiter;
1214
1081
  dispatcher;
1215
- // DoH proxy state (lazy-resolved on first request)
1216
- dohResolved = false;
1217
- dohRetried = false;
1218
- directUnverified = false;
1219
- // The first direct connection has not yet been verified
1220
- dohNode = null;
1221
- dohAgent = null;
1222
- dohBaseUrl = null;
1223
1082
  constructor(config) {
1224
1083
  this.config = config;
1225
1084
  this.rateLimiter = new RateLimiter(3e4, config.verbose);
@@ -1227,97 +1086,6 @@ var OkxRestClient = class _OkxRestClient {
1227
1086
  this.dispatcher = new ProxyAgent(config.proxyUrl);
1228
1087
  }
1229
1088
  }
1230
- /**
1231
- * Lazily resolve the DoH proxy node on the first request.
1232
- * Uses cache-first strategy via the resolver.
1233
- */
1234
- async ensureDoh() {
1235
- if (this.dohResolved || this.dispatcher) return;
1236
- this.dohResolved = true;
1237
- try {
1238
- const { hostname, protocol } = new URL(this.config.baseUrl);
1239
- const result = await resolveDoh(hostname);
1240
- if (!result.mode) {
1241
- this.directUnverified = true;
1242
- if (this.config.verbose) {
1243
- vlog("DoH: no cache, trying direct connection first");
1244
- }
1245
- return;
1246
- }
1247
- if (result.mode === "direct") {
1248
- if (this.config.verbose) {
1249
- vlog("DoH: mode=direct (overseas or cached), using direct connection");
1250
- }
1251
- return;
1252
- }
1253
- if (result.node) {
1254
- this.applyDohNode(result.node, protocol);
1255
- }
1256
- } catch (err) {
1257
- if (this.config.verbose) {
1258
- const cause = err instanceof Error ? err.message : String(err);
1259
- vlog(`DoH resolution failed, falling back to direct: ${cause}`);
1260
- }
1261
- }
1262
- }
1263
- /** Apply a DoH node: set up the custom Agent + base URL. */
1264
- applyDohNode(node, protocol) {
1265
- this.dohNode = node;
1266
- this.dohBaseUrl = `${protocol}//${node.host}`;
1267
- this.dohAgent = new Agent({
1268
- connect: {
1269
- lookup: (_hostname, options, callback) => {
1270
- if (options?.all) {
1271
- callback(null, [{ address: node.ip, family: 4 }]);
1272
- } else {
1273
- callback(null, node.ip, 4);
1274
- }
1275
- }
1276
- }
1277
- });
1278
- if (this.config.verbose) {
1279
- vlog(`DoH proxy active: \u2192 ${node.host} (${node.ip}), ttl=${node.ttl}s`);
1280
- }
1281
- }
1282
- /**
1283
- * Handle network failure: re-resolve with --exclude and retry once.
1284
- * Returns true if retry should proceed, false if already retried.
1285
- */
1286
- async handleDohNetworkFailure() {
1287
- if (this.dohRetried) return false;
1288
- this.dohRetried = true;
1289
- const failedIp = this.dohNode?.ip ?? "";
1290
- const { hostname, protocol } = new URL(this.config.baseUrl);
1291
- this.dohNode = null;
1292
- this.dohAgent = null;
1293
- this.dohBaseUrl = null;
1294
- if (!failedIp) this.directUnverified = false;
1295
- if (this.config.verbose) {
1296
- vlog(failedIp ? `DoH: proxy node ${failedIp} failed, re-resolving with --exclude` : "DoH: direct connection failed, calling binary for DoH resolution");
1297
- }
1298
- try {
1299
- const result = await reResolveDoh(hostname, failedIp, this.dohUserAgent);
1300
- if (result.mode === "proxy" && result.node) {
1301
- this.applyDohNode(result.node, protocol);
1302
- return true;
1303
- }
1304
- } catch {
1305
- }
1306
- if (this.config.verbose) {
1307
- vlog("DoH: re-resolution failed or switched to direct, retrying with direct connection");
1308
- }
1309
- return true;
1310
- }
1311
- get activeBaseUrl() {
1312
- return this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
1313
- }
1314
- get activeDispatcher() {
1315
- return this.dispatcher ?? this.dohAgent ?? void 0;
1316
- }
1317
- /** User-Agent for DoH proxy requests: OKX/@okx_ai/{packageName}/{version} */
1318
- get dohUserAgent() {
1319
- return `OKX/@okx_ai/${this.config.userAgent ?? "unknown"}`;
1320
- }
1321
1089
  logRequest(method, url, auth) {
1322
1090
  if (!this.config.verbose) return;
1323
1091
  vlog(`\u2192 ${method} ${url}`);
@@ -1332,13 +1100,14 @@ var OkxRestClient = class _OkxRestClient {
1332
1100
  vlog(`\u2190 ${status} | code=${code ?? "0"} | ${rawLen}B | ${elapsed}ms | trace=${traceId ?? "-"}`);
1333
1101
  }
1334
1102
  }
1335
- async publicGet(path42, query, rateLimit) {
1103
+ async publicGet(path42, query, rateLimit, simulatedTrading) {
1336
1104
  return this.request({
1337
1105
  method: "GET",
1338
1106
  path: path42,
1339
1107
  auth: "public",
1340
1108
  query,
1341
- rateLimit
1109
+ rateLimit,
1110
+ simulatedTrading
1342
1111
  });
1343
1112
  }
1344
1113
  async privateGet(path42, query, rateLimit) {
@@ -1487,17 +1256,13 @@ var OkxRestClient = class _OkxRestClient {
1487
1256
  * Security: validates Content-Type and enforces maxBytes limit.
1488
1257
  */
1489
1258
  async privatePostBinary(path42, body, opts) {
1490
- await this.ensureDoh();
1491
1259
  const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
1492
1260
  const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
1493
1261
  const bodyJson = body ? JSON.stringify(body) : "";
1494
1262
  const endpoint = `POST ${path42}`;
1495
- this.logRequest("POST", `${this.activeBaseUrl}${path42}`, "private");
1263
+ this.logRequest("POST", `${this.config.baseUrl}${path42}`, "private");
1496
1264
  const reqConfig = { method: "POST", path: path42, auth: "private" };
1497
1265
  const headers = this.buildHeaders(reqConfig, path42, bodyJson, getNow());
1498
- if (this.dohNode) {
1499
- headers.set("User-Agent", this.dohUserAgent);
1500
- }
1501
1266
  const t0 = Date.now();
1502
1267
  const response = await this.fetchBinary(path42, endpoint, headers, bodyJson, t0);
1503
1268
  const elapsed = Date.now() - t0;
@@ -1531,10 +1296,10 @@ var OkxRestClient = class _OkxRestClient {
1531
1296
  method: "POST",
1532
1297
  headers,
1533
1298
  body: bodyJson || void 0,
1534
- signal: AbortSignal.timeout(this.config.timeoutMs),
1535
- dispatcher: this.activeDispatcher
1299
+ signal: AbortSignal.timeout(this.config.timeoutMs)
1536
1300
  };
1537
- return await fetch(`${this.activeBaseUrl}${path42}`, fetchOptions);
1301
+ if (this.dispatcher) fetchOptions.dispatcher = this.dispatcher;
1302
+ return await fetch(`${this.config.baseUrl}${path42}`, fetchOptions);
1538
1303
  } catch (error) {
1539
1304
  if (this.config.verbose) {
1540
1305
  vlog(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
@@ -1556,7 +1321,8 @@ var OkxRestClient = class _OkxRestClient {
1556
1321
  if (reqConfig.auth === "private") {
1557
1322
  this.setAuthHeaders(headers, reqConfig.method, requestPath, bodyJson, timestamp);
1558
1323
  }
1559
- if (this.config.demo) {
1324
+ const useSimulated = reqConfig.simulatedTrading !== void 0 ? reqConfig.simulatedTrading : this.config.demo;
1325
+ if (useSimulated) {
1560
1326
  headers.set("x-simulated-trading", "1");
1561
1327
  }
1562
1328
  return headers;
@@ -1565,10 +1331,9 @@ var OkxRestClient = class _OkxRestClient {
1565
1331
  // JSON request
1566
1332
  // ---------------------------------------------------------------------------
1567
1333
  async request(reqConfig) {
1568
- await this.ensureDoh();
1569
1334
  const queryString = buildQueryString(reqConfig.query);
1570
1335
  const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
1571
- const url = `${this.activeBaseUrl}${requestPath}`;
1336
+ const url = `${this.config.baseUrl}${requestPath}`;
1572
1337
  const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
1573
1338
  const timestamp = getNow();
1574
1339
  this.logRequest(reqConfig.method, url, reqConfig.auth);
@@ -1576,9 +1341,6 @@ var OkxRestClient = class _OkxRestClient {
1576
1341
  await this.rateLimiter.consume(reqConfig.rateLimit);
1577
1342
  }
1578
1343
  const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
1579
- if (this.dohNode) {
1580
- headers.set("User-Agent", this.dohUserAgent);
1581
- }
1582
1344
  const t0 = Date.now();
1583
1345
  let response;
1584
1346
  try {
@@ -1586,20 +1348,13 @@ var OkxRestClient = class _OkxRestClient {
1586
1348
  method: reqConfig.method,
1587
1349
  headers,
1588
1350
  body: reqConfig.method === "POST" ? bodyJson : void 0,
1589
- signal: AbortSignal.timeout(this.config.timeoutMs),
1590
- dispatcher: this.activeDispatcher
1351
+ signal: AbortSignal.timeout(this.config.timeoutMs)
1591
1352
  };
1353
+ if (this.dispatcher) {
1354
+ fetchOptions.dispatcher = this.dispatcher;
1355
+ }
1592
1356
  response = await fetch(url, fetchOptions);
1593
1357
  } catch (error) {
1594
- if (!this.dohRetried) {
1595
- if (this.config.verbose) {
1596
- vlog(`Network failure, refreshing DoH: ${error instanceof Error ? error.message : String(error)}`);
1597
- }
1598
- const shouldRetry = await this.handleDohNetworkFailure();
1599
- if (shouldRetry && reqConfig.method === "GET") {
1600
- return this.request(reqConfig);
1601
- }
1602
- }
1603
1358
  if (this.config.verbose) {
1604
1359
  const elapsed2 = Date.now() - t0;
1605
1360
  const cause = error instanceof Error ? error.message : String(error);
@@ -1614,19 +1369,6 @@ var OkxRestClient = class _OkxRestClient {
1614
1369
  const rawText = await response.text();
1615
1370
  const elapsed = Date.now() - t0;
1616
1371
  const traceId = extractTraceId(response.headers);
1617
- if (this.directUnverified && !this.dohNode) {
1618
- this.directUnverified = false;
1619
- const { hostname: h } = new URL(this.config.baseUrl);
1620
- writeCache(h, {
1621
- mode: "direct",
1622
- node: null,
1623
- failedNodes: [],
1624
- updatedAt: Date.now()
1625
- });
1626
- if (this.config.verbose) {
1627
- vlog("DoH: direct connection succeeded, cached mode=direct");
1628
- }
1629
- }
1630
1372
  return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
1631
1373
  }
1632
1374
  };
@@ -1766,14 +1508,137 @@ var INDICATOR_BARS = [
1766
1508
  "1Wutc"
1767
1509
  ];
1768
1510
  var INDICATOR_CODE_OVERRIDES = {
1511
+ // Aliases
1512
+ "boll": "BB",
1513
+ // server supports BB not BOLL
1514
+ // Names where default rule produces underscores but backend uses no separator
1769
1515
  "rainbow": "BTCRAINBOW",
1770
- "range-filter": "RANGEFILTER",
1516
+ // default: RAINBOW
1771
1517
  "stoch-rsi": "STOCHRSI",
1772
- "pi-cycle-top": "PI_CYCLE_TOP",
1773
- "pi-cycle-bottom": "PI_CYCLE_BOTTOM",
1774
- // boll is an alias for bb; server supports BB not BOLL
1775
- "boll": "BB"
1518
+ // default: STOCH_RSI
1519
+ "bull-engulf": "BULLENGULF",
1520
+ // default: BULL_ENGULF
1521
+ "bear-engulf": "BEARENGULF",
1522
+ // default: BEAR_ENGULF
1523
+ "bull-harami": "BULLHARAMI",
1524
+ // default: BULL_HARAMI
1525
+ "bear-harami": "BEARHARAMI",
1526
+ // default: BEAR_HARAMI
1527
+ "bull-harami-cross": "BULLHARAMICROSS",
1528
+ // default: BULL_HARAMI_CROSS
1529
+ "bear-harami-cross": "BEARHARAMICROSS",
1530
+ // default: BEAR_HARAMI_CROSS
1531
+ "three-soldiers": "THREESOLDIERS",
1532
+ // default: THREE_SOLDIERS
1533
+ "three-crows": "THREECROWS",
1534
+ // default: THREE_CROWS
1535
+ "hanging-man": "HANGINGMAN",
1536
+ // default: HANGING_MAN
1537
+ "inverted-hammer": "INVERTEDH",
1538
+ // default: INVERTED_HAMMER (backend uses INVERTEDH)
1539
+ "shooting-star": "SHOOTINGSTAR",
1540
+ // default: SHOOTING_STAR
1541
+ "nvi-pvi": "NVIPVI",
1542
+ // default: NVI_PVI
1543
+ "top-long-short": "TOPLONGSHORT"
1544
+ // default: TOP_LONG_SHORT
1545
+ // Note: range-filter → RANGE_FILTER is correct via the default rule; no override needed.
1776
1546
  };
1547
+ var KNOWN_INDICATORS = [
1548
+ // Moving Averages
1549
+ { name: "ma", description: "Simple Moving Average" },
1550
+ { name: "ema", description: "Exponential Moving Average" },
1551
+ { name: "wma", description: "Weighted Moving Average" },
1552
+ { name: "dema", description: "Double Exponential Moving Average" },
1553
+ { name: "tema", description: "Triple Exponential Moving Average" },
1554
+ { name: "zlema", description: "Zero-Lag Exponential Moving Average" },
1555
+ { name: "hma", description: "Hull Moving Average" },
1556
+ { name: "kama", description: "Kaufman Adaptive Moving Average" },
1557
+ // Trend
1558
+ { name: "macd", description: "MACD" },
1559
+ { name: "sar", description: "Parabolic SAR" },
1560
+ { name: "adx", description: "Average Directional Index" },
1561
+ { name: "aroon", description: "Aroon Indicator" },
1562
+ { name: "cci", description: "Commodity Channel Index" },
1563
+ { name: "dpo", description: "Detrended Price Oscillator" },
1564
+ { name: "envelope", description: "Envelope" },
1565
+ { name: "halftrend", description: "HalfTrend" },
1566
+ { name: "alphatrend", description: "AlphaTrend" },
1567
+ // Momentum
1568
+ { name: "rsi", description: "Relative Strength Index" },
1569
+ { name: "stoch-rsi", description: "Stochastic RSI" },
1570
+ { name: "stoch", description: "Stochastic Oscillator" },
1571
+ { name: "roc", description: "Rate of Change" },
1572
+ { name: "mom", description: "Momentum" },
1573
+ { name: "ppo", description: "Price Percentage Oscillator" },
1574
+ { name: "trix", description: "TRIX" },
1575
+ { name: "ao", description: "Awesome Oscillator" },
1576
+ { name: "uo", description: "Ultimate Oscillator" },
1577
+ { name: "wr", description: "Williams %R" },
1578
+ // Volatility
1579
+ { name: "bb", description: "Bollinger Bands" },
1580
+ { name: "boll", description: "Bollinger Bands (alias for bb)" },
1581
+ { name: "bbwidth", description: "Bollinger Band Width" },
1582
+ { name: "bbpct", description: "Bollinger Band %B" },
1583
+ { name: "atr", description: "Average True Range" },
1584
+ { name: "keltner", description: "Keltner Channel" },
1585
+ { name: "donchian", description: "Donchian Channel" },
1586
+ { name: "hv", description: "Historical Volatility" },
1587
+ { name: "stddev", description: "Standard Deviation" },
1588
+ // Volume
1589
+ { name: "obv", description: "On-Balance Volume" },
1590
+ { name: "vwap", description: "Volume Weighted Average Price" },
1591
+ { name: "mvwap", description: "Moving VWAP" },
1592
+ { name: "cmf", description: "Chaikin Money Flow" },
1593
+ { name: "mfi", description: "Money Flow Index" },
1594
+ { name: "ad", description: "Accumulation/Distribution" },
1595
+ // Statistical
1596
+ { name: "lr", description: "Linear Regression" },
1597
+ { name: "slope", description: "Linear Regression Slope" },
1598
+ { name: "angle", description: "Linear Regression Angle" },
1599
+ { name: "variance", description: "Variance" },
1600
+ { name: "meandev", description: "Mean Deviation" },
1601
+ { name: "sigma", description: "Sigma" },
1602
+ { name: "stderr", description: "Standard Error" },
1603
+ // Custom
1604
+ { name: "kdj", description: "KDJ Stochastic Oscillator" },
1605
+ { name: "supertrend", description: "Supertrend" },
1606
+ // Ichimoku
1607
+ { name: "tenkan", description: "Ichimoku Tenkan-sen (Conversion Line)" },
1608
+ { name: "kijun", description: "Ichimoku Kijun-sen (Base Line)" },
1609
+ { name: "senkoa", description: "Ichimoku Senkou Span A (Leading Span A)" },
1610
+ { name: "senkob", description: "Ichimoku Senkou Span B (Leading Span B)" },
1611
+ { name: "chikou", description: "Ichimoku Chikou Span (Lagging Span)" },
1612
+ // Candlestick Patterns
1613
+ { name: "doji", description: "Doji candlestick pattern" },
1614
+ { name: "bull-engulf", description: "Bullish Engulfing pattern" },
1615
+ { name: "bear-engulf", description: "Bearish Engulfing pattern" },
1616
+ { name: "bull-harami", description: "Bullish Harami pattern" },
1617
+ { name: "bear-harami", description: "Bearish Harami pattern" },
1618
+ { name: "bull-harami-cross", description: "Bullish Harami Cross pattern" },
1619
+ { name: "bear-harami-cross", description: "Bearish Harami Cross pattern" },
1620
+ { name: "three-soldiers", description: "Three White Soldiers pattern" },
1621
+ { name: "three-crows", description: "Three Black Crows pattern" },
1622
+ { name: "hanging-man", description: "Hanging Man pattern" },
1623
+ { name: "inverted-hammer", description: "Inverted Hammer pattern" },
1624
+ { name: "shooting-star", description: "Shooting Star pattern" },
1625
+ // Bitcoin On-Chain
1626
+ { name: "ahr999", description: "AHR999 Bitcoin accumulation index" },
1627
+ { name: "rainbow", description: "Bitcoin Rainbow Chart" },
1628
+ // Other
1629
+ { name: "fisher", description: "Fisher Transform" },
1630
+ { name: "nvi-pvi", description: "Negative/Positive Volume Index (returns both)" },
1631
+ { name: "pmax", description: "PMAX" },
1632
+ { name: "qqe", description: "QQE Mod" },
1633
+ { name: "tdi", description: "Traders Dynamic Index" },
1634
+ { name: "waddah", description: "Waddah Attar Explosion" },
1635
+ { name: "range-filter", description: "Range Filter" },
1636
+ { name: "cho", description: "Chande Momentum Oscillator" },
1637
+ { name: "tr", description: "True Range" },
1638
+ { name: "tp", description: "Typical Price" },
1639
+ { name: "mp", description: "Median Price" },
1640
+ { name: "top-long-short", description: "Top Trader Long/Short Ratio (timeframe-independent)" }
1641
+ ];
1777
1642
  function resolveIndicatorCode(name) {
1778
1643
  const lower = name.toLowerCase();
1779
1644
  return INDICATOR_CODE_OVERRIDES[lower] ?? name.toUpperCase().replace(/-/g, "_");
@@ -1800,7 +1665,7 @@ function registerIndicatorTools() {
1800
1665
  },
1801
1666
  indicator: {
1802
1667
  type: "string",
1803
- description: "Indicator name (case-insensitive). Examples: ma, ema, rsi, macd, bb, kdj, supertrend, ahr999, rainbow, pi-cycle-top, pi-cycle-bottom, mayer, envelope, halftrend, alphatrend, pmax, waddah, tdi, qqe, range-filter"
1668
+ description: "Indicator name (case-insensitive). Call market_list_indicators to see all supported names."
1804
1669
  },
1805
1670
  bar: {
1806
1671
  type: "string",
@@ -1857,6 +1722,14 @@ function registerIndicatorTools() {
1857
1722
  );
1858
1723
  return normalizeResponse(response);
1859
1724
  }
1725
+ },
1726
+ {
1727
+ name: "market_list_indicators",
1728
+ module: "market",
1729
+ description: "List all supported technical indicator names and descriptions. Call this before market_get_indicator to discover valid indicator names. No credentials required.",
1730
+ isWrite: false,
1731
+ inputSchema: { type: "object", properties: {} },
1732
+ handler: async () => ({ data: KNOWN_INDICATORS })
1860
1733
  }
1861
1734
  ];
1862
1735
  }
@@ -2454,6 +2327,118 @@ function registerAccountTools() {
2454
2327
  }
2455
2328
  ];
2456
2329
  }
2330
+ function extractInstrumentParams(instId, data) {
2331
+ const instruments = Array.isArray(data) ? data : [];
2332
+ if (instruments.length === 0) {
2333
+ throw new Error(`Failed to fetch instrument info for ${instId}: empty instrument list. Cannot determine ctVal for conversion.`);
2334
+ }
2335
+ const inst = instruments[0];
2336
+ const ctValStr = String(inst.ctVal ?? "");
2337
+ const ctVal = parseFloat(ctValStr);
2338
+ if (!isFinite(ctVal) || ctVal <= 0) {
2339
+ throw new Error(`Invalid ctVal "${ctValStr}" for ${instId}. ctVal must be a positive number for conversion.`);
2340
+ }
2341
+ const minSzStr = String(inst.minSz ?? "1");
2342
+ const minSz = parseFloat(minSzStr);
2343
+ if (!isFinite(minSz) || minSz <= 0) {
2344
+ throw new Error(`Invalid minSz "${minSzStr}" for ${instId}. minSz must be a positive number for conversion.`);
2345
+ }
2346
+ const lotSzStr = String(inst.lotSz ?? "1");
2347
+ const lotSz = parseFloat(lotSzStr);
2348
+ if (!isFinite(lotSz) || lotSz <= 0) {
2349
+ throw new Error(`Invalid lotSz "${lotSzStr}" for ${instId}. lotSz must be a positive number for conversion.`);
2350
+ }
2351
+ return { ctVal, ctValStr, minSz, minSzStr, lotSz, lotSzStr };
2352
+ }
2353
+ function extractLastPx(instId, data) {
2354
+ const tickers = Array.isArray(data) ? data : [];
2355
+ if (tickers.length === 0) {
2356
+ throw new Error(`Failed to fetch ticker price for ${instId}: empty ticker response. Cannot determine last price for conversion.`);
2357
+ }
2358
+ const lastStr = String(tickers[0].last ?? "");
2359
+ const lastPx = parseFloat(lastStr);
2360
+ if (!isFinite(lastPx) || lastPx <= 0) {
2361
+ throw new Error(`Invalid last price "${lastStr}" for ${instId}. Last price must be a positive number for conversion.`);
2362
+ }
2363
+ return { lastPx, lastStr };
2364
+ }
2365
+ function extractLeverage(instId, mgnMode, data) {
2366
+ const leverageData = Array.isArray(data) ? data : [];
2367
+ if (leverageData.length === 0) {
2368
+ throw new Error(
2369
+ `Failed to fetch leverage info for ${instId} (mgnMode=${mgnMode}): empty response. Cannot determine leverage for margin conversion. Please set leverage first using set_leverage.`
2370
+ );
2371
+ }
2372
+ const leverStr = String(leverageData[0].lever ?? "1");
2373
+ const lever = parseFloat(leverStr);
2374
+ if (!isFinite(lever) || lever <= 0) {
2375
+ throw new Error(`Invalid leverage "${leverStr}" for ${instId}. Leverage must be a positive number for margin conversion.`);
2376
+ }
2377
+ return { lever, leverStr };
2378
+ }
2379
+ function computeContracts(p) {
2380
+ const { instId, sz, isMarginMode, inst, lastPx, lastStr, lever, leverStr } = p;
2381
+ const { ctVal, ctValStr, minSz, minSzStr, lotSz, lotSzStr } = inst;
2382
+ const userAmount = parseFloat(sz);
2383
+ const contractValue = ctVal * lastPx;
2384
+ const effectiveNotional = isMarginMode ? userAmount * lever : userAmount;
2385
+ const lotSzDecimals = lotSzStr.includes(".") ? lotSzStr.split(".")[1].length : 0;
2386
+ const precision = 10 ** (lotSzDecimals + 4);
2387
+ const rawContracts = Math.round(effectiveNotional / contractValue * precision) / precision;
2388
+ const rawLots = Math.round(rawContracts / lotSz * precision) / precision;
2389
+ const contractsRounded = parseFloat((Math.floor(rawLots) * lotSz).toFixed(lotSzDecimals));
2390
+ if (contractsRounded < minSz) {
2391
+ const minAmount = isMarginMode ? (minSz * contractValue / lever).toFixed(2) : (minSz * contractValue).toFixed(2);
2392
+ const unit = isMarginMode ? "USDT margin" : "USDT";
2393
+ throw new Error(
2394
+ `sz=${sz} ${unit} is too small for ${instId}. Minimum order size is ${minSzStr} contracts (\u2248 ${minAmount} ${unit}). (ctVal=${ctValStr}, lastPx=${lastStr}, minSz=${minSzStr}, lotSz=${lotSzStr}` + (isMarginMode ? `, lever=${leverStr}` : "") + `).
2395
+ `
2396
+ );
2397
+ }
2398
+ const contractsStr = contractsRounded.toFixed(lotSzDecimals);
2399
+ const conversionNote = isMarginMode ? `Converting ${sz} USDT margin (${leverStr}x leverage) \u2192 ${contractsStr} contracts (notional value \u2248 ${(contractsRounded * contractValue).toFixed(2)} USDT, ctVal=${ctValStr}, lastPx=${lastStr}, lever=${leverStr}, minSz=${minSzStr}, lotSz=${lotSzStr})` : `Converting ${sz} USDT \u2192 ${contractsStr} contracts (ctVal=${ctValStr}, lastPx=${lastStr}, minSz=${minSzStr}, lotSz=${lotSzStr})`;
2400
+ return { contractsStr, conversionNote };
2401
+ }
2402
+ async function resolveQuoteCcySz(instId, sz, tgtCcy, instType, client, tdMode) {
2403
+ if (tgtCcy === void 0 || tgtCcy === "base_ccy") {
2404
+ return { sz, tgtCcy, conversionNote: void 0 };
2405
+ }
2406
+ if (tgtCcy !== "quote_ccy" && tgtCcy !== "margin") {
2407
+ throw new ValidationError(
2408
+ `Unknown tgtCcy value "${tgtCcy}". Valid values: base_ccy, quote_ccy, margin.`,
2409
+ `Check the --tgtCcy flag. Use base_ccy (default, sz in contracts), quote_ccy (sz in USDT notional), or margin (sz in USDT margin cost).`
2410
+ );
2411
+ }
2412
+ const isMarginMode = tgtCcy === "margin";
2413
+ if (isMarginMode && !tdMode) {
2414
+ throw new Error(
2415
+ "tdMode (cross or isolated) is required when tgtCcy=margin. Cannot determine leverage without knowing the margin mode."
2416
+ );
2417
+ }
2418
+ const mgnMode = tdMode === "cross" ? "cross" : "isolated";
2419
+ const fetchPromises = [
2420
+ client.publicGet("/api/v5/public/instruments", { instType, instId }),
2421
+ client.publicGet("/api/v5/market/ticker", { instId })
2422
+ ];
2423
+ if (isMarginMode) {
2424
+ fetchPromises.push(client.privateGet("/api/v5/account/leverage-info", { instId, mgnMode }));
2425
+ }
2426
+ const results = await Promise.all(fetchPromises);
2427
+ const inst = extractInstrumentParams(instId, results[0].data);
2428
+ const { lastPx, lastStr } = extractLastPx(instId, results[1].data);
2429
+ const { lever, leverStr } = isMarginMode ? extractLeverage(instId, mgnMode, results[2].data) : { lever: 1, leverStr: "1" };
2430
+ const { contractsStr, conversionNote } = computeContracts({
2431
+ instId,
2432
+ sz,
2433
+ isMarginMode,
2434
+ inst,
2435
+ lastPx,
2436
+ lastStr,
2437
+ lever,
2438
+ leverStr
2439
+ });
2440
+ return { sz: contractsStr, tgtCcy: void 0, conversionNote };
2441
+ }
2457
2442
  function registerAlgoTradeTools() {
2458
2443
  return [
2459
2444
  {
@@ -2532,8 +2517,8 @@ function registerAlgoTradeTools() {
2532
2517
  },
2533
2518
  tgtCcy: {
2534
2519
  type: "string",
2535
- enum: ["base_ccy", "quote_ccy"],
2536
- description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT"
2520
+ enum: ["base_ccy", "quote_ccy", "margin"],
2521
+ description: "Size unit. base_ccy(default): sz in contracts; quote_ccy: sz in USDT notional value; margin: sz in USDT margin cost (actual position = sz * leverage)"
2537
2522
  },
2538
2523
  reduceOnly: {
2539
2524
  type: "boolean",
@@ -2549,6 +2534,14 @@ function registerAlgoTradeTools() {
2549
2534
  handler: async (rawArgs, context) => {
2550
2535
  const args = asRecord(rawArgs);
2551
2536
  const reduceOnly = args.reduceOnly;
2537
+ const resolved = await resolveQuoteCcySz(
2538
+ requireString(args, "instId"),
2539
+ requireString(args, "sz"),
2540
+ readString(args, "tgtCcy"),
2541
+ "SWAP",
2542
+ context.client,
2543
+ readString(args, "tdMode")
2544
+ );
2552
2545
  const response = await context.client.privatePost(
2553
2546
  "/api/v5/trade/order-algo",
2554
2547
  compactObject({
@@ -2557,8 +2550,8 @@ function registerAlgoTradeTools() {
2557
2550
  side: requireString(args, "side"),
2558
2551
  posSide: readString(args, "posSide"),
2559
2552
  ordType: requireString(args, "ordType"),
2560
- sz: requireString(args, "sz"),
2561
- tgtCcy: readString(args, "tgtCcy"),
2553
+ sz: resolved.sz,
2554
+ tgtCcy: resolved.tgtCcy,
2562
2555
  tpTriggerPx: readString(args, "tpTriggerPx"),
2563
2556
  tpOrdPx: readString(args, "tpOrdPx"),
2564
2557
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -2574,7 +2567,11 @@ function registerAlgoTradeTools() {
2574
2567
  }),
2575
2568
  privateRateLimit("swap_place_algo_order", 20)
2576
2569
  );
2577
- return normalizeResponse(response);
2570
+ const result = normalizeResponse(response);
2571
+ if (resolved.conversionNote) {
2572
+ result._conversion = resolved.conversionNote;
2573
+ }
2574
+ return result;
2578
2575
  }
2579
2576
  },
2580
2577
  {
@@ -2865,8 +2862,8 @@ function registerFuturesAlgoTools() {
2865
2862
  },
2866
2863
  tgtCcy: {
2867
2864
  type: "string",
2868
- enum: ["base_ccy", "quote_ccy"],
2869
- description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT"
2865
+ enum: ["base_ccy", "quote_ccy", "margin"],
2866
+ description: "Size unit. base_ccy(default): sz in contracts; quote_ccy: sz in USDT notional value; margin: sz in USDT margin cost (actual position = sz * leverage)"
2870
2867
  },
2871
2868
  reduceOnly: {
2872
2869
  type: "boolean",
@@ -2882,6 +2879,14 @@ function registerFuturesAlgoTools() {
2882
2879
  handler: async (rawArgs, context) => {
2883
2880
  const args = asRecord(rawArgs);
2884
2881
  const reduceOnly = args.reduceOnly;
2882
+ const resolved = await resolveQuoteCcySz(
2883
+ requireString(args, "instId"),
2884
+ requireString(args, "sz"),
2885
+ readString(args, "tgtCcy"),
2886
+ "FUTURES",
2887
+ context.client,
2888
+ readString(args, "tdMode")
2889
+ );
2885
2890
  const response = await context.client.privatePost(
2886
2891
  "/api/v5/trade/order-algo",
2887
2892
  compactObject({
@@ -2890,8 +2895,8 @@ function registerFuturesAlgoTools() {
2890
2895
  side: requireString(args, "side"),
2891
2896
  posSide: readString(args, "posSide"),
2892
2897
  ordType: requireString(args, "ordType"),
2893
- sz: requireString(args, "sz"),
2894
- tgtCcy: readString(args, "tgtCcy"),
2898
+ sz: resolved.sz,
2899
+ tgtCcy: resolved.tgtCcy,
2895
2900
  tpTriggerPx: readString(args, "tpTriggerPx"),
2896
2901
  tpOrdPx: readString(args, "tpOrdPx"),
2897
2902
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -2907,7 +2912,11 @@ function registerFuturesAlgoTools() {
2907
2912
  }),
2908
2913
  privateRateLimit("futures_place_algo_order", 20)
2909
2914
  );
2910
- return normalizeResponse(response);
2915
+ const result = normalizeResponse(response);
2916
+ if (resolved.conversionNote) {
2917
+ result._conversion = resolved.conversionNote;
2918
+ }
2919
+ return result;
2911
2920
  }
2912
2921
  },
2913
2922
  {
@@ -3238,19 +3247,19 @@ function safeWriteFile(targetDir, fileName, data) {
3238
3247
  throw new Error(`Invalid file name: "${fileName}"`);
3239
3248
  }
3240
3249
  const resolvedDir = resolve(targetDir);
3241
- const filePath = join3(resolvedDir, safeName);
3250
+ const filePath = join(resolvedDir, safeName);
3242
3251
  const resolvedPath = resolve(filePath);
3243
3252
  if (!resolvedPath.startsWith(resolvedDir + sep)) {
3244
3253
  throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
3245
3254
  }
3246
- mkdirSync2(resolvedDir, { recursive: true });
3255
+ mkdirSync(resolvedDir, { recursive: true });
3247
3256
  const tmpPath = `${resolvedPath}.${randomUUID()}.tmp`;
3248
3257
  try {
3249
- writeFileSync2(tmpPath, data);
3250
- renameSync2(tmpPath, resolvedPath);
3258
+ writeFileSync(tmpPath, data);
3259
+ renameSync(tmpPath, resolvedPath);
3251
3260
  } catch (err) {
3252
3261
  try {
3253
- unlinkSync2(tmpPath);
3262
+ unlinkSync(tmpPath);
3254
3263
  } catch {
3255
3264
  }
3256
3265
  throw err;
@@ -3266,13 +3275,14 @@ function validateZipEntryPath(targetDir, entryName) {
3266
3275
  return resolvedEntry;
3267
3276
  }
3268
3277
  var MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024;
3269
- async function downloadSkillZip(client, name, targetDir) {
3278
+ async function downloadSkillZip(client, name, targetDir, format = "zip") {
3270
3279
  const result = await client.privatePostBinary(
3271
3280
  "/api/v5/skill/download",
3272
3281
  { name },
3273
3282
  { maxBytes: MAX_DOWNLOAD_BYTES }
3274
3283
  );
3275
- const fileName = `${name}.zip`;
3284
+ const ext = format === "skill" ? "skill" : "zip";
3285
+ const fileName = `${name}.${ext}`;
3276
3286
  const filePath = safeWriteFile(targetDir, fileName, result.data);
3277
3287
  return filePath;
3278
3288
  }
@@ -3315,7 +3325,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3315
3325
  const maxFiles = limits?.maxFiles ?? DEFAULT_MAX_FILES;
3316
3326
  const maxCompressionRatio = limits?.maxCompressionRatio ?? DEFAULT_MAX_COMPRESSION_RATIO;
3317
3327
  const resolvedTarget = resolve2(targetDir);
3318
- mkdirSync3(resolvedTarget, { recursive: true });
3328
+ mkdirSync2(resolvedTarget, { recursive: true });
3319
3329
  return new Promise((resolvePromise, reject) => {
3320
3330
  yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
3321
3331
  if (err) return reject(err);
@@ -3338,7 +3348,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3338
3348
  zipfile.close();
3339
3349
  return reject(streamErr);
3340
3350
  }
3341
- mkdirSync3(dirname2(resolvedPath), { recursive: true });
3351
+ mkdirSync2(dirname(resolvedPath), { recursive: true });
3342
3352
  const writeStream = createWriteStream(resolvedPath);
3343
3353
  readStream.pipe(writeStream);
3344
3354
  writeStream.on("close", () => zipfile.readEntry());
@@ -3354,11 +3364,11 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3354
3364
  });
3355
3365
  }
3356
3366
  function readMetaJson(contentDir) {
3357
- const metaPath = join4(contentDir, "_meta.json");
3367
+ const metaPath = join2(contentDir, "_meta.json");
3358
3368
  if (!existsSync(metaPath)) {
3359
3369
  throw new Error(`_meta.json not found in ${contentDir}. Invalid skill package.`);
3360
3370
  }
3361
- const raw = readFileSync2(metaPath, "utf-8");
3371
+ const raw = readFileSync(metaPath, "utf-8");
3362
3372
  let parsed;
3363
3373
  try {
3364
3374
  parsed = JSON.parse(raw);
@@ -3380,26 +3390,26 @@ function readMetaJson(contentDir) {
3380
3390
  };
3381
3391
  }
3382
3392
  function validateSkillMdExists(contentDir) {
3383
- const skillMdPath = join4(contentDir, "SKILL.md");
3393
+ const skillMdPath = join2(contentDir, "SKILL.md");
3384
3394
  if (!existsSync(skillMdPath)) {
3385
3395
  throw new Error(`SKILL.md not found in ${contentDir}. Invalid skill package.`);
3386
3396
  }
3387
3397
  }
3388
- var DEFAULT_REGISTRY_PATH = join5(homedir3(), ".okx", "skills", "registry.json");
3398
+ var DEFAULT_REGISTRY_PATH = join3(homedir(), ".okx", "skills", "registry.json");
3389
3399
  function readRegistry(registryPath = DEFAULT_REGISTRY_PATH) {
3390
3400
  if (!existsSync2(registryPath)) {
3391
3401
  return { version: 1, skills: {} };
3392
3402
  }
3393
3403
  try {
3394
- const raw = readFileSync3(registryPath, "utf-8");
3404
+ const raw = readFileSync2(registryPath, "utf-8");
3395
3405
  return JSON.parse(raw);
3396
3406
  } catch {
3397
3407
  return { version: 1, skills: {} };
3398
3408
  }
3399
3409
  }
3400
3410
  function writeRegistry(registry, registryPath = DEFAULT_REGISTRY_PATH) {
3401
- mkdirSync4(dirname3(registryPath), { recursive: true });
3402
- writeFileSync3(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
3411
+ mkdirSync3(dirname2(registryPath), { recursive: true });
3412
+ writeFileSync2(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
3403
3413
  }
3404
3414
  function upsertSkillRecord(meta, registryPath = DEFAULT_REGISTRY_PATH) {
3405
3415
  const registry = readRegistry(registryPath);
@@ -3472,7 +3482,7 @@ function registerSkillsTools() {
3472
3482
  {
3473
3483
  name: "skills_download",
3474
3484
  module: "skills",
3475
- 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.",
3485
+ description: "Download a skill package 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 \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.",
3476
3486
  inputSchema: {
3477
3487
  type: "object",
3478
3488
  properties: {
@@ -3482,7 +3492,12 @@ function registerSkillsTools() {
3482
3492
  },
3483
3493
  targetDir: {
3484
3494
  type: "string",
3485
- description: "Directory path where the zip file will be saved"
3495
+ description: "Directory path where the file will be saved"
3496
+ },
3497
+ format: {
3498
+ type: "string",
3499
+ description: "Output file format: 'zip' or 'skill' (default: 'skill')",
3500
+ enum: ["zip", "skill"]
3486
3501
  }
3487
3502
  },
3488
3503
  required: ["name", "targetDir"],
@@ -3515,7 +3530,8 @@ async function handleSearch(args, ctx) {
3515
3530
  async function handleDownload(args, ctx) {
3516
3531
  const name = String(args.name);
3517
3532
  const targetDir = String(args.targetDir);
3518
- const filePath = await downloadSkillZip(ctx.client, name, targetDir);
3533
+ const format = args.format === "zip" ? "zip" : "skill";
3534
+ const filePath = await downloadSkillZip(ctx.client, name, targetDir, format);
3519
3535
  return {
3520
3536
  endpoint: "POST /api/v5/skill/download",
3521
3537
  requestTime: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4078,7 +4094,7 @@ function registerEarnTools() {
4078
4094
  {
4079
4095
  name: "earn_get_savings_balance",
4080
4096
  module: "earn.savings",
4081
- description: "Get Simple Earn (savings/flexible earn) balance. Returns current holdings, lent amount, pending interest, and the user's set rate. Response fields: amt (total held), loanAmt (actively lent), pendingAmt (awaiting match), earnings (cumulative interest), rate (user's own minimum lending rate setting \u2014 NOT market yield, NOT APY). To get the actual market lending rate, call earn_get_lending_rate_history instead.",
4097
+ description: "Get Simple Earn (savings/flexible earn) balance. Returns current holdings for all currencies or a specific one. To show market rates alongside balance (\u5E02\u573A\u5747\u5229\u7387), call earn_get_lending_rate_history. earn_get_lending_rate_history also returns fixed-term (\u5B9A\u671F) product offers, so one call gives a complete view of both flexible and fixed options. Do NOT use for fixed-term (\u5B9A\u671F) order queries \u2014 use earn_get_fixed_order_list instead.",
4082
4098
  isWrite: false,
4083
4099
  inputSchema: {
4084
4100
  type: "object",
@@ -4099,6 +4115,43 @@ function registerEarnTools() {
4099
4115
  return normalizeResponse(response);
4100
4116
  }
4101
4117
  },
4118
+ {
4119
+ name: "earn_get_fixed_order_list",
4120
+ module: "earn.savings",
4121
+ description: "Get Simple Earn Fixed (\u5B9A\u671F\u8D5A\u5E01) lending order list. Returns orders sorted by creation time descending. Use this to check status of fixed-term lending orders (pending/earning/expired/settled/cancelled). Do NOT use for flexible earn balance \u2014 use earn_get_savings_balance instead. If the result is empty, do NOT display any fixed-term section in the output.",
4122
+ isWrite: false,
4123
+ inputSchema: {
4124
+ type: "object",
4125
+ properties: {
4126
+ ccy: {
4127
+ type: "string",
4128
+ description: "Currency, e.g. USDT. Omit for all."
4129
+ },
4130
+ state: {
4131
+ type: "string",
4132
+ description: "Order state: pending (\u5339\u914D\u4E2D), earning (\u8D5A\u5E01\u4E2D), expired (\u903E\u671F), settled (\u5DF2\u7ED3\u7B97), cancelled (\u5DF2\u64A4\u9500). Omit for all."
4133
+ }
4134
+ }
4135
+ },
4136
+ handler: async (rawArgs, context) => {
4137
+ const args = asRecord(rawArgs);
4138
+ const response = await context.client.privateGet(
4139
+ "/api/v5/finance/simple-earn-fixed/order-list",
4140
+ compactObject({
4141
+ ccy: readString(args, "ccy"),
4142
+ state: readString(args, "state")
4143
+ }),
4144
+ privateRateLimit("earn_get_fixed_order_list", 3)
4145
+ );
4146
+ const result = normalizeResponse(response);
4147
+ if (Array.isArray(result["data"])) {
4148
+ result["data"] = result["data"].map(
4149
+ ({ finalSettlementDate: _, ...rest }) => rest
4150
+ );
4151
+ }
4152
+ return result;
4153
+ }
4154
+ },
4102
4155
  {
4103
4156
  name: "earn_savings_purchase",
4104
4157
  module: "earn.savings",
@@ -4243,10 +4296,112 @@ function registerEarnTools() {
4243
4296
  return normalizeResponse(response);
4244
4297
  }
4245
4298
  },
4299
+ {
4300
+ name: "earn_fixed_purchase",
4301
+ module: "earn.savings",
4302
+ description: "Purchase Simple Earn Fixed (\u5B9A\u671F) product, two-step flow. First call (confirm omitted or false): returns purchase preview with product details and risk warning. Preview offer fields: lendQuota = remaining quota (\u5269\u4F59\u989D\u5EA6), soldOut = whether product is sold out (lendQuota is 0). YOU MUST display the 'warning' field from the preview response to the user VERBATIM before asking for confirmation \u2014 do NOT omit or summarize it. Second call (confirm=true): executes the purchase. Only proceed after the user explicitly confirms. IMPORTANT: Orders in 'pending' (\u5339\u914D\u4E2D) state can still be cancelled via earn_fixed_redeem; once the status changes to 'earning' (\u8D5A\u5E01\u4E2D), funds are LOCKED until maturity \u2014 no early redemption allowed.",
4303
+ isWrite: true,
4304
+ inputSchema: {
4305
+ type: "object",
4306
+ properties: {
4307
+ ccy: {
4308
+ type: "string",
4309
+ description: "Currency, e.g. USDT"
4310
+ },
4311
+ amt: {
4312
+ type: "string",
4313
+ description: "Purchase amount"
4314
+ },
4315
+ term: {
4316
+ type: "string",
4317
+ description: "Term, e.g. 90D"
4318
+ },
4319
+ confirm: {
4320
+ type: "boolean",
4321
+ description: "Omit or false on the first call to preview the purchase details; set to true on the second call to execute after user confirms."
4322
+ }
4323
+ },
4324
+ required: ["ccy", "amt", "term"]
4325
+ },
4326
+ handler: async (rawArgs, context) => {
4327
+ const args = asRecord(rawArgs);
4328
+ const ccy = requireString(args, "ccy");
4329
+ const amt = requireString(args, "amt");
4330
+ const term = requireString(args, "term");
4331
+ const confirm = readBoolean(args, "confirm") ?? false;
4332
+ if (!confirm) {
4333
+ const [rateResponse, fixedResponse] = await Promise.all([
4334
+ context.client.publicGet(
4335
+ "/api/v5/finance/savings/lending-rate-history",
4336
+ compactObject({ ccy, limit: 1 }),
4337
+ publicRateLimit("earn_get_lending_rate_history", 6)
4338
+ ),
4339
+ context.client.privateGet(
4340
+ "/api/v5/finance/simple-earn-fixed/offers",
4341
+ compactObject({ ccy }),
4342
+ privateRateLimit("earn_fixed_purchase_preview_offers", 2)
4343
+ ).catch(() => null)
4344
+ ]);
4345
+ const rateResult = normalizeResponse(rateResponse);
4346
+ const fixedResult = fixedResponse ? normalizeResponse(fixedResponse) : { data: [] };
4347
+ const rateArr = Array.isArray(rateResult["data"]) ? rateResult["data"] : [];
4348
+ const allOffers = Array.isArray(fixedResult["data"]) ? fixedResult["data"] : [];
4349
+ const matchedOffer = allOffers.find(
4350
+ (o) => o["term"] === term && o["ccy"] === ccy
4351
+ );
4352
+ const { borrowingOrderQuota: _, ...offerWithoutTotal } = matchedOffer ?? {};
4353
+ const offerWithSoldOut = matchedOffer ? { ...offerWithoutTotal, soldOut: offerWithoutTotal["lendQuota"] === "0" } : null;
4354
+ return {
4355
+ preview: true,
4356
+ ccy,
4357
+ amt,
4358
+ term,
4359
+ offer: offerWithSoldOut,
4360
+ currentFlexibleRate: rateArr[0]?.["lendingRate"] ?? null,
4361
+ warning: "\u26A0\uFE0F Orders still in 'pending' state can be cancelled before matching completes. Once the status changes to 'earning', funds are LOCKED until maturity \u2014 early redemption is NOT allowed. Please call again with confirm=true to proceed."
4362
+ };
4363
+ }
4364
+ assertNotDemo(context.config, "earn_fixed_purchase");
4365
+ const response = await context.client.privatePost(
4366
+ "/api/v5/finance/simple-earn-fixed/purchase",
4367
+ { ccy, amt, term },
4368
+ privateRateLimit("earn_fixed_purchase", 2)
4369
+ );
4370
+ return normalizeResponse(response);
4371
+ }
4372
+ },
4373
+ {
4374
+ name: "earn_fixed_redeem",
4375
+ module: "earn.savings",
4376
+ description: "Redeem Simple Earn Fixed (\u5B9A\u671F\u8D5A\u5E01) order. [CAUTION] Redeems a fixed-term lending order. Always redeems the full order amount. Only orders in 'pending' (\u5339\u914D\u4E2D) state can be redeemed \u2014 orders in 'earning' state are locked until maturity and cannot be redeemed early. Do NOT use for flexible earn redemption \u2014 use earn_savings_redeem instead.",
4377
+ isWrite: true,
4378
+ inputSchema: {
4379
+ type: "object",
4380
+ properties: {
4381
+ reqId: {
4382
+ type: "string",
4383
+ description: "Request ID of the fixed-term order to redeem"
4384
+ }
4385
+ },
4386
+ required: ["reqId"]
4387
+ },
4388
+ handler: async (rawArgs, context) => {
4389
+ assertNotDemo(context.config, "earn_fixed_redeem");
4390
+ const args = asRecord(rawArgs);
4391
+ const response = await context.client.privatePost(
4392
+ "/api/v5/finance/simple-earn-fixed/redeem",
4393
+ {
4394
+ reqId: requireString(args, "reqId")
4395
+ },
4396
+ privateRateLimit("earn_fixed_redeem", 2)
4397
+ );
4398
+ return normalizeResponse(response);
4399
+ }
4400
+ },
4246
4401
  {
4247
4402
  name: "earn_get_lending_rate_history",
4248
4403
  module: "earn.savings",
4249
- description: "Query Simple Earn lending rates. Public endpoint (no API key required). Use this tool when the user asks about current or historical lending rates for Simple Earn, or when displaying savings balance with market rate context. Response fields per record: rate (market lending rate \u2014 the rate borrowers pay this period; user's minimum setting must be \u2264 this to be eligible), lendingRate (actual yield received by lenders; stablecoins e.g. USDT/USDC only: subject to pro-rata dilution \u2014 when eligible supply exceeds borrowing demand total interest is shared so lendingRate < rate; non-stablecoins: lendingRate = rate, no dilution; always use lendingRate as the true APY to show users), ts (settlement timestamp ms). To get current APY: use limit=1 and read lendingRate.",
4404
+ description: "Query Simple Earn lending rates and fixed-term offers. Use this tool when the user asks about Simple Earn products, current or historical lending rates, or when displaying savings balance with market rate context (\u5E02\u573A\u5747\u5229\u7387). Returns lending rate history (lendingRate field, newest-first) AND available fixed-term (\u5B9A\u671F) offers with APR, term, min amount, and quota \u2014 one call gives a complete view of both flexible and fixed options. In fixedOffers: lendQuota = remaining quota (\u5269\u4F59\u989D\u5EA6), soldOut = whether product is sold out (lendQuota is 0). To get current flexible APY: use limit=1 and read lendingRate.",
4250
4405
  isWrite: false,
4251
4406
  inputSchema: {
4252
4407
  type: "object",
@@ -4265,23 +4420,45 @@ function registerEarnTools() {
4265
4420
  },
4266
4421
  limit: {
4267
4422
  type: "number",
4268
- description: "Max results (default 100)"
4423
+ description: "Max results (default 7)"
4269
4424
  }
4270
4425
  }
4271
4426
  },
4272
4427
  handler: async (rawArgs, context) => {
4273
4428
  const args = asRecord(rawArgs);
4274
- const response = await context.client.publicGet(
4275
- "/api/v5/finance/savings/lending-rate-history",
4276
- compactObject({
4277
- ccy: readString(args, "ccy"),
4278
- after: readString(args, "after"),
4279
- before: readString(args, "before"),
4280
- limit: readNumber(args, "limit")
4281
- }),
4282
- publicRateLimit("earn_get_lending_rate_history", 6)
4283
- );
4284
- return normalizeResponse(response);
4429
+ const ccy = readString(args, "ccy");
4430
+ const [rateResponse, fixedResponse] = await Promise.all([
4431
+ context.client.publicGet(
4432
+ "/api/v5/finance/savings/lending-rate-history",
4433
+ compactObject({
4434
+ ccy,
4435
+ after: readString(args, "after"),
4436
+ before: readString(args, "before"),
4437
+ limit: readNumber(args, "limit") ?? 7
4438
+ }),
4439
+ publicRateLimit("earn_get_lending_rate_history", 6)
4440
+ ),
4441
+ context.client.privateGet(
4442
+ "/api/v5/finance/simple-earn-fixed/offers",
4443
+ compactObject({ ccy }),
4444
+ privateRateLimit("earn_get_lending_rate_history_fixed", 2)
4445
+ ).catch(() => null)
4446
+ ]);
4447
+ const rateResult = normalizeResponse(rateResponse);
4448
+ const rateData = Array.isArray(rateResult["data"]) ? rateResult["data"].map(
4449
+ ({ rate: _, ...rest }) => rest
4450
+ ) : [];
4451
+ const fixedResult = fixedResponse ? normalizeResponse(fixedResponse) : { data: [] };
4452
+ const allOffers = fixedResult["data"] ?? [];
4453
+ const fixedOffers = allOffers.map(({ borrowingOrderQuota: _, ...rest }) => ({
4454
+ ...rest,
4455
+ soldOut: rest["lendQuota"] === "0"
4456
+ }));
4457
+ return {
4458
+ ...rateResult,
4459
+ data: rateData,
4460
+ fixedOffers
4461
+ };
4285
4462
  }
4286
4463
  }
4287
4464
  ];
@@ -4620,7 +4797,7 @@ function registerDcdTools() {
4620
4797
  {
4621
4798
  name: "dcd_get_products",
4622
4799
  module: "earn.dcd",
4623
- description: "Get DCD products with yield and quota info.",
4800
+ description: "Get DCD products with yield and quota info. Yields in response are decimal fractions, not percentages.",
4624
4801
  isWrite: false,
4625
4802
  inputSchema: {
4626
4803
  type: "object",
@@ -4674,7 +4851,7 @@ function registerDcdTools() {
4674
4851
  {
4675
4852
  name: "dcd_get_orders",
4676
4853
  module: "earn.dcd",
4677
- description: "Get DCD order history.",
4854
+ description: "Get DCD order history. Yields in response are decimal fractions, not percentages.",
4678
4855
  isWrite: false,
4679
4856
  inputSchema: {
4680
4857
  type: "object",
@@ -4718,7 +4895,7 @@ function registerDcdTools() {
4718
4895
  {
4719
4896
  name: "dcd_subscribe",
4720
4897
  module: "earn.dcd",
4721
- description: "Subscribe to a DCD product: get quote and execute atomically. Confirm product, amount, and currency with user before calling. Optional minAnnualizedYield (percent) rejects the order if quote yield falls below threshold. Returns order result with quote snapshot (annualizedYield, absYield).",
4898
+ description: "Subscribe to a DCD product: get quote and execute atomically. Confirm product, amount, and currency with user before calling. Optional minAnnualizedYield rejects the order if quote yield falls below threshold. Returns order result with quote snapshot (minAnnualizedYield is in percent; response yields are decimal fractions).",
4722
4899
  isWrite: true,
4723
4900
  inputSchema: {
4724
4901
  type: "object",
@@ -4757,13 +4934,23 @@ function registerDcdTools() {
4757
4934
  });
4758
4935
  }
4759
4936
  if (minAnnualizedYield !== void 0) {
4760
- const actualYield = parseFloat(quote["annualizedYield"]);
4761
- if (!isNaN(actualYield) && actualYield < minAnnualizedYield) {
4937
+ const rawYield = parseFloat(quote["annualizedYield"]);
4938
+ if (isNaN(rawYield)) {
4939
+ throw new OkxApiError(
4940
+ "Quote returned non-numeric annualizedYield, cannot verify minimum yield threshold.",
4941
+ {
4942
+ code: "INVALID_YIELD_VALUE",
4943
+ suggestion: "Order not placed. The quote did not include a valid annualizedYield. Retry or pick a different product."
4944
+ }
4945
+ );
4946
+ }
4947
+ const actualYieldPct = rawYield * 100;
4948
+ if (actualYieldPct < minAnnualizedYield) {
4762
4949
  throw new OkxApiError(
4763
- `Quote yield ${actualYield}% is below the minimum threshold of ${minAnnualizedYield}%.`,
4950
+ `Quote yield ${actualYieldPct.toFixed(2)}% is below the minimum threshold of ${minAnnualizedYield}%.`,
4764
4951
  {
4765
4952
  code: "YIELD_BELOW_MIN",
4766
- suggestion: `Order not placed. Actual: ${actualYield}%, required: >= ${minAnnualizedYield}%. Try a different product or lower your minimum yield.`
4953
+ suggestion: `Order not placed. Actual: ${actualYieldPct.toFixed(2)}%, required: >= ${minAnnualizedYield}%. Try a different product or lower your minimum yield.`
4767
4954
  }
4768
4955
  );
4769
4956
  }
@@ -4908,7 +5095,7 @@ function registerAutoEarnTools() {
4908
5095
  }
4909
5096
  var EARN_DEMO_MESSAGE = "Earn features (savings, DCD, on-chain staking, auto-earn) are not available in simulated trading mode.";
4910
5097
  var EARN_DEMO_SUGGESTION = "Switch to a live account to use Earn features.";
4911
- var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem"]);
5098
+ var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem", "earn_fixed_purchase"]);
4912
5099
  function withDemoGuard(tool) {
4913
5100
  if (!tool.isWrite || DEMO_GUARD_SKIP.has(tool.name)) return tool;
4914
5101
  const originalHandler = tool.handler;
@@ -4969,12 +5156,12 @@ function buildContractTradeTools(cfg) {
4969
5156
  },
4970
5157
  sz: {
4971
5158
  type: "string",
4972
- 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."
5159
+ 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 notional value; set tgtCcy=margin to specify sz as margin cost (notional = sz * leverage)."
4973
5160
  },
4974
5161
  tgtCcy: {
4975
5162
  type: "string",
4976
- enum: ["base_ccy", "quote_ccy"],
4977
- description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT"
5163
+ enum: ["base_ccy", "quote_ccy", "margin"],
5164
+ description: "Size unit. base_ccy(default): sz in contracts; quote_ccy: sz in USDT notional value; margin: sz in USDT margin cost (actual position = sz * leverage)"
4978
5165
  },
4979
5166
  px: { type: "string", description: "Required for limit/post_only/fok/ioc" },
4980
5167
  reduceOnly: {
@@ -4993,6 +5180,14 @@ function buildContractTradeTools(cfg) {
4993
5180
  const args = asRecord(rawArgs);
4994
5181
  const reduceOnly = args.reduceOnly;
4995
5182
  const attachAlgoOrds = buildAttachAlgoOrds(args);
5183
+ const resolved = await resolveQuoteCcySz(
5184
+ requireString(args, "instId"),
5185
+ requireString(args, "sz"),
5186
+ readString(args, "tgtCcy"),
5187
+ defaultType,
5188
+ context.client,
5189
+ readString(args, "tdMode")
5190
+ );
4996
5191
  const response = await context.client.privatePost(
4997
5192
  "/api/v5/trade/order",
4998
5193
  compactObject({
@@ -5001,8 +5196,8 @@ function buildContractTradeTools(cfg) {
5001
5196
  side: requireString(args, "side"),
5002
5197
  posSide: readString(args, "posSide"),
5003
5198
  ordType: requireString(args, "ordType"),
5004
- sz: requireString(args, "sz"),
5005
- tgtCcy: readString(args, "tgtCcy"),
5199
+ sz: resolved.sz,
5200
+ tgtCcy: resolved.tgtCcy,
5006
5201
  px: readString(args, "px"),
5007
5202
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
5008
5203
  clOrdId: readString(args, "clOrdId"),
@@ -5011,7 +5206,11 @@ function buildContractTradeTools(cfg) {
5011
5206
  }),
5012
5207
  privateRateLimit(n("place_order"), 60)
5013
5208
  );
5014
- return normalizeResponse(response);
5209
+ const result = normalizeResponse(response);
5210
+ if (resolved.conversionNote) {
5211
+ result._conversion = resolved.conversionNote;
5212
+ }
5213
+ return result;
5015
5214
  }
5016
5215
  },
5017
5216
  // ── cancel_order ─────────────────────────────────────────────────────────
@@ -5479,6 +5678,12 @@ function registerFuturesTools() {
5479
5678
  ];
5480
5679
  }
5481
5680
  var TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1e3;
5681
+ var DEMO_PROPERTY = {
5682
+ demo: {
5683
+ type: "boolean",
5684
+ description: "Query simulated trading (demo) market data. Default: false (live market data)."
5685
+ }
5686
+ };
5482
5687
  function registerMarketTools() {
5483
5688
  return [
5484
5689
  {
@@ -5492,7 +5697,8 @@ function registerMarketTools() {
5492
5697
  instId: {
5493
5698
  type: "string",
5494
5699
  description: "e.g. BTC-USDT, BTC-USDT-SWAP"
5495
- }
5700
+ },
5701
+ ...DEMO_PROPERTY
5496
5702
  },
5497
5703
  required: ["instId"]
5498
5704
  },
@@ -5501,7 +5707,8 @@ function registerMarketTools() {
5501
5707
  const response = await context.client.publicGet(
5502
5708
  "/api/v5/market/ticker",
5503
5709
  { instId: requireString(args, "instId") },
5504
- publicRateLimit("market_get_ticker", 20)
5710
+ publicRateLimit("market_get_ticker", 20),
5711
+ readBoolean(args, "demo") ?? false
5505
5712
  );
5506
5713
  return normalizeResponse(response);
5507
5714
  }
@@ -5525,7 +5732,8 @@ function registerMarketTools() {
5525
5732
  instFamily: {
5526
5733
  type: "string",
5527
5734
  description: "e.g. BTC-USD"
5528
- }
5735
+ },
5736
+ ...DEMO_PROPERTY
5529
5737
  },
5530
5738
  required: ["instType"]
5531
5739
  },
@@ -5538,7 +5746,8 @@ function registerMarketTools() {
5538
5746
  uly: readString(args, "uly"),
5539
5747
  instFamily: readString(args, "instFamily")
5540
5748
  }),
5541
- publicRateLimit("market_get_tickers", 20)
5749
+ publicRateLimit("market_get_tickers", 20),
5750
+ readBoolean(args, "demo") ?? false
5542
5751
  );
5543
5752
  return normalizeResponse(response);
5544
5753
  }
@@ -5558,7 +5767,8 @@ function registerMarketTools() {
5558
5767
  sz: {
5559
5768
  type: "number",
5560
5769
  description: "Depth per side, default 1, max 400"
5561
- }
5770
+ },
5771
+ ...DEMO_PROPERTY
5562
5772
  },
5563
5773
  required: ["instId"]
5564
5774
  },
@@ -5570,7 +5780,8 @@ function registerMarketTools() {
5570
5780
  instId: requireString(args, "instId"),
5571
5781
  sz: readNumber(args, "sz")
5572
5782
  }),
5573
- publicRateLimit("market_get_orderbook", 20)
5783
+ publicRateLimit("market_get_orderbook", 20),
5784
+ readBoolean(args, "demo") ?? false
5574
5785
  );
5575
5786
  return normalizeResponse(response);
5576
5787
  }
@@ -5603,7 +5814,8 @@ function registerMarketTools() {
5603
5814
  limit: {
5604
5815
  type: "number",
5605
5816
  description: "Max results (default 100)"
5606
- }
5817
+ },
5818
+ ...DEMO_PROPERTY
5607
5819
  },
5608
5820
  required: ["instId"]
5609
5821
  },
@@ -5611,6 +5823,7 @@ function registerMarketTools() {
5611
5823
  const args = asRecord(rawArgs);
5612
5824
  const afterTs = readString(args, "after");
5613
5825
  const beforeTs = readString(args, "before");
5826
+ const demo = readBoolean(args, "demo") ?? false;
5614
5827
  const query = compactObject({
5615
5828
  instId: requireString(args, "instId"),
5616
5829
  bar: readString(args, "bar"),
@@ -5622,9 +5835,9 @@ function registerMarketTools() {
5622
5835
  const hasTimestamp = afterTs !== void 0 || beforeTs !== void 0;
5623
5836
  const useHistory = afterTs !== void 0 && Number(afterTs) < Date.now() - TWO_DAYS_MS;
5624
5837
  const path42 = useHistory ? "/api/v5/market/history-candles" : "/api/v5/market/candles";
5625
- const response = await context.client.publicGet(path42, query, rateLimit);
5838
+ const response = await context.client.publicGet(path42, query, rateLimit, demo);
5626
5839
  if (!useHistory && hasTimestamp && Array.isArray(response.data) && response.data.length === 0) {
5627
- return normalizeResponse(await context.client.publicGet("/api/v5/market/history-candles", query, rateLimit));
5840
+ return normalizeResponse(await context.client.publicGet("/api/v5/market/history-candles", query, rateLimit, demo));
5628
5841
  }
5629
5842
  return normalizeResponse(response);
5630
5843
  }
@@ -5652,7 +5865,8 @@ function registerMarketTools() {
5652
5865
  instFamily: {
5653
5866
  type: "string",
5654
5867
  description: "e.g. BTC-USD"
5655
- }
5868
+ },
5869
+ ...DEMO_PROPERTY
5656
5870
  },
5657
5871
  required: ["instType"]
5658
5872
  },
@@ -5666,7 +5880,8 @@ function registerMarketTools() {
5666
5880
  uly: readString(args, "uly"),
5667
5881
  instFamily: readString(args, "instFamily")
5668
5882
  }),
5669
- publicRateLimit("market_get_instruments", 20)
5883
+ publicRateLimit("market_get_instruments", 20),
5884
+ readBoolean(args, "demo") ?? false
5670
5885
  );
5671
5886
  return normalizeResponse(response);
5672
5887
  }
@@ -5698,13 +5913,15 @@ function registerMarketTools() {
5698
5913
  limit: {
5699
5914
  type: "number",
5700
5915
  description: "History records (default 20, max 100)"
5701
- }
5916
+ },
5917
+ ...DEMO_PROPERTY
5702
5918
  },
5703
5919
  required: ["instId"]
5704
5920
  },
5705
5921
  handler: async (rawArgs, context) => {
5706
5922
  const args = asRecord(rawArgs);
5707
5923
  const isHistory = readBoolean(args, "history") ?? false;
5924
+ const demo = readBoolean(args, "demo") ?? false;
5708
5925
  if (isHistory) {
5709
5926
  const response2 = await context.client.publicGet(
5710
5927
  "/api/v5/public/funding-rate-history",
@@ -5714,14 +5931,16 @@ function registerMarketTools() {
5714
5931
  before: readString(args, "before"),
5715
5932
  limit: readNumber(args, "limit") ?? 20
5716
5933
  }),
5717
- publicRateLimit("market_get_funding_rate", 20)
5934
+ publicRateLimit("market_get_funding_rate", 20),
5935
+ demo
5718
5936
  );
5719
5937
  return normalizeResponse(response2);
5720
5938
  }
5721
5939
  const response = await context.client.publicGet(
5722
5940
  "/api/v5/public/funding-rate",
5723
5941
  { instId: requireString(args, "instId") },
5724
- publicRateLimit("market_get_funding_rate", 20)
5942
+ publicRateLimit("market_get_funding_rate", 20),
5943
+ demo
5725
5944
  );
5726
5945
  return normalizeResponse(response);
5727
5946
  }
@@ -5748,7 +5967,8 @@ function registerMarketTools() {
5748
5967
  },
5749
5968
  instFamily: {
5750
5969
  type: "string"
5751
- }
5970
+ },
5971
+ ...DEMO_PROPERTY
5752
5972
  },
5753
5973
  required: ["instType"]
5754
5974
  },
@@ -5762,7 +5982,8 @@ function registerMarketTools() {
5762
5982
  uly: readString(args, "uly"),
5763
5983
  instFamily: readString(args, "instFamily")
5764
5984
  }),
5765
- publicRateLimit("market_get_mark_price", 10)
5985
+ publicRateLimit("market_get_mark_price", 10),
5986
+ readBoolean(args, "demo") ?? false
5766
5987
  );
5767
5988
  return normalizeResponse(response);
5768
5989
  }
@@ -5782,7 +6003,8 @@ function registerMarketTools() {
5782
6003
  limit: {
5783
6004
  type: "number",
5784
6005
  description: "Default 20, max 500"
5785
- }
6006
+ },
6007
+ ...DEMO_PROPERTY
5786
6008
  },
5787
6009
  required: ["instId"]
5788
6010
  },
@@ -5794,7 +6016,8 @@ function registerMarketTools() {
5794
6016
  instId: requireString(args, "instId"),
5795
6017
  limit: readNumber(args, "limit") ?? 20
5796
6018
  }),
5797
- publicRateLimit("market_get_trades", 20)
6019
+ publicRateLimit("market_get_trades", 20),
6020
+ readBoolean(args, "demo") ?? false
5798
6021
  );
5799
6022
  return normalizeResponse(response);
5800
6023
  }
@@ -5814,7 +6037,8 @@ function registerMarketTools() {
5814
6037
  quoteCcy: {
5815
6038
  type: "string",
5816
6039
  description: "e.g. USD or USDT"
5817
- }
6040
+ },
6041
+ ...DEMO_PROPERTY
5818
6042
  }
5819
6043
  },
5820
6044
  handler: async (rawArgs, context) => {
@@ -5825,7 +6049,8 @@ function registerMarketTools() {
5825
6049
  instId: readString(args, "instId"),
5826
6050
  quoteCcy: readString(args, "quoteCcy")
5827
6051
  }),
5828
- publicRateLimit("market_get_index_ticker", 20)
6052
+ publicRateLimit("market_get_index_ticker", 20),
6053
+ readBoolean(args, "demo") ?? false
5829
6054
  );
5830
6055
  return normalizeResponse(response);
5831
6056
  }
@@ -5862,7 +6087,8 @@ function registerMarketTools() {
5862
6087
  history: {
5863
6088
  type: "boolean",
5864
6089
  description: "true=older historical data"
5865
- }
6090
+ },
6091
+ ...DEMO_PROPERTY
5866
6092
  },
5867
6093
  required: ["instId"]
5868
6094
  },
@@ -5879,7 +6105,8 @@ function registerMarketTools() {
5879
6105
  before: readString(args, "before"),
5880
6106
  limit: readNumber(args, "limit")
5881
6107
  }),
5882
- publicRateLimit("market_get_index_candles", 20)
6108
+ publicRateLimit("market_get_index_candles", 20),
6109
+ readBoolean(args, "demo") ?? false
5883
6110
  );
5884
6111
  return normalizeResponse(response);
5885
6112
  }
@@ -5895,7 +6122,8 @@ function registerMarketTools() {
5895
6122
  instId: {
5896
6123
  type: "string",
5897
6124
  description: "SWAP or FUTURES ID, e.g. BTC-USDT-SWAP"
5898
- }
6125
+ },
6126
+ ...DEMO_PROPERTY
5899
6127
  },
5900
6128
  required: ["instId"]
5901
6129
  },
@@ -5904,7 +6132,8 @@ function registerMarketTools() {
5904
6132
  const response = await context.client.publicGet(
5905
6133
  "/api/v5/public/price-limit",
5906
6134
  { instId: requireString(args, "instId") },
5907
- publicRateLimit("market_get_price_limit", 20)
6135
+ publicRateLimit("market_get_price_limit", 20),
6136
+ readBoolean(args, "demo") ?? false
5908
6137
  );
5909
6138
  return normalizeResponse(response);
5910
6139
  }
@@ -5931,7 +6160,8 @@ function registerMarketTools() {
5931
6160
  },
5932
6161
  instFamily: {
5933
6162
  type: "string"
5934
- }
6163
+ },
6164
+ ...DEMO_PROPERTY
5935
6165
  },
5936
6166
  required: ["instType"]
5937
6167
  },
@@ -5945,7 +6175,8 @@ function registerMarketTools() {
5945
6175
  uly: readString(args, "uly"),
5946
6176
  instFamily: readString(args, "instFamily")
5947
6177
  }),
5948
- publicRateLimit("market_get_open_interest", 20)
6178
+ publicRateLimit("market_get_open_interest", 20),
6179
+ readBoolean(args, "demo") ?? false
5949
6180
  );
5950
6181
  return normalizeResponse(response);
5951
6182
  }
@@ -5966,7 +6197,8 @@ function registerMarketTools() {
5966
6197
  instId: {
5967
6198
  type: "string",
5968
6199
  description: "Optional: filter by specific instrument ID, e.g. AAPL-USDT-SWAP"
5969
- }
6200
+ },
6201
+ ...DEMO_PROPERTY
5970
6202
  },
5971
6203
  required: []
5972
6204
  },
@@ -5977,7 +6209,8 @@ function registerMarketTools() {
5977
6209
  const response = await context.client.publicGet(
5978
6210
  "/api/v5/public/instruments",
5979
6211
  compactObject({ instType, instId }),
5980
- publicRateLimit("market_get_stock_tokens", 20)
6212
+ publicRateLimit("market_get_stock_tokens", 20),
6213
+ readBoolean(args, "demo") ?? false
5981
6214
  );
5982
6215
  const data = response.data;
5983
6216
  const filtered = Array.isArray(data) ? data.filter((item) => item.instCategory === "3") : data;
@@ -5987,7 +6220,7 @@ function registerMarketTools() {
5987
6220
  {
5988
6221
  name: "market_get_instruments_by_category",
5989
6222
  module: "market",
5990
- 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.",
6223
+ 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 for crude oil). Use this to find instIds before querying prices or placing orders. Filters client-side by instCategory.",
5991
6224
  isWrite: false,
5992
6225
  inputSchema: {
5993
6226
  type: "object",
@@ -6005,7 +6238,8 @@ function registerMarketTools() {
6005
6238
  instId: {
6006
6239
  type: "string",
6007
6240
  description: "Optional: filter by specific instrument ID"
6008
- }
6241
+ },
6242
+ ...DEMO_PROPERTY
6009
6243
  },
6010
6244
  required: ["instCategory"]
6011
6245
  },
@@ -6017,7 +6251,8 @@ function registerMarketTools() {
6017
6251
  const response = await context.client.publicGet(
6018
6252
  "/api/v5/public/instruments",
6019
6253
  compactObject({ instType, instId }),
6020
- publicRateLimit("market_get_instruments_by_category", 20)
6254
+ publicRateLimit("market_get_instruments_by_category", 20),
6255
+ readBoolean(args, "demo") ?? false
6021
6256
  );
6022
6257
  const data = response.data;
6023
6258
  const filtered = Array.isArray(data) ? data.filter((item) => item.instCategory === instCategory) : data;
@@ -6085,8 +6320,8 @@ function registerOptionAlgoTools() {
6085
6320
  },
6086
6321
  tgtCcy: {
6087
6322
  type: "string",
6088
- enum: ["base_ccy", "quote_ccy"],
6089
- description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT (may not be supported for options)"
6323
+ enum: ["base_ccy", "quote_ccy", "margin"],
6324
+ description: "Size unit. base_ccy(default): sz in contracts; quote_ccy: sz in USDT notional value; margin: sz in USDT margin cost (actual position = sz * leverage)"
6090
6325
  },
6091
6326
  reduceOnly: {
6092
6327
  type: "boolean",
@@ -6102,6 +6337,14 @@ function registerOptionAlgoTools() {
6102
6337
  handler: async (rawArgs, context) => {
6103
6338
  const args = asRecord(rawArgs);
6104
6339
  const reduceOnly = readBoolean(args, "reduceOnly");
6340
+ const resolved = await resolveQuoteCcySz(
6341
+ requireString(args, "instId"),
6342
+ requireString(args, "sz"),
6343
+ readString(args, "tgtCcy"),
6344
+ "OPTION",
6345
+ context.client,
6346
+ readString(args, "tdMode")
6347
+ );
6105
6348
  const response = await context.client.privatePost(
6106
6349
  "/api/v5/trade/order-algo",
6107
6350
  compactObject({
@@ -6109,8 +6352,8 @@ function registerOptionAlgoTools() {
6109
6352
  tdMode: requireString(args, "tdMode"),
6110
6353
  side: requireString(args, "side"),
6111
6354
  ordType: requireString(args, "ordType"),
6112
- sz: requireString(args, "sz"),
6113
- tgtCcy: readString(args, "tgtCcy"),
6355
+ sz: resolved.sz,
6356
+ tgtCcy: resolved.tgtCcy,
6114
6357
  tpTriggerPx: readString(args, "tpTriggerPx"),
6115
6358
  tpOrdPx: readString(args, "tpOrdPx"),
6116
6359
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -6317,7 +6560,12 @@ function registerOptionTools() {
6317
6560
  },
6318
6561
  sz: {
6319
6562
  type: "string",
6320
- description: "Contracts count (NOT USDT). Use market_get_instruments for ctVal."
6563
+ description: "Contracts count by default. Set tgtCcy=quote_ccy to specify USDT notional value; set tgtCcy=margin to specify USDT margin cost (notional = sz * leverage)."
6564
+ },
6565
+ tgtCcy: {
6566
+ type: "string",
6567
+ enum: ["base_ccy", "quote_ccy", "margin"],
6568
+ description: "Size unit. base_ccy(default): sz in contracts; quote_ccy: sz in USDT notional value; margin: sz in USDT margin cost (actual position = sz * leverage)"
6321
6569
  },
6322
6570
  px: {
6323
6571
  type: "string",
@@ -6354,6 +6602,14 @@ function registerOptionTools() {
6354
6602
  const args = asRecord(rawArgs);
6355
6603
  const reduceOnly = args.reduceOnly;
6356
6604
  const attachAlgoOrds = buildAttachAlgoOrds(args);
6605
+ const resolved = await resolveQuoteCcySz(
6606
+ requireString(args, "instId"),
6607
+ requireString(args, "sz"),
6608
+ readString(args, "tgtCcy"),
6609
+ "OPTION",
6610
+ context.client,
6611
+ readString(args, "tdMode")
6612
+ );
6357
6613
  const response = await context.client.privatePost(
6358
6614
  "/api/v5/trade/order",
6359
6615
  compactObject({
@@ -6361,7 +6617,8 @@ function registerOptionTools() {
6361
6617
  tdMode: requireString(args, "tdMode"),
6362
6618
  side: requireString(args, "side"),
6363
6619
  ordType: requireString(args, "ordType"),
6364
- sz: requireString(args, "sz"),
6620
+ sz: resolved.sz,
6621
+ tgtCcy: resolved.tgtCcy,
6365
6622
  px: readString(args, "px"),
6366
6623
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
6367
6624
  clOrdId: readString(args, "clOrdId"),
@@ -7517,12 +7774,12 @@ function createToolRunner(client, config) {
7517
7774
  };
7518
7775
  }
7519
7776
  function configFilePath() {
7520
- return join6(homedir4(), ".okx", "config.toml");
7777
+ return join4(homedir2(), ".okx", "config.toml");
7521
7778
  }
7522
7779
  function readFullConfig() {
7523
7780
  const path42 = configFilePath();
7524
7781
  if (!existsSync3(path42)) return { profiles: {} };
7525
- const raw = readFileSync4(path42, "utf-8");
7782
+ const raw = readFileSync3(path42, "utf-8");
7526
7783
  try {
7527
7784
  return parse(raw);
7528
7785
  } catch (err) {
@@ -7550,11 +7807,11 @@ var CONFIG_HEADER = `# OKX Trade Kit Configuration
7550
7807
  `;
7551
7808
  function writeFullConfig(config) {
7552
7809
  const path42 = configFilePath();
7553
- const dir = dirname4(path42);
7810
+ const dir = dirname3(path42);
7554
7811
  if (!existsSync3(dir)) {
7555
- mkdirSync5(dir, { recursive: true });
7812
+ mkdirSync4(dir, { recursive: true });
7556
7813
  }
7557
- writeFileSync4(path42, CONFIG_HEADER + stringify(config), "utf-8");
7814
+ writeFileSync3(path42, CONFIG_HEADER + stringify(config), "utf-8");
7558
7815
  }
7559
7816
  function expandShorthand(moduleId) {
7560
7817
  if (moduleId === "all") return [...MODULES];
@@ -7668,21 +7925,21 @@ function loadConfig(cli) {
7668
7925
  verbose: cli.verbose ?? false
7669
7926
  };
7670
7927
  }
7671
- var CACHE_FILE = join7(homedir5(), ".okx", "update-check.json");
7928
+ var CACHE_FILE = join5(homedir3(), ".okx", "update-check.json");
7672
7929
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7673
- function readCache2() {
7930
+ function readCache() {
7674
7931
  try {
7675
7932
  if (existsSync4(CACHE_FILE)) {
7676
- return JSON.parse(readFileSync5(CACHE_FILE, "utf-8"));
7933
+ return JSON.parse(readFileSync4(CACHE_FILE, "utf-8"));
7677
7934
  }
7678
7935
  } catch {
7679
7936
  }
7680
7937
  return {};
7681
7938
  }
7682
- function writeCache2(cache) {
7939
+ function writeCache(cache) {
7683
7940
  try {
7684
- mkdirSync6(join7(homedir5(), ".okx"), { recursive: true });
7685
- writeFileSync5(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7941
+ mkdirSync5(join5(homedir3(), ".okx"), { recursive: true });
7942
+ writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7686
7943
  } catch {
7687
7944
  }
7688
7945
  }
@@ -7729,14 +7986,14 @@ async function fetchLatestVersion(packageName) {
7729
7986
  function refreshCacheInBackground(packageName) {
7730
7987
  fetchLatestVersion(packageName).then((latest) => {
7731
7988
  if (!latest) return;
7732
- const cache = readCache2();
7989
+ const cache = readCache();
7733
7990
  cache[packageName] = { latestVersion: latest, checkedAt: Date.now() };
7734
- writeCache2(cache);
7991
+ writeCache(cache);
7735
7992
  }).catch(() => {
7736
7993
  });
7737
7994
  }
7738
7995
  function checkForUpdates(packageName, currentVersion) {
7739
- const cache = readCache2();
7996
+ const cache = readCache();
7740
7997
  const entry = cache[packageName];
7741
7998
  if (entry && isNewerVersion(currentVersion, entry.latestVersion)) {
7742
7999
  process.stderr.write(
@@ -7751,6 +8008,69 @@ Run: npm install -g ${packageName}
7751
8008
  refreshCacheInBackground(packageName);
7752
8009
  }
7753
8010
  }
8011
+ var LOG_LEVEL_PRIORITY = {
8012
+ error: 0,
8013
+ warn: 1,
8014
+ info: 2,
8015
+ debug: 3
8016
+ };
8017
+ var SENSITIVE_KEY_PATTERN = /apiKey|secretKey|passphrase|password|secret/i;
8018
+ function sanitize(value) {
8019
+ if (value === null || value === void 0) {
8020
+ return value;
8021
+ }
8022
+ if (Array.isArray(value)) {
8023
+ return value.map(sanitize);
8024
+ }
8025
+ if (typeof value === "object") {
8026
+ const result = {};
8027
+ for (const [k, v] of Object.entries(value)) {
8028
+ if (SENSITIVE_KEY_PATTERN.test(k)) {
8029
+ result[k] = "[REDACTED]";
8030
+ } else {
8031
+ result[k] = sanitize(v);
8032
+ }
8033
+ }
8034
+ return result;
8035
+ }
8036
+ return value;
8037
+ }
8038
+ var TradeLogger = class {
8039
+ logLevel;
8040
+ logDir;
8041
+ constructor(logLevel = "info", logDir) {
8042
+ this.logLevel = logLevel;
8043
+ this.logDir = logDir ?? path2.join(os2.homedir(), ".okx", "logs");
8044
+ }
8045
+ getLogPath(date) {
8046
+ const d = date ?? /* @__PURE__ */ new Date();
8047
+ const yyyy = d.getUTCFullYear();
8048
+ const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
8049
+ const dd = String(d.getUTCDate()).padStart(2, "0");
8050
+ return path2.join(this.logDir, `trade-${yyyy}-${mm}-${dd}.log`);
8051
+ }
8052
+ log(level, tool, params, result, durationMs) {
8053
+ if (LOG_LEVEL_PRIORITY[level] > LOG_LEVEL_PRIORITY[this.logLevel]) {
8054
+ return;
8055
+ }
8056
+ const entry = {
8057
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8058
+ level: level.toUpperCase(),
8059
+ tool,
8060
+ durationMs,
8061
+ params: sanitize(params),
8062
+ result: sanitize(result)
8063
+ };
8064
+ try {
8065
+ fs2.mkdirSync(this.logDir, { recursive: true });
8066
+ fs2.appendFileSync(this.getLogPath(), JSON.stringify(entry) + "\n", "utf8");
8067
+ } catch {
8068
+ }
8069
+ }
8070
+ static sanitize(params) {
8071
+ return sanitize(params);
8072
+ }
8073
+ };
7754
8074
  var CLIENT_NAMES = {
7755
8075
  "claude-desktop": "Claude Desktop",
7756
8076
  cursor: "Cursor",
@@ -7900,11 +8220,11 @@ function runSetup(options) {
7900
8220
  // src/commands/diagnose.ts
7901
8221
  import dns from "dns/promises";
7902
8222
  import net from "net";
7903
- import os4 from "os";
8223
+ import os5 from "os";
7904
8224
  import tls from "tls";
7905
8225
 
7906
8226
  // src/commands/diagnose-utils.ts
7907
- import fs2 from "fs";
8227
+ import fs4 from "fs";
7908
8228
  import { createRequire } from "module";
7909
8229
 
7910
8230
  // src/formatter.ts
@@ -7917,6 +8237,10 @@ var activeOutput = stdioOutput;
7917
8237
  function setOutput(impl) {
7918
8238
  activeOutput = impl;
7919
8239
  }
8240
+ var envContext = null;
8241
+ function setEnvContext(ctx) {
8242
+ envContext = ctx;
8243
+ }
7920
8244
  function output(message) {
7921
8245
  activeOutput.out(message);
7922
8246
  }
@@ -7929,10 +8253,23 @@ function outputLine(message) {
7929
8253
  function errorLine(message) {
7930
8254
  activeOutput.err(message + EOL);
7931
8255
  }
8256
+ var jsonEnvEnabled = false;
8257
+ function setJsonEnvEnabled(enabled) {
8258
+ jsonEnvEnabled = enabled;
8259
+ }
7932
8260
  function printJson(data) {
7933
- activeOutput.out(JSON.stringify(data, null, 2) + EOL);
8261
+ const payload = jsonEnvEnabled && envContext ? {
8262
+ env: envContext.demo ? "demo" : "live",
8263
+ profile: envContext.profile,
8264
+ data
8265
+ } : data;
8266
+ activeOutput.out(JSON.stringify(payload, null, 2) + EOL);
7934
8267
  }
7935
8268
  function printTable(rows) {
8269
+ if (envContext) {
8270
+ const envLabel = envContext.demo ? "demo (simulated trading)" : "live";
8271
+ activeOutput.out(`Environment: ${envLabel}` + EOL + EOL);
8272
+ }
7936
8273
  if (rows.length === 0) {
7937
8274
  activeOutput.out("(no data)" + EOL);
7938
8275
  return;
@@ -8009,7 +8346,7 @@ var Report = class {
8009
8346
  lines.push(`${key.padEnd(14)} ${value}`);
8010
8347
  }
8011
8348
  lines.push(sep2, "");
8012
- fs2.writeFileSync(filePath, lines.join("\n"), "utf8");
8349
+ fs4.writeFileSync(filePath, lines.join("\n"), "utf8");
8013
8350
  return true;
8014
8351
  } catch (_e) {
8015
8352
  return false;
@@ -8044,7 +8381,7 @@ function writeReportIfRequested(report, outputPath) {
8044
8381
  errorLine(` Warning: failed to write report to: ${outputPath}`);
8045
8382
  }
8046
8383
  }
8047
- function sanitize(value) {
8384
+ function sanitize2(value) {
8048
8385
  value = value.replace(
8049
8386
  /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi,
8050
8387
  "****-uuid-****"
@@ -8055,14 +8392,14 @@ function sanitize(value) {
8055
8392
  }
8056
8393
 
8057
8394
  // src/commands/diagnose-mcp.ts
8058
- import fs4 from "fs";
8059
- import path2 from "path";
8060
- import os2 from "os";
8395
+ import fs5 from "fs";
8396
+ import path4 from "path";
8397
+ import os4 from "os";
8061
8398
  import { spawnSync, spawn } from "child_process";
8062
8399
  import { createRequire as createRequire2 } from "module";
8063
8400
  import { fileURLToPath } from "url";
8064
8401
  var _require2 = createRequire2(import.meta.url);
8065
- var __dirname = path2.dirname(fileURLToPath(import.meta.url));
8402
+ var __dirname = path4.dirname(fileURLToPath(import.meta.url));
8066
8403
  function readMcpVersion() {
8067
8404
  const candidates = [
8068
8405
  // Installed as global or local dependency
@@ -8115,13 +8452,13 @@ function checkMcpEntryPoint(report) {
8115
8452
  if (!entryPath) {
8116
8453
  const candidates = [
8117
8454
  // Installed locally
8118
- path2.join(process.cwd(), "node_modules", ".bin", "okx-trade-mcp"),
8455
+ path4.join(process.cwd(), "node_modules", ".bin", "okx-trade-mcp"),
8119
8456
  // Monorepo workspace (e.g. running from source)
8120
- path2.join(__dirname, "..", "..", "..", "..", "mcp", "dist", "index.js")
8457
+ path4.join(__dirname, "..", "..", "..", "..", "mcp", "dist", "index.js")
8121
8458
  ];
8122
8459
  for (const candidate of candidates) {
8123
8460
  try {
8124
- fs4.accessSync(candidate, fs4.constants.X_OK | fs4.constants.R_OK);
8461
+ fs5.accessSync(candidate, fs5.constants.X_OK | fs5.constants.R_OK);
8125
8462
  entryPath = candidate;
8126
8463
  break;
8127
8464
  } catch (_e) {
@@ -8146,9 +8483,9 @@ var CLIENT_LIMITS = {
8146
8483
  cursor: { perServer: 40, total: 80 }
8147
8484
  };
8148
8485
  function checkJsonMcpConfig(configPath) {
8149
- if (!fs4.existsSync(configPath)) return "missing";
8486
+ if (!fs5.existsSync(configPath)) return "missing";
8150
8487
  try {
8151
- const raw = fs4.readFileSync(configPath, "utf8");
8488
+ const raw = fs5.readFileSync(configPath, "utf8");
8152
8489
  const parsed = JSON.parse(raw);
8153
8490
  const mcpServers = parsed["mcpServers"];
8154
8491
  if (!mcpServers) return "not-configured";
@@ -8166,15 +8503,15 @@ function checkJsonMcpConfig(configPath) {
8166
8503
  }
8167
8504
  }
8168
8505
  function checkClaudeCodeConfig() {
8169
- const home = os2.homedir();
8506
+ const home = os4.homedir();
8170
8507
  const candidates = [
8171
- path2.join(home, ".claude", "settings.json"),
8172
- path2.join(home, ".claude.json")
8508
+ path4.join(home, ".claude", "settings.json"),
8509
+ path4.join(home, ".claude.json")
8173
8510
  ];
8174
8511
  let anyFound = false;
8175
8512
  let anyParseError = false;
8176
8513
  for (const cfgPath of candidates) {
8177
- if (!fs4.existsSync(cfgPath)) continue;
8514
+ if (!fs5.existsSync(cfgPath)) continue;
8178
8515
  anyFound = true;
8179
8516
  const result = checkJsonMcpConfig(cfgPath);
8180
8517
  if (result === "found") return "found";
@@ -8191,8 +8528,8 @@ function handleJsonClient(clientId, report, configuredClients) {
8191
8528
  const status = checkJsonMcpConfig(configPath);
8192
8529
  if (status === "missing") return false;
8193
8530
  if (status === "found") {
8194
- ok(name, `configured (${sanitize(configPath)})`);
8195
- report.add(`client_${clientId}`, `OK ${sanitize(configPath)}`);
8531
+ ok(name, `configured (${sanitize2(configPath)})`);
8532
+ report.add(`client_${clientId}`, `OK ${sanitize2(configPath)}`);
8196
8533
  configuredClients.push(clientId);
8197
8534
  return false;
8198
8535
  }
@@ -8200,8 +8537,8 @@ function handleJsonClient(clientId, report, configuredClients) {
8200
8537
  fail(name, "okx-trade-mcp not found in mcpServers", [`Run: okx setup --client ${clientId}`]);
8201
8538
  report.add(`client_${clientId}`, "NOT_CONFIGURED");
8202
8539
  } else {
8203
- fail(name, `JSON parse error in ${sanitize(configPath)}`, [
8204
- `Check ${sanitize(configPath)} for JSON syntax errors`,
8540
+ fail(name, `JSON parse error in ${sanitize2(configPath)}`, [
8541
+ `Check ${sanitize2(configPath)} for JSON syntax errors`,
8205
8542
  `Then run: okx setup --client ${clientId}`
8206
8543
  ]);
8207
8544
  report.add(`client_${clientId}`, "PARSE_ERROR");
@@ -8294,37 +8631,37 @@ function checkToolCount(report, configuredClients, getSpecs = allToolSpecs) {
8294
8631
  }
8295
8632
  }
8296
8633
  function readLogTail(logPath) {
8297
- const stat = fs4.statSync(logPath);
8634
+ const stat = fs5.statSync(logPath);
8298
8635
  const readSize = Math.min(8192, stat.size);
8299
8636
  const buffer = Buffer.alloc(readSize);
8300
- const fd = fs4.openSync(logPath, "r");
8637
+ const fd = fs5.openSync(logPath, "r");
8301
8638
  try {
8302
- fs4.readSync(fd, buffer, 0, readSize, Math.max(0, stat.size - readSize));
8639
+ fs5.readSync(fd, buffer, 0, readSize, Math.max(0, stat.size - readSize));
8303
8640
  } finally {
8304
- fs4.closeSync(fd);
8641
+ fs5.closeSync(fd);
8305
8642
  }
8306
8643
  return buffer.toString("utf8").split("\n").filter((l) => l.trim()).slice(-5);
8307
8644
  }
8308
8645
  function getMcpLogCandidates() {
8309
8646
  if (process.platform === "darwin") {
8310
- const logsDir = path2.join(os2.homedir(), "Library", "Logs", "Claude");
8647
+ const logsDir = path4.join(os4.homedir(), "Library", "Logs", "Claude");
8311
8648
  const candidates = [
8312
- path2.join(logsDir, "mcp.log"),
8313
- path2.join(logsDir, "mcp-server-okx-trade-mcp.log")
8649
+ path4.join(logsDir, "mcp.log"),
8650
+ path4.join(logsDir, "mcp-server-okx-trade-mcp.log")
8314
8651
  ];
8315
8652
  try {
8316
- const extra = fs4.readdirSync(logsDir).filter((f) => f.startsWith("mcp") && f.endsWith(".log")).map((f) => path2.join(logsDir, f));
8653
+ const extra = fs5.readdirSync(logsDir).filter((f) => f.startsWith("mcp") && f.endsWith(".log")).map((f) => path4.join(logsDir, f));
8317
8654
  candidates.push(...extra);
8318
8655
  } catch (_e) {
8319
8656
  }
8320
8657
  return candidates;
8321
8658
  }
8322
8659
  if (process.platform === "win32") {
8323
- const appData2 = process.env.APPDATA ?? path2.join(os2.homedir(), "AppData", "Roaming");
8324
- return [path2.join(appData2, "Claude", "logs", "mcp.log")];
8660
+ const appData2 = process.env.APPDATA ?? path4.join(os4.homedir(), "AppData", "Roaming");
8661
+ return [path4.join(appData2, "Claude", "logs", "mcp.log")];
8325
8662
  }
8326
- const configHome = process.env.XDG_CONFIG_HOME ?? path2.join(os2.homedir(), ".config");
8327
- return [path2.join(configHome, "Claude", "logs", "mcp.log")];
8663
+ const configHome = process.env.XDG_CONFIG_HOME ?? path4.join(os4.homedir(), ".config");
8664
+ return [path4.join(configHome, "Claude", "logs", "mcp.log")];
8328
8665
  }
8329
8666
  function checkMcpLogs(report) {
8330
8667
  section("MCP Server Logs (recent)");
@@ -8338,7 +8675,7 @@ function checkMcpLogs(report) {
8338
8675
  report.add("mcp_log", logPath);
8339
8676
  if (lines.length > 0) {
8340
8677
  ok("last lines", `(${lines.length} shown)`);
8341
- for (const line of lines) outputLine(` ${sanitize(line)}`);
8678
+ for (const line of lines) outputLine(` ${sanitize2(line)}`);
8342
8679
  } else {
8343
8680
  ok("last lines", "(empty log)");
8344
8681
  }
@@ -8464,11 +8801,11 @@ function checkModuleLoading(entryPath, report) {
8464
8801
  return true;
8465
8802
  } else {
8466
8803
  const errMsg = result.stderr?.trim() || result.error?.message || "non-zero exit";
8467
- fail("module load", `failed: ${sanitize(errMsg)}`, [
8804
+ fail("module load", `failed: ${sanitize2(errMsg)}`, [
8468
8805
  "MCP server may have import errors or missing dependencies",
8469
8806
  `Try: node ${entryPath} --version`
8470
8807
  ]);
8471
- report.add("module_load", `FAIL ${sanitize(errMsg)}`);
8808
+ report.add("module_load", `FAIL ${sanitize2(errMsg)}`);
8472
8809
  return false;
8473
8810
  }
8474
8811
  }
@@ -8479,7 +8816,7 @@ async function cmdDiagnoseMcp(options = {}) {
8479
8816
  const report = new Report();
8480
8817
  report.add("ts", (/* @__PURE__ */ new Date()).toISOString());
8481
8818
  report.add("mode", "mcp");
8482
- report.add("os", `${process.platform} ${process.arch} ${os2.release()}`);
8819
+ report.add("os", `${process.platform} ${process.arch} ${os4.release()}`);
8483
8820
  checkMcpPackageVersion(report);
8484
8821
  const nodePassed = checkNodeCompat(report);
8485
8822
  const { entryPath, passed: entryPassed } = checkMcpEntryPoint(report);
@@ -8511,7 +8848,7 @@ async function cmdDiagnoseMcp(options = {}) {
8511
8848
 
8512
8849
  // src/commands/diagnose.ts
8513
8850
  var CLI_VERSION = readCliVersion();
8514
- var GIT_HASH = true ? "19e8da3" : "dev";
8851
+ var GIT_HASH = true ? "ee71999" : "dev";
8515
8852
  function maskKey2(key) {
8516
8853
  if (!key) return "(not set)";
8517
8854
  if (key.length <= 8) return "****";
@@ -8591,14 +8928,14 @@ function checkEnvironment(report) {
8591
8928
  }
8592
8929
  ok("CLI", `v${CLI_VERSION} (${GIT_HASH})`);
8593
8930
  ok("OS", `${process.platform} ${process.arch}`);
8594
- ok("OS release", os4.release());
8931
+ ok("OS release", os5.release());
8595
8932
  ok("Shell", process.env.SHELL ?? "(unknown)");
8596
8933
  ok("Locale", `${process.env.LANG ?? process.env.LC_ALL ?? "(unknown)"}`);
8597
8934
  ok("Timezone", Intl.DateTimeFormat().resolvedOptions().timeZone);
8598
8935
  report.add("cli", `${CLI_VERSION} (${GIT_HASH})`);
8599
8936
  report.add("node", `${nodeVersion} ${process.platform} ${process.arch}`);
8600
- const machine = typeof os4.machine === "function" ? os4.machine() : process.arch;
8601
- report.add("os", `${os4.type()} ${os4.release()} ${machine}`);
8937
+ const machine = typeof os5.machine === "function" ? os5.machine() : process.arch;
8938
+ report.add("os", `${os5.type()} ${os5.release()} ${machine}`);
8602
8939
  report.add("shell", process.env.SHELL ?? "-");
8603
8940
  report.add("locale", process.env.LANG ?? process.env.LC_ALL ?? "-");
8604
8941
  report.add("tz", Intl.DateTimeFormat().resolvedOptions().timeZone);
@@ -8755,10 +9092,10 @@ async function cmdDiagnose(config, profile, options = {}) {
8755
9092
  }
8756
9093
  function checkConfigFile(report) {
8757
9094
  section("Config File");
8758
- const path5 = configFilePath();
9095
+ const path6 = configFilePath();
8759
9096
  try {
8760
9097
  readFullConfig();
8761
- ok("Config parse", `${path5} OK`);
9098
+ ok("Config parse", `${path6} OK`);
8762
9099
  report.add("config_parse", "OK");
8763
9100
  return true;
8764
9101
  } catch (e) {
@@ -8808,24 +9145,24 @@ async function runCliChecks(config, profile, outputPath) {
8808
9145
 
8809
9146
  // src/commands/upgrade.ts
8810
9147
  import { spawnSync as spawnSync2 } from "child_process";
8811
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8 } from "fs";
8812
- import { dirname as dirname6, join as join9 } from "path";
8813
- import { homedir as homedir7 } from "os";
9148
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
9149
+ import { dirname as dirname5, join as join7 } from "path";
9150
+ import { homedir as homedir5 } from "os";
8814
9151
  var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
8815
- var CACHE_FILE2 = join9(homedir7(), ".okx", "last_check");
9152
+ var CACHE_FILE2 = join7(homedir5(), ".okx", "last_check");
8816
9153
  var THROTTLE_MS = 12 * 60 * 60 * 1e3;
8817
- var NPM_BIN = join9(dirname6(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
9154
+ var NPM_BIN = join7(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
8818
9155
  function readLastCheck() {
8819
9156
  try {
8820
- return parseInt(readFileSync7(CACHE_FILE2, "utf-8").trim(), 10) || 0;
9157
+ return parseInt(readFileSync6(CACHE_FILE2, "utf-8").trim(), 10) || 0;
8821
9158
  } catch {
8822
9159
  return 0;
8823
9160
  }
8824
9161
  }
8825
9162
  function writeLastCheck() {
8826
9163
  try {
8827
- mkdirSync8(join9(homedir7(), ".okx"), { recursive: true });
8828
- writeFileSync7(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
9164
+ mkdirSync7(join7(homedir5(), ".okx"), { recursive: true });
9165
+ writeFileSync6(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
8829
9166
  } catch {
8830
9167
  }
8831
9168
  }
@@ -8928,7 +9265,7 @@ function loadProfileConfig(opts) {
8928
9265
  import { EOL as EOL2 } from "os";
8929
9266
 
8930
9267
  // src/commands/client-setup.ts
8931
- import * as fs5 from "fs";
9268
+ import * as fs6 from "fs";
8932
9269
  var DETECTABLE_CLIENTS = ["claude-desktop", "cursor", "windsurf"];
8933
9270
  function cmdSetupClient(options) {
8934
9271
  runSetup(options);
@@ -8937,14 +9274,14 @@ function cmdSetupClients() {
8937
9274
  const detected = [];
8938
9275
  for (const id of DETECTABLE_CLIENTS) {
8939
9276
  const p = getConfigPath(id);
8940
- if (p && fs5.existsSync(p)) {
9277
+ if (p && fs6.existsSync(p)) {
8941
9278
  detected.push({ id, path: p });
8942
9279
  }
8943
9280
  }
8944
9281
  if (detected.length > 0) {
8945
9282
  outputLine("Detected clients:");
8946
- for (const { id, path: path5 } of detected) {
8947
- outputLine(` ${id.padEnd(16)} ${path5}`);
9283
+ for (const { id, path: path6 } of detected) {
9284
+ outputLine(` ${id.padEnd(16)} ${path6}`);
8948
9285
  }
8949
9286
  outputLine("");
8950
9287
  }
@@ -9328,7 +9665,7 @@ var HELP_TREE = {
9328
9665
  description: "Earn products \u2014 Simple Earn, On-chain Earn, and DCD (Dual Currency Deposit)",
9329
9666
  subgroups: {
9330
9667
  savings: {
9331
- description: "Simple Earn \u2014 flexible savings and lending",
9668
+ description: "Simple Earn \u2014 flexible savings, fixed-term, and lending",
9332
9669
  commands: {
9333
9670
  balance: {
9334
9671
  usage: "okx earn savings balance [<ccy>]",
@@ -9352,7 +9689,19 @@ var HELP_TREE = {
9352
9689
  },
9353
9690
  "rate-history": {
9354
9691
  usage: "okx earn savings rate-history [--ccy <ccy>] [--limit <n>]",
9355
- description: "Query Simple Earn lending rates (public, no auth needed)"
9692
+ description: "Query Simple Earn lending rates and fixed-term offers (requires auth)"
9693
+ },
9694
+ "fixed-orders": {
9695
+ usage: "okx earn savings fixed-orders [--ccy <ccy>] [--state <pending|earning|expired|settled|cancelled>]",
9696
+ description: "List fixed-term earn orders"
9697
+ },
9698
+ "fixed-purchase": {
9699
+ usage: "okx earn savings fixed-purchase --ccy <ccy> --amt <n> --term <term> [--confirm]",
9700
+ description: "Purchase Simple Earn Fixed (\u5B9A\u671F). Preview by default; add --confirm to execute. Funds locked until maturity"
9701
+ },
9702
+ "fixed-redeem": {
9703
+ usage: "okx earn savings fixed-redeem <reqId>",
9704
+ description: "Redeem a fixed-term earn order (full amount)"
9356
9705
  }
9357
9706
  }
9358
9707
  },
@@ -9517,6 +9866,39 @@ var HELP_TREE = {
9517
9866
  description: "Run network / MCP server diagnostics",
9518
9867
  usage: "okx diagnose [--cli | --mcp | --all] [--profile <name>] [--demo | --live] [--output <file>]"
9519
9868
  },
9869
+ skill: {
9870
+ description: "OKX Skills Marketplace \u2014 search, install, and manage agent skills",
9871
+ commands: {
9872
+ search: {
9873
+ usage: "okx skill search [--keyword <kw>] [--categories <id>] [--page <n>] [--limit <n>]",
9874
+ description: "Search for skills in the marketplace"
9875
+ },
9876
+ categories: {
9877
+ usage: "okx skill categories",
9878
+ description: "List available skill categories"
9879
+ },
9880
+ add: {
9881
+ usage: "okx skill add <name>",
9882
+ description: "Download and install a skill to detected agents"
9883
+ },
9884
+ download: {
9885
+ usage: "okx skill download <name> [--dir <path>] [--format zip|skill]",
9886
+ description: "Download a skill package without installing"
9887
+ },
9888
+ remove: {
9889
+ usage: "okx skill remove <name>",
9890
+ description: "Remove an installed skill"
9891
+ },
9892
+ check: {
9893
+ usage: "okx skill check <name>",
9894
+ description: "Check if an installed skill has a newer version"
9895
+ },
9896
+ list: {
9897
+ usage: "okx skill list",
9898
+ description: "List all locally installed skills"
9899
+ }
9900
+ }
9901
+ },
9520
9902
  upgrade: {
9521
9903
  description: "Upgrade okx CLI and MCP server to the latest stable version",
9522
9904
  usage: "okx upgrade [--check] [--beta] [--force] [--json]"
@@ -9532,6 +9914,7 @@ function printGlobalHelp() {
9532
9914
  " --demo Use simulated trading (demo) mode",
9533
9915
  " --live Force live trading mode (overrides profile demo=true; mutually exclusive with --demo)",
9534
9916
  " --json Output raw JSON",
9917
+ " --env With --json, wrap output as {env, profile, data}",
9535
9918
  " --verbose Show detailed network request/response info (stderr)",
9536
9919
  " --version, -v Show version",
9537
9920
  " --help Show this help",
@@ -9646,8 +10029,8 @@ function printCommandList(lines, commands) {
9646
10029
  lines.push("");
9647
10030
  }
9648
10031
  }
9649
- function printHelp(...path5) {
9650
- const [moduleName, subgroupName] = path5;
10032
+ function printHelp(...path6) {
10033
+ const [moduleName, subgroupName] = path6;
9651
10034
  if (!moduleName) {
9652
10035
  printGlobalHelp();
9653
10036
  } else if (!subgroupName) {
@@ -9663,6 +10046,7 @@ var CLI_OPTIONS = {
9663
10046
  profile: { type: "string" },
9664
10047
  demo: { type: "boolean", default: false },
9665
10048
  json: { type: "boolean", default: false },
10049
+ env: { type: "boolean", default: false },
9666
10050
  help: { type: "boolean", default: false },
9667
10051
  version: { type: "boolean", short: "v", default: false },
9668
10052
  // setup command
@@ -9768,6 +10152,8 @@ var CLI_OPTIONS = {
9768
10152
  orders: { type: "string" },
9769
10153
  // earn
9770
10154
  rate: { type: "string" },
10155
+ reqId: { type: "string" },
10156
+ confirm: { type: "boolean", default: false },
9771
10157
  // audit
9772
10158
  since: { type: "string" },
9773
10159
  tool: { type: "string" },
@@ -9808,6 +10194,7 @@ var CLI_OPTIONS = {
9808
10194
  categories: { type: "string" },
9809
10195
  dir: { type: "string" },
9810
10196
  page: { type: "string" },
10197
+ format: { type: "string" },
9811
10198
  // diagnostics — cli/mcp/all/output are diagnose-specific; verbose is shared
9812
10199
  verbose: { type: "boolean", default: false },
9813
10200
  mcp: { type: "boolean", default: false },
@@ -9847,7 +10234,7 @@ function getData(result) {
9847
10234
  return result.data;
9848
10235
  }
9849
10236
  async function cmdMarketInstruments(run, opts) {
9850
- const result = await run("market_get_instruments", { instType: opts.instType, instId: opts.instId });
10237
+ const result = await run("market_get_instruments", { instType: opts.instType, instId: opts.instId, demo: opts.demo ?? false });
9851
10238
  const items = getData(result);
9852
10239
  if (opts.json) return printJson(items);
9853
10240
  printTable(
@@ -9862,7 +10249,7 @@ async function cmdMarketInstruments(run, opts) {
9862
10249
  );
9863
10250
  }
9864
10251
  async function cmdMarketFundingRate(run, instId, opts) {
9865
- const result = await run("market_get_funding_rate", { instId, history: opts.history, limit: opts.limit });
10252
+ const result = await run("market_get_funding_rate", { instId, history: opts.history, limit: opts.limit, demo: opts.demo ?? false });
9866
10253
  const items = getData(result);
9867
10254
  if (opts.json) return printJson(items);
9868
10255
  if (opts.history) {
@@ -9890,7 +10277,7 @@ async function cmdMarketFundingRate(run, instId, opts) {
9890
10277
  }
9891
10278
  }
9892
10279
  async function cmdMarketMarkPrice(run, opts) {
9893
- const result = await run("market_get_mark_price", { instType: opts.instType, instId: opts.instId });
10280
+ const result = await run("market_get_mark_price", { instType: opts.instType, instId: opts.instId, demo: opts.demo ?? false });
9894
10281
  const items = getData(result);
9895
10282
  if (opts.json) return printJson(items);
9896
10283
  printTable(
@@ -9903,7 +10290,7 @@ async function cmdMarketMarkPrice(run, opts) {
9903
10290
  );
9904
10291
  }
9905
10292
  async function cmdMarketTrades(run, instId, opts) {
9906
- const result = await run("market_get_trades", { instId, limit: opts.limit });
10293
+ const result = await run("market_get_trades", { instId, limit: opts.limit, demo: opts.demo ?? false });
9907
10294
  const items = getData(result);
9908
10295
  if (opts.json) return printJson(items);
9909
10296
  printTable(
@@ -9917,7 +10304,7 @@ async function cmdMarketTrades(run, instId, opts) {
9917
10304
  );
9918
10305
  }
9919
10306
  async function cmdMarketIndexTicker(run, opts) {
9920
- const result = await run("market_get_index_ticker", { instId: opts.instId, quoteCcy: opts.quoteCcy });
10307
+ const result = await run("market_get_index_ticker", { instId: opts.instId, quoteCcy: opts.quoteCcy, demo: opts.demo ?? false });
9921
10308
  const items = getData(result);
9922
10309
  if (opts.json) return printJson(items);
9923
10310
  printTable(
@@ -9931,7 +10318,7 @@ async function cmdMarketIndexTicker(run, opts) {
9931
10318
  );
9932
10319
  }
9933
10320
  async function cmdMarketIndexCandles(run, instId, opts) {
9934
- const result = await run("market_get_index_candles", { instId, bar: opts.bar, limit: opts.limit, history: opts.history });
10321
+ const result = await run("market_get_index_candles", { instId, bar: opts.bar, limit: opts.limit, history: opts.history, demo: opts.demo ?? false });
9935
10322
  const candles = getData(result);
9936
10323
  if (opts.json) return printJson(candles);
9937
10324
  printTable(
@@ -9944,8 +10331,8 @@ async function cmdMarketIndexCandles(run, instId, opts) {
9944
10331
  }))
9945
10332
  );
9946
10333
  }
9947
- async function cmdMarketPriceLimit(run, instId, json) {
9948
- const result = await run("market_get_price_limit", { instId });
10334
+ async function cmdMarketPriceLimit(run, instId, json, demo) {
10335
+ const result = await run("market_get_price_limit", { instId, demo: demo ?? false });
9949
10336
  const items = getData(result);
9950
10337
  if (json) return printJson(items);
9951
10338
  const r = items?.[0];
@@ -9961,7 +10348,7 @@ async function cmdMarketPriceLimit(run, instId, json) {
9961
10348
  });
9962
10349
  }
9963
10350
  async function cmdMarketOpenInterest(run, opts) {
9964
- const result = await run("market_get_open_interest", { instType: opts.instType, instId: opts.instId });
10351
+ const result = await run("market_get_open_interest", { instType: opts.instType, instId: opts.instId, demo: opts.demo ?? false });
9965
10352
  const items = getData(result);
9966
10353
  if (opts.json) return printJson(items);
9967
10354
  printTable(
@@ -9973,8 +10360,8 @@ async function cmdMarketOpenInterest(run, opts) {
9973
10360
  }))
9974
10361
  );
9975
10362
  }
9976
- async function cmdMarketTicker(run, instId, json) {
9977
- const result = await run("market_get_ticker", { instId });
10363
+ async function cmdMarketTicker(run, instId, json, demo) {
10364
+ const result = await run("market_get_ticker", { instId, demo: demo ?? false });
9978
10365
  const items = getData(result);
9979
10366
  if (json) return printJson(items);
9980
10367
  if (!items?.length) {
@@ -9997,8 +10384,8 @@ async function cmdMarketTicker(run, instId, json) {
9997
10384
  time: new Date(Number(t["ts"])).toLocaleString()
9998
10385
  });
9999
10386
  }
10000
- async function cmdMarketTickers(run, instType, json) {
10001
- const result = await run("market_get_tickers", { instType });
10387
+ async function cmdMarketTickers(run, instType, json, demo) {
10388
+ const result = await run("market_get_tickers", { instType, demo: demo ?? false });
10002
10389
  const items = getData(result);
10003
10390
  if (json) return printJson(items);
10004
10391
  printTable(
@@ -10011,8 +10398,8 @@ async function cmdMarketTickers(run, instType, json) {
10011
10398
  }))
10012
10399
  );
10013
10400
  }
10014
- async function cmdMarketOrderbook(run, instId, sz, json) {
10015
- const result = await run("market_get_orderbook", { instId, sz });
10401
+ async function cmdMarketOrderbook(run, instId, sz, json, demo) {
10402
+ const result = await run("market_get_orderbook", { instId, sz, demo: demo ?? false });
10016
10403
  const data = getData(result);
10017
10404
  if (json) return printJson(data);
10018
10405
  const book = data[0];
@@ -10029,7 +10416,7 @@ async function cmdMarketOrderbook(run, instId, sz, json) {
10029
10416
  for (const [p, s] of bids) outputLine(` ${p.padStart(16)} ${s}`);
10030
10417
  }
10031
10418
  async function cmdMarketCandles(run, instId, opts) {
10032
- const result = await run("market_get_candles", { instId, bar: opts.bar, limit: opts.limit, after: opts.after, before: opts.before });
10419
+ const result = await run("market_get_candles", { instId, bar: opts.bar, limit: opts.limit, after: opts.after, before: opts.before, demo: opts.demo ?? false });
10033
10420
  const candles = getData(result);
10034
10421
  if (opts.json) return printJson(candles);
10035
10422
  printTable(
@@ -10043,6 +10430,10 @@ async function cmdMarketCandles(run, instId, opts) {
10043
10430
  }))
10044
10431
  );
10045
10432
  }
10433
+ function cmdMarketIndicatorList(json) {
10434
+ if (json) return printJson(KNOWN_INDICATORS);
10435
+ printTable(KNOWN_INDICATORS.map(({ name, description }) => ({ name, description })));
10436
+ }
10046
10437
  async function cmdMarketIndicator(run, indicator, instId, opts) {
10047
10438
  const params = opts.params ? opts.params.split(",").map((p) => Number(p.trim())).filter((n) => !Number.isNaN(n)) : void 0;
10048
10439
  const result = await run("market_get_indicator", {
@@ -10095,7 +10486,8 @@ async function cmdMarketInstrumentsByCategory(run, opts) {
10095
10486
  const result = await run("market_get_instruments_by_category", {
10096
10487
  instCategory: opts.instCategory,
10097
10488
  instType: opts.instType,
10098
- instId: opts.instId
10489
+ instId: opts.instId,
10490
+ demo: opts.demo ?? false
10099
10491
  });
10100
10492
  const items = getData(result);
10101
10493
  if (opts.json) return printJson(items);
@@ -10123,7 +10515,7 @@ async function cmdMarketInstrumentsByCategory(run, opts) {
10123
10515
  );
10124
10516
  }
10125
10517
  async function cmdMarketStockTokens(run, opts) {
10126
- const result = await run("market_get_stock_tokens", { instType: opts.instType, instId: opts.instId });
10518
+ const result = await run("market_get_stock_tokens", { instType: opts.instType, instId: opts.instId, demo: opts.demo ?? false });
10127
10519
  const items = getData(result);
10128
10520
  if (opts.json) return printJson(items);
10129
10521
  printTable(
@@ -10140,9 +10532,9 @@ async function cmdMarketStockTokens(run, opts) {
10140
10532
  }
10141
10533
 
10142
10534
  // src/commands/account.ts
10143
- import * as fs6 from "fs";
10144
- import * as path4 from "path";
10145
- import * as os5 from "os";
10535
+ import * as fs7 from "fs";
10536
+ import * as path5 from "path";
10537
+ import * as os6 from "os";
10146
10538
  function getData2(result) {
10147
10539
  return result.data;
10148
10540
  }
@@ -10353,10 +10745,10 @@ function readAuditLogs(logDir, days = 7) {
10353
10745
  const yyyy = d.getUTCFullYear();
10354
10746
  const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
10355
10747
  const dd = String(d.getUTCDate()).padStart(2, "0");
10356
- const filePath = path4.join(logDir, `trade-${yyyy}-${mm}-${dd}.log`);
10748
+ const filePath = path5.join(logDir, `trade-${yyyy}-${mm}-${dd}.log`);
10357
10749
  let content;
10358
10750
  try {
10359
- content = fs6.readFileSync(filePath, "utf8");
10751
+ content = fs7.readFileSync(filePath, "utf8");
10360
10752
  } catch {
10361
10753
  continue;
10362
10754
  }
@@ -10372,7 +10764,7 @@ function readAuditLogs(logDir, days = 7) {
10372
10764
  return entries;
10373
10765
  }
10374
10766
  function cmdAccountAudit(opts) {
10375
- const logDir = path4.join(os5.homedir(), ".okx", "logs");
10767
+ const logDir = path5.join(os6.homedir(), ".okx", "logs");
10376
10768
  const limit = Math.min(Number(opts.limit) || 20, 100);
10377
10769
  let entries = readAuditLogs(logDir);
10378
10770
  if (opts.tool) entries = entries.filter((e) => e.tool === opts.tool);
@@ -11360,6 +11752,7 @@ async function cmdOptionPlace(run, opts) {
11360
11752
  side: opts.side,
11361
11753
  ordType: opts.ordType,
11362
11754
  sz: opts.sz,
11755
+ tgtCcy: opts.tgtCcy,
11363
11756
  px: opts.px,
11364
11757
  reduceOnly: opts.reduceOnly,
11365
11758
  clOrdId: opts.clOrdId,
@@ -11827,6 +12220,22 @@ async function cmdEarnSavingsBalance(run, ccy, json) {
11827
12220
  pendingAmt: r["pendingAmt"]
11828
12221
  }));
11829
12222
  }
12223
+ async function cmdEarnFixedOrderList(run, opts) {
12224
+ const data = extractData(await run("earn_get_fixed_order_list", {
12225
+ ccy: opts.ccy,
12226
+ state: opts.state
12227
+ }));
12228
+ printDataList(data, opts.json, "No fixed earn orders", (r) => ({
12229
+ reqId: r["reqId"],
12230
+ ccy: r["ccy"],
12231
+ amt: r["amt"],
12232
+ rate: r["rate"],
12233
+ term: r["term"],
12234
+ state: r["state"],
12235
+ accruedInterest: r["accruedInterest"],
12236
+ cTime: new Date(Number(r["cTime"])).toLocaleString()
12237
+ }));
12238
+ }
11830
12239
  async function cmdEarnSavingsPurchase(run, opts) {
11831
12240
  const data = extractData(await run("earn_savings_purchase", { ccy: opts.ccy, amt: opts.amt, rate: opts.rate }));
11832
12241
  if (opts.json) {
@@ -11872,14 +12281,107 @@ async function cmdEarnLendingHistory(run, opts) {
11872
12281
  ts: new Date(Number(r["ts"])).toLocaleString()
11873
12282
  }));
11874
12283
  }
12284
+ function printFixedPurchasePreview(rec) {
12285
+ const offer = rec["offer"];
12286
+ outputLine("");
12287
+ outputLine("\u{1F4CB} Fixed Earn Purchase Preview");
12288
+ outputLine(` Currency: ${rec["ccy"]}`);
12289
+ outputLine(` Amount: ${rec["amt"]}`);
12290
+ outputLine(` Term: ${rec["term"]}`);
12291
+ if (rec["currentFlexibleRate"]) {
12292
+ outputLine(` Current flexible rate: ${rec["currentFlexibleRate"]}`);
12293
+ }
12294
+ if (offer) {
12295
+ printKv({
12296
+ rate: offer["rate"],
12297
+ minLend: offer["minLend"],
12298
+ remainingQuota: offer["lendQuota"],
12299
+ soldOut: offer["soldOut"] ? "Yes" : "No"
12300
+ });
12301
+ } else {
12302
+ outputLine(" \u26A0\uFE0F No matching offer found for this term.");
12303
+ }
12304
+ outputLine("");
12305
+ outputLine(rec["warning"] ?? "");
12306
+ outputLine("");
12307
+ outputLine("Re-run with --confirm to execute.");
12308
+ }
12309
+ async function cmdEarnFixedPurchase(run, opts) {
12310
+ const result = await run("earn_fixed_purchase", {
12311
+ ccy: opts.ccy,
12312
+ amt: opts.amt,
12313
+ term: opts.term,
12314
+ confirm: opts.confirm
12315
+ });
12316
+ if (!result || typeof result !== "object") {
12317
+ outputLine("No response data");
12318
+ return;
12319
+ }
12320
+ const rec = result;
12321
+ if (rec["preview"]) {
12322
+ if (opts.json) {
12323
+ printJson(rec);
12324
+ return;
12325
+ }
12326
+ printFixedPurchasePreview(rec);
12327
+ return;
12328
+ }
12329
+ const data = extractData(result);
12330
+ if (opts.json) {
12331
+ printJson(data);
12332
+ return;
12333
+ }
12334
+ const r = data[0];
12335
+ if (!r) {
12336
+ outputLine("No response data");
12337
+ return;
12338
+ }
12339
+ printKv({ reqId: r["reqId"], ccy: r["ccy"], amt: r["amt"], term: r["term"] });
12340
+ }
12341
+ async function cmdEarnFixedRedeem(run, opts) {
12342
+ const data = extractData(await run("earn_fixed_redeem", { reqId: opts.reqId }));
12343
+ if (opts.json) {
12344
+ printJson(data);
12345
+ return;
12346
+ }
12347
+ if (!data.length) {
12348
+ outputLine("No response data");
12349
+ return;
12350
+ }
12351
+ printTable(data.map((r) => ({ reqId: r["reqId"] })));
12352
+ }
11875
12353
  async function cmdEarnLendingRateHistory(run, opts) {
11876
- const data = extractData(await run("earn_get_lending_rate_history", { ccy: opts.ccy, limit: opts.limit }));
11877
- printDataList(data, opts.json, "No rate history data", (r) => ({
12354
+ const result = await run("earn_get_lending_rate_history", { ccy: opts.ccy, limit: opts.limit });
12355
+ const data = extractData(result);
12356
+ const fixedOffers = extractFixedOffers(result);
12357
+ if (opts.json) {
12358
+ printJson({ data, fixedOffers });
12359
+ return;
12360
+ }
12361
+ printDataList(data, false, "No rate history data", (r) => ({
11878
12362
  ccy: r["ccy"],
11879
12363
  lendingRate: r["lendingRate"],
11880
- rate: r["rate"],
11881
12364
  ts: new Date(Number(r["ts"])).toLocaleString()
11882
12365
  }));
12366
+ if (fixedOffers.length > 0) {
12367
+ outputLine("");
12368
+ outputLine("Fixed-term offers:");
12369
+ printTable(fixedOffers.map((r) => ({
12370
+ ccy: r["ccy"],
12371
+ term: r["term"],
12372
+ rate: r["rate"],
12373
+ minLend: r["minLend"],
12374
+ remainingQuota: r["lendQuota"],
12375
+ soldOut: r["soldOut"] ? "Yes" : "No"
12376
+ })));
12377
+ }
12378
+ }
12379
+ function extractFixedOffers(result) {
12380
+ if (result && typeof result === "object") {
12381
+ const offers = result["fixedOffers"];
12382
+ if (Array.isArray(offers)) return offers;
12383
+ }
12384
+ return [];
11883
12385
  }
11884
12386
 
11885
12387
  // src/commands/auto-earn.ts
@@ -12406,7 +12908,7 @@ async function cmdDcdRedeemExecute(run, opts) {
12406
12908
  ordId: r["ordId"],
12407
12909
  state: r["state"],
12408
12910
  redeemSz: q["redeemSz"] ? `${parseFloat(q["redeemSz"]).toFixed(8)} ${q["redeemCcy"]}` : "\u2014",
12409
- termRate: q["termRate"] ? `${q["termRate"]}%` : "\u2014"
12911
+ termRate: q["termRate"] ? `${(parseFloat(q["termRate"]) * 100).toFixed(2)}%` : "\u2014"
12410
12912
  });
12411
12913
  }
12412
12914
  async function cmdDcdOrderState(run, opts) {
@@ -12459,7 +12961,7 @@ async function cmdDcdOrders(run, opts) {
12459
12961
  quoteCcy: r["quoteCcy"],
12460
12962
  strike: r["strike"],
12461
12963
  notionalSz: r["notionalSz"],
12462
- annualizedYield: r["annualizedYield"],
12964
+ annualizedYield: r["annualizedYield"] ? `${(parseFloat(r["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
12463
12965
  yieldSz: r["yieldSz"],
12464
12966
  settleTime: r["settleTime"] ? new Date(Number(r["settleTime"])).toLocaleDateString() : "",
12465
12967
  // scheduled settlement time
@@ -12499,7 +13001,7 @@ async function cmdDcdQuoteAndBuy(run, opts) {
12499
13001
  outputLine("Quote:");
12500
13002
  printKv({
12501
13003
  quoteId: q["quoteId"],
12502
- annualizedYield: q["annualizedYield"] ? `${q["annualizedYield"]}%` : "\u2014",
13004
+ annualizedYield: q["annualizedYield"] ? `${(parseFloat(q["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
12503
13005
  absYield: q["absYield"],
12504
13006
  notionalSz: q["notionalSz"],
12505
13007
  notionalCcy: q["notionalCcy"]
@@ -12523,16 +13025,17 @@ async function cmdDcdQuoteAndBuy(run, opts) {
12523
13025
  }
12524
13026
 
12525
13027
  // src/commands/skill.ts
12526
- import { tmpdir, homedir as homedir9 } from "os";
12527
- import { join as join11, dirname as dirname7 } from "path";
12528
- import { mkdirSync as mkdirSync9, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
13028
+ import { tmpdir, homedir as homedir7 } from "os";
13029
+ import { join as join9, dirname as dirname6 } from "path";
13030
+ import { mkdirSync as mkdirSync8, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
12529
13031
  import { execFileSync as execFileSync2 } from "child_process";
12530
13032
  import { randomUUID as randomUUID2 } from "crypto";
12531
13033
  function resolveNpx() {
12532
- const sibling = join11(dirname7(process.execPath), "npx");
13034
+ const sibling = join9(dirname6(process.execPath), "npx");
12533
13035
  if (existsSync7(sibling)) return sibling;
12534
13036
  return "npx";
12535
13037
  }
13038
+ var THIRD_PARTY_INSTALL_NOTICE = "Note: This skill was created by a third-party developer, not by OKX. Review SKILL.md before use.";
12536
13039
  async function cmdSkillSearch(run, opts) {
12537
13040
  const args = {};
12538
13041
  if (opts.keyword) args.keyword = opts.keyword;
@@ -12582,13 +13085,13 @@ async function cmdSkillCategories(run, json) {
12582
13085
  outputLine("");
12583
13086
  }
12584
13087
  async function cmdSkillAdd(name, config, json) {
12585
- const tmpBase = join11(tmpdir(), `okx-skill-${randomUUID2()}`);
12586
- mkdirSync9(tmpBase, { recursive: true });
13088
+ const tmpBase = join9(tmpdir(), `okx-skill-${randomUUID2()}`);
13089
+ mkdirSync8(tmpBase, { recursive: true });
12587
13090
  try {
12588
13091
  outputLine(`Downloading ${name}...`);
12589
13092
  const client = new OkxRestClient(config);
12590
13093
  const zipPath = await downloadSkillZip(client, name, tmpBase);
12591
- const contentDir = await extractSkillZip(zipPath, join11(tmpBase, "content"));
13094
+ const contentDir = await extractSkillZip(zipPath, join9(tmpBase, "content"));
12592
13095
  const meta = readMetaJson(contentDir);
12593
13096
  validateSkillMdExists(contentDir);
12594
13097
  outputLine("Installing to detected agents...");
@@ -12598,7 +13101,7 @@ async function cmdSkillAdd(name, config, json) {
12598
13101
  timeout: 6e4
12599
13102
  });
12600
13103
  } catch (e) {
12601
- const savedZip = join11(process.cwd(), `${name}.zip`);
13104
+ const savedZip = join9(process.cwd(), `${name}.zip`);
12602
13105
  try {
12603
13106
  copyFileSync2(zipPath, savedZip);
12604
13107
  } catch {
@@ -12608,23 +13111,19 @@ async function cmdSkillAdd(name, config, json) {
12608
13111
  throw e;
12609
13112
  }
12610
13113
  upsertSkillRecord(meta);
12611
- if (json) {
12612
- outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
12613
- } else {
12614
- outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
12615
- }
13114
+ printSkillInstallResult(meta, json);
12616
13115
  } finally {
12617
13116
  rmSync(tmpBase, { recursive: true, force: true });
12618
13117
  }
12619
13118
  }
12620
- async function cmdSkillDownload(name, targetDir, config, json) {
13119
+ async function cmdSkillDownload(name, targetDir, config, json, format = "zip") {
12621
13120
  outputLine(`Downloading ${name}...`);
12622
13121
  const client = new OkxRestClient(config);
12623
- const filePath = await downloadSkillZip(client, name, targetDir);
13122
+ const filePath = await downloadSkillZip(client, name, targetDir, format);
12624
13123
  if (json) {
12625
13124
  outputLine(JSON.stringify({ name, filePath }, null, 2));
12626
13125
  } else {
12627
- outputLine(`\u2713 Downloaded ${name}.zip`);
13126
+ outputLine(`\u2713 Downloaded ${name}.${format}`);
12628
13127
  outputLine(` Path: ${filePath}`);
12629
13128
  }
12630
13129
  }
@@ -12641,7 +13140,7 @@ function cmdSkillRemove(name, json) {
12641
13140
  timeout: 6e4
12642
13141
  });
12643
13142
  } catch {
12644
- const agentsPath = join11(homedir9(), ".agents", "skills", name);
13143
+ const agentsPath = join9(homedir7(), ".agents", "skills", name);
12645
13144
  try {
12646
13145
  rmSync(agentsPath, { recursive: true, force: true });
12647
13146
  } catch {
@@ -12705,11 +13204,19 @@ function cmdSkillList(json) {
12705
13204
  outputLine("");
12706
13205
  outputLine(`${skills.length} skills installed.`);
12707
13206
  }
13207
+ function printSkillInstallResult(meta, json) {
13208
+ if (json) {
13209
+ outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
13210
+ } else {
13211
+ outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
13212
+ outputLine(` ${THIRD_PARTY_INSTALL_NOTICE}`);
13213
+ }
13214
+ }
12708
13215
 
12709
13216
  // src/index.ts
12710
13217
  var _require3 = createRequire3(import.meta.url);
12711
13218
  var CLI_VERSION2 = _require3("../package.json").version;
12712
- var GIT_HASH2 = true ? "19e8da3" : "dev";
13219
+ var GIT_HASH2 = true ? "ee71999" : "dev";
12713
13220
  function handleConfigCommand(action, rest, json, lang, force) {
12714
13221
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
12715
13222
  if (action === "show") return cmdConfigShow(json);
@@ -12739,51 +13246,54 @@ function handleSetupCommand(v) {
12739
13246
  });
12740
13247
  }
12741
13248
  function handleMarketPublicCommand(run, action, rest, v, json) {
12742
- if (action === "ticker") return cmdMarketTicker(run, rest[0], json);
12743
- if (action === "tickers") return cmdMarketTickers(run, rest[0], json);
13249
+ if (action === "ticker") return cmdMarketTicker(run, rest[0], json, v.demo);
13250
+ if (action === "tickers") return cmdMarketTickers(run, rest[0], json, v.demo);
12744
13251
  if (action === "instruments")
12745
- return cmdMarketInstruments(run, { instType: v.instType, instId: v.instId, json });
13252
+ return cmdMarketInstruments(run, { instType: v.instType, instId: v.instId, json, demo: v.demo });
12746
13253
  if (action === "mark-price")
12747
- return cmdMarketMarkPrice(run, { instType: v.instType, instId: v.instId, json });
13254
+ return cmdMarketMarkPrice(run, { instType: v.instType, instId: v.instId, json, demo: v.demo });
12748
13255
  if (action === "index-ticker")
12749
- return cmdMarketIndexTicker(run, { instId: v.instId, quoteCcy: v.quoteCcy, json });
12750
- if (action === "price-limit") return cmdMarketPriceLimit(run, rest[0], json);
13256
+ return cmdMarketIndexTicker(run, { instId: v.instId, quoteCcy: v.quoteCcy, json, demo: v.demo });
13257
+ if (action === "price-limit") return cmdMarketPriceLimit(run, rest[0], json, v.demo);
12751
13258
  if (action === "open-interest")
12752
- return cmdMarketOpenInterest(run, { instType: v.instType, instId: v.instId, json });
13259
+ return cmdMarketOpenInterest(run, { instType: v.instType, instId: v.instId, json, demo: v.demo });
12753
13260
  if (action === "stock-tokens")
12754
- return cmdMarketStockTokens(run, { instType: v.instType, instId: v.instId, json });
13261
+ return cmdMarketStockTokens(run, { instType: v.instType, instId: v.instId, json, demo: v.demo });
12755
13262
  if (action === "instruments-by-category")
12756
13263
  return cmdMarketInstrumentsByCategory(run, {
12757
13264
  instCategory: v.instCategory,
12758
13265
  instType: v.instType,
12759
13266
  instId: v.instId,
12760
- json
13267
+ json,
13268
+ demo: v.demo
12761
13269
  });
12762
- if (action === "indicator") {
12763
- const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
12764
- const backtestTime = v["backtest-time"] !== void 0 ? Number(v["backtest-time"]) : void 0;
12765
- return cmdMarketIndicator(run, rest[0], rest[1], {
12766
- bar: v.bar,
12767
- params: v.params,
12768
- list: v.list,
12769
- limit,
12770
- backtestTime,
12771
- json
12772
- });
12773
- }
13270
+ if (action === "indicator") return handleIndicatorAction(run, rest, v, json);
13271
+ }
13272
+ function handleIndicatorAction(run, rest, v, json) {
13273
+ if (rest[0] === "list") return cmdMarketIndicatorList(json);
13274
+ const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
13275
+ const backtestTime = v["backtest-time"] !== void 0 ? Number(v["backtest-time"]) : void 0;
13276
+ return cmdMarketIndicator(run, rest[0], rest[1], {
13277
+ bar: v.bar,
13278
+ params: v.params,
13279
+ list: v.list,
13280
+ limit,
13281
+ backtestTime,
13282
+ json
13283
+ });
12774
13284
  }
12775
13285
  function handleMarketDataCommand(run, action, rest, v, json) {
12776
13286
  const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
12777
13287
  if (action === "orderbook")
12778
- return cmdMarketOrderbook(run, rest[0], v.sz !== void 0 ? Number(v.sz) : void 0, json);
13288
+ return cmdMarketOrderbook(run, rest[0], v.sz !== void 0 ? Number(v.sz) : void 0, json, v.demo);
12779
13289
  if (action === "candles")
12780
- return cmdMarketCandles(run, rest[0], { bar: v.bar, limit, after: v.after, before: v.before, json });
13290
+ return cmdMarketCandles(run, rest[0], { bar: v.bar, limit, after: v.after, before: v.before, json, demo: v.demo });
12781
13291
  if (action === "funding-rate")
12782
- return cmdMarketFundingRate(run, rest[0], { history: v.history ?? false, limit, json });
13292
+ return cmdMarketFundingRate(run, rest[0], { history: v.history ?? false, limit, json, demo: v.demo });
12783
13293
  if (action === "trades")
12784
- return cmdMarketTrades(run, rest[0], { limit, json });
13294
+ return cmdMarketTrades(run, rest[0], { limit, json, demo: v.demo });
12785
13295
  if (action === "index-candles")
12786
- return cmdMarketIndexCandles(run, rest[0], { bar: v.bar, limit, history: v.history ?? false, json });
13296
+ return cmdMarketIndexCandles(run, rest[0], { bar: v.bar, limit, history: v.history ?? false, json, demo: v.demo });
12787
13297
  }
12788
13298
  function handleMarketCommand(run, action, rest, v, json) {
12789
13299
  return handleMarketPublicCommand(run, action, rest, v, json) ?? handleMarketDataCommand(run, action, rest, v, json);
@@ -13116,6 +13626,7 @@ function handleOptionCommand(run, action, rest, v, json) {
13116
13626
  side: v.side,
13117
13627
  ordType: v.ordType,
13118
13628
  sz: v.sz,
13629
+ tgtCcy: v.tgtCcy,
13119
13630
  px: v.px,
13120
13631
  reduceOnly: v.reduceOnly,
13121
13632
  clOrdId: v.clOrdId,
@@ -13406,6 +13917,9 @@ function handleEarnSavingsCommand(run, action, rest, v, json) {
13406
13917
  if (action === "set-rate") return cmdEarnSetLendingRate(run, { ccy: v.ccy, rate: v.rate, json });
13407
13918
  if (action === "lending-history") return cmdEarnLendingHistory(run, { ccy: v.ccy, limit, json });
13408
13919
  if (action === "rate-history") return cmdEarnLendingRateHistory(run, { ccy: v.ccy, limit, json });
13920
+ if (action === "fixed-orders") return cmdEarnFixedOrderList(run, { ccy: v.ccy, state: v.state, json });
13921
+ if (action === "fixed-purchase") return cmdEarnFixedPurchase(run, { ccy: v.ccy, amt: v.amt, term: v.term, confirm: v.confirm ?? false, json });
13922
+ if (action === "fixed-redeem") return cmdEarnFixedRedeem(run, { reqId: v.reqId, json });
13409
13923
  errorLine(`Unknown earn savings command: ${action}`);
13410
13924
  process.exitCode = 1;
13411
13925
  }
@@ -13488,8 +14002,9 @@ function handleSkillAdd(rest, config, json) {
13488
14002
  if (n) return cmdSkillAdd(n, config, json);
13489
14003
  }
13490
14004
  function handleSkillDownload(rest, v, config, json) {
13491
- const n = requireSkillName(rest, "Usage: okx skill download <name> [--dir <path>]");
13492
- if (n) return cmdSkillDownload(n, v.dir ?? process.cwd(), config, json);
14005
+ const n = requireSkillName(rest, "Usage: okx skill download <name> [--dir <path>] [--format zip|skill]");
14006
+ const format = v.format === "skill" ? "skill" : "zip";
14007
+ if (n) return cmdSkillDownload(n, v.dir ?? process.cwd(), config, json, format);
13493
14008
  }
13494
14009
  function handleSkillRemove(rest, json) {
13495
14010
  const n = requireSkillName(rest, "Usage: okx skill remove <name>");
@@ -13524,6 +14039,35 @@ function printHelpForLevel(positionals) {
13524
14039
  else if (!subgroup) printHelp(module);
13525
14040
  else printHelp(module, subgroup);
13526
14041
  }
14042
+ function wrapRunnerWithLogger(baseRunner, logger, verbose = false) {
14043
+ const writeToolNames = new Set(allToolSpecs().filter((t) => t.isWrite).map((t) => t.name));
14044
+ return async (toolName, args) => {
14045
+ const startTime = Date.now();
14046
+ try {
14047
+ const result = await baseRunner(toolName, args);
14048
+ if (writeToolNames.has(toolName)) {
14049
+ markFailedIfSCodeError(result.data);
14050
+ }
14051
+ const elapsed = Date.now() - startTime;
14052
+ logger.log("info", toolName, args, { status: "ok" }, elapsed);
14053
+ if (verbose) {
14054
+ logger.log("debug", toolName, args, result, elapsed);
14055
+ }
14056
+ return result;
14057
+ } catch (error) {
14058
+ logger.log("error", toolName, args, error, Date.now() - startTime);
14059
+ throw error;
14060
+ }
14061
+ };
14062
+ }
14063
+ async function runDiagnose(v) {
14064
+ let config;
14065
+ try {
14066
+ config = loadProfileConfig({ profile: v.profile, demo: v.demo, live: v.live, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
14067
+ } catch {
14068
+ }
14069
+ return cmdDiagnose(config, v.profile ?? "default", { mcp: v.mcp, cli: v.cli, all: v.all, output: v.output });
14070
+ }
13527
14071
  async function main() {
13528
14072
  setOutput({
13529
14073
  out: (m) => process.stdout.write(m),
@@ -13545,25 +14089,14 @@ async function main() {
13545
14089
  if (module === "config") return handleConfigCommand(action, rest, json, v.lang, v.force);
13546
14090
  if (module === "setup") return handleSetupCommand(v);
13547
14091
  if (module === "upgrade") return cmdUpgrade(CLI_VERSION2, { beta: v.beta, check: v.check, force: v.force }, json);
13548
- if (module === "diagnose") {
13549
- let config2;
13550
- try {
13551
- config2 = loadProfileConfig({ profile: v.profile, demo: v.demo, live: v.live, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
13552
- } catch {
13553
- }
13554
- return cmdDiagnose(config2, v.profile ?? "default", { mcp: v.mcp, cli: v.cli, all: v.all, output: v.output });
13555
- }
14092
+ if (module === "diagnose") return runDiagnose(v);
13556
14093
  const config = loadProfileConfig({ profile: v.profile, demo: v.demo, live: v.live, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
14094
+ setEnvContext({ demo: config.demo, profile: v.profile ?? "default" });
14095
+ setJsonEnvEnabled(v.env ?? false);
13557
14096
  const client = new OkxRestClient(config);
13558
14097
  const baseRunner = createToolRunner(client, config);
13559
- const writeToolNames = new Set(allToolSpecs().filter((t) => t.isWrite).map((t) => t.name));
13560
- const run = async (toolName, args) => {
13561
- const result = await baseRunner(toolName, args);
13562
- if (writeToolNames.has(toolName)) {
13563
- markFailedIfSCodeError(result.data);
13564
- }
13565
- return result;
13566
- };
14098
+ const logger = new TradeLogger(v.verbose ? "debug" : "info");
14099
+ const run = wrapRunnerWithLogger(baseRunner, logger, v.verbose ?? false);
13567
14100
  const moduleHandlers = {
13568
14101
  market: () => handleMarketCommand(run, action, rest, v, json),
13569
14102
  account: () => handleAccountCommand(run, action, rest, v, json),
@@ -13608,7 +14141,8 @@ export {
13608
14141
  handleSpotCommand,
13609
14142
  handleSwapAlgoCommand,
13610
14143
  handleSwapCommand,
13611
- printHelp
14144
+ printHelp,
14145
+ wrapRunnerWithLogger
13612
14146
  };
13613
14147
  /*! Bundled license information:
13614
14148