Alpha — experimental. Breaking changes ship without a deprecation cycle, and nothing here carries a stability promise yet.What is built vs. planned →

CLI reference

One binary, subcommands. Human logs go to stderr; requested data goes to stdout; exit codes are stable. All variant selection lives in config, never in flags — same command, same output, on every machine.

Implemented

transtyle build [instance...]

Runs the full pipeline and writes artifacts. With no arguments, builds every target instance in the config; with names, only those (transtyle build shadcn shadcn-v3).

npx transtyle build shadcn
#
# shadcn  42% native · 53% derived · 3% approximated · 3% dropped
#   ↳ dist/shadcn/globals.transtyle.css
#   ↳ dist/shadcn/usage.md
#   ↳ dist/shadcn/report.json
#
# ✔ build complete

Per instance, emits the exporter’s artifacts plus report.json (schema-versioned: coverage items, diagnostics, file list). If any error-level diagnostic exists, nothing is emitted — a build never half-succeeds.

transtyle check [instance...]

The pipeline minus EMIT — same code path, guaranteed to agree with real builds. Runs schema validation, alias/cycle detection, mode validation, WCAG contrast checks, and coverage computation, writing nothing.

--json

check only. Prints the full diagnostics array and per-target coverage to stdout as one JSON object — human-readable logs still go to stderr, so both work in the same invocation (pipe stdout to jq, read stderr in your terminal):

npx transtyle check --json
# { "diagnostics": [ { "severity": "warning", "code": "TST1305", "message": "..." }, ... ],
#   "targets": [ { "target": "shadcn", "coverage": [ ... ] }, ... ] }

--cwd <dir>

Run against a project directory from anywhere: transtyle build --cwd examples/cathode.

transtyle explain <slot> [--mode <name>]

Prints a slot’s resolved value, provenance, and — for derived/defaulted values — the rule that computed it and every input, recursively indented. Accepts the slot with or without the semantic./semantic.color. prefix.

npx transtyle explain primary.on-tint
#
# semantic.color.primary.on-tint = oklch(0.48 0.162 255)  [#005bb6]
#  └─ derived by rule contrast-pick(subtle)@standard@1
#     inputs: semantic.color.primary.tint = oklch(0.95 0.017 255)  [#e7effa]
#      └─ derived by rule mix-toward-surface(0.92)@standard@1
#         inputs: semantic.color.primary.solid = oklch(0.55 0.18 255)  [#026fd7]
#          └─ aliased → option.color.blue.600

An unknown slot exits 2 and lists the 5 closest catalog names instead of a bare error.

transtyle diff [ref]

Semantic diff of the resolved token graph against a git ref (default HEAD), plus per-target impact. It compiles both the working tree and the project at the ref and compares resolved values per mode — so a token rename that changes no resolved value reports nothing, while one authored change shows its full derived cascade.

npx transtyle diff              # what have I changed since my last commit?
npx transtyle diff main         # what does this branch do to the compiled themes?
#
# Semantic diff vs main:
# [light]
#   ~ semantic.color.primary.solid  oklch(0.55 0.18 255)  [#026fd7] → oklch(0.55 0.19 25)  [#ca3535]
#   ...
# Per-target impact:
#   bootstrap: 63 lines changed
#   shadcn: 34 lines changed

It also flags contrast regressions — pairs that passed your configured WCAG standard before the change and fail after it:

⚠ Contrast regressions:
  ✖ text.base on elevation.0.surface (light): 18.1:1 → 2.2:1 — now FAILS 4.5:1

That’s the difference between check (“contrast is bad”) and diff (“this change made it bad”) — the second is what a green CI baseline can otherwise lose silently.

Exits 0 when the compiled themes are identical, 1 when there are changes (composes in CI like git diff --exit-code), 2 on a missing repo/unknown ref. --json prints a machine-readable report to stdout for PR tooling, including a contrastRegressions array. Full contract: the diff spec.

transtyle init [name]

Scaffolds transtyle.config.json + tokens/brand.tokens.json (a minimal example: one brand color, elevation levels 0–1, text, border, radius, fonts — each with a $description: "TODO: ..." placeholder) and a css-variables target so the first build works immediately. Refuses (exit 2) if a config already exists.

transtyle add <target>

Validates the target against the CLI’s known exporters and inserts "<target>": { "output": "dist/<target>" } into the existing config. Refuses (exit 2) for an unknown or already-configured target.

Exit codes

Code Meaning
0 Success (possibly with warnings below your check.failOn threshold)
1 Diagnostics at or above the failOn threshold
2 Usage or config error (unknown command, missing config, broken exporter)

transtyle diff overloads exit 1 to mean “changes found” (like git diff --exit-code), not a diagnostic failure.

Diagnostics format

Every diagnostic has a stable code, printed with severity:

⚠ TST2101 text.muted vs elevation.1.surface is 4.4:1 in light mode (< 4.5:1 wcag21-aa)
✖ TST1104 Alias cycle: semantic.color.a → semantic.color.b → semantic.color.a

The full code table lives in Weird things & diagnostics.

Specced, not yet implemented

These exist as design (see Status & roadmap) and will keep the same principles when they land:

Command What it will do
transtyle init (interactive mode) A brand-color prompt instead of the fixed placeholder scaffold shipped today
transtyle add <exporter> (community plugins) Install + register third-party exporter packages, printing their manifest first
transtyle explain <token> --target <t> (new flag) Also show which target variable the value maps to and why (today’s explain stops at provenance)
transtyle import <source> Materialize an importer’s output (Figma, Tailwind, Bootstrap) as reviewable token files
transtyle preview Local themed preview site across all targets

Programmatic use: every command wraps @transtyle/core’s public compile() — the CLI contains no logic a build-tool integration can’t reach.