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

@netbirdio/explain

v0.1.3

Published

Full-stack AI assistant library with React frontend components and Node.js backend handler

Readme

@netbirdio/explain

AI-powered "Explain" assistant for React apps. Users click on UI elements and get contextual AI explanations via a chat panel.

The package has two entry points:

  • @netbirdio/explain/client — React components (provider, chat panel, floating button)
  • @netbirdio/explain/server — Node.js handler that proxies requests to Anthropic or OpenAI

No CSS framework required — the package is fully self-contained with inline styles and CSS custom properties.

Installation

npm install @netbirdio/explain

Peer dependencies: react >=18, react-dom >=18.

For local development as a workspace package, add it to your package.json:

{
  "dependencies": {
    "@netbirdio/explain": "file:./packages/@netbirdio/explain"
  }
}

If you're using Next.js, add to next.config.js:

module.exports = {
  transpilePackages: ["@netbirdio/explain"],
};

Client

Setup

Wrap your app with AIAssistantProvider:

import { AIAssistantProvider } from "@netbirdio/explain/client";

export default function App({ children }) {
  return (
    <AIAssistantProvider
      endpoint="http://localhost:3080/api/ai/chat"
      apiKey="your-api-key"
    >
      {children}
    </AIAssistantProvider>
  );
}

This renders the floating action button and chat panel automatically. No CSS imports are needed — the provider injects all required styles via CSS custom properties.

Props

| Prop | Type | Required | Description | | ---------- | ----------- | -------- | ----------------------------------- | | endpoint | string | Yes | URL of the AI chat API | | apiKey | string | No | Bearer token sent with each request | | children | ReactNode | Yes | Your application |

Marking elements as explainable

Add the data-nb-explain attribute to any element you want users to be able to click on in explain mode:

<div data-nb-explain>
  <Label>Name</Label>
  <Input value={name} onChange={setName} />
</div>

When clicked, the library extracts a label from the element (first <label>, heading, or text content) and sends it as the query.

You can also pass a custom label directly:

<div data-nb-explain="Database connection string">...</div>

Documentation URLs

Attach docs to an element or its parent so the AI can reference them:

<div data-nb-explain-docs='["https://docs.example.com/resources"]'>
  <div data-nb-explain>...</div>
</div>

Excluding elements

Use data-nb-explain-ignore to prevent an element from being selectable in explain mode:

<button data-nb-explain-ignore onClick={enterExplainMode}>
  Explain
</button>

Setting page/modal context

Use the useAIAssistant hook to provide context that gets included in every query:

import { useAIAssistant } from "@netbirdio/explain/client";

function MyModal() {
  const { setExplainContext, clearExplainContext } = useAIAssistant();

  useEffect(() => {
    setExplainContext({
      modalName: "Add Resource",
      pageName: "Networks",
      docsUrls: ["https://docs.example.com/networks"],
    });
    return () => clearExplainContext();
  }, []);

  return <div data-nb-explain>...</div>;
}

This produces queries like: Explain "Name" on Add Resource modal in Networks.

Hook API

useAIAssistant() returns:

| Method / Property | Description | | ------------------------ | ------------------------------------------ | | openChat(query?) | Open the chat panel, optionally with a query | | closeChat() | Close the chat panel | | isChatOpen | Whether the chat panel is open | | explainMode | Whether explain mode is active | | enterExplainMode() | Activate explain mode (click-to-explain) | | exitExplainMode() | Deactivate explain mode | | setExplainContext(ctx) | Set page/modal context for queries | | clearExplainContext() | Clear the context |

Theming

The package ships with a dark theme out of the box. All visual properties are controlled via CSS custom properties (--nb-explain-*) injected into :root by the provider. Override any of them in your own CSS to match your app's look and feel.

How it works

  1. AIAssistantProvider injects a <style> tag with default values for all --nb-explain-* variables.
  2. Components use inline styles that reference these variables (e.g., background: var(--nb-explain-bg)).
  3. Your CSS can override any variable — later declarations on :root or more specific selectors win.

Overriding in CSS

Add a stylesheet or <style> block after the provider mounts (or use higher specificity):

:root {
  /* Change to a light theme */
  --nb-explain-bg: #ffffff;
  --nb-explain-bg-subtle: rgba(0, 0, 0, 0.04);
  --nb-explain-bg-hover: rgba(0, 0, 0, 0.06);
  --nb-explain-border: rgba(0, 0, 0, 0.12);
  --nb-explain-text: #1a1a1a;
  --nb-explain-text-muted: #6b7280;
  --nb-explain-text-dim: #9ca3af;
  --nb-explain-accent: #2563eb;
  --nb-explain-accent-hover: #3b82f6;
  --nb-explain-user-bg: #2563eb;
  --nb-explain-user-text: #ffffff;
}

Full variable reference

| Variable | Default | Description | | --------------------- | -------------------------------- | ---------------------------------- | | --nb-explain-bg | #0a0a0f | Chat panel background | | --nb-explain-bg-subtle | rgba(255,255,255,0.06) | Input field & assistant message bg | | --nb-explain-bg-hover | rgba(255,255,255,0.08) | Hover state background | | --nb-explain-border | rgba(255,255,255,0.1) | Border color | | --nb-explain-text | #f0f0f5 | Primary text color | | --nb-explain-text-muted | #9ca3af | Secondary text color | | --nb-explain-text-dim | #6b7280 | Placeholder / tertiary text | | --nb-explain-accent | #eab308 | Accent color (buttons, icons) | | --nb-explain-accent-hover | #facc15 | Accent hover state | | --nb-explain-accent-glow | rgba(234,179,8,0.15) | Accent glow (avatar backgrounds) | | --nb-explain-user-bg | #4f46e5 | User message bubble background | | --nb-explain-user-text | #ffffff | User message text color | | --nb-explain-user-glow | rgba(79,70,229,0.25) | User avatar glow | | --nb-explain-radius | 12px | Panel border radius | | --nb-explain-radius-sm | 8px | Message bubble border radius | | --nb-explain-radius-xs | 6px | Button border radius | | --nb-explain-font | system font stack | Font family for all components | | --nb-explain-shadow | large drop shadow | Chat panel box shadow | | --nb-explain-banner-bg | rgba(234,179,8,0.92) | Explain mode banner background | | --nb-explain-banner-text | #000000 | Explain mode banner text | | --nb-explain-error-text | #f87171 | Error message text |

