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

@darqlabs/curator-sdk-react

v0.1.4

Published

Official React components for Curator — drop-in chat widget and headless hook backed by @darqlabs/curator-sdk.

Readme

@darqlabs/curator-sdk-react

Official React components for Curator. Drop in a ready-to-use chat widget that talks to your deployed agents, or wire up your own UI with the headless hook.

Powered by assistant-ui internals — token-by-token streaming, edit/regenerate primitives, and tool-call extension points are available to consumers who want to go deeper than the drop-in widget.

// once, at the app root
import { Curator } from "@darqlabs/curator-sdk"
import { CuratorProvider } from "@darqlabs/curator-sdk-react"

const curator = new Curator({
  apiKey: "CURATOR_API_KEY",
  project: "default",
  environment: "production",
})

export function App() {
  return (
    <CuratorProvider client={curator}>
      <Routes />
    </CuratorProvider>
  )
}

// anywhere inside
import { CuratorChat } from "@darqlabs/curator-sdk-react"

export function Support() {
  return <CuratorChat agent="support-bot" />
}

Install

npm install @darqlabs/curator-sdk @darqlabs/curator-sdk-react

React 18 or 19. Works with Vite, Next.js, Remix, anything with react and react-dom.

Why pass the client?

The components consume a Curator instance (built from @darqlabs/curator-sdk) rather than an API key. That keeps auth, project / environment defaults, custom fetch, and headers in one place — the same instance can power multiple chats, file uploads, or your own SDK calls.

Wrap your tree once with <CuratorProvider> and components further down read the client from context automatically. Or pass curator explicitly to override (e.g. when one screen needs to talk to a different client).

Drop-in component

<CuratorChat
  agent="support-bot"
  // optional — overrides the provider's client
  curator={otherCurator}
  // optional UX
  title="Support"
  placeholder="Ask anything…"
  welcomeMessage="Hi! How can I help?"
  // optional locator overrides
  project="default"
  environment="production"
  conversationId={savedConversationId}
  // optional message actions (both default off)
  enableRetry             // ↻ regenerate button under the latest assistant reply
  enableCopy              // copy button on assistant messages
  // optional styling
  theme="light"           // or "dark"
  className="my-chat"
  style={{ height: 520 }}
  // optional events
  onMessage={(msg) => console.log(msg)}       // final assistant message (msg.metadata = last state snapshot)
  onState={(s) => console.log(s.metadata)}    // live structured state, every turn (see below)
  onError={(err) => console.error(err)}
/>

Live structured state (onState)

If your agent produces a structured result — a booking draft, an extracted form, a plan — it can report that result as it works, so your app renders progress live instead of waiting for the final message. On the agent side this is the reserved __emit_state tool (call it each turn with the complete current object); on the client side it surfaces as onState.

