Curated Claude Code catalog
Updated 07.05.2026 · 19:39 CET
01 / Skill
tambo-ai

tambo

Quality
9.0

Tambo AI is an open-source React toolkit for building agents that render dynamic UI components. It excels when you need to integrate AI-powered interfaces directly into your application, allowing agents to respond to user input by generating and updating React components.

USP

Tambo provides a fullstack solution, handling LLM conversation loops, streaming infrastructure, and conversation state management, so developers can focus on defining components with Zod schemas. It uniquely allows agents to directly "spea…

Use cases

  • 01Building AI chat interfaces with dynamic component generation
  • 02Creating AI-powered analytics dashboards with visualizations
  • 03Integrating generative UI into existing React applications
  • 04Developing agents that render and update UI components
  • 05Adding dynamic, interactive elements controlled by an AI agent

Detected files (8)

  • .claude/skills/api-resource-lifecycle/SKILL.mdskill
    Show content (7849 bytes)
    ---
    name: api-resource-lifecycle
    description: Guides CRUD operations for API resources with cascading dependencies, descriptive validation, and orphan prevention. Use when adding delete/remove operations, creating validation logic, building resources that depend on other resources, or when the user mentions "cascade delete", "orphan records", "duplicate detection", "validation errors", "resource cleanup", or "rollback on failure".
    metadata:
      internal: true
    ---
    
    # API Resource Lifecycle
    
    Patterns for building reliable CRUD operations in Tambo Cloud.
    
    ## Gotchas
    
    - The codebase uses `CONFLICT` for duplicate name errors in skills but `BAD_REQUEST` for the same pattern in MCP servers. Always use `CONFLICT` for duplicates.
    - PostgreSQL unique violation code is `23505`. Catch it and map to a domain-specific exception rather than letting the raw DB error propagate.
    - When replacing a provider key, all skills with `externalSkillMetadata` for that provider must have their metadata cleared, or they'll reference a stale key.
    - Skills are silently skipped (not errored) at runtime when the provider doesn't support them. This is intentional -- don't add validation that blocks skill creation for unsupported providers.
    - `Promise.allSettled` (not `Promise.all`) for batch external API calls -- partial failures need cleanup, not an all-or-nothing abort.
    
    ## Cascading Deletes
    
    Default to `onDelete: "cascade"` in the schema. Only use manual transaction cascades when deletion requires external API calls, metadata cleanup, or cross-reference logic.
    
    ```typescript
    // packages/db/src/schema.ts
    export const skills = pgTable("skills", {
      id: text("id").primaryKey(),
      projectId: text("project_id")
        .notNull()
        .references(() => projects.id, { onDelete: "cascade" }),
      // ...
    });
    ```
    
    ### Manual cascades in transactions
    
    When deletion requires cleanup beyond FK cascades (external APIs, metadata, cross-references), wrap in a transaction:
    
    ```typescript
    // packages/db/src/operations/project.ts
    export async function deleteProject(db: HydraDb, id: string): Promise<boolean> {
      return await db.transaction(async (tx) => {
        await tx
          .delete(schema.providerKeys)
          .where(eq(schema.providerKeys.projectId, id));
        await tx.delete(schema.apiKeys).where(eq(schema.apiKeys.projectId, id));
        await tx
          .delete(schema.projectMembers)
          .where(eq(schema.projectMembers.projectId, id));
        const deleted = await tx
          .delete(schema.projects)
          .where(eq(schema.projects.id, id))
          .returning();
        return deleted.length > 0;
      });
    }
    ```
    
    **Reference:** `packages/db/src/operations/project.ts` lines 255-278
    
    ### Metadata cleanup on replacement
    
    When a resource is replaced (not deleted), clear dependent metadata so dependents re-sync under the new resource:
    
    ```typescript
    // apps/web/server/api/routers/project.ts - addProviderKey
    // When replacing a provider key, clear skill metadata for that provider
    const skills = await operations.listSkillsForProject(ctx.db, projectId);
    await Promise.all(
      skills
        .filter((s) => s.externalSkillMetadata?.[providerName])
        .map(async (s) => {
          const { [providerName]: _, ...remaining } = s.externalSkillMetadata ?? {};
          return operations.updateSkill(ctx.db, {
            projectId,
            skillId: s.id,
            externalSkillMetadata: remaining,
          });
        }),
    );
    ```
    
    **Reference:** `apps/web/server/api/routers/project.ts` lines 863-880
    
    ### Rules
    
    - If a child record has no meaning without its parent, use `onDelete: "cascade"` in the schema
    - If deletion requires multi-step cleanup or external API calls, wrap in a transaction
    - When replacing a resource, clear dependent metadata so dependents re-sync
    - Always delete children before the parent in manual cascades
    - Check for affected dependents and document the cascade chain in code comments
    
    ## Validation Errors
    
    Every error must say what went wrong and what to do instead. Include the specific value that failed and an example of what's valid.
    
    ```typescript
    // Zod: include format example
    name: z.string().min(1, "Name is required").max(64)
      .regex(SKILL_NAME_PATTERN, "Name must be kebab-case (e.g. scheduling-assistant)"),
    
    // tRPC: include the conflicting value
    throw new TRPCError({
      code: "CONFLICT",
      message: `Server key "${serverKey}" is already in use by another MCP server in this project`,
    });
    ```
    
    ### Error code mapping
    
    | Situation               | tRPC Code               | When to use                           |
    | ----------------------- | ----------------------- | ------------------------------------- |
    | Resource not found      | `NOT_FOUND`             | ID lookup returned null               |
    | Input validation failed | `BAD_REQUEST`           | Zod schema or business rule violation |
    | Duplicate resource      | `CONFLICT`              | Name/key already exists               |
    | Unexpected failure      | `INTERNAL_SERVER_ERROR` | Catch-all for unhandled errors        |
    
    ## Duplicate Detection
    
    Default to database unique constraints with custom exception mapping. Only use pre-creation queries when no DB constraint exists.
    
    ```typescript
    // packages/db/src/operations/skills.ts
    const PG_UNIQUE_VIOLATION = "23505";
    const SKILLS_NAME_UNIQUE_CONSTRAINT = "skills_project_id_name_idx";
    
    export class SkillNameConflictError extends Error {
      constructor(name: string) {
        super(`A skill named "${name}" already exists in this project`);
        this.name = "SkillNameConflictError";
      }
    }
    
    export async function createSkill(
      db: HydraDb,
      data: NewSkill,
    ): Promise<DBSkill> {
      try {
        const [skill] = await db.insert(schema.skills).values(data).returning();
        return skill;
      } catch (error) {
        if (isSkillNameConflict(error)) {
          throw new SkillNameConflictError(data.name);
        }
        throw error;
      }
    }
    ```
    
    ### Pre-creation queries (fallback)
    
    When no unique DB constraint exists, query before inserting:
    
    ```typescript
    const existingKeys = await getExistingServerKeys(ctx.db, projectId);
    if (existingKeys.includes(serverKey)) {
      throw new TRPCError({
        code: "CONFLICT",
        message: `Server key "${serverKey}" is already in use by another MCP server in this project`,
      });
    }
    ```
    
    **Reference:** `apps/web/server/api/routers/tools.ts` lines 119-126
    
    ## Orphan Prevention
    
    When creating a resource that syncs to an external API, roll back on failure:
    
    ```typescript
    // apps/web/server/api/routers/skills.ts
    // Create in DB first, then sync to provider
    const skill = await operations.createSkill(ctx.db, { ... });
    
    // If provider sync fails, delete the DB record
    try {
      await createSkillOnProviderAndPersist(ctx.db, input.projectId, skill);
    } catch (error) {
      await operations.deleteSkill(ctx.db, input.projectId, skill.id);
      throw error;
    }
    ```
    
    **Rules:**
    
    - Create the DB record first (gives you an ID for the external API call)
    - If the external sync fails, delete the DB record to avoid orphans
    - The user sees an error and can retry, rather than seeing a broken record
    - For batch operations, use `Promise.allSettled` and clean up partial failures
    
    **Reference:** `apps/web/server/api/routers/skills.ts` lines 195-204
    
    ## Checklist
    
    When building a new CRUD operation:
    
    ```
    Resource Lifecycle Checklist:
    - [ ] Delete cascades documented (which dependents are affected?)
    - [ ] Database-level cascades set up for simple parent-child FK relationships
    - [ ] Manual cascade logic wrapped in a transaction for complex cleanup
    - [ ] All Zod validation messages are descriptive with examples
    - [ ] TRPCError messages include the specific value that caused the error
    - [ ] Duplicate detection before creation (custom exception or pre-query)
    - [ ] Error codes are correct (CONFLICT for duplicates, NOT_FOUND for missing)
    - [ ] External API sync failures roll back the DB record
    - [ ] Resource replacement clears dependent metadata
    ```
    
  • .claude/skills/building-settings-ui/SKILL.mdskill
    Show content (10082 bytes)
    ---
    name: building-settings-ui
    description: >-
      Use this skill when adding or modifying settings UI in Tambo Cloud. Covers where a new settings
      section belongs (Agent tab vs Settings tab), and the component patterns used across both pages
      (card layout, toasts, confirmation dialogs, destructive styling, save behavior conventions).
      Triggers on "add a new settings section", "where should X go?", "settings UI", "settings page",
      "agent page", or any work touching apps/web/components/dashboard-components/project-details/,
      project-settings.tsx, or agent-settings.tsx.
      Not for full-stack feature building (DB, tRPC, tests); those patterns will get their own skills.
    metadata:
      internal: true
    ---
    
    # Building Settings UI
    
    Guide for placing and styling settings sections in the Tambo Cloud dashboard. Covers two concerns: where a feature belongs (which tab/page), and how to build the UI component to match existing patterns.
    
    ## Architecture
    
    Settings are split across two top-level tabs in the project layout:
    
    - **Agent tab** (`/[projectId]/agent`) - How the AI agent behaves
    - **Settings tab** (`/[projectId]/settings`) - Project infrastructure and access
    
    Each tab renders a flat vertical stack of Card components. There is no sidebar navigation; each page is short enough to scroll naturally.
    
    ### Tab layout
    
    ```
    Overview | Observability | Agent | Settings
    ```
    
    - Layout file: `apps/web/app/(authed)/(dashboard)/[projectId]/layout.tsx`
    - Agent page: `apps/web/app/(authed)/(dashboard)/[projectId]/agent/page.tsx`
    - Settings page: `apps/web/app/(authed)/(dashboard)/[projectId]/settings/page.tsx`
    
    ## Gotchas
    
    1. **Do not add a new top-level tab** without explicit team alignment. Current tabs (Overview, Observability, Agent, Settings) have been stable.
    2. **`EditWithTamboButton` goes inside `CardTitle`**, not as a sibling of `CardHeader`. It must have a `description` prop explaining what the section configures.
    3. **Invalidate the query before toasting** in `onSuccess`. Reversing the order can show a success toast while the UI still displays old data.
    4. **Use `DeleteConfirmationDialog`**, never inline `AlertDialog` for destructive confirmations.
    5. **Use `text-destructive` semantic color**, never `text-red-500`. Cancel/discard buttons are NOT destructive.
    
    ---
    
    ## Feature Placement
    
    ### Agent Tab Sections
    
    | #   | Section             | What it configures                                 | Component                        |
    | --- | ------------------- | -------------------------------------------------- | -------------------------------- |
    | 1   | Model               | Provider + model selection, API key, custom params | `provider-key-section.tsx`       |
    | 2   | Custom Instructions | System prompt, prompt override toggle              | `custom-instructions-editor.tsx` |
    | 3   | Skills              | Skill definitions and imports                      | `skills-section.tsx`             |
    | 4   | Tool Call Limit     | Max tool calls per response                        | `tool-call-limit-editor.tsx`     |
    | 5   | MCP                 | MCP server URLs + headers                          | `available-mcp-servers.tsx`      |
    
    **Container:** `apps/web/components/dashboard-components/agent-settings.tsx`
    
    ### Settings Tab Sections
    
    | #   | Section        | What it configures             | Component                  |
    | --- | -------------- | ------------------------------ | -------------------------- |
    | 1   | Name           | Project display name           | `project-name-section.tsx` |
    | 2   | API Keys       | API key list + create          | `api-key-list.tsx`         |
    | 3   | Authentication | OAuth mode, token requirements | `oauth-settings.tsx`       |
    | 4   | Danger Zone    | Project deletion               | `danger-zone-section.tsx`  |
    
    **Container:** `apps/web/components/dashboard-components/project-settings.tsx`
    
    All section components live in `apps/web/components/dashboard-components/project-details/`.
    
    ### Placement Decision Tree
    
    1. **Configures AI agent behavior?** (model selection, prompts, tools, memory, context) -> **Agent tab**
    2. **Configures project infrastructure?** (API keys, naming, deletion, billing, webhooks) -> **Settings tab**
    3. **Configures who can access?** (auth, tokens, team members, permissions) -> **Settings tab**
    4. **Monitoring or debugging view?** -> **Observability tab**
    5. **High-level summary or status?** -> **Overview tab**
    6. **None of the above?** -> Ask the user.
    
    ### Conditional and Dependent Settings
    
    Some settings only apply when another setting is in a specific state. Follow these patterns:
    
    **Show but warn (soft dependency).** The section renders normally but displays an `Alert` when the dependency isn't met. The user can still see and configure the setting. Use this when the feature exists but won't work at runtime.
    
    Example: Skills section shows a provider compatibility notice when the selected provider doesn't support skills:
    
    ```tsx
    // skills-section.tsx
    const isProviderSupported = SKILLS_SUPPORTED_PROVIDERS.has(
      defaultLlmProviderName,
    );
    // Renders full skills UI + warning Alert if !isProviderSupported
    ```
    
    **Conditionally pass props (data dependency).** The parent reads one setting and passes it as a prop so the child can adapt its behavior. Use this when the child's content or options change based on the parent's state.
    
    Example: MCP servers section receives `providerType` to toggle agent-mode-specific UI:
    
    ```tsx
    // agent-settings.tsx
    <AvailableMcpServers providerType={projectData?.providerType} />;
    // available-mcp-servers.tsx
    const isAgentMode = providerType === AiProviderType.AGENT;
    ```
    
    Example: Custom LLM parameters change available suggestions based on provider and model:
    
    ```tsx
    // provider-key-section.tsx passes selectedProvider to the parameter editor
    <CustomLlmParametersEditor selectedProvider={selectedProvider} />
    ```
    
    **Rules for new dependent settings:**
    
    1. Never hide a section entirely based on another setting's state. Always render the card; use an Alert or disabled state to communicate the dependency.
    2. The warning message must name the dependency and tell the user what to change (e.g., "Switch to a supported provider to enable skills").
    3. Keep dependency checks in the section component, not the container. The container (`agent-settings.tsx`, `project-settings.tsx`) should pass data, not make visibility decisions.
    4. If a setting depends on state from a different tab, pass it through the shared project query (`api.project.getProject`) rather than cross-tab state.
    
    ### Adding a New Section
    
    1. **Create the component** in `project-details/` following the Card layout pattern below.
    2. **Import and render** in the correct container (`agent-settings.tsx` or `project-settings.tsx`).
    3. **Update the skeleton** in `settings-skeletons.tsx` (either `AgentPageSkeleton` or `SettingsPageSkeleton`).
    
    ---
    
    ## Component Patterns
    
    ### Card Layout
    
    Every settings section uses `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent` from `@/components/ui/card`.
    
    ```tsx
    <Card>
      <CardHeader>
        <CardTitle className="text-lg font-semibold">
          Section Name
          <EditWithTamboButton description="Configure section settings..." />
        </CardTitle>
        <CardDescription className="text-sm font-sans text-foreground">
          Description of what this section does.
        </CardDescription>
      </CardHeader>
      <CardContent className="space-y-4">{/* Content */}</CardContent>
    </Card>
    ```
    
    ### Toast Notifications
    
    Every mutation shows a toast on both success and error. Import `useToast` from `@/hooks/use-toast`.
    
    ```tsx
    const mutation = api.someRoute.someMutation.useMutation({
      onSuccess: async () => {
        await utils.someRoute.someQuery.invalidate();
        toast({ title: "Success", description: "Setting updated successfully" });
      },
      onError: () => {
        toast({
          title: "Error",
          description: "Failed to update setting",
          variant: "destructive",
        });
      },
    });
    ```
    
    Never skip the error toast.
    
    ### Confirmation Dialogs
    
    All destructive actions use `DeleteConfirmationDialog` from `@/components/dashboard-components/delete-confirmation-dialog`:
    
    ```tsx
    const [alertState, setAlertState] = useState<{
      show: boolean;
      title: string;
      description: string;
      data?: { id: string };
    }>({ show: false, title: "", description: "" });
    
    <DeleteConfirmationDialog
      mode="single"
      alertState={alertState}
      setAlertState={setAlertState}
      onConfirm={handleConfirmDelete}
    />;
    ```
    
    Title includes the item name (`Delete "${name}"?`). Description warns the action cannot be undone.
    
    ### Destructive Action Styling
    
    ```tsx
    <Button
      variant="ghost"
      size="icon"
      className="text-destructive hover:text-destructive hover:bg-destructive/10"
    >
      <Trash2 className="h-4 w-4" />
    </Button>
    ```
    
    Use `Trash2` from `lucide-react`. Use `hover:bg-destructive/10` for ghost variant hover.
    
    ### Save Behavior
    
    **Toggles: Immediate save.** `onCheckedChange` fires the mutation directly. Include `aria-label` with state context.
    
    **Form fields: Edit/Save/Cancel.** Track `isEditing`, `savedValue`, and `displayValue` state. Cancel reverts to `savedValue`. Save button disabled during mutation, shows "Saving...". `autoFocus` on first input when entering edit mode.
    
    **Reference implementations:** `custom-instructions-editor.tsx` (edit/save/cancel), `tool-call-limit-editor.tsx` (simpler form), `project-name-section.tsx` (basic edit/save/cancel).
    
    ### Danger Zone Pattern
    
    For irreversible destructive actions, use the Danger Zone card pattern:
    
    ```tsx
    <Card className="border-destructive/50">
      <CardHeader>
        <CardTitle>Danger Zone</CardTitle>
        <CardDescription>Warning about permanence.</CardDescription>
      </CardHeader>
      <CardContent>
        <Button
          variant="ghost"
          className="text-destructive hover:text-destructive hover:bg-destructive/10"
          aria-label="Delete this project"
        >
          Delete this project
        </Button>
      </CardContent>
    </Card>
    ```
    
    The `DeleteConfirmationDialog` should be owned by the parent component that handles the mutation and post-delete navigation.
    
  • .claude/skills/validating-accessibility/SKILL.mdskill
    Show content (5168 bytes)
    ---
    name: validating-accessibility
    description: >-
      Use this skill when creating, modifying, or reviewing any .tsx component in apps/web, even if
      the user doesn't mention "accessibility." Covers semantic HTML, aria labels, navigation landmarks,
      forms, dialogs, and keyboard navigation. Trigger on: adding buttons, links, toggles, icons, or
      any interactive element; building or editing forms; adding dialogs or modals; reviewing UI code.
      Includes inline verification patterns for scanning violations. Not for styling or layout changes
      that don't involve interactive elements.
    metadata:
      internal: true
    ---
    
    # Accessibility Checklist
    
    Every UI component in `apps/web` must meet these standards. No partial compliance.
    
    ## Gotchas
    
    - **`role="button"` divs may exist in the codebase** -- fix them when touching affected files. `<TableHead>` elements with `role="button"` for sortable columns are acceptable.
    - **Nested interactive elements** -- when replacing a `<div role="button">` that contains a child `<button>` (e.g., a copy button inside a collapsible toggle), do not just swap the outer div to `<button>`. That creates invalid nested buttons. Instead, restructure into sibling elements: a toggle `<button>` and a separate action `<button>` side by side in a flex container.
    - **Standalone inputs outside react-hook-form need manual ID pairing** -- use `useId()` with `htmlFor`/`id`. The shadcn `<FormField>` handles this automatically, but raw `<Input>` does not.
    - **AlertDialog vs Dialog** -- use `AlertDialog` for destructive confirmations (requires `AlertDialogTitle` + `AlertDialogDescription`). Use `Dialog` for content/forms. Never build custom modal overlays.
    - **Icon-only buttons without `aria-label`** are common in new code. Every icon-only button needs one, and it must include context: `Delete API key ${keyName}`, not just "Delete".
    
    ## Semantic HTML
    
    Use native elements. Never recreate `<button>` behavior with `<div role="button">` + keyboard handlers.
    
    | Interaction      | Element                                                |
    | ---------------- | ------------------------------------------------------ |
    | Clickable action | `<button>` or `<Button>` from `@/components/ui/button` |
    | Navigation link  | `<Link>` (Next.js) or `<a>`                            |
    | Navigation group | `<nav>` with descriptive `aria-label`                  |
    | Item list        | `<ul>`/`<ol>` + `<li>`                                 |
    | Section heading  | `<h1>`-`<h6>` in order, never skip levels              |
    
    ## Aria Labels
    
    Every interactive element without visible text needs `aria-label` with both action AND target:
    
    ```tsx
    <Button size="icon" aria-label={`Delete API key ${keyName}`}>
      <Trash2 className="h-4 w-4" />
    </Button>
    
    <Switch aria-label={`${enabled ? "Disable" : "Enable"} skill ${skillName}`} />
    ```
    
    Prefer state-aware labels ("Copied!" vs "Copy"). Buttons with visible text skip `aria-label`.
    
    **Reference implementations:** `copy-button.tsx` (state-aware), `context-attachment-badge.tsx` (contextual remove), `thread-table-header.tsx` (sort state) -- all in `apps/web/components/`.
    
    ## Navigation Landmarks
    
    Wrap navigation groups in `<nav>` with a unique `aria-label` per region on the page.
    
    ## Forms
    
    Use shadcn Form components from `@/components/ui/form` (`FormField`, `FormItem`, `FormLabel`, `FormControl`, `FormMessage`). They handle ID generation, label association, `aria-describedby`, and `aria-invalid` automatically.
    
    For standalone inputs outside react-hook-form, pair `useId()` with `htmlFor`/`id`. Never use placeholder as label substitute.
    
    ## Keyboard Navigation
    
    - Only `tabIndex={0}` or `tabIndex={-1}` (never positive values)
    - Never remove focus outlines
    - Prefer `<button>` over manual Enter/Space handlers
    
    ## Verification
    
    Scan `apps/web/components` for common violations. For each check, grep for the pattern and fix any matches found.
    
    ### Check 1: `role="button"` on non-button elements
    
    Search for `role="button"` in `.tsx` files. Flag `<div` or `<span` elements with this attribute; they should be `<button>` or `<Button>` instead. `<TableHead>` elements with `role="button"` for sortable columns are acceptable.
    
    **Pattern:** `role="button"`
    
    ### Check 2: `<div onClick>` patterns
    
    Search for `<div` elements with `onClick` handlers. These should use `<button>` instead for proper keyboard support.
    
    **Pattern:** `<div[^>]*onClick`
    
    ### Check 3: Positive tabIndex values
    
    Search for `tabIndex` with values greater than 0. Only `tabIndex={0}` and `tabIndex={-1}` are allowed.
    
    **Pattern:** `tabIndex={[1-9]`
    
    ### Check 4: Icon buttons missing aria-label
    
    Search for `size="icon"` in `.tsx` files. For each match, check surrounding lines (5-10 above and below) for `aria-label` on the same `<Button>` element or an `sr-only` span. Flag buttons that have neither.
    
    **Pattern:** `size="icon"` without nearby `aria-label`
    
    ### Manual checks
    
    These cannot be detected by pattern matching:
    
    - [ ] Form inputs have associated `<label>` elements
    - [ ] Navigation groups use `<nav>` with unique `aria-label`
    - [ ] Dialogs use Radix-based components (AlertDialog or Dialog)
    - [ ] Focus outlines intact
    
  • plugins/tambo/skills/building-with-tambo/SKILL.mdskill
    Show content (16332 bytes)
    ---
    name: building-with-tambo
    description: Integrates Tambo into existing React apps — detects tech stack, installs @tambo-ai/react, wires TamboProvider, registers components with Zod schemas, and sets up tools/context. Use when adding AI-powered generative UI to an existing codebase. Triggers on "add Tambo", "integrate Tambo", "add AI chat to my app", "add generative UI", or when the user has an existing React/Next.js/Vite project and wants to add AI-powered components. For brand-new projects, use generative-ui instead.
    ---
    
    # Building with Tambo
    
    Detect tech stack and integrate Tambo while preserving existing patterns.
    
    ## Reference Guides
    
    Load these when you reach the relevant step or need deeper implementation details:
    
    - [Components](references/components.md) - **Load at Step 5.** Generative vs interactable components, propsSchema, ComponentRenderer.
    - [Component Rendering](references/component-rendering.md) - Streaming props, loading states, persistent state. Load when building custom message rendering.
    - [Threads and Input](references/threads.md) - **Load when building custom chat UI.** useTambo(), useTamboThreadInput(), userKey/userToken auth, suggestions, voice.
    - [Tools and Context](references/tools-and-context.md) - **Load when wiring host app APIs.** defineTool(), MCP servers, contextHelpers.
    - [CLI Reference](references/cli.md) - **Load at Step 6.** `tambo add` component library, `tambo init` flags, non-interactive mode.
    - [Skills](references/skills.md) - **Mention as a next step after setup.** Project-scoped agent skills via CLI and dashboard.
    - [Add Components to Registry](references/add-components-to-registry.md) - **Load when registering existing app components.** Analyzes props, generates Zod schemas, writes descriptions.
    
    Shared references (components, rendering, threads, tools/context, CLI, skills) are duplicated into generative-ui so each skill works independently. `add-components-to-registry` is unique to this skill.
    
    ## Workflow
    
    1. **Detect tech stack** - Analyze package.json, lock files, project structure, monorepo layout
    2. **Confirm with user** - Present findings, ask about preferences
    3. **Install dependencies** - Add @tambo-ai/react and zod using the project's package manager
    4. **Create provider setup** - Wire TamboProvider with apiKey, userKey, components
    5. **Create component registry** - Set up lib/tambo.ts
    6. **Add chat UI** - Install pre-built Tambo components via CLI, set up path aliases and globals.css
    
    ## Step 1: Detect Tech Stack
    
    Check these files to understand the project:
    
    ```bash
    # Key files to read
    package.json           # Dependencies, scripts, AND package manager
    tsconfig.json          # TypeScript config, path aliases
    next.config.*          # Next.js
    vite.config.*          # Vite
    tailwind.config.*      # Tailwind CSS
    postcss.config.*       # PostCSS
    src/index.* or app/    # Entry points
    yarn.lock / pnpm-lock.yaml / package-lock.json  # Which package manager
    ```
    
    ### Detection Checklist
    
    | Technology       | Detection                                         |
    | ---------------- | ------------------------------------------------- |
    | Next.js          | `next` in dependencies, `next.config.*` exists    |
    | Vite             | `vite` in devDependencies, `vite.config.*` exists |
    | Create React App | `react-scripts` in dependencies                   |
    | TypeScript       | `typescript` in deps, `tsconfig.json` exists      |
    | Tailwind         | `tailwindcss` in deps, config file exists         |
    | Plain CSS        | No Tailwind, CSS files in src/                    |
    | Zod              | `zod` in dependencies                             |
    | Other validation | `yup`, `joi`, `superstruct` in deps               |
    
    ### Package Manager Detection
    
    **Always detect and use the project's package manager.** Do not assume npm.
    
    | Lock file           | Manager | Install command               |
    | ------------------- | ------- | ----------------------------- |
    | `package-lock.json` | npm     | `npm install @tambo-ai/react` |
    | `yarn.lock`         | Yarn    | `yarn add @tambo-ai/react`    |
    | `pnpm-lock.yaml`    | pnpm    | `pnpm add @tambo-ai/react`    |
    
    For **monorepos**, install in the correct workspace:
    
    - Yarn: `yarn workspace <app-name> add @tambo-ai/react`
    - pnpm: `pnpm --filter <app-name> add @tambo-ai/react`
    - npm: `npm install @tambo-ai/react -w <app-name>`
    
    ### Monorepo Detection
    
    Check for monorepo indicators:
    
    - `workspaces` field in root `package.json`
    - `pnpm-workspace.yaml`
    - `turbo.json` or `nx.json`
    - Multiple `package.json` files in `apps/` or `packages/`
    
    If monorepo detected, identify which package is the web app that will use Tambo (usually in `apps/web`, `apps/frontend`, or similar).
    
    ### Global Keyboard Shortcuts Detection
    
    Check if the app captures keyboard events globally (common in drawing tools, editors, IDEs). Look for:
    
    - `document.addEventListener("keydown", ...)` in the codebase
    - Canvas-based apps (Excalidraw, Figma-like, code editors)
    - Keyboard shortcut libraries (hotkeys-js, mousetrap, etc.)
    
    If found, the Tambo chat UI wrapper must stop event propagation (see Step 6).
    
    ## Step 2: Confirm with User
    
    Present findings including the package manager and any special concerns:
    
    ```
    I detected your project uses:
    - Framework: Next.js 14 (App Router)
    - Package manager: Yarn
    - Styling: Tailwind CSS
    - Validation: No Zod (will need to add)
    - TypeScript: Yes
    - Monorepo: No
    - Global keyboard shortcuts: No
    
    Should I:
    1. Install Tambo with these settings?
    2. Use plain CSS instead of Tailwind for Tambo components?
    3. Something else?
    ```
    
    ## Step 3: Install Dependencies
    
    Use the project's package manager (detected in Step 1):
    
    ```bash
    # npm
    npm install @tambo-ai/react
    npm install zod  # if no Zod installed
    
    # yarn
    yarn add @tambo-ai/react
    yarn add zod
    
    # pnpm
    pnpm add @tambo-ai/react
    pnpm add zod
    
    # Monorepo (install in the correct workspace)
    yarn workspace <app-name> add @tambo-ai/react zod
    pnpm --filter <app-name> add @tambo-ai/react zod
    npm install @tambo-ai/react zod -w <app-name>
    ```
    
    ## Step 4: Create Provider Setup
    
    ### Next.js App Router
    
    ```tsx
    // app/providers.tsx
    "use client";
    import { TamboProvider } from "@tambo-ai/react";
    import { components } from "@/lib/tambo";
    
    export function Providers({ children }: { children: React.ReactNode }) {
      return (
        <TamboProvider
          apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY}
          userKey="default-user"
          components={components}
        >
          {children}
        </TamboProvider>
      );
    }
    ```
    
    **IMPORTANT:** `userKey` is required for authentication. Without it, message submission fails at runtime with "authentication is not ready." Note: `userKey` is typed as optional in TypeScript (`userKey?: string`), so the compiler won't catch this — the failure is purely at runtime. In production, use a real user identifier (e.g., session ID, user ID from your auth system). For development/demo, a static string such as `"default-user"` works.
    
    ```tsx
    // app/layout.tsx
    import { Providers } from "./providers";
    
    export default function RootLayout({ children }) {
      return (
        <html>
          <body>
            <Providers>{children}</Providers>
          </body>
        </html>
      );
    }
    ```
    
    ### Next.js Pages Router
    
    ```tsx
    // pages/_app.tsx
    import { TamboProvider } from "@tambo-ai/react";
    import { components } from "@/lib/tambo";
    
    export default function App({ Component, pageProps }) {
      return (
        <TamboProvider
          apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY}
          userKey="default-user"
          components={components}
        >
          <Component {...pageProps} />
        </TamboProvider>
      );
    }
    ```
    
    ### Vite / CRA
    
    ```tsx
    // src/main.tsx
    import { TamboProvider } from "@tambo-ai/react";
    import { components } from "./lib/tambo";
    import App from "./App";
    
    ReactDOM.createRoot(document.getElementById("root")!).render(
      <TamboProvider
        apiKey={import.meta.env.VITE_TAMBO_API_KEY}
        userKey="default-user"
        components={components}
      >
        <App />
      </TamboProvider>,
    );
    ```
    
    ## Step 5: Create Component Registry
    
    ```tsx
    // lib/tambo.ts (or src/lib/tambo.ts)
    import { TamboComponent } from "@tambo-ai/react";
    
    export const components: TamboComponent[] = [
      // Components will be registered here
    ];
    ```
    
    ## Step 6: Add Chat UI
    
    Pick the right chat layout for the app:
    
    | Component                    | Best for                                                        | How it renders                                                        |
    | ---------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------- |
    | `message-thread-collapsible` | Overlaying on top of existing UI (drawing tools, simple apps)   | Fixed-position floating panel, bottom-right corner, toggle open/close |
    | `message-thread-panel`       | Apps with sidebar layouts (dashboards, admin panels, SaaS apps) | Side panel with thread history, resizable                             |
    | `message-thread-full`        | Dedicated chat pages or full-screen chat experiences            | Full-height thread with all features                                  |
    
    Choose based on the app's layout. If there's a sidebar or dashboard layout, use `panel`. If the chat should float over existing content, use `collapsible`. If the whole page is the chat, use `full`.
    
    ```bash
    npx tambo add message-thread-panel --yes
    # Or: npx tambo add message-thread-collapsible --yes
    # Or: npx tambo add message-thread-full --yes
    
    # With other package managers:
    # yarn dlx tambo add <component> --yes
    # pnpm dlx tambo add <component> --yes
    ```
    
    ### After `tambo add`, complete the setup:
    
    1. **CSS setup** — `tambo add` adds Tailwind directives and CSS variables to your project's CSS entry file. The file it modifies depends on the framework:
       - **Next.js**: `app/globals.css` (or `src/app/globals.css`) — already imported in layout by default.
       - **Vite**: `src/index.css` (or `index.css`) — already imported in `main.tsx` by default.
    
       If your entry file already imports a CSS file, `tambo add` modifies that file in place. No new import is needed.
    
    2. **Path alias** — Tambo components use `@/` imports. Check if the project's tsconfig already has `@/*` in its `paths`. Many Next.js projects created with `create-next-app` have this, but not all (e.g., Cal.com uses `~/*` and `@components/*` instead).
    
       If `@/*` is missing, add it to the app tsconfig (`tsconfig.app.json` when present, otherwise `tsconfig.json`):
    
       ```json
       {
         "compilerOptions": {
           "paths": {
             "@/*": ["./src/*"]
           }
         }
       }
       ```
    
       If the project has no `src/` directory and components land in the project root, use `["./*"]` instead of `["./src/*"]`. Check where `tambo add` placed the components to determine the correct path.
    
       For **Vite projects**, also add the alias to `vite.config.ts`:
    
       ```ts
       // vite.config.ts
       import path from "path";
    
       export default defineConfig({
         resolve: {
           alias: {
             "@": path.resolve(__dirname, "src"),
           },
         },
       });
       ```
    
    ### Keyboard Event Isolation
    
    **Critical for apps with global keyboard shortcuts** (drawing tools, editors, terminal-like apps). Without this, typing in the Tambo chat input triggers the host app's shortcuts.
    
    Wrap the Tambo chat UI in a div that stops keyboard event propagation:
    
    ```tsx
    <div
      onKeyDown={(e) => e.stopPropagation()}
      onKeyUp={(e) => e.stopPropagation()}
    >
      {/* Use whichever component you installed */}
      <MessageThreadCollapsible />{" "}
      {/* or <MessageThreadPanel /> or <MessageThreadFull /> */}
    </div>
    ```
    
    ### Z-Index for Full-Screen Apps
    
    Apps with full-screen canvases or overlays may render on top of the Tambo chat. The `MessageThreadCollapsible` component uses `position: fixed`, but the host app's canvas may have a higher stacking context. Wrap with a fixed container that sits above everything:
    
    ```tsx
    <div
      style={{ position: "fixed", inset: 0, zIndex: 9999, pointerEvents: "none" }}
    >
      <div style={{ pointerEvents: "auto" }}>
        {/* Use whichever component you installed */}
        <MessageThreadCollapsible />{" "}
        {/* or <MessageThreadPanel /> or <MessageThreadFull /> */}
      </div>
    </div>
    ```
    
    The outer div creates a stacking context above the canvas, while `pointerEvents: none/auto` ensures only the chat panel is clickable — the rest of the screen passes clicks through to the canvas.
    
    You can combine keyboard isolation and z-index in one wrapper.
    
    ## Adapting to Existing Patterns
    
    ### No Tailwind? Use Plain CSS
    
    If the project doesn't use Tailwind, `tambo add` will install Tailwind v4 via PostCSS alongside the existing styling. This is additive — it won't break existing CSS/SCSS. The Tambo components use Tailwind, but the rest of your app keeps its styling.
    
    If you'd prefer to avoid Tailwind entirely:
    
    ```bash
    # Skip --yes flag to customize styling during add
    npx tambo add message-thread-full
    # Select "CSS Modules" or "Plain CSS" when prompted
    ```
    
    ### Existing Validation Library?
    
    If using Yup/Joi instead of Zod, user can either:
    
    1. Add Zod just for Tambo schemas (recommended - small addition)
    2. Convert schemas (more work, not recommended)
    
    ### Monorepo?
    
    Run commands from the web app package, not the monorepo root.
    
    Run `npx tambo init --project-name=<app-name>` from the web app directory. This opens the browser for authentication and polls until the user completes auth (up to 15 minutes). Use a long timeout. Once auth completes, the CLI creates the project and writes the API key to `.env.local`.
    
    **Monorepo gotchas:**
    
    - **Env vars**: `tambo init` creates `.env.local` in the current directory. Vite loads env from `envDir` (check `vite.config.*`). Ensure the env file is where Vite looks for it.
    - **Dependency hoisting**: Some package managers hoist deps to the monorepo root. If Vite can't resolve a Tambo dependency, install it at the root level too (e.g., `yarn add -W @tambo-ai/react`).
    - **Path aliases**: Vite resolves from the `vite.config.*` directory. The `@/` alias must resolve to the correct `src/` directory within the workspace, not the monorepo root.
    
    ## Environment Variables
    
    `npx tambo init --project-name=<name>` opens the browser and polls until the user authenticates (up to 15 minutes). Use a long timeout. Once auth completes, it creates the project and writes `.env.local`.
    
    If manual setup is needed (monorepo, read-only filesystem), add the appropriate variable:
    
    | Framework | Variable                    |
    | --------- | --------------------------- |
    | Next.js   | `NEXT_PUBLIC_TAMBO_API_KEY` |
    | Vite      | `VITE_TAMBO_API_KEY`        |
    | CRA       | `REACT_APP_TAMBO_API_KEY`   |
    
    ## Verification
    
    After setup, verify by creating a simple test:
    
    ```tsx
    import { useTambo } from "@tambo-ai/react";
    
    function TestComponent() {
      const { thread, isIdle } = useTambo();
      console.log("Tambo connected:", isIdle);
      return <div>Tambo is set up!</div>;
    }
    ```
    
    ## Reporting Bugs & Feedback
    
    If you encounter any of these while helping a user, suggest filing a GitHub issue:
    
    - A bug in `@tambo-ai/react`, `@tambo-ai/client`, or the `tambo` CLI
    - Documentation that contradicts actual behavior
    - Missing or misleading instructions in this skill
    
    ### Issue Template
    
    Use this template when drafting an issue for `tambo-ai/tambo`:
    
    ````markdown
    **Title:** [Short description of the problem]
    
    **Expected behavior**
    What should happen.
    
    **Actual behavior**
    What happens instead, including any error messages or stack traces.
    
    **Repro steps**
    
    1. Run `npx tambo create-app ...` (or whatever command)
    2. Add this code:
    
    ```tsx
    // minimal code that reproduces the issue
    ```
    
    3. Observe the error
    
    **Environment**
    
    - `@tambo-ai/react` version:
    - `@tambo-ai/client` version:
    - Framework (Next.js / Vite / CRA) and version:
    - Node.js version:
    - OS:
    
    **Additional context**
    Link to relevant docs or skill file path if applicable.
    ````
    
    **Security:** Redact API keys, tokens, and any customer data before including logs or code snippets in the issue.
    
    ### Suggesting an Issue to the User
    
    When you hit a problem that looks like a Tambo bug, say something like:
    
    > This looks like a bug in `@tambo-ai/react`. Want me to open a GitHub issue on `tambo-ai/tambo` with the repro steps and environment details?
    
    Always wait for the user to confirm before filing.
    
  • .claude/skills/creating-styled-wrappers/SKILL.mdskill
    Show content (7752 bytes)
    ---
    name: creating-styled-wrappers
    description: Creates styled wrapper components that compose headless/base compound components. Use when refactoring styled components to use base primitives, implementing opinionated design systems on top of headless components, or when the user mentions "use base components", "compose primitives", "styled wrapper", or "refactor to use base".
    metadata:
      internal: true
    ---
    
    # Styling Compound Wrappers
    
    Create styled wrapper components that compose headless base compound components. This skill complements `building-compound-components` (which builds the base primitives) by focusing on **how to properly consume and wrap them** with styling and additional behavior.
    
    **Real-world example**: See [references/real-world-example.md](references/real-world-example.md) for a complete before/after MessageInput refactoring.
    
    ## Core Principle: Compose, Don't Duplicate
    
    Styled wrappers should **compose** base components, not **re-implement** their logic.
    
    ```tsx
    // WRONG - re-implementing what base already does
    const StyledInput = ({ children, className }) => {
      const { value, setValue, submit } = useTamboThreadInput(); // Duplicated!
      const [isDragging, setIsDragging] = useState(false); // Duplicated!
      const handleDrop = useCallback(/* ... */); // Duplicated!
    
      return (
        <form onDrop={handleDrop} className={className}>
          {children}
        </form>
      );
    };
    
    // CORRECT - compose the base component
    const StyledInput = ({ children, className, variant }) => {
      return (
        <BaseInput.Root className={cn(inputVariants({ variant }), className)}>
          <BaseInput.Content className="rounded-xl data-[dragging]:border-dashed">
            {children}
          </BaseInput.Content>
        </BaseInput.Root>
      );
    };
    ```
    
    ## Refactoring Workflow
    
    Copy this checklist and track progress:
    
    ```
    Styled Wrapper Refactoring:
    - [ ] Step 1: Identify duplicated logic
    - [ ] Step 2: Import base components
    - [ ] Step 3: Wrap with Base Root
    - [ ] Step 4: Apply state-based styling and behavior
    - [ ] Step 5: Wrap sub-components with styling
    - [ ] Step 6: Final verification
    ```
    
    ### Step 1: Identify Duplicated Logic
    
    Look for patterns that indicate logic should come from base:
    
    - SDK hooks (`useTamboThread`, `useTamboThreadInput`, etc.)
    - Context creation (`React.createContext`)
    - State management that mirrors base component state
    - Event handlers (drag, submit, etc.) that base components handle
    
    ### Step 2: Import Base Components
    
    ```tsx
    import { MessageInput as MessageInputBase } from "@tambo-ai/react-ui-base/message-input";
    ```
    
    ### Step 3: Wrap with Base Root
    
    Replace custom context/state management with the base Root:
    
    ```tsx
    // Before
    const MessageInput = ({ children, variant }) => {
      return (
        <MessageInputInternal variant={variant}>{children}</MessageInputInternal>
      );
    };
    
    // After
    const MessageInput = ({ children, variant, className }) => {
      return (
        <MessageInputBase.Root className={cn(variants({ variant }), className)}>
          {children}
        </MessageInputBase.Root>
      );
    };
    ```
    
    ### Step 4: Apply State-Based Styling and Behavior
    
    State access follows a hierarchy — use the simplest option that works:
    
    1. **Data attributes** (preferred for styling) — base components expose `data-*` attributes
    2. **Render props** (for behavior changes) — use when rendering different components
    3. **Context hooks** (for sub-components) — OK for styled sub-components needing deep context access
    
    ```tsx
    // BEST - data-* classes for styling, render props only for behavior
    // Note: use `data-[dragging]:*` syntax (v3-compatible), not `data-dragging:*` (v4 only)
    const StyledContent = ({ children }) => (
      <BaseComponent.Content
        className={cn(
          "group rounded-xl border",
          "data-[dragging]:border-dashed data-[dragging]:border-emerald-400",
        )}
      >
        {({ elicitation, resolveElicitation }) => (
          <>
            {/* Drop overlay uses group-data-* for styling */}
            <div className="hidden group-data-[dragging]:flex absolute inset-0 bg-emerald-50/90">
              <p>Drop files here</p>
            </div>
    
            {elicitation ? (
              <ElicitationUI
                request={elicitation}
                onResponse={resolveElicitation}
              />
            ) : (
              children
            )}
          </>
        )}
      </BaseComponent.Content>
    );
    
    // OK - styled sub-components can use context hook for deep access
    const StyledTextarea = ({ placeholder }) => {
      const { value, setValue, handleSubmit, editorRef } = useMessageInputContext();
      return (
        <CustomEditor
          ref={editorRef}
          value={value}
          onChange={setValue}
          onSubmit={handleSubmit}
          placeholder={placeholder}
        />
      );
    };
    ```
    
    **When to use context hooks vs render props:**
    
    - Render props: when the parent wrapper needs state for behavior changes
    - Context hooks: when a styled sub-component needs values not exposed via render props
    
    ### Step 5: Wrap Sub-Components
    
    ```tsx
    // Submit button
    const SubmitButton = ({ className, children }) => (
      <BaseComponent.SubmitButton className={cn("w-10 h-10 rounded-lg", className)}>
        {({ showCancelButton }) =>
          children ?? (showCancelButton ? <Square /> : <ArrowUp />)
        }
      </BaseComponent.SubmitButton>
    );
    
    // Error
    const Error = ({ className }) => (
      <BaseComponent.Error className={cn("text-sm text-destructive", className)} />
    );
    
    // Staged images - base pre-computes props array, just iterate
    const StagedImages = ({ className }) => (
      <BaseComponent.StagedImages className={cn("flex gap-2", className)}>
        {({ images }) =>
          images.map((imageProps) => (
            <ImageBadge key={imageProps.image.id} {...imageProps} />
          ))
        }
      </BaseComponent.StagedImages>
    );
    ```
    
    ### Step 6: Final Verification
    
    ```
    Final Checks:
    - [ ] No duplicate context creation
    - [ ] No duplicate SDK hooks in root wrappers
    - [ ] No duplicate state management or event handlers
    - [ ] Base namespace imported and `Base.Root` used as wrapper
    - [ ] `data-*` classes used for styling (with `group-data-*` for children)
    - [ ] Render props used only for rendering behavior changes
    - [ ] Base sub-components wrapped with styling
    - [ ] Icon factories passed from styled layer to base hooks
    - [ ] Visual sub-components and CSS variants stay in styled layer
    ```
    
    ## What Belongs in Styled Layer
    
    ### Icon Factories
    
    When base hooks need icons, pass a factory function:
    
    ```tsx
    // Base hook accepts optional icon factory
    export function useCombinedResourceList(
      providers: ResourceProvider[] | undefined,
      search: string,
      createMcpIcon?: (serverName: string) => React.ReactNode,
    ) {
      /* ... */
    }
    
    // Styled layer provides the factory
    const resources = useCombinedResourceList(providers, search, (serverName) => (
      <McpServerIcon name={serverName} className="w-4 h-4" />
    ));
    ```
    
    ### CSS Variants
    
    ```tsx
    const inputVariants = cva("w-full", {
      variants: {
        variant: {
          default: "",
          solid: "[&>div]:shadow-xl [&>div]:ring-1",
          bordered: "[&>div]:border-2",
        },
      },
    });
    ```
    
    ### Layout Logic, Visual Sub-Components, Custom Data Fetching
    
    These all stay in the styled layer. Base handles behavior; styled handles presentation.
    
    ## Type Handling
    
    Handle ref type differences between base and styled components:
    
    ```tsx
    // Base context may have RefObject<T | null>
    // Styled component may need RefObject<T>
    <TextEditor ref={editorRef as React.RefObject<TamboEditor>} />
    ```
    
    ## Anti-Patterns
    
    - **Re-implementing base logic** - if base handles it, compose it
    - **Using render props for styling** - prefer `data-*` classes; render props are for behavior changes
    - **Duplicating context in wrapper** - use base Root which provides context
    - **Hardcoding icons in base hooks** - use factory functions to keep styling in styled layer
    
  • .claude/skills/ai-sdk-model-manager/SKILL.mdskill
    Show content (7767 bytes)
    ---
    name: ai-sdk-model-manager
    description: Manages AI SDK model configurations - updates packages, identifies missing models, adds new models with research, and updates documentation
    metadata:
      internal: true
    ---
    
    # AI SDK Model Manager
    
    This skill helps maintain AI SDK model configurations in the Tambo Cloud codebase. It automates the process of keeping model definitions up-to-date with the latest AI SDK releases.
    
    ## What This Skill Does
    
    1. **Updates AI SDK Packages** - Checks and updates @ai-sdk/openai, @ai-sdk/google, @ai-sdk/groq, and other provider packages to their latest versions
    2. **Identifies Missing Models** - Compares TypeScript definitions in the SDKs against configured models to find newly available models
    3. **Researches Models** - Gathers information about new models including capabilities, context windows, pricing, and use cases
    4. **Prompts User** - Asks which models to add before making changes
    5. **Adds Models** - Updates model configuration files with proper TypeScript types and metadata
    6. **Updates Documentation** - Updates relevant docs and README files to reflect new model availability
    
    ## When to Use This Skill
    
    Use this skill when:
    
    - You want to check if AI SDK packages need updating
    - New models have been released by OpenAI, Google, Anthropic, or other providers
    - You're getting TypeScript errors about model IDs not being in SDK types
    - You want to ensure Tambo supports the latest models
    
    ## Files This Skill Works With
    
    - `packages/core/src/llms/models/*.ts` - Model configuration files
    - `packages/backend/package.json` - AI SDK dependencies (source of truth for versions)
    - `docs/content/docs/models/*.mdx` - Model documentation
    - `README.md` - Main documentation file
    
    ## Process
    
    ### Step 1: Update AI SDK Packages
    
    Check current versions and update to latest:
    
    ```bash
    cd packages/backend
    npm outdated | grep '@ai-sdk'
    npm install @ai-sdk/openai@latest @ai-sdk/google@latest @ai-sdk/groq@latest @ai-sdk/anthropic@latest @ai-sdk/mistral@latest
    ```
    
    ### Step 2: Identify Missing Models
    
    For each provider, inspect the TypeScript definitions:
    
    ```bash
    # Check what models are in the SDK types
    cat node_modules/@ai-sdk/openai/dist/index.d.ts | grep 'type.*ModelId'
    cat node_modules/@ai-sdk/google/dist/index.d.ts | grep 'type.*ModelId'
    cat node_modules/@ai-sdk/groq/dist/index.d.ts | grep 'type.*ModelId'
    ```
    
    Compare against current model configurations in:
    
    - `packages/core/src/llms/models/openai.ts`
    - `packages/core/src/llms/models/gemini.ts`
    - `packages/core/src/llms/models/groq.ts`
    - `packages/core/src/llms/models/anthropic.ts`
    - `packages/core/src/llms/models/mistral.ts`
    
    ### Step 3: Research New Models
    
    **Use the researcher subagent to gather information about each missing model:**
    
    ```
    Launch a researcher subagent to find:
    - Official documentation link
    - Model capabilities (reasoning, vision, function calling, etc.)
    - Context window size (inputTokenLimit)
    - Pricing tier
    - Best use cases
    - Release date and status (experimental, stable, deprecated)
    ```
    
    The researcher subagent has access to web search and can efficiently gather this information for multiple models in parallel.
    
    ### Step 4: Prompt User
    
    Present findings:
    
    ```
    Found the following new models in updated AI SDK packages:
    
    OpenAI:
    - gpt-6-preview (200k context, experimental reasoning model)
    - gpt-4.2-turbo (1M context, improved function calling)
    
    Google:
    - gemini-3.5-pro (2M context, advanced reasoning)
    
    Which models would you like to add? (all/none/specific)
    ```
    
    Wait for user response before proceeding.
    
    ### Step 5: Add Selected Models
    
    **Consider launching parallel subagents to add models to each provider file:**
    
    For models spread across multiple providers (OpenAI, Google, Groq), launch separate subagents to edit each file concurrently. This is faster than doing them sequentially.
    
    For each model being added, ensure these required fields:
    
    - `apiName`: Exact model ID string from SDK
    - `displayName`: Human-friendly name
    - `status`: "untested" | "tested" | "known-issues"
    - `notes`: Brief description of capabilities and use cases
    - `docLink`: Official provider documentation URL
    - `tamboDocLink`: "https://docs.tambo.co"
    - `inputTokenLimit`: Context window size in tokens
    - `modelSpecificParams`: Any special parameters (reasoning, thinking, etc.)
    
    Follow existing patterns in each file and ensure model IDs match SDK type definitions exactly.
    
    Example:
    
    ```typescript
    "gpt-6-preview": {
      apiName: "gpt-6-preview",
      displayName: "gpt-6-preview",
      status: "untested",
      notes: "Experimental next-generation reasoning model with extended context",
      docLink: "https://platform.openai.com/docs/models/gpt-6-preview",
      tamboDocLink: "https://docs.tambo.co",
      inputTokenLimit: 200000,
      modelSpecificParams: reasoningParameters,
    },
    ```
    
    ### Step 6: Verify TypeScript
    
    Run type checking to ensure all model IDs are valid:
    
    ```bash
    cd packages/core
    npm run check-types
    ```
    
    If there are type errors, fix model IDs to match SDK definitions exactly.
    
    ### Step 7: Update Documentation
    
    **Consider using subagents to update documentation in parallel:**
    
    If updating multiple documentation files, launch parallel subagents to handle:
    
    1. **README.md** - Update the "Supported LLM Providers" section if new providers or significant models were added
    2. **docs/content/docs/models/\*.mdx** - Add new models to appropriate documentation pages with:
       - Model name and description
       - Key capabilities
       - Context window
       - Example use cases
       - Links to provider docs
    
    ### Step 8: Run Quality Checks
    
    Before completing:
    
    ```bash
    cd packages/core
    npm run lint
    npm run check-types
    npm run test
    ```
    
    ### Step 9: Create Pull Request
    
    **Create a PR with proper conventional commit format:**
    
    ```bash
    gh pr create --title "feat(models): add [model names] support" --body "$(cat <<'EOF'
    Updated AI SDK packages and added support for newly released models:
    
    Models added:
    - [list models here]
    
    Package updates:
    - @ai-sdk/openai: X.X.X → X.X.X
    - @ai-sdk/groq: X.X.X → X.X.X
    
    All type checks passing, documentation updated.
    EOF
    )"
    ```
    
    **PR title format:** `feat(models): add [model names] support`
    
    Use `feat(models):` for new models or `deps(core):` for package updates only.
    
    ## Guidelines
    
    - **Use subagents for efficiency** - Launch researcher subagents for gathering information and parallel subagents for editing multiple files
    - **Always research before adding** - Don't guess at model capabilities or context limits
    - **Match SDK types exactly** - Model IDs must match the TypeScript definitions in node_modules
    - **Mark new models as "untested"** - Let the team test before marking as "tested"
    - **Include official doc links** - Always link to provider's official documentation
    - **Be conservative** - Only add models the user explicitly approves
    - **Update docs comprehensively** - Don't just update code, update all relevant documentation
    
    ## Error Handling
    
    If you encounter:
    
    - **Type errors after adding models** - Double-check the model ID matches the SDK's TypeScript definition exactly
    - **Missing model in SDK** - The provider may not have released it yet, suggest waiting for next SDK update
    - **Conflicting model names** - Use the SDK's preferred naming convention
    - **Unknown context limits** - Research provider docs or mark as "unknown" and note it needs verification
    
    ## Notes
    
    - This skill should be run periodically (monthly or when new models are announced)
    - Always check the git diff before committing to ensure only intended changes were made
    - Some models may have special requirements (API access, pricing tier, etc.) - note these in the model's `notes` field
    - If a model is renamed in the SDK, update both the key and apiName, and consider adding a deprecation note to the old entry
    
  • .claude/skills/compound-components/SKILL.mdskill
    Show content (8331 bytes)
    ---
    name: building-compound-components
    description: Creates unstyled compound components that separate business logic from styles. Use when building headless UI primitives, creating component libraries, implementing Radix-style namespaced components, or when the user mentions "compound components", "headless", "unstyled", "primitives", or "render props".
    metadata:
      internal: true
    ---
    
    # Building Compound Components
    
    Create unstyled, composable React components following the Radix UI / Base UI pattern. Components expose behavior via context while consumers control rendering.
    
    ## Project Rules
    
    These rules are specific to this codebase and override general patterns.
    
    ### Hooks Are Internal
    
    Hooks are implementation details, not public API. **Never export hooks from the index.**
    
    ```tsx
    // index.tsx - CORRECT
    export const Component = {
      Root: ComponentRoot,
      Content: ComponentContent,
    };
    export type { ComponentRootProps, ComponentContentRenderProps };
    
    // index.tsx - WRONG
    export { useComponentContext }; // Don't export hooks
    ```
    
    Consumers access state via **render props**, not hooks. When styled wrappers in the **same package** need hook access, import directly from the source file:
    
    ```tsx
    import { useComponentContext } from "../base/component/component-context";
    ```
    
    ### No Custom Data Fetching in Primitives
    
    Base components can use `@tambo-ai/react` SDK hooks (components require Tambo provider anyway). **Custom data fetching logic** (combining sources, external providers) belongs in the styled layer.
    
    ```tsx
    // OK - SDK hooks in primitive
    const Root = ({ children }) => {
      const { value, setValue, submit } = useTamboThreadInput();
      const { isIdle, cancel } = useTamboThread();
      return <Context.Provider value={{ value, setValue, isIdle }}>{children}</Context.Provider>;
    };
    
    // WRONG - custom data fetching in primitive
    const Textarea = ({ resourceProvider }) => {
      const { data: mcpResources } = useTamboMcpResourceList(search);
      const externalResources = useFetchExternal(resourceProvider);
      const combined = [...mcpResources, ...externalResources];
      return <div>{combined.map(...)}</div>;
    };
    ```
    
    ### Pre-computed Props Arrays for Collections
    
    When exposing collections via render props, **pre-compute all props in a memoized array** rather than providing a getter function.
    
    ```tsx
    // AVOID - getter function pattern
    const Items = ({ children }) => {
      const { rawItems, selectedId, removeItem } = useContext();
      const getItemProps = (index: number) => ({
        /* new object every call */
      });
      return children({ items: rawItems, getItemProps });
    };
    
    // PREFERRED - pre-computed array
    const Items = ({ children }) => {
      const { rawItems, selectedId, removeItem } = useContext();
    
      const items = React.useMemo<ItemRenderProps[]>(
        () =>
          rawItems.map((item, index) => ({
            item,
            index,
            isSelected: selectedId === item.id,
            onSelect: () => setSelectedId(item.id),
            onRemove: () => removeItem(item.id),
          })),
        [rawItems, selectedId, removeItem],
      );
    
      return children({ items });
    };
    ```
    
    ## Workflow
    
    Copy this checklist and track progress:
    
    ```
    Compound Component Progress:
    - [ ] Step 1: Create context file
    - [ ] Step 2: Create Root component
    - [ ] Step 3: Create consumer components
    - [ ] Step 4: Create namespace export (index.tsx)
    - [ ] Step 5: Verify all guidelines met
    ```
    
    ### Step 1: Create context file
    
    ```
    my-component/
    ├── index.tsx
    ├── component-context.tsx
    ├── component-root.tsx
    ├── component-item.tsx
    └── component-content.tsx
    ```
    
    Create a context with a null default and a hook that throws on missing provider:
    
    ```tsx
    // component-context.tsx
    const ComponentContext = React.createContext<ComponentContextValue | null>(
      null,
    );
    
    export function useComponentContext() {
      const context = React.useContext(ComponentContext);
      if (!context) {
        throw new Error("Component parts must be used within Component.Root");
      }
      return context;
    }
    
    export { ComponentContext };
    ```
    
    ### Step 2: Create Root component
    
    Root manages state and provides context. Use `forwardRef`, support `asChild` via Radix `Slot`, and expose state via data attributes:
    
    ```tsx
    // component-root.tsx
    export const ComponentRoot = React.forwardRef<
      HTMLDivElement,
      ComponentRootProps
    >(({ asChild, defaultOpen = false, children, ...props }, ref) => {
      const [isOpen, setIsOpen] = React.useState(defaultOpen);
      const Comp = asChild ? Slot : "div";
    
      return (
        <ComponentContext.Provider
          value={{ isOpen, toggle: () => setIsOpen(!isOpen) }}
        >
          <Comp ref={ref} data-state={isOpen ? "open" : "closed"} {...props}>
            {children}
          </Comp>
        </ComponentContext.Provider>
      );
    });
    ComponentRoot.displayName = "Component.Root";
    ```
    
    ### Step 3: Create consumer components
    
    Choose the composition pattern based on need:
    
    **Direct children** (simplest, for static content):
    
    ```tsx
    const Content = ({ children, className, ...props }) => {
      const { data } = useComponentContext();
      return (
        <div className={className} {...props}>
          {children}
        </div>
      );
    };
    ```
    
    **Render prop** (when consumer needs internal state):
    
    ```tsx
    const Content = ({ children, ...props }) => {
      const { data, isLoading } = useComponentContext();
      const content =
        typeof children === "function" ? children({ data, isLoading }) : children;
      return <div {...props}>{content}</div>;
    };
    ```
    
    **Sub-context** (for lists where each item needs own context):
    
    ```tsx
    const Steps = ({ children }) => {
      const { reasoning } = useMessageContext();
      return (
        <StepsContext.Provider value={{ steps: reasoning }}>
          {children}
        </StepsContext.Provider>
      );
    };
    
    const Step = ({ children, index }) => {
      const { steps } = useStepsContext();
      return (
        <StepContext.Provider value={{ step: steps[index], index }}>
          {children}
        </StepContext.Provider>
      );
    };
    ```
    
    ### Step 4: Create namespace export
    
    ```tsx
    // index.tsx
    export const Component = {
      Root: ComponentRoot,
      Trigger: ComponentTrigger,
      Content: ComponentContent,
    };
    
    // Re-export types only - never hooks
    export type { ComponentRootProps } from "./component-root";
    export type { ComponentContentProps } from "./component-content";
    ```
    
    ### Step 5: Verify guidelines
    
    - **No styles in primitives** - consumers control all styling via className/props
    - **Data attributes for CSS** - expose state like `data-state="open"`, `data-disabled`, `data-loading`
    - **Support asChild** - let consumers swap the underlying element via Radix `Slot`
    - **Forward refs** - always use `forwardRef`
    - **Display names** - set for DevTools (`Component.Root`, `Component.Item`)
    - **Throw on missing context** - fail fast with clear error messages
    - **Export types** - consumers need `ComponentProps`, `RenderProps` interfaces
    - **Hooks stay internal** - never export from index, expose state via render props
    - **SDK hooks OK, custom fetching not** - `@tambo-ai/react` hooks are fine, combining logic goes in styled layer
    - **Pre-compute collection props** - use `useMemo` arrays, not getter functions
    
    ## Pattern Selection
    
    | Scenario             | Pattern         | Why                             |
    | -------------------- | --------------- | ------------------------------- |
    | Static content       | Direct children | Simplest, most flexible         |
    | Need internal state  | Render prop     | Explicit state access           |
    | List/iteration       | Sub-context     | Each item gets own context      |
    | Element polymorphism | asChild         | Change underlying element       |
    | CSS-only styling     | Data attributes | No JS needed for style variants |
    
    ## Anti-Patterns
    
    - **Hardcoded styles** - primitives should be unstyled
    - **Prop drilling** - use context instead
    - **Missing error boundaries** - throw when context is missing
    - **Inline functions in render prop types** - define proper interfaces
    - **Default exports** - use named exports in namespace object
    - **Exporting hooks** - hooks are internal; expose state via render props
    - **Custom data fetching in primitives** - SDK hooks are fine, but combining/external fetching belongs in styled layer
    - **Re-implementing base logic** - styled wrappers should compose, not duplicate
    - **Getter functions for collections** - pre-compute props arrays in useMemo instead
    
  • .claude-plugin/marketplace.jsonmarketplace
    Show content (598 bytes)
    {
      "name": "tambo-marketplace",
      "owner": {
        "name": "Tambo AI",
        "email": "support@tambo.co"
      },
      "metadata": {
        "description": "Official Tambo plugins for Claude Code",
        "homepage": "https://tambo.co"
      },
      "plugins": [
        {
          "name": "tambo",
          "source": "./plugins/tambo",
          "description": "Build agents that speak your UI",
          "version": "1.0.0",
          "author": {
            "name": "Tambo AI",
            "email": "support@tambo.co"
          },
          "category": "frameworks",
          "keywords": ["ai", "react", "generative-ui", "components", "streaming"]
        }
      ]
    }
    

