perp-cli 0.4.2 → 0.4.9
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 +46 -5
- package/dist/arb/reconcile.d.ts +28 -0
- package/dist/arb/reconcile.js +162 -0
- package/dist/arb/sizing.d.ts +12 -1
- package/dist/arb/sizing.js +38 -5
- package/dist/arb/state.d.ts +6 -0
- package/dist/cli-spec.d.ts +0 -4
- package/dist/cli-spec.js +8 -1
- package/dist/commands/account.d.ts +1 -1
- package/dist/commands/account.js +300 -63
- package/dist/commands/agent.js +16 -9
- package/dist/commands/arb/index.js +243 -49
- package/dist/commands/arb-auto.js +696 -9
- package/dist/commands/arb.d.ts +2 -1
- package/dist/commands/arb.js +535 -131
- package/dist/commands/bot.js +207 -0
- package/dist/commands/dex.js +7 -1
- package/dist/commands/funding.d.ts +2 -1
- package/dist/commands/funding.js +211 -4
- package/dist/commands/funds.d.ts +4 -0
- package/dist/commands/funds.js +741 -0
- package/dist/commands/history.d.ts +2 -1
- package/dist/commands/history.js +508 -5
- package/dist/commands/manage.js +6 -4
- package/dist/commands/market.js +35 -2
- package/dist/commands/multileg.d.ts +16 -0
- package/dist/commands/multileg.js +174 -0
- package/dist/commands/pnl.d.ts +12 -0
- package/dist/commands/pnl.js +243 -0
- package/dist/commands/risk.d.ts +2 -0
- package/dist/commands/risk.js +56 -0
- package/dist/commands/settings.js +147 -0
- package/dist/commands/stream.js +132 -55
- package/dist/commands/trade.d.ts +1 -1
- package/dist/commands/trade.js +270 -267
- package/dist/commands/wallet.d.ts +4 -0
- package/dist/commands/wallet.js +11 -1
- package/dist/config.d.ts +6 -1
- package/dist/config.js +21 -1
- package/dist/dashboard/ws-feeds.js +1 -1
- package/dist/equity-tracker.d.ts +57 -0
- package/dist/equity-tracker.js +209 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +69 -0
- package/dist/exchanges/hyperliquid-spot.js +309 -0
- package/dist/exchanges/hyperliquid.d.ts +26 -5
- package/dist/exchanges/hyperliquid.js +171 -73
- package/dist/exchanges/interface.d.ts +1 -1
- package/dist/exchanges/lighter-spot.d.ts +55 -0
- package/dist/exchanges/lighter-spot.js +415 -0
- package/dist/exchanges/lighter.d.ts +10 -2
- package/dist/exchanges/lighter.js +210 -56
- package/dist/exchanges/pacifica.d.ts +11 -4
- package/dist/exchanges/pacifica.js +64 -37
- package/dist/exchanges/spot-interface.d.ts +45 -0
- package/dist/exchanges/spot-interface.js +22 -0
- package/dist/execution-log.d.ts +2 -2
- package/dist/funding/rates.d.ts +36 -0
- package/dist/funding/rates.js +146 -0
- package/dist/index.js +126 -161
- package/dist/liquidity.d.ts +12 -0
- package/dist/liquidity.js +43 -0
- package/dist/mcp-server.js +43 -96
- package/dist/signer/evm-local.d.ts +12 -0
- package/dist/signer/evm-local.js +19 -0
- package/dist/signer/index.d.ts +3 -0
- package/dist/signer/index.js +2 -0
- package/dist/signer/interface.d.ts +31 -0
- package/dist/signer/interface.js +7 -0
- package/dist/signer/solana-local.d.ts +13 -0
- package/dist/signer/solana-local.js +19 -0
- package/dist/smart-order.js +14 -3
- package/dist/strategies/split-order.d.ts +49 -0
- package/dist/strategies/split-order.js +138 -0
- package/dist/utils.js +7 -1
- package/dist/ws/market-feed.d.ts +74 -0
- package/dist/ws/market-feed.js +227 -0
- package/package.json +1 -1
- package/skills/perp-cli/SKILL.md +80 -4
- package/skills/perp-cli/references/agent-operations.md +19 -9
- package/skills/perp-cli/references/commands.md +54 -32
- package/skills/perp-cli/references/strategies.md +27 -17
- package/skills/perp-cli/scripts/arb-monitor.sh +74 -0
- package/skills/perp-cli/scripts/funding-analysis.sh +85 -0
- package/skills/perp-cli/scripts/preflight.sh +98 -0
- package/skills/perp-cli/scripts/spot-perp-scan.sh +83 -0
- package/skills/perp-cli/scripts/validate-arb.sh +112 -0
- package/skills/perp-cli/templates/arb-scan.sh +0 -15
- package/skills/perp-cli/templates/open-position.sh +0 -38
- package/skills/perp-cli/templates/wallet-setup.sh +0 -17
package/dist/commands/account.js
CHANGED
|
@@ -1,42 +1,210 @@
|
|
|
1
1
|
import { PacificaAdapter } from "../exchanges/pacifica.js";
|
|
2
|
-
import {
|
|
3
|
-
import { makeTable, formatUsd, formatPnl, printJson, jsonOk, jsonError, symbolMatch } from "../utils.js";
|
|
2
|
+
import { makeTable, formatUsd, formatPnl, printJson, jsonOk, jsonError, symbolMatch, withJsonErrors } from "../utils.js";
|
|
4
3
|
import chalk from "chalk";
|
|
4
|
+
import { assessRisk } from "../risk.js";
|
|
5
|
+
const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
|
|
6
|
+
async function fetchExchangeSnapshot(name, getAdapter) {
|
|
7
|
+
try {
|
|
8
|
+
const adapter = await getAdapter(name);
|
|
9
|
+
const [balance, positions, orders] = await Promise.all([
|
|
10
|
+
adapter.getBalance(),
|
|
11
|
+
adapter.getPositions(),
|
|
12
|
+
adapter.getOpenOrders(),
|
|
13
|
+
]);
|
|
14
|
+
return {
|
|
15
|
+
exchange: name,
|
|
16
|
+
connected: true,
|
|
17
|
+
balance,
|
|
18
|
+
positions,
|
|
19
|
+
openOrders: orders.length,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
return {
|
|
24
|
+
exchange: name,
|
|
25
|
+
connected: false,
|
|
26
|
+
balance: null,
|
|
27
|
+
positions: [],
|
|
28
|
+
openOrders: 0,
|
|
29
|
+
error: err instanceof Error ? err.message : String(err),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function buildSummary(snapshots) {
|
|
34
|
+
let totalEquity = 0;
|
|
35
|
+
let totalAvailable = 0;
|
|
36
|
+
let totalMarginUsed = 0;
|
|
37
|
+
let totalUnrealizedPnl = 0;
|
|
38
|
+
let totalPositions = 0;
|
|
39
|
+
let totalOpenOrders = 0;
|
|
40
|
+
const allPositions = [];
|
|
41
|
+
for (const snap of snapshots) {
|
|
42
|
+
if (snap.balance) {
|
|
43
|
+
totalEquity += Number(snap.balance.equity);
|
|
44
|
+
totalAvailable += Number(snap.balance.available);
|
|
45
|
+
totalMarginUsed += Number(snap.balance.marginUsed);
|
|
46
|
+
totalUnrealizedPnl += Number(snap.balance.unrealizedPnl);
|
|
47
|
+
}
|
|
48
|
+
totalPositions += snap.positions.length;
|
|
49
|
+
totalOpenOrders += snap.openOrders;
|
|
50
|
+
for (const pos of snap.positions) {
|
|
51
|
+
allPositions.push({ ...pos, exchange: snap.exchange });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Risk metrics
|
|
55
|
+
const marginUtilization = totalEquity > 0 ? (totalMarginUsed / totalEquity) * 100 : 0;
|
|
56
|
+
let largestPosition = null;
|
|
57
|
+
for (const pos of allPositions) {
|
|
58
|
+
const notional = Math.abs(Number(pos.size) * Number(pos.markPrice));
|
|
59
|
+
if (!largestPosition || notional > largestPosition.notional) {
|
|
60
|
+
largestPosition = { symbol: pos.symbol, exchange: pos.exchange, notional };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const exchangeConcentration = snapshots
|
|
64
|
+
.filter(s => s.balance && Number(s.balance.equity) > 0)
|
|
65
|
+
.map(s => ({
|
|
66
|
+
exchange: s.exchange,
|
|
67
|
+
pct: totalEquity > 0 ? (Number(s.balance.equity) / totalEquity) * 100 : 0,
|
|
68
|
+
}))
|
|
69
|
+
.sort((a, b) => b.pct - a.pct);
|
|
70
|
+
// Full risk assessment (reuses same data, no extra API calls)
|
|
71
|
+
const riskBalances = snapshots
|
|
72
|
+
.filter(s => s.balance)
|
|
73
|
+
.map(s => ({ exchange: s.exchange, balance: s.balance }));
|
|
74
|
+
const riskPositions = allPositions.map(p => ({ exchange: p.exchange, position: p }));
|
|
75
|
+
const assessment = assessRisk(riskBalances, riskPositions);
|
|
76
|
+
return {
|
|
77
|
+
totalEquity,
|
|
78
|
+
totalAvailable,
|
|
79
|
+
totalMarginUsed,
|
|
80
|
+
totalUnrealizedPnl,
|
|
81
|
+
totalPositions,
|
|
82
|
+
totalOpenOrders,
|
|
83
|
+
exchanges: snapshots,
|
|
84
|
+
positions: allPositions,
|
|
85
|
+
riskMetrics: {
|
|
86
|
+
marginUtilization,
|
|
87
|
+
largestPosition,
|
|
88
|
+
exchangeConcentration,
|
|
89
|
+
},
|
|
90
|
+
risk: {
|
|
91
|
+
level: assessment.level,
|
|
92
|
+
canTrade: assessment.canTrade,
|
|
93
|
+
violations: assessment.violations,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
5
97
|
function pac(adapter) {
|
|
6
98
|
if (!(adapter instanceof PacificaAdapter))
|
|
7
99
|
throw new Error("This command requires --exchange pacifica");
|
|
8
100
|
return adapter;
|
|
9
101
|
}
|
|
10
|
-
export function registerAccountCommands(program, getAdapter, isJson) {
|
|
102
|
+
export function registerAccountCommands(program, getAdapter, isJson, getAdapterForExchange) {
|
|
11
103
|
const account = program.command("account").description("Account commands");
|
|
12
|
-
account
|
|
13
|
-
.command("info")
|
|
14
|
-
.description("Show account balance and info")
|
|
15
|
-
.action(async () => {
|
|
16
|
-
const adapter = await getAdapter();
|
|
17
|
-
const bal = await adapter.getBalance();
|
|
18
|
-
if (isJson())
|
|
19
|
-
return printJson(jsonOk(bal));
|
|
20
|
-
console.log(chalk.cyan.bold(`\n ${adapter.name.toUpperCase()} Account Info\n`));
|
|
21
|
-
console.log(` Equity: $${formatUsd(bal.equity)}`);
|
|
22
|
-
console.log(` Available: $${formatUsd(bal.available)}`);
|
|
23
|
-
console.log(` Margin Used: $${formatUsd(bal.marginUsed)}`);
|
|
24
|
-
console.log(` Unreal. PnL: ${formatPnl(bal.unrealizedPnl)}`);
|
|
25
|
-
console.log();
|
|
26
|
-
});
|
|
27
104
|
account
|
|
28
105
|
.command("balance")
|
|
29
|
-
.
|
|
106
|
+
.alias("info")
|
|
107
|
+
.description("Account overview: perp balance, spot holdings, and 24h funding")
|
|
30
108
|
.action(async () => {
|
|
31
109
|
const adapter = await getAdapter();
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
110
|
+
const exName = adapter.name.toLowerCase();
|
|
111
|
+
// Fetch perp balance + funding in parallel
|
|
112
|
+
const [bal, fundingPayments] = await Promise.all([
|
|
113
|
+
adapter.getBalance(),
|
|
114
|
+
adapter.getFundingPayments(200).catch(() => []),
|
|
115
|
+
]);
|
|
116
|
+
// Spot balances (HL and LT only)
|
|
117
|
+
let spotBalances = [];
|
|
118
|
+
try {
|
|
119
|
+
if (exName === "hyperliquid") {
|
|
120
|
+
const { HyperliquidSpotAdapter } = await import("../exchanges/hyperliquid-spot.js");
|
|
121
|
+
const hlSpot = new HyperliquidSpotAdapter(adapter);
|
|
122
|
+
await hlSpot.init();
|
|
123
|
+
const raw = await hlSpot.getSpotBalances();
|
|
124
|
+
// Get spot prices for USD valuation
|
|
125
|
+
const markets = await hlSpot.getSpotMarkets();
|
|
126
|
+
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
127
|
+
// HL spot API returns token names with -SPOT suffix (e.g., "HYPE-SPOT", "USDC-SPOT")
|
|
128
|
+
const stripSuffix = (t) => t.replace(/-SPOT$/i, "").toUpperCase();
|
|
129
|
+
spotBalances = raw
|
|
130
|
+
.filter(b => Number(b.total) > 0)
|
|
131
|
+
.map(b => {
|
|
132
|
+
const base = stripSuffix(b.token);
|
|
133
|
+
return {
|
|
134
|
+
...b,
|
|
135
|
+
valueUsd: base === "USDC" ? Number(b.total) : (priceMap.get(base) ?? 0) * Number(b.total),
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else if (exName === "lighter") {
|
|
140
|
+
const { LighterAdapter } = await import("../exchanges/lighter.js");
|
|
141
|
+
const { LighterSpotAdapter } = await import("../exchanges/lighter-spot.js");
|
|
142
|
+
const ltSpot = new LighterSpotAdapter(adapter);
|
|
143
|
+
await ltSpot.init();
|
|
144
|
+
const raw = await ltSpot.getSpotBalances();
|
|
145
|
+
const markets = await ltSpot.getSpotMarkets();
|
|
146
|
+
const priceMap = new Map(markets.map(m => [m.baseToken.toUpperCase(), Number(m.markPrice)]));
|
|
147
|
+
spotBalances = raw
|
|
148
|
+
.filter(b => Number(b.total) > 0)
|
|
149
|
+
.map(b => ({
|
|
150
|
+
...b,
|
|
151
|
+
valueUsd: b.token === "USDC" || b.token === "USDC_SPOT"
|
|
152
|
+
? Number(b.total)
|
|
153
|
+
: (priceMap.get(b.token.toUpperCase()) ?? 0) * Number(b.total),
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch { /* spot not available */ }
|
|
158
|
+
// 24h funding summary
|
|
159
|
+
const cutoff24h = Date.now() - 24 * 60 * 60 * 1000;
|
|
160
|
+
const recent = fundingPayments.filter(f => f.time >= cutoff24h);
|
|
161
|
+
const funding24h = recent.reduce((sum, f) => sum + Number(f.payment), 0);
|
|
162
|
+
// Total spot value
|
|
163
|
+
const totalSpotUsd = spotBalances.reduce((s, b) => s + (b.valueUsd ?? 0), 0);
|
|
164
|
+
if (isJson()) {
|
|
165
|
+
return printJson(jsonOk({
|
|
166
|
+
perp: bal,
|
|
167
|
+
spot: spotBalances,
|
|
168
|
+
funding24h,
|
|
169
|
+
fundingPayments24h: recent.length,
|
|
170
|
+
totalSpotValueUsd: totalSpotUsd,
|
|
171
|
+
totalAccountValueUsd: Number(bal.equity) + totalSpotUsd,
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
// ── Perp Balance ──
|
|
175
|
+
console.log(chalk.cyan.bold(`\n ${exName.toUpperCase()} Account Balance\n`));
|
|
176
|
+
console.log(chalk.white.bold(" Perp Account"));
|
|
177
|
+
console.log(` Equity: $${formatUsd(bal.equity)}`);
|
|
178
|
+
console.log(` Available: $${formatUsd(bal.available)}`);
|
|
179
|
+
console.log(` Margin Used: $${formatUsd(bal.marginUsed)}`);
|
|
180
|
+
console.log(` Unreal. PnL: ${formatPnl(bal.unrealizedPnl)}`);
|
|
181
|
+
// ── Spot Holdings ──
|
|
182
|
+
if (spotBalances.length > 0) {
|
|
183
|
+
console.log(chalk.white.bold("\n Spot Holdings"));
|
|
184
|
+
const rows = spotBalances.map(b => [
|
|
185
|
+
chalk.white.bold(b.token),
|
|
186
|
+
b.total,
|
|
187
|
+
b.available,
|
|
188
|
+
b.held !== "0" ? b.held : chalk.gray("-"),
|
|
189
|
+
b.valueUsd ? `$${formatUsd(b.valueUsd)}` : chalk.gray("-"),
|
|
190
|
+
]);
|
|
191
|
+
console.log(makeTable(["Token", "Total", "Available", "Held", "Value"], rows));
|
|
192
|
+
}
|
|
193
|
+
else if (exName === "hyperliquid" || exName === "lighter") {
|
|
194
|
+
console.log(chalk.gray("\n No spot holdings.\n"));
|
|
195
|
+
}
|
|
196
|
+
// ── Funding 24h ──
|
|
197
|
+
console.log(chalk.white.bold(" Funding (24h)"));
|
|
198
|
+
console.log(` Payments: ${recent.length}`);
|
|
199
|
+
console.log(` Total: ${formatPnl(funding24h)}`);
|
|
200
|
+
// ── Total ──
|
|
201
|
+
const totalValue = Number(bal.equity) + totalSpotUsd;
|
|
202
|
+
if (totalSpotUsd > 0) {
|
|
203
|
+
console.log(chalk.white.bold("\n Total"));
|
|
204
|
+
console.log(` Perp Equity: $${formatUsd(bal.equity)}`);
|
|
205
|
+
console.log(` Spot Value: $${formatUsd(totalSpotUsd)}`);
|
|
206
|
+
console.log(` Total Value: $${formatUsd(totalValue)}`);
|
|
207
|
+
}
|
|
40
208
|
console.log();
|
|
41
209
|
});
|
|
42
210
|
account
|
|
@@ -172,42 +340,9 @@ export function registerAccountCommands(program, getAdapter, isJson) {
|
|
|
172
340
|
]);
|
|
173
341
|
console.log(makeTable(["Time", "Symbol", "Payment"], rows));
|
|
174
342
|
});
|
|
175
|
-
account
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
.action(async () => {
|
|
179
|
-
const adapter = await getAdapter();
|
|
180
|
-
if (adapter instanceof PacificaAdapter) {
|
|
181
|
-
const portfolio = await adapter.sdk.getPortfolio(adapter.publicKey);
|
|
182
|
-
printJson(jsonOk(portfolio));
|
|
183
|
-
}
|
|
184
|
-
else if (adapter instanceof HyperliquidAdapter) {
|
|
185
|
-
const state = await adapter.client.info.perpetuals.getClearinghouseState(adapter.address);
|
|
186
|
-
printJson(jsonOk(state));
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
account
|
|
190
|
-
.command("balance-history")
|
|
191
|
-
.description("Balance change history")
|
|
192
|
-
.action(async () => {
|
|
193
|
-
const adapter = await getAdapter();
|
|
194
|
-
const p = pac(adapter);
|
|
195
|
-
const raw = await p.sdk.getBalanceHistory(p.publicKey);
|
|
196
|
-
if (isJson())
|
|
197
|
-
return printJson(jsonOk(raw));
|
|
198
|
-
const history = (raw.data ?? raw);
|
|
199
|
-
if (!Array.isArray(history) || history.length === 0) {
|
|
200
|
-
console.log(chalk.gray("\n No balance history.\n"));
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
const rows = history.slice(0, 30).map((h) => [
|
|
204
|
-
new Date(Number(h.created_at ?? h.timestamp ?? 0)).toLocaleString(),
|
|
205
|
-
String(h.type ?? h.event_type ?? ""),
|
|
206
|
-
formatPnl(String(h.amount ?? h.change ?? "0")),
|
|
207
|
-
`$${formatUsd(String(h.balance ?? "0"))}`,
|
|
208
|
-
]);
|
|
209
|
-
console.log(makeTable(["Time", "Type", "Change", "Balance"], rows));
|
|
210
|
-
});
|
|
343
|
+
// "account portfolio" removed — use top-level "portfolio" for cross-exchange view
|
|
344
|
+
// or "account balance" for single-exchange balance
|
|
345
|
+
// "account balance-history" removed — Pacifica-only, rarely used
|
|
211
346
|
account
|
|
212
347
|
.command("margin <symbol>")
|
|
213
348
|
.description("Margin details for a specific symbol position")
|
|
@@ -422,4 +557,106 @@ export function registerAccountCommands(program, getAdapter, isJson) {
|
|
|
422
557
|
]);
|
|
423
558
|
console.log(makeTable(["ID", "Symbol", "Side", "Size", "Filled", "Duration"], rows));
|
|
424
559
|
});
|
|
560
|
+
// ── Portfolio (top-level command) ──
|
|
561
|
+
if (getAdapterForExchange) {
|
|
562
|
+
program
|
|
563
|
+
.command("portfolio")
|
|
564
|
+
.description("Cross-exchange portfolio overview (all exchanges at once)")
|
|
565
|
+
.option("--exchange <exchanges>", "Comma-separated exchanges to include (default: all)")
|
|
566
|
+
.action(async (opts) => {
|
|
567
|
+
await withJsonErrors(isJson(), async () => {
|
|
568
|
+
const exchanges = opts.exchange
|
|
569
|
+
? opts.exchange.split(",").map(e => e.trim())
|
|
570
|
+
: [...EXCHANGES];
|
|
571
|
+
const snapshots = await Promise.all(exchanges.map(ex => fetchExchangeSnapshot(ex, getAdapterForExchange)));
|
|
572
|
+
const summary = buildSummary(snapshots);
|
|
573
|
+
if (isJson()) {
|
|
574
|
+
return printJson(jsonOk(summary));
|
|
575
|
+
}
|
|
576
|
+
// ── Header ──
|
|
577
|
+
console.log(chalk.cyan.bold("\n Cross-Exchange Portfolio\n"));
|
|
578
|
+
// ── Balance Summary ──
|
|
579
|
+
console.log(chalk.white.bold(" Balances"));
|
|
580
|
+
const balRows = snapshots.map(s => {
|
|
581
|
+
if (!s.connected) {
|
|
582
|
+
return [chalk.white(s.exchange), chalk.red("disconnected"), "-", "-", "-", chalk.gray(s.error ?? "")];
|
|
583
|
+
}
|
|
584
|
+
const b = s.balance;
|
|
585
|
+
return [
|
|
586
|
+
chalk.white.bold(s.exchange),
|
|
587
|
+
`$${formatUsd(b.equity)}`,
|
|
588
|
+
`$${formatUsd(b.available)}`,
|
|
589
|
+
`$${formatUsd(b.marginUsed)}`,
|
|
590
|
+
formatPnl(b.unrealizedPnl),
|
|
591
|
+
chalk.green("connected"),
|
|
592
|
+
];
|
|
593
|
+
});
|
|
594
|
+
// Totals row
|
|
595
|
+
balRows.push([
|
|
596
|
+
chalk.cyan.bold("TOTAL"),
|
|
597
|
+
chalk.cyan.bold(`$${formatUsd(summary.totalEquity)}`),
|
|
598
|
+
chalk.cyan.bold(`$${formatUsd(summary.totalAvailable)}`),
|
|
599
|
+
chalk.cyan.bold(`$${formatUsd(summary.totalMarginUsed)}`),
|
|
600
|
+
formatPnl(summary.totalUnrealizedPnl),
|
|
601
|
+
"",
|
|
602
|
+
]);
|
|
603
|
+
console.log(makeTable(["Exchange", "Equity", "Available", "Margin Used", "uPnL", "Status"], balRows));
|
|
604
|
+
// ── Positions ──
|
|
605
|
+
if (summary.positions.length > 0) {
|
|
606
|
+
console.log(chalk.white.bold("\n Open Positions"));
|
|
607
|
+
const posRows = summary.positions.map(p => {
|
|
608
|
+
const sideColor = p.side === "long" ? chalk.green : chalk.red;
|
|
609
|
+
const notional = Math.abs(Number(p.size) * Number(p.markPrice));
|
|
610
|
+
return [
|
|
611
|
+
chalk.white.bold(p.symbol),
|
|
612
|
+
chalk.gray(p.exchange),
|
|
613
|
+
sideColor(p.side.toUpperCase()),
|
|
614
|
+
p.size,
|
|
615
|
+
`$${formatUsd(p.entryPrice)}`,
|
|
616
|
+
`$${formatUsd(p.markPrice)}`,
|
|
617
|
+
formatPnl(p.unrealizedPnl),
|
|
618
|
+
`$${formatUsd(notional)}`,
|
|
619
|
+
`${p.leverage}x`,
|
|
620
|
+
];
|
|
621
|
+
});
|
|
622
|
+
console.log(makeTable(["Symbol", "Exchange", "Side", "Size", "Entry", "Mark", "uPnL", "Notional", "Lev"], posRows));
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
console.log(chalk.gray("\n No open positions.\n"));
|
|
626
|
+
}
|
|
627
|
+
// ── Risk Metrics ──
|
|
628
|
+
console.log(chalk.white.bold("\n Risk Metrics"));
|
|
629
|
+
const levelColor = {
|
|
630
|
+
low: chalk.green,
|
|
631
|
+
medium: chalk.yellow,
|
|
632
|
+
high: chalk.red,
|
|
633
|
+
critical: chalk.bgRed.white,
|
|
634
|
+
}[summary.risk.level];
|
|
635
|
+
console.log(` Risk Level: ${levelColor(summary.risk.level.toUpperCase())}`);
|
|
636
|
+
console.log(` Can Trade: ${summary.risk.canTrade ? chalk.green("YES") : chalk.red("NO")}`);
|
|
637
|
+
const mu = summary.riskMetrics.marginUtilization;
|
|
638
|
+
const muColor = mu < 30 ? chalk.green : mu < 60 ? chalk.yellow : chalk.red;
|
|
639
|
+
console.log(` Margin Utilization: ${muColor(`${mu.toFixed(1)}%`)}`);
|
|
640
|
+
if (summary.riskMetrics.largestPosition) {
|
|
641
|
+
const lp = summary.riskMetrics.largestPosition;
|
|
642
|
+
console.log(` Largest Position: ${lp.symbol} on ${lp.exchange} ($${formatUsd(lp.notional)})`);
|
|
643
|
+
}
|
|
644
|
+
if (summary.riskMetrics.exchangeConcentration.length > 0) {
|
|
645
|
+
console.log(` Exchange Allocation:`);
|
|
646
|
+
for (const ec of summary.riskMetrics.exchangeConcentration) {
|
|
647
|
+
const bar = "\u2588".repeat(Math.round(ec.pct / 5)) + "\u2591".repeat(20 - Math.round(ec.pct / 5));
|
|
648
|
+
console.log(` ${ec.exchange.padEnd(12)} ${bar} ${ec.pct.toFixed(1)}%`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (summary.risk.violations.length > 0) {
|
|
652
|
+
console.log(chalk.red.bold("\n Risk Violations"));
|
|
653
|
+
for (const v of summary.risk.violations) {
|
|
654
|
+
const sevColor = { low: chalk.green, medium: chalk.yellow, high: chalk.red, critical: chalk.bgRed.white }[v.severity];
|
|
655
|
+
console.log(` ${sevColor(v.severity.toUpperCase().padEnd(8))} ${v.message}`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
console.log();
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
}
|
|
425
662
|
}
|
package/dist/commands/agent.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { printJson, jsonOk } from "../utils.js";
|
|
2
3
|
import { ERROR_CODES } from "../errors.js";
|
|
3
4
|
import chalk from "chalk";
|
|
5
|
+
const _require = createRequire(import.meta.url);
|
|
6
|
+
const _pkg = _require("../../package.json");
|
|
4
7
|
/** Infer parameter type from commander flags/description */
|
|
5
8
|
function inferType(flags, desc) {
|
|
6
9
|
if (flags.includes("[boolean]") || !flags.includes("<") && !flags.includes("["))
|
|
@@ -27,9 +30,11 @@ function extractSchema(cmd, parentPath = "perp") {
|
|
|
27
30
|
const fullCommand = `${parentPath} ${cmd.name()}`.trim();
|
|
28
31
|
const args = (cmd.registeredArguments ?? []).map((a) => {
|
|
29
32
|
const desc = a.description || "";
|
|
33
|
+
// Args are always values (not flags), so infer from name/desc semantics, never "boolean"
|
|
34
|
+
const rawType = inferType(`<${a.name()}>`, desc);
|
|
30
35
|
return {
|
|
31
36
|
name: a.name(),
|
|
32
|
-
type:
|
|
37
|
+
type: rawType === "boolean" ? "string" : rawType,
|
|
33
38
|
required: a.required,
|
|
34
39
|
description: desc,
|
|
35
40
|
enum: inferEnum(desc),
|
|
@@ -60,7 +65,7 @@ function extractSchema(cmd, parentPath = "perp") {
|
|
|
60
65
|
export function registerAgentCommands(program, getAdapter, isJson) {
|
|
61
66
|
const agent = program
|
|
62
67
|
.command("agent")
|
|
63
|
-
.description("Agent-friendly commands
|
|
68
|
+
.description("Agent-friendly commands");
|
|
64
69
|
// ── agent schema ── dump full command tree as JSON
|
|
65
70
|
agent
|
|
66
71
|
.command("schema")
|
|
@@ -72,7 +77,7 @@ export function registerAgentCommands(program, getAdapter, isJson) {
|
|
|
72
77
|
}
|
|
73
78
|
const envelope = {
|
|
74
79
|
schemaVersion: "2.0",
|
|
75
|
-
cliVersion:
|
|
80
|
+
cliVersion: _pkg.version,
|
|
76
81
|
generatedAt: new Date().toISOString(),
|
|
77
82
|
exchanges: ["pacifica", "hyperliquid", "lighter"],
|
|
78
83
|
errorCodes: errorCodeDocs,
|
|
@@ -82,10 +87,12 @@ export function registerAgentCommands(program, getAdapter, isJson) {
|
|
|
82
87
|
};
|
|
83
88
|
printJson(jsonOk(envelope));
|
|
84
89
|
});
|
|
85
|
-
// ── Top-level schema alias ──
|
|
86
|
-
program
|
|
90
|
+
// ── Top-level schema alias (hidden) ──
|
|
91
|
+
const schemaAlias = program
|
|
87
92
|
.command("schema")
|
|
88
|
-
.description("Output CLI schema as JSON (alias for agent schema)")
|
|
93
|
+
.description("Output CLI schema as JSON (alias for agent schema)");
|
|
94
|
+
schemaAlias._hidden = true;
|
|
95
|
+
schemaAlias
|
|
89
96
|
.action(() => {
|
|
90
97
|
const errorCodeDocs = {};
|
|
91
98
|
for (const [key, val] of Object.entries(ERROR_CODES)) {
|
|
@@ -93,7 +100,7 @@ export function registerAgentCommands(program, getAdapter, isJson) {
|
|
|
93
100
|
}
|
|
94
101
|
const envelope = {
|
|
95
102
|
schemaVersion: "2.0",
|
|
96
|
-
cliVersion:
|
|
103
|
+
cliVersion: _pkg.version,
|
|
97
104
|
generatedAt: new Date().toISOString(),
|
|
98
105
|
exchanges: ["pacifica", "hyperliquid", "lighter"],
|
|
99
106
|
errorCodes: errorCodeDocs,
|
|
@@ -110,7 +117,7 @@ export function registerAgentCommands(program, getAdapter, isJson) {
|
|
|
110
117
|
.action(() => {
|
|
111
118
|
printJson(jsonOk({
|
|
112
119
|
name: "perp-cli",
|
|
113
|
-
version:
|
|
120
|
+
version: _pkg.version,
|
|
114
121
|
description: "Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter) with HIP-3 deployed dex support",
|
|
115
122
|
exchanges: ["pacifica", "hyperliquid", "lighter"],
|
|
116
123
|
capabilities: [
|
|
@@ -276,7 +283,7 @@ export function registerAgentCommands(program, getAdapter, isJson) {
|
|
|
276
283
|
.action(async () => {
|
|
277
284
|
const results = {
|
|
278
285
|
timestamp: new Date().toISOString(),
|
|
279
|
-
cli_version:
|
|
286
|
+
cli_version: _pkg.version,
|
|
280
287
|
};
|
|
281
288
|
// Check exchange APIs
|
|
282
289
|
const { pingPacifica, pingHyperliquid } = await import("../shared-api.js");
|