@cryptocom/cdcx-cli 1.2.2
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 +394 -0
- package/install.sh +251 -0
- package/npm/bin.mjs +34 -0
- package/npm/lib.mjs +52 -0
- package/npm/post-install.mjs +58 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# cdcx
|
|
2
|
+
|
|
3
|
+
A CLI, MCP server, and terminal dashboard for the [Crypto.com Exchange API](https://exchange-docs.crypto.com/exchange/v1/rest-ws/index.html). Single binary, zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
86 REST endpoints across 10 API groups, dynamically generated from the Crypto.com Exchange OpenAPI spec. Real-time WebSocket streaming. Full-screen TUI dashboard. Paper trading. Works as a standalone CLI, an MCP tool server for AI agents, or an interactive terminal.
|
|
6
|
+
|
|
7
|
+
> **Caution:** This software interacts with the live Crypto.com Exchange and can execute real financial transactions. Test with `cdcx paper` before using real funds.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
curl -sSfL https://raw.githubusercontent.com/crypto-com/cdcx-cli/main/install.sh | sh
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**From source:**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cargo install --git https://github.com/crypto-com/cdcx-cli.git --bin cdcx
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Why cdcx?
|
|
24
|
+
|
|
25
|
+
### For AI Agents
|
|
26
|
+
|
|
27
|
+
Every response is structured JSON. 86 MCP tools with typed parameters, enum validation, safety enforcement, and schema discovery — all generated from the OpenAPI spec at runtime. Your LLM can trade, analyze markets, and manage positions without custom tooling.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cdcx mcp --services market,account,trade
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"cdcx": {
|
|
37
|
+
"command": "npx",
|
|
38
|
+
"args": ["-y", "@cryptocom/cdcx-cli@latest", "mcp", "--services", "market,account,trade"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Compatible with Claude Code, Cursor, Claude Desktop, Codex, Github Copilot, Gemini CLI, and other MCP clients. Includes 13 agent skill files in `skills/` for guided workflows.
|
|
45
|
+
|
|
46
|
+
Things you can ask your AI agent:
|
|
47
|
+
|
|
48
|
+
> *"What's the current BTC price and 24h volume?"*
|
|
49
|
+
|
|
50
|
+
> *"Paper trade BTC for a few rounds and show me the P&L"*
|
|
51
|
+
|
|
52
|
+
> *"Place an OTOCO bracket on BTC_USDT: entry at 70000, stop-loss at 65000, take-profit at 75000"*
|
|
53
|
+
|
|
54
|
+
### For the Command Line
|
|
55
|
+
|
|
56
|
+
Every endpoint is a command. `--help` on everything. `--dry-run` to preview. `--output json` for scripting. Tab completion. Profiles for multi-account.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cdcx market ticker BTC_USDT -o table
|
|
60
|
+
cdcx trade order BUY BTC_USDT 0.001 --dry-run
|
|
61
|
+
cdcx stream ticker BTC_USDT ETH_USDT
|
|
62
|
+
cdcx paper buy BTC_USDT --quantity 0.01
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### For the Dashboard
|
|
66
|
+
|
|
67
|
+
Full-screen terminal trading interface. 6 tabs, real-time streaming, candlestick charts, heatmap mode, split screen, order workflows. Zero flicker.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cdcx tui
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Press `p` to toggle paper mode. Press `O` for OTOCO bracket orders. Press `?` for all shortcuts.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Quick Start
|
|
78
|
+
|
|
79
|
+
Public market data works without credentials:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
cdcx market ticker BTC_USDT -o table
|
|
83
|
+
cdcx market book BTC_USDT --depth 10 -o table
|
|
84
|
+
cdcx market candlestick BTC_USDT --timeframe 1h -o table
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Paper trading works without credentials:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
cdcx paper init --balance 50000
|
|
91
|
+
cdcx paper buy BTC_USDT --quantity 0.01
|
|
92
|
+
cdcx paper positions
|
|
93
|
+
cdcx paper balance
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For trading, set credentials:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
export CDCX_API_KEY="your-key"
|
|
100
|
+
export CDCX_API_SECRET="your-secret"
|
|
101
|
+
cdcx account summary
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## MCP Server
|
|
107
|
+
|
|
108
|
+
Expose the Exchange API as MCP tools for AI agents:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
cdcx mcp --services market,account,trade
|
|
112
|
+
cdcx mcp --services all --allow-dangerous
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Service groups (MCP): `market`, `account`, `trade`, `advanced`, `margin`, `staking`, `funding`, `fiat`, `otc`, `stream`, `all`
|
|
116
|
+
|
|
117
|
+
> **Note:** `account` also exposes historical endpoints (orders, trades, transactions). `funding` covers wallet deposit/withdrawal endpoints. Paper trading is a CLI-only feature and has no MCP tools.
|
|
118
|
+
|
|
119
|
+
### Safety Model
|
|
120
|
+
|
|
121
|
+
| Tier | Behavior | Examples |
|
|
122
|
+
|------|----------|---------|
|
|
123
|
+
| **read** | No confirmation | `market ticker`, `market book` |
|
|
124
|
+
| **sensitive_read** | No confirmation | `account summary`, `trade open-orders` |
|
|
125
|
+
| **mutate** | Requires `acknowledged: true` | `trade order`, `trade cancel` |
|
|
126
|
+
| **dangerous** | Requires `--allow-dangerous` | `trade cancel-all`, `wallet withdraw` |
|
|
127
|
+
|
|
128
|
+
### Agent Skills
|
|
129
|
+
|
|
130
|
+
13 skill files in `skills/` covering:
|
|
131
|
+
|
|
132
|
+
| Skill | Purpose |
|
|
133
|
+
|-------|---------|
|
|
134
|
+
| `cdcx-market-intel` | Market analysis and price discovery |
|
|
135
|
+
| `cdcx-portfolio-intel` | Portfolio analysis and risk assessment |
|
|
136
|
+
| `cdcx-execution` | Order placement with safety checks |
|
|
137
|
+
| `cdcx-advanced` | OCO, OTO, OTOCO contingency orders |
|
|
138
|
+
| `cdcx-paper-strategy` | Paper trading strategy testing |
|
|
139
|
+
| `cdcx-wallet-ops` | Deposits, withdrawals, network management |
|
|
140
|
+
| `cdcx-auth-setup` | Credential configuration |
|
|
141
|
+
| `cdcx-autonomy-levels` | Safety tier configuration |
|
|
142
|
+
| `cdcx-check-balance` | Balance and credential verification |
|
|
143
|
+
| `cdcx-place-limit-order` | Limit order workflow with preflight checks |
|
|
144
|
+
| `cdcx-isolated-margin` | Isolated margin trading (equity/RWA perpetuals) |
|
|
145
|
+
| `recipe-morning-brief` | Daily market briefing workflow |
|
|
146
|
+
| `recipe-emergency-flatten` | Emergency position flattening |
|
|
147
|
+
|
|
148
|
+
### Plugin Installation
|
|
149
|
+
|
|
150
|
+
Install cdcx as a one-click plugin from your AI coding tool's marketplace.
|
|
151
|
+
|
|
152
|
+
**Claude Code:**
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
claude plugin marketplace add crypto-com/cdcx-cli
|
|
156
|
+
claude plugin install cdcx-cli@cdcx-cli
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Codex CLI:**
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
codex plugin marketplace add crypto-com/cdcx-cli
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Other:** Open Settings > MCP Servers, add:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"cdcx": {
|
|
170
|
+
"command": "npx",
|
|
171
|
+
"args": ["-y", "@cryptocom/cdcx-cli@latest", "mcp", "--services", "market"]
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
To expand services beyond market data, update the `--services` flag:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
market,trade,account # Trading agent
|
|
180
|
+
market,trade,account,advanced # With OCO/OTOCO
|
|
181
|
+
all --allow-dangerous # Full access (withdrawals enabled)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## CLI Reference
|
|
187
|
+
|
|
188
|
+
### Market Data (public)
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
cdcx market ticker # All tickers
|
|
192
|
+
cdcx market ticker BTC_USDT # Single instrument
|
|
193
|
+
cdcx market book BTC_USDT --depth 20 # Order book
|
|
194
|
+
cdcx market trades BTC_USDT # Recent trades
|
|
195
|
+
cdcx market candlestick BTC_USDT --timeframe 1h
|
|
196
|
+
cdcx market instruments # All instruments
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Trading (requires auth)
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
cdcx trade order BUY BTC_USDT 0.001 --type LIMIT --price 50000
|
|
203
|
+
cdcx trade open-orders
|
|
204
|
+
cdcx trade cancel --order-id ORDER_ID
|
|
205
|
+
cdcx trade cancel-all
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Advanced Orders
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
cdcx advanced create-oto --instrument-name BTC_USDT ...
|
|
212
|
+
cdcx advanced create-otoco --instrument-name BTC_USDT ...
|
|
213
|
+
cdcx advanced open-orders
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Paper Trading
|
|
217
|
+
|
|
218
|
+
Local paper trading engine with live market prices. No auth required.
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
cdcx paper init --balance 50000 # Create account
|
|
222
|
+
cdcx paper buy BTC_USDT --quantity 0.01 # Market buy
|
|
223
|
+
cdcx paper sell BTC_USDT --quantity 0.01 # Market sell
|
|
224
|
+
cdcx paper buy BTC_USDT --quantity 0.01 --price 65000 # Limit buy
|
|
225
|
+
cdcx paper positions # Portfolio + P&L
|
|
226
|
+
cdcx paper history # Trade history
|
|
227
|
+
cdcx paper balance # Account balance
|
|
228
|
+
cdcx paper reset --balance 100000 # Reset account
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Streaming (WebSocket)
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
cdcx stream ticker BTC_USDT ETH_USDT # Real-time tickers
|
|
235
|
+
cdcx stream book BTC_USDT # Order book updates
|
|
236
|
+
cdcx stream trades BTC_USDT # Trade executions
|
|
237
|
+
cdcx stream orders # Your order updates (auth)
|
|
238
|
+
cdcx stream positions # Position changes (auth)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Account
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
cdcx account summary # Balances
|
|
245
|
+
cdcx account positions # Open positions
|
|
246
|
+
cdcx trade fee-rate # Fee rates
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### History / Wallet / Staking / Fiat / Margin
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
cdcx history orders cdcx wallet deposit-address --currency BTC
|
|
253
|
+
cdcx history trades cdcx wallet deposit-history
|
|
254
|
+
cdcx history transactions cdcx wallet withdrawal-history
|
|
255
|
+
cdcx staking instruments cdcx fiat accounts
|
|
256
|
+
cdcx staking positions cdcx margin transfer --dry-run
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Interactive Dashboard
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
cdcx tui # Launch
|
|
265
|
+
cdcx tui --theme amber # With theme
|
|
266
|
+
cdcx tui --setup # Setup wizard
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Features
|
|
270
|
+
|
|
271
|
+
- **6 tabs:** Market, Portfolio, Orders, History, Watchlist, Positions
|
|
272
|
+
- **Real-time streaming:** WebSocket ticker, order book, candlestick, trade channels
|
|
273
|
+
- **Sparklines:** 24h Braille-dot price charts inline per instrument
|
|
274
|
+
- **Heatmap mode:** rows glow red/green by 24h performance
|
|
275
|
+
- **Candlestick charts:** volume bars, 9 timeframes, streaming updates, time axis
|
|
276
|
+
- **Multi-chart compare:** up to 4 instruments side by side
|
|
277
|
+
- **Split screen:** table + chart, auto-updates on selection
|
|
278
|
+
- **Order book detail:** cumulative depth bars, buy/sell pressure bar
|
|
279
|
+
- **Order workflows:** place order, OCO, OTOCO, cancel
|
|
280
|
+
- **Live portfolio P&L:** session P&L in status bar, cash vs position breakdown in Portfolio tab
|
|
281
|
+
- **Paper mode:** toggle `p` to trade against local paper engine with unrealized/realized P&L
|
|
282
|
+
- **Instrument picker:** search-as-you-type overlay
|
|
283
|
+
- **Price alerts:** set thresholds with terminal bell notification
|
|
284
|
+
- **Ticker tape:** scrolling top movers banner
|
|
285
|
+
- **6 themes:** terminal-pro, cyber-midnight, monochrome, neon, micky-d, amber + custom TOML
|
|
286
|
+
- **Mouse support:** click to select, scroll, double-click to detail
|
|
287
|
+
- **Export:** `y` copies table as CSV to clipboard
|
|
288
|
+
|
|
289
|
+
### Keyboard Shortcuts
|
|
290
|
+
|
|
291
|
+
| Key | Action |
|
|
292
|
+
|-----|--------|
|
|
293
|
+
| `1`-`6` / `Tab` | Switch tabs |
|
|
294
|
+
| `Enter` | Instrument detail (book + trades) |
|
|
295
|
+
| `k` | Candlestick chart |
|
|
296
|
+
| `m` | Compare charts (up to 4) |
|
|
297
|
+
| `h` | Toggle heatmap |
|
|
298
|
+
| `i` | Instrument spotlight |
|
|
299
|
+
| `\` | Split screen (table + chart) |
|
|
300
|
+
| `[ / ]` | Cycle chart timeframe |
|
|
301
|
+
| `s / S` | Sort / reverse sort |
|
|
302
|
+
| `/` | Search instruments |
|
|
303
|
+
| `t` | Place order |
|
|
304
|
+
| `o` | OCO order (stop-loss + take-profit) |
|
|
305
|
+
| `O` | OTOCO order (entry + SL + TP) |
|
|
306
|
+
| `c` | Cancel orders |
|
|
307
|
+
| `p` | Toggle LIVE / PAPER mode |
|
|
308
|
+
| `!` | Set price alert |
|
|
309
|
+
| `y` | Copy to clipboard (CSV) |
|
|
310
|
+
| `?` | Help overlay |
|
|
311
|
+
| `q` | Quit |
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Configuration
|
|
316
|
+
|
|
317
|
+
### Credentials
|
|
318
|
+
|
|
319
|
+
Resolved in order: flags > `CDCX_API_KEY`/`CDCX_API_SECRET` env > `CDC_API_KEY`/`CDC_API_SECRET` env > `~/.config/cdcx/config.toml` profile.
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
cdcx setup # Interactive credential setup
|
|
323
|
+
cdcx --profile uat account summary # Use named profile
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### TUI Config
|
|
327
|
+
|
|
328
|
+
`~/.config/cdcx/tui.toml`:
|
|
329
|
+
|
|
330
|
+
```toml
|
|
331
|
+
theme = "terminal-pro"
|
|
332
|
+
tick_rate_ms = 250
|
|
333
|
+
watchlist = ["BTC_USDT", "ETH_USDT", "SOL_USDT", "CRO_USDT"]
|
|
334
|
+
|
|
335
|
+
[themes.my-theme]
|
|
336
|
+
bg = "#1a1a2e"
|
|
337
|
+
accent = "#00d4ff"
|
|
338
|
+
positive = "#00ff88"
|
|
339
|
+
negative = "#ff4444"
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Global Flags
|
|
343
|
+
|
|
344
|
+
| Flag | Description |
|
|
345
|
+
|------|-------------|
|
|
346
|
+
| `-o, --output` | Output format: `json` (default), `table`, `ndjson` |
|
|
347
|
+
| `--dry-run` | Preview request without executing |
|
|
348
|
+
| `--env` | Environment: `production` (default), `uat` |
|
|
349
|
+
| `--profile` | Config profile name |
|
|
350
|
+
| `--yes` | Skip confirmation prompts |
|
|
351
|
+
| `-v, --verbose` | Verbose output |
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Architecture
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
crates/cdcx-core/ # API client, auth, signing, schema, OpenAPI parser, paper engine
|
|
359
|
+
crates/cdcx-cli/ # CLI binary, dispatch, MCP server, setup
|
|
360
|
+
crates/cdcx-tui/ # Terminal dashboard (ratatui + crossterm)
|
|
361
|
+
schemas/ # CLI overlay files (command aliases, positional args, defaults)
|
|
362
|
+
skills/ # Agent skill files for guided workflows
|
|
363
|
+
site/ # Marketing site (single HTML file)
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
- **OpenAPI sole source:** All API commands and MCP tools are generated from the exchange's OpenAPI spec at runtime (24h cache). Thin TOML overlay files in `schemas/` add CLI-only metadata (positional args, defaults, command aliases). No hand-maintained endpoint definitions.
|
|
367
|
+
- **Single binary:** ~11MB, no runtime dependencies
|
|
368
|
+
- **Zero flicker:** ratatui double-buffer character-level diffing
|
|
369
|
+
|
|
370
|
+
## Development
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
cargo test # Run all tests
|
|
374
|
+
cargo build --release # Build release binary
|
|
375
|
+
cargo run -- market ticker BTC_USDT # Run from source
|
|
376
|
+
cargo run -- tui # TUI from source
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Git hooks
|
|
380
|
+
|
|
381
|
+
Install once per clone to catch CI failures locally:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
./hooks/install.sh
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
- `pre-commit` runs `cargo fmt --check` + `cargo clippy -- -D warnings`
|
|
388
|
+
- `pre-push` runs the full test suite (skipped for doc-only pushes)
|
|
389
|
+
|
|
390
|
+
The toolchain is pinned to `stable` via `rust-toolchain.toml`; run `rustup update stable` if your local clippy is older than CI's.
|
|
391
|
+
|
|
392
|
+
## License
|
|
393
|
+
|
|
394
|
+
Dual-licensed under [MIT](LICENSE-MIT) and [Apache 2.0](LICENSE-APACHE).
|
package/install.sh
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# cdcx installer — detects platform, downloads latest release, verifies checksum, installs.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# curl -sSfL https://raw.githubusercontent.com/crypto-com/cdcx-cli/main/install.sh | sh
|
|
6
|
+
#
|
|
7
|
+
# Or from source:
|
|
8
|
+
# cargo install --git https://github.com/crypto-com/cdcx-cli.git --bin cdcx
|
|
9
|
+
|
|
10
|
+
set -eu
|
|
11
|
+
|
|
12
|
+
REPO="crypto-com/cdcx-cli"
|
|
13
|
+
BINARY="cdcx"
|
|
14
|
+
# INSTALL_DIR is resolved after the banner:
|
|
15
|
+
# 1. honour an explicit INSTALL_DIR env var if set
|
|
16
|
+
# 2. otherwise prefer /usr/local/bin when writable
|
|
17
|
+
# 3. otherwise fall back to ~/.local/bin (no sudo required)
|
|
18
|
+
DEFAULT_SYSTEM_DIR="/usr/local/bin"
|
|
19
|
+
DEFAULT_USER_DIR="${HOME}/.local/bin"
|
|
20
|
+
SKIP_VERIFICATION=false
|
|
21
|
+
|
|
22
|
+
# ── Colors ──────────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
RED='\033[0;31m'
|
|
25
|
+
GREEN='\033[0;32m'
|
|
26
|
+
YELLOW='\033[0;33m'
|
|
27
|
+
CYAN='\033[0;36m'
|
|
28
|
+
BOLD='\033[1m'
|
|
29
|
+
NC='\033[0m'
|
|
30
|
+
|
|
31
|
+
info() { printf "${CYAN}${BOLD}==>${NC} %s\n" "$1"; }
|
|
32
|
+
ok() { printf "${GREEN}${BOLD} ✓${NC} %s\n" "$1"; }
|
|
33
|
+
warn() { printf "${YELLOW}${BOLD} !${NC} %s\n" "$1"; }
|
|
34
|
+
error() { printf "${RED}${BOLD}Error:${NC} %s\n" "$1" >&2; exit 1; }
|
|
35
|
+
|
|
36
|
+
# Parse command-line flags (must be after function definitions)
|
|
37
|
+
while [ $# -gt 0 ]; do
|
|
38
|
+
case "$1" in
|
|
39
|
+
--skip-verification)
|
|
40
|
+
SKIP_VERIFICATION=true
|
|
41
|
+
shift
|
|
42
|
+
;;
|
|
43
|
+
*)
|
|
44
|
+
error "Unknown option: $1"
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
# ── Banner ──────────────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
printf "${CYAN}${BOLD}"
|
|
52
|
+
cat << 'BANNER'
|
|
53
|
+
|
|
54
|
+
██████╗██████╗ ██████╗██╗ ██╗
|
|
55
|
+
██╔════╝██╔══██╗██╔════╝╚██╗██╔╝
|
|
56
|
+
██║ ██║ ██║██║ ╚███╔╝
|
|
57
|
+
██║ ██║ ██║██║ ██╔██╗
|
|
58
|
+
╚██████╗██████╔╝╚██████╗██╔╝██╗
|
|
59
|
+
╚═════╝╚═════╝ ╚═════╝╚═╝ ╚═╝
|
|
60
|
+
|
|
61
|
+
Crypto.com Exchange CLI
|
|
62
|
+
|
|
63
|
+
BANNER
|
|
64
|
+
printf "${NC}"
|
|
65
|
+
|
|
66
|
+
# ── Platform detection ──────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
detect_os() {
|
|
69
|
+
case "$(uname -s)" in
|
|
70
|
+
Linux*) echo "linux" ;;
|
|
71
|
+
Darwin*) echo "darwin" ;;
|
|
72
|
+
MINGW*|MSYS*|CYGWIN*) echo "windows" ;;
|
|
73
|
+
*) error "Unsupported operating system: $(uname -s)" ;;
|
|
74
|
+
esac
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
detect_arch() {
|
|
78
|
+
case "$(uname -m)" in
|
|
79
|
+
x86_64|amd64) echo "x86_64" ;;
|
|
80
|
+
arm64|aarch64) echo "aarch64" ;;
|
|
81
|
+
*) error "Unsupported architecture: $(uname -m)" ;;
|
|
82
|
+
esac
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
OS="$(detect_os)"
|
|
86
|
+
ARCH="$(detect_arch)"
|
|
87
|
+
|
|
88
|
+
info "Detected platform: ${OS}-${ARCH}"
|
|
89
|
+
|
|
90
|
+
# ── Fetch latest release ────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
info "Fetching latest release..."
|
|
93
|
+
|
|
94
|
+
RELEASE_JSON="$(curl -sSfL "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null)" || error "Failed to fetch release info from GitHub."
|
|
95
|
+
|
|
96
|
+
# Extract version tag
|
|
97
|
+
VERSION="$(echo "$RELEASE_JSON" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')"
|
|
98
|
+
if [ -z "$VERSION" ]; then
|
|
99
|
+
error "Could not determine latest release version."
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Validate VERSION against semantic versioning pattern to prevent code injection
|
|
103
|
+
if ! echo "$VERSION" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then
|
|
104
|
+
error "Invalid version format: ${VERSION}. Expected semantic version (e.g., v1.0.0)."
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
ok "Latest version: ${VERSION}"
|
|
108
|
+
|
|
109
|
+
# ── Construct download URL ──────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
# Release assets are named with the bare version (no leading "v"),
|
|
112
|
+
# matching the release.yml workflow which strips the "v" prefix.
|
|
113
|
+
VERSION_BARE="${VERSION#v}"
|
|
114
|
+
|
|
115
|
+
# Archive naming: cdcx-{version}-{arch}-{os}.tar.gz (or .zip for windows)
|
|
116
|
+
# Linux binaries ship as musl (statically linked, glibc-free) for portability.
|
|
117
|
+
PLATFORM="${OS}"
|
|
118
|
+
if [ "$OS" = "darwin" ]; then
|
|
119
|
+
PLATFORM="apple-darwin"
|
|
120
|
+
elif [ "$OS" = "linux" ]; then
|
|
121
|
+
PLATFORM="unknown-linux-musl"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
if [ "$OS" = "windows" ]; then
|
|
125
|
+
ARCHIVE="${BINARY}-${VERSION_BARE}-${ARCH}-pc-windows-msvc.zip"
|
|
126
|
+
else
|
|
127
|
+
ARCHIVE="${BINARY}-${VERSION_BARE}-${ARCH}-${PLATFORM}.tar.gz"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${VERSION}/${ARCHIVE}"
|
|
131
|
+
CHECKSUM_URL="https://github.com/${REPO}/releases/download/${VERSION}/checksums.txt"
|
|
132
|
+
|
|
133
|
+
info "Downloading ${ARCHIVE}..."
|
|
134
|
+
|
|
135
|
+
# ── Download and verify ─────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
TMPDIR="$(mktemp -d)"
|
|
138
|
+
trap 'rm -rf "$TMPDIR"' EXIT
|
|
139
|
+
|
|
140
|
+
# Download archive
|
|
141
|
+
curl -sSfL -o "${TMPDIR}/${ARCHIVE}" "$DOWNLOAD_URL" || \
|
|
142
|
+
error "Download failed. This platform (${ARCH}-${PLATFORM}) may not have a pre-built binary.\n Try: cargo install --git https://github.com/${REPO}.git --bin cdcx"
|
|
143
|
+
|
|
144
|
+
ok "Downloaded ${ARCHIVE}"
|
|
145
|
+
|
|
146
|
+
# Download and verify checksum (if available)
|
|
147
|
+
CHECKSUM_DOWNLOADED=false
|
|
148
|
+
if curl -sSfL -o "${TMPDIR}/checksums.txt" "$CHECKSUM_URL" 2>/dev/null; then
|
|
149
|
+
CHECKSUM_DOWNLOADED=true
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
if [ "$SKIP_VERIFICATION" = true ]; then
|
|
153
|
+
warn "Checksum verification skipped — this is insecure. Only skip if you trust the download source."
|
|
154
|
+
warn "For security, verification is enabled by default. Re-run without --skip-verification for secure installs."
|
|
155
|
+
else
|
|
156
|
+
if [ "$CHECKSUM_DOWNLOADED" = false ]; then
|
|
157
|
+
error "Checksums not available for this release. Cannot verify binary integrity.\n To bypass verification (not recommended), re-run with --skip-verification"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
EXPECTED="$(awk -v f="$ARCHIVE" '$2 == f { print $1 }' "${TMPDIR}/checksums.txt")"
|
|
161
|
+
if [ -z "$EXPECTED" ]; then
|
|
162
|
+
error "Checksum entry not found for ${ARCHIVE}. Binary file name mismatch or corrupted checksums.txt.\n To bypass verification (not recommended), re-run with --skip-verification"
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
if command -v sha256sum > /dev/null 2>&1; then
|
|
166
|
+
ACTUAL="$(sha256sum "${TMPDIR}/${ARCHIVE}" | awk '{print $1}')"
|
|
167
|
+
elif command -v shasum > /dev/null 2>&1; then
|
|
168
|
+
ACTUAL="$(shasum -a 256 "${TMPDIR}/${ARCHIVE}" | awk '{print $1}')"
|
|
169
|
+
else
|
|
170
|
+
error "No SHA256 tool found (neither sha256sum nor shasum available). Cannot verify checksum.\n Install sha256sum or shasum, or re-run with --skip-verification (not recommended)"
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
if [ "$ACTUAL" != "$EXPECTED" ]; then
|
|
174
|
+
error "Checksum verification failed!\n Expected: ${EXPECTED}\n Got: ${ACTUAL}\n The downloaded file may be corrupted. Please try again."
|
|
175
|
+
fi
|
|
176
|
+
ok "Checksum verified"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# ── Extract ─────────────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
info "Extracting..."
|
|
182
|
+
|
|
183
|
+
if [ "$OS" = "windows" ]; then
|
|
184
|
+
unzip -qo "${TMPDIR}/${ARCHIVE}" -d "${TMPDIR}" || error "Failed to extract archive."
|
|
185
|
+
else
|
|
186
|
+
tar -xzf "${TMPDIR}/${ARCHIVE}" -C "${TMPDIR}" || error "Failed to extract archive."
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Find the binary (may be in a subdirectory)
|
|
190
|
+
EXTRACTED_BIN="$(find "${TMPDIR}" -name "${BINARY}" -type f | head -1)"
|
|
191
|
+
if [ -z "$EXTRACTED_BIN" ]; then
|
|
192
|
+
# Try with .exe for Windows
|
|
193
|
+
EXTRACTED_BIN="$(find "${TMPDIR}" -name "${BINARY}.exe" -type f | head -1)"
|
|
194
|
+
fi
|
|
195
|
+
if [ -z "$EXTRACTED_BIN" ]; then
|
|
196
|
+
error "Could not find ${BINARY} binary in extracted archive."
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
chmod +x "$EXTRACTED_BIN"
|
|
200
|
+
ok "Extracted ${BINARY}"
|
|
201
|
+
|
|
202
|
+
# ── Install ─────────────────────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
# Resolve install location. Honour an explicit INSTALL_DIR; otherwise prefer the
|
|
205
|
+
# system dir when writable, fall back to ~/.local/bin when not. Never prompt for
|
|
206
|
+
# sudo automatically — scripts piped to `sh` can't read a TTY, and many
|
|
207
|
+
# environments (CI, corporate laptops, rootless containers) lack sudo entirely.
|
|
208
|
+
if [ -n "${INSTALL_DIR:-}" ]; then
|
|
209
|
+
: # user supplied INSTALL_DIR; respect it verbatim
|
|
210
|
+
elif [ -w "$DEFAULT_SYSTEM_DIR" ] 2>/dev/null; then
|
|
211
|
+
INSTALL_DIR="$DEFAULT_SYSTEM_DIR"
|
|
212
|
+
else
|
|
213
|
+
INSTALL_DIR="$DEFAULT_USER_DIR"
|
|
214
|
+
info "${DEFAULT_SYSTEM_DIR} is not writable — installing to ${INSTALL_DIR} (no sudo needed)"
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
mkdir -p "$INSTALL_DIR" || error "Could not create ${INSTALL_DIR}"
|
|
218
|
+
|
|
219
|
+
info "Installing to ${INSTALL_DIR}/${BINARY}..."
|
|
220
|
+
|
|
221
|
+
if [ -w "$INSTALL_DIR" ]; then
|
|
222
|
+
mv "$EXTRACTED_BIN" "${INSTALL_DIR}/${BINARY}" || \
|
|
223
|
+
error "Failed to move binary into ${INSTALL_DIR}. Set INSTALL_DIR to a writable directory and re-run."
|
|
224
|
+
else
|
|
225
|
+
error "${INSTALL_DIR} is not writable. Set INSTALL_DIR=\$HOME/.local/bin (or another writable path) and re-run."
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
ok "Installed ${BINARY} to ${INSTALL_DIR}/${BINARY}"
|
|
229
|
+
|
|
230
|
+
# ── Verify ──────────────────────────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
if command -v "$BINARY" > /dev/null 2>&1; then
|
|
233
|
+
INSTALLED_VERSION="$("$BINARY" --version 2>/dev/null || echo "cdcx")"
|
|
234
|
+
printf "\n"
|
|
235
|
+
printf "${GREEN}${BOLD} ✓ ${INSTALLED_VERSION} installed successfully!${NC}\n"
|
|
236
|
+
printf "\n"
|
|
237
|
+
info "Binary: ${INSTALL_DIR}/${BINARY}"
|
|
238
|
+
printf "\n"
|
|
239
|
+
printf " ${CYAN}Get started:${NC}\n"
|
|
240
|
+
printf " ${BOLD}cdcx market ticker BTC_USDT${NC} # Live ticker\n"
|
|
241
|
+
printf " ${BOLD}cdcx tui${NC} # Interactive dashboard\n"
|
|
242
|
+
printf " ${BOLD}cdcx setup${NC} # Configure API keys\n"
|
|
243
|
+
printf " ${BOLD}cdcx --help${NC} # All commands\n"
|
|
244
|
+
printf "\n"
|
|
245
|
+
else
|
|
246
|
+
warn "${BINARY} was installed but is not in your PATH."
|
|
247
|
+
printf " Add this line to ${BOLD}~/.zshrc${NC} or ${BOLD}~/.bashrc${NC}:\n"
|
|
248
|
+
printf " ${BOLD}export PATH=\"${INSTALL_DIR}:\$PATH\"${NC}\n"
|
|
249
|
+
printf " Then reload: ${BOLD}source ~/.zshrc${NC} (or open a new shell).\n"
|
|
250
|
+
printf " Or run directly without modifying PATH: ${BOLD}${INSTALL_DIR}/${BINARY}${NC}\n"
|
|
251
|
+
fi
|
package/npm/bin.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "child_process";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import { findBinary, install, ROOT_DIR, LOCAL_BIN } from "./lib.mjs";
|
|
6
|
+
|
|
7
|
+
let bin = findBinary();
|
|
8
|
+
if (!bin) {
|
|
9
|
+
try {
|
|
10
|
+
install();
|
|
11
|
+
} catch {
|
|
12
|
+
// global install failed — fall through to local attempt
|
|
13
|
+
}
|
|
14
|
+
bin = findBinary();
|
|
15
|
+
if (!bin) {
|
|
16
|
+
try {
|
|
17
|
+
install(ROOT_DIR);
|
|
18
|
+
} catch {
|
|
19
|
+
process.stderr.write("cdcx installation failed.\n");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
if (!existsSync(LOCAL_BIN)) {
|
|
23
|
+
process.stderr.write("cdcx installation failed — binary not found.\n");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
bin = LOCAL_BIN;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
execFileSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
32
|
+
} catch (e) {
|
|
33
|
+
process.exit(e.status ?? 1);
|
|
34
|
+
}
|
package/npm/lib.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { execFileSync, execSync } from "child_process";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
export const BINARY = "cdcx";
|
|
8
|
+
export const ROOT_DIR = join(__dirname, "..");
|
|
9
|
+
export const LOCAL_BIN = join(ROOT_DIR, BINARY);
|
|
10
|
+
const INSTALL_SCRIPT = join(ROOT_DIR, "install.sh");
|
|
11
|
+
|
|
12
|
+
export function findBinary() {
|
|
13
|
+
if (existsSync(LOCAL_BIN)) {
|
|
14
|
+
try {
|
|
15
|
+
execFileSync(LOCAL_BIN, ["--version"], { stdio: "pipe" });
|
|
16
|
+
return LOCAL_BIN;
|
|
17
|
+
} catch {
|
|
18
|
+
// exists but not functional — fall through
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const bin = execSync(`command -v ${BINARY}`, { encoding: "utf8" }).trim();
|
|
23
|
+
execFileSync(bin, ["--version"], { stdio: "pipe" });
|
|
24
|
+
return bin;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function installedVersion(bin) {
|
|
31
|
+
try {
|
|
32
|
+
const out = execFileSync(bin, ["--version"], { encoding: "utf8" }).trim();
|
|
33
|
+
const match = out.match(/(\d+\.\d+\.\d+)/);
|
|
34
|
+
return match ? match[1] : null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function install(dir) {
|
|
41
|
+
if (!existsSync(INSTALL_SCRIPT)) {
|
|
42
|
+
process.stderr.write(
|
|
43
|
+
`${BINARY} not found. Install it: curl -sSfL https://raw.githubusercontent.com/crypto-com/cdcx-cli/main/install.sh | sh\n`
|
|
44
|
+
);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const env = dir ? { ...process.env, INSTALL_DIR: dir } : process.env;
|
|
48
|
+
execFileSync("sh", [INSTALL_SCRIPT], {
|
|
49
|
+
stdio: ["pipe", process.stderr, process.stderr],
|
|
50
|
+
env,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import {
|
|
5
|
+
findBinary,
|
|
6
|
+
install,
|
|
7
|
+
installedVersion,
|
|
8
|
+
ROOT_DIR,
|
|
9
|
+
} from "./lib.mjs";
|
|
10
|
+
|
|
11
|
+
function isUpdateDisabled() {
|
|
12
|
+
try {
|
|
13
|
+
const configPath = join(homedir(), ".config", "cdcx", "config.toml");
|
|
14
|
+
if (!existsSync(configPath)) return false;
|
|
15
|
+
const content = readFileSync(configPath, "utf8");
|
|
16
|
+
return /^\s*disable_update_check\s*=\s*"?true"?\s*$/m.test(content);
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const bin = findBinary();
|
|
23
|
+
|
|
24
|
+
if (!bin) {
|
|
25
|
+
// No binary at all — must install regardless of update preference
|
|
26
|
+
try {
|
|
27
|
+
install();
|
|
28
|
+
} catch {
|
|
29
|
+
try {
|
|
30
|
+
install(ROOT_DIR);
|
|
31
|
+
} catch {
|
|
32
|
+
process.stderr.write("cdcx installation failed.\n");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} else if (!isUpdateDisabled()) {
|
|
37
|
+
const expected = JSON.parse(
|
|
38
|
+
readFileSync(join(ROOT_DIR, "package.json"), "utf8")
|
|
39
|
+
).version;
|
|
40
|
+
const current = installedVersion(bin);
|
|
41
|
+
|
|
42
|
+
if (current !== expected) {
|
|
43
|
+
try {
|
|
44
|
+
install();
|
|
45
|
+
} catch {
|
|
46
|
+
// global failed — try local
|
|
47
|
+
}
|
|
48
|
+
const updated = findBinary();
|
|
49
|
+
if (!updated || installedVersion(updated) !== expected) {
|
|
50
|
+
try {
|
|
51
|
+
install(ROOT_DIR);
|
|
52
|
+
} catch {
|
|
53
|
+
process.stderr.write("cdcx installation failed.\n");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cryptocom/cdcx-cli",
|
|
3
|
+
"version": "1.2.2",
|
|
4
|
+
"description": "MCP server for crypto.com exchange CLI",
|
|
5
|
+
"author": "Crypto.com",
|
|
6
|
+
"homepage": "https://github.com/crypto-com/cdcx-cli",
|
|
7
|
+
"license": "MIT OR Apache-2.0",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"crypto.com",
|
|
10
|
+
"crypto",
|
|
11
|
+
"mcp",
|
|
12
|
+
"model-context-protocol"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"url": "git+https://github.com/crypto-com/cdcx-cli.git",
|
|
16
|
+
"type": "git"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20.0.0"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"os": [
|
|
23
|
+
"darwin",
|
|
24
|
+
"linux"
|
|
25
|
+
],
|
|
26
|
+
"files": [
|
|
27
|
+
"README.md",
|
|
28
|
+
"install.sh",
|
|
29
|
+
"npm/bin.mjs",
|
|
30
|
+
"npm/lib.mjs",
|
|
31
|
+
"npm/post-install.mjs"
|
|
32
|
+
],
|
|
33
|
+
"bin": {
|
|
34
|
+
"cdcx-cli": "npm/bin.mjs"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"bump": "./scripts/version-bump.sh",
|
|
38
|
+
"postinstall": "node ./npm/post-install.mjs"
|
|
39
|
+
}
|
|
40
|
+
}
|