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

@pinecall/web

v0.3.10

Published

Pinecall web client — WebRTC voice, text chat, and React widgets for Pinecall agents

Readme

@pinecall/web

npm

The web client for Pinecall agents — real-time WebRTC voice, text chat, and drop-in React widgets, in one package.

Migrating from @pinecall/voice-core / @pinecall/voice-widget / @pinecall/chat-core? They are now a single package. See Entry points below — the React widget moves to the package root, vanilla voice to /core, and chat to /chat + /chat/react.

Install

npm install @pinecall/web
# React is a peer dep — only needed for the widget + chat/react entries
npm install react react-dom

Entry points

| Import | What | Needs React | |--------|------|-------------| | @pinecall/web | React widgets — VoiceWidget, ContactHub, ChatView, useVoice, useVoiceSession, presets | ✅ | | @pinecall/web/core | VoiceSession — framework-agnostic WebRTC voice client | ❌ | | @pinecall/web/chat | ChatSession — framework-agnostic text chat client | ❌ | | @pinecall/web/chat/react | usePinecallChat — React hook over ChatSession | ✅ | | @pinecall/web/orb | <pinecall-orb> — framework-agnostic voice orb (Custom Element) | ❌ | | @pinecall/web/orb/react | <Orb> — thin React wrapper for <pinecall-orb> | ✅ | | @pinecall/web/modal | <pinecall-modal> — glass call modal (Custom Element): orb or wave visual, live captions, text-during-call, transcript view | ❌ | | @pinecall/web/modal/react | <CallModal> — thin React wrapper for <pinecall-modal> | ✅ | | @pinecall/web/chatbox | <pinecall-chat> — docked chatbox (Custom Element): text chat that can escalate to a WebRTC voice call, with conversation continuity | ❌ | | @pinecall/web/chatbox/react | <ChatBox> — thin React wrapper for <pinecall-chat> | ✅ |

Web Components vs React widget: the /orb, /modal and /chatbox entries are native Custom Elements — they work in any framework (React, Vue, Svelte, Angular, vanilla) and need no React. The original @pinecall/web React widget stays available unchanged.

Quick Start

React widget

import { VoiceWidget } from "@pinecall/web";

<VoiceWidget agent="mara" name="Mara" preset="midnight" />

Vanilla voice (any framework)

import { VoiceSession } from "@pinecall/web/core";

const session = new VoiceSession({ agent: "mara" });
session.subscribe(() => console.log(session.getState()));
await session.connect();

Chat hook

import { usePinecallChat } from "@pinecall/web/chat/react";

const chat = usePinecallChat({ agent: "florencia" });

Web Components (any framework)

<!-- voice orb -->
<pinecall-orb agent="mara" name="Mara" preset="midnight"></pinecall-orb>

<!-- call modal: orb or wave visual, captions, text-during-call -->
<pinecall-modal agent="mara" name="Mara" visual="wave"></pinecall-modal>

<!-- docked chatbox: text chat + a call button to escalate to voice -->
<pinecall-chat agent="mara" name="Mara" greeting="Hi! How can I help?"></pinecall-chat>

<script type="module">
  import "@pinecall/web/orb";
  import "@pinecall/web/modal";
  import "@pinecall/web/chatbox";
  // function/object props are set as PROPERTIES (not attributes):
  const modal = document.querySelector("pinecall-modal");
  modal.tokenProvider = async () => (await fetch("/api/token")).json();
  // the chatbox tokenProvider is channel-aware (text vs voice):
  document.querySelector("pinecall-chat").tokenProvider =
    async (channel) => (await fetch(`/api/token?channel=${channel}`)).json();
</script>

Orbopens attribute: "inline" (captions beside the orb, default), "modal" (opens a <pinecall-modal>), or "chat" (opens a <pinecall-chat>). One orb, any presentation.

Chatbox — text-first; a call button escalates to a WebRTC voice call and the conversation continues (prior transcript carried over). Attributes: greeting (first bot bubble, client-side), auto-call (start in a call), no-call (pure text). Its tokenProvider is channel-aware: (channel: "chat" | "webrtc") => {token, server}.