README

Tambo AI

Build agents that speak your UI

The open-source generative UI toolkit for React. Connect your components—Tambo handles streaming, state management, and MCP.

npm version License Last Commit Discord GitHub stars

tambo-ai/tambo | Trendshift

Start For FreeDocsDiscord


Tambo 1.0 is here! Read the announcement: Introducing Tambo: Generative UI for React


Table of Contents

What is Tambo?

Tambo is a React toolkit for building agents that render UI (also known as generative UI).

Register your components with Zod schemas. The agent picks the right one and streams the props so users can interact with them. "Show me sales by region" renders your <Chart>. "Add a task" updates your <TaskBoard>.

Get started in 5 minutes →

https://github.com/user-attachments/assets/8381d607-b878-4823-8b24-ecb8053bef23

What's Included

Tambo is a fullstack solution for adding generative UI to your app. You get a React SDK plus a backend that handles conversation state and agent execution.

1. Agent included — Tambo runs the LLM conversation loop for you. Bring your own API key (OpenAI, Anthropic, Gemini, Mistral, or any OpenAI-compatible provider). Works with agent frameworks like LangChain and Mastra, but they're not required.

2. Streaming infrastructure — Props stream to your components as the LLM generates them. Cancellation, error recovery, and reconnection are handled for you.

