perp-cli 0.12.14 → 0.12.17

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 CHANGED
@@ -218,16 +218,43 @@ Optional: add keys for trading and account data:
218
218
 
219
219
  ## AI Agent Skill
220
220
 
221
- Install as a skill for Claude Code, Cursor, Codex, Gemini CLI, etc.:
221
+ Drop-in skill for Claude Code, Cursor, Codex, Gemini CLI, and other agent CLIs. Bundles a non-interactive command guide, ops playbook, strategy framework, and reusable shell scripts (preflight, arb monitor, validation) so your agent stops at the right confirmation points and never wedges on interactive wizards.
222
+
223
+ ### Install
222
224
 
223
225
  ```bash
224
- # Using npx (recommended)
226
+ # Claude Code — slash command
227
+ /install-skill hypurrquant/perp-cli
228
+
229
+ # Cursor / Codex / generic agent CLIs — via npx
225
230
  npx skills add hypurrquant/perp-cli
226
231
 
227
- # Or via Claude Code slash command
228
- /install-skill hypurrquant/perp-cli
232
+ # Manual install (any agent that reads a Skills directory)
233
+ git clone https://github.com/hypurrquant/perp-cli.git
234
+ cp -R perp-cli/skills/perp-cli ~/.claude/skills/ # or your CLI's skills dir
229
235
  ```
230
236
 
237
+ The skill ships inside the npm package (`perp-cli/skills/perp-cli`), so a global install also makes it available locally:
238
+
239
+ ```bash
240
+ npm install -g perp-cli@latest
241
+ ls "$(npm root -g)/perp-cli/skills/perp-cli"
242
+ ```
243
+
244
+ ### What's in the skill
245
+
246
+ | File | Purpose |
247
+ |------|---------|
248
+ | `SKILL.md` | Top-level agent guide — rules, workflows, examples |
249
+ | `references/commands.md` | Full non-interactive command reference (every group) |
250
+ | `references/agent-operations.md` | Setup flows, idempotency, error handling, common mistakes |
251
+ | `references/strategies.md` | Funding-arb decision framework + risk management |
252
+ | `scripts/preflight.sh` | Install + wallet + connectivity + risk readiness check |
253
+ | `scripts/arb-monitor.sh` | Live monitor for open arb positions |
254
+ | `scripts/spot-perp-scan.sh` | Scan spot+perp opportunities with balance context |
255
+ | `scripts/funding-analysis.sh` | Cross-exchange funding overview / per-symbol drilldown |
256
+ | `scripts/validate-arb.sh` | Pre-execution validation (price / balance / risk / dry-run) |
257
+
231
258
  See [`skills/perp-cli/SKILL.md`](skills/perp-cli/SKILL.md) for the full agent guide.
232
259
 
233
260
  ## Agent-First CLI Design
@@ -613,9 +613,21 @@ export function registerAccountCommands(program, getAdapter, isJson, getAdapterF
613
613
  p = pac(adapter);
614
614
  }
615
615
  catch (err) {
616
+ // pac() throws a generic "Market settings are only available on
617
+ // Pacifica" — replace with a TWAP-specific message so HL/LT/Aster
618
+ // users see what's actually unsupported. Codex QA matrix #1 fix.
619
+ const orig = err instanceof Error ? err.message : String(err);
620
+ const msg = orig.includes("only available on Pacifica")
621
+ ? "TWAP orders are a Pacifica-only feature."
622
+ : orig;
616
623
  if (isJson())
617
- return printJson(jsonError("EXCHANGE_ERROR", err instanceof Error ? err.message : String(err)));
618
- console.error(chalk.red(`\n ${err instanceof Error ? err.message : String(err)}\n`));
624
+ return printJson(jsonError("NOT_SUPPORTED", msg, {
625
+ details: { remediation: "Use 'perp -e pacifica account twap-orders'" },
626
+ }));
627
+ console.error(chalk.red(`\n ${msg}\n`));
628
+ if (orig.includes("only available on Pacifica")) {
629
+ console.error(chalk.gray(` Run: perp -e pacifica account twap-orders\n`));
630
+ }
619
631
  return;
620
632
  }
621
633
  const sdk = p.sdk;
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ import { registerInitCommand } from "./commands/init.js";
49
49
  import { registerAlertCommands } from "./commands/alerts.js";
50
50
  import { loadSettings, saveSettings } from "./settings.js";
51
51
  import { setSharedApiNetwork } from "./shared-api.js";
52
+ import { LANDING_EXCHANGES, asterAgentMissing as getAsterAgentMissing, renderLandingExchangeLine } from "./landing.js";
52
53
  const _require = createRequire(import.meta.url);
53
54
  const _pkg = _require("../package.json");
54
55
  const program = new Command();
@@ -615,11 +616,16 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
615
616
  }
616
617
  else {
617
618
  // Configured — show exchange status + balance
618
- const EX_NAMES = ["pacifica", "hyperliquid", "lighter", "aster"];
619
- const exLabel = (e) => e === "pacifica" ? "Pacifica" : e === "hyperliquid" ? "Hyperliquid" : e === "lighter" ? "Lighter" : "Aster";
620
619
  // Ping + balance in parallel (with 5s timeout to keep landing fast)
621
620
  const withTimeout = (p, ms) => Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), ms))]);
