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

@noodleseed/assistant

v1.5.0

Published

Embed the Noodle Seed customer-branded assistant in your web app: Web Component, React wrapper, and backend session helper. Authoring and deploying the server is @noodleseed/one.

Readme

@noodleseed/assistant

Customer-branded embedded assistant surfaces for Noodle Seed deployments.

The package exports the canonical <noodle-assistant> Web Component, a React wrapper from @noodleseed/assistant/react, a DOM-free client from @noodleseed/assistant/client, and the backend-only createAssistantSession helper from @noodleseed/assistant/server. Light, dark, and automatic themes work without configuration; the component inherits the deployed MCP server's brand kit while slots, methods, events, and semantic CSS variables let a SaaS developer integrate it without depending on internal DOM selectors.

Never pass an embed client secret, model key, MCP token, or raw application session into the browser component. Exchange the already-authenticated user from the customer backend and return only the short-lived assistant session.

The model configuration and SaaS integration credentials have different owners:

| Owner | Configuration | Destination | | ----------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------- | | Noodle deployment | ASSISTANT_MODEL_BASE_URL, ASSISTANT_MODEL, ASSISTANT_MODEL_API_KEY | Managed with noodle variables set / noodle secrets set | | Customer backend | NOODLE_SERVICE_URL, NOODLE_ASSISTANT_CLIENT_ID, NOODLE_ASSISTANT_CLIENT_SECRET | Backend environment or secret manager only |

allowedOrigins accepts exact origins. Production origins must be HTTPS; plain HTTP is accepted only for loopback development origins such as http://localhost:3000. Local MCP authoring remains available without login, but an external browser embed needs an active deployment before its deployment-bound client can be created.

Runtime requirements: Node.js 20+ for the server helper; the package ships ESM and CommonJS with full export conditions, so bundlers and plain Node resolve it without aliases or type shims.

The full, always-current integration guide (access modes, session response contract, framework notes, and troubleshooting) lives at https://docs.noodleseed.dev/guides/embedded-assistant. Authoring and deploying the server itself is @noodleseed/one (npm install -g @noodleseed/one).

Quick start

Install the package in the customer web application with that application's existing package manager. For example:

npm install @noodleseed/assistant

Author server.ts

Put customer identity and colors in the server's top-level branding option. Put only assistant-specific structure and treatment in embeddedAssistant({ presentation }):

import {
  embeddedAssistant,
  openAICompatible,
  secret,
  server,
  tool,
  variable,
  z,
} from "@noodleseed/one";

export default server(
  "acme_support",
  {
    title: "Acme Support",
    version: "1.0.0",
    branding: {
      name: "Acme Assistant",
      accent: "#5B4CF0",
      surface: "#FFFFFF",
      surfaceDark: "#15131A",
      mark: { uri: "https://assets.acme.example/mark.svg", alt: "Acme" },
      colorScheme: "auto",
    },
    assistant: embeddedAssistant({
      model: openAICompatible({
        baseUrl: variable("ASSISTANT_MODEL_BASE_URL"),
        model: variable("ASSISTANT_MODEL"),
        apiKey: secret("ASSISTANT_MODEL_API_KEY"),
      }),
      allowedOrigins: ["http://localhost:3000", "https://app.acme.example"],
      layout: {
        mode: "floating",
        position: "bottom-right",
        panelWidth: 520,
        panelMinHeight: 540,
        panelMaxHeight: 740,
        edgeOffset: 24,
      },
      behavior: { showTimestamps: true },
      labels: {
        welcomeHeading: "How can Acme help?",
        welcomeMessage: "Fast answers from the tools your team already uses.",
        composerPlaceholder: "Message Acme Support…",
        sessionReady: "Acme support is online",
      },
      presentation: {
        panel: {
          surface: "solid",
          elevation: "dramatic",
          border: "strong",
          radius: 20,
        },
        launcher: {
          icon: "chat",
          size: "lg",
          status: "session",
          effect: "pulse",
        },
        header: {
          mark: "status",
          badge: { text: "Support online", tone: "success", indicator: true },
        },
        composer: {
          leadingIcon: "brand-mark",
          sendIcon: "paper-plane",
          shape: "rounded",
        },
        messages: { userStyle: "accent", assistantStyle: "bubble" },
      },
    }),
  },
  [
    tool("status", {
      description: "Read the support service status.",
      input: z.object({}),
      output: z.object({ status: z.string() }),
      fulfil: () => ({ status: "operational" }),
    }),
  ],
);

