Skip to content

Navigation Menu

Sign in
Sign up

Repository files navigation

client-intake-agent

A deployable AI intake agent that runs the first pass of your discovery calls for you.

Instead of a dead contact form that dumps name / email / message into your inbox, this is a short, friendly chat that qualifies the lead — asks the right follow-ups, extracts structured answers, scores the opportunity, and drops a hot / warm / cold summary into your database and/or a webhook.

It's a Next.js template you clone, edit one config file, and deploy. The shipped example is an AI-consulting discovery intake (the author's own), but the whole flow is driven by a single typed config — retool it for any business.


Why this exists

Discovery calls are where consultants and agencies burn the most time on the worst-fit leads. The first 10 minutes of nearly every call are the same questions: what do you do, what's the problem, how soon, what's the budget, are you the decision-maker. A plain web form can't ask a good follow-up, so you end up on a call to find out the budget was 500ドル.

This template does that first pass automatically:

Plain contact form client-intake-agent
Interaction Static fields A real conversation that adapts to answers
Missing / vague answers You find out on the call The agent asks a follow-up on the spot
Output Free-text blob Structured JSON keyed by field
Prioritization You read every submission Auto-scored hot / warm / cold with a reason breakdown
Where it goes Your inbox Supabase table + webhook (Zapier, n8n, CRM, ...)

Example. A visitor chats through the intake; the agent extracts:

{
 "name": "Dana Reyes",
 "company": "Northwind Logistics",
 "email": "dana@northwind.co",
 "problem": "Manually re-keying supplier invoices into our ERP",
 "team_size": 40,
 "timeline": "immediately",
 "budget": "20ドルk-50ドルk",
 "decision_maker": true
}

...and scores it before it ever hits your inbox:

{
 "score": 100,
 "maxScore": 100,
 "tier": "Hot",
 "breakdown": [
 { "label": "Ready to start now", "points": 30 },
 { "label": "Strong budget", "points": 30 },
 { "label": "Is a decision maker", "points": 20 },
 { "label": "Team of 10+", "points": 10 },
 { "label": "Described a concrete problem", "points": 10 }
 ]
}

How it works

Visitor ──chat──▶ Next.js UI ──POST /api/chat──▶ Claude (tool use)
 │
 extracts fields via the `submit_intake` tool
 │
 ┌────────────────┼────────────────┐
 score lead store in Supabase POST webhook
 (pure, typed) (optional) (optional, signed)

Each turn is one turn-based (non-streaming) call to Claude with a submit_intake tool whose JSON schema is generated from your configured fields. The agent chats normally until it has everything it needs, then calls the tool — that's the signal to score the lead and deliver it. Scoring is a pure, fully-tested function, so the qualification logic is deterministic and doesn't depend on the model.

  • Model: Claude Sonnet 5 by default (override with ANTHROPIC_MODEL).
  • Extraction: Anthropic tool use, schema derived from intake.config.ts.
  • Persistence: Supabase (optional).
  • Delivery: signed webhook (optional). Add your own destinations easily.

Quick start

# 1. Clone (or use this repo as a GitHub template) and install
git clone https://github.com/brutusdev0/client-intake-agent.git
cd client-intake-agent
pnpm install
# 2. Configure environment
cp .env.example .env
# → set ANTHROPIC_API_KEY (required)
# → optionally set LEAD_WEBHOOK_URL / Supabase vars
# 3. Run
pnpm dev
# open http://localhost:3000

With no webhook or database configured, completed leads are logged to the server console — so you can try the full flow with just an Anthropic key.


Configure your intake

Everything lives in intake.config.ts. You edit this file, not the code under lib/. It has four parts:

export const intakeConfig: IntakeConfig = {
 business: { name: 'Rivet AI Consulting', agentName: 'Ava' },
 greeting: "Hi! I'm Ava... what should I call you, and what company are you with?",
 persona: 'Warm, concise, curious. Speaks like a consultant on a discovery call.',
 fields: [
 { key: 'name', label: 'Name', type: 'text', description: '...', required: true },
 {
 key: 'budget',
 label: 'Budget range',
 type: 'select',
 description: 'Rough budget they have in mind.',
 options: ['under 5ドルk', '5ドルk-20ドルk', '20ドルk-50ドルk', '50ドルk+', 'not sure yet'],
 required: true,
 },
 // ...
 ],
 scoring: { rules: [/* ... */], tiers: [/* ... */] },
};

