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 - -

- - Agent Switch repository growth and traffic metrics - -

- -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 + +

+ + Agent Switch rolling 14-day clone activity and repository metrics + +

+ +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 的公开徽章。 - -### 仓库增长曲线 - -

- - Agent Switch 仓库增长与流量指标 - -

- -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 的公开徽章。 + +### 克隆趋势 + +

+ + Agent Switch 最近 14 天克隆趋势与仓库指标 + +

+ +曲线展示 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.