@okx_ai/okx-trade-cli 1.3.0-beta.2 → 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,16 +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";
857
- import fs2 from "fs";
858
- import path2 from "path";
859
- import os2 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";
860
869
  import * as fs3 from "fs";
861
870
  import * as path3 from "path";
862
871
  import * as os3 from "os";
863
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
+ }
864
997
  function getNow() {
865
998
  return (/* @__PURE__ */ new Date()).toISOString();
866
999
  }
@@ -1079,6 +1212,14 @@ var OkxRestClient = class _OkxRestClient {
1079
1212
  config;
1080
1213
  rateLimiter;
1081
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;
1082
1223
  constructor(config) {
1083
1224
  this.config = config;
1084
1225
  this.rateLimiter = new RateLimiter(3e4, config.verbose);
@@ -1086,6 +1227,97 @@ var OkxRestClient = class _OkxRestClient {
1086
1227
  this.dispatcher = new ProxyAgent(config.proxyUrl);
1087
1228
  }
1088
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
+ }
1089
1321
  logRequest(method, url, auth) {
1090
1322
  if (!this.config.verbose) return;
1091
1323
  vlog(`\u2192 ${method} ${url}`);
@@ -1255,13 +1487,17 @@ var OkxRestClient = class _OkxRestClient {
1255
1487
  * Security: validates Content-Type and enforces maxBytes limit.
1256
1488
  */
1257
1489
  async privatePostBinary(path42, body, opts) {
1490
+ await this.ensureDoh();
1258
1491
  const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
1259
1492
  const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
1260
1493
  const bodyJson = body ? JSON.stringify(body) : "";
1261
1494
  const endpoint = `POST ${path42}`;
1262
- this.logRequest("POST", `${this.config.baseUrl}${path42}`, "private");
1495
+ this.logRequest("POST", `${this.activeBaseUrl}${path42}`, "private");
1263
1496
  const reqConfig = { method: "POST", path: path42, auth: "private" };
1264
1497
  const headers = this.buildHeaders(reqConfig, path42, bodyJson, getNow());
1498
+ if (this.dohNode) {
1499
+ headers.set("User-Agent", this.dohUserAgent);
1500
+ }
1265
1501
  const t0 = Date.now();
1266
1502
  const response = await this.fetchBinary(path42, endpoint, headers, bodyJson, t0);
1267
1503
  const elapsed = Date.now() - t0;
@@ -1295,10 +1531,10 @@ var OkxRestClient = class _OkxRestClient {
1295
1531
  method: "POST",
1296
1532
  headers,
1297
1533
  body: bodyJson || void 0,
1298
- signal: AbortSignal.timeout(this.config.timeoutMs)
1534
+ signal: AbortSignal.timeout(this.config.timeoutMs),
1535
+ dispatcher: this.activeDispatcher
1299
1536
  };
1300
- if (this.dispatcher) fetchOptions.dispatcher = this.dispatcher;
1301
- return await fetch(`${this.config.baseUrl}${path42}`, fetchOptions);
1537
+ return await fetch(`${this.activeBaseUrl}${path42}`, fetchOptions);
1302
1538
  } catch (error) {
1303
1539
  if (this.config.verbose) {
1304
1540
  vlog(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
@@ -1329,9 +1565,10 @@ var OkxRestClient = class _OkxRestClient {
1329
1565
  // JSON request
1330
1566
  // ---------------------------------------------------------------------------
1331
1567
  async request(reqConfig) {
1568
+ await this.ensureDoh();
1332
1569
  const queryString = buildQueryString(reqConfig.query);
1333
1570
  const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
1334
- const url = `${this.config.baseUrl}${requestPath}`;
1571
+ const url = `${this.activeBaseUrl}${requestPath}`;
1335
1572
  const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
1336
1573
  const timestamp = getNow();
1337
1574
  this.logRequest(reqConfig.method, url, reqConfig.auth);
@@ -1339,6 +1576,9 @@ var OkxRestClient = class _OkxRestClient {
1339
1576
  await this.rateLimiter.consume(reqConfig.rateLimit);
1340
1577
  }
1341
1578
  const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
1579
+ if (this.dohNode) {
1580
+ headers.set("User-Agent", this.dohUserAgent);
1581
+ }
1342
1582
  const t0 = Date.now();
1343
1583
  let response;
1344
1584
  try {
@@ -1346,13 +1586,20 @@ var OkxRestClient = class _OkxRestClient {
1346
1586
  method: reqConfig.method,
1347
1587
  headers,
1348
1588
  body: reqConfig.method === "POST" ? bodyJson : void 0,
1349
- signal: AbortSignal.timeout(this.config.timeoutMs)
1589
+ signal: AbortSignal.timeout(this.config.timeoutMs),
1590
+ dispatcher: this.activeDispatcher
1350
1591
  };
1351
- if (this.dispatcher) {
1352
- fetchOptions.dispatcher = this.dispatcher;
1353
- }
1354
1592
  response = await fetch(url, fetchOptions);
1355
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
+ }
1356
1603
  if (this.config.verbose) {
1357
1604
  const elapsed2 = Date.now() - t0;
1358
1605
  const cause = error instanceof Error ? error.message : String(error);
@@ -1367,6 +1614,19 @@ var OkxRestClient = class _OkxRestClient {
1367
1614
  const rawText = await response.text();
1368
1615
  const elapsed = Date.now() - t0;
1369
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
+ }
1370
1630
  return this.processResponse(rawText, response, elapsed, traceId, reqConfig, requestPath);
1371
1631
  }
1372
1632
  };
@@ -1506,137 +1766,14 @@ var INDICATOR_BARS = [
1506
1766
  "1Wutc"
1507
1767
  ];
1508
1768
  var INDICATOR_CODE_OVERRIDES = {
1509
- // Aliases
1510
- "boll": "BB",
1511
- // server supports BB not BOLL
1512
- // Names where default rule produces underscores but backend uses no separator
1513
1769
  "rainbow": "BTCRAINBOW",
1514
- // default: RAINBOW
1770
+ "range-filter": "RANGEFILTER",
1515
1771
  "stoch-rsi": "STOCHRSI",
1516
- // default: STOCH_RSI
1517
- "bull-engulf": "BULLENGULF",
1518
- // default: BULL_ENGULF
1519
- "bear-engulf": "BEARENGULF",
1520
- // default: BEAR_ENGULF
1521
- "bull-harami": "BULLHARAMI",
1522
- // default: BULL_HARAMI
1523
- "bear-harami": "BEARHARAMI",
1524
- // default: BEAR_HARAMI
1525
- "bull-harami-cross": "BULLHARAMICROSS",
1526
- // default: BULL_HARAMI_CROSS
1527
- "bear-harami-cross": "BEARHARAMICROSS",
1528
- // default: BEAR_HARAMI_CROSS
1529
- "three-soldiers": "THREESOLDIERS",
1530
- // default: THREE_SOLDIERS
1531
- "three-crows": "THREECROWS",
1532
- // default: THREE_CROWS
1533
- "hanging-man": "HANGINGMAN",
1534
- // default: HANGING_MAN
1535
- "inverted-hammer": "INVERTEDH",
1536
- // default: INVERTED_HAMMER (backend uses INVERTEDH)
1537
- "shooting-star": "SHOOTINGSTAR",
1538
- // default: SHOOTING_STAR
1539
- "nvi-pvi": "NVIPVI",
1540
- // default: NVI_PVI
1541
- "top-long-short": "TOPLONGSHORT"
1542
- // default: TOP_LONG_SHORT
1543
- // 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"
1544
1776
  };
1545
- var KNOWN_INDICATORS = [
1546
- // Moving Averages
1547
- { name: "ma", description: "Simple Moving Average" },
1548
- { name: "ema", description: "Exponential Moving Average" },
1549
- { name: "wma", description: "Weighted Moving Average" },
1550
- { name: "dema", description: "Double Exponential Moving Average" },
1551
- { name: "tema", description: "Triple Exponential Moving Average" },
1552
- { name: "zlema", description: "Zero-Lag Exponential Moving Average" },
1553
- { name: "hma", description: "Hull Moving Average" },
1554
- { name: "kama", description: "Kaufman Adaptive Moving Average" },
1555
- // Trend
1556
- { name: "macd", description: "MACD" },
1557
- { name: "sar", description: "Parabolic SAR" },
1558
- { name: "adx", description: "Average Directional Index" },
1559
- { name: "aroon", description: "Aroon Indicator" },
1560
- { name: "cci", description: "Commodity Channel Index" },
1561
- { name: "dpo", description: "Detrended Price Oscillator" },
1562
- { name: "envelope", description: "Envelope" },
1563
- { name: "halftrend", description: "HalfTrend" },
1564
- { name: "alphatrend", description: "AlphaTrend" },
1565
- // Momentum
1566
- { name: "rsi", description: "Relative Strength Index" },
1567
- { name: "stoch-rsi", description: "Stochastic RSI" },
1568
- { name: "stoch", description: "Stochastic Oscillator" },
1569
- { name: "roc", description: "Rate of Change" },
1570
- { name: "mom", description: "Momentum" },
1571
- { name: "ppo", description: "Price Percentage Oscillator" },
1572
- { name: "trix", description: "TRIX" },
1573
- { name: "ao", description: "Awesome Oscillator" },
1574
- { name: "uo", description: "Ultimate Oscillator" },
1575
- { name: "wr", description: "Williams %R" },
1576
- // Volatility
1577
- { name: "bb", description: "Bollinger Bands" },
1578
- { name: "boll", description: "Bollinger Bands (alias for bb)" },
1579
- { name: "bbwidth", description: "Bollinger Band Width" },
1580
- { name: "bbpct", description: "Bollinger Band %B" },
1581
- { name: "atr", description: "Average True Range" },
1582
- { name: "keltner", description: "Keltner Channel" },
1583
- { name: "donchian", description: "Donchian Channel" },
1584
- { name: "hv", description: "Historical Volatility" },
1585
- { name: "stddev", description: "Standard Deviation" },
1586
- // Volume
1587
- { name: "obv", description: "On-Balance Volume" },
1588
- { name: "vwap", description: "Volume Weighted Average Price" },
1589
- { name: "mvwap", description: "Moving VWAP" },
1590
- { name: "cmf", description: "Chaikin Money Flow" },
1591
- { name: "mfi", description: "Money Flow Index" },
1592
- { name: "ad", description: "Accumulation/Distribution" },
1593
- // Statistical
1594
- { name: "lr", description: "Linear Regression" },
1595
- { name: "slope", description: "Linear Regression Slope" },
1596
- { name: "angle", description: "Linear Regression Angle" },
1597
- { name: "variance", description: "Variance" },
1598
- { name: "meandev", description: "Mean Deviation" },
1599
- { name: "sigma", description: "Sigma" },
1600
- { name: "stderr", description: "Standard Error" },
1601
- // Custom
1602
- { name: "kdj", description: "KDJ Stochastic Oscillator" },
1603
- { name: "supertrend", description: "Supertrend" },
1604
- // Ichimoku
1605
- { name: "tenkan", description: "Ichimoku Tenkan-sen (Conversion Line)" },
1606
- { name: "kijun", description: "Ichimoku Kijun-sen (Base Line)" },
1607
- { name: "senkoa", description: "Ichimoku Senkou Span A (Leading Span A)" },
1608
- { name: "senkob", description: "Ichimoku Senkou Span B (Leading Span B)" },
1609
- { name: "chikou", description: "Ichimoku Chikou Span (Lagging Span)" },
1610
- // Candlestick Patterns
1611
- { name: "doji", description: "Doji candlestick pattern" },
1612
- { name: "bull-engulf", description: "Bullish Engulfing pattern" },
1613
- { name: "bear-engulf", description: "Bearish Engulfing pattern" },
1614
- { name: "bull-harami", description: "Bullish Harami pattern" },
1615
- { name: "bear-harami", description: "Bearish Harami pattern" },
1616
- { name: "bull-harami-cross", description: "Bullish Harami Cross pattern" },
1617
- { name: "bear-harami-cross", description: "Bearish Harami Cross pattern" },
1618
- { name: "three-soldiers", description: "Three White Soldiers pattern" },
1619
- { name: "three-crows", description: "Three Black Crows pattern" },
1620
- { name: "hanging-man", description: "Hanging Man pattern" },
1621
- { name: "inverted-hammer", description: "Inverted Hammer pattern" },
1622
- { name: "shooting-star", description: "Shooting Star pattern" },
1623
- // Bitcoin On-Chain
1624
- { name: "ahr999", description: "AHR999 Bitcoin accumulation index" },
1625
- { name: "rainbow", description: "Bitcoin Rainbow Chart" },
1626
- // Other
1627
- { name: "fisher", description: "Fisher Transform" },
1628
- { name: "nvi-pvi", description: "Negative/Positive Volume Index (returns both)" },
1629
- { name: "pmax", description: "PMAX" },
1630
- { name: "qqe", description: "QQE Mod" },
1631
- { name: "tdi", description: "Traders Dynamic Index" },
1632
- { name: "waddah", description: "Waddah Attar Explosion" },
1633
- { name: "range-filter", description: "Range Filter" },
1634
- { name: "cho", description: "Chande Momentum Oscillator" },
1635
- { name: "tr", description: "True Range" },
1636
- { name: "tp", description: "Typical Price" },
1637
- { name: "mp", description: "Median Price" },
1638
- { name: "top-long-short", description: "Top Trader Long/Short Ratio (timeframe-independent)" }
1639
- ];
1640
1777
  function resolveIndicatorCode(name) {
1641
1778
  const lower = name.toLowerCase();
1642
1779
  return INDICATOR_CODE_OVERRIDES[lower] ?? name.toUpperCase().replace(/-/g, "_");
@@ -1663,7 +1800,7 @@ function registerIndicatorTools() {
1663
1800
  },
1664
1801
  indicator: {
1665
1802
  type: "string",
1666
- 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"
1667
1804
  },
1668
1805
  bar: {
1669
1806
  type: "string",
@@ -1720,14 +1857,6 @@ function registerIndicatorTools() {
1720
1857
  );
1721
1858
  return normalizeResponse(response);
1722
1859
  }
1723
- },
1724
- {
1725
- name: "market_list_indicators",
1726
- module: "market",
1727
- description: "List all supported technical indicator names and descriptions. Call this before market_get_indicator to discover valid indicator names. No credentials required.",
1728
- isWrite: false,
1729
- inputSchema: { type: "object", properties: {} },
1730
- handler: async () => ({ data: KNOWN_INDICATORS })
1731
1860
  }
1732
1861
  ];
1733
1862
  }
@@ -2325,112 +2454,6 @@ function registerAccountTools() {
2325
2454
  }
2326
2455
  ];
2327
2456
  }
2328
- function extractInstrumentParams(instId, data) {
2329
- const instruments = Array.isArray(data) ? data : [];
2330
- if (instruments.length === 0) {
2331
- throw new Error(`Failed to fetch instrument info for ${instId}: empty instrument list. Cannot determine ctVal for conversion.`);
2332
- }
2333
- const inst = instruments[0];
2334
- const ctValStr = String(inst.ctVal ?? "");
2335
- const ctVal = parseFloat(ctValStr);
2336
- if (!isFinite(ctVal) || ctVal <= 0) {
2337
- throw new Error(`Invalid ctVal "${ctValStr}" for ${instId}. ctVal must be a positive number for conversion.`);
2338
- }
2339
- const minSzStr = String(inst.minSz ?? "1");
2340
- const minSz = parseFloat(minSzStr);
2341
- if (!isFinite(minSz) || minSz <= 0) {
2342
- throw new Error(`Invalid minSz "${minSzStr}" for ${instId}. minSz must be a positive number for conversion.`);
2343
- }
2344
- const lotSzStr = String(inst.lotSz ?? "1");
2345
- const lotSz = parseFloat(lotSzStr);
2346
- if (!isFinite(lotSz) || lotSz <= 0) {
2347
- throw new Error(`Invalid lotSz "${lotSzStr}" for ${instId}. lotSz must be a positive number for conversion.`);
2348
- }
2349
- return { ctVal, ctValStr, minSz, minSzStr, lotSz, lotSzStr };
2350
- }
2351
- function extractLastPx(instId, data) {
2352
- const tickers = Array.isArray(data) ? data : [];
2353
- if (tickers.length === 0) {
2354
- throw new Error(`Failed to fetch ticker price for ${instId}: empty ticker response. Cannot determine last price for conversion.`);
2355
- }
2356
- const lastStr = String(tickers[0].last ?? "");
2357
- const lastPx = parseFloat(lastStr);
2358
- if (!isFinite(lastPx) || lastPx <= 0) {
2359
- throw new Error(`Invalid last price "${lastStr}" for ${instId}. Last price must be a positive number for conversion.`);
2360
- }
2361
- return { lastPx, lastStr };
2362
- }
2363
- function extractLeverage(instId, mgnMode, data) {
2364
- const leverageData = Array.isArray(data) ? data : [];
2365
- if (leverageData.length === 0) {
2366
- throw new Error(
2367
- `Failed to fetch leverage info for ${instId} (mgnMode=${mgnMode}): empty response. Cannot determine leverage for margin conversion. Please set leverage first using set_leverage.`
2368
- );
2369
- }
2370
- const leverStr = String(leverageData[0].lever ?? "1");
2371
- const lever = parseFloat(leverStr);
2372
- if (!isFinite(lever) || lever <= 0) {
2373
- throw new Error(`Invalid leverage "${leverStr}" for ${instId}. Leverage must be a positive number for margin conversion.`);
2374
- }
2375
- return { lever, leverStr };
2376
- }
2377
- function computeContracts(p) {
2378
- const { instId, sz, isMarginMode, inst, lastPx, lastStr, lever, leverStr } = p;
2379
- const { ctVal, ctValStr, minSz, minSzStr, lotSz, lotSzStr } = inst;
2380
- const userAmount = parseFloat(sz);
2381
- const contractValue = ctVal * lastPx;
2382
- const effectiveNotional = isMarginMode ? userAmount * lever : userAmount;
2383
- const lotSzDecimals = lotSzStr.includes(".") ? lotSzStr.split(".")[1].length : 0;
2384
- const precision = 10 ** (lotSzDecimals + 4);
2385
- const rawContracts = Math.round(effectiveNotional / contractValue * precision) / precision;
2386
- const rawLots = Math.round(rawContracts / lotSz * precision) / precision;
2387
- const contractsRounded = parseFloat((Math.floor(rawLots) * lotSz).toFixed(lotSzDecimals));
2388
- if (contractsRounded < minSz) {
2389
- const minAmount = isMarginMode ? (minSz * contractValue / lever).toFixed(2) : (minSz * contractValue).toFixed(2);
2390
- const unit = isMarginMode ? "USDT margin" : "USDT";
2391
- throw new Error(
2392
- `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}` : "") + `).
2393
- `
2394
- );
2395
- }
2396
- const contractsStr = contractsRounded.toFixed(lotSzDecimals);
2397
- 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})`;
2398
- return { contractsStr, conversionNote };
2399
- }
2400
- async function resolveQuoteCcySz(instId, sz, tgtCcy, instType, client, tdMode) {
2401
- if (tgtCcy !== "quote_ccy" && tgtCcy !== "margin") {
2402
- return { sz, tgtCcy, conversionNote: void 0 };
2403
- }
2404
- const isMarginMode = tgtCcy === "margin";
2405
- if (isMarginMode && !tdMode) {
2406
- throw new Error(
2407
- "tdMode (cross or isolated) is required when tgtCcy=margin. Cannot determine leverage without knowing the margin mode."
2408
- );
2409
- }
2410
- const mgnMode = tdMode === "cross" ? "cross" : "isolated";
2411
- const fetchPromises = [
2412
- client.publicGet("/api/v5/public/instruments", { instType, instId }),
2413
- client.publicGet("/api/v5/market/ticker", { instId })
2414
- ];
2415
- if (isMarginMode) {
2416
- fetchPromises.push(client.privateGet("/api/v5/account/leverage-info", { instId, mgnMode }));
2417
- }
2418
- const results = await Promise.all(fetchPromises);
2419
- const inst = extractInstrumentParams(instId, results[0].data);
2420
- const { lastPx, lastStr } = extractLastPx(instId, results[1].data);
2421
- const { lever, leverStr } = isMarginMode ? extractLeverage(instId, mgnMode, results[2].data) : { lever: 1, leverStr: "1" };
2422
- const { contractsStr, conversionNote } = computeContracts({
2423
- instId,
2424
- sz,
2425
- isMarginMode,
2426
- inst,
2427
- lastPx,
2428
- lastStr,
2429
- lever,
2430
- leverStr
2431
- });
2432
- return { sz: contractsStr, tgtCcy: void 0, conversionNote };
2433
- }
2434
2457
  function registerAlgoTradeTools() {
2435
2458
  return [
2436
2459
  {
@@ -2509,8 +2532,8 @@ function registerAlgoTradeTools() {
2509
2532
  },
2510
2533
  tgtCcy: {
2511
2534
  type: "string",
2512
- enum: ["base_ccy", "quote_ccy", "margin"],
2513
- 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)"
2535
+ enum: ["base_ccy", "quote_ccy"],
2536
+ description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT"
2514
2537
  },
2515
2538
  reduceOnly: {
2516
2539
  type: "boolean",
@@ -2526,14 +2549,6 @@ function registerAlgoTradeTools() {
2526
2549
  handler: async (rawArgs, context) => {
2527
2550
  const args = asRecord(rawArgs);
2528
2551
  const reduceOnly = args.reduceOnly;
2529
- const resolved = await resolveQuoteCcySz(
2530
- requireString(args, "instId"),
2531
- requireString(args, "sz"),
2532
- readString(args, "tgtCcy"),
2533
- "SWAP",
2534
- context.client,
2535
- readString(args, "tdMode")
2536
- );
2537
2552
  const response = await context.client.privatePost(
2538
2553
  "/api/v5/trade/order-algo",
2539
2554
  compactObject({
@@ -2542,8 +2557,8 @@ function registerAlgoTradeTools() {
2542
2557
  side: requireString(args, "side"),
2543
2558
  posSide: readString(args, "posSide"),
2544
2559
  ordType: requireString(args, "ordType"),
2545
- sz: resolved.sz,
2546
- tgtCcy: resolved.tgtCcy,
2560
+ sz: requireString(args, "sz"),
2561
+ tgtCcy: readString(args, "tgtCcy"),
2547
2562
  tpTriggerPx: readString(args, "tpTriggerPx"),
2548
2563
  tpOrdPx: readString(args, "tpOrdPx"),
2549
2564
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -2559,11 +2574,7 @@ function registerAlgoTradeTools() {
2559
2574
  }),
2560
2575
  privateRateLimit("swap_place_algo_order", 20)
2561
2576
  );
2562
- const result = normalizeResponse(response);
2563
- if (resolved.conversionNote) {
2564
- result._conversion = resolved.conversionNote;
2565
- }
2566
- return result;
2577
+ return normalizeResponse(response);
2567
2578
  }
2568
2579
  },
2569
2580
  {
@@ -2854,8 +2865,8 @@ function registerFuturesAlgoTools() {
2854
2865
  },
2855
2866
  tgtCcy: {
2856
2867
  type: "string",
2857
- enum: ["base_ccy", "quote_ccy", "margin"],
2858
- 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)"
2868
+ enum: ["base_ccy", "quote_ccy"],
2869
+ description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT"
2859
2870
  },
2860
2871
  reduceOnly: {
2861
2872
  type: "boolean",
@@ -2871,14 +2882,6 @@ function registerFuturesAlgoTools() {
2871
2882
  handler: async (rawArgs, context) => {
2872
2883
  const args = asRecord(rawArgs);
2873
2884
  const reduceOnly = args.reduceOnly;
2874
- const resolved = await resolveQuoteCcySz(
2875
- requireString(args, "instId"),
2876
- requireString(args, "sz"),
2877
- readString(args, "tgtCcy"),
2878
- "FUTURES",
2879
- context.client,
2880
- readString(args, "tdMode")
2881
- );
2882
2885
  const response = await context.client.privatePost(
2883
2886
  "/api/v5/trade/order-algo",
2884
2887
  compactObject({
@@ -2887,8 +2890,8 @@ function registerFuturesAlgoTools() {
2887
2890
  side: requireString(args, "side"),
2888
2891
  posSide: readString(args, "posSide"),
2889
2892
  ordType: requireString(args, "ordType"),
2890
- sz: resolved.sz,
2891
- tgtCcy: resolved.tgtCcy,
2893
+ sz: requireString(args, "sz"),
2894
+ tgtCcy: readString(args, "tgtCcy"),
2892
2895
  tpTriggerPx: readString(args, "tpTriggerPx"),
2893
2896
  tpOrdPx: readString(args, "tpOrdPx"),
2894
2897
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -2904,11 +2907,7 @@ function registerFuturesAlgoTools() {
2904
2907
  }),
2905
2908
  privateRateLimit("futures_place_algo_order", 20)
2906
2909
  );
2907
- const result = normalizeResponse(response);
2908
- if (resolved.conversionNote) {
2909
- result._conversion = resolved.conversionNote;
2910
- }
2911
- return result;
2910
+ return normalizeResponse(response);
2912
2911
  }
2913
2912
  },
2914
2913
  {
@@ -3239,19 +3238,19 @@ function safeWriteFile(targetDir, fileName, data) {
3239
3238
  throw new Error(`Invalid file name: "${fileName}"`);
3240
3239
  }
3241
3240
  const resolvedDir = resolve(targetDir);
3242
- const filePath = join(resolvedDir, safeName);
3241
+ const filePath = join3(resolvedDir, safeName);
3243
3242
  const resolvedPath = resolve(filePath);
3244
3243
  if (!resolvedPath.startsWith(resolvedDir + sep)) {
3245
3244
  throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
3246
3245
  }
3247
- mkdirSync(resolvedDir, { recursive: true });
3246
+ mkdirSync2(resolvedDir, { recursive: true });
3248
3247
  const tmpPath = `${resolvedPath}.${randomUUID()}.tmp`;
3249
3248
  try {
3250
- writeFileSync(tmpPath, data);
3251
- renameSync(tmpPath, resolvedPath);
3249
+ writeFileSync2(tmpPath, data);
3250
+ renameSync2(tmpPath, resolvedPath);
3252
3251
  } catch (err) {
3253
3252
  try {
3254
- unlinkSync(tmpPath);
3253
+ unlinkSync2(tmpPath);
3255
3254
  } catch {
3256
3255
  }
3257
3256
  throw err;
@@ -3316,7 +3315,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3316
3315
  const maxFiles = limits?.maxFiles ?? DEFAULT_MAX_FILES;
3317
3316
  const maxCompressionRatio = limits?.maxCompressionRatio ?? DEFAULT_MAX_COMPRESSION_RATIO;
3318
3317
  const resolvedTarget = resolve2(targetDir);
3319
- mkdirSync2(resolvedTarget, { recursive: true });
3318
+ mkdirSync3(resolvedTarget, { recursive: true });
3320
3319
  return new Promise((resolvePromise, reject) => {
3321
3320
  yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
3322
3321
  if (err) return reject(err);
@@ -3339,7 +3338,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3339
3338
  zipfile.close();
3340
3339
  return reject(streamErr);
3341
3340
  }
3342
- mkdirSync2(dirname(resolvedPath), { recursive: true });
3341
+ mkdirSync3(dirname2(resolvedPath), { recursive: true });
3343
3342
  const writeStream = createWriteStream(resolvedPath);
3344
3343
  readStream.pipe(writeStream);
3345
3344
  writeStream.on("close", () => zipfile.readEntry());
@@ -3355,11 +3354,11 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3355
3354
  });
3356
3355
  }
3357
3356
  function readMetaJson(contentDir) {
3358
- const metaPath = join2(contentDir, "_meta.json");
3357
+ const metaPath = join4(contentDir, "_meta.json");
3359
3358
  if (!existsSync(metaPath)) {
3360
3359
  throw new Error(`_meta.json not found in ${contentDir}. Invalid skill package.`);
3361
3360
  }
3362
- const raw = readFileSync(metaPath, "utf-8");
3361
+ const raw = readFileSync2(metaPath, "utf-8");
3363
3362
  let parsed;
3364
3363
  try {
3365
3364
  parsed = JSON.parse(raw);
@@ -3381,26 +3380,26 @@ function readMetaJson(contentDir) {
3381
3380
  };
3382
3381
  }
3383
3382
  function validateSkillMdExists(contentDir) {
3384
- const skillMdPath = join2(contentDir, "SKILL.md");
3383
+ const skillMdPath = join4(contentDir, "SKILL.md");
3385
3384
  if (!existsSync(skillMdPath)) {
3386
3385
  throw new Error(`SKILL.md not found in ${contentDir}. Invalid skill package.`);
3387
3386
  }
3388
3387
  }
3389
- var DEFAULT_REGISTRY_PATH = join3(homedir(), ".okx", "skills", "registry.json");
3388
+ var DEFAULT_REGISTRY_PATH = join5(homedir3(), ".okx", "skills", "registry.json");
3390
3389
  function readRegistry(registryPath = DEFAULT_REGISTRY_PATH) {
3391
3390
  if (!existsSync2(registryPath)) {
3392
3391
  return { version: 1, skills: {} };
3393
3392
  }
3394
3393
  try {
3395
- const raw = readFileSync2(registryPath, "utf-8");
3394
+ const raw = readFileSync3(registryPath, "utf-8");
3396
3395
  return JSON.parse(raw);
3397
3396
  } catch {
3398
3397
  return { version: 1, skills: {} };
3399
3398
  }
3400
3399
  }
3401
3400
  function writeRegistry(registry, registryPath = DEFAULT_REGISTRY_PATH) {
3402
- mkdirSync3(dirname2(registryPath), { recursive: true });
3403
- 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");
3404
3403
  }
3405
3404
  function upsertSkillRecord(meta, registryPath = DEFAULT_REGISTRY_PATH) {
3406
3405
  const registry = readRegistry(registryPath);
@@ -3473,7 +3472,7 @@ function registerSkillsTools() {
3473
3472
  {
3474
3473
  name: "skills_download",
3475
3474
  module: "skills",
3476
- 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.",
3477
3476
  inputSchema: {
3478
3477
  type: "object",
3479
3478
  properties: {
@@ -4079,7 +4078,7 @@ function registerEarnTools() {
4079
4078
  {
4080
4079
  name: "earn_get_savings_balance",
4081
4080
  module: "earn.savings",
4082
- 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.",
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.",
4083
4082
  isWrite: false,
4084
4083
  inputSchema: {
4085
4084
  type: "object",
@@ -4100,43 +4099,6 @@ function registerEarnTools() {
4100
4099
  return normalizeResponse(response);
4101
4100
  }
4102
4101
  },
4103
- {
4104
- name: "earn_get_fixed_order_list",
4105
- module: "earn.savings",
4106
- 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.",
4107
- isWrite: false,
4108
- inputSchema: {
4109
- type: "object",
4110
- properties: {
4111
- ccy: {
4112
- type: "string",
4113
- description: "Currency, e.g. USDT. Omit for all."
4114
- },
4115
- state: {
4116
- type: "string",
4117
- 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."
4118
- }
4119
- }
4120
- },
4121
- handler: async (rawArgs, context) => {
4122
- const args = asRecord(rawArgs);
4123
- const response = await context.client.privateGet(
4124
- "/api/v5/finance/simple-earn-fixed/order-list",
4125
- compactObject({
4126
- ccy: readString(args, "ccy"),
4127
- state: readString(args, "state")
4128
- }),
4129
- privateRateLimit("earn_get_fixed_order_list", 3)
4130
- );
4131
- const result = normalizeResponse(response);
4132
- if (Array.isArray(result["data"])) {
4133
- result["data"] = result["data"].map(
4134
- ({ finalSettlementDate: _, ...rest }) => rest
4135
- );
4136
- }
4137
- return result;
4138
- }
4139
- },
4140
4102
  {
4141
4103
  name: "earn_savings_purchase",
4142
4104
  module: "earn.savings",
@@ -4281,112 +4243,10 @@ function registerEarnTools() {
4281
4243
  return normalizeResponse(response);
4282
4244
  }
4283
4245
  },
4284
- {
4285
- name: "earn_fixed_purchase",
4286
- module: "earn.savings",
4287
- 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.",
4288
- isWrite: true,
4289
- inputSchema: {
4290
- type: "object",
4291
- properties: {
4292
- ccy: {
4293
- type: "string",
4294
- description: "Currency, e.g. USDT"
4295
- },
4296
- amt: {
4297
- type: "string",
4298
- description: "Purchase amount"
4299
- },
4300
- term: {
4301
- type: "string",
4302
- description: "Term, e.g. 90D"
4303
- },
4304
- confirm: {
4305
- type: "boolean",
4306
- 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."
4307
- }
4308
- },
4309
- required: ["ccy", "amt", "term"]
4310
- },
4311
- handler: async (rawArgs, context) => {
4312
- const args = asRecord(rawArgs);
4313
- const ccy = requireString(args, "ccy");
4314
- const amt = requireString(args, "amt");
4315
- const term = requireString(args, "term");
4316
- const confirm = readBoolean(args, "confirm") ?? false;
4317
- if (!confirm) {
4318
- const [rateResponse, fixedResponse] = await Promise.all([
4319
- context.client.publicGet(
4320
- "/api/v5/finance/savings/lending-rate-history",
4321
- compactObject({ ccy, limit: 1 }),
4322
- publicRateLimit("earn_get_lending_rate_history", 6)
4323
- ),
4324
- context.client.privateGet(
4325
- "/api/v5/finance/simple-earn-fixed/offers",
4326
- compactObject({ ccy }),
4327
- privateRateLimit("earn_fixed_purchase_preview_offers", 2)
4328
- ).catch(() => null)
4329
- ]);
4330
- const rateResult = normalizeResponse(rateResponse);
4331
- const fixedResult = fixedResponse ? normalizeResponse(fixedResponse) : { data: [] };
4332
- const rateArr = Array.isArray(rateResult["data"]) ? rateResult["data"] : [];
4333
- const allOffers = Array.isArray(fixedResult["data"]) ? fixedResult["data"] : [];
4334
- const matchedOffer = allOffers.find(
4335
- (o) => o["term"] === term && o["ccy"] === ccy
4336
- );
4337
- const { borrowingOrderQuota: _, ...offerWithoutTotal } = matchedOffer ?? {};
4338
- const offerWithSoldOut = matchedOffer ? { ...offerWithoutTotal, soldOut: offerWithoutTotal["lendQuota"] === "0" } : null;
4339
- return {
4340
- preview: true,
4341
- ccy,
4342
- amt,
4343
- term,
4344
- offer: offerWithSoldOut,
4345
- currentFlexibleRate: rateArr[0]?.["lendingRate"] ?? null,
4346
- 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."
4347
- };
4348
- }
4349
- assertNotDemo(context.config, "earn_fixed_purchase");
4350
- const response = await context.client.privatePost(
4351
- "/api/v5/finance/simple-earn-fixed/purchase",
4352
- { ccy, amt, term },
4353
- privateRateLimit("earn_fixed_purchase", 2)
4354
- );
4355
- return normalizeResponse(response);
4356
- }
4357
- },
4358
- {
4359
- name: "earn_fixed_redeem",
4360
- module: "earn.savings",
4361
- 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.",
4362
- isWrite: true,
4363
- inputSchema: {
4364
- type: "object",
4365
- properties: {
4366
- reqId: {
4367
- type: "string",
4368
- description: "Request ID of the fixed-term order to redeem"
4369
- }
4370
- },
4371
- required: ["reqId"]
4372
- },
4373
- handler: async (rawArgs, context) => {
4374
- assertNotDemo(context.config, "earn_fixed_redeem");
4375
- const args = asRecord(rawArgs);
4376
- const response = await context.client.privatePost(
4377
- "/api/v5/finance/simple-earn-fixed/redeem",
4378
- {
4379
- reqId: requireString(args, "reqId")
4380
- },
4381
- privateRateLimit("earn_fixed_redeem", 2)
4382
- );
4383
- return normalizeResponse(response);
4384
- }
4385
- },
4386
4246
  {
4387
4247
  name: "earn_get_lending_rate_history",
4388
4248
  module: "earn.savings",
4389
- 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.",
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.",
4390
4250
  isWrite: false,
4391
4251
  inputSchema: {
4392
4252
  type: "object",
@@ -4405,45 +4265,23 @@ function registerEarnTools() {
4405
4265
  },
4406
4266
  limit: {
4407
4267
  type: "number",
4408
- description: "Max results (default 7)"
4268
+ description: "Max results (default 100)"
4409
4269
  }
4410
4270
  }
4411
4271
  },
4412
4272
  handler: async (rawArgs, context) => {
4413
4273
  const args = asRecord(rawArgs);
4414
- const ccy = readString(args, "ccy");
4415
- const [rateResponse, fixedResponse] = await Promise.all([
4416
- context.client.publicGet(
4417
- "/api/v5/finance/savings/lending-rate-history",
4418
- compactObject({
4419
- ccy,
4420
- after: readString(args, "after"),
4421
- before: readString(args, "before"),
4422
- limit: readNumber(args, "limit") ?? 7
4423
- }),
4424
- publicRateLimit("earn_get_lending_rate_history", 6)
4425
- ),
4426
- context.client.privateGet(
4427
- "/api/v5/finance/simple-earn-fixed/offers",
4428
- compactObject({ ccy }),
4429
- privateRateLimit("earn_get_lending_rate_history_fixed", 2)
4430
- ).catch(() => null)
4431
- ]);
4432
- const rateResult = normalizeResponse(rateResponse);
4433
- const rateData = Array.isArray(rateResult["data"]) ? rateResult["data"].map(
4434
- ({ rate: _, ...rest }) => rest
4435
- ) : [];
4436
- const fixedResult = fixedResponse ? normalizeResponse(fixedResponse) : { data: [] };
4437
- const allOffers = fixedResult["data"] ?? [];
4438
- const fixedOffers = allOffers.map(({ borrowingOrderQuota: _, ...rest }) => ({
4439
- ...rest,
4440
- soldOut: rest["lendQuota"] === "0"
4441
- }));
4442
- return {
4443
- ...rateResult,
4444
- data: rateData,
4445
- fixedOffers
4446
- };
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);
4447
4285
  }
4448
4286
  }
4449
4287
  ];
@@ -4782,7 +4620,7 @@ function registerDcdTools() {
4782
4620
  {
4783
4621
  name: "dcd_get_products",
4784
4622
  module: "earn.dcd",
4785
- 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.",
4786
4624
  isWrite: false,
4787
4625
  inputSchema: {
4788
4626
  type: "object",
@@ -4836,7 +4674,7 @@ function registerDcdTools() {
4836
4674
  {
4837
4675
  name: "dcd_get_orders",
4838
4676
  module: "earn.dcd",
4839
- description: "Get DCD order history. Yields in response are decimal fractions, not percentages.",
4677
+ description: "Get DCD order history.",
4840
4678
  isWrite: false,
4841
4679
  inputSchema: {
4842
4680
  type: "object",
@@ -4880,7 +4718,7 @@ function registerDcdTools() {
4880
4718
  {
4881
4719
  name: "dcd_subscribe",
4882
4720
  module: "earn.dcd",
4883
- 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).",
4884
4722
  isWrite: true,
4885
4723
  inputSchema: {
4886
4724
  type: "object",
@@ -4919,23 +4757,13 @@ function registerDcdTools() {
4919
4757
  });
4920
4758
  }
4921
4759
  if (minAnnualizedYield !== void 0) {
4922
- const rawYield = parseFloat(quote["annualizedYield"]);
4923
- if (isNaN(rawYield)) {
4924
- throw new OkxApiError(
4925
- "Quote returned non-numeric annualizedYield, cannot verify minimum yield threshold.",
4926
- {
4927
- code: "INVALID_YIELD_VALUE",
4928
- suggestion: "Order not placed. The quote did not include a valid annualizedYield. Retry or pick a different product."
4929
- }
4930
- );
4931
- }
4932
- const actualYieldPct = rawYield * 100;
4933
- if (actualYieldPct < minAnnualizedYield) {
4760
+ const actualYield = parseFloat(quote["annualizedYield"]);
4761
+ if (!isNaN(actualYield) && actualYield < minAnnualizedYield) {
4934
4762
  throw new OkxApiError(
4935
- `Quote yield ${actualYieldPct.toFixed(2)}% is below the minimum threshold of ${minAnnualizedYield}%.`,
4763
+ `Quote yield ${actualYield}% is below the minimum threshold of ${minAnnualizedYield}%.`,
4936
4764
  {
4937
4765
  code: "YIELD_BELOW_MIN",
4938
- 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.`
4939
4767
  }
