commit 7f9211b22c6de41386b5fde8bce0df511c9ef273 Author: Zhongwei Li Date: Sat Nov 29 18:52:31 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..9e3967b --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,15 @@ +{ + "name": "api-rate-limiter", + "description": "Implement rate limiting with token bucket, sliding window, and Redis", + "version": "1.0.0", + "author": { + "name": "Jeremy Longshore", + "email": "[email protected]" + }, + "skills": [ + "./skills" + ], + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e13826 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# api-rate-limiter + +Implement rate limiting with token bucket, sliding window, and Redis diff --git a/commands/add-rate-limiting.md b/commands/add-rate-limiting.md new file mode 100644 index 0000000..13acbe2 --- /dev/null +++ b/commands/add-rate-limiting.md @@ -0,0 +1,655 @@ +--- +description: Add rate limiting to API endpoints +shortcut: ratelimit +--- + +# Add Rate Limiting to API Endpoints + +Implement production-ready rate limiting with token bucket, sliding window, or fixed window algorithms using Redis for distributed state management. + +## When to Use This Command + +Use `/add-rate-limiting` when you need to: +- Protect APIs from abuse and DDoS attacks +- Enforce fair usage policies across user tiers +- Prevent resource exhaustion from runaway clients +- Comply with downstream API rate limits +- Implement freemium pricing models with usage tiers +- Control costs for expensive operations (AI inference, video processing) + +DON'T use this when: +- Building internal-only APIs with trusted clients (use circuit breakers instead) +- Single-user applications (no shared resource contention) +- Already behind API gateway with built-in rate limiting (avoid double limiting) + +## Design Decisions + +This command implements **Token Bucket algorithm with Redis** as the primary approach because: +- Allows burst traffic while maintaining average rate (better UX) +- Distributed state enables horizontal scaling +- Redis atomic operations prevent race conditions +- Standard algorithm with well-understood behavior + +**Alternative considered: Sliding Window** +- More accurate rate limiting (no reset boundary issues) +- Higher Redis memory usage (stores timestamp per request) +- Slightly higher computational overhead +- Recommended for strict compliance requirements + +**Alternative considered: Fixed Window** +- Simplest implementation (single counter) +- Burst at window boundaries (2x limit possible) +- Lower memory footprint +- Recommended only for non-critical rate limiting + +**Alternative considered: Leaky Bucket** +- Constant output rate (smooths bursty traffic) +- Complex to explain to users +- Less common in practice +- Recommended for queuing systems, not APIs + +## Prerequisites + +Before running this command: +1. Redis server installed and accessible (standalone or cluster) +2. Node.js/Python runtime for middleware implementation +3. API framework that supports middleware (Express, FastAPI, etc.) +4. Understanding of your API usage patterns and SLO requirements +5. Monitoring infrastructure to track rate limit metrics + +## Implementation Process + +### Step 1: Choose Rate Limiting Strategy +Select algorithm based on requirements: Token Bucket for user-facing APIs, Sliding Window for strict compliance, Fixed Window for internal APIs. + +### Step 2: Configure Redis Connection +Set up Redis client with connection pooling, retry logic, and failover handling for high availability. + +### Step 3: Implement Rate Limiter Middleware +Create middleware that intercepts requests, checks Redis state, and enforces limits with proper HTTP headers. + +### Step 4: Define Rate Limit Tiers +Configure different limits for user segments (anonymous, free, premium, enterprise) based on business requirements. + +### Step 5: Add Monitoring and Alerting +Instrument rate limiter with metrics for blocked requests, Redis latency, and tier usage patterns. + +## Output Format + +The command generates: +- `rate-limiter.js` or `rate_limiter.py` - Core rate limiting middleware +- `redis-config.js` - Redis connection configuration with failover +- `rate-limit-tiers.json` - Tiered limit definitions +- `rate-limiter.test.js` - Comprehensive test suite +- `README.md` - Integration guide and configuration options +- `docker-compose.yml` - Redis setup for local development + +## Code Examples + +### Example 1: Token Bucket Rate Limiter with Express and Redis + +```javascript +// rate-limiter.js +const Redis = require('ioredis'); + +class TokenBucketRateLimiter { + constructor(redisClient, options = {}) { + this.redis = redisClient; + this.defaultOptions = { + points: 100, // Number of tokens + duration: 60, // Time window in seconds + blockDuration: 60, // Block duration after limit exceeded + keyPrefix: 'rl', // Redis key prefix + ...options + }; + } + + /** + * Token bucket algorithm using Redis + * Returns: { allowed: boolean, remaining: number, resetTime: number } + */ + async consume(identifier, points = 1, options = {}) { + const opts = { ...this.defaultOptions, ...options }; + const key = `${opts.keyPrefix}:${identifier}`; + const now = Date.now(); + + // Lua script for atomic token bucket operations + const luaScript = ` + local key = KEYS[1] + local capacity = tonumber(ARGV[1]) + local refill_rate = tonumber(ARGV[2]) + local requested = tonumber(ARGV[3]) + local now = tonumber(ARGV[4]) + local ttl = tonumber(ARGV[5]) + + -- Get current state or initialize + local tokens = tonumber(redis.call('HGET', key, 'tokens')) + local last_refill = tonumber(redis.call('HGET', key, 'last_refill')) + + if not tokens then + tokens = capacity + last_refill = now + end + + -- Calculate tokens to add since last refill + local time_passed = now - last_refill + local tokens_to_add = math.floor(time_passed * refill_rate) + tokens = math.min(capacity, tokens + tokens_to_add) + last_refill = now + + -- Check if we can fulfill request + if tokens >= requested then + tokens = tokens - requested + redis.call('HMSET', key, 'tokens', tokens, 'last_refill', last_refill) + redis.call('EXPIRE', key, ttl) + return {1, tokens, last_refill} + else + redis.call('HMSET', key, 'tokens', tokens, 'last_refill', last_refill) + redis.call('EXPIRE', key, ttl) + return {0, tokens, last_refill} + end + `; + + const refillRate = opts.points / opts.duration; + const result = await this.redis.eval( + luaScript, + 1, + key, + opts.points, + refillRate, + points, + now, + opts.duration + ); + + const [allowed, remaining, lastRefill] = result; + const resetTime = lastRefill + (opts.duration * 1000); + + return { + allowed: allowed === 1, + remaining: Math.floor(remaining), + resetTime: new Date(resetTime).toISOString(), + retryAfter: allowed === 1 ? null : Math.ceil((opts.duration * 1000 - (now - lastRefill)) / 1000) + }; + } + + /** + * Express middleware factory + */ + middleware(getTier = null) { + return async (req, res, next) => { + try { + // Determine identifier (user ID or IP) + const identifier = req.user?.id || req.ip; + + // Get tier configuration + const tier = getTier ? await getTier(req) : 'default'; + const tierConfig = this.getTierConfig(tier); + + // Consume tokens + const result = await this.consume(identifier, 1, tierConfig); + + // Set rate limit headers + res.set({ + 'X-RateLimit-Limit': tierConfig.points, + 'X-RateLimit-Remaining': result.remaining, + 'X-RateLimit-Reset': result.resetTime + }); + + if (!result.allowed) { + res.set('Retry-After', result.retryAfter); + return res.status(429).json({ + error: 'Too Many Requests', + message: `Rate limit exceeded. Try again in ${result.retryAfter} seconds.`, + retryAfter: result.retryAfter + }); + } + + next(); + } catch (error) { + console.error('Rate limiter error:', error); + // Fail open to avoid blocking all traffic on Redis failure + next(); + } + }; + } + + getTierConfig(tier) { + const tiers = { + anonymous: { points: 20, duration: 60 }, + free: { points: 100, duration: 60 }, + premium: { points: 1000, duration: 60 }, + enterprise: { points: 10000, duration: 60 } + }; + return tiers[tier] || tiers.free; + } +} + +// Usage example +const redis = new Redis({ + host: process.env.REDIS_HOST || 'localhost', + port: process.env.REDIS_PORT || 6379, + retryStrategy: (times) => Math.min(times * 50, 2000), + maxRetriesPerRequest: 3 +}); + +const rateLimiter = new TokenBucketRateLimiter(redis); + +// Apply to all routes +app.use(rateLimiter.middleware(async (req) => { + if (req.user?.subscription === 'enterprise') return 'enterprise'; + if (req.user?.subscription === 'premium') return 'premium'; + if (req.user) return 'free'; + return 'anonymous'; +})); + +// Apply stricter limit to specific expensive endpoint +app.post('/api/ai/generate', + rateLimiter.middleware(() => ({ points: 10, duration: 3600 })), + handleGenerate +); + +module.exports = TokenBucketRateLimiter; +``` + +### Example 2: Sliding Window Rate Limiter in Python with FastAPI + +```python +# rate_limiter.py +import time +import redis.asyncio as aioredis +from fastapi import Request, Response, HTTPException +from typing import Optional, Callable +import asyncio + +class SlidingWindowRateLimiter: + def __init__(self, redis_client: aioredis.Redis, window_size: int = 60, max_requests: int = 100): + self.redis = redis_client + self.window_size = window_size + self.max_requests = max_requests + self.key_prefix = "rate_limit" + + async def is_allowed(self, identifier: str, tier_config: dict = None) -> dict: + """ + Sliding window algorithm using Redis sorted set + Each request is a member with score = timestamp + """ + config = tier_config or {'max_requests': self.max_requests, 'window_size': self.window_size} + now = time.time() + window_start = now - config['window_size'] + key = f"{self.key_prefix}:{identifier}" + + # Redis pipeline for atomic operations + pipe = self.redis.pipeline() + + # Remove old entries outside the window + pipe.zremrangebyscore(key, 0, window_start) + + # Count requests in current window + pipe.zcard(key) + + # Add current request + pipe.zadd(key, {str(now): now}) + + # Set expiration + pipe.expire(key, config['window_size'] + 10) + + results = await pipe.execute() + request_count = results[1] + + if request_count >= config['max_requests']: + # Get oldest request in window to calculate retry time + oldest = await self.redis.zrange(key, 0, 0, withscores=True) + if oldest: + oldest_time = oldest[0][1] + retry_after = int(config['window_size'] - (now - oldest_time)) + 1 + else: + retry_after = config['window_size'] + + return { + 'allowed': False, + 'remaining': 0, + 'reset_time': int(now + retry_after), + 'retry_after': retry_after + } + + remaining = config['max_requests'] - request_count - 1 + reset_time = int(now + config['window_size']) + + return { + 'allowed': True, + 'remaining': remaining, + 'reset_time': reset_time, + 'retry_after': None + } + + def middleware(self, get_tier: Optional[Callable] = None): + """FastAPI middleware factory""" + async def rate_limit_middleware(request: Request, call_next): + # Get identifier (user ID or IP) + identifier = getattr(request.state, 'user_id', None) or request.client.host + + # Get tier configuration + tier_config = None + if get_tier: + tier_config = await get_tier(request) + + # Check rate limit + result = await self.is_allowed(identifier, tier_config) + + # Always set rate limit headers + response = None + if result['allowed']: + response = await call_next(request) + else: + response = Response( + content=f'{{"error": "Rate limit exceeded", "retry_after": {result["retry_after"]}}}', + status_code=429, + media_type="application/json" + ) + + response.headers['X-RateLimit-Limit'] = str(tier_config['max_requests'] if tier_config else self.max_requests) + response.headers['X-RateLimit-Remaining'] = str(result['remaining']) + response.headers['X-RateLimit-Reset'] = str(result['reset_time']) + + if not result['allowed']: + response.headers['Retry-After'] = str(result['retry_after']) + + return response + + return rate_limit_middleware + +# Usage in FastAPI +from fastapi import FastAPI +from contextlib import asynccontextmanager + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Startup + app.state.redis = await aioredis.from_url("redis://localhost:6379") + app.state.rate_limiter = SlidingWindowRateLimiter(app.state.redis) + yield + # Shutdown + await app.state.redis.close() + +app = FastAPI(lifespan=lifespan) + +async def get_user_tier(request: Request) -> dict: + """Determine user tier from request""" + user = getattr(request.state, 'user', None) + if not user: + return {'max_requests': 20, 'window_size': 60} # Anonymous + elif user.get('subscription') == 'enterprise': + return {'max_requests': 10000, 'window_size': 60} + elif user.get('subscription') == 'premium': + return {'max_requests': 1000, 'window_size': 60} + else: + return {'max_requests': 100, 'window_size': 60} # Free tier + +# Apply rate limiting middleware +app.middleware("http")(app.state.rate_limiter.middleware(get_user_tier)) +``` + +### Example 3: DDoS Protection with Multi-Layer Rate Limiting + +```javascript +// advanced-rate-limiter.js - Multi-layer protection +const Redis = require('ioredis'); + +class MultiLayerRateLimiter { + constructor(redisClient) { + this.redis = redisClient; + } + + /** + * Layered rate limiting strategy: + * 1. IP-based (DDoS protection) + * 2. User-based (fair usage) + * 3. Endpoint-specific (expensive operations) + */ + async checkLayers(req) { + const layers = [ + // Layer 1: IP-based rate limiting (DDoS protection) + { + name: 'ip', + identifier: req.ip, + limits: { points: 1000, duration: 60 }, // 1000 req/min per IP + priority: 'high' + }, + // Layer 2: User-based rate limiting + { + name: 'user', + identifier: req.user?.id || `anon:${req.ip}`, + limits: this.getUserTierLimits(req.user), + priority: 'medium' + }, + // Layer 3: Endpoint-specific limiting + { + name: 'endpoint', + identifier: `${req.user?.id || req.ip}:${req.path}`, + limits: this.getEndpointLimits(req.path), + priority: 'low' + } + ]; + + for (const layer of layers) { + const result = await this.checkLimit(layer); + if (!result.allowed) { + return { + blocked: true, + layer: layer.name, + ...result + }; + } + } + + return { blocked: false }; + } + + async checkLimit(layer) { + const key = `rl:${layer.name}:${layer.identifier}`; + const now = Date.now(); + + const count = await this.redis.incr(key); + + if (count === 1) { + await this.redis.expire(key, layer.limits.duration); + } + + const ttl = await this.redis.ttl(key); + const allowed = count <= layer.limits.points; + + return { + allowed, + remaining: Math.max(0, layer.limits.points - count), + resetTime: now + (ttl * 1000), + retryAfter: allowed ? null : ttl + }; + } + + getUserTierLimits(user) { + if (!user) return { points: 20, duration: 60 }; + const tiers = { + free: { points: 100, duration: 60 }, + premium: { points: 1000, duration: 60 }, + enterprise: { points: 10000, duration: 60 } + }; + return tiers[user.subscription] || tiers.free; + } + + getEndpointLimits(path) { + const expensiveEndpoints = { + '/api/ai/generate': { points: 10, duration: 3600 }, // 10/hour + '/api/video/render': { points: 5, duration: 3600 }, // 5/hour + '/api/export/large': { points: 20, duration: 3600 } // 20/hour + }; + return expensiveEndpoints[path] || { points: 1000, duration: 60 }; + } + + middleware() { + return async (req, res, next) => { + try { + const result = await this.checkLayers(req); + + if (result.blocked) { + res.set({ + 'X-RateLimit-Layer': result.layer, + 'X-RateLimit-Remaining': result.remaining, + 'Retry-After': result.retryAfter + }); + + return res.status(429).json({ + error: 'Rate limit exceeded', + layer: result.layer, + retryAfter: result.retryAfter, + message: `Too many requests. Please retry after ${result.retryAfter} seconds.` + }); + } + + next(); + } catch (error) { + console.error('Multi-layer rate limiter error:', error); + next(); // Fail open + } + }; + } +} + +module.exports = MultiLayerRateLimiter; +``` + +## Error Handling + +| Error | Cause | Solution | +|-------|-------|----------| +| "Redis connection failed" | Redis server unreachable | Check Redis server status, verify connection string, implement connection retry | +| "Rate limiter fail-closed" | Redis timeout, middleware blocking all traffic | Implement fail-open strategy with circuit breaker pattern | +| "Inconsistent rate limits" | Clock skew across servers | Use Redis time (`TIME` command) instead of server time | +| "Memory exhaustion" | Too many keys, no TTL set | Always set TTL on rate limit keys, use key expiration monitoring | +| "False positives from NAT" | Multiple users behind same IP | Use authenticated user IDs when available, consider X-Forwarded-For | + +## Configuration Options + +**Rate Limit Algorithms** +- **Token Bucket**: Best for user-facing APIs with burst allowance +- **Sliding Window**: Most accurate, higher memory usage +- **Fixed Window**: Simplest, allows boundary bursts +- **Leaky Bucket**: Constant rate, complex UX + +**Tier Definitions** +```json +{ + "anonymous": { "points": 20, "duration": 60 }, + "free": { "points": 100, "duration": 60 }, + "premium": { "points": 1000, "duration": 60 }, + "enterprise": { "points": 10000, "duration": 60 } +} +``` + +**Redis Configuration** +- **Connection pooling**: Minimum 5 connections +- **Retry strategy**: Exponential backoff up to 2s +- **Failover**: Redis Sentinel or Cluster for HA +- **Persistence**: AOF for rate limit state recovery + +## Best Practices + +DO: +- Return standard rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) +- Implement graceful degradation (fail open on Redis failure) +- Use user ID over IP when authenticated (avoids NAT issues) +- Set TTL on all Redis keys to prevent memory leaks +- Monitor rate limiter performance (latency, block rate) +- Provide clear error messages with retry guidance + +DON'T: +- Block legitimate traffic (tune limits based on real usage) +- Use client-side rate limiting only (easily bypassed) +- Forget to handle Redis connection failures (causes complete outage) +- Implement synchronous Redis calls (adds latency to every request) +- Use rate limiting as only defense against DDoS (need multiple layers) + +TIPS: +- Start conservative, increase limits based on monitoring +- Use different limits for different operations (read vs write) +- Implement per-endpoint rate limits for expensive operations +- Cache tier lookups to reduce database queries +- Log rate limit violations for security analysis +- Provide upgrade paths for users hitting limits + +## Performance Considerations + +**Latency Impact** +- Token bucket: 1-2ms added to request (single Redis call) +- Sliding window: 2-4ms (multiple Redis operations) +- With pipelining: <1ms for all algorithms + +**Redis Memory Usage** +- Token bucket: ~100 bytes per user +- Sliding window: ~50 bytes per request in window +- Fixed window: ~50 bytes per user per window + +**Throughput** +- Redis can handle 100k+ operations/second +- Use Redis Cluster for horizontal scaling +- Pipeline Redis operations when possible +- Consider local caching for extremely high throughput + +## Security Considerations + +1. **DDoS Protection**: Implement IP-based rate limiting as first layer +2. **Credential Stuffing**: Add stricter limits on authentication endpoints +3. **API Scraping**: Implement progressive delays for repeated violations +4. **Distributed Attacks**: Use shared Redis across all API servers +5. **Bypass Attempts**: Validate X-Forwarded-For headers, don't trust blindly +6. **State Consistency**: Use Redis transactions to prevent race conditions + +## Troubleshooting + +**Rate Limits Not Enforced** +```bash +# Check Redis connectivity +redis-cli -h localhost -p 6379 ping + +# Verify keys are being created +redis-cli --scan --pattern 'rl:*' | head -10 + +# Check TTL is set correctly +redis-cli TTL rl:user:123456 +``` + +**Too Many False Positives** +```bash +# Review blocked requests by IP +redis-cli --scan --pattern 'rl:ip:*' | xargs redis-cli MGET + +# Check tier assignments +# Review application logs for tier calculation + +# Analyze legitimate traffic patterns +# Adjust limits based on p95/p99 usage +``` + +**Redis Memory Issues** +```bash +# Check memory usage +redis-cli INFO memory + +# Count rate limit keys +redis-cli --scan --pattern 'rl:*' | wc -l + +# Review keys without TTL +redis-cli --scan --pattern 'rl:*' | xargs redis-cli TTL | grep -c "^-1" +``` + +## Related Commands + +- `/create-monitoring` - Monitor rate limit metrics and violations +- `/api-authentication-builder` - Integrate with auth for user-based limits +- `/api-load-tester` - Test rate limiter under realistic load +- `/setup-logging` - Log rate limit violations for analysis + +## Version History + +- v1.0.0 (2024-10): Initial implementation with token bucket and sliding window +- Planned v1.1.0: Add adaptive rate limiting based on system load diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..12e2209 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,93 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:jeremylongshore/claude-code-plugins-plus:plugins/api-development/api-rate-limiter", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "9e134796cd3834b2c114ba429138d321313b9463", + "treeHash": "4681c74fc68708fab3d65366e96ee8ff7588529d1cd66814f0ee3339dba71ed4", + "generatedAt": "2025-11-28T10:18:07.744614Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "api-rate-limiter", + "description": "Implement rate limiting with token bucket, sliding window, and Redis", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "ae54372002bde47e0fd42713f359d7f7451fff30362506befbf714360119c1b5" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "b43868c03b7d1ab25aec8e57da55ddbf19cbd1dcabc512268cbffae9add186e3" + }, + { + "path": "commands/add-rate-limiting.md", + "sha256": "5d5eac511d4c1c824706cf9eab197b1c2207112981dd5269dc0f01c257e3a1d3" + }, + { + "path": "skills/skill-adapter/references/examples.md", + "sha256": "922bbc3c4ebf38b76f515b5c1998ebde6bf902233e00e2c5a0e9176f975a7572" + }, + { + "path": "skills/skill-adapter/references/best-practices.md", + "sha256": "c8f32b3566252f50daacd346d7045a1060c718ef5cfb07c55a0f2dec5f1fb39e" + }, + { + "path": "skills/skill-adapter/references/README.md", + "sha256": "4ba21318cbcdc1e9837ad8373070e2fd0ff89dc64eebe7a0768cecc3134035a0" + }, + { + "path": "skills/skill-adapter/scripts/helper-template.sh", + "sha256": "0881d5660a8a7045550d09ae0acc15642c24b70de6f08808120f47f86ccdf077" + }, + { + "path": "skills/skill-adapter/scripts/validation.sh", + "sha256": "92551a29a7f512d2036e4f1fb46c2a3dc6bff0f7dde4a9f699533e446db48502" + }, + { + "path": "skills/skill-adapter/scripts/README.md", + "sha256": "778710b170fef62572e60d76c04c0c7f11ff40a9a4b9e405bf98caa6814d0cc1" + }, + { + "path": "skills/skill-adapter/assets/test-data.json", + "sha256": "ac17dca3d6e253a5f39f2a2f1b388e5146043756b05d9ce7ac53a0042eee139d" + }, + { + "path": "skills/skill-adapter/assets/README.md", + "sha256": "629f15d82e15a8d4fd1c36d891be86fec282dce472da17b02675c726ed7f379f" + }, + { + "path": "skills/skill-adapter/assets/rate_limit_template.conf", + "sha256": "700c91a873be9af28b4c39c495e6343f9b6bd642aeac445b42e3e66b3784eff8" + }, + { + "path": "skills/skill-adapter/assets/skill-schema.json", + "sha256": "f5639ba823a24c9ac4fb21444c0717b7aefde1a4993682897f5bf544f863c2cd" + }, + { + "path": "skills/skill-adapter/assets/error_message_template.json", + "sha256": "2b9146ce11359adafbc33ecc071cfd813ec48aa7b5b8e0e4c6ab5016d1cdb3aa" + }, + { + "path": "skills/skill-adapter/assets/config-template.json", + "sha256": "0c2ba33d2d3c5ccb266c0848fc43caa68a2aa6a80ff315d4b378352711f83e1c" + } + ], + "dirSha256": "4681c74fc68708fab3d65366e96ee8ff7588529d1cd66814f0ee3339dba71ed4" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/skill-adapter/assets/README.md b/skills/skill-adapter/assets/README.md new file mode 100644 index 0000000..5affd20 --- /dev/null +++ b/skills/skill-adapter/assets/README.md @@ -0,0 +1,6 @@ +# Assets + +Bundled resources for api-rate-limiter skill + +- [ ] rate_limit_template.conf: Template configuration file for the rate limiting implementation. +- [ ] error_message_template.json: Template for the JSON error message returned when rate limits are exceeded. diff --git a/skills/skill-adapter/assets/config-template.json b/skills/skill-adapter/assets/config-template.json new file mode 100644 index 0000000..16f1712 --- /dev/null +++ b/skills/skill-adapter/assets/config-template.json @@ -0,0 +1,32 @@ +{ + "skill": { + "name": "skill-name", + "version": "1.0.0", + "enabled": true, + "settings": { + "verbose": false, + "autoActivate": true, + "toolRestrictions": true + } + }, + "triggers": { + "keywords": [ + "example-trigger-1", + "example-trigger-2" + ], + "patterns": [] + }, + "tools": { + "allowed": [ + "Read", + "Grep", + "Bash" + ], + "restricted": [] + }, + "metadata": { + "author": "Plugin Author", + "category": "general", + "tags": [] + } +} diff --git a/skills/skill-adapter/assets/error_message_template.json b/skills/skill-adapter/assets/error_message_template.json new file mode 100644 index 0000000..d1c78b7 --- /dev/null +++ b/skills/skill-adapter/assets/error_message_template.json @@ -0,0 +1,33 @@ +{ + "_comment": "Template for error message when rate limits are exceeded", + "error": { + "code": 429, + "_comment": "HTTP status code for Too Many Requests", + "message": "Too Many Requests", + "_comment": "General error message", + "details": { + "reason": "Rate limit exceeded", + "_comment": "Specific reason for the error", + "limit": 100, + "_comment": "The allowed request limit", + "remaining": 0, + "_comment": "Requests remaining in the current window", + "reset_at": "2024-01-24T12:00:00Z", + "_comment": "ISO 8601 timestamp for when the limit resets", + "policy": "100 requests per minute", + "_comment": "Human-readable policy description", + "algorithm": "token_bucket", + "_comment": "Rate limiting algorithm used (token_bucket, sliding_window, fixed_window)", + "scope": "user", + "_comment": "Scope of the rate limit (user, ip, global)", + "key": "user_id:123", + "_comment": "Key used for rate limiting (e.g., user ID, IP address)", + "retry_after": 60, + "_comment": "Seconds to wait before retrying the request (for Retry-After header)", + "documentation_url": "https://example.com/api/rate-limiting", + "_comment": "URL to documentation about rate limiting", + "support_email": "support@example.com" + "_comment": "Support email for questions about rate limits" + } + } +} \ No newline at end of file diff --git a/skills/skill-adapter/assets/rate_limit_template.conf b/skills/skill-adapter/assets/rate_limit_template.conf new file mode 100644 index 0000000..bbe60a3 --- /dev/null +++ b/skills/skill-adapter/assets/rate_limit_template.conf @@ -0,0 +1,97 @@ +# rate_limit_template.conf + +# This is a template configuration file for the API Rate Limiter plugin. +# Fill in the placeholders with your desired values. +# Save this file as rate_limit.conf (or similar) and specify its path when configuring the plugin. + +# --- General Settings --- +# Enable/Disable rate limiting +enabled: true # Set to 'false' to disable rate limiting + +# Rate limiting algorithm: token_bucket, sliding_window, fixed_window +algorithm: token_bucket + +# Redis connection details +redis_host: localhost +redis_port: 6379 +redis_password: "" # Leave blank if no password +redis_db: 0 + +# --- Global Rate Limits (applied to all requests if no user/IP specific limits are defined) --- +global_limit_enabled: false # Enable/disable global rate limiting + +# Number of requests allowed within the specified time window +global_requests_per_window: 100 + +# Time window in seconds +global_window_seconds: 60 + +# --- Token Bucket Settings (if algorithm is set to 'token_bucket') --- +# Number of tokens in the bucket +token_bucket_capacity: 100 + +# Rate at which tokens are added to the bucket (tokens per second) +token_replenishment_rate: 10 + +# --- Sliding Window Settings (if algorithm is set to 'sliding_window') --- +# Number of requests allowed within the window +sliding_window_limit: 100 + +# Window size in seconds +sliding_window_seconds: 60 + +# --- Fixed Window Settings (if algorithm is set to 'fixed_window') --- +# Number of requests allowed within the window +fixed_window_limit: 100 + +# Window size in seconds +fixed_window_seconds: 60 + +# --- Per User/IP Rate Limits --- +# Enable per-user rate limiting (requires user identification mechanism) +user_limit_enabled: false + +# Number of requests allowed per user within the specified time window +user_requests_per_window: 50 + +# Time window in seconds for user rate limiting +user_window_seconds: 60 + +# Enable per-IP rate limiting +ip_limit_enabled: true + +# Number of requests allowed per IP within the specified time window +ip_requests_per_window: 20 + +# Time window in seconds for IP rate limiting +ip_window_seconds: 60 + +# --- Burst Handling --- +# Allow a burst of requests beyond the rate limit (e.g., for initial page load) +burst_limit_enabled: true + +# Maximum number of requests allowed in a burst +burst_limit: 20 + +# --- Rate Limit Headers --- +# Enable sending rate limit headers in the response +enable_rate_limit_headers: true + +# Header name for the rate limit +rate_limit_header: X-RateLimit-Limit + +# Header name for the remaining requests +rate_limit_remaining_header: X-RateLimit-Remaining + +# Header name for the time until the limit resets (in seconds) +rate_limit_reset_header: X-RateLimit-Reset + +# --- Advanced Settings --- +# Key prefix for storing rate limit data in Redis +redis_key_prefix: rate_limit: + +# Custom error message when rate limit is exceeded +rate_limit_exceeded_message: "Rate limit exceeded. Please try again later." + +# HTTP status code to return when rate limit is exceeded +rate_limit_exceeded_status_code: 429 # Too Many Requests \ No newline at end of file diff --git a/skills/skill-adapter/assets/skill-schema.json b/skills/skill-adapter/assets/skill-schema.json new file mode 100644 index 0000000..8dc154c --- /dev/null +++ b/skills/skill-adapter/assets/skill-schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Claude Skill Configuration", + "type": "object", + "required": ["name", "description"], + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "maxLength": 64, + "description": "Skill identifier (lowercase, hyphens only)" + }, + "description": { + "type": "string", + "maxLength": 1024, + "description": "What the skill does and when to use it" + }, + "allowed-tools": { + "type": "string", + "description": "Comma-separated list of allowed tools" + }, + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "Semantic version (x.y.z)" + } + } +} diff --git a/skills/skill-adapter/assets/test-data.json b/skills/skill-adapter/assets/test-data.json new file mode 100644 index 0000000..f0cd871 --- /dev/null +++ b/skills/skill-adapter/assets/test-data.json @@ -0,0 +1,27 @@ +{ + "testCases": [ + { + "name": "Basic activation test", + "input": "trigger phrase example", + "expected": { + "activated": true, + "toolsUsed": ["Read", "Grep"], + "success": true + } + }, + { + "name": "Complex workflow test", + "input": "multi-step trigger example", + "expected": { + "activated": true, + "steps": 3, + "toolsUsed": ["Read", "Write", "Bash"], + "success": true + } + } + ], + "fixtures": { + "sampleInput": "example data", + "expectedOutput": "processed result" + } +} diff --git a/skills/skill-adapter/references/README.md b/skills/skill-adapter/references/README.md new file mode 100644 index 0000000..85e4935 --- /dev/null +++ b/skills/skill-adapter/references/README.md @@ -0,0 +1,8 @@ +# References + +Bundled resources for api-rate-limiter skill + +- [ ] rate_limiting_algorithms.md: Explains different rate limiting algorithms (token bucket, sliding window, fixed window) in detail. +- [ ] redis_setup.md: Provides instructions on setting up Redis for distributed rate limiting. +- [ ] api_gateway_integration.md: Describes how to integrate the rate limiting logic with different API gateways (e.g., Kong, Tyk, AWS API Gateway). +- [ ] example_rate_limit_config.json: Example configuration file for rate limiting. diff --git a/skills/skill-adapter/references/best-practices.md b/skills/skill-adapter/references/best-practices.md new file mode 100644 index 0000000..3505048 --- /dev/null +++ b/skills/skill-adapter/references/best-practices.md @@ -0,0 +1,69 @@ +# Skill Best Practices + +Guidelines for optimal skill usage and development. + +## For Users + +### Activation Best Practices + +1. **Use Clear Trigger Phrases** + - Match phrases from skill description + - Be specific about intent + - Provide necessary context + +2. **Provide Sufficient Context** + - Include relevant file paths + - Specify scope of analysis + - Mention any constraints + +3. **Understand Tool Permissions** + - Check allowed-tools in frontmatter + - Know what the skill can/cannot do + - Request appropriate actions + +### Workflow Optimization + +- Start with simple requests +- Build up to complex workflows +- Verify each step before proceeding +- Use skill consistently for related tasks + +## For Developers + +### Skill Development Guidelines + +1. **Clear Descriptions** + - Include explicit trigger phrases + - Document all capabilities + - Specify limitations + +2. **Proper Tool Permissions** + - Use minimal necessary tools + - Document security implications + - Test with restricted tools + +3. **Comprehensive Documentation** + - Provide usage examples + - Document common pitfalls + - Include troubleshooting guide + +### Maintenance + +- Keep version updated +- Test after tool updates +- Monitor user feedback +- Iterate on descriptions + +## Performance Tips + +- Scope skills to specific domains +- Avoid overlapping trigger phrases +- Keep descriptions under 1024 chars +- Test activation reliability + +## Security Considerations + +- Never include secrets in skill files +- Validate all inputs +- Use read-only tools when possible +- Document security requirements diff --git a/skills/skill-adapter/references/examples.md b/skills/skill-adapter/references/examples.md new file mode 100644 index 0000000..b1d8bd2 --- /dev/null +++ b/skills/skill-adapter/references/examples.md @@ -0,0 +1,70 @@ +# Skill Usage Examples + +This document provides practical examples of how to use this skill effectively. + +## Basic Usage + +### Example 1: Simple Activation + +**User Request:** +``` +[Describe trigger phrase here] +``` + +**Skill Response:** +1. Analyzes the request +2. Performs the required action +3. Returns results + +### Example 2: Complex Workflow + +**User Request:** +``` +[Describe complex scenario] +``` + +**Workflow:** +1. Step 1: Initial analysis +2. Step 2: Data processing +3. Step 3: Result generation +4. Step 4: Validation + +## Advanced Patterns + +### Pattern 1: Chaining Operations + +Combine this skill with other tools: +``` +Step 1: Use this skill for [purpose] +Step 2: Chain with [other tool] +Step 3: Finalize with [action] +``` + +### Pattern 2: Error Handling + +If issues occur: +- Check trigger phrase matches +- Verify context is available +- Review allowed-tools permissions + +## Tips & Best Practices + +- ✅ Be specific with trigger phrases +- ✅ Provide necessary context +- ✅ Check tool permissions match needs +- ❌ Avoid vague requests +- ❌ Don't mix unrelated tasks + +## Common Issues + +**Issue:** Skill doesn't activate +**Solution:** Use exact trigger phrases from description + +**Issue:** Unexpected results +**Solution:** Check input format and context + +## See Also + +- Main SKILL.md for full documentation +- scripts/ for automation helpers +- assets/ for configuration examples diff --git a/skills/skill-adapter/scripts/README.md b/skills/skill-adapter/scripts/README.md new file mode 100644 index 0000000..f002980 --- /dev/null +++ b/skills/skill-adapter/scripts/README.md @@ -0,0 +1,7 @@ +# Scripts + +Bundled resources for api-rate-limiter skill + +- [ ] generate_rate_limit_config.py: Generates rate limiting configuration files based on user input. +- [ ] test_rate_limiting.sh: Tests the rate limiting implementation using curl or similar tools. +- [ ] deploy_rate_limiting.sh: Deploys the rate limiting configuration to the API gateway or server. diff --git a/skills/skill-adapter/scripts/helper-template.sh b/skills/skill-adapter/scripts/helper-template.sh new file mode 100755 index 0000000..c4aae90 --- /dev/null +++ b/skills/skill-adapter/scripts/helper-template.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Helper script template for skill automation +# Customize this for your skill's specific needs + +set -e + +function show_usage() { + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo " -v, --verbose Enable verbose output" + echo "" +} + +# Parse arguments +VERBOSE=false + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + *) + echo "Unknown option: $1" + show_usage + exit 1 + ;; + esac +done + +# Your skill logic here +if [ "$VERBOSE" = true ]; then + echo "Running skill automation..." +fi + +echo "✅ Complete" diff --git a/skills/skill-adapter/scripts/validation.sh b/skills/skill-adapter/scripts/validation.sh new file mode 100755 index 0000000..590af58 --- /dev/null +++ b/skills/skill-adapter/scripts/validation.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Skill validation helper +# Validates skill activation and functionality + +set -e + +echo "🔍 Validating skill..." + +# Check if SKILL.md exists +if [ ! -f "../SKILL.md" ]; then + echo "❌ Error: SKILL.md not found" + exit 1 +fi + +# Validate frontmatter +if ! grep -q "^---$" "../SKILL.md"; then + echo "❌ Error: No frontmatter found" + exit 1 +fi + +# Check required fields +if ! grep -q "^name:" "../SKILL.md"; then + echo "❌ Error: Missing 'name' field" + exit 1 +fi + +if ! grep -q "^description:" "../SKILL.md"; then + echo "❌ Error: Missing 'description' field" + exit 1 +fi + +echo "✅ Skill validation passed"