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

@wedobrandish/astro-chat

v0.6.4

Published

Streaming AI chat widget + API handlers for Astro: BYO LLM (Anthropic/OpenAI), proxy backends, or Brick brick-api-chatbots (JWT + SSE).

Downloads

983

Readme

@wedobrandish/astro-chat

Streaming AI chat widget + server handlers for Astro.

| Path | Route | Widget wire format | Use when | |------|--------|-------------------|----------| | BYO LLM | chatPost + anthropic() / openai() / proxy() | OpenAI-shaped SSE (wireFormat: 'openai', default) | OSS sites, custom backends, 15-minute Anthropic/OpenAI setup | | Brick production | brickChatPost from @wedobrandish/astro-chat/brick | Brick SSE (wireFormat: 'brick') | Published Brick templates → brick-api-chatbots (CloudFront + JWT + Bedrock) |

The browser always POSTs to your Astro route (/api/chat by default). Secrets stay server-side — never pass API keys or HMAC secrets to ChatWidget.

Contents

Quick start (Anthropic, ~15 minutes)

  1. Install

    npm install @wedobrandish/astro-chat

    Peers: astro ^4 || ^5 || ^6, zod ^4. No Anthropic npm SDK required (uses fetch).

  2. Env (Astro app, server-only — never PUBLIC_):

    ANTHROPIC_API_KEY=sk-ant-...
  3. Routesrc/pages/api/chat.ts:

    import type { APIRoute } from "astro";
    import { chatPost } from "@wedobrandish/astro-chat";
    import { anthropic } from "@wedobrandish/astro-chat/providers";
    
    export const prerender = false;
    
    const provider = anthropic({
      apiKey: import.meta.env.ANTHROPIC_API_KEY ?? "",
    });
    
    export const POST: APIRoute = async ({ request }) => {
      return chatPost(request, {
        knowledge: {
          businessName: "My Studio",
          businessType: "Design",
          description: "We build brands and websites.",
          faqs: [{ question: "What are your rates?", answer: "We quote per project." }],
        },
        provider,
      });
    };
  4. Widget — add ChatWidget (see Widget) with apiPath="/api/chat".

Icons default to Bootstrap Icons (bi bi-*). Optional stylesheet:

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css"
/>

Preview

astro-chat: launcher, teaser, and chat panel

Providers

Import from @wedobrandish/astro-chat/providers.

| Factory | Use case | |--------|-----------| | anthropic({ apiKey, model?, maxTokens? }) | Claude via Messages API; SSE translated to OpenAI-shaped deltas for the widget. | | openai({ apiKey, model?, maxTokens?, baseURL? }) | OpenAI or compatible servers (Ollama, Azure, etc.); stream passed through. | | proxy({ url, headers? }) | Your backend — body: { messages, site_id, business_context, stream: true } (Brick / FastAPI path). |

Keys stay server-side (import.meta.env.*), never in the browser.

Knowledge schema (SiteKnowledge)

Generic sites pass a small object; Brick templates can use brickConfigToKnowledge (see Migration).

interface SiteKnowledge {
  businessName: string;
  businessType?: string;
  description?: string;
  sections?: Record<string, unknown>;
  faqs?: Array<{ question: string; answer: string }>;
  contact?: { email?: string; phone?: string; address?: string; url?: string; nextSteps?: string[] };
  extraContext?: string;
}

buildSystemPrompt(knowledge) and buildSuggestionQueries(knowledge, max?) are exported from the main entry and @wedobrandish/astro-chat/context.

Guardrails

Default behavior (override with guardrails: { ... } on chatPost):

  • Max 24 turns, 8000 chars per message (after optional HTML strip).
  • Per-IP rate limit 30 requests / 60s (in-memory LRU, 10k keys). Set rateLimitPerIP: null to disable (recommended when your backend already limits, e.g. Brick).
  • Block patterns for basic prompt-injection phrases; stripHtml: true on input.

Bring your own store for multi-instance deploys: implement RateLimitStore from @wedobrandish/astro-chat/guardrails (e.g. Redis / Upstash — example in plan doc).

API route (chatPost)

New signature (recommended)

chatPost(request, {
  knowledge: siteKnowledge, // or () => siteKnowledge
  provider: anthropic({ apiKey: ... }), // or openai() / proxy()
  siteId?: "tenant-id",
  systemPrompt?: string | ((k: SiteKnowledge) => string),
  guardrails?: Partial<Guardrails>,
});

