{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-roster",
  "title": "AI Agent Roster",
  "description": "Display a directory of AI agents with status indicators, match patterns, and selection support. Supports grid and list layouts for multi-agent orchestration UIs.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/ai/ai-agent-roster/components/elements/ai-agent-roster.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  Bot,\n  Check,\n  ChevronRight,\n  Clock,\n  Cpu,\n  Loader2,\n  Power,\n} from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype AgentStatus = \"active\" | \"idle\" | \"busy\" | \"offline\";\n\ninterface RosterAgent {\n  id: string;\n  name: string;\n  description?: string;\n  matchOn?: string[];\n  status: AgentStatus;\n  icon?: React.ReactNode;\n  model?: string;\n}\n\ninterface AiAgentRosterContextValue {\n  agents: RosterAgent[];\n  activeAgentId?: string;\n  layout: \"grid\" | \"list\";\n  onSelect?: (agentId: string) => void;\n}\n\nconst AiAgentRosterContext =\n  React.createContext<AiAgentRosterContextValue | null>(null);\n\nfunction useAgentRosterContext() {\n  const context = React.useContext(AiAgentRosterContext);\n  if (!context) {\n    throw new Error(\n      \"AiAgentRoster components must be used within <AiAgentRoster>\",\n    );\n  }\n  return context;\n}\n\ninterface AiAgentRosterProps {\n  agents: RosterAgent[];\n  activeAgentId?: string;\n  onSelect?: (agentId: string) => void;\n  layout?: \"grid\" | \"list\";\n  children?: React.ReactNode;\n  className?: string;\n}\n\nfunction AiAgentRoster({\n  agents,\n  activeAgentId,\n  onSelect,\n  layout = \"grid\",\n  children,\n  className,\n}: AiAgentRosterProps) {\n  const contextValue = React.useMemo(\n    () => ({ agents, activeAgentId, layout, onSelect }),\n    [agents, activeAgentId, layout, onSelect],\n  );\n\n  return (\n    <AiAgentRosterContext.Provider value={contextValue}>\n      <div\n        data-slot=\"ai-agent-roster\"\n        data-layout={layout}\n        className={cn(\n          \"rounded-lg border border-border bg-card text-card-foreground\",\n          className,\n        )}\n      >\n        {children || <AiAgentRosterContent />}\n      </div>\n    </AiAgentRosterContext.Provider>\n  );\n}\n\ninterface AiAgentRosterHeaderProps {\n  children?: React.ReactNode;\n  className?: string;\n}\n\nfunction AiAgentRosterHeader({\n  children,\n  className,\n}: AiAgentRosterHeaderProps) {\n  const { agents } = useAgentRosterContext();\n\n  const activeCount = React.useMemo(\n    () =>\n      agents.filter((a) => a.status === \"active\" || a.status === \"busy\").length,\n    [agents],\n  );\n\n  return (\n    <div\n      data-slot=\"ai-agent-roster-header\"\n      className={cn(\n        \"flex items-center justify-between border-b border-border px-4 py-3\",\n        className,\n      )}\n    >\n      <div className=\"flex items-center gap-2\">\n        <div className=\"flex size-8 shrink-0 items-center justify-center rounded-md bg-muted\">\n          <Bot className=\"size-4 text-muted-foreground\" />\n        </div>\n        <div>\n          <h3 className=\"font-semibold text-sm\">\n            {children || \"Agent Roster\"}\n          </h3>\n          <p className=\"text-xs text-muted-foreground\">\n            {activeCount} of {agents.length} active\n          </p>\n        </div>\n      </div>\n    </div>\n  );\n}\n\ninterface AiAgentRosterContentProps {\n  children?: React.ReactNode;\n  className?: string;\n}\n\nfunction AiAgentRosterContent({\n  children,\n  className,\n}: AiAgentRosterContentProps) {\n  const { agents, layout } = useAgentRosterContext();\n\n  return (\n    <div\n      data-slot=\"ai-agent-roster-content\"\n      className={cn(\n        \"p-4\",\n        layout === \"grid\" ? \"grid gap-3 sm:grid-cols-2\" : \"flex flex-col gap-2\",\n        className,\n      )}\n    >\n      {children ||\n        agents.map((agent) => <AiAgentCard key={agent.id} agent={agent} />)}\n    </div>\n  );\n}\n\ninterface AiAgentCardProps {\n  agent: RosterAgent;\n  className?: string;\n}\n\nfunction AiAgentCard({ agent, className }: AiAgentCardProps) {\n  const { activeAgentId, onSelect } = useAgentRosterContext();\n\n  const isActive = activeAgentId === agent.id;\n\n  const statusConfig = React.useMemo(() => {\n    const configs: Record<\n      AgentStatus,\n      {\n        icon: React.ReactNode;\n        label: string;\n        className: string;\n        dotClassName: string;\n      }\n    > = {\n      active: {\n        icon: <Check className=\"size-3\" />,\n        label: \"Active\",\n        className: \"text-green-700 dark:text-green-400\",\n        dotClassName: \"bg-green-500\",\n      },\n      idle: {\n        icon: <Clock className=\"size-3\" />,\n        label: \"Idle\",\n        className: \"text-muted-foreground\",\n        dotClassName: \"bg-muted-foreground\",\n      },\n      busy: {\n        icon: <Loader2 className=\"size-3 animate-spin\" />,\n        label: \"Busy\",\n        className: \"text-blue-700 dark:text-blue-400\",\n        dotClassName: \"bg-blue-500 animate-pulse\",\n      },\n      offline: {\n        icon: <Power className=\"size-3\" />,\n        label: \"Offline\",\n        className: \"text-muted-foreground/50\",\n        dotClassName: \"bg-muted-foreground/50\",\n      },\n    };\n    return configs[agent.status];\n  }, [agent.status]);\n\n  const handleClick = React.useCallback(() => {\n    if (onSelect && agent.status !== \"offline\") {\n      onSelect(agent.id);\n    }\n  }, [onSelect, agent.id, agent.status]);\n\n  const handleKeyDown = React.useCallback(\n    (e: React.KeyboardEvent) => {\n      if (\n        (e.key === \"Enter\" || e.key === \" \") &&\n        onSelect &&\n        agent.status !== \"offline\"\n      ) {\n        e.preventDefault();\n        onSelect(agent.id);\n      }\n    },\n    [onSelect, agent.id, agent.status],\n  );\n\n  return (\n    <div\n      data-slot=\"ai-agent-card\"\n      data-status={agent.status}\n      data-active={isActive}\n      role={onSelect ? \"button\" : undefined}\n      tabIndex={onSelect && agent.status !== \"offline\" ? 0 : undefined}\n      onClick={handleClick}\n      onKeyDown={handleKeyDown}\n      className={cn(\n        \"group relative flex flex-col gap-2 rounded-lg border bg-background p-3 transition-all\",\n        isActive\n          ? \"border-primary ring-2 ring-primary/20\"\n          : \"border-border hover:border-muted-foreground/30\",\n        onSelect && agent.status !== \"offline\" && \"cursor-pointer\",\n        agent.status === \"offline\" && \"opacity-60\",\n        className,\n      )}\n    >\n      <div className=\"flex items-start justify-between gap-2\">\n        <div className=\"flex items-center gap-2\">\n          <div\n            className={cn(\n              \"flex size-9 shrink-0 items-center justify-center rounded-md transition-colors\",\n              isActive\n                ? \"bg-primary text-primary-foreground\"\n                : \"bg-muted text-muted-foreground\",\n            )}\n          >\n            {agent.icon || <Cpu className=\"size-4\" />}\n          </div>\n          <div className=\"min-w-0\">\n            <h4 className=\"font-medium text-sm truncate\">{agent.name}</h4>\n            {agent.model && (\n              <p className=\"text-xs text-muted-foreground font-mono truncate\">\n                {agent.model}\n              </p>\n            )}\n          </div>\n        </div>\n        <div\n          className={cn(\n            \"flex items-center gap-1.5 text-xs font-medium\",\n            statusConfig.className,\n          )}\n        >\n          <span\n            className={cn(\"size-2 rounded-full\", statusConfig.dotClassName)}\n          />\n          <span className=\"hidden sm:inline\">{statusConfig.label}</span>\n        </div>\n      </div>\n\n      {agent.description && (\n        <p className=\"text-xs text-muted-foreground line-clamp-2\">\n          {agent.description}\n        </p>\n      )}\n\n      {agent.matchOn && agent.matchOn.length > 0 && (\n        <div className=\"flex flex-wrap gap-1\">\n          {agent.matchOn.slice(0, 3).map((pattern, index) => (\n            <span\n              key={index}\n              className=\"inline-flex items-center rounded-md bg-muted px-1.5 py-0.5 text-[10px] font-mono text-muted-foreground\"\n            >\n              {pattern}\n            </span>\n          ))}\n          {agent.matchOn.length > 3 && (\n            <span className=\"inline-flex items-center rounded-md bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground\">\n              +{agent.matchOn.length - 3}\n            </span>\n          )}\n        </div>\n      )}\n\n      {onSelect && agent.status !== \"offline\" && (\n        <ChevronRight\n          className={cn(\n            \"absolute right-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground opacity-0 transition-all group-hover:opacity-100 group-hover:translate-x-0.5\",\n            isActive && \"opacity-100 text-primary\",\n          )}\n        />\n      )}\n    </div>\n  );\n}\n\ninterface AiAgentRosterEmptyProps {\n  children?: React.ReactNode;\n  className?: string;\n}\n\nfunction AiAgentRosterEmpty({ children, className }: AiAgentRosterEmptyProps) {\n  return (\n    <div\n      data-slot=\"ai-agent-roster-empty\"\n      className={cn(\n        \"flex flex-col items-center justify-center py-8 text-center\",\n        className,\n      )}\n    >\n      <div className=\"flex size-12 items-center justify-center rounded-full bg-muted mb-3\">\n        <Bot className=\"size-6 text-muted-foreground\" />\n      </div>\n      <p className=\"text-sm text-muted-foreground\">\n        {children || \"No agents available\"}\n      </p>\n    </div>\n  );\n}\n\nexport {\n  AiAgentRoster,\n  AiAgentRosterHeader,\n  AiAgentRosterContent,\n  AiAgentCard,\n  AiAgentRosterEmpty,\n};\nexport type { AiAgentRosterProps, RosterAgent, AgentStatus };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Compound component for agent directories. Sub-components: AiAgentRoster, AiAgentRosterHeader, AiAgentRosterContent, AiAgentCard, AiAgentRosterEmpty. Agent statuses: active, idle, busy, offline.",
  "categories": [
    "ai"
  ],
  "type": "registry:ui"
}