Skip to content

Repository files navigation

exp: every token, explained from the man pages you have

CI Local first man and mdoc MIT

Install · First explanation · Search · Tutorial · Design · Caveats

The man page already knows. exp finds the paragraph. Read status and caveats before relying on it.

exp explains a shell command line by matching every token to the exact paragraph that documents it in the man pages installed on your machine. Deterministic, offline, no model, no bundled database: the answer describes the version of each tool you actually have.

At a glance

exp explaining tar -xzvf a.tgz and sudo xargs -0 rm -f

Grouped shorts, an option argument, and a wrapper chain. Follow the tutorial or play the cast.

Type a command line, get it back with every span underlined and a numbered footnote for each: the page, the section, and the paragraph that documents that token. Grouped shorts split per letter, an option that takes an argument shares its footnote with the argument, wrappers like sudo and xargs hand off to the command they run, and shell grammar (pipes, redirects, $( ), globs) is explained natively. Pages are resolved the way man -w does it and parsed from their groff source, so the answer describes the versions installed here, not a snapshot from somewhere else.

Install

cargo install --git https://github.com/0typos/exp --locked

Or from a checkout with cargo install --path .. A Rust toolchain is the only requirement; SQLite is bundled. Pages compressed with xz, bzip2, or zstd are decompressed through the matching command line tool, gzip in-process.

First explanation

exp 'tar -xzvf a.tgz'
tar -xzvf a.tgz
1   2 3 5 5
       4

┌───┬───────┬──────────────────────────────────────┬────────────────────────────────────────┐
│ # │ token │ source                               │ explanation                            │
├───┼───────┼──────────────────────────────────────┼────────────────────────────────────────┤
│ 1 │ tar   │ tar(1) §NAME                         │ tar - an archiving utility             │
├───┼───────┼──────────────────────────────────────┼────────────────────────────────────────┤
│ 2 │ -x    │ tar(1) §DESCRIPTION / Operation mode │ -x, --extract, --get                   │
│   │       │                                      │ Extract files from an archive. …       │
├───┼───────┼──────────────────────────────────────┼────────────────────────────────────────┤
│ 5 │ f     │ tar(1) §OPTIONS / Device selection   │ -f, --file=ARCHIVE                     │
│   │ a.tgz │ and switching                        │ Use archive file or device ARCHIVE. …  │
└───┴───────┴──────────────────────────────────────┴────────────────────────────────────────┘

Every span is underlined and coloured on a terminal; the ruler puts its footnote number under the span. Spans that point at the same paragraph share a row (f and the archive it names).

Quote the whole line. Unquoted words also work, but exp's own flags must then come first or after --:

exp 'git log --oneline | head -n 5 > out.txt'
exp --format text -- find . -name '*.log' -exec rm {} \;
fc -ln -1 | exp                                   # the previous command
Explain the last command from your shell

fish:

function explain --description 'explain the last command'
    exp (history --max 1)
end
funcsave explain

bash or zsh:

alias explain='fc -ln -1 | exp'

Choose a format

--format for
pretty (default) the terminal: highlighted line, ruler, box table wrapped to the width
text pagers and plain logs: ruler plus footnotes, blank line between them
markdown issues and notes: the command in a code block and a table
json (or --json) tooling: byte offsets, kinds, confidence, full provenance; the snapshot format
exp --format markdown 'rsync -avz --delete src/ dst/'
exp --json 'ps aux' | jq '.footnotes[].source | select(.type=="page") | .tag'

text, markdown and JSON output

Search the corpus

Explaining fills a SQLite cache with every paragraph it extracted, FTS5 indexed. Search it by tool, by category, or across everything:

exp search --tool rsync checksum
exp search --category archive exclude
exp search --category network --regex '^--proxy'
exp search --tool tar --fuzzy 'exclde vcs'
exp search --all --zone any 'follow symlinks'
exp index --all --report          # index the whole manpath; list pages that extracted badly
$ exp search --tool rsync checksum
rsync(1) §OPTIONS  -c, --checksum — --[checksum], -c
rsync(1) §OPTIONS  --checksum-seed — --[checksum]-seed=NUM

exp categories lists the shipped groups (archive, files, text, network, processes, shell, git, containers, packages). A member ending in * covers subcommand pages (git-*). Add your own in ~/.config/exp/categories.toml.

search by tool, category, regex and fuzzy match

The ugly tail

Real command lines are not all getopt. These are data in knowledge/commands.toml, overridable per command in ~/.config/exp/commands.toml:

case example what happens
wrappers sudo xargs -0 rm -f, env FOO=1 timeout 5s nice -n 10 ls the wrapped command gets its own page, recursively
find -exec find . -name '*.log' -exec rm {} \; everything up to \; or + is a command
subcommands git log, docker run, systemctl start routed to git-log(1), docker-run(1), or the COMMANDS section
legacy bundles tar xzvf a.tgz, ps aux expanded per letter against the page's bare tags
key=value dd bs=4M, ssh -o StrictHostKeyChecking=no, git -c core.fileMode=false bs= matched directly; keys inside arguments resolved from ssh_config(5) or git-config(1)
modes chmod u+x,go-w, chmod 0644 operand patterns pick the paragraph that documents the syntax
builtins cd -P, export FOO=1, source x from bash(1)'s SHELL BUILTIN COMMANDS
ambiguous arity a page that omits an option's argument fish completions fill it in when installed

ps aux, dd, and ssh -o explained

How it works

  1. tree-sitter-bash parses the line. Byte ranges from the tree are the spans in the output.
  2. Each command's page is resolved like man -w: $MANPATH or $PATH-derived roots, $MANSECT order, .so redirects followed.
  3. The groff source is parsed directly. man(7): .SH/.SS zones, .TP/.IP/.HP and docbook or asciidoctor tagged paragraphs. mdoc(7): .Sh/.Ss and .Bl -tag lists with .It Fl items. Tags become option specs: spellings, argument policy, argument name.
  4. Pages are cached in SQLite (~/.cache/exp/cache.sqlite) with an FTS5 index, keyed by file, invalidated on mtime, hash, or extractor version change.
  5. The matcher walks the argument words and assigns each span a paragraph, or the SYNOPSIS at low confidence when nothing matches.

DESIGN.md records each decision and why, including the measured extraction health across a 21,000-page manpath.

Documentation

guide what it answers
Tutorial What does each feature look like, with a recording per section?
Design Why is extraction, matching, and caching shaped this way?
Demos How are the casts and GIFs regenerated?
Brand Where are the mark, hero, and palette?
Knowledge Which commands are special, and how do I add one?

Development

cargo test                                          # fast tier: unit, snapshots, golden corpus, properties
INSTA_UPDATE=always cargo test                      # accept reviewed snapshot changes
cargo mutants -f src/matcher.rs -f src/optspec.rs   # full tier: do the matcher tests bite? (weekly and on-demand workflow, not part of push CI)
docs/demos/record                                   # re-record the tutorial casts and GIFs

Tests never read the host manpath. tests/fixtures/man/ holds vendored pages (GNU man(7) pages from coreutils, tar, rsync, grep, findutils, git, procps, util-linux; mdoc(7) pages from OpenSSH, sudo, file). Each keeps its upstream licence. The golden corpus in tests/golden/corpus.txt lists command line → (span, page, zone, option) triples; a heuristic that fails it becomes a rule or a TOML entry, never an inline hack.

Status, caveats, and what is still owed

This is a 0.1.0. It does what the sections above show, and the following is where it stops.

Limits of the approach

  • Options that a page documents only in prose, with no tagged paragraph, cannot be matched. Those tokens point at the SYNOPSIS and are marked low confidence. On this development host that is about 150 of the section 1 and 8 pages (attr, cups, devlink, clevis); exp index --all --report shows yours.
  • Pages with no NAME section or no structure at all (some Sphinx and pandoc output, stap stubs) explain the command word poorly. There is no rendered-text fallback yet; measurements in DESIGN.md say the tail is small, but it exists.
  • Shell builtins are explained from their paragraph in bash(1) as a whole; their individual options are not matched. Commands with a real page (coreutils echo, test) use that page, as explainshell does.
  • Unknown options are assumed to take no argument. That is the safer failure, and it means a wrongly unmatched option can shift the meaning of the word after it to low confidence.
  • The man page you have is the truth. If your page is old, wrong, or from a different implementation than the binary on your PATH, the explanation follows the page.

Parity with explainshell.com

The golden corpus was checked by hand against the fixture page text, not against explainshell.com, because the site blocks non-browser clients from the build host. Conventions follow explainshell where known (per-letter short groups, operands to the SYNOPSIS, wrapped commands from their own pages). A side-by-side pass is still owed and tracked in tests/golden/corpus.txt.

Platform coverage

  • Linux is exercised daily. macOS runs in CI, but Apple's mdoc(7) page set has not been reviewed by hand beyond the vendored OpenSSH, sudo, and file pages; expect rough edges on BSD-specific macros until it has.
  • No Windows.

Operational notes

  • The cache fills lazily. Named tools are indexed on demand, but --all and category members written as git-* only see what is cached; run exp index --all once (about 20 seconds for 21,000 pages) and the tool says so when the cache is small.
  • The recorded demos run against the vendored fixture pages so they are reproducible, which is why the archive-category search in the demo shows only tar.
  • The crate is published as exp-cli because exp is taken on crates.io; the binary and the library are both still exp.
  • No release binaries yet; install from source.

Non-goals

Web UI, daemon mode, fetching pages over the network, LLM integration, Windows.

License

MIT

About

Explain any shell command line from the man pages you have. Local-first, deterministic, no network.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages