From 0cdb08d18049d0490f9c73438dd8211d5676f305 Mon Sep 17 00:00:00 2001 From: "Claude (GTM agent)" Date: Wed, 2 Sep 2026 15:30:35 +0200 Subject: [PATCH 1/4] Add /agent-skills-adoption, a dated statistics page with a source beside every figure Nobody publishes a census of agent skills. This page collects the partial counts that do exist and adds three we can measure ourselves: the AI crawler split in our own server logs, the Search Console beta report on impressions inside AI answers, and one small site's organic impression series. Every figure lives in lib/seo/agent-skills-adoption/datapoints.ts with the source id and the date it was read, so the monthly refresh is an edit to one file. The page declares its own size and carries a limits section for the five questions it cannot answer. --- analytics/posthog/events.ts | 4 + app/agent-skills-adoption/layout.tsx | 11 + app/agent-skills-adoption/opengraph-image.tsx | 10 + app/agent-skills-adoption/page.tsx | 50 ++ app/agent-skills-adoption/twitter-image.tsx | 10 + .../agent-skills-adoption-page.tsx | 391 +++++++++++++++ components/resources/resource-chrome.tsx | 3 + lib/seo/agent-skills-adoption/datapoints.ts | 197 ++++++++ lib/seo/agent-skills-adoption/index.ts | 474 ++++++++++++++++++ lib/seo/agent-skills-adoption/types.ts | 19 + lib/seo/agent-skills/index.ts | 6 + lib/seo/resources.ts | 8 + next.config.ts | 12 + public/llms.txt | 1 + tests/agent-skills-adoption.test.mjs | 390 ++++++++++++++ 15 files changed, 1586 insertions(+) create mode 100644 app/agent-skills-adoption/layout.tsx create mode 100644 app/agent-skills-adoption/opengraph-image.tsx create mode 100644 app/agent-skills-adoption/page.tsx create mode 100644 app/agent-skills-adoption/twitter-image.tsx create mode 100644 components/agent-skills-adoption/agent-skills-adoption-page.tsx create mode 100644 lib/seo/agent-skills-adoption/datapoints.ts create mode 100644 lib/seo/agent-skills-adoption/index.ts create mode 100644 lib/seo/agent-skills-adoption/types.ts create mode 100644 tests/agent-skills-adoption.test.mjs diff --git a/analytics/posthog/events.ts b/analytics/posthog/events.ts index 864faf70..86d8c671 100644 --- a/analytics/posthog/events.ts +++ b/analytics/posthog/events.ts @@ -79,6 +79,10 @@ type NonTeamEventPropertiesMap = { | "cowork_skills_hero" | "cowork_skills_inline" | "cowork_skills_closing" + | "agent_skills_adoption_header" + | "agent_skills_adoption_hero" + | "agent_skills_adoption_inline" + | "agent_skills_adoption_closing" | "manage_ai_skills_header" | "manage_ai_skills_hero" | "manage_ai_skills_inline" diff --git a/app/agent-skills-adoption/layout.tsx b/app/agent-skills-adoption/layout.tsx new file mode 100644 index 00000000..283ab12c --- /dev/null +++ b/app/agent-skills-adoption/layout.tsx @@ -0,0 +1,11 @@ +import { ResourceShell } from "@/components/resources/resource-chrome" + +export default function AgentSkillsAdoptionLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + return ( + + {children} + + ) +} diff --git a/app/agent-skills-adoption/opengraph-image.tsx b/app/agent-skills-adoption/opengraph-image.tsx new file mode 100644 index 00000000..d078d203 --- /dev/null +++ b/app/agent-skills-adoption/opengraph-image.tsx @@ -0,0 +1,10 @@ +import { createSocialImageResponse, OG_SIZE } from "@/lib/og/template" +import { agentSkillsAdoption } from "@/lib/seo/agent-skills-adoption" + +export const alt = agentSkillsAdoption.ogAlt +export const size = OG_SIZE +export const contentType = "image/png" + +export default function OpenGraphImage() { + return createSocialImageResponse(size, agentSkillsAdoption.og) +} diff --git a/app/agent-skills-adoption/page.tsx b/app/agent-skills-adoption/page.tsx new file mode 100644 index 00000000..dd22b1ea --- /dev/null +++ b/app/agent-skills-adoption/page.tsx @@ -0,0 +1,50 @@ +import type { Metadata } from "next" + +import { AgentSkillsAdoptionPage } from "@/components/agent-skills-adoption/agent-skills-adoption-page" +import { markdownTwinAlternates } from "@/lib/markdown/twins" +import { OG_SIZE, TWITTER_SIZE } from "@/lib/og/template" +import { agentSkillsAdoption } from "@/lib/seo/agent-skills-adoption" +import { siteConfig } from "@/lib/site" + +const socialTitle = "Agent skills adoption: the numbers" + +export const metadata: Metadata = { + title: { absolute: agentSkillsAdoption.seoTitle }, + description: agentSkillsAdoption.description, + alternates: markdownTwinAlternates(agentSkillsAdoption.path), + openGraph: { + type: "article", + url: agentSkillsAdoption.path, + title: socialTitle, + description: agentSkillsAdoption.description, + siteName: siteConfig.name, + locale: siteConfig.locale, + publishedTime: agentSkillsAdoption.publishedAt, + modifiedTime: agentSkillsAdoption.modifiedAt, + images: [ + { + url: `${agentSkillsAdoption.path}/opengraph-image`, + width: OG_SIZE.width, + height: OG_SIZE.height, + alt: agentSkillsAdoption.ogAlt, + }, + ], + }, + twitter: { + card: "summary_large_image", + title: socialTitle, + description: agentSkillsAdoption.description, + images: [ + { + url: `${agentSkillsAdoption.path}/twitter-image`, + width: TWITTER_SIZE.width, + height: TWITTER_SIZE.height, + alt: agentSkillsAdoption.ogAlt, + }, + ], + }, +} + +export default function Page() { + return +} diff --git a/app/agent-skills-adoption/twitter-image.tsx b/app/agent-skills-adoption/twitter-image.tsx new file mode 100644 index 00000000..71d03b85 --- /dev/null +++ b/app/agent-skills-adoption/twitter-image.tsx @@ -0,0 +1,10 @@ +import { createSocialImageResponse, TWITTER_SIZE } from "@/lib/og/template" +import { agentSkillsAdoption } from "@/lib/seo/agent-skills-adoption" + +export const alt = agentSkillsAdoption.ogAlt +export const size = TWITTER_SIZE +export const contentType = "image/png" + +export default function TwitterImage() { + return createSocialImageResponse(size, agentSkillsAdoption.og) +} diff --git a/components/agent-skills-adoption/agent-skills-adoption-page.tsx b/components/agent-skills-adoption/agent-skills-adoption-page.tsx new file mode 100644 index 00000000..72205046 --- /dev/null +++ b/components/agent-skills-adoption/agent-skills-adoption-page.tsx @@ -0,0 +1,391 @@ +import Link from "next/link" +import { ExternalLinkIcon } from "lucide-react" + +import { JsonLd } from "@/components/json-ld" +import { + formatArticleDate, + NoteList, + SectionHeading, + SectionSources, + SectionTable, +} from "@/components/resources/article-parts" +import { ResourceBreadcrumb } from "@/components/resources/resource-breadcrumb" +import { ResourceCta } from "@/components/resources/resource-chrome" +import type { + AgentSkillsAdoptionDefinition, + AgentSkillsAdoptionInlineLink, + AgentSkillsAdoptionSource, +} from "@/lib/seo/agent-skills-adoption" +import { buildResourceArticleSchema } from "@/lib/seo/resource-article-schema" +import { resourcePaths } from "@/lib/seo/resources" +import { siteConfig } from "@/lib/site" + +function InlineLink({ link }: { link: AgentSkillsAdoptionInlineLink }) { + return ( +

+ {link.lead}{" "} + + {link.label} + + {link.trail} +

