|
#!/usr/bin/env bun |
|
/** |
|
* Fetch GitHub star counts for agents listed in vercel-labs/skills |
|
* https://github.com/vercel-labs/skills?tab=readme-ov-file#supported-agents |
|
* Sorted descending by stars. |
|
* |
|
* Usage: |
|
* bun scripts/agent-stars.ts |
|
* GITHUB_TOKEN=ghp_xxx bun scripts/agent-stars.ts |
|
*/ |
|
|
|
type AgentEntry = |
|
| { kind: "open"; repo: string } |
|
| { kind: "closed"; vendor: string } |
|
| { kind: "unknown" }; |
|
|
|
// Agent display name -> source classification |
|
const AGENTS: Record<string, AgentEntry> = { |
|
Amp: { kind: "closed", vendor: "Sourcegraph" }, |
|
Antigravity: { kind: "closed", vendor: "Google DeepMind" }, |
|
Augment: { kind: "closed", vendor: "Augment" }, |
|
"Claude Code": { kind: "open", repo: "anthropics/claude-code" }, |
|
Cline: { kind: "open", repo: "cline/cline" }, |
|
CodeBuddy: { kind: "closed", vendor: "Tencent" }, |
|
Codex: { kind: "open", repo: "openai/codex" }, |
|
"Command Code": { kind: "closed", vendor: "CommandCodeAI" }, |
|
Continue: { kind: "open", repo: "continuedev/continue" }, |
|
"Cortex Code": { kind: "closed", vendor: "Snowflake" }, |
|
Crush: { kind: "open", repo: "charmbracelet/crush" }, |
|
Cursor: { kind: "closed", vendor: "Anysphere" }, |
|
"Deep Agents": { kind: "open", repo: "langchain-ai/deepagents" }, |
|
Droid: { kind: "closed", vendor: "Factory AI" }, |
|
"Gemini CLI": { kind: "open", repo: "google-gemini/gemini-cli" }, |
|
"GitHub Copilot": { kind: "closed", vendor: "Microsoft" }, |
|
Goose: { kind: "open", repo: "block/goose" }, |
|
iFlow: { kind: "open", repo: "iflow-ai/iflow-cli"}, |
|
Junie: { kind: "closed", vendor: "JetBrains" }, |
|
KiloCode: { kind: "open", repo: "Kilo-Org/kilocode" }, |
|
"Kimi Code CLI": { kind: "closed", vendor: "Moonshot AI" }, |
|
Kiro: { kind: "closed", vendor: "Amazon" }, |
|
Kode: { kind: "open", repo: "shareAI-lab/Kode-Agent" }, |
|
MCPJam: { kind: "open", repo: "MCPJam/inspector" }, |
|
Mux: { kind: "unknown" }, |
|
Neovate: { kind: "unknown" }, |
|
OpenCode: { kind: "open", repo: "anomalyco/opencode" }, |
|
OpenClaw: { kind: "open", repo: "openclaw/openclaw" }, |
|
OpenHands: { kind: "open", repo: "All-Hands-AI/OpenHands" }, |
|
Pi: { kind: "open", repo: "badlogic/pi-mono" }, |
|
Pochi: { kind: "closed", vendor: "TabbyML" }, |
|
Qoder: { kind: "closed", vendor: "Qoder" }, |
|
Qwen: { kind: "open", repo: "QwenLM/qwen-code" }, |
|
Replit: { kind: "closed", vendor: "Replit" }, |
|
Roo: { kind: "open", repo: "RooVetGit/Roo-Code" }, |
|
ADAL: { kind: "closed", vendor: "ADAL" }, |
|
Trae: { kind: "open", repo: "bytedance/trae-agent" }, |
|
Vibe: { kind: "unknown" }, |
|
Warp: { kind: "open", repo: "warpdotdev/Warp" }, |
|
Windsurf: { kind: "closed", vendor: "Codeium" }, |
|
Zed: { kind: "open", repo: "zed-industries/zed" }, |
|
Zencoder: { kind: "closed", vendor: "Zencoder" }, |
|
}; |
|
|
|
interface OpenResult { |
|
agent: string; |
|
repo: string; |
|
stars: number; |
|
} |
|
|
|
interface ClosedEntry { |
|
agent: string; |
|
vendor: string; |
|
} |
|
|
|
async function fetchStars(repo: string, token?: string): Promise<number | null> { |
|
const headers: Record<string, string> = { |
|
Accept: "application/vnd.github+json", |
|
"User-Agent": "agent-stars-script", |
|
"X-GitHub-Api-Version": "2022-11-28", |
|
}; |
|
if (token) headers["Authorization"] = `Bearer ${token}`; |
|
|
|
const res = await fetch(`https://api.github.com/repos/${repo}`, { headers }); |
|
if (res.status === 404) return null; |
|
if (!res.ok) { |
|
console.error(` HTTP ${res.status} for ${repo}`); |
|
return null; |
|
} |
|
const data = (await res.json()) as { stargazers_count: number }; |
|
return data.stargazers_count; |
|
} |
|
|
|
function consoleTable(headers: string[], rows: string[][], widths: number[]): void { |
|
const sep = widths.map((w) => "-".repeat(w)).join("-+-"); |
|
console.log(headers.map((h, i) => h.padEnd(widths[i])).join(" | ")); |
|
console.log(sep); |
|
for (const row of rows) { |
|
console.log(row.map((cell, i) => cell.padEnd(widths[i])).join(" | ")); |
|
} |
|
} |
|
|
|
function mdTable(headers: string[], rows: string[][]): string { |
|
const header = `| ${headers.join(" | ")} |`; |
|
const sep = `| ${headers.map(() => "---").join(" | ")} |`; |
|
const body = rows.map((row) => `| ${row.join(" | ")} |`).join("\n"); |
|
return [header, sep, body].join("\n"); |
|
} |
|
|
|
function buildMarkdown(open: OpenResult[], closed: ClosedEntry[], unknown: string[]): string { |
|
const date = new Date().toISOString().slice(0, 10); |
|
const lines: string[] = [ |
|
"# Agent GitHub Stars", |
|
"", |
|
`> Generated on ${date} from [vercel-labs/skills](https://github.com/vercel-labs/skills?tab=readme-ov-file#supported-agents)`, |
|
"", |
|
"## Open Source Agents", |
|
"", |
|
mdTable( |
|
["#", "Agent", "Stars", "GitHub Repo"], |
|
open.map(({ agent, repo, stars }, i) => [ |
|
String(i + 1), |
|
agent, |
|
stars.toLocaleString(), |
|
`[${repo}](https://github.com/${repo})`, |
|
]), |
|
), |
|
"", |
|
"## Closed Source Agents", |
|
"", |
|
mdTable( |
|
["Agent", "Vendor"], |
|
closed.map(({ agent, vendor }) => [agent, vendor]), |
|
), |
|
]; |
|
|
|
if (unknown.length > 0) { |
|
lines.push("", "## Unknown / No Repo Found", "", unknown.map((a) => `- ${a}`).join("\n")); |
|
} |
|
|
|
return lines.join("\n") + "\n"; |
|
} |
|
|
|
async function main(): Promise<void> { |
|
const token = process.env.GITHUB_TOKEN; |
|
if (!token) console.log("Tip: set GITHUB_TOKEN to avoid rate limiting\n"); |
|
|
|
// Parse --output [file] flag |
|
const outputFlagIdx = process.argv.indexOf("--output"); |
|
const outputPath: string | null = |
|
outputFlagIdx !== -1 |
|
? (process.argv[outputFlagIdx + 1] ?? "agent-stars.md") |
|
: null; |
|
|
|
const open: OpenResult[] = []; |
|
const closed: ClosedEntry[] = []; |
|
const unknown: string[] = []; |
|
|
|
await Promise.all( |
|
Object.entries(AGENTS).map(async ([agent, entry]) => { |
|
if (entry.kind === "closed") { |
|
closed.push({ agent, vendor: entry.vendor }); |
|
return; |
|
} |
|
if (entry.kind === "unknown") { |
|
unknown.push(agent); |
|
return; |
|
} |
|
const stars = await fetchStars(entry.repo, token); |
|
if (stars === null) { |
|
unknown.push(agent); |
|
} else { |
|
open.push({ agent, repo: entry.repo, stars }); |
|
} |
|
}), |
|
); |
|
|
|
open.sort((a, b) => b.stars - a.stars); |
|
closed.sort((a, b) => a.agent.localeCompare(b.agent)); |
|
unknown.sort(); |
|
|
|
// ── Console output ─────────────────────────────────────────────── |
|
console.log("\n## Open Source Agents (sorted by ⭐)\n"); |
|
consoleTable( |
|
["#", "Agent", "Stars", "GitHub Repo"], |
|
open.map(({ agent, repo, stars }, i) => [ |
|
String(i + 1), |
|
agent, |
|
stars.toLocaleString(), |
|
`https://github.com/${repo}`, |
|
]), |
|
[3, 20, 10, 45], |
|
); |
|
|
|
console.log("\n## Closed Source Agents\n"); |
|
consoleTable( |
|
["Agent", "Vendor"], |
|
closed.map(({ agent, vendor }) => [agent, vendor]), |
|
[20, 25], |
|
); |
|
|
|
if (unknown.length > 0) { |
|
console.log("\n## Unknown / No Repo Found\n"); |
|
console.log(unknown.join(", ")); |
|
} |
|
|
|
// ── Markdown file (optional) ───────────────────────────────────── |
|
if (outputPath) { |
|
await Bun.write(outputPath, buildMarkdown(open, closed, unknown)); |
|
console.log(`\nMarkdown written to ${outputPath}`); |
|
} |
|
} |
|
|
|
main().catch((err: unknown) => { |
|
console.error(err); |
|
process.exit(1); |
|
}); |