# CLI `nab` exposes four subcommands: `lock`, `download`, `config`, and `cache`. The first two read project shape from `[tool.nab]` in the project's `pyproject.toml` or a project-directory `nab.toml`; the CLI carries runtime knobs and can override a project key for one run with a `--project-` flag. `config` inspects the layered configuration, and `cache` inspects and clears the on-disk cache. ## Synopsis ``` nab [--version | -V] nab [OUTPUT FLAGS] lock [PATH] [RUNTIME OPTIONS] [--output PATH] [--format pylock|requirements|requirements-without-hashes] nab [OUTPUT FLAGS] download [PATH] [RUNTIME OPTIONS] [--output DIR] [--max-concurrency N] nab [OUTPUT FLAGS] config {list | get | explain } [--path PATH] nab [OUTPUT FLAGS] cache {dir | verify | clear} [--cache-dir PATH] ``` `OUTPUT FLAGS` are the global verbosity, colour, and progress knobs listed under Output control below. They are read before the subcommand, so they may appear anywhere on the line. `PATH` is positional and defaults to `pyproject.toml` in the current directory. Run `nab lock --help` (or `-h`) for the full per-command flag list. Boolean flags render as a `--flag` / `--no-flag` pair (for example `--cache` / `--no-cache`). `--offline` is layered, so an explicit `--offline True` / `--offline False` overrides the config layers; bare `--offline` / `--no-offline` are shorthands for those two values. ## `nab lock` Resolve and emit a lockfile or pin list. Three formats: * `--format pylock` (default) writes a [PEP 751] `pylock.toml`. * `--format requirements` writes a pip-compatible `requirements.txt` with one `--hash=:` line per recorded digest. * `--format requirements-without-hashes` writes a sorted `name==version` list with no hashes. Dependency-group selection (PEP 735): * `--groups foo bar` folds the named groups from the project's `[dependency-groups]` table into the resolve. The selected group names land in the lockfile's top-level `dependency-groups` array. The separate `default-groups` array records the `[tool.nab].default-groups` project setting, not this run's selection. * `--all-groups` selects every group defined in the project. Extras selection: * `--extras foo bar` folds entries from the project's own `[project.optional-dependencies]` table into the resolve. The selected extra names land in the lockfile's top-level `extras` array. * `--all-extras` selects every declared extra. Both `--groups` and `--extras` produce a single union resolve. A package that only a selected extra or group reaches is emitted with a `'X' in extras` or `'X' in dependency_groups` marker, so an installer given neither leaves it out; see [Lockfiles](lockfile.md). Workspace flags (see [Lock a workspace](../how-to/workspaces.md)): * `--workspace-discovery` (default) walks upward from the locked project for a `[tool.nab.workspace]` root and prefers its in-tree members over PyPI. `--no-workspace-discovery` skips that search, so the run resolves the named project alone. * `--no-emit-workspace` drops the workspace members' own `[[packages]]` entries from the emitted lockfile, along with the dependency edges and membership gates that reference them; the resolver still uses the members during the resolve. Default off (`--no-no-emit-workspace`). Use it when the lockfile is consumed by pip's PEP 751 install or `--require-hashes`, which reject the directory entry a member pin emits because it cannot be hashed; pair it with `pip install --no-deps -e `. `--output` defaults to `pylock.toml` for `pylock` and `requirements.txt` for the two requirements formats. Pass `--output -` to write to stdout instead. A matrix has no default requirements file: no one file can carry every tuple's pins (see below), so the requirements formats print to stdout unless `--output` names a template. A requirements `--output` template writes one file per tuple. The variables are `{python_version}`, `{platform_id}`, and `{selection}`, which names the conflict fork a tuple belongs to (`extra-cpu`, `group-black22.group-isort5`, empty when the resolve did not fork): ```console $ nab lock --format requirements-without-hashes --extras cpu gpu \ --output 'req-{python_version}-{selection}.txt' Wrote req-3.12-extra-cpu.txt (12 packages, tuple py312-linux_x86_64-extra-cpu) Wrote req-3.12-extra-gpu.txt (14 packages, tuple py312-linux_x86_64-extra-gpu) ``` A template that maps two tuples onto one path is rejected, naming the variable that would separate them. Exits non-zero on resolution failure; the message starts with `error: resolution failed:` followed by a derivation tree, and any captured diagnostics are appended under a `Diagnostics:` section. Universal mode (`[tool.nab].mode = "universal"`) is supported for all three formats: * `pylock` produces one PEP 751 file with per-tuple `Package` entries gated by markers (`python_version`, `sys_platform`, `platform_machine`, plus `implementation_name` when the matrix declares a non-CPython implementation or more than one). Versions agreed across every tuple appear once without a marker; divergent versions appear once per `(version, source)` group with the matching tuples disjoined. * `requirements` and `requirements-without-hashes` emit a sequence of `# label` comment blocks, one per `(python, platform, implementation)` tuple, followed by that tuple's pins. Pip's hash-checking mode cannot install a single requirements.txt across multiple tuples, so the per-tuple block format is for inspection or for tools that consume one block at a time. Failed tuples render as `# {label}: FAILED` followed by the indented error and exit `1`. `--python X.Y` resolves for that Python on this machine instead of the running interpreter, like pip's `--python-version`; it moves only the python axis, so a declared `[tool.nab.environment].platform` stays. It is rejected in universal mode, where the matrix declares the Python axis. A project option can be overridden for one run with a `--project-` flag: `--project-resolution`, `--project-mode`, `--project-requires-python`, `--project-uploaded-prior-to`, `--project-dist-policy`, `--project-build-policy`, and the repeatable `--project-constraint` and `--project-default-group`. Each changes the resolved set, so passing one prints a reproducibility notice on stderr and records the override in the lockfile's `[tool.nab]` block, since the lock no longer derives from the committed files alone. `--locked` re-resolves and checks that the committed `pylock.toml` is already up to date, writing nothing. It exits non-zero if the lock would change or is missing, so CI can assert the lock is current. It covers `pylock` output to a file in single-environment mode. When a mismatch is provable from the inputs alone, a changed direct dependency, a narrowed `requires-python`, a changed extra or group, or a tightened constraint, `--locked` fails fast with that reason before resolving. Otherwise it runs the full re-resolve, and only that comparison reports the lock up to date: nab is non-sticky, so a lock can satisfy every input yet be stale once a newer admissible version exists. `--upgrade` re-anchors the `PD` resolve window to the current time instead of reusing the timestamp recorded in an existing lockfile, and prints a notice naming the cutoff it dropped. ## `nab download` Resolve and download every wheel, sdist, and direct-URL archive into a local directory. The download is idempotent: files whose recorded sha256 matches a local file are left alone. Local and VCS pins are skipped. Universal mode (`[tool.nab].mode = "universal"`) re-resolves across the matrix and downloads the union of every tuple's artefacts into the same directory, deduplicated by URL so a wheel shared across tuples is fetched once. * `--output` defaults to `wheels/`. * `--max-concurrency` controls parallel HTTP fetches (default `8`, minimum `1`). Layered, so it can also be set in an `nab.toml` or `NAB_MAX_CONCURRENCY`. * `--workspace-discovery` (default) mirrors `nab lock`: it finds a `[tool.nab.workspace]` root and resolves against the in-tree members. `--no-workspace-discovery` turns that off. See [Lock a workspace](../how-to/workspaces.md). `--offline`, `--cache-dir`, `--http-backend`, `--max-concurrency` and the `--project-*` overrides flow through the same layered config sources `nab lock` uses, so a `NAB_*` variable or a system/user/project `nab.toml` is read for `nab download` as for `nab lock`. A summary of how many files were written and how many were already present is printed to stderr. ## `nab config` Inspect the effective layered configuration (read-only). Three actions: * `nab config list` prints every option with its value, scope, and the source it came from. * `nab config get ` prints one effective value. * `nab config explain ` prints the full source stack for one key, the winning source marked with a `>`. Array options concatenate every layer instead of one winning, so each source is shown as a contribution and the merged effective value is printed on a trailing `= effective:` line. Add `--include-rejected` to also list sources that tried to set the key but were rejected by the category gate. The same per-option override flags the run commands accept (the `--project-*` overrides and the user knobs `--offline`, `--cache-dir`, `--http-backend`, `--max-concurrency`) layer a CLI value on top, so the inspector reflects the same effective values a run would see. See [Configuration](configuration.md) for the source ladder and the `NAB_*` environment variables. ## `nab cache` Inspect and clear the on-disk cache. It takes one location selector, `--cache-dir PATH`. Without it the root is the one a run in the same directory uses: `cache-dir` is read off the config source ladder, so a `nab.toml` or `NAB_CACHE_DIR` sets it too (see [Configuration](configuration.md)). Three actions: * `nab cache dir` prints the resolved cache root to stdout, whether or not it exists yet. * `nab cache verify` walks the cached index records read-only and reports any corrupt entry by path and reason. Cloned repositories and extracted archives hold upstream files, so they are not parsed. * `nab cache clear` removes every bucket under the root, including the cloned repositories and extracted archives, returning the cache to cold. ## Runtime flags Both subcommands accept the same runtime knobs: | Flag | Default | Effect | | ---- | ------- | ------ | | `--cache-dir PATH` | `~/.cache/nab` | Override the on-disk cache root. | | `--no-cache` | off | Disable cache for this run. A declared VCS or archive source is materialised into a temporary directory instead, so it is refetched every run. Combine with `--offline` only if the cache already has every file. | | `--offline {True,False}` | unset | Use cache only, never hit the network. Layered: `--offline True` forces offline, `--offline False` forces network even over a lower `offline = true`. Bare `--offline` / `--no-offline` are shorthands for `True` / `False`. | | `--http-backend {urllib3,httpx}` | `urllib3` | Pick the async transport for fetches. Layered, so it can also be set in an `nab.toml` or `NAB_HTTP_BACKEND`. `httpx` needs its extra (see [Install nab](../how-to/install.md)). | A name absent from the index is remembered for a short window, so a repeated lookup is answered from cache, offline included. `urllib3` is the only backend pulled in by the base install. The others surface a helpful `ImportError` if selected without the matching extra. ## Global flags | Flag | Effect | | ---- | ------ | | `--version`, `-V` | Print `nab ` and exit `0`. | | `--help`, `-h` | Standard argparse-style help. Per-subcommand help works too: `nab lock --help`. | The verbosity, colour, and progress flags below are also global. ## Output control These flags set how much `nab` writes to stderr, whether it colours it, and whether it animates a progress line. They are global: `nab` reads them before the subcommand, so they work with `lock`, `download`, `config`, and `cache`. stdout carries only the requested output (the lockfile, the requirements list, the `config` dump), so it stays pipeable at every verbosity. | Flag | Effect | | ---- | ------ | | `-v`, `-vv`, `--verbose` | Raise verbosity. `-v` adds the engine's `INFO` records, `-vv` adds `DEBUG`. `--verbose` counts as one `-v`; repeats add, and `-vvv` saturates at `-vv`. | | `-q`, `-qq`, `--quiet` | Lower verbosity. `-q` drops the run summary and notes, keeping warnings and errors; `-qq` keeps only errors. `--quiet` counts as one `-q`. | | `--color` | When to colour stderr: `auto` (default), `always`, or `never`. `auto` colours only an stderr terminal and honours `NO_COLOR`, `FORCE_COLOR`, and `TERM=dumb`; `always` and `never` win outright. Colour touches only a message's leading token, so it reads the same stripped. | | `--no-color` | Shorthand for `--color never`. | | `--no-progress` | Suppress the live progress line (also `NAB_NO_PROGRESS`). | Verbosity is the count of `-v` minus the count of `-q`. The five levels, quietest first, are silent (`-qq`), quiet (`-q`), normal (the default), verbose (`-v`), and debug (`-vv`). Errors print at every level; warnings print unless `-qq`; the run summary and notes print at normal and above. While `nab lock` or `nab download` resolves, a live line repaints on stderr, counting package listings fetched and packages pinned: ``` Resolving... 12 fetched, 5 pinned ``` It shows only at normal verbosity on an stderr terminal; `--no-progress` (or `NAB_NO_PROGRESS`) turns it off, and it never writes to stdout. ## Environment variables | Variable | Effect | | -------- | ------ | | `XDG_CACHE_HOME` | If set, `nab`'s default cache root is `$XDG_CACHE_HOME/nab` instead of `~/.cache/nab`. | | `NAB_VERBOSITY` | Default verbosity when no `-v` / `-q` flag is given: one of `silent`, `quiet`, `normal`, `verbose`, `debug`. A `-v` / `-q` flag overrides it. An unrecognised value is rejected. | | `NAB_NO_PROGRESS` | If set to a non-empty value, suppress the live progress line, like `--no-progress`. | | `NO_COLOR` | If set to a non-empty value, disable colour under `--color auto`. | | `FORCE_COLOR` | If set to a non-empty value, force colour under `--color auto`. | | `TERM` | `dumb` disables colour under `--color auto`. | ## Exit codes | Code | Meaning | | ---- | ------- | | `0` | Success. | | `1` | Resolution failed, lockfile cannot be written (missing hash), download failed, missing `[project].dependencies`, invalid `[tool.nab]` configuration, or `--locked` found the lockfile out of date or missing. | | `130` | Interrupted with Ctrl-C. `nab` prints `error: interrupted` and exits. | [PEP 751]: https://peps.python.org/pep-0751/