nxv — Nix Version Index

SkillDev tools

Find any version of any Nix package across nixpkgs git history using the nxv CLI or HTTP API. Use when asked which nixpkgs commit shipped a specific package version (e.g. "python 2.7", "nodejs 15", "ruby 2.6"), when looking up package metadata/license/homepage, when generating a `nix shell nixpkgs/<commit>#pkg` invocation for an old version, or when querying the public/private nxv server. Triggers include "find python 2.7 in nixpkgs", "which commit had nodejs 15.14", "when was foo added/removed", "give me the nix shell command for ruby 2.6", "search nixpkgs for X", or any question about historical Nix package versions.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the nxv — Nix Version Index skill

What this skill tells your AI

The instructions your AI receives, as published by utensils/nxv in .agents/skills/nxv/SKILL.md and read by ahel’s review.

nxv is a Rust CLI + HTTP API that indexes nixpkgs channel-release history (2016+) into a local SQLite database with a bloom filter for fast lookups. It answers: "which exact nixpkgs commit shipped version X of package Y?" and produces the nix shell nixpkgs/<commit>#pkg command you need to actually use it.

Quick Reference

nxv search python                        # All python packages (most recent per version)
nxv search python 2.7                    # Filter by version (prefix match)
nxv search python --exact                # Exact attribute name only
nxv search "json parser" --desc          # Full-text search package descriptions
nxv run python 2.7                       # Resolve and open a pinned shell
nxv run python 3.11 --with nodejs@20     # Multi-package shell
nxv info python311                       # Detailed info for current version
nxv info python311 3.11.4                # Detailed info for specific version
nxv history python311                    # Version timeline (first/last seen)
nxv history python311 3.11.4             # When was 3.11.4 available?
nxv stats                                # Index statistics
nxv update                               # Update nxv itself to the latest release
nxv sync                                 # Download or refresh the local package index
nxv sync --force                         # Force a full index re-download
nxv serve --host 0.0.0.0 --port 8080     # Start HTTP API + web UI
nxv completions zsh                      # Generate shell completions
nxv skill install codex                  # Install this skill for one explicit agent
nxv skill install --detected             # Install for detected AI agents
nxv skill list                           # Agents, skill paths, install status

How to Use This Skill

Parse $ARGUMENTS to determine the action:

  • If arguments look like a subcommand (search, run, info, history, stats, update, sync, serve, completions, skill, and indexer-only index, dedupe, publish, keygen), run that subcommand.
  • If arguments look like a package name (e.g. python, nodejs 15, ruby 2.6), default to nxv search.
  • If arguments look like a question ("when was X added", "which commit has Y"), pick search or history accordingly.
  • If no arguments, run nxv stats to give the user a quick health check of their index.

For agents: always pass --format json (CLI) or hit the HTTP API and pipe to jq. The table format is human-only; column widths are terminal-dependent. Exception: nxv stats has no --format flag — use GET /api/v1/stats when you need stats as JSON.

Global Options

Every CLI invocation accepts:

FlagDescription
--db-path <PATH>Path to the index database (default: platform data dir)
-v, --verbose-v info, -vv debug (SQL queries, HTTP requests)
-q, --quietSuppress all output except errors
--no-colorDisable colored output (also honors any non-empty NO_COLOR)
--api-timeout <SECS>API request timeout when using remote backend (default: 30)

Local vs Remote Backend

nxv transparently runs against either a local SQLite index or a remote nxv serve instance. Set NXV_API_URL to switch:

# Use the public hosted instance — no local index needed
NXV_API_URL=https://nxv.urandom.io nxv search nodejs 15
NXV_API_URL=https://nxv.urandom.io nxv info python311

# Or your own private instance
export NXV_API_URL=http://gpu-host:8080
nxv search rust 1.70

If NXV_API_URL is unset, the CLI uses the local index at ~/Library/Application Support/nxv/index.db (macOS) or ~/.local/share/nxv/index.db (Linux). Run nxv sync to download the latest published index on first use.

