// ============================================================================
// Tool / engine icons — single source of truth
//
// Every engine + host-tool brand mark is defined exactly once here and reused
// by Settings -> Engines, Settings -> Stack, the Wizard, and the chat model
// picker. engines.ts pulls its icons from TOOL_ICONS; nothing redefines an SVG.
// ============================================================================
export interface ToolIcon {
light: string;
dark: string;
}
// Git & Chrome are full-colour brand marks, so light and dark share one string.
const GIT_ICON = '';
const CHROME_ICON = '';
export const TOOL_ICONS = {
'opencode': { light: '', dark: '' },
'claude-code': { light: '', dark: '' },
'copilot': { light: '', dark: '' },
'codex': { light: '', dark: '' },
'qwen': { light: '', dark: '' },
'pi': { light: '', dark: '' },
'cline': { light: '', dark: '' },
'cursor': { light: '', dark: '' },
git: { light: GIT_ICON, dark: GIT_ICON },
chrome: { light: CHROME_ICON, dark: CHROME_ICON }
} satisfies Record;
// Stack tool id -> TOOL_ICONS key (engine tools map to their EngineType).
const STACK_TOOL_ICON_KEY: Record = {
claude: 'claude-code',
opencode: 'opencode',
copilot: 'copilot',
codex: 'codex',
qwen: 'qwen',
pi: 'pi',
cline: 'cline',
cursor: 'cursor',
git: 'git',
chrome: 'chrome'
};
/** Resolve the icon for a Stack tool id. */
export function getToolIcon(tool: string): ToolIcon | null {
const key = STACK_TOOL_ICON_KEY[tool];
return key ? TOOL_ICONS[key] : null;
}
// ============================================================================
// Integration brand marks
//
// Keyed by PROVIDER ID, so a provider declares no icon field of its own and one
// whose artwork has not been added yet falls back to a generic glyph instead of
// blocking the entry.
//
// The same rule as above applies: an inline SVG with a light and a dark
// variant, rendered with `{@html}`. A monochrome glyph tinted with the theme
// foreground stops being a logo, so a provider goes in here only once its real
// mark is available — never as a traced approximation.
// ============================================================================
export const PROVIDER_ICONS: Record = {
// Official marks, taken from each vendor's own site. GitHub's Octocat is one
// artwork whose fill flips with the theme — its own brand guidance is
// "black on light, white on dark", so the two variants differ only in fill.
// Context7 ships a
// light/dark pair (the wordmark is dropped — only the square mark is used at
// this size). Firecrawl's flame is one artwork whose inner fill tracks the
// surface behind it, so the two variants differ only in that fill; the source
// viewBox is the site's layout box rather than the glyph's, so it is re-framed
// on the mark's own bounding box or it renders small and off-centre at 24px.
context7: { light: '', dark: '' },
firecrawl: { light: '', dark: '' },
// Vercel's mark is one triangle whose fill flips with the theme — their brand
// guidance is black on light and white on dark, so the two variants differ
// only in that fill, and the viewBox is the one their own asset pack ships
// (1155x1000) rather than a re-typed approximation of it.
// Supabase's mark is one artwork for both themes — the green reads on light
// and dark alike, which is what their own guidance assumes. Its gradient ids
// are namespaced: an inline SVG's `id` is document-global, so a bare
// `paint0_linear` would be captured by whichever mark rendered first.
supabase: { light: '', dark: '' },
vercel: { light: '', dark: '' },
// Neon ships one logomark in two greens — #37C38F for a light background,
// #34D59A for a dark one — which is the pair, not a tint we chose.
neon: { light: '', dark: '' },
github: { light: '', dark: '' }
};
/** Resolve the brand mark for an integration provider, or null to fall back. */
export function getProviderIcon(providerId: string): ToolIcon | null {
return PROVIDER_ICONS[providerId] ?? null;
}