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

@getstub/agent

v0.3.1

Published

Verifiable disclosure receipts for AI agent actions. Show whose side your agent was on, checked at a neutral registry.

Readme

@getstub/agent

Give your AI agent a receipt that shows whose side it was on.

Your agent issues a small signed record after each consequential action: what was asked, what was done, what money moved, what influenced the result. Your user checks it at a neutral registry. Three calls, and the third is one line per action.

Here is a live one before you install anything:

https://api.getstub.dev/check/72b4baee1719ec34acc5df5c514a12fd2e19b9a6776de4b45706988f765c77d8

Install

npm i @getstub/agent

Node 18 or newer. Uses real Ed25519 signatures through Web Crypto, so it runs anywhere modern with no native dependencies.

Quickstart

Three steps, once each, then one call per action.

import { Stub } from '@getstub/agent';

// 1. Declare your standing mandate, once. Policy level: who pays you, and
//    which kinds of conflict exist in your business at all. The sentence
//    your company approves one time.
const stub = new Stub({
  operator: 'Nimbus Assistant',
  declared: {
    paid_by: 'the traveler, subscription',
    conflicts: ['commission'],
  },
});

// 2. Register once. Idempotent, so calling it again is safe.
await stub.register();

// 3. Issue a receipt after your agent acts.
const receipt = await stub.issue({
  principal: user.id, // hashed on your side, never leaves as itself
  requested: 'find me a flight to Lisbon under 300',
  done: 'booked BA fare, 271',
  value_moved: '271, no commission',
  not_disclosed: [],
});

console.log(receipt.url);
// https://api.getstub.dev/check/<id>
// Hand this to the person your agent served. They check it. You never have to be trusted.

Keys, and keeping your identity

Read this before you deploy. Your keypair is your identity. There is no account, no password, no email; whoever holds the private key is the operator. The client generates one on first use, which is fine for a script and wrong for anything that restarts.

Save it once, and load it every time:

// once, locally, to create the identity
const stub = new Stub({ operator: 'Your Agent', operatorId: 'op_yours', declared: {...} });
await stub.register();
console.log(JSON.stringify(await stub.exportKeys()));   // store this JWK safely
// in production, every run
const stub = await Stub.fromKeys(JSON.parse(process.env.STUB_KEYPAIR), {
  operator: 'Your Agent',
  operatorId: 'op_yours',
  declared: { paid_by: '...', conflicts: [...] },
  principalSalt: process.env.STUB_SALT,
});

Note Stub.fromKeys, not the constructor. Passing stored JWK to new Stub({ keypair }) will not work, because the constructor expects live CryptoKey objects.

Two things to get right:

  • Store the keypair and the salt together, and treat both as secrets. A different salt means the same user hashes to a different digest, which quietly breaks your own erasure story.
  • Re-registering with the same key is fine and updates your name and mandate. Registering the same operator id with a different key is refused, which is what stops anyone taking your identity.

If you lose the key, or need to change it

const { keypair } = await stub.rotateKeys();   // generates a new pair
// or: await stub.rotateKeys(myPreGeneratedJwk)
// persist `keypair` immediately, before the next issue()

Rotation is proved by signing the new registration with the key currently on record, so only you can do it. Your operator id, your history, and every receipt you have already issued stay intact: each stub records the key that signed it, so rotating never invalidates the past.

If you lose the current key entirely, you cannot rotate and the operator id stays claimed. Keep a copy somewhere you will still have in a year.

Where disclosures come from

Nobody writes disclosures by hand. Your ranking code already knows when a boost, a commission, or a partner filter applied, because it applied them. Pass the same flags through with the influenced helpers, at the same line where the boost happens:

import { Stub, influenced } from '@getstub/agent';

const inf = [];
if (pick.paid_placement) inf.push(influenced.placement(pick.seller));
if (pick.commission_pct) inf.push(influenced.commission(pick.commission_pct + '%'));

await stub.issue({ ...action, not_disclosed: inf });

