@onebullex-group/cli 1.3.0 → 1.4.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
@@ -1,61 +1,85 @@
1
- # Changelog
2
-
3
- All notable changes to `@onebullex-group/cli` (formerly briefly published as `@onemore-group/cli`).
4
-
5
- ## 1.1.2 — 2026-05-16
6
-
7
- Rename only — no functional changes from 1.1.1.
8
-
9
- - Package renamed from `@onebullex/cli` → `@onebullex-group/cli`. The
10
- CLI binary remains `onebullex` (unchanged), the README still
11
- describes "OneBullEx Open API V2" — only the npm package name moved.
12
- - A short-lived `@onemore-group/cli@1.1.2` was published earlier the
13
- same day and has been unpublished. If you installed it, switch to
14
- `@onebullex-group/cli@1.1.2` (identical contents).
15
- - Existing 1.1.0 / 1.1.1 were never published.
16
-
17
- ## Unreleased
18
-
19
- - ci(cli): nightly `cli-e2e-nightly` job in `.gitlab-ci.yml`, runs the full
20
- `tests/e2e/` suite when triggered by a Pipeline Schedule. Reads
21
- `ONEBULLEX_E2E_API_KEY` / `_SECRET` from GitLab CI variables. Marked
22
- `allow_failure: true` with one retry so testnet flakes don't block the
23
- schedule.
24
- - test(cli): testnet write e2e suite covering all 20 V2 write endpoints,
25
- organized into 4 business-flow blocks (configuration / limit orders /
26
- plan + order-entrust / position + TP-SL). Gated on `ONEBULLEX_E2E=1`
27
- with separate `ONEBULLEX_E2E_API_KEY` / `_SECRET` env vars. Default-
28
- skipped. Each block has best-effort cleanup so consecutive runs leave
29
- the testnet account state-clean.
30
-
31
- ## 1.1.0 — 2026-05-15
32
-
33
- Adds Phase 2: 20 write operations covering order placement / cancellation,
34
- plan + TP/SL entrust management, and position margin / leverage / type changes.
35
-
36
- - 5 order write commands (create, create-batch, cancel, cancel-batch, cancel-all)
37
- - 7 entrust write commands (create-plan, create-profit, cancel-plan,
38
- cancel-profit, update-profit, cancel-all-plan, cancel-all-profit)
39
- - 2 order-entrust write commands (cancel, cancel-all)
40
- - 6 position write commands (adjust-leverage, margin, auto-margin, close-all,
41
- merge, change-type)
42
- - New `executeWrite` helper guards every write with a default dry-run; `--confirm`
43
- is required for the actual POST. Dry-run preview goes to stdout, the
44
- `--confirm` hint goes to stderr.
45
- - Drift-detection lint now covers all 60 V2 endpoints; the `PHASE1_DEFERRED`
46
- whitelist is empty.
47
- - Adds 21 new write command integration tests verifying both the dry-run path
48
- (no fetch, structured preview) and the confirm path (camelCase body shape).
49
-
50
- ## 1.0.0 — 2026-05-15
51
-
52
- Initial release. Covers OneBullEx Open API V2:
53
-
54
- - 22 public endpoints (server time, symbol/ticker/depth/kline/funding/...)
55
- - 18 private read-only endpoints (balance/order/entrust/position/...)
56
- - HMAC-SHA256 auth aligned byte-for-byte with `SignatureUtilsV2.java`
57
- - 6 config commands with multi-profile support, `--stdin` enforcement on
58
- api-key/secret, and 0600 file mode
59
- - Drift-detection lint comparing CLI registry against V2 markdown docs
60
- (endpoint coverage + parameter coverage + write-safety)
61
- - 155 unit / integration tests + 5 testnet e2e smoke tests
1
+ # Changelog
2
+
3
+ All notable changes to `@onebullex-group/cli` (formerly briefly published as `@onemore-group/cli`).
4
+
5
+ ## 1.1.2 — 2026-05-16
6
+
7
+ Rename only — no functional changes from 1.1.1.
8
+
9
+ - Package renamed from `@onebullex/cli` → `@onebullex-group/cli`. The
10
+ CLI binary remains `onebullex` (unchanged), the README still
11
+ describes "OneBullEx Open API V2" — only the npm package name moved.
12
+ - A short-lived `@onemore-group/cli@1.1.2` was published earlier the
13
+ same day and has been unpublished. If you installed it, switch to
14
+ `@onebullex-group/cli@1.1.2` (identical contents).
15
+ - Existing 1.1.0 / 1.1.1 were never published.
16
+
17
+ ## 1.4.0 — 2026-08-26
18
+
19
+ Syncs the three V2 endpoints added to the Java SDK since 1.3.1 (commits
20
+ `566645a`, `f684e52`). Registry now covers 65 documented endpoints.
21
+
22
+ - `balance funding` — GET `/v2/balance/funding`, funding (USDT) wallet balance.
23
+ - `bot redeeming-info` — GET `/v2/bot/redeeming-info`, redemption queue summary.
24
+ Introduces a new top-level `bot` command group.
25
+ - `balance transfer` — POST `/v2/balance/transfer`, moves funds between the
26
+ funding and contract wallets. **The first command in this CLI that moves real
27
+ money rather than trading state.** Default dry-run applies as with every write.
28
+ - New `core/bill-no.ts` (exported as `@onebullex-group/cli/core/bill-no`):
29
+ generates the `obe-<uuid>` idempotency key that `balance transfer` requires.
30
+ `--bill-no` is therefore optional at the CLI boundary even though the API
31
+ marks it required. Retrying a transfer MUST reuse the original billNo — a
32
+ fresh one is treated as a second transfer by the exchange.
33
+ - The coverage lint gained a scoped `CLI_DEFAULTED_PARAMS` exception so a
34
+ doc-required param may be a plain `--option` when the CLI defaults it. Scoped
35
+ per `METHOD path`; deliberately not added to `GLOBAL_FLAGS`, which would have
36
+ dropped billNo from the derived MCP input schema.
37
+ - Docs: README endpoint counts corrected to the registry-derived 65
38
+ (21 public / 23 private read / 21 write). The previous "60" had been stale
39
+ since two endpoints were added without updating it.
40
+
41
+ ## Unreleased
42
+
43
+ - ci(cli): nightly `cli-e2e-nightly` job in `.gitlab-ci.yml`, runs the full
44
+ `tests/e2e/` suite when triggered by a Pipeline Schedule. Reads
45
+ `ONEBULLEX_E2E_API_KEY` / `_SECRET` from GitLab CI variables. Marked
46
+ `allow_failure: true` with one retry so testnet flakes don't block the
47
+ schedule.
48
+ - test(cli): testnet write e2e suite covering all 20 V2 write endpoints,
49
+ organized into 4 business-flow blocks (configuration / limit orders /
50
+ plan + order-entrust / position + TP-SL). Gated on `ONEBULLEX_E2E=1`
51
+ with separate `ONEBULLEX_E2E_API_KEY` / `_SECRET` env vars. Default-
52
+ skipped. Each block has best-effort cleanup so consecutive runs leave
53
+ the testnet account state-clean.
54
+
55
+ ## 1.1.0 — 2026-05-15
56
+
57
+ Adds Phase 2: 20 write operations covering order placement / cancellation,
58
+ plan + TP/SL entrust management, and position margin / leverage / type changes.
59
+
60
+ - 5 order write commands (create, create-batch, cancel, cancel-batch, cancel-all)
61
+ - 7 entrust write commands (create-plan, create-profit, cancel-plan,
62
+ cancel-profit, update-profit, cancel-all-plan, cancel-all-profit)
63
+ - 2 order-entrust write commands (cancel, cancel-all)
64
+ - 6 position write commands (adjust-leverage, margin, auto-margin, close-all,
65
+ merge, change-type)
66
+ - New `executeWrite` helper guards every write with a default dry-run; `--confirm`
67
+ is required for the actual POST. Dry-run preview goes to stdout, the
68
+ `--confirm` hint goes to stderr.
69
+ - Drift-detection lint now covers all 60 V2 endpoints; the `PHASE1_DEFERRED`
70
+ whitelist is empty.
71
+ - Adds 21 new write command integration tests verifying both the dry-run path
72
+ (no fetch, structured preview) and the confirm path (camelCase body shape).
73
+
74
+ ## 1.0.0 — 2026-05-15
75
+
76
+ Initial release. Covers OneBullEx Open API V2:
77
+
78
+ - 22 public endpoints (server time, symbol/ticker/depth/kline/funding/...)
79
+ - 18 private read-only endpoints (balance/order/entrust/position/...)
80
+ - HMAC-SHA256 auth aligned byte-for-byte with `SignatureUtilsV2.java`
81
+ - 6 config commands with multi-profile support, `--stdin` enforcement on
82
+ api-key/secret, and 0600 file mode
83
+ - Drift-detection lint comparing CLI registry against V2 markdown docs
84
+ (endpoint coverage + parameter coverage + write-safety)
85
+ - 155 unit / integration tests + 5 testnet e2e smoke tests
package/README.md CHANGED
@@ -1,243 +1,263 @@
1
- # @onebullex-group/cli
2
-
3
- OneBullEx Open API V2 的命令行工具。
4
-
5
- > 当前版本(1.1.0)覆盖 V2 文档的全部 60 个端点:22 个公开 + 18 个私有只读 + 20 个写。所有写命令默认 dry-run,需 `--confirm` 才真发请求。
6
-
7
- ---
8
-
9
- ## 安装
10
-
11
- ```bash
12
- npm i -g @onebullex-group/cli
13
- ```
14
-
15
- 或本地构建:
16
-
17
- ```bash
18
- cd cli
19
- pnpm install
20
- pnpm build
21
- pnpm link --global
22
- ```
23
-
24
- 校验:
25
-
26
- ```bash
27
- onebullex --version
28
- onebullex public time --base-url https://futures-openapi.1bullex.com
29
- ```
30
-
31
- ## Quick Start
32
-
33
- ```bash
34
- # 1. 初始化一个 profile(向导式)
35
- onebullex config init
36
-
37
- # 2. 看下当前 profile 配置(敏感字段已脱敏)
38
- onebullex config show
39
-
40
- # 3. 跑公开接口验证联通性
41
- onebullex public time
42
- onebullex symbol list
43
-
44
- # 4. 跑私有只读接口(需要在 config init 时填了 api-key/secret)
45
- onebullex balance list
46
- onebullex position list
47
- ```
48
-
49
- ## 命令分组
50
-
51
- 完整命令列表跟 V2 文档逐项对齐,可用 `onebullex <group> --help` 查询。
52
-
53
- ### 公开(22)
54
-
55
- ```
56
- public time GET /v2/public/time
57
- symbol list / get / coins / all
58
- ticker get / list / agg / agg-list
59
- depth get
60
- deal list
61
- kline list
62
- mark-price list / get
63
- index-price list / get* (* index-price get 需要认证)
64
- funding rate / history
65
- risk-balance list
66
- open-interest get
67
- leverage brackets-list / brackets-get
68
- ```
69
-
70
- ### 私有只读(18)
71
-
72
- ```
73
- listen-key get
74
- balance list / get / bills
75
- order list-unfinished / list-unfinished-multi / list / list-by-ids /
76
- list-history / get / trades
77
- entrust plan-list / plan-history / plan-get / profit-list / profit-get
78
- order-entrust list
79
- position list
80
- ```
81
-
82
- ### 写操作(20)
83
-
84
- 所有写命令默认 dry-run:仅打印预览不发请求。**加 `--confirm` 才会真正下单/撤单/改仓。**
85
-
86
- ```
87
- order create / create-batch / cancel / cancel-batch / cancel-all
88
- entrust create-plan / create-profit /
89
- cancel-plan / cancel-profit / update-profit /
90
- cancel-all-plan / cancel-all-profit
91
- order-entrust cancel / cancel-all
92
- position adjust-leverage / margin / auto-margin /
93
- close-all / merge / change-type
94
- ```
95
-
96
- 例:
97
- ```bash
98
- # dry-run(默认,退出码 4,preview 在 stdout)
99
- onebullex order create --symbol btc_usdt --order-type LIMIT \
100
- --order-side BUY --position-side LONG --orig-qty 0.001 --price 50000
101
-
102
- # 真正下单
103
- onebullex order create --symbol btc_usdt --order-type LIMIT \
104
- --order-side BUY --position-side LONG --orig-qty 0.001 --price 50000 \
105
- --confirm
106
- ```
107
-
108
- CLI flag 用 kebab-case,会映射到 API 的 camelCase 字段(如 `--order-side` → `orderSide`)。批量类(`create-batch` / `cancel-batch`)的 `--list` / `--order-ids` 接受 JSON 字符串数组。
109
-
110
- ### 配置管理(6)
111
-
112
- ```
113
- config init 交互式向导
114
- config set <key> 仅 base-url / identify / env 可用 --value
115
- api-key / secret 必须用 --stdin
116
- config show [--reveal] 默认脱敏,加 --reveal 后明文(带 stderr 警告)
117
- config use <profile> 切换默认 profile
118
- config list-profiles 表格列出全部 profile
119
- config remove <p> 删除 profile(防止删掉最后一个)
120
- ```
121
-
122
- ## 配置文件
123
-
124
- 默认路径 `~/.onebullex/config.json`,权限 0600(非 Windows 上自动校正)。
125
-
126
- ```json
127
- {
128
- "version": 1,
129
- "default-profile": "test",
130
- "profiles": {
131
- "test": {
132
- "base-url": "https://futures-openapi.1bullex.com",
133
- "api-key": "AK...",
134
- "secret": "...",
135
- "identify": null,
136
- "env": "test"
137
- }
138
- }
139
- }
140
- ```
141
-
142
- 可通过 `ONEBULLEX_CONFIG_PATH` 环境变量覆盖路径(脚本/CI 友好)。
143
-
144
- ## 优先级与全局 flag
145
-
146
- 每次调用按以下顺序解析:
147
-
148
- ```
149
- CLI flag > 环境变量 > 配置文件 profile > 报错(要求 config init)
150
- ```
151
-
152
- | 全局 flag | 环境变量 | 说明 |
153
- |---|---|---|
154
- | `--profile <name>` | `ONEBULLEX_PROFILE` | 临时切换 profile |
155
- | `--api-key <key>` | `ONEBULLEX_API_KEY` | 覆盖 api key |
156
- | `--secret <s>` | `ONEBULLEX_SECRET` | 覆盖 secret(不建议 CLI 上明文) |
157
- | `--base-url <url>` | `ONEBULLEX_BASE_URL` | 覆盖 base URL |
158
- | `--output <fmt>` | — | `table` / `json`,TTY 默认 table,否则 json |
159
- | `--debug` | `ONEBULLEX_DEBUG=1` | 把请求/响应(脱敏后)写到 stderr |
160
- | `--timeout <ms>` | — | HTTP 超时,默认 10s |
161
- | `--confirm` | — | Phase 2 写命令使用,未传时走 dry-run |
162
-
163
- ## 安全
164
-
165
- - secret 与 api-key **明文落盘**在 config.json,权限收紧到 0600
166
- - `config set api-key` / `config set secret` 拒绝 `--value`,强制 `--stdin`,防止 shell history 泄漏
167
- - `config show` 默认脱敏(`AK****1234` / `****`),仅 `--reveal` 时明文并带 stderr 警告
168
- - `--debug` 输出会通过 redact 过滤掉 `X-Signature` / `X-API-KEY` / `Authorization` / `secret` 字段
169
- - 每次调用都会校验 `~/.onebullex/config.json` 的 mode(非 win32),不是 0600 时打 warn 并自动修复
170
-
171
- ## 输出格式
172
-
173
- - TTY 下默认表格(`cli-table3`),列名取并集,时间戳列自动渲染成 `YYYY-MM-DD HH:mm:ss`
174
- - 非 TTY(管道、重定向、CI)默认 JSON,`jq` 友好
175
- - 任意时候用 `--output json` 强制 JSON,`--output table` 强制表格
176
-
177
- ## 防漂移 lint
178
-
179
- `pnpm check-coverage` 跑三层校验:
180
-
181
- 1. **端点级**:把 `docs/onebullex-openapi-cn-v2.md` 里的所有 `#### GET/POST /v2/*` 标题与 `cli/src/registry.ts` 对账,缺/多就 fail
182
- 2. **参数级**:用 ts-morph 解析每个命令文件,提取 commander 的 `command()` / `requiredOption()` / `option()`,与文档参数表的 `必填` 列对账
183
- 3. **写守门**:方法是 GET 但 `isWrite=true`,或者 method 是 POST 但 `isWrite=false`(`/v2/order/list-by-ids` 是已知例外)
184
-
185
- CI 上当文档新增一个端点时,coverage 检查会立刻 fail,强制开发者补一个新命令。
186
-
187
- ## 开发
188
-
189
- ```bash
190
- cd cli
191
- pnpm install
192
- pnpm dev <command> # 直接跑 src/index.ts,无需先 build
193
- pnpm test # 跑全部单测/集成测
194
- pnpm typecheck # tsc --noEmit
195
- pnpm lint # biome
196
- pnpm check-coverage # 防漂移 lint
197
- pnpm build # 产出 dist/index.js
198
- ```
199
-
200
- ### 跑 testnet e2e
201
-
202
- 只读 e2e(默认跳过)只验证公开接口连通性:
203
-
204
- ```bash
205
- ONEBULLEX_E2E=1 pnpm vitest run tests/e2e/public.e2e.test.ts
206
- ```
207
-
208
- 写 e2e(默认跳过)会真发请求到 testnet:下/撤远价限价单、改杠杆、改持仓模式、用 MARKET 单开仓 → 加保证金 → 创建止盈止损 → close-all。需要测试网账户和 ≥ 50 USDT 余额:
209
-
210
- ```bash
211
- ONEBULLEX_E2E=1 \
212
- ONEBULLEX_E2E_API_KEY=<testnet-key> \
213
- ONEBULLEX_E2E_SECRET=<testnet-secret> \
214
- pnpm vitest run tests/e2e/write.e2e.test.ts
215
- ```
216
-
217
- 按业务流程组织成 4 个 block,串行执行:configuration chain → limit order chain → plan + order-entrust chain → position + profit chain。每个 block 末尾有 best-effort `cancel-all` / `close-all`,跑挂时也尽量不留挂单/持仓残留。
218
-
219
- #### Nightly CI(可选)
220
-
221
- `cli/.gitlab-ci.yml` 里有一个 `cli-e2e-nightly` job,仅在 schedule 触发时跑(`$CI_PIPELINE_SOURCE == "schedule"`),跑完整 `tests/e2e/` 套件。开启方式:
222
-
223
- 1. 在 GitLab project **Settings → CI/CD → Variables** 添加 masked + protected 变量:
224
- - `ONEBULLEX_E2E_API_KEY` = testnet API key
225
- - `ONEBULLEX_E2E_SECRET` = testnet API secret
226
- 2. **Build → Pipeline schedules → New schedule**,cron 设成例如 `0 19 * * *`(每日 03:00 UTC+8),target branch 选 `main`。
227
-
228
- job 标 `allow_failure: true` 且 retry 一次——testnet 偶发抖动不会阻塞调度。变量没配时只有 public e2e 跑,写 e2e 自动 skip(HAS_AUTH 门控)。
229
-
230
- ## 与 Java SDK 的关系
231
-
232
- 签名实现严格对齐 `src/main/java/com/onebullex/api/common/SignatureUtilsV2.java`:
233
-
234
- - timestamp 秒级(10 位)
235
- - nonce 是 UUID v4 去横线(32 位 hex 小写)
236
- - query string 按 key 字典序排序,**不做 URL 编码**
237
- - HMAC-SHA256,输出 hex 小写
238
-
239
- Java 端通过 `SignatureVectorGenTest` 生成 `cli/tests/fixtures/signature-vectors.json`,CLI 端 `signature-parity.test.ts` 把 7 条向量逐条对照 Java 输出,保证字节级一致。
240
-
241
- ## License
242
-
243
- MIT
1
+ # @onebullex-group/cli
2
+
3
+ OneBullEx Open API V2 的命令行工具。
4
+
5
+ > 当前版本(1.4.0)覆盖 V2 文档的全部 65 个端点:21 个公开 + 23 个私有只读 + 21 个写。所有写命令默认 dry-run,需 `--confirm` 才真发请求。
6
+
7
+ ---
8
+
9
+ ## 安装
10
+
11
+ ```bash
12
+ npm i -g @onebullex-group/cli
13
+ ```
14
+
15
+ 或本地构建:
16
+
17
+ ```bash
18
+ cd cli
19
+ pnpm install
20
+ pnpm build
21
+ pnpm link --global
22
+ ```
23
+
24
+ 校验:
25
+
26
+ ```bash
27
+ onebullex --version
28
+ onebullex public time --base-url https://futures-openapi.1bullex.com
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```bash
34
+ # 1. 初始化一个 profile(向导式)
35
+ onebullex config init
36
+
37
+ # 2. 看下当前 profile 配置(敏感字段已脱敏)
38
+ onebullex config show
39
+
40
+ # 3. 跑公开接口验证联通性
41
+ onebullex public time
42
+ onebullex symbol list
43
+
44
+ # 4. 跑私有只读接口(需要在 config init 时填了 api-key/secret)
45
+ onebullex balance list
46
+ onebullex position list
47
+ ```
48
+
49
+ ## 命令分组
50
+
51
+ 完整命令列表跟 V2 文档逐项对齐,可用 `onebullex <group> --help` 查询。
52
+
53
+ ### 公开(21)
54
+
55
+ ```
56
+ public time GET /v2/public/time
57
+ symbol list / get / coins / all
58
+ ticker get / list / agg / agg-list
59
+ depth get
60
+ deal list
61
+ kline list
62
+ mark-price list / get
63
+ index-price list / get* (* index-price get 需要认证)
64
+ funding rate / history
65
+ risk-balance list
66
+ open-interest get
67
+ leverage brackets-list / brackets-get
68
+ ```
69
+
70
+ ### 私有只读(23)
71
+
72
+ ```
73
+ listen-key get
74
+ balance list / get / bills / funding
75
+ bot redeeming-info
76
+ order list-unfinished / list-unfinished-multi / list / list-by-ids /
77
+ list-history / get / trades
78
+ entrust plan-list / plan-history / plan-get / profit-list / profit-get
79
+ order-entrust list
80
+ position list
81
+ ```
82
+
83
+ ### 写操作(21)
84
+
85
+ 所有写命令默认 dry-run:仅打印预览不发请求。**加 `--confirm` 才会真正下单/撤单/改仓/划转。**
86
+
87
+ ```
88
+ balance transfer ⚠ 真实资金划转,见下方说明
89
+ order create / create-batch / cancel / cancel-batch / cancel-all
90
+ entrust create-plan / create-profit /
91
+ cancel-plan / cancel-profit / update-profit /
92
+ cancel-all-plan / cancel-all-profit
93
+ order-entrust cancel / cancel-all
94
+ position adjust-leverage / margin / auto-margin /
95
+ close-all / merge / change-type
96
+ ```
97
+
98
+ 例:
99
+ ```bash
100
+ # dry-run(默认,退出码 4,preview 在 stdout)
101
+ onebullex order create --symbol btc_usdt --order-type LIMIT \
102
+ --order-side BUY --position-side LONG --orig-qty 0.001 --price 50000
103
+
104
+ # 真正下单
105
+ onebullex order create --symbol btc_usdt --order-type LIMIT \
106
+ --order-side BUY --position-side LONG --orig-qty 0.001 --price 50000 \
107
+ --confirm
108
+ ```
109
+
110
+ CLI flag 用 kebab-case,会映射到 API 的 camelCase 字段(如 `--order-side` → `orderSide`)。批量类(`create-batch` / `cancel-batch`)的 `--list` / `--order-ids` 接受 JSON 字符串数组。
111
+
112
+ #### ⚠ 资金划转(`balance transfer`)
113
+
114
+ 在资金账户(`FUNDING`,存钱、不能交易)和合约账户(`CONTRACT`,交易占保证金)之间搬钱。
115
+ 划错不能撤销,只能反向再划一次。
116
+
117
+ ```bash
118
+ # dry-run:预览里会显示自动生成的 billNo
119
+ onebullex balance transfer --from FUNDING --to CONTRACT --amount 500
120
+
121
+ # 真正划转 —— 把预览里那个 billNo 原样带上
122
+ onebullex balance transfer --from FUNDING --to CONTRACT --amount 500 \
123
+ --bill-no obe-3f6c1e9a-... --confirm
124
+ ```
125
+
126
+ `--bill-no` 是幂等单号。省略时 CLI 会生成一个 `obe-<uuid>`,但**重试同一笔划转必须
127
+ 复用第一次的单号** —— 换新单号会被服务端当成第二笔,等于划两次。所以脚本里重试请
128
+ 先把 billNo 存下来,不要依赖自动生成。`--from` 与 `--to` 不能相同。
129
+
130
+ ### 配置管理(6)
131
+
132
+ ```
133
+ config init 交互式向导
134
+ config set <key> 仅 base-url / identify / env 可用 --value
135
+ api-key / secret 必须用 --stdin
136
+ config show [--reveal] 默认脱敏,加 --reveal 后明文(带 stderr 警告)
137
+ config use <profile> 切换默认 profile
138
+ config list-profiles 表格列出全部 profile
139
+ config remove <p> 删除 profile(防止删掉最后一个)
140
+ ```
141
+
142
+ ## 配置文件
143
+
144
+ 默认路径 `~/.onebullex/config.json`,权限 0600(非 Windows 上自动校正)。
145
+
146
+ ```json
147
+ {
148
+ "version": 1,
149
+ "default-profile": "test",
150
+ "profiles": {
151
+ "test": {
152
+ "base-url": "https://futures-openapi.1bullex.com",
153
+ "api-key": "AK...",
154
+ "secret": "...",
155
+ "identify": null,
156
+ "env": "test"
157
+ }
158
+ }
159
+ }
160
+ ```
161
+
162
+ 可通过 `ONEBULLEX_CONFIG_PATH` 环境变量覆盖路径(脚本/CI 友好)。
163
+
164
+ ## 优先级与全局 flag
165
+
166
+ 每次调用按以下顺序解析:
167
+
168
+ ```
169
+ CLI flag > 环境变量 > 配置文件 profile > 报错(要求 config init)
170
+ ```
171
+
172
+ | 全局 flag | 环境变量 | 说明 |
173
+ |---|---|---|
174
+ | `--profile <name>` | `ONEBULLEX_PROFILE` | 临时切换 profile |
175
+ | `--api-key <key>` | `ONEBULLEX_API_KEY` | 覆盖 api key |
176
+ | `--secret <s>` | `ONEBULLEX_SECRET` | 覆盖 secret(不建议 CLI 上明文) |
177
+ | `--base-url <url>` | `ONEBULLEX_BASE_URL` | 覆盖 base URL |
178
+ | `--output <fmt>` | — | `table` / `json`,TTY 默认 table,否则 json |
179
+ | `--debug` | `ONEBULLEX_DEBUG=1` | 把请求/响应(脱敏后)写到 stderr |
180
+ | `--timeout <ms>` | — | HTTP 超时,默认 10s |
181
+ | `--confirm` | — | Phase 2 写命令使用,未传时走 dry-run |
182
+
183
+ ## 安全
184
+
185
+ - secret 与 api-key **明文落盘**在 config.json,权限收紧到 0600
186
+ - `config set api-key` / `config set secret` 拒绝 `--value`,强制 `--stdin`,防止 shell history 泄漏
187
+ - `config show` 默认脱敏(`AK****1234` / `****`),仅 `--reveal` 时明文并带 stderr 警告
188
+ - `--debug` 输出会通过 redact 过滤掉 `X-Signature` / `X-API-KEY` / `Authorization` / `secret` 字段
189
+ - 每次调用都会校验 `~/.onebullex/config.json` 的 mode(非 win32),不是 0600 时打 warn 并自动修复
190
+
191
+ ## 输出格式
192
+
193
+ - TTY 下默认表格(`cli-table3`),列名取并集,时间戳列自动渲染成 `YYYY-MM-DD HH:mm:ss`
194
+ - 非 TTY(管道、重定向、CI)默认 JSON,`jq` 友好
195
+ - 任意时候用 `--output json` 强制 JSON,`--output table` 强制表格
196
+
197
+ ## 防漂移 lint
198
+
199
+ `pnpm check-coverage` 跑三层校验:
200
+
201
+ 1. **端点级**:把 `docs/onebullex-openapi-cn-v2.md` 里的所有 `#### GET/POST /v2/*` 标题与 `cli/src/registry.ts` 对账,缺/多就 fail
202
+ 2. **参数级**:用 ts-morph 解析每个命令文件,提取 commander 的 `command()` / `requiredOption()` / `option()`,与文档参数表的 `必填` 列对账
203
+ 3. **写守门**:方法是 GET 但 `isWrite=true`,或者 method 是 POST 但 `isWrite=false`(`/v2/order/list-by-ids` 是已知例外)
204
+
205
+ CI 上当文档新增一个端点时,coverage 检查会立刻 fail,强制开发者补一个新命令。
206
+
207
+ ## 开发
208
+
209
+ ```bash
210
+ cd cli
211
+ pnpm install
212
+ pnpm dev <command> # 直接跑 src/index.ts,无需先 build
213
+ pnpm test # 跑全部单测/集成测
214
+ pnpm typecheck # tsc --noEmit
215
+ pnpm lint # biome
216
+ pnpm check-coverage # 防漂移 lint
217
+ pnpm build # 产出 dist/index.js
218
+ ```
219
+
220
+ ### 跑 testnet e2e
221
+
222
+ 只读 e2e(默认跳过)只验证公开接口连通性:
223
+
224
+ ```bash
225
+ ONEBULLEX_E2E=1 pnpm vitest run tests/e2e/public.e2e.test.ts
226
+ ```
227
+
228
+ 写 e2e(默认跳过)会真发请求到 testnet:下/撤远价限价单、改杠杆、改持仓模式、用 MARKET 单开仓 → 加保证金 → 创建止盈止损 → close-all。需要测试网账户和 ≥ 50 USDT 余额:
229
+
230
+ ```bash
231
+ ONEBULLEX_E2E=1 \
232
+ ONEBULLEX_E2E_API_KEY=<testnet-key> \
233
+ ONEBULLEX_E2E_SECRET=<testnet-secret> \
234
+ pnpm vitest run tests/e2e/write.e2e.test.ts
235
+ ```
236
+
237
+ 按业务流程组织成 4 个 block,串行执行:configuration chain → limit order chain → plan + order-entrust chain → position + profit chain。每个 block 末尾有 best-effort `cancel-all` / `close-all`,跑挂时也尽量不留挂单/持仓残留。
238
+
239
+ #### Nightly CI(可选)
240
+
241
+ `cli/.gitlab-ci.yml` 里有一个 `cli-e2e-nightly` job,仅在 schedule 触发时跑(`$CI_PIPELINE_SOURCE == "schedule"`),跑完整 `tests/e2e/` 套件。开启方式:
242
+
243
+ 1. 在 GitLab project **Settings → CI/CD → Variables** 添加 masked + protected 变量:
244
+ - `ONEBULLEX_E2E_API_KEY` = testnet API key
245
+ - `ONEBULLEX_E2E_SECRET` = testnet API secret
246
+ 2. **Build → Pipeline schedules → New schedule**,cron 设成例如 `0 19 * * *`(每日 03:00 UTC+8),target branch 选 `main`。
247
+
248
+ job 标 `allow_failure: true` 且 retry 一次——testnet 偶发抖动不会阻塞调度。变量没配时只有 public e2e 跑,写 e2e 自动 skip(HAS_AUTH 门控)。
249
+
250
+ ## 与 Java SDK 的关系
251
+
252
+ 签名实现严格对齐 `src/main/java/com/onebullex/api/common/SignatureUtilsV2.java`:
253
+
254
+ - timestamp 秒级(10 位)
255
+ - nonce 是 UUID v4 去横线(32 位 hex 小写)
256
+ - query string 按 key 字典序排序,**不做 URL 编码**
257
+ - HMAC-SHA256,输出 hex 小写
258
+
259
+ Java 端通过 `SignatureVectorGenTest` 生成 `cli/tests/fixtures/signature-vectors.json`,CLI 端 `signature-parity.test.ts` 把 7 条向量逐条对照 Java 输出,保证字节级一致。
260
+
261
+ ## License
262
+
263
+ MIT
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ generateNonce,
4
+ sign
5
+ } from "./chunk-LVNSFQXS.js";
2
6
  import {
3
7
  OneBullExError
4
8
  } from "./chunk-JRUFIHU7.js";
