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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,25 +4,37 @@
4
4
  import { createRequire as createRequire3 } from "module";
5
5
 
6
6
  // ../core/dist/index.js
7
- import { ProxyAgent } from "undici";
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";
8
20
  import { createHmac } from "crypto";
9
21
  import fs from "fs";
10
22
  import path from "path";
11
23
  import os from "os";
12
- import { writeFileSync, renameSync, unlinkSync, mkdirSync } from "fs";
13
- import { join, resolve, basename, sep } from "path";
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";
14
26
  import { randomUUID } from "crypto";
15
27
  import yauzl from "yauzl";
16
- import { createWriteStream, mkdirSync as mkdirSync2 } from "fs";
17
- import { resolve as resolve2, dirname } from "path";
18
- import { readFileSync, existsSync } from "fs";
19
- import { join as join2 } from "path";
20
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
21
- import { join as join3, dirname as dirname2 } from "path";
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";
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";
26
38
 
27
39
  // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
28
40
  function getLineColFromPtr(string, ptr) {
@@ -851,13 +863,137 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
851
863
  }
852
864
 
853
865
  // ../core/dist/index.js
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";
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";
857
869
  import * as fs3 from "fs";
858
870
  import * as path3 from "path";
859
871
  import * as os3 from "os";
860
872
  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
+ }
861
997
  function getNow() {
862
998
  return (/* @__PURE__ */ new Date()).toISOString();
863
999
  }
