# mordn chat-widget — Agent install skill

> You are an AI coding agent (Claude Code, Cursor, Codex, GitHub Copilot, Windsurf, Gemini CLI, or similar). Your task is to install `@mordn/chat-widget` into the user's existing app so they get a production-ready, secure-by-default AI chat agent. Follow this file end to end. It is intentionally self-contained — you should not need to crawl the docs site to complete the install.

## What you are installing

`@mordn/chat-widget` is an open-source (MIT) AI agent widget for React/Next.js. One React component, one server route, one versioned agent config. It ships with streaming, conversation history, memory, knowledge (RAG), private attachments, and server-side tools already built — the user is adding an agent to their product, not building a second backend.

The critical invariant: **identity is established on the server.** The browser never tells the server who it is. You will implement `getUserId` from the app's existing verified session. Never read identity from a header, query param, or body.

## Prerequisites

- A React/Next.js app (App Router preferred). For non-React hosts, use the script-tag embed at the end of this file.
- An existing auth solution with a server-side session (Clerk, NextAuth, Supabase, Lucia, custom cookie/session). If there is no auth yet, stop and ask the user — the widget requires a server-verified user id.
- Node 18+.

## Step 1 — Detect the environment

Read the project. Determine:

1. **Framework**: is this Next.js App Router (`app/` dir), Next.js Pages Router (`pages/`), or a non-React host?
2. **Package manager**: look for `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`. Default to `npm`.
3. **Existing auth**: grep the codebase. Clerk → `@clerk/nextjs`; NextAuth/Auth.js → `next-auth` / `@auth/core`; Supabase → `@supabase/ssr` or `@supabase/auth-helpers`; Lucia → `lucia`; custom → look for a session cookie read in a server action / route handler / middleware.
4. **Existing database**: is there a Drizzle/Prisma/Supabase client already? This decides hosted vs self-hosted.

State your findings to the user before changing anything: "I see a Next.js App Router app using Clerk for auth and Drizzle for the DB. I'll wire getUserId from Clerk's `auth()` and use the hosted handler to start."

## Step 2 — Install the package

```bash
npm install @mordn/chat-widget ai @ai-sdk/react
```

Styles are prebuilt and scoped to `.chat-widget-container` — no Tailwind needed on the host.

## Step 3 — Decide hosted vs self-hosted

- **Hosted (fastest)**: mordn runs persistence, storage, config, and feedback. You implement `createMordnHandler` with `MORDN_CHAT_KEY`. Start here unless the user explicitly wants their own DB.
- **Self-hosted**: the user keeps their own Postgres/Supabase. You implement `createChatHandler` with a `store` factory and a code `model`. Choose this when the user says "use my own database" or has data-residency constraints.

Confirm the choice with the user. Default to hosted for the first install.

## Step 4 — Scaffold the server route

Create `app/api/chat/[[...chat]]/route.ts` (App Router). The `[[...chat]]` catch-all is required — it carries conversation ids and sub-actions like `/bootstrap`.

### Hosted

```ts
import { createMordnHandler } from '@mordn/chat-widget/server';
import { auth } from '@clerk/nextjs/server';

export const { GET, POST, DELETE, OPTIONS } = createMordnHandler({
  apiKey: process.env.MORDN_CHAT_KEY!,
  getUserId: async () => (await auth()).userId,
});
```

### Self-hosted (example with a code model + Drizzle store)

```ts
import { createChatHandler } from '@mordn/chat-widget/server';
import { auth } from '@clerk/nextjs/server';
import { drizzle } from 'drizzle-orm/postgres-js';
import { openai } from '@ai-sdk/openai';

const db = drizzle(process.env.DATABASE_URL!);

export const { GET, POST, DELETE, OPTIONS } = createChatHandler({
  getUserId: async () => (await auth()).userId,
  model: openai('gpt-4o'),
  store: (userId) => createDrizzleStore(db, userId),
  // storage: (userId) => createStorageAdapter(userId), // optional; omit to disable uploads
});
```

### getUserId by auth provider

