@okx_ai/okx-trade-cli 1.2.9-beta.1 → 1.2.9-beta.2

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: {
@@ -4881,6 +4805,13 @@ function buildContractTradeTools(cfg) {
4881
4805
  const args = asRecord(rawArgs);
4882
4806
  const reduceOnly = args.reduceOnly;
4883
4807
  const attachAlgoOrds = buildAttachAlgoOrds(args);
4808
+ const resolved = await resolveQuoteCcySz(
4809
+ requireString(args, "instId"),
4810
+ requireString(args, "sz"),
4811
+ readString(args, "tgtCcy"),
4812
+ defaultType,
4813
+ context.client
4814
+ );
4884
4815
  const response = await context.client.privatePost(
4885
4816
  "/api/v5/trade/order",
4886
4817
  compactObject({
@@ -4889,8 +4820,8 @@ function buildContractTradeTools(cfg) {
4889
4820
  side: requireString(args, "side"),
4890
4821
  posSide: readString(args, "posSide"),
4891
4822
  ordType: requireString(args, "ordType"),
4892
- sz: requireString(args, "sz"),
4893
- tgtCcy: readString(args, "tgtCcy"),
4823
+ sz: resolved.sz,
4824
+ tgtCcy: resolved.tgtCcy,
4894
4825
  px: readString(args, "px"),
4895
4826
  reduceOnly: typeof reduceOnly === "boolean" ? String(reduceOnly) : void 0,
4896
4827
  clOrdId: readString(args, "clOrdId"),
@@ -4899,7 +4830,11 @@ function buildContractTradeTools(cfg) {
4899
4830
  }),
4900
4831
  privateRateLimit(n("place_order"), 60)
4901
4832
  );
4902
- return normalizeResponse(response);
4833
+ const result = normalizeResponse(response);
4834
+ if (resolved.conversionNote) {
4835
+ result._conversion = resolved.conversionNote;
4836
+ }
4837
+ return result;
4903
4838
  }
4904
4839
  },
4905
4840
  // ── cancel_order ─────────────────────────────────────────────────────────
@@ -5974,7 +5909,7 @@ function registerOptionAlgoTools() {
5974
5909
  tgtCcy: {
5975
5910
  type: "string",
5976
5911
  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)"
5912
+ description: "Size unit. base_ccy(default): sz in contracts, quote_ccy: sz in USDT (auto-converted to contracts)"
5978
5913
  },
5979
5914
  reduceOnly: {
5980
5915
  type: "boolean",
@@ -5990,6 +5925,13 @@ function registerOptionAlgoTools() {
5990
5925
  handler: async (rawArgs, context) => {
5991
5926
  const args = asRecord(rawArgs);
5992
5927
  const reduceOnly = readBoolean(args, "reduceOnly");
5928
+ const resolved = await resolveQuoteCcySz(
5929
+ requireString(args, "instId"),
5930
+ requireString(args, "sz"),
5931
+ readString(args, "tgtCcy"),
5932
+ "OPTION",
5933
+ context.client
5934
+ );
5993
5935
  const response = await context.client.privatePost(
5994
5936
  "/api/v5/trade/order-algo",
5995
5937
  compactObject({
@@ -5997,8 +5939,8 @@ function registerOptionAlgoTools() {
5997
5939
  tdMode: requireString(args, "tdMode"),
5998
5940
  side: requireString(args, "side"),
5999
5941
  ordType: requireString(args, "ordType"),
6000
- sz: requireString(args, "sz"),
6001
- tgtCcy: readString(args, "tgtCcy"),
5942
+ sz: resolved.sz,
5943
+ tgtCcy: resolved.tgtCcy,
6002
5944
  tpTriggerPx: readString(args, "tpTriggerPx"),
6003
5945
  tpOrdPx: readString(args, "tpOrdPx"),
6004
5946
  tpTriggerPxType: readString(args, "tpTriggerPxType"),
@@ -7405,7 +7347,7 @@ function createToolRunner(client, config) {
7405
7347
  };
7406
7348
  }
7407
7349
  function configFilePath() {
7408
- return join5(homedir3(), ".okx", "config.toml");
7350
+ return join4(homedir2(), ".okx", "config.toml");
7409
7351
  }
7410
7352
  function readFullConfig() {
7411
7353
  const path42 = configFilePath();
@@ -7556,7 +7498,7 @@ function loadConfig(cli) {
7556
7498
  verbose: cli.verbose ?? false
7557
7499
  };
7558
7500
  }
7559
- var CACHE_FILE = join6(homedir4(), ".okx", "update-check.json");
7501
+ var CACHE_FILE = join5(homedir3(), ".okx", "update-check.json");
7560
7502
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7561
7503
  function readCache() {
7562
7504
  try {
@@ -7569,7 +7511,7 @@ function readCache() {
7569
7511
  }
7570
7512
  function writeCache(cache) {
7571
7513
  try {
7572
- mkdirSync5(join6(homedir4(), ".okx"), { recursive: true });
7514
+ mkdirSync5(join5(homedir3(), ".okx"), { recursive: true });
7573
7515
  writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7574
7516
  } catch {
7575
7517
  }
@@ -8399,7 +8341,7 @@ async function cmdDiagnoseMcp(options = {}) {
8399
8341
 
8400
8342
  // src/commands/diagnose.ts
8401
8343
  var CLI_VERSION = readCliVersion();
8402
- var GIT_HASH = true ? "53b8eb6" : "dev";
8344
+ var GIT_HASH = true ? "5f833a2" : "dev";
8403
8345
  function maskKey2(key) {
8404
8346
  if (!key) return "(not set)";
8405
8347
  if (key.length <= 8) return "****";
@@ -8697,12 +8639,12 @@ async function runCliChecks(config, profile, outputPath) {
8697
8639
  // src/commands/upgrade.ts
8698
8640
  import { spawnSync as spawnSync2 } from "child_process";
8699
8641
  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";
8642
+ import { dirname as dirname5, join as join7 } from "path";
8643
+ import { homedir as homedir5 } from "os";
8702
8644
  var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
8703
- var CACHE_FILE2 = join8(homedir6(), ".okx", "last_check");
8645
+ var CACHE_FILE2 = join7(homedir5(), ".okx", "last_check");
8704
8646
  var THROTTLE_MS = 12 * 60 * 60 * 1e3;
8705
- var NPM_BIN = join8(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
8647
+ var NPM_BIN = join7(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
8706
8648
  function readLastCheck() {
8707
8649
  try {
8708
8650
  return parseInt(readFileSync6(CACHE_FILE2, "utf-8").trim(), 10) || 0;
@@ -8712,7 +8654,7 @@ function readLastCheck() {
8712
8654
  }
8713
8655
  function writeLastCheck() {
8714
8656
  try {
8715
- mkdirSync7(join8(homedir6(), ".okx"), { recursive: true });
8657
+ mkdirSync7(join7(homedir5(), ".okx"), { recursive: true });
8716
8658
  writeFileSync6(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
8717
8659
  } catch {
8718
8660
  }
@@ -12411,16 +12353,17 @@ async function cmdDcdQuoteAndBuy(run, opts) {
12411
12353
  }
12412
12354
 
12413
12355
  // src/commands/skill.ts
12414
- import { tmpdir, homedir as homedir8 } from "os";
12415
- import { join as join10, dirname as dirname6 } from "path";
12356
+ import { tmpdir, homedir as homedir7 } from "os";
12357
+ import { join as join9, dirname as dirname6 } from "path";
12416
12358
  import { mkdirSync as mkdirSync8, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
12417
12359
  import { execFileSync as execFileSync2 } from "child_process";
12418
12360
  import { randomUUID as randomUUID2 } from "crypto";
12419
12361
  function resolveNpx() {
12420
- const sibling = join10(dirname6(process.execPath), "npx");
12362
+ const sibling = join9(dirname6(process.execPath), "npx");
12421
12363
  if (existsSync7(sibling)) return sibling;
12422
12364
  return "npx";
12423
12365
  }
12366
+ var THIRD_PARTY_INSTALL_NOTICE = "Note: This skill was created by a third-party developer, not by OKX. Review SKILL.md before use.";
12424
12367
  async function cmdSkillSearch(run, opts) {
12425
12368
  const args = {};
12426
12369
  if (opts.keyword) args.keyword = opts.keyword;
@@ -12470,13 +12413,13 @@ async function cmdSkillCategories(run, json) {
12470
12413
  outputLine("");
12471
12414
  }
12472
12415
  async function cmdSkillAdd(name, config, json) {
12473
- const tmpBase = join10(tmpdir(), `okx-skill-${randomUUID2()}`);
12416
+ const tmpBase = join9(tmpdir(), `okx-skill-${randomUUID2()}`);
12474
12417
  mkdirSync8(tmpBase, { recursive: true });
12475
12418
  try {
12476
12419
  outputLine(`Downloading ${name}...`);
12477
12420
  const client = new OkxRestClient(config);
12478
12421
  const zipPath = await downloadSkillZip(client, name, tmpBase);
12479
- const contentDir = await extractSkillZip(zipPath, join10(tmpBase, "content"));
12422
+ const contentDir = await extractSkillZip(zipPath, join9(tmpBase, "content"));
12480
12423
  const meta = readMetaJson(contentDir);
12481
12424
  validateSkillMdExists(contentDir);
12482
12425
  outputLine("Installing to detected agents...");
@@ -12486,7 +12429,7 @@ async function cmdSkillAdd(name, config, json) {
12486
12429
  timeout: 6e4
12487
12430
  });
12488
12431
  } catch (e) {
12489
- const savedZip = join10(process.cwd(), `${name}.zip`);
12432
+ const savedZip = join9(process.cwd(), `${name}.zip`);
12490
12433
  try {
12491
12434
  copyFileSync2(zipPath, savedZip);
12492
12435
  } catch {
@@ -12496,11 +12439,7 @@ async function cmdSkillAdd(name, config, json) {
12496
12439
  throw e;
12497
12440
  }
12498
12441
  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
- }
12442
+ printSkillInstallResult(meta, json);
12504
12443
  } finally {
12505
12444
  rmSync(tmpBase, { recursive: true, force: true });
12506
12445
  }
@@ -12529,7 +12468,7 @@ function cmdSkillRemove(name, json) {
12529
12468
  timeout: 6e4
12530
12469
  });
12531
12470
  } catch {
12532
- const agentsPath = join10(homedir8(), ".agents", "skills", name);
12471
+ const agentsPath = join9(homedir7(), ".agents", "skills", name);
12533
12472
  try {
12534
12473
  rmSync(agentsPath, { recursive: true, force: true });
12535
12474
  } catch {
@@ -12593,11 +12532,19 @@ function cmdSkillList(json) {
12593
12532
  outputLine("");
12594
12533
  outputLine(`${skills.length} skills installed.`);
12595
12534
  }
12535
+ function printSkillInstallResult(meta, json) {
12536
+ if (json) {
12537
+ outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
12538
+ } else {
12539
+ outputLine(`\u2713 Skill "${meta.name}" v${meta.version} installed`);
12540
+ outputLine(` ${THIRD_PARTY_INSTALL_NOTICE}`);
12541
+ }
12542
+ }
12596
12543
 
12597
12544
  // src/index.ts
12598
12545
  var _require3 = createRequire3(import.meta.url);
12599
12546
  var CLI_VERSION2 = _require3("../package.json").version;
12600
- var GIT_HASH2 = true ? "53b8eb6" : "dev";
12547
+ var GIT_HASH2 = true ? "5f833a2" : "dev";
12601
12548
  function handleConfigCommand(action, rest, json, lang, force) {
12602
12549
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
12603
12550
  if (action === "show") return cmdConfigShow(json);