3. Tambo Cloud or self-host — Cloud is a hosted backend that manages conversation state and agent orchestration. Self-hosted runs the same backend on your infrastructure via Docker.

Most software is built around a one-size-fits-all mental model. We built Tambo to help developers build software that adapts to users.

Get Started

npm create tambo-app my-tambo-app  # auto-initializes git + tambo setup
cd my-tambo-app
npm run dev

Tambo Cloud is a hosted backend, free to get started with plenty of credits to start building. Self-hosted runs on your own infrastructure.

Check out the pre-built component library for agent and generative UI primitives:

https://github.com/user-attachments/assets/6cbc103b-9cc7-40f5-9746-12e04c976dff

Or fork a template:

TemplateDescription
AI Chat with Generative UIChat interface with dynamic component generation
AI Analytics DashboardAnalytics dashboard with AI-powered visualization

How It Works

Tell the AI which components it can use. Zod schemas define the props. These schemas become LLM tool definitions—the agent calls them like functions and Tambo renders the result.

Generative Components

Render once in response to a message. Charts, summaries, data visualizations.

https://github.com/user-attachments/assets/3bd340e7-e226-4151-ae40-aab9b3660d8b

const components: TamboComponent[] = [
  {
    name: "Graph",
    description: "Displays data as charts using Recharts library",
    component: Graph,
    propsSchema: z.object({
      data: z.array(z.object({ name: z.string(), value: z.number() })),
      type: z.enum(["line", "bar", "pie"]),
    }),
  },
];