Legacy (deprecated, still works)

Same as v0.2.x — forwards to your URL with { messages, site_id, business_context, stream: true } and no package-level rate limit:

chatPost(request, {
  loadConfig: () => loadConfig() as ChatbotSiteConfig,
  apiUrl: import.meta.env.CHATBOT_API_URL,
  siteId: "optional",
});

Requires config.chatbot?.enabled === true (403 otherwise).

OpenAI example (src/pages/api/chat.ts)

import type { APIRoute } from "astro";
import { chatPost } from "@wedobrandish/astro-chat";
import { openai } from "@wedobrandish/astro-chat/providers";

export const prerender = false;

const provider = openai({
  apiKey: import.meta.env.OPENAI_API_KEY ?? "",
  // baseURL: "http://127.0.0.1:11434/v1", // Ollama example
});

export const POST: APIRoute = async ({ request }) => {
  return chatPost(request, {
    knowledge: { businessName: "Demo Co", description: "We ship widgets." },
    provider,
  });
};

Proxy / Brick example (generic OpenAI-shaped backend)

Not the production brick-api-chatbots stack. For CloudFront + JWT + session_id/message, use @wedobrandish/astro-chat/brick below.

import type { APIRoute } from "astro";
import { chatPost } from "@wedobrandish/astro-chat";
import { proxy } from "@wedobrandish/astro-chat/providers";
import { brickConfigToKnowledge } from "@wedobrandish/astro-chat/adapters/brick";
import { loadConfig } from "../lib/loadConfig";
import type { ChatbotSiteConfig } from "@wedobrandish/astro-chat/types";

export const prerender = false;

export const POST: APIRoute = async ({ request }) => {
  const config = loadConfig() as ChatbotSiteConfig;
  if (!config.chatbot?.enabled) {
    return new Response(JSON.stringify({ error: "Chat is disabled." }), { status: 403 });
  }
  return chatPost(request, {
    knowledge: () => brickConfigToKnowledge(config),
    provider: proxy({ url: import.meta.env.CHATBOT_API_URL! }),
    siteId: "my-tenant-id",
    guardrails: { rateLimitPerIP: null },
  });
};

Brick API chatbots (production)

For Brick templates talking to brick-api-chatbots (CloudFront → Lambda → Bedrock). This is the integration used by classic_property and the recommended path for published Brick sites.

Secrets stay on the server — declare env in astro.config.mjs, set in .env / Netlify Functions. Pass chatbot config only to the widget (no env props on the component).

What you get (v0.5+)

  • brickChatPost — origin check, JWT mint, body hash, upstream SSE passthrough
  • brickChatbotToWidgetProps — maps brick-config.jsonChatWidget props
  • Brick wire theme on ChatWidget when wireFormat="brick":
    • Header, user bubbles, and send button use template CSS vars (--color-accent, etc.)
    • Suggestions render in the message thread (not above the input)
    • Launcher morphs to a close icon when the panel is open
    • “Powered by Brick” badge
    • Progressive typewriter reveal + markdown (micromark), even when the upstream sends one large token chunk

Templates should emit theme tokens on :root (e.g. via generateThemeCSS() from brick-config.json) so the widget matches site colors.

1. Install

npm install @wedobrandish/astro-chat

Add Bootstrap Icons (launcher / header icons):

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css"
/>

2. Astro integration (astro.config.mjs)