622
- const statusResults = await Promise.allSettled(EX_NAMES.map(async (ex) => {
621
+ // Aster venue ships only signed account endpoints — no public
622
+ // address-based balance query exists (HL/PAC/LT all do). So when
623
+ // a user has an env-PK but no Aster agent, every getBalance()
624
+ // call throws NOT_SUPPORTED at venue. Detect that case here so
625
+ // landing can surface a clear "agent required" hint instead of
626
+ // a generic dash.
627
+ const asterAgentMissing = getAsterAgentMissing();
628
+ const statusResults = await Promise.allSettled(LANDING_EXCHANGES.map(async (ex) => {
623
629
  try {
624
630
  const adapter = await withTimeout(getAdapterForExchange(ex), 5000);
625
631
  const [balance, positions] = await withTimeout(Promise.all([adapter.getBalance(), adapter.getPositions()]), 5000);
@@ -668,10 +674,7 @@ if (rawArgs.length === 0 || (!hasSubcommand && !rawArgs.includes("-h") && !rawAr
668
674
  if (r.status !== "fulfilled")
669
675
  continue;
670
676
  const s = r.value;
671
- const icon = s.ok ? chalk.green("●") : chalk.red("○");
672
- const eq = s.ok ? chalk.white(`$${Number(s.equity).toLocaleString("en", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`) : chalk.gray("—");
673
- const pos = s.ok && s.positions > 0 ? chalk.yellow(` ${s.positions} pos`) : "";
674
- console.log(` ${icon} ${chalk.cyan(exLabel(s.exchange).padEnd(14))} ${eq}${pos}`);
677
+ console.log(renderLandingExchangeLine(s, asterAgentMissing));
675
678
  if (s.ok) {
676
679
  totalEquity += s.equity;
677
680
  totalPositions += s.positions;
@@ -705,7 +708,21 @@ else {
705
708
  const msg = err instanceof Error ? err.message : String(err);
706
709
  if (isJson()) {
707
710
  const { jsonError } = await import("./utils.js");
708
- console.log(JSON.stringify(jsonError("FATAL", msg)));
711
+ const { PerpError } = await import("./errors.js");
712
+ // Preserve typed PerpError code + remediation through the top-level
713
+ // catch (Rule #2: stay loud, keep semantic envelope).
714
+ if (err instanceof PerpError) {
715
+ const s = err.structured;
716
+ console.log(JSON.stringify(jsonError(s.code, s.message, {
717
+ status: s.status,
718
+ retryable: s.retryable,
719
+ retryAfterMs: s.retryAfterMs,
720
+ remediation: s.remediation,
721
+ })));
722
+ }
723
+ else {
724
+ console.log(JSON.stringify(jsonError("FATAL", msg)));
725
+ }
709
726
  }
710
727
  else {
711
728
  console.error(chalk.red(msg));
@@ -0,0 +1,9 @@
1
+ export declare const LANDING_EXCHANGES: readonly ["pacifica", "hyperliquid", "lighter", "aster"];
2
+ export type LandingExchangeStatus = {
3
+ exchange: typeof LANDING_EXCHANGES[number];
4
+ ok: boolean;
5
+ equity: number;
6
+ positions: number;
7
+ };
8
+ export declare function asterAgentMissing(): boolean;
9
+ export declare function renderLandingExchangeLine(status: LandingExchangeStatus, asterAgentMissing: boolean): string;
@@ -0,0 +1,20 @@
1
+ import chalk from "chalk";
2
+ import { listAgents } from "./agent-wallet/store.js";
3
+ export const LANDING_EXCHANGES = ["pacifica", "hyperliquid", "lighter", "aster"];
4
+ function exchangeLabel(exchange) {
5
+ return exchange === "pacifica" ? "Pacifica" : exchange === "hyperliquid" ? "Hyperliquid" : exchange === "lighter" ? "Lighter" : "Aster";
6
+ }
7
+ export function asterAgentMissing() {
8
+ return listAgents("aster").length === 0;
9
+ }
10
+ export function renderLandingExchangeLine(status, asterAgentMissing) {
11
+ if (!status.ok && status.exchange === "aster" && asterAgentMissing) {
12
+ return ` ${chalk.yellow("⚙")} ${chalk.cyan(exchangeLabel(status.exchange).padEnd(14))} ${chalk.yellow("agent required")} ${chalk.gray("→ perp wallet agent approve aster")}`;
13
+ }
14
+ const icon = status.ok ? chalk.green("●") : chalk.red("○");
15
+ const equity = status.ok
16
+ ? chalk.white(`$${Number(status.equity).toLocaleString("en", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`)
17
+ : chalk.gray("—");
18
+ const positions = status.ok && status.positions > 0 ? chalk.yellow(` ${status.positions} pos`) : "";
19
+ return ` ${icon} ${chalk.cyan(exchangeLabel(status.exchange).padEnd(14))} ${equity}${positions}`;
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perp-cli",
3
- "version": "0.12.14",
3
+ "version": "0.12.17",
4
4
  "description": "Multi-DEX Perpetual Futures CLI - Pacifica, Hyperliquid, Lighter, Aster",
5
5
  "bin": {
6
6
  "perp": "dist/index.js",