opencode STATS.md 深度解析:GitHub / npm 双渠道下载统计的自动化流水线
STATS.md 是 opencode 仓库根目录下的下载量统计档案,按天记录 GitHub Release 与 npm 两个分发渠道的累计下载量及日增量,最新一行(2026-01-29)总下载量已达 10,190,453。本篇围绕这张统计表展开:先讲清表格每一列的口径与读法,再结合 统计脚本 和 定时工作流 还原"数据从哪里来、差值怎么算、文件怎么写回仓库"的完整链路,最后基于表内真实数据解读增长拐点、渠道占比变化以及缺失/重复行背后的原因。
STATS.md 的表格结构与读数口径
统计表是一张四列的 Markdown 表格,自 2025-06-29 起每天追加一行:
| 列 | 含义 | 口径说明 |
|---|---|---|
| Date | 统计日期 | 脚本执行当天,取 new Date().toISOString().split("T")[0] 的 UTC 日期 |
| GitHub Downloads | GitHub Release 资产累计下载量 | 所有历史 release 全部 asset 的 download_count 之和 |
| npm Downloads | npm 包 opencode-ai 累计下载量 |
2020-01-01 至"当前年份 + 5 年"区间的逐日下载量求和 |
| Total | 两渠道之和 | GitHub + npm |
每个数字后面括号里的增量(如 58,209 (+0)、10,190,453 (+386,234))是相对上一行的日环比变化。首日增量为 +0,因为脚本在文件不存在或解析不到上一行时把基线记为 0。
两个数字都不是"当日新增",而是累计值:GitHub 侧遍历全部历史 release 求和,npm 侧查询的也是全量历史区间,因此两列天然单调递增,日增量只反映"当天采集时点上累计值比上一次采集时点多了多少"。这一点在解读异常行(下文)时很关键。
数据采集流水线:从 API 到一行 Markdown
STATS.md 不是人工维护的,由一条 GitHub Actions 工作流每天自动驱动。整条链路涉及三个文件:工作流定义、采集脚本、以及被写入的 STATS.md。
定时触发与提交机制
工作流定义在 .github/workflows/stats.yml:
- 触发方式:
schedule使用 cron 表达式0 12 * * *,即每天 12:00 UTC 运行一次;同时开放workflow_dispatch允许手动触发。 - 仓库守卫:
if: github.repository == 'anomalyco/opencode',只在主仓库生效,fork 中不会执行。 - 并发控制:
concurrency: ${{ github.workflow }}-${{ github.ref }},同一分支上上一次运行未完成时新运行会被去重,避免同一天产生多笔重复统计(下文会出现的重复日期行就与此类机制的边界情况有关)。 - 权限:仅授予
contents: write,用于把新行提交回仓库。 - 运行环境:
blacksmith-4vcpu-ubuntu-2404自建 runner。
核心步骤只有两步:执行 bun script/stats.ts,然后以 GitHub Action 身份提交:
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add STATS.md
git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
git push
注意提交信息前缀是 ignore:,这类消息通常会被 changelog 工具自动排除,避免"每日更新统计"污染发布日志。另外 POSTHOG_KEY 以 secret 形式注入,用于脚本末尾的指标上报。
数据源一:GitHub Releases API
脚本 中 fetchReleases() 负责拉取 GitHub 侧数据:
- 分页请求
https://api.github.com/repos/anomalyco/opencode/releases?page=N&per_page=100,每页 100 条; - 每页之间
await new Promise((resolve) => setTimeout(resolve, 1000)),人为限速 1 秒,规避 API 频率限制; - 直到某一页返回空或不足 100 条为止,把所有 release 汇总返回。
随后 calculate() 遍历每个 release 的全部 assets,累加 asset.download_count 得到该 release 的下载量,再对全部 release 求和,得到 GitHub 侧累计值。这里统计的是 Release 资产下载(各平台二进制安装包等),不含 npm 渠道,也不含源码压缩包以外的其他资产来源。
数据源二:npm Downloads API
fetchNpmDownloads("opencode-ai") 调用 npm 官方统计接口:
https://api.npmjs.org/downloads/range/2020-01-01:{当前年份 + 5 年}-12-31/opencode-ai
脚本注释明确写了这样设计的原因:"Use a range from 2020 to current year + 5 years to ensure it works forever"——把结束日期设为当前年份加 5 年,保证日期范围永远是"过去到未来",脚本无需随年份维护。返回值是逐日 downloads 数组,脚本用 reduce 求和得到 opencode-ai 包的全量累计下载量。失败时仅打印 warning 并返回 0,不会中断整个流程。
写回逻辑:解析上一行、算差值、追加
save(githubTotal, npmDownloads) 是 STATS.md 表格逐行增长的直接来源,逻辑值得逐段看(见 script/stats.ts#L125-L189):
-
定位上一行:读取现有
STATS.md,从文件末尾向前逐行扫描,用正则匹配第一个数据行:/\|\s*[\d-]+\s*\|\s*([\d,]+)\s*(?:\([^)]*\))?\s*\|\s*([\d,]+)\s*(?:\([^)]*\))?\s*\|\s*([\d,]+)\s*(?:\([^)]*\))?\s*\|/正则把千分位逗号、可选的括号增量都做了容忍处理,取出 GitHub / npm / Total 三个上一期累计值;文件不存在或没有匹配时三者回退为 0。
-
计算增量:
githubChange = githubTotal - previousGithub,npm 与 total 同理。 -
格式化:累计值用
toLocaleString()加千分位;增量为正时写成(+1,338),为负时写成(-x),为 0 时写成(+0)。 -
兜底建表:若文件中找不到
# Download Stats标题,会先补上表头,再追加新行,最后跑bunx prettier --write统一表格对齐——这正是 STATS.md 里列宽整齐的原因。 -
上报:脚本末尾向 PostHog 发送两条
download事件(source: "github"与source: "npm",distinct_id固定为"download"),用于在分析平台侧观察下载趋势。没有POSTHOG_KEY时仅打印 warning 并跳过。
这套"末尾扫描 + 正则解析"的设计是刻意的脆弱-鲁权:它不依赖数据库或临时文件,只依赖 STATS.md 自身作为状态存储,即使 workflow 重跑、环境重建也能延续序列。
数据解读:增长曲线、渠道拐点与异常行
基于 STATS.md 现有 200 余行数据(2025-06-29 至 2026-01-29),可以读出几个有依据的事实性结论。
关键里程碑
| 日期 | Total | 事件 |
|---|---|---|
| 2025-10-19 | 1,005,287 | 总下载量首次破 100 万 |
| 2025-12-10 | 2,017,599 | 首次破 200 万 |
| 2026-01-14 | 5,214,290 | 首次破 500 万 |
| 2026-01-29 | 10,190,453 | 首次破 1000 万(表内最后一行) |
从 100 万到 200 万用了约 52 天,而从 500 万到 1000 万只用了 15 天(2026-01-14 到 2026-01-29),增长显著加速。单渠道最大单日增量出现在 2026-01-16:GitHub 单日 +552,622,Total 单日 +661,678,是表内最高峰。
渠道结构:GitHub 在 2026-01 反超 npm
表内两列的相对位置随时间发生了一次明确翻转:
- 2026-01-05 及以前,npm 列始终大于 GitHub 列(如 2026-01-05:GitHub 1,738,171 < npm 1,353,043 不成立除外,实际 2026-01-05 为 GitHub 1,738,171 / npm 1,353,043,GitHub 已略高)。以 2026-01-06 为表内首个明确反超日:GitHub 1,960,988(单日 +222,817)对比 npm 1,377,377(+24,334),此后再未回落。
- 到 2026-01-29,GitHub 渠道累计 7,815,471,占比约 77%;npm 渠道 2,374,982,占比约 23%。
从渠道含义看,这反映安装方式向直接下载 Release 二进制(各平台原生安装包)倾斜;具体原因(发布节奏、安装引导变化等)仓库内没有直接记录,此处不下结论。
异常行:缺失日期、重复行与 +0
统计表本身就是"流水线运行日志",几处异常恰好印证了上文机制:
- 缺失 2025-07-07:表内从 07-06 直接跳到 07-08,说明当天 12:00 UTC 的运行未产生提交(当天未执行或被并发控制丢弃)。
- 缺失 2025-10-13:10-12 到 10-14 之间无行,且 10-14 的增量是 +13,005/+10,541,接近两个日期的累计量,符合"补记"特征。
- 2025-08-27 / 08-28 npm 列 +0:GitHub 列照常增长而 npm 列纹丝不动,可推断这两天 npm 统计接口返回的累计值与前一天相同(npm 官方计数存在延迟或波动),并非脚本故障——脚本对接口失败会返回 0 并打 warning,而 0 与前一值相减不会出现 +0 且总量仍在增长的情况。
- 2025-10-30 出现两行:同日两行分别为 (613,746 / 542,064) 与 (617,846 / 555,026),是当天两次执行(例如手动
workflow_dispatch叠加定时触发,或重试)各追加了一行;由于脚本总是"追加"而非"覆盖当天行",重复执行会留下双行。这也解释了为什么第二行的增量是相对第一行算的。
这些异常不影响累计值的单调可信性,但提醒读者:STATS.md 的每一行对应"某次采集时点",而非严格的自然日快照。
延伸:与 packages/stats 统计站点的区别
仓库中另有一个同名易混的模块 packages/stats,它是独立的统计站点(SolidStart 前端 + Effect/Drizzle 数据层 + Lambda),面向的是推理请求、token 用量、成本等运行指标数据,由 infra/stats.ts 部署到 stats. 域名下,与本文的"下载量统计"是两套互不相关的体系:
- 本文的 STATS.md 链路:GitHub Releases API + npm API →
script/stats.ts→ 根目录 Markdown 表格; packages/stats站点链路:Iceberg 事件表(inference.event)→stat-sync守护进程每小时聚合 → PlanetScale 数据库 → SolidStart 站点。
从 packages/stats/server 的同步守护进程 可以看到其"每日一次全量刷新 + 每小时增量"的策略,但那属于运行指标域,与下载统计无关,读者如需运行统计站点可参考其 AGENTS.md:在仓库根目录执行 bun dev:stats 即可本地启动。
小结
- STATS.md 的四个数字口径:GitHub 全 release 资产累计下载、npm
opencode-ai全量累计下载(2020 起)、两者之和、以及相对上一采集时点的日增量。 - 生成链路完全自动化:
.github/workflows/stats.yml每天 12:00 UTC 触发 →script/stats.ts分页拉取 GitHub Releases(每页 100 条、间隔 1 秒)并查询 npm range API → 从文件末尾正则解析上一行 → 追加新行 → prettier 格式化 → 以ignore:前缀提交并推送,同时向 PostHog 上报两条download事件。 - 数据本身可读性良好:总下载量从 2025-10-19 破百万到 2026-01-29 破一千万;2026 年 1 月中旬 GitHub 渠道日增量多次超过 npm 数个量级,渠道占比翻转至约 77% / 23%;缺失日、+0 日与重复日都是流水线时点行为的直接记录,可按上文机制逐条归因。
- 复用这套模式时,关键设计点是"用被统计文件自身作为状态存储":只解析最后一行即可恢复基线,无需额外基础设施,代价是异常行(重复/缺失)会留在文件中作为历史痕迹。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00