better-chatbot-patterns

from jackspace/claudeskillz

ClaudeSkillz: For when you need skills, but lazier

8 stars2 forksUpdated Nov 20, 2025
npx skills add https://github.com/jackspace/claudeskillz --skill better-chatbot-patterns

SKILL.md

better-chatbot-patterns

Status: Production Ready Last Updated: 2025-10-29 Dependencies: None Latest Versions: next@15.3.2, ai@5.0.82, zod@3.24.2, zustand@5.0.3


Overview

This skill extracts reusable patterns from the better-chatbot project for use in custom AI chatbot implementations. Unlike the better-chatbot skill (which teaches project conventions), this skill provides portable templates you can adapt to any project.

Patterns included:

  1. Server action validators (auth, validation, FormData)
  2. Tool abstraction system (multi-type tool handling)
  3. Multi-AI provider setup
  4. Workflow execution patterns
  5. State management conventions

Pattern 1: Server Action Validators

The Problem

Manual server action auth and validation leads to:

  • Inconsistent auth checks
  • Repeated FormData parsing boilerplate
  • Non-standard error handling
  • Type safety issues

The Solution: Validated Action Utilities

Create lib/action-utils.ts:

import { z } from "zod"

// Type for action result
type ActionResult<T> =
  | { success: true; data: T }
  | { success: false; error: string }

// Pattern 1: Simple validation (no auth)
export function validatedAction<TSchema extends z.ZodType>(
  schema: TSchema,
  handler: (
    data: z.infer<TSchema>,
    formData: FormData
  ) => Promise<ActionResult<any>>
) {
  return async (formData: FormData): Promise<ActionResult<any>> => {
    try {
      const rawData = Object.fromEntries(formData.entries())
      const parsed = schema.safeParse(rawData)

      if (!parsed.success) {
        return { success: false, error: parsed.error.errors[0].message }
      }

      return await handler(parsed.data, formData)
    } catch (error) {
      return { success: false, error: String(error) }
    }
  }
}

// Pattern 2: With user context (adapt getUser() to your auth system)
export function validatedActionWithUser<TSchema extends z.ZodType>(
  schema: TSchema,
  handler: (
    data: z.infer<TSchema>,
    formData: FormData,
    user: { id: string; email: string } // Adapt to your User type
  ) => Promise<ActionResult<any>>
) {
  return async (formData: FormData): Promise<ActionResult<any>> => {
    try {
      // Adapt this to your auth system (Better Auth, Clerk, Auth.js, etc.)
      const user = await getUser()
      if (!user) {
        return { success: false, error: "Unauthorized" }
      }

      const rawData = Object.fromEntries(formData.entries())
      const parsed = schema.safeParse(rawData)

      if (!parsed.success) {
        return { success: false, error: parsed.error.errors[0].message }
      }

      return await handler(parsed.data, formData, user)
    } catch (error) {
      return { success: false, error: String(error) }
    }
  }
}

// Pattern 3: With permission check (adapt to your roles system)
export function validatedActionWithPermission<TSchema extends z.ZodType>(
  schema: TSchema,
  permission: "admin" | "user-manage" | string, // Your permission types
  handler: (
    data: z.infer<TSchema>,
    formData: FormData,
    user: { id: string; email: string; role: string }
  ) => Promise<ActionResult<any>>
) {
  return async (formData: FormData): Promise<ActionResult<any>> => {
    try {
      const user = await getUser()
      if (!user) {
        return { success: false, error: "Unauthorized" }
      }

      // Adapt this to your permission system
      const hasPermission = await checkPermission(user, permission)
      if (!hasPermission) {
        return { success: false, error: "Forbidden" }
      }

      const rawData = Object.fromEntries(formData.entries())
      const parsed = schema.safeParse(rawData)

      if (!parsed.success) {
        return { success: false, error: parsed.error.errors[0].message }
      }

      return await handler(parsed.data, formData, user)
    } catch (error) {
      return { success: false, error: String(error) }
    }
  }
}

// Placeholder functions - replace with your auth system
async function getUser() {
  // Better Auth: await auth()
  // Clerk: const { userId } = auth(); if (!userId) return null; return await currentUser()
  // Auth.js: const session = await getServerSession(); return session?.user
  throw new Error("Implement getUser() with your auth provider")
}

async function checkPermission(user: any, permission: string) {
  // Implement based on your role system
  throw new Error("Implement checkPermission() with your role system")
}

Usage Example

// app/actions/profile.ts
"use server"

import { validatedActionWithUser } from "@/lib/action-utils"
import { z } from "zod"
import { db } from "@/lib/db"

const updateProfileSchema = z.object({
  name: z.string().min(1),
  email: z.string().email()
})

export const updateProfile = validatedActionWithUser(
  updateProfileSchema,
  async (data, formData, user) => {
    // user is guaranteed authenticated
    // data is validated and typed
    await db.update(users).set(data).where(eq(users.id,

...
Read full content

Repository Stats

Stars8
Forks2
LicenseMIT License