pricing.md 1.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/LICENSE +21 -0
- package/README.md +118 -0
- package/SKILL.md +593 -0
- package/data/tools/algolia.json +101 -0
- package/data/tools/amplitude.json +73 -0
- package/data/tools/auth0-b2b.json +175 -0
- package/data/tools/auth0-b2c.json +183 -0
- package/data/tools/buildkite.json +104 -0
- package/data/tools/checkly.json +112 -0
- package/data/tools/circleci.json +102 -0
- package/data/tools/clerk.json +109 -0
- package/data/tools/cloudflare.json +71 -0
- package/data/tools/contentful.json +71 -0
- package/data/tools/convex.json +74 -0
- package/data/tools/courier.json +80 -0
- package/data/tools/deno-deploy.json +145 -0
- package/data/tools/depot.json +66 -0
- package/data/tools/descope.json +130 -0
- package/data/tools/engagespot.json +84 -0
- package/data/tools/fly-io.json +53 -0
- package/data/tools/github-actions.json +64 -0
- package/data/tools/honeycomb.json +65 -0
- package/data/tools/inngest.json +80 -0
- package/data/tools/launchdarkly.json +81 -0
- package/data/tools/mergent.json +116 -0
- package/data/tools/mixpanel.json +70 -0
- package/data/tools/mux.json +164 -0
- package/data/tools/neon.json +173 -0
- package/data/tools/netlify.json +99 -0
- package/data/tools/new-relic.json +104 -0
- package/data/tools/novu.json +94 -0
- package/data/tools/orama.json +153 -0
- package/data/tools/paddle.json +46 -0
- package/data/tools/planetscale-postgres.json +108 -0
- package/data/tools/planetscale-vitess.json +82 -0
- package/data/tools/plausible.json +75 -0
- package/data/tools/posthog.json +101 -0
- package/data/tools/postmark.json +93 -0
- package/data/tools/railway.json +161 -0
- package/data/tools/render.json +94 -0
- package/data/tools/resend.json +135 -0
- package/data/tools/sanity.json +98 -0
- package/data/tools/sendgrid.json +78 -0
- package/data/tools/sentry.json +276 -0
- package/data/tools/statsig.json +70 -0
- package/data/tools/storyblok.json +121 -0
- package/data/tools/stripe.json +46 -0
- package/data/tools/supabase.json +100 -0
- package/data/tools/tigris.json +70 -0
- package/data/tools/trigger-dev.json +90 -0
- package/data/tools/turso.json +226 -0
- package/data/tools/unosend.json +88 -0
- package/data/tools/upstash.json +85 -0
- package/data/tools/vercel.json +146 -0
- package/data/tools/weaviate.json +93 -0
- package/data/tools/workos.json +127 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +23 -0
- package/dist/src/registry/registry.d.ts +52 -0
- package/dist/src/registry/registry.js +216 -0
- package/dist/src/schema/pricing.d.ts +191 -0
- package/dist/src/schema/pricing.js +89 -0
- package/dist/src/server.d.ts +3 -0
- package/dist/src/server.js +32 -0
- package/dist/src/tools/compare-tools.d.ts +61 -0
- package/dist/src/tools/compare-tools.js +115 -0
- package/dist/src/tools/estimate-cost.d.ts +44 -0
- package/dist/src/tools/estimate-cost.js +61 -0
- package/dist/src/tools/find-cheapest.d.ts +54 -0
- package/dist/src/tools/find-cheapest.js +93 -0
- package/dist/src/tools/get-pricing.d.ts +51 -0
- package/dist/src/tools/get-pricing.js +23 -0
- package/dist/src/tools/search-tools.d.ts +55 -0
- package/dist/src/tools/search-tools.js +52 -0
- package/package.json +40 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "weaviate",
|
|
3
|
+
"name": "Weaviate",
|
|
4
|
+
"description": "Open-source vector database for hybrid search, RAG, and AI-native applications with managed cloud deployment",
|
|
5
|
+
"url": "https://weaviate.io",
|
|
6
|
+
"pricingUrl": "https://weaviate.io/pricing.md",
|
|
7
|
+
"category": "search",
|
|
8
|
+
"tags": [
|
|
9
|
+
"vector-database",
|
|
10
|
+
"semantic-search",
|
|
11
|
+
"hybrid-search",
|
|
12
|
+
"rag",
|
|
13
|
+
"embeddings"
|
|
14
|
+
],
|
|
15
|
+
"lastVerified": "2026-04-02",
|
|
16
|
+
"freshnessCategory": "volatile",
|
|
17
|
+
"currency": "USD",
|
|
18
|
+
"portability": {
|
|
19
|
+
"switchingCost": "significant",
|
|
20
|
+
"openStandard": null,
|
|
21
|
+
"whatYouLose": "Weaviate-specific schema and module configuration, Query Agent integration, vector compression settings, multi-tenancy setup, hybrid search tuning"
|
|
22
|
+
},
|
|
23
|
+
"tiers": [
|
|
24
|
+
{
|
|
25
|
+
"name": "Free Trial",
|
|
26
|
+
"slug": "free-trial",
|
|
27
|
+
"pricingModel": "free",
|
|
28
|
+
"basePrice": 0,
|
|
29
|
+
"billingPeriod": "monthly",
|
|
30
|
+
"annualDiscount": null,
|
|
31
|
+
"seatPrice": null,
|
|
32
|
+
"usageMetrics": [
|
|
33
|
+
{
|
|
34
|
+
"name": "query-agent-requests",
|
|
35
|
+
"pricePerUnit": 0,
|
|
36
|
+
"unitQuantity": 1,
|
|
37
|
+
"includedQuantity": 250,
|
|
38
|
+
"unit": "requests"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"features": [],
|
|
42
|
+
"limits": {
|
|
43
|
+
"trialDays": 14,
|
|
44
|
+
"queryAgentRequests": 250
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "Flex",
|
|
49
|
+
"slug": "flex",
|
|
50
|
+
"pricingModel": "usage_based",
|
|
51
|
+
"basePrice": 45,
|
|
52
|
+
"billingPeriod": "monthly",
|
|
53
|
+
"annualDiscount": null,
|
|
54
|
+
"seatPrice": null,
|
|
55
|
+
"usageMetrics": [
|
|
56
|
+
{
|
|
57
|
+
"name": "query-agent-requests",
|
|
58
|
+
"pricePerUnit": 0,
|
|
59
|
+
"unitQuantity": 1,
|
|
60
|
+
"includedQuantity": 30000,
|
|
61
|
+
"unit": "requests"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"features": [],
|
|
65
|
+
"limits": {
|
|
66
|
+
"queryAgentRequests": 30000,
|
|
67
|
+
"uptimeSLA": 995
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "Premium",
|
|
72
|
+
"slug": "premium",
|
|
73
|
+
"pricingModel": "custom",
|
|
74
|
+
"basePrice": 400,
|
|
75
|
+
"billingPeriod": "monthly",
|
|
76
|
+
"annualDiscount": null,
|
|
77
|
+
"seatPrice": null,
|
|
78
|
+
"usageMetrics": [
|
|
79
|
+
{
|
|
80
|
+
"name": "query-agent-requests",
|
|
81
|
+
"pricePerUnit": 0,
|
|
82
|
+
"unitQuantity": 1,
|
|
83
|
+
"includedQuantity": 0,
|
|
84
|
+
"unit": "requests"
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"features": [],
|
|
88
|
+
"limits": {
|
|
89
|
+
"uptimeSLA": 9995
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "workos",
|
|
3
|
+
"name": "WorkOS",
|
|
4
|
+
"description": "Enterprise-ready authentication and identity platform with SSO, directory sync, and user management",
|
|
5
|
+
"url": "https://workos.com",
|
|
6
|
+
"pricingUrl": "https://workos.com/pricing.md",
|
|
7
|
+
"category": "auth",
|
|
8
|
+
"tags": [
|
|
9
|
+
"authentication",
|
|
10
|
+
"sso",
|
|
11
|
+
"directory-sync",
|
|
12
|
+
"enterprise",
|
|
13
|
+
"oauth",
|
|
14
|
+
"oidc",
|
|
15
|
+
"scim"
|
|
16
|
+
],
|
|
17
|
+
"lastVerified": "2026-04-02",
|
|
18
|
+
"freshnessCategory": "volatile",
|
|
19
|
+
"currency": "USD",
|
|
20
|
+
"portability": {
|
|
21
|
+
"switchingCost": "significant",
|
|
22
|
+
"openStandard": "OAuth 2.0 / OIDC",
|
|
23
|
+
"whatYouLose": "AuthKit UI components, WorkOS SDKs, SSO connection configurations, directory sync integrations, audit log pipeline"
|
|
24
|
+
},
|
|
25
|
+
"tiers": [
|
|
26
|
+
{
|
|
27
|
+
"name": "Pay as You Go",
|
|
28
|
+
"slug": "pay-as-you-go",
|
|
29
|
+
"pricingModel": "usage_based",
|
|
30
|
+
"basePrice": 0,
|
|
31
|
+
"billingPeriod": "monthly",
|
|
32
|
+
"annualDiscount": null,
|
|
33
|
+
"seatPrice": null,
|
|
34
|
+
"usageMetrics": [
|
|
35
|
+
{
|
|
36
|
+
"name": "MAUs",
|
|
37
|
+
"pricePerUnit": 2500,
|
|
38
|
+
"unitQuantity": 1000000,
|
|
39
|
+
"includedQuantity": 1000000,
|
|
40
|
+
"tieredPricing": [
|
|
41
|
+
{
|
|
42
|
+
"upTo": 1000000,
|
|
43
|
+
"pricePerUnit": 0
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"upTo": null,
|
|
47
|
+
"pricePerUnit": 2500
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"unit": "MAUs"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "SSO connections",
|
|
54
|
+
"pricePerUnit": 125,
|
|
55
|
+
"unitQuantity": 1,
|
|
56
|
+
"includedQuantity": 0,
|
|
57
|
+
"tieredPricing": [
|
|
58
|
+
{
|
|
59
|
+
"upTo": 15,
|
|
60
|
+
"pricePerUnit": 125
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"upTo": 30,
|
|
64
|
+
"pricePerUnit": 100
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"upTo": 50,
|
|
68
|
+
"pricePerUnit": 80
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"upTo": 100,
|
|
72
|
+
"pricePerUnit": 65
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"upTo": 200,
|
|
76
|
+
"pricePerUnit": 50
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"unit": "connections"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "Directory Sync connections",
|
|
83
|
+
"pricePerUnit": 125,
|
|
84
|
+
"unitQuantity": 1,
|
|
85
|
+
"includedQuantity": 0,
|
|
86
|
+
"tieredPricing": [
|
|
87
|
+
{
|
|
88
|
+
"upTo": 15,
|
|
89
|
+
"pricePerUnit": 125
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"upTo": 30,
|
|
93
|
+
"pricePerUnit": 100
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"upTo": 50,
|
|
97
|
+
"pricePerUnit": 80
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"upTo": 100,
|
|
101
|
+
"pricePerUnit": 65
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"upTo": 200,
|
|
105
|
+
"pricePerUnit": 50
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
"unit": "connections"
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"features": [],
|
|
112
|
+
"limits": {}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "Annual Credits",
|
|
116
|
+
"slug": "annual-credits",
|
|
117
|
+
"pricingModel": "custom",
|
|
118
|
+
"basePrice": null,
|
|
119
|
+
"billingPeriod": "annual",
|
|
120
|
+
"annualDiscount": null,
|
|
121
|
+
"seatPrice": null,
|
|
122
|
+
"usageMetrics": [],
|
|
123
|
+
"features": [],
|
|
124
|
+
"limits": {}
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
9
|
+
const registry_js_1 = require("./registry/registry.js");
|
|
10
|
+
const server_js_1 = require("./server.js");
|
|
11
|
+
// __dirname is dist/src/ when compiled, so go up two levels to project root
|
|
12
|
+
const DATA_DIR = path_1.default.join(__dirname, "..", "..", "data", "tools");
|
|
13
|
+
async function main() {
|
|
14
|
+
const registry = new registry_js_1.Registry(DATA_DIR);
|
|
15
|
+
console.error(`Pricing.md: loaded ${registry.size} tools across ${registry.categories().length} categories`);
|
|
16
|
+
const server = (0, server_js_1.createServer)(registry);
|
|
17
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
18
|
+
await server.connect(transport);
|
|
19
|
+
}
|
|
20
|
+
main().catch((err) => {
|
|
21
|
+
console.error("Fatal:", err);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ToolEntry, type Category } from "../schema/pricing.js";
|
|
2
|
+
export interface SearchParams {
|
|
3
|
+
query?: string;
|
|
4
|
+
category?: Category;
|
|
5
|
+
hasFreeOption?: boolean;
|
|
6
|
+
maxBasePrice?: number;
|
|
7
|
+
tags?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface EstimateCostParams {
|
|
10
|
+
toolId: string;
|
|
11
|
+
usage: Record<string, number>;
|
|
12
|
+
seats?: number;
|
|
13
|
+
tierSlug?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface CostEstimate {
|
|
16
|
+
toolId: string;
|
|
17
|
+
toolName: string;
|
|
18
|
+
tierName: string;
|
|
19
|
+
tierSlug: string;
|
|
20
|
+
currency: string;
|
|
21
|
+
basePrice: number;
|
|
22
|
+
seatCost: number;
|
|
23
|
+
usageCosts: {
|
|
24
|
+
metric: string;
|
|
25
|
+
quantity: number;
|
|
26
|
+
cost: number;
|
|
27
|
+
unit: string;
|
|
28
|
+
}[];
|
|
29
|
+
totalMonthly: number;
|
|
30
|
+
exceedsLimits: boolean;
|
|
31
|
+
limitWarnings: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare class Registry {
|
|
34
|
+
private tools;
|
|
35
|
+
private byCategory;
|
|
36
|
+
constructor(dataDir: string);
|
|
37
|
+
private loadTools;
|
|
38
|
+
get size(): number;
|
|
39
|
+
get(id: string): ToolEntry | undefined;
|
|
40
|
+
search(params: SearchParams): ToolEntry[];
|
|
41
|
+
compare(toolIds: string[]): (ToolEntry | null)[];
|
|
42
|
+
estimateCost(params: EstimateCostParams): CostEstimate[];
|
|
43
|
+
private checkSeatLimits;
|
|
44
|
+
private checkUsageLimits;
|
|
45
|
+
private isSeatLimitKey;
|
|
46
|
+
private normalizeLimitKey;
|
|
47
|
+
private normalizeMetricAlias;
|
|
48
|
+
private calculateMetricCost;
|
|
49
|
+
private calculateTieredCost;
|
|
50
|
+
categories(): string[];
|
|
51
|
+
allTools(): ToolEntry[];
|
|
52
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Registry = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const pricing_js_1 = require("../schema/pricing.js");
|
|
10
|
+
class Registry {
|
|
11
|
+
tools = new Map();
|
|
12
|
+
byCategory = new Map();
|
|
13
|
+
constructor(dataDir) {
|
|
14
|
+
this.loadTools(dataDir);
|
|
15
|
+
}
|
|
16
|
+
loadTools(dataDir) {
|
|
17
|
+
const files = fs_1.default.readdirSync(dataDir).filter((f) => f.endsWith(".json"));
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(dataDir, file), "utf-8");
|
|
20
|
+
const data = JSON.parse(raw);
|
|
21
|
+
const result = pricing_js_1.ToolEntrySchema.safeParse(data);
|
|
22
|
+
if (!result.success) {
|
|
23
|
+
console.warn(`Skipping ${file}: invalid schema (${result.error.issues.length} issues)`);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const tool = result.data;
|
|
27
|
+
this.tools.set(tool.id, tool);
|
|
28
|
+
const catList = this.byCategory.get(tool.category) || [];
|
|
29
|
+
catList.push(tool);
|
|
30
|
+
this.byCategory.set(tool.category, catList);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
get size() {
|
|
34
|
+
return this.tools.size;
|
|
35
|
+
}
|
|
36
|
+
get(id) {
|
|
37
|
+
return this.tools.get(id);
|
|
38
|
+
}
|
|
39
|
+
search(params) {
|
|
40
|
+
let results = Array.from(this.tools.values());
|
|
41
|
+
if (params.category) {
|
|
42
|
+
results = results.filter((t) => t.category === params.category);
|
|
43
|
+
}
|
|
44
|
+
if (params.hasFreeOption) {
|
|
45
|
+
results = results.filter((t) => t.tiers.some((tier) => tier.pricingModel === "free" || tier.basePrice === 0));
|
|
46
|
+
}
|
|
47
|
+
if (params.maxBasePrice !== undefined) {
|
|
48
|
+
results = results.filter((t) => t.tiers.some((tier) => tier.basePrice !== null && tier.basePrice <= params.maxBasePrice));
|
|
49
|
+
}
|
|
50
|
+
if (params.tags && params.tags.length > 0) {
|
|
51
|
+
const searchTags = params.tags.map((t) => t.toLowerCase());
|
|
52
|
+
results = results.filter((t) => searchTags.some((st) => t.tags.map((tag) => tag.toLowerCase()).includes(st)));
|
|
53
|
+
}
|
|
54
|
+
if (params.query) {
|
|
55
|
+
const q = params.query.toLowerCase();
|
|
56
|
+
results = results.filter((t) => t.name.toLowerCase().includes(q) ||
|
|
57
|
+
t.description.toLowerCase().includes(q) ||
|
|
58
|
+
t.tags.some((tag) => tag.toLowerCase().includes(q)) ||
|
|
59
|
+
t.category.toLowerCase().includes(q));
|
|
60
|
+
}
|
|
61
|
+
return results;
|
|
62
|
+
}
|
|
63
|
+
compare(toolIds) {
|
|
64
|
+
return toolIds.map((id) => this.tools.get(id) || null);
|
|
65
|
+
}
|
|
66
|
+
estimateCost(params) {
|
|
67
|
+
const tool = this.tools.get(params.toolId);
|
|
68
|
+
if (!tool)
|
|
69
|
+
return [];
|
|
70
|
+
const tiersToEstimate = params.tierSlug
|
|
71
|
+
? tool.tiers.filter((t) => t.slug === params.tierSlug)
|
|
72
|
+
: tool.tiers.filter((t) => t.pricingModel !== "custom");
|
|
73
|
+
return tiersToEstimate.map((tier) => {
|
|
74
|
+
const basePrice = tier.basePrice ?? 0;
|
|
75
|
+
const seats = params.seats ?? 1;
|
|
76
|
+
const seatCost = tier.seatPrice !== null ? tier.seatPrice * seats : 0;
|
|
77
|
+
const usageCosts = [];
|
|
78
|
+
const limitWarnings = [];
|
|
79
|
+
this.checkSeatLimits(tier.limits, params.seats, limitWarnings);
|
|
80
|
+
for (const metric of tier.usageMetrics) {
|
|
81
|
+
const quantity = params.usage[metric.name];
|
|
82
|
+
if (quantity === undefined)
|
|
83
|
+
continue;
|
|
84
|
+
// Check if usage exceeds included quantity on a free/capped tier
|
|
85
|
+
if (quantity > metric.includedQuantity && metric.pricePerUnit === 0 && !metric.tieredPricing) {
|
|
86
|
+
limitWarnings.push(`${metric.name}: usage (${quantity.toLocaleString()}) exceeds included ${metric.includedQuantity.toLocaleString()} ${metric.unit} with no overage pricing — this tier won't work`);
|
|
87
|
+
}
|
|
88
|
+
const cost = this.calculateMetricCost(metric, quantity);
|
|
89
|
+
usageCosts.push({
|
|
90
|
+
metric: metric.name,
|
|
91
|
+
quantity,
|
|
92
|
+
cost,
|
|
93
|
+
unit: metric.unit,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
this.checkUsageLimits(tier.limits, params.usage, limitWarnings);
|
|
97
|
+
const totalMonthly = basePrice + seatCost + usageCosts.reduce((sum, uc) => sum + uc.cost, 0);
|
|
98
|
+
return {
|
|
99
|
+
toolId: tool.id,
|
|
100
|
+
toolName: tool.name,
|
|
101
|
+
tierName: tier.name,
|
|
102
|
+
tierSlug: tier.slug,
|
|
103
|
+
currency: tool.currency,
|
|
104
|
+
basePrice,
|
|
105
|
+
seatCost,
|
|
106
|
+
usageCosts,
|
|
107
|
+
totalMonthly,
|
|
108
|
+
exceedsLimits: limitWarnings.length > 0,
|
|
109
|
+
limitWarnings,
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
checkSeatLimits(limits, requestedSeats, limitWarnings) {
|
|
114
|
+
if (requestedSeats === undefined)
|
|
115
|
+
return;
|
|
116
|
+
const seatLimitEntries = Object.entries(limits).filter(([key]) => this.isSeatLimitKey(key));
|
|
117
|
+
for (const [key, limit] of seatLimitEntries) {
|
|
118
|
+
if (requestedSeats > limit) {
|
|
119
|
+
limitWarnings.push(`seats: requested ${requestedSeats} exceeds ${key} limit of ${limit} on this tier`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
checkUsageLimits(limits, usage, limitWarnings) {
|
|
124
|
+
for (const [limitKey, limitValue] of Object.entries(limits)) {
|
|
125
|
+
const normalized = this.normalizeLimitKey(limitKey);
|
|
126
|
+
if (!normalized)
|
|
127
|
+
continue;
|
|
128
|
+
const requestedQuantity = usage[normalized.metric];
|
|
129
|
+
if (requestedQuantity === undefined)
|
|
130
|
+
continue;
|
|
131
|
+
if (normalized.period === "monthly" && requestedQuantity > limitValue) {
|
|
132
|
+
limitWarnings.push(`${normalized.metric}: requested ${requestedQuantity.toLocaleString()} exceeds ${limitKey} limit of ${limitValue.toLocaleString()} on this tier`);
|
|
133
|
+
}
|
|
134
|
+
if (normalized.period === "daily") {
|
|
135
|
+
const maxMonthlyQuantity = limitValue * 31;
|
|
136
|
+
if (requestedQuantity > maxMonthlyQuantity) {
|
|
137
|
+
limitWarnings.push(`${normalized.metric}: requested ${requestedQuantity.toLocaleString()} exceeds ${limitKey} limit of ${limitValue.toLocaleString()} per day on this tier`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
isSeatLimitKey(key) {
|
|
143
|
+
return ["seats", "seat", "teammates", "teammate", "members", "users"].some((token) => key.toLowerCase().includes(token));
|
|
144
|
+
}
|
|
145
|
+
normalizeLimitKey(key) {
|
|
146
|
+
const lowerKey = key.toLowerCase();
|
|
147
|
+
if (this.isSeatLimitKey(lowerKey))
|
|
148
|
+
return null;
|
|
149
|
+
const period = lowerKey.includes("permonth") || lowerKey.includes("monthly")
|
|
150
|
+
? "monthly"
|
|
151
|
+
: lowerKey.includes("perday") || lowerKey.includes("daily")
|
|
152
|
+
? "daily"
|
|
153
|
+
: "unknown";
|
|
154
|
+
const metric = lowerKey
|
|
155
|
+
.replace(/permonth|perday|monthly|daily/g, "")
|
|
156
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
157
|
+
.replace(/^-+|-+$/g, "");
|
|
158
|
+
if (!metric)
|
|
159
|
+
return null;
|
|
160
|
+
return {
|
|
161
|
+
metric: this.normalizeMetricAlias(metric),
|
|
162
|
+
period,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
normalizeMetricAlias(metric) {
|
|
166
|
+
const aliases = {
|
|
167
|
+
email: "emails",
|
|
168
|
+
emails: "emails",
|
|
169
|
+
request: "requests",
|
|
170
|
+
requests: "requests",
|
|
171
|
+
seat: "seats",
|
|
172
|
+
seats: "seats",
|
|
173
|
+
teammate: "seats",
|
|
174
|
+
teammates: "seats",
|
|
175
|
+
member: "seats",
|
|
176
|
+
members: "seats",
|
|
177
|
+
user: "seats",
|
|
178
|
+
users: "seats",
|
|
179
|
+
};
|
|
180
|
+
return aliases[metric] ?? metric;
|
|
181
|
+
}
|
|
182
|
+
calculateMetricCost(metric, quantity) {
|
|
183
|
+
const billableQuantity = Math.max(0, quantity - metric.includedQuantity);
|
|
184
|
+
if (billableQuantity === 0)
|
|
185
|
+
return 0;
|
|
186
|
+
if (metric.tieredPricing && metric.tieredPricing.length > 0) {
|
|
187
|
+
return this.calculateTieredCost(metric.tieredPricing, quantity, metric.includedQuantity, metric.unitQuantity);
|
|
188
|
+
}
|
|
189
|
+
return (billableQuantity / metric.unitQuantity) * metric.pricePerUnit;
|
|
190
|
+
}
|
|
191
|
+
calculateTieredCost(tiers, quantity, includedQuantity, unitQuantity) {
|
|
192
|
+
let cost = 0;
|
|
193
|
+
let remaining = Math.max(0, quantity - includedQuantity);
|
|
194
|
+
let prevUpTo = includedQuantity;
|
|
195
|
+
for (const tier of tiers) {
|
|
196
|
+
if (remaining <= 0)
|
|
197
|
+
break;
|
|
198
|
+
if (tier.upTo !== null && tier.upTo <= includedQuantity)
|
|
199
|
+
continue;
|
|
200
|
+
const tierCeiling = tier.upTo !== null ? Math.max(0, tier.upTo - prevUpTo) : remaining;
|
|
201
|
+
const quantityInTier = Math.min(remaining, tierCeiling);
|
|
202
|
+
cost += (quantityInTier / unitQuantity) * tier.pricePerUnit;
|
|
203
|
+
remaining -= quantityInTier;
|
|
204
|
+
if (tier.upTo !== null)
|
|
205
|
+
prevUpTo = tier.upTo;
|
|
206
|
+
}
|
|
207
|
+
return cost;
|
|
208
|
+
}
|
|
209
|
+
categories() {
|
|
210
|
+
return Array.from(this.byCategory.keys()).sort();
|
|
211
|
+
}
|
|
212
|
+
allTools() {
|
|
213
|
+
return Array.from(this.tools.values());
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.Registry = Registry;
|