Helpers: influenced.placement, .commission, .partnerOnly, .ownBrand, .dataShare, and .other(kind, detail) for anything else. Plain strings still work. An empty array is itself proof: conflicts exist in your business, and none touched this action, signed.

The kinds available

Commercial, where money or a deal bends the result: placement, commission, partnerOnly, ownBrand, dataShare.

Structural, where no money is involved and the result is still shaped: engagementWeighted (ranking on popularity), sourcePrimacy (preferring one class of source), accessScope (what you cannot see, such as paywalled material), languageScope, curatedInputs (someone chose your sources, and it was you), personalisation (the result was conditioned on inferred user attributes).

The structural set exists because a receipt that only ever says "we took a commission" misses most of how a recommendation actually gets shaped. Most systems have several of these and almost none declare them.

influenced.other(kind, detail) takes anything else, but prefer a standard kind where one fits: records stay comparable across operators only if the vocabulary is shared. STANDARD_KINDS is exported if you want to check your own.

The one field that matters

not_disclosed is the load-bearing field. It is where the agent records what it held back: partner-only searches, paid placement, a commission the user did not see. An honest agent leaves it empty and proves it. A biased agent either declares the bias here, or signs a record that hides it, which is a confession with a signature on it.

// A commissioned agent, being honest about it:
await stub.issue({
  principal: 'user:sam',
  requested: 'find me a hotel in Rome',
  done: 'booked a partner hotel',
  value_moved: '340, 12% commission to operator',
  not_disclosed: ['only partner hotels were searched'],
});

The Stub Mark

Issue receipts for a few days and you earn a live mark you can put on your site, your docs, or your README. There is nothing to install, buy, or apply for. It is drawn by the registry from your own receipts, so you cannot set it and neither can we, and it goes quiet on its own if you stop issuing.

You do not have to go looking for it. register() hands you the embed line, and the client tells you once when you have earned it.

const { mark } = await stub.register();
console.log(mark.markdown);
// [![Stub](https://api.getstub.dev/badge/op_yours.svg)](https://getstub.dev/o/op_yours)

Or ask at any time, including where you currently stand:

const m = await stub.mark({ live: true });
// { badgeUrl, pageUrl, html, markdown, standing, stubsIssued, threshold, stubsToEarn }

Paste either form wherever it belongs:

<a href="https://getstub.dev/o/op_yours">
  <img src="https://api.getstub.dev/badge/op_yours.svg" alt="Stub registry status" height="20">
</a>

An image served by the registry. No script, no tracking, nothing loaded onto your users, and the same width in every state so nothing in your footer shifts when your standing changes.

Clicking it opens your operator page: the mandate you declared, when you last issued, how often your receipts carry a disclosure, and your most recent records. That page is not on your servers, which is the part that makes it worth anything to the person reading it.

Set STUB_QUIET=1 if you would rather the client said nothing.

Privacy by default

Pass your internal user id as principal and the client hashes it on your side with a salt only you hold. The registry rejects anything that is not an opaque digest, so no name or email can be stored even by accident. Set the salt once so digests stay stable across restarts:

const stub = new Stub({
  operator: 'Nimbus Assistant',
  declared: { paid_by: 'the traveler, subscription', conflicts: ['commission'] },
  principalSalt: process.env.STUB_SALT,
});

If a user asks to be forgotten, drop their mapping to the salt and their digests become permanently meaningless. Keep requested and done about the action, not the person; the client warns in development if free text looks like it carries an email or phone number.

What you get back

issue() returns { id, url }.

  • id is the stub's permanent identifier.
  • url is the receipt page the served party opens. It resolves at the registry, is witnessed by a key you do not hold, and cannot be edited or deleted, including by us.

Verify independently

Every check is co-signed by the registry's witness key. The public half is published, so anyone can verify a receipt without trusting the registry either:

https://api.getstub.dev/witness

Pricing

Free tier: 1,000 stubs a month. Checking is always free.

Links

  • Docs and quickstart: https://getstub.dev
  • Registry: https://api.getstub.dev
  • Witness key: https://api.getstub.dev/witness

Copyright Stub 2026. MIT licensed.