Skip to content

Commit d858a2b

Browse files
committed
feat: weekly traffic snapshot into stats/ (survives the 14-day API window)
1 parent 5528eb9 commit d858a2b

6 files changed

Lines changed: 191 additions & 0 deletions

File tree

.github/workflows/traffic.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: traffic
2+
3+
# Persists the GitHub traffic API's 14-day rolling window into stats/.
4+
# Weekly is enough (2x margin on the window). The TRAFFIC_TOKEN secret must
5+
# be a fine-grained PAT with repository Administration read permission:
6+
# the traffic endpoints reject the default Actions GITHUB_TOKEN.
7+
8+
on:
9+
schedule:
10+
- cron: "0 6 * * 1"
11+
workflow_dispatch:
12+
13+
permissions:
14+
contents: write
15+
16+
jobs:
17+
snapshot:
18+
runs-on: ubuntu-latest
19+
timeout-minutes: 5
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Snapshot traffic API into stats/
24+
env:
25+
GH_TOKEN: ${{ secrets.TRAFFIC_TOKEN }}
26+
run: |
27+
[ -n "$GH_TOKEN" ] || { echo "TRAFFIC_TOKEN secret is missing (fine-grained PAT, Administration: read)"; exit 1; }
28+
bash scripts/traffic-snapshot.sh
29+
30+
- name: Commit snapshot
31+
run: |
32+
git config user.name "github-actions[bot]"
33+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
34+
git add stats/
35+
git diff --cached --quiet || git commit -m "chore: weekly traffic snapshot"
36+
git push

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## 2026-07-19
44

5+
### feat: weekly traffic snapshot (stats/)
6+
7+
GitHub's traffic API only keeps a 14-day rolling window, so `scripts/traffic-snapshot.sh` merges per-day views/clones into `stats/traffic.json` (latest fetch wins on overlapping days) and appends dated referrer/path snapshots as JSONL. Runs Mondays via `.github/workflows/traffic.yml` (workflow_dispatch too); needs a `TRAFFIC_TOKEN` fine-grained PAT secret with repository Administration read, because the traffic endpoints reject the default Actions token. Seeded with the current window: unique cloners ≈ real installs (install.sh and npx both end in `git clone`; updates are `git pull` and don't count).
8+
59
### feat: `--segment` mode for embedding in other status lines
610

