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

@askly/widget

v2.4.0

Published

Embeddable AI support chat widget for React — drop-in customer support chatbot with voice, RAG (docs-grounded) answers, and human handoff. One line to install.

Readme

Askly — Embeddable AI Support Chat Widget for React

npm version downloads types license

Askly is an embeddable AI support chat widget for React and plain HTML. Drop it into any site to give customers an AI-powered support chatbot with voice conversations, RAG answers grounded in your own docs, and one-click handoff to a human agent — installed with a single line.

🌐 Website & live demo → askly.co.in

Features

  • 🤖 AI-powered answers — resolves customer questions automatically from your knowledge base.
  • 📚 RAG (docs-grounded) — replies grounded in your own documentation, not hallucinated.
  • 🎙️ Voice chat — customers can talk to the assistant, not just type.
  • 🙋 Human handoff — one-click escalation to a live agent when needed.
  • ⚛️ React + CDN — use as an npm module, or a single <script> tag on any site (no React required).
  • 🎨 Fully themeable — colors, logo, position, and copy configured in the portal.

Installation

Script tag (any website)

One line, before </body>. The bundle is self-contained — no React or other dependencies on your page:

<script src="https://unpkg.com/@askly/widget@latest/dist/widget.js" data-app-id="YOUR_APP_ID" async></script>

That's the whole install. Your branding — name, logo, theme, welcome message — loads automatically from your Askly portal settings.

NPM (React / bundlers)

npm install @askly/widget
import Askly from '@askly/widget';

Askly.init({ appId: "YOUR_APP_ID" });

The npm builds (index.esm.js / index.umd.js) treat React as a peer dependency, so your bundle isn't double-shipping it.

Quick Start

All you need is your appId — copy it from Widget → Install Code in your Askly tenant portal. Everything else (name, theme, logo, welcome message, voice, etc.) is configured there and loaded automatically; the init props below are optional overrides.

The widget talks to Askly's hosted backend automatically — there is nothing else to configure.

Configuration

The Askly.init() method accepts an object with the following configuration options:

| Option | Type | Required | Default | Description | | :--- | :--- | :--- | :--- | :--- | | appId | string | Yes | - | Your app ID, from the tenant portal's Widget → Install Code screen. Script tag: data-app-id. |

widgetId and orgId are still accepted as deprecated aliases for appId, so existing embeds keep working.

Configured in the portal

The widget's appearance and behaviour — display name, logo, theme color, welcome message, position, voice, sounds, timestamps, "powered by" text, and so on — are set on the Widget screen in your Askly portal and loaded automatically by appId. Logos are uploaded right there and apply instantly. You do not pass these in code. (They remain accepted as optional init() overrides for advanced cases, but the portal is the source of truth.)

Identifying an authenticated user

Anonymous visitors are tracked automatically — the widget mints a visitor_id per browser, so someone who returns tomorrow is recognised as the same person and their history follows them. You don't configure anything for that.

Call Askly.identify() once your user logs in. Everything they did anonymously on this browser is merged into their profile, so your agents see one person with one history instead of a stranger every session.

Askly.identify({
  userId: "usr_8812",
  email: "[email protected]",
  name: "Priya Raman",
  hash: "<HMAC — see below>",
  timestamp: 1735689600,
  attributes: { plan: "pro", signupDate: "2024-03-11" },
});

| Option | Type | Description | | :--- | :--- | :--- | | userId | string | Required. Your application's user ID. | | hash | string | HMAC-SHA256 of userId + timestamp, using your Widget Secret Key. | | timestamp | number | Unix seconds the hash was generated for. Valid for 5 minutes. | | email / name / phone | string | Optional profile fields shown to your agents. | | attributes | object | Any extra traits you want on the contact record. |

Generate hash on your backend. Your Widget Secret Key must never reach browser JS — anyone holding it could impersonate any of your users and read their conversations.

// Node.js, on your server
const crypto = require("crypto");
const timestamp = Math.floor(Date.now() / 1000);
const hash = crypto
  .createHmac("sha256", process.env.ASKLY_WIDGET_SECRET)
  .update(`${userId}${timestamp}`)
  .digest("hex");

Without a valid hash the traits are still used locally, but Askly will not link the accounts.

Logging out

Askly.reset();     // forget this visitor and clear local chat history
Askly.shutdown();  // the same, and remove the widget from the page

