paygate-mcp 6.5.0 → 6.7.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 +70 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +263 -0
- package/dist/server.js.map +1 -1
- package/package.json +35 -8
package/README.md
CHANGED
|
@@ -92,6 +92,8 @@ Agent → PayGate (auth + billing) → Your MCP Server (stdio or HTTP)
|
|
|
92
92
|
- **Quota Status** — `GET /keys/quota-status?key=...` returns daily/monthly quota usage for any key — calls and credits used/remaining/limits, reset periods, quota source (per-key vs global vs none)
|
|
93
93
|
- **Credit History** — `GET /keys/credit-history?key=...` returns per-key credit mutation log — tracks initial allocation, topups, transfers (in/out), auto-topups, with type/limit/since filters, balance-before/after on every entry, newest-first ordering, capped at 100 entries per key
|
|
94
94
|
- **Spending Velocity** — `GET /keys/spending-velocity?key=...` returns credit burn rate and depletion forecast — credits/calls per hour/day, estimated depletion date, top tools by spend, configurable analysis window (1h–30d)
|
|
95
|
+
- **Key Comparison** — `GET /keys/compare?keys=pg_a,pg_b` returns side-by-side comparison of 2–10 keys — credits, usage, velocity, rate limits, status, metadata (namespace/group/tags) — with not-found key reporting
|
|
96
|
+
- **Key Health Score** — `GET /keys/health?key=...` returns composite health score (0–100) with weighted component breakdown: balance health (30%), quota utilization (25%), rate limit pressure (20%), error rate (25%) — status levels (healthy/good/caution/warning/critical), key issue detection (revoked/suspended/expired/expiring/zero credits), alias support
|
|
95
97
|
- **Config Hot Reload** — `POST /config/reload` reloads pricing, rate limits, webhooks, quotas, and behavior flags from config file without server restart
|
|
96
98
|
- **Webhook Events** — POST batched usage events to any URL for external billing/alerting
|
|
97
99
|
- **Config File Mode** — Load all settings from a JSON file (`--config`)
|
|
@@ -1796,6 +1798,74 @@ curl "http://localhost:3402/keys/spending-velocity?key=pg_...&window=48" -H "X-A
|
|
|
1796
1798
|
|
|
1797
1799
|
`estimatedDepletionDate` and `estimatedHoursRemaining` are `null` when there's no spending activity. `topTools` shows the 5 highest-spend tools from usage data.
|
|
1798
1800
|
|
|
1801
|
+
### Key Comparison
|
|
1802
|
+
|
|
1803
|
+
`GET /keys/compare?keys=pg_a,pg_b,pg_c` returns side-by-side comparison of 2–10 keys:
|
|
1804
|
+
|
|
1805
|
+
```bash
|
|
1806
|
+
curl "http://localhost:3402/keys/compare?keys=pg_abc,pg_xyz" -H "X-Admin-Key: YOUR_ADMIN_KEY"
|
|
1807
|
+
```
|
|
1808
|
+
|
|
1809
|
+
**Response:**
|
|
1810
|
+
|
|
1811
|
+
```json
|
|
1812
|
+
{
|
|
1813
|
+
"compared": 2,
|
|
1814
|
+
"keys": [
|
|
1815
|
+
{
|
|
1816
|
+
"key": "pg_abc123...",
|
|
1817
|
+
"name": "prod-agent",
|
|
1818
|
+
"status": "active",
|
|
1819
|
+
"credits": { "current": 750, "totalSpent": 250 },
|
|
1820
|
+
"usage": { "totalCalls": 50, "totalAllowed": 48, "totalDenied": 2 },
|
|
1821
|
+
"velocity": { "creditsPerHour": 12.5, "creditsPerDay": 300, "estimatedHoursRemaining": 60 },
|
|
1822
|
+
"rateLimit": { "used": 3, "limit": 60, "remaining": 57 },
|
|
1823
|
+
"metadata": { "namespace": "prod", "group": "team-a", "createdAt": "2026-02-01T00:00:00Z", "tags": { "env": "prod" } }
|
|
1824
|
+
},
|
|
1825
|
+
{
|
|
1826
|
+
"key": "pg_xyz789...",
|
|
1827
|
+
"name": "staging-agent",
|
|
1828
|
+
"status": "active",
|
|
1829
|
+
"credits": { "current": 200, "totalSpent": 800 },
|
|
1830
|
+
"usage": { "totalCalls": 120, "totalAllowed": 120, "totalDenied": 0 },
|
|
1831
|
+
"velocity": { "creditsPerHour": 8.3, "creditsPerDay": 200, "estimatedHoursRemaining": 24 },
|
|
1832
|
+
"rateLimit": { "used": 0, "limit": 60, "remaining": 60 },
|
|
1833
|
+
"metadata": { "namespace": "staging", "group": null, "createdAt": "2026-02-15T00:00:00Z", "tags": {} }
|
|
1834
|
+
}
|
|
1835
|
+
]
|
|
1836
|
+
}
|
|
1837
|
+
```
|
|
1838
|
+
|
|
1839
|
+
Keys not found are reported in a `notFound` array. Supports aliases. Maximum 10 keys per comparison.
|
|
1840
|
+
|
|
1841
|
+
### Key Health Score
|
|
1842
|
+
|
|
1843
|
+
`GET /keys/health?key=...` returns a composite health score (0–100) with weighted component breakdown:
|
|
1844
|
+
|
|
1845
|
+
```bash
|
|
1846
|
+
curl "http://localhost:3402/keys/health?key=pg_abc" -H "X-Admin-Key: YOUR_ADMIN_KEY"
|
|
1847
|
+
```
|
|
1848
|
+
|
|
1849
|
+
**Response:**
|
|
1850
|
+
|
|
1851
|
+
```json
|
|
1852
|
+
{
|
|
1853
|
+
"key": "pg_abc123...",
|
|
1854
|
+
"name": "prod-agent",
|
|
1855
|
+
"score": 72,
|
|
1856
|
+
"status": "caution",
|
|
1857
|
+
"issues": ["Key expires within 7 days", "Credits depleting rapidly"],
|
|
1858
|
+
"components": {
|
|
1859
|
+
"balance": { "score": 40, "risk": "warning", "weight": 0.30 },
|
|
1860
|
+
"quota": { "score": 85, "risk": "good", "weight": 0.25 },
|
|
1861
|
+
"rateLimit": { "score": 100, "risk": "healthy", "weight": 0.20 },
|
|
1862
|
+
"errorRate": { "score": 75, "risk": "good", "weight": 0.25 }
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
```
|
|
1866
|
+
|
|
1867
|
+
Components: **balance** (30%, hours until credit depletion), **quota** (25%, max utilization across daily/monthly limits), **rateLimit** (20%, current window usage), **errorRate** (25%, denied/total ratio). Status: healthy (≥90), good (≥75), caution (≥50), warning (≥25), critical (<25). Issues detect: revoked, suspended, expired, expiring soon, zero credits, rapid depletion. Supports aliases.
|
|
1868
|
+
|
|
1799
1869
|
### IP Allowlisting
|
|
1800
1870
|
|
|
1801
1871
|
Restrict API keys to specific IP addresses or CIDR ranges:
|
package/dist/server.d.ts
CHANGED
|
@@ -183,6 +183,8 @@ export declare class PayGateServer {
|
|
|
183
183
|
private handleQuotaStatus;
|
|
184
184
|
private handleCreditHistory;
|
|
185
185
|
private handleSpendingVelocity;
|
|
186
|
+
private handleKeyComparison;
|
|
187
|
+
private handleKeyHealth;
|
|
186
188
|
private handleSetAutoTopup;
|
|
187
189
|
private handleBalance;
|
|
188
190
|
private handleLimits;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -540,6 +540,10 @@ class PayGateServer {
|
|
|
540
540
|
return this.handleCreditHistory(req, res);
|
|
541
541
|
case '/keys/spending-velocity':
|
|
542
542
|
return this.handleSpendingVelocity(req, res);
|
|
543
|
+
case '/keys/compare':
|
|
544
|
+
return this.handleKeyComparison(req, res);
|
|
545
|
+
case '/keys/health':
|
|
546
|
+
return this.handleKeyHealth(req, res);
|
|
543
547
|
case '/keys/templates':
|
|
544
548
|
if (req.method === 'GET')
|
|
545
549
|
return this.handleListTemplates(req, res);
|
|
@@ -1087,6 +1091,8 @@ class PayGateServer {
|
|
|
1087
1091
|
quotaStatus: 'GET /keys/quota-status?key=... — Current daily/monthly quota usage (requires X-Admin-Key)',
|
|
1088
1092
|
creditHistory: 'GET /keys/credit-history?key=... — Per-key credit mutation history (requires X-Admin-Key)',
|
|
1089
1093
|
spendingVelocity: 'GET /keys/spending-velocity?key=... — Spending rate and depletion forecast (requires X-Admin-Key)',
|
|
1094
|
+
keyComparison: 'GET /keys/compare?keys=pg_a,pg_b — Side-by-side key comparison (requires X-Admin-Key)',
|
|
1095
|
+
keyHealth: 'GET /keys/health?key=... — Composite health score (0-100) with component breakdown (requires X-Admin-Key)',
|
|
1090
1096
|
keyTemplates: 'GET /keys/templates — List key templates + POST to create/update (requires X-Admin-Key)',
|
|
1091
1097
|
deleteTemplate: 'POST /keys/templates/delete — Delete a key template (requires X-Admin-Key)',
|
|
1092
1098
|
pricing: 'GET /pricing — Tool pricing breakdown (public)',
|
|
@@ -2829,6 +2835,263 @@ class PayGateServer {
|
|
|
2829
2835
|
topTools,
|
|
2830
2836
|
}));
|
|
2831
2837
|
}
|
|
2838
|
+
// ─── /keys/compare — Side-by-side key comparison ────────────────────────────
|
|
2839
|
+
handleKeyComparison(req, res) {
|
|
2840
|
+
if (req.method !== 'GET') {
|
|
2841
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2842
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
if (!this.checkAdmin(req, res))
|
|
2846
|
+
return;
|
|
2847
|
+
const urlParts = req.url?.split('?') || [];
|
|
2848
|
+
const params = new URLSearchParams(urlParts[1] || '');
|
|
2849
|
+
const keysParam = params.get('keys');
|
|
2850
|
+
if (!keysParam) {
|
|
2851
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2852
|
+
res.end(JSON.stringify({ error: 'Missing required query parameter: keys (comma-separated)' }));
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
const keyIds = keysParam.split(',').map(k => k.trim()).filter(Boolean);
|
|
2856
|
+
if (keyIds.length < 2) {
|
|
2857
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2858
|
+
res.end(JSON.stringify({ error: 'At least 2 keys required for comparison' }));
|
|
2859
|
+
return;
|
|
2860
|
+
}
|
|
2861
|
+
if (keyIds.length > 10) {
|
|
2862
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2863
|
+
res.end(JSON.stringify({ error: 'Maximum 10 keys per comparison' }));
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
const comparisons = [];
|
|
2867
|
+
const notFound = [];
|
|
2868
|
+
for (const keyId of keyIds) {
|
|
2869
|
+
const resolved = this.gate.store.resolveKey(keyId);
|
|
2870
|
+
const actualKey = resolved ? resolved.key : keyId;
|
|
2871
|
+
const record = this.gate.store.getKey(actualKey);
|
|
2872
|
+
if (!record) {
|
|
2873
|
+
notFound.push(keyId);
|
|
2874
|
+
continue;
|
|
2875
|
+
}
|
|
2876
|
+
// Get usage stats
|
|
2877
|
+
const keyUsage = this.gate.meter.getKeyUsage(actualKey);
|
|
2878
|
+
// Get velocity (24h window)
|
|
2879
|
+
const velocity = this.creditLedger.getSpendingVelocity(actualKey, record.credits, 24);
|
|
2880
|
+
// Get rate limit status
|
|
2881
|
+
const rateStatus = this.gate.rateLimiter.getStatus(actualKey);
|
|
2882
|
+
// Determine key status
|
|
2883
|
+
let status = 'active';
|
|
2884
|
+
if (!record.active)
|
|
2885
|
+
status = 'revoked';
|
|
2886
|
+
else if (record.suspended)
|
|
2887
|
+
status = 'suspended';
|
|
2888
|
+
else if (record.expiresAt && new Date(record.expiresAt).getTime() < Date.now())
|
|
2889
|
+
status = 'expired';
|
|
2890
|
+
comparisons.push({
|
|
2891
|
+
key: actualKey.slice(0, 10) + '...',
|
|
2892
|
+
name: record.name,
|
|
2893
|
+
status,
|
|
2894
|
+
credits: {
|
|
2895
|
+
current: record.credits,
|
|
2896
|
+
totalSpent: keyUsage.totalCreditsSpent,
|
|
2897
|
+
},
|
|
2898
|
+
usage: {
|
|
2899
|
+
totalCalls: keyUsage.totalCalls,
|
|
2900
|
+
totalAllowed: keyUsage.totalAllowed,
|
|
2901
|
+
totalDenied: keyUsage.totalDenied,
|
|
2902
|
+
},
|
|
2903
|
+
velocity: {
|
|
2904
|
+
creditsPerHour: velocity.creditsPerHour,
|
|
2905
|
+
creditsPerDay: velocity.creditsPerDay,
|
|
2906
|
+
estimatedHoursRemaining: velocity.estimatedHoursRemaining,
|
|
2907
|
+
},
|
|
2908
|
+
rateLimit: {
|
|
2909
|
+
used: rateStatus.used,
|
|
2910
|
+
limit: rateStatus.limit,
|
|
2911
|
+
remaining: rateStatus.remaining,
|
|
2912
|
+
},
|
|
2913
|
+
metadata: {
|
|
2914
|
+
namespace: record.namespace || null,
|
|
2915
|
+
group: record.group || null,
|
|
2916
|
+
createdAt: record.createdAt,
|
|
2917
|
+
tags: record.tags || {},
|
|
2918
|
+
},
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2921
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2922
|
+
res.end(JSON.stringify({
|
|
2923
|
+
compared: comparisons.length,
|
|
2924
|
+
notFound: notFound.length > 0 ? notFound : undefined,
|
|
2925
|
+
keys: comparisons,
|
|
2926
|
+
}));
|
|
2927
|
+
}
|
|
2928
|
+
// ─── /keys/health — Composite health score ──────────────────────────────────
|
|
2929
|
+
handleKeyHealth(req, res) {
|
|
2930
|
+
if (req.method !== 'GET') {
|
|
2931
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
2932
|
+
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2935
|
+
if (!this.checkAdmin(req, res))
|
|
2936
|
+
return;
|
|
2937
|
+
const urlParts = req.url?.split('?') || [];
|
|
2938
|
+
const params = new URLSearchParams(urlParts[1] || '');
|
|
2939
|
+
const key = params.get('key');
|
|
2940
|
+
if (!key) {
|
|
2941
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
2942
|
+
res.end(JSON.stringify({ error: 'Missing required query parameter: key' }));
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
// Use resolveKeyRaw to include expired/suspended/revoked keys in health check
|
|
2946
|
+
const record = this.gate.store.resolveKeyRaw(key);
|
|
2947
|
+
if (!record) {
|
|
2948
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
2949
|
+
res.end(JSON.stringify({ error: 'Key not found' }));
|
|
2950
|
+
return;
|
|
2951
|
+
}
|
|
2952
|
+
const actualKey = record.key;
|
|
2953
|
+
// ── Component 1: Balance health (30% weight) ──
|
|
2954
|
+
// Based on spending velocity → how many hours of usage remaining
|
|
2955
|
+
const velocity = this.creditLedger.getSpendingVelocity(actualKey, record.credits, 24);
|
|
2956
|
+
let balanceScore = 100;
|
|
2957
|
+
let balanceRisk = 'healthy';
|
|
2958
|
+
if (velocity.creditsPerHour > 0) {
|
|
2959
|
+
const hoursLeft = velocity.estimatedHoursRemaining ?? Infinity;
|
|
2960
|
+
if (hoursLeft <= 1) {
|
|
2961
|
+
balanceScore = 0;
|
|
2962
|
+
balanceRisk = 'critical';
|
|
2963
|
+
}
|
|
2964
|
+
else if (hoursLeft <= 6) {
|
|
2965
|
+
balanceScore = 20;
|
|
2966
|
+
balanceRisk = 'critical';
|
|
2967
|
+
}
|
|
2968
|
+
else if (hoursLeft <= 24) {
|
|
2969
|
+
balanceScore = 40;
|
|
2970
|
+
balanceRisk = 'warning';
|
|
2971
|
+
}
|
|
2972
|
+
else if (hoursLeft <= 72) {
|
|
2973
|
+
balanceScore = 60;
|
|
2974
|
+
balanceRisk = 'caution';
|
|
2975
|
+
}
|
|
2976
|
+
else if (hoursLeft <= 168) {
|
|
2977
|
+
balanceScore = 80;
|
|
2978
|
+
balanceRisk = 'good';
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
else if (record.credits <= 0) {
|
|
2982
|
+
balanceScore = 0;
|
|
2983
|
+
balanceRisk = 'critical';
|
|
2984
|
+
}
|
|
2985
|
+
// ── Component 2: Quota utilization (25% weight) ──
|
|
2986
|
+
let quotaScore = 100;
|
|
2987
|
+
let quotaRisk = 'healthy';
|
|
2988
|
+
const quotaConfig = record.quota || this.config.globalQuota;
|
|
2989
|
+
if (quotaConfig) {
|
|
2990
|
+
// Reset counters if day/month rolled over before reading
|
|
2991
|
+
this.gate.quotaTracker.resetIfNeeded(record);
|
|
2992
|
+
let maxUtilization = 0;
|
|
2993
|
+
if (quotaConfig.dailyCallLimit && quotaConfig.dailyCallLimit > 0) {
|
|
2994
|
+
maxUtilization = Math.max(maxUtilization, record.quotaDailyCalls / quotaConfig.dailyCallLimit);
|
|
2995
|
+
}
|
|
2996
|
+
if (quotaConfig.monthlyCallLimit && quotaConfig.monthlyCallLimit > 0) {
|
|
2997
|
+
maxUtilization = Math.max(maxUtilization, record.quotaMonthlyCalls / quotaConfig.monthlyCallLimit);
|
|
2998
|
+
}
|
|
2999
|
+
if (quotaConfig.dailyCreditLimit && quotaConfig.dailyCreditLimit > 0) {
|
|
3000
|
+
maxUtilization = Math.max(maxUtilization, record.quotaDailyCredits / quotaConfig.dailyCreditLimit);
|
|
3001
|
+
}
|
|
3002
|
+
if (quotaConfig.monthlyCreditLimit && quotaConfig.monthlyCreditLimit > 0) {
|
|
3003
|
+
maxUtilization = Math.max(maxUtilization, record.quotaMonthlyCredits / quotaConfig.monthlyCreditLimit);
|
|
3004
|
+
}
|
|
3005
|
+
quotaScore = Math.max(0, Math.round((1 - maxUtilization) * 100));
|
|
3006
|
+
if (maxUtilization >= 1)
|
|
3007
|
+
quotaRisk = 'critical';
|
|
3008
|
+
else if (maxUtilization >= 0.9)
|
|
3009
|
+
quotaRisk = 'warning';
|
|
3010
|
+
else if (maxUtilization >= 0.75)
|
|
3011
|
+
quotaRisk = 'caution';
|
|
3012
|
+
else if (maxUtilization >= 0.5)
|
|
3013
|
+
quotaRisk = 'good';
|
|
3014
|
+
}
|
|
3015
|
+
// ── Component 3: Rate limit pressure (20% weight) ──
|
|
3016
|
+
const rateStatus = this.gate.rateLimiter.getStatus(actualKey);
|
|
3017
|
+
let rateLimitScore = 100;
|
|
3018
|
+
let rateLimitRisk = 'healthy';
|
|
3019
|
+
if (rateStatus.limit > 0) {
|
|
3020
|
+
const utilization = rateStatus.used / rateStatus.limit;
|
|
3021
|
+
rateLimitScore = Math.max(0, Math.round((1 - utilization) * 100));
|
|
3022
|
+
if (utilization >= 1)
|
|
3023
|
+
rateLimitRisk = 'critical';
|
|
3024
|
+
else if (utilization >= 0.9)
|
|
3025
|
+
rateLimitRisk = 'warning';
|
|
3026
|
+
else if (utilization >= 0.75)
|
|
3027
|
+
rateLimitRisk = 'caution';
|
|
3028
|
+
else if (utilization >= 0.5)
|
|
3029
|
+
rateLimitRisk = 'good';
|
|
3030
|
+
}
|
|
3031
|
+
// ── Component 4: Error rate (25% weight) ──
|
|
3032
|
+
const keyUsage = this.gate.meter.getKeyUsage(actualKey);
|
|
3033
|
+
let errorScore = 100;
|
|
3034
|
+
let errorRisk = 'healthy';
|
|
3035
|
+
if (keyUsage.totalCalls > 0) {
|
|
3036
|
+
const errorRate = keyUsage.totalDenied / keyUsage.totalCalls;
|
|
3037
|
+
errorScore = Math.max(0, Math.round((1 - errorRate) * 100));
|
|
3038
|
+
if (errorRate >= 0.5)
|
|
3039
|
+
errorRisk = 'critical';
|
|
3040
|
+
else if (errorRate >= 0.25)
|
|
3041
|
+
errorRisk = 'warning';
|
|
3042
|
+
else if (errorRate >= 0.1)
|
|
3043
|
+
errorRisk = 'caution';
|
|
3044
|
+
else if (errorRate > 0)
|
|
3045
|
+
errorRisk = 'good';
|
|
3046
|
+
}
|
|
3047
|
+
// ── Composite score (weighted) ──
|
|
3048
|
+
const overallScore = Math.round(balanceScore * 0.30 +
|
|
3049
|
+
quotaScore * 0.25 +
|
|
3050
|
+
rateLimitScore * 0.20 +
|
|
3051
|
+
errorScore * 0.25);
|
|
3052
|
+
let overallStatus = 'healthy';
|
|
3053
|
+
if (overallScore < 25)
|
|
3054
|
+
overallStatus = 'critical';
|
|
3055
|
+
else if (overallScore < 50)
|
|
3056
|
+
overallStatus = 'warning';
|
|
3057
|
+
else if (overallScore < 75)
|
|
3058
|
+
overallStatus = 'caution';
|
|
3059
|
+
else if (overallScore < 90)
|
|
3060
|
+
overallStatus = 'good';
|
|
3061
|
+
// Check key-level issues
|
|
3062
|
+
const issues = [];
|
|
3063
|
+
if (!record.active)
|
|
3064
|
+
issues.push('Key is revoked');
|
|
3065
|
+
if (record.suspended)
|
|
3066
|
+
issues.push('Key is suspended');
|
|
3067
|
+
if (record.expiresAt && new Date(record.expiresAt).getTime() < Date.now())
|
|
3068
|
+
issues.push('Key has expired');
|
|
3069
|
+
if (record.expiresAt) {
|
|
3070
|
+
const hoursToExpiry = (new Date(record.expiresAt).getTime() - Date.now()) / 3_600_000;
|
|
3071
|
+
if (hoursToExpiry > 0 && hoursToExpiry <= 24)
|
|
3072
|
+
issues.push('Key expires within 24 hours');
|
|
3073
|
+
else if (hoursToExpiry > 0 && hoursToExpiry <= 168)
|
|
3074
|
+
issues.push('Key expires within 7 days');
|
|
3075
|
+
}
|
|
3076
|
+
if (record.credits <= 0)
|
|
3077
|
+
issues.push('Zero credits remaining');
|
|
3078
|
+
if (balanceRisk === 'critical')
|
|
3079
|
+
issues.push('Credits depleting rapidly');
|
|
3080
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
3081
|
+
res.end(JSON.stringify({
|
|
3082
|
+
key: actualKey.slice(0, 10) + '...',
|
|
3083
|
+
name: record.name,
|
|
3084
|
+
score: overallScore,
|
|
3085
|
+
status: overallStatus,
|
|
3086
|
+
issues: issues.length > 0 ? issues : undefined,
|
|
3087
|
+
components: {
|
|
3088
|
+
balance: { score: balanceScore, risk: balanceRisk, weight: 0.30 },
|
|
3089
|
+
quota: { score: quotaScore, risk: quotaRisk, weight: 0.25 },
|
|
3090
|
+
rateLimit: { score: rateLimitScore, risk: rateLimitRisk, weight: 0.20 },
|
|
3091
|
+
errorRate: { score: errorScore, risk: errorRisk, weight: 0.25 },
|
|
3092
|
+
},
|
|
3093
|
+
}));
|
|
3094
|
+
}
|
|
2832
3095
|
// ─── /keys/auto-topup — Configure auto-topup ────────────────────────────────
|
|
2833
3096
|
async handleSetAutoTopup(req, res) {
|
|
2834
3097
|
if (req.method !== 'POST') {
|