---
name: download-stats
description: Generate download-stats CSVs and a chart for nub across its distribution channels (npm + GitHub release assets, which subsume Homebrew). Invoke (via the Skill tool) when asked to check, chart, or export download numbers, install counts, or adoption stats. Encodes the channel map, which numbers may be added together, each channel's real granularity and lag, and the checksum-asset inflation gotcha.
metadata:
internal: true
---
# download-stats — pull nub's download numbers into CSVs and a chart
One command does everything:
```sh
node scripts/download-stats.mjs # npm from the API, GitHub from the ledger branch, then CSVs + chart
node scripts/download-stats.mjs --npm-only # skip the gh-authenticated GitHub half
node scripts/download-stats.mjs --chart-only # re-render from the CSVs on disk, no network
node scripts/download-stats.mjs --no-chart --out
--package @nubjs/nub --repo nubjs/nub
```
Output lands in `tmp/download-stats/` (gitignored — derived data, regenerated on demand):
| File | What it holds |
|---|---|
| `downloads-weekly.csv` | **The clean series.** `week_start,week_end,days,complete,npm,github,total` — one row per UTC Monday week. `github` fills only when snapshot intervals cover all seven days; `total` also needs the npm week complete. |
| `npm-weekly.csv` | The npm half by UTC Monday week, with a `gaps` column and `platforms_total` for reference. |
| `npm-monthly.csv` | Calendar months, with `expected_days`, `gaps`, and the current month's `projected_remainder`/`projected_total`. |
| `npm-daily.csv` | One row per day since first publish: the meta package, each platform package, `platforms_total`, running cumulative. |
| `github-releases.csv` | Per-release, per-asset cumulative counters, one set per snapshot date — a flattened export of the ledger, regenerated each run. |
| `github-intervals.csv` | Written once two or more snapshots exist — the deltas between them, with a `per_day` rate. Intervals, NOT weeks: a snapshot delta ignores week boundaries. |
| `summary.json` | Machine-readable totals, the platform split, and the caveats as data. |
| `downloads.html` | Self-contained page — no deps, no CDN, opens from disk. Hero total, an all-time source split, and one stacked chart (npm below, GitHub above) toggling weekly/monthly. |
Pure aggregation logic lives in `scripts/lib/download-stats.mjs` and is tested in `scripts/download-stats.test.mjs`; the renderer is `scripts/lib/download-chart.mjs`.
The page draws COMPLETE PERIODS ONLY. A part-period bar is not a small period, it is an unfinished one, and drawing it puts a fake cliff at the right edge; the month in progress is the single exception, shown at its full projected height with the forecast hatched so the bar still spans a real month. Bars stack by SOURCE — the only split that can be stacked, since the npm platform packages are pulled BY an npm install and stacking them would draw the same install twice.
Two renderer traps, both of which shipped a silently-wrong chart before they were caught by magnifying a screenshot rather than by reading the code:
- **A CSS custom property does not resolve inside `` content** — the tile is a paint resource, not part of the rendered tree, so `var(--x)` there paints nothing. Emit literal hex per mode and select with a CSS `fill:url(#id)` rule on the referencing element.
- **A paint server inside a `display:none` subtree is dead.** The weekly and monthly charts are both in the document with one hidden, so a `` emitted per chart gives duplicate ids where `url()` takes the first — which is whichever chart the toggle just hid. Define patterns ONCE in a zero-sized but never-hidden `