/
Alex_Ural
/
opencode
Обзор
Документация
Войти
/
Alex_Ural
/
opencode
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
packages/tui/src/theme/index.ts
1 089 строк
27 KB
Dax
refactor(server): canonicalize service API (#31049)
07 июн 2026, 06:27
Не верифицирован
07 июн 2026, 06:27
fe0c4f8
Код
Авторство
О чём код?
import { SyntaxStyle, RGBA, type TerminalColors } from "@opentui/core" import aura from "./assets/aura.json" with { type: "json" } import ayu from "./assets/ayu.json" with { type: "json" } import carbonfox from "./assets/carbonfox.json" with { type: "json" } import catppuccinFrappe from "./assets/catppuccin-frappe.json" with { type: "json" } import catppuccinMacchiato from "./assets/catppuccin-macchiato.json" with { type: "json" } import catppuccin from "./assets/catppuccin.json" with { type: "json" } import cobalt2 from "./assets/cobalt2.json" with { type: "json" } import cursor from "./assets/cursor.json" with { type: "json" } import dracula from "./assets/dracula.json" with { type: "json" } import everforest from "./assets/everforest.json" with { type: "json" } import flexoki from "./assets/flexoki.json" with { type: "json" } import github from "./assets/github.json" with { type: "json" } import gruvbox from "./assets/gruvbox.json" with { type: "json" } import kanagawa from "./assets/kanagawa.json" with { type: "json" } import lucentOrng from "./assets/lucent-orng.json" with { type: "json" } import material from "./assets/material.json" with { type: "json" } import matrix from "./assets/matrix.json" with { type: "json" } import mercury from "./assets/mercury.json" with { type: "json" } import monokai from "./assets/monokai.json" with { type: "json" } import nightowl from "./assets/nightowl.json" with { type: "json" } import nord from "./assets/nord.json" with { type: "json" } import onedark from "./assets/one-dark.json" with { type: "json" } import opencode from "./assets/opencode.json" with { type: "json" } import orng from "./assets/orng.json" with { type: "json" } import osakaJade from "./assets/osaka-jade.json" with { type: "json" } import palenight from "./assets/palenight.json" with { type: "json" } import rosepine from "./assets/rosepine.json" with { type: "json" } import solarized from "./assets/solarized.json" with { type: "json" } import synthwave84 from "./assets/synthwave84.json" with { type: "json" } import tokyonight from "./assets/tokyonight.json" with { type: "json" } import vercel from "./assets/vercel.json" with { type: "json" } import vesper from "./assets/vesper.json" with { type: "json" } import zenburn from "./assets/zenburn.json" with { type: "json" } export type Theme = { readonly primary: RGBA readonly secondary: RGBA readonly accent: RGBA readonly error: RGBA readonly warning: RGBA readonly success: RGBA readonly info: RGBA readonly text: RGBA readonly textMuted: RGBA readonly selectedListItemText: RGBA readonly background: RGBA readonly backgroundPanel: RGBA readonly backgroundElement: RGBA readonly backgroundMenu: RGBA readonly border: RGBA readonly borderActive: RGBA readonly borderSubtle: RGBA readonly diffAdded: RGBA readonly diffRemoved: RGBA readonly diffContext: RGBA readonly diffHunkHeader: RGBA readonly diffHighlightAdded: RGBA readonly diffHighlightRemoved: RGBA readonly diffAddedBg: RGBA readonly diffRemovedBg: RGBA readonly diffContextBg: RGBA readonly diffLineNumber: RGBA readonly diffAddedLineNumberBg: RGBA readonly diffRemovedLineNumberBg: RGBA readonly markdownText: RGBA readonly markdownHeading: RGBA readonly markdownLink: RGBA readonly markdownLinkText: RGBA readonly markdownCode: RGBA readonly markdownBlockQuote: RGBA readonly markdownEmph: RGBA readonly markdownStrong: RGBA readonly markdownHorizontalRule: RGBA readonly markdownListItem: RGBA readonly markdownListEnumeration: RGBA readonly markdownImage: RGBA readonly markdownImageText: RGBA readonly markdownCodeBlock: RGBA readonly syntaxComment: RGBA readonly syntaxKeyword: RGBA readonly syntaxFunction: RGBA readonly syntaxVariable: RGBA readonly syntaxString: RGBA readonly syntaxNumber: RGBA readonly syntaxType: RGBA readonly syntaxOperator: RGBA readonly syntaxPunctuation: RGBA readonly thinkingOpacity: number _hasSelectedListItemText: boolean } type ThemeColor = Exclude<keyof Theme, "thinkingOpacity" | "_hasSelectedListItemText"> export type SyntaxStyleOverrides = Record<string, { italic?: boolean }> export function selectedForeground(theme: Theme, bg?: RGBA): RGBA { // If theme explicitly defines selectedListItemText, use it if (theme._hasSelectedListItemText) { return theme.selectedListItemText } // For transparent backgrounds, calculate contrast based on the actual bg (or fallback to primary) if (theme.background.a === 0) { const targetColor = bg ?? theme.primary const { r, g, b } = targetColor const luminance = 0.299 * r + 0.587 * g + 0.114 * b return luminance > 0.5 ? RGBA.fromInts(0, 0, 0) : RGBA.fromInts(255, 255, 255) } // Fall back to background color return theme.background } type HexColor = `#${string}` type RefName = string type Variant = { dark: HexColor | RefName light: HexColor | RefName } type ColorValue = HexColor | RefName | Variant | RGBA export type ThemeJson = { $schema?: string defs?: Record<string, HexColor | RefName> theme: Omit<Record<ThemeColor, ColorValue>, "selectedListItemText" | "backgroundMenu"> & { selectedListItemText?: ColorValue backgroundMenu?: ColorValue thinkingOpacity?: number } } export const DEFAULT_THEMES: Record<string, ThemeJson> = { aura, ayu, catppuccin, ["catppuccin-frappe"]: catppuccinFrappe, ["catppuccin-macchiato"]: catppuccinMacchiato, cobalt2, cursor, dracula, everforest, flexoki, github, gruvbox, kanagawa, material, matrix, mercury, monokai, nightowl, nord, ["one-dark"]: onedark, ["osaka-jade"]: osakaJade, opencode, orng, ["lucent-orng"]: lucentOrng, palenight, rosepine, solarized, synthwave84, tokyonight, vesper, vercel, zenburn, carbonfox, } const pluginThemes: Record<string, ThemeJson> = {} let customThemes: Record<string, ThemeJson> = {} let systemTheme: ThemeJson | undefined const listeners = new Set<(themes: Record<string, ThemeJson>) => void>() function listThemes() { // Priority: defaults < plugin installs < custom files < generated system. const themes = { ...DEFAULT_THEMES, ...pluginThemes, ...customThemes, } if (!systemTheme) return themes return { ...themes, system: systemTheme, } } function syncThemes() { const themes = listThemes() for (const listener of listeners) listener(themes) } export function allThemes() { return listThemes() } export function isTheme(theme: unknown): theme is ThemeJson { if (typeof theme !== "object" || theme === null || Array.isArray(theme)) return false const value = Reflect.get(theme, "theme") return typeof value === "object" && value !== null && !Array.isArray(value) } export function subscribeThemes(listener: (themes: Record<string, ThemeJson>) => void) { listeners.add(listener) return () => listeners.delete(listener) } export function setCustomThemes(themes: Record<string, ThemeJson>) { customThemes = themes syncThemes() } export function setSystemTheme(theme: ThemeJson | undefined) { systemTheme = theme syncThemes() } export function hasTheme(name: string) { if (!name) return false return allThemes()[name] !== undefined } export function addTheme(name: string, theme: unknown) { if (!name) return false if (!isTheme(theme)) return false if (hasTheme(name)) return false pluginThemes[name] = theme syncThemes() return true } export function upsertTheme(name: string, theme: unknown) { if (!name) return false if (!isTheme(theme)) return false if (customThemes[name] !== undefined) { customThemes[name] = theme } else { pluginThemes[name] = theme } syncThemes() return true } export function resolveTheme(theme: ThemeJson, mode: "dark" | "light") { const defs = theme.defs ?? {} function resolveColor(c: ColorValue, chain: string[] = []): RGBA { if (c instanceof RGBA) return c if (typeof c === "string") { if (c === "transparent" || c === "none") return RGBA.fromInts(0, 0, 0, 0) if (c.startsWith("#")) return RGBA.fromHex(c) if (chain.includes(c)) { throw new Error(`Circular color reference: ${[...chain, c].join(" -> ")}`) } const next = defs[c] ?? theme.theme[c as ThemeColor] if (next === undefined) { throw new Error(`Color reference "${c}" not found in defs or theme`) } return resolveColor(next, [...chain, c]) } if (typeof c === "number") { return ansiToRgba(c) } return resolveColor(c[mode], chain) } const resolved = Object.fromEntries( Object.entries(theme.theme) .filter(([key]) => key !== "selectedListItemText" && key !== "backgroundMenu" && key !== "thinkingOpacity") .map(([key, value]) => { return [key, resolveColor(value as ColorValue)] }), ) as Partial<Record<ThemeColor, RGBA>> // Handle selectedListItemText separately since it's optional const hasSelectedListItemText = theme.theme.selectedListItemText !== undefined if (hasSelectedListItemText) { resolved.selectedListItemText = resolveColor(theme.theme.selectedListItemText!) } else { // Backward compatibility: if selectedListItemText is not defined, use background color // This preserves the current behavior for all existing themes resolved.selectedListItemText = resolved.background } // Handle backgroundMenu - optional with fallback to backgroundElement if (theme.theme.backgroundMenu !== undefined) { resolved.backgroundMenu = resolveColor(theme.theme.backgroundMenu) } else { resolved.backgroundMenu = resolved.backgroundElement } // Handle thinkingOpacity - optional with default of 0.6 const thinkingOpacity = theme.theme.thinkingOpacity ?? 0.6 return { ...resolved, _hasSelectedListItemText: hasSelectedListItemText, thinkingOpacity, } as Theme } function ansiToRgba(code: number): RGBA { // Standard ANSI colors (0-15) if (code < 16) { const ansiColors = [ "#000000", // Black "#800000", // Red "#008000", // Green "#808000", // Yellow "#000080", // Blue "#800080", // Magenta "#008080", // Cyan "#c0c0c0", // White "#808080", // Bright Black "#ff0000", // Bright Red "#00ff00", // Bright Green "#ffff00", // Bright Yellow "#0000ff", // Bright Blue "#ff00ff", // Bright Magenta "#00ffff", // Bright Cyan "#ffffff", // Bright White ] return RGBA.fromHex(ansiColors[code] ?? "#000000") } // 6x6x6 Color Cube (16-231) if (code < 232) { const index = code - 16 const b = index % 6 const g = Math.floor(index / 6) % 6 const r = Math.floor(index / 36) const val = (x: number) => (x === 0 ? 0 : x * 40 + 55) return RGBA.fromInts(val(r), val(g), val(b)) } // Grayscale Ramp (232-255) if (code < 256) { const gray = (code - 232) * 10 + 8 return RGBA.fromInts(gray, gray, gray) } // Fallback for invalid codes return RGBA.fromInts(0, 0, 0) } export function tint(base: RGBA, overlay: RGBA, alpha: number): RGBA { const r = base.r + (overlay.r - base.r) * alpha const g = base.g + (overlay.g - base.g) * alpha const b = base.b + (overlay.b - base.b) * alpha return RGBA.fromInts(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)) } export function terminalMode(colors: TerminalColors): "dark" | "light" | undefined { const bg = colors.defaultBackground if (!bg) return const { r, g, b } = RGBA.fromHex(bg) return 0.299 * r + 0.587 * g + 0.114 * b > 0.5 ? "light" : "dark" } export function generateSystem(colors: TerminalColors, mode: "dark" | "light"): ThemeJson { const bg = RGBA.fromHex(colors.defaultBackground ?? colors.palette[0]!) const fg = RGBA.fromHex(colors.defaultForeground ?? colors.palette[7]!) const transparent = RGBA.fromValues(bg.r, bg.g, bg.b, 0) const isDark = mode == "dark" const col = (i: number) => { const value = colors.palette[i] if (value) return RGBA.fromHex(value) return ansiToRgba(i) } // Generate gray scale based on terminal background const grays = generateGrayScale(bg, isDark) const textMuted = generateMutedTextColor(bg, isDark) // ANSI color references const ansiColors = { black: col(0), red: col(1), green: col(2), yellow: col(3), blue: col(4), magenta: col(5), cyan: col(6), white: col(7), redBright: col(9), greenBright: col(10), } const diffAlpha = isDark ? 0.22 : 0.14 const diffAddedBg = tint(bg, ansiColors.green, diffAlpha) const diffRemovedBg = tint(bg, ansiColors.red, diffAlpha) const diffContextBg = grays[2] const diffAddedLineNumberBg = tint(diffContextBg, ansiColors.green, diffAlpha) const diffRemovedLineNumberBg = tint(diffContextBg, ansiColors.red, diffAlpha) const diffLineNumber = textMuted return { theme: { // Primary colors using ANSI primary: ansiColors.cyan, secondary: ansiColors.magenta, accent: ansiColors.cyan, // Status colors using ANSI error: ansiColors.red, warning: ansiColors.yellow, success: ansiColors.green, info: ansiColors.cyan, // Text colors text: fg, textMuted, selectedListItemText: bg, // Background colors - use transparent to respect terminal transparency background: transparent, backgroundPanel: grays[2], backgroundElement: grays[3], backgroundMenu: grays[3], // Border colors borderSubtle: grays[6], border: grays[7], borderActive: grays[8], // Diff colors diffAdded: ansiColors.green, diffRemoved: ansiColors.red, diffContext: grays[7], diffHunkHeader: grays[7], diffHighlightAdded: ansiColors.greenBright, diffHighlightRemoved: ansiColors.redBright, diffAddedBg, diffRemovedBg, diffContextBg, diffLineNumber, diffAddedLineNumberBg, diffRemovedLineNumberBg, // Markdown colors markdownText: fg, markdownHeading: fg, markdownLink: ansiColors.blue, markdownLinkText: ansiColors.cyan, markdownCode: ansiColors.green, markdownBlockQuote: ansiColors.yellow, markdownEmph: ansiColors.yellow, markdownStrong: fg, markdownHorizontalRule: grays[7], markdownListItem: ansiColors.blue, markdownListEnumeration: ansiColors.cyan, markdownImage: ansiColors.blue, markdownImageText: ansiColors.cyan, markdownCodeBlock: fg, // Syntax colors syntaxComment: textMuted, syntaxKeyword: ansiColors.magenta, syntaxFunction: ansiColors.blue, syntaxVariable: fg, syntaxString: ansiColors.green, syntaxNumber: ansiColors.yellow, syntaxType: ansiColors.cyan, syntaxOperator: ansiColors.cyan, syntaxPunctuation: fg, }, } } function generateGrayScale(bg: RGBA, isDark: boolean): Record<number, RGBA> { const grays: Record<number, RGBA> = {} // RGBA stores floats in range 0-1, convert to 0-255 const bgR = bg.r * 255 const bgG = bg.g * 255 const bgB = bg.b * 255 const luminance = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB for (let i = 1; i <= 12; i++) { const factor = i / 12.0 let grayValue: number let newR: number let newG: number let newB: number if (isDark) { if (luminance < 10) { grayValue = Math.floor(factor * 0.4 * 255) newR = grayValue newG = grayValue newB = grayValue } else { const newLum = luminance + (255 - luminance) * factor * 0.4 const ratio = newLum / luminance newR = Math.min(bgR * ratio, 255) newG = Math.min(bgG * ratio, 255) newB = Math.min(bgB * ratio, 255) } } else { if (luminance > 245) { grayValue = Math.floor(255 - factor * 0.4 * 255) newR = grayValue newG = grayValue newB = grayValue } else { const newLum = luminance * (1 - factor * 0.4) const ratio = newLum / luminance newR = Math.max(bgR * ratio, 0) newG = Math.max(bgG * ratio, 0) newB = Math.max(bgB * ratio, 0) } } grays[i] = RGBA.fromInts(Math.floor(newR), Math.floor(newG), Math.floor(newB)) } return grays } function generateMutedTextColor(bg: RGBA, isDark: boolean): RGBA { // RGBA stores floats in range 0-1, convert to 0-255 const bgR = bg.r * 255 const bgG = bg.g * 255 const bgB = bg.b * 255 const bgLum = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB let grayValue: number if (isDark) { if (bgLum < 10) { // Very dark/black background grayValue = 180 // #b4b4b4 } else { // Scale up for lighter dark backgrounds grayValue = Math.min(Math.floor(160 + bgLum * 0.3), 200) } } else { if (bgLum > 245) { // Very light/white background grayValue = 75 // #4b4b4b } else { // Scale down for darker light backgrounds grayValue = Math.max(Math.floor(100 - (255 - bgLum) * 0.2), 60) } } return RGBA.fromInts(grayValue, grayValue, grayValue) } export function generateSyntax(theme: Theme) { return SyntaxStyle.fromTheme(getSyntaxRules(theme)) } export function generateSubtleSyntax(theme: Theme, overrides?: SyntaxStyleOverrides) { const rules = getSyntaxRules(theme) return SyntaxStyle.fromTheme( rules.map((rule) => { const override = rule.scope.reduce((acc, scope) => ({ ...acc, ...overrides?.[scope] }), {}) if (rule.style.foreground) { const fg = rule.style.foreground return { ...rule, style: { ...rule.style, ...override, foreground: RGBA.fromInts( Math.round(fg.r * 255), Math.round(fg.g * 255), Math.round(fg.b * 255), Math.round(theme.thinkingOpacity * 255), ), }, } } return rule }), ) } function getSyntaxRules(theme: Theme) { return [ { scope: ["default"], style: { foreground: theme.text, }, }, { scope: ["prompt"], style: { foreground: theme.accent, }, }, { scope: ["extmark.file"], style: { foreground: theme.warning, bold: true, }, }, { scope: ["extmark.agent"], style: { foreground: theme.secondary, bold: true, }, }, { scope: ["extmark.paste"], style: { foreground: selectedForeground(theme, theme.warning), background: theme.warning, bold: true, }, }, { scope: ["comment"], style: { foreground: theme.syntaxComment, italic: true, }, }, { scope: ["comment.documentation"], style: { foreground: theme.syntaxComment, italic: true, }, }, { scope: ["string", "symbol"], style: { foreground: theme.syntaxString, }, }, { scope: ["number", "boolean"], style: { foreground: theme.syntaxNumber, }, }, { scope: ["character.special"], style: { foreground: theme.syntaxString, }, }, { scope: ["keyword.return", "keyword.conditional", "keyword.repeat", "keyword.coroutine"], style: { foreground: theme.syntaxKeyword, italic: true, }, }, { scope: ["keyword.type"], style: { foreground: theme.syntaxType, bold: true, italic: true, }, }, { scope: ["keyword.function", "function.method"], style: { foreground: theme.syntaxFunction, }, }, { scope: ["keyword"], style: { foreground: theme.syntaxKeyword, italic: true, }, }, { scope: ["keyword.import"], style: { foreground: theme.syntaxKeyword, }, }, { scope: ["operator", "keyword.operator", "punctuation.delimiter"], style: { foreground: theme.syntaxOperator, }, }, { scope: ["keyword.conditional.ternary"], style: { foreground: theme.syntaxOperator, }, }, { scope: ["variable", "variable.parameter", "function.method.call", "function.call"], style: { foreground: theme.syntaxVariable, }, }, { scope: ["variable.member", "function", "constructor"], style: { foreground: theme.syntaxFunction, }, }, { scope: ["type", "module"], style: { foreground: theme.syntaxType, }, }, { scope: ["constant"], style: { foreground: theme.syntaxNumber, }, }, { scope: ["property"], style: { foreground: theme.syntaxVariable, }, }, { scope: ["class"], style: { foreground: theme.syntaxType, }, }, { scope: ["parameter"], style: { foreground: theme.syntaxVariable, }, }, { scope: ["punctuation", "punctuation.bracket"], style: { foreground: theme.syntaxPunctuation, }, }, { scope: ["variable.builtin", "type.builtin", "function.builtin", "module.builtin", "constant.builtin"], style: { foreground: theme.error, }, }, { scope: ["variable.super"], style: { foreground: theme.error, }, }, { scope: ["string.escape", "string.regexp"], style: { foreground: theme.syntaxKeyword, }, }, { scope: ["keyword.directive"], style: { foreground: theme.syntaxKeyword, italic: true, }, }, { scope: ["punctuation.special"], style: { foreground: theme.syntaxOperator, }, }, { scope: ["keyword.modifier"], style: { foreground: theme.syntaxKeyword, italic: true, }, }, { scope: ["keyword.exception"], style: { foreground: theme.syntaxKeyword, italic: true, }, }, // Markdown specific styles { scope: ["markup.heading"], style: { foreground: theme.markdownHeading, bold: true, }, }, { scope: ["markup.heading.1"], style: { foreground: theme.markdownHeading, bold: true, underline: true, }, }, { scope: ["markup.heading.2"], style: { foreground: theme.markdownHeading, bold: true, }, }, { scope: ["markup.heading.3"], style: { foreground: theme.markdownHeading, bold: true, }, }, { scope: ["markup.heading.4"], style: { foreground: theme.markdownHeading, bold: true, }, }, { scope: ["markup.heading.5"], style: { foreground: theme.markdownHeading, bold: true, }, }, { scope: ["markup.heading.6"], style: { foreground: theme.markdownHeading, bold: true, }, }, { scope: ["markup.bold", "markup.strong"], style: { foreground: theme.markdownStrong, bold: true, }, }, { scope: ["markup.italic"], style: { foreground: theme.markdownEmph, italic: true, }, }, { scope: ["markup.list"], style: { foreground: theme.markdownListItem, }, }, { scope: ["markup.quote"], style: { foreground: theme.markdownBlockQuote, italic: true, }, }, { scope: ["markup.raw", "markup.raw.block"], style: { foreground: theme.markdownCode, }, }, { scope: ["markup.raw.inline"], style: { foreground: theme.markdownCode, background: theme.background, }, }, { scope: ["markup.link"], style: { foreground: theme.markdownLink, underline: true, }, }, { scope: ["markup.link.label"], style: { foreground: theme.markdownLinkText, underline: true, }, }, { scope: ["markup.link.url"], style: { foreground: theme.markdownLink, underline: true, }, }, { scope: ["label"], style: { foreground: theme.markdownLinkText, }, }, { scope: ["spell", "nospell"], style: { foreground: theme.text, }, }, { scope: ["conceal"], style: { foreground: theme.textMuted, }, }, // Additional common highlight groups { scope: ["string.special", "string.special.url"], style: { foreground: theme.markdownLink, underline: true, }, }, { scope: ["character"], style: { foreground: theme.syntaxString, }, }, { scope: ["float"], style: { foreground: theme.syntaxNumber, }, }, { scope: ["comment.error"], style: { foreground: theme.error, italic: true, bold: true, }, }, { scope: ["comment.warning"], style: { foreground: theme.warning, italic: true, bold: true, }, }, { scope: ["comment.todo", "comment.note"], style: { foreground: theme.info, italic: true, bold: true, }, }, { scope: ["namespace"], style: { foreground: theme.syntaxType, }, }, { scope: ["field"], style: { foreground: theme.syntaxVariable, }, }, { scope: ["type.definition"], style: { foreground: theme.syntaxType, bold: true, }, }, { scope: ["keyword.export"], style: { foreground: theme.syntaxKeyword, }, }, { scope: ["attribute", "annotation"], style: { foreground: theme.warning, }, }, { scope: ["tag"], style: { foreground: theme.error, }, }, { scope: ["tag.attribute"], style: { foreground: theme.syntaxKeyword, }, }, { scope: ["tag.delimiter"], style: { foreground: theme.syntaxOperator, }, }, { scope: ["markup.strikethrough"], style: { foreground: theme.textMuted, }, }, { scope: ["markup.underline"], style: { foreground: theme.text, underline: true, }, }, { scope: ["markup.list.checked"], style: { foreground: theme.success, }, }, { scope: ["markup.list.unchecked"], style: { foreground: theme.textMuted, }, }, { scope: ["diff.plus"], style: { foreground: theme.diffAdded, background: theme.diffAddedBg, }, }, { scope: ["diff.minus"], style: { foreground: theme.diffRemoved, background: theme.diffRemovedBg, }, }, { scope: ["diff.delta"], style: { foreground: theme.diffContext, background: theme.diffContextBg, }, }, { scope: ["error"], style: { foreground: theme.error, bold: true, }, }, { scope: ["warning"], style: { foreground: theme.warning, bold: true, }, }, { scope: ["info"], style: { foreground: theme.info, }, }, { scope: ["debug"], style: { foreground: theme.textMuted, }, }, ] }