+ ) +} + +export function AgentSkillsAdoptionPage({ + entry, +}: { + entry: AgentSkillsAdoptionDefinition +}) { + const sources: readonly AgentSkillsAdoptionSource[] = entry.sources + + return ( + <> + + +
+ + +
+

+ {entry.eyebrow} +

+

+ {entry.title} +

+

+ Data as of{" "} + +

+
+ {entry.intro.map((paragraph) => ( +

+ {paragraph} +

+ ))} +
+ +
+
+
Publisher
+
+ + {siteConfig.name} + +
+
+
+
Published
+
+ +
+
+
+
Last checked
+
+ +
+
+
+
+ +
+

+ In short +

+

+ {entry.answer} +

+ + +
+ +
+ + +
    + {entry.method.steps.map((step, index) => ( +
  1. + + {String(index + 1).padStart(2, "0")} + +

    + {step} +

    +
  2. + ))} +
+ +
+ +
+ + + + + +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + + +
+ +
+ +
+ {entry.notDocumented.entries.map((item) => ( +
+

+ {item.title} +

+

+ {item.body} +

+
+ ))} +
+ +
+ +
+ + + + +
+ +
+

+ 07 / Questions +

+

+ Frequently asked questions +

+
+ {entry.faq.map((item) => ( +
+
+ {item.question} +
+
+ {item.answer} +
+
+ ))} +
+
+ +
+

+ Sources +

+

+ + Editorial method: + {" "} + every figure on this page names the source it came from and the day + it was read. Public counts come from the first-party page or + repository below. Traffic and search figures come from + instrumentation on this site, described in the method section. Where + a figure cannot be reproduced, the limits section says so instead of + filling the gap. +

+ +
+ +
+
+ + + View all resources + +
+
+ {entry.related.map((link) => ( + + + {link.label} + + + {link.description} + + + ))} +
+
+ +
+

+ The counting stops at your team library. +

+

+ Free forever, MIT licensed, and open source. Create a library, save + the skills your team actually uses, and stop guessing which one a + teammate should open. +

+ +
+
+ + ) +} diff --git a/components/resources/resource-chrome.tsx b/components/resources/resource-chrome.tsx index d3d766d9..78bb587d 100644 --- a/components/resources/resource-chrome.tsx +++ b/components/resources/resource-chrome.tsx @@ -9,6 +9,7 @@ import { FooterNavColumns } from "@/components/footer-nav" import { ThemeToggle } from "@/components/theme-toggle" import { TrackedLink } from "@/components/tracked-link" import { Button } from "@/components/ui/button" +import type { AgentSkillsAdoptionCtaPlacement } from "@/lib/seo/agent-skills-adoption/types" import type { AgentSkillsSupportCtaPlacement } from "@/lib/seo/agent-skills-support/types" import type { AgentSkillsCtaPlacement } from "@/lib/seo/agent-skills/types" import type { AgentsMdVsSkillMdCtaPlacement } from "@/lib/seo/agents-md-vs-skill-md/types" @@ -38,6 +39,7 @@ type ResourceHeaderLocation = | "about_header" | "connect_header" | "developers_header" + | "agent_skills_adoption_header" | "agent_skills_header" | "agent_skills_support_header" | "agents_md_header" @@ -62,6 +64,7 @@ type ResourceHeaderLocation = | "resources_header" | "where_skills_header" type ResourceCtaLocation = + | AgentSkillsAdoptionCtaPlacement | AgentSkillsCtaPlacement | AgentSkillsSupportCtaPlacement | AgentsMdVsSkillMdCtaPlacement diff --git a/lib/seo/agent-skills-adoption/datapoints.ts b/lib/seo/agent-skills-adoption/datapoints.ts new file mode 100644 index 00000000..812ac6a7 --- /dev/null +++ b/lib/seo/agent-skills-adoption/datapoints.ts @@ -0,0 +1,197 @@ +/** + * Every published figure on /agent-skills-adoption, in one place. + * + * The page is dated and refreshed monthly. Keeping the numbers here rather + * than in the copy or the JSX means a refresh is an edit to this file: change + * `value`, change `measuredOn`, and the table, the Markdown twin, the JSON-LD + * dates, and the tests all move with it. Prose in `index.ts` never repeats a + * figure it cannot reach from here. + * + * Rules for adding a row: + * - `sourceId` has to exist in `agentSkillsAdoption.sources`, or the page + * fails its own test. + * - `measuredOn` is the day the figure was read, not the day it was written. + * - a figure that could not be read on refresh day is deleted, not carried + * forward with an old date. + */ + +export interface AdoptionDatapoint { + /** Stable key, used by the tests and by the prose that refers to a row. */ + id: string + /** Row label in the table. */ + label: string + /** The figure, already formatted for display. */ + value: string + /** What the figure counts, and what it does not. */ + detail: string + /** ISO date the figure was read from its source. */ + measuredOn: string + /** Id of the entry in the page's source list. */ + sourceId: string +} + +/** The window the server-log figures cover, in UTC. */ +export const crawlWindow = { + start: "2026-08-26", + end: "2026-09-02", + days: 7, +} as const + +/** Counts that anyone can reproduce from a public first-party source. */ +export const ecosystemDatapoints: readonly AdoptionDatapoint[] = [ + { + id: "clients", + label: "Clients on the showcase", + value: "46", + detail: + "Agent products listed on the Client Showcase published with the Agent Skills specification. Vendor self-listing, not a test of whether each one loads a SKILL.md the same way.", + measuredOn: "2026-09-02", + sourceId: "agentskills-clients", + }, + { + id: "anthropic-skills", + label: "Skills in anthropics/skills", + value: "19", + detail: + "Folders with a SKILL.md under skills/ in the reference repository, read at commit 5304866. A twentieth SKILL.md sits under template/ and is a scaffold, so it is excluded.", + measuredOn: "2026-09-02", + sourceId: "anthropic-skills-repo", + }, + { + id: "anthropic-skills-change", + label: "Change in that repository since August 18", + value: "0", + detail: + "The same 19 folders were present when we counted them on August 18, 2026. The reference repository is not where the format is growing.", + measuredOn: "2026-09-02", + sourceId: "anthropic-skills-repo", + }, + { + id: "skills-sh-total", + label: "Skills listed on the skills.sh leaderboard", + value: "1,297,018", + detail: + "The all-time view of the public directory run by Vercel. It counts skills that have been installed at least once through the npx skills add command, and its own API flags forks and copies separately, so the figure is neither a census of skills that exist nor a count of distinct ones.", + measuredOn: "2026-09-02", + sourceId: "skills-sh", + }, +] + +/** + * Server-side request logs for skillsboard.sh. One event per page-route + * invocation, so the counts are a floor rather than an exact request total. + */ +export const crawlerDatapoints: readonly AdoptionDatapoint[] = [ + { + id: "ai-content-hits", + label: "AI crawler requests for content", + value: "690", + detail: + "Requests to a page path over seven days from 11 named AI crawlers: ChatGPT-User, PerplexityBot, OAI-SearchBot, Amazonbot, meta-externalagent, DuckAssistBot, GoogleOther, Claude-User, ClaudeBot, Bytespider, and GPTBot. Requests to /api/mcp and /.well-known are excluded, because those are protocol handshakes rather than reading.", + measuredOn: "2026-09-02", + sourceId: "posthog-log-drain", + }, + { + id: "search-content-hits", + label: "Googlebot and bingbot requests for content", + value: "117", + detail: + "The same window, the same path filter, the two classic search crawlers: bingbot 86, Googlebot 31.", + measuredOn: "2026-09-02", + sourceId: "posthog-log-drain", + }, + { + id: "ai-vs-search-ratio", + label: "AI crawlers per classic search crawler request", + value: "5.9x", + detail: + "690 divided by 117. On the same site over the week of August 18 to 25, 2026 the ratio was 9.3x, so it moves week to week and a single figure from a single site should not be read as a trend.", + measuredOn: "2026-09-02", + sourceId: "posthog-log-drain", + }, + { + id: "top-ai-crawler", + label: "Busiest single AI crawler", + value: "322", + detail: + "ChatGPT-User, the agent OpenAI sends when a user asks a question that needs a live page. It reads more of this site than Googlebot and bingbot together.", + measuredOn: "2026-09-02", + sourceId: "posthog-log-drain", + }, + { + id: "noise", + label: "Requests excluded as noise", + value: "23,365", + detail: + "Requests in the same window from a Codex MCP client stuck in an authentication retry loop against /api/mcp. It touches no page of content. Counted as AI crawling, it would have turned the ratio above into roughly 200 to 1, describing traffic that read no page.", + measuredOn: "2026-09-02", + sourceId: "posthog-log-drain", + }, +] + +/** + * Search demand as it reaches one small site. Absolute values are tiny by + * design of the site, not of the market: the useful part is the shape. + */ +export const searchDatapoints: readonly AdoptionDatapoint[] = [ + { + id: "impressions-july", + label: "Google impressions, July 2026", + value: "98", + detail: + "Impressions for skillsboard.sh across all queries, with 2 clicks. The site had a handful of pages about the format at that point.", + measuredOn: "2026-09-02", + sourceId: "gsc-search-analytics", + }, + { + id: "impressions-august", + label: "Google impressions, August 2026", + value: "3,919", + detail: + "Same property, same method, with 23 clicks and an average position of 23.4. Roughly 40 times July, on a base small enough that the multiple says more about the base than about the market.", + measuredOn: "2026-09-02", + sourceId: "gsc-search-analytics", + }, + { + id: "ai-impressions", + label: "Impressions inside Google AI features", + value: "226", + detail: + "Impressions in AI Overviews and AI Mode over three months, from the Search Console beta report that covers them. 210 of the 226, or 93 percent, fell in the last ten days of August. The report has no click column and is a beta.", + measuredOn: "2026-08-31", + sourceId: "gsc-ai-report", + }, + { + id: "ai-impressions-top-page", + label: "Single page taking half of those", + value: "114", + detail: + "The /claude-skills explainer, out of 16 pages with any AI-feature impression at all. The next page had 39.", + measuredOn: "2026-08-31", + sourceId: "gsc-ai-report", + }, +] + +/** Table rows for a section, derived so a figure is written once. */ +export function datapointRows( + datapoints: readonly AdoptionDatapoint[], +): readonly { label: string; cells: readonly string[] }[] { + return datapoints.map((datapoint) => ({ + label: datapoint.label, + cells: [datapoint.value, datapoint.detail, datapoint.measuredOn], + })) +} + +export const datapointColumns = [ + "Figure", + "Value", + "What it counts", + "Read on", +] as const + +/** Every datapoint the page publishes, in the order it publishes them. */ +export const allDatapoints: readonly AdoptionDatapoint[] = [ + ...ecosystemDatapoints, + ...crawlerDatapoints, + ...searchDatapoints, +] diff --git a/lib/seo/agent-skills-adoption/index.ts b/lib/seo/agent-skills-adoption/index.ts new file mode 100644 index 00000000..5cf1206b --- /dev/null +++ b/lib/seo/agent-skills-adoption/index.ts @@ -0,0 +1,474 @@ +import type { OgTemplateContent } from "@/lib/og/template" +import { + agentSkillsAdoptionPath, +} from "@/lib/seo/agent-skills-adoption/types" +import { + allDatapoints, + crawlerDatapoints, + crawlWindow, + datapointColumns, + datapointRows, + ecosystemDatapoints, + searchDatapoints, +} from "@/lib/seo/agent-skills-adoption/datapoints" +import { agentSkillsSupportPath } from "@/lib/seo/agent-skills-support/types" +import { agentSkillsPath } from "@/lib/seo/agent-skills/types" +import { bestClaudeSkillsPath } from "@/lib/seo/best-claude-skills/types" +import { claudeSkillsPath } from "@/lib/seo/claude-skills/types" +import { guidePaths, type GuidePath } from "@/lib/seo/guides/types" +import { manageAiSkillsPath } from "@/lib/seo/manage-ai-skills/types" +import { pricingPath } from "@/lib/seo/pricing-schema" +import { whereToFindClaudeSkillsPath } from "@/lib/seo/where-to-find-claude-skills/types" + +export { + agentSkillsAdoptionPath, + type AgentSkillsAdoptionCtaPlacement, + type AgentSkillsAdoptionPath, +} from "@/lib/seo/agent-skills-adoption/types" + +export interface AgentSkillsAdoptionSource { + /** Stable key referenced by the sections and datapoints it backs. */ + id: string + label: string + href: string + note: string +} + +export interface AgentSkillsAdoptionFaqEntry { + question: string + answer: string +} + +export interface AgentSkillsAdoptionRelatedLink { + label: string + href: string + description: string +} + +/** + * One contextual link out of a section, rendered as a sentence. The href union + * is the set of internal destinations this page is allowed to point at, so a + * path that does not exist fails the build instead of shipping as a dead link. + */ +export interface AgentSkillsAdoptionInlineLink { + lead: string + label: string + href: + | GuidePath + | typeof agentSkillsPath + | typeof agentSkillsSupportPath + | typeof bestClaudeSkillsPath + | typeof claudeSkillsPath + | typeof manageAiSkillsPath + | typeof pricingPath + | typeof whereToFindClaudeSkillsPath + trail: string +} + +/** One table of figures, with the prose and the sources behind it. */ +export interface AgentSkillsAdoptionTableSection { + title: string + intro: string + columns: readonly string[] + rows: readonly { + label: string + cells: readonly string[] + }[] + /** Prose that follows the table, one paragraph per entry. */ + notes: readonly string[] + link: AgentSkillsAdoptionInlineLink + sourceIds: readonly string[] +} + +export interface AgentSkillsAdoptionDefinition { + path: typeof agentSkillsAdoptionPath + contentType: "article" + topics: readonly string[] + relatedGuidePaths: readonly ( + | GuidePath + | typeof agentSkillsPath + | typeof agentSkillsSupportPath + | typeof manageAiSkillsPath + | typeof whereToFindClaudeSkillsPath + )[] + eyebrow: string + title: string + /** Full document title, including the brand suffix. */ + seoTitle: string + description: string + /** Scannable positioning above the fold. */ + intro: readonly string[] + /** Answer-first summary, sized for extraction. */ + answer: string + answerNotes: readonly string[] + answerSourceIds: readonly string[] + method: { + title: string + intro: string + body: readonly string[] + steps: readonly string[] + sourceIds: readonly string[] + } + ecosystem: AgentSkillsAdoptionTableSection + crawlers: AgentSkillsAdoptionTableSection + search: AgentSkillsAdoptionTableSection + notDocumented: { + title: string + intro: string + entries: readonly { + title: string + body: string + }[] + sourceIds: readonly string[] + } + reuse: { + title: string + intro: string + body: readonly string[] + link: AgentSkillsAdoptionInlineLink + sourceIds: readonly string[] + } + faq: readonly AgentSkillsAdoptionFaqEntry[] + sources: readonly AgentSkillsAdoptionSource[] + related: readonly AgentSkillsAdoptionRelatedLink[] + og: OgTemplateContent + ogAlt: string + publishedAt: string + modifiedAt: string +} + +export const agentSkillsAdoption: AgentSkillsAdoptionDefinition = { + path: agentSkillsAdoptionPath, + contentType: "article", + topics: [ + "agent skills", + "adoption data", + "AI crawlers", + "search visibility", + "statistics", + ], + relatedGuidePaths: [ + agentSkillsPath, + agentSkillsSupportPath, + whereToFindClaudeSkillsPath, + ], + eyebrow: "Adoption data", + title: "Agent skills adoption: the numbers", + seoTitle: "Agent Skills Adoption: The Numbers | Skills Board", + description: + "Dated figures on agent skills adoption, each with the source beside it: 46 clients on the official showcase, 1,297,018 skills on the skills.sh leaderboard, and server logs where AI crawlers read one site 5.9 times more often than Googlebot and bingbot.", + intro: [ + "Nobody publishes a census of agent skills. There is no registry every skill has to enter, no vendor reporting installs, and no survey of how many teams have written one. What exists instead is a set of partial counts, each measuring something narrow, each reproducible if you say plainly what it measures.", + "This page collects those counts and adds three we can measure ourselves: what AI crawlers do to a small site's server logs, what the Google Search Console beta report says about impressions inside AI answers, and how one site's organic impressions moved between July and August 2026. Every figure carries the source and the day it was read.", + ], + answer: + "As of September 2, 2026, the Agent Skills client showcase lists 46 agent products, the reference repository anthropics/skills holds 19 skills and has not changed since August 18, and the public skills.sh directory lists 1,297,018 skills that have been installed at least once. On skillsboard.sh over the seven days to September 2, named AI crawlers made 690 requests for page content against 117 from Googlebot and bingbot combined, a ratio of 5.9 to 1.", + answerNotes: [ + "Two of those four figures come from public pages anyone can open. The third is a leaderboard whose count depends on install telemetry, and the fourth is one site's server log. None of them is a measure of how many agent skills exist, and this page does not claim one.", + "Skills Board is the agent-native skills registry for teams: a web application where a team keeps, searches, and shares its AI skills. The figures below come from running that site, so the search and crawler numbers describe a small site with a few dozen pages, not the market.", + ], + answerSourceIds: [ + "agentskills-clients", + "anthropic-skills-repo", + "skills-sh", + "posthog-log-drain", + ], + method: { + title: "How each figure was produced", + intro: + "Four methods, in descending order of how easily you can reproduce them.", + body: [ + "Anything on this page that describes the wider ecosystem comes from a public page or a public repository, so a reader can open the same source and get the same number on the same day. Anything that describes traffic or search comes from instrumentation on skillsboard.sh, which nobody else can open, so the method is written out below in enough detail to judge it.", + "The page is dated at the top and refreshed monthly. A figure that cannot be read on refresh day is removed rather than carried forward, and every figure lives in a single typed data module in the open-source repository, so a reader can check what changed between two refreshes by reading one file's history.", + ], + steps: [ + "Showcase and repository counts: fetch the published page or clone the repository, count, and record the commit or the fetch date. The anthropics/skills count is folders containing a SKILL.md under skills/, read at commit 5304866.", + "Directory count: read the total the skills.sh all-time leaderboard prints beside its own tab label. The number is theirs, the reading is ours, and its limits are stated in the section below.", + "Crawler counts: server-side request logs from skillsboard.sh, delivered from Vercel to PostHog as one event per page-route invocation, classified by user agent string. Requests to /api/mcp and to /.well-known paths are excluded because they are protocol handshakes. Known monitoring agents and a Codex client stuck in an authentication retry loop are excluded by name.", + "Search counts: the Search Console Search Analytics API for the property, queried per calendar month with dataState set to all so provisional days are included. The AI-feature impressions come from the separate Generative AI performance report, which has no API and was read in the interface on August 31, 2026.", + ], + sourceIds: [ + "posthog-log-drain", + "vercel-log-drains", + "gsc-search-analytics", + "gsc-ai-report", + "skillsboard-repo", + ], + }, + ecosystem: { + title: "What the public sources count", + intro: + "Four figures from three public sources, and none of them answers how many agent skills exist.", + columns: [...datapointColumns], + rows: datapointRows(ecosystemDatapoints), + notes: [ + "The showcase count is the most stable of the three sources and the least informative about usage: a vendor appears once its own documentation says it reads SKILL.md, and it stays there whether one user or a million use the feature. It is a supply-side number about clients, not a demand-side number about skills.", + "The leaderboard count is the opposite. It moves with real installs, which makes it the closest public proxy for how many skills are in circulation, and it is also the figure most likely to be misread. It counts a skill from the first time anyone installs it through the command line tool, so a skill that a team keeps private or shares by copying a folder never appears at all, and a fork of a popular skill appears as its own entry.", + "The reference repository is worth watching precisely because it barely moves. Anthropic publishes 19 skills there and published the same 19 two weeks earlier. Growth in this format is happening in other people's repositories, which is why a directory built on install telemetry finds more than a million entries while the first-party catalog finds nineteen.", + ], + link: { + lead: "The showcase entries are unpacked client by client in", + label: "which AI clients read SKILL.md", + href: agentSkillsSupportPath, + trail: ", with the directories each vendor documents.", + }, + sourceIds: [ + "agentskills-clients", + "agentskills-spec", + "anthropic-skills-repo", + "skills-sh", + "skills-sh-api", + ], + }, + crawlers: { + title: "Who reads a site about agent skills", + intro: `Server-side request logs for skillsboard.sh over the seven days to September 2, 2026 (${crawlWindow.start} to ${crawlWindow.end}, UTC), classified by user agent.`, + columns: [...datapointColumns], + rows: datapointRows(crawlerDatapoints), + notes: [ + "The split is the point. On a site whose subject is agent skills, the crawlers that feed AI answers ask for pages roughly six times as often as the two crawlers that feed classic search results, and one of them, the agent OpenAI sends when a user asks a live question, out-reads Googlebot and bingbot together on its own.", + "The same measurement a week earlier gave 9.3 to 1. Both numbers are real and they disagree, which is what a single week of a single small site buys you. Publish the window with the ratio or the ratio means nothing.", + "The excluded row matters more than the included ones. A single misconfigured client generated 23,365 requests in the same seven days, all of them authentication retries against the MCP endpoint and none of them a page. Server logs are full of traffic that looks like interest and is not, and a crawler statistic that does not say what it threw away is not a statistic.", + "The pages these crawlers ask for are the explainers rather than the product pages: the home page took 177 requests, the Codex explainer 74, the resources hub 54, and the Claude skills explainer 42.", + ], + link: { + lead: "The pages they read most are the format explainers, starting with", + label: "the Agent Skills standard itself", + href: agentSkillsPath, + trail: ".", + }, + sourceIds: ["posthog-log-drain", "vercel-log-drains"], + }, + search: { + title: "What search demand looks like from one small site", + intro: + "Google Search Console for skillsboard.sh. Small absolute numbers, stated as such, with the shape left visible.", + columns: [...datapointColumns], + rows: datapointRows(searchDatapoints), + notes: [ + "A site that goes from 98 impressions to 3,919 in a month has multiplied by 40 and is still a small site. The multiple is worth publishing because the denominator is published beside it; quoted alone it would be marketing.", + "The AI-feature figure is the one with no public equivalent anywhere else, and it is also the weakest. Google's report covers AI Overviews and AI Mode, gives impressions and nothing else, and is in beta. It cannot tell you whether a single person clicked through, and the concentration in the last ten days of August could be a change in how the site is indexed or a change in how the report samples.", + "One page took half of the AI-feature impressions. That is what a long tail looks like before it is long: one explainer page that answers a head question, and fifteen others sharing the remainder.", + ], + link: { + lead: "The page taking half of those impressions explains", + label: "what a Claude skill is and how to write one", + href: claudeSkillsPath, + trail: ".", + }, + sourceIds: ["gsc-search-analytics", "gsc-ai-report"], + }, + notDocumented: { + title: "What none of this measures", + intro: + "Five questions a reader might expect this page to answer, and the reason it does not.", + entries: [ + { + title: "How many agent skills exist", + body: "No source counts them. The skills.sh leaderboard counts skills installed at least once through its own command line tool, which excludes every skill kept inside a company, every skill shared as a copied folder, and every skill installed by pointing an agent at a repository directly. It also counts forks as separate entries. Treat 1,297,018 as a floor on installed-and-public skills and as no kind of answer to how many have been written.", + }, + { + title: "How many people use agent skills", + body: "No vendor publishes it. Anthropic, OpenAI, Cursor, and the rest document that their products read SKILL.md and report nothing about how often. The client showcase counts products, not seats. Any figure you see for users of agent skills is currently either a vendor's private number or somebody's estimate, and this page has neither.", + }, + { + title: "Whether AI crawlers turn into readers", + body: "The crawler figures count requests to a server, not people. A request from ChatGPT-User means somebody asked a question that caused a fetch, which is closer to a reader than a Googlebot request is, but the log cannot say whether the answer was used, cited, or seen. The Search Console AI report is the nearest thing to a citation signal and it has no click column, so the two halves of the question cannot be joined with anything we can measure.", + }, + { + title: "Whether any of this generalizes", + body: "It should not be assumed to. The traffic and search figures come from one site of a few dozen pages, published in 2026, on a subject that AI crawlers have an obvious reason to fetch. A site about something else, or a larger site, would very likely see a different ratio. The reason to publish these anyway is that almost nobody publishes server-side crawler splits at all, and a small honest number beats an absent one.", + }, + { + title: "How many teams share skills with each other", + body: "This is the question our own product exists for and the one we can say least about. We know what happens inside libraries on skillsboard.sh and will not publish user data to make a statistic. Nothing public measures it either: no vendor reports team-level skill sharing, and a repository of skills in a private organization is invisible to every counting method on this page.", + }, + ], + sourceIds: [ + "skills-sh", + "skills-sh-api", + "agentskills-clients", + "posthog-log-drain", + "gsc-ai-report", + ], + }, + reuse: { + title: "Citing these figures", + intro: + "The numbers are free to reuse. The conditions are the ones that keep them useful.", + body: [ + "Quote a figure with the date beside it and name what it counts. A crawler ratio without its window, or the leaderboard total presented as the number of agent skills that exist, is worse than no figure, and it is the misreading this page is written to prevent.", + "Link to this page rather than copying the table, because the table changes. It is refreshed monthly, figures that stop being readable are removed rather than aged, and the data module behind it is in the open-source repository with its full history.", + "If you need something this page does not have, ask. We hold server-side crawler logs for skillsboard.sh with no retention limit and can usually answer a narrow question about which agents read what.", + ], + link: { + lead: "Skills Board is free forever and open source, and its terms are on", + label: "the pricing page", + href: pricingPath, + trail: ".", + }, + sourceIds: ["skillsboard-repo", "posthog-log-drain"], + }, + faq: [ + { + question: "How many agent skills are there?", + answer: + "No source counts them. The nearest public figure is the skills.sh leaderboard, which listed 1,297,018 skills on September 2, 2026, but it counts only skills installed at least once through its own command line tool and counts forks separately. Private and hand-copied skills are invisible to it.", + }, + { + question: "How many products support agent skills?", + answer: + "The Client Showcase published alongside the Agent Skills specification listed 46 agent products on September 2, 2026. Vendors list themselves there, so it reflects declared support rather than tested behavior, and it says nothing about how many people use the feature in each product.", + }, + { + question: "How many skills does Anthropic publish?", + answer: + "Nineteen, in the anthropics/skills repository, counted as folders containing a SKILL.md under the skills directory at commit 5304866 on September 2, 2026. A twentieth SKILL.md is a template scaffold. The same 19 were there on August 18, so the reference catalog is stable rather than growing.", + }, + { + question: "Do AI crawlers read more than Google on a skills site?", + answer: + "On skillsboard.sh over the seven days to September 2, 2026, eleven named AI crawlers made 690 requests for page content against 117 from Googlebot and bingbot combined. That is 5.9 to one. The same measurement a week earlier gave 9.3 to one, so treat it as one site's week rather than a rate.", + }, + { + question: "Which AI crawler reads the most?", + answer: + "ChatGPT-User, the agent OpenAI sends when a person asks something that needs a live page, made 322 of the 690 AI crawler requests in the seven days to September 2, 2026. PerplexityBot followed with 113, then OAI-SearchBot with 84 and Amazonbot with 73. Claude-User and GPTBot barely appear.", + }, + { + question: "Can I cite these agent skills statistics?", + answer: + "Yes, with attribution to Skills Board and the date beside the figure. Every number here names its source and the day it was read. Please link to this page instead of copying the table, because it is refreshed monthly and figures that stop being readable are removed rather than aged.", + }, + ], + sources: [ + { + id: "agentskills-clients", + label: "Agent Skills Client Showcase", + href: "https://agentskills.io/clients", + note: "The list of agent products that support the format, published with the specification. Counted on September 2, 2026.", + }, + { + id: "agentskills-spec", + label: "Agent Skills specification", + href: "https://agentskills.io/specification", + note: "The format the counted clients implement, and what a skill has to contain to be one.", + }, + { + id: "anthropic-skills-repo", + label: "anthropics/skills on GitHub", + href: "https://github.com/anthropics/skills", + note: "Anthropic's reference repository. Counted at commit 5304866 on September 2, 2026.", + }, + { + id: "skills-sh", + label: "skills.sh leaderboard", + href: "https://www.skills.sh/", + note: "The public directory run by Vercel. The all-time total printed beside its own tab label, read on September 2, 2026.", + }, + { + id: "skills-sh-api", + label: "skills.sh API reference", + href: "https://www.skills.sh/docs/api", + note: "States that the leaderboard counts installs and flags forks and copies with an isDuplicate field, which is why the total is a floor rather than a census.", + }, + { + id: "posthog-log-drain", + label: "PostHog Vercel log drain source", + href: "https://posthog.com/docs/cdp/source_webhooks/source-vercel-log-drain", + note: "How the server-side request logs behind the crawler figures are collected. One event per page-route invocation, so the counts are a floor.", + }, + { + id: "vercel-log-drains", + label: "Vercel log drains", + href: "https://vercel.com/docs/log-drains", + note: "The delivery mechanism on the other side, and the reason these requests are visible at all when client-side analytics never sees a crawler.", + }, + { + id: "gsc-search-analytics", + label: "Search Console Search Analytics API", + href: "https://developers.google.com/webmaster-tools/v1/searchanalytics/query", + note: "The method behind the monthly impression figures, queried with dataState set to all so provisional days are included.", + }, + { + id: "gsc-ai-report", + label: "Search Console Generative AI performance report", + href: "https://support.google.com/webmasters/answer/16984139", + note: "Google's own description of the beta report: impressions in AI Overviews and AI Mode, no clicks, no queries, no API. Read in the interface on August 31, 2026.", + }, + { + id: "skillsboard-repo", + label: "Skills Board source on GitHub", + href: "https://github.com/TommyBez/skillsboard", + note: "The open-source application these figures come from, including the data module that holds every number on this page.", + }, + ], + related: [ + { + label: "Agent Skills: the open standard", + href: agentSkillsPath, + description: + "What the specification defines, which agents implement it, and where each one looks on disk.", + }, + { + label: "Agent Skills support: which AI clients read SKILL.md", + href: agentSkillsSupportPath, + description: + "The showcase entries as a compatibility matrix, with the directories each vendor documents.", + }, + { + label: "Where to find Claude skills", + href: whereToFindClaudeSkillsPath, + description: + "The catalogs and repositories the counts on this page draw from, and what each one screens.", + }, + { + label: "Manage AI skills across your organization", + href: manageAiSkillsPath, + description: + "What each vendor's distribution mechanism covers, and the selection layer none of them records.", + }, + { + label: "Best Claude skills: a curated register", + href: bestClaudeSkillsPath, + description: + "A worked example of counting carefully: entries read one by one against stated criteria.", + }, + { + label: "How to write a SKILL.md file", + href: guidePaths.writeSkillMd, + description: + "The frontmatter fields and description rules behind every skill any of these sources counts.", + }, + ], + og: { + eyebrow: "Adoption data", + title: [ + { text: "Agent skills adoption:" }, + { text: "the numbers", accent: true }, + ], + description: + "46 clients on the official showcase, 1,297,018 skills on the public leaderboard, and AI crawlers reading one site 5.9x more than Google and Bing.", + contextLabel: "skillsboard.sh", + chips: ["Sourced", "Dated", "Refreshed monthly"], + footnote: "Data as of September 2, 2026", + variant: "ink", + }, + ogAlt: + "Agent skills adoption: the numbers, a dated statistics page from Skills Board with the source beside every figure.", + publishedAt: "2026-09-02", + modifiedAt: "2026-09-02", +} + +/** Guard: every datapoint has to point at a source the page lists. */ +const sourceIds = new Set(agentSkillsAdoption.sources.map((source) => source.id)) + +for (const datapoint of allDatapoints) { + if (!sourceIds.has(datapoint.sourceId)) { + throw new Error( + `Datapoint ${datapoint.id} cites an unknown source: ${datapoint.sourceId}`, + ) + } +} + +export { allDatapoints, crawlWindow } diff --git a/lib/seo/agent-skills-adoption/types.ts b/lib/seo/agent-skills-adoption/types.ts new file mode 100644 index 00000000..292a9fc3 --- /dev/null +++ b/lib/seo/agent-skills-adoption/types.ts @@ -0,0 +1,19 @@ +/** + * Top-level route, for the same reason /manage-ai-skills and + * /claude-code-for-teams are: the page answers a head query itself rather than + * a task-shaped variation of it, so it does not sit under /guides. The path + * does not end in `-skills`, so it carries its own Markdown negotiation + * rewrite in `next.config.ts` the way /skill-examples does. + */ +export const agentSkillsAdoptionPath = "/agent-skills-adoption" as const + +export type AgentSkillsAdoptionPath = typeof agentSkillsAdoptionPath + +/** + * CTA placements on the statistics page, kept in sync with the + * landing_cta_clicked union. The sticky shell CTA is + * agent_skills_adoption_header, following the naming every other marketing + * chrome uses for its nav slot. + */ +export type AgentSkillsAdoptionCtaPlacement = + `agent_skills_adoption_${"hero" | "inline" | "closing"}` diff --git a/lib/seo/agent-skills/index.ts b/lib/seo/agent-skills/index.ts index ce2d9a20..fb0f70b8 100644 --- a/lib/seo/agent-skills/index.ts +++ b/lib/seo/agent-skills/index.ts @@ -705,6 +705,12 @@ export const agentSkills: AgentSkillsDefinition = { }, ], related: [ + { + label: "Agent skills adoption: the numbers", + href: "/agent-skills-adoption", + description: + "Dated counts of clients, published skills, and crawler traffic, with the source beside every figure.", + }, { label: "Skill examples: real SKILL.md files, explained", href: "/skill-examples", diff --git a/lib/seo/resources.ts b/lib/seo/resources.ts index a9c0e840..8fc73728 100644 --- a/lib/seo/resources.ts +++ b/lib/seo/resources.ts @@ -1,3 +1,8 @@ +import { agentSkillsAdoption } from "@/lib/seo/agent-skills-adoption" +import { + agentSkillsAdoptionPath, + type AgentSkillsAdoptionPath, +} from "@/lib/seo/agent-skills-adoption/types" import { agentSkillsSupport } from "@/lib/seo/agent-skills-support" import { agentSkillsSupportPath, @@ -76,6 +81,7 @@ export type ResourceContentType = "guide" | "article" /** Every path the resource hub, related links, and sitemap can address. */ export type ResourcePath = | GuidePath + | AgentSkillsAdoptionPath | AgentSkillsPath | AgentSkillsSupportPath | AgentsMdVsSkillMdPath @@ -108,6 +114,7 @@ export interface ResourceIndexEntry { export const resourceEntries = [ ...guides, agentSkills, + agentSkillsAdoption, agentSkillsSupport, anthropicSkills, bestClaudeSkills, @@ -170,6 +177,7 @@ const resourceClusterDefinitions = [ paths: [ claudeCodeForTeamsPath, manageAiSkillsPath, + agentSkillsAdoptionPath, whereToFindClaudeSkillsPath, bestClaudeSkillsPath, guidePaths.chooseFirstTeamSkill, diff --git a/next.config.ts b/next.config.ts index 140b2986..2ca03e72 100644 --- a/next.config.ts +++ b/next.config.ts @@ -160,6 +160,11 @@ const nextConfig = { destination: "/agent-skills-support", permanent: true, }, + { + source: "/agent-skills-adoption/", + destination: "/agent-skills-adoption", + permanent: true, + }, { source: "/claude-skills/", destination: "/claude-skills", @@ -294,6 +299,13 @@ const nextConfig = { has: [MARKDOWN_ACCEPT], destination: "/api/markdown?path=/agent-skills-support", }, + // `/agent-skills-adoption` does not end in `-skills` either, so the + // shared rule above never reaches it. + { + source: "/agent-skills-adoption", + has: [MARKDOWN_ACCEPT], + destination: "/api/markdown?path=/agent-skills-adoption", + }, { source: "/developers", has: [MARKDOWN_ACCEPT], diff --git a/public/llms.txt b/public/llms.txt index 8fa71301..a9443c90 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -50,6 +50,7 @@ Markdown versions: the home page, the resources, comparisons, and alternatives h - [Best Claude skills: a curated register](https://www.skillsboard.sh/best-claude-skills): Twenty-seven register entries covering thirty-five Claude skills that cleared seven stated criteria, grouped by job, each read from its own SKILL.md with publisher, license, and where it runs, plus the nine popular candidates that were dropped and why - [Anthropic skills: the first-party catalog](https://www.skillsboard.sh/anthropic-skills): Every skill Anthropic publishes itself, in three sets: the four pre-built document skills, the nineteen folders in anthropics/skills, and the thirteen bundled with Claude Code, with what each does, where it loads, and how it is licensed - [Agent Skills: the open standard](https://www.skillsboard.sh/agent-skills): What the Agent Skills specification defines, which agents implement it and where each one looks on disk, what travels between them, and where to read real examples +- [Agent skills adoption: the numbers](https://www.skillsboard.sh/agent-skills-adoption): Dated adoption figures with the source beside each one, covering the 46 clients on the official showcase, the 19 skills in anthropics/skills, the 1,297,018 entries on the skills.sh leaderboard, the server-side split between AI crawlers and classic search crawlers on this site, and what none of those figures measures - [Skill examples: real SKILL.md files](https://www.skillsboard.sh/skill-examples): Eight example skills from anthropics/skills read file by file, the pattern each one demonstrates, verbatim excerpts with their source, what all nineteen examples declare in frontmatter, and the six places the examples diverge from the specification - [Claude skills](https://www.skillsboard.sh/claude-skills): What a Claude Skill is, the SKILL.md format, where skills run, and how to install, write, and share one - [Codex skills](https://www.skillsboard.sh/codex-skills): What a Codex skill is, the .agents/skills locations Codex scans, what transfers from a Claude skill, and how to add one diff --git a/tests/agent-skills-adoption.test.mjs b/tests/agent-skills-adoption.test.mjs new file mode 100644 index 00000000..a574d721 --- /dev/null +++ b/tests/agent-skills-adoption.test.mjs @@ -0,0 +1,390 @@ +import assert from "node:assert/strict" +import { access, readFile } from "node:fs/promises" +import { test } from "node:test" + +import "./helpers/register-app-aliases.mjs" + +const { agentSkillsAdoption, agentSkillsAdoptionPath } = await import( + "../lib/seo/agent-skills-adoption/index.ts" +) +const { allDatapoints, crawlWindow, datapointColumns } = await import( + "../lib/seo/agent-skills-adoption/datapoints.ts" +) +const { agentSkills } = await import("../lib/seo/agent-skills/index.ts") +const { resourceClusters, resourceEntries } = await import( + "../lib/seo/resources.ts" +) +const { markdownTwinAlternates, renderMarkdownTwin } = await import( + "../lib/markdown/twins.ts" +) +const { buildResourceArticleSchema } = await import( + "../lib/seo/resource-article-schema.ts" +) +const { default: sitemap } = await import("../app/sitemap.ts") +const { default: nextConfig } = await import("../next.config.ts") + +const entry = agentSkillsAdoption +const canonical = `https://www.skillsboard.sh${agentSkillsAdoptionPath}` +const markdown = renderMarkdownTwin(agentSkillsAdoptionPath) ?? "" + +/** Em dash and en dash are not allowed anywhere in published copy. */ +const dashPattern = /[\u2013\u2014]/ + +/** The three figure tables, in the order the page renders them. */ +const tables = [entry.ecosystem, entry.crawlers, entry.search] + +async function exists(relative) { + try { + await access(new URL(relative, import.meta.url)) + return true + } catch { + return false + } +} + +test("the statistics page is registered everywhere a resource is addressed", () => { + assert.equal(entry.path, "/agent-skills-adoption") + assert.ok( + resourceEntries.some((candidate) => candidate.path === entry.path), + "missing from the resource registry", + ) + assert.ok( + resourceClusters.some((cluster) => + cluster.entries.some((candidate) => candidate.path === entry.path), + ), + "missing from every topic cluster", + ) + assert.ok( + sitemap().some((candidate) => candidate.url === canonical), + "missing from the sitemap", + ) +}) + +test("the route renders the page and its social images", async () => { + for (const file of [ + "page.tsx", + "layout.tsx", + "opengraph-image.tsx", + "twitter-image.tsx", + ]) { + assert.ok( + await exists(`../app${entry.path}/${file}`), + `app${entry.path}/${file} does not exist`, + ) + } +}) + +test("the page mounts its own four CTA locations", async () => { + const layout = await readFile( + new URL(`../app${entry.path}/layout.tsx`, import.meta.url), + "utf8", + ) + assert.ok( + layout.includes("agent_skills_adoption_header"), + "the shell reports another page's location", + ) + + const page = await readFile( + new URL( + "../components/agent-skills-adoption/agent-skills-adoption-page.tsx", + import.meta.url, + ), + "utf8", + ) + for (const suffix of ["hero", "inline", "closing"]) { + assert.ok( + page.includes(`agent_skills_adoption_${suffix}`), + `the page never renders the agent_skills_adoption_${suffix} CTA`, + ) + } + + const events = await readFile( + new URL("../analytics/posthog/events.ts", import.meta.url), + "utf8", + ) + for (const suffix of ["header", "hero", "inline", "closing"]) { + assert.ok( + events.includes(`"agent_skills_adoption_${suffix}"`), + `landing_cta_clicked cannot report agent_skills_adoption_${suffix}`, + ) + } +}) + +test("the page is listed in the static llms.txt", async () => { + const llms = await readFile( + new URL("../public/llms.txt", import.meta.url), + "utf8", + ) + assert.ok(llms.includes(`${canonical})`), "missing from public/llms.txt") + + // The sibling articles pin the single `Last reviewed` line to the date of + // the last full re-review of every pinned page. This page re-read its own + // ten sources only, so the shared line stays where it is and the page + // carries its own date instead, in `modifiedAt` and on the page itself. + assert.ok( + llms.includes("Last reviewed: 2026-08-19"), + "the shared review date moved without a full re-review", + ) + assert.ok( + entry.modifiedAt >= "2026-08-19", + "the entry predates the last full llms.txt review", + ) +}) + +test("the canonical URL is reachable with and without a trailing slash", async () => { + const { redirects, rewrites } = nextConfig + const redirectRules = await redirects() + const redirect = redirectRules.find( + (rule) => + rule.source === `${entry.path}/` && rule.destination === entry.path, + ) + assert.ok(redirect, "the trailing-slash spelling has no redirect") + assert.equal(redirect.permanent, true, "the redirect is not permanent") + + // The path does not end in `-skills`, so the shared Accept rewrite does not + // reach it and it carries a rule of its own. + assert.doesNotMatch(entry.path.slice(1), /^[^/]*-skills$/) + const { beforeFiles } = await rewrites() + const negotiated = beforeFiles.find((rule) => rule.source === entry.path) + assert.ok(negotiated, "the Markdown Accept rewrite is missing") + assert.equal(negotiated.destination, `/api/markdown?path=${entry.path}`) +}) + +test("the Markdown twin carries every section, the tables, and the FAQ", () => { + assert.ok(markdown.startsWith(`# ${entry.title}\n`)) + assert.ok(markdown.includes(`Canonical URL: ${canonical}`)) + assert.deepEqual(markdownTwinAlternates(entry.path), { + canonical: entry.path, + types: { "text/markdown": `${entry.path}.md` }, + }) + + for (const title of [ + entry.method.title, + ...tables.map((section) => section.title), + entry.notDocumented.title, + entry.reuse.title, + ]) { + assert.ok( + markdown.includes(`## ${title}`), + `missing section heading: ${title}`, + ) + } + + for (const section of tables) { + const header = `| ${section.columns.join(" | ")} |` + assert.ok(markdown.includes(header), `${section.title} lost its header row`) + } + + for (const item of entry.faq) { + assert.ok(markdown.includes(`### ${item.question}`), item.question) + assert.ok(markdown.includes(item.answer), "missing FAQ answer") + } +}) + +test("every published figure lives in the data module, dated and sourced", () => { + assert.ok(allDatapoints.length >= 12, "fewer than twelve datapoints") + + const known = new Set(entry.sources.map((source) => source.id)) + const ids = new Set() + + for (const datapoint of allDatapoints) { + assert.ok(!ids.has(datapoint.id), `duplicate datapoint id: ${datapoint.id}`) + ids.add(datapoint.id) + assert.match( + datapoint.measuredOn, + /^\d{4}-\d{2}-\d{2}$/, + `${datapoint.id} has no reading date`, + ) + assert.ok( + datapoint.measuredOn <= entry.modifiedAt, + `${datapoint.id} claims to have been read after the page was checked`, + ) + assert.ok( + known.has(datapoint.sourceId), + `${datapoint.id} cites an unknown source: ${datapoint.sourceId}`, + ) + assert.ok( + datapoint.detail.length > 80, + `${datapoint.id} does not say what it counts`, + ) + } +}) + +test("every table renders the data module rather than its own numbers", () => { + const columns = [...datapointColumns] + const rowCount = tables.reduce( + (total, section) => total + section.rows.length, + 0, + ) + assert.equal(rowCount, allDatapoints.length, "a datapoint renders nowhere") + + for (const section of tables) { + assert.deepEqual(section.columns, columns) + for (const row of section.rows) { + assert.equal(row.cells.length, 3, `${row.label} has the wrong cell count`) + const datapoint = allDatapoints.find( + (candidate) => candidate.label === row.label, + ) + assert.ok(datapoint, `${row.label} is not backed by a datapoint`) + assert.equal(row.cells[0], datapoint.value) + assert.equal(row.cells[2], datapoint.measuredOn) + } + } +}) + +test("the page states the window behind the crawler figures", () => { + assert.equal(crawlWindow.days, 7) + assert.ok(entry.crawlers.intro.includes(crawlWindow.end)) + const crawlers = JSON.stringify(entry.crawlers) + assert.ok( + crawlers.includes("9.3x"), + "the page hides that the same ratio read differently a week earlier", + ) + assert.ok( + crawlers.includes("23,365"), + "the page does not declare the traffic it excluded", + ) +}) + +test("the page declares its own size rather than implying a market", () => { + const copy = JSON.stringify(entry) + assert.ok(copy.includes("one small site"), "the page hides its own size") + assert.ok( + entry.notDocumented.entries.length >= 5, + "fewer than five declared limits", + ) + for (const item of entry.notDocumented.entries) { + assert.ok(item.body.length > 200, `${item.title} gives no detail`) + } + + const gaps = JSON.stringify(entry.notDocumented) + assert.ok( + gaps.includes("No source counts them"), + "the page does not say that no census of skills exists", + ) + assert.ok( + gaps.includes("It should not be assumed to"), + "the page does not rule out generalizing from one site", + ) +}) + +test("the FAQ is self-contained, extractable, and cites its own dates", () => { + assert.ok(entry.faq.length >= 5) + + for (const item of entry.faq) { + const words = item.answer.trim().split(/\s+/).filter(Boolean).length + assert.ok( + words >= 40 && words <= 60, + `"${item.question}" answer is ${words} words`, + ) + // An answer that quotes a figure has to date it. An answer with no + // figure in it has nothing to date. + if (/\d/.test(item.answer)) { + assert.match( + item.answer, + /2026/, + `"${item.question}" gives a figure without a date`, + ) + } + } + + for (const term of [ + "how many agent skills", + "support agent skills", + "cite these agent skills statistics", + ]) { + assert.ok( + entry.faq.some((item) => item.question.toLowerCase().includes(term)), + `no FAQ entry addresses ${term}`, + ) + } +}) + +test("no copy on the page uses an em dash or an en dash", () => { + assert.doesNotMatch(JSON.stringify(entry), dashPattern) + assert.doesNotMatch(markdown, dashPattern) +}) + +test("the product copy follows the positioning rules", () => { + const copy = JSON.stringify(entry) + assert.doesNotMatch(copy, /shared library/i) + assert.doesNotMatch(copy, /recommend/i) + assert.ok( + copy.includes("agent-native skills registry for teams"), + "the page does not use the agreed product description", + ) +}) + +test("the page links out, and a sibling page links in", () => { + const outbound = new Set( + [ + ...tables.map((section) => section.link.href), + entry.reuse.link.href, + ...entry.related.map((link) => link.href), + ].filter((href) => href.startsWith("/")), + ) + assert.ok(outbound.size >= 6, "fewer than six internal outbound links") + + for (const destination of [ + "/agent-skills", + "/agent-skills-support", + "/claude-skills", + "/pricing", + ]) { + assert.ok(outbound.has(destination), `no link to ${destination}`) + } + + assert.ok( + agentSkills.related.some((link) => link.href === entry.path), + `${agentSkills.path} does not link to ${entry.path}`, + ) +}) + +test("the schema carries TechArticle, FAQPage, and a breadcrumb", () => { + const graph = buildResourceArticleSchema(entry)["@graph"] + const byType = (type) => graph.find((node) => node["@type"] === type) + + const article = byType("TechArticle") + assert.ok(article, "missing TechArticle") + assert.equal(article.headline, entry.title) + assert.equal(article.url, canonical) + assert.equal(article.dateModified, entry.modifiedAt) + assert.deepEqual( + article.citation, + entry.sources.map((source) => source.href), + ) + + const faq = byType("FAQPage") + assert.ok(faq, "missing FAQPage") + assert.equal(faq.mainEntity.length, entry.faq.length) + assert.equal(faq.mainEntity[0].acceptedAnswer.text, entry.faq[0].answer) + + const breadcrumbs = byType("BreadcrumbList") + assert.ok(breadcrumbs, "missing BreadcrumbList") + assert.equal(breadcrumbs.itemListElement.at(-1).item, canonical) +}) + +test("every section cites a source that the page actually lists", () => { + const known = new Set(entry.sources.map((source) => source.id)) + assert.ok(entry.sources.length >= 9, "too few sources") + + const cited = [ + entry.answerSourceIds, + entry.method.sourceIds, + ...tables.map((section) => section.sourceIds), + entry.notDocumented.sourceIds, + entry.reuse.sourceIds, + ] + + for (const ids of cited) { + assert.ok(ids.length > 0, "a section cites nothing") + for (const id of ids) { + assert.ok(known.has(id), `unknown source id: ${id}`) + } + } + + const used = new Set(cited.flat()) + for (const source of entry.sources) { + assert.ok(used.has(source.id), `${source.id} is listed but never cited`) + } +}) From e263deb1d4aba2542f932a9cab2ef2601f07320c Mon Sep 17 00:00:00 2001 From: "Claude (GTM agent)" Date: Wed, 2 Sep 2026 15:38:45 +0200 Subject: [PATCH 2/4] Correct the skills.sh figure: 1,297,018 is not a count of skills --- lib/seo/agent-skills-adoption/datapoints.ts | 13 +++++++++-- lib/seo/agent-skills-adoption/index.ts | 24 ++++++++++----------- public/llms.txt | 2 +- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/seo/agent-skills-adoption/datapoints.ts b/lib/seo/agent-skills-adoption/datapoints.ts index 812ac6a7..77be4b39 100644 --- a/lib/seo/agent-skills-adoption/datapoints.ts +++ b/lib/seo/agent-skills-adoption/datapoints.ts @@ -66,12 +66,21 @@ export const ecosystemDatapoints: readonly AdoptionDatapoint[] = [ measuredOn: "2026-09-02", sourceId: "anthropic-skills-repo", }, + { + id: "skills-sh-skills", + label: "Skills on the skills.sh all-time leaderboard", + value: "9,704", + detail: + "The skill count the all-time view of the public directory run by Vercel reports in its own page data, read on September 2, 2026. A skill enters it once somebody installs it through the npx skills add command, forks and copies are flagged rather than merged, and the trending view of the same site reported 9,922 the same day, so the figure moves with the view.", + measuredOn: "2026-09-02", + sourceId: "skills-sh", + }, { id: "skills-sh-total", - label: "Skills listed on the skills.sh leaderboard", + label: "Unlabelled total printed beside that leaderboard tab", value: "1,297,018", detail: - "The all-time view of the public directory run by Vercel. It counts skills that have been installed at least once through the npx skills add command, and its own API flags forks and copies separately, so the figure is neither a census of skills that exist nor a count of distinct ones.", + "The number skills.sh prints as All Time (1,297,018) beside its own tab, with no unit given on the page or in its API reference. It is not a count of skills, because the same page data reports 9,704 of those, and it is not a sum of install counts, because the leaderboard's top skill alone reported 3,220,754 installs the same day. It is published here only so that nobody quotes it as a number of skills.", measuredOn: "2026-09-02", sourceId: "skills-sh", }, diff --git a/lib/seo/agent-skills-adoption/index.ts b/lib/seo/agent-skills-adoption/index.ts index 5cf1206b..7bcbe0d4 100644 --- a/lib/seo/agent-skills-adoption/index.ts +++ b/lib/seo/agent-skills-adoption/index.ts @@ -156,15 +156,15 @@ export const agentSkillsAdoption: AgentSkillsAdoptionDefinition = { title: "Agent skills adoption: the numbers", seoTitle: "Agent Skills Adoption: The Numbers | Skills Board", description: - "Dated figures on agent skills adoption, each with the source beside it: 46 clients on the official showcase, 1,297,018 skills on the skills.sh leaderboard, and server logs where AI crawlers read one site 5.9 times more often than Googlebot and bingbot.", + "Dated figures on agent skills adoption, each with the source beside it: 46 clients on the official showcase, 9,704 skills on the skills.sh all-time leaderboard, and server logs where AI crawlers read one site 5.9 times more often than Googlebot and bingbot.", intro: [ "Nobody publishes a census of agent skills. There is no registry every skill has to enter, no vendor reporting installs, and no survey of how many teams have written one. What exists instead is a set of partial counts, each measuring something narrow, each reproducible if you say plainly what it measures.", "This page collects those counts and adds three we can measure ourselves: what AI crawlers do to a small site's server logs, what the Google Search Console beta report says about impressions inside AI answers, and how one site's organic impressions moved between July and August 2026. Every figure carries the source and the day it was read.", ], answer: - "As of September 2, 2026, the Agent Skills client showcase lists 46 agent products, the reference repository anthropics/skills holds 19 skills and has not changed since August 18, and the public skills.sh directory lists 1,297,018 skills that have been installed at least once. On skillsboard.sh over the seven days to September 2, named AI crawlers made 690 requests for page content against 117 from Googlebot and bingbot combined, a ratio of 5.9 to 1.", + "As of September 2, 2026, the Agent Skills client showcase lists 46 agent products, the reference repository anthropics/skills holds 19 skills and has not changed since August 18, and the all-time leaderboard on the public skills.sh directory lists 9,704 skills, each of them installed at least once through that directory's own command line tool. On skillsboard.sh over the seven days to September 2, named AI crawlers made 690 requests for page content against 117 from Googlebot and bingbot combined, a ratio of 5.9 to 1.", answerNotes: [ - "Two of those four figures come from public pages anyone can open. The third is a leaderboard whose count depends on install telemetry, and the fourth is one site's server log. None of them is a measure of how many agent skills exist, and this page does not claim one.", + "Two of those four figures come from public pages anyone can open. The third is a leaderboard that only sees a skill once its own command line tool installs it, and the fourth is one site's server log. None of them is a measure of how many agent skills exist, and this page does not claim one.", "Skills Board is the agent-native skills registry for teams: a web application where a team keeps, searches, and shares its AI skills. The figures below come from running that site, so the search and crawler numbers describe a small site with a few dozen pages, not the market.", ], answerSourceIds: [ @@ -183,7 +183,7 @@ export const agentSkillsAdoption: AgentSkillsAdoptionDefinition = { ], steps: [ "Showcase and repository counts: fetch the published page or clone the repository, count, and record the commit or the fetch date. The anthropics/skills count is folders containing a SKILL.md under skills/, read at commit 5304866.", - "Directory count: read the total the skills.sh all-time leaderboard prints beside its own tab label. The number is theirs, the reading is ours, and its limits are stated in the section below.", + "Directory count: open the skills.sh all-time leaderboard and read the skill count its own page data reports for that view. The larger number printed beside the tab label is a different figure, and skills.sh does not say what it counts, so it is quoted below as the unlabelled total it is rather than as a number of skills.", "Crawler counts: server-side request logs from skillsboard.sh, delivered from Vercel to PostHog as one event per page-route invocation, classified by user agent string. Requests to /api/mcp and to /.well-known paths are excluded because they are protocol handshakes. Known monitoring agents and a Codex client stuck in an authentication retry loop are excluded by name.", "Search counts: the Search Console Search Analytics API for the property, queried per calendar month with dataState set to all so provisional days are included. The AI-feature impressions come from the separate Generative AI performance report, which has no API and was read in the interface on August 31, 2026.", ], @@ -198,13 +198,13 @@ export const agentSkillsAdoption: AgentSkillsAdoptionDefinition = { ecosystem: { title: "What the public sources count", intro: - "Four figures from three public sources, and none of them answers how many agent skills exist.", + "Five figures from three public sources, and none of them answers how many agent skills exist.", columns: [...datapointColumns], rows: datapointRows(ecosystemDatapoints), notes: [ "The showcase count is the most stable of the three sources and the least informative about usage: a vendor appears once its own documentation says it reads SKILL.md, and it stays there whether one user or a million use the feature. It is a supply-side number about clients, not a demand-side number about skills.", - "The leaderboard count is the opposite. It moves with real installs, which makes it the closest public proxy for how many skills are in circulation, and it is also the figure most likely to be misread. It counts a skill from the first time anyone installs it through the command line tool, so a skill that a team keeps private or shares by copying a folder never appears at all, and a fork of a popular skill appears as its own entry.", - "The reference repository is worth watching precisely because it barely moves. Anthropic publishes 19 skills there and published the same 19 two weeks earlier. Growth in this format is happening in other people's repositories, which is why a directory built on install telemetry finds more than a million entries while the first-party catalog finds nineteen.", + "The leaderboard count is the opposite. It moves with real installs, which makes it the closest public proxy for how many skills are in circulation, and it is also the figure most likely to be misread. A skill enters it the first time anyone installs it through the command line tool, so a skill that a team keeps private or shares by copying a folder never appears at all, and a fork of a popular skill appears as its own entry. The much larger total the site prints beside the tab label is not that count and is not defined anywhere it publishes, which is exactly how a directory of a few thousand skills gets quoted as a million.", + "The reference repository is worth watching precisely because it barely moves. Anthropic publishes 19 skills there and published the same 19 two weeks earlier. Growth in this format is happening in other people's repositories, which is why a directory built on install telemetry lists thousands of skills while the first-party catalog holds nineteen.", ], link: { lead: "The showcase entries are unpacked client by client in", @@ -265,7 +265,7 @@ export const agentSkillsAdoption: AgentSkillsAdoptionDefinition = { entries: [ { title: "How many agent skills exist", - body: "No source counts them. The skills.sh leaderboard counts skills installed at least once through its own command line tool, which excludes every skill kept inside a company, every skill shared as a copied folder, and every skill installed by pointing an agent at a repository directly. It also counts forks as separate entries. Treat 1,297,018 as a floor on installed-and-public skills and as no kind of answer to how many have been written.", + body: "No source counts them, and the nearest thing to one is smaller than it looks. The skills.sh all-time leaderboard listed 9,704 skills on September 2, 2026, and it sees a skill only once its own command line tool installs it, so every skill kept inside a company, every skill passed around as a copied folder, and every skill installed by pointing an agent straight at a repository is missing, while forks appear as entries of their own. The site also prints 1,297,018 beside that tab without saying what it counts, and that number has been read elsewhere as a count of skills. It is not one.", }, { title: "How many people use agent skills", @@ -313,7 +313,7 @@ export const agentSkillsAdoption: AgentSkillsAdoptionDefinition = { { question: "How many agent skills are there?", answer: - "No source counts them. The nearest public figure is the skills.sh leaderboard, which listed 1,297,018 skills on September 2, 2026, but it counts only skills installed at least once through its own command line tool and counts forks separately. Private and hand-copied skills are invisible to it.", + "No source counts them. The nearest public figure is the skills.sh all-time leaderboard, which listed 9,704 skills on September 2, 2026, counting only skills installed at least once through its own command line tool and counting forks separately. Private and hand-copied skills are invisible to it. The larger 1,297,018 that page prints is not a skill count.", }, { question: "How many products support agent skills?", @@ -364,13 +364,13 @@ export const agentSkillsAdoption: AgentSkillsAdoptionDefinition = { id: "skills-sh", label: "skills.sh leaderboard", href: "https://www.skills.sh/", - note: "The public directory run by Vercel. The all-time total printed beside its own tab label, read on September 2, 2026.", + note: "The public directory run by Vercel. Its all-time leaderboard, and the skill count and the unlabelled total that view reports, read on September 2, 2026.", }, { id: "skills-sh-api", label: "skills.sh API reference", href: "https://www.skills.sh/docs/api", - note: "States that the leaderboard counts installs and flags forks and copies with an isDuplicate field, which is why the total is a floor rather than a census.", + note: "States that the leaderboard ranks skills by install count and flags forks and copies with an isDuplicate field, and defines no field matching the total the site prints beside its tab.", }, { id: "posthog-log-drain", @@ -448,7 +448,7 @@ export const agentSkillsAdoption: AgentSkillsAdoptionDefinition = { { text: "the numbers", accent: true }, ], description: - "46 clients on the official showcase, 1,297,018 skills on the public leaderboard, and AI crawlers reading one site 5.9x more than Google and Bing.", + "46 clients on the official showcase, 9,704 skills on the public skills.sh leaderboard, and AI crawlers reading one site 5.9x more than Google and Bing.", contextLabel: "skillsboard.sh", chips: ["Sourced", "Dated", "Refreshed monthly"], footnote: "Data as of September 2, 2026", diff --git a/public/llms.txt b/public/llms.txt index a9443c90..fcf768cf 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -50,7 +50,7 @@ Markdown versions: the home page, the resources, comparisons, and alternatives h - [Best Claude skills: a curated register](https://www.skillsboard.sh/best-claude-skills): Twenty-seven register entries covering thirty-five Claude skills that cleared seven stated criteria, grouped by job, each read from its own SKILL.md with publisher, license, and where it runs, plus the nine popular candidates that were dropped and why - [Anthropic skills: the first-party catalog](https://www.skillsboard.sh/anthropic-skills): Every skill Anthropic publishes itself, in three sets: the four pre-built document skills, the nineteen folders in anthropics/skills, and the thirteen bundled with Claude Code, with what each does, where it loads, and how it is licensed - [Agent Skills: the open standard](https://www.skillsboard.sh/agent-skills): What the Agent Skills specification defines, which agents implement it and where each one looks on disk, what travels between them, and where to read real examples -- [Agent skills adoption: the numbers](https://www.skillsboard.sh/agent-skills-adoption): Dated adoption figures with the source beside each one, covering the 46 clients on the official showcase, the 19 skills in anthropics/skills, the 1,297,018 entries on the skills.sh leaderboard, the server-side split between AI crawlers and classic search crawlers on this site, and what none of those figures measures +- [Agent skills adoption: the numbers](https://www.skillsboard.sh/agent-skills-adoption): Dated adoption figures with the source beside each one, covering the 46 clients on the official showcase, the 19 skills in anthropics/skills, the 9,704 skills on the skills.sh all-time leaderboard, the server-side split between AI crawlers and classic search crawlers on this site, and what none of those figures measures - [Skill examples: real SKILL.md files](https://www.skillsboard.sh/skill-examples): Eight example skills from anthropics/skills read file by file, the pattern each one demonstrates, verbatim excerpts with their source, what all nineteen examples declare in frontmatter, and the six places the examples diverge from the specification - [Claude skills](https://www.skillsboard.sh/claude-skills): What a Claude Skill is, the SKILL.md format, where skills run, and how to install, write, and share one - [Codex skills](https://www.skillsboard.sh/codex-skills): What a Codex skill is, the .agents/skills locations Codex scans, what transfers from a Claude skill, and how to add one From df7328c5580c4a37c24c46b8cc6bf388ccb98748 Mon Sep 17 00:00:00 2001 From: "Claude (GTM agent)" Date: Wed, 2 Sep 2026 15:57:42 +0200 Subject: [PATCH 3/4] Open the adoption page on the figure, not the disclaimer Rewrite the intro and the answer-first summary so the first sentence is the crawler ratio rather than three negations about what nobody counts. The answer leads with the two contrasts (crawler ratio, impression growth) before the ecosystem counts, all still dated. The first answer note becomes a reading key instead of a second disclaimer; the limits section is unchanged. --- lib/seo/agent-skills-adoption/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/seo/agent-skills-adoption/index.ts b/lib/seo/agent-skills-adoption/index.ts index 7bcbe0d4..0cc459a6 100644 --- a/lib/seo/agent-skills-adoption/index.ts +++ b/lib/seo/agent-skills-adoption/index.ts @@ -158,13 +158,13 @@ export const agentSkillsAdoption: AgentSkillsAdoptionDefinition = { description: "Dated figures on agent skills adoption, each with the source beside it: 46 clients on the official showcase, 9,704 skills on the skills.sh all-time leaderboard, and server logs where AI crawlers read one site 5.9 times more often than Googlebot and bingbot.", intro: [ - "Nobody publishes a census of agent skills. There is no registry every skill has to enter, no vendor reporting installs, and no survey of how many teams have written one. What exists instead is a set of partial counts, each measuring something narrow, each reproducible if you say plainly what it measures.", - "This page collects those counts and adds three we can measure ourselves: what AI crawlers do to a small site's server logs, what the Google Search Console beta report says about impressions inside AI answers, and how one site's organic impressions moved between July and August 2026. Every figure carries the source and the day it was read.", + "AI crawlers read this site 5.9 times more often than Googlebot and bingbot combined. That is one of the few figures about agent skills anyone can check, because the ecosystem has no census: every number in circulation measures something narrow. This page collects the counts that exist, adds the ones we measure ourselves from server logs and Search Console, and puts the source and the reading date beside each figure.", + "So you get both halves: what the public sources count today across the showcase, the reference repository, and the skills.sh directory, and what a site publishing about agent skills sees in its own crawler logs and search reports.", ], answer: - "As of September 2, 2026, the Agent Skills client showcase lists 46 agent products, the reference repository anthropics/skills holds 19 skills and has not changed since August 18, and the all-time leaderboard on the public skills.sh directory lists 9,704 skills, each of them installed at least once through that directory's own command line tool. On skillsboard.sh over the seven days to September 2, named AI crawlers made 690 requests for page content against 117 from Googlebot and bingbot combined, a ratio of 5.9 to 1.", + "On skillsboard.sh over the seven days to September 2, 2026, named AI crawlers made 690 requests for page content against 117 from Googlebot and bingbot combined, a ratio of 5.9 to 1. Organic impressions for the same site went from 98 in July 2026 to 3,919 in August. The public counts sit beside those two: on September 2, 2026 the Agent Skills client showcase lists 46 agent products, the reference repository anthropics/skills holds 19 skills and has not changed since August 18, and the all-time leaderboard on the public skills.sh directory lists 9,704 skills, each of them installed at least once through that directory's own command line tool.", answerNotes: [ - "Two of those four figures come from public pages anyone can open. The third is a leaderboard that only sees a skill once its own command line tool installs it, and the fourth is one site's server log. None of them is a measure of how many agent skills exist, and this page does not claim one.", + "Each figure sits at a different level of checkability. The showcase and the repository are public pages anyone can open, the leaderboard only sees a skill once its own command line tool installs it, and the crawler and search figures come from one site's own instrumentation.", "Skills Board is the agent-native skills registry for teams: a web application where a team keeps, searches, and shares its AI skills. The figures below come from running that site, so the search and crawler numbers describe a small site with a few dozen pages, not the market.", ], answerSourceIds: [ From fa3ae260134849996414945ffe4d03094dd832cf Mon Sep 17 00:00:00 2001 From: "Claude (GTM agent)" Date: Wed, 2 Sep 2026 16:25:07 +0200 Subject: [PATCH 4/4] Cut the page back to public counts and rename it to /agent-skills-by-the-numbers The headline question was how adopted agent skills are, and three of the figures answered a different one. The AI crawler ratio, the organic impression series, and the Search Console AI report all measured this site, not the format, so they are removed along with their sources, their method steps, and the copy that leaned on them. What is left is one coherent page of public counts, each re-read at its source on September 2, 2026: 46 products on the client showcase and the 11 whose own documentation states it, 9,704 skills on skills.sh with the eight most installed listed by name and publisher, 19 skills in anthropics/skills at commit 5304866, 15 bundled skills in the Claude Code commands reference, and star counts for the four repositories the format is written against. The install table is new and is the strongest public adoption signal that exists for this format, so the section says plainly what install telemetry misses: private skills, copied folders, and the difference between an install and a use. The route is renamed to match the new title. Nothing was indexed under the old path, so it is removed rather than redirected, and a test asserts no trace of it survives in the app tree, llms.txt, next.config.ts, or the event union. --- analytics/posthog/events.ts | 8 +- app/agent-skills-adoption/opengraph-image.tsx | 10 - app/agent-skills-adoption/page.tsx | 50 -- app/agent-skills-adoption/twitter-image.tsx | 10 - .../layout.tsx | 4 +- .../opengraph-image.tsx | 10 + app/agent-skills-by-the-numbers/page.tsx | 50 ++ .../twitter-image.tsx | 10 + .../agent-skills-by-the-numbers-page.tsx} | 191 +++---- components/resources/resource-chrome.tsx | 6 +- lib/markdown/content-markdown.ts | 1 + lib/seo/agent-skills-adoption/datapoints.ts | 206 -------- lib/seo/agent-skills-adoption/index.ts | 474 ------------------ .../agent-skills-by-the-numbers/datapoints.ts | 245 +++++++++ lib/seo/agent-skills-by-the-numbers/index.ts | 467 +++++++++++++++++ .../types.ts | 13 +- lib/seo/agent-skills/index.ts | 6 +- lib/seo/resources.ts | 14 +- next.config.ts | 10 +- public/llms.txt | 2 +- ...s => agent-skills-by-the-numbers.test.mjs} | 267 +++++++--- 21 files changed, 1070 insertions(+), 984 deletions(-) delete mode 100644 app/agent-skills-adoption/opengraph-image.tsx delete mode 100644 app/agent-skills-adoption/page.tsx delete mode 100644 app/agent-skills-adoption/twitter-image.tsx rename app/{agent-skills-adoption => agent-skills-by-the-numbers}/layout.tsx (60%) create mode 100644 app/agent-skills-by-the-numbers/opengraph-image.tsx create mode 100644 app/agent-skills-by-the-numbers/page.tsx create mode 100644 app/agent-skills-by-the-numbers/twitter-image.tsx rename components/{agent-skills-adoption/agent-skills-adoption-page.tsx => agent-skills-by-the-numbers/agent-skills-by-the-numbers-page.tsx} (65%) delete mode 100644 lib/seo/agent-skills-adoption/datapoints.ts delete mode 100644 lib/seo/agent-skills-adoption/index.ts create mode 100644 lib/seo/agent-skills-by-the-numbers/datapoints.ts create mode 100644 lib/seo/agent-skills-by-the-numbers/index.ts rename lib/seo/{agent-skills-adoption => agent-skills-by-the-numbers}/types.ts (55%) rename tests/{agent-skills-adoption.test.mjs => agent-skills-by-the-numbers.test.mjs} (55%) diff --git a/analytics/posthog/events.ts b/analytics/posthog/events.ts index 86d8c671..7c197650 100644 --- a/analytics/posthog/events.ts +++ b/analytics/posthog/events.ts @@ -79,10 +79,10 @@ type NonTeamEventPropertiesMap = { | "cowork_skills_hero" | "cowork_skills_inline" | "cowork_skills_closing" - | "agent_skills_adoption_header" - | "agent_skills_adoption_hero" - | "agent_skills_adoption_inline" - | "agent_skills_adoption_closing" + | "agent_skills_by_the_numbers_header" + | "agent_skills_by_the_numbers_hero" + | "agent_skills_by_the_numbers_inline" + | "agent_skills_by_the_numbers_closing" | "manage_ai_skills_header" | "manage_ai_skills_hero" | "manage_ai_skills_inline" diff --git a/app/agent-skills-adoption/opengraph-image.tsx b/app/agent-skills-adoption/opengraph-image.tsx deleted file mode 100644 index d078d203..00000000 --- a/app/agent-skills-adoption/opengraph-image.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { createSocialImageResponse, OG_SIZE } from "@/lib/og/template" -import { agentSkillsAdoption } from "@/lib/seo/agent-skills-adoption" - -export const alt = agentSkillsAdoption.ogAlt -export const size = OG_SIZE -export const contentType = "image/png" - -export default function OpenGraphImage() { - return createSocialImageResponse(size, agentSkillsAdoption.og) -} diff --git a/app/agent-skills-adoption/page.tsx b/app/agent-skills-adoption/page.tsx deleted file mode 100644 index dd22b1ea..00000000 --- a/app/agent-skills-adoption/page.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type { Metadata } from "next" - -import { AgentSkillsAdoptionPage } from "@/components/agent-skills-adoption/agent-skills-adoption-page" -import { markdownTwinAlternates } from "@/lib/markdown/twins" -import { OG_SIZE, TWITTER_SIZE } from "@/lib/og/template" -import { agentSkillsAdoption } from "@/lib/seo/agent-skills-adoption" -import { siteConfig } from "@/lib/site" - -const socialTitle = "Agent skills adoption: the numbers" - -export const metadata: Metadata = { - title: { absolute: agentSkillsAdoption.seoTitle }, - description: agentSkillsAdoption.description, - alternates: markdownTwinAlternates(agentSkillsAdoption.path), - openGraph: { - type: "article", - url: agentSkillsAdoption.path, - title: socialTitle, - description: agentSkillsAdoption.description, - siteName: siteConfig.name, - locale: siteConfig.locale, - publishedTime: agentSkillsAdoption.publishedAt, - modifiedTime: agentSkillsAdoption.modifiedAt, - images: [ - { - url: `${agentSkillsAdoption.path}/opengraph-image`, - width: OG_SIZE.width, - height: OG_SIZE.height, - alt: agentSkillsAdoption.ogAlt, - }, - ], - }, - twitter: { - card: "summary_large_image", - title: socialTitle, - description: agentSkillsAdoption.description, - images: [ - { - url: `${agentSkillsAdoption.path}/twitter-image`, - width: TWITTER_SIZE.width, - height: TWITTER_SIZE.height, - alt: agentSkillsAdoption.ogAlt, - }, - ], - }, -} - -export default function Page() { - return -} diff --git a/app/agent-skills-adoption/twitter-image.tsx b/app/agent-skills-adoption/twitter-image.tsx deleted file mode 100644 index 71d03b85..00000000 --- a/app/agent-skills-adoption/twitter-image.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { createSocialImageResponse, TWITTER_SIZE } from "@/lib/og/template" -import { agentSkillsAdoption } from "@/lib/seo/agent-skills-adoption" - -export const alt = agentSkillsAdoption.ogAlt -export const size = TWITTER_SIZE -export const contentType = "image/png" - -export default function TwitterImage() { - return createSocialImageResponse(size, agentSkillsAdoption.og) -} diff --git a/app/agent-skills-adoption/layout.tsx b/app/agent-skills-by-the-numbers/layout.tsx similarity index 60% rename from app/agent-skills-adoption/layout.tsx rename to app/agent-skills-by-the-numbers/layout.tsx index 283ab12c..456259f6 100644 --- a/app/agent-skills-adoption/layout.tsx +++ b/app/agent-skills-by-the-numbers/layout.tsx @@ -1,10 +1,10 @@ import { ResourceShell } from "@/components/resources/resource-chrome" -export default function AgentSkillsAdoptionLayout({ +export default function AgentSkillsByTheNumbersLayout({ children, }: Readonly<{ children: React.ReactNode }>) { return ( - + {children} ) diff --git a/app/agent-skills-by-the-numbers/opengraph-image.tsx b/app/agent-skills-by-the-numbers/opengraph-image.tsx new file mode 100644 index 00000000..b6ac3278 --- /dev/null +++ b/app/agent-skills-by-the-numbers/opengraph-image.tsx @@ -0,0 +1,10 @@ +import { createSocialImageResponse, OG_SIZE } from "@/lib/og/template" +import { agentSkillsByTheNumbers } from "@/lib/seo/agent-skills-by-the-numbers" + +export const alt = agentSkillsByTheNumbers.ogAlt +export const size = OG_SIZE +export const contentType = "image/png" + +export default function OpenGraphImage() { + return createSocialImageResponse(size, agentSkillsByTheNumbers.og) +} diff --git a/app/agent-skills-by-the-numbers/page.tsx b/app/agent-skills-by-the-numbers/page.tsx new file mode 100644 index 00000000..e627ad82 --- /dev/null +++ b/app/agent-skills-by-the-numbers/page.tsx @@ -0,0 +1,50 @@ +import type { Metadata } from "next" + +import { AgentSkillsByTheNumbersPage } from "@/components/agent-skills-by-the-numbers/agent-skills-by-the-numbers-page" +import { markdownTwinAlternates } from "@/lib/markdown/twins" +import { OG_SIZE, TWITTER_SIZE } from "@/lib/og/template" +import { agentSkillsByTheNumbers } from "@/lib/seo/agent-skills-by-the-numbers" +import { siteConfig } from "@/lib/site" + +const socialTitle = "Agent skills by the numbers" + +export const metadata: Metadata = { + title: { absolute: agentSkillsByTheNumbers.seoTitle }, + description: agentSkillsByTheNumbers.description, + alternates: markdownTwinAlternates(agentSkillsByTheNumbers.path), + openGraph: { + type: "article", + url: agentSkillsByTheNumbers.path, + title: socialTitle, + description: agentSkillsByTheNumbers.description, + siteName: siteConfig.name, + locale: siteConfig.locale, + publishedTime: agentSkillsByTheNumbers.publishedAt, + modifiedTime: agentSkillsByTheNumbers.modifiedAt, + images: [ + { + url: `${agentSkillsByTheNumbers.path}/opengraph-image`, + width: OG_SIZE.width, + height: OG_SIZE.height, + alt: agentSkillsByTheNumbers.ogAlt, + }, + ], + }, + twitter: { + card: "summary_large_image", + title: socialTitle, + description: agentSkillsByTheNumbers.description, + images: [ + { + url: `${agentSkillsByTheNumbers.path}/twitter-image`, + width: TWITTER_SIZE.width, + height: TWITTER_SIZE.height, + alt: agentSkillsByTheNumbers.ogAlt, + }, + ], + }, +} + +export default function Page() { + return +} diff --git a/app/agent-skills-by-the-numbers/twitter-image.tsx b/app/agent-skills-by-the-numbers/twitter-image.tsx new file mode 100644 index 00000000..e8d2c738 --- /dev/null +++ b/app/agent-skills-by-the-numbers/twitter-image.tsx @@ -0,0 +1,10 @@ +import { createSocialImageResponse, TWITTER_SIZE } from "@/lib/og/template" +import { agentSkillsByTheNumbers } from "@/lib/seo/agent-skills-by-the-numbers" + +export const alt = agentSkillsByTheNumbers.ogAlt +export const size = TWITTER_SIZE +export const contentType = "image/png" + +export default function TwitterImage() { + return createSocialImageResponse(size, agentSkillsByTheNumbers.og) +} diff --git a/components/agent-skills-adoption/agent-skills-adoption-page.tsx b/components/agent-skills-by-the-numbers/agent-skills-by-the-numbers-page.tsx similarity index 65% rename from components/agent-skills-adoption/agent-skills-adoption-page.tsx rename to components/agent-skills-by-the-numbers/agent-skills-by-the-numbers-page.tsx index 72205046..84bb937c 100644 --- a/components/agent-skills-adoption/agent-skills-adoption-page.tsx +++ b/components/agent-skills-by-the-numbers/agent-skills-by-the-numbers-page.tsx @@ -12,15 +12,15 @@ import { import { ResourceBreadcrumb } from "@/components/resources/resource-breadcrumb" import { ResourceCta } from "@/components/resources/resource-chrome" import type { - AgentSkillsAdoptionDefinition, - AgentSkillsAdoptionInlineLink, - AgentSkillsAdoptionSource, -} from "@/lib/seo/agent-skills-adoption" + AgentSkillsByTheNumbersDefinition, + AgentSkillsByTheNumbersInlineLink, + AgentSkillsByTheNumbersSource, +} from "@/lib/seo/agent-skills-by-the-numbers" import { buildResourceArticleSchema } from "@/lib/seo/resource-article-schema" import { resourcePaths } from "@/lib/seo/resources" import { siteConfig } from "@/lib/site" -function InlineLink({ link }: { link: AgentSkillsAdoptionInlineLink }) { +function InlineLink({ link }: { link: AgentSkillsByTheNumbersInlineLink }) { return (

{link.lead}{" "} @@ -35,12 +35,22 @@ function InlineLink({ link }: { link: AgentSkillsAdoptionInlineLink }) { ) } -export function AgentSkillsAdoptionPage({ +export function AgentSkillsByTheNumbersPage({ entry, }: { - entry: AgentSkillsAdoptionDefinition + entry: AgentSkillsByTheNumbersDefinition }) { - const sources: readonly AgentSkillsAdoptionSource[] = entry.sources + const sources: readonly AgentSkillsByTheNumbersSource[] = entry.sources + + const sections = [ + { key: "clients", eyebrow: "01 / Clients", section: entry.clients }, + { key: "installs", eyebrow: "02 / Installs", section: entry.installs }, + { + key: "repositories", + eyebrow: "03 / Repositories", + section: entry.repositories, + }, + ] as const return ( <> @@ -57,10 +67,7 @@ export function AgentSkillsAdoptionPage({ {entry.title}

- Data as of{" "} - +

{entry.intro.map((paragraph) => ( @@ -70,7 +77,7 @@ export function AgentSkillsAdoptionPage({ ))}
- + -
- - -
    - {entry.method.steps.map((step, index) => ( -
  1. - - {String(index + 1).padStart(2, "0")} - -

    - {step} -

    -
  2. + {sections.map(({ key, eyebrow, section }) => ( +
    + + {section.tables.map((table) => ( + ))} -
- -
- -
- - - - - -
- -
- - - - -
- -
- -
- -
- - - - - -
+ + + {key === "installs" ? ( +
+ +
+ ) : null} + + + ))}
-
- - - - -
-

- 07 / Questions + 05 / Questions

Editorial method: {" "} - every figure on this page names the source it came from and the day - it was read. Public counts come from the first-party page or - repository below. Traffic and search figures come from - instrumentation on this site, described in the method section. Where - a figure cannot be reproduced, the limits section says so instead of - filling the gap. + every figure on this page comes from a source you can open, and + carries the day it was read. Nothing here is estimated, modelled, or + taken from private telemetry. Where a question cannot be answered + from a public source, the limits section says so instead of filling + the gap.