npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@curless/agentbank-sdk

v0.4.1

Published

Official buyer/agent SDK for agentbank — OAuth, agent + customer management, Wallets, and the Pay spend-control plane.

Readme

@curless/agentbank-sdk

Official buyer-side SDK for agentbank — agent-commerce payments.

Two things live here:

  • The buyer's wallet — a person signs in, binds a card, sets their own spend limits, and mints a one-off credential to pay a merchant with. This is what most integrators come for.
  • The Pay spend-control plane — agent OAuth, agent/customer management, authorize/capture against an agent's budget.
npm install @curless/agentbank-sdk

Install this package, not @curless/agentbank-core. Core is the shared kernel (crypto, errors, HTTP) that this package is built on; it arrives as a transitive dependency and has nothing in it you should be calling.

The buyer's wallet

The wallet is independent of any merchant. It holds the buyer's card and the buyer's limits, and it issues a credential; it never talks to a shop. The credential is what travels.

Sign in

The buyer signs in in their own browser, not in your app — verifying an email means clicking a link in an inbox, and tokenizing a real card means Stripe.js on a page. Neither fits in a chat window or a CLI. So sign-in is the RFC 8628 device flow: you get a URL, you show it, you wait.

import { createBuyerSession } from '@curless/agentbank-sdk';

const wallet = createBuyerSession({ baseUrl: 'https://mcp.curless.ai' });

const login = await wallet.startDeviceLogin();
console.log(`Open ${login.verificationUriComplete} to sign in`);
const user = await login.wait(); // resolves when they finish in the browser

wait() polls for you and tells pending / slow_down / expired / denied apart — an expired sign-in is a different sentence from one still in progress. The device code itself never leaves the returned object: it is the bearer credential that collects the session, so a caller who only needs a URL never sees it.

There is also wallet.login(email, password) for a deployment configured with a single account.

Pay for something

// The merchant priced this and opened a checkout; you have its id and total.
const credential = await wallet.payCredential({
  amount: 192_000,          // minor units — €1,920.00
  currency: 'EUR',
  merchantRef: merchantId,  // checked against the buyer's own allowlist
});

// Hand credential.token to that merchant's checkout as the payment token.
// You never see a card number, and neither does the merchant.

The credential is a Stripe Shared Payment Token: one seller, one currency, one maximum, fifteen minutes. It cannot be replayed against a different merchant or a larger sum.

The buyer's own limits are evaluated here, before Stripe is asked for anything. A refusal is the buyer's limit talking, not a payment failure — worth saying to them in those words:

try {
  await wallet.payCredential({ amount: 192_000, currency: 'EUR', merchantRef });
} catch (err) {
  if (AgentbankError.is(err) && err.status === 403) {
    // "this is over the daily limit you set" — not "the payment failed"
  }
}

The rest of the wallet

await wallet.me();            // who this is + spendPolicy + spentToday/Month
await wallet.setLimits({ dailyLimit: 50_000, merchantAllowlist: ['sinocare'] });
await wallet.balance();
await wallet.orders({ limit: 20 });

await wallet.cards();               // { cards, unavailable? }  ← see below
await wallet.bindCard('pm_card_visa');
await wallet.unbindCard('pm_123');

await wallet.logout();        // revokes server-side, then forgets it

wallet.fetch<T>(path, init) is the escape hatch for anything not wrapped above — it carries the session and the same 401/403 handling. Reach for a method first: the /v1/buyer/* paths are ours to change, and the methods are the part we keep.

cards() returns unavailable for a reason. An empty cards with no unavailable means the buyer has bound none. An empty cards with it means we could not read them. Flatten the two and you tell someone their cards are gone, and watch them bind another.

setLimits replaces the policy rather than merging it — an omitted field clears that limit. Read me() first and spread if you mean to change one.

Session state

  • Only a 401 ends the session. A 403 does not: hitting a limit you set yourself must not sign you out, or you cannot reach the session you would need to raise it.
  • wallet.current() returns the signed-in user or null, no round-trip.
  • Calls made while signed out throw with code buyer_not_logged_in; calls made after expiry throw buyer_session_expired. They are deliberately different strings, because "your session expired" rendered as "you have no orders" is the same bug twice.

The Pay spend-control plane

Admin (manage agents, fund, approve)

import { Agentbank } from '@curless/agentbank-sdk';

const ab = new Agentbank({
  baseUrl: 'https://mcp.curless.ai',
  apiKey: 'agb_admin_...', // an agentbank:admin / pay:admin key
});

const agent = await ab.agents.create({
  name: 'procurement-bot',
  spendPolicy: { perTransactionLimit: 50_00, dailyLimit: 500_00, approvalRequiredAbove: 100_00 },
});

await ab.pay.deposit({ amount: 1000_00, currency: 'USD' });
await ab.pay.fundAgent(agent.id, { amount: 500_00, currency: 'USD' });
const cred = await ab.agents.issueCredential(agent.id); // cred.secret shown once

Agent (spend, with auto-managed token)

const agentClient = Agentbank.withClientCredentials({
  baseUrl: 'https://mcp.curless.ai',
  clientSecret: cred.secret,
});

const auth = await agentClient.pay.authorize({
  agentId: agent.id,
  merchantRef: 'acme.example',
  amount: 12_00,
  idempotencyKey: 'order-123',
});
// auth.status: 'authorized' | 'pending_approval' | 'denied'
if (auth.status === 'authorized') await agentClient.pay.capture(auth.id);

SpendPolicy is one type across both halves — an agent's budget and a buyer's wallet limits are the same shape, evaluated by the same code on the server.

Notes

  • Amounts are minor-unit integers (USD = cents, EUR = cents).
  • Never send a card number. Cards are bound by Stripe reference (pm_… / tok_…); a PAN reaching a server is a compliance incident, and this API refuses one rather than storing it.
  • Errors throw AgentbankError with .status + .code — including transport failures: status === 0 with code timeout / aborted / network_error means no HTTP response happened. One catch type. Use AgentbankError.is(err), not instanceof, so the guard survives two copies of the package in one dependency tree.
  • Timeouts: every request has a 30s deadline by default. Tune per client (new Agentbank({ ..., timeoutMs })) or per request (RequestOptions.timeoutMs; 0 disables). RequestOptions.signal accepts an AbortSignal for caller-side cancellation.
  • ESM-only; Node ≥ 18 (uses global fetch).
  • Pass fetch in the constructor to inject a custom implementation (tests, proxies).

Prefer not to write code?

npx -y @curless/agentbank-mcp is this wallet as an MCP server — ten tools, no configuration, the buyer signs in at runtime. Same session, same limits.