Skip to content

Commit 509daea

Browse files
anhmtkcursoragent
andcommitted
feat(chrome-extension): MV3 draft popup for AgentShare Live Traffic
Polls public /api/v1/public/bot-traffic/stats; toolbar badge via service worker. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 34c9aa2 commit 509daea

12 files changed

Lines changed: 618 additions & 0 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@ AI hardware, robotics, mini PCs, robot/RC power — https://agentshare.dev/cover
112112

113113
---
114114

115+
## Live Traffic extension (Chrome / Edge) — draft
116+
117+
Watch **AI agents & bots** hitting AgentShare in real time — toolbar popup + badge. GA4-style visibility for API/MCP traffic (not pageviews).
118+
119+
| Resource | URL |
120+
|----------|-----|
121+
| **Source (MV3)** | [chrome-extension/](./chrome-extension/) |
122+
| **Public dashboard** | https://agentshare.dev/public/bot-traffic |
123+
| **Public API** | `GET https://agentshare.dev/api/v1/public/bot-traffic/stats` |
124+
125+
Load unpacked: `chrome://extensions` → Developer mode → Load unpacked → `chrome-extension/` folder.
126+
127+
---
128+
115129
## License
116130

117131
MIT — [LICENSE](./LICENSE)

chrome-extension/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# AgentShare Live Traffic — Chrome / Edge extension (draft)
2+
3+
Real-time **AI agent & bot traffic** on [agentshare.dev](https://agentshare.dev) — a lightweight “GA4 for API/MCP-first” pulse in your browser toolbar.
4+
5+
Part of the open [agentshare-mcp](https://github.com/anhmtk/agentshare-mcp) repo. Production API: `GET https://agentshare.dev/api/v1/public/bot-traffic/stats` (sanitized, no IPs or API keys).
6+
7+
## Load unpacked (dev)
8+
9+
1. Chrome → `chrome://extensions` (Edge → `edge://extensions`)
10+
2. Enable **Developer mode**
11+
3. **Load unpacked** → select this folder (`chrome-extension/`)
12+
4. Pin **AgentShare Live Traffic** → click icon for popup
13+
14+
## What it shows
15+
16+
- Requests & authenticated agents (rolling ~15 min window)
17+
- Traffic mix: good / suspicious / malicious
18+
- Top intents & client types (MCP, Python, Node, …)
19+
- Country chips + link to full map dashboard
20+
21+
Toolbar **badge** = request count (updates every minute via service worker).
22+
23+
## Files
24+
25+
| File | Role |
26+
|------|------|
27+
| `manifest.json` | MV3 manifest |
28+
| `popup.html` / `popup.css` / `popup.js` | Toolbar popup UI |
29+
| `background.js` | Badge polling |
30+
| `config.js` | API URL defaults |
31+
| `icons/` | Extension icons (from AgentShare brand assets) |
32+
33+
## Roadmap (not in v0.1)
34+
35+
- Public 24h trend sparkline
36+
- Optional `apiBase` in `chrome.storage.sync` for self-hosted mirrors
37+
- Chrome Web Store listing + donate link
38+
39+
## Feedback
40+
41+
[GitHub Issues](https://github.com/anhmtk/agentshare-mcp/issues) — tag `chrome-extension`.
42+
43+
## License
44+
45+
MIT — same as parent repository.

chrome-extension/background.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* MV3 service worker — toolbar badge from public stats (optional pulse).
3+
*/
4+
import { BADGE_ALARM_MINUTES, statsUrl } from "./config.js";
5+
6+
const ALARM_NAME = "agentshare-traffic-poll";
7+
8+
async function getApiBase() {
9+
const stored = await chrome.storage.sync.get(["apiBase"]);
10+
const base = stored.apiBase;
11+
return typeof base === "string" && base.trim() ? base.trim().replace(/\/$/, "") : undefined;
12+
}
13+
14+
async function refreshBadge() {
15+
const url = statsUrl(await getApiBase());
16+
try {
17+
const res = await fetch(url, {
18+
credentials: "omit",
19+
headers: { Accept: "application/json" },
20+
});
21+
if (!res.ok) throw new Error(String(res.status));
22+
const json = await res.json();
23+
const data = json?.data;
24+
const total = Number(data?.total_requests ?? 0);
25+
const text = total > 0 ? (total > 99 ? "99+" : String(total)) : "";
26+
await chrome.action.setBadgeText({ text });
27+
await chrome.action.setBadgeBackgroundColor({ color: total > 0 ? "#15803d" : "#64748b" });
28+
await chrome.storage.local.set({
29+
lastStats: data ?? null,
30+
lastFetchOk: true,
31+
lastFetchAt: Date.now(),
32+
});
33+
} catch {
34+
await chrome.action.setBadgeText({ text: "!" });
35+
await chrome.action.setBadgeBackgroundColor({ color: "#dc2626" });
36+
await chrome.storage.local.set({ lastFetchOk: false, lastFetchAt: Date.now() });
37+
}
38+
}
39+
40+
chrome.runtime.onInstalled.addListener(() => {
41+
chrome.alarms.create(ALARM_NAME, { periodInMinutes: BADGE_ALARM_MINUTES });
42+
void refreshBadge();
43+
});
44+
45+
chrome.alarms.onAlarm.addListener((alarm) => {
46+
if (alarm.name === ALARM_NAME) void refreshBadge();
47+
});
48+
49+
chrome.runtime.onStartup.addListener(() => {
50+
void refreshBadge();
51+
});

chrome-extension/config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Shared defaults — background + popup.
3+
*/
4+
export const DEFAULT_API_BASE = "https://agentshare.dev";
5+
export const STATS_PATH = "/api/v1/public/bot-traffic/stats";
6+
export const POLL_MS = 12000;
7+
export const BADGE_ALARM_MINUTES = 1;
8+
9+
export function statsUrl(apiBase = DEFAULT_API_BASE) {
10+
return `${apiBase.replace(/\/$/, "")}${STATS_PATH}`;
11+
}

chrome-extension/icons/icon128.png

28.7 KB
Loading

chrome-extension/icons/icon16.png

604 Bytes
Loading

chrome-extension/icons/icon32.png

1.55 KB
Loading

chrome-extension/icons/icon48.png

33 KB
Loading

chrome-extension/manifest.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "AgentShare Live Traffic",
4+
"short_name": "AgentShare",
5+
"version": "0.1.0",
6+
"description": "Real-time AI agent & bot traffic on agentshare.dev — GA4-style visibility for API/MCP-first sites.",
7+
"homepage_url": "https://agentshare.dev/public/bot-traffic",
8+
"author": "AgentShare",
9+
"icons": {
10+
"16": "icons/icon16.png",
11+
"32": "icons/icon32.png",
12+
"48": "icons/icon48.png",
13+
"128": "icons/icon128.png"
14+
},
15+
"action": {
16+
"default_title": "AgentShare Live Traffic",
17+
"default_popup": "popup.html",
18+
"default_icon": {
19+
"16": "icons/icon16.png",
20+
"32": "icons/icon32.png",
21+
"48": "icons/icon48.png"
22+
}
23+
},
24+
"background": {
25+
"service_worker": "background.js",
26+
"type": "module"
27+
},
28+
"permissions": ["alarms", "storage"],
29+
"host_permissions": ["https://agentshare.dev/*"],
30+
"minimum_chrome_version": "114"
31+
32+
}

chrome-extension/popup.css

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/* AgentShare Live Traffic — Chrome popup (MV3) */
2+
* {
3+
box-sizing: border-box;
4+
}
5+
6+
body {
7+
margin: 0;
8+
width: 380px;
9+
max-height: 560px;
10+
overflow-y: auto;
11+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
12+
font-size: 13px;
13+
line-height: 1.4;
14+
color: #0f172a;
15+
background: #f8fafc;
16+
}
17+
18+
.hdr {
19+
display: flex;
20+
align-items: flex-start;
21+
gap: 10px;
22+
padding: 12px 14px 10px;
23+
background: #fff;
24+
border-bottom: 1px solid #e2e8f0;
25+
position: sticky;
26+
top: 0;
27+
z-index: 2;
28+
}
29+
30+
.logo {
31+
border-radius: 6px;
32+
}
33+
34+
.hdr-text h1 {
35+
margin: 0;
36+
font-size: 1rem;
37+
font-weight: 800;
38+
}
39+
40+
.sub {
41+
margin: 2px 0 0;
42+
font-size: 0.78rem;
43+
color: #64748b;
44+
}
45+
46+
.live-dot {
47+
width: 9px;
48+
height: 9px;
49+
border-radius: 999px;
50+
background: #94a3b8;
51+
margin-top: 5px;
52+
margin-left: auto;
53+
flex-shrink: 0;
54+
}
55+
56+
.live-dot.ok {
57+
background: #22c55e;
58+
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.25);
59+
}
60+
61+
.live-dot.err {
62+
background: #ef4444;
63+
}
64+
65+
.metrics {
66+
display: grid;
67+
grid-template-columns: 1fr 1fr;
68+
gap: 8px;
69+
padding: 10px 14px;
70+
}
71+
72+
.metric {
73+
background: #fff;
74+
border: 1px solid #e2e8f0;
75+
border-radius: 10px;
76+
padding: 10px 12px;
77+
}
78+
79+
.metric .label {
80+
display: block;
81+
font-size: 0.72rem;
82+
text-transform: uppercase;
83+
letter-spacing: 0.04em;
84+
color: #64748b;
85+
font-weight: 600;
86+
}
87+
88+
.metric .value {
89+
display: block;
90+
font-size: 1.55rem;
91+
font-weight: 800;
92+
margin-top: 2px;
93+
font-variant-numeric: tabular-nums;
94+
}
95+
96+
.metric .hint {
97+
display: block;
98+
font-size: 0.7rem;
99+
color: #94a3b8;
100+
margin-top: 2px;
101+
}
102+
103+
.breakdown {
104+
display: flex;
105+
flex-wrap: wrap;
106+
gap: 6px;
107+
padding: 0 14px 8px;
108+
}
109+
110+
.pill {
111+
font-size: 0.72rem;
112+
font-weight: 600;
113+
padding: 4px 8px;
114+
border-radius: 999px;
115+
border: 1px solid transparent;
116+
}
117+
118+
.pill-good {
119+
background: #dcfce7;
120+
color: #166534;
121+
border-color: #bbf7d0;
122+
}
123+
124+
.pill-suspicious {
125+
background: #fef9c3;
126+
color: #854d0e;
127+
border-color: #fde047;
128+
}
129+
130+
.pill-malicious {
131+
background: #fee2e2;
132+
color: #991b1b;
133+
border-color: #fecaca;
134+
}
135+
136+
.pill-human {
137+
background: #e0e7ff;
138+
color: #3730a3;
139+
border-color: #c7d2fe;
140+
}
141+
142+
.panels {
143+
display: grid;
144+
grid-template-columns: 1fr 1fr;
145+
gap: 8px;
146+
padding: 0 14px 8px;
147+
}
148+
149+
.panel {
150+
background: #fff;
151+
border: 1px solid #e2e8f0;
152+
border-radius: 10px;
153+
padding: 8px 10px;
154+
}
155+
156+
.panel h2 {
157+
margin: 0 0 6px;
158+
font-size: 0.72rem;
159+
text-transform: uppercase;
160+
letter-spacing: 0.04em;
161+
color: #64748b;
162+
}
163+
164+
.list {
165+
margin: 0;
166+
padding-left: 1.1rem;
167+
font-size: 0.78rem;
168+
}
169+
170+
.list li {
171+
margin-bottom: 3px;
172+
word-break: break-word;
173+
}
174+
175+
.count {
176+
color: #64748b;
177+
font-weight: 600;
178+
}
179+
180+
.panel-countries {
181+
margin: 0 14px 8px;
182+
}
183+
184+
.chips {
185+
display: flex;
186+
flex-wrap: wrap;
187+
gap: 6px;
188+
}
189+
190+
.chip {
191+
font-size: 0.72rem;
192+
padding: 3px 8px;
193+
border-radius: 6px;
194+
background: #f1f5f9;
195+
border: 1px solid #e2e8f0;
196+
color: #334155;
197+
}
198+
199+
.status {
200+
margin: 0;
201+
padding: 0 14px 8px;
202+
font-size: 0.72rem;
203+
color: #64748b;
204+
}
205+
206+
.ftr {
207+
padding: 10px 14px 12px;
208+
border-top: 1px solid #e2e8f0;
209+
background: #fff;
210+
font-size: 0.78rem;
211+
text-align: center;
212+
}
213+
214+
.ftr a {
215+
color: #2563eb;
216+
text-decoration: none;
217+
font-weight: 600;
218+
}
219+
220+
.ftr a:hover {
221+
text-decoration: underline;
222+
}
223+
224+
.sep {
225+
color: #cbd5e1;
226+
margin: 0 4px;
227+
}
228+
229+
.empty {
230+
color: #94a3b8;
231+
list-style: none;
232+
margin-left: -1.1rem;
233+
}

0 commit comments

Comments
 (0)