Docs Reference
Command line: typo
typo [options] [file...]
The typo bin points at src/cli/main.ts, a Bun script (#!/usr/bin/env bun, using node:util, node:fs/promises and Bun’s runtime). It needs Bun. Without linking the bin, run bun run src/cli/main.ts ... from the package folder (or bun /path/to/typography/src/cli/main.ts).
It typesets text, HTML or Markdown with the same engine as the library functions (typesetText, typesetHtml, typesetMarkdown).
Options
| Flag | Short | Argument | Default | Meaning |
|---|---|---|---|---|
--lang |
-l |
a pack code (the 24 EU languages, uk, ch) |
en |
Language of the text. |
--register |
-r |
display, literary |
display |
Register. |
--format |
-f |
text, html, markdown (md is accepted) |
by extension | How to read the input. |
--quotes |
language, us |
language |
language: the quote marks of the language. us: “…” and ‘…’ in any language and register. |
|
--disable |
-d |
rule ids or groups, comma separated | none | Switch rules off, for example dash.range,space. The flag may be given more than once. |
--enable |
-e |
rule ids or groups, comma separated | none | Switch on rules that are off by default (--rules marks them). A rule named in both --enable and --disable is a usage error. |
--rules |
List the rules (the config’s rules when it has them), one per line: id, category (tradition, symbols, math or data, see rules.md) and description, and exit 0. A rule that is off by default is marked (off by default; --enable <id>). |
|||
--explain |
Print every change and the rule behind it, instead of the typeset text. | |||
--json |
JSON output for --explain. An error without --explain. |
|||
--write |
-w |
Rewrite the files in place. | ||
--check |
-c |
Change nothing; exit 1 if any input would change. | ||
--avoid |
characters | none | Characters the font cannot draw (--avoid "⇒½¼¾−"): a rule that would insert one of them leaves the text as it was typed, for that one change. See api.md. |
|
--nbsp |
entity, char |
entity |
HTML only: write a no-break space as or as the character. Checked for every format, used only for HTML. |
|
--config |
a file | typography in ./package.json |
Read the options from a config (see Config below). | |
--help |
-h |
Show the help and exit 0. | ||
--version |
-v |
Print the version of the package (from package.json) and exit 0. |
Input
- With no file (or
-as the file), the command reads standard input to the end. A terminal with nothing piped in waits for input. - With files, each is read as UTF-8.
- One file and no
--write,--checkor--explain: the typeset text goes to standard output. - Several files need
--write,--checkor--explain; otherwise the command refuses (exit 2), because their outputs would run together. --writeneeds files; it does not work on standard input.
Format by extension
When -f is not given:
| File | Format |
|---|---|
.html, .htm, .xhtml (any case) |
html |
.md, .markdown (any case) |
markdown |
| anything else, and standard input | text |
-f overrides the extension. An unknown format is a usage error.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success. With --check: nothing would change. |
| 1 | --check only: at least one input would change. |
| 2 | A usage error (unknown flag, language, register, format, rule or --nbsp value; a config that cannot be read or does not check; --write with --check; --json without --explain; --explain with --write or --check; --write without files; several files with no --write/--check/--explain) or a file that could not be read. |
Messages go to standard error, prefixed typo:. With --check, each file that would change is named on standard error as <file>: would change. With --write, each file that was rewritten is named as <file>: typeset; a file that needs no change is not written. When a file cannot be read, the command reports it, goes on to the next file and exits 2 at the end (a later would change does not lower it to 1).
--write and --check cannot be used together. --explain only reports.
--explain
Prints one line per change, in the order the rules ran:
note.txt: quote: "\"" -> "«"
note.txt: dash.range: "-" -> "–"
The file name is left out when the input is standard input. With --json the output is an array of { file, rule, from, to } objects, where file is the path or null for standard input:
[
{ "file": null, "rule": "ellipsis", "from": "...", "to": "…" }
]
--explain accepts several files and prints one combined list at the end. It exits 0 unless a file could not be read.
Examples
typo note.txt # typeset to standard output
cat note.txt | typo -l uk # standard input, Ukrainian
typo -w docs/*.md # rewrite Markdown files in place
typo -r literary -l en story.txt # British quotes, spaced en dash
typo -d dash.range,space page.html # a rule and a whole group off
typo -f html --nbsp char < page.txt # read as HTML, write a real no-break space
typo --rules # the rule ids, their categories and what each does
typo --explain note.txt # what would change, and why
typo --explain --json -l de < note.txt
In CI
--check changes nothing and fails the build if a file is not already typeset:
bun run src/cli/main.ts --check -l en docs/*.md || exit 1
Exit 1 means “would change”, exit 2 means the command was misused or a file is missing, so a CI step can tell the two apart. A second run on the output of --write reports nothing (the engine is stable on its own output).
Config
The options can come from a config (TypographyConfig, see api.md), so a project writes them once:
--config <file>: a module (.ts,.mts,.js,.mjs,.cjs) whose default export is the config, usually written withdefineConfig, or a.jsonfile holding the config object. A path is relative to the current directory.- Without
--config: thetypographykey ofpackage.jsonin the current directory, if the file and the key exist.
{
"name": "site",
"typography": { "lang": "uk", "quoteStyle": "us", "enable": ["symbol.plusminus"] }
}
Precedence: a flag beats the config, and --config beats package.json, which is then not read; nothing is merged, so --disable quote replaces the config’s disable list rather than adding to it. The config is checked like defineConfig (unknown key, bad value, a rule name that matches no rule): a mistake is a usage error (exit 2) that names the file. Custom rules can only come from a module; in JSON they are an error. nbsp is used for HTML only, and hangAmount is accepted and ignored (it is for TypographyRoot).
typo --config typography.config.ts -w docs/*.md
typo -l de page.md # package.json's config, with the language replaced
Rules and groups
--disable, --enable and --rules use the ids in rules.md. A name that matches no rule is a usage error (exit 2) and the message lists the ids. The category --rules shows (tradition, symbols, math, data) is a label, not a name --disable takes.
Something wrong or unclear here? Report an issue.