Required so ChatWidget loads in Vite dev (micromark's development export otherwise pulls debug and breaks the client script):

import { defineConfig, envField } from 'astro/config';
import astroChat from '@wedobrandish/astro-chat/integration';

export default defineConfig({
  integrations: [astroChat()],
  env: {
    schema: {
      BRICK_CHATBOT_HMAC_SECRET: envField.string({ context: 'server', access: 'secret', optional: true }),
      BRICK_CHATBOT_UPSTREAM_URL: envField.string({ context: 'server', access: 'secret', optional: true }),
    },
  },
});

3. Env (.env / Netlify)

# .env — never commit real values
BRICK_CHATBOT_HMAC_SECRET=
BRICK_CHATBOT_UPSTREAM_URL=

Env names are also exported as BRICK_CHATBOT_ENV from @wedobrandish/astro-chat/brick.

4. Route — src/pages/api/chat.ts

import type { APIRoute } from 'astro';
import { getSecret } from 'astro:env/server';
import { brickChatPost } from '@wedobrandish/astro-chat/brick';
import { loadConfig } from '../../lib/loadConfig';

export const prerender = false;

export const POST: APIRoute = async ({ request }) => {
  const config = loadConfig();
  return brickChatPost(request, {
    chatbot: config.chatbot,
    secrets: {
      hmacSecret: getSecret('BRICK_CHATBOT_HMAC_SECRET'),
      upstreamUrl: getSecret('BRICK_CHATBOT_UPSTREAM_URL'),
    },
  });
};

5. Widget — layout

---
import ChatWidget from '@wedobrandish/astro-chat/components/ChatWidget.astro';
import { brickChatbotToWidgetProps } from '@wedobrandish/astro-chat/brick';
---
{config.chatbot?.enabled && (
  <ChatWidget {...brickChatbotToWidgetProps(config.chatbot)} />
)}

brickChatbotToWidgetProps sets wireFormat: 'brick', maps launcher icons, and applies astro-chat-root--bottom-left when configured.

6. brick-config.json

"chatbot": {
  "enabled": true,
  "siteId": "onbrick",
  "allowedOrigins": ["http://localhost:4321"],
  "welcomeMessage": "Hi! Ask me anything…",
  "suggestedPrompts": ["What services do you offer?"],
  "headerTitle": "Chat with us",
  "launcherPosition": "bottom-right",
  "launcherIcon": "chat"
}

siteId must match an enabled tenant in brick-api-chatbots.

Brick SSE contract

| Event | Widget behavior | |-------|-----------------| | token | Append text (typewriter reveal) | | error | Friendly banner; fatal codes hide the widget for the session | | done | Stream finished | | tool_call / tool_result | Consumed silently (no tool internals shown) |

Request body: { session_id: string, message: string }. Session id is stored in sessionStorage per tab.

Widget

In a layout or page:

---
import ChatWidget from '@wedobrandish/astro-chat/components/ChatWidget.astro';
import { CHAT_BOOTSTRAP_ICON_DEFAULTS, buildSuggestionQueries } from '@wedobrandish/astro-chat';

const knowledge = {
  businessName: 'Acme',
  faqs: [{ question: 'Hours?', answer: '9–5' }],
};
const suggestionQueries = buildSuggestionQueries(knowledge, 4);
---

<ChatWidget
  headerLine1="Online"
  headerLine2={knowledge.businessName}
  welcomeMessage="Hi! How can I help?"
  suggestionQueries={suggestionQueries}
  actionLinks={[{ title: 'Contact', url: '#contact', primary: true }]}
  apiPath="/api/chat"
  icons={{ ...CHAT_BOOTSTRAP_ICON_DEFAULTS, launcher: 'bi bi-chat-heart-fill' }}
/>

Use camelCase CSS keys in styles. For Brick configs, buildChatbotSuggestionQueries(config) remains available (deprecated).

Props

| Prop | Description | |------|-------------| | headerLine1 | Small header line (e.g. status: “Online”). | | headerLine2 | Main header title (e.g. business name). | | welcomeMessage | First assistant message when the panel opens. | | suggestionQueries | string[] — quick-send chip labels. | | actionLinks | Optional { title, url, primary? }[]. | | assistantAvatarUrl | Optional image URL for the header avatar (openai wire only; hidden in brick theme). | | apiPath | POST endpoint for SSE chat. Default /api/chat. | | wireFormat | 'openai' (default) or 'brick'. Use brickChatbotToWidgetProps() for Brick sites. | | class | Extra classes on the root element (e.g. astro-chat-root--bottom-left). | | style | Extra root styles. | | styles | Nested partials for header, body, footer, teaser, launcher. | | icons | Bootstrap classes or Astro icon components. | | (slots) | launcher-icon, close-icon, send-icon, avatar-fallback. | | launcher | mode, text, iconClass, ariaLabel, button, etc. |

Full styling and icon notes are unchanged from earlier releases; see sections below for Bootstrap vs Lucide.

Icons (Bootstrap by default)

  • Omit icons → defaults (bi bi-chat-dots-fill, bi bi-x-lg, bi bi-send-fill, bi bi-building).
  • Stringsicons={{ launcher: 'bi bi-chat-heart-fill', ... }}.
  • Curated listsCHAT_BOOTSTRAP_ICON_SUGGESTIONS, CHAT_BOOTSTRAP_ICON_DEFAULTS from @wedobrandish/astro-chat.

Optional: Lucide or other Astro components

| Approach | How | |----------|-----| | icons + component | icons={{ close: CloseIcon }} — pass CloseIcon, not <CloseIcon />. | | Named slot | <CloseIcon slot="close-icon" size={20} /> — slot wins over icons. |

launcher.iconClass overrides icons.launcher only.

Custom icons (Astro slots)

| Slot | Replaces | |------|----------| | launcher-icon | Floating trigger | | close-icon | Header close | | send-icon | Send button | | avatar-fallback | Header placeholder when no assistantAvatarUrl |

Migration

From 0.2.x (chatPost legacy)

  • No code change requiredchatPost(request, { loadConfig, apiUrl }) still works (deprecated).
  • Optional: switch to knowledge + proxy({ url }) and brickConfigToKnowledge for explicit guardrails and clearer OSS boundaries.
  • Prompt shape for generic SiteKnowledge differs slightly from the old flat JSON; snapshot-test your system prompt if you rely on byte-identical business_context for a hosted backend.

0.5.1 → 0.5.2

  • Required if you shipped 0.5.1: the integration silently failed to register the micromark alias. Bump to ^0.5.2 — no manual Vite alias needed.

0.5.0 → 0.5.2

  • Add integrations: [astroChat()] from @wedobrandish/astro-chat/integration in astro.config.mjs.
  • Bump dependency: "@wedobrandish/astro-chat": "^0.5.2".

Brick templates (0.4 → 0.5)

  • Replace bespoke chatbot UI + chatJwt.ts + custom /api/chat proxy with:
    • astroChat() in astro.config.mjs
    • brickChatPost on the route
    • ChatWidget + brickChatbotToWidgetProps() in your layout
  • BrickChatWidget.astro was removed in 0.5.0 — use ChatWidget with wireFormat: 'brick' instead.
  • Bump dependency: "@wedobrandish/astro-chat": "^0.5.2".

From 0.3.x

  • Bump to ^0.5.2 for Brick production helpers under @wedobrandish/astro-chat/brick.

FAQ

  • Brick template with hosted bot? Use @wedobrandish/astro-chat/brick — not the generic proxy() path.
  • Can I use this without a backend? Yes — use anthropic() or openai() with a server env key.
  • Self-hosted LLM? Yes — openai({ apiKey, baseURL }) pointing at an OpenAI-compatible endpoint.
  • Are conversations stored? No — the widget sends full history each request unless you add storage.
  • Is the API key exposed to the browser? No — only the Astro server reads env vars.

Exports

| Subpath | Key symbols | |---------|-------------| | @wedobrandish/astro-chat | chatPost, buildSystemPrompt, buildSuggestionQueries, legacy builders, types, widget helpers | | @wedobrandish/astro-chat/api | chatPost | | @wedobrandish/astro-chat/context | context builders | | @wedobrandish/astro-chat/types | SiteKnowledge, ChatbotSiteConfig, widget types | | @wedobrandish/astro-chat/validation | chatRequestSchema | | @wedobrandish/astro-chat/providers | anthropic, openai, proxy | | @wedobrandish/astro-chat/guardrails | guardrail types, createMemoryRateLimitStore, helpers | | @wedobrandish/astro-chat/adapters/brick | brickConfigToKnowledge | | @wedobrandish/astro-chat/brick | brickChatPost, brickChatbotToWidgetProps, createBrickChatTypewriter, mintJwt, BRICK_CHATBOT_ENV, types | | @wedobrandish/astro-chat/chat-widget-styles | appearance helpers | | @wedobrandish/astro-chat/bootstrap-icons | icon presets | | @wedobrandish/astro-chat/components/ChatWidget.astro | UI component |

Examples

See examples/anthropic-minimal, examples/openai-minimal, and examples/brick-proxy (install from repo; file:../../ to this package).

Before publishing (maintainers)

See PUBLISH.md. Quick check:

  1. Scope@wedobrandish/astro-chat, "publishConfig": { "access": "public" }.
  2. Dry runnpm pack --dry-run.
  3. Releasenpm run typecheck && npm test, then npm publish --access public.

Contributing

See CONTRIBUTING.md.

License

MIT — see LICENSE.