{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ansi-output",
  "title": "ANSI Output",
  "description": "Components for rendering ANSI escape sequences as colored text in notebook outputs. Includes AnsiOutput, AnsiStreamOutput, and AnsiErrorOutput variants.",
  "dependencies": [
    "anser",
    "escape-carriage"
  ],
  "files": [
    {
      "path": "registry/outputs/ansi-output.tsx",
      "content": "import Anser from \"anser\";\nimport { escapeCarriageReturn } from \"escape-carriage\";\nimport type { CSSProperties, ReactNode } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Theme-aware ANSI color mapping.\n *\n * We call anser's ansiToJson with use_classes: true so it gives us structured\n * data we can remap. The base 16 ANSI colors go through CSS variables so they\n * adapt to light/dark mode. Extended colors (256-color palette and 24-bit\n * truecolor) render as inline rgb() styles since they're already precise.\n *\n * anser returns these shapes in class mode:\n *\n *   Standard 16:    fg = \"ansi-red\",           fg_truecolor = null\n *   256 (0-15):     fg = \"ansi-red\" etc,       fg_truecolor = null\n *   256 (16-255):   fg = \"ansi-palette-123\",   fg_truecolor = null\n *   24-bit RGB:     fg = \"ansi-truecolor\",     fg_truecolor = \"237, 17, 128\"\n *   No color:       fg = null,                 fg_truecolor = null\n */\n\n// The 16 class names anser emits for standard colors.\nconst ANSI_CLASS_NAMES = new Set([\n  \"ansi-black\",\n  \"ansi-red\",\n  \"ansi-green\",\n  \"ansi-yellow\",\n  \"ansi-blue\",\n  \"ansi-magenta\",\n  \"ansi-cyan\",\n  \"ansi-white\",\n  \"ansi-bright-black\",\n  \"ansi-bright-red\",\n  \"ansi-bright-green\",\n  \"ansi-bright-yellow\",\n  \"ansi-bright-blue\",\n  \"ansi-bright-magenta\",\n  \"ansi-bright-cyan\",\n  \"ansi-bright-white\",\n]);\n\nfunction isStandardColor(name: string | null): boolean {\n  return name !== null && ANSI_CLASS_NAMES.has(name);\n}\n\nfunction isPaletteColor(name: string | null): boolean {\n  return !!name?.startsWith(\"ansi-palette-\");\n}\n\n/**\n * Resolve a 256-color palette index to an rgb() string.\n *\n * Indices 0-15 are handled by anser as standard class names.\n * Indices 16-231 are a 6×6×6 color cube.\n * Indices 232-255 are a grayscale ramp.\n */\nfunction paletteIndexToRgb(index: number): string {\n  if (index >= 16 && index <= 231) {\n    const adjusted = index - 16;\n    const r = Math.floor(adjusted / 36);\n    const g = Math.floor((adjusted % 36) / 6);\n    const b = adjusted % 6;\n    return `rgb(${r ? r * 40 + 55 : 0}, ${g ? g * 40 + 55 : 0}, ${b ? b * 40 + 55 : 0})`;\n  }\n  if (index >= 232 && index <= 255) {\n    const level = (index - 232) * 10 + 8;\n    return `rgb(${level}, ${level}, ${level})`;\n  }\n  return \"inherit\";\n}\n\n/**\n * Parse an anser JSON entry's color fields into a React style + className.\n */\nfunction resolveAnsiStyle(entry: Anser.AnserJsonEntry): {\n  style: CSSProperties;\n  className: string;\n} {\n  const style: CSSProperties = {};\n  const classes: string[] = [];\n\n  // Foreground\n  if (entry.fg) {\n    if (isStandardColor(entry.fg)) {\n      // Use CSS variable via class: .ansi-red-fg { color: var(--ansi-red) }\n      classes.push(`${entry.fg}-fg`);\n    } else if (entry.fg === \"ansi-truecolor\" && entry.fg_truecolor) {\n      style.color = `rgb(${entry.fg_truecolor})`;\n    } else if (isPaletteColor(entry.fg)) {\n      const index = parseInt(entry.fg.replace(\"ansi-palette-\", \"\"), 10);\n      style.color = paletteIndexToRgb(index);\n    }\n  }\n\n  // Background\n  if (entry.bg) {\n    if (isStandardColor(entry.bg)) {\n      classes.push(`${entry.bg}-bg`);\n    } else if (entry.bg === \"ansi-truecolor\" && entry.bg_truecolor) {\n      style.backgroundColor = `rgb(${entry.bg_truecolor})`;\n    } else if (isPaletteColor(entry.bg)) {\n      const index = parseInt(entry.bg.replace(\"ansi-palette-\", \"\"), 10);\n      style.backgroundColor = paletteIndexToRgb(index);\n    }\n  }\n\n  // Decorations\n  for (const decoration of entry.decorations) {\n    switch (decoration) {\n      case \"bold\":\n        style.fontWeight = \"bold\";\n        break;\n      case \"dim\":\n        style.opacity = 0.5;\n        break;\n      case \"italic\":\n        style.fontStyle = \"italic\";\n        break;\n      case \"hidden\":\n        style.visibility = \"hidden\";\n        break;\n      case \"strikethrough\":\n        style.textDecoration =\n          style.textDecoration === \"underline\"\n            ? \"underline line-through\"\n            : \"line-through\";\n        break;\n      case \"underline\":\n        style.textDecoration =\n          style.textDecoration === \"line-through\"\n            ? \"underline line-through\"\n            : \"underline\";\n        break;\n    }\n  }\n\n  return { style, className: classes.join(\" \") };\n}\n\n/**\n * Backspace handling ported from ansi-to-react (originally from Jupyter Classic).\n */\nfunction fixBackspace(txt: string): string {\n  let result = txt;\n  let previous: string;\n  do {\n    previous = result;\n    // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional backspace (\\x08) matching for terminal emulation\n    result = result.replace(/[^\\n]\\x08/gm, \"\");\n  } while (result.length < previous.length);\n  return result;\n}\n\n/**\n * Parse ANSI text into structured JSON entries using anser.\n */\nfunction ansiToJson(input: string): Anser.AnserJsonEntry[] {\n  const cleaned = escapeCarriageReturn(fixBackspace(input));\n  return Anser.ansiToJson(cleaned, {\n    json: true,\n    remove_empty: true,\n    use_classes: true,\n  });\n}\n\n/**\n * Render parsed ANSI entries to React spans.\n */\nfunction renderAnsiEntries(entries: Anser.AnserJsonEntry[]): ReactNode[] {\n  return entries.map((entry, i) => {\n    const { style, className } = resolveAnsiStyle(entry);\n    const hasStyle = Object.keys(style).length > 0;\n    const hasClass = className.length > 0;\n\n    if (!hasStyle && !hasClass) {\n      return <span key={i}>{entry.content}</span>;\n    }\n\n    return (\n      <span\n        key={i}\n        style={hasStyle ? style : undefined}\n        className={hasClass ? className : undefined}\n      >\n        {entry.content}\n      </span>\n    );\n  });\n}\n\n// ---------------------------------------------------------------------------\n// Public components\n// ---------------------------------------------------------------------------\n\ninterface AnsiOutputProps {\n  children: string;\n  className?: string;\n  isError?: boolean;\n}\n\n/**\n * AnsiOutput renders ANSI escape sequences as colored text.\n *\n * Standard 16 colors use CSS variables (theme-aware, adapts to light/dark).\n * 256-color and 24-bit truecolor use inline rgb() styles for full fidelity.\n */\nexport function AnsiOutput({\n  children,\n  className = \"\",\n  isError = false,\n}: AnsiOutputProps) {\n  if (!children || typeof children !== \"string\") {\n    return null;\n  }\n\n  const entries = ansiToJson(children);\n\n  return (\n    <div\n      data-slot=\"ansi-output\"\n      className={cn(\n        \"not-prose font-mono text-sm whitespace-pre-wrap leading-relaxed\",\n        isError && \"text-red-600 dark:text-red-400\",\n        className,\n      )}\n    >\n      <code>{renderAnsiEntries(entries)}</code>\n    </div>\n  );\n}\n\ninterface AnsiStreamOutputProps {\n  text: string;\n  streamName: \"stdout\" | \"stderr\";\n  className?: string;\n}\n\n/**\n * AnsiStreamOutput component specifically for stdout/stderr rendering.\n */\nexport function AnsiStreamOutput({\n  text,\n  streamName,\n  className = \"\",\n}: AnsiStreamOutputProps) {\n  const isStderr = streamName === \"stderr\";\n  const streamClasses = isStderr\n    ? \"text-red-600 dark:text-red-400\"\n    : \"text-gray-700 dark:text-gray-300\";\n\n  return (\n    <div\n      data-slot=\"ansi-stream-output\"\n      className={cn(\"not-prose py-2\", streamClasses, className)}\n    >\n      <AnsiOutput isError={isStderr}>{text}</AnsiOutput>\n    </div>\n  );\n}\n\ninterface AnsiErrorOutputProps {\n  ename?: string;\n  evalue?: string;\n  traceback?: string[] | string;\n  className?: string;\n}\n\n/**\n * AnsiErrorOutput component specifically for error messages and tracebacks.\n */\nexport function AnsiErrorOutput({\n  ename,\n  evalue,\n  traceback,\n  className = \"\",\n}: AnsiErrorOutputProps) {\n  return (\n    <div\n      data-slot=\"ansi-error-output\"\n      className={cn(\n        \"not-prose border-l-2 border-red-200 dark:border-red-800 py-3 pl-1\",\n        className,\n      )}\n    >\n      {ename && evalue && (\n        <div className=\"mb-1 font-semibold text-red-700 dark:text-red-400\">\n          <AnsiOutput isError>{`${ename}: ${evalue}`}</AnsiOutput>\n        </div>\n      )}\n      {traceback && (\n        <div className=\"mt-2 text-xs text-red-600 dark:text-red-400 opacity-80\">\n          <AnsiOutput isError>\n            {Array.isArray(traceback) ? traceback.join(\"\\n\") : traceback}\n          </AnsiOutput>\n        </div>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/outputs/ansi-output.tsx"
    }
  ],
  "cssVars": {
    "light": {
      "ansi-black": "#3c3c3c",
      "ansi-red": "#c23621",
      "ansi-green": "#25bc24",
      "ansi-yellow": "#adad27",
      "ansi-blue": "#492ee1",
      "ansi-magenta": "#d338d3",
      "ansi-cyan": "#33bbc8",
      "ansi-white": "#cbcccd",
      "ansi-bright-black": "#818383",
      "ansi-bright-red": "#fc391f",
      "ansi-bright-green": "#31e722",
      "ansi-bright-yellow": "#eaec23",
      "ansi-bright-blue": "#5833ff",
      "ansi-bright-magenta": "#f935f8",
      "ansi-bright-cyan": "#14f0f0",
      "ansi-bright-white": "#e9ebeb",
      "ansi-black-bg": "#3c3c3c",
      "ansi-red-bg": "#c23621",
      "ansi-green-bg": "#25bc24",
      "ansi-yellow-bg": "#b5a000",
      "ansi-blue-bg": "#492ee1",
      "ansi-magenta-bg": "#d338d3",
      "ansi-cyan-bg": "#33bbc8",
      "ansi-white-bg": "#cbcccd"
    },
    "dark": {
      "ansi-black": "#6e7681",
      "ansi-red": "#f97583",
      "ansi-green": "#85e89d",
      "ansi-yellow": "#ffea7f",
      "ansi-blue": "#79b8ff",
      "ansi-magenta": "#f692ce",
      "ansi-cyan": "#56d4dd",
      "ansi-white": "#fafbfc",
      "ansi-bright-black": "#959da5",
      "ansi-bright-red": "#fdaeb7",
      "ansi-bright-green": "#bef5cb",
      "ansi-bright-yellow": "#fff5b1",
      "ansi-bright-blue": "#c8e1ff",
      "ansi-bright-magenta": "#fedbf0",
      "ansi-bright-cyan": "#a9f1f7",
      "ansi-bright-white": "#ffffff",
      "ansi-black-bg": "#484f58",
      "ansi-red-bg": "#b31d28",
      "ansi-green-bg": "#176f2c",
      "ansi-yellow-bg": "#735c0f",
      "ansi-blue-bg": "#1158c7",
      "ansi-magenta-bg": "#5a32a3",
      "ansi-cyan-bg": "#0e7481",
      "ansi-white-bg": "#959da5"
    }
  },
  "css": {
    "@layer base": {
      ".ansi-black-fg": {
        "color": "var(--ansi-black)"
      },
      ".ansi-red-fg": {
        "color": "var(--ansi-red)"
      },
      ".ansi-green-fg": {
        "color": "var(--ansi-green)"
      },
      ".ansi-yellow-fg": {
        "color": "var(--ansi-yellow)"
      },
      ".ansi-blue-fg": {
        "color": "var(--ansi-blue)"
      },
      ".ansi-magenta-fg": {
        "color": "var(--ansi-magenta)"
      },
      ".ansi-cyan-fg": {
        "color": "var(--ansi-cyan)"
      },
      ".ansi-white-fg": {
        "color": "var(--ansi-white)"
      },
      ".ansi-bright-black-fg": {
        "color": "var(--ansi-bright-black)"
      },
      ".ansi-bright-red-fg": {
        "color": "var(--ansi-bright-red)"
      },
      ".ansi-bright-green-fg": {
        "color": "var(--ansi-bright-green)"
      },
      ".ansi-bright-yellow-fg": {
        "color": "var(--ansi-bright-yellow)"
      },
      ".ansi-bright-blue-fg": {
        "color": "var(--ansi-bright-blue)"
      },
      ".ansi-bright-magenta-fg": {
        "color": "var(--ansi-bright-magenta)"
      },
      ".ansi-bright-cyan-fg": {
        "color": "var(--ansi-bright-cyan)"
      },
      ".ansi-bright-white-fg": {
        "color": "var(--ansi-bright-white)"
      },
      ".ansi-black-bg": {
        "background-color": "var(--ansi-black-bg)"
      },
      ".ansi-red-bg": {
        "background-color": "var(--ansi-red-bg)"
      },
      ".ansi-green-bg": {
        "background-color": "var(--ansi-green-bg)"
      },
      ".ansi-yellow-bg": {
        "background-color": "var(--ansi-yellow-bg)"
      },
      ".ansi-blue-bg": {
        "background-color": "var(--ansi-blue-bg)"
      },
      ".ansi-magenta-bg": {
        "background-color": "var(--ansi-magenta-bg)"
      },
      ".ansi-cyan-bg": {
        "background-color": "var(--ansi-cyan-bg)"
      },
      ".ansi-white-bg": {
        "background-color": "var(--ansi-white-bg)"
      }
    }
  },
  "type": "registry:component"
}