From 61534975002fe90cbde4ddd197f76f9f0d98b02e Mon Sep 17 00:00:00 2001 From: Pedro Rodrigues <44656907+Rodriguespn@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:18:54 +0000 Subject: [PATCH] docs(skills): agent skills (#42148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Docs update — Rewrite the Agent Skills documentation as a single flat page with a skills table. ## What is the current behavior? The Agent Skills docs have a nested structure with an index page and individual dynamic pages for each skill (fetched from the `supabase/agent-skills` repo). Skills also inject sub-items into the sidebar navigation. ## What is the new behavior? This PR replaces the nested skill pages with a single, flat Agent Skills page that: - Lists all skills in a **table** with name, description, and a **copy-to-install button** (fetched dynamically from the [supabase/agent-skills](https://github.com/supabase/agent-skills) repo) - Includes **installation commands** for both the skills CLI (`npx skills add`) and Claude Code plugins - Links skill names directly to their source on GitHub instead of rendering full skill content inline - Removes the dynamic `[slug]` route, sidebar nav injection, and local skill example files ### Navigation Structure ``` Start > AI Tools > Agent Skills (new) > Prompts (existing) > Supabase MCP server (existing) ``` Closes [AI-361](https://linear.app/supabase/issue/AI-361/create-skills-documentation-page-in-ai-tooling-docs) --------- Co-authored-by: Claude Sonnet 4.5 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ai-skills/AiSkills.utils.ts | 103 ++++++++++++++++++ .../ai-skills/AiSkillsIndex.tsx | 60 ++++++++++ .../getting-started/ai-skills/CopyButton.tsx | 29 +++++ .../app/guides/getting-started/layout.tsx | 4 +- apps/docs/components/Breadcrumbs.tsx | 12 ++ .../NavigationMenu.constants.ts | 4 + .../guides/getting-started/ai-skills.mdx | 48 ++++++++ apps/docs/features/docs/MdxBase.shared.tsx | 2 + supa-mdx-lint/Rule001HeadingCase.toml | 1 + 9 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 apps/docs/app/guides/getting-started/ai-skills/AiSkills.utils.ts create mode 100644 apps/docs/app/guides/getting-started/ai-skills/AiSkillsIndex.tsx create mode 100644 apps/docs/app/guides/getting-started/ai-skills/CopyButton.tsx create mode 100644 apps/docs/content/guides/getting-started/ai-skills.mdx diff --git a/apps/docs/app/guides/getting-started/ai-skills/AiSkills.utils.ts b/apps/docs/app/guides/getting-started/ai-skills/AiSkills.utils.ts new file mode 100644 index 00000000000..1ed1a7ca49b --- /dev/null +++ b/apps/docs/app/guides/getting-started/ai-skills/AiSkills.utils.ts @@ -0,0 +1,103 @@ +import matter from 'gray-matter' +import { cache } from 'react' + +const SKILLS_REPO = { + org: 'supabase', + repo: 'agent-skills', + branch: 'main', + path: 'skills', +} + +interface SkillMetadata { + name?: string + title?: string + description?: string +} + +interface SkillSummary { + name: string + description: string + installCommand: string +} + +interface GitHubContentItem { + name: string + path: string + type: 'file' | 'dir' +} + +async function fetchGitHubDirectory(path: string): Promise { + const url = `https://api.github.com/repos/${SKILLS_REPO.org}/${SKILLS_REPO.repo}/contents/${path}?ref=${SKILLS_REPO.branch}` + + let response: Response + try { + response = await fetch(url, { + headers: { + Accept: 'application/vnd.github.v3+json', + 'User-Agent': 'Supabase-Docs', + }, + next: { revalidate: 3600 }, + }) + } catch (err) { + throw new Error(`Failed to fetch agent skills directory from GitHub (network error)`, { + cause: err, + }) + } + + if (!response.ok) { + throw new Error( + `Failed to fetch agent skills directory from GitHub: ${response.status} ${response.statusText}` + ) + } + + const data = await response.json() + if (!Array.isArray(data)) { + throw new Error('Expected directory listing from GitHub agent skills repo') + } + + return data +} + +async function fetchGitHubFile(path: string): Promise { + const url = `https://raw.githubusercontent.com/${SKILLS_REPO.org}/${SKILLS_REPO.repo}/${SKILLS_REPO.branch}/${path}` + + let response: Response + try { + response = await fetch(url, { + next: { revalidate: 3600 }, + }) + } catch (err) { + throw new Error(`Failed to fetch agent skill file from GitHub (network error)`, { cause: err }) + } + + if (!response.ok) { + throw new Error( + `Failed to fetch agent skill file from GitHub: ${response.status} ${response.statusText}` + ) + } + + return await response.text() +} + +async function getAiSkillsImpl(): Promise { + const directories = await fetchGitHubDirectory(SKILLS_REPO.path) + const skillDirs = directories.filter((item) => item.type === 'dir') + + const skills = await Promise.all( + skillDirs.map(async (item) => { + const skillPath = `${SKILLS_REPO.path}/${item.name}/SKILL.md` + const rawContent = await fetchGitHubFile(skillPath) + const { data } = matter(rawContent) as { data: SkillMetadata } + + return { + name: item.name, + description: data.description || '', + installCommand: `npx skills add supabase/agent-skills --skill ${item.name}`, + } + }) + ) + + return skills.sort((a, b) => a.name.localeCompare(b.name)) +} + +export const getAiSkills = cache(getAiSkillsImpl) diff --git a/apps/docs/app/guides/getting-started/ai-skills/AiSkillsIndex.tsx b/apps/docs/app/guides/getting-started/ai-skills/AiSkillsIndex.tsx new file mode 100644 index 00000000000..1f5311a563c --- /dev/null +++ b/apps/docs/app/guides/getting-started/ai-skills/AiSkillsIndex.tsx @@ -0,0 +1,60 @@ +import { getAiSkills } from './AiSkills.utils' +import { CopyButton } from './CopyButton' + +export async function AiSkillsIndex() { + let skills: Awaited> = [] + + try { + skills = await getAiSkills() + } catch { + // Swallow errors from getAiSkills to keep the page usable + } + + if (!skills.length) { + return ( +
+ Unable to load AI skills at the moment. +
+ ) + } + return ( +
+ + + + + + + + + + {skills.map((skill) => ( + + + + + + ))} + +
SkillDescriptionInstall command
+ + {skill.name} + + {skill.description} +
+
+ + + {skill.installCommand} + +
+
+
+
+ ) +} diff --git a/apps/docs/app/guides/getting-started/ai-skills/CopyButton.tsx b/apps/docs/app/guides/getting-started/ai-skills/CopyButton.tsx new file mode 100644 index 00000000000..1f974067655 --- /dev/null +++ b/apps/docs/app/guides/getting-started/ai-skills/CopyButton.tsx @@ -0,0 +1,29 @@ +'use client' + +import { useState } from 'react' +import { Check, Copy } from 'lucide-react' +import { cn } from 'ui' + +export function CopyButton({ text }: { text: string }) { + const [copied, setCopied] = useState(false) + + const handleCopy = async () => { + await navigator.clipboard.writeText(text) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + + return ( + + ) +} diff --git a/apps/docs/app/guides/getting-started/layout.tsx b/apps/docs/app/guides/getting-started/layout.tsx index ba83678f3b7..431edbe4588 100644 --- a/apps/docs/app/guides/getting-started/layout.tsx +++ b/apps/docs/app/guides/getting-started/layout.tsx @@ -3,7 +3,9 @@ import Layout from '~/layouts/guides' import { getAiPrompts } from '../getting-started/ai-prompts/[slug]/AiPrompts.utils' export default async function GettingStartedLayout({ children }: { children: React.ReactNode }) { - const additionalNavItems = { prompts: await getPrompts() } + const additionalNavItems = { + prompts: await getPrompts(), + } return {children} } diff --git a/apps/docs/components/Breadcrumbs.tsx b/apps/docs/components/Breadcrumbs.tsx index 2f04f9c1869..b9bde64e769 100644 --- a/apps/docs/components/Breadcrumbs.tsx +++ b/apps/docs/components/Breadcrumbs.tsx @@ -176,6 +176,18 @@ function useBreadcrumbs() { return breadcrumbs } + // TODO: Breadcrumbs currently can't infer the "AI Tools" parent for /guides/getting-started/ai-* routes, + // so we special-case these paths here. Remove when Breadcrumbs can derive this hierarchy from NavigationMenu. + const isAiSkillsPage = pathname.startsWith('/guides/getting-started/ai-skills') + if (isAiSkillsPage) { + const breadcrumbs = [ + { name: 'Getting started', url: '/guides/getting-started' }, + { name: 'AI Tools' }, + { name: 'Agent Skills', url: '/guides/getting-started/ai-skills' }, + ] + return breadcrumbs + } + const menuId = getMenuId(pathname) const menu = NavItems[menuId] return findMenuItemByUrl(menu, pathname, []) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 37a38306f94..6dd2fadc00c 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -499,6 +499,10 @@ export const gettingstarted: NavMenuConstant = { name: 'Prompts', url: '/guides/getting-started/ai-prompts' as `/${string}`, }, + { + name: 'Agent Skills', + url: '/guides/getting-started/ai-skills' as `/${string}`, + }, { name: 'Supabase MCP server', url: '/guides/getting-started/mcp' as `/${string}`, diff --git a/apps/docs/content/guides/getting-started/ai-skills.mdx b/apps/docs/content/guides/getting-started/ai-skills.mdx new file mode 100644 index 00000000000..1552d6ce8ca --- /dev/null +++ b/apps/docs/content/guides/getting-started/ai-skills.mdx @@ -0,0 +1,48 @@ +--- +title: Agent Skills +--- + +Agent Skills are folders of instructions, scripts, and resources that agents can discover and use to do things more accurately and efficiently. Agents are increasingly capable, but often don't have the context they need to do real work reliably. Skills solve this by giving agents access to procedural knowledge and company-, team-, and user-specific context they can load on demand. Agents with access to a set of skills can extend their capabilities based on the task they're working on. + +## Installing skills + +Install all Supabase skills using the skills CLI: + +```bash +npx skills add supabase/agent-skills +``` + +To install a specific skill from the repository: + +```bash +npx skills add supabase/agent-skills --skill SKILL_NAME +``` + +### Claude Code plugin + +You can also install the skills as Claude Code plugins: + +```bash +/plugin marketplace add supabase/agent-skills +/plugin install postgres-best-practices@supabase-agent-skills +``` + +Skills work with 18+ AI agents including Claude Code, GitHub Copilot, Cursor, Cline, and many others. + +## Available skills + + + +## Finding more skills + +Browse the [skills.sh directory](https://skills.sh) to discover skills from the community. You can also search for skills using the CLI: + +```bash +npx skills find QUERY +``` + +## Learn more + +- [Agent Skills Repository](https://github.com/supabase/agent-skills) +- [Agent Skills Documentation](https://agentskills.io/home) +- [Agent Skills Overview](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) diff --git a/apps/docs/features/docs/MdxBase.shared.tsx b/apps/docs/features/docs/MdxBase.shared.tsx index 3502b92392d..3a1484efc4f 100644 --- a/apps/docs/features/docs/MdxBase.shared.tsx +++ b/apps/docs/features/docs/MdxBase.shared.tsx @@ -7,6 +7,7 @@ import { IconPanel } from 'ui-patterns/IconPanel' import SqlToRest from 'ui-patterns/SqlToRest' import { Heading } from 'ui/src/components/CustomHTMLElements' import { AiPromptsIndex } from '~/app/guides/getting-started/ai-prompts/[slug]/AiPromptsIndex' +import { AiSkillsIndex } from '~/app/guides/getting-started/ai-skills/AiSkillsIndex' import { AppleSecretGenerator } from '~/components/AppleSecretGenerator' import AuthProviders from '~/components/AuthProviders' import { AuthSmsProviderConfig } from '~/components/AuthSmsProviderConfig' @@ -44,6 +45,7 @@ const components = { AccordionItem, Admonition: AdmonitionWithMargin, AiPromptsIndex, + AiSkillsIndex, AuthSmsProviderConfig, AppleSecretGenerator, AuthProviders, diff --git a/supa-mdx-lint/Rule001HeadingCase.toml b/supa-mdx-lint/Rule001HeadingCase.toml index bacce926e18..531d6003b70 100644 --- a/supa-mdx-lint/Rule001HeadingCase.toml +++ b/supa-mdx-lint/Rule001HeadingCase.toml @@ -37,6 +37,7 @@ may_uppercase = [ "Clerk", "Cloudflare", "Cloudflare Workers?", + "Claude Code", "Code Exchange", "Colab", "Compute",