Search

Find packages and the commits where each version existed:

nxv search python                                    # Recent per (pkg, version)
nxv search python 3.11                               # Version prefix filter
nxv search python 2.7.3 --all-depths                 # Include nested package-set members
nxv search python --exact                            # Exact attribute name only
nxv search python --license MIT                      # Filter by license
nxv search python --sort date --reverse              # Oldest first
nxv search "web server" --desc                       # FTS5 description search
nxv search python --show-platforms                   # Add platforms column
nxv search python --full                             # All commits (no dedup)
nxv search python --limit 5                          # Cap at 5 results
nxv search python --format json                      # Machine-readable JSON
nxv search python --format plain                     # TSV for shell scripts

For version-qualified prefix searches, nxv first resolves the shallowest attribute-path tier matching the package prefix, then applies the version prefix. This keeps python 3.11 on interpreter attributes such as python311; a query like python 2.7.3 reports a precise miss with nearby interpreter versions instead of returning libraries whose own version is 2.7.3. A package-set query such as python27Packages 2.7.3 resolves its member depth naturally. Pass --all-depths to request the legacy broad prefix behavior explicitly. The flag requires a version and conflicts with --exact and --desc.

Version-miss diagnostics and suggestions are written to stderr. Successful JSON searches return an array of package rows; an empty miss emits no stdout.

JSON shape per row (the same shape is returned by nxv info, nxv history <pkg> <version>, nxv history --full, and the /api/v1 package endpoints):

{
  "id": 9630,
  "name": "python3",
  "version": "3.12.13",
  "first_commit_hash": "d78e468770f4ab5e00c5015f4d77c1a499a76dc8",
  "first_commit_date": "2026-03-06T20:06:54Z",
  "last_commit_hash": "3d2613bc58a1f5b7805467a63a825e1d7bc9b7a9",
  "last_commit_date": "2026-07-21T12:39:35Z",
  "attribute_path": "python312",
  "description": "High-level dynamically-typed programming language",
  "license": ["Python-2.0"],
  "homepage": "https://www.python.org",
  "maintainers": ["mweinelt"],
  "platforms": ["x86_64-linux", "aarch64-darwin"],
  "source_path": "pkgs/development/interpreters/python/cpython/default.nix",
  "known_vulnerabilities": null
}
FieldTypeNotes
idintegerIndex row id. Not stable across index rebuilds — never persist it.
namestringUpstream derivation name. Not installable — see below.
attribute_pathstringThe nixpkgs attribute. This is what you install with.
versionstringPackage version.
first_commit_hash / last_commit_hashstringFull 40-char nixpkgs commit hashes.
first_commit_date / last_commit_datestringRFC 3339 / ISO 8601 UTC.
description, homepage, source_pathstring | nullsource_path is null for older packages.
license, maintainers, platformsarray | nullArrays of strings. null when the package declares none.
known_vulnerabilitiesarray | nullnull (or []) means no known advisory; non-empty means the package is insecure.

name vs attribute_path — these routinely differ and confusing them produces commands that fail. name is the upstream derivation name (pname for top-level attrs, the final attribute segment for nested ones); attribute_path is the address you actually install with. For the row above, nix shell nixpkgs/<hash>#python312 works and #python3 may not. Always use attribute_path.

license, maintainers, platforms, and known_vulnerabilities are real JSON arrays, so jq reaches them directly:

nxv search python312 --exact --format json | jq -r '.[0].license[]'        # Python-2.0
nxv search python312 --exact --format json | jq -r '.[0].platforms | join(", ")'
nxv search hello --format json | jq -r '.[] | select(.known_vulnerabilities != null) | .attribute_path'

Version note: older nxv releases emitted these four fields as JSON-encoded strings ("license": "[\"Python-2.0\"]"), requiring a second fromjson. If you must support both, use (.license | if type == "string" then fromjson else . end).

Info

Detailed metadata for one package version (description, license, homepage, platforms, source path, known vulnerabilities):

nxv info python311                       # Latest known version
nxv info python311 3.11.4                # Specific version (positional)
nxv info python311 -V 3.11.4             # Specific version (flag form)
nxv info python311 --format json

info resolves the package name as an exact attribute path first, so it needs no --exact flag: nxv info python311 3.11.4 returns python311 only, never python311Full or python311Packages.*. If the package is known but never had the requested version, info reports not found instead of falling back to unrelated prefix matches — an empty result means "this package never had that version", not "try harder".

An unknown attribute path is widened to a prefix search, but what gets prefix-matched depends on whether a version was given:

nxv info python311Packages.tk 3.11.4     # widened over ATTRIBUTE PATHS -> resolves
nxv info python311Packages.tk            # widened over the NAME field -> usually no match

So partial attribute paths generally only resolve when you also pass a version. For open-ended prefix lookups use nxv search (with --exact as needed) instead.

History

Version timeline — when each version first appeared and when it was last seen:

nxv history python311                    # All versions of python311
nxv history python311 3.11.4             # Just one version's window
nxv history ripgrep --full               # Add commits, license, homepage, etc.
nxv history python311 --format json

JSON shape per row — plain nxv history <pkg> returns this compact timeline shape:

{
  "version": "3.11.4",
  "first_seen": "2023-06-15T00:00:00+00:00",
  "last_seen": "2023-12-01T00:00:00+00:00",
  "is_insecure": false,
  "known_vulnerabilities": null
}

is_insecure means nixpkgs reports a known advisory for that software version, resolved by package name so every attribute packaging the same build agrees (emacs and emacs28 at 28.2 are both flagged). It does not tell you whether nix will refuse to build a specific attribute at a specific revision — for that, name the version (nxv history <pkg> <version>) and read its own known_vulnerabilities.

Note the field names differ from search (first_seen/last_seen, not first_commit_date/last_commit_date), and there are no commit hashes. Adding --full, or naming a version (nxv history python311 3.11.4), switches the output to the full search row shape documented above — use one of those when you need a commit hash to feed to nix shell. Like the compact timeline, bare --full resolves the package by its exact installable attribute_path.

Using a Found Version

The quickest interactive path is nxv run, which resolves the same package and version query as search and opens one pinned shell:

nxv run python 2.7
nxv run python 3.11 --with nodejs@20 --with jq

Additional --with values use PACKAGE@VERSION when a version is needed. nxv resolves every query before launch, prefers an exact attribute match before falling back to search's deterministic relevance rules, and uses each result's latest observed commit. Modern revisions are combined in one nix shell; if any result predates flakes, nxv uses one compatible nix-shell -p environment instead. On Apple Silicon, the pre-flake fallback evaluates packages as x86_64-darwin and requires Rosetta.

For manual command construction, take a first_commit_hash (or last_commit_hash) from search/history output and feed it to Nix:

# Drop into a shell with that exact version
nix shell nixpkgs/e4a45f9#python

# Run it once
nix run nixpkgs/e4a45f9#python

# Add to a flake input
inputs.nixpkgs-python27.url = "github:NixOS/nixpkgs/<commit>";

Pick first_commit_hash for the canonical "introduced in" commit; pick last_commit_hash if you want the most recent commit that still shipped that version.

Caveats for Old Commits

The direct nix shell nixpkgs/<commit>#<attribute_path> form may fail for old nixpkgs revisions even when nxv found a valid observation:

  • Modern Nix rejects the retired edition field in flake files from roughly 2020 and earlier.
  • Revisions from before late 2021 predate usable aarch64-darwin support for many packages. On Apple Silicon, evaluate the package as x86_64-darwin and run it through Rosetta when necessary.

Use a classic nixpkgs import with the full commit hash and the result's exact attribute_path:

nix shell --impure --expr '
  (import (builtins.fetchTarball
    "https://github.com/NixOS/nixpkgs/archive/<full-hash>.tar.gz")
    { system = "x86_64-darwin"; }).ruby
' --command ruby --version

Replace .ruby and the command with the returned attribute and executable. The platforms field comes from package metadata (meta.platforms); it is not proof that the historical revision evaluates on that system or that Hydra produced a cached binary for it.

Application and Index Management

nxv update                               # Update the nxv application only
nxv sync                                 # Download or refresh the package index only
nxv sync --force                         # Force full re-download of the index
nxv sync --skip-verify                   # Skip minisign signature check (INSECURE)
nxv sync --public-key /path/key.pub      # Use a custom public key (self-hosted index)
nxv sync --manifest-url <URL>            # Use a custom manifest (self-hosted index)
nxv stats                                # Index size, commit range, last update

nxv update only checks GitHub for the latest nxv release:

  • Local install (install.sh / manual download): downloads the platform binary, verifies SHA-256, atomically swaps the running executable.
  • Nix / cargo / Homebrew: prints the matching upgrade hint (e.g. brew upgrade nxv) and exits successfully.

nxv sync independently refreshes the SQLite index and bloom filter. It never checks for or replaces the application. If a published index requires a newer schema, run nxv update (or the printed package-manager command) and retry nxv sync.

API Server

nxv serve                                            # 127.0.0.1:8080 (default)
nxv serve --host 0.0.0.0 --port 3000                 # Public bind
nxv serve --cors                                     # Enable CORS for all origins
nxv serve --cors-origins https://app.example.com     # Restrict CORS
nxv serve --rate-limit 10 --rate-limit-burst 20      # Per-IP rate limit

