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

@three-ws/concierge

v0.1.1

Published

An AI concierge for any website: a floating chat widget with a rigged 3D avatar that blinks and lipsyncs, streaming answers grounded in your live page, browser-native voice in/out, and a one-tag embed. Powered by three.ws.

Downloads

239

Readme

@three-ws/concierge

An AI concierge for any website, with a face.

One tag adds a floating chat widget where a rigged 3D avatar blinks, idles, and lipsyncs while it answers visitors. Answers stream in over SSE, are grounded in the live page (no crawler, no vector DB, no setup), and speak aloud with browser-native TTS. Push-to-talk voice input included where the browser supports it.

Live demo: three.ws/concierge (the widget on that page is this package answering questions about itself).

Install

1. One tag (no build, no account)

<script src="https://three.ws/concierge/concierge.global.js"
        data-concierge
        data-site-name="Acme"
        data-accent="#f97316"
        data-suggestions="What is Acme?|What does it cost?"></script>

Also served from npm CDNs after install: https://unpkg.com/@three-ws/concierge/dist/concierge.global.js.

2. npm

npm install @three-ws/concierge three

three is a peer dependency for the module build; the .global.js build inlines it.

3. Web component

<script type="module">import '@three-ws/concierge';</script>

<three-concierge
    site-name="Acme"
    accent="#f97316"
    knowledge="Pro plan is $20/month. Support: [email protected]."
    suggestions="What is Acme?|What does it cost?">
</three-concierge>

4. Imperative

import { Concierge } from '@three-ws/concierge';

const concierge = new Concierge({
	siteName: 'Acme',
	accent: '#f97316',
	knowledge: FAQ_TEXT,
	persona: 'warm, playful, concise',
});

concierge.on('message', ({ role, content }) => analytics.track('concierge', { role }));
await concierge.ask('What does the Pro plan cost?');

How answers stay accurate

There is no crawler and no index to keep fresh. At ask-time the widget snapshots the live DOM (title, meta description, headings, nav labels, main content, all capped) and merges in your curated knowledge string. The backend builds a grounded system prompt from that snapshot and instructs the model to refuse to invent anything it cannot see. Whatever your page says today is what the concierge knows today.

Add data-concierge-ignore to any element you never want harvested.

Shopping mode (Shopify)

On a Shopify store the concierge becomes a full shopping assistant: it reads your live catalog and helps visitors find the right product, compare options, and check shipping and returns, then shows real product cards (image, live price, link, add-to-cart) for what it recommends.

There is still no crawler, no index, and no product feed to maintain. Shopify serves every storefront's catalog and policies as public endpoints, and the widget reads them at ask-time:

  • GET /products.json — the live catalog (variants, prices, images, tags, type)
  • GET /collections.json — the collections
  • GET /policies/shipping-policy, /policies/refund-policy, … — shipping/returns/privacy/terms

It fetches these once (same-origin on the store, so no CORS wall), caches them for the session, then for each question runs a small keyword retrieval to pick the handful of products the shopper actually asked about. Only that handful plus a compact store summary is sent to the answer endpoint, and the cards are rendered from that same set, so prices and links are always real, never model-invented. No embeddings service, no vector DB.

Install is the same one tag, dropped into theme.liquid before </body>:

<script src="https://three.ws/concierge/concierge.global.js"
        data-concierge
        data-site-name="Larkspur Supply"
        data-avatar="nova"
        data-accent="#3f7d5b"></script>

Shopping mode turns on automatically because the widget detects the Shopify storefront (window.Shopify). Force it, target a specific store, or set the currency with data-shopping="true", data-shop="your-store.myshopify.com", and data-currency="GBP".

Add-to-cart works when the widget runs on the store itself: the button posts to Shopify's public /cart/add.js and fires a cart:refresh event so themes update their cart count. Off-store, cards show a View link to the product page.

Handles price intent too ("a gift under $75", "cheapest hoodie", "anything on sale?") and grounds shipping/returns answers in your published policies. See examples/shopify.html and the tutorial: three.ws/docs/tutorials/shopify-shopping-assistant.

Options

Constructor config / element attributes / data-* script attributes are the same set:

