List a package's versions with the date each was published. Built for coding agents that keep dependencies current from the shell.
$ pkgwhen pypi:openai-agents
VERSION PUBLISHED AGE
0.22.0 2026-08-19 18d
0.21.1 2026-08-16 20d
0.21.0 2026-08-15 22d
...
0.0.1 2025-03-04 550d yanked
One argument names the registry and the package. PyPI, npm, and GitHub Releases come back in the same table, newest first, with the age of each version and a mark on the ones dependency updaters usually skip.
An agent that keeps dependencies current keeps asking the same question: when did this version come out? A release-age rule (Renovate's minimumReleaseAge, uv's exclude-newer, pnpm's minimumReleaseAge) holds a version back until it is a day old, so "why is there no PR yet" and "is this pin the latest" both come down to a publish date.
The registries answer it, but not in one shape. npm has npm view PKG time --json, which returns every version in one dictionary. PyPI has a JSON API and no command. GitHub has gh release list. So the agent fetches the JSON with curl and writes five to ten lines of Python to pull out the dates. In one month of the author's Claude Code sessions, that happened 132 times.
pkgwhen is those lines, made into a command. For GitHub it is not a replacement for gh release list; it is the same answer in the same table as the other two.
Install it where the agent runs:
brew install iwamot/tap/pkgwhenOr with Go:
go install github.com/iwamot/pkgwhen@latestOr download a prebuilt binary from the Releases page.
Then tell the agent to use it, in CLAUDE.md, AGENTS.md, or whichever file your agent reads:
To find which versions of a package exist and when each was published, use `pkgwhen` instead of curl and an ad-hoc script: `pkgwhen pypi:NAME`, `pkgwhen npm:NAME`, or `pkgwhen github-releases:OWNER/REPO`. Add `@VERSION` to print one version, `--min-age 1d` to list only versions old enough to pass a one-day release age, and `--since 30d` for versions published in the last 30 days. A mark means dependency updaters usually skip that version, so a newer marked version is not a reason to expect a PR. Exit 1 means the version does not exist (yet); rerun while it exits 1, and stop and read stderr on any other exit code.That paragraph is all the agent needs. pkgwhen --instructions prints the same paragraph, for setup scripts and machines where this page is not at hand.
Checking whether a version has passed a one-day release age. Only versions published more than a day ago are listed, so the newest one being absent is the answer:
$ pkgwhen --min-age 1d -n 3 npm:@types/node
VERSION PUBLISHED AGE
26.4.1 2026-09-01 4d
26.4.0 2026-08-27 10d
26.3.0 2026-08-24 12d
... and 2356 more (pass -n N or --all)
A window that leaves nothing says why on stderr, and names the nearest version it dropped, so an empty table is never silent:
$ pkgwhen --since 1d pypi:openai-agents
pkgwhen: no version published in the last 1d; latest is 0.22.0, published 19d ago
VERSION PUBLISHED AGE
One version, with the time of day. The tag on GitHub is v2.2.12, and the plain version finds it too:
$ pkgwhen github-releases:jdx/aube@2.2.12
v2.2.12 2026-09-06T00:44:39Z 13h
A version that is not there yet. Nothing is printed on stdout and the exit code is 1, so a loop can wait for a version that was just published; a name the registry has never heard of exits 4 instead, so the same loop stops on a typo. Before waiting on github-releases, pass a token: without one the API allows 60 requests an hour, which this loop spends in about ten minutes, and a private repository answers 404 exactly like a missing one. The || [ $? -ne 1 ] ends the loop on any other exit code, so a rate limit or an unreachable registry stops it instead of retrying forever:
$ pkgwhen npm:welt-io-x@1.2.3
pkgwhen: npm:welt-io-x@1.2.3: version not found; latest is 1.2.2, published 3d ago; run `pkgwhen npm:welt-io-x` to see the versions that exist
$ until pkgwhen npm:welt-io-x@1.2.3 || [ $? -ne 1 ]; do sleep 30; done
The first release of a brand-new package is the one wait that needs both codes, because until it lands the package itself is not there either:
$ until pkgwhen npm:welt-io-x@1.0.0; do rc=$?; [ $rc -eq 1 ] || [ $rc -eq 4 ] || break; sleep 30; done
The same list as JSON, for a script that compares rather than reads:
$ pkgwhen --json -n 1 pypi:openai-agents
{
"registry": "pypi",
"name": "openai-agents",
"versions": [
{
"version": "0.22.0",
"published": "2026-08-19T13:45:19Z",
"age_seconds": 1557272,
"yanked": false,
"deprecated": false,
"prerelease": false
}
],
"more": 118
}
$ pkgwhen --help
pkgwhen — list a package's versions with the date each was published.
Usage:
pkgwhen [options] REGISTRY:NAME[@VERSION]
Examples:
pkgwhen pypi:openai-agents
pkgwhen npm:@types/node@22.0.0
pkgwhen github-releases:jdx/aube --since 30d
REGISTRY is pypi, npm, or github-releases. NAME is the package name, or
OWNER/REPO for GitHub. Versions are printed newest first by publish date,
at most 20 unless -n or --all is given. With @VERSION, only that version
is printed, with the time of day, and the options that narrow a list do
not apply.
Options:
--min-age DUR only versions published more than DUR ago (1d, 36h, 2w)
--since DUR only versions published within the last DUR
(--min-age keeps the older side, --since the newer side)
-n N print at most N versions (default 20)
--all print every version
--json print JSON instead of the table, with ISO 8601 timestamps
-h, --help show this help
-v, --version show the version
--instructions print the paragraph for the agent's instruction file
Environment:
GITHUB_TOKEN, then GH_TOKEN, then `gh auth token`, is used for
github-releases, and is required for a private repository.
Marks at the end of a row:
yanked the version was yanked (PyPI)
deprecated the version is deprecated (npm)
pre prerelease
Exit codes:
0 printed
1 the version given with @VERSION does not exist
2 usage error: fix the flags or the argument
3 registry error: check the token or the network, then retry
4 the package, or the repository on GitHub, does not exist
- Versions are ordered by publish date, not by version number, so a patch to an older line appears where it was published. To compare two versions, ask for each with
@VERSION. - Ages are measured from the current UTC time. The table shows whole days, hours below a day, minutes below an hour, and seconds below a minute;
--min-ageand--sinceare compared to the second. - npm: the full packument is fetched, because only it carries publish dates. For a large package that is a few megabytes, compressed in transit.
- PyPI: a version's date is the earliest upload among its files, which is the moment uv's
exclude-newertreats it as available. A version is markedyankedwhen any of its files is, which is how Renovate reads it. Versions with no files are left out. - GitHub: the date is
published_at, which is what Renovate uses and which can trail the draft's creation by as long as the draft took to finish. Draft releases are dropped.GITHUB_TOKEN,GH_TOKEN, orgh auth tokenis used when available; without one, the API allows 60 requests an hour. Either that limit or the short-term one is reported as a rate limit with how long to wait, rather than as a bare 403. Releases are read in the order GitHub returns them, newest created first, and only as many pages as the requested count needs unless a date option or--allis given. - An empty answer says which kind it is. A window that dropped everything names the nearest version it dropped, on stderr, and still exits 0. A version the registry does not have exits 1 and names the latest one that does exist; a package or repository it does not have exits 4, because the next step differs: wait, or check the name.
- Nothing is cached and nothing is written. Every call asks the registry.
- While the version is 0.x, the exit codes and the shape of the output can still change between releases; from 1.0 they only gain cases.
- Sorting by version number. The question here is when, and the registries already order by version.
- Other registries. Go, Cargo, and the rest can be added when a use for them turns up; the shape is one small package per registry.
- Aggregators such as deps.dev. They lag the registries by minutes and miss yanks, which is exactly where a publish date matters.
- Waiting. Exit 1 and a shell loop cover it without a flag.
- Installing anything.
pkgwhenonly reads.
MIT