The server bundles:

  • Web UI at / (Tailwind v4 + vanilla JS, embedded at build time)
  • OpenAPI docs at /docs (Scalar UI)
  • REST API at /api/v1/* — see endpoints below
  • Cache headers: 1h on cacheable package routes; never on /health, /metrics

HTTP Endpoints

All paths are under /api/v1. Wrapped responses always look like { "data": ..., "meta": {...} } for paginated lists, { "data": ... } for single items.

MethodPathPurpose
GET/search?q=<name>&limit=&offset=&sort=&exact=&all_depths=Search packages
GET/search/description?q=<text>&limit=&offset=Full-text search descriptions (FTS5)
GET/packages/{attr}All version records for a package
GET/packages/{attr}/historyVersion timeline (first/last seen)
GET/packages/{attr}/versions/{version}All records for one version
GET/packages/{attr}/versions/{version}/firstFirst-seen commit
GET/packages/{attr}/versions/{version}/lastLast-seen commit
GET/statsIndex statistics
GET/healthLiveness probe (uncached)
GET/metricsServer metrics (uncached)

Search query parameters:

ParameterTypeDescription
qstringSearch query (required)
versionstringVersion filter (prefix match)
exactbooleanExact attribute name match
all_depthsbooleanInclude every attribute depth; requires version
licensestringLicense filter
sortstringrelevance (default), date, version, or name
reversebooleanReverse sort order
limitintegerMax results (default 50)
offsetintegerResults to skip (default 0)

Quick examples against the public instance:

curl -s "https://nxv.urandom.io/api/v1/search?q=python&version=3.11&limit=5" | jq
curl -s "https://nxv.urandom.io/api/v1/packages/python311/history" | jq '.data[0:3]'
curl -s "https://nxv.urandom.io/api/v1/packages/nodejs-15_x/versions/15.14.0/first" | jq
curl -s "https://nxv.urandom.io/api/v1/stats" | jq '.data'

Version-qualified search responses add an optional meta.resolution object with scope, resolved_depth, requested_version, version_matched, optional deeper_matches_available, and up to five {attribute_path, version} suggestions. This metadata is additive; package rows and the CLI JSON array are unchanged.

Skill Management

nxv can install this very skill for any major AI coding agent — the binary embeds the SKILL.md and writes it where each agent looks, per the Agent Skills standard:

nxv skill install codex                  # Install user-wide for one agent
nxv skill install --detected             # Explicitly install for detected agents
nxv skill install codex --project        # Install for Codex in the current project
nxv skill install --detected --project   # Map detected agents to project paths
nxv skill install claude codex           # Install for specific agents only
nxv skill install --all                  # Install for every supported agent
nxv skill install copilot --dir ~/repo   # Project install into another directory
nxv skill list                           # Show agents, paths, install status
nxv skill show                           # Print the SKILL.md to stdout
nxv skill uninstall --project            # Remove project-level installs

Supported agents and where the skill lands (<dir>/nxv/SKILL.md):

AgentUser-wideProject-level
claude~/.claude/skills/.claude/skills/
codex~/.codex/skills/.agents/skills/
pi~/.pi/agent/skills/.pi/skills/
openclaw~/.openclaw/skills/.agents/skills/
copilot~/.copilot/skills/.github/skills/
cursor~/.cursor/skills/.agents/skills/
gemini~/.gemini/skills/.agents/skills/
amp~/.config/amp/skills/.agents/skills/
goose~/.config/goose/skills/.agents/skills/
agents~/.agents/skills/.agents/skills/

The table shows each agent's primary directory — the one nxv skill install <agent> writes to. Several agents read additional locations: Copilot reads .github/skills/, .claude/skills/, or .agents/skills/ in a repository, and Pi reads .agents/skills/ as well as .pi/skills/.

Semantics:

  • Installation requires one explicit target mode: one or more agent names, --detected, or --all. With no target, nxv exits without writing anything.
  • --detected checks user configuration directories. With --project / --dir, those detected agents are mapped to their corresponding project paths. No detected agents is an error; use the explicit agents target for the generic Agent Skills directory.
  • Agents sharing a directory (e.g. codex/cursor/gemini at project level) are deduplicated into a single write.
  • Install overwrites skills/nxv/SKILL.md unconditionally and never touches other files; uninstall removes only that file (and the nxv/ directory if it is then empty).

Indexer Commands (feature-gated)

These require nxv built with --features indexer (cargo build --features indexer or nix build .#nxv-indexer). The indexer ingests channel-release snapshots from releases.nixos.org — no nixpkgs checkout and no Nix evaluation needed for the main path:

# Ingest new channel releases (default channels: nixpkgs-unstable + nixos-unstable-small)
nxv index                                            # Incremental: only new releases
nxv index --channel nixpkgs-unstable                 # Restrict to one channel
nxv index --since 2024-01-01 --until 2024-06-30      # Bound by release date
nxv index --strict --report report.json              # CI mode: gates fatal, JSON report
nxv index --backfill-evals                           # One-time 2016-2020 era (needs `nix`, ~2-3h)
nxv index --head-eval                                # Evaluate master HEAD when channels stall (needs `nix`)
nxv index --retry-failed                             # Re-attempt failed/parked releases
nxv index --max-releases 5                           # Bound a run (testing)

# Repair duplicate rows in pre-v4 databases (also runs during v3->v4 migration)
nxv dedupe --dry-run                                 # Preview
nxv dedupe                                           # Run

# Publish distribution-ready compressed artifacts + manifest
nxv publish --output ./publish --url-prefix https://your-server/nxv
nxv publish --output ./publish --url-prefix https://... --sign --secret-key nxv.key
nxv publish --output ./publish --url-prefix https://... --artifact-name-prefix run-123-

# Generate a minisign keypair for signing manifests
nxv keygen --secret-key ./nxv.key --public-key ./nxv.pub

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
136
Forks
2
Last commit
Aug 2026

Others that do the same job

Advanced
Catalog kind
skill
Gateway key
nxv
Source
github.com/utensils/nxv