{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-terminal",
  "title": "AI: Terminal",
  "description": "ANSI terminal output with streaming state, copying and scroll management.",
  "dependencies": [
    "@untitledui/icons@^0.0.23",
    "ansi-to-react@^6.2.6"
  ],
  "registryDependencies": [
    "@nx-ui/utils",
    "@nx-ui/button"
  ],
  "files": [
    {
      "path": "components/ai/terminal.tsx",
      "content": "// Ported from vercel/ai-elements@6a9d5b1822ffb10bba4bd97175f01edd7d8651cd.\n\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport Ansi from \"ansi-to-react\";\nimport { Check as CheckIcon, Copy01 as CopyIcon, Terminal as TerminalIcon, Trash01 as Trash2Icon } from \"@untitledui/icons\";\nimport type { ComponentProps, HTMLAttributes } from \"react\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\n\ninterface TerminalContextType {\n  output: string;\n  isStreaming: boolean;\n  autoScroll: boolean;\n  onClear?: () => void;\n}\n\nconst TerminalContext = createContext<TerminalContextType>({\n  autoScroll: true,\n  isStreaming: false,\n  output: \"\",\n});\n\nexport type TerminalHeaderProps = HTMLAttributes<HTMLDivElement>;\n\nexport const TerminalHeader = ({\n  className,\n  children,\n  ...props\n}: TerminalHeaderProps) => (\n  <div\n    className={cn(\n      \"flex items-center justify-between border-zinc-800 border-b px-4 py-2\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n  </div>\n);\n\nexport type TerminalTitleProps = HTMLAttributes<HTMLDivElement>;\n\nexport const TerminalTitle = ({\n  className,\n  children,\n  ...props\n}: TerminalTitleProps) => (\n  <div\n    className={cn(\"flex items-center gap-2 text-sm text-zinc-400\", className)}\n    {...props}\n  >\n    <TerminalIcon className=\"size-4\" />\n    {children ?? \"Terminal\"}\n  </div>\n);\n\nexport type TerminalStatusProps = HTMLAttributes<HTMLDivElement>;\n\nexport const TerminalStatus = ({\n  className,\n  children,\n  ...props\n}: TerminalStatusProps) => {\n  const { isStreaming } = useContext(TerminalContext);\n\n  if (!isStreaming) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\"flex items-center gap-2 text-xs text-zinc-400\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport type TerminalActionsProps = HTMLAttributes<HTMLDivElement>;\n\nexport const TerminalActions = ({\n  className,\n  children,\n  ...props\n}: TerminalActionsProps) => (\n  <div className={cn(\"flex items-center gap-1\", className)} {...props}>\n    {children}\n  </div>\n);\n\nexport type TerminalCopyButtonProps = ComponentProps<typeof Button> & {\n  onCopy?: () => void;\n  onError?: (error: Error) => void;\n  timeout?: number;\n};\n\nexport const TerminalCopyButton = ({\n  onCopy,\n  onError,\n  timeout = 2000,\n  children,\n  className,\n  \"aria-label\": ariaLabel,\n  ...props\n}: TerminalCopyButtonProps) => {\n  const [isCopied, setIsCopied] = useState(false);\n  const timeoutRef = useRef<number>(0);\n  const { output } = useContext(TerminalContext);\n\n  const copyToClipboard = useCallback(async () => {\n    if (typeof window === \"undefined\" || !navigator?.clipboard?.writeText) {\n      onError?.(new Error(\"Clipboard API not available\"));\n      return;\n    }\n\n    try {\n      await navigator.clipboard.writeText(output);\n      setIsCopied(true);\n      onCopy?.();\n      timeoutRef.current = window.setTimeout(() => setIsCopied(false), timeout);\n    } catch (error) {\n      onError?.(error as Error);\n    }\n  }, [output, onCopy, onError, timeout]);\n\n  useEffect(\n    () => () => {\n      window.clearTimeout(timeoutRef.current);\n    },\n    []\n  );\n\n  const Icon = isCopied ? CheckIcon : CopyIcon;\n\n  return (\n    <Button\n      className={cn(\n        \"size-7 shrink-0 text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100\",\n        className\n      )}\n      onClick={copyToClipboard}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n      aria-label={ariaLabel ?? \"Copy terminal output\"}\n    >\n      {children ?? <Icon size={14} />}\n    </Button>\n  );\n};\n\nexport type TerminalClearButtonProps = ComponentProps<typeof Button>;\n\nexport const TerminalClearButton = ({\n  children,\n  className,\n  \"aria-label\": ariaLabel,\n  ...props\n}: TerminalClearButtonProps) => {\n  const { onClear } = useContext(TerminalContext);\n\n  if (!onClear) {\n    return null;\n  }\n\n  return (\n    <Button\n      className={cn(\n        \"size-7 shrink-0 text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100\",\n        className\n      )}\n      onClick={onClear}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n      aria-label={ariaLabel ?? \"Clear terminal output\"}\n    >\n      {children ?? <Trash2Icon size={14} />}\n    </Button>\n  );\n};\n\nexport type TerminalContentProps = HTMLAttributes<HTMLDivElement>;\n\nexport const TerminalContent = ({\n  className,\n  children,\n  ...props\n}: TerminalContentProps) => {\n  const { output, isStreaming, autoScroll } = useContext(TerminalContext);\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    if (autoScroll && containerRef.current) {\n      containerRef.current.scrollTop = containerRef.current.scrollHeight;\n    }\n  }, [output, autoScroll]);\n\n  return (\n    <div\n      className={cn(\n        \"max-h-96 overflow-auto p-4 font-mono text-sm leading-relaxed\",\n        className\n      )}\n      ref={containerRef}\n      {...props}\n    >\n      {children ?? (\n        <pre className=\"whitespace-pre-wrap break-words\">\n          <Ansi>{output}</Ansi>\n          {isStreaming && (\n            <span className=\"ml-0.5 inline-block h-4 w-2 animate-pulse bg-zinc-100\" />\n          )}\n        </pre>\n      )}\n    </div>\n  );\n};\n\nexport type TerminalProps = HTMLAttributes<HTMLDivElement> & {\n  output: string;\n  isStreaming?: boolean;\n  autoScroll?: boolean;\n  onClear?: () => void;\n};\n\nexport const Terminal = ({\n  output,\n  isStreaming = false,\n  autoScroll = true,\n  onClear,\n  className,\n  children,\n  ...props\n}: TerminalProps) => {\n  const contextValue = useMemo(\n    () => ({ autoScroll, isStreaming, onClear, output }),\n    [autoScroll, isStreaming, onClear, output]\n  );\n\n  return (\n    <TerminalContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          \"flex flex-col overflow-hidden rounded-lg border bg-zinc-950 text-zinc-100\",\n          className\n        )}\n        {...props}\n      >\n        {children ?? (\n          <>\n            <TerminalHeader>\n              <TerminalTitle />\n              <div className=\"flex items-center gap-1\">\n                <TerminalStatus />\n                <TerminalActions>\n                  <TerminalCopyButton />\n                  {onClear && <TerminalClearButton />}\n                </TerminalActions>\n              </div>\n            </TerminalHeader>\n            <TerminalContent />\n          </>\n        )}\n      </div>\n    </TerminalContext.Provider>\n  );\n};\n",
      "type": "registry:ui",
      "target": "components/ai/terminal.tsx"
    }
  ],
  "type": "registry:ui"
}