reset-beer-mcp 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -43
- package/browser-extension/background.js +49 -0
- package/browser-extension/icons/icon128.png +0 -0
- package/browser-extension/icons/icon16.png +0 -0
- package/browser-extension/icons/icon48.png +0 -0
- package/browser-extension/manifest.json +24 -0
- package/browser-extension/popup.html +333 -0
- package/browser-extension/popup.js +147 -0
- package/index.js +1 -19
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,63 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# reset-beer-mcp
|
|
2
2
|
|
|
3
|
-
> MCP
|
|
4
|
-
|
|
5
|
-
Query AI quota reset signals directly from Claude, Cursor, or any MCP-compatible AI assistant.
|
|
6
|
-
|
|
7
|
-
## Quick Start
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install -g @reset-beer/mcp
|
|
11
|
-
export RESET_BEER_API_KEY="rb_your_key_here"
|
|
12
|
-
reset-beer-mcp resets
|
|
13
|
-
```
|
|
3
|
+
> MCP server for [Reset Beer](https://reset.beer) — AI quota reset tracking + price change monitoring.
|
|
14
4
|
|
|
15
5
|
## Claude Desktop Config
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
{
|
|
19
|
-
"mcpServers": {
|
|
20
|
-
"reset-beer": {
|
|
21
|
-
"command": "npx",
|
|
22
|
-
"args": ["-y", "@reset-beer/mcp"],
|
|
23
|
-
"env": { "RESET_BEER_API_KEY": "rb_your_key_here" }
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Cursor Config
|
|
30
|
-
|
|
31
|
-
Add to `.cursor/mcp.json`:
|
|
32
6
|
```json
|
|
33
7
|
{
|
|
34
8
|
"mcpServers": {
|
|
35
9
|
"reset-beer": {
|
|
36
10
|
"url": "https://reset.beer/api/mcp",
|
|
37
|
-
"headers": { "Authorization": "Bearer
|
|
11
|
+
"headers": { "Authorization": "Bearer rb_your_key" }
|
|
38
12
|
}
|
|
39
13
|
}
|
|
40
14
|
}
|
|
41
15
|
```
|
|
42
16
|
|
|
43
17
|
## Tools
|
|
44
|
-
|
|
45
18
|
| Tool | Description |
|
|
46
19
|
|------|-------------|
|
|
47
|
-
| `list_recent_resets` |
|
|
48
|
-
| `get_product_status` | Get latest
|
|
20
|
+
| `list_recent_resets` | Query recent AI quota reset events |
|
|
21
|
+
| `get_product_status` | Get latest event for a product |
|
|
49
22
|
| `list_products` | List all tracked AI products |
|
|
50
|
-
|
|
51
|
-
## Supported Products
|
|
52
|
-
|
|
53
|
-
OpenAI Codex, ChatGPT Work, Claude/Claude Code, Google Gemini, xAI Grok, DeepSeek, Kimi/Moonshot, Alibaba Qwen, Zhipu GLM, Volcengine Ark, OpenRouter, Cursor, MiniMax, Tencent Hunyuan, Baidu Qianfan
|
|
54
|
-
|
|
55
|
-
## Get an API Key
|
|
56
|
-
|
|
57
|
-
1. Sign up at [reset.beer](https://reset.beer)
|
|
58
|
-
2. Upgrade to Pro ($9.99 one-time, lifetime access)
|
|
59
|
-
3. Generate key at [reset.beer/account](https://reset.beer/en-US/account)
|
|
23
|
+
| `list_price_changes` | Query recent price drops and increases |
|
|
60
24
|
|
|
61
25
|
## License
|
|
62
|
-
|
|
63
26
|
MIT
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const API_BASE = "https://reset.beer/api/mcp";
|
|
2
|
+
|
|
3
|
+
// Check for new signals every 15 minutes
|
|
4
|
+
chrome.alarms.create("checkSignals", { periodInMinutes: 15 });
|
|
5
|
+
|
|
6
|
+
chrome.alarms.onAlarm.addListener(async (alarm) => {
|
|
7
|
+
if (alarm.name !== "checkSignals") return;
|
|
8
|
+
|
|
9
|
+
const { apiKey } = await chrome.storage.local.get("apiKey");
|
|
10
|
+
if (!apiKey) return;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(API_BASE, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
16
|
+
body: JSON.stringify({
|
|
17
|
+
jsonrpc: "2.0", id: Date.now(), method: "tools/call",
|
|
18
|
+
params: { name: "list_recent_resets", arguments: { hours: 1, limit: 5 } }
|
|
19
|
+
})
|
|
20
|
+
});
|
|
21
|
+
const data = await res.json();
|
|
22
|
+
if (data.error) return;
|
|
23
|
+
|
|
24
|
+
const events = JSON.parse(data.result.content[0].text);
|
|
25
|
+
const signalEvents = events.events || [];
|
|
26
|
+
|
|
27
|
+
// Notify for new signals not seen before
|
|
28
|
+
const { seenIds } = await chrome.storage.local.get("seenIds");
|
|
29
|
+
const seen = new Set(seenIds || []);
|
|
30
|
+
|
|
31
|
+
for (const event of signalEvents) {
|
|
32
|
+
const eventId = `${event.product}:${event.announcedAt}`;
|
|
33
|
+
if (!seen.has(eventId)) {
|
|
34
|
+
seen.add(eventId);
|
|
35
|
+
chrome.notifications.create({
|
|
36
|
+
type: "basic",
|
|
37
|
+
iconUrl: "icons/icon128.png",
|
|
38
|
+
title: `🔔 ${event.product} — Reset Signal`,
|
|
39
|
+
message: event.summary?.slice(0, 200) || "New quota reset signal detected.",
|
|
40
|
+
priority: 2
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await chrome.storage.local.set({ seenIds: Array.from(seen).slice(-100) });
|
|
46
|
+
} catch (e) {
|
|
47
|
+
// Silent fail
|
|
48
|
+
}
|
|
49
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "Reset Beer — AI Quota Reset Alerts",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Real-time AI quota reset alerts. Track Codex, Claude, Gemini, Cursor + 11 more. Never waste paid credits again.",
|
|
6
|
+
"permissions": ["storage", "alarms", "notifications"],
|
|
7
|
+
"host_permissions": ["https://reset.beer/*"],
|
|
8
|
+
"action": {
|
|
9
|
+
"default_popup": "popup.html",
|
|
10
|
+
"default_icon": {
|
|
11
|
+
"16": "icons/icon16.png",
|
|
12
|
+
"48": "icons/icon48.png",
|
|
13
|
+
"128": "icons/icon128.png"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"background": {
|
|
17
|
+
"service_worker": "background.js"
|
|
18
|
+
},
|
|
19
|
+
"icons": {
|
|
20
|
+
"16": "icons/icon16.png",
|
|
21
|
+
"48": "icons/icon48.png",
|
|
22
|
+
"128": "icons/icon128.png"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<style>
|
|
6
|
+
:root {
|
|
7
|
+
--stout-950: #100a06;
|
|
8
|
+
--stout-900: #191009;
|
|
9
|
+
--stout-800: #2b1a10;
|
|
10
|
+
--amber-450: #fbbf24;
|
|
11
|
+
--amber-500: #f59e0b;
|
|
12
|
+
--foam-50: #fffbf5;
|
|
13
|
+
--foam-200: #f3e8db;
|
|
14
|
+
--foam-300: #e7d5c5;
|
|
15
|
+
--hop-500: #22c55e;
|
|
16
|
+
--hop-400: #4ade80;
|
|
17
|
+
--sky-400: #38bdf8;
|
|
18
|
+
--sky-500: #0ea5e9;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
22
|
+
|
|
23
|
+
body {
|
|
24
|
+
width: 380px;
|
|
25
|
+
min-height: 480px;
|
|
26
|
+
max-height: 600px;
|
|
27
|
+
background: var(--stout-950);
|
|
28
|
+
color: var(--foam-50);
|
|
29
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
30
|
+
overflow-y: auto;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Header */
|
|
34
|
+
.header {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
padding: 14px 16px 12px;
|
|
39
|
+
border-bottom: 1px solid rgba(255,247,237,0.08);
|
|
40
|
+
position: sticky;
|
|
41
|
+
top: 0;
|
|
42
|
+
background: var(--stout-950);
|
|
43
|
+
z-index: 10;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.brand {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: 8px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.brand-icon {
|
|
53
|
+
width: 26px; height: 26px;
|
|
54
|
+
border-radius: 6px;
|
|
55
|
+
background: linear-gradient(to bottom, rgba(245,158,11,0.2), #191009);
|
|
56
|
+
display: flex; align-items: center; justify-content: center;
|
|
57
|
+
font-size: 14px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.brand-name {
|
|
61
|
+
font-size: 13px;
|
|
62
|
+
font-weight: 700;
|
|
63
|
+
letter-spacing: 0.1em;
|
|
64
|
+
color: var(--foam-50);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.status-dot {
|
|
68
|
+
width: 8px; height: 8px;
|
|
69
|
+
border-radius: 50%;
|
|
70
|
+
background: var(--hop-500);
|
|
71
|
+
animation: pulse 2s ease-in-out infinite;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@keyframes pulse {
|
|
75
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
76
|
+
50% { opacity: 0.6; transform: scale(0.85); }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Plan badge */
|
|
80
|
+
.plan-badge {
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
gap: 4px;
|
|
84
|
+
padding: 4px 10px;
|
|
85
|
+
border-radius: 999px;
|
|
86
|
+
font-size: 11px;
|
|
87
|
+
font-weight: 600;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.plan-badge.free {
|
|
91
|
+
background: rgba(243,232,219,0.08);
|
|
92
|
+
color: var(--foam-200);
|
|
93
|
+
border: 1px solid rgba(243,232,219,0.12);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.plan-badge.pro {
|
|
97
|
+
background: rgba(245,158,11,0.12);
|
|
98
|
+
color: var(--amber-450);
|
|
99
|
+
border: 1px solid rgba(245,158,11,0.3);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Signal list */
|
|
103
|
+
.signals {
|
|
104
|
+
padding: 8px 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.signal-item {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: flex-start;
|
|
110
|
+
gap: 10px;
|
|
111
|
+
padding: 12px 16px;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
transition: background 0.15s ease;
|
|
114
|
+
border-left: 3px solid transparent;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.signal-item:hover {
|
|
118
|
+
background: rgba(245,158,11,0.04);
|
|
119
|
+
border-left-color: var(--amber-500);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.signal-item.urgent {
|
|
123
|
+
border-left-color: var(--sky-500);
|
|
124
|
+
background: rgba(14,165,233,0.05);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.signal-dot {
|
|
128
|
+
width: 8px; height: 8px;
|
|
129
|
+
border-radius: 50%;
|
|
130
|
+
margin-top: 5px;
|
|
131
|
+
flex-shrink: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.signal-dot.confirmed { background: var(--amber-500); }
|
|
135
|
+
.signal-dot.signal { background: var(--sky-400); }
|
|
136
|
+
|
|
137
|
+
.signal-content { flex: 1; min-width: 0; }
|
|
138
|
+
|
|
139
|
+
.signal-product {
|
|
140
|
+
font-size: 13px;
|
|
141
|
+
font-weight: 600;
|
|
142
|
+
color: var(--foam-50);
|
|
143
|
+
margin-bottom: 2px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.signal-summary {
|
|
147
|
+
font-size: 12px;
|
|
148
|
+
color: var(--foam-300);
|
|
149
|
+
opacity: 0.7;
|
|
150
|
+
display: -webkit-box;
|
|
151
|
+
-webkit-line-clamp: 2;
|
|
152
|
+
-webkit-box-orient: vertical;
|
|
153
|
+
overflow: hidden;
|
|
154
|
+
line-height: 1.4;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.signal-meta {
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
gap: 8px;
|
|
161
|
+
margin-top: 4px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.signal-time { font-size: 11px; color: var(--foam-200); opacity: 0.4; }
|
|
165
|
+
|
|
166
|
+
.signal-badge {
|
|
167
|
+
font-size: 10px;
|
|
168
|
+
padding: 1px 6px;
|
|
169
|
+
border-radius: 4px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.signal-badge.official {
|
|
173
|
+
background: rgba(245,158,11,0.1);
|
|
174
|
+
color: var(--amber-450);
|
|
175
|
+
border: 1px solid rgba(245,158,11,0.2);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.signal-badge.signal-level {
|
|
179
|
+
background: rgba(56,189,248,0.1);
|
|
180
|
+
color: var(--sky-400);
|
|
181
|
+
border: 1px solid rgba(56,189,248,0.2);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* Empty state */
|
|
185
|
+
.empty {
|
|
186
|
+
text-align: center;
|
|
187
|
+
padding: 60px 20px;
|
|
188
|
+
color: var(--foam-200);
|
|
189
|
+
opacity: 0.5;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.empty-icon { font-size: 32px; margin-bottom: 12px; }
|
|
193
|
+
.empty-text { font-size: 13px; line-height: 1.6; }
|
|
194
|
+
|
|
195
|
+
/* Upgrade banner */
|
|
196
|
+
.upgrade {
|
|
197
|
+
margin: 8px 16px;
|
|
198
|
+
padding: 12px 16px;
|
|
199
|
+
border-radius: 12px;
|
|
200
|
+
background: linear-gradient(135deg, rgba(245,158,11,0.08), rgba(245,158,11,0.03));
|
|
201
|
+
border: 1px solid rgba(245,158,11,0.2);
|
|
202
|
+
display: flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
justify-content: space-between;
|
|
205
|
+
gap: 12px;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.upgrade-text { font-size: 12px; color: var(--foam-200); line-height: 1.5; }
|
|
209
|
+
.upgrade-text strong { color: var(--amber-450); }
|
|
210
|
+
|
|
211
|
+
.upgrade-btn {
|
|
212
|
+
padding: 6px 14px;
|
|
213
|
+
border-radius: 999px;
|
|
214
|
+
background: var(--amber-500);
|
|
215
|
+
color: var(--stout-950);
|
|
216
|
+
font-size: 11px;
|
|
217
|
+
font-weight: 700;
|
|
218
|
+
cursor: pointer;
|
|
219
|
+
border: none;
|
|
220
|
+
white-space: nowrap;
|
|
221
|
+
text-decoration: none;
|
|
222
|
+
flex-shrink: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.upgrade-btn:hover { background: var(--amber-450); }
|
|
226
|
+
|
|
227
|
+
/* Footer */
|
|
228
|
+
.footer {
|
|
229
|
+
padding: 10px 16px;
|
|
230
|
+
border-top: 1px solid rgba(255,247,237,0.06);
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
justify-content: space-between;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.footer-link {
|
|
237
|
+
font-size: 11px;
|
|
238
|
+
color: var(--foam-200);
|
|
239
|
+
opacity: 0.4;
|
|
240
|
+
text-decoration: none;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.footer-link:hover { color: var(--amber-450); opacity: 1; }
|
|
244
|
+
|
|
245
|
+
/* Loading */
|
|
246
|
+
.loading {
|
|
247
|
+
display: flex;
|
|
248
|
+
flex-direction: column;
|
|
249
|
+
align-items: center;
|
|
250
|
+
justify-content: center;
|
|
251
|
+
padding: 60px 20px;
|
|
252
|
+
gap: 16px;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.spinner {
|
|
256
|
+
width: 28px; height: 28px;
|
|
257
|
+
border: 3px solid rgba(245,158,11,0.2);
|
|
258
|
+
border-top-color: var(--amber-500);
|
|
259
|
+
border-radius: 50%;
|
|
260
|
+
animation: spin 0.8s linear infinite;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
264
|
+
|
|
265
|
+
/* API key input */
|
|
266
|
+
.apikey-section {
|
|
267
|
+
padding: 20px 16px;
|
|
268
|
+
text-align: center;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.apikey-section p { font-size: 13px; color: var(--foam-200); margin-bottom: 16px; line-height: 1.6; opacity: 0.7; }
|
|
272
|
+
|
|
273
|
+
.apikey-input {
|
|
274
|
+
width: 100%;
|
|
275
|
+
padding: 10px 12px;
|
|
276
|
+
border-radius: 8px;
|
|
277
|
+
border: 1px solid rgba(255,247,237,0.12);
|
|
278
|
+
background: var(--stout-800);
|
|
279
|
+
color: var(--foam-50);
|
|
280
|
+
font-size: 13px;
|
|
281
|
+
font-family: monospace;
|
|
282
|
+
margin-bottom: 12px;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.apikey-input:focus { outline: none; border-color: var(--amber-500); }
|
|
286
|
+
|
|
287
|
+
.apikey-btn {
|
|
288
|
+
width: 100%;
|
|
289
|
+
padding: 10px;
|
|
290
|
+
border-radius: 8px;
|
|
291
|
+
background: var(--amber-500);
|
|
292
|
+
color: var(--stout-950);
|
|
293
|
+
font-weight: 700;
|
|
294
|
+
font-size: 13px;
|
|
295
|
+
border: none;
|
|
296
|
+
cursor: pointer;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.apikey-btn:hover { background: var(--amber-450); }
|
|
300
|
+
|
|
301
|
+
.error-msg { font-size: 12px; color: #f87171; margin-top: 8px; }
|
|
302
|
+
</style>
|
|
303
|
+
</head>
|
|
304
|
+
<body>
|
|
305
|
+
<!-- Header -->
|
|
306
|
+
<div class="header">
|
|
307
|
+
<div class="brand">
|
|
308
|
+
<div class="brand-icon">🍺</div>
|
|
309
|
+
<span class="brand-name">Reset Beer</span>
|
|
310
|
+
<div class="status-dot" id="statusDot"></div>
|
|
311
|
+
</div>
|
|
312
|
+
<span class="plan-badge free" id="planBadge">Free</span>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<!-- Main content (dynamic) -->
|
|
316
|
+
<div id="mainContent">
|
|
317
|
+
<!-- Default: loading -->
|
|
318
|
+
<div class="loading" id="loadingState">
|
|
319
|
+
<div class="spinner"></div>
|
|
320
|
+
<span style="font-size:13px;color:var(--foam-200);opacity:0.5;">Loading signals…</span>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
|
|
324
|
+
<!-- Footer -->
|
|
325
|
+
<div class="footer">
|
|
326
|
+
<a class="footer-link" href="https://reset.beer/en-US/live" target="_blank">Dashboard</a>
|
|
327
|
+
<a class="footer-link" href="https://reset.beer/en-US/subscribe" target="_blank">Upgrade</a>
|
|
328
|
+
<span class="footer-link" style="opacity:0.3;">reset.beer</span>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<script src="popup.js"></script>
|
|
332
|
+
</body>
|
|
333
|
+
</html>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const API_BASE = "https://reset.beer/api/mcp";
|
|
2
|
+
const PRO_CHECK = "https://reset.beer/api/billing/status";
|
|
3
|
+
|
|
4
|
+
let apiKey = null;
|
|
5
|
+
|
|
6
|
+
// Load saved API key on open
|
|
7
|
+
chrome.storage.local.get(["apiKey", "plan"], (result) => {
|
|
8
|
+
if (result.apiKey) {
|
|
9
|
+
apiKey = result.apiKey;
|
|
10
|
+
checkPlanAndLoadSignals();
|
|
11
|
+
} else {
|
|
12
|
+
showApiKeySetup();
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function showApiKeySetup() {
|
|
17
|
+
const main = document.getElementById("mainContent");
|
|
18
|
+
main.innerHTML = `
|
|
19
|
+
<div class="apikey-section">
|
|
20
|
+
<p>Enter your Reset Beer Pro API key to receive real-time quota reset signals.</p>
|
|
21
|
+
<input class="apikey-input" type="text" id="apiKeyInput" placeholder="rb_your_api_key" />
|
|
22
|
+
<button class="apikey-btn" id="saveKey">Connect</button>
|
|
23
|
+
<div class="error-msg" id="errorMsg" style="display:none;"></div>
|
|
24
|
+
<p style="margin-top:12px;font-size:11px;opacity:0.5;">
|
|
25
|
+
Get your key at <a href="https://reset.beer/en-US/account" target="_blank" style="color:var(--amber-450);">reset.beer/account</a>
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
`;
|
|
29
|
+
document.getElementById("saveKey").addEventListener("click", saveApiKey);
|
|
30
|
+
document.getElementById("apiKeyInput").addEventListener("keydown", (e) => {
|
|
31
|
+
if (e.key === "Enter") saveApiKey();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function saveApiKey() {
|
|
36
|
+
const input = document.getElementById("apiKeyInput");
|
|
37
|
+
const key = input.value.trim();
|
|
38
|
+
const errEl = document.getElementById("errorMsg");
|
|
39
|
+
errEl.style.display = "none";
|
|
40
|
+
|
|
41
|
+
if (!key.startsWith("rb_")) {
|
|
42
|
+
errEl.textContent = "Invalid key format. Keys start with rb_";
|
|
43
|
+
errEl.style.display = "block";
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Validate key against MCP endpoint
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(API_BASE, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "Authorization": `Bearer ${key}`, "Content-Type": "application/json" },
|
|
52
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} })
|
|
53
|
+
});
|
|
54
|
+
const data = await res.json();
|
|
55
|
+
if (data.result) {
|
|
56
|
+
apiKey = key;
|
|
57
|
+
chrome.storage.local.set({ apiKey: key });
|
|
58
|
+
checkPlanAndLoadSignals();
|
|
59
|
+
} else {
|
|
60
|
+
errEl.textContent = data.error?.message || "Invalid key";
|
|
61
|
+
errEl.style.display = "block";
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
errEl.textContent = "Connection failed. Try again.";
|
|
65
|
+
errEl.style.display = "block";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function checkPlanAndLoadSignals() {
|
|
70
|
+
// Check plan via billing status API
|
|
71
|
+
try {
|
|
72
|
+
const res = await fetch(PRO_CHECK);
|
|
73
|
+
// This endpoint requires auth session, not API key
|
|
74
|
+
// For extension, we'll use MCP tool response to infer plan
|
|
75
|
+
} catch {}
|
|
76
|
+
|
|
77
|
+
// Load signals
|
|
78
|
+
await loadSignals();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function loadSignals() {
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch(API_BASE, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
jsonrpc: "2.0", id: 2, method: "tools/call",
|
|
88
|
+
params: { name: "list_recent_resets", arguments: { hours: 72, limit: 20 } }
|
|
89
|
+
})
|
|
90
|
+
});
|
|
91
|
+
const data = await res.json();
|
|
92
|
+
|
|
93
|
+
if (data.error) {
|
|
94
|
+
showError(data.error.message);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const events = JSON.parse(data.result.content[0].text);
|
|
99
|
+
renderSignals(events.events || []);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
showError("Failed to load signals. Check your connection.");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function renderSignals(events) {
|
|
106
|
+
const main = document.getElementById("mainContent");
|
|
107
|
+
|
|
108
|
+
if (events.length === 0) {
|
|
109
|
+
main.innerHTML = `
|
|
110
|
+
<div class="empty">
|
|
111
|
+
<div class="empty-icon">🍺</div>
|
|
112
|
+
<div class="empty-text">No quota reset signals in the last 24 hours.<br>All clear — go build something.</div>
|
|
113
|
+
</div>
|
|
114
|
+
`;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const html = `
|
|
119
|
+
<div class="signals">
|
|
120
|
+
${events.map(event => `
|
|
121
|
+
<div class="signal-item ${event.evidence === 'official_signal' ? 'urgent' : ''}">
|
|
122
|
+
<div class="signal-dot ${event.evidence === 'official_confirmed' ? 'confirmed' : 'signal'}"></div>
|
|
123
|
+
<div class="signal-content">
|
|
124
|
+
<div class="signal-product">${event.product}</div>
|
|
125
|
+
<div class="signal-summary">${event.summary}</div>
|
|
126
|
+
<div class="signal-meta">
|
|
127
|
+
<span class="signal-time">${new Date(event.announcedAt).toLocaleString([], { month:'short', day:'numeric', hour:'2-digit', minute:'2-digit' })}</span>
|
|
128
|
+
<span class="signal-badge ${event.evidence === 'official_confirmed' ? 'official' : 'signal-level'}">${event.evidence}</span>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
`).join("")}
|
|
133
|
+
</div>
|
|
134
|
+
`;
|
|
135
|
+
main.innerHTML = html;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function showError(msg) {
|
|
139
|
+
const main = document.getElementById("mainContent");
|
|
140
|
+
main.innerHTML = `
|
|
141
|
+
<div class="empty">
|
|
142
|
+
<div class="empty-icon">⚠️</div>
|
|
143
|
+
<div class="empty-text">${msg}</div>
|
|
144
|
+
<button class="apikey-btn" style="margin-top:16px;max-width:200px;" onclick="location.reload()">Retry</button>
|
|
145
|
+
</div>
|
|
146
|
+
`;
|
|
147
|
+
}
|
package/index.js
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Reset Beer MCP Server
|
|
4
|
-
* Query AI quota reset signals from Claude, Cursor, or any MCP client.
|
|
5
|
-
*
|
|
6
|
-
* Setup:
|
|
7
|
-
* 1. Get an API key at https://reset.beer/en-US/account (Pro required)
|
|
8
|
-
* 2. Set RESET_BEER_API_KEY environment variable
|
|
9
|
-
* 3. Add to your MCP client config
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
2
|
const API_BASE = "https://reset.beer/api/mcp";
|
|
13
3
|
const apiKey = process.env.RESET_BEER_API_KEY;
|
|
14
4
|
|
|
@@ -21,16 +11,12 @@ if (!apiKey) {
|
|
|
21
11
|
async function callMcp(method, params = {}) {
|
|
22
12
|
const response = await fetch(API_BASE, {
|
|
23
13
|
method: "POST",
|
|
24
|
-
headers: {
|
|
25
|
-
"Authorization": `Bearer ${apiKey}`,
|
|
26
|
-
"Content-Type": "application/json"
|
|
27
|
-
},
|
|
14
|
+
headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
28
15
|
body: JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method, params })
|
|
29
16
|
});
|
|
30
17
|
return response.json();
|
|
31
18
|
}
|
|
32
19
|
|
|
33
|
-
// CLI usage
|
|
34
20
|
const [,, command, ...args] = process.argv;
|
|
35
21
|
switch (command) {
|
|
36
22
|
case "resets": {
|
|
@@ -54,13 +40,9 @@ switch (command) {
|
|
|
54
40
|
default:
|
|
55
41
|
console.log(`
|
|
56
42
|
Reset Beer MCP Server
|
|
57
|
-
|
|
58
43
|
Usage:
|
|
59
44
|
reset-beer-mcp resets [hours] List recent quota resets
|
|
60
45
|
reset-beer-mcp status <product> Get product status
|
|
61
46
|
reset-beer-mcp products List all tracked products
|
|
62
|
-
|
|
63
|
-
Environment:
|
|
64
|
-
RESET_BEER_API_KEY Your API key from reset.beer/account
|
|
65
47
|
`);
|
|
66
48
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reset-beer-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "MCP server for Reset Beer — AI quota reset tracking across 15+ products (Codex, Claude, Gemini, Cursor, etc.)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"reset-beer-mcp": "
|
|
8
|
+
"reset-beer-mcp": "index.js"
|
|
9
9
|
},
|
|
10
10
|
"keywords": [
|
|
11
11
|
"mcp",
|
|
@@ -40,5 +40,6 @@
|
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
42
42
|
"node": ">=18"
|
|
43
|
-
}
|
|
44
|
-
|
|
43
|
+
},
|
|
44
|
+
"mcpName": "io.github.suxinsk/reset-beer-mcp"
|
|
45
|
+
}
|