@@ -1076,6 +1212,14 @@ var OkxRestClient = class _OkxRestClient {
1076
1212
  config;
1077
1213
  rateLimiter;
1078
1214
  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;
1079
1223
  constructor(config) {
1080
1224
  this.config = config;
1081
1225
  this.rateLimiter = new RateLimiter(3e4, config.verbose);
@@ -1083,6 +1227,97 @@ var OkxRestClient = class _OkxRestClient {
1083
1227
  this.dispatcher = new ProxyAgent(config.proxyUrl);
1084
1228
  }
1085
1229
  }
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
+ }
1086
1321
  logRequest(method, url, auth) {
1087
1322
  if (!this.config.verbose) return;
1088
1323
  vlog(`\u2192 ${method} ${url}`);
@@ -1252,13 +1487,17 @@ var OkxRestClient = class _OkxRestClient {
1252
1487
  * Security: validates Content-Type and enforces maxBytes limit.
1253
1488
  */
1254
1489
  async privatePostBinary(path42, body, opts) {
1490
+ await this.ensureDoh();
1255
1491
  const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
1256
1492
  const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
1257
1493
  const bodyJson = body ? JSON.stringify(body) : "";
1258
1494
  const endpoint = `POST ${path42}`;
1259
- this.logRequest("POST", `${this.config.baseUrl}${path42}`, "private");
1495
+ this.logRequest("POST", `${this.activeBaseUrl}${path42}`, "private");
1260
1496
  const reqConfig = { method: "POST", path: path42, auth: "private" };
1261
1497
  const headers = this.buildHeaders(reqConfig, path42, bodyJson, getNow());
1498
+ if (this.dohNode) {
1499
+ headers.set("User-Agent", this.dohUserAgent);
1500
+ }
1262
1501
  const t0 = Date.now();
1263
1502
  const response = await this.fetchBinary(path42, endpoint, headers, bodyJson, t0);
1264
1503
  const elapsed = Date.now() - t0;
@@ -1292,10 +1531,10 @@ var OkxRestClient = class _OkxRestClient {
1292
1531
  method: "POST",
1293
1532
  headers,
1294
1533
  body: bodyJson || void 0,
1295
- signal: AbortSignal.timeout(this.config.timeoutMs)
1534
+ signal: AbortSignal.timeout(this.config.timeoutMs),
1535
+ dispatcher: this.activeDispatcher
1296
1536
  };
1297
- if (this.dispatcher) fetchOptions.dispatcher = this.dispatcher;
1298
- return await fetch(`${this.config.baseUrl}${path42}`, fetchOptions);
1537
+ return await fetch(`${this.activeBaseUrl}${path42}`, fetchOptions);
1299
1538
  } catch (error) {
1300
1539
  if (this.config.verbose) {
1301
1540
  vlog(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
@@ -1326,9 +1565,10 @@ var OkxRestClient = class _OkxRestClient {
1326
1565
  // JSON request
1327
1566
  // ---------------------------------------------------------------------------
1328
1567
  async request(reqConfig) {
1568
+ await this.ensureDoh();
1329
1569
  const queryString = buildQueryString(reqConfig.query);
1330
1570
  const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
1331
- const url = `${this.config.baseUrl}${requestPath}`;
1571
+ const url = `${this.activeBaseUrl}${requestPath}`;
1332
1572
  const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
1333
1573
  const timestamp = getNow();
1334
1574
  this.logRequest(reqConfig.method, url, reqConfig.auth);
@@ -1336,6 +1576,9 @@ var OkxRestClient = class _OkxRestClient {
1336
1576
  await this.rateLimiter.consume(reqConfig.rateLimit);
1337
1577
  }
1338
1578
  const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
1579
+ if (this.dohNode) {
1580
+ headers.set("User-Agent", this.dohUserAgent);
1581
+ }
1339
1582
  const t0 = Date.now();
1340
1583
  let response;
1341
1584
  try {
@@ -1343,13 +1586,20 @@ var OkxRestClient = class _OkxRestClient {
1343
1586
  method: reqConfig.method,
1344
1587
  headers,
1345
1588
  body: reqConfig.method === "POST" ? bodyJson : void 0,
1346
- signal: AbortSignal.timeout(this.config.timeoutMs)
1589
+ signal: AbortSignal.timeout(this.config.timeoutMs),
1590
+ dispatcher: this.activeDispatcher
1347
1591
  };
1348
- if (this.dispatcher) {
1349
- fetchOptions.dispatcher = this.dispatcher;
1350
- }
1351
1592
  response = await fetch(url, fetchOptions);
1352
1593
  } 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
+ }
1353
1603
  if (this.config.verbose) {
1354
1604
  const elapsed2 = Date.now() - t0;
1355
1605
  const cause = error instanceof Error ? error.message : String(error);
@@ -1364,6 +1614,19 @@ var OkxRestClient = class _OkxRestClient {
1364
1614
  const rawText = await response.text();
1365
1615
  const elapsed = Date.now() - t0;
1366
1616
  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
+ }
1367
1630
  return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
1368
1631
  }
1369
1632
  };
@@ -1503,137 +1766,14 @@ var INDICATOR_BARS = [
1503
1766
  "1Wutc"
1504
1767
  ];
1505
1768
  var INDICATOR_CODE_OVERRIDES = {
1506
- // Aliases
1507
- "boll": "BB",
1508
- // server supports BB not BOLL
1509
- // Names where default rule produces underscores but backend uses no separator
1510
1769
  "rainbow": "BTCRAINBOW",
1511
- // default: RAINBOW
1770
+ "range-filter": "RANGEFILTER",
1512
1771
  "stoch-rsi": "STOCHRSI",
1513
- // default: STOCH_RSI
1514
- "bull-engulf": "BULLENGULF",
1515
- // default: BULL_ENGULF
1516
- "bear-engulf": "BEARENGULF",
1517
- // default: BEAR_ENGULF
1518
- "bull-harami": "BULLHARAMI",
1519
- // default: BULL_HARAMI
1520
- "bear-harami": "BEARHARAMI",
1521
- // default: BEAR_HARAMI
1522
- "bull-harami-cross": "BULLHARAMICROSS",
1523
- // default: BULL_HARAMI_CROSS
1524
- "bear-harami-cross": "BEARHARAMICROSS",
1525
- // default: BEAR_HARAMI_CROSS
1526
- "three-soldiers": "THREESOLDIERS",
1527
- // default: THREE_SOLDIERS
1528
- "three-crows": "THREECROWS",
1529
- // default: THREE_CROWS
1530
- "hanging-man": "HANGINGMAN",
1531
- // default: HANGING_MAN
1532
- "inverted-hammer": "INVERTEDH",
1533
- // default: INVERTED_HAMMER (backend uses INVERTEDH)
1534
- "shooting-star": "SHOOTINGSTAR",
1535
- // default: SHOOTING_STAR
1536
- "nvi-pvi": "NVIPVI",
1537
- // default: NVI_PVI
1538
- "top-long-short": "TOPLONGSHORT"
1539
- // default: TOP_LONG_SHORT
1540
- // Note: range-filter → RANGE_FILTER is correct via the default rule; no override needed.
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"
1541
1776
  };
1542
- var KNOWN_INDICATORS = [
1543
- // Moving Averages
1544
- { name: "ma", description: "Simple Moving Average" },
1545
- { name: "ema", description: "Exponential Moving Average" },
1546
- { name: "wma", description: "Weighted Moving Average" },
1547
- { name: "dema", description: "Double Exponential Moving Average" },
1548
- { name: "tema", description: "Triple Exponential Moving Average" },
1549
- { name: "zlema", description: "Zero-Lag Exponential Moving Average" },
1550
- { name: "hma", description: "Hull Moving Average" },
1551
- { name: "kama", description: "Kaufman Adaptive Moving Average" },
1552
- // Trend
1553
- { name: "macd", description: "MACD" },
1554
- { name: "sar", description: "Parabolic SAR" },
1555
- { name: "adx", description: "Average Directional Index" },
1556
- { name: "aroon", description: "Aroon Indicator" },
1557
- { name: "cci", description: "Commodity Channel Index" },
1558
- { name: "dpo", description: "Detrended Price Oscillator" },
1559
- { name: "envelope", description: "Envelope" },
1560
- { name: "halftrend", description: "HalfTrend" },
1561
- { name: "alphatrend", description: "AlphaTrend" },
1562
- // Momentum
1563
- { name: "rsi", description: "Relative Strength Index" },
1564
- { name: "stoch-rsi", description: "Stochastic RSI" },
1565
- { name: "stoch", description: "Stochastic Oscillator" },
1566
- { name: "roc", description: "Rate of Change" },
1567
- { name: "mom", description: "Momentum" },
1568
- { name: "ppo", description: "Price Percentage Oscillator" },
1569
- { name: "trix", description: "TRIX" },
1570
- { name: "ao", description: "Awesome Oscillator" },
1571
- { name: "uo", description: "Ultimate Oscillator" },
1572
- { name: "wr", description: "Williams %R" },
1573
- // Volatility
1574
- { name: "bb", description: "Bollinger Bands" },
1575
- { name: "boll", description: "Bollinger Bands (alias for bb)" },
1576
- { name: "bbwidth", description: "Bollinger Band Width" },
1577
- { name: "bbpct", description: "Bollinger Band %B" },
1578
- { name: "atr", description: "Average True Range" },
1579
- { name: "keltner", description: "Keltner Channel" },
1580
- { name: "donchian", description: "Donchian Channel" },
1581
- { name: "hv", description: "Historical Volatility" },
1582
- { name: "stddev", description: "Standard Deviation" },
1583
- // Volume
1584
- { name: "obv", description: "On-Balance Volume" },
1585
- { name: "vwap", description: "Volume Weighted Average Price" },
1586
- { name: "mvwap", description: "Moving VWAP" },
1587
- { name: "cmf", description: "Chaikin Money Flow" },
1588
- { name: "mfi", description: "Money Flow Index" },
1589
- { name: "ad", description: "Accumulation/Distribution" },
1590
- // Statistical
1591
- { name: "lr", description: "Linear Regression" },
1592
- { name: "slope", description: "Linear Regression Slope" },
1593
- { name: "angle", description: "Linear Regression Angle" },
1594
- { name: "variance", description: "Variance" },
1595
- { name: "meandev", description: "Mean Deviation" },
1596
- { name: "sigma", description: "Sigma" },
1597
- { name: "stderr", description: "Standard Error" },
1598
- // Custom
1599
- { name: "kdj", description: "KDJ Stochastic Oscillator" },
1600
- { name: "supertrend", description: "Supertrend" },
1601
- // Ichimoku
1602
- { name: "tenkan", description: "Ichimoku Tenkan-sen (Conversion Line)" },
1603
- { name: "kijun", description: "Ichimoku Kijun-sen (Base Line)" },
1604
- { name: "senkoa", description: "Ichimoku Senkou Span A (Leading Span A)" },
1605
- { name: "senkob", description: "Ichimoku Senkou Span B (Leading Span B)" },
1606
- { name: "chikou", description: "Ichimoku Chikou Span (Lagging Span)" },
1607
- // Candlestick Patterns
1608
- { name: "doji", description: "Doji candlestick pattern" },
1609
- { name: "bull-engulf", description: "Bullish Engulfing pattern" },
1610
- { name: "bear-engulf", description: "Bearish Engulfing pattern" },
1611
- { name: "bull-harami", description: "Bullish Harami pattern" },
1612
- { name: "bear-harami", description: "Bearish Harami pattern" },
1613
- { name: "bull-harami-cross", description: "Bullish Harami Cross pattern" },
1614
- { name: "bear-harami-cross", description: "Bearish Harami Cross pattern" },
1615
- { name: "three-soldiers", description: "Three White Soldiers pattern" },
1616
- { name: "three-crows", description: "Three Black Crows pattern" },
1617
- { name: "hanging-man", description: "Hanging Man pattern" },
1618
- { name: "inverted-hammer", description: "Inverted Hammer pattern" },
1619
- { name: "shooting-star", description: "Shooting Star pattern" },
1620
- // Bitcoin On-Chain
1621
- { name: "ahr999", description: "AHR999 Bitcoin accumulation index" },
1622
- { name: "rainbow", description: "Bitcoin Rainbow Chart" },
1623
- // Other
1624
- { name: "fisher", description: "Fisher Transform" },
1625
- { name: "nvi-pvi", description: "Negative/Positive Volume Index (returns both)" },
1626
- { name: "pmax", description: "PMAX" },
1627
- { name: "qqe", description: "QQE Mod" },
1628
- { name: "tdi", description: "Traders Dynamic Index" },
1629
- { name: "waddah", description: "Waddah Attar Explosion" },
1630
- { name: "range-filter", description: "Range Filter" },
1631
- { name: "cho", description: "Chande Momentum Oscillator" },
1632
- { name: "tr", description: "True Range" },
1633
- { name: "tp", description: "Typical Price" },
1634
- { name: "mp", description: "Median Price" },
1635
- { name: "top-long-short", description: "Top Trader Long/Short Ratio (timeframe-independent)" }
1636
- ];
1637
1777
  function resolveIndicatorCode(name) {
1638
1778
  const lower = name.toLowerCase();
1639
1779
  return INDICATOR_CODE_OVERRIDES[lower] ?? name.toUpperCase().replace(/-/g, "_");
@@ -1660,7 +1800,7 @@ function registerIndicatorTools() {
1660
1800
  },
1661
1801
  indicator: {
1662
1802
  type: "string",
1663
- description: "Indicator name (case-insensitive). Call market_list_indicators to see all supported names."
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"
1664
1804
  },
1665
1805
  bar: {
1666
1806
  type: "string",
@@ -1717,14 +1857,6 @@ function registerIndicatorTools() {
1717
1857
  );
1718
1858
  return normalizeResponse(response);
1719
1859
  }
1720
- },
1721
- {
1722
- name: "market_list_indicators",
1723
- module: "market",
1724
- description: "List all supported technical indicator names and descriptions. Call this before market_get_indicator to discover valid indicator names. No credentials required.",
1725
- isWrite: false,
1726
- inputSchema: { type: "object", properties: {} },
1727
- handler: async () => ({ data: KNOWN_INDICATORS })
1728
1860
  }
1729
1861
  ];
1730
1862
  }
@@ -2322,59 +2454,6 @@ function registerAccountTools() {
2322
2454
  }
2323
2455
  ];
2324
2456
  }
2325
- async function resolveQuoteCcySz(instId, sz, tgtCcy, instType, client) {
2326
- if (tgtCcy !== "quote_ccy") {
2327
- return { sz, tgtCcy, conversionNote: void 0 };
2328
- }
2329
- const [instrumentsRes, tickerRes] = await Promise.all([
2330
- client.publicGet("/api/v5/public/instruments", {
2331
- instType,
2332
- instId
2333
- }),
2334
- client.publicGet("/api/v5/market/ticker", { instId })
2335
- ]);
2336
- const instruments = Array.isArray(instrumentsRes.data) ? instrumentsRes.data : [];
2337
- if (instruments.length === 0) {
2338
- throw new Error(
2339
- `Failed to fetch instrument info for ${instId}: empty instrument list. Cannot determine ctVal for quote_ccy conversion.`
2340
- );
2341
- }
2342
- const ctValStr = String(instruments[0].ctVal ?? "");
2343
- const ctVal = parseFloat(ctValStr);
2344
- if (!isFinite(ctVal) || ctVal <= 0) {
2345
- throw new Error(
2346
- `Invalid ctVal "${ctValStr}" for ${instId}. ctVal must be a positive number for quote_ccy conversion.`
2347
- );
2348
- }
2349
- const tickers = Array.isArray(tickerRes.data) ? tickerRes.data : [];
2350
- if (tickers.length === 0) {
2351
- throw new Error(
2352
- `Failed to fetch ticker price for ${instId}: empty ticker response. Cannot determine last price for quote_ccy conversion.`
2353
- );
2354
- }
2355
- const lastStr = String(tickers[0].last ?? "");
2356
- const lastPx = parseFloat(lastStr);
2357
- if (!isFinite(lastPx) || lastPx <= 0) {
2358
- throw new Error(
2359
- `Invalid last price "${lastStr}" for ${instId}. Last price must be a positive number for quote_ccy conversion.`
2360
- );
2361
- }
2362
- const usdtAmount = parseFloat(sz);
2363
- const contractValue = ctVal * lastPx;
2364
- const contracts = Math.floor(usdtAmount / contractValue);
2365
- if (contracts <= 0) {
2366
- const minUsdt = contractValue.toFixed(2);
2367
- throw new Error(
2368
- `sz=${sz} USDT is too small to buy even 1 contract of ${instId}. Minimum amount required is at least ${minUsdt} USDT (ctVal=${ctValStr}, lastPx=${lastStr}, 1 contract = ${minUsdt} USDT).`
2369
- );
2370
- }
2371
- const conversionNote = `Converting ${sz} USDT \u2192 ${contracts} contracts (ctVal=${ctValStr}, lastPx=${lastStr}, formula: floor(${sz} / (${ctValStr} \xD7 ${lastStr})) = ${contracts})`;
2372
- return {
2373
- sz: String(contracts),
2374
- tgtCcy: void 0,
2375
- conversionNote
2376
- };
2377
- }
2378
2457
  function registerAlgoTradeTools() {
2379
2458
  return [
2380
2459
  {
@@ -2470,13 +2549,6 @@ function registerAlgoTradeTools() {
2470
2549
  handler: async (rawArgs, context) => {
2471
2550
  const args = asRecord(rawArgs);
2472
2551
  const reduceOnly = args.reduceOnly;
2473
- const resolved = await resolveQuoteCcySz(
2474
- requireString(args, "instId"),
2475
- requireString(args, "sz"),
2476
- readString(args, "tgtCcy"),
2477
- "SWAP",
2478
- context.client
2479
- );
2480
2552
  const response = await context.client.privatePost(
2481
2553
  "/api/v5/trade/order-algo",
2482
2554
  compactObject({
@@ -2485,8 +2557,8 @@ function registerAlgoTradeTools() {
2485
2557
  side: requireString(args, "side"),
2486
2558
  posSide: readString(args, "posSide"),
2487
2559
  ordType: requireString(args, "ordType"),
2488
- sz: resolved.sz,
2489
- tgtCcy: resolved.tgtCcy,
2560
+ sz: requireString(args, "sz"),
2561
+ tgtCcy: readString(args, "tgtCcy"),
2490
2562
  tpTriggerPx: readString(args, "tpTriggerPx"),
2491
2563
  tpOrdPx: readString(args, "tpOrdPx"),
2492
2564
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -2502,11 +2574,7 @@ function registerAlgoTradeTools() {
2502
2574
  }),
2503
2575
  privateRateLimit("swap_place_algo_order", 20)
2504
2576
  );
2505
- const result = normalizeResponse(response);
2506
- if (resolved.conversionNote) {
2507
- result._conversion = resolved.conversionNote;
2508
- }
2509
- return result;
2577
+ return normalizeResponse(response);
2510
2578
  }
2511
2579
  },
2512
2580
  {
@@ -2814,13 +2882,6 @@ function registerFuturesAlgoTools() {
2814
2882
  handler: async (rawArgs, context) => {
2815
2883
  const args = asRecord(rawArgs);
2816
2884
  const reduceOnly = args.reduceOnly;
2817
- const resolved = await resolveQuoteCcySz(
2818
- requireString(args, "instId"),
2819
- requireString(args, "sz"),
2820
- readString(args, "tgtCcy"),
2821
- "FUTURES",
2822
- context.client
2823
- );
2824
2885
  const response = await context.client.privatePost(
2825
2886
  "/api/v5/trade/order-algo",
2826
2887
  compactObject({
@@ -2829,8 +2890,8 @@ function registerFuturesAlgoTools() {
2829
2890
  side: requireString(args, "side"),
2830
2891
  posSide: readString(args, "posSide"),
2831
2892
  ordType: requireString(args, "ordType"),
2832
- sz: resolved.sz,
2833
- tgtCcy: resolved.tgtCcy,
2893
+ sz: requireString(args, "sz"),
2894
+ tgtCcy: readString(args, "tgtCcy"),
2834
2895
  tpTriggerPx: readString(args, "tpTriggerPx"),
2835
2896
  tpOrdPx: readString(args, "tpOrdPx"),
2836
2897
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -2846,11 +2907,7 @@ function registerFuturesAlgoTools() {
2846
2907
  }),
2847
2908
  privateRateLimit("futures_place_algo_order", 20)
2848
2909
  );
2849
- const result = normalizeResponse(response);
2850
- if (resolved.conversionNote) {
2851
- result._conversion = resolved.conversionNote;
2852
- }
2853
- return result;
2910
+ return normalizeResponse(response);
2854
2911
  }
2855
2912
  },
2856
2913
  {
@@ -3181,19 +3238,19 @@ function safeWriteFile(targetDir, fileName, data) {
3181
3238
  throw new Error(`Invalid file name: "${fileName}"`);
3182
3239
  }
3183
3240
  const resolvedDir = resolve(targetDir);
3184
- const filePath = join(resolvedDir, safeName);
3241
+ const filePath = join3(resolvedDir, safeName);
3185
3242
  const resolvedPath = resolve(filePath);
3186
3243
  if (!resolvedPath.startsWith(resolvedDir + sep)) {
3187
3244
  throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
3188
3245
  }
3189
- mkdirSync(resolvedDir, { recursive: true });
3246
+ mkdirSync2(resolvedDir, { recursive: true });
3190
3247
  const tmpPath = `${resolvedPath}.${randomUUID()}.tmp`;
3191
3248
  try {
3192
- writeFileSync(tmpPath, data);
3193
- renameSync(tmpPath, resolvedPath);
3249
+ writeFileSync2(tmpPath, data);
3250
+ renameSync2(tmpPath, resolvedPath);
3194
3251
  } catch (err) {
3195
3252
  try {
3196
- unlinkSync(tmpPath);
3253
+ unlinkSync2(tmpPath);
3197
3254
  } catch {
3198
3255
  }
3199
3256
  throw err;
@@ -3258,7 +3315,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3258
3315
  const maxFiles = limits?.maxFiles ?? DEFAULT_MAX_FILES;
3259
3316
  const maxCompressionRatio = limits?.maxCompressionRatio ?? DEFAULT_MAX_COMPRESSION_RATIO;
3260
3317
  const resolvedTarget = resolve2(targetDir);
3261
- mkdirSync2(resolvedTarget, { recursive: true });
3318
+ mkdirSync3(resolvedTarget, { recursive: true });
3262
3319
  return new Promise((resolvePromise, reject) => {
3263
3320
  yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
3264
3321
  if (err) return reject(err);
@@ -3281,7 +3338,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3281
3338
  zipfile.close();
3282
3339
  return reject(streamErr);
3283
3340
  }
3284
- mkdirSync2(dirname(resolvedPath), { recursive: true });
3341
+ mkdirSync3(dirname2(resolvedPath), { recursive: true });
3285
3342
  const writeStream = createWriteStream(resolvedPath);
3286
3343
  readStream.pipe(writeStream);
3287
3344
  writeStream.on("close", () => zipfile.readEntry());
@@ -3297,11 +3354,11 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3297
3354
  });
3298
3355
  }
3299
3356
  function readMetaJson(contentDir) {
3300
- const metaPath = join2(contentDir, "_meta.json");
3357
+ const metaPath = join4(contentDir, "_meta.json");
3301
3358
  if (!existsSync(metaPath)) {
3302
3359
  throw new Error(`_meta.json not found in ${contentDir}. Invalid skill package.`);
3303
3360
  }
3304
- const raw = readFileSync(metaPath, "utf-8");
3361
+ const raw = readFileSync2(metaPath, "utf-8");
3305
3362
  let parsed;
3306
3363
  try {
3307
3364
  parsed = JSON.parse(raw);
@@ -3323,26 +3380,26 @@ function readMetaJson(contentDir) {
3323
3380
  };
3324
3381
  }
3325
3382
  function validateSkillMdExists(contentDir) {
3326
- const skillMdPath = join2(contentDir, "SKILL.md");
3383
+ const skillMdPath = join4(contentDir, "SKILL.md");
3327
3384
  if (!existsSync(skillMdPath)) {
3328
3385
  throw new Error(`SKILL.md not found in ${contentDir}. Invalid skill package.`);
3329
3386
  }
3330
3387
  }
3331
- var DEFAULT_REGISTRY_PATH = join3(homedir(), ".okx", "skills", "registry.json");
3388
+ var DEFAULT_REGISTRY_PATH = join5(homedir3(), ".okx", "skills", "registry.json");
3332
3389
  function readRegistry(registryPath = DEFAULT_REGISTRY_PATH) {
3333
3390
  if (!existsSync2(registryPath)) {
3334
3391
  return { version: 1, skills: {} };
3335
3392
  }
3336
3393
  try {
3337
- const raw = readFileSync2(registryPath, "utf-8");
3394
+ const raw = readFileSync3(registryPath, "utf-8");
3338
3395
  return JSON.parse(raw);
3339
3396
  } catch {
3340
3397
  return { version: 1, skills: {} };
3341
3398
  }
3342
3399
  }
3343
3400
  function writeRegistry(registry, registryPath = DEFAULT_REGISTRY_PATH) {
3344
- mkdirSync3(dirname2(registryPath), { recursive: true });
3345
- writeFileSync2(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
3401
+ mkdirSync4(dirname3(registryPath), { recursive: true });
3402
+ writeFileSync3(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
3346
3403
  }
3347
3404
  function upsertSkillRecord(meta, registryPath = DEFAULT_REGISTRY_PATH) {
3348
3405
  const registry = readRegistry(registryPath);
@@ -3415,7 +3472,7 @@ function registerSkillsTools() {
3415
3472
  {
3416
3473
  name: "skills_download",
3417
3474
  module: "skills",
3418
- description: "Download a skill zip file from OKX Skills Marketplace to a local directory. Always call skills_search first to confirm the skill name exists. Downloads the latest approved version. NOTE: Downloads third-party developer content as a zip \u2014 does NOT install to agents. For full installation use CLI: okx skill add <name>. Use when the user wants to inspect or manually install a skill package.",
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.",
3419
3476
  inputSchema: {
3420
3477
  type: "object",
3421
3478
  properties: {
@@ -4563,7 +4620,7 @@ function registerDcdTools() {
4563
4620
  {
4564
4621
  name: "dcd_get_products",
4565
4622
  module: "earn.dcd",
4566
- description: "Get DCD products with yield and quota info. Yields in response are decimal fractions, not percentages.",
4623
+ description: "Get DCD products with yield and quota info.",
4567
4624
  isWrite: false,
4568
4625
  inputSchema: {
4569
4626
  type: "object",
@@ -4617,7 +4674,7 @@ function registerDcdTools() {
4617
4674
  {
4618
4675
  name: "dcd_get_orders",
4619
4676
  module: "earn.dcd",
4620
- description: "Get DCD order history. Yields in response are decimal fractions, not percentages.",
4677
+ description: "Get DCD order history.",
4621
4678
  isWrite: false,
4622
4679
  inputSchema: {
4623
4680
  type: "object",
@@ -4661,7 +4718,7 @@ function registerDcdTools() {
4661
4718
  {
4662
4719
  name: "dcd_subscribe",
4663
4720
  module: "earn.dcd",
4664
- 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).",
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).",
4665
4722
  isWrite: true,
4666
4723
  inputSchema: {
4667
4724
  type: "object",
@@ -4700,23 +4757,13 @@ function registerDcdTools() {
4700
4757
  });
4701
4758
  }
4702
4759
  if (minAnnualizedYield !== void 0) {
4703
- const rawYield = parseFloat(quote["annualizedYield"]);
4704
- if (isNaN(rawYield)) {
4760
+ const actualYield = parseFloat(quote["annualizedYield"]);
4761
+ if (!isNaN(actualYield) && actualYield < minAnnualizedYield) {
4705
4762
  throw new OkxApiError(
4706
- "Quote returned non-numeric annualizedYield, cannot verify minimum yield threshold.",
4707
- {
4708
- code: "INVALID_YIELD_VALUE",
4709
- suggestion: "Order not placed. The quote did not include a valid annualizedYield. Retry or pick a different product."
4710
- }
4711
- );
4712
- }
4713
- const actualYieldPct = rawYield * 100;
4714
- if (actualYieldPct < minAnnualizedYield) {
4715
- throw new OkxApiError(
4716
- `Quote yield ${actualYieldPct.toFixed(2)}% is below the minimum threshold of ${minAnnualizedYield}%.`,
4763
+ `Quote yield ${actualYield}% is below the minimum threshold of ${minAnnualizedYield}%.`,
4717
4764
  {
4718
4765
  code: "YIELD_BELOW_MIN",
4719
- suggestion: `Order not placed. Actual: ${actualYieldPct.toFixed(2)}%, required: >= ${minAnnualizedYield}%. Try a different product or lower your minimum yield.`
4766
+ suggestion: `Order not placed. Actual: ${actualYield}%, required: >= ${minAnnualizedYield}%. Try a different product or lower your minimum yield.`
4720
4767
  }
4721
4768
  );
4722
4769
  }
@@ -4946,13 +4993,6 @@ function buildContractTradeTools(cfg) {
4946
4993
  const args = asRecord(rawArgs);
4947
4994
  const reduceOnly = args.reduceOnly;
4948
4995
  const attachAlgoOrds = buildAttachAlgoOrds(args);
4949
- const resolved = await resolveQuoteCcySz(
4950
- requireString(args, "instId"),
4951
- requireString(args, "sz"),
4952
- readString(args, "tgtCcy"),
4953
- defaultType,
4954
- context.client
4955
- );
4956
4996
  const response = await context.client.privatePost(
4957
4997
  "/api/v5/trade/order",
4958
4998
  compactObject({
@@ -4961,8 +5001,8 @@ function buildContractTradeTools(cfg) {
4961
5001
  side: requireString(args, "side"),
4962
5002
  posSide: readString(args, "posSide"),
4963
5003
  ordType: requireString(args, "ordType"),
4964
- sz: resolved.sz,
4965
- tgtCcy: resolved.tgtCcy,
5004
+ sz: requireString(args, "sz"),
5005
+ tgtCcy: readString(args, "tgtCcy"),
4966
5006
  px: readString(args, "px"),
4967
5007
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
4968
5008
  clOrdId: readString(args, "clOrdId"),
@@ -4971,11 +5011,7 @@ function buildContractTradeTools(cfg) {
4971
5011
  }),
4972
5012
  privateRateLimit(n("place_order"), 60)
4973
5013
  );
4974
- const result = normalizeResponse(response);
4975
- if (resolved.conversionNote) {
4976
- result._conversion = resolved.conversionNote;
4977
- }
4978
- return result;
5014
+ return normalizeResponse(response);
4979
5015
  }
4980
5016
  },
4981
5017
  // ── cancel_order ─────────────────────────────────────────────────────────
@@ -6050,7 +6086,7 @@ function registerOptionAlgoTools() {
6050
6086
  tgtCcy: {
6051
6087
  type: "string",
6052
6088
  enum: ["base_ccy", "quote_ccy"],
6053
- description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT (auto-converted to contracts)"
6089
+ description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT (may not be supported for options)"
6054
6090
  },
6055
6091
  reduceOnly: {
6056
6092
  type: "boolean",
@@ -6066,13 +6102,6 @@ function registerOptionAlgoTools() {
6066
6102
  handler: async (rawArgs, context) => {
6067
6103
  const args = asRecord(rawArgs);
6068
6104
  const reduceOnly = readBoolean(args, "reduceOnly");
6069
- const resolved = await resolveQuoteCcySz(
6070
- requireString(args, "instId"),
6071
- requireString(args, "sz"),
6072
- readString(args, "tgtCcy"),
6073
- "OPTION",
6074
- context.client
6075
- );
6076
6105
  const response = await context.client.privatePost(
6077
6106
  "/api/v5/trade/order-algo",
6078
6107
  compactObject({
@@ -6080,8 +6109,8 @@ function registerOptionAlgoTools() {
6080
6109
  tdMode: requireString(args, "tdMode"),
6081
6110
  side: requireString(args, "side"),
6082
6111
  ordType: requireString(args, "ordType"),
6083
- sz: resolved.sz,
6084
- tgtCcy: resolved.tgtCcy,
6112
+ sz: requireString(args, "sz"),
6113
+ tgtCcy: readString(args, "tgtCcy"),
6085
6114
  tpTriggerPx: readString(args, "tpTriggerPx"),
6086
6115
  tpOrdPx: readString(args, "tpOrdPx"),
6087
6116
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -6288,12 +6317,7 @@ function registerOptionTools() {
6288
6317
  },
6289
6318
  sz: {
6290
6319
  type: "string",
6291
- description: "Contracts count by default. Set tgtCcy=quote_ccy to use USDT amount instead."
6292
- },
6293
- tgtCcy: {
6294
- type: "string",
6295
- enum: ["base_ccy", "quote_ccy"],
6296
- description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT (auto-converted to contracts)"
6320
+ description: "Contracts count (NOT USDT). Use market_get_instruments for ctVal."
6297
6321
  },
6298
6322
  px: {
6299
6323
  type: "string",
@@ -6330,13 +6354,6 @@ function registerOptionTools() {
6330
6354
  const args = asRecord(rawArgs);
6331
6355
  const reduceOnly = args.reduceOnly;
6332
6356
  const attachAlgoOrds = buildAttachAlgoOrds(args);
6333
- const resolved = await resolveQuoteCcySz(
6334
- requireString(args, "instId"),
6335
- requireString(args, "sz"),
6336
- readString(args, "tgtCcy"),
6337
- "OPTION",
6338
- context.client
6339
- );
6340
6357
  const response = await context.client.privatePost(
6341
6358
  "/api/v5/trade/order",
6342
6359
  compactObject({
@@ -6344,8 +6361,7 @@ function registerOptionTools() {
6344
6361
  tdMode: requireString(args, "tdMode"),
6345
6362
  side: requireString(args, "side"),
6346
6363
  ordType: requireString(args, "ordType"),
6347
- sz: resolved.sz,
6348
- tgtCcy: resolved.tgtCcy,
6364
+ sz: requireString(args, "sz"),
6349
6365
  px: readString(args, "px"),
6350
6366
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
6351
6367
  clOrdId: readString(args, "clOrdId"),
@@ -7501,12 +7517,12 @@ function createToolRunner(client, config) {
7501
7517
  };
7502
7518
  }
7503
7519
  function configFilePath() {
7504
- return join4(homedir2(), ".okx", "config.toml");
7520
+ return join6(homedir4(), ".okx", "config.toml");
7505
7521
  }
7506
7522
  function readFullConfig() {
7507
7523
  const path42 = configFilePath();
7508
7524
  if (!existsSync3(path42)) return { profiles: {} };
7509
- const raw = readFileSync3(path42, "utf-8");
7525
+ const raw = readFileSync4(path42, "utf-8");
7510
7526
  try {
7511
7527
  return parse(raw);
7512
7528
  } catch (err) {
@@ -7534,11 +7550,11 @@ var CONFIG_HEADER = `# OKX Trade Kit Configuration
7534
7550
  `;
7535
7551
  function writeFullConfig(config) {
7536
7552
  const path42 = configFilePath();
7537
- const dir = dirname3(path42);
7553
+ const dir = dirname4(path42);
7538
7554
  if (!existsSync3(dir)) {
7539
- mkdirSync4(dir, { recursive: true });
7555
+ mkdirSync5(dir, { recursive: true });
7540
7556
  }
7541
- writeFileSync3(path42, CONFIG_HEADER + stringify(config), "utf-8");
7557
+ writeFileSync4(path42, CONFIG_HEADER + stringify(config), "utf-8");
7542
7558
  }
7543
7559
  function expandShorthand(moduleId) {
7544
7560
  if (moduleId === "all") return [...MODULES];
@@ -7652,21 +7668,21 @@ function loadConfig(cli) {
7652
7668
  verbose: cli.verbose ?? false
7653
7669
  };
7654
7670
  }
7655
- var CACHE_FILE = join5(homedir3(), ".okx", "update-check.json");
7671
+ var CACHE_FILE = join7(homedir5(), ".okx", "update-check.json");
7656
7672
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7657
- function readCache() {
7673
+ function readCache2() {
7658
7674
  try {
7659
7675
  if (existsSync4(CACHE_FILE)) {
7660
- return JSON.parse(readFileSync4(CACHE_FILE, "utf-8"));
7676
+ return JSON.parse(readFileSync5(CACHE_FILE, "utf-8"));
7661
7677
  }
7662
7678
  } catch {
7663
7679
  }
7664
7680
  return {};
7665
7681
  }
7666
- function writeCache(cache) {
7682
+ function writeCache2(cache) {
7667
7683
  try {
7668
- mkdirSync5(join5(homedir3(), ".okx"), { recursive: true });
7669
- writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7684
+ mkdirSync6(join7(homedir5(), ".okx"), { recursive: true });
7685
+ writeFileSync5(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7670
7686
  } catch {
7671
7687
  }
7672
7688
  }
@@ -7713,14 +7729,14 @@ async function fetchLatestVersion(packageName) {
7713
7729
  function refreshCacheInBackground(packageName) {
7714
7730
  fetchLatestVersion(packageName).then((latest) => {
7715
7731
  if (!latest) return;
7716
- const cache = readCache();
7732
+ const cache = readCache2();
7717
7733
  cache[packageName] = { latestVersion: latest, checkedAt: Date.now() };
7718
- writeCache(cache);
7734
+ writeCache2(cache);
7719
7735
  }).catch(() => {
7720
7736
  });
7721
7737
  }
7722
7738
  function checkForUpdates(packageName, currentVersion) {
7723
- const cache = readCache();
7739
+ const cache = readCache2();
7724
7740
  const entry = cache[packageName];
7725
7741
  if (entry && isNewerVersion(currentVersion, entry.latestVersion)) {
7726
7742
  process.stderr.write(
@@ -7901,10 +7917,6 @@ var activeOutput = stdioOutput;
7901
7917
  function setOutput(impl) {
7902
7918
  activeOutput = impl;
7903
7919
  }
7904
- var envContext = null;
7905
- function setEnvContext(ctx) {
7906
- envContext = ctx;
7907
- }
7908
7920
  function output(message) {
7909
7921
  activeOutput.out(message);
7910
7922
  }
@@ -7918,18 +7930,9 @@ function errorLine(message) {
7918
7930
  activeOutput.err(message + EOL);
7919
7931
  }
7920
7932
  function printJson(data) {
7921
- const payload = envContext ? {
7922
- env: envContext.demo ? "demo" : "live",
7923
- profile: envContext.profile,
7924
- data
7925
- } : data;
7926
- activeOutput.out(JSON.stringify(payload, null, 2) + EOL);
7933
+ activeOutput.out(JSON.stringify(data, null, 2) + EOL);
7927
7934
  }
7928
7935
  function printTable(rows) {
7929
- if (envContext) {
7930
- const envLabel = envContext.demo ? "demo (simulated trading)" : "live";
7931
- activeOutput.out(`Environment: ${envLabel}` + EOL + EOL);
7932
- }
7933
7936
  if (rows.length === 0) {
7934
7937
  activeOutput.out("(no data)" + EOL);
7935
7938
  return;
@@ -8508,7 +8511,7 @@ async function cmdDiagnoseMcp(options = {}) {
8508
8511
 
8509
8512
  // src/commands/diagnose.ts
8510
8513
  var CLI_VERSION = readCliVersion();
8511
- var GIT_HASH = true ? "ab11ece" : "dev";
8514
+ var GIT_HASH = true ? "19e8da3" : "dev";
8512
8515
  function maskKey2(key) {
8513
8516
  if (!key) return "(not set)";
8514
8517
  if (key.length <= 8) return "****";
@@ -8805,24 +8808,24 @@ async function runCliChecks(config, profile, outputPath) {
8805
8808
 
8806
8809
  // src/commands/upgrade.ts
8807
8810
  import { spawnSync as spawnSync2 } from "child_process";
8808
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
8809
- import { dirname as dirname5, join as join7 } from "path";
8810
- import { homedir as homedir5 } from "os";
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";
8811
8814
  var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
8812
- var CACHE_FILE2 = join7(homedir5(), ".okx", "last_check");
8815
+ var CACHE_FILE2 = join9(homedir7(), ".okx", "last_check");
8813
8816
  var THROTTLE_MS = 12 * 60 * 60 * 1e3;
8814
- var NPM_BIN = join7(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
8817
+ var NPM_BIN = join9(dirname6(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
8815
8818
  function readLastCheck() {
8816
8819
  try {
8817
- return parseInt(readFileSync6(CACHE_FILE2, "utf-8").trim(), 10) || 0;
8820
+ return parseInt(readFileSync7(CACHE_FILE2, "utf-8").trim(), 10) || 0;
8818
8821
  } catch {
8819
8822
  return 0;
8820
8823
  }
8821
8824
  }
8822
8825
  function writeLastCheck() {
8823
8826
  try {
8824
- mkdirSync7(join7(homedir5(), ".okx"), { recursive: true });
8825
- writeFileSync6(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
8827
+ mkdirSync8(join9(homedir7(), ".okx"), { recursive: true });
8828
+ writeFileSync7(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
8826
8829
  } catch {
8827
8830
  }
8828
8831
  }
@@ -10040,10 +10043,6 @@ async function cmdMarketCandles(run, instId, opts) {
10040
10043
  }))
10041
10044
  );
10042
10045
  }
10043
- function cmdMarketIndicatorList(json) {
10044
- if (json) return printJson(KNOWN_INDICATORS);
10045
- printTable(KNOWN_INDICATORS.map(({ name, description }) => ({ name, description })));
10046
- }
10047
10046
  async function cmdMarketIndicator(run, indicator, instId, opts) {
10048
10047
  const params = opts.params ? opts.params.split(",").map((p) => Number(p.trim())).filter((n) => !Number.isNaN(n)) : void 0;
10049
10048
  const result = await run("market_get_indicator", {
@@ -11361,7 +11360,6 @@ async function cmdOptionPlace(run, opts) {
11361
11360
  side: opts.side,
11362
11361
  ordType: opts.ordType,
11363
11362
  sz: opts.sz,
11364
- tgtCcy: opts.tgtCcy,
11365
11363
  px: opts.px,
11366
11364
  reduceOnly: opts.reduceOnly,
11367
11365
  clOrdId: opts.clOrdId,
@@ -12408,7 +12406,7 @@ async function cmdDcdRedeemExecute(run, opts) {
12408
12406
  ordId: r["ordId"],
12409
12407
  state: r["state"],
12410
12408
  redeemSz: q["redeemSz"] ? `${parseFloat(q["redeemSz"]).toFixed(8)} ${q["redeemCcy"]}` : "\u2014",
12411
- termRate: q["termRate"] ? `${(parseFloat(q["termRate"]) * 100).toFixed(2)}%` : "\u2014"
12409
+ termRate: q["termRate"] ? `${q["termRate"]}%` : "\u2014"
12412
12410
  });
12413
12411
  }
12414
12412
  async function cmdDcdOrderState(run, opts) {
@@ -12461,7 +12459,7 @@ async function cmdDcdOrders(run, opts) {
12461
12459
  quoteCcy: r["quoteCcy"],
12462
12460
  strike: r["strike"],
12463
12461
  notionalSz: r["notionalSz"],
12464
- annualizedYield: r["annualizedYield"] ? `${(parseFloat(r["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
12462
+ annualizedYield: r["annualizedYield"],
12465
12463
  yieldSz: r["yieldSz"],
12466
12464
  settleTime: r["settleTime"] ? new Date(Number(r["settleTime"])).toLocaleDateString() : "",
12467
12465
  // scheduled settlement time
@@ -12501,7 +12499,7 @@ async function cmdDcdQuoteAndBuy(run, opts) {
12501
12499
  outputLine("Quote:");
12502
12500
  printKv({
12503
12501
  quoteId: q["quoteId"],
12504
- annualizedYield: q["annualizedYield"] ? `${(parseFloat(q["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
12502
+ annualizedYield: q["annualizedYield"] ? `${q["annualizedYield"]}%` : "\u2014",
12505
12503
  absYield: q["absYield"],
12506
12504
  notionalSz: q["notionalSz"],
12507
12505
  notionalCcy: q["notionalCcy"]
@@ -12525,17 +12523,16 @@ async function cmdDcdQuoteAndBuy(run, opts) {
12525
12523
  }
12526
12524
 
12527
12525
  // src/commands/skill.ts
12528
- import { tmpdir, homedir as homedir7 } from "os";
12529
- import { join as join9, dirname as dirname6 } from "path";
12530
- import { mkdirSync as mkdirSync8, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
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";
12531
12529
  import { execFileSync as execFileSync2 } from "child_process";
12532
12530
  import { randomUUID as randomUUID2 } from "crypto";
12533
12531
  function resolveNpx() {
12534
- const sibling = join9(dirname6(process.execPath), "npx");
12532
+ const sibling = join11(dirname7(process.execPath), "npx");
12535
12533
  if (existsSync7(sibling)) return sibling;
12536
12534
  return "npx";
12537
12535
  }
12538
- var THIRD_PARTY_INSTALL_NOTICE = "Note: This skill was created by a third-party developer, not by OKX. Review SKILL.md before use.";
12539
12536
  async function cmdSkillSearch(run, opts) {
12540
12537
  const args = {};
12541
12538
  if (opts.keyword) args.keyword = opts.keyword;
@@ -12585,13 +12582,13 @@ async function cmdSkillCategories(run, json) {
12585
12582
  outputLine("");
12586
12583
  }
12587
12584
  async function cmdSkillAdd(name, config, json) {
12588
- const tmpBase = join9(tmpdir(), `okx-skill-${randomUUID2()}`);
12589
- mkdirSync8(tmpBase, { recursive: true });
12585
+ const tmpBase = join11(tmpdir(), `okx-skill-${randomUUID2()}`);
12586
+ mkdirSync9(tmpBase, { recursive: true });
12590
12587
  try {
12591
12588
  outputLine(`Downloading ${name}...`);
12592
12589
  const client = new OkxRestClient(config);
12593
12590
  const zipPath = await downloadSkillZip(client, name, tmpBase);
12594
- const contentDir = await extractSkillZip(zipPath, join9(tmpBase, "content"));
12591
+ const contentDir = await extractSkillZip(zipPath, join11(tmpBase, "content"));
12595
12592
  const meta = readMetaJson(contentDir);
12596
12593
  validateSkillMdExists(contentDir);
12597
12594
  outputLine("Installing to detected agents...");
@@ -12601,7 +12598,7 @@ async function cmdSkillAdd(name, config, json) {
12601
12598
  timeout: 6e4
12602
12599
  });
12603
12600
  } catch (e) {
12604
- const savedZip = join9(process.cwd(), `${name}.zip`);
12601
+ const savedZip = join11(process.cwd(), `${name}.zip`);
12605
12602
  try {
12606
12603
  copyFileSync2(zipPath, savedZip);
12607
12604
  } catch {
@@ -12611,7 +12608,11 @@ async function cmdSkillAdd(name, config, json) {
12611
12608
  throw e;
12612
12609
  }
12613
12610
  upsertSkillRecord(meta);
12614
- printSkillInstallResult(meta, json);
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
+ }
12615
12616
  } finally {
12616
12617
  rmSync(tmpBase, { recursive: true, force: true });
12617
12618
  }
@@ -12640,7 +12641,7 @@ function cmdSkillRemove(name, json) {
12640
12641
  timeout: 6e4
12641
12642
  });
12642
12643
  } catch {
12643
- const agentsPath = join9(homedir7(), ".agents", "skills", name);
12644
+ const agentsPath = join11(homedir9(), ".agents", "skills", name);
12644
12645
  try {
12645
12646
  rmSync(agentsPath, { recursive: true, force: true });
12646
12647
  } catch {
@@ -12704,19 +12705,11 @@ function cmdSkillList(json) {
12704
12705
  outputLine("");
12705
12706
  outputLine(`${skills.length} skills installed.`);
12706
12707
  }
12707
- function printSkillInstallResult(meta, json) {
12708
- if (json) {
12709
- outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
12710
- } else {
12711
- outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
12712
- outputLine(` ${THIRD_PARTY_INSTALL_NOTICE}`);
12713
- }
12714
- }
12715
12708
 
12716
12709
  // src/index.ts
12717
12710
  var _require3 = createRequire3(import.meta.url);
12718
12711
  var CLI_VERSION2 = _require3("../package.json").version;
12719
- var GIT_HASH2 = true ? "ab11ece" : "dev";
12712
+ var GIT_HASH2 = true ? "19e8da3" : "dev";
12720
12713
  function handleConfigCommand(action, rest, json, lang, force) {
12721
12714
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
12722
12715
  if (action === "show") return cmdConfigShow(json);
@@ -12766,20 +12759,18 @@ function handleMarketPublicCommand(run, action, rest, v, json) {
12766
12759
  instId: v.instId,
12767
12760
  json
12768
12761
  });
12769
- if (action === "indicator") return handleIndicatorAction(run, rest, v, json);
12770
- }
12771
- function handleIndicatorAction(run, rest, v, json) {
12772
- if (rest[0] === "list") return cmdMarketIndicatorList(json);
12773
- const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
12774
- const backtestTime = v["backtest-time"] !== void 0 ? Number(v["backtest-time"]) : void 0;
12775
- return cmdMarketIndicator(run, rest[0], rest[1], {
12776
- bar: v.bar,
12777
- params: v.params,
12778
- list: v.list,
12779
- limit,
12780
- backtestTime,
12781
- json
12782
- });
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
+ }
12783
12774
  }
12784
12775
  function handleMarketDataCommand(run, action, rest, v, json) {
12785
12776
  const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
@@ -13125,7 +13116,6 @@ function handleOptionCommand(run, action, rest, v, json) {
13125
13116
  side: v.side,
13126
13117
  ordType: v.ordType,
13127
13118
  sz: v.sz,
13128
- tgtCcy: v.tgtCcy,
13129
13119
  px: v.px,
13130
13120
  reduceOnly: v.reduceOnly,
13131
13121
  clOrdId: v.clOrdId,
@@ -13534,14 +13524,6 @@ function printHelpForLevel(positionals) {
13534
13524
  else if (!subgroup) printHelp(module);
13535
13525
  else printHelp(module, subgroup);
13536
13526
  }
13537
- async function runDiagnose(v) {
13538
- let config;
13539
- try {
13540
- config = loadProfileConfig({ profile: v.profile, demo: v.demo, live: v.live, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
13541
- } catch {
13542
- }
13543
- return cmdDiagnose(config, v.profile ?? "default", { mcp: v.mcp, cli: v.cli, all: v.all, output: v.output });
13544
- }
13545
13527
  async function main() {
13546
13528
  setOutput({
13547
13529
  out: (m) => process.stdout.write(m),
@@ -13563,9 +13545,15 @@ async function main() {
13563
13545
  if (module === "config") return handleConfigCommand(action, rest, json, v.lang, v.force);
13564
13546
  if (module === "setup") return handleSetupCommand(v);
13565
13547
  if (module === "upgrade") return cmdUpgrade(CLI_VERSION2, { beta: v.beta, check: v.check, force: v.force }, json);
13566
- if (module === "diagnose") return runDiagnose(v);
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
+ }
13567
13556
  const config = loadProfileConfig({ profile: v.profile, demo: v.demo, live: v.live, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
13568
- setEnvContext({ demo: config.demo, profile: v.profile ?? "default" });
13569
13557
  const client = new OkxRestClient(config);
13570
13558
  const baseRunner = createToolRunner(client, config);
13571
13559
  const writeToolNames = new Set(allToolSpecs().filter((t) => t.isWrite).map((t) => t.name));