mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
docs(skills): agent skills (#42148)
## 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 <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
9 files changed
+262
-1
No files matched your search
@@ -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<GitHubContentItem[]> {
|
||||
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<string> {
|
||||
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<SkillSummary[]> {
|
||||
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)
|
||||
@@ -0,0 +1,60 @@
|
||||
import { getAiSkills } from './AiSkills.utils'
|
||||
import { CopyButton } from './CopyButton'
|
||||
|
||||
export async function AiSkillsIndex() {
|
||||
let skills: Awaited<ReturnType<typeof getAiSkills>> = []
|
||||
|
||||
try {
|
||||
skills = await getAiSkills()
|
||||
} catch {
|
||||
// Swallow errors from getAiSkills to keep the page usable
|
||||
}
|
||||
|
||||
if (!skills.length) {
|
||||
return (
|
||||
<div className="not-prose text-sm text-foreground-lighter">
|
||||
Unable to load AI skills at the moment.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="not-prose overflow-x-auto">
|
||||
<table className="w-full text-sm border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b border-default">
|
||||
<th className="text-left py-2 pr-4 text-foreground-lighter font-medium">Skill</th>
|
||||
<th className="text-left py-2 pr-4 text-foreground-lighter font-medium">Description</th>
|
||||
<th className="text-left py-2 text-foreground-lighter font-medium">Install command</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{skills.map((skill) => (
|
||||
<tr key={skill.name} className="border-b border-default">
|
||||
<td className="py-3 pr-4 font-mono text-xs whitespace-nowrap">
|
||||
<a
|
||||
href={`https://github.com/supabase/agent-skills/tree/main/skills/${skill.name}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-foreground hover:text-brand transition-colors"
|
||||
>
|
||||
{skill.name}
|
||||
</a>
|
||||
</td>
|
||||
<td className="py-3 pr-4 text-foreground-lighter">{skill.description}</td>
|
||||
<td className="w-px p-0">
|
||||
<div className="h-full max-w-xs overflow-x-auto flex items-center py-3">
|
||||
<div className="flex items-center gap-2 whitespace-nowrap">
|
||||
<CopyButton text={skill.installCommand} />
|
||||
<code className="font-mono text-xs text-foreground-lighter">
|
||||
{skill.installCommand}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className={cn(
|
||||
'inline-flex items-center rounded-md border p-1.5 transition-colors',
|
||||
'border-default bg-surface-100 text-foreground-lighter hover:bg-surface-200 hover:text-foreground'
|
||||
)}
|
||||
title={copied ? 'Copied!' : 'Copy to clipboard'}
|
||||
>
|
||||
{copied ? <Check size={14} /> : <Copy size={14} />}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -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 <Layout additionalNavItems={additionalNavItems}>{children}</Layout>
|
||||
}
|
||||
|
||||
@@ -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, [])
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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
|
||||
|
||||
<AiSkillsIndex />
|
||||
|
||||
## 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)
|
||||
@@ -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,
|
||||
|
||||
@@ -37,6 +37,7 @@ may_uppercase = [
|
||||
"Clerk",
|
||||
"Cloudflare",
|
||||
"Cloudflare Workers?",
|
||||
"Claude Code",
|
||||
"Code Exchange",
|
||||
"Colab",
|
||||
"Compute",
|
||||
|
||||
Reference in new issue
Block a user