mirror of
https://github.com/Heretek-AI/Token-Max.git
synced 2026-10-12 02:03:18 +00:00
chore(guardrails): add data validator, wire into CI, reproducible plan generation
- scripts/validate-data.mjs enforces plan schema + AGENTS invariants (non-empty limits/models, positive token budgets on paid tiers, conservative <= midpoint <= optimistic, annual price math, id/filename) - validate-data + lint now run in deploy and update-data workflows - generate_plans.py: relative output path, single LAST_VERIFIED constant - fix zero-token paid tiers (kilo-code Teams, opencode Zen, tabnine) with documented platform-fee-equivalent estimates so stacking no longer drops them
This commit is contained in:
@@ -30,6 +30,12 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Validate data invariants
|
||||
run: npm run validate-data
|
||||
|
||||
- name: Build site
|
||||
run: npm run build
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ jobs:
|
||||
AA_API_KEY: ${{ secrets.AA_API_KEY }}
|
||||
run: npm run update-data
|
||||
|
||||
- name: Validate data invariants
|
||||
run: npm run validate-data
|
||||
|
||||
- name: Commit and push changes
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
|
||||
@@ -33,9 +33,11 @@
|
||||
"Kilo Gateway (500+ models)"
|
||||
],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Platform fee only; inference at provider list rates",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "Token cost = provider rates + 5% gateway fee (or BYOK, zero fee)"
|
||||
"description": "Platform fee; ~15M fee-equivalent tokens at open-model rates",
|
||||
"estimatedMillionTokens": 15,
|
||||
"midpointEstimate": 30,
|
||||
"optimisticEstimate": 45,
|
||||
"assumptions": "Platform-fee plan: no token bundle included; inference billed separately at provider rates + 5% gateway fee (BYOK zero fee). Figures are the fee-equivalent value of $15 at ~$1.00/M (conservative) to ~$0.33/M (optimistic open-model gateway rates) for cross-plan comparison only"
|
||||
},
|
||||
"notes": "Security/governance features included"
|
||||
},
|
||||
|
||||
@@ -40,9 +40,11 @@
|
||||
"Free stealth models"
|
||||
],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Per-token billing at curated gateway rates; min $20",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "Balance-based; cost equals published per-model rates"
|
||||
"description": "Prepaid balance (~10M fee-equivalent tokens)",
|
||||
"estimatedMillionTokens": 10,
|
||||
"midpointEstimate": 20,
|
||||
"optimisticEstimate": 40,
|
||||
"assumptions": "$20 minimum balance at zero markup buys $20 of inference at curated gateway rates; conservative 10M at ~$2.00/M frontier blend, midpoint 20M at ~$1.00/M, optimistic 40M at ~$0.50/M open-model rates; excludes the $1.23 card fee"
|
||||
},
|
||||
"notes": "US-hosted, zero retention"
|
||||
},
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
"Tabnine Protected"
|
||||
],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Uncapped volumes; token cost = LLM provider list price + 5% service fee",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "No fixed monthly token bundle; supply your own LLM for unlimited usage, or reserve token quota at actual provider rates"
|
||||
"description": "Platform fee; ~19.5M fee-equivalent tokens",
|
||||
"estimatedMillionTokens": 19.5,
|
||||
"midpointEstimate": 39,
|
||||
"optimisticEstimate": 78,
|
||||
"assumptions": "Platform-fee plan: no token bundle included; Tabnine-provided LLM access is billed at provider list price + 5% handling fee, BYO LLM endpoint is unlimited. Figures are fee-equivalent at ~$2.00/M (conservative), ~$1.00/M (midpoint) and ~$0.50/M (optimistic) for cross-plan comparison only"
|
||||
},
|
||||
"notes": "Completions + grounded chat in IDEs; Jira integration; SOC 2/ISO 27001"
|
||||
},
|
||||
@@ -35,9 +37,11 @@
|
||||
"Tabnine Protected"
|
||||
],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Uncapped; provider list price + 5% handling fee",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "No fixed token bundle; value is the platform, agents and context engine rather than quota"
|
||||
"description": "Platform fee; ~29.5M fee-equivalent tokens",
|
||||
"estimatedMillionTokens": 29.5,
|
||||
"midpointEstimate": 59,
|
||||
"optimisticEstimate": 118,
|
||||
"assumptions": "Platform-fee plan: no token bundle included; same LLM economics as Code Assistant. Figures are fee-equivalent at ~$2.00/M / ~$1.00/M / ~$0.50/M for cross-plan comparison only"
|
||||
},
|
||||
"notes": "Adds autonomous agents, Coaching Guidelines, Jira/Confluence/database MCP access"
|
||||
},
|
||||
|
||||
+55
-46
@@ -5,7 +5,8 @@ null = None
|
||||
false = False
|
||||
true = True
|
||||
|
||||
target_dir = "/home/john/Projects/Token-Max/data/coding-plans"
|
||||
LAST_VERIFIED = "2026-09-18"
|
||||
target_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "coding-plans")
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
|
||||
|
||||
@@ -24,7 +25,7 @@ write_json(
|
||||
"name": "Cursor",
|
||||
"category": "coding-ide",
|
||||
"url": "https://cursor.com/docs/account/pricing",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Hobby",
|
||||
@@ -142,7 +143,7 @@ write_json(
|
||||
"name": "GitHub Copilot",
|
||||
"category": "coding-ide",
|
||||
"url": "https://github.com/features/copilot/plans",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free",
|
||||
@@ -284,7 +285,7 @@ write_json(
|
||||
"name": "Claude Code",
|
||||
"category": "coding-ide",
|
||||
"url": "https://claude.com/pricing",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Pro",
|
||||
@@ -365,7 +366,7 @@ write_json(
|
||||
"name": "OpenAI Codex (ChatGPT)",
|
||||
"category": "coding-ide",
|
||||
"url": "https://openai.com/chatgpt/pricing/",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free",
|
||||
@@ -457,7 +458,7 @@ write_json(
|
||||
"name": "Google Antigravity",
|
||||
"category": "coding-ide",
|
||||
"url": "https://antigravity.google/pricing",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Individual",
|
||||
@@ -584,7 +585,7 @@ write_json(
|
||||
"name": "Meta Muse Code",
|
||||
"category": "coding-ide",
|
||||
"url": "https://developer.meta.com/ai/products/muse-code",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Everyday",
|
||||
@@ -657,7 +658,7 @@ write_json(
|
||||
"name": "Kiro",
|
||||
"category": "coding-ide",
|
||||
"url": "https://kiro.dev/pricing/",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free",
|
||||
@@ -784,7 +785,7 @@ write_json(
|
||||
"name": "Kilo Code",
|
||||
"category": "coding-router",
|
||||
"url": "https://kilo.ai/pricing",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Individual",
|
||||
@@ -810,9 +811,11 @@ write_json(
|
||||
},
|
||||
"models": ["Kilo Gateway (500+ models)"],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Platform fee only; inference at provider list rates",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "Token cost = provider rates + 5% gateway fee (or BYOK, zero fee)",
|
||||
"description": "Platform fee; ~15M fee-equivalent tokens at open-model rates",
|
||||
"estimatedMillionTokens": 15,
|
||||
"midpointEstimate": 30,
|
||||
"optimisticEstimate": 45,
|
||||
"assumptions": "Platform-fee plan: no token bundle included; inference billed separately at provider rates + 5% gateway fee (BYOK zero fee). Figures are the fee-equivalent value of $15 at ~$1.00/M (conservative) to ~$0.33/M (optimistic open-model gateway rates) for cross-plan comparison only",
|
||||
},
|
||||
"notes": "Security/governance features included",
|
||||
},
|
||||
@@ -849,7 +852,7 @@ write_json(
|
||||
"name": "Lovable",
|
||||
"category": "coding-ide",
|
||||
"url": "https://lovable.dev/pricing",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free",
|
||||
@@ -921,7 +924,7 @@ write_json(
|
||||
"name": "Kimi Code",
|
||||
"category": "coding-router",
|
||||
"url": "https://www.kimi.com/coding-plan/",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Moderato",
|
||||
@@ -1004,7 +1007,7 @@ write_json(
|
||||
"name": "Windsurf (Cognition)",
|
||||
"category": "coding-ide",
|
||||
"url": "https://windsurf.com/pricing",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free",
|
||||
@@ -1111,7 +1114,7 @@ write_json(
|
||||
"name": "Augment Code",
|
||||
"category": "coding-ide",
|
||||
"url": "https://www.augmentcode.com/pricing",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Standard",
|
||||
@@ -1180,7 +1183,7 @@ write_json(
|
||||
"name": "Replit",
|
||||
"category": "coding-ide",
|
||||
"url": "https://replit.com/pricing",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Core",
|
||||
@@ -1249,7 +1252,7 @@ write_json(
|
||||
"name": "Amazon Q Developer",
|
||||
"category": "coding-ide",
|
||||
"url": "https://aws.amazon.com/q/developer/pricing/",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free Tier",
|
||||
@@ -1303,7 +1306,7 @@ write_json(
|
||||
"name": "Tabnine",
|
||||
"category": "coding-ide",
|
||||
"url": "https://www.tabnine.com/pricing/",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Code Assistant",
|
||||
@@ -1315,9 +1318,11 @@ write_json(
|
||||
},
|
||||
"models": ["Tabnine Protected"],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Uncapped volumes; token cost = LLM provider list price + 5% service fee",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "No fixed monthly token bundle; supply your own LLM for unlimited usage, or reserve token quota at actual provider rates",
|
||||
"description": "Platform fee; ~19.5M fee-equivalent tokens",
|
||||
"estimatedMillionTokens": 19.5,
|
||||
"midpointEstimate": 39,
|
||||
"optimisticEstimate": 78,
|
||||
"assumptions": "Platform-fee plan: no token bundle included; Tabnine-provided LLM access is billed at provider list price + 5% handling fee, BYO LLM endpoint is unlimited. Figures are fee-equivalent at ~$2.00/M (conservative), ~$1.00/M (midpoint) and ~$0.50/M (optimistic) for cross-plan comparison only",
|
||||
},
|
||||
"notes": "Completions + grounded chat in IDEs; Jira integration; SOC 2/ISO 27001",
|
||||
},
|
||||
@@ -1331,9 +1336,11 @@ write_json(
|
||||
},
|
||||
"models": ["Tabnine Protected"],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Uncapped; provider list price + 5% handling fee",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "No fixed token bundle; value is the platform, agents and context engine rather than quota",
|
||||
"description": "Platform fee; ~29.5M fee-equivalent tokens",
|
||||
"estimatedMillionTokens": 29.5,
|
||||
"midpointEstimate": 59,
|
||||
"optimisticEstimate": 118,
|
||||
"assumptions": "Platform-fee plan: no token bundle included; same LLM economics as Code Assistant. Figures are fee-equivalent at ~$2.00/M / ~$1.00/M / ~$0.50/M for cross-plan comparison only",
|
||||
},
|
||||
"notes": "Adds autonomous agents, Coaching Guidelines, Jira/Confluence/database MCP access",
|
||||
},
|
||||
@@ -1375,7 +1382,7 @@ write_json(
|
||||
"name": "Aider",
|
||||
"category": "coding-ide",
|
||||
"url": "https://aider.chat",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free / BYOK",
|
||||
@@ -1421,7 +1428,7 @@ write_json(
|
||||
"name": "CommandCode",
|
||||
"category": "coding-router",
|
||||
"url": "https://commandcode.ai/pricing",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Go",
|
||||
@@ -1530,7 +1537,7 @@ write_json(
|
||||
"name": "OpenCode",
|
||||
"category": "coding-router",
|
||||
"url": "https://opencode.ai/zen",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "OpenSource CLI",
|
||||
@@ -1565,9 +1572,11 @@ write_json(
|
||||
"Free stealth models",
|
||||
],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Per-token billing at curated gateway rates; min $20",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "Balance-based; cost equals published per-model rates",
|
||||
"description": "Prepaid balance (~10M fee-equivalent tokens)",
|
||||
"estimatedMillionTokens": 10,
|
||||
"midpointEstimate": 20,
|
||||
"optimisticEstimate": 40,
|
||||
"assumptions": "$20 minimum balance at zero markup buys $20 of inference at curated gateway rates; conservative 10M at ~$2.00/M frontier blend, midpoint 20M at ~$1.00/M, optimistic 40M at ~$0.50/M open-model rates; excludes the $1.23 card fee",
|
||||
},
|
||||
"notes": "US-hosted, zero retention",
|
||||
},
|
||||
@@ -1612,7 +1621,7 @@ write_json(
|
||||
"name": "OpenRouter",
|
||||
"category": "coding-router",
|
||||
"url": "https://openrouter.ai",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free",
|
||||
@@ -1668,7 +1677,7 @@ write_json(
|
||||
"name": "BytePlus ModelArk Coding Plan",
|
||||
"category": "api-provider",
|
||||
"url": "https://www.byteplus.com/en/activity/arkcodingplan",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Lite",
|
||||
@@ -1734,7 +1743,7 @@ write_json(
|
||||
"name": "Alibaba Cloud",
|
||||
"category": "api-provider",
|
||||
"url": "https://www.alibabacloud.com/en/campaign/ai-landing-page-token",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Personal Lite",
|
||||
@@ -1849,7 +1858,7 @@ write_json(
|
||||
"name": "MiniMax Token Plan",
|
||||
"category": "api-provider",
|
||||
"url": "https://platform.minimax.io/docs/guides/pricing-token-plan",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Plus",
|
||||
@@ -1932,7 +1941,7 @@ write_json(
|
||||
"name": "OpenAI API",
|
||||
"category": "api-provider",
|
||||
"url": "https://platform.openai.com",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "PAYG",
|
||||
@@ -1981,7 +1990,7 @@ write_json(
|
||||
"name": "Anthropic API",
|
||||
"category": "api-provider",
|
||||
"url": "https://console.anthropic.com",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "PAYG",
|
||||
@@ -2030,7 +2039,7 @@ write_json(
|
||||
"name": "Google AI Studio",
|
||||
"category": "api-provider",
|
||||
"url": "https://aistudio.google.com",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free",
|
||||
@@ -2119,7 +2128,7 @@ write_json(
|
||||
"name": "DeepSeek API",
|
||||
"category": "api-provider",
|
||||
"url": "https://platform.deepseek.com",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "PAYG (Flash)",
|
||||
@@ -2174,7 +2183,7 @@ write_json(
|
||||
"name": "Groq API",
|
||||
"category": "api-provider",
|
||||
"url": "https://console.groq.com",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free",
|
||||
@@ -2228,7 +2237,7 @@ write_json(
|
||||
"name": "Mistral API",
|
||||
"category": "api-provider",
|
||||
"url": "https://mistral.ai/pricing",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free (Le Plan Studio)",
|
||||
@@ -2289,7 +2298,7 @@ write_json(
|
||||
"name": "Together.ai",
|
||||
"category": "api-provider",
|
||||
"url": "https://together.ai",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free",
|
||||
@@ -2345,7 +2354,7 @@ write_json(
|
||||
"name": "Fireworks.ai",
|
||||
"category": "api-provider",
|
||||
"url": "https://fireworks.ai",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free",
|
||||
@@ -2398,7 +2407,7 @@ write_json(
|
||||
"name": "Meta Llama API",
|
||||
"category": "api-provider",
|
||||
"url": "https://meta.com",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Standard",
|
||||
@@ -2454,7 +2463,7 @@ write_json(
|
||||
"name": "Ollama Cloud",
|
||||
"category": "api-provider",
|
||||
"url": "https://ollama.com",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Free",
|
||||
@@ -2544,7 +2553,7 @@ write_json(
|
||||
"name": "Z.ai GLM Coding Plan",
|
||||
"category": "api-provider",
|
||||
"url": "https://z.ai/subscribe",
|
||||
"lastVerified": "2026-09-18",
|
||||
"lastVerified": LAST_VERIFIED,
|
||||
"tiers": [
|
||||
{
|
||||
"name": "Lite",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"fetch-benchmarks": "node scripts/fetch-benchmarks.mjs",
|
||||
"build-data": "node scripts/build-data.mjs",
|
||||
"update-data": "node scripts/fetch-models.mjs && node scripts/fetch-benchmarks.mjs && node scripts/build-data.mjs",
|
||||
"validate-data": "node scripts/validate-data.mjs",
|
||||
"lint": "oxlint",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"timestamp": "2026-09-19T04:11:37.228Z"
|
||||
"timestamp": "2026-09-19T04:54:16.160Z"
|
||||
}
|
||||
+20
-12
@@ -1283,9 +1283,11 @@
|
||||
"Kilo Gateway (500+ models)"
|
||||
],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Platform fee only; inference at provider list rates",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "Token cost = provider rates + 5% gateway fee (or BYOK, zero fee)"
|
||||
"description": "Platform fee; ~15M fee-equivalent tokens at open-model rates",
|
||||
"estimatedMillionTokens": 15,
|
||||
"midpointEstimate": 30,
|
||||
"optimisticEstimate": 45,
|
||||
"assumptions": "Platform-fee plan: no token bundle included; inference billed separately at provider rates + 5% gateway fee (BYOK zero fee). Figures are the fee-equivalent value of $15 at ~$1.00/M (conservative) to ~$0.33/M (optimistic open-model gateway rates) for cross-plan comparison only"
|
||||
},
|
||||
"notes": "Security/governance features included"
|
||||
},
|
||||
@@ -2177,9 +2179,11 @@
|
||||
"Free stealth models"
|
||||
],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Per-token billing at curated gateway rates; min $20",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "Balance-based; cost equals published per-model rates"
|
||||
"description": "Prepaid balance (~10M fee-equivalent tokens)",
|
||||
"estimatedMillionTokens": 10,
|
||||
"midpointEstimate": 20,
|
||||
"optimisticEstimate": 40,
|
||||
"assumptions": "$20 minimum balance at zero markup buys $20 of inference at curated gateway rates; conservative 10M at ~$2.00/M frontier blend, midpoint 20M at ~$1.00/M, optimistic 40M at ~$0.50/M open-model rates; excludes the $1.23 card fee"
|
||||
},
|
||||
"notes": "US-hosted, zero retention"
|
||||
},
|
||||
@@ -2359,9 +2363,11 @@
|
||||
"Tabnine Protected"
|
||||
],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Uncapped volumes; token cost = LLM provider list price + 5% service fee",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "No fixed monthly token bundle; supply your own LLM for unlimited usage, or reserve token quota at actual provider rates"
|
||||
"description": "Platform fee; ~19.5M fee-equivalent tokens",
|
||||
"estimatedMillionTokens": 19.5,
|
||||
"midpointEstimate": 39,
|
||||
"optimisticEstimate": 78,
|
||||
"assumptions": "Platform-fee plan: no token bundle included; Tabnine-provided LLM access is billed at provider list price + 5% handling fee, BYO LLM endpoint is unlimited. Figures are fee-equivalent at ~$2.00/M (conservative), ~$1.00/M (midpoint) and ~$0.50/M (optimistic) for cross-plan comparison only"
|
||||
},
|
||||
"notes": "Completions + grounded chat in IDEs; Jira integration; SOC 2/ISO 27001"
|
||||
},
|
||||
@@ -2377,9 +2383,11 @@
|
||||
"Tabnine Protected"
|
||||
],
|
||||
"estimatedTokenBudget": {
|
||||
"description": "Uncapped; provider list price + 5% handling fee",
|
||||
"estimatedMillionTokens": 0,
|
||||
"assumptions": "No fixed token bundle; value is the platform, agents and context engine rather than quota"
|
||||
"description": "Platform fee; ~29.5M fee-equivalent tokens",
|
||||
"estimatedMillionTokens": 29.5,
|
||||
"midpointEstimate": 59,
|
||||
"optimisticEstimate": 118,
|
||||
"assumptions": "Platform-fee plan: no token bundle included; same LLM economics as Code Assistant. Figures are fee-equivalent at ~$2.00/M / ~$1.00/M / ~$0.50/M for cross-plan comparison only"
|
||||
},
|
||||
"notes": "Adds autonomous agents, Coaching Guidelines, Jira/Confluence/database MCP access"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
const PLANS_DIR = path.join(process.cwd(), 'data/coding-plans');
|
||||
const PUBLIC_PLANS = path.join(process.cwd(), 'public/data/plans.json');
|
||||
|
||||
const CATEGORIES = new Set(['coding-ide', 'coding-router', 'api-provider']);
|
||||
const STACKING_POLICIES = new Set(['allowed', 'silent', 'prohibited', 'unknown']);
|
||||
|
||||
const errors = [];
|
||||
|
||||
function err(msg) {
|
||||
errors.push(msg);
|
||||
}
|
||||
|
||||
function isIsoDate(value) {
|
||||
return typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(value));
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function validatePlan(plan, filename) {
|
||||
const where = `${filename}`;
|
||||
if (!plan || typeof plan !== 'object') {
|
||||
err(`${where}: not a JSON object`);
|
||||
return;
|
||||
}
|
||||
if (typeof plan.id !== 'string' || plan.id.length === 0) err(`${where}: missing id`);
|
||||
const idFromFile = filename.replace(/\.json$/, '');
|
||||
if (plan.id !== idFromFile) err(`${where}: id "${plan.id}" does not match filename`);
|
||||
if (typeof plan.name !== 'string' || plan.name.trim() === '') err(`${where}: missing name`);
|
||||
if (!CATEGORIES.has(plan.category)) err(`${where}: category "${plan.category}" is not one of ${[...CATEGORIES].join(', ')}`);
|
||||
if (typeof plan.url !== 'string' || !/^https?:\/\//.test(plan.url)) err(`${where}: url must be an http(s) URL`);
|
||||
if (!isIsoDate(plan.lastVerified)) err(`${where}: lastVerified must be YYYY-MM-DD`);
|
||||
if (typeof plan.dataTraining !== 'string' || plan.dataTraining.trim() === '') err(`${where}: dataTraining must be a non-empty string`);
|
||||
if (typeof plan.ipIndemnity !== 'boolean' && (typeof plan.ipIndemnity !== 'string' || plan.ipIndemnity.trim() === '')) {
|
||||
err(`${where}: ipIndemnity must be a boolean or non-empty string`);
|
||||
}
|
||||
if (!Array.isArray(plan.gotchas) || plan.gotchas.length === 0) err(`${where}: gotchas must be a non-empty array`);
|
||||
if (!Array.isArray(plan.tosHighlights)) err(`${where}: tosHighlights must be an array`);
|
||||
if (plan.stackingPolicy !== undefined && !STACKING_POLICIES.has(plan.stackingPolicy)) {
|
||||
err(`${where}: stackingPolicy "${plan.stackingPolicy}" must be one of ${[...STACKING_POLICIES].join(', ')}`);
|
||||
}
|
||||
|
||||
if (!Array.isArray(plan.tiers) || plan.tiers.length === 0) {
|
||||
err(`${where}: tiers must be a non-empty array`);
|
||||
return;
|
||||
}
|
||||
|
||||
const seenTierNames = new Set();
|
||||
for (const tier of plan.tiers) {
|
||||
const t = `${where} [${tier?.name ?? '?'}]`;
|
||||
if (!isPlainObject(tier)) {
|
||||
err(`${t}: tier is not an object`);
|
||||
continue;
|
||||
}
|
||||
if (typeof tier.name !== 'string' || tier.name.trim() === '') err(`${t}: missing tier name`);
|
||||
if (seenTierNames.has(tier.name)) err(`${t}: duplicate tier name`);
|
||||
seenTierNames.add(tier.name);
|
||||
if (tier.monthlyPrice !== null && (typeof tier.monthlyPrice !== 'number' || tier.monthlyPrice < 0 || !Number.isFinite(tier.monthlyPrice))) {
|
||||
err(`${t}: monthlyPrice must be a non-negative number or null`);
|
||||
}
|
||||
if (tier.annualPrice !== undefined && tier.annualPrice !== null) {
|
||||
if (typeof tier.annualPrice !== 'number' || tier.annualPrice <= 0) {
|
||||
err(`${t}: annualPrice must be a positive number or null`);
|
||||
} else if (typeof tier.monthlyPrice === 'number' && tier.monthlyPrice > 0 && tier.annualPrice / 12 >= tier.monthlyPrice) {
|
||||
err(`${t}: annualPrice/12 (${(tier.annualPrice / 12).toFixed(2)}) must be lower than monthlyPrice ${tier.monthlyPrice}`);
|
||||
}
|
||||
}
|
||||
if (!isPlainObject(tier.limits) || Object.keys(tier.limits).length === 0) err(`${t}: limits must be a non-empty object`);
|
||||
if (!Array.isArray(tier.models) || tier.models.length === 0) err(`${t}: models must be a non-empty array`);
|
||||
if (Array.isArray(tier.models)) {
|
||||
const dupes = tier.models.filter((m, i) => tier.models.indexOf(m) !== i);
|
||||
if (dupes.length > 0) err(`${t}: duplicate model entries: ${[...new Set(dupes)].join(', ')}`);
|
||||
}
|
||||
|
||||
const tb = tier.estimatedTokenBudget;
|
||||
if (!isPlainObject(tb)) {
|
||||
err(`${t}: estimatedTokenBudget must be an object (never null)`);
|
||||
continue;
|
||||
}
|
||||
if (typeof tb.description !== 'string' || tb.description.trim() === '') err(`${t}: estimatedTokenBudget.description required`);
|
||||
if (typeof tb.assumptions !== 'string' || tb.assumptions.trim() === '') err(`${t}: estimatedTokenBudget.assumptions required`);
|
||||
if (typeof tb.estimatedMillionTokens !== 'number' || !Number.isFinite(tb.estimatedMillionTokens)) {
|
||||
err(`${t}: estimatedTokenBudget.estimatedMillionTokens must be a number`);
|
||||
continue;
|
||||
}
|
||||
const paid = typeof tier.monthlyPrice === 'number' && tier.monthlyPrice > 0;
|
||||
if (paid && tb.estimatedMillionTokens <= 0) {
|
||||
err(`${t}: paid tier ($${tier.monthlyPrice}) must have estimatedMillionTokens > 0 (stacking drops zero budgets)`);
|
||||
}
|
||||
if (tb.estimatedMillionTokens < 0) err(`${t}: estimatedMillionTokens must not be negative`);
|
||||
if (tb.midpointEstimate !== undefined && tb.midpointEstimate !== null) {
|
||||
if (typeof tb.midpointEstimate !== 'number' || tb.midpointEstimate < tb.estimatedMillionTokens) {
|
||||
err(`${t}: midpointEstimate must be a number >= estimatedMillionTokens`);
|
||||
}
|
||||
}
|
||||
if (tb.optimisticEstimate !== undefined && tb.optimisticEstimate !== null) {
|
||||
const floor = tb.midpointEstimate ?? tb.estimatedMillionTokens;
|
||||
if (typeof tb.optimisticEstimate !== 'number' || tb.optimisticEstimate < floor) {
|
||||
err(`${t}: optimisticEstimate must be a number >= midpoint/conservative estimate`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let files = [];
|
||||
try {
|
||||
files = (await fs.readdir(PLANS_DIR)).filter(f => f.endsWith('.json') && !f.startsWith('_')).sort();
|
||||
} catch (e) {
|
||||
console.error(`Cannot read ${PLANS_DIR}: ${e.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (files.length === 0) {
|
||||
console.error('No plan files found.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const plans = new Map();
|
||||
for (const file of files) {
|
||||
const raw = await fs.readFile(path.join(PLANS_DIR, file), 'utf-8');
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
err(`${file}: invalid JSON: ${e.message}`);
|
||||
continue;
|
||||
}
|
||||
validatePlan(parsed, file);
|
||||
plans.set(parsed.id, parsed);
|
||||
}
|
||||
|
||||
// Cross-check the consolidated artifact the frontend consumes.
|
||||
let consolidated = null;
|
||||
try {
|
||||
consolidated = JSON.parse(await fs.readFile(PUBLIC_PLANS, 'utf-8'));
|
||||
} catch (e) {
|
||||
err(`public/data/plans.json: cannot read: ${e.message}`);
|
||||
}
|
||||
if (Array.isArray(consolidated)) {
|
||||
const consolidatedIds = new Set(consolidated.map(p => p.id));
|
||||
for (const id of plans.keys()) {
|
||||
if (!consolidatedIds.has(id)) err(`public/data/plans.json: missing plan "${id}" (stale build artifact?)`);
|
||||
}
|
||||
for (const p of consolidated) {
|
||||
if (!plans.has(p.id)) {
|
||||
err(`public/data/plans.json: contains unknown plan "${p.id}"`);
|
||||
continue;
|
||||
}
|
||||
const source = plans.get(p.id);
|
||||
if (JSON.stringify(source) !== JSON.stringify(p)) {
|
||||
err(`public/data/plans.json: "${p.id}" differs from data/coding-plans/${p.id}.json — run npm run build-data`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error(`\nValidation FAILED with ${errors.length} error(s):`);
|
||||
for (const e of errors) console.error(` - ${e}`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`\nValidated ${plans.size} plan files against schema invariants. OK`);
|
||||
}
|
||||
|
||||
main().catch(e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user