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 +31 -4
- package/dist/commands/account.js +14 -2
- package/dist/index.js +25 -8
- package/dist/landing.d.ts +9 -0
- package/dist/landing.js +20 -0
- package/package.json +1 -1
- package/skills/perp-cli/SKILL.md +250 -133
- package/skills/perp-cli/references/agent-operations.md +95 -60
- package/skills/perp-cli/references/commands.md +267 -119
- package/skills/perp-cli/references/strategies.md +10 -7
- package/skills/perp-cli/scripts/arb-monitor.sh +15 -15
- package/skills/perp-cli/scripts/funding-analysis.sh +11 -6
- package/skills/perp-cli/scripts/validate-arb.sh +2 -1
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
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
228
|
-
/
|
|
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
|
package/dist/commands/account.js
CHANGED
|
@@ -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("
|
|
618
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/landing.js
ADDED
|
@@ -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
|
+
}
|