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

@sales-bot-llm/sdk

v0.2.3

Published

Frontend SDK for the Sales Bot chat API — framework-agnostic core with React, Vue, and vanilla adapters

Readme

@sales-bot-llm/sdk

Frontend SDK for the Sales Bot chat API. A framework-agnostic core (~8 KB) plus thin adapters for React, Vue, a drop-in floating widget, and a plain <script> tag.

Sub-project #2 of 5 in the Sales Bot platform. Consumes the SSE wire format defined by sub-project #1 (sales_bot/).

What you get

| Entry point | Use when | | --- | --- | | @sales-bot-llm/sdk/core | Custom UI, Node integration, or any framework not covered below. Returns a SalesBotClient with an async-iterable ask(). | | @sales-bot-llm/sdk/react | React 18+ apps. useSalesBot() hook returns { ask, messages, isStreaming, error, conversationId, visitorToken, reset }. | | @sales-bot-llm/sdk/vue | Vue 3.4+ apps. Same shape as React, but state is exposed as Ref<…>. | | @sales-bot-llm/sdk/widget | Drop-in floating launcher + chat panel rendered into a shadow DOM. createWidget({ embedKey, theme }).mount(). | | @sales-bot-llm/sdk/vanilla | Plain HTML page. Load dist/vanilla.global.js via <script>window.SalesBot.default.widget({ … }). |

All entry points talk to the same SalesBotClient and consume the same SSE event stream.

Install

pnpm add @sales-bot-llm/sdk
# or: npm install @sales-bot-llm/sdk / yarn add @sales-bot-llm/sdk

React and Vue are optional peer deps — only install the one(s) you use.

Quickstart

Core (framework-agnostic)

import { SalesBotClient } from '@sales-bot-llm/sdk/core'

const client = new SalesBotClient({
  embedKey: 'pk_live_...',           // from the Bot config in the admin UI
  baseUrl: 'https://api.example.com', // your sales_bot deployment
})

for await (const event of client.ask('What does your trial include?')) {
  if (event.event === 'delta') {
    process.stdout.write(event.data.content)
  } else if (event.event === 'done') {
    break
  }
}

client.ask() is an async iterable over the typed SSE union (turn_started, delta, tool_call_started, tool_call_finished, message_complete, usage, done, error). Each event type maps to a strongly-typed payload — see src/core/types.ts.

The client also offers:

  • client.identify({ externalId, email, name, traits }) — merges into the next ask() call.
  • client.on(eventName, handler) — event-bus subscription, returns an unsubscribe.
  • client.getConversationId() / setConversationId(id | null) — persisted in storage so reloads continue the same conversation.
  • client.loadHistory() — fetch the last 20 messages of the current conversation.
  • client.endConversation() — close the conversation server-side and clear local state.
  • client.getBotConfig() — public bot config (title, greeting, etc.), cached for the client's lifetime.

React

import { useSalesBot } from '@sales-bot-llm/sdk/react'

function Chat() {
  const { ask, messages, isStreaming, error } = useSalesBot({
    embedKey: 'pk_live_...',
    baseUrl: 'https://api.example.com',
  })

  return (
    <>
      {messages.map(m => (
        <div key={m.id} data-role={m.role}>{m.content}</div>
      ))}
      <button onClick={() => ask('Show me the pricing')} disabled={isStreaming}>
        Ask
      </button>
      {error && <p role="alert">{error.message}</p>}
    </>
  )
}

Vue

<script setup lang="ts">
import { useSalesBot } from '@sales-bot-llm/sdk/vue'

const { ask, messages, isStreaming, error } = useSalesBot({
  embedKey: 'pk_live_...',
  baseUrl: 'https://api.example.com',
})
</script>

<template>
  <div v-for="m in messages" :key="m.id" :data-role="m.role">{{ m.content }}</div>
  <button @click="ask('Show me the pricing')" :disabled="isStreaming">Ask</button>
  <p v-if="error" role="alert">{{ error.message }}</p>
</template>

Floating widget

import { createWidget } from '@sales-bot-llm/sdk/widget'

createWidget({
  embedKey: 'pk_live_...',
  baseUrl: 'https://api.example.com',
  title: 'Ask us anything',
  position: 'bottom-right',
  theme: {
    primary: '#3b82f6',
    radius: '16px',
    fontFamily: 'Inter, system-ui, sans-serif',
  },
}).mount()

The widget renders into a shadow root, so it can't be styled (or broken) by host-page CSS. Use the typed theme for common knobs and customCss for anything else. Every theme key maps to a --sb-<kebab-case> CSS variable on the shadow host — see src/core/types.ts for the full list.

Vanilla <script> tag

<script src="https://your-cdn/sales-bot/vanilla.global.js"></script>
<script>
  window.SalesBot.default.widget({
    embedKey: 'pk_live_...',
    baseUrl: 'https://api.example.com',
  })
</script>

Note the .default.widget indirection — the IIFE bundle exposes the module's exports wrapper, not the module itself. window.SalesBot.default.widget(...) is the recommended path; window.SalesBot.SalesBot.widget(...) also works.