4940
4768
  );
4941
4769
  }
@@ -5080,7 +4908,7 @@ function registerAutoEarnTools() {
5080
4908
  }
5081
4909
  var EARN_DEMO_MESSAGE = "Earn features (savings, DCD, on-chain staking, auto-earn) are not available in simulated trading mode.";
5082
4910
  var EARN_DEMO_SUGGESTION = "Switch to a live account to use Earn features.";
5083
- var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem", "earn_fixed_purchase"]);
4911
+ var DEMO_GUARD_SKIP = /* @__PURE__ */ new Set(["dcd_redeem"]);
5084
4912
  function withDemoGuard(tool) {
5085
4913
  if (!tool.isWrite || DEMO_GUARD_SKIP.has(tool.name)) return tool;
5086
4914
  const originalHandler = tool.handler;
@@ -5141,12 +4969,12 @@ function buildContractTradeTools(cfg) {
5141
4969
  },
5142
4970
  sz: {
5143
4971
  type: "string",
5144
- 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)."
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."
5145
4973
  },
5146
4974
  tgtCcy: {
5147
4975
  type: "string",
5148
- enum: ["base_ccy", "quote_ccy", "margin"],
5149
- 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)"
4976
+ enum: ["base_ccy", "quote_ccy"],
4977
+ description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT"
5150
4978
  },