Interactable Components

Persist and update as users refine requests. Shopping carts, spreadsheets, task boards.

https://github.com/user-attachments/assets/12d957cd-97f1-488e-911f-0ff900ef4062

const InteractableNote = withInteractable(Note, {
  componentName: "Note",
  description: "A note supporting title, content, and color modifications",
  propsSchema: z.object({
    title: z.string(),
    content: z.string(),
    color: z.enum(["white", "yellow", "blue", "green"]).optional(),
  }),
});

Docs: generative components, interactable components

The Provider

Wrap your app with TamboProvider. You must provide either userKey or userToken to identify the thread owner.

<TamboProvider
  apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
  userKey={currentUserId}
  components={components}
>
  <Chat />
  <InteractableNote id="note-1" title="My Note" content="Start writing..." />
</TamboProvider>

Use userKey for server-side or trusted environments. Use userToken (OAuth access token) for client-side apps where the token contains the user identity. See User Authentication for details.

Docs: provider options

Hooks

useTambo() is the primary hook — it gives you messages, streaming state, and thread management. useTamboThreadInput() handles user input and message submission.

const { messages, isStreaming } = useTambo();
const { value, setValue, submit, isPending } = useTamboThreadInput();

Docs: threads and messages, streaming status, full tutorial

Features

