20. CLI

lini [options] <input.lini>
lini fmt [--check] [--stdout] <input.lini>
lini desugar <input.lini>
lini highlight <input.lini>
lini highlight --css
lini serve [--port N] [--static] [--theme NAME|FILE|A/B] [PATH]
lini theme [NAME]
FlagMeaning
-o FILEOutput path (default stdout).
--format svg|htmlsvg (default) or HTML wrapper.
--checkThe full compile, no artefact written — the same errors and warnings, the same exit code. (fmt --check is that subcommand’s own flag — below.)
--port Nlini serve only — the preview port (default 7700).
--jsonEmit diagnostics as a JSON document (stable codes, severity, spans, related spans, machine-applicable fixes — SPEC 21) instead of SVG; the tooling/LSP form. Exit 1 if any error-level diagnostic fired.
--theme NAME|FILE|A/BA built-in theme (dark, high-contrast, …), a CSS file of --lini-* overrides, or a light/dark pair (light/dark).
--no-warn / --strictSilence warnings / treat them as errors — under every form (--check, --json, SVG) alike.
--staticA self-contained SVG that renders identically anywhere (SPEC 10.6, SPEC 18).
--embed-fontEmbed the used bundled family × weights as base64 @font-face — browser-only (SPEC 18). Both font flags need the default-on font build feature; name-only output never does.
--watchRecompile on every input change (requires -o).
-h, -VHelp / version.

lini - reads stdin (filename <stdin> in errors). lini serve runs a local live preview (default port 7700): a .lini file live-reloads that one file; a directory (or no path → the current directory) opens the playground — pick, edit, and render any .lini file beneath it in the browser. A served compile reads image assets (SPEC 7) under the same boundary that confines the file list: the served root — a file target’s root is its directory — and an asset path escaping it is a compile error; a plain lini compile is unbounded (you compile your own file). lini theme lists the built-in themes; lini theme NAME prints one as a --lini-* CSS file — a ready starting point for your own (light-dark() colours, the font commented out).

lini fmt reformats to canonical style — 2-space indent, key: value; declarations grouped on one line, a style-only node collapsed onto its head line when it fits (|box#api| { fill: red }), a lone label trailing the head (|box#api| "API"), children one per line in [ ], table cells padded into aligned columns — a styled cell ("Apple" { color: --red-ink }) keeps its block and its row steps out of the aligned grid; unstyled rows stay aligned — a draw: value broken before each move() and wrapped between calls at the column limit (continuations indented, so a profile reads as its subpaths), comments and blank lines preserved — a comment opening its line stays leading, one following code stays on that code’s line, since it annotates the statement it trails. --check exits 1 if it would change anything; --stdout writes instead of rewriting.

lini desugar prints the file fully lowered to primitives — the Desugar pass (SPEC 19) that is the engine’s true input — so the lowered form re-renders byte-identically. A chart’s or sequence’s type desugars here (a |chart| is a |block| wearing .lini-chart); its geometric primitive subtree is a layout-phase artefact (SPEC 19), like a routed link’s geometry. A teaching/debugging view; prints to stdout, never rewrites, comments not preserved. A |table|’s header band and its per-column alignment are likewise not shown: both are decided from the resolved columns: (SPEC 8), which any tier of the cascade can set, so they are a cascade-phase artefact — the structure desugar does show (each cell in its |cell|, an entity’s label as its title |header|) is the part that needs no column count.

lini highlight prints the file as <span class="lini-tok-…"> HTML — the one syntax highlighter, at a shell. It is lexical: it never parses, so a file mid-edit still colours and the only failure left is I/O; and it is byte-preserving — strip the tags, undo the four entity escapes, and the source comes back exactly, which is what lets a host drop the output into a <pre> and trust the listing. Newlines pass through as newlines (a caller that cannot carry one rewrites them itself). The classes are the token kinds, under the reserved prefix like every other name Lini writes into a host document (SPEC 18, SPEC 23): lini-tok- + comment · string · number · const · keyword · type · type-user · prop · prop-user · var · op · class · punct. The words behind them come from the same source the editor grammars do (SPEC 22), so a new type or property colours the moment it has a ledger row.

lini highlight --css prints the token palette those spans wear — nine --lini-tok-* role variables as light-dark() pairs, then the rules that paint the thirteen classes from them — so the markup and its colours come from one place and a listing reads the same in a book, on a site, and in the playground. The role defaults are layered (@layer lini.defaults), so a host re-tints one by redeclaring its variable with no !important; the sheet sets no color-scheme, leaving the light/dark choice to whatever the host has set on the listing’s ancestors.

The same scanner is lini::highlight_html to a crate and highlight() to the browser build; a host that can link Rust should, and this subcommand is for the one that cannot.

Exit codes: 0 success · 1 parse/resolution error or --check reformat needed · 2 I/O · 3 invalid CLI.