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

butt-dial-sdk

v0.5.0

Published

Client-side tunnel SDK for the Butt-Dial communication service — outbound, inbound callbacks, retry orchestration, and end-to-end diagnostic. Node/TypeScript port of the Python butt-dial-sdk.

Downloads

530

Readme

butt-dial-sdk (Node)

A stateless client-side tunnel to the Butt-Dial communication service for Node.js / TypeScript hosts. Ships outbound messaging, inbound callbacks, retry orchestration, and an end-to-end diagnostic CLI.

Status: 0.1.0 — first Node release. Wire-protocol parity with the Python SDK (0.4.4); same audit (rounds 1–7, F01–F92) governs both.


Install

npm install butt-dial-sdk

Node 18.17+ (for native fetch and crypto.timingSafeEqual).

Building a host app? Run npx buttdial guide for the agent-readable contract — patterns to use, anti-patterns to avoid. Run npx buttdial example list to see the canonical patterns; npx buttdial errors for the typed-exception reference.

Identity model — host owns the agent UUID

The host application generates the UUID for each agent and tells BD at registration. BD echoes it back along with a minted per-agent bearer token. Three tokens, three jobs:

| Token | Held by | Used for | |---|---|---| | teamToken | host backend | AccountsClient — provisioning, registration, billing, lifecycle | | agentToken | per-agent runtime | Agent — sending and receiving on this agent's number | | agentId | host-supplied UUID | identifies the agent across both surfaces |

Quickstart — register an agent and send a message

import { randomUUID } from "node:crypto";
import { AccountsClient, Agent } from "butt-dial-sdk";

// 1. Workspace admin: register an agent (host owns the UUID).
const accounts = new AccountsClient({
  baseUrl: "https://call.95percent.ai",
  teamToken: TEAM_TOKEN,
});
const agentId = randomUUID();
const out = await accounts.registerAgent(agentId, "Maya");
const agentToken = out.token;   // BD minted; persist to your vault

// 2. Per-agent runtime: send a message.
const maya = new Agent({
  baseUrl: "https://call.95percent.ai",
  agentId,
  agentToken,
});
const result = await maya.sendMessage({
  to: "+14155550123",
  body: "Hello from Maya",
  idempotencyKey: `msg-${randomUUID()}`,
});
console.log(result.messageSid);

Or, for single-agent setups, pull from the environment:

export BUTT_DIAL_BASE_URL=https://call.95percent.ai
export BUTT_DIAL_AGENT_ID=<the UUID your host generated>
export BUTT_DIAL_AGENT_TOKEN=<token BD minted at registration>
import { Agent } from "butt-dial-sdk";
const maya = Agent.fromEnv();

See examples/ for full working code.


What's included

| Component | What it does | |---|---| | Agent | Per-agent runtime. sendMessage() plus 13 typed WhatsApp action helpers (react, edit, forward, deleteMessage, typing, markRead, sendPoll, sendButtons, sendLocation, sendContact, postStatus, setChatTtl, sendViewOnce). MCP/SSE transport via @modelcontextprotocol/sdk with 3-attempt exponential backoff. | | InboundHandler | Decorator-style callbacks (onMessage, onDeliveryReceipt, onStatusUpdate). Ships WhatsApp payload parser + signature verification + subscription-challenge handler. Express middleware adapter included. | | RetryWorker | Background retry loop against a host-provided FailedMessageRepository interface. Pluggable 2^n-minute backoff. Dead-letter transition with optional onDeadLetter hook. | | AccountsClient | Workspace admin REST wrapper. Onboarding + token lifecycle (register → verify-email → setPhone → OTP → confirmPhonerotateToken) plus per-agent provisioning (registerAgent accepts a host-supplied agentId, BD echoes it back with the minted agentToken). Typed exceptions per server error code. | | verifyWebhookSignature | Stripe-style HMAC-SHA256 verifier for the account.verified webhook BD posts after a user clicks their verification link. 5-minute replay protection, constant-time compare via crypto.timingSafeEqual. | | buttdial CLI | guide, example list/show/extract, errors, doctor subcommands. Bundled docs and examples ship inside the npm package. |


Onboard a tenant — the canonical pattern

For host apps that auto-register a Butt-Dial workspace when a customer signs up: pass branding / returnUrl / webhookUrl to register(). The verification email is branded as your product, the verify page redirects users back to you, and BD posts back to your webhook the moment the user clicks. No polling, no "did it work?" dead-end.

See examples/with-onboarding-handshake/ for a complete Express implementation.

import { AccountsClient } from "butt-dial-sdk";
import { verifyWebhookSignature } from "butt-dial-sdk/webhooks";

const accounts = new AccountsClient({ baseUrl: "https://call.95percent.ai" });

// 1. Register the workspace (auto-fired from your /signup handler).
const resp = await accounts.register({
  orgName: "Acme Inc",
  ownerEmail: "[email protected]",
  branding: {
    logoUrl: "https://acme.com/logo-mark.svg",
    primaryColor: "#00C9A7",
    productName: "Acme",
    supportEmail: "[email protected]",
  },
  returnUrl: "https://acme.com/welcome",
  webhookUrl: "https://acme.com/api/webhooks/butt-dial/account-verified",
});
// Capture once — webhookSecret is NOT retrievable later.
await vault.put(workspaceId, "bdWebhookSecret", resp.webhookSecret);
await vault.put(workspaceId, "bdPollToken", resp.pollToken);

// 2. Receive the webhook when the user clicks (Express).
app.post(
  "/api/webhooks/butt-dial/account-verified",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const ok = verifyWebhookSignature({
      rawBody: req.body,
      timestampHeader: String(req.headers["x-bd-timestamp"] ?? ""),
      signatureHeader: String(req.headers["x-bd-signature"] ?? ""),
      secret: await vault.get(workspaceId, "bdWebhookSecret"),
    });
    if (!ok) return res.status(401).json({ error: "invalid signature" });
    const body = JSON.parse(req.body.toString("utf8"));
    workspace.bdTeamToken = body.teamToken;
    workspace.provisioned = true;
    await workspace.save();
    res.json({ ok: true });
  },
);

Idempotency: dedupe webhook deliveries by the X-BD-Delivery-Id header. BD retries (1s / 5s / 25s / 2m / 10m) until you 2xx, then dead-letters.

Smoke-test only / dev? The bare register({orgName, ownerEmail}) form still works but emits process.emitWarning(..., 'DeprecationWarning') on every call: it produces a BD-branded email and gives no completion signal. Don't ship a host app with the bare form.


CLI

npx buttdial guide               # full integration manual (AGENTS.md)
npx buttdial example list        # canonical patterns
npx buttdial example show with-onboarding-handshake
npx buttdial example extract with-onboarding-handshake ./my-app/
npx buttdial errors              # typed-exception reference
npx buttdial doctor --to +1...   # 5-stage live diagnostic

The doctor exits non-zero on any failed stage — wire it into CI as a live integration canary.


Design principles

  • Stateless — SDK owns no DB, no files, no global state beyond a configured Agent / AccountsClient.
  • Interfaces, not classes — host implements FailedMessageRepository as a plain TS interface. SDK never queries the DB.
  • Errors are reported, not thrownSendResult.failed/error/errorCode/stageFailed/remediation is the primary surface. The few places the SDK throws inherit from ButtDialError.
  • Async-first — built on native fetch, @modelcontextprotocol/sdk, native crypto.

License

MIT — free for commercial use, no obligations.