eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1#!/usr/bin/env node
2// πŸ€– Refresh the public contributors index (web/status/contributors.json)
3// from GitHub + git history.
4//
5// Humans come from the GitHub contributors API (commit counts, avatars,
6// profile links β€” needs `gh` authenticated for this private repo). AI
7// contributors come from Co-Authored-By trailers with the anthropic email β€”
8// this codebase is AI-written, so the models sit in the index alongside the
9// humans, on the same metric.
10//
11// node tools/contributors.mjs # print the would-be JSON (dry run)
12// node tools/contributors.mjs --write # rewrite web/status/contributors.json
13//
14// The JSON is COMMITTED (like carbon.json) and republished as-is by
15// deploy/status-publish.sh. Stats fields (commits, url, avatar) regenerate on
16// every run; the human-owned fields are PRESERVED:
17// bio β€” seeded from the GitHub profile bio on first appearance, then
18// yours: customise it by editing your entry in a PR
19// name β€” display name, same rule
20// hidden β€” set true to stay out of the published index entirely
21// Avatars are embedded as data URIs (the status pages are self-contained β€”
22// no external requests), fetched small (s=96).
23import { readFileSync, writeFileSync, existsSync } from "node:fs";
24import { execSync } from "node:child_process";
25import { join } from "node:path";
26
27const write = process.argv.includes("--write");
28const sh = (cmd) => execSync(cmd, { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
29
30const jsonPath = join(process.cwd(), "web/status/contributors.json");
31const prev = existsSync(jsonPath) ? JSON.parse(readFileSync(jsonPath, "utf8")) : { contributors: [] };
32const prevById = new Map(prev.contributors.map((c) => [c.id, c]));
33
34const REPO = "elevenmessenger/messenger";
35const contributors = [];
36
37// ---- humans: GitHub contributors API -----------------------------------------
38const ghContribs = JSON.parse(sh(`gh api repos/${REPO}/contributors`));
39for (const g of ghContribs) {
40 const old = prevById.get(g.login) || {};
41 const profile = JSON.parse(sh(`gh api users/${g.login}`));
42 // Avatar as a data URI so the page stays self-contained (no external hosts).
43 const png = sh(`curl -sL "${g.avatar_url}&s=96" | base64`).replace(/\n/g, "");
44 contributors.push({
45 id: g.login,
46 kind: "human",
47 name: old.name ?? profile.name ?? g.login,
48 bio: old.bio ?? profile.bio ?? "",
49 commits: g.contributions,
50 url: g.html_url,
51 avatar: "data:image/png;base64," + png,
52 ...(old.hidden ? { hidden: true } : {}),
53 });
54}
55
56// ---- AI: Co-Authored-By trailers (anthropic email only β€” human co-author
57// trailers are already counted as commit authors above) ------------------------
58const trailerCounts = new Map();
59for (const line of sh(`git log --format='%(trailers:key=Co-Authored-By,valueonly)'`).split("\n")) {
60 const m = line.match(/^(.+?) <([^>]+)>\s*$/);
61 if (!m || !m[2].endsWith("@anthropic.com")) continue;
62 const name = m[1].replace(/\s*\(1M context\)\s*/, "").trim();
63 trailerCounts.set(name, (trailerCounts.get(name) || 0) + 1);
64}
65for (const [name, commits] of trailerCounts) {
66 const id = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
67 const old = prevById.get(id) || {};
68 contributors.push({
69 id,
70 kind: "ai",
71 name: old.name ?? name,
72 bio: old.bio ?? "",
73 commits,
74 ...(old.hidden ? { hidden: true } : {}),
75 });
76}
77
78contributors.sort((a, b) => b.commits - a.commits);
79const out = {
80 updated_at: new Date().toISOString(),
81 repo: REPO,
82 metric: "commits to main β€” humans as commit authors (GitHub), AI models as Co-Authored-By trailers (git log)",
83 contributors,
84};
85
86if (write) {
87 writeFileSync(jsonPath, JSON.stringify(out, null, 2) + "\n");
88 console.error("wrote " + jsonPath + " β€” " + contributors.length + " contributors (" +
89 contributors.filter((c) => c.kind === "human").length + " human, " +
90 contributors.filter((c) => c.kind === "ai").length + " ai)");
91} else {
92 // Dry run: avatars elided for readability.
93 console.log(JSON.stringify({ ...out, contributors: contributors.map((c) => ({ ...c, avatar: c.avatar ? "<data-uri>" : undefined })) }, null, 2));
94 console.error("dry run β€” pass --write to rewrite " + jsonPath);
95}
96