5
9
  import {
6
10
  redactObject
7
11
  } from "./chunk-OOLZIYXS.js";
8
- import {
9
- generateNonce,
10
- sign
11
- } from "./chunk-LVNSFQXS.js";
12
12
 
13
13
  // src/core/http-client.ts
14
14
  function flattenForSign(body) {
@@ -34,13 +34,17 @@ var HttpClient = class {
34
34
  cfg;
35
35
  fetch;
36
36
  async getPublic(path, query) {
37
- return this.withRetry(() => this.send("GET", path, query, void 0, false));
37
+ return this.withRetry(
38
+ () => this.send("GET", path, query, void 0, false, false)
39
+ );
38
40
  }
39
- async getPrivate(path, query) {
40
- return this.withRetry(() => this.send("GET", path, query, void 0, true));
41
+ async getPrivate(path, query, opts) {
42
+ return this.withRetry(
43
+ () => this.send("GET", path, query, void 0, true, opts?.raw ?? false)
44
+ );
41
45
  }
42
46
  async postPrivate(path, body) {
43
- return this.send("POST", path, void 0, body, true);
47
+ return this.send("POST", path, void 0, body, true, false);
44
48
  }
45
49
  /**
46
50
  * GET 请求重试包装:网络错误最多重试 2 次(共 3 次尝试),延迟 300ms / 900ms。
@@ -61,7 +65,7 @@ var HttpClient = class {
61
65
  }
62
66
  throw lastErr;
63
67
  }
64
- async send(method, path, query, body, requireAuth) {
68
+ async send(method, path, query, body, requireAuth, raw) {
65
69
  const headers = {
66
70
  "Content-Type": "application/json",
67
71
  Accept: "application/json",
@@ -124,6 +128,9 @@ var HttpClient = class {
124
128
  if (this.cfg.debug) {
125
129
  console.error("[debug response]", JSON.stringify(redactObject(json)));
126
130
  }
131
+ if (raw) {
132
+ return json;
133
+ }
127
134
  const returnCode = json.code ?? json.returnCode;
128
135
  const msgInfo = json.msg ?? json.msgInfo ?? "";
129
136
  if (returnCode !== 0 && returnCode !== "0") {
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/bill-no.ts
4
+ import { randomUUID } from "crypto";
5
+ function generateBillNo() {
6
+ return `obe-${randomUUID()}`;
7
+ }
8
+ function resolveBillNo(supplied) {
9
+ const trimmed = supplied?.trim();
10
+ return trimmed ? trimmed : generateBillNo();
11
+ }
12
+
13
+ export {
14
+ generateBillNo,
15
+ resolveBillNo
16
+ };
@@ -212,6 +212,22 @@ var REGISTRY = [
212
212
  needsAuth: true,
213
213
  implFile: "src/commands/balance/bills.ts"
214
214
  },
215
+ {
216
+ command: "balance funding",
217
+ method: "GET",
218
+ path: "/v2/balance/funding",
219
+ isWrite: false,
220
+ needsAuth: true,
221
+ implFile: "src/commands/balance/funding.ts"
222
+ },
223
+ {
224
+ command: "bot redeeming-info",
225
+ method: "GET",
226
+ path: "/v2/bot/redeeming-info",
227
+ isWrite: false,
228
+ needsAuth: true,
229
+ implFile: "src/commands/bot/redeeming-info.ts"
230
+ },
215
231
  {
216
232
  command: "order list-unfinished",
217
233
  method: "GET",
@@ -341,6 +357,14 @@ var REGISTRY = [
341
357
  implFile: "src/commands/position/history.ts"
342
358
  },
343
359
  // ───── Phase 2: write operations ─────
360
+ {
361
+ command: "balance transfer",
362
+ method: "POST",
363
+ path: "/v2/balance/transfer",
364
+ isWrite: true,
365
+ needsAuth: true,
366
+ implFile: "src/commands/balance/transfer.ts"
367
+ },
344
368
  {
345
369
  command: "order create",
346
370
  method: "POST",
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 划转幂等单号(billNo)生成与解析。
3
+ *
4
+ * `/v2/balance/transfer` 要求调用方提供唯一的 billNo 作为幂等键。文档标注必填,
5
+ * 但为了 CLI / MCP 两条路的易用性,我们允许省略并在此补一个 UUID v4。
6
+ *
7
+ * 幂等语义提醒:自动生成只在"首次发起"时安全。重试同一笔划转必须复用上一次的
8
+ * billNo,否则服务端会当成两笔新划转。dry-run 预览里展示的就是最终会发出去的
9
+ * billNo,confirm 时应原样带回。
10
+ */
11
+ /** 生成一个 `obe-` 前缀的 UUID v4,便于在对账时区分来源。 */
12
+ declare function generateBillNo(): string;
13
+ /**
14
+ * 解析调用方传入的 billNo:有值则去掉首尾空白后原样返回,
15
+ * 空/仅空白/未传则生成一个新的。
16
+ */
17
+ declare function resolveBillNo(supplied: string | undefined | null): string;
18
+
19
+ export { generateBillNo, resolveBillNo };
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ generateBillNo,
4
+ resolveBillNo
5
+ } from "../chunk-6DC55RF7.js";
6
+ export {
7
+ generateBillNo,
8
+ resolveBillNo
9
+ };
@@ -16,7 +16,9 @@ declare class HttpClient {
16
16
  private readonly fetch;
17
17
  constructor(cfg: ResolvedConfig, fetchFn?: FetchFn);
18
18
  getPublic<T>(path: string, query?: Query): Promise<T>;
19
- getPrivate<T>(path: string, query?: Query): Promise<T>;
19
+ getPrivate<T>(path: string, query?: Query, opts?: {
20
+ raw?: boolean;
21
+ }): Promise<T>;
20
22
  postPrivate<T>(path: string, body?: Record<string, unknown>): Promise<T>;
21
23
  /**
22
24
  * GET 请求重试包装:网络错误最多重试 2 次(共 3 次尝试),延迟 300ms / 900ms。
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  HttpClient,
4
4
  flattenForSign
5
- } from "../chunk-CVOY2SOS.js";
5
+ } from "../chunk-3XZDH7DV.js";
6
+ import "../chunk-LVNSFQXS.js";
6
7
  import "../chunk-JRUFIHU7.js";
7
8
  import "../chunk-OOLZIYXS.js";
8
9
  import "../chunk-ZYO5P4DH.js";
9
- import "../chunk-LVNSFQXS.js";
10
10
  export {
11
11
  HttpClient,
12
12
  flattenForSign
package/dist/index.js CHANGED
@@ -3,6 +3,9 @@ import {
3
3
  effectiveFormat,
4
4
  render
5
5
  } from "./chunk-J3TAAD3Q.js";
6
+ import {
7
+ resolveBillNo
8
+ } from "./chunk-6DC55RF7.js";
6
9
  import {
7
10
  DEFAULT_CONFIG_PATH,
8
11
  checkConfigPermissions,
@@ -13,7 +16,8 @@ import {
13
16
  } from "./chunk-AKGCHJPQ.js";
14
17
  import {
15
18
  HttpClient
16
- } from "./chunk-CVOY2SOS.js";
19
+ } from "./chunk-3XZDH7DV.js";
20
+ import "./chunk-LVNSFQXS.js";
17
21
  import {
18
22
  ConfigError,
19
23
  DryRunInterception,
@@ -24,7 +28,6 @@ import {
24
28
  redactSecret
25
29
  } from "./chunk-OOLZIYXS.js";
26
30
  import "./chunk-ZYO5P4DH.js";
27
- import "./chunk-LVNSFQXS.js";
28
31
 
29
32
  // src/index.ts
30
33
  import { Command } from "commander";
@@ -570,6 +573,106 @@ function registerBalanceBills(parent, ctx) {
570
573
  );
571
574
  }
572
575
 
576
+ // src/commands/balance/funding.ts
577
+ function registerBalanceFunding(parent, ctx) {
578
+ parent.command("funding").description("Get funding account (USDT) balance (GET /v2/balance/funding)").action(async () => {
579
+ const c = ctx.http({ requireAuth: true });
580
+ const data = await c.getPrivate("/v2/balance/funding", {});
581
+ const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
582
+ ctx.stdout(
583
+ render2(data, {
584
+ format: effectiveFormat2(ctx.outputFormat, ctx.isTTY),
585
+ isTTY: ctx.isTTY
586
+ })
587
+ );
588
+ });
589
+ }
590
+
591
+ // src/core/dry-run.ts
592
+ function renderDryRun(p) {
593
+ const lines = [
594
+ "--- DRY RUN (no request sent) ---",
595
+ `${p.method} ${p.url}`
596
+ ];
597
+ if (p.body !== void 0) {
598
+ lines.push("Body:");
599
+ lines.push(JSON.stringify(p.body, null, 2));
600
+ }
601
+ lines.push("");
602
+ lines.push("To execute, re-run with --confirm.");
603
+ return lines.join("\n");
604
+ }
605
+
606
+ // src/core/execute-write.ts
607
+ async function executeWrite(opts) {
608
+ if (!opts.confirm) {
609
+ const url = `${opts.config?.baseUrl ?? ""}${opts.path}`;
610
+ const preview = renderDryRun({ method: opts.method, url, body: opts.body });
611
+ throw new DryRunInterception(preview);
612
+ }
613
+ return opts.send();
614
+ }
615
+
616
+ // src/commands/_write/_helpers.ts
617
+ function pruneEmpty(o) {
618
+ const out = {};
619
+ for (const [k, v] of Object.entries(o)) {
620
+ if (v != null && v !== "") out[k] = v;
621
+ }
622
+ return out;
623
+ }
624
+ async function runWrite(ctx, path, body) {
625
+ const cfg = ctx.resolveConfig({ requireAuth: true });
626
+ const c = ctx.http({ requireAuth: true });
627
+ const data = await executeWrite({
628
+ confirm: ctx.confirm ?? false,
629
+ method: "POST",
630
+ path,
631
+ body,
632
+ send: () => c.postPrivate(path, body),
633
+ config: cfg
634
+ });
635
+ ctx.stdout(
636
+ render(data, {
637
+ format: effectiveFormat(ctx.outputFormat, ctx.isTTY),
638
+ isTTY: ctx.isTTY
639
+ })
640
+ );
641
+ }
642
+
643
+ // src/commands/balance/transfer.ts
644
+ function registerBalanceTransfer(parent, ctx) {
645
+ parent.command("transfer").description("Transfer funds between accounts (POST /v2/balance/transfer)").requiredOption("--from <account>", "source account: FUNDING | CONTRACT").requiredOption("--to <account>", "destination account: FUNDING | CONTRACT").requiredOption("--amount <amount>", "transfer amount, must be greater than 0").option("--bill-no <id>", "idempotency key; auto-generated when omitted").option("--remark <text>", "optional note").action(
646
+ async (opts) => {
647
+ if (opts.from === opts.to) {
648
+ throw new Error("--from and --to must differ (the API rejects identical accounts)");
649
+ }
650
+ await runWrite(ctx, "/v2/balance/transfer", {
651
+ billNo: resolveBillNo(opts.billNo),
652
+ from: opts.from,
653
+ to: opts.to,
654
+ amount: opts.amount,
655
+ ...opts.remark != null && opts.remark !== "" ? { remark: opts.remark } : {}
656
+ });
657
+ }
658
+ );
659
+ }
660
+
661
+ // src/commands/bot/redeeming-info.ts
662
+ function registerBotRedeemingInfo(parent, ctx) {
663
+ parent.command("redeeming-info").description("Get bot redemption queue summary (GET /v2/bot/redeeming-info)").action(async () => {
664
+ const c = ctx.http({ requireAuth: true });
665
+ const data = await c.getPrivate("/v2/bot/redeeming-info", {});
666
+ const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
667
+ ctx.stdout(
668
+ render2(data, {
669
+ format: effectiveFormat2(ctx.outputFormat, ctx.isTTY),
670
+ isTTY: ctx.isTTY
671
+ })
672
+ );
673
+ });
674
+ }
675
+
573
676
  // src/commands/order/list-unfinished.ts
574
677
  function prune13(o) {
575
678
  const out = {};
@@ -926,10 +1029,14 @@ function registerPositionConfs(parent, ctx) {
926
1029
  function registerPositionHistory(parent, ctx) {
927
1030
  parent.command("history").description("Get historical position detail (GET /v2/position/history/detail)").requiredOption("--symbol <symbol>", "trading pair").requiredOption("--position-id <id>", "position id").action(async (opts) => {
928
1031
  const c = ctx.http({ requireAuth: true });
929
- const data = await c.getPrivate("/v2/position/history/detail", {
930
- symbol: opts.symbol,
931
- positionId: opts.positionId
932
- });
1032
+ const data = await c.getPrivate(
1033
+ "/v2/position/history/detail",
1034
+ {
1035
+ symbol: opts.symbol,
1036
+ positionId: opts.positionId
1037
+ },
1038
+ { raw: true }
1039
+ );
933
1040
  const { render: render2, effectiveFormat: effectiveFormat2 } = await import("./output-UIZGG7EU.js");
934
1041
  ctx.stdout(
935
1042
  render2(data, {
@@ -1168,58 +1275,6 @@ function registerConfigRemove(parent, ctx) {
1168
1275
  });
1169
1276
  }
1170
1277
 
1171
- // src/core/dry-run.ts
1172
- function renderDryRun(p) {
1173
- const lines = [
1174
- "--- DRY RUN (no request sent) ---",
1175
- `${p.method} ${p.url}`
1176
- ];
1177
- if (p.body !== void 0) {
1178
- lines.push("Body:");
1179
- lines.push(JSON.stringify(p.body, null, 2));
1180
- }
1181
- lines.push("");
1182
- lines.push("To execute, re-run with --confirm.");
1183
- return lines.join("\n");
1184
- }
1185
-
1186
- // src/core/execute-write.ts
1187
- async function executeWrite(opts) {
1188
- if (!opts.confirm) {
1189
- const url = `${opts.config?.baseUrl ?? ""}${opts.path}`;
1190
- const preview = renderDryRun({ method: opts.method, url, body: opts.body });
1191
- throw new DryRunInterception(preview);
1192
- }
1193
- return opts.send();
1194
- }
1195
-
1196
- // src/commands/_write/_helpers.ts
1197
- function pruneEmpty(o) {
1198
- const out = {};
1199
- for (const [k, v] of Object.entries(o)) {
1200
- if (v != null && v !== "") out[k] = v;
1201
- }
1202
- return out;
1203
- }
1204
- async function runWrite(ctx, path, body) {
1205
- const cfg = ctx.resolveConfig({ requireAuth: true });
1206
- const c = ctx.http({ requireAuth: true });
1207
- const data = await executeWrite({
1208
- confirm: ctx.confirm ?? false,
1209
- method: "POST",
1210
- path,
1211
- body,
1212
- send: () => c.postPrivate(path, body),
1213
- config: cfg
1214
- });
1215
- ctx.stdout(
1216
- render(data, {
1217
- format: effectiveFormat(ctx.outputFormat, ctx.isTTY),
1218
- isTTY: ctx.isTTY
1219
- })
1220
- );
1221
- }
1222
-
1223
1278
  // src/commands/order/create.ts
1224
1279
  function registerOrderCreate(parent, ctx) {
1225
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(
@@ -1508,7 +1563,7 @@ function buildProgram(opts) {
1508
1563
  return new HttpClient(cfg, opts?.fetch);
1509
1564
  }
1510
1565
  };
1511
- const program = new Command("onebullex").version(true ? "1.3.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();
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();
1512
1567
  program.hook("preAction", (thisCommand, _actionCommand) => {
1513
1568
  const flags = thisCommand.opts();
1514
1569
  if (flags.output === "table" || flags.output === "json") {
@@ -1565,6 +1620,10 @@ function buildProgram(opts) {
1565
1620
  registerBalanceList(balance, ctx);
1566
1621
  registerBalanceGet(balance, ctx);
1567
1622
  registerBalanceBills(balance, ctx);
1623
+ registerBalanceFunding(balance, ctx);
1624
+ registerBalanceTransfer(balance, ctx);
1625
+ const bot = program.command("bot").description("Bot redemption queue");
1626
+ registerBotRedeemingInfo(bot, ctx);
1568
1627
  const order = program.command("order").description("Orders");
1569
1628
  registerOrderListUnfinished(order, ctx);
1570
1629
  registerOrderListUnfinishedMulti(order, ctx);
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-PEVZEMJG.js";
4
+ } from "./chunk-KS5FFAKW.js";
5
5
 
6
6
  // scripts/check-cli-coverage.ts
7
7
  import { readFileSync } from "fs";
@@ -130,8 +130,14 @@ var GLOBAL_FLAGS = /* @__PURE__ */ new Set([
130
130
  function normalize(name) {
131
131
  return name.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
132
132
  }
133
+ var CLI_DEFAULTED_PARAMS = /* @__PURE__ */ new Map([
134
+ // billNo is an idempotency key; cli/src/core/bill-no.ts generates one when
135
+ // the caller omits it.
136
+ ["POST /v2/balance/transfer", /* @__PURE__ */ new Set(["bill-no"])]
137
+ ]);
133
138
  function diffParams(endpoint, docParams, cliParams) {
134
139
  const errors = [];
140
+ const defaulted = CLI_DEFAULTED_PARAMS.get(`${endpoint.method} ${endpoint.path}`) ?? /* @__PURE__ */ new Set();
135
141
  const docMap = new Map(
136
142
  docParams.map((p) => [normalize(p.name), p])
137
143
  );
@@ -143,7 +149,7 @@ function diffParams(endpoint, docParams, cliParams) {
143
149
  const cp = cliMap.get(k);
144
150
  if (!cp) {
145
151
  errors.push(`${endpoint.method} ${endpoint.path}: missing required CLI option --${k}`);
146
- } else if (!cp.required) {
152
+ } else if (!cp.required && !defaulted.has(k)) {
147
153
  errors.push(
148
154
  `${endpoint.method} ${endpoint.path}: --${k} should be requiredOption (doc says \u5FC5\u586B=\u662F)`
149
155
  );
package/dist/registry.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  REGISTRY
4
- } from "./chunk-PEVZEMJG.js";
4
+ } from "./chunk-KS5FFAKW.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.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "OneBullEx Open API V2 command-line interface",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,6 +16,7 @@
16
16
  "./core/errors": "./dist/core/errors.js",
17
17
  "./core/signature": "./dist/core/signature.js",
18
18
  "./core/types": "./dist/core/types.js",
19
+ "./core/bill-no": "./dist/core/bill-no.js",
19
20
  "./core/redact": "./dist/core/redact.js"
20
21
  },
21
22
  "files": [