blofin-cli 0.1.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/README.md +176 -0
- package/dist/chunk-ZOIYPWGU.js +53 -0
- package/dist/index.js +359 -0
- package/dist/setup-2N757DQH.js +50 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# blofin-cli
|
|
2
|
+
|
|
3
|
+
Agent-first CLI for BloFin exchange. Exposes all 30 tools from `blofin-core` as grouped shell commands.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g blofin-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Configure API credentials (interactive)
|
|
15
|
+
blofin setup
|
|
16
|
+
|
|
17
|
+
# Get ticker prices (table output by default)
|
|
18
|
+
blofin market tickers --instId=BTC-USDT
|
|
19
|
+
|
|
20
|
+
# JSON output
|
|
21
|
+
blofin market tickers --instId=BTC-USDT -o json
|
|
22
|
+
|
|
23
|
+
# Use demo trading environment
|
|
24
|
+
blofin --demo market tickers
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
Run the interactive setup wizard to save API credentials to `~/.config/blofin/config.json`:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
blofin setup
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The wizard prompts for API key, secret key, passphrase, and demo mode preference. Existing values are shown masked (`...xxxx`); press Enter to keep them.
|
|
36
|
+
|
|
37
|
+
Alternatively, use environment variables (they take priority over the config file):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
export BLOFIN_API_KEY="your-key"
|
|
41
|
+
export BLOFIN_API_SECRET="your-secret"
|
|
42
|
+
export BLOFIN_PASSPHRASE="your-passphrase"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
blofin [flags] <group> <subcommand> [--param=value ...]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Market Data (no auth required)
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
blofin market tickers --instId=BTC-USDT
|
|
55
|
+
blofin market instruments
|
|
56
|
+
blofin market orderbook --instId=BTC-USDT --size=20
|
|
57
|
+
blofin market candles --instId=BTC-USDT --bar=1H --limit=24
|
|
58
|
+
blofin market mark-price --instId=BTC-USDT
|
|
59
|
+
blofin market trades --instId=BTC-USDT
|
|
60
|
+
blofin market funding-rate --instId=BTC-USDT
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Account
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
blofin account balance --productType=USDT-FUTURES
|
|
67
|
+
blofin account positions
|
|
68
|
+
blofin account config
|
|
69
|
+
blofin account leverage --instId=BTC-USDT --marginMode=cross # query
|
|
70
|
+
blofin account leverage --instId=BTC-USDT --marginMode=cross --leverage=10 # set
|
|
71
|
+
blofin account margin-mode # query
|
|
72
|
+
blofin account margin-mode --marginMode=cross # set
|
|
73
|
+
blofin account position-mode
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Trading
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Dangerous operations require --confirm
|
|
80
|
+
blofin trade place --instId=BTC-USDT --side=buy --orderType=market \
|
|
81
|
+
--size=0.01 --marginMode=cross --positionSide=net --confirm
|
|
82
|
+
|
|
83
|
+
blofin trade cancel --orderId=123456 --confirm
|
|
84
|
+
|
|
85
|
+
blofin trade close --instId=BTC-USDT --marginMode=cross \
|
|
86
|
+
--positionSide=net --confirm
|
|
87
|
+
|
|
88
|
+
# Read operations
|
|
89
|
+
blofin trade orders --instId=BTC-USDT # pending orders
|
|
90
|
+
blofin trade orders --orderId=123456 # order detail
|
|
91
|
+
blofin trade orders --status=filled # order history
|
|
92
|
+
blofin trade fills --instId=BTC-USDT
|
|
93
|
+
|
|
94
|
+
# TP/SL and algo orders
|
|
95
|
+
blofin trade tpsl --instId=BTC-USDT --side=sell --marginMode=cross \
|
|
96
|
+
--positionSide=net --tpTriggerPrice=55000 --tpOrderPrice=-1 --size=-1
|
|
97
|
+
blofin trade tpsl-orders --instId=BTC-USDT
|
|
98
|
+
blofin trade cancel-tpsl --tpslId=123456
|
|
99
|
+
blofin trade algo --instId=BTC-USDT --orderType=trigger \
|
|
100
|
+
--side=buy --size=0.01 --triggerPrice=50000 --marginMode=cross --positionSide=net
|
|
101
|
+
blofin trade algo-orders --orderType=trigger
|
|
102
|
+
blofin trade cancel-algo --algoId=123456
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Assets
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
blofin asset balances --accountType=futures
|
|
109
|
+
blofin asset transfer --currency=USDT --fromAccount=funding \
|
|
110
|
+
--toAccount=futures --amount=100 --confirm
|
|
111
|
+
blofin asset bills
|
|
112
|
+
blofin asset deposits
|
|
113
|
+
blofin asset withdrawals
|
|
114
|
+
blofin asset apikey-info
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Global Flags
|
|
118
|
+
|
|
119
|
+
| Flag | Description |
|
|
120
|
+
|------|-------------|
|
|
121
|
+
| `--help` | Show help (global or per-group) |
|
|
122
|
+
| `--version` | Show version |
|
|
123
|
+
| `-o, --output <format>` | Output format: `table` (default) or `json` |
|
|
124
|
+
| `--demo` | Use demo trading environment |
|
|
125
|
+
| `--confirm` | Required for dangerous operations |
|
|
126
|
+
| `--read-only` | Only expose read-level tools |
|
|
127
|
+
| `--modules=<list>` | Comma-separated module filter (e.g. `public,account`) |
|
|
128
|
+
|
|
129
|
+
## Output Formats
|
|
130
|
+
|
|
131
|
+
### Table (default)
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
┌────────────┬──────────┬──────────┐
|
|
135
|
+
│ instId │ last │ vol24h │
|
|
136
|
+
├────────────┼──────────┼──────────┤
|
|
137
|
+
│ BTC-USDT │ 67000.5 │ 12345.67 │
|
|
138
|
+
└────────────┴──────────┴──────────┘
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### JSON (`-o json`)
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"tool": "get_tickers",
|
|
146
|
+
"ok": true,
|
|
147
|
+
"data": { "code": "0", "data": [...] },
|
|
148
|
+
"timestamp": 1700000000000
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Credential Resolution
|
|
153
|
+
|
|
154
|
+
Priority (highest to lowest):
|
|
155
|
+
|
|
156
|
+
1. **Environment variables** — `BLOFIN_API_KEY`, `BLOFIN_API_SECRET`, `BLOFIN_PASSPHRASE`
|
|
157
|
+
2. **Config file** — `~/.config/blofin/config.json` (created by `blofin setup`)
|
|
158
|
+
3. **Defaults** — empty strings (public endpoints only)
|
|
159
|
+
|
|
160
|
+
The `--demo` flag and `BLOFIN_BASE_URL` env var control which environment is used:
|
|
161
|
+
|
|
162
|
+
| Condition | Base URL |
|
|
163
|
+
|-----------|----------|
|
|
164
|
+
| `--demo` flag | `https://demo-trading-openapi.blofin.com` |
|
|
165
|
+
| `demo: true` in config | `https://demo-trading-openapi.blofin.com` |
|
|
166
|
+
| `BLOFIN_BASE_URL` set | Uses that URL (overrides all) |
|
|
167
|
+
| Otherwise | `https://openapi.blofin.com` (production) |
|
|
168
|
+
|
|
169
|
+
## Command Groups
|
|
170
|
+
|
|
171
|
+
| Group | Description | Subcommands |
|
|
172
|
+
|-------|-------------|-------------|
|
|
173
|
+
| `market` | Market data (public) | `instruments`, `tickers`, `orderbook`, `trades`, `candles`, `mark-price`, `funding-rate` |
|
|
174
|
+
| `account` | Account information | `balance`, `positions`, `config`, `leverage`, `margin-mode`, `position-mode` |
|
|
175
|
+
| `trade` | Trading operations | `place`, `cancel`, `close`, `orders`, `tpsl`, `cancel-tpsl`, `tpsl-orders`, `algo`, `cancel-algo`, `algo-orders`, `fills` |
|
|
176
|
+
| `asset` | Asset management | `balances`, `transfer`, `bills`, `deposits`, `withdrawals`, `apikey-info` |
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { DEMO_BASE_URL, PROD_BASE_URL } from "blofin-core";
|
|
6
|
+
function configDir() {
|
|
7
|
+
return join(homedir(), ".config", "blofin");
|
|
8
|
+
}
|
|
9
|
+
function configFile() {
|
|
10
|
+
return join(configDir(), "config.json");
|
|
11
|
+
}
|
|
12
|
+
function getConfigPath() {
|
|
13
|
+
return configFile();
|
|
14
|
+
}
|
|
15
|
+
function defaultConfig() {
|
|
16
|
+
return { apiKey: "", secretKey: "", passphrase: "", demo: false };
|
|
17
|
+
}
|
|
18
|
+
function loadCliConfig() {
|
|
19
|
+
try {
|
|
20
|
+
const raw = readFileSync(configFile(), "utf-8");
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
return {
|
|
23
|
+
apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : "",
|
|
24
|
+
secretKey: typeof parsed.secretKey === "string" ? parsed.secretKey : "",
|
|
25
|
+
passphrase: typeof parsed.passphrase === "string" ? parsed.passphrase : "",
|
|
26
|
+
demo: typeof parsed.demo === "boolean" ? parsed.demo : false
|
|
27
|
+
};
|
|
28
|
+
} catch {
|
|
29
|
+
return defaultConfig();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function saveCliConfig(config) {
|
|
33
|
+
mkdirSync(configDir(), { recursive: true, mode: 448 });
|
|
34
|
+
writeFileSync(configFile(), JSON.stringify(config, null, 2) + "\n", {
|
|
35
|
+
mode: 384
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function resolveCredentials(flags) {
|
|
39
|
+
const fileConfig = loadCliConfig();
|
|
40
|
+
const apiKey = process.env.BLOFIN_API_KEY ?? fileConfig.apiKey ?? "";
|
|
41
|
+
const secretKey = process.env.BLOFIN_API_SECRET ?? fileConfig.secretKey ?? "";
|
|
42
|
+
const passphrase = process.env.BLOFIN_PASSPHRASE ?? fileConfig.passphrase ?? "";
|
|
43
|
+
const demo = flags.demo ?? fileConfig.demo;
|
|
44
|
+
const baseUrl = process.env.BLOFIN_BASE_URL ?? (demo ? DEMO_BASE_URL : PROD_BASE_URL);
|
|
45
|
+
return { apiKey, secretKey, passphrase, baseUrl };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
getConfigPath,
|
|
50
|
+
loadCliConfig,
|
|
51
|
+
saveCliConfig,
|
|
52
|
+
resolveCredentials
|
|
53
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
resolveCredentials
|
|
4
|
+
} from "./chunk-ZOIYPWGU.js";
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
import { readFileSync } from "fs";
|
|
10
|
+
import { parseArgs } from "util";
|
|
11
|
+
import {
|
|
12
|
+
ALL_MODULES,
|
|
13
|
+
buildTools,
|
|
14
|
+
loadConfig,
|
|
15
|
+
BlofinClient
|
|
16
|
+
} from "blofin-core";
|
|
17
|
+
|
|
18
|
+
// src/commands.ts
|
|
19
|
+
function group(description, commands) {
|
|
20
|
+
const map = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const [sub, toolName, desc, risk] of commands) {
|
|
22
|
+
map.set(sub, { toolName, description: desc, riskLevel: risk });
|
|
23
|
+
}
|
|
24
|
+
return { description, commands: map };
|
|
25
|
+
}
|
|
26
|
+
var COMMAND_GROUPS = /* @__PURE__ */ new Map([
|
|
27
|
+
[
|
|
28
|
+
"market",
|
|
29
|
+
group("Market data (public)", [
|
|
30
|
+
["instruments", "get_instruments", "List available instruments", "read"],
|
|
31
|
+
["tickers", "get_tickers", "Get ticker prices", "read"],
|
|
32
|
+
["orderbook", "get_order_book", "Get order book depth", "read"],
|
|
33
|
+
["trades", "get_market_trades", "Get recent trades", "read"],
|
|
34
|
+
["candles", "get_candlesticks", "Get candlestick/kline data", "read"],
|
|
35
|
+
["mark-price", "get_mark_price", "Get mark price", "read"],
|
|
36
|
+
["funding-rate", "get_funding_rate", "Get funding rate", "read"]
|
|
37
|
+
])
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
"account",
|
|
41
|
+
group("Account information", [
|
|
42
|
+
["balance", "get_balance", "Get account balance", "read"],
|
|
43
|
+
["positions", "get_positions", "Get open positions", "read"],
|
|
44
|
+
["config", "get_account_config", "Get account configuration", "read"],
|
|
45
|
+
["leverage", "manage_leverage", "Get or set leverage", "write"],
|
|
46
|
+
["margin-mode", "manage_margin_mode", "Get or set margin mode", "write"],
|
|
47
|
+
["position-mode", "manage_position_mode", "Get or set position mode", "write"]
|
|
48
|
+
])
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
"trade",
|
|
52
|
+
group("Trading operations", [
|
|
53
|
+
["place", "place_order", "Place a new order", "dangerous"],
|
|
54
|
+
["cancel", "cancel_order", "Cancel an order", "dangerous"],
|
|
55
|
+
["close", "close_position", "Close a position", "dangerous"],
|
|
56
|
+
["orders", "get_orders", "Get order list", "read"],
|
|
57
|
+
["tpsl", "place_tpsl", "Place take-profit/stop-loss", "write"],
|
|
58
|
+
["cancel-tpsl", "cancel_tpsl", "Cancel TP/SL order", "write"],
|
|
59
|
+
["tpsl-orders", "get_tpsl_orders", "Get TP/SL orders", "read"],
|
|
60
|
+
["algo", "place_algo_order", "Place an algo order", "write"],
|
|
61
|
+
["cancel-algo", "cancel_algo_order", "Cancel an algo order", "write"],
|
|
62
|
+
["algo-orders", "get_algo_orders", "Get algo orders", "read"],
|
|
63
|
+
["fills", "get_fills_history", "Get fill history", "read"]
|
|
64
|
+
])
|
|
65
|
+
],
|
|
66
|
+
[
|
|
67
|
+
"asset",
|
|
68
|
+
group("Asset management", [
|
|
69
|
+
["balances", "get_asset_balances", "Get asset balances", "read"],
|
|
70
|
+
["transfer", "fund_transfer", "Transfer between accounts", "dangerous"],
|
|
71
|
+
["bills", "get_transfer_history", "Get transfer history", "read"],
|
|
72
|
+
["deposits", "get_deposit_history", "Get deposit history", "read"],
|
|
73
|
+
["withdrawals", "get_withdrawal_history", "Get withdrawal history", "read"],
|
|
74
|
+
["apikey-info", "get_apikey_info", "Get API key info", "read"]
|
|
75
|
+
])
|
|
76
|
+
]
|
|
77
|
+
]);
|
|
78
|
+
function resolveCommand(groupName, subcommand) {
|
|
79
|
+
return COMMAND_GROUPS.get(groupName)?.commands.get(subcommand);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/output.ts
|
|
83
|
+
import Table from "cli-table3";
|
|
84
|
+
import { toToolResponse, toToolError } from "blofin-core";
|
|
85
|
+
function outputTable(rows, columns, title) {
|
|
86
|
+
if (rows.length === 0) return title ? `${title}
|
|
87
|
+
(no data)` : "(no data)";
|
|
88
|
+
const cols = columns ?? Object.keys(rows[0]);
|
|
89
|
+
const table = new Table({ head: [...cols] });
|
|
90
|
+
for (const row of rows) {
|
|
91
|
+
table.push(cols.map((c) => String(row[c] ?? "")));
|
|
92
|
+
}
|
|
93
|
+
const rendered = table.toString();
|
|
94
|
+
return title ? `${title}
|
|
95
|
+
${rendered}` : rendered;
|
|
96
|
+
}
|
|
97
|
+
function outputResult(toolName, data, format) {
|
|
98
|
+
if (format === "json") {
|
|
99
|
+
return JSON.stringify(toToolResponse(toolName, data), null, 2);
|
|
100
|
+
}
|
|
101
|
+
const rows = extractRows(data);
|
|
102
|
+
if (rows.length > 0) {
|
|
103
|
+
return outputTable(rows);
|
|
104
|
+
}
|
|
105
|
+
return JSON.stringify(data, null, 2);
|
|
106
|
+
}
|
|
107
|
+
function outputError(toolName, error, format) {
|
|
108
|
+
if (format === "json") {
|
|
109
|
+
return JSON.stringify(toToolError(toolName, error), null, 2);
|
|
110
|
+
}
|
|
111
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
112
|
+
return `Error: ${msg}`;
|
|
113
|
+
}
|
|
114
|
+
function extractRows(data) {
|
|
115
|
+
if (data == null || typeof data !== "object") return [];
|
|
116
|
+
const envelope = data;
|
|
117
|
+
const inner = envelope.data;
|
|
118
|
+
if (Array.isArray(inner) && inner.length > 0 && isRecord(inner[0])) {
|
|
119
|
+
return inner;
|
|
120
|
+
}
|
|
121
|
+
if (isRecord(inner)) {
|
|
122
|
+
return [inner];
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(data) && data.length > 0 && isRecord(data[0])) {
|
|
125
|
+
return data;
|
|
126
|
+
}
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
function isRecord(v) {
|
|
130
|
+
return v != null && typeof v === "object" && !Array.isArray(v);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/index.ts
|
|
134
|
+
var CLI_VERSION = (() => {
|
|
135
|
+
const pkgPath = new URL("../package.json", import.meta.url);
|
|
136
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
137
|
+
return pkg.version;
|
|
138
|
+
})();
|
|
139
|
+
var VALID_MODULES = new Set(ALL_MODULES);
|
|
140
|
+
var CLI_FLAGS = /* @__PURE__ */ new Set([
|
|
141
|
+
"help",
|
|
142
|
+
"version",
|
|
143
|
+
"read-only",
|
|
144
|
+
"confirm",
|
|
145
|
+
"modules",
|
|
146
|
+
"output",
|
|
147
|
+
"demo"
|
|
148
|
+
]);
|
|
149
|
+
function formatGlobalHelp() {
|
|
150
|
+
const lines = [
|
|
151
|
+
`blofin-cli v${CLI_VERSION}`,
|
|
152
|
+
"",
|
|
153
|
+
"Usage: blofin [flags] <group> <subcommand> [--param=value ...]",
|
|
154
|
+
"",
|
|
155
|
+
"Groups:"
|
|
156
|
+
];
|
|
157
|
+
for (const [name, group2] of COMMAND_GROUPS) {
|
|
158
|
+
lines.push(` ${name.padEnd(12)} ${group2.description}`);
|
|
159
|
+
}
|
|
160
|
+
lines.push(
|
|
161
|
+
"",
|
|
162
|
+
"Commands:",
|
|
163
|
+
" setup Configure API credentials",
|
|
164
|
+
"",
|
|
165
|
+
"Global flags:",
|
|
166
|
+
" --help Show help",
|
|
167
|
+
" --version Show version",
|
|
168
|
+
" -o, --output Output format: table (default) | json",
|
|
169
|
+
" --demo Use demo trading environment",
|
|
170
|
+
" --read-only Exclude write/dangerous tools",
|
|
171
|
+
" --confirm Allow dangerous operations",
|
|
172
|
+
" --modules Comma-separated module filter",
|
|
173
|
+
"",
|
|
174
|
+
"Run 'blofin <group> --help' for subcommand details."
|
|
175
|
+
);
|
|
176
|
+
return lines.join("\n");
|
|
177
|
+
}
|
|
178
|
+
function formatGroupHelp(groupName) {
|
|
179
|
+
const group2 = COMMAND_GROUPS.get(groupName);
|
|
180
|
+
if (!group2) return `Unknown group: ${groupName}`;
|
|
181
|
+
const lines = [
|
|
182
|
+
`blofin ${groupName} \u2014 ${group2.description}`,
|
|
183
|
+
"",
|
|
184
|
+
"Subcommands:"
|
|
185
|
+
];
|
|
186
|
+
const maxSub = Math.max(
|
|
187
|
+
...[...group2.commands.keys()].map((k) => k.length)
|
|
188
|
+
);
|
|
189
|
+
for (const [sub, cmd] of group2.commands) {
|
|
190
|
+
const tag = `[${cmd.riskLevel}]`;
|
|
191
|
+
lines.push(` ${sub.padEnd(maxSub + 2)} ${tag.padEnd(14)} ${cmd.description}`);
|
|
192
|
+
}
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
195
|
+
function parseModules(raw) {
|
|
196
|
+
const ids = raw.split(",");
|
|
197
|
+
const invalid = ids.filter((id) => !VALID_MODULES.has(id));
|
|
198
|
+
if (invalid.length > 0) {
|
|
199
|
+
return `Invalid module(s): ${invalid.join(", ")}. Valid modules: ${ALL_MODULES.join(", ")}`;
|
|
200
|
+
}
|
|
201
|
+
return ids;
|
|
202
|
+
}
|
|
203
|
+
var SHORT_VALUE_FLAGS = /* @__PURE__ */ new Map([
|
|
204
|
+
["-o", "output"]
|
|
205
|
+
]);
|
|
206
|
+
function expandShortFlags(args) {
|
|
207
|
+
const result = [];
|
|
208
|
+
for (let i = 0; i < args.length; i++) {
|
|
209
|
+
const longName = SHORT_VALUE_FLAGS.get(args[i]);
|
|
210
|
+
if (longName && i + 1 < args.length) {
|
|
211
|
+
result.push(`--${longName}=${args[i + 1]}`);
|
|
212
|
+
i++;
|
|
213
|
+
} else {
|
|
214
|
+
result.push(args[i]);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
async function runCli(args) {
|
|
220
|
+
const normalizedArgs = expandShortFlags(args);
|
|
221
|
+
const { values, positionals } = parseArgs({
|
|
222
|
+
args: normalizedArgs,
|
|
223
|
+
strict: false,
|
|
224
|
+
allowPositionals: true
|
|
225
|
+
});
|
|
226
|
+
const help = values["help"] === true;
|
|
227
|
+
const version = values["version"] === true;
|
|
228
|
+
const readOnly = values["read-only"] === true;
|
|
229
|
+
const confirm = values["confirm"] === true;
|
|
230
|
+
const demo = values["demo"] === true ? true : void 0;
|
|
231
|
+
const modulesRaw = typeof values["modules"] === "string" ? values["modules"] : void 0;
|
|
232
|
+
const outputFlag = typeof values["output"] === "string" ? values["output"] : void 0;
|
|
233
|
+
const format = outputFlag === "json" ? "json" : "table";
|
|
234
|
+
if (version) {
|
|
235
|
+
return { output: CLI_VERSION, ok: true };
|
|
236
|
+
}
|
|
237
|
+
let modules;
|
|
238
|
+
if (modulesRaw) {
|
|
239
|
+
const parsed = parseModules(modulesRaw);
|
|
240
|
+
if (typeof parsed === "string") {
|
|
241
|
+
return {
|
|
242
|
+
output: outputError("cli", new Error(parsed), format),
|
|
243
|
+
ok: false
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
modules = parsed;
|
|
247
|
+
}
|
|
248
|
+
if (positionals.length === 0 || help && positionals.length === 0) {
|
|
249
|
+
return { output: formatGlobalHelp(), ok: true };
|
|
250
|
+
}
|
|
251
|
+
const first = positionals[0];
|
|
252
|
+
if (first === "setup") {
|
|
253
|
+
const { runSetup } = await import("./setup-2N757DQH.js");
|
|
254
|
+
return runSetup();
|
|
255
|
+
}
|
|
256
|
+
const groupName = first;
|
|
257
|
+
const group2 = COMMAND_GROUPS.get(groupName);
|
|
258
|
+
if (!group2) {
|
|
259
|
+
return {
|
|
260
|
+
output: outputError(
|
|
261
|
+
"cli",
|
|
262
|
+
new Error(`Unknown command: ${first}. Run 'blofin --help' for usage.`),
|
|
263
|
+
format
|
|
264
|
+
),
|
|
265
|
+
ok: false
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (help || positionals.length < 2) {
|
|
269
|
+
return { output: formatGroupHelp(groupName), ok: true };
|
|
270
|
+
}
|
|
271
|
+
const subcommand = positionals[1];
|
|
272
|
+
const cmdDef = resolveCommand(groupName, subcommand);
|
|
273
|
+
if (!cmdDef) {
|
|
274
|
+
return {
|
|
275
|
+
output: outputError(
|
|
276
|
+
"cli",
|
|
277
|
+
new Error(
|
|
278
|
+
`Unknown subcommand: ${groupName} ${subcommand}. Run 'blofin ${groupName} --help' for available subcommands.`
|
|
279
|
+
),
|
|
280
|
+
format
|
|
281
|
+
),
|
|
282
|
+
ok: false
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const creds = resolveCredentials({ demo });
|
|
286
|
+
const config = loadConfig({
|
|
287
|
+
modules,
|
|
288
|
+
readOnly,
|
|
289
|
+
apiKey: creds.apiKey,
|
|
290
|
+
secretKey: creds.secretKey,
|
|
291
|
+
passphrase: creds.passphrase,
|
|
292
|
+
baseUrl: creds.baseUrl
|
|
293
|
+
});
|
|
294
|
+
const tools = buildTools(config);
|
|
295
|
+
const tool = tools.find((t) => t.name === cmdDef.toolName);
|
|
296
|
+
if (!tool) {
|
|
297
|
+
return {
|
|
298
|
+
output: outputError(
|
|
299
|
+
cmdDef.toolName,
|
|
300
|
+
new Error(
|
|
301
|
+
`Tool '${cmdDef.toolName}' not available (may be excluded by --read-only or --modules).`
|
|
302
|
+
),
|
|
303
|
+
format
|
|
304
|
+
),
|
|
305
|
+
ok: false
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
if (tool.riskLevel === "dangerous" && !confirm) {
|
|
309
|
+
return {
|
|
310
|
+
output: outputError(
|
|
311
|
+
tool.name,
|
|
312
|
+
new Error(
|
|
313
|
+
`Command '${groupName} ${subcommand}' is dangerous. Use --confirm to execute.`
|
|
314
|
+
),
|
|
315
|
+
format
|
|
316
|
+
),
|
|
317
|
+
ok: false
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const params = {};
|
|
321
|
+
for (const [key, value] of Object.entries(values)) {
|
|
322
|
+
if (CLI_FLAGS.has(key)) continue;
|
|
323
|
+
params[key] = value;
|
|
324
|
+
}
|
|
325
|
+
const client = new BlofinClient(config);
|
|
326
|
+
const ctx = { client, config };
|
|
327
|
+
try {
|
|
328
|
+
const result = await tool.handler(params, ctx);
|
|
329
|
+
return {
|
|
330
|
+
output: outputResult(tool.name, result, format),
|
|
331
|
+
ok: true
|
|
332
|
+
};
|
|
333
|
+
} catch (error) {
|
|
334
|
+
return {
|
|
335
|
+
output: outputError(tool.name, error, format),
|
|
336
|
+
ok: false
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function isMainModule() {
|
|
341
|
+
if (typeof process === "undefined" || !process.argv[1]) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
const self = fileURLToPath(import.meta.url);
|
|
345
|
+
const invoked = resolve(process.argv[1]);
|
|
346
|
+
return self === invoked;
|
|
347
|
+
}
|
|
348
|
+
if (isMainModule()) {
|
|
349
|
+
runCli(process.argv.slice(2)).then(({ output, ok }) => {
|
|
350
|
+
if (output) console.log(output);
|
|
351
|
+
if (!ok) process.exit(1);
|
|
352
|
+
}).catch((err) => {
|
|
353
|
+
console.error(err);
|
|
354
|
+
process.exit(1);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
export {
|
|
358
|
+
runCli
|
|
359
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getConfigPath,
|
|
3
|
+
loadCliConfig,
|
|
4
|
+
saveCliConfig
|
|
5
|
+
} from "./chunk-ZOIYPWGU.js";
|
|
6
|
+
|
|
7
|
+
// src/setup.ts
|
|
8
|
+
import { createInterface } from "readline/promises";
|
|
9
|
+
function mask(value) {
|
|
10
|
+
if (value.length <= 4) return value ? "****" : "(not set)";
|
|
11
|
+
return `...${value.slice(-4)}`;
|
|
12
|
+
}
|
|
13
|
+
async function runSetup() {
|
|
14
|
+
const rl = createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stderr
|
|
17
|
+
});
|
|
18
|
+
try {
|
|
19
|
+
const current = loadCliConfig();
|
|
20
|
+
process.stderr.write("\nBloFin CLI Setup\n");
|
|
21
|
+
process.stderr.write(`Config file: ${getConfigPath()}
|
|
22
|
+
|
|
23
|
+
`);
|
|
24
|
+
const apiKey = await promptField(rl, "API Key", current.apiKey);
|
|
25
|
+
const secretKey = await promptField(rl, "Secret Key", current.secretKey);
|
|
26
|
+
const passphrase = await promptField(rl, "Passphrase", current.passphrase);
|
|
27
|
+
const demoAnswer = await rl.question(
|
|
28
|
+
`Use demo trading? [${current.demo ? "Y/n" : "y/N"}]: `
|
|
29
|
+
);
|
|
30
|
+
const demo = demoAnswer.trim() === "" ? current.demo : demoAnswer.trim().toLowerCase().startsWith("y");
|
|
31
|
+
const config = { apiKey, secretKey, passphrase, demo };
|
|
32
|
+
saveCliConfig(config);
|
|
33
|
+
const msg = `
|
|
34
|
+
Config saved to ${getConfigPath()}
|
|
35
|
+
`;
|
|
36
|
+
process.stderr.write(msg);
|
|
37
|
+
return { output: "", ok: true };
|
|
38
|
+
} finally {
|
|
39
|
+
rl.close();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function promptField(rl, label, current) {
|
|
43
|
+
const answer = await rl.question(
|
|
44
|
+
`${label} [${mask(current)}]: `
|
|
45
|
+
);
|
|
46
|
+
return answer.trim() || current;
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
runSetup
|
|
50
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "blofin-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"blofin": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup src/index.ts --format esm",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"test": "vitest run"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"blofin-core": "^0.2.2",
|
|
16
|
+
"cli-table3": "^0.6.5"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^22.0.0",
|
|
20
|
+
"tsup": "^8.0.0",
|
|
21
|
+
"typescript": "^5.9.3",
|
|
22
|
+
"vitest": "^4.0.18"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT"
|
|
31
|
+
}
|