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

@cms.ai/brand_brain

v0.1.0

Published

Client-side SDK for building CMS.ai Brand Brain (Guide) experiences into your own website UI.

Downloads

256

Readme

@cms.ai/brand_brain

Client-side SDK for building CMS.ai Brand Brain (Guide) experiences directly into your own website UI — your own chat, your own layout — instead of the drop-in overlay. It handles the visitor session, streaming chat across every guide skill, structured deliverables (documents, recommendations, how-tos, playlists, business cases, solution designs, assessments), conversation history, and lead capture, all against your branded CMS.ai host.

Framework-agnostic core + an optional React binding.

Machine-readable reference: llms.txt ships in this package — point your AI coding tool at it for the full wire contract.

Install

npm install @cms.ai/brand_brain

Quick start (React)

import { useGuide } from "@cms.ai/brand_brain/react";

function Chat() {
  const { messages, skillTurn, gate, ask, submitGate, retryLastAsk, isReady, isStreaming } = useGuide({
    baseUrl: "https://guide.yourcompany.com", // your branded CMS.ai host
    slug: "default", // which guide to load
  });

  return (
    <div>
      {messages.map((m) => (
        <p key={m.id} data-role={m.role}>
          {m.content}
        </p>
      ))}

      {/* Structured deliverable for the current turn — see "Rendering skills" */}
      {skillTurn && skillTurn.documents.length > 0 && (
        <ul>
          {skillTurn.documents.map((d) => (
            <li key={d.id}>
              <a href={d.url}>{d.title}</a>
            </li>
          ))}
        </ul>
      )}

      {/* The guide may ask for an email before finishing a turn */}
      {gate && (
        <form
          onSubmit={async (e) => {
            e.preventDefault();
            const email = new FormData(e.currentTarget).get("email") as string;
            const result = await submitGate(email);
            if (result.success && !result.needsVerification) await retryLastAsk();
          }}
        >
          <input name="email" type="email" required placeholder="Work email" />
          <button type="submit">Continue</button>
        </form>
      )}

      <button disabled={!isReady || isStreaming} onClick={() => ask("What can you help me with?")}>
        Ask
      </button>
    </div>
  );
}

useGuide bootstraps the client, streams replies into messages, folds every structured skill event into skillTurn, surfaces email gates on gate, and cleans the session up on unmount. Pass onEvent in the config to observe every raw stream event.

Quick start (vanilla JS/TS)

import { createSkillTurn, initGuide, reduceSkillEvent } from "@cms.ai/brand_brain";

const guide = await initGuide({
  baseUrl: "https://guide.yourcompany.com",
  slug: "default",
});

let turn = createSkillTurn();
for await (const event of guide.ask("How does pricing work?")) {
  if (event.type === "text") {
    appendToChatBubble(event.delta); // your render fn
  } else if (event.type === "skill") {
    turn = reduceSkillEvent(turn, event); // fold structured payloads into renderable state
    renderSkill(turn);
  } else if (event.type === "gate") {
    const email = await promptForEmail(); // your UI
    await guide.submitGate(email);
  }
}

baseUrl and where this runs

baseUrl should be your branded CMS.ai host on your own root domain (e.g. guide.yourcompany.com, the CNAME we provision). Same-site requests mean the visitor identity cookies attach in every browser with no third-party-cookie caveats.

If your app runs on a different site than the guide host (e.g. a preview on *.lovable.app talking to *.guides.navless.ai), everything still works — chat, skills, lead capture — but Safari and other browsers that block third-party cookies will treat the visitor as anonymous on each page load (no cross-visit identity, no conversation resume). Build the UI so nothing depends on the visitor being remembered, and move to a same-site branded host for production.

The ask() event stream

ask() returns an async iterator of GuideStreamEvents:

| event.type | Meaning | | ---------------------------------------- | ---------------------------------------------------------------------------------------------- | | run-started / run-finished | Turn lifecycle | | message-start / text / message-end | The streamed chat reply (text.delta is the token chunk) | | skill | A structured skill payload, fully typed and discriminated by name — see below | | gate | The guide needs an email before continuing — submitGate(email), then re-send the message | | error | A run or skill error — { message, code?, retryable?, retryHint? } | | skill-unknown | Forward-compat escape hatch for structured events newer than this SDK version — safe to ignore |

Every turn runs exactly one skill (plain answers are the answer skill). Skill routing is automatic — the server picks the skill from the message. Force one with guide.ask(text, { forcedSkill: SkillResponseType.HowTo }).

A turn's skill events always follow the same lifecycle:

skill_start { skill, messageId }        ← which skill is running
  …payload events (see per-skill list)…
skill_end   { skillInstanceId? }        ← deliverable persisted; id usable with guide.skills.*

You rarely need to handle the events by hand: reduceSkillEvent (used internally by useGuide) folds them into a GuideSkillTurn — one flat, renderable state object.

Rendering skills

What fills in on GuideSkillTurn depends on skillTurn.skill. Everything not listed stays at its empty default.

answer — grounded Q&A (always enabled)

The reply streams as text events into the chat bubble. Alongside it:

  • documents: GuideDocument[] — the sources behind the answer. Render as source/citation cards: title, optional url, thumbnailUrl, and citationIndex matching [N] marks in the reply text.
  • followUpRecommendations: SkillRecommendation[] — follow-up pills. On click: ask(rec.prompt, { forcedSkill: rec.skill }).
  • ctas: SkillCTA[] — render the first CTA below the response. Discriminated on kind: url (button opening href), form (in-guide form formId), product (card linking to href), scheduler (button opening the url meeting link).

