@okx_ai/okx-trade-cli 1.2.9-beta.1 → 1.2.9-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,30 +4,25 @@
4
4
  import { createRequire as createRequire3 } from "module";
5
5
 
6
6
  // ../core/dist/index.js
7
- import { Agent, ProxyAgent } from "undici";
8
- import { unlink } from "fs/promises";
9
- import { execFile } from "child_process";
10
- import { access, constants } from "fs/promises";
11
- import { homedir } from "os";
12
- import { join } from "path";
7
+ import { ProxyAgent } from "undici";
13
8
  import { createHmac } from "crypto";
14
9
  import fs from "fs";
15
10
  import path from "path";
16
11
  import os from "os";
17
12
  import { writeFileSync, renameSync, unlinkSync, mkdirSync } from "fs";
18
- import { join as join2, resolve, basename, sep } from "path";
13
+ import { join, resolve, basename, sep } from "path";
19
14
  import { randomUUID } from "crypto";
20
15
  import yauzl from "yauzl";
21
16
  import { createWriteStream, mkdirSync as mkdirSync2 } from "fs";
22
17
  import { resolve as resolve2, dirname } from "path";
23
18
  import { readFileSync, existsSync } from "fs";
24
- import { join as join3 } from "path";
19
+ import { join as join2 } from "path";
25
20
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
26
- import { join as join4, dirname as dirname2 } from "path";
27
- import { homedir as homedir2 } from "os";
21
+ import { join as join3, dirname as dirname2 } from "path";
22
+ import { homedir } from "os";
28
23
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync3 } from "fs";
29
- import { join as join5, dirname as dirname3 } from "path";
30
- import { homedir as homedir3 } from "os";
24
+ import { join as join4, dirname as dirname3 } from "path";
25
+ import { homedir as homedir2 } from "os";
31
26
 
32
27
  // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
33
28
  function getLineColFromPtr(string, ptr) {
@@ -857,66 +852,12 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
857
852
 
858
853
  // ../core/dist/index.js
859
854
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync4 } from "fs";
860
- import { join as join6 } from "path";
861
- import { homedir as homedir4 } from "os";
855
+ import { join as join5 } from "path";
856
+ import { homedir as homedir3 } from "os";
862
857
  import * as fs3 from "fs";
863
858
  import * as path3 from "path";
864
859
  import * as os3 from "os";
865
860
  import { execFileSync } from "child_process";
866
- var EXEC_TIMEOUT_MS = 15e3;
867
- var DOH_BIN_DIR = join(homedir(), ".okx", "bin");
868
- var DOH_CACHE_FILE = join(homedir(), ".okx", ".doh-cache.json");
869
- function getDohBinaryPath() {
870
- if (process.env.OKX_DOH_BINARY_PATH) {
871
- return process.env.OKX_DOH_BINARY_PATH;
872
- }
873
- const ext = process.platform === "win32" ? ".exe" : "";
874
- return join(DOH_BIN_DIR, `okx-doh-resolver${ext}`);
875
- }
876
- function getDohCachePath() {
877
- return DOH_CACHE_FILE;
878
- }
879
- async function dohBinaryExists() {
880
- try {
881
- const flag = process.platform === "win32" ? constants.F_OK : constants.X_OK;
882
- await access(getDohBinaryPath(), flag);
883
- return true;
884
- } catch {
885
- return false;
886
- }
887
- }
888
- function execDohBinary(domain) {
889
- const binPath = getDohBinaryPath();
890
- return new Promise((resolve3) => {
891
- execFile(
892
- binPath,
893
- ["--domain", domain],
894
- { timeout: EXEC_TIMEOUT_MS, encoding: "utf-8" },
895
- (error, stdout) => {
896
- if (error) {
897
- resolve3(null);
898
- return;
899
- }
900
- try {
901
- const result = JSON.parse(stdout);
902
- if (result.code === 0 && result.data) {
903
- resolve3(result.data);
904
- } else {
905
- resolve3(null);
906
- }
907
- } catch {
908
- resolve3(null);
909
- }
910
- }
911
- );
912
- });
913
- }
914
- async function resolveDoh(domain) {
915
- if (!await dohBinaryExists()) {
916
- return null;
917
- }
918
- return execDohBinary(domain);
919
- }
920
861
  function getNow() {
921
862
  return (/* @__PURE__ */ new Date()).toISOString();
922
863
  }
