mirror of
https://github.com/tiennm99/awesome-coding-agents.git
synced 2026-07-20 04:17:13 +00:00
feat: initial scaffold of awesome-coding-agents tracker
Go updater that fetches AI agent coding tool repo stats via GitHub GraphQL (batched, one query), sorts by star count, appends a daily snapshot to data/history.jsonl, and regenerates README.md from templates/readme.tmpl. Daily workflow at .github/workflows/update.yml refreshes rankings and commits changes. Seed list in data/agents.yml covers 19 tracked repos.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Stat struct {
|
||||
Owner string
|
||||
Repo string
|
||||
Category string
|
||||
Notes string
|
||||
Description string
|
||||
Stars int
|
||||
Language string
|
||||
PushedAt time.Time
|
||||
URL string
|
||||
NameWithOwner string
|
||||
}
|
||||
|
||||
type repoNode struct {
|
||||
StargazerCount int `json:"stargazerCount"`
|
||||
Description string `json:"description"`
|
||||
PrimaryLanguage *struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"primaryLanguage"`
|
||||
PushedAt time.Time `json:"pushedAt"`
|
||||
URL string `json:"url"`
|
||||
NameWithOwner string `json:"nameWithOwner"`
|
||||
}
|
||||
|
||||
type graphQLResponse struct {
|
||||
Data map[string]*repoNode `json:"data"`
|
||||
Errors []struct {
|
||||
Message string `json:"message"`
|
||||
Path []any `json:"path"`
|
||||
} `json:"errors"`
|
||||
}
|
||||
|
||||
const repoFields = `
|
||||
stargazerCount
|
||||
description
|
||||
primaryLanguage { name }
|
||||
pushedAt
|
||||
url
|
||||
nameWithOwner
|
||||
`
|
||||
|
||||
func fetchStats(token string, agents []Agent) ([]Stat, error) {
|
||||
var b strings.Builder
|
||||
b.WriteString("query {\n")
|
||||
for i, a := range agents {
|
||||
fmt.Fprintf(&b, " r%d: repository(owner: %q, name: %q) {%s}\n", i, a.Owner, a.Repo, repoFields)
|
||||
}
|
||||
b.WriteString("}\n")
|
||||
|
||||
body, err := json.Marshal(map[string]string{"query": b.String()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://api.github.com/graphql", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "awesome-coding-agents-updater")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("graphql HTTP %d: %s", resp.StatusCode, raw)
|
||||
}
|
||||
|
||||
var out graphQLResponse
|
||||
if err := json.Unmarshal(raw, &out); err != nil {
|
||||
return nil, fmt.Errorf("decode response: %w (body=%s)", err, raw)
|
||||
}
|
||||
for _, e := range out.Errors {
|
||||
// repos that 404 or are renamed land here; continue with partial data
|
||||
fmt.Fprintf(os.Stderr, "graphql warn: %s (path=%v)\n", e.Message, e.Path)
|
||||
}
|
||||
|
||||
stats := make([]Stat, 0, len(agents))
|
||||
for i, a := range agents {
|
||||
node := out.Data[fmt.Sprintf("r%d", i)]
|
||||
if node == nil {
|
||||
fmt.Fprintf(os.Stderr, "skip %s/%s — no data\n", a.Owner, a.Repo)
|
||||
continue
|
||||
}
|
||||
lang := ""
|
||||
if node.PrimaryLanguage != nil {
|
||||
lang = node.PrimaryLanguage.Name
|
||||
}
|
||||
stats = append(stats, Stat{
|
||||
Owner: a.Owner,
|
||||
Repo: a.Repo,
|
||||
Category: a.Category,
|
||||
Notes: a.Notes,
|
||||
Description: node.Description,
|
||||
Stars: node.StargazerCount,
|
||||
Language: lang,
|
||||
PushedAt: node.PushedAt,
|
||||
URL: node.URL,
|
||||
NameWithOwner: node.NameWithOwner,
|
||||
})
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func sortByStars(stats []Stat) {
|
||||
sort.SliceStable(stats, func(i, j int) bool {
|
||||
return stats[i].Stars > stats[j].Stars
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user