Call one of these on logout, particularly for shared computers — otherwise the next person to use the browser inherits the previous person's conversations.

Identifying at init time

If the user is already logged in when the page loads, you can pass the same values to init() instead of calling identify() separately:

| Option | Type | Description | | :--- | :--- | :--- | | userId | string | Authenticated user ID. | | timestamp | number | Signature timestamp. | | signature | string | HMAC request signature (same value as hash above). |

Asking visitors for their email

Anonymous visitors can't be replied to once they close the tab — the answer just waits in a widget they may never reopen. So the widget can ask for an email, and Askly delivers the reply there instead when they've gone.

Configured on the Widget screen in your portal, not in code:

| Mode | Behaviour | | :--- | :--- | | When it matters (default) | A dismissible prompt after the assistant replies, and a required one only once a conversation is waiting on your team — the point where email is the only way to reach them. Barely affects how many people start a chat. | | Before every chat | Nobody can send a first message without an email. Captures the most addresses, at a cost of roughly 30% fewer conversations — including ones the assistant would have resolved on its own. | | Never | Visitors are never asked. |

Anyone whose address you already have — from identify(), a previous chat, or because they typed it — is never asked again.

An address given this way is not verified: it proves the person wants replies there, not who they are. It is never used to match them to an existing contact, so nobody can read someone else's conversation history by typing their address. Only addresses given through this prompt are ever emailed; one merely spotted in the text of a message is not consent to write to them.

Restricting the widget to certain pages

Control where the widget appears using URL path prefixes (matched against window.location.pathname).

| Option | Type | Description | | :--- | :--- | :--- | | includePaths | string[] | Allowlist — the widget renders only on paths matching one of these prefixes. | | excludePaths | string[] | Blocklist — the widget renders everywhere except paths matching one of these prefixes. |

// Show ONLY on support & docs pages
Askly.init({ appId: "YOUR_APP_ID", includePaths: ["/support", "/docs"] });

// Show everywhere EXCEPT admin & checkout
Askly.init({ appId: "YOUR_APP_ID", excludePaths: ["/admin", "/checkout"] });

Via script tag, use comma-separated values: data-include-paths="/support,/docs" or data-exclude-paths="/admin,/checkout".

Notes:

  • includePaths wins: if it's set, excludePaths is ignored.
  • Prefix match, not exact — "/docs" also matches /docs/getting-started. No glob/regex.
  • Optimized: on non-matching pages the path check runs before anything mounts, so there's no DOM node, no React root, and no config network request — the SDK does essentially nothing.
  • The decision is evaluated once at init; it's a UX targeting tool, not a security boundary (the script can still be loaded on any page).

Lifecycle Callbacks

Callbacks are functions, so they can only be attached in code (not from the portal):

Askly.init({
  appId: "YOUR_APP_ID",
  onMessageSent: (message) => console.log("Sent:", message),
  onMessageReceived: (reply) => console.log("Received:", reply),
  onChatOpened: () => console.log("Opened"),
  onChatClosed: () => console.log("Closed"),
  onEscalation: (data) => console.log("Escalated to agent:", data)
});

Backend Integration

None required. The widget talks to Askly's hosted backend automatically — you only provide an appId.

Development

# Install dependencies
npm install

# Compile production bundles
npm run build

Releasing

npm version <patch|minor|major>
npm publish            # prepublishOnly rebuilds dist/

Publishing is not the last step. The tenant portal bundles this package (AsklyWidget.tsx and the help centre both import("@askly/widget")), and its Docker build runs npm ci — which installs whatever package-lock.json pins and ignores the ^ range entirely. So a new version does not reach Askly's own site until the portal's lockfile is bumped:

cd ../tenant-portal && npm install @askly/widget@latest   # updates package-lock.json

Skipping this is how 2.2.0 shipped to npm while the portal kept serving 2.1.0 for a day — visitors were unrecognisable there, and every message showed as a separate person in the Inbox.

Why Askly?

Is this an AI customer support widget I can embed in React? Yes — Askly is an embeddable AI support chat widget that installs into any React app or plain HTML page with a single line, no backend to build.

How does the AI answer questions accurately? Askly uses RAG (retrieval-augmented generation) to ground every reply in your own documentation, so answers stay accurate and on-brand instead of hallucinated.

Can customers reach a human? Yes — the widget supports one-click escalation to a live agent via the onEscalation callback and built-in human handoff.

License

MIT