The command line
stxt validates, formats and describes STXT documents from a terminal, a script or
a continuous-integration job.
It uses the TypeScript library @stxt-lang/core and carries no
parser of its own: the errors and their codes are the same as in the VS Code
extension, the playground and the libraries. Its use on a complete project, with its
.stxt/ directory, is in The working environment.
Installation
It is published on npm as
@stxt-lang/cli and needs
Node 20 or newer:
npm install -g @stxt-lang/cli
stxt --version
stxt 1.0.4 (@stxt-lang/core 1.0.3, spec 2026-09-07)
--version prints the version of the command, that of the library and the date of
the STXT-SPEC the library implements. Without installing, through npx:
npx @stxt-lang/cli validate --recursive docs/
Within the 1.x line of the package, the commands, their options, the exit codes and
--format json are stable. The text output may change.
Synopsis
stxt [--version | --help]
stxt validate <file|dir|->... [--recursive] [--format text|json] [--warn-schema | --no-schema]
[--verbose] [--max-nesting N] [--max-line-length N] [--max-input-size N]
stxt format <file|dir|->... [--recursive] [--tabs | --spaces] [--write | --check] [--clean]
[--verbose] [--max-nesting N] [--max-line-length N] [--max-input-size N]
stxt describe <file|-> [--max-nesting N] [--max-line-length N] [--max-input-size N]
stxt schemas [path]
stxt install <file> [--local | --user | --system | --root <dir>] [--force] [--ignore-non-definitions]
| Command | What it does |
|---|---|
stxt validate |
Parses and validates documents against the grammars it resolves; no output when all pass |
stxt format |
Rewrites documents in canonical form, keeping comments |
stxt describe |
Emits the logical tree of a document as JSON (STXT-TREE-SPEC) |
stxt schemas |
Shows the resolution chain and which grammar applies to each namespace |
stxt install |
Validates a grammar and installs it into a level of the resolution chain |
Conventions common to every command:
- Options are long, with a double dash. Four have a short alias:
-v(--version),-h(--help),-r(--recursive) and-w(--write). --versionand--helpare accepted in any position and take precedence. There is no per-command help:stxt validate --helpprints the general one.- An unknown option or command is a usage error: code
2, with no action performed. - Results go to standard output, the report of
validateincluded. Usage and read errors, the syntax errors offormatanddescribe, and the progress of--verbose, go to standard error. -denotes standard input invalidate,formatanddescribe, once per call, and the document is reported as<stdin>. Without arguments, no command reads standard input: that is a usage error.- No command rewrites files without an explicit option:
formatonly writes with--write, andinstallonly overwrites with--force.
Exit codes
| Code | Meaning |
|---|---|
0 |
Success: the documents validate, nothing needs reformatting, the definition was installed |
1 |
Document failure: syntax or grammar errors, files that --check would change, an unreadable file, a definition that cannot be installed, a resolution chain with errors in schemas |
2 |
Misuse: unknown command or option, missing argument, incompatible options, a directory without --recursive |
stxt validate
Parses each document, resolves its grammar chain (see Grammar resolution and
STXT_PATH) and validates it against the grammar of every namespace it uses. When
everything passes it writes nothing and exits with 0.
| Option | Effect |
|---|---|
--recursive, -r |
Descends into directories and validates all their *.stxt, sorted by name; .stxt/ directories are skipped |
--format text |
One finding per line, plus a summary (default) |
--format json |
The same findings as a JSON array |
--warn-schema |
Grammar errors are reported as warnings and do not fail; syntax errors still do |
--no-schema |
Syntax only: no grammar is resolved or applied |
--verbose |
Writes Validating <file> to standard error before each document |
--max-nesting N, --max-line-length N, --max-input-size N |
The parser limits (STXT-SPEC §11.2): nesting levels, line length and total input size; defaults 100, 10000 and 10000000; -1 disables the limit |
A directory requires --recursive; without it, it is a usage error (2). Files,
directories and - can be mixed in the same call.
An exceeded limit is reported as one more parse error and aborts the parse of that
document. validate parses in streaming: the memory it uses is on the order of the
largest root node and not of the document. A document larger than the default input
size is validated with --max-input-size -1.
In text mode, the findings of each document are written when that document finishes,
and the count at the end. The array of --format json is written whole at the end.
--verbose shows the progress without touching standard output:
stxt validate --recursive --verbose docs/
Validating /home/ana/books/docs/book.stxt
With -, the grammar chain is that of the current directory, the one stxt schemas
shows without an argument:
git show HEAD:docs/book.stxt | stxt validate -
The findings
With the project of The working environment (the
com.acme.book template in .stxt/), a docs/book.stxt with an invalid date and
without the mandatory ISBN:
stxt validate docs/book.stxt
/home/ana/libros/docs/book.stxt:6: [INVALID_VALUE] Published: Invalid date (1 de octubre de 2025) (error)
/home/ana/libros/docs/book.stxt:1: [TOO_FEW_CHILDREN] 0 nodes of 'com.acme.book:isbn' and min is 1 (error)
2 error(s), 0 warning(s)
Each line is file:line: [CODE] message (severity), with the absolute path, and the
count comes at the end. The codes are stable and the same in every tool. The most
common ones:
| Code | Scope |
|---|---|
INDENTATION_MIXED, INDENTATION_LEVEL_NOT_VALID, INVALID_LINE |
Syntax (STXT-SPEC) |
INVALID_VALUE, TOO_FEW_CHILDREN, CHILD_NOT_DECLARED, NODE_NOT_DEFINED_IN_SCHEMA |
Grammar (STXT-SCHEMA-SPEC) |
SCHEMA_NOT_FOUND |
The document uses a namespace the chain does not define |
DISCOVERY_DUPLICATE_NAMESPACE, DISCOVERY_NOT_A_DEFINITION |
The resolution chain itself (STXT-DISCOVERY-SPEC) |
FILE_NOT_READABLE |
The file does not exist or cannot be read |
A cardinality error is reported on the line of the parent (Book, line 1). Errors of
the resolution chain (an invalid grammar in .stxt/, two definitions of the same
namespace at the same level) are reported on line 0 of the offending file and fail
the validation:
stxt validate docs/book.stxt
/home/ana/libros/.stxt/otro/copia.stxt:0: [DISCOVERY_DUPLICATE_NAMESPACE] Duplicate definition for namespace 'com.acme.book' at level /home/ana/libros/.stxt: already defined in /home/ana/libros/.stxt/@stxt.template/com.acme.book.stxt (error)
1 error(s), 0 warning(s)
Without a grammar, or with the grammar as a warning
- A document without a namespace is not validated and passes (STXT-SCHEMA-SPEC §5).
- One whose namespace the chain does not define produces
SCHEMA_NOT_FOUND, also when the chain is empty. - A definition (
@stxt.schemaor@stxt.template) is always checked against its meta-schema.
--warn-schema downgrades grammar errors to warnings, to introduce a grammar into a
project with documents that predate it:
stxt validate --warn-schema docs/book.stxt
/home/ana/libros/docs/book.stxt:6: [INVALID_VALUE] Published: Invalid date (1 de octubre de 2025) (warning)
/home/ana/libros/docs/book.stxt:1: [TOO_FEW_CHILDREN] 0 nodes of 'com.acme.book:isbn' and min is 1 (warning)
0 error(s), 2 warning(s)
It exits with 0. With --no-schema only the syntax is checked: the same document
passes with no output.
JSON output
With --format json the output is an array with one object per finding (file,
line, code, message, severity), or [] when everything passes, with no
summary. The exit code is the same as in text mode.
stxt validate --format json docs/book.stxt
[{"file":"/home/ana/libros/docs/book.stxt","line":6,"code":"INVALID_VALUE","message":"Published: Invalid date (1 de octubre de 2025)","severity":"error"},{"file":"/home/ana/libros/docs/book.stxt","line":1,"code":"TOO_FEW_CHILDREN","message":"0 nodes of 'com.acme.book:isbn' and min is 1","severity":"error"}]
# How many findings of each code there are in the whole project
stxt validate --format json --recursive docs/ | jq -r '.[].code' | sort | uniq -c
stxt format
Rewrites documents line by line, following the reformatting of STXT-TREE-SPEC §12:
- It normalises indentation to tabs (or to four spaces with
--spaces), leaves a single space after the colon and removes trailing whitespace. - It keeps comments, blank lines, the original line ending (CRLF included) and the absence of a final newline.
- The content of
>>blocks is only re-indented. The final blank lines of a block are not content and are left unindented. - Of the indentation of a comment it converts only the whole units (tabs or groups of four spaces).
- It writes the namespace only where the source wrote it.
Three modes, mutually exclusive:
| Mode | What it does | Exits with 1 if... |
|---|---|---|
| (default) | Prints the result to standard output; does not write to disk | Some document does not parse |
--check |
Lists the files that would change (<file>: would be reformatted); writes nothing |
Some would change, or does not parse |
--write, -w |
Rewrites each file in place, only when it changes, and reports it (Formatted <file>) |
Some document does not parse |
Other options:
| Option | Effect |
|---|---|
--tabs / --spaces |
Indent with tabs (default) or with four spaces |
--recursive, -r |
As in validate: descends, sorts by name, skips the .stxt/ directories |
--clean |
Re-serialises the logical tree (STXT-TREE-SPEC §11): comments and blank lines are lost |
--verbose |
Writes Formatting <file> (Checking <file> with --check) to standard error before each document |
--max-nesting N, --max-line-length N, --max-input-size N |
The parser limits, as in validate |
--check is equivalent to gofmt -l or prettier --check. A document with syntax
errors is reported and not reformatted. format applies no grammar. Combining
--write with --check, or --tabs with --spaces, is a usage error (2).
A document written with spaces, with a comment, a blank line and some extra spaces:
# Book record
Book (com.acme.book):
Title: Arquitectura de software moderna
Authors:
Author: María Pérez
ISBN: 978-84-123456-7-8
stxt format docs/book.stxt
# Book record
Book (com.acme.book):
Title: Arquitectura de software moderna
Authors:
Author: María Pérez
ISBN: 978-84-123456-7-8
The other two modes on the same file:
stxt format --check docs/book.stxt
/home/ana/libros/docs/book.stxt: would be reformatted
# exit code 1
stxt format --write docs/book.stxt
Formatted /home/ana/libros/docs/book.stxt
stxt format --check docs/book.stxt
# writes nothing any more: exit code 0
With -, format is a filter: it reads from standard input and writes to standard
output. --check - answers <stdin>: would be reformatted and 1 if it would
change. --write - is a usage error:
stxt format --spaces - < docs/book.stxt
stxt format --write -
stxt format: --write cannot be used with - (the standard input); the result is printed to stdout
stxt describe
Writes the logical tree of a document to standard output in the canonical JSON of
STXT-TREE-SPEC: an array of root nodes, each with name,
canonicalName, the effective namespace and the form ("inline" with value and
children, or "block" with lines). It includes no positions or comments.
It resolves no grammar and validates nothing. If the document has syntax errors it
emits no partial tree: it reports them on standard error and exits with 1. It
accepts - and the same parser limits as validate.
Book (com.acme.book):
Title: Arquitectura de software moderna
Chapter: Introducción
Content >>
Conceptos básicos y objetivos del libro.stxt describe docs/book.stxt
[
{
"name": "Book",
"canonicalName": "book",
"namespace": "com.acme.book",
"form": "inline",
"value": "",
"children": [
{
"name": "Title",
"canonicalName": "title",
"namespace": "com.acme.book",
"form": "inline",
"value": "Arquitectura de software moderna",
"children": []
},
{
"name": "Chapter",
"canonicalName": "chapter",
"namespace": "com.acme.book",
"form": "inline",
"value": "Introducción",
"children": [
{
"name": "Content",
"canonicalName": "content",
"namespace": "com.acme.book",
"form": "block",
"lines": [
"Conceptos básicos y objetivos del libro."
]
}
]
}
]
}
]
# The book's title, with jq
stxt describe docs/book.stxt | jq -r '.[0].children[] | select(.canonicalName == "title") | .value'
stxt schemas
Shows the resolution chain of a document or a directory (by default, the current
one) and, for each namespace, the active definition and its file. It is the first
diagnostic for a SCHEMA_NOT_FOUND.
stxt schemas docs
Resolution chain for /home/ana/libros/docs:
/home/ana/libros/.stxt
Namespaces:
com.acme.book <- /home/ana/libros/.stxt/@stxt.template/com.acme.book.stxt
The chain lists the levels in order of precedence. If the project sits inside another
with its own .stxt/, both appear, and for each namespace the closest one wins.
With no level at all:
stxt schemas
Resolution chain for /home/ana/notas:
(empty: no .stxt directory found)
No namespaces resolved.
Errors of the chain go to standard error, in an Errors: block at the end. With two
definitions of the same namespace at the same level, the namespace is left without an
active definition, and validate fails with the same error:
stxt schemas docs
Resolution chain for /home/ana/libros/docs:
/home/ana/libros/.stxt
No namespaces resolved.
Errors:
DISCOVERY_DUPLICATE_NAMESPACE /home/ana/libros/.stxt/otro/copia.stxt: Duplicate definition for namespace 'com.acme.book' at level /home/ana/libros/.stxt: already defined in /home/ana/libros/.stxt/@stxt.template/com.acme.book.stxt
It exits with 1 if the chain has errors or the path does not exist, and with 0 in
every other case, also with an empty chain.
stxt install
Installs the definitions of a file into a level of the resolution chain. Before
writing, it checks that the file has the .stxt extension, that it parses and that
each root node is a definition that validates against its meta-schema; if anything
fails, it writes nothing. Each definition is written separately, in canonical form,
as <level>/@stxt.schema/<namespace>.stxt or
<level>/@stxt.template/<namespace>.stxt.
That layout is a convention of the CLI: STXT-DISCOVERY-SPEC §3 gives no meaning to file names or to the subdirectories of a level, and a grammar placed by hand may be at any path.
| Level | Option | Where it writes |
|---|---|---|
| Project | --local |
./.stxt (the current directory; default) |
| User | --user |
~/.stxt (%USERPROFILE%\.stxt on Windows) |
| System | --system |
/etc/stxt (%ProgramData%\stxt on Windows) |
| Any | --root <dir> |
The given directory, whether it exists or not |
| Option | Effect |
|---|---|
--force |
Overwrites a definition already installed for that namespace (path match or namespace match) |
--ignore-non-definitions |
Installs the definitions of the file and skips the other root nodes, instead of failing |
stxt install book-template.stxt
Installed com.acme.book (@stxt.template) to /home/ana/libros/.stxt/@stxt.template/com.acme.book.stxt
The cases that make the installation fail (code 1):
stxt install book-template.stxt
stxt install: /home/ana/libros/.stxt/@stxt.template/com.acme.book.stxt already exists (use --force to overwrite)
stxt install docs/book.stxt
stxt install: docs/book.stxt:1: root node 'Book' belongs to 'com.acme.book', not to @stxt.schema or @stxt.template (use --ignore-non-definitions to install only the definitions)
stxt install broken.stxt
stxt install: broken.stxt: invalid @stxt.template definition: Type not valid: NOTATYPE
stxt install notes.txt
stxt install: not an STXT document (.stxt expected): notes.txt
Grammar resolution and STXT_PATH
validate, schemas and install use the chain of
STXT-DISCOVERY-SPEC §4, the same as the VS Code extension
and the libraries:
- The levels are every
.stxt/directory from the document's folder upwards, then~/.stxtand finally/etc/stxt. - Inside a level every
.stxtfile is loaded, recursively, and each must be a definition. - Precedence is per namespace: the closest level that defines it prevails, and the other levels contribute the namespaces it does not define.
- Two definitions of the same namespace at the same level are an error, and that namespace is left without a definition.
The STXT_PATH environment variable replaces the whole chain with a list of
directories separated by : (; on Windows), in order of precedence. The entries
need not be called .stxt, and one that does not exist contributes nothing. In CI
it avoids depending on the ~/.stxt or the /etc/stxt of the machine:
STXT_PATH=./.stxt stxt validate --recursive docs/
A STXT_PATH that is set but empty leaves the chain empty: validate fails with
SCHEMA_NOT_FOUND on every document with a namespace, except with --no-schema or
--warn-schema, and schemas shows (empty: STXT_PATH provides no directories).
Common tasks
Continuous integration: the documents validate and are formatted.
npx @stxt-lang/cli validate --recursive docs/
npx @stxt-lang/cli format --check --recursive docs/
Formatting a whole project for the first time, switching it to spaces. The
.stxt/ directories are skipped; the grammars are formatted in a separate call.
stxt format --write --spaces --recursive .
A document that is not on disk, with -:
curl -s https://example.com/api/book.stxt | stxt validate -
curl -s https://example.com/api/book.stxt | stxt describe - | jq '.[0].name'