Fields support text, longtext, email, number, select, and boolean. The description is guidance for the model — be specific; it's what the agent uses to decide what to ask and how to interpret answers. required fields must be collected before the agent can submit.

Scoring

Scoring turns extracted answers into a tier. Each rule awards (or subtracts) points when a field matches a condition; the highest tier whose minScore is met wins.

scoring: {
 rules: [
 { field: 'timeline', equals: 'immediately', points: 30, label: 'Ready to start now' },
 { field: 'budget', oneOf: ['50ドルk+', '20ドルk-50ドルk'], points: 30, label: 'Strong budget' },
 { field: 'decision_maker', equals: true, points: 20, label: 'Is a decision maker' },
 { field: 'team_size', gte: 10, points: 10, label: 'Team of 10+' },
 { field: 'problem', present: true, points: 10, label: 'Described a concrete problem' },
 { field: 'timeline', equals: 'just exploring', points: -10, label: 'Just exploring' },
 ],
 tiers: [
 { name: 'Hot', minScore: 70 },
 { name: 'Warm', minScore: 40 },
 { name: 'Cold', minScore: 0 },
 ],
}

Supported conditions: equals, oneOf, includes (case-insensitive substring), gte, lte, and present. String comparisons are case-insensitive. The breakdown in the output lists exactly which rules fired, so every score is explainable.


Lead destinations

Configure any combination — or none (leads log to the console in dev).

Webhook

Set LEAD_WEBHOOK_URL and every completed lead is POSTed there as JSON. Point it at Zapier, n8n, Make, a CRM, or your own endpoint.

If you also set LEAD_WEBHOOK_SECRET, each request is signed with an X-Signature: sha256=<hmac> header over the raw body. Verify it on your end:

import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody: string, signature: string, secret: string): boolean {
 const expected = `sha256=${createHmac('sha256', secret).update(rawBody).digest('hex')}`;
 const a = Buffer.from(signature);
 const b = Buffer.from(expected);
 return a.length === b.length && timingSafeEqual(a, b);
}

The webhook payload is the full lead: { createdAt, fields, score, transcript }.

Supabase

Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY, then apply the schema:

# Supabase SQL editor, or:
supabase db execute --file supabase/schema.sql

Leads are inserted into a leads table (fields, score, tier, total_score, transcript). The service-role key is used server-side only in the API route — it never reaches the browser, and Row Level Security stays on.

Add your own

Destinations live in lib/destinations/. Write a module beside webhook.ts (email via Resend, a Google Sheet append, a CRM call) and wire it into deliverLead() in lib/destinations/index.ts. Delivery is best-effort per destination — one failing won't block the others or the visitor.


Deploy

This is a standard Next.js app — deploy anywhere that runs Next 16. On Vercel: import the repo, set the environment variables from .env.example, and deploy. The /api/chat route runs on the Node.js runtime.


Project structure

intake.config.ts ← the file you edit (fields, persona, scoring)
app/
 page.tsx server component → renders the chat
 api/chat/route.ts one turn: run agent → score → deliver
components/Chat.tsx the chat UI (client component)
lib/
 intake.ts builds the system prompt + submit_intake tool from config
 llm.ts the Anthropic call for one turn
 scoring.ts pure, tested lead scoring
 db.ts Supabase persistence (optional)
 destinations/ webhook + delivery orchestration
 types.ts shared types
supabase/schema.sql the leads table
test/ Vitest unit tests for scoring, intake, webhook

Testing

pnpm test # run the suite
pnpm typecheck # tsc --noEmit
pnpm lint # ESLint

The core logic — scoring rules, tool-schema generation, prompt assembly, and webhook signing — is covered by unit tests that run in CI on every push.

License

MIT © Rick (brutusdev0). See LICENSE.

About

AI chat intake agent that runs the first pass of your discovery calls. Qualifies leads, extracts structured answers with Claude, scores them hot/warm/cold, and delivers to Supabase + webhook. Deployable Next.js template, config-driven.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /