Reference

Command line

Every option, what each output prints, the errors and the exit statuses: the whole reference aless --help prints.

The reference aless --help prints, for aless 0.1.1. aless -h prints a summary of the options, and man aless this reference as a manual page. The same text is plain text here, as the terminal shows it.

Usage #

aless [OPTIONS] [FILE]...
the viewer, in a terminal
aless --paths --depth 1 FILE
JSON on standard output, anywhere
<command> | aless [OPTIONS]
standard input, read as JSON unless -k says

-h prints a summary of the options, and --help this whole reference.

Without a screen #

Scripts, agents, pipes

aless prints instead of starting the viewer when an option listed under Output options is given, when standard output is not a terminal (a pipe, a file, an agent's tool call), or when TERM=dumb, and it never waits for keys. A run reads one input (--check reads several), prints one answer on standard output and exits 0, or prints {"error": {...}} on standard error and exits non-zero: see Output, Errors and Exit status. When the viewer cannot start for want of a terminal, aless says so at once, before reading any input, with a usage error and status 2.

For an agent, five rules:

  1. Pass an output option: --json, --paths, --find, --where, --check, --render or --alchemy. Never --panes, which opens the viewer.
  2. Name the file as an argument. Standard input is read as JSON unless -k FORMAT says otherwise.
  3. Check the exit status before reading standard output: only 0 means it holds the answer (--check's report comes with 1 too).
  4. Quote a path for the shell: --path '.items[0]'.
  5. Start small on a big or unknown file: --paths --depth 1, then --path into it.

aless --generate skill prints an Agent Skill that teaches an agent all of this.

Output options #

Any of them prints instead of starting the viewer

--json

The document as JSON, or the value at the start that --path or --at gives: what aless prints without a screen when no other output option is given. Indented 2 spaces a level (--indent N), or on one line with --compact. Numbers are 64-bit floats, so an integer beyond 2^53 comes out as the nearest one, and NaN and the infinities as null; --render json keeps each number as the source spelled it.

--paths

An entry for the start and for each node below it, in document order, in a listing: {file, format, path, entries, total, limit, truncated} (see Output). --depth N goes N levels below the start, and --limit N lists N entries at most.

--find <REGEX>

The entries of the nodes, the start and those below it, whose "key": value text matches REGEX, in a listing with pattern and matches (see Output). It is the viewer's search: case is ignored unless REGEX has a capital letter or ends in /s, and [ ] { } match themselves unless escaped. --depth and --limit apply as they do to --paths.

--where

The start's entry, with file and format: with --at, the path a source position is in; with --path, the line and column a path starts at; with neither, the root's.

--check

Parse every FILE, or standard input when none is named, and report on each: {ok, files: [{file, format, ok, error}]}, where a failing file's error is the error object a run on that file alone prints. The one output option that reads several inputs. Exit status 1 when any input fails, with the report still on standard output. Takes no --path or --at.

--render <FORMAT>

The value at the start written as FORMAT, streamed as the input is read: csv, its records (the elements of the array at the start; the lines of JSON Lines; the records of CSV and TSV) under a header row, every field quoted, CRLF line ends; json, the value as --json writes it, but with each number spelled as in the source where that is JSON, and NaN and the infinities refused; or any format whose crate carries a render. FORMAT is one of csv, ini, json, json5, jsonc, jsonic, jsonl, markdown, toml, xml, yaml or zon. Every format but json declares what it does not keep in a warning on standard error, on success too. Takes --path, not --at. With --alchemy it names the format the program's table or JSON events are written as.

--alchemy <FILE>

Run the alchemy program in FILE over the input, and stream what its export answers: a text as it is, a table as CSV (--render json: JSON records, an object per row), JSON events as compact JSON (--render csv: a table of them); --render FORMAT writes a table or JSON events as FORMAT. The program selects what it reads, so no --path, --at or other output option goes with it. The input reaches it as --render reads it: JSON Lines, CSV and TSV a record at a time.

--alchemy-expr <TEXT>

--alchemy, with the program's text on the command line: --alchemy-expr 'def export [input] input' writes the document as JSON.

--explain

With --alchemy or --alchemy-expr, print the program's plan report as one JSON object instead of running it, and read no input: the chain of calls, the protocols, what is retained and under which limits, the ordering contract, the renderer (the program's own default, whatever --render names), and the guarantee with its qualification. Run it first on a program you did not write.

--path <PATH>

Start at PATH instead of the root, in the jq syntax every output prints (see Paths). Not with --at, --check or a program.

--at <LINE[:COL]>

Start at the node at that source position, counted from 1, columns in characters (see Positions). Not with --path, --check, --render or a program.

--limit <N>

--paths and --find list at most N entries (default 200; 0 for all); total and truncated say what was left out.

--compact

JSON on one line: the answer of --json, --paths, --find, --where, --check, --render json and --explain, and an error or a warning on standard error. A program's own JSON is on one line whatever is given.

--max-output <SIZE>

Stop an --alchemy program that writes more than SIZE (default 1G; 0 for no limit): a transduce error, RESOURCE_LIMIT_EXCEEDED naming max_output_bytes, status 5. SIZE is bytes, or a whole number with K, M or G (1024s).

Options for both #

The output and the viewer

-k, --kind, --format <FORMAT>

Parse every input as FORMAT instead of by its extension: a format's name (json, jsonl, jsonic, jsonc, json5, yaml, toml, ini, csv, tsv, xml, zon, markdown, feed or text), one of its extensions (yml, md, ndjson), or a --grammar NAME. Standard input is read as JSON unless this says otherwise. --format is another name for this option.

--grammar <NAME=FILE>

Read a file whose extension or whole name is NAME, in any case (x.hosts, /etc/hosts), with the ABNF grammar in FILE, which is read within --max-size and compiled within --timeout before any input is read. NAME is then a format, as -k takes it and every output reports it; NAME,NAME2=FILE gives it two names, and the option repeats. See Custom grammars.

--grammar-expr <NAME=ABNF>

--grammar, with the grammar's text on the command line: everything after the first =.

--depth <N>

--paths and --find go at most N levels below the start (default: every level). The viewer folds the containers deeper than N levels when it opens a file.

--indent <N>

Indent --json and --render json N spaces a level, and the viewer's tree (default 2; at most 16).

--max-size <SIZE>

Refuse an input larger than SIZE (default 64M; 0 for no limit), and a --grammar or --alchemy file too, with a too_large error, status 5: a parse takes about 40 bytes of memory per byte of input. SIZE is bytes, or a whole number with K, M or G (1024s). JSON Lines, CSV and TSV that --render or --alchemy streams are read a record at a time, and the limit does not apply to them.

--timeout <SECONDS>

Stop a parse, or a --grammar compile, that runs longer than SECONDS (2.5, 90s, 2m; default none; 0 for no limit), with a timeout error, status 6, that says how far it got. Under --render and --alchemy it covers the whole run. Without a screen, on standard input, the time runs from the start, so waiting on the input counts. A parse runs at about a megabyte a second: a caller with a deadline of its own should pass one a few seconds shorter.

Viewer options #

Ignored without a screen, where --panes is refused

--no-watch

Do not reload a file when it changes.

--watch

Reload a file when it changes, keeping your place (the default).

-m, --mode <MODE>

Start in data mode (the default), the streamlined tree, or in line mode, every line of a pretty-printed rendering; m switches between them.

-n, --line-numbers

Show absolute line numbers.

-N, --no-line-numbers

Hide absolute line numbers.

-r, --relative-line-numbers

Show line numbers relative to the focused row.

-R, --no-relative-line-numbers

Hide relative line numbers.

--scrolloff <N>

Keep N rows around the focus when scrolling (default 3).

--hidden

Show dot-files in the file explorer.

--ascii

Draw fold markers with ASCII characters (v and >).

--no-color, --no-colour

Draw without colours, as when NO_COLOR is set.

--no-mouse

Leave the mouse to the terminal rather than capture it.

--panes <PANES>

Open panes beside the input: out, the document as --render or --alchemy writes it (JSON when neither is given), and program, the program; out,program opens both. With --panes, --render and --alchemy choose the output pane's content instead of printing, the other output options are refused, and so is a run without a terminal (status 2). C-w moves between the panes, s shows a pane's text or its tree, and :pane out|program|close opens or closes one.

--stacked

Stack the panes rather than place them side by side.

Other options #

-h, --help

Print a summary of the options (-h), or this reference (--help).

-V, --version

Print the version: aless 0.1.1.

--generate <WHAT>

Print a file made from this reference: man, the man page (aless.1); complete-bash, complete-zsh, complete-fish or complete-powershell, that shell's completions; or skill, the Agent Skill (SKILL.md) that teaches an agent to drive aless. See Files.

Input and formats #

FILE is a path, or - for standard input. Without a FILE, aless reads standard input when it is not a terminal; otherwise output fails with a usage error, and the viewer explores the current directory. Every output option but --check reads one input: run aless once per file, or --check several. A directory is a usage error without a screen, and opens the file explorer in the viewer. Options end at --, after which every argument is a FILE, and a long option's value may follow it or be attached to it (--kind=yaml).

The format comes from a file's extension, or from its whole name for a --grammar NAME (/etc/hosts), and -k FORMAT overrides it for every input. A file whose extension no format claims is read as plain text, an array of its lines. Map keys keep their source order. The formats, with the extensions that imply each:

FormatExtensions
jsonjson geojson har jsonld webmanifest
jsonljsonl ndjson
jsonicjsonic
jsoncjsonc (trailing commas accepted)
json5json5
yamlyaml yml
tomltoml
iniini cfg conf cnf
csvcsv (records keyed by the header row)
tsvtsv tab (as csv)
xmlxml svg xhtml xsd xsl xslt plist
zonzon
markdownmd markdown (its syntax tree)
feedrss atom (normalised to an Atom shape)
texttxt text log (and every extension no format claims)

Paths #

--path takes jq's syntax, which every output prints, so a path can go straight back in: ., .a.b[0], ."odd key", .["a.b"], .[0] for a root array's first item and .[-1] for its last. Also accepted: a.b[0] and [0] without the leading dot, JSONPath's $.a['b'][0], and JSON Pointer's /a/b/0. There are no wildcards, slices or recursive descent: pipe --json into jq for those. Quote a path for the shell, whose globbing would take [0]. Under --render, [-1] on an array is a usage error, since a stream cannot count from the end; on an object it is the key -1, as everywhere.

Positions #

--at takes LINE or LINE:COL, counted from 1, columns in characters, as an entry's line and col are. Inside the text, --at answers the node starting last at or before the position on its line, the innermost of several starting there (a line alone, or a column before the line's first node, that first node; on a line with no node of its own, a comment or a closing bracket, the last node before the line, or the document's first node when none is). Outside the text it names nothing, status 4: a line past the last line, or a column past the end of its line, where a line's text excludes its terminator (LF or CRLF), a trailing terminator starts no line, and an empty line has no column inside it.

Output #

An entry describes one node:

{"path":".spec.replicas","kind":"number","line":12,"col":3,"value":3}
path
where it is, in jq syntax: give it back to --path, or to jq, unchanged
kind
object, array, string, number, boolean or null
line, col
where the node starts, from 1, columns in characters: at its key when it has one, else at its value. Exact for the JSON family, TOML, INI, CSV and ZON, best-effort for YAML, XML and Markdown, and null when unknown
length
a container's item count, or the full length of a string cut short
value
a scalar's value. A string over 200 characters is cut to 200, with "truncated": true; NaN and the infinities are "NaN", "Infinity" and "-Infinity"

What each option prints on standard output when it succeeds:

--json
the value itself, indented 2 spaces a level (--indent N), or on one line (--compact)
--paths
{file, format, path, entries: [entry, ...], total, limit, truncated}
--find
{file, format, path, pattern, matches: [entry, ...], total, limit, truncated}
--where
{file, format, ...entry}: the entry, with file and format first
--check
{ok, files: [{file, format, ok, error}]}, error null for a file that parses
--render csv
CSV: a header row, then a record per row, every field quoted, CRLF line ends
--render json
the value, indented as --json is, each number as the source spelled it where that is JSON
--render FORMAT
the document in FORMAT, in its always-quoted profile, so that nothing reads back as another kind
--alchemy
what the program exports: a text as it is, a table as CSV, JSON events as compact JSON
--explain
{entry, output, protocol, chain, retention, renderer, guarantee, qualification, ...}

file is the path as given, or - for standard input; format is what the input was read as; path is where the listing starts. total counts every entry the listing found, and truncated is true when that is more than --limit let through: raise --limit (0 for all), or narrow with --path or --depth.

A --render that succeeds, in any format but json, also writes a warning on standard error that says what the format does not keep: {"warning": {"kind": "loss", "message", "file", "render", "loss": [sentence, ...]}}, with adapter naming the inferred table or records when one stood between the shapes. Status 0 is success whatever standard error holds.

Errors #

An error is one JSON object on standard error, and standard output is then empty, with two exceptions: a failed --check still prints its report, and a --render or --alchemy stream that fails leaves what it had written, every record whole and none in part (but for a record over 16 MB, and another format's render, such as --render yaml, which can stop inside one). A parse error:

{"error": {"kind": "parse", "file": "bad.json", "format": "json",
  "code": "unexpected", "message": "unexpected end of input",
  "line": 2, "col": 1, "hint": "The document ends before it is ...",
  "source_line": "", "report": "[tabnas/unexpected]: unexpected ..."}}

kind says what failed, and which fields come with it. A parse error's fields are always there, null where they do not apply; another kind's fields named "when" are there only then:

parse
the input did not parse: file, format, code (the grammar's: unexpected, unterminated_string, too_deep, ...), message, line, col, hint, source_line (the line the error is on) and report (the whole report the viewer shows, uncoloured)
io
an input, a --grammar file or a program file could not be read, or the output not written: a parse error's fields, with the code io (file null when it was standard output)
too_large
over --max-size: an io error's fields, plus size (null for standard input, which is read no further) and limit, in bytes; the hint names the --max-size that would read it
timeout
past --timeout: a parse error's fields, line and col showing how far the parse got (null when the input was still being read, or the parse finished late; a read a record at a time names the line its record starts on, col null), plus seconds
not_found
--path or --at names nothing: file, format, message, the path or at given, nearest (the entry of the deepest node the path reached, or of the node a position inside the text would have answered) and keys (that node's first keys when it is an object)
usage
the command is wrong: message, and nothing more, but that a --grammar that does not compile adds grammar, and file when it came from one
transduce
a --render or --alchemy stream failed: code (INPUT_INVALID, RESOURCE_LIMIT_EXCEEDED, OUTPUT_FAILED, DUPLICATE_MEMBER, TARGET_VALUE_UNREPRESENTABLE, ...), message, file, format, then path, limit ({name, value}), line, col and hint when the failure has them, and output: "partial" when some of the result had been written, else "none". One a program raised with a position of its own has file, line and col the program's, format null, and input, the document's name
alchemy
the program is wrong: code (DSL_PARSE_ERROR, DSL_TYPE_ERROR, STREAM_REUSED, STREAMABILITY_UNKNOWN), message led by a finer code (unbalanced, unknown_name, arity, ...), file (the program's path, or --alchemy-expr), format null, line and col in the program, output, and input (the document's name) once the input was open

An io, too_large or timeout error about a --grammar file adds grammar, with file the grammar file and format null. An error met while --render was writing (transduce, parse, timeout, or the render's own alchemy one) adds loss, the sentences its warning gives on success: an empty list for json. --compact puts an error on one line. The README's "Scripts and agents" section has every case at length.

Exit status #

0
success: standard output holds the answer
1
the input did not parse (parse); with --check, an input failed, and the report says which; with --render or --alchemy, the input or its records will not do (transduce: INPUT_INVALID, and the other input, protocol and target codes)
2
bad usage (usage): an unknown option, a bad path, no input, a directory, a --grammar or an --alchemy program that does not compile (alchemy), or the viewer without a terminal
3
an input, a --grammar file or an --alchemy program file could not be read, or the output not written (io; transduce OUTPUT_FAILED)
4
--path or --at names nothing (not_found)
5
an input, a --grammar file or an --alchemy program file is over --max-size (too_large); with --render or --alchemy, over a limit of the transducer's, a program's output over --max-output among them (transduce RESOURCE_LIMIT_EXCEEDED)
6
a parse, or a --grammar compile, ran past --timeout, or the input was still being read when it passed; with --render or --alchemy, the whole run, the program's work included (timeout)

Large inputs and streaming #

An input is read whole and parsed whole before anything is printed, at about a megabyte a second and 40 bytes of memory per byte of input. --max-size (default 64M) refuses a larger input before it is read, and --timeout stops a parse that runs too long with an error that says how far it got: a caller with a deadline of its own should pass a --timeout a few seconds shorter. --path and --depth shrink the output, not the parse. A reader that stops early (aless --json big.json | head) ends aless quietly, with status 0.

--render and --alchemy stream instead. JSON Lines, CSV and TSV are read a record at a time, whatever their size, so --max-size does not apply to them (a record over 64 MB fails); the JSON family, jsonic, YAML, ZON and Markdown write their records as the parse proceeds, and the other formats after it. A document a grammar refuses to stream part-way is read whole and written all the same when nothing has been written yet; otherwise the error says output "partial".

A document nested deeper than aless reads fails as a parse error with the code too_deep: past about 1,000 levels, or sooner where the grammar has a limit of its own (127 levels for JSON, JSONL, JSONic, JSON5, YAML, TOML, INI and ZON, 256 for XML, 512 for JSONC).

Numbers are 64-bit floats: --json and entries give an integer beyond 2^53 as the nearest one, and --json writes NaN and the infinities as null. --render json keeps a number as the source spelled it where that is JSON, and refuses NaN and the infinities (transduce TARGET_VALUE_UNREPRESENTABLE, status 1).

Custom grammars #

--grammar NAME=FILE reads the files whose extension or whole name is NAME, in any case, with the ABNF grammar in FILE (RFC 5234, compiled by tabnas/abnf), and -k NAME reads any input with it. ; @object a b and ; @array comments on a rule say what it builds; without them the value is the compiler's parse tree, a {"rule", "src", "kids"} node per rule. The input is read as plain text: { } [ ] : , are ordinary characters, # starts a comment to the end of the line, and a word is the token TX (NR, ST and VL bring numbers, quoted strings and true, false and null back). A grammar that does not compile is a usage error before any input is read, and so is a repetition count over 1,024, or repetitions that would have the compiler write more than 1,024 rules. An input the grammar does not accept is a parse error, with format the grammar's name.

Grammars for /etc/hosts, crontabs, /etc/passwd, /etc/group, /etc/fstab, /etc/resolv.conf and KEY=value files, each with a sample and a guide to writing one, are in aless's source:

https://github.com/rjrodger/aless/tree/main/tests/fixtures/grammars

The viewer #

In a terminal each FILE opens in a tab of its own, watched and reloaded when it changes, keeping your place. A directory opens in the file explorer, where Enter opens a file. Without a FILE the viewer reads standard input when it is not a terminal, and otherwise explores the current directory. The keys are jless's, and F1 or :help inside aless lists them all; the ones used most:

j k
down / up; a count before a key repeats it (3j)
h l
collapse / expand, or go to the parent / first child
J K
next / previous sibling
g G
first / last row; Ng goes to row N
C-d C-u
half a page down / up
Space
toggle the focused container
c e
collapse / expand the focused node and its siblings
/ ?
search forward / backward; n N for the next / previous match
yy yp yq
copy the value / its path / its jq path
m
switch between data mode and line mode
s
show the source text at the focused node
Tab
the next tab (Shift-Tab the previous)
:
a command: :open PATH, :format FORMAT, :w FILE, :help
q
close the tab, quitting with the last; C-c quits

Environment #

NO_COLOR
set and not empty: no colours, as --no-color
TERM
dumb: print instead of starting the viewer, as when standard output is not a terminal, since such a terminal cannot draw it

Files #

A release archive carries the man page and the completions, which --generate also prints:

man/aless.1
the man page (--generate man)
completions/aless.bash
bash (--generate complete-bash)
completions/_aless
zsh (--generate complete-zsh)
completions/aless.fish
fish (--generate complete-fish)
completions/_aless.ps1
PowerShell (--generate complete-powershell)

A shell reads its completions from where it looks for them (a package puts them there), or from its startup file each time it starts:

eval "$(aless --generate complete-bash)"     # ~/.bashrc
eval "$(aless --generate complete-zsh)"      # ~/.zshrc, after compinit
aless --generate complete-fish | source      # config.fish

The Agent Skill (--generate skill) is a file an agent loads from a directory of its own. For Claude Code:

mkdir -p ~/.claude/skills/aless
aless --generate skill > ~/.claude/skills/aless/SKILL.md

Examples #

aless --paths --depth 1 config.yaml       what is in it
aless --json --path '.spec.containers[0]' deploy.yaml
aless --where --at 42:7 deploy.yaml       the path at a linter's 42:7
aless --where --path .a.b config.yaml     the line a path is on
aless --find '"image":' deploy.yaml       every image, with its line
aless --check $(git ls-files '*.toml')    do they all parse?
aless -k csv --json < data.csv            stdin is JSON unless -k says
aless --render csv --path .items x.json   the records as CSV
aless --render json big.yaml              the document as JSON
aless --render yaml data.csv              any format as any other
aless --alchemy export.alc api.json       a program's output
aless --alchemy export.alc --explain      what the program will do
aless --grammar hosts=hosts.abnf --json /etc/hosts

See also #

jq(1), jless(1)
where the paths and the keys come from
the documentation
tutorials, how-to guides, this reference with every key of the viewer, and how aless works: https://aless.tabnas.dev
the source
the code, its README and the releases: https://github.com/rjrodger/aless
alchemy
the language --alchemy runs, in its docs/language.md: https://github.com/tabnas/alchemy
tabnas/abnf
the ABNF compiler --grammar uses, and its annotations: https://github.com/tabnas/abnf