@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 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.4.0)覆盖 V2 文档的全部 65 个端点:21 个公开 + 23 个私有只读 + 21 个写。所有写命令默认 dry-run,需 `--confirm` 才真发请求。
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
- ### 公开(21)
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* (* index-price 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
- ### 私有只读(23)
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 0.001 --price 50000
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 0.001 --price 50000 \
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: true,
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
- const json = await res.json();
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 };
@@ -1,13 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  HttpClient,
4
- flattenForSign
5
- } from "../chunk-3XZDH7DV.js";
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-3XZDH7DV.js";
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)").option("--time-range <type>", "time range type").action(async (symbol, opts) => {
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.timeRange })
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>", "time range type").action(async (opts) => {
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.timeRange })
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)").option("--time-range <type>", "time range type").action(async (symbol, opts) => {
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.timeRange })
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>", "time range type").action(async (opts) => {
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.timeRange })
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)").option("--level <level>", "depth level", parseInt).action(async (symbol, opts) => {
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: true });
362
- const data = await c.getPrivate("/v2/public/q/symbol-index-price", { symbol });
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("List balance bill records (GET /v2/balance/bills)").option("--coin <coin>", "filter by coin").option("--type <type>", "bill type").option("--start-time <ms>", "start time in milliseconds", parseInt).option("--end-time <ms>", "end time in milliseconds", parseInt).option("--page <page>", "page number", parseInt).option("--size <size>", "page size", parseInt).action(
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
- page: opts.page,
562
- size: opts.size
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)").option("--direction <direction>", "order direction").action(async (symbol, opts) => {
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>", "order direction").option("--limit <limit>", "number of records", parseInt).option("--force-close", "filter force-close orders").action(
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>", "order direction").option("--limit <limit>", "number of records", parseInt).action(
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("Get plan entrust detail (GET /v2/entrust/plan-detail)").action(async (id) => {
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("Get TP/SL entrust detail (GET /v2/entrust/profit-detail)").action(async (id) => {
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 (flags) => {
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 | STOP_LIMIT | etc.").requiredOption("--order-side <side>", "BUY | SELL").requiredOption("--position-side <side>", "LONG | SHORT | NET").requiredOption("--orig-qty <qty>", "order quantity (string, decimal-safe)").option("--price <price>", "limit price (string)").option("--time-in-force <tif>", "GTC | IOC | FOK | GTX").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(
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 | NET").requiredOption("--entrust-type <type>", "plan entrust type").requiredOption("--time-in-force <tif>", "time-in-force, e.g. GTC").requiredOption("--trigger-price-type <type>", "INDEX | MARK | LATEST").requiredOption("--orig-qty <qty>", "order quantity (string)").option("--stop-price <price>", "stop price").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(
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 | NET").option("--order-side <side>", "BUY | SELL").option("--trigger-price-type <type>", "INDEX | MARK | LATEST").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(
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.4.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();
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
  }
@@ -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(md: string, path: string): DocParam[];
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-KS5FFAKW.js";
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(md, path) {
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 defaulted = CLI_DEFAULTED_PARAMS.get(`${endpoint.method} ${endpoint.path}`) ?? /* @__PURE__ */ new Set();
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
- cliParams.filter((p) => !GLOBAL_FLAGS.has(p.name)).map((p) => [normalize(p.name), p])
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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  REGISTRY
4
- } from "./chunk-KS5FFAKW.js";
4
+ } from "./chunk-NMW73ILO.js";
5
5
  export {
6
6
  REGISTRY
7
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebullex-group/cli",
3
- "version": "1.4.0",
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": [