perp-cli 0.3.16 → 0.3.18
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 +61 -249
- package/dist/__tests__/cross-chain-margin.test.js +2 -2
- package/dist/exchanges/lighter.js +23 -9
- package/dist/index.js +2 -0
- package/dist/utils.js +71 -1
- package/package.json +1 -1
- package/skills/perp-cli/SKILL.md +56 -6
package/README.md
CHANGED
|
@@ -1,296 +1,108 @@
|
|
|
1
1
|
# perp-cli
|
|
2
2
|
|
|
3
|
-
Multi-DEX perpetual futures CLI
|
|
4
|
-
|
|
5
|
-
Trade, bridge, arbitrage, and manage positions across 3 exchanges from a single command line.
|
|
3
|
+
Multi-DEX perpetual futures CLI — **Pacifica** (Solana), **Hyperliquid** (HyperEVM), **Lighter** (Ethereum).
|
|
6
4
|
|
|
7
5
|
```bash
|
|
8
6
|
npm install -g perp-cli
|
|
9
|
-
perp
|
|
7
|
+
perp --json portfolio
|
|
10
8
|
```
|
|
11
9
|
|
|
12
10
|
## Features
|
|
13
11
|
|
|
14
|
-
- **3 Exchanges** —
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **Full JSON Output** — Every command supports `--json` for programmatic access
|
|
21
|
-
|
|
22
|
-
## Quick Start
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
# Interactive setup (recommended for first-time users)
|
|
26
|
-
perp init
|
|
27
|
-
|
|
28
|
-
# Or configure exchange keys manually
|
|
29
|
-
export HL_PRIVATE_KEY=<evm-hex-key> # Hyperliquid
|
|
30
|
-
export PRIVATE_KEY=<solana-base58-key> # Pacifica
|
|
31
|
-
export LIGHTER_PRIVATE_KEY=<evm-hex-key> # Lighter
|
|
32
|
-
|
|
33
|
-
# Or use wallet management
|
|
34
|
-
perp wallet import evm <key> # Import & auto-bind to HL + Lighter
|
|
35
|
-
perp wallet import solana <key> # Import & auto-bind to Pacifica
|
|
36
|
-
|
|
37
|
-
# Set default exchange (skip -e flag)
|
|
38
|
-
perp settings set default-exchange hyperliquid
|
|
39
|
-
|
|
40
|
-
# Check status
|
|
41
|
-
perp status
|
|
42
|
-
perp -e pacifica status
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Commands
|
|
46
|
-
|
|
47
|
-
### Market Data
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
perp market list # All available markets
|
|
51
|
-
perp market mid BTC # Mid price
|
|
52
|
-
perp market info BTC # Market details (tick size, max leverage)
|
|
53
|
-
perp market book BTC # Orderbook
|
|
54
|
-
perp market funding BTC # Funding rate
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Account
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
perp account info # Balance & margin
|
|
61
|
-
perp account positions # Open positions
|
|
62
|
-
perp account orders # Open orders
|
|
63
|
-
perp account history # Trade history
|
|
64
|
-
perp account margin BTC # Position-level margin info
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### Trading
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
perp trade buy BTC 0.01 # Market buy
|
|
71
|
-
perp trade sell BTC 0.01 # Market sell
|
|
72
|
-
perp trade buy BTC 0.01 -p 60000 # Limit buy @ $60,000
|
|
73
|
-
perp trade close BTC # Close position
|
|
74
|
-
perp trade cancel <orderId> # Cancel order
|
|
75
|
-
perp trade tp BTC 65000 # Take-profit
|
|
76
|
-
perp trade sl BTC 58000 # Stop-loss
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Position Management
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
perp manage leverage BTC 10 # Set leverage
|
|
83
|
-
perp manage margin-mode BTC cross # Cross/isolated margin
|
|
84
|
-
```
|
|
12
|
+
- **3 Exchanges** — trade, bridge, arbitrage across Pacifica, Hyperliquid, Lighter
|
|
13
|
+
- **Funding Rate Arb** — scan spreads + one-command dual-leg execution (`arb exec`)
|
|
14
|
+
- **Portfolio** — single call returns balances, positions, risk level across all exchanges
|
|
15
|
+
- **CCTP V2 Bridge** — $0 fee USDC bridging (Solana, Arbitrum, Base)
|
|
16
|
+
- **Agent-First Design** — `--json`, `--fields`, `--ndjson`, `--dry-run`, runtime schema introspection
|
|
17
|
+
- **Safety** — pre-trade validation, response sanitization, client-id deduplication
|
|
85
18
|
|
|
86
|
-
|
|
19
|
+
## Setup
|
|
87
20
|
|
|
88
21
|
```bash
|
|
89
|
-
|
|
90
|
-
perp
|
|
91
|
-
perp
|
|
92
|
-
perp
|
|
93
|
-
perp deposit lighter info # Show all Lighter deposit routes
|
|
94
|
-
perp withdraw pacifica 50 # Withdraw USDC
|
|
95
|
-
perp withdraw hyperliquid 50
|
|
96
|
-
perp deposit info # All deposit instructions
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Cross-Chain Bridge (CCTP V2)
|
|
22
|
+
# Set exchange keys
|
|
23
|
+
perp wallet set hl <EVM_KEY> # Hyperliquid
|
|
24
|
+
perp wallet set pac <SOLANA_KEY> # Pacifica
|
|
25
|
+
perp wallet set lt <EVM_KEY> # Lighter (API key auto-generated)
|
|
100
26
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
perp bridge chains # Supported chains
|
|
105
|
-
perp bridge quote --from solana --to arbitrum --amount 100
|
|
106
|
-
perp bridge send --from solana --to arbitrum --amount 100
|
|
107
|
-
perp bridge exchange --from pacifica --to hyperliquid --amount 100
|
|
108
|
-
perp bridge status <orderId>
|
|
27
|
+
# Verify
|
|
28
|
+
perp wallet show
|
|
109
29
|
```
|
|
110
30
|
|
|
111
|
-
|
|
31
|
+
Same EVM key works for both Hyperliquid and Lighter.
|
|
112
32
|
|
|
113
|
-
|
|
33
|
+
## Core Commands
|
|
114
34
|
|
|
115
35
|
```bash
|
|
116
|
-
|
|
117
|
-
perp
|
|
118
|
-
perp arb auto --min-spread 30 # Auto-execute arb (daemon mode)
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Automated Strategies
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
# Grid trading
|
|
125
|
-
perp run grid BTC --range 5 --grids 10 --size 100
|
|
126
|
-
|
|
127
|
-
# DCA (Dollar Cost Average)
|
|
128
|
-
perp run dca BTC --interval 1h --size 50 --side buy
|
|
36
|
+
# Portfolio (balances + positions + risk across all exchanges)
|
|
37
|
+
perp --json portfolio
|
|
129
38
|
|
|
130
|
-
#
|
|
131
|
-
perp
|
|
132
|
-
|
|
39
|
+
# Market data
|
|
40
|
+
perp --json -e <EX> market list
|
|
41
|
+
perp --json -e <EX> market book <SYM>
|
|
42
|
+
perp --json -e <EX> market funding <SYM>
|
|
133
43
|
|
|
134
|
-
|
|
44
|
+
# Trading
|
|
45
|
+
perp --json -e <EX> trade market <SYM> buy <SIZE>
|
|
46
|
+
perp --json -e <EX> trade close <SYM>
|
|
47
|
+
perp --json -e <EX> trade leverage <SYM> <N>
|
|
135
48
|
|
|
136
|
-
|
|
137
|
-
perp
|
|
138
|
-
perp
|
|
139
|
-
perp stream funding # Live funding rates
|
|
140
|
-
```
|
|
49
|
+
# Funding rate arbitrage
|
|
50
|
+
perp --json arb scan --min 5 # find opportunities
|
|
51
|
+
perp --json arb exec <SYM> <longEx> <shortEx> <$> --leverage 2 --isolated # execute both legs
|
|
141
52
|
|
|
142
|
-
|
|
53
|
+
# Risk
|
|
54
|
+
perp --json risk limits --max-leverage 5
|
|
55
|
+
perp --json risk liquidation-distance
|
|
143
56
|
|
|
144
|
-
|
|
145
|
-
perp dashboard # Real-time multi-exchange dashboard
|
|
146
|
-
perp dashboard --port 3457 # Custom port
|
|
147
|
-
perp dashboard --exchanges hl,pac # Specific exchanges only
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
WebSocket-based real-time monitoring: positions, orders, balances, funding rates, arb opportunities across all exchanges. Includes HIP-3 dex position tracking.
|
|
151
|
-
|
|
152
|
-
### Cross-Exchange Tools
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
perp gap show # Price gaps between exchanges
|
|
156
|
-
perp gap watch --min 0.05 # Live gap monitor
|
|
157
|
-
perp portfolio # Unified portfolio view
|
|
158
|
-
perp risk overview # Cross-exchange risk assessment
|
|
159
|
-
perp analytics pnl # P&L analytics
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Background Jobs
|
|
163
|
-
|
|
164
|
-
```bash
|
|
165
|
-
perp jobs list # Running background jobs
|
|
166
|
-
perp jobs start arb # Start arb in background (tmux)
|
|
167
|
-
perp jobs stop <id> # Stop a job
|
|
168
|
-
perp jobs logs <id> # View job logs
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Alerts
|
|
172
|
-
|
|
173
|
-
```bash
|
|
174
|
-
perp alert add -t price -s BTC --above 100000 --telegram
|
|
175
|
-
perp alert add -t funding -s ETH --spread 30
|
|
176
|
-
perp alert daemon --interval 30
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
## Claude Code Agent Skill
|
|
180
|
-
|
|
181
|
-
Install as a Claude Code plugin or via the universal skills CLI:
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
# Universal skill installer (works with Claude Code, Cursor, Codex, Gemini CLI, etc.)
|
|
185
|
-
npx skills add hypurrquant/perp-cli
|
|
186
|
-
|
|
187
|
-
# Or in Claude Code directly
|
|
188
|
-
/plugin marketplace add hypurrquant/perp-cli
|
|
189
|
-
/plugin install perp-cli
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
Once installed, Claude can trade for you via natural language:
|
|
193
|
-
|
|
194
|
-
```
|
|
195
|
-
perp --json wallet show # Check configured wallets
|
|
196
|
-
perp --json -e hl market list # Browse markets
|
|
197
|
-
perp --json -e hl trade buy BTC 0.01 # Execute trades
|
|
198
|
-
perp --json arb rates # Find funding rate arb
|
|
57
|
+
# Bridge
|
|
199
58
|
perp --json bridge quote --from solana --to arbitrum --amount 100
|
|
59
|
+
perp --json bridge send --from solana --to arbitrum --amount 100
|
|
200
60
|
```
|
|
201
61
|
|
|
202
|
-
|
|
62
|
+
Exchange flags: `-e hyperliquid`, `-e pacifica`, `-e lighter` (aliases: `hl`, `pac`, `lt`).
|
|
203
63
|
|
|
204
|
-
## AI Agent
|
|
64
|
+
## AI Agent Skill
|
|
205
65
|
|
|
206
|
-
|
|
66
|
+
Install as a skill for Claude Code, Cursor, Codex, Gemini CLI, etc.:
|
|
207
67
|
|
|
208
68
|
```bash
|
|
209
|
-
#
|
|
210
|
-
|
|
211
|
-
perp schema --json # CLI schema with error codes
|
|
212
|
-
|
|
213
|
-
# Machine-readable trading
|
|
214
|
-
perp --json -e hyperliquid market mid BTC
|
|
215
|
-
perp --json trade buy ETH 0.1
|
|
216
|
-
perp --json account positions
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
All JSON responses follow a consistent envelope:
|
|
69
|
+
# Using npx (recommended)
|
|
70
|
+
npx skills add hypurrquant/perp-cli
|
|
220
71
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
"ok": true,
|
|
224
|
-
"data": { ... },
|
|
225
|
-
"meta": { "timestamp": "2026-03-08T..." }
|
|
226
|
-
}
|
|
72
|
+
# Or via Claude Code slash command
|
|
73
|
+
/install-skill hypurrquant/perp-cli
|
|
227
74
|
```
|
|
228
75
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
```json
|
|
232
|
-
{
|
|
233
|
-
"ok": false,
|
|
234
|
-
"error": { "code": "INSUFFICIENT_BALANCE", "message": "...", "retryable": false },
|
|
235
|
-
"meta": { "timestamp": "..." }
|
|
236
|
-
}
|
|
237
|
-
```
|
|
76
|
+
See [`skills/perp-cli/SKILL.md`](skills/perp-cli/SKILL.md) for the full agent guide.
|
|
238
77
|
|
|
239
|
-
|
|
78
|
+
## Agent-First CLI Design
|
|
240
79
|
|
|
241
|
-
|
|
80
|
+
Built following [agent-first CLI principles](https://justin.poehnelt.com/posts/rewrite-your-cli-for-ai-agents/):
|
|
242
81
|
|
|
243
82
|
```bash
|
|
244
|
-
|
|
245
|
-
perp
|
|
246
|
-
|
|
247
|
-
```
|
|
83
|
+
# Every command returns structured JSON envelope
|
|
84
|
+
perp --json portfolio
|
|
85
|
+
# → { "ok": true, "data": {...}, "meta": { "timestamp": "..." } }
|
|
248
86
|
|
|
249
|
-
|
|
87
|
+
# Runtime schema introspection (don't guess commands — query this)
|
|
88
|
+
perp --json agent schema
|
|
250
89
|
|
|
251
|
-
|
|
90
|
+
# Filter output to save tokens
|
|
91
|
+
perp --json --fields totalEquity,risk portfolio
|
|
252
92
|
|
|
253
|
-
|
|
254
|
-
perp
|
|
255
|
-
perp wallet generate evm # Generate new EVM wallet
|
|
256
|
-
perp wallet import solana <key> # Import existing key
|
|
257
|
-
perp wallet use <name> # Auto-bind to exchanges by type
|
|
258
|
-
perp wallet list # List all wallets
|
|
259
|
-
perp wallet balance # On-chain balances
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
Wallets are stored in `~/.perp/wallets.json` (file permission 0600).
|
|
93
|
+
# Stream large lists as NDJSON (one JSON per line)
|
|
94
|
+
perp --json --ndjson -e hl market list
|
|
263
95
|
|
|
264
|
-
|
|
96
|
+
# Pre-validate before executing
|
|
97
|
+
perp --json -e hl trade check BTC buy 0.01
|
|
98
|
+
perp --json --dry-run -e hl trade market BTC buy 0.01
|
|
265
99
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
PRIVATE_KEY= # Default key (Solana base58 or EVM hex)
|
|
269
|
-
PACIFICA_PRIVATE_KEY= # Pacifica (Solana)
|
|
270
|
-
HL_PRIVATE_KEY= # Hyperliquid (EVM)
|
|
271
|
-
LIGHTER_PRIVATE_KEY= # Lighter (EVM)
|
|
272
|
-
LIGHTER_API_KEY= # Lighter API key (40-byte)
|
|
100
|
+
# Idempotent orders with client ID
|
|
101
|
+
perp --json -e hl trade market BTC buy 0.01 --client-id my-unique-id
|
|
273
102
|
```
|
|
274
103
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
### Settings
|
|
278
|
-
|
|
279
|
-
```bash
|
|
280
|
-
perp settings show # Current settings
|
|
281
|
-
perp settings set default-exchange hyperliquid # Set default exchange
|
|
282
|
-
perp settings referrals on # Enable referral codes
|
|
283
|
-
perp settings set referralCodes.hyperliquid MYCODE
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
## Tech Stack
|
|
287
|
-
|
|
288
|
-
- **TypeScript** + Node.js (ESM)
|
|
289
|
-
- **Solana**: `@solana/web3.js`, `tweetnacl`, `bs58`
|
|
290
|
-
- **EVM**: `ethers` v6
|
|
291
|
-
- **Exchanges**: `hyperliquid` SDK, `lighter-ts-sdk` (WASM), `@pacifica/sdk`
|
|
292
|
-
- **Bridge**: Circle CCTP V2 (Solana ↔ EVM), deBridge DLN (fallback)
|
|
293
|
-
- **Testing**: Vitest (860+ tests)
|
|
104
|
+
All responses are auto-sanitized (control chars stripped, prompt injection patterns blocked).
|
|
105
|
+
Errors include `retryable` flag — only retry when `true`.
|
|
294
106
|
|
|
295
107
|
## License
|
|
296
108
|
|
|
@@ -225,8 +225,8 @@ describe("computeAutoSize", () => {
|
|
|
225
225
|
bids: [["99", "10000"]], // $990K depth
|
|
226
226
|
});
|
|
227
227
|
const size = await computeAutoSize(longAdapter, shortAdapter, "BTC", 5.0);
|
|
228
|
-
// Default maxPositionUsd is
|
|
229
|
-
expect(size).toBeLessThanOrEqual(
|
|
228
|
+
// Default maxPositionUsd is 100000
|
|
229
|
+
expect(size).toBeLessThanOrEqual(100000);
|
|
230
230
|
});
|
|
231
231
|
});
|
|
232
232
|
// ── Rebalance computation ──
|
|
@@ -97,15 +97,29 @@ export class LighterAdapter {
|
|
|
97
97
|
}
|
|
98
98
|
// Initialize signer for trading if we have an API key (reuse singleton WASM client)
|
|
99
99
|
if (this._apiKey && !signerReady) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
100
|
+
// Clean & validate API key before passing to WASM (Go hex decoder gives cryptic errors on non-hex)
|
|
101
|
+
let cleanKey = this._apiKey.trim().replace(/['"` \t\n\r.]/g, "");
|
|
102
|
+
if (cleanKey.startsWith("0x"))
|
|
103
|
+
cleanKey = cleanKey.slice(2);
|
|
104
|
+
if (cleanKey.length > 0 && !/^[0-9a-fA-F]+$/.test(cleanKey)) {
|
|
105
|
+
console.error(`[lighter] API key contains invalid chars (expected hex). Check LIGHTER_API_KEY in ~/.perp/.env or run 'perp -e lighter manage setup-api-key' to regenerate.`);
|
|
106
|
+
this._apiKey = "";
|
|
107
|
+
}
|
|
108
|
+
else if (cleanKey.length === 0) {
|
|
109
|
+
this._apiKey = "";
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this._apiKey = cleanKey;
|
|
113
|
+
const client = await LighterAdapter.getWasmClient();
|
|
114
|
+
await client.createClient({
|
|
115
|
+
url: this._baseUrl,
|
|
116
|
+
privateKey: this._apiKey,
|
|
117
|
+
chainId: this._chainId,
|
|
118
|
+
apiKeyIndex: this._apiKeyIndex,
|
|
119
|
+
accountIndex: this._accountIndex,
|
|
120
|
+
});
|
|
121
|
+
this._signer = client;
|
|
122
|
+
}
|
|
109
123
|
}
|
|
110
124
|
if (this._apiKey) {
|
|
111
125
|
this._readOnly = false;
|
package/dist/index.js
CHANGED
|
@@ -56,6 +56,8 @@ program
|
|
|
56
56
|
.option("-n, --network <network>", "Network: mainnet or testnet", "mainnet")
|
|
57
57
|
.option("-k, --private-key <key>", "Private key")
|
|
58
58
|
.option("--json", "Output raw JSON (for piping)")
|
|
59
|
+
.option("--fields <fields>", "Comma-separated fields to include in JSON output (e.g. totalEquity,positions)")
|
|
60
|
+
.option("--ndjson", "Output newline-delimited JSON (one object per line for streaming)")
|
|
59
61
|
.option("--dry-run", "Simulate trades without executing (log as simulated)")
|
|
60
62
|
.option("--dex <name>", "HIP-3 deployed perp dex name (Hyperliquid only)")
|
|
61
63
|
.configureOutput({
|
package/dist/utils.js
CHANGED
|
@@ -51,8 +51,78 @@ export function makeTable(head, rows) {
|
|
|
51
51
|
rows.forEach((r) => table.push(r));
|
|
52
52
|
return table.toString();
|
|
53
53
|
}
|
|
54
|
+
/** Pick only specified keys from an object (shallow). Supports nested paths via dot notation. */
|
|
55
|
+
function pickFields(obj, fields) {
|
|
56
|
+
if (obj == null || typeof obj !== "object")
|
|
57
|
+
return obj;
|
|
58
|
+
const result = {};
|
|
59
|
+
for (const field of fields) {
|
|
60
|
+
const parts = field.split(".");
|
|
61
|
+
let src = obj;
|
|
62
|
+
for (const p of parts) {
|
|
63
|
+
if (src == null || typeof src !== "object") {
|
|
64
|
+
src = undefined;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
src = src[p];
|
|
68
|
+
}
|
|
69
|
+
if (src !== undefined) {
|
|
70
|
+
result[field] = src;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
// ── Response Sanitization ──
|
|
76
|
+
// Strips control characters and potential prompt-injection patterns from all string values.
|
|
77
|
+
// Applied automatically to all JSON output to prevent agent confusion from external data.
|
|
78
|
+
const CONTROL_CHAR_RE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
|
|
79
|
+
const PROMPT_INJECTION_RE = /(?:^|\n)\s*(?:SYSTEM:|ASSISTANT:|HUMAN:|<\/?(?:system|prompt|instruction|tool_use)>|ignore (?:previous|above|all) instructions?)/gi;
|
|
80
|
+
function sanitizeValue(val) {
|
|
81
|
+
if (typeof val === "string") {
|
|
82
|
+
let s = val.replace(CONTROL_CHAR_RE, "");
|
|
83
|
+
if (PROMPT_INJECTION_RE.test(s)) {
|
|
84
|
+
s = s.replace(PROMPT_INJECTION_RE, "[SANITIZED]");
|
|
85
|
+
}
|
|
86
|
+
return s;
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(val))
|
|
89
|
+
return val.map(sanitizeValue);
|
|
90
|
+
if (val && typeof val === "object") {
|
|
91
|
+
const out = {};
|
|
92
|
+
for (const [k, v] of Object.entries(val)) {
|
|
93
|
+
out[k] = sanitizeValue(v);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
return val;
|
|
98
|
+
}
|
|
99
|
+
// ── JSON Output ──
|
|
100
|
+
const _isNdjson = () => process.argv.includes("--ndjson");
|
|
54
101
|
export function printJson(data) {
|
|
55
|
-
|
|
102
|
+
// Sanitize all string values in output
|
|
103
|
+
let output = sanitizeValue(data);
|
|
104
|
+
// Apply --fields filter
|
|
105
|
+
const fieldsArg = process.argv.indexOf("--fields");
|
|
106
|
+
if (fieldsArg !== -1 && process.argv[fieldsArg + 1] && output && typeof output === "object") {
|
|
107
|
+
const fields = process.argv[fieldsArg + 1].split(",").map(f => f.trim());
|
|
108
|
+
const envelope = output;
|
|
109
|
+
if (envelope.ok && envelope.data && typeof envelope.data === "object") {
|
|
110
|
+
envelope.data = pickFields(envelope.data, fields);
|
|
111
|
+
}
|
|
112
|
+
output = envelope;
|
|
113
|
+
}
|
|
114
|
+
// NDJSON mode: if data.data is an array, emit one JSON line per element
|
|
115
|
+
if (_isNdjson() && output && typeof output === "object") {
|
|
116
|
+
const envelope = output;
|
|
117
|
+
if (envelope.ok && Array.isArray(envelope.data)) {
|
|
118
|
+
const meta = envelope.meta;
|
|
119
|
+
for (const item of envelope.data) {
|
|
120
|
+
console.log(JSON.stringify({ ok: true, data: item, meta }));
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
console.log(JSON.stringify(output, null, _isNdjson() ? undefined : 2));
|
|
56
126
|
}
|
|
57
127
|
export function errorAndExit(msg) {
|
|
58
128
|
if (process.argv.includes("--json")) {
|
package/package.json
CHANGED
package/skills/perp-cli/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ allowed-tools: "Bash(perp:*), Bash(npx perp-cli:*), Bash(npx -y perp-cli:*)"
|
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
7
|
author: hypurrquant
|
|
8
|
-
version: "0.3.
|
|
8
|
+
version: "0.3.18"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# perp-cli Agent Guide
|
|
@@ -15,9 +15,11 @@ Multi-DEX perpetual futures CLI — Pacifica (Solana), Hyperliquid (HyperEVM), L
|
|
|
15
15
|
## Rules
|
|
16
16
|
|
|
17
17
|
1. **Always use `--json`** on every command.
|
|
18
|
-
2. **
|
|
19
|
-
3. **
|
|
20
|
-
4. **NEVER
|
|
18
|
+
2. **Always use `--dry-run`** before any mutating trade (then execute without it after user confirms).
|
|
19
|
+
3. **Always use `--fields`** to reduce output when you only need specific data (saves tokens).
|
|
20
|
+
4. **NEVER use `perp init`** — interactive, will hang.
|
|
21
|
+
5. **NEVER trade without user confirmation.**
|
|
22
|
+
6. **NEVER read ~/.perp/.env or key files.**
|
|
21
23
|
|
|
22
24
|
## Install
|
|
23
25
|
|
|
@@ -56,6 +58,30 @@ perp --json risk limits --max-leverage 5
|
|
|
56
58
|
|
|
57
59
|
Exchange aliases: `hl`, `pac`, `lt`. Symbols auto-resolve (`BTC`, `SOL`, `ICP`).
|
|
58
60
|
|
|
61
|
+
## Agent Tools
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Discover all commands and parameters at runtime (don't guess — query this)
|
|
65
|
+
perp --json agent schema
|
|
66
|
+
|
|
67
|
+
# Pre-validate a trade before execution (checks balance, liquidity, risk limits)
|
|
68
|
+
perp --json -e <EX> trade check <SYM> <SIDE> <SIZE>
|
|
69
|
+
|
|
70
|
+
# Dry-run: simulate trade without executing
|
|
71
|
+
perp --json --dry-run -e <EX> trade market <SYM> buy <SIZE>
|
|
72
|
+
|
|
73
|
+
# Filter output to specific fields (saves tokens)
|
|
74
|
+
perp --json --fields totalEquity,positions portfolio
|
|
75
|
+
|
|
76
|
+
# Stream large lists as NDJSON (one JSON per line, no buffering)
|
|
77
|
+
perp --json --ndjson -e <EX> market list
|
|
78
|
+
|
|
79
|
+
# Prevent duplicate orders with client ID
|
|
80
|
+
perp --json -e <EX> trade market <SYM> buy <SIZE> --client-id <UNIQUE_ID>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
All string outputs are auto-sanitized (control chars stripped, prompt injection patterns blocked).
|
|
84
|
+
|
|
59
85
|
## Arb Workflow
|
|
60
86
|
|
|
61
87
|
```
|
|
@@ -82,6 +108,18 @@ Exchange aliases: `hl`, `pac`, `lt`. Symbols auto-resolve (`BTC`, `SOL`, `ICP`).
|
|
|
82
108
|
`arb scan` returns `longExch`, `shortExch`, `netSpread`.
|
|
83
109
|
ALWAYS follow exactly. NEVER reverse. NEVER enter if `netSpread <= 0`.
|
|
84
110
|
|
|
111
|
+
## Slippage Check (CRITICAL)
|
|
112
|
+
|
|
113
|
+
`arb scan` returns `netSpread` in bps (already fee-adjusted).
|
|
114
|
+
Before executing, compare against estimated slippage:
|
|
115
|
+
```
|
|
116
|
+
Slippage ≈ 2-5 bps per leg for top pairs (BTC, ETH, SOL)
|
|
117
|
+
≈ 5-15 bps per leg for low-liquidity (altcoins)
|
|
118
|
+
Total round-trip slippage = (entry + exit) × 2 legs
|
|
119
|
+
```
|
|
120
|
+
**NEVER enter if:** `netSpread < total round-trip slippage`
|
|
121
|
+
Example: netSpread=12 bps, estimated slippage=4 bps/leg × 4 = 16 bps → SKIP.
|
|
122
|
+
|
|
85
123
|
## Portfolio Response
|
|
86
124
|
|
|
87
125
|
`portfolio` includes `risk: { level, canTrade, violations[] }` — no need for separate `risk status`.
|
|
@@ -100,8 +138,20 @@ Close both: perp --json -e <EX1> trade close <SYM> & perp --json -e <EX2> trade
|
|
|
100
138
|
|
|
101
139
|
Responses: `{ "ok": true, "data": {...} }` or `{ "ok": false, "error": { "code": "...", "retryable": true/false } }`
|
|
102
140
|
|
|
103
|
-
|
|
104
|
-
|
|
141
|
+
If `error.retryable` is `false`, do NOT retry — fix the cause first.
|
|
142
|
+
|
|
143
|
+
| Error | Action |
|
|
144
|
+
|-------|--------|
|
|
145
|
+
| `RATE_LIMITED` | wait 5s, retry (max 3) |
|
|
146
|
+
| `EXCHANGE_UNREACHABLE` | wait 10s, retry. 3x fail → skip that exchange |
|
|
147
|
+
| `TIMEOUT` | wait 5s, retry (max 3) |
|
|
148
|
+
| `INSUFFICIENT_BALANCE` | reduce size or bridge funds to that exchange |
|
|
149
|
+
| `SYMBOL_NOT_FOUND` | `perp --json -e <EX> market list` to verify symbol |
|
|
150
|
+
| `RISK_VIOLATION` | check `risk limits`, ask user to adjust if needed |
|
|
151
|
+
| `SIZE_TOO_SMALL` | `perp --json -e <EX> market info <SYM>` for min order size |
|
|
152
|
+
| `MARGIN_INSUFFICIENT` | reduce leverage or close existing positions |
|
|
153
|
+
| `DUPLICATE_ORDER` | already submitted — check positions, don't retry |
|
|
154
|
+
| Lighter `invalid signature` | check ~/.perp/.env or `perp --json -e lighter manage setup-api-key` |
|
|
105
155
|
|
|
106
156
|
## References
|
|
107
157
|
|