@onebullex-group/cli 1.4.0 → 1.5.0
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/CHANGELOG.md +38 -0
- package/README.md +51 -10
- package/dist/{chunk-KS5FFAKW.js → chunk-NMW73ILO.js} +25 -1
- package/dist/chunk-SGLPG2FL.js +39 -0
- package/dist/{chunk-3XZDH7DV.js → chunk-YE5LER6U.js} +84 -17
- package/dist/core/download.d.ts +41 -0
- package/dist/core/download.js +15 -0
- package/dist/core/http-client.d.ts +32 -1
- package/dist/core/http-client.js +8 -4
- package/dist/index.js +109 -30
- package/dist/lint-api.d.ts +1 -1
- package/dist/lint-api.js +21 -6
- package/dist/registry.js +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,44 @@ Rename only — no functional changes from 1.1.1.
|
|
|
14
14
|
`@onebullex-group/cli@1.1.2` (identical contents).
|
|
15
15
|
- Existing 1.1.0 / 1.1.1 were never published.
|
|
16
16
|
|
|
17
|
+
## 1.5.0 — 2026-10-07
|
|
18
|
+
|
|
19
|
+
Syncs V2 doc updates since 1.4.0 (Java SDK: cursor bills, async position
|
|
20
|
+
export, system user-info/download, public index-price). Registry 65 → 68.
|
|
21
|
+
|
|
22
|
+
- `position history-async` — GET `/v2/position/history/async`. Returns
|
|
23
|
+
`downloadId`. Local 1-year range check so an over-wide request cannot burn
|
|
24
|
+
one of the 5 monthly export slots.
|
|
25
|
+
- `system user-info` — GET `/v2/system/user/info`. New `system` command group.
|
|
26
|
+
- `system download <downloadId>` — GET `/v2/system/download`. Writes the Excel
|
|
27
|
+
stream to disk (`--out` file or directory; default `~/.onebullex/downloads/`)
|
|
28
|
+
and prints `{path, bytes, downloadId}`. New `core/download.ts`.
|
|
29
|
+
- `HttpClient.getPrivateBinary` — JSON business errors (`file_not_ready`, …)
|
|
30
|
+
still throw `OneBullExError`; a file stream is returned as bytes. Matches
|
|
31
|
+
Java `BaseClientV2.executeGetBytes`.
|
|
32
|
+
- `balance bills` switched from `page`/`size` to cursor `id`/`direction`/`limit`
|
|
33
|
+
plus optional `--symbol`.
|
|
34
|
+
- `index-price get` is public again (docs dropped the auth requirement).
|
|
35
|
+
- Breaking: ticker commands renamed `--time-range` → required
|
|
36
|
+
`--time-range-type` (API field `timeRangeType`). The old flag name was never
|
|
37
|
+
sent as `timeRangeType` on the MCP path, so live calls got `invalid param`.
|
|
38
|
+
- `HttpClient` upper-cases the `direction` query param before signing. The
|
|
39
|
+
server is case-sensitive (`next` → `invalid_direction`); done in the client so
|
|
40
|
+
MCP calls, which skip commander actions, get it too.
|
|
41
|
+
- `depth get --level` and `order list-unfinished --direction` are now required
|
|
42
|
+
(the API already rejected the request without them).
|
|
43
|
+
- `entrust create-plan --stop-price` is required (server always demanded it).
|
|
44
|
+
- Help text: `positionSide` is LONG/SHORT (no BOTH/NET); plan trigger types
|
|
45
|
+
are MARK_PRICE/LATEST_PRICE; origQty is integer contracts.
|
|
46
|
+
- Fix: `config init --yes --api-key/--secret/--base-url` silently dropped those
|
|
47
|
+
values (commander bound them to the same-named root options). Added
|
|
48
|
+
`--no-set-default` so a second profile can be added without changing the
|
|
49
|
+
default.
|
|
50
|
+
- Docs: README "多个 API Key(多 profile)" section.
|
|
51
|
+
- Coverage lint: CRLF-safe `parseDocParams` (Windows checkouts were silently
|
|
52
|
+
skipping the parameter lint); scoped `CLI_ONLY_PARAMS` (`--out`) and
|
|
53
|
+
positional aliases (`plan-get <id>` → `entrustId`).
|
|
54
|
+
|
|
17
55
|
## 1.4.0 — 2026-08-26
|
|
18
56
|
|
|
19
57
|
Syncs the three V2 endpoints added to the Java SDK since 1.3.1 (commits
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
OneBullEx Open API V2 的命令行工具。
|
|
4
4
|
|
|
5
|
-
> 当前版本(1.
|
|
5
|
+
> 当前版本(1.5.0)覆盖 V2 文档的全部 68 个端点:22 个公开 + 25 个私有只读 + 21 个写。所有写命令默认 dry-run,需 `--confirm` 才真发请求。
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -50,24 +50,24 @@ onebullex position list
|
|
|
50
50
|
|
|
51
51
|
完整命令列表跟 V2 文档逐项对齐,可用 `onebullex <group> --help` 查询。
|
|
52
52
|
|
|
53
|
-
### 公开(
|
|
53
|
+
### 公开(22)
|
|
54
54
|
|
|
55
55
|
```
|
|
56
56
|
public time GET /v2/public/time
|
|
57
57
|
symbol list / get / coins / all
|
|
58
|
-
ticker get / list / agg / agg-list
|
|
59
|
-
depth get
|
|
58
|
+
ticker get / list / agg / agg-list (`--time-range-type` 必填,如 H24 / UTC_P_8)
|
|
59
|
+
depth get (`--level` 必填,1–50)
|
|
60
60
|
deal list
|
|
61
61
|
kline list
|
|
62
62
|
mark-price list / get
|
|
63
|
-
index-price list / get
|
|
63
|
+
index-price list / get
|
|
64
64
|
funding rate / history
|
|
65
65
|
risk-balance list
|
|
66
66
|
open-interest get
|
|
67
67
|
leverage brackets-list / brackets-get
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
### 私有只读(
|
|
70
|
+
### 私有只读(25)
|
|
71
71
|
|
|
72
72
|
```
|
|
73
73
|
listen-key get
|
|
@@ -77,7 +77,8 @@ order list-unfinished / list-unfinished-multi / list / list-by-ids /
|
|
|
77
77
|
list-history / get / trades
|
|
78
78
|
entrust plan-list / plan-history / plan-get / profit-list / profit-get
|
|
79
79
|
order-entrust list
|
|
80
|
-
position list
|
|
80
|
+
position list / confs / history / history-async
|
|
81
|
+
system user-info / download
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
### 写操作(21)
|
|
@@ -99,11 +100,11 @@ position adjust-leverage / margin / auto-margin /
|
|
|
99
100
|
```bash
|
|
100
101
|
# dry-run(默认,退出码 4,preview 在 stdout)
|
|
101
102
|
onebullex order create --symbol btc_usdt --order-type LIMIT \
|
|
102
|
-
--order-side BUY --position-side LONG --orig-qty
|
|
103
|
+
--order-side BUY --position-side LONG --orig-qty 1 --price 50000
|
|
103
104
|
|
|
104
105
|
# 真正下单
|
|
105
106
|
onebullex order create --symbol btc_usdt --order-type LIMIT \
|
|
106
|
-
--order-side BUY --position-side LONG --orig-qty
|
|
107
|
+
--order-side BUY --position-side LONG --orig-qty 1 --price 50000 \
|
|
107
108
|
--confirm
|
|
108
109
|
```
|
|
109
110
|
|
|
@@ -130,7 +131,7 @@ onebullex balance transfer --from FUNDING --to CONTRACT --amount 500 \
|
|
|
130
131
|
### 配置管理(6)
|
|
131
132
|
|
|
132
133
|
```
|
|
133
|
-
config init
|
|
134
|
+
config init 交互式向导;新增 profile,--no-set-default 不改默认
|
|
134
135
|
config set <key> 仅 base-url / identify / env 可用 --value
|
|
135
136
|
api-key / secret 必须用 --stdin
|
|
136
137
|
config show [--reveal] 默认脱敏,加 --reveal 后明文(带 stderr 警告)
|
|
@@ -161,6 +162,46 @@ config remove <p> 删除 profile(防止删掉最后一个)
|
|
|
161
162
|
|
|
162
163
|
可通过 `ONEBULLEX_CONFIG_PATH` 环境变量覆盖路径(脚本/CI 友好)。
|
|
163
164
|
|
|
165
|
+
## 多个 API Key(多 profile)
|
|
166
|
+
|
|
167
|
+
一个 profile 对应一套 API Key。同一台电脑要用多个 Key,就在同一个配置文件里建多个 profile,调用时选一个。
|
|
168
|
+
|
|
169
|
+
**添加 profile**:每次 `config init` 新增(或覆盖同名)一个 profile,已有的 profile 保留。默认会把新 profile 设为默认,加 `--no-set-default` 则不改默认。
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
onebullex config init # 交互式:Profile name 填 alice,按提示填 key/secret
|
|
173
|
+
onebullex config init --no-set-default # 再加一个:Profile name 填 bob,默认仍是 alice
|
|
174
|
+
onebullex config list-profiles # 带 * 的是默认 profile
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
非交互(脚本)时,key/secret 建议用 `--stdin` 写入,不进 shell history:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
onebullex config init --yes --no-set-default --name bob --env prod
|
|
181
|
+
onebullex --profile bob config set api-key --stdin # 粘贴后 Ctrl+D(Windows: Ctrl+Z 回车)
|
|
182
|
+
onebullex --profile bob config set secret --stdin
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**选用 profile**(优先级从高到低):
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
onebullex --profile bob balance list # 单条命令临时切换,不改默认
|
|
189
|
+
ONEBULLEX_PROFILE=bob onebullex balance list # 当前 shell / 脚本内生效
|
|
190
|
+
onebullex config use bob # 改默认 profile(影响之后所有未指定的调用)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
多个终端或多个 Claude 会话同时跑时,**用 `--profile` 或 `ONEBULLEX_PROFILE`,不要靠 `config use` 来回切**:默认 profile 存在共享的配置文件里,一边切换,另一边下一次调用就会用错账户。
|
|
194
|
+
|
|
195
|
+
需要完全隔离(例如每个账户一份文件、不同权限的机器用户)时,用 `ONEBULLEX_CONFIG_PATH` 指向不同的配置文件:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
ONEBULLEX_CONFIG_PATH=~/.onebullex/alice.json onebullex balance list
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
profile 名写错时会报 `未找到 baseUrl 配置`,先用 `config list-profiles` 核对名字。
|
|
202
|
+
|
|
203
|
+
MCP 场景下怎么同时连多个账户,见 [mcp/README.md](../mcp/README.md#多个-api-key多账户)。
|
|
204
|
+
|
|
164
205
|
## 优先级与全局 flag
|
|
165
206
|
|
|
166
207
|
每次调用按以下顺序解析:
|
|
@@ -128,7 +128,7 @@ var REGISTRY = [
|
|
|
128
128
|
method: "GET",
|
|
129
129
|
path: "/v2/public/q/symbol-index-price",
|
|
130
130
|
isWrite: false,
|
|
131
|
-
needsAuth:
|
|
131
|
+
needsAuth: false,
|
|
132
132
|
implFile: "src/commands/index-price/get.ts"
|
|
133
133
|
},
|
|
134
134
|
{
|
|
@@ -356,6 +356,30 @@ var REGISTRY = [
|
|
|
356
356
|
needsAuth: true,
|
|
357
357
|
implFile: "src/commands/position/history.ts"
|
|
358
358
|
},
|
|
359
|
+
{
|
|
360
|
+
command: "position history-async",
|
|
361
|
+
method: "GET",
|
|
362
|
+
path: "/v2/position/history/async",
|
|
363
|
+
isWrite: false,
|
|
364
|
+
needsAuth: true,
|
|
365
|
+
implFile: "src/commands/position/history-async.ts"
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
command: "system user-info",
|
|
369
|
+
method: "GET",
|
|
370
|
+
path: "/v2/system/user/info",
|
|
371
|
+
isWrite: false,
|
|
372
|
+
needsAuth: true,
|
|
373
|
+
implFile: "src/commands/system/user-info.ts"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
command: "system download",
|
|
377
|
+
method: "GET",
|
|
378
|
+
path: "/v2/system/download",
|
|
379
|
+
isWrite: false,
|
|
380
|
+
needsAuth: true,
|
|
381
|
+
implFile: "src/commands/system/download.ts"
|
|
382
|
+
},
|
|
359
383
|
// ───── Phase 2: write operations ─────
|
|
360
384
|
{
|
|
361
385
|
command: "balance transfer",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/download.ts
|
|
4
|
+
import { existsSync, mkdirSync, statSync, writeFileSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { dirname, join, resolve } from "path";
|
|
7
|
+
var DOWNLOAD_PATH = "/v2/system/download";
|
|
8
|
+
var DEFAULT_DOWNLOAD_DIR = join(homedir(), ".onebullex", "downloads");
|
|
9
|
+
function defaultFilename(downloadId) {
|
|
10
|
+
return `position_history_${downloadId.replace(/[^\w.-]/g, "_")}.xlsx`;
|
|
11
|
+
}
|
|
12
|
+
function resolveTarget(out, filename, cwd = process.cwd()) {
|
|
13
|
+
if (!out) return join(DEFAULT_DOWNLOAD_DIR, filename);
|
|
14
|
+
const abs = resolve(cwd, out);
|
|
15
|
+
const looksLikeDir = /[\\/]$/.test(out) || existsSync(abs) && statSync(abs).isDirectory();
|
|
16
|
+
return looksLikeDir ? join(abs, filename) : abs;
|
|
17
|
+
}
|
|
18
|
+
async function downloadToFile(opts) {
|
|
19
|
+
const downloadId = opts.downloadId.trim();
|
|
20
|
+
if (!downloadId) throw new Error("downloadId is required");
|
|
21
|
+
const file = await opts.client.getPrivateBinary(DOWNLOAD_PATH, { downloadId });
|
|
22
|
+
const target = resolveTarget(opts.out, file.filename ?? defaultFilename(downloadId), opts.cwd);
|
|
23
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
24
|
+
writeFileSync(target, file.bytes);
|
|
25
|
+
return {
|
|
26
|
+
downloadId,
|
|
27
|
+
path: target,
|
|
28
|
+
bytes: file.bytes.length,
|
|
29
|
+
contentType: file.contentType
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
DOWNLOAD_PATH,
|
|
35
|
+
DEFAULT_DOWNLOAD_DIR,
|
|
36
|
+
defaultFilename,
|
|
37
|
+
resolveTarget,
|
|
38
|
+
downloadToFile
|
|
39
|
+
};
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
generateNonce,
|
|
4
4
|
sign
|
|
5
5
|
} from "./chunk-LVNSFQXS.js";
|
|
6
|
-
import {
|
|
7
|
-
OneBullExError
|
|
8
|
-
} from "./chunk-JRUFIHU7.js";
|
|
9
6
|
import {
|
|
10
7
|
redactObject
|
|
11
8
|
} from "./chunk-OOLZIYXS.js";
|
|
9
|
+
import {
|
|
10
|
+
OneBullExError
|
|
11
|
+
} from "./chunk-JRUFIHU7.js";
|
|
12
12
|
|
|
13
13
|
// src/core/http-client.ts
|
|
14
14
|
function flattenForSign(body) {
|
|
@@ -46,6 +46,37 @@ var HttpClient = class {
|
|
|
46
46
|
async postPrivate(path, body) {
|
|
47
47
|
return this.send("POST", path, void 0, body, true, false);
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* 签名 GET,返回原始字节(用于 /v2/system/download 这类文件流接口)。
|
|
51
|
+
*
|
|
52
|
+
* 业务错误(文件未就绪、次数超限等)仍以 JSON 返回,此时按普通响应校验并
|
|
53
|
+
* 抛 OneBullExError;对齐 Java 端 BaseClientV2.executeWithRetryBytes。
|
|
54
|
+
*/
|
|
55
|
+
async getPrivateBinary(path, query) {
|
|
56
|
+
return this.withRetry(async () => {
|
|
57
|
+
const res = await this.request("GET", path, query, void 0, true);
|
|
58
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
59
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
60
|
+
if (isJsonBody(contentType, bytes)) {
|
|
61
|
+
const json = JSON.parse(new TextDecoder().decode(bytes));
|
|
62
|
+
this.unwrap(json, res.status, path);
|
|
63
|
+
throw new OneBullExError(
|
|
64
|
+
"Expected a file stream but the server returned a JSON success body",
|
|
65
|
+
"UNEXPECTED_JSON",
|
|
66
|
+
res.status,
|
|
67
|
+
path
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (this.cfg.debug) {
|
|
71
|
+
console.error("[debug response]", `${bytes.length} bytes (${contentType})`);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
bytes,
|
|
75
|
+
contentType,
|
|
76
|
+
filename: parseFilename(res.headers.get("content-disposition"))
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
49
80
|
/**
|
|
50
81
|
* GET 请求重试包装:网络错误最多重试 2 次(共 3 次尝试),延迟 300ms / 900ms。
|
|
51
82
|
* OneBullExError(业务错误)不重试。
|
|
@@ -66,12 +97,34 @@ var HttpClient = class {
|
|
|
66
97
|
throw lastErr;
|
|
67
98
|
}
|
|
68
99
|
async send(method, path, query, body, requireAuth, raw) {
|
|
100
|
+
const res = await this.request(method, path, query, body, requireAuth);
|
|
101
|
+
const json = await res.json();
|
|
102
|
+
if (this.cfg.debug) {
|
|
103
|
+
console.error("[debug response]", JSON.stringify(redactObject(json)));
|
|
104
|
+
}
|
|
105
|
+
if (raw) {
|
|
106
|
+
return json;
|
|
107
|
+
}
|
|
108
|
+
return this.unwrap(json, res.status, path);
|
|
109
|
+
}
|
|
110
|
+
/** Normalize: V2 API uses "code"/"msg"; some endpoints use "returnCode"/"msgInfo" */
|
|
111
|
+
unwrap(json, status, path) {
|
|
112
|
+
const returnCode = json.code ?? json.returnCode;
|
|
113
|
+
const msgInfo = json.msg ?? json.msgInfo ?? "";
|
|
114
|
+
if (returnCode !== 0 && returnCode !== "0") {
|
|
115
|
+
throw new OneBullExError(msgInfo, returnCode ?? "UNKNOWN", status, path);
|
|
116
|
+
}
|
|
117
|
+
return json.data;
|
|
118
|
+
}
|
|
119
|
+
/** 签名 + 发送,返回 HTTP 层成功(2xx)的 Response,由调用方决定如何解析 body。 */
|
|
120
|
+
async request(method, path, query, body, requireAuth) {
|
|
69
121
|
const headers = {
|
|
70
122
|
"Content-Type": "application/json",
|
|
71
123
|
Accept: "application/json",
|
|
72
124
|
"User-Agent": "@onebullex-group/cli"
|
|
73
125
|
};
|
|
74
126
|
let url = `${this.cfg.baseUrl}${path}`;
|
|
127
|
+
if (method === "GET") query = normalizeQuery(query);
|
|
75
128
|
const params = method === "GET" ? { ...query ?? {} } : flattenForSign(body);
|
|
76
129
|
if (requireAuth) {
|
|
77
130
|
if (!this.cfg.apiKey || !this.cfg.secret) {
|
|
@@ -124,23 +177,37 @@ var HttpClient = class {
|
|
|
124
177
|
path
|
|
125
178
|
);
|
|
126
179
|
}
|
|
127
|
-
|
|
128
|
-
if (this.cfg.debug) {
|
|
129
|
-
console.error("[debug response]", JSON.stringify(redactObject(json)));
|
|
130
|
-
}
|
|
131
|
-
if (raw) {
|
|
132
|
-
return json;
|
|
133
|
-
}
|
|
134
|
-
const returnCode = json.code ?? json.returnCode;
|
|
135
|
-
const msgInfo = json.msg ?? json.msgInfo ?? "";
|
|
136
|
-
if (returnCode !== 0 && returnCode !== "0") {
|
|
137
|
-
throw new OneBullExError(msgInfo, returnCode ?? "UNKNOWN", res.status, path);
|
|
138
|
-
}
|
|
139
|
-
return json.data;
|
|
180
|
+
return res;
|
|
140
181
|
}
|
|
141
182
|
};
|
|
183
|
+
function normalizeQuery(query) {
|
|
184
|
+
if (!query || typeof query.direction !== "string") return query;
|
|
185
|
+
return { ...query, direction: query.direction.toUpperCase() };
|
|
186
|
+
}
|
|
187
|
+
function isJsonBody(contentType, bytes) {
|
|
188
|
+
if (contentType.toLowerCase().includes("json")) return true;
|
|
189
|
+
let i = 0;
|
|
190
|
+
while (i < bytes.length && /\s/.test(String.fromCharCode(bytes[i] ?? 0))) i++;
|
|
191
|
+
return i < bytes.length && bytes[i] === 123;
|
|
192
|
+
}
|
|
193
|
+
function parseFilename(disposition) {
|
|
194
|
+
if (!disposition) return void 0;
|
|
195
|
+
const star = /filename\*\s*=\s*(?:[\w-]+'[^']*')?([^;]+)/i.exec(disposition);
|
|
196
|
+
const plain = /filename\s*=\s*"?([^";]+)"?/i.exec(disposition);
|
|
197
|
+
let name = star?.[1] ?? plain?.[1];
|
|
198
|
+
if (!name) return void 0;
|
|
199
|
+
try {
|
|
200
|
+
name = decodeURIComponent(name.trim());
|
|
201
|
+
} catch {
|
|
202
|
+
name = name.trim();
|
|
203
|
+
}
|
|
204
|
+
const base = name.split(/[\\/]/).pop()?.trim();
|
|
205
|
+
return base && base !== "." && base !== ".." ? base : void 0;
|
|
206
|
+
}
|
|
142
207
|
|
|
143
208
|
export {
|
|
144
209
|
flattenForSign,
|
|
145
|
-
HttpClient
|
|
210
|
+
HttpClient,
|
|
211
|
+
normalizeQuery,
|
|
212
|
+
parseFilename
|
|
146
213
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { HttpClient } from './http-client.js';
|
|
2
|
+
import './types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 异步导出文件下载(`GET /v2/system/download`)。
|
|
6
|
+
*
|
|
7
|
+
* 接口成功时返回文件流(Excel),未就绪/失败/超限时返回 JSON 错误。CLI 命令与
|
|
8
|
+
* MCP 工具共用这里的落盘逻辑:MCP 不能把二进制塞进 JSON 文本结果,只能写到本地
|
|
9
|
+
* 再把路径返回给 AI。
|
|
10
|
+
*
|
|
11
|
+
* 限制提醒:同一 downloadId 一小时内最多成功下载 5 次 —— 每次调用都会消耗额度,
|
|
12
|
+
* 不要用它来"轮询"任务是否完成。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
declare const DOWNLOAD_PATH = "/v2/system/download";
|
|
16
|
+
/** `--out` 省略时的落盘目录 */
|
|
17
|
+
declare const DEFAULT_DOWNLOAD_DIR: string;
|
|
18
|
+
interface DownloadResult {
|
|
19
|
+
downloadId: string;
|
|
20
|
+
/** 实际写入的绝对路径 */
|
|
21
|
+
path: string;
|
|
22
|
+
bytes: number;
|
|
23
|
+
contentType: string;
|
|
24
|
+
}
|
|
25
|
+
/** 服务端未给出 Content-Disposition 时的文件名,与 Java 端 downloadToFile 一致 */
|
|
26
|
+
declare function defaultFilename(downloadId: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* 解析最终落盘路径:
|
|
29
|
+
* - 未给 out → DEFAULT_DOWNLOAD_DIR/<filename>
|
|
30
|
+
* - out 是已存在的目录,或以 / \ 结尾 → out/<filename>
|
|
31
|
+
* - 否则 out 视为文件路径
|
|
32
|
+
*/
|
|
33
|
+
declare function resolveTarget(out: string | undefined, filename: string, cwd?: string): string;
|
|
34
|
+
declare function downloadToFile(opts: {
|
|
35
|
+
client: HttpClient;
|
|
36
|
+
downloadId: string;
|
|
37
|
+
out?: string;
|
|
38
|
+
cwd?: string;
|
|
39
|
+
}): Promise<DownloadResult>;
|
|
40
|
+
|
|
41
|
+
export { DEFAULT_DOWNLOAD_DIR, DOWNLOAD_PATH, type DownloadResult, defaultFilename, downloadToFile, resolveTarget };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_DOWNLOAD_DIR,
|
|
4
|
+
DOWNLOAD_PATH,
|
|
5
|
+
defaultFilename,
|
|
6
|
+
downloadToFile,
|
|
7
|
+
resolveTarget
|
|
8
|
+
} from "../chunk-SGLPG2FL.js";
|
|
9
|
+
export {
|
|
10
|
+
DEFAULT_DOWNLOAD_DIR,
|
|
11
|
+
DOWNLOAD_PATH,
|
|
12
|
+
defaultFilename,
|
|
13
|
+
downloadToFile,
|
|
14
|
+
resolveTarget
|
|
15
|
+
};
|
|
@@ -20,12 +20,43 @@ declare class HttpClient {
|
|
|
20
20
|
raw?: boolean;
|
|
21
21
|
}): Promise<T>;
|
|
22
22
|
postPrivate<T>(path: string, body?: Record<string, unknown>): Promise<T>;
|
|
23
|
+
/**
|
|
24
|
+
* 签名 GET,返回原始字节(用于 /v2/system/download 这类文件流接口)。
|
|
25
|
+
*
|
|
26
|
+
* 业务错误(文件未就绪、次数超限等)仍以 JSON 返回,此时按普通响应校验并
|
|
27
|
+
* 抛 OneBullExError;对齐 Java 端 BaseClientV2.executeWithRetryBytes。
|
|
28
|
+
*/
|
|
29
|
+
getPrivateBinary(path: string, query?: Query): Promise<BinaryResponse>;
|
|
23
30
|
/**
|
|
24
31
|
* GET 请求重试包装:网络错误最多重试 2 次(共 3 次尝试),延迟 300ms / 900ms。
|
|
25
32
|
* OneBullExError(业务错误)不重试。
|
|
26
33
|
*/
|
|
27
34
|
private withRetry;
|
|
28
35
|
private send;
|
|
36
|
+
/** Normalize: V2 API uses "code"/"msg"; some endpoints use "returnCode"/"msgInfo" */
|
|
37
|
+
private unwrap;
|
|
38
|
+
/** 签名 + 发送,返回 HTTP 层成功(2xx)的 Response,由调用方决定如何解析 body。 */
|
|
39
|
+
private request;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* GET query 归一化,在签名之前执行(签名与 URL 用同一份值)。
|
|
43
|
+
*
|
|
44
|
+
* `direction` 在所有 V2 GET 接口里都是大写枚举(游标 `NEXT`/`PREV`,
|
|
45
|
+
* listUnfinished 的 `BUY`/`SELL`),服务端区分大小写,`next` 会返回
|
|
46
|
+
* `invalid_direction`。放在这里而不是各命令里,是因为 MCP 路径不经过
|
|
47
|
+
* commander action。
|
|
48
|
+
*/
|
|
49
|
+
declare function normalizeQuery(query: Query | undefined): Query | undefined;
|
|
50
|
+
interface BinaryResponse {
|
|
51
|
+
bytes: Uint8Array;
|
|
52
|
+
contentType: string;
|
|
53
|
+
/** 从 Content-Disposition 解析出的文件名;服务端未给出时为 undefined */
|
|
54
|
+
filename?: string;
|
|
29
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* 解析 `attachment; filename="x.xlsx"` / `filename*=UTF-8''x.xlsx`。
|
|
58
|
+
* 只取 basename,防止服务端给出 `../` 之类的路径。
|
|
59
|
+
*/
|
|
60
|
+
declare function parseFilename(disposition: string | null): string | undefined;
|
|
30
61
|
|
|
31
|
-
export { type FetchFn, HttpClient, type Query, flattenForSign };
|
|
62
|
+
export { type BinaryResponse, type FetchFn, HttpClient, type Query, flattenForSign, normalizeQuery, parseFilename };
|
package/dist/core/http-client.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
HttpClient,
|
|
4
|
-
flattenForSign
|
|
5
|
-
|
|
4
|
+
flattenForSign,
|
|
5
|
+
normalizeQuery,
|
|
6
|
+
parseFilename
|
|
7
|
+
} from "../chunk-YE5LER6U.js";
|
|
6
8
|
import "../chunk-LVNSFQXS.js";
|
|
7
|
-
import "../chunk-JRUFIHU7.js";
|
|
8
9
|
import "../chunk-OOLZIYXS.js";
|
|
9
10
|
import "../chunk-ZYO5P4DH.js";
|
|
11
|
+
import "../chunk-JRUFIHU7.js";
|
|
10
12
|
export {
|
|
11
13
|
HttpClient,
|
|
12
|
-
flattenForSign
|
|
14
|
+
flattenForSign,
|
|
15
|
+
normalizeQuery,
|
|
16
|
+
parseFilename
|
|
13
17
|
};
|
package/dist/index.js
CHANGED
|
@@ -14,20 +14,23 @@ import {
|
|
|
14
14
|
resolveConfig,
|
|
15
15
|
writeConfigFile
|
|
16
16
|
} from "./chunk-AKGCHJPQ.js";
|
|
17
|
+
import {
|
|
18
|
+
downloadToFile
|
|
19
|
+
} from "./chunk-SGLPG2FL.js";
|
|
17
20
|
import {
|
|
18
21
|
HttpClient
|
|
19
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-YE5LER6U.js";
|
|
20
23
|
import "./chunk-LVNSFQXS.js";
|
|
24
|
+
import {
|
|
25
|
+
redactSecret
|
|
26
|
+
} from "./chunk-OOLZIYXS.js";
|
|
27
|
+
import "./chunk-ZYO5P4DH.js";
|
|
21
28
|
import {
|
|
22
29
|
ConfigError,
|
|
23
30
|
DryRunInterception,
|
|
24
31
|
EXIT,
|
|
25
32
|
OneBullExError
|
|
26
33
|
} from "./chunk-JRUFIHU7.js";
|
|
27
|
-
import {
|
|
28
|
-
redactSecret
|
|
29
|
-
} from "./chunk-OOLZIYXS.js";
|
|
30
|
-
import "./chunk-ZYO5P4DH.js";
|
|
31
34
|
|
|
32
35
|
// src/index.ts
|
|
33
36
|
import { Command } from "commander";
|
|
@@ -116,11 +119,11 @@ function prune(o) {
|
|
|
116
119
|
return out;
|
|
117
120
|
}
|
|
118
121
|
function registerTickerGet(parent, ctx) {
|
|
119
|
-
parent.command("get <symbol>").description("Get ticker for a symbol (GET /v2/public/q/ticker)").
|
|
122
|
+
parent.command("get <symbol>").description("Get ticker for a symbol (GET /v2/public/q/ticker)").requiredOption("--time-range-type <type>", "H24, or calendar-day window UTC_P_0..UTC_P_12 / UTC_N_1..UTC_N_12 (e.g. UTC_P_8)").action(async (symbol, opts) => {
|
|
120
123
|
const c = ctx.http({ requireAuth: false });
|
|
121
124
|
const data = await c.getPublic(
|
|
122
125
|
"/v2/public/q/ticker",
|
|
123
|
-
prune({ symbol, timeRangeType: opts.
|
|
126
|
+
prune({ symbol, timeRangeType: opts.timeRangeType })
|
|
124
127
|
);
|
|
125
128
|
const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
|
|
126
129
|
ctx.stdout(
|
|
@@ -141,11 +144,11 @@ function prune2(o) {
|
|
|
141
144
|
return out;
|
|
142
145
|
}
|
|
143
146
|
function registerTickerList(parent, ctx) {
|
|
144
|
-
parent.command("list").description("List all tickers (GET /v2/public/q/tickers)").requiredOption("--time-range <type>", "
|
|
147
|
+
parent.command("list").description("List all tickers (GET /v2/public/q/tickers)").requiredOption("--time-range-type <type>", "H24, or calendar-day window UTC_P_0..UTC_P_12 / UTC_N_1..UTC_N_12 (e.g. UTC_P_8)").action(async (opts) => {
|
|
145
148
|
const c = ctx.http({ requireAuth: false });
|
|
146
149
|
const data = await c.getPublic(
|
|
147
150
|
"/v2/public/q/tickers",
|
|
148
|
-
prune2({ timeRangeType: opts.
|
|
151
|
+
prune2({ timeRangeType: opts.timeRangeType })
|
|
149
152
|
);
|
|
150
153
|
const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
|
|
151
154
|
ctx.stdout(
|
|
@@ -166,11 +169,11 @@ function prune3(o) {
|
|
|
166
169
|
return out;
|
|
167
170
|
}
|
|
168
171
|
function registerTickerAgg(parent, ctx) {
|
|
169
|
-
parent.command("agg <symbol>").description("Get aggregated ticker for a symbol (GET /v2/public/q/agg-ticker)").
|
|
172
|
+
parent.command("agg <symbol>").description("Get aggregated ticker for a symbol (GET /v2/public/q/agg-ticker)").requiredOption("--time-range-type <type>", "H24, or calendar-day window UTC_P_0..UTC_P_12 / UTC_N_1..UTC_N_12 (e.g. UTC_P_8)").action(async (symbol, opts) => {
|
|
170
173
|
const c = ctx.http({ requireAuth: false });
|
|
171
174
|
const data = await c.getPublic(
|
|
172
175
|
"/v2/public/q/agg-ticker",
|
|
173
|
-
prune3({ symbol, timeRangeType: opts.
|
|
176
|
+
prune3({ symbol, timeRangeType: opts.timeRangeType })
|
|
174
177
|
);
|
|
175
178
|
const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
|
|
176
179
|
ctx.stdout(
|
|
@@ -191,11 +194,11 @@ function prune4(o) {
|
|
|
191
194
|
return out;
|
|
192
195
|
}
|
|
193
196
|
function registerTickerAggList(parent, ctx) {
|
|
194
|
-
parent.command("agg-list").description("List all aggregated tickers (GET /v2/public/q/agg-tickers)").requiredOption("--time-range <type>", "
|
|
197
|
+
parent.command("agg-list").description("List all aggregated tickers (GET /v2/public/q/agg-tickers)").requiredOption("--time-range-type <type>", "H24, or calendar-day window UTC_P_0..UTC_P_12 / UTC_N_1..UTC_N_12 (e.g. UTC_P_8)").action(async (opts) => {
|
|
195
198
|
const c = ctx.http({ requireAuth: false });
|
|
196
199
|
const data = await c.getPublic(
|
|
197
200
|
"/v2/public/q/agg-tickers",
|
|
198
|
-
prune4({ timeRangeType: opts.
|
|
201
|
+
prune4({ timeRangeType: opts.timeRangeType })
|
|
199
202
|
);
|
|
200
203
|
const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
|
|
201
204
|
ctx.stdout(
|
|
@@ -216,7 +219,7 @@ function prune5(o) {
|
|
|
216
219
|
return out;
|
|
217
220
|
}
|
|
218
221
|
function registerDepthGet(parent, ctx) {
|
|
219
|
-
parent.command("get <symbol>").description("Get order book depth (GET /v2/public/q/depth)").
|
|
222
|
+
parent.command("get <symbol>").description("Get order book depth (GET /v2/public/q/depth)").requiredOption("--level <level>", "depth levels, 1-50", parseInt).action(async (symbol, opts) => {
|
|
220
223
|
const c = ctx.http({ requireAuth: false });
|
|
221
224
|
const data = await c.getPublic(
|
|
222
225
|
"/v2/public/q/depth",
|
|
@@ -358,8 +361,8 @@ function registerIndexPriceList(parent, ctx) {
|
|
|
358
361
|
// src/commands/index-price/get.ts
|
|
359
362
|
function registerIndexPriceGet(parent, ctx) {
|
|
360
363
|
parent.command("get <symbol>").description("Get index price for a symbol (GET /v2/public/q/symbol-index-price)").action(async (symbol) => {
|
|
361
|
-
const c = ctx.http({ requireAuth:
|
|
362
|
-
const data = await c.
|
|
364
|
+
const c = ctx.http({ requireAuth: false });
|
|
365
|
+
const data = await c.getPublic("/v2/public/q/symbol-index-price", { symbol });
|
|
363
366
|
const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
|
|
364
367
|
ctx.stdout(
|
|
365
368
|
render2(data, {
|
|
@@ -548,18 +551,22 @@ function prune12(o) {
|
|
|
548
551
|
return out;
|
|
549
552
|
}
|
|
550
553
|
function registerBalanceBills(parent, ctx) {
|
|
551
|
-
parent.command("bills").description(
|
|
554
|
+
parent.command("bills").description(
|
|
555
|
+
"List balance bill records, cursor-paginated (GET /v2/balance/bills). Pass the last item id as --id with --direction NEXT for the next page."
|
|
556
|
+
).option("--coin <coin>", "filter by coin").option("--symbol <symbol>", "filter by trading pair").option("--type <type>", "bill type, e.g. EXCHANGE / FEE / FUND / CLOSE_POSITION").option("--start-time <ms>", "start time in milliseconds", parseInt).option("--end-time <ms>", "end time in milliseconds", parseInt).option("--id <id>", "cursor: bill id to page from").option("--direction <direction>", "cursor direction: NEXT | PREV (API default NEXT)").option("--limit <limit>", "records per page (API default 10)", parseInt).action(
|
|
552
557
|
async (opts) => {
|
|
553
558
|
const c = ctx.http({ requireAuth: true });
|
|
554
559
|
const data = await c.getPrivate(
|
|
555
560
|
"/v2/balance/bills",
|
|
556
561
|
prune12({
|
|
557
562
|
coin: opts.coin,
|
|
563
|
+
symbol: opts.symbol,
|
|
558
564
|
type: opts.type,
|
|
559
565
|
startTime: opts.startTime,
|
|
560
566
|
endTime: opts.endTime,
|
|
561
|
-
|
|
562
|
-
|
|
567
|
+
id: opts.id,
|
|
568
|
+
direction: opts.direction,
|
|
569
|
+
limit: opts.limit
|
|
563
570
|
})
|
|
564
571
|
);
|
|
565
572
|
const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
|
|
@@ -682,7 +689,7 @@ function prune13(o) {
|
|
|
682
689
|
return out;
|
|
683
690
|
}
|
|
684
691
|
function registerOrderListUnfinished(parent, ctx) {
|
|
685
|
-
parent.command("list-unfinished <symbol>").description("List unfinished orders for a symbol (GET /v2/order/listUnfinished)").
|
|
692
|
+
parent.command("list-unfinished <symbol>").description("List unfinished orders for a symbol (GET /v2/order/listUnfinished)").requiredOption("--direction <direction>", "order side: BUY | SELL").action(async (symbol, opts) => {
|
|
686
693
|
const c = ctx.http({ requireAuth: true });
|
|
687
694
|
const data = await c.getPrivate(
|
|
688
695
|
"/v2/order/listUnfinished",
|
|
@@ -769,7 +776,7 @@ function prune15(o) {
|
|
|
769
776
|
return out;
|
|
770
777
|
}
|
|
771
778
|
function registerOrderListHistory(parent, ctx) {
|
|
772
|
-
parent.command("list-history").description("List order history (GET /v2/order/list-history)").option("--symbol <symbol>", "filter by symbol").option("--start-time <ms>", "start time in milliseconds", parseInt).option("--end-time <ms>", "end time in milliseconds", parseInt).option("--id <id>", "order ID cursor").option("--direction <direction>", "
|
|
779
|
+
parent.command("list-history").description("List order history (GET /v2/order/list-history)").option("--symbol <symbol>", "filter by symbol").option("--start-time <ms>", "start time in milliseconds", parseInt).option("--end-time <ms>", "end time in milliseconds", parseInt).option("--id <id>", "order ID cursor").option("--direction <direction>", "cursor direction: NEXT | PREV (API default NEXT)").option("--limit <limit>", "number of records", parseInt).option("--force-close", "filter force-close orders").action(
|
|
773
780
|
async (opts) => {
|
|
774
781
|
const c = ctx.http({ requireAuth: true });
|
|
775
782
|
const data = await c.getPrivate(
|
|
@@ -878,7 +885,7 @@ function prune18(o) {
|
|
|
878
885
|
return out;
|
|
879
886
|
}
|
|
880
887
|
function registerEntrustPlanHistory(parent, ctx) {
|
|
881
|
-
parent.command("plan-history").description("List plan entrust history (GET /v2/entrust/plan-list-history)").option("--symbol <symbol>", "filter by symbol").option("--start-time <ms>", "start time in milliseconds", parseInt).option("--end-time <ms>", "end time in milliseconds", parseInt).option("--id <id>", "cursor ID").option("--direction <direction>", "
|
|
888
|
+
parent.command("plan-history").description("List plan entrust history (GET /v2/entrust/plan-list-history)").option("--symbol <symbol>", "filter by symbol").option("--start-time <ms>", "start time in milliseconds", parseInt).option("--end-time <ms>", "end time in milliseconds", parseInt).option("--id <id>", "cursor ID").option("--direction <direction>", "cursor direction: NEXT | PREV (API default NEXT)").option("--limit <limit>", "number of records", parseInt).action(
|
|
882
889
|
async (opts) => {
|
|
883
890
|
const c = ctx.http({ requireAuth: true });
|
|
884
891
|
const data = await c.getPrivate(
|
|
@@ -905,7 +912,9 @@ function registerEntrustPlanHistory(parent, ctx) {
|
|
|
905
912
|
|
|
906
913
|
// src/commands/entrust/plan-get.ts
|
|
907
914
|
function registerEntrustPlanGet(parent, ctx) {
|
|
908
|
-
parent.command("plan-get <id>").description(
|
|
915
|
+
parent.command("plan-get <id>").description(
|
|
916
|
+
"Get plan entrust detail (GET /v2/entrust/plan-detail). Positional <id> is sent as entrustId."
|
|
917
|
+
).action(async (id) => {
|
|
909
918
|
const c = ctx.http({ requireAuth: true });
|
|
910
919
|
const data = await c.getPrivate("/v2/entrust/plan-detail", { entrustId: id });
|
|
911
920
|
const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
|
|
@@ -945,7 +954,9 @@ function registerEntrustProfitList(parent, ctx) {
|
|
|
945
954
|
|
|
946
955
|
// src/commands/entrust/profit-get.ts
|
|
947
956
|
function registerEntrustProfitGet(parent, ctx) {
|
|
948
|
-
parent.command("profit-get <id>").description(
|
|
957
|
+
parent.command("profit-get <id>").description(
|
|
958
|
+
"Get TP/SL entrust detail (GET /v2/entrust/profit-detail). Positional <id> is sent as profitId."
|
|
959
|
+
).action(async (id) => {
|
|
949
960
|
const c = ctx.http({ requireAuth: true });
|
|
950
961
|
const data = await c.getPrivate("/v2/entrust/profit-detail", { profitId: id });
|
|
951
962
|
const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
|
|
@@ -1057,7 +1068,9 @@ async function prompt(rl, q, def) {
|
|
|
1057
1068
|
return ans.length === 0 && def !== void 0 ? def : ans;
|
|
1058
1069
|
}
|
|
1059
1070
|
function registerConfigInit(parent, ctx) {
|
|
1060
|
-
parent.command("init").description("Initialize a new profile (interactive)").option("--name <name>", "profile name (skip prompt)").option("--env <env>", "environment: test | prod").option("--base-url <url>", "override base url").option("--api-key <key>", "API key (only allowed with --yes; prefer interactive)").option("--secret <secret>", "API secret (only allowed with --yes; prefer interactive)").option("--identify <id>", "sub-account identify header (optional)").option("--set-default", "mark this profile as default-profile", true).option("--yes", "non-interactive mode (use provided flags only)").action(async (
|
|
1071
|
+
parent.command("init").description("Initialize a new profile (interactive)").option("--name <name>", "profile name (skip prompt)").option("--env <env>", "environment: test | prod").option("--base-url <url>", "override base url").option("--api-key <key>", "API key (only allowed with --yes; prefer interactive)").option("--secret <secret>", "API secret (only allowed with --yes; prefer interactive)").option("--identify <id>", "sub-account identify header (optional)").option("--set-default", "mark this profile as default-profile (default)", true).option("--no-set-default", "add the profile without changing default-profile").option("--yes", "non-interactive mode (use provided flags only)").action(async (subFlags, cmd) => {
|
|
1072
|
+
const flags = cmd.optsWithGlobals();
|
|
1073
|
+
flags.setDefault = subFlags.setDefault;
|
|
1061
1074
|
const path = ctx.getConfigPath();
|
|
1062
1075
|
let existing = null;
|
|
1063
1076
|
try {
|
|
@@ -1277,7 +1290,7 @@ function registerConfigRemove(parent, ctx) {
|
|
|
1277
1290
|
|
|
1278
1291
|
// src/commands/order/create.ts
|
|
1279
1292
|
function registerOrderCreate(parent, ctx) {
|
|
1280
|
-
parent.command("create").description("Place a new order (POST /v2/order/create)").requiredOption("--symbol <symbol>", "trading pair, e.g. btc_usdt").requiredOption("--order-type <type>", "LIMIT | MARKET
|
|
1293
|
+
parent.command("create").description("Place a new order (POST /v2/order/create)").requiredOption("--symbol <symbol>", "trading pair, e.g. btc_usdt").requiredOption("--order-type <type>", "LIMIT | MARKET").requiredOption("--order-side <side>", "BUY | SELL").requiredOption("--position-side <side>", "LONG | SHORT").requiredOption("--orig-qty <qty>", "integer contracts (string)").option("--price <price>", "required when --order-type LIMIT; ignored for MARKET").option("--time-in-force <tif>", "GTC | IOC | FOK | GTX (MARKET defaults IOC, LIMIT defaults GTC; MARKET only allows IOC/FOK)").option("--reduce-only <bool>", "true | false").option("--client-order-id <id>", "client-supplied order id").option("--position-id <id>", "existing position id (for hedge mode)").option("--leverage <lv>", "override leverage").option("--trigger-profit-price <price>", "TP trigger price").option("--trigger-stop-price <price>", "SL trigger price").option("--profit-order-type <type>", "TP child order type").option("--stop-order-type <type>", "SL child order type").option("--profit-order-price <price>", "TP child order price").option("--stop-order-price <price>", "SL child order price").option("--market-order-level <lv>", "market order depth level").action(
|
|
1281
1294
|
async (opts) => {
|
|
1282
1295
|
const body = pruneEmpty({
|
|
1283
1296
|
symbol: opts.symbol,
|
|
@@ -1340,7 +1353,7 @@ function registerOrderCancelAll(parent, ctx) {
|
|
|
1340
1353
|
|
|
1341
1354
|
// src/commands/entrust/create-plan.ts
|
|
1342
1355
|
function registerEntrustCreatePlan(parent, ctx) {
|
|
1343
|
-
parent.command("create-plan").description("Create a plan entrust (POST /v2/entrust/create-plan)").requiredOption("--symbol <symbol>", "trading pair").requiredOption("--order-side <side>", "BUY | SELL").requiredOption("--position-side <side>", "LONG | SHORT
|
|
1356
|
+
parent.command("create-plan").description("Create a plan entrust (POST /v2/entrust/create-plan)").requiredOption("--symbol <symbol>", "trading pair").requiredOption("--order-side <side>", "BUY | SELL").requiredOption("--position-side <side>", "LONG | SHORT").requiredOption("--entrust-type <type>", "STOP | STOP_MARKET").requiredOption("--time-in-force <tif>", "GTC | IOC | FOK | GTX").requiredOption("--trigger-price-type <type>", "MARK_PRICE | LATEST_PRICE").requiredOption("--orig-qty <qty>", "integer contracts (string)").requiredOption("--stop-price <price>", "trigger price (server-required despite the doc marking it conditional)").option("--price <price>", "limit price").option("--position-id <id>", "existing position id").option("--market-order-level <lv>", "market order depth level").option("--expire-time <ms>", "expiration time in ms").action(
|
|
1344
1357
|
async (opts) => {
|
|
1345
1358
|
const body = pruneEmpty({
|
|
1346
1359
|
symbol: opts.symbol,
|
|
@@ -1363,7 +1376,7 @@ function registerEntrustCreatePlan(parent, ctx) {
|
|
|
1363
1376
|
|
|
1364
1377
|
// src/commands/entrust/create-profit.ts
|
|
1365
1378
|
function registerEntrustCreateProfit(parent, ctx) {
|
|
1366
|
-
parent.command("create-profit").description("Create a TP/SL entrust (POST /v2/entrust/create-profit)").requiredOption("--symbol <symbol>", "trading pair").requiredOption("--orig-qty <qty>", "order quantity (string)").option("--position-side <side>", "LONG | SHORT
|
|
1379
|
+
parent.command("create-profit").description("Create a TP/SL entrust (POST /v2/entrust/create-profit)").requiredOption("--symbol <symbol>", "trading pair").requiredOption("--orig-qty <qty>", "order quantity (string)").option("--position-side <side>", "LONG | SHORT (required when --position-id is omitted)").option("--order-side <side>", "BUY | SELL (required when --position-id is omitted)").option("--trigger-price-type <type>", "MARK_PRICE | LATEST_PRICE (API default LATEST_PRICE)").option("--trigger-profit-price <price>", "TP trigger price").option("--trigger-stop-price <price>", "SL trigger price").option("--profit-order-type <type>", "TP child order type").option("--stop-order-type <type>", "SL child order type").option("--profit-order-price <price>", "TP child order price").option("--stop-order-price <price>", "SL child order price").option("--position-id <id>", "existing position id").option("--reduce-only <bool>", "true | false").option("--profit-flag <flag>", "profit flag").option("--expire-time <ms>", "expiration time in ms").action(
|
|
1367
1380
|
async (opts) => {
|
|
1368
1381
|
const body = pruneEmpty({
|
|
1369
1382
|
symbol: opts.symbol,
|
|
@@ -1522,6 +1535,68 @@ function registerPositionChangeType(parent, ctx) {
|
|
|
1522
1535
|
);
|
|
1523
1536
|
}
|
|
1524
1537
|
|
|
1538
|
+
// src/commands/position/history-async.ts
|
|
1539
|
+
var MAX_RANGE_MS = 366 * 24 * 60 * 60 * 1e3;
|
|
1540
|
+
function registerPositionHistoryAsync(parent, ctx) {
|
|
1541
|
+
parent.command("history-async").description(
|
|
1542
|
+
"Create an async position-history export task (GET /v2/position/history/async). Max 1-year range; at most 5 tasks per account per month. Download the result with `system download <downloadId>`."
|
|
1543
|
+
).requiredOption("--start-time <ms>", "start time in milliseconds", parseInt).requiredOption("--end-time <ms>", "end time in milliseconds", parseInt).action(async (opts) => {
|
|
1544
|
+
if (!Number.isFinite(opts.startTime) || !Number.isFinite(opts.endTime)) {
|
|
1545
|
+
throw new Error("--start-time and --end-time must be millisecond timestamps");
|
|
1546
|
+
}
|
|
1547
|
+
if (opts.endTime < opts.startTime) {
|
|
1548
|
+
throw new Error("--end-time must not be earlier than --start-time");
|
|
1549
|
+
}
|
|
1550
|
+
if (opts.endTime - opts.startTime > MAX_RANGE_MS) {
|
|
1551
|
+
throw new Error("time range must not exceed 1 year (API limit)");
|
|
1552
|
+
}
|
|
1553
|
+
const c = ctx.http({ requireAuth: true });
|
|
1554
|
+
const data = await c.getPrivate("/v2/position/history/async", {
|
|
1555
|
+
startTime: opts.startTime,
|
|
1556
|
+
endTime: opts.endTime
|
|
1557
|
+
});
|
|
1558
|
+
const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
|
|
1559
|
+
ctx.stdout(
|
|
1560
|
+
render2(data, {
|
|
1561
|
+
format: effectiveFormat2(ctx.outputFormat, ctx.isTTY),
|
|
1562
|
+
isTTY: ctx.isTTY
|
|
1563
|
+
})
|
|
1564
|
+
);
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// src/commands/system/user-info.ts
|
|
1569
|
+
function registerSystemUserInfo(parent, ctx) {
|
|
1570
|
+
parent.command("user-info").description("Get the user bound to the current API key (GET /v2/system/user/info)").action(async () => {
|
|
1571
|
+
const c = ctx.http({ requireAuth: true });
|
|
1572
|
+
const data = await c.getPrivate("/v2/system/user/info", {});
|
|
1573
|
+
const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
|
|
1574
|
+
ctx.stdout(
|
|
1575
|
+
render2(data, {
|
|
1576
|
+
format: effectiveFormat2(ctx.outputFormat, ctx.isTTY),
|
|
1577
|
+
isTTY: ctx.isTTY
|
|
1578
|
+
})
|
|
1579
|
+
);
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// src/commands/system/download.ts
|
|
1584
|
+
function registerSystemDownload(parent, ctx) {
|
|
1585
|
+
parent.command("download <downloadId>").description(
|
|
1586
|
+
"Download an async export file (GET /v2/system/download). Saves to ~/.onebullex/downloads/ unless --out is given. Each success counts toward the 5-per-hour limit for that downloadId."
|
|
1587
|
+
).option("--out <path>", 'target file, or an existing directory / path ending in "/"').action(async (downloadId, opts) => {
|
|
1588
|
+
const c = ctx.http({ requireAuth: true });
|
|
1589
|
+
const result = await downloadToFile({ client: c, downloadId, out: opts.out, cwd: ctx.cwd });
|
|
1590
|
+
const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
|
|
1591
|
+
ctx.stdout(
|
|
1592
|
+
render2(result, {
|
|
1593
|
+
format: effectiveFormat2(ctx.outputFormat, ctx.isTTY),
|
|
1594
|
+
isTTY: ctx.isTTY
|
|
1595
|
+
})
|
|
1596
|
+
);
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1525
1600
|
// src/index.ts
|
|
1526
1601
|
function buildProgram(opts) {
|
|
1527
1602
|
const stdoutFn = opts?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
@@ -1563,7 +1638,7 @@ function buildProgram(opts) {
|
|
|
1563
1638
|
return new HttpClient(cfg, opts?.fetch);
|
|
1564
1639
|
}
|
|
1565
1640
|
};
|
|
1566
|
-
const program = new Command("onebullex").version(true ? "1.
|
|
1641
|
+
const program = new Command("onebullex").version(true ? "1.5.0" : "0.0.0-dev").description("OneBullEx exchange CLI").option("-p, --profile <name>", "config profile to use").option("-o, --output <format>", "output format: table | json").option("--debug", "enable debug logging").option("--timeout <ms>", "request timeout in milliseconds", parseInt).option("--confirm", "confirm write operations (required for non-dry-run writes)").option("--base-url <url>", "override base URL").option("--api-key <key>", "override api key (env: ONEBULLEX_API_KEY)").option("--secret <s>", "override secret (env: ONEBULLEX_SECRET; CLI flag discouraged for security)").exitOverride();
|
|
1567
1642
|
program.hook("preAction", (thisCommand, _actionCommand) => {
|
|
1568
1643
|
const flags = thisCommand.opts();
|
|
1569
1644
|
if (flags.output === "table" || flags.output === "json") {
|
|
@@ -1658,12 +1733,16 @@ function buildProgram(opts) {
|
|
|
1658
1733
|
registerPositionList(position, ctx);
|
|
1659
1734
|
registerPositionConfs(position, ctx);
|
|
1660
1735
|
registerPositionHistory(position, ctx);
|
|
1736
|
+
registerPositionHistoryAsync(position, ctx);
|
|
1661
1737
|
registerPositionAdjustLeverage(position, ctx);
|
|
1662
1738
|
registerPositionMargin(position, ctx);
|
|
1663
1739
|
registerPositionAutoMargin(position, ctx);
|
|
1664
1740
|
registerPositionCloseAll(position, ctx);
|
|
1665
1741
|
registerPositionMerge(position, ctx);
|
|
1666
1742
|
registerPositionChangeType(position, ctx);
|
|
1743
|
+
const system = program.command("system").description("Account identity and async export downloads");
|
|
1744
|
+
registerSystemUserInfo(system, ctx);
|
|
1745
|
+
registerSystemDownload(system, ctx);
|
|
1667
1746
|
const config = program.command("config").description("Manage CLI configuration");
|
|
1668
1747
|
registerConfigInit(config, ctx);
|
|
1669
1748
|
registerConfigSet(config, ctx);
|
|
@@ -1711,7 +1790,7 @@ async function main(argv) {
|
|
|
1711
1790
|
return EXIT.GENERIC;
|
|
1712
1791
|
}
|
|
1713
1792
|
}
|
|
1714
|
-
var isEntry = process.argv[1] && (process.argv[1].endsWith("/index.ts") || process.argv[1].endsWith("/index.js") || process.argv[1].endsWith("\\index.ts") || process.argv[1].endsWith("\\index.js"));
|
|
1793
|
+
var isEntry = process.argv[1] && (process.argv[1].endsWith("/index.ts") || process.argv[1].endsWith("/index.js") || process.argv[1].endsWith("\\index.ts") || process.argv[1].endsWith("\\index.js") || process.argv[1].endsWith("/onebullex") || process.argv[1].endsWith("\\onebullex"));
|
|
1715
1794
|
if (isEntry) {
|
|
1716
1795
|
void main(process.argv).then((code) => process.exit(code));
|
|
1717
1796
|
}
|
package/dist/lint-api.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ declare function diffEndpoints(doc: readonly Endpoint[], cli: readonly Endpoint[
|
|
|
25
25
|
* - GET / query table: 5 columns `参数 | 位置 | 类型 | 必填 | 说明`
|
|
26
26
|
* - POST body table: 4 columns `字段 | 类型 | 必填 | 说明`
|
|
27
27
|
*/
|
|
28
|
-
declare function parseDocParams(
|
|
28
|
+
declare function parseDocParams(rawMd: string, path: string): DocParam[];
|
|
29
29
|
/**
|
|
30
30
|
* Parses a TS source string and returns commander positional/option flags as
|
|
31
31
|
* `(name, required)` pairs. Positional `<x>` → required, `[x]` → optional;
|
package/dist/lint-api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
REGISTRY
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-NMW73ILO.js";
|
|
5
5
|
|
|
6
6
|
// scripts/check-cli-coverage.ts
|
|
7
7
|
import { readFileSync } from "fs";
|
|
@@ -37,7 +37,8 @@ function diffEndpoints(doc, cli) {
|
|
|
37
37
|
function escapeRegex(s) {
|
|
38
38
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
39
39
|
}
|
|
40
|
-
function parseDocParams(
|
|
40
|
+
function parseDocParams(rawMd, path) {
|
|
41
|
+
const md = rawMd.replace(/\r\n/g, "\n");
|
|
41
42
|
const headingRe = new RegExp(
|
|
42
43
|
`####\\s+(?:GET|POST)\\s+${escapeRegex(path)}\\b`,
|
|
43
44
|
"m"
|
|
@@ -135,15 +136,29 @@ var CLI_DEFAULTED_PARAMS = /* @__PURE__ */ new Map([
|
|
|
135
136
|
// the caller omits it.
|
|
136
137
|
["POST /v2/balance/transfer", /* @__PURE__ */ new Set(["bill-no"])]
|
|
137
138
|
]);
|
|
139
|
+
var CLI_ONLY_PARAMS = /* @__PURE__ */ new Map([
|
|
140
|
+
// where to save the downloaded file; the API only sees downloadId
|
|
141
|
+
["GET /v2/system/download", /* @__PURE__ */ new Set(["out"])]
|
|
142
|
+
]);
|
|
143
|
+
var CLI_POSITIONAL_ALIASES = /* @__PURE__ */ new Map([
|
|
144
|
+
["GET /v2/entrust/plan-detail", /* @__PURE__ */ new Map([["id", "entrust-id"]])],
|
|
145
|
+
["GET /v2/entrust/profit-detail", /* @__PURE__ */ new Map([["id", "profit-id"]])]
|
|
146
|
+
]);
|
|
138
147
|
function diffParams(endpoint, docParams, cliParams) {
|
|
139
148
|
const errors = [];
|
|
140
|
-
const
|
|
149
|
+
const key = `${endpoint.method} ${endpoint.path}`;
|
|
150
|
+
const defaulted = CLI_DEFAULTED_PARAMS.get(key) ?? /* @__PURE__ */ new Set();
|
|
151
|
+
const cliOnly = CLI_ONLY_PARAMS.get(key) ?? /* @__PURE__ */ new Set();
|
|
152
|
+
const aliases = CLI_POSITIONAL_ALIASES.get(key) ?? /* @__PURE__ */ new Map();
|
|
141
153
|
const docMap = new Map(
|
|
142
154
|
docParams.map((p) => [normalize(p.name), p])
|
|
143
155
|
);
|
|
144
|
-
const cliMap = new Map(
|
|
145
|
-
|
|
146
|
-
|
|
156
|
+
const cliMap = /* @__PURE__ */ new Map();
|
|
157
|
+
for (const p of cliParams) {
|
|
158
|
+
if (GLOBAL_FLAGS.has(p.name) || cliOnly.has(p.name)) continue;
|
|
159
|
+
const mapped = aliases.get(p.name) ?? normalize(p.name);
|
|
160
|
+
cliMap.set(mapped, { name: mapped, required: p.required });
|
|
161
|
+
}
|
|
147
162
|
for (const [k, dp] of docMap.entries()) {
|
|
148
163
|
if (dp.required) {
|
|
149
164
|
const cp = cliMap.get(k);
|
package/dist/registry.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onebullex-group/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "OneBullEx Open API V2 command-line interface",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"./core/signature": "./dist/core/signature.js",
|
|
18
18
|
"./core/types": "./dist/core/types.js",
|
|
19
19
|
"./core/bill-no": "./dist/core/bill-no.js",
|
|
20
|
+
"./core/download": "./dist/core/download.js",
|
|
20
21
|
"./core/redact": "./dist/core/redact.js"
|
|
21
22
|
},
|
|
22
23
|
"files": [
|