paygate-mcp 1.7.0 → 1.8.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/README.md CHANGED
@@ -50,6 +50,8 @@ Agent → PayGate (auth + billing) → Your MCP Server (stdio or HTTP)
50
50
  - **Admin Lifecycle Events** — Webhook notifications for key.created, key.revoked, key.rotated, key.topup
51
51
  - **IP Allowlisting** — Restrict API keys to specific IPs or CIDR ranges (IPv4)
52
52
  - **Key Tags/Metadata** — Attach arbitrary key-value tags to API keys for external system integration
53
+ - **Usage Analytics** — Time-series analytics API with tool breakdown, top consumers, and trend comparison
54
+ - **Alert Webhooks** — Configurable alerts for spending thresholds, low credits, quota warnings, key expiry, rate limit spikes
53
55
  - **Refund on Failure** — Automatically refund credits when downstream tool calls fail
54
56
  - **Webhook Events** — POST batched usage events to any URL for external billing/alerting
55
57
  - **Config File Mode** — Load all settings from a JSON file (`--config`)
@@ -286,6 +288,9 @@ A real-time admin UI for managing keys, viewing usage, and monitoring tool calls
286
288
  | `/.well-known/mcp-payment` | GET | None | Server payment metadata (SEP-2007) |
287
289
  | `/pricing` | GET | None | Full per-tool pricing breakdown |
288
290
  | `/metrics` | GET | None | Prometheus metrics (counters, gauges, uptime) |
291
+ | `/analytics` | GET | `X-Admin-Key` | Usage analytics (time-series, tool breakdown, trends) |
292
+ | `/alerts` | GET | `X-Admin-Key` | Consume pending alerts |
293
+ | `/alerts` | POST | `X-Admin-Key` | Configure alert rules |
289
294
  | `/audit` | GET | `X-Admin-Key` | Query audit log (filter by type, actor, time) |
290
295
  | `/audit/export` | GET | `X-Admin-Key` | Export full audit log (JSON or CSV) |
291
296
  | `/audit/stats` | GET | `X-Admin-Key` | Audit log statistics |
@@ -510,6 +515,8 @@ When webhooks are enabled, admin operations also fire webhook events:
510
515
  | `key.topup` | POST /topup | keyMasked, creditsAdded, newBalance |
511
516
  | `key.revoked` | POST /keys/revoke | keyMasked |
512
517
  | `key.rotated` | POST /keys/rotate | oldKeyMasked, newKeyMasked |
518
+ | `key.expired` | Gate evaluation | keyMasked |
519
+ | `alert.fired` | Gate evaluation | alertType, keyPrefix, message, value, threshold |
513
520
 
514
521
  Admin events appear in the `adminEvents` array of the webhook payload (separate from usage `events`). Both arrays can be present in the same batch.
515
522
 
@@ -649,7 +656,7 @@ curl http://localhost:3402/audit/stats \
649
656
  -H "X-Admin-Key: YOUR_ADMIN_KEY"
