ID@agntn/registriesv0.5.0

One PURL. Every registry.

One TypeScript interface over npm, PyPI, crates.io, RubyGems, Packagist and Arch Linux. Package, versions, dependencies and maintainers come back in the same shape, from a CLI, a library call or an AI SDK tool. The registries keep their quirks to themselves.

Registries
6
7 API hosts
Lookups
4
one shape each
No key
6 registries
public APIs only
Install$ pnpm add @agntn/registries

PURL in, package out

A package URL names the ecosystem, the namespace, the name and the version. The library parses it once, picks the adapter, asks the registry and hands back one Package. This panel walks through 6 packages. Each one starts as a recorded sample and gets swapped for the docs worker's live answer the moment it lands.

  • pkg:npm/lodash, pkg:cargo/serde, pkg:alpm/aur/paru: one addressing scheme, ECMA-427
  • License normalized to SPDX, repository URL canonicalized, versions dated
  • A missing package is a typed NotFoundError, never an empty object
Read Package URLs
CallfetchPackageFromPURL("pkg:npm/lodash")recorded
Package / npm

lodash 4.18.1

Lodash modular utilities.

Licenses
MIT
Repository
github.com/lodash/lodash
Versions
117 · newest 2026-04-01
Keywords
modules, stdlib, util

Same calls, every adapter

Every registry is a class with the same four methods and a URL builder. Swap the PURL and the rest of the code stays. Upstream quirks, from npm's dist-tags to the AUR's RPC, stay inside the adapter and never leak through the public types.

  • fetchPackage, fetchVersions, fetchDependencies, fetchMaintainers on every registry
  • create('npm') imports one adapter on first use, nothing runs at import, no switch statement
  • Bring your own Client for retries, timeouts, rate limiting and a User-Agent
Read Package, versions, dependencies, maintainers
Filenpm.tssame calls · 6 registries

Read

import { createFromPURL } from "@agntn/registries";// NpmRegistry, imported on the first create()const [registry, name] = await createFromPURL("pkg:npm/lodash");const pkg = await registry.fetchPackage(name);const versions = await registry.fetchVersions(name);const deps = await registry.fetchDependencies(name, pkg.latestVersion);const people = await registry.fetchMaintainers(name);// "4.18.1", 117 versions, 0 deps, 2 maintainers

A terminal client with a lockfile

The same lookups from a shell. Answers land in the platform cache directory with a lockfile that records when each entry was fetched and how long it stays fresh. The second call for the same package never leaves the machine.

  • registries info, versions, deps, maintainers, with --json for scripts
  • Cache on unstorage with sha256 integrity and a TTL per data type
  • Filesystem by default, any unstorage driver on the edge
Read The command line
List$ registries versions npm/lodash --limit 4117 total
  1. 4.18.12026-04-01ok
  2. 4.18.02026-03-31deprecated
  3. 4.17.232026-01-21ok
  4. 4.17.212021-02-20ok

Ask again for the same PURL and the answer comes out of the cache, not the registry, until the lockfile says its TTL ran out.

0 dependencies · 2 maintainersrecorded

6 adapters, one shape

npm hides deprecation in a version field, crates.io yanks, Packagist marks nothing at all, and Arch splits into the official repos and the AUR. Each adapter maps its API onto the shared types and keeps the rest to itself. Adding one means writing that mapping, not the HTTP client, the retry policy or the cache.

Import@agntn/registries/registries/<name>

ecosystems()6 registries · manifest order
PURLHow it reads
npmpkg:npm/@scope/nameOne package document from registry.npmjs.org. Scopes in the namespace, deprecated as a status.deprecated
crates.iopkg:cargo/nameCrate, versions, dependencies and owners across four endpoints. yanked as a status, docs.rs for docs.yanked
PyPIpkg:pypi/nameJSON API for metadata, Simple API for versions. Names normalized per PEP 503, extras become scopes.yanked
RubyGemspkg:gem/nameGem, versions, dependencies of each version and owners. yanked as a status, rubydoc for docs.yanked
Packagistpkg:composer/vendor/nameOne document per vendor/name with every version inside. php and ext-* requirements skipped.nothing
Arch Linuxpkg:alpm/arch/name · pkg:alpm/aur/nameOfficial repos and the AUR behind one type, routed by namespace. One version per package, flagged out of date as deprecated.deprecated
read from builtins / no networkcreate("<key>") loads one

One tool call, four hosts

Each tool is written once. The AI SDK, the MCP server and the Pi and OMP extensions all get that one definition, so a model asking about a PURL gets the same answer wherever it sits. Even the refusals match. A description that says "ignore previous instructions" is still just a description.

  • Six AI SDK tools on the /ai subpath, packageTool through ecosystemsTool, one schema each
  • registries mcp serves the same six over stdio, and the Pi and OMP extensions register them in a terminal
  • Bulk lookups skip a failed package instead of failing the batch
Read Agents
Callregistries_package("pkg:npm/lodash")
Tool / read-only

npm

The agent gets the normalized Package as JSON. No prose, no guessing what npm meant.

name
"lodash"
latestVersion
"4.18.1"
licenses
"MIT"
AI SDK · MCP · Pi · OMPregistries mcp · stdio
Startpnpm add @agntn/registriesNode.js 26 or newer

Start with one command

One install gives you the library, the registries CLI and the MCP server. Hand it a PURL and read what the registry said.

  • PinPre-1.0, so pin exact versions.
  • KeysNo API keys. Every registry here answers the public.
  • DataDescriptions and maintainer names are what the owner typed. Treat them as data.

First lookup

$ pnpm add @agntn/registriesimport { fetchPackageFromPURL } from "@agntn/registries";const pkg = await fetchPackageFromPURL("pkg:npm/lodash");pkg.latestVersion;  // "4.18.1"pkg.licenses;       // "MIT"