reference

CLI reference

Commands, input selection, exit status and every diagnostic code.

CommandBehavior
smill versionPrint smill <version> and LF; dev unless a build stamped it
smill check <file-or-directory>... [--strict [--source-base-url <GitHub-blob-directory>]]Validate selected Markdown; no success output. --strict also renders the selection in memory and fails on renderer warnings such as unresolved links (REN103)
smill inspect <file>Validate one file and print canonical JSON
smill import reference <file> [options]Controlled conversion, dry-run and loss reports
smill serve [file-or-directory] [--open] [--port 0..65535] [--hugo /absolute/path]Authenticated read-only local live preview through installed Hugo 0.167.0
smill build hugo <file-or-directory>... --out <new-project> [--title <name>] [--description <text>] [--base-path </path/>] [--site-url <https-site-url>] [--edit-base-url <GitHub-edit-directory>] [--source-base-url <GitHub-blob-directory>] [--theme editorial|classic-compact|reference-cards] [--palette <name>] [--header-link <label>=<url>]...Export a self-contained Hugo project; never launches Hugo
smill build navi <file-or-directory>... --out <new-dir> [--shell bash|zsh|fish|nu] [--root <dir>] [--strict]Export {.navi #id} cards as navi .cheat files with .navi.json metadata companions; never runs navi or any command

--header-link is repeatable (at most four) and adds a text link to the top bar and the home page introduction, for example --header-link 'Example library=/sheetmill/demo/'. The label is 1 to 40 printable characters (the only space allowed is U+0020) without surrounding whitespace, and labels must be unique (exact, case-sensitive). The value after the first = must be an https:// URL without credentials or a root-relative path starting with a single /. Other schemes (javascript:, data:, http:), //host URLs, spaces, quotes, angle brackets, backslashes, control and non-ASCII characters in the URL fail with REN003 and exit 1 before anything is written. Root-relative values are used as written and are not prefixed with --base-path.

A release build stamps the version with the linker, for example go build -trimpath -ldflags "-X github.com/wstein/sheetmill/internal/cli.Version=v0.1.0" ./cmd/smill; the repository provides no build target. Official release binaries come only from the tag-triggered release workflow (releasing); other distribution builds need separate approval.

Cobra v1.10.2 parses commands and flags. --help works at every command level; top-level help (smill --help, smill help) ends with a notice pointing AI agents to the agent guide and llms.txt published with the manual; import/build flags may precede or follow the positional input. Check/inspect have no content options. There is no recursive or stdin mode. Directories are scanned only one level, in sorted order, for lowercase .md files. Subdirectories and other extensions are ignored; an empty batch selection is an error. Duplicate explicitly selected paths are checked once. Selected files must be regular files. Symlinks in selected paths or .md directory entries are rejected, including ancestor symlinks. On macOS use /private/tmp instead of /tmp for scratch files, or resolve the explicit path yourself first. Input reads are confined with os.Root and limited to 8 MiB plus one byte for oversized-input detection. No implicit filesystem crawling occurs. Source symlinks are unsupported; this is not a sandbox against an adversary concurrently mutating all parent directories.

Stdout contains requested version/JSON/help data and a build export summary. Diagnostics use stderr: path:line:column: CODE: actionable message. Validations across explicit inputs continue after errors. Exit status precedence is IO/usage (2), then invalid source (1), then success (0). Broken stdout also produces exit 2.

CodeMeaning
SRC001, SRC002Invalid UTF-8; source size limit
FM001, FM002Missing opening/closing frontmatter delimiter
FM003Invalid YAML document/root
FM004Duplicate mapping key
FM005Unsupported YAML construct, type or nesting
VAL001Missing/invalid title
VAL002Invalid general known metadata field
VAL003Invalid cheatsheet metadata/schema
VAL004Invalid cheatsheet.updated or cheatsheet.tool_version (sheet freshness)
MD001, MD002Card without section; empty structural heading
ATTR001, ATTR002, ATTR003Unsupported/malformed attribute; duplicate/conflict; attribute group outside a root ##/### heading
ID001Duplicate explicit/reserved ID
IO001, IO002, IO003Input access/selection; stdout failure; output creation/write
REN001, REN002, REN003Unsupported IR; unsafe URL; invalid, reserved or ambiguous route, base path, site URL, header link, theme or palette (see diagnostics)
REN101, REN102, REN103Inert raw HTML; images retained as unfetched links; unresolved internal link (warnings)
NAV000, NAV001, NAV006, NAV008navi export errors: --strict with warnings; profile; metadata; navi identity collision (NAV007 is retired)
NAV101–NAV106navi export warnings: card skipped (shape, command text, placeholder, declarations); license; empty sheet (NAV107 retired: companions preserve frontmatter). See navi export
IMP001–IMP112See import diagnostic codes

Go package tests exercise the same Run entrypoint as the executable, without building a distributable CLI binary (the release workflow builds those). Parser fuzz targets run via:

go test ./internal/frontmatter -run '^$' -fuzz FuzzParse -fuzztime 5s -parallel 2
go test ./internal/markdown -run '^$' -fuzz FuzzParse -fuzztime 5s -parallel 2

The one-sheet E2E harness also invokes a temporary build of this actual entry point, then pinned Hugo and Chromium. It validates both desktop and mobile reader behavior, no-JavaScript navigation and inert source variants. The temporary executable is verification output, never a distribution artifact.

Local saved-file preview

smill serve ./sheets --hugo /absolute/path/to/hugo
smill serve ./sheets/editor.md --open --port 0

serve defaults to the current directory and port 0 (available IPv4 loopback port). It requires installed Hugo 0.167.0, resolved from the absolute --hugo option or PATH; it downloads nothing. The selection is read-only and direct, nonrecursive, with retained os.Root handles. Empty directories can start a session and receive new direct .md files. A single-file selection never exposes its siblings. Do not use symlinked source paths; /private/tmp is appropriate on macOS. Source limits: 100 files, 8 MiB each and 32 MiB aggregate.

Stdout prints selected scope and one private bootstrap address. Open it once to receive an HttpOnly SameSite=Strict session cookie, then use the clean root URL in that browser. Do not share the bootstrap address. --open launches the fixed platform browser opener after readiness; failure leaves the printed address and running session available. Different ports keep distinct cookies.

Save from an external editor. Pending saves, invalid diagnostics, retained last-valid output, removed references, publisher failures and disconnected state are visible in trusted Studio chrome. Valid peers remain independently usable. A new session has its own source/publishing lifecycle; same-port restart requires the new bootstrap address. No browser editor, command runner or project-write API exists. See tested security boundary.

Unlike batch validation's source-invalid exit 1, invalid source during serve starts/keeps a diagnostic session. Usage/selection/tool/port/startup failures exit 2; normal interruption exits 0 after cleanup; runtime infrastructure failure exits 2. STU001 diagnoses unavailable/unstable/oversized selected input in the UI; STU002 reports CLI session setup/runtime/cleanup failures on stderr. Compiler and renderer diagnostic codes/spans remain unchanged. Browser state contains source-relative names, never the host selection path or session credentials.