presentation is a closed set of semantic primitives for the panel, launcher, header, composer, and messages. The Atlas-style product treatment above is the maximum supported customization level: it can change geometry, status decoration, controls, and message treatment without replacing the assistant's structure. It has no raw HTML, CSS, inline SVG, class-name, or callback field; markup-looking text stays text. presentation.panel.radius is a bounded panel-specific geometry override, not a second color or identity source. An HTTPS or packaged SVG referenced by branding.logo, branding.mark, or branding.avatar is a bounded asset, not inline renderer markup.

If presentation is omitted, the quiet premium baseline remains: solid panel, soft elevation, subtle border/motion, medium brand-mark launcher without status or effect, undecorated header, pill composer with no leading icon and an arrow-up send icon, and bubble user/plain assistant messages at comfortable width. Partial objects merge with those defaults.

Configure and deploy

Configure the model through Noodle managed config, validate, and deploy the assistant-enabled server before creating the embed client:

noodle variables set ASSISTANT_MODEL_BASE_URL --scope env --org acme --app support --env prod --value https://model.example/v1
noodle variables set ASSISTANT_MODEL --scope env --org acme --app support --env prod --value your-model
noodle secrets set ASSISTANT_MODEL_API_KEY --scope env --org acme --app support --env prod --from-env ASSISTANT_MODEL_API_KEY
noodle check --target embedded-assistant
noodle deploy --org acme --app support --env prod

Then create the deployment-bound client:

noodle assistant clients create --name web --org acme --app support --env prod

The command saves { clientId, clientSecret } to a mode-0600 file and prints its path, never the secret. Move those values to the customer backend's secret manager without printing or committing them. A framework route can exchange the current signed-in user like this:

import { createAssistantSession } from "@noodleseed/assistant/server";

export async function POST(request: Request) {
  const user = await requireCurrentUser(request);
  const { context } = await request.json();
  const session = await createAssistantSession({
    serviceUrl: process.env.NOODLE_SERVICE_URL!,
    clientId: process.env.NOODLE_ASSISTANT_CLIENT_ID!,
    clientSecret: process.env.NOODLE_ASSISTANT_CLIENT_SECRET!,
    origin: process.env.PUBLIC_APP_ORIGIN!,
    user: { id: user.id, email: user.email, roles: user.roles },
    context,
    preferences: { locale: user.locale, timeZone: user.timeZone },
  });
  return Response.json(session);
}

Then add the framework-neutral element:

import "@noodleseed/assistant";
<noodle-assistant
  session-endpoint="/api/assistant/session"
  theme="auto"
></noodle-assistant>

Or use React:

import { NoodleAssistant } from "@noodleseed/assistant/react";

<NoodleAssistant sessionEndpoint="/api/assistant/session" theme="auto" />;

For a customer-owned renderer, subscribe to the same turn and interaction stream without registering a custom element or touching browser storage:

import { createAssistantClient } from "@noodleseed/assistant/client";

const assistant = createAssistantClient({
  sessionEndpoint: "/api/assistant/session",
  clientContext: () => ({
    locale: navigator.language,
    timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
  }),
});

assistant.updateModelContext({
  content: [{ type: "text", text: "The time-off form is mounted." }],
  structuredContent: { widget: { name: "time-off", lifecycle: "mounted" } },
});

const unsubscribe = assistant.subscribe((event) => {
  renderAssistantEvent(event);
  if (event.event === "view_available") {
    renderRegisteredView(event.data.resourceUri, event.data.result);
  }
});
await assistant.sendMessage("Book next Thursday and Friday off");
await assistant.respond("interaction_123", { action: "accept" });
// Resolve one interaction once; alternatives are { action: 'decline' } or { action: 'cancel' }.

unsubscribe();

clientContext contains untrusted locale/timezone presentation hints and is evaluated for every turn. The client resolves a turn or interaction only after exactly one valid terminal done event followed by stream EOF. A truncated or malformed stream, duplicate done, or any frame after done is invalid_response; it never emits message_completed or interaction_completed for that response. updateContext remains the separate untrusted page-context channel used on the next session exchange. updateModelContext({ content, structuredContent }) replaces the complete renderer snapshot attached to each later message turn without starting a turn itself; updates do not merge with prior fields. The same method is available on <noodle-assistant> for cohesive surface snapshots such as mounted, submitted, cancelled, or dismissed. Model context is untrusted, per-turn data: it is not conversation history or authorization input, and credential-shaped or unbounded updates are rejected. Backend preferences are the signed-in user's saved locale/time-zone choices and outrank those hints. Only message turns retry once after an expired session; the client never auto-retries interaction decisions. A tool_proposed event carries a complete schema-projected review of the tool input and collected answers. For a connector-backed tool it also identifies the exact connector version, operation, and resolved arguments; confirmable flows contain at most that one connector operation. Sensitive fields may be redacted, but any other truncation or omission fails closed. Accept is bound to that server-held action and claims one execution attempt; drift fails closed, while decline/cancel resolve without execution. Normal terminal outcomes scrub private arguments and continuations immediately. Only an accepted action still in the executing state retains them during its one-hour unknown-outcome recovery window; expiry records a bounded interaction_outcome_unknown result and scrubs the payload. Without downstream idempotency this is not an exactly-once business-effect guarantee. A caller may explicitly repeat the same id and decision to retrieve the durable stored outcome without another execution attempt. At the manifest/runtime boundary, only confirm: true enables this gate; omitted or false preserves direct execution and standard annotations remain hints. TypeScript action helpers likewise require { confirm: true }; action/destructive/open-world hints alone never enforce approval. Capable bidirectional MCP transports render the same gate as standard form elicitation and fail closed when that exchange is unavailable. An input_requested event carries the message and portable form schema recorded by ctx.elicit. After the turn stream completes, a headless renderer answers it with respond(id, { action: 'accept', content }), or stops it with decline/cancel. The Web Component renders the same primitive as native controls. Accepted content is schema-validated before the runtime resumes, and the server-held continuation is never exposed to browser code. Schema-invalid content returns arg_invalid and leaves the same input interaction pending so the renderer can submit a corrected answer. Every interactive flow collects all elicited input before its first connector operation.

A completed widget-linked tool emits typed view_available data with its call/interaction id, tool, ui:// resource identity, optional title, and bounded/redacted public result. This is an availability signal, not proof of rendering. Map the identity to a component already trusted by your application; never fetch the ui:// URI or inject its resource HTML into your page. The standard element does not render it, but forwards the same detail as a DOM event:

element.addEventListener("assistant-view-available", (event) => {
  renderRegisteredView(event.detail.resourceUri, event.detail.result);
});

Assistant text renders as provider deltas arrive. If a turn finds an expired session, the component calls the same authenticated session endpoint and retries that unprocessed message once. Confirmations never replay across sessions. React applications may observe recovery and structured failures:

<NoodleAssistant
  sessionEndpoint="/api/assistant/session"
  onSessionExpired={() => reportAssistantRecovery()}
  onError={({ code, status, retryable }) =>
    reportAssistantError({ code, status, retryable })
  }
/>

createAssistantSession returns the opaque token, expiry, resolved non-secret configuration, and explicit turn/interaction endpoint URLs. Forward that response unchanged to the component or headless client.

The MCP server's top-level branding block is the portable deployment source for identity and colors shared by widgets and the assistant. The embedding application may use the typed appearance object when it needs exact control over assistant-specific roles:

<NoodleAssistant
  sessionEndpoint="/api/assistant/session"
  appearance={{
    light: {
      panel: { surface: "#FFFFFF", text: "#101828", border: "#E4E7EC" },
      composer: { surface: "#F9FAFB", text: "#101828", border: "#D0D5DD" },
      confirmation: { surface: "#F8FAFC", text: "#101828", border: "#CBD5E1" },
      primaryButton: { surface: "#635BFF", text: "#FFFFFF" },
    },
  }}
  onAppearanceWarning={(warning) => reportThemeWarning(warning)}
/>

The same object is available as element.appearance. It covers canvas, panel, header, assistant/user messages, composer, suggestions, confirmation, primary/secondary buttons, launcher, code, and MCP App frame roles in light and dark modes. Exact colors are preserved; insufficient contrast emits the typed assistant-appearance-warning event. Stable public CSS variables remain available for stylesheet-based integration:

noodle-assistant {
  --ns-assistant-accent: var(--app-primary);
  --ns-assistant-font-family: var(--app-font);
  --ns-assistant-panel-width: 440px;
}

For each matching region or token, precedence is the typed host appearance object, then host-provided slot content or a public CSS custom property, then compiled server presentation/branding, then built-in defaults. The public slots are launcher-icon, header-leading, header-actions, empty-state, composer-leading, composer-trailing, and conversation-footer; slotted host DOM replaces the renderer fallback for that region. Slots are a trusted embedding-page integration API, not a way to put HTML or callbacks in deployment configuration. Internal shadow-DOM selectors and classes are not public API.

Server branding controls customer name, themed logo/mark/avatar assets, semantic light/dark colors, density, radius, typography, and automatic theme. embeddedAssistant(...) controls floating/inline/drawer layout, position and dimensions, mobile and launcher/header/avatar/timestamp behavior, visible labels, suggested prompts, privacy/terms links, locale, and text direction. Named slots cover launcher/header/composer/footer extensions. Public methods include open, close, toggle, focusComposer, sendMessage, stop, reconnect, updateContext, updateModelContext, respond, confirmTool, and resetSession; lifecycle, message, tool-proposal, interaction-resolution, view-availability, context, and error events support application integration. confirmTool(id) remains the compatibility shorthand for respond(id, { action: 'accept' }).