| Config (camelCase) | Attribute (kebab-case) | Default | What it does | | --- | --- | --- | --- | | endpoint | endpoint | https://three.ws/api/concierge | Answer API. Any server speaking the same wire format works. | | avatar | avatar | visitor's saved pick, else sol | Initial catalog avatar: sol, nova, vera, atlas, echo. | | avatars | avatars | all | Comma-separated allow-list for the picker. | | customAvatar | custom-avatar | none | URL of your own rigged GLB (replaces the catalog + picker). | | assetBase | asset-base | https://three.ws/avatars/ | Self-host the catalog GLBs. | | name | name | avatar's name | Display name in the header. | | siteName | site-name | og:site_name else hostname | Used in the greeting and grounding. | | greeting | greeting | generated | Empty-state + teaser line. | | suggestions | suggestions | generated | Prompt chips. Pipe-separated in attributes, max 4. | | knowledge | knowledge | none | Curated facts (FAQ, policies, pricing). Leads the grounding. | | shop | shop | auto-detected | Shopify store domain. Turns on shopping mode (catalog + product cards). | | shopping | shopping | auto on a store | Force shopping mode on (true) or off (false). | | currency | currency | store's / USD | ISO code for product prices. | | maxProducts | max-products | 4 | Product cards recommended per answer (1–8). | | persona | persona | none | One-line tone instruction for the model. | | accent | accent | indigo | Any CSS color; restyles the whole widget. | | position | position | bottom-right | Or bottom-left. | | theme | theme | auto | auto follows prefers-color-scheme; or pin dark / light. | | open | open | false | Start with the panel open. | | muted | muted | false | Start with voice off (remembered per visitor). | | picker | no-picker | on | Avatar picker. | | teaser | no-teaser | on | Proactive greeting bubble (once per session, dismissable). | | zIndex | z-index | 2147482800 | Stacking override. | | lang | lang | browser language | BCP-47 hint for voice in/out and replies. |

API

const c = new Concierge(config);
c.ask(text)            // Promise<string>: renders, streams, speaks; resolves to the answer
c.setOpen(true|false)  // open/close the panel
c.toggle()
c.setAvatar('nova')    // hot-swap the rig
c.setMuted(true)
c.reset()              // clear the conversation
c.dispose()
c.on(event, fn)        // 'ready' | 'open' | 'close' | 'message' | 'agentchange'
                       // | 'catalog' (store catalog loaded) | 'addtocart' | 'error'

<three-concierge> proxies the same methods and re-dispatches events as DOM CustomEvents (three-concierge:message etc., bubbling + composed).

Lower-level building blocks are exported too: AvatarStage (the 3D bust renderer), SpeechNarrator (TTS + viseme sync), createLipsync / buildMorphMap, harvestSiteContext / buildSitePayload, askConcierge (the SSE client), renderMarkdown / stripMarkdown, and createMic.

The wire format (bring your own backend)

The default endpoint is free and anonymous, hosted by three.ws. To run your own, accept this POST and stream this SSE:

→ POST { message, history: [{role, content}], site: {url, name, title,
         description, headings[], nav[], knowledge, content}, persona?, lang? }

← data: { "type": "chunk", "text": "..." }     (repeated)
← data: { "type": "done", "provider": "...", "model": "..." }
← data: { "type": "error", "code": "...", "message": "..." }

Point the widget at it with endpoint / data-endpoint. The reference implementation is api/concierge.js in the three.ws repo.

Behavior details worth knowing

  • Zero cost while closed. The WebGL stage, GLB download, and speech engines initialize on first open, not on page load.
  • Safe to import on a server. import '@three-ws/concierge' evaluates without a DOM, so Next / Nuxt / SvelteKit / Astro can import it at module scope: the element class and the auto-init are both browser-gated, and the widget only mounts once it runs in a page.
  • Speaks while streaming. Completed sentences are handed to the speech engine as they arrive; the mouth morphs are driven by a text-to-viseme timeline synced to the utterance.
  • Degrades deliberately. No WebGL → the avatar stage hides, chat keeps working. No SpeechRecognition → the mic button never renders. TTS missing or muted → captions + lipsync still play. Endpoint down → a friendly error bubble with a working retry.
  • Persists sensibly. Conversation per tab session (sessionStorage), avatar choice + mute per visitor (localStorage).
  • Accessible. Dialog semantics, focus management, aria-live streaming region, Escape to close, full keyboard path, prefers-reduced-motion respected.
  • Safe rendering. Model output is parsed with marked (CommonMark + GFM) and then sanitized with DOMPurify under an allowlist that permits only text-level markup. Media, forms, styles, and event handlers cannot survive the pass; link hrefs are restricted to http(s):/mailto:/relative and hardened with rel="noopener noreferrer".

Develop

npm install        # in this directory, or use the monorepo root
npm test           # node --test, no browser needed
npm run build      # dist/concierge.mjs + dist/concierge.global.js + dist/concierge.css

License

Proprietary. See LICENSE. Free to embed on any site via the published builds.