explainshell README

The project's README from github.com/idank/explainshell. As one markdown file: README.md.

explainshell.com - match command-line arguments to their help text

CI Deploy

explainshell is a tool (with a web interface) capable of parsing manpages, extracting options and explaining a given command-line by matching each argument to the relevant help text in the manpage.

How?

explainshell is built from the following components:

  1. manpage reader which extracts metadata (name, synopsis, aliases) from a given manpage (manpage.py)
  2. an options extractor that parses roff macros or uses an LLM to extract options (extraction/)
  3. a storage backend that saves processed manpages to sqlite (store.py)
  4. a matcher that walks the command's AST (parsed by bashlex) and contextually matches each node to the relevant help text (matcher.py)

When querying explainshell, it:

  1. parses the query into an AST
  2. visits interesting nodes in the AST, such as:
    • command nodes - these nodes represent a simple command
    • shell related nodes - these nodes represent something the shell interprets such as '|', '&&'
  3. for every command node we check if we know how to explain the current program, and then go through the rest of the tokens, trying to match each one to the list of known options
  4. returns a list of matches that are rendered with Flask

Running explainshell locally

# Clone repository
$ git clone https://github.com/idank/explainshell.git
$ cd explainshell

# Set up Python virtualenv
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install -r requirements-dev.txt

# Download the live db
$ make download-latest-db

# Or parse a manpage
$ python -m explainshell.manager extract --mode llm:codex/gpt-5.6-sol/medium manpages/ubuntu/26.04/1/tar.1.gz

# Run the web server
$ make serve
# open http://localhost:5000

Storage

Processed manpages live in a single SQLite database (explainshell.db) with three tables:

The source path is the primary key across both manpages and parsed_manpages, and doubles as a namespace: queries can be scoped to a specific distro/release by filtering on the path prefix.

The db for the live service is saved in a Github release.

Manpage archives

explainshell.com sources manpages from known archives (currently the Ubuntu archive and manned.org). All manpage sources (gz files) are committed to explainshell-manpages (a git submodule of this repo). It's not necessary to clone this repo to run explainshell locally.

To generate the archive locally:

$ git submodule update --init --recursive
$ make ubuntu-archive UBUNTU_RELEASE=resolute
$ make arch-archive # Arch only has a 'latest' archive

This outputs gzipped manpages under manpages/<distro>/<release>/.

Processing manpages

The manager CLI requires a database path for most commands. Set it via the DB_PATH environment variable or pass --db <path> on the command line. Commands that don't need a database (e.g. extract --dry-run, diff extractors) work without it.

Use the manager to extract options from gzipped manpages and save them to the database:

# Calls out to 'codex exec' to extract the manpage and writes the result to test.db.
$ python -m explainshell.manager --db test.db extract --mode llm:codex/gpt-5.6-sol/medium manpages/ubuntu/26.04/1/tar.1.gz

# Or set DB_PATH:
$ export DB_PATH=$(pwd)/test.db

# Can also use an API key (see .env.example).
$ python -m explainshell.manager extract --mode llm:openai/gpt-5-mini manpages/ubuntu/26.04/1/find.1.gz
$ python -m explainshell.manager extract --mode llm:openai/gpt-5-mini --batch 50 manpages/ubuntu/26.04/

The --mode flag selects the extraction strategy:

Other extract flags: --overwrite (re-process existing entries), --filter-db <spec> (with --overwrite, only re-extract rows whose stored extractor matches <spec>; same syntax as --mode; repeatable to match any of several specs), --dry-run (extract without writing to DB), -j <N> (parallel workers), --batch <N> (provider batch API for LLM modes, including gemini/, openai/, and azure/), --small-only / --large-only (partition the corpus at ~2 KB gz so a cheap model handles small pages and a capable one handles the rest):

# pass 1 - cheap model on small pages
$ python -m explainshell.manager extract --mode llm:codex/gpt-5.6-luna/medium --small-only manpages/...
# pass 2 - capable model on the rest (already-stored small pages are skipped)
$ python -m explainshell.manager extract --mode llm:codex/gpt-5.6-sol/medium --large-only manpages/...

To compare extraction results, use the diff subcommand:

# Diff against the database
$ python -m explainshell.manager diff db --mode llm:openai/gpt-5-mini manpages/ubuntu/26.04/1/tar.1.gz

# Compare two extractors head-to-head
$ python -m explainshell.manager diff extractors llm:openai/gpt-5-mini..llm:openai/gpt-5 manpages/ubuntu/26.04/1/tar.1.gz

Querying the database

# Show aggregate stats
$ python -m explainshell.manager show stats

# Look up a command
$ python -m explainshell.manager show manpage tar

# List available distros
$ python -m explainshell.manager show distros

# Run integrity checks
$ python -m explainshell.manager db-check

Tests

$ make tests-all          # lint + unit tests + e2e

