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.126.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)

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
  • Seven languages, no wiring — the splash card ships translated and follows your page's <html lang> on its own (see Language)
  • 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 app.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';

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.

Splash screen

When your workspace turns the splash on under Appearance, the SDK shows a pre-session card before the session starts: a greeting, what the agent can do, the microphone and screen-access rows it needs, and a Start / Continue button. It is independent of the launcher style, so you can run both or turn the launcher off and let the card be the entry point.

There is nothing for you to place. Every embed gets the card — script tag and React alike — and your workspace also chooses where it appears: centred over the page on its own backdrop, or inside the chat panel. The SDK renders it either way and handles its own dismissal.

Language

The card is translated into English, French, German, Italian, Spanish, and Portuguese (Portugal and Brazil). A page that says <html lang="de"> renders a German card with no code change — the SDK reads <html lang>, then the browser preference. Pass locale only when <html lang> does not reflect the language on screen:

<LiveAgent appKey="pk_live_your_key" locale="pt-BR" />
  • Any BCP-47 tag works. 'de-AT' and 'pt' both resolve; a language Skippr does not ship falls back to English rather than failing.
  • You can rewrite any string, per language. Pick a card language in the splash editor under Appearance. Fields you leave alone keep Skippr's translation, so clearing one restores it rather than blanking it. That includes the default capability rows — edit a row's English and set its copy for each language too, or those languages keep the original row's translation.

Placing the card yourself

Use <SplashScreen> when you also want the card inline in your own layout — a sidebar, a grid cell, a panel, rather than centred over the page. In exchange for the placement control, you own the surrounding state:

import { useState } from 'react';
import { LiveAgent, SplashScreen } from '@skippr/live-agent-sdk';

function App() {
  const [showSplash, setShowSplash] = useState(true);

  return (
    <LiveAgent appKey="pk_live_your_key">
      {showSplash && (
        <aside className="w-[380px] p-4">
          <SplashScreen onDismiss={() => setShowSplash(false)} />
        </aside>
      )}
    </LiveAgent>
  );
}

| Prop | Type | Description | |------|------|-------------| | className | string | Applied to the component's own light-DOM element. This is the placement and sizing lever — the card fills whatever slot you give it. | | onDismiss | () => void | Called when the user dismisses the splash, via either the close button or "Skip for now". |

  • You own unmounting. <SplashScreen> renders nothing once it no longer applies — the user dismissed it, a session is running, or your workspace has the splash turned off — but it stays mounted until you remove it.
  • Conditionally render your own wrapper too. A styled slot (padding, border, grid cell) survives as an empty box when the card stops rendering, as in the example above.
  • It follows the workspace placement. <SplashScreen> renders while your workspace shows the splash over the page. When the workspace moves the splash into the chat panel, the panel renders the card and <SplashScreen> renders nothing.
  • It only shows while the widget is closed. The splash and the open widget panel never share the screen, so <SplashScreen> renders nothing once the user opens the panel and returns when they close it again.

Behaviour of both

  • Dismissal is shared. Closing or skipping either card dismisses the splash for the page load, so the SDK's own card and any <SplashScreen> you place come and go together.
  • Session start needs no callback. useLiveAgent() already reports isStarting / isConnected, so read the session from there rather than mirroring it into your own state.
  • Bring it back after a skip. Dismissal lasts for the page load, so the card returns on the next navigation. On a single-page app, call reopenSplash() from useLiveAgent() — wire it to a "Start guided tour" link or a help-menu item. Skipping never reveals a launcher that your workspace has turned off; the host owns when the card comes back.
  • Check the config before you build around it. useLiveAgent() exposes isSplashConfigured so you can pick between the splash and your own launcher; it reflects the workspace setting only, not whether the card is on screen right now.
  • The card adapts to the agent. When your workspace's default agent is an always-on expert, the card renders a tips layout instead of the guided-tour one: contextual suggestions for the current page, each starting a session on click, plus a catch-all CTA. No prop switches this — it follows the agent configuration.

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. | | locale | string | — | BCP-47 tag naming the language to render the splash card in, e.g. 'de' or 'pt-BR'. Only needed when your page's <html lang> does not reflect the language on screen — otherwise the card follows <html lang>, then the browser preference, with no configuration at all. A language Skippr does not ship falls back to English. | | 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 | | isSplashConfigured | boolean | Whether your workspace has the splash screen turned on. Configuration only — it does not tell you whether the splash is currently on screen |

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 | | reopenSplash | () => void | Show the splash screen again after the user skipped it. No-op while a session is running or when your workspace has the splash turned off |

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, userContext, locale, variant, minimizable, captureMode. | | Skippr.open() | Surface the widget from your own button. Brings back a skipped splash screen, opens the chat panel when the splash lives in it, and otherwise reveals the voice bar. The only entry point when your workspace has the launcher turned off | | 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