5151
4979
  px: { type: "string", description: "Required for limit/post_only/fok/ioc" },
5152
4980
  reduceOnly: {
@@ -5165,14 +4993,6 @@ function buildContractTradeTools(cfg) {
5165
4993
  const args = asRecord(rawArgs);
5166
4994
  const reduceOnly = args.reduceOnly;
5167
4995
  const attachAlgoOrds = buildAttachAlgoOrds(args);
5168
- const resolved = await resolveQuoteCcySz(
5169
- requireString(args, "instId"),
5170
- requireString(args, "sz"),
5171
- readString(args, "tgtCcy"),
5172
- defaultType,
5173
- context.client,
5174
- readString(args, "tdMode")
5175
- );
5176
4996
  const response = await context.client.privatePost(
5177
4997
  "/api/v5/trade/order",
5178
4998
  compactObject({
@@ -5181,8 +5001,8 @@ function buildContractTradeTools(cfg) {
5181
5001
  side: requireString(args, "side"),
5182
5002
  posSide: readString(args, "posSide"),
5183
5003
  ordType: requireString(args, "ordType"),
5184
- sz: resolved.sz,
5185
- tgtCcy: resolved.tgtCcy,
5004
+ sz: requireString(args, "sz"),
5005
+ tgtCcy: readString(args, "tgtCcy"),
5186
5006
  px: readString(args, "px"),
5187
5007
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
5188
5008
  clOrdId: readString(args, "clOrdId"),
@@ -5191,11 +5011,7 @@ function buildContractTradeTools(cfg) {
5191
5011
  }),
5192
5012
  privateRateLimit(n("place_order"), 60)
5193
5013
  );
5194
- const result = normalizeResponse(response);
5195
- if (resolved.conversionNote) {
5196
- result._conversion = resolved.conversionNote;
5197
- }
5198
- return result;
5014
+ return normalizeResponse(response);
5199
5015
  }
5200
5016
  },
5201
5017
  // ── cancel_order ─────────────────────────────────────────────────────────
@@ -6269,8 +6085,8 @@ function registerOptionAlgoTools() {
6269
6085
  },
6270
6086
  tgtCcy: {
6271
6087
  type: "string",
6272
- enum: ["base_ccy", "quote_ccy", "margin"],
6273
- 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)"
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)"
6274
6090
  },
