• leash-runner CLI — node packages/runner/dist/cli.js boots the Hono server.

Health + pause

  • GET /health{ ok, paused, source }.
  • GET /pause — same plus env_kill.
Wire on-chain pause via createHttpServer(store, { resolvePause: createPauseResolver({ fetchOnchainPaused }) }).

Receipt feeds

  • GET /a/:mint/receipts.jsonl — append-only NDJSON feed for one agent (in-memory Map in v0.1, swap for SQLite later).
  • POST /a/:mint/receipts — append a ReceiptV1. The runner validates against ReceiptV1Schema and rejects payloads where receipt.agent !== :mint.

Forward to the Leash API

When LEASH_API_URL and LEASH_API_KEY are set, the runner mirrors every accepted receipt to the API in the background:
LEASH_API_URL=https://api.leash.market \
LEASH_API_KEY=lsh_live_... \
node packages/runner/dist/cli.js
The forward is fire-and-forget — POST /a/:mint/receipts still returns 200 immediately even if the API is unreachable, and errors are logged with console.warn. The same receipt lands in api.leash.market, explorer.leash.market, and your local receipts.jsonl file with no extra wiring. The runner stores EndpointV1 descriptors that back the playground’s shareable /x/<id> x402 paywalls (see the Create a payment link guide).
  • GET /endpoints — list every endpoint. Filter with ?owner_agent=<mint>.
  • GET /endpoints/:id — fetch one endpoint by id.
  • POST /endpoints — create or update. Body is an EndpointV1 without v / created_at / updated_at. The runner generates an id if you don’t supply one.
  • DELETE /endpoints/:id — remove. Returns 204 on success.
EndpointV1 accepts two optional post-payment hooks that the /x/<id> route honours automatically — webhook_url (fire-and-forget POST of { payment, response }), and wrap_receipt (embed the receipt envelope in JSON responses). See Create a payment link for the full payload reference. Persistence is an append-only JSONL file at ./.leash/endpoints.jsonl (override with LEASH_RUNNER_DATA); set to an empty string to disable. Lines are either an EndpointV1 (upsert) or {"$delete": "<id>"} (remove).
import { createHttpServer, createMemoryStore, createEndpointStore } from '@leash/runner';

const app = createHttpServer(createMemoryStore(), {
  endpoints: createEndpointStore({ persistPath: '/var/lib/leash/endpoints.jsonl' }),
});

Typed runner client

The same package exports a typed client so SDK consumers don’t reach for raw fetch (and don’t misspell paths). It validates against EndpointV1Schema / ReceiptV1Schema on the way out.
import { createRunnerClient } from '@leash/runner';

const runner = createRunnerClient({ url: 'http://localhost:4040' });

await runner.health(); // { ok, paused, source }
await runner.endpoints.list({ ownerAgent }); // EndpointV1[]
const ep = await runner.endpoints.get('abc'); // EndpointV1 | null
await runner.endpoints.create({
  /* ... */
}); // EndpointV1
await runner.receipts.post(receipt); // returns canonical receipt_hash
await runner.receipts.list(mint); // ReceiptV1[]