MCP Integrations

Connect to Linear, Slack, databases, or your own MCP servers. Tambo supports the full MCP protocol: tools, prompts, elicitations, and sampling.

import { MCPTransport } from "@tambo-ai/react/mcp";

const mcpServers = [
  {
    name: "filesystem",
    url: "http://localhost:8261/mcp",
    transport: MCPTransport.HTTP,
  },
];

<TamboProvider
  apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
  userKey={currentUserId}
  components={components}
  mcpServers={mcpServers}
>
  <App />
</TamboProvider>;

https://github.com/user-attachments/assets/c7a13915-8fed-4758-be1b-30a60fad0cda

Docs: MCP integration

Local Tools

Sometimes you need functions that run in the browser. DOM manipulation, authenticated fetches, accessing React state. Define them as tools and the AI can call them.

const tools: TamboTool[] = [
  {
    name: "getWeather",
    description: "Fetches weather for a location",
    tool: async (params: { location: string }) =>
      fetch(`/api/weather?q=${encodeURIComponent(params.location)}`).then((r) =>
        r.json(),
      ),
    inputSchema: z.object({
      location: z.string(),
    }),
    outputSchema: z.object({
      temperature: z.number(),
      condition: z.string(),
      location: z.string(),
    }),
  },
];

<TamboProvider
  apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
  userKey={currentUserId}
  tools={tools}
  components={components}