Data attributes

| Attribute | Description | | -------------------- | ------------------------------------------------------------------ | | data-nb-explain | Marks element as explainable. Value can be a custom label or boolean. | | data-nb-explain-docs | JSON array of documentation URLs for context. | | data-nb-explain-ignore | Element is non-interactive during explain mode. |


Server

The server module provides a framework-agnostic handler that proxies chat requests to Anthropic or OpenAI.

With Express

import express from "express";
import { createAssistant } from "@netbirdio/explain/server";

const assistant = createAssistant({
  provider: "anthropic",
  apiKey: process.env.ANTHROPIC_API_KEY!,
  model: "claude-sonnet-4-20250514",
  systemPrompt: "You are a helpful assistant for MyApp.",
});

const app = express();
app.use(express.json());

app.post("/api/ai/chat", assistant.handler({ apiKey: "your-api-key" }));

app.listen(3080);

With plain Node.js HTTP

import http from "http";
import { createAssistant } from "@netbirdio/explain/server";

const assistant = createAssistant({
  provider: "openai",
  apiKey: process.env.OPENAI_API_KEY!,
  model: "gpt-4o",
});

const handle = assistant.handler({ apiKey: "your-api-key" });

http.createServer(handle).listen(3080);

Programmatic usage (no HTTP)

const assistant = createAssistant({
  provider: "anthropic",
  apiKey: process.env.ANTHROPIC_API_KEY!,
});

const { reply } = await assistant.chat({
  messages: [{ role: "user", content: "What is a network resource?" }],
});

createAssistant(config)

| Option | Type | Required | Default | | -------------- | ----------------------------- | -------- | ------------------------ | | provider | "anthropic" | "openai" | Yes | — | | apiKey | string | Yes | — | | model | string | No | Provider default | | systemPrompt | string | No | Generic assistant prompt |

assistant.handler(opts?)

Returns a (req, res) => Promise<void> handler compatible with Express, plain http, and similar frameworks.

| Option | Type | Description | | -------- | -------- | -------------------------------------------------------------- | | apiKey | string | If set, requires Authorization: Bearer <key> on requests |

API contract

Request POST /api/ai/chat

{
  "messages": [
    { "role": "context", "content": "Docs: https://..." },
    { "role": "user", "content": "Explain network routes" }
  ]
}

Response

{
  "reply": "Network routes allow you to..."
}

Error codes: 400 (bad request), 401 (unauthorized), 502 (LLM error).


Standalone dev server

The server/ directory in the dashboard repo contains a ready-to-run Express server for local development.

cd server
cp .env .env.local   # edit with your LLM API key
npm install
node index.js

Environment variables:

| Variable | Default | Description | | ------------------- | -------------------------- | ---------------------- | | PORT | 3080 | Server port | | API_KEY | nb-ai-dev-key-change-me | Bearer token for auth | | LLM_PROVIDER | anthropic | anthropic or openai | | ANTHROPIC_API_KEY | — | Anthropic API key | | ANTHROPIC_MODEL | claude-sonnet-4-20250514 | Model ID | | OPENAI_API_KEY | — | OpenAI API key | | OPENAI_MODEL | gpt-4o | Model ID | | SYSTEM_PROMPT | Generic NetBird prompt | System prompt for the LLM |


Full integration example

// layout.tsx — wrap app with provider
import { AIAssistantProvider } from "@netbirdio/explain/client";

export default function Layout({ children }) {
  return (
    <AIAssistantProvider
      endpoint={process.env.NEXT_PUBLIC_AI_SERVER_URL || "http://localhost:3080/api/ai/chat"}
      apiKey={process.env.NEXT_PUBLIC_AI_API_KEY || "nb-ai-dev-key-change-me"}
    >
      {children}
    </AIAssistantProvider>
  );
}
// MyModal.tsx — add explain support to a modal
import { useAIAssistant } from "@netbirdio/explain/client";
import { Sparkles } from "lucide-react";

function MyModal() {
  const { setExplainContext, clearExplainContext, explainMode, enterExplainMode, exitExplainMode } =
    useAIAssistant();

  useEffect(() => {
    setExplainContext({
      modalName: "Add Resource",
      pageName: "Networks",
      docsUrls: ["https://docs.netbird.io/manage/networks"],
    });
    return () => clearExplainContext();
  }, []);

  return (
    <div data-nb-explain>
      <button
        data-nb-explain-ignore
        onClick={() => (explainMode ? exitExplainMode() : enterExplainMode())}
      >
        <Sparkles size={13} />
        {explainMode ? "Click an element..." : "Explain"}
      </button>

      <div data-nb-explain>
        <label>Name</label>
        <input placeholder="e.g., Postgres Database" />
      </div>

      <div data-nb-explain>
        <label>Address</label>
        <input placeholder="e.g., 10.0.0.1" />
      </div>
    </div>
  );
}

License

BSD-3-Clause