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

@avatar-state-machine-interface/react

v0.2.0

Published

Drop-in React components and hooks for embedding ASMI avatars (Avatar State Machine Interface). Bundles the correctness-critical session + face primitives so coding AIs don't hand-roll them. Pairs with @avatar-state-machine-interface/runtime.

Readme

@avatar-state-machine-interface/react

Drop-in React components and hooks for embedding ASMI avatars (Avatar State Machine Interface) into any React site. Pairs with the runtime package @avatar-state-machine-interface/runtime.

You still design the avatar at broen.tech/apps/asmi. This package handles the correctness-critical UI surface:

  • Live mid-turn expression swaps (neutral → attentive → thinking → smiling) via the runtime's trace hook
  • Animation playback with all four trigger types (timer, mouse-enter, click, response-received)
  • Idle auto-return so the face doesn't freeze on the last turn's expression
  • Transparent face region separated from the chat shell so the avatar blends into the host site's background
npm install @avatar-state-machine-interface/react \
            @avatar-state-machine-interface/runtime

Turnkey usage — <AsmiAvatar>

import { AsmiAvatar } from "@avatar-state-machine-interface/react";
import type { LlmProvider } from "@avatar-state-machine-interface/react";
import { definition } from "./asmi-definition.json"; // fetched via MCP

const llmProvider: LlmProvider = {
  async generate({ systemPrompt, userPrompt, history, temperature, maxTokens }) {
    // Call OpenAI / Anthropic / Gemini / whatever your site already uses.
    return (await yourLlmClient.complete({
      system: systemPrompt,
      user: userPrompt,
      history,
      temperature,
      maxTokens,
    })).text;
  },
};

export default function Page() {
  return <AsmiAvatar definition={definition} llmProvider={llmProvider} debug />;
}

Drop debug once you've verified the state machine paints the expected expression trail (use devtools' console filter to see every state transition, action, expression emission, and LLM call).

Theming

Auto-detection is on by default. <AsmiAvatar> reads the host site's body background + text color and scans for the primary CTA button to derive the accent, then sets those as inline --asmi-* CSS custom properties on the widget wrapper. You get a widget that matches the host palette out of the box — no prop wiring required.

To override explicitly (precise brand colors, dark-mode switching, or anything the detector misses), set the --asmi-* variables on :root or any ancestor of the widget — those win via normal CSS specificity. Pass autoTheme={false} to skip the detection entirely if you have a fully hand-crafted override:

:root {
  --asmi-panel-bg: transparent;
  --asmi-panel-fg: #0f172a;
  --asmi-panel-muted: #64748b;
  --asmi-panel-border: rgba(0,0,0,0.1);
  --asmi-accent: #D36135;
  --asmi-accent-contrast: #fff;
  --asmi-bubble-bg: rgba(0,0,0,0.04);
  --asmi-input-bg: transparent;
  --asmi-shadow: 0 20px 60px rgba(0,0,0,0.25);
  --asmi-radius: 16px;
  --asmi-font: inherit;
  --asmi-scroll-thumb: rgba(128,128,128,0.3);
  --asmi-scroll-thumb-hover: rgba(128,128,128,0.5);
}

Manually applying the detected theme

If you compose the primitives directly (below), use useHostTheme to spread the same auto-detected palette onto your own wrapper:

import { useHostTheme, useAsmiSession, AsmiFace } from "@avatar-state-machine-interface/react";

export function CustomAvatar({ definition, llmProvider }) {
  const session = useAsmiSession(definition, llmProvider);
  const themeVars = useHostTheme();
  return <div style={themeVars}>{/* your layout */}</div>;
}

Custom chat UI — useAsmiSession + <AsmiFace>

Want file uploads, paste handling, voice input, or a radically different layout? Drop the turnkey wrapper and compose the primitives yourself:

import { useAsmiSession, AsmiFace } from "@avatar-state-machine-interface/react";

