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

@pidgeon-ai/issuer

v0.4.1

Published

A Post issuer you run yourself, inside your own network: your domain signs your AI agents' cards and sign-ins with a key that never leaves it, for the agents your identity provider (Keycloak, Auth0, Okta) says you have.

Readme

@pidgeon-ai/issuer

A Post issuer you run yourself, inside your own network. Your own domain signs your AI agents' cards, sign-ins and grants, with a key that never leaves you, for the agents your identity provider (Keycloak, Auth0, Okta) says you have. Nothing is sent to Pidgeon: not the key, not the list of agents.

It is for companies that may not hand authentication to a third party. Pidgeon Cloud runs the same issuer for everyone else. Both speak the same protocol, so @pidgeon-ai/id (the agent's side) and @pidgeon-ai/sign-in (the site's) work with either one unchanged.

npx @pidgeon-ai/issuer keygen acme.com

What runs where

| Where | What | | ---------------------- | -------------------------------------------------------------------------- | | Inside your network | The issuer (pidgeon-issuer serve, or the container), its key, its config | | Your identity provider | Who the agents are: their addresses, keys and groups | | post.<your domain> | The public documents: your keys, your agents' cards, OpenID discovery | | _post.<your domain> | A TXT record, so verifiers find post.<your domain> | | Each agent | Its own Ed25519 key, which signs its mail, its requests and its sign-ins |

The token endpoint only needs to reach your agents. Everything a stranger reads is a document: publish it through your reverse proxy, or write it out with pidgeon-issuer export <dir> and serve it from any static host.

Run it

  1. The issuer's key, once: pidgeon-issuer keygen acme.com. It writes pidgeon-issuer-key.json (mode 0600). Mount it as a secret, or put the seed in PIDGEON_ISSUER_KEY. Or keep it in AWS KMS, below. Any Signer fits in its place, so an HSM needs no change to the rest.
  2. Each agent's key, on the agent's machine: pidgeon-issuer keygen acme.com --out ~/.config/pidgeon/id/acme.com.json. Give the identity provider the key line it prints; the private half stays with the agent.
  3. issuer.json: copy issuer.example.json. directory.keycloak reads the agents from Keycloak (below); directory.static lists them by hand. grants maps a group to what its agents may do, signed by your domain as the organisation. clients are the sites you let your agents sign in to.
  4. DNS: pidgeon-issuer dns prints the _post record and where post.acme.com must answer.
  5. Serve: pidgeon-issuer serve (port PORT, default 8080), or docker build -f packages/issuer/Dockerfile -t pidgeon-issuer . from this repository's root and run it with the config and the key mounted at /etc/pidgeon-issuer/.

Keycloak

An agent is a Keycloak client with a service account, in the agents group. Its key is the Ed25519 key Keycloak authenticates it with (Credentials → Signed JWT, EdDSA), so the agent holds one key for Keycloak and for sites; a client on a secret names its key in a pidgeon.key attribute instead. pidgeon.address (default <clientId>@<domain>) and pidgeon.kind say the rest, and its groups are what grants maps.

"directory": {
  "keycloak": {
    "url": "https://sso.acme.internal",
    "realm": "acme",
    "client_id": "pidgeon-issuer",
    "group": "agents"
  }
}

pidgeon-issuer is a client of its own with a service account holding realm-management's view-clients and view-users, nothing that writes; its secret is KEYCLOAK_CLIENT_SECRET. examples/self-hosted-issuer runs all of it in Docker.

Auth0

