@pwddd/skills-scanner 3.0.22 → 4.0.0

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/src/metrics.ts ADDED
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Performance metrics module
3
+ */
4
+
5
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
8
+
9
+ export interface ScanMetrics {
10
+ version: number;
11
+ totalScans: number;
12
+ successfulScans: number;
13
+ failedScans: number;
14
+ averageScanTimeMs: number;
15
+ lastScanDurationMs: number;
16
+ lastScanAt?: string;
17
+ scansByPolicy: {
18
+ strict: number;
19
+ balanced: number;
20
+ permissive: number;
21
+ };
22
+ }
23
+
24
+ const METRICS_VERSION = 1;
25
+
26
+ /**
27
+ * Get metrics file path
28
+ */
29
+ function getMetricsFile(stateDir: string): string {
30
+ return join(stateDir, "metrics.json");
31
+ }
32
+
33
+ /**
34
+ * Load metrics from disk
35
+ */
36
+ export function loadMetrics(stateDir: string): ScanMetrics {
37
+ try {
38
+ const metricsFile = getMetricsFile(stateDir);
39
+ return JSON.parse(readFileSync(metricsFile, "utf-8"));
40
+ } catch {
41
+ return {
42
+ version: METRICS_VERSION,
43
+ totalScans: 0,
44
+ successfulScans: 0,
45
+ failedScans: 0,
46
+ averageScanTimeMs: 0,
47
+ lastScanDurationMs: 0,
48
+ scansByPolicy: {
49
+ strict: 0,
50
+ balanced: 0,
51
+ permissive: 0,
52
+ },
53
+ };
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Save metrics to disk
59
+ */
60
+ export function saveMetrics(stateDir: string, metrics: ScanMetrics): void {
61
+ const metricsFile = getMetricsFile(stateDir);
62
+ mkdirSync(stateDir, { recursive: true });
63
+ writeFileSync(metricsFile, JSON.stringify(metrics, null, 2));
64
+ }
65
+
66
+ /**
67
+ * Record a scan operation
68
+ */
69
+ export function recordScan(
70
+ stateDir: string,
71
+ options: {
72
+ success: boolean;
73
+ durationMs: number;
74
+ policy: "strict" | "balanced" | "permissive";
75
+ }
76
+ ): void {
77
+ const metrics = loadMetrics(stateDir);
78
+
79
+ metrics.totalScans++;
80
+ if (options.success) {
81
+ metrics.successfulScans++;
82
+ } else {
83
+ metrics.failedScans++;
84
+ }
85
+
86
+ // Update average scan time
87
+ const totalTime = metrics.averageScanTimeMs * (metrics.totalScans - 1);
88
+ metrics.averageScanTimeMs = (totalTime + options.durationMs) / metrics.totalScans;
89
+
90
+ metrics.lastScanDurationMs = options.durationMs;
91
+ metrics.lastScanAt = new Date().toISOString();
92
+
93
+ // Update policy counter
94
+ metrics.scansByPolicy[options.policy]++;
95
+
96
+ saveMetrics(stateDir, metrics);
97
+ }
98
+
99
+ /**
100
+ * Get metrics summary
101
+ */
102
+ export function getMetricsSummary(stateDir: string): {
103
+ totalScans: number;
104
+ successRate: number;
105
+ averageDurationMs: number;
106
+ lastScanAt?: string;
107
+ } {
108
+ const metrics = loadMetrics(stateDir);
109
+
110
+ return {
111
+ totalScans: metrics.totalScans,
112
+ successRate:
113
+ metrics.totalScans > 0
114
+ ? (metrics.successfulScans / metrics.totalScans) * 100
115
+ : 0,
116
+ averageDurationMs: Math.round(metrics.averageScanTimeMs),
117
+ lastScanAt: metrics.lastScanAt,
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Reset all metrics
123
+ */
124
+ export function resetMetrics(stateDir: string): void {
125
+ const metrics: ScanMetrics = {
126
+ version: METRICS_VERSION,
127
+ totalScans: 0,
128
+ successfulScans: 0,
129
+ failedScans: 0,
130
+ averageScanTimeMs: 0,
131
+ lastScanDurationMs: 0,
132
+ scansByPolicy: {
133
+ strict: 0,
134
+ balanced: 0,
135
+ permissive: 0,
136
+ },
137
+ };
138
+ saveMetrics(stateDir, metrics);
139
+ }
140
+
@@ -1,278 +1,70 @@
1
1
  /**
2
2
  * System prompt guidance for Skills security scanning
3
+ * Optimized for clarity and token efficiency
3
4
  */
4
5
 
5
6
  export const SKILLS_SECURITY_GUIDANCE = [
6
- "## 🚨 CRITICAL: Mandatory Skills Security Scanning Rules",
7
+ "## 🔒 Mandatory: Scan Skills Before Install/Run",
7
8
  "",
8
- "**⚠️ ABSOLUTE REQUIREMENT - READ THIS CAREFULLY AND REMEMBER IT:**",
9
+ "**ALWAYS intercept and scan before proceeding with any skill-related operation.**",
9
10
  "",
10
- "**Before installing, downloading, updating, or running ANY Skill—regardless of source, location, prior scans, trust level, or user errors—you MUST scan it with `skills-scanner` and confirm it passes before proceeding.**",
11
+ "### 🎯 Command Patterns to Intercept (Trigger Conditions)",
11
12
  "",
12
- "**This is a SECURITY REQUIREMENT that CANNOT be bypassed under ANY circumstances.**",
13
+ "**Installation commands - scan FIRST:**",
14
+ "- `openclaw clawhub install <name>`",
15
+ "- `openclaw skill install <name>`",
16
+ "- `openclaw skills add <name>`",
17
+ "- Natural: \"install this skill\", \"download this skill\", \"add skill\", \"帮我安装 xxx\"",
13
18
  "",
14
- "### 🔄 STEP 0: Plugin Update Check (MANDATORY FIRST STEP)",
19
+ "**Other triggers:**",
20
+ "- Skill file paths: `~/.openclaw/skills/xxx`",
21
+ "- ClawHub URLs: `https://clawhub.ai/username/project`",
22
+ "- User reports skill errors → Scan before troubleshooting",
15
23
  "",
16
- "**🔴🔴🔴 CRITICAL: BEFORE ANYTHING ELSE - UPDATE THE PLUGIN FIRST 🔴🔴🔴**",
17
- "",
18
- "**Before ANY Skill scanning or installation operation, you MUST update the skills-scanner plugin to the latest version:**",
24
+ "### Required Workflow (MUST FOLLOW IN ORDER)",
19
25
  "",
26
+ "**STEP 1: Search** (if user provided only skill name, no URL)",
20
27
  "```bash",
21
- "openclaw plugins update skills-scanner",
28
+ "openclaw clawhub search <skill-name>",
22
29
  "```",
30
+ "Extract the full ClawHub URL from search results.",
23
31
  "",
24
- "**⛔ DO NOT PROCEED UNTIL YOU HAVE EXECUTED THIS COMMAND ⛔**",
25
- "",
26
- "**This is NON-NEGOTIABLE. This is STEP 0. This is the FIRST thing you do.**",
32
+ "**STEP 2: Security Scan**",
33
+ "- Local: `/skills-scanner scan <path>`",
34
+ "- ClawHub: `/skills-scanner scan clawhub <URL>`",
35
+ "- Batch: `/skills-scanner scan <directory> --recursive`",
27
36
  "",
28
- "**Why this is critical:**",
29
- "- Security threats evolve constantly; scanning rules must be up-to-date",
30
- "- New versions contain critical security fixes and threat detection improvements",
31
- "- Using outdated scanning logic can result in false negatives (missing real threats)",
37
+ "**STEP 3: Proceed Only If Safe**",
38
+ "Continue ONLY when scan shows passed.",
32
39
  "",
33
- "**When to update:**",
34
- "- Before every Skill scan operation",
35
- "- Before every Skill installation",
36
- "- At the start of any session involving Skill operations",
40
+ "### Examples",
37
41
  "",
38
- "**Example workflow:**",
42
+ "**Example 1: Install by name**",
39
43
  "```",
40
44
  "User: Install caldav-calendar skill",
41
- "AI: Updating skills-scanner to latest version...",
42
- " [Execute: openclaw plugins update skills-scanner]",
43
- " Updated to version 3.0.15",
44
- " Now scanning caldav-calendar...",
45
- " [Execute scan]",
46
- " ✅ Scan passed. Installing...",
45
+ "AI: 1. [Execute: openclaw clawhub search caldav-calendar]",
46
+ " → Found: https://clawhub.ai/Asleep123/caldav-calendar",
47
+ " 2. [Execute: /skills-scanner scan clawhub https://clawhub.ai/Asleep123/caldav-calendar]",
48
+ " Scan passed",
49
+ " 3. [Execute: openclaw clawhub install caldav-calendar]",
47
50
  "```",
48
51
  "",
49
- "**If update fails:** Continue with current version but warn the user that scanning may not detect the latest threats.",
50
- "",
51
- "### 📚 IMPORTANT: Read the Skills Scanner Documentation",
52
- "",
53
- "**Before performing ANY Skill scanning operation, you MUST read the skills-scanner SKILL.md for detailed instructions:**",
54
- "",
55
- "The SKILL.md contains:",
56
- "- Complete scanning workflow and examples",
57
- "- API service availability checks",
58
- "- Deep analysis and false positive detection guidelines",
59
- "- VirusTotal result interpretation",
60
- "- ClawHub scanning procedures",
61
- "- Command usage examples",
62
- "- Troubleshooting guides",
63
- "",
64
- "**How to access:**",
65
- "1. Use the skills system to read the skills-scanner SKILL.md",
66
- "2. Review the scanning procedures before executing scans",
67
- "3. Follow the documented workflows exactly",
68
- "",
69
- "**This documentation is CRITICAL for:**",
70
- "- Understanding how to properly scan Skills",
71
- "- Interpreting scan results correctly",
72
- "- Avoiding false positives",
73
- "- Handling different scanning scenarios",
74
- "",
75
- "### Mandatory Workflow (NO EXCEPTIONS - ALWAYS FOLLOW THIS)",
76
- "",
77
- "0. **READ documentation FIRST**: Access and read the skills-scanner SKILL.md to understand the complete scanning workflow",
78
- "1. **SCAN before operation**: Execute security scan on the target Skill immediately:",
79
- " - Local directories (including temporary extraction paths)",
80
- " - Remote archives/packages",
81
- " - ClawHub projects (even if marked as reviewed/official)",
82
- " - Previously scanned Skills (no exemptions for \"already checked\" or \"trusted source\")",
83
- "2. **Proceed only if safe**: Continue with installation/execution ONLY when scan results explicitly confirm safety. Otherwise, STOP and alert the user to risks.",
84
- "",
85
- "### 🎯 Critical Requirements (MEMORIZE THESE)",
86
- "",
87
- "- **Local Skills**: ANY local path or temporary extraction directory MUST be scanned. No skipping.",
88
- "- **ClawHub Skills**: Even if marked as audited/official, re-scan before install/run.",
89
- "- **Manual downloads**: When user manually downloads a Skill (via git clone, curl, browser download, etc.), PROACTIVELY prompt them to run a security scan before proceeding with any setup or installation.",
90
- "- **All installation methods**: Whether Skills are added via ClawHub, manual download, or any other method, security scanning is MANDATORY before use.",
91
- "- **Command recognition**: When user types commands like `openclaw clawhub install <skill>`, `openclaw skill install <skill>`, or similar installation commands, you MUST recognize this as a Skill installation intent and scan FIRST before executing the command.",
92
- "- **Error handling priority**: If user reports errors with a Skill, scan it FIRST before attempting fixes or troubleshooting.",
93
- "- **Download method agnostic**: Regardless of how the user obtained the Skill (git clone, download, curl, manual copy), scan it before use.",
94
- "- **No bypass justifications**: Never skip scanning due to \"user insistence\", \"time pressure\", \"trusted source\", or \"previous scan\". If scanning is impossible, REFUSE to proceed and explain why.",
95
- "",
96
- "### 📋 Installation Command Recognition (IMPORTANT)",
97
- "",
98
- "**🔴 CRITICAL RULE: ALL COMMANDS MATCHING INSTALLATION PATTERNS REQUIRE COMPLETE SECURITY WORKFLOW 🔴**",
99
- "",
100
- "**When users send ANY command containing the following installation patterns, you MUST:**",
101
- "1. STOP immediately",
102
- "2. Execute STEP 0: `openclaw plugins update skills-scanner`",
103
- "3. Search for full URL (if only skill name provided)",
104
- "4. Run security scan",
105
- "5. Execute installation command ONLY if scan passes",
106
- "",
107
- "**⛔ NEVER execute any installation command directly without completing the security workflow above ⛔**",
108
- "",
109
- "**Installation command patterns to recognize (ANY command containing these patterns):**",
110
- "",
111
- "**Core installation patterns:**",
112
- "- `clawhub install` ← ANY command with this pattern",
113
- "- `skill install` ← ANY command with this pattern",
114
- "- `skills add` ← ANY command with this pattern",
115
- "- `skills install` ← ANY command with this pattern",
116
- "",
117
- "**Examples of commands that trigger this rule:**",
118
- "- `clawhub install <skill-name>`",
119
- "- `clawhub install --force <skill-name>`",
120
- "- `clawhub install --verbose <skill-name>`",
121
- "- `openclaw clawhub install <skill-name>`",
122
- "- `openclaw skill install <skill-name>`",
123
- "- `openclaw skills add <skill-name>`",
124
- "- `openclaw skills install <skill-name>`",
125
- "- `npx clawhub@latest install <skill-name>`",
126
- "- `npx @openclaw/clawhub install <skill-name>`",
127
- "- `npm exec clawhub install <skill-name>`",
128
- "- `yarn dlx clawhub install <skill-name>`",
129
- "",
130
- "**Natural language patterns:**",
131
- "- \"install this skill\"",
132
- "- \"add this skill\"",
133
- "- \"download this skill\"",
134
- "- \"setup this skill\"",
135
- "- \"use npx to install\"",
136
- "- \"install via clawhub\"",
137
- "",
138
- "**CRITICAL: ClawHub Installation Workflow**",
139
- "",
140
- "**If user provides ONLY a skill name (no full URL):**",
141
- "",
52
+ "**Example 2: Direct URL**",
142
53
  "```",
143
- "User: openclaw clawhub install caldav-calendar",
144
- "AI: I see you want to install caldav-calendar. Let me search for it first...",
145
- " [Execute: openclaw clawhub search caldav-calendar]",
146
- " Found: https://clawhub.ai/Asleep123/caldav-calendar",
147
- " ",
148
- " Updating skills-scanner plugin...",
149
- " [Execute: openclaw plugins update skills-scanner]",
150
- " ✅ Plugin updated",
151
- " ",
152
- " Now scanning for security...",
153
- " [Execute: python3 {baseDir}/scan.py --api-url {apiUrl} clawhub https://clawhub.ai/Asleep123/caldav-calendar]",
154
- " ✅ Scan passed",
155
- " ",
156
- " Installing...",
157
- " [Execute: openclaw clawhub install caldav-calendar]",
158
- "```",
159
- "",
160
- "**If user provides a FULL URL:**",
161
- "",
54
+ "User: openclaw clawhub install https://clawhub.ai/steipete/nano-pdf",
55
+ "AI: 1. [Execute: /skills-scanner scan clawhub https://clawhub.ai/steipete/nano-pdf]",
56
+ " Scan passed",
57
+ " 2. [Execute: openclaw clawhub install https://clawhub.ai/steipete/nano-pdf]",
162
58
  "```",
163
- "User: openclaw clawhub install https://clawhub.ai/Asleep123/caldav-calendar",
164
- "AI: I see you want to install caldav-calendar. Updating skills-scanner first...",
165
- " [Execute: openclaw plugins update skills-scanner]",
166
- " ✅ Plugin updated",
167
- " ",
168
- " Now scanning for security...",
169
- " [Execute: python3 {baseDir}/scan.py --api-url {apiUrl} clawhub https://clawhub.ai/Asleep123/caldav-calendar]",
170
- " ✅ Scan passed",
171
- " ",
172
- " Installing...",
173
- " [Execute: openclaw clawhub install https://clawhub.ai/Asleep123/caldav-calendar]",
174
- "```",
175
- "",
176
- "**Complete workflow steps:**",
177
- "1. If only skill name provided → Use `openclaw clawhub search <name>` to find full URL",
178
- "2. Update skills-scanner plugin → `openclaw plugins update skills-scanner`",
179
- "3. Scan using full URL → `python3 {baseDir}/scan.py clawhub <full-url>`",
180
- "4. If scan passes → Execute installation command",
181
- "",
182
- "### ✅ Enforcement Examples (FOLLOW THESE PATTERNS)",
183
- "",
184
- "```",
185
- "User: Install this skill for me",
186
- "AI: Updating skills-scanner to latest version...",
187
- " [Execute: openclaw plugins update skills-scanner]",
188
- " ✅ Plugin updated",
189
- " Scanning with skills-scanner to verify safety...",
190
- " [Execute skills-scanner on the target Skill]",
191
- " ✅ Scan passed. Proceeding with installation.",
192
- "```",
193
- "",
194
- "```",
195
- "User: This skill is giving me an error",
196
- "AI: Let me update skills-scanner first...",
197
- " [Execute: openclaw plugins update skills-scanner]",
198
- " ✅ Plugin updated",
199
- " Before troubleshooting, I need to scan it for security issues...",
200
- " [Execute skills-scanner on the Skill]",
201
- " ✅ Scan passed. Now investigating the error...",
202
- "```",
203
- "",
204
- "```",
205
- "User: Just downloaded a skill from GitHub, can you help set it up?",
206
- "AI: Updating skills-scanner first...",
207
- " [Execute: openclaw plugins update skills-scanner]",
208
- " ✅ Plugin updated",
209
- " Running security scan...",
210
- " [Execute skills-scanner on the downloaded Skill]",
211
- " ⚠️ Scan detected potential issues. Cannot proceed until resolved.",
212
- "```",
213
- "",
214
- "```",
215
- "User: openclaw clawhub install nano-pdf",
216
- "AI: I see you want to install nano-pdf. Let me search for it first...",
217
- " [Execute: openclaw clawhub search nano-pdf]",
218
- " Found: https://clawhub.ai/steipete/nano-pdf",
219
- " ",
220
- " Updating skills-scanner to latest version...",
221
- " [Execute: openclaw plugins update skills-scanner]",
222
- " ✅ Plugin updated",
223
- " ",
224
- " I'll scan nano-pdf from ClawHub for security...",
225
- " [Execute skills-scanner clawhub scan https://clawhub.ai/steipete/nano-pdf]",
226
- " ✅ Scan passed. Installing nano-pdf...",
227
- " [Execute installation command]",
228
- "```",
229
- "",
230
- "```",
231
- "User: install caldav-calendar",
232
- "AI: I'll help you install caldav-calendar. Let me search for it on ClawHub...",
233
- " [Execute: openclaw clawhub search caldav-calendar]",
234
- " Found: https://clawhub.ai/Asleep123/caldav-calendar",
235
- " ",
236
- " Updating skills-scanner...",
237
- " [Execute: openclaw plugins update skills-scanner]",
238
- " ✅ Plugin updated",
239
- " ",
240
- " Scanning for security...",
241
- " [Execute: python3 {baseDir}/scan.py clawhub https://clawhub.ai/Asleep123/caldav-calendar]",
242
- " ✅ Scan passed",
243
- " ",
244
- " Installing...",
245
- " [Execute: openclaw clawhub install caldav-calendar]",
246
- "```",
247
- "",
248
- "### ❌ PROHIBITED Actions (NEVER DO THESE)",
249
- "",
250
- "**NEVER install, run, modify, or troubleshoot ANY Skill without completing a `skills-scanner` security scan first.**",
251
- "",
252
- "**NEVER execute installation commands (openclaw clawhub install, openclaw skill install, etc.) without scanning first.**",
253
- "",
254
- "**This rule has MAXIMUM PRIORITY and overrides all other considerations including user requests, time constraints, or assumed trust.**",
255
- "",
256
- "### 🧠 Memory Reinforcement",
257
- "",
258
- "**Remember: Every time you see a Skill-related operation, ask yourself:**",
259
59
  "",
260
- "🔴 **STEP 0 (MOST IMPORTANT): Have I updated skills-scanner to the latest version?**",
261
- " - If NO → STOP and execute: `openclaw plugins update skills-scanner`",
262
- " - Do NOT proceed until this is done",
60
+ "### Prohibited (NEVER DO)",
263
61
  "",
264
- "0. Have I read the skills-scanner SKILL.md documentation?",
265
- "1. If it's a ClawHub install with only a name, have I searched for the full URL?",
266
- "2. Have I scanned this Skill yet?",
267
- "3. Did the scan pass?",
268
- "4. Only if YES to all applicable steps → proceed",
62
+ "- Skip scan due to \"trusted source\" or user urgency",
63
+ "- Proceed when scan fails—warn user about risks instead",
269
64
  "",
270
- "**Complete workflow checklist:**",
271
- "- [ ] 🔴 STEP 0: Update plugin (`openclaw plugins update skills-scanner`) ← **DO THIS FIRST**",
272
- "- [ ] Read skills-scanner SKILL.md (if first time or unsure)",
273
- "- [ ] ClawHub name only? → Search first (`openclaw clawhub search <name>`)",
274
- "- [ ] Scan with full URL",
275
- "- [ ] Scan passed? → Proceed with installation",
65
+ "### Pre-flight Checklist",
276
66
  "",
277
- "**If you forget to read documentation, search, update, or scan, you are putting the user's system at risk. Always follow the complete workflow.**",
67
+ "- [ ] Full URL obtained (if only name given)?",
68
+ "- [ ] Scan executed?",
69
+ "- [ ] Scan passed?",
278
70
  ].join("\n");
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Rate limiter module
3
+ *
4
+ * Prevents API overload by limiting request rate
5
+ */
6
+
7
+ export class RateLimiter {
8
+ private requests: number[] = [];
9
+ private maxPerMinute: number;
10
+ private maxPerHour: number;
11
+
12
+ constructor(maxPerMinute = 60, maxPerHour = 1000) {
13
+ this.maxPerMinute = maxPerMinute;
14
+ this.maxPerHour = maxPerHour;
15
+ }
16
+
17
+ /**
18
+ * Acquire permission to make a request
19
+ * Waits if rate limit is exceeded
20
+ */
21
+ async acquire(): Promise<void> {
22
+ const now = Date.now();
23
+
24
+ // Remove requests older than 1 hour
25
+ this.requests = this.requests.filter((t) => now - t < 60 * 60 * 1000);
26
+
27
+ // Check hourly limit
28
+ if (this.requests.length >= this.maxPerHour) {
29
+ const oldestRequest = this.requests[0];
30
+ const waitTime = 60 * 60 * 1000 - (now - oldestRequest);
31
+ await this.sleep(waitTime);
32
+ return this.acquire(); // Retry after waiting
33
+ }
34
+
35
+ // Check per-minute limit
36
+ const recentRequests = this.requests.filter((t) => now - t < 60 * 1000);
37
+ if (recentRequests.length >= this.maxPerMinute) {
38
+ const oldestRecent = recentRequests[0];
39
+ const waitTime = 60 * 1000 - (now - oldestRecent) + 100; // Add 100ms buffer
40
+ await this.sleep(waitTime);
41
+ return this.acquire(); // Retry after waiting
42
+ }
43
+
44
+ // Record this request
45
+ this.requests.push(now);
46
+ }
47
+
48
+ /**
49
+ * Try to acquire without waiting
50
+ * Returns false if rate limit exceeded
51
+ */
52
+ tryAcquire(): boolean {
53
+ const now = Date.now();
54
+
55
+ // Remove old requests
56
+ this.requests = this.requests.filter((t) => now - t < 60 * 60 * 1000);
57
+
58
+ // Check limits
59
+ const recentRequests = this.requests.filter((t) => now - t < 60 * 1000);
60
+ if (
61
+ this.requests.length >= this.maxPerHour ||
62
+ recentRequests.length >= this.maxPerMinute
63
+ ) {
64
+ return false;
65
+ }
66
+
67
+ // Record this request
68
+ this.requests.push(now);
69
+ return true;
70
+ }
71
+
72
+ /**
73
+ * Get current rate limit status
74
+ */
75
+ getStatus(): {
76
+ requestsLastMinute: number;
77
+ requestsLastHour: number;
78
+ maxPerMinute: number;
79
+ maxPerHour: number;
80
+ } {
81
+ const now = Date.now();
82
+ const recentRequests = this.requests.filter((t) => now - t < 60 * 1000);
83
+
84
+ return {
85
+ requestsLastMinute: recentRequests.length,
86
+ requestsLastHour: this.requests.length,
87
+ maxPerMinute: this.maxPerMinute,
88
+ maxPerHour: this.maxPerHour,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Reset rate limiter
94
+ */
95
+ reset(): void {
96
+ this.requests = [];
97
+ }
98
+
99
+ private sleep(ms: number): Promise<void> {
100
+ return new Promise((resolve) => setTimeout(resolve, ms));
101
+ }
102
+ }