LLM evaluation

The LLM eval (tests/evals/llm/llm_eval.py) runs the LLM extractor on the corpus listed in tests/evals/llm/corpus.txt and produces a summary.json plus per-page artifacts (markdown/, prompts/, responses/). Runs are auto-saved with timestamps to tests/evals/llm/runs/<timestamp>-<label>/ and include git metadata.

The idea is to run it before making changes that affect the LLM extractor pipeline (producing a baseline), and then again with the changes. Some variance between runs is expected due to LLM indeterminism.

# Run on the default corpus, parallelizing realtime calls
$ python tests/evals/llm/llm_eval.py run --label baseline --model openai/gpt-5-mini --jobs 10

# Compare two run directories (oldest first)
$ python tests/evals/llm/llm_eval.py compare tests/evals/llm/runs/<baseline-run> tests/evals/llm/runs/<current-run>

# List all saved runs
$ python tests/evals/llm/llm_eval.py list

Use --batch <size> instead of --jobs to route through the provider's batch API - cheaper but minutes-to-hours of queue latency, so it pays off only on much larger corpora than the default 12-page one.

Markdown render eval

A review-oriented harness for mandoc -T markdown changes lives in tests/evals/render/. It renders a vendored manpage corpus with two mandoc binaries, compares structural metrics, and builds a screenshot diff report with a draggable expected/actual slider. It is intentionally not part of make tests-all - run it manually when changing the markdown rendering path. See tests/evals/render/README.md for usage.

Thanks

Thanks to the following companies for including explainshell in their open source programs:

Anthropic            Datadog            DigitalOcean            OpenAI

License

The explainshell code is licensed under GPLv3.

The released database is a different matter: it contains the (near-)verbatim text of tens of thousands of man pages extracted from Ubuntu and Arch Linux packages. Each of those pages keeps its own upstream license (GPL, BSD, MIT, and many others) held by its respective authors - the GPLv3 above does not cover them, and I can't relicense them. My own contribution to the database (the selection, schema, and extracted option structure) is a thin layer on top of that content.

If you want to redistribute the database (or a derivative of it), the permission has to come from the licenses of the individual man pages - do your own compliance review, and preserve the upstream authors' copyright notices as their licenses require. The source column tells you which distro/release each page came from.

explainshell (idank/explainshell)

Searching Database…

这是什么?explainshell 是一个带网页界面的工具,用来解释命令行:它会解析 man 手册页,提取其中的选项,然后把你输入的每个参数对应到相关的帮助说明上。
它是一个我访问的网站,还是需要我自己运行的程序?两者都是。explainshell.com 是托管好的在线服务,同一套代码也可以在本地以 Flask 网页应用的方式运行,底层由一个 SQLite 数据库支撑。
我怎么在本地把它跑起来?按照 README 中 'Running explainshell locally' 一节操作即可。你不需要 manpage 子模块,在线服务处理好的数据库会作为 GitHub release 的附件发布。
使用它需要 LLM 的 API 密钥吗?不需要。查询本身不依赖任何模型,默认的提取器直接解析 roff 宏;只有使用 --mode llm:(例如 llm:openai/gpt-5-mini)重建数据库时才需要 LLM 服务商。
遇到它不认识的命令或参数会怎样?覆盖范围受限于它的 man 手册数据库:对每个命令节点,它先判断是否认识对应的程序,再把剩下的部分与已知选项进行匹配——不认识的内容就得不到任何解释。
代码是 GPLv3 的——那 man 手册数据库也受这个协议约束吗?不是。发布出去的数据库原样嵌入了几万份 man 手册页,每一份都遵循各自上游的许可协议(GPL、BSD、MIT 等),所以分发数据库需要你自行做好合规审查,并保留上游的版权声明。
LLM 提取模式会不会幻觉出错误的帮助文本?不会。LLM 只返回 man 手册源文件中的行号范围;你看到的帮助文本始终是从原始手册页里切出来的,因此从结构上就不可能产生幻觉。
它是怎么弄清楚我命令里每个部分的含义的?你的输入会先用 bashlex 解析成 AST。匹配器会遍历命令节点和 '|'、'&&' 这类 shell 节点,把每个标记与 man 手册数据库中的已知选项进行匹配。
explainshell.db 这个数据库里有什么?就是一个包含三张表的 SQLite 文件:manpages(经 zlib 压缩的源文本)、parsedmanpages(提取出的选项、概要、别名),以及把命令名关联到手册页的 mappings 表,其中还包括 git commit 这类别名和子命令形式。
它收录了哪些 man 手册页?手册页来自 Ubuntu 软件源和 manned.org。每一页都按发行版/版本路径作为键,因此查询时可以限定在某个特定的发行版或版本。
Creator
idank
Size
14.9 MB
Stars
14.3K
Updated
CommitAug 26
Age
13y
Commits · 90 Days