perp-cli 0.12.13 → 0.12.14

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.
@@ -530,11 +530,11 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
530
530
  steps.push({ step: 1, command: "perp strategy plan example", description: "Show example execution plan format" }, { step: 2, command: "perp --json strategy plan validate <file>", description: "Validate plan file" }, { step: 3, command: "perp --json strategy plan execute <file> --dry-run", description: "Dry-run the plan", dangerous: true });
531
531
  }
532
532
  else if (g.includes("status") || g.includes("check") || g.includes("overview") || g.includes("portfolio")) {
533
- steps.push({ step: 1, command: `perp -e ${ex} --json status`, description: "Full account overview" }, { step: 2, command: `perp -e ${ex} --json account positions`, description: "Detailed positions" }, { step: 3, command: `perp -e ${ex} --json account orders`, description: "Open orders" }, { step: 4, command: "perp --json portfolio", description: "Cross-exchange portfolio summary" });
533
+ steps.push({ step: 1, command: `perp -e ${ex} --json portfolio`, description: "Single-exchange portfolio overview" }, { step: 2, command: `perp -e ${ex} --json account positions`, description: "Detailed positions" }, { step: 3, command: `perp -e ${ex} --json account orders`, description: "Open orders" }, { step: 4, command: "perp --json portfolio --arb", description: "Cross-exchange portfolio + top arb opportunities" });
534
534
  }
535
535
  else if (g.includes("deposit")) {
536
536
  const amount = extractNumber(g) || "<amount>";
537
- steps.push({ step: 1, command: "perp --json wallet balance", description: "Check wallet balance" }, { step: 2, command: `perp --json funds deposit ${ex} ${amount}`, description: `Deposit $${amount} to ${ex}`, dangerous: true }, { step: 3, command: `perp -e ${ex} --json account balance`, description: "Verify deposit arrived" });
537
+ steps.push({ step: 1, command: "perp --json wallet balance", description: "Check wallet balance" }, { step: 2, command: `perp --json funds deposit ${ex} ${amount}`, description: `Deposit $${amount} to ${ex}`, dangerous: true }, { step: 3, command: `perp -e ${ex} --json portfolio`, description: "Verify deposit arrived" });
538
538
  }