711
`statusline.sh --segment` prints only the cost + CO2 pair (`$0.68 · 35g CO₂`) and exits before the progress bar, the 5h-quota lookup (so never any network call) and the git branch call. Built for [ccstatusline](https://github.com/sirmalloc/ccstatusline) custom command widgets, which pass the same status JSON on stdin; documented in the README ("Using with ccstatusline"). Full mode output is byte-identical to before.

scripts/traffic-snapshot.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env bash
2+
# traffic-snapshot.sh — Persist GitHub traffic data (14-day rolling window)
3+
# into stats/, merging per-day entries so the series survives the window.
4+
# Runs weekly via .github/workflows/traffic.yml; can be run locally too
5+
# (uses your gh auth; in Actions, GH_TOKEN must be a PAT with
6+
# repository Administration read permission - the traffic API requires it).
7+
set -euo pipefail
8+
9+
REPO="${TRAFFIC_REPO:-gwittebolle/claude-carbon}"
10+
STATS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/stats"
11+
mkdir -p "$STATS_DIR"
12+
FILE="$STATS_DIR/traffic.json"
13+
[ -f "$FILE" ] || echo '{"views":{},"clones":{}}' > "$FILE"
14+
15+
VIEWS="$(gh api "repos/$REPO/traffic/views")"
16+
CLONES="$(gh api "repos/$REPO/traffic/clones")"
17+
18+
# Upsert per-day entries; the latest fetch wins on overlapping days, so a
19+
# partially-counted current day gets corrected by the next run.
20+
jq --argjson v "$VIEWS" --argjson c "$CLONES" '
21+
.views = (.views + ($v.views | map({(.timestamp[:10]): {count: .count, uniques: .uniques}}) | add // {}))
22+
| .clones = (.clones + ($c.clones | map({(.timestamp[:10]): {count: .count, uniques: .uniques}}) | add // {}))
23+
' "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
24+
25+
# Referrers and popular paths have no per-day breakdown; store dated
26+
# snapshots of the whole 14-day window instead.
27+
DATE="$(date -u +%F)"
28+
gh api "repos/$REPO/traffic/popular/referrers" | jq -c --arg d "$DATE" '{date: $d, referrers: .}' >> "$STATS_DIR/referrers.jsonl"
29+
gh api "repos/$REPO/traffic/popular/paths" | jq -c --arg d "$DATE" '{date: $d, paths: .}' >> "$STATS_DIR/paths.jsonl"
30+
31+
echo "Snapshot done: $(jq '.views | length' "$FILE") days of views, $(jq '.clones | length' "$FILE") days of clones."

stats/paths.jsonl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"date":"2026-07-19","paths":[{"path":"/gwittebolle/claude-carbon","title":"Overview","count":214,"uniques":146},{"path":"/gwittebolle/claude-carbon/issues","title":"/issues","count":12,"uniques":9},{"path":"/gwittebolle/claude-carbon/blob/main/README.md","title":"/blob/main/README.md","count":12,"uniques":6},{"path":"/gwittebolle/claude-carbon/blob/main/install.sh","title":"/blob/main/install.sh","count":6,"uniques":6},{"path":"/gwittebolle/claude-carbon/blob/main/METHODOLOGY.md","title":"/blob/main/METHODOLOGY.md","count":6,"uniques":4},{"path":"/gwittebolle/claude-carbon/tree/main/skills","title":"/tree/main/skills","count":5,"uniques":3},{"path":"/gwittebolle/claude-carbon/tree/main/.claude-plugin","title":"/tree/main/.claude-plugin","count":4,"uniques":3},{"path":"/gwittebolle/claude-carbon/blob/main/docs/example-report-v2.png","title":"/blob/main/docs/example-report-v2.png","count":3,"uniques":3},{"path":"/gwittebolle/claude-carbon/commits","title":"/commits","count":3,"uniques":3},{"path":"/gwittebolle/claude-carbon/tree/main/skills/carbon-card","title":"/tree/main/skills/carbon-card","count":3,"uniques":3}]}

stats/referrers.jsonl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"date":"2026-07-19","referrers":[{"referrer":"Google","count":63,"uniques":38},{"referrer":"Bing","count":14,"uniques":3},{"referrer":"heise.de","count":13,"uniques":9},{"referrer":"DuckDuckGo","count":10,"uniques":5},{"referrer":"teams.public.onecdn.static.microsoft","count":10,"uniques":2},{"referrer":"reddit.com","count":9,"uniques":9},{"referrer":"linkedin.com","count":7,"uniques":5},{"referrer":"claude.ai","count":7,"uniques":2},{"referrer":"kagi.com","count":5,"uniques":3},{"referrer":"statics.teams.cdn.office.net","count":4,"uniques":4}]}

stats/traffic.json

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
{
2+
"views": {
3+
"2026-07-05": {
4+
"count": 6,
5+
"uniques": 5
6+
},
7+
"2026-07-06": {
8+
"count": 26,
9+
"uniques": 20
10+
},
11+
"2026-07-07": {
12+
"count": 23,
13+
"uniques": 21
14+
},
15+
"2026-07-08": {
16+
"count": 34,
17+
"uniques": 32
18+
},
19+
"2026-07-09": {
20+
"count": 26,
21+
"uniques": 19
22+
},
23+
"2026-07-10": {
24+
"count": 31,
25+
"uniques": 19
26+
},
27+
"2026-07-11": {
28+
"count": 8,
29+
"uniques": 4
30+
},
31+
"2026-07-12": {
32+
"count": 9,
33+
"uniques": 7
34+
},
35+
"2026-07-13": {
36+
"count": 18,
37+
"uniques": 14
38+
},
39+
"2026-07-14": {
40+
"count": 13,
41+
"uniques": 9
42+
},
43+
"2026-07-15": {
44+
"count": 25,
45+
"uniques": 19
46+
},
47+
"2026-07-16": {
48+
"count": 39,
49+
"uniques": 21
50+
},
51+
"2026-07-17": {
52+
"count": 39,
53+
"uniques": 15
54+
},
55+
"2026-07-18": {
56+
"count": 6,
57+
"uniques": 5
58+
}
59+
},
60+
"clones": {
61+
"2026-07-05": {
62+
"count": 8,
63+
"uniques": 6
64+
},
65+
"2026-07-06": {
66+
"count": 9,
67+
"uniques": 8
68+
},
69+
"2026-07-07": {
70+
"count": 22,
71+
"uniques": 18
72+
},
73+
"2026-07-08": {
74+
"count": 32,
75+
"uniques": 14
76+
},
77+
"2026-07-09": {
78+
"count": 11,
79+
"uniques": 8
80+
},
81+
"2026-07-10": {
82+
"count": 13,
83+
"uniques": 10
84+
},
85+
"2026-07-11": {
86+
"count": 6,
87+
"uniques": 6
88+
},
89+
"2026-07-12": {
90+
"count": 3,
91+
"uniques": 3
92+
},
93+
"2026-07-13": {
94+
"count": 15,
95+
"uniques": 11
96+
},
97+
"2026-07-14": {
98+
"count": 9,
99+
"uniques": 6
100+
},
101+
"2026-07-15": {
102+
"count": 38,
103+
"uniques": 21
104+
},
105+
"2026-07-16": {
106+
"count": 28,
107+
"uniques": 24
108+
},
109+
"2026-07-17": {
110+
"count": 6,
111+
"uniques": 6
112+
},
113+
"2026-07-18": {
114+
"count": 5,
115+
"uniques": 4
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)