onState fires on every turn the agent reports state (including mid-run, while it's still calling tools), before onMessage. state.metadata is the complete current snapshot — schema-agnostic; validate/shape it in your app.

function BookingCopilot() {
  const [draft, setDraft] = useState(null)
  return (
    <>
      <CuratorChat
        agent="booking-copilot"
        onState={(s) => setDraft(s.metadata?.bookingDraft)}  // updates as the agent fills it in
      />
      <BookingPreview draft={draft} />
    </>
  )
}

The same snapshot also arrives on the final message as msg.metadata via onMessage, so a non-streaming consumer still gets the last state without handling onState.

Message actions

| Prop | Default | Effect | | --- | --- | --- | | enableRetry | false | Shows a ↻ button under the latest assistant message that re-sends the preceding user turn. | | enableCopy | false | Shows a copy button on assistant messages (with a brief check-mark confirmation). |

Theming

Two built-in palettes via the theme prop ("light" / "dark"). For finer control, override the design-token CSS variables through the style prop:

<CuratorChat
  agent="support-bot"
  style={{
    ["--curator-accent"]: "#2563eb",       // agent bubble + send button background
    ["--curator-accent-text"]: "#ffffff",  // agent bubble + send button text/icon
    ["--curator-user-bubble"]: "#f1f5f9",  // user bubble background
    ["--curator-bg"]: "#fafafa",           // chat background
    ["--curator-text"]: "#18181b",         // primary text
    ["--curator-muted"]: "#71717a",        // action icons, timestamps, hints
    ["--curator-border"]: "rgba(0,0,0,0.08)",
    ["--curator-error"]: "#b91c1c",
    ["--curator-radius"]: "16px",          // container corner radius
  } as React.CSSProperties}
/>

| Token | Controls | | --- | --- | | --curator-accent / --curator-accent-text | Agent (assistant) bubble and the send button | | --curator-user-bubble | User bubble background (text uses --curator-text) | | --curator-bg / --curator-surface | Chat background / composer + input background | | --curator-muted | Action icons, welcome text, timestamps | | --curator-border, --curator-error, --curator-radius | Borders, error text, container radius |

Use the style prop (not a CSS class or an ancestor rule): the widget sets these variables inline on its own root for the active theme, and inline styles win over a class — the style prop is applied last, so it's the one override that reliably takes effect. The widget ships no external stylesheet.

Floating launcher

For a chat-bubble-in-the-corner widget instead of an inline panel, use <CuratorChatLauncher />. Same props as <CuratorChat /> plus placement controls:

import { CuratorChatLauncher } from "@darqlabs/curator-sdk-react"

<CuratorChatLauncher
  agent="support-bot"
  placement="bottom-right"     // or bottom-left / top-right / top-left
  offset={24}                  // px from the docked corner
  panelWidth={380}
  panelHeight={560}
  defaultOpen={false}
  // all <CuratorChat /> props are forwarded:
  title="Support"
  theme="light"
  welcomeMessage="Hi! How can I help?"
/>

Renders via a portal to document.body, so it escapes stacking contexts in the host app. ESC closes the panel; clicking outside doesn't (intentional — keeps chat state from getting lost on a stray click).

Headless hook

For full UI control, use useCuratorChat directly. curator is optional when a <CuratorProvider> is in scope:

import { useCuratorChat } from "@darqlabs/curator-sdk-react"

function MyChat() {
  const { messages, send, sending, error, conversationId } = useCuratorChat({
    agent: "support-bot",
  })

  return (
    <div>
      {messages.map((m) =>
        m.kind === "user" ? <UserBubble key={m.localId} text={m.content} />
        : m.kind === "assistant" ? <BotBubble key={m.runId} text={m.content} />
        : <ErrorRow key={m.localId} text={m.message} />,
      )}
      {sending && <Typing />}
      <Composer onSubmit={(text) => send(text)} />
    </div>
  )
}

The hook returns:

| | | | --- | --- | | messages | Chronological list of bubbles (user / assistant / error). | | send(content, opts?) | Append a user message and await the assistant's reply. opts.files for attachments, opts.timeoutMs to override the wait. | | sending | True while a send is in flight. | | error | Most recent error, or null. Cleared on next successful send. | | chat | Underlying SDK Chat instance. | | conversationId | Conversation id once one has been created. | | reset() | Clear visible messages (keeps the same conversation). |

Reading the client directly

import { useCurator } from "@darqlabs/curator-sdk-react"

function UploadButton() {
  const curator = useCurator()
  return (
    <button onClick={async () => {
      const fileRef = await curator.files.upload({ filename, mimeType, data })
      // …
    }}>
      Upload
    </button>
  )
}

Throws a clear error if no <CuratorProvider> is mounted higher in the tree.

Errors

The hook captures all SDK errors into a friendly kind: "error" bubble — auth, agent failures, timeouts, approval-required pauses, etc. — and also exposes the raw error via error and the onError prop so you can log or surface them however you like.

Security in the browser

The chat runs wherever your Curator client runs. If you instantiate the client in browser code, the API key ships in your bundle. Recommended patterns:

  1. Backend proxy. Run the SDK on your server; have your frontend call your server. Your server holds the key.
  2. Trusted internal tools. Employee dashboards behind SSO are usually fine.
  3. Server-issued tokens. Not supported yet — reach out if you need them.