add skills to docs (#576)

This commit is contained in:
Chris Tate
2026-03-01 09:02:06 -06:00
committed by GitHub
parent 14ec5b5ffa
commit 79d8dfe34c
19 changed files with 720 additions and 292 deletions
+40
View File
@@ -0,0 +1,40 @@
import { readFile } from "fs/promises";
import { join } from "path";
import { NextRequest, NextResponse } from "next/server";
import { mdxToCleanMarkdown } from "@/lib/mdx-to-markdown";
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const docPath = searchParams.get("path");
if (!docPath) {
return NextResponse.json(
{ error: "Missing ?path= parameter" },
{ status: 400 },
);
}
const normalized = docPath
.replace(/^\//, "")
.replace(/\.\./g, "")
.replace(/[^a-zA-Z0-9/_-]/g, "");
const slug = normalized;
const filePath = slug
? join(process.cwd(), "src", "app", ...slug.split("/"), "page.mdx")
: join(process.cwd(), "src", "app", "page.mdx");
try {
const raw = await readFile(filePath, "utf-8");
const markdown = mdxToCleanMarkdown(raw);
return new NextResponse(markdown, {
headers: {
"Content-Type": "text/markdown; charset=utf-8",
"Cache-Control": "public, max-age=3600",
},
});
} catch {
return NextResponse.json({ error: "Page not found" }, { status: 404 });
}
}
+158 -115
View File
@@ -1,117 +1,112 @@
@import "tailwindcss";
@import "tw-animate-css";
@plugin "tailwindcss-animate";
@source "../../node_modules/streamdown/dist/index.js";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;
--font-mono: var(--font-geist-mono), ui-monospace, "SF Mono", "Cascadia Mono", "Segoe UI Mono", Menlo, Consolas, monospace;
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-border: var(--border);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
}
:root {
--radius: 0.5rem;
--background: oklch(1.0 0 0);
--foreground: oklch(0.1 0 0);
--card: oklch(0.98 0 0);
--card-foreground: oklch(0.1 0 0);
--popover: oklch(0.98 0 0);
--popover-foreground: oklch(0.1 0 0);
--primary: oklch(0.1 0 0);
--primary-foreground: oklch(1.0 0 0);
--secondary: oklch(0.92 0 0);
--secondary-foreground: oklch(0.1 0 0);
--muted: oklch(0.92 0 0);
--muted-foreground: oklch(0.45 0 0);
--accent: oklch(0.92 0 0);
--accent-foreground: oklch(0.1 0 0);
--destructive: oklch(0.55 0.2 25);
--destructive-foreground: oklch(1.0 0 0);
--border: oklch(0.85 0 0);
--input: oklch(0.85 0 0);
--ring: oklch(0.6 0 0);
--chat-bg: oklch(0.95 0 0);
--background: #fff;
--foreground: #171717;
--border: #e5e5e5;
--muted: #f5f5f5;
--muted-foreground: #737373;
--primary: #171717;
--primary-foreground: #fff;
}
.dark {
--background: oklch(0.0 0 0);
--foreground: oklch(0.98 0 0);
--card: oklch(0.08 0 0);
--card-foreground: oklch(0.98 0 0);
--popover: oklch(0.08 0 0);
--popover-foreground: oklch(0.98 0 0);
--primary: oklch(0.98 0 0);
--primary-foreground: oklch(0.0 0 0);
--secondary: oklch(0.15 0 0);
--secondary-foreground: oklch(0.98 0 0);
--muted: oklch(0.15 0 0);
--muted-foreground: oklch(0.6 0 0);
--accent: oklch(0.15 0 0);
--accent-foreground: oklch(0.1 0 0);
--destructive: oklch(0.65 0.2 25);
--destructive-foreground: oklch(0.98 0 0);
--border: oklch(0.25 0 0);
--input: oklch(0.25 0 0);
--ring: oklch(0.4 0 0);
--chat-bg: oklch(0.25 0 0);
--background: #0a0a0a;
--foreground: #f5f5f5;
--border: #262626;
--muted: #262626;
--muted-foreground: #a3a3a3;
--primary: #f5f5f5;
--primary-foreground: #0a0a0a;
}
@custom-variant dark (&:is(.dark *));
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--font-sans: var(--font-geist);
--font-mono: var(--font-geist-mono);
}
* {
border-color: var(--border);
}
body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-geist), system-ui, sans-serif;
}
/* Hide page scrollbar */
html {
scrollbar-width: none;
scroll-behavior: smooth;
}
html::-webkit-scrollbar {
display: none;
::selection {
background-color: #000;
color: #fff;
}
@media (prefers-color-scheme: dark) {
::selection {
background-color: #fff;
color: #000;
}
}
/* Article tables */
article table {
width: 100%;
font-size: 0.875rem;
margin-bottom: 1rem;
border-collapse: collapse;
}
article th {
border-bottom: 1px solid #e5e5e5;
padding: 0.5rem 0.75rem;
text-align: left;
font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #737373;
}
article td {
border-bottom: 1px solid #f5f5f5;
padding: 0.5rem 0.75rem;
color: #525252;
}
:is(.dark) article th {
border-bottom-color: #262626;
color: #a3a3a3;
}
:is(.dark) article td {
border-bottom-color: rgba(38, 38, 38, 0.5);
color: #a3a3a3;
}
button {
cursor: pointer;
}
/* Code blocks */
pre {
background: var(--card) !important;
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.875rem;
overflow-x: auto;
font-family: var(--font-geist-mono), monospace;
font-size: 0.8125rem;
line-height: 1.7;
}
pre:not(.shiki) {
background: var(--muted);
}
.code-block pre {
margin: 0;
}
@@ -127,17 +122,26 @@ pre {
}
}
code {
font-family: var(--font-geist-mono), monospace;
}
:not(pre) > code {
background: var(--card);
background: var(--muted);
padding: 0.125rem 0.375rem;
border-radius: 3px;
font-size: 0.875em;
}
/* Shiki dual theme support */
.shiki,
.shiki span {
color: var(--shiki-light) !important;
background-color: var(--shiki-light-bg) !important;
}
.dark .shiki,
.dark .shiki span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
}
/* Prose */
.prose {
max-width: 100%;
@@ -145,9 +149,9 @@ code {
.prose h1 {
font-size: 1.5rem;
font-weight: 500;
font-weight: 600;
letter-spacing: -0.02em;
margin-bottom: 0.5rem;
margin-bottom: 1.5rem;
color: var(--foreground);
}
@@ -158,59 +162,102 @@ code {
}
.prose h2 {
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0;
text-transform: uppercase;
color: var(--muted-foreground);
font-size: 1.125rem;
font-weight: 600;
margin-top: 3rem;
margin-bottom: 1rem;
color: var(--foreground);
}
.prose h2:first-child {
margin-top: 0;
}
.prose h3 {
font-size: 0.875rem;
font-weight: 500;
font-size: 1rem;
font-weight: 600;
margin-top: 2rem;
margin-bottom: 0.75rem;
color: var(--foreground);
opacity: 0.85;
}
.prose p {
margin-bottom: 1.25rem;
line-height: 1.7;
color: var(--muted-foreground);
margin-bottom: 1rem;
line-height: 1.65;
color: #525252;
font-size: 0.875rem;
}
:is(.dark) .prose p {
color: #a3a3a3;
}
.prose ul, .prose ol {
margin-bottom: 1.25rem;
margin-bottom: 1rem;
padding-left: 1.25rem;
}
.prose ul {
list-style-type: disc;
}
.prose ol {
list-style-type: decimal;
}
.prose li {
margin-bottom: 0.5rem;
color: var(--muted-foreground);
margin-bottom: 0.25rem;
color: #525252;
font-size: 0.875rem;
line-height: 1.6;
}
:is(.dark) .prose li {
color: #a3a3a3;
}
.prose li strong {
color: var(--foreground);
opacity: 0.85;
font-weight: 500;
}
.prose a {
color: var(--foreground);
text-decoration: underline;
text-decoration-color: #d4d4d4;
text-underline-offset: 2px;
}
.prose a:hover {
text-decoration-color: var(--foreground);
}
:is(.dark) .prose a {
text-decoration-color: #525252;
}
:is(.dark) .prose a:hover {
text-decoration-color: var(--foreground);
}
.prose strong {
font-weight: 500;
color: var(--foreground);
}
.prose blockquote {
margin-bottom: 1rem;
border-left: 2px solid #e5e5e5;
padding-left: 1rem;
font-size: 0.875rem;
color: #737373;
}
:is(.dark) .prose blockquote {
border-left-color: #525252;
color: #a3a3a3;
}
.prose table {
width: 100%;
border-collapse: collapse;
@@ -284,7 +331,3 @@ code {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
button {
cursor: pointer;
}
+20 -19
View File
@@ -1,18 +1,19 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Inter, Geist_Mono } from "next/font/google";
import { GeistPixelSquare } from "geist/font/pixel";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
import { MobileNavProvider } from "@/components/mobile-nav-context";
import { Header } from "@/components/header";
import { Sidebar } from "@/components/sidebar";
import { DocsSidebar } from "@/components/docs-sidebar";
import { DocsMobileNav } from "@/components/docs-mobile-nav";
import { CopyPageButton } from "@/components/copy-page-button";
import { DocsChat } from "@/components/docs-chat";
import { cookies } from "next/headers";
import { SpeedInsights } from "@vercel/speed-insights/next";
import { Analytics } from "@vercel/analytics/next";
const geist = Geist({
variable: "--font-geist",
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
});
@@ -66,23 +67,23 @@ export default async function RootLayout({
)}
</head>
<body
className={`${geist.variable} ${geistMono.variable} ${GeistPixelSquare.variable} antialiased bg-background text-foreground`}
className={`${inter.variable} ${geistMono.variable} ${GeistPixelSquare.variable} bg-white text-neutral-900 antialiased dark:bg-neutral-950 dark:text-neutral-100`}
>
<ThemeProvider>
<MobileNavProvider>
<Header />
<div className="flex min-h-[calc(100vh-3.5rem)]">
<Sidebar />
<main className="flex-1 overflow-auto">
<div className="max-w-2xl mx-auto px-4 sm:px-6 py-8 sm:py-12">
<div className="prose">
{children}
</div>
</div>
</main>
<Header />
<DocsMobileNav />
<div className="max-w-5xl mx-auto px-6 py-8 lg:py-12 flex gap-16">
<aside className="w-48 shrink-0 hidden lg:block sticky top-28 h-[calc(100vh-7rem)] overflow-y-auto">
<DocsSidebar />
</aside>
<div className="flex-1 min-w-0 max-w-2xl pb-20">
<div className="flex justify-end mb-4">
<CopyPageButton />
</div>
<article className="prose">{children}</article>
</div>
<DocsChat defaultOpen={chatOpen} defaultWidth={chatWidth} />
</MobileNavProvider>
</div>
<DocsChat defaultOpen={chatOpen} defaultWidth={chatWidth} />
</ThemeProvider>
<SpeedInsights />
<Analytics />
+60
View File
@@ -0,0 +1,60 @@
import { pageMetadata } from "@/lib/page-metadata"
export const metadata = pageMetadata("skills")
# Skills
agent-browser ships with skills that teach AI coding agents how to use it for specific workflows. Install a skill and your agent in Cursor, Claude Code, or Codex can automate browser tasks without manual guidance.
## Available Skills
- **agent-browser** — General browser automation: navigation, snapshots, forms, screenshots, data extraction, sessions, authentication, diffing, and the full command reference.
- **dogfood** — Systematic exploratory testing. Navigates an app like a real user, finds bugs and UX issues, and produces a structured report with screenshots and repro videos.
- **electron** — Automate any Electron app (VS Code, Slack, Discord, Figma, etc.) by connecting to its built-in Chrome DevTools Protocol port. This is how agent-browser drives native desktop apps like the Slack macOS app.
- **slack** — Browser-based Slack automation. Check unreads, navigate channels, search conversations, send messages, and extract data — no API tokens needed.
## Installation
```bash
npx skills add vercel-labs/agent-browser --skill agent-browser
npx skills add vercel-labs/agent-browser --skill dogfood
npx skills add vercel-labs/agent-browser --skill electron
npx skills add vercel-labs/agent-browser --skill slack
```
After installing, your AI agent will automatically activate the right skill when it encounters a matching request.
## agent-browser
The core skill. Teaches agents the full agent-browser API: the navigate-snapshot-interact-re-snapshot workflow, all commands, command chaining, authentication (auth vault and state persistence), sessions, diffing, JavaScript evaluation, annotated screenshots, semantic locators, and configuration.
Example agent interactions:
- "Open example.com and fill out the contact form"
- "Take a screenshot of the dashboard after logging in"
- "Compare staging and production versions of the homepage"
## dogfood
A structured workflow for exploratory testing. The agent opens a target URL, systematically explores the app (navigating pages, testing forms, clicking buttons, checking console errors), and documents every issue it finds with:
- Numbered repro steps
- Step-by-step screenshots
- Repro videos for interactive bugs
- Severity classification
The output is a markdown report in an output directory, ready to hand to the responsible team. Run it with a single prompt like "dogfood vercel.com" or "QA http://localhost:3000 — focus on the billing page".
## electron
Electron apps (VS Code, Slack, Discord, Figma, Notion, Spotify, etc.) are built on Chromium and expose a Chrome DevTools Protocol (CDP) port that agent-browser can connect to. This skill teaches agents how to launch or connect to any Electron app, then use the standard snapshot-interact workflow to automate it.
Electron apps are built on Chromium, so they expose a Chrome DevTools Protocol (CDP) port that agent-browser can connect to. Launch the app with `--remote-debugging-port`, connect, and use the standard snapshot-interact workflow. This is the foundation that the **slack** skill builds on.
## slack
Browser-based Slack automation. Connects to an existing Slack session (via `agent-browser connect 9222`) or opens Slack in a new browser, then uses snapshots and element refs to navigate the UI. Covers checking unreads, navigating channels and DMs, searching conversations, extracting message data, and taking screenshots — all without needing Slack API tokens or bot setup.
## Source
All skill files are in the [`skills/`](https://github.com/vercel-labs/agent-browser/tree/main/skills) directory of the repository.