Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions src/lib/pricing-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
* Legacy grandfather only (do not use on new checkout paths):
* agent_pass_pro_monthly | agent_pass_pro_yearly
*/
export const PRICES_UPDATED_ON = "November 1, 2026";
export const PRICES_UPDATED_ON_ISO = "2026-11-01";

export const FREE_PRICING = { price: 0 } as const;

export const PRO_PRICING = {
monthly: 29,
yearly: 240,
Expand All @@ -26,14 +31,14 @@ export const PRO_INCLUDED_AGENTS = 1;
* Pro = human builder seat + 1 included production agent.
* Agent Pass = standalone monthly seat ($12/agent) — no Pro required; also covers extras beyond Pro's included 1.
*/
export const PRO_VS_AGENT_PASS =
`Pro is the human builder seat (Agent Factory, Store, University, reviews) and includes ${PRO_INCLUDED_AGENTS} production agent. Extra production agents beyond that are Agent Pass at $${AGENT_PASS_PRICING.monthlyPerAgent}/agent/mo. Agent Pass is also sold standalone (monthly only, no Pro required) for people who only run agents.`;
export const PRO_VS_AGENT_PASS = `Pro is the human builder seat (Agent Factory, Store, University, reviews) and includes ${PRO_INCLUDED_AGENTS} production agent. Extra production agents beyond that are Agent Pass at $${AGENT_PASS_PRICING.monthlyPerAgent}/agent/mo. Agent Pass is also sold standalone (monthly only, no Pro required) for people who only run agents.`;

export const PRO_YEARLY_SAVINGS = PRO_PRICING.yearlyList - PRO_PRICING.yearly; // 108
export const PRO_YEARLY_DISCOUNT_PCT = Math.round(
(PRO_YEARLY_SAVINGS / PRO_PRICING.yearlyList) * 100,
); // 31

export const PRICE_FREE = `$${FREE_PRICING.price}`;
export const PRICE_MONTHLY = `$${PRO_PRICING.monthly}`;
export const PRICE_YEARLY = `$${PRO_PRICING.yearly}`;
export const PRICE_YEARLY_LIST = `$${PRO_PRICING.yearlyList}`;
Expand All @@ -43,3 +48,65 @@ export const SAVE_SHORT = `save $${PRO_YEARLY_SAVINGS}`;

/** Canonical one-line answer reused in FAQ copy, meta descriptions and JSON-LD. */
export const PRICING_SENTENCE = `Browsing and installing public capabilities is free — no account needed. Pro is ${PRICE_YEARLY} per year (down from ${PRICE_YEARLY_LIST}, ${PRO_YEARLY_DISCOUNT_PCT}% off) or ${PRICE_MONTHLY} per month for the human builder seat (Agent Factory, Agent Store, SAK University and unlimited tested reviews) and includes ${PRO_INCLUDED_AGENTS} production agent. Extra production agents are Agent Pass at ${PRICE_AGENT_PASS} per agent per month (monthly only) — buy Agent Pass without Pro if you only run agents. Enterprise adds a private registry, SSO and audit logs.`;

/** Dated price source shared by the visible /pricing copy and structured data. */
export const PRICE_UPDATE_SENTENCE = `Prices updated on ${PRICES_UPDATED_ON}. Pro costs ${PRICE_MONTHLY} per month or ${PRICE_YEARLY} per year, and Agent Pass costs ${PRICE_AGENT_PASS} per agent per month.`;
export const PRICING_FAQ_QUESTION = "How much does Super Agent Skill cost?";
export const PRICING_FAQ_ANSWER = `${PRICE_UPDATE_SENTENCE} The Free plan costs ${PRICE_FREE} and is free forever. Enterprise pricing is custom.`;

const PRICING_URL = "https://superagentskill.com/pricing";

function paidOffer(name: string, price: number, unitText: string) {
return {
"@type": "Offer",
name,
url: PRICING_URL,
price: String(price),
priceCurrency: "USD",
priceSpecification: {
"@type": "UnitPriceSpecification",
name,
price: String(price),
priceCurrency: "USD",
unitText,
},
};
}

export const PRICING_SOFTWARE_LD = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: "Super Agent Skill",
applicationCategory: "DeveloperApplication",
operatingSystem: "Any (MCP-compatible)",
url: PRICING_URL,
dateModified: PRICES_UPDATED_ON_ISO,
offers: [
{
"@type": "Offer",
name: "Free",
url: PRICING_URL,
price: String(FREE_PRICING.price),
priceCurrency: "USD",
},
paidOffer("Pro monthly", PRO_PRICING.monthly, "MONTH"),
paidOffer("Pro yearly", PRO_PRICING.yearly, "YEAR"),
paidOffer("Agent Pass per agent per month", AGENT_PASS_PRICING.monthlyPerAgent, "agent/month"),
],
};

