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

@handrail-chat/client

v0.4.0

Published

Framework-agnostic client for Handrail — transport, session exchange, and conversation state.

Readme

@handrail-chat/client

The framework-agnostic core of Handrail — an in-app help surface where customers ask for help in your product and agents answer from Slack.

If you are building with React, install @handrail-chat/react instead. It depends on this package and gives you a panel, a launcher and hooks. This one is for building your own UI, or a binding for another framework.

bun add @handrail-chat/client

No runtime dependencies.

What it gives you

import { createHandrail, createSseTransport } from "@handrail-chat/client";

const handrail = createHandrail({
  transport: createSseTransport({
    baseUrl: "https://api.handrail.chat",
    getToken: async () => (await fetch("/api/handrail-token")).json().then((r) => r.token),
  }),
});

handrail.subscribe(() => render(handrail.getState()));
await handrail.start();

await handrail.send("my export keeps failing");

getState() returns messages, conversations, connection state and status. subscribe fires on every change. There is no state library here and none is forced on you.

See the React package's README for the token contract — what your backend has to sign, and why.

The transport seam

Transport is an interface with two implementations. SseTransport talks to a real Handrail; MockTransport is memory, and needs no server, database or Slack. Nothing above the transport can tell them apart, which is what makes the same components drivable from fixtures in tests and stories.

import { createMockTransport } from "@handrail-chat/client";

const transport = createMockTransport();
const handrail = createHandrail({ transport });

transport.receiveAgentMessage("Looking into it now");

Context for the agent

Two ways to tell whoever answers in Slack something the customer did not type. Neither is ever shown to the customer. The React package's README covers both in depth, with a full example.

// An outcome the agent must not miss. Its own Slack message; works with the
// panel closed and on a resolved conversation.
await handrail.postNote('👍 "Exchanging tickets" solved it', { conversationId });

// What the customer is doing while they talk. Grey lines in one growing Slack
// message; sent only while the panel is open and the conversation unresolved.
handrail.setPanelOpen(true); // from your panel, as it opens and closes
handrail.trackActivity({ kind: "page", text: "Order #1234", url: location.href });

trackActivity never throws and returns nothing: events are batched for a quarter of a second, and a failed send is dropped rather than retried.

Notes

Live updates use fetch with a stream, not EventSource — the latter cannot set headers, which would force the session token into a query string where it lands in access logs and browser history.

start() never rejects, so it is safe to call without awaiting. If the widget cannot connect — your getToken throws, or the network is down — status is "error" and error says why, and it retries with backoff until it can. Gate a support launcher on status, not on whether start() resolved.

Reconnecting refetches a delta, not the whole history.

Sends are optimistic and reconcile by a client-generated id, so there is no duplicate when the server's echo arrives. A failed send stays in state marked failed with its clientId, ready for retry.

License

UNLICENSED — not yet open source.