>
  <App />
</TamboProvider>;

Docs: local tools

Context, Auth, and Suggestions

Additional context lets you pass metadata to give the AI better responses. User state, app settings, current page. User authentication passes tokens from your auth provider. Suggestions generates prompts users can click based on what they're doing.

<TamboProvider
  apiKey={process.env.NEXT_PUBLIC_TAMBO_API_KEY!}
  userToken={userToken}
  contextHelpers={{
    selectedItems: () => ({
      key: "selectedItems",
      value: selectedItems.map((i) => i.name).join(", "),
    }),
    currentPage: () => ({ key: "page", value: window.location.pathname }),
  }}
/>
const { suggestions, accept } = useTamboSuggestions({ maxSuggestions: 3 });

suggestions.map((s) => (
  <button key={s.id} onClick={() => accept(s)}>
    {s.title}
  </button>
));

Docs: additional context, user authentication, suggestions

Supported LLM Providers

OpenAI, Anthropic, Cerebras, Google Gemini, Mistral, and any OpenAI-compatible provider. Full list. Missing one? Let us know.

How Tambo Compares

FeatureTamboVercel AI SDKCopilotKitAssistant UI
Component selectionAI decides which components to renderManual tool-to-component mappingVia agent frameworks (LangGraph)Chat-focused tool UI
MCP integrationBuilt-inExperimental (v4.2+)Recently addedRequires AI SDK v5
Persistent stateful componentsYesNoShared state patternsNo
Client-side tool executionDeclarative, automaticManual via onToolCallAgent-side onlyNo
Self-hostableMIT (SDK + backend)Apache 2.0 (SDK only)MITMIT
Hosted optionTambo CloudNoCopilotKit CloudAssistant Cloud
Best forFull app UI controlStreaming and tool abstractionsMulti-agent workflowsChat interfaces

Community

Join the Discord to chat with other developers and the core team.

Interested in contributing? Read the Contributing Guide.

Join the conversation on Twitter and follow @tambo_ai.

License

MIT unless otherwise noted. Some workspaces (like apps/api) are Apache-2.0.


Tambo AI Animation

For AI/LLM agents: docs.tambo.co/llms.txt