Common attributes: agent, server, name, label, preset, avatar, visual (orb | wave). Properties: config, metadata, tokenProvider, theme. Events: pinecall:status, pinecall:transcript, pinecall:error, pinecall:open, pinecall:close. Theme via the --vw-* / --pm-* CSS custom properties (e.g. --pm-user / --pm-bot for speaker colors).

In React, prefer the wrappers so object/function props bind cleanly:

import { CallModal } from "@pinecall/web/modal/react";

<CallModal agent="mara" name="Mara" visual="wave"
           tokenProvider={async () => (await fetch("/api/token")).json()} />

Structure

web/
├── src/
│   ├── index.ts        @pinecall/web        — React widgets barrel
│   ├── core/           @pinecall/web/core   — VoiceSession (vanilla)
│   ├── chat/           @pinecall/web/chat[/react] — ChatSession + React hook
│   ├── orb/            @pinecall/web/orb[/react]  — <pinecall-orb> custom element
│   ├── modal/          @pinecall/web/modal[/react] — <pinecall-modal> call modal
│   ├── chatbox/        @pinecall/web/chatbox[/react] — <pinecall-chat> chatbox
│   └── widget/         React components (VoiceWidget, ContactHub, ChatView…)
├── docs/               diagrams + legacy changelogs
├── examples/           demo pages (orb/modal/chatbox.html) + token-server + react app
├── tsup.config.ts      Build (10 entries → ESM + CJS + DTS)
└── tsconfig.json

Development

pnpm install
pnpm build       # build all 4 entries (ESM + CJS + DTS)
pnpm dev         # tsup watch
pnpm typecheck

Publishing

npm version <patch|minor|major>
pnpm release     # build + npm publish

Widget Theme & Orb States

The <VoiceWidget> orb cycles through visual states as the session progresses. Each state has a configurable color (RGB triplet):

| Orb State | CSS Class | Theme Property | Default Color | When | |-----------|-----------|---------------|---------------|------| | Idle | — | orbFrom/orbMid/orbTo | Pearl gradient | Not connected | | Connecting | .connecting | colorConnecting | 245, 158, 11 (amber) | Establishing WebRTC | | Active | .active | colorActive | 76, 175, 80 (green) | Connected, waiting | | User speaking | .user-speaking | colorUserSpeaking | 52, 211, 153 (emerald) | User talking | | Agent speaking | .speaking | colorSpeaking | 248, 113, 113 (rose) | Agent talking | | Thinking | .thinking | colorThinking | 139, 92, 246 (violet) | Processing | | Idle warning | .idle-warning | colorWarning | 255, 160, 0 (orange) | User silent too long, call will timeout |

Idle Warning

When the server emits session.idle_warning, the orb switches to the idle-warning state — a blinking amber/orange animation. This warns the user that the call will end due to inactivity.

// Customize the warning color via theme
<VoiceWidget
  agent="mara"
  theme={{ colorWarning: "255, 60, 60" }}  // red warning
/>

The idle warning is cleared when:

  • The user starts speaking
  • The session disconnects
  • session.timeout fires (auto-disconnect)

Theme Presets

5 built-in presets: dark (default), midnight, aurora, sunset, light.

<VoiceWidget agent="mara" preset="midnight" />

Custom Theme

Override individual colors on top of any preset:

<VoiceWidget
  agent="mara"
  preset="dark"
  theme={{
    colorActive: "0, 200, 100",
    colorWarning: "255, 80, 0",
    ringColor: "100, 100, 200",
  }}
/>

All theme properties accept RGB triplets (e.g. "255, 160, 0") for use with CSS rgba().

Session Limits (via @pinecall/sdk)

Session limits are configured on the agent (server-side SDK) and flow through to the WebRTC widget automatically:

// Server-side (agent.js)
const agent = pc.deploy("my-agent", {
  // ...voice, stt, llm config...
  sessionLimits: {
    idle_timeout_seconds: 20,   // hang up after 20s of silence
    idle_warning_seconds: 10,   // warn 10s before timeout
    max_duration_seconds: 600,  // hard cap at 10 minutes
  },
});

agent.on("session.idle_warning", (event, call) => {
  call.say("Are you still there?");
});

The widget receives session.idle_warning via DataChannel and:

  1. Switches the orb to the idle-warning state (blinking colorWarning)
  2. On session.timeout, auto-disconnects and resets to idle

License

MIT