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

@gnomondigital/nebulas-kit-react

v0.10.4

Published

React components for A2A chat. Requires `@gnomondigital/nebulas-kit-core` and a server proxy.

Readme

@gnomondigital/nebulas-kit-react

React components for A2A chat. Requires @gnomondigital/nebulas-kit-core and a server proxy.

Install

npm install @gnomondigital/nebulas-kit-react @gnomondigital/nebulas-kit-core

Usage

import { A2AChat } from "@gnomondigital/nebulas-kit-react";

export default function ChatPage() {
  return (
    <A2AChat
      a2aEndpoint="/api/a2a"
      configEndpoint="/api/a2a/config"
      user={{ name: "John", picture: "https://..." }}
      labels={{
        placeholder: "Ask anything...",
        newChat: "New chat",
        howCanIHelp: "How can I help?",
        askAbout: "Ask me anything.",
      }}
      suggestedPrompts={[
        "Find top 5 Python developers",
        "Summarize the pipeline",
      ]}
    />
  );
}

File attachments

The composer’s attach-file control (paperclip) is hidden by default. Set showAttachFileButton on A2AChat or A2AChatFloating to show it and allow PDF, Word, Markdown, and plain-text uploads (same accepted types as the underlying input).

<A2AChat
  a2aEndpoint="/api/a2a"
  configEndpoint="/api/a2a/config"
  showAttachFileButton
/>

Auth0 session (cookies)

Same-origin fetch sends session cookies to your /api/a2a route. Optionally pass user for avatars. No getAuthHeaders required when the proxy resolves the user on the server.

Env ROP (server-side service user)

When your API uses createA2AProxyHandlerWithServiceAuth, the browser does not send a token. Use A2AChat or A2AChatFloating with only a2aEndpoint and configEndpoint.

Resource Owner Password (browser token)

Use getAuthHeaders so config and chat requests include Authorization: Bearer …. Pass authDependency (e.g. the token string) so config reloads after login without relying on a stable function reference.

import { A2AChat, useA2AAuth } from "@gnomondigital/nebulas-kit-react";

export default function ChatPage() {
  const { token, login, logout } = useA2AAuth({ authEndpoint: "/api/a2a/auth" });

  if (!token) {
    return (
      <form
        onSubmit={(e) => {
          e.preventDefault();
          const form = e.target as HTMLFormElement;
          login(
            (form.elements.namedItem("username") as HTMLInputElement).value,
            (form.elements.namedItem("password") as HTMLInputElement).value
          );
        }}
      >
        <input name="username" />
        <input name="password" type="password" />
        <button type="submit">Login</button>
      </form>
    );
  }

  return (
    <>
      <button type="button" onClick={logout}>
        Log out
      </button>
      <A2AChat
        a2aEndpoint="/api/a2a"
        configEndpoint="/api/a2a/config"
        getAuthHeaders={() => ({ Authorization: `Bearer ${token}` })}
        authDependency={token}
      />
    </>
  );
}

Floating widget

Add once in a root layout: fixed chat button (bottom-right), panel with the same chat UI.

Auth0 session — optional user from your auth library; endpoints use your session proxy.

import { A2AChatFloating } from "@gnomondigital/nebulas-kit-react";

export function RootChat() {
  return (
    <A2AChatFloating
      a2aEndpoint="/api/a2a"
      configEndpoint="/api/a2a/config"
      nebulasBaseUrl="/api/nebulas"
      user={{ name: "Jane", picture: "https://..." }}
      panelTitle="Support"
      suggestedPrompts={["What can you help with?"]}
    />
  );
}

Env ROP — no extra client props beyond endpoints (and optional suggestedPrompts / labels).

<A2AChatFloating a2aEndpoint="/api/a2a" configEndpoint="/api/a2a/config" />

Browser ROP — built-in sign-in form in the panel; wire your token endpoint at authEndpoint.

<A2AChatFloating
  authEndpoint="/api/a2a/auth"
  a2aEndpoint="/api/a2a"
  configEndpoint="/api/a2a/config"
/>

A2AChatFloating renders into document.body by default (usePortal) so parent stacking contexts do not clip it. Set usePortal={false} to keep it in the React tree.

Requirements

  • Tailwind CSS (configure in your app)
  • Server proxy (Next.js or Express) — see @gnomondigital/nebulas-kit-core

Surfaces (NebulasSurface)

A surface is a widget authored in Nebulas — most often a form. NebulasSurface renders a stored one on its own, outside any chat, and submits it back to the backend where its declared handler runs.

import { NebulasSurface } from "@gnomondigital/nebulas-kit-react";

<NebulasSurface
  surfaceName="contact_sales"
  onResult={(result) => console.log(result.status, result.message)}
/>

Address it by surfaceName or by surfaceId — one or the other, never both.

