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).
  • --version and --help are accepted in any position and take precedence. There is no per-command help: stxt validate --help prints 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 validate included. Usage and read errors, the syntax errors of format and describe, and the progress of --verbose, go to standard error.
  • - denotes standard input in validate, format and describe, 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: format only writes with --write, and install only 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.schema or @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 ~/.stxt and finally /etc/stxt.
  • Inside a level every .stxt file 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'

Limits

  • It does not convert to other formats: the only structured outputs are the JSON of describe and that of validate --format json.
  • It carries no parser. Parsing or validation errors are reported at stxt-js; errors in options, messages or behaviour of the command, at stxt-cli.