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

@zehnmindai/desk-sdk

v0.2.1

Published

In-app support for zehn-desk: a Support screen, a launcher bubble and screenshot reports for any web app.

Readme

@zehnmindai/desk-sdk

In-app support for zehn-desk: a Support screen, a floating launcher and screenshot reports your users send from inside your web app. Every report lands in the same desk as your Telegram groups — AI triage, dedup, SLA, replies, analytics — with the device, the screen, the last actions and a masked screenshot attached.

  • Looks like part of your app: one brand colour (in code, or set in the desk console with no code at all) becomes the whole palette; it takes your page's font and follows your own dark-mode switch.
  • Framework-free core, Preact UI in a Shadow DOM (your CSS stays out, ours stays in), React bindings.
  • Identity: anonymous per browser, or your signed JWT — no account creation on our side.
  • Screenshot of the current screen, sensitive fields masked (data-zd-mask, passwords, card fields), annotate before sending.
  • Replies show up in-app (polling); AI answers with 👍/👎; a "did this solve it?" once resolved.
  • Deflection: "Search for help" on the home screen and matching published answers while the person types.
  • A floating panel, a full-height drawer for portals, or mounted into your own Support page; full screen on phones.
  • ≤ 1 KB loader, ~37 KB widget (gzip), four UI languages (en · ru · uz · uz-Cyrl).

5-minute quickstart

Script tag (no build step):

<script async src="https://desk.zehnmind.com/sdk/v1/zehn-desk.js" data-app-key="zd_app_…" data-api-url="https://desk-api.zehnmind.com"></script>
<script>
  // a queue that works before the SDK has loaded (and after)
  window.ZehnDesk = window.ZehnDesk || function () { (window.ZehnDesk.q = window.ZehnDesk.q || []).push(arguments); };
  ZehnDesk("identify", "<jwt from your backend>");
</script>

npm:

npm i @zehnmindai/desk-sdk
import { ZehnDesk } from "@zehnmindai/desk-sdk";

const desk = ZehnDesk.init({
  appKey: "zd_app_…",                         // Console → Apps
  apiUrl: "https://desk-api.zehnmind.com",
  appVersion: "2.3.1",
  locale: "auto",                              // en | ru | uz | uz-Cyrl | auto
  mask: { selectors: [".secret"] },            // besides data-zd-mask, password and card inputs
});

desk.identify(userJwt);   // when the person signs in (see "Identity")
desk.open();              // desk.open("new") · desk.open({ ticketId })
desk.on("unread", ({ messages }) => badge.textContent = messages || "");

React:

import { ZehnDeskProvider, ZehnDeskSupport, ZehnDeskButton } from "@zehnmindai/desk-sdk/react";

<ZehnDeskProvider config={{ appKey, apiUrl, user: { jwt }, launcher: false }}>
  <App />
  <ZehnDeskButton className="help">Help</ZehnDeskButton>          {/* unread in <span data-zd-unread> */}
  <Route path="/support" element={<ZehnDeskSupport />} />           {/* the Support screen inline */}
</ZehnDeskProvider>

Get the app key from the desk console (Console → Apps → New app); the same page shows these snippets with your values filled in.

Blend it into your app

Without code. In the desk console, Console → Apps → Appearance sets the brand colour, a logo, the greeting (per language), the reply time shown on the home screen, the header style and where the launcher sits. Every installed widget picks it up on its next session. Anything below, passed in code, wins over the console.

One colour. theme.primary takes any CSS colour, including your own variable — var(--brand) is followed live, so if your dark mode changes it, the widget follows. The text on the brand (white or dark), a brand text colour that stays legible in both themes, tints and the header gradient are worked out for you.

ZehnDesk.init({
  appKey: "zd_app_…",
  theme: {
    primary: "var(--brand)",   // or "#2563EB"; primaryContrast forces the text colour on it
    mode: "auto",              // default: your page's own dark switch first (a .dark class, data-theme, data-bs-theme…), then the system
    font: "inherit",           // default: your page's font · "system" · or a font-family
    radius: 12,                // panel corners; cards ¾, chips ½
  },
  panel: { placement: "drawer", header: "compact" },  // floating (default) | drawer · hero (default) | compact
  launcher: { position: "bottom-left", offset: { y: 72 }, variant: "label", label: "Help" },  // or false
});
desk.setTheme({ mode: "dark" });  // merges over what init got