650
657
  ```
651
658
 
652
- **Tracked events:** `key.created`, `key.revoked`, `key.topup`, `key.acl_updated`, `key.expiry_updated`, `key.quota_updated`, `key.limit_updated`, `gate.allow`, `gate.deny`, `session.created`, `session.destroyed`, `oauth.client_registered`, `oauth.token_issued`, `oauth.token_revoked`, `admin.auth_failed`, `billing.refund`.
659
+ **Tracked events:** `key.created`, `key.revoked`, `key.topup`, `key.acl_updated`, `key.expiry_updated`, `key.quota_updated`, `key.limit_updated`, `key.tags_updated`, `key.ip_updated`, `gate.allow`, `gate.deny`, `session.created`, `session.destroyed`, `oauth.client_registered`, `oauth.token_issued`, `oauth.token_revoked`, `admin.auth_failed`, `admin.alerts_configured`, `billing.refund`.
653
660
 
654
661
  **Retention:** Ring buffer (default 10,000 events), age-based cleanup (default 30 days), automatic periodic enforcement.
655
662
 
@@ -789,6 +796,65 @@ curl -X POST http://localhost:3402/keys \
789
796
 
790
797
  Limits: max 50 tags per key, max 100 chars per key/value. Tags appear in `/balance` responses and key listings.
791
798
 
799
+ ### Usage Analytics
800
+
801
+ Query aggregated usage data for dashboards, reports, and trend analysis:
802
+
803
+ ```bash
804
+ # Get analytics for the last 24 hours (hourly buckets)
805
+ curl "http://localhost:3402/analytics?from=2026-02-25T00:00:00Z&to=2026-02-26T00:00:00Z&granularity=hourly" \
806
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
807
+
808
+ # Daily granularity with top 5 consumers
809
+ curl "http://localhost:3402/analytics?granularity=daily&topN=5" \
810
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
811
+ ```
812
+
813
+ Returns:
814
+ - **timeSeries** — Bucketed call counts, credits charged, and denials per time window
815
+ - **toolBreakdown** — Per-tool stats (calls, credits, average cost) sorted by usage
816
+ - **topConsumers** — Top N API keys by credits spent, with each key's most-used tool
817
+ - **trend** — Current vs previous period comparison with percentage changes (calls, credits, denials)
818
+ - **summary** — Total calls, credits, unique keys, and unique tools
819
+
820
+ Query parameters: `from` (ISO date), `to` (ISO date), `granularity` (`hourly` or `daily`, default: `hourly`), `topN` (number, default: `10`).
821
+
822
+ ### Alert Webhooks
823
+
824
+ Configure rules to fire alerts when usage thresholds are crossed. Alerts are delivered via webhooks as `alert.fired` admin events:
825
+
826
+ ```bash
827
+ # Configure alert rules
828
+ curl -X POST http://localhost:3402/alerts \
829
+ -H "Content-Type: application/json" \
830
+ -H "X-Admin-Key: YOUR_ADMIN_KEY" \
831
+ -d '{"rules": [
832
+ {"type": "spending_threshold", "threshold": 80},
833
+ {"type": "credits_low", "threshold": 50},
834
+ {"type": "quota_warning", "threshold": 90},
835
+ {"type": "key_expiry_soon", "threshold": 86400},
836
+ {"type": "rate_limit_spike", "threshold": 10}
837
+ ]}'
838
+
839
+ # Consume pending alerts (returns and clears queue)
840
+ curl http://localhost:3402/alerts \
841
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
842
+ ```
843
+
844
+ **Alert types:**
845
+
846
+ | Type | Threshold Meaning | Fires When |
847
+ |------|-------------------|------------|
848
+ | `spending_threshold` | Percentage (0–100) | Key has spent ≥ threshold% of its initial credits |
849
+ | `credits_low` | Absolute credits | Key's remaining credits drop below threshold |
850
+ | `quota_warning` | Percentage (0–100) | Key's daily call usage exceeds threshold% of quota |
851
+ | `key_expiry_soon` | Seconds | Key expires within threshold seconds |
852
+ | `rate_limit_spike` | Count | Key has ≥ threshold rate-limit denials in 5 minutes |
853
+
854
+ Each rule has an optional `cooldownSeconds` (default: 300) to prevent alert storms. Alerts are automatically checked on every gate evaluation (tool call).
855
+
856
+ When webhooks are enabled (`--webhook-url`), alerts fire as `alert.fired` events in the `adminEvents` webhook payload with full context (key, rule type, current value, threshold).
857
+
792
858
  ### Rate Limit Response Headers
793
859
 
794
860
  Every `/mcp` response includes rate limit and credits headers when an API key is provided:
@@ -941,6 +1007,8 @@ const result = await client.callTool('search', { query: 'hello' });
941
1007
  - [x] Admin lifecycle events — Webhook notifications for key management operations
942
1008
  - [x] IP allowlisting — Restrict API keys to specific IPs or CIDR ranges
943
1009
  - [x] Key tags/metadata — Attach key-value tags for external system integration
1010
+ - [x] Usage analytics — Time-series analytics API with tool breakdown, trends, and top consumers
1011
+ - [x] Alert webhooks — Configurable threshold alerts (spending, credits, quota, expiry, rate limits)
944
1012
  - [ ] Horizontal scaling — Redis-backed state for multi-process deployments
945
1013
 
946
1014
  ## Requirements
@@ -0,0 +1,85 @@
1
+ /**
2
+ * AlertEngine — Configurable alerts for proactive monitoring.
3
+ *
4
+ * Alert types:
5
+ * - spending_threshold: Fires when a key's totalSpent crosses X% of its credits
6
+ * - credits_low: Fires when remaining credits fall below threshold
7
+ * - quota_warning: Fires when daily/monthly quota usage exceeds X%
8
+ * - key_expiry_soon: Fires when a key will expire within N seconds
9
+ * - rate_limit_spike: Fires when rate limit denials exceed N in a window
10
+ *
11
+ * Each alert fires at most once per key per cooldown period (default 1 hour).
12
+ */
13
+ import { ApiKeyRecord } from './types';
14
+ export type AlertType = 'spending_threshold' | 'credits_low' | 'quota_warning' | 'key_expiry_soon' | 'rate_limit_spike';
15
+ export interface AlertRule {
16
+ type: AlertType;
17
+ /** Threshold value — meaning depends on type:
18
+ * - spending_threshold: percentage (0-100) of initial credits spent
19
+ * - credits_low: absolute credits remaining
20
+ * - quota_warning: percentage (0-100) of quota used
21
+ * - key_expiry_soon: seconds before expiry to alert
22
+ * - rate_limit_spike: number of denials in a 5-min window
23
+ */
24
+ threshold: number;
25
+ /** Cooldown in seconds between repeated alerts for same key (default: 3600) */
26
+ cooldownSeconds?: number;
27
+ }
28
+ export interface Alert {
29
+ type: AlertType;
30
+ timestamp: string;
31
+ keyPrefix: string;
32
+ keyName: string;
33
+ message: string;
34
+ threshold: number;
35
+ currentValue: number;
36
+ metadata: Record<string, unknown>;
37
+ }
38
+ export interface AlertEngineConfig {
39
+ rules: AlertRule[];
40
+ /** If true, alerts are only logged but not emitted */
41
+ dryRun?: boolean;
42
+ }
43
+ export declare class AlertEngine {
44
+ private readonly rules;
45
+ private readonly dryRun;
46
+ /** Map of "alertType:keyPrefix" → last alert timestamp */
47
+ private readonly cooldowns;
48
+ /** Rate limit denial counter: "keyPrefix" → timestamps of recent denials */
49
+ private readonly rateLimitDenials;
50
+ /** Pending alerts waiting to be consumed */
51
+ private pendingAlerts;
52
+ constructor(config?: AlertEngineConfig);
53
+ /**
54
+ * Check a key record against all alert rules.
55
+ * Call this after every gate evaluation.
56
+ */
57
+ check(key: string, record: ApiKeyRecord, context?: {
58
+ rateLimitDenied?: boolean;
59
+ }): Alert[];
60
+ /**
61
+ * Record a rate limit denial for spike detection.
62
+ */
63
+ recordRateLimitDenial(key: string): void;
64
+ /**
65
+ * Consume pending alerts (returns and clears the queue).
66
+ */
67
+ consumeAlerts(): Alert[];
68
+ /**
69
+ * Get pending alert count.
70
+ */
71
+ get pendingCount(): number;
72
+ /**
73
+ * Get configured rules.
74
+ */
75
+ get configuredRules(): AlertRule[];
76
+ /**
77
+ * Evaluate a single rule against a key record.
78
+ */
79
+ private evaluateRule;
80
+ /**
81
+ * Clear cooldowns (for testing).
82
+ */
83
+ clearCooldowns(): void;
84
+ }
85
+ //# sourceMappingURL=alerts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts.d.ts","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAIvC,MAAM,MAAM,SAAS,GACjB,oBAAoB,GACpB,aAAa,GACb,eAAe,GACf,iBAAiB,GACjB,kBAAkB,CAAC;AAEvB,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,sDAAsD;IACtD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAID,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,0DAA0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA+B;IAChE,4CAA4C;IAC5C,OAAO,CAAC,aAAa,CAAe;gBAExB,MAAM,CAAC,EAAE,iBAAiB;IAKtC;;;OAGG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,KAAK,EAAE;IAyB1F;;OAEG;IACH,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAcxC;;OAEG;IACH,aAAa,IAAI,KAAK,EAAE;IAMxB;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;OAEG;IACH,IAAI,eAAe,IAAI,SAAS,EAAE,CAEjC;IAED;;OAEG;IACH,OAAO,CAAC,YAAY;IAuHpB;;OAEG;IACH,cAAc,IAAI,IAAI;CAKvB"}
package/dist/alerts.js ADDED
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ /**
3
+ * AlertEngine — Configurable alerts for proactive monitoring.
4
+ *
5
+ * Alert types:
6
+ * - spending_threshold: Fires when a key's totalSpent crosses X% of its credits
7
+ * - credits_low: Fires when remaining credits fall below threshold
8
+ * - quota_warning: Fires when daily/monthly quota usage exceeds X%
9
+ * - key_expiry_soon: Fires when a key will expire within N seconds
10
+ * - rate_limit_spike: Fires when rate limit denials exceed N in a window
11
+ *
12
+ * Each alert fires at most once per key per cooldown period (default 1 hour).
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.AlertEngine = void 0;
16
+ // ─── Engine ───────────────────────────────────────────────────────────────────
17
+ class AlertEngine {
18
+ rules;
19
+ dryRun;
20
+ /** Map of "alertType:keyPrefix" → last alert timestamp */
21
+ cooldowns = new Map();
22
+ /** Rate limit denial counter: "keyPrefix" → timestamps of recent denials */
23
+ rateLimitDenials = new Map();
24
+ /** Pending alerts waiting to be consumed */
25
+ pendingAlerts = [];
26
+ constructor(config) {
27
+ this.rules = config?.rules || [];
28
+ this.dryRun = config?.dryRun || false;
29
+ }
30
+ /**
31
+ * Check a key record against all alert rules.
32
+ * Call this after every gate evaluation.
33
+ */
34
+ check(key, record, context) {
35
+ const fired = [];
36
+ const keyPrefix = key.slice(0, 10) + '...';
37
+ for (const rule of this.rules) {
38
+ const cooldownKey = `${rule.type}:${keyPrefix}`;
39
+ const cooldownMs = (rule.cooldownSeconds || 3600) * 1000;
40
+ const lastFired = this.cooldowns.get(cooldownKey) || 0;
41
+ const now = Date.now();
42
+ if (now - lastFired < cooldownMs)
43
+ continue;
44
+ const alert = this.evaluateRule(rule, key, record, keyPrefix, context);
45
+ if (alert) {
46
+ this.cooldowns.set(cooldownKey, now);
47
+ fired.push(alert);
48
+ if (!this.dryRun) {
49
+ this.pendingAlerts.push(alert);
50
+ }
51
+ }
52
+ }
53
+ return fired;
54
+ }
55
+ /**
56
+ * Record a rate limit denial for spike detection.
57
+ */
58
+ recordRateLimitDenial(key) {
59
+ const keyPrefix = key.slice(0, 10) + '...';
60
+ let denials = this.rateLimitDenials.get(keyPrefix);
61
+ if (!denials) {
62
+ denials = [];
63
+ this.rateLimitDenials.set(keyPrefix, denials);
64
+ }
65
+ denials.push(Date.now());
66
+ // Keep only last 5 minutes
67
+ const cutoff = Date.now() - 5 * 60 * 1000;
68
+ this.rateLimitDenials.set(keyPrefix, denials.filter(t => t > cutoff));
69
+ }
70
+ /**
71
+ * Consume pending alerts (returns and clears the queue).
72
+ */
73
+ consumeAlerts() {
74
+ const alerts = [...this.pendingAlerts];
75
+ this.pendingAlerts = [];
76
+ return alerts;
77
+ }
78
+ /**
79
+ * Get pending alert count.
80
+ */
81
+ get pendingCount() {
82
+ return this.pendingAlerts.length;
83
+ }
84
+ /**
85
+ * Get configured rules.
86
+ */
87
+ get configuredRules() {
88
+ return [...this.rules];
89
+ }
90
+ /**
91
+ * Evaluate a single rule against a key record.
92
+ */
93
+ evaluateRule(rule, key, record, keyPrefix, context) {
94
+ const now = new Date().toISOString();
95
+ const base = {
96
+ type: rule.type,
97
+ timestamp: now,
98
+ keyPrefix,
99
+ keyName: record.name,
100
+ threshold: rule.threshold,
101
+ };
102
+ switch (rule.type) {
103
+ case 'spending_threshold': {
104
+ // Alert when totalSpent exceeds threshold% of (totalSpent + remaining credits)
105
+ const totalBudget = record.credits + record.totalSpent;
106
+ if (totalBudget <= 0)
107
+ return null;
108
+ const spentPct = (record.totalSpent / totalBudget) * 100;
109
+ if (spentPct >= rule.threshold) {
110
+ return {
111
+ ...base,
112
+ currentValue: Math.round(spentPct * 10) / 10,
113
+ message: `Key "${record.name}" has spent ${Math.round(spentPct)}% of total budget (${record.totalSpent}/${totalBudget} credits)`,
114
+ metadata: { credits: record.credits, totalSpent: record.totalSpent, totalBudget },
115
+ };
116
+ }
117
+ return null;
118
+ }
119
+ case 'credits_low': {
120
+ if (record.credits <= rule.threshold) {
121
+ return {
122
+ ...base,
123
+ currentValue: record.credits,
124
+ message: `Key "${record.name}" has only ${record.credits} credits remaining (threshold: ${rule.threshold})`,
125
+ metadata: { credits: record.credits },
126
+ };
127
+ }
128
+ return null;
129
+ }
130
+ case 'quota_warning': {
131
+ if (!record.quota)
132
+ return null;
133
+ // Check daily call quota
134
+ if (record.quota.dailyCallLimit > 0) {
135
+ const pct = (record.quotaDailyCalls / record.quota.dailyCallLimit) * 100;
136
+ if (pct >= rule.threshold) {
137
+ return {
138
+ ...base,
139
+ currentValue: Math.round(pct * 10) / 10,
140
+ message: `Key "${record.name}" daily call quota at ${Math.round(pct)}% (${record.quotaDailyCalls}/${record.quota.dailyCallLimit})`,
141
+ metadata: {
142
+ quotaType: 'dailyCalls',
143
+ used: record.quotaDailyCalls,
144
+ limit: record.quota.dailyCallLimit,
145
+ },
146
+ };
147
+ }
148
+ }
149
+ // Check monthly call quota
150
+ if (record.quota.monthlyCallLimit > 0) {
151
+ const pct = (record.quotaMonthlyCalls / record.quota.monthlyCallLimit) * 100;
152
+ if (pct >= rule.threshold) {
153
+ return {
154
+ ...base,
155
+ currentValue: Math.round(pct * 10) / 10,
156
+ message: `Key "${record.name}" monthly call quota at ${Math.round(pct)}% (${record.quotaMonthlyCalls}/${record.quota.monthlyCallLimit})`,
157
+ metadata: {
158
+ quotaType: 'monthlyCalls',
159
+ used: record.quotaMonthlyCalls,
160
+ limit: record.quota.monthlyCallLimit,
161
+ },
162
+ };
163
+ }
164
+ }
165
+ return null;
166
+ }
167
+ case 'key_expiry_soon': {
168
+ if (!record.expiresAt)
169
+ return null;
170
+ const expiresMs = new Date(record.expiresAt).getTime();
171
+ const remainingMs = expiresMs - Date.now();
172
+ const thresholdMs = rule.threshold * 1000;
173
+ if (remainingMs > 0 && remainingMs <= thresholdMs) {
174
+ const remainingHours = Math.round(remainingMs / 3600_000 * 10) / 10;
175
+ return {
176
+ ...base,
177
+ currentValue: Math.round(remainingMs / 1000),
178
+ message: `Key "${record.name}" expires in ${remainingHours} hours (${record.expiresAt})`,
179
+ metadata: { expiresAt: record.expiresAt, remainingSeconds: Math.round(remainingMs / 1000) },
180
+ };
181
+ }
182
+ return null;
183
+ }
184
+ case 'rate_limit_spike': {
185
+ if (!context?.rateLimitDenied)
186
+ return null;
187
+ const denials = this.rateLimitDenials.get(keyPrefix) || [];
188
+ const recentCount = denials.length;
189
+ if (recentCount >= rule.threshold) {
190
+ return {
191
+ ...base,
192
+ currentValue: recentCount,
193
+ message: `Key "${record.name}" hit ${recentCount} rate limit denials in 5 min (threshold: ${rule.threshold})`,
194
+ metadata: { denialCount: recentCount, windowMinutes: 5 },
195
+ };
196
+ }
197
+ return null;
198
+ }
199
+ default:
200
+ return null;
201
+ }
202
+ }
203
+ /**
204
+ * Clear cooldowns (for testing).
205
+ */
206
+ clearCooldowns() {
207
+ this.cooldowns.clear();
208
+ this.rateLimitDenials.clear();
209
+ this.pendingAlerts = [];
210
+ }
211
+ }
212
+ exports.AlertEngine = AlertEngine;
213
+ //# sourceMappingURL=alerts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts.js","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * AnalyticsEngine — Time-series analytics over UsageEvents.
3
+ *
4
+ * Provides:
5
+ * - Time-bucketed aggregation (hourly, daily)
6
+ * - Per-tool breakdown with success/failure rates
7
+ * - Top consumers by credits or call count
8
+ * - Trend comparison (current vs previous period)
9
+ * - Revenue (credits) over time
10
+ */
11
+ import { UsageEvent } from './types';
12
+ export type BucketGranularity = 'hourly' | 'daily';
13
+ export interface TimeBucket {
14
+ /** Bucket start time (ISO 8601) */
15
+ start: string;
16
+ /** Bucket end time (ISO 8601) */
17
+ end: string;
18
+ /** Total calls in this bucket */
19
+ calls: number;
20
+ /** Allowed calls */
21
+ allowed: number;
22
+ /** Denied calls */
23
+ denied: number;
24
+ /** Credits charged */
25
+ credits: number;
26
+ }
27
+ export interface ToolBreakdown {
28
+ tool: string;
29
+ calls: number;
30
+ allowed: number;
31
+ denied: number;
32
+ credits: number;
33
+ /** Success rate (0-1) */
34
+ successRate: number;
35
+ /** Average credits per allowed call */
36
+ avgCreditsPerCall: number;
37
+ }
38
+ export interface TopConsumer {
39
+ /** Key name (or masked prefix) */
40
+ keyName: string;
41
+ calls: number;
42
+ credits: number;
43
+ denied: number;
44
+ /** Most-used tool */
45
+ topTool: string;
46
+ }
47
+ export interface TrendComparison {
48
+ /** Current period stats */
49
+ current: {
50
+ calls: number;
51
+ credits: number;
52
+ denied: number;
53
+ };
54
+ /** Previous period stats (same duration) */
55
+ previous: {
56
+ calls: number;
57
+ credits: number;
58
+ denied: number;
59
+ };
60
+ /** Percentage change (-100 to +Infinity) */
61
+ change: {
62
+ calls: number | null;
63
+ credits: number | null;
64
+ denied: number | null;
65
+ };
66
+ }
67
+ export interface AnalyticsReport {
68
+ /** Query time range */
69
+ from: string;
70
+ to: string;
71
+ granularity: BucketGranularity;
72
+ /** Time-series data */
73
+ timeSeries: TimeBucket[];
74
+ /** Per-tool breakdown (sorted by calls desc) */
75
+ tools: ToolBreakdown[];
76
+ /** Top consumers (sorted by credits desc) */
77
+ topConsumers: TopConsumer[];
78
+ /** Trend vs previous equivalent period */
79
+ trend: TrendComparison;
80
+ /** Summary stats */
81
+ summary: {
82
+ totalCalls: number;
83
+ totalCredits: number;
84
+ totalDenied: number;
85
+ uniqueKeys: number;
86
+ uniqueTools: number;
87
+ successRate: number;
88
+ };
89
+ }
90
+ export declare class AnalyticsEngine {
91
+ /**
92
+ * Generate a full analytics report from usage events.
93
+ */
94
+ report(events: UsageEvent[], options?: {
95
+ from?: string;
96
+ to?: string;
97
+ granularity?: BucketGranularity;
98
+ topN?: number;
99
+ }): AnalyticsReport;
100
+ /**
101
+ * Bucket events into time intervals.
102
+ */
103
+ private buildTimeSeries;
104
+ /**
105
+ * Breakdown per tool, sorted by calls descending.
106
+ */
107
+ private buildToolBreakdown;
108
+ /**
109
+ * Top consumers by credits spent, with their most-used tool.
110
+ */
111
+ private buildTopConsumers;
112
+ /**
113
+ * Compare current period vs previous period of same length.
114
+ */
115
+ private buildTrend;
116
+ }
117
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}