export const PRICING_FAQ_LD = {
"@context": "https://schema.org",
"@type": "FAQPage",
url: PRICING_URL,
mainEntity: [
{
"@type": "Question",
name: PRICING_FAQ_QUESTION,
acceptedAnswer: {
"@type": "Answer",
text: PRICING_FAQ_ANSWER,
},
},
],
};
29 changes: 23 additions & 6 deletions src/routes/pricing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createFileRoute, Link } from "@tanstack/react-router";
import { useState } from "react";
import { Check } from "lucide-react";
import { Nav } from "@/components/site/Nav";
import { JsonLd } from "@/components/site/JsonLd";
import { Footer } from "@/components/site/Footer";
import { PaymentTestModeBanner } from "@/components/PaymentTestModeBanner";
import { useAuth } from "@/hooks/use-auth";
Expand All @@ -10,6 +11,12 @@ import { useStripeCheckout } from "@/hooks/useStripeCheckout";
import {
AGENT_PASS_PRICING,
PRICE_AGENT_PASS,
PRICE_FREE,
PRICE_UPDATE_SENTENCE,
PRICING_FAQ_QUESTION,
PRICING_FAQ_ANSWER,
PRICING_SOFTWARE_LD,
PRICING_FAQ_LD,
PRICE_MONTHLY,
PRICE_YEARLY,
PRICE_YEARLY_LIST,
Expand Down Expand Up @@ -111,6 +118,7 @@ function Pricing() {

return (
<div className="min-h-screen bg-background">
<JsonLd data={[PRICING_SOFTWARE_LD, PRICING_FAQ_LD]} />
<PaymentTestModeBanner />
<Nav />

Expand All @@ -130,14 +138,15 @@ function Pricing() {
</section>

<section className="mx-auto max-w-6xl px-4 py-16 sm:px-6">
<p className="mb-8 text-center text-sm text-muted-foreground">{PRICE_UPDATE_SENTENCE}</p>
<div className="grid gap-5 lg:grid-cols-4">
{/* Free */}
<div className="rounded-2xl border border-border bg-background p-6">
<div className="font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">
Free forever
</div>
<div className="mt-4 flex items-end gap-1">
<span className="text-4xl font-semibold tracking-tight">$0</span>
<span className="text-4xl font-semibold tracking-tight">{PRICE_FREE}</span>
</div>
<p className="mt-3 text-sm text-muted-foreground">
For exploring the registry and shipping your first agent.
Expand All @@ -160,10 +169,12 @@ function Pricing() {

{/* Pro */}
<div className="rounded-2xl border border-primary/40 bg-background p-6 shadow-elevated">
<div className="font-mono text-[11px] uppercase tracking-[0.18em] text-primary">Pro</div>
<div className="font-mono text-[11px] uppercase tracking-[0.18em] text-primary">
Pro
</div>
<p className="mt-2 text-sm text-muted-foreground">
Pro is the human plan: Agent Factory, Agent Store, University and unlimited adversarial
reviews — plus 1 production agent included. Extra agents are Agent Pass.
Pro is the human plan: Agent Factory, Agent Store, University and unlimited
adversarial reviews — plus 1 production agent included. Extra agents are Agent Pass.
</p>

<div className="mt-4 flex w-fit items-center rounded-full border border-border bg-surface p-1 text-xs">
Expand Down Expand Up @@ -269,8 +280,7 @@ function Pricing() {
</div>
</label>
<p className="mt-2 text-sm text-muted-foreground">
Total{" "}
<span className="font-semibold text-foreground">${agentTotal}</span>
Total <span className="font-semibold text-foreground">${agentTotal}</span>
/month
</p>

Expand Down Expand Up @@ -321,6 +331,13 @@ function Pricing() {
</div>
</div>

<section className="mt-14" aria-labelledby="pricing-faq-heading">
<h2 id="pricing-faq-heading" className="text-2xl font-semibold tracking-tight">
{PRICING_FAQ_QUESTION}
</h2>
<p className="mt-3 max-w-3xl text-muted-foreground">{PRICING_FAQ_ANSWER}</p>
</section>

<div className="mt-14 rounded-2xl border border-border bg-surface p-8 md:flex md:items-center md:justify-between">
<div>
<div className="font-mono text-xs uppercase tracking-wider text-primary">
Expand Down
11 changes: 9 additions & 2 deletions src/routes/sitemap[.]xml.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ import { createFileRoute } from "@tanstack/react-router";
import { supabaseAdmin as _supabaseAdmin } from "@/integrations/supabase/client.server";
import { USE_CASES } from "@/lib/seo/use-cases";
import { COMPARISONS } from "@/lib/seo/comparisons";
import { PRICES_UPDATED_ON_ISO } from "@/lib/pricing-copy";

const supabaseAdmin = _supabaseAdmin;

const SITE = "https://superagentskill.com";

// Static marketing routes only — auth/account pages (/login, /reset-password,
// /account/*) are intentionally excluded from the sitemap.
const STATIC_ROUTES: Array<{ path: string; priority: number; changefreq: string }> = [
const STATIC_ROUTES: Array<{
path: string;
priority: number;
changefreq: string;
lastmod?: string;
}> = [
{ path: "/", priority: 1.0, changefreq: "weekly" },
{ path: "/about", priority: 0.5, changefreq: "weekly" },
{ path: "/how-it-works", priority: 0.85, changefreq: "monthly" },
Expand All @@ -19,7 +25,7 @@ const STATIC_ROUTES: Array<{ path: string; priority: number; changefreq: string
{ path: "/play", priority: 0.55, changefreq: "weekly" },
{ path: "/nda", priority: 0.3, changefreq: "yearly" },
{ path: "/connect", priority: 0.95, changefreq: "weekly" },
{ path: "/pricing", priority: 0.9, changefreq: "weekly" },
{ path: "/pricing", priority: 0.9, changefreq: "weekly", lastmod: PRICES_UPDATED_ON_ISO },
{ path: "/docs", priority: 0.9, changefreq: "weekly" },
{ path: "/agents.md", priority: 0.8, changefreq: "weekly" },
{ path: "/docs/mcp", priority: 0.85, changefreq: "weekly" },
Expand Down Expand Up @@ -110,6 +116,7 @@ export const Route = createFileRoute("/sitemap.xml")({
GET: async () => {
const staticUrls: UrlEntry[] = STATIC_ROUTES.map((r) => ({
loc: `${SITE}${r.path}`,
lastmod: r.lastmod,
priority: r.priority,
changefreq: r.changefreq,
}));
Expand Down
154 changes: 142 additions & 12 deletions tests/pricing-copy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ const {
PRO_VS_AGENT_PASS,
} = await import("../src/lib/pricing-copy.ts");

const {
validateCheckoutQuantity,
shouldEnableAdjustableQuantity,
} = await import("../src/lib/checkout-quantity.ts");
const { validateCheckoutQuantity, shouldEnableAdjustableQuantity } =
await import("../src/lib/checkout-quantity.ts");

test("Pro pricing: $29/mo, $240/yr, list $348 → Save $108 · 31% off", () => {
assert.equal(PRO_PRICING.monthly, 29);
Expand Down Expand Up @@ -61,14 +59,8 @@ test("shouldEnableAdjustableQuantity defaults for Agent Pass lookup keys", () =>
shouldEnableAdjustableQuantity({ priceId: "sak_agent_pass_monthly", quantity: 3 }),
true,
);
assert.equal(
shouldEnableAdjustableQuantity({ priceId: "sak_pro_monthly" }),
false,
);
assert.equal(
shouldEnableAdjustableQuantity({ priceId: "sak_pro_monthly", quantity: 2 }),
true,
);
assert.equal(shouldEnableAdjustableQuantity({ priceId: "sak_pro_monthly" }), false);
assert.equal(shouldEnableAdjustableQuantity({ priceId: "sak_pro_monthly", quantity: 2 }), true);
assert.equal(
shouldEnableAdjustableQuantity({
priceId: "sak_pro_monthly",
Expand All @@ -78,3 +70,141 @@ test("shouldEnableAdjustableQuantity defaults for Agent Pass lookup keys", () =>
false,
);
});

const {
FREE_PRICING,
PRICE_FREE,
PRICES_UPDATED_ON,
PRICES_UPDATED_ON_ISO,
PRICE_UPDATE_SENTENCE,
PRICING_FAQ_QUESTION,
PRICING_FAQ_ANSWER,
PRICING_SOFTWARE_LD,
PRICING_FAQ_LD,
} = await import("../src/lib/pricing-copy.ts");

test("dated pricing copy publishes the confirmed prices and update date", () => {
assert.equal(PRICES_UPDATED_ON, "November 1, 2026");
assert.equal(PRICES_UPDATED_ON_ISO, "2026-11-01");
assert.equal(FREE_PRICING.price, 0);
assert.equal(PRICE_FREE, "$0");
assert.equal(
PRICE_UPDATE_SENTENCE,
"Prices updated on November 1, 2026. Pro costs $29 per month or $240 per year, and Agent Pass costs $12 per agent per month.",
);
assert.ok(PRICING_FAQ_ANSWER.startsWith(PRICE_UPDATE_SENTENCE));
assert.match(PRICING_FAQ_ANSWER, /Free plan costs \$0 and is free forever/);
assert.ok(!PRICING_FAQ_ANSWER.includes("—"));
});

test("pricing SoftwareApplication JSON-LD exposes Free and each paid billing unit", () => {
const json = JSON.stringify(PRICING_SOFTWARE_LD);
const schema = JSON.parse(json);
assert.equal(schema["@context"], "https://schema.org");
assert.equal(schema["@type"], "SoftwareApplication");
assert.equal(schema.name, "Super Agent Skill");
assert.equal(schema.url, "https://superagentskill.com/pricing");
assert.equal(schema.dateModified, PRICES_UPDATED_ON_ISO);
assert.ok(!json.includes("aggregateRating"));
assert.equal(schema.offers.length, 4);
const [free, monthly, yearly, agentPass] = schema.offers;
assert.equal(free.name, "Free");
assert.equal(free.price, "0");
for (const offer of schema.offers) {
assert.equal(offer["@type"], "Offer");
assert.equal(offer.priceCurrency, "USD");
assert.equal(offer.url, schema.url);
}
for (const [offer, name, price, unitText] of [
[monthly, "Pro monthly", "29", "MONTH"],
[yearly, "Pro yearly", "240", "YEAR"],
[agentPass, "Agent Pass per agent per month", "12", "agent/month"],
]) {
assert.equal(offer.name, name);
assert.equal(offer.price, price);
assert.deepEqual(offer.priceSpecification, {
"@type": "UnitPriceSpecification",
name,
price,
priceCurrency: "USD",
unitText,
});
}
});

test("pricing FAQPage JSON-LD uses the same complete question and visible answer", () => {
const schema = JSON.parse(JSON.stringify(PRICING_FAQ_LD));
assert.equal(schema["@context"], "https://schema.org");
assert.equal(schema["@type"], "FAQPage");
assert.equal(PRICING_FAQ_QUESTION, "How much does Super Agent Skill cost?");
assert.deepEqual(schema.mainEntity, [
{
"@type": "Question",
name: PRICING_FAQ_QUESTION,
acceptedAnswer: { "@type": "Answer", text: PRICING_FAQ_ANSWER },
},
]);
});

test("pricing SSR includes the visible price source and matching JSON-LD", async () => {
const { build } = await import("esbuild");
const { mkdtempSync, rmSync } = await import("node:fs");
const { resolve, join } = await import("node:path");
const { pathToFileURL } = await import("node:url");
const { createElement } = await import("react");
const { renderToStaticMarkup } = await import("react-dom/server");
// Isolate auth, checkout and navigation; render the real page and JsonLd component.
const dir = mkdtempSync(resolve("node_modules/.pricing-ssr-"));
try {
const outfile = join(dir, "pricing.mjs");
await build({
entryPoints: [resolve("src/routes/pricing.tsx")],
outfile,
bundle: true,
packages: "external",
platform: "node",
format: "esm",
jsx: "automatic",
alias: { "@": resolve("src") },
plugins: [
{
name: "pricing-ssr-isolation",
setup(builder) {
builder.onResolve(
{
filter:
/^(@tanstack\/react-router|@\/hooks\/|@\/components\/(site\/(Nav|Footer)|PaymentTestModeBanner))/,
},
(args) => ({ path: args.path, namespace: "pricing-fixture" }),
);
builder.onLoad({ filter: /.*/, namespace: "pricing-fixture" }, () => ({
contents: `
export const createFileRoute = () => options => options;
export const useRouter = () => ({ options: {} });
export const Link = () => null;
export const Nav = () => null;
export const Footer = () => null;
export const PaymentTestModeBanner = () => null;
export const useAuth = () => ({ user: null });
export const useSubscription = () => ({ isActive: false });
export const useStripeCheckout = () => ({ isOpen: false });
`,
}));
},
},
],
});
const { Route } = await import(pathToFileURL(outfile).href);
const html = renderToStaticMarkup(createElement(Route.component));
assert.ok(html.includes(PRICE_UPDATE_SENTENCE));
assert.ok(html.includes(PRICING_FAQ_QUESTION));
assert.ok(html.includes(PRICING_FAQ_ANSWER));
const scripts = [
...html.matchAll(/<script[^>]*type="application\/ld\+json"[^>]*>(.*?)<\/script>/gs),
];
assert.equal(scripts.length, 1);
assert.deepEqual(JSON.parse(scripts[0][1]), [PRICING_SOFTWARE_LD, PRICING_FAQ_LD]);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
Loading