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

@skippr/live-agent-sdk

v0.92.0

Published

[![Website](https://img.shields.io/badge/Website-skippr.ai-blue)](https://skippr.ai) [![NPM Version](https://img.shields.io/npm/v/%40skippr%2Flive-agent-sdk?color=red)](https://www.npmjs.com/package/@skippr/live-agent-sdk)

Downloads

5,956

Readme

@skippr/live-agent-sdk

Website NPM Version

Embed product specialists that see, speak, and act in real time. Configure modules — for onboarding, demos, training, support, and anything else — and let your users pick which one to talk to from a single embed.

Key Features

  • Multi-module picker — the widget fetches your active modules and lets the user pick which one to start (onboarding, demo, support, etc.), so a single embed covers every use case
  • Real-time voice — two-way audio with live transcription
  • Capture modesscreenshare (user shares screen) or auto (DOM capture)
  • Agent controls — opt-in capabilities like element highlighting and on-screen actions, configured per module (auto mode only)
  • Bring your own button — start, pause, and end sessions from your own UI with the useLiveAgent hook (see Bring your own button)
  • Chat + transcript — text messaging with voice transcripts merged into one thread
  • Session agenda — structured phases with progress tracking
  • Flexible auth — email OTP (direct auth) or backend-signed JWT (secret mode)
  • Drop-in integration — one React component or script tag, no WebRTC code needed
  • Host-safe styles — prefixed CSS that won't conflict with your app

Prerequisites

You need an appKey from the Skippr Platform.

  1. Sign up at specialist.skippr.ai
  2. Create your modules in the dashboard — for example an onboarding agent to walk new users through setup and a support module for help. Every active module shows up in the widget's picker automatically.
  3. Create an App Key in Settings and copy the appKey — one App Key works for all your modules

Optionally, you can pin the widget to a single module by passing its id as agentId (see Pinning below). Without it, the user picks from all active modules at runtime.

Installation

React

npm install @skippr/live-agent-sdk

Script Tag

No install needed. Add this to any webpage:

<script src="https://unpkg.com/@skippr/live-agent-sdk/dist/skippr-sdk.js"></script>
<script>
  Skippr.initialize({
    appKey: 'pk_live_your_key',
  });
</script>

Quick Start

The widget opens with a picker of your active modules — the user chooses which one to start a session with.

React

import { LiveAgent } from '@skippr/live-agent-sdk';
import '@skippr/live-agent-sdk/styles';

function App() {
  return <LiveAgent appKey="pk_live_your_key" />;
}

Script Tag

<script src="https://unpkg.com/@skippr/live-agent-sdk/dist/skippr-sdk.js"></script>
<script>
  Skippr.initialize({
    appKey: 'pk_live_your_key',
  });
</script>

Pinning to a specific module

If you want to skip the picker and always open straight into one module, pass its id as agentId:

<LiveAgent appKey="pk_live_your_key" agentId="your_module_id" />

Authentication

The SDK supports two identity modes, configured per App Key in the Skippr dashboard.

Direct Auth (default)

Users log in via email OTP inside the widget. No backend integration needed. The SDK handles the full OTP flow: email input, code verification, and token persistence automatically.

Best suited for development and testing. Direct Auth persists tokens in the browser and has no automatic token refresh, so it works in production but isn't what we recommend there. For production, we suggest Secret Mode — your backend vouches for the user and controls token lifetime.

React:

<LiveAgent appKey="pk_live_your_key" />

Script tag:

Skippr.initialize({
  appKey: 'pk_live_your_key',
});

Secret Mode

Your backend signs a short-lived JWT with the App Key's identity secret. You give the SDK a getUserToken callback that returns a freshly signed JWT each time it's called; the SDK exchanges it for a bearer token server-side and skips the login form entirely.

Because it's a callback (not a static token), the SDK can keep the session alive without a page reload: it invokes getUserToken to bootstrap, again proactively shortly before the current bearer expires, and once more if a request comes back 401. Your callback should always return a current JWT from your source of truth — typically by re-minting it on your backend per call.

The bearer's lifetime is the session token lifetime you set on the App Key in the dashboard (15 minutes to 24 hours, default 1 hour).

Step 1: Generate a signed JWT on your backend

The JWT must be signed with HS256 using the identity secret you received when creating the App Key.

| Claim | Type | Required | Description | |-------|------|----------|-------------| | sub | string | Yes | Unique user identifier in your system | | name | string | No | User's display name | | email | string | No | User's email address | | exp | number | Recommended | Expiration timestamp (Unix seconds) |

Node.js example:

import jwt from 'jsonwebtoken';

const userToken = jwt.sign(
  {
    sub: user.id,
    name: user.name,
    email: user.email,
  },
  process.env.SKIPPR_IDENTITY_SECRET,
  { algorithm: 'HS256', expiresIn: '1h' },
);

Step 2: Give the SDK a callback that fetches a fresh token

React:

<LiveAgent
  appKey="pk_live_your_key"
  getUserToken={() => fetch('/api/skippr-token').then((r) => r.text())}
/>

Script tag:

Skippr.initialize({
  appKey: 'pk_live_your_key',
  getUserToken: () => fetch('/api/skippr-token').then((r) => r.text()),
});

The callback runs whenever the SDK needs a token, so each call should return a newly minted JWT (e.g. from the /api/skippr-token endpoint on your backend that signs the JWT shown in Step 1).

Static token (testing)

If you already hold a signed JWT and just want to drop it in, pass it as a static userToken string instead of a callback. The SDK exchanges it for a bearer token the same way.

<LiveAgent appKey="pk_live_your_key" userToken={signedJwt} />

Best suited for quick tests. A static token can't be re-minted, so once it expires the session cannot refresh — for production use the getUserToken callback above. When both are provided, getUserToken wins.

Custom Components

Any component rendered inside <LiveAgent> can use the useLiveAgent hook to access session state and controls:

import { LiveAgent, useLiveAgent } from '@skippr/live-agent-sdk';

function ConnectionStatus() {
  const { isConnected } = useLiveAgent();

  if (isConnected) return <p>Agent connected</p>;
  return <p>Agent disconnected</p>;
}

function App() {
  return (
    <LiveAgent appKey="pk_live_your_key">
      <ConnectionStatus />
    </LiveAgent>
  );
}

Bring your own button

You can bring your own widget or button and start, pause, and end sessions from it — no need to rely on the built-in launcher:

import { LiveAgent, useLiveAgent } from '@skippr/live-agent-sdk';

function TalkToExpertButton() {
  const { isConnected, isStarting, displayModules, selectModule, disconnect } = useLiveAgent();

  if (isConnected) {
    return <button onClick={() => disconnect()}>End session</button>;
  }

  const module = displayModules[0];
  return (
    <button disabled={!module || isStarting} onClick={() => module && selectModule(module.id)}>
      {isStarting ? 'Connecting…' : 'Talk to an expert'}
    </button>
  );
}

function App() {
  return (
    <LiveAgent appKey="pk_live_your_key">
      <TalkToExpertButton />
    </LiveAgent>
  );
}
  • Start — call selectModule(id) with your agent's id (the same id you'd pass as agentId). Running multiple agents? Pick one from displayModules. If the user has a paused session with that agent, it resumes automatically.
  • Close — call disconnect() to end the current session.
  • Show the right label — use isConnected and isStarting to switch your button between start, connecting, and end states.

API Reference

<LiveAgent>

Self-contained widget component. Renders a floating button that opens a sidebar panel for real-time agent interaction.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | appKey | string | required | Publishable App Key from the Skippr dashboard | | agentId | string | — | Pin the widget to a specific module (pass that module's id). Omit to let the user pick from all your active modules at runtime. | | getUserToken | () => Promise<string> | — | Async callback returning a freshly signed JWT for secret mode. The SDK calls it to bootstrap, again proactively before the bearer expires, and once more on a 401 to re-bootstrap. | | userToken | string | — | Static signed JWT for secret mode (testing). Exchanged for a bearer token but can't be refreshed once expired. getUserToken takes precedence when both are set. | | userContext | Record<string, string \| number \| boolean> | — | Background facts about the current user so the agent can tailor the conversation (e.g. { name: 'Alex', plan: 'pro', signedUpDaysAgo: 3, hasCompletedSetup: false }). Must be a flat object of strings, numbers, or booleans - nested objects and arrays are not supported. | | variant | 'floating' \| 'sidebar' | 'floating' | Widget display mode | | minimizable | boolean | true | Whether the widget can be minimized | | defaultOpen | boolean | false | Whether the panel starts open | | welcomeMessage | string | — | Message shown on the minimized bubble | | startSessionLabel | string | 'Talk to Skippr' | Label for the start-session button (only shown when agentId is pinned) | | autoFocusChat | boolean | true | Whether the chat input auto-focuses when opened | | captureMode | 'screenshare' \| 'auto' | 'auto' | How the agent sees the page — 'auto' uses DOM capture (no permission prompt), 'screenshare' prompts the user to share | | animateAgentCursor | boolean | false | Animate the agent's cursor as it points to elements on the page. Primarily configured per agent in the dashboard; this prop is a fallback when the agent has no preference set. |

useLiveAgent()

Hook for accessing session state and panel controls. Must be called within <LiveAgent>.

State

| Field | Type | Description | |-------|------|-------------| | isConnected | boolean | Whether the agent is connected | | isStarting | boolean | Whether a session is being created or resumed | | isDisconnecting | boolean | Whether the session is being torn down | | isPausing | boolean | Whether a pause request is in flight (before isPaused commits) | | isPaused | boolean | Whether the current session is paused | | resumableSession | { id: string; agentId: string } \| null | The operating module's paused session, or null | | isPanelOpen | boolean | Whether the panel is currently open | | isMinimized | boolean | Whether the widget is minimized | | isAuthenticated | boolean | Whether the user is authenticated | | variant | 'floating' \| 'sidebar' | Current widget display mode | | position | 'left' \| 'right' | Current widget position | | error | string | Error message, if any | | displayModules | Module[] | The user's active modules, for pickers and custom start buttons | | activeModule | Module \| null | The module the current session was started with | | isLoadingModules | boolean | Whether the picker list is being fetched | | modulesError | string \| null | Error from fetching the picker list, if any |

interface Module {
  id: string;
  name: string;
  description: string | null;
  type: string;
  priority: number;
  controls: { highlight?: boolean; actions?: boolean };
}

Methods

| Method | Type | Description | |--------|------|-------------| | startSession | (opts: { agentId: string; agentControls?: { highlight?: boolean; actions?: boolean } }) => Promise<void> | Start a new agent session | | pauseSession | () => Promise<void> | Pause the active session and disconnect from LiveKit; resumable later | | resumeSession | () => Promise<void> | Resume the pinned agent's paused session, reconnecting to continue | | disconnect | () => Promise<void> | End the current session | | openPanel | () => void | Open the panel | | closePanel | () => void | Close the panel | | togglePanel | () => void | Toggle the panel open/closed | | expandPanel | () => void | Expand from minimized state | | minimizePanel | () => void | Minimize the widget | | setPosition | (position: 'left' \| 'right') => void | Change widget position | | selectModule | (id: string) => void | Start a session with the module of the given id | | refetchModules | () => Promise<void> | Refetch the picker list |

Additional Hooks

All hooks must be called within <LiveAgent>.

| Hook | Returns | Description | |------|---------|-------------| | useMediaControls() | { isMuted, isScreenSharing, toggleMute, toggleScreenShare } | Mic and screen share state and toggles | | useAgentVoiceState() | { state, isSpeaking, isListening } | Agent voice activity state — state is the full agent state ('listening' \| 'thinking' \| 'speaking' \| ...) | | useIsLocalSpeaking() | boolean | Whether the local user is currently speaking | | useIsSessionHeld() | boolean | true while the user has paused the live session; false when active or when there is no session. | | useElapsedSeconds(isRunning) | number | Drift-safe elapsed seconds since the flag flipped to true |

Utilities

| Export | Signature | Description | |--------|-----------|-------------| | formatTime | (seconds: number) => string | Format seconds as mm:ss |

Global API (Script Tag)

Available on window.Skippr when using the script tag bundle.

| Method | Description | |--------|-------------| | Skippr.initialize(config) | Mount the widget. Accepts appKey (required), agentId (optional — omit for picker), getUserToken, userToken, variant, minimizable, captureMode. | | Skippr.logout() | Revoke the current session server-side, clear stored auth tokens, and show the login form (direct auth mode) | | Skippr.destroy() | Remove the widget from the page and clear auth tokens |

On-screen actions

When an agent has the actions control enabled (auto mode only), it can act on the page for the user, not just guide them. A small on-screen indicator shows each action as it happens.

Actions are guardrailed: the SDK refuses to act on masked fields (passwords, secrets), sensitive iframes (Stripe, Plaid, captchas), or disabled controls. Use data-skippr-private (below) to put anything else off limits.

Hiding elements from the agent

To hide an element (and everything inside it) from the agent in captureMode: 'auto', add the data-skippr-private attribute:

<div data-skippr-private>
  <SensitiveContent />
</div>
<section data-skippr-private>
  <input type="password" />
</section>

Use this for sensitive content or anything you don't want the agent to see or reference.

Support

For questions, technical support, or feedback:


© 2026 Skippr