$npx -y skills add jackspace/ClaudeSkillz --skill better-chatbot-patternsThis skill provides reusable implementation patterns extracted from the better-chatbot project for custom AI chatbot deployments. Use this skill when building AI chatbots with server action validators, tool abstraction systems, workflow execution, or multi-AI provider integration
| 1 | # better-chatbot-patterns |
| 2 | |
| 3 | **Status**: Production Ready |
| 4 | **Last Updated**: 2025-10-29 |
| 5 | **Dependencies**: None |
| 6 | **Latest Versions**: next@15.3.2, ai@5.0.82, zod@3.24.2, zustand@5.0.3 |
| 7 | |
| 8 | --- |
| 9 | |
| 10 | ## Overview |
| 11 | |
| 12 | 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. |
| 13 | |
| 14 | **Patterns included**: |
| 15 | 1. Server action validators (auth, validation, FormData) |
| 16 | 2. Tool abstraction system (multi-type tool handling) |
| 17 | 3. Multi-AI provider setup |
| 18 | 4. Workflow execution patterns |
| 19 | 5. State management conventions |
| 20 | |
| 21 | --- |
| 22 | |
| 23 | ## Pattern 1: Server Action Validators |
| 24 | |
| 25 | ### The Problem |
| 26 | Manual server action auth and validation leads to: |
| 27 | - Inconsistent auth checks |
| 28 | - Repeated FormData parsing boilerplate |
| 29 | - Non-standard error handling |
| 30 | - Type safety issues |
| 31 | |
| 32 | ### The Solution: Validated Action Utilities |
| 33 | |
| 34 | Create `lib/action-utils.ts`: |
| 35 | |
| 36 | ```typescript |
| 37 | import { z } from "zod" |
| 38 | |
| 39 | // Type for action result |
| 40 | type ActionResult<T> = |
| 41 | | { success: true; data: T } |
| 42 | | { success: false; error: string } |
| 43 | |
| 44 | // Pattern 1: Simple validation (no auth) |
| 45 | export function validatedAction<TSchema extends z.ZodType>( |
| 46 | schema: TSchema, |
| 47 | handler: ( |
| 48 | data: z.infer<TSchema>, |
| 49 | formData: FormData |
| 50 | ) => Promise<ActionResult<any>> |
| 51 | ) { |
| 52 | return async (formData: FormData): Promise<ActionResult<any>> => { |
| 53 | try { |
| 54 | const rawData = Object.fromEntries(formData.entries()) |
| 55 | const parsed = schema.safeParse(rawData) |
| 56 | |
| 57 | if (!parsed.success) { |
| 58 | return { success: false, error: parsed.error.errors[0].message } |
| 59 | } |
| 60 | |
| 61 | return await handler(parsed.data, formData) |
| 62 | } catch (error) { |
| 63 | return { success: false, error: String(error) } |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | // Pattern 2: With user context (adapt getUser() to your auth system) |
| 69 | export function validatedActionWithUser<TSchema extends z.ZodType>( |
| 70 | schema: TSchema, |
| 71 | handler: ( |
| 72 | data: z.infer<TSchema>, |
| 73 | formData: FormData, |
| 74 | user: { id: string; email: string } // Adapt to your User type |
| 75 | ) => Promise<ActionResult<any>> |
| 76 | ) { |
| 77 | return async (formData: FormData): Promise<ActionResult<any>> => { |
| 78 | try { |
| 79 | // Adapt this to your auth system (Better Auth, Clerk, Auth.js, etc.) |
| 80 | const user = await getUser() |
| 81 | if (!user) { |
| 82 | return { success: false, error: "Unauthorized" } |
| 83 | } |
| 84 | |
| 85 | const rawData = Object.fromEntries(formData.entries()) |
| 86 | const parsed = schema.safeParse(rawData) |
| 87 | |
| 88 | if (!parsed.success) { |
| 89 | return { success: false, error: parsed.error.errors[0].message } |
| 90 | } |
| 91 | |
| 92 | return await handler(parsed.data, formData, user) |
| 93 | } catch (error) { |
| 94 | return { success: false, error: String(error) } |
| 95 | } |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | // Pattern 3: With permission check (adapt to your roles system) |
| 100 | export function validatedActionWithPermission<TSchema extends z.ZodType>( |
| 101 | schema: TSchema, |
| 102 | permission: "admin" | "user-manage" | string, // Your permission types |
| 103 | handler: ( |
| 104 | data: z.infer<TSchema>, |
| 105 | formData: FormData, |
| 106 | user: { id: string; email: string; role: string } |
| 107 | ) => Promise<ActionResult<any>> |
| 108 | ) { |
| 109 | return async (formData: FormData): Promise<ActionResult<any>> => { |
| 110 | try { |
| 111 | const user = await getUser() |
| 112 | if (!user) { |
| 113 | return { success: false, error: "Unauthorized" } |
| 114 | } |
| 115 | |
| 116 | // Adapt this to your permission system |
| 117 | const hasPermission = await checkPermission(user, permission) |
| 118 | if (!hasPermission) { |
| 119 | return { success: false, error: "Forbidden" } |
| 120 | } |
| 121 | |
| 122 | const rawData = Object.fromEntries(formData.entries()) |
| 123 | const parsed = schema.safeParse(rawData) |
| 124 | |
| 125 | if (!parsed.success) { |
| 126 | return { success: false, error: parsed.error.errors[0].message } |
| 127 | } |
| 128 | |
| 129 | return await handler(parsed.data, formData, user) |
| 130 | } catch (error) { |