Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ The site defaults to the host root; the docs are under `/docs/`. Pages CI sets
`NEXT_PUBLIC_BASE_PATH=/agent-cli` for its deployment, builds and checks pull
requests, and deploys main. Command schemas and the skill guide are generated
from the CLI with `npm run docs:generate` and before docs builds.
Agents can fetch the same packaged skill as plain Markdown at
[SKILL.md](https://bloxwap.github.io/agent-cli/SKILL.md).

[Docs index](docs/index.md), [security](docs/security.md),
[error catalog](docs/errors.md), and [SKILL.md](packages/agent-cli/SKILL.md) remain
Expand Down
8 changes: 7 additions & 1 deletion apps/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ canonical and social URLs (default `https://bloxwap.github.io`). The static prev
reads the base path from the built artifact and needs no Next.js server.

Edit MDX in `content/docs/` and sidebar order in `meta.json`. `commands.mdx` and
`skill.mdx` are generated from the built CLI and packaged SKILL.md. Run
`skill.mdx`, plus the plain Markdown `public/SKILL.md`, are generated from the built
CLI and packaged SKILL.md. Run
`npm run docs:generate` after CLI changes. Dev/build also synchronize them;
`docs:check` rejects stale generated content, checks sidebar/local MDX links,
verifies HIG provenance, and checks Next.js/TypeScript types.

The canonical skill is https://bloxwap.github.io/agent-cli/SKILL.md
(`http://localhost:3903/SKILL.md` during local development). It preserves the
packaged skill's frontmatter and is linked from the skill guide and `llms.txt`.
The export and browser checks verify that the served file matches the source.

Build output lives in `out/`. The postbuild checker validates local HTML links
and assets, base-path containment, social metadata and 1200×630 PNG cards. Search
uses a static `search.json` index in the browser. `llms.txt`, robots, and sitemap
Expand Down
4 changes: 4 additions & 0 deletions apps/docs/content/docs/skill.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ title: Agent skill guide
description: Install and use the packaged SKILL.md with Codex or Claude Code.
---

import Link from 'next/link';

<Link href="/SKILL.md" download="SKILL.md" prefetch={false}>Download SKILL.md</Link> as plain Markdown, including the skill frontmatter.

The npm package ships `SKILL.md` and embeds the same content in the CLI. Install it with:

```sh
Expand Down
158 changes: 158 additions & 0 deletions apps/docs/public/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
name: agent-cli
description: Operate the agent-cli Hyperliquid trading CLI, including managed-agent onboarding, funding, orders, event monitoring, rotation, and normalized error recovery.
metadata:
version: "0.2.0"
---

Use `agent-cli` for the user's authorized Hyperliquid account operations. Obtain
command schemas with `agent-cli schema COMMAND`; arguments can be flags or
`--json '{...}'`, or a JSON object on stdin. Ordinary commands print exactly one
JSON object: `{ok:true,data}` or `{ok:false,code,message,retryable,details}`.
Logs/QR appear on stderr. `events --follow` and `logs` explicitly stream JSON lines.
Use `--network testnet` during testing; never silently change to mainnet.

## Onboarding sequence

1. The user creates and owns their own Privy account and app. Configure that
app ID and a local secret reference, e.g. `config set privyAppId APP` and
`config set privyAppSecretRef env:BLOXWAP_PRIVY_APP_SECRET`. The user sets the
environment secret outside the conversation on their own machine or runner.
Bloxwap does not host the app, receive credentials, or custody user keys.
Privy manages the trading-agent key in the user's app; master-wallet approval
always remains in the user's external wallet. The default Bloxwap builder is
`0x71b09A08257078A4d642F7Dd7315e656c837329a`, with `f=10` (1 bp = 0.01%).
Override with `config set builder.address` / `config set builder.fee` or
`BLOXWAP_BUILDER_ADDRESS` / `BLOXWAP_BUILDER_FEE`.
2. Run `init --master-address 0x... --network testnet`. Show the returned approval
payload/deeplink/QR. The user signs in their master wallet; submit only the
resulting signature with `init --signature 0x...`. Submit builder approval
separately with `init --builder-signature 0x...` for the configured 0.01% default. Continue only after `approved:true`.
3. Run `fund --amount 10 --origin-chain 42161`. Show `qrAscii`/deposit address,
origin chain/token, minimum, fees, expected received amount and expiry. On
testnet, do not treat the default Arbitrum mainnet chain/token as a test route:
use an explicitly supported test chain/token and `originDecimals`, or the
credential-gated CI harness with mocked Relay and pre-funded test collateral.
If no test route is known, stop funding and report the missing route. The user
transfers the quoted origin token. If `signatureSteps` require signatures,
show the exact payload and submit each via `deposit` in returned order.
4. Poll `status` until each active deposit is `settled`, for at most 12 polls
or 5 minutes, whichever comes first. Start with 1-second delays, double to a
15-second cap, and stop with an explicit pending report if the budget expires.
Settlement requires Relay success and Hyperliquid balance receipt. Stop on
failed/refunded deposits and explain their status. Do not place orders while
any deposit is awaiting funds or pending.
5. Run `run --detach`, then monitor `events --follow --since SEQ`. Persist the
last handled sequence; replay after reconnect. An unfilled or rejected order
remains incomplete: do not claim the requested trade succeeded. A fill updates the trading
plan, an error follows the catalog below, and expiry warnings prompt rotation
using external master-wallet approval.

## Completion and approval boundaries

Read this skill and query schemas before acting. Keep a progress record with the
network, current stage, deposit IDs, last event sequence and order cloid/oid;
record public IDs only. A missing user-owned Privy app, wallet signature, funding
route or settled balance is a blocked stage. Report the exact next action without
requesting any secret in chat or moving to the next stage. Repeat `init` to resume
persisted approvals rather than creating another agent. An `ok:true` transport
envelope does not imply exchange acceptance: inspect `data.result` and reconcile
`orders`/`events` after uncertain mutations. Never retry an order with a new cloid
until the original intent has been reconciled.

For rotation, verify `swapped:true` before asking the external wallet to sign old
agent revocation. Require `oldRevoked:true` before claiming rotation complete.
`revoke` immediately disables local signing; only `exchangeRevoked:true` confirms
that the signed exchange revocation was submitted. Inspect `status` to verify the
agent is absent from exchange approvals. Cancel outstanding orders before final
revocation, and report any cancellation or cleanup failure explicitly.

## Commands and example envelopes

Examples show representative `data`, not live balances or service promises.
Full inputs/outputs are discoverable with `schema`; amounts/prices are strings.

| Input | Example output |
| --- | --- |
| `--help` | `{"ok":true,"data":{"commands":[{"name":"status","description":"Read account status"}],"globalFlags":["--json","--config","--network","--pretty"]}}` |
| `schema order` | `{"ok":true,"data":{"input":{"type":"object"},"output":{"oneOf":[]}}}` (actual schemas include properties) |
| `config get network` | `{"ok":true,"data":"testnet"}` |
| `config set limits.maxLeverage 5` | `{"ok":true,"data":{"version":1,"limits":{"maxLeverage":5}}}` |
| `config validate` | `{"ok":true,"data":{"valid":true,"version":1}}` |
| `init --master-address 0x...` | `{"ok":true,"data":{"approved":false,"requiresSignature":true,"approvals":{"agent":{"typedData":{}}},"qrAscii":"..."}}` |
| `fund --amount 10 --no-qr` | `{"ok":true,"data":{"depositId":"...","depositAddress":"0x...","expectedReceived":"100000000","builderFeeRate":"0.01%","builderAddress":"0x71b09A08257078A4d642F7Dd7315e656c837329a","state":"awaiting_funds","qrAscii":null}}` |
| `deposit --deposit-id ID` | `{"ok":true,"data":{"depositId":"ID","steps":[{"id":"step:0","requiresSignature":true,"payload":{}}]}}` |
| `deposit --deposit-id ID --step-id step:0 --signature 0x...` | `{"ok":true,"data":{"depositId":"ID","steps":[]}}` |
| `status` | `{"ok":true,"data":{"balance":{"accountValue":"100"},"agent":{"approved":true,"daysRemaining":"179"},"deposits":[],"policy":{"id":"...","version":"2"},"runner":{"pid":"123"}}}` |
| `balance` | `{"ok":true,"data":{"accountValue":"100","accountValueUnits":"10000000000","withdrawable":"96","marginUsed":"4"}}` |
| `market --coin BTC` | `{"ok":true,"data":{"coin":"BTC","asset":"0","szDecimals":"5","maxLeverage":"40","mid":"20000"}}` |
| `positions` | `{"ok":true,"data":[{"coin":"BTC","size":"0.001","entryPrice":"20000","leverage":"5","marginMode":"cross"}]}` |
| `orders` | `{"ok":true,"data":[{"coin":"BTC","oid":"123","sz":"0.001","limitPx":"20000","side":"B"}]}` |
| `order --json '{"coin":"BTC","side":"buy","size":"0.001","price":"20000","tif":"Gtc","cloid":"0x11111111111111111111111111111111"}'` | `{"ok":true,"data":{"result":{"status":"ok","response":{"data":{"statuses":[{"resting":{"oid":123}}]}}},"builderFeeRate":"0.01%","builderAddress":"0x71b09A08257078A4d642F7Dd7315e656c837329a","cloid":"0x..."}}` |
| `order --json '{"coin":"BTC","side":"sell","size":"0.001","type":"market","slippage":0.01,"reduceOnly":true}'` | `{"ok":true,"data":{"result":{"status":"ok","response":{"data":{"statuses":[{"filled":{"oid":123}}]}}}}}` |
| `cancel --oid 123` or `cancel --all` | `{"ok":true,"data":{"cancelled":["123"],"result":{"status":"ok"}}}` |
| `modify --json '{"modifications":[{"oid":123,"price":"20100","size":"0.001"}]}'` | `{"ok":true,"data":{"status":"ok"}}` |
| `leverage --coin BTC --leverage 5 --mode isolated` | `{"ok":true,"data":{"status":"ok"}}` |
| `run --detach` | `{"ok":true,"data":{"pid":123,"startedAt":1700000000000,"network":"testnet"}}` |
| `events --since 12 --types '["fill"]'` | `{"ok":true,"data":[{"seq":13,"timestamp":"2026-01-01T00:00:00.000Z","type":"fill","data":{"tid":1}}]}` |
| `events --follow` | `{"seq":13,"timestamp":"2026-01-01T00:00:00.000Z","type":"fill","data":{"tid":1}}` (one line per event) |
| `logs --follow --level warn` | `{"level":"warn","requestId":"...","message":"Risk rejected","time":"..."}` (JSON lines) |
| `rotate` then `rotate --signature 0x...` | `{"ok":true,"data":{"oldAgent":"0x...","newAgent":"0x...","swapped":true,"oldRevoked":false,"revokeApproval":{}}}` |
| `rotate --revoke-signature 0x...` | `{"ok":true,"data":{"swapped":true,"oldRevoked":true}}` |
| `revoke --cancel-orders` | `{"ok":true,"data":{"locallyRevoked":true,"exchangeRevoked":false,"requiresSignature":true,"approval":{}}}` |
| `revoke --signature 0x...` | `{"ok":true,"data":{"locallyRevoked":true,"exchangeRevoked":true}}` |
| `audit --intent ID` | `{"ok":true,"data":[{"intentId":"ID","stage":"decision","data":{"allowed":true},"previousHash":"...","hash":"..."}]}` |
| `skill install --target codex` | `{"ok":true,"data":{"target":"codex","path":".../agent-cli/SKILL.md","version":"0.2.0"}}` |

Before an order, query `market --coin BTC` for `mid`, `szDecimals`, asset and
market leverage ceiling. Compute size/notional with decimal strings/bigints,
using the user-approved limit or the bounded IOC execution price. A valid schema
alone does not establish that an order is tradable or within the budget.

Use `--intent-id ID --intent "reason"` on trading actions to connect the user's
reasoning to the audit. Keep cloid stable when retrying a known order intent.
For TP/SL use `type:"tp"|"sl"` and `triggerPrice` with size/price/reduceOnly;
market orders use an explicit slippage-bounded IOC, not an unbounded market order.

## Error recovery

| Code | Action |
| --- | --- |
| RATE_LIMIT, NETWORK_ERROR | At most 3 read retries with exponential backoff; for uncertain mutations, reconcile order state/cloid before retrying. Stop and report unresolved results |
| NONCE_COLLISION | Retry using a newly issued nonce; inspect a stale lock if persistent |
| DEPOSIT_PENDING | Poll status until settled; stop on terminal funding failure |
| RUNNER_NOT_RUNNING | Start runner or use configured direct fallback |
| INSUFFICIENT_MARGIN | Reduce exposure or request authorized funding |
| POLICY_REJECTED | Stop and revise intent within limits; do not bypass policy |
| AGENT_EXPIRED | Rotate with master-wallet approval |
| AGENT_NOT_APPROVED | Complete init/approval or explain revocation |
| INVALID_SIZE, INVALID_PRICE | Correct input using market precision |
| UNKNOWN_MARKET | Refresh metadata; use an allowed market |
| ORDER_NOT_FOUND | Refresh orders before changing intent |
| CONFIG_INVALID | Correct configuration/input; never insert secrets |
| UPSTREAM_ERROR, INTERNAL | Stop, inspect redacted details/logs and report failure |

## Gotchas

HyperCore perps USDC uses **8 decimals**; EVM USDC uses **6**. Preserve strings and
bigints. Some deposits require **two distinct signatures**; resume stored steps
rather than restarting the route. Agent expiry is at most **180 days**. Never
ask for, paste, log or import a master private key or seed. The managed agent is
trade-only; local limits include markets, size/notional, leverage, daily loss and
open orders. Privy sees an L1 action hash, so it cannot independently inspect
notional/action contents; Hyperliquid agent permissions and local checks enforce
those boundaries. State is always `~/.bloxwap`, env prefix `BLOXWAP_`.


## Verification boundaries

Offline skill tests validate the documented command examples against CLI schemas.
Mocked lifecycle tests exercise resumable approvals, pending funding, events
and failure handling. These checks do **not** prove a live agent or
exchange run. The nightly testnet lifecycle uses only a company-owned disposable
CI master account and separate test Privy app, real Hyperliquid testnet and Privy,
and an explicitly mocked Relay route backed by already funded test collateral.
It never transfers real funds. Missing CI credentials fail the job; a skipped or
mocked test must never be reported as live validation. Record the workflow run,
commit, lifecycle report, event and audit artifacts before claiming a testnet pass.
See `docs/agent-workflow.md` and `docs/testnet.md` in the repository for setup.
5 changes: 3 additions & 2 deletions apps/docs/public/llms.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Bloxwap Agent CLI

Hyperliquid trading with Privy custody and Relay funding. Master keys stay external.
Hyperliquid trading with user-owned Privy apps and Relay funding. Master keys stay external.

- [Overview and installation](https://bloxwap.github.io/docs/)
- [quickstart](https://bloxwap.github.io/docs/quickstart/)
Expand All @@ -15,6 +15,7 @@ Hyperliquid trading with Privy custody and Relay funding. Master keys stay exter
- [skill](https://bloxwap.github.io/docs/skill/)
- [audit](https://bloxwap.github.io/docs/audit/)

- [SKILL.md — agent instructions](https://bloxwap.github.io/SKILL.md)
- [Sitemap](https://bloxwap.github.io/sitemap.xml)
- [Static search index](https://bloxwap.github.io/search.json)
- [Source and packaged SKILL.md](https://github.com/bloxwap/agent-cli/tree/main/packages/agent-cli)
- [SKILL.md source](https://github.com/bloxwap/agent-cli/blob/main/packages/agent-cli/SKILL.md)
9 changes: 8 additions & 1 deletion apps/docs/scripts/check-export.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ async function walk(path) {
}
for (const required of [
'index.html', '404.html', 'docs/index.html', 'search.json', 'icon.svg',
'llms.txt', 'robots.txt', '404.md',
'llms.txt', 'robots.txt', '404.md', 'SKILL.md',
]) {
if (!await exists(join(root, required))) failures.push(`Missing ${required}`);
}

// Agent-readiness contract: every machine-readable file the site advertises must exist
// and behave the way llms.txt and the 404 page claim it does.
const skillSource = await readFile(new URL('../../../packages/agent-cli/SKILL.md', import.meta.url));
if (await exists(join(root, 'SKILL.md')) && !(await readFile(join(root, 'SKILL.md'))).equals(skillSource)) {
failures.push('SKILL.md must exactly match the packaged skill, including its frontmatter');
}
const llms = await readFile(join(root, 'llms.txt'), 'utf8');
const skillUrl = new URL(`${basePath}/SKILL.md`, siteUrl).href;
if (!llms.includes(`](${skillUrl})`)) failures.push(`llms.txt: missing canonical skill link ${skillUrl}`);
const robots = await readFile(join(root, 'robots.txt'), 'utf8');
if (!/^sitemap:\s*https:\/\/\S+\/sitemap\.xml\s*$/im.test(robots)) failures.push('robots.txt: missing absolute Sitemap directive');
const notFoundMarkdown = await readFile(join(root, '404.md'), 'utf8');
Expand Down
11 changes: 8 additions & 3 deletions apps/docs/scripts/generate-content.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ for (const command of invoke('--help').commands) {
}
const skillSource = readFileSync(new URL('../../../packages/agent-cli/SKILL.md', import.meta.url), 'utf8');
const skill = '---\ntitle: Agent skill guide\ndescription: Install and use the packaged SKILL.md with Codex or Claude Code.\n---\n\n' +
'import Link from \'next/link\';\n\n' +
'<Link href="/SKILL.md" download="SKILL.md" prefetch={false}>Download SKILL.md</Link> as plain Markdown, including the skill frontmatter.\n\n' +
'The npm package ships `SKILL.md` and embeds the same content in the CLI. Install it with:\n\n```sh\nagent-cli skill install --target codex\nagent-cli skill install --target claude\n```\n\nCodex uses `$CODEX_HOME/skills/agent-cli/SKILL.md` (default `~/.codex/skills/agent-cli/SKILL.md`). Claude Code uses `~/.claude/skills/agent-cli/SKILL.md`. Reinstalling identical content is idempotent; replacing edited content requires `--force`.\n\nThe following guide comes directly from the packaged skill.\n\n' + skillSource.replace(/^---\n[\s\S]*?\n---\n/, '');
for (const [name, text] of [['commands.mdx', commands], ['skill.mdx', skill]]) {
const target = new URL(name, pages);
for (const [name, target, text] of [
['commands.mdx', new URL('commands.mdx', pages), commands],
['skill.mdx', new URL('skill.mdx', pages), skill],
['public/SKILL.md', new URL('../public/SKILL.md', import.meta.url), skillSource],
]) {
if (process.argv.includes('--check')) {
if (readFileSync(target, 'utf8') !== text) throw new Error(`${name} is stale: run npm run docs:generate`);
} else writeFileSync(target, text);
}
console.log('Command schemas and packaged skill documentation synchronized.');
console.log('Command schemas, skill guide and public SKILL.md synchronized.');
4 changes: 2 additions & 2 deletions apps/docs/scripts/prepare-public.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://bloxwap.github.io';
const absolute = (path) => new URL(`${basePath}${path}`, siteUrl).href;
const pages = JSON.parse(readFileSync(new URL('../content/docs/meta.json', import.meta.url), 'utf8')).pages;
writeFileSync(new URL('../public/robots.txt', import.meta.url), `User-agent: *\nAllow: /\nSitemap: ${absolute('/sitemap.xml')}\n`);
writeFileSync(new URL('../public/llms.txt', import.meta.url), '# Bloxwap Agent CLI\n\nHyperliquid trading with Privy custody and Relay funding. Master keys stay external.\n\n' +
writeFileSync(new URL('../public/llms.txt', import.meta.url), '# Bloxwap Agent CLI\n\nHyperliquid trading with user-owned Privy apps and Relay funding. Master keys stay external.\n\n' +
pages.map((page) => `- [${page === 'index' ? 'Overview and installation' : page}](${absolute(page === 'index' ? '/docs/' : `/docs/${page}/`)})`).join('\n') +
`\n\n- [Sitemap](${absolute('/sitemap.xml')})\n- [Static search index](${absolute('/search.json')})\n- [Source and packaged SKILL.md](https://github.com/bloxwap/agent-cli/tree/main/packages/agent-cli)\n`);
`\n\n- [SKILL.md — agent instructions](${absolute('/SKILL.md')})\n- [Sitemap](${absolute('/sitemap.xml')})\n- [Static search index](${absolute('/search.json')})\n- [SKILL.md source](https://github.com/bloxwap/agent-cli/blob/main/packages/agent-cli/SKILL.md)\n`);
Loading
Loading