@limitrate/cli
v2.1.1
Published
CLI dashboard for LimitRate rate limiting inspection
Maintainers
Readme
@limitrate/cli
CLI dashboard and event storage for LimitRate.
Installation
The CLI is automatically available when you install any LimitRate package:
npx limitrate inspectOr install globally:
pnpm add -g @limitrate/cli
# or
npm install -g @limitrate/cliCommands
npx limitrate inspect
Launch the interactive dashboard to view real-time statistics:
npx limitrate inspectDisplays:
- Total API cost spent (last 48 hours)
- Request count per endpoint
- Top spenders by user
- Cost-exceeded events
- Rate-exceeded events
- Recent blocked requests
Navigation:
↑/↓— Scroll through listTab— Switch between tabsq— Quit
npx limitrate clear
Clear all stored events:
npx limitrate clearThis removes the SQLite database at .limitrate/events.db.
Event Storage
Automatic Storage
When you use @limitrate/express with the onEvent handler, events are automatically saved to SQLite:
import { saveEvent } from '@limitrate/cli';
app.use(limitrate({
// ... config
onEvent: (event) => {
saveEvent(event); // Store for CLI dashboard
// Your custom logic
if (event.type === 'cost_exceeded') {
console.log('Cost cap exceeded!', event);
}
}
}));Manual Storage
You can also manually save events:
import { saveEvent } from '@limitrate/cli';
saveEvent({
timestamp: Date.now(),
type: 'rate_exceeded',
user: 'user-123',
plan: 'free',
endpoint: 'POST|/api/ask',
ip: '1.2.3.4',
value: 11,
threshold: 10,
window: '1m'
});Storage Location
Events are stored in SQLite at:
.limitrate/events.dbRetention: Events older than 48 hours are automatically pruned.
Event Types
allowed
Request passed all rate limits and cost caps:
{
timestamp: 1638360000000,
type: 'allowed',
user: 'user-123',
plan: 'free',
endpoint: 'POST|/api/ask',
ip: '1.2.3.4'
}rate_exceeded
User hit rate limit:
{
timestamp: 1638360000000,
type: 'rate_exceeded',
user: 'user-123',
plan: 'free',
endpoint: 'POST|/api/ask',
ip: '1.2.3.4',
value: 11, // Current request count
threshold: 10, // Limit
window: '1m' // Time window
}cost_exceeded
User hit cost cap:
{
timestamp: 1638360000000,
type: 'cost_exceeded',
user: 'user-123',
plan: 'free',
endpoint: 'POST|/api/ask',
ip: '1.2.3.4',
value: 0.11, // Current cost ($)
threshold: 0.10, // Cap ($)
window: '1h', // Time window
estimatedCost: 0.003 // Cost of this request
}ip_blocked
IP on blocklist:
{
timestamp: 1638360000000,
type: 'ip_blocked',
user: 'anonymous',
plan: 'free',
endpoint: 'POST|/api/ask',
ip: '1.2.3.4'
}Programmatic API
saveEvent(event: LimitRateEvent): void
Save an event to SQLite:
import { saveEvent } from '@limitrate/cli';
saveEvent({
timestamp: Date.now(),
type: 'rate_exceeded',
user: 'user-123',
plan: 'free',
endpoint: 'POST|/api/ask',
ip: '1.2.3.4',
value: 11,
threshold: 10,
window: '1m'
});getEvents(filter?: EventFilter): LimitRateEvent[]
Query stored events:
import { getEvents } from '@limitrate/cli';
// Get all events from last 24 hours
const events = getEvents({
since: Date.now() - 24 * 60 * 60 * 1000
});
// Get cost-exceeded events for a specific user
const costEvents = getEvents({
type: 'cost_exceeded',
user: 'user-123'
});
// Get events for a specific endpoint
const endpointEvents = getEvents({
endpoint: 'POST|/api/ask'
});clearEvents(): void
Clear all events:
import { clearEvents } from '@limitrate/cli';
clearEvents();getStats(): DashboardStats
Get aggregated statistics:
import { getStats } from '@limitrate/cli';
const stats = getStats();
console.log(stats);
// {
// totalCost: 12.45,
// totalRequests: 1234,
// endpointCounts: {
// 'POST|/api/ask': 567,
// 'GET|/api/data': 667
// },
// topSpenders: [
// { user: 'user-123', cost: 5.67, requests: 345 },
// { user: 'user-456', cost: 3.21, requests: 234 }
// ],
// recentBlocked: [
// { timestamp: 1638360000000, user: 'user-789', reason: 'rate_exceeded' }
// ]
// }Example Integration
import express from 'express';
import { limitrate } from '@limitrate/express';
import { saveEvent, getStats } from '@limitrate/cli';
const app = express();
app.use(limitrate({
identifyUser: (req) => req.user?.id || req.ip,
identifyPlan: (req) => req.user?.plan || 'free',
store: { type: 'redis', url: process.env.REDIS_URL },
policies: {
free: {
endpoints: {
'POST|/api/ask': {
rate: { maxPerMinute: 10, actionOnExceed: 'block' },
cost: {
estimateCost: (ctx) => 0.003,
hourlyCap: 0.10,
actionOnExceed: 'block'
}
}
}
}
},
onEvent: (event) => {
// Save to SQLite for CLI dashboard
saveEvent(event);
// Send alerts
if (event.type === 'cost_exceeded') {
console.log(`🚨 User ${event.user} exceeded cost cap`);
}
}
}));
// Admin endpoint to get stats
app.get('/admin/stats', (req, res) => {
const stats = getStats();
res.json(stats);
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
console.log('View dashboard: npx limitrate inspect');
});Development
Database Schema
The SQLite database has a single events table:
CREATE TABLE events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp INTEGER NOT NULL,
type TEXT NOT NULL,
user TEXT NOT NULL,
plan TEXT NOT NULL,
endpoint TEXT NOT NULL,
ip TEXT,
value REAL,
threshold REAL,
window TEXT,
estimated_cost REAL
);
CREATE INDEX idx_timestamp ON events(timestamp);
CREATE INDEX idx_type ON events(type);
CREATE INDEX idx_user ON events(user);
CREATE INDEX idx_endpoint ON events(endpoint);Auto-Pruning
Events older than 48 hours are automatically deleted on every write to keep the database small.
License
Apache-2.0