539
539
  else if (g.includes("withdraw")) {
540
540
  const amount = extractNumber(g) || "<amount>";
@@ -567,7 +567,7 @@ server.tool("suggest_command", "Given a natural language trading goal, suggest t
567
567
  steps.push({ step: 1, command: "perp settings show", description: "Show current settings" }, { step: 2, command: "perp settings referrals on", description: "Enable referral codes (no extra fees, just volume tracking)" });
568
568
  }
569
569
  else {
570
- steps.push({ step: 1, command: `perp -e ${ex} --json status`, description: "Check account status" }, { step: 2, command: `perp -e ${ex} --json account balance`, description: "Check exchange connectivity via balance fetch" });
570
+ steps.push({ step: 1, command: `perp -e ${ex} --json portfolio`, description: "Check account overview" }, { step: 2, command: `perp --json portfolio --health --exchanges ${ex}`, description: "Check exchange connectivity" });
571
571
  }
572
572
  return {
573
573
  content: [{
@@ -1055,10 +1055,10 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
1055
1055
  const schema = {
1056
1056
  schemaVersion: "2.0",
1057
1057
  name: "perp",
1058
- description: "Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter)",
1059
- exchanges: ["pacifica", "hyperliquid", "lighter"],
1058
+ description: "Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter, Aster)",
1059
+ exchanges: ["pacifica", "hyperliquid", "lighter", "aster"],
1060
1060
  globalFlags: [
1061
- { flag: "-e, --exchange <name>", description: "Exchange to use (pacifica, hyperliquid, lighter)", default: "pacifica" },
1061
+ { flag: "-e, --exchange <name>", description: "Exchange to use (pacifica, hyperliquid, lighter, aster)", default: "pacifica" },
1062
1062
  { flag: "--json", description: "Output as JSON for structured parsing" },
1063
1063
  { flag: "-n, --network <net>", description: "Network: mainnet or testnet", default: "mainnet" },
1064
1064
  { flag: "--dry-run", description: "Simulate without executing (for trade commands)" },
@@ -1080,7 +1080,7 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
1080
1080
  account: {
1081
1081
  description: "Account data (read-only)",
1082
1082
  subcommands: {
1083
- info: { usage: "perp account balance", description: "Balance, equity, margin, PnL" },
1083
+ info: { usage: "perp portfolio -e <exchange>", description: "Single-exchange balance, equity, margin, PnL" },
1084
1084
  positions: { usage: "perp account positions", description: "Open positions" },
1085
1085
  orders: { usage: "perp account orders", description: "Open/pending orders" },
1086
1086
  history: { usage: "perp account history", description: "Order history" },
@@ -1240,7 +1240,7 @@ server.resource("cli_schema", "perp://schema", { mimeType: "application/json", d
1240
1240
  },
1241
1241
  portfolio: { usage: "perp portfolio", description: "Cross-exchange portfolio summary" },
1242
1242
  status: { usage: "perp portfolio", description: "Full account overview" },
1243
- health: { usage: "perp account balance --json", description: "Exchange connectivity check via balance fetch" },
1243
+ health: { usage: "perp portfolio --health", description: "Exchange connectivity check" },
1244
1244
  settings: {
1245
1245
  description: "CLI settings",
1246
1246
  subcommands: {
@@ -1418,7 +1418,7 @@ server.tool("get_funding_analysis", "Analyze funding income across exchanges —
1418
1418
  symbol: z.string().optional().describe("Filter by symbol"),
1419
1419
  }, async ({ period, exchange: exFilter, symbol: symFilter }) => {
1420
1420
  try {
1421
- const exchanges = exFilter ? [exFilter] : ["pacifica", "hyperliquid", "lighter"];
1421
+ const exchanges = exFilter ? [exFilter] : ["pacifica", "hyperliquid", "lighter", "aster"];
1422
1422
  const periodMatch = period.match(/^(\d+)(d|w|m)$/);
1423
1423
  const sinceMs = periodMatch
1424
1424
  ? Date.now() - parseInt(periodMatch[1]) * ({ d: 86400000, w: 604800000, m: 2592000000 }[periodMatch[2]] ?? 86400000)
@@ -1488,7 +1488,7 @@ server.tool("get_pnl_analysis", "P&L analysis combining trading fees and funding
1488
1488
  exchange: z.string().optional().describe("Filter by exchange"),
1489
1489
  }, async ({ period, exchange: exFilter }) => {
1490
1490
  try {
1491
- const exchanges = exFilter ? [exFilter] : ["pacifica", "hyperliquid", "lighter"];
1491
+ const exchanges = exFilter ? [exFilter] : ["pacifica", "hyperliquid", "lighter", "aster"];
1492
1492
  const periodMatch = period.match(/^(\d+)(d|w|m)$/);
1493
1493
  const sinceMs = periodMatch
1494
1494
  ? Date.now() - parseInt(periodMatch[1]) * ({ d: 86400000, w: 604800000, m: 2592000000 }[periodMatch[2]] ?? 86400000)
@@ -1602,7 +1602,7 @@ server.tool("get_arb_compare", "Compare active arbitrage positions side by side
1602
1602
  // ============================================================
1603
1603
  server.resource("market_prices", "market://prices", { mimeType: "application/json", description: "Live cross-exchange prices for all perpetual futures markets" }, async () => {
1604
1604
  const prices = {};
1605
- await Promise.allSettled(["pacifica", "hyperliquid", "lighter"].map(async (ex) => {
1605
+ await Promise.allSettled(["pacifica", "hyperliquid", "lighter", "aster"].map(async (ex) => {
1606
1606
  try {
1607
1607
  const adapter = await getOrCreateAdapter(ex);
1608
1608
  const markets = await adapter.getMarkets();
@@ -1669,7 +1669,8 @@ server.prompt("trading-guide", "Step-by-step guide for trading with perp-cli via
1669
1669
  ## Supported Exchanges
1670
1670
  - **pacifica** (Solana) — SOL-based perpetual futures
1671
1671
  - **hyperliquid** (HyperEVM) — Low-fee perpetuals with HIP-3 DEXes
1672
- - **lighter** (Ethereum) — Ethereum-based perpetual futures`,
1672
+ - **lighter** (Ethereum) — Ethereum-based perpetual futures
1673
+ - **aster** (BNB Chain) — BNB Chain perpetual futures`,
1673
1674
  },
1674
1675
  }],
1675
1676
  }));
@@ -10,21 +10,20 @@ export async function validateTrade(adapter, params) {
10
10
  const checks = [];
11
11
  const warnings = [];
12
12
  const sym = params.symbol.toUpperCase();
13
- // Fetch market data, balance, positions, orderbook in parallel.
13
+ // Fetch market list, balance, and positions in parallel.
14
+ // Defer the orderbook call until symbol membership is confirmed so invalid
15
+ // symbols return a structured symbol_valid failure instead of a venue error.
14
16
  // SSOT rule #2: errors must propagate, never silently substitute defaults.
15
17
  // getMarkets retains a tracked-error wrapper so the failed-load case surfaces
16
18
  // a structured symbol_valid check (not a generic exception).
17
19
  let marketsError = null;
18
- const [markets, balance, positions, orderbook] = await Promise.all([
20
+ const [markets, balance, positions] = await Promise.all([
19
21
  adapter.getMarkets().catch((e) => {
20
22
  marketsError = e instanceof Error ? e.message : String(e);
21
23
  return [];
22
24
  }),
23
25
  adapter.getBalance(),
24
26
  adapter.getPositions(),
25
- params.type !== "limit"
26
- ? adapter.getOrderbook(sym)
27
- : Promise.resolve({ bids: [], asks: [] }),
28
27
  ]);
29
28
  if (markets.length === 0 && marketsError) {
30
29
  checks.push({ check: "symbol_valid", passed: false, message: `Failed to load markets from ${adapter.name}: ${marketsError}` });
@@ -42,6 +41,9 @@ export async function validateTrade(adapter, params) {
42
41
  checks.push({ check: "symbol_valid", passed: false, message: `${sym} not found on ${adapter.name}` });
43
42
  return { valid: false, checks, warnings, timestamp: new Date().toISOString() };
44
43
  }
44
+ const orderbook = params.type !== "limit"
45
+ ? await adapter.getOrderbook(sym)
46
+ : { bids: [], asks: [] };
45
47
  const markPrice = Number(market.markPrice);
46
48
  if (params.type === "limit" && (params.price === undefined || !Number.isFinite(params.price))) {
47
49
  throw new Error(`Limit order requires explicit price (received ${params.price}); refusing silent mark-price substitution.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.12.13",
3
+ "version": "0.12.14",
4
4
  "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster",
5
5
  "bin": {
6
6
  "perp": "dist/index.js",
@@ -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.12.13"
8
+ version: "0.12.14"
9
9
  ---
10
10
 
11
11
  # perp-cli Agent Guide
@@ -41,7 +41,7 @@ Multi-DEX perpetual futures CLI + MCP server — Pacifica (Solana), Hyperliquid
41
41
  ## Install
42
42
 
43
43
  ```bash
44
- perp --version 2>/dev/null # check if exists (must be >= 0.12.13)
44
+ perp --version 2>/dev/null # check if exists (must be >= 0.12.14)
45
45
  npm install -g perp-cli@latest 2>/dev/null || npx -y perp-cli@latest --json --version
46
46
  ```
47
47