@okx_ai/okx-trade-cli 1.3.6-beta.3 → 1.3.7
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/LICENSE +21 -0
- package/dist/index.js +578 -338
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/scripts/postinstall-download.js +152 -0
package/dist/index.js
CHANGED
|
@@ -34,16 +34,22 @@ import os from "os";
|
|
|
34
34
|
import { writeFileSync as writeFileSync2, renameSync as renameSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
35
35
|
import { join as join4, resolve, basename, sep } from "path";
|
|
36
36
|
import { randomUUID } from "crypto";
|
|
37
|
+
import { createHash } from "crypto";
|
|
38
|
+
import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
39
|
+
import { join as join5 } from "path";
|
|
40
|
+
import { createHash as createHash2, createPublicKey, verify as cryptoVerify } from "crypto";
|
|
41
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
42
|
+
import { join as join6, resolve as resolve2, sep as sep2 } from "path";
|
|
37
43
|
import yauzl from "yauzl";
|
|
38
44
|
import { createWriteStream, mkdirSync as mkdirSync3 } from "fs";
|
|
39
|
-
import { resolve as
|
|
40
|
-
import { readFileSync as
|
|
41
|
-
import { join as
|
|
42
|
-
import { readFileSync as
|
|
43
|
-
import { join as
|
|
45
|
+
import { resolve as resolve3, dirname as dirname2 } from "path";
|
|
46
|
+
import { readFileSync as readFileSync4, existsSync } from "fs";
|
|
47
|
+
import { join as join7 } from "path";
|
|
48
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync2 } from "fs";
|
|
49
|
+
import { join as join8, dirname as dirname3 } from "path";
|
|
44
50
|
import { homedir as homedir4 } from "os";
|
|
45
|
-
import { readFileSync as
|
|
46
|
-
import { join as
|
|
51
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync3 } from "fs";
|
|
52
|
+
import { join as join9, dirname as dirname4 } from "path";
|
|
47
53
|
import { homedir as homedir5 } from "os";
|
|
48
54
|
|
|
49
55
|
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
|
|
@@ -121,7 +127,7 @@ function skipVoid(str, ptr, banNewLines, banComments) {
|
|
|
121
127
|
ptr++;
|
|
122
128
|
return banComments || c !== "#" ? ptr : skipVoid(str, skipComment(str, ptr), banNewLines);
|
|
123
129
|
}
|
|
124
|
-
function skipUntil(str, ptr,
|
|
130
|
+
function skipUntil(str, ptr, sep3, end, banNewLines = false) {
|
|
125
131
|
if (!end) {
|
|
126
132
|
ptr = indexOfNewline(str, ptr);
|
|
127
133
|
return ptr < 0 ? str.length : ptr;
|
|
@@ -130,7 +136,7 @@ function skipUntil(str, ptr, sep2, end, banNewLines = false) {
|
|
|
130
136
|
let c = str[i];
|
|
131
137
|
if (c === "#") {
|
|
132
138
|
i = indexOfNewline(str, i);
|
|
133
|
-
} else if (c ===
|
|
139
|
+
} else if (c === sep3) {
|
|
134
140
|
return i + 1;
|
|
135
141
|
} else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
|
|
136
142
|
return i;
|
|
@@ -873,8 +879,8 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
|
|
|
873
879
|
}
|
|
874
880
|
|
|
875
881
|
// ../core/dist/index.js
|
|
876
|
-
import { readFileSync as
|
|
877
|
-
import { join as
|
|
882
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6, existsSync as existsSync4 } from "fs";
|
|
883
|
+
import { join as join10 } from "path";
|
|
878
884
|
import { homedir as homedir6 } from "os";
|
|
879
885
|
import fs2 from "fs";
|
|
880
886
|
import path2 from "path";
|
|
@@ -891,25 +897,37 @@ import {
|
|
|
891
897
|
renameSync as renameSync4
|
|
892
898
|
} from "fs";
|
|
893
899
|
import { homedir as homedir9, platform as platform2 } from "os";
|
|
894
|
-
import { join as
|
|
900
|
+
import { join as join13, dirname as dirname7 } from "path";
|
|
895
901
|
import { createWriteStream as createWriteStream2, unlinkSync as unlinkSync3 } from "fs";
|
|
896
902
|
import { get as httpsGet } from "https";
|
|
897
903
|
import { get as httpGet } from "http";
|
|
898
904
|
import {
|
|
899
|
-
readFileSync as
|
|
905
|
+
readFileSync as readFileSync9,
|
|
900
906
|
mkdirSync as mkdirSync8,
|
|
901
907
|
chmodSync,
|
|
902
908
|
existsSync as existsSync6,
|
|
903
909
|
unlinkSync as unlinkSync4,
|
|
904
910
|
renameSync as renameSync3
|
|
905
911
|
} from "fs";
|
|
906
|
-
import { createHash } from "crypto";
|
|
912
|
+
import { createHash as createHash3 } from "crypto";
|
|
907
913
|
import { homedir as homedir8, platform, arch } from "os";
|
|
908
|
-
import { join as
|
|
909
|
-
import { readFileSync as
|
|
910
|
-
import { join as
|
|
914
|
+
import { join as join12, dirname as dirname6 } from "path";
|
|
915
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10, unlinkSync as unlinkSync6, existsSync as existsSync8 } from "fs";
|
|
916
|
+
import { join as join14 } from "path";
|
|
911
917
|
import { homedir as homedir10 } from "os";
|
|
912
|
-
|
|
918
|
+
function hasProxyEnv(env = process.env) {
|
|
919
|
+
return Boolean(
|
|
920
|
+
env.HTTPS_PROXY || env.https_proxy || env.HTTP_PROXY || env.http_proxy
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
function installEnvProxyDispatcher(deps = {}) {
|
|
924
|
+
const env = deps.env ?? process.env;
|
|
925
|
+
if (!hasProxyEnv(env)) return false;
|
|
926
|
+
const register = deps.register ?? (() => setGlobalDispatcher(new EnvHttpProxyAgent()));
|
|
927
|
+
register();
|
|
928
|
+
return true;
|
|
929
|
+
}
|
|
930
|
+
installEnvProxyDispatcher();
|
|
913
931
|
var EXEC_TIMEOUT_MS = 3e4;
|
|
914
932
|
var ALLOWED_DOMAIN_RE = /^[\w.-]+\.okx\.com$/;
|
|
915
933
|
var PILOT_BIN_DIR = join(homedir(), ".okx", "bin");
|
|
@@ -932,25 +950,25 @@ function execPilotBinary(domain, exclude = [], userAgent) {
|
|
|
932
950
|
if (userAgent) {
|
|
933
951
|
args.push("--user-agent", userAgent);
|
|
934
952
|
}
|
|
935
|
-
return new Promise((
|
|
953
|
+
return new Promise((resolve4) => {
|
|
936
954
|
execFile(
|
|
937
955
|
binPath,
|
|
938
956
|
args,
|
|
939
957
|
{ timeout: EXEC_TIMEOUT_MS, encoding: "utf-8" },
|
|
940
958
|
(error, stdout) => {
|
|
941
959
|
if (error) {
|
|
942
|
-
|
|
960
|
+
resolve4(null);
|
|
943
961
|
return;
|
|
944
962
|
}
|
|
945
963
|
try {
|
|
946
964
|
const result = JSON.parse(stdout);
|
|
947
965
|
if (result.code === 0 && result.data) {
|
|
948
|
-
|
|
966
|
+
resolve4(result.data);
|
|
949
967
|
} else {
|
|
950
|
-
|
|
968
|
+
resolve4(null);
|
|
951
969
|
}
|
|
952
970
|
} catch {
|
|
953
|
-
|
|
971
|
+
resolve4(null);
|
|
954
972
|
}
|
|
955
973
|
}
|
|
956
974
|
);
|
|
@@ -1284,7 +1302,7 @@ var EXIT_CODES = {
|
|
|
1284
1302
|
NOT_LOGGED_IN: 2,
|
|
1285
1303
|
REFRESH_FAILED: 3
|
|
1286
1304
|
};
|
|
1287
|
-
function finalizeToken(code, token,
|
|
1305
|
+
function finalizeToken(code, token, resolve4, reject) {
|
|
1288
1306
|
if (code === EXIT_CODES.SUCCESS) {
|
|
1289
1307
|
if (!token) {
|
|
1290
1308
|
reject(new AuthenticationError(
|
|
@@ -1293,7 +1311,7 @@ function finalizeToken(code, token, resolve3, reject) {
|
|
|
1293
1311
|
));
|
|
1294
1312
|
return;
|
|
1295
1313
|
}
|
|
1296
|
-
|
|
1314
|
+
resolve4(token);
|
|
1297
1315
|
return;
|
|
1298
1316
|
}
|
|
1299
1317
|
if (code === EXIT_CODES.NOT_LOGGED_IN) {
|
|
@@ -1330,7 +1348,7 @@ function defaultWindowsPipeName() {
|
|
|
1330
1348
|
return WIN_PIPE_PREFIX + randomBytes(32).toString("hex");
|
|
1331
1349
|
}
|
|
1332
1350
|
function execAuthTokenWindows(binPath, makePipeName = defaultWindowsPipeName) {
|
|
1333
|
-
return new Promise((
|
|
1351
|
+
return new Promise((resolve4, reject) => {
|
|
1334
1352
|
const pipeName = makePipeName();
|
|
1335
1353
|
const server = createServer();
|
|
1336
1354
|
const chunks = [];
|
|
@@ -1350,7 +1368,7 @@ function execAuthTokenWindows(binPath, makePipeName = defaultWindowsPipeName) {
|
|
|
1350
1368
|
const tryFinalize = () => {
|
|
1351
1369
|
if (!pipeClosed || exitCode === void 0) return;
|
|
1352
1370
|
const token = Buffer.concat(chunks).toString("utf-8").trim();
|
|
1353
|
-
settle(() => finalizeToken(exitCode, token,
|
|
1371
|
+
settle(() => finalizeToken(exitCode, token, resolve4, reject));
|
|
1354
1372
|
};
|
|
1355
1373
|
server.on("connection", (socket) => {
|
|
1356
1374
|
connectionMade = true;
|
|
@@ -1397,7 +1415,7 @@ function execAuthToken() {
|
|
|
1397
1415
|
return process.platform === "win32" ? execAuthTokenWindows(binPath) : execAuthTokenUnix(binPath);
|
|
1398
1416
|
}
|
|
1399
1417
|
function execAuthTokenUnix(binPath) {
|
|
1400
|
-
return new Promise((
|
|
1418
|
+
return new Promise((resolve4, reject) => {
|
|
1401
1419
|
const child = spawn2(binPath, ["token"], {
|
|
1402
1420
|
stdio: ["ignore", "ignore", "inherit", "pipe"]
|
|
1403
1421
|
// stdin stdout stderr fd3 (pipe)
|
|
@@ -1410,35 +1428,35 @@ function execAuthTokenUnix(binPath) {
|
|
|
1410
1428
|
});
|
|
1411
1429
|
child.on("close", (code) => {
|
|
1412
1430
|
const token = Buffer.concat(chunks).toString("utf-8").trim();
|
|
1413
|
-
finalizeToken(code, token,
|
|
1431
|
+
finalizeToken(code, token, resolve4, reject);
|
|
1414
1432
|
});
|
|
1415
1433
|
});
|
|
1416
1434
|
}
|
|
1417
1435
|
function execAuthStatus() {
|
|
1418
1436
|
const binPath = getAuthBinaryPath();
|
|
1419
|
-
return new Promise((
|
|
1437
|
+
return new Promise((resolve4) => {
|
|
1420
1438
|
execFile2(
|
|
1421
1439
|
binPath,
|
|
1422
1440
|
["status", "--json"],
|
|
1423
1441
|
{ timeout: EXEC_TIMEOUT_MS2, encoding: "utf-8" },
|
|
1424
1442
|
(error, stdout) => {
|
|
1425
1443
|
if (error) {
|
|
1426
|
-
|
|
1444
|
+
resolve4(null);
|
|
1427
1445
|
return;
|
|
1428
1446
|
}
|
|
1429
1447
|
try {
|
|
1430
1448
|
const result = JSON.parse(stdout);
|
|
1431
|
-
|
|
1449
|
+
resolve4(result);
|
|
1432
1450
|
} catch {
|
|
1433
|
-
|
|
1451
|
+
resolve4(null);
|
|
1434
1452
|
}
|
|
1435
1453
|
}
|
|
1436
1454
|
);
|
|
1437
1455
|
});
|
|
1438
1456
|
}
|
|
1439
1457
|
function sleep(ms) {
|
|
1440
|
-
return new Promise((
|
|
1441
|
-
setTimeout(
|
|
1458
|
+
return new Promise((resolve4) => {
|
|
1459
|
+
setTimeout(resolve4, ms);
|
|
1442
1460
|
});
|
|
1443
1461
|
}
|
|
1444
1462
|
var RateLimiter = class {
|
|
@@ -4264,6 +4282,223 @@ async function downloadSkillZip(client, name, targetDir, format = "zip") {
|
|
|
4264
4282
|
const filePath = safeWriteFile(targetDir, fileName, result.data);
|
|
4265
4283
|
return filePath;
|
|
4266
4284
|
}
|
|
4285
|
+
function listFilesRecursive(dir, base = "") {
|
|
4286
|
+
const results = [];
|
|
4287
|
+
for (const entry of readdirSync(dir)) {
|
|
4288
|
+
const fullPath = join5(dir, entry);
|
|
4289
|
+
const relPath = base ? `${base}/${entry}` : entry;
|
|
4290
|
+
if (statSync(fullPath).isDirectory()) {
|
|
4291
|
+
results.push(...listFilesRecursive(fullPath, relPath));
|
|
4292
|
+
} else {
|
|
4293
|
+
results.push(relPath);
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
return results;
|
|
4297
|
+
}
|
|
4298
|
+
function computeFileHashes(contentDir) {
|
|
4299
|
+
const hashes = {};
|
|
4300
|
+
for (const relPath of listFilesRecursive(contentDir)) {
|
|
4301
|
+
if (relPath === "_meta.json") continue;
|
|
4302
|
+
const bytes = readFileSync2(join5(contentDir, relPath));
|
|
4303
|
+
hashes[relPath] = "sha256:" + createHash("sha256").update(bytes).digest("hex");
|
|
4304
|
+
}
|
|
4305
|
+
return hashes;
|
|
4306
|
+
}
|
|
4307
|
+
async function getPublicKey(client, keyId) {
|
|
4308
|
+
try {
|
|
4309
|
+
const query = {};
|
|
4310
|
+
if (keyId !== void 0) query.keyId = keyId;
|
|
4311
|
+
const result = await client.privateGet(
|
|
4312
|
+
"/api/v5/skill/signing-key",
|
|
4313
|
+
query
|
|
4314
|
+
);
|
|
4315
|
+
if (!result.data || result.data.status !== "active") return null;
|
|
4316
|
+
return result.data.publicKey;
|
|
4317
|
+
} catch (e) {
|
|
4318
|
+
if (e instanceof ConfigError) throw e;
|
|
4319
|
+
return null;
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
var ED25519_SPKI_HEADER = Buffer.from("302a300506032b6570032100", "hex");
|
|
4323
|
+
function parseSSHPublicKey(base64) {
|
|
4324
|
+
try {
|
|
4325
|
+
const buf = Buffer.from(base64, "base64");
|
|
4326
|
+
let offset = 0;
|
|
4327
|
+
if (buf.length < 4) return null;
|
|
4328
|
+
const algLen = buf.readUInt32BE(offset);
|
|
4329
|
+
offset += 4;
|
|
4330
|
+
if (offset + algLen + 4 > buf.length) return null;
|
|
4331
|
+
if (buf.subarray(offset, offset + algLen).toString("ascii") !== "ssh-ed25519") return null;
|
|
4332
|
+
offset += algLen;
|
|
4333
|
+
const keyLen = buf.readUInt32BE(offset);
|
|
4334
|
+
offset += 4;
|
|
4335
|
+
if (keyLen !== 32 || offset + keyLen > buf.length) return null;
|
|
4336
|
+
return buf.subarray(offset, offset + keyLen);
|
|
4337
|
+
} catch {
|
|
4338
|
+
return null;
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
async function verifySkillSignature(contentDir, signing, opts) {
|
|
4342
|
+
if (!signing) {
|
|
4343
|
+
const fallback = await tryServerFallback(contentDir, opts);
|
|
4344
|
+
if (fallback?.status === "verified_by_server") return fallback;
|
|
4345
|
+
return {
|
|
4346
|
+
status: "failed",
|
|
4347
|
+
error: fallback?.error ?? "Skill is not signed",
|
|
4348
|
+
...fallback?.mismatched?.length && { mismatched: fallback.mismatched }
|
|
4349
|
+
};
|
|
4350
|
+
}
|
|
4351
|
+
const publicKeyBase64 = opts?.fetchPublicKey ? await opts.fetchPublicKey(signing.public_key_id) ?? "" : "";
|
|
4352
|
+
if (!publicKeyBase64) {
|
|
4353
|
+
return localFail(
|
|
4354
|
+
contentDir,
|
|
4355
|
+
opts,
|
|
4356
|
+
`Unknown signing key: ${signing.public_key_id}. Please update CLI.`,
|
|
4357
|
+
"Unknown signing key \u2014 consider updating CLI"
|
|
4358
|
+
);
|
|
4359
|
+
}
|
|
4360
|
+
const rawKey = parseSSHPublicKey(publicKeyBase64);
|
|
4361
|
+
if (!rawKey) {
|
|
4362
|
+
return localFail(
|
|
4363
|
+
contentDir,
|
|
4364
|
+
opts,
|
|
4365
|
+
`Malformed public key for key ID: ${signing.public_key_id}`,
|
|
4366
|
+
"Malformed public key from server \u2014 consider updating CLI",
|
|
4367
|
+
signing.public_key_id
|
|
4368
|
+
);
|
|
4369
|
+
}
|
|
4370
|
+
if (!ed25519Verify(signing, rawKey)) {
|
|
4371
|
+
return localFail(
|
|
4372
|
+
contentDir,
|
|
4373
|
+
opts,
|
|
4374
|
+
"Invalid signature",
|
|
4375
|
+
"Signature mismatch \u2014 key may be outdated or file was modified",
|
|
4376
|
+
signing.public_key_id
|
|
4377
|
+
);
|
|
4378
|
+
}
|
|
4379
|
+
const bindingError = checkNameVersionBinding(signing, opts);
|
|
4380
|
+
if (bindingError) {
|
|
4381
|
+
return { status: "failed", error: bindingError, publicKeyId: signing.public_key_id };
|
|
4382
|
+
}
|
|
4383
|
+
const integrityFailure = await checkFileIntegrity(signing, contentDir, opts);
|
|
4384
|
+
if (integrityFailure) return integrityFailure;
|
|
4385
|
+
const signedFiles = new Set(Object.keys(signing.files));
|
|
4386
|
+
const extraFiles = listFilesRecursive(contentDir).filter(
|
|
4387
|
+
(f) => f !== "_meta.json" && !signedFiles.has(f)
|
|
4388
|
+
);
|
|
4389
|
+
return { status: "verified", publicKeyId: signing.public_key_id, filesChecked: signedFiles.size, extraFiles };
|
|
4390
|
+
}
|
|
4391
|
+
async function localFail(contentDir, opts, localError, serverHint, keyId) {
|
|
4392
|
+
const fallback = await tryServerFallback(contentDir, opts);
|
|
4393
|
+
if (fallback?.status === "verified_by_server") {
|
|
4394
|
+
return { ...fallback, error: serverHint };
|
|
4395
|
+
}
|
|
4396
|
+
return {
|
|
4397
|
+
status: "failed",
|
|
4398
|
+
error: localError,
|
|
4399
|
+
...keyId && { publicKeyId: keyId },
|
|
4400
|
+
...fallback?.mismatched?.length && { mismatched: fallback.mismatched }
|
|
4401
|
+
};
|
|
4402
|
+
}
|
|
4403
|
+
function ed25519Verify(signing, rawKey) {
|
|
4404
|
+
const payloadObj = { files: signing.files, public_key_id: signing.public_key_id };
|
|
4405
|
+
if (signing.name !== void 0) payloadObj.name = signing.name;
|
|
4406
|
+
if (signing.version !== void 0) payloadObj.version = signing.version;
|
|
4407
|
+
const message = Buffer.from(canonicalize(payloadObj), "utf-8");
|
|
4408
|
+
const sig = Buffer.from(signing.signature, "base64");
|
|
4409
|
+
const spkiKey = Buffer.concat([ED25519_SPKI_HEADER, rawKey]);
|
|
4410
|
+
const keyObj = createPublicKey({ key: spkiKey, format: "der", type: "spki" });
|
|
4411
|
+
return cryptoVerify(null, message, keyObj, sig);
|
|
4412
|
+
}
|
|
4413
|
+
function checkNameVersionBinding(signing, opts) {
|
|
4414
|
+
if (signing.name !== void 0 && opts?.skillName !== void 0 && signing.name !== opts.skillName) {
|
|
4415
|
+
return `Skill name mismatch: signature binds "${signing.name}" but installing as "${opts.skillName}"`;
|
|
4416
|
+
}
|
|
4417
|
+
if (signing.version !== void 0 && opts?.skillVersion !== void 0 && signing.version !== opts.skillVersion) {
|
|
4418
|
+
return `Skill version mismatch: signature binds "${signing.version}" but package declares "${opts.skillVersion}"`;
|
|
4419
|
+
}
|
|
4420
|
+
return null;
|
|
4421
|
+
}
|
|
4422
|
+
async function checkFileIntegrity(signing, contentDir, opts) {
|
|
4423
|
+
const resolvedContentDir = resolve2(contentDir);
|
|
4424
|
+
for (const [filename, expectedHash] of Object.entries(signing.files)) {
|
|
4425
|
+
const resolvedPath = resolve2(join6(contentDir, filename));
|
|
4426
|
+
if (!resolvedPath.startsWith(resolvedContentDir + sep2)) {
|
|
4427
|
+
return { status: "failed", error: `Path traversal detected in signing manifest: ${filename}`, publicKeyId: signing.public_key_id };
|
|
4428
|
+
}
|
|
4429
|
+
let bytes;
|
|
4430
|
+
try {
|
|
4431
|
+
bytes = readFileSync3(resolvedPath);
|
|
4432
|
+
} catch {
|
|
4433
|
+
return { status: "failed", error: `File missing: ${filename}`, publicKeyId: signing.public_key_id };
|
|
4434
|
+
}
|
|
4435
|
+
const actual = "sha256:" + createHash2("sha256").update(bytes).digest("hex");
|
|
4436
|
+
if (actual !== expectedHash) {
|
|
4437
|
+
return localFail(
|
|
4438
|
+
contentDir,
|
|
4439
|
+
opts,
|
|
4440
|
+
`File integrity check failed: ${filename}`,
|
|
4441
|
+
"_meta.json may be corrupted",
|
|
4442
|
+
signing.public_key_id
|
|
4443
|
+
);
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
return null;
|
|
4447
|
+
}
|
|
4448
|
+
async function tryServerFallback(contentDir, opts) {
|
|
4449
|
+
if (!opts?.serverSideVerify || !opts.skillName) return null;
|
|
4450
|
+
const fileHashes = computeFileHashes(contentDir);
|
|
4451
|
+
const result = await opts.serverSideVerify(
|
|
4452
|
+
opts.skillName,
|
|
4453
|
+
opts.skillVersion,
|
|
4454
|
+
fileHashes
|
|
4455
|
+
);
|
|
4456
|
+
if (!result) return null;
|
|
4457
|
+
if (result.verified) {
|
|
4458
|
+
return {
|
|
4459
|
+
status: "verified_by_server",
|
|
4460
|
+
filesChecked: Object.keys(fileHashes).length,
|
|
4461
|
+
serverVersion: result.version
|
|
4462
|
+
};
|
|
4463
|
+
}
|
|
4464
|
+
return {
|
|
4465
|
+
status: "failed",
|
|
4466
|
+
filesChecked: Object.keys(fileHashes).length,
|
|
4467
|
+
mismatched: result.mismatched,
|
|
4468
|
+
...result.message && { error: result.message }
|
|
4469
|
+
};
|
|
4470
|
+
}
|
|
4471
|
+
function canonicalize(obj) {
|
|
4472
|
+
return JSON.stringify(deepSortKeys(obj));
|
|
4473
|
+
}
|
|
4474
|
+
function compareKeys(a, b) {
|
|
4475
|
+
if (a < b) return -1;
|
|
4476
|
+
if (a > b) return 1;
|
|
4477
|
+
return 0;
|
|
4478
|
+
}
|
|
4479
|
+
function deepSortKeys(val) {
|
|
4480
|
+
if (val === null || typeof val !== "object") return val;
|
|
4481
|
+
if (Array.isArray(val)) return val.map(deepSortKeys);
|
|
4482
|
+
const sorted = {};
|
|
4483
|
+
for (const key of Object.keys(val).sort(compareKeys)) {
|
|
4484
|
+
sorted[key] = deepSortKeys(val[key]);
|
|
4485
|
+
}
|
|
4486
|
+
return sorted;
|
|
4487
|
+
}
|
|
4488
|
+
async function serverSideVerify(client, skillName, version, files) {
|
|
4489
|
+
try {
|
|
4490
|
+
const body = { skillName, files };
|
|
4491
|
+
if (version !== void 0) body.version = version;
|
|
4492
|
+
const result = await client.privatePost(
|
|
4493
|
+
"/api/v5/skill/verify",
|
|
4494
|
+
body
|
|
4495
|
+
);
|
|
4496
|
+
return result.data;
|
|
4497
|
+
} catch (e) {
|
|
4498
|
+
if (e instanceof ConfigError) throw e;
|
|
4499
|
+
return null;
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4267
4502
|
var DEFAULT_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
|
|
4268
4503
|
var DEFAULT_MAX_FILES = 1e3;
|
|
4269
4504
|
var DEFAULT_MAX_COMPRESSION_RATIO = 100;
|
|
@@ -4302,7 +4537,7 @@ async function extractSkillZip(zipPath, targetDir, limits) {
|
|
|
4302
4537
|
const maxTotalBytes = limits?.maxTotalBytes ?? DEFAULT_MAX_TOTAL_BYTES;
|
|
4303
4538
|
const maxFiles = limits?.maxFiles ?? DEFAULT_MAX_FILES;
|
|
4304
4539
|
const maxCompressionRatio = limits?.maxCompressionRatio ?? DEFAULT_MAX_COMPRESSION_RATIO;
|
|
4305
|
-
const resolvedTarget =
|
|
4540
|
+
const resolvedTarget = resolve3(targetDir);
|
|
4306
4541
|
mkdirSync3(resolvedTarget, { recursive: true });
|
|
4307
4542
|
return new Promise((resolvePromise, reject) => {
|
|
4308
4543
|
yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
|
|
@@ -4342,11 +4577,11 @@ async function extractSkillZip(zipPath, targetDir, limits) {
|
|
|
4342
4577
|
});
|
|
4343
4578
|
}
|
|
4344
4579
|
function readMetaJson(contentDir) {
|
|
4345
|
-
const metaPath =
|
|
4580
|
+
const metaPath = join7(contentDir, "_meta.json");
|
|
4346
4581
|
if (!existsSync(metaPath)) {
|
|
4347
4582
|
throw new Error(`_meta.json not found in ${contentDir}. Invalid skill package.`);
|
|
4348
4583
|
}
|
|
4349
|
-
const raw =
|
|
4584
|
+
const raw = readFileSync4(metaPath, "utf-8");
|
|
4350
4585
|
let parsed;
|
|
4351
4586
|
try {
|
|
4352
4587
|
parsed = JSON.parse(raw);
|
|
@@ -4364,22 +4599,49 @@ function readMetaJson(contentDir) {
|
|
|
4364
4599
|
name: String(meta.name),
|
|
4365
4600
|
version: String(meta.version),
|
|
4366
4601
|
title: typeof meta.title === "string" ? meta.title : "",
|
|
4367
|
-
description: typeof meta.description === "string" ? meta.description : ""
|
|
4602
|
+
description: typeof meta.description === "string" ? meta.description : "",
|
|
4603
|
+
signing: parseSigningBlock(meta.signing)
|
|
4604
|
+
};
|
|
4605
|
+
}
|
|
4606
|
+
function tryReadMetaJson(contentDir) {
|
|
4607
|
+
try {
|
|
4608
|
+
return readMetaJson(contentDir);
|
|
4609
|
+
} catch {
|
|
4610
|
+
return null;
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
function parseSigningBlock(raw) {
|
|
4614
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
4615
|
+
const s = raw;
|
|
4616
|
+
if (typeof s.signature !== "string" || typeof s.public_key_id !== "string" || !s.files) {
|
|
4617
|
+
return void 0;
|
|
4618
|
+
}
|
|
4619
|
+
const rawFiles = s.files;
|
|
4620
|
+
const files = {};
|
|
4621
|
+
for (const [key, value] of Object.entries(rawFiles)) {
|
|
4622
|
+
if (typeof value === "string") files[key] = value;
|
|
4623
|
+
}
|
|
4624
|
+
return {
|
|
4625
|
+
signature: s.signature,
|
|
4626
|
+
public_key_id: s.public_key_id,
|
|
4627
|
+
files,
|
|
4628
|
+
...typeof s.name === "string" && { name: s.name },
|
|
4629
|
+
...typeof s.version === "string" && { version: s.version }
|
|
4368
4630
|
};
|
|
4369
4631
|
}
|
|
4370
4632
|
function validateSkillMdExists(contentDir) {
|
|
4371
|
-
const skillMdPath =
|
|
4633
|
+
const skillMdPath = join7(contentDir, "SKILL.md");
|
|
4372
4634
|
if (!existsSync(skillMdPath)) {
|
|
4373
4635
|
throw new Error(`SKILL.md not found in ${contentDir}. Invalid skill package.`);
|
|
4374
4636
|
}
|
|
4375
4637
|
}
|
|
4376
|
-
var DEFAULT_REGISTRY_PATH =
|
|
4638
|
+
var DEFAULT_REGISTRY_PATH = join8(homedir4(), ".okx", "skills", "registry.json");
|
|
4377
4639
|
function readRegistry(registryPath = DEFAULT_REGISTRY_PATH) {
|
|
4378
4640
|
if (!existsSync2(registryPath)) {
|
|
4379
4641
|
return { version: 1, skills: {} };
|
|
4380
4642
|
}
|
|
4381
4643
|
try {
|
|
4382
|
-
const raw =
|
|
4644
|
+
const raw = readFileSync5(registryPath, "utf-8");
|
|
4383
4645
|
return JSON.parse(raw);
|
|
4384
4646
|
} catch {
|
|
4385
4647
|
return { version: 1, skills: {} };
|
|
@@ -4389,7 +4651,7 @@ function writeRegistry(registry, registryPath = DEFAULT_REGISTRY_PATH) {
|
|
|
4389
4651
|
mkdirSync4(dirname3(registryPath), { recursive: true });
|
|
4390
4652
|
writeFileSync3(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
|
|
4391
4653
|
}
|
|
4392
|
-
function upsertSkillRecord(meta, registryPath = DEFAULT_REGISTRY_PATH) {
|
|
4654
|
+
function upsertSkillRecord(meta, registryPath = DEFAULT_REGISTRY_PATH, verification) {
|
|
4393
4655
|
const registry = readRegistry(registryPath);
|
|
4394
4656
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4395
4657
|
const existing = registry.skills[meta.name];
|
|
@@ -4399,7 +4661,8 @@ function upsertSkillRecord(meta, registryPath = DEFAULT_REGISTRY_PATH) {
|
|
|
4399
4661
|
description: meta.description,
|
|
4400
4662
|
installedAt: existing?.installedAt ?? now,
|
|
4401
4663
|
updatedAt: now,
|
|
4402
|
-
source: "marketplace"
|
|
4664
|
+
source: "marketplace",
|
|
4665
|
+
...verification !== void 0 && { verification }
|
|
4403
4666
|
};
|
|
4404
4667
|
writeRegistry(registry, registryPath);
|
|
4405
4668
|
}
|
|
@@ -12195,12 +12458,12 @@ function createToolRunner(client, config) {
|
|
|
12195
12458
|
};
|
|
12196
12459
|
}
|
|
12197
12460
|
function configFilePath() {
|
|
12198
|
-
return
|
|
12461
|
+
return join9(homedir5(), ".okx", "config.toml");
|
|
12199
12462
|
}
|
|
12200
12463
|
function readFullConfig() {
|
|
12201
12464
|
const path42 = configFilePath();
|
|
12202
12465
|
if (!existsSync3(path42)) return { profiles: {} };
|
|
12203
|
-
const raw =
|
|
12466
|
+
const raw = readFileSync6(path42, "utf-8");
|
|
12204
12467
|
try {
|
|
12205
12468
|
return parse(raw);
|
|
12206
12469
|
} catch (err) {
|
|
@@ -12350,7 +12613,7 @@ async function loadConfig(cli) {
|
|
|
12350
12613
|
verbose: cli.verbose ?? false
|
|
12351
12614
|
};
|
|
12352
12615
|
}
|
|
12353
|
-
var CACHE_FILE =
|
|
12616
|
+
var CACHE_FILE = join10(homedir6(), ".okx", "update-check.json");
|
|
12354
12617
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
12355
12618
|
var NEGATIVE_CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
12356
12619
|
var DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
@@ -12358,7 +12621,7 @@ var FETCH_TIMEOUT_MS = 3e3;
|
|
|
12358
12621
|
function readCache2() {
|
|
12359
12622
|
try {
|
|
12360
12623
|
if (existsSync4(CACHE_FILE)) {
|
|
12361
|
-
return JSON.parse(
|
|
12624
|
+
return JSON.parse(readFileSync7(CACHE_FILE, "utf-8"));
|
|
12362
12625
|
}
|
|
12363
12626
|
} catch {
|
|
12364
12627
|
}
|
|
@@ -12366,7 +12629,7 @@ function readCache2() {
|
|
|
12366
12629
|
}
|
|
12367
12630
|
function writeCache2(cache) {
|
|
12368
12631
|
try {
|
|
12369
|
-
mkdirSync6(
|
|
12632
|
+
mkdirSync6(join10(homedir6(), ".okx"), { recursive: true });
|
|
12370
12633
|
writeFileSync5(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
12371
12634
|
} catch {
|
|
12372
12635
|
}
|
|
@@ -12396,13 +12659,13 @@ function buildNpmrcCandidates() {
|
|
|
12396
12659
|
let dir = process.cwd();
|
|
12397
12660
|
const root = dir.startsWith("/") ? "/" : dir.slice(0, 3);
|
|
12398
12661
|
while (true) {
|
|
12399
|
-
add(
|
|
12662
|
+
add(join10(dir, ".npmrc"));
|
|
12400
12663
|
if (dir === root) break;
|
|
12401
|
-
const parent =
|
|
12664
|
+
const parent = join10(dir, "..");
|
|
12402
12665
|
if (parent === dir) break;
|
|
12403
12666
|
dir = parent;
|
|
12404
12667
|
}
|
|
12405
|
-
add(
|
|
12668
|
+
add(join10(homedir6(), ".npmrc"));
|
|
12406
12669
|
if (process.platform !== "win32") {
|
|
12407
12670
|
add("/etc/npmrc");
|
|
12408
12671
|
}
|
|
@@ -12411,7 +12674,7 @@ function buildNpmrcCandidates() {
|
|
|
12411
12674
|
function readNpmrcRegistry(filePath) {
|
|
12412
12675
|
try {
|
|
12413
12676
|
if (!existsSync4(filePath)) return null;
|
|
12414
|
-
const lines =
|
|
12677
|
+
const lines = readFileSync7(filePath, "utf-8").split(/\r?\n/);
|
|
12415
12678
|
for (const line of lines) {
|
|
12416
12679
|
const trimmed = line.trim();
|
|
12417
12680
|
if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
@@ -12738,6 +13001,14 @@ function runSetup(options) {
|
|
|
12738
13001
|
`);
|
|
12739
13002
|
}
|
|
12740
13003
|
}
|
|
13004
|
+
var HttpStatusError = class extends Error {
|
|
13005
|
+
statusCode;
|
|
13006
|
+
constructor(statusCode) {
|
|
13007
|
+
super(`HTTP ${statusCode}`);
|
|
13008
|
+
this.name = "HttpStatusError";
|
|
13009
|
+
this.statusCode = statusCode;
|
|
13010
|
+
}
|
|
13011
|
+
};
|
|
12741
13012
|
function isRedirect(statusCode) {
|
|
12742
13013
|
return statusCode !== void 0 && statusCode >= 300 && statusCode < 400;
|
|
12743
13014
|
}
|
|
@@ -12752,7 +13023,7 @@ function validateRedirect(res, requestUrl, redirectCount, maxRedirects) {
|
|
|
12752
13023
|
return location;
|
|
12753
13024
|
}
|
|
12754
13025
|
function fetchResponse(url, timeoutMs) {
|
|
12755
|
-
return new Promise((
|
|
13026
|
+
return new Promise((resolve4, reject) => {
|
|
12756
13027
|
let redirects = 0;
|
|
12757
13028
|
const maxRedirects = 5;
|
|
12758
13029
|
function doRequest(requestUrl) {
|
|
@@ -12770,10 +13041,14 @@ function fetchResponse(url, timeoutMs) {
|
|
|
12770
13041
|
return;
|
|
12771
13042
|
}
|
|
12772
13043
|
if (res.statusCode !== 200) {
|
|
12773
|
-
|
|
13044
|
+
if (res.statusCode === void 0) {
|
|
13045
|
+
reject(new Error("HTTP unknown"));
|
|
13046
|
+
} else {
|
|
13047
|
+
reject(new HttpStatusError(res.statusCode));
|
|
13048
|
+
}
|
|
12774
13049
|
return;
|
|
12775
13050
|
}
|
|
12776
|
-
|
|
13051
|
+
resolve4(res);
|
|
12777
13052
|
});
|
|
12778
13053
|
req.on("error", reject);
|
|
12779
13054
|
req.on("timeout", () => {
|
|
@@ -12786,10 +13061,10 @@ function fetchResponse(url, timeoutMs) {
|
|
|
12786
13061
|
}
|
|
12787
13062
|
function download(url, destPath, timeoutMs) {
|
|
12788
13063
|
return fetchResponse(url, timeoutMs).then(
|
|
12789
|
-
(res) => new Promise((
|
|
13064
|
+
(res) => new Promise((resolve4, reject) => {
|
|
12790
13065
|
const file = createWriteStream2(destPath);
|
|
12791
13066
|
res.pipe(file);
|
|
12792
|
-
file.on("finish", () => file.close(() =>
|
|
13067
|
+
file.on("finish", () => file.close(() => resolve4()));
|
|
12793
13068
|
file.on("error", (err) => {
|
|
12794
13069
|
try {
|
|
12795
13070
|
unlinkSync3(destPath);
|
|
@@ -12802,10 +13077,10 @@ function download(url, destPath, timeoutMs) {
|
|
|
12802
13077
|
}
|
|
12803
13078
|
function downloadText(url, timeoutMs) {
|
|
12804
13079
|
return fetchResponse(url, timeoutMs).then(
|
|
12805
|
-
(res) => new Promise((
|
|
13080
|
+
(res) => new Promise((resolve4, reject) => {
|
|
12806
13081
|
const chunks = [];
|
|
12807
13082
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
12808
|
-
res.on("end", () =>
|
|
13083
|
+
res.on("end", () => resolve4(Buffer.concat(chunks).toString("utf8")));
|
|
12809
13084
|
res.on("error", reject);
|
|
12810
13085
|
})
|
|
12811
13086
|
);
|
|
@@ -12834,10 +13109,10 @@ function getBinaryName() {
|
|
|
12834
13109
|
return platform() === "win32" ? "okx-pilot.exe" : "okx-pilot";
|
|
12835
13110
|
}
|
|
12836
13111
|
function hashFile(filePath) {
|
|
12837
|
-
const buf =
|
|
13112
|
+
const buf = readFileSync9(filePath);
|
|
12838
13113
|
return {
|
|
12839
13114
|
size: buf.byteLength,
|
|
12840
|
-
sha256:
|
|
13115
|
+
sha256: createHash3("sha256").update(buf).digest("hex")
|
|
12841
13116
|
};
|
|
12842
13117
|
}
|
|
12843
13118
|
function getPilotStatus(binaryPath, opts) {
|
|
@@ -12952,7 +13227,7 @@ async function installPilotBinary(destPath, sources = CDN_SOURCES, onProgress) {
|
|
|
12952
13227
|
if (earlyResult) return earlyResult;
|
|
12953
13228
|
const platformDir = getPlatformDir();
|
|
12954
13229
|
const binaryName = getBinaryName();
|
|
12955
|
-
const resolvedDest = destPath ??
|
|
13230
|
+
const resolvedDest = destPath ?? join12(homedir8(), ".okx", "bin", binaryName);
|
|
12956
13231
|
const tmpPath = resolvedDest + ".tmp";
|
|
12957
13232
|
mkdirSync8(dirname6(resolvedDest), { recursive: true });
|
|
12958
13233
|
const localHash = existsSync6(resolvedDest) ? hashFile(resolvedDest) : null;
|
|
@@ -13004,9 +13279,41 @@ function removePilotBinary(binaryPath) {
|
|
|
13004
13279
|
}
|
|
13005
13280
|
}
|
|
13006
13281
|
var AUTH_CDN_PATH_PREFIX = "/upgradeapp/tools/oauth";
|
|
13282
|
+
var LINUX_ARM64_FALLBACK_DIR = "linux-x64";
|
|
13007
13283
|
function getAuthBinaryName() {
|
|
13008
13284
|
return platform2() === "win32" ? "okx-auth.exe" : "okx-auth";
|
|
13009
13285
|
}
|
|
13286
|
+
async function _resolveAuthPlatformFromNative(native, sources, timeoutMs) {
|
|
13287
|
+
if (native !== "linux-arm64") {
|
|
13288
|
+
return native;
|
|
13289
|
+
}
|
|
13290
|
+
const checksumPath = `${AUTH_CDN_PATH_PREFIX}/linux-arm64/checksum.json`;
|
|
13291
|
+
let got404 = false;
|
|
13292
|
+
for (const { host, protocol } of sources) {
|
|
13293
|
+
const url = `${protocol}://${host}${checksumPath}`;
|
|
13294
|
+
try {
|
|
13295
|
+
await downloadText(url, timeoutMs);
|
|
13296
|
+
return "linux-arm64";
|
|
13297
|
+
} catch (err) {
|
|
13298
|
+
if (err instanceof HttpStatusError && err.statusCode === 404) {
|
|
13299
|
+
got404 = true;
|
|
13300
|
+
break;
|
|
13301
|
+
}
|
|
13302
|
+
}
|
|
13303
|
+
}
|
|
13304
|
+
if (got404) {
|
|
13305
|
+
console.warn(
|
|
13306
|
+
"[okx-auth] Native linux-arm64 binary not yet on CDN. Falling back to linux-x64 binary \u2014 x86_64 emulation (binfmt_misc/Rosetta) required."
|
|
13307
|
+
);
|
|
13308
|
+
return LINUX_ARM64_FALLBACK_DIR;
|
|
13309
|
+
}
|
|
13310
|
+
return "linux-arm64";
|
|
13311
|
+
}
|
|
13312
|
+
async function resolveAuthPlatformDir(sources = CDN_SOURCES, timeoutMs = DOWNLOAD_TIMEOUT_MS) {
|
|
13313
|
+
const native = getPlatformDir();
|
|
13314
|
+
if (!native) return null;
|
|
13315
|
+
return _resolveAuthPlatformFromNative(native, sources, timeoutMs);
|
|
13316
|
+
}
|
|
13010
13317
|
function getAuthStatus(binaryPath, opts) {
|
|
13011
13318
|
const resolvedPath = binaryPath ?? getAuthBinaryPath();
|
|
13012
13319
|
const platformDir = getPlatformDir();
|
|
@@ -13020,7 +13327,7 @@ function getAuthStatus(binaryPath, opts) {
|
|
|
13020
13327
|
return { binaryPath: resolvedPath, exists: true, platform: platformDir, fileSize: size, sha256 };
|
|
13021
13328
|
}
|
|
13022
13329
|
async function fetchAuthCdnChecksum(sources = CDN_SOURCES, timeoutMs = DOWNLOAD_TIMEOUT_MS) {
|
|
13023
|
-
const platformDir =
|
|
13330
|
+
const platformDir = await resolveAuthPlatformDir(sources, timeoutMs);
|
|
13024
13331
|
if (!platformDir) return null;
|
|
13025
13332
|
const checksumPath = `${AUTH_CDN_PATH_PREFIX}/${platformDir}/checksum.json`;
|
|
13026
13333
|
for (const { host, protocol } of sources) {
|
|
@@ -13089,12 +13396,51 @@ function installPreChecks2(destPath, sources) {
|
|
|
13089
13396
|
function isLocalUpToDate2(localHash, checksum) {
|
|
13090
13397
|
return localHash !== null && localHash.size === checksum.size && localHash.sha256 === checksum.sha256;
|
|
13091
13398
|
}
|
|
13399
|
+
async function tryInstallFromOneSource(ctx) {
|
|
13400
|
+
const { host, protocol, platformDir, checksumPath, binaryPath, tmpPath, resolvedDest, localHash, onProgress } = ctx;
|
|
13401
|
+
try {
|
|
13402
|
+
const checksum = await fetchAndValidateChecksum2(
|
|
13403
|
+
host,
|
|
13404
|
+
protocol,
|
|
13405
|
+
checksumPath,
|
|
13406
|
+
platformDir,
|
|
13407
|
+
DOWNLOAD_TIMEOUT_MS,
|
|
13408
|
+
onProgress
|
|
13409
|
+
);
|
|
13410
|
+
if (isLocalUpToDate2(localHash, checksum)) {
|
|
13411
|
+
onProgress?.("Already up to date (checksum match)");
|
|
13412
|
+
return { kind: "done", result: { status: "up-to-date", source: host } };
|
|
13413
|
+
}
|
|
13414
|
+
await downloadAndVerify2(host, protocol, binaryPath, tmpPath, checksum, DOWNLOAD_TIMEOUT_MS, onProgress);
|
|
13415
|
+
atomicReplace2(tmpPath, resolvedDest);
|
|
13416
|
+
onProgress?.(`Downloaded and verified from ${host}`);
|
|
13417
|
+
return { kind: "done", result: { status: "installed", source: host } };
|
|
13418
|
+
} catch (err) {
|
|
13419
|
+
try {
|
|
13420
|
+
unlinkSync5(tmpPath);
|
|
13421
|
+
} catch {
|
|
13422
|
+
}
|
|
13423
|
+
const errObj = err instanceof Error ? err : new Error(String(err));
|
|
13424
|
+
onProgress?.(`${host} failed: ${errObj.message}`);
|
|
13425
|
+
return { kind: "next", err: errObj };
|
|
13426
|
+
}
|
|
13427
|
+
}
|
|
13428
|
+
function buildInstallFailureResult(errors) {
|
|
13429
|
+
const formatted = errors.map(({ host, err }) => `${host}: ${err.message}`).join("\n");
|
|
13430
|
+
const allHttpErrors = errors.length > 0 && errors.every(({ err }) => err instanceof HttpStatusError);
|
|
13431
|
+
const prefix = allHttpErrors ? "okx-auth binary not available on CDN for this platform" : "All CDN sources failed";
|
|
13432
|
+
return { status: "failed", error: `${prefix}:
|
|
13433
|
+
${formatted}` };
|
|
13434
|
+
}
|
|
13092
13435
|
async function installAuthBinary(destPath, sources = CDN_SOURCES, onProgress) {
|
|
13093
13436
|
const earlyResult = installPreChecks2(destPath, sources);
|
|
13094
13437
|
if (earlyResult) return earlyResult;
|
|
13095
|
-
const platformDir =
|
|
13438
|
+
const platformDir = await resolveAuthPlatformDir(sources);
|
|
13439
|
+
if (!platformDir) {
|
|
13440
|
+
return { status: "failed", error: "Unsupported platform" };
|
|
13441
|
+
}
|
|
13096
13442
|
const binaryName = getAuthBinaryName();
|
|
13097
|
-
const resolvedDest = destPath ??
|
|
13443
|
+
const resolvedDest = destPath ?? join13(homedir9(), ".okx", "bin", binaryName);
|
|
13098
13444
|
const tmpPath = resolvedDest + ".tmp";
|
|
13099
13445
|
mkdirSync9(dirname7(resolvedDest), { recursive: true });
|
|
13100
13446
|
const localHash = existsSync7(resolvedDest) ? hashFile(resolvedDest) : null;
|
|
@@ -13102,35 +13448,21 @@ async function installAuthBinary(destPath, sources = CDN_SOURCES, onProgress) {
|
|
|
13102
13448
|
const binaryPath = `${AUTH_CDN_PATH_PREFIX}/${platformDir}/${binaryName}`;
|
|
13103
13449
|
const errors = [];
|
|
13104
13450
|
for (const { host, protocol } of sources) {
|
|
13105
|
-
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
|
|
13109
|
-
|
|
13110
|
-
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
await downloadAndVerify2(host, protocol, binaryPath, tmpPath, checksum, DOWNLOAD_TIMEOUT_MS, onProgress);
|
|
13119
|
-
atomicReplace2(tmpPath, resolvedDest);
|
|
13120
|
-
onProgress?.(`Downloaded and verified from ${host}`);
|
|
13121
|
-
return { status: "installed", source: host };
|
|
13122
|
-
} catch (err) {
|
|
13123
|
-
try {
|
|
13124
|
-
unlinkSync5(tmpPath);
|
|
13125
|
-
} catch {
|
|
13126
|
-
}
|
|
13127
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
13128
|
-
errors.push(`${host}: ${msg}`);
|
|
13129
|
-
onProgress?.(`${host} failed: ${msg}`);
|
|
13130
|
-
}
|
|
13451
|
+
const attempt = await tryInstallFromOneSource({
|
|
13452
|
+
host,
|
|
13453
|
+
protocol,
|
|
13454
|
+
platformDir,
|
|
13455
|
+
checksumPath,
|
|
13456
|
+
binaryPath,
|
|
13457
|
+
tmpPath,
|
|
13458
|
+
resolvedDest,
|
|
13459
|
+
localHash,
|
|
13460
|
+
onProgress
|
|
13461
|
+
});
|
|
13462
|
+
if (attempt.kind === "done") return attempt.result;
|
|
13463
|
+
errors.push({ host, err: attempt.err });
|
|
13131
13464
|
}
|
|
13132
|
-
return
|
|
13133
|
-
${errors.join("\n")}` };
|
|
13465
|
+
return buildInstallFailureResult(errors);
|
|
13134
13466
|
}
|
|
13135
13467
|
function removeAuthBinary(binaryPath) {
|
|
13136
13468
|
const resolvedPath = binaryPath ?? getAuthBinaryPath();
|
|
@@ -13145,12 +13477,12 @@ function removeAuthBinary(binaryPath) {
|
|
|
13145
13477
|
throw new Error(`Failed to remove ${resolvedPath}: ${msg}`);
|
|
13146
13478
|
}
|
|
13147
13479
|
}
|
|
13148
|
-
var CACHE_PATH =
|
|
13480
|
+
var CACHE_PATH = join14(homedir10(), ".okx", "auth-binary-check.json");
|
|
13149
13481
|
var CHECK_INTERVAL_MS2 = 2 * 60 * 60 * 1e3;
|
|
13150
13482
|
function readCache3() {
|
|
13151
13483
|
try {
|
|
13152
13484
|
if (!existsSync8(CACHE_PATH)) return null;
|
|
13153
|
-
const data = JSON.parse(
|
|
13485
|
+
const data = JSON.parse(readFileSync10(CACHE_PATH, "utf-8"));
|
|
13154
13486
|
if (typeof data.cdnSha256 !== "string" || typeof data.checkedAt !== "number") return null;
|
|
13155
13487
|
return { cdnSha256: data.cdnSha256, checkedAt: data.checkedAt };
|
|
13156
13488
|
} catch {
|
|
@@ -13159,7 +13491,7 @@ function readCache3() {
|
|
|
13159
13491
|
}
|
|
13160
13492
|
function writeCache3(cdnSha256) {
|
|
13161
13493
|
try {
|
|
13162
|
-
mkdirSync10(
|
|
13494
|
+
mkdirSync10(join14(homedir10(), ".okx"), { recursive: true });
|
|
13163
13495
|
writeFileSync7(CACHE_PATH, JSON.stringify({ cdnSha256, checkedAt: Date.now() }, null, 2), "utf-8");
|
|
13164
13496
|
} catch {
|
|
13165
13497
|
}
|
|
@@ -13294,7 +13626,7 @@ function markFailedIfSCodeError(data) {
|
|
|
13294
13626
|
// src/commands/auth.ts
|
|
13295
13627
|
function runOkxAuth(args) {
|
|
13296
13628
|
const binPath = getAuthBinaryPath();
|
|
13297
|
-
return new Promise((
|
|
13629
|
+
return new Promise((resolve4, reject) => {
|
|
13298
13630
|
const child = spawn3(binPath, args, {
|
|
13299
13631
|
stdio: "inherit"
|
|
13300
13632
|
});
|
|
@@ -13302,13 +13634,13 @@ function runOkxAuth(args) {
|
|
|
13302
13634
|
reject(new Error(`Failed to spawn okx-auth: ${err.message}`));
|
|
13303
13635
|
});
|
|
13304
13636
|
child.on("close", (code) => {
|
|
13305
|
-
|
|
13637
|
+
resolve4(code ?? 1);
|
|
13306
13638
|
});
|
|
13307
13639
|
});
|
|
13308
13640
|
}
|
|
13309
13641
|
function runOkxAuthCapture(args) {
|
|
13310
13642
|
const binPath = getAuthBinaryPath();
|
|
13311
|
-
return new Promise((
|
|
13643
|
+
return new Promise((resolve4, reject) => {
|
|
13312
13644
|
const child = spawn3(binPath, args, {
|
|
13313
13645
|
stdio: ["inherit", "pipe", "inherit"]
|
|
13314
13646
|
});
|
|
@@ -13318,7 +13650,7 @@ function runOkxAuthCapture(args) {
|
|
|
13318
13650
|
reject(new Error(`Failed to spawn okx-auth: ${err.message}`));
|
|
13319
13651
|
});
|
|
13320
13652
|
child.on("close", (code) => {
|
|
13321
|
-
|
|
13653
|
+
resolve4({
|
|
13322
13654
|
code: code ?? 1,
|
|
13323
13655
|
stdout: Buffer.concat(chunks).toString("utf-8")
|
|
13324
13656
|
});
|
|
@@ -13523,14 +13855,14 @@ async function confirmRemoval(force, binaryPath) {
|
|
|
13523
13855
|
return true;
|
|
13524
13856
|
}
|
|
13525
13857
|
function askConfirmation(prompt2) {
|
|
13526
|
-
return new Promise((
|
|
13858
|
+
return new Promise((resolve4) => {
|
|
13527
13859
|
const rl = readline.createInterface({
|
|
13528
13860
|
input: process.stdin,
|
|
13529
13861
|
output: process.stdout
|
|
13530
13862
|
});
|
|
13531
13863
|
rl.question(prompt2, (answer) => {
|
|
13532
13864
|
rl.close();
|
|
13533
|
-
|
|
13865
|
+
resolve4(answer.trim().toLowerCase() === "y");
|
|
13534
13866
|
});
|
|
13535
13867
|
});
|
|
13536
13868
|
}
|
|
@@ -13562,110 +13894,6 @@ async function handleAuthCommand(action, _rest, v) {
|
|
|
13562
13894
|
}
|
|
13563
13895
|
}
|
|
13564
13896
|
|
|
13565
|
-
// src/commands/outcomes.ts
|
|
13566
|
-
import { spawn as spawn4 } from "child_process";
|
|
13567
|
-
import { existsSync as existsSync9 } from "fs";
|
|
13568
|
-
import { delimiter, join as join13 } from "path";
|
|
13569
|
-
var OUTCOMES_BINARY_NAME = process.platform === "win32" ? "okx-outcomes.exe" : "okx-outcomes";
|
|
13570
|
-
function resolveOutcomesBinaryPath() {
|
|
13571
|
-
const override = process.env.OKX_OUTCOMES_BIN;
|
|
13572
|
-
if (override) {
|
|
13573
|
-
if (existsSync9(override)) return override;
|
|
13574
|
-
errorLine(
|
|
13575
|
-
`Warning: OKX_OUTCOMES_BIN is set to '${override}' but no file exists there; falling back to PATH search.`
|
|
13576
|
-
);
|
|
13577
|
-
}
|
|
13578
|
-
const paths = (process.env.PATH ?? "").split(delimiter);
|
|
13579
|
-
for (const dir of paths) {
|
|
13580
|
-
if (!dir) continue;
|
|
13581
|
-
const full = join13(dir, OUTCOMES_BINARY_NAME);
|
|
13582
|
-
if (existsSync9(full)) return full;
|
|
13583
|
-
}
|
|
13584
|
-
return null;
|
|
13585
|
-
}
|
|
13586
|
-
function printInstallHint() {
|
|
13587
|
-
errorLine("Error: okx-outcomes binary not found in PATH.");
|
|
13588
|
-
errorLine("");
|
|
13589
|
-
errorLine("Install (macOS / Linux):");
|
|
13590
|
-
errorLine(" curl -fsSL https://raw.githubusercontent.com/okx/outcomes/master/install.sh | sh");
|
|
13591
|
-
errorLine("");
|
|
13592
|
-
errorLine("Install (Windows): download okx-outcomes.exe from");
|
|
13593
|
-
errorLine(" https://github.com/okx/outcomes/releases");
|
|
13594
|
-
errorLine("and place it on your PATH.");
|
|
13595
|
-
errorLine("");
|
|
13596
|
-
errorLine("Or set OKX_OUTCOMES_BIN env var to a custom binary path.");
|
|
13597
|
-
}
|
|
13598
|
-
function runOkxOutcomes(args) {
|
|
13599
|
-
const binPath = resolveOutcomesBinaryPath();
|
|
13600
|
-
if (!binPath) {
|
|
13601
|
-
printInstallHint();
|
|
13602
|
-
return Promise.resolve(127);
|
|
13603
|
-
}
|
|
13604
|
-
return new Promise((resolve3, reject) => {
|
|
13605
|
-
const child = spawn4(binPath, args, { stdio: "inherit" });
|
|
13606
|
-
child.on(
|
|
13607
|
-
"error",
|
|
13608
|
-
(err) => reject(new Error(`Failed to spawn okx-outcomes: ${err.message}`))
|
|
13609
|
-
);
|
|
13610
|
-
child.on("close", (code) => resolve3(code ?? 1));
|
|
13611
|
-
});
|
|
13612
|
-
}
|
|
13613
|
-
function printOutcomesHelp() {
|
|
13614
|
-
const lines = [
|
|
13615
|
-
"Usage: okx outcomes <command> [args...]",
|
|
13616
|
-
"",
|
|
13617
|
-
"OKX Outcomes - YES/NO event contract trading via the okx-outcomes binary.",
|
|
13618
|
-
"",
|
|
13619
|
-
"Common commands:",
|
|
13620
|
-
" data events List outcome events",
|
|
13621
|
-
" data event <eventId> Get event detail",
|
|
13622
|
-
" data event-markets <eventId> Event + all its markets (returns asset ids)",
|
|
13623
|
-
" data market <marketId> Get single market detail",
|
|
13624
|
-
" data trending List trending events",
|
|
13625
|
-
" data ticker <assetId> 24h ticker for an outcome asset",
|
|
13626
|
-
" data candles <assetId> OHLCV candles for an outcome asset",
|
|
13627
|
-
" search <keyword> Search events/markets",
|
|
13628
|
-
" (Note: events/event/market etc. live UNDER the `data` namespace \u2014 calling them",
|
|
13629
|
-
" as top-level commands prints the binary's help instead of returning JSON.)",
|
|
13630
|
-
"",
|
|
13631
|
-
" clob price/prices/midpoint(s)/spread(s)/book(s) --asset <id>",
|
|
13632
|
-
" CLOB read-side market data",
|
|
13633
|
-
" clob create-order Place limit order (EIP-712 signed)",
|
|
13634
|
-
" clob market-order Cross book immediately (IOC/FOK)",
|
|
13635
|
-
" clob cancel-oid | cancel-all | heartbeat",
|
|
13636
|
-
"",
|
|
13637
|
-
" ctf split/merge/redeem Conditional token operations",
|
|
13638
|
-
"",
|
|
13639
|
-
" account balance/order/orders/positions/closed-positions/trades",
|
|
13640
|
-
" HMAC-auth account queries",
|
|
13641
|
-
"",
|
|
13642
|
-
" wallet show Show derived wallet address",
|
|
13643
|
-
" status Health check",
|
|
13644
|
-
" setup Interactive .env wizard",
|
|
13645
|
-
"",
|
|
13646
|
-
"Run 'okx outcomes <command> --help' for command-specific help.",
|
|
13647
|
-
"",
|
|
13648
|
-
"Note: place flags after the subcommand, e.g. 'okx outcomes events --json'",
|
|
13649
|
-
" (or before the module: 'okx --json outcomes events'). Writing",
|
|
13650
|
-
" 'okx outcomes --json events' is not supported.",
|
|
13651
|
-
"",
|
|
13652
|
-
"Requires: curl -fsSL https://raw.githubusercontent.com/okx/outcomes/master/install.sh | sh"
|
|
13653
|
-
];
|
|
13654
|
-
for (const line of lines) outputLine(line);
|
|
13655
|
-
}
|
|
13656
|
-
async function handleOutcomesCommand(action, rest, v) {
|
|
13657
|
-
if (!action || action === "--help" || action === "-h" || action === "help") {
|
|
13658
|
-
printOutcomesHelp();
|
|
13659
|
-
return;
|
|
13660
|
-
}
|
|
13661
|
-
const forwardArgs = [action, ...rest];
|
|
13662
|
-
if (v.json && !forwardArgs.includes("--json") && !forwardArgs.includes("-j")) {
|
|
13663
|
-
forwardArgs.push("--json");
|
|
13664
|
-
}
|
|
13665
|
-
const code = await runOkxOutcomes(forwardArgs);
|
|
13666
|
-
if (code !== 0) process.exitCode = code;
|
|
13667
|
-
}
|
|
13668
|
-
|
|
13669
13897
|
// src/commands/diagnose.ts
|
|
13670
13898
|
import dns from "dns/promises";
|
|
13671
13899
|
import net from "net";
|
|
@@ -13691,26 +13919,26 @@ var Report = class {
|
|
|
13691
13919
|
this.lines.push({ key, value });
|
|
13692
13920
|
}
|
|
13693
13921
|
print() {
|
|
13694
|
-
const
|
|
13922
|
+
const sep3 = "\u2500".repeat(52);
|
|
13695
13923
|
outputLine("");
|
|
13696
|
-
outputLine(` \u2500\u2500 Diagnostic Report (copy & share) ${
|
|
13924
|
+
outputLine(` \u2500\u2500 Diagnostic Report (copy & share) ${sep3.slice(35)}`);
|
|
13697
13925
|
for (const { key, value } of this.lines) {
|
|
13698
13926
|
outputLine(` ${key.padEnd(14)} ${value}`);
|
|
13699
13927
|
}
|
|
13700
|
-
outputLine(` ${
|
|
13928
|
+
outputLine(` ${sep3}`);
|
|
13701
13929
|
outputLine("");
|
|
13702
13930
|
}
|
|
13703
13931
|
/** Write report to a file path, returns true on success. */
|
|
13704
13932
|
writeToFile(filePath) {
|
|
13705
13933
|
try {
|
|
13706
|
-
const
|
|
13934
|
+
const sep3 = "\u2500".repeat(52);
|
|
13707
13935
|
const lines = [
|
|
13708
|
-
`\u2500\u2500 Diagnostic Report (copy & share) ${
|
|
13936
|
+
`\u2500\u2500 Diagnostic Report (copy & share) ${sep3.slice(35)}`
|
|
13709
13937
|
];
|
|
13710
13938
|
for (const { key, value } of this.lines) {
|
|
13711
13939
|
lines.push(`${key.padEnd(14)} ${value}`);
|
|
13712
13940
|
}
|
|
13713
|
-
lines.push(
|
|
13941
|
+
lines.push(sep3, "");
|
|
13714
13942
|
fs4.writeFileSync(filePath, lines.join("\n"), "utf8");
|
|
13715
13943
|
return true;
|
|
13716
13944
|
} catch (_e) {
|
|
@@ -13760,7 +13988,7 @@ function sanitize2(value) {
|
|
|
13760
13988
|
import fs5 from "fs";
|
|
13761
13989
|
import path4 from "path";
|
|
13762
13990
|
import os4 from "os";
|
|
13763
|
-
import { spawnSync, spawn as
|
|
13991
|
+
import { spawnSync, spawn as spawn4 } from "child_process";
|
|
13764
13992
|
import { createRequire as createRequire2 } from "module";
|
|
13765
13993
|
import { fileURLToPath } from "url";
|
|
13766
13994
|
var _require2 = createRequire2(import.meta.url);
|
|
@@ -14086,15 +14314,15 @@ async function checkStdioHandshake(entryPath, report) {
|
|
|
14086
14314
|
clientInfo: { name: "okx-diagnose", version: "1.0" }
|
|
14087
14315
|
}
|
|
14088
14316
|
});
|
|
14089
|
-
return new Promise((
|
|
14317
|
+
return new Promise((resolve4) => {
|
|
14090
14318
|
let settled = false;
|
|
14091
14319
|
const settle = (passed) => {
|
|
14092
14320
|
if (settled) return;
|
|
14093
14321
|
settled = true;
|
|
14094
14322
|
clearTimeout(timer);
|
|
14095
|
-
|
|
14323
|
+
resolve4(passed);
|
|
14096
14324
|
};
|
|
14097
|
-
const child =
|
|
14325
|
+
const child = spawn4(process.execPath, [entryPath], {
|
|
14098
14326
|
stdio: ["pipe", "pipe", "pipe"],
|
|
14099
14327
|
env: { ...process.env }
|
|
14100
14328
|
});
|
|
@@ -14213,7 +14441,7 @@ async function cmdDiagnoseMcp(options = {}) {
|
|
|
14213
14441
|
|
|
14214
14442
|
// src/commands/diagnose.ts
|
|
14215
14443
|
var CLI_VERSION = readCliVersion();
|
|
14216
|
-
var GIT_HASH = true ? "
|
|
14444
|
+
var GIT_HASH = true ? "2c1da5c1" : "dev";
|
|
14217
14445
|
function maskKey2(key) {
|
|
14218
14446
|
if (!key) return "(not set)";
|
|
14219
14447
|
if (key.length <= 8) return "****";
|
|
@@ -14230,7 +14458,7 @@ async function checkDns(hostname) {
|
|
|
14230
14458
|
}
|
|
14231
14459
|
async function checkSocket(createFn, successEvent, timeoutMs) {
|
|
14232
14460
|
const t0 = Date.now();
|
|
14233
|
-
return new Promise((
|
|
14461
|
+
return new Promise((resolve4) => {
|
|
14234
14462
|
const socket = createFn();
|
|
14235
14463
|
const cleanup = () => {
|
|
14236
14464
|
socket.removeAllListeners();
|
|
@@ -14238,15 +14466,15 @@ async function checkSocket(createFn, successEvent, timeoutMs) {
|
|
|
14238
14466
|
};
|
|
14239
14467
|
socket.once(successEvent, () => {
|
|
14240
14468
|
cleanup();
|
|
14241
|
-
|
|
14469
|
+
resolve4({ ok: true, ms: Date.now() - t0 });
|
|
14242
14470
|
});
|
|
14243
14471
|
socket.once("timeout", () => {
|
|
14244
14472
|
cleanup();
|
|
14245
|
-
|
|
14473
|
+
resolve4({ ok: false, ms: Date.now() - t0, error: `timed out after ${timeoutMs}ms` });
|
|
14246
14474
|
});
|
|
14247
14475
|
socket.once("error", (err) => {
|
|
14248
14476
|
cleanup();
|
|
14249
|
-
|
|
14477
|
+
resolve4({ ok: false, ms: Date.now() - t0, error: err.message });
|
|
14250
14478
|
});
|
|
14251
14479
|
});
|
|
14252
14480
|
}
|
|
@@ -14574,23 +14802,23 @@ function suggestSubcommand(action, knownActions, knownPaths = []) {
|
|
|
14574
14802
|
|
|
14575
14803
|
// src/commands/upgrade.ts
|
|
14576
14804
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
14577
|
-
import { readFileSync as
|
|
14578
|
-
import { dirname as dirname8, join as
|
|
14805
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, mkdirSync as mkdirSync11 } from "fs";
|
|
14806
|
+
import { dirname as dirname8, join as join15 } from "path";
|
|
14579
14807
|
import { homedir as homedir11 } from "os";
|
|
14580
14808
|
var PACKAGES = ["@okx_ai/okx-trade-mcp", "@okx_ai/okx-trade-cli"];
|
|
14581
|
-
var CACHE_FILE2 =
|
|
14809
|
+
var CACHE_FILE2 = join15(homedir11(), ".okx", "last_check");
|
|
14582
14810
|
var THROTTLE_MS = 12 * 60 * 60 * 1e3;
|
|
14583
|
-
var NPM_BIN =
|
|
14811
|
+
var NPM_BIN = join15(dirname8(process.execPath), process.platform === "win32" ? "npm.cmd" : "npm");
|
|
14584
14812
|
function readLastCheck() {
|
|
14585
14813
|
try {
|
|
14586
|
-
return parseInt(
|
|
14814
|
+
return parseInt(readFileSync11(CACHE_FILE2, "utf-8").trim(), 10) || 0;
|
|
14587
14815
|
} catch {
|
|
14588
14816
|
return 0;
|
|
14589
14817
|
}
|
|
14590
14818
|
}
|
|
14591
14819
|
function writeLastCheck() {
|
|
14592
14820
|
try {
|
|
14593
|
-
mkdirSync11(
|
|
14821
|
+
mkdirSync11(join15(homedir11(), ".okx"), { recursive: true });
|
|
14594
14822
|
writeFileSync8(CACHE_FILE2, String(Math.floor(Date.now() / 1e3)), "utf-8");
|
|
14595
14823
|
} catch {
|
|
14596
14824
|
}
|
|
@@ -15746,63 +15974,6 @@ var CLI_REGISTRY = {
|
|
|
15746
15974
|
description: "Upgrade okx CLI and MCP server to the latest stable version",
|
|
15747
15975
|
usage: "okx upgrade [--check] [--beta] [--force] [--json]"
|
|
15748
15976
|
},
|
|
15749
|
-
// ── outcomes ───────────────────────────────────────────────────────────────
|
|
15750
|
-
// External binary wrapper - forwards to the okx-outcomes binary (formerly okx-predict).
|
|
15751
|
-
// All toolName=null because outcomes commands are not exposed as MCP tools.
|
|
15752
|
-
outcomes: {
|
|
15753
|
-
description: "OKX Outcomes markets (YES/NO event contracts) via external okx-outcomes binary",
|
|
15754
|
-
commands: {
|
|
15755
|
-
// Note: events / event / event-markets / market / trending / ticker /
|
|
15756
|
-
// candles live UNDER the `data` namespace in the upstream binary.
|
|
15757
|
-
// Calling them as top-level commands prints the binary's help text
|
|
15758
|
-
// instead of returning JSON. Always invoke as `okx outcomes data <cmd>`.
|
|
15759
|
-
data: {
|
|
15760
|
-
toolName: null,
|
|
15761
|
-
usage: "okx outcomes data <events|event|event-markets|market|trending|ticker|candles> [args...]",
|
|
15762
|
-
description: "Public market data namespace: events, event(-markets), market, trending, ticker, candles"
|
|
15763
|
-
},
|
|
15764
|
-
search: {
|
|
15765
|
-
toolName: null,
|
|
15766
|
-
usage: "okx outcomes search <keyword> [--limit <n>] [--cursor <c>]",
|
|
15767
|
-
description: "Search events/markets by keyword"
|
|
15768
|
-
},
|
|
15769
|
-
account: {
|
|
15770
|
-
toolName: null,
|
|
15771
|
-
usage: "okx outcomes account <balance|order|orders|positions|closed-positions|trades>",
|
|
15772
|
-
description: "HMAC-auth account queries"
|
|
15773
|
-
},
|
|
15774
|
-
clob: {
|
|
15775
|
-
toolName: null,
|
|
15776
|
-
usage: "okx outcomes clob <price|prices|midpoint|midpoints|spread|spreads|book|books|order|orders|trades|create-order|market-order|cancel-oid|cancel-all|heartbeat>",
|
|
15777
|
-
description: "CLOB market data (--asset) + EIP-712 signed order operations"
|
|
15778
|
-
},
|
|
15779
|
-
ctf: {
|
|
15780
|
-
toolName: null,
|
|
15781
|
-
usage: "okx outcomes ctf <split|merge|redeem> --market <id> [--amount <xp>]",
|
|
15782
|
-
description: "Conditional Token Framework: split xp into YES/NO, merge, redeem"
|
|
15783
|
-
},
|
|
15784
|
-
wallet: {
|
|
15785
|
-
toolName: null,
|
|
15786
|
-
usage: "okx outcomes wallet show",
|
|
15787
|
-
description: "Show derived wallet address (from PREDICTIONS_AGENT_PRIVATE_KEY)"
|
|
15788
|
-
},
|
|
15789
|
-
status: {
|
|
15790
|
-
toolName: null,
|
|
15791
|
-
usage: "okx outcomes status [--json]",
|
|
15792
|
-
description: "Health check: API + balance reachability"
|
|
15793
|
-
},
|
|
15794
|
-
setup: {
|
|
15795
|
-
toolName: null,
|
|
15796
|
-
usage: "okx outcomes setup",
|
|
15797
|
-
description: "Interactive .env wizard for PREDICTIONS_* env vars"
|
|
15798
|
-
},
|
|
15799
|
-
shell: {
|
|
15800
|
-
toolName: null,
|
|
15801
|
-
usage: "okx outcomes shell",
|
|
15802
|
-
description: "Interactive REPL (do not invoke from agent context)"
|
|
15803
|
-
}
|
|
15804
|
-
}
|
|
15805
|
-
},
|
|
15806
15977
|
// ── list-tools ──────────────────────────────────────────────────────────────
|
|
15807
15978
|
"list-tools": {
|
|
15808
15979
|
description: "List all available tools and their parameters (use --json for machine-readable output)",
|
|
@@ -16675,30 +16846,6 @@ function parseCli(argv) {
|
|
|
16675
16846
|
}
|
|
16676
16847
|
return { values, positionals };
|
|
16677
16848
|
}
|
|
16678
|
-
function peekFirstPositional(argv) {
|
|
16679
|
-
const booleanFlags = /* @__PURE__ */ new Set();
|
|
16680
|
-
for (const [name, opt] of Object.entries(CLI_OPTIONS)) {
|
|
16681
|
-
const o = opt;
|
|
16682
|
-
if (o.type === "boolean") {
|
|
16683
|
-
booleanFlags.add(name);
|
|
16684
|
-
if (o.short) booleanFlags.add(o.short);
|
|
16685
|
-
}
|
|
16686
|
-
}
|
|
16687
|
-
for (let i = 0; i < argv.length; i++) {
|
|
16688
|
-
const tok = argv[i];
|
|
16689
|
-
if (tok === "--") {
|
|
16690
|
-
const next2 = argv[i + 1];
|
|
16691
|
-
return next2 === void 0 ? void 0 : { module: next2, idx: i + 1 };
|
|
16692
|
-
}
|
|
16693
|
-
if (!tok.startsWith("-")) return { module: tok, idx: i };
|
|
16694
|
-
if (tok.includes("=")) continue;
|
|
16695
|
-
const name = tok.replace(/^--?/, "");
|
|
16696
|
-
if (booleanFlags.has(name)) continue;
|
|
16697
|
-
const next = argv[i + 1];
|
|
16698
|
-
if (next !== void 0 && !next.startsWith("-")) i++;
|
|
16699
|
-
}
|
|
16700
|
-
return void 0;
|
|
16701
|
-
}
|
|
16702
16849
|
|
|
16703
16850
|
// src/commands/market.ts
|
|
16704
16851
|
function getData2(result) {
|
|
@@ -18678,7 +18825,7 @@ Config saved to ${p}
|
|
|
18678
18825
|
}
|
|
18679
18826
|
};
|
|
18680
18827
|
function prompt(rl, question) {
|
|
18681
|
-
return new Promise((
|
|
18828
|
+
return new Promise((resolve4) => rl.question(question, resolve4));
|
|
18682
18829
|
}
|
|
18683
18830
|
function cmdConfigShow(json) {
|
|
18684
18831
|
const config = readFullConfig();
|
|
@@ -20101,18 +20248,21 @@ async function cmdDcdQuoteAndBuy(run, opts) {
|
|
|
20101
20248
|
|
|
20102
20249
|
// src/commands/skill.ts
|
|
20103
20250
|
import { tmpdir, homedir as homedir13 } from "os";
|
|
20104
|
-
import { join as
|
|
20105
|
-
import { mkdirSync as mkdirSync12, rmSync, existsSync as
|
|
20251
|
+
import { join as join17, dirname as dirname9 } from "path";
|
|
20252
|
+
import { mkdirSync as mkdirSync12, rmSync, existsSync as existsSync10, copyFileSync as copyFileSync2 } from "fs";
|
|
20106
20253
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
20107
20254
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
20108
20255
|
function resolveNpx() {
|
|
20109
|
-
const sibling =
|
|
20110
|
-
if (
|
|
20256
|
+
const sibling = join17(dirname9(process.execPath), "npx");
|
|
20257
|
+
if (existsSync10(sibling)) return sibling;
|
|
20111
20258
|
return "npx";
|
|
20112
20259
|
}
|
|
20113
20260
|
function npxEnv() {
|
|
20114
20261
|
return { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" };
|
|
20115
20262
|
}
|
|
20263
|
+
function getSkillContentDir(name) {
|
|
20264
|
+
return join17(homedir13(), ".agents", "skills", name);
|
|
20265
|
+
}
|
|
20116
20266
|
var THIRD_PARTY_INSTALL_NOTICE = "Note: This skill was created by a third-party developer, not by OKX. Review SKILL.md before use.";
|
|
20117
20267
|
async function cmdSkillSearch(run, opts) {
|
|
20118
20268
|
const args = {};
|
|
@@ -20162,16 +20312,56 @@ async function cmdSkillCategories(run, json) {
|
|
|
20162
20312
|
}
|
|
20163
20313
|
outputLine("");
|
|
20164
20314
|
}
|
|
20165
|
-
async function
|
|
20166
|
-
|
|
20315
|
+
async function wrapVerify(fn) {
|
|
20316
|
+
try {
|
|
20317
|
+
return await fn();
|
|
20318
|
+
} catch (e) {
|
|
20319
|
+
if (e instanceof ConfigError) {
|
|
20320
|
+
throw new Error(
|
|
20321
|
+
`Signature verification requires authentication \u2014 run \`okx auth login\` first, or use --force to bypass.`
|
|
20322
|
+
);
|
|
20323
|
+
}
|
|
20324
|
+
throw e;
|
|
20325
|
+
}
|
|
20326
|
+
}
|
|
20327
|
+
async function cmdSkillAdd(name, config, json, force = false, exec = execFileSync2, _deps) {
|
|
20328
|
+
const _download = _deps?.download ?? downloadSkillZip;
|
|
20329
|
+
const _extract = _deps?.extract ?? extractSkillZip;
|
|
20330
|
+
const tmpBase = join17(tmpdir(), `okx-skill-${randomUUID2()}`);
|
|
20167
20331
|
mkdirSync12(tmpBase, { recursive: true });
|
|
20168
20332
|
try {
|
|
20169
20333
|
outputLine(`Downloading ${name}...`);
|
|
20170
20334
|
const client = new OkxRestClient(config);
|
|
20171
|
-
const zipPath = await
|
|
20172
|
-
const contentDir = await
|
|
20335
|
+
const zipPath = await _download(client, name, tmpBase);
|
|
20336
|
+
const contentDir = await _extract(zipPath, join17(tmpBase, "content"));
|
|
20173
20337
|
const meta = readMetaJson(contentDir);
|
|
20174
20338
|
validateSkillMdExists(contentDir);
|
|
20339
|
+
outputLine("Verifying signature...");
|
|
20340
|
+
const verifyResult = await wrapVerify(
|
|
20341
|
+
() => verifySkillSignature(contentDir, meta.signing, {
|
|
20342
|
+
fetchPublicKey: (keyId) => getPublicKey(client, keyId),
|
|
20343
|
+
serverSideVerify: (sName, version, files) => serverSideVerify(client, sName, version, files),
|
|
20344
|
+
skillName: meta.name,
|
|
20345
|
+
skillVersion: meta.version
|
|
20346
|
+
})
|
|
20347
|
+
);
|
|
20348
|
+
if (verifyResult.status === "failed") {
|
|
20349
|
+
if (!force) {
|
|
20350
|
+
throw new Error(`Signature verification failed: ${verifyResult.error ?? "unknown error"}. Use --force to install anyway.`);
|
|
20351
|
+
}
|
|
20352
|
+
process.stderr.write(`WARNING: Signature verification failed \u2014 ${verifyResult.error ?? "unknown"}. Installing anyway (--force).
|
|
20353
|
+
`);
|
|
20354
|
+
} else if (verifyResult.status === "verified_by_server") {
|
|
20355
|
+
if (!json) {
|
|
20356
|
+
outputLine(` Verified by server (v${verifyResult.serverVersion ?? "?"})`);
|
|
20357
|
+
if (verifyResult.error) outputLine(` Note: ${verifyResult.error}`);
|
|
20358
|
+
}
|
|
20359
|
+
} else if (!json) {
|
|
20360
|
+
outputLine(` Signature verified (key: ${verifyResult.publicKeyId}, files: ${verifyResult.filesChecked})`);
|
|
20361
|
+
if (verifyResult.extraFiles?.length) {
|
|
20362
|
+
outputLine(` Note: ${verifyResult.extraFiles.length} extra unsigned file(s) present`);
|
|
20363
|
+
}
|
|
20364
|
+
}
|
|
20175
20365
|
outputLine("Installing to detected agents...");
|
|
20176
20366
|
try {
|
|
20177
20367
|
exec(resolveNpx(), ["skills", "add", contentDir, "-y", "-g"], {
|
|
@@ -20180,7 +20370,7 @@ async function cmdSkillAdd(name, config, json, exec = execFileSync2) {
|
|
|
20180
20370
|
env: npxEnv()
|
|
20181
20371
|
});
|
|
20182
20372
|
} catch (e) {
|
|
20183
|
-
const savedZip =
|
|
20373
|
+
const savedZip = join17(process.cwd(), `${name}.zip`);
|
|
20184
20374
|
try {
|
|
20185
20375
|
copyFileSync2(zipPath, savedZip);
|
|
20186
20376
|
} catch {
|
|
@@ -20189,7 +20379,8 @@ async function cmdSkillAdd(name, config, json, exec = execFileSync2) {
|
|
|
20189
20379
|
errorLine(`You can manually install from: ${savedZip}`);
|
|
20190
20380
|
throw e;
|
|
20191
20381
|
}
|
|
20192
|
-
|
|
20382
|
+
const registryStatus = verifyResult.status === "failed" && force ? "bypassed" : verifyResult.status;
|
|
20383
|
+
upsertSkillRecord(meta, void 0, registryStatus);
|
|
20193
20384
|
printSkillInstallResult(meta, json);
|
|
20194
20385
|
} finally {
|
|
20195
20386
|
rmSync(tmpBase, { recursive: true, force: true });
|
|
@@ -20220,7 +20411,7 @@ function cmdSkillRemove(name, json, exec = execFileSync2) {
|
|
|
20220
20411
|
env: npxEnv()
|
|
20221
20412
|
});
|
|
20222
20413
|
} catch {
|
|
20223
|
-
const agentsPath =
|
|
20414
|
+
const agentsPath = getSkillContentDir(name);
|
|
20224
20415
|
try {
|
|
20225
20416
|
rmSync(agentsPath, { recursive: true, force: true });
|
|
20226
20417
|
} catch {
|
|
@@ -20284,6 +20475,59 @@ function cmdSkillList(json) {
|
|
|
20284
20475
|
outputLine("");
|
|
20285
20476
|
outputLine(`${skills.length} skills installed.`);
|
|
20286
20477
|
}
|
|
20478
|
+
async function cmdSkillVerify(name, config, json) {
|
|
20479
|
+
const record = getSkillRecord(name);
|
|
20480
|
+
if (!record) {
|
|
20481
|
+
errorLine(`Skill "${name}" is not installed.`);
|
|
20482
|
+
process.exitCode = 1;
|
|
20483
|
+
return;
|
|
20484
|
+
}
|
|
20485
|
+
const contentDir = getSkillContentDir(name);
|
|
20486
|
+
if (!existsSync10(contentDir)) {
|
|
20487
|
+
errorLine(`Skill content directory not found: ${contentDir}`);
|
|
20488
|
+
errorLine(`Try reinstalling with: okx skill add ${name}`);
|
|
20489
|
+
process.exitCode = 1;
|
|
20490
|
+
return;
|
|
20491
|
+
}
|
|
20492
|
+
const meta = tryReadMetaJson(contentDir);
|
|
20493
|
+
const client = new OkxRestClient(config);
|
|
20494
|
+
let result;
|
|
20495
|
+
try {
|
|
20496
|
+
result = await wrapVerify(
|
|
20497
|
+
() => verifySkillSignature(contentDir, meta?.signing, {
|
|
20498
|
+
fetchPublicKey: (keyId) => getPublicKey(client, keyId),
|
|
20499
|
+
serverSideVerify: (sName, version, files) => serverSideVerify(client, sName, version, files),
|
|
20500
|
+
skillName: name,
|
|
20501
|
+
skillVersion: meta?.version
|
|
20502
|
+
})
|
|
20503
|
+
);
|
|
20504
|
+
} catch (e) {
|
|
20505
|
+
errorLine(e instanceof Error ? e.message : String(e));
|
|
20506
|
+
process.exitCode = 1;
|
|
20507
|
+
return;
|
|
20508
|
+
}
|
|
20509
|
+
if (meta) {
|
|
20510
|
+
upsertSkillRecord(meta, void 0, result.status);
|
|
20511
|
+
}
|
|
20512
|
+
if (result.status === "failed") {
|
|
20513
|
+
process.exitCode = 1;
|
|
20514
|
+
}
|
|
20515
|
+
if (json) {
|
|
20516
|
+
outputLine(JSON.stringify(result, null, 2));
|
|
20517
|
+
return;
|
|
20518
|
+
}
|
|
20519
|
+
if (result.status === "verified") {
|
|
20520
|
+
outputLine(`\u2713 ${name}: signature verified (key: ${result.publicKeyId}, files: ${result.filesChecked})`);
|
|
20521
|
+
if (result.extraFiles?.length) {
|
|
20522
|
+
outputLine(` Note: ${result.extraFiles.length} extra unsigned file(s) present`);
|
|
20523
|
+
}
|
|
20524
|
+
} else if (result.status === "verified_by_server") {
|
|
20525
|
+
outputLine(`\u2713 ${name}: verified by server (v${result.serverVersion ?? "?"})`);
|
|
20526
|
+
if (result.error) outputLine(` Note: ${result.error}`);
|
|
20527
|
+
} else if (result.status === "failed") {
|
|
20528
|
+
errorLine(`\u2717 ${name}: verification failed \u2014 ${result.error ?? "unknown"}`);
|
|
20529
|
+
}
|
|
20530
|
+
}
|
|
20287
20531
|
function printSkillInstallResult(meta, json) {
|
|
20288
20532
|
if (json) {
|
|
20289
20533
|
outputLine(JSON.stringify({ name: meta.name, version: meta.version, status: "installed" }, null, 2));
|
|
@@ -20434,14 +20678,14 @@ function formatBytes2(bytes) {
|
|
|
20434
20678
|
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
20435
20679
|
}
|
|
20436
20680
|
function askConfirmation2(prompt2) {
|
|
20437
|
-
return new Promise((
|
|
20681
|
+
return new Promise((resolve4) => {
|
|
20438
20682
|
const rl = readline2.createInterface({
|
|
20439
20683
|
input: process.stdin,
|
|
20440
20684
|
output: process.stdout
|
|
20441
20685
|
});
|
|
20442
20686
|
rl.question(prompt2, (answer) => {
|
|
20443
20687
|
rl.close();
|
|
20444
|
-
|
|
20688
|
+
resolve4(answer.trim().toLowerCase() === "y");
|
|
20445
20689
|
});
|
|
20446
20690
|
});
|
|
20447
20691
|
}
|
|
@@ -20904,7 +21148,7 @@ async function cmdEventCancel(run, opts) {
|
|
|
20904
21148
|
// src/index.ts
|
|
20905
21149
|
var _require3 = createRequire3(import.meta.url);
|
|
20906
21150
|
var CLI_VERSION2 = _require3("../package.json").version;
|
|
20907
|
-
var GIT_HASH2 = true ? "
|
|
21151
|
+
var GIT_HASH2 = true ? "2c1da5c1" : "dev";
|
|
20908
21152
|
function handlePilotCommand(action, json, force, binaryPath) {
|
|
20909
21153
|
if (action === "status") return cmdPilotStatus(json, binaryPath);
|
|
20910
21154
|
if (action === "install") return cmdPilotInstall(json, binaryPath);
|
|
@@ -22216,9 +22460,13 @@ function requireSkillName(rest, usage) {
|
|
|
22216
22460
|
}
|
|
22217
22461
|
return name;
|
|
22218
22462
|
}
|
|
22219
|
-
function handleSkillAdd(rest, config, json) {
|
|
22463
|
+
function handleSkillAdd(rest, v, config, json) {
|
|
22220
22464
|
const n = requireSkillName(rest, "Usage: okx skill add <name>");
|
|
22221
|
-
if (n) return cmdSkillAdd(n, config, json);
|
|
22465
|
+
if (n) return cmdSkillAdd(n, config, json, v.force ?? false);
|
|
22466
|
+
}
|
|
22467
|
+
function handleSkillVerify(rest, config, json) {
|
|
22468
|
+
const n = requireSkillName(rest, "Usage: okx skill verify <name>");
|
|
22469
|
+
if (n) return cmdSkillVerify(n, config, json);
|
|
22222
22470
|
}
|
|
22223
22471
|
function handleSkillDownload(rest, v, config, json) {
|
|
22224
22472
|
const n = requireSkillName(rest, "Usage: okx skill download <name> [--dir <path>] [--format zip|skill]");
|
|
@@ -22237,12 +22485,13 @@ function handleSkillCommand(run, action, rest, v, json, config) {
|
|
|
22237
22485
|
if (action === "search") return cmdSkillSearch(run, { keyword: rest[0] ?? v.keyword, categories: v.categories, page: v.page, limit: v.limit, json });
|
|
22238
22486
|
if (action === "categories") return cmdSkillCategories(run, json);
|
|
22239
22487
|
if (action === "list") return cmdSkillList(json);
|
|
22240
|
-
if (action === "add") return handleSkillAdd(rest, config, json);
|
|
22488
|
+
if (action === "add") return handleSkillAdd(rest, v, config, json);
|
|
22241
22489
|
if (action === "download") return handleSkillDownload(rest, v, config, json);
|
|
22242
22490
|
if (action === "remove") return handleSkillRemove(rest, json);
|
|
22243
22491
|
if (action === "check") return handleSkillCheck(run, rest, json);
|
|
22492
|
+
if (action === "verify") return handleSkillVerify(rest, config, json);
|
|
22244
22493
|
errorLine(`Unknown skill command: ${action}`);
|
|
22245
|
-
errorLine("Valid: search, categories, add, download, remove, check, list");
|
|
22494
|
+
errorLine("Valid: search, categories, add, download, remove, check, list, verify");
|
|
22246
22495
|
process.exitCode = 1;
|
|
22247
22496
|
}
|
|
22248
22497
|
function handleEventCommand(run, action, rest, v, json) {
|
|
@@ -22372,16 +22621,7 @@ async function main() {
|
|
|
22372
22621
|
err: (m) => process.stderr.write(m)
|
|
22373
22622
|
});
|
|
22374
22623
|
checkForUpdates("@okx_ai/okx-trade-cli", CLI_VERSION2);
|
|
22375
|
-
const
|
|
22376
|
-
const peek = peekFirstPositional(rawArgv);
|
|
22377
|
-
if (peek?.module === "outcomes") {
|
|
22378
|
-
const after = rawArgv.slice(peek.idx + 1);
|
|
22379
|
-
const action2 = after[0];
|
|
22380
|
-
const rest2 = after.slice(1);
|
|
22381
|
-
const json2 = rawArgv.includes("--json") || rawArgv.includes("-j");
|
|
22382
|
-
return handleOutcomesCommand(action2, rest2, { json: json2 });
|
|
22383
|
-
}
|
|
22384
|
-
const { values, positionals } = parseCli(rawArgv);
|
|
22624
|
+
const { values, positionals } = parseCli(process.argv.slice(2));
|
|
22385
22625
|
if (values.version) {
|
|
22386
22626
|
printVersion();
|
|
22387
22627
|
return;
|