@pionex/pionex-ai-kit 0.2.17 → 0.2.19
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 +739 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { createInterface } from "readline";
|
|
5
|
+
import { basename } from "path";
|
|
5
6
|
|
|
6
7
|
// ../core/dist/index.js
|
|
7
8
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
@@ -839,6 +840,7 @@ import * as fs from "fs";
|
|
|
839
840
|
import * as path from "path";
|
|
840
841
|
import * as os from "os";
|
|
841
842
|
import { execFileSync } from "child_process";
|
|
843
|
+
import crypto from "crypto";
|
|
842
844
|
function configFilePath() {
|
|
843
845
|
return join(homedir(), ".pionex", "config.toml");
|
|
844
846
|
}
|
|
@@ -848,6 +850,11 @@ function readFullConfig() {
|
|
|
848
850
|
const raw = readFileSync(path2, "utf-8");
|
|
849
851
|
return parse(raw);
|
|
850
852
|
}
|
|
853
|
+
function readTomlProfile(profileName) {
|
|
854
|
+
const config = readFullConfig();
|
|
855
|
+
const name = profileName ?? config.default_profile ?? "default";
|
|
856
|
+
return config.profiles?.[name] ?? {};
|
|
857
|
+
}
|
|
851
858
|
function writeFullConfig(config) {
|
|
852
859
|
const path2 = configFilePath();
|
|
853
860
|
const dir = dirname(path2);
|
|
@@ -979,6 +986,513 @@ function runSetup(options) {
|
|
|
979
986
|
`
|
|
980
987
|
);
|
|
981
988
|
}
|
|
989
|
+
var PIONEX_API_DEFAULT_BASE_URL = "https://api.pionex.com";
|
|
990
|
+
var MODULES = ["market", "account", "orders"];
|
|
991
|
+
var DEFAULT_MODULES = ["market", "account", "orders"];
|
|
992
|
+
var ConfigError = class extends Error {
|
|
993
|
+
suggestion;
|
|
994
|
+
constructor(message, suggestion) {
|
|
995
|
+
super(message);
|
|
996
|
+
this.name = "ConfigError";
|
|
997
|
+
this.suggestion = suggestion;
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
var PionexApiError = class extends Error {
|
|
1001
|
+
status;
|
|
1002
|
+
endpoint;
|
|
1003
|
+
responseText;
|
|
1004
|
+
constructor(message, opts) {
|
|
1005
|
+
super(message);
|
|
1006
|
+
this.name = "PionexApiError";
|
|
1007
|
+
this.status = opts?.status;
|
|
1008
|
+
this.endpoint = opts?.endpoint;
|
|
1009
|
+
this.responseText = opts?.responseText;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
function toToolErrorPayload(error) {
|
|
1013
|
+
if (error instanceof ConfigError) {
|
|
1014
|
+
return {
|
|
1015
|
+
error: true,
|
|
1016
|
+
type: "ConfigError",
|
|
1017
|
+
message: error.message,
|
|
1018
|
+
suggestion: error.suggestion
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
if (error instanceof PionexApiError) {
|
|
1022
|
+
return {
|
|
1023
|
+
error: true,
|
|
1024
|
+
type: "PionexApiError",
|
|
1025
|
+
message: error.message,
|
|
1026
|
+
status: error.status,
|
|
1027
|
+
endpoint: error.endpoint,
|
|
1028
|
+
responseText: error.responseText
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1032
|
+
return { error: true, type: "Error", message };
|
|
1033
|
+
}
|
|
1034
|
+
function parseModuleList(rawModules) {
|
|
1035
|
+
if (!rawModules || rawModules.trim().length === 0) return [...DEFAULT_MODULES];
|
|
1036
|
+
const trimmed = rawModules.trim().toLowerCase();
|
|
1037
|
+
if (trimmed === "all") return [...MODULES];
|
|
1038
|
+
const requested = trimmed.split(",").map((x) => x.trim()).filter(Boolean);
|
|
1039
|
+
if (requested.length === 0) return [...DEFAULT_MODULES];
|
|
1040
|
+
const out = [];
|
|
1041
|
+
for (const m of requested) {
|
|
1042
|
+
if (!MODULES.includes(m)) {
|
|
1043
|
+
throw new ConfigError(`Unknown module "${m}".`, `Use one of: ${MODULES.join(", ")} or "all".`);
|
|
1044
|
+
}
|
|
1045
|
+
out.push(m);
|
|
1046
|
+
}
|
|
1047
|
+
return Array.from(new Set(out));
|
|
1048
|
+
}
|
|
1049
|
+
function loadConfig(cli) {
|
|
1050
|
+
const toml = readTomlProfile(cli.profile);
|
|
1051
|
+
const apiKey = process.env.PIONEX_API_KEY?.trim() || toml.api_key;
|
|
1052
|
+
const apiSecret = process.env.PIONEX_API_SECRET?.trim() || toml.secret_key;
|
|
1053
|
+
const hasAuth = Boolean(apiKey && apiSecret);
|
|
1054
|
+
const partialAuth = Boolean(apiKey) || Boolean(apiSecret);
|
|
1055
|
+
if (partialAuth && !hasAuth) {
|
|
1056
|
+
throw new ConfigError(
|
|
1057
|
+
"Partial Pionex API credentials detected.",
|
|
1058
|
+
"Set both PIONEX_API_KEY and PIONEX_API_SECRET (env vars or config.toml profile)."
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
const baseUrl = (cli.baseUrl?.trim() || process.env.PIONEX_BASE_URL?.trim() || toml.base_url || PIONEX_API_DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1062
|
+
if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) {
|
|
1063
|
+
throw new ConfigError(`Invalid base URL "${baseUrl}".`, "PIONEX_BASE_URL must start with http:// or https://");
|
|
1064
|
+
}
|
|
1065
|
+
return {
|
|
1066
|
+
apiKey,
|
|
1067
|
+
apiSecret,
|
|
1068
|
+
hasAuth,
|
|
1069
|
+
baseUrl,
|
|
1070
|
+
modules: parseModuleList(cli.modules),
|
|
1071
|
+
readOnly: cli.readOnly
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
function requireAuth(config) {
|
|
1075
|
+
if (!config.apiKey || !config.apiSecret) {
|
|
1076
|
+
throw new ConfigError(
|
|
1077
|
+
"This operation requires authentication, but no Pionex API credentials were found.",
|
|
1078
|
+
"Run 'pionex-ai-kit onboard' to create ~/.pionex/config.toml, or set PIONEX_API_KEY and PIONEX_API_SECRET."
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
return { apiKey: config.apiKey, apiSecret: config.apiSecret };
|
|
1082
|
+
}
|
|
1083
|
+
function buildQueryString(query) {
|
|
1084
|
+
if (!query) return "";
|
|
1085
|
+
const entries = Object.entries(query).filter(([, v]) => v !== void 0 && v !== null);
|
|
1086
|
+
if (entries.length === 0) return "";
|
|
1087
|
+
const params = new URLSearchParams();
|
|
1088
|
+
for (const [k, v] of entries) params.set(k, String(v));
|
|
1089
|
+
return params.toString();
|
|
1090
|
+
}
|
|
1091
|
+
function buildSignedRequest(config, method, path2, query, bodyJson) {
|
|
1092
|
+
const { apiKey, apiSecret } = requireAuth(config);
|
|
1093
|
+
const timestamp = Date.now().toString();
|
|
1094
|
+
const params = { ...query, timestamp };
|
|
1095
|
+
const sortedKeys = Object.keys(params).sort();
|
|
1096
|
+
const queryString = sortedKeys.map((k) => `${k}=${params[k]}`).join("&");
|
|
1097
|
+
const pathUrl = `${path2}?${queryString}`;
|
|
1098
|
+
let payload = `${method}${pathUrl}`;
|
|
1099
|
+
if (bodyJson != null) payload += bodyJson;
|
|
1100
|
+
const signature = crypto.createHmac("sha256", apiSecret).update(payload).digest("hex");
|
|
1101
|
+
const url = `${config.baseUrl}${pathUrl}`;
|
|
1102
|
+
const headers = {
|
|
1103
|
+
"PIONEX-KEY": apiKey,
|
|
1104
|
+
"PIONEX-SIGNATURE": signature,
|
|
1105
|
+
"Content-Type": "application/json"
|
|
1106
|
+
};
|
|
1107
|
+
return { url, headers, bodyJson };
|
|
1108
|
+
}
|
|
1109
|
+
async function readTextSafe(res) {
|
|
1110
|
+
try {
|
|
1111
|
+
return await res.text();
|
|
1112
|
+
} catch {
|
|
1113
|
+
return "";
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
var PionexRestClient = class {
|
|
1117
|
+
config;
|
|
1118
|
+
constructor(config) {
|
|
1119
|
+
this.config = config;
|
|
1120
|
+
}
|
|
1121
|
+
async publicGet(path2, query = {}) {
|
|
1122
|
+
const qs = buildQueryString(query);
|
|
1123
|
+
const endpoint = qs ? `${path2}?${qs}` : path2;
|
|
1124
|
+
const url = `${this.config.baseUrl}${endpoint}`;
|
|
1125
|
+
const res = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json" } });
|
|
1126
|
+
if (!res.ok) {
|
|
1127
|
+
const txt = await readTextSafe(res);
|
|
1128
|
+
throw new PionexApiError(`HTTP ${res.status}: ${txt || res.statusText}`, { status: res.status, endpoint, responseText: txt });
|
|
1129
|
+
}
|
|
1130
|
+
const data = await res.json();
|
|
1131
|
+
return { endpoint, requestTime: (/* @__PURE__ */ new Date()).toISOString(), data };
|
|
1132
|
+
}
|
|
1133
|
+
async signedGet(path2, query = {}) {
|
|
1134
|
+
const { url, headers } = buildSignedRequest(this.config, "GET", path2, query, null);
|
|
1135
|
+
const endpoint = `${path2}?${buildQueryString({ ...query, timestamp: "..." })}`;
|
|
1136
|
+
const res = await fetch(url, { method: "GET", headers });
|
|
1137
|
+
if (!res.ok) {
|
|
1138
|
+
const txt = await readTextSafe(res);
|
|
1139
|
+
throw new PionexApiError(`HTTP ${res.status}: ${txt || res.statusText}`, { status: res.status, endpoint: path2, responseText: txt });
|
|
1140
|
+
}
|
|
1141
|
+
const data = await res.json();
|
|
1142
|
+
return { endpoint: path2, requestTime: (/* @__PURE__ */ new Date()).toISOString(), data };
|
|
1143
|
+
}
|
|
1144
|
+
async signedPost(path2, body) {
|
|
1145
|
+
const bodyJson = JSON.stringify(body);
|
|
1146
|
+
const { url, headers, bodyJson: bj } = buildSignedRequest(this.config, "POST", path2, {}, bodyJson);
|
|
1147
|
+
const res = await fetch(url, { method: "POST", headers, body: bj ?? void 0 });
|
|
1148
|
+
if (!res.ok) {
|
|
1149
|
+
const txt = await readTextSafe(res);
|
|
1150
|
+
throw new PionexApiError(`HTTP ${res.status}: ${txt || res.statusText}`, { status: res.status, endpoint: path2, responseText: txt });
|
|
1151
|
+
}
|
|
1152
|
+
const data = await res.json();
|
|
1153
|
+
return { endpoint: path2, requestTime: (/* @__PURE__ */ new Date()).toISOString(), data };
|
|
1154
|
+
}
|
|
1155
|
+
async signedDelete(path2, body) {
|
|
1156
|
+
const bodyJson = JSON.stringify(body);
|
|
1157
|
+
const { url, headers, bodyJson: bj } = buildSignedRequest(this.config, "DELETE", path2, {}, bodyJson);
|
|
1158
|
+
const res = await fetch(url, { method: "DELETE", headers, body: bj ?? void 0 });
|
|
1159
|
+
if (!res.ok) {
|
|
1160
|
+
const txt = await readTextSafe(res);
|
|
1161
|
+
throw new PionexApiError(`HTTP ${res.status}: ${txt || res.statusText}`, { status: res.status, endpoint: path2, responseText: txt });
|
|
1162
|
+
}
|
|
1163
|
+
const data = await res.json();
|
|
1164
|
+
return { endpoint: path2, requestTime: (/* @__PURE__ */ new Date()).toISOString(), data };
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
function registerMarketTools() {
|
|
1168
|
+
return [
|
|
1169
|
+
{
|
|
1170
|
+
name: "pionex_market_get_depth",
|
|
1171
|
+
module: "market",
|
|
1172
|
+
isWrite: false,
|
|
1173
|
+
description: "Get order book depth (bids and asks) for a symbol. Use for spread, liquidity, or best bid/ask.",
|
|
1174
|
+
inputSchema: {
|
|
1175
|
+
type: "object",
|
|
1176
|
+
additionalProperties: false,
|
|
1177
|
+
properties: {
|
|
1178
|
+
symbol: { type: "string", description: "e.g. BTC_USDT" },
|
|
1179
|
+
limit: { type: "integer", description: "Price levels (1-100), default 5" }
|
|
1180
|
+
},
|
|
1181
|
+
required: ["symbol"]
|
|
1182
|
+
},
|
|
1183
|
+
async handler(args, { client }) {
|
|
1184
|
+
const symbol = String(args.symbol);
|
|
1185
|
+
const limit = args.limit == null ? void 0 : Number(args.limit);
|
|
1186
|
+
const q = { symbol };
|
|
1187
|
+
if (limit != null && Number.isFinite(limit)) q.limit = limit;
|
|
1188
|
+
return (await client.publicGet("/api/v1/market/depth", q)).data;
|
|
1189
|
+
}
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
name: "pionex_market_get_trades",
|
|
1193
|
+
module: "market",
|
|
1194
|
+
isWrite: false,
|
|
1195
|
+
description: "Get recent trades for a symbol. Use for latest price and volume.",
|
|
1196
|
+
inputSchema: {
|
|
1197
|
+
type: "object",
|
|
1198
|
+
additionalProperties: false,
|
|
1199
|
+
properties: {
|
|
1200
|
+
symbol: { type: "string", description: "e.g. BTC_USDT" },
|
|
1201
|
+
limit: { type: "integer", description: "Default 5 (1-100)" }
|
|
1202
|
+
},
|
|
1203
|
+
required: ["symbol"]
|
|
1204
|
+
},
|
|
1205
|
+
async handler(args, { client }) {
|
|
1206
|
+
const symbol = String(args.symbol);
|
|
1207
|
+
const limit = args.limit == null ? void 0 : Number(args.limit);
|
|
1208
|
+
const q = { symbol };
|
|
1209
|
+
if (limit != null && Number.isFinite(limit)) q.limit = limit;
|
|
1210
|
+
return (await client.publicGet("/api/v1/market/trades", q)).data;
|
|
1211
|
+
}
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
name: "pionex_market_get_symbol_info",
|
|
1215
|
+
module: "market",
|
|
1216
|
+
isWrite: false,
|
|
1217
|
+
description: "Get symbol metadata (precision, min size, price limits). Call before placing orders to avoid amount/size filter errors.",
|
|
1218
|
+
inputSchema: {
|
|
1219
|
+
type: "object",
|
|
1220
|
+
additionalProperties: false,
|
|
1221
|
+
properties: {
|
|
1222
|
+
symbols: {
|
|
1223
|
+
type: "string",
|
|
1224
|
+
description: 'Optional. One or more symbols, comma-separated, e.g. "BTC_USDT" or "BTC_USDT,ADA_USDT".'
|
|
1225
|
+
},
|
|
1226
|
+
type: {
|
|
1227
|
+
type: "string",
|
|
1228
|
+
enum: ["SPOT", "PERP"],
|
|
1229
|
+
description: "Optional. If no symbols are specified, filter by type (default SPOT)."
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
},
|
|
1233
|
+
async handler(args, { client }) {
|
|
1234
|
+
const q = {};
|
|
1235
|
+
if (args.symbols) q.symbols = String(args.symbols);
|
|
1236
|
+
if (!args.symbols && args.type) q.type = String(args.type);
|
|
1237
|
+
return (await client.publicGet("/api/v1/common/symbols", q)).data;
|
|
1238
|
+
}
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
name: "pionex_market_get_tickers",
|
|
1242
|
+
module: "market",
|
|
1243
|
+
isWrite: false,
|
|
1244
|
+
description: "Get 24-hour ticker(s): open, close, high, low, volume, amount, count. Optional symbol or type (SPOT/PERP).",
|
|
1245
|
+
inputSchema: {
|
|
1246
|
+
type: "object",
|
|
1247
|
+
additionalProperties: false,
|
|
1248
|
+
properties: {
|
|
1249
|
+
symbol: { type: "string", description: "e.g. BTC_USDT; if omitted, returns all tickers filtered by type" },
|
|
1250
|
+
type: { type: "string", enum: ["SPOT", "PERP"], description: "If symbol is not specified, filter by type." }
|
|
1251
|
+
}
|
|
1252
|
+
},
|
|
1253
|
+
async handler(args, { client }) {
|
|
1254
|
+
const q = {};
|
|
1255
|
+
if (args.symbol) q.symbol = String(args.symbol);
|
|
1256
|
+
if (args.type) q.type = String(args.type);
|
|
1257
|
+
return (await client.publicGet("/api/v1/market/tickers", q)).data;
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
name: "pionex_market_get_klines",
|
|
1262
|
+
module: "market",
|
|
1263
|
+
isWrite: false,
|
|
1264
|
+
description: "Get OHLCV klines (candlestick) for a symbol. Use for charts or historical price/volume.",
|
|
1265
|
+
inputSchema: {
|
|
1266
|
+
type: "object",
|
|
1267
|
+
additionalProperties: false,
|
|
1268
|
+
properties: {
|
|
1269
|
+
symbol: { type: "string", description: "e.g. BTC_USDT" },
|
|
1270
|
+
interval: { type: "string", enum: ["1M", "5M", "15M", "30M", "60M", "4H", "8H", "12H", "1D"], description: "Kline interval." },
|
|
1271
|
+
endTime: { type: "integer", description: "End time in milliseconds." },
|
|
1272
|
+
limit: { type: "integer", description: "Default 100 (1-500)." }
|
|
1273
|
+
},
|
|
1274
|
+
required: ["symbol", "interval"]
|
|
1275
|
+
},
|
|
1276
|
+
async handler(args, { client }) {
|
|
1277
|
+
const symbol = String(args.symbol);
|
|
1278
|
+
const interval = String(args.interval);
|
|
1279
|
+
const q = { symbol, interval };
|
|
1280
|
+
if (args.endTime != null) q.endTime = Number(args.endTime);
|
|
1281
|
+
if (args.limit != null) q.limit = Number(args.limit);
|
|
1282
|
+
return (await client.publicGet("/api/v1/market/klines", q)).data;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
];
|
|
1286
|
+
}
|
|
1287
|
+
function registerAccountTools() {
|
|
1288
|
+
return [
|
|
1289
|
+
{
|
|
1290
|
+
name: "pionex_account_get_balance",
|
|
1291
|
+
module: "account",
|
|
1292
|
+
isWrite: false,
|
|
1293
|
+
description: "Query spot account balances for all currencies. Requires API key and secret in ~/.pionex/config.toml or env.",
|
|
1294
|
+
inputSchema: { type: "object", additionalProperties: false, properties: {} },
|
|
1295
|
+
async handler(_args, { client }) {
|
|
1296
|
+
return (await client.signedGet("/api/v1/account/balances")).data;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
];
|
|
1300
|
+
}
|
|
1301
|
+
function registerOrdersTools() {
|
|
1302
|
+
return [
|
|
1303
|
+
{
|
|
1304
|
+
name: "pionex_orders_new_order",
|
|
1305
|
+
module: "orders",
|
|
1306
|
+
isWrite: true,
|
|
1307
|
+
description: "Create a spot order on Pionex. LIMIT requires symbol/side/type=LIMIT/price/size. MARKET BUY requires amount (quote). MARKET SELL requires size (base).",
|
|
1308
|
+
inputSchema: {
|
|
1309
|
+
type: "object",
|
|
1310
|
+
additionalProperties: false,
|
|
1311
|
+
properties: {
|
|
1312
|
+
symbol: { type: "string", description: "e.g. BTC_USDT" },
|
|
1313
|
+
side: { type: "string", enum: ["BUY", "SELL"] },
|
|
1314
|
+
type: { type: "string", enum: ["LIMIT", "MARKET"] },
|
|
1315
|
+
clientOrderId: { type: "string", description: "Optional client order id (max 64 chars)" },
|
|
1316
|
+
size: { type: "string", description: "Quantity; required for limit and market sell" },
|
|
1317
|
+
price: { type: "string", description: "Required for limit order" },
|
|
1318
|
+
amount: { type: "string", description: "Quote amount; required for market buy" },
|
|
1319
|
+
IOC: { type: "boolean", description: "Immediate-or-cancel, default false" }
|
|
1320
|
+
},
|
|
1321
|
+
required: ["symbol", "side", "type"]
|
|
1322
|
+
},
|
|
1323
|
+
async handler(args, { client, config }) {
|
|
1324
|
+
if (config.readOnly) {
|
|
1325
|
+
throw new Error("Server is running in --read-only mode; order placement is disabled.");
|
|
1326
|
+
}
|
|
1327
|
+
const body = {};
|
|
1328
|
+
if (args.symbol != null) body.symbol = String(args.symbol);
|
|
1329
|
+
if (args.side != null) body.side = String(args.side);
|
|
1330
|
+
if (args.type != null) body.type = String(args.type);
|
|
1331
|
+
if (args.clientOrderId != null) body.clientOrderId = String(args.clientOrderId);
|
|
1332
|
+
if (args.size != null) body.size = String(args.size);
|
|
1333
|
+
if (args.price != null) body.price = String(args.price);
|
|
1334
|
+
if (args.amount != null) body.amount = String(args.amount);
|
|
1335
|
+
if (args.IOC != null) body.IOC = Boolean(args.IOC);
|
|
1336
|
+
return (await client.signedPost("/api/v1/trade/order", body)).data;
|
|
1337
|
+
}
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
name: "pionex_orders_get_order",
|
|
1341
|
+
module: "orders",
|
|
1342
|
+
isWrite: false,
|
|
1343
|
+
description: "Get a single order by order ID.",
|
|
1344
|
+
inputSchema: {
|
|
1345
|
+
type: "object",
|
|
1346
|
+
additionalProperties: false,
|
|
1347
|
+
properties: {
|
|
1348
|
+
symbol: { type: "string", description: "e.g. BTC_USDT" },
|
|
1349
|
+
orderId: { type: "integer", description: "Order id" }
|
|
1350
|
+
},
|
|
1351
|
+
required: ["symbol", "orderId"]
|
|
1352
|
+
},
|
|
1353
|
+
async handler(args, { client }) {
|
|
1354
|
+
const symbol = String(args.symbol);
|
|
1355
|
+
const orderId = Number(args.orderId);
|
|
1356
|
+
return (await client.signedGet("/api/v1/trade/order", { symbol, orderId })).data;
|
|
1357
|
+
}
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
name: "pionex_orders_get_order_by_client_order_id",
|
|
1361
|
+
module: "orders",
|
|
1362
|
+
isWrite: false,
|
|
1363
|
+
description: "Get a single order by client order ID.",
|
|
1364
|
+
inputSchema: {
|
|
1365
|
+
type: "object",
|
|
1366
|
+
additionalProperties: false,
|
|
1367
|
+
properties: {
|
|
1368
|
+
symbol: { type: "string", description: "e.g. BTC_USDT" },
|
|
1369
|
+
clientOrderId: { type: "string", description: "Client order id" }
|
|
1370
|
+
},
|
|
1371
|
+
required: ["symbol", "clientOrderId"]
|
|
1372
|
+
},
|
|
1373
|
+
async handler(args, { client }) {
|
|
1374
|
+
const symbol = String(args.symbol);
|
|
1375
|
+
const clientOrderId = String(args.clientOrderId);
|
|
1376
|
+
return (await client.signedGet("/api/v1/trade/orderByClientOrderId", { symbol, clientOrderId })).data;
|
|
1377
|
+
}
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
name: "pionex_orders_get_open_orders",
|
|
1381
|
+
module: "orders",
|
|
1382
|
+
isWrite: false,
|
|
1383
|
+
description: "List open (unfilled) orders for a symbol.",
|
|
1384
|
+
inputSchema: {
|
|
1385
|
+
type: "object",
|
|
1386
|
+
additionalProperties: false,
|
|
1387
|
+
properties: { symbol: { type: "string", description: "e.g. BTC_USDT" } },
|
|
1388
|
+
required: ["symbol"]
|
|
1389
|
+
},
|
|
1390
|
+
async handler(args, { client }) {
|
|
1391
|
+
const symbol = String(args.symbol);
|
|
1392
|
+
return (await client.signedGet("/api/v1/trade/openOrders", { symbol })).data;
|
|
1393
|
+
}
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
name: "pionex_orders_get_all_orders",
|
|
1397
|
+
module: "orders",
|
|
1398
|
+
isWrite: false,
|
|
1399
|
+
description: "List order history for a symbol (filled and cancelled), with optional limit.",
|
|
1400
|
+
inputSchema: {
|
|
1401
|
+
type: "object",
|
|
1402
|
+
additionalProperties: false,
|
|
1403
|
+
properties: {
|
|
1404
|
+
symbol: { type: "string", description: "e.g. BTC_USDT" },
|
|
1405
|
+
limit: { type: "integer", description: "Default 1 (1-100)" }
|
|
1406
|
+
},
|
|
1407
|
+
required: ["symbol"]
|
|
1408
|
+
},
|
|
1409
|
+
async handler(args, { client }) {
|
|
1410
|
+
const symbol = String(args.symbol);
|
|
1411
|
+
const q = { symbol };
|
|
1412
|
+
if (args.limit != null) q.limit = Number(args.limit);
|
|
1413
|
+
return (await client.signedGet("/api/v1/trade/allOrders", q)).data;
|
|
1414
|
+
}
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
name: "pionex_orders_cancel_order",
|
|
1418
|
+
module: "orders",
|
|
1419
|
+
isWrite: true,
|
|
1420
|
+
description: "Cancel an open order by order ID.",
|
|
1421
|
+
inputSchema: {
|
|
1422
|
+
type: "object",
|
|
1423
|
+
additionalProperties: false,
|
|
1424
|
+
properties: {
|
|
1425
|
+
symbol: { type: "string", description: "e.g. BTC_USDT" },
|
|
1426
|
+
orderId: { type: "integer", description: "Order id" }
|
|
1427
|
+
},
|
|
1428
|
+
required: ["symbol", "orderId"]
|
|
1429
|
+
},
|
|
1430
|
+
async handler(args, { client, config }) {
|
|
1431
|
+
if (config.readOnly) {
|
|
1432
|
+
throw new Error("Server is running in --read-only mode; order cancellation is disabled.");
|
|
1433
|
+
}
|
|
1434
|
+
const symbol = String(args.symbol);
|
|
1435
|
+
const orderId = Number(args.orderId);
|
|
1436
|
+
return (await client.signedDelete("/api/v1/trade/order", { symbol, orderId })).data;
|
|
1437
|
+
}
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
name: "pionex_orders_get_fills",
|
|
1441
|
+
module: "orders",
|
|
1442
|
+
isWrite: false,
|
|
1443
|
+
description: "Get filled trades (fills) for a symbol in a time range. Requires API key. Returns up to 100 latest fills.",
|
|
1444
|
+
inputSchema: {
|
|
1445
|
+
type: "object",
|
|
1446
|
+
additionalProperties: false,
|
|
1447
|
+
properties: {
|
|
1448
|
+
symbol: { type: "string", description: "e.g. BTC_USDT" },
|
|
1449
|
+
startTime: { type: "integer", description: "Start time in milliseconds." },
|
|
1450
|
+
endTime: { type: "integer", description: "End time in milliseconds." }
|
|
1451
|
+
},
|
|
1452
|
+
required: ["symbol"]
|
|
1453
|
+
},
|
|
1454
|
+
async handler(args, { client }) {
|
|
1455
|
+
const symbol = String(args.symbol);
|
|
1456
|
+
const q = { symbol };
|
|
1457
|
+
if (args.startTime != null) q.startTime = Number(args.startTime);
|
|
1458
|
+
if (args.endTime != null) q.endTime = Number(args.endTime);
|
|
1459
|
+
return (await client.signedGet("/api/v1/trade/fills", q)).data;
|
|
1460
|
+
}
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
name: "pionex_orders_cancel_all_orders",
|
|
1464
|
+
module: "orders",
|
|
1465
|
+
isWrite: true,
|
|
1466
|
+
description: "Cancel all open orders for a symbol.",
|
|
1467
|
+
inputSchema: {
|
|
1468
|
+
type: "object",
|
|
1469
|
+
additionalProperties: false,
|
|
1470
|
+
properties: { symbol: { type: "string", description: "e.g. BTC_USDT" } },
|
|
1471
|
+
required: ["symbol"]
|
|
1472
|
+
},
|
|
1473
|
+
async handler(args, { client, config }) {
|
|
1474
|
+
if (config.readOnly) {
|
|
1475
|
+
throw new Error("Server is running in --read-only mode; cancel-all is disabled.");
|
|
1476
|
+
}
|
|
1477
|
+
const symbol = String(args.symbol);
|
|
1478
|
+
return (await client.signedDelete("/api/v1/trade/allOrders", { symbol })).data;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
];
|
|
1482
|
+
}
|
|
1483
|
+
function allToolSpecs() {
|
|
1484
|
+
return [...registerMarketTools(), ...registerAccountTools(), ...registerOrdersTools()];
|
|
1485
|
+
}
|
|
1486
|
+
function createToolRunner(client, config) {
|
|
1487
|
+
const fullConfig = { ...config, modules: [...MODULES] };
|
|
1488
|
+
const toolMap = new Map(allToolSpecs().map((t) => [t.name, t]));
|
|
1489
|
+
return async (toolName, args) => {
|
|
1490
|
+
const tool = toolMap.get(toolName);
|
|
1491
|
+
if (!tool) throw new Error(`Unknown tool: ${toolName}`);
|
|
1492
|
+
const data = await tool.handler(args, { config: fullConfig, client });
|
|
1493
|
+
return { endpoint: toolName, requestTime: (/* @__PURE__ */ new Date()).toISOString(), data };
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
982
1496
|
|
|
983
1497
|
// src/index.ts
|
|
984
1498
|
var DEFAULT_PROFILE_NAME = "pionx-prod";
|
|
@@ -1093,25 +1607,236 @@ function cmdSetup(argv) {
|
|
|
1093
1607
|
}
|
|
1094
1608
|
runSetup({ client: normalizedClient });
|
|
1095
1609
|
}
|
|
1096
|
-
function
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1610
|
+
function parseFlags(argv) {
|
|
1611
|
+
const positionals = [];
|
|
1612
|
+
const flags = {};
|
|
1613
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1614
|
+
const a = argv[i];
|
|
1615
|
+
if (!a.startsWith("--")) {
|
|
1616
|
+
positionals.push(a);
|
|
1617
|
+
continue;
|
|
1618
|
+
}
|
|
1619
|
+
const eq = a.indexOf("=");
|
|
1620
|
+
if (eq !== -1) {
|
|
1621
|
+
const k2 = a.slice(2, eq);
|
|
1622
|
+
const v = a.slice(eq + 1);
|
|
1623
|
+
flags[k2] = v === "true" ? true : v === "false" ? false : v;
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
const k = a.slice(2);
|
|
1627
|
+
const next = argv[i + 1];
|
|
1628
|
+
if (!next || next.startsWith("--")) {
|
|
1629
|
+
flags[k] = true;
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
flags[k] = next;
|
|
1633
|
+
i++;
|
|
1104
1634
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1635
|
+
return { positionals, flags };
|
|
1636
|
+
}
|
|
1637
|
+
function printPionexHelp() {
|
|
1638
|
+
process.stdout.write(`
|
|
1639
|
+
Usage: pionex-trade-cli <group> <command> [args] [--flags]
|
|
1640
|
+
|
|
1641
|
+
Groups:
|
|
1642
|
+
market Market data (public)
|
|
1643
|
+
account Account data (requires auth)
|
|
1644
|
+
orders Spot orders (requires auth)
|
|
1645
|
+
|
|
1646
|
+
Examples:
|
|
1647
|
+
pionex-trade-cli market depth BTC_USDT --limit 5
|
|
1648
|
+
pionex-trade-cli market tickers --symbol BTC_USDT
|
|
1649
|
+
pionex-trade-cli market symbols --symbols BTC_USDT
|
|
1650
|
+
pionex-trade-cli account balance
|
|
1651
|
+
pionex-trade-cli orders new --symbol BTC_USDT --side BUY --type MARKET --amount 10
|
|
1652
|
+
pionex-trade-cli orders cancel --symbol BTC_USDT --order-id 123
|
|
1653
|
+
|
|
1654
|
+
Global flags:
|
|
1655
|
+
--profile <name> Profile in ~/.pionex/config.toml
|
|
1656
|
+
--modules <list> Comma-separated modules (market,account,orders or all)
|
|
1657
|
+
--base-url <url> Override API base URL
|
|
1658
|
+
--read-only Disable write operations (orders new/cancel)
|
|
1659
|
+
--dry-run Print the tool call payload without executing (write ops only)
|
|
1660
|
+
`);
|
|
1661
|
+
}
|
|
1662
|
+
async function runPionexCommand(argv) {
|
|
1663
|
+
const { positionals, flags } = parseFlags(argv);
|
|
1664
|
+
const group = positionals[0];
|
|
1665
|
+
const command = positionals[1];
|
|
1666
|
+
if (!group || group === "help" || group === "--help" || group === "-h") {
|
|
1667
|
+
printPionexHelp();
|
|
1107
1668
|
return;
|
|
1108
1669
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1670
|
+
const config = loadConfig({
|
|
1671
|
+
profile: typeof flags.profile === "string" ? flags.profile : void 0,
|
|
1672
|
+
modules: typeof flags.modules === "string" ? flags.modules : void 0,
|
|
1673
|
+
baseUrl: typeof flags["base-url"] === "string" ? flags["base-url"] : typeof flags.baseUrl === "string" ? flags.baseUrl : void 0,
|
|
1674
|
+
readOnly: Boolean(flags["read-only"] || flags.readOnly)
|
|
1675
|
+
});
|
|
1676
|
+
const client = new PionexRestClient(config);
|
|
1677
|
+
const runTool = createToolRunner(client, config);
|
|
1678
|
+
const dryRun = Boolean(flags["dry-run"] || flags.dryRun);
|
|
1679
|
+
if (group === "market") {
|
|
1680
|
+
if (command === "depth") {
|
|
1681
|
+
const symbol = positionals[2];
|
|
1682
|
+
if (!symbol) throw new Error("Missing symbol. Example: pionex-trade-cli market depth BTC_USDT");
|
|
1683
|
+
const limit = flags.limit != null ? Number(flags.limit) : void 0;
|
|
1684
|
+
const out = await runTool("pionex_market_get_depth", { symbol, limit });
|
|
1685
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
if (command === "trades") {
|
|
1689
|
+
const symbol = positionals[2];
|
|
1690
|
+
if (!symbol) throw new Error("Missing symbol. Example: pionex-trade-cli market trades BTC_USDT");
|
|
1691
|
+
const limit = flags.limit != null ? Number(flags.limit) : void 0;
|
|
1692
|
+
const out = await runTool("pionex_market_get_trades", { symbol, limit });
|
|
1693
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
if (command === "symbols") {
|
|
1697
|
+
const symbols = typeof flags.symbols === "string" ? flags.symbols : void 0;
|
|
1698
|
+
const type = typeof flags.type === "string" ? flags.type : void 0;
|
|
1699
|
+
const out = await runTool("pionex_market_get_symbol_info", { symbols, type });
|
|
1700
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
if (command === "tickers") {
|
|
1704
|
+
const symbol = typeof flags.symbol === "string" ? flags.symbol : void 0;
|
|
1705
|
+
const type = typeof flags.type === "string" ? flags.type : void 0;
|
|
1706
|
+
const out = await runTool("pionex_market_get_tickers", { symbol, type });
|
|
1707
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
if (command === "klines") {
|
|
1711
|
+
const symbol = typeof flags.symbol === "string" ? flags.symbol : positionals[2];
|
|
1712
|
+
const interval = typeof flags.interval === "string" ? flags.interval : positionals[3];
|
|
1713
|
+
if (!symbol || !interval)
|
|
1714
|
+
throw new Error("Missing symbol/interval. Example: pionex-trade-cli market klines BTC_USDT 60M");
|
|
1715
|
+
const endTime = flags.endTime != null ? Number(flags.endTime) : void 0;
|
|
1716
|
+
const limit = flags.limit != null ? Number(flags.limit) : void 0;
|
|
1717
|
+
const out = await runTool("pionex_market_get_klines", { symbol, interval, endTime, limit });
|
|
1718
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
throw new Error(`Unknown market command: ${command}`);
|
|
1722
|
+
}
|
|
1723
|
+
if (group === "account") {
|
|
1724
|
+
if (command === "balance") {
|
|
1725
|
+
const out = await runTool("pionex_account_get_balance", {});
|
|
1726
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
throw new Error(`Unknown account command: ${command}`);
|
|
1730
|
+
}
|
|
1731
|
+
if (group === "orders") {
|
|
1732
|
+
if (command === "new") {
|
|
1733
|
+
const symbol = typeof flags.symbol === "string" ? flags.symbol : void 0;
|
|
1734
|
+
const side = typeof flags.side === "string" ? flags.side : void 0;
|
|
1735
|
+
const type = typeof flags.type === "string" ? flags.type : void 0;
|
|
1736
|
+
const clientOrderId = typeof flags["client-order-id"] === "string" ? flags["client-order-id"] : typeof flags.clientOrderId === "string" ? flags.clientOrderId : void 0;
|
|
1737
|
+
const size = typeof flags.size === "string" ? flags.size : void 0;
|
|
1738
|
+
const price = typeof flags.price === "string" ? flags.price : void 0;
|
|
1739
|
+
const amount = typeof flags.amount === "string" ? flags.amount : void 0;
|
|
1740
|
+
const IOC = typeof flags.IOC === "boolean" ? flags.IOC : void 0;
|
|
1741
|
+
if (!symbol || !side || !type) throw new Error("Missing required flags: --symbol --side --type");
|
|
1742
|
+
const payload = { symbol, side, type, clientOrderId, size, price, amount, IOC };
|
|
1743
|
+
if (dryRun) {
|
|
1744
|
+
process.stdout.write(JSON.stringify({ tool: "pionex_orders_new_order", args: payload }, null, 2) + "\n");
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
const out = await runTool("pionex_orders_new_order", payload);
|
|
1748
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
if (command === "get") {
|
|
1752
|
+
const symbol = typeof flags.symbol === "string" ? flags.symbol : void 0;
|
|
1753
|
+
const orderId = flags["order-id"] != null ? Number(flags["order-id"]) : void 0;
|
|
1754
|
+
if (!symbol || orderId == null) throw new Error("Missing required flags: --symbol --order-id");
|
|
1755
|
+
const out = await runTool("pionex_orders_get_order", { symbol, orderId });
|
|
1756
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
if (command === "open") {
|
|
1760
|
+
const symbol = typeof flags.symbol === "string" ? flags.symbol : void 0;
|
|
1761
|
+
if (!symbol) throw new Error("Missing required flag: --symbol");
|
|
1762
|
+
const out = await runTool("pionex_orders_get_open_orders", { symbol });
|
|
1763
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
if (command === "all") {
|
|
1767
|
+
const symbol = typeof flags.symbol === "string" ? flags.symbol : void 0;
|
|
1768
|
+
const limit = flags.limit != null ? Number(flags.limit) : void 0;
|
|
1769
|
+
if (!symbol) throw new Error("Missing required flag: --symbol");
|
|
1770
|
+
const out = await runTool("pionex_orders_get_all_orders", { symbol, limit });
|
|
1771
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
if (command === "fills") {
|
|
1775
|
+
const symbol = typeof flags.symbol === "string" ? flags.symbol : void 0;
|
|
1776
|
+
const startTime = flags.startTime != null ? Number(flags.startTime) : void 0;
|
|
1777
|
+
const endTime = flags.endTime != null ? Number(flags.endTime) : void 0;
|
|
1778
|
+
if (!symbol) throw new Error("Missing required flag: --symbol");
|
|
1779
|
+
const out = await runTool("pionex_orders_get_fills", { symbol, startTime, endTime });
|
|
1780
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
if (command === "cancel") {
|
|
1784
|
+
const symbol = typeof flags.symbol === "string" ? flags.symbol : void 0;
|
|
1785
|
+
const orderId = flags["order-id"] != null ? Number(flags["order-id"]) : void 0;
|
|
1786
|
+
if (!symbol || orderId == null) throw new Error("Missing required flags: --symbol --order-id");
|
|
1787
|
+
const payload = { symbol, orderId };
|
|
1788
|
+
if (dryRun) {
|
|
1789
|
+
process.stdout.write(JSON.stringify({ tool: "pionex_orders_cancel_order", args: payload }, null, 2) + "\n");
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
const out = await runTool("pionex_orders_cancel_order", payload);
|
|
1793
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
if (command === "cancel-all") {
|
|
1797
|
+
const symbol = typeof flags.symbol === "string" ? flags.symbol : void 0;
|
|
1798
|
+
if (!symbol) throw new Error("Missing required flag: --symbol");
|
|
1799
|
+
const payload = { symbol };
|
|
1800
|
+
if (dryRun) {
|
|
1801
|
+
process.stdout.write(JSON.stringify({ tool: "pionex_orders_cancel_all_orders", args: payload }, null, 2) + "\n");
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
const out = await runTool("pionex_orders_cancel_all_orders", payload);
|
|
1805
|
+
process.stdout.write(JSON.stringify(out.data, null, 2) + "\n");
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
throw new Error(`Unknown orders command: ${command}`);
|
|
1809
|
+
}
|
|
1810
|
+
throw new Error(`Unknown group: ${group}`);
|
|
1811
|
+
}
|
|
1812
|
+
function main() {
|
|
1813
|
+
const invokedAs = basename(process.argv[1] || "");
|
|
1814
|
+
const cmd = process.argv[2];
|
|
1815
|
+
if (invokedAs.includes("pionex-ai-kit")) {
|
|
1816
|
+
if (cmd === "onboard") {
|
|
1817
|
+
cmdOnboard().catch((e) => {
|
|
1818
|
+
process.stderr.write(String(e) + "\n");
|
|
1819
|
+
process.exit(1);
|
|
1820
|
+
});
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
if (cmd === "setup") {
|
|
1824
|
+
cmdSetup(process.argv.slice(3));
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
if (cmd === "help" || cmd === "--help" || cmd === "-h" || !cmd) {
|
|
1828
|
+
printHelp();
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
process.stderr.write("Unknown command: " + cmd + ". Run 'pionex-ai-kit help'.\n");
|
|
1832
|
+
process.exit(1);
|
|
1111
1833
|
return;
|
|
1112
1834
|
}
|
|
1113
|
-
process.
|
|
1114
|
-
|
|
1835
|
+
runPionexCommand(process.argv.slice(2)).catch((e) => {
|
|
1836
|
+
const payload = toToolErrorPayload(e);
|
|
1837
|
+
process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
|
|
1838
|
+
process.exit(1);
|
|
1839
|
+
});
|
|
1115
1840
|
}
|
|
1116
1841
|
main();
|
|
1117
1842
|
/*! Bundled license information:
|