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

@ai-matrx/meet

v0.7.8

Published

Enterprise, AI-native calls and meetings for Matrx clients: LiveKit Cloud rooms with server-minted tokens, instant ring/accept/decline, Zoom-grade meetings with durable links, lobby admission and guest join, host controls, Egress recording with enforced c

Readme

@ai-matrx/meet

Enterprise, AI-native calls and meetings for every Matrx client: tap a person and be in a call in two seconds, or run a meeting that rivals Zoom — lobby, host controls, screen share, recording — where the AI half is not an add-on but the reason ours is better.

One provider, one component, under an hour to full production conferencing including the AI.

pnpm add @ai-matrx/meet

Built on @ai-matrx/realtime for signaling and @ai-matrx/messaging for invitations. There is zero hand-rolled .channel( code in this package, and there must be none in yours.


Why this package exists

Video calling is the feature every team estimates at two weeks and ships in three months, because the hard parts are not the tiles. They are: who is allowed in this room, what happens when the token expires mid-meeting, why the camera light stays on after you leave, why the phone in the other room is still ringing after you answered on your laptop, why the headset you unplugged is still "selected", and what a guest who has no account is allowed to do. Every one of those has a name in this README and a regression test beside it.

We are deliberately inflexible. LiveKit Cloud is the media infrastructure, hardcoded (D1/D2). There is no provider abstraction and never will be. The package does exactly what we want it to do, and that is the product.


The doctrine

1. The client NEVER holds a LiveKit secret.

Not in an env var, not in a build-time constant, not "just for local dev". A LiveKit API secret in a browser bundle mints a token for any room in the project, including meetings the holder was never invited to — it is a full compromise of every conversation in the organization.

So the package has no code path that could accept one. It asks aidream; aidream mints. That is the only authorization path into a room (R1), and a guest's entire capability is the token they were given: exactly one room, nothing else (R5).

2. A token is refreshed before it bites, and minted ONCE.

Refresh happens at 80% of the token's life, not when a reconnect fails at 100% — the classic two-hour-meeting bug is a session that outlives its token and cannot come back. And every concurrent asker shares ONE mint: a React strict-mode double effect that mints twice gives one identity two LiveKit sessions, and LiveKit disconnects the older one. That looks exactly like "my call randomly drops when I switch tabs".

A token with no expires_at is refused rather than used, because a token the package cannot schedule a refresh for is a meeting that will silently fail to reconnect.

3. The phase never lies. There is no infinite spinner.

requesting-token → lobby | connecting → connected ⇄ reconnecting → left, and any failure becomes failed with the remedy attached to the snapshot. Reconnecting is a visible state, because a frozen tile with no explanation is indistinguishable from a crashed app.

Device failures get four different sentences, not one shrug: permission denied, no such device, device already in use by another app, and insecure context (getUserMedia does not exist on http). A single "media error" would tell the user the wrong thing three times out of four.

4. A call reaches you on TWO legs, and rings ONCE.

The durable leg is the invite row and its Postgres Changes event: it survives a reload, a late-joining tab, and a dropped socket, and it is what makes a missed call a fact rather than a lost packet. The instant leg is a broadcast on your user channel, with no database round trip. Both are deduplicated on the invite id.

Neither is a fallback for the other. Remove the durable leg and calls get lost; remove the instant leg and ringing feels slow.

Around that: every ring ends (an unanswered call writes missed), answering on one device stops every other (the settled signal on your own channel), being in a call answers busy rather than ringing out, and the state machine refuses to resurrect a settled call when a late accept arrives.

5. A guest is a first-class identity, not a degraded user.

A meeting link works for anyone (D6). Guests enter a name, wait in the lobby, and the host admits them. Authenticated users skip the prompt; host powers require authentication.

This is enforced by TYPES, not by discipline: MeetIdentity.userId is UserId | null, and every operation that needs an account takes a UserId. A guest provider has no call center at all — so <CallButton> renders nothing rather than a disabled button (no dead ends).

6. Admission is a server decision. Roles come from the token.

admit is an auth-checked RPC; the broadcast that follows only tells the knocker, who has no database read of their own. Two hosts clicking "admit" is the ordinary case, and the RPC is what makes it idempotent.

Every participant's role is read from the server-minted token metadata. A client that could decide it was host could mute anyone. And a co-host cannot remove the host — without that rank check, "host controls" is a mutual-eviction button.

7. A durable link is an identity, not a URL for one occurrence.

A meeting slug resolves before, during, and after its scheduled time, and a recurring meeting keeps ONE link. A link that dies when the meeting ends breaks every "here are the notes" follow-up, which is when people actually go looking for it.

Slugs deliberately exclude the characters people mishear reading them aloud: no 0/O, no 1/l.

8. Consent is a property of the package, not a prop.

There is no showRecordingIndicator={false}. Recording someone without telling them is illegal in a two-party-consent jurisdiction and indefensible everywhere else, so the indicator and the join-time notice ship with the package. The only knob is the org's recordingPolicy — disabled / host-controlled / always-on — which decides whether recording may happen at all, never whether people are told.

The indicator turns solid when the server says the egress is running, never on a client's hope; a refused start reverts it rather than leaving a red dot over a recording that is not running. And a recording lands in the platform files system as a file id, never a provider URL — a LiveKit URL is an orphan the day the room is garbage collected.

9. The AI is a participant, and it looks like one.

The note-taker joins the room as a real LiveKit participant with is_agent in its token metadata, so it renders as a labelled tile. An invisible listener in a meeting is the thing nobody should ever ship.

Everything AI executes on the platform agent system through @ai-matrx/agents. The package never talks to a model, never holds a prompt, and never carries an agent definition — it takes agent IDs as injected identity, and an unconfigured capability reports unavailable with the remedy while the UI hides that action entirely.

🚨 THE USER-INPUT LAW. user_input is what a human typed. Transcripts, rosters, and agendas travel as named variables. The server stores and frames user_input as the turn's human utterance, so smuggling a transcript through it corrupts every conversation it touches. Exactly one call in this package sends a user_input: the in-meeting question a person actually asked.

Interim transcript lines never reach an agent, and a truncated transcript says so on the wire.

10. The stage is arithmetic, and it never shuffles.

Screen share (or an explicit pin) owns the stage; then active speakers, then raised hands, then cameras-on, then stable by join time. Tiles that reshuffle on every roster event are the most disorienting thing a meeting UI can do.

A large meeting pages and says +N more people — it does not render 200 videos and die, and it does not silently drop the people it cannot fit (R5).

11. Nothing fails silently, and no control is a lie.

Every error is a MeetError with a stable code and a remedy sentence. Every stand-in announces itself: an unplugged headset says "your remembered microphone is no longer connected — using the system default" rather than quietly switching you to the laptop mic.

A control an actor cannot use is absent, never disabled-looking and never a button the server will refuse.


The one-hour integration (R7)

import "@ai-matrx/meet/tokens.css";   // defaults — override these with your brand
import "./brand.css";
import "@ai-matrx/meet/styles.css";   // structural, last

import { MeetProvider, IncomingCallHost, MeetingRoom, CallButton } from "@ai-matrx/meet/react";

export function App({ children }) {
  return (
    <MeetProvider
      client={supabase}
      baseUrl={AIDREAM_URL}
      displayName={user.name}
      organizationId={org.id}
      accessToken={async () => (await supabase.auth.getSession()).data.session?.access_token ?? null}
      transport={matrxTransport}
      agents={{ noteTaker: NOTE_TAKER_AGENT_ID, liveIntelligence: LIVE_AGENT_ID }}
    >
      {/* Mount ONCE. Calls now ring on whatever surface the user is on. */}
      <IncomingCallHost />
      {children}
    </MeetProvider>
  );
}

// Anywhere a person appears:
<CallButton person={{ userId, displayName, avatarUrl }} />

// Your meeting route:
<MeetingRoom roomName={roomName} meetingId={meetingId} />

🚨 You do not tell Meet who the user is, and there is no prop for it. The acting user is read from client's own session — the same client that mints the JWT on every request — so the id in a p_user_id argument and the id in the token cannot disagree. Through 0.5.x this WAS a prop, every host filled it from a copy in its own state, and a rotated auth cookie left that copy naming an account the tab could no longer prove it was; meet_pending_call_invites refused those reads at 403 with "the acting user … is not the authenticated user". Pass displayName and avatarUrl — they are chrome — and nothing else about identity. A userId prop is ignored and says so.

A guest join is the same provider with guestName, which is the one way to declare the guest lane. A guest has no account, so that mount reads no session at all:

<MeetProvider client={supabase} baseUrl={AIDREAM_URL} organizationId={orgId}
              guestName={typedName} accessToken={async () => null}>
  <MeetingRoom roomName={meeting.roomName} slug={slug} />
</MeetProvider>

Invitations ride the messaging seam — register the handlers once:

<MessagingProvider
  actions={[
    createCallInviteHandler({ calls, repository, onJoin: (invite) => router.push(`/meet/${invite.roomName}`) }),
    createMeetingInviteHandler({ onOpen: (payload) => router.push(`/meet/${payload.slug}`) }),
  ]}
>

What the host owns, and what it must never do

Inject: the Supabase client, aidream's base URL, who the user is (or a guest name), the org, an access-token source, the Matrx transport, agent ids, brand token values, and a router.

Never: catch a package error to reinterpret it, retry a package call, validate a package input, or branch on a package quirk. That logic belongs in here (C22) — open a PR against this package, release it, and delete the massage.


Server prerequisites

This is the client half. Two server-side pieces are required and are not built yet:

  • The aidream meet router — token minting (identity, grants, TTL, guest + lobby gating), Egress start/stop, LiveKit webhooks that keep the database TRUE-current, and the LiveKit Agents note-taker. The exact routes this package calls are MEET_ROUTES in src/core/tokens.ts.
  • The communication.meet_* schema in Matrx Main — the tables and RPCs named in src/core/repository.ts, under the platform's canonical DB conventions.

Until both exist the package fails loudly, with a remedy naming the missing piece. It never fabricates a room, a token, or a meeting record. See FEATURE.md.


Gates

pnpm --filter @ai-matrx/meet typecheck
pnpm --filter @ai-matrx/meet test
pnpm --filter @ai-matrx/meet check:package   # build + publint + packed-tarball canary

MIT licensed. Source: aidream/apps/shared/meet.