@@ -1135,80 +1076,12 @@ var OkxRestClient = class _OkxRestClient {
1135
1076
  config;
1136
1077
  rateLimiter;
1137
1078
  dispatcher;
1138
- // DoH proxy state (lazy-resolved on first request)
1139
- dohResolverFn;
1140
- dohResolved = false;
1141
- dohNode = null;
1142
- dohAgent = null;
1143
- dohBaseUrl = null;
1144
- /**
1145
- * @param config - OKX API client configuration
1146
- * @param options - Optional overrides (e.g. custom DoH resolver for testing).
1147
- * Pass `{ resolveDoh: null }` to disable DoH entirely.
1148
- */
1149
- constructor(config, options) {
1079
+ constructor(config) {
1150
1080
  this.config = config;
1151
1081
  this.rateLimiter = new RateLimiter(3e4, config.verbose);
1152
1082
  if (config.proxyUrl) {
1153
1083
  this.dispatcher = new ProxyAgent(config.proxyUrl);
1154
1084
  }
1155
- this.dohResolverFn = options?.resolveDoh !== void 0 ? options.resolveDoh : resolveDoh;
1156
- }
1157
- /**
1158
- * Lazily resolve the DoH proxy node on the first request.
1159
- * Skipped entirely when the user has configured proxy_url or DoH is disabled.
1160
- * On failure, silently falls back to direct connection.
1161
- */
1162
- async ensureDoh() {
1163
- if (this.dohResolved || this.dispatcher || !this.dohResolverFn) return;
1164
- this.dohResolved = true;
1165
- try {
1166
- const { hostname, protocol } = new URL(this.config.baseUrl);
1167
- const node = await this.dohResolverFn(hostname);
1168
- if (!node) return;
1169
- if (node.ip === hostname) {
1170
- if (this.config.verbose) {
1171
- vlog(`DoH: resolved ip matches hostname (${hostname}), using direct connection`);
1172
- }
1173
- return;
1174
- }
1175
- this.dohNode = node;
1176
- this.dohBaseUrl = `${protocol}//${node.host}`;
1177
- this.dohAgent = new Agent({
1178
- connect: {
1179
- lookup: (_hostname, options, callback) => {
1180
- if (options?.all) {
1181
- callback(null, [{ address: node.ip, family: 4 }]);
1182
- } else {
1183
- callback(null, node.ip, 4);
1184
- }
1185
- }
1186
- }
1187
- });
1188
- if (this.config.verbose) {
1189
- vlog(`DoH proxy active: ${hostname} \u2192 ${node.host} (${node.ip}), ttl=${node.ttl}s`);
1190
- }
1191
- } catch (err) {
1192
- if (this.config.verbose) {
1193
- const cause = err instanceof Error ? err.message : String(err);
1194
- vlog(`DoH resolution failed, falling back to direct: ${cause}`);
1195
- }
1196
- }
1197
- }
1198
- /**
1199
- * Invalidate DoH state and delete the binary's cache file so the next
1200
- * resolution performs a fresh lookup. Called on network-level failures
1201
- * when a DoH proxy node was in use.
1202
- */
1203
- async invalidateDoh() {
1204
- this.dohNode = null;
1205
- this.dohAgent = null;
1206
- this.dohBaseUrl = null;
1207
- this.dohResolved = false;
1208
- try {
1209
- await unlink(getDohCachePath());
1210
- } catch {
1211
- }
1212
1085
  }
1213
1086
  logRequest(method, url, auth) {
1214
1087
  if (!this.config.verbose) return;
@@ -1379,18 +1252,13 @@ var OkxRestClient = class _OkxRestClient {
1379
1252
  * Security: validates Content-Type and enforces maxBytes limit.
1380
1253
  */
1381
1254
  async privatePostBinary(path42, body, opts) {
1382
- await this.ensureDoh();
1383
1255
  const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
1384
1256
  const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
1385
1257
  const bodyJson = body ? JSON.stringify(body) : "";
1386
1258
  const endpoint = `POST ${path42}`;
1387
- const baseUrl = this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
1388
- this.logRequest("POST", `${baseUrl}${path42}`, "private");
1259
+ this.logRequest("POST", `${this.config.baseUrl}${path42}`, "private");
1389
1260
  const reqConfig = { method: "POST", path: path42, auth: "private" };
1390
1261
  const headers = this.buildHeaders(reqConfig, path42, bodyJson, getNow());
1391
- if (this.dohNode) {
1392
- headers.set("User-Agent", "OKX/2.7.2");
1393
- }
1394
1262
  const t0 = Date.now();
1395
1263
  const response = await this.fetchBinary(path42, endpoint, headers, bodyJson, t0);
1396
1264
  const elapsed = Date.now() - t0;
@@ -1420,19 +1288,14 @@ var OkxRestClient = class _OkxRestClient {
1420
1288
  /** Execute fetch for binary endpoint, wrapping network errors. */
1421
1289
  async fetchBinary(path42, endpoint, headers, bodyJson, t0) {
1422
1290
  try {
1423
- const baseUrl = this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
1424
1291
  const fetchOptions = {
1425
1292
  method: "POST",
1426
1293
  headers,
1427
1294
  body: bodyJson || void 0,
1428
1295
  signal: AbortSignal.timeout(this.config.timeoutMs)
1429
1296
  };
1430
- if (this.dispatcher) {
1431
- fetchOptions.dispatcher = this.dispatcher;
1432
- } else if (this.dohAgent) {
1433
- fetchOptions.dispatcher = this.dohAgent;
1434
- }
1435
- return await fetch(`${baseUrl}${path42}`, fetchOptions);
1297
+ if (this.dispatcher) fetchOptions.dispatcher = this.dispatcher;
1298
+ return await fetch(`${this.config.baseUrl}${path42}`, fetchOptions);
1436
1299
  } catch (error) {
1437
1300
  if (this.config.verbose) {
1438
1301
  vlog(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
@@ -1463,11 +1326,9 @@ var OkxRestClient = class _OkxRestClient {
1463
1326
  // JSON request
1464
1327
  // ---------------------------------------------------------------------------
1465
1328
  async request(reqConfig) {
1466
- await this.ensureDoh();
1467
1329
  const queryString = buildQueryString(reqConfig.query);
1468
1330
  const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
1469
- const baseUrl = this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
1470
- const url = `${baseUrl}${requestPath}`;
1331
+ const url = `${this.config.baseUrl}${requestPath}`;
1471
1332
  const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
1472
1333
  const timestamp = getNow();
1473
1334
  this.logRequest(reqConfig.method, url, reqConfig.auth);
@@ -1475,9 +1336,6 @@ var OkxRestClient = class _OkxRestClient {
1475
1336
  await this.rateLimiter.consume(reqConfig.rateLimit);
1476
1337
  }
1477
1338
  const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
1478
- if (this.dohNode) {
1479
- headers.set("User-Agent", "OKX/2.7.2");
1480
- }
1481
1339
  const t0 = Date.now();
1482
1340
  let response;
1483
1341
  try {
@@ -1489,18 +1347,9 @@ var OkxRestClient = class _OkxRestClient {
1489
1347
  };
1490
1348
  if (this.dispatcher) {
1491
1349
  fetchOptions.dispatcher = this.dispatcher;
1492
- } else if (this.dohAgent) {
1493
- fetchOptions.dispatcher = this.dohAgent;
1494
1350
  }
1495
1351
  response = await fetch(url, fetchOptions);
1496
1352
  } catch (error) {
1497
- if (this.dohNode) {
1498
- if (this.config.verbose) {
1499
- vlog(`DoH request failed, invalidating cache and retrying: ${error instanceof Error ? error.message : String(error)}`);
1500
- }
1501
- await this.invalidateDoh();
1502
- return this.request(reqConfig);
1503
- }
1504
1353
  if (this.config.verbose) {
1505
1354
  const elapsed2 = Date.now() - t0;
1506
1355
  const cause = error instanceof Error ? error.message : String(error);
@@ -2342,6 +2191,59 @@ function registerAccountTools() {
2342
2191
  }
2343
2192
  ];
2344
2193
  }
2194
+ async function resolveQuoteCcySz(instId, sz, tgtCcy, instType, client) {
2195
+ if (tgtCcy !== "quote_ccy") {
2196
+ return { sz, tgtCcy, conversionNote: void 0 };
2197
+ }
2198
+ const [instrumentsRes, tickerRes] = await Promise.all([
2199
+ client.publicGet("/api/v5/public/instruments", {
2200
+ instType,
2201
+ instId
2202
+ }),
2203
+ client.publicGet("/api/v5/market/ticker", { instId })
2204
+ ]);
2205
+ const instruments = Array.isArray(instrumentsRes.data) ? instrumentsRes.data : [];
2206
+ if (instruments.length === 0) {
2207
+ throw new Error(
2208
+ `Failed to fetch instrument info for ${instId}: empty instrument list. Cannot determine ctVal for quote_ccy conversion.`
2209
+ );
2210
+ }
2211
+ const ctValStr = String(instruments[0].ctVal ?? "");
2212
+ const ctVal = parseFloat(ctValStr);
2213
+ if (!isFinite(ctVal) || ctVal <= 0) {
2214
+ throw new Error(
2215
+ `Invalid ctVal "${ctValStr}" for ${instId}. ctVal must be a positive number for quote_ccy conversion.`
2216
+ );
2217
+ }
2218
+ const tickers = Array.isArray(tickerRes.data) ? tickerRes.data : [];
2219
+ if (tickers.length === 0) {
2220
+ throw new Error(
2221
+ `Failed to fetch ticker price for ${instId}: empty ticker response. Cannot determine last price for quote_ccy conversion.`
2222
+ );
2223
+ }
2224
+ const lastStr = String(tickers[0].last ?? "");
2225
+ const lastPx = parseFloat(lastStr);
2226
+ if (!isFinite(lastPx) || lastPx <= 0) {
2227
+ throw new Error(
2228
+ `Invalid last price "${lastStr}" for ${instId}. Last price must be a positive number for quote_ccy conversion.`
2229
+ );
2230
+ }
2231
+ const usdtAmount = parseFloat(sz);
2232
+ const contractValue = ctVal * lastPx;
2233
+ const contracts = Math.floor(usdtAmount / contractValue);
2234
+ if (contracts <= 0) {
2235
+ const minUsdt = contractValue.toFixed(2);
2236
+ throw new Error(
2237
+ `sz=${sz} USDT is too small to buy even 1 contract of ${instId}. Minimum amount required is at least ${minUsdt} USDT (ctVal=${ctValStr}, lastPx=${lastStr}, 1 contract = ${minUsdt} USDT).`
2238
+ );
2239
+ }
2240
+ const conversionNote = `Converting ${sz} USDT \u2192 ${contracts} contracts (ctVal=${ctValStr}, lastPx=${lastStr}, formula: floor(${sz} / (${ctValStr} \xD7 ${lastStr})) = ${contracts})`;
2241
+ return {
2242
+ sz: String(contracts),
2243
+ tgtCcy: void 0,
2244
+ conversionNote
2245
+ };
2246
+ }
2345
2247
  function registerAlgoTradeTools() {
2346
2248
  return [
2347
2249
  {
@@ -2437,6 +2339,13 @@ function registerAlgoTradeTools() {
2437
2339
  handler: async (rawArgs, context) => {
2438
2340
  const args = asRecord(rawArgs);
2439
2341
  const reduceOnly = args.reduceOnly;
2342
+ const resolved = await resolveQuoteCcySz(
2343
+ requireString(args, "instId"),
2344
+ requireString(args, "sz"),
2345
+ readString(args, "tgtCcy"),
2346
+ "SWAP",
2347
+ context.client
2348
+ );
2440
2349
  const response = await context.client.privatePost(
2441
2350
  "/api/v5/trade/order-algo",
2442
2351
  compactObject({
@@ -2445,8 +2354,8 @@ function registerAlgoTradeTools() {
2445
2354
  side: requireString(args, "side"),
2446
2355
  posSide: readString(args, "posSide"),
2447
2356
  ordType: requireString(args, "ordType"),
2448
- sz: requireString(args, "sz"),
2449
- tgtCcy: readString(args, "tgtCcy"),
2357
+ sz: resolved.sz,
2358
+ tgtCcy: resolved.tgtCcy,
2450
2359
  tpTriggerPx: readString(args, "tpTriggerPx"),
2451
2360
  tpOrdPx: readString(args, "tpOrdPx"),
2452
2361
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -2462,7 +2371,11 @@ function registerAlgoTradeTools() {
2462
2371
  }),
2463
2372
  privateRateLimit("swap_place_algo_order", 20)
2464
2373
  );
2465
- return normalizeResponse(response);
2374
+ const result = normalizeResponse(response);
2375
+ if (resolved.conversionNote) {
2376
+ result._conversion = resolved.conversionNote;
2377
+ }
2378
+ return result;
2466
2379
  }
2467
2380
  },
2468
2381
  {
@@ -2770,6 +2683,13 @@ function registerFuturesAlgoTools() {
2770
2683
  handler: async (rawArgs, context) => {
2771
2684
  const args = asRecord(rawArgs);
2772
2685
  const reduceOnly = args.reduceOnly;
2686
+ const resolved = await resolveQuoteCcySz(
2687
+ requireString(args, "instId"),
2688
+ requireString(args, "sz"),
2689
+ readString(args, "tgtCcy"),
2690
+ "FUTURES",
2691
+ context.client
2692
+ );
2773
2693
  const response = await context.client.privatePost(
2774
2694
  "/api/v5/trade/order-algo",
2775
2695
  compactObject({
@@ -2778,8 +2698,8 @@ function registerFuturesAlgoTools() {
2778
2698
  side: requireString(args, "side"),
2779
2699
  posSide: readString(args, "posSide"),
2780
2700
  ordType: requireString(args, "ordType"),
2781
- sz: requireString(args, "sz"),
2782
- tgtCcy: readString(args, "tgtCcy"),
2701
+ sz: resolved.sz,
2702
+ tgtCcy: resolved.tgtCcy,
2783
2703
  tpTriggerPx: readString(args, "tpTriggerPx"),
2784
2704
  tpOrdPx: readString(args, "tpOrdPx"),
2785
2705
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -2795,7 +2715,11 @@ function registerFuturesAlgoTools() {
2795
2715
  }),
2796
2716
  privateRateLimit("futures_place_algo_order", 20)
2797
2717
  );
2798
- return normalizeResponse(response);
2718
+ const result = normalizeResponse(response);
2719
+ if (resolved.conversionNote) {
2720
+ result._conversion = resolved.conversionNote;
2721
+ }
2722
+ return result;
2799
2723
  }
2800
2724
  },
2801
2725
  {
@@ -3126,7 +3050,7 @@ function safeWriteFile(targetDir, fileName, data) {
3126
3050
  throw new Error(`Invalid file name: "${fileName}"`);
3127
3051
  }
3128
3052
  const resolvedDir = resolve(targetDir);
3129
- const filePath = join2(resolvedDir, safeName);
3053
+ const filePath = join(resolvedDir, safeName);
3130
3054
  const resolvedPath = resolve(filePath);
3131
3055
  if (!resolvedPath.startsWith(resolvedDir + sep)) {
3132
3056
  throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
@@ -3242,7 +3166,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3242
3166
  });
3243
3167
  }
3244
3168
  function readMetaJson(contentDir) {
3245
- const metaPath = join3(contentDir, "_meta.json");
3169
+ const metaPath = join2(contentDir, "_meta.json");
3246
3170
  if (!existsSync(metaPath)) {
3247
3171
  throw new Error(`_meta.json not found in ${contentDir}. Invalid skill package.`);
3248
3172
  }
@@ -3268,12 +3192,12 @@ function readMetaJson(contentDir) {
3268
3192
  };
3269
3193
  }
3270
3194
  function validateSkillMdExists(contentDir) {
3271
- const skillMdPath = join3(contentDir, "SKILL.md");
3195
+ const skillMdPath = join2(contentDir, "SKILL.md");
3272
3196
  if (!existsSync(skillMdPath)) {
3273
3197
  throw new Error(`SKILL.md not found in ${contentDir}. Invalid skill package.`);
3274
3198
  }
3275
3199
  }
3276
- var DEFAULT_REGISTRY_PATH = join4(homedir2(), ".okx", "skills", "registry.json");
3200
+ var DEFAULT_REGISTRY_PATH = join3(homedir(), ".okx", "skills", "registry.json");
3277
3201
  function readRegistry(registryPath = DEFAULT_REGISTRY_PATH) {
3278
3202
  if (!existsSync2(registryPath)) {
3279
3203
  return { version: 1, skills: {} };
@@ -3360,7 +3284,7 @@ function registerSkillsTools() {
3360
3284
  {
3361
3285
  name: "skills_download",
3362
3286
  module: "skills",
3363
- description: "Download a skill zip file from OKX Skills Marketplace to a local directory. Always call skills_search first to confirm the skill name exists. Downloads the latest approved version. NOTE: 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.",
3287
+ description: "Download a skill zip file from OKX Skills Marketplace to a local directory. Always call skills_search first to confirm the skill name exists. Downloads the latest approved version. NOTE: Downloads third-party developer content as a zip \u2014 does NOT install to agents. For full installation use CLI: okx skill add <name>. Use when the user wants to inspect or manually install a skill package.",
3364
3288
  inputSchema: {
3365
3289
  type: "object",
3366
3290
  properties: {
@@ -4508,7 +4432,7 @@ function registerDcdTools() {
4508
4432
  {
4509
4433
  name: "dcd_get_products",
4510
4434
  module: "earn.dcd",
4511
- description: "Get DCD products with yield and quota info.",
4435
+ description: "Get DCD products with yield and quota info. Yields in response are decimal fractions, not percentages.",
4512
4436
  isWrite: false,
4513
4437
  inputSchema: {
4514
4438
  type: "object",
@@ -4562,7 +4486,7 @@ function registerDcdTools() {
4562
4486
  {
4563
4487
  name: "dcd_get_orders",
4564
4488
  module: "earn.dcd",
4565
- description: "Get DCD order history.",
4489
+ description: "Get DCD order history. Yields in response are decimal fractions, not percentages.",
4566
4490
  isWrite: false,
4567
4491
  inputSchema: {
4568
4492
  type: "object",
@@ -4606,7 +4530,7 @@ function registerDcdTools() {
4606
4530
  {
4607
4531
  name: "dcd_subscribe",
4608
4532
  module: "earn.dcd",
4609
- 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).",
4533
+ 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).",
4610
4534
  isWrite: true,
4611
4535
  inputSchema: {
4612
4536
  type: "object",
@@ -4645,13 +4569,23 @@ function registerDcdTools() {
4645
4569
  });
4646
4570
  }
4647
4571
  if (minAnnualizedYield !== void 0) {
4648
- const actualYield = parseFloat(quote["annualizedYield"]);
4649
- if (!isNaN(actualYield) && actualYield < minAnnualizedYield) {
4572
+ const rawYield = parseFloat(quote["annualizedYield"]);
4573
+ if (isNaN(rawYield)) {
4650
4574
  throw new OkxApiError(
4651
- `Quote yield ${actualYield}% is below the minimum threshold of ${minAnnualizedYield}%.`,
4575
+ "Quote returned non-numeric annualizedYield, cannot verify minimum yield threshold.",
4576
+ {
4577
+ code: "INVALID_YIELD_VALUE",
4578
+ suggestion: "Order not placed. The quote did not include a valid annualizedYield. Retry or pick a different product."
4579
+ }
4580
+ );
4581
+ }
4582
+ const actualYieldPct = rawYield * 100;
4583
+ if (actualYieldPct < minAnnualizedYield) {
4584
+ throw new OkxApiError(
4585
+ `Quote yield ${actualYieldPct.toFixed(2)}% is below the minimum threshold of ${minAnnualizedYield}%.`,
4652
4586
  {
4653
4587
  code: "YIELD_BELOW_MIN",
4654
- suggestion: `Order not placed. Actual: ${actualYield}%, required: >= ${minAnnualizedYield}%. Try a different product or lower your minimum yield.`
4588
+ suggestion: `Order not placed. Actual: ${actualYieldPct.toFixed(2)}%, required: >= ${minAnnualizedYield}%. Try a different product or lower your minimum yield.`
4655
4589
  }
4656
4590
  );
4657
4591
  }
@@ -4881,6 +4815,13 @@ function buildContractTradeTools(cfg) {
4881
4815
  const args = asRecord(rawArgs);
4882
4816
  const reduceOnly = args.reduceOnly;
4883
4817
  const attachAlgoOrds = buildAttachAlgoOrds(args);
4818
+ const resolved = await resolveQuoteCcySz(
4819
+ requireString(args, "instId"),
4820
+ requireString(args, "sz"),
4821
+ readString(args, "tgtCcy"),
4822
+ defaultType,
4823
+ context.client
4824
+ );
4884
4825
  const response = await context.client.privatePost(
4885
4826
  "/api/v5/trade/order",
4886
4827
  compactObject({
@@ -4889,8 +4830,8 @@ function buildContractTradeTools(cfg) {
4889
4830
  side: requireString(args, "side"),
4890
4831
  posSide: readString(args, "posSide"),
4891
4832
  ordType: requireString(args, "ordType"),
4892
- sz: requireString(args, "sz"),
4893
- tgtCcy: readString(args, "tgtCcy"),
4833
+ sz: resolved.sz,
4834
+ tgtCcy: resolved.tgtCcy,
4894
4835
  px: readString(args, "px"),
4895
4836
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
4896
4837
  clOrdId: readString(args, "clOrdId"),
@@ -4899,7 +4840,11 @@ function buildContractTradeTools(cfg) {
4899
4840
  }),
4900
4841
  privateRateLimit(n("place_order"), 60)
4901
4842
  );
4902
- return normalizeResponse(response);
4843
+ const result = normalizeResponse(response);
4844
+ if (resolved.conversionNote) {
4845
+ result._conversion = resolved.conversionNote;
4846
+ }
4847
+ return result;
4903
4848
  }
4904
4849
  },
4905
4850
  // ── cancel_order ─────────────────────────────────────────────────────────
@@ -5974,7 +5919,7 @@ function registerOptionAlgoTools() {
5974
5919
  tgtCcy: {
5975
5920
  type: "string",
5976
5921
  enum: ["base_ccy", "quote_ccy"],
5977
- description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT (may not be supported for options)"
5922
+ description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT (auto-converted to contracts)"
5978
5923
  },
5979
5924
  reduceOnly: {
5980
5925
  type: "boolean",
@@ -5990,6 +5935,13 @@ function registerOptionAlgoTools() {
5990
5935
  handler: async (rawArgs, context) => {
5991
5936
  const args = asRecord(rawArgs);
5992
5937
  const reduceOnly = readBoolean(args, "reduceOnly");
5938
+ const resolved = await resolveQuoteCcySz(
5939
+ requireString(args, "instId"),
5940
+ requireString(args, "sz"),
5941
+ readString(args, "tgtCcy"),
5942
+ "OPTION",
5943
+ context.client
5944
+ );
5993
5945
  const response = await context.client.privatePost(
5994
5946
  "/api/v5/trade/order-algo",
5995
5947
  compactObject({
@@ -5997,8 +5949,8 @@ function registerOptionAlgoTools() {
5997
5949
  tdMode: requireString(args, "tdMode"),
5998
5950
  side: requireString(args, "side"),
5999
5951
  ordType: requireString(args, "ordType"),
6000
- sz: requireString(args, "sz"),
6001
- tgtCcy: readString(args, "tgtCcy"),
5952
+ sz: resolved.sz,
5953
+ tgtCcy: resolved.tgtCcy,
6002
5954
  tpTriggerPx: readString(args, "tpTriggerPx"),
6003
5955
  tpOrdPx: readString(args, "tpOrdPx"),
6004
5956
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -6205,7 +6157,12 @@ function registerOptionTools() {
6205
6157
  },
6206
6158
  sz: {
6207
6159
  type: "string",
6208
- description: "Contracts count (NOT USDT). Use market_get_instruments for ctVal."
6160
+ description: "Contracts count by default. Set tgtCcy=quote_ccy to use USDT amount instead."
6161
+ },
6162
+ tgtCcy: {
6163
+ type: "string",
6164
+ enum: ["base_ccy", "quote_ccy"],
6165
+ description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT (auto-converted to contracts)"
6209
6166
  },
6210
6167
  px: {
6211
6168
  type: "string",
@@ -6242,6 +6199,13 @@ function registerOptionTools() {
6242
6199
  const args = asRecord(rawArgs);
6243
6200
  const reduceOnly = args.reduceOnly;
6244
6201
  const attachAlgoOrds = buildAttachAlgoOrds(args);
6202
+ const resolved = await resolveQuoteCcySz(
6203
+ requireString(args, "instId"),
6204
+ requireString(args, "sz"),
6205
+ readString(args, "tgtCcy"),
6206
+ "OPTION",
6207
+ context.client
6208
+ );
6245
6209
  const response = await context.client.privatePost(
6246
6210
  "/api/v5/trade/order",
6247
6211
  compactObject({
@@ -6249,7 +6213,8 @@ function registerOptionTools() {
6249
6213
  tdMode: requireString(args, "tdMode"),
6250
6214
  side: requireString(args, "side"),
6251
6215
  ordType: requireString(args, "ordType"),
6252
- sz: requireString(args, "sz"),
6216
+ sz: resolved.sz,
6217
+ tgtCcy: resolved.tgtCcy,
6253
6218
  px: readString(args, "px"),
6254
6219
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
6255
6220
  clOrdId: readString(args, "clOrdId"),
@@ -7405,7 +7370,7 @@ function createToolRunner(client, config) {
7405
7370
  };
7406
7371
  }
7407
7372
  function configFilePath() {
7408
- return join5(homedir3(), ".okx", "config.toml");
7373
+ return join4(homedir2(), ".okx", "config.toml");
7409
7374
  }
7410
7375
  function readFullConfig() {
7411
7376
  const path42 = configFilePath();
@@ -7556,7 +7521,7 @@ function loadConfig(cli) {
7556
7521
  verbose: cli.verbose ?? false
7557
7522
  };
7558
7523
  }
7559
- var CACHE_FILE = join6(homedir4(), ".okx", "update-check.json");
7524
+ var CACHE_FILE = join5(homedir3(), ".okx", "update-check.json");
7560
7525
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7561
7526
  function readCache() {
7562
7527
  try {
@@ -7569,7 +7534,7 @@ function readCache() {
7569
7534
  }
7570
7535
  function writeCache(cache) {
7571
7536
  try {
7572
- mkdirSync5(join6(homedir4(), ".okx"), { recursive: true });
7537
+ mkdirSync5(join5(homedir3(), ".okx"), { recursive: true });
7573
7538
  writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7574
7539
  } catch {
7575
7540
  }
@@ -8399,7 +8364,7 @@ async function cmdDiagnoseMcp(options = {}) {
8399
8364
 
8400
8365
  // src/commands/diagnose.ts
8401
8366
  var CLI_VERSION = readCliVersion();
8402
- var GIT_HASH = true ? "53b8eb6" : "dev";
8367
+ var GIT_HASH = true ? "d684c01" : "dev";
8403
8368
  function maskKey2(key) {
8404
8369
  if (!key) return "(not set)";
8405
8370
  if (key.length <= 8) return "****";
@@ -8697,12 +8662,12 @@ async function runCliChecks(config, profile, outputPath) {
8697
8662
  // src/commands/upgrade.ts
8698
8663
  import { spawnSync as spawnSync2 } from "child_process";
8699
8664
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
8700
- import { dirname as dirname5, join as join8 } from "path";
8701
- import { homedir as homedir6 } from "os";
8665
+ import { dirname as dirname5, join as join7 } from "path";
8666
+ import { homedir as homedir5 } from "os";
8702
8667
  var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
8703
- var CACHE_FILE2 = join8(homedir6(), ".okx", "last_check");
8668
+ var CACHE_FILE2 = join7(homedir5(), ".okx", "last_check");
8704
8669
  var THROTTLE_MS = 12 * 60 * 60 * 1e3;
8705
- var NPM_BIN = join8(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
8670
+ var NPM_BIN = join7(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
8706
8671
  function readLastCheck() {
8707
8672
  try {
8708
8673
  return parseInt(readFileSync6(CACHE_FILE2, "utf-8").trim(), 10) || 0;
@@ -8712,7 +8677,7 @@ function readLastCheck() {
8712
8677
  }
8713
8678
  function writeLastCheck() {
8714
8679
  try {
8715
- mkdirSync7(join8(homedir6(), ".okx"), { recursive: true });
8680
+ mkdirSync7(join7(homedir5(), ".okx"), { recursive: true });
8716
8681
  writeFileSync6(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
8717
8682
  } catch {
8718
8683
  }
@@ -11248,6 +11213,7 @@ async function cmdOptionPlace(run, opts) {
11248
11213
  side: opts.side,
11249
11214
  ordType: opts.ordType,
11250
11215
  sz: opts.sz,
11216
+ tgtCcy: opts.tgtCcy,
11251
11217
  px: opts.px,
11252
11218
  reduceOnly: opts.reduceOnly,
11253
11219
  clOrdId: opts.clOrdId,
@@ -12294,7 +12260,7 @@ async function cmdDcdRedeemExecute(run, opts) {
12294
12260
  ordId: r["ordId"],
12295
12261
  state: r["state"],
12296
12262
  redeemSz: q["redeemSz"] ? `${parseFloat(q["redeemSz"]).toFixed(8)} ${q["redeemCcy"]}` : "\u2014",
12297
- termRate: q["termRate"] ? `${q["termRate"]}%` : "\u2014"
12263
+ termRate: q["termRate"] ? `${(parseFloat(q["termRate"]) * 100).toFixed(2)}%` : "\u2014"
12298
12264
  });
12299
12265
  }
12300
12266
  async function cmdDcdOrderState(run, opts) {
@@ -12347,7 +12313,7 @@ async function cmdDcdOrders(run, opts) {
12347
12313
  quoteCcy: r["quoteCcy"],
12348
12314
  strike: r["strike"],
12349
12315
  notionalSz: r["notionalSz"],
12350
- annualizedYield: r["annualizedYield"],
12316
+ annualizedYield: r["annualizedYield"] ? `${(parseFloat(r["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
12351
12317
  yieldSz: r["yieldSz"],
12352
12318
  settleTime: r["settleTime"] ? new Date(Number(r["settleTime"])).toLocaleDateString() : "",
12353
12319
  // scheduled settlement time
@@ -12387,7 +12353,7 @@ async function cmdDcdQuoteAndBuy(run, opts) {
12387
12353
  outputLine("Quote:");
12388
12354
  printKv({
12389
12355
  quoteId: q["quoteId"],
12390
- annualizedYield: q["annualizedYield"] ? `${q["annualizedYield"]}%` : "\u2014",
12356
+ annualizedYield: q["annualizedYield"] ? `${(parseFloat(q["annualizedYield"]) * 100).toFixed(2)}%` : "\u2014",
12391
12357
  absYield: q["absYield"],
12392
12358
  notionalSz: q["notionalSz"],
12393
12359
  notionalCcy: q["notionalCcy"]
@@ -12411,16 +12377,17 @@ async function cmdDcdQuoteAndBuy(run, opts) {
12411
12377
  }
12412
12378
 
12413
12379
  // src/commands/skill.ts
12414
- import { tmpdir, homedir as homedir8 } from "os";
12415
- import { join as join10, dirname as dirname6 } from "path";
12380
+ import { tmpdir, homedir as homedir7 } from "os";
12381
+ import { join as join9, dirname as dirname6 } from "path";
12416
12382
  import { mkdirSync as mkdirSync8, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
12417
12383
  import { execFileSync as execFileSync2 } from "child_process";
12418
12384
  import { randomUUID as randomUUID2 } from "crypto";
12419
12385
  function resolveNpx() {
12420
- const sibling = join10(dirname6(process.execPath), "npx");
12386
+ const sibling = join9(dirname6(process.execPath), "npx");
12421
12387
  if (existsSync7(sibling)) return sibling;
12422
12388
  return "npx";
12423
12389
  }
12390
+ var THIRD_PARTY_INSTALL_NOTICE = "Note: This skill was created by a third-party developer, not by OKX. Review SKILL.md before use.";
12424
12391
  async function cmdSkillSearch(run, opts) {
12425
12392
  const args = {};
12426
12393
  if (opts.keyword) args.keyword = opts.keyword;
@@ -12470,13 +12437,13 @@ async function cmdSkillCategories(run, json) {
12470
12437
  outputLine("");
12471
12438
  }
12472
12439
  async function cmdSkillAdd(name, config, json) {
12473
- const tmpBase = join10(tmpdir(), `okx-skill-${randomUUID2()}`);
12440
+ const tmpBase = join9(tmpdir(), `okx-skill-${randomUUID2()}`);
12474
12441
  mkdirSync8(tmpBase, { recursive: true });
12475
12442
  try {
12476
12443
  outputLine(`Downloading ${name}...`);
12477
12444
  const client = new OkxRestClient(config);
12478
12445
  const zipPath = await downloadSkillZip(client, name, tmpBase);
12479
- const contentDir = await extractSkillZip(zipPath, join10(tmpBase, "content"));
12446
+ const contentDir = await extractSkillZip(zipPath, join9(tmpBase, "content"));
12480
12447
  const meta = readMetaJson(contentDir);
12481
12448
  validateSkillMdExists(contentDir);
12482
12449
  outputLine("Installing to detected agents...");
@@ -12486,7 +12453,7 @@ async function cmdSkillAdd(name, config, json) {
12486
12453
  timeout: 6e4
12487
12454
  });
12488
12455
  } catch (e) {
12489
- const savedZip = join10(process.cwd(), `${name}.zip`);
12456
+ const savedZip = join9(process.cwd(), `${name}.zip`);
12490
12457
  try {
12491
12458
  copyFileSync2(zipPath, savedZip);
12492
12459
  } catch {
@@ -12496,11 +12463,7 @@ async function cmdSkillAdd(name, config, json) {
12496
12463
  throw e;
12497
12464
  }
12498
12465
  upsertSkillRecord(meta);
12499
- if (json) {
12500
- outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
12501
- } else {
12502
- outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
12503
- }
12466
+ printSkillInstallResult(meta, json);
12504
12467
  } finally {
12505
12468
  rmSync(tmpBase, { recursive: true, force: true });
12506
12469
  }
@@ -12529,7 +12492,7 @@ function cmdSkillRemove(name, json) {
12529
12492
  timeout: 6e4
12530
12493
  });
12531
12494
  } catch {
12532
- const agentsPath = join10(homedir8(), ".agents", "skills", name);
12495
+ const agentsPath = join9(homedir7(), ".agents", "skills", name);
12533
12496
  try {
12534
12497
  rmSync(agentsPath, { recursive: true, force: true });
12535
12498
  } catch {
@@ -12593,11 +12556,19 @@ function cmdSkillList(json) {
12593
12556
  outputLine("");
12594
12557
  outputLine(`${skills.length} skills installed.`);
12595
12558
  }
12559
+ function printSkillInstallResult(meta, json) {
12560
+ if (json) {
12561
+ outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
12562
+ } else {
12563
+ outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
12564
+ outputLine(` ${THIRD_PARTY_INSTALL_NOTICE}`);
12565
+ }
12566
+ }
12596
12567
 
12597
12568
  // src/index.ts
12598
12569
  var _require3 = createRequire3(import.meta.url);
12599
12570
  var CLI_VERSION2 = _require3("../package.json").version;
12600
- var GIT_HASH2 = true ? "53b8eb6" : "dev";
12571
+ var GIT_HASH2 = true ? "d684c01" : "dev";
12601
12572
  function handleConfigCommand(action, rest, json, lang, force) {
12602
12573
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
12603
12574
  if (action === "show") return cmdConfigShow(json);
@@ -13004,6 +12975,7 @@ function handleOptionCommand(run, action, rest, v, json) {
13004
12975
  side: v.side,
13005
12976
  ordType: v.ordType,
13006
12977
  sz: v.sz,
12978
+ tgtCcy: v.tgtCcy,
13007
12979
  px: v.px,
13008
12980
  reduceOnly: v.reduceOnly,
13009
12981
  clOrdId: v.clOrdId,