An agent is a machine-to-machine application whose metadata names it: pidgeon_key (the agent's public key), pidgeon_address, and optionally pidgeon_kind, pidgeon_groups (comma-separated) and pidgeon_disabled (true to switch it off; Auth0 applications have no enabled switch). The issuer reads them with an application of its own authorized for the Management API with read:clients only; its secret is AUTH0_CLIENT_SECRET.

"directory": { "auth0": { "tenant": "acme.eu.auth0.com", "client_id": "<the issuer's app>" } }

Okta

An agent is an active OAuth service app whose profile names it: "profile": { "pidgeon": { "key": "…", "address": "…", "kind": "agent", "groups": ["refunds"] } }. Deactivate the app to switch it off. The issuer reads them with a service app of its own granted okta.apps.read and a read-only admin role (READ_ONLY_ADMIN is enough), signing in with Private Key JWT; its RSA or P-256 private key is OKTA_PRIVATE_KEY, and its kid is key_id.

"directory": { "okta": { "org": "https://acme.okta.com", "client_id": "<the issuer's app>" } }

On Auth0 and Okta the agent authenticates to the provider with its own RSA or EC credential: neither accepts an Ed25519 key for that, so the one-key trick is Keycloak's alone.

Sign in to a site

A site lists your issuer beside Pidgeon's:

import { pidgeonSignIn } from '@pidgeon-ai/sign-in';

export const pidgeon = pidgeonSignIn({
  clientId: 'client_shop',
  url: 'https://shop.example',
  secret: process.env.PIDGEON_SESSION_SECRET!,
  issuers: ['https://post.acme.com'],
});

and your agent signs in as it would through Pidgeon Cloud: its card comes from post.acme.com, and it goes to the issuer that signed the card.

import { loadIdentity, signInWithPidgeon } from '@pidgeon-ai/id';

const { site } = await signInWithPidgeon(await loadIdentity('acme.com'), {
  agent: '[email protected]',
  site: 'https://shop.example/api/auth/pidgeon/token',
});

The key in AWS KMS

Make an Ed25519 key that never leaves KMS and name it instead of a file:

aws kms create-key --key-spec ECC_NIST_EDWARDS25519 --key-usage SIGN_VERIFY
aws kms create-alias --alias-name alias/pidgeon-issuer --target-key-id <key id>
"key": { "kms": "alias/pidgeon-issuer", "region": "eu-west-1" }

The issuer's role needs kms:GetPublicKey and kms:Sign on that key and nothing else; credentials come the AWS SDK's usual way. Install @aws-sdk/client-kms beside the library (the container image has it). The key is read once at start: an alias is pinned to the key it names then, and every signature is checked against that key before it leaves.

A person signs in, through your identity provider

Your staff sign in to the same sites as themselves, through your own OpenID Connect provider (Keycloak, Auth0, Okta, Entra), with the flow Pidgeon Cloud uses. Register the issuer at the provider as a confidential client whose redirect URI is https://post.acme.com/api/post/oidc/callback, then:

"person": {
  "oidc": { "issuer": "https://sso.acme.com/realms/acme", "client_id": "pidgeon-issuer" }
},
"clients": [
  {
    "client_id": "client_shop",
    "host": "shop.example",
    "redirect_uris": ["https://shop.example/api/auth/pidgeon/callback"]
  }
]

with the client's secret in PIDGEON_PERSON_CLIENT_SECRET and a random PIDGEON_ISSUER_SECRET of 32 characters or more (openssl rand -base64 32), which seals the sign-in in progress and the sixty-second code, so there is still no database. The issuer will not start without it. Rotating it only ends the sign-ins in flight.

A site that sets issuer: 'https://post.acme.com' signs people in there. The person must have a verified address at your domain that is not an agent's; the site gets kind: "person", your provider's sub and that address.

What it checks before it signs anything

  • The token request is signed by a key your identity provider gives an agent, covers what it must, and is used once (one replica, or pass a shared replay store).
  • The card is one this issuer signed, current, that agent's, naming that key.
  • The agent is live in the identity provider now: disable it there and its next sign-in fails.
  • A grant is one this issuer signed, that agent's, still held through its group, and for that site.

Left out

Userinfo, introspection and revocation endpoints (an ID token lasts five minutes; revoking an agent is disabling it), dynamic client registration, and Pidgeon's card log, which would mean sending us your agents. Authorization stays yours: a grant is a relation (customer:42#refunds.issue for an agent), so it can be written from the engine you already run.

Apache-2.0.