A name is what its author knows, and it survives moving between environments: staging and production hold the same contact_sales form under different ids. It costs one lookup before the first render, done by the Kit's backend so the definition never reaches the browser. Submissions then use the id the render answered with, so a form is looked up once, not once per click.

An id is exact and skips the lookup. It is the definition id, not an A2UI instance id — the instance is created by the render call.

<NebulasSurface surfaceId="66b0c1f2e4b0a1c2d3e4f5a6" />

Either way it needs the surface handler mounted at /api/nebulas-surfaces (see @gnomondigital/nebulas-kit-core); point endpoint elsewhere if you mount it on another path.

| Prop | Purpose | |---|---| | inputs | Values for the definition's declared inputs, including hidden ones (utm_source, a tenant id, …) | | trackingParams | Attribution parameters to lift off the page URL and pass as hidden inputs (see below) | | searchParams | Where to read those from, when not window.location.search | | data | Prefill merged over the initial data model | | theme | brand (default) applies the surface's brand tokens; product leaves your page's theme in charge | | sessionId | Conversation id, when the form belongs to one — the side effect is then recorded in that transcript | | onResult | Every action's raw backend result | | onChatMessage | A chat handler produces a message meant for a conversation; standing alone, the host decides what to do with it | | t | Resolves i18n:-prefixed labels not covered by the surface's own labels; keys pass through by default | | locale | Which language of the surface's own labels to read (default "en") | | resolveImageSrc | Turns an image src into a loadable URL (e.g. neb:// artifact ids) | | successDialog | Shows a dialog instead of the inline note on a successful submit, then reloads to a blank form (see below) | | captcha | Gates a submit behind a solved challenge (see below) |

Validation declared on the surface is checked in the browser before a submit and again by the server before any handler runs. What comes back lands in place: a refresh patches the data, a follow-up surface renders beneath, field errors appear under their fields, and a one-shot handler (chat, script, datatable) leaves the form read-only.

Hidden UTM / attribution parameters

A lead should carry where it came from, without the visitor seeing a field for it. Point trackingParams at the keys to lift from the page URL:

// ?utm_source=newsletter&utm_campaign=spring
<NebulasSurface surfaceId="66b0…" trackingParams />                      // the five standard utm_* keys
<NebulasSurface surfaceId="66b0…" trackingParams={["utm_source", "gclid", "ref"]} />

They are read once on mount and sent as inputs to the render call, so the surface's data can bind them into the submitted model. Only parameters actually present in the URL are sent, and an explicit inputs value for the same key wins.

Each key must be declared as an input on the surface definition, normally with hidden: true so an agent never invents it. The backend rejects an input it does not declare — that is why this is opt-in rather than automatic: a form declaring no utm_* input would otherwise fail to render on any page whose URL happened to carry one. A misconfiguration shows the backend's own message, which names the unknown keys and the declared ones.

When the host owns the query string — Next's useSearchParams(), a router location, or a landing URL captured before the visitor navigated to the form — pass it explicitly:

const params = useSearchParams();
<NebulasSurface surfaceId="66b0…" trackingParams searchParams={params.toString()} />

A dialog on success, instead of the inline note

By default a successful one-shot submit (chat, script, datatable) leaves the form disabled with a note underneath. Pass successDialog to show a modal instead, then reload to a blank form once it closes:

<NebulasSurface
  surfaceName="contact_form"
  successDialog={{
    title: "Thank you!",
    message: "We've received your inquiry.",
  }}
/>

An error note (a rejected submission) still shows inline — only the success note is replaced, since the dialog now carries that role.

Captcha

NebulasSurface does not bundle a captcha library — pass captcha with a render function that mounts your own widget (reCAPTCHA, Turnstile, hCaptcha, …) and calls back with the verified token:

<NebulasSurface
  surfaceName="contact_form"
  captcha={{
    render: (onVerify) => (
      <Turnstile siteKey="…" onSuccess={onVerify} onExpire={() => onVerify(null)} />
    ),
  }}
/>

A submit attempted before the token arrives is blocked client-side (shown via labels.captchaRequired). Once solved, the token rides along with the action as a JSON Pointer into the submitted data — /captcha_token by default, override with dataPath — independently of whether the surface sends its full data model, so the backend can validate it. The token is single-use: it clears once a submit succeeds (a rejected submission keeps it, so fixing a field does not mean solving the challenge again), and the widget is expected to remount and ask again after a reload.

Rendering it yourself

SurfaceRenderer (a SurfaceState in, actions out), useSurfaceActions, useSurfaceModel and renderCatalogComponent are exported for hosts that fetch and lay out surfaces themselves.

Optional dependency

Chart components and fenced ```vega blocks need vega-embed, an optional peer. Without it the chart renders as nothing and the rest of the surface is unaffected.