Guidance for Claude Code (claude.ai/code) when working in this repository.
pnpm install
pnpm build # Build all packages (Turborepo)
pnpm typecheck # Type-check all packages
pnpm check # Lint and format check (ultracite/biome)
pnpm fix # Auto-fix lint/format issues
pnpm knip # Check for unused exports/dependencies
pnpm test # Run all tests
pnpm validate # knip + check + typecheck + test + build. ALWAYS run before declaring a task done.
pnpm dev # Watch mode
# Per-package
pnpm --filter chat test
pnpm --filter @chat-adapter/slack build
pnpm --filter docs dev # Preview the docs site (chat-sdk.dev) locally- Install dependencies with
pnpm add, not by editingpackage.jsonby hand. sample-messages.mdfiles in adapter packages contain real-world webhook logs — useful when writing parsers or fixtures.- Commits must be signed and verified — see
.github/CONTRIBUTING.md. - We follow Conventional Commits (
feat:,fix:,docs:,chore:, optionally scoped). The release workflow's auto-PR useschore(release): version packages— don't reuse that exact subject for unrelated commits. - See the Ultracite section at the bottom for the in-code style rules Biome doesn't catch automatically.
pnpm monorepo, Turborepo orchestrated. All packages are ESM ("type": "module"), TypeScript, bundled with tsup.
packages/chat— core SDK (chatnpm package):Chatclass, types, mdast-based markdown utilitiespackages/adapter-{slack,teams,gchat,discord,telegram,whatsapp,github,linear,zoom}— platform adapterspackages/adapter-shared— utilities shared across adapterspackages/state-{memory,redis,ioredis,pg}— state adapterspackages/integration-tests— integration tests against real platform APIsapps/docs— fumadocs-based docs site (chat-sdk.dev)examples/nextjs-chat— example Next.js app
- Chat (
packages/chat/src/chat.ts) — main entry point; coordinates adapters and handlers. - Adapter — platform-specific implementation: webhook verification + parsing, normalized format conversion,
FormatConverterfor markdown ↔ platform AST. - StateAdapter — persistence for subscriptions, distributed locks, key/value cache, lists, and queues.
- Thread — conversation thread with
post(),subscribe(),startTyping(),setState(), etc. - Message — normalized message:
text,formatted(mdast AST),raw(platform-specific).
{adapter}:{channel}:{thread} — e.g. slack:C123ABC:1234567890.123456. Some adapters base64-encode IDs that contain delimiters (Teams, Google Chat).
- Platform →
/api/webhooks/{platform} - Adapter verifies, parses, calls
chat.handleIncomingMessage() Chatacquires a thread lock, then routes toonSubscribedMessage,onNewMention, oronNewMessagehandlers depending on context.- Handler receives
ThreadandMessage.
Messages use mdast as the canonical format. Each adapter's FormatConverter provides:
toAst(platformText)— platform → mdastfromAst(ast)— mdast → platformrenderPostable(message)—PostableMessage→ platform string
packages/chat/src/mock-adapter.ts exports test utilities:
createMockAdapter(name)—Adapterwithvi.fn()mockscreateMockState()— in-memory subscriptions/locks/cachecreateTestMessage(id, text, overrides?)mockLogger
For production-traffic-driven testing, see packages/integration-tests/fixtures/replay/README.md (recording / export / replay workflow). Recordings are tagged with the deployed git SHA and exported via pnpm recording:list / pnpm recording:export <session-id> from examples/nextjs-chat.
User-facing docs live in apps/docs/content/docs/ (rendered at chat-sdk.dev/docs). When changing behavior, public APIs, or env vars, update the relevant page in the same PR. Preview locally with pnpm --filter docs dev.
Uses Changesets with fixed versioning (every package shares one version). Every PR that changes a package's behavior must include a changeset (pnpm changeset). Full rules in .github/CONTRIBUTING.md.
Key env vars (see turbo.json for the full list):
SLACK_BOT_TOKEN,SLACK_SIGNING_SECRETTEAMS_APP_ID,TEAMS_APP_PASSWORD,TEAMS_APP_TENANT_IDGOOGLE_CHAT_CREDENTIALSorGOOGLE_CHAT_USE_ADCWHATSAPP_ACCESS_TOKEN,WHATSAPP_APP_SECRET,WHATSAPP_PHONE_NUMBER_ID,WHATSAPP_VERIFY_TOKENREDIS_URL— Redis state adapterPOSTGRES_URL/DATABASE_URL— PostgreSQL state adapterBOT_USERNAME— default bot username
.github/CONTRIBUTING.md— dev setup, signed commits, conventional commits, changesets, docs preview, building your own adapter.github/SUPPORT.md— where to send help/usage questions.github/SECURITY.md— private vulnerability reporting.github/ISSUE_TEMPLATE/— bug, feature, docs, and adapter-request templates.github/CODEOWNERS—@vercel/chat-sdkowns everything; release plumbing is locked to@cramforce
This project uses Ultracite (Biome-based). pnpm check / pnpm fix run it across the monorepo. Most issues auto-fix.
Beyond what Biome enforces:
- Type safety — prefer
unknownoverany;as constfor literals; named constants over magic numbers. - Modern JS/TS —
for...ofover.forEach;?.and??;constby default; template literals; destructure. - Async — always
awaitreturned promises; no async Promise executors. - React — function components only; hooks at top level; stable
keys (prefer IDs over indices); no components defined inside other components; semantic HTML and ARIA;<Image>over<img>; ref-as-prop in React 19+. - Errors — throw
Errorobjects with descriptive messages; early returns over nested conditionals; noconsole.log/debugger/alertin shipped code. - Performance — top-level regex literals; no spread-in-accumulator loops; specific imports over barrel files.
- Security —
rel="noopener"ontarget="_blank"; avoiddangerouslySetInnerHTML; nevereval()or assign todocument.cookie.