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
-ksays
-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:
- Pass an output option:
--json,--paths,--find,--where,--check,--renderor--alchemy. Never--panes, which opens the viewer. - Name the file as an argument. Standard input is read as JSON unless
-kFORMAT says otherwise. - Check the exit status before reading standard output: only 0 means it holds the answer (--check's report comes with 1 too).
- Quote a path for the shell:
--path'.items[0]'. - Start small on a big or unknown file:
--paths--depth1, then--pathinto 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
Options for both #
The output and the viewer
Viewer options #
Ignored without a screen, where --panes is refused
Other options #
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:
| Format | Extensions |
|---|---|
json | json geojson har jsonld webmanifest |
jsonl | jsonl ndjson |
jsonic | jsonic |
jsonc | jsonc (trailing commas accepted) |
json5 | json5 |
yaml | yaml yml |
toml | toml |
ini | ini cfg conf cnf |
csv | csv (records keyed by the header row) |
tsv | tsv tab (as csv) |
xml | xml svg xhtml xsd xsl xslt plist |
zon | zon |
markdown | md markdown (its syntax tree) |
feed | rss atom (normalised to an Atom shape) |
text | txt 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 (
--indentN), 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
--jsonis, 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
--grammarfile 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-sizethat 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--pathor--atnames 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
--grammarthat does not compile adds grammar, and file when it came from one transduce- a
--renderor--alchemystream 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--renderor--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
--grammaror an--alchemyprogram that does not compile (alchemy), or the viewer without a terminal 3- an input, a
--grammarfile or an--alchemyprogram file could not be read, or the output not written (io; transduce OUTPUT_FAILED) 4--pathor--atnames nothing (not_found)5- an input, a
--grammarfile or an--alchemyprogram file is over--max-size(too_large); with--renderor--alchemy, over a limit of the transducer's, a program's output over--max-outputamong them (transduce RESOURCE_LIMIT_EXCEEDED) 6- a parse, or a
--grammarcompile, ran past--timeout, or the input was still being read when it passed; with--renderor--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 (
--generateman) completions/aless.bash- bash (
--generatecomplete-bash) completions/_aless- zsh (
--generatecomplete-zsh) completions/aless.fish- fish (
--generatecomplete-fish) completions/_aless.ps1- PowerShell (
--generatecomplete-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
--alchemyruns, in its docs/language.md: https://github.com/tabnas/alchemy tabnas/abnf- the ABNF compiler
--grammaruses, and its annotations: https://github.com/tabnas/abnf