Package Exports
- klar-cli
- klar-cli/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (klar-cli) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
klar
Color accessibility tools for AI coding agents and the terminal — powered by OKCA.
(Installed as the klar-cli package; the command is klar.)
All commands run locally with zero network calls.
What this is
klar ships with OKCA (OK Contrast Algorithm) — a WCAG-compatible contrast
algorithm on the familiar 1–21 scale, with zero false passes against the
WCAG 2.x threshold across a 1,249-pair audit of Tailwind, the GOV.UK Design
System, and USWDS v3.x.
The problem OKCA solves:
Hot pink (
#ff69b4) on near-black (#1a1a1a) scores 6.6:1 under WCAG and passes AA. It's also illegible. A same-luminance neutral grey scores identically — the WCAG formula is blind to saturation.Full write-up: Hot Pink Passes WCAG. That's the Problem.
Try it in 10 seconds
npx klar-cli contrast "#ff69b4" "#1a1a1a"Install
# Global CLI
npm install -g klar-cli
klar contrast "#fff" "#000"
# Or run without installing
npx klar-cli contrast "#fff" "#000"Contrast algorithms
Built-in (no plugin required): okca (default), wcag2, deltaE.
Additional contrast algorithms are available as optional, separately-installed
plugins discovered by the klar-plugin-* naming convention. Each plugin is
maintained and licensed independently — see PLUGINS.md.
Global Options
| Flag | Description |
|---|---|
-v, --version |
Print version |
-h, --help |
Print help |
--no-plugins |
Disable discovery of contrast-algorithm plugins for this run; use built-in algorithms only |
Plugin loading can also be restricted via the KLAR_PLUGINS allowlist,
KLAR_NO_PLUGINS, or a package.json "klar": { "plugins": [...] } entry — see
SECURITY.md.
Output Modes
Every command supports three output modes (except variants which lacks -q):
| Flag | Behavior | Stdout format |
|---|---|---|
| (none) | Human-readable with ANSI color swatches | Multi-line text |
--json |
Machine-readable structured data | Pretty-printed JSON |
-q, --quiet |
Single value only, no labels | Plain text, one line |
--json and -q write to stdout only, with no ANSI codes, making them safe for piping.
Command Reference
contrast — Calculate contrast between two colors
klar contrast <color1> <color2> [options]Arguments:
| Arg | Description |
|---|---|
color1 |
First color — hex, rgb(), oklch() |
color2 |
Second color — hex, rgb(), oklch() |
Options:
| Flag | Default | Description |
|---|---|---|
-t, --type <type> |
okca |
Algorithm: okca, wcag2, deltaE (built-in), plus any installed plugins |
--json |
JSON output | |
-q, --quiet |
Print only the numeric contrast value |
Built-in contrast algorithms:
| Type | Range | Meaning |
|---|---|---|
okca |
1 to 21 | OKCA: OKLCH-native, WCAG-compatible ratio. 0 false passes vs WCAG (1,249-pair audit). |
wcag2 |
1 to 21 | Traditional WCAG 2.x luminance ratio |
deltaE |
0 to 100 | Delta E 2000 perceptual color difference |
Additional algorithms are available as optional plugins — see PLUGINS.md.
JSON schema:
{
"contrast": 21, // number — the calculated value
"type": "okca", // string — algorithm used
"colorOne": "#ffffff", // string — first color as provided
"colorTwo": "#000000" // string — second color as provided
}Quiet output: single number, e.g. 21
Examples:
klar contrast "#ffffff" "#000000"
klar contrast "#fff" "#000" --type wcag2
klar contrast "#fff" "#000" --type okca --json
klar contrast "oklch(50% 0.2 240)" "#000" -q
klar contrast "rgb(59,130,246)" "#ffffff" --type deltaEpair — Generate a random accessible color pair
klar pair [options]Options:
| Flag | Default | Description |
|---|---|---|
--min-lightness <n> |
Minimum OKLCH lightness 0–100 | |
--max-lightness <n> |
Maximum OKLCH lightness 0–100 | |
--json |
JSON output | |
-q, --quiet |
Print only two hex values space-separated |
JSON schema:
{
"colorOne": "#1c2333", // string — foreground hex
"colorTwo": "#ffe3e8", // string — background hex
"contrast": 13 // number — OKCA score between the pair
}Quiet output: two hex values space-separated, e.g. #1c2333 #ffe3e8
Examples:
klar pair
klar pair --min-lightness 20 --max-lightness 80
klar pair --json
klar pair -qmeta — Get OKLCH metadata for a color
klar meta <color> [options]Arguments:
| Arg | Description |
|---|---|
color |
Color to analyze — hex, rgb(), oklch() |
Options:
| Flag | Description |
|---|---|
--json |
JSON output |
-q, --quiet |
Print L C H space-separated |
JSON schema:
{
"color": "#3b82f6", // string — input color
"lightness": 0.62, // number — OKLCH L (0–1 scale)
"chroma": 0.19, // number — OKLCH C (0–~0.4)
"hue": 259.81, // number — OKLCH H (0–360 degrees)
"saturation": 30.17 // number — saturation percentage
}Quiet output: L C H space-separated, e.g. 0.62 0.19 259.81
Examples:
klar meta "#3b82f6"
klar meta "rgb(59, 130, 246)" --json
klar meta "#000000" -qvariants — Generate a perceptually-spaced color variant grid
Two modes: adaptive (default) and fixed-step.
Adaptive mode (default)
The grid walks chroma first (at the base lightness), then lightness
independently per chroma level — so each column explores the full gamut
range at that chroma. Within each column, adjacent cells differ by at
least 11 Delta E 2000 (configurable via --min-delta). Only in-gamut
cells are emitted — no wasted tokens on out-of-gamut placeholders.
klar variants <color> [--min-delta <n>] [--json]The grid size depends on the input color's sRGB gamut:
- Neutral colors (e.g.
#808080) produce larger grids because they have wide gamut boundaries in both lightness and chroma. - High-chroma colors (e.g.
#3b82f6,#ff6600) produce smaller grids because their gamut is narrow — there are fewer perceptually distinct variants.
Each column has constant chroma with lightness decreasing top-to-bottom. This is correct behavior, not a limitation — every cell in the output is a meaningfully different color that an agent can act on.
Fixed-step mode
Activated by passing --light-steps and/or --chroma-steps, or by using --color-space hsl.
Divides the space into uniform intervals. May produce out-of-gamut cells ("color": "").
klar variants <color> --light-steps <n> --chroma-steps <n> [--color-space <space>] [--json]Arguments:
| Arg | Description |
|---|---|
color |
Base color — hex, rgb(), oklch() |
Options:
| Flag | Default | Description |
|---|---|---|
--min-delta <n> |
11 |
Minimum Delta E 2000 between adjacent cells (adaptive mode) |
--light-steps <n> |
Fixed lightness steps — activates fixed-step mode | |
--chroma-steps <n> |
Fixed chroma steps — activates fixed-step mode | |
--color-space <space> |
oklch |
oklch or hsl (hsl forces fixed-step mode) |
--json |
JSON output |
No -q mode (grid data doesn't reduce to a single value).
JSON schema: 2D array [row][col], each cell:
{
"color": "#7f8895", // string — hex (empty "" only in fixed-step mode)
"lightness": 0.623, // number — OKLCH L
"chroma": 0.023, // number — OKLCH C
"hue": 259.81, // number — OKLCH H
"deltaE": 18, // number — Delta E 2000 from base color
"deltaChroma": -88, // number — % chroma change from base
"deltaLightness": 0 // number — % lightness change from base
}In adaptive mode, every cell has a valid hex color — no empty strings.
In fixed-step mode, cells with "color": "" are out-of-gamut and should be skipped.
Examples:
# Adaptive (default) — perceptually-spaced, zero waste
klar variants "#3b82f6"
klar variants "#ff6600" --min-delta 15
klar variants "#3b82f6" --json
# Fixed-step — uniform intervals, may have out-of-gamut cells
klar variants "#3b82f6" --light-steps 10 --chroma-steps 5
klar variants "#3b82f6" --color-space hsl --light-steps 5 --chroma-steps 3match — Match chroma between two colors
Adjusts the first color's chroma to match the second color's chroma in OKLCH space.
klar match <color1> <color2> [options]Arguments:
| Arg | Description |
|---|---|
color1 |
Color to adjust |
color2 |
Reference color (chroma source) |
Options:
| Flag | Description |
|---|---|
--json |
JSON output |
-q, --quiet |
Print two adjusted hex values space-separated |
JSON schema:
{
"success": true, // boolean — false when no in-gamut match exists
"colors": ["#f86d25", "#3b82f6"], // [string, string] | null
"chroma": 0.188 // number — matched chroma value
}When success is false, neither color can adopt the other's chroma within the
sRGB gamut: colors is null and the command exits 1 (see Exit Codes).
Quiet output: two hex values space-separated, e.g. #f86d25 #3b82f6
Examples:
klar match "#ff6600" "#3b82f6"
klar match "#ff6600" "#3b82f6" --json
klar match "#ff6600" "#3b82f6" -qlightness — Get min/max lightness for a color in sRGB gamut
Returns the OKLCH lightness boundaries where the color's hue + chroma remain displayable in sRGB.
klar lightness <color> [options]Arguments:
| Arg | Description |
|---|---|
color |
Color to analyze — hex, rgb(), oklch() |
Options:
| Flag | Description |
|---|---|
--json |
JSON output |
-q, --quiet |
Print min and max space-separated |
JSON schema:
{
"originalCoords": [0.623, 0.188, 259.81], // [L, C, H] in OKLCH
"lightMin": 0.466, // number — minimum L in gamut
"lightMax": 0.648 // number — maximum L in gamut
}Quiet output: min max space-separated (4 decimal places), e.g. 0.4660 0.6480
Examples:
klar lightness "#3b82f6"
klar lightness "#3b82f6" --json
klar lightness "#3b82f6" -qfind — Find a color meeting a target contrast
Binary-searches OKLCH lightness to find a variation of the reference color that meets the target contrast against the base color. The target is a floor (a minimum): the result is guaranteed to be at or above it, returning the color closest to the target from above.
klar find <base-color> <reference-color> [options]Arguments:
| Arg | Description |
|---|---|
base-color |
Color to keep fixed (e.g. background) |
reference-color |
Color to adjust (e.g. text) |
Options:
| Flag | Default | Description |
|---|---|---|
--target <n> |
(required) | Target contrast value |
-t, --type <type> |
okca |
Algorithm: okca, wcag2, deltaE (built-in), plus any installed plugins |
--tolerance <n> |
0.5 |
Acceptable overshoot above the target — the band within which the search stops early. Never accepts a result below the target. |
--json |
JSON output | |
-q, --quiet |
Print only the adjusted hex color |
JSON schema:
{
"adjustedColor": "#2563eb", // string — result hex
"actualContrast": 4.6, // number — achieved contrast (of adjustedColor)
"iterations": 10, // number — binary search steps
"success": true, // boolean — true when actualContrast >= target
"message": "", // string? — explanation if failed
"oklch": { // object? — OKLCH of adjusted color
"l": 0.523,
"c": 0.188,
"h": 259.8
}
}When success is false the target is unachievable by adjusting lightness
alone: adjustedColor holds the closest reachable color, message explains
why, and the command exits 1 (see Exit Codes).
Quiet output: adjusted hex, e.g. #2563eb. On a soft failure the closest
color is still printed, but the command exits 1 — so $(klar find … -q)
inside && won't apply a non-compliant color.
Examples:
klar find "#ffffff" "#3b82f6" --target 4.5
klar find "#000000" "#cccccc" --target 4.5 --type wcag2
klar find "#ffffff" "#3b82f6" --target 4.5 --json
klar find "#ffffff" "#3b82f6" --target 4.5 -q
klar find "#ffffff" "#3b82f6" --target 7 --type okca --tolerance 0.1plugins — List registered contrast algorithm plugins
klar plugins list [options]Lists every contrast-algorithm plugin klar discovered, with its source, package
name, version, and resolved path — so you can see exactly what third-party code
klar will run. Built-in algorithms (okca, wcag2, deltaE) are always
available and are not plugins. See PLUGINS.md and SECURITY.md.
Options:
| Flag | Description |
|---|---|
--json |
JSON output |
-q, --quiet |
Print only plugin ids, one per line |
JSON schema: array of objects, one per registered plugin:
[
{
"id": "cvd-brettel", // string — the --type value
"displayName": "CVD (Brettel)", // string — human-readable name
"description": "…", // string — one-line summary
"source": "global", // "project" | "global" | null
"packageName": "klar-plugin-cvd-brettel", // string | null
"version": "1.0.0", // string | null
"resolvedPath": "/abs/path/to/plugin" // string | null
}
]Quiet output: plugin ids, one per line.
Examples:
klar plugins list
klar plugins list --json
klar plugins list -qColor Input Formats
All commands accept colors in these formats:
| Format | Example |
|---|---|
| Hex (6 digit) | "#3b82f6" |
| Hex (3 digit) | "#fff" |
| RGB function | "rgb(59, 130, 246)" |
| OKLCH function | "oklch(62% 0.19 260)" |
Shell quoting: Always quote colors containing # or parentheses to prevent shell interpretation:
klar contrast "#fff" "#000" # Hex — must quote #
klar meta "rgb(59, 130, 246)" # RGB — must quote parens
klar meta "oklch(62% 0.19 260)" # OKLCH — must quote parensAdvanced Workflows
For AI agents: See AGENT_PLAYBOOK.md for end-to-end art direction workflows — palette building, dark mode translation, accessibility auditing, and more.
Scripting with --json and jq
# Extract just the contrast score from a check
klar contrast "#1a1a2e" "#e94560" --json | jq '.contrast'
# Compare the built-in algorithms for the same pair
for algo in okca wcag2 deltaE; do
echo -n "$algo: "
klar contrast "#1a1a2e" "#e94560" --type $algo -q
done
# Generate a pair and immediately check all algorithms
PAIR=$(klar pair --json)
FG=$(echo "$PAIR" | jq -r '.colorOne')
BG=$(echo "$PAIR" | jq -r '.colorTwo')
klar contrast "$FG" "$BG" --type wcag2 --jsonFinding accessible color variations
# Given a brand blue, find a shade that passes WCAG AA on white
klar find "#ffffff" "#3b82f6" --target 4.5 --type wcag2 -q
# Find accessible text for dark background
klar find "#1a1a2e" "#e94560" --target 4.5 --type okca --json
# Check if the result actually meets the target
ADJUSTED=$(klar find "#ffffff" "#3b82f6" --target 4.5 --type wcag2 -q)
klar contrast "#ffffff" "$ADJUSTED" --type wcag2 -qAnalyzing gamut boundaries
# Get the lightness range for a color
klar lightness "#3b82f6" --json | jq '{min: .lightMin, max: .lightMax}'
# Check how much room to darken or lighten
META=$(klar meta "#3b82f6" --json)
RANGE=$(klar lightness "#3b82f6" --json)
echo "Current L: $(echo $META | jq '.lightness')"
echo "Range: $(echo $RANGE | jq '.lightMin') to $(echo $RANGE | jq '.lightMax')"Variant exploration
# Adaptive grid (default) — all cells are in-gamut and perceptually distinct
klar variants "#3b82f6" --json | jq '[.[][] | {color, lightness, deltaE}]'
# List all variant hexes for downstream use
klar variants "#3b82f6" --json | jq '[.[][] | .color]'
# Find the variant most different from the base
klar variants "#3b82f6" --json \
| jq '[.[][]] | sort_by(-.deltaE) | .[0]'
# Use a tighter grid for more choices (minDelta=8)
klar variants "#808080" --min-delta 8 --json | jq '[.[][]] | length'
# Fixed-step mode when you need a specific grid size
klar variants "#ff6600" --light-steps 5 --chroma-steps 3 --json \
| jq '[.[][] | select(.color != "") | {color, lightness, deltaE}]'Chroma matching for brand palettes
# Match chroma between brand primary and secondary
klar match "#ff6600" "#3b82f6" --json | jq '.chroma'
# Verify both colors now share the same chroma
MATCHED=$(klar match "#ff6600" "#3b82f6" --json)
C1=$(echo "$MATCHED" | jq -r '.colors[0]')
C2=$(echo "$MATCHED" | jq -r '.colors[1]')
echo "Color 1 chroma: $(klar meta "$C1" --json | jq '.chroma')"
echo "Color 2 chroma: $(klar meta "$C2" --json | jq '.chroma')"Combining commands in pipelines
# Full accessibility audit for a color pair
FG="#1a1a2e"
BG="#e94560"
echo "=== Color Pair Audit ==="
echo "Foreground: $FG"
klar meta "$FG" --json | jq '{L: .lightness, C: .chroma, H: .hue}'
echo "Background: $BG"
klar meta "$BG" --json | jq '{L: .lightness, C: .chroma, H: .hue}'
echo "---"
echo "OKCA: $(klar contrast "$FG" "$BG" --type okca -q)"
echo "WCAG: $(klar contrast "$FG" "$BG" --type wcag2 -q)"
echo "DeltaE: $(klar contrast "$FG" "$BG" --type deltaE -q)"Exit Codes
klar follows a grep-style convention so scripts can branch on the outcome:
| Code | Meaning |
|---|---|
0 |
Success — the operation produced a satisfying result |
1 |
Soft failure — a valid operation whose answer is negative (find target unachievable, match infeasible) |
2 |
Usage error — invalid input or arguments |
Usage errors (2) are written to stderr with an Error: prefix. On a soft
failure (1) the result payload is still written to stdout — find prints
the closest reachable color, --json reports "success": false — so the exit
code guards a pipeline while the data remains inspectable:
# Apply only when a compliant color was actually found
ADJUSTED=$(klar find "$BG" "$FG" --target 4.5 -q) && apply "$ADJUSTED" \
|| echo "no color meets the target on this background"Architecture
The CLI uses standalone service classes (no framework DI). Contrast algorithms are loaded via a plugin registry — built-in types (okca, wcag2, deltaE) plus any optional contrast-algorithm plugins discovered by the klar-plugin-* convention at runtime (see PLUGINS.md).
Development
npm install
npm run build # Compile TypeScript to dist/
npm test # Run tests
npm run dev # Watch mode (tsc --watch)License
MIT