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

@kcrz/klaud

v0.3.0

Published

Give an application a login through a Klaud group, in one browser handoff

Readme

@kcrz/klaud

Give an application a login through a Klaud.me group, in one browser handoff.

klaud is a CLI for developers and AI coding agents. It connects a local application to a Klaud group, provisions OpenID Connect credentials, and writes them to your environment file without anyone copying a secret between browser tabs.

Requires Bun 1.3 or newer.

bunx @kcrz/klaud connect --access link --json

How sign-in fits together

Klaud is a standard OpenID Connect provider.

| Setting | Value | | --- | --- | | Issuer | https://klaud.me (the KLAUD_ISSUER that connect writes) | | Discovery | https://klaud.me/.well-known/openid-configuration | | Callback in a Better Auth app | <origin>/api/auth/callback/klaud |

Better Auth genericOAuth needs nothing but the issuer:

import { genericOAuth } from 'better-auth/plugins'

genericOAuth({
  config: [
    {
      providerId: 'klaud',
      discoveryUrl: `${process.env.KLAUD_ISSUER}/.well-known/openid-configuration`,
      clientId: process.env.KLAUD_CLIENT_ID!,
      clientSecret: process.env.KLAUD_CLIENT_SECRET!,
      scopes: ['openid', 'profile', 'email'],
    },
  ],
})

On Cloudflare Workers (and other isolate runtimes), list the endpoints instead of discoveryUrl. Discovery fetches while Better Auth initializes, and a request that waits on another request's fetch never gets a response there, so every /api/auth call hangs:

genericOAuth({
  config: [
    {
      providerId: 'klaud',
      authorizationUrl: `${process.env.KLAUD_ISSUER}/api/auth/oauth2/authorize`,
      tokenUrl: `${process.env.KLAUD_ISSUER}/api/auth/oauth2/token`,
      userInfoUrl: `${process.env.KLAUD_ISSUER}/api/auth/oauth2/userinfo`,
      clientId: process.env.KLAUD_CLIENT_ID!,
      clientSecret: process.env.KLAUD_CLIENT_SECRET!,
      scopes: ['openid', 'profile', 'email'],
    },
  ],
})

Commands

klaud connect

Creates a group and an application in Klaud after one approval in the browser, then writes the credentials.

klaud connect [options]

| Option | Meaning | | --- | --- | | --app-name <name> | Application name. Defaults to the directory name. | | --group-name <name> | Group name. Defaults to the application name. | | --access <mode> | Who gets in: link, telegram, discord, patreon, github, gitlab. | | --redirect <url> | Redirect URL to register. Repeatable, so local and production go in together. Defaults to http://localhost:<port>/api/auth/callback/klaud, with the port read from scripts.dev in package.json (--port, -p or PORT=), else 3000. | | --env-file <path> | Where credentials go. Defaults to .env. | | --json | Machine output, see below. |

What it does:

  1. Prints the redirect URLs it will register and where the port came from (checking).
  2. Prints the approval link (authorization_required) and waits for the browser (waiting_for_user). The link is only printed; open it yourself or hand it to the person approving.
  3. Writes KLAUD_ISSUER, KLAUD_CLIENT_ID and KLAUD_CLIENT_SECRET to the env file. A key that already holds a value stops the command (env_conflict) instead of being overwritten.
  4. Writes a version 2 binding and the group field schema to .klaud/app.json. It holds no secret and can be committed. Version 1 bindings continue to be read.
  5. Stores a registration token for this application in ~/.klaud/credentials.json (mode 0600). add-url, remove-url, doctor and claims use it; it expires after a month.

Running connect again in a bound project with its secret still in place does nothing and reports linked. The client secret is handed over exactly once and cannot be fetched again.

klaud add-url <url>

Registers one more redirect URL, for example after a deploy. Registering an existing URL again is a no-op.

klaud add-url https://my-app.example.com/api/auth/callback/klaud

klaud remove-url <url>

Removes a redirect URL the application no longer serves, for example the old host after a move. Removing a URL that is not registered is a no-op. The last URL cannot be removed (last_redirect_url): add the new one first.

klaud remove-url https://old-host.example.com/api/auth/callback/klaud

klaud status

Prints what this directory is bound to (bound), or failed with not_bound.

klaud claims

Fetches the current group field schema using the stored registration token, updates .klaud/app.json and prints the schema. The terminal claims event lists field names in added, removed and changed; these arrays are empty when the schema is unchanged. A failed request leaves the file intact.

bunx @kcrz/klaud claims --json