Framework recipes for primary: Bootstrap "var(--bs-primary)", shadcn/ui "hsl(var(--primary))", Tailwind v4 "var(--color-primary)", MUI (CSS variables) "var(--mui-palette-primary-main)".

Script tag, still no JavaScript: data-primary="#2563EB", data-theme="auto|system|light|dark", data-position="bottom-left", data-offset-x / data-offset-y, data-launcher="icon|label|false", data-placement="drawer", data-header="compact", data-locale.

Tokens and parts. Every colour is a CSS custom property on the zehn-desk element (--zd-primary, --zd-bg, --zd-surface, --zd-text, --zd-muted, --zd-border, --zd-success, --zd-warning, --zd-danger, --zd-radius, --zd-font, --zd-font-size, --zd-panel-width, --zd-z, …) and the widget exposes ::part() hooks (launcher, badge, panel, header, body, card, button, input, bubble, footer) — see docs/integration.md.

Identity

Anonymous users are keyed by a random device id kept in localStorage. To attach reports to a signed-in person, mint a short-lived HS256 JWT on your backend with the app's JWT secret (shown once when the app is created, rotatable):

# Python
jwt.encode({"sub": user.id, "name": user.name, "email": user.email, "iat": now, "exp": now + 3600}, APP_JWT_SECRET, algorithm="HS256")
// Node (jose)
await new SignJWT({ name, email }).setProtectedHeader({ alg: "HS256" }).setSubject(user.id).setIssuedAt().setExpirationTime("1h").sign(new TextEncoder().encode(APP_JWT_SECRET));

sub is required and must be stable; exp ≤ 24 h. Pass it as user: { jwt } at init or call desk.identify(jwt) later (a string, or { jwt }) — tickets the person opened anonymously on that device are merged into their account, and the home screen greets them by first name.

Screenshots and privacy

By default the widget re-renders the current page into an image when the report form opens (nothing leaves the browser until Send). Elements matching [data-zd-mask], input[type=password], [autocomplete^=cc-] and [autocomplete=one-time-code] are painted over; add your own with mask.selectors. Cross-origin images that taint the canvas produce a grey placeholder frame instead. Options: screenshot: { mode: "dom" | "display" | "off", auto: false } — display asks the browser for a real screen capture (permission prompt); screenshot.provider hands the widget your own capture (a canvas-heavy app, a native wrapper) — masking is then yours.

What a report carries: the text, the images, app version, browser/OS, viewport, locale, timezone, the current path (no query string), and the last 50 breadcrumbs (route changes, button clicks, request paths + status codes — never bodies or query strings). breadcrumbs: false turns the trail off; beforeSend lets you edit or veto every report.

Content-Security-Policy

script-src  https://desk.zehnmind.com;
connect-src https://desk-api.zehnmind.com;
img-src     blob: data: https://desk-api.zehnmind.com https://<your MinIO host>;   // the console logo is served by the desk API

API

ZehnDesk.init(config) → handle · ZehnDesk.mount(el, config) → the Support screen inside your element · ZehnDesk.preview(el, options) → the real widget over sample data, no network (docs, design reviews) · handle: open(view | { ticketId }), close(), toggle(), identify(jwt | { jwt }), logout(), setContext({...}), addBreadcrumb(name, data), setLocale(l), setTheme({ mode, primary, font, radius, tokens, zIndex }), on(event, fn) (ready · open · close · unread · ticket:created · message:received · deflected · appearance · error), unreadCount, state, destroy(). The script-tag build keeps window.ZehnDesk callable after it loads: ZehnDesk("open"), ZehnDesk("identify", jwt).

Keyboard: Escape closes the panel and returns focus to where it came from; the floating panel and the drawer are non-modal, the phone full-screen panel traps focus. Enter sends a reply on a keyboard (Shift+Enter for a new line); on a phone, Enter is a new line.

Development

npm i
npm run dev            # demo host app on :3011 against http://localhost:8011 (VITE_APP_KEY in demo/.env.local)
                       # /demo/gallery.html: every screen over sample data, no backend needed
npm run check          # typecheck · lint · locales · tests · build · size-limit

src/core/i18n/uz-Cyrl.ts is generated from uz.ts (npm run gen:uz-cyrl); never edit it by hand. Releases: conventional commits → release-please PR → tag → npm publish --provenance (CI). Pre-1.0 a feat: bumps the patch (bump-patch-for-minor-pre-major): a minor release needs a Release-As: X.Y.0 footer on its commit.