howto — step-by-step guide

  1. howToOutline arrives first: { title, steps: [{ id, title }] }. Render immediately as a numbered skeleton.
  2. howToSteps then replaces the skeleton with detailed steps: { id, title, description, icon?, content? }. content is an optional source card (title, url, excerpt, thumbnailUrl).

Suggested UI: numbered checklist with step cards; a progress affordance (steps completed) works well. The persisted instance (skillInstanceId) supports saving progress via guide.skills.update.

recommend — content recommendations

contentRecommendations: ContentRecommendation[] — cards with title, rationale, and confidence (high/medium/low). On click: ask(rec.prompt, { forcedSkill: rec.targetSkill }).

playlist — curated content playlist

  • playlistHeader: { title, description }
  • playlistItems: PlaylistItem[] — content cards: title, description, contentType (e.g. video_youtube, pdf, url), url, thumbnailUrl. rationale streams in late per item — render it when it appears.

diagnose — self-assessment

  • diagnoseHeader: { targetTopic, subtitle, questionCount, subtype? }
  • diagnoseQuestions append one at a time — render a stepper (questionCount sizes it). Each question has category, questionText, and single-choice options (label A–D, text, optional score).

There is no correct answer — it's a self-assessment. In standalone mode, use score per option to compute per-category results for a summary chart. In context_gathering mode (subtype), after the user answers, send a follow-up message summarizing their answers (see followUpSkill).

businesscase — generated business case

  1. businessCaseFormats + businessCaseDrafts arrive: render the formats (label, description, icon) as a chooser.
  2. When the user picks one, send its label as a new ask(...). The draft then streams into businessCaseContent[subType] as markdown — render with your markdown component.

solutiondesign — architecture diagram

  • solutionDesignHeader: { title, summary }
  • solutionDesignNodes / solutionDesignEdges — a flowchart: nodes have label, description, nodeType (process, decision, datastore, external, trigger, start, end), and a Lucide icon name; edges connect node ids with optional labels. Render with any diagram library — or fall back to a grouped list of nodes with their descriptions.

The email gate

Guides can be configured to require an email before delivering a skill. Mid-turn you'll get a gate event (the stream then ends), and in React gate becomes non-null. Flow:

  1. Render an email form.
  2. await submitGate(email) — the pending gate's context is attached automatically.
  3. If result.needsVerification is true, tell the user to check their inbox (magic link). Otherwise call retryLastAsk() to re-send the message and finish the turn.

Theming

guide.theme (a BrandKitTheme) carries the brand's design tokens as CSS color strings (hex or oklch(...)), named shadcn-style: primary, primaryForeground, secondary, secondaryForeground, background, foreground, muted, mutedForeground, border, input, ring. All optional — absent tokens mean "keep your default".

const { guide } = useGuide({ baseUrl, slug: "default" });

const style = guide?.theme
  ? ({
      "--primary": guide.theme.primary,
      "--primary-foreground": guide.theme.primaryForeground,
      "--background": guide.theme.background,
      "--foreground": guide.theme.foreground,
    } as React.CSSProperties)
  : undefined;

return <div style={style}>…</div>;

Also on guide: logoUrl, faviconUrl, heroImageUrl, companyName, and customization (per-guide copy overrides — welcomeMessage, chatInputPlaceholders, suggestionsTitle, …). Use customization.welcomeMessage as the empty-state greeting and chatInputPlaceholders as rotating input placeholders. skillIconStyle customizes skill icon colors (mode: default | mono | per_skill).

API

initGuide(config)GuideClient. Config: baseUrl, slug, optional domain (defaults to the baseUrl hostname, which is the registered account domain), consent ("auto" | { functional?, analytical? } | false), trackLoad (default true), fetch.

GuideClient:

  • guide — resolved metadata (id, accountId, theme, customization, …)
  • ask(message, { forcedSkill?, signal? }) — stream a reply (see above)
  • messages / reset() — in-memory conversation history
  • setConsent({ functional?, analytical? }) / getStatus()
  • submitGate(email, options?) — capture a lead (magic link when verification is required)
  • track(eventType, properties?) / trackPageView(url?) — analytics (call trackPageView on SPA route changes)
  • endSession() — end the visit (call on pagehide; useGuide does this on unmount)
  • conversationsgetOrCreate(), list(), get(id, { cursor?, limit? }), delete(id)
  • skillslistHistory(), get(id), update(id, data), selectDraft(id), markShared(id), importShared(id)

Core helpers: createSkillTurn() / reduceSkillEvent(turn, event) fold skill events into a GuideSkillTurn outside React.

useGuide(config) (from @cms.ai/brand_brain/react) returns { client, guide, messages, skillTurn, gate, isReady, isStreaming, error, ask, submitGate, retryLastAsk, reset }. Config additionally accepts onEvent(event).

Notes

  • The SDK calls the existing public CMS.ai API — it introduces no new endpoints.
  • @navless/* internals are bundled in; the published package has no @navless/* runtime dependencies. react / react-dom are optional peers used only by @cms.ai/brand_brain/react.