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

offboard

v0.1.5

Published

Drop-in web SDK for Offboard: an AI exit interview inside your cancel flow that diagnoses the real churn reason and returns a structured outcome.

Readme

offboard (web SDK)

Drop-in cancel-flow SDK. Renders the exit-interview modal, runs the brief (≤5-question) interview against the Offboard engine, and hands your app a structured Outcome.

Status: stable. Install it, point it at your deployed Offboard engine (or the hosted default), and it runs the full flow end to end. To stand up the engine, see docs/DEPLOY.md in the engine repo.

Install

npm install offboard

Use

import Offboard from "offboard";

// Once, at startup.
Offboard.init({
  publicKey: "pk_live_...",
  // apiBaseUrl defaults to the hosted engine; override for self-hosting / staging:
  // apiBaseUrl: "https://offboard.your-domain.com",
});

// When the user clicks "Cancel subscription".
cancelButton.addEventListener("click", () => {
  Offboard.showCancelFlow({
    userId: "user_123",
    // Optional behavioral context — the richer this is, the sharper the diagnosis.
    context: {
      plan: "Starter",
      mrr: 49,
      tenure_days: 210,
      logins_last_30d: 27,
      activated: true,
      usage_summary: "Daily active; repeatedly hitting the event cap.",
    },
    // The SDK shows the authorized offer IN the chat. You react to the user's choice:
    onAccept: (outcome) => {
      // They took the save. Apply it yourself — Offboard never calls Stripe.
      //   outcome.intervention.id  -> the authorized action to apply
      //   outcome.reason           -> the REAL reason (e.g. "price_value_mismatch")
      //   outcome.mode             -> "act" (auto-apply) | "suggest" (maybe human-approve)
      applyOffer(outcome);
    },
    onCancel: (outcome) => {
      // Declined the offer, none was authorized, or the escape hatch was tapped.
      completeCancellation();
    },
    // Optional analytics hook, fires when the interview concludes (before the offer step):
    onResolved: (outcome) => track(outcome),
  });
});

React

import { useOffboardCancelFlow } from "offboard/react";

function CancelButton({ user }) {
  const cancel = useOffboardCancelFlow({
    publicKey: "pk_live_...",          // or call Offboard.init() once elsewhere
    userId: user.id,
    identityToken: user.offboardToken, // see "Identity verification" below
    onAccept: (o) => applyOffer(o),
    onCancel: () => completeCancellation(),
  });
  return <button onClick={cancel}>Cancel subscription</button>;
}

react is an optional peer dependency — the core offboard import stays framework-free.

Match your app's look

The widget renders a neutral, theme-aware (light/dark) surface by default. To make it look like your product, pass a theme. Colours are raw HSL triples"222 47% 11%", not #hex or hsl(...).

Offboard.showCancelFlow({
  userId: "user_123",
  theme: {
    // If your app uses shadcn/ui CSS variables, inherit them automatically — colours AND
    // radius, light and dark, with no further config:
    adoptHostTokens: true,
    // …or just set a brand accent (the primary CTA + send button):
    accent: "222 47% 11%",
  },
  // …callbacks
});

Precedence: explicit tokens (accent, primary, background, radius, …) > adoptHostTokens > the built-in neutral default. adoptHostTokens assumes classic HSL-triple shadcn tokens; on other setups set the tokens explicitly. Button and offer copy are overridable too: justCancelLabel, acceptLabel, declineLabel, offerEyebrow.

Identity verification (required for paid offers)

The cancel flow runs in the browser, so anything the browser sends can be forged. If a user opens devtools and posts mrr: 99999, they must not be able to unlock your richest save offer. So when your Offboard key is configured with a signing secret, the engine ignores the raw context economics and authorizes only off a token your backend signs.

  1. Your server signs the user's real economics with your secret (never expose the secret to the browser) and hands the token to your frontend.
  2. Pass it as identityToken. The engine verifies the HMAC and prices the save off the signed claims.

The token is base64url(payload) + "." + base64url(HMAC_SHA256(secret, payload)), where payload is JSON of the user fields plus iat (issued-at, unix seconds; tokens expire after 10 minutes). Any language can mint it; the canonical implementation is api/identity.py::sign_identity in the engine repo. Example payload:

{ "user_id": "user_123", "plan": "Growth", "mrr": 199, "tenure_days": 210,
  "activated": true, "logins_last_30d": 27, "signals": { "seats_used": 7 }, "iat": 1736380800 }

Keys without a signing secret (local dev / the demo) trust the request body as-is — never ship a production cancel flow on an unsecured key.

Design guarantees the SDK enforces

  • The "Just cancel" escape hatch is always rendered (hard constraint #3). Someone who chooses to answer is telling the truth; someone cornered types anything to escape.
  • The conversation is bounded — the engine stops at 5 questions (usually fewer); the modal closes on the server's done signal.
  • The model never authorizes anything. intervention_id is chosen by deterministic server-side policy; the SDK just relays it.
  • Transport failures never trap the user — on error the modal degrades to letting them cancel, it does not loop.

Develop

npm install
npm run build       # tsc -> dist/
npm run typecheck   # strict, no emit

The wire types in src/types.ts mirror engine/taxonomy.py. Keep them in lockstep — that shared Outcome shape is the contract between this SDK and the engine.