add colors.toml and 0x-hex support, rescuing 44 more community themes

Poked at the 62 "no parseable source" skips from the first pass and found two things
the pipeline didn't handle: several community repos genuinely ship colors.toml too — some
in Omarchy 4's own semantic schema (best case, no derivation needed at all, same quality
as the official 22), others reusing the filename for an unrelated flat color0..color15
scheme — and a handful of alacritty.toml files use 0xrrggbb instead of #rrggbb.

palette.ts's loadPalette is now a thin wrapper over two exported pieces (parseTomlText,
paletteFromRecord) so the community pipeline can attempt the real schema first without
duplicating the reader; falls through to the color16 or ANSI-derived paths otherwise.
generate-themes.ts --check still reports 0 changes for the official set — the refactor
is behavior-preserving there, the lowercase-hex normalization it picked up along the way
touched 7 official theme files cosmetically (their source colors.toml has uppercase hex),
regenerated for real rather than left inconsistent with their own source.

147 -> 191 community themes ported. Of the original 62 unparseable repos, most turned out
to be simply gone (404 on GitHub — repos deleted or renamed since the site listed them);
the rest needed one of the two format additions above. 20 themes remain stuck: 16 dead
repos, 2 with no repo link at all, and the same handful of genuinely low-contrast source
palettes (mostly Solarized variants) where fixing contrast would mean changing the
author's actual colours rather than porting them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 01:48:21 +00:00
co-authored by Claude Sonnet 5
parent a58f4d2b32
commit 861fc4a58b
57 changed files with 4859 additions and 124 deletions
+36 -14
View File
@@ -3,8 +3,10 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { execSync } from 'node:child_process';
import { join } from 'node:path';
import { mapPalette } from './themes/map';
import type { Palette } from './themes/palette';
import { paletteFromRecord, parseTomlText } from './themes/palette';
import { deriveOmarchyPalette } from './themes/from-ansi';
import { parseAlacrittyToml, parseGhosttyConf } from './themes/ansi-source';
import { parseAlacrittyToml, parseColor16Toml, parseGhosttyConf } from './themes/ansi-source';
import { contrast, ensureContrast, TEXT, UI } from './themes/contrast';
// Port community Omarchy themes from omarchythemes.com into the same 84-token shape the official
@@ -36,7 +38,7 @@ type ManifestEntry = {
id: string;
name: string;
repo: string;
kind: 'alacritty.toml' | 'ghostty.conf';
kind: string;
dark: boolean;
};
@@ -105,12 +107,29 @@ async function discoverRepo(slug: string): Promise<string | undefined> {
return m ? `${m[1]}/${m[2]}` : undefined;
}
async function sourceFor(
repo: string,
): Promise<{ ansi: NonNullable<ReturnType<typeof parseAlacrittyToml>>; kind: ManifestEntry['kind'] } | undefined> {
async function sourceFor(repo: string): Promise<{ palette: Palette; kind: string; derived: boolean } | undefined> {
const branch = ghApiDefaultBranch(repo);
const raw = (p: string) => `https://raw.githubusercontent.com/${repo}/${branch}/${p}`;
// `colors.toml` is ambiguous across community repos: it might genuinely be Omarchy 4's own semantic
// schema (in which case no derivation is needed at all — same quality as the official 22), or it might
// reuse the filename for an unrelated flat `color0..color15` ANSI scheme. Try the real schema first;
// only fall through to the ANSI reading if the required keys aren't there.
const tomlText = await fetchText(raw('colors.toml'));
if (tomlText) {
const kv = parseTomlText(tomlText);
try {
return {
palette: paletteFromRecord(kv, `${repo}/colors.toml`),
kind: 'colors.toml (Omarchy schema)',
derived: false,
};
} catch {
const ansi = parseColor16Toml(kv);
if (ansi) return { palette: deriveOmarchyPalette(ansi), kind: 'colors.toml (16-colour ANSI)', derived: true };
}
}
for (const path of [
'alacritty.toml',
'.config/alacritty.toml',
@@ -120,13 +139,13 @@ async function sourceFor(
const text = await fetchText(raw(path));
if (!text) continue;
const ansi = parseAlacrittyToml(text);
if (ansi) return { ansi, kind: 'alacritty.toml' };
if (ansi) return { palette: deriveOmarchyPalette(ansi), kind: 'alacritty.toml', derived: true };
}
for (const path of ['ghostty.conf', 'ghostty-theme', '.config/ghostty/config']) {
const text = await fetchText(raw(path));
if (!text) continue;
const ansi = parseGhosttyConf(text);
if (ansi) return { ansi, kind: 'ghostty.conf' };
if (ansi) return { palette: deriveOmarchyPalette(ansi), kind: 'ghostty.conf', derived: true };
}
return undefined;
}
@@ -136,9 +155,9 @@ async function portOne(slug: string): Promise<{ entry: ManifestEntry; css: strin
if (!repo) return { skip: 'no github repo link found on its omarchythemes.com page' };
const source = await sourceFor(repo);
if (!source) return { skip: `no parseable alacritty.toml/ghostty.conf in ${repo}` };
if (!source) return { skip: `no parseable colors.toml/alacritty.toml/ghostty.conf in ${repo}` };
const palette = deriveOmarchyPalette(source.ansi);
const palette = source.palette;
const tokens = mapPalette(palette);
// `--input` carries a real accessibility requirement (a field boundary you can't see) where `--border`
@@ -161,16 +180,19 @@ async function portOne(slug: string): Promise<{ entry: ManifestEntry; css: strin
const body = Object.entries(tokens)
.map(([k, v]) => ` ${k}: ${v};`)
.join('\n');
const modeLine = source.kind.startsWith('colors.toml (Omarchy')
? `${palette.mode === 'dark' ? 'Dark theme' : 'Light theme'} (${palette.mode}) — the source repo's own colors.toml says so directly.`
: `${palette.mode === 'dark' ? 'Dark theme' : 'Light theme'} (${palette.mode}), by luminance of its own background — the source repo carries no mode flag of its own to read.`;
const provenanceLine = source.derived
? `went through deriveOmarchyPalette (scripts/themes/from-ansi.ts) because the source has no Omarchy 4\n colors.toml — some tokens here are reconstructed, not authored.`
: `came straight from the repo's own colors.toml — the same schema and reader the official 22 themes\n use, so nothing here is reconstructed.`;
const css = `/*
${name} — ported from a community Omarchy theme, not from basecamp/omarchy's own set.
${palette.mode === 'dark' ? 'Dark theme' : 'Light theme'} (${palette.mode}), by luminance of its own
background — the source repo carries no mode flag of its own to read.
${modeLine}
Source: https://github.com/${repo} (${source.kind}), read by scripts/port-community-themes.ts. Values
went through deriveOmarchyPalette (scripts/themes/from-ansi.ts) because a single-theme repo ships
per-app configs, not Omarchy 4's own colors.toml shape — some tokens here are reconstructed, not
authored. Every text/surface pair is contrast-checked the same way the official themes are.
${provenanceLine} Every text/surface pair is contrast-checked the same way the official themes are.
DO NOT EDIT BY HAND. Re-run the script to refresh from the source repo.
*/