export function CustomAvatar({ definition, llmProvider }) {
  const session = useAsmiSession(definition, llmProvider, { debug: true });

  return (
    <div className="my-layout">
      <AsmiFace
        definition={definition}
        expression={session.state.expression}
        size={320}
        responseTriggerKey={session.history.length}
      />
      <MyCustomChatLog history={session.history} />
      <MyCustomInputWithFileUpload onSubmit={session.send} disabled={session.sending} />
    </div>
  );
}

Everything the state machine needs is encapsulated in the hook. The face primitive is a pure render of the expression prop (plus its own animation-trigger state). Replace the chat surface freely — the kernel keeps working.

Example: file upload + clipboard paste

function InputWithAttachments({ onSubmit, disabled }: {
  onSubmit: (text: string) => void;
  disabled: boolean;
}) {
  const [text, setText] = useState("");
  const [attachment, setAttachment] = useState<File | null>(null);

  async function handleSend() {
    // Upload attachment via your own API, then include a reference in
    // the user's message or pass it to your LlmProvider out-of-band.
    if (attachment) {
      const url = await uploadFile(attachment);
      onSubmit(`${text}\n(attached: ${url})`);
    } else {
      onSubmit(text);
    }
    setText("");
    setAttachment(null);
  }

  return (
    <div>
      <textarea
        value={text}
        onChange={(e) => setText(e.target.value)}
        onPaste={(e) => {
          const file = e.clipboardData.files?.[0];
          if (file) setAttachment(file);
        }}
        disabled={disabled}
      />
      <input type="file" onChange={(e) => setAttachment(e.target.files?.[0] ?? null)} />
      <button onClick={handleSend} disabled={disabled}>Send</button>
    </div>
  );
}

Multimodal LlmProvider.generate (structured attachments payload) is on the roadmap for v0.2. v0.1 is text-only; the API surface is designed so attachments can be added additively without breaking.

Example: voice input (speech-to-text)

Wire any STT library (Web Speech API, Deepgram, AssemblyAI) to a button, then call session.send(transcript) when the user stops speaking. The hook doesn't care where the string came from.

API

useAsmiSession(definition, llmProvider, options?)

| Option | Type | Default | Notes | |---|---|---|---| | idleTimeoutSeconds | number | 45 | Seconds before state snaps back to idle/neutral. 0 disables. History is preserved. | | debug | boolean | false | console.info every trace event. | | onTrace | (e: TraceEvent) => void | undefined | Fires for every internal step (state transition, action exec, expression emit, LLM call). | | onOutboundEvent | (e: OutboundEvent) => void | undefined | Fires for each outbound app event (asmi:handoff, etc). Also auto-dispatched on window. | | initialState | SessionState | idle/neutral | Rehydrate from persistence. | | initialHistory | HistoryEntry[] | [] | Rehydrate from persistence. | | initialMetadata | SessionMetadata | fresh | Rehydrate from persistence. | | sessionContext | Record<string, unknown> | undefined | Extra context passed to processMessage — e.g. { page, visitorTimezone }. |

Returns { state, history, metadata, sending, send, reset, setHistory, setExpression, bumpActivity }.

<AsmiFace>

Pure render primitive. Accepts definition, expression, size, className, style, onClick, ariaLabel, responseTriggerKey. Plays animations configured on the expression asset.

<AsmiAvatar>

Turnkey wrapper. Accepts every useAsmiSession option plus: collapsedSize, expandedSize, position, floating, placeholder, renderInput, renderMessage, className.

Use renderInput for custom input surfaces (file uploads, voice) and renderMessage for custom bubble styling.

What's NOT in this package

  • Multimodal LlmProvider.generate (attachments, images, files) — v0.2 roadmap.
  • Automatic persistence (localStorage history, session resume) — the integrator wires initialHistory + setHistory to whatever storage they already use.
  • Framework wrappers (Vue, Svelte, vanilla JS, Web Components) — React first.

License

MIT. See LICENSE.

Links