diff --git a/.github/repository-metrics-traffic.json b/.github/repository-metrics-traffic.json
index 2211b7d..2ed149a 100644
--- a/.github/repository-metrics-traffic.json
+++ b/.github/repository-metrics-traffic.json
@@ -3,5 +3,63 @@
"views_14d": 0,
"unique_cloners_14d": 11,
"clones_14d": 12,
- "traffic_as_of": "2026-08-10T00:00:00Z"
+ "traffic_as_of": "2026-08-10T00:00:00Z",
+ "clone_series_14d": [
+ {
+ "count": 0,
+ "timestamp": "2026-07-27T00:00:00Z"
+ },
+ {
+ "count": 9,
+ "timestamp": "2026-07-28T00:00:00Z"
+ },
+ {
+ "count": 1,
+ "timestamp": "2026-07-29T00:00:00Z"
+ },
+ {
+ "count": 1,
+ "timestamp": "2026-07-30T00:00:00Z"
+ },
+ {
+ "count": 0,
+ "timestamp": "2026-07-31T00:00:00Z"
+ },
+ {
+ "count": 0,
+ "timestamp": "2026-08-01T00:00:00Z"
+ },
+ {
+ "count": 0,
+ "timestamp": "2026-08-02T00:00:00Z"
+ },
+ {
+ "count": 0,
+ "timestamp": "2026-08-03T00:00:00Z"
+ },
+ {
+ "count": 1,
+ "timestamp": "2026-08-04T00:00:00Z"
+ },
+ {
+ "count": 0,
+ "timestamp": "2026-08-05T00:00:00Z"
+ },
+ {
+ "count": 0,
+ "timestamp": "2026-08-06T00:00:00Z"
+ },
+ {
+ "count": 0,
+ "timestamp": "2026-08-07T00:00:00Z"
+ },
+ {
+ "count": 0,
+ "timestamp": "2026-08-08T00:00:00Z"
+ },
+ {
+ "count": 0,
+ "timestamp": "2026-08-09T00:00:00Z"
+ }
+ ]
}
diff --git a/README.md b/README.md
index b6513f6..72bf9b3 100644
--- a/README.md
+++ b/README.md
@@ -48,28 +48,6 @@ credential store, and deterministic per-agent projections.
> and [release checksums](https://github.com/JNHFlow21/agent-switch/releases/tag/v0.2.0)
> before installing.
-
-## Project activity
-
-| Public signal | Live or latest owner-visible value |
-| --- | ---: |
-| Stars / forks / commits / release downloads | Live badges above |
-| README visits | Public counter above; may include bots and repeat visits |
-| Unique repository visitors | **0** in the rolling 14-day GitHub Traffic window |
-| Unique Git cloners | **11** (**12** total clones) in the rolling 14-day window |
-
-Traffic snapshot: 2026-08-10. GitHub exposes clone and unique-visitor analytics only to maintainers, so those values are a dated, transparent snapshot rather than a token-backed public badge.
-
-### Repository growth
-
-
-
-
-
-
-
-Stars, forks, and commits refresh after every new star and once a week. Traffic cards use the dated aggregate owner snapshot shown in the chart because GitHub does not grant its Actions token access to repository Traffic. No long-lived token is embedded in this README.
-
The native app follows the macOS language preference and ships complete
**English** and **Simplified Chinese** interface catalogs.
@@ -334,6 +312,27 @@ The native app lives in [`macos-app/AgentSwitch`](macos-app/AgentSwitch) and
targets macOS 14+. See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete test
and privacy gate.
+## Project activity
+
+| Public signal | Live or latest owner-visible value |
+| --- | ---: |
+| Stars / forks / commits / release downloads | Live badges above |
+| README visits | Public counter above; may include bots and repeat visits |
+| Unique repository visitors | **0** in the rolling 14-day GitHub Traffic window |
+| Unique Git cloners | **11** (**12** total clones) in the rolling 14-day window |
+
+Traffic snapshot: 2026-08-10. GitHub exposes clone and unique-visitor analytics only to maintainers, so those values are a dated, transparent snapshot rather than a token-backed public badge.
+
+### Clone activity
+
+
+
+
+
+
+
+The curve shows cumulative clones inside GitHub's rolling 14-day Traffic window. The Traffic series is a dated aggregate owner snapshot; stars, forks, and commits in the side cards refresh after every new star and weekly. No long-lived token is embedded in this README.
+
## License
[MIT](LICENSE) © 2026 JNHFlow21
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 5f66000..6782b65 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -46,28 +46,6 @@ Agent Switch 用一个本地 MCP 注册表、一个私密凭据存储和可重
> quarantine。安装前请先检查[公开源码](https://github.com/JNHFlow21/agent-switch)
> 和[发行校验和](https://github.com/JNHFlow21/agent-switch/releases/tag/v0.2.0)。
-
-## 项目数据
-
-| 公开指标 | 实时数据或最近一次维护者可见数据 |
-| --- | ---: |
-| Star / Fork / Commit / 发行下载量 | 见上方实时徽章 |
-| README 访问量 | 见上方公开计数器;可能包含机器人和重复访问 |
-| 仓库独立访客 | GitHub Traffic 最近 14 天滚动窗口内为 **0** |
-| 独立克隆者 | 最近 14 天滚动窗口内为 **11**(共 **12** 次克隆) |
-
-数据快照日期:2026-08-10。GitHub 只向仓库维护者提供克隆与独立访客数据,因此这里采用注明日期的透明快照,而不是需要私密 Token 的公开徽章。
-
-### 仓库增长曲线
-
-
-
-
-
-
-
-Star、Fork 与 Commit 会在每次获得新 Star 后及每周自动刷新。由于 GitHub 不允许 Actions Token 读取仓库 Traffic,流量卡片使用图中注明日期的仓库所有者聚合快照;README 中不嵌入任何长期 Token。
-
原生应用会跟随 macOS 的语言偏好,并完整提供**英文**与**简体中文**界面。
## 快速开始
@@ -316,6 +294,27 @@ python -m unittest discover -s tests/integration
原生应用位于 [`macos-app/AgentSwitch`](macos-app/AgentSwitch),最低支持
macOS 14。完整测试和隐私门禁见 [CONTRIBUTING.md](CONTRIBUTING.md)。
+## 项目数据
+
+| 公开指标 | 实时数据或最近一次维护者可见数据 |
+| --- | ---: |
+| Star / Fork / Commit / 发行下载量 | 见上方实时徽章 |
+| README 访问量 | 见上方公开计数器;可能包含机器人和重复访问 |
+| 仓库独立访客 | GitHub Traffic 最近 14 天滚动窗口内为 **0** |
+| 独立克隆者 | 最近 14 天滚动窗口内为 **11**(共 **12** 次克隆) |
+
+数据快照日期:2026-08-10。GitHub 只向仓库维护者提供克隆与独立访客数据,因此这里采用注明日期的透明快照,而不是需要私密 Token 的公开徽章。
+
+### 克隆趋势
+
+
+
+
+
+
+
+曲线展示 GitHub Traffic 最近 14 天滚动窗口内的累计克隆次数。Traffic 序列采用注明日期的仓库所有者聚合快照;侧边的 Star、Fork 与 Commit 会在每次获得新 Star 后及每周自动刷新。README 中不嵌入任何长期 Token。
+
## 许可证
[MIT](LICENSE) © 2026 JNHFlow21
diff --git a/scripts/render_repository_metrics.py b/scripts/render_repository_metrics.py
index 7d2e371..0ad568f 100755
--- a/scripts/render_repository_metrics.py
+++ b/scripts/render_repository_metrics.py
@@ -4,7 +4,7 @@
from __future__ import annotations
import argparse
-from datetime import datetime, timezone
+from datetime import datetime, timedelta, timezone
from html import escape
import json
import math
@@ -57,6 +57,14 @@ def fetch_snapshot(
"views_14d": int(views["count"]),
"unique_cloners_14d": int(clones["uniques"]),
"clones_14d": int(clones["count"]),
+ "clone_series_14d": [
+ {
+ "timestamp": str(item["timestamp"]),
+ "count": int(item["count"]),
+ }
+ for item in clones.get("clones", [])
+ if isinstance(item, dict) and "timestamp" in item and "count" in item
+ ],
"traffic_as_of": generated_at,
}
except HTTPError as error:
@@ -68,6 +76,7 @@ def fetch_snapshot(
"views_14d",
"unique_cloners_14d",
"clones_14d",
+ "clone_series_14d",
"traffic_as_of",
}
missing = sorted(required - traffic_fallback.keys())
@@ -79,6 +88,7 @@ def fetch_snapshot(
"views_14d": int(traffic_fallback["views_14d"]),
"unique_cloners_14d": int(traffic_fallback["unique_cloners_14d"]),
"clones_14d": int(traffic_fallback["clones_14d"]),
+ "clone_series_14d": list(traffic_fallback["clone_series_14d"]),
"traffic_as_of": str(traffic_fallback["traffic_as_of"]),
}
_, commit_headers = _request_json(f"/repos/{encoded}/commits?per_page=1", token)
@@ -87,23 +97,6 @@ def fetch_snapshot(
last_page = re.search(r"[?&]page=(\d+)[^>]*>; rel=\"last\"", link)
commit_count = int(last_page.group(1)) if last_page else 1
- starred_at: list[str] = []
- page = 1
- while True:
- entries, _ = _request_json(
- f"/repos/{encoded}/stargazers?per_page=100&page={page}",
- token,
- accept="application/vnd.github.star+json",
- )
- starred_at.extend(
- item["starred_at"]
- for item in entries
- if isinstance(item, dict) and isinstance(item.get("starred_at"), str)
- )
- if len(entries) < 100:
- break
- page += 1
-
return {
"repository": repository,
"created_at": repo["created_at"],
@@ -113,7 +106,6 @@ def fetch_snapshot(
"commits": commit_count,
**traffic,
"traffic_live": traffic_live,
- "starred_at": sorted(starred_at),
}
@@ -131,39 +123,45 @@ def _compact(value: int) -> str:
def render_svg(snapshot: dict[str, Any]) -> str:
repository = escape(str(snapshot["repository"]))
- created = _parse_time(str(snapshot["created_at"]))
generated = _parse_time(str(snapshot["generated_at"]))
- if generated <= created:
- generated = created.replace(microsecond=0)
-
+ traffic_as_of = _parse_time(str(snapshot.get("traffic_as_of", snapshot["generated_at"])))
stars = max(0, int(snapshot["stars"]))
- star_dates = sorted(
- date for date in (_parse_time(str(value)) for value in snapshot.get("starred_at", []))
- if created <= date <= generated
+ total_clones = max(0, int(snapshot["clones_14d"]))
+ clone_series = sorted(
+ (
+ _parse_time(str(item["timestamp"])),
+ max(0, int(item["count"])),
+ )
+ for item in snapshot.get("clone_series_14d", [])
+ if isinstance(item, dict) and "timestamp" in item and "count" in item
)
+ chart_start = clone_series[0][0] if clone_series else traffic_as_of - timedelta(days=13)
+ chart_end = clone_series[-1][0] if clone_series else traffic_as_of
+ if chart_end <= chart_start:
+ chart_end = chart_start + timedelta(days=1)
chart_x, chart_y, chart_w, chart_h = 64.0, 150.0, 540.0, 292.0
baseline = chart_y + chart_h
- seconds = max((generated - created).total_seconds(), 1.0)
- y_max = max(stars, 1)
+ seconds = max((chart_end - chart_start).total_seconds(), 1.0)
+ series_total = sum(count for _, count in clone_series)
+ y_max = max(total_clones, series_total, 1)
def x_for(date: datetime) -> float:
- return chart_x + chart_w * max(0.0, min(1.0, (date - created).total_seconds() / seconds))
+ return chart_x + chart_w * max(0.0, min(1.0, (date - chart_start).total_seconds() / seconds))
def y_for(value: int) -> float:
return baseline - chart_h * max(0.0, min(1.0, value / y_max))
path = [f"M {chart_x:.1f} {y_for(0):.1f}"]
- count = 0
- for date in star_dates:
+ cumulative = 0
+ for date, count in clone_series:
x = x_for(date)
- path.append(f"L {x:.1f} {y_for(count):.1f}")
- count += 1
- path.append(f"L {x:.1f} {y_for(count):.1f}")
- if count < stars:
- path.append(f"L {chart_x + chart_w:.1f} {y_for(stars):.1f}")
- else:
- path.append(f"L {chart_x + chart_w:.1f} {y_for(count):.1f}")
+ path.append(f"L {x:.1f} {y_for(cumulative):.1f}")
+ cumulative += count
+ path.append(f"L {x:.1f} {y_for(cumulative):.1f}")
+ if cumulative < total_clones:
+ cumulative = total_clones
+ path.append(f"L {chart_x + chart_w:.1f} {y_for(cumulative):.1f}")
line_path = " ".join(path)
area_path = f"{line_path} L {chart_x + chart_w:.1f} {baseline:.1f} Z"
@@ -202,20 +200,19 @@ def y_for(value: int) -> float:
)
empty_note = ""
- if stars == 0:
- empty_note = 'Waiting for the first star '
+ if total_clones == 0:
+ empty_note = 'No clones in this 14-day window '
updated = generated.strftime("%Y-%m-%d UTC")
- traffic_as_of = _parse_time(str(snapshot.get("traffic_as_of", snapshot["generated_at"])))
if snapshot.get("traffic_live", False):
traffic_note = "GitHub Traffic: rolling 14-day owner view"
else:
traffic_note = f"GitHub Traffic owner snapshot: {traffic_as_of.strftime('%Y-%m-%d')}"
- start_label = created.strftime("%Y-%m")
- end_label = generated.strftime("%Y-%m")
+ start_label = chart_start.strftime("%Y-%m-%d")
+ end_label = chart_end.strftime("%Y-%m-%d")
return f'''
{repository} repository metrics
- Star growth curve with stars, forks, commits, visitors, cloners, and clone totals.
+ Cumulative clone curve for the rolling 14-day Traffic window with stars, forks, commits, visitors, cloners, and clone totals.