themecode: Adapt, Generate, and Check Editor Themes#
themecode is a semi-latent tool: every operation is pure, offline, and deterministic – no network access, no LM calls.
Where adaptation is ambiguous (a color has no classifiable role and no near palette match), the tool does not guess silently: it falls back to the nearest perceptual swatch and surfaces the decision in the report (verdict fallback, plus the unclassified-role list) for a human to review.
Subcommands:
themecode adapt <file>– port a foreign theme (Sublime.sublime-color-scheme, VSCode JSON, kimi-code JSON) onto the Irix palette. Each color usage walks the decision ladder: exact (already an Irix swatch, kept byte-identical) -> near (within the OKLab--threshold, snapped) -> semantic (role classified, replaced from theirix.data/semantics.yamlcanon, rung-class-guarded so a surface/border-rung swatch never lands in a foreground slot) -> fallback (nearest swatch bydelta_e_ok). Rewrites are textual and structure-preserving (see schemes: Theme File Format Adapters).themecode generate <target>– emit an Irix-native theme for a new app (vscode,kimi) from the semantics canon. Adding a target is one function plus aGENERATORSentry.themecode check <file>– static validation: parseability, hex validity, Irix coverage (share of colors that are exact swatches), and contrast floors.
Adapt, generate, and check editor themes against the Irix palette.
themecode is a semi-latent tool: every operation is pure, offline, and deterministic –
no network access, no LM calls. Where adaptation is ambiguous (a color has no classifiable
role and no near palette match), the tool does not guess silently: it falls back to the
nearest perceptual swatch and surfaces the decision in the report (verdict fallback,
plus the unclassified-role list) for a human to review.
Subcommands:
themecode adapt <file>– port a foreign theme (Sublime.sublime-color-scheme, VSCode JSON, kimi-code JSON) onto the Irix palette. Each color usage walks the decision ladder: exact (already an Irix swatch, kept byte-identical) -> near (within the OKLab--threshold, snapped) -> semantic (role classified, replaced from theirix.data/semantics.yamlcanon, rung-class-guarded so a surface/border-rung swatch never lands in a foreground slot) -> fallback (nearest swatch bydelta_e_ok). Rewrites are textual and structure-preserving (seeirix.schemes).themecode generate <target>– emit an Irix-native theme for a new app (vscode,kimi) from the semantics canon. Adding a target is one function plus aGENERATORSentry.themecode check <file>– static validation: parseability, hex validity, Irix coverage (share of colors that are exact swatches), and contrast floors.
- class irix.themecode.Decision(usage: Usage, verdict: str, new_hex: str, address: str, source_address: str = '')#
The outcome of running one
Usagethrough the adapt decision ladder.source_addressrecords the pre-slide semantic address whenslide_rungadjusted the rung class (empty otherwise), so the report can show[cyan-text1 ⇐ selection].- verdict: str#
Alias for field number 1
- new_hex: str#
Alias for field number 2
- address: str#
Alias for field number 3
- source_address: str#
Alias for field number 4
- class irix.themecode.CheckReport(ok: bool, lines: list[str])#
The result of
themecode check: pass flag plus human-readable report lines.- ok: bool#
Alias for field number 0
- lines: list[str]#
Alias for field number 1
- irix.themecode.rung_class(shade: str) str#
Return the rung class of a shade name:
surface|border|foreground.sheet*/shape*are surfaces,bound*/line*are borders, andsign*/text*are foregrounds. Unknown shades return''.
- irix.themecode.slide_rung(address: str, kind: str) str#
Slide a semantic address to the correct rung class for a usage, same hue family.
The semantic rung of the ladder maps a role to its canonical address regardless of how the color is used – but a surface-rung swatch (
selection:cyan-bound1) placed in a foreground slot is nearly invisible, and a text-rung swatch used as a background blows out the surface. When the address’s rung class mismatches the usage’skind, keep the hue and slide to the correct class: foreground usages always land on the hue’stext1rung (the canonical readable-text rung – a misrunged foreground is the failure mode this guard exists to prevent, not one to approximate); surface and border usages land on the class’s nearest rung by shade order.- Parameters:
address – A canonical
hue-shadeaddress from the semantics table.kind – The usage’s rung class (
'foreground'|'surface'|'border'), or''when unknown – no slide.
- class irix.themecode.ThemeCoder(*, palette: Palette = <factory>, threshold: float = 0.02, semantics: Semantics = <factory>, mode: str = 'dark')#
Decision engine for
themecode adapt/check: palette + threshold + semantics.- threshold#
OKLab
delta_e_okceiling for thenearverdict.- Type:
float
- mode#
Which mode’s semantics addresses to resolve (must agree with
palette); drives thesemanticdecision-ladder step for roles carrying a per-mode{dark: ..., light: ...}override.- Type:
str
- model_config = {'arbitrary_types_allowed': True}#
Configuration for the model, should be a dictionary conforming to [
ConfigDict][pydantic.config.ConfigDict].
- nearest(hex_val: str, kind: str = '') tuple[str, str, float]#
Return
(address, hex, delta_e)of the swatch nearest to hex_val.- Parameters:
hex_val – The color to match.
kind – Restrict the candidate pool to one rung class (
'foreground','surface','border');''searches the whole palette. Kinded usages never snap to an off-class swatch – the same rung guardslide_runggives the semantic step, applied to perceptual matching.
- decide(usage: Usage) Decision#
Run the adapt decision ladder for one usage.
Order: exact (normalized hex is already a swatch, kept as-is) -> near (
delta_e_okto the nearest swatch in the usage’s rung class is withinthreshold) -> semantic (the usage’s role resolves through the semantics canon, rung-class-guarded byslide_rung) -> fallback (nearest swatch in the usage’s rung class). Unkinded usages (kind == '') match the whole palette.- Parameters:
usage – The color usage to decide.
- replacements(decisions: list[Decision]) list[Replacement]#
Project decisions to per-site replacements (
exact/no-change excluded).
- rewrite_mapping(decisions: list[Decision]) dict[str, str]#
Build the old-hex -> new-hex rewrite map from decisions.
exactdecisions never rewrite (kept byte-identical). When several usages share one source hex but decide differently, the higher-fidelity verdict (exact > near > semantic > fallback) wins the shared rewrite.
- report_lines(decisions: list[Decision]) list[str]#
Render the per-usage adapt report (locator, role, verdict, old -> new).
- check(path: Path, text: str) CheckReport#
Statically validate a theme file against this coder’s palette.
Checks: the file parses under some adapter; every hex literal in the raw text is well-formed; Irix coverage (share of usages that are exact swatches); contrast floors – editor foreground vs. background must clear 4.5 (failure), while border/selection chrome below 3.0 is reported as a warning only (the house chrome is deliberately low-contrast).
- Parameters:
path – File path (used for adapter detection and messages).
text – Raw file content.
- irix.themecode.generate_vscode(palette: Palette, sem: Semantics, mode: Mode) str#
Emit an Irix-native VSCode theme JSON document.
- Parameters:
palette – The palette variant to resolve addresses with.
sem – The canonical semantics table.
mode –
'dark'or'light'(the themetypefield, and which per-mode override halfsem.addressresolves).
- irix.themecode.generate_kimi(palette: Palette, sem: Semantics, mode: Mode) str#
Emit the 19-token kimi-code theme JSON, resolved from the semantics canon.
- Parameters:
palette – The palette variant to resolve addresses with.
sem – The canonical semantics table.
mode –
'dark'or'light'(the themebasefield, and which per-mode override halfsem.addressresolves).
- irix.themecode.GENERATORS: dict[str, object] = {'kimi': <function generate_kimi>, 'vscode': <function generate_vscode>}#
Generation target registry – adding a target is one function plus one entry here.
- irix.themecode.pick_mode(adapter: type[Adapter], text: str, usages: list[Usage]) str#
Pick
'dark'or'light'for a scheme whose mode was leftauto.The probe is the scheme’s main background (sublime
backgroundglobal, vscodeeditor.background): luminance < 0.5 means dark. With no background (kimi themes), the probe is the main text color instead – bright text implies a dark theme. When neither exists, dark is the documented default.- Parameters:
adapter – The detected format adapter.
text – Raw file content.
usages – The adapter’s parsed usages.
- irix.themecode.default_output(path: Path) Path#
Return the default adapt output path:
<name>.irix.<ext>beside the input.
- irix.themecode.backup(path: Path) Path#
Copy path to
<file>.bak; never clobber – timestamp the backup if one exists.
- irix.themecode.main(*vargs: str) None#
CLI entry point: dispatch the
adapt/generate/checksubcommands.