Configuration

All entry points accept the same SalesBotClientOptions:

| Option | Required | Default | Purpose | | --- | --- | --- | --- | | embedKey | yes | — | Public bot key, pk_live_…. Created in the admin UI. | | baseUrl | no | http://localhost:3000 | Sales Bot backend URL. | | storage | no | localStorage with MemoryStorage fallback | Custom StorageAdapter for the visitor token + conversation id. | | customHeaders | no | — | Extra headers merged into every request. Useful in Node where the browser-style Origin header must be set manually. |

Widget-only extras (see src/core/types.ts for the full WidgetOptions shape):

  • container?: HTMLElement — mount target (default: document.body)
  • position?: 'bottom-right' | 'bottom-left'
  • title?, placeholder?
  • theme?: WidgetTheme — typed CSS-variable overrides (colors, sizing, fonts, effects)
  • customCss?: string — raw CSS appended inside the shadow root

Errors

Every error surfaces as a SalesBotError with a typed code you can switch on:

import { SalesBotError } from '@sales-bot-llm/sdk/core'

try {
  for await (const ev of client.ask('hi')) { /* … */ }
} catch (e) {
  if (e instanceof SalesBotError) {
    switch (e.code) {
      case 'origin_not_allowed': /* domain not verified for this Bot */
      case 'rate_limited':       /* back off */
      case 'out_of_credits':     /* show top-up CTA */
      case 'network_error':      /* retry if e.retryable */
      // …
    }
  }
}

Backend codes mirror the platform error catalog; SDK-only codes are network_error and parse_error.

Origin header — the most common gotcha

The backend validates Origin on every chat request. Browsers send it automatically, but the Bot's Resource must list your origin as a verified domain (e.g. localhost, or your production host). A 403 origin_not_allowed almost always means the Resource isn't configured for the host that's calling the SDK.

In Node (no browser, no automatic Origin), pass customHeaders: { Origin: 'https://yourapp.example' }.

Repo layout

sales_bot_sdk/
├── src/
│   ├── core/       framework-agnostic client, types, transport, SSE parser, storage
│   ├── react/      useSalesBot hook
│   ├── vue/        useSalesBot composable
│   ├── widget/     createWidget — shadow-DOM floating panel
│   └── vanilla/    IIFE entry, exposes window.SalesBot
├── tests/          vitest specs incl. SSE contract, framework adapters, widget
├── example/        Vite + React playground (see example/README.md)
├── tsup.config.ts  bundling: ESM + CJS for libs, IIFE for vanilla
├── biome.json      lint + format
└── package.json    exports map driving the public surface

Development

Node 20+ and pnpm 10+.

pnpm install
pnpm build              # tsup → dist/*.{js,cjs,d.ts} + vanilla.global.js (IIFE)
pnpm dev                # tsup --watch
pnpm test               # vitest run (unit + SSE-contract + adapter tests)
pnpm test:watch
pnpm test:coverage
pnpm typecheck          # tsc --noEmit
pnpm lint               # biome check .
pnpm lint:fix
pnpm size               # size-limit budgets: core 8KB, react/vue 12KB, widget 25KB, vanilla 20KB

The tests/ tree mirrors the src/ tree, plus a contract/ folder that pins the SSE event taxonomy against the backend's sse-events.contract.spec.ts. If the contract tests fail, do not loosen them — coordinate the change with the backend team. The wire format is the integration boundary.

Run against a local backend

# In ../sales_bot/:
pnpm start:dev          # API on :3000

# In sales_bot_sdk/:
pnpm build
pnpm --filter @sales-bot/sdk-example dev   # http://localhost:5173

See example/README.md for the full set-up, including how to verify a localhost Resource and Bot in the admin UI.

SSE wire contract

The SDK is locked to the backend's SSE event taxonomy. The full list and payload shapes live in src/core/types.ts — search for SalesBotEvent. Any change here is a breaking change for both sides and must be coordinated.

Event names: turn_started, delta, tool_call_started, tool_call_finished, message_complete, usage, done, error.

Bundle outputs

pnpm build produces (in dist/):

| File | Format | Purpose | | --- | --- | --- | | core.{js,cjs,d.ts} | ESM / CJS | Framework-agnostic client | | react.{js,cjs,d.ts} | ESM / CJS | React adapter | | vue.{js,cjs,d.ts} | ESM / CJS | Vue adapter | | widget.{js,cjs,d.ts} | ESM / CJS | Floating widget | | vanilla.global.js | IIFE, minified | <script>-loadable, exposes window.SalesBot |

All ESM/CJS bundles are side-effect-free and tree-shakable.

Publishing

The package is published as @sales-bot-llm/sdk on the public npm registry under access=public.

pnpm build
pnpm test
pnpm size
npm publish              # NPM_TOKEN must be set in the environment

In CI, set NPM_TOKEN as a repo secret; npm picks it up automatically from the .npmrc directive.

License

MIT — see package.json.