6275
6091
  reduceOnly: {
6276
6092
  type: "boolean",
@@ -6286,14 +6102,6 @@ function registerOptionAlgoTools() {
6286
6102
  handler: async (rawArgs, context) => {
6287
6103
  const args = asRecord(rawArgs);
6288
6104
  const reduceOnly = readBoolean(args, "reduceOnly");
6289
- const resolved = await resolveQuoteCcySz(
6290
- requireString(args, "instId"),
6291
- requireString(args, "sz"),
6292
- readString(args, "tgtCcy"),
6293
- "OPTION",
6294
- context.client,
6295
- readString(args, "tdMode")
6296
- );
6297
6105
  const response = await context.client.privatePost(
6298
6106
  "/api/v5/trade/order-algo",
6299
6107
  compactObject({
@@ -6301,8 +6109,8 @@ function registerOptionAlgoTools() {
6301
6109
  tdMode: requireString(args, "tdMode"),
6302
6110
  side: requireString(args, "side"),
6303
6111
  ordType: requireString(args, "ordType"),
6304
- sz: resolved.sz,
6305
- tgtCcy: resolved.tgtCcy,
6112
+ sz: requireString(args, "sz"),
6113
+ tgtCcy: readString(args, "tgtCcy"),
6306
6114
  tpTriggerPx: readString(args, "tpTriggerPx"),
6307
6115
  tpOrdPx: readString(args, "tpOrdPx"),
6308
6116
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -6509,12 +6317,7 @@ function registerOptionTools() {
6509
6317
  },
6510
6318
  sz: {
6511
6319
  type: "string",
6512
- 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)."
6513
- },
6514
- tgtCcy: {
6515
- type: "string",
6516
- enum: ["base_ccy", "quote_ccy", "margin"],
6517
- 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)"
6320
+ description: "Contracts count (NOT USDT). Use market_get_instruments for ctVal."
6518
6321
  },
6519
6322
  px: {
6520
6323
  type: "string",
@@ -6551,14 +6354,6 @@ function registerOptionTools() {
6551
6354
  const args = asRecord(rawArgs);
6552
6355
  const reduceOnly = args.reduceOnly;
6553
6356
  const attachAlgoOrds = buildAttachAlgoOrds(args);
6554
- const resolved = await resolveQuoteCcySz(
6555
- requireString(args, "instId"),
6556
- requireString(args, "sz"),
6557
- readString(args, "tgtCcy"),
6558
- "OPTION",
6559
- context.client,
6560
- readString(args, "tdMode")
6561
- );
6562
6357
  const response = await context.client.privatePost(
6563
6358
  "/api/v5/trade/order",
6564
6359
  compactObject({
@@ -6566,8 +6361,7 @@ function registerOptionTools() {
6566
6361
  tdMode: requireString(args, "tdMode"),
6567
6362
  side: requireString(args, "side"),
6568
6363
  ordType: requireString(args, "ordType"),
6569
- sz: resolved.sz,
6570
- tgtCcy: resolved.tgtCcy,
6364
+ sz: requireString(args, "sz"),
6571
6365
  px: readString(args, "px"),
6572
6366
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
6573
6367
  clOrdId: readString(args, "clOrdId"),
@@ -7723,12 +7517,12 @@ function createToolRunner(client, config) {
7723
7517
  };
7724
7518
  }
7725
7519
  function configFilePath() {
7726
- return join4(homedir2(), ".okx", "config.toml");
7520
+ return join6(homedir4(), ".okx", "config.toml");
7727
7521
  }
7728
7522
  function readFullConfig() {
7729
7523
  const path42 = configFilePath();
7730
7524
  if (!existsSync3(path42)) return { profiles: {} };
7731
- const raw = readFileSync3(path42, "utf-8");
7525
+ const raw = readFileSync4(path42, "utf-8");
7732
7526
  try {
7733
7527
  return parse(raw);
7734
7528
  } catch (err) {
@@ -7756,11 +7550,11 @@ var CONFIG_HEADER = `# OKX Trade Kit Configuration
7756
7550
  `;
7757
7551
  function writeFullConfig(config) {
7758
7552
  const path42 = configFilePath();
7759
- const dir = dirname3(path42);
7553
+ const dir = dirname4(path42);
7760
7554
  if (!existsSync3(dir)) {
7761
- mkdirSync4(dir, { recursive: true });
7555
+ mkdirSync5(dir, { recursive: true });
7762
7556
  }
7763
- writeFileSync3(path42, CONFIG_HEADER + stringify(config), "utf-8");
7557
+ writeFileSync4(path42, CONFIG_HEADER + stringify(config), "utf-8");
7764
7558
  }
7765
7559
  function expandShorthand(moduleId) {
7766
7560
  if (moduleId === "all") return [...MODULES];
@@ -7874,21 +7668,21 @@ function loadConfig(cli) {
7874
7668
  verbose: cli.verbose ?? false
7875
7669
  };
7876
7670
  }
7877
- var CACHE_FILE = join5(homedir3(), ".okx", "update-check.json");
7671
+ var CACHE_FILE = join7(homedir5(), ".okx", "update-check.json");
7878
7672
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7879
- function readCache() {
7673
+ function readCache2() {
7880
7674
  try {
7881
7675
  if (existsSync4(CACHE_FILE)) {
7882
- return JSON.parse(readFileSync4(CACHE_FILE, "utf-8"));
7676
+ return JSON.parse(readFileSync5(CACHE_FILE, "utf-8"));
7883
7677
  }
7884
7678
  } catch {
7885
7679
  }
7886
7680
  return {};
7887
7681
  }
7888
- function writeCache(cache) {
7682
+ function writeCache2(cache) {
7889
7683
  try {
7890
- mkdirSync5(join5(homedir3(), ".okx"), { recursive: true });
7891
- 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");
7892
7686
  } catch {
7893
7687
  }
7894
7688
  }
@@ -7935,14 +7729,14 @@ async function fetchLatestVersion(packageName) {
7935
7729
  function refreshCacheInBackground(packageName) {
7936
7730
  fetchLatestVersion(packageName).then((latest) => {
7937
7731
  if (!latest) return;
7938
- const cache = readCache();
7732
+ const cache = readCache2();
7939
7733
  cache[packageName] = { latestVersion: latest, checkedAt: Date.now() };
7940
- writeCache(cache);
7734
+ writeCache2(cache);
7941
7735
  }).catch(() => {
7942
7736
  });
7943
7737
  }
7944
7738
  function checkForUpdates(packageName, currentVersion) {
7945
- const cache = readCache();
7739
+ const cache = readCache2();
7946
7740
  const entry = cache[packageName];
7947
7741
  if (entry && isNewerVersion(currentVersion, entry.latestVersion)) {
7948
7742
  process.stderr.write(
@@ -7957,69 +7751,6 @@ Run: npm install -g ${packageName}
7957
7751
  refreshCacheInBackground(packageName);
7958
7752
  }
7959
7753
  }
7960
- var LOG_LEVEL_PRIORITY = {
7961
- error: 0,
7962
- warn: 1,
7963
- info: 2,
7964
- debug: 3
7965
- };
7966
- var SENSITIVE_KEY_PATTERN = /apiKey|secretKey|passphrase|password|secret/i;
7967
- function sanitize(value) {
7968
- if (value === null || value === void 0) {
7969
- return value;
7970
- }
7971
- if (Array.isArray(value)) {
7972
- return value.map(sanitize);
7973
- }
7974
- if (typeof value === "object") {
7975
- const result = {};
7976
- for (const [k, v] of Object.entries(value)) {
7977
- if (SENSITIVE_KEY_PATTERN.test(k)) {
7978
- result[k] = "[REDACTED]";
7979
- } else {
7980
- result[k] = sanitize(v);
7981
- }
7982
- }
7983
- return result;
7984
- }
7985
- return value;
7986
- }
7987
- var TradeLogger = class {
7988
- logLevel;
7989
- logDir;
7990
- constructor(logLevel = "info", logDir) {
7991
- this.logLevel = logLevel;
7992
- this.logDir = logDir ?? path2.join(os2.homedir(), ".okx", "logs");
7993
- }
7994
- getLogPath(date) {
7995
- const d = date ?? /* @__PURE__ */ new Date();
7996
- const yyyy = d.getUTCFullYear();
7997
- const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
7998
- const dd = String(d.getUTCDate()).padStart(2, "0");
7999
- return path2.join(this.logDir, `trade-${yyyy}-${mm}-${dd}.log`);
8000
- }
8001
- log(level, tool, params, result, durationMs) {
8002
- if (LOG_LEVEL_PRIORITY[level] > LOG_LEVEL_PRIORITY[this.logLevel]) {
8003
- return;
8004
- }
8005
- const entry = {
8006
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8007
- level: level.toUpperCase(),
8008
- tool,
8009
- durationMs,
8010
- params: sanitize(params),
8011
- result: sanitize(result)
8012
- };
8013
- try {
8014
- fs2.mkdirSync(this.logDir, { recursive: true });
8015
- fs2.appendFileSync(this.getLogPath(), JSON.stringify(entry) + "\n", "utf8");
8016
- } catch {
8017
- }
8018
- }
8019
- static sanitize(params) {
8020
- return sanitize(params);
8021
- }
8022
- };
8023
7754
  var CLIENT_NAMES = {
8024
7755
  "claude-desktop": "Claude Desktop",
8025
7756
  cursor: "Cursor",
@@ -8169,11 +7900,11 @@ function runSetup(options) {
8169
7900
  // src/commands/diagnose.ts
8170
7901
  import dns from "dns/promises";
8171
7902
  import net from "net";
8172
- import os5 from "os";
7903
+ import os4 from "os";
8173
7904
  import tls from "tls";
8174
7905
 
8175
7906
  // src/commands/diagnose-utils.ts
8176
- import fs4 from "fs";
7907
+ import fs2 from "fs";
8177
7908
  import { createRequire } from "module";
8178
7909
 
8179
7910
  // src/formatter.ts
@@ -8186,10 +7917,6 @@ var activeOutput = stdioOutput;
8186
7917
  function setOutput(impl) {
8187
7918
  activeOutput = impl;
8188
7919
  }
8189
- var envContext = null;
8190
- function setEnvContext(ctx) {
8191
- envContext = ctx;
8192
- }
8193
7920
  function output(message) {
8194
7921
  activeOutput.out(message);
8195
7922
  }
@@ -8202,23 +7929,10 @@ function outputLine(message) {
8202
7929
  function errorLine(message) {
8203
7930
  activeOutput.err(message + EOL);
8204
7931
  }
8205
- var jsonEnvEnabled = false;
8206
- function setJsonEnvEnabled(enabled) {
8207
- jsonEnvEnabled = enabled;
8208
- }
8209
7932
  function printJson(data) {
8210
- const payload = jsonEnvEnabled && envContext ? {
8211
- env: envContext.demo ? "demo" : "live",
8212
- profile: envContext.profile,
8213
- data
8214
- } : data;
8215
- activeOutput.out(JSON.stringify(payload, null, 2) + EOL);
7933
+ activeOutput.out(JSON.stringify(data, null, 2) + EOL);
8216
7934
  }
8217
7935
  function printTable(rows) {
8218
- if (envContext) {
8219
- const envLabel = envContext.demo ? "demo (simulated trading)" : "live";
8220
- activeOutput.out(`Environment: ${envLabel}` + EOL + EOL);
8221
- }
8222
7936
  if (rows.length === 0) {
8223
7937
  activeOutput.out("(no data)" + EOL);
8224
7938
  return;
@@ -8295,7 +8009,7 @@ var Report = class {
8295
8009
  lines.push(`${key.padEnd(14)} ${value}`);
8296
8010
  }
8297
8011
  lines.push(sep2, "");
8298
- fs4.writeFileSync(filePath, lines.join("\n"), "utf8");
8012
+ fs2.writeFileSync(filePath, lines.join("\n"), "utf8");
8299
8013
  return true;
8300
8014
  } catch (_e) {
8301
8015
  return false;
@@ -8330,7 +8044,7 @@ function writeReportIfRequested(report, outputPath) {
8330
8044
  errorLine(` Warning: failed to write report to: ${outputPath}`);
8331
8045
  }
8332
8046
  }
8333
- function sanitize2(value) {
8047
+ function sanitize(value) {
8334
8048
  value = value.replace(
8335
8049
  /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi,
8336
8050
  "****-uuid-****"
@@ -8341,14 +8055,14 @@ function sanitize2(value) {
8341
8055
  }
8342
8056
 
8343
8057
  // src/commands/diagnose-mcp.ts
8344
- import fs5 from "fs";
8345
- import path4 from "path";
8346
- import os4 from "os";
8058
+ import fs4 from "fs";
8059
+ import path2 from "path";
8060
+ import os2 from "os";
8347
8061
  import { spawnSync, spawn } from "child_process";
8348
8062
  import { createRequire as createRequire2 } from "module";
8349
8063
  import { fileURLToPath } from "url";
8350
8064
  var _require2 = createRequire2(import.meta.url);
8351
- var __dirname = path4.dirname(fileURLToPath(import.meta.url));
8065
+ var __dirname = path2.dirname(fileURLToPath(import.meta.url));
8352
8066
  function readMcpVersion() {
8353
8067
  const candidates = [
8354
8068
  // Installed as global or local dependency
@@ -8401,13 +8115,13 @@ function checkMcpEntryPoint(report) {
8401
8115
  if (!entryPath) {
8402
8116
  const candidates = [
8403
8117
  // Installed locally
8404
- path4.join(process.cwd(), "node_modules", ".bin", "okx-trade-mcp"),
8118
+ path2.join(process.cwd(), "node_modules", ".bin", "okx-trade-mcp"),
8405
8119
  // Monorepo workspace (e.g. running from source)
8406
- path4.join(__dirname, "..", "..", "..", "..", "mcp", "dist", "index.js")
8120
+ path2.join(__dirname, "..", "..", "..", "..", "mcp", "dist", "index.js")
8407
8121
  ];
8408
8122
  for (const candidate of candidates) {
8409
8123
  try {
8410
- fs5.accessSync(candidate, fs5.constants.X_OK | fs5.constants.R_OK);
8124
+ fs4.accessSync(candidate, fs4.constants.X_OK | fs4.constants.R_OK);
8411
8125
  entryPath = candidate;
8412
8126
  break;
8413
8127
  } catch (_e) {
@@ -8432,9 +8146,9 @@ var CLIENT_LIMITS = {
8432
8146
  cursor: { perServer: 40, total: 80 }
8433
8147
  };
8434
8148
  function checkJsonMcpConfig(configPath) {
8435
- if (!fs5.existsSync(configPath)) return "missing";
8149
+ if (!fs4.existsSync(configPath)) return "missing";
8436
8150
  try {
8437
- const raw = fs5.readFileSync(configPath, "utf8");
8151
+ const raw = fs4.readFileSync(configPath, "utf8");
8438
8152
  const parsed = JSON.parse(raw);
8439
8153
  const mcpServers = parsed["mcpServers"];
8440
8154
  if (!mcpServers) return "not-configured";
@@ -8452,15 +8166,15 @@ function checkJsonMcpConfig(configPath) {
8452
8166
  }
8453
8167
  }
8454
8168
  function checkClaudeCodeConfig() {
8455
- const home = os4.homedir();
8169
+ const home = os2.homedir();
8456
8170
  const candidates = [
8457
- path4.join(home, ".claude", "settings.json"),
8458
- path4.join(home, ".claude.json")
8171
+ path2.join(home, ".claude", "settings.json"),
8172
+ path2.join(home, ".claude.json")
8459
8173
  ];
8460
8174
  let anyFound = false;
8461
8175
  let anyParseError = false;
8462
8176
  for (const cfgPath of candidates) {
8463
- if (!fs5.existsSync(cfgPath)) continue;
8177
+ if (!fs4.existsSync(cfgPath)) continue;
8464
8178
  anyFound = true;
8465
8179
  const result = checkJsonMcpConfig(cfgPath);
8466
8180
  if (result === "found") return "found";
@@ -8477,8 +8191,8 @@ function handleJsonClient(clientId, report, configuredClients) {
8477
8191
  const status = checkJsonMcpConfig(configPath);
8478
8192
  if (status === "missing") return false;
8479
8193
  if (status === "found") {
8480
- ok(name, `configured (${sanitize2(configPath)})`);
8481
- report.add(`client_${clientId}`, `OK ${sanitize2(configPath)}`);
8194
+ ok(name, `configured (${sanitize(configPath)})`);
8195
+ report.add(`client_${clientId}`, `OK ${sanitize(configPath)}`);
8482
8196
  configuredClients.push(clientId);
8483
8197
  return false;
8484
8198
  }
@@ -8486,8 +8200,8 @@ function handleJsonClient(clientId, report, configuredClients) {
8486
8200
  fail(name, "okx-trade-mcp not found in mcpServers", [`Run: okx setup --client ${clientId}`]);
8487
8201
  report.add(`client_${clientId}`, "NOT_CONFIGURED");
8488
8202
  } else {
8489
- fail(name, `JSON parse error in ${sanitize2(configPath)}`, [
8490
- `Check ${sanitize2(configPath)} for JSON syntax errors`,
8203
+ fail(name, `JSON parse error in ${sanitize(configPath)}`, [
8204
+ `Check ${sanitize(configPath)} for JSON syntax errors`,
8491
8205
  `Then run: okx setup --client ${clientId}`
8492
8206
  ]);
8493
8207
  report.add(`client_${clientId}`, "PARSE_ERROR");
@@ -8580,37 +8294,37 @@ function checkToolCount(report, configuredClients, getSpecs = allToolSpecs) {
8580
8294
  }
8581
8295
  }
8582
8296
  function readLogTail(logPath) {
8583
- const stat = fs5.statSync(logPath);
8297
+ const stat = fs4.statSync(logPath);
8584
8298
  const readSize = Math.min(8192, stat.size);
8585
8299
  const buffer = Buffer.alloc(readSize);
8586
- const fd = fs5.openSync(logPath, "r");
8300
+ const fd = fs4.openSync(logPath, "r");
8587
8301
  try {
8588
- fs5.readSync(fd, buffer, 0, readSize, Math.max(0, stat.size - readSize));
8302
+ fs4.readSync(fd, buffer, 0, readSize, Math.max(0, stat.size - readSize));
8589
8303
  } finally {
8590
- fs5.closeSync(fd);
8304
+ fs4.closeSync(fd);
8591
8305
  }
8592
8306
  return buffer.toString("utf8").split("\n").filter((l) => l.trim()).slice(-5);
8593
8307
  }
8594
8308
  function getMcpLogCandidates() {
8595
8309
  if (process.platform === "darwin") {
8596
- const logsDir = path4.join(os4.homedir(), "Library", "Logs", "Claude");
8310
+ const logsDir = path2.join(os2.homedir(), "Library", "Logs", "Claude");
8597
8311
  const candidates = [
8598
- path4.join(logsDir, "mcp.log"),
8599
- path4.join(logsDir, "mcp-server-okx-trade-mcp.log")
8312
+ path2.join(logsDir, "mcp.log"),
8313
+ path2.join(logsDir, "mcp-server-okx-trade-mcp.log")
8600
8314
  ];
8601
8315
  try {
8602
- const extra = fs5.readdirSync(logsDir).filter((f) => f.startsWith("mcp") && f.endsWith(".log")).map((f) => path4.join(logsDir, f));
8316
+ const extra = fs4.readdirSync(logsDir).filter((f) => f.startsWith("mcp") && f.endsWith(".log")).map((f) => path2.join(logsDir, f));
8603
8317
  candidates.push(...extra);
8604
8318
  } catch (_e) {
8605
8319
  }
8606
8320
  return candidates;
8607
8321
  }
8608
8322
  if (process.platform === "win32") {
8609
- const appData2 = process.env.APPDATA ?? path4.join(os4.homedir(), "AppData", "Roaming");
8610
- return [path4.join(appData2, "Claude", "logs", "mcp.log")];
8323
+ const appData2 = process.env.APPDATA ?? path2.join(os2.homedir(), "AppData", "Roaming");
8324
+ return [path2.join(appData2, "Claude", "logs", "mcp.log")];
8611
8325
  }
8612
- const configHome = process.env.XDG_CONFIG_HOME ?? path4.join(os4.homedir(), ".config");
8613
- return [path4.join(configHome, "Claude", "logs", "mcp.log")];
8326
+ const configHome = process.env.XDG_CONFIG_HOME ?? path2.join(os2.homedir(), ".config");
8327
+ return [path2.join(configHome, "Claude", "logs", "mcp.log")];
8614
8328
  }
8615
8329
  function checkMcpLogs(report) {
8616
8330
  section("MCP Server Logs (recent)");
@@ -8624,7 +8338,7 @@ function checkMcpLogs(report) {
8624
8338
  report.add("mcp_log", logPath);
8625
8339
  if (lines.length > 0) {
8626
8340
  ok("last lines", `(${lines.length} shown)`);
8627
- for (const line of lines) outputLine(` ${sanitize2(line)}`);
8341
+ for (const line of lines) outputLine(` ${sanitize(line)}`);
8628
8342
  } else {
8629
8343
  ok("last lines", "(empty log)");
8630
8344
  }
@@ -8750,11 +8464,11 @@ function checkModuleLoading(entryPath, report) {
8750
8464
  return true;
8751
8465
  } else {
8752
8466
  const errMsg = result.stderr?.trim() || result.error?.message || "non-zero exit";
8753
- fail("module load", `failed: ${sanitize2(errMsg)}`, [
8467
+ fail("module load", `failed: ${sanitize(errMsg)}`, [
8754
8468
  "MCP server may have import errors or missing dependencies",
8755
8469
  `Try: node ${entryPath} --version`
8756
8470
  ]);
8757
- report.add("module_load", `FAIL ${sanitize2(errMsg)}`);
8471
+ report.add("module_load", `FAIL ${sanitize(errMsg)}`);
8758
8472
  return false;
8759
8473
  }
8760
8474
  }
@@ -8765,7 +8479,7 @@ async function cmdDiagnoseMcp(options = {}) {
8765
8479
  const report = new Report();
8766
8480
  report.add("ts", (/* @__PURE__ */ new Date()).toISOString());
8767
8481
  report.add("mode", "mcp");
8768
- report.add("os", `${process.platform} ${process.arch} ${os4.release()}`);
8482
+ report.add("os", `${process.platform} ${process.arch} ${os2.release()}`);
8769
8483
  checkMcpPackageVersion(report);
8770
8484
  const nodePassed = checkNodeCompat(report);
8771
8485
  const { entryPath, passed: entryPassed } = checkMcpEntryPoint(report);
@@ -8797,7 +8511,7 @@ async function cmdDiagnoseMcp(options = {}) {
8797
8511
 
8798
8512
  // src/commands/diagnose.ts
8799
8513
  var CLI_VERSION = readCliVersion();
8800
- var GIT_HASH = true ? "0a5911f" : "dev";
8514
+ var GIT_HASH = true ? "19e8da3" : "dev";
8801
8515
  function maskKey2(key) {
8802
8516
  if (!key) return "(not set)";
8803
8517
  if (key.length <= 8) return "****";
@@ -8877,14 +8591,14 @@ function checkEnvironment(report) {
8877
8591
  }
8878
8592
  ok("CLI", `v${CLI_VERSION} (${GIT_HASH})`);
8879
8593
  ok("OS", `${process.platform} ${process.arch}`);
8880
- ok("OS release", os5.release());
8594
+ ok("OS release", os4.release());
8881
8595
  ok("Shell", process.env.SHELL ?? "(unknown)");
8882
8596
  ok("Locale", `${process.env.LANG ?? process.env.LC_ALL ?? "(unknown)"}`);
8883
8597
  ok("Timezone", Intl.DateTimeFormat().resolvedOptions().timeZone);
8884
8598
  report.add("cli", `${CLI_VERSION} (${GIT_HASH})`);
8885
8599
  report.add("node", `${nodeVersion} ${process.platform} ${process.arch}`);
8886
- const machine = typeof os5.machine === "function" ? os5.machine() : process.arch;
8887
- report.add("os", `${os5.type()} ${os5.release()} ${machine}`);
8600
+ const machine = typeof os4.machine === "function" ? os4.machine() : process.arch;
8601
+ report.add("os", `${os4.type()} ${os4.release()} ${machine}`);
8888
8602
  report.add("shell", process.env.SHELL ?? "-");
8889
8603
  report.add("locale", process.env.LANG ?? process.env.LC_ALL ?? "-");
8890
8604
  report.add("tz", Intl.DateTimeFormat().resolvedOptions().timeZone);
@@ -9041,10 +8755,10 @@ async function cmdDiagnose(config, profile, options = {}) {
9041
8755
  }
9042
8756
  function checkConfigFile(report) {
9043
8757
  section("Config File");
9044
- const path6 = configFilePath();
8758
+ const path5 = configFilePath();
9045
8759
  try {
9046
8760
  readFullConfig();
9047
- ok("Config parse", `${path6} OK`);
8761
+ ok("Config parse", `${path5} OK`);
9048
8762
  report.add("config_parse", "OK");
9049
8763
  return true;
9050
8764
  } catch (e) {
@@ -9094,24 +8808,24 @@ async function runCliChecks(config, profile, outputPath) {
9094
8808
 
9095
8809
  // src/commands/upgrade.ts
9096
8810
  import { spawnSync as spawnSync2 } from "child_process";
9097
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
9098
- import { dirname as dirname5, join as join7 } from "path";
9099
- 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";
9100
8814
  var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
9101
- var CACHE_FILE2 = join7(homedir5(), ".okx", "last_check");
8815
+ var CACHE_FILE2 = join9(homedir7(), ".okx", "last_check");
9102
8816
  var THROTTLE_MS = 12 * 60 * 60 * 1e3;
9103
- 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");
9104
8818
  function readLastCheck() {
9105
8819
  try {
9106
- return parseInt(readFileSync6(CACHE_FILE2, "utf-8").trim(), 10) || 0;
8820
+ return parseInt(readFileSync7(CACHE_FILE2, "utf-8").trim(), 10) || 0;
9107
8821
  } catch {
9108
8822
  return 0;
9109
8823
  }
9110
8824
  }
9111
8825
  function writeLastCheck() {
9112
8826
  try {
9113
- mkdirSync7(join7(homedir5(), ".okx"), { recursive: true });
9114
- 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");
9115
8829
  } catch {
9116
8830
  }
9117
8831
  }
@@ -9214,7 +8928,7 @@ function loadProfileConfig(opts) {
9214
8928
  import { EOL as EOL2 } from "os";
9215
8929
 
9216
8930
  // src/commands/client-setup.ts
9217
- import * as fs6 from "fs";
8931
+ import * as fs5 from "fs";
9218
8932
  var DETECTABLE_CLIENTS = ["claude-desktop", "cursor", "windsurf"];
9219
8933
  function cmdSetupClient(options) {
9220
8934
  runSetup(options);
@@ -9223,14 +8937,14 @@ function cmdSetupClients() {
9223
8937
  const detected = [];
9224
8938
  for (const id of DETECTABLE_CLIENTS) {
9225
8939
  const p = getConfigPath(id);
9226
- if (p && fs6.existsSync(p)) {
8940
+ if (p && fs5.existsSync(p)) {
9227
8941
  detected.push({ id, path: p });
9228
8942
  }
9229
8943
  }
9230
8944
  if (detected.length > 0) {
9231
8945
  outputLine("Detected clients:");
9232
- for (const { id, path: path6 } of detected) {
9233
- outputLine(` ${id.padEnd(16)} ${path6}`);
8946
+ for (const { id, path: path5 } of detected) {
8947
+ outputLine(` ${id.padEnd(16)} ${path5}`);
9234
8948
  }
9235
8949
  outputLine("");
9236
8950
  }
@@ -9614,7 +9328,7 @@ var HELP_TREE = {
9614
9328
  description: "Earn products \u2014 Simple Earn, On-chain Earn, and DCD (Dual Currency Deposit)",
9615
9329
  subgroups: {
9616
9330
  savings: {
9617
- description: "Simple Earn \u2014 flexible savings, fixed-term, and lending",
9331
+ description: "Simple Earn \u2014 flexible savings and lending",
9618
9332
  commands: {
9619
9333
  balance: {
9620
9334
  usage: "okx earn savings balance [<ccy>]",
@@ -9638,19 +9352,7 @@ var HELP_TREE = {
9638
9352
  },
9639
9353
  "rate-history": {
9640
9354
  usage: "okx earn savings rate-history [--ccy <ccy>] [--limit <n>]",
9641
- description: "Query Simple Earn lending rates and fixed-term offers (requires auth)"
9642
- },
9643
- "fixed-orders": {
9644
- usage: "okx earn savings fixed-orders [--ccy <ccy>] [--state <pending|earning|expired|settled|cancelled>]",
9645
- description: "List fixed-term earn orders"
9646
- },
9647
- "fixed-purchase": {
9648
- usage: "okx earn savings fixed-purchase --ccy <ccy> --amt <n> --term <term> [--confirm]",
9649
- description: "Purchase Simple Earn Fixed (\u5B9A\u671F). Preview by default; add --confirm to execute. Funds locked until maturity"
9650
- },
9651
- "fixed-redeem": {
9652
- usage: "okx earn savings fixed-redeem <reqId>",
9653
- description: "Redeem a fixed-term earn order (full amount)"
9355
+ description: "Query Simple Earn lending rates (public, no auth needed)"
9654
9356
  }
9655
9357
  }
9656
9358
  },
@@ -9830,7 +9532,6 @@ function printGlobalHelp() {
9830
9532
  " --demo Use simulated trading (demo) mode",
9831
9533
  " --live Force live trading mode (overrides profile demo=true; mutually exclusive with --demo)",
9832
9534
  " --json Output raw JSON",
9833
- " --env With --json, wrap output as {env, profile, data}",
9834
9535
  " --verbose Show detailed network request/response info (stderr)",
9835
9536
  " --version, -v Show version",
9836
9537
  " --help Show this help",
@@ -9945,8 +9646,8 @@ function printCommandList(lines, commands) {
9945
9646
  lines.push("");
9946
9647
  }
9947
9648
  }
9948
- function printHelp(...path6) {
9949
- const [moduleName, subgroupName] = path6;
9649
+ function printHelp(...path5) {
9650
+ const [moduleName, subgroupName] = path5;
9950
9651
  if (!moduleName) {
9951
9652
  printGlobalHelp();
9952
9653
  } else if (!subgroupName) {
@@ -9962,7 +9663,6 @@ var CLI_OPTIONS = {
9962
9663
  profile: { type: "string" },
9963
9664
  demo: { type: "boolean", default: false },
9964
9665
  json: { type: "boolean", default: false },
9965
- env: { type: "boolean", default: false },
9966
9666
  help: { type: "boolean", default: false },
9967
9667
  version: { type: "boolean", short: "v", default: false },
9968
9668
  // setup command
@@ -10068,8 +9768,6 @@ var CLI_OPTIONS = {
10068
9768
  orders: { type: "string" },
10069
9769
  // earn
10070
9770
  rate: { type: "string" },
10071
- reqId: { type: "string" },
10072
- confirm: { type: "boolean", default: false },
10073
9771
  // audit
10074
9772
  since: { type: "string" },
10075
9773
  tool: { type: "string" },
@@ -10345,10 +10043,6 @@ async function cmdMarketCandles(run, instId, opts) {
10345
10043
  }))
10346
10044
  );
10347
10045
  }
10348
- function cmdMarketIndicatorList(json) {
10349
- if (json) return printJson(KNOWN_INDICATORS);
10350
- printTable(KNOWN_INDICATORS.map(({ name, description }) => ({ name, description })));
10351
- }
10352
10046
  async function cmdMarketIndicator(run, indicator, instId, opts) {
10353
10047
  const params = opts.params ? opts.params.split(",").map((p) => Number(p.trim())).filter((n) => !Number.isNaN(n)) : void 0;
10354
10048
  const result = await run("market_get_indicator", {
@@ -10446,9 +10140,9 @@ async function cmdMarketStockTokens(run, opts) {
10446
10140
  }
10447
10141
 
10448
10142
  // src/commands/account.ts
10449
- import * as fs7 from "fs";
10450
- import * as path5 from "path";
10451
- import * as os6 from "os";
10143
+ import * as fs6 from "fs";
10144
+ import * as path4 from "path";
10145
+ import * as os5 from "os";
10452
10146
  function getData2(result) {
10453
10147
  return result.data;
10454
10148
  }
@@ -10659,10 +10353,10 @@ function readAuditLogs(logDir, days = 7) {
10659
10353
  const yyyy = d.getUTCFullYear();
10660
10354
  const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
10661
10355
  const dd = String(d.getUTCDate()).padStart(2, "0");
10662
- const filePath = path5.join(logDir, `trade-${yyyy}-${mm}-${dd}.log`);
10356
+ const filePath = path4.join(logDir, `trade-${yyyy}-${mm}-${dd}.log`);
10663
10357
  let content;
10664
10358
  try {
10665
- content = fs7.readFileSync(filePath, "utf8");
10359
+ content = fs6.readFileSync(filePath, "utf8");
10666
10360
  } catch {
10667
10361
  continue;
10668
10362
  }
@@ -10678,7 +10372,7 @@ function readAuditLogs(logDir, days = 7) {
10678
10372
  return entries;
10679
10373
  }
10680
10374
  function cmdAccountAudit(opts) {
10681
- const logDir = path5.join(os6.homedir(), ".okx", "logs");
10375
+ const logDir = path4.join(os5.homedir(), ".okx", "logs");
10682
10376
  const limit = Math.min(Number(opts.limit) || 20, 100);
10683
10377
  let entries = readAuditLogs(logDir);
10684
10378
  if (opts.tool) entries = entries.filter((e) => e.tool === opts.tool);
@@ -11666,7 +11360,6 @@ async function cmdOptionPlace(run, opts) {
11666
11360
  side: opts.side,
11667
11361
  ordType: opts.ordType,
11668
11362
  sz: opts.sz,
11669
- tgtCcy: opts.tgtCcy,
11670
11363
  px: opts.px,
11671
11364
  reduceOnly: opts.reduceOnly,
11672
11365
  clOrdId: opts.clOrdId,
@@ -12134,22 +11827,6 @@ async function cmdEarnSavingsBalance(run, ccy, json) {
12134
11827
  pendingAmt: r["pendingAmt"]
12135
11828
  }));
12136
11829
  }
12137
- async function cmdEarnFixedOrderList(run, opts) {
12138
- const data = extractData(await run("earn_get_fixed_order_list", {
12139
- ccy: opts.ccy,
12140
- state: opts.state
12141
- }));
12142
- printDataList(data, opts.json, "No fixed earn orders", (r) => ({
12143
- reqId: r["reqId"],
12144
- ccy: r["ccy"],
12145
- amt: r["amt"],
12146
- rate: r["rate"],
12147
- term: r["term"],
12148
- state: r["state"],
12149
- accruedInterest: r["accruedInterest"],
12150
- cTime: new Date(Number(r["cTime"])).toLocaleString()
12151
- }));
12152
- }
12153
11830
  async function cmdEarnSavingsPurchase(run, opts) {
12154
11831
  const data = extractData(await run("earn_savings_purchase", { ccy: opts.ccy, amt: opts.amt, rate: opts.rate }));
12155
11832
  if (opts.json) {
@@ -12195,107 +11872,14 @@ async function cmdEarnLendingHistory(run, opts) {
12195
11872
  ts: new Date(Number(r["ts"])).toLocaleString()
12196
11873
  }));
12197
11874
  }
12198
- function printFixedPurchasePreview(rec) {
12199
- const offer = rec["offer"];
12200
- outputLine("");
12201
- outputLine("\u{1F4CB} Fixed Earn Purchase Preview");
12202
- outputLine(` Currency: ${rec["ccy"]}`);
12203
- outputLine(` Amount: ${rec["amt"]}`);
12204
- outputLine(` Term: ${rec["term"]}`);
12205
- if (rec["currentFlexibleRate"]) {
12206
- outputLine(` Current flexible rate: ${rec["currentFlexibleRate"]}`);
12207
- }
12208
- if (offer) {
12209
- printKv({
12210
- rate: offer["rate"],
12211
- minLend: offer["minLend"],
12212
- remainingQuota: offer["lendQuota"],
12213
- soldOut: offer["soldOut"] ? "Yes" : "No"
12214
- });
12215
- } else {
12216
- outputLine(" \u26A0\uFE0F No matching offer found for this term.");
12217
- }
12218
- outputLine("");
12219
- outputLine(rec["warning"] ?? "");
12220
- outputLine("");
12221
- outputLine("Re-run with --confirm to execute.");
12222
- }
12223
- async function cmdEarnFixedPurchase(run, opts) {
12224
- const result = await run("earn_fixed_purchase", {
12225
- ccy: opts.ccy,
12226
- amt: opts.amt,
12227
- term: opts.term,
12228
- confirm: opts.confirm
12229
- });
12230
- if (!result || typeof result !== "object") {
12231
- outputLine("No response data");
12232
- return;
12233
- }
12234
- const rec = result;
12235
- if (rec["preview"]) {
12236
- if (opts.json) {
12237
- printJson(rec);
12238
- return;
12239
- }
12240
- printFixedPurchasePreview(rec);
12241
- return;
12242
- }
12243
- const data = extractData(result);
12244
- if (opts.json) {
12245
- printJson(data);
12246
- return;
12247
- }
12248
- const r = data[0];
12249
- if (!r) {
12250
- outputLine("No response data");
12251
- return;
12252
- }
12253
- printKv({ reqId: r["reqId"], ccy: r["ccy"], amt: r["amt"], term: r["term"] });
12254
- }
12255
- async function cmdEarnFixedRedeem(run, opts) {
12256
- const data = extractData(await run("earn_fixed_redeem", { reqId: opts.reqId }));
12257
- if (opts.json) {
12258
- printJson(data);
12259
- return;
12260
- }
12261
- if (!data.length) {
12262
- outputLine("No response data");
12263
- return;
12264
- }
12265
- printTable(data.map((r) => ({ reqId: r["reqId"] })));
12266
- }
12267
11875
  async function cmdEarnLendingRateHistory(run, opts) {
12268
- const result = await run("earn_get_lending_rate_history", { ccy: opts.ccy, limit: opts.limit });
12269
- const data = extractData(result);
12270
- const fixedOffers = extractFixedOffers(result);
12271
- if (opts.json) {
12272
- printJson({ data, fixedOffers });
12273
- return;
12274
- }
12275
- printDataList(data, false, "No rate history data", (r) => ({
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) => ({
12276
11878
  ccy: r["ccy"],
12277
11879
  lendingRate: r["lendingRate"],
11880
+ rate: r["rate"],
12278
11881
  ts: new Date(Number(r["ts"])).toLocaleString()
12279
11882
  }));
12280
- if (fixedOffers.length > 0) {
12281
- outputLine("");
12282
- outputLine("Fixed-term offers:");
12283
- printTable(fixedOffers.map((r) => ({
12284
- ccy: r["ccy"],
12285
- term: r["term"],
12286
- rate: r["rate"],
12287
- minLend: r["minLend"],
12288
- remainingQuota: r["lendQuota"],
12289
- soldOut: r["soldOut"] ? "Yes" : "No"
12290
- })));
12291
- }
12292
- }
12293
- function extractFixedOffers(result) {
12294
- if (result && typeof result === "object") {
12295
- const offers = result["fixedOffers"];
12296
- if (Array.isArray(offers)) return offers;
12297
- }
12298
- return [];
12299
11883
  }
12300
11884
 
12301
11885
  // src/commands/auto-earn.ts
@@ -12822,7 +12406,7 @@ async function cmdDcdRedeemExecute(run, opts) {
12822
12406
  ordId: r["ordId"],
12823
12407
  state: r["state"],
12824
12408
  redeemSz: q["redeemSz"] ? `${parseFloat(q["redeemSz"]).toFixed(8)} ${q["redeemCcy"]}` : "\u2014",
12825
- termRate: q["termRate"] ? `${(parseFloat(q["termRate"]) * 100).toFixed(2)}%` : "\u2014"
12409
+ termRate: q["termRate"] ? `${q["termRate"]}%` : "\u2014"
12826
12410
  });
12827
12411
  }
12828
12412
  async function cmdDcdOrderState(run, opts) {
@@ -12875,7 +12459,7 @@ async function cmdDcdOrders(run, opts) {
12875
12459
  quoteCcy: r["quoteCcy"],
12876
12460
  strike: r["strike"],
12877
12461
  notionalSz: r["notionalSz"],
12878
- annualizedYield: r["annualizedYield"] ? `${(parseFloat(r["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
12462
+ annualizedYield: r["annualizedYield"],
12879
12463
  yieldSz: r["yieldSz"],
12880
12464
  settleTime: r["settleTime"] ? new Date(Number(r["settleTime"])).toLocaleDateString() : "",
12881
12465
  // scheduled settlement time
@@ -12915,7 +12499,7 @@ async function cmdDcdQuoteAndBuy(run, opts) {
12915
12499
  outputLine("Quote:");
12916
12500
  printKv({
12917
12501
  quoteId: q["quoteId"],
12918
- annualizedYield: q["annualizedYield"] ? `${(parseFloat(q["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
12502
+ annualizedYield: q["annualizedYield"] ? `${q["annualizedYield"]}%` : "\u2014",
12919
12503
  absYield: q["absYield"],
12920
12504
  notionalSz: q["notionalSz"],
12921
12505
  notionalCcy: q["notionalCcy"]
@@ -12939,17 +12523,16 @@ async function cmdDcdQuoteAndBuy(run, opts) {
12939
12523
  }
12940
12524
 
12941
12525
  // src/commands/skill.ts
12942
- import { tmpdir, homedir as homedir7 } from "os";
12943
- import { join as join9, dirname as dirname6 } from "path";
12944
- 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";
12945
12529
  import { execFileSync as execFileSync2 } from "child_process";
12946
12530
  import { randomUUID as randomUUID2 } from "crypto";
12947
12531
  function resolveNpx() {
12948
- const sibling = join9(dirname6(process.execPath), "npx");
12532
+ const sibling = join11(dirname7(process.execPath), "npx");
12949
12533
  if (existsSync7(sibling)) return sibling;
12950
12534
  return "npx";
12951
12535
  }
12952
- var THIRD_PARTY_INSTALL_NOTICE = "Note: This skill was created by a third-party developer, not by OKX. Review SKILL.md before use.";
12953
12536
  async function cmdSkillSearch(run, opts) {
12954
12537
  const args = {};
12955
12538
  if (opts.keyword) args.keyword = opts.keyword;
@@ -12999,13 +12582,13 @@ async function cmdSkillCategories(run, json) {
12999
12582
  outputLine("");
13000
12583
  }
13001
12584
  async function cmdSkillAdd(name, config, json) {
13002
- const tmpBase = join9(tmpdir(), `okx-skill-${randomUUID2()}`);
13003
- mkdirSync8(tmpBase, { recursive: true });
12585
+ const tmpBase = join11(tmpdir(), `okx-skill-${randomUUID2()}`);
12586
+ mkdirSync9(tmpBase, { recursive: true });
13004
12587
  try {
13005
12588
  outputLine(`Downloading ${name}...`);
13006
12589
  const client = new OkxRestClient(config);
13007
12590
  const zipPath = await downloadSkillZip(client, name, tmpBase);
13008
- const contentDir = await extractSkillZip(zipPath, join9(tmpBase, "content"));
12591
+ const contentDir = await extractSkillZip(zipPath, join11(tmpBase, "content"));
13009
12592
  const meta = readMetaJson(contentDir);
13010
12593
  validateSkillMdExists(contentDir);
13011
12594
  outputLine("Installing to detected agents...");
@@ -13015,7 +12598,7 @@ async function cmdSkillAdd(name, config, json) {
13015
12598
  timeout: 6e4
13016
12599
  });
13017
12600
  } catch (e) {
13018
- const savedZip = join9(process.cwd(), `${name}.zip`);
12601
+ const savedZip = join11(process.cwd(), `${name}.zip`);
13019
12602
  try {
13020
12603
  copyFileSync2(zipPath, savedZip);
13021
12604
  } catch {
@@ -13025,7 +12608,11 @@ async function cmdSkillAdd(name, config, json) {
13025
12608
  throw e;
13026
12609
  }
13027
12610
  upsertSkillRecord(meta);
13028
- 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
+ }
13029
12616
  } finally {
13030
12617
  rmSync(tmpBase, { recursive: true, force: true });
13031
12618
  }
@@ -13054,7 +12641,7 @@ function cmdSkillRemove(name, json) {
13054
12641
  timeout: 6e4
13055
12642
  });
13056
12643
  } catch {
13057
- const agentsPath = join9(homedir7(), ".agents", "skills", name);
12644
+ const agentsPath = join11(homedir9(), ".agents", "skills", name);
13058
12645
  try {
13059
12646
  rmSync(agentsPath, { recursive: true, force: true });
13060
12647
  } catch {
@@ -13118,19 +12705,11 @@ function cmdSkillList(json) {
13118
12705
  outputLine("");
13119
12706
  outputLine(`${skills.length} skills installed.`);
13120
12707
  }
13121
- function printSkillInstallResult(meta, json) {
13122
- if (json) {
13123
- outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
13124
- } else {
13125
- outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
13126
- outputLine(` ${THIRD_PARTY_INSTALL_NOTICE}`);
13127
- }
13128
- }
13129
12708
 
13130
12709
  // src/index.ts
13131
12710
  var _require3 = createRequire3(import.meta.url);
13132
12711
  var CLI_VERSION2 = _require3("../package.json").version;
13133
- var GIT_HASH2 = true ? "0a5911f" : "dev";
12712
+ var GIT_HASH2 = true ? "19e8da3" : "dev";
13134
12713
  function handleConfigCommand(action, rest, json, lang, force) {
13135
12714
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
13136
12715
  if (action === "show") return cmdConfigShow(json);
@@ -13180,20 +12759,18 @@ function handleMarketPublicCommand(run, action, rest, v, json) {
13180
12759
  instId: v.instId,
13181
12760
  json
13182
12761
  });
13183
- if (action === "indicator") return handleIndicatorAction(run, rest, v, json);
13184
- }
13185
- function handleIndicatorAction(run, rest, v, json) {
13186
- if (rest[0] === "list") return cmdMarketIndicatorList(json);
13187
- const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
13188
- const backtestTime = v["backtest-time"] !== void 0 ? Number(v["backtest-time"]) : void 0;
13189
- return cmdMarketIndicator(run, rest[0], rest[1], {
13190
- bar: v.bar,
13191
- params: v.params,
13192
- list: v.list,
13193
- limit,
13194
- backtestTime,
13195
- json
13196
- });
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
+ }
13197
12774
  }
13198
12775
  function handleMarketDataCommand(run, action, rest, v, json) {
13199
12776
  const limit = v.limit !== void 0 ? Number(v.limit) : void 0;
@@ -13539,7 +13116,6 @@ function handleOptionCommand(run, action, rest, v, json) {
13539
13116
  side: v.side,
13540
13117
  ordType: v.ordType,
13541
13118
  sz: v.sz,
13542
- tgtCcy: v.tgtCcy,
13543
13119
  px: v.px,
13544
13120
  reduceOnly: v.reduceOnly,
13545
13121
  clOrdId: v.clOrdId,
@@ -13830,9 +13406,6 @@ function handleEarnSavingsCommand(run, action, rest, v, json) {
13830
13406
  if (action === "set-rate") return cmdEarnSetLendingRate(run, { ccy: v.ccy, rate: v.rate, json });
13831
13407
  if (action === "lending-history") return cmdEarnLendingHistory(run, { ccy: v.ccy, limit, json });
13832
13408
  if (action === "rate-history") return cmdEarnLendingRateHistory(run, { ccy: v.ccy, limit, json });
13833
- if (action === "fixed-orders") return cmdEarnFixedOrderList(run, { ccy: v.ccy, state: v.state, json });
13834
- if (action === "fixed-purchase") return cmdEarnFixedPurchase(run, { ccy: v.ccy, amt: v.amt, term: v.term, confirm: v.confirm ?? false, json });
13835
- if (action === "fixed-redeem") return cmdEarnFixedRedeem(run, { reqId: v.reqId, json });
13836
13409
  errorLine(`Unknown earn savings command: ${action}`);
13837
13410
  process.exitCode = 1;
13838
13411
  }
@@ -13951,31 +13524,6 @@ function printHelpForLevel(positionals) {
13951
13524
  else if (!subgroup) printHelp(module);
13952
13525
  else printHelp(module, subgroup);
13953
13526
  }
13954
- function wrapRunnerWithLogger(baseRunner, logger) {
13955
- const writeToolNames = new Set(allToolSpecs().filter((t) => t.isWrite).map((t) => t.name));
13956
- return async (toolName, args) => {
13957
- const startTime = Date.now();
13958
- try {
13959
- const result = await baseRunner(toolName, args);
13960
- if (writeToolNames.has(toolName)) {
13961
- markFailedIfSCodeError(result.data);
13962
- }
13963
- logger.log("info", toolName, args, result, Date.now() - startTime);
13964
- return result;
13965
- } catch (error) {
13966
- logger.log("error", toolName, args, error, Date.now() - startTime);
13967
- throw error;
13968
- }
13969
- };
13970
- }
13971
- async function runDiagnose(v) {
13972
- let config;
13973
- try {
13974
- config = loadProfileConfig({ profile: v.profile, demo: v.demo, live: v.live, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
13975
- } catch {
13976
- }
13977
- return cmdDiagnose(config, v.profile ?? "default", { mcp: v.mcp, cli: v.cli, all: v.all, output: v.output });
13978
- }
13979
13527
  async function main() {
13980
13528
  setOutput({
13981
13529
  out: (m) => process.stdout.write(m),
@@ -13997,14 +13545,25 @@ async function main() {
13997
13545
  if (module === "config") return handleConfigCommand(action, rest, json, v.lang, v.force);
13998
13546
  if (module === "setup") return handleSetupCommand(v);
13999
13547
  if (module === "upgrade") return cmdUpgrade(CLI_VERSION2, { beta: v.beta, check: v.check, force: v.force }, json);
14000
- 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
+ }
14001
13556
  const config = loadProfileConfig({ profile: v.profile, demo: v.demo, live: v.live, verbose: v.verbose, userAgent: `okx-trade-cli/${CLI_VERSION2}`, sourceTag: "CLI" });
14002
- setEnvContext({ demo: config.demo, profile: v.profile ?? "default" });
14003
- setJsonEnvEnabled(v.env ?? false);
14004
13557
  const client = new OkxRestClient(config);
14005
13558
  const baseRunner = createToolRunner(client, config);
14006
- const logger = new TradeLogger("info");
14007
- const run = wrapRunnerWithLogger(baseRunner, logger);
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
+ };
14008
13567
  const moduleHandlers = {
14009
13568
  market: () => handleMarketCommand(run, action, rest, v, json),
14010
13569
  account: () => handleAccountCommand(run, action, rest, v, json),
@@ -14049,8 +13608,7 @@ export {
14049
13608
  handleSpotCommand,
14050
13609
  handleSwapAlgoCommand,
14051
13610
  handleSwapCommand,
14052
- printHelp,
14053
- wrapRunnerWithLogger
13611
+ printHelp
14054
13612
  };
14055
13613
  /*! Bundled license information:
14056
13614