The schema contains name, label, type, required, options, description and filledBy. It contains no member answers or secrets. filledBy: "member" means the participant answers; provider:… means the membership source supplies the answer, with participant input as a fallback.

klaud doctor

Checks that sign-in is wired correctly, one link of the chain per event:

| Check | Passes when | | --- | --- | | binding | .klaud/app.json exists and is readable. | | env | The env file sets KLAUD_ISSUER, KLAUD_CLIENT_ID, KLAUD_CLIENT_SECRET. Only presence is checked; values are never printed. | | discovery | ${KLAUD_ISSUER}/.well-known/openid-configuration answers and its issuer equals KLAUD_ISSUER. | | client | Klaud knows the application, and its client id equals KLAUD_CLIENT_ID. | | redirect_urls | At least one redirect URL is registered. The event lists them and says whether the local dev callback is among them (localRedirectRegistered); a missing local URL is reported with an add-url hint but does not fail the run. |

klaud doctor [--env-file .env] [--json]

The run ends in healthy, or in failed carrying the code of the first failed check.


Machine output (--json)

Every command accepts --json. Then stdout carries exactly one JSON object per line and nothing else; without it, human lines go to stderr. Every run ends in a terminal event, and a failure always exits non-zero.

| status | Terminal | Fields | | --- | --- | --- | | checking | no | message?, redirectUrls? | | authorization_required | no | url | | waiting_for_user | no | — | | check | no | check, ok, code?, message, nextAction?, missing?, redirectUrls?, localRedirectUrl?, localRedirectRegistered? | | linked | yes (connect) | appName, groupName, clientId, envFile, claimsScope | | url_added | yes (add-url) | url, redirectUrls | | url_removed | yes (remove-url) | url, redirectUrls | | bound | yes (status) | applicationId, groupId, issuer | | claims | yes (claims) | applicationId, claims, added, removed, changed | | healthy | yes (doctor) | applicationId, clientId, issuer, redirectUrls | | failed | yes | code, message, nextAction?: { command } |

{"status":"checking","message":"Redirect URL uses port 3200 from scripts.dev in package.json; pass --redirect to choose another.","redirectUrls":["http://localhost:3200/api/auth/callback/klaud"]}
{"status":"authorization_required","url":"https://klaud.me/setup/…"}
{"status":"waiting_for_user"}
{"status":"linked","appName":"my-app","groupName":"my-app","clientId":"…","envFile":".env"}

Failure codes include not_bound, token_missing, unauthorized, last_redirect_url, env_conflict, already_bound, declined_by_user, session_expired, rate_limited, access_not_configured, invalid_request, unknown_command, network, internal_error, and from doctor: env_missing, discovery_unreachable, issuer_mismatch, client_id_mismatch, no_redirect_urls.

Secrets — the client secret, the registration token, the poll secret — never appear in any output.


Receiving group claims in Better Auth

Your application must request the claims scope. connect writes credentials and the schema; it does not choose OAuth scopes for the application. With the scope, both userinfo and the ID token contain klaud: { group: { id, name }, claims: { … } }. Without it, the response is unchanged. Group fields named email or name stay inside klaud.claims.

import { genericOAuth } from 'better-auth/plugins'

const issuer = process.env.KLAUD_ISSUER!

genericOAuth({
  config: [{
    providerId: 'klaud',
    discoveryUrl: `${issuer}/.well-known/openid-configuration`,
    clientId: process.env.KLAUD_CLIENT_ID!,
    clientSecret: process.env.KLAUD_CLIENT_SECRET!,
    scopes: ['openid', 'profile', 'email', 'claims'],
    async getUserInfo(tokens) {
      const discovery = await fetch(`${issuer}/.well-known/openid-configuration`)
        .then((response) => response.json())
      const response = await fetch(discovery.userinfo_endpoint, {
        headers: { authorization: `Bearer ${tokens.accessToken}` },
      })
      if (!response.ok) throw new Error('Klaud userinfo request failed')
      const profile = await response.json()
      // Read profile.klaud?.claims.city when building your application profile. Persisting custom fields
      // in Better Auth requires declaring those fields in user.additionalFields.
      return {
        id: profile.sub,
        name: profile.name,
        email: profile.email,
        emailVerified: profile.email_verified,
        image: profile.picture,
        klaud: profile.klaud,
      }
    },
  }],
})

getUserInfo receives the typed answers from Klaud; add mapProfileToUser and configured additional user fields if the application needs to persist them. Optional unanswered fields are absent.

Environment variables

| Variable | Meaning | Default | | --- | --- | --- | | KLAUD_URL | Control plane URL | https://klaud.me |


License

MIT