statusline-theme
Set the colors block of statusline_config.json for the usage-aware Claude
Code statusline. You handle three jobs:
- (A) Apply a palette — map every non-semantic color KEY onto a bundled
palette and write the resolved hex values.
- (B) Recolor element(s) — change only the specific keys the user named
("make branch green", "model should be #ff0066").
- (C) Snap to a palette — replace each current color with the nearest color
in a chosen palette, measured by CIEDE2000 (perceptual distance).
Hard rules (never violate)
- Never recolor the SEMANTIC band keys —
ctx_bar, ctx_bar_mid, ctx_bar_high, ctx_bar_crit, ctx_bar_max,
ctx_bar_track — unless the user explicitly opts in ("also theme the
gauge bands", "recolor the severity bands too"). These encode a green to red
severity ramp shared by the context gauge AND the usage gauges; recoloring
them silently would break the meaning. When in doubt, leave them out and say
so.
Likewise, never recolor the SEMANTIC status/incident keys —
status_investigating, status_identified, status_monitoring,
status_maintenance, status_default, status_title, status_count,
status_header — unless the user explicitly opts in ("also theme the incident
colors", "recolor the status indicator too"). These encode Claude service
incident severity and have universally understood meaning; recoloring them
silently would make the statusline misleading.
- Confirm before writing. Always: resolve the ACTIVE config (Step 0) ->
read it -> compute new colors -> show BEFORE/AFTER swatches + a diff ->
test-render -> get explicit confirmation -> only THEN commit (via the helper's
commit, see "Writing the result"). Never write without a yes.
- Only touch the
colors block. Preserve templates, weather,
emoji_width, ctx_bar_empty, and every _comment / _-prefixed key
exactly as-is. Commit the whole file back, changing only colors.
Never blind-overwrite the active config; install only via the helper's
history-aware commit so the prior version is snapshotted first.
- Valid hex only. Every value must be
#RRGGBB. Invalid values are
silently ignored by the renderer, so never emit them.
- English only in everything you show and write.
Files in this skill
palettes.json — 15 palettes, each with 9 ROLES
(text, muted, red, peach, yellow, green, sky, blue, mauve), verified hex.
rolemap.json — the KEY -> ROLE map for "apply palette" mode, plus the
optional band opt-in map.
colorutil.py — stdlib-only: hex<->rgb, sRGB->XYZ->Lab, ciede2000,
nearest_color, and swatch_line / print_swatches for truecolor previews.
assets/sample_input.json — canonical Claude Code session JSON for test renders.
Read palettes.json and rolemap.json on demand (don't paste them inline
unless needed). Run colorutil.py via the shell for math and swatches.
The shared active-config + history helper lives in the sibling
statusline-config skill (one copy only, stdlib): from this skill it is
../statusline-config/statusline_io.py. You call it as a CLI subprocess; you
never import it.
Step 0 — Resolve the ACTIVE statusline (do this first)
Claude Code runs a specific statusline — usually the deployed copy (e.g.
~/.claude/statusline.py), NOT the repo copy. You MUST theme the config that
the active renderer actually reads, not a hardcoded repo path. Resolve it with
the sibling helper:
python <skills-dir>/statusline-config/statusline_io.py locate
<skills-dir> is the directory containing this skill; from here the helper is
../statusline-config/statusline_io.py. Parse the JSON it prints and read
config_path (the active config you will theme) and statusline_py (the
renderer used for test renders). Handle source:
"pointer" — a saved pointer resolved it. Use config_path directly.
"settings" — derived from the active settings.json. Use config_path,
and OFFER to save a pointer so future runs are instant:
python <skills-dir>/statusline-config/statusline_io.py save-pointer --statusline-py <statusline_py> --config <config_path> [--settings <settings_json>] [--launcher <launcher>].
"none" — nothing resolvable. Ask the user where their statusline is
installed (the statusline.py and its statusline_config.json), then offer to
save a pointer via save-pointer for next time.
Use the resolved config_path everywhere below as the live config to read and
(after confirmation) commit to. Never assume the repo path. If config_path is
missing or unreadable, fall back to renderer defaults but tell the user and
prefer to confirm the real path.
Config locations
- Live config: the
config_path returned by Step 0 — the file the ACTIVE
renderer reads (usually the deployed statusline_config.json next to the
deployed statusline.py, or the path in $STATUSLINE_CONFIG baked into the
statusLine.command). Do NOT hardcode the repo path; the repo copy is often
not the active one.
- The full set of non-semantic color KEYS you may set:
time, version, model, effort, peak, offpeak, offpeak_warn, usd, ctx_label, ctx_value, ctx_percent, ctx_icon, model_icon, total_icon, total, bracket, input, separator, output, cached_icon, cached, path, git_icon, branch, git_status, changes, weather, ctx_bracket.
- The status/incident color keys (
status_investigating, status_identified,
status_monitoring, status_maintenance, status_default, status_title,
status_count, status_header) are semantic and excluded from automatic
palette application. They may only be set on explicit user opt-in (see Hard
rule 1 and Mode A).
- The user's config may only define a subset of these. When applying a palette,
set the keys the rolemap covers that are already present, plus any the user
asks for. Don't invent keys the user never had unless they explicitly want a
full palette application across every key.
Listing palettes
When the user asks "what palettes / themes are there?", read palettes.json and
list each id with its _label. Optionally render a one-line swatch per palette
so they can see it (see "Showing swatches"). The ids are:
catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, catppuccin-mocha, dracula, nord, gruvbox-dark, tokyo-night, one-dark, solarized-dark, solarized-light, monokai, rose-pine, everforest-dark, ayu-dark.
Accept fuzzy names: "catppuccin" -> ask which flavor (default mocha); "solarized"
-> ask dark vs light; "tokyonight"/"tokyo" -> tokyo-night; "rose pine" ->
rose-pine; "everforest" -> everforest-dark; "ayu" -> ayu-dark.
The standard loop (every mode)
- Resolve the ACTIVE config first (see "Step 0") and use its
config_path
as the live file for every step below.
- Read the active
config_path. Capture the existing colors object (call
it BEFORE). If the file is missing/invalid, start from the renderer defaults —
but tell the user, and prefer to ask for the real path.
- Compute the AFTER
colors object per the chosen mode (below).
- Preview: print BEFORE vs AFTER truecolor swatches and a key-by-key diff
(only the keys that change). Note explicitly that the semantic band keys are
left untouched (unless opted in).
- Validate: every AFTER value matches
^#[0-9a-fA-F]{6}$.
- Test-render with the canonical sample input against a CANDIDATE config so
the live file is untouched (see "Test render").
- Confirm: ask the user to approve. Only on an explicit yes do you commit.
- Commit the full config (only
colors changed) history-aware via the
helper's commit subcommand (see "Writing the result").
Mode A — apply a whole palette
- Resolve the palette id (ask to disambiguate flavors if needed). Read its role
hex values from
palettes.json.
- Read
rolemap.json -> roles. For each KEY in roles, look up its ROLE,
then the role's hex in the palette. That hex becomes the new value for that
key.
- Build AFTER = current
colors with those keys overwritten. Do not add the
band keys. If the user has band keys already set, leave them as they are.
- Band opt-in: only if the user explicitly asked to theme the bands, also
apply
rolemap.json -> bands_optin (ctx_bar/ctx_bar_mid -> green,
ctx_bar_high -> peach, ctx_bar_crit/ctx_bar_max -> red, ctx_bar_track ->
muted). Otherwise say "leaving the severity bands at their defaults".
- Status opt-in: only if the user explicitly asked to theme the incident/
status colors ("also theme the incident colors", "recolor the status
indicator too"), also apply
rolemap.json -> status_optin
(status_investigating -> peach, status_identified/status_header -> red,
status_monitoring -> blue, status_maintenance -> sky, status_default ->
yellow, status_title/status_count -> muted). Otherwise say "leaving the
incident severity colors at their defaults".
- Preview, validate, test-render, confirm, commit (history-aware, see "Writing
the result").
Mapping reference (authoritative copy is rolemap.json):
time->yellow version->muted model->red effort->blue
ctx_label/ctx_value/ctx_icon/model_icon/total_icon->muted
git_icon/bracket/separator/cached_icon->muted
ctx_percent->text ctx_bracket->text
total->green input->red output->blue cached->mauve
path->blue branch/git_status/changes->yellow
weather->sky peak->red offpeak->green offpeak_warn->peach usd->green
Mode B — recolor named element(s)
The user names one or more elements and a color each ("make branch green",
"path #88c0d0", "turn the time gold").
- Map each named element to a KEY. Common phrasings:
- "branch" ->
branch (consider also git_status/changes if they say
"git status colors" — ask if ambiguous).
- "path"/"cwd"/"directory" ->
path.
- "model"/"model name" ->
model. "time"/"clock" -> time.
- "version" ->
version. "weather" -> weather. "effort" -> effort.
- "context %"/"ctx percent" ->
ctx_percent. "brackets" -> bracket (and/or
ctx_bracket — ask). "input/output/cached tokens" -> input/output/
cached. "total" -> total.
- "status indicator"/"incident indicator"/"investigating color" ->
status_investigating; "identified color" -> status_identified;
"monitoring color" -> status_monitoring; "maintenance color" ->
status_maintenance; "status default color" -> status_default;
"status title color" -> status_title; "status count color" ->
status_count; "status banner"/"incident banner"/"status header" ->
status_header. When the user says "recolor the status/incident colors"
without specifying a severity, ask which states they want to change (or
offer to apply the full status_optin palette mapping).
- If the named element is one of the band keys or status/incident keys,
STOP and confirm the opt-in first, because that overrides the severity ramp.
- Resolve the requested color:
- A hex (
#rrggbb or #rgb): use it (normalize via colorutil.hex_to_rgb/
rgb_to_hex).
- A color word ("green", "gold", "sky blue"): pick a sensible hex. If a
palette is in play (the config already looks like a known palette, or the
user mentions one), prefer that palette's matching role. Otherwise use a
reasonable common value and SHOW it so the user can veto.
- AFTER = current
colors with just those keys changed. Preview only the
changed keys, validate, test-render, confirm, commit (history-aware).
Mode C — snap to the nearest palette color
"Snap my colors to Nord", "make my current colors match Gruvbox as closely as
possible." This keeps the user's per-key intent but pulls each color onto the
chosen palette.
Resolve the palette id; read its 9 role hexes as the candidate set.
For each NON-SEMANTIC key currently in colors, compute the nearest palette
color by CIEDE2000 and use it as the AFTER value. Use colorutil:
python <skill>/colorutil.py # self-test (<skill> = this skill's directory)
For the snapping itself, import the helper or run a tiny inline script. On
macOS/Linux a bash heredoc works; on Windows/PowerShell write the snippet to
a temp .py file and run it (PowerShell has no heredoc). Use the active
config_path from Step 0 for the config you read, and this skill's own
directory for colorutil.py / palettes.json. Bash example (substitute the
resolved config_path for <config_path> and this skill's dir for <skill>):
python3 - <<'PY'
import json, sys
sys.path.insert(0, "<skill>")
import colorutil
pal = json.load(open("<skill>/palettes.json"))
cur = json.load(open("<config_path>")).get("colors", {})
roles = [v for k, v in pal["nord"].items() if not k.startswith("_")]
bands = {"ctx_bar","ctx_bar_mid","ctx_bar_high","ctx_bar_crit","ctx_bar_max","ctx_bar_track"}
status = {"status_investigating","status_identified","status_monitoring","status_maintenance","status_default","status_title","status_count","status_header"}
for key, hexv in cur.items():
if key.startswith("_") or key in bands or key in status: # never snap band or status keys by default
continue
try:
best, de = colorutil.nearest_color(hexv, roles)
except ValueError:
continue
print(f"{key}: {hexv} -> {best} (dE00={de:.2f})")
PY
Swap nord for the chosen palette id and adjust the paths (the active
config_path and this skill's dir). Skip the band keys (and _-prefixed
keys) in the loop unless the user opted in.
AFTER = current colors with each non-semantic key replaced by its nearest
palette hex. Preview (show the per-key delta-E so the user sees how far each
moved), validate, test-render, confirm, commit (history-aware).
Showing swatches (BEFORE/AFTER preview)
Use colorutil.swatch_line / print_swatches to render truecolor blocks so the
user can actually see the colors in the terminal. Example: build a list of
(label, hex) for the changed keys and print two lines, BEFORE and AFTER. On
Windows/PowerShell write the snippet to a temp .py file and run it (no
heredoc); the bash heredoc below is illustrative for macOS/Linux.
python3 - <<'PY'
import sys
sys.path.insert(0, "<skill>") # this skill's directory
import colorutil
before = [("model","#E06C75"), ("branch","#E5C07B"), ("path","#61AFEF")]
after = [("model","#f38ba8"), ("branch","#f9e2af"), ("path","#89b4fa")]
print("BEFORE:"); colorutil.print_swatches(before)
print("AFTER: "); colorutil.print_swatches(after)
PY
Also show a plain text diff of just the changed keys, e.g.:
model #E06C75 -> #f38ba8
branch #E5C07B -> #f9e2af
path #61AFEF -> #89b4fa
(semantic bands ctx_bar* left unchanged)
Test render (do not touch the live file)
The canonical sample input is bundled as assets/sample_input.json. Write the
candidate config to a temp path, then run the resolved active renderer
(statusline_py from Step 0) with STATUSLINE_CONFIG pointed at the candidate
so the user's live file stays untouched until they confirm. A non-empty colored
line means success.
Use the statusline_py from Step 0 wherever <statusline.py> appears, and this
skill's bundled <skill>/assets/sample_input.json for the sample. Do not
hardcode any particular machine's path.
PowerShell:
$env:STATUSLINE_CONFIG="<candidate.json>"; Get-Content <skill>/assets/sample_input.json | uv run <statusline.py>
# fallback: ... | python <statusline.py>
bash:
STATUSLINE_CONFIG=<candidate.json> uv run <statusline.py> < <skill>/assets/sample_input.json
# fallback: python3 instead of uv run
Writing the result (history-aware commit)
Never blind-overwrite the live config. On explicit confirmation, install the new
config through the sibling helper's commit subcommand, which snapshots the
current config and appends a changelog entry before writing.
Build the full new config object in memory: the current config with ONLY the
colors block changed. Keep key order stable, keep all _comment keys and
every _-prefixed key, preserve templates/weather/emoji_width/
ctx_bar_empty exactly, and keep it valid JSON. (This is the same CANDIDATE
you test-rendered.)
Write that new config to a temp file (e.g. in the OS temp dir) so large
JSON never goes through argv.
Run the helper's commit against the active config_path from Step 0:
python <skills-dir>/statusline-config/statusline_io.py commit \
--config <config_path> --new <temp-config> \
--skill statusline-theme \
--summary "<what changed, one line>" \
--why "<the user's intent>" \
--diff "<the changed-key list, e.g. model #E06C75 -> #f38ba8 ...>"
The helper validates the temp file is JSON (exits 4 touching nothing if not),
snapshots the about-to-be-replaced config into
.statusline-config-history/YYYYMMDD-hhmm.json next to the active config,
writes the new bytes to config_path, and appends a human entry to
.statusline-config-history/YYYYMMDD.md. For --summary keep it to the color
change (e.g. "Applied Catppuccin Mocha palette" or "Recolored branch ->
green"); for --diff pass the same per-key BEFORE -> AFTER list you previewed.
Read the JSON the helper prints (snapshot, changelog, config_path) and
tell the user what changed in one line plus the snapshot + changelog paths,
and how to revert (restore the snapshot file, re-run this skill, or restore
from git). The snapshot is the easiest undo.
Do NOT write the live config any other way (no direct file write) — always go
through commit so history is recorded.
Notes on specific palettes
A few palettes don't have a distinct color for every role; the closest named
color is reused (this is intentional and documented in palettes.json):
- Monokai: no separate blue — cyan
#66d9ef serves both sky and blue.
- Rose Pine: gold
#f6c177 serves peach and yellow; foam #9ccfd8
serves sky and blue; pine #31748f is the green role.
- Solarized dark/light: the accent hues are identical; only
text/muted
differ between the two variants.
- Catppuccin comes in four flavors (latte/frappe/macchiato/mocha); Latte and
Solarized Light are light themes — flag that if the user is on a dark terminal.
1---2name: statusline-theme3description: Owns ALL color decisions for the Claude Code statusline. Use when the user wants to "theme my statusline", "apply Catppuccin/Dracula/Nord/Gruvbox/Tokyo Night/One Dark/Solarized/Monokai/Rose Pine/Everforest/Ayu", "change the colors", "use a <palette> color scheme", "make <element> <color>" (e.g. "make branch green", "turn the model name red"), "recolor the branch/path/time", or "snap my colors to <palette>". Three modes: apply a whole palette, recolor named elements, or snap existing colors to the nearest palette color.4---56# statusline-theme78Set the `colors` block of `statusline_config.json` for the usage-aware Claude9Code statusline. You handle three jobs:1011- **(A) Apply a palette** — map every non-semantic color KEY onto a bundled12 palette and write the resolved hex values.13- **(B) Recolor element(s)** — change only the specific keys the user named14 ("make branch green", "model should be #ff0066").15- **(C) Snap to a palette** — replace each current color with the nearest color16 in a chosen palette, measured by CIEDE2000 (perceptual distance).1718## Hard rules (never violate)19201. **Never recolor the SEMANTIC band keys** —21 `ctx_bar`, `ctx_bar_mid`, `ctx_bar_high`, `ctx_bar_crit`, `ctx_bar_max`,22 `ctx_bar_track` — unless the user **explicitly opts in** ("also theme the23 gauge bands", "recolor the severity bands too"). These encode a green to red24 severity ramp shared by the context gauge AND the usage gauges; recoloring25 them silently would break the meaning. When in doubt, leave them out and say26 so.27 Likewise, **never recolor the SEMANTIC status/incident keys** —28 `status_investigating`, `status_identified`, `status_monitoring`,29 `status_maintenance`, `status_default`, `status_title`, `status_count`,30 `status_header` — unless the user explicitly opts in ("also theme the incident31 colors", "recolor the status indicator too"). These encode Claude service32 incident severity and have universally understood meaning; recoloring them33 silently would make the statusline misleading.342. **Confirm before writing.** Always: resolve the ACTIVE config (Step 0) ->35 read it -> compute new colors -> show BEFORE/AFTER swatches + a diff ->36 test-render -> get explicit confirmation -> only THEN commit (via the helper's37 `commit`, see "Writing the result"). Never write without a yes.383. **Only touch the `colors` block.** Preserve `templates`, `weather`,39 `emoji_width`, `ctx_bar_empty`, and every `_comment` / `_`-prefixed key40 exactly as-is. Commit the whole file back, changing only `colors`.41 Never blind-overwrite the active config; install only via the helper's42 history-aware `commit` so the prior version is snapshotted first.434. **Valid hex only.** Every value must be `#RRGGBB`. Invalid values are44 silently ignored by the renderer, so never emit them.455. **English only** in everything you show and write.4647## Files in this skill4849- `palettes.json` — 15 palettes, each with 9 ROLES50 (`text, muted, red, peach, yellow, green, sky, blue, mauve`), verified hex.51- `rolemap.json` — the KEY -> ROLE map for "apply palette" mode, plus the52 optional band opt-in map.53- `colorutil.py` — stdlib-only: hex<->rgb, sRGB->XYZ->Lab, `ciede2000`,54 `nearest_color`, and `swatch_line` / `print_swatches` for truecolor previews.55- `assets/sample_input.json` — canonical Claude Code session JSON for test renders.5657Read `palettes.json` and `rolemap.json` on demand (don't paste them inline58unless needed). Run `colorutil.py` via the shell for math and swatches.5960The shared **active-config + history helper** lives in the sibling61`statusline-config` skill (one copy only, stdlib): from this skill it is62`../statusline-config/statusline_io.py`. You call it as a CLI subprocess; you63never import it.6465## Step 0 — Resolve the ACTIVE statusline (do this first)6667Claude Code runs a specific statusline — usually the **deployed** copy (e.g.68`~/.claude/statusline.py`), NOT the repo copy. You MUST theme the config that69the active renderer actually reads, not a hardcoded repo path. Resolve it with70the sibling helper:7172```73python <skills-dir>/statusline-config/statusline_io.py locate74```7576`<skills-dir>` is the directory containing this skill; from here the helper is77`../statusline-config/statusline_io.py`. Parse the JSON it prints and read78`config_path` (the active config you will theme) and `statusline_py` (the79renderer used for test renders). Handle `source`:8081- **`"pointer"`** — a saved pointer resolved it. Use `config_path` directly.82- **`"settings"`** — derived from the active `settings.json`. Use `config_path`,83 and OFFER to save a pointer so future runs are instant:84 `python <skills-dir>/statusline-config/statusline_io.py save-pointer85 --statusline-py <statusline_py> --config <config_path> [--settings <settings_json>]86 [--launcher <launcher>]`.87- **`"none"`** — nothing resolvable. **Ask the user** where their statusline is88 installed (the `statusline.py` and its `statusline_config.json`), then offer to89 save a pointer via `save-pointer` for next time.9091Use the resolved `config_path` everywhere below as the live config to read and92(after confirmation) commit to. Never assume the repo path. If `config_path` is93missing or unreadable, fall back to renderer defaults but tell the user and94prefer to confirm the real path.9596## Config locations9798- Live config: the `config_path` returned by Step 0 — the file the ACTIVE99 renderer reads (usually the deployed `statusline_config.json` next to the100 deployed `statusline.py`, or the path in `$STATUSLINE_CONFIG` baked into the101 `statusLine.command`). Do NOT hardcode the repo path; the repo copy is often102 not the active one.103- The full set of non-semantic color KEYS you may set:104 `time, version, model, effort, peak, offpeak, offpeak_warn, usd, ctx_label,105 ctx_value, ctx_percent, ctx_icon, model_icon, total_icon, total, bracket,106 input, separator, output, cached_icon, cached, path, git_icon, branch,107 git_status, changes, weather, ctx_bracket`.108- The **status/incident color keys** (`status_investigating`, `status_identified`,109 `status_monitoring`, `status_maintenance`, `status_default`, `status_title`,110 `status_count`, `status_header`) are semantic and excluded from automatic111 palette application. They may only be set on explicit user opt-in (see Hard112 rule 1 and Mode A).113- The user's config may only define a subset of these. When applying a palette,114 set the keys the rolemap covers that are already present, plus any the user115 asks for. Don't invent keys the user never had unless they explicitly want a116 full palette application across every key.117118## Listing palettes119120When the user asks "what palettes / themes are there?", read `palettes.json` and121list each id with its `_label`. Optionally render a one-line swatch per palette122so they can see it (see "Showing swatches"). The ids are:123124`catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, catppuccin-mocha,125dracula, nord, gruvbox-dark, tokyo-night, one-dark, solarized-dark,126solarized-light, monokai, rose-pine, everforest-dark, ayu-dark`.127128Accept fuzzy names: "catppuccin" -> ask which flavor (default mocha); "solarized"129-> ask dark vs light; "tokyonight"/"tokyo" -> `tokyo-night`; "rose pine" ->130`rose-pine`; "everforest" -> `everforest-dark`; "ayu" -> `ayu-dark`.131132## The standard loop (every mode)1331340. **Resolve** the ACTIVE config first (see "Step 0") and use its `config_path`135 as the live file for every step below.1361. **Read** the active `config_path`. Capture the existing `colors` object (call137 it BEFORE). If the file is missing/invalid, start from the renderer defaults —138 but tell the user, and prefer to ask for the real path.1392. **Compute** the AFTER `colors` object per the chosen mode (below).1403. **Preview**: print BEFORE vs AFTER truecolor swatches and a key-by-key diff141 (only the keys that change). Note explicitly that the semantic band keys are142 left untouched (unless opted in).1434. **Validate**: every AFTER value matches `^#[0-9a-fA-F]{6}$`.1445. **Test-render** with the canonical sample input against a CANDIDATE config so145 the live file is untouched (see "Test render").1466. **Confirm**: ask the user to approve. Only on an explicit yes do you commit.1477. **Commit** the full config (only `colors` changed) history-aware via the148 helper's `commit` subcommand (see "Writing the result").149150---151152## Mode A — apply a whole palette1531541. Resolve the palette id (ask to disambiguate flavors if needed). Read its role155 hex values from `palettes.json`.1562. Read `rolemap.json` -> `roles`. For each KEY in `roles`, look up its ROLE,157 then the role's hex in the palette. That hex becomes the new value for that158 key.1593. Build AFTER = current `colors` with those keys overwritten. **Do not add the160 band keys.** If the user has band keys already set, leave them as they are.1614. **Band opt-in:** only if the user explicitly asked to theme the bands, also162 apply `rolemap.json` -> `bands_optin` (ctx_bar/ctx_bar_mid -> green,163 ctx_bar_high -> peach, ctx_bar_crit/ctx_bar_max -> red, ctx_bar_track ->164 muted). Otherwise say "leaving the severity bands at their defaults".1655. **Status opt-in:** only if the user explicitly asked to theme the incident/166 status colors ("also theme the incident colors", "recolor the status167 indicator too"), also apply `rolemap.json` -> `status_optin`168 (status_investigating -> peach, status_identified/status_header -> red,169 status_monitoring -> blue, status_maintenance -> sky, status_default ->170 yellow, status_title/status_count -> muted). Otherwise say "leaving the171 incident severity colors at their defaults".1726. Preview, validate, test-render, confirm, commit (history-aware, see "Writing173 the result").174175Mapping reference (authoritative copy is `rolemap.json`):176177```178time->yellow version->muted model->red effort->blue179ctx_label/ctx_value/ctx_icon/model_icon/total_icon->muted180git_icon/bracket/separator/cached_icon->muted181ctx_percent->text ctx_bracket->text182total->green input->red output->blue cached->mauve183path->blue branch/git_status/changes->yellow184weather->sky peak->red offpeak->green offpeak_warn->peach usd->green185```186187## Mode B — recolor named element(s)188189The user names one or more elements and a color each ("make branch green",190"path #88c0d0", "turn the time gold").1911921. Map each named element to a KEY. Common phrasings:193 - "branch" -> `branch` (consider also `git_status`/`changes` if they say194 "git status colors" — ask if ambiguous).195 - "path"/"cwd"/"directory" -> `path`.196 - "model"/"model name" -> `model`. "time"/"clock" -> `time`.197 - "version" -> `version`. "weather" -> `weather`. "effort" -> `effort`.198 - "context %"/"ctx percent" -> `ctx_percent`. "brackets" -> `bracket` (and/or199 `ctx_bracket` — ask). "input/output/cached tokens" -> `input`/`output`/200 `cached`. "total" -> `total`.201 - "status indicator"/"incident indicator"/"investigating color" ->202 `status_investigating`; "identified color" -> `status_identified`;203 "monitoring color" -> `status_monitoring`; "maintenance color" ->204 `status_maintenance`; "status default color" -> `status_default`;205 "status title color" -> `status_title`; "status count color" ->206 `status_count`; "status banner"/"incident banner"/"status header" ->207 `status_header`. When the user says "recolor the status/incident colors"208 without specifying a severity, ask which states they want to change (or209 offer to apply the full `status_optin` palette mapping).210 - If the named element is one of the **band keys** or **status/incident keys**,211 STOP and confirm the opt-in first, because that overrides the severity ramp.2122. Resolve the requested color:213 - A hex (`#rrggbb` or `#rgb`): use it (normalize via `colorutil.hex_to_rgb`/214 `rgb_to_hex`).215 - A color word ("green", "gold", "sky blue"): pick a sensible hex. If a216 palette is in play (the config already looks like a known palette, or the217 user mentions one), prefer that palette's matching role. Otherwise use a218 reasonable common value and SHOW it so the user can veto.2193. AFTER = current `colors` with just those keys changed. Preview only the220 changed keys, validate, test-render, confirm, commit (history-aware).221222## Mode C — snap to the nearest palette color223224"Snap my colors to Nord", "make my current colors match Gruvbox as closely as225possible." This keeps the user's per-key intent but pulls each color onto the226chosen palette.2272281. Resolve the palette id; read its 9 role hexes as the candidate set.2292. For each NON-SEMANTIC key currently in `colors`, compute the nearest palette230 color by CIEDE2000 and use it as the AFTER value. Use `colorutil`:231232 ```233 python <skill>/colorutil.py # self-test (<skill> = this skill's directory)234 ```235236 For the snapping itself, import the helper or run a tiny inline script. On237 macOS/Linux a bash heredoc works; on Windows/PowerShell write the snippet to238 a temp `.py` file and run it (PowerShell has no heredoc). Use the **active239 `config_path` from Step 0** for the config you read, and this skill's own240 directory for `colorutil.py` / `palettes.json`. Bash example (substitute the241 resolved `config_path` for `<config_path>` and this skill's dir for `<skill>`):242243 ```bash244 python3 - <<'PY'245 import json, sys246 sys.path.insert(0, "<skill>")247 import colorutil248 pal = json.load(open("<skill>/palettes.json"))249 cur = json.load(open("<config_path>")).get("colors", {})250 roles = [v for k, v in pal["nord"].items() if not k.startswith("_")]251 bands = {"ctx_bar","ctx_bar_mid","ctx_bar_high","ctx_bar_crit","ctx_bar_max","ctx_bar_track"}252 status = {"status_investigating","status_identified","status_monitoring","status_maintenance","status_default","status_title","status_count","status_header"}253 for key, hexv in cur.items():254 if key.startswith("_") or key in bands or key in status: # never snap band or status keys by default255 continue256 try:257 best, de = colorutil.nearest_color(hexv, roles)258 except ValueError:259 continue260 print(f"{key}: {hexv} -> {best} (dE00={de:.2f})")261 PY262 ```263264 Swap `nord` for the chosen palette id and adjust the paths (the active265 `config_path` and this skill's dir). **Skip the band keys** (and `_`-prefixed266 keys) in the loop unless the user opted in.2673. AFTER = current `colors` with each non-semantic key replaced by its nearest268 palette hex. Preview (show the per-key delta-E so the user sees how far each269 moved), validate, test-render, confirm, commit (history-aware).270271---272273## Showing swatches (BEFORE/AFTER preview)274275Use `colorutil.swatch_line` / `print_swatches` to render truecolor blocks so the276user can actually see the colors in the terminal. Example: build a list of277`(label, hex)` for the changed keys and print two lines, BEFORE and AFTER. On278Windows/PowerShell write the snippet to a temp `.py` file and run it (no279heredoc); the bash heredoc below is illustrative for macOS/Linux.280281```bash282python3 - <<'PY'283import sys284sys.path.insert(0, "<skill>") # this skill's directory285import colorutil286before = [("model","#E06C75"), ("branch","#E5C07B"), ("path","#61AFEF")]287after = [("model","#f38ba8"), ("branch","#f9e2af"), ("path","#89b4fa")]288print("BEFORE:"); colorutil.print_swatches(before)289print("AFTER: "); colorutil.print_swatches(after)290PY291```292293Also show a plain text diff of just the changed keys, e.g.:294295```296 model #E06C75 -> #f38ba8297 branch #E5C07B -> #f9e2af298 path #61AFEF -> #89b4fa299 (semantic bands ctx_bar* left unchanged)300```301302## Test render (do not touch the live file)303304The canonical sample input is bundled as `assets/sample_input.json`. Write the305candidate config to a temp path, then run the **resolved active renderer**306(`statusline_py` from Step 0) with `STATUSLINE_CONFIG` pointed at the candidate307so the user's live file stays untouched until they confirm. A non-empty colored308line means success.309310Use the `statusline_py` from Step 0 wherever `<statusline.py>` appears, and this311skill's bundled `<skill>/assets/sample_input.json` for the sample. Do not312hardcode any particular machine's path.313314PowerShell:315316```powershell317$env:STATUSLINE_CONFIG="<candidate.json>"; Get-Content <skill>/assets/sample_input.json | uv run <statusline.py>318# fallback: ... | python <statusline.py>319```320321bash:322323```bash324STATUSLINE_CONFIG=<candidate.json> uv run <statusline.py> < <skill>/assets/sample_input.json325# fallback: python3 instead of uv run326```327328## Writing the result (history-aware commit)329330Never blind-overwrite the live config. On explicit confirmation, install the new331config through the sibling helper's `commit` subcommand, which snapshots the332current config and appends a changelog entry before writing.3333341. Build the full new config object in memory: the current config with ONLY the335 `colors` block changed. Keep key order stable, keep all `_comment` keys and336 every `_`-prefixed key, preserve `templates`/`weather`/`emoji_width`/337 `ctx_bar_empty` exactly, and keep it valid JSON. (This is the same CANDIDATE338 you test-rendered.)3392. **Write that new config to a temp file** (e.g. in the OS temp dir) so large340 JSON never goes through argv.3413. Run the helper's `commit` against the **active `config_path`** from Step 0:342343 ```344 python <skills-dir>/statusline-config/statusline_io.py commit \345 --config <config_path> --new <temp-config> \346 --skill statusline-theme \347 --summary "<what changed, one line>" \348 --why "<the user's intent>" \349 --diff "<the changed-key list, e.g. model #E06C75 -> #f38ba8 ...>"350 ```351352 The helper validates the temp file is JSON (exits 4 touching nothing if not),353 snapshots the about-to-be-replaced config into354 `.statusline-config-history/YYYYMMDD-hhmm.json` next to the active config,355 writes the new bytes to `config_path`, and appends a human entry to356 `.statusline-config-history/YYYYMMDD.md`. For `--summary` keep it to the color357 change (e.g. "Applied Catppuccin Mocha palette" or "Recolored branch ->358 green"); for `--diff` pass the same per-key BEFORE -> AFTER list you previewed.3594. Read the JSON the helper prints (`snapshot`, `changelog`, `config_path`) and360 tell the user what changed in one line plus the snapshot + changelog paths,361 and how to revert (restore the snapshot file, re-run this skill, or restore362 from git). The snapshot is the easiest undo.363364Do NOT write the live config any other way (no direct file write) — always go365through `commit` so history is recorded.366367## Notes on specific palettes368369A few palettes don't have a distinct color for every role; the closest named370color is reused (this is intentional and documented in `palettes.json`):371372- **Monokai**: no separate blue — cyan `#66d9ef` serves both `sky` and `blue`.373- **Rose Pine**: gold `#f6c177` serves `peach` and `yellow`; foam `#9ccfd8`374 serves `sky` and `blue`; pine `#31748f` is the `green` role.375- **Solarized dark/light**: the accent hues are identical; only `text`/`muted`376 differ between the two variants.377- **Catppuccin** comes in four flavors (latte/frappe/macchiato/mocha); Latte and378 Solarized Light are light themes — flag that if the user is on a dark terminal.