import { readFileSync } from 'node:fs'; // Reading an Omarchy theme's `colors.toml`. // // Omarchy 4 ships a genuinely semantic palette rather than sixteen ANSI slots: three background depths, // four foreground weights, an accent, a selection and a muted, plus the named hues. That is why this port // can be a mapping at all — the previous one had to guess, and the guessing is what produced themes with // no surfaces and unreadable highlights. // // Verified across all 22 themes in basecamp/omarchy@master: every one declares the 24 keys below. export type Palette = { mode: 'dark' | 'light'; accent: string; selection: string; muted: string; background: string; darkBackground: string; darkerBackground: string; lighterBackground: string; foreground: string; darkForeground: string; lightForeground: string; brightForeground: string; red: string; yellow: string; green: string; cyan: string; blue: string; magenta: string; /** Not in every theme — 19 of 22. Falls back to yellow-red mid, which is what orange is. */ orange: string; brightRed: string; brightYellow: string; brightGreen: string; brightCyan: string; brightBlue: string; brightMagenta: string; }; /** * A deliberately small TOML reader: this file is flat `key = "value"` and nothing else. * * Exported (not just used by `loadPalette` below) because a community theme can ship this exact shape * too — see `port-community-themes.ts`, which reads it from a fetched string rather than a file path. */ export function parseTomlText(text: string): Record { const out: Record = {}; for (const line of text.split('\n')) { const m = line.match(/^\s*([a-z_0-9]+)\s*=\s*"([^"]*)"/i); // basecamp/omarchy's own files are already lowercase hex; a community `colors.toml` isn't guaranteed // to be, so normalize here rather than leaving the two sources visibly inconsistent. if (m) out[m[1]!] = m[2]!.toLowerCase(); } return out; } /** The 24-key contract, given an already-parsed record rather than a path — see `parseTomlText`. */ export function paletteFromRecord(t: Record, label: string): Palette { const need = (key: string): string => { const v = t[key]; if (!v) throw new Error(`${label}: missing "${key}"`); return v; }; return { mode: t.mode === 'light' ? 'light' : 'dark', accent: need('accent'), selection: need('selection'), muted: need('muted'), background: need('background'), darkBackground: need('dark_background'), darkerBackground: need('darker_background'), lighterBackground: need('lighter_background'), foreground: need('foreground'), darkForeground: need('dark_foreground'), lightForeground: need('light_foreground'), brightForeground: need('bright_foreground'), red: need('red'), yellow: need('yellow'), green: need('green'), cyan: need('cyan'), blue: need('blue'), magenta: need('magenta'), orange: t.orange ?? need('yellow'), brightRed: need('bright_red'), brightYellow: need('bright_yellow'), brightGreen: need('bright_green'), brightCyan: need('bright_cyan'), brightBlue: need('bright_blue'), brightMagenta: need('bright_magenta'), }; } export function loadPalette(tomlPath: string): Palette { return paletteFromRecord(parseTomlText(readFileSync(tomlPath, 'utf8')), tomlPath); }