Skip to main content
Glama
README.md
# whichlib

The dependency picker for coding agents. Ask which library to use and get a
scored, verified answer instead of a guess.

whichlib is an MCP server with three tools (`recommend_repos`,
`compare_repos`, `trending_repos`) and a free dashboard, Fresh Repos, that
shows the most-starred GitHub repositories created in the last day, week and
month. Every repository gets a transparent 0–100 score from momentum,
maintenance, adoption (stars, forks, npm and PyPI downloads) and license,
plus a one-line verdict.

**For teams (waitlist):** alerts when a dependency your repos use goes stale,
and rules your team's coding agents must follow when they add one. Add a 👍
to [the waitlist issue](https://github.com/josifb/whichlib/issues/1); at 20
signups it gets built. whichlib itself stays free.

## Quick start

Agents: see [MCP server](#mcp-server) below for the one-line install.

Dashboard: open [whichlib.com/repos](https://whichlib.com/repos/). The page
calls the GitHub Search API straight from your browser. No account.

Optional: paste a GitHub token under **Settings** on the page to raise the
API limit from 10 to 30 requests per minute. A fine-grained token with no
permissions is enough. It stays in your browser's local storage.

## What you get

- Three tabs: **Today**, **This week**, **This month**. Each lists the 100
  most-starred repos created in that window.
- Sort by any column: stars, stars per day, forks, open issues, created date,
  last push, language, license or name. Click again to reverse.
- Filter by language (17 languages) or by free text over name, description
  and topics.
- The rank column always shows the stars rank, so after sorting by forks you
  still see where a repo stands.
- Results are cached in the browser for 60 minutes per tab and language.
- Light and dark themes follow your system setting.

## How the numbers are defined

- **Trending** here means "created in the period, ranked by stars". That is
  what the GitHub Search API supports. GitHub's own trending page ranks by
  stars gained in the period, which has no public API. The nightly snapshots
  in this repo will make that possible later.
- **Downloads** do not exist for repositories on GitHub, only for release
  files. Forks are shown as the nearest public signal.
- **Stars/day** is stars divided by the repo's age, floored at one hour.

## Score

Every repo gets a score from 0 to 100, a tier and a one-line verdict. The
breakdown is always returned so a person or an agent can see why. The same
file, `whichlib/lib/score.js`, runs in the dashboard and in Node, so
the two can never disagree.

| Part | Weight | Signal |
|---|---|---|
| Momentum | 40% | Stars gained over the last 7 days from our daily star counts (top 1,000 repos per language plus new trending repos). Without history, stars per day since creation times 7, with age floored at one day, scaled by the npm/PyPI download trend when known (last week against the three weeks before, clamped to 0.5–2x). Log scale: 50 a week is already good, 5,000 is the max. |
| Maintenance | 25% | Days since last push: full marks up to 30 days, zero at 365, linear between. Minus 0.2 when open issues exceed a tenth of the stars. Stability guard: a repo with 10k+ stars or 100k+ weekly downloads, pushed within the last year and not archived, never drops below 0.5 here. Heavy use plus silence is stability, not decay. |
| Adoption | 25% | With weekly downloads known: 50% stars (max 100k), 20% forks (max 20k), 30% downloads (max 1M). Otherwise 70% stars, 30% forks. All log scale. |
| License | 10% | Permissive 1.0, weak copyleft 0.75, strong copyleft 0.5, unrecognised 0.5, none 0. |

Tiers: **Strong** 75 and above, **Solid** 50, **Watch** 25, **Avoid**
below 25. Repos younger than 30 days get **New** instead, with the flag
`too-new` and a verdict that starts "Too new to judge": a launch burst of
stars and a push today say nothing yet about upkeep. Their 0-100 score is
still computed, so rankings do not change. The names are chosen to read correctly for a six-week-old project
and a six-year-old library alike. Archived repos are capped at 20 and get the
verdict "Archived, avoid." A missing license is always named in the verdict.

Verdicts read like "Rising fast, 10.6k downloads/wk, pushed 2 days ago, MIT",
"Gaining steadily, 145M downloads/wk, quiet for 6 months, widely used,
BSD-3-CLAUSE" or "Slow growth, no push in 60 days, GPL-3.0".

### Downloads

GitHub has no download count for repositories, but package registries do.
After each snapshot, the enrich step maps JavaScript and TypeScript repos to
npm and Python repos to PyPI, then fetches last week's downloads:

- A package counts as the repo's only when the registry's own metadata links
  back to `github.com/<owner>/<repo>`. A matching name alone is never enough,
  so a new repo called `widget` is not credited with the downloads of an
  unrelated `widget` package.
- Candidates tried: `<repo>` and `@<owner>/<repo>` on npm, `<repo>` on PyPI.
- Mappings are cached in `registry-map.json` on the `data` branch. Negatives
  are re-checked after 7 days, positives kept, downloads refreshed daily.
- On the first run, 63 of 858 eligible repos mapped to a package. Most repos
  under a month old are not published yet, which is expected.

Other languages (Rust, Go, Java...) are skipped for now. Cargo, Go and Maven
can follow the same pattern.

Caveat: the dashboard has no snapshot history, so momentum uses the fallback.
Scores on the Today tab are therefore provisional; the report and the MCP
server use real stars-gained figures once there are two or more days of
snapshots.

```
cd whichlib
npm run score      # top 25 repos from the latest snapshot with score and verdict
```

## MCP server

The same score, served to coding agents. Three tools over stdio, or remote
at `https://whichlib.com/mcp`. Speaks MCP 2026-07-28; older clients keep
working.

| Tool | Input | What it returns |
|---|---|---|
| `recommend_repos` | `need` in plain words, optional `language`, `limit` (1–10, default 5) | The best repositories for the need, ranked by fit (score × relevance), with npm/PyPI downloads and a verdict each. Candidates come from GitHub's relevance order, its stars order and a topic query; see "How recommend finds and ranks candidates" below. |
| `compare_repos` | `repos`: 2–10 names as `owner/repo` | The repositories side by side, best first, same breakdown. |
| `trending_repos` | `period` day/week/month/rising, optional `language`, `limit` (default 20), `withDownloads` | Most-starred repos created in the period, scored. `rising`: repos of any age by stars gained this week (top 1,000 per language plus new repos are tracked). |

Every result carries readable text and `structuredContent` (JSON) with the
score, tier, verdict, the four subscores, flags, packages and downloads.

Requires Node 22 or newer. Install into Claude Code (`-s user` makes it
available in every project):

```
claude mcp add whichlib -s user -- npx -y whichlib
```

Or as a Claude Code plugin, which connects to the hosted server (nothing to
install) and adds a skill that makes Claude check a library with whichlib
before adding it ([josifb/whichlib-claude-plugin](https://github.com/josifb/whichlib-claude-plugin)):

```
/plugin marketplace add josifb/whichlib
/plugin install whichlib@whichlib
```

Cursor, Windsurf, Claude Desktop and others take the same command in their
MCP config:

```json
{ "mcpServers": { "whichlib": { "command": "npx", "args": ["-y", "whichlib"] } } }
```

Use it without installing: the hosted server needs no Node.

```
claude mcp add --transport http whichlib https://whichlib.com/mcp
```

Limits and the web API are on [whichlib.com/docs](https://whichlib.com/docs/).

To run from a clone instead: `node whichlib/mcp/server.mjs`.

Environment variables, both optional:

- `GITHUB_TOKEN` raises GitHub's limits (search 10 to 30 per minute). A
  fine-grained token with no permissions is enough. Recommend makes three
  searches per call, so without a token it allows about three recommendations
  per minute.
- Star history: momentum uses real stars gained per week for the top 1,000
  repos per language plus new trending repos, from daily star counts on the
  `data` branch. Installed from npm, the server keeps the last 10 days in
  `~/.whichlib/stars/` and refreshes them in the background at most every
  12 hours from `raw.githubusercontent.com` (public files, no token, nothing
  sent). `WHICHLIB_HISTORY=off` turns the download off; repos without history
  fall back to stars per day since creation. In a clone, `npm run pull-data`
  fills `whichlib/data/stars` instead, and `FRESH_REPOS_DATA_DIR` points at
  any folder of daily files.
- `WHICHLIB_TELEMETRY=off` or `DO_NOT_TRACK=1` disables anonymous call
  counting. What is counted: tool name, a random install id, version,
  platform, Node major version and the MCP protocol version of the call. Never queries, repository names or
  results. The collector is a small Cloudflare Worker in `telemetry/`, and
  its aggregate numbers are public at
  https://whichlib-telemetry.todorovskijosif.workers.dev/stats.

Try it without a client:

```
cd whichlib
npm run mcp:smoke   # starts the server over stdio, lists tools, calls each one
```

Known bias, reduced: maintenance used to drop to zero at 90 days without a
push, which put httpx (145M weekly downloads, six quiet months) in "Watch".
The curve now runs to a year and the stability guard keeps widely used repos
at 0.5 or better; httpx lands in "Solid". Release cadence from the GitHub
releases API is the proper long-term signal and is still to come.

### Recommendation eval

`mcp/eval/needs.json` holds 20 needs ("pdf parser" in Python, "state
management" in TypeScript, ...) each with a set of accepted answers a senior
engineer would consider reasonable. `npm run eval` runs them through
`recommend_repos` live and reports how often an accepted repo appears at
rank 1, 3 and 5, for our ranking and for baselines built from the same
candidate pool. Reports land in `mcp/eval/results/`.

Result on 2026-09-27, after query expansion (second report in `results/`):

| Ranking | hit@1 | hit@3 | hit@5 | MRR |
|---|---|---|---|---|
| ours (fit, see below) | 75% | 95% | 100% | 0.85 |
| GitHub relevance order | 65% | 80% | 95% | 0.76 |
| stars order | 45% | 65% | 75% | 0.56 |
| score only, no relevance | 30% | 65% | 70% | 0.46 |

The first report, before expansion, had the same hit rates for our ranking
(75 / 95 / 100, MRR 0.86) on a smaller pool. Expansion raised recall from 53
to 74 accepted repos across the 20 pools, never fewer on any need, and the
baselines fell on that noisier pool while ours held. The fit rules are what
keep the noise out.

### How recommend finds and ranks candidates

Retrieval, three GitHub searches per need:

1. Text search in GitHub's relevance order, with known synonyms OR-ed in
   (`async OR asynchronous runtime`), so vocabulary differences stop hiding
   libraries like tokio.
2. The same text search in stars order, for the big names whose description
   only mentions the subject.
3. One topic query sorted by stars (`topic:cli`, `topic:image-processing`),
   which surfaces what maintainers tagged themselves. The head word is used
   when it is specific (pdf, cli, orm) and the hyphenated phrase when it is
   broad (image-processing, state-management). GitHub rejects `OR` between
   topics, so it is one per request.

Language filters use families: JavaScript includes TypeScript and Python
includes Jupyter, because many libraries moved to TypeScript.

Ranking key is `fit = score × relevance`:

- relevance is 1.0 at GitHub relevance rank 1 falling to 0.5 at rank 25,
  0.75 when found only through the topic query, 0.4 when found only in the
  stars order;
- ×0.75 when the repo names the subject only in its topic tags and ×0.5 when
  nowhere in its name, description or topics (it matched README text only);
- ×0.8 when you asked for a framework, library, parser or client and the repo
  reads like an application rather than a building block.

On the hosted service (whichlib.com), relevance instead uses a TypeSafe Jev
judgment of whether each candidate is an installable library whose main
purpose is the need (returned as `signals.jevFit`, with a weak-fit flag below
0.5) in place of the word-match factors; the npm package and own-token calls
use the word-match rules above.

Both `score` and `fit` are returned, with the relevance rank, the sources the
repo came from and the two signals, so an agent can see why.

`node mcp/eval/inspect.mjs "<need>" [language] [wanted/repo ...]` prints the
whole candidate pool for one need with these values.

## Nightly snapshot job

`whichlib/snapshot/` is a zero-dependency Node 22 script that stores the
top 100 repos for 3 periods times 9 languages into
`whichlib/data/snapshots/YYYY-MM-DD.json`. Consecutive snapshots are
what a momentum score needs.

```
cd whichlib
npm test          # 16 unit tests, no network
npm run snapshot  # about 3 minutes without a token, 1 minute with GITHUB_TOKEN
```

A GitHub Actions workflow ([`.github/workflows/snapshot.yml`](.github/workflows/snapshot.yml))
runs the job every day at 06:17 UTC and commits the result to the `data`
branch, so history accumulates without bloating `main`. Trigger it by hand
from the Actions tab or with `gh workflow run snapshot`. Bring the files down
locally with:

```
cd whichlib
npm run pull-data  # copies new snapshots from origin/data into data/snapshots/
npm run score      # now with real 7-day stars gained once there are 2+ days
```

A Windows Task Scheduler alternative is in
[`whichlib/README.md`](whichlib/README.md).

## Repository layout

```
whichlib/             the npm package: MCP server, score, dashboard, jobs, eval, tests
whichlib/dashboard/   redirect to whichlib.com/repos
site/repos/           Fresh Repos dashboard (source)
whichlib/lib/         score.js, shared by browser and Node
whichlib/mcp/         MCP server: recommend_repos, compare_repos, trending_repos
whichlib/snapshot/    snapshot job, enrichment, history builder, score report
whichlib/server.json  MCP registry manifest
telemetry/            call counter: Cloudflare Worker + Analytics Engine
RELEASING.md          release steps
```

## Roadmap

1. Done: dashboard, nightly snapshots, transparent score with tiers and
   verdicts, npm and PyPI downloads, MCP server with recommend, compare and
   trending tools.
2. Named whichlib. Package shape, `server.json` for the MCP registry and the
   call counter are ready; remaining: deploy the counter, make the
   repository public, `npm publish`, `mcp-publisher publish`, list in the
   Claude Code plugin marketplace and the awesome-mcp lists.
3. Then wait four weeks and read weekly active installs and calls per
   install. That decides whether the team tier with policy rules gets built.
4. Later: Cargo, Go and Maven adoption; release cadence in maintenance;
   downloads in the dashboard; grow the eval past 20 needs from real usage.

## License

[MIT](LICENSE).

TDQS

A4.6/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct stage of the dependency-adoption workflow: recommend_repos finds candidates from a plain-language need, compare_repos evaluates known names, and trending_repos surfaces new/rising projects. The descriptions explicitly cross-reference each other ('use compare_repos when you already have names', 'not the right tool for picking a dependency'), leaving no realistic overlap.

Naming Consistency4/5

All three names are lowercase snake_case and share the '_repos' noun suffix, which makes the set easy to scan. The only minor deviation is 'trending_repos', which uses a gerund/adjectival modifier rather than the verb_noun pattern of recommend/compare_repos.

Tool Count5/5

Three tools is exactly the right scope for a narrow library-selection server: discover, compare, and browse trends. Nothing looks padded or missing at the surface level, and each tool earns its place with a distinct workflow role.

Completeness4/5

The lifecycle (find candidates -> compare shortlist -> discover new projects) is well covered, including scoring, tiers, and registry download data. The main gap is a single-repository detail lookup: compare_repos requires 2-10 repos, so an agent cannot inspect one already-chosen dependency's full breakdown through a dedicated path.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive