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

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,30 @@
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 { 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";
8
13
  import { createHmac } from "crypto";
9
14
  import fs from "fs";
10
15
  import path from "path";
11
16
  import os from "os";
12
17
  import { writeFileSync, renameSync, unlinkSync, mkdirSync } from "fs";
13
- import { join, resolve, basename, sep } from "path";
18
+ import { join as join2, resolve, basename, sep } from "path";
14
19
  import { randomUUID } from "crypto";
15
20
  import yauzl from "yauzl";
16
21
  import { createWriteStream, mkdirSync as mkdirSync2 } from "fs";
17
22
  import { resolve as resolve2, dirname } from "path";
18
23
  import { readFileSync, existsSync } from "fs";
19
- import { join as join2 } from "path";
24
+ import { join as join3 } from "path";
20
25
  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";
26
+ import { join as join4, dirname as dirname2 } from "path";
25
27
  import { homedir as homedir2 } from "os";
28
+ 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";
26
31
 
27
32
  // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
28
33
  function getLineColFromPtr(string, ptr) {
@@ -852,12 +857,66 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
852
857
 
853
858
  // ../core/dist/index.js
854
859
  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";
860
+ import { join as join6 } from "path";
861
+ import { homedir as homedir4 } from "os";
857
862
  import * as fs3 from "fs";
858
863
  import * as path3 from "path";
859
864
  import * as os3 from "os";
860
865
  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
+ }
861
920
  function getNow() {
862
921
  return (/* @__PURE__ */ new Date()).toISOString();
863
922
  }
@@ -1076,12 +1135,80 @@ var OkxRestClient = class _OkxRestClient {
1076
1135
  config;
1077
1136
  rateLimiter;
1078
1137
  dispatcher;
1079
- constructor(config) {
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) {
1080
1150
  this.config = config;
1081
1151
  this.rateLimiter = new RateLimiter(3e4, config.verbose);
1082
1152
  if (config.proxyUrl) {
1083
1153
  this.dispatcher = new ProxyAgent(config.proxyUrl);
1084
1154
  }
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
+ }
1085
1212
  }
1086
1213
  logRequest(method, url, auth) {
1087
1214
  if (!this.config.verbose) return;
@@ -1252,13 +1379,18 @@ var OkxRestClient = class _OkxRestClient {
1252
1379
  * Security: validates Content-Type and enforces maxBytes limit.
1253
1380
  */
1254
1381
  async privatePostBinary(path42, body, opts) {
1382
+ await this.ensureDoh();
1255
1383
  const maxBytes = opts?.maxBytes ?? _OkxRestClient.DEFAULT_MAX_BYTES;
1256
1384
  const expectedCT = opts?.expectedContentType ?? "application/octet-stream";
1257
1385
  const bodyJson = body ? JSON.stringify(body) : "";
1258
1386
  const endpoint = `POST ${path42}`;
1259
- this.logRequest("POST", `${this.config.baseUrl}${path42}`, "private");
1387
+ const baseUrl = this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
1388
+ this.logRequest("POST", `${baseUrl}${path42}`, "private");
1260
1389
  const reqConfig = { method: "POST", path: path42, auth: "private" };
1261
1390
  const headers = this.buildHeaders(reqConfig, path42, bodyJson, getNow());
1391
+ if (this.dohNode) {
1392
+ headers.set("User-Agent", "OKX/2.7.2");
1393
+ }
1262
1394
  const t0 = Date.now();
1263
1395
  const response = await this.fetchBinary(path42, endpoint, headers, bodyJson, t0);
1264
1396
  const elapsed = Date.now() - t0;
@@ -1288,14 +1420,19 @@ var OkxRestClient = class _OkxRestClient {
1288
1420
  /** Execute fetch for binary endpoint, wrapping network errors. */
1289
1421
  async fetchBinary(path42, endpoint, headers, bodyJson, t0) {
1290
1422
  try {
1423
+ const baseUrl = this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
1291
1424
  const fetchOptions = {
1292
1425
  method: "POST",
1293
1426
  headers,
1294
1427
  body: bodyJson || void 0,
1295
1428
  signal: AbortSignal.timeout(this.config.timeoutMs)
1296
1429
  };
1297
- if (this.dispatcher) fetchOptions.dispatcher = this.dispatcher;
1298
- return await fetch(`${this.config.baseUrl}${path42}`, fetchOptions);
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);
1299
1436
  } catch (error) {
1300
1437
  if (this.config.verbose) {
1301
1438
  vlog(`\u2717 NetworkError after ${Date.now() - t0}ms: ${error instanceof Error ? error.message : String(error)}`);
@@ -1326,9 +1463,11 @@ var OkxRestClient = class _OkxRestClient {
1326
1463
  // JSON request
1327
1464
  // ---------------------------------------------------------------------------
1328
1465
  async request(reqConfig) {
1466
+ await this.ensureDoh();
1329
1467
  const queryString = buildQueryString(reqConfig.query);
1330
1468
  const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
1331
- const url = `${this.config.baseUrl}${requestPath}`;
1469
+ const baseUrl = this.dohNode ? this.dohBaseUrl : this.config.baseUrl;
1470
+ const url = `${baseUrl}${requestPath}`;
1332
1471
  const bodyJson = reqConfig.body ? JSON.stringify(reqConfig.body) : "";
1333
1472
  const timestamp = getNow();
1334
1473
  this.logRequest(reqConfig.method, url, reqConfig.auth);
@@ -1336,6 +1475,9 @@ var OkxRestClient = class _OkxRestClient {
1336
1475
  await this.rateLimiter.consume(reqConfig.rateLimit);
1337
1476
  }
1338
1477
  const headers = this.buildHeaders(reqConfig, requestPath, bodyJson, timestamp);
1478
+ if (this.dohNode) {
1479
+ headers.set("User-Agent", "OKX/2.7.2");
1480
+ }
1339
1481
  const t0 = Date.now();
1340
1482
  let response;
1341
1483
  try {
@@ -1347,9 +1489,18 @@ var OkxRestClient = class _OkxRestClient {
1347
1489
  };
1348
1490
  if (this.dispatcher) {
1349
1491
  fetchOptions.dispatcher = this.dispatcher;
1492
+ } else if (this.dohAgent) {
1493
+ fetchOptions.dispatcher = this.dohAgent;
1350
1494
  }
1351
1495
  response = await fetch(url, fetchOptions);
1352
1496
  } 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
+ }
1353
1504
  if (this.config.verbose) {
1354
1505
  const elapsed2 = Date.now() - t0;
1355
1506
  const cause = error instanceof Error ? error.message : String(error);
@@ -2975,7 +3126,7 @@ function safeWriteFile(targetDir, fileName, data) {
2975
3126
  throw new Error(`Invalid file name: "${fileName}"`);
2976
3127
  }
2977
3128
  const resolvedDir = resolve(targetDir);
2978
- const filePath = join(resolvedDir, safeName);
3129
+ const filePath = join2(resolvedDir, safeName);
2979
3130
  const resolvedPath = resolve(filePath);
2980
3131
  if (!resolvedPath.startsWith(resolvedDir + sep)) {
2981
3132
  throw new Error(`Path traversal detected: "${fileName}" resolves outside target directory`);
@@ -3091,7 +3242,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
3091
3242
  });
3092
3243
  }
3093
3244
  function readMetaJson(contentDir) {
3094
- const metaPath = join2(contentDir, "_meta.json");
3245
+ const metaPath = join3(contentDir, "_meta.json");
3095
3246
  if (!existsSync(metaPath)) {
3096
3247
  throw new Error(`_meta.json not found in ${contentDir}. Invalid skill package.`);
3097
3248
  }
@@ -3117,12 +3268,12 @@ function readMetaJson(contentDir) {
3117
3268
  };
3118
3269
  }
3119
3270
  function validateSkillMdExists(contentDir) {
3120
- const skillMdPath = join2(contentDir, "SKILL.md");
3271
+ const skillMdPath = join3(contentDir, "SKILL.md");
3121
3272
  if (!existsSync(skillMdPath)) {
3122
3273
  throw new Error(`SKILL.md not found in ${contentDir}. Invalid skill package.`);
3123
3274
  }
3124
3275
  }
3125
- var DEFAULT_REGISTRY_PATH = join3(homedir(), ".okx", "skills", "registry.json");
3276
+ var DEFAULT_REGISTRY_PATH = join4(homedir2(), ".okx", "skills", "registry.json");
3126
3277
  function readRegistry(registryPath = DEFAULT_REGISTRY_PATH) {
3127
3278
  if (!existsSync2(registryPath)) {
3128
3279
  return { version: 1, skills: {} };
@@ -7254,7 +7405,7 @@ function createToolRunner(client, config) {
7254
7405
  };
7255
7406
  }
7256
7407
  function configFilePath() {
7257
- return join4(homedir2(), ".okx", "config.toml");
7408
+ return join5(homedir3(), ".okx", "config.toml");
7258
7409
  }
7259
7410
  function readFullConfig() {
7260
7411
  const path42 = configFilePath();
@@ -7405,7 +7556,7 @@ function loadConfig(cli) {
7405
7556
  verbose: cli.verbose ?? false
7406
7557
  };
7407
7558
  }
7408
- var CACHE_FILE = join5(homedir3(), ".okx", "update-check.json");
7559
+ var CACHE_FILE = join6(homedir4(), ".okx", "update-check.json");
7409
7560
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7410
7561
  function readCache() {
7411
7562
  try {
@@ -7418,7 +7569,7 @@ function readCache() {
7418
7569
  }
7419
7570
  function writeCache(cache) {
7420
7571
  try {
7421
- mkdirSync5(join5(homedir3(), ".okx"), { recursive: true });
7572
+ mkdirSync5(join6(homedir4(), ".okx"), { recursive: true });
7422
7573
  writeFileSync4(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
7423
7574
  } catch {
7424
7575
  }
@@ -8248,7 +8399,7 @@ async function cmdDiagnoseMcp(options = {}) {
8248
8399
 
8249
8400
  // src/commands/diagnose.ts
8250
8401
  var CLI_VERSION = readCliVersion();
8251
- var GIT_HASH = true ? "e46af2f" : "dev";
8402
+ var GIT_HASH = true ? "53b8eb6" : "dev";
8252
8403
  function maskKey2(key) {
8253
8404
  if (!key) return "(not set)";
8254
8405
  if (key.length <= 8) return "****";
@@ -8546,12 +8697,12 @@ async function runCliChecks(config, profile, outputPath) {
8546
8697
  // src/commands/upgrade.ts
8547
8698
  import { spawnSync as spawnSync2 } from "child_process";
8548
8699
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
8549
- import { dirname as dirname5, join as join7 } from "path";
8550
- import { homedir as homedir5 } from "os";
8700
+ import { dirname as dirname5, join as join8 } from "path";
8701
+ import { homedir as homedir6 } from "os";
8551
8702
  var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
8552
- var CACHE_FILE2 = join7(homedir5(), ".okx", "last_check");
8703
+ var CACHE_FILE2 = join8(homedir6(), ".okx", "last_check");
8553
8704
  var THROTTLE_MS = 12 * 60 * 60 * 1e3;
8554
- var NPM_BIN = join7(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
8705
+ var NPM_BIN = join8(dirname5(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
8555
8706
  function readLastCheck() {
8556
8707
  try {
8557
8708
  return parseInt(readFileSync6(CACHE_FILE2, "utf-8").trim(), 10) || 0;
@@ -8561,7 +8712,7 @@ function readLastCheck() {
8561
8712
  }
8562
8713
  function writeLastCheck() {
8563
8714
  try {
8564
- mkdirSync7(join7(homedir5(), ".okx"), { recursive: true });
8715
+ mkdirSync7(join8(homedir6(), ".okx"), { recursive: true });
8565
8716
  writeFileSync6(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
8566
8717
  } catch {
8567
8718
  }
@@ -12260,13 +12411,13 @@ async function cmdDcdQuoteAndBuy(run, opts) {
12260
12411
  }
12261
12412
 
12262
12413
  // src/commands/skill.ts
12263
- import { tmpdir, homedir as homedir7 } from "os";
12264
- import { join as join9, dirname as dirname6 } from "path";
12414
+ import { tmpdir, homedir as homedir8 } from "os";
12415
+ import { join as join10, dirname as dirname6 } from "path";
12265
12416
  import { mkdirSync as mkdirSync8, rmSync, existsSync as existsSync7, copyFileSync as copyFileSync2 } from "fs";
12266
12417
  import { execFileSync as execFileSync2 } from "child_process";
12267
12418
  import { randomUUID as randomUUID2 } from "crypto";
12268
12419
  function resolveNpx() {
12269
- const sibling = join9(dirname6(process.execPath), "npx");
12420
+ const sibling = join10(dirname6(process.execPath), "npx");
12270
12421
  if (existsSync7(sibling)) return sibling;
12271
12422
  return "npx";
12272
12423
  }
@@ -12319,13 +12470,13 @@ async function cmdSkillCategories(run, json) {
12319
12470
  outputLine("");
12320
12471
  }
12321
12472
  async function cmdSkillAdd(name, config, json) {
12322
- const tmpBase = join9(tmpdir(), `okx-skill-${randomUUID2()}`);
12473
+ const tmpBase = join10(tmpdir(), `okx-skill-${randomUUID2()}`);
12323
12474
  mkdirSync8(tmpBase, { recursive: true });
12324
12475
  try {
12325
12476
  outputLine(`Downloading ${name}...`);
12326
12477
  const client = new OkxRestClient(config);
12327
12478
  const zipPath = await downloadSkillZip(client, name, tmpBase);
12328
- const contentDir = await extractSkillZip(zipPath, join9(tmpBase, "content"));
12479
+ const contentDir = await extractSkillZip(zipPath, join10(tmpBase, "content"));
12329
12480
  const meta = readMetaJson(contentDir);
12330
12481
  validateSkillMdExists(contentDir);
12331
12482
  outputLine("Installing to detected agents...");
@@ -12335,7 +12486,7 @@ async function cmdSkillAdd(name, config, json) {
12335
12486
  timeout: 6e4
12336
12487
  });
12337
12488
  } catch (e) {
12338
- const savedZip = join9(process.cwd(), `${name}.zip`);
12489
+ const savedZip = join10(process.cwd(), `${name}.zip`);
12339
12490
  try {
12340
12491
  copyFileSync2(zipPath, savedZip);
12341
12492
  } catch {
@@ -12378,7 +12529,7 @@ function cmdSkillRemove(name, json) {
12378
12529
  timeout: 6e4
12379
12530
  });
12380
12531
  } catch {
12381
- const agentsPath = join9(homedir7(), ".agents", "skills", name);
12532
+ const agentsPath = join10(homedir8(), ".agents", "skills", name);
12382
12533
  try {
12383
12534
  rmSync(agentsPath, { recursive: true, force: true });
12384
12535
  } catch {
@@ -12446,7 +12597,7 @@ function cmdSkillList(json) {
12446
12597
  // src/index.ts
12447
12598
  var _require3 = createRequire3(import.meta.url);
12448
12599
  var CLI_VERSION2 = _require3("../package.json").version;
12449
- var GIT_HASH2 = true ? "e46af2f" : "dev";
12600
+ var GIT_HASH2 = true ? "53b8eb6" : "dev";
12450
12601
  function handleConfigCommand(action, rest, json, lang, force) {
12451
12602
  if (action === "init") return cmdConfigInit(lang === "zh" ? "zh" : "en");
12452
12603
  if (action === "show") return cmdConfigShow(json);