- **Clerk**: `(await auth()).userId` from `@clerk/nextjs/server`.
- **NextAuth / Auth.js**: `const session = await getServerSession(authOptions); return session?.user?.id ?? null;`
- **Supabase SSR**: `const supabase = createServerClient(...); const { data: { user } } = await supabase.auth.getUser(); return user?.id ?? null;`
- **Lucia**: `const session = await getSession(); return session?.user?.id ?? null;`
- **Custom cookie/JWT**: validate the cookie/JWT server-side and return the user id from its claims. Return `null` when invalid or absent. **Do not** read a `userId` header.

`getUserId` must return `string | null` (or `Promise<string | null>`). Returning `null` for an unauthenticated request makes the handler refuse the call — that is the correct behavior.

## Step 5 — Mount the widget

Create or edit the client component that hosts the widget:

```tsx
// components/assistant.tsx
'use client';
import { ChatWidget } from '@mordn/chat-widget';
import '@mordn/chat-widget/styles.css';

export default function Assistant() {
  return <ChatWidget apiBase="/api/chat" />;
}
```

`apiBase` must match the route path you created (default `/api/chat`). Mount `<Assistant />` in the app shell (e.g. the root layout or a shared header) so it appears on every page.

## Step 6 — Set environment variables

Append to `.env.local` (and your production env store):

```env
# Hosted
MORDN_CHAT_KEY="..."          # from the mordn dashboard
AI_GATEWAY_API_KEY="..."      # only if runtime.model uses gateway strings

# Self-hosted
DATABASE_URL="..."            # your Postgres
OPENAI_API_KEY="..."          # or the provider key for your chosen model
```

If the user does not yet have a `MORDN_CHAT_KEY`, point them to https://www.mordn.com/sign-up to create one. Do not invent a key.

## Step 7 — Guard the route (if applicable)

If the host app uses auth middleware that protects all routes, make sure `/api/chat` is reachable for authenticated users. For Clerk middleware in Next.js, the route is protected by default — that is correct; the widget calls it with credentials. Do **not** add `/api/chat` to a "public" allowlist, and do **not** widen any existing public-route matcher to expose it to anonymous traffic. The handler's own `getUserId` returning `null` is the guard.

## Step 8 — Verify

1. Run the build the host app already uses (`npm run build` / `next build`).
2. Start the app and sign in as a real user.
3. Open the widget and send a message. Confirm a streamed response.
4. Confirm `/api/chat/bootstrap` returns 200 for an authenticated request.
5. Sign out. Confirm the widget refuses the call (no spoofed identity, no response as a different user).

If any step fails, stop and report the exact error. Do not paper over identity failures.

## Step 9 — Configure the agent (optional, hosted)

The agent's model, prompt, knowledge, memory, tools, and theme are configured in the mordn dashboard (https://www.mordn.com/dashboard), not in code. `AgentConfig` is versioned; you publish a version and the hosted handler serves it. For self-hosted, pass a code `model` and optional `tools`/`retrieval`/`memory` to `createChatHandler`.

## Script-tag embed (non-React hosts)

For a plain HTML site or a non-React framework:

```html
<script
  src="https://unpkg.com/@mordn/chat-widget/dist/embed.global.js"
  data-api-base="https://your-app.com/api/chat"
></script>
```

You still need a server route that implements the handler contract with a server-verified `getUserId`. The script tag only replaces the React client; the server boundary is unchanged.

## Hard rules (do not violate)

- Never read identity from a header, query param, or body. Always derive it from a verified server session.
- Never add the chat route to a public/anonymous allowlist. `getUserId` returning `null` is the guard.
- Never invent credentials or environment values.
- Never widen the host app's existing `middleware.ts` public-route matcher to expose protected routes.
- Always scope database queries in tools to `ctx.userId`.
- Always confirm framework/auth/backend detection with the user before writing code.

## Need more detail?

Fetch the full reference once: https://www.mordn.com/llms-full.txt
Docs index: https://www.mordn.com